Channel和Buffer
概述与实现
所有的IO在NIO中都是从channel开始,channel像是流,buffer像是缓冲区,流可以读到缓冲区中,缓冲区可以写到流中,如下图所示

channel主要实现:FileChannel(文件流),DatagramChannel(数据报流-UDP),SocketChannel(socket流-TCP),ServerSocketChannel(服务端socket流-TCP)
Buffer主要实现(主要是七个IO的基本类型):ByteBuffer,CharBuffer,DoubleBuffer,FloatBuffer,IntBuffer,LongBuffer,ShortBuffer
简单的FileChannel读取数据到buffer
buffer内存模型的三个标记位:
1.capacity(容量):buffer初始化时分配的容量
2.position(位置):buffer内读取或写入的位置(读取或写入时position从0开始,如果在get(position)方法中有赋值,则从该位置开始)
3.limit(最大容量):读取或写入的最大位置(读取时,置为position的位置,写入时,置为catacity的位置) 总结:不管是读取还是写入,都是从position标志的位置开始,到limit的位置结束

将channel读入buffer的示例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
public void simpleChannelToBuffer(String fromPath){
ByteBuffer buffer=
null;
RandomAccessFile file=
null;
FileChannel fileChannel=
null;
try {
file =
new RandomAccessFile(fromPath,
"rw");
fileChannel = file.getChannel();
buffer = ByteBuffer.allocate(
48);
int bytesRead =
0 ;
while((bytesRead=fileChannel.read(buffer))!=-
1){
System.out.print(bytesRead);
buffer.flip();
while(buffer.hasRemaining()){
System.out.print((
char)buffer.get());
}
buffer.clear();
}
}
catch (FileNotFoundException e) {
e.printStackTrace();
}
catch (IOException e) {
e.printStackTrace();
}
finally {
try {
fileChannel.close();
file.close();
}
catch (IOException e) {
e.printStackTrace();
}
}
}
|
Scatter(分散)与Gather(聚集)
Scatter(分散):从channel读取数据写入多个buffer中

代码如下:
1
2
3
4
|
ByteBuffer header = ByteBuffer.allocate(
128);
ByteBuffer body = ByteBuffer.allocate(
1024);
ByteBuffer[] bufferArray = { header, body };
channel.read(bufferArray);
|
Gather(聚集):将多个buffer的数据写入同一个channel中

代码如下
1
2
3
4
|
ByteBuffer header = ByteBuffer.allocate(
128);
ByteBuffer body = ByteBuffer.allocate(
1024);
ByteBuffer[] bufferArray = { header, body };
channel.write(bufferArray);
|
通道之间的数据传输
1
2
3
4
5
6
7
8
|
RandomAccessFile fromFile =
new RandomAccessFile(
"fromFile.txt",
"rw");
FileChannel fromChannel = fromFile.getChannel();
RandomAccessFile toFile =
new RandomAccessFile(
"toFile.txt",
"rw");
FileChannel toChannel = toFile.getChannel();
long position =
0;
long count = fromChannel.size();
toChannel.transferFrom(position, count, fromChannel);
|
注意:在SocketChannel中,传输的只会是准备好的数据,可能不足count大小,但是一有数据就会传输,只到buffer被填满
Selector
如果你想单线程异步处理多个流,或者是你的应用打开了多个链接(通道),但是每个链接的的流量又很低,这就是Selector的工作

Selector首先要注册Channel,调用select()方法,一直阻塞到有某个注册的通道(Channel)有事件(数据或者新接连)就绪,等到这个方法返回,线程就开始处理这个通道中的事件
注意:与Selector一起使用时,Channel必须处于非阻塞模式下。这意味着不能将FileChannel与Selector一起使用,因为FileChannel不能切换到非阻塞模式。而套接字通道都可以
selector可以监听channel以下四种不同类型的事件:
- OP_CONNECT(连接就绪) :比如:socketChannel
- OP_ACCEPT(接收就绪):比如:ServerSocketChannel
- OP_READ(读就绪)
- OP_WRITE(写就绪)
代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
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()) {
}
else
if (key.isConnectable()) {
}
else
if (key.isReadable()) {
}
else
if (key.isWritable()) {
}
keyIterator.remove();
}
}
|
pipe
Java NIO 管道是2个线程之间的单向数据连接。Pipe有一个source通道和一个sink通道。数据会被写到sink通道,从source通道读取

首先创建Pipe:Pipe pipe = Pipe.open();
ThreadA向管道写数据,访问sink通道,代码如下:
1
2
3
4
5
6
7
8
9
|
Pipe.SinkChannel sinkChannel = pipe.sink();
String newData =
"New String to write to file..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(
48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
while(buf.hasRemaining()) {
sinkChannel.write(buf);
}
|
ThreadB向管道读取数据,访问source通道,代码如下:
1
2
3
|
Pipe.SourceChannel sourceChannel = pipe.source();
ByteBuffer buf = ByteBuffer.allocate(
48);
int bytesRead = sourceChannel.read(buf);
|
NIO和IO
NIO与IO的差异如下:
IO | NIO |
---|
面向流 | 面向缓冲 |
阻塞IO | 非阻塞IO |
无 | 选择器 |
面向流和面向缓冲
IO是面向流的,是一个不可控制处理方式,必须从头到尾直到读取所有的字节,没有缓冲的余地,而NIO是面向缓冲区的,它先将流中的数据读取到缓冲区中,然后再对数据进行处理,这样增加了数据处理的灵活性
阻塞和非阻塞
IO是阻塞IO,当一个线程在读取数据的时候是阻塞的,再次期间不能做其他的任何事情,直到读写数据结束
NIO是非阻塞IO,当一个线程在读取数据时,在该数据变成可读性之前,也就是空闲时间,可以做其他事情
选择器
Java NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道