C# 如何高效地实现字节数组 (byte array) 和十六进制字符串 (hex string) 的相互转化呢?
ByteArray->HexString
手动实现
1 2 3 4 5 6 7 8 9
| public static string ByteArrayToHexString(byte[] bytes) { StringBuilder sb = new StringBuilder(); foreach (byte b in bytes) { sb.Append(b.ToString("x2")); } return sb.ToString(); }
|
手动实现优化
1 2 3 4 5 6 7 8 9 10 11 12 13
| public static string ByteArrayToHexString(byte[] bytes) { char[] c = new char[bytes.Length * 2]; byte b; for (int i = 0; i < bytes.Length; i++) { b = ((byte)(bytes[i] >> 4)); c[i * 2] = (char)(b > 9 ? b + 0x37 : b + 0x30); b = ((byte)(bytes[i] & 0xF)); c[i * 2 + 1] = (char)(b > 9 ? b + 0x37 : b + 0x30); } return new string(c); }
|
使用 BitConverter
1 2 3 4
| public static string ByteArrayToHexString(byte[] bytes) { return BitConverter.ToString(bytes).Replace("-", ""); }
|
使用 Convert.ToHexString (限.NET 5+)
1
| string hex = Convert.ToHexString(bytes);
|
使用 SoapHexBinary (限.NET Framework)
1
| string hex = new SoapHexBinary(bytes).ToString();
|
性能测试与比较
我们使用 BenchmarkDotNet
在.NET 9.0
和.NET Framework 4.8
上对上述方法进行性能测试。
.NET 9.0
测试代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; using System.Text;
namespace BenchMarkerNet9 { public class Program { private static void Main(string[] args) { var summary = BenchmarkRunner.Run<Benchmarker>(); } };
[MemoryDiagnoser] public class Benchmarker { public byte[] bytes = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10 };
[Benchmark] public string ByteArrayToHexString1() { StringBuilder sb = new StringBuilder(); foreach (byte b in bytes) { sb.Append(b.ToString("x2")); } return sb.ToString(); }
[Benchmark] public string ByteArrayToHexString2() { char[] c = new char[bytes.Length * 2]; byte b; for (int i = 0; i < bytes.Length; i++) { b = ((byte)(bytes[i] >> 4)); c[i * 2] = (char)(b > 9 ? b + 0x37 : b + 0x30); b = ((byte)(bytes[i] & 0xF)); c[i * 2 + 1] = (char)(b > 9 ? b + 0x37 : b + 0x30); } return new string(c); }
[Benchmark] public string ByteArrayToHexString3() { return BitConverter.ToString(bytes).Replace("-", string.Empty); }
[Benchmark] public string ByteArrayToHexString4() { return Convert.ToHexString(bytes); } } }
|
测试结果如下:
1 2 3 4 5 6
| | Method | Mean | Error | StdDev | Gen0 | Allocated | |---------------------- |----------:|---------:|---------:|-------:|----------:| | ByteArrayToHexString1 | 156.29 ns | 2.677 ns | 4.006 ns | 0.0429 | 808 B | | ByteArrayToHexString2 | 22.63 ns | 0.475 ns | 0.650 ns | 0.0093 | 176 B | | ByteArrayToHexString3 | 156.63 ns | 0.864 ns | 0.808 ns | 0.0110 | 208 B | | ByteArrayToHexString4 | 11.45 ns | 0.262 ns | 0.408 ns | 0.0047 | 88 B |
|
Mean : Arithmetic mean of all measurements 平均值
Error : Half of 99.9% confidence interval 误差
StdDev : Standard deviation of all measurements 方差
Gen0 : GC Generation 0 collects per 1000 operations 第 0 代 GC 次数
Allocated : Allocated memory per single operation (managed only, inclusive, 1KB = 1024B) 分配的内存
1 ns : 1 Nanosecond (0.000000001 sec) 纳秒
可见,Convert.ToHexString
是最快的,手动实现优化
次之,BitConverter
和手动实现
性能很差。
.NET Framework 4.8
测试代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; using System; using System.Runtime.Remoting.Metadata.W3cXsd2001; using System.Text;
namespace BenchMarker480 { public class Program { private static void Main(string[] args) { var summary = BenchmarkRunner.Run<Benchmarker>(); } };
[MemoryDiagnoser] public class Benchmarker { public byte[] bytes = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10 };
[Benchmark] public string ByteArrayToHexString1() { StringBuilder sb = new StringBuilder(); foreach (byte b in bytes) { sb.Append(b.ToString("x2")); } return sb.ToString(); }
[Benchmark] public string ByteArrayToHexString2() { char[] c = new char[bytes.Length * 2]; byte b; for (int i = 0; i < bytes.Length; i++) { b = ((byte)(bytes[i] >> 4)); c[i * 2] = (char)(b > 9 ? b + 0x37 : b + 0x30); b = ((byte)(bytes[i] & 0xF)); c[i * 2 + 1] = (char)(b > 9 ? b + 0x37 : b + 0x30); } return new string(c); }
[Benchmark] public string ByteArrayToHexString3() { return BitConverter.ToString(bytes).Replace("-", string.Empty); }
[Benchmark] public string ByteArrayToHexString4() { return new SoapHexBinary(bytes).ToString(); } } }
|
测试结果如下:
1 2 3 4 5 6
| | Method | Mean | Error | StdDev | Median | Gen0 | Allocated | |---------------------- |----------:|----------:|----------:|----------:|-------:|----------:| | ByteArrayToHexString1 | 539.00 ns | 22.907 ns | 67.543 ns | 513.00 ns | 0.1035 | 545 B | | ByteArrayToHexString2 | 37.79 ns | 0.656 ns | 0.613 ns | 37.91 ns | 0.0297 | 156 B | | ByteArrayToHexString3 | 194.82 ns | 2.988 ns | 3.557 ns | 194.53 ns | 0.0565 | 296 B | | ByteArrayToHexString4 | 339.67 ns | 5.352 ns | 4.745 ns | 340.74 ns | 0.1135 | 597 B |
|
可见,手动实现优化
是最快的,BitConverter
次之,SoapHexBinary
和手动实现
性能很差。
结论
Convert.ToHexString
是最快的,假设你的项目是.NET 5+,推荐使用。- 如果你的项目是.NET 5 以下,推荐使用
手动实现优化
。 - 纵向对比会发现,
.NET 9
的 StringBuilder
和 BitConverter
性能比.NET framework 4.8
有较大提升。
注意
- byte 数组有一个扩展方法
ToString()
,但是它返回的是 "System.Byte[]"
。这是因为 System.Byte
继承自 System.Object
,而 System.Object
有一个 ToString()
方法,且 System.Byte
没有重写这个方法。同样,Console.WriteLine(bytes)
也会返回 System.Byte[]
。 - 不可以使用
Encoding.xxx.GetString(bytes)
,因为这个方法会将字节数组按照指定的编码转换成字符串,而不是转换为十六进制字符串。 - 将 byte 数组写入 json 或其他文本文件时,建议使用
Convert.ToBase64String(bytes)
,不建议使用 hex string。
HexString->ByteArray
手动实现
1 2 3 4 5 6 7 8 9
| public static byte[] HexStringToByteArray(string hex) { byte[] bytes = new byte[hex.Length / 2]; for (int i = 0; i < hex.Length; i += 2) { bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16); } return bytes; }
|
基于 Linq 的实现
1 2 3 4 5 6 7
| public static byte[] HexStringToByteArray(string hex) { return Enumerable.Range(0, hex.Length) .Where(x => x % 2 == 0) .Select(x => Convert.ToByte(hex.Substring(x, 2), 16)) .ToArray(); }
|
使用 Convert.FromHexString (限.NET 5+)
1 2 3 4
| public static byte[] HexStringToByteArray(string hex) { return Convert.FromHexString(hex); }
|
使用 SoapHexBinary (限.NET Framework)
1 2 3 4
| public static byte[] HexStringToByteArray(string hex) { return new SoapHexBinary(hex).Value; }
|
性能测试与比较
环境同上。
.NET 9.0
测试代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running;
namespace BenchMarkerNet9 { public class Program { private static void Main(string[] args) { var summary = BenchmarkRunner.Run<Benchmarker>(); } };
[MemoryDiagnoser] public class Benchmarker { public string hex = "0102030405060708090A0B0C0D0E0F10";
[Benchmark] public byte[] HexToBytes1() { byte[] bytes = new byte[hex.Length / 2]; for (int i = 0; i < hex.Length; i += 2) { bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16); } return bytes; }
[Benchmark] public byte[] HexToBytes2() { return Enumerable.Range(0, hex.Length) .Where(x => x % 2 == 0) .Select(x => Convert.ToByte(hex.Substring(x, 2), 16)) .ToArray(); }
[Benchmark] public byte[] HexToBytes3() { return Convert.FromHexString(hex); } } }
|
测试结果如下:
1 2 3 4 5
| | Method | Mean | Error | StdDev | Gen0 | Allocated | |------------ |-----------:|----------:|-----------:|-------:|----------:| | HexToBytes1 | 172.904 ns | 8.5000 ns | 25.0625 ns | 0.0293 | 552 B | | HexToBytes2 | 200.896 ns | 3.9337 ns | 5.7660 ns | 0.0410 | 776 B | | HexToBytes3 | 7.851 ns | 0.1848 ns | 0.2270 ns | 0.0021 | 40 B |
|
可见,Convert.FromHexString
是最快的,手动实现
和基于Linq的实现
性能很差。
.NET Framework 4.8
测试代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; using System; using System.Linq; using System.Runtime.Remoting.Metadata.W3cXsd2001;
namespace BenchMarker480 { public class Program { private static void Main(string[] args) { var summary = BenchmarkRunner.Run<Benchmarker>(); } };
[MemoryDiagnoser] public class Benchmarker { public string hex = "0102030405060708090A0B0C0D0E0F10";
[Benchmark] public byte[] HexToBytes1() { byte[] bytes = new byte[hex.Length / 2]; for (int i = 0; i < hex.Length; i += 2) { bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16); } return bytes; }
[Benchmark] public byte[] HexToBytes2() { return Enumerable.Range(0, hex.Length) .Where(x => x % 2 == 0) .Select(x => Convert.ToByte(hex.Substring(x, 2), 16)) .ToArray(); }
[Benchmark] public byte[] HexToBytes3() { return SoapHexBinary.Parse(hex).Value; } } }
|
测试结果如下:
1 2 3 4 5
| | Method | Mean | Error | StdDev | Gen0 | Allocated | |------------ |-----------:|---------:|---------:|-------:|----------:| | HexToBytes1 | 414.5 ns | 13.73 ns | 40.48 ns | 0.0663 | 349 B | | HexToBytes2 | 586.6 ns | 3.08 ns | 2.57 ns | 0.0992 | 525 B | | HexToBytes3 | 1,285.0 ns | 18.47 ns | 17.27 ns | 0.3071 | 1610 B |
|
可见,手动实现
是最快的,基于Linq的实现
次之,SoapHexBinary
性能很差。
结论
Convert.FromHexString
是最快的,假设你的项目是.NET 5+,推荐使用。- 如果你的项目是.NET 5 以下,推荐使用
手动实现
。 - 纵向对比会发现,
.NET 9
的手动实现
性能和 Linq
性能比.NET framework 4.8
有较大提升。
参考