Java NIO(一):Channel 与 Buffer
2018-03-01 本文已影响108人
聪明的奇瑞
Java NIO 概述
- Java NIO 是 Java4 之后提供的一种带缓冲区、非阻塞 IO、且是双向通信的,数据是从通道到缓冲区,或从缓冲区到通道
- 它由 Channel、Buffer、Selector 这几个部分构成了核心的 API
Java IO 与 NIO 区别
- Java IO:
- 面向流、阻塞IO、单向通信
- 从流中读取一个或多个字节、直至读取所有字节、它不能移动流中的数据
- 优点:如果连接少,他的延迟是最低的,因为一个线程只处理一个连接,适用于少连接且延迟低的场景,比如说数据库连接
- JavaNIO:
- 面向缓冲、非阻塞IO、选择器、双向通信
- 将数据读取到一个缓冲区,需要时可以在缓冲区前后移动,增加灵活性
- 优点:阻塞业务处理但不阻塞数据接收,如果是需要管理同时成千上万个连接,这些连接每次只发送少量数据,如聊天室,适用于高并发且处理简单的场景
- 缺点:NIO 可以使用一个或几个单线程管理多个通道(网络连接),但付出的代价是解析数据可能会比从一个阻塞流中读取数据更复杂
Channel(通道)
- Channel 类似流,但又有些不同:
- 既可以从通道中读取数据,又可以写数据到通道。但流的读写通常是单向的
- 通道可以异步地读写
- 通道中的数据总是要先读到一个Buffer,或者总是要从一个Buffer中写入
- Channel 是通道的高层接口,其有几个实现类:
- FileChannel:从文件中读写数据,但无法设置为非阻塞模式
- DatagramChannel:能通过 UDP 读写网络中的数据
- SocketChannel:能通过 TCP 读写网络中的数据
- ServerSocketChannel:可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel
Buffer(缓冲区)
- 缓冲区本质上是一块可以读写数据的内存,这块内存被包装成了 NIO 的 Buffer 对象,并提供一组方法,用来方便的访问该块内存。
- 数据是从通道读入缓冲区,从缓冲区写入到通道中的
- Buffer 是缓冲区的高层接口,其有几个实现类:
- ByteBuffer、MappedByteBuffer、CharBuffer、DoubleBuffer
- FloatBuffer、IntBuffer、LongBuffer、ShortBuffer
- 通过 allocate 方法分配一个指定大小的 Buffer
ByteBuffer buf = ByteBuffer.allocate(48);
CharBuffer buf = CharBuffer.allocate(1024);
- 新创建的 Buffer 为写模式,向 Buffer 中写数据有两种方式:
- 从 Channel 写到 Buffer
int bytesRead = inChannel.read(buf);
- 通过 put 方法写到 Buffer
buf.put(127);
- 在写模式下调用 flip() 时 Buffer 切换为读模式,从 Buffer 中读数据有两种方式:
- 从Buffer读取数据到Channel
int bytesWritten = inChannel.write(buf);
- 通过 get 方法从 Buffer 中读取数据
byte aByte = buf.get();
- 常用方法:
- flip():翻转缓冲区,切换输入/输出模式
buf.put("header ".getBytes()); // 往缓冲区输入数据 in.read(buf); // 将数据从从通道读入缓冲区 buf.flip(); // 翻转缓冲区 out.write(buf); // 输出缓冲区数据写入到通道
- rewind():将 position 设回 0,所以可以重读 Buffer 中所有数据
- clear():清空缓冲区,Buffer 切换回写模式,将 position 设回 0,但实际上 Buffer 中的数据并未被清除,只是标记回 0 后新写入的数据会覆盖原来的数据
- compact():清除已读过的数据,未读的数据会被移至缓冲区起始处,Buffer 切换回写模式,新写入的数据将从缓冲区未读数据后面写入
- mark():标记 Buffer 中一个特定的 position,之后可通过 reset() 方法恢复到这个 position
-
equals():比较两个 Buffer 是否相同
- 它必须有相同的类型(int、byte 等)
- Buffer 中剩余的 byte、char 等个数相同
- Buffer 中所有剩余的 byte、char 等都相同
-
compareTo():比较两个 Buffer 大小,如满足以下条件则认为一个 Buffer 小于另一个 Buffer
- 第一个不相等的元素小于另一个 Buffer 中对应的元素
- 所有元素都相等,第一个 Buffer 剩余的空间小于第二个 Buffer
Buffer 的 capacity,position 和 limit
- Buffer 包含了三个属性:
-
capacity:
- 代表 Buffer 的容量大小
- 一旦 Buffer 满了就需要将其清空(通过读取数据或清除数据)才能继续往缓冲区里写数据
-
position:
- 当写数据到Buffer中时 position 表示当前的位置,初始值为 0,最大值为 capacity - 1,每写入一个 byte、long 等数据时 position 会下移到下一个可写的 Buffer 单元
- 当切换 Buffer 为读数据时,position 会被重置为 0,每读入一个 byte、long 等数据时下移到下一个可读的位置
-
limit:
- 在写模式下,limit 表示最多能写入多少数据,等于 capacity
- 在读模式下,limit 表示最多能读到多少数据,等于于写模式下的 position 值
-
capacity:
示例
- 使用 Buffer 读写数据一般遵循以下四个步骤:
- 输入数据到 Buffer
- 调用 flip() 翻转缓冲区为输出模式
- 将 Buffer 中数据输出
- 调用 clear() 方法或者 compact() 方法清空缓冲区
可以通过 RandomAccessFile 对象的 getChannel 方法获取 Channel,因为数据无论如何变化终究还是以字节形式存储
try (RandomAccessFile raf = new RandomAccessFile("/Users/linyuan/Documents/字目录.txt", "rw")) {
// 获取通道 Channel
FileChannel fileChannel = raf.getChannel();
// 创建缓冲区 Buffer
ByteBuffer byteBuffer = ByteBuffer.allocate(52);
// 将通道的数据读入缓冲区
int bytesRead = fileChannel.read(byteBuffer);
while (bytesRead != -1) {
// 切换缓冲区模式
byteBuffer.flip();
// 读取缓冲区字节
while (byteBuffer.hasRemaining()) {
System.out.print((char) byteBuffer.get());
}
byteBuffer.clear();
bytesRead = fileChannel.read(byteBuffer);
}
} catch (Exception e) {
e.printStackTrace();
}