java NIO —— 缓冲区

来源:互联网 发布:matlab 关联矩阵 编辑:程序博客网 时间:2024/06/08 19:24
一、缓冲区基础 
概念上,缓冲区就像一个基本数据元素数组。
1、 属性 
所有的缓冲区都具有四个属性来提供关于其所包含的数据元素的信息。它们是: 
2、容量(Capacity) 
缓冲区能够容纳的数据元素的最大数量。这一容量在缓冲区创建时被设定,并且永远不能被改变。 
3、上界(Limit) 
缓冲区的第一个不能被读或写的元素。或者说,缓冲区中现存元素的计数。 
4、位置(Position) 
下一个要被读或写的元素的索引。位置会自动由相应的get( )和put( )函数更新,每调用一次position会指向后一个元素。 
5、标记(Mark) 
一个备忘位置。调用 mark(  )来设定 mark = postion。调用 reset(  )设定 position = mark。标记在设定前是未定义的(undefined)。 
这四个属性之间总是遵循以下关系: 
0 <= mark <= position <= limit <= capacity 

假设一个新创建的容量为 10的 ByteBuffer 逻辑视图如下:


容量是固定的,其他3个属性可以在使用缓冲区时改变。

二、缓冲区API

1、父类Buffer:

public abstract class Buffer {  
        public final int capacity(  )  
        public final int position(  )  
        public final Buffer position (int newPosition)
        public final int limit(  )  
        public final Buffer limit (int newLimit)  
        public final Buffer mark(  )  
        public final Buffer reset(  )  
        public final Buffer clear(  )  
        public final Buffer flip(  )  
        public final Buffer rewind(  )  
        public final int remaining(  )  
        public final boolean hasRemaining(  )  
        public abstract boolean isReadOnly(  );  
}

2、子类ByteBuffer:

public abstract class ByteBuffer  
        extends Buffer implements Comparable  
{  
        // This is a partial API listing  
  
        public abstract byte get(  );  
        public abstract byte get (int index);  
        public abstract ByteBuffer put (byte b);  
        public abstract ByteBuffer put (int index, byte b);  

当调用get()和put()时,位置指针position会自动前进一位,但如果调用get(int)和put(int)时,位置指针不会自动前进一位。

注意:在对缓冲区操作时,都不会将缓冲区中的数据清空,即使是用clear(),也只是将指针还原到容器初创建状态,你先用buffer.clear(),

然后再调用buffer.get(2),还是能取到值,操作方法都只是改变了position,limit等指针的位置而已。

例子:将hello字符串放入一个字节数组中

ByteBuffer buffer = ByteBuffer.allocate(10);

buffer.put((byte)'H').put((byte)'e').put((byte)'l').put((byte)'l').put((byte)'o'); 

调用5次put()后的缓冲区:

通过绝对方案放置元素:

buffer.put(0,(byte)'M')

buffer.put((byte)'w'); 

新的缓冲区图如下:


3、翻转flip()

我们现在要把上面的缓冲区中的数据取出来,如果现在在缓冲区上执行 get(),那么它将从position位置处取数据,但这数据是未定义数据,取不到我们要的数据。

所以我们必须将位置值重新设为 0,就会从正确位置开始获取,但是它是怎样知道何时到达我们所插入数据末端的呢?这就是上界属性被引入的目的。上界属性指明了缓冲区有效内容的末端。所以我们需要将上界属性设置为当前位置,然后将位置重置为0。我们可以人工用下面的代码实现:  

buffer.limit(buffer.position()).position(0);  

上面就是flip()方法了:

所以调用buffer.flip()后,缓冲区如下所以:


rewind()函数与 flip()相似,但不影响上界属性。它只是将位置值设回 0。您可以使用 rewind()后退,重读已经被翻转的缓冲区中的数据。  
注意:不要将缓冲区翻转两次,它实际上会大小变为 0。翻转一次后再翻转一次,会把上界设为位置的值0,并把位置设为 0。上界和位置都变成 0。

尝试对缓冲区上位置和上界都为 0 的 get()操作会导致 BufferUnderflowException 异常。而 put()则会导致BufferOverflowException 异常。  

4、释放

布尔函数 hasRemaining()会在释放缓冲区时告诉您是否已经达到缓冲区的上界。以下是一种将数据元素从缓冲区释放到一个数组的方法:
for (int i = 0; buffer.hasRemaining(  ), i++) {  
        myByteArray [i] = buffer.get(  );  

作为选择,remaining()函数将告知您从当前位置到上界还剩余的元素数目。您也可以通过下面的循环来释放缓冲区。 
int count = buffer.remaining(  );   
for (int i = 0; i < count, i++) {  
        myByteArray [i] = buffer.get(  );  

5、压缩

有时,您可能只想从缓冲区中释放一部分数据,而不是全部,然后重新填充。为了实现这一点,未读的数据元素需要下移以使第一个元素索引为 0。

这里使用了compact()

假设被部分释放的缓冲区如下:


调用buffer.compact()后,缓冲区如下:


6、标记

缓冲区的标记在 mark(  )函数被调用之前是未定义的,调用时标记被设为当前位置的值。reset(  )函数将位置设为当前的标记值。如果标记值未定义,调用 reset(  )将导致 InvalidMarkException 异常。一些缓冲区函数会抛弃已经设定的标记(rewind(  ),clear(  ),以及 flip(  )总是抛弃标记)。如果新设定的值比当前的标记小,调用limit( )或 position( )带有索引参数的版本会抛弃标记。

buffer.position(2).mark().position(4); 


如果这个缓冲区现在被传递给一个通道,两个字节(“ow”)将会被发送,而位置会前进到 6。如果我们此时调用 reset(  ),位置将会被设为标记。再次将缓冲区传递给通道将导致四个字节(“llow”)被发送。 

7、比较

public abstract class ByteBuffer  
        extends Buffer implements Comparable  
{  
        // This is a partial API listing  
  
        public boolean equals (Object ob)  
        public int compareTo (Object ob)  

两个缓冲区被认为相等的充要条件是: 

(1)两个对象类型相同。包含不同数据类型的 buffer 永远不会相等,而且 buffer绝不会等于非buffer对象。 
(2)两个对象都剩余同样数量的元素。Buffer 的容量不需要相同,而且缓冲区中剩余数据的索引也不必相同。但每个缓冲区中剩余元素的数目(从位置到上界)必须相同。 
(3)在每个缓冲区中应被Get()函数返回的剩余数据元素序列必须一致。 

相等的缓冲区:


不相等的缓冲区:


8、批量移动

public abstract class CharBuffer  
        extends Buffer implements CharSequence, Comparable  
{  
        // This is a partial API listing  
  
        public CharBuffer get (char [] dst)  
        public CharBuffer get (char [] dst, int offset, int length)  
  
        public final CharBuffer put (char[] src)  
        public CharBuffer put (char [] src, int offset, int length)  
        public CharBuffer put (CharBuffer src)  
  
        public final CharBuffer put (String src)  
        public CharBuffer put (String src, int start, int end)  

public class BufferTest2 {public static void main(String[] args) {CharBuffer buffer = CharBuffer.allocate(100);String str = "zebiao";for(int i = 0; i < str.length(); i++){buffer.put(str.charAt(i));}char[] array2 = new char[]{'a','b','c','d','e'};buffer.put(array2,0,3);buffer.flip();char[] array = new char[20];int length = buffer.remaining();buffer.get(array, 0, length);for(char c : array){System.out.println(c);}}}
输出结果:

z
e
b
i
a
o
a
b
c

三、创建缓冲区

public abstract class CharBuffer  
        extends Buffer implements CharSequence, Comparable  
{  
        // This is a partial API listing  
  
        public static CharBuffer allocate (int capacity)  
  
        public static CharBuffer wrap (char [] array)  
        public static CharBuffer wrap (char [] array, int offset,   
  int length)  
  
        public final boolean hasArray(  )  
        public final char [] array(  )  
        public final int arrayOffset(  )  
}

 
CharBuffer charBuffer = CharBuffer.allocate (100); 

char [] myArray = new char [100]; 
CharBuffer charbuffer = CharBuffer.wrap (myArray); 

这段代码构造了一个新的缓冲区对象,但数据元素会存在于数组中。这意味着通过调用put()函数造成的对缓冲区的改动会直接影响这个数组,而且对这个数组的任何改动也会对这个缓冲区对象可见。

带有 offset 和 length 作为参数的 wrap()函数版本则会构造一个按照您提供的offset和 length 参数值初始化位置和上界的缓冲区。这样做: 
CharBuffer charbuffer = CharBuffer.wrap (myArray, 12, 42); 创建了一个 position 值为 12,limit 值为 54,容量为 myArray.length 的缓冲区。 
这个函数并不像您可能认为的那样,创建了一个只占用了一个数组子集的缓冲区。这个缓冲区可以存取这个数组的全部范围;offset 和 length 参数只是设置了初始的状态。调用使用上面代码中的方法创建的缓冲区中的 clear()函数,然后对其进行填充,直到超过上界值,这将会重写数组中的所有元素。

四、 复制缓冲区 

public abstract class CharBuffer  
        extends Buffer implements CharSequence, Comparable  
{  
        // This is a partial API listing  
  
        public abstract CharBuffer duplicate(  );  
        public abstract CharBuffer asReadOnlyBuffer(  );  
        public abstract CharBuffer slice(  );  

 Duplicate()函数创建了一个与原始缓冲区相似的新缓冲区。两个缓冲区共享数据元素,拥有同样的容量,但每个缓冲区拥有各自的位置,上界和标记属性。对一个缓冲区内的数据元素所做的改变会反映在另外一个缓冲区上。

public class BufferTest3 {public static void main(String[] args) {CharBuffer buffer = CharBuffer.allocate(10);char[] array = new char[]{'h','z','b','i','a','o'};buffer.put(array);buffer.flip();CharBuffer buffer2 = buffer.duplicate();buffer2.position(2);System.out.println(buffer2.get());System.out.println(buffer.get());buffer2.put(2,'x');System.out.println(buffer2.get(2));System.out.println(buffer.get(2));}}
输出结果:

b
h
x
x


分割缓冲区与复制相似,但 slice()创建一个从原始缓冲区的当前位置开始的新缓冲区,并且其容量是原始缓冲区的剩余元素数量(limit-position)。这个新缓冲区与原始缓冲区共享一段数据元素子序列。

要创建一个映射到数组位置 12-20(9 个元素)的 buffer 对象,应使用下面的代码实现: 
 
char [] myBuffer = new char [100];  
CharBuffer cb = CharBuffer.wrap (myBuffer);  
cb.position(12).limit(21);  
CharBuffer sliced = cb.slice(  ); 

public class BufferTest3 {public static void main(String[] args) {char[] array = new char[]{'h','z','b','i','a','o'};CharBuffer buffer = CharBuffer.wrap(array);buffer.position(1).limit(4);CharBuffer buffer2 = buffer.slice();while(buffer2.hasRemaining()){System.out.println(buffer2.get());}System.out.println("========");buffer2.put(2,'x');buffer2.flip();while(buffer2.hasRemaining()){System.out.println(buffer2.get());}System.out.println("========");buffer.position(0).limit(array.length);while(buffer.hasRemaining()){System.out.println(buffer.get());}}}

输出结果:

z

b
i
========
z
b
x
========
h
z
b
x
a
o

0 0
原创粉丝点击