IO
什么是IO
IO即输入/输出,是input和output的缩写。输入输出是所有程序都必须的部分--使用输入机制,允许程序读取外部数据(包括来自磁盘、光盘等存储设备的数据)、用户输入数据(键盘、鼠标);使用输出机制,允许程序记录运行状态,将程序数据输出到磁盘、光盘、显示器等存储设备中,或显示屏显示。
什么是流
IO流是java对输入/输出源的抽象表述,是实现输入/输出的基础。
通俗理解流一定地动态的、可转移的。IO流也是动态的,输入源和输出源可以看成两个管道,其中流动的数据。
流的分类
按流向:输入流和输出流
流向,是以程序运行所在内存为主的,流向内存称为输入流,从内存流出称为输出流。
输入流和输出流都可以看成是一根水管,输入流中的数据可以看成一个个水滴,初始时输入流是包含水滴的,输出流是空的。所以输入流只能从中读取数据,而不能向其写入数据;输出流只能写入数据,不能从中读取数据。
按数据单位:字节流和字符流
字节流和字符流是按输入输出流可操作的最小数据单元来区分的。字节流操作的数据单元是8位的字节,字符流操作的数据单元是16位字符。
字节流主要由InputStream和OutputStream作为基类,字符流主要由Reader和Writer作为基类。
按流的角色:节点流和处理流
节点流也称为低级流,是直接从/向一个特定的IO设备读/写数据的流,也就是输入输出时直接连接到实际的数据源的流。
处理流也称为高级流,是对节点流进行一系列功能的包装。通过处理流来包装不同的节点流,即可以消除不同节点流的实现差异,也可以提供更方便的方法来完成输入输出功能。
节点流的构造方法是具体的物理节点,处理流的构造方式需要一个节点流作为参数
输入/输出流体系
转换流:用于实现将字节流转换成字符流。
推回输入流:推回输入流带有一个推回缓冲区,当程序调用这个推回输入流的unread()方法时,系统将会把指定数组的内容推回到该缓冲区里,而推回输入流每次调用read()方法时总是先从缓冲区读取,只有完全读取了缓冲区的内容后,但还没有装满read()所需的数组时才会从原输入流中读取。
缓冲流:是相对于其他流一个一个数据流动来说,缓冲流是一堆一堆数据的流动
BIO
什么是BIO
BIO是一种同步阻塞的通信。b有两说,一为base,jdk中最早抽象出的io体系;一为block,jdk 1.0 中的io体系是阻塞的。一般我们认为b取block之意,所以BIO也就是阻塞流。
BIO体系图
IO使用小结
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
什么是同步阻塞
同步和异步关注的通信机制,同步是调用者主动等待返回结果,异步是被调用者通知调用者,或调用者通过回调函数处理请求。比如自己去买酒,老板说去仓库取,同步状态下,如果老板一直没有回来,自己就一直在等待。异步状态下,老板会说从仓库取到了给你打电话。
阻塞与非阻塞关注的是程序在等待调用结果时的状态。在老板取酒的时候,如果是同步自己只能默默等待,相当于线程被挂起;如果是异步自己可以一边等待一边抽根烟,过几分钟询问一下。
BIO-同步阻塞通信
通过上边的BIO体系图,我们发现,传统的IO通信方式就是BIO。
try{
//在此造成阻塞
Socket socket = new Socket();
//客户端发来请求才会继续执行
BufferReader reader = new BufferReader(new InputStreamReader(s.getInputStream(),charset));
String mess = null;
while((mess = reader.readLine()) != null){
System.out.printIn(mess);
}
s.close();
} catch (IOException e){
e.printStackTrace();
}
BIO程序服务端是如何处理客户端请求呢?首先服务端需要实例化一个socket进行监听,然后一直堵塞等待客户端请求,如果客户端发来N个请求,那么这N个请求就必须等待前一个请求得到相应才能继续执行。这样会造成性能损耗。
如何解决上一个问题呢?可以使用多线程。但是如果并发请求量很大,有一万、十万个请求会有什么影响呢?最可怕的影响就是爆内存。。32位系统1个线层对象默认最大需要320kb内存,64位系统默认最大需要1M内存,业务对象还需要内存,内存恐不够。另外过多的线程需要OS频繁切换,也会大大影响性能。
那么如何解决上一个问题呢?可以使用线程池。使用线程池就可以保证万无一失了吗?阻塞等待接收客户端的数据时,这段时间是占着线程的,而且线程池中的线程数也是有限的,如果并发量很大,将导致没有线程可以处理请求,就会造成线程池阻塞、请求时间长,甚至拒绝服务。
所以BIO存在的问题如下:
- 性能问题:一连接一线程模型导致服务端的并发接入数和系统吞吐量受到极大限制;
- 可靠性问题:由于I/O操作采用同步阻塞模式,当网络拥塞或者通信对端处理缓慢会导致I/O线程被挂住,阻塞时间无法预测;
- 可维护性问题:I/O线程数无法有效控制、资源无法有效共享(多线程并发问题),系统可维护性差
如何才能不造成阻塞,而且在没有数据时,还可以处理其他请求,有了数据才处理数据,这样就会变得很好。用NIO啊,下篇文章我们介绍NIO。