您的位置:首页 > Web前端

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

2017-03-17 22:02 567 查看
本文翻译自: 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的实现类

这是 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 bytes
ByteBuffer buf = ByteBuffer.allocate(48);

RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();

//create buffer with capacity of 48 bytes
ByteBuffer 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 的图示说明。下文对这三个属性的讲解也是参考此图。



[b]Capacity[/b]

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

[b]Position[/b]

当向Buffer写入数据时,数据将被存入到Buffer中的内存块上的一个特定位置。在初始情况下,position值为0,即代表内存块的起始位置。当一个字节或long型数组写入到Buffer后,position将指向下一个存储单元以便以后插入数据。position最大能达到capacity - 1。

当从一个Buffer中读数据时,同样需要指定一个读的起始位置。当我们将一个Buffer从写模式翻转成读模式时,即调用flip()方法,position值复位成0。这时如果我们读一个数据,将读出position指向位置的数据,并且position会加1指向下一个存储单元。

<
b822
strong>Limit
[/b]

在写模式时,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()

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java nio 非阻塞IO