如何将字节数组转换为十六进制字符串,反之亦然?
或者:
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();
}
要么:
public static string ByteArrayToString(byte[] ba)
{
return BitConverter.ToString(ba).Replace("-","");
}
还有更多的变种,例如这里.
反向转换将如下所示:
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;
}
使用Substring
是结合使用的最佳选择Convert.ToByte
.有关更多信息,请参阅此答案.如果你需要更好的性能,你必须避免Convert.ToByte
在丢弃之前SubString
.
注:截至2015-08-20的新领导者.
我通过一些粗略的Stopwatch
性能测试运行了各种转换方法,一个带有随机句子的运行(n = 61,1000次迭代)和一个带有Project Gutenburg文本的运行(n = 1,238,957,150次迭代).以下是结果,大致从最快到最慢.所有测量均以刻度(10,000滴= 1毫秒)为单位,所有相关注释与[最慢] StringBuilder
实施进行比较.对于使用的代码,请参阅下面或测试框架repo,其中我现在维护用于运行此代码的代码.
警告:不要将这些属性用于任何混凝土; 它们只是样本数据的样本运行.如果您确实需要一流的性能,请在代表您的生产需求的环境中测试这些方法,并使用代表您将使用的数据.
由字节的查询unsafe
(经由CodesInChaos) (加到试验通过回购airbreather)
文字:4,727.85(105.2X)
句子:0.28(99.7X)
按字节查找(通过CodesInChaos)
文字:10,853.96(快45.8倍)
句子:0.65(快42.7倍)
Byte Manipulation 2(通过CodesInChaos)
文字:12,967.69(快38.4倍)
句子:0.73(快37.9倍)
字节操作(通过Waleed Eissa)
文字:16,856.64(快29.5倍)
句子:0.70(快39.5倍)
查找/转移(通过Nathan Moinvaziri)
文字:23,201.23(快21.4倍)
句子:1.24(快22.3倍)
通过轻咬(通过Brian Lambert)查找
文字:23,879.41(快20.8倍)
句子:1.15(快23.9倍)
BitConverter
(通过Tomalak)
文字:113,269.34(快4.4倍)
句子:9.98(快2.8倍)
{SoapHexBinary}.ToString
(通过Mykroft)
文字:178,601.39(快2.8倍)
句子:10.68(快2.6倍)
{byte}.ToString("X2")
(使用foreach
)(来自Will Dean的回答)
文字:308,805.38(快2.4倍)
句子:16.89(快2.4倍)
{byte}.ToString("X2")
(使用{IEnumerable}.Aggregate
,需要System.Linq)(通过标记)
文字:352,828.20(快2.1倍)
句子:16.87(快2.4倍)
Array.ConvertAll
(使用string.Join
)(通过Will Dean)
文字:675,451.57(快1.1倍)
句子:17.95(快2.2倍)
Array.ConvertAll
(使用string.Concat
,需要.NET 4.0)(通过Will Dean)
文字:752,078.70(快1.0倍)
句子:18.28(快2.2倍)
{StringBuilder}.AppendFormat
(使用foreach
)(通过Tomalak)
文字:672,115.77(快1.1倍)
句子:36.82(快1.1倍)
{StringBuilder}.AppendFormat
(使用{IEnumerable}.Aggregate
,需要System.Linq)(源自Tomalak的答案)
文字:718,380.63(快1.0倍)
句子:39.71(快1.0倍)
查找表已经领先于字节操作.基本上,有一些形式的预计算任何给定的半字节或字节将以十六进制表示.然后,当你翻阅数据时,你只需查看下一部分,看看它会是什么十六进制字符串.然后以某种方式将该值添加到结果字符串输出中.对于长时间的字节操作,一些开发人员可能更难阅读,是表现最好的方法.
您最好的选择仍然是找到一些有代表性的数据并在类似生产的环境中进行尝试.如果您有不同的内存限制,您可能更喜欢使用较少分配的方法,但速度更快但消耗更多内存的方法.
随意玩我使用的测试代码.这里包含一个版本,但可以随意克隆存储库并添加自己的方法.如果您发现任何有趣的内容或希望帮助改进其使用的测试框架,请提交拉取请求.
将新的静态方法(Func
)添加到/Tests/ConvertByteArrayToHexString/Test.cs.
将该方法的名称添加到TestCandidates
同一个类中的返回值.
通过GenerateTestInput
在同一个类中切换注释,确保运行所需的输入版本,句子或文本.
点击F5并等待输出(在/ bin文件夹中也生成HTML转储).
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(); }
添加了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
变体.如果你想在不安全的游戏中玩游戏,那么你可以在短曲目和大文本上获得超过任何先前最佳获胜者的巨大性能提升.
有一个名为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(); }
在编写加密代码时,通常会避免数据相关的分支和表查找,以确保运行时不依赖于数据,因为数据相关的时序可能导致旁路攻击.
它也很快.
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); }
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
.
结合2)和3),显示(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.但由于-1表示所有位都是1,我们可以改为使用& -7
from (0 & -7) == 0
和(-1 & -7) == -7
.
进一步考虑:
我没有使用第二个循环变量来索引c
,因为测量表明从中计算它i
更便宜.
完全使用i < bytes.Length
循环的上限允许JITter消除边界检查bytes[i]
,因此我选择了该变体.
创建b
一个int允许从字节到字节的不必要的转换.
如果你想要更多的灵活性BitConverter
,但又不想要那些笨重的20世纪90年代风格的显式循环,那么你可以做到:
String.Join(String.Empty, Array.ConvertAll(bytes, x => x.ToString("X2")));
或者,如果您使用的是.NET 4.0:
String.Concat(Array.ConvertAll(bytes, x => x.ToString("X2")));
(后者来自对原帖的评论.)
另一种基于查找表的方法.这个每个字节只使用一个查找表,而不是每个半字节的查找表.
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); }
我还测试了使用这种变型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); }
或者,如果您认为直接写入字符串是可以接受的:
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; }
您可以使用BitConverter.ToString方法:
byte[] bytes = {0, 1, 2, 4, 8, 16, 32, 64, 128, 256} Console.WriteLine( BitConverter.ToString(bytes));
输出:
00-01-02-04-08-10-20-40-80-FF
更多信息:BitConverter.ToString方法(Byte [])
我今天刚遇到同样的问题,我遇到了这段代码:
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); }
来源:论坛帖子byte []数组到十六进制字符串(参见PZahra的帖子).我稍微修改了代码以删除0x前缀.
我对代码进行了一些性能测试,它比使用BitConverter.ToString()快了近八倍(根据patridge的帖子,速度最快).
这是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; }
修订版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; }
修订版避免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; }
每个十六进制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; }
您剩下的是一个字符串阅读器,其唯一添加的"值"是_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; }
你真的需要一个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; }
现在的解决方案是什么样的?完全像在开头一样,只使用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
是!
对于替补框架的道具鹧to,它很容易入侵.使用的输入是以下SHA-1哈希重复5000次以生成100,000字节长的字符串.
209113288F93A9AB8E474EA78D899AFDBB874355
玩得开心!(但要适度优化.)
使用查找表也可以解决此问题.这将需要编码器和解码器的少量静态存储器.然而,这种方法会很快:
编码器表512字节或1024字节(如果需要大小写,则为两倍大小)
解码器表256字节或64 KiB(单个字符查找或双字符查找)
我的解决方案使用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])); }
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]]; }
StringBuilderToStringFromBytes: 106148 BitConverterToStringFromBytes: 15783 ArrayConvertAllToStringFromBytes: 54290 ByteManipulationToCharArray: 8444 TableBasedToCharArray: 5651 *
*这个解决方案
在解码过程中,IOException和IndexOutOfRangeException可能会发生(如果一个字符的值太大> 256).应该实现de/encoding流或数组的方法,这只是一个概念证明.
补充@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; }
说明:
& 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; }
为什么要复杂?这在Visual Studio 2008中很简单:
C#:
string hex = BitConverter.ToString(YourByteArray).Replace("-", "");
VB:
Dim hex As String = BitConverter.ToString(YourByteArray).Replace("-", "")
不要在这里找到很多答案,但我发现了一个相当优化的(比接受的好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
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; }
我尝试了一些东西unsafe
并将(明显多余的)字符到半字节if
序列移动到另一个方法,但这是它得到的最快.
(我承认这回答了问题的一半.我觉得字符串 - > byte []转换的代表性不足,而字节[] - >字符串角度似乎被很好地覆盖了.因此,这个答案.)
安全版本:
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; } } }
不安全版本适合那些喜欢表现并且不怕不安全的人.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; } } }
BTW 对于每次调用转换函数错误时初始化字母表的基准测试,字母必须是const(对于字符串)或静态只读(对于char []).然后,byte []到string的基于字母的转换变得和字节操作版本一样快.
当然,必须在Release(优化)中编译测试,并关闭调试选项"Suppress JIT optimization"(如果代码必须是可调试的,则"启用我的代码"相同).