<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          NIo開發(fā)利器ByteBuffer

          共 13895字,需瀏覽 28分鐘

           ·

          2021-06-28 09:42

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

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

          主要API和屬性

          ByteBuffer類圖

          他是對于Buffer的一個默認(rèn)實現(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)存的時候,內(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ū),注意這里并不會清空數(shù)據(jù),只是將各項指標(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;
          }
          //重新從頭進行讀寫,初始化指針和標(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的時候)
          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對上分配的緩沖區(qū),一般由**byte[]**實現(xiàn),它有一個好處,就是它的內(nèi)存的分配與回收由JVM自動完成,用戶不必自己再操心內(nèi)存釋放的問題,但是缺點也很明顯,就是它再數(shù)據(jù)傳輸?shù)臅r候,需要將數(shù)據(jù)從JVM復(fù)制到本地物理內(nèi)存上,多了一次復(fù)制操作!

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

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

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

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

          構(gòu)造方法

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

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

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

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

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

          // mark = -1      pos = 0      lim = 128    cap = 128   hb = 字節(jié)數(shù)組對象    offset = 0
          ByteBuffer(int mark, int pos, int lim, int cap, byte[] hb, int offset) {
          super(mark, pos, lim, cap);
          //前面創(chuàng)建的字節(jié)數(shù)組對象
          this.hb = hb;
          //保存一個偏移量 默認(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);
          //........ 忽略無必要代碼.........
          }

          此時我們的一個堆內(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ù)會發(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還記得嗎,就是我們再創(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ù)的講解我們再次寫入幾個數(shù)據(jù)的時候,邏輯和上方一樣:

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

          get方法

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

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

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

          heapRam.flip();

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

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

          filp方法

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

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

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

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

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

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

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

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

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

          當(dāng)我們傳遞了一個字節(jié)數(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;
          }

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

          image-20210321132547345

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

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

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

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

          我們看上圖的數(shù)據(jù)結(jié)構(gòu)數(shù)據(jù)可知,該結(jié)果為0,就必定會報錯,所以說,當(dāng)我們向再次切換成寫模式的話,就一定要初始化 pos,還是調(diào)用filp方法嗎?重新調(diào)用filp方法固然可行,但是,調(diào)用filp方法并不會初始化limit的大小,造成明明我們分配了128個字節(jié)的大小,但是可用的永遠(yuǎn)都只有5個,所以,我們?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)全部的進行初始化了,指針重新歸0,但是JDK考慮到性能影響byte數(shù)組中的數(shù)據(jù)并沒有被清除,只會被新數(shù)據(jù)覆蓋調(diào)!

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

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

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

          image-20210321140847866

          mark方法

          我們此時想,一會讀完數(shù)據(jù)了,還想再次回到當(dāng)前的位置進行數(shù)據(jù)的二次讀取,我們此時就應(yīng)該調(diào)用mark()方法,打個標(biāo)記,它的底層會記錄當(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)記位置開始讀取的時候:

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

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

          reset方法

          rewind方法

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

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

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

          rewind

          堆外緩沖區(qū)

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

          //1. 分配一個最大能夠存儲128個字節(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ù)組的一個基本偏移 數(shù)組基本偏移
          long arrayBaseOffset = (long)o.arrayBaseOffset(byte[].class);
          byte[] bytes = "歡迎關(guān)注公眾號:【源碼學(xué)徒】 學(xué)習(xí)更多源碼知識!".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);
          //從物理機將數(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));
          }

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

          有了基本的知識,我們一起分下下堆外內(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);
          //判斷是否對其的頁面
          if (pa && (base % ps != 0)) {
          // 向上對其頁面 并保存地址
          address = base + ps - (base & (ps - 1));
          } else {
          //保存地址
          address = base;
          }
          //這個極其重要,是JVM管理堆外內(nèi)存的重要方法
          cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
          att = null;
          }

          我們來逐行進行分析,首先是  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是一個null,因為它是堆外緩沖區(qū),不依賴與JVM內(nèi)部的內(nèi)存分配!

          image-20210321150824205

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

          base = unsafe.allocateMemory(size);

          這里是調(diào)用的 native方法分配的緩沖區(qū),是C來實現(xiàn)的,unsafe是JDK內(nèi)部使用的一個操作物理內(nèi)存的工具類,一般不對外開放,如果想要使用可以通過反射的方式獲取,獲取方式上面已經(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)存的地址,方面后面進行數(shù)據(jù)的讀寫

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

          這個方法極其重要,主要負(fù)責(zé)改堆外內(nèi)存的釋放,他是一個虛引用,具體的講解上一篇文章  深入分析NIO的零拷貝述的很詳細(xì),看一下JVM是如何釋放一個不由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);
          //計算剩余空間
          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);
          //重新計算當(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) {
          //計算 偏移量 = 字節(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;
          //計算偏移量 本次應(yīng)該偏移的數(shù)量
          offset += size;
          //計算地址 下次讀取的起始位置
          dstAddr += size;
          }
          }

          我們會發(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)存的切換方式 一致,不做講述,忘記的小伙伴請翻到上面看下!

          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);
          //將讀寫指針切換至對應(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) {
          //計算當(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);
          //計算剩余的數(shù)據(jù)長度
          length -= size;
          //計算下次拷貝的地址的偏移量
          srcAddr += size;
          //計算下次復(fù)制的偏移量
          offset += size;
          }
          }

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

          image-20210321200004469

          堆外緩沖區(qū)比較重要的幾個點:

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

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

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

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



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



          瀏覽 69
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  久久国精品 | 免费搞黄网站。 | 天天日天天射天天干 | 久久精品久久久久 | 东京热一区二区 |