JAVA直接記憶體(堆外記憶體)
本篇主要講解如何使用直接記憶體(堆外記憶體),並按照下面的步驟進行說明:
1 |
相關背景-->讀寫操作-->關鍵屬性-->讀寫實踐-->擴充套件-->參考說明
|
希望對想使用直接記憶體的朋友,提供點快捷的參考。
資料型別
下面這些,都是在使用DirectBuffer
中必備的一些常識,暫作了解吧!如果想要深入理解,可以看看下面參考的那些部落格。
基本型別長度
在Java中有很多的基本型別,比如:
byte
,一個位元組是8位bit,也就是1Bshort
,16位bit,也就是2Bint
,32位bit,也就是4Blong
, 64位bit,也就是8Bchar
float
,32位bit,也就是4Bdouble
,64位bit,也就是8B
不同的型別都會按照自己的位數來儲存,並且可以自動進行轉換提升。byte
、char
、short
都可以自動提升為int
,如果運算元有long
,就會自動提升為long
,float
和double
也是如此。
大端小端
由於一個數據型別可能有很多個位元組組成的,那麼它們是如何擺放的。這個是有講究的:
- 大端:低地址位 存放 高有效位元組
- 小端:低地址位 存放 低有效位元組
舉個例子,一個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 ;
}
|