1. 程式人生 > >JAVA直接記憶體(堆外記憶體)

JAVA直接記憶體(堆外記憶體)

本篇主要講解如何使用直接記憶體(堆外記憶體),並按照下面的步驟進行說明:

1 相關背景-->讀寫操作-->關鍵屬性-->讀寫實踐-->擴充套件-->參考說明

希望對想使用直接記憶體的朋友,提供點快捷的參考。

資料型別

下面這些,都是在使用DirectBuffer中必備的一些常識,暫作了解吧!如果想要深入理解,可以看看下面參考的那些部落格。

基本型別長度

在Java中有很多的基本型別,比如:

  • byte,一個位元組是8位bit,也就是1B
  • short,16位bit,也就是2B
  • int,32位bit,也就是4B
  • long, 64位bit,也就是8B
  • char
    ,16位bit,也就是2B
  • float,32位bit,也就是4B
  • double,64位bit,也就是8B

不同的型別都會按照自己的位數來儲存,並且可以自動進行轉換提升。
bytecharshort都可以自動提升為int,如果運算元有long,就會自動提升為longfloatdouble也是如此。

大端小端

由於一個數據型別可能有很多個位元組組成的,那麼它們是如何擺放的。這個是有講究的:

  • 大端:低地址位 存放 高有效位元組
  • 小端:低地址位 存放 低有效位元組

舉個例子,一個char是有兩個位元組組成的,這兩個位元組儲存可能會顯示成如下的模樣,比如字元a:

1 2 3 低地址位    高地址位
大端;        00              96 小端:        96              00

String與new String的區別

再說說"hello"new String("hello")的區別:

如果是"hello",JVM會先去共享的字串池中查詢,有沒有"hello"這個詞,如果有直接返回它的引用;如果沒有,就會建立這個物件,再返回。因此,"a"+"b"相當於存在3個物件,分別是"a""b""ab"

new String("hello"),則省去了查詢的過程,直接就建立一個hello的物件,並且返回引用。

讀寫資料

在直接記憶體中,通過allocateDirect(int byte_length)

申請直接記憶體。這段記憶體可以理解為一段普通的基於Byte的陣列,因此插入和讀取都跟普通的陣列差不多。

只不過提供了基於不同資料型別的插入方法,比如:

  • put(byte) 插入一個byte
  • put(byte[]) 插入一個byte陣列
  • putChar(char) 插入字元
  • putInt(int) 插入Int
  • putLong(long) 插入long

等等….詳細的使用方法,也可以參考下面的圖片:

對應讀取資料,跟寫入差不多:

注意所有沒有index引數的方法,都是按照當前position的位置進行操作的。

下面看看什麼是position,還有什麼其他的屬性吧!

基本的屬性值

它有幾個關鍵的指標:

1 mark-->position-->limit-->capacity

另外,還有remaining=limit-position

先說說他們的意思吧!

當前位置——position

position是當前陣列的指標,指示當前資料位置。舉個例子:

1 2 3 4 5 6 7 ByteBuffer buffer = ByteBuffer.allocateDirect(1024); buffer.putChar('a'); System.out.println(buffer); buffer.putChar('c'); System.out.println(buffer); buffer.putInt(10); System.out.println(buffer);

由於一個char是2個位元組,一個Int是4個位元組,因此position的位置分別是:

1 2,4,8

注意,Position的位置是插入資料的當前位置,如果插入資料,就會自動後移。
也就是說,如果儲存的是兩個位元組的資料,position的位置是在第三個位元組上,下標就是2。

1 2 3 java.nio.DirectByteBuffer[pos=2 lim=1024 cap=1024] java.nio.DirectByteBuffer[pos=4 lim=1024 cap=1024] java.nio.DirectByteBuffer[pos=8 lim=1024 cap=1024]
  • position可以通過position()獲得,也可以通過position(int)設定。
1 2 3 4 5 6 7 8 //position(int)方法的原始碼 public final Buffer position(int newPosition) { if ((newPosition > limit) || (newPosition < 0)) throw new IllegalArgumentException(); position = newPosition; if (mark > position) mark = -1; return this; }

注意:position的位置要比limit小,比mark大

空間容量——capacity

capacity是當前申請的直接記憶體的容量,它是申請後就不會改變的。

  • capacity則可以通過capacity()方法獲得。

限制大小——limit

我們可能想要改變這段直接記憶體的大小,因此可以通過一個叫做Limit的屬性設定。

  • limit則可以通過limit()獲得,通過limit(int)進行設定。

注意limit要比mark和position大,比capacity小。

1 2 3 4 5 6 7 8 9 //limit(int)方法的原始碼 public final Buffer limit(int newLimit) { if ((newLimit > capacity) || (newLimit < 0)) throw new IllegalArgumentException(); limit = newLimit; if (position > limit) position = limit; if (mark > limit) mark = -1; return this; }

標記位置——mark

mark,就是一個標記為而已,記錄當前的position的值。常用的場景,就是記錄某一次插入資料的位置,方便下一次進行回溯。

  • 可以使用mark()方法進行標記,
  • 使用reset()方法進行清除,
  • 使用rewind()方法進行初始化
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 //mark方法標記當前的position,預設為-1 public final Buffer mark() { mark = position; return this; } //reset方法重置mark的位置,position的位置,不能小於mark的位置,否則會出錯 public final Buffer reset() { int m = mark; if (m < 0) throw new InvalidMarkException(); position = m; return this; } //重置mark為-1.position為0 public final Buffer rewind() { position = 0; mark = -1; return this; }

    使用案例

    1 2 3 4 5 6 7 8 9 10 11 ByteBuffer buffer = ByteBuffer.allocateDirect(1024); buffer.putChar('a'); buffer.putChar('c'); System.out.println("插入完資料 " + buffer); buffer.mark();// 記錄mark的位置 buffer.position(30);// 設定的position一定要比mark大,否則mark無法重置 System.out.println("reset前 " + buffer); buffer.reset();// 重置reset ,reset後的position=mark System.out.println("reset後 " + buffer); buffer.rewind();//清除標記,position變成0,mark變成-1 System.out.println("清除標記後 " + buffer);

    可以看到如下的執行結果:

    1 2 3 4 插入完資料 java.nio.DirectByteBuffer[pos=4 lim=1024 cap=1024] reset前 java.nio.DirectByteBuffer[pos=30 lim=1024 cap=1024] reset後 java.nio.DirectByteBuffer[pos=4 lim=1024 cap=1024] 清除標記後 java.nio.DirectByteBuffer[pos=0 lim=1024 cap=1024]

剩餘空間——remaing

remaing則表示當前的剩餘空間:

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

讀寫實踐

寫操作主要就是按照自己的資料型別,寫入到直接記憶體中,注意每次寫入資料的時候,position都會自動加上寫入資料的長度,指向下一個該寫入的起始位置:

下面看看如何寫入一段byte[]或者字串:

1 2 3 4 5 6 7 ByteBuffer buffer = ByteBuffer.allocateDirect(10); byte[] data = {1,2}; buffer.put(data); System.out.println("寫byte[]後 " + buffer); buffer.clear(); buffer.put("hello".getBytes()); System.out.println("寫string後 " + buffer);

輸出的內容為:

1 2 byte[]後 java.nio.DirectByteBuffer[pos=2 lim=10 cap=10] 寫string後 java.nio.DirectByteBuffer[pos=5 lim=10 cap=10]

讀的時候,可以通過一個外部的byte[]陣列進行讀取。由於沒有找到直接操作直接記憶體的方法: 因此如果想在JVM應用中使用直接記憶體,需要申請一段堆中的空間,存放資料。

如果有更好的方法,還請留言。

1 2 3 4 5 6 7 8 9 10 11 ByteBuffer buffer = ByteBuffer.allocateDirect(10); buffer.put(new byte[]{1,2,3,4}); System.out.println("剛寫完資料 " +buffer); buffer.flip(); System.out.println("flip之後 " +buffer); byte[] target = new byte[buffer.limit()]; buffer.get(target);//自動讀取target.length個數據 for(byte b : target){ System.out.println(b); } System.out.println("讀取完陣列 " +buffer);

輸出為

1 2 3 4 5 6 7 剛寫完資料 java.nio.DirectByteBuffer[pos=4 lim=10 cap=10] flip之後 java.nio.DirectByteBuffer[pos=0 lim=4 cap=10] 1 2 3 4 讀取完陣列 java.nio.DirectByteBuffer[pos=4 lim=4 cap=10]

常用方法

上面的讀寫例子中,有幾個常用的方法:

clear()

這個方法用於清除mark和position,還有limit的位置:

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

flip()

這個方法主要用於改變當前的Position為limit,主要是用於讀取操作。

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

compact()

這個方法在讀取一部分資料的時候比較常用。
它會把當前的Position移到0,然後position+1移到1。

1 2 3 4 5 6 7 8 9 10 11 12 public ByteBuffer compact() { int pos = position(); int lim = limit(); assert (pos <= lim); int rem = (pos <= lim ? lim - pos : 0); unsafe.copyMemory(ix(pos), ix(0), rem << 0); position(rem); limit(capacity()); discardMark(); return this; }

比如一段空間內容為:

1 123456789

當position的位置在2時,呼叫compact方法,會變成:

1 345678989

isDirect()

這個方法用於判斷是否是直接記憶體。如果是返回true,如果不是返回false。

rewind()

這個方法用於重置mark標記:

1 2 3 4 5 public final Buffer rewind() { position = 0; mark = -1; return this; }

參考