对Java应用程序的一些分析表明,它花费了大量时间将UTF-8字节数组解码为String对象。UTF-8字节流来自LMDB数据库,数据库中的值是Protobuf消息,这就是它对UTF-8解码如此之多的原因。由此引起的另一个问题是,由于在JVM中将内存映射解码为字符串对象,因此字符串占用了大量内存。
我想重构这个应用程序,这样它就不会在每次从数据库中读取消息时都分配一个新的字符串。我希望String对象中的底层char数组只指向内存位置。
package testreflect;
import java.lang.reflect.Field;
import sun.misc.Unsafe;
public class App {
public static void main(String[] args) throws Exception {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe UNSAFE = (Unsafe) field.get(null);
char[] sourceChars = new char[] { 'b', 'a', 'r', 0x2018 };
// Encoding to a byte array; asBytes would be an LMDB entry
byte[] asBytes = new byte[sourceChars.length * 2];
UNSAFE.copyMemory(sourceChars,
UNSAFE.arrayBaseOffset(sourceChars.getClass()),
asBytes,
UNSAFE.arrayBaseOffset(asBytes.getClass()),
sourceChars.length*(long)UNSAFE.arrayIndexScale(sourceChars.getClass()));
// Copying the byte array to the char array works, but is there a way to
// have the char array simply point to the byte array without copying?
char[] test = new char[sourceChars.length];
UNSAFE.copyMemory(asBytes,
UNSAFE.arrayBaseOffset(asBytes.getClass()),
test,
UNSAFE.arrayBaseOffset(test.getClass()),
asBytes.length*(long)UNSAFE.arrayIndexScale(asBytes.getClass()));
// Allocate a String object, but set its underlying
// byte array manually to avoid the extra memory copy
long stringOffset = UNSAFE.objectFieldOffset(String.class.getDeclaredField("value"));
String stringTest = (String) UNSAFE.allocateInstance(String.class);
UNSAFE.putObject(stringTest, stringOffset, test);
System.out.println(stringTest);
}
}
到目前为止,我已经了解了如何将字节数组复制到char数组,并使用Unsafe包在String对象中设置底层数组。这应该会减少应用程序在解码UTF-8字节上浪费的CPU时间。
但是,这并不能解决内存问题。有没有办法让char数组指向一个内存位置,并完全避免内存分配?完全避免复制将减少JVM为这些字符串进行的不必要分配的数量,从而为操作系统缓存来自LMDB数据库的条目留下更多空间。
发布于 2018-10-13 13:35:22
我认为你在这里采取了错误的方法。
到目前为止,我已经了解了如何将字节数组复制到字符数组,并使用
包在String对象中设置底层数组。这应该会减少应用程序在解码UTF-8字节上浪费的CPU时间。
呃..。不是的。
使用内存复制从byte[]
复制到char[]
是行不通的。目标char[]
中的每个char
实际上将包含原始char[]
中的2个字节。如果你随后尝试将char[]
包装成String
,你会得到一种奇怪的mojibake。
真正的UTF-8到String
的转换所做的是将表示UTF-8码点的1到4个字节(码元)转换成表示UTF-16中相同码点的1或2个16位码元。这不能使用普通的内存拷贝来完成。
如果您不熟悉它,那么有必要阅读Wikipedia article on UTF-8,这样您就可以理解文本是如何编码的。
解决方案取决于您打算如何处理文本数据。
String
(或StringBuilder
或char[]
)对象的形式,那么除了执行完整转换之外,您别无选择。CharSequence
子类,它包装消息中的字节并动态解码UTF-8。byte[]
对象中实现。byte[]
对象中实现。这些操作可以直接在UTF-8编码的数据上执行。https://stackoverflow.com/questions/52789313
复制相似问题