Java IO工作机制
参考资料:1.深入分析JavaWeb技术内幕
2.Linux磁盘及IO工作解析
1.Java IO类库框架
1.1 IO类型
基于字节操作的IO接口:InputStream和OutputStream.
基于字符操作的IO接口:Reader和Writer.
基于磁盘操作的IO接口:File.
基于网络操作的IO接口:Socket.
前两组主要是传输数据的数据格式,后两组主要是传输数据的方式.IO的核心问题是数据格式影响IO操作和传输方式影响IO操作,数据格式和传输方式是影响IO运行效率最关键的因素.
1.2 IO类图
图1-1 IO类库层次结构
注意点:
1.InputStream类下,SocketInputStream是FileInputStream的子类.
1.OutputStream类下,SocketOutputStream是FileOutputStream的子类.
3.Writer类提供一个抽象方法write(char cbuf[], int off, int len).
4.Reader类提供一个抽象方法 int read(char cbuf[], int off, int len).
图1-2 IO类库表图
1.3 IO作用
作用:数据持久化或网络传输都是以字节进行,所以必须有字节到字符或字符到字节的转化.
InputStreamReader类是字节到字符的转化桥梁,过程中要指定编码字符集(CharSet),否则采用操作系统默认字符集.OutputStreamWriter类相类似.
2.磁盘IO工作机制
2.1 访问文件方式
读取和写入文件IO操作都调用操作系统提供的接口,因为磁盘设备由操作系统管理,应用程序访问物理设备只能通过系统调用方式来工作.
数据在磁盘中唯一最小描述是文件,上层应用程序只能通过文件来操作磁盘上数据,文件也是操作系统和磁盘驱动器交互的最小单元.
2.1.1 标准访问文件方式
标准IO又称为缓存IO,缓存IO机制中,数据先从磁盘复制到内核空间的缓冲区,然后从内核空间缓冲区复制到应用程序的地址空间。
缓存IO的优点:1.在一定程度上分离了内核空间和用户空间,保护系统本身的运行安全;2.可以减少读盘的次数,从而提高性能。
缓存IO的缺点:数据在传输过程中需要在应用程序地址空间(用户空间)和缓存(内核空间)之间进行多次数据拷贝操作,数据拷贝操作所带来的CPU以及内存开销是非常大的。
2.1.2 直接IO方式
直接IO就是应用程序直接访问磁盘数据,不经过内核缓冲区,目的是减少一次从内核缓冲区到用户程序缓存的数据复制。
数据库管理系统比操作系统更了解数据库中存放的数据,提供一种更加有效的缓存机制来提高数据库中数据的存取性能。
直接IO的缺点:如果访问的数据不在应用程序缓存中,每次数据都会直接从磁盘加载,直接加载非常缓慢。通常直接IO与异步IO结合使用。
图2-1 DirectIO和BufferIO的比较
2.1.3 IO模型
IO 请求的两个阶段 :
等待资源阶段 : IO 请求一般需要请求特殊的资源(如磁盘、 RAM 、文件),当资源被上一个使用者使用没有被释放时, IO 请求就会被阻塞,直到能够使用这个资源。
使用资源阶段 :真正进行数据接收和发生。
举例说就是排队 和服务。
在等待数据 阶段, IO 分为阻塞 IO 和非阻塞 IO 。
阻塞 IO :资源不可用时, IO 请求一直阻塞,直到反馈结果(有数据或超时)。
非阻塞 IO :资源不可用时, IO 请求离开返回,返回数据标识资源不可用
在使用资源 阶段, IO 分为同步 IO 和异步 IO 。
同步 IO :应用阻塞在发送或接收数据的状态,直到数据成功传输或返回失败。
异步 IO :应用发送或接收数据后立刻返回,数据写入 OS 缓存,由 OS 完成数据发送或接收,并返回成功或失败的信息给应用。
2.1.4 内存映射方式
操作系统将内存中的某一块区域与磁盘中的文件关联起来,当要访问内存中的一段数据时,转换为访问文件的某一段数据。这种方式的目的同样是减少数据从内核空间缓存到用户空间缓存的数据复制操作,因为这两个空间的数据是共享的.
内存映射减少数据在用户空间和内核空间之间的拷贝操作,适合大量数据传输.
2.2 Java序列化
Java序列化是将对象转化成字节数组,通过保存和转移字节数据来达到持久化的目的.需要持久化,对象必须继承java.io.Serializable接口.反序列化时,必须有原始类作为模板,才能将对象还原.序列化总结:
1.当父类继承Serializable接口时,所有子类都可以被序列化.
2.子类实现Serializable接口,父类没有,父类中属性不能被序列化(不报错误,数据丢失),但是子类中属性仍能正确序列化.
3.如果序列化属性是对象,则这个对象也必须实现Serializable接口,否则报错.
4.反序列化时,如果对象的属性修改或删减,则修改的部分属性丢失,但不会报错.
5.反序列化时,如果serialVersionUID被修改,则反序列化失败.
3.网络IO工作机制
之后在网络TCP/IP再讲解.
4.NIO工作方式
4.1 NIO核心
NIO主要有三大核心部分:
Channel(通道)
Buffer(缓冲区)
Selector
传统IO基于字节流和字符流进行操作,而NIO基于Channel和Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。
图4-1 Channel将数据读入Buffer,Buffer将数据写入Channel
Selector(选择区)用于监听多个通道的事件(比如:连接打开,数据到达),单个线程可以监听多个数据通道。
图4-2 单线程使用Selector处理三个Channel
IO |
NIO |
|
面向操作 |
Stream |
Buffer |
模式 |
阻塞 |
非阻塞 |
4.2 Channel
4.2.1 Channel特点
FileChannel
DatagramChannel
SocketChannel
SocketChannel
分别对应IO/UDP/TCP(Client/Server)数据的读写操作.
4.3 Buffer
4.3.1 Buffer核心
Buffer用于和NIO Channel进行交互.缓冲区本质是一块可以写入数据和读取数据的内存,包装成NIO Buffer对象,便于使用.
4.3.2 Buffer基本用法
使用Buffer遵循以下步骤:
1.写入数据到Buffer
2.调用flip()方法-作用:Buffer写模式切换为读模式
3.从Buffer中读取数据
4.调用clear()或compact()方法-作用:清空缓存区
4.3.3 Buffer的属性
索引 | 说明 |
---|---|
capacity | 缓冲区数组的总长度 |
position | 下一个要操作的数据元素的位置 |
limit | 缓冲区数组中不可操作的下一个元素的位置:limit<=capacity |
mark | 用于记录当前position的前一个位置或者默认是0 |
CharBuffer buffer = CharBuffer.allocate(1024);
从Buffer中读取数据两种方式:
int byteWritten = inChannel.write(buf);
Byte aByte = buf.get();
int byteRead = inChannel.read(buf);
buf.put(127);
代码示例如下:
ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);
ByteBuffer[] bufferArray = { header, body };
channel.read(bufferArray);
Gatheting Writes是指数据从多个Buffer写入到一个Channel中.
4.4 Selector
使用单线程来处理通道可以减少处理Channel的线程.
//创建Selector对象
Selector selector = Selector.open();
//与Selector一起使用,Channel必须处于非阻塞模式
channel.configureBlocking(false);
//将Channel注册到Selector上
SelectionKey key = channel.register(selector,Selectionkey.OP_READ);
4.4.1 Selector监听Channel事件
1.Selection.OP_CONNECT
2.Selection.OP_ACCEPT
3.Selection.OP_READ
4.Selection.OP_WRITE
分别表示连接就绪,接收就绪,读取就绪,写入就绪.
4.4.2 SelectionKey对象属性
interest集合
int interestSet = selectionKey.interestOps();
boolean isInterestedInAccept = (interestSet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT;
boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;
boolean isInterestedInRead = interestSet & SelectionKey.OP_READ;
boolean isInterestedInWrite = interestSet & SelectionKey.OP_WRITE;
ready集合
int readySet = selectionKey.readyOps();
selectionKey.isAcceptable();
selectionKey.isConnectable();
selectionKey.isReadable();
selectionKey.isWritable();
Selector
Selector selector = selectionKey.selector();
Channel
Channel channel = selectionKey.channel();
附加对象(可选)
selectionKey.attach(theObject);
Object attachedObj = selectionKey.attachment();
完整示例
Selector selector = Selector.open();
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
while(true) {
int readyChannels = selector.select();
if(readyChannels == 0) continue;
Set selectedKeys = selector.selectedKeys();
Iterator keyIterator = selectedKeys.iterator();
while(keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if(key.isAcceptable()) {
// a connection was accepted by a ServerSocketChannel.
} else if (key.isConnectable()) {
// a connection was established with a remote server.
} else if (key.isReadable()) {
// a channel is ready for reading
} else if (key.isWritable()) {
// a channel is ready for writing
}
keyIterator.remove();
}
}