admin

详述 JAVA byte[]与数值之间的转换
前言像数值与 byte[] 之间的转换,在平常 web 后台开发中可能用得不多,但是在协议传输的时候还是比较常见的...
扫描右侧二维码阅读全文
03
2018/04

详述 JAVA byte[]与数值之间的转换

前言

像数值与 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 中确实就可以了,转换代码如下:

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 位丢掉,所以可以直接上面那样写。大家可能看到更多的代码时下面这样的:

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 字串还原到指定位置,但是还是有需要注意的地方,先贴代码吧:

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 位清零就不会出现问题了。

总结

其他的shortlongbyte[]的转换类似,只要弄清了位操作的关系就一切OK了。PS:今天面试了一家游戏公司,本以为会问些数据结构算法以及类似上面位操作的问题,结果聊的过程中感觉并未涉及到上面的东西,笔试+面试一起将近才1个小时左右。笔试自身可能问题较大吧,以至于面试时面试官都未问及过多的技术问题。笔试全问答题,这还是头次遇到这种笔试,很多东西都记得不是很清楚了,但好在上面涉及的东西自己都有接触过,尤其是内存泄露那块是深有体验,不然还真没什么可答的内容写上去。最后一个附加题,和最新的 JAVA 10 有关,lzyer 大神在 JAVA 10 发布的时候对我说过出了个新特性就是用var来声明变量,当时自己在学习kotlin也有这个关键字,所以也未去关注 JAVA 10 的这个新特性,所以最后的这个 var 是否是弱类型 的附加题也只能空着了。。

Last modification:April 3rd, 2018 at 06:59 pm
If you think my article is useful to you, please feel free to appreciate

Leave a Comment