Buffer本质是内存的一块,可以写入或者获取数据。
java.nio定义了CharBuffer\ShortBuffer\IntBuffer\LongBuffer\FloatBuffer\DoubleBuffer\ByteBuffer->MappedByteBuffer的实现,核心是ByteBuffer。可以对应理解为相应基本类型的数组。
flip()方法-仅用于将buffer从写入模式切换到读取模式,而且在读取时必须调用,否则读不出数据。
注意,通常在说 NIO 的读操作的时候,我们说的是从 Channel 中读数据到 Buffer 中,对应的是对 Buffer 的写入操作。
public final Buffer flip() {
limit = position; // 将 limit 设置为实际写入的数据数量
position = 0; // 重置 position 为 0
mark = -1;
return this;
}
allocate(int capacity)
可以实例化一个Buffer,另外wrap方法亦可以初始化Buffer,它接收一个byte[] 参数。
使用put方法填充Buffer。可以接收byte、int(指定index位置)和byte、byte[]参数,需要控制Buffer大小不能超过capacity.
或者将来自channel的数据填充到Buffer中,依照前文所述,在系统层面上称之为NIO的读操作。
int num = channel.read(buffer);
返回从channel中读入到Buffer的数据大小。
首先切换模式,使用flip()方法,即切换position和limit。对应一系列put方法, 也有一系列get方法,如根据position获取数据的byte get()、获取指定位置数据的byte get(int index)、将buffer中的数据写入到数组中的ByteBuffer get(byte[] dest)。
更加常用的是写操作,将Buffer中的值通过各种channel写入到对应的位置。
int num = channel.write(buffer);
buffer的Mark属性主要是为了临时保存Position的值,提供mark()方法将mark的值设置为当前的position。后续有需要的时候调用reset()方法,可以回到Position为mark的地方。
rewind():重置position为0,将mark设置为-1,可以用于从头读写buffer。
clear(): 重新初始化buffer的主要属性,相当于重新实例化buffer,一般在重新填充buffer之前调用clear()。
clear()并不会清空buffer中的数据,只是初始化了buffer中的主要属性。所以后续写入的数据,会在position=0的位置重新写入,相当于清空了数据。
compact():也是准备在buffer写入新数据之前调用。但是它会将position到limit之间的数据移到左边,在这个基础上再开始写入。此时limit等于capacity,position指向原来数据的右面。
具体的一个compact()实现:
//整体是将position到limit之间的数据移到左面。
public LongBuffer compact() {
System.arraycopy(hb, ix(position()), hb, ix(0), remaining());
position(remaining());//将position设置为limit-position
limit(capacity());//将limit设置为capacity
discardMark();//将mark设置为-1
return this;
}
channel是数据来源或数据写入的目的地,java.nio包中实现的有FileChannel\SocketChannel\DatagramChannel\ServerSocketChannel.
其中:
重点关注SocketChannel和ServerSocketChannel.
Channel类似于IO中的流,读操作的时候把channel中的数据填充到buffer中,写操作时将Buffer中的数据写入到channel中。
再次说明,NIO层面上的“读”操作和“写”操作是相对于Channel而言,即“读”是从Channel中读到buffer中(对应channe的读取),“写”是从buffer写到channel中(对应channel的写入)。而对应buffer的操作正相反,“读”对应buffer的“写入”,“写”对应buffer的“读取”。
读操作:channel.read(buffer); 将数据从channel读到buffer中,进行后续处理。
写操作:channel.write(buffer); 将数据从Buffer写入到channel中。
※ 这两个方法都是channel的实例方法。
※ 所有channel都和buffer做交互。
打开一个TCP连接:
SocketChannel sc = SocketChannel.open();
sc.connect(new InetSocketAddress("http://www.baidu.com", 80));
//读取
sc.read(buffer);
//写入
while(buffer.hasRemaining()) {
sc.write(buffer);
}
后续部分在ServerSocketChannel中再看。
用于监听机器端口,管理从这个端口进来的TCP连接。
ServerSocketChannel ssc = ServerSocketChannel.open();
//监听8080
ssc.socket().bind(new InetSocketAddress(8080));
while(true) {
//一旦有TCP连接进入,对应创建一个socketChannel处理。
SocketChannel sc = ssc.accept();
}
DatagramChannel一个类处理服务端和客户端。
//监听端口
DatagramChannel dc = DatagramChannel.open();
dc.socket().bind(new InetSocketAddress(9090));
ByteBuffer byf = ByteBuffer.allocate(48);
ddc.receive(buf);
//发送数据
String data = "new Data";
ByteBuffer buf = ByteBuffer.allocate(48);
buf.put(data.getBytes());
buf.flip();//准备切换到buffer的读取
int byteSent = dc.send(buf, new InetSocketAddress("anyuri"), 80);
NIO中的多路复用器,用于一个线程管理多个channel.Selector建立在非阻塞的基础上(FileChannel不能使用).
使用方式(基本的接口操作):
//开启Selector
Selector selector = Selector.open();
//注册Channel到Selector上
//必须开启非阻塞模式
SocketChannel channel = SocketChannel.open(new InetSocketAddress("anyuri", 80));
channel.configureBlocking(false);
//注册
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
register(Selector s, int ops)中第二个int参数,代表需要监听的类型。SelectionKey中共定义了4种类型常量:
OP_READ = 1 << 0; (00000001)
OP_WRITE = 1 << 2; (00000100)
OP_CONNECT = 1 << 3; (00001000)
OP_ACCEPT = 1 << 4; (00010000)
可以同时监听多个事件,如要同时监听read和accept事件,指定ops为OP_READ+OP_ACCEPT即可。
channel注册该方法返回一个selectionKey实例。
接下来调用select()方法获取通道信息。
回顾步骤:
Selector 的其他方法:
Selector操作的简单示例:
SocketChannel sc = SocketChannel.open(new InetSocketAddress("anyuri", 80));
sc.configureBlocking(false);
Selector selector = Selector.open();
SelectionKey key = sc.register(selector, SelectionKey.OP_READ);
while(true) {
int readyChannelNum = selector.select();
if (readyChannelNum == 0) continue;
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
while(keyIterator.hasNext()) {
if (key.isAcceptable()) {
//某连接处于accept状态
} else if (key.isConnectable()) {
//某连接被远程服务器建立,处于可连接状态
} else if (key.isReadable()) {
//某连接处于可读状态
} else if (key.isWritable()) {
//某连接处于可写状态
}
//移除该key
keyIterator.remove();
}
}