Java通信之IO演进(三)- NIO之Buffer

一. Buffer(缓冲区)介绍

Java NIO Buffers用于和NIO Channel交互。 我们从Channel中读取数据到buffers里,从Buffer把数据写入到Channels.

Buffer本质上会占用一块内存区,可以用来写入数据,并在稍后读取出来。这块内存被NIO Buffer包裹起来,对外提供一系列的读写方便开发的接口。

在Java NIO中使用的核心缓冲区如下(覆盖了通过I/O发送的基本数据类型:byte, char、short, int, long, float, double ,long):

  • ByteBuffer

  • CharBuffer

  • ShortBuffer

  • IntBuffer

  • FloatBuffer

  • DoubleBuffer

  • LongBuffer

Java通信之IO演进(三)- NIO之Buffer_第1张图片

利用Buffer读写数据,通常遵循四个步骤:

  1. 先分配大小给buffer

  2. 把数据写入buffer;

  3. 调用flip;

  4. 从Buffer中读取数据;

  5. 调用buffer.clear()或者buffer.compact()。

public static void main(String args[]) throws Exception {  
    //创建一个随机读取文件对象
    RandomAccessFile fin = new RandomAccessFile("E://test.txt","rw");
    //创建文件的操作管道
    FileChannel fc = fin.getChannel();
    //1. 分配一个10个大小缓冲区,说白了就是分配一个10个大小的byte数组
    ByteBuffer buffer = ByteBuffer.allocate(10);  
    output("初始化", buffer);
    //3.先读一下
    fc.read(buffer);  
    output("调用read()", buffer);
    //3.准备操作之前,先锁定操作范围
    buffer.flip();  
    output("调用flip()", buffer);
    //4.判断有没有可读数据, 可用
    while (buffer.remaining() > 0) {  
       fc.write(buffer);  
    }
    output("调用get()", buffer);
    //5.可以理解为解锁
    buffer.compact();  
    output("调用clear()", buffer);
    //最后把管道关闭
    fin.close();  
}  

当写入数据到buffer中时,buffer会记录已经写入的数据大小。当需要读数据时,通过 flip()方法把buffer从写模式调整为读模式;在读模式下,可以读取所有已经写入的数据。

当读取完数据后,需要清空buffer,以满足后续写入操作。清空buffer有两种方式:调用 clear()compact() 方法。clear会清空整个buffer,compact则只清空已读取的数据,未被读取的数据会被移动到buffer的开始位置,写入位置则近跟着未读数据之后。

 

二. Buffer的容量,位置,上限(Buffer Capacity, Position and Limit)

Buffer缓冲区实质上就是一块内存,用于写入数据,也供后续再次读取数据。这块内存被NIO Buffer管理,并提供一系列的方法用于更简单的操作这块内存。

一个Buffer有三个属性是必须掌握的,分别是:

  • capacity容量:  作为一块内存,buffer有一个固定的大小,叫做capacity(容量)。也就是最多只能写入容量值得字节,整形等数据

  • position位置:  类似指针,需要随着读写归零移动。

    1. 当写入数据到Buffer的时候需要从一个确定的位置开始,默认初始化时这个位置position为0,一旦写入了数据比如一个字节,整形数据,那么position的值就会指向数据之后的一个单元,position最大可以到capacity-1.

    2. 当从Buffer读取数据时,也需要从一个确定的位置开始。buffer从写入模式变为读取模式时,position会归零,每次读取后,position向后移动。

  • limit限制:  写满时,等于capacity, 未写满时,等于读取到的最大字节数,可与position相等

position和limit的具体含义取决于当前buffer的模式。capacity在两种模式下都表示容量。

读写模式下position和limit的含义:

Java通信之IO演进(三)- NIO之Buffer_第2张图片

 

三. Buffer的常见方法

方法 介绍
abstract Object array() 返回支持此缓冲区的数组 (可选操作)
abstract int arrayOffset() 返回该缓冲区的缓冲区的第一个元素的在数组中的偏移量 (可选操作)
int capacity() 返回此缓冲区的容量
Buffer clear() 清除此缓存区。将position = 0;limit = capacity;mark = -1;
Buffer flip() flip()方法可以吧Buffer从写模式切换到读模式。调用flip方法会把position归零,并设置limit为之前的position的值。 也就是说,现在position代表的是读取位置,limit标示的是已写入的数据位置。
abstract boolean hasArray() 告诉这个缓冲区是否由可访问的数组支持
boolean hasRemaining() return position < limit,返回是否还有未读内容
abstract boolean isDirect() 判断个缓冲区是否为 direct
abstract boolean isReadOnly() 判断告知这个缓冲区是否是只读的
int limit() 返回此缓冲区的限制
Buffer position(int newPosition) 设置这个缓冲区的位置
int remaining() return limit - position; 返回limit和position之间相对位置差
Buffer rewind() 把position设为0,mark设为-1,不改变limit的值
Buffer mark() 将此缓冲区的标记设置在其位置

四. Buffer的使用方式/方法介绍

分配缓冲区(Allocating a Buffer)

ByteBuffer buffer = ByteBuffer.allocate(10);

写入数据到缓冲区(Writing Data to a Buffer)

写数据到Buffer有两种方法:

  • 从Channel中写数据到Buffer

      fc.read(buffer);

  • 手动写数据到Buffer,调用put方法

      buffer.put("hello".getBytes());

put方法有很多不同版本,对应不同的写数据方法。例如把数据写到特定的位置,或者把一个字节数据写入buffer。

翻转(flip())

flip()方法可以吧Buffer从写模式切换到读模式。调用flip方法会把position归零,并设置limit为之前的position的值。 也就是说,现在position代表的是读取位置,limit标示的是已写入的数据位置。

Java通信之IO演进(三)- NIO之Buffer_第3张图片

从Buffer读取数据(Reading Data from a Buffer)

从Buffer读数据也有两种方式。

  • 从buffer读数据到channel

     fc.wirte(buffer);

  • 从buffer直接读取数据,调用get方法

     buffer.get()

rewind()

Buffer.rewind()方法将position置为0,这样我们可以重复读取buffer中的数据。limit保持不变

clear() and compact()

一旦我们从buffer中读取完数据,需要复用buffer为下次写数据做准备。只需要调用clear()或compact()方法。

如果调用的是clear()方法,position将被设回0,limit被设置成 capacity的值。换句话说,Buffer 被清空了。Buffer中的数据并未清除,只是这些标记告诉我们可以从哪里开始往Buffer里写数据。

如果Buffer还有一些数据没有读取完,调用clear就会导致这部分数据被“遗忘”,因为我们没有标记这部分数据未读。

针对这种情况,如果需要保留未读数据,那么可以使用compact。 因此 compact()clear() 的区别就在于: 对未读数据的处理,是保留这部分数据还是一起清空

mark()与reset()方法

通过调用Buffer.mark()方法,可以标记Buffer中的一个特定position。之后可以通过调用Buffer.reset()方法恢复到这个position。例如:

    1. buffer.mark(); //call buffer.get() a couple of times, e.g. during parsing.

    2. buffer.reset();  //set position back to mark.

equals() and compareTo()

可以用eqauls和compareTo比较两个buffer

equals():

判断两个buffer相对,需满足:

  • 类型相同

  • buffer中剩余字节数相同

  • 所有剩余字节相等

从上面的三个条件可以看出,equals只比较buffer中的部分内容,并不会去比较每一个元素。

compareTo():

compareTo也是比较buffer中的剩余元素,只不过这个方法适用于比较排序的:

四 Buffer常用方法测试

这里以ByteBuffer为例子说明抽象类Buffer的实现类的一些常见方法的使用:

public static void main(String args[]) throws Exception {  
    //创建一个随机读取文件对象
    RandomAccessFile fin = new RandomAccessFile("E://test.txt","rw");
    //创建文件的操作管道
    FileChannel fc = fin.getChannel();
    //分配一个10个大小缓冲区,说白了就是分配一个10个大小的byte数组
    ByteBuffer buffer = ByteBuffer.allocate(10);  
    output("初始化", buffer);
    //先读一下
    fc.read(buffer);  
    output("调用read()", buffer);
    //准备操作之前,先锁定操作范围
    buffer.flip();  
    output("调用flip()", buffer);
    //判断有没有可读数据
    while (buffer.remaining() > 0) {  
        buffer.get();
    }
    output("调用get()", buffer);
    //可以理解为解锁
    buffer.compact();
    output("调用clear()", buffer);
    //最后把管道关闭
    fin.close();  
}  

//把这个缓冲里面实时状态给答应出来
public static void output(String step, ByteBuffer buffer) {
    System.out.println(step + " : "); 
    //容量,数组大小
    System.out.print("capacity: " + buffer.capacity() + ", ");
    //当前操作数据所在的位置,也可以叫做游标
    System.out.print("position: " + buffer.position() + ", ");
    //锁定值,flip,数据操作范围索引只能在position - limit 之间
    System.out.println("limit: " + buffer.limit());
    System.out.println();
}

你可能感兴趣的:(NIO,Buffer,NIO)