@w1992wishes
2018-08-18T17:24:30.000000Z
字数 5034
阅读 1376
JAVA_NIO
以CharBuffer为例,下面是创建CharBuffer的关键函数:
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( )
}
新的缓冲区是由分配或包装操作创建的。
分配操作创建一个缓冲区对象并分配一个私有的空间来储存容量大小的数据元素。
包装操作创建一个缓冲区对象但是不分配任何空间来储存数据元素,它使用提供的数组作为存储空间来储存缓冲区中的数据元素。
调用allocate方法实际上会返回new HeapCharBuffer(capacity, capacity)对象;
缓存空间存储在CharBuffer类的成员属性char[] hb数组里,即JVM堆里;
如下,分配一个容量为100个char变量的Charbuffer:
CharBuffer charBuffer = CharBuffer.allocate (100);
allocate方法其实比较简单,这段代码隐含地从JVM堆空间中分配了一个char型数组来储存100个char变量。不过有点需要注意的是,allocate方法除了可以分配JVM堆空间,还可以分配直接内存空间(如ByteBuffer,可以调用allocateDirect方法分配直接内存),其内部是通过调用unsafe.allocateMemory方法实现直接内存分配的,该空间不在JVM堆内部,如果是直接内存空间的话,调用hasArray()方法会返回false。
调用wrap方法实际上会也会返回new HeapCharBuffer(array, offset, length)对象;
与allocate方法的区别是,它的缓存存储空间是外部传入的;
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的缓冲区。
通过allocate()或者wrap()函数创建的缓冲区通常都是间接的,即不是分配直接内存空间,而是分配JVM堆空间。间接的缓冲区使用备份数组,可以通过上面列出的API函数获得对这些数组的存取权。
hasArray()判断缓冲区是否有一个可存取的备份数组:
如果返回true,array()函数会返回这个缓冲区对象所使用的数组存储空间的引用。
如果返回false,调用array()函数或者arrayOffset()函数会得到一个UnsupportedOperationException异常。
如果一个缓冲区是只读的,调用array()函数或者arrayOffset()会抛出一个ReadOnlyBufferException异常,来阻止得到存取权来修改只读缓冲区的内容。如果通过其它的方式获得了对备份数组的存取权限,对这个数组的修改也会直接影响到这个只读缓冲区。
arrayOffset(),返回缓冲区数据在数组中存储的开始位置的偏移量(从数组头0开始计算)。如果使用了带有三个参数的版本的wrap()函数来创建一个缓冲区,对于这个缓冲区,arrayOffset()会一直返回0。
public abstract class CharBuffer extends Buffer implements CharSequence, Comparable {
// This is a partial API listing
public static CharBuffer wrap (CharSequence csq)
public static CharBuffer wrap (CharSequence csq, int start, int end)
}
Wrap()函数创建一个只读的备份存储区是CharSequence接口或者其实现的的缓冲区对象。
实现了Charsequence接口的类:String,StringBuffer,和CharBuffer:
CharBuffer charBuffer = CharBuffer.wrap ("Hello World");
缓冲区不限于管理数组中的外部数据,它们也能管理其他缓冲区中的外部数据。当一个管理其他缓冲器所包含的数据元素的缓冲器被创建时,这个缓冲器被称为视图缓冲器。大多数的视图缓冲器都是ByteBuffer。
视图存储器总是通过调用已存在的存储器实例中的函数来创建。使用已存在的存储器实例中的工厂方法意味着视图对象为原始存储器的内部实现细节私有。
复制缓冲区的主要方法:
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( );
}
函数创建了一个与原始缓冲区相似的新缓冲区。两个缓冲区共享数据元 素,拥有同样的容量,但每个缓冲区拥有各自的位置,上界和标记属性。对一个缓冲区内的数据元素所做的改变会反映在另外一个缓冲区上。这一副本缓冲区具有与原始缓冲区同样的数据视图。如果原始的缓冲区为只读,或者为直接缓冲区,新的缓冲区将继承这些属性。
public class DuplicateBuffer {
public static void main(String[] args) {
CharBuffer charbuffer1 = CharBuffer.allocate(10);
CharBuffer charbuffer2 = charbuffer1.duplicate();
charbuffer1.put('a').put('b').put('c');
charbuffer1.flip();
System.out.println(charbuffer1);
System.out.println(charbuffer2);
}
}
结果为:abc abc
asReadOnlyBuffer()函数来生成一个只读的缓冲区视图,这与duplicate()相同,除了这个新的缓冲区不允许使用put(),并且其isReadOnly()函数将会返回true。对这一只读缓冲区的put()函数的调用尝试会导致抛出ReadOnlyBufferException异常。
public class OnlyReadBuffer {
public static void main(String[] args) {
CharBuffer charbuffer1 = CharBuffer.allocate(10);
CharBuffer charbuffer2 = charbuffer1.asReadOnlyBuffer();
charbuffer1.put('a').put('b').put('c');
charbuffer1.flip();
System.out.println(charbuffer1);
System.out.println(charbuffer2);
charbuffer2.put('c');//ReadOnlyBufferException
}
}
分割缓冲区与复制相似,但slice()创建一个从原始缓冲区的当前位置开始的新缓冲区,并且其容量是原始缓冲区的剩余元素数量(limit-position)。这个新缓冲区与原始缓冲区共享一段数据元素子序列。分割出来的缓冲区也会继承只读和直接属性。
CharBuffer buffer = CharBuffer.allocate (8);
buffer.position (3).limit (5);
CharBuffer sliceBuffer = buffer.slice( );
直接缓存区是在虚拟机内存外,开辟的内存,IO操作直接进行,不再对其进行复制,但创建和销毁开销大。
非直接缓存区在虚拟机内存中创建,易回收,但占用虚拟机内存开销,处理中有复制过程。
操作系统在内存区域中进行I/O操作,这些内存区域,就操作系统方面而言,是相连的字节序列。所以,只有字节缓冲区有资格参与I/O操作。操作系统会直接存取进程——在本例中是JVM进程的内存空间,以传输数据。这也意味着I/O操作的目标内存区域必须是连续的字节序列。在JVM中,字节数组可能不会在内存中连续存储,或者无用存储单元收集可能随时对其进行移动。在Java中,数组是对象,而数据存储在对象中的方式在不同的JVM实现中都各有不同。出于这一原因,引入了直接缓冲区的概念。
直接字节缓冲区通常是I/O操作最好的选择。在设计方面,它们支持JVM可用的最高效I/O机制。非直接字节缓冲区可以被传递给通道,但是这样可能导致性能损耗。通常非直接缓冲不可能成为一个本地I/O操作的目标。如果向一个通道中传递一个非直接ByteBuffer对象用于写入,通道可能会在每次调用中隐含地进行下面的操作:
创建一个临时的直接ByteBuffer对象。
将非直接缓冲区的内容复制到临时缓冲中。
使用临时缓冲区执行低层次I/O操作。
临时缓冲区对象离开作用域,并最终成为被回收的无用数据。
直接缓冲区是I/O的最佳选择,但可能比创建非直接缓冲区要花费更高的成本。直接缓冲区使用的内存是通过调用本地操作系统方面的代码分配的,绕过了标准JVM堆栈。建立和销毁直接缓冲区会明显比具有堆栈的缓冲区更加破费,这取决于主操作系统以及JVM实现。直接缓冲区的内存区域不受无用存储单元收集支配,因为它们位于标准JVM堆栈之外。
直接ByteBuffer是通过调用ByteBuffer.allocateDirect()函数产生的,注意用一个wrap()函数所创建的被包装的缓冲区总是非直接的。
public abstract class ByteBuffer extends Buffer implements Comparable {
// This is a partial API listing
public static ByteBuffer allocate(int capacity)
public static ByteBuffer allocateDirect(int capacity)
public abstract boolean isDirect();
}
所有的缓冲区都提供了一个叫做isDirect()的boolean函数,来测试特定缓冲区是否为直接缓冲区。
java nio