有人能解释一下将字节数组转换为十六进制字符串的过程吗?

4

最近我开始研究MD5哈希(在Java中),虽然我找到了一些算法和方法来帮助我完成这个过程,但我仍然不知道它是如何实际工作的。

例如,我从这个链接中找到了以下内容:

private static String convertToHex(byte[] data) {
    StringBuffer buf = new StringBuffer();
    for (int i = 0; i < data.length; i++) {
        int halfbyte = (data[i] >>> 4) & 0x0F;
        int two_halfs = 0;
        do {
            if ((0 <= halfbyte) && (halfbyte <= 9))
                buf.append((char) ('0' + halfbyte));
            else
                buf.append((char) ('a' + (halfbyte - 10)));
                halfbyte = data[i] & 0x0F;
            } while(two_halfs++ < 1);
        }
    return buf.toString();
}

我在Java中没有发现需要使用位移运算符,所以对此有点生疏。有没有人能够友好地解释(用简单的语言)以上代码如何进行转换?">>>"是什么意思?

我也在StackOverflow上找到了其他解决方案,例如这里这里,这些解决方案使用BigInteger而不是位移运算符。

try {
   String s = "TEST STRING";
   MessageDigest md5 = MessageDigest.getInstance("MD5");
   md5.update(s.getBytes(),0,s.length());
   String signature = new BigInteger(1,md5.digest()).toString(16);
   System.out.println("Signature: "+signature);

} catch (final NoSuchAlgorithmException e) {
   e.printStackTrace();
}

为什么这种方法也能起作用,哪种方法更有效?感谢您的时间。
4个回答

10
private static String convertToHex(byte[] data) {
    StringBuffer buf = new StringBuffer();
    for (int i = 0; i < data.length; i++) {

到目前为止...只是基本的设置并开始循环遍历数组中的所有字节

        int halfbyte = (data[i] >>> 4) & 0x0F;

将字节转换为十六进制后,无论在哪个进制下观察,其结果都是两个十六进制数字或8个二进制数字。上述语句向右移动高4位(>>>是无符号右移),并逻辑与 0000 1111,以便结果是一个整数,等于该字节的高4位(第一个十六进制数字)。

假设输入为23,则其二进制表示为0001 0111。右移和逻辑与操作将其转换为0000 0001。

        int two_halfs = 0;
        do {

这只是设置了 do/while 循环运行两次的条件

            if ((0 <= halfbyte) && (halfbyte <= 9))
                buf.append((char) ('0' + halfbyte));
            else
                buf.append((char) ('a' + (halfbyte - 10)));

这里我们展示了实际的十六进制数字,基本上只是用零或字符作为起点并向上移位到正确的字符。第一个if语句涵盖了所有的0-9数字,第二个if语句涵盖了所有的10-15数字(十六进制中的a-f)。

再次举例说明,0000 0001在十进制中等于1。我们进入了上面的if代码块,并将'0'字符加1得到字符'1',将其附加到字符串中并继续执行。

                halfbyte = data[i] & 0x0F;

现在我们把整数设为与字节的低位相等,并重复该操作。

同样,如果我们的输入是 23 ... 0001 0111,在逻辑 AND 之后就变成了只有 0000 0111,即十进制下的数字 7。按照上述逻辑重复操作,字符 '7' 就会被显示出来。

            } while(two_halfs++ < 1);

现在我们只需移动到数组中的下一个字节并重复上述步骤。

        }
    return buf.toString();
}
为了回答你接下来的问题,Java API已经在BigInteger中内置了一个基础转换工具。请参阅toString(int radix)文档。
我不知道Java API使用的实现方式,但我敢打赌Java实现比你最初发布的简单算法更加高效。

1
+1 鼓励你的努力并且比我更快完成了翻译。唯一需要补充的是关于位运算文档的参考链接:http://www.j2ee.me/docs/books/tutorial/java/nutsandbolts/op3.html - Welbog
当您将原始位向右移动4位时,您会得到4个零和原始输入的前4位。为什么在这种情况下需要执行&0x0F - 我没有看到任何区别(最后4位始终保持不变)?尽管如此,我对第二次执行&0x0F很清楚。 - sventevit

2
回答这个问题:

为什么那也可以工作

它不行。至少,不是与循环版本相同的方式。new BigInteger(...).toString(16)将不显示前导零,而前一个版本会。通常对于像写出字节数组这样的事情(特别是表示哈希之类的东西),您会希望有一个固定长度的输出,因此如果要使用该版本,则必须适当地填充它。

感谢您注意到这个区别。 - aberrant80

1

关于位移操作的详细解释,请查看以下SO问题中的答案 什么是位移(bit-shift)运算符,它们如何工作?

他似乎试图将一个单字节转换为小于16的数字,通过这样做,他可以轻松确定该字节表示的字符与代码

  if ((0 <= halfbyte) && (halfbyte <= 9))
                buf.append((char) ('0' + halfbyte));
            else
                buf.append((char) ('a' + (halfbyte - 10)));

这是一个简单的答案,但我本来就不那么聪明 =D


0

这些东西你不必自己编写,因为它们已经在apache-commons-codec中编写好了:

import org.apache.commons.codec.binary.Hex;
...
Hex.encodeHexString(byte[] array)

Hex类中有很多更有用的方法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接