将字节转换为十六进制

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

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

372076 次浏览

试试这个方法:

byte bv = 10;
String hexString = Integer.toHexString(bv);

处理数组(如果我没理解错的话):

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();

正如polygeneluants所提到的,与Integer.toHexString()相比,String.format()是正确的答案(因为它以正确的方式处理负数)。

使用

Integer.toHexString((int)b);

如果你想要一个恒定宽度的十六进制表示,即0A而不是A,这样你就可以明确地恢复字节,尝试format():

StringBuilder result = new StringBuilder();
for (byte bb : byteArray) {
result.append(String.format("%02X", bb));
}
return result.toString();
    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]conversion
      • 标志'0' -结果将是零填充
      • 宽度2
      • 转换'X' -结果被格式化为十六进制整数,大写
      • 李< / ul > < / > 李< / ul > < / >

      看看问题的文本,也有可能是这样要求的:

          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);这是可行的,但有一些注意事项。由于形参是int,因此将对byte实参进行扩展基元转换,这涉及到符号扩展。

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

      8位的byte在Java中是有符号的,被符号扩展为32位的int。为了有效地撤销这个符号扩展,可以用0xFF掩盖byte

          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"
      

      这两个因素结合起来应该使String.format解决方案更可取。

      参考文献

下面是一个将字节转换为十六进制的简单函数

   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();
}

如果存在性能问题,创建(并销毁)一堆String实例并不是一个好方法。

请忽略那些冗长(重复)参数检查语句(ifs)。那是为了(另一个)教育目的。

完整的maven项目:http://jinahya.googlecode.com/svn/trunk/com.googlecode.jinahya/hex-codec/

编码…

/**
* Encodes a single nibble.
*
* @param decoded the nibble to encode.
*
* @return the encoded half octet.
*/
protected static int encodeHalf(final int decoded) {


switch (decoded) {
case 0x00:
case 0x01:
case 0x02:
case 0x03:
case 0x04:
case 0x05:
case 0x06:
case 0x07:
case 0x08:
case 0x09:
return decoded + 0x30; // 0x30('0') - 0x39('9')
case 0x0A:
case 0x0B:
case 0x0C:
case 0x0D:
case 0x0E:
case 0x0F:
return decoded + 0x57; // 0x41('a') - 0x46('f')
default:
throw new IllegalArgumentException("illegal half: " + decoded);
}
}




/**
* Encodes a single octet into two nibbles.
*
* @param decoded the octet to encode.
* @param encoded the array to which each encoded nibbles are written.
* @param offset the offset in the array.
*/
protected static void encodeSingle(final int decoded, final byte[] encoded,
final int offset) {


if (encoded == null) {
throw new IllegalArgumentException("null encoded");
}


if (encoded.length < 2) {
// not required
throw new IllegalArgumentException(
"encoded.length(" + encoded.length + ") < 2");
}


if (offset < 0) {
throw new IllegalArgumentException("offset(" + offset + ") < 0");
}


if (offset >= encoded.length - 1) {
throw new IllegalArgumentException(
"offset(" + offset + ") >= encoded.length(" + encoded.length
+ ") - 1");
}


encoded[offset] = (byte) encodeHalf((decoded >> 4) & 0x0F);
encoded[offset + 1] = (byte) encodeHalf(decoded & 0x0F);
}




/**
* Decodes given sequence of octets into a sequence of nibbles.
*
* @param decoded the octets to encode
*
* @return the encoded nibbles.
*/
protected static byte[] encodeMultiple(final byte[] decoded) {


if (decoded == null) {
throw new IllegalArgumentException("null decoded");
}


final byte[] encoded = new byte[decoded.length << 1];


int offset = 0;
for (int i = 0; i < decoded.length; i++) {
encodeSingle(decoded[i], encoded, offset);
offset += 2;
}


return encoded;
}




/**
* Encodes given sequence of octets into a sequence of nibbles.
*
* @param decoded the octets to encode.
*
* @return the encoded nibbles.
*/
public byte[] encode(final byte[] decoded) {


return encodeMultiple(decoded);
}

解码…

/**
* Decodes a single nibble.
*
* @param encoded the nibble to decode.
*
* @return the decoded half octet.
*/
protected static int decodeHalf(final int encoded) {


switch (encoded) {
case 0x30: // '0'
case 0x31: // '1'
case 0x32: // '2'
case 0x33: // '3'
case 0x34: // '4'
case 0x35: // '5'
case 0x36: // '6'
case 0x37: // '7'
case 0x38: // '8'
case 0x39: // '9'
return encoded - 0x30;
case 0x41: // 'A'
case 0x42: // 'B'
case 0x43: // 'C'
case 0x44: // 'D'
case 0x45: // 'E'
case 0x46: // 'F'
return encoded - 0x37;
case 0x61: // 'a'
case 0x62: // 'b'
case 0x63: // 'c'
case 0x64: // 'd'
case 0x65: // 'e'
case 0x66: // 'f'
return encoded - 0x57;
default:
throw new IllegalArgumentException("illegal half: " + encoded);
}
}




/**
* Decodes two nibbles into a single octet.
*
* @param encoded the nibble array.
* @param offset the offset in the array.
*
* @return decoded octet.
*/
protected static int decodeSingle(final byte[] encoded, final int offset) {


if (encoded == null) {
throw new IllegalArgumentException("null encoded");
}


if (encoded.length < 2) {
// not required
throw new IllegalArgumentException(
"encoded.length(" + encoded.length + ") < 2");
}


if (offset < 0) {
throw new IllegalArgumentException("offset(" + offset + ") < 0");
}


if (offset >= encoded.length - 1) {
throw new IllegalArgumentException(
"offset(" + offset + ") >= encoded.length(" + encoded.length
+ ") - 1");
}


return (decodeHalf(encoded[offset]) << 4)
| decodeHalf(encoded[offset + 1]);
}




/**
* Encodes given sequence of nibbles into a sequence of octets.
*
* @param encoded the nibbles to decode.
*
* @return the encoded octets.
*/
protected static byte[] decodeMultiple(final byte[] encoded) {


if (encoded == null) {
throw new IllegalArgumentException("null encoded");
}


if ((encoded.length & 0x01) == 0x01) {
throw new IllegalArgumentException(
"encoded.length(" + encoded.length + ") is not even");
}


final byte[] decoded = new byte[encoded.length >> 1];


int offset = 0;
for (int i = 0; i < decoded.length; i++) {
decoded[i] = (byte) decodeSingle(encoded, offset);
offset += 2;
}


return decoded;
}




/**
* Decodes given sequence of nibbles into a sequence of octets.
*
* @param encoded the nibbles to decode.
*
* @return the decoded octets.
*/
public byte[] decode(final byte[] encoded) {


return decodeMultiple(encoded);
}

如果你乐于使用外部库,org.apache.commons.codec.binary.Hex类有一个encodeHex方法,该方法接受byte[]并返回char[]。这个方法比format选项快得多,并且封装了转换的细节。还附带了用于相反转换的decodeHex方法。

其他人已经报道了一般情况。但是如果你有一个已知形式的字节数组,例如MAC地址,那么你可以:

byte[] mac = { (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00 };


String str = String.format("%02X:%02X:%02X:%02X:%02X:%02X",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);

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

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

字节数据类型是8位有符号2的补整数。最小值为-128,最大值为127(包括)。在大型数组中,字节数据类型对于节省内存非常有用,因为节省内存实际上很重要。它们也可以用来代替int,它们的限制有助于澄清你的代码;变量的范围是有限的这一事实可以作为一种文档形式。

这是什么意思?首先,也是最重要的,它意味着我们正在使用8位。例如,我们可以把数字2写成0000 0010。然而,因为它是2的补,我们写一个- 2像这样:1111 1110。这也意味着转换为十六进制是非常直接的。也就是说,您只需将每个4位段直接转换为十六进制。注意,要理解这个公式中的负数,你首先需要理解2的补数。如果你还不理解two的补语,你可以在这里读到一个很好的解释:http://www.cs.cornell.edu/~tomf/notes/cps104/twoscomp.html


将二的补数转换为十六进制

一旦一个数是二的补数,那么把它转换成十六进制就非常简单了。一般来说,从二进制转换为十六进制是非常简单的,正如您将在接下来的两个示例中看到的,您可以直接从2的补码转换为十六进制。

例子

示例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中执行此操作

现在我们已经介绍了这个概念,您将发现我们可以通过一些简单的屏蔽和移动实现我们想要的效果。要理解的关键是您试图转换的字节已经在2的补码中。你自己不做这个转换。我认为这是这个问题的一个主要混淆点。以下面的字节数组为例:

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

我们只是手动将它们转换为十六进制,但我们如何在Java中做到这一点呢?方法如下:

创建一个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的十六进制数集。

接下来我们需要分离低阶位。因为我们想要的比特已经在正确的位置,我们可以把它们屏蔽掉:

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));
}

希望这个解释能让那些想知道在网上找到的许多例子中到底发生了什么的人更清楚。希望我没有犯任何严重的错误,但建议和纠正是非常欢迎的!

最快的方式,我已经发现这样做如下:

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);

这是一条非常快的路。不需要外部库。

final protected static char[] HEXARRAY = "0123456789abcdef".toCharArray();


public static String encodeHexString( byte[] bytes ) {


char[] hexChars = new char[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = HEXARRAY[v >>> 4];
hexChars[j * 2 + 1] = HEXARRAY[v & 0x0F];
}
return new String(hexChars);
}

这是我迄今为止发现的运行最快的代码。我在23ms内运行了109015个长度为32的字节数组。我在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;
}

然后你就可以做了

String s = new String( encodeHex(myByteArray) );

我不知道你说的字节字符串到底是什么意思,但这里有一些从字节到字符串的转换,反之亦然,当然官方文档上有更多

Integer intValue = 149;

对应的字节值为:

Byte byteValue = intValue.byteValue(); // this will convert the rightmost byte of the intValue to byte, because Byte is an 8 bit object and Integer is at least 16 bit, and it will give you a signed number in this case -107

从Byte变量中返回整数值:

Integer anInt = byteValue.intValue(); // This will convert the byteValue variable to a signed Integer
< p > 从字节和整数到十六进制字符串: < br > 我是这样做的:

Integer anInt = 149
Byte aByte = anInt.byteValue();


String hexFromInt = "".format("0x%x", anInt); // This will output 0x95
String hexFromByte = "".format("0x%x", aByte); // This will output 0x95
< p > 将字节数组转换为十六进制字符串: < br > 据我所知,没有简单的函数可以将某个Object的数组中的所有元素转换为另一个Object的元素,所以你必须自己做。可以使用以下函数:

从byte[]到String:

    public static String byteArrayToHexString(byte[] byteArray){
String hexString = "";


for(int i = 0; i < byteArray.length; i++){
String thisByte = "".format("%x", byteArray[i]);
hexString += thisByte;
}


return hexString;
}

从十六进制字符串到字节[]:

public static byte[] hexStringToByteArray(String hexString){
byte[] bytes = new byte[hexString.length() / 2];


for(int i = 0; i < hexString.length(); i += 2){
String sub = hexString.substring(i, i + 2);
Integer intVal = Integer.parseInt(sub, 16);
bytes[i / 2] = intVal.byteValue();
String hex = "".format("0x%x", bytes[i / 2]);
}


return bytes;
}

现在已经太迟了,但我希望这能帮助到其他人;)

BigInteger n = new BigInteger(byteArray);
String hexa = n.toString(16);

最好的解决方案是这样的一句话:

String hex=DatatypeConverter.printHexBinary(byte[] b);

如上所述在这里

你可以使用充气城堡供应商库中的方法:

org.bouncycastle.util.encoders.Hex.toHexString(byteArray);
Bouncy Castle Crypto包是一个Java实现 加密算法。这个jar包含JCE提供程序和 用于JDK 1.5的Bouncy Castle Cryptography API的轻量级API JDK 1.8 . < / p >

Maven的依赖:

<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.60</version>
</dependency>

或从Apache Commons Codec:

org.apache.commons.codec.binary.Hex.encodeHexString(byteArray);

Apache Commons Codec包包含简单的编码器和解码器 用于各种格式,如Base64和十六进制。除了 这些编码器和解码器被广泛使用,编解码器包也有 维护语音编码实用程序的集合

Maven的依赖:

<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.11</version>
</dependency>

这是你的快速方法:

    private static final String[] hexes = new String[]{
"00","01","02","03","04","05","06","07","08","09","0A","0B","0C","0D","0E","0F",
"10","11","12","13","14","15","16","17","18","19","1A","1B","1C","1D","1E","1F",
"20","21","22","23","24","25","26","27","28","29","2A","2B","2C","2D","2E","2F",
"30","31","32","33","34","35","36","37","38","39","3A","3B","3C","3D","3E","3F",
"40","41","42","43","44","45","46","47","48","49","4A","4B","4C","4D","4E","4F",
"50","51","52","53","54","55","56","57","58","59","5A","5B","5C","5D","5E","5F",
"60","61","62","63","64","65","66","67","68","69","6A","6B","6C","6D","6E","6F",
"70","71","72","73","74","75","76","77","78","79","7A","7B","7C","7D","7E","7F",
"80","81","82","83","84","85","86","87","88","89","8A","8B","8C","8D","8E","8F",
"90","91","92","93","94","95","96","97","98","99","9A","9B","9C","9D","9E","9F",
"A0","A1","A2","A3","A4","A5","A6","A7","A8","A9","AA","AB","AC","AD","AE","AF",
"B0","B1","B2","B3","B4","B5","B6","B7","B8","B9","BA","BB","BC","BD","BE","BF",
"C0","C1","C2","C3","C4","C5","C6","C7","C8","C9","CA","CB","CC","CD","CE","CF",
"D0","D1","D2","D3","D4","D5","D6","D7","D8","D9","DA","DB","DC","DD","DE","DF",
"E0","E1","E2","E3","E4","E5","E6","E7","E8","E9","EA","EB","EC","ED","EE","EF",
"F0","F1","F2","F3","F4","F5","F6","F7","F8","F9","FA","FB","FC","FD","FE","FF"
};


public static String byteToHex(byte b) {
return hexes[b&0xFF];
}

就像其他答案一样,我建议使用String.format()BigInteger。但是如果要将字节数组解释为大端二进制表示而不是二补二进制表示(带signum且可能的十六进制值范围未完全使用),则使用BigInteger(int signum, byte[] magnitude),而不是BigInteger (byte [] val)

例如,对于长度为8的字节数组,使用:

String.format("%016X", new BigInteger(1,bytes))

优点:

  • 前导零
  • 没有符号
  • 只有内置函数
  • 只有一行代码

劣势:

  • 也许有更有效的方法

例子:

byte[] bytes = new byte[8];
Random r = new Random();
System.out.println("big-endian       | two's-complement");
System.out.println("-----------------|-----------------");
for (int i = 0; i < 10; i++) {
r.nextBytes(bytes);
System.out.print(String.format("%016X", new BigInteger(1,bytes)));
System.out.print(" | ");
System.out.print(String.format("%016X", new BigInteger(bytes)));
System.out.println();
}

示例输出:

big-endian       | two's-complement
-----------------|-----------------
3971B56BC7C80590 | 3971B56BC7C80590
64D3C133C86CCBDC | 64D3C133C86CCBDC
B232EFD5BC40FA61 | -4DCD102A43BF059F
CD350CC7DF7C9731 | -32CAF338208368CF
82CDC9ECC1BC8EED | -7D3236133E437113
F438C8C34911A7F5 | -BC7373CB6EE580B
5E99738BE6ACE798 | 5E99738BE6ACE798
A565FE5CE43AA8DD | -5A9A01A31BC55723
032EBA783D2E9A9F | 032EBA783D2E9A9F
8FDAA07263217ABA | -70255F8D9CDE8546

使用BigIntegerbyte[]转换为十六进制字符串的简单方法:

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

它是如何工作的?

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

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

如果你想在十六进制结果中包含前导零,检查它的大小并在必要时添加缺少的0:

if (hex.length() % 2 == 1)
hex = "0" + hex;

笔记

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

返回从hex到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;
}

最后一个注意事项是,如果byte[]有几个前导零,它们将丢失。

如果你使用奇妙仙子,那么有:

package com.google.crypto.tink.subtle;


public final class Hex {
public static String encode(final byte[] bytes) { ... }
public static byte[] decode(String hex) { ... }
}

所以像这样的东西应该是有用的:

import com.google.crypto.tink.subtle.Hex;


byte[] bytes = {-1, 0, 1, 2, 3 };
String enc = Hex.encode(bytes);
byte[] dec = Hex.decode(enc)

如果你喜欢流,这里有一个格式-连接方法的单表达式版本:

String hex = IntStream.range(0, bytes.length)
.map(i -> bytes[i] & 0xff)
.mapToObj(b -> String.format("%02x", b))
.collect(Collectors.joining());

很遗憾没有像Arrays::streamUnsignedBytes这样的方法。

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();

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

jshell> HexFormat.of().formatHex(digest)
$7 ==> "3e25960a79dbc69b674cd4ec67a72c62"

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

jshell> HexFormat.of().withUpperCase().formatHex(digest)
$8 ==> "3E25960A79DBC69B674CD4EC67A72C62"

只是添加我的两分,因为我看到许多答案使用字符数组和/或使用StringBuilder实例,并声称是快速或更快。

由于我使用ASCII表组织有一个不同的想法,代码点48-57为0-9,代码点65-70为a-f,代码点97-102为a-f,我想测试哪个想法是最快的。

因为我已经好几年没有做过类似的事情了,所以我要做一个广泛的拍摄。我在不同大小的数组中使用了10亿字节(1M, 1K, 10),因此每个数组有1000倍1M字节,每个数组有1M倍1000字节,每个数组有100M倍10字节。

结果是1-F中的char数组胜出。使用char数组而不是StringBuilder作为输出也很容易(对象更少,不需要测试容量,在增长时不需要新数组或复制)。此外,当使用foreach (for(var b: bytes))循环时,你似乎会得到一个小的惩罚。

使用我的想法的版本大约是。每个数组1M字节时慢15%,每个数组1000字节时慢21%,每个数组10字节时慢18%。StringBuilder版本分别慢了210%、380%和310%。

这很糟糕,但也不是那么出乎意料,因为在第一级缓存中查找小数组胜过if和add... .(一次缓存访问+偏移量计算vs.一次if,一次跳转,一次添加->不确定你是否会跳)。

我的版本:

public static String bytesToHex(byte [] bytes) {
    

char [] result = new char [bytes.length * 2];
    

for(int index = 0; index < bytes.length; index++) {
int v = bytes[index];


int upper = (v >>> 4) & 0xF;
result[index * 2] = (char)(upper + (upper < 10 ? 48 : 65 - 10));
        

int lower = v & 0xF;
result[index * 2 + 1] = (char)(lower + (lower < 10 ? 48 : 65 - 10));
}
    

return new String(result);
}

PS:是的,我做了多次测试,每次都做了最好的测试,做了热身,也做了100亿字符的测试,以确保相同的图片... .