5. Java入门之流是什么?流的分类、方法及应用(超详细介绍,含相关练习)

1. 流的概述

,又称为IO流,它是用于系统或文件的输入输出。其中IOInputOutput的缩写

  • Input代表输入(读取),把硬盘中的数据读取到内存中使用
  • Output代表输出(写入),把内存中的数据写入到硬盘中保存

注意:输入与输出是对内存而言,读取与写入是对硬盘而言

那话说回来,为什么需要这个众所周知,程序是在内存中运行的,而运行过程中产生的数据自然而然也是最先出现在内存当中,如果我想要生成一个运行日志或者保存一些数据以供日后查看,那是不可能保存在内存中的,因为内存当中的内容断电(或关闭程序)就会消失,因此需要保存在我们的硬盘

怎么保存呢?这时候的作用就体现出来了,举一个简单的例子:你面前有两个水桶,一个装满水(内存),一个没有水(硬盘),你想把装满水的水桶中的水(数据)转换(保存)到另外一个桶,显然这个过程你是不可能瞬间完成的,你得借助一些工具,例如水管或水瓢()来协助这个转换(保存)的过程

也许上面所举的这个栗子不那么恰当,但是也可大致帮助理解流的大概作用和概念

2. 字节流

一切的文件数据在储存时候,都是以二进制数字的形式进行储存的,都是一个一个的字节(一字节=八个二进制位),那么自然而然,在传输的时候也是如此,所以自然而然任意文件数据都可以以字节的形式进行传输,也称为字节流,随后的章节还会介绍其他类型的流,但要注意的是其底层的传输形式始终是二进制数据

注意:这一章节内容需要好好理解,随后章节所介绍的在其构造方法和成员方法及使用上与字节流非常相似

2.1 字节输出流

字节输出流,顾名思义就是表示从内存中输出字节到硬盘进行保存,下面来介绍一下它的相关类、构造方法与成员方法

  • java.io.OutputStream字节输出流,这是一个抽象类,是所有表示输出字节流的超类,在这个类当中定义了一些子类共性的成员方法(其实我们也只需要关注这个内容):
public void close():关闭此输出流并释放与此流有关的所有系统资源。 
public void flush():刷新此输出流并强制写出所有缓冲的输出字节。 
public void write(byte[] b):将 b.length 个字节从指定的 byte 数组写入此输出流。 
public void write(byte[] b, int off, int len):将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流。 
public abstract void write(int b):将指定的字节写入此输出流。
  • java.io.FileOutputStream文件字节输出流,这是OutputStream实现类,这也是本节重点介绍和使用的一个类,他的作用是把内存中的数据写入到硬盘的文件当中,他常用的构造方法有四种:
FileOutputStream(File file):创建一个向指定 File 对象表示的文件中写入数据的文件输出流,若该文件已经存在则覆盖。 
FileOutputStream(File file, boolean append):创建一个向指定 File 对象表示的文件中写入数据的文件输出流,append为续写开关。 
FileOutputStream(String name):创建一个向具有指定路径的文件中写入数据的输出文件流,若该文件已经存在则覆盖。 
FileOutputStream(String name, boolean append):创建一个向具有指定路径的文件中写入数据的输出文件流,append为续写开关。 

看文字是不是很没意思?是不是很抽象?下面我们来实现一把,演示一下字节输出流写入数据到文件!
在这里插入图片描述
源码展示完毕,下面来看看成果,可以到指定路径处找到该txt文件,用记事本或Notepad++打开
在这里插入图片描述
如果你的文件产生乱码,可以看看右下角处是不是UTF-8,这是IDEA的默认编码方式,如果你的不是该格式则可以在左上角 文件 -> 另存为 -> 格式更改UTF-8再次查看,关于编码的内容在第五章有详细介绍
在这里插入图片描述
如果这时你发现输入的字都挤在一堆,不好看,想每次输入之间换个行,有没有办法呢?有!

fos.write("\n".getBytes());
fos.write("\r".getBytes());
fos.write("\r\n".getBytes());

三种方法,任意挑选!我们来改一改代码
在这里插入图片描述
再来看一看效果
在这里插入图片描述
本小节的最后再介绍一下写入数据的原理,你以为全部都是Java或者JVM完成吗?其实不是的,它们还是要依靠我们的OS(操作系统),其大致流程如下:
Java程序 --> JVM(Java虚拟机) --> OS(操作系统) --> OS调用写数据的方法 --> 把数据写到文件中

2.2 字节输入流

有输出就会有输入,当我要读取计算机中的文件,我就要用到字节输入流,按照国际惯例,下面来介绍一下它的相关类、构造方法与成员方法

  • java.io.InputStream字节输入流,这是一个抽象类,是所有表示输入字节流的超类,在这个类当中定义了一些子类共性的成员方法(其实我们也只需要关注这个内容):
public abstract int read():从输入流中读取数据的下一个字节。 
public int read(byte[] b):从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中。 
public void close():关闭此输入流并释放与该流关联的所有系统资源。 
  • java.io.FileIntputStream文件字节输入流,这是IntputStream实现类,这也是本节重点介绍和使用的一个类,他的作用是把硬盘中的数据读取到内存中使用,他常用的构造方法有两种:
FileInputStream(File file):通过打开一个到实际文件的连接来创建一个 FileInputStream,该文件通过文件系统中的 File 对象 file 指定。 
FileInputStream(String name):通过打开一个到实际文件的连接来创建一个 FileInputStream,该文件通过文件系统中的路径名 name 指定。

同样的,下面我们来实现一把,贴上演示代码,这是一次读取一个字节的输入方式:
在这里插入图片描述
这是利用数组一次读取多个字节的输入方式,效率更高
在这里插入图片描述
两种方式的运行结果是一致的,只是效率不同,下面来看看运行的结果,此处的test.txt文件为上一小节保存的文件。你发现89对应的Y有了,World有了,但是我的中文“你好”哪里去了?
在这里插入图片描述
这就是使用字节流读取中文时候会产生的问题,因为一个中文字符可能会占用多个字节进行储存,那读取时候就有可能产生混乱,因此Java为此提供了字符流,专门用于处理文本文件,详细请看第三章

2.3 字节输入流读取原理

为方便演示其原理,我们在记事本中只保存ABCDE五个英文字母,重新写了一段演示代码来说明他的原理

首先是一次读取一个字节的方式:
在这里插入图片描述
在这里插入图片描述
下面来个示意图
在这里插入图片描述
若是该种一次读取一个数据的形式,那么硬盘就会有一个数据指针,读取后就指向下一个待读取数据,一次读取一个之后就交给OS–>JVM–>Java程序,最后打印输出,显然该过程是非常的,于是有了第二种方式,一次读取多个字节:
在这里插入图片描述
在这里插入图片描述
注意这里,我以每次两个字节的方式输出,为什么最后会是ED呢,理想情况应该是E,那么下面也来画图说一下多个字节的传输原理
在这里插入图片描述
我定义了长度为2的字节数组,因此每次传输都会传输2个字节的内容,每次读取到新的数据都会把旧数据所擦除,而最后的一次读取只有一个数据,因此只把数组的第一个位置擦除写入新的,而第二的位置的保持不变,因而会返回ED,这时候则可以利用读取长度len来控制程序的输出:
在这里插入图片描述
此时结果输出如下:
在这里插入图片描述

2.4 练习-文件复制

字节流相关的内容已经介绍完了,这里给出一个练习题加以巩固:
从C盘中复制一张照片(或其他文件)到D盘中

下面附上参考代码以供参考:
在这里插入图片描述
另外自己可以尝试把代码更改为一次读取一个字节的形式,或者是更改字节数组的大小(一般设置为1024整数倍),对比复制文件的速度

3. 字符流

在上一章节当中,我们发现使用字节流读取中文时会产生乱码,那么怎么去解决这个问题呢?使用字符流来传输文本文件!字符流,顾明思议是用来传输字符的,他为我们传输文本文件提供了极大的便利,因此也可称之为便捷类,但要注意的是,字符流的底层其实依然是用字节流进行传输

3.1 字符输出流

字符输出流,顾名思义就是表示从内存中输出字符到硬盘进行保存,下面来介绍一下它的相关类、构造方法与成员方法

  • java.io.Writer字符输出流,这是一个抽象类,是所有表示输出字符流的超类,在这个类当中定义了一些子类共性的成员方法(其实我们也只需要关注这个内容):
public abstract void close():关闭此流,但要先刷新它。 
public abstract void flush():刷新该流的缓冲。 
public void write(char[] cbuf):写入字符数组。 
public abstract void write(char[] cbuf, int off, int len):写入字符数组的某一部分。 
public void write(int c):写入单个字符。 
public void write(String str):写入字符串。 
public void write(String str, int off, int len):写入字符串的某一部分。 
  • java.io.FileWriter文件字符输出流,同样是一个便捷类,这是OutputStreamWriter子类,而OutputStreamWriterWriter实现类,这里与上一章节略有不同,注意分辨。FileWriter是本节重点介绍和使用的一个类,他的作用是把内存中的数据写入到硬盘的文件当中,他常用的构造方法有四种:
FileWriter(File file):根据给定的 File 对象构造一个 FileWriter 对象。 
FileWriter(File file, boolean append):根据给定的 File 对象构造一个 FileWriter 对象。 
FileWriter(String fileName):根据给定的文件路径构造一个 FileWriter 对象。 
FileWriter(String fileName, boolean append):根据给定的文件路径以及指示是否附加写入数据的 boolean 值来构造 FileWriter 对象。 

同样的,文字枯燥无味,下面我们来实现一把,贴上演示代码:
在这里插入图片描述
来看一下所生成的文件
在这里插入图片描述
注意此处与字节流同样的,在0-127之间的数字都会被翻译ASCII表中对应的字符,倘若你真的想存储78这个数字,可以采用字符串的形式:
在这里插入图片描述
注意此处续写开关是打开了的,因此其生成的文件内容是这样的:
在这里插入图片描述
可见78已经正常显示出来并且是在原文件的基础上续写

同样的,如果只想保存一个字符串的某一部分,也是可以进行控制
在这里插入图片描述
结果如下:
在这里插入图片描述

3.2 字符输入流

字符输入流,顾名思义就是表示从硬盘中输入字符到内存中使用,下面来介绍一下它的相关类、构造方法与成员方法

  • java.io.Reader字符输入流,这是一个抽象类,是所有表示输入字符流的超类,在这个类当中定义了一些子类共性的成员方法(其实我们也只需要关注这个内容):
public abstract  void close():关闭该流并释放与之关联的所有资源。 
public int read():读取单个字符。 
public int read(char[] cbuf):将字符读入数组。 
  • java.io.FileReader文件字符输入流,同样是一个便捷类,这是InputStreamReader子类,而InputStreamReaderReader实现类FileReader是本节重点介绍和使用的一个类,他的作用是把硬盘中的数据读取字符到内存当中使用,他常用的构造方法有两种:
FileReader(File file):在给定从中读取数据的 File 的情况下创建一个新 FileReader。
FileReader(String fileName):在给定从中读取数据的文件路径的情况下创建一个新 FileReader。

同样的,文字枯燥无味,下面我们来实现一把,贴上演示代码:
在这里插入图片描述
其输出结果
在这里插入图片描述
其使用方法与字节流大同小异,此处不做过多叙述,同样该章的练习题目与上一章也类似,可自行尝试

3.3 流中的异常处理

看到这里,有跟着练习的同学们应该会发现,所使用的语句基本上都会有抛出异常,当然最简单的处理方式是把它throws声明抛出,而此处将介绍一下try...catch...finally的方法

首先是在JDK 1.7之前的处理方法一般格式是:

try{
		可能会产生异常的代码
}catch(异常类变量 变量名){
		异常处理逻辑(通常也是打印输出)
}finally{
		一定要执行的代码(释放资源)
}

而在JDK 1.7中,出现了一个新特性,该特性允许在try后增加一个括号,可以在该括号中定义流对象,那么这个流对象的作用域就在try中,当try中的代码执行完毕(包括有异常的情况),会自动把流对象释放资源,不需要写fianlly,其一般格式是:

try(定义流对象;定义流对象;...;...){
		可能会产生异常的代码
}catch(异常类变量 变量名){
		异常处理逻辑(通常也是打印输出)
}

其实针对该异常处理,在JDK 1.9中也有新特性出现,但个人认为不如JDK 1.7中的方便,有兴趣的同学可以百度了解

4. 缓冲流

前面介绍了我们平时使用较多的四种类型的流,而缓冲流则是对前面介绍四种流的增强。那么,增强哪里了呢?还记得前面所说的读取时候可用字节/字符数组的方式提高速度吗?对,那是个很有用的方法,但是还不够快!因此缓冲流在创建流对象时候,会创建一个内置默认大小的缓冲区数组提高读写效率,所以缓冲流也被称为高速流,是对四个基本的FileXxxx流的增强

4.1 字节缓冲流

BufferedOutputStream-字节缓冲输出流
public class BufferedOutputStream extends FilterOutputStream extends OutputStream

构造方法

BufferedOutputStream(OutputStream out):创建一个新的缓冲输出流,以将数据写入指定的底层输出流。 
BufferedOutputStream(OutputStream out, int size):创建一个新的缓冲输出流,以将具有指定缓冲区大小的数据写入指定的底层输出流。 

成员方法

public void flush():刷新此缓冲的输出流。 
public void write(byte[] b, int off, int len):将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此缓冲的输出流。 
public void write(int b):将指定的字节写入此缓冲的输出流

在构造方法中,需要传递一个OutputStream对象,因OutputStream是抽象类,因此我们可以传递他的子类FileOutputStream

另外注意基本流FileOutputStream是在增强流BufferedOutputStream中使用的,因此关闭BufferedOutputStream时会自动关闭FileOutputStream

BufferedInputStream-字节缓冲输入流
public class BufferedInputStream extends FilterInputStream extends InputStream

构造方法

BufferedInputStream(InputStream in):创建一个 BufferedInputStream 并保存其参数,即输入流 in,以便将来使用。 
BufferedInputStream(InputStream in, int size):创建具有指定缓冲区大小的 BufferedInputStream 并保存其参数,即输入流 in,以便将来使用。 

成员方法

public void close():关闭此输入流并释放与该流关联的所有系统资源。
public int read():参见 InputStream 的 read 方法的常规协定。 
public int read(byte[] b, int off, int len):从此字节输入流中给定偏移量处开始将各字节读取到指定的 byte 数组中。 

在构造方法中,需要传递一个InputStream对象,因InputStream是抽象类,因此我们可以传递他的子类FileInputStream

另外注意基本流FileInputStream是在增强流BufferedInputStream中使用的,因此关闭BufferedInputStream时会自动关闭FileInputStream

4.2 字符缓冲流

BufferedWriter-字符缓冲输出流
public class BufferedWriter extends Writer

构造方法

BufferedWriter(Writer out):创建一个使用默认大小输出缓冲区的缓冲字符输出流。 
BufferedWriter(Writer out, int sz):创建一个使用给定大小输出缓冲区的新缓冲字符输出流。 

成员方法

public void close():关闭此流,但要先刷新它。 
public void flush():刷新该流的缓冲。 
public void newLine():写入一个行分隔符。 
public void write(char[] cbuf, int off, int len):写入字符数组的某一部分。 
public void write(int c):写入单个字符。 
public void write(String s, int off, int len):写入字符串的某一部分。 

注意该类成员方法中,有个前面介绍中其他类所没有的方法newLine(),可利用该方法写入换行符号

在构造方法中,需要传递一个Writer对象,因Writer是抽象类,因此我们可以传递他子类OutputStreamWriter的子类FileWriter

另外注意基本流FileWriter是在增强流BufferedWriter中使用的,因此关闭BufferedWriter时会自动关闭FileWriter

BufferedReader-字符缓冲输入流
public class BufferedReader extends Reader

构造方法

BufferedReader(Reader in):创建一个使用默认大小输入缓冲区的缓冲字符输入流。 
BufferedReader(Reader in, int sz):创建一个使用指定大小输入缓冲区的缓冲字符输入流。 

成员方法

public void close():关闭该流并释放与之关联的所有资源。 
public int read():读取单个字符。 
public int read(char[] cbuf, int off, int len):将字符读入数组的某一部分。 
public String readLine():读取一个文本行。 

注意该类成员方法中,有个前面介绍中其他类所没有的方法readLine(),可利用该方法写入一行文本

在构造方法中,需要传递一个Reader对象,因Reader是抽象类,因此我们可以传递他子类InputStreamReader的子类FileReader

另外注意基本流FileReader是在增强流BufferedReader中使用的,因此关闭BufferedReader时会自动关闭FileReader

4.3 缓冲流的效率测试

前面说了缓冲流的作用是用于提高效率,那么究竟有没有起到这个作用呢,效果明不明显呢,我们可以利用不同的类复制同一个文件来测试其所花费的时间,测试源码如下:
(代码中123.jpg,234.jpg,345.jpg,456.jpg均为同一个文件不同命名,文件的大小为4,028,937字节[3.84MB])

public class CopyTest {
    public static void main(String[] args) throws IOException {

        FileInputStream fis = new FileInputStream("E:\\123.jpg");
        FileOutputStream fos = new FileOutputStream("D:\\456.jpg");
        int len = 0;
        long a1 = System.currentTimeMillis();
        while ((len = fis.read()) != -1){
            fos.write(len);
        }
        long a2 = System.currentTimeMillis();
        System.out.println("用基本fis与fos复制共耗时:" + (a2 - a1) + "毫秒");
        fos.close();
        fis.close();

        fis = new FileInputStream("E:\\234.jpg");
        fos = new FileOutputStream("D:\\567.jpg");
        byte[] bytes = new byte[1024];
        long b1 = System.currentTimeMillis();
        while ((len = fis.read(bytes)) != -1){
            fos.write(bytes,0,len);
        }
        long b2 = System.currentTimeMillis();
        System.out.println("用数组fis与fos复制共耗时:" + (b2 - b1) + "毫秒");
        fos.close();
        fis.close();

        fis = new FileInputStream("E:\\345.jpg");
        fos = new FileOutputStream("D:\\678.jpg");
        BufferedInputStream bis = new BufferedInputStream(fis);
        BufferedOutputStream bos = new BufferedOutputStream(fos);
        int len1 = 0;
        long c1 = System.currentTimeMillis();
        while ((len = bis.read()) != -1){
            bos.write(len1);
        }
        long c2 = System.currentTimeMillis();
        System.out.println("用基本bis与bos复制共耗时:" + (c2 - c1) + "毫秒");
        bos.close();
        bis.close();

        fis = new FileInputStream("E:\\456.jpg");
        fos = new FileOutputStream("D:\\789.jpg");
        bis = new BufferedInputStream(fis);
        bos = new BufferedOutputStream(fos);
        byte[] bytes1 = new byte[1024];
        long d1 = System.currentTimeMillis();
        while ((len = bis.read(bytes1)) != -1){
            bos.write(bytes1,0,len1);
        }
        long d2 = System.currentTimeMillis();
        System.out.println("用数组bis与bos复制共耗时:" + (d2 - d1) + "毫秒");
        bos.close();
        bis.close();

        System.out.println("测试完成!");
    }
}

为了测试结果更加准确,进行了两次测试,其结果如下:
在这里插入图片描述
显然,在采用基本流一次传输一个字节的方式进行传输,耗时非常久,但采用基本流一次传输多个字节的方式和增强流的一次传输一个字节的方式均有较大的提升,想要尽量提高传输效率,可以使用增强流的一次传输多个字节的方式以及增大缓冲数组的大小

4.4 练习-对文本内容进行排序

下面来做一个小练习,主要任务是对文本内容排序,该文本文件每一段中有序号且顺序乱了,要求排序为正常顺序,文件内容如下:
在这里插入图片描述
应该要按照其每段前面的序号来进行排序,基本思路为:把文件从硬盘读取HashMap集合中(因为该集合会自动排序,1.2.3…),然后把HashMap集合中的数据写入到硬盘。理论存在,实践开始,以下是源码:
在这里插入图片描述
完成排序后的结果:
在这里插入图片描述

5. 转换流

5.1 字符编码与字符集

字符编码:
计算机中储存的信息都是用二进制表示的,而我们在屏幕上所看到的数字、英文、标点符号等都是二进制数转换后的结果。按照某种规则,把字符储存在计算机当中,称为编码;而将计算机中的二进制数按照某种规则解析显示出来,称为解码。假如按照A规则储存,按照A规则解析,那是可以正常显示的,但是如果按照A规则储存,按照B规则解析则会乱码简而言之,字符编码就是自然语言和二进制数之间的对应规则

字符集:
字符集也称为编码表,包括各种文字、标点符号、数字、图形符号等,常见的字符集有ASCII字符集、GBK字符集、Unicode字符集等,其中Unicode字符集又分为UTF-8, UTF-16UTF-32,下面给出一些字符集的相关介绍:
在这里插入图片描述
在目前使用当中UTF-8最常用的编码,而IDEA也是采用UTF-8编码
在这里插入图片描述
那么,这跟转换流有什么关系呢?转换流,顾明思议是可以进行编码转换,即可以按照我所指定的格式进行输入输出

5.2 OutputStreamWriter介绍

public class OutputStreamWriter extends Writer

构造方法

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

注意charsetName为字符串输入,可输入GBK、gbk、UTF-8、utf-8

成员方法

public void close():关闭此流,但要先刷新它。 
public void flush():刷新该流的缓冲。 
public String getEncoding():返回此流使用的字符编码的名称。 
public void write(char[] cbuf, int off, int len):写入字符数组的某一部分。 
public void write(int c):写入单个字符。 
public void write(String str, int off, int len):写入字符串的某一部分。 

在构造方法中,需要传递一个OutputStream对象,因OutputStream是抽象类,因此我们可以传递他的子类FileOutputStream

另外注意基本流FileOutputStream是在转换流OutputStreamWriter中使用的,因此关闭OutputStreamWriter时会自动关闭FileOutputStream

给出一个简单的代码演示:
在这里插入图片描述

5.3 InputStreamReader介绍

public class InputStreamReader extends Reader

构造方法

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

注意charsetName为字符串输入,可输入GBK、gbk、UTF-8、utf-8

成员方法

public void close():关闭该流并释放与之关联的所有资源。 
public String getEncoding():返回此流使用的字符编码的名称。 
public int read():读取单个字符。 
public int read(char[] cbuf, int offset, int length):将字符读入数组中的某一部分。 

在构造方法中,需要传递一个InputStream对象,因InputStream是抽象类,因此我们可以传递他的子类FileInputStream

另外注意基本流FileInputStream是在转换流InputStreamReader中使用的,因此关闭InputStreamReader时会自动关闭FileInputStream

给出一个简单的代码演示:
在这里插入图片描述

5.4 练习-转换文件编码

转换流的基本使用已经介绍完了,下面尝试来做一下练习,把一个文件的编码格式进行转换,把GBK编码的文本文件转换为UTF-8编码的文本文件

首先我们准备一个GBK文本文件,新建一个记事本,打开左上角文件,点击另存为,编码改为ANSI(这个就是GBK编码),点击保存后可以查看记事本右下角是否成功,然后可以在里面随便输入文字后保存
在这里插入图片描述
准备工作做好后,开干,源码奉上:
在这里插入图片描述
可以查看保存的文件,已经转换成功:
在这里插入图片描述

6. 对象流

前面所介绍的都是保存一些英文字母、数字和字符串等,如果我想要保存一些自定义的对象(类),有没有办法呢?那肯定是毫无疑问的有!这时候就有个专业名词了,序列化与反序列化

  • 把对象以流的方式写入到文件中保存,称对象的序列化
  • 把文件中保存的对象以流的方式读取,称对象的反序列化

6.1 Properties集合

Properties集合是唯一一个与IO流相结合的集合,可以把集合中的数据写入到硬盘中储存,也可把硬盘中保存的文件(键值对)读取到集合中使用,为什么是键值对?因为Properties是一个双列集合,其keyvalue都是默认字符串

public class Properties extends Hashtable<Object,Object>

构造方法

Properties():创建一个无默认值的空属性列表。

成员方法有很多,我们只关注storeload

public void load(InputStream inStream):从输入流中读取属性列表(键和元素对)。
public void load(Reader reader):按简单的面向行的格式从输入字符流中读取属性列表(键和元素对)。 
public void store(OutputStream out, String comments):以适合使用 load(InputStream) 方法加载到 Properties 表中的格式,将此 Properties 表中的属性列表(键和元素对)写入输出流。 
public void store(Writer writer, String comments):以适合使用 load(Reader) 方法的格式,将此 Properties 表中的属性列表(键和元素对)写入输出字符。 

给出一个简单的代码演示
在这里插入图片描述
运行结果如下:
在这里插入图片描述
在这里插入图片描述

6.2 对象序列化流

public class ObjectOutputStream extends OutputStream

构造方法

ObjectOutputStream(OutputStream out):创建写入指定 OutputStream 的 ObjectOutputStream。

成员方法有很多,继承下来的成员方法此前已经进行介绍,此处我们只关注其特有的成员方法storeload

public void writeObject(Object obj):将指定的对象写入 ObjectOutputStream。 

在构造方法中,需要传递一个OutputStream对象,因OutputStream是抽象类,因此我们可以传递他的子类FileOutputStream

另外注意基本流FileOutputStream是在对象流ObjectOutputStream中使用的,因此关闭ObjectOutputStream时会自动关闭FileOutputStream

给出一个简单的代码演示
在这里插入图片描述
在这里插入图片描述
一运行,发现,报错NotSerializableException
在这里插入图片描述
于是你会机智地去查一下API文档,但是发现好像没什么东西
在这里插入图片描述
这时候你发现该异常有个实现接口Serializable,机智的你又点了进去,发现了一句话
在这里插入图片描述
懂了!就是需要我的类实现一下这个接口,以启动序列化的这个功能(你也可以理解为开关),马上跑去修改****Person
在这里插入图片描述
再次运行主程序,没有报错,打开生成文件发现,乱码
在这里插入图片描述
别担心,这是正常现象,下一节当中我们可以运用反序列流把它给读取出来

最后还有一点点小拓展:假如类中有一个变量(例如储存了动态密码),我不想对他进行序列化,可不可以呢?可以的,这种情况有两种处理方法,static静态关键字transient瞬态关键字,只要在变量前随意加上其中一个关键字,就可以保护该变量不被序列化,那么该变量序列化后的值为默认值(null,0,0.0之类)

6.3 对象反序列化流

public class ObjectInputStream extends InputStream

构造方法

ObjectInputStream(InputStream in):创建从指定 InputStream 读取的 ObjectInputStream。

成员方法有很多,继承下来的成员方法此前已经进行介绍,此处我们只关注其特有的成员方法storeload

public Object readObject():从 ObjectInputStream 读取对象。  

在构造方法中,需要传递一个InputStream对象,因InputStream是抽象类,因此我们可以传递他的子类FileInputStream

另外注意基本流FileInputStream是在对象流ObjectInputStream中使用的,因此关闭ObjectInputStream时会自动关闭FileInputStream

给出一个简单的代码演示
在这里插入图片描述
运行结果是可以把刚才序列化的对象给读取出来,但是此处有一个EOFException,为什么会有这个异常呢,其实原因在于上述的源程序中while循环里的判断语句(该种形式的具体解决方法我暂时还没找出来),但我们可以转变形式,利用ArrayList进行多个对象的存储读取,详细见6.5的序列化练习
在这里插入图片描述

6.4 InvalidClassException异常

在进行对象的序列化之后,假如我想对Person类做一点小修改,但是没有重新进行序列化,倘若这时候你想直接反序列化把对象读取出来,他会给你报一个异常InvalidClassException,为什么呢?

还记得刚才Person类中所实现的一个接口Serializable吗?这个接口会在进行序列化的时候根据Person的内容生成一个序列号添加到文件当中,这时你对Person进行了修改,但是没有重新序列化,即所保存的文件仍然是旧的序列号,当你进行反序列化的时候,他会读取更改后的Person类,发现这个序列号与文件的不一样,因此就会触发异常

如果我不想重新序列化又想他可以读取有没有办法呢?也是有的,在最初序列化时候,你给Person类写入一个固定的序列号,随后即使Person做什么修改,只要你不去修改这个序列号,他都不会变化,以下是在类中定义固定序列号的方法:

private static final long serialVersionUID = 1L;

序列号的数值你可以随意定义,唯一需要保证的是long类型的数即可

6.5 练习-序列化集合

对象流的相关内容已经介绍完了,国际惯例,来做个小练习,序列化集合,当我想把多个对象都保存到文件中的时候,就需要用到该方法,以下为源码:
在这里插入图片描述
其运行结果如下,可见该种方法不会出现6.3中的EOFException异常:
在这里插入图片描述

7. 打印流

说起打印,我们在学习过程中可用得多了,想看什么东西就来打印一把,平时我们所打印的地方都是打印在IDEA控制台,那么如果我想打印到一个文件当中行不行呢?当然,也是行的!下面来介绍一下打印流

public class PrintStream extends FilterOutputStream extends OutputStream

构造方法

PrintStream(File file):创建具有指定文件且不带自动行刷新的新打印流。
PrintStream(OutputStream out):创建新的打印流。
PrintStream(String fileName):创建具有指定文件名称且不带自动行刷新的新打印流。

成员方法有很多,继承下来的成员方法不作介绍,此处我们只关注其特有的成员方法printprintln我们也相当熟悉,因此此处也不再介绍

在构造方法中,需要传递一个OutputStream对象,因OutputStream是抽象类,因此我们可以传递他的子类FileOutputStream

我们都知道,打印语句的默认都是打印到控制台中,所以我们new了打印流还不行,我们还要让他知道我想打印到打印流中,下面介绍一个可以改变输出语句目的地的方法,该方法在java.lang.System下,所使用到的方法是一个静态方法

public static void setOut(PrintStream out) :重新分配“标准”输出流。

给出一个简单的代码演示
在这里插入图片描述
可见在华丽的分割线========后已经没有输出语句,说明以及改变流向到文件了:
在这里插入图片描述
此时可以再查看一下文件
在这里插入图片描述

8. 流的框架图

在前文中各个章节都学习了不同的流,内容太多可能会容易引起混乱,希望该框架图可以帮助大家理解记忆

字节流框架:
在这里插入图片描述
字符流框架:
在这里插入图片描述

至此,所有与流相关常用的类已经介绍完毕了!理解上有困难的同学可以重复做几次练习,重点理解好第二章节的内容及第八章的框架图随后的内容就比较容易理解了!

猜你喜欢

转载自blog.csdn.net/weixin_45453739/article/details/106975224