流的概念和作用
流:代表任何有能力产出数据的数据源对象或者是有能力接受数据的接收端对象。
流的本质:数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。
Java IO所采用的模型
Java的IO模型设计非常优秀,它使用 Decorator
(装饰者)模式,按功能划分 Stream
,您可以动态装配这些Stream
,以便获得您需要的功能。
例如,您需要一个具有缓冲的文件输入流,则应当组合使用 FileInputStream
和 BufferedInputStream
。
IO流的分类
按数据流的方向分为 输入流、输出流
此输入、输出是相对于我们写的代码程序而言,
输入流:从别的地方(本地文件,网络上的资源等)获取资源 输入到 我们的程序中
输出流:从我们的程序中 输出到 别的地方(本地文件), 将一个字符串保存到本地文件中,就需要使用输出流。
按处理数据单位不同分为 字节流、字符流
1字符 = 2字节 、 1字节(byte) = 8位(bit) 、 一个汉字占两个字节长度
字节流:每次读取(写出)一个字节,当传输的资源文件有中文时,就会出现乱码,
字符流:每次读取(写出)两个字节,有中文时,使用该流就可以正确传输显示中文。
按功能不同分为 节点流、处理流
节点流:以从或向一个特定的地方(节点)读写数据。
直接使用节点流,读写不方便,为了更快的读写文件,才有了处理流。
常用的节点流:
- 父 类 :
InputStream
、OutputStream
、Reader
、Writer
- 文 件 :
FileInputStream
、FileOutputStrean
、FileReader
、FileWriter
文件进行处理的节点流 - 数 组 :
ByteArrayInputStream
、ByteArrayOutputStream
、CharArrayReader
、CharArrayWriter
对数组进行处理的节点流(对应的不再是文件,而是内存中的一个数组) - 字符串 :
StringReader
、StringWriter
对字符串进行处理的节点流 - 管 道 :
PipedInputStream
、PipedOutputStream
、PipedReader
、PipedWriter
对管道进行处理的节点流
处理流:是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。
处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多次包装,称为流的链接。
常用的处理流:
- 缓冲流:
BufferedInputStrean
、BufferedOutputStream
、BufferedReader
、BufferedWriter
增加缓冲功能,避免频繁读写硬盘。 - 转换流:
InputStreamReader
、OutputStreamReader
实现字节流和字符流之间的转换。 - 数据流:
DataInputStream
、DataOutputStream
等-提供将基础数据类型写入到文件中,或者读取出来。
- 父 类 :
4个基本的抽象流类型,所有的流都继承这四个:
输入流 | 输出流 | |
---|---|---|
字节流 | InputStream | OutputStream |
字符流 | Reader | Writer |
总结流的分类:
1、首先自己要知道是选择输入流还是输出流,这就要根据自己的情况而定,如果你想从程序写东西到别的地方,那么就选择输出流,反之用输入流
2、然后考虑你传输数据时,是选择使用字节流传输还是字符流,也就是每次传1个字节还是2个字节,有中文肯定就选择字符流了。(详情见1.8)
3、前面两步就可以选出一个合适的节点流了,比如字节输入流inputStream,如果要在此基础上增强功能,那么就在处理流中选择一个合适的即可。
字符流的由来: Java中字符是采用Unicode标准,一个字符是16位,即一个字符使用两个字节来表示。为此,JAVA中引入了处理字符的流。因为数据编码的不同,而有了对字符进行高效操作的流对象。本质其实就是基于字节流读取时,去查了指定的码表。
IO流特性
1、先进先出,最先写入输出流的数据最先被输入流读取到。
2、顺序存取,可以一个接一个地往流中写入一串字节,读出时也将按写入顺序读取一串字节,不能随机访问中间的数据。(RandomAccessFile可以从文件的任意位置进行存取(输入输出)操作)
3、只读或只写,每个流只能是输入流或输出流的一种,不能同时具备两个功能,输入流只能进行读操作,对输出流只能进行写操作。在一个数据传输通道中,如果既要写入数据,又要读取数据,则要分别提供两个流。
IO流常用到的五类一接口
在整个Java.io包中最重要的就是5个类和一个接口。5个类指的是File、OutputStream、InputStream、Writer、Reader;一个接口指的是Serializable。
Java IO流对象
输入字节流InputStream
ByteArrayInputStream
:字节数组输入流,该类的功能就是从字节数组(byte[])中进行以字节为单位的读取,也就是将资源文件都以字节的形式存入到该类中的字节数组中去,我们拿也是从这个字节数组中拿
PipedInputStream
:管道字节输入流,它和 PipedOutputStream
一起使用,能实现多线程间的管道通信
FilterInputStream
:装饰者模式中处于装饰者,具体的装饰者都要继承它,所以在该类的子类下都是用来装饰别的流的,也就是处理类。具体装饰者模式在下面会讲解到,到时就明白了
BufferedInputStream
:缓冲流,对处理流进行装饰,增强,内部会有一个缓存区,用来存放字节,每次都是将缓存区存满然后发送,而不是一个字节或两个字节这样发送。效率更高
DataInputStream
:数据输入流,它是用来装饰其它输入流,它“允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型”
FileInputSream
:文件输入流。它通常用于对文件进行读取操作
File:对指定目录的文件进行操作,具体可以查看讲解File的博文。注意,该类虽然是在IO包下,但是并不继承自四大基础类。
ObjectInputStream
:对象输入流,用来提供对“基本数据或对象”的持久存储。通俗点讲,也就是能直接传输对象(反序列化中使用)
输出字节流OutputStream
OutputStream
是所有的输出字节流的父类,它是一个抽象类。
ByteArrayOutputStream
、FileOutputStream
是两种基本的介质流,它们分别向Byte 数组、和本地文件中写入数据。PipedOutputStream
是向与其它线程共用的管道中写入数据,
ObjectOutputStream
和所有FilterOutputStream
的子类都是装饰流(序列化中使用)。
字符输入流Reader
Reader
是所有的输入字符流的父类,它是一个抽象类。
CharReader
、StringReader
是两种基本的介质流,它们分别将Char 数组、String中读取数据。PipedReader 是从与其它线程共用的管道中读取数据。
BufferedReader
很明显就是一个装饰器,它和其子类负责装饰其它Reader 对象。
FilterReader
是所有自定义具体装饰流的父类,其子类 PushbackReader
对 Reader
对象进行装饰,会增加一个行号。
InputStreamReader
是一个连接字节流和字符流的桥梁,它将字节流转变为字符流。ileReader
可以说是一个达到此功能、常用的工具类,在其源代码中明显使用了将 FileInputStream
转变为 Reader
的方法。我们可以从这个类中得到一定的技巧。Reader 中各个类的用途和使用方法基本和 InputStream
中的类使用一致。后面会有 Reader
与 InputStream
的对应关系。
字符输出流Writer
Writer
是所有的输出字符流的父类,它是一个抽象类。
CharArrayWriter
、StringWriter
是两种基本的介质流,它们分别向Char 数组、String 中写入数据。PipedWriter 是向与其它线程共用的管道中写入数据,
BufferedWriter
是一个装饰器为Writer 提供缓冲功能。
PrintWriter
和PrintStream
极其类似,功能和使用也非常相似。
OutputStreamWriter
是 OutputStream
到 Writer
转换的桥梁,它的子类 FileWriter
其实就是一个实现此功能的具体类(具体可以研究源码)。功能和使用和 OutputStream
极其类似,后面会有它们的对应图。
节流和字符流使用情况:(重要)
字符流和字节流的使用范围:字节流一般用来处理图像,视频,以及PPT,Word类型的文件。
字符流一般用于处理纯文本类型的文件,如TXT文件等。
字节流可以用来处理纯文本文件,但是字符流不能用于处理图像视频等非文本类型的文件。
字符流与字节流转换
转换流的作用,文本文件在硬盘中以字节流的形式存储时,通过InputStreamReader读取后转化为字符流给程序处理,程序处理的字符流通过OutputStreamWriter转换为字节流保存。
转换流的特点:
- 其是字符流和字节流之间的桥梁
- 可对读取到的字节数据经过指定编码转换成字符
- 可对读取到的字符数据经过指定编码转换成字节
何时使用转换流?
- 当字节和字符之间有转换动作时;
- 流操作的数据需要编码或解码时。
具体的对象体现:
- InputStreamReader:字节到字符的桥梁
- OutputStreamWriter:字符到字节的桥梁
这两个流对象是字符体系中的成员,它们有转换作用,本身又是字符流,所以在构造的时候需要传入字节流对象进来。
OutputStreamWriter(OutStreamout):将字节流以字符流输出。
InputStreamReader(InputStream in):将字节流以字符流输入。
字节流和字符流的区别(重点)
节流没有缓冲区,是直接输出的,而字符流是输出到缓冲区的。因此在输出时,字节流不调用 colse()
方法时,信息已经输出了,而字符流只有在调用 close()
方法关闭缓冲区时,信息才输出。要想字符流在未关闭时输出信息,则需要手动调用 flush()
方法。
- 读写单位不同:字节流以字节(8bit)为单位,字符流以字符为单位,根据码表映射字符,一次可能读多个字节。
- 处理对象不同:字节流能处理所有类型的数据(如图片、avi等),而字符流只能处理字符类型的数据。
结论:只要是处理纯文本数据,就优先考虑使用字符流。除此之外都使用字节流。
System类对IO的支持
针对一些频繁的设备交互,Java语言系统预定了3个可以直接使用的流对象,分别是:
System.in
(标准输入),通常代表键盘输入。System.out
(标准输出):通常写往显示器。System.err
(标准错误输出):通常写往显示器。
序列化与反序列化
序列化:将保存在内存中的对象数据转化为二进制数据流进行传输,任何对象都可以序列化
反序列化:将二进制数据换回原对象
transient关键字(一个类某些属性不需要序列化):序列化与反序列化操作时是将整个对象的所有属性序列化,那么transient关键字可以将某些内容不需要保存,就可以通过transient关键字来定义
Java IO 分类
Java BIO: 同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
Java NIO: 同步非阻塞,服务器实现模式为一个请求一个线程,即当一个连接创建后,不需要对应一个线程,这个连接会被注册到多路复用器上面,所以所有的连接只需要一个线程就可以搞定,当这个线程中的多路复用器进行轮询的时候,发现连接上有请求的话,才开启一个线程进行处理,也就是一个请求一个线程模式。BIO与NIO一个比较重要的不同,是我们使用BIO的时候往往会引入多线程,每个连接一个单独的线程;而NIO则是使用单线程或者只使用少量的多线程,每个连接共用一个线程。
Java AIO(NIO.2): 异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。
名词解释
同步:指的是用户进程触发IO操作需要等待或者轮询的去查看IO操作执行完成才能执行其他操作.这种方式性能比较差,只有一些对数据安全性要求比较高的场景中才会使用.
异步:异步是指用户进程触发IO操作以后便开始做自己的事情,而当IO操作已经完成的时候会得到IO完成的通知(异步的特点就是通知)
阻塞:所谓阻塞方式的意思是指, 当试图对该文件描述符进行读写时, 如果当时没有东西可读,或者暂时不可写, 程序就进入等待 状态, 直到有东西可读或者可写为止
非阻塞:非阻塞状态下, 如果没有东西可读, 或者不可写, 读写函数马上返回, 而不会等待
BIO、NIO、AIO适用场景分析
BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。
Java NIO和IO的主要区别
面向流与面向缓冲是 Java NIO和 IO之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。 Java NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。
阻塞与非阻塞IO Java IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。 Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。
选择器(Selectors) Java NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道。
IO流案例
上面基本上都是网上copy的,感觉以后应该会用得着,所以就记录下来了。光扯这些理论知识肯定屌用没有,必须要理论实践相结合才行,所以下面是今天学的时候写的一些案例。
参考博客:Java IO流学习总结,讲的是真的好,牛逼!
FileOutputStream 类的使用:写文件
1 | //1.使用FileOutputStream流写文件 |
当然,我们执行之后,你好
就会被写入到 test.txt
文件中去
FileInputStream类的使用:读文件
1 | //读取文件 |
执行之后,控制台就会打印 test.txt
文件中的内容
File类的使用:扫描E盘所有文件
1 |
|
执行之后,就会打印我们E盘下面的每一个文件,反正我的文件太™多了,它会打印很长时间…
FileFilter类的使用:获取指定目录的所有文件夹
1 | //file.listFiles(FileFilter filter) |
执行完之后会我们我们E盘下面的所有文件夹,只有一层的,文件不会打印出来
FilenameFilter类的使用:扫描出指定路径的所有png图片
1 | //FileFilter是io包里面的一个接口,从名字上可以看出,这个类是文件名字过滤功能的。 |
我们执行完之后,控制台会打印对应路径下的所有png文件。
字节缓冲流的使用:文件复制
1 | public class CopyDemo { |
这里有两个问题,我们注意一下:
1、flush问题:BufferedOutputStream
的每一次write其实是将内容写入byte[],当buffer容量到达上限时,会触发真正的磁盘写入。而另一种触发磁盘写入的办法就是调用flush()了。
2、close问题:close
关闭输入流,并且释放资源。Buffered缓冲流的close方法可以关闭其装饰的内层流。
那么如果我们想逐个关闭流,我们该怎么做?
答案是:先关闭外层流,再关闭内层流。一般情况下是:先打开的后关闭,后打开的先关闭;另一种情况:看依赖关系,如果流a依赖流b,应该先关闭流a,再关闭流b。例如处理流a依赖节点流b,应该先关闭处理流a,再关闭节点流b。
字符缓冲流的使用:文件复制
1 | public class CopyDemo2 { |
转换流的使用:txt文件复制
1 | public class SwitchDemo { |
字节数组输入流的使用:读写过程测试
1 | //字节流ByteArrayInputStream的读写过程测试 |
控制台输出结果:
1 | [104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33] |
字节数组输出流的使用:读出的字节流用 FileOutputStream写入文件
1 | //将ByteArrayOutputStream读出的字节流用FileOutputStream写入文件 |
执行完之后,你好,世界!hello world!
会写入到 arraystream.txt
文件中去。
其他
下面的一些知识点,我感觉我有点陌生,就是感觉不是很熟悉,但应该也挺有用的,所以就把它们单独列出来。
打印流
如果我们要想进行数据的输出,首先想到的就是要使用 OutputStream
类,但这个类在进行输出数据的时候并不是十分方便。
OutputStream
类之中所提供的 write()
方法只适合输出字节数组,但如果要求输出字符、数字、日期,OutputStream
类就不能很方便地胜任工作了。
在Java的I/O包中,打印流是一个输出信息最方便的流类,它可以将原样输出各种类型的类型。
除了输出数据,打印流还提供两项其他功能:
(1)与其他输出流不同的是,打印流的方法不会抛出IOException
,其异常情况仅设置内部标志位, 这些标志位可通过checkError()
方法来读取。
(2)打印流具有自动刷新的功能。 例如,当写入字节数组时,flush()
方法会被自动调用。
在Java中提供了两种打印流:PrintStream
(字节打印流)和 PrintWriter
(字符打印流)。
PrintStream(字节打印流)
下面首先以PrintStream类为例进行分析,观察PrintStream类的继承结构。
1 | java.lang.Object |
对于打印流而言,它所使用的设计模式称为装饰设计模式,即将一个设计不是非常完善的功能,添加一些代码之后变得完善起来。
PrintStream
类提供了一系列的 print
和 println
方法,可以实现将基本数据类型的格式转换成字符串输出。在前面的程序中大量用到的“System.out.println
”语句中的 System.out
,就是 PrintStream
类的一个实例对象。PrintStream
有下面几个构造方法:
1 | PrintStream(OutputStream out) |
其中,autoflush控制在Java中遇到换行符(\n)时是否自动清空缓冲区,encoding是指定编码方式。
Java的 PrintStream
对象具有多个重载的 print
和 println
方法,它们可输出各种类型(包括Object)的数据。
对于基本数据类型的数据,print
和 println
方法会先将它们转换成字符串的形式,然后再输出,而不是输出原始的字节内容,如整数221的打印结果是字符“2”、“2”、“1”所组合成的一个字符串,而不是整数221在内存中的原始字节数据。
对于一个非基本数据类型的对象,print
和 println
方法会先调用对象的 toString
方法,然后输出 toString
方法所返回的字符串。
PrintWriter (字符打印流)
在Java的I/O包中,提供了一个与 PrintStream
对应的 PrintWriter
类,PrintWriter
类有下列几个构造方法:
1 | PrintWriter(OutputStream) |
PrintWriter即使遇到换行符(\n)也不会自动清空缓冲区,只在设置了autoflush模式下使用了println方法后才会自动清空缓冲区。
PrintWriter
相对 PrintStream
最便利的一个地方就是 println
方法的行为,在Windows下的文本换行是“\r\n”,而在Linux下的文本换行是“\n”。如果希望程序能够生成平台相关的文本换行,而不是在各种平台下都用“\n”作为文本换行,那么就应该使用 PrintWriter
的 println
方法,PrintWriter
的 println
方法能根据不同的操作系统而生成相应的换行符。
案例1:向屏幕输出信息
1 | //控制台打印 |
案例2:向文件输出信息
1 |
|
管道流
在UNIX/Linux中有一个很有用的概念——管道(pipe),它具有将一个程序的输出当作另一个程序的输入的能力。
在Java中,它的 I/O系统建立在数据流概念之上,也可以使用“管道”流进行线程之间的通信,在这个机制中,输入流和输出流必须相连接,这样的通信有别于一般的共享数据(Shared Data)缓冲区通信,其不需要一个共享的数据空间。
管道流主要用于连接两个线程间的通信。管道流也分为字节流(PipedInputStream、PipedOutputStream)与字符流(PipedReader、PipedWriter)两种类型.
一个
PipedInputStream
对象必须和一个PipedOutputStream
对象进行连接而产生一个通信管道,PipedOutputStream
可以向管道中写入数据,PipedInputStream
可以从管道中读取PipedOutputStream
写入的数据。
案例:用管道流进行线程之间的通信
1 | public class PipeStreamDemo { |
执行main方法结果:
Data流
DataInputStream
继承于 FilterInputStream
装饰其它输入流,它“允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型”。
DataOutputStream
将内存里的东西序列化,可以不经过转码,也可以将对象写入文件。
UTF-8的数据是变长的,可以是1-4个字节;在 readUTF()
中,我们最终是将全部的UTF-8数据保存到“字符数组(而不是字节数组)”中,再将其转换为String字符串。
writeInt 总是将一个整数写出为 4字节的二进制数量值;
1.2)不管它有多少位, writeDouble 总是将一个double值写出为 8 字节的二进制数量值;
1.3)这样产生的结果并非人可阅读的, 但是对于给定类型的每个值, 所需的空间总是相同的, 而且将其读回也比解析文本要更快;(干货——解析二进制数据比解析文本数据要更快)
1 | //写入测试 |
1 | //读出测试 |
结果:
随机流
随机流(RandomAccessFile)不属于IO流,支持对文件的读取和写入随机访问。
首先把随机访问的文件对象看作存储在文件系统中的一个大型 byte 数组,然后通过指向该 byte 数组的光标或索引(即:文件指针 FilePointer)在该数组任意位置读取或写入任意数据。
相关方法
1、对象声明
1 | RandomAccessFile raf = newRandomAccessFile(File file, String mode); |
其中参数 mode 的值可选 “r”:可读,”w” :可写,”rw”:可读性;
2、获取当前文件指针位置:int RandowAccessFile.getFilePointer();
3、改变文件指针位置(相对位置、绝对位置):
1> 绝对位置:RandowAccessFile.seek(int index);
2> 相对位置:RandowAccessFile.skipByte(int step);
相对当前位置
4、给写入文件预留空间:RandowAccessFile.setLength(long len);
指定位置读取文件
1 | //使用RandomAccessFile实现从指定位置读取文件的功能 |
向文件中追加内容
1 | //使用RandomAccessFile实现向文件中追加内容的功能 |