我有一个字节数组充满十六进制数字和打印它的简单方式是相当没有意义的,因为有许多不可打印的元素。我需要的是精确的十六进制形式:3a5f771c
当前回答
在存储查找表的很小代价下,这个实现非常简单和快速。
private static final char[] BYTE2HEX=(
"000102030405060708090A0B0C0D0E0F"+
"101112131415161718191A1B1C1D1E1F"+
"202122232425262728292A2B2C2D2E2F"+
"303132333435363738393A3B3C3D3E3F"+
"404142434445464748494A4B4C4D4E4F"+
"505152535455565758595A5B5C5D5E5F"+
"606162636465666768696A6B6C6D6E6F"+
"707172737475767778797A7B7C7D7E7F"+
"808182838485868788898A8B8C8D8E8F"+
"909192939495969798999A9B9C9D9E9F"+
"A0A1A2A3A4A5A6A7A8A9AAABACADAEAF"+
"B0B1B2B3B4B5B6B7B8B9BABBBCBDBEBF"+
"C0C1C2C3C4C5C6C7C8C9CACBCCCDCECF"+
"D0D1D2D3D4D5D6D7D8D9DADBDCDDDEDF"+
"E0E1E2E3E4E5E6E7E8E9EAEBECEDEEEF"+
"F0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF").toCharArray();
;
public static String getHexString(byte[] bytes) {
final int len=bytes.length;
final char[] chars=new char[len<<1];
int hexIndex;
int idx=0;
int ofs=0;
while (ofs<len) {
hexIndex=(bytes[ofs++] & 0xFF)<<1;
chars[idx++]=BYTE2HEX[hexIndex++];
chars[idx++]=BYTE2HEX[hexIndex];
}
return new String(chars);
}
其他回答
最简单的解决方案,没有外部库,没有数字常量:
public static String byteArrayToHex(byte[] a) {
StringBuilder sb = new StringBuilder(a.length * 2);
for(byte b: a)
sb.append(String.format("%02x", b));
return sb.toString();
}
javax.xml.bind.DatatypeConverter.printHexBinary()方法是用于XML绑定的Java体系结构(JAXB)的一部分,是将字节[]转换为十六进制字符串的方便方法。DatatypeConverter类还包括许多其他有用的数据操作方法。
在Java 8和更早的版本中,JAXB是Java标准库的一部分。它在Java 9中被弃用,在Java 11中被移除,这是将所有Java EE包移动到它们自己的库中的努力的一部分。说来话长。现在,javax.xml.bind还不存在,如果希望使用包含DatatypeConverter的JAXB,则需要从Maven安装JAXB API和JAXB运行时。
使用示例:
byte bytes[] = {(byte)0, (byte)0, (byte)134, (byte)0, (byte)61};
String hex = javax.xml.bind.DatatypeConverter.printHexBinary(bytes);
会导致:
000086003D
这个和这个答案是一样的。
private static String bytesToHexString(byte[] bytes, int length) {
if (bytes == null || length == 0) return null;
StringBuilder ret = new StringBuilder(2*length);
for (int i = 0 ; i < length ; i++) {
int b;
b = 0x0f & (bytes[i] >> 4);
ret.append("0123456789abcdef".charAt(b));
b = 0x0f & bytes[i];
ret.append("0123456789abcdef".charAt(b));
}
return ret.toString();
}
我更喜欢用这个:
final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
public static String bytesToHex(byte[] bytes, int offset, int count) {
char[] hexChars = new char[count * 2];
for ( int j = 0; j < count; j++ ) {
int v = bytes[j+offset] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
它是对公认答案的稍微灵活的改编。 就我个人而言,我既保留了公认的答案,也保留了这个重载,以便在更多的环境中使用。
下面是一些常见的选项,从简单(一行程序)到复杂(庞大的库)。如果您对性能感兴趣,请参阅下面的微基准测试。
选项1:代码片段-简单(仅使用JDK/Android)
选项1a: BigInteger
一个非常简单的解决方案是使用BigInteger的十六进制表示:
new BigInteger(1, someByteArray).toString(16);
注意,因为它处理的数字不是任意字节字符串,它将省略前导零——这可能是也可能不是你想要的(例如,3字节输入的000AE3 vs 0AE3)。这也非常慢,大约比选项2慢100倍。
选项1b: String.format()
使用%X占位符,String.format()能够将大多数基本类型(short, int, long)编码为十六进制:
String.format("%X", ByteBuffer.wrap(eightByteArray).getLong());
选项1c:整数/长(只有4/8字节数组)
如果你只有4个字节的数组,你可以使用Integer类的toHexString方法:
Integer.toHexString(ByteBuffer.wrap(fourByteArray).getInt());
这同样适用于8字节数组和Long
Long.toHexString(ByteBuffer.wrap(eightByteArray).getLong());
选项1d: JDK17+ HexFormat
最后,JDK 17通过HexFormat提供了直接的十六进制编码的一级支持:
HexFormat hex = HexFormat.of();
hex.formatHex(someByteArray)
选项2:代码片段-高级
这是一个完整的功能,复制和粘贴代码片段,支持大写/小写和小写。它经过优化以最小化内存复杂性和最大化性能,并且应该与所有现代Java版本(5+)兼容。
private static final char[] LOOKUP_TABLE_LOWER = new char[]{0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66};
private static final char[] LOOKUP_TABLE_UPPER = new char[]{0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46};
public static String encode(byte[] byteArray, boolean upperCase, ByteOrder byteOrder) {
// our output size will be exactly 2x byte-array length
final char[] buffer = new char[byteArray.length * 2];
// choose lower or uppercase lookup table
final char[] lookup = upperCase ? LOOKUP_TABLE_UPPER : LOOKUP_TABLE_LOWER;
int index;
for (int i = 0; i < byteArray.length; i++) {
// for little endian we count from last to first
index = (byteOrder == ByteOrder.BIG_ENDIAN) ? i : byteArray.length - i - 1;
// extract the upper 4 bit and look up char (0-A)
buffer[i << 1] = lookup[(byteArray[index] >> 4) & 0xF];
// extract the lower 4 bit and look up char (0-A)
buffer[(i << 1) + 1] = lookup[(byteArray[index] & 0xF)];
}
return new String(buffer);
}
public static String encode(byte[] byteArray) {
return encode(byteArray, false, ByteOrder.BIG_ENDIAN);
}
完整的源代码与Apache v2许可证和解码器可以在这里找到。
选项3:使用一个小型优化库:bytes-java
在从事上一个项目时,我创建了这个用于在Java中使用字节的小工具包。它没有外部依赖关系,并且与Java 7+兼容。它包括,除其他外,一个非常快速和经过良好测试的HEX en/解码器:
import at.favre.lib.bytes.Bytes;
...
Bytes.wrap(someByteArray).encodeHex()
你可以在Github上查看:bytes-java。
选项4:Apache Commons Codec
当然也有好的通用编解码器。(警告意见)当我在上面概述的项目中工作时,我分析了代码,非常失望;大量重复的无组织代码,过时的和外来的编解码器可能只对极少数有用,而且非常过度设计和缓慢的流行编解码器实现(特别是Base64)。因此,如果你想使用它或其他选择,我会做出明智的决定。无论如何,如果你仍然想使用它,这里有一个代码片段:
import org.apache.commons.codec.binary.Hex;
...
Hex.encodeHexString(someByteArray));
选项5:谷歌番石榴
通常情况下,您已经将番石榴作为依赖项。如果是这样,就用:
import com.google.common.io.BaseEncoding;
...
BaseEncoding.base16().lowerCase().encode(someByteArray);
选项6:Spring Security
如果你使用Spring框架和Spring Security,你可以使用以下方法:
import org.springframework.security.crypto.codec.Hex
...
new String(Hex.encode(someByteArray));
选择7:充气城堡
如果你已经使用了安全框架Bouncy Castle,你可以使用它的Hex util:
import org.bouncycastle.util.encoders.Hex;
...
Hex.toHexString(someByteArray);
不是真的选项8:Java 9+兼容性或“不使用jaxb javax/xml/bind/DatatypeConverter”
在以前的Java(8及以下)版本中,JAXB的Java代码是作为运行时依赖项包含的。由于Java 9和Jigsaw模块化,如果没有显式声明,你的代码不能访问模块之外的其他代码。所以要注意,如果你得到一个异常:
java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException
在Java 9+的JVM上运行时。如果是这样,那么将实现切换到上面的任何替代方案。再看看这个问题。
微基准测试
下面是对不同大小的字节数组进行编码的简单JMH微基准测试的结果。这些值是每秒操作数,所以越高越好。 请注意,微观基准测试通常并不代表现实世界的行为,所以对这些结果持保留态度。
| Name (ops/s) | 16 byte | 32 byte | 128 byte | 0.95 MB |
|----------------------|-----------:|-----------:|----------:|--------:|
| Opt1: BigInteger | 2,088,514 | 1,008,357 | 133,665 | 4 |
| Opt2/3: Bytes Lib | 20,423,170 | 16,049,841 | 6,685,522 | 825 |
| Opt4: Apache Commons | 17,503,857 | 12,382,018 | 4,319,898 | 529 |
| Opt5: Guava | 10,177,925 | 6,937,833 | 2,094,658 | 257 |
| Opt6: Spring | 18,704,986 | 13,643,374 | 4,904,805 | 601 |
| Opt7: BC | 7,501,666 | 3,674,422 | 1,077,236 | 152 |
| Opt8: JAX-B | 13,497,736 | 8,312,834 | 2,590,940 | 346 |
规格:JDK 8u202, i7-7700K, Win10, 24GB Ram。点击这里查看完整的基准测试。
基准更新2022
下面是使用当前的JMH 1.35、Java 17和更高端的计算机的结果
| Name (ops/s) | 16 byte | 32 byte | 128 byte | 0.95 MB |
|----------------------|-----------:|-----------:|----------:|--------:|
| Opt1: BigInteger | 2,941,403 | 1,389,448 | 242,096 | 5 |
| Opt2/3: Bytes Lib | 31,724,981 | 22,786,906 | 6,197,028 | 930 |
规格:JDK temurin 17.0.4, Ryzen 5900X, Win11, 24GB DDR4 Ram
推荐文章
- Eclipse调试器总是阻塞在ThreadPoolExecutor上,没有任何明显的异常,为什么?
- Java生成两个给定值之间的随机数
- 如何有效地从数组列表或字符串数组中删除所有空元素?
- 比较JUnit断言中的数组,简洁的内置方式?
- codestyle;把javadoc放在注释之前还是之后?
- 如何在Spring中定义List bean ?
- 将Set<T>转换为List<T>的最简洁的方法
- 在JavaScript中,什么相当于Java的Thread.sleep() ?
- 使用Java重命名文件
- URL从Java中的类路径加载资源
- .toArray(new MyClass[0]) or .toArray(new MyClass[myList.size()])?
- Hibernate中不同的保存方法之间有什么区别?
- Java 8流和数组操作
- Java Regex捕获组
- Openssl不被视为内部或外部命令