zoukankan      html  css  js  c++  java
  • java进阶知识--缓冲流、转换流、序列化流、打印流

    上篇介绍了IO基础概念,IO流的基类、文件流等。此篇将介绍一些更强大的流,比如能够高效读写的缓冲流、能够转换编码的转换流、能够持久化存储对象的序列化流等等。

    一、缓冲流

     1.1 概述

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

        按数据类型分类:

      • 字节缓冲流BufferedInputStreamBufferedOutputStream

      • 字符缓冲流BufferedReaderBufferedWriter

     1.2 字节缓冲流

       构造方法

      • public BufferedInputStream(InputStream in) :创建一个新的缓冲输入流。

      • public BufferedOutputStream(OutputStream out): 创建一个新的缓冲输出流。

      1.3 字符缓冲流

       构造方法

      • public BufferedReader(Reader in) :创建一个新的缓冲输入流。

      • public BufferedWriter(Writer out): 创建一个新的缓冲输出流。

       特有方法

        字符缓冲流的基本方法与普通字符流调用方式一致,不再阐述,我们来看它们具备的特有方法。

      • BufferedReader:public String readLine(): 读取一个文本行。 

    public class BufferedReaderDemo {
        public static void main(String[] args) throws IOException {
          	 // 创建流对象
            BufferedReader br = new BufferedReader(new FileReader("in.txt"));
    		// 定义字符串,保存读取的一行文字
            String line  = null;
          	// 循环读取,读取到最后返回null
            while ((line = br.readLine()) != null) {
                System.out.print(line);
                System.out.println("------");
            }
    		// 释放资源
            br.close();
        }
    }
      • BufferedWriter:public void newLine(): 写入一个行分隔符。行分隔符字符串由系统属性 line.separator 定义,并且不一定是单个新行 (' ') 符。

    public class BufferedWriterDemo throws IOException {
        public static void main(String[] args) throws IOException  {
          	// 创建流对象
    		BufferedWriter bw = new BufferedWriter(new FileWriter("out.txt"));
          	// 写出数据
            bw.write("你好啊");
          	// 写出换行
            bw.newLine();
            bw.write("老铁");
            bw.newLine();
    		// 释放资源
            bw.close();
        }
    }

    二、转换流

     2.1 字符编码和字符集

       2.1.1 字符编码 Character Encoding

        概述:就是一套自然语言的字符与二进制数之间的对应规则

      •  编码:按照某种规则,将字符转换成二进制数存储到计算机中。字符到字节的过程。
      •  解码:将存储在计算机中的二进制数按照某种规则显示出来。字节到字符的过程。

       2.1.2 字符集 Charset

        概述:字符集也叫编码表,是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等。

     编码表:生活中文字和计算机中二进制的对应规则。

        计算机要准确的存储和识别各种字符集符号,需要进行字符编码,一套字符集必然至少有一套字符编码。常见字符集有ASCII字符集、GBK字符集、Unicode字符集等。

        可见,当指定了编码,它所对应的字符集自然就指定了,所以编码才是我们最终要关心的。

    • ASCII字符集

      • ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统,用于显示现代英语,主要包括控制字符(回车键、退格、换行键等)和可显示字符(英文大小写字符、阿拉伯数字和西文符号)。

      • 基本的ASCII字符集,使用7位(bits)表示一个字符,共128字符。ASCII的扩展字符集使用8位(bits)表示一个字符,共256字符,方便支持欧洲常用字符。

    • ISO-8859-1字符集

      • 拉丁码表,别名Latin-1,用于显示欧洲使用的语言,包括荷兰、丹麦、德语、意大利语、西班牙语等。

      • ISO-8859-1使用单字节编码,兼容ASCII编码。

    • GBxxx字符集

      • GB就是国标的意思,是为了显示中文而设计的一套字符集。

      • GB2312:简体中文码表。一个小于127的字符的意义与原来相同。但两个大于127的字符连在一起时,就表示一个汉字,这样大约可以组合了包含7000多个简体汉字,此外数学符号、罗马希腊的字母、日文的假名们都编进去了,连在ASCII里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的"全角"字符,而原来在127号以下的那些就叫"半角"字符了。

      • GBK:最常用的中文码表。是在GB2312标准基础上的扩展规范,使用了双字节编码方案,共收录了21003个汉字,完全兼容GB2312标准,同时支持繁体汉字以及日韩汉字等。

      • GB18030:最新的中文码表。收录汉字70244个,采用多字节编码,每个字可以由1个、2个或4个字节组成。支持中国国内少数民族的文字,同时支持繁体汉字以及日韩汉字等。

    • Unicode字符集

      • Unicode编码系统为表达任意语言的任意字符而设计,是业界的一种标准,也称为统一码、标准万国码。

      • 最多使用4个字节的数字来表达每个字母、符号,或者文字。有三种编码方案,UTF-8、UTF-16和UTF-32。最为常用的UTF-8编码。

      • UTF-8编码,可以用来表示Unicode标准中任何字符,它是电子邮件、网页及其他存储或传送文字的应用中,优先采用的编码。互联网工程工作小组(IETF)要求所有互联网协议都必须支持UTF-8编码。所以,我们开发Web应用,也要使用UTF-8编码。它使用一至四个字节为每个字符编码,编码规则:

        1. 128个US-ASCII字符,只需一个字节编码。

        2. 拉丁文等字符,需要二个字节编码。

        3. 大部分常用字(含中文),使用三个字节编码。

        4. 其他极少使用的Unicode辅助字符,使用四字节编码。

     2.2 InputStreamReader类

       2.2.1 概述

        转换流java.io.InputStreamReader,是Reader的子类,是FileReader的父类,是从字节流到字符流的桥梁。它读取字节,并使用指定的字符集将其解码为字符。它的字符集可以由名称指定,也可以接受平台的默认字符集。

       2.2.2 构造方法

      • InputStreamReader(InputStream in): 创建一个使用默认字符集的字符流。

      • InputStreamReader(InputStream in, String charsetName): 创建一个指定字符集的字符流。

     2.3 OutputStreamWriter类

       2.3.1 概述

        转换流java.io.OutputStreamWriter ,是Writer的子类,是FileWriter的父类,是从字符流到字节流的桥梁。使用指定的字符集将其编码为字节。它的字符集可以由名称指定,也可以接受平台的默认字符集。

       2.3.2 构造方法

      • OutputStreamWriter(OutputStream in): 创建一个使用默认字符集的字符流。

      • OutputStreamWriter(OutputStream in, String charsetName): 创建一个指定字符集的字符流。

    三、序列化流

     3.1 概述

        Java 提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,该字节序列包含该对象的数据对象的类型对象中存储的属性等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化对象的数据对象的类型对象中存储的数据信息,都可以用来在内存中创建对象。

        序列化把对象转换为字节序列,写入到文件中的过程称为对象的序列化,也叫写对象。也就是将对象写入到IO流中,是一种用来处理对象流的机制。(所谓对象流也就是将对象的内容进行流化。)

        序列化的作用:是为了解决在对对象流进行读写操作时所引发的问题。序列化机制允许将实现序列化的Java对象转换为字节序列,这些字节序列可以保存在磁盘上,或通过网络传输,以达到以后恢复成原来的对象。序列化机制使得对象可以脱离程序的运行而独立存在。

        实现序列化的步骤

          1. 相关对象实现Serializable接口;

          2. 创建一个ObjectOutputStream输出流;

          3. 调用ObjectOutputStream对象的writeObject(Object obj)方法输出可序列化对象。

    实现对象序列化需要满足的条件以及注意点:

    • 要对一个对象序列化,这个对象就需要实现Serializable接口,如果这个对象中有一个变量是另一个对象的引用,则引用的对象也要实现Serializable接口,这个过程是递归的。Serializable接口中没有定义任何方法,只是作为一个标记来指示实现该接口的类可以进行序列化。
    • 该类必须实现java.io.Serializable 接口,Serializable 是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException

    • 序列化只能保存对象的非静态成员变量,而不能保存任何成员方法和静态成员变量,并且保存的只是变量的值,变量的修饰符对序列化没有影响。
    • 该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化,或者因为其数据的特性决定了它会经常变化而不具有可持久性,则该属性必须注明是瞬态的,使用transient 关键字修饰,否则编译器将报错。任何用 transient 关键字标明的成员变量,都不会被保存。

    • 序列化可能涉及将对象存放到磁盘上或在网络上发送数据,这时会产生安全问题。对于一些需要保密的数据(如用户密码等),不应保存在永久介质中,为了保证安全,应在这些变量前也加上 transient 关键字。

        反序列化将序列化过程中所生成的字节序列,读取出来转换成对象的过程成为对象的反序列化,也叫读对象。

        实现反序列化的步骤

          1. 创建一个ObjectInputStream输入流;

          2. 调用ObjectInputStream对象的readObject()方法得到序列化对象。

     注意:反序列化的对象是由 JVM 自己生成的,不通过构造方法生成。

     3.2 ObjectOutputStream类

        java.io.ObjectOutputStream 序列化流,将Java对象的原始数据类型写出到文件,实现对象的持久存储。

       构造方法

      • public ObjectOutputStream(OutputStream out): 创建一个指定输出流的序列化流。

       写出对象方法

      • public final void writeObject(Object obj) : 将指定的对象写出。

     3.3 ObjectInputStream类

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

       构造方法

      • public ObjectInputStream(InputStream in): 创建一个指定InputStream的ObjectInputStream。

       读取对象方法

      • public final Object readObject() : 读取一个对象。

     注意:1. 对于JVM可以反序列化对象,它必须是能够找到class文件的类。如果找不到该类的class文件,则抛出一个 ClassNotFoundException 异常。

        2. 当JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个InvalidClassException异常(序列化冲突异常)。

          发生InvalidClassException异常的原因如下:

        • 该类的序列版本号与从流中读取的类描述符的版本号不匹配

        • 该类包含未知数据类型

        • 该类没有可访问的无参数构造方法

          如何解决InvalidClassException异常,来保证对象前后的兼容性?

          Serializable 接口给需要序列化的类,提供了一个序列版本号。serialVersionUID 该版本号的目的在于验证序列化的对象和对应类是否版本匹配。

     3.4 序列化版本号serialVersionUID

        java序列化提供了一个private static final long serialVersionUID 的序列化版本号,只有版本号相同,即使更改了序列化属性,对象也可以正确被反序列化回来。

     注意:序列化版本号可自由指定。

        如果不指定

          ① JVM会根据类信息自己计算一个版本号,这样随着class的升级,就无法正确反序列化;

          ② 不利于jvm间的移植,可能class文件没有更改,但不同jvm可能计算的规则不一样,这样也会导致无法反序列化。

        什么情况下需要修改serialVersionUID呢?

      • 如果只是修改了方法,反序列化不受影响,则无需修改版本号;
      • 如果只是修改了静态变量,瞬态变量(transient修饰的变量),反序列化不受影响,无需修改版本号;
      • 如果修改了非瞬态变量,或者如果新类中实例变量的类型与序列化时类的类型不一致,则会反序列化失败,这时候需要更改serialVersionUID。如果只是新增了实例变量,则反序列化回来新增的是默认值;如果减少了实例变量,反序列化时会忽略掉减少的实例变量。

        序列化应用场景与注意点

      • 所有需要网络传输的对象都需要实现序列化接口,通过建议所有的javaBean都实现Serializable接口。
      • 对象的类名、实例变量(包括基本类型,数组,对其他对象的引用)都会被序列化;方法、类变量、transient实例变量都不会被序列化。
      • 如果想让某个变量不被序列化,使用transient修饰。
      • 序列化对象的引用类型成员变量,也必须是可序列化的,否则,会报错。
      • 反序列化时必须有序列化对象的class文件。
      • 当通过文件、网络来读取序列化后的对象时,必须按照实际写入的顺序读取。
      • 单例类序列化,需要重写readResolve()方法;否则会破坏单例原则。
      • 同一对象序列化多次,只有第一次序列化为二进制流,以后都只是保存序列化编号,不会重复序列化。
      • 建议所有可序列化的类加上serialVersionUID 版本号,方便项目升级。

    小贴士:序列化流和反序列化流不同于字节流、字符流等,后者是把字符串写入/读取文件,前者可以将对象写入/读取文本文件中。

    四、打印流(了解)

     4.1 概述

        平时我们在控制台打印输出,是调用print方法和println方法完成的,这两个方法都来自于java.io.PrintStream类,该类能够方便地打印各种数据类型的值,是一种便捷的输出方式。

     4.2 PrintStream类

       构造方法

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

       改变打印流向

         System.out就是PrintStream类型的,只不过它的流向是系统规定的,打印在控制台上。不过,既然是流对象,我们就可以改变它的流向。

         方法:setOut(PrintStream out);  //改变输出的目的地

         比如,将设置系统的打印流流向,内容打印到文本文件中:

    public class PrintDemo {
        public static void main(String[] args) throws IOException {
    		// 调用系统的打印流,控制台直接输出97
            System.out.println(97);
          
    		// 创建打印流,指定文件的名称
            PrintStream ps = new PrintStream("ps.txt");
          	
          	// 设置系统的打印流流向,输出到ps.txt
            System.setOut(ps);
          	// 调用系统的打印流,ps.txt中输出97
            System.out.println(97);
        }
    }
    

      

  • 相关阅读:
    2.17-2.23第一周总结
    10号总结
    9日总结
    8号总结
    7号寒假总结
    6号
    读后感《程序员的修炼之道:从小工到专家》1
    java第二次动手动脑
    回文判断
    二进制的原码,反码以及补码介绍
  • 原文地址:https://www.cnblogs.com/sun9/p/13527553.html
Copyright © 2011-2022 走看看