Java NIO (一) 缓存区(Buffers)

前言

最近在学习java nio,但是苦于没有找到中文资料,所以下决心计划将资料《Java I-O, NIO and NIO.2》中的NIO和NIO.2两部分翻译成中文,自己水平有限,如有不当之处请看客指出,我会努力修改。附上此文档下载地址:http://download.youkuaiyun.com/download/hongguo_cheng/10051014

NIO基于Buffer,Buffer中的内容被发送到I/O或从I/O接收,通过Channels提供服务。本章介绍NIO的Buffer相关的类。

Buffers 介绍

buffer是存储要发送到I/O服务或从I/O服务(用于执行输入/输出的操作系统组件)的固定数据量的对象。它位于应用程序和将缓冲数据写入服务或从服务读取数据并将其存入缓冲区的channel之间。

Buffers处理以下四个属性:

  • Capacity:buffer可以存储的数据对象的总数。这个capacity是在buffer创建时指定并且后续不允许修改。
  • Limit:不应该被读取或写入的第一个元素的从零开始的索引。 换句话说,它标识buffer中“实时”数据项的数量。
  • Position:可以读取的下一个数据项的从零开始的索引或数据项可被写入的位置。
  • Mark:当调用buffer的reset()方法时,buffer的position会重置到从零开始的索引,mark在初始化时未定义。

这四个属性的关系如下: 0 <= mark <= position <= limit <= capacity

图1-1 说明一个新创建的和面向字节的缓冲区,capacity为7。
buffer的容量为7的示意图

上图说明:面向字节的buffer的逻辑布局包含一个未定义的mark,当前的position,limit和capacity。

图1-1 上面的buffer可以存储最大数量为7的元素。mark初始时未定义,position初始时设置为0,limit初始时设置为与capacity一致(7),capacity指定了这个buffer可以存储的最大数量的字节数。你可以访问从0-6的positions,Position 7位于buffer之外了。

Buffer 类以及其子类

Buffers由派生自抽象类java.nio.Buffer的类实现,下面描述了Buffer的方法

  • Object array() :返回支持buffer的数组
  • int arrayOffset() : 返回buffer内的第一个元素的偏移量
  • int capacity() :返回buffer的capacity
  • Buffer clear() :清除此buffer,将position设为0,limit设为capacity,mark被清除,该方法不会清除buffer中的数据,而是将其命名为clear的确因为它最常用于情况也是如此。
  • Buffer flip() :翻转此buffer,limit设为当前的position,position设为0,当mark被定义,它将被丢弃。
  • boolean hasArray() :当此buffer是由数组支持和不能是只读时返回true,否则返回false。当此方法返回true,可以安全的调用array()和arrayOffset()方法。
  • boolean isDirect() :当此buffer是直接字节buffer(稍后章节会讨论)时返回true,否则返回false。
  • boolean isReadOnly() : 当此buffer是只读时返回true,否则返回false。
  • int limit() : 返回buffer的limit。
  • Buffer limit(int newLimit) : 设置此buffer的limit为newLimit。当position比newLimit大时,position会被设置为newLimit。当mark被定义以及比newLimit大时,mark将被丢弃。当newLimit是负数或者比capacity大时会抛java.lang.IllegalArgumentException异常。否则返回此buffer对象。
  • Buffer mark() :设置buffer的mark到此position上然后返回此buffer对象。
  • int position() :返回buffer的position值。
  • Buffer position(int newPosition):设置buffer的position为newPosition。当mark被定义以及比newPosition大时,mark会被丢弃。当newPosition是负数或者比limit大时会抛java.lang.IllegalArgumentException异常。否则返回此buffer对象。
  • int remaining() : 返回buffer中位置从position到limit的元素个数。
  • Buffer reset() :重置此buffer的position到之前mark的位置。调用此方法既不会改变mark的值也不会丢弃mark的值。当mark未设置时调用此方法会抛java.nio.InvalidMarkException异常。不然此方法返回此buffer对象。
  • Buffer rewind() : 倒回然后返回此buffer,buffer的position会设置为0和mark被丢弃。

以上显示了许多Buffer的方法中返回Buffer引用,以便可以将实例方法调用链接在一起。例如,不是指定的以下三行:

buf.mark();
buf.position(2);
buf.reset();

你可以更加方便的指定下面的一行

buf.mark().position(2).reset();

上面还显示了全部的buffer可以读取但不是所有的buffer可以写入-例如,一个基于内存映射文件的buffer就只能时只读的。你禁止向只读buffer写入;否则会抛ReadOnlyBufferException异常。在尝试写入该Buffer之前不确定此Buffer是否可写时,调用isReadOnly()方法判断是否只读。

警告 Buffers是线程不安全的。你想用多线程访问一个buffer时必须使用同步。

java.nio包中包含几个扩展Buffer的抽象类,除了Boolean之外,每个基本类型都有对应的一个抽象类:ByteBuffer, CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, LongBuffer 和 ShortBuffer。更进一步讲,此包还包含了MappedByteBuffer作为ByteBuffer的一个子类。

注意 操作系统执行面向字节的I/O,使用ByteBuffer去创建面向字节的buffer,用于存储要写入到目的地或从源读取的字节。其他基本数据类型的buffer 类使你创建多字节的view buffers(稍后讨论)所以你可以从概念上讲去执行字符型,双精度浮点型,32位整型等等的I/O操作。然而,I/O的操作是以字节流的形式在操作。

下面的示例说明ByteBuffer类中的capacity,limit,position以及剩余元素。

package com.nio.demo;

import java.nio.Buffer;
import java.nio.ByteBuffer;

public class ByteBufferDemo {
    public static void main(String[] args) {
        Buffer buffer = ByteBuffer.allocate(7);
        System.out.println("Capacity: " + buffer.capacity());
        System.out.println("Limit: " + buffer.limit());
        System.out.println("Position: " + buffer.position());
        System.out.println("Remaining: " + buffer.remaining());
        System.out.println("Changing buffer limit to 5");
        buffer.limit(5);
        System.out.println("Limit: " + buffer.limit());
        System.out.println("Position: " + buffer.position());
        System.out.println("Remaining: " + buffer.remaining());
        System.out.println("Changing buffer position to 3");
        buffer.position(3);
        System.out.println("Position: " + buffer.position());
        System.out.println("Remaining: " + buffer.remaining());
        System.out.println(buffer);
    }
}

上面的代码中的main()方法首先需要获取一个buffer对象,Buffer类是抽象不能实例化,所以使用ByteBuffer类和它的allocate()实例方法去分配包含7个字节的buffer,然后main方法分类的调用Buffer的方法去演示capacity,limit,position和剩余元素。

上述代码执行结果:

Capacity: 7
Limit: 7
Position: 0
Mark:java.nio.HeapByteBuffer[pos=0 lim=7 cap=7]
Remaining: 7
Changing buffer limit to 5
Limit: 5
Position: 0
Remaining: 5
Changing buffer position to 3
Position: 3
Remaining: 2
java.nio.HeapByteBuffer[pos=3 lim=5 cap=7]

最后输出一行显示分配给buffer的ByteBuffer实例实际上是java.nio.HeapByteBuffer类的一个实例。

深入理解Buffers

前面讨论的Buffer类已经给了你一些关于NIO buffers的大体情况。然而,还有更多的要去探索研究。这部分讲解会让你从buffer的创建,buffer的读和写,buffer的翻转,buffer的mark,Buffer子类的操作,字节排序和直接buffers等几个方面更加深入的了解buffers。

注意 虽然基本数据类型的buffer类有相似的能力,但是ByteBuffer是最大和最通用的。毕竟,字节是操作系统传输数据项使用的基本单元。因此我会使用ByteBuffer来演示大部分buffer的操作。我也会使用CharBuffer去增加多样性操作。

Buffer的创建

ByteBuffer和其他基本数据类型buffer类声明了多个类方法去创建一个对应类型的buffer对象。例如,ByteBuffer声明了一下类方法去创建ByteBuffer实例:

  • ByteBuffer allocate(int capacity):分配一个拥有capacity大小的字节buffer对象。此buffer的position是0,limit就是它的capacity,mark未定义和buffer中每个元素初始值为0。它有个支持数组,其数组偏移量为0。当capacity是负数时此方法会抛IllegalArgumentException异常。
  • ByteBuffer allocateDirect(int capacity):分配一个拥有capacity大小的直接字节buffer(后面讨论)。它的position是0,它的limit就是它的capacity,mark未定义和每个元素初始值为0。是否有一个支持数组是未指定的。当capacity是负数时此方法会抛IllegalArgumentException异常。JDK 7之前,通过此方法分配的直接buffers是在页面边界上对齐的。但在JDK7中,这个实现修改了以便直接buffers不在是页面边界对齐的了。这将减少创建大量小buffer的应用程序的内存需求。
  • ByteBuffer wrap(byte[] array):将字节数组包含到缓冲区中。这新的buffer由数组支持。就是buffer的修改会导致数组的修改,反之亦然。新的buffer的capacity和limit是数组的长度,它的position是0,mark未定义。它的数组偏移量为0。
  • ByteBuffer wrap(byte[] array, int offset, int length) :将字节数组包含到缓冲区中。这新的buffer由数组支持。新的buffer的capacity是数组长度,position是offset,limit是offset+length,mark未定义。它的数组偏移量为0。offset是负数或比array.length大或者length是负数或比array.length - offset大时此方法会抛IndexOutOfBoundsException异常。

这些方法展示了创建buffer的两个方式:创建ByteBuffer对象并分配一个存储capacity字节的内部数组或者创建ByteBuffer对象并使用指定数组去存储这些字节。

考虑以下示例:

ByteBuffer buffer = ByteBuffer.allocate(10);
byte[] bytes = new byte[200];
ByteBuffer buffer2 = ByteBufer.wrap(bytes);

第一行创建一个字节Buffer,内部字节数组最多可存储10个字节,第二行和第三行创建一个字节数组和一个使用该数组来存储最多200字节的字节Buffer。

现在考虑下面的示例

buffer = ByteBuffer.wrap(bytes, 10, 50);

这个示例创建了一个字节buffer,这个buffer的position是10,limit是50,capacity是bytes.length(长度是200)。虽然出现了此buffer只可以访问数组的子范围,实际上是可以访问整个数组的:数值10和50只是position和limit的起始值。
通过allocate()方法或wrap()方法创建的ByteBuffers(和其他基本数据类型buffers)是非直接字节buffers(稍后会学习直接字节buffer)。非直接字节buffers有支持数组,只要hasArray()返回true,你就可以通过array()方法访问此支持数组(当hasArray()返回true,你需要调用arrayOffset()方法去获取数组中第一个数据的位置。)

以下示例说明了buffer的分配和数组包含。

package com.nio.demo;

import java.nio.ByteBuffer;

public class CreateByteBufferDemo {
    public static void main(String[] args) {
        ByteBuffer byteBuffer = ByteBuffer.allocate(10);
        if (byteBuffer.hasArray()) {
            System.out.println("byteBuffer array: " + byteBuffer.array());
            System.out.println("byteBuffer array offset: " +
                    byteBuffer.arrayOffset());
            System.out.println("Capacity: " + byteBuffer.capacity());
            System.out.println("Limit: " + byteBuffer.limit());
            System.out.println("Position: " + byteBuffer.position());
            System.out.println("Remaining: " + byteBuffer.remaining());
        }
        System.out.println("-----------------------------------------------------");
        byte[] bytes = new byte[200];
        ByteBuffer buffer2 = ByteBuffer.wrap(bytes, 10, 50);
        if (buffer2.hasArray()) {
            System.out.println("buffer2 array: " + buffer2.array());
            System.out.println("buffer2 array offset: " +
                    buffer2.arrayOffset());
            System.out.println("Capacity: " + buffer2.capacity());
            System.out.println("Limit: " + buffer2.limit());
            System.out.println("Position: " + buffer2.position());
            System.out.println("Remaining: " + buffer2.remaining());
        }
    }
}

执行代码,结果如下

byteBuffer array: [B@46b9979b
byteBuffer array offset: 0
Capacity: 10
Limit: 10
Position: 0
Remaining: 10
-----------------------------------------------------
buffer2 array: [B@42906563
buffer2 array offset: 0
Capacity: 200
Limit: 60
Position: 10
Remaining: 50

除了管理存储在外部数组的元素(通过wrap()方法),buffers可以存储于其他buffers中的数据。当创建管理其他buffers数据的buffer时,已创建的buffer被称为view buffer。任意一个buffer的修改都会影响另一个buffer。

View buffers通过Buffer子类的duplicate()方法来创建。view buffer的结果和原始buffer是相等的;两个buffers共享相同数据项以及有相等的capacities。然而,每个buffer有自己的position,limit和mark。当复制的buffer是只读或直接时,view buffer也是只读或直接的。

考虑下面示例:

ByteBuffer buffer = ByteBuffer.allocate(10);
ByteBuffer viewBuffer = buffer.duplicate();

由viewBuffer定义的ByteBuffer实例与buffer共享相同的内部数组的10个元素。此时,这些buffers拥有相同的position,limit和(未定义)mark。但是,一个buffer中的这些属性可以独立于其他buffer中的属性进行修改。
View buffers也可以通过调用ByteBuffer的asXBuffer()方法来创建。例如,LongBuffer asLongBuffer()返回一个view buffer,它将字节Buffer概念化为长整数buffer。

注意 只读的view buffer可以通过调用ByteBuffer asReadOnlyBuffer()此方法创建。任何尝试去修改只读view buffer内容时会抛ReadOnlyBufferException异常。然而,原始buffer内容(不是只读buffer提供)可以被修改,只读的view buffer会响应这些修改。

Buffer 写和读

ByteBuffer和其他的原始类型buffer类定义了一些重载的put()和get()方法用于往buffer中写入数据项或者从buffer中读取数据项。这些方法当需要索引参数时是绝对的或不需要索引时就是相对的。

例如,ByteBuffer声明了绝对索引值ByteBuffer put(int index, byte b)方法将字节b存储在索引值的buffer中,并使用byte get(int index)方法来获取位于索引处的字节。这个类也声明了相对的方法ByteBuffer put(byte b) 将字节b存储在当前position然后当前position加一,以及相对的byte get()方法去获取位于buffer中的当前position的字节然后当前position加一。

当方法put()和get()传递的index值是负数或者大于等于buffer的limit值时会抛IndexOutOfBoundsException异常。方法put()调用时当前position大于等于limit时抛java.nio.BufferOverflowException异常以及方法get()调用时当前position大于等于limit时抛java.nio.BufferUnderflowException异常。更进一步讲,当buffe是只读时调用put()方法会抛ReadOnlyBufferException异常。

以下清单说明了相对索引值的put()方法和绝对索引值的get()方法

package com.nio;

import java.nio.ByteBuffer;

public class BufferDemo {

    public static void main(String[] args) {
        ByteBuffer buffer = ByteBuffer.allocate(7);
        System.out.println("Capacity = " + buffer.capacity());
        System.out.println("Limit = " + buffer.limit());
        System.out.println("Position = " + buffer.position());
        System.out.println("Remaining = " + buffer.remaining());

        buffer.put((byte) 10).put((byte) 20).put((byte) 30);

        System.out.println("Capacity = " + buffer.capacity());
        System.out.println("Limit = " + buffer.limit());
        System.out.println("Position = " + buffer.position());
        System.out.println("Remaining = " + buffer.remaining());

        for (int i = 0; i < buffer.position(); i++) {
            System.out.println(buffer.get(i));
        }
    }

}

编译此代码清单然后运行这个程序,你应该可以看到以下输出:

Capacity = 7
Limit = 7
Position = 0
Remaining = 7
Capacity = 7
Limit = 7
Position = 3
Remaining = 4
10
20
30

以下图标说明了buffer调用了3次put()方法后的状态

随后的三次绝对get()方法的调用不会修改这个position,这个position还是值3.

提示 为了最大限度的提高效率,你可以使用ByteBuffer put(byte[] src), ByteBuffer put(byte[] src, int offset, int length), ByteBuffer get(byte[] dest)和ByteBuffer get(byte[] dst, int offset, int length) 方法来写入和读取字节数组。

翻转Buffer

当填充完buffer后,你需要为其准备channel来进行输出。当你通过buffer时,channel将访问超过当前position位置后未定义的数据。

为了解决这个问题,你需要重置position之0,但是如何让channel知道已经到达插入数据的终点?解决办法就是和limit属性一起工作,这个limit指明了buffer活动部分的终点。基本上,你设置limit到当前position然后重置当前position为0。

你应该执行以下代码完成此任务,该代码也会清除任何定义的mark标记:

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

然而,还有一个更简单的方式去完成同样的任务,展示如下:

buffer.flip();

不管以上那种方式,此buffer已经准备好输出。

假设 buffer.flip(); 是在以下清单中main()方法的最后执行,下表显示了调用flip()后buffer的状态。

调用buffer.remaining()会返回3,这个值指明可被输出的字节数(10,20和30)。

以下清单提供了另一个buffer-fliping的示例,这个示例使用了字符buffer。

package com.nio;

import java.nio.CharBuffer;

public class CharBufferDemo {
    public static void main(String[] args) {
        String[] poem = {
                "Roses are red",
                "Violets are blue",
                "Sugar is sweet",
                "And so are you."
        };

        CharBuffer charBuffer = CharBuffer.allocate(50);

        for (int i = 0; i < poem.length; i++) {
            // Fill the buffer
            for (int j = 0; j < poem[i].length(); j++) {
                charBuffer.put(poem[i].charAt(j));
            }
            // Flip the buffer so that its contents can be read.
            charBuffer.flip();

            // Drain the buffer
            while (charBuffer.hasRemaining()) {
                System.out.print(charBuffer.get());
            }

            charBuffer.clear();

            System.out.println();
        }
    }
}

编译代码(java CharBufferDemo.java)然后运行这个程序(java CharBufferDemo),你会看到以下输出:

Roses are red
Violets are blue
Sugar is sweet
And so are you.

注意 rewind()和flip()方法很类似,但是其忽略了limit,当然,调用flip()两次不会返回到原始状态。相反,此buffer有一个0的大小,调用put()方法结果抛BufferOverflowException异常,调用get()方法结果抛BufferUnderflowException异常或者(调用get(int)方法这种情形下)抛IndexOutOfBoundsException异常。

Buffers 标记

你可以通过mark()方法来标记一个buffer和通过reset()方法来返回已标记的位置。例如,假如你已经执行了一下代码:

ByteBuffer buffer = ByteBuffer.allocate(7);
buffer.put((byte)10).put((byte)20).put((byte)30).put((byte)40);
buffer.limit(4);

此buffer的当前position是4。
继续,假如你又执行了buffer.position(1).mark().position(3);下表指明了当前buffer的在此点的状态。

如果你把此buffer发送给一个channel,字节40将会被发送(当前position是3因为position(3))和position会被提高到4.如果你顺序执行buffer.reset();然后发送此buffer到一个channel中,此时position将被设置为mark(1)和字节20,30和40(从当前position到limit前一个位置的所有字节)将会被发送到这个channel中(按此顺序)。

以下清单说明了mark/reset情形。

package com.nio;

import java.nio.ByteBuffer;

public class MarkBufferDemo {
    public static void main(String[] args) {
        ByteBuffer buffer = ByteBuffer.allocate(7);
        buffer.put((byte) 10).put((byte) 20).put((byte) 30).put((byte) 40);
        buffer.limit(4);
        buffer.position(1).mark().position(3);
        System.out.println(buffer.get());
        System.out.println();
        buffer.reset();
        while (buffer.hasRemaining()) {
            System.out.println(buffer.get());
        }
    }
}

编译代码(java MarkBufferDemo.java)然后运行这个程序(java MarkBufferDemo),你会看到以下输出:

40

20
30
40

警告 不要混淆reset()和clear()两个方法,clear()方法将buffer标记为空,而reset()方法将buffer的当前position标记为先前设置的标记位,或者在没有先前标记时抛出InvalidMarkException异常。

Buffer的子类操作

ByteBuffer和其他原始类型的buffer类声明了compact()方法,通过此方法将buffer中从当前position到limit的所有字节复制到此buffer的开始位置来压缩buffer。在索引p = position()位置的字节被复制到索引为0的位置,在索引p + 1位置的字节被复制到索引1位置,以此类推直到索引limit() - 1的位置被复制到索引 n = limit() - 1 - p的位置上。此buffer当前position设置为n + 1,它的limit设置为capacity。这些mark,如果已定义,则废弃。

在从buffer写入数据后调用compact()方法来处理不是所有buffer内容被写入的情况。考虑以下例子,通过 buffer buf从一个输入channel复制内容到一个输出channel中:

buf.clear();// 准备buffer
while(in.read(buf) != -1) {
    buf.flip();// 准备buffer写出
    out.write(buf);// 写出buffer
    buf.compact();// 部分写出是这样做
}

compact()方法的调用会将未写入的buffer数据移动到buffer的初始位置以便下次调用read()方法时读取的数据追加到buffer的数据,而不是在未指定compact()是覆盖该数据。

偶尔你可能需要比较buffer是否相等或者排序。除了ByteBuffer’s MappedByteBuffer子类外所有Buffer子类都重写了equals()和compareTo()方法来执行这些比较操作–MappedByteBuffer是从ByteBuffer父类中继承了这些方法。下面的示例向你展示了怎样比较字节buffer byteBuf1和ByteBuf2 的相等和排序:

System.out.println(byteBuf1.equals(byteBuf2));
System.out.println(byteBuf1.compareTo(byteBuf2));

ByteBuffer中的equals()合同规定,当且仅当他们具有相同的元素类型时,两个字节buffer相等;他们具有相同数量的剩余元素;并且独立于他们的起始位置考虑的剩余元素的两个序列是单独相等的。该合同对于其他子类也是相同的。

ByteBuffer中的compareTo()方法表明,通过将字节序列中的剩余元素的序列进行比较来比较两个字节buffer的顺序,而不考虑对应buffer内每个序列的起始位置。比较一下字节元素,就像调用Byte.compare(byte, byte)方法一样。类似的描述同样适用于其他buffer的子类中。

Byte 排序

除了Boolean类型(由一位或一个字节表示)其他非字节基本数据类型是由多字节组成的:一个字符或者一个短整型由2个字节组成,32位整型或浮点型由4个字节组成,长整型或双精度浮点型是由8个字节组成。这些多字节类型中的一个的每个值被存储在连续的存储器位置的序列中。然而不同操作系统之间这些字节的顺序是不同的。

例如,考虑32位长整型0x10203040。这个值得四个字节可能存储在内存上(从低位到高位)是10,20,30,40;这样安排被称为大端序(最重要的字节,大端,存储在最低地址)。或者,这些字节可以存储为40,30,20,10;这样的安排被称为小端序(最不重要的字节,小端,被存储在最低的地址)。

当从多字节buffe中写入/读取时,java提供了java.nio.ByteOrder类来帮助你处理字节排序问题。ByteOrder声明了ByteOrder nativeOrder()方法,它作为ByteOrder实例来返回操作系统的字节顺序。因为这个实例是ByteOrder的BIG_ENDIAN和LITTLE_ENDIAN常量之一,也因为没有其他的ByteOrder实例被创建,你可以通过==或!=操作符来将nativeOrder()的返回值与其中一个常量进行比较。

每一个多字节buffer类(例如FloatBuffer)声明了ByteOrder order()方法来返回这个buffer字节的排序。这个方法返回ByteOrder.BIG_ENDIAN或ByteOrder.LITTLE_ENDIAN。

从order()返回的ByteOrder值可以根据Buffer的创建方式而取得不同的值。如果一个多字节buffer(例如一个浮点型buffer)通过分配或包装一个已存在的数组来创建,这个buffer的字节顺序就是底层操作系统的本地排序。然而,如果一个多字节buffer作为一个字节buffer视图被创建,当这个视图创建时,此buffer视图的字节排序就是这个字节buffer的排序。之后这个视图buffer的字节排序不能被修改。

当涉及到字节排序时ByteBuffer与其他的多字节buffer是不同的。它的默认字节排序总是大端,即使底层操作系统的字节排序是小端。ByteBuffer默认是大端排序时因为Java的默认字节排序也是大端,这就使得类文件和序列化对象存储在JVM中一致的存储数据。

因为这个大端序列的默认值会影响小端操作系统的性能,所以ByteBuffer也声明了一个ByteBuffer命令(ByteOrder bo)来改变字节Buffer的字节顺序。

虽然改变字节Buffer(仅访问单字节数据项)的字节顺序似乎不寻常,但此方法非常有用,因为ByteBuffer还声明了写入和读取多字节值的方便方法(例如,ByteBuffer putInt(int value) 和int getInt())。 这些方便方法根据字节Buffer的当前字节顺序写入这些值。 此外,您可以随后将ByteBuffer的LongBuffer称为LongBuffer()或另一个asxBuffer()方法返回一个视图Buffer,其顺序将反映该字节Buffer改变了字节顺序。

直接字节Buffers

与多字节Buffers不同,字节Buffers可以作为基于channel I/O的数据源/目的地。这不能作为一个惊喜因为操作系统在内存区域中执行I/O操作,这些内存区域是连续的8位字节的序列上(不是浮点值,也不是32位整型等等)。

操作系统可以直接访问进程的地址空间。例如,操作系统可以直接访问JVM进程的地址空间去执行基于字节数组的数据转移操作。然而,JVM可能不会连续存储字节数组或者垃圾收集器可能将字节数组移动到其他位置。由于这些限制就创建了直接字节buffer。

直接字节buffer是一个与channels和本机代码交互去执行I/O操作的字节buffer。直接字节buffer尝试在内存区域中存储在channel中用于通过本地代码执行直接(原始)访问的内存区域中,该本地代码告诉操作系统直接排除或填充内存区域。

直接字节Buffers在JVM上执行I/O操作是最有效的。虽然你也可以传输非直接字节buffer到channels,可能出现性能问题因为非直接buffer不总是能够作为本地I/O操作的目标。

当传送一个非直接字节buffer给channel时,它会创建一个临时的直接字节buffer,将非直接字节buffer的内容拷贝到直接字节buffer中,会在临时直接字节buffer上执行I/O操作,然后拷贝临时直接字节buffer的内容到内直接字节buffer中。然后临时直接字节buffer将被垃圾收集器回收。

虽然对于I/O操作最佳,直接字节buffer的创建是很昂贵的因为操作系统需要分配JVM堆以外的内存,设置/拆除此内存可能要比位于堆内的buffer的时间更长。

在你的代码工作之后,如果你想优化性能,你可以调用ByteBuffer allocateDirect()方法轻松获得一个直接字节buffer,这个我稍后讨论。

总结

buffer是一个NIO对象,它存储要发送给I/O服务或从I/O服务中接受的固定数量的数据。它位于应用程序和将缓冲数据写入服务或从服务读取数据并将其存入buffer的channel之间。

Buffers具有capacity,limit,position以及mark属性。这四个属性关系如下:0 <= mark <= position <= limit <= capacity。

Buffers由派生自抽象类Buffer的抽象类实现。这些类包含ByteBuffer, CharBuffer, DoubleBuffer,FloatBuffer,IntBuffer,LongBuffer和ShortBuffer。进一步将,ByteBuffer是抽象类MappedByteBuffer的子类。

在这章,你学习了怎么创建buffers(包含视图buffer),读和写buffer内容,翻转buffer,标记buffer和在buffer上执行额外操作例如压缩。也学习了关于字节排序和直接字节buffers。

说明

终于翻译完这一章了,中间有翻译不周的请各位看官谅解。下一章就开始NIO的另一个组件Channel了,一起期待一下吧!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值