从 ByteBuffer 中提取 Long (Java/Scala)

Posted

技术标签:

【中文标题】从 ByteBuffer 中提取 Long (Java/Scala)【英文标题】:Extract Longs from ByteBuffer (Java/Scala) 【发布时间】:2016-09-28 23:47:06 【问题描述】:

我正在构建由两个 Longs 组成的 BigInt 数字,每个数字如下:

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 转换回两个 Longs(正是用作输入的两个值)。我不能只做以下事情:

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 个字节可用,但由于这个“填充”必须正确考虑两个数字的二进制补码,所以我没有成功。

【问题讨论】:

您是否要编写类似 BigDecimal 的代码? 请原谅我的无知,不知道 scala,但我怀疑 bb.getByte.getLong 是否有效。 bb.getByte不返回0,导致0.getLong @Andreas 你完全正确,我写这篇文章的时候已经很晚了 【参考方案1】:

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

【讨论】:

您还可以考虑在第一步中删除 Long 并从构造 2 个 BigInteger 开始并执行 val number2 = modulo*BigInt(243423) + BigInt(524543)。 (手动设置第一位没有技巧 - 更高级别的编程。不确定这是否适用于您的情况) 我花了一段时间才从数学上理解它,而我自己永远也想不出这个。但它运行良好且简洁,无需使用ByteBuffer。并且模 BigInt 可以重复使用。谢谢!【参考方案2】:

使用 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 的模数建议那么简洁,巴士值得进行小小的心理体操 :)

【讨论】:

【参考方案3】:

而不是使用ByteBuffer.wrap,你可以只使用足够大的allocateByteBuffer(即大小为17字节)和put(byte[])在正确位置的字节数组(即,使其与缓冲区的 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

【讨论】:

【参考方案4】:

您提出的提取方法有效,您只需将前导 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

【讨论】:

如果你把“1”作为第一个字节,你正在改变数字的值。您正在添加:2^128 = 340282366920938463463374607431768211456 在某些领域被认为很多:) @PiotrR,你是对的,当然,但 OP 只规定 number 必须 A) 始终为正,并且 B) 足够长以提取 msblsb准确/有效。没有说明number 值也应该反映msblsb 值。另一方面,也许这是未说明但有意的。这就是为什么我提供了第二个maskbb 解决方案,它从未更改的number 中提取msb/lsb

以上是关于从 ByteBuffer 中提取 Long (Java/Scala)的主要内容,如果未能解决你的问题,请参考以下文章

从长度为无符号整数的 ByteBuffer 中读取 UTF-8 字符串

如何从map中提取value并转换成long

图解ByteBuffer

Blob 存储上的 Azure 触发器,从图像 (Blob) 中提取 EXIF (lat/long/direction...) 数据

Kafka Connect 转换:从 json 字段中提取 Long 值并作为键插入

如何找出存储在 ByteBuffer 中的总字节数?