如果将同步 I/O
方式下的数据传输比做数据传输的零星方式(这里的零星是指在数据传输的过程中是以零星的字节方式进行的),那么就可以将非阻塞 I/O
方 式下的数据传输比做数据传输的集装箱方式(在字节和低层数据传输之间,多了一层缓冲区,因此,可以将缓冲区看做是装载字节的集装箱)。大家可以想象,如果 我们要运送比较少的货物,用集装箱好象有点不太合算,而如果要运送上百吨的货物,用集装箱来运送的成本会更低。在数据传输过程中也是一样,如果数据量很小 时,使用同步 I/O
方式会更适合,如果数据量很大时(一般以 G
为单位),使用非阻塞 I/O
方式的效率会更高。因此,从理论上说,数据量越大,使用非阻塞 I/O
方式的单位成本就会越低。产生这种结果的原因和缓冲区的一些特性有着直接的关系。在本节中,将对缓冲区的一些主要特性进行讲解,使读者可以充分理解缓冲区的概念,并能通过缓冲区来提高程序的执行效率。
创建缓冲区
Java
提供了七个基本的缓冲区,分别由七个类来管理,它们都可以在 java.nio
包中找到。这七个类如下所示:
- ByteBuffer
- ShortBuffer
- IntBuffer
- CharBuffer
- FloatBuffer
- DoubleBuffer
- LongBuffer
这七个类中的方法类似,只是它们的返回值或参数和相应的简单类型相对应,如 ByteBuffer
类的 get
方法返回了 byte
类型的数据,而 put
方法需要一个 byte
类型的参数。在 CharBuffer
类中的 get
和 put
方法返回和传递的数据类型就是 char
。这七个类都没有 public
构造方法,因此,它们不能通过 new
来创建相应的对象实例。这些类都可以通过两种方式来创建相应的对象实例。
1.
通过静态方法 allocate
来创建缓冲区。
这七类都有一个静态的 allocate
方法,通过这个方法可以创建有最大容量限制的缓冲区对象。 allocate
的定义如下:
ByteBuffer
类中的
allocate
方法:
public
static
ByteBuffer allocate(
int
capacity)
IntBuffer
类中的
allocate
方法:
public
static
IntBuffer allocate(
int
capacity)
其他五个缓冲区类中的 allocate
方法定义和上面的定义类似,只是返回值的类型是相应的缓冲区类。
allocate
方法有一个参数 capacity
,用来指定缓冲区容量的最大值。 capacity
的不能小于 0
,否则会抛出一个 IllegalArgumentException
异常。使用 allocate
来创建缓冲区,并不是一下子就分配给缓冲区 capacity
大小的空间,而是根据缓冲区中存储数据的情况来动态分配缓冲区的大小(实际上,在低层 Java
采用了数据结构中的堆来管理缓冲区的大小),因此,这个 capacity
可以是一个很大的值,如 1024*1024
( 1M
)。 allocate
的使用方法如下:
ByteBuffer byteBuffer
=
ByteBuffer.allocate(
1024
);
IntBuffer intBuffer = IntBuffer.allocate( 1024 );
IntBuffer intBuffer = IntBuffer.allocate( 1024 );
在使用 allocate
创建缓冲区时应用注意, capacity
的含义随着缓冲区的不同而不同。如创建字节缓冲区时, capacity
指的是字节数。而在创建整型 (int)
缓冲区时, capacity
指的是 int
型值的数目,如果转换成字数, capacity
的值应该乘 4
。如上面代码中的 intBuffer
缓冲区最大可容纳的字节数是 1024*4 = 4096
个。
2.
通过静态方法 wrap
来创建缓冲区。
使用 allocate
方法可以创建一个空的缓冲区。而 wrap
方法可以利用已经存在的数据来创建缓冲区。 wrap
方法可以将数组直接转换成相应类型的缓冲区。 wrap
方法有两种重载形式,它们的定义如下:
ByteBuffer
类中的
wrap
方法:
public
static
ByteBuffer wrap(
byte
[] array)
public static ByteBuffer wrap( byte [] array, int offset, int length)
public static ByteBuffer wrap( byte [] array, int offset, int length)
IntBuffer
类中的
wrap
方法:
public
static
IntBuffer wrap(
byte
[] array)
public static IntBuffer wrap( byte [] array, int offset, int length)
public static IntBuffer wrap( byte [] array, int offset, int length)
其他五个缓冲区类中的 wrap
方法定义和上面的定义类似,只是返回值的类型是相应的缓冲区类。
在 wrap
方法中的 array
参数是要转换的数组(如果是其他的缓冲区类,数组的类型就是相应的简单类型,如 IntBuffer
类中的 wrap
方法的 array
就是 int[]
类型)。 offset
是要转换的子数组的偏移量,也就是子数组在 array
中的开始索引。 length
是要转换的子数组的长度。利用后两个参数可以将 array
数组中的一部分转换成缓冲区对象。它们的使用方法如下:
byte
[] myByte
=
new
byte
[] {
1
,
2
,
3
};
int [] myInt = new int [] { 1 , 2 , 3 , 4 };
ByteBuffer byteBuffer = ByteBuffer.wrap(myByte);
IntBuffer intBuffer = IntBuffer.wrap(myInt, 1 , 2 );
int [] myInt = new int [] { 1 , 2 , 3 , 4 };
ByteBuffer byteBuffer = ByteBuffer.wrap(myByte);
IntBuffer intBuffer = IntBuffer.wrap(myInt, 1 , 2 );
可以通过缓冲区类的 capacity
方法来得到缓冲区的大小。 capacity
方法的定义如下:
public
final
int
capacity()
如果使用 allocate
方法来创建缓冲区, capacity
方法的返回值就是 capacity
参数的值。而使用 wrap
方法来创建缓冲区, capacity
方法的返回值是 array
数组的长度,但要注意,使用 wrap
来转换 array
的字数组时, capacity
的长度仍然是原数组的长度,如上面代码中的 intBuffer
缓冲区的 capacity
值是 4
,而不是 2
。
除了可以将数组转换成缓冲区外,也可以通过缓冲区类的 array
方法将缓冲区转换成相应类型的数组。 IntBuffer
类的 array
方法的定义方法如下(其他缓冲区类的 array
的定义类似):
public
final
int
[] array()
下面的代码演示了如何使用 array
方法将缓冲区转换成相应类型的数组。
int
[] myInt
=
new
int
[] {
1
,
2
,
3
,
4
,
5
,
6
};
IntBuffer intBuffer = IntBuffer.wrap(myInt, 1 , 3 );
for ( int v : intBuffer.array())
System.out.print(v + " " );
IntBuffer intBuffer = IntBuffer.wrap(myInt, 1 , 3 );
for ( int v : intBuffer.array())
System.out.print(v + " " );
在执行上面代码后,我们发现输出的结果是 1 2 3 4 5 6
,而不是 2 3 4
。这说明在将子数组转换成缓冲区的过程中实际上是将整个数组转换成了缓冲区,这就是用 wrap
包装子数组后, capacity
的值仍然是原数组长度的真正原因。在使用 array
方法时应注意,在以下两种缓冲区中不能使用 array
方法:
- 只读的缓冲区
如果使用只读缓冲区的 array
方法,将会抛出一个 ReadOnlyBufferException
异常。
- 使用 allocateDirect方法创建的缓冲区 。
如果调用这种缓冲区中的 array
方法,将会抛出一个 UnsupportedOperationException
异常。
可以通过缓冲区类的 hasArray
方法来判断这个缓冲区是否可以使用 array
方法,如果返回 true
,则说明这个缓冲区可以使用 array
方法,否则,使用 array
方法将会抛出上述的两种异常之一。
注意: 使用array方法返回的数组并不是缓冲区数据的副本。被返回的数组实际上就是缓冲区中的数据,也就是说,array方法只返回了缓冲区数据的引用。当数 组中的数据被修改后,缓冲区中的数据也会被修改,返之也是如此。关于这方面内容将在下一节“读写缓冲区中的数据”中详细讲解。
注意: 使用array方法返回的数组并不是缓冲区数据的副本。被返回的数组实际上就是缓冲区中的数据,也就是说,array方法只返回了缓冲区数据的引用。当数 组中的数据被修改后,缓冲区中的数据也会被修改,返之也是如此。关于这方面内容将在下一节“读写缓冲区中的数据”中详细讲解。
在上述的七个缓冲区类中, ByteBuffer
类和 CharBuffer
类各自还有另外一种方法来创建缓冲区对象。
l ByteBuffer
类
可以通过 ByteBuffer
类的 allocateDirect
方法来创建 ByteBuffer
对象。 allocateDirect
方法的定义如下:
public
static
ByteBuffer allocateDirect(
int
capacity)
使用 allocateDirect
方法可以一次性分配 capacity
大小的连续字节空间。通过 allocateDirect
方法来创建具有连续空间的 ByteBuffer
对象虽然可以在一定程度上提高效率,但这种方式并不是平台独立的。也就是说,在一些操作系统平台上使用 allocateDirect
方法来创建 ByteBuffer
对象会使效率大幅度提高,而在另一些操作系统平台上,性能会表现得非常差。而且 allocateDirect
方法需要较长的时间来分配内存空间,在释放空间时也较慢。因此,在使用 allocateDirect
方法时应谨慎。
通过 isDirect
方法可以判断缓冲区对象(其他的缓冲区类也有 isDirect
方法,因为, ByteBuffer
对象可以转换成其他的缓冲区对象,这部分内容将在后面讲解)是用哪种方式创建的,如果 isDirect
方法返回 true
,则这个缓冲区对象是用 allocateDirect
方法创建的,否则,就是用其他方法创建的缓冲区对象。
l CharBuffer
类
我们可以发现,上述的七种缓冲区中并没有字符串缓冲区,而字符串在程序中却是最常用的一种数据类型。不过不要担心,虽然 java.nio
包中并未提供字符串缓冲区,但却可以将字符串转换成字符缓冲区(就是 CharBuffer
对象)。在 CharBuffer
类中的 wrap
方法除了上述的两种重载形式外,又多了两种重载形式,它们的定义如下:
public
static
CharBuffer wrap(CharSequence csq)
public static CharBuffer wrap(CharSequence csq, int start, int end)
public static CharBuffer wrap(CharSequence csq, int start, int end)
其中 csq
参数表示要转换的字符串,但我们注意到 csq
的类型并不是 String
,而是 CharSequence
。 CharSequence
类 Java
中四个可以表示字符串的类的父类,这四个类是 String
、 StringBuffer
、 StringBuilder
和 CharBuffer
(大家要注意, StringBuffer
和本节讲的缓冲区类一点关系都没有,这个类在 java.lang
包中)。也就是说, CharBuffer
类的 wrap
方法可以将这四个类的对象转换成 CharBuffer
对象。
另外两个参数 start
和 end
分别是子字符串的开始索引和结束索引的下一个位置,如将字符串 "1234"
中的 "23"
转换成 CharBuffer
对象的语句如下:
CharBuffer cb
=
CharBuffer.wrap(
"
1234
"
,
1
,
3
);
下面的代码演示了如何使用 wrap
方法将不同形式的字符串转换成 CharBuffer
对象。
StringBuffer stringBuffer
=
new
StringBuffer(
"
通过StringBuffer创建CharBuffer对象
"
);
StringBuilder stringBuilder = new StringBuilder( " 通过StringBuilder创建CharBuffer对象 " );
CharBuffer charBuffer1 = CharBuffer.wrap( " 通过String创建CharBuffer对象 " );
CharBuffer charBuffer2 = CharBuffer.wrap(stringBuffer);
CharBuffer charBuffer3 = CharBuffer.wrap(stringBuilder);
CharBuffer charBuffer4 = CharBuffer.wrap(charBuffer1, 1 , 3 );
StringBuilder stringBuilder = new StringBuilder( " 通过StringBuilder创建CharBuffer对象 " );
CharBuffer charBuffer1 = CharBuffer.wrap( " 通过String创建CharBuffer对象 " );
CharBuffer charBuffer2 = CharBuffer.wrap(stringBuffer);
CharBuffer charBuffer3 = CharBuffer.wrap(stringBuilder);
CharBuffer charBuffer4 = CharBuffer.wrap(charBuffer1, 1 , 3 );
本文出自 “软件改变整个宇宙 ” 博客,请务必保留此出处http://androidguy.blog.51cto.com/974126/214328