【JavaNIO】缓冲区Buffer

学习内容主要基于B站UP主,青空の霞光的视频,加上自己的理解。

缓冲区

Buffer类及其实现

Buffer类是缓冲区的实现,类似于Java中的数组,用于存放和获取数组。但是Buffer相比Java中的数组,功能更加强大,包含一系列对于数组的快捷操作。

Buffer是一个抽象类,其核心包括:

public abstract class Buffer {
   private int mark = -1;
   private int position = 0;  //指针
   private int limit;		  //缓冲区最大限制
   private int capacity;
    
   //缓冲区实现子类的数据内存地址
   long address;

Buffer有多个子类,包括所有基本类型(除了boolean)

  • IntBuffer:int类型的缓冲区
  • LongBuffer:long类型的缓冲区

StringBuffer不属于Buffer体系

如何创建一个IntBuffer?

//创建一个缓冲区无法直接new,而是需要使用静态方法生成,有两种方式
//1.申请一个容量为10的缓冲区
IntBuffer buffer = IntBuffer.allocate(10);

//2.将现有数组直接转为缓冲区
int[] arr = new int[]{1,2,3,4,5};
IntBuffer buffer2 = IntBuffer.wrap(arr);

System.out.println(buffer2.get(1));  //2
buffer2.put(1, 9);
System.out.println(buffer2.get(1));  //9

IntBuffer.allocate()实现

IntBuffer的内部实现:

public static IntBuffer allocate(int capacity) {
    if (capacity < 0) { //判断容量
        throw createCapacityException(capacity);
    } else {
        //创建一个新的IntBuffer实现类
        //HeapIntBuffer是在堆内存中存放数据,本质上是数组
        return new HeapIntBuffer(capacity, capacity, (MemorySegmentProxy)null);
    }
}

HeapIntBuffer的实现

HeapIntBuffer(int[] buf, int off, int len) { //不是public是默认权限
    //调用父类IntBuffer的构造方法
    //mark:标记,默认值-1
    //off:当下起始标志
    //off+len:最大下标位置
    //buf.length:底层维护数组真正长度
    //0:起始偏移位置
    super(-1, off, off + len, buf.length, buf, 0);
    this.address = ARRAY_BASE_OFFSET;
}

父类IntBuffer的构造方法

final int[] hb; 	//数据最终存在hb中
final int offset; 	//默认为0
boolean isReadOnly;  //默认为 false

IntBuffer(int mark, int pos, int lim, int cap, int[] hb, int offset) {
    //调用Buffer类的构造方法
    super(mark, pos, lim, cap);
    //hb是真正要存放数据的数组,堆缓冲区的底层就是这个数组
    this.hb = hb;
    //起始偏移位置
    this.offset = offset;
}

Buffer的构造方法

//position是指针
Buffer(int mark, int pos, int lim, int cap) {
    if (cap < 0) { //容量不能小于0
        throw createCapacityException(cap);
    } else {
        this.capacity = cap; //缓冲区容量
        this.limit(lim); //设定最大position位置
        this.position(pos); //设定起始位置
        if (mark >= 0) { //标记>=0
            if (mark > pos) { //标记位置大于起始位置,报错
                throw new IllegalArgumentException("mark > position: (" + mark + " > " + pos + ")");
            }
			//否则设置标记位置
            this.mark = mark;
        }

    }
}

在这里插入图片描述

缓冲区写操作

上面是Buffer类的基本操作,如何向缓冲区中存放数据以及获取数据:

数据的存放包括下面四个方法:

  • public abstract IntBuffer put(int i):在当前position位置插入数据,具体由子类实现
  • public abstract IntBuffer put(int index, int i):在指定位置存放数据,由具体子类实现
  • public final IntBuffer put(int[] src):直接存放所有数组中的内容(数组长度不能超出缓冲区大小)
  • public IntBuffer put(int[] src, int offset, int length):直接存放数组中的内容,可以指定范围
  • public IntBuffer put(IntBuffer src):直接存放另一个缓冲区的内容

举例:

  1. 第一个put:在当前位置插入一个数据,会在后边一直插入
IntBuffer buffer = IntBuffer.allocate(10);

buffer.put(1);
buffer.put(2);
//buffer.array()是用来获取hb数组的
System.out.println(Arrays.toString(buffer.array())); //[1, 2, 0, 0, 0, 0, 0, 0, 0, 0]
  1. 第二个put:可以指定往某个位置
IntBuffer buffer = IntBuffer.allocate(10);

buffer.put(1, 888);
System.out.println(Arrays.toString(buffer.array())); //[0, 888, 0, 0, 0, 0, 0, 0, 0, 0]
  1. 第三个put:可以传入Int数组,会依次将数组中的元素传入缓冲区
IntBuffer buffer = IntBuffer.allocate(10);

int[] arr = new int[]{111,222,333};
buffer.put(arr);
System.out.println(Arrays.toString(buffer.array())); //[111, 222, 333, 0, 0, 0, 0, 0, 0, 0]
  1. 第四个put:
IntBuffer buffer = IntBuffer.allocate(10);

int[] arr = new int[]{111,222,333};
buffer.put(arr, 1, 2); //从1号位开始,读2个
System.out.println(Arrays.toString(buffer.array())); //[222, 333, 0, 0, 0, 0, 0, 0, 0, 0]
  1. 第五个put
IntBuffer buffer = IntBuffer.allocate(10);

IntBuffer src = IntBuffer.wrap(new int[]{1,2,3,4,5});
buffer.put(src);
System.out.println(Arrays.toString(buffer.array())); //[1, 2, 3, 4, 5, 0, 0, 0, 0, 0]

若缓冲区容量不够会报错

丢进缓冲区的数据会在缓冲区已有数据之后插入


put操作底层源码

对于public abstract IntBuffer put(int i),在IntBuffer中只是定义出来了,其具体的实现是在其子类比如HeapIntBuffer中实现的

   public IntBuffer put(int x) {
      this.hb[this.ix(this.nextPutIndex())] = x; //给hb数组中position + offset位置赋值为x
      return this;
   }
   final int nextPutIndex() {
      int p = this.position; //获取position,开始时为0,记录下一个要操作的数的位置
      if (p >= this.limit) { //position不能超过底层数组最大长度
         throw new BufferOverflowException();
      } else {
         this.position = p + 1; //Buffer类中的position自增
         return p; //返回自增前的p,即当前位置
      }
   }
   protected int ix(int i) {
      return i + this.offset; //返回i加偏移量,offset默认为0
   }

在这里插入图片描述
对于public abstract IntBuffer put(int index, int i)

   public IntBuffer put(int i, int x) {
      this.hb[this.ix(this.checkIndex(i))] = x; 
      return this;
   }
   final int checkIndex(int i) {
      if (i >= 0 && i < this.limit) { //limit是position最远能到达的位置
         return i;  
      } else {
         throw new IndexOutOfBoundsException();
      }
   }

对于public final IntBuffer put(int[] src)

   public final IntBuffer put(int[] src) {
      return this.put(src, 0, src.length); //从0到length
   }   

   public IntBuffer put(int[] src, int offset, int length) {
      if (this.isReadOnly()) {
         throw new ReadOnlyBufferException();
      } else {
          //检查截取范围是否合法
         Objects.checkFromIndexSize(offset, length, src.length); 
         int pos = this.position();
          //判断要插入的数据在缓冲区是否能装下
         if (length > this.limit() - pos) { //最大容量 - 当前位置,剩下为剩余空间
            throw new BufferOverflowException();
         } else {
             //计算出最终读取位置,从position开始插入
            this.putArray(pos, src, offset, length); 
            this.position(pos + length);
            return this;
         }
      }
   }
   private IntBuffer putArray(int index, int[] src, int offset, int length) {
      if ((long)length << 2 > 6L) {
         long bufAddr = this.address + ((long)index << 2);
         long srcOffset = ARRAY_BASE_OFFSET + ((long)offset << 2);
         long len = (long)length << 2;

         try {
            if (this.order() != ByteOrder.nativeOrder()) {
               SCOPED_MEMORY_ACCESS.copySwapMemory((Scope)null, this.scope(), src, srcOffset, this.base(), bufAddr, len, 4L);
            } else {
               SCOPED_MEMORY_ACCESS.copyMemory((Scope)null, this.scope(), src, srcOffset, this.base(), bufAddr, len);
            }
         } finally {
            Reference.reachabilityFence(this);
         }
      } else {
          //计算出最终读取位置,
         int end = offset + length;
         int i = offset;

         for(int j = index; i < end; ++j) {
            this.put(j, src[i]); //直接从position位置开始继续写入,直到指定范围结束
            ++i;
         }
      }

对于第五个put public IntBuffer put(IntBuffer src)

   public IntBuffer put(IntBuffer src) {
      if (src == this) {
         throw createSameBufferException();
      } else if (this.isReadOnly()) { //只读不允许插入操作
         throw new ReadOnlyBufferException();
      } else {
         int srcPos = src.position(); //src当前位置
         int srcLim = src.limit(); //src限制大小
         int srcRem = srcPos <= srcLim ? srcLim - srcPos : 0; //src剩余容量
         int pos = this.position();
         int lim = this.limit();
         int rem = pos <= lim ? lim - pos : 0; //当前剩余容量
         if (srcRem > rem) { //判断当前剩余容量是否小于src容量
            throw new BufferOverflowException();
         } else {
             //从position位置开始继续写入,其中通过get方法一个一个读取数据出来
            this.putBuffer(pos, src, srcPos, srcRem);
            this.position(pos + srcRem);
            src.position(srcPos + srcRem);
            return this;
         }
      }
   }

缓冲区读操作

读操作有四个方法:

`public abstract int get()`
    `public abstract int get(int index)`
    `public IntBuffer get(int[] dst)`
    `public IntBuffer get(int[] dst, int offset, int length)`
  • public abstract int get():直接获取当前position位置的数据
    public int get() {
        return hb[ix(nextGetIndex())];
    }
    final int nextGetIndex() {                          // package-private
        if (position >= limit)
            throw new BufferUnderflowException();
        return position++;
    }
  • public abstract int get(int index):获取指定位置的数据
  • public IntBuffer get(int[] dst):将数据读取到给定的数组中
int[] arr = new int[]{1,2,3,4,5};
IntBuffer buffer = IntBuffer.wrap(arr);

int[] arr1 = new int[3];
buffer.get(arr1); //将缓冲区中的数据读到arr1中

System.out.println(Arrays.toString(arr1)); //[1, 2, 3]
    public IntBuffer get(int[] dst) {
        return get(dst, 0, dst.length); 
    }
   public IntBuffer get(int[] 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(); //开始从position把数据读到数组中,从数组的offset开始
        return this;
    }

如果需要直接获取数组,可以使用array()方法拿到

    public final int[] array() {
        if (hb == null) //为空说明底层不是数组实现的
            throw new UnsupportedOperationException();
        if (isReadOnly) //只读
            throw new ReadOnlyBufferException();
        return hb; //直接返回hb
    }

array()拿到的底层数组,所有修改会直接生效在缓冲区中

int[] arr = new int[]{1,2,3,4,5};
IntBuffer buffer = IntBuffer.wrap(arr);

int[] array = buffer.array();
//拿到数组对象直接在缓冲区改
array[0] = 9999;
System.out.println(buffer.get());
System.out.println(Arrays.toString(array));

除了常规读取方式,还可以通过mark()实现跳转读取

  • public final Buffer mark():标记当前位置
    public final Buffer mark() {
        mark = position;
        return this;
    }
  • public final Buffer reset():让当前的posotion跳转到mark标记的位置
    public final Buffer reset() {
        int m = mark;
        if (m < 0)
            throw new InvalidMarkException();
        position = m;
        return this;
    }
  • public IntBuffer get(int[] dst, int offset, int length)

缓冲区其他操作

  • public abstract IntBuffer compact():压缩缓冲区

将整个缓冲区的大小和数据内容变为position到limit之间的数据,并移动到数组头部

    public IntBuffer compact() {
		//将hb数组当前的position位置的数据拷贝到头部去,将长度改成刚计算出来的空间
        //remaining():计算position距离最大位置的长度 lim - pos
        System.arraycopy(hb, ix(position()), hb, ix(0), remaining());
        position(remaining()); //将pos移动到rem
        limit(capacity()); //pos位置改为最大容量
        discardMark(); //mark变为-1
        return this;
    }

在这里插入图片描述

public static void main(String[] args) {
    IntBuffer buffer = IntBuffer.wrap(new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 0});
    for (int i = 0; i < 4; i++) {
        buffer.get(); //先读4个
    }
    //压缩缓冲区
    buffer.compact();

    System.out.println("压缩后:" + Arrays.toString(buffer.array()));
    System.out.println("当前position:" + buffer.position());
    System.out.println("当前limit:" + buffer.limit());
}
压缩后:[5, 6, 7, 8, 9, 0, 7, 8, 9, 0]
当前position:6
当前limit:10
  • public IntBuffer duplicate:复制缓冲区,创建一个新的数据相同的缓冲区
    public IntBuffer duplicate() {
        return new HeapIntBuffer(hb,
                                        this.markValue(),
                                        this.position(),
                                        this.limit(),
                                        this.capacity(),
                                        offset);
    }

在复制后,修改原buffer的内容,duplicate的内容也会变。这是因为他们底层使用的是同一个数组

    public static void main(String[] args) {
        IntBuffer buffer = IntBuffer.wrap(new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 0});

        IntBuffer duplicate = buffer.duplicate();

        buffer.put(0, 999);
        System.out.println(duplicate.get()); //999
    }
  • public abstract IntBuffer slice:划分缓冲区,将原有缓冲区划分成更小的
    public IntBuffer slice() {
        return new HeapIntBuffer(hb,	//返回一个新的划分出的缓冲区,但底层数组用的还是同一个
                                 -1, //mark
                                 0, //pos
                                 this.remaining(), //新容量变为剩余空间大小
                                 this.remaining(),
                                 this.position() + offset);//offset地址=当前pos+原有offset
    }

    public final int remaining() {
        return limit - position;
    }
  • public final Buffer rewind():将position归零,mark变为-1
  • public final Buffer clear():清空缓冲区

缓冲区比较操作

缓冲区可以进行比较,equals方法和compareTo方法都重写过

  • equals方法
    public boolean equals(Object ob) {
        if (this == ob) //是同一对象
            return true;
        if (!(ob instanceof IntBuffer)) //类型是否相同
            return false;
        IntBuffer that = (IntBuffer)ob; //转为IntBuffer
        if (this.remaining() != that.remaining()) //剩余容量是否相同
            return false;
        //开始比较剩余内容
        int p = this.position();
        //从最后一个倒着往回读
        for (int i = this.limit() - 1, j = that.limit() - 1; i >= p; i--, j--)
            if (!equals(this.get(i), that.get(j))) //发现问题直接false
                return false;
        return true;
    }
  • compareTo方法
    public int compareTo(IntBuffer that) {
        //选一个剩余空间最小的
        //n为当前pos + 剩余的最小空间
        int n = this.position() + Math.min(this.remaining(), that.remaining());
        //从两个缓冲区当前位置开始,到n
        for (int i = this.position(), j = that.position(); i < n; i++, j++) {
            int cmp = compare(this.get(i), that.get(j)); //比较
            if (cmp != 0) 
                return cmp; //当有不同的,就返回比较出来的值
        }
        return this.remaining() - that.remaining(); //内容一样,就比长度
    }

只读缓冲区

只读缓冲区,只能对其进行读操作,不能进行写操作

public abstract IntBuffer asReadOnlyBuffer():基于当前缓冲区生成一个只读缓冲区

    public IntBuffer asReadOnlyBuffer() {

        return new HeapIntBufferR(hb,
                                     this.markValue(),
                                     this.position(),
                                     this.limit(),
                                     this.capacity(),
                                     offset);
    }

class HeapIntBufferR extends HeapIntBuffer {
    
        protected HeapIntBufferR(int[] buf,
                                   int mark, int pos, int lim, int cap,
                                   int off)
    {
        super(buf, mark, pos, lim, cap, off);
        this.isReadOnly = true;
    }

在这里插入图片描述

ByteBuffer和CharBuffer

之前一直使用IntBuffer。

ByteBuffer底层存放的是多个单byte字节

ByteBuffer

public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer> {

    final byte[] hb;                  // Non-null only for heap buffers
    final int offset;
    boolean isReadOnly;       

直接缓冲区

之前是堆缓冲区,即数据是保存在一个数组中的。占用的是堆内存

可以创建直接缓冲区,申请堆外内存进行数据保存,采用操作系统本地的IO,相比堆缓冲区快

使用allocateDirect()方法创建直接缓冲区

public static void main(String[] args) {
    //申请直接缓冲区
    ByteBuffer buffer = ByteBuffer.allocateDirect(10);

    buffer.put((byte) 66);
    buffer.flip();
    System.out.println(buffer.get());
}

底层源码

    public static ByteBuffer allocateDirect(int capacity) {
        return new DirectByteBuffer(capacity);
    }

DirectByteBuffer并非直接继承自ByteBuffer,而是MappedByteBuffer,并实现了DirectBuffer接口

在这里插入图片描述
DirectBuffer

public interface DirectBuffer {
    long address(); //获取内存地址

    Object attachment(); //附加对象,为了保证某些情况下内存不被释放

    Cleaner cleaner(); //内存清理类
}

DirectByteBuffer类的成员变量

    // 取出unsafe类
    protected static final Unsafe unsafe = Bits.unsafe();

    // 在内存中直接创建内存空间地址
    private static final long arrayBaseOffset = (long)unsafe.arrayBaseOffset(byte[].class);

    // 是否有非对齐访问能力,根据CPU架构而定,inter amd apple都支持
    protected static final boolean unaligned = Bits.unaligned();

	// 直接缓冲区内存地址,为了提升速度放到Buffer类中
    //    protected long address;

    // An object attached to this buffer. If this buffer is a view of another
    // buffer then we use this field to keep a reference to that buffer to
    // ensure that its memory isn't freed before we are done with it.
	// 附加对象
    private final Object att;

DirectByteBuffer构造方法

DirectByteBuffer(int cap) {                   // package-private

        super(-1, 0, cap, cap);
        boolean pa = VM.isDirectMemoryPageAligned(); //是否直接内存分页对齐,需要额外计算
        int ps = Bits.pageSize();
    	//计算最终需要申请的大小
        long size = Math.max(1L, (long)cap + (pa ? ps : 0));
    	// 判断堆外内存是否足够,足够则保留内存
        Bits.reserveMemory(size, cap);

        long base = 0;
        try {
            // 通过unsafe申请内存空间,并得到内存地址
            base = unsafe.allocateMemory(size);
        } catch (OutOfMemoryError x) {
            // 申请失败就取消保留内存
            Bits.unreserveMemory(size, cap);
            throw x;
        }
    	//将申请的内存每个字节都设为0
        unsafe.setMemory(base, size, (byte) 0);
        if (pa && (base % ps != 0)) {
            // Round up to page boundary
            address = base + ps - (base & (ps - 1));
        } else {
            // 将address变量设定为base的地址
            address = base;
        }
    	//创建针对于此缓冲区的cleaner,进行内存清理
        cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
        att = null;
    }

在构造方法中,通过unsafe类来申请足够的堆外内存保存数据。当不使用此缓冲区时,使用Cleaner进行清理

public class Cleaner extends PhantomReference<Object> {
    //引用队列
    private static final ReferenceQueue<Object> dummyQueue = new ReferenceQueue();
    
    //Cleaner采用双向链表,每创建一个Cleaner对象都会添加一个节点
    private static Cleaner first = null;
    private Cleaner next = null;
    private Cleaner prev = null;
    
    //执行清理的具体流程
    private final Runnable thunk;
    
    //添加操作,新对象变为头节点
    private static synchronized Cleaner add(Cleaner var0) {
        if (first != null) {
            var0.next = first;
            first.prev = var0;
        }

        first = var0;
        return var0;
    }

    private static synchronized boolean remove(Cleaner var0) {
        if (var0.next == var0) {
            return false;
        } else {
            if (first == var0) {
                if (var0.next != null) {
                    first = var0.next;
                } else {
                    first = var0.prev;
                }
            }

            if (var0.next != null) {
                var0.next.prev = var0.prev;
            }

            if (var0.prev != null) {
                var0.prev.next = var0.next;
            }

            var0.next = var0;
            var0.prev = var0;
            return true;
        }
    }

    // 创建鬼引用的对象就是传进的缓冲区对象
    private Cleaner(Object referent, Runnable thunk) {
        super(referent, dummyQueue);
        this.thunk = thunk;
    }

    //创建新的Cleaner
    public static Cleaner create(Object var0, Runnable var1) {
        //通过add,将Cleaner添加到队列
        return var1 == null ? null : add(new Cleaner(var0, var1));
    }
	
    //清理操作
    public void clean() {
        if (remove(this)) {
            try {
                this.thunk.run(); //具体操作
            } catch (final Throwable var2) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        if (System.err != null) {
                            (new Error("Cleaner terminated abnormally", var2)).printStackTrace();
                        }

                        System.exit(1);
                        return null;
                    }
                });
            }

        }
    }

具体的清理程序

    private static class Deallocator implements Runnable {

        private static Unsafe unsafe = Unsafe.getUnsafe();

        private long address; //内存地址
        private long size; //大小
        private int capacity; //申请的容量

        private Deallocator(long address, long size, int capacity) {
            assert (address != 0);
            this.address = address;
            this.size = size;
            this.capacity = capacity;
        }

        public void run() {
            if (address == 0) {
                // Paranoia
                return;
            }
            // 调用unsafe进行内存释放操作
            unsafe.freeMemory(address); 
            //内存地址改为 0, null
            address = 0;
            //取消开始的保留内存
            Bits.unreserveMemory(size, capacity);
        }

    }

清理时也是调用Unsafe类进行内存释放操作。

  • 对于普通的堆缓冲区,一旦对象没有引用,就可以随时被GC回收

  • 对于堆外内存,只能手动进行内存回收。当DirectByteBuffer也失去引用,也会触发内存回收

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值