java1.4引入了NIO.标准的IO基于字节流和字符流进行操作的,而NIO是基于通道(Channel)和缓冲区(Buffer)进行操作.
NIO中的读操作是从channel到buffer,写操作是从buffer到channel
标准IO中流总是单向的,channel则是双向的,数组总是从channel读到buffer,然后再从buffer取出。或者先写入buffer,再从buffer进入到channe中传输到目标方
Buffer
// Invariants: mark <= position <= limit <= capacity
private int mark = -1;
private int position = 0;
private int limit;
// 固定大小,满了需要清空(读/清除)
private int capacity;
position取决于读或者写,每写入一个数据,position向前移动一位。position最大可为capacity – 1.从写模式切换到读模式时,position会变为0,同时,每读一位,position向后移动一位。
写模式下,limit等于Buffer的capacity。 当切换Buffer到读模式时,此时的 limit 等于 Buffer 中实际的数据大小,因为 Buffer 不一定被写满了。
buffer的初始化:
2中方法:
// 1
ByteBuffer buffer=ByteBuffer.allocate(1024);
// 2
ByteBuffer writeBuffer=ByteBuffer.wrap(("我已经收到你的请求,你的请求内容是"+re).getBytes());
填充buffer
有可以自己控制填充数据的put方法比如
// 填充一个 byte 值
public abstract ByteBuffer put(byte b);
// 将一个数组中的值填充进去
public final ByteBuffer put(byte[] src) {...}
最常用的还有就是很常用的从channel来的数据,会先进入buffer才能被读取到
int num = channel.read(buf); // num即从channel读入到buffer的数据大小
获取buffer中的值
获取buffer值的过程一定是在从channel向buffer中写入之后,所以需要先从写模式切换到读模式,采用flip()
public final Buffer flip() {
limit = position; // 将 limit 设置为实际写入的数据数量
position = 0; // 重置 position 为 0
mark = -1; // mark 之后再说
return this;
}
对应填充buffer的put,这里还有很多get函数获取
// 将 Buffer 中的数据写入到数组中
public ByteBuffer get(byte[] dst)
// 根据当前position 来获取数据
public abstract byte get();
// 获取buffer中的字节数组
buffer.array()
将buffer中的值写入到channel
int num = channel.write(buf);
clear&&compact
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
重置buffer,相当于创建buffer新实例,可以看到数据并没有被清除,但是当新的数据到来时,原来数据会被覆盖,也就相当于清除
compact则是会保留原来数据调用这个方法以后,会先处理还没有读取的数据,也就是 position 到 limit 之间的数据(还没有读过的数据),先将这些数据移到左边,然后在这个基础上再开始写入。很明显,此时 limit 还是等于 capacity,position 指向原来数据的右边。
Buffer的工作无非就是
put() 一下数据、flip() 切换到读模式、然后用 get() 获取数据、clear() 一下清空数据、重新回到 put() 写入数据。
Selector
非阻塞的实现就是基于selector,所谓的多路复用,即是一个线程管理多个channel
Selector 建立在非阻塞模式之上,所以注册到 Selector 的 Channel 必须要支持非阻塞模式
select()
调用此方法,会将上次 select 之后的准备好的 channel 对应的 SelectionKey 复制到 selected set 中。如果没有任何通道准备好,这个方法会阻塞,直到至少有一个通道准备好。
selectNow()
功能和 select 一样,区别在于如果没有准备好的通道,那么此方法会立即返回 0。
select(long timeout)
看了前面两个,这个应该很好理解了,如果没有通道准备好,此方法会等待一会
wakeup()
这个方法是用来唤醒等待在 select() 和 select(timeout) 上的线程的。如果 wakeup() 先被调用,此时没有线程在 select 上阻塞,那么之后的一个 select() 或 select(timeout) 会立即返回,而不会阻塞,当然,它只会作用一次。