从 ByteBuffer 中提取 Long (Java/Scala)
Posted
技术标签:
【中文标题】从 ByteBuffer 中提取 Long (Java/Scala)【英文标题】:Extract Longs from ByteBuffer (Java/Scala) 【发布时间】:2016-09-28 23:47:06 【问题描述】:我正在构建由两个 Long
s 组成的 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
转换回两个 Long
s(正是用作输入的两个值)。我不能只做以下事情:
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
,你可以只使用足够大的allocate
ByteBuffer
(即大小为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
包含仅 msb
和lsb
位,那么您可以创建一个掩码来帮助提取。
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) 足够长以提取 msb
和 lsb
准确/有效。没有说明number
值也应该反映msb
和lsb
值。另一方面,也许这是未说明但有意的。这就是为什么我提供了第二个maskbb
解决方案,它从未更改的number
中提取msb
/lsb
。以上是关于从 ByteBuffer 中提取 Long (Java/Scala)的主要内容,如果未能解决你的问题,请参考以下文章
从长度为无符号整数的 ByteBuffer 中读取 UTF-8 字符串
Blob 存储上的 Azure 触发器,从图像 (Blob) 中提取 EXIF (lat/long/direction...) 数据