Java NIO之Buffer

Java NIO可以理解为,非阻塞IO(Non-blocking I/O),或者称之为,新的IO(New I/O)。比较核心的组件是:

(1)Buffer,缓冲区,用于存储数据。

(2)Channel,通道,数据传输的载体。

(3)Selector,选择器或者称为多路复用器,用于监听Channel,实现一个线程监听多个通道,减少线程上下文切换的开销。

本篇博客主要介绍的是Buffer,除了boolea类型之外,其他基本数据类型都有对应的Buffer,都继承于java.nio.Buffer,如下:

下面以ByteBuffer为例,介绍Buffer的一些原理和用法。

首先,明确一点,Buffer实质就是数组,别把它看得过于神秘,相关操作也是对数组的操作而已。

先看抽象类Buffer中定义的四个属性:

    // 四者之间的大小关系: mark <= position <= limit <= capacity
    private int mark = -1;     // 用于标记position的位置
    private int position = 0;  // 下一个要读或要写的数组下标
    private int limit;         // 能够读或者写的个数
    private int capacity;      // 数组的容量,不会改变

源码中,也给出了四者之间大小的关系。由于capacity一旦确定,就不会改变,所以不是我们关注的重点。缓冲区有读模式和写模式之分:

在写模式下:

(1)position代表下一个可以写入的数据的数组下标

(2)limit,是限制的意思,代表最多可以写入多少个数据,大小等于capacity

在读模式下:

(1)position代表下一个可以读取数据的数组下标

(2)limit,代表最多可以读取多少个数据,等于上一次写模式下的position

 

下面通过一些简单的例子进一步理解它们:

    public static void main(String[] args) {
        String data = "abc123";
        // 分配指定大小的空间
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        printMsg("before put data",buffer);

        // 把数据放入缓存中
        buffer.put(data.getBytes());
        printMsg("after put data",buffer);

        // 从写模式,切换为读模式
        buffer.flip();
        printMsg("after flip()",buffer);

        byte[] dest = new byte[buffer.limit()];
        // 从缓存中取数据
        buffer.get(dest, 0, 3);
        printMsg("第一次取出的数据:"+new String(dest,0,3),buffer);

        // 标记position的位置
        buffer.mark();

        // 从缓存中取数据
        buffer.get(dest, 0, 2);
        printMsg("第二次取出的数据:"+new String(dest,0,2),buffer);

        // 重置
        buffer.reset();
        printMsg("after reset data",buffer);

    }

    // 打印信息
    public static void printMsg(String title,ByteBuffer buffer){
        System.out.println(title);
        System.out.println("position: "+buffer.position());
        System.out.println("limit: "+buffer.limit());
        System.out.println("========================");
    }

1.没有放数据之前:

        // 分配指定大小的空间
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        printMsg("before put data",buffer);

 我们申请了1024个字节的缓冲区,其初始值为:

before put data
position: 0
limit: 1024

 

2.写入数据到缓冲区之后


        // 把数据放入缓存中
        buffer.put(data.getBytes());
        printMsg("after put data",buffer);

我们往缓冲区放了 abc123 ,由于英文和数字占一个字节,故一共占6个字节,将数组从下标0-5填充了数据,故postion为6,即下一个可写入的下标:

after put data
position: 6
limit: 1024

查看其源码:

    public final ByteBuffer put(byte[] src) {
        return put(src, 0, src.length);
    }

 调用了重载方法:

    public ByteBuffer put(byte[] src, int offset, int length) {
        checkBounds(offset, length, src.length);
        if (length > remaining())
            throw new BufferOverflowException();
        int end = offset + length;
        for (int i = offset; i < end; i++)
            this.put(src[i]);
        return this;
    }

最后还是调用了存单个字节的方法:

 this.put(src[i]);

3.从写模式,切换为读模式:

        // 从写模式,切换为读模式
        buffer.flip();
        printMsg("after flip()",buffer);

查看其输出:

after flip()
position: 0
limit: 6

可以看到,其postion已经重置为0了,表示将从0开始读取数据,而limit的值,就是上次写模式下的position,看其源码就明白了:

    public final Buffer flip() {
        limit = position;
        position = 0;
        mark = -1;
        return this;
    }

4.第一次读取数据并mark():

        byte[] dest = new byte[buffer.limit()];
        // 从缓存中取数据
        buffer.get(dest, 0, 3);
        printMsg("第一次取出的数据:"+new String(dest,0,3),buffer);

        // 标记position的位置
        buffer.mark();

 此次读取了3个字节的数据,position应为3,limit还是6:

第一次取出的数据:abc
position: 3
limit: 6

看看其get方法都做了什么:

    public ByteBuffer get(byte[] dst, int offset, int length) {
        checkBounds(offset, length, dst.length);
        if (length > remaining())
            throw new BufferUnderflowException();
        int end = offset + length;
        for (int i = offset; i < end; i++)
            dst[i] = get();
        return this;
    }

 最终还是读取了获取单个字节的方法:

 dst[i] = get();

5.第二次读取

        buffer.get(dest, 0, 2);
        printMsg("第二次取出的数据:"+new String(dest,0,2),buffer);

这次只读了2个字节的数据,所以position应为5,limit还是6:

第二次取出的数据:12
position: 5
limit: 6

这次操作,主要是为了演示说明mark()方法和reset()方法的作用,加深理解

6.reset重置到mark的位置

        buffer.reset();
        printMsg("after reset data",buffer);

通过调用reset方法,可以将position恢复到上一次mark的位置,因为mark记录了position的值:

after reset data
position: 3
limit: 6

所以position的值恢复到了,第一次读取数据之后的值,也就是3,这样就可以实现对某部分数据的重复读取了。

其源码如下:

    public final Buffer reset() {
        int m = mark;
        if (m < 0)
            throw new InvalidMarkException();
        position = m;
        return this;
    }

关键的一点是,将mark的值重新赋值给position:

 position = m;

其实,把缓冲区当做数组就很好理解了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值