一、缓冲区基础

1、缓冲区的基础

        在 NIO 中,Buffer 是数据读写的核心。它是一个固定容量的用于承载数据的容器,用于在通道(Channel)和程序之间存放数据。

通俗来说:

通道负责“运输”,缓冲区负责“存放”。
数据总是先进入 Buffer,再从中读出。

2、Buffer的四大属性

属性含义可修改说明
capacity容量缓冲区可容纳的最大元素数
limit上界第一个不能被读写的位置
position当前位置下一个读或写操作的索引
mark标记记录一个position,可通过 reset 回到这里

属性之间总是遵循的关系:0 <= mark <= position <= limit <= capacity

3、缓冲区API

        缓冲区的各类操作大部分是围绕Buffer的四大属性来进行的,先看Buffer类的方法签名:

public abstract class Buffer {
    public final int capacity( )
    public final int position( )
    public final Buffer position (int newPositio)
    public final int limit( ) 
    public final Buffer limit (int newLimit) 
    public final Buffer mark( )
    public final Buffer reset( ) 
    public final Buffer clear( ) 
    public final Buffer flip( ) 
    public final Buffer rewind( ) 
    public final int remaining( ) 
    public final boolean hasRemaining( ) 
    public abstract boolean isReadOnly( ); 
}

        细心的同学应该可以看到很多方法返回值都是Buffer引用(this),这意味着可以链式(级联)调用,比如:

buffer.mark().position(5).reset( );

下面的如涉及具体操作都将以CharBuffer为例,其它的都大差不差。

3.1 存取 (get/put)

  Buffer 的核心作用是存储数据。通过 put() 写入数据,通过 get() 读取数据。它支持相对操作绝对操作两种方式:

操作类型方法示例特点
相对buffer.put(byte) / buffer.get()自动移动 position
绝对buffer.put(int index, byte) / buffer.get(int index)不影响 position

        异常处理

异常触发条件
BufferOverflowException写超出 limit
BufferUnderflowException读超出 limit
IndexOutOfBoundsException索引越界
3.2 翻转 (flip)

  flip() 是最常用的状态切换操作,可以理解为将 写模式 转为 读模式,将进行以下操作:

limit = position
position = 0

        这样通道或程序就可以从头开始读取刚写入的数据

        但是:重复 flip() 会导致 limit=position=0,此时缓冲区为空,读取会抛异常

3.3 释放 (drain)

        “释放”指从缓冲区中取出数据,通常配合通道读取,常见于:

  • 通道写入:channel.write(buffer)

  • 手动取出:循环 get()

······示例

buffer.flip(); // 进入读模式
while (buffer.hasRemaining()) {
    byte b = buffer.get();
    System.out.print((char) b);
}
3.4 压缩 (compact)

当缓冲区只读取了部分数据,compact() 可以将未读数据前移,以便继续写入新数据。

未读部分移到开头
position = 未读数据长度
limit = capacity

图解示意:

3.5 标记 (mark/reset)

提供在缓冲区中回退到某个位置的能力。

方法功能
mark()记录当前位置
reset()回到标记位置

注意:标记会在以下操作中被清除:

  • clear()

  • flip()

  • rewind()

  • 修改 limit 小于 mark 时

3.6 比较 (equals/compare To)

比较缓冲区内容时,只比较剩余部分(position→limit)

方法含义
boolean equals()内容完全相同
int compareTo()更加严格,按字典序比较

两个缓冲区相等的充要条件:

  • 两个对象类型相同
  • 两个对象都剩余同样数量的元素(Buffer的容量不需要相同,而且缓冲区中剩余数据的索引也不必相同)
  • 在每个缓冲区中应被Get()函数返回的剩余数据元素序列必须一致
3.7 批量移动(bulk operations)

相比逐个 get/put,批量移动能显著提高效率。

方向方法
数组 → 缓冲区

put (char[] src)

put (char [] src, int offset, int length)

缓冲区 → 数组

get (char [] dst)

get (char [] dst, int offset, int length)

缓冲区 → 缓冲区put (otherBuffer)

提示:

  • 将缓冲区内的数据放入数组时,批量传输的大小总是固定的。省略长度意味着整个数组会被填满;因此当传入一个数组并且没有指定长度,就相当于要求整个数组被填充。如果缓冲区中的数据不够完全填满数组,您会得到一个BufferUnderflowException异常。
  • 同样的,在将数据装入缓冲区时,如果缓冲区中没有足够的空间,那么不会有数据被传递,同时抛出一个BufferOverflowException异常。

数据读取并处理实例:

        char [] smallArray = new char [10];
        while (buffer.hasRemaining()) {
            int length = Math.min (buffer.remaining(), smallArray.length);
            buffer.get (smallArray, 0, length);
            processData (smallArray, length);
        }

4、常见的状态变化

方法作用将会进行的操作
clear()准备写入数据position=0, limit=capacity
flip()从写模式切到读模式limit=position, position=0
rewind()重头读position=0,不改 limit
compact()丢弃已读数据,保留未读position=未读数据末尾
mark()/reset()标记/回到标记用于重复读取

免责声明
本文内容整理自《Java NIO 中文版》及个人学习笔记,仅供学习与交流使用。文中示例及观点不代表官方立场,如有错误或侵权请联系作者及时更正或删除。转载请注明出处。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐