Java NIO——2 缓冲区

时间:2023-02-10 10:58:37
一、缓冲区基础
1、缓冲区并不是多线程安全的。
2、属性(容量、上界、位置、标记)
capacity
limit  第一个不能被读或写的元素
position  下一个要被读或写的元素索引
mark   一个备忘位置
3、方法操作
(1)翻转
buffer.flip()     等同于 buffer.limit(buffer.position()).position(0)
(2)、释放
buffer.flip();
for(int i=0; buffer.hasRemaining(); i++)
     myByteArray[i] = buffer.get();
public class BufferFillDrain
{
public static void main(String[] argv) throws Exception
{
CharBuffer buffer = CharBuffer.allocate(100); while (fillBuffer(buffer))
{
buffer.flip();
drainBuffer(buffer);
buffer.clear();
}
} private static void drainBuffer(CharBuffer buffer)
{
while (buffer.hasRemaining())
{
System.out.print(buffer.get());
} System.out.println("");
} private static boolean fillBuffer(CharBuffer buffer)
{
if (index >= strings.length) { return (false); } String string = strings[index++]; for (int i = 0; i < string.length(); i++)
{
buffer.put(string.charAt(i));
} return (true);
} private static int index = 0; private static String[] strings =
{ "A random string value", "The product of an infinite number of monkeys",
"Hey hey we're the Monkees",
"Opening act for the Monkees: Jimi Hendrix",
"'Scuse me while I kiss this fly", // Sorry Jimi ;-)
"Help Me! Help Me!", };
}
输出结果:
A random string value
The product of an infinite number of monkeys
Hey hey we're the Monkees
Opening act for the Monkees: Jimi Hendrix
'Scuse me while I kiss this fly
Help Me!  Help Me!

(2)压缩
释放一部分数据,而不是全部,然后重新填充。调用 compact()的作用是丢弃已经释放的数据,保留未释放的数据,
并使缓冲区对重新填充容量准备就绪。
buffer.compact()
for(int i=0; buffer.hasRemaining(); i++)
     afterBuffer[i] = buffer.get();
这一缓冲区工具在复制数据时要比您使用 get()和 put()函数高效得多。


(3)标记、重置
mark() 标记position为备忘位置
reset()  重置position为备忘位置

(4)比较
两个缓冲区被认为相等的充要条件是:
1)两个对象类型相同。包含不同数据类型的 buffer 永远不会相等,而且 buffer绝不会等于非buffer对象。 
2)两个对象都剩余同样数量的元素。Buffer 的容量不需要相同,而且缓冲区中剩余数据的索引也不必相同。但每个缓冲区中剩余元素的数目(从位置到上界)必须相同。 
在每个缓冲区中应被Get()函数返回的剩余数据元素序列必须一致。 

(5)批量移动
一次移动一个数据元素,并不高效。buffer API 提供了向缓冲区内外批量移动数据元素的函数。

二、创建缓冲区
分配一个容量为100 个char变量的 Charbuffer:  CharBuffer charBuffer = CharBuffer.allocate (100);
数组用做缓冲区的备份存储器,调用wrap()函数: 
char [] myArray = new char [100]; 
CharBuffer charbuffer = CharBuffer.wrap (myArray); 

三、复制缓冲区
当一个管理其他缓冲器所包含的数据元素的缓冲器被创建时,这个缓冲器被称为视图缓冲器。
(1)duplicate()函数创建了一个与原始缓冲区相似的新缓冲区。两个缓冲区共享数据元素,拥有同样的容量,但每个缓冲区拥有各自的位置,上界和标记属性。
(2)asReadOnlyBuffer()函数生成一个只读的缓冲区视图
如果一个只读的缓冲区与一个可写的缓冲区共享数据,或者有包装好的备份数组,那么对这个可写的缓冲区或直接对这个数组的改变将反映在所有关联的缓冲区上,包括只读缓冲区。
(3)slice()创建一个从原始缓冲区的当前位置开始的新缓冲区,并且其容量是原始缓冲区的剩余元素数量(limit-position)新缓冲区与原始缓冲区共享一段数据元素子序列,分割出来的缓冲区也会继承只读和直接属性。

四、字节缓冲区

1、字节顺序(ByteOrder)
每个缓冲区类都具有一个能够通过调用 order()查询的当前字节顺序设定
对于除了 ByteOrder 之外的其他缓冲区类,字节顺序是一个只读属性,并且可能根据缓冲区的建立方式而采用不同的值。除了ByteBuffer,其他通过分配或包装一个数组所创建的缓冲区将从 order()返回与ByteOrder.nativeOrder()相同的数值。ByteBuffer 的字符顺序设定可以随时通过调用以 ByteOrder.BIG_ENDIAN 或
ByteOrder.LITTL_ENDIAN 为参数的order()函数来改变。
视图的字节顺序设定在创建后不能被改变,而且如果原始的字节缓冲区的字节顺序在之后被改变,它也不会受到影响

ByteBuffer 类有所不同:
默认字节顺序总是 ByteBuffer.BIG_ENDIAN,无论系统的固有字节顺序是什么。Java 的默认字节顺序是大端字节顺序,这允许类文件等以及串行化的对象可以在任何 JVM 中工作。

2、直接缓冲区(
ByteBuffer.allocateDirect()
)
(1)通道只接收 ByteBuffer 作为参数
(2)操作系统的在内存区域中进行 I/O 操作。就操作系统方面而言,这些内存区域是相连的字节序列。于是,只有字节缓冲区有资格参与I/O 操作。操作系统会直接存取 JVM 进程的内存空间,以传输数据。这意味着 I/O 操作的目标内存区域必须是连续的字节序列。在 JVM 中,字节数组可能不会在内存中连续存储,或者无用存储单元收集可能随时对其进行移动。在 Java 中,数组是对象,而数据存储在对象中的方式在不同的JVM 实现中都各有不同。
引入了直接缓冲区的概念。直接缓冲区被用于与通道和固有 I/O 例程交互。
(3)直接缓冲区时 I/O 的最佳选择,但可能比创建非直接缓冲区要花费更高的成本。直接缓冲区使用的内存是通过调用本地操作系统方面的代码分配的,绕过了标准 JVM 堆栈。建立和销毁直接缓冲区会明显比具有堆栈的缓冲区更加破费,这取决于主操作系统以及 JVM 实现。直接缓冲区的内存区域不受无用存储单元收集支配,因为它们位于标准JVM堆栈之外。
直接 ByteBuffer 是通过调用具有所需容量的 
ByteBuffer.allocateDirect()函数产生的,
注意用一个 wrap()函数所创建的被包装的缓冲区总是非直接的。


3、视图缓冲区
ByteBuffer 类允许创建视图来将 byte 型缓冲区字节数据映射为其它的原始数据类型。
一个ByteBuffer的CharBuffer视图
Java NIO——2 缓冲区
public class BufferCharView
{
public static void main (String [] argv)
throws Exception
{
ByteBuffer byteBuffer =
ByteBuffer.allocate (7).order (ByteOrder.BIG_ENDIAN);
CharBuffer charBuffer = byteBuffer.asCharBuffer( ); // Load the ByteBuffer with some bytes
byteBuffer.put (0, (byte)0);
byteBuffer.put (1, (byte)'H');
byteBuffer.put (2, (byte)0);
byteBuffer.put (3, (byte)'i');
byteBuffer.put (4, (byte)0);
byteBuffer.put (5, (byte)'!');
byteBuffer.put (6, (byte)0); println (byteBuffer);
println (charBuffer);
} // Print info about a buffer
private static void println (Buffer buffer)
{
System.out.println ("pos=" + buffer.position( )
+ ", limit=" + buffer.limit( )
+ ", capacity=" + buffer.capacity( )
+ ": '" + buffer.toString( ) + "'");
}
}

输出元素:

pos=0, limit=7, capacity=7: 'java.nio.HeapByteBuffer[pos=0 lim=7 cap=7]'
pos=0, limit=3, capacity=3: 'Hi!'


4、数据元素视图
ByteBuffer 类为每一种原始数据类型提供了存取的和转化的方法:
        public abstract char getChar(  );  
        public abstract char getChar (int index);  
        public abstract short getShort(  );  
        public abstract short getShort (int index);  
        public abstract int getInt(  );  
        public abstract int getInt (int index);  
        public abstract long getLong(  );  
        public abstract long getLong (int index);  
        public abstract float getFloat(  );  
        public abstract float getFloat (int index);  
        public abstract double getDouble(  );  
        public abstract double getDouble (int index);  
        public abstract ByteBuffer putChar (char value);  
        public abstract ByteBuffer putChar (int index, char value);  
        public abstract ByteBuffer putShort (short value);  
        public abstract ByteBuffer putShort (int index, short value);  
        public abstract ByteBuffer putInt (int value);  
        public abstract ByteBuffer putInt (int index, int value);  
        public abstract ByteBuffer putLong (long value);  
        public abstract ByteBuffer putLong (int index, long value);  
        public abstract ByteBuffer putFloat (float value);  
        public abstract ByteBuffer putFloat (int index, float value);  
        public abstract ByteBuffer putDouble (double value);  
        public abstract ByteBuffer putDouble (int index, double value);

5、存取无符号数据 
public class Unsigned
{
public static short getUnsignedByte (ByteBuffer bb)
{
return ((short)(bb.get( ) & 0xff));
} public static void putUnsignedByte (ByteBuffer bb, int value)
{
bb.put ((byte)(value & 0xff));
} public static short getUnsignedByte (ByteBuffer bb, int position)
{
return ((short)(bb.get (position) & (short)0xff));
} public static void putUnsignedByte (ByteBuffer bb, int position,
int value)
{
bb.put (position, (byte)(value & 0xff));
} // --------------------------------------------------------------- public static int getUnsignedShort (ByteBuffer bb)
{
return (bb.getShort( ) & 0xffff);
} public static void putUnsignedShort (ByteBuffer bb, int value)
{
bb.putShort ((short)(value & 0xffff));
} public static int getUnsignedShort (ByteBuffer bb, int position)
{
return (bb.getShort (position) & 0xffff);
} public static void putUnsignedShort (ByteBuffer bb, int position,
int value)
{
bb.putShort (position, (short)(value & 0xffff));
} // --------------------------------------------------------------- public static long getUnsignedInt (ByteBuffer bb) {
return ((long)bb.getInt( ) & 0xffffffffL);
} public static void putUnsignedInt (ByteBuffer bb, long value)
{
bb.putInt ((int)(value & 0xffffffffL));
} public static long getUnsignedInt (ByteBuffer bb, int position)
{
return ((long)bb.getInt (position) & 0xffffffffL);
} public static void putUnsignedInt (ByteBuffer bb, int position,
long value)
{
bb.putInt (position, (int)(value & 0xffffffffL));
}
}


6、内存映射缓冲区
映射缓冲区是带有存储在文件,通过内存映射来存取数据元素的字节缓冲区。映射缓冲区通常是直接存取内存的,只能通过 FileChannel 类创建。