1. 流的概念:
1) Java把所有设备中的有序数据抽象成流,而流(stream)就是指从源节点到目的节点的有序数据,而这里的节点就是存储节点;
2) 而上述只是一种对Java的流的一种描述性概括,这里给出Java中流的严格定义:
i. 流是存储节点上的有序数据;
ii. 流必然要从某个源节点流向某个目的节点,即流具有动态的特性(流动性);
iii. 流必定是从静态数据转换而来的:即原本数据是静态的保存在存储节点中的,而一旦要将其从一个节点传到另一个节点就必须先将这样的静态数据转换成动态的流才行!
iv. 节点上原来的静态数据就叫做节点数据,一旦转换成流之后就叫做节点上的流,简称节点流,而存在流的节点就叫做流节点;
3) 因此一个完整的流的生命周期是这样的:静态的节点数据 --(转换)--> 动态的节点流(源节点) --(流向目标节点)--> 节点流(目标节点) --(转换)--> 静态节点数据 --(进行进一步处理)--> ...
2. 流的分类:
1) 输入输出流:
i. 输入流:只能从流中读取数据到程序中;
ii. 输出流:只能从程序中写数据到流中;
iii. 因此输入输出流只是一种方向问题,如果两个程序通信,A向B传输局,那么A中使用的输出流,而该流留到B后B要从流中读取数据,因此该流在B中就是输入流了,因此这是相对的;
iv. Java以InputStream和Reader作为输入流的基类,以OutputStream和Writer作为输出流的基类,只不过InputStream和OutputStream处理的是字节流,而Reader和Writer处理的是字符流;
2) 字节流和字符流:
i. 两者处理的数据不同,但是用法几乎完全相同;
ii. 字节流以8bit的字节作为数据单元,而字符流以16bit的字符(Java使用Unicode,2字节表示一个字符)作为数据单位;
iii. InputStream和OutputStream就是字节流,而Reader和Writer是字符流;
3) 节点流:
i. 节点流前面已经讲过了,就是存储节点上的待传输的有序数据,它是直接和存储节点相关的,节点流对象的很多方法都是直接操作存储节点的硬件驱动,不同节点流所包含的方法不尽相同,比较繁琐,因此节点流是一种低级流;
ii. Java中的存储节点:
a. Java几乎将一切可以存放静态数据的东西都抽象成节点(存储节点);
b. 最常见的流如:文件(File对象,代表磁盘设备)、套接字(Socket对象,代表一个网络节点)等;
c. 特殊的如:数组、StringBuffer等可以存储可修改字符的字符串等,毕竟虽然它们不是什么眼睛就能看到的存储设备,但是它们同样可以保存静态数据,因此也可以将它们作为存储节点,还可以将它们保存的数据转换成流;
!!为啥要将数组、字符串等作为节点呢?其实在C语言中就已经接触过了,比如sprintf格式化一个字符串,不就是将该字符串先转化成一个输出流,然后将格式字符输出到该字符串当中以形成一个新字符串的吗?
iii. 常见的节点流:
a. 节点流都是跟具体的存储节点相关的,因此其类名中往往包含存储节点的名字;
b. 比如常见的FileInputStream就是文件节点流(输入流),可以将一个文件转换成输入流,然后在程序里从输入流中读取文件的数据;
c. 特殊的如CharArrayReader就是一个字符数组节点流(输入流),可以将一个字符数组转换成输入流,然后在程序里从输入流中读取数组中的字符;
!!小结:
a. 节点流很底层,直接和存储节点相关,因此是一种低级流;
b. Java的节点流类型都是以节点名称作为前缀,然后以InputStream、OuputStream、Reader、Writer作为后缀,即名称中必然包含存储节点;
c. 既然和节点相关,那么其对象方法也是和节点相关的(包含节点的驱动),因此不同节点流的对象方法不尽相同,差异较大;
4) 处理流:
i. 和节点流刚好对应,处理流是一种高级流;
!节点流是驱动层级的低级流,而处理流是一种应用层级的高级流;
ii. 处理流用来将已存在的节点流进行包装,然后用统一的方式进行流操作,这样就可以隐藏底层各个节点流的差异了;
iii. 比如两个节点流FileOutputStream和ByteArraryOutputStream进行流操作的对象不放不尽相同,可以完成同一个目标两者调用的方法可能不同(甚至调用方法的数量、参数都不同),但是都经过高级处理流PrintStream包装,那么包装后得到的PrintStream对象可以用统一的方法完成两者要完成的任务,并且调用的方法更简单高效!
iv. 这就是Java设计I/O体系是的良苦用心了:
a. 这是一种装饰器设计,处理流也称为包装流,即用处理流来包装(装饰)不同的节点流;
b. 比如用同一个处理流PrintStream来包装文件节点流、Socket节点流、打印机节点流、数组节点流、键盘/显示屏的标准节点流等等,然后用一套统一的方法完成对这些节点的输入输出工作,这让编程变得异常简单!!
c. 毕竟存储节点实在是太多了(多种多样),其节点流API也必然是五花八门,程序员真心无法背出那么多API,那么干脆就用处理流包装它们,然后用一套统一的API进行操作,因此最多也只需要背一套处理流的API就行了;
d. 具体来讲,如果要往输出流中插入一行数据,那么文件节点流可能需要两个方法来完成,Socket节点流可能需要一个方法完成,而前者的方法参数可能有3个,而后者可能有5个,差异非常大,背起来很麻烦,但是如果都包装秤PrintStream了话,就直接调用一个println方法就行了!很方便不是码?
!!并且,标准I/O(即键盘/屏幕)中的输出流用的就是PrintStream,也就是我们常用的System.out,你想想,System.out你用的多频繁,一定很熟悉吧,那么现在你往文件、Socket、打印机、数组等节点里面输出数据也就跟System.out一样,那不是很开心吗??
v. 常见的处理流:PrintStream(不用讲了,System.out、网络编程中经常用到)、BufferedReader(可以使输入流一行一行地读取)等等;
3. 创建节点流和处理流:
1) 创建节点流:
i. 很简单,必须要先准备好节点,然后再用其构造成相应的节点流,使用节点流的构造器构造;
ii. 构造器的格式:Xxx节点流(节点对象 | 结点名称);
iii. 示例:
a. FileInputStream(File file); // 直接用节点对象来构造
!!File就是Xxx,即节点类型的名称,而InputStream就是具体的流,这里是字节输入流,当然也可以是OutputStream、Reader、Writer
b. FileInputStream(String name); // 就用结点的名称来构造
2) 创建处理流:
i. 也很简单,上面讲过,精髓就是包装!因此必须先准备好一个低级节点流让其包装(使用处理流的构造器包装);
ii. 构造器格式:Xxx处理流(节点流对象);
iii. 示例:
a. PrintStream(OutputStream out); // OutputStream是所有字节输出流的基类,因此这里是向上转换,传节点流即可
b. BufferedReader(Reader in); // 同样Reader是所有字符输入流的基类,因此也是向上转换,传节点流即可
c. PrintStream ps = new PrintStream(new FileOutputStream("test.txt"));
d. BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); // System.in是字节流,先要转化成字符流才行,InputStreamReader就是将字节流转化成字符流