详述 JAVA byte[]与数值之间的转换 时间: 2018-04-03 18:53 分类: JAVA ####前言 像数值与 byte[] 之间的转换,在平常 web 后台开发中可能用得不多,但是在协议传输的时候还是比较常见的。之前用 JAVA 写 PCQQ 协议登录的时候就有用到(传输时QQ号码转成byte[]),只不过当时是直接找来代码放进工具类使用的,并未研究其原理,所以每次想到数值与 byte[] 之间的转换时,心里总是没底,更何况最近需要面试,不怕一万就怕万一,所以决定还是好好地来补下心里的这个漏洞。 ####int 转 byte[] 在转换之前,还是好好回想下在 JAVA 中一个整数占几个字节?一个字节占几位? 答案是:4个字节、每个字节占8位。 好了,有了上面知识,就可以对其进行转换了。 既然一个整数是占 4 个字节,那么,转换时肯定需要一个长度为 4 的 byte 数组。 好吧,其实到了这里,很多朋友可能还是云里雾里的,就怎么个将整数的 4 个字节放进长度为 4 的数组里呢? 这里我们又得了解一下计算机是怎么存储数字的了。。 在计算机里面,所有的东西都是以二进制形式存储和执行的,说白了就是0、1构成。一个字节占8位,也就是占了 8 个这样的 0 或者 1。 所以数字在计算机中也可以用二进制串来表示。比如一个整数:156215481 的二进制串在 JAVA 中可以通过以下方式获取(当然了,你自己笔算也OK啦): > Integer.toBinaryString(156215481) > 结果:1001010011111010100010111001 但是我们看到的并不是 32 位,因为前面的 0 没有输出。 我们把它补全如下: > 00001001 01001111 10101000 10111001 为了便于查看,将它每 8 位加个空格。 还有点要注意的就是在操作系统中,数字都是以补码的形式存储的,正数的原码、补码、反码都相同,负数的话,原码=符号位不变,补码其余位取反末尾+1,看下面例子: 给定一个数字 -88,通过上面的方式获得它的二进制串如下: > 11111111111111111111111110101000 可以看到最高位为 1,即符号位为 1,所以是一个负数,那么将它转为原码就是: > 10000000000000000000000001010111 + 1 > 10000000000000000000000001011000 `10000000000000000000000001011000`转成整数就是: -1 * (2^6 + 2^4 + 2^3) = -1 * (64 + 16 + 8) = -88 接下来就看如何将整数转为 byte[],整数转为 byte[] 其实就是将其二进制串中对应的位放进 byte[] 里,就以上面的`00001001 01001111 10101000 10111001`为例,我们要做的就是将其做以下处理(这里以大端方式存储,大小端的不同自行查阅资料): > 00001001 放进 byte[0] > 01001111 放进 byte[1] > 10101000 放进 byte[2] > 10111001 放进 byte[3] 现在问题就是如何将一个整数的二进制进行分割了,也就是如何分成 4 个字节。 用位运算再好不过了,其实这里的知识有点基础了,都是操作系统课程上学过的内容,就不过多解释了(其实也不好解释哈,和死记硬背的东西差不多,比如二进制01串怎么转为数字)。 好了,不多 BB 了,下面进行分割吧: > 获取 byte[0] 要存储的那 8 位:156215481 >> 24 结果是 000000000000000000000000 00001001 获取 byte[1] 要存储的那 8 位:156215481 >> 16 结果是 0000000000000000 00001001 01001111 获取 byte[2] 要存储的那 8 位:156215481 >> 8 结果是 00000000 00001001 01001111 10101000 获取 byte[3] 要存储的那 8 位: 结果是 00001001 01001111 10101000 10111001 上面的右移操作仅仅是把对应位置的 8 个字节放到最低位的 8 个字节上面而已,有人可能会问这样就可以了吗? 答案是:在 JAVA 中确实就可以了,转换代码如下: ```java public static byte[] intToBytes(int value) { byte[] src = new byte[4]; src[0] = (byte) ((value>>24)); src[1] = (byte) ((value>>16)); src[2] = (byte) ((value>>8)); src[3] = (byte) (value); return src; } ``` 因为 JAVA 中整数转 byte 是必须进行強转的,并且強转会将整形的 高32 位丢掉,所以可以直接上面那样写。大家可能看到更多的代码时下面这样的: ```java public static byte[] intToBytes(int value) { byte[] src = new byte[4]; src[0] = (byte) ((value>>24) & 0xFF); src[1] = (byte) ((value>>16)& 0xFF); src[2] = (byte) ((value>>8)&0xFF); src[3] = (byte) (value & 0xFF); return src; } ``` 后面的 `& 0xFF` 是什么鬼?将 `0xFF` 的二进制输出你就知道怎么回事了: > 00000000 00000000 00000000 11111111 `& 0xFF`操作不就是把一个数的高位全部清零么?和前面的強转操作,没什么区别。 ####byte[] 转 int 理解了上面的 int 转 byte[],下面的这个 byte[] 转 int 应该就不是什么难事了,整个操作就是将 byte[] 数组里的 01 字串还原到指定位置,但是还是有需要注意的地方,先贴代码吧: ```java public static int bytesToInt(byte[] src, int offset) { int value = (src[offset] & 0xFF) << 24 | (src[(offset + 1)] & 0xFF) << 16 | (src[(offset + 2)] & 0xFF) << 8 | src[(offset + 3)] & 0xFF; return value; } ``` 可以看到只不过是将原先的右移变成了左移,然后通过或运算将01字串放到对应的位置上而已,但是这里必须在左移之前进行`& 0xFF`操作,举个例子: 上面例子中数字的低 8 位:`10111001`,如果不进行`& 0xFF`操作,那么将一个 byte 进行左移操作的时候,会现将其隐式地转为 int 类型,而 `10111001`转成 int 的时候,会将其最高位的 1 当做符号位,与之对应的 int 就是 -71:`11111111111111111111111110111001`,可想而知用它来进行或运算,会把高 32 位全部置为 1,所以在左移之前,byte 转为 int 的时候`& 0xFF`将其高 32 位清零就不会出现问题了。 ####总结 其他的`short`、`long`与`byte[]`的转换类似,只要弄清了位操作的关系就一切OK了。PS:今天面试了一家游戏公司,本以为会问些数据结构算法以及类似上面位操作的问题,结果聊的过程中感觉并未涉及到上面的东西,笔试+面试一起将近才1个小时左右。笔试自身可能问题较大吧,以至于面试时面试官都未问及过多的技术问题。笔试全问答题,这还是头次遇到这种笔试,很多东西都记得不是很清楚了,但好在上面涉及的东西自己都有接触过,尤其是内存泄露那块是深有体验,不然还真没什么可答的内容写上去。最后一个附加题,和最新的 JAVA 10 有关,[lzyer](http://znotes.in "lzyer") 大神在 JAVA 10 发布的时候对我说过出了个新特性就是用`var`来声明变量,当时自己在学习`kotlin`也有这个关键字,所以也未去关注 JAVA 10 的这个新特性,所以最后的这个 `var 是否是弱类型` 的附加题也只能空着了。。 标签: 无