目录
[文件内容的读写 ------ 数据流](#文件内容的读写 —— 数据流)
[InputStream 概述](#InputStream 概述)
[FileInputStream 概述](#FileInputStream 概述)
[read 方法:](#read 方法:)
[OutputStream 概述](#OutputStream 概述)
[FileOutputStream 概述](#FileOutputStream 概述)
[write 方法:](#write 方法:)
[Reader 字符流](#Reader 字符流)
[Writer 字符流](#Writer 字符流)
文件内容的读写 ------ 数据流
流是操作系统提供的概念,C / Java 基于流再进行封装
举个栗子:
水流,生生不息,绵延不断
水流的特点:比如要 100ml 的水,可以一次接 10ml,分 10 次接完,也可以一次接 5ml,分 20 次接完,也可以一次接 100ml 一次接完........(此处我们接的方法有任意多种,但最终得到的效果是一样的)
文件流也是类似的情况:比如要读写 100 字节的数据,可以一次读写 10 字节,分 10 次。也可以一次读写 5 字节,分 20 次。也可以一次读写 1 字节,分 100 次读写完。也可以一次读写 100 字节,一次搞完............(此处读写的方式也 是任意多的情况,最终得到的效果也是一样的)

Java 标准库对于流进行了一系列的封装,提供了一系列的封装,提供了一组类来负责进行这些工作。针对这么多的类,大体可以分成两个类别
一:字节流 : 以字节为单位进行读写,一次最少读写一个字节。
代表类: InputStream 输入 OutputStream 输出
二:字符流 : 以字符为单位进行读写,比如,如果是 utf8 表示汉字,3 个字节就是一个汉字,每次读写都得以 3 个字节为单位(即以一个汉字为单位)来进行读写,不能一次读写半个汉字。
代表类: Reader 输入 Writer 输出
读写文件内容在各种编程语言中,都是"固定套路"~~~
1)打开文件 2)关闭文件 3)读文件 4)写文件
InputStream 概述
方法:

说明:
InputStream 只是一个抽象类,要使用还需要具体的实现类。关于 InputStream 的实现类有很多,基本可以认为不同的输入设备都可以对应一个 InputStream 类,我们现在只关心从文件中读取,所以使用 FileInputStream

这是源码中 InputStream 类的声明,abstract 表示的是这是一个抽象类。
有些类,我们不希望它能够创建出实例来。使用 abstract 来描述,编译器就可以进行更加严格的检查。所谓面向对象,就是通过代码抽象的表示实际事物的一种方式。
抽象这个词语本身理解起来就比较抽象,但这个词语又是计算机中比较常见的术语。
如何理解抽象呢? ==》 可以尝试理解抽象的反义词 ==》 形象 / (不抽象...)
如下图,这是以只具体的小猫~~~
具体的猫,信息量就能看到更多,比如说这个猫。品种是中华田园猫,毛色是黑白相间的,也称为是狸花猫,眼睛是棕色的,白色胡须长长的长在嘴巴附近....
抽象的小猫:
只具备猫的最基本特征,但无法得到更多的信息。
信息越多,就越具体。信息越少,就越抽象。
程序员写代码,很多时候,就是要用抽象的方式来表示具体的事物~~~
interface 和 abstract 之间的区别
FileInputStream 概述
构造方法:

示例 FileInputStream,从文件读取,创建对象的过程,就是打开文件的过程~~~

当我们实例化之后,发现需要抛出异常:

我们上面讲,在各种编程语言中,读写文件都是四部,即打开文件,关闭文件,读文件,写文件。现在,我们既然打开了文件,就要考虑关闭文件

关闭文件发现也需要抛出异常:

此时,类声明后面的 throws 直接是一个 IOException,其实,上面实例化抛出的异常 FileNotFoundException(文件不存在异常) 异常 是 IOException 的子类,即 FileNotException 也是一种特殊的 IOException~~~
关闭文件的操作,也可以理解成释放了文件的相关资源。
我们最初在计算机的组成中讲过,进程是一个或这多个 PCB 块组成的,PCB 块中有一个属性是 文件描述符表,记录了当前进程都打开了那些文件。用顺序表 / 数组存储,数组中的每个元素都是一个结构体,这个结构体就具体的描述了我们打开的文件在文件系统上的一些属性。每次打开一个文件,都是需要在文件描述符表中占据一个位置的,如果不关闭,一直打开的话,就会导致文件描述符表被耗尽(文件描述符表的长度是有上限的)。当文件描述符表被耗尽之后,后续再打开文件就会打开失败,进一步就会引起后续一些列的逻辑问题~~~
从而导致文件资源泄露,内存泄漏。
那 Java 中各种集合类,ArrayList 这种,满了都存在自动扩容机制呀,为啥文件描述表不能自动扩容呢??? ==》 会付出很大的代价。对于操作系统的内核来说,对于性能的要求是很高的,内核的任务很重,对于性能的要求也搞,如果要进行上述自动扩容的操作,要十分慎重~~~如果是因为扩容而引起了系统的卡顿,就得不偿失了~~~
由于 Java 提供了 GC(垃圾回收机制),我们就不太需要担心内存泄漏的问题,相当于是,我们的家里请了一个保洁阿姨,每隔一定周期就会帮我们收拾屋子,丢丢垃圾什么的,体验感会很好~~~

如果我们的 close 代码直接像上面这么写,很可能就执行不到~~~如果中间的其他逻辑出现了 return 或者出现抛出其他异常的情况,close 就无法执行到了~~~

我们可以想起用 finally 来确保 close 的一定执行。但又出现问题是,由于 inputStream 是在 try 的代码块中实例化,出了 try 块,finally 不认识 inputStream 了,我们需要在外面创建 InputStream,再在 try 块中实例化。代码如下:

Java 中,还对 try 操作提供了另外一个版本:即 try with resources:

注意:只有实现了 Closeable 接口的类,才能放到 try ( ) 里面。这样写之后,一旦代码出了 try 代码块,此时 try 会自动的帮我们调用 inputStream 的 close 方法~~~
read 方法:

第一个:无参数版本,每次调用读取一个字节,返回值就是读取到的这个字节的值,看起来是 int,实际上是 byte,实际的取值是 0 - 255,还有一个特殊情况是,如果读到了文件的末尾,继续 read,就会返回 -1。
第二个:一个参数版本,传入的是字节数组参数,是一个"输出行参数",byte[ ] 是引用类型,方法内部针对数组内容进行修改,方法执行结束之后,方法外部也能生效~~~
上面两组代码的打印结果分别是几呢???
第一组的结果是 0。原因:func 函数中传入的 a,是形参,对函数的形参的修改,并不会改变函数实参的值,所以打印的结果仍然是实参的值 0
第二组的结果是 100。原因:方法内部是针对数组内容进行修改的,数组是引用类型,a[0] 解引用操作,方法执行结束之后,方法外部也能生效~~~
那这个代码段,执行后打印的结果是什么呢???
结果是 ""。原因:上述代码段执行的操作是引用赋值,并非是解引用,只是在方法内部把形参的引用指向了其他字符串了,而在方法外部的实参对象并没有被改变~~~
第三个:三个参数版本,大体和第二个版本一样,只是增添了开始和结束的位置~ off --> offset
注意:**read 的第二个 第三个版本,返回的 int 表示的是实际读取到的字节个数。**默认情况下,read 会尝试把数组填满,但是文件的实际剩余长度可能并不足以填满,所以返回值告诉我们实际是填充了多少个字节
代码示例:

打印结果:

因为我们是以 16 进制打印的,所以 abcde 分别对应 61 62 63 64 65


一个参数的版本:
buffer 也是计算机中的常见术语,表示"缓冲器",往往是一个内存空间,读文件,就是把硬盘的数据读到内存中去。上面的写法,一次读若干个字节,要比一次读一个字节更加高效
操作硬盘,本身就是一个比较低效的操作,我们肯定更加期望的是,低效的操作,出现的次数越少越好,效率就会越高~~~
如果 test.txt 文件中是汉字的话,我们也可以基于字节数组构成 String。
代码示例:

注意:在 String s = new String(buffer, 0, n); 此处构造 String 是通过前 n 个字节构造,而不是整个数组,实际读取文件的内容可能不足 1024
OutputStream 概述
方法

说明
OutputStream 同样只是一个抽象类,使用还需要具体的实现类
FileOutputStream 概述
大致和 FileInputStream 类似~~~
write 方法:

第一个版本:一次写一个字节
第二个版本:一次写整个数组
第三个版本:一次写数组的一部分
代码示例:

97 -- 102 的对应的 a b c d e f 的 ASCII 码值,执行结果:
此处的 OutputStream,默认情况下,会把文件之前的内容全部清空掉,然后从头重新写!!!(并不是 write 方法操作引起的,而是打开操作引起的~~~)
写文件的时候,也不是说,必须要把文件内容情况,也可以使用追加写的方式,不清空文件内容,把新的内容写到文件末尾~~~ 此处的参数叫做 append 追加

执行结果:

Reader 字符流
Reader 字符流的使用方式和 InputStream 差不多

此处的 read 方法就是按照 char 为单位进行操作了
代码示例:

这个代码里面,还有一个关键问题,char 占几个字节??? ==》 2 字节。
但是如果 test.txt 中是中文,中文按照 utf8 编码,每个字符是 3 个字节,但是此处读取来的字符咋就成了 2 个字节呢???
下面的代码,其实是相当于把文件中的 utf8 在按照字符读取的时候,先转成 unicode,每个 char 里面存储的是对应的 unicode 的值。然后基于 unicode 最终再构造成 utf8 的 String(文件 utf8 => char[ ] unicode => String utf8 这个转换过程 Java 里面已经封装好了,没办法直接感知到~~~ )

Writer 字符流
Writer 字符流和 OutputStream 其实差不多~~~


代码示例:

总结:
补充: Scanner 也可以使用 Scanner 来辅助我们读取文件。Scanner(System.in) ==> 本质上就是一个 InputStream

