【2019-07-02 注:标题是Hex.encodeHexString(byte[] data) 的源码解析,但在实际测试过程中,改了方法名称,内部实现还是完全一样的。】
最近正在研究加密的相关方法和思想,有时候会用到byte类型的数组密钥或者密文,由于经常会遇到要将这鬼东西与数据库进行存取操作的情况,并且大多数情况都是将这样的byte数组转化为字符串进行存储的,因此用到了题目中的API,现对其源码实现进行总结和思考。
public static final char[] DIGITS_UPPER = { '0', '1', '2', '3', '4', '5',
'6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };public static String byte2HexString(final byte[] data) {final int l = data.length;final char[] out = new char[l << 1];// two characters form the hex value.for (int i = 0, j = 0; i < l; i++) {out[j++] = DIGITS_UPPER[(0xF0 & data[i]) >>> 4];out[j++] = DIGITS_UPPER[0x0F & data[i]];}return new String(out);
}
源码中我们注意到了DIGITS_UPPER这个char[] 数组,事实上这个byte2HexString的实现原理是:将data中的每个byte元素拆分为两个十六进制数,作为DIGITS_UPPER数组的下标,然后找到其对应的char字符,组装成一个char[]数组。
测试用例
public static void main(String[] args) {byte[] org = {1, 2, 3, 15, 10, 16, 17, 32, 0, 8};// 01 02 03 0F 0A 10 11 20 00 08String hexstr = DataConvertUtil.byte2HexString(org);System.out.println("转化结果:" + hexstr);}
执行结果:
转化结果:0102030F0A1011200008
结果是完全没有问题的,下面是图解(20190702追加图解):
首先data.length获取到data数组的元素个数(数组长度也就是元素的个数),然后再初始化一个新的char数组,out = new char[l << 1];这里值得注意的是char数组的长度,源码中用移位运算来表示,事实上在我上一篇文章中已经写得很明确,将一个数左移一位等价于将这个数 ×2,也就是说这个char数组的长度是data数组的二倍!为什么?原因如下:data中的每个元素都是byte类型,每个元素8个bit,分为高四位低四位,而每四位就可以表示一个0x0到0x15的十六进制数字,因此我们想将data中的每个元素用两个十六进制的数字表示就必须初始化一个是data长度两倍的char数组,这就是out对象的长度是[l << 1]的原因。
我们在源码汇中也可以看到这样的注释// two characters form the hex value.
紧接着,for循环。我们可以在脑海中创建这样一幅景象,两个并列的数组byte[] data和char[] out我们从data中取第一个元素,将其以二进制的数字表示出来,然后拆分高四位低四位,分别找到对应的两个十六进制的字符表示,然后再塞入char[]数组中,这就是这个for循环的功能!
0xF0 & data[i]根据“与运算”的逻辑规则,我们得知其目的是为了使得data[i]的低四位都变成0;然后>>>4无符号右移4位,取得高四位所表示的数值,我们可以换一种理解,“与”的目的是让低四位变成0,然后右移是为了去掉高四位后面的四个0,这样我们就拿到了高四位表示的值(我们已经知道二进制的四位数的取值范围就是0到15)。然后将这个高四位表示的值作为index找到对应的DIGITS_LOWER数组中的字符,并赋值给out[j],然后++两次,代表高位一次和低位一次,这样,我们就成功的完成了一组高四位低四位的拆分、替换、重组,然后只要循环data数组的长度就可以对每个byte元素进行相同的拆分重组操作了。
最后返回char[] out 。
这是一个非常简单实用的byte[]数组转化为String的小方法,API的开发者巧妙的利用'0'到'f'这十六个字符“代表” 0 到 15这十六个十六进制数,然后将byte元素逐一拆分并重组成一个新的char[]数组。
以上是本人经过实践之后对源码的分析和理解,喜欢的朋友记得点赞哦,作为一个工作仅两年的小菜鸟来说诚然是不小的鼓励,如果同行的朋友有什么问题和建议,还请不吝赐教!
【2019-07-02 追加更新
这里需要注意一个问题,即byte的取值范围问题,原来之前的知识有漏洞,忽视了下面这句代码的重要性:
out[j++] = DIGITS_UPPER[(0xF0 & data[i]) >>> 4];
这句代码的信息量很大,首先, >>> 是为了取byte元素的高四位,这个不难理解,但是为什么一定要先进行(0xF0 & data[i]) 运算?这个 & 运算是不是可有可无?
一开始我也认为是可有可无的,但实际上并不是!byte 变量的取值范围很小,是 [-128 ~ 127],同样是 1000 0000,int类型所表示的是128, 而byte类型锁表示的数是 -128 ,因此,必须先将 byte 变量进行扩容转正,那么实现方法就是 & 0xF0,否则的话,如果 一个 byte类型的F0 ,表示的数代表 -16,再 >>> 4 的话,就会得到 -1 ,很明显不符合要求,因此& 0xF0 这句代码必不可少。
另外一个问题是 j++ ,++ 在后代表先取值,在加 1,那么out[j++] = DIGITS_UPPER[(0xF0 & data[i]) >>> 4]; 就相当于下面这句代码:
out[j] = DIGITS_UPPER[(0xF0 & data[i]) >>> 4];
j++;
总之,千万别小看这些细节,搞不明白还真容易翻船!不得不佩服这些写底层API “轮子”的大神,能把各种语法和知识用到极致!
如果老铁们有幸看到这篇文章,而且也玩头条的话,欢迎关注我的头条号,不得不说,以前写博客是两三天一更,已经感觉很累,现在头条号要日更,还要篇篇精品,真的非常累,希望大家多多关注!头条号搜索 “Java圣斗士” ,谢谢老铁了!
】