NIO ByteBuffer实现原理 您所在的位置:网站首页 结构图中hb什么意思 NIO ByteBuffer实现原理

NIO ByteBuffer实现原理

2023-03-24 11:53| 来源: 网络整理| 查看: 265

前言

Java NIO 主要由下面3部分组成:

Buffer

Channel

Selector

在传统IO中,流是基于字节的方式进行读写的。

在NIO中,使用通道(Channel)基于缓冲区数据块的读写。

流是基于字节一个一个的读取和写入。

通道是基于块的方式进行读取和写入。

Buffer 类结构图

Buffer 的类结构图如下:

Buffer类结构图

从图中发现java中8中基本的类型,除了boolean外,其它的都有特定的Buffer子类。

Buffer类分析 Filed

每个缓冲区都有这4个属性,无论缓冲区是何种类型都有相同的方法来设置这些值

private int mark = -1; private int position = 0; private int limit; private int capacity; 1. 标记(mark)

初始值-1,表示未标记。

标记一个位置,方便以后reset重新从该位置读取数据。

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

缓冲区中读取或写入的下一个位置。这个位置从0开始,最大值等于缓冲区的大小

//获取缓冲区的位置 public final int position() { return position; } //设置缓冲区的位置 public final Buffer position(int newPosition) { if ((newPosition > limit) || (newPosition < 0)) throw new IllegalArgumentException(); position = newPosition; if (mark > position) mark = -1; return this; } 3. 限度(limit) //获取limit位置 public final int limit() { return limit; } //设置limit位置 public final Buffer limit(int newLimit) { if ((newLimit > capacity) || (newLimit < 0)) throw new IllegalArgumentException(); limit = newLimit; if (position > limit) position = limit; if (mark > limit) mark = -1; return this; } 4. 容量(capacity)

缓冲区可以保存元素的最大数量。该值在创建缓存区时指定,一旦创建完成后就不能修改该值。

//获取缓冲区的容量 public final int capacity() { return capacity; } filp 方法 public final Buffer flip() { limit = position; position = 0; mark = -1; return this; }

将limit设置成当前position的坐标

将position设置为0

取消标记

rewind 方法 public final Buffer rewind() { position = 0; mark = -1; return this; }

从源码中发现,rewind修改了position和mark,而没有修改limit。

将position设置为0

取消mark标记

clear 方法 public final Buffer clear() { position = 0; limit = capacity; mark = -1; return this; }

将position坐标设置为0

limit设置为capacity

取消标记

从clear方法中,我们发现Buffer中的数据没有清空,如果通过Buffer.get(i)的方式还是可以访问到数据的。如果再次向缓冲区中写入数据,他会覆盖之前存在的数据。

remaining 方法

查看当前位置和limit之间的元素数。

public final int remaining() { return limit - position; } hasRemaining 方法

判断当前位置和limit之间是否还有元素

public final boolean hasRemaining() { return position < limit; } ByteBuffer 类分析

ByteBuffer类结果图

从图中我们可以发现 ByteBuffer继承于Buffer类,ByteBuffer是个抽象类,它有两个实现的子类HeapByteBuffer和MappedByteBuffer类

HeapByteBuffer:在堆中创建的缓冲区。就是在jvm中创建的缓冲区。

MappedByteBuffer:直接缓冲区。物理内存中创建缓冲区,而不在堆中创建。

allocate 方法(创建堆缓冲区) public static ByteBuffer allocate(int capacity) { if (capacity < 0) throw new IllegalArgumentException(); return new HeapByteBuffer(capacity, capacity); }

我们发现allocate方法创建的缓冲区是创建的HeapByteBuffer实例。

HeapByteBuffer 构造 HeapByteBuffer(int cap, int lim) { // package-private super(-1, 0, lim, cap, new byte[cap], 0); }

从堆缓冲区中看出,所谓堆缓冲区就是在堆内存中创建一个byte[]数组。

allocateDirect创建直接缓冲区 public static ByteBuffer allocateDirect(int capacity) { return new DirectByteBuffer(capacity); }

我们发现allocate方法创建的缓冲区是创建的DirectByteBuffer实例。

DirectByteBuffer构造

DirectByteBuffer 构造方法

直接缓冲区是通过java中Unsafe类进行在物理内存中创建缓冲区。

wrap 方法 public static ByteBuffer wrap(byte[] array) public static ByteBuffer wrap(byte[] array, int offset, int length);

可以通过wrap类把字节数组包装成缓冲区ByteBuffer实例。

这里需要注意的的,把array的引用赋值给ByteBuffer对象中字节数组。如果array数组中的值更改,则ByteBuffer中的数据也会更改的。

get 方法

public byte get()获取position坐标元素,并将position+1;

public byte get(int i)获取指定索引下标的元素

public ByteBuffer get(byte[] dst)从当前position中读取元素填充到dst数组中,每填充一个元素position+1;

public ByteBuffer get(byte[] dst, int offset, int length)从当前position中读取元素到dst数组的offset下标开始填充length个元素。

put 方法

public ByteBuffer put(byte x)写入一个元素并position+1

public ByteBuffer put(int i, byte x)指定的索引写入一个元素

public final ByteBuffer put(byte[] src)写入一个自己数组,并position+数组长度

public ByteBuffer put(byte[] src, int offset, int length)从一个自己数组的offset开始length个元素写入到ByteBuffer中,并把position+length

public ByteBuffer put(ByteBuffer src)写入一个ByteBuffer,并position加入写入的元素个数

视图缓冲区

Paste_Image.png

ByteBuffer可以转换成其它类型的Buffer。例如CharBuffer、IntBuffer 等。

压缩缓冲区 public ByteBuffer compact() { System.arraycopy(hb, ix(position()), hb, ix(0), remaining()); position(remaining()); limit(capacity()); discardMark(); return this; }

1、把缓冲区positoin到limit中的元素向前移动positoin位

2、设置position为remaining()

3、 limit为缓冲区容量

4、取消标记

例如:ByteBuffer.allowcate(10);

内容:[0 ,1 ,2 ,3 4, 5, 6, 7, 8, 9]

compact前 [0 ,1 ,2 , 3, 4, 5, 6, 7, 8, 9] pos=4 lim=10 cap=10 compact后 [4, 5, 6, 7, 8, 9, 6, 7, 8, 9] pos=6 lim=10 cap=10 slice方法 public ByteBuffer slice() { return new HeapByteBuffer(hb, -1, 0, this.remaining(), this.remaining(), this.position() + offset); }

创建一个分片缓冲区。分配缓冲区与主缓冲区共享数据。

分配的起始位置是主缓冲区的position位置

容量为limit-position。

分片缓冲区无法看到主缓冲区positoin之前的元素。

直接缓冲区和堆缓冲区性能对比

下面我们从缓冲区创建的性能和读取性能两个方面进行性能对比。

读写性能对比 public static void directReadWrite() throws Exception { int time = 10000000; long start = System.currentTimeMillis(); ByteBuffer buffer = ByteBuffer.allocate(4*time); for(int i=0;i


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有