重新解释底层位模式并将其写入数组或字段的最有效方法是什么?

What is the most efficient way to reinterpret underlying bit patterns and write it into and array or field?

使用Unsafe.putXXX可以将基本类型放入数组或对象字段中。

但是像下面这样的代码会产生错误。

import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.util.Arrays;

public class Main {

  public static void main(String[] args) {
    VarHandle varHandle = MethodHandles.arrayElementVarHandle(long[].class);

    byte[] array = new byte[32];

    printArray(array);
    varHandle.set(array, 1, 5);
    printArray(array);

    System.out.println(varHandle.get(array, 1));
  }

  private static void printArray(byte[] array) {
    System.out.println(Arrays.toString(array));
  }

}
Exception in thread "main" java.lang.ClassCastException: Cannot cast [B to [J
    at java.base/java.lang.Class.cast(Class.java:3780)
    at Main.main(Main.java:15)

另外bytes可以写成:

byte[] array = new byte[32];
long v = 5,
int i = 8;
int high = (int) (v >>> 32);
int low = (int) v;
array[i + 0] = (byte) (high >>> 24);
array[i + 1] = (byte) (high >>> 16);
array[i + 2] = (byte) (high >>> 8);
array[i + 3] = (byte) high;
array[i + 4] = (byte) (low >>> 24);
array[i + 5] = (byte) (low >>> 16);
array[i + 6] = (byte) (low >>> 8);
array[i + 7] = (byte) low;

有没有一种有效的方法来重新解释不同的类型并将它们写入字段和数组可能避免 Unsafe 但同样有效。

编译器或 JIT 将识别意图并进行相应优化的任何特殊情况。

对于 byte[] 特别是您可以使用 MethodHandles::byteArrayViewVarHandle:

public static void main(String[] args) {
    VarHandle varHandle = MethodHandles.byteArrayViewVarHandle(long[].class,
                                                               ByteOrder.nativeOrder());

    byte[] array = new byte[32];

    printArray(array);
    varHandle.set(array, 1, 5);
    printArray(array);

    System.out.println(varHandle.get(array, 1));
}

private static void printArray(byte[] array) {
    System.out.println(Arrays.toString(array));
}

您必须使用 VarHandles 跳过一些障碍才能使它们与 Unsafe 一样快;

  1. 确保 VarHandle 本身是常量,这可以通过将其放入静态最终字段并从那里访问来完成。
  2. 确保 VarHandle 调用是准确的。这意味着 将第二个参数转换为 long,因为 VarHandle 除了 a 一样长。 (在最新的 JDK 中,您可以使用 VarHandle::withInvokeExactBehavior 来强制执行)。

这可以通过包装 VarHandle 集并在执行转换的辅助方法中调用来变得更容易:

private static final VarHandle LONG_ARR_HANDLE 
        = MethodHandles.byteArrayViewVarHandle(long[].class,
                                               ByteOrder.nativeOrder());

public static void setLong(byte[] bytes, int index, long value) {
    LONG_ARR_HANDLE.set(bytes, index, value);
}   

public static long getLong(byte[] bytes, int index) {
    return (long) LONG_ARR_HANDLE.get(bytes, index);
}    

使用 ByteBuffer 的规范方法也是从 Java 开始最快的方法 17. 自己衡量(and/or 考虑其他选项)如果你不在 Java 17岁了。

ByteBuffer byteBuffer = ByteBuffer.allocate(bytesLength);
byteBuffer.asLongBuffer().put(longs);
return byteBuffer.array();

I did an experiment with serializing float[] to byte[] 并根据所需的字节顺序和 Java 版本不同的方法出现不同,但 ByteBufferMemorySegmentUnsafe 或库使用它们是最重要的。 VarHandles 并没有落后太多,但它们仍然很循环。

在使用 G1 的 OpenJDK 17 上,在我的 MBP 16" 2019 上,结果是:

Benchmark             (size)  Mode  Cnt      Score      Error  Units

beByteBuffer             512  avgt    5    193.351 ±   21.366  ns/op
beByteBufferWrap         512  avgt    5    197.477 ±   43.743  ns/op
beDataOutputStream       512  avgt    5   1590.887 ±   60.744  ns/op
beKryo                   512  avgt    5    861.624 ±   11.927  ns/op
beManualUnpacking        512  avgt    5    861.573 ±   10.108  ns/op
beObjectOutputStream     512  avgt    5   2168.386 ±   12.950  ns/op
beVarHandle              512  avgt    5    225.611 ±    1.839  ns/op

leByteBuffer             512  avgt    5    155.345 ±    1.229  ns/op
leByteBufferWrap         512  avgt    5    154.523 ±    0.853  ns/op
leDataOutputStream       512  avgt    5  55414.147 ± 9390.669  ns/op
leKryoUnsafe             512  avgt    5    156.019 ±   18.235  ns/op
leManualUnpacking        512  avgt    5    880.687 ±   12.856  ns/op
leMemorySegment          512  avgt    5    148.483 ±    0.897  ns/op
leUnsafeCopyMemory       512  avgt    5    149.107 ±    2.011  ns/op
leVarHandle              512  avgt    5    225.615 ±    1.357  ns/op

请注意,如果您尚未达到 Java 17,您的结果可能会大不相同! ObjectOutputStream 在 Java 17 和 two of the four *byteBuffer variants were 2* as slow too 之前要慢得多。因此,请针对您的特定 JVM 和 JVM 配置进行测量。不过,总的来说,ByteBufferMemorySegment 是前进的方向。