ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

NIO源码解析-Buffer简介

2021-09-04 19:59:56  阅读:127  来源: 互联网

标签:NIO Buffer buffer 源码 ByteBuffer put position byte


前言:

    java.nio包下的Buffer抽象类及其相关实现类,本质上是作为一个固定数量的容器来使用的。

    不同于InputStream和OutputStream时的数据容器byte[],Buffer相关实现类容器可以存储不同基础类型的数据,同时可以对容器中的数据进行检索,反复的操作。

    Buffer(缓冲区)的工作与Channel(通道)紧密相连。Channel是IO发生时的通过的入口(或出口,channel是双向的),而Buffer是这些数据传输的目标(或来源)。

1.Buffer基本属性

    // Invariants: mark <= position <= limit <= capacity
    // 标记地址,与reset搭配使用
    private int mark = -1;
    // 下一个要被读或写的元素的索引
    private int position = 0;
    // 容器现存元素的计数
    private int limit;
    // 容器总容量大小,在Buffer创建时被设定
    private int capacity;

    对于如下一段代码

// position=mark=0
// limit=capacity=10
ByteBuffer buffer = ByteBuffer.allocate(10);

    正是通过以上四个属性,实现了数据的反复操作。

2.Buffer的创建

    在Buffer的实现类中,使用最广泛的还是ByteBuffer,所以以下示例都是基于ByteBuffer(更具体说是HeapByteBuffer)来说明的,后续会有专门的文章来说明其他基本类型的使用及实现。

    根据ByteBuffer的API,我们可以看到以下四种创建方式:

// 1.直接分配capacity大小的Buffer,具体实现类型为HeapByteBuffer
public static ByteBuffer allocate(int capacity)
    
// 2.直接分配capacity大小的Buffer,具体实现类型为DirectByteBuffer
public static ByteBuffer allocateDirect(int capacity)
        
// 3.直接使用array作为底层数据
public static ByteBuffer wrap(byte[] array)
    
// 4.直接使用array作为底层数据,并且指定offset和length
public static ByteBuffer wrap(byte[] array,int offset, int length)

    另:有关于HeapByteBuffer和DirectByteBuffer,笔者会在...中会做详细讲解

2.1 allocate创建方式

ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.put((byte)'f');
buffer.put((byte)'i');
buffer.put((byte)'r');
buffer.put((byte)'e');

    具体存储图如下:

 2.2 wrap创建方式

// wrap(byte[] array,int offset, int length)
// position=offset
// limit=position+length
// capacity=array.length()
ByteBuffer byteBuffer = ByteBuffer.wrap("fire".getBytes(), 1, 2);

    具体存储图如下:

3.Buffer的基本操作方法

3.1 添加数据

ByteBuffer buffer = ByteBuffer.allocate(10);
// 1.逐字节存放 ByteBuffer put(byte b)
buffer.put((byte)'h');

// 2.字节存放到对应index ByteBuffer put(int index, byte b);
buffer.put(0,(byte)'h');

// 3.添加字节数组 ByteBuffer put(byte[] src)
byte[] bytes = {'h','e','l','l','o'};
buffer.put(bytes);

// 4.添加其他基础类型 ByteBuffer putInt(int x) ...
buffer.putInt(1);

3.2 获取数据

ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.put("hello".getBytes());

// 1.获取index下的数据
byte b = buffer.get(0);

// 2.逐个获取(需要先flip下,将position置为0)
buffer.flip();
for (int i = 0; i < buffer.remaining(); i++) {
    byte b1 = buffer.get();
}

// 3.将数据传输到bytes中
buffer.flip();
byte[] bytes = new byte[5];
ByteBuffer byteBuffer = buffer.get(bytes);

3.3 缓冲区翻转

    1)flip是一个比较重要也比较简单的方法,当我们使用put方法将Buffer填充满之后,此时调用get来获取Buffer中的数据时,会获取不到数据,由于get是从当前position来获取数据的,故需要先调用flip来将position置为0

// flip源码如下
// 比较简单,我们也可以手动设置 buffer.limit(buffer.position()).position(0);
public final Buffer flip() {
    limit = position;
    position = 0;
    mark = -1;
    return this;
}

    2)rewind 相对于flip方法而言,rewind也可以重复读取数据,唯一区别就是没有重新设置limit参数

// 源码如下
public final Buffer rewind() {
    position = 0;
    mark = -1;
    return this;
}

    上述两者之间有何不同呢,通过下面一个示例来说明下

ByteBuffer buffer = ByteBuffer.allocate(10);
// 执行完成执行position为4
buffer.put("fire".getBytes());

// 为了测试flip与rewind的不同,重新设置为3
buffer.position(3);

// flip后 pos=0 lim=3 cap=10
buffer.flip();
for (int i = 0; i < buffer.remaining(); i++) {
    byte b1 = buffer.get();
    System.out.println(b1);// 102 105
}

// 注意:需要单独测试,注释掉上述的flip相关代码
// rewind后 pos=0 limit=cap=10
buffer.rewind();
for (int i = 0; i < buffer.remaining(); i++) {
    byte b1 = buffer.get();
    System.out.println(b1);// 102 105 114 101
}

    总结:针对flip而言,flip之后的Buffer数据操作上限就是上次操作到的位置position

    而rewind,上限依旧是limit,可以重新操作全部数据

3.4 缓冲区压缩

    有时我们需要从缓冲区中释放已经操作过的数据,然后重新填充数据(针对未操作过的数据,我们是需要保留的)。

    我们可以将未操作过的数据(也就是position-limit之间的数据),重新拷贝到0位置,即可实现上述需求。而Buffer中已经针对这种场景实现了具体方法,也就是compact方法

// 示例如下
ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.put("fire".getBytes());

// 获取一个数据,position前移
buffer.flip();
buffer.get();

// 进行数据压缩
ByteBuffer compact = buffer.compact();

    压缩前的buffer:

     压缩后的buffer:

    相比较而言:将原position到limit之间的数据(1-4,也就是i r e)拷贝到index=0位置,position也就是3,后续新写入数据直接覆盖原position=3的位置数据。

    

3.5 标记与重置

    mark和reset方法,mark用来做标记,reset用来调回到做标记的位置。比较简单,直接看示例

ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.put("fire".getBytes());

// 直接将position置为2
buffer.position(2);

// 做标记,在position=2位置做标记
buffer.mark();

// 获取position=2的数据,
byte b = buffer.get(); // 114
// 获取下一个position的数据后,执行reset,然后重新获取数据,发现是同一个数据
buffer.reset();
byte c = buffer.get(); // 114

    经过reset操作后,position重新回到2,也就是mark时的position,故两次get方法获取的是同一个position的值

3.6 复制

    Buffer还提供了快速复制一个Buffer的功能

ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.put("fire".getBytes());

// 复制buffer
ByteBuffer duplicate = buffer.duplicate();

    复制后的duplicate与buffer共享源数据数组,只是拥有不同的position、limit

总结:

    Buffer作为数据存储的容器,其有很多的实现类和API,本文中对其基本API进行了分析,后续我们继续对其实现类进行分析。

标签:NIO,Buffer,buffer,源码,ByteBuffer,put,position,byte
来源: https://blog.csdn.net/qq_26323323/article/details/120104627

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有