中文教程 http://ifeve.com/java-nio-all/
原地址 http://tutorials.jenkov.com/java-nio/index.html
概述
核心组件:
- Channels
- Buffers
- Selectors
Channel 有点像流,数据可以从Channel读到Buffer中,也可以从Buffer写到Channel中,Channel的主要实现:
- FileChannel
- DatagramChannel
- SocketChannel
- ServerSocketChannel
这些涵盖了UDP和TCP网络IO,和文件IO
Buffer 在NIO中的主要实现:
- ByteBuffer
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer
覆盖了通过IO发送的基本数据类型:byte、short、int、long、float、double 和 characters
Selectors 允许单线程处理多个Channel,如果应用有很多连接(Channel),但是每个连接只有很低的流量,使用Selectors就会很方便,如下图一个聊天服务,一个线程使用一个Selector处理3个Channel
多线程的上下文切换开销很大,每个线程还得占用资源,使用Selector便可以使用少量的线程,处理更多的Channel
要使用Selector,需要向Selector中注册Channel,然后调用select()方法,这个方法会一直阻塞到某个注册的Channel有事件就绪。一旦这个方法返回,线程就就可以处理这些事件。事件的例子有如新连接进来,数据接收等。
详述
Channel
channel和流(stream)的区别:
- 既可以从Channel中读数据,同时也可以往通道中写数据。流的读写是单向的
- Channel可以异步的进行读写
- Channel总是把数据读到Buffer或者从一个Buffer中写入
Buffer
缓冲区实际上就是一块可以先写入数据,然后从中读取数据的内存块,这块内存被包装成NIO Buffer对象,并提供一组方法便于访问。
使用Buffer去读或者写数据一般遵循以下四个步骤:
- 写入数据到Buffer
- 调用buffer.flip()
- 从Buffer中读出数据
- 调用buffer.clear()或者buffer.compact()
当向Buffer写入数据时,Buffer会跟踪写入的数据量,一旦需要读这些数据,需要使用filp()方法将Buffer从写模式切换到读模式,在读的模式下,便可以读取之前写入的所有数据。
一旦读取完所有数据,需要清空缓存区,让他可以再次被写入(调用clear()或者compact())。clear方法会清空整个缓冲区,compact只会清除已经读过的数据,未读的数据会移到缓冲区起始位置,新写入的数据会放到这些未读数据之后。
Buffer的三个属性:
- capacity :缓冲区内存块的容量
- position :根据读或写操作动态改变。写:初始为0,写入数据后,position移到下一个可插入数据的位置;读:将Buffer切换为read模式,position重置为0,读取数据时,position移动到下一个可读位置
- limit:写模式下,代表最多能写入多少数据,等同于capacity;读模式下,代表最多能读多少数据,即写模式下的position值。相当于你能读到之前写入的所有数据。
Java NIO 和 IO的区别
IO | NIO |
---|---|
面向流 | 面向缓冲 |
阻塞IO | 非阻塞IO |
Selectors |
面向流与面向缓冲
面向流意味着每次从流中读取一个或多个字节,直到读取完所有字节,没有被缓存在任何地方。
面向缓冲便是将数据读取到一个缓冲区,需要时可以在缓冲区中前后移动取数据,增加灵活性
阻塞与非阻塞IO
阻塞IO意味着一个线程调用read()或write()时,该线程阻塞,直到数据被读取或者完全写入,此过程期间该线程不能干别的事情。
非阻塞IO是一个线程通过一个Channel请求读取数据,仅能读取当前可用数据,如果没有可用数据,便不会读取,而不是保持线程阻塞。所以直至数据变成可读取之前,该线程可以继续做别的事情。一个线程管理多个Channel,便可以在空闲时间处理其他Channel上的任务
Selectors 帮助一个Thread管理多个Channel
比如读取如下文本
Name: Anna
Age: 25
Email: anna@mailserver.com
Phone: 1234567890
IO的方法:
InputStream input = ... ; // get the InputStream from the client socket
BufferedReader reader = new BufferedReader(new InputStreamReader(input));
String nameLine = reader.readLine();
String ageLine = reader.readLine();
String emailLine = reader.readLine();
String phoneLine = reader.readLine();
每次使用reader.readLine()
得到返回值时,便知道该行已经读取完毕
使用NIO
ByteBuffer buffer = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buffer);
while(! bufferFull(bytesRead) ) {
bytesRead = inChannel.read(buffer);
}
每次isChannel.read()
返回时,缓存区内存在哪些数据并不清楚,需要bufferFull()
判断缓冲区是否准备好被处理。
所以使用NIO对于读取数据解析过程会比阻塞IO读取数据更复杂