问题

我有一个字节数组。我希望将该数组的每个字节字符串转换为其对应的十六进制值。

Java中是否有任何函数将字节数组转换为十六进制?


#1 热门回答(240 赞)

byte[] bytes = {-1, 0, 1, 2, 3 };
    StringBuilder sb = new StringBuilder();
    for (byte b : bytes) {
        sb.append(String.format("%02X ", b));
    }
    System.out.println(sb.toString());
    // prints "FF 00 01 02 03 "

也可以看看

  • java.util.Formatter语法%[flags] [width]转换标志'0' - 结果将为零填充宽度2转换'X' - 结果格式化为十六进制整数,大写

查看问题的文本,这也可能是所要求的:

String[] arr = {"-1", "0", "10", "20" };
    for (int i = 0; i < arr.length; i++) {
        arr[i] = String.format("%02x", Byte.parseByte(arr[i]));
    }
    System.out.println(java.util.Arrays.toString(arr));
    // prints "[ff, 00, 0a, 14]"

这里有几个答案用途Integer.toHexString(int);这是可行的,但有一些警告。由于参数是aint,因此对byte参数执行扩展的基元转换,这涉及符号扩展。

byte b = -1;
    System.out.println(Integer.toHexString(b));
    // prints "ffffffff"

用Java签名的8位byte符号扩展为32位int。要有效地撤消此符号扩展,可以屏蔽bytewith0xFF

byte b = -1;
    System.out.println(Integer.toHexString(b & 0xFF));
    // prints "ff"

使用toHexString的另一个问题是它没有用零填充:

byte b = 10;
    System.out.println(Integer.toHexString(b & 0xFF));
    // prints "a"

这两个因素相结合应该使得更加优先考虑。

###参考文献

  • JLS 4.2.1积分类型和值对于字节,从-128到127(含)
  • JLS 5.1.2扩展原始转换

#2 热门回答(52 赞)

我发帖是因为现有的答案都没有解释为什么他们的方法有效,我认为这对这个问题非常重要。在某些情况下,这会导致所提出的解决方案显得不必要地复杂和微妙。为了说明我将提供一个相当简单的方法,但我将提供更多细节来帮助说明为什么it可以工作的

首先,我们要做什么?我们希望将字节值(或字节数组)转换为表示ASCII中十六进制值的字符串。所以第一步是找出Java中的字节究竟是什么:

字节数据类型是8位带符号的二进制补码整数。它的最小值为-128,最大值为127(含)。字节数据类型可用于在大型阵列中保存内存,其中节省的内存实际上很重要。它们也可用于代替int,其限制有助于澄清你的代码;变量范围有限的事实可以作为一种文档形式。

这是什么意思?一些事情:首先也是最重要的是,这意味着我们正在使用8位.例如,我们可以将数字2写为0000 0010.但是,由于它是2的补码,我们写下这样的负2:1111 1110.还意味着转换为十六进制非常简单。也就是说,你只需将每个4位段直接转换为十六进制。请注意,要理解此方案中的负数,首先需要了解两个补码。如果你还没有理解两个补码,你可以在这里阅读一个很好的解释:http://www.cs.cornell.edu/~tomf/notes/cps104/twoscomp.html

#将两个补码转换为一般的十六进制

一旦数字是二进制补码,将它转换为十六进制就很简单了。通常,从二进制转换为十六进制非常简单,正如你将在接下来的两个示例中看到的那样,你可以直接从二进制补码转换为十六进制。

例子

**示例1:**将2转换为十六进制。

1)首先将2转换为2的二进制补码:

2 (base 10) = 0000 0010 (base 2)

2)现在将二进制转换为十六进制:

0000 = 0x0 in hex
0010 = 0x2 in hex

therefore 2 = 0000 0010 = 0x02.

**示例2:**转换为-2(以二进制补码)到十六进制。

1)首先将-2转换为二进制补码中的二进制:

-2 (base 10) = 0000 0010 (direct conversion to binary) 
               1111 1101 (invert bits)
               1111 1110 (add 1)
therefore: -2 = 1111 1110 (in two's complement)

2)现在转换为十六进制:

1111 = 0xF in hex
1110 = 0xE in hex

therefore: -2 = 1111 1110 = 0xFE.

#在Java中这样做

现在我们已经涵盖了这个概念,你会发现我们可以通过一些简单的屏蔽和移动实现我们想要的。要理解的关键是,你尝试转换的字节已经是两个补码.****你自己不做这个转换。我认为这是对这个问题的一个主要困惑点。以下面的字节数组为例:

byte[] bytes = new byte[]{-2,2};

我们只是手动将它们转换为十六进制,但是我们怎么能用Java做呢?就是这样:

**第1步:**创建一个StringBuffer来保存我们的计算。

StringBuffer buffer = new StringBuffer();

**步骤2:**分离高阶位,将它们转换为十六进制,并将它们附加到缓冲区

给定二进制数1111 1110,我们可以通过首先将它们移位4来隔离高阶位,然后将数字的其余部分归零。从逻辑上讲,这很简单,但是,由于符号扩展,Java(以及许多语言)中的实现细节引入了皱纹。实质上,当你移动一个字节值时,Java首先将你的值转换为整数,然后执行符号扩展。因此,虽然你希望1111 1110 >> 4为0000 1111,但实际上,在Java中它表示为二进制补码0xFFFFFFFF!

所以回到我们的例子:

1111 1110 >> 4 (shift right 4) = 1111 1111 1111 1111 1111 1111 1111 1111 (32 bit sign-extended number in two's complement)

然后我们可以用掩码隔离这些位:

1111 1111 1111 1111 1111 1111 1111 1111 & 0xF = 0000 0000 0000 0000 0000 0000 0000 1111
therefore: 1111 = 0xF in hex.

在Java中,我们可以一次完成所有这些:

Character.forDigit((bytes[0] >> 4) & 0xF, 16);

forDigit函数只是将传递给它的数字映射到十六进制数字0-F的集合上。

**步骤3:**接下来我们需要隔离低阶位。由于我们想要的位已经在正确的位置,我们可以将它们掩盖掉:

1111 1110 & 0xF = 0000 0000 0000 0000 0000 0000 0000 1110 (recall sign extension from before)
therefore: 1110 = 0xE in hex.

像以前一样,在Java中我们可以一次完成所有这些:

Character.forDigit((bytes[0] & 0xF), 16);

把这一切放在一起我们可以把它作为for循环并转换整个数组:

for(int i=0; i < bytes.length; i++){
    buffer.append(Character.forDigit((bytes[i] >> 4) & 0xF, 16));
    buffer.append(Character.forDigit((bytes[i] & 0xF), 16));
}

希望这个解释能让你清楚地知道你在网上找到的许多例子中究竟发生了什么。希望我没有发生任何令人震惊的错误,但建议和更正非常受欢迎!


#3 热门回答(17 赞)

最快的我发现这样做是由以下几点:

private static final String    HEXES    = "0123456789ABCDEF";

static String getHex(byte[] raw) {
    final StringBuilder hex = new StringBuilder(2 * raw.length);
    for (final byte b : raw) {
        hex.append(HEXES.charAt((b & 0xF0) >> 4)).append(HEXES.charAt((b & 0x0F)));
    }
    return hex.toString();
}

它比String.format快〜50倍。如果你想测试它:

public class MyTest{
    private static final String    HEXES        = "0123456789ABCDEF";

    @Test
    public void test_get_hex() {
        byte[] raw = {
            (byte) 0xd0, (byte) 0x0b, (byte) 0x01, (byte) 0x2a, (byte) 0x63,
            (byte) 0x78, (byte) 0x01, (byte) 0x2e, (byte) 0xe3, (byte) 0x6c,
            (byte) 0xd2, (byte) 0xb0, (byte) 0x78, (byte) 0x51, (byte) 0x73,
            (byte) 0x34, (byte) 0xaf, (byte) 0xbb, (byte) 0xa0, (byte) 0x9f,
            (byte) 0xc3, (byte) 0xa9, (byte) 0x00, (byte) 0x1e, (byte) 0xd5,
            (byte) 0x4b, (byte) 0x89, (byte) 0xa3, (byte) 0x45, (byte) 0x35,
            (byte) 0xd6, (byte) 0x10,
        };

        int N = 77777;
        long t;

        {
            t = System.currentTimeMillis();
            for (int i = 0; i < N; i++) {
                final StringBuilder hex = new StringBuilder(2 * raw.length);
                for (final byte b : raw) {
                    hex.append(HEXES.charAt((b & 0xF0) >> 4)).append(HEXES.charAt((b & 0x0F)));
                }
                hex.toString();
            }
            System.out.println(System.currentTimeMillis() - t); // 50
        }

        {
            t = System.currentTimeMillis();
            for (int i = 0; i < N; i++) {
                StringBuilder hex = new StringBuilder(2 * raw.length);
                for (byte b : raw) {
                    hex.append(String.format("%02X", b));
                }
                hex.toString();
            }
            System.out.println(System.currentTimeMillis() - t); // 2535
        }

    }
}

编辑:刚发现一些东西只是一个小而且保持在一条线但是不兼容与JRE 9.使用风险自负

import javax.xml.bind.DatatypeConverter;

DatatypeConverter.printHexBinary(raw);

原文链接