Buffer 类详解.docx

上传人:b****5 文档编号:12087826 上传时间:2023-04-17 格式:DOCX 页数:18 大小:28.15KB
下载 相关 举报
Buffer 类详解.docx_第1页
第1页 / 共18页
Buffer 类详解.docx_第2页
第2页 / 共18页
Buffer 类详解.docx_第3页
第3页 / 共18页
Buffer 类详解.docx_第4页
第4页 / 共18页
Buffer 类详解.docx_第5页
第5页 / 共18页
点击查看更多>>
下载资源
资源描述

Buffer 类详解.docx

《Buffer 类详解.docx》由会员分享,可在线阅读,更多相关《Buffer 类详解.docx(18页珍藏版)》请在冰豆网上搜索。

Buffer 类详解.docx

Buffer类详解

Buffer类详解

Buffer类是java.nio的构造基础。

一个Buffer对象是固定数量的数据的容器,其作用是一个存储器,或者分段运输区,在这里,数据可被存储并在之后用于检索。

缓冲区可以被写满或释放。

对于每个非布尔原始数据类型都有一个缓冲区类,即 Buffer的子类有:

ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer和ShortBuffer,是没有BooleanBuffer之说的。

尽管缓冲区作用于它们存储的原始数据类型,但缓冲区十分倾向于处理字节。

非字节缓冲区可以在后台执行从字节或到字节的转换,这取决于缓冲区是如何创建的。

 

◇缓冲区的四个属性 

       所有的缓冲区都具有四个属性来提供关于其所包含的数据元素的信息,这四个属性尽管简单,但其至关重要,需熟记于心:

∙容量(Capacity):

缓冲区能够容纳的数据元素的最大数量。

这一容量在缓冲区创建时被设定,并且永远不能被改变。

∙上界(Limit):

缓冲区的第一个不能被读或写的元素。

缓冲创建时,limit的值等于capacity的值。

假设capacity=1024,我们在程序中设置了limit=512,说明,Buffer的容量为1024,但是从512之后既不能读也不能写,因此可以理解成,Buffer的实际可用大小为512。

∙位置(Position):

下一个要被读或写的元素的索引。

位置会自动由相应的get()和put()函数更新。

这里需要注意的是positon的位置是从0开始的。

∙标记(Mark):

一个备忘位置。

标记在设定前是未定义的(undefined)。

使用场景是,假设缓冲区中有10个元素,position目前的位置为2(也就是如果get的话是第三个元素),现在只想发送6-10之间的缓冲数据,此时我们可以buffer.mark(buffer.position()),即把当前的position记入mark中,然后buffer.postion(6),此时发送给channel的数据就是6-10的数据。

发送完后,我们可以调用 buffer.reset()使得position=mark,因此这里的mark只是用于临时记录一下位置用的。

请切记,在使用Buffer时,我们实际操作的就是这四个属性的值。

我们发现,Buffer类并没有包括get()或put()函数。

但是,每一个Buffer的子类都有这两个函数,但它们所采用的参数类型,以及它们返回的数据类型,对每个子类来说都是唯一的,所以它们不能在顶层Buffer类中被抽象地声明。

它们的定义必须被特定类型的子类所遵从。

若不加特殊说明,我们在下面讨论的一些内容,都是以ByteBuffer为例,当然,它当然有get()和put()方法了。

 

◇相对存取和绝对存取

Java代码

1.publicabstractclassByteBufferextendsBufferimplementsComparable{ 

2.//ThisisapartialAPIlisting

3.publicabstractbyteget();  

4.publicabstractbyteget(intindex);  

5.publicabstractByteBufferput(byteb);  

6.publicabstractByteBufferput(intindex,byteb); 

7.} 

       来看看上面的代码,有不带索引参数的方法和带索引参数的方法。

不带索引的get和put,这些调用执行完后,position的值会自动前进。

当然,对于put,如果调用多次导致位置超出上界(注意,是limit而不是capacity),则会抛出BufferOverflowException异常;对于get,如果位置不小于上界(同样是limit而不是capacity),则会抛出BufferUnderflowException异常。

这种不带索引参数的方法,称为相对存取,相对存取会自动影响缓冲区的位置属性。

带索引参数的方法,称为绝对存取,绝对存储不会影响缓冲区的位置属性,但如果你提供的索引值超出范围(负数或不小于上界),也将抛出IndexOutOfBoundsException异常。

 

◇翻转 

       我们把hello这个串通过put存入一ByteBuffer中,如下所示:

将hello存入ByteBuffer中

Java代码

1.ByteBufferbuffer=ByteBuffer.allocate(1024); 

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

       此时,position=5,limit=capacity=1024。

现在我们要从正确的位置从buffer读数据,我们可以把position置为0,那么字符串的结束位置在哪呢?

这里上界该出场了。

如果把上界设置成当前position的位置,即5,那么limit就是结束的位置。

上界属性指明了缓冲区有效内容的末端。

人工实现翻转:

Java代码

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

       但这种从填充到释放状态的缓冲区翻转是API设计者预先设计好的,他们为我们提供了一个非常便利的函数:

buffer.flip()。

另外,rewind()函数与flip()相似,但不影响上界属性,它只是将位置值设回0。

在进行buffer读操作的时候,一般都会使用buffer.flip()函数。

  

◇释放(Drain) 

       这里的释放,指的是缓冲区通过put填充数据后,然后被读出的过程。

上面讲了,要读数据,首先得翻转。

那么怎么读呢?

hasRemaining()会在释放缓冲区时告诉你是否已经达到缓冲区的上界:

hasRemaining()函数和Remaining()函数有密切的功能,

Java代码

1.for(inti=0;buffer.hasRemaining();i++){ 

2.   myByteArray[i]=buffer.get(); 

3.} 

       很明显,上面的代码,每次都要判断元素是否到达上界。

我们可以做:

改变后的释放过程

Java代码

1.intcount=buffer.hasRemaining(); 

2.for(inti=0;i

3.   myByteArray[i]=buffer.get(); 

4.} 

       第二段代码看起来很高效,但请注意,缓冲区并不是多线程安全的。

如果你想以多线程同时存取特定的缓冲区,你需要在存取缓冲区之前进行同步。

因此,使用第二段代码的前提是,你对缓冲区有专门的控制。

 

◇buffer.clear() 

       clear()函数将缓冲区重置为空状态。

它并不改变缓冲区中的任何数据元素,而是仅仅将limit设为容量的值,并把position设回0。

 

◇Compact(不知咋翻译,压缩?

紧凑?

) 

       有时候,我们只想释放出一部分数据,即只读取部分数据。

当然,你可以把postion指向你要读取的第一个数据的位置,将limit设置成最后一个元素的位置+1。

但是,一旦缓冲区对象完成填充并释放,它就可以被重新使用了。

所以,缓冲区一旦被读取出来,已经没有使用价值了。

 

       以Mellow为例,填充后为Mellow,但如果我们仅仅想读取llow。

读取完后,缓冲区就可以重新使用了。

Me这两个位置对于我们而言是没用的。

我们可以将llow复制至0-3上,Me则被冲掉。

但是4和5仍然为o和w。

这个事我们当然可以自行通过get和put来完成,但api给我们提供了一个compact()的函数,此函数比我们自己使用get和put要高效的多。

 

 

Compact之前的缓冲区 

       pact()会使缓冲区的状态图如下图所示:

 

 

Compact之后的缓冲区 

       这里发生了几件事:

1.数据元素2-5被复制到0-3位置,位置4和5不受影响,但现在正在或已经超出了当前位置,因此是“死的”。

它们可以被之后的put()调用重写。

2.Position已经被设为被复制的数据元素的数目,也就是说,缓冲区现在被定位在缓冲区中最后一个“存活”元素的后一个位置。

3.上界属性被设置为容量的值,因此缓冲区可以被再次填满。

4.调用compact()的作用是丢弃已经释放的数据,保留未释放的数据,并使缓冲区对重新填充容量准备就绪。

该例子中,你当然可以将Me之前已经读过,即已经被释放过。

◇缓冲区的比较 

       有时候比较两个缓冲区所包含的数据是很有必要的。

所有的缓冲区都提供了一个常规的equals()函数用以测试两个缓冲区的是否相等,以及一个compareTo()函数用以比较缓冲区。

 

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

∙两个对象类型相同。

包含不同数据类型的buffer永远不会相等,而且buffer绝不会等于非buffer对象。

∙两个对象都剩余同样数量(limit-position)的元素。

Buffer的容量不需要相同,而且缓冲区中剩余数据的索引也不必相同。

∙在每个缓冲区中应被get()函数返回的剩余数据元素序列([position,limit-1]位置对应的元素序列)必须一致。

 

两个被认为是相等的缓冲区 

 

两个被认为是不相等的缓冲区 

       缓冲区也支持用compareTo()函数以词典顺序进行比较,当然,这是所有的缓冲区实现了java.lang.Comparable语义化的接口。

这也意味着缓冲区数组可以通过调用java.util.Arrays.sort()函数按照它们的内容进行排序。

 

       与equals()相似,compareTo()不允许不同对象间进行比较。

但compareTo()更为严格:

如果你传递一个类型错误的对象,它会抛出ClassCastException异常,但equals()只会返回false。

 

       比较是针对每个缓冲区你剩余数据(从position到limit)进行的,与它们在equals()中的方式相同,直到不相等的元素被发现或者到达缓冲区的上界。

如果一个缓冲区在不相等元素发现前已经被耗尽,较短的缓冲区被认为是小于较长的缓冲区。

这里有个顺序问题:

下面小于零的结果(表达式的值为true)的含义是buffer2

切记,这代表的并不是buffer1

Java代码 

1.if(pareTo(buffer2)<0){ 

2.//dosth,itmeansbuffer2

3.   doSth(); 

4.} 

◇批量移动 

       缓冲区的设计目的就是为了能够高效传输数据,一次移动一个数据元素并不高效。

如你在下面的程序清单中所看到的那样,bufferAPI提供了向缓冲区你外批量移动数据元素的函数:

Java代码 

1.publicabstractclassByteBufferextendsBufferimplementsComparable{ 

2.publicByteBufferget(byte[]dst); 

3.publicByteBufferget(byte[]dst,intoffset,intlength); 

4.publicfinalByteBufferput(byte[]src);  

5.publicByteBufferput(byte[]src,intoffset,intlength); 

6.} 

       如你在上面的程序清单中所看到的那样,bufferAPI提供了向缓冲区内外批量移动数据元素的函数。

以get为例,它将缓冲区中的内容复制到指定的数组中,当然是从position开始咯。

第二种形式使用offset和length参数来指定复制到目标数组的子区间。

这些批量移动的合成效果与前文所讨论的循环是相同的,但是这些方法可能高效得多,因为这种缓冲区实现能够利用本地代码或其他的优化来移动数据。

 

       批量移动总是具有指定的长度。

也就是说,你总是要求移动固定数量的数据元素。

因此,get(dist)和get(dist,0,dist.length)是等价的。

 

       对于以下几种情况的数据复制会发生异常:

∙如果你所要求的数量的数据不能被传送,那么不会有数据被传递,缓冲区的状态保持不变,同时抛出BufferUnderflowException异常。

∙如果缓冲区中的数据不够完全填满数组,你会得到一个异常。

这意味着如果你想将一个小型缓冲区传入一个大型数组,你需要明确地指定缓冲区中剩余的数据长度。

          如果缓冲区存有比数组能容纳的数量更多的数据,你可以重复利用如下代码进行读取:

Java代码 

1.byte[]smallArray=newByte[10]; 

2.

3.while(buffer.hasRemaining()){ 

4.intlength=Math.min(buffer.remaining(),smallArray.length); 

5.   buffer.get(smallArray,0,length); 

6.//每取出一部分数据后,即调用processData方法,length表示实际上取到了多少字节的数据

7.   processData(smallArray,length); 

8.} 

       put()的批量版本工作方式相似,只不过它是将数组里的元素写入buffer中而已,这里不再赘述。

 

◇创建缓冲区 

       Buffer的七种子类,没有一种能够直接实例化,它们都是抽象类,但是都包含静态工厂方法来创建相应类的新实例。

这部分讨论中,将以 CharBuffer类为例,对于其它六种主要的缓冲区类也是适用的。

下面是创建一个缓冲区的关键函数,对所有的缓冲区类通用(要按照需要替换类名):

Java代码 

1.publicabstractclassCharBufferextendsBufferimplementsCharSequence,Comparable{ 

2.//ThisisapartialAPIlisting

3.

4.publicstaticCharBufferallocate(intcapacity); 

5.publicstaticCharBufferwrap(char[]array); 

6.publicstaticCharBufferwrap(char[]array,intoffset,intlength); 

7.

8.publicfinalbooleanhasArray(); 

9.publicfinalchar[]array(); 

10.publicfinalintarrayOffset(); 

11.} 

       新的缓冲区是由分配(allocate)或包装(wrap)操作创建的。

分配(allocate)操作创建一个缓冲区对象并分配一个私有的空间来储存容量大小的数据元素。

包装(wrap)操作创建一个缓冲区对象但是不分配任何空间来储存数据元素。

它使用你所提供的数组作为存储空间来储存缓冲区中的数据元素。

demos:

Java代码 

1.//这段代码隐含地从堆空间中分配了一个char型数组作为备份存储器来储存100个char变量。

2.CharBuffercharBuffer=CharBuffer.allocate(100); 

3.

4./**

5.*这段代码构造了一个新的缓冲区对象,但数据元素会存在于数组中。

这意味着通过调用put()函数造成的对缓

6.*冲区的改动会直接影响这个数组,而且对这个数组的任何改动也会对这个缓冲区对象可见。

7.*/

8.char[]myArray=newchar[100];  

9.CharBuffercharbuffer=CharBuffer.wrap(myArray); 

10.

11./**

12.*带有offset和length作为参数的wrap()函数版本则会构造一个按照你提供的offset和length参

13.*数值初始化position和limit的缓冲区。

14.*

15.*这个函数并不像你可能认为的那样,创建了一个只占用了一个数组子集的缓冲区。

这个缓冲区可以存取这个数组

16.*的全部范围;offset和length参数只是设置了初始的状态。

调用clear()函数,然后对其进行填充,

17.*直到超过limit,这将会重写数组中的所有元素。

18.*

19.*slice()函数可以提供一个只占用备份数组一部分的缓冲区。

20.*

21.*下面的代码创建了一个position值为12,limit值为54,容量为myArray.length的缓冲区。

22.*/

23.CharBuffercharbuffer=CharBuffer.wrap(myArray,12,42); 

       通过allocate()或者wrap()函数创建的缓冲区通常都是间接的。

间接的缓冲区使用备份数组,你可以通过上面列出的api函数获得对这些数组的存取权。

 

       boolean型函数hasArray()告诉你这个缓冲区是否有一个可存取的备份数组。

如果这个函数的返回true,array()函数会返回这个缓冲区对象所使用的数组存储空间的引用。

如果hasArray()函数返回false,不要调用array()函数或者arrayOffset()函数。

如果你这样做了你会得到一个UnsupportedOperationException异常。

 

       如果一个缓冲区是只读的,它的备份数组将会是超出limit的,即使一个数组对象被提供给wrap()函数。

调用array()函数或arrayOffset()会抛出一个ReadOnlyBufferException异常以阻止你得到存取权来修改只读缓冲区的内容。

如果你通过其它的方式获得了对备份数组的存取权限,对这个数组的修改也会直接影响到这个只读缓冲区。

 

       arrayOffset(),返回缓冲区数据在数组中存储的开始位置的偏移量(从数组头0开始计算)。

如果你使用了带有三个参数的版本的wrap()函数来创建一个缓冲区,对于这个缓冲区,arrayOffset()会一直返回0。

不理解吗?

offset和length只是指示了当前的position和limit,是一个瞬间值,可以通过clear()来从0重新存数据,所以arrayOffset()返回的是0。

当然,如果你切分(slice()函数)了由一个数组提供存储的缓冲区,得到的缓冲区可能会有一个非0的数组偏移量。

 

◇复制缓冲区 

       缓冲区不限于管理数组中的外部数据,它们也能管理其他缓冲区中的外部数据。

当一个管理其他缓冲器所包含的数据元素的缓冲器被创建时,这个缓冲器被称为视图缓冲器。

 

       视图存储器总是通过调用已存在的存储器实例中的函数来创建。

使用已存在的存储器实例中的工厂方法意味着视图对象为原始存储器的你部实现细节私有。

数据元素可以直接存取,无论它们是存储在数组中还是以一些其他的方式,而不需经过原始缓冲区对象的get()/put()API。

如果原始缓冲区是直接缓冲区,该缓冲区(视图缓冲区)的视图会具有同样的效率优势。

 

       继续以CharBuffer为例,但同样的操作可被用于任何基本的缓冲区类型。

用于复制缓冲区的api:

Java代码 

1.publicabstractclassCharBufferextendsBufferimplementsCharSequence,Comparable{ 

2.//ThisisapartialAPIlisting

3.

4.publicabstractCharBufferduplicate(); 

5.publicabstractCharBufferasReadOnlyBuffer(); 

6.publicabstractCharBufferslice(); 

7.} 

●duplidate() 

       复制一个缓冲区会创建一个新的Buffer对象,但并不复制数据。

原始缓冲区和副本都会操作同样的数据元素。

 

       duplicate()函数创建了一个与原始缓冲区相似的新缓冲区。

两个缓冲区共享数据元素,拥有同样的容量,但每个缓冲区拥有各自的position、limit和mark属性。

对一个缓冲区你的数据元素所做的改变会反映在另外一个缓冲区上。

这一副本缓冲区具有与原始缓冲区同样的数据视图。

如果原始的缓冲区为只读,或者为直接缓冲区,新的缓冲区将继承这些属性。

duplicate()复制缓冲区:

Java代码 

1.CharBufferbuffer=CharBuffer.allocate(8); 

2.buffer.position(3).limit(6).mark().position(5); 

3.CharBufferdupeBuffer=buffer.duplicate(); 

4.buffer.clear(); 

复制一个缓冲区 

●asReadOnlyBuffer() 

       asReadOnlyBuffer()函数来生成一个只读的缓冲区视图。

这与duplicate()相同,除了这个新的缓冲区不允许使用put(),并且其isReadOnly()函数将会返回true。

 

      

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 工程科技 > 能源化工

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1