在 .NET Core 中,如何把byte[] 转换为 16 进制字符串?你能想到哪些方法?什么方式性能最好?今天和大家分享几种转换方式。
往往在处理字符串性能问题时,第一应该想到的是怎么想办法减少内存分配,怎么优化字符串构建。
下面就通过递进的方式介绍几种实现方式。
1. 使用StringBuilder
在需要做大量字符串拼接的场景中,我们第一就会想到StringBuilder,相比string类型来说StringBuilder更高效。在这个例子中,它通过一次性分配足够的内存,然后配合字节格式化方法AppendFormat进行转换,并逐个追加每个字节的 16 进制表明,以此减少内存分配的开销。
usingSystem;
usingSystem.Text;
publicclass BytesToHexString
{
publicstatic stringToHexStringStringBuilder(byte[] bytes)
{
StringBuilderhex = new StringBuilder(bytes.Length* 2);
foreach(byte b in bytes)
{
hex.AppendFormat(“{0:x2}”,b);
}
returnhex.ToString();
}
}
下面我们使用Benchmark对ToHexStringStringBuilder方法进行些基准测试,分别对字节数组长度为100、1000、10000个元素分别进行10000次测试,然后进行横向对比。

可以发现这个方法随着数组长度增加整体性能是在下降的。
2. 使用BitConverter
BitConverter 是 .NET中的内置类,它提供了一种简单的方式来转换基础数据类型为字符串。代码超级简洁,但是其本身只能输出固定格式如“0A-BC-99”,有连接符“-”并且字母都是大写,因此只适合简单需求,如果有复杂要求还行额外单独处理。
usingSystem;
publicclass BytesToHexString
{
publicstatic stringToHexStringBitConverter (byte[] bytes)
{
returnBitConverter.ToString(bytes);
}
}
下面我们再次使用Benchmark对ToHexStringBitConverter方法进行些基准测试,分别对字节数组长度为100、1000、10000个元素各进行10000次测试,进行横向对比。

和StringBuilder方式对比,性能得到大幅度提升。
3. 使用 Convert(.NET5+)
Convert是 .NET 中的内置类,Convert.ToHexString是在 .NET 5 中引入的方法,用于将字节数组直接转换为十六进制字符串,改方法设计之初就思考了性能,它在实现上减少了额外的内存分配和操作,因此它比BitConverter.ToString 更高效。但是其本身只能输出固定格式如“0ABC99”,没有连接符“-”并且字母都是大写。
usingSystem;
publicclass BytesToHexString
{
publicstatic stringToHexStringConvert (byte[] bytes)
{
returnConvert.ToHexString(bytes);
}
}
下面我们再次使用Benchmark对ToHexStringConvert方法进行些基准测试,分别对字节数组长度为100、1000、10000个元素各进行10000次测试,然后进行横向对比。

和BitConverter方式对比,性能也是大幅度提升。
4. 使用位运算
在将 byte[] 转换为 16 进制字符串时,每个字节会被转化为两个字符。因此,我们需要一个长度为 bytes.Length * 2 的字符数组来存储最终的 16 进制字符串。同时定义字符串hex = “0123456789abcdef”;这个字符串中包含了所有可能的 16 进制字符,接下来遍历循环把每个字节通过位运算分解为2个 4 位的部分(高 4 位和低 4 位),然后通过字符串hex将高4位转为16进制第一个字符,低4位转为第二个字符。以下是一个示例实现:
usingSystem;
publicclass BytesToHexString
{
publicstatic stringToHexStringBitOperation (byte[] bytes)
{
char[] hexChars = new char[bytes.Length* 2];
conststring hex = “0123456789abcdef”;
for(int i = 0;i < bytes.Length; i++)
{
hexChars[i *2] = hex[bytes[i] >> 4];
hexChars[i *2+ 1] = hex[bytes[i] & 0x0F];
}
returnnew string(hexChars);
}
}
下面我们再次使用Benchmark对ToHexStringBitOperation方法进行些基准测试,分别对字节数组长度为100、1000、10000个元素各进行10000次测试,然后进行横向对比。

虽然和BitConverter相比,性能提升3倍多,但是和Convert方式相比却有所差距。
如果对位运算不是很清楚的,可以留言,后面可以单独出一篇文章讲解一下。
5. 使用 unsafe 代码块(高级)
如果你需要极致的性能,并且可以接受 unsafe 代码,你可以使用指针来操作字节数组。这种方法可以极大地提高性能,但需要注意内存安全问题。
usingSystem;
publicclass BytesToHexString
{
publicstatic unsafestring ToHexStringUnsafe(byte[] bytes)
{
conststring hex = “0123456789ABCDEF”;
varhexChars = new char[bytes.Length* 2];
fixed(byte* bytePtr = bytes)
{
fixed(char* charPtr = hexChars)
{
byte* source = bytePtr;
char* dest = charPtr;
for(int i = 0;i < bytes.Length; i++)
{
byteb = source[i];
dest[i *2] = hex[b >> 4];
dest[i *2+ 1] = hex[b & 0x0F];
}
}
}
returnnew string(hexChars);
}
}
下面我们再次使用Benchmark对ToHexStringBitConverter方法进行些基准测试,分别对字节数组长度为100、1000、10000个元素各进行10000次测试,然后进行横向对比。

和位运算方式相比,并没有像前面的大幅提升,相差无几。
下面看看5种方法,整体对比情况:

通过上面一系列测试,我们可以得到如下总结:
灵活性:StringBuilder、位操作、unsafe 代码块 > BitConverter、Convert
性能:Convert > unsafe 代码块 > 位操作> BitConverter > StringBuilder
如果只是要把字节数组转化为字符串没有什么要求,那么直接选择官方自带方法Convert.ToHexString;如果对于输出格式有要求,则可以用位操作的方式自己实现个性化需求;当在极端特殊情况下可以思考unsafe 代码块方式。