1. NIo開發(fā)利器ByteBuffer

        共 13895字,需瀏覽 28分鐘

         ·

        2021-06-28 09:42

        有道無術(shù),術(shù)尚可求也!有術(shù)無道,止于術(shù)!

        想要使用NIO開發(fā)Socket分服務(wù)端和客戶端,必須掌握的一個(gè)知識(shí)點(diǎn)就是ByteBuffer的使用,他是NIO再數(shù)據(jù)傳輸中的利器!相比于BIO傳輸過程中的字節(jié)流,ByteBuffer更能體現(xiàn)出服務(wù)端/客戶端對(duì)于數(shù)據(jù)的操作效率,ByteBuffer內(nèi)存維護(hù)一個(gè)指針,使得傳輸?shù)臄?shù)據(jù)真正的能夠達(dá)到重復(fù)使用,重復(fù)讀寫的能力!

        主要API和屬性

        ByteBuffer類圖

        他是對(duì)于Buffer的一個(gè)默認(rèn)實(shí)現(xiàn),具體主要的屬性和方法我們需要看Buffer類:

        主要屬性

        //指針標(biāo)記
        private int mark = -1;
        //指針的當(dāng)前位置
        private int position = 0;
        //翻轉(zhuǎn)后界限
        private int limit;
        //最大容量
        private int capacity;
        //當(dāng)為堆外內(nèi)存的時(shí)候,內(nèi)存的地址
        long address;

        主要方法

        //返回當(dāng)前緩沖區(qū)的最大容量
        public final int capacity() {return capacity;}
        //返回當(dāng)前的指針位置
        public final int position() {return position;}
        //返回當(dāng)前的讀寫界限
        public final int limit() {return limit;}
        //標(biāo)記當(dāng)前指針位置
        public final Buffer mark() {
        mark = position;
        return this;
        }
        //恢復(fù)當(dāng)前指針位置
        public final Buffer reset() {
        int m = mark;
        if (m < 0)
        throw new InvalidMarkException();
        position = m;
        return this;
        }
        //清空緩沖區(qū),注意這里并不會(huì)清空數(shù)據(jù),只是將各項(xiàng)指標(biāo)初始化,后續(xù)再寫入數(shù)據(jù)就直接覆蓋
        public final Buffer clear() {
        position = 0;
        limit = capacity;
        mark = -1;
        return this;
        }
        //切換讀寫模式
        public final Buffer flip() {
        limit = position;
        position = 0;
        mark = -1;
        return this;
        }
        //重新從頭進(jìn)行讀寫,初始化指針和標(biāo)記位置
        public final Buffer rewind() {
        position = 0;
        mark = -1;
        return this;
        }
        //剩余可讀可寫的數(shù)量
        public final int remaining() {return limit - position;}
        //當(dāng)前是否可讀/可寫
        public final boolean hasRemaining() {return position < limit;}
        //是不是只讀的
        public abstract boolean isReadOnly();
        //是不是支持?jǐn)?shù)組訪問
        public abstract boolean hasArray();
        //獲取當(dāng)前緩存的字節(jié)數(shù)組(當(dāng)hasArray返回為true的時(shí)候)
        public abstract Object array();
        //是不是堆外緩沖區(qū)也就是直接緩沖區(qū)
        public abstract boolean isDirect();
        //取消緩沖區(qū)
        final void discardMark() {mark = -1;}

        堆內(nèi)緩沖

        什么是堆內(nèi)緩沖區(qū)?所謂的堆內(nèi)緩沖區(qū),顧名思義就是再JVM對(duì)上分配的緩沖區(qū),一般由**byte[]**實(shí)現(xiàn),它有一個(gè)好處,就是它的內(nèi)存的分配與回收由JVM自動(dòng)完成,用戶不必自己再操心內(nèi)存釋放的問題,但是缺點(diǎn)也很明顯,就是它再數(shù)據(jù)傳輸?shù)臅r(shí)候,需要將數(shù)據(jù)從JVM復(fù)制到本地物理內(nèi)存上,多了一次復(fù)制操作!

        創(chuàng)建堆內(nèi)緩沖區(qū)

        java堆內(nèi)緩沖區(qū)的默認(rèn)實(shí)現(xiàn)是 HeapByteBuffer,但是這個(gè)對(duì)象是一個(gè) default權(quán)限的類,你是無法直接創(chuàng)建的,只能通過JDK底層暴露的api來創(chuàng)建:

        //1. 分配一個(gè)最大能夠存儲(chǔ)128個(gè)字節(jié)的堆內(nèi)存
        ByteBuffer heapRam = ByteBuffer.allocate(128);
        //2. 或者直接初始化數(shù)據(jù)創(chuàng)建
        ByteBuffer wrapBuffer = ByteBuffer.wrap("歡迎關(guān)注公眾號(hào):【源碼學(xué)徒】 學(xué)習(xí)更多源碼知識(shí)!".getBytes());

        堆內(nèi)緩沖區(qū)API源碼解析

        構(gòu)造方法

        以上兩種方案創(chuàng)建的都是一個(gè)堆內(nèi)緩沖區(qū),他們創(chuàng)建的邏輯大致相同,我們以 ByteBuffer.allocate為例進(jìn)行分析:

        public static ByteBuffer allocate(int capacity) {
        if (capacity < 0) {
        throw new IllegalArgumentException();
        }
        //創(chuàng)建一個(gè)堆內(nèi)緩沖區(qū)
        return new HeapByteBuffer(capacity, capacity);
        }

        我們可以看到,通過 ByteBuffer.allocate創(chuàng)建的緩沖區(qū)是一個(gè) HeapByteBuffer,他是堆內(nèi)緩沖區(qū)!我們繼續(xù)往下分析:

        HeapByteBuffer(int cap, int lim) {
        super(-1, 0, lim, cap, new byte[cap], 0);
        }

        注意此時(shí),cap和lim都是我們傳遞的大小,內(nèi)部還創(chuàng)建了一個(gè)cap大小的字節(jié)數(shù)組傳遞下去!他就是堆內(nèi)最終存儲(chǔ)數(shù)據(jù)的數(shù)組!

        // mark = -1      pos = 0      lim = 128    cap = 128   hb = 字節(jié)數(shù)組對(duì)象    offset = 0
        ByteBuffer(int mark, int pos, int lim, int cap, byte[] hb, int offset) {
        super(mark, pos, lim, cap);
        //前面創(chuàng)建的字節(jié)數(shù)組對(duì)象
        this.hb = hb;
        //保存一個(gè)偏移量 默認(rèn)為0
        this.offset = offset;
        }

        然后再調(diào)用父類的構(gòu)造參數(shù):

        Buffer(int mark, int pos, int lim, int cap) {
        if (cap < 0){
        throw new IllegalArgumentException("Negative capacity: " + cap);
        }
        //保存最大容量
        this.capacity = cap;
        //保存limit
        limit(lim);
        //保存pos指針位置
        position(pos);
        //........ 忽略無必要代碼.........
        }

        此時(shí)我們的一個(gè)堆內(nèi)緩沖區(qū)就創(chuàng)建完成了,它的內(nèi)部結(jié)構(gòu)如下:

        init 堆內(nèi)緩沖區(qū)的結(jié)構(gòu)

        put方法

        現(xiàn)在我們的容器創(chuàng)建好了,我們就需要往里面懟數(shù)據(jù)了呀,我們需要往里面寫入一段字節(jié)數(shù)組:

        heapRam.put("A".getBytes());

        我們調(diào)用put方法往ByteBuffer里面寫入一段數(shù)據(jù)會(huì)發(fā)生什么呢?

        public ByteBuffer put(byte[] src, int offset, int length) {
        //先判斷當(dāng)前數(shù)據(jù)的長度是否超過可寫長度了
        // remaining() = limit - position = 128 - 0
        if (length > remaining()) {
        throw new BufferOverflowException();
        }
        //hb還記得嗎,就是我們?cè)賱?chuàng)建堆內(nèi)緩沖區(qū)所創(chuàng)建的字節(jié)數(shù)組
        //這里就是將我們的數(shù)據(jù)拷貝到從當(dāng)前的指針位置開始的堆內(nèi)緩存(hb字節(jié)數(shù)組)
        System.arraycopy(src, offset, hb, ix(position()), length);
        //將當(dāng)前的指針位置 + 數(shù)據(jù)長度,我們本次寫入的數(shù)據(jù)長度是1 那么當(dāng)前的指針?biāo)饕褪?1
        position(position() + length);
        return this;
        }

        當(dāng)調(diào)用put方法后,內(nèi)部數(shù)據(jù)結(jié)構(gòu)如下:

        調(diào)用put方法后的數(shù)據(jù)結(jié)構(gòu)

        為了方便后續(xù)的講解我們?cè)俅螌懭霂讉€(gè)數(shù)據(jù)的時(shí)候,邏輯和上方一樣:

        heapRam.put("B".getBytes());
        heapRam.put("C".getBytes());
        heapRam.put("D".getBytes());
        heapRam.put("E".getBytes());
        再次調(diào)用put方法

        get方法

        我們現(xiàn)在再緩沖區(qū)里面寫入了 ABCDE五個(gè)數(shù)據(jù),此時(shí)我們?nèi)绻霃木彌_區(qū)取數(shù)據(jù),就應(yīng)該調(diào)用另外一個(gè)api:get()方法

        byte b = heapRam.get();
        System.out.println(new String(new byte[]));

        但是,很奇怪的是,我們打印了一個(gè)空,并沒有想象中的打印一個(gè)A,這是為什么呢?我們由上面的分析可以知道,每次緩沖區(qū)對(duì)于數(shù)據(jù)的操作都是基于指針來做的,我們每一次操作數(shù)據(jù),指針都會(huì)后移一位,當(dāng)我們發(fā)生一個(gè)get()請(qǐng)求后,指針依舊會(huì)后移,將下標(biāo)為5的數(shù)據(jù)返回同時(shí)自身自增變?yōu)?.但是下標(biāo)為5的并沒有數(shù)據(jù),只能返回一個(gè)空數(shù)據(jù),所以我們?nèi)绻霃念^讀數(shù)據(jù),就必須想辦法將指針復(fù)位,重新變?yōu)?,我們此時(shí)往里面寫數(shù)據(jù),我們稱之為寫模式,想要切換到讀模式就必須調(diào)用 **heapRam.flip();**方法來切換讀寫模式,復(fù)位讀寫指針!

        heapRam.flip();

        那這個(gè)api具體做了什么呢?僅僅是將讀寫指針復(fù)位嗎?  那我提出一個(gè)問題,不妨讀者讀到這里思考一下,如果僅僅是指針復(fù)位的話,我們?nèi)绾慰刂撇蛔層脩糇x超呢? **我們只寫入了5個(gè)數(shù),如何避免用戶讀第六個(gè)數(shù)據(jù)呢?**我們帶著疑問,看下 flip方法究竟做了什么:

        public final Buffer flip() {
        //將當(dāng)前的指針位置賦值給limit
        limit = position;
        //讀寫指針復(fù)位
        position = 0;
        mark = -1;
        return this;
        }

        filp方法

        我們可以看到,filp方法再復(fù)位讀寫指針之前,記錄了一個(gè)位置 limit,具體他是干嘛的,我么稍后再說       到現(xiàn)在為止,我們的數(shù)據(jù)結(jié)構(gòu)如下:

        filp方法
        byte b = heapRam.get();
        System.out.println(new String(new byte[]));

        此時(shí)我們?cè)俅握{(diào)用get方法,指針后移,同時(shí)返回當(dāng)前指針位置代表的數(shù)據(jù):注意 ix方法不用管,他是計(jì)算偏移量的,這里始終是0

        get方法
        public byte get() {
        return hb[ix(nextGetIndex())];
        }

        nextGetIndex:主要是判斷當(dāng)前指針是否超過了 limit的限制,同時(shí)自增指針位置

        final int nextGetIndex() {
        //limit的作用在這里被體現(xiàn),判斷你的讀指針是不是讀超了數(shù)據(jù)范圍
        if (position >= limit){
        throw new BufferUnderflowException();
        }
        //返回讀指針的位置,并自增1
        return position++;
        }

        get方法主要是直接返回字節(jié)數(shù)組某個(gè)下標(biāo)的位置的字節(jié)數(shù)據(jù)!

        我們多讀一些數(shù)據(jù):

        byte[] bytes = new byte[4];
        heapRam.get(bytes);
        System.out.println(new String(bytes));

        當(dāng)我們傳遞了一個(gè)字節(jié)數(shù)組去讀取的時(shí)候,它的內(nèi)部是如何做的呢?

        public ByteBuffer get(byte[] dst) {
        return get(dst, 0, dst.length);
        }
        public ByteBuffer get(byte[] dst, int offset, int length) {
        checkBounds(offset, length, dst.length);
        if (length > remaining())
        throw new BufferUnderflowException();
        //將數(shù)據(jù)拷貝至我們傳遞的字節(jié)數(shù)組中
        System.arraycopy(hb, ix(position()), dst, offset, length);
        //讀指針位置+我們要讀取的長度
        position(position() + length);
        return this;
        }

        此時(shí)緩沖區(qū)的內(nèi)部數(shù)據(jù)結(jié)構(gòu)如下:

        image-20210321132547345

        我們把數(shù)據(jù)讀完了,下面,我又想往里面寫數(shù)據(jù)了,假設(shè)直接寫是否能寫呢? put方法向下表為5的地方寫一個(gè)數(shù)據(jù),同時(shí)指針后移,似乎可行,我們分析一下put方法,具體我們前面已經(jīng)分析過了,再put方法的源碼中,有這么一段邏輯:

        if (length > remaining())
        throw new BufferOverflowException();

        假設(shè)寫入數(shù)據(jù)的長度,大于剩余可寫長度,就會(huì)報(bào)錯(cuò),我們具體看下這個(gè)方法的邏輯:

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

        我們看上圖的數(shù)據(jù)結(jié)構(gòu)數(shù)據(jù)可知,該結(jié)果為0,就必定會(huì)報(bào)錯(cuò),所以說,當(dāng)我們向再次切換成寫模式的話,就一定要初始化 pos,還是調(diào)用filp方法嗎?重新調(diào)用filp方法固然可行,但是,調(diào)用filp方法并不會(huì)初始化limit的大小,造成明明我們分配了128個(gè)字節(jié)的大小,但是可用的永遠(yuǎn)都只有5個(gè),所以,我們?nèi)绻胱寯?shù)據(jù)重新能夠初始化,就必須讓limit = capacity,JDK也為我們提供了接口:clear

        clear方法

        heapRam.clear();
         public final Buffer clear() {
        //讀寫指針歸零
        position = 0;
        //limit初始化為初始狀態(tài)
        limit = capacity;
        //標(biāo)記初始化為初始狀態(tài)
        mark = -1;
        return this;
        }

        可以看到,clear方法將我們緩沖區(qū)中的所有指標(biāo)全部的進(jìn)行初始化了,指針重新歸0,但是JDK考慮到性能影響byte數(shù)組中的數(shù)據(jù)并沒有被清除,只會(huì)被新數(shù)據(jù)覆蓋調(diào)!

        由同學(xué)會(huì)問,你不是說ByteBuffer可以進(jìn)行重復(fù)的讀取嗎? 這明明只能讀一遍,讀完就得初始化指針位置,你騙人!

        別著急,想要進(jìn)行重復(fù)的讀寫操作,我們必須還要掌握另外一組API:mark() 、reset();

        我們假設(shè)我們此時(shí)處于讀模式,數(shù)據(jù)結(jié)構(gòu)如下:

        image-20210321140847866

        mark方法

        我們此時(shí)想,一會(huì)讀完數(shù)據(jù)了,還想再次回到當(dāng)前的位置進(jìn)行數(shù)據(jù)的二次讀取,我們此時(shí)就應(yīng)該調(diào)用mark()方法,打個(gè)標(biāo)記,它的底層會(huì)記錄當(dāng)前指針的位置:

        heapRam.mark();
        public final Buffer mark() {
        //記錄當(dāng)前讀指針的位置
        mark = position;
        return this;
        }

        調(diào)用mark方法之后,我們的數(shù)據(jù)結(jié)構(gòu)如下:

        mark方法

        然后我們將數(shù)據(jù)讀完:

        image-20210321143705395

        reset方法

        我們將數(shù)據(jù)讀完之后,想再從標(biāo)記位置開始讀取的時(shí)候:

        heapRam.reset();
        public final Buffer reset() {
        //獲取當(dāng)前標(biāo)記的位置
        int m = mark;
        if (m < 0)
        //如果標(biāo)記位為負(fù)數(shù),就證明沒有進(jìn)行標(biāo)記過直接報(bào)錯(cuò)
        throw new InvalidMarkException();
        //然后將標(biāo)記位置賦值給當(dāng)前的指針位置
        position = m;
        return this;
        }

        當(dāng)前的數(shù)據(jù)結(jié)構(gòu)如下:

        reset方法

        rewind方法

        如此,我們就可以進(jìn)行復(fù)讀了,相類似的方法還有:rewind

        heapRam.rewind();
        public final Buffer rewind() {
        //回復(fù)讀寫指針為0
        position = 0;
        //廢棄標(biāo)記位置
        mark = -1;
        return this;
        }

        rewind方法是直接返回的 緩沖區(qū)的頭部,同時(shí)廢棄標(biāo)記的位置 !

        rewind

        堆外緩沖區(qū)

        創(chuàng)建堆外緩沖區(qū)

        //1. 分配一個(gè)最大能夠存儲(chǔ)128個(gè)字節(jié)的堆外內(nèi)存
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(128);

        jvm如何操作堆外內(nèi)存

        public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        //獲取JDK底層的操作物理內(nèi)存的工具類
        Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        Unsafe o = (Unsafe) theUnsafe.get(null);
        //從物理內(nèi)存分配一塊128的內(nèi)存
        long address = o.allocateMemory(128);
        //獲取字節(jié)數(shù)組的一個(gè)基本偏移 數(shù)組基本偏移
        long arrayBaseOffset = (long)o.arrayBaseOffset(byte[].class);
        byte[] bytes = "歡迎關(guān)注公眾號(hào):【源碼學(xué)徒】 學(xué)習(xí)更多源碼知識(shí)!".getBytes();

        //向物理內(nèi)存復(fù)制一段數(shù)據(jù)
        // 數(shù)據(jù)源 數(shù)據(jù)的基本偏移 目標(biāo)數(shù)據(jù)源 要復(fù)制到的內(nèi)存地址 復(fù)制數(shù)據(jù)的長度
        o.copyMemory(bytes, arrayBaseOffset, null, address, bytes.length);
        //從物理機(jī)將數(shù)據(jù)拷貝回JVM內(nèi)存中
        byte[] copy = new byte[bytes.length];
        // 數(shù)據(jù)源 物理地址 目標(biāo)數(shù)據(jù)源 數(shù)組基本偏移量 復(fù)制數(shù)據(jù)的長度
        o.copyMemory(null, address, copy, arrayBaseOffset, bytes.length);
        //釋放內(nèi)存
        o.freeMemory(address);
        System.out.println(new String(copy));
        }

        上述的操作是分配一個(gè)物理內(nèi)存、將一段數(shù)據(jù)寫進(jìn)物理內(nèi)存、然后將數(shù)據(jù)從物理內(nèi)存讀進(jìn)JVM數(shù)組、釋放物理內(nèi)存

        有了基本的知識(shí),我們一起分下下堆外內(nèi)存的源碼把!

        堆外緩沖區(qū)Api源碼解析

        構(gòu)造方法

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

        我們可以看到,堆外緩沖區(qū)是由DirectByteBuffer來代表的!

        DirectByteBuffer(int cap) {

        super(-1, 0, cap, cap);
        //......忽略其他代碼..........

        long base = 0;
        try {
        //從物理內(nèi)存分配一塊指定大小的內(nèi)存 并返回當(dāng)前分配內(nèi)存的地址
        base = unsafe.allocateMemory(size);
        } catch (OutOfMemoryError x) {
        Bits.unreserveMemory(size, cap);
        throw x;
        }
        //初始化內(nèi)存
        unsafe.setMemory(base, size, (byte) 0);
        //判斷是否對(duì)其的頁面
        if (pa && (base % ps != 0)) {
        // 向上對(duì)其頁面 并保存地址
        address = base + ps - (base & (ps - 1));
        } else {
        //保存地址
        address = base;
        }
        //這個(gè)極其重要,是JVM管理堆外內(nèi)存的重要方法
        cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
        att = null;
        }

        我們來逐行進(jìn)行分析,首先是  super(-1, 0, cap, cap);

        //      -1   0   128    128
        MappedByteBuffer(int mark, int pos, int lim, int cap) {
        //繼續(xù)調(diào)用父類
        super(mark, pos, lim, cap);
        this.fd = null;
        }
        // -1   0   128   128
        ByteBuffer(int mark, int pos, int lim, int cap) {
        //在往上
        this(mark, pos, lim, cap, null, 0);
        }
        //   -1   0   128   128   null    0
        ByteBuffer(int mark, int pos, int lim, int cap, byte[] hb, int offset) {
        super(mark, pos, lim, cap);
        this.hb = hb;
        this.offset = offset;
        }

        到這里就不往上分析了,它和創(chuàng)建堆內(nèi)緩沖是一樣的,保存一些基本的變量,但是注意  這里傳遞的hb是一個(gè)null,因?yàn)樗嵌淹饩彌_區(qū),不依賴與JVM內(nèi)部的內(nèi)存分配!

        image-20210321150824205

        此時(shí)基本數(shù)據(jù)保存完畢,開始分配一塊堆外內(nèi)存:

        base = unsafe.allocateMemory(size);

        這里是調(diào)用的 native方法分配的緩沖區(qū),是C來實(shí)現(xiàn)的,unsafe是JDK內(nèi)部使用的一個(gè)操作物理內(nèi)存的工具類,一般不對(duì)外開放,如果想要使用可以通過反射的方式獲取,獲取方式上面已經(jīng)寫出來了,同學(xué)們沒事可以玩一下!

        unsafe.setMemory(base, size, (byte) 0);

        初始化內(nèi)存區(qū)域,將所分配的內(nèi)存里面的數(shù)據(jù)默認(rèn)設(shè)置為字節(jié)0

        address = base;

        保存物理內(nèi)存的地址,方面后面進(jìn)行數(shù)據(jù)的讀寫

        cleaner = Cleaner.create(this, new Deallocator(base, size, cap));

        這個(gè)方法極其重要,主要負(fù)責(zé)改堆外內(nèi)存的釋放,他是一個(gè)虛引用,具體的講解上一篇文章  深入分析NIO的零拷貝述的很詳細(xì),看一下JVM是如何釋放一個(gè)不由JVM控制的堆外內(nèi)存的!這里就不做具體的講解了!

        image-20210321160738660

        put方法

        現(xiàn)在我們向堆外內(nèi)存寫入一段數(shù)據(jù):

        byteBuffer.put("ABCDE".getBytes());

        我們看下源碼是如何來操作堆外內(nèi)存的

        public final ByteBuffer put(byte[] src) {
        return put(src, 0, src.length);
        }
        public ByteBuffer put(byte[] src, int offset, int length) {

        if (((long)length << 0) > Bits.JNI_COPY_FROM_ARRAY_THRESHOLD) {
        //檢查越界
        checkBounds(offset, length, src.length);
        //獲取當(dāng)前的讀寫指針
        int pos = position();
        //獲取當(dāng)前的學(xué)些界限
        int lim = limit();
        //判斷是否超過讀寫界限
        assert (pos <= lim);
        //計(jì)算剩余空間
        int rem = (pos <= lim ? lim - pos : 0);
        //判斷寫入數(shù)據(jù)是否大于剩余空間
        if (length > rem) {
        throw new BufferOverflowException();
        }
        //向物理內(nèi)存拷貝數(shù)據(jù)
        Bits.copyFromArray(src, arrayBaseOffset, (long)offset << 0, ix(pos), (long)length << 0);
        //重新計(jì)算當(dāng)前的讀寫指針
        position(pos + length);
        } else {
        super.put(src, offset, length);
        }
        return this;



        }

        我們發(fā)現(xiàn),里面最關(guān)鍵的一段代碼是  Bits.copyFromArray(src, arrayBaseOffset, (long)offset << 0, ix(pos), (long)length << 0);

        它傳遞的是:要寫入的數(shù)據(jù)的字節(jié)數(shù)組、字節(jié)基準(zhǔn)偏移、偏移量(0)、地址+讀寫指針的位置(addres + pos)、要寫入的數(shù)據(jù)的長度

        static void copyFromArray(Object src, long srcBaseOffset, long srcPos, long dstAddr, long length) {
        //計(jì)算 偏移量 = 字節(jié)數(shù)組基準(zhǔn)偏移量 + offset(0)
        long offset = srcBaseOffset + srcPos;
        //如果存在數(shù)據(jù)
        while (length > 0) {
        //判斷數(shù)據(jù)是否大于1MB 如果大于1MB就默認(rèn)只傳遞1MB,剩余數(shù)據(jù)交給下一次循環(huán)
        long size = (length > UNSAFE_COPY_THRESHOLD) ? UNSAFE_COPY_THRESHOLD : length;
        //拷貝數(shù)據(jù)
        unsafe.copyMemory(src, offset, null, dstAddr, size);
        //判斷本次拷貝后剩余未拷貝的數(shù)據(jù)
        length -= size;
        //計(jì)算偏移量 本次應(yīng)該偏移的數(shù)量
        offset += size;
        //計(jì)算地址 下次讀取的起始位置
        dstAddr += size;
        }
        }

        我們會(huì)發(fā)現(xiàn),里面的代碼有一部分我們極其熟悉,正是上面我演示unsafe如何使用的代碼,這里就是將數(shù)據(jù)拷貝至堆外內(nèi)存的!現(xiàn)在它的內(nèi)存結(jié)構(gòu)如下:

        image-20210321161014909

        filp方法

        byteBuffer.flip();
        image-20210321193732857

        現(xiàn)在我們要獲取數(shù)據(jù)了就也必須調(diào)用filp方法切換讀寫模式,直接緩沖區(qū)的切換方式和堆內(nèi)內(nèi)存的切換方式 一致,不做講述,忘記的小伙伴請(qǐng)翻到上面看下!

        get方法

        byte[] bytes = new byte[5];
        byteBuffer.get(bytes);
        public ByteBuffer get(byte[] dst) {
        return get(dst, 0, dst.length);
        }
        // bytes     0     5
        public ByteBuffer get(byte[] dst, int offset, int length) {
        if (((long)length << 0) > Bits.JNI_COPY_TO_ARRAY_THRESHOLD) {
        checkBounds(offset, length, dst.length);
        //獲取當(dāng)前的讀指針 0
        int pos = position();
        //獲取當(dāng)前的limit
        int lim = limit();
        //判斷是否超過界限
        assert (pos <= lim);
        int rem = (pos <= lim ? lim - pos : 0);
        if (length > rem) {
        throw new BufferUnderflowException();
        }
        //關(guān)鍵方法 將物理內(nèi)存中的數(shù)據(jù)復(fù)制到JVM內(nèi)存中來
        Bits.copyToArray(ix(pos), dst, arrayBaseOffset, (long)offset << 0, (long)length << 0);
        //將讀寫指針切換至對(duì)應(yīng)位置 5
        position(pos + length);
        } else {
        super.get(dst, offset, length);
        }
        return this;
        }

        可以看出,當(dāng)前的關(guān)鍵代碼是 Bits.copyToArray(ix(pos), dst, arrayBaseOffset, (long)offset << 0, (long)length << 0);   我們分析一下

        static void copyToArray(long srcAddr, Object dst, long dstBaseOffset, long dstPos, long length) {
        //計(jì)算當(dāng)前的偏移量
        long offset = dstBaseOffset + dstPos;
        while (length > 0) {
        //最大拷貝長度是 1MB 高于1MB的下次循環(huán)再次拷貝
        long size = (length > UNSAFE_COPY_THRESHOLD) ? UNSAFE_COPY_THRESHOLD : length;
        // 將數(shù)據(jù)拷貝回指定的數(shù)組中
        // 源數(shù)據(jù) 數(shù)據(jù)所在的內(nèi)存地址 目標(biāo)位置 偏移量 拷貝的長度
        unsafe.copyMemory(null, srcAddr, dst, offset, size);
        //計(jì)算剩余的數(shù)據(jù)長度
        length -= size;
        //計(jì)算下次拷貝的地址的偏移量
        srcAddr += size;
        //計(jì)算下次復(fù)制的偏移量
        offset += size;
        }
        }

        當(dāng)前數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu)為:

        image-20210321200004469

        堆外緩沖區(qū)比較重要的幾個(gè)點(diǎn):

        1. 緩沖區(qū)的創(chuàng)建(構(gòu)造函數(shù))
        2. 數(shù)據(jù)的存儲(chǔ)(put方法)
        3. 數(shù)據(jù)的獲取(get方法)
        4. 堆外內(nèi)存的釋放

        都已經(jīng)介紹完畢,其他類似的API譬如 clear、markreset、rewind 再上面的外內(nèi)內(nèi)存的介紹中都已經(jīng)介紹完畢了,邏輯都一樣,感興趣的小伙伴可以自己追一下源碼!

        對(duì)于NIO的學(xué)習(xí),這個(gè)緩沖區(qū)是必不可少的一節(jié)課!務(wù)必要搞明白呀!

        才疏學(xué)淺,如果文章中理解有誤,歡迎大佬們私聊指正!歡迎關(guān)注作者的公眾號(hào),一起進(jìn)步,一起學(xué)習(xí)!



        ??「轉(zhuǎn)發(fā)」「在看」,是對(duì)我最大的支持??



        瀏覽 69
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 淫欲的美女理论电影完整版 | 国产视频国产区 | 男生插女生b | 国产美女一级片 | blacked性猛交raw |