java文件序列化
从Java的第一个版本开始,许多开发人员每天都在努力实现至少与C / C ++一样好的性能。 JVM供应商正在通过实现一些新的JIT算法来尽力而为,但仍有许多工作要做,尤其是在我们如何使用Java方面。
例如,对象<->文件序列化有很多优势,尤其是在写入/读取可以轻松放入内存的对象方面。 我将尝试阐明该主题。
所有测试都在下面显示的简单对象上执行:
public class TestObject implements Serializable {
private long longVariable;
private long[] longArray;
private String stringObject;
private String secondStringObject; //just for testing nulls
/* getters and setters */
}
为了更简洁,我将仅显示write方法(尽管另一种方法也非常相似)。 完整的源代码可在我的GitHub(http://github.com/jkubrynski/serialization-tests)上找到。
最标准的Java序列化(我们都从这里开始)如下所示:
public void testWriteBuffered(TestObject test, String fileName) throws IOException {
ObjectOutputStream objectOutputStream = null;
try {
FileOutputStream fos = new FileOutputStream(fileName);
BufferedOutputStream bos = new BufferedOutputStream(fos);
objectOutputStream = new ObjectOutputStream(bos);
objectOutputStream.writeObject(test);
} finally {
if (objectOutputStream != null) {
objectOutputStream.close();
}
}
}
加快标准序列化的最简单方法是使用RandomAccessFile对象:
public void testWriteBuffered(TestObject test, String fileName) throws IOException {
ObjectOutputStream objectOutputStream = null;
try {
RandomAccessFile raf = new RandomAccessFile(fileName, "rw");
FileOutputStream fos = new FileOutputStream(raf.getFD());
objectOutputStream = new ObjectOutputStream(fos);
objectOutputStream.writeObject(test);
} finally {
if (objectOutputStream != null) {
objectOutputStream.close();
}
}
更复杂的技术是使用Kryo框架。 新旧版本之间的差异很大。 我都检查了。 因为性能比较没有发现任何明显的不同,所以我将重点介绍第二个版本,因为它更加用户友好,甚至更快。
private static Kryo kryo = new Kryo(); // version 2.x
public void testWriteBuffered(TestObject test, String fileName) throws IOException {
Output output = null;
try {
RandomAccessFile raf = new RandomAccessFile(fileName, "rw");
output = new Output(new FileOutputStream(raf.getFD()), MAX_BUFFER_SIZE);
kryo.writeObject(output, test);
} finally {
if (output != null) {
output.close();
}
}
}
最后一个选择是受Martin Thompson文章启发的解决方案。 它显示了如何以C ++方式和Java处理内存
public void testWriteBuffered(TestObject test, String fileName) throws IOException {
RandomAccessFile raf = null;
try {
MemoryBuffer memoryBuffer = new MemoryBuffer(MAX_BUFFER_SIZE);
raf = new RandomAccessFile(fileName, "rw");
test.write(memoryBuffer);
raf.write(memoryBuffer.getBuffer());
} catch (IOException e) {
if (raf != null) {
raf.close();
}
}
}
TestObject的写入方法如下所示:
public void write(MemoryBuffer unsafeBuffer) {
unsafeBuffer.putLong(longVariable);
unsafeBuffer.putLongArray(longArray);
// we support nulls
boolean objectExists = stringObject != null;
unsafeBuffer.putBoolean(objectExists);
if (objectExists) {
unsafeBuffer.putCharArray(stringObject.toCharArray());
}
objectExists = secondStringObject != null;
unsafeBuffer.putBoolean(objectExists);
if (objectExists) {
unsafeBuffer.putCharArray(secondStringObject.toCharArray());
}
}
直接内存缓冲区类(简称,只是为了展示这个主意):
public class MemoryBuffer {
// getting Unsafe by reflection
public static final Unsafe unsafe = UnsafeUtil.getUnsafe();
private final byte[] buffer;
private static final long byteArrayOffset = unsafe.arrayBaseOffset(byte[].class);
private static final long longArrayOffset = unsafe.arrayBaseOffset(long[].class);
// other offsets
private static final int SIZE_OF_LONG = 8;
// other sizes
private long pos = 0;
public MemoryBuffer(int bufferSize) {
this.buffer = new byte[bufferSize];
}
public final byte[] getBuffer() {
return buffer;
}
public final void putLong(long value) {
unsafe.putLong(buffer, byteArrayOffset + pos, value);
pos += SIZE_OF_LONG;
}
public final long getLong() {
long result = unsafe.getLong(buffer, byteArrayOffset + pos);
pos += SIZE_OF_LONG;
return result;
}
public final void putLongArray(final long[] values) {
putInt(values.length);
long bytesToCopy = values.length << 3;
unsafe.copyMemory(values, longArrayOffset, buffer, byteArrayOffset + pos, bytesToCopy);
pos += bytesToCopy;
}
public final long[] getLongArray() {
int arraySize = getInt();
long[] values = new long[arraySize];
long bytesToCopy = values.length << 3;
unsafe.copyMemory(buffer, byteArrayOffset + pos, values, longArrayOffset, bytesToCopy);
pos += bytesToCopy;
return values;
}
/* other methods */
}
卡尺运行多个小时的结果如下所示:
全程旅行[ns] | 标准偏差[ns] | |
---|---|---|
标准 | 207307 | 2362 |
英国皇家空军的标准 | 42661 | 733 |
KRYO 1.x | 12027 | 112 |
KRYO 2.x | 11479 | 259 |
不安全 | 8554 | 91 |
最后我们可以得出一些结论:
- 不安全的序列化比标准使用java.io.Serializable的速度快23倍以上
- 使用RandomAccessFile可以将标准缓冲序列化速度提高近4倍
- Kryo动态序列化比手工实现的直接缓冲区慢约35%。
最后,正如我们所看到的,仍然没有金锤。 对于我们很多人来说,获得3000 ns(0.003ms)的时间不值得为我们要与文件序列化的每个对象编写自定义实现。 对于标准解决方案,我们主要选择Kryo。 但是,在低延迟系统中,100ns似乎是永恒的,选择将完全不同。
翻译自: https://www.javacodegeeks.com/2013/09/speed-up-with-fast-java-and-file-serialization.html
java文件序列化