从 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
来包含 仅 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
使用 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 的模提议那么简洁,巴士值得一点脑力体操:)
我正在构造 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
来包含 仅 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
使用 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 的模提议那么简洁,巴士值得一点脑力体操:)