IO流

发布时间 2023-08-15 15:27:41作者: 未翻身的咸鱼

IO流

IO概述和分类

IO流概述

  • IO流可以将数据写到文件中,实现数据永久化存储
  • 读取文件中已经存在的数据
  • 输入与输出相对于内存来说:从内存提出来时输出,存入内存时输入。

I :表示input,是数据从硬盘到内存的过程,称之为读,读是读到内存。
O :表示output,是数据从内存到硬盘的过程,称之为写,写为写到次磁盘。

过程:数据的流动,内存在读,内存在写。

IO流分类

IO流相关知识

  • 纯文本文件:用windows记事本打开能读懂(可以用记事本代开,并且能读懂的文件),那么这样的文件就是纯文本文件。

  • doc、excel(office)等可以用字符流操作

IO流具体分类和使用场景

按数据的流向

  • 输入流:读数据

  • 输出流:写数据

按数据类型来分

  • 字节流

    • 字节流输入

    • 字节流输出

  • 字符流

    • 字符流入流

    • 字符流出流

使用场景

  • 如果操作的是纯文本文件(可以使用记事本打开),优先使用字符流

  • 如果操作的是图片、视频、音频等二进制文件,优先使用字节流

  • 如果不确定文件类型,优先使用字节流,字节流是万能的流。

字节流

字节流的创建


(右侧写入出应为:outputStream)

  • 字节流抽象基类

    • InputStream:这个抽象类是表示字节输入流的所有类的超类。

    • OutputStream:这个抽象类是表示字节输出流的所有类的超类。

    • 子类名特点:子类名称都是以其父类名作为子类名的后缀。

  • 字节输入/出流

    • FileInputStream(String name): 创建文件输入流以指定的名称读取文件。

    • FileOutputStream(String name):创建文件输出流以指定的名称写入文件。

案例:往images文件夹中的a.txt文件中写入数据
往images文件夹中的a.txt文件中写入数据(只能写英文,中文乱码)

package IO.Test01;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class OutputStream01 {
    public static void main(String[] args) throws IOException {
        //1.创建文件输出流对象
        FileOutputStream fileOutputStream = new FileOutputStream("D:\\Test01\\InputStreamTest01");
        //2.写出内容
        fileOutputStream.write(98);
        fileOutputStream.write(97);
        fileOutputStream.write(96);
        fileOutputStream.write('I');
        fileOutputStream.close();
    }
}

字节流写数据步骤

  1. 创建字节输出对象。
    注意事项:
    如果文件不存在,就创建

  2. 写数据
    注意事项:
    写出的整数,实际上写道文件中,是在ASCII码表中的那个字符。

  3. 释放资源
    注意事项:
    每次使用完流必须要释放资源。

字节流写入数据的3种方法

void write(int b): 一次写一个字节数据

void write(byte[] b): 一次写一个字节数组数据

void write(byte[] b,int off,int len):一次写一个字节数组的部分数据,从off索引开始,写len长度,

示例代码
package IO.Test01;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileOutPutStream02 {
    public static void main(String[] args) throws IOException {
        //创建文件输出对象
        FileOutputStream fileOutputStream = new FileOutputStream("D:\\Test01\\InputStreamTest01");
        byte a = 10;
        fileOutputStream.write(a);
        byte[] bytes = {1, 2, 3, 4};
        fileOutputStream.write(bytes);
        
        fileOutputStream.write(bytes, 1, 2);//bytes数组,索引从1开始,存入2个长度
    }
}

  • 注意:当复制图片或者其他文件时,如果输入流用到了字节数组,那么输出流也必须用字节数组(方法中含有byte[]的),不然会复制0字节,导致复制失败。

字节流写数据的三个问题(换行、追加写入、写入字符串)

换行

字节流写数据如何实现换行呢?

  • 写完数据后,加换行符
    windows:\r\n 单写\r \n 也可以
    linux:\n
    mac:\r
实例代码
package IO.Test01;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileOutputStream03 {
    public static void main(String[] args) throws IOException {
        //1.创建文件输出流对象
        FileOutputStream fileOutputStream = new FileOutputStream("D:\\Test01\\InputStreamTest01.txt");
        //2.写出内容
        byte[] bytes ={97,98,99,100,101};
        fileOutputStream.write(bytes);
        //加入换行符 getBytes()能够在字符串转为byte类型
        fileOutputStream.write("\r\n".getBytes());
        //存入bytes数组,索引从off开始,存入len个长度
        fileOutputStream.write(bytes,1,3);
        fileOutputStream.write("\r\n".getBytes());
        //3.关闭流
        fileOutputStream.close();
    }
}

追加写入

字节流写数据如何实现追加写入呢?

  • public FileOutputStream​(String name,boolean append):append追加,ture代表追加,false代表不追加。该方法默认为false,不开启追加。

  • 创建文件输出流以指定的名称写入文件。如果第二个参数为true ,不会清空文件里面的内容

实例代码
package IO.Test01;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileOutputStream04 {
    public static void main(String[] args) {
        //1.创建对象
        FileOutputStream fileOutputStream = null;
        try {
            File file = new File("D:\\Test01\\InputStreamTest01.txt");
            //第一次捕获异常
            fileOutputStream = new FileOutputStream(file,true);//也可以直接写路径,但是船舰File对象好处是,可以进行非空判断
            if (file != null && file.length() >= 0) {
                //2.写数据
                String str = "energy in my heart";
                //3.将字符串转为字节类型数组 调用getbytes()方法
                 fileOutputStream.write(str.getBytes());//第二次捕获异常
            }


        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                fileOutputStream.close();//第三次捕获异常
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}

写入字符串

字节流写入字符串怎么写?

  • 利用getByte()方法,将字符串转换位byte类型存入
实例代码
package IO.Test01;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileOutputStream04 {
    public static void main(String[] args) {
        //1.创建对象
        FileOutputStream fileOutputStream = null;
        try {
            File file = new File("D:\\Test01\\InputStreamTest01.txt");
            //第一次捕获异常
            fileOutputStream = new FileOutputStream(file,true);//也可以直接写路径,但是船舰File对象好处是,可以进行非空判断
            if (file != null && file.length() >= 0) {
                //2.写数据
                String str = "energy in my heart";
                //3.将字符串转为字节类型数组 调用getbytes()方法
                 fileOutputStream.write(str.getBytes());//第二次捕获异常
            }


        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                fileOutputStream.close();//第三次捕获异常
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}

字节流写数据加try…catch异常处理

  • 思考:那么我们如何操作才能让close方法一定执行呢?
  • finally:在异常处理时提供finally块来执行所有清除操作。比如说IO流中的释放资源
    特点:被finally控制的语句一定会执行,除非JVM退出
    异常处理标准格式:try….catch…finally
package IO.Test01;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class TryCatch {
    public static void main(String[] args) {
        //创建文件输出流对象
        // 第一次报错创建输出/处流时报错,直接捕获异常即可
        //在外层创建对象,防止因为作用域(在try种创建对象,那么就只能在被try包围的大括号内调用)导致对象不能被调用
        FileOutputStream fo = null;
        try {
            fo = new FileOutputStream("D:\\Test01");
            //2.写数据
            fo.write(98);//第二次报错,追加catch即可
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {//手动追加final,无论是否出错,都要关闭流
            try {
                fo.close();//第三次报错,直接捕获异常即可
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

小结

字节流读数据 一次读一个字节
步骤:

  1. 创建字节输出流对象
    文件不存在,就创建。 注意:如果文件不存在,就直接报错。
    文件存在就清空。如果不想被清空则加true

  2. 写数据
    可以写一个字节,写一个字节数组,写一个字节数组的一部分
    写一个回车换行:\r\n 注意:读出来的是文件中数据的ASCII码表值。

  3. 释放资源 注意事项:每次使用完流必须要释放资源。

字节流读取数据

方法名 说明
int read​() 一次读取一个字节数据,每读取一次,索引向下移动一。
int read​(byte[] b) 一次读一个字节组数据(通常数组长度为1024,1024b=1kb,可以提高输入输出效率。),把数据封装到参数b中,返回值为本次读取到的字节个数,b默认值为0
  • 读取到最后返回值为:-1
int read​()实例代码
package IO.Test01;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class FileInputStream01 {
    public static void main(String[] args) {
        FileInputStream fileInputStream = null;
        try {
            //第一次捕获异常
            fileInputStream = new FileInputStream("D:\\Test01\\InputStreamTest01.txt");
            
            //实例1
            int read = fileInputStream.read();//第二次捕获异常,捕获第一个字节,然后使索引加1
            System.out.println((char) read);//是的read输出为char型
            //最佳使用方法while循环遍历
            int len;
            while ((len = fileInputStream.read()) != -1) {
                System.out.println(len);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                fileInputStream.close();//第三次捕获异常
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}

int read​(byte[] b)实例代码
package IO.Test01;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileInputStream02 {
    public static void main(String[] args) {
        FileInputStream fileInputStream = null;//输入
        FileOutputStream fileOutputStream = null;//输出
        try {
            fileInputStream = new FileInputStream("D:\\Test01\\InputStreamTest01.txt");//输入
            fileOutputStream = new FileOutputStream("D:\\Test01\\InputStreamTest02.txt");//输出
//            byte[] bytes = {97, 98, 99};
//            int read = fileInputStream.read(bytes);//read(byte[] b)返回读取到的字节数
//            System.out.println(read);//3
//            System.out.println(fileInputStream.read());//100,读取的第四个字节,每次跨国len个字节,len为byte[]的长度

            //文件之间的拷贝
            byte[] bytes = new byte[1024];//1024b=1kb
            int len;
            // int read(byte[] b) : 一次读一个字节组数据(通常数组长度为1024),把数据封装到参数b中,返回值为本次读取到的字节个数,b默认值为0
            while ((len = fileInputStream.read(bytes)) != -1) {//fileInputStream.read(bytes)会将得到的数据封装到bytes中
                fileOutputStream.write(bytes, 0, len);
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                fileOutputStream.close();
                fileInputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}


  • 注意:read()方法在一次使用里面最好只调用一次

  • 注意:当复制图片或者其他文件时,如果输入流用到了字节数组,那么输出流也必须用字节数组(方法中含有byte[]的),不然会复制0字节,导致复制失败。

字节流读取数据步骤

  • 创建字节输入流对象。
    注意事项:
    如果文件存在则读取。

  • 读取数据
    注意事项:
    读取的是字节的ASCII码需要转换为char类型
    读取中文会乱码。

  • 释放资源
    注意事项:
    每次使用完流必须要释放资源。

字节缓冲流

类似于看视频的时候,先将视频缓冲一下,即使网速慢,也可以向看缓冲的一部分。

  • 字节缓冲流:

    • BufferOutputStream:缓冲输出流

    • BufferedInputStream:缓冲输入流

  • 构造方法:

    • 字节缓冲输出流:BufferedOutputStream​(OutputStream out)

    • 字节缓冲输入流:BufferedInputStream​(InputStream in)

示例代码
package IO.BufferedOutStream;

import java.io.*;

public class BufferedOutputStream01 {

    public static void main(String[] args) {
        //创建 字节缓冲流对象
        BufferedOutputStream bos = null;
        BufferedInputStream bis = null;
        try {
            bos = new BufferedOutputStream(new FileOutputStream("D:\\Test01\\InputStreamTest01.txt"));
            bis = new BufferedInputStream(new FileInputStream("D:\\Test01\\InputStreamTest01.txt"));
            //实现对拷操作
            byte[] bytes = new byte[1024];
            int len;
            while ((len = bis.read(bytes)) != -1) {
                //将数据写出到file文件中
                bos.write(bytes, 0, len);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                bis.close();
                bos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

字节缓冲流复制多级文件夹(有点小难)
package Homework.IO;

import java.io.*;
import java.nio.file.Files;

//把一个文件夹目录   拷贝到项目模块目录下。(要求:文件夹及其子文件全部拷贝)

//尝试使用缓冲流完成这个copy操作。
public class Demo03 {
    public static void main(String[] args) throws IOException {
        File srcFile = new File("D:\\Test01");
        File destFile = new File("D:\\IOTest");
        create(srcFile, destFile);
    }

    public static void create(File srcFile, File destFile) throws IOException {
        //创建输入输出流
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        File[] files = srcFile.listFiles();//获取源路径下的所有路径名
        for (File file : files) {//循环遍历源路径下的所有一级文件
            if (file.isDirectory()) {
                String name = srcFile.getName();//获取目标名
                File file2 = new File(destFile.getPath() + File.separator + file.getName());//防止拒绝访问
                if (!file2.exists()) {//判断该文件夹是否存在
                    //如果不存在则创建该文件夹
                    file2.mkdir();
                    //递归调用该方法,继续复制
                    create(file, file2);
                }
            } else {
                //如果不是文件夹,则复制文件
                try {//创建输入输出流
                    File newFile = new File(destFile.getPath() + File.separator + file.getName());//防止拒绝访问
                    bis = new BufferedInputStream(new FileInputStream(file));
                    bos = new BufferedOutputStream(new FileOutputStream(newFile));
                    //写数据
                    int len;
                    byte[] bytes = new byte[1024];
                    while ((len = bis.read(bytes)) != -1) {
                        bos.write(bytes);
                    }
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    try {
                        bos.flush();
                        bos.close();
                        bis.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }


            }

        }
    }
}


为什么构造方法需要的是字节流,而不是具体的文件或者路径呢?

答:字节缓冲流仅仅提供缓冲区,而真正的读写数据还得依靠基本的字节流对象进行操作


提高拷贝速度的解决方案

为了解决速度问题,字节流通过创建字节数组,可以一次读写多个数据。
一次读一个字节数组的方法:

  • public int read​(byte[] b):从输入流读取最多b.length个字节的数据
  • 返回的是读入缓冲区的总字节数,也就是实际的读取字节个数

小结

方法名 说明
void write​(int b) 一次写一个字节数据
void write​(byte[] b) 一次写一个字节数组数据
void write​(byte[] b, int off, int len) 一次写一个字节数组的部分数据
int read​() 一次读取一个字节数据
int read​(byte[] b) 一次读一个字节组数据,把数据封装到参数b中,返回值为本次读取到的字节个数
  • 操作所有类型的文件 没有读取到位-1
  • 字节缓冲流:可以提高读写效率
  • 流:后用先关

字符流

字符流概述以及相关理论知识

  • 字符流的作用
    把文件中的数据读取到内存时,如果此时文件中出现了中文,那么字节流就会出现乱码现象。所以纯文本的文件,我们就需要使用字符流来进行操作。

编码表

基础知识:

  • 计算机中储存的信息都是用二进制数表示的;我们在屏幕上看到的英文、汉字等字符是二进制数转换之后的结果

  • 按照某种规则,将字符存储到计算机中,称为编码。

  • 按照同样的规则,将存储在计算机中的二进制数解析显示出来,称为解码 。

  • 编码和解码的方式必须一致,否则会导致乱码。
    简单理解:
    存储一个字符a,首先需在码表中查到对应的数字是97,然后按照转换成二进制的规则进行存储。
    读取的时候,先把二进制解析出来,再转成97,通过97查找到对应的字符是a。

ASCII字符集:

  • ASCII(American Standard Code for Information Interchange,美国信息交换标准代码):包括了数字,大小写字符和一些常见的标点符号。

  • 注意:ASCII码表中是没有中文的

  • GBK:window系统默认的码表。兼容ASCII码表,也包含了21003个汉字,并支持繁体汉字以及部分日韩文字。(GBK在电脑种也称为ANSI)

  • 注意:GBK是中国的码表,一个中文以两个字节的形式存储。但不包含世界上所有国家的文字。

Unicode码表:

  • 由国际组织ISO 制定,是统一的万国码,计算机科学领域里的一项业界标准,容纳世界上大多数国家的所有常见文字和符号。
    但是因为表示的字符太多,所以Unicode码表中的数字不是直接以二进制的形式存储到计算机的,会先通过UTF-7,UTF-7.5,UTF-8,UTF-16,以及 UTF-32的编码方式再存储到计算机,其中最为常见的就是UTF-8
    注意: Unicode是万国码,以UTF-8编码后一个中文以三个字节的形式存储

  • idea中更换编码表:

汉字存储和展示过程解析

字符串中的编码解码问题

编码:将数据编写成对应的编码

  • byte[] getBytes​():使用平台的默认字符集将该 String编码为一系列字节,将结果存储到新的字节数组中

  • byte[] getBytes​(String charsetName):使用指定的字符集将该 String编码为一系列字节,将结果存储到新的字节数组中

解码:将码值解出来,获得对应的数据

  • String​(byte[] bytes):通过使用平台的默认字符集解码指定的字节数组来构造新的 String

  • String​(byte[] bytes, String charsetName):通过指定的字符集解码指定的字节数组来构造新的 String

实例代码
package ReaderAndWirter;

import java.io.UnsupportedEncodingException;
import java.util.Arrays;

public class Code_Learn {
    public static void main(String[] args) throws UnsupportedEncodingException {
      //编码byte[] getBytes(String charsetName)
        String str = "小黑子树枝666";//想要输入的值
        byte[] bytes = str.getBytes();//将想要输入的值转换为字节类型,存入字节数组,UTF-8默认编码格式,一个中文三个字节
        System.out.println(Arrays.toString(bytes));//输出bytes集合,中文会变成负数
        //[-27, -80, -113, -23, -69, -111, -27, -83, -112, -26, -96, -111, -26, -98, -99, 54, 54, 54]

        byte[] bytes1 = str.getBytes("UTF-8");//抛出异常,UnsupportedEncodingException
        System.out.println(Arrays.toString(bytes1));
        //[-27, -80, -113, -23, -69, -111, -27, -83, -112, -26, -96, -111, -26, -98, -99, 54, 54, 54]

        byte[] gbks = str.getBytes("GBK");//GBK中,一个中文两个字节
        System.out.println(Arrays.toString(gbks));
        //[-48, -95, -70, -38, -41, -45, -54, -9, -42, -90, 54, 54, 54]

      //解码String(byte[] bytes, String charsetName)
        String str1 = new String(bytes);
        System.out.println(str1);//默认编码 UTF-8
        //小黑子树枝666

        String str2 = new String(bytes, "GBK");
        System.out.println(str2);//默认编码 UTF-8,用GBK去解码,会出现乱码
        // 灏忛粦瀛愭爲鏋�666

    }
}

字符流的创建

  • FileReader字符输入流
构造器 说明
public FileReader​(File file) 创建字符输入流管道与源文件对象接通
public FileReader​(String pathname) 创建字符输入流管道与源文件路径接通
  • FileWriter字符输出流
构造器 说明
public FileWriter(File file) 创建字符输出流管道与源文件对象接通
public FileWriter​(File file,boolean append) 创建字符输出流管道与源文件对象接通,可追加数据
public FileWriter​(String filepath) 创建字符输出流管道与源文件路径接通
public FileWriter​(String filepath,boolean append) 创建字符输出流管道与源文件路径接通,可追加数据
实例代码
package ReaderAndWirter;

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class ReadAndWrite01 {
    public static void main(String[] args) {
        //创建字符输入流对象
        FileReader fileReader = null;
        FileWriter fileWriter = null;


        try {
            fileReader = new FileReader("src/ikun");
            fileWriter = new FileWriter("src/abc.txt");//没有文件的话,会先创建
            //开始读取 写入文件
            int len;
            while ((len=fileReader.read())!=-1){
                fileWriter.write(len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            //关流
            try {
                fileWriter.close();
                fileReader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }
}

  • 字符流写数据步骤:
  1. 创建字符输出流对象
    注意事项:
    如果文件不存在,就创建。但是要保证父级路径存在。
    如果文件存在就清空。
  2. 写数据
    注意事项:
    1. 写出int类型的整数,实际写出的是整数在码表上对应的字母。
    2. 写出字符串数据,是把字符串本身原样写出。
  3. 释放资源
    注意事项:
    每次使用完流必须要释放资源。

字符流写数据的5种方式

方法名 说明
void write​(int c) 写一个字符
void write​(char[] cbuf) 写入一个字符数组
void write​(char[] cbuf, int off, int len) 写入字符数组的一部分
void write​(String str) 写一个字符串
void write​(String str, int off, int len) 写一个字符串的一部分
点击查看代码
package ReaderAndWirter;

import java.io.FileWriter;
import java.io.IOException;

public class ReaderAndWriter01 {
    public static void main(String[] args) throws IOException {//本示例代码直接抛出异常
        FileWriter fileWriter = null;
        fileWriter = new FileWriter("src/a");
        char[] chars = {97, 98, 99, 100};
        String str = "ikun";
        fileWriter.write(98);// void write(int c)
        fileWriter.write(chars);//void write(char[] cbuf)
        fileWriter.write(chars, 1, 2);//void write(char[] cbuf, int off, int len)
        fileWriter.write("98");//void write(String str)
        fileWriter.write(str);//void write(String str, int off, int len)
        fileWriter.flush();
        fileWriter.close();
    }
}

字符流读和关闭流、刷新流

方法名 说明
flush() 刷新流,还可以继续写数据
close() 关闭流,释放资源,但是在关闭之前会先刷新流。一旦关闭,就不能再写数据
int read​() 一次读一个字符数据
int read​(char[] cbuf) 一次读一个字符数组数据

flush作用:清除缓冲流中的残留数据。当close被调用时,输出流会被关闭,此时缓冲区可能还会有数据每存入。此时就要调用flush,清空缓冲区,在关闭close。

账号案例代码
package ReaderAndWirter;

import java.io.FileWriter;
import java.io.IOException;
import java.util.Scanner;

public class ReaderAndWriterTest01 {
    public static void main(String[] args) {
        //1.创建扫描器 键盘录入 用户名密码
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入用户名:");
        String username = scanner.next();
        System.out.println("请输入密码:");
        String password = scanner.next();

        //2.创建 输出流 将用户数据存入本地文件夹中
        FileWriter fw = null;
        try {
            fw = new FileWriter("src/abc.txt");
            byte[] bytes = new byte[1024];
            fw.write("username:"+username);
            fw.write("\r\n");
            fw.write("password:"+password);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                fw.flush();
                fw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}

字符流的缓冲流

  • BufferedWriter:将文本写入字符输出流,缓冲字符,以提供单个字符,数组和字符串的高效写入,可以指定缓冲区大小,或者可以接受默认大小。默认值足够大,可用于大多数用途

  • BufferedReader:从字符输入流读取文本,缓冲字符,以提供字符,数组和行的高效读取,可以指定缓冲区大小,或者可以使用默认大小。 默认值足够大,可用于大多数用途

构造方法:

  • BufferedWriter​(Writer out)

    • void newLine​():写一行行分隔符,行分隔符字符串由系统属性定义
  • BufferedReader​(Reader in)

    • public String readLine​() :读一行文字。 结果包含行的内容的字符串,不包括任何行终止字符,如果流的结尾已经到达,则为null,方法开始时便先进入下一行,所以想要读取全部数据,第一行应为/r(空行)
      取到没有数据时就返回null(因为其它read()方法当读到没有数据时返回-1),而实际上readLine()是一个阻塞函数,当没有数据读取时,就一直会阻塞在那,而不是返回null。

CSDN博主关于readLine的解析:https://blog.csdn.net/qq30211478/article/details/78500315

示例代码1
package Buffered;

import java.io.*;
/*构造方法:
 * BufferedWriter(Writer out)
 * void newLine():写一行行分隔符,行分隔符字符串由系统属性定义

 * BufferedReader(Reader in)
 * public String readLine() :读一行文字。 结果包含行的内容的字符串,不包括任何行终止字符,如果流的结尾已经到达,则为null
 */

public class BufferedReaderAndWriter {
    public static void main(String[] args) {
        //创建缓冲流字符输入流/输出流
        BufferedReader bufferedReader = null;
        BufferedWriter bufferedWriter = null;
        try {
            bufferedReader = new BufferedReader(new FileReader("src/a"));
            bufferedWriter = new BufferedWriter(new FileWriter("src/aaaa"));
            //普通方法
//            int len;
//            while ((len = bufferedReader.read()) != -1) {
//                bufferedWriter.write(len);
//            }
            String line;
            while ((line = bufferedReader.readLine()) != null) {//public String readLine()
                bufferedWriter.write(line);//写一行数据
                bufferedWriter.newLine();//换行

            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                bufferedWriter.flush();
                bufferedWriter.close();
                bufferedReader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

示例代码2
package ReaderAndWirter;

import java.io.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

public class Demo01 {
    public static void main(String[] args) {
        //创建输入和输出流对象
        BufferedWriter bw = null;
        BufferedReader br = null;
        //创建集合,便于排序
        ArrayList<String> arrayList = new ArrayList<>();
        try {
            br = new BufferedReader(new FileReader("src/a"));
            bw = new BufferedWriter(new FileWriter("src/a", true));//添加true使得写文件时,不会清空文件夹的内容
            //读取数据
            String line;
            while ((line = br.readLine()) != null) {
                //取出数据 添加到集合中 并排序
                arrayList.add(line);
                // bw.write(line);

            }
            //对集合中的元素 完成排序
            Collections.sort(arrayList, new Comparator<String>() {//Collections.sort(list,Comparator<String>())自定义集合排序方式
                @Override
                public int compare(String o1, String o2) {
                    return o1.hashCode() - o2.hashCode();//利用哈希值进行比较
                }
            });
            System.out.println("排序后:" + arrayList);//打印集合内容

              //将集合中的内容写入到文件夹中
/*            for (String s : arrayList) {
                bw.write(s);
                bw.newLine();
            }
*/
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                br.close();
                bw.flush();
                bw.close();

            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

IO流小结

转换流

转换流概述

  • 转换输入/输出的编码格式

转换流构造方法以及创建

方法名 说明
InputStreamReader(InputStream in) 使用默认字符编码创建InputStreamReader对象
InputStreamReader(InputStream in,String chatset) 使用指定的字符编码创建InputStreamReader对象
OutputStreamWriter(OutputStream out) 使用默认字符编码创建OutputStreamWriter对象
OutputStreamWriter(OutputStream out,String charset) 使用指定的字符编码创建OutputStreamWriter对象
package ReaderAndWirter;
//读取文件中的数据,排序后再次写到本地文件
import java.io.*;
import java.nio.charset.Charset;//jdk 11之后

public class Transfer {
    public static void main(String[] args) throws IOException {
        //创建输入流对象 默认UTF-8
        InputStreamReader irs = new InputStreamReader(new FileInputStream("src/abc.txt"));

        //创建输出流对象  默认UTF-8
        OutputStreamWriter ows  =  new OutputStreamWriter(new FileOutputStream("src/ikun"));

        int len;
        while((len=irs.read())!=-1){
            ows.write(len);
        }
        ows.close();
        irs.close();
        //JDK11之后提供的在创建字符流的时候 指定编码规则
         FileReader fr=new FileReader("src/ikun", Charset.forName("UTF-8"));
    }
}

转换流的使用场景

在JDK11之前,指定编码读写

JDK11之后,

  • FileReader(File file, Charset charset):创建一个新的FileReader,给出File读取和创建charset

  • FileReader(String fileName, Charset charset):创建一个给定文件名称的FileReader,给出File读取和创建charset

  • 【注意】:

charset需要通过Charset.forName(“”)获取

小结

转换流就是来进行字节流和字符流之间转换的

  • InputStreamReader是从字节流到字符流的桥梁
    它读取字节,并使用指定的编码将其解码为字符。
    它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集。

  • OutputStreamWriter是从字符流到字节流的桥梁
    是从字符流到字节流的桥梁,使用指定的编码将写入的字符编码为字节。
    它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集。

对象操作流

对象操作流的概述

了解对象操作流

  • 特点:可以把对象以字节的形式写到本地文件,直接打开文件,是读不懂的,需要再次用对象操作流读到内存中。

对象操作流概述

  • 对象操作流分为两类:对象操作输入流和对象操作输出流

  • 对象操作输出流(对象序列化流):就是将对象写到本地文件中,或者在网络中传输对象

  • 对象操作输入流(对象反序列化流):把写到本地文件中的对象读到内存中,或者接收网络中传输的对象

对象序列化和发序列化概述

  • 对象序列化:就是将对象保存到磁盘中,或者在网络中传输对象

  • 这种机制就是使用一个字节序列表示一个对象,该字节序列包含:对象的类型、对象的数据和对象中存储的属性等信息

  • 字节序列写到文件之后,相当于文件中持久保存了一个对象的信息

  • 反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化

对象输入流

  • 对象操作输出流(对象序列化流):就是将对象写到本地文件中,或者在网络中传输对象

  • 构造方法

方法名 说明
ObjectOutputStream(OutputStream out) 创建一个写入指定的OutputStream的ObjectOutputStream
  • 序列化对象

方法名 说明
void writeObject(Object obj) 将指定的对象写入ObjectOutputStream
  • 注意事项
    • 一个对象要想被序列化,该对象所属的类必须必须实现Serializable 接口
    • Serializable是一个标记接口,实现该接口,不需要重写任何方法

对象输出流

  • 对象操作输入流(对象反序列化流):把写到本地文件中的对象读到内存中,或者接收网络中传输的对象

  • 构造方法

方法名 说明
ObjectInputStream(InputStream in) 创建从指定的InputStream读取的ObjectInputStream

  • 反序列化对象的方法
方法名 说明
Object readObject() 从ObjectInputStream读取一个对象
自定义类代码
package ObjectManipilationStream;

import java.io.Serializable;

//学生对象
public class Students implements Serializable {
    private String name;
    private int age;

    @Override
    public String toString() {
        return "Students{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Students(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

对象流操作对象代码
package ObjectManipilationStream;

import java.io.*;

/*
 * 对象操作流 用来操作对象
 * 1. 对象输入流 将对象从文件中读取出来 反序列化过程
 * 2. 对象输出流 将对象保存到文件中 序列化的过程
 * 序列化: 将对象保存到磁盘
 * 反序列化:将对象从磁盘中取出
 *
 * 前提条件:在向文件中存储对象时,必须将类实现序列化接口
 * */
public class ObjectManipulationStream01 {
    public static void main(String[] args) {
        ObjectOutputStream oos = null;
        ObjectInputStream ois = null;
        try {
            //1.创建输入输出流
            ois = new ObjectInputStream(new FileInputStream("src/ObjectManipilationStream/Object01"));
            oos = new ObjectOutputStream(new FileOutputStream("src/ObjectManipilationStream/Object01"));
            //2.写数据
            Students s1 = new Students("撒旦", 98);
            oos.writeObject(s1);//形参为对象类型
            //读数据
            Students s2 = (Students) ois.readObject();//强制类型转换才可以用Students接收
            System.out.println(s2);//Students{name='撒旦', age=98}
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        } finally {

            try {
                ois.close();
                oos.flush();
                oos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }


    }
}

对象流操作其他基本数据类型代码
package ObjectManipilationStream;

import IO.Demo01.Student;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Date;

public class ObjectManipulationStream02 {
    public static void main(String[] args) throws IOException {
        //创建
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("src/a"));
        //写
        oos.writeInt(123);//注意方法名
        oos.writeObject(new Date());
        oos.writeObject(new Students("小黑子", 666));
        //关
        oos.flush();
        oos.close();
    }
}

对象操作流的注意事项

注意:

  1. 对象流不仅可以读写对象,还可以读写基本数据类型。

  2. 使用对象流读写对象时,该对象必须序列化与反序列化。

  3. 系统提供的类(如Date等)已经实现了序列化接口,自定义类必须手动实现序列化接口。

对象操作流问题

用对象序列化流序列化了一个对象后,我们再去修改对象所属的Javabean类,比如添加一个属性,读取数据会不会出问题呢?

  • 会出问题,会抛出InvalidClassException异常
    如果出问题了,如何解决呢?
  • 1.给对象所属的类加一个serialVersionUID
    private static final long serialVersionUID = 42L;
    2.自动生成

如果一个对象中的某个成员变量的值不想被序列化,又该如何实现呢?

  • 给该成员变量加transient关键字修饰,该关键字标记的成员变量不参与序列化过程
自定义类代码
package ObjectManipilationStream;

import java.io.Serializable;

//学生对象
/*生成serialVersionUID两种方法
 * 1.死值 private static final long serialVersionUID = 42l;
 * 2.系统生成
 * */
public class Students implements Serializable {
    //写一个死值
    private static final long serialVersionUID = 42l;
    //serialVersionUID = -6439917428245416448
    private String name;
    private int age;
    private String sex;//二次修改后增添的值
    
    @Override
    public String toString() {
        return "Students{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Students(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

测试代码
package ObjectManipilationStream;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;

public class ObjectManipulationStream03 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("src/a"));
        Students s2 = (Students) ois.readObject();
        System.out.println(s2);
        ois.close();
    }
    /*
    Exception in thread "main" java.io.InvalidClassException: ObjectManipilationStream.Students;
    local class incompatible: stream classdesc serialVersionUID = -6439917428245416448(存储时的序列化ID), local class serialVersionUID = -8371228507770588511(修改后新的序列化ID)二者不相同,程序报错
    at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)
    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:2028)
    at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1875)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2209)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1692)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:508)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:466)
    at ObjectManipilationStream.ObjectManipulationStream03.main(ObjectManipulationStream03.java:11)
    */
}

Properties

Porperties概述

  • 是一个Map体系的集合类 继承了Hashtable implements Map接口

  • Properties中有跟IO相关的方法

  • 只存字符串

  • Properties通常用来读取,properties后缀的文件,称之为java中的配置文件

Properties方法

  • Properties作为集合的特有方法:
方法名 说明
Object setProperty​(String key, String value) 设置集合的键和值,都是String类型,底层调用 Hashtable方法 put
String getProperty​(String key) 使用此属性列表中指定的键搜索属性
Set stringPropertyNames​() 从该属性列表中返回一个不可修改的键集,其中键,及其对应的值是字符串
  • Properties常用其他方法: Properties底层有Map接口,所以有Map的方法例如

    • remove(String key),清除键值,返回键对应的值

    • getKey()。获取键值

Properties作为Map集合使用(增删改查)
package Properties;

import java.util.Properties;
import java.util.Set;

/*Properties作为Map集合实现增删改查
* 只能存储字符串的
* Object setProperty(String key, String value)	设置集合的键和值,都是String类型,底层调用 Hashtable方法 put
* String getProperty(String key)	使用此属性列表中指定的键搜索属性
* Set<String> stringPropertyNames()	从该属性列表中返回一个不可修改的键集,其中键集其对应的值是字符串
* */
public class Properties01 {
    public static void main(String[] args) {
        Properties properties = new Properties();//{age=18, name=小黑子, sex=男}
        //增
        properties.setProperty("name","小黑子");
        properties.setProperty("age","18");
        properties.setProperty("sex","男");
        //删
        Object name = properties.remove("name");//{age=18, sex=男}
        System.out.println(name);//张三 删除键,返回的是键对应的值
        //改
        properties.setProperty("age","19");//{age=19, sex=男} 键名不能重复
        //查
        Set<Object> set = properties.keySet();//获取键,但是没法获取值,因为keySet返回类型为Object
        for (Object o : set) {//只有键名,没有值
           // System.out.println(o);
        }

        Set<String> set1 = properties.stringPropertyNames();//从该属性列表中返回一个不可修改的键集,其中键,及其对应的值是字符串
        for (String s : set1) {//获取值
            String value = properties.getProperty(s);
            System.out.println("Key:"+s+"Value"+"\t"+value);
        }

        System.out.println(properties);
    }
}

Properties和IO流结合的方法

  • Properties和IO流结合的方法:
方法名 说明
void load​(InputStream inStream) 从输入字节流读取属性列表(键和元素对)
void load​(Reader reader) 从输入字符流读取属性列表(键和元素对)
void store​(Writer writer, String comments) 将此属性列表(键和元素对)写入此 Properties表中,以适合使用 load(Reader)方法的格式写入输出字符流
示例代码
package Properties;

import java.io.*;
import java.util.Properties;
import java.util.Set;

/*
* void load(InputStream inStream)	从输入字节流读取属性列表(键和元素对)
void load(Reader reader)	从输入字符流读取属性列表(键和元素对)
void store(Writer writer, String comments)	将此属性列表(键和元素对)写入此 Properties表中,以适合使用 load(Reader)方法的格式写入输出字符流
*Properties通产用来读取,properties后缀的文件,称之为java中的配置文件
*
* */
public class Properties02 {
    public static void main(String[] args) throws IOException {
        //创建Properties 集合对象
        Properties properties = new Properties();
        properties.setProperty("ikun01","树枝666");
        properties.setProperty("ikun02","食不食油饼");
        properties.setProperty("ikun03","香精监狱");
        properties.setProperty("ikun04","香翅捞饭");
        System.out.println(properties);//{ikun=香翅捞饭}
        //{ikun04=香翅捞饭, ikun03=香精监狱, ikun02=食不食油饼, ikun01=树枝666}
        //创建输入流
        FileInputStream fis = new FileInputStream("src/Prop.properties");
        //加载
        properties.load(fis);
        System.out.println(properties);
        //{ikun04=香翅捞饭, ikun03=香精监狱, ikun02=食不食油饼, ikun01=树枝666}

        //将properties集合中的内容存储到配置文件中 以下创建的为字节流 想要存储汉字需要字符流
        properties.store(new FileOutputStream("src/Prop.properties"),"abc");//comments为备注,会自动在文件中生成‘#comments’,还会自动生成时间备注

        Set<String> set = properties.stringPropertyNames();
        for (String s : set) {
            String value = properties.getProperty(s);
            System.out.println("Key:"+s+"Value"+"\t"+value);
        }
        /*
        Key:ikun04Value	香翅捞饭
        Key:ikun1Value	123
        Key:ikun03Value	香精监狱
        Key:ikun02Value	食不食油饼
        Key:ikun01Value	树枝666
        Key:ikun2Value	246
*/
        
    }
}

案例代码
package Properties;

import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Properties;

/*
* - 案例需求
xx.properties:
jdbc.username=root
jdbc.password=1234
  在Properties文件中手动写上用户名密码,读取到集合中,将该数据封装成用户对象,写到本地文件.
- 实现步骤
  - 创建Properties集合,将本地文件中的数据加载到集合中.
  - 获取集合中的键值对数据,封装到用户对象中.
  - 创建序列化流对象,将用户对象序列化到本地文件中.
* */
public class Properties03 {
    public static void main(String[] args) throws IOException {
        //创建properties集合对象
        Properties properties = new Properties();
        //2.存储内容
        properties.setProperty("jdbc.username", "rppt");
        properties.setProperty("jdbc.password", "1234");
        //根据键名 获取值 存入对象中
        User user = new User();
        user.setUsername(properties.getProperty("jdbc.username"));
        user.setPassword(Integer.parseInt(properties.getProperty("jdbc.password")));
        //将对象存入到文件中 对象流要注意实现接口
        ObjectOutputStream oos= new ObjectOutputStream(new FileOutputStream("src/jdbc.properties"));
        oos.writeObject(user);
        //3.读取集合内容 写入到配置文件中 创建字符流
        FileWriter fw = new FileWriter("src/jdbc.properties");
        properties.store(fw, "jdbc");
        //关闭流
        oos.flush();
        oos.close();
        fw.close();
    }
}

  • 案例图片

登陆账户(判断用户名和密码)案例代码
package Properties;

import java.io.*;
import java.util.*;

/*
 * 案例:使用字符缓冲流读取user.txt中用户名和密码
 * 需求:读取用户名和密码,判断用户输入数据是否正确
 * 步骤:
 * 用户通过控制台输入数据
 * 读取文件内容,进行数据撕裂获取username, password
 * 比较用户输入数据是否正确
 */
public class Demo02 {
    public static void main(String[] args) throws IOException {//为了减少代码行数,本示例代码选择直接抛出异常
        //定义限制次数
        int time=3;
        //创建扫描器,获取用户输入数据
        Scanner scanner = new Scanner(System.in);
        while (true){//循环输入用户名和密码
            //输入用户名
            System.out.println("请输入用户名:");
            String username = scanner.next();
            //输入密码
            System.out.println("请输入密码:");
            String password = scanner.next();
            if (getTarget(username,password)==1){
                System.out.println("登陆成功");
                break;
            }else if (getTarget(username,password)==0){
                --time;
                System.out.println("密码错误,您当前还有"+time+"次输入次数");
            }else{
                System.out.println("用户名不存在");
            }
            if (time==0){
                break;
            }

        }
    }

    //创建方法,判断用户输入是否在本地中存在
    public static int getTarget(String username, String password) throws IOException {
        //比较次数结果
        int size = 0;
        //创建properties操作流
        Properties properties = new Properties();
        //创建字符缓冲流,获取保存的用户名和密码
        BufferedReader bf = new BufferedReader(new FileReader("src/ProtertiesTest.protetties"));
        //加载
        properties.load(bf);
        //对properties获取的值存入到集合中
        Set<String> set = properties.stringPropertyNames();
        //循环判断用户输入是否正确
        for (String s : set) {
            size++;
            if (s.equals(username)) {//判断用户名是否正确
                if (properties.getProperty(s).equals(password)) {//密码正确
                    return  1;
                }
            } else if (s.equals(username) && !properties.getProperty(s).equals(password))//密码不正确
            {
                return 0;
            }
                if (size == set.size()) {//未找到相关用户名
                    return -1;
                }
        }
        return -1;
    }
}

输入、输出流拒绝访问解决方案

  • IO流是对文件进行操作,直接对文件夹操作就会因为没有系统权限而被阻拦,但是通过复制路径的方法可以绕过阻拦。
package Homework.File.File01;

import java.io.*;

public class Test {//该代码较为简单,没有明显体现出复制转换法的作用,但是到了较大的问题,比如本博客中的字节缓冲流中的,复制多级文件夹代码,就体现出来了。
    public static void main(String[] args) throws IOException {//为了减少行数本代码直接抛出异常
        //注意:两路径均以文件夹为结尾,IO流输出时会被拒绝访问
        File srcFile = new File("D:\\Test01 - 副本");//源路径
        File destFile = new File("D:\\Test01");//目标路径
        //输入缓冲流
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile));
        //输出缓冲流
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFile));
        //读写文件,正常状况下会报错,拒绝访问
        File[] files = srcFile.listFiles();
        for (File file : files) {
            int len;
            byte[] bytes = new byte[1024];
            while ((len = bis.read(bytes)) != -1){
                bos.write(bytes);
            }
        }
        
        //我们复制路径,绕过权限访问(直接确定到文件夹,中的文件名)
        //newFile是由,目标路径的路径+分隔符+源路径下文件名组成,用newFile替换destFile绕过权限访问
        File newFile = new File(destFile.getPath()+File.separator+srcFile.getName());
        //修改后的写入方法
        File[] files1 = srcFile.listFiles();
        for (File file : files1) {
            int len;
            bos = new BufferedOutputStream(new FileOutputStream(newFile));
            byte[] bytes = new byte[1024];
            while ((len = bis.read(bytes)) != -1){
                bos.write(bytes);
            }
        }



    }
}

输入输出时,字节数组和字节的应用问题

  • 在输入输出时,如果字节数组和字节运用不好,会导致复制0字节的内容(复制的结果仅仅为创建了一个文件名而已,里面没有内容)

  • 正确格式1:

int len;
        byte[] bytes = new byte[1024];
        while ((len = bis.read(bytes)) != -1) {
            bos.write(bytes);
        }
  • 正确格式2
 int len;
        byte[] bytes = new byte[1024];
        while ((len = bis.read(bytes)) != -1) {
            bos.write(bytes,0,len);
        }
  • 正确格式3:
 int len;
        byte[] bytes = new byte[1024];
        while ((len = bis.read()) != -1) {
            bos.write(len);
        }
  • 离奇错误:复制后的文件大小扩大不少
int len;//错误方法!!!!
       byte[] bytes = new byte[1024];
       while ((len = bis.read()) != -1) {
           bos.write(bytes);
       }

close和flush

  • 输出流必须夹close,不然最后数据不会被写入文件,flush也可以使写入的数据进入文件。所以不想关闭输入流时,可以先flush一下,存入数据,以后在关闭close。