java IO
1.IO相关概念
1.1什么是IO
所谓IO即input和output的缩写,是对数据的流入和流出的一种抽象,编程中很常见的一个概念。
1.2什么是流
体会一下这几个词:水流(静止的水想必没人会叫水流),物流,人流(此人流非彼人流 = =!),可以发现流的特点:动态的,可转移
的,从一处到另一处的
1.3 java io
java为了我们调用方便,而屏蔽输入/输出源和流动细节,抽象出的用于解决数据流动问题的类体系,这就是java的io流
1.4 输入流和输出流
用于读取的流称为输入流,用于写入的流称为输出流。输入输出的概念一般针对内存来说的
1.5 字节流和字符流
输入输出流可操作的最小单位来区分字节流和字符流,最小单位是一个字节(8bit)的为字节流,
最小单位是一个字符(16bit)的为字符流。
1.6 节点流和包装(处理)流
1)节点流偏向实现细节,直接与细节打交道,比如FileInputStream,而包装(处理)流偏功能,以目标功能为抽象,比如PrintStream。
2)区分节点流和包装(处理)流最简单的一个方式:处理流的构造方法中需要另一个流作为参数,而节点流构造方法则是具体的物理节点,如上FileInputStream构造法中需要一个文件路径或者File对象,
而PrintStream构造方法中则需要一个流对象
3)包装流使用了装饰器模式(什么是装饰器模式?传送门),包装流对节点流进行了一系列功能的强化包装,让包装后的流拥有了更多的操作手段或更高的操作效率,而隐藏节点流底层的复杂性。
1.7 低级流和高级流
低级流和高级流对应的概念即对应上面的节点流和包装(处理)流概念
1.8 普通流和缓冲流
普通流和缓冲流主要是针对读写性能上提出的相对概念。普通流与缓冲流的区别在于一个一个数据的流
动还是一堆一堆数据的流动。
1.9 bio,nio,aio
bio:阻塞式IO模型:服务端启动,等待客户端的连接,在客户端连接到服务端后,服务端启动一个线程去监听客户端消息,客户端发送消息,并等待服务端返回(客户端一直阻塞),
服务端收到消息,将消息返回给客户端,此时一次交互完成。若还需交互,则不释放连接,客户端再次将消息发送给服务端,并等待返回,若不需要交互,则客户端释放连接。
nio: 同步非阻塞的 IO 模型。同步是指线程不断轮询 IO 事件是否就绪,非阻塞是指线程在等待 IO 的时候,可以同时做其他任务。同步的核心就是 Selector,Selector 代替了线程本身轮询 IO 事件,避免了阻塞同时减少了不必要的线程消耗;非阻塞的核心就是通道和缓冲区,当 IO 事件就绪时,可以通过写道缓冲区,保证 IO 的成功,而无需线程阻塞式地等待。
aio:异步非阻塞IO、是在JDK 1.7的时候才推出的模型。 是利用了本地操作系统的IO操作处理模式,当有IO操作产生之后,会启动有一个单独的线程,它将所有的IO操作全部交由系统完成,只需要知道返回结果即可。 主要的模式是基于操作回调的方式来完成处理的,如果以烧水为例:在烧水的过程之中你不需要去关注这个水的状态,在烧水完成后才进行通知。
发展历程:bio(jdk1.0) -> nio(jdk1.4) -> aio(jdk1.7)
下面我们来说说bio
bio类体系
1.1 体系图
1.2 从数据来源或者操作对象角度看,IO类还可以划分为:
- 文件(file):FileInputStream、FileOutputStream、FileReader、FileWriter
- 数组([]):
字节数组(byte[]):ByteArrayInputStream、ByteArrayOutputStream
-
字符数组(char[]):CharArrayReader、CharArrayWriter
- 管道操作:PipedInputStream、PipedOutputStream、PipedReader、PipedWriter
- 基本数据类型:DataInputStream、DataOutputStream
- 缓冲操作:BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter
- 打印:PrintStream、PrintWriter
- 对象序列化反序列化:ObjectInputStream、ObjectOutputStream
- 转换:InputStreamReader、OutputStreWriter
- 字符串(String):StringBufferInputStream、StringBufferOutputStream、StringReader、StringWriter
2.BIO体系中的类该怎么使用?
要回答这个问题,我们需要将这个问题进行拆分为下面四个问题
2.1 什么时候该用输入流,什么时候用输出流?
从流中读取信息使用输入流(xxxInputStream/xxxReader),写入信息使用输出流(xxxOutputStream/xxxWriter)
2.2 什么时候该用字节流,什么时候用字符流?
处理纯文本数据时使用字符流(xxxReader/xxxWriter),处理非纯文本时使用字节流(xxxStream)。最后其实不管什么类型文件都可以用字节流处理,包括纯文本,但会增加一些额外的工作量。所以还是按原则选择最合适的流来处理
2.3 什么时候该用节点流,什么时候用包装(处理)流?
不管你用什么包装(处理)流,都需要先使用节点流获取对应节点的数据流,然后根据具体需求来选择相应的包装(处理)流来对节点流进行包装修饰,从而获取相应的功能
2.4 什么时候该用普通流,什么时候用缓冲流?
一般如果对数据流不做加工处理,而是单纯的读写,如数据转移(拷贝,上传,下载),则需要使用缓冲流来提高性能,当然你也可以自己使用buff数组来提高读写效率。
2.5 使用小结
1)判断操作的数据类型
纯文本数据:读用Reader系,写用Writer系
非纯文本数据:读用InputStream系,写用OutputStream系
如果纯文本数据只是简单的复制,下载,上传,不对数据内容本身做处理,那么使用Stream系
2)判断操作的物理节点
内存:ByteArrayXXX
硬盘:FileXXX
网络:http中的request和response均可获取流对象,tcp中socket对象可获取流对象
键盘(输入设备):System.in
显示器(输出设备):System.out
3)搞清读写顺序,一般是先获取输入流,从输入流中读取数据,然后再写到输出流中。
4)是否需增加特殊功能,如需要用缓冲提高读写效率则使用BufferedXXX,如果需要获取文本行号,则使用LineNumberXXX,如果需要转换流则使用InputStreamReader和OutputStreamWriter,如果需要写入和读取对象则使用ObjectOutputStream和ObjectInputStream
3.使用IO流一些注意点
3.1 关于流读写性能问题
流的读写是比较耗时的操作,因此为了提高性能,便有缓冲的这个概念(,在java bio中使用缓冲一般有两种方式。一种是自己申明一个缓冲数组,利用这个数组来提高读写效率;另一种方式是使用jdk提供的处理流BufferedXXX类。下面我们分别演示不使用缓冲读写,使用自定义的缓冲读写,使用BufferedXXX缓冲读写一个文件。
下面写一个使用BufferedXXX类自定义大小缓冲来读写文件的例子
/**
* @param src 被拷贝的文件对象
* @param dest 拷贝目的地文件对象
* @param size 自定义缓冲区大小
*/
public static void copyByBufferedStream(File src,File dest,int size) {
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
long start = System.currentTimeMillis();
try{
bis = new BufferedInputStream(new FileInputStream(src));
bos = new BufferedOutputStream(new FileOutputStream(dest));
int b = 0;
byte[] buff = new byte[size];
while( (b = bis.read(buff))!=-1){//数据读入缓冲区
bos.write(buff,0,b);//将缓存区数据写入输出流中
}
bos.flush();
}catch(IOException e){
e.printStackTrace();
}finally{
close(bos,bis);
}
System.out.println("使用BufferedXXXStream拷贝大小"+getSize(src)+"的文件使用了缓冲数组耗时:"+(System.currentTimeMillis()-start)+"毫秒");
}
3.2 如何判断文件是否读写完毕?
我们一般在处理文件,一般是一边从输出流中读数据,然后将读出的部分进行处理,最后将处理好的数据写入到输出流中。那么要将一个文件完整的处理完,我们必须知道什么时候已经读到文件的末尾了。
一般来说可以根据read方法返回的值,如果返回了-1表示没有可读取的字节了。
另一种是使用available()方法查看还有多少可供读取的,当输入流每读一个字节,available()返回的值便减小1,这种模式很像游标的模式,但要注意的是available的适用场景是非阻塞读取,如本地文件读取,如果是网络io使用该方法,可能你拿到的值就不对了。
3.3 关于网络流中使用available()方法的问题
当你在网络io中,比如你用socket编程时获取到的流进行读写时,会发现使用available方法有问题,原因是网络io的特点是:
1.非实时性。你调用available()方法判断剩余流的大小时,远端数据可能还未发送,或者要发送的数据处于队列中,因此通过available()拿到的可用长度可能是0
2.非连续性。由于网络数据传输中,一般会分段多次发送,available仅仅能返回本次的可用长度。
鉴于以上两个特点,使用available判断网络io还有多少数据可读是不合适的,因此解决该问题一般采用自定义协议,比如文件大小,文件名等信息放入流的头几个字节中,接收方根据收到的头信息来解析出对法传送的文件大小,根据大小来判断还剩多少字节需要读取,是否读取完毕。
3.4 关于flush()的问题
为什么缓冲输出流写数据结束需要调用flush方法?我们以BufferedOutputStream的write(int b)方法源码为例,源码如下:
public synchronized void write(int b) throws IOException {
if (count >= buf.length) {
flushBuffer();
}
buf[count++] = (byte)b;
}
BufferedOutputStream在write时候,只有count >= buf.length,即缓冲区数据填满的时候才会自动调用flushBuffer()将缓冲区数据进行写入,也就是说如果缓冲区数据未满则将不会写入,这时我们需人为的调用flush()方法将未满的缓冲区数据进行写入。
来源:CSDN
作者:十点半睡
链接:https://blog.csdn.net/weixin_43299321/article/details/103888005