最近在通过Redis hmset()方式来优化数据压缩和检索效率,本文主要分享一下在优化检索效率时遇到的一个困惑点。
直接上代码:
def toHexString(byteArray: Array[Byte]) = {
if (byteArray == null || byteArray.length < 1) {
throw new IllegalArgumentException("this byteArray must not be null or empty")
}
val hexString = new StringBuilder
for (i <- byteArray.indices) {
val c = byteArray(i) & 0xff
if (c < 0x10) {
hexString.append("0")
}
hexString.append(Integer.toHexString(0xff & byteArray(i)))
}
hexString.toString.toLowerCase
}
byteArray 是由一段字符串经过MD5加密后,输出的byte数组。首先介绍一下这两个概念:byteArray(i) & 0xff
计算结果是int类型,0x10
十进制是16。我起初难以理解为什么在接下来的循环中要将byteArray(i) & 0xff
再赋值给int类型c呢?byteArray(i)
是8位二进制,0xFF
转化成8位二进制就是11111111
,那么byteArray(i)&0xFF
不还是byteArray(i)
本身吗?有意思吗?
后来我写了一个demo
/**
* @Auther: fc.w
* @Date: 2019/3/8
*/
object AppTe {
def main(args: Array[String]): Unit = {
val a: Array[Byte] = Array[Byte](10)
a(0) = -127
println(a(0))
val c: Int = a(0) & 0xff
println(c)
// val unionId = "ohmdTtyH9H_DJS3dgYAarbo1Jfg4"
// val mdInst = MessageDigest.getInstance("MD5")
// val md = mdInst.digest(unionId.getBytes())
// val r = new Array[Byte](3)
// System.arraycopy(md, 13, r, 0, 3)
// for (i <- r.indices) {
// println(r(i))
// if (r(i) < 0) {
// println(r(i) +" "+ (r(i) & 127))
// r(i) = (r(i) & 127).toByte
// }
// }
// println(toHexString(r))
}
}
先打印a(0)
,在打印a(0) &0xff
后的值,本来我想结果应该都是 -127.
结果:
-127
129
到底是为什么呢?&0xff
反而不对了。
真不懂呀,后来往补码那个方向想了想。
记得在学计算机原理的时候,了解到计算机内的存储都是利用二进制的补码进行存储的。
复习一下,原码反码补码这三个概念
计算机存储数据机制:正数存储的二进制原码 ;负数存储的是二进制的补码;补码是负数的绝对值反码加1。
- 对于正数(00000001)原码来说,首位表示符号位,反码 补码都是本身
- 对于负数(100000001)原码来说,反码是对原码除了符号位之外作取反运算即(111111110)
- 补码是对反码作+1运算即(111111111)
概念就这么简单。
0xFF 是计算机十六进制的表示: 0x就是代表十六进制,A B C D E F 分别代表10 11 12 13 14 15 F就是15 一个F 代表4位二进制:可以看做 是 8 4 2 1。
0xFF的二进制表示就是:1111 1111。 高24位补0:0000 0000 0000 0000 0000 0000 1111 1111;
-12的补码与0xFF 进行与(&)操作 最后就是0000 0000 0000 0000 0000 0000 1111 0100
当将-127赋值给a(0)时候,a(0)作为一个byte类型,其计算机存储的补码是10000001(8位)。
将a(0) 作为int类型向控制台输出的时候,jvm作了一个补位的处理,因为int类型是32位所以补位后的补码就是1111111111111111111111111 10000001(32位),这个32位二进制补码表示的也是-127.
发现没有,虽然byte->int计算机背后存储的二进制补码由10000001(8位)转化成了1111111111111111111111111 10000001(32位)很显然这两个补码表示的十进制数字依然是相同的。
但是我做byte->int的转化 所有时候都只是为了保持 十进制的一致性吗?
不一定吧?好比我们拿到的文件流转成byte数组,难道我们关心的是byte数组的十进制的值是多少吗?我们关心的是其背后二进制存储的补码吧。
所以大家应该能猜到为什么byte类型的数字要&0xff再赋值给int类型,其本质原因就是想保持二进制补码的一致性。
当byte要转化为int的时候,高的24位必然会补1,这样,其二进制补码其实已经不一致了,&0xff可以将高的24位置为0,低8位保持原样。这样做的目的就是为了保证二进制数据的一致性。
当然啦,保证了二进制数据性的同时,如果二进制被当作byte和int来解读,其10进制的值必然是不同的,因为符号位位置已经发生了变化。
例2中,int c = a(0)&0xff;
a(0)&0xff=1111111111111111111111111 10000001 & 11111111=000000000000000000000000 10000001
,这个值算一下就是129,
所以c的输出的值就是129。有人问为什么上面的式子中a(0)不是8位而是32位,因为当系统检测到byte可能会转化成int或者说byte与int类型进行运算的时候,就会将byte的内存空间高位补1(也就是按符号位补位)扩充到32位,再参与运算。上面的0xff其实是int类型的字面量值,所以可以说byte与int进行运算。
网友评论