初探Java的Buffer类

  • Post author:
  • Post category:java



传统阻塞型I/O的问题


用过Java的Socket编程的人一定都知道传统的网络I/O编程是ServerSocket的accept方法一直等待着TCP请求的接入,每当收到一个TCP请求后,ServerSocket就会创建出一组I/O流,把它们交给一个线程去处理,这种情况下的结构关系就是每条线程处理一个I/O,就像下面这张图一样

在这里插入图片描述

这种设计有几个问题:

1.假设访问的高峰期并发量较大,我们必须为程序配置一个较大的线程池,可是当过了高峰期,并发量减少了,那些空闲的线程岂不是一种资源的浪费?

2.当I/O流打开以后如果数据传输并不频繁,那么这个线程很大一部分时间都是在等待中度过的,这又是一种资源的利用率低下。

那么怎么让更少的线程能够处理更多的事情呢?于是就引出了我们要讲的nio。

nio就是非阻塞型I/O(non-blocking I/O),它有三个核心的概念:Selector,Channel,Buffer。

它们的关系就像下图一样

在这里插入图片描述

Buffer(缓冲区)的底层其实就是一个数组,它提供了一些方法来向数组中写入和读出数据。

Channel(通道)是一个可以向Buffer中写入和读取数据的对象,但是其本身不能直接读取数据。

这个博客主要是探索Buffer类的一些底层实现,channel类以后再去看。


Buffer的几个常用方法:

  • allocate() – 初始化一块缓冲区
  • put() – 向缓冲区写入数据
  • get() – 向缓冲区读数据
  • filp() – 将缓冲区的读写模式转换
  • clear() – 这个并不是把缓冲区里的数据清除,而是利用后来写入的数据来覆盖原来写入的数据,以达到类似清除了老的数据的效果
  • compact() – 从读数据切换到写模式,数据不会被清空,会将所有未读的数据copy到缓冲区头部,后续写数据不会覆盖,而是在这些数据之后写数据
  • mark() – 对position做出标记,配合reset使用
  • reset() – 将position置为标记值


Buffer的几个核心属性:


  • capacity

    – 缓冲区大小,缓冲区一旦创建出来以后,这个属性就不会再变化了

  • position

    – 读写数据的定位指针,用来标识当前读取到了哪一个位置。

  • limit

    – 读写的边界,用于限制指针的最大指向位置。当指针走到边界上的时候就要停住,否则就会抛出BufferUnderflowException


Buffer的基本用法


使用Buffer读写数据一般遵循以下四个步骤:

  1. 写入数据到Buffer
  2. 调用flip()方法改变读写模式
  3. 从Buffer中读取数据
  4. 调用clear()方法或者compact()方法

    下面用代码来展示下
public static void main(String[] args) {
        //生成一个长度为10的缓冲区
        IntBuffer intBuffer = IntBuffer.allocate(10);
        for (int i = 0; i < intBuffer.capacity(); ++i){
            int randomNum = new SecureRandom().nextInt(20);
            intBuffer.put(randomNum);
        }
        //状态翻转
        intBuffer.flip();
        while (intBuffer.hasRemaining()){
            //读取数据
            System.out.print(intBuffer.get() + ",");
        }
        //clear方法本质上并不是删除数据
        intBuffer.clear();
        System.out.print("\n");
        System.out.println("-----------------------------");
        while (intBuffer.hasRemaining()){
            System.out.print(intBuffer.get() + ",");
        }
    }

控制台输出如下

在这里插入图片描述

可以看到,调用了clear方法以后,缓冲区里的数据并没有被清除。这是什么原因呢,先不急,先改写一下上面的代码。

public static void main(String[] args) {
        IntBuffer intBuffer = IntBuffer.allocate(10);
        System.out.println("初始的Buffer:" + intBuffer);
        for (int i = 0; i < 5; ++i){
            int randomNum = new SecureRandom().nextInt(20);
            intBuffer.put(randomNum);
        }

        System.out.println("flip之前:limit = "+ intBuffer);
        intBuffer.flip();
        System.out.println("flip之后:limit = "+ intBuffer);

        System.out.println("进入读取");
        while (intBuffer.hasRemaining()){
            System.out.println(intBuffer);
            System.out.println(intBuffer.get());
        }
    }

控制台输出结果如下:

在这里插入图片描述

输出结果中的pos代表的就是

position

属性,lim代表

limit

属性,cap代表

capacity

属性。

在缓冲区刚初始化出来的时候,position指向的是数组的第一个位置,limit和数组的容量一样。

用图片来表示大概就如下图

在这里插入图片描述

向缓冲区中每写入一个数据,指针就会后移一位

在这里插入图片描述

当调用了flip()方法后,position又会重新指向数组的第一个位置,而limit会指向原来的position的位置。

在这里插入图片描述

从源码上来看,就是把position赋值给limit,再把position变回0。

如果用图示的话就如下图:

在这里插入图片描述

然后再调用get方法的时候,每调用读取一个数据,position就会向后移动一位,直到position到达了limit的位置。实际上intBuffer.hasRemaining()方法就是判断position < limit。



版权声明:本文为czx2018原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。