Buffer

作者: JiinYuu | 来源:发表于2018-12-05 22:44 被阅读0次

Java NIO的Buffer是和其Channel一起用的。还是那句话,数据从Channel读入Buffer,从Buffer写入Channel

一个Buffer本质上一个可以写入数据的内存块,写入之后,你还可以从中读取数据。一个Buffer就是一个内存块的包装,同时提供了很多简单的方法来操作它。

Basic Buffer Usage

Buffer来读写数据一般分为以下4步:

  1. 将数据写入Buffer,一般是从Channel读入;
  2. 调用buffer.flip()
  3. Buffer获取数据;
  4. 调用buffer.clear()或者buffer.compact()

当你往Buffer里面写数据时,Buffer会记录你已经写入了多少数据。如果你想读取数据了,需要调用flip()方法将Buffer从写模式切换到读模式。在读模式下,你可以读取所有写入的数据。

一旦数据读完,你需要清理Buffer以将其重新切回写模式。有两种方式清理:调用clear()或者compact()clear()方法会清空整个Buffercompact()方法只会清除你以及读取过的数据。所有没有被读取的数据都会移动到Buffer的头部,并且后续写入的数据都会放到这些数据之后。

以下是一个Buffer的简单使用示例,其中写、切换、读以及清理操作都进行了注释:

RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();
// 创建一个48字节容量的Buffer
ByteBuffer buf = ByteBuffer.allocate(48);

int bytesRead = inChannel.read(buf); // 读取数据到Buffer
while (bytesRead != -1) {
    buf.flip(); // 切换到读模式
            
    while (buf.hasRemaining()) {
        System.out.print((char) buf.get()); // 读取一个字节
    }

    buf.clear(); // 清理Buffer,使其切换到写模式
    bytesRead = inChannel.read(buf);
}
aFile.close();

Buffer Capacity, Position and Limit

如上所述,一个Buffer本质上一个可以写入数据的内存块,写入之后,你还可以从中读取数据。一个Buffer就是一个内存块的包装,同时提供了很多简单的方法来操作它。

为了了解Buffer的工作原理,有三个属性你需要熟悉:

  • capacity
  • position
  • limit

positionlimit的具体含义依赖于当前Buffer是读模式还是写模式。capacity的含义则与Buffer的模式无关。

以下是上述三个属性在不同模式下的含义说明图: 读写模式下的capacity、position以及limit

Capacity

作为一个内存块,Buffer有一个确定的固定的大小,称作capacity。你只能往Buffer中写入与其capacity相等数量的字节、长整形数字、字符等。一旦Buffer满了,在下次写入数据之前,你必须先进行清理(从中读取数据或者清空它)。

Position

当你往Buffer里写入数据时,一定是写到一个具体的position。初始position是0。当一个字节、整数、字符等被写入Buffer时,position会前进到Buffer的下一个单元格,以便插入数据。position最大取值为capacity - 1

当你从Buffer读取数据时,同样也一定是从一个具体position读取。当你把一个Buffer从写模式切换到读模式时,其position会重置为0。当你从当前position读取一个数据,position会前进到下一个要读取的位置。

Limit

在写模式下,limit代表着你最多可以往Buffer里写入多少数据(字节、整数、字符等)。同时,在写模式下,limitcapacity相等。

切换到读模式后,limit代表着你从Buffer中最多能读取多少数据(字节、整数、字符等)。因此,当一个Buffer切换到读模式时,limit会被重置为写模式下的position的值。换句话说,之前写了多少数据,你就能读多少数据(limit被设置为了写入的数据(字节、整数、字符等)个数,即读模式下的position的值)。

Buffer Types

Java NIO提供了以下类型的Buffer

  • ByteBuffer
  • MappedByteBuffer
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffer

如你所见,这些不同类型的Buffer代表不同类型的数据。换句话说,它们允许你以charshortintlongfloatdouble的形式处理缓冲区中的字节。

MappedByteBuffer有些特别,我会在专门的章节中详细介绍。

Allocating a Buffer

要获得一个Buffer,需要先分配它。每个类型的Buffer都有一个allocate()方法来干这件事。以下是一个分配48字节容量的ByteBuffer的例子:

ByteBuffer buf = ByteBuffer.allocate(48);

以下是一个分配1024字符容量的CharBuffer的例子:

CharBuffer buf = CharBuffer.allocate(1024);

Writing Data to a Buffer

有两种方法可以把数据写入Buffer

  1. Channel将数据写入Buffer
  2. 手动调用put()方法将数据写入Buffer

以下是从Channel将数据写入Buffer的例子:

int bytesRead = inChannel.read(buf);

以下是手动写入的例子:

buf.put(127);

还有很多重载的put()方法,允许你通过各种方式将数据写入Buffer。比如,将数据写到指定位置,或者写入一个字节数组。详细信息可以参考各个类型的Buffer的实现类的JavaDoc。

flip()

flip()方法可以将Buffer从写模式切换到读模式。调用此方法会将position重置为0,且limit会被设置为position的值。

换句话说,position现在代表读的位置,而limit代表写入缓冲区的字节数、字符数等,即最多可以读取多少字节、字符等。

Reading Data from a Buffer

有两种方法从Buffer中读取数据:

  1. 将数据从Buffer读入Channel
  2. 手动调用get()方法读取。

以下是将数据从Buffer读入Channel的例子:

int bytesWritten = inChannel.write(buf);

以下是手动读取的例子:

byte aByte = buf.get();  

同样,还有很多重载的get()方法,允许你用各种方法读取数据。比如,从指定位置读取,或者读取一个字节数组。详细信息可以参考各个类型的Buffer的实现类的JavaDoc。

rewind()

rewind()会方法将position重置为0。因此你可以重新读取Buffer里的所有数据。limit会保持不变,依然代表最多可以从Buffer读取的数据量。

clear() and compact()

一旦读完Buffer中的数据,就必须使其为再次写入做好准备。你可以通过调用clear()compact()来实现这一点。

如果你调用了clear()方法,position会被重置为0,limit也会被设置为capacity。换句话说,Buffer被清空了。但Buffer里的数据并没有被清理。仅仅是标记了你可以在哪写入数据。

如果你调用clear()方法的是,Buffer里面还有未读取的数据,这些数据会被遗忘,意味着再也什么标记可以让你知道哪些数据是被读取过的,哪些还没有被读取。

如果Buffer里面还有数据,你想之后读取它们,现在你想先写入些数据,那么你可以调用compact()方法代替clear()方法。

compact()方法会将所有未读取的数据拷贝到Buffer的头部。然后设置position为最后一个未读数据的后面位置。和clear()一样,limit还是被设置为capacity。现在Buffer已经做好写入的准备了,但不用担心覆盖未读取的数据。

mark() and reset()

通过调用mark()方法,你可以给Buffer打一个位置标记。以后你可以通过调用reset()方法将position设置为之前打的标记的位置。如下:

buffer.mark();
// 调用buffer.get()一次或多次读取数据。
buffer.reset();

equals() and compareTo()

你可以使用equals()compareTo比较两个Buffer

equals()

两个Buffer是相等的,当:

  1. 它们的类型一样(字节、字符、整数等);
  2. 它们剩余的(未读取的)数据(字节、字符、整数等)量相同;
  3. 所有剩余的数据(字节、字符、整数等)都相等。

如你所见,equals()方法仅仅比较Buffer的一部分,并不是其内部的每一个元素。事实上,它只比较剩余的元素。

compareTo()

compareTo()方法比较两个Buffer中剩余的元素(字节、字符等),以便进行排序。一个Buffer被认为比另一个,当:

  1. 第一个与对应元素不相等的元素小于另一个Buffer中对应的元素 ;
  2. 所有元素都相等,但第一个Buffer先于第二个被读完(它的元素更少)。

说明

发现貌似有人在看这个系列文章了,有必要说明下,这个Java NIO系列来源于jenkov.com,本文只是翻译,希望大家千万不要误会,本文不是原创。原文地址:Java NIO

相关文章

网友评论

      本文标题:Buffer

      本文链接:https://www.haomeiwen.com/subject/zcbicqtx.html