NIO Buffer 内部机理使用姿势

关于NIO Buffer中4个重要状态属性

position、limit、capacity 与 mark

Buffer本身是一个容器,称作缓冲区,里面包装了特定的一种原生类型,其子类包括ByteBuffer、CharBuffer、LongBuffer、IntBuffer、DoubleBuffer、ShortBuffer、FloatBuffer。

注意没有Boolean类型的Buffer

Buffer是一种特定原生类型线性的有限的元素序列,Buffer中比较重要的4个属性:position、limit、capacity、mark. 在使用 Buffer 时,我们实际操作的就是这四个属性的值。

具体介绍下 4 个属性:

  • capacity(容量):
    一个buffer能够容纳数据元素的最大数量,capacity不会为负数,且永远不能被改变。
    假设:IntBuffer.allocate(1024), 分配了大小为1024的元素个数,则capacity就等于1024。
  • limit(上界):一个buffer的limit指的是第一个不能在读也不能在写的元素索引. limit不会为负数,并且一定是小于capacity的。

    假设:IntBuffer.allocate(1024), 我们在程序中设置limit=512,说明Buffer的容量是1024,但是从512之后既不能读也不能写了,进一步说明该Buffer的实际可用大小是512。

  • position(位置):一个buffer的position指的是下一个将要读或者写的元素的索引.position不会为负数,并且一定是小于limit的. position的位置主要由get()和put()方法的调用来更新。
  • mark(标记):
    一个备忘地址,作为临时标记位置使用,标记在设定前是未定义的。

mark的使用场景:

假设 IntBuffer.allocate(1024),现在position位置为10,现在只想发送512到1024之间的缓冲数据,此时我们可以buffer.mark(buffer.position())既将position记入mark位置,然后buffer.postion(512),此时发送的数据就是512到1024之间的数据。发送完成后,调用buffer.reset()将mark临时标记赋值给position使得position=mark。注意如果未设定mark,而调用了buffer.reset()方法则会抛出InvalidMarkException。

不变式:

0 <= mark <= position <= limit <= capacity

传输数据:

Buffer中的每个子类中都有get()和put()方法.
带参数的put和get方法称作绝对存入/取出,位置是通过参数指定的.
绝对操作不影响position位置,但是如果索引位置超出limit,则会抛出IndexOutOfBoundsException;
不带参数的put和get称作相对存入/取出,即position位置自动前进.
对于get相对操作,如果位置超过了limit,则会抛出BufferUnderflowException;
对于put相对操作,如果位置超过了limit,则会抛出BufferOverflowException;

线程安全性:

buffer在多线程并发下并不是安全的。如果一个buffer会在多个线程使用,那么需要使用恰当的同步操作来访问buffer。也就是buffer本身并不是线程安全的。

** NIO Buffer 常用方法介绍**

这部分我们以实际代码为例来说明:

clear() 方法:

清除,将buffer重置为空状态,它并不会更改缓冲区内的任何数据元素. 如果此时还没有读取的数据,则就无法读取到了。

public final Buffer clear() {
        position = 0; // 位置重置为0
        limit = capacity; // limit重置为capacity
        mark = -1; // 丢弃标记
        return this;
}

flip() 方法:

翻转,使buffer从写模式转换到读模式。

源码:

public final Buffer flip() {
        limit = position; // 将limit设置为position
        position = 0; // position重置为0
        mark = -1; // 丢弃标记
        return this;
}

rewind() 方法:

重绕,重置position为0,limit保持不变,此时调用rewind前buffer已处于读模式下了, 可以重新读取buffer中的数据。

源码:

public final Buffer rewind() {
        position = 0; // 重置position为0
        mark = -1; // 丢弃标记
        return this;
}

compact() 方法:

将所有未读的数据拷贝到Buffer起始处. 然后将position设到最后一个未读元素正后面. limit属性依然像clear()方法一样,设置成capacity. 现在Buffer准备好写数据了,但是不会覆盖未读的数据。

注意compact方法的实现是由原生类型的子类实现,比如ByteBuffer则由HeapByteBuffer中实现。

源码:

public ByteBuffer compact() {
        System.arraycopy(hb, ix(position()), hb, ix(0), remaining());
        position(remaining());
        limit(capacity());
        discardMark();
        return this;
 }

hasRemaining() 方法:

会在读取缓冲区时告诉你是否已经达到缓冲区的上界. 可以通过remaining()高效读取buffer数据。

int count = buffer.remaining();
for (int i = 0; i < count; i++) {
    myByteArray[i] = buffer.get();
} 

slice() 方法:

对原有数据的一个快照,共享相同的底层数据元素. 调用slice方法后,会得到大于等于position且小于limit之间的数据,对于改变slice方法获得大buffer数据,也能够反映到原buffer上。

示例代码:

public class NioTest_slice {
    public static void main(String[] args) {
        ByteBuffer byteBuffer = ByteBuffer.allocate(10);
        int capacity = byteBuffer.capacity();
        for (int i = 0 ; i < capacity; i++) {
            byteBuffer.put((byte)i);
        }

        // 设置position、limit
        byteBuffer.position(2);
        byteBuffer.limit(6);

        //slice方法是前闭后开的 大于等于position,小于limit
        ByteBuffer subByteBuffer = byteBuffer.slice();

        // 改变subByteBuffer内容,也能反映到byteBuffer上
        for (int j = 0; j < subByteBuffer.capacity(); j++) {
            // 2到5位置的数*2
            subByteBuffer.put((byte)(2 * subByteBuffer.get(j)));
        }

        // 设置回原来的值,打印输出看下byteBuffer数据变化
        byteBuffer.position(0);
        byteBuffer.limit(capacity);
        while (byteBuffer.hasRemaining()) {
            System.out.println(byteBuffer.get());
        }
    }
}

输出结果中看到第3个位置到第5个位置的数据都??2了,返回4,6,8.

0
1
4
6
8
10
6
7
8
9

duplicate() 方法:

创建了一个与原始缓冲区相似的新缓冲区,与调用slice方法一样也是共享相同的底层数据元素, 拥有同样的容量, 但每个缓冲区拥有各自的 position、limit 和 mark 属性。

示例代码:

public class NioTest_duplicate {
    public static void main(String[] args) {
        ByteBuffer byteBuffer = ByteBuffer.allocate(10);
        int capacity = byteBuffer.capacity();
        for (int i = 0 ; i < capacity; i++) {
            byteBuffer.put((byte)i);
        }

        ByteBuffer subByteBuffer = byteBuffer.duplicate();
        subByteBuffer.position(0); // 单独给duplicate出来的buffer设置position
        System.out.println("byteBuffer position:" + byteBuffer.position() + "--subByteBuffer position:" + subByteBuffer.position());

        // 改变subByteBuffer内容,也能反映到byteBuffer上
        for (int j = 0; j < subByteBuffer.capacity(); j++) {
            // 2到5位置的数*2
            subByteBuffer.put((byte)(2 * subByteBuffer.get(j)));
        }

        // 切换到读模式
        byteBuffer.flip();
        while (byteBuffer.hasRemaining()) {
            System.out.println(byteBuffer.get());
        }
    }
}

输出结果能看到原buffer数据元素变化

byteBuffer position:10--subByteBuffer position:0
0
2
4
6
8
10
12
14
16
18

asReadOnlyBuffer() 方法:

我们可以随时将一个普通的Buffer调用asReadOnlyBuffer方法返回一个只读Buffer. 但是,不能将一个只读Buffer转换为读写Buffer。

关于 Buffer 的 Scattering 和 Gathering

scatter / gather经常用于需要将传输的数据分开处理的场合,例如传输一个由消息头和消息体组成的消息,你可能会将消息体和消息头分散到不同的buffer中,这样你可以方便的处理消息头和消息体。

Scattering:

Scattering Reads在移动下一个buffer前,必须填满当前的buffer,这也意味着它不适用于动态消息(译者注:消息大小不固定). 换句话说,如果存在消息头和消息体,消息头必须完成填充(例如 128byte),Scattering Reads才能正常工作。

Gattering:

buffer数组是write()方法的入参,write()方法会按照buffer在数组中的顺序,将数据写入到channel,注意只有position和limit之间的数据才会被写入。
因此,如果一个buffer的容量为128byte,但是仅仅包含58byte的数据,那么这58byte的数据将被写入到channel中。因此与Scattering Reads相反,Gathering Writes能较好的处理动态消息。

示例代码:

public class NioTest_scatteringandgathering {

    public static void main(String[] args) throws  Exception {
        try(ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();) {
            InetSocketAddress inetSocketAddress = new InetSocketAddress(8899);
            serverSocketChannel.socket().bind(inetSocketAddress); // 绑定到8899端口

            int messageLength = 2 + 3 + 4;

            ByteBuffer[] byteBuffers = new ByteBuffer[3];
            byteBuffers[0] = ByteBuffer.allocate(2);
            byteBuffers[1] = ByteBuffer.allocate(3);
            byteBuffers[2] = ByteBuffer.allocate(4);

            SocketChannel socketChannel = serverSocketChannel.accept();

            do {
                int byteRead = 0;
                while (byteRead < messageLength) {
                    long r = socketChannel.read(byteBuffers);
                    System.out.println("--------------r:" + r);
                    byteRead += r;

                    System.out.println("byteRead:" + byteRead);

                    Arrays.asList(byteBuffers).stream().map(buffer -> "position:" + buffer.position() + ", limit:" + buffer.limit())
                            .forEach(System.out::println);
                }

                Arrays.asList(byteBuffers).forEach(Buffer::flip);

                // 实际写的个数
                int byteWrites = 0;
                while (byteWrites < messageLength) {
                    long r = socketChannel.write(byteBuffers);
                    byteWrites += r;
                }

                Arrays.asList(byteBuffers).forEach(Buffer::clear);

                System.out.println("byteRead:" + byteRead + ", byteWrite:" + byteWrites + ", messageLength:" + messageLength);
            } while (true);
        }
    }
}

以上服务启动后,我们通过nc localhost 8899或者telecom localhost 8899,然后输入Hello world字符串。

控制台上能看到输出:

--------------r:9
byteRead:9
position:2, limit:2
position:3, limit:3
position:4, limit:4
byteRead:9, byteWrite:9, messageLength:9

欢迎关注我的公众号,扫二维码关注,文章首发到公众号,与你一同成长~

原文地址:https://www.cnblogs.com/ldws/p/12067004.html

时间: 2024-11-10 11:49:26

NIO Buffer 内部机理使用姿势的相关文章

Java NIO —— Buffer(缓冲区)

Buffer是一个抽象类,位于java.nio包中,主要用作缓冲区.注意:Buffer是非线程安全类. 缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存.这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问该块内存. NIO 有以下几种Buffer类型: ByteBuffer MappedByteBuffer CharBuffer DoubleBuffer FloatBuffer IntBuffer LongBuffer ShortBuffer capacity

Hadoop核心架构HDFS+MapReduce+Hbase+Hive内部机理详解

通过这一阶段的调研总结,从内部机理的角度详细分析,HDFS.MapReduce.Hbase.Hive是如何运行,以及基于Hadoop数据仓库的构建和分布式数据库内部具体实现.如有不足,后续及时修改. HDFS的体系架构 整个Hadoop的体系结构主要是通过HDFS来实现对分布式存储的底层支持,并通过MR来实现对分布式并行任务处理的程序支持. HDFS采用主从(Master/Slave)结构模型,一个HDFS集群是由一个NameNode和若干个DataNode组成的(在最新的Hadoop2.2版本

Java NIO Buffer

Java NIO中的Buffer用于和NIO通道进行交互.如你所知,数据是从通道读入缓冲区,从缓冲区写入到通道中的.交互图如下: 缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存.这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问该块内存. 下面是NIO Buffer相关的话题列表: 1.Buffer的基本用法 使用Buffer读写数据一般遵循以下四个步骤: 写入数据到Buffer 调用flip()方法 从Buffer中读取数据 调用clear()方法或者com

nio buffer的capacity,position和limit

nio Buffer的capacity,position,limit的3个属性说明: capacity:Buffer的内存固定的一个大小值,一般创建Buffer时初始化写入-->ByteBuffer.allocate(capacity),需注意写入数据时需考虑数据大小是否超过Buffer的大小,如果超过需对Buffer进行扩容. position和limit都分读和写2种情况: position在读和写情况的分析: 1)写数据到Buffer中时,position表示写入数据的当前位置.posit

28 Java学习之NIO Buffer(二)(待补充)

一. Buffer介绍 Buffer,故名思意,缓冲区,实际上是一个容器,是一个连续数组.Channel提供从文件.网络读取数据的渠道,但是读取或写入的数据都必须经由Buffer.具体看下面这张图就理解了: 上面的图描述了从一个客户端向服务端发送数据,然后服务端接收数据的过程.客户端发送数据时,必须先将数据存入Buffer中,然后将Buffer中的内容写入通道.服务端这边接收数据必须通过Channel将数据读入到Buffer中,然后再从Buffer中取出数据来处理. Buffer本质上就是一块内

NumPy 超详细教程(3):ndarray 的内部机理及高级迭代

系列文章地址 NumPy 最详细教程(1):NumPy 数组 NumPy 超详细教程(2):数据类型 NumPy 超详细教程(3):ndarray 的内部机理及高级迭代 ndarray 对象的内部机理 在前面的内容中,我们已经详细讲述了 ndarray 的使用,在本章的开始部分,我们来聊一聊 ndarray 的内部机理,以便更好的理解后续的内容. 1.ndarray 的组成 ndarray 与数组不同,它不仅仅包含数据信息,还包括其他描述信息.ndarray 内部由以下内容组成: 数据指针:一个

JAVA NIO buffer (知识三)

java nio 里的buffer是缓存数据,通常缓冲区是一个数组,字节数组,也可以是别的类型.最常用的就是bytebuffer, 还有一些其它的类型: charbuffer, shortbuffer, intbuffer, longbuffer, floatbuffer, doublebufer. 一开始在知识(一)里写到,想要用nio读取数据,都是从channel读取到buffer.然后应用从buffer读取数据,同样写数据也是,先把数据写到buffer中,然后读道channel中. 基本上

JAVA基础知识之NIO——Buffer

1 package nio; 2 3 import java.nio.CharBuffer; 4 5 public class BufferTest { 6 public static void main(String[] args) { 7 //创建Buffer 8 CharBuffer buff = CharBuffer.allocate(8); 9 System.out.println("capacity: "+buff.capacity()); 10 System.out.pr

【JAVA】【NIO】4、Java NIO Buffer

Java NIO的Buffer用于和channel进行交互. buffer本质上是一个内存块,你可以写数据,然后读取出来. 这个内存块是通过NIO的Buffer对象进行包装的,该对象提供了一系列的方法,使得对内存块的访问更加容易了. 基本的Buffer使用 使用Buffer读写数据一般有如下4步: 1.将数据写入 Buffer 2.调用buffer.flip()方法 3.从Buffer中读出数据 4.调用buffer.clear()方法或buffer.compact()方法 当你将数据写入buf