• 解读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不是线程安全的

  • 相关阅读:
    bzoj 2257 (JSOI 2009) 瓶子与燃料
    bzoj 2257 (JSOI 2009) 瓶子与燃料
    splay 模板 洛谷3369
    费用流 模板 洛谷3381
    bzoj 1024 [SCOI2009]生日快乐——模拟
    bzoj 3231 [Sdoi2008]递归数列——矩阵乘法
    hdu 5823 color II——子集dp(独立集)
    bzoj 1093 [ZJOI2007]最大半连通子图——缩点+拓扑
    洛谷 3959 宝藏——枚举+状压dp
    bzoj 1034 [ZJOI2008]泡泡堂BNB——贪心
  • 原文地址:https://www.cnblogs.com/leekeggs/p/13379894.html
Copyright © 2020-2023  润新知