Tom*_*lak 1289
或者:
public static string ByteArrayToString(byte[] ba)
{
StringBuilder hex = new StringBuilder(ba.Length * 2);
foreach (byte b in ba)
hex.AppendFormat("{0:x2}", b);
return hex.ToString();
}
Run Code Online (Sandbox Code Playgroud)
要么:
public static string ByteArrayToString(byte[] ba)
{
return BitConverter.ToString(ba).Replace("-","");
}
Run Code Online (Sandbox Code Playgroud)
还有更多的变种,例如这里.
反向转换将如下所示:
public static byte[] StringToByteArray(String hex)
{
int NumberChars = hex.Length;
byte[] bytes = new byte[NumberChars / 2];
for (int i = 0; i < NumberChars; i += 2)
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
return bytes;
}
Run Code Online (Sandbox Code Playgroud)
使用Substring
是结合使用的最佳选择Convert.ToByte
.有关更多信息,请参阅此答案.如果你需要更好的性能,你必须避免Convert.ToByte
在丢弃之前SubString
.
pat*_*dge 464
注:截至2015-08-20的新领导者.
我通过一些粗略的Stopwatch
性能测试运行了各种转换方法,一个带有随机句子的运行(n = 61,1000次迭代)和一个带有Project Gutenburg文本的运行(n = 1,238,957,150次迭代).以下是结果,大致从最快到最慢.所有测量均以刻度(10,000滴= 1毫秒)为单位,所有相关注释与[最慢] StringBuilder
实施进行比较.对于使用的代码,请参阅下面或测试框架repo,其中我现在维护用于运行此代码的代码.
警告:不要将这些属性用于任何混凝土; 它们只是样本数据的样本运行.如果您确实需要一流的性能,请在代表您的生产需求的环境中测试这些方法,并使用代表您将使用的数据.
unsafe
(经由CodesInChaos) (加到试验通过回购airbreather)
BitConverter
(通过Tomalak)
{SoapHexBinary}.ToString
(通过Mykroft)
{byte}.ToString("X2")
(使用foreach
)(来自Will Dean的回答)
{byte}.ToString("X2")
(使用{IEnumerable}.Aggregate
,需要System.Linq)(通过标记)
Array.ConvertAll
(使用string.Join
)(通过Will Dean)
Array.ConvertAll
(使用string.Concat
,需要.NET 4.0)(通过Will Dean)
{StringBuilder}.AppendFormat
(使用foreach
)(通过Tomalak)
{StringBuilder}.AppendFormat
(使用{IEnumerable}.Aggregate
,需要System.Linq)(源自Tomalak的答案)
查找表已经领先于字节操作.基本上,有一些形式的预计算任何给定的半字节或字节将以十六进制表示.然后,当你翻阅数据时,你只需查看下一部分,看看它会是什么十六进制字符串.然后以某种方式将该值添加到结果字符串输出中.对于长时间的字节操作,一些开发人员可能更难阅读,是表现最好的方法.
您最好的选择仍然是找到一些有代表性的数据并在类似生产的环境中进行尝试.如果您有不同的内存限制,您可能更喜欢使用较少分配的方法,但速度更快但消耗更多内存的方法.
随意玩我使用的测试代码.这里包含一个版本,但可以随意克隆存储库并添加自己的方法.如果您发现任何有趣的内容或希望帮助改进其使用的测试框架,请提交拉取请求.
Func<byte[], string>
)添加到/Tests/ConvertByteArrayToHexString/Test.cs.TestCandidates
同一个类中的返回值.GenerateTestInput
在同一个类中切换注释,确保运行所需的输入版本,句子或文本.static string ByteArrayToHexStringViaStringJoinArrayConvertAll(byte[] bytes) {
return string.Join(string.Empty, Array.ConvertAll(bytes, b => b.ToString("X2")));
}
static string ByteArrayToHexStringViaStringConcatArrayConvertAll(byte[] bytes) {
return string.Concat(Array.ConvertAll(bytes, b => b.ToString("X2")));
}
static string ByteArrayToHexStringViaBitConverter(byte[] bytes) {
string hex = BitConverter.ToString(bytes);
return hex.Replace("-", "");
}
static string ByteArrayToHexStringViaStringBuilderAggregateByteToString(byte[] bytes) {
return bytes.Aggregate(new StringBuilder(bytes.Length * 2), (sb, b) => sb.Append(b.ToString("X2"))).ToString();
}
static string ByteArrayToHexStringViaStringBuilderForEachByteToString(byte[] bytes) {
StringBuilder hex = new StringBuilder(bytes.Length * 2);
foreach (byte b in bytes)
hex.Append(b.ToString("X2"));
return hex.ToString();
}
static string ByteArrayToHexStringViaStringBuilderAggregateAppendFormat(byte[] bytes) {
return bytes.Aggregate(new StringBuilder(bytes.Length * 2), (sb, b) => sb.AppendFormat("{0:X2}", b)).ToString();
}
static string ByteArrayToHexStringViaStringBuilderForEachAppendFormat(byte[] bytes) {
StringBuilder hex = new StringBuilder(bytes.Length * 2);
foreach (byte b in bytes)
hex.AppendFormat("{0:X2}", b);
return hex.ToString();
}
static string ByteArrayToHexViaByteManipulation(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);
}
static string ByteArrayToHexViaByteManipulation2(byte[] bytes) {
char[] c = new char[bytes.Length * 2];
int b;
for (int i = 0; i < bytes.Length; i++) {
b = bytes[i] >> 4;
c[i * 2] = (char)(55 + b + (((b - 10) >> 31) & -7));
b = bytes[i] & 0xF;
c[i * 2 + 1] = (char)(55 + b + (((b - 10) >> 31) & -7));
}
return new string(c);
}
static string ByteArrayToHexViaSoapHexBinary(byte[] bytes) {
SoapHexBinary soapHexBinary = new SoapHexBinary(bytes);
return soapHexBinary.ToString();
}
static string ByteArrayToHexViaLookupAndShift(byte[] bytes) {
StringBuilder result = new StringBuilder(bytes.Length * 2);
string hexAlphabet = "0123456789ABCDEF";
foreach (byte b in bytes) {
result.Append(hexAlphabet[(int)(b >> 4)]);
result.Append(hexAlphabet[(int)(b & 0xF)]);
}
return result.ToString();
}
static readonly uint* _lookup32UnsafeP = (uint*)GCHandle.Alloc(_Lookup32, GCHandleType.Pinned).AddrOfPinnedObject();
static string ByteArrayToHexViaLookup32UnsafeDirect(byte[] bytes) {
var lookupP = _lookup32UnsafeP;
var result = new string((char)0, bytes.Length * 2);
fixed (byte* bytesP = bytes)
fixed (char* resultP = result) {
uint* resultP2 = (uint*)resultP;
for (int i = 0; i < bytes.Length; i++) {
resultP2[i] = lookupP[bytesP[i]];
}
}
return result;
}
static uint[] _Lookup32 = Enumerable.Range(0, 255).Select(i => {
string s = i.ToString("X2");
return ((uint)s[0]) + ((uint)s[1] << 16);
}).ToArray();
static string ByteArrayToHexViaLookupPerByte(byte[] bytes) {
var result = new char[bytes.Length * 2];
for (int i = 0; i < bytes.Length; i++)
{
var val = _Lookup32[bytes[i]];
result[2*i] = (char)val;
result[2*i + 1] = (char) (val >> 16);
}
return new string(result);
}
static string ByteArrayToHexViaLookup(byte[] bytes) {
string[] hexStringTable = new string[] {
"00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "0A", "0B", "0C", "0D", "0E", "0F",
"10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "1A", "1B", "1C", "1D", "1E", "1F",
"20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "2A", "2B", "2C", "2D", "2E", "2F",
"30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "3A", "3B", "3C", "3D", "3E", "3F",
"40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "4A", "4B", "4C", "4D", "4E", "4F",
"50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "5A", "5B", "5C", "5D", "5E", "5F",
"60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "6A", "6B", "6C", "6D", "6E", "6F",
"70", "71", "72", "73", "74", "75", "76", "77", "78", "79", "7A", "7B", "7C", "7D", "7E", "7F",
"80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "8A", "8B", "8C", "8D", "8E", "8F",
"90", "91", "92", "93", "94", "95", "96", "97", "98", "99", "9A", "9B", "9C", "9D", "9E", "9F",
"A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", "AA", "AB", "AC", "AD", "AE", "AF",
"B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "BA", "BB", "BC", "BD", "BE", "BF",
"C0", "C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9", "CA", "CB", "CC", "CD", "CE", "CF",
"D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7", "D8", "D9", "DA", "DB", "DC", "DD", "DE", "DF",
"E0", "E1", "E2", "E3", "E4", "E5", "E6", "E7", "E8", "E9", "EA", "EB", "EC", "ED", "EE", "EF",
"F0", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "FA", "FB", "FC", "FD", "FE", "FF",
};
StringBuilder result = new StringBuilder(bytes.Length * 2);
foreach (byte b in bytes) {
result.Append(hexStringTable[b]);
}
return result.ToString();
}
Run Code Online (Sandbox Code Playgroud)
添加了Waleed对分析的回答.蛮快.
添加string.Concat
Array.ConvertAll
了完整性的变体(需要.NET 4.0).与string.Join
版本相同.
测试回购包括更多变体,如StringBuilder.Append(b.ToString("X2"))
.没有打乱任何结果.foreach
比例更快{IEnumerable}.Aggregate
,但BitConverter
仍然获胜.
添加了Mykroft的SoapHexBinary
分析答案,获得了第三名.
添加了CodesInChaos的字节操作答案,它接管了第一名(在大块文本上大幅度提升).
添加了Nathan Moinvaziri的查找答案以及Brian Lambert博客的变体.两者都相当快,但没有在我使用的测试机器上取得领先(AMD Phenom 9750).
添加了@ CodesInChaos的新的基于字节的查找答案.它似乎在句子测试和全文测试中都处于领先地位.
为这个答案的回购添加了呼吸器的优化和unsafe
变体.如果你想在不安全的游戏中玩游戏,那么你可以在短曲目和大文本上获得超过任何先前最佳获胜者的巨大性能提升.
Myk*_*oft 234
有一个名为SoapHexBinary的类可以完全满足您的需求.
using System.Runtime.Remoting.Metadata.W3cXsd2001;
public static byte[] GetStringToBytes(string value)
{
SoapHexBinary shb = SoapHexBinary.Parse(value);
return shb.Value;
}
public static string GetBytesToString(byte[] value)
{
SoapHexBinary shb = new SoapHexBinary(value);
return shb.ToString();
}
Run Code Online (Sandbox Code Playgroud)
Cod*_*aos 137
在编写加密代码时,通常会避免数据相关的分支和表查找,以确保运行时不依赖于数据,因为数据相关的时序可能导致旁路攻击.
它也很快.
static string ByteToHexBitFiddle(byte[] bytes)
{
char[] c = new char[bytes.Length * 2];
int b;
for (int i = 0; i < bytes.Length; i++) {
b = bytes[i] >> 4;
c[i * 2] = (char)(55 + b + (((b-10)>>31)&-7));
b = bytes[i] & 0xF;
c[i * 2 + 1] = (char)(55 + b + (((b-10)>>31)&-7));
}
return new string(c);
}
Run Code Online (Sandbox Code Playgroud)
Ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl fhtagn
进入这里的人,放弃所有的希望
对奇怪的小提琴的解释:
bytes[i] >> 4
提取一个字节的高半字节bytes[i] & 0xF
提取一个字节的低半字节b - 10
< 0
的价值观b < 10
,这将成为一个十进制数字>= 0
对价值观b > 10
,这将成为从信A
来F
.i >> 31
由于符号扩展,使用带符号的32位整数提取符号.这将是-1
为了i < 0
和0
为i >= 0
.(b-10)>>31
将0
用于字母和-1
数字.0
,并且b
在10到15的范围内.我们想将它映射到A
(65)到F
(70),这意味着添加55('A'-10
).b
从0到9的范围映射到范围0
(48)到9
(57).这意味着它需要变成-7('0' - 55
).& -7
from (0 & -7) == 0
和(-1 & -7) == -7
.进一步考虑:
c
,因为测量表明从中计算它i
更便宜.i < bytes.Length
循环的上限允许JITter消除边界检查bytes[i]
,因此我选择了该变体.b
一个int允许从字节到字节的不必要的转换.Wil*_*ean 92
如果你想要更多的灵活性BitConverter
,但又不想要那些笨重的20世纪90年代风格的显式循环,那么你可以做到:
String.Join(String.Empty, Array.ConvertAll(bytes, x => x.ToString("X2")));
Run Code Online (Sandbox Code Playgroud)
或者,如果您使用的是.NET 4.0:
String.Concat(Array.ConvertAll(bytes, x => x.ToString("X2")));
Run Code Online (Sandbox Code Playgroud)
(后者来自对原帖的评论.)
Cod*_*aos 65
另一种基于查找表的方法.这个每个字节只使用一个查找表,而不是每个半字节的查找表.
private static readonly uint[] _lookup32 = CreateLookup32();
private static uint[] CreateLookup32()
{
var result = new uint[256];
for (int i = 0; i < 256; i++)
{
string s=i.ToString("X2");
result[i] = ((uint)s[0]) + ((uint)s[1] << 16);
}
return result;
}
private static string ByteArrayToHexViaLookup32(byte[] bytes)
{
var lookup32 = _lookup32;
var result = new char[bytes.Length * 2];
for (int i = 0; i < bytes.Length; i++)
{
var val = lookup32[bytes[i]];
result[2*i] = (char)val;
result[2*i + 1] = (char) (val >> 16);
}
return new string(result);
}
Run Code Online (Sandbox Code Playgroud)
我还测试了使用这种变型ushort
,struct{char X1, X2}
,struct{byte X1, X2}
在查找表中.
根据编译目标(x86,X64),它们具有大致相同的性能或略慢于此变体.
为了更高的性能,它的unsafe
兄弟姐妹:
private static readonly uint[] _lookup32Unsafe = CreateLookup32Unsafe();
private static readonly uint* _lookup32UnsafeP = (uint*)GCHandle.Alloc(_lookup32Unsafe,GCHandleType.Pinned).AddrOfPinnedObject();
private static uint[] CreateLookup32Unsafe()
{
var result = new uint[256];
for (int i = 0; i < 256; i++)
{
string s=i.ToString("X2");
if(BitConverter.IsLittleEndian)
result[i] = ((uint)s[0]) + ((uint)s[1] << 16);
else
result[i] = ((uint)s[1]) + ((uint)s[0] << 16);
}
return result;
}
public static string ByteArrayToHexViaLookup32Unsafe(byte[] bytes)
{
var lookupP = _lookup32UnsafeP;
var result = new char[bytes.Length * 2];
fixed(byte* bytesP = bytes)
fixed (char* resultP = result)
{
uint* resultP2 = (uint*)resultP;
for (int i = 0; i < bytes.Length; i++)
{
resultP2[i] = lookupP[bytesP[i]];
}
}
return new string(result);
}
Run Code Online (Sandbox Code Playgroud)
或者,如果您认为直接写入字符串是可以接受的:
public static string ByteArrayToHexViaLookup32UnsafeDirect(byte[] bytes)
{
var lookupP = _lookup32UnsafeP;
var result = new string((char)0, bytes.Length * 2);
fixed (byte* bytesP = bytes)
fixed (char* resultP = result)
{
uint* resultP2 = (uint*)resultP;
for (int i = 0; i < bytes.Length; i++)
{
resultP2[i] = lookupP[bytesP[i]];
}
}
return result;
}
Run Code Online (Sandbox Code Playgroud)
Bag*_*get 61
您可以使用BitConverter.ToString方法:
byte[] bytes = {0, 1, 2, 4, 8, 16, 32, 64, 128, 256}
Console.WriteLine( BitConverter.ToString(bytes));
Run Code Online (Sandbox Code Playgroud)
输出:
00-01-02-04-08-10-20-40-80-FF
更多信息:BitConverter.ToString方法(Byte [])
Wal*_*ssa 53
我今天刚遇到同样的问题,我遇到了这段代码:
private static string ByteArrayToHex(byte[] barray)
{
char[] c = new char[barray.Length * 2];
byte b;
for (int i = 0; i < barray.Length; ++i)
{
b = ((byte)(barray[i] >> 4));
c[i * 2] = (char)(b > 9 ? b + 0x37 : b + 0x30);
b = ((byte)(barray[i] & 0xF));
c[i * 2 + 1] = (char)(b > 9 ? b + 0x37 : b + 0x30);
}
return new string(c);
}
Run Code Online (Sandbox Code Playgroud)
来源:论坛帖子byte []数组到十六进制字符串(参见PZahra的帖子).我稍微修改了代码以删除0x前缀.
我对代码进行了一些性能测试,它比使用BitConverter.ToString()快了近八倍(根据patridge的帖子,速度最快).
ant*_*riz 32
更新日期:2022-04-17
using System;
string result = Convert.ToHexString(bytesToConvert);
Run Code Online (Sandbox Code Playgroud)
Thymine的比较似乎已经过时且不完整,特别是在 .NET 5 及其之后Convert.ToHexString
,所以我决定~~陷入字节到十六进制字符串的兔子洞~~创建一个新的、更新的比较,使用更多方法从两个问题的答案中进行比较这两个问题。
我使用BenchamrkDotNet而不是定制的基准测试脚本,希望这将使结果更加准确。
请记住,微基准测试永远无法代表实际情况,您应该进行测试。
我在内核为5.15.32 的Linux上运行了这些基准测试,并在配备2x8 GB DDR4 @ 2133 MHz的AMD Ryzen 5800H上运行。
请注意,整个基准测试可能需要很长时间才能完成 - 在我的机器上大约需要 40 分钟。
提到的所有方法(除非另有说明)仅关注大写输出。这意味着输出将类似于B33F69
, not b33f69
.
的输出Convert.ToHexString
始终为大写。不过,值得庆幸的是,与 配合使用时,性能没有任何显着下降ToLower()
,尽管unsafe
如果您担心的话,两种方法都会更快。
在某些方法中,有效地使字符串小写可能是一个挑战(尤其是那些具有位运算符魔法的方法),但在大多数情况下,在映射中更改参数或将字母从大写更改为小写X2
就足够了。x2
它是按 排序的Mean N=100
。参考点是StringBuilderForEachByte方法。
方法(单位为纳秒) | 平均值 N=10 | 比率N=10 | 平均值 N=100 | 比率N=100 | 平均值 N=500 | 比率N=500 | 平均 N=1k | 比率N=1k | 平均 N=10k | 比率N=10k | 平均 N=100k | 比率N=100k |
---|---|---|---|---|---|---|---|---|---|---|---|---|
StringBuilderAggregateBytesAppendFormat | 364.92 | 1.48 | 3,680.00 | 1.74 | 18,928.33 | 1.86 | 38,362.94 | 1.87 | 380,994.74 | 1.72 | 42,618,861.57 | 1.62 |
StringBuilderForEachAppendFormat | 309.59 | 1.26 | 3,203.11 | 1.52 | 20,775.07 | 2.04 | 41,398.07 | 2.02 | 426,839.96 | 1.93 | 37,220,750.15 | 1.41 |
字符串连接选择 | 310.84 | 1.26 | 2,765.91 | 1.31 | 13,549.12 | 1.33 | 28,691.16 | 1.40 | 304,163.97 | 1.38 | 63,541,601.12 | 2.41 |
字符串连接选择 | 301.34 | 1.22 | 2,733.64 | 1.29 | 14,449.53 | 1.42 | 29,174.83 | 1.42 | 307,196.94 | 1.39 | 32,877,994.95 | 1.25 |
字符串连接数组转换全部 | 279.21 | 1.13 | 2,608.71 | 1.23 | 13,305.96 | 1.30 | 27,207.12 | 1.32 | 295,589.61 | 1.34 | 62,950,871.38 | 2.39 |
StringBuilderAggregateBytesAppend | 276.18 | 1.12 | 2,599.62 | 1.23 | 12,788.11 | 1.25 | 26,043.54 | 1.27 | 255,389.06 | 1.16 | 27,664,344.41 | 1.05 |
StringConcatArrayConvertAll | 244.81 | 0.99 | 2,361.08 | 1.12 | 11,881.18 | 1.16 | 23,709.21 | 1.15 | 265,197.33 | 1.20 | 56,044,744.44 | 2.12 |
每个字节的字符串生成器 | 246.09 | 1.00 | 2,112.77 | 1.00 | 10,200.36 | 1.00 | 20,540.77 | 1.00 | 220,993.95 | 1.00 | 26,387,941.13 | 1.00 |
StringBuilderForEachBytePre分配 | 213.85 | 0.87 | 1,897.19 | 0.90 | 9,340.66 | 0.92 | 19,142.27 | 0.93 | 204,968.88 | 0.93 | 24,902,075.81 | 0.94 |
BitConverter替换 | 140.09 | 0.57 | 1,207.74 | 0.57 | 6,170.46 | 0.60 | 12,438.23 | 0.61 | 145,022.35 | 0.66 | 17,719,082.72 | 0.67 |
查找每半字节 | 63.78 | 0.26 | 421.75 | 0.20 | 1,978.22 | 0.19 | 3,957.58 | 0.19 | 35,358.21 | 0.16 | 4,993,649.91 | 0.19 |
查找与移位 | 53.22 | 0.22 | 311.56 | 0.15 | 1,461.15 | 0.14 | 2,924.11 | 0.14 | 26,180.11 | 0.12 | 3,771,827.62 | 0.14 |
While属性查找 | 41.83 | 0.17 | 308.59 | 0.15 | 1,473.10 | 0.14 | 2,925.66 | 0.14 | 28,440.28 | 0.13 | 5,060,341.10 | 0.19 |
LookupAndShiftAlphabetArray | 37.06 | 0.15 | 290.96 | 0.14 | 1,387.01 | 0.14 | 3,087.86 | 0.15 | 29,883.54 | 0.14 | 5,136,607.61 | 0.19 |
字节操作十进制 | 35.29 | 0.14 | 251.69 | 0.12 | 1,180.38 | 0.12 | 2,347.56 | 0.11 | 22,731.55 | 0.10 | 4,645,593.05 | 0.18 |
字节操作十六进制乘法 | 35.45 | 0.14 | 235.22 | 0.11 | 1,342.50 | 0.13 | 2,661.25 | 0.13 | 25,810.54 | 0.12 | 7,833,116.68 | 0.30 |
字节操作十六进制增量 | 36.43 | 0.15 | 234.31 | 0.11 | 1,345.38 | 0.13 | 2,737.89 | 0.13 | 26,413.92 | 0.12 | 7,820,224.57 | 0.30 |
本地查找时 | 42.03 | 0.17 | 223.59 | 0.11 | 1,016.93 | 0.10 | 1,979.24 | 0.10 | 19,360.07 | 0.09 | 4,150,234.71 | 0.16 |
查找和移动字母跨度 | 30:00 | 0.12 | 216.51 | 0.10 | 1,020.65 | 0.10 | 2,316.99 | 0.11 | 22,357.13 | 0.10 | 4,580,277.95 | 0.17 |
查找和移位字母跨度乘法 | 29.04 | 0.12 | 207.38 | 0.10 | 985.94 | 0.10 | 2,259.29 | 0.11 | 22,287.12 | 0.10 | 4,563,518.13 | 0.17 |
逐字节查找 | 32.45 | 0.13 | 205.84 | 0.10 | 951.30 | 0.09 | 1,906.27 | 0.09 | 18,311.03 | 0.08 | 3,908,692.66 | 0.15 |
LookupSpanPerByteSpan | 25.69 | 0.10 | 184.29 | 0.09 | 863.79 | 0.08 | 2,035.55 | 0.10 | 19,448.30 | 0.09 | 4,086,961.29 | 0.15 |
查找每字节跨度 | 27.03 | 0.11 | 184.26 | 0.09 | 866.03 | 0.08 | 2,005.34 | 0.10 | 19,760.55 | 0.09 | 4,192,457.14 | 0.16 |
Lookup32SpanUnsafeDirect | 16.90 | 0.07 | 99.20 | 0.05 | 436.66 | 0.04 | 895.23 | 0.04 | 8,266.69 | 0.04 | 1,506,058.05 | 0.06 |
Lookup32UnsafeDirect | 16.51 | 0.07 | 98.64 | 0.05 | 436.49 | 0.04 | 878.28 | 0.04 | 8,278.18 | 0.04 | 1,753,655.67 | 0.07 |
转换为十六进制字符串 | 19.27 | 0.08 | 64.83 | 0.03 | 295.15 | 0.03 | 585.86 | 0.03 | 5,445.73 | 0.02 | 1,478,363.32 | 0.06 |
ConvertToHexString.ToLower() | 45.66 | - | 175.16 | - | 787.86 | - | 1,516.65 | - | 13,939.71 | - | 2,620,046.76 | - |
该方法ConvertToHexString
无疑是最快的,在我看来,如果可以选择,应该始终使用它 - 它快速且干净。
using System;
string result = Convert.ToHexString(bytesToConvert);
Run Code Online (Sandbox Code Playgroud)
如果没有,我决定在下面重点介绍另外两种我认为有价值的方法。我决定不突出显示unsafe
方法,因为这样的代码可能不仅不安全,而且我参与过的大多数项目都不允许这样的代码。
第一个是LookupPerByteSpan
。该代码与此答案中CodesInChaos中的
代码几乎相同。这是最快的非方法基准测试。原始版本与此版本之间的区别在于,对较短的输入(最多 512 字节)使用堆栈分配。这使得该方法在这些输入上速度快了约 10%,但在较大输入上速度慢了约 5%。由于我处理的大多数数据都比较短,因此我选择了这一数据。也非常快,但是与所有其他方法相比,其映射的代码大小太大。LookupPerByte
unsafe
LookupSpanPerByteSpan
ReadOnlySpan<byte>
private static readonly uint[] Lookup32 = Enumerable.Range(0, 256).Select(i =>
{
string s = i.ToString("X2");
return s[0] + ((uint)s[1] << 16);
}).ToArray();
public string ToHexString(byte[] bytes)
{
var result = bytes.Length * 2 <= 1024
? stackalloc char[bytes.Length * 2]
: new char[bytes.Length * 2];
for (int i = 0; i < bytes.Length; i++)
{
var val = Lookup32[bytes[i]];
result[2 * i] = (char)val;
result[2 * i + 1] = (char)(val >> 16);
}
return new string(result);
}
Run Code Online (Sandbox Code Playgroud)
第二个是LookupAndShiftAlphabetSpanMultiply
。首先,我想提一下,这是我的创作。不过,我相信这种方法不仅速度很快,而且简单易懂。速度来自 C# 7.3 中发生的变化,其中ReadOnlySpan<byte>
返回常量数组初始化的声明方法new byte {1, 2, 3, ...}
被编译为程序的静态数据,因此省略了冗余内存分配。[来源]
private static ReadOnlySpan<byte> HexAlphabetSpan => new[]
{
(byte)'0', (byte)'1', (byte)'2', (byte)'3',
(byte)'4', (byte)'5', (byte)'6', (byte)'7',
(byte)'8', (byte)'9', (byte)'A', (byte)'B',
(byte)'C', (byte)'D', (byte)'E', (byte)'F'
};
public static string ToHexString(byte[] bytes)
{
var res = bytes.Length * 2 <= 1024 ? stackalloc char[bytes.Length * 2] : new char[bytes.Length * 2];
for (var i = 0; i < bytes.Length; ++i)
{
var j = i * 2;
res[j] = (char)HexAlphabetSpan[bytes[i] >> 4];
res[j + 1] = (char)HexAlphabetSpan[bytes[i] & 0xF];
}
return new string(res);
}
Run Code Online (Sandbox Code Playgroud)
所有方法的源代码、基准测试和此答案都可以作为要点在我的 GitHub 上找到。
Tru*_*ems 25
byte[]
(字节数组)转换为十六进制string
,请使用:System.Convert.ToHexString
var myBytes = new byte[100];
var myString = System.Convert.ToHexString(myBytes);
Run Code Online (Sandbox Code Playgroud)
string
为byte[]
,请使用:System.Convert.FromHexString
var myString = "E10B116E8530A340BCC7B3EAC208487B";
var myBytes = System.Convert.FromHexString(myString);
Run Code Online (Sandbox Code Playgroud)
小智 21
从 .NET 5 RC2 开始,您可以使用:
Convert.ToHexString(byte[] inArray)
它返回一个string
和Convert.FromHexString(string s)
返回一个byte[]
.可以使用带跨度参数的重载。
tne*_*tne 19
这是Tomalak非常受欢迎的答案(及后续编辑)修订版4的答案.
我将说明这种编辑是错误的,并解释为什么它可以被还原.在此过程中,您可能会了解一些关于某些内部构件的内容,并看到另一个例子,说明过早优化的真正含义以及它如何咬你.
tl; dr:只是使用Convert.ToByte
,String.Substring
如果你赶时间(下面的"原始代码"),如果你不想重新实现它,它是最好的组合Convert.ToByte
.Convert.ToByte
如果您需要性能,请使用不使用的更高级的内容(请参阅其他答案).千万不能使用别的以外String.Substring
与组合Convert.ToByte
,除非有人一些有趣的事情在这个答案的评论说这个.
警告:如果Convert.ToByte(char[], Int32)
在框架中实现了重载,则此答案可能会过时.这不太可能很快发生.
作为一般规则,我不太喜欢说"不要过早优化",因为没有人知道什么时候"过早".在决定是否优化时,您必须考虑的唯一事项是:"我是否有时间和资源来正确调查优化方法?".如果不这样做,那么它的太早,等到项目比较成熟,或者直到你所需要的性能(如果有实际需要,那么你会做的时间).与此同时,尽可能做最简单的事情.
原始代码:
public static byte[] HexadecimalStringToByteArray_Original(string input)
{
var outputLength = input.Length / 2;
var output = new byte[outputLength];
for (var i = 0; i < outputLength; i++)
output[i] = Convert.ToByte(input.Substring(i * 2, 2), 16);
return output;
}
Run Code Online (Sandbox Code Playgroud)
修订版4:
public static byte[] HexadecimalStringToByteArray_Rev4(string input)
{
var outputLength = input.Length / 2;
var output = new byte[outputLength];
using (var sr = new StringReader(input))
{
for (var i = 0; i < outputLength; i++)
output[i] = Convert.ToByte(new string(new char[2] { (char)sr.Read(), (char)sr.Read() }), 16);
}
return output;
}
Run Code Online (Sandbox Code Playgroud)
修订版避免String.Substring
使用StringReader
而是使用.给出的原因是:
编辑:您可以使用单个传递解析器来提高长字符串的性能,如下所示:
那么,看看参考代码String.Substring
,它显然已经"单通"了; 为什么不应该呢?它在字节级操作,而不是在代理对上操作.
但它会分配一个新的字符串,但是你需要分配一个传递给它Convert.ToByte
.此外,修订版中提供的解决方案在每次迭代时分配另一个对象(双字符数组); 您可以安全地将该分配放在循环之外并重用该数组以避免这种情况.
public static byte[] HexadecimalStringToByteArray(string input)
{
var outputLength = input.Length / 2;
var output = new byte[outputLength];
var numeral = new char[2];
using (var sr = new StringReader(input))
{
for (var i = 0; i < outputLength; i++)
{
numeral[0] = (char)sr.Read();
numeral[1] = (char)sr.Read();
output[i] = Convert.ToByte(new string(numeral), 16);
}
}
return output;
}
Run Code Online (Sandbox Code Playgroud)
每个十六进制numeral
表示使用两个数字(符号)的单个八位字节.
但是,为什么要打StringReader.Read
两次电话?只需调用它的第二个重载并让它一次读取两个char数组中的两个字符; 并将通话量减少两个.
public static byte[] HexadecimalStringToByteArray(string input)
{
var outputLength = input.Length / 2;
var output = new byte[outputLength];
var numeral = new char[2];
using (var sr = new StringReader(input))
{
for (var i = 0; i < outputLength; i++)
{
var read = sr.Read(numeral, 0, 2);
Debug.Assert(read == 2);
output[i] = Convert.ToByte(new string(numeral), 16);
}
}
return output;
}
Run Code Online (Sandbox Code Playgroud)
您剩下的是一个字符串阅读器,其唯一添加的"值"是_pos
您可以自己声明的并行索引(内部)(j
例如),冗余长度变量(内部_length
)和对输入的冗余引用string(内部_s
).换句话说,它没用.
如果您想知道如何Read
"读取",只需查看代码,它所做的就是调用String.CopyTo
输入字符串.剩下的就是保持账面管理,以维持我们不需要的价值.
所以,已经删除了字符串阅读器,并打电话给CopyTo
自己; 它更简单,更清晰,更高效.
public static byte[] HexadecimalStringToByteArray(string input)
{
var outputLength = input.Length / 2;
var output = new byte[outputLength];
var numeral = new char[2];
for (int i = 0, j = 0; i < outputLength; i++, j += 2)
{
input.CopyTo(j, numeral, 0, 2);
output[i] = Convert.ToByte(new string(numeral), 16);
}
return output;
}
Run Code Online (Sandbox Code Playgroud)
你真的需要一个j
以两个平行步长递增的索引i
吗?当然不是,只需乘以i
2(编译器应该能够优化为加法).
public static byte[] HexadecimalStringToByteArray_BestEffort(string input)
{
var outputLength = input.Length / 2;
var output = new byte[outputLength];
var numeral = new char[2];
for (int i = 0; i < outputLength; i++)
{
input.CopyTo(i * 2, numeral, 0, 2);
output[i] = Convert.ToByte(new string(numeral), 16);
}
return output;
}
Run Code Online (Sandbox Code Playgroud)
现在的解决方案是什么样的?完全像在开头一样,只使用String.Substring
分配字符串并将数据复制到其中,而不是使用中间数组将十六进制数字复制到其中,然后自己分配字符串并再次从中复制数据数组和字符串(当你在字符串构造函数中传递它时).如果字符串已经在实习池中,则可以优化第二个副本,但String.Substring
在这些情况下也可以避免它.
实际上,如果你String.Substring
再看一遍,你会发现它使用了一些关于如何构造字符串的低级内部知识来比你通常做的更快地分配字符串,并且它CopyTo
直接在那里内联使用相同的代码来避免呼叫开销.
String.Substring
手动方法
结论?如果你想使用Convert.ToByte(String, Int32)
(因为你不想自己重新实现这个功能),似乎没有办法击败String.Substring
; 你所做的只是在圆圈中运行,重新发明轮子(仅使用次优材料).
请注意,如果您不需要极端性能,使用Convert.ToByte
和String.Substring
是一个非常有效的选择.请记住:如果您有时间和资源来调查它是如何正常工作的,请选择替代方案.
如果有的话Convert.ToByte(char[], Int32)
,事情就会有所不同(有可能做我上面所描述的并完全避免String
).
我怀疑那些通过"避免String.Substring
" 来报告性能更好的人也会避免Convert.ToByte(String, Int32)
,如果你还需要表现,那么你应该这样做.看看无数的其他答案,发现所有不同的方法来做到这一点.
免责声明:我没有反编译最新版本的框架,以验证参考源是最新的,我认为是.
现在,这一切听起来都很好而且合乎逻辑,如果你已经成功到目前为止,这一点甚至是显而易见的.但这是真的吗?
Intel(R) Core(TM) i7-3720QM CPU @ 2.60GHz
Cores: 8
Current Clock Speed: 2600
Max Clock Speed: 2600
--------------------
Parsing hexadecimal string into an array of bytes
--------------------
HexadecimalStringToByteArray_Original: 7,777.09 average ticks (over 10000 runs), 1.2X
HexadecimalStringToByteArray_BestEffort: 8,550.82 average ticks (over 10000 runs), 1.1X
HexadecimalStringToByteArray_Rev4: 9,218.03 average ticks (over 10000 runs), 1.0X
Run Code Online (Sandbox Code Playgroud)
是!
对于替补框架的道具鹧to,它很容易入侵.使用的输入是以下SHA-1哈希重复5000次以生成100,000字节长的字符串.
209113288F93A9AB8E474EA78D899AFDBB874355
Run Code Online (Sandbox Code Playgroud)
玩得开心!(但要适度优化.)
drp*_*zen 16
使用查找表也可以解决此问题.这将需要编码器和解码器的少量静态存储器.然而,这种方法会很快:
我的解决方案使用1024个字节作为编码表,使用256个字节进行解码.
private static readonly byte[] LookupTable = new byte[] {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
private static byte Lookup(char c)
{
var b = LookupTable[c];
if (b == 255)
throw new IOException("Expected a hex character, got " + c);
return b;
}
public static byte ToByte(char[] chars, int offset)
{
return (byte)(Lookup(chars[offset]) << 4 | Lookup(chars[offset + 1]));
}
Run Code Online (Sandbox Code Playgroud)
private static readonly char[][] LookupTableUpper;
private static readonly char[][] LookupTableLower;
static Hex()
{
LookupTableLower = new char[256][];
LookupTableUpper = new char[256][];
for (var i = 0; i < 256; i++)
{
LookupTableLower[i] = i.ToString("x2").ToCharArray();
LookupTableUpper[i] = i.ToString("X2").ToCharArray();
}
}
public static char[] ToCharLower(byte[] b, int bOffset)
{
return LookupTableLower[b[bOffset]];
}
public static char[] ToCharUpper(byte[] b, int bOffset)
{
return LookupTableUpper[b[bOffset]];
}
Run Code Online (Sandbox Code Playgroud)
StringBuilderToStringFromBytes: 106148
BitConverterToStringFromBytes: 15783
ArrayConvertAllToStringFromBytes: 54290
ByteManipulationToCharArray: 8444
TableBasedToCharArray: 5651 *
Run Code Online (Sandbox Code Playgroud)
*这个解决方案
在解码过程中,IOException和IndexOutOfRangeException可能会发生(如果一个字符的值太大> 256).应该实现de/encoding流或数组的方法,这只是一个概念证明.
Cop*_*ick 16
补充@CodesInChaos(反向方法)
public static byte[] HexToByteUsingByteManipulation(string s)
{
byte[] bytes = new byte[s.Length / 2];
for (int i = 0; i < bytes.Length; i++)
{
int hi = s[i*2] - 65;
hi = hi + 10 + ((hi >> 31) & 7);
int lo = s[i*2 + 1] - 65;
lo = lo + 10 + ((lo >> 31) & 7) & 0x0f;
bytes[i] = (byte) (lo | hi << 4);
}
return bytes;
}
Run Code Online (Sandbox Code Playgroud)
说明:
& 0x0f
是支持小写字母
hi = hi + 10 + ((hi >> 31) & 7);
是相同的:
hi = ch-65 + 10 + (((ch-65) >> 31) & 7);
为"0" .."9"是相同hi = ch - 65 + 10 + 7;
它是hi = ch - 48
(这是因为0xffffffff & 7
).
对于'A'..'F'它是hi = ch - 65 + 10;
(这是因为0x00000000 & 7
).
对于'a'..'f',我们必须使用大数字,因此我们必须0
通过使用一些位来从默认版本中减去32 & 0x0f
.
65是代码 'A'
48是代码 '0'
7是ASCII表()之间'9'
和之间的字母数.'A'
...456789:;<=>?@ABCD...
这是一篇很棒的文章.我喜欢Waleed的解决方案.我还没有通过patridge的测试来运行它,但它看起来非常快.我还需要反向过程,将十六进制字符串转换为字节数组,因此我将其写为Waleed解决方案的逆转.不确定它是否比Tomalak的原始解决方案更快.同样,我也没有通过patridge的测试运行相反的过程.
private byte[] HexStringToByteArray(string hexString)
{
int hexStringLength = hexString.Length;
byte[] b = new byte[hexStringLength / 2];
for (int i = 0; i < hexStringLength; i += 2)
{
int topChar = (hexString[i] > 0x40 ? hexString[i] - 0x37 : hexString[i] - 0x30) << 4;
int bottomChar = hexString[i + 1] > 0x40 ? hexString[i + 1] - 0x37 : hexString[i + 1] - 0x30;
b[i / 2] = Convert.ToByte(topChar + bottomChar);
}
return b;
}
Run Code Online (Sandbox Code Playgroud)
Microsoft 的开发人员提供了一个很好、简单的转换:
public static string ByteArrayToString(byte[] ba)
{
// Concatenate the bytes into one long string
return ba.Aggregate(new StringBuilder(32),
(sb, b) => sb.Append(b.ToString("X2"))
).ToString();
}
Run Code Online (Sandbox Code Playgroud)
虽然上面的内容干净紧凑,但性能迷们会因为使用枚举器而尖叫。您可以通过Tomalak 原始答案的改进版本获得最佳性能:
public static string ByteArrayToString(byte[] ba)
{
StringBuilder hex = new StringBuilder(ba.Length * 2);
for(int i=0; i < ba.Length; i++) // <-- Use for loop is faster than foreach
hex.Append(ba[i].ToString("X2")); // <-- ToString is faster than AppendFormat
return hex.ToString();
}
Run Code Online (Sandbox Code Playgroud)
这是迄今为止我在这里看到的所有例程中最快的。不要只相信我的话...对每个例程进行性能测试并亲自检查其 CIL 代码。
小智 8
为什么要复杂?这在Visual Studio 2008中很简单:
C#:
string hex = BitConverter.ToString(YourByteArray).Replace("-", "");
Run Code Online (Sandbox Code Playgroud)
VB:
Dim hex As String = BitConverter.ToString(YourByteArray).Replace("-", "")
Run Code Online (Sandbox Code Playgroud)
不要在这里找到很多答案,但我发现了一个相当优化的(比接受的好4.5倍),十六进制字符串解析器的直接实现.首先,我的测试输出(第一批是我的实现):
Give me that string:
04c63f7842740c77e545bb0b2ade90b384f119f6ab57b680b7aa575a2f40939f
Time to parse 100,000 times: 50.4192 ms
Result as base64: BMY/eEJ0DHflRbsLKt6Qs4TxGfarV7aAt6pXWi9Ak58=
BitConverter'd: 04-C6-3F-78-42-74-0C-77-E5-45-BB-0B-2A-DE-90-B3-84-F1-19-F6-AB-5
7-B6-80-B7-AA-57-5A-2F-40-93-9F
Accepted answer: (StringToByteArray)
Time to parse 100000 times: 233.1264ms
Result as base64: BMY/eEJ0DHflRbsLKt6Qs4TxGfarV7aAt6pXWi9Ak58=
BitConverter'd: 04-C6-3F-78-42-74-0C-77-E5-45-BB-0B-2A-DE-90-B3-84-F1-19-F6-AB-5
7-B6-80-B7-AA-57-5A-2F-40-93-9F
With Mono's implementation:
Time to parse 100000 times: 777.2544ms
Result as base64: BMY/eEJ0DHflRbsLKt6Qs4TxGfarV7aAt6pXWi9Ak58=
BitConverter'd: 04-C6-3F-78-42-74-0C-77-E5-45-BB-0B-2A-DE-90-B3-84-F1-19-F6-AB-5
7-B6-80-B7-AA-57-5A-2F-40-93-9F
With SoapHexBinary:
Time to parse 100000 times: 845.1456ms
Result as base64: BMY/eEJ0DHflRbsLKt6Qs4TxGfarV7aAt6pXWi9Ak58=
BitConverter'd: 04-C6-3F-78-42-74-0C-77-E5-45-BB-0B-2A-DE-90-B3-84-F1-19-F6-AB-5
7-B6-80-B7-AA-57-5A-2F-40-93-9F
Run Code Online (Sandbox Code Playgroud)
base64和'BitConverter'd'行用于测试正确性.请注意它们是平等的.
实施:
public static byte[] ToByteArrayFromHex(string hexString)
{
if (hexString.Length % 2 != 0) throw new ArgumentException("String must have an even length");
var array = new byte[hexString.Length / 2];
for (int i = 0; i < hexString.Length; i += 2)
{
array[i/2] = ByteFromTwoChars(hexString[i], hexString[i + 1]);
}
return array;
}
private static byte ByteFromTwoChars(char p, char p_2)
{
byte ret;
if (p <= '9' && p >= '0')
{
ret = (byte) ((p - '0') << 4);
}
else if (p <= 'f' && p >= 'a')
{
ret = (byte) ((p - 'a' + 10) << 4);
}
else if (p <= 'F' && p >= 'A')
{
ret = (byte) ((p - 'A' + 10) << 4);
} else throw new ArgumentException("Char is not a hex digit: " + p,"p");
if (p_2 <= '9' && p_2 >= '0')
{
ret |= (byte) ((p_2 - '0'));
}
else if (p_2 <= 'f' && p_2 >= 'a')
{
ret |= (byte) ((p_2 - 'a' + 10));
}
else if (p_2 <= 'F' && p_2 >= 'A')
{
ret |= (byte) ((p_2 - 'A' + 10));
} else throw new ArgumentException("Char is not a hex digit: " + p_2, "p_2");
return ret;
}
Run Code Online (Sandbox Code Playgroud)
我尝试了一些东西unsafe
并将(明显多余的)字符到半字节if
序列移动到另一个方法,但这是它得到的最快.
(我承认这回答了问题的一半.我觉得字符串 - > byte []转换的代表性不足,而字节[] - >字符串角度似乎被很好地覆盖了.因此,这个答案.)
.NET 5 添加了Convert.ToHexString方法。
对于那些使用旧版本 .NET 的用户
internal static class ByteArrayExtensions
{
public static string ToHexString(this byte[] bytes, Casing casing = Casing.Upper)
{
Span<char> result = stackalloc char[0];
if (bytes.Length > 16)
{
var array = new char[bytes.Length * 2];
result = array.AsSpan();
}
else
{
result = stackalloc char[bytes.Length * 2];
}
int pos = 0;
foreach (byte b in bytes)
{
ToCharsBuffer(b, result, pos, casing);
pos += 2;
}
return result.ToString();
}
private static void ToCharsBuffer(byte value, Span<char> buffer, int startingIndex = 0, Casing casing = Casing.Upper)
{
uint difference = (((uint)value & 0xF0U) << 4) + ((uint)value & 0x0FU) - 0x8989U;
uint packedResult = ((((uint)(-(int)difference) & 0x7070U) >> 4) + difference + 0xB9B9U) | (uint)casing;
buffer[startingIndex + 1] = (char)(packedResult & 0xFF);
buffer[startingIndex] = (char)(packedResult >> 8);
}
}
public enum Casing : uint
{
// Output [ '0' .. '9' ] and [ 'A' .. 'F' ].
Upper = 0,
// Output [ '0' .. '9' ] and [ 'a' .. 'f' ].
Lower = 0x2020U,
}
Run Code Online (Sandbox Code Playgroud)
改编自 .NET 存储库 https://github.com/dotnet/runtime/blob/v5.0.3/src/libraries/System.Private.CoreLib/src/System/Convert.cs https://github.com/dotnet /runtime/blob/v5.0.3/src/libraries/Common/src/System/HexConverter.cs
Waleed Eissa 代码的反函数(十六进制字符串到字节数组):
public static byte[] HexToBytes(this string hexString)
{
byte[] b = new byte[hexString.Length / 2];
char c;
for (int i = 0; i < hexString.Length / 2; i++)
{
c = hexString[i * 2];
b[i] = (byte)((c < 0x40 ? c - 0x30 : (c < 0x47 ? c - 0x37 : c - 0x57)) << 4);
c = hexString[i * 2 + 1];
b[i] += (byte)(c < 0x40 ? c - 0x30 : (c < 0x47 ? c - 0x37 : c - 0x57));
}
return b;
}
Run Code Online (Sandbox Code Playgroud)
支持小写的 Waleed Eissa 函数:
public static string BytesToHex(this byte[] barray, bool toLowerCase = true)
{
byte addByte = 0x37;
if (toLowerCase) addByte = 0x57;
char[] c = new char[barray.Length * 2];
byte b;
for (int i = 0; i < barray.Length; ++i)
{
b = ((byte)(barray[i] >> 4));
c[i * 2] = (char)(b > 9 ? b + addByte : b + 0x30);
b = ((byte)(barray[i] & 0xF));
c[i * 2 + 1] = (char)(b > 9 ? b + addByte : b + 0x30);
}
return new string(c);
}
Run Code Online (Sandbox Code Playgroud)
我注意到大多数测试都是在将字节数组转换为十六进制字符串的函数上执行的。因此,在这篇文章中,我将重点关注另一方面:将十六进制字符串转换为字节数组的函数。如果您只对结果感兴趣,可以跳到摘要部分。测试代码文件在帖子末尾提供。
我想从接受的答案(由 Tomalak)命名该函数 StringToByteArrayV1,或者将其快捷方式命名为 V1。其余函数将以同样的方式命名:V2、V3、V4、...等。
我通过传递 1 个字节的所有 256 个可能值,然后检查输出以查看是否正确来测试正确性。结果:
注意:V5_3 解决了这个问题(V5_1 和 V5_2)
我已经使用秒表类完成了性能测试。
input length: 10,000,000 bytes
runs: 100
average elapsed time per run:
V1 = 136.4ms
V2 = 104.5ms
V3 = 22.0ms
V4 = 9.9ms
V5_1 = 10.2ms
V5_2 = 9.0ms
V5_3 = 9.3ms
V6 = 18.3ms
V7 = 9.8ms
V8 = 8.8ms
V9 = 10.2ms
V10 = 19.0ms
V11 = 12.2ms
V12 = 27.4ms
V13 = 21.8ms
V14 = 12.0ms
V15 = 14.9ms
V16 = 15.3ms
V17 = 9.5ms
V18 got excluded from this test, because it was very slow when using very long string
V19 = 222.8ms
V20 = 66.0ms
V21 = 15.4ms
V1 average ticks per run: 1363529.4
V2 is more fast than V1 by: 1.3 times (ticks ratio)
V3 is more fast than V1 by: 6.2 times (ticks ratio)
V4 is more fast than V1 by: 13.8 times (ticks ratio)
V5_1 is more fast than V1 by: 13.3 times (ticks ratio)
V5_2 is more fast than V1 by: 15.2 times (ticks ratio)
V5_3 is more fast than V1 by: 14.8 times (ticks ratio)
V6 is more fast than V1 by: 7.4 times (ticks ratio)
V7 is more fast than V1 by: 13.9 times (ticks ratio)
V8 is more fast than V1 by: 15.4 times (ticks ratio)
V9 is more fast than V1 by: 13.4 times (ticks ratio)
V10 is more fast than V1 by: 7.2 times (ticks ratio)
V11 is more fast than V1 by: 11.1 times (ticks ratio)
V12 is more fast than V1 by: 5.0 times (ticks ratio)
V13 is more fast than V1 by: 6.3 times (ticks ratio)
V14 is more fast than V1 by: 11.4 times (ticks ratio)
V15 is more fast than V1 by: 9.2 times (ticks ratio)
V16 is more fast than V1 by: 8.9 times (ticks ratio)
V17 is more fast than V1 by: 14.4 times (ticks ratio)
V19 is more SLOW than V1 by: 1.6 times (ticks ratio)
V20 is more fast than V1 by: 2.1 times (ticks ratio)
V21 is more fast than V1 by: 8.9 times (ticks ratio)
Run Code Online (Sandbox Code Playgroud)
V18 took long time at the previous test,
so let's decrease length for it:
input length: 1,000,000 bytes
runs: 100
average elapsed time per run: V1 = 14.1ms , V18 = 146.7ms
V1 average ticks per run: 140630.3
V18 is more SLOW than V1 by: 10.4 times (ticks ratio)
Run Code Online (Sandbox Code Playgroud)
input length: 100 byte
runs: 1,000,000
V1 average ticks per run: 14.6
V2 is more fast than V1 by: 1.4 times (ticks ratio)
V3 is more fast than V1 by: 5.9 times (ticks ratio)
V4 is more fast than V1 by: 15.7 times (ticks ratio)
V5_1 is more fast than V1 by: 15.1 times (ticks ratio)
V5_2 is more fast than V1 by: 18.4 times (ticks ratio)
V5_3 is more fast than V1 by: 16.3 times (ticks ratio)
V6 is more fast than V1 by: 5.3 times (ticks ratio)
V7 is more fast than V1 by: 15.7 times (ticks ratio)
V8 is more fast than V1 by: 18.0 times (ticks ratio)
V9 is more fast than V1 by: 15.5 times (ticks ratio)
V10 is more fast than V1 by: 7.8 times (ticks ratio)
V11 is more fast than V1 by: 12.4 times (ticks ratio)
V12 is more fast than V1 by: 5.3 times (ticks ratio)
V13 is more fast than V1 by: 5.2 times (ticks ratio)
V14 is more fast than V1 by: 13.4 times (ticks ratio)
V15 is more fast than V1 by: 9.9 times (ticks ratio)
V16 is more fast than V1 by: 9.2 times (ticks ratio)
V17 is more fast than V1 by: 16.2 times (ticks ratio)
V18 is more fast than V1 by: 1.1 times (ticks ratio)
V19 is more SLOW than V1 by: 1.6 times (ticks ratio)
V20 is more fast than V1 by: 1.9 times (ticks ratio)
V21 is more fast than V1 by: 11.4 times (ticks ratio)
Run Code Online (Sandbox Code Playgroud)
在使用以下代码https://github.com/Ghosticollis/performance-tests/blob/main/MTestPerformance.cs中的任何内容之前,最好先阅读本文中的免责声明部分
我建议使用以下函数之一,因为其性能良好,并且支持大小写:
这是 V5_3 的最终形状:
static byte[] HexStringToByteArrayV5_3(string hexString) {
int hexStringLength = hexString.Length;
byte[] b = new byte[hexStringLength / 2];
for (int i = 0; i < hexStringLength; i += 2) {
int topChar = hexString[i];
topChar = (topChar > 0x40 ? (topChar & ~0x20) - 0x37 : topChar - 0x30) << 4;
int bottomChar = hexString[i + 1];
bottomChar = bottomChar > 0x40 ? (bottomChar & ~0x20) - 0x37 : bottomChar - 0x30;
b[i / 2] = (byte)(topChar + bottomChar);
}
return b;
}
Run Code Online (Sandbox Code Playgroud)
警告:我没有适当的测试知识。这些原始测试的主要目的是快速概述所有已发布功能的优点。如果您需要准确的结果,请使用适当的测试工具。
最后,我想说,我是 stackoverflow 的新手,如果我的帖子有遗漏,抱歉。如果您能提出意见来完善这篇文章,我们将不胜感激。
扩展方法(免责声明:完全未经测试的代码,顺便说一句......):
public static class ByteExtensions
{
public static string ToHexString(this byte[] ba)
{
StringBuilder hex = new StringBuilder(ba.Length * 2);
foreach (byte b in ba)
{
hex.AppendFormat("{0:x2}", b);
}
return hex.ToString();
}
}
Run Code Online (Sandbox Code Playgroud)
等.. 使用Tomalak 的三种解决方案之一(最后一种是字符串上的扩展方法)。
安全版本:
public static class HexHelper
{
[System.Diagnostics.Contracts.Pure]
public static string ToHex(this byte[] value)
{
if (value == null)
throw new ArgumentNullException("value");
const string hexAlphabet = @"0123456789ABCDEF";
var chars = new char[checked(value.Length * 2)];
unchecked
{
for (int i = 0; i < value.Length; i++)
{
chars[i * 2] = hexAlphabet[value[i] >> 4];
chars[i * 2 + 1] = hexAlphabet[value[i] & 0xF];
}
}
return new string(chars);
}
[System.Diagnostics.Contracts.Pure]
public static byte[] FromHex(this string value)
{
if (value == null)
throw new ArgumentNullException("value");
if (value.Length % 2 != 0)
throw new ArgumentException("Hexadecimal value length must be even.", "value");
unchecked
{
byte[] result = new byte[value.Length / 2];
for (int i = 0; i < result.Length; i++)
{
// 0(48) - 9(57) -> 0 - 9
// A(65) - F(70) -> 10 - 15
int b = value[i * 2]; // High 4 bits.
int val = ((b - '0') + ((('9' - b) >> 31) & -7)) << 4;
b = value[i * 2 + 1]; // Low 4 bits.
val += (b - '0') + ((('9' - b) >> 31) & -7);
result[i] = checked((byte)val);
}
return result;
}
}
}
Run Code Online (Sandbox Code Playgroud)
不安全版本适合那些喜欢表现并且不怕不安全的人.ToHex快35%,FromHex快10%.
public static class HexUnsafeHelper
{
[System.Diagnostics.Contracts.Pure]
public static unsafe string ToHex(this byte[] value)
{
if (value == null)
throw new ArgumentNullException("value");
const string alphabet = @"0123456789ABCDEF";
string result = new string(' ', checked(value.Length * 2));
fixed (char* alphabetPtr = alphabet)
fixed (char* resultPtr = result)
{
char* ptr = resultPtr;
unchecked
{
for (int i = 0; i < value.Length; i++)
{
*ptr++ = *(alphabetPtr + (value[i] >> 4));
*ptr++ = *(alphabetPtr + (value[i] & 0xF));
}
}
}
return result;
}
[System.Diagnostics.Contracts.Pure]
public static unsafe byte[] FromHex(this string value)
{
if (value == null)
throw new ArgumentNullException("value");
if (value.Length % 2 != 0)
throw new ArgumentException("Hexadecimal value length must be even.", "value");
unchecked
{
byte[] result = new byte[value.Length / 2];
fixed (char* valuePtr = value)
{
char* valPtr = valuePtr;
for (int i = 0; i < result.Length; i++)
{
// 0(48) - 9(57) -> 0 - 9
// A(65) - F(70) -> 10 - 15
int b = *valPtr++; // High 4 bits.
int val = ((b - '0') + ((('9' - b) >> 31) & -7)) << 4;
b = *valPtr++; // Low 4 bits.
val += (b - '0') + ((('9' - b) >> 31) & -7);
result[i] = checked((byte)val);
}
}
return result;
}
}
}
Run Code Online (Sandbox Code Playgroud)
BTW 对于每次调用转换函数错误时初始化字母表的基准测试,字母必须是const(对于字符串)或静态只读(对于char []).然后,byte []到string的基于字母的转换变得和字节操作版本一样快.
当然,必须在Release(优化)中编译测试,并关闭调试选项"Suppress JIT optimization"(如果代码必须是可调试的,则"启用我的代码"相同).
老派人的最快方法......想念你的指针
static public byte[] HexStrToByteArray(string str)
{
byte[] res = new byte[(str.Length % 2 != 0 ? 0 : str.Length / 2)]; //check and allocate memory
for (int i = 0, j = 0; j < res.Length; i += 2, j++) //convert loop
res[j] = (byte)((str[i] % 32 + 9) % 25 * 16 + (str[i + 1] % 32 + 9) % 25);
return res;
}
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
805968 次 |
最近记录: |