也许我以错误的方式解决这个问题,但我遗漏了一些 absolute put 方法在 ByteBuffer
上.
如果您查看 ByteBuffer
,您会发现大多数 put 方法都具有绝对变体和相对变体。
除了:
- 将
byte
数组的一部分写入ByteBuffer
。 - 正在将
ByteBuffer
写入ByteBuffer
。
..我正是需要这些。
要清楚 ByteBuffer
有方法:
put(byte[] src, int offset, int length)
put(ByteBuffer src)
但缺少:
put(int index, byte[] src, int offset, int length)
put(int index, ByteBuffer src)
我有理由不想移动缓冲区的位置指针,因此我只想使用绝对放置方法。
知道为什么这些方法被排除在外吗?
我当然可以在不移动缓冲区的位置指针的情况下模仿缺失的方法,但这将涉及循环遍历源字节。 Javadoc 明确指出这些方法(可能)比移动比循环和逐个移动字节更有效。我相信 Javadoc,因为我的测试表明相同。我需要从我的实现中挤出尽可能多的速度,因此我当然倾向于利用我可以得到的任何批量方法......如果它们存在的话。
偶然ByteBuffer
还缺少用于部分字节数组移动的绝对 get
方法。但我目前实际上并不需要这样的方法。但同样奇怪的是它不存在。
最佳答案
获得所需方法的一种方法是让第二个 ByteBuffer 共享相同的内存,这样您就可以在不更改原始位置的情况下更改其位置。
不幸的是,slice
方法也不采用位置参数;相反,它使用原始缓冲区的当前位置。所以你不能这样做:
dstBuffer.slice(100).put(srcBuffer);
这里有一些想法,没有特别的顺序,只是我想到的顺序:
如果它适合您使用缓冲区的方式,您可以使用
slice()
准备缓冲区的副本,并在需要将数据放在独立于原始位置的位置。如果你想绝对放置的位置总是大于或等于原始缓冲区的位置指针,你可以这样做:
dstBuffer.slice().position(desiredPosition - dstBuffer.position()).put(srcBuffer);
不幸的是,放在较早的位置是行不通的,因为切片上的位置不允许为负数。 编辑:没关系,我忘了 duplicate方法。请参阅@BorisBrodski 的出色回答。
如果您不使用直接字节缓冲区,System.arraycopy 既简单又快速:
System.arraycopy( srcBuffer.array(), srcBuffer.arrayOffset() + srcBuffer.position(), dstBuffer.array(), dstBuffer.arrayOffset() + desiredPosition, srcBuffer.remaining() );
如果不需要并发访问,则可以在需要进行绝对放置时临时更改缓冲区的位置,然后再将其放回原处。如果您需要并发访问但线程争用很低,您可以同步对缓冲区的所有访问(可能很明显,但为了完整性而包括在内):
synchronize (lock) { int originalPosition = dstBuffer.position(); dstBuffer.position(desiredPosition); dstBuffer.put(srcBuffer); dstBuffer.position(originalPosition); }
如果其他想法都不适合您,您可以修改缓冲区。这很困惑,但这是一个例子:
private static final sun.misc.Unsafe UNSAFE; static { Object result = null; try { Class<?> klass = Class.forName("sun.misc.Unsafe"); for (Field field : klass.getDeclaredFields()) { if (field.getType() == klass && (field.getModifiers() & (Modifier.FINAL | Modifier.STATIC)) == (Modifier.FINAL | Modifier.STATIC)) { field.setAccessible(true); result = field.get(null); break; } } } catch (Throwable t) {} UNSAFE = result == null ? null : (sun.misc.Unsafe)result; } private static final Field ADDRESS_FIELD; static { Field f; try { f = Buffer.class.getDeclaredField("address"); f.setAccessible(true); } catch (NoSuchFieldException | SecurityException e) { f = null; } ADDRESS_FIELD = f; } public static void absolutePut(ByteBuffer dstBuffer, int dstPosition, ByteBuffer srcBuffer) { if (!srcBuffer.isDirect()) { absolutePut(dstBuffer, dstPosition, srcBuffer.array(), srcBuffer.arrayOffset() + srcBuffer.position(), srcBuffer.remaining()); return; } if (UNSAFE != null && ADDRESS_FIELD != null && dstBuffer.isDirect()) { try { long dstAddress = (long)ADDRESS_FIELD.get(dstBuffer) + dstPosition; long srcAddress = (long)ADDRESS_FIELD.get(srcBuffer) + srcBuffer.position(); UNSAFE.copyMemory(srcAddress, dstAddress, srcBuffer.remaining()); } catch (IllegalAccessException e) { throw new RuntimeException(e); } } else { // fallback to basic loop for (int i = srcBuffer.position(); i < srcBuffer.limit(); i++) { dstBuffer.put(dstPosition + i, srcBuffer.get(i)); } } } public static void absolutePut(ByteBuffer dstBuffer, int dstPosition, byte[] src, int srcOffset, int length) { if (UNSAFE != null && ADDRESS_FIELD != null && dstBuffer.isDirect()) { try { long dstAddress = (long)ADDRESS_FIELD.get(dstBuffer) + dstPosition; UNSAFE.copyMemory( src, UNSAFE.arrayBaseOffset(byte[].class) + srcOffset, null, dstAddress, length); } catch (IllegalAccessException e) { throw new RuntimeException(e); } } else { // fallback to System.arraycopy System.arraycopy( src, srcOffset, dstBuffer.array(), dstBuffer.arrayOffset() + dstPosition, length); } }
我已经使用直接和非直接缓冲区的混合对该代码进行了一些最小测试,它似乎没问题。如果反射技术失败(例如,因为您在 applet 安全沙箱中或 Java 实现不兼容),它可以回退到更简单的方法。
关于java - 缺少 ByteBuffer 上的一些绝对方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15409727/