NIO Buffer

 

      一个 Buffer 其实就是一块内存区域, 我们可以在这个内存区域中进行数据的读写. NIO Buffer 其实是这样的内存块的一个封装, 并提供了一些操作方法让我们能够方便地进行数据的读写.

 

      缓冲区buffer主要是和通道数据交互,即从通道中读入数据到缓冲区,和从缓冲区中把数据写入到通道中,通过这样完成对数据的传输。

 

    所有缓冲区都有4个属性:capacity、limit、position、mark,并遵循:capacity>=limit>=position>=mark>=0,下表格是对着4个属性的解释:  

属性 描述
Capacity 对于现有的一个内存块,一个Buffer有一个固定的大小,也称他为”capacity“。你可以只是写这个容量的bytes,long,chars等等进入到Buffer。一旦这个buffer满了,你需要清空他(读数据或者清空他)在你写更多的数据进去之前。
Limit

在写模式下,一个buffer的limit是限制有多少数据可以写到buffer。在写模式下,这个limit等于这个buffer的capacity。

当反转这个buffer进入到读模式的时候,limit意味着限制有多少数据可以从这个数据中读取。因此,当反转一个buffer进入到读模式的时候,limit就会设置为写模式下的写的position。换句话说,你可以读取和写一样多的字节(limit被设置成被写字节的数量,这个是被position标记的)

Position

当你写数据进入到Buffer的时候,你可以在一个确定的位置做这个。最初的,这个position的值是0。当一个byte,long型的数据写入到buffer的时候,这个position将会提前指向到这个buffer下一个单元去插入数据。position的最大值为capacity - 1。

当你从一个buffer中读取数据的时候,你也可以从一个给予的position处读取数据。当你反转一个buffer从写模式到读模式的时候,这个position将会重新置为0。就像你从一个buffer中指定的位置读数据是一样的,并且position提前到下一个位置去读取。

Mark 标记,调用mark()来设置mark=position,在调用

一、创建缓冲区

        所有的缓冲区类都不能直接使用new关键字实例化,它们都是抽象类,但是它们都有一个用于创建相应实例的静态工厂方法,以ByteBuffer类为例子:

//创建一个容量为10的byte缓冲区

ByteBuffer buff = ByteBuffer.allocate(10);


上面代码将会从堆空间中分配一个容量大小为10的byte数组作为缓冲区的byte数据存储器。对于其他缓冲区类上面方式也适用,如创建容量为10的CharBuffer:

//创建一个容量为10的char缓冲区
CharBuffer buff = CharBuffer.allocate(10);

        如果想用一个指定大小的数组作为缓冲区的数据的存储器,可以使用wrap()方法:

byte[] bytes = new byte[10];

ByteBuffer buff = ByteBuffer.wrap(bytes);

上面代码中缓冲区的数据会存放在bytes数组中,bytes数组或buff缓冲区任何一方中数据的改动都会影响另一方。还可以创建指定初始位置(position)和上界(limit)的缓冲区:

//使用一个指定数组作为缓冲区的存储器

//并创建一个position=3,limit=8,capacity=10的缓冲区

byte[] bytes = new byte[10];

ByteBuffer buff = ByteBuffer.wrap(bytes, 3, 8);

下图是新创建的一个容量为10的字节缓冲区的内存图:

二、操作缓冲区

1、存取(Buffer.get() & Buffer.put())

   使用get()从缓冲区中取数据,使用put()向缓冲区中存数据。

                // 创建一个容量为10的byte数据缓冲区
		ByteBuffer buff = ByteBuffer.allocate(10);
		// 存入4次数据
		buff.put((byte) 'A');
		buff.put((byte) 'B');
		buff.put((byte) 'C');
		buff.put((byte) 'D');
		// 翻转缓冲区
		buff.flip();
		// 读取2次数据
		System.out.println((char)buff.get());
		System.out.println((char)buff.get());

 上面有提过缓冲区四个属性值一定遵循capacity>=limit>=position>=mark>=0,put()时,若position超过limit则会抛出BufferOverflowException;get()时,若position超过limit则会抛出BufferUnderflowException。

下面代码put()四次后的缓冲区内存示意图:

上面代码执行buff.flip()将缓冲区翻转后的内存示意图:

上面代码执两次get()后的缓冲区内存示意图:

再向Buffer中读写数据时有2个方法也非常有用:

Buffer.remaining():返回从当前位置到上界的数据元素数量;

Buffer.hasRemaining():告诉我们从当前位置到上界是否有数据元素;

2、翻转(Buffer.flip())

翻转就是将一个处于存数据状态的缓冲区变为一个处于准备取数据的状态,使用flip()方式实现翻转。

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

rewind()方法与flip()很相似,区别在于rewind()不会影响limit,而flip()会重设limit属性值:

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

3、压缩(Buffer.compact())

压缩就是将已读取了的数据丢弃,保留未读取的数据并将保留的数据重新填充到缓冲区的顶部,然后继续向缓冲区写入数据。

        // 创建一个容量为10的byte数据缓冲区
        ByteBuffer buff = ByteBuffer.allocate(10);
        // 填充缓冲区
        buff.put((byte)'A');
        buff.put((byte)'B');
        buff.put((byte)'C');
        buff.put((byte)'D');
        System.out.println("first put : " + new String(buff.array()));
        //翻转
        buff.flip();
        //释放
        System.out.println((char)buff.get());
        System.out.println((char)buff.get());
        //压缩
        buff.compact();
        System.out.println("compact after get : " + new String(buff.array()));
        //继续填充
        buff.put((byte)'E');
        buff.put((byte)'F');
        //输出所有
        System.out.println("put after compact : " + new String(buff.array()));
first put : ABCD      
A
B
compact after get : CDCD      
put after compact : CDEF      

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准备好写了,但是你不会覆盖为读取的数据。

4、标记(Buffer.mark())

        标记就是记住当前位置(使用mark()方法标记),之后可以将位置恢复到标记处(使用reset()方法恢复),mark()和reset()

        public final Buffer mark() {
		mark = position;
		return this;
	}
 
	public final Buffer reset() {
		int m = mark;
		if (m < 0)
			throw new InvalidMarkException();
		position = m;
		return this;
	}

5、比较两个缓冲区是否相等

       比较两个缓冲区是否相等有2种方法:equals(Object ob) 和compareTo(ByteBuffer that),这两个方法都是在Buffer的子类中实现的。

     equals比较的两个缓冲区中的每个值,所以允许不同的Buffer对象进行比较;compareTo有类型限制,ByteBuffer只能和ByteBuffer进行比较;比较两个缓冲区实际上是比较两个缓冲区中每个缓冲区position到limit之间(不包括limit)的缓冲值。如下图:

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章