从 ByteBuffer 中提取长整数 (Java/Scala)

Extract Longs from ByteBuffer (Java/Scala)

我正在构造 BigInt 个数字,每个数字由两个 Long 组成,每个数字的构成方式如下:

val msb = -1L // some arbitrary long value, can be anything between Long.Min/MaxValue
val lsb = 25L // a second arbitrary long value        

val bb = ByteBuffer
  .allocate(17)
  .put(0.toByte) // 1 byte
  .putLong(msb) // 8 bytes
  .putLong(lsb) // 8 bytes

val number = BigInt(bb.array) // in this case: 340282366920938463444927863358058659865

之所以在前面加了一个0-Byte,是为了保证结果是正数。否则,由于二进制补码,结果 BigInt 可能 为负数。之后调用的算法期望数字大于或等于零。

到目前为止,还不错。

我无法逆转整个过程 - 将 BigInt 转换回两个 Long(正是用作输入的两个值)。我不能只做以下事情:

val arr = number.toByteArray
val bb = ByteBuffer.wrap(arr)
val ignore = bb.getByte
val msb = bb.getLong
val lsb = bb.getLong

假设 BigInt 号码是3. 然后 .toByteArray 将导致大小为 1 的数组,而不是 16(或 17),因此对 getLong 的调用将导致 BufferUnderflowException.

解决这个问题最简单的方法是什么?我尝试了几种方法来手动填充缓冲区,直到有 16 个字节可用,但是由于这个 "padding" 必须正确地考虑两个数字的补码,所以我没有成功。

Modulo operation 在这里可以提供帮助:

....
val number = BigInt(bb.array) // in this case: 340282366920938463444927863358058659865

val modulo = BigInt(2).pow(64)
val lsb2 = (number / modulo).toLong     //25
val msb2 = (number.mod(modulo)).toLong  //-1

您可以 allocate 一个足够大的 ByteBuffer(即大小为 17 字节)而不是使用 ByteBuffer.wrap 并且 put(byte[]) 字节数组位于正确的位置(即它 "lines up" 与缓冲区的 lsb) 像这样:

val number = BigInt("340282366920938463444927863358058659865")

val arr = number.toByteArray  // of length 0-17
val bb = ByteBuffer.allocate(17)
bb.position(1 + (16 - arr.length))
bb.put(arr)
bb.rewind()

val ignore = bb.get
val msb = bb.getLong
val lsb = bb.getLong

您提出的提取方法 起作用,您只需要更好地使用前导 0 字节。

val bb = ByteBuffer
  .allocate(17)
  .put(1.toByte) // 1 byte (some positive value)
  .putLong(msb)  // 8 bytes
  .putLong(lsb)  // 8 bytes

val number = BigInt(bb.array) // never negative, always 17 bytes

val bbx = ByteBuffer.wrap(number.toByteArray)
bbx.get      // throw away
bbx.getLong  // msb
bbx.getLong  // lsb

如果出于某种原因,您需要 number 来包含 msblsb 位,那么您可以创建一个掩码协助提取。

val maskbb = ByteBuffer
  .allocate(17)
  .put(Byte.MinValue) // 1 byte
  .putLong(0L) // 8 bytes
  .putLong(0L) // 8 bytes

val arr = (BigInt(maskbb.array) + number).toByteArray
val bbx = ByteBuffer.wrap(arr)
... // the rest us unchanged

使用 plumbing/padding 方法,并使用问题中定义的 number

val msb, lsb = split(number) // (-1,25)

/** split the passed Bigint into a (msb: Long, lsb: Long) tuple */
def split(bi: BigInt) = splitArray(bi.toByteArray.takeRight(16)) // Considers only the last bytes if there are more than 16

/** assumes arrays of size 16 or less */
def splitArray(ba: Array[Byte]): (Long, Long) = (
    toLong(ba.take(ba.length - 8)), // Take the msb part: anything before the last 8 bytes (take() seems happy with negative numbers ;))
    toLong(ba.takeRight(8))         // Take at most 8 bytes from the lsb part
   ) 

/** Convert the passed byte-array to a long. Expect arrays of size 8 and less. */
def toLong(ba: Array[Byte]) = ByteBuffer.wrap(zeroPad(ba)).getLong

/** prefix the passed array with 0 bytes. Expect arrays of size 8 and less,
    returns an array of length 8. */
def zeroPad(ba: Array[Byte]) = Array.fill[Byte](8 - ba.length)(0) ++ ba 

不像 Piotr 的模提议那么简洁,巴士值得一点脑力体操:)