Java代码将字节转换为十六进制

Viv*_*vek 172 java

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

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

pol*_*nts 295

    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 "
Run Code Online (Sandbox Code Playgroud)

也可以看看

  • java.util.Formatter 句法
    • %[flags][width]conversion
      • 标志'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]"
Run Code Online (Sandbox Code Playgroud)

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

    byte b = -1;
    System.out.println(Integer.toHexString(b));
    // prints "ffffffff"
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

两个因素结合起来应该使String.format解决方案更具优先性.

参考


jsi*_*let 62

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

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

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

这是什么意思?一些事情:首先,最重要的是,这意味着我们正在使用8位.因此,例如我们可以将数字2写为0000 0010.但是,由于它是二进制补码,我们写一个负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)
Run Code Online (Sandbox Code Playgroud)

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

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

therefore 2 = 0000 0010 = 0x02. 
Run Code Online (Sandbox Code Playgroud)

示例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)
Run Code Online (Sandbox Code Playgroud)

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

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

therefore: -2 = 1111 1110 = 0xFE.
Run Code Online (Sandbox Code Playgroud)


在Java中这样做

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

byte[] bytes = new byte[]{-2,2};
Run Code Online (Sandbox Code Playgroud)

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

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

StringBuffer buffer = new StringBuffer();
Run Code Online (Sandbox Code Playgroud)

步骤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)
Run Code Online (Sandbox Code Playgroud)

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

1111 1111 1111 1111 1111 1111 1111 1111 & 0xF = 0000 0000 0000 0000 0000 0000 0000 1111
therefore: 1111 = 0xF in hex. 
Run Code Online (Sandbox Code Playgroud)

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

Character.forDigit((bytes[0] >> 4) & 0xF, 16);
Run Code Online (Sandbox Code Playgroud)

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.  
Run Code Online (Sandbox Code Playgroud)

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

Character.forDigit((bytes[0] & 0xF), 16);
Run Code Online (Sandbox Code Playgroud)

将这一切放在一起我们可以将其作为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));
}
Run Code Online (Sandbox Code Playgroud)

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

  • 最佳答案!十六进制字符串到字节的对称实现将转换然后使用`Character.digit()`,如`(byte)((Character.digit(str.charAt(0),16)<< 4)+ Character.digit( str.charAt(1),16))` (4认同)

Cry*_*ark 21

我发现这样做的最快方法如下:

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();
}
Run Code Online (Sandbox Code Playgroud)

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

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
        }

    }
}
Run Code Online (Sandbox Code Playgroud)

编辑:刚刚找到的东西只有一个小的速度,并且在一条线上但与JRE 9 不兼容.使用风险自负

import javax.xml.bind.DatatypeConverter;

DatatypeConverter.printHexBinary(raw);
Run Code Online (Sandbox Code Playgroud)

  • Java 9中不再提供DatatypeConverter.危险的是使用它的代码将在Java 1.8或更早版本(Java 9,源代码设置为早期版本)下编译,但在Java 9下获得运行时异常. (2认同)
  • 如果您使用 char 数组作为 HEXES 常量,而不是 String 和 charAt(),您将获得大约 20% 的速度提升。 (2认同)

0x2*_*9A3 15

试试这种方式:

byte bv = 10;
String hexString = Integer.toHexString(bv);
Run Code Online (Sandbox Code Playgroud)

处理数组(如果我理解正确的话):

byte[] bytes = {9, 10, 11, 15, 16};
StringBuffer result = new StringBuffer();
for (byte b : bytes) {
    result.append(String.format("%02X ", b));
    result.append(" "); // delimiter
}
return result.toString();
Run Code Online (Sandbox Code Playgroud)

正如提到的多基因润滑剂,String.format()是正确的答案Integer.toHexString()(因为它以正确的方式处理负数).

  • 这将签署扩展,例如尝试`-1`. (2认同)

lyn*_*ynx 13

最佳解决方案是这个badass one-liner:

String hex=DatatypeConverter.printHexBinary(byte[] b);
Run Code Online (Sandbox Code Playgroud)

这里提到的

  • Java 9中不再提供DatatypeConverter.危险的是使用它的代码将在Java 1.8或更早版本(Java 9,源代码设置为早期版本)下编译,但在Java 9下获得运行时异常. (4认同)

Kil*_*oth 9

如果你想要一个恒定宽度的十六进制表示,即0A代替A,以便你可以明确地恢复字节,请尝试format():

StringBuilder result = new StringBuilder();
for (byte bb : byteArray) {
    result.append(String.format("%02X", bb));
}
return result.toString();
Run Code Online (Sandbox Code Playgroud)


Ali*_*ani 9

Java 17:引入java.util.HexFormat

Java 17 附带了一个实用程序,可将字节数组和数字转换为其十六进制对应项。假设我们有一个字节数组形式的“Hello World”的 MD5 摘要:

var md5 = MessageDigest.getInstance("md5");
md5.update("Hello world".getBytes(UTF_8));

var digest = md5.digest();
Run Code Online (Sandbox Code Playgroud)

现在我们可以使用该HexFormat.of().formatHex(byte[])方法将给定转换byte[]为其十六进制形式:

jshell> HexFormat.of().formatHex(digest)
$7 ==> "3e25960a79dbc69b674cd4ec67a72c62"
Run Code Online (Sandbox Code Playgroud)

withUpperCase()方法返回先前输出的大写版本:

jshell> HexFormat.of().withUpperCase().formatHex(digest)
$8 ==> "3E25960A79DBC69B674CD4EC67A72C62"
Run Code Online (Sandbox Code Playgroud)


ajr*_*ton 7

如果您乐意使用外部库,org.apache.commons.codec.binary.Hex该类有一个encodeHex方法,它接受byte[]并返回一个char[].此方法比format选项快很多,并且封装了转换的详细信息.还decodeHex提供了相反转换的方法.

  • 更简单的方法是使用内置函数javax.xml.bind.DatatypeConverter/parseHexBinary和printHexBinary.请参阅http://stackoverflow.com/questions/9655181/convert-from-byte-array-to-hex-string-in-java (4认同)
  • +1 此选项。Hex 还有一个方法 encodeHexString,它接受一个 byte[] 并返回一个 String。 (2认同)

Sve*_*kov 7

使用以下简短而简单的方式转换byte[]为十六进制字符串BigInteger

import java.math.BigInteger;

byte[] bytes = new byte[] {(byte)255, 10, 20, 30};
String hex = new BigInteger(1, bytes).toString(16);
System.out.println(hex); // ff0a141e
Run Code Online (Sandbox Code Playgroud)

怎么运行的?

内置系统类java.math.BigInteger类(java.math.BigInteger)与二进制和十六进制数据兼容:

  • 它有一个构造函数BigInteger(signum=1, byte[])来创建一个大整数byte[](设置其第一个参数signum= 1以正确处理负字节)
  • 使用BigInteger.toString(16)的大整数转换为十六进制字符串
  • 要解析一个十六进制数字,请使用new BigInteger("ffa74b", 16)-无法正确处理前导零

如果您可能想在十六进制结果中使用前导零,请检查其大小并在必要时添加缺失的零:

if (hex.length() % 2 == 1)
    hex = "0" + hex;
Run Code Online (Sandbox Code Playgroud)

笔记

使用new BigInteger(1, bytes)而不是new BigInteger(bytes),因为Java被“ 设计破坏 ”,并且byte数据类型不包含字节而是带符号的小整数[-128 ... 127]。如果第一个字节为负,则BigInteger假定您传递一个负大整数。只需1作为第一个参数(signum=1)传递即可。

从十六进制到byte[]的转换非常棘手:有时在生成的输出中输入前导零,应按以下方式清除它:

byte[] bytes = new BigInteger("ffa74b", 16).toByteArray();
if (bytes[0] == 0) {
    byte[] newBytes = new byte[bytes.length - 1];
    System.arraycopy(bytes, 1, newBytes, 0, newBytes.length);
    bytes = newBytes;
}
Run Code Online (Sandbox Code Playgroud)

最后要注意的是,如果byte[]具有多个前导零,则它们将丢失。

  • 如果前导字节的十进制值小于 16,您还将得到一个包含奇数个十六进制字符的字符串。 (2认同)

Jer*_*naw 5

这是到目前为止我发现运行最快的代码。我在23ms的长度为32的109015字节数组上运行了它。我在VM上运行它,因此它可能在裸机上运行得更快。

public static final char[] HEX_DIGITS =         {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};

public static char[] encodeHex( final byte[] data ){
    final int l = data.length;
    final char[] out = new char[l<<1];
    for( int i=0,j=0; i<l; i++ ){
        out[j++] = HEX_DIGITS[(0xF0 & data[i]) >>> 4];
        out[j++] = HEX_DIGITS[0x0F & data[i]];
    }
    return out;
}
Run Code Online (Sandbox Code Playgroud)

那你就可以做

String s = new String( encodeHex(myByteArray) );
Run Code Online (Sandbox Code Playgroud)


Wen*_*der 5

BigInteger n = new BigInteger(byteArray);
String hexa = n.toString(16);
Run Code Online (Sandbox Code Playgroud)


Jus*_*tas 5

您可以使用Bouncy Castle Provider库中的方法:

org.bouncycastle.util.encoders.Hex.toHexString(byteArray);
Run Code Online (Sandbox Code Playgroud)

Bouncy Castle Crypto软件包是密码算法的Java实现。该jar包含JCE提供程序和轻量级API,用于JDK 1.5至JDK 1.8的Bouncy Castle密码学API。

Maven的依赖:

<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15on</artifactId>
    <version>1.60</version>
</dependency>
Run Code Online (Sandbox Code Playgroud)

或来自Apache Commons Codec

org.apache.commons.codec.binary.Hex.encodeHexString(byteArray);
Run Code Online (Sandbox Code Playgroud)

Apache Commons Codec软件包包含用于各种格式(例如Base64和Hexadecimal)的简单编码器和解码器。除了这些广泛使用的编码器和解码器之外,编解码器包还维护了语音编码实用程序的集合。

Maven的依赖:

<dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    <version>1.11</version>
</dependency>
Run Code Online (Sandbox Code Playgroud)