我有一个字节数组充满十六进制数字和打印它的简单方式是相当没有意义的,因为有许多不可打印的元素。我需要的是精确的十六进制形式:3a5f771c
当前回答
下面是一些常见的选项,从简单(一行程序)到复杂(庞大的库)。如果您对性能感兴趣,请参阅下面的微基准测试。
选项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
其他回答
HexFormat是在Java 17中加入的:
String hex = HexFormat.of().formatHex(array);
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
这个和这个答案是一样的。
public static String toHexString(byte[] bytes) {
StringBuilder sb = new StringBuilder();
if (bytes != null)
for (byte b:bytes) {
final String hexString = Integer.toHexString(b & 0xff);
if(hexString.length()==1)
sb.append('0');
sb.append(hexString);//.append(' ');
}
return sb.toString();//.toUpperCase();
}
使用DatatypeConverter:
public String toHexString(byte... bytes) {
return Optional.ofNullable(bytes)
.filter(bs->bs.length>0)
.map(DatatypeConverter::printHexBinary)
.map(str->IntStream.range(0, str.length())
.filter(i->(i%2)==0) // take every second index
.mapToObj(i->"0x" + str.substring(i, i+2))
.collect(Collectors.joining(" ")))
.orElse("");
}
在存储查找表的很小代价下,这个实现非常简单和快速。
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);
}
从这里的讨论,特别是这个答案,这是我目前使用的函数:
private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
public static String bytesToHex(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] = HEX_ARRAY[v >>> 4];
hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
}
return new String(hexChars);
}
My own tiny benchmarks (a million bytes a thousand times, 256 bytes 10 million times) showed it to be much faster than any other alternative, about half the time on long arrays. Compared to the answer I took it from, switching to bitwise ops --- as suggested in the discussion --- cut about 20% off of the time for long arrays. (Edit: When I say it's faster than the alternatives, I mean the alternative code offered in the discussions. Performance is equivalent to Commons Codec, which uses very similar code.)
2k20版本,相对于Java 9的压缩字符串:
private static final byte[] HEX_ARRAY = "0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII);
public static String bytesToHex(byte[] bytes) {
byte[] hexChars = new byte[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = HEX_ARRAY[v >>> 4];
hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
}
return new String(hexChars, StandardCharsets.UTF_8);
}
推荐文章
- 在流中使用Java 8 foreach循环移动到下一项
- 访问限制:'Application'类型不是API(必需库rt.jar的限制)
- 用Java计算两个日期之间的天数
- 如何配置slf4j-simple
- 在Jar文件中运行类
- 带参数的可运行?
- 我如何得到一个字符串的前n个字符而不检查大小或出界?
- 我可以在Java中设置enum起始值吗?
- Java中的回调函数
- c#和Java中的泛型有什么不同?和模板在c++ ?
- 在Java中,流相对于循环的优势是什么?
- Jersey在未找到InjectionManagerFactory时停止工作
- 在Java流是peek真的只是调试?
- Recyclerview不调用onCreateViewHolder
- 将JSON字符串转换为HashMap