C# 字节数组和十六进制字符串的相互转化

本文最后更新于 2025年2月14日

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 9StringBuilderBitConverter 性能比.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 有较大提升。

参考


C# 字节数组和十六进制字符串的相互转化
https://refrain69.pages.dev/csharp-hexstring-bytearray-conversion/
发布于
2025年2月13日
更新于
2025年2月14日
许可协议