转载

Apache工具包方法——Hex.encodeHexString(byte[] data)源码浅析 普通字符串与Hex编码字符串之间转换

最近正在研究加密的相关方法和思想,有时候会用到byte类型的数组密钥或者密文,由于经常会遇到要将这鬼东西与数据库进行存取操作的情况,并且大多数情况都是将这样的byte数组转化为字符串进行存储的,因此用到了题目中的API,现对其源码实现进行总结和思考。

  1. /**
  2. * Used to build output as Hex
  3. */
  4. private static final char[] DIGITS_LOWER =
  5. {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
  6. protected static char[] encodeHex(final byte[] data, final char[] DIGITS_LOWER) {
  7. final int l = data.length;
  8. final char[] out = new char[l << 1];
  9. // two characters form the hex value.
  10. for (int i = 0, j = 0; i < l; i++) {
  11. out[j++] = DIGITS_LOWER[(0xF0 & data[i]) >>> 4];
  12. out[j++] = DIGITS_LOWER[0x0F & data[i]];
  13. }
  14. return out;
  15. }

源码如上。

源码中我们注意到了DIGITS_LOWER这个char[] 数组,事实上这个encodeHex的实现原理是将data中的每个元素拆分为两个十六进制数,然后在DIGITS_LOWER中分别找到对应的表示字符,来组装成一个char[]数组,然后再通过new String(char[] chars) 方法转化为我们需要的“十六进制”字符串。

一步一步来看。

首先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[]数组。

普通字符串与Hex编码字符串之间转换

import java.io.UnsupportedEncodingException;
import org.apache.commons.codec.binary.Hex;

public class Example {
    /**
     * 将普通字符串转换成Hex编码字符串
     * 
     * @param dataCoding 编码格式,15表示GBK编码,8表示UnicodeBigUnmarked编码,0表示ISO8859-1编码
     * @param realStr 普通字符串
     * @return Hex编码字符串
     * @throws UnsupportedEncodingException 
     */
    public static String encodeHexStr(int dataCoding, String realStr) {
        String hexStr = null;
        if (realStr != null) {
            try {
                if (dataCoding == 15) {
                    hexStr = new String(Hex.encodeHex(realStr.getBytes("GBK")));
                } else if ((dataCoding & 0x0C) == 0x08) {
                    hexStr = new String(Hex.encodeHex(realStr.getBytes("UnicodeBigUnmarked")));
                } else {
                    hexStr = new String(Hex.encodeHex(realStr.getBytes("ISO8859-1")));
                }
            } catch (UnsupportedEncodingException e) {
                System.out.println(e.toString());
            }
        }
        return hexStr;
    }
    
    /**
     * 将Hex编码字符串转换成普通字符串
     * 
     * @param dataCoding 反编码格式,15表示GBK编码,8表示UnicodeBigUnmarked编码,0表示ISO8859-1编码
     * @param hexStr Hex编码字符串
     * @return 普通字符串
     */
    public static String decodeHexStr(int dataCoding, String hexStr) {
        String realStr = null;
        try {
            if (hexStr != null) {
                if (dataCoding == 15) {
                    realStr = new String(Hex.decodeHex(hexStr.toCharArray()), "GBK");
                } else if ((dataCoding & 0x0C) == 0x08) {
                    realStr = new String(Hex.decodeHex(hexStr.toCharArray()), "UnicodeBigUnmarked");
                } else {
                    realStr = new String(Hex.decodeHex(hexStr.toCharArray()), "ISO8859-1");
                }
            }
        } catch (Exception e) {
            System.out.println(e.toString());
        }
        
        return realStr;
    }

}


正文到此结束
Loading...