java

位置:IT落伍者 >> java >> 浏览文章

Java网络编程从入门到精通(33):非阻塞I/O的缓沖区(Buffer)


发布日期:2023年01月11日
 
Java网络编程从入门到精通(33):非阻塞I/O的缓沖区(Buffer)

如果将同步I/O方式下的数据传输比做数据传输的零星方式(这里的零星是指在数据传输的过程中是以零星的字节方式进行的)那么就可以将非阻塞I/O方式下的数据传输比做数据传输的集装箱方式(在字节和低层数据传输之间多了一层缓沖区因此可以将缓沖区看做是装载字节的集装箱)大家可以想象如果我们要运送比较少的货物用集装箱好象有点不太合算而如果要运送上百吨的货物用集装箱来运送的成本会更低在数据传输过程中也是一样如果数据量很小时使用同步I/O方式会更适合如果数据量很大时(一般以G为单位)使用非阻塞I/O方式的效率会更高因此从理论上说数据量越大使用非阻塞I/O方式的单位成本就会越低产生这种结果的原因和缓沖区的一些特性有着直接的关系在本节中将对缓沖区的一些主要特性进行讲解使读者可以充分理解缓沖区的概念并能通过缓沖区来提高程序的执行效率

创建缓沖区

Java提供了七个基本的缓沖区分别由七个类来管理它们都可以在javanio包中找到这七个类如下所示

ByteBuffer

ShortBuffer

IntBuffer

CharBuffer

FloatBuffer

DoubleBuffer

LongBuffer

这七个类中的方法类似只是它们的返回值或参数和相应的简单类型相对应如ByteBuffer类的get方法返回了byte类型的数据而put方法需要一个byte类型的参数在CharBuffer类中的get和put方法返回和传递的数据类型就是char这七个类都没有public构造方法因此它们不能通过new来创建相应的对象实例这些类都可以通过两种方式来创建相应的对象实例

通过静态方法allocate来创建缓沖区

这七类都有一个静态的allocate方法通过这个方法可以创建有最大容量限制的缓沖区对象allocate的定义如下

ByteBuffer类中的allocate方法

publicstaticByteBufferallocate(intcapacity)

IntBuffer类中的allocate方法

publicstaticIntBufferallocate(intcapacity)

其他五个缓沖区类中的allocate 方法定义和上面的定义类似只是返回值的类型是相应的缓沖区类

allocate方法有一个参数capacity用来指定缓沖区容量的最大值capacity的不能小于否则会抛出一个IllegalArgumentException异常使用allocate来创建缓沖区并不是一下子就分配给缓沖区capacity大小的空间而是根据缓沖区中存储数据的情况来动态分配缓沖区的大小(实际上在低层Java采用了数据结构中的堆来管理缓沖区的大小)因此这个capacity可以是一个很大的值*M)allocate的使用方法如下

ByteBufferbyteBuffer=ByteBufferallocate();

IntBufferintBuffer=IntBufferallocate();

在使用allocate创建缓沖区时应用注意capacity的含义随着缓沖区的不同而不同如创建字节缓沖区时capacity指的是字节数而在创建整型(int)缓沖区时capacity指的是int型值的数目如果转换成字数capacity的值应该乘如上面代码中的intBuffer缓沖区最大可容纳的字节数是* =

通过静态方法wrap来创建缓沖区

使用allocate方法可以创建一个空的缓沖区而wrap方法可以利用已经存在的数据来创建缓沖区wrap方法可以将数组直接转换成相应类型的缓沖区wrap方法有两种重载形式它们的定义如下

ByteBuffer类中的wrap方法

publicstaticByteBufferwrap(byte[]array)

publicstaticByteBufferwrap(byte[]arrayintoffsetintlength)

IntBuffer类中的wrap方法

publicstaticIntBufferwrap(byte[]array)

publicstaticIntBufferwrap(byte[]arrayintoffsetintlength)

其他五个缓沖区类中的wrap 方法定义和上面的定义类似只是返回值的类型是相应的缓沖区类

在wrap方法中的array参数是要转换的数组(如果是其他的缓沖区类数组的类型就是相应的简单类型如IntBuffer类中的wrap方法的array就是int[]类型)offset是要转换的子数组的偏移量也就是子数组在array中的开始索引length是要转换的子数组的长度利用后两个参数可以将array数组中的一部分转换成缓沖区对象它们的使用方法如下

byte[]myByte=newbyte[]{};

int[]myInt=newint[]{};

ByteBufferbyteBuffer=ByteBufferwrap(myByte);

IntBufferintBuffer=IntBufferwrap(myInt);

可以通过缓沖区类的capacity方法来得到缓沖区的大小capacity方法的定义如下

publicfinalintcapacity()

如果使用allocate方法来创建缓沖区capacity方法的返回值就是capacity参数的值而使用wrap方法来创建缓沖区capacity方法的返回值是array数组的长度但要注意使用wrap来转换array的字数组时capacity的长度仍然是原数组的长度如上面代码中的intBuffer缓沖区的capacity值是而不是

除了可以将数组转换成缓沖区外也可以通过缓沖区类的array方法将缓沖区转换成相应类型的数组IntBuffer类的array方法的定义方法如下(其他缓沖区类的array的定义类似)

publicfinalint[]array()

下面的代码演示了如何使用array方法将缓沖区转换成相应类型的数组

int[]myInt=newint[]{};

IntBufferintBuffer=IntBufferwrap(myInt);

for(intv:intBufferarray())

Systemoutprint(v+);

在执行上面代码后我们发现输出的结果是 而不是 这说明在将子数组转换成缓沖区的过程中实际上是将整个数组转换成了缓沖区这就是用wrap包装子数组后capacity的值仍然是原数组长度的真正原因在使用array方法时应注意在以下两种缓沖区中不能使用array方法

只读的缓沖区如果使用只读缓沖区的array方法将会抛出一个ReadOnlyBufferException异常

使用allocateDirect方法创建的缓沖区

如果调用这种缓沖区中的array方法将会抛出一个UnsupportedOperationException异常

可以通过缓沖区类的hasArray方法来判断这个缓沖区是否可以使用array方法如果返回true则说明这个缓沖区可以使用array方法否则使用array方法将会抛出上述的两种异常之一

注意 使用array方法返回的数组并不是缓沖区数据的副本被返回的数组实际上就是缓沖区中的数据也就是说array方法只返回了缓沖区数据的引用当数组中的数据被修改后缓沖区中的数据也会被修改返之也是如此关于这方面内容将在下一节读写缓沖区中的数据中详细讲解

在上述的七个缓沖区类中ByteBuffer类和CharBuffer类各自还有另外一种方法来创建缓沖区对象

ByteBuffer类

可以通过ByteBuffer类的allocateDirect方法来创建ByteBuffer对象allocateDirect方法的定义如下

publicstaticByteBufferallocateDirect(intcapacity)

使用allocateDirect方法可以一次性分配capacity大小的连续字节空间通过allocateDirect方法来创建具有连续空间的ByteBuffer对象虽然可以在一定程度上提高效率但这种方式并不是平台独立的也就是说在一些操作系统平台上使用allocateDirect方法来创建ByteBuffer对象会使效率大幅度提高而在另一些操作系统平台上性能会表现得非常差而且allocateDirect方法需要较长的时间来分配内存空间在释放空间时也较慢因此在使用allocateDirect方法时应谨慎

通过isDirect方法可以判断缓沖区对象(其他的缓沖区类也有isDirect方法因为ByteBuffer对象可以转换成其他的缓沖区对象这部分内容将在后面讲解)是用哪种方式创建的如果isDirect方法返回true则这个缓沖区对象是用allocateDirect方法创建的否则就是用其他方法创建的缓沖区对象

CharBuffer类

我们可以发现上述的七种缓沖区中并没有字符串缓沖区而字符串在程序中却是最常用的一种数据类型不过不要担心虽然javanio包中并未提供字符串缓沖区但却可以将字符串转换成字符缓沖区(就是CharBuffer对象)在CharBuffer类中的wrap方法除了上述的两种重载形式外又多了两种重载形式它们的定义如下

publicstaticCharBufferwrap(CharSequencecsq)

publicstaticCharBufferwrap(CharSequencecsqintstartintend)

其中csq参数表示要转换的字符串但我们注意到csq的类型并不是String而是CharSequenceCharSequence类Java中四个可以表示字符串的类的父类这四个类是StringStringBufferStringBuilder和CharBuffer(大家要注意StringBuffer和本节讲的缓沖区类一点关系都没有这个类在javalang包中)也就是说CharBuffer类的wrap方法可以将这四个类的对象转换成CharBuffer对象

另外两个参数start和end分别是子字符串的开始索引和结束索引的下一个位置如将字符串中的 转换成CharBuffer对象的语句如下

CharBuffercb=CharBufferwrap();

下面的代码演示了如何使用wrap方法将不同形式的字符串转换成CharBuffer对象

StringBufferstringBuffer=newStringBuffer(通过StringBuffer创建CharBuffer对象);

StringBuilderstringBuilder=newStringBuilder(通过StringBuilder创建CharBuffer对象);

CharBuffercharBuffer=CharBufferwrap(通过String创建CharBuffer对象);

CharBuffercharBuffer=CharBufferwrap(stringBuffer);

CharBuffercharBuffer=CharBufferwrap(stringBuilder);

CharBuffercharBuffer=CharBufferwrap(charBuffer);

               

上一篇:Spring整合HIbernate

下一篇:Java线程:并发协作-死锁