Java NIO之Buffer

buffer

buffer类是nio的基础

Java buffer class

  • buffer对象可以成为一个固定大小的容器.
  • buffer和channel关系紧密, channel是io传输的入口, buffer是数据传输的源头或者是目标.
  • 对于向外传输,数据(我们想要发送的)被放置在缓冲区中。缓冲区被传递到一个输出通道。
  • 对于向内传输,通道将数据存储在我们提供的缓冲区中。然后数据从缓冲区复制到通道内。
  • 像上面说的这种数据转换是nio api执行高效的关键

buffer类结构如下, 顶部是通用的Buffer类, 该类提供了通用的操作, 这些操作与存储的数据类型无关.


Java NIO之Buffer_第1张图片
image

Buffer Attributes

原则上来说, buffer对象就是一个包含了原始数据的数组对象. buffer对象比一般数组有优势的地方是buffer封装了数据内容以及一些数据的信息.
下面是Buffer类的主要属性

  • Capacity: 容量, buffer可以存储的最大数据量. capacity在buffer创建的时候指定, 后面不许修改.
  • Limit: 长度, 当前buffer存储的数据量.
  • Position: 位置, 下一个可以被读取或者写的位置. position会根据get()和put()方法更新值.
  • Mark: 标记, 执行mark()方法会令mark = position, 执行reset()会令position = mark. 在没有设置mark前,mark的值为null.

Creating Buffers

一共有7种数据类型的Buffer(就是没有boolean). 每个类都是抽象的, 不可以直接创建. Buffer提供了两种创建方式

CharBuffer charBuffer = CharBuffer.allocate (100);

这种会在堆中创建一个可以容纳100个char的数组.

char [] myArray = new char [100];
CharBuffer charbuffer = CharBuffer.wrap (myArray);

这种和上面的区别是使用自己创建的数组, 那么后面对于该数组的修改myArray和charbuffer都可以感知到.

Working With Buffers

Accessing the Buffer - get() and put() Methods

这里的方法描述使用完全可以类比list. 区别是出现越界抛出的异常不同, 另一个是Buffer的大小是固定的, 不会像list自动扩容.

Filling the Buffer

现在我们尝试将"Hello"放到buffer中

CharBuffer charBuffer = CharBuffer.allocate (10);
buffer.put('H').put('e').put('l').put('l').put('o');
Java NIO之Buffer_第2张图片
image

下面对数据进行修改

buffer.put(0, 'M').put('w');
Java NIO之Buffer_第3张图片
image

可以看到位置0的数据已经修改, 但是并没有影响position, 还是可以继续put数据.

Flipping the Buffer

填充好数据后就要准备获取数据了. 如果在填充好数据马上执行get()方法, 会返回一个null数据, 因为此时的position指向的位置为null. 所以如果想要从头获取数据则需要将position设置为0. 那么一共要读取多少数据呢? 这就是limit需要做的. 在设置position之前需要设置limit的值. 可以使用flip()方法完成上述内容.

public final Buffer flip() {
    limit = position;
    position = 0;
    mark = -1;
    return this;
}

聪明如你, 肯定马上意识到一个问题, 如果连续调用两次filp()方法会怎么样? 根据代码, 两次调用后position = 0, limit = 0. 之后执行get()和put()方法都会抛出异常.

Draining the Buffer

有了上面的基础, 我们就可以循环读取所有的buffer内容了. 思路就是一直读取, 直到position == limit, 当然我们可以自己实现这个逻辑, 不过还是使用Buffer自带的方便, Buffer提供两个方法hasRemaining() 和 remaining(), 我们只需要每次获取数据前调用进行校验.

public final int remaining() {
    return limit - position;
}

public final boolean hasRemaining() {
    return position < limit;
}

Compacting the Buffer

有时候, 我们可能会只读取buffer中的一部分数据, 读取之后再往buffer中填充数据. 为了实现这个, 需要将未读取的数据转移到位置0的地方. Buffer提供了compact()方法完成上述操作.
我们可以通过这种方式将buffer当作一个先入先出队列. 当然还存在很多性能比这个好的队列算法, 但是从socket中读取逻辑数据块, 压缩方式是一中方便的同步方式.

注: 原文:You can use a buffer in this way as a First In First Out (FIFO) queue. More efficient algorithms certainly exist (buffer shifting is not a very efficient way to do queuing), but compacting may be a convenient way to synchronize a buffer with logical blocks of data (packets) in a stream you are reading from a socket.

在执行完compact()方法后, 在读取之前记得执行flip().

Bulk Data Movement from Buffers

Buffer的设计目标就是提高数据传输的效率, 但是之前介绍的方法都是一个一个数据读取, 这显然不够高效. 下面来看下批量的读取方式.
以CharBuffer为例, 该类提供了两个批量获取方法, 如下

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

两个方法都是将buffer中的内容复制到指定数组中, 区别是多参数方法会指定dst数组的开始offset和复制的长度length. 也就是说get(dst) 和 get(dst, 0, dst.length)是一样的.
当没有数据可以传输, 或者传输的数据不够数组的大小的话在执行批量操作后会抛出异常. 如果buffer的长度较小, 就需要在复制数据时指定长度, 代码如下:

char [] bigArray = new char [1000];

// 获取buffer可读取长度
int length = buffer.remaining(  );

// 读取指定长度, 这里假设length < 1000
buffer.get (bigArrray, 0, length);

// 数据处理
processData (bigArray, length);

当buffer比较大时, 我们可以使用迭代的方式分批获取数据, 代码如下:

char [] smallArray = new char [10];

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

put的批量方法与get比较类似, 就不赘述了.

Duplicating Buffers

以下三种方式可以复制buffer对象

public abstract CharBuffer duplicate();
public abstract CharBuffer asReadOnlyBuffer();
public abstract CharBuffer slice();

duplicate()会创建一个跟原buffer一模一样的buffer, 二者的数据共享(position limit mark这些不共享), 修改一个的数据另一个也会有影响.
asReadOnlyBuffer()方法与duplicate()的不同在于, asReadOnlyBuffer()创建的buffer是只读的, 禁止访问put方法, 否则会抛出异常.
slice()方法与duplicate()方法的区别在于, slice()只保留原buffer position到limit之间的数据.如图所示


Java NIO之Buffer_第4张图片
image

参考链接 https://howtodoinjava.com/java/nio/java-nio-2-0-working-with-buffers/

你可能感兴趣的:(Java NIO之Buffer)