【JAVA】【源码学习】ByteBuffer

摘要

最近看Netty相关的源码,Netty自己封装了一套完整、复杂的buffer功能,但是用到java nio,则必须转换为ByteBuffer,于是先学习ByteBuffer,才能更深入的理解ByteBuf。

Buffer

虽然实际使用中最常见到的的是ByteBuffer,但是常用的位置相关操作都封装在抽象类Buffer中,比如mark、flip之类的操作,可以说是Buffer定义了操作逻辑。主要变量:

	private int mark = -1;  // 位置标记
    private int position = 0; // 当前位置
    private int limit;    // 位置操作的限制
    private int capacity; // 缓存大小

这几个变量的关系:

0 <= mark <= position <= limit <= capacity

先看下主要操作的源码,更容易理解:

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;
    }
public final Buffer flip() {
        limit = position;
        position = 0;
        mark = -1;
        return this;
    }
public final Buffer rewind() {
        position = 0;
        mark = -1;
        return this;
    }
public final Buffer clear() {
        position = 0;
        limit = capacity;
        mark = -1;
        return this;
    }
public final int remaining() {
        int rem = limit - position;
        return rem > 0 ? rem : 0;
    }

比较容易理解,mark是用来记录当前指针位置的,reset是用来还原之前mark的位置的;clear清除相关变量;而rewind只是重置位置指针;最关键的是flip,是将limit设为当前位置,而位置指针置0。
所以,在nio常用的读写场景下,共用一份内存支持读写,核心还是围绕position和limit的设置。
以flip函数的示例来看:

buf.put(magic);    // buf中写入magic header, 
in.read(buf);      // in stream读取数据写入buf
当前数组:|magic|data|				|
				    pos		   limit=capacity
buf.flip();        // Flip buffer
当前数组:|magic|data|				|
		 pos 	  limit		     capacity
out.write(buf);    // out stream读取buf写入到输出流, 读取长度限制为limit
当前数组:|magic|data|				|
		 		pos->limit		 capacity

这个例子能够比较形象的说明Buffer的使用方式,对于更复杂操作,比如每次读或写都没完成(<limit), 那就需要结合mark或者compact(在ByteBuffer中定义)来完成,但是核心还是围绕position和limit,只要能够设置正确,满足业务要求,自己定义关键变量来支撑使用也是可以的,Buffer也允许单独设置这些变量。

ByteBuffer

ByteBuffer定义了分配buffer的静态方法,以及get/put等常用操作,所以代码中通常见到ByteBuffer,基本都是当做接口来用的。

// java堆上分配buffer
public static ByteBuffer allocate(int capacity) {
        if (capacity < 0)
            throw new IllegalArgumentException();
        return new HeapByteBuffer(capacity, capacity);
    }
// 分配native内存
public static ByteBuffer allocateDirect(int capacity) {
        return new DirectByteBuffer(capacity);
    }
public abstract ByteBuffer put(byte b);
public abstract byte get(int index);

HeapByteBuffer

堆Buffer,实际的存储就是一个byte数组,不支持容量变化,大道至简哈~
引入了字节序,对于多字节数据操作如int float时会用到

boolean bigEndian                                   // package-private
        = true;
    boolean nativeByteOrder                             // package-private
        = (Bits.byteOrder() == ByteOrder.BIG_ENDIAN);
// 剩余空间切片。注意pos置0了,使用offset将数组起始位置前移了
// 对,pos是使用时需要关注的,而数组和offset相当于是backend,一般情况不需要过多关注
public ByteBuffer slice() {
        int pos = this.position();
        int lim = this.limit();
        int rem = (pos <= lim ? lim - pos : 0);
        return new HeapByteBuffer(hb,
                                        -1,
                                        0,
                                        rem,
                                        rem,
                                        pos + offset);
    }

// 原样拷贝一个实例,一个实例操作导致的数据变化会影响其它实例
// 当然,pos这些变量是独立变化的,只读的情况下当然没有问题
public ByteBuffer duplicate() {
        return new HeapByteBuffer(hb,
                                        this.markValue(),
                                        this.position(),
                                        this.limit(),
                                        this.capacity(),
                                        offset);
    }

DirectByteBuffer

比HeapByteBuffer稍微复杂一点, 因为使用了直接内存,那分配和释放就是需要关注的地方,其它函数较常规,与HeapByteBuffer差别不大。

// 内存释放接口,被调用时释放内存
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.freeMemory(address); // 释放内存
            address = 0;
            Bits.unreserveMemory(size, capacity); // 更新内存计数
        }
    }

    private final Cleaner cleaner;
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 {
            base = unsafe.allocateMemory(size); // 分配内存
        } catch (OutOfMemoryError x) {
            Bits.unreserveMemory(size, cap);
            throw x;
        }
        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;
        }
        // 创建内存清理器,类似finalize的功能,检测到对象释放后会调用Deallocator释放内存
        cleaner = Cleaner.create(this, new Deallocator(base, size, cap)); 
        att = null;
    }

另外,DirectByteBuffer继承MappedByteBuffer,而MappedByteBuffer中有文件映射相关功能,只有文件映射时才会用到,可能会有疑问,按逻辑是不是MappedByteBuffer继承DirectByteBuffer更合理?类注释有解释

// This is a little bit backwards: By rights MappedByteBuffer should be a
// subclass of DirectByteBuffer, but to keep the spec clear and simple, and
// for optimization purposes, it's easier to do it the other way around.
// This works because DirectByteBuffer is a package-private class.

其它类

上面几个类是核心类,其它还有一众方便使用的辅助类,如IntBuffer、HeapIntBuffer、DirectIntBuffer
、HeapIntBufferR(只读)、ByteBufferAsIntBufferRB(大端只读)等类。

总结

写的有点简略,主要是浏览了一遍之后,发现除了几个核心类,其他的大部分都是功能包装没必要花精力写。而核心类中主要是buffer的操作原理(围绕position和limit),以及DirectByteBuffer(native内存管理),而native内存管理又涉及Cleaner,所以下一篇就是Cleaner相关的内容啦。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值