【问题标题】:Extract Longs from ByteBuffer (Java/Scala)从 ByteBuffer 中提取 Long (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 你完全正确,我写这篇文章的时候已经很晚了

标签: java arrays scala bytebuffer


【解决方案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
      猜你喜欢
      • 2010-10-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-06-14
      • 2014-06-03
      • 1970-01-01
      相关资源
      最近更新 更多