Java NIO由一下几个核心部分:
- Channels
- Buffers
- Selectors 通道 ( Channels ) Java NIO的通道类似流,但又有些不同:
- 既可以从通道中读取数据,又可写数据到通道
- 通道可以异步地读写
- 通道中的数据总是要先读到一个Buffer,或者要从一个Buffer写入
Channel的实现:
- FileChannel:从文件中读写数据
- SocketChannel:通过TCP读写网络中的数据
- DatagramChannel:通过UDP读写网络中的数据
- ServerSocketChannel:监听新进来的TCP连接,想web服务器那样,对每一个新进来的连接都会创建一个SocketChannel
下面是一个读取文件实例
RandomAccessFile aFile = new RandomAccessFile("LinuxPro.iml", "rw");
FileChannel fileChannel = aFile.getChannel();
ByteBuffer bf = ByteBuffer.allocate(48);
while (fileChannel.read(bf) != -1) {
System.out.println("Read:" + bf);
bf.flip();
System.out.println("Read:" + bf);
while (bf.hasRemaining()) {
System.out.print((char) bf.get());
}
System.out.println();
bf.clear();
}
aFile.close();
缓存区 ( Buffer )
使用Buffer读写数据一般遵循一下四个步骤:
- 写入数据到Buffer
- 调用flip()方法
- 从Buffer中读取数据
- 调用clear()方法或者campact方法
Buffer的 capacity, position 和 limit
limit:在写模式下,Buffer的limit表示你最多能往Buffer里写多少数据,写模式下,等于Buffer的capacity。 position:在写模式下,position表示当前的位置。初始值为0,最大可为capacity-1. capacity:一个内存块,Buffer的固定的大小值。
Buffer的类型
Java NIO有以下Buffer类型:
- ByteBuffer
- MappedByteBuffer
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer
Buffer 的分配与读据
Buffer的分配
ByteBuffer buf = ByteBuffer.allocate(48);
写数据到Buffer有两种方式:
- 从Channel写到Buffer
- 通过Buffer的put()方法写到Biffer里
从Channel写到Buffer的例子
int bytesRead = inChannel.read(buf); //read into buffer.
通过put方法写Buffer的例子
buf.put(127);
从Buffer中读取数据有两种方式:
- 从Buffer读取数据到Channel
- 使用get()方法从Buffer中读取数据
从Buffer读取数据到Channel的例子 int byteWrtten = inChannel.write(buf);
使用get()方法从Buffer读取数据的例子
byte aByte = buf.get();
flip() 方法
flip 方法将 Buffer 从写模式切换到读模式。调用 flip() 方法会将 position 设回 0,并将 limit 设置成之前 position 的值。
rewind() 方法
Buffer.rewind() 将 position 设回 0,所以你可以重读 Buffer 中的所有数据。limit 保持不变,仍然表示能从 Buffer 中读取多少个元素(byte、char 等)。
clear() 与 compact()方法
一旦读完 Buffer 中的数据,需要让 Buffer 准备好再次被写入。可以通过 clear() 或 compact() 方法来完成。 如果调用的是 clear() 方法,position 将被设回 0,limit 被设置成 capacity 的值。 如果 Buffer 中有一些未读的数据,调用 clear() 方法,数据将 “被遗忘”,意味着不再有任何标记会告诉你哪些数据被读过,哪些还没有。 如果 Buffer 中仍有未读的数据,且后续还需要这些数据,但是此时想要先先写些数据,那么使用 compact() 方法。 compact() 方法将所有未读的数据拷贝到 Buffer 起始处。然后将 position 设到最后一个未读元素正后面。limit 属性依然像 clear() 方法一样,设置成 capacity。现在 Buffer 准备好写数据了,但是不会覆盖未读的数据。
equals() 与 compareTo() 方法
当满足下列条件时,表示两个 Buffer 相等equals():
- 有相同的类型(byte、char、int 等)。
- Buffer 中剩余的 byte、char 等的个数相等。
- Buffer 中所有剩余的 byte、char 等都相同。
compareTo() 方法比较两个 Buffer 的剩余元素 (byte、char 等), 如果满足下列条件,则认为一个 Buffer“小于” 另一个 Buffer:
- 第一个不相等的元素小于另一个 Buffer 中对应的元素 。
- 所有元素都相等,但第一个 Buffer 比另一个先耗尽 (第一个 Buffer 的元素个数比另一个少)。
Scatter/Gather
Java NIO 开始支持 scatter/gather,scatter/gather 用于描述从 Channel(译者注:Channel 在中文经常翻译为通道)中读取或者写入到 Channel 的操作。 分散(scatter)从 Channel 中读取是指在读操作时将读取的数据写入多个 buffer 中。因此,Channel 将从 Channel 中读取的数据 “分散(scatter)” 到多个 Buffer 中。 聚集(gather)写入 Channel 是指在写操作时将多个 buffer 的数据写入同一个 Channel,因此,Channel 将多个 Buffer 中的数据 “聚集(gather)” 后发送到 Channel。 代码示例如下:
ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);
//write data into buffers
ByteBuffer[] bufferArray = { header, body };
channel.write(bufferArray);
通道之间的数据传输
在 Java NIO 中,如果两个通道中有一个是 FileChannel,那你可以直接将数据从一个 channel(译者注:channel 中文常译作通道)传输到另外一个 channel。 transferFrom():FileChannel 的 transferFrom() 方法可以将数据从源通道传输到 FileChannel 中(译者注:这个方法在 JDK 文档中的解释为将字节从给定的可读取字节通道传输到此通道的文件中)。 transferTo():transferTo() 方法将数据从 FileChannel 传输到其他的 channel 中。 示例代码如下:
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();
fromChannel.transferTo(position, count, toChannel);
选择器
Selector(选择器)是 Java NIO 中能够检测一到多个 NIO 通道,并能够知晓通道是否为诸如读写事件做好准备的组件。这样,一个单独的线程可以管理多个 channel,从而管理多个网络连接。 完整的示例如下:
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();
}
}
Pipe ( NIO 管道 )
Java NIO 管道是 2 个线程之间的单向数据连接。Pipe有一个 source 通道和一个 sink 通道。数据会被写到 sink 通道,从 source 通道读取。
创建管道
通过Pipe.open()方法打开管道。例如:
Pipe pipe = Pipe.open();
向管道写数据
要向管道写数据,需要访问 sink 通道。像这样:
Pipe.SinkChannel sinkChannel = pipe.sink();
通过调用 SinkChannel 的write()方法,将数据写入SinkChannel, 像这样:
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);
}
从管道读取数据
从读取管道的数据,需要访问 source 通道,像这样:
Pipe.SourceChannel sourceChannel = pipe.source();
调用 source 通道的read()方法来读取数据,像这样:
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = sourceChannel.read(buf);
Java NIO 与 IO
当学习了 Java NIO 和 IO 的 API 后,一个问题马上涌入脑海: 我应该何时使用 IO,何时使用 NIO 呢?在本文中,我会尽量清晰地解析 Java NIO 和 IO 的差异、它们的使用场景,以及它们如何影响您的代码设计。 Java NIO 和 IO 的主要区别
IO | NIO |
---|---|
面向流 | 面向缓冲 |
阻塞IO | 非阻塞IO |
无 | 选择器 |
作 者:ChanghuiN 原文链接:http://www.hchstudio.cn/article/2016/692b/ 版权声明:非特殊声明均为本站原创作品,转载时请注明作者和原文链接。