java - 使用 Java unsafe 将 char 数组指向内存位置

标签 java unsafe-pointers

对 Java 应用程序的一些分析表明,它花费了大量时间将 UTF-8 字节数组解码为 String 对象。 UTF-8 字节流来自 LMDB 数据库,数据库中的值是 Protobuf 消息,这就是它对 UTF-8 进行如此多解码的原因。由此引起的另一个问题是,由于在 JVM 中从内存映射解码为 String 对象,字符串占用了大量内存。

我想重构这个应用程序,这样它就不会在每次从数据库读取消息时分配一个新的字符串。我希望 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);
    }
}

到目前为止,我已经弄清楚如何使用 Unsafe 包将字节数组复制到字符数组并在 String 对象中设置底层数组。这应该会减少应用程序在解码 UTF-8 字节时浪费的 CPU 时间。

但是,这并不能解决内存问题。有没有办法让 char 数组指向内存位置并完全避免内存分配?完全避免复制将减少 JVM 对这些字符串进行不必要的分配的数量,从而为操作系统留出更多空间来缓存 LMDB 数据库中的条目。

最佳答案

我认为您在这里采取了错误的方法。

So far, I've figured out how to copy a byte array to a char array and set the underlying array in a String object using the Unsafe package. This should reduce the amount of CPU time the application is wasting decoding UTF-8 bytes.

呃……不。

使用内存复制从 byte[] 复制到 char[] 是行不通的。目标 char 中的每个 char[] 实际上将包含原始 char[] 的 2 个字节。如果您随后尝试将 String 包装为 String ,您将得到一种奇怪的 mojibake

真正的 UTF-8 到 String 转换是如何将表示 UTF-8 代码点的 1 到 4 个字节(代码单元)转换为表示 UTF-16 中相同代码点的 1 或 2 个 16 位代码单元。使用普通内存副本无法完成此操作。

如果您不熟悉它,值得阅读 Wikipedia article on UTF-8,以便了解文本的编码方式。

<小时/>

解决方案取决于您打算如何处理文本数据。

  • 如果数据确实必须采用 StringBuilder (或 char[]CharSequence )对象的形式,那么您实际上别无选择,只能进行完整转换。尝试其他任何事情,你都可能会搞砸;例如乱码文本和潜在的 JVM 崩溃。

  • 如果您想要“类似字符串”的东西,您可以想象实现 charAt 的自定义子类,它将消息中的字节包装起来并动态解码 UTF-8。但有效地做到这一点会成为一个问题,特别是将 O(1) 方法实现为 byte[] 方法。

  • 如果您只是想保存和/或比较(整个)文本,可以通过将它们表示为 ojit_code 对象或在 ojit_code 对象中来完成。这些操作可以直接对UTF-8编码的数据进行。

  • 如果输入文本实际上可以以固定 8 位字符大小(例如 ASCII、Latin-1 等)的字符编码或 UTF-16 形式发送,那就可以简化事情。

关于java - 使用 Java unsafe 将 char 数组指向内存位置,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52789313/

相关文章:

null - Swift:gettimeofday 和不安全指针

ios - 将 UnsafeMutablePointer 转换为 UnsafeMutableRawPointer

swift - 如何从 Swift 中的索引开始将内存复制到 UnsafeMutableRawPointer?

swift - 无法将类型 'UnsafePointer<MIDINotification>' 的值转换为预期参数类型 'UnsafePointer<_>'

swift - 在 UnsafePointer<DSPSplitComplex> 处写入值

java - 无法序列化 Quartz,java.io.NotSerializedException : org.quartz.impl.StdScheduler

java - 如何在 Java 中将字符串转换为 DOMSource?

java - 带有注释参数的私有(private)方法的 Android java.lang.VerifyError

java - Jlabel 不会使用 ActionListener 中的 setText 进行更新?

java - json 字符串中缺少对象名称