您的位置:首页 > 编程语言 > Java开发

加快Java的文件序列化速度

2012-10-12 16:31 162 查看
自从第一个Java版本开始,很多开发人员一直都在尝试让Java获得最少和C/C++一样的表现。JVM提供商尽他们最大的努力去实现一些新的JIT算法,但是还是有很多需要做的,特别是在我们使用Java的方法上。

例如,在对象<->文件序列化上就差距很大--尤其在读写内存对象上。我将就这个主题做一些解释和分享。

所有的测试都是在下面这个对象上执行的:

1
public
class
TestObject
implements
Serializable
{
2
3
private
long
longVariable;
4
private
long
[]
longArray;
5
private
String
stringObject;
6
private
String
secondStringObject;
//just
for testing nulls
7
8
/*
getters and setters */
9
}
为了简单起见,我将只贴出写入方法(尽管读取类似),完整的源码在我的GitHub上可以找到(http://github.com/jkubrynski/serialization-tests

最标准的java序列化(我们都是从这里学起的)是这样的:

01
public
void
testWriteBuffered(TestObject
test, String fileName)
throws
IOException
{
02
ObjectOutputStream
objectOutputStream =
null
;
03
try
{
04
FileOutputStream
fos =
new
FileOutputStream(fileName);
05
BufferedOutputStream
bos =
new
BufferedOutputStream(fos);
06
objectOutputStream
=
new
ObjectOutputStream(bos);
07
objectOutputStream.writeObject(test);
08
}
finally
{
09
if
(objectOutputStream
!=
null
)
{
10
objectOutputStream.close();
11
}
12
}
13
}
提升标准序列化速度的最简单方法时使用RandomAccessFile对象:

01
public
void
testWriteBuffered(TestObject
test, String fileName)
throws
IOException
{
02
ObjectOutputStream
objectOutputStream =
null
;
03
try
{
04
RandomAccessFile
raf =
new
RandomAccessFile(fileName,
"rw"
);
05
FileOutputStream
fos =
new
FileOutputStream(raf.getFD());
06
objectOutputStream
=
new
ObjectOutputStream(fos);
07
objectOutputStream.writeObject(test);
08
}
finally
{
09
if
(objectOutputStream
!=
null
)
{
10
objectOutputStream.close();
11
}
12
}
更高深点的技术是使用Kryo框架,新旧版本的差距是很大的,我做过测试。因为性能比较上并没有体现出特别引人注意的差异,所以我将使用2.x版本,因为它对用户更友好而且更快些。

01
private
static
Kryo
kryo =
new
Kryo();
//
version 2.x
02
03
public
void
testWriteBuffered(TestObject
test, String fileName)
throws
IOException
{
04
Output
output =
null
;
05
try
{
06
RandomAccessFile
raf =
new
RandomAccessFile(fileName,
"rw"
);
07
output
=
new
Output(
new
FileOutputStream(raf.getFD()),
MAX_BUFFER_SIZE);
08
kryo.writeObject(output,
test);
09
}
finally
{
10
if
(output
!=
null
)
{
11
output.close();
12
}
13
}
14
}
最后一个方案是在Martin Thompson的文章中提到的(Native C/C++ Like Performance
For Java Object Serialisation),介绍了怎样在Java中像C++那样和内存打交道。

01
public
void
testWriteBuffered(TestObject
test, String fileName)
throws
IOException
{
02
RandomAccessFile
raf =
null
;
03
try
{
04
MemoryBuffer
memoryBuffer =
new
MemoryBuffer(MAX_BUFFER_SIZE);
05
raf
=
new
RandomAccessFile(fileName,
"rw"
);
06
test.write(memoryBuffer);
07
raf.write(memoryBuffer.getBuffer());
08
}
catch
(IOException
e) {
09
if
(raf
!=
null
)
{
10
raf.close();
11
}
12
}
13
}
TestObject写入方法如下:

01
public
void
write(MemoryBuffer
unsafeBuffer) {
02
unsafeBuffer.putLong(longVariable);
03
unsafeBuffer.putLongArray(longArray);
04
//
we support nulls
05
boolean
objectExists
= stringObject !=
null
;
06
unsafeBuffer.putBoolean(objectExists);
07
if
(objectExists)
{
08
unsafeBuffer.putCharArray(stringObject.toCharArray());
09
}
10
objectExists
= secondStringObject !=
null
;
11
unsafeBuffer.putBoolean(objectExists);
12
if
(objectExists)
{
13
unsafeBuffer.putCharArray(secondStringObject.toCharArray());
14
}
15
}
直接内存缓冲区类(已简化了的,仅仅为了展示这个思想)

01
public
class
MemoryBuffer
{
02
//
getting Unsafe by reflection
03
public
static
final
Unsafe
unsafe = UnsafeUtil.getUnsafe();
04
05
private
final
byte
[]
buffer;
06
07
private
static
final
long
byteArrayOffset
= unsafe.arrayBaseOffset(
byte
[].
class
);
08
private
static
final
long
longArrayOffset
= unsafe.arrayBaseOffset(
long
[].
class
);
09
/*
other offsets */
10
11
private
static final int SIZE_OF_LONG = 8;
12
/*
other sizes */
13
14
private
long pos = 0;
15
16
public
MemoryBuffer(int bufferSize) {
17
this.buffer
= new byte[bufferSize];
18
}
19
20
public
final byte[] getBuffer() {
21
return
buffer;
22
}
23
24
public
final void putLong(long value) {
25
unsafe.putLong(buffer,
byteArrayOffset + pos, value);
26
pos
+= SIZE_OF_LONG;
27
}
28
29
public
final long getLong() {
30
long
result = unsafe.getLong(buffer, byteArrayOffset + pos);
31
pos
+= SIZE_OF_LONG;
32
return
result;
33
}
34
35
public
final void putLongArray(final long[] values) {
36
putInt(values.length);
37
long
bytesToCopy = values.length << 3;
38
unsafe.copyMemory(values,
longArrayOffset, buffer, byteArrayOffset + pos, bytesToCopy);
39
pos
+= bytesToCopy;
40
}
41
42
43
public
final long[] getLongArray() {
44
int
arraySize = getInt();
45
long[]
values = new long[arraySize];
46
long
bytesToCopy = values.length << 3;
47
unsafe.copyMemory(buffer,
byteArrayOffset + pos, values, longArrayOffset, bytesToCopy);
48
pos
+= bytesToCopy;
49
return
values;
50
}
51
52
/*
other methods */
53
}
几个小时的Caliper测试结果如下:

Full trip [ns] Standard deviation [ns]
Standard 2073072362
Standard on RAF42661733
KRYO 1.x 12027112
KRYO 2.x11479259
Unsafe855491
在最后我们可以得出一些结论:

Unsafe序列化比标准的java.io.Serizlizable快了23倍
使用RandomAccessFile可以使标准的有缓冲序列化加速将近4倍
Kryo-dynamic序列化大约比手写实现的直接缓冲满了35%

最后,就像我们看到的那样,还是没有绝对的答案。对于我们中的大多数人来说,获得3000ns(0.003ms)的速度提升是不值得为每个需要序列化的对象来写单独实现的。在标准的方案中,我们大多数选择Kryo 。然而,在惜时如金的低延时系统中,这个选择将会是完全不同的。

OSCHINA编译,原文链接
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐