net core中byte数组如何高效转换为16进制字符串

在 .NET Core 中,如何把 byte[] 转换为 16 进制字符串?你能想到哪些方法?什么方式性能最好?今天和大家分享几种转换方式。

往往在处理字符串性能问题时,首先应该想到的是怎么想办法减少内存分配,怎么优化字符串构建。

下面就通过递进的方式介绍几种实现方式。

使用 StringBuilder

在需要做大量字符串拼接的场景中,我们首先就会想到StringBuilder,相比string类型来说StringBuilder更高效。在这个例子中,它通过一次性分配足够的内存,然后配合字节格式化方法AppendFormat进行转换,并逐个追加每个字节的 16 进制表示,以此减少内存分配的开销。

using System;
using System.Text;

public class BytesToHexString
{
	public static string ToHexStringStringBuilder(byte[] bytes)
	{
		var hex = new StringBuilder(bytes.Length * 2);
		foreach (byte b in bytes)
		{
			hex.AppendFormat("{0:x2}", b);
		}
		return hex.ToString();
	}
}

下面我们使用Benchmark对ToHexStringStringBuilder方法进行些基准测试,分别对字节数组长度为100、1000、10000个元素分别进行10000次测试,然后进行横向对比。

可以发现这个方法随着数组长度增加整体性能是在下降的。

使用 BitConverter

BitConverter 是 .NET 中的内置类,它提供了一种简单的方式来转换基础数据类型为字符串。代码非常简洁,但是其本身只能输出固定格式如“0A-BC-99”,有连接符“-”并且字母都是大写,因此只适合简单需求,如果有复杂要求还行额外单独处理。

using System;

public class BytesToHexString
{
	public static string ToHexStringBitConverter (byte[] bytes)
	{
		return BitConverter.ToString(bytes);
	}
}

下面我们再次使用Benchmark对ToHexStringBitConverter方法进行些基准测试,分别对字节数组长度为100、1000、10000个元素各进行10000次测试,进行横向对比。

StringBuilder方式对比,性能得到大幅度提升。

使用 Convert(.NET5+)

Convert是 .NET 中的内置类,Convert.ToHexString是在 .NET 5 中引入的方法,用于将字节数组直接转换为十六进制字符串,改方法设计之初就考虑了性能,它在实现上减少了额外的内存分配和操作,因此它比 BitConverter.ToString 更高效。但是其本身只能输出固定格式如“0ABC99”,没有连接符“-”并且字母都是大写。

using System;

public class BytesToHexString
{
	public static string ToHexStringConvert (byte[] bytes)
	{
		return Convert.ToHexString (bytes);
	}
}

下面我们再次使用Benchmark对ToHexStringConvert方法进行些基准测试,分别对字节数组长度为100、1000、10000个元素各进行10000次测试,然后进行横向对比。

BitConverter方式对比,性能也是大幅度提升。

使用位运算

在将 byte[] 转换为 16 进制字符串时,每个字节会被转化为两个字符。因此,我们需要一个长度为 bytes.Length * 2 的字符数组来存储最终的 16 进制字符串。同时定义字符串hex = "0123456789abcdef";这个字符串中包含了所有可能的 16 进制字符,接下来遍历循环把每个字节通过位运算分解为2个 4 位的部分(高 4 位和低 4 位),然后通过字符串hex将高4位转为16进制第一个字符,低4位转为第二个字符。以下是一个示例实现:

using System;

public class BytesToHexString
{
	public static string ToHexStringBitOperation (byte[] bytes)
	{
		char[] hexChars = new char[bytes.Length * 2];
		const string hex = "0123456789abcdef";

		for (int i = 0; i < bytes.length i hexcharsi 2='hex[bytes[i]'>> 4];
			hexChars[i * 2 + 1] = hex[bytes[i] & 0x0F];
		}

		return new string(hexChars);
	}
}

下面我们再次使用Benchmark对ToHexStringBitOperation方法进行些基准测试,分别对字节数组长度为100、1000、10000个元素各进行10000次测试,然后进行横向对比。

虽然和BitConverter相比,性能提升3倍多,但是和Convert方式相比却有所差距。

如果对位运算不是很明白的,可以留言,后面可以单独出一篇文章讲解一下。

使用 unsafe 代码块(高级)

如果你需要极致的性能,并且可以接受 unsafe 代码,你可以使用指针来操作字节数组。这种方法可以极大地提高性能,但需要注意内存安全问题。

using System;

public class BytesToHexString
{
	public static unsafe string ToHexStringUnsafe(byte[] bytes)
	{
		const string hex = "0123456789ABCDEF";
		var hexChars = 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 byte b='source[i];' desti 2='hex[b'>> 4];
					dest[i * 2 + 1] = hex[b & 0x0F];
				}
			}
		}

		return new string(hexChars);
	}
}

下面我们再次使用Benchmark对ToHexStringBitConverter方法进行些基准测试,分别对字节数组长度为100、1000、10000个元素各进行10000次测试,然后进行横向对比。

位运算方式相比,并没有像前面的大幅提升,相差无几。

下面看看5种方法,整体对比情况:

通过上面一系列测试,我们可以得到如下总结:

灵活性:StringBuilder、位操作、unsafe 代码块 > BitConverter、Convert

性能:Convert > unsafe 代码块 > 位操作 > BitConverter > StringBuilder

如果只是要把字节数组转化为字符串没有什么要求,那么直接选择官方自带方法Convert.ToHexString;如果对于输出格式有要求,则可以用位操作的方式自己实现个性化需求;当在极端特殊情况下可以考虑unsafe 代码块方式。

相关文章

C语言-指针与16进制

①规则16进制1、2、3、4、5、6、7、8、9、10用A表示、11用B表示、12用C表示、13用D表示、14用E表达、15用F表达、16进位。~②例子#includemain(){int a[10]...

深入理解二进制十进制十六进制

平常我们在上C语言课时,进制数有二进制,八进制,十进制,十六进制,但这里我们为什么不讲八进制,因为在平时的工作中,几乎不会用八进制。十进制:我们平时使用的数都是由0~9共十个数字组成的。例如1,9,1...

PLC编程必看!5种常见进制数解析,搞懂才能玩转PLC!

今天咱们聊聊PLC编程中那些“神秘”的进制数——二进制、十进制、十六进制…是不是一听就头大?别慌!看完这篇,保证你从“懵逼”变“大神”!1.二进制(Binary)●定义:仅由 0 和 1 组成的进制,...

C语言转义字符

字符集(Character Set)为每个字符分配了唯一的编号,我们不妨将它称为编码值。在C语言中,一个字符除了可以用它的实体(也就是真正的字符)表示,还可以用编码值表示。这种使用编码值来间接地表示字...

资深的程序员都是这样排查Java问题的,附工具单

摘要: 平时的工作中经常碰到很多疑难问题的处理,在解决问题的同时,有一些工具起到了相当大的作用,在此书写下来,一是作为笔记,可以让自己后续忘记了可快速翻阅,二是分享,希望看到此文的同学们可以拿出自己日...