Java NIO之Buffer详细理解

刺骨的言语ヽ痛彻心扉 2023-02-17 03:16 270阅读 0赞

Buffer**简介**

Buffer:是一个指定特定数据类型的容器,主要用于和Channel进行数据交互。在多线程操作下 Buffer 是不安全的。

在Java NIO中使用的核心缓冲区如下(覆盖了通过I/O发送的基本数据类型:byte, char、short, int, long, float, double, long):

ByteBuffer

CharBuffer

ShortBuffer

IntBuffer

FloatBuffer

DoubleBuffer

LongBuffer

Buffer的基本属性

capacity(容量) :缓冲区能够容纳的数据元素的最大数量。

limit(界限):缓冲区中第一个不能读或写的元素位置

position(位置):下一个读或写的元素的索引。位置会随着get()和put()函数更新

mark(标记):标记一个备忘的位置

以上四个属性的关系:

mark <= position <= limit <= capacity

Buffer**使用**

接下来以最常用的ByteBuffer为例进行介绍

1.创建Buffer对象

  1. public static ByteBuffer allocate(int capacity) //静态方法,通过内存分配创建Buffer对象
  2. public static ByteBuffer wrap(byte[] array) //静态方法,将[]byte包装成ByteBuffer对象
  3. public static ByteBuffer wrap(byte[] array,int offset, int length) //基本同上,但指定了初始位置和长度
  4. public static ByteBuffer allocateDirect(int capacity) //通过分配直接缓冲区创建Buffer对象

非直接缓冲区:通过allocate()分配缓冲区,将缓冲区建立在JVM的内存中

直接缓冲区:通过allocateDirect()分配直接缓冲区,将缓冲区建立在物理内存中,可以提高效率。

内核地址空间和用户地址空间之间形成了一个物理内存映射文件,减少了内核到用户空间的copy过程

public abstract boolean isDirect() //可以通过该方法判断是否是直接缓冲区

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NraDIwMTVqYXZh_size_16_color_FFFFFF_t_70

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NraDIwMTVqYXZh_size_16_color_FFFFFF_t_70 1

2.读写常用的方法

在Buffer中有两种模式,一种是写模式,一种是读模式。

put()相关函数:向ByteBuffer中添加元素(byte,[]byte,ByteBuffer等)

flip()函数:将Buffer从写模式切换到读模式。调用flip()方法会将position设回0,并将limit设置成之前position的值。

get():从Buffer中读取元素

hasRemaining():判断Buffer中是否还有元素可读

clear():清空元素

compact():压缩元素(扩展空间)

利用Buffer读写数据,通常遵循四个步骤:

调用put()把数据写入buffer;

调用flip()从写模式切换成读模式;

调用get()从Buffer中读取数据;

调用buffer.clear()或者buffer.compact() 清除元素

Buffer**源码**

构造器

  1. //构造函数,根据指定的参数来初始化Buffer特定的属性
  2. //此构造函数是包私有的
  3. Buffer(int mark, int pos, int lim, int cap) { // package-private
  4. if (cap < 0)
  5. throw new IllegalArgumentException("Negative capacity: " + cap);
  6. this.capacity = cap;
  7. limit(lim);
  8. position(pos);
  9. if (mark >= 0) {
  10. if (mark > pos)
  11. throw new IllegalArgumentException("mark > position: ("
  12. + mark + " > " + pos + ")");
  13. this.mark = mark;
  14. }
  15. }

构造器中使用了两个方法limit()和position()

  1. 函数的功能:设置Bufferlimit,如果position大于newLimit,则将position设置为新的limit
  2. 如果mark被定义且大于新的limit,则就会被抛弃。
  3. public final Buffer limit(int newLimit) {
  4. //有效性检查,即limit必须满足这样的关系:0<=limit<=position.
  5. if ((newLimit > capacity) || (newLimit < 0))
  6. throw new IllegalArgumentException();
  7. limit = newLimit;
  8. //如果position大于newLimit,则将position设置为新的limit。
  9. if (position > limit) position = limit;
  10. //如果mark被定义且大于新的limit,则会被抛弃(即设置为-1)
  11. if (mark > limit) mark = -1;
  12. return this;
  13. }

函数功能:设置Buffer的position.如果mark被定义且大于new position,则就会被抛弃。

  1. public final Buffer position(int newPosition) {
  2. //有效性检查,即0<=newPosition<=limit.
  3. if ((newPosition > limit) || (newPosition < 0))
  4. throw new IllegalArgumentException();
  5. position = newPosition;
  6. //如果mark被定义且大于new position,则就会被抛弃。
  7. if (mark > position) mark = -1;
  8. return this;
  9. }

allocate()方法

  1. 由于Buffer类是一个抽象类,是不可以实例化对象的,因此在Buffer中是不存在allocate(int cap)方法的,allocate(int cap)方法在其子类中均有实现。这里就以IntBuffer为例,看下Buffer子类IntBufferallocate(int cap)方法。
  2. public static IntBuffer allocate(int capacity) {
  3. if (capacity < 0)
  4. throw new IllegalArgumentException();
  5. return new HeapIntBuffer(capacity, capacity);
  6. }

函数功能:分配一个新的用来装载int类型数据的Buffer对象实例。这个new buffer的position为0,limit为capacity,mark为未定义的(即为-1)。buffer中的元素全部初始化为零。

在allocate方法中直接是实例化了一个IntBuffer子类的对象。

既然这里涉及要HeapIntBuffer类,我们就看下这个类

  1. public abstract class IntBuffer extends Buffer implements Comparable<IntBuffer>
  2. class HeapIntBuffer extends IntBuffer

从继承关系我们知道:HeapIntBuffer这个类是IntBuffer的子类

这个类的构造函数为

  1. HeapIntBuffer(int cap, int lim) { // package-private
  2. super(-1, 0, lim, cap, new int[cap], 0);
  3. }

在这个构造函数中直接调用了父类IntBuffer中对应的构造函数。看下IntBuffer类中的这个构造函数。

在看构造函数之前,这里要说下IntBuffer类中的几个属性。

  1. final int\[\] hb; // Non-null only for heap buffers
  2. final int offset;
  3. boolean isReadOnly; // Valid only for heap buffers

IntBuffer类中主要包括一个int类型的数组,即从这里我们知道,Buffer类的底层数据结构是借助于数组来完成的。

继续看IntBuffer类的构造方法

  1. // Creates a new buffer with the given mark, position, limit, capacity,
  2. // backing array, and array offset
  3. IntBuffer(int mark, int pos, int lim, int cap, // package-private
  4. int[] hb, int offset)
  5. {
  6. super(mark, pos, lim, cap);//调用父类Buffer中对应有参的构造函数
  7. this.hb = hb;
  8. this.offset = offset;
  9. }

以上,就是当我们使用如下代码得到IntBuffer实例的整个过程。

IntBuffer buffer = IntBuffer.allocate(cap);

2、put(int i)方法介绍

下面来看IntBuffer类中的put方法

  1. public abstract IntBuffer put(int i);
  2. public abstract int get(int index);

在IntBuffer类中put、get方法都是抽象的。

有了上面allocate方法的过程分析,我们知道IntBuffer buffer = IntBuffer.allocate(cap)

中的buffer实际上是父类的引用指向的是子类的对象。当我们使用buffer.put(value)/buffer.get().

实际上是调用子类HeapIntBuffer类中的put/get方法,这就是多态。在Java中相当重要的一个特征。

HeapIntBuffer类中put方法的实现如下:

  1. public IntBuffer put(int x) {
  2. hb[ix(nextPutIndex())] = x;
  3. return this;
  4. }

put方法实现的思想就是:将值存储在position位置即可。

这里涉及到两个新的函数,分别为:

1、nextPutIndex(),函数功能:简单来说就是返回下一个要写入元素的索引位置(即当前position值),并将position进行加一操作。

2、ix(int i):偏移offset个位置

这两个函数的实现如下:

  1. //函数功能:首先检查当前的position是否小于limit,如果小于则存储,否则抛异常。
  2. final int nextPutIndex() { // package-private
  3. if (position >= limit)
  4. throw new BufferOverflowException();
  5. return position++;
  6. }
  7. //函数功能:偏移offset个位置
  8. protected int ix(int i) {
  9. return i + offset;
  10. }

3、get()方法介绍

看完了put方法,接下来来看下get方法

函数的功能:取得Buffer中position位置所指向的元素。

  1. //函数功能:获取buffer中position所指向的元素。
  2. public int get() {
  3. return hb[ix(nextGetIndex())];
  4. }
  5. //函数功能:获取buffer中索引为i位置的元素
  6. public int get(int i) {
  7. return hb[ix(checkIndex(i))];
  8. }

在get()方法中也涉及到两个另外的函数:

1、nextGetIndex(),函数功能:返回下一个读取的元素的索引位置(即position值)

2、ix(int i)

  1. final int nextGetIndex() { // package-private
  2. if (position >= limit)
  3. throw new BufferUnderflowException();
  4. return position++;
  5. }
  6. //将position向右移动nb个位置,这个函数目前还没有看见在哪里得到的应用
  7. final int nextGetIndex(int nb) { // package-private
  8. if (limit - position < nb)
  9. throw new BufferUnderflowException();
  10. int p = position;
  11. position += nb;
  12. return p;
  13. }

以上就是get方法的内部实现,也相当简单哈。

4、flip()方法介绍

其实,在刚开始看别人博客的时候,是最不能理解这个函数的功能的,疑问在于:为什么要在读Buffer中的内容时,要先调用这个方法呢。

其实在自己把Buffer的读模式和写模式弄清楚之后,这个函数的功能我也就明白了。

  1. //函数功能:将Buffer从写模式转化为读模式。
  2. //具体为:将limit设置为此时position并且将position设置为零。如果mark被定义了则被抛弃(即设置为-1)
  3. //这个方法的应用场景为:在管道读或调用put方法之后,调用此方法来为管道写或者是get操作做准备。
  4. public final Buffer flip() {
  5. limit = position;
  6. position = 0;
  7. mark = -1;
  8. return this;
  9. }

以上就是flip()方法的介绍,是不是也比较简单哈。

5、hasRemaining()介绍

函数功能:判断Buffer中是否还有可读取的元素。

  1. public final boolean hasRemaining() {
  2. return position < limit;
  3. }

此方法的内部实现直接是判断position是否小于limit.

所以,逻辑也是相当简单的。

6、clear() compact()方法的介绍

clear()函数功能:清空Buffer中所有的元素。

其内部实现直接是将Buffer里面几个属性进行了重置。

  1. public final Buffer clear() {
  2. position = 0;
  3. limit = capacity;
  4. mark = -1;
  5. return this;
  6. }

最后看下compact方法

compact()方法的功能:将已读元素进行清除,未读元素拷贝保留并拷贝到Buffer最开始位置。这个也是和clear()方法不同的地方。

具体实现如下:

  1. public IntBuffer compact() {
  2. //先进行拷贝,即将剩余的没有访问的元素拷贝到Buffer从零开始的位置
  3. System.arraycopy(hb, ix(position()), hb, ix(0), remaining());//remaining()函数返回的是剩余元素个数
  4. //设置position为下一个写入元素的位置
  5. position(remaining());
  6. //设置limit为Buffer容量
  7. limit(capacity());
  8. discardMark();//废弃mark,即将mark设置为-1
  9. return this;
  10. }
  11. public final int remaining() {
  12. return limit - position;
  13. }

以上内容主要参考

《Java NIO》

https://blog.csdn.net/u010412719/article/details/52775637

https://blog.csdn.net/u010853261/article/details/53464397

https://www.cnblogs.com/KKSJS/p/9909587.html

https://www.cnblogs.com/snailclimb/p/Buffer.html

发表评论

表情:
评论列表 (有 0 条评论,270人围观)

还没有评论,来说两句吧...

相关阅读

    相关 NIOBuffer

    Buffer 其实在Buffer中官方的javadoc中已经讲得非常清楚了,本篇博客是本人做为笔记使用。 下面是我截取的javadoc很详细的介绍了关于mark,po

    相关 Java NIOSelector详细理解

    介绍        Selector一般称为选择器。它是Java NIO核心组件之一,选择器管理着一个被注册的通道集合的信息和它们的就绪状态。通道是和选择器一起被注册的,

    相关 Java NIOChannel详细理解

    介绍 理解:通道是一个连接I/O服务的管道并提供与该服务交互的方法。 Channel类似于传统的”流”,但是Channel不能直接访问数据,需要和缓冲区Buffer进行

    相关 Java NIOBuffer

    缓冲区基础 本质上,缓冲区是就是一个数组。所有的缓冲区都具有四个属性来提供关于其所包含的数组的信息。它们是: 容量(Capacity) 缓冲区能够容纳的数据元素的

    相关 NIOBuffer

    > Java NIO中的Buffer用于和NIO通道进行交互。数据是从通道读入缓冲区,从缓冲区写入到通道中的。缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内

    相关 java NIObuffer

    Java NIO中的Buffer用于和NIO通道进行交互。如你所知,数据是从通道读入缓冲区,从缓冲区写入到通道中的。 缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内