天天看點

Java直接(堆外)記憶體使用詳解

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

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

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

資料類型

下面這些,都是在使用

DirectBuffer

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

基本類型長度

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

  • byte

    ,一個位元組是8位bit,也就是1B
  • short

    ,16位bit,也就是2B
  • int

    ,32位bit,也就是4B
  • long

    , 64位bit,也就是8B
  • char

  • float

  • double

    ,64位bit,也就是8B

不同的類型都會按照自己的位數來存儲,并且可以自動進行轉換提升。

byte

char

short

都可以自動提升為

int

,如果操作數有

long

,就會自動提升為

long

float

double

也是如此。

大端小端

由于一個資料類型可能有很多個位元組組成的,那麼它們是如何擺放的。這個是有講究的:

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

舉個例子,一個

char

是有兩個位元組組成的,這兩個位元組存儲可能會顯示成如下的模樣,比如字元

a

:

低位址位    高位址位
大端;        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

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

Java直接(堆外)記憶體使用詳解

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

Java直接(堆外)記憶體使用詳解

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

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

基本的屬性值

它有幾個關鍵的名額:

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

另外,還有

remaining=limit-position

先說說他們的意思吧!

目前位置——position

position是目前數組的指針,訓示目前資料位置。舉個例子:

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的位置分别是:

2,4,8
           

注意,Position的位置是插入資料的目前位置,如果插入資料,就會自動後移。

也就是說,如果存儲的是兩個位元組的資料,position的位置是在第三個位元組上,下标就是2。

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)設定。
//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小。

//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()

    方法進行初始化
//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;
}
           

使用案例

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);
           

可以看到如下的運作結果:

插入完資料 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

則表示目前的剩餘空間:

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

讀寫實踐

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

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

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);
           

輸出的内容為:

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

讀的時候,可以通過一個外部的

byte[]

數組進行讀取。由于沒有找到直接操作直接記憶體的方法: 是以如果想在JVM應用中使用直接記憶體,需要申請一段堆中的空間,存放資料。

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

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);
           

輸出為

剛寫完資料 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的位置:

public final Buffer clear() {
        position = 0;
        limit = capacity;
        mark = -1;
        return this;
    }
           

flip()

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

public final Buffer flip() {
        limit = position;
        position = 0;
        mark = -1;
        return this;
    }
           

compact()

這個方法在讀取一部分資料的時候比較常用。

它會把目前的Position移到0,然後position+1移到1。

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;
    }
           

比如一段空間内容為:

123456789
           

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

345678989
           

isDirect()

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

rewind()

這個方法用于重置mark标記:

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

參考

1 Java基本資料類型

2 Java中大端與小端

作者:xingoo

出處:http://www.cnblogs.com/xing901022

本文版權歸作者和部落格園共有。歡迎轉載,但必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接!