Java NIO(一):Channel 与 Buffer

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 是缓冲区的高层接口,其有几个实现类:
    • ByteBufferMappedByteBufferCharBufferDoubleBuffer
    • FloatBufferIntBufferLongBufferShortBuffer
  • 通过 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 值

示例

  • 使用 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();
}

你可能感兴趣的:(Java NIO(一):Channel 与 Buffer)