Java-NIO-缓冲区基础

NIO中,有自己设计的一套缓冲区系统。
使用I/O,离不开缓冲区,高效的缓冲区,往往起到事半功倍的效果。
下面就看看NIO中的缓冲区是如何设计的。
首先,我们是针对概念来说明缓冲区的属性。缓冲区的四个属性:
1、[b]容量[/b](Capacity)
这是一个缓冲区被创建后的固定属性,不可改变。代表该缓冲区的最大容量。
2、[b]上界[/b](Limit)
这个表明当前缓冲区中的元素计数。
3、[b]位置[/b](Position)
这是下一个要存取的元素索引(下标)。使用get,put方法
4、标记(Mark)
一个用来备忘的设置。例如调用mark()则使得mark = position。在调用reset()时,position = mark。

下面是这四个属性的大小比较
0< =mark <= position <= limit <= capacity

那么这几个属性扮演什么样的角色呢?
我们通过函数来一一说明。有了缓冲区,我们还得有各种各样的针对缓冲区的操作。

package java.nio;
public abstract class Buffer {
public final int capacity();
public final int position();
public final Buffer position(int newPositio);
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();
}

这里注意,上面的代码只是为了说明Buffer的操作。实际中可能很多方法有实现。
注意这些API的设计中,例如, mark()返回的是一个Buffer,clear()返回的也是一个Buffer,这样设计,是为了级联操纵。例如

buffer.mark().clear().position(0);

当然如果太长了,也是不好看的。
这里还有一个方法需要注意,isReadOnly()这说明,并非所有的缓冲区都是可以写的。所以如果这个方法返回true,那么,这个缓冲区就不能写了。

接下来开始说具体方法:
1、存取。
存取操作是通过get和put方法实现的。

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);
}

这里需要注意的是,如果存取中的index参数超出限制,那么,是会抛出IndexOutOfBoundsException。如果是put超出,那么将抛出BufferOverflowException异常。

2、填充。
填充主要使用put方法。这里的put要注意的是,如果是ByteBuffer不能这样使用buffer.put('H')
因为我们put的并不是char类型,而必须是byte,所以要强制转换 buffer.put((byte)'H');
还有,put可以使用索引下标。例如:buffer.put(0,(byte)'H')。如果不适用下标索引,那么put到的都是放在position位置。

3、翻转:
在缓冲区填充满以后,我们需要通知使用者,这要如何做呢?
实际上,limit的引入,就是解决这个问题的,使用者将从position开始,到limit的元素作为读取元素。我们可以这样做。buffer.limit(buffer.position()).position(0);
当然,这里还有一个函数可以更方便 buffer.flip();
这里还有一个函数和flip类似,rewind,这个函数不影响limit,只是把position重置为0,意思就是重读。如果连续两次flip,那么我们得到的就是一个position和limit都为0的缓冲区,对这样的缓冲区使用get,是会抛出BufferUnderflowException异常。

4、释放
缓冲区的基本操作都有了。我们可以循环读取缓冲区的数据,但是如何知道到了上界呢?
函数 hasRemaining() 和 remaining()就是这样的函数。我们可以这样使用:

int i = 0;
while(buffer.hasRemaining()){
array[i++] = buffer.get();
...
}
or
int count = buffer.remaining();
for(int i = 0 ; i < count; i++){
array[i] = buffer.get();
}

当然这样的效率都不会很高。
注意:缓冲区并不保证并发。
当我们使用完缓冲区,就可以清空了。使用 clear函数。
clear函数并不改变元素。只是将 limit = capacity; position=0而已。

5、压缩
compact函数。
这个压缩很就是把已经读过的数据抛弃,使用后面的数据覆盖(移动至索引0),并且把limit设置为capacity。
压缩类似于先进先出的FIFO队列。压缩完成后,注意position移动到了未处理数据之后,等待继续填充。position也就是移动的元素的个数。
这里比较不好理解。如果画个图,可能就容易理解了。
压缩前的缓冲区

[img]http://dl.iteye.com/upload/attachment/0066/6150/0a21925f-f879-3890-8b58-dcdca78f6b41.jpg[/img]

调用 buffer.compact();

[img]http://dl.iteye.com/upload/attachment/0066/6154/15ec3654-4143-3f43-a82b-9ba66ee2c383.jpg[/img]

这里需要分清楚的就是,之前我们是在读取数据,当读了部分数据以后,调用压缩函数,此时,就变成了填充数据,如果要继续读,那么需要调用flip函数。

6、标记
mark函数做标记,reset函数恢复标记。如果没有调用mark,也就是说mark是undefined,那么会抛出InvalidMarkException异常。
注意一些函数调用会清除标志,例如rewind,clear,flip
看到这里,觉得还是蛮复杂。相互之间的耦合度比较高。所以最好自己写代码弄清楚。

7、比较
这里缓冲区的比较,实际上是比较当前位置,到上界的元素是否相同。
注意除了equals还有compareTo函数。compareTo函数必须是两个相同的缓冲区,否则会抛出异常ClassCastException,而equals只是返回false。

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);
}


这里提供的函数get,就可以获取我们所需的缓冲区中的内容。
buffer.get(myArray)
等价于
buffer.get(myArray,0,myArray.length)

但是,需要注意的是,如果数组元素太大,而我们又没有指定大小,意味着缓冲区要填充满数组,但是缓冲区太小,这样,就会抛出BufferUnderflowException异常。为了不抛出这样的异常,我们可以这样做:


char [] bigArray = new char[1000];
int length = buffer.remaining();
buffer.get(bigArray,0,length);
...


如果说我们的数组很小,而缓冲区很大,那么我们可以这么做:

char[] smallArray = new char[10];
while(buffer.hasRemaining()){
int length = Math.min(buffer.remaining(),smallArray.length);
buffer.get(smallArray,0,length);
...
}


对于put函数,也是类似。
buffer.put(myArray) 等价于 buffer.put(myArray,0,myArray.length)
当然,如果我们往缓冲区put大于容量的数组,也会抛出BufferOverflowException异常来的。所以,我们可以类似的这样做:

while(srcBuffer.hasRemaining()){
dstBuffer.put(srcBuffer.get());
}


对于put来说,我们还可以存入string,虽然string和char不同,但是为了方便
我们可以
buffer.put(myString) 等价于 buffer.put(myString,0,myString.length)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值