十九、缓冲流、转换流、序列化流、打印流

1、缓冲流

缓冲流,也叫高效流,是对4个基本的 FileXxx 流的增强,所以也是4个流,按照数据类型分类:

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

缓冲流的基本原理,是在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO 次数,从而提高读写的效率。

(1)字节缓冲流
java.io.BufferedOutputStream extends OutputStream
BufferedOutputStream:字节缓冲输出流

java.io.BufferedInputStream extends InputStream
BufferedInputStream:字节缓冲输入流

构造方法:

  • BufferedOutputStream(OutputStream out) 创建一个新的缓冲输出流,以将数据写入指定的底层输出流。
  • BufferedInputStream(InputStream in) 创建一个 BufferedInputStream 并保存其参数,即输入流 in,以便将来使用。

代码实现:

public void testBufferedStream() throws IOException {

        // 创建流对象
        String src = "E:\\javatest文件\\src\\1.png";
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(src));
        String des = "E:\\javatest文件\\des\\bis.png";
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(des));

        //int read(byte[] b) 从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中。
        byte[] bytes = new byte[1024];//存储每次读取的数据
        int len = 0; //记录每次读取的有效字节个数
        while ((len = bis.read(bytes)) != -1) {
            bos.write(bytes, 0, len);
        }

        //释放资源
        bos.close();
        bis.close();
    }
(2)字符缓冲流
java.io.BufferedWriter extends Writer
BufferedWriter:字符缓冲输出流

java.io.BufferedReader extends Reader
BufferedReader:字符缓冲输入流

构造方法:

  • BufferedWriter(Writer out) 创建一个使用默认大小输出缓冲区的缓冲字符输出流。
  • BufferedReader(Reader in) 创建一个使用默认大小输入缓冲区的缓冲字符输入流。

特有方法:

  • BufferedWriter: void newLine() 写入一个行分隔符。会根据不同的操作系统,获取不同的行分隔符。
  • BufferedReader: String readLine() 读取一个文本行。读取一行数据行的终止符号:通过换行 ('\n')、回车 ('\r') 或回车后直接跟着换行(\r\n)字符之一即可认为某行已终止。

代码实现:

/*
3.侍中、侍郎郭攸之、费祎、董允等,此皆良实,志虑忠纯,是以先帝简拔以遗陛下。愚以为宫中之事,事无大小,悉 以咨之,然后施行,必得裨补阙漏,有所广益。
2.宫中府中,俱为一体,陟罚臧否,不宜异同。若有作奸犯科及为忠善者,宜付有司论其刑赏,以昭陛下平明之理,不 宜偏私,使内外异法也。 
1.先帝创业未半而中道崩殂,今天下三分,益州疲弊,此诚危急存亡之秋也。然侍卫之臣不懈于内,忠志之士忘身于外 者,盖追先帝之殊遇,欲报之于陛下也。诚宜开张圣听,以光先帝遗德,恢弘志士之气,不宜妄自菲薄,引喻失义,以 塞忠谏之路也。
*/
public void testBuffered() throws IOException {
        //1.创建一个HashMap集合对象,可以:存储每行文本的序号(1,2,3,..);value:存储每行的文本
        HashMap<String, String> map = new HashMap<>();
        //2.创建字符缓冲输入流对象,构造方法中绑定字符输入流
        String src = "E:\\javatest文件\\src\\in.txt";
        BufferedReader br = new BufferedReader(new FileReader(src));
        //3.创建字符缓冲输出流对象,构造方法中绑定字符输出流
        String des = "E:\\javatest文件\\des\\out.txt";
        BufferedWriter bw = new BufferedWriter(new FileWriter(des));
        //4.使用字符缓冲输入流中的方法readline,逐行读取文本
        String line;
        while ((line = br.readLine()) != null) {
            //5.对读取到的文本进行切割,获取行中的序号和文本内容
            String[] arr = line.split("\\.");
            //6.把切割好的序号和文本的内容存储到HashMap集合中(key序号是有序的,会自动排序1,2,3,4..)
            map.put(arr[0], arr[1]);
        }

        //7.遍历HashMap集合,获取每一个键值对
        for (String key : map.keySet()) {
            String value = map.get(key);
            //8.把每一个键值对,拼接为一个文本行
            line = key + "." + value;
            //9.把拼接好的文本,使用字符缓冲输出流中的方法write,写入到文件中
            bw.write(line);
            bw.newLine();//写换行
        }
        //10.释放资源
        bw.close();
        br.close();
    }

2、转换流

(1)字符编码和字符集
  • 字符编码 Character Encoding:就是一套自然语言的字符与二进制数之间的对应规则。
  • 字符集 Charset:也叫编码表。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符 号、数字等。

常见字符 集有ASCII字符集、GBK字符集、Unicode字符集等。

  • ASCII码表:0~ 9(48~ 57)、A~ Z(65~ 90)、a~ z(97~ 122)
  • GBK:最常用的中文码表。是在GB2312(简体中文码表)标准基础上的扩展规范,使用了双字节编码方案。
  • UTF-8:它使用一至四个字节为每个字符编码。
    编码规则:①128个US-ASCII字符,只需一个字节编码。 ②拉丁文等字符,需要二个字节编码。 ③大部分常用字(含中文),使用三个字节编码。④其他极少使用的Unicode辅助字符,使用四字节编码。
(2)InputStreamReader和OutputStreamWriter
java.io.OutputStreamWriter extends Writer
OutputStreamWriter:是字符流通向字节流的桥梁:可使用指定的 charset 将要写入流中的字符编码成字节。(编码:把能看懂的变成看不懂)

java.io.InputStreamReader extends Reader
InputStreamReader:是字节流通向字符流的桥梁:它使用指定的 charset 读取字节并将其解码为字符。(解码:把看不懂的变成能看懂的)

构造方法:

  • OutputStreamWriter(OutputStream out):创建使用默认字符编码的 OutputStreamWriter。
  • OutputStreamWriter(OutputStream out, String charsetName): 创建使用指定字符集的 OutputStreamWriter。
  • InputStreamReader(InputStream in) 创建一个使用默认字符集的 InputStreamReader。
  • InputStreamReader(InputStream in, String charsetName) 创建使用指定字符集的 InputStreamReader。

小贴士:String charsetName表示指定的编码表名称,不区分大小写,可以是utf-8/UTF-8、gbk/GBK、...不指定默认使用UTF-8。

代码实现:

/*将GBK编码的文本文件,转换为UTF-8编码的文本文件。*/
public void testReverseStream() throws IOException {
       //1.创建InputStreamReader对象,构造方法中传递字节输入流和指定的编码表名称GBK
        String src = "E:\\javatest文件\\src\\gbk.txt";
        InputStreamReader isr = new InputStreamReader(new FileInputStream(src), "GBK");
        //2.创建OutputStreamWriter对象,构造方法中传递字节输出流和指定的编码表名称UTF-8
        String des = "E:\\javatest文件\\des\\utf8.txt";
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(des), "UTF-8");
        //3.使用InputStreamReader对象中的方法read读取文件
        char[] cs = new char[1024];//存储读取到的多个字符
        int len = 0;//记录的是每次读取的有效字符个数
        while ((len = isr.read(cs)) != -1) {
            //4.使用OutputStreamWriter对象中的方法write,把读取的数据写入到文件中
            osw.write(cs, 0, len);
        }
        //5.释放资源
        osw.close();
        isr.close();
    }

3、序列化流

01- 看图理解序列化.jpg
(1)ObjectOutputStream和ObjectInputStream
java.io.ObjectOutputStream extends OutputStream
ObjectOutputStream(对象的序列化流):把对象以流的方式写入到文件中保存。

java.io.ObjectInputStream extends InputStream
ObjectInputStream(对象的反序列化流):把文件中保存的对象,以流的方式读取出来使用。

构造方法:

  • ObjectOutputStream(OutputStream out) 创建写入指定 OutputStream 的 ObjectOutputStream。
  • ObjectInputStream(InputStream in) 创建从指定 InputStream 读取的 ObjectInputStream。

特有方法:

  • ObjectOutputStream:void writeObject(Object obj) 将指定的对象写入ObjectOutputStream。
  • ObjectInputStream:Object readObject() 从ObjectInputStream读取对象。

序列化操作:
一个对象要想序列化,必须满足两个条件:

  • 该类必须实现 java.io.Serializable 接口, Serializable是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出 NotSerializableException。
  • 该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用 transient 关键字修饰。

反序列化操作:
对于JVM可以反序列化对象,它必须是能够找到class文件的类。
如果找不到该类的class文件,则抛出一个 ClassNotFoundException 异常。
如果能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操 作也会失败,抛出一个 InvalidClassException 异常。
发生这个异常的原因如下:

  • 该类的序列版本号与从流中读取的类描述符的版本号不匹配
  • 该类包含未知数据类型
  • 该类没有可访问的无参数构造方法

小贴士:
Serializable 接口给需要序列化的类,提供了一个序列版本号。 serialVersionUID 该版本号的目的在于验证序列化的对象和对应类是否版本匹配。
被static修饰的成员变量不能被序列化的,序列化的都是对象。
被transient修饰成员变量,不能被序列化。

02- 序列号冲突异常的原理和解决方案.png

代码实现:

/*将GBK编码的文本文件,转换为UTF-8编码的文本文件。*/
public class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    //被transient修饰成员变量,不能被序列化
    private transient String sex;
    private int age;

    public Person() {
    }

    public Person(String name, String sex, int age) {
        this.name = name;
        this.sex = sex;
        this.age = age;
    }
    //省略toString方法和get、set方法
}

public void testObjectStream() throws IOException, ClassNotFoundException {
        //1.定义一个存储Person对象的ArrayList集合
        ArrayList<Person> list = new ArrayList<>();
        //2.往ArrayList集合中存储Person对象
        list.add(new Person("张三", "男", 18));
        list.add(new Person("李四", "男", 19));
        list.add(new Person("王五", "男", 20));
        //3.创建一个序列化流ObjectOutputStream对象
        String pathName = "E:\\javatest文件\\obj.txt";
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(pathName));
        //4.使用ObjectOutputStream对象中的方法writeObject,对集合进行序列化
        oos.writeObject(list);
        //5.创建一个反序列化ObjectInputStream对象
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(pathName));
        //6.使用ObjectInputStream对象中的方法readObject读取文件中保存的集合
        Object o = ois.readObject();
        //7.把Object类型的集合转换为ArrayList类型
        ArrayList<Person> list2 = (ArrayList<Person>) o;
        //8.遍历ArrayList集合
        for (Person p : list2) {
            System.out.println(p);
            //打印输出:
            //Person{name='张三', sex='null', age=18}
            //Person{name='李四', sex='null', age=19}
            //Person{name='王五', sex='null', age=20}
        }
        //9.释放资源
        ois.close();
        oos.close();
    }

4、打印流

(1)PrintStream
java.io.PrintStream extends OutputStream
PrintStream(打印流):为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式。

构造方法:

  • PrintStream(File file):输出的目的地是一个文件
  • PrintStream(OutputStream out):输出的目的地是一个字节输出流。
  • InputStreamReader(InputStream in) 创建一个使用默认字符集的 InputStreamReader。
  • PrintStream(String fileName):输出的目的地是一个文件路径。

改变打印流向:
System.out 就是 PrintStream 类型的,只不过它的流向是系统规定的,打印在控制台上。既然是流对象, 我们就可以改变它的流向。
使用System.setOut方法改变输出语句的目的地改为参数中传递的打印流的目的地:static void setOut(PrintStream out)重新分配“标准”输出流。

注意:
如果使用继承自父类的write方法写数据,那么查看数据的时候会查询编码表 97->a。
如果使用自己特有的方法print/println方法写数据,写的数据原样输出 97->97。

代码实现:

public void testPrintStream() throws IOException {

        //创建打印流PrintStream对象,构造方法中绑定要输出的目的地
        String pathName = "E:\\javatest文件\\print.txt";
        PrintStream ps = new PrintStream(pathName);

        //如果使用继承自父类的write方法写数据,那么查看数据的时候会查询编码表 97->a
        ps.write(97);
        ps.write("\r\n".getBytes());
        //如果使用自己特有的方法print/println方法写数据,写的数据原样输出 97->97
        ps.println(97);
        ps.println(8.8);
        ps.println('a');
        ps.println("HelloWorld");
        ps.println(true);

        System.out.println("在控制台输出");
        System.setOut(ps);//把输出语句的目的地改变为打印流的目的地
        System.out.println("在打印流的目的地中输出");

        //释放资源
        ps.close();
    }
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容