本篇主要讲解如何使用直接内存(堆外内存),并按照下面的步骤进行说明:
1
|
相关背景-->读写操作-->关键属性-->读写实践-->扩展-->参考说明
|
希望对想使用直接内存的朋友,提供点快捷的参考。
下面这些,都是在使用DirectBuffer
中必备的一些常识,暂作了解吧!如果想要深入理解,可以看看下面参考的那些博客。
在Java中有很多的基本类型,比如:
byte
,一个字节是8位bit,也就是1Bshort
,16位bit,也就是2Bint
,32位bit,也就是4Blong
, 64位bit,也就是8Bchar
,16位bit,也就是2Bfloat
,32位bit,也就是4Bdouble
,64位bit,也就是8B不同的类型都会按照自己的位数来存储,并且可以自动进行转换提升。byte
、char
、short
都可以自动提升为int
,如果操作数有long
,就会自动提升为long
,float
和double
也是如此。
由于一个数据类型可能有很多个字节组成的,那么它们是如何摆放的。这个是有讲究的:
举个例子,一个char
是有两个字节组成的,这两个字节存储可能会显示成如下的模样,比如字符a
:
1
2
3
|
低地址位 高地址位
大端;
00
96
小端:
96
00
|
再说说"hello"
和new String("hello")
的区别:
如果是"hello"
,JVM会先去共享的字符串池中查找,有没有"hello"
这个词,如果有直接返回它的引用;如果没有,就会创建这个对象,再返回。因此,"a"+"b"
相当于存在3个对象,分别是"a"
、"b"
、"ab"
。
而new String("hello")
,则省去了查找的过程,直接就创建一个hello
的对象,并且返回引用。
在直接内存中,通过allocateDirect(int byte_length)
申请直接内存。这段内存可以理解为一段普通的基于Byte
的数组,因此插入和读取都跟普通的数组差不多。
只不过提供了基于不同数据类型的插入方法,比如:
等等….详细的使用方法,也可以参考下面的图片:
对应读取数据,跟写入差不多:
注意所有没有index参数的方法,都是按照当前position的位置进行操作的。
下面看看什么是position,还有什么其他的属性吧!
它有几个关键的指标:
1
|
mark-->position-->limit-->capacity
|
另外,还有remaining=limit-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
]
|
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
是当前申请的直接内存的容量,它是申请后就不会改变的。
我们可能想要改变这段直接内存的大小,因此可以通过一个叫做Limit的属性设置。
注意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,就是一个标记为而已,记录当前的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
则表示当前的剩余空间:
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
]
|
上面的读写例子中,有几个常用的方法:
这个方法用于清除mark和position,还有limit的位置:
1
2
3
4
5
6
|
public
final
Buffer clear() {
position =
0
;
limit = capacity;
mark = -
1
;
return
this
;
}
|
这个方法主要用于改变当前的Position为limit,主要是用于读取操作。
1
2
3
4
5
6
|
public
final
Buffer flip() {
limit = position;
position =
0
;
mark = -
1
;
return
this
;
}
|
这个方法在读取一部分数据的时候比较常用。
它会把当前的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
|
这个方法用于判断是否是直接内存。如果是返回true,如果不是返回false。
这个方法用于重置mark标记:
1
2
3
4
5
|
public
final
Buffer rewind() {
position =
0
;
mark = -
1
;
return
this
;
}
|