Java NIO Channel & Buffer(Java NIO 通道和缓存)

来源:互联网 发布:℃-ute 知乎 编辑:程序博客网 时间:2024/05/20 21:22

本文翻译自: http://tutorials.jenkov.com/java-nio/index.html, 本人第一次开始写博客,第一次翻译,如有问题,欢迎指正~

上一篇JAVA NIO 概述

Java NIO Channel

Java NIO Channel 和 Java IO Stream 非常相似,不过也有一点区别:

  • 我们可以同时向一个 Channel 读出和写入,但是 Stream却只能单方向的读或者写
  • 我们可以异步的读写一个Channel
  • Channel 总是读出数据到一个Buffer,或者从一个Buffer中写入

下面是一个图示说明,和上篇博客的图示一样,这里再次引用:
Channel读出数据到一个Buffer,Buffer中的数据写入Channel

Channel的实现类

这是 Java NIO 中几个比较重要的Channel实现类:

  • FileChannel -> 能够从文件中读出数据或向文件中写入数据
  • DatagramChannel -> 能够使用UDP协议在网络中收发数据
  • SocketChannel -> 能够使用TCP协议在网络中收发数据
  • ServerSocketChannel -> 能够像Web服务器那样,监听到来的TCP连接,并为此TCP连接创建SocketChannel

Channel的简单示例代码

下面是使用FileChannel从Buffer中读入数据的简单示例:

RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");FileChannel inChannel = aFile.getChannel();ByteBuffer buf = ByteBuffer.allocate(48);int bytesRead = inChannel.read(buf);while (bytesRead != -1) {  System.out.println("Read " + bytesRead);  buf.flip();  while(buf.hasRemaining()){      System.out.print((char) buf.get());  }  buf.clear();  bytesRead = inChannel.read(buf);}aFile.close();

请注意调用buf.flip(),这是ByteBuffer的读和写的翻转函数。开始时,我们将数据从Channel读入到Buffer,接着我们将Buffer从写入模式翻转(flip)到读出模式,然后,我们可以将数据从Buffer中读出。本文将在下面详细介绍Java NIO Buffer。

Java NIO Buffer

当要和Java NIO Channel交互时,需要使用 Java NIO Buffer。如上文所讲,数据从Channel中读出存入Buffer,数据从Buffer读出写入Channel。
基本上来说,Buffer就是一块内存。我们可以向其中写入数据,也可以从中读出数据。这个内存块被一个提供了一系列地非常方便地操作内存块方法的NIO Java类封装了起来。

Buffer基本用法

使用一个Buffer去读写数据一般需要这4个过程:

  • 向Buffer中写入数据
  • 调用buffer.flip()读写模式翻转函数
  • 将数据从Buffer中读出
  • 调用buffer.clear()清空缓存或者调用buffer.compact()清除已读数据

当我们向Buffer写入数据时,Buffer会一直记录着写入数据的数量。一旦我们需要从Buffer中读出数据时,首先需要调用flip()函数将Buffer从写入模式翻转到读出模式,然后可以从中读出所以写入到Buffer中的数据(注意:一般flip()只用来将Buffer从写入模式翻转成读模式。当需要将Buffer从读模式翻转成写模式时,调用clear() 或 compact()。其实flip()方法的代码和clear()是一样的)。

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

一旦Buffer中的所有数据,我们需要清理Buffer,为再次写入做准备。有两个函数可以实现清理操作:clear() 和 compact() 。clear()方法会清空整个Buffer内存块。而compact()方法仅清楚我们已经读出过的数据,并将未读的数据移动到内存块的起始位置,以后新写入的数据将放入到紧接在未读数据之后的位置。
下面是一个带有写、翻转、读和清除操作的简单Buffer使用示例:

RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");FileChannel inChannel = aFile.getChannel();//create buffer with capacity of 48 bytesByteBuffer buf = ByteBuffer.allocate(48);RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");FileChannel inChannel = aFile.getChannel();//create buffer with capacity of 48 bytesByteBuffer buf = ByteBuffer.allocate(48);int bytesRead = inChannel.read(buf); //read into buffer.while (bytesRead != -1) {  buf.flip();  //make buffer ready for read  while(buf.hasRemaining()){      System.out.print((char) buf.get()); // read 1 byte at a time  }  buf.clear(); //make buffer ready for writing  bytesRead = inChannel.read(buf);}aFile.close();while (bytesRead != -1) {  buf.flip();  //make buffer ready for read  while(buf.hasRemaining()){      System.out.print((char) buf.get()); // read 1 byte at a time  }  buf.clear(); //make buffer ready for writing  bytesRead = inChannel.read(buf);}aFile.close();

Buffer Capacity, Position and Limit(容量、位置和限制)

正如前面所讲,Buffer是一个内存块。为了完成一些操作,Buffer需要一些变量来记录这个内存块的属性。同时为了更好的理解Buffer是怎样工作的,接下来将讲解着三个属性:

  • capacity 容量
  • position 操作位置
  • limit 操作限制位置

position 和 limit 代表的意义和Buffer是处于读模式或者写模式相关,而不管Buffer处在什么模式下,capacity 的意义不变。
下面是一张写模式和读模式下的capacity 、position 和limit 的图示说明。下文对这三个属性的讲解也是参考此图。

读或写模式下,Buffer的capacity , position , limit 的意义

Capacity

Buffer管理一个内存块有固定的大小,这个大小成为“容量(Capacity)”。我们只能够向Buffer中写入和Capacity相同大小的字节,long型数或字符等。一旦Buffer写满,在想要写入新的数据前,必须清空Buffer,即把数据读出,并调用clear()或compact()方法。

Position

当向Buffer写入数据时,数据将被存入到Buffer中的内存块上的一个特定位置。在初始情况下,position值为0,即代表内存块的起始位置。当一个字节或long型数组写入到Buffer后,position将指向下一个存储单元以便以后插入数据。position最大能达到capacity - 1。
当从一个Buffer中读数据时,同样需要指定一个读的起始位置。当我们将一个Buffer从写模式翻转成读模式时,即调用flip()方法,position值复位成0。这时如果我们读一个数据,将读出position指向位置的数据,并且position会加1指向下一个存储单元。

Limit

在写模式时,limit表示我们能想Buffer中写入多少数据,此时在默认情况下limit 的值和capacity 相等。当然也可以在Buffer处于写模式中时,调用limit(int newLimit)方法,设置能写入的数据小于capacity 的限制。
当Buffer翻转到读模式时,limit 意味着我们能从缓存中读出多少数据。一次Buffer翻转到读模式时,limit 的值设为Buffer处在写模式时的position 的值。也就是说,我们最多只能读出和写入数据数量相同的数据。

常见Buffer的子类(Buffer Types)

Java NIO 有以下一种常见Buffer类型:

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

很容易理解,这些不同的Buffer用来缓存不同的数据类型。例如,我们可以用CharBuffer缓存Char型的数据。其中MappedByteBuffer有点特殊,将在以后的章节中专门对它讲解。

分配一个缓存区(Allocating a Buffer)

想要获取一个Buffer对象,首先要为它分配内存。每个Buffer类都有一个allocate() 静态方法作为产生相应Buffer对象的工厂方法。下面是一个产生容量为48字节的ByteBuffer对象的示例代码:

ByteBuffer buf = ByteBuffer.allocate(48);

下面是一个产生容量为1024个字符的CharBuffer对象的示例代码:

CharBuffer buf = CharBuffer.allocate(1024);

向Buffer中写入数据

我们有两种方式向Buffer中写入数据:

  • 从Channel中读出数据并写向Buffer

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

  • 调用put() 方法,手动向Buffer中写入数据

    buf.put(127);

put()方法有多个重载,方便我们以不同的方式向Buffer中写入数据。例如,在Buffer的特定位置写入数据,或者向Buffer中写入一整个数组的数据等。想要获取更详细的信息可以阅读具体某个类的实现文档(JavaDoc)。

从Buffer中读出数据

像写入数据一样,同样有两种方式从Buffer中读出数据:

  • 从Buffer中读出数据并写入Channel

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

  • 调用get()方法,手动从Buffer中读数据

    byte aByte = buf.get();

get()方法同put()方法一样有多个重载,提供不同的方式从Buffer中读出数据。例如,从Buffer的特定位置读出数据,或者一次从Buffer中读出一组数据。

flip()

flip()方法将Buffer从写入模式切换到读出模式,调用flip()时,position值设定为0,limit 值设为Buffer处在写入模式时的position 的值。
换句话说,position 现在标记着读的起始位置,limit 现在标记了有多少个数据可以读。

rewind()

调用rewind()时,position 复位成0,所以此时我们可以再次读出Buffer中的所有数据。在这个方法中,并没有涉及到 limit值,因此它依然标记着Buffer中还有多少个数据可以读。

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

clear() and compact()

一旦我们完成从Buffer中读出数据,需要让Buffer 翻转到写入模式,为再次写入数据做好准备。此时我们可以调用clear() 或 compact() 完成此功能。
如果调用clear() 方法,position 复位成0, limit 值设定为capacity 的值。可以说此时Buffer被清空了,但是Buffer中的数据并没有被删除。只是我们可以从Buffer管理的内存块的起始位置开始写入数据,覆盖掉之前的数据。
如果Buffer中还有未读取的数据,调用clear()会遗弃这些未读取的数据。这意味着再也没有标记能告诉哪些数据已经读取过,哪些数据还未读取。
如果Buffer中还有未读取的数据,并且我们以后还需要读取它,只是现在有一些新的数据需要写入Buffer,此时应该调用compact(),而非clear()。
compact() 方法将Buffer中未读的数据复制到内存块的起始位置,然后将 position 指向紧靠着未读数据的最后一个元素的后面, limit 依然设为capacity 的值。现在,Buffer便可接着将新的数据写在未读数据的后面。

mark() and reset()

equals() and compareTo()

0 0
原创粉丝点击