Java NIO学习教程(二)

原文地址: link.

4.Java NIO Buffer

在与NIO Channel交互时使用Java NIO Buffer。如你所知,数据从Channel读入Buffer,并从Buffer写入Channel

Buffer本质上是一个可以写入数据的内存块,然后可以再次读取。此内存块包含在NIO Buffer对象中,该对象提供了一组方法,可以更轻松地使用内存块。

Basic Buffer Usage
使用Buffer读取和写入数据通常遵循以下四个步骤:

  • 将数据写入Buffer
  • 调用buffer.flip( )
  • 从Buffer读取数据
  • 调用buffer.clear()或buffer.compact()

将数据写入Buffer时,Buffer会跟踪你写入的数据量。一旦需要读取数据,就需要使用flip( )方法调用将Buffer从写入模式切换到读取模式。在读取模式下,Buffer允许你读取写入Buffer的所有数据。

一旦读完所有数据,就需要清除Buffer,以便再次写入。你可以通过两种方式完成此操作:通过调用clear( )或调用compact( )clear( )方法清除整个Buffer。compact( ) 方法仅清除你已读取的数据。任何未读数据都会移动到Buffer的开头,现在Buffer写入数据将在未读数据之后。

这是一个简单的Buffer用法示例,其中写入,翻转,读取和清除操作以粗体显示:

public class Exam2 {
    public static void main(String[] args) throws IOException {
        RandomAccessFile aFile = new RandomAccessFile("data.txt", "rw");
        FileChannel inChannel = aFile.getChannel();

        //创建容量为48字节的Buffer
        ByteBuffer buf = ByteBuffer.allocate(48);

        //读入Buffer
        int bytesRead = inChannel.read(buf);
        while (bytesRead != -1) {

            buf.flip();   //使Buffer准备好读取

            while (buf.hasRemaining()) {
                //一次读取1个字节
                System.out.print((char) buf.get());
            }

            //使Buffer准备好写入
            buf.clear();
            bytesRead = inChannel.read(buf);
        }
        aFile.close();
    }
}

Buffer Capacity(容量), Position(位置) 和 Limit(限制)
Buffer本质上是一个可以写入数据的内存块,然后可以再次读取。此内存块包含在NIO Buffer对象中,该对象提供了一组方法,可以更轻松地使用内存块。

Buffer有三个属性需要熟悉,以便了解其Buffer工作原理。这些是:

  • capacity(容量)
  • position(位置)
  • limit(限制)

positionlimit的含义取决于Buffer是处于读取还是写入模式。无论缓冲模式如何,capacity总是意味着相同。

以下是写入和读取模式下的capacitypositionlimit的说明。解释如下图所示。

图4

Capacity
作为一个内存块,Buffer具有一定的固定大小,也称为“容量”。可以将字节,长整数,字符等写入Buffer但不能超过Buffer的容量大小。Buffer已满后,你需要清空它(读取数据或清除它),然后才能将更多数据写入其中。

Position
当你将数据写入时Buffer,你可以在某个位置执行此操作。最初位置为​​0,当一个字节,长整数等已写入Buffer时,位置被提前指向Buffer中的下一个单元以插入数据。位置最大为:capacity - 1。

当从Buffer读取数据时,也可以从给定位置读取数据。将Buffer从写入模式切换到读取模式时,位置将重置为0。当从Buffer读取数据时,将从位置读取数据,并将位置提前到下一个要读取的位置。

Limit
在写入模式下,Buffer的限制(limit)是可以写入缓冲区的数据量的限制。在写入模式下,limit等于Buffercapacity

Buffer翻转为读取模式时,限制(limit)意味着可以从数据中读取的数据量的限制。因此,当将Buffer翻转到读取模式时,限制(limit)被设置为写入模式当时的位置(position)。换句话说,你可以读取写入的字节数(限制(limit)设置为写入的字节数,由位置标记)。

Buffer Types
Java NIO附带以下Buffer类型:

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

如你所见,这些Buffer类型代表不同的数据类型。换句话说,它们允许你使用char,short,int,long,float或double来处理Buffer中的字节。

MappedByteBuffer有点特殊,将在其自己的文本中介绍。

Allocating a Buffer
要获取Buffer对象,必须先分配它。每个Buffer类都有一个allocate( )方法来执行此操作。下面是一个显示ByteBuffer分配的示例,容量为48字节:

ByteBuffer buf = ByteBuffer.allocate(48);

下面是一个为1024个字符分配空间的CharBuffer示例:

CharBuffer buf = CharBuffer.allocate(1024);

将数据写入Buffer
可以通过两种方式将数据写入Buffer

  • 将数据Channel写入Buffer
  • 通过Buffer的put()方法,自己将数据写入缓冲区

下面是一个示例,显示了Channel如何将数据写入Buffer

int bytesRead = inChannel.read(buf);  //读入Buffer

这是一个通过put()方法将数据写入Buffer的示例:

buf.put(127);  

put( )方法还有许多其他版本,允许你以多种不同方式将数据写入Buffer。例如,在特定位置写入,或将一个字节数组写入Buffer。有关更多详细信息,请参阅JavaDoc以获取具体的Buffer实现。

flip( )
flip( )方法将Buffer从写入模式切换到读取模式。调用flip( )会将position设置为0,并将limit设为position之前的位置。

换句话说,position现在标记读取位置,limit标记有多少字节,字符等写入Buffer ——可读取的字节数,字符数等。

从Buffer读取数据
有两种方法可以从Buffer读取数据。

  • 将数据从Buffer读入Channel。
  • 使用其中的get( )方法自己从Buffer读取数据。

以下是如何将Buffer中的数据读入Channel的示例:

// 从Buffer读入Channel
int bytesWritten = inChannel.write(buf);

以下是使用get( )方法从Buffer读取数据的示例:

byte aByte = buf.get();

get( )方法还有许多其他版本,允许你以多种不同方式从Buffer中读取数据。例如,读取特定位置,或从Buffer读取字节数组。有关更多详细信息,请参阅JavaDoc以获取具体的Buffer实现。

rewind( )
Buffer.rewind( )方法将position设置回0,这样你就可以重新读取Buffer中的所有数据了。limit保持不变,因此仍然标记可以从缓冲区读取多少元素(字节,字符等)。

clear( )和compact( )
完成从Buffer中读取数据后,必须使Buffer准备好再次写入。你可以通过调用clear( )或compact( )来实现。

如果在调用clear( )方法,position设置回0和limit设置为capacity。换句话说,Buffer被清除了。Buffer中的数据未清除。只有标记告诉你可以将数据写入Buffer的位置。

如果在调用clear( )方法时缓冲区中有未读数据,该数据将被“遗忘”,这意味着你不再有任何标记来告知已读取的数据以及未读取的数据。

如果Buffer中仍有未读数据,并且你想稍后读取它,但你需要先进行一些写入,请调用compact( )方法而不是clear( )方法。

compact( ) 方法将所有未读数据复制到Buffer的开头。然后它将position设置在最后一个未读元素之后。limit属性仍然设置为capacity,就像clear( )那样。现在Buffer已准备好写入,但是不会覆盖未读数据。

mark( )和reset( )
你可以通过调用Buffer.mark( )方法获取Buffer中position的位置。然后,你可以通过调用Buffer.reset( )方法将position重置回标记位置。这是一个例子:

buffer.mark();

// 多次调用buffer.get(),例如在解析期间。

buffer.reset(); //将位置设置回标记。

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

equals( )
如果,两个Buffer相等:

  • 它们的类型相同(byte,char,int等)
  • 它们在Buffer中具有相同数量的剩余字节,字符等。
  • 所有剩余的字节,字符等都相等。

如你所见,equals仅比较Buffer的一部分,而不是它内部的每个元素。实际上,它只是比较Buffer中的剩余元素。

compareTo( )
compareTo( )方法比较两个Buffer的剩余元素(字节,字符等),用于例如排序例程。在以下情况下,Buffer被视为“小于”另一个Buffer:

  • 与另一个Buffer中的对应元素相等的第一个元素小于另一个Buffer中的元素。
  • 所有元素都相等,但第一个Buffer在第二个Buffer之前耗尽了元素(它有更少的元素)。

5.Java NIO Scatter / Gather

Java NIO具有内置的scatter(分散)/gather(聚集)支持。Scatter / gather是用于读取和写入通道的概念。

从通道读取的scatter是将数据读入多个缓冲区的读取操作。因此,Channel将来自Channel的数据“分散”到多个Buffer中。

Channel的收集写入是将来自多个Buffer的数据写入单个Channel的写入操作。因此,Channel将来自多个Buffer的数据“收集”到一个Channel中。

在需要单独处理传输数据的各个部分的情况下,scatter/gather非常有用。例如,如果消息由标题和正文组成,则可以将标题和正文保留在单独的缓冲区中。这样做可以使你更容易分别使用标题和正文。

Scattering Reads
“散射读取”将数据从单个通道读入多个Buffer。以下是该原则的说明:

以下是Scatter原理的说明:
图5

下面是一个代码示例,演示如何执行scatter读取:

ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body   = ByteBuffer.allocate(1024);

ByteBuffer[] bufferArray = { header, body };

channel.read(bufferArray);

注意Buffer如何首先插入到数组中,然后将数组作为参数传递给channel.read( )方法。然后read( )方法,按照Buffer在数组中出现的顺序从Channel写入数据。一旦Buffer已满,Channel就会继续填充下一个Buffer。

散射读取在进入下一个Buffer之前填充一个缓冲区,意味着它不适合动态大小的消息部分。换句话说,如果你有一个标题和一个正文,并且标题是固定大小(例如128个字节),那么散射读取工作正常。

Gathering Writes
“聚集写入”将来自多个缓冲区的数据写入单个通道。以下是该原理的说明:
图6

这是一个代码示例,演示如何执行收集写入:

ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body   = ByteBuffer.allocate(1024);

ByteBuffer[] bufferArray = { header, body };

channel.write(bufferArray);

Buffer数组传递给write( )方法,该方法按照在数组中遇到的顺序写入Buffer的内容。仅写入缓冲区的位置和限制之间的数据。因此,如果缓冲区的容量为128字节,但只包含58个字节,则只有58个字节从该缓冲区写入通道。因此,与散射读取相比,聚集写入与动态大小的消息部分可以正常工作。

【上篇】Java NIO学习教程(一)==>点击

【下篇】Java NIO学习教程(三)==>点击

发布了127 篇原创文章 · 获赞 28 · 访问量 3万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 技术黑板 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览