zoukankan      html  css  js  c++  java
  • 解读Java NIO Buffer

    从jdk1.4开始,java中引入了nio包,提供了非阻塞式的网络编程模型,提供网络性能。nio中核心组件有三个:channel、buffer、selector。这里主要探讨buffer的概念和使用。buffer本质上是数据容器,可以存储java中的各种原始数据类型,并提供了读、写等各种操作。

    1. 什么是buffer

    buffer是一个数据容器,确切的说是存储原始数据类型的数据容器(除了boolean类型)。

    • ByteBuffer:存储byte类型的数据
    • CharBuffer:存储char类型的数据
    • ShortBuffer:存储short类型的数据
    • IntBuffer:存储int类型的数据
    • LongBuffer:存储long类型的数据
    • FloatBuffer:存储float类型的数据
    • DoubleBuffer:存储double类型的数据

    2. buffer中的核心属性

    • capacity:代表buffer的容量,即可以容纳的元素数量。capacity>0,且不能改变。
    • limit:向容器内写入数据或者从容器内读取数据时,不能等于或超过limit对应的索引。0<limit<=capacity
    • position:向容器内写入数据或者从容器内读取数据时,下一个元素的索引。0<=position<limit
    • mark:标记位,当调用buffer中的reset方法时,position会被重置到mark对应的位置。注意:必须先调用mark方法设置mark值,才能调用reset方法,否则会抛出InvalidMarkException。

    三者之间的关系: mark<= position <= limit <= capacity

    3. buffer中的get和put操作

    buffer中的get和put操作分为两类:相对get/put操作(relative get/put operation)和绝对get/put操作(absolute get/put operation)。

    • 相对操作(relative operation):从当前的position开始,读取/写入一个或多个元素,然后增加position的值(根据元素数量改变position的值)。如果相对get操作超出limit的限制,会抛出BufferUnderflowException;如果相对put操作超出limit限制,会抛出BufferOverflowException。
    • 绝对操作(absolute operation):显式的指定index来读取/写入一个元素。absolute operation可能会导致IndexOutofBoundsException。 注意:absolute operation操作不会改变position的值

    get和put操作是在Buffer的子类中定义的。

    4. buffer中其它常用操作

    • capacity():获取buffer的容量

    • position():获取当前的position值

    • limit():获取当前的limit值

    • mark():设置mark变量的值,mark=position

    • reset():重置position,position=mark

    • clear():清空buffer,position=0, limit=capacity, mark=-1。该方法通常在使用通道读取数据或者put操作填充buffer之前调用。 注意:该方法并不会真的删除buffer中的数据。

    • flip():翻转buffer,limit=position, position=0, mark=-1。执行该方法为读取数据做准备。

    • rewind():执行该方法后,position=0, mark=-1,通常用于重新读取buffer中的数据。

    • remaining():获取buffer中的元素数量(limit-position)。

    • array():获取buffer中的数组。注意:如果改变buffer中的元素,会影响返回的数组中元素,反之亦然。

    5. 解读buffer的常见操作引起的容器变化

    这里我以CharBuffer为例,解释buffer中的常见操作,以及容器是如何变化的。

    下图展示了put(char c)操作(relative operation)引起的一些列变化,

    图片

    (1)首先新建一个容量为5的buffer,次数position=0,limit=5(图中未标出来);

    (2)第1次调用 put(char c),写入元素a,此时position变为1,limit不变;

    (3)第2次调用put(char c),写入元素b,此时position变为2,limit不变;

    (4)第2次调用put(char c),写入元素c,此时position变为3,limit不变;

    (5)第2次调用put(char c),写入元素d,此时position变为4,limit不变;

    (6)第2次调用put(char c),写入元素e,此时position变为5,limit不变(limit=position)

    接下来演示flip()操作和get操作(relative operation)给buffer容器带来的变化

    图片

    (1)执行flip()操作后,position=0,limit=5;

    (2)执行get()操作后,返回元素a,position=1,limit=5;

    (3)执行get()操作后,返回元素b,position=2,limit=5;

    (4)执行get()操作后,返回元素c,position=3,limit=5;

    (5)执行get()操作后,返回元素d,position=4,limit=5;

    (6)执行get()操作后,返回元素e,position=5,limit=5;

    注意:执行get()操作,容器内的元素并不会被删除。

    绝对get和put操作我就不在这里演示了,只要容器里存在元素,你随时可以通过绝对get和put操作,读取、写入元素。

    注意:绝对get和put操作不会抛出BufferUnderflowException和BufferOverflowException。

    如果这时我们对buffer容器执行rewind()或者clear()操作,容器变化如下:

    图片

    执行rewind操作后,position=0,limit保持不变(=5),容器内元素又变得可以读取了。因此,当我们想要重复读取容器内元素的时候,就可以借助rewind()来帮助我们达成目的。

    注意:执行clear()操作后,position=0,limit=capacity=5,虽然这个时候我们仍然可以对buffer容器执行get操作,但是我们不应该这么做!!clear操作的目的是清空缓存区,为之后的channel-read或put操作服务的,使得它们可以再次填充buffer容器。

    接下来我们再来看一下mark和reset操作,假设容器内部情况如下图:

    图片

    容器内又五个元素,position=0,limit=5,接下来我们依此对容器执行下述几步操作:

    get() ==> get() ==> mark() ==> get() ==> get() ==> reset() ==> get() ==> get()

    容器变化情况如下图所示:

    图片

    可以看到,通过mark和reset,我们可以重复读取某一区间内的元素。

    6. 测试代码

    CharBuffer buffer = CharBuffer.allocate(5); 
    char[] chars = buffer.array(); 
    System.out.println(String.format("未存储任何元素之前,limit=%d, position=%d, chars=%s", buffer.limit(), buffer.position(), Arrays.toString(chars))); 
    // 写入第1个元素 
    buffer.put('a'); 
    System.out.println(String.format("写入1个元素之后,limit=%d, position=%d, chars=%s", buffer.limit(), buffer.position(), Arrays.toString(chars))); 
    // 写入第2个元素 
    buffer.put('b'); 
    System.out.println(String.format("写入2个元素之后,limit=%d, position=%d, chars=%s", buffer.limit(), buffer.position(), Arrays.toString(chars))); 
    // 写入第3个元素 
    buffer.put('c'); 
    System.out.println(String.format("写入3个元素之后,limit=%d, position=%d, chars=%s", buffer.limit(), buffer.position(), Arrays.toString(chars))); 
    // 写入第4个元素 
    buffer.put('d'); 
    System.out.println(String.format("写入4个元素之后,limit=%d, position=%d, chars=%s", buffer.limit(), buffer.position(), Arrays.toString(chars))); 
    // 写入第5个元素 
    buffer.put('e'); 
    System.out.println(String.format("写入5个元素之后,limit=%d, position=%d, chars=%s", buffer.limit(), buffer.position(), Arrays.toString(chars))); 
    /* 
    // 如果继续往容器中写入元素,buffer会抛出BufferOverflowException 
    buffer.put("f"); // 执行这行代码会抛出BufferOverflowException异常 
    */ 
    
    // 执行flip操作,为读取buffer中的数据做准备 
    buffer.flip(); 
    System.out.println("
    执行flip操作后---------------"); 
    System.out.println(String.format("未读取任何元素之前,limit=%d, position=%d", buffer.limit(), buffer.position())); 
    System.out.println(String.format("读取1个元素之后,limit=%d, position=%d,当前读取的元素:%s", buffer.limit(), buffer.position(), buffer.get())); 
    System.out.println(String.format("读取2个元素之后,limit=%d, position=%d,当前读取的元素:%s", buffer.limit(), buffer.position(), buffer.get())); 
    System.out.println(String.format("读取3个元素之后,limit=%d, position=%d,当前读取的元素:%s", buffer.limit(), buffer.position(), buffer.get())); 
    System.out.println(String.format("读取4个元素之后,limit=%d, position=%d,当前读取的元素:%s", buffer.limit(), buffer.position(), buffer.get())); 
    System.out.println(String.format("读取5个元素之后,limit=%d, position=%d,当前读取的元素:%s", buffer.limit(), buffer.position(), buffer.get())); 
    // buffer.get(); // 执行这行代码会抛出BufferUnderflowException 
    
    // 执行rewind操作,重新读取buffer中的数据 
    buffer.rewind(); 
    System.out.println("
    执行rewind操作后---------------"); 
    System.out.println(String.format("未读取任何元素之前,limit=%d, position=%d", buffer.limit(), buffer.position())); 
    System.out.println(String.format("读取1个元素之后,limit=%d, position=%d,当前读取的元素:%s", buffer.limit(), buffer.position(), buffer.get())); 
    System.out.println(String.format("读取2个元素之后,limit=%d, position=%d,当前读取的元素:%s", buffer.limit(), buffer.position(), buffer.get())); 
    System.out.println(String.format("读取3个元素之后,limit=%d, position=%d,当前读取的元素:%s", buffer.limit(), buffer.position(), buffer.get())); 
    System.out.println(String.format("读取4个元素之后,limit=%d, position=%d,当前读取的元素:%s", buffer.limit(), buffer.position(), buffer.get())); 
    System.out.println(String.format("读取5个元素之后,limit=%d, position=%d,当前读取的元素:%s", buffer.limit(), buffer.position(), buffer.get())); 
    
    System.out.println("
    测试absolute put/get operation"); 
    System.out.println(buffer.get(0)); 
    buffer.put(0, 's'); 
    System.out.println(buffer.get(0)); 
    System.out.println(String.format("limit=%d, position=%d", buffer.limit(), buffer.position())); 
    
    // 执行clear操作后,不应该再调用get()方法获取容器内元素! 
    /* 
    buffer.clear(); 
    System.out.println(String.format("未读取任何元素之前,limit=%d, position=%d", buffer.limit(), buffer.position())); 
    System.out.println(String.format("读取1个元素之后,limit=%d, position=%d,当前读取的元素:%s", buffer.limit(), buffer.position(), buffer.get())); 
    System.out.println(String.format("读取2个元素之后,limit=%d, position=%d,当前读取的元素:%s", buffer.limit(), buffer.position(), buffer.get())); 
    System.out.println(String.format("读取3个元素之后,limit=%d, position=%d,当前读取的元素:%s", buffer.limit(), buffer.position(), buffer.get())); 
    System.out.println(String.format("读取4个元素之后,limit=%d, position=%d,当前读取的元素:%s", buffer.limit(), buffer.position(), buffer.get())); 
    System.out.println(String.format("读取5个元素之后,limit=%d, position=%d,当前读取的元素:%s", buffer.limit(), buffer.position(), buffer.get())); 
    */ 
    
    System.out.println("
    测试mark、reset"); 
    buffer.rewind(); // 调用rewind,使得我们可以重新读取buffer中的元素 
    Assert.assertEquals('s', buffer.get()); 
    Assert.assertEquals('b', buffer.get()); 
    buffer.mark(); // mark=2,position=2 
    Assert.assertEquals('c', buffer.get()); 
    Assert.assertEquals('d', buffer.get()); 
    buffer.reset(); // position重新指向2,即position=2 
    Assert.assertEquals('c', buffer.get()); 
    Assert.assertEquals('d', buffer.get()); 
    

    7. buffer不是线程安全的

  • 相关阅读:
    LeetCode 32. 最长有效括号(Longest Valid Parentheses)
    LeetCode 141. 环形链表(Linked List Cycle)
    LeetCode 160. 相交链表(Intersection of Two Linked Lists)
    LeetCode 112. 路径总和(Path Sum)
    LeetCode 124. 二叉树中的最大路径和(Binary Tree Maximum Path Sum)
    LightGBM新特性总结
    sql service 事务与锁
    C#泛型实例详解
    C# 中的委托和事件(详解)
    C# DateTime日期格式化
  • 原文地址:https://www.cnblogs.com/leekeggs/p/13379894.html
Copyright © 2011-2022 走看看