1.缓冲流

1.1 概念

基本原理:在创建流对象时,会创建一个内置的默认大小的缓冲区数组临时存储数据,通过缓冲区读写,减少系统底层IO次数,从而提高读写的效率。(故缓冲流又称为高效流)

4个基本的FileXxx流对应的缓冲流同分为4种:

  • 字节缓冲流:BufferedInputStream,BufferedOutputStream;
  • 字符缓冲流:BufferedReader,BufferedWriter。

注意!!:缓冲流的使用方式与非缓冲流的是一致的,只是构造方法有所不同

1.2 字节缓冲流

1.2.1构造方法
  • public BufferedInputStream(InputStream in) : 创建一个新的字节缓冲输入流
  • public BufferedOutputStream(OutputStream out) : 创建一个新的字节缓冲输出流
1.2.2 字节缓冲流的特点:
  • 传递的流对象时字节输入/输出流的超类,即所有的字节输入/输出流都能用缓冲流。
  • 字节缓冲输出流:调用缓冲输出流的write方法输出数据不是直接输出到目标文件,而是先输出到到缓冲区数组中。等缓冲区数组满了/调用了flush/close方法才会将缓冲区数组的数据通过OutputStream输出到目标文件中。
    注意!!:数据先通过BufferedOutputStream存到缓冲区,而从缓冲区写出到目标文件还是调用OutputStream的write方法。
  • 字节缓冲输入流:调用输入流的read方法不会直接从目标文件中读取到内存,而是先读取到缓存区数组中。
    注意!!:数据先利用InputStream的read方法读取到缓冲区,然后才通过BufferedInputStream读入内存。
  • 字节缓冲流的缓冲区本质是一个字节数组,默认大小为8192字节

示例代码如下:

//创建字节缓冲输入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("bis.txt"));
//创建字节缓冲输出流
BufferedOutputStream bis = new BufferedOutputStream(new FileOutputStream("bos.txt"));

//创建字节缓冲输入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("bis.txt"));
//创建字节缓冲输出流
BufferedOutputStream bis = new BufferedOutputStream(new FileOutputStream("bos.txt"));

字节缓冲输入流范例代码:

public class Notes01 {
    public static void main(String[] args) {
        //创建字节缓冲输入流对象
        //可用新特性,也可以抛异常,下面以新特性为例
        try(BufferedInputStream bis = new BufferedInputStream(new FileInputStream("test.txt"))){
            //先用一个个字节读入
            /*
            单字节读入固定套路
             */
            int len = -1;
            while ((len = bis.read()) != -1) {
                System.out.println((char)len);  //只是为了在控制台看到读取效果
            }
            //以一个字节数组读入
            /*
            字节数组读入固定套路
             */
            byte[] buf = new byte[1024];
            int leng = -1;
            while ((leng = bis.read(buf)) != -1) {
                System.out.println(new String(buf,0,leng));  //只是为了在控制台看到读取效果
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

public class Notes01 {
    public static void main(String[] args) {
        //创建字节缓冲输入流对象
        //可用新特性,也可以抛异常,下面以新特性为例
        try(BufferedInputStream bis = new BufferedInputStream(new FileInputStream("test.txt"))){
            //先用一个个字节读入
            /*
            单字节读入固定套路
             */
            int len = -1;
            while ((len = bis.read()) != -1) {
                System.out.println((char)len);  //只是为了在控制台看到读取效果
            }
            //以一个字节数组读入
            /*
            字节数组读入固定套路
             */
            byte[] buf = new byte[1024];
            int leng = -1;
            while ((leng = bis.read(buf)) != -1) {
                System.out.println(new String(buf,0,leng));  //只是为了在控制台看到读取效果
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

字节缓冲输出流范例代码

public class Notes01Out {
    public static void main(String[] args) {
        try(BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("test.txt",true))) {
            //以一个个字节输出
            bos.write(97);
            //以一个字节数组输出
            bos.write("你好吗\r\n".getBytes());
            //刷新缓冲区保存数据
            bos.flush();
            //关闭流,关闭缓冲流同时也会关闭字节输出流
            //因为新特性,建流放在try()里,不手动关流系统也会在执行完后自动关流 
            //bos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

public class Notes01Out {
    public static void main(String[] args) {
        try(BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("test.txt",true))) {
            //以一个个字节输出
            bos.write(97);
            //以一个字节数组输出
            bos.write("你好吗\r\n".getBytes());
            //刷新缓冲区保存数据
            bos.flush();
            //关闭流,关闭缓冲流同时也会关闭字节输出流
            //因为新特性,建流放在try()里,不手动关流系统也会在执行完后自动关流 
            //bos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

1.3 字符缓冲流

1.3.1构造方法
  • public BufferedReader(Reader in) : 创建一个新的缓冲字符输入流
  • public BufferedWriter(Writer out) : 创建一个新的缓冲字符输出流
    示例代码如下:
//创建字符缓冲输入流
BufferedReader br = new BufferedReader(new FileReader("br.txt"));
//创建字符缓冲输出流
BufferedWriter bw = new BufferedWriter(new FileWriter("bw.txt"));

//创建字符缓冲输入流
BufferedReader br = new BufferedReader(new FileReader("br.txt"));
//创建字符缓冲输出流
BufferedWriter bw = new BufferedWriter(new FileWriter("bw.txt"));
1.3.2 字符缓冲流的特点:
  • 传递的流对象时字符输入/输出流的超类,即所有的字符输入/输出流都能用缓冲流。
  • 字符缓冲输出流:调用缓冲输出流的write方法输出数据不是直接输出到目标文件,而是先输出到到缓冲区数组中。等缓冲区数组满了/调用了flush/close方法才会将缓冲区数组的数据通过Writer输出到目标文件中。
    注意!!:数据先通过BufferedWriter存到缓冲区,而从缓冲区写出到目标文件还是调用Writer的write方法。
  • 字符缓冲输入流:调用输入流的read方法不会直接从目标文件中读取到内存,而是先读取到缓存区数组中。
    注意!!:数据先利用Reader的read方法读取到缓冲区,然后才通过BufferedReader读入内存。
  • 字符缓冲流的缓冲区本质是一个字符数组,默认大小为8192字符(即8192*2个字节)
1.3.2 特有的方法

字符缓冲流的基本方法与普通字符流一致,特有方法如下

  • BufferedReader: public String readLine(): 读一行文字,读取到末尾返回null。
  • BufferedWriter: public void newLine(): 输出一个换行符,根据系统属性定义符号。

字符缓冲输入流范例代码:

public class Notes02_BufferedReader {
    public static void main(String[] args) {
        //创建流对象
        try (BufferedReader br = new BufferedReader(new FileReader("test.txt"))){
            /*
            固定套路
             */
            String line = null;
            while ((line = br.readLine()) != null) {    //直接使用特有方法,当然也可以用read方法,套路与FileReader相同
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

public class Notes02_BufferedReader {
    public static void main(String[] args) {
        //创建流对象
        try (BufferedReader br = new BufferedReader(new FileReader("test.txt"))){
            /*
            固定套路
             */
            String line = null;
            while ((line = br.readLine()) != null) {    //直接使用特有方法,当然也可以用read方法,套路与FileReader相同
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

字符缓冲输出流范例代码:

public class Notes02_BufferedWriter {
    public static void main(String[] args) {
        //创建流对象
        try (BufferedWriter bw = new BufferedWriter(new FileWriter("test.txt"))){
            //单字符输出
            bw.write(97);
            //字符数组输出
            char[] chars = {'再','见'};
            bw.write(chars);
            //字符串输出
            bw.write("中国人");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

public class Notes02_BufferedWriter {
    public static void main(String[] args) {
        //创建流对象
        try (BufferedWriter bw = new BufferedWriter(new FileWriter("test.txt"))){
            //单字符输出
            bw.write(97);
            //字符数组输出
            char[] chars = {'再','见'};
            bw.write(chars);
            //字符串输出
            bw.write("中国人");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

2.转换流

2.1 字符编码

  • 编码:字符按某种规则存储到计算机中。
  • 解码:将存储在计算机中的二进制数据按某种规则解析显示出来。
  • 字符编码Character Encoding : 就是一套自然语言的字符与二进制数之间的对应规则。
  • 编码表:字符与其对应的二进制相互映射的表

常见的码表:

ASCII:美国码表,只包含字母、数字、美标点符号等,每个字符占一个字节,有128种字符映射关系;

GBK:兼容ASCII和GB2312。ASCII表的内容占一个字节,中文两个字节(第一个为负数,第二个可正可负),有128*256个字符映射关系;

Unicode:国际码表,所有字符占两个字节,有65536个字符映射关系;

UTF-8:基于Unicode,根据字符内容选择字节存储,英文占一个字节,中文占三个字节;

2.2 转换流的存在意义

  • 产生乱码的原因:文本存储使用的码表与读取时使用的码表不一致

因为Windows默认GBK而IDEA默认UTF-8,会有乱码的可能。

  • 意义:指定码表读写数据。

2.3 InputStreamReader类

2.3.1概念

InputStreamReader继承Reader,是字节流转换为字符流的桥梁。读取字节时使用指定的码表解码。

2.3.2 构造方法
  • InputStreamReader(InputStream in) : 创建一个使用默认字符集的字符流
  • InputStreamReader(InputStream in, String charsetName) : 创建一个使用指定字符集的字符流
    示例代码如下:
//创建字节输入流的转换流
InputStreamReader isr = new InputStreamReader(new FileInputStream("in.txt"));
//创建指定码表的字节输入流的转换流
InputStreamReader isr2 = new InputStreamReader(new FileInputStream("in.txt") , "GBK");

//创建字节输入流的转换流
InputStreamReader isr = new InputStreamReader(new FileInputStream("in.txt"));
//创建指定码表的字节输入流的转换流
InputStreamReader isr2 = new InputStreamReader(new FileInputStream("in.txt") , "GBK");
2.3.3 InputStreamReader类的特点
  • 只能读入文本文件的内容
  • 传递的流对象时字节输入流的超类,即所有的字节输入流都能用转换流。
2.3.4 InputStreamReader转换流程
  • 先由字节输入流从目标文件中读取数据(读入的是二进制数据),然后将读取的数据交给字符转换流,由字符转换流查询指定码表将二进制数据转换为对应的字符
public class Notes03_InputStreamReader {
    public static void main(String[] args) {
        //使用转换流转换字节输入流
        try(InputStreamReader isr = new InputStreamReader(new FileInputStream("test.txt"),"gbk")){
            char[] chars = new char[1024];
            int len = -1;
            //System.out.println(len);
            while((len = isr.read(chars)) != -1)
            	System.out.println(new String(chars,0,len));
        } catch (IOException e) {
            e.printStackTrace();
        }
        //使用转换流转换字符输入流
        /*
        由于FileReader自身的原因,只能用默认码表读取文件数据
         */
    }
}

public class Notes03_InputStreamReader {
    public static void main(String[] args) {
        //使用转换流转换字节输入流
        try(InputStreamReader isr = new InputStreamReader(new FileInputStream("test.txt"),"gbk")){
            char[] chars = new char[1024];
            int len = -1;
            //System.out.println(len);
            while((len = isr.read(chars)) != -1)
            	System.out.println(new String(chars,0,len));
        } catch (IOException e) {
            e.printStackTrace();
        }
        //使用转换流转换字符输入流
        /*
        由于FileReader自身的原因,只能用默认码表读取文件数据
         */
    }
}

2.4 OutputStreamWriter类

2.4.1概念

OutputStreamWriter继承Writer,字符流转换字节流的桥梁。使用指定的字符集将字符编码为字节。

2.4.2 构造方法
  • OutputStreamWriter(OutputStream out) : 创建一个使用默认字符集的字符流
  • OutputStreamWriter(OutputStream out, String charsetName) : 创建一个使用指定字符集的字符流
    示例代码如下:
//创建字节输入流的转换流
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream ("out.txt"));
//创建指定码表的字节输入流的转换流
OutputStreamWriter osw2 = new OutputStreamWriter(new FileOutputStream ("out.txt") , "GBK");

//创建字节输入流的转换流
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream ("out.txt"));
//创建指定码表的字节输入流的转换流
OutputStreamWriter osw2 = new OutputStreamWriter(new FileOutputStream ("out.txt") , "GBK");
2.4.3 OutputStreamWriter类的特点
  • 只能写出到文本文件
  • 传递的流对象时字节输出流的超类,即所有的字节输出流都能用转换流。
2.4.4 OutputStreamWriter转换流程
  • 先由字符转换流查询指定码表将字符转换为二进制数据,然后将二进制数据交给字节输出流输出到目标文件中
public class Notes03_OutputStreamWriter {
    public static void main(String[] args) {
        //使用转换流转换字节输出流
        try(OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("test.txt"),"gbk")){
            //输出字符
            osw.write('你');
            //输出字符串
            osw.write("nihao");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

public class Notes03_OutputStreamWriter {
    public static void main(String[] args) {
        //使用转换流转换字节输出流
        try(OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("test.txt"),"gbk")){
            //输出字符
            osw.write('你');
            //输出字符串
            osw.write("nihao");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

3.序列化流

3.1序列化

3.1.1概念
  • 对象序列化:将对象转换为字节并保持到文件中的过程。需要使用对象输出流ObjectOutputStream。
  • 对象反序列化:将保持在文件中的字节读取出来并转换为对象的过程。需要使用对象输入流ObjectInputStream。

3.2 对象输出流(ObjectOutputStream类)

3.2.1 概念

将Java对象的原始数据类型写出到文件,实现对象的持久存储

3.2.2 构造方法
  • public ObjectOutputStream(OutputStream out) : 创建一个指定OutputStream的ObjectOutputStream。

示例代码如下:

FileOutputStream fileOut = new FileOutputStream("employee.txt");
ObjectOutputStream out = new ObjectOutputStream(fileOut);

FileOutputStream fileOut = new FileOutputStream("employee.txt");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
3.2.3 序列化操作

对象序列化必须满足的两个条件:

  • 该类必须实现java.io.Serializable 接口, Serializable 是一个标记接口,不实现此接口的类的对象在序列化过程中会抛出NotSerializableException(对象不支持序列化操作);
  • 写出对象方法
    public final void writeObject (Object obj) : 将指定的对象写出。

注意!!:序列化之后的文件必定是乱码形式,但只要反序列化时能正确获得对象,那就没有任何影响。

3.2.4 对象序列化注意事项

1)使用static,将隐私数据静态化,数据不会在序列化中显示。但是使用static修饰的数据最好是类共有的数据(比如相同的国籍、民族),不推荐使用

2)使用transient关键字,修饰成员变量,保证该变量的值不会被序列化到文件中

  • transient 使用格式
  • 修饰符 transient 数据类型 变量名;

3.3 对象输入流(ObjectInputStream类)

3.3.1 概念

ObjectInputStream反序列化流,将之前使用ObjectOutputStream序列化的原始数据恢复为对象。

3.3.2 构造方法
  • public ObjectInputStream(InputStream in) : 创建一个指定InputStream的ObjectInputStream。
3.3.3 反序列化操作

1)序列号:一个类在实现Serializable接口后,在编译时系统都会为类随机生成一个序列号,并跟随其.class文件一起传输。如果类修改后重新编译,则系统又会随机生成另一个序列号跟随重新编译后的.class文件

2)方式一:序列号一致

思路:找到对象的class文件,进行反序列化操作,但要求对象必须是可以找到class文件的类

  • 调用读取对象方法:
    public final Object readObject () : 读取一个对象。返回的是一个Object对象,如果要获得指定类型对象则需要强转

3)方式二:序列号冲突

思路:如果类重新编译,则会出现序列化和反序列化时的序列号不一致的问题。解决方案是:将类的序列号进行固定,不让系统随机生成序列号,则重新编译后序列号仍可保持一致

  • 自定义序列号:
    private static final long serialVersionUID = 自定义序列号

之后与方式一操作相同

注意!!:不管方式一还是方式二都建议自定义序列号

示例代码:

/*
读入对象
*/
public class Notes04_ObjectInputStream {
    public static void main(String[] args) {
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("stu.txt"))) {
            Student stu = (Student)ois.readObject();    //使用独有方法,如果未知数据类型,则需要用Object接收
            System.out.println(stu);
        }  catch (Exception e) {
            e.printStackTrace();
        }
    }
}
class Student implements Serializable{
    private String name;
    private int age;
    public transient String id;// 学号
    // 自定义序列号
    private static final long serialVersionUID = 123456789L;
    /*
    此处省略getter/setter,以及toString()
    */
}

/*
在另外一个类写出对象
*/
public class Notes04_ObjectOutputStream {
    public static void main(String[] args)throws Exception{
        // 创建学生对象
        Student stu = new Student("小明",20);
        stu.idCard = "1501266";
        // 创建对象输出流对象
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("stu.txt"));
        // 将对象保存到流关联的目标文件中
        oos.writeObject(stu);
        // 关闭流
        oos.close();
    }
}

/*
读入对象
*/
public class Notes04_ObjectInputStream {
    public static void main(String[] args) {
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("stu.txt"))) {
            Student stu = (Student)ois.readObject();    //使用独有方法,如果未知数据类型,则需要用Object接收
            System.out.println(stu);
        }  catch (Exception e) {
            e.printStackTrace();
        }
    }
}
class Student implements Serializable{
    private String name;
    private int age;
    public transient String id;// 学号
    // 自定义序列号
    private static final long serialVersionUID = 123456789L;
    /*
    此处省略getter/setter,以及toString()
    */
}

/*
在另外一个类写出对象
*/
public class Notes04_ObjectOutputStream {
    public static void main(String[] args)throws Exception{
        // 创建学生对象
        Student stu = new Student("小明",20);
        stu.idCard = "1501266";
        // 创建对象输出流对象
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("stu.txt"));
        // 将对象保存到流关联的目标文件中
        oos.writeObject(stu);
        // 关闭流
        oos.close();
    }
}

4.打印流

4.1 作用及分类

  • 作用是为了方便原样输出各种数据类型的值
  • 特点:只有输出流,没有输入流
  • 分类:字节打印流(PrintStream)
字符打印流(PrintWriter)

4.2 构造方法

  • public PrintStream(String fileName) : 使用指定的文件名创建一个新的字节打印流。
  • public PrintWriter(String fileName) : 使用指定的文件名创建一个新的字符打印流。

示例代码如下:

PrintStream ps = new PrintStream("ps.txt");
PrintWriter ps = new PrintWriter("ps.txt");

PrintStream ps = new PrintStream("ps.txt");
PrintWriter ps = new PrintWriter("ps.txt");

4.3 常用方法

  • print(数据类型 变量名):不换行输出数据到目标文件
  • println(数据类型 变量名):自动换行输出数据到目标文件
  • 同样有write()方法,print方法里面包含write()

5 各种IO流的选取

字节流输入流:InputStream 所有字节输入流的父类
	- FileInputStream:字节非缓冲输入流,效率低,不推荐直接使用
	- BufferedInputStream:字节缓冲输入流,效率高,推荐使用
	- ObjectInputStream:对象输入流,当需要从文件中读取自定义对象时使用

字节输出流:OutputStream 所有字节输出流的父类
	- FileOutputStream:字节非缓冲输出流 效率低,不推荐直接使用
	- BufferedOutputStream:字节缓冲输出流,效率高,推荐使用
	- ObjectOutputStream:对象输出流,当需要保存自定义对象到文件中时使用
	- PrintStream:字节打印流,当希望原样输出各种数据类型的值时使用

字符流输入流:Reader 所有字符输入流的父类
	- FileReader:字符非缓冲输入流,效率低,不推荐直接使用
	- BufferedReader:字符缓冲输入流,效率高,推荐使用
	- InputStreamReader:字符转换输入流,当需要修改默认码表去读数据时使用,否则不推荐使用

字符输出流:Writer 所有字符输出流的父类
	- FileWriter:字符非缓冲输出流 ,效率低,不推荐直接使用
	- BufferedWriter:字符缓冲输出流,效率高,推荐使用
	- OutputStreamWriter:字符转换输出流,当需要修改码表去输出数据时使用
	- PrintWriter:字符打印流,当希望原样输出各种数据类型的值时使用

如何选择流
	 如果清楚要操作的文件类型是文本文件,则强烈推荐字符流。
	 如果不清楚要操作的文件类型是什么类型的,则只能选择字节流
		

字节流输入流:InputStream 所有字节输入流的父类
	- FileInputStream:字节非缓冲输入流,效率低,不推荐直接使用
	- BufferedInputStream:字节缓冲输入流,效率高,推荐使用
	- ObjectInputStream:对象输入流,当需要从文件中读取自定义对象时使用

字节输出流:OutputStream 所有字节输出流的父类
	- FileOutputStream:字节非缓冲输出流 效率低,不推荐直接使用
	- BufferedOutputStream:字节缓冲输出流,效率高,推荐使用
	- ObjectOutputStream:对象输出流,当需要保存自定义对象到文件中时使用
	- PrintStream:字节打印流,当希望原样输出各种数据类型的值时使用

字符流输入流:Reader 所有字符输入流的父类
	- FileReader:字符非缓冲输入流,效率低,不推荐直接使用
	- BufferedReader:字符缓冲输入流,效率高,推荐使用
	- InputStreamReader:字符转换输入流,当需要修改默认码表去读数据时使用,否则不推荐使用

字符输出流:Writer 所有字符输出流的父类
	- FileWriter:字符非缓冲输出流 ,效率低,不推荐直接使用
	- BufferedWriter:字符缓冲输出流,效率高,推荐使用
	- OutputStreamWriter:字符转换输出流,当需要修改码表去输出数据时使用
	- PrintWriter:字符打印流,当希望原样输出各种数据类型的值时使用

如何选择流
	 如果清楚要操作的文件类型是文本文件,则强烈推荐字符流。
	 如果不清楚要操作的文件类型是什么类型的,则只能选择字节流