JAVA NIO 之 Buffer

2018-09-28  本文已影响0人  苏黎世黄昏

原文:https://segmentfault.com/a/1190000006824155

Java NIO Buffer


当我们需要与 NIO Channel 进行交互时,我们就需要使用到 NIO Buffer,即数据从 Buffer写入到 Channel 中,并且从 Channel 中读取到 Buffer 中。

实际上,NIO Buffer 其实是一块内存区域的封装,并提供了一些操作方法让我们能够方便地进行数据的读写。

Buffer 类型有:

这些 Buffer 已经覆盖了能从 IO 中传输的所有的 Java 基本数据类型。

NIO Buffer 的基本使用

使用 NIO Buffer 的步骤如下:

当我们将数据写入到 Buffer 中时,Buffer 会记录我们已经写了多少的数据。当我们需要从 Buffer 中读取数据时,必须调用 Buffer.flip() 将 Buffer 切换为读模式。

一旦读取了所有的 Buffer 数据,那么我们必须清理 Buffer,让其变为重新可写的,清理 Buffer 可以调用 Buffer.clear() 或 Buffer.compact()。

示例:

public class Test {
    public static void main(String[] args) {
        IntBuffer intBuffer = IntBuffer.allocate(2);
        intBuffer.put(12345678);
        intBuffer.put(2);
        intBuffer.flip();
        System.err.println(intBuffer.get());
        System.err.println(intBuffer.get());
    }
}

上面代码中,我们分配两个单位大小的 IntBuffer,因此它可以写入两个 int 值。
我们使用 put 方法将 int 值写入,然后使用 flip 方法将 buffer 转换为读模式,然后连续使用 get 方法从 buffer 中获取这两个 int 值。

每当调用一次 get 方法读取数据时,buffer 的读指针都会向前移动一个单位长度(在这里是一个 int 长度)

Buffer 属性

一个 Buffer 有三个属性:

其中 position 和 limit 的含义与 Buffer 处于读模式或写模式有关,而 capacity 的含义与 Buffer 所处的模式无关。

Capacity

一个内存块会有一个固定的大小,即容量(capacity),我们最多写入 capacity 个单位的数据到 Buffer 中,例如一个 DoubleBuffer,其 Capacity 是 100,那么我们最多可以写入 100 个 double 数据。

Position

当从一个 Buffer 中写入数据时,我们是从 Buffer 的一个确定的位置(position)开始写入的。在最初的状态时,position 的值是 0。每当我们写入了一个单位的数据后,position 就会递增 1。

当我们从 Buffer 中读取数据时,我们也是从某个特定的位置开始读取的。当我们调用了 filp() 方法将 Buffer 从写模式转换到读模式时,position 的值会自动被设置为0。每当我们读取一个单位的数据,position 的值递增 1。

position 表示了读写操作的位置指针。

limit

limit - position 表示此时还可以写入/读取多少单位的数据。
例如在写模式,如果此时 limit 是 10,position 是 2,则表示已经写入了 2 个单位的数据,还可以写入 10 - 2 = 8 个单位的数据。

示例:

public class Test {
    public static void main(String args[]) {
        IntBuffer intBuffer = IntBuffer.allocate(10);
        intBuffer.put(10);
        intBuffer.put(101);
        System.err.println("Write mode: ");
        System.err.println("\tCapacity: " + intBuffer.capacity());
        System.err.println("\tPosition: " + intBuffer.position());
        System.err.println("\tLimit: " + intBuffer.limit());

        intBuffer.flip();
        System.err.println("Read mode: ");
        System.err.println("\tCapacity: " + intBuffer.capacity());
        System.err.println("\tPosition: " + intBuffer.position());
        System.err.println("\tLimit: " + intBuffer.limit());
    }
}

这里我们首先写入两个 int 值,此时 capacity = 10,position = 2,limit = 10;
然后我们调用 flip 转换为读模式, 此时 capacity = 10,position = 0,limit = 2。

分配 Buffer

为了获取一个 Buffer 对象,我们首先需要分配内存空间。每个类型的 Buffer 都有一个 allocate() 方法,我们可以通过这个方法分配 Buffer:

ByteBuffer buf = ByteBuffer.allocate(48);

这里我们分配了 48 * sizeof(Byte) 字节的内存空间。

CharBuffer buf = CharBuffer.allocate(1024);

这里我们分配了大小为 1024 个字符的 Buffer,即这个 Buffer 可以存储 1024 个 Char,其大小为 1024 * 2 个字节。

Direct Buffer 和 Non-Direct Buffer 的区别

Direct Buffer:

Non-Direct Buffer:

Buffer 的读写

写入数据到 Buffer

// read into buffer.
int bytesRead = inChannel.read(buf); 
buf.put(127);

从 Buffer 中读取数据

// read from buffer into channel.
int bytesWritten = inChannel.write(buf);
byte aByte = buf.get();

重置 position

Buffer.rewind() 方法可以重置 position 的值为0,因此我们可以重新读取/写入 Buffer 了。
如果是读模式,则重置的是读模式的 position,如果是写模式,则重置的是写模式的 position。

示例:

public class Test {
    public static void main(String[] args) {
        IntBuffer intBuffer = IntBuffer.allocate(2);
        intBuffer.put(1);
        intBuffer.put(2);
        System.err.println("position: " + intBuffer.position());

        intBuffer.rewind();
        System.err.println("position: " + intBuffer.position());
        intBuffer.put(1);
        intBuffer.put(2);
        System.err.println("position: " + intBuffer.position());

        intBuffer.flip();
        System.err.println("position: " + intBuffer.position());
        intBuffer.get();
        intBuffer.get();
        System.err.println("position: " + intBuffer.position());

        intBuffer.rewind();
        System.err.println("position: " + intBuffer.position());
    }
}

rewind() 主要针对于读模式,在读模式时,读取到 limit 后,可以调用 rewind() 方法,将读 position 置为 0。

关于 mark() 和 reset()

我们可以通过调用 Buffer.mark() 将当前的 position 的值保存起来,随后可以通过调用 Buffer.reset() 方法将 position 的值恢复回来。

示例:

public class Test {
    public static void main(String[] args) {
        IntBuffer intBuffer = IntBuffer.allocate(2);
        intBuffer.put(1);
        intBuffer.put(2);
        intBuffer.flip();
        System.err.println(intBuffer.get());
        System.err.println("position: " + intBuffer.position());
        intBuffer.mark();
        System.err.println(intBuffer.get());

        System.err.println("position: " + intBuffer.position());
        intBuffer.reset();
        System.err.println("position: " + intBuffer.position());
        System.err.println(intBuffer.get());
    }
}

这里我们写入两个 int 值,然后首先读取了一个值。此时读 position 的值为 1。
接着我们调用 mark() 方法将当前的 position 保存起来(在读模式,因此保存的是读的 position),然后再次读取,此时 position 就是 2 了。
接着使用 reset() 恢复原来的读 position,因此读 position 又为 1 了,可以再次读取数据。

flip, rewind 和 clear 的区别

flip

flip 方法源码

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

Buffer 的读/写模式共用一个 position 和 limit 变量,当从写模式变为读模式时,原先的 写 position 就变成了读模式的 limit。

rewind

rewind 方法源码

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

rewind,即倒带,这个方法仅仅是将 position 置为 0。

clear

clear 方法源码

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

根据源码我们可以知道,clear 将 positin 设置为 0,将 limit 设置为 capacity。

clear 方法使用场景:

示例:

IntBuffer intBuffer = IntBuffer.allocate(2);
intBuffer.flip();
System.err.println("position: " + intBuffer.position());
System.err.println("limit: " + intBuffer.limit());
System.err.println("capacity: " + intBuffer.capacity());

// 这里不能读, 因为 limit == position == 0, 没有数据.
//System.err.println(intBuffer.get());

intBuffer.clear();
System.err.println("position: " + intBuffer.position());
System.err.println("limit: " + intBuffer.limit());
System.err.println("capacity: " + intBuffer.capacity());

// 这里可以读取数据了, 因为 clear 后, limit == capacity == 2, position == 0,
// 即使我们没有写入任何的数据到 buffer 中.
System.err.println(intBuffer.get()); // 读取到0
System.err.println(intBuffer.get()); // 读取到0

Buffer 的比较

我们可以通过 equals() 或 compareTo() 方法比较两个 Buffer,当且仅当如下条件满足时,两个 Buffer 是相等的:

通过上述条件我们可以发现,比较两个 Buffer 时,并不是 Buffer 中的每个元素都进行比较,而是比较 Buffer 中剩余的元素。

上一篇下一篇

猜你喜欢

热点阅读