如何将字节数组转换为十六进制字符串,反之亦然?

如何将字节数组转换为十六进制字符串,反之亦然?

1096546 次浏览

您可以从. NET 5开始使用#0
还有一个反向操作的方法:#0


对于旧版本的. NET,您可以使用:

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

扩展方法(免责声明:完全未经测试的代码,顺便说一句…):

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();}}

使用Tomalak的三种解决方案中的任何一个(最后一个是字符串上的扩展方法)。

您可以使用BitConver. 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

更多信息:BitConver. ToString方法(Byte[])

如果你想要比BitConverter更灵活,但不想要那些笨重的1990年代风格的显式循环,那么你可以这样做:

String.Join(String.Empty, Array.ConvertAll(bytes, x => x.ToString("X2")));

或者,如果您使用的是. NET 4.0:

String.Concat(Array.ConvertAll(bytes, x => x.ToString("X2")));

(后者来自对原始帖子的评论。

性能分析

注:新领导截至2015-08-20。

我通过一些粗略的Stopwatch性能测试运行了各种转换方法中的每一种,一次使用随机句子运行(n=61,1000次迭代)和一次使用古腾堡项目文本运行(n=1,238,957,150次迭代)。以下是结果,大致从最快到最慢。所有测量都以滴答(10,000滴答=1毫秒)为单位,所有相关注释都与[最慢的]StringBuilder实现进行了比较。对于使用的代码,请参阅下面或测试框架仓库,我现在维护了运行它的代码。

免责声明

警告:不要依赖这些统计数据来做任何具体的事情;它们只是示例数据的示例运行。如果您确实需要一流的性能,请在代表您的生产需求的环境中测试这些方法,并使用代表您将使用的数据。

搜索结果

查找表已经领先于字节操作。基本上,有某种形式的预计算任何给定的半字节或字节将在十六进制中。然后,当你翻阅数据时,你只需查找下一部分,看看它将是什么十六进制字符串。然后,该值以某种方式添加到结果字符串输出中。长期以来,字节操作是性能最好的方法,可能对一些开发人员来说更难阅读。

你最好的选择仍然是找到一些有代表性的数据并在类似生产的环境中尝试它。如果你有不同的内存限制,你可能更喜欢分配更少的方法,而不是更快但消耗更多内存的方法。

测试代码

随意使用我使用的测试代码。这里包含了一个版本,但可以随意克隆回购并添加您自己的方法。如果您发现任何有趣的东西或想帮助改进它使用的测试框架,请提交拉取请求。

  1. 将新的静态方法(Func<byte[], string>)添加到 /Tests/ConvertByteArrayToHexString/Test.cs.
  2. 将该方法的名称添加到同一类中的TestCandidates返回值。
  3. 通过在同一类中切换GenerateTestInput中的注释,确保您正在运行所需的输入版本,句子或文本。
  4. 点击F5并等待输出(超文本标记语言转储也在 /bin文件夹中生成)。
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();}

更新(2010-01-13)

瓦利德对分析的回答。相当快。

更新(2011-10-05)

为完整性添加了string.ConcatArray.ConvertAll变体(需要. NET 4.0)。与string.Join版本相同。

更新(2012-02-05)

测试存储库包含更多的变体,例如StringBuilder.Append(b.ToString("X2"))。没有任何结果被打乱。例如,foreach{IEnumerable}.Aggregate快,但BitConverter仍然获胜。

更新(2012-04-03)

增加了Mykroft的SoapHexBinary分析答案,占据了第三位。

更新(2013-01-15)

添加了CodesInChaos的字节操作答案,它占据了第一位(在大块文本上以很大的优势)。

更新(2013-05-23)

添加了Nathan Moinfaziri的查找答案和Brian Lambert博客中的变体。两者都相当快,但在我使用的测试机器(AMD Phenom 9750)上没有领先。

更新(2014-07-31)

添加了@CodesInChaos新的基于字节的查找答案。它似乎在句子测试和全文测试中都处于领先地位。

更新(2015-08-20)

在这个答案仓库中添加了呼吸器的优化和unsafe变体。如果你想在不安全的游戏中玩,你可以在短字符串和大文本上获得比任何先前的顶级赢家更大的性能提升。

我今天遇到了同样的问题,我遇到了这个代码:

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前缀。

我对代码进行了一些性能测试,它比使用BitConver. ToString()快了近8倍(根据帕特里奇的帖子,最快)。

插入到一个SQL字符串(如果你不使用命令参数):

public static String ByteArrayToSQLHexString(byte[] Source){return = "0x" + BitConverter.ToString(Source).Replace("-", "");}

如果你想获得wcoenen报道的“4倍速度提升”,那么如果不明显:将hex.Substring(i, 2)替换为hex[i]+hex[i+1]

您还可以更进一步,通过在两个地方使用i++来摆脱i+=2

这是一篇很棒的文章。我喜欢Waleed的解决方案。我还没有通过帕特里奇的测试运行它,但它似乎相当快。我还需要反向过程,将十六进制字符串转换为字节数组,所以我把它写成了Waleed解决方案的反转。不确定它是否比Tomalak的原始解决方案更快。同样,我也没有通过帕特里奇的测试运行反向过程。

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;}

有一个名为SoapHexBgic的类可以完全按照您的要求执行。

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();}

我没有得到你建议的代码,奥利普罗。hex[i] + hex[i+1]显然返回了int

然而,通过从Waleeds代码中获取一些提示并将其锤击在一起,我确实取得了一些成功。它很丑,但根据我的测试(使用爱国者测试机制),与其他代码相比,它似乎可以工作并执行1/3的时间。取决于输入大小。切换?: s首先分离0-9可能会产生略快的结果,因为数字比字母更多。

public static byte[] StringToByteArray2(string hex){byte[] bytes = new byte[hex.Length/2];int bl = bytes.Length;for (int i = 0; i < bl; ++i){bytes[i] = (byte)((hex[2 * i] > 'F' ? hex[2 * i] - 0x57 : hex[2 * i] > '9' ? hex[2 * i] - 0x37 : hex[2 * i] - 0x30) << 4);bytes[i] |= (byte)(hex[2 * i + 1] > 'F' ? hex[2 * i + 1] - 0x57 : hex[2 * i + 1] > '9' ? hex[2 * i + 1] - 0x37 : hex[2 * i + 1] - 0x30);}return bytes;}

就速度而言,这似乎比这里的任何东西都好:

  public static string ToHexString(byte[] data) {byte b;int i, j, k;int l = data.Length;char[] r = new char[l * 2];for (i = 0, j = 0; i < l; ++i) {b = data[i];k = b >> 4;r[j++] = (char)(k > 9 ? k + 0x37 : k + 0x30);k = b & 15;r[j++] = (char)(k > 9 ? k + 0x37 : k + 0x30);}return new string(r);}

来自微软的开发人员,一个很好,简单的转换:

public static string ByteArrayToString(byte[] ba){// Concatenate the bytes into one long stringreturn ba.Aggregate(new StringBuilder(32),(sb, b) => sb.Append(b.ToString("X2"))).ToString();}

虽然上面的代码干净而紧凑,但性能迷会使用枚举器对它尖叫。你可以通过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 foreachhex.Append(ba[i].ToString("X2"));   // <-- ToString is faster than AppendFormat
return hex.ToString();}

这是迄今为止我见过的所有例程中最快的。不要只相信我的话……性能测试每个例程并亲自检查其CIL代码。

为什么要使它复杂?这在Visual Studio 2008中很简单:

C#:

string hex = BitConverter.ToString(YourByteArray).Replace("-", "");

VB:

Dim hex As String = BitConverter.ToString(YourByteArray).Replace("-", "")

如果性能很重要,这里有一个优化的解决方案:

    static readonly char[] _hexDigits = "0123456789abcdef".ToCharArray();public static string ToHexString(this byte[] bytes){char[] digits = new char[bytes.Length * 2];for (int i = 0; i < bytes.Length; i++){int d1, d2;d1 = Math.DivRem(bytes[i], 16, out d2);digits[2 * i] = _hexDigits[d1];digits[2 * i + 1] = _hexDigits[d2];}return new string(digits);}

它比BitConverter.ToString快约2.5倍,比BitConverter.ToString+删除“-”字符快约7倍。

这个问题也可以使用查找表来解决。这将需要编码器和解码器的少量静态内存。然而,这种方法会很快:

  • 编码器表512字节或1024字节(两次大小,如果大写和小写需要)
  • 解码器表256字节或64 KiB(单个char查找或双char查找)

我的解决方案使用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:   106148BitConverterToStringFromBytes:     15783ArrayConvertAllToStringFromBytes:  54290ByteManipulationToCharArray:        8444TableBasedToCharArray:              5651 *

*此解决方案

说明

在解码IOException和IndexOutOfRangeException期间可能会发生(如果字符的值太高>256)。应该实现对流或数组进行解/编码的方法,这只是概念证明。

为了性能,我会选择drphro的解决方案。解码器的一个微小优化可以是为任一char使用一个表来摆脱“<<4”。

显然,这两个方法调用的成本很高。如果对输入或输出数据进行某种检查(可能是CRC、校验和或其他),则可以跳过if (b == 255)...,从而也可以完全调用方法。

使用offset++offset而不是offsetoffset + 1可能会带来一些理论上的好处,但我怀疑编译器处理得比我好。

private static readonly byte[] LookupTableLow = 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};
private static readonly byte[] LookupTableHigh = 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, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,0xFF, 0xA0, 0xB0, 0xC0, 0xD0, 0xE0, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,0xFF, 0xA0, 0xB0, 0xC0, 0xD0, 0xE0, 0xF0, 0xFF, 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 LookupLow(char c){var b = LookupTableLow[c];if (b == 255)throw new IOException("Expected a hex character, got " + c);return b;}
private static byte LookupHigh(char c){var b = LookupTableHigh[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)(LookupHigh(chars[offset++]) | LookupLow(chars[offset]));}

这只是我的头顶,还没有经过测试或基准测试。

多样性的另一种变化:

public static byte[] FromHexString(string src){if (String.IsNullOrEmpty(src))return null;
int index = src.Length;int sz = index / 2;if (sz <= 0)return null;
byte[] rc = new byte[sz];
while (--sz >= 0){char lo = src[--index];char hi = src[--index];
rc[sz] = (byte)(((hi >= '0' && hi <= '9') ? hi - '0' :(hi >= 'a' && hi <= 'f') ? hi - 'a' + 10 :(hi >= 'A' && hi <= 'F') ? hi - 'A' + 10 :0)<< 4 |((lo >= '0' && lo <= '9') ? lo - '0' :(lo >= 'a' && lo <= 'f') ? lo - 'a' + 10 :(lo >= 'A' && lo <= 'F') ? lo - 'A' + 10 :0));}
return rc;}

我怀疑这样的速度会让大多数其他测试都大吃一惊…

Public Function BufToHex(ByVal buf() As Byte) As StringDim sB As New System.Text.StringBuilderFor i As Integer = 0 To buf.Length - 1sB.Append(buf(i).ToString("x2"))Next iReturn sB.ToStringEnd Function

这里不想堆积太多答案,但我发现了一个相当最佳(比接受的好约4.5倍)、直接的十六进制字符串解析器实现。首先,我的测试输出(第一批是我的实现):

Give me that string:04c63f7842740c77e545bb0b2ade90b384f119f6ab57b680b7aa575a2f40939f
Time to parse 100,000 times: 50.4192 msResult 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-57-B6-80-B7-AA-57-5A-2F-40-93-9F
Accepted answer: (StringToByteArray)Time to parse 100000 times: 233.1264msResult 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-57-B6-80-B7-AA-57-5A-2F-40-93-9F
With Mono's implementation:Time to parse 100000 times: 777.2544msResult 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-57-B6-80-B7-AA-57-5A-2F-40-93-9F
With SoapHexBinary:Time to parse 100000 times: 845.1456msResult 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-57-B6-80-B7-AA-57-5A-2F-40-93-9F

Base64和BitConver'行用于测试正确性。请注意,它们是相等的。

实施:

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序列移动到另一种方法,但这是最快的。

(我承认这回答了一半的问题。我觉得string->byte[]转换被低估了,而byte[]->string角度似乎被很好地覆盖了。因此,这个答案。)

这适用于从字符串到字节数组…

public static byte[] StrToByteArray(string str){Dictionary<string, byte> hexindex = new Dictionary<string, byte>();for (byte i = 0; i < 255; i++)hexindex.Add(i.ToString("X2"), i);
List<byte> hexres = new List<byte>();for (int i = 0; i < str.Length; i += 2)hexres.Add(hexindex[str.Substring(i, 2)]);
return hexres.ToArray();}

我猜它的速度值得额外16个字节。

    static char[] hexes = new char[]{'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};public static string ToHexadecimal (this byte[] Bytes){char[] Result = new char[Bytes.Length << 1];int Offset = 0;for (int i = 0; i != Bytes.Length; i++) {Result[Offset++] = hexes[Bytes[i] >> 4];Result[Offset++] = hexes[Bytes[i] & 0x0F];}return new string(Result);}

在编写加密代码时,通常会避免依赖于数据的分支和表查找,以确保运行时不依赖于数据,因为依赖于数据的时序可能会导致侧通道攻击。

它也相当快。

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


放弃所有的希望,你们谁进入这里

对奇怪的小提琴的解释:

  1. bytes[i] >> 4提取字节的高半字节
    bytes[i] & 0xF提取一个字节的低半字节
  2. b - 10
    b < 10< 0,它将成为十进制数字
    b > 10>= 0,它将成为从AF的字母。
  3. 在有符号的32位整数上使用i >> 31提取符号,这要归功于符号扩展。i < 0-1i >= 00
  4. 结合2)和3),表明(b-10)>>31对于字母将是0,对于数字将是-1
  5. 查看字母的大小写,最后一个求和变为0b在10到15之间。我们想将其映射到A(65)到F(70),这意味着添加55('A'-10)。
  6. 查看数字的情况,我们希望调整最后一个求和,以便将b从0到9的范围映射到0(48)到9(57)的范围。这意味着它需要变成-7('0' - 55)。
    现在我们可以乘以7。但是由于-1由所有位为1表示,我们可以使用& -7,因为(0 & -7) == 0(-1 & -7) == -7

一些进一步的考虑:

  • 我没有使用第二个循环变量来索引到c,因为测量表明从i计算它更便宜。
  • 使用i < bytes.Length作为循环的上限允许JITter消除对bytes[i]的边界检查,所以我选择了那个变体。
  • 使b成为int允许不必要的从字节到字节的转换。

两个mashup,将两个nibble操作折叠为一个。

可能相当有效的版本:

public static string ByteArrayToString2(byte[] ba){char[] c = new char[ba.Length * 2];for( int i = 0; i < ba.Length * 2; ++i){byte b = (byte)((ba[i>>1] >> 4*((i&1)^1)) & 0xF);c[i] = (char)(55 + b + (((b-10)>>31)&-7));}return new string( c );}

颓废的linq-with bit黑客版本:

public static string ByteArrayToString(byte[] ba){return string.Concat( ba.SelectMany( b => new int[] { b >> 4, b & 0xF }).Select( b => (char)(55 + b + (((b-10)>>31)&-7))) );}

反向:

public static byte[] HexStringToByteArray( string s ){byte[] ab = new byte[s.Length>>1];for( int i = 0; i < s.Length; i++ ){int b = s[i];b = (b - '0') + ((('9' - b)>>31)&-7);ab[i>>1] |= (byte)(b << 4*((i&1)^1));}return ab;}

这是我的尝试。我创建了一对扩展类来扩展字符串和字节。在大文件测试中,性能与字节操作2相当。

下面ToHexString的代码是查找和移位算法的优化实现。它与Behrooz的几乎相同,但事实证明使用foreach进行迭代,并且计数器比显式索引for更快。

它在我的机器上仅次于字节操作2,并且是非常可读的代码。以下测试结果也很有趣:

ToHexStringCharArrayWith CharArrayLookup:41,589.69平均滴答(超过1000次运行),1.5XToHexStringCharArrayWith StringLookup:50,764.06平均滴答(超过1000次运行),1.2XToHexStringStringBuilderWith CharArrayLookup:62,812.87平均滴答(超过1000次运行),1.0X

根据上述结果,似乎可以得出以下结论:

  1. 索引到字符串以执行查找的惩罚与char数组在大文件测试中很重要。
  2. 使用已知容量的StringBuilder与char的惩罚创建字符串的已知大小的数组甚至更重要。

以下是代码:

using System;
namespace ConversionExtensions{public static class ByteArrayExtensions{private readonly static char[] digits = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
public static string ToHexString(this byte[] bytes){char[] hex = new char[bytes.Length * 2];int index = 0;
foreach (byte b in bytes){hex[index++] = digits[b >> 4];hex[index++] = digits[b & 0x0F];}
return new string(hex);}}}

using System;using System.IO;
namespace ConversionExtensions{public static class StringExtensions{public static byte[] ToBytes(this string hexString){if (!string.IsNullOrEmpty(hexString) && hexString.Length % 2 != 0){throw new FormatException("Hexadecimal string must not be empty and must contain an even number of digits to be valid.");}
hexString = hexString.ToUpperInvariant();byte[] data = new byte[hexString.Length / 2];
for (int index = 0; index < hexString.Length; index += 2){int highDigitValue = hexString[index] <= '9' ? hexString[index] - '0' : hexString[index] - 'A' + 10;int lowDigitValue = hexString[index + 1] <= '9' ? hexString[index + 1] - '0' : hexString[index + 1] - 'A' + 10;
if (highDigitValue < 0 || lowDigitValue < 0 || highDigitValue > 15 || lowDigitValue > 15){throw new FormatException("An invalid digit was encountered. Valid hexadecimal digits are 0-9 and A-F.");}else{byte value = (byte)((highDigitValue << 4) | (lowDigitValue & 0x0F));data[index / 2] = value;}}
return data;}}}

下面是我把代码放在机器上的测试项目中得到的测试结果。我还添加了一个从十六进制转换为字节数组的测试。执行我的代码的测试运行是ByteArrayToHexViaOptimizedLookupAndShift和HexToByteArrayViaByteMani。HexToByteArrayViaConvertToByte来自XXXX。HexToByteArrayViaSoapHexBpedia是来自@Mykroft的答案。

Intel奔腾III Xeon处理器

    Cores: 4 <br/>Current Clock Speed: 1576 <br/>Max Clock Speed: 3092 <br/>

将字节数组转换为十六进制字符串表示


ByteArrayToHexViaByteManiPulation2:39,366.64平均滴答数(超过1000次运行),22.4X

ByteArrayToHexViaOptimizedLookupAndShift:平均41,588.64次(超过1000次运行),21.2X

ByteArrayToHexViaLookup:55,509.56个平均刻度(超过1000次运行),15.9X

ByteArrayToHexViaByte操作:65,349.12个平均滴答(超过1000次运行),13.5X

ByteArrayToHexViaLookupAndShift:平均86,926.87次(超过1000次)运行),10.2X

ByteArrayToHexStringViaBitConver:平均139,353.73滴答声(超过1000次运行),6.3倍

ByteArrayToHexViaSoapHexB微小: 314,598.77平均滴答(超过1000次运行),2.8X

ByteArrayToHexStringViaStringBuilderForEachByteToString:344,264.63

EachByteToString:无变化,无变化平均滴答声(超过1000次运行),2.6X

ByteArrayToHexStringViaStringBuilderAggregate ByteToString: 382,623.44

ByteArrayToString: 382,623.44//操作系统平均滴答声(超过1000次运行),2.3倍

ByteArrayToHexStringViaStringBuilderForEachAppendFormat: 818,111.95//每个字节数组都有对应的格式。

ByteArrayToHexStringViaStringBuilderForEachAppendFormat平均滴答声(超过1000次运行),1.1X

ByteArrayToHexStringViaStringConcatArrayConvertAll:平均839,244.84滴答声(超过1000次运行),1.1X

ByteArrayToHexStringViaStringBuilderAggregate AppendFormat: 867,303.98

服务端参数平均滴答声(超过1000次运行),1.0X

ByteArrayToHexStringViaStringJoinArrayConvertAll: 882,710.28平均值滴答声(超过1000次运行),1.0X


由@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',我们必须使用大数,因此我们必须通过使用& 0x0f制作一些位0来从默认版本中减去32。

65是'A'的代码

48是'0'的代码

7是ASCII表(...456789:;<=>?@ABCD...)中'9''A'之间的字母数。

这个版本的ByteArrayToHexViaByte操作可以更快。

从我的报告:

  • ByteArrayToHexViaByteManiPulation3:1,68平均滴答(超过1000次运行),17,5X
  • ByteArrayToHexViaByteManiPulation2:1,73平均滴答(超过1000次运行),16,9X
  • ByteArrayToHexViaByte操作:平均2,90个报价(超过1000次运行),10,1X
  • ByteArrayToHexViaLookupAndShift: 3,22平均滴答(超过1000次运行),9,1X
  • static private readonly char[] hexAlphabet = new char[]{'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};static string ByteArrayToHexViaByteManipulation3(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] = hexAlphabet[b];b = ((byte)(bytes[i] & 0xF));c[i * 2 + 1] = hexAlphabet[b];}return new string(c);}

And I think this one is an optimization:

    static private readonly char[] hexAlphabet = new char[]{'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};static string ByteArrayToHexViaByteManipulation4(byte[] bytes){char[] c = new char[bytes.Length * 2];for (int i = 0, ptr = 0; i < bytes.Length; i++, ptr += 2){byte b = bytes[i];c[ptr] = hexAlphabet[b >> 4];c[ptr + 1] = hexAlphabet[b & 0xF];}return new string(c);}

未针对速度进行优化,但比大多数答案(. NET 4.0)更LINQy:

<Extension()>Public Function FromHexToByteArray(hex As String) As Byte()hex = If(hex, String.Empty)If hex.Length Mod 2 = 1 Then hex = "0" & hexReturn Enumerable.Range(0, hex.Length \ 2).Select(Function(i) Convert.ToByte(hex.Substring(i * 2, 2), 16)).ToArrayEnd Function
<Extension()>Public Function ToHexString(bytes As IEnumerable(Of Byte)) As StringReturn String.Concat(bytes.Select(Function(b) b.ToString("X2")))End Function

快速功能…

private static readonly byte[] HexNibble = new byte[] {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,0x8, 0x9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,0x0, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0x0,0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,0x0, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF};
public static byte[] HexStringToByteArray( string str ){int byteCount = str.Length >> 1;byte[] result = new byte[byteCount + (str.Length & 1)];for( int i = 0; i < byteCount; i++ )result[i] = (byte) (HexNibble[str[i << 1] - 48] << 4 | HexNibble[str[(i << 1) + 1] - 48]);if( (str.Length & 1) != 0 )result[byteCount] = (byte) HexNibble[str[str.Length - 1] - 48];return result;}

还有XmlWriter.WriteBinHex(请参阅MSDN页面)。如果您需要将十六进制字符串放入XML流,这非常有用。

这是一个独立的方法来看看它是如何工作的:

    public static string ToBinHex(byte[] bytes){XmlWriterSettings xmlWriterSettings = new XmlWriterSettings();xmlWriterSettings.ConformanceLevel = ConformanceLevel.Fragment;xmlWriterSettings.CheckCharacters = false;xmlWriterSettings.Encoding = ASCIIEncoding.ASCII;MemoryStream memoryStream = new MemoryStream();using (XmlWriter xmlWriter = XmlWriter.Create(memoryStream, xmlWriterSettings)){xmlWriter.WriteBinHex(bytes, 0, bytes.Length);}return Encoding.ASCII.GetString(memoryStream.ToArray());}

安全版本:

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 - 15int 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 - 15int 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对于基准测试,每次调用转换函数时初始化alphabet都是错误的,alphabet必须是const(用于字符串)或静态只读(用于char[])。然后基于字母的byte[]到string的转换变得和字节操作版本一样快。

当然,测试必须在发布(带优化)中编译,并关闭调试选项“抑制JIT优化”(如果代码必须可调试,则“启用我的代码”也是如此)。

我将参加这个位摆弄比赛,因为我有一个答案也使用位摆弄解码十六进制。请注意,使用字符数组可能更快,因为调用StringBuilder方法也需要时间。

public static String ToHex (byte[] data){int dataLength = data.Length;// pre-create the stringbuilder using the length of the data * 2, precisely enoughStringBuilder sb = new StringBuilder (dataLength * 2);for (int i = 0; i < dataLength; i++) {int b = data [i];
// check using calculation over bits to see if first tuple is a letter// isLetter is zero if it is a digit, 1 if it is a letterint isLetter = (b >> 7) & ((b >> 6) | (b >> 5)) & 1;
// calculate the code using a multiplication to make up the difference between// a digit character and an alphanumerical characterint code = '0' + ((b >> 4) & 0xF) + isLetter * ('A' - '9' - 1);// now append the result, after casting the code point to a charactersb.Append ((Char)code);
// do the same with the lower (less significant) tupleisLetter = (b >> 3) & ((b >> 2) | (b >> 1)) & 1;code = '0' + (b & 0xF) + isLetter * ('A' - '9' - 1);sb.Append ((Char)code);}return sb.ToString ();}
public static byte[] FromHex (String hex){
// pre-create the arrayint resultLength = hex.Length / 2;byte[] result = new byte[resultLength];// set validity = 0 (0 = valid, anything else is not valid)int validity = 0;int c, isLetter, value, validDigitStruct, validDigit, validLetterStruct, validLetter;for (int i = 0, hexOffset = 0; i < resultLength; i++, hexOffset += 2) {c = hex [hexOffset];
// check using calculation over bits to see if first char is a letter// isLetter is zero if it is a digit, 1 if it is a letter (upper & lowercase)isLetter = (c >> 6) & 1;
// calculate the tuple value using a multiplication to make up the difference between// a digit character and an alphanumerical character// minus 1 for the fact that the letters are not zero basedvalue = ((c & 0xF) + isLetter * (-1 + 10)) << 4;
// check validity of all the other bitsvalidity |= c >> 7; // changed to >>, maybe not OK, use UInt?
validDigitStruct = (c & 0x30) ^ 0x30;validDigit = ((c & 0x8) >> 3) * (c & 0x6);validity |= (isLetter ^ 1) * (validDigitStruct | validDigit);
validLetterStruct = c & 0x18;validLetter = (((c - 1) & 0x4) >> 2) * ((c - 1) & 0x2);validity |= isLetter * (validLetterStruct | validLetter);
// do the same with the lower (less significant) tuplec = hex [hexOffset + 1];isLetter = (c >> 6) & 1;value ^= (c & 0xF) + isLetter * (-1 + 10);result [i] = (byte)value;
// check validity of all the other bitsvalidity |= c >> 7; // changed to >>, maybe not OK, use UInt?
validDigitStruct = (c & 0x30) ^ 0x30;validDigit = ((c & 0x8) >> 3) * (c & 0x6);validity |= (isLetter ^ 1) * (validDigitStruct | validDigit);
validLetterStruct = c & 0x18;validLetter = (((c - 1) & 0x4) >> 2) * ((c - 1) & 0x2);validity |= isLetter * (validLetterStruct | validLetter);}
if (validity != 0) {throw new ArgumentException ("Hexadecimal encoding incorrect for input " + hex);}
return result;}

从Java代码转换。

另一种基于查找表的方法。这种方法对每个字节只使用一个查找表,而不是每个字节一个查找表。

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);}

我还在查找表中使用ushortstruct{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);elseresult[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;}

这是对Tomalak非常流行的答案修订版4的回答(以及随后的编辑)。

我将证明此编辑是错误的,并解释为什么可以恢复它。在此过程中,你可能会了解到一些内部知识,并看到另一个示例,说明过早优化的真正含义以及它如何伤害你。

tl; dr:如果你赶时间,就使用Convert.ToByteString.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。给出的原因是:

编辑:您可以通过使用单个字符串来提高长字符串的性能通过解析器,如下所示:

好吧,看看#0的参考代码,它显然已经是“单通道”了;为什么不应该呢?它在字节级别运行,而不是在代理对上运行。

然而,它确实分配了一个新字符串,但无论如何你都需要分配一个传递给Convert.ToByte。此外,修订版中提供的解决方案在每次迭代时分配另一个对象(two-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++){numeral[0] = (char)sr.Read();numeral[1] = (char)sr.Read();output[i] = Convert.ToByte(new string(numeral), 16);}}return output;}

每个十六进制numeral代表一个使用两个数字(符号)的八位字节。

但是,为什么要两次调用StringReader.Read呢?只需调用它的第二个重载并要求它一次读取two-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),以及对输入字符串的冗余引用(内部_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;}

你真的需要一个以与i平行的2步递增的j索引吗?当然不是,只需将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分配字符串并将数据复制到它,而是使用一个中间数组,将十六进制数字复制到该数组,然后自己分配字符串并将数据再次从数组复制到字符串中(当你在字符串构造函数中传递它时)。如果字符串已经在intern池中,第二个副本可能会被优化,但在这种情况下String.Substring也可以避免它。

事实上,如果你再看看String.Substring,你会发现它使用了一些关于如何构造字符串的低级内部知识来比你通常更快地分配字符串,并且它直接内联了CopyTo使用的相同代码,以避免调用开销。

String.Substring

  • 最坏的情况:一个快速分配,一个快速复制。
  • 最佳情况:没有分配,没有副本。

手动方法

  • 最坏的情况:两个正常分配,一个正常副本,一个快速副本。
  • 最佳情况:一个正常分配,一个正常副本。

结论?如果你想使用#0(因为你不想自己重新实现该功能),似乎没有办法击败String.Substring;你所做的只是绕圈子,重新发明轮子(仅使用次优材料)。

请注意,如果您不需要极端的性能,使用Convert.ToByteString.Substring是一个完全有效的选择。记住:只有在您有时间和资源调查它如何正常工作的情况下,才选择替代方案。

如果有Convert.ToByte(char[], Int32),事情当然会有所不同(可以做我上面描述的事情并完全避免String)。

我怀疑那些通过“避免String.Substring”来报告更好性能的人也会避免Convert.ToByte(String, Int32),如果你无论如何都需要性能,你真的应该这样做。看看无数其他答案,发现所有不同的方法来做到这一点。

免责声明:我还没有反编译最新版本的框架来验证参考源是否是最新的,我假设它是。

现在,这一切听起来都很好,合乎逻辑,希望如果你能做到这一点,甚至是显而易见的。但这是真的吗?

Intel(R) Core(TM) i7-3720QM CPU @ 2.60GHzCores: 8Current Clock Speed: 2600Max Clock Speed: 2600--------------------Parsing hexadecimal string into an array of bytes--------------------HexadecimalStringToByteArray_Original: 7,777.09 average ticks (over 10000 runs), 1.2XHexadecimalStringToByteArray_BestEffort: 8,550.82 average ticks (over 10000 runs), 1.1XHexadecimalStringToByteArray_Rev4: 9,218.03 average ticks (over 10000 runs), 1.0X

耶!

Props to Partridge为台式框架,很容易破解。使用的输入是以下SHA-1哈希重复5000次以生成100,000字节长的字符串。

209113288F93A9AB8E474EA78D899AFDBB874355

玩得开心!(但要适度优化。)

下面通过允许本机小写选项来扩展优秀答案这里,并且还处理null或空输入并使其成为扩展方法。

    /// <summary>/// Converts the byte array to a hex string very fast. Excellent job/// with code lightly adapted from 'community wiki' here: https://stackoverflow.com/a/14333437/264031/// (the function was originally named: ByteToHexBitFiddle). Now allows a native lowerCase option/// to be input and allows null or empty inputs (null returns null, empty returns empty)./// </summary>public static string ToHexString(this byte[] bytes, bool lowerCase = false){if (bytes == null)return null;else if (bytes.Length == 0)return "";
char[] c = new char[bytes.Length * 2];
int b;int xAddToAlpha = lowerCase ? 87 : 55;int xAddToDigit = lowerCase ? -39 : -7;
for (int i = 0; i < bytes.Length; i++) {
b = bytes[i] >> 4;c[i * 2] = (char)(xAddToAlpha + b + (((b - 10) >> 31) & xAddToDigit));
b = bytes[i] & 0xF;c[i * 2 + 1] = (char)(xAddToAlpha + b + (((b - 10) >> 31) & xAddToDigit));}
string val = new string(c);return val;}
public static string ToHexString(this IEnumerable<byte> bytes, bool lowerCase = false){if (bytes == null)return null;byte[] arr = bytes.ToArray();return arr.ToHexString(lowerCase);}

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;}

支持小写的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);}

另一种方法是使用stackalloc来减少GC内存压力:

static string ByteToHexBitFiddle(byte[] bytes){var c = stackalloc char[bytes.Length * 2 + 1];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));}c[bytes.Length * 2 ] = '\0';return new string(c);}
static string ByteArrayToHexViaLookupPerByte2(byte[] bytes){var result3 = new uint[bytes.Length];for (int i = 0; i < bytes.Length; i++)result3[i] = _Lookup32[bytes[i]];var handle = GCHandle.Alloc(result3, GCHandleType.Pinned);try{var result = Marshal.PtrToStringUni(handle.AddrOfPinnedObject(), bytes.Length * 2);return result;}finally{handle.Free();}}

在我的测试中,这个函数总是在不安全实现之后的第二个条目。

不幸的是,测试平台不是那么可靠……如果你运行它多次,列表就会被打乱,谁知道在不安全之后哪个真的是最快的!它没有考虑预升温、jit编译时间和GC性能命中。我想重写它以获得更多信息,但我真的没有时间。

具有扩展支持的基本解决方案

public static class Utils{public static byte[] ToBin(this 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;}public static string ToHex(this byte[] ba){return  BitConverter.ToString(ba).Replace("-", "");}}

像下面这样使用这个类

    byte[] arr1 = new byte[] { 1, 2, 3 };string hex1 = arr1.ToHex();byte[] arr2 = hex1.ToBin();

我想出了一个不同的容忍额外字符(空格、破折号…)的代码。它主要受这里一些可接受的快速答案的启发。它允许解析以下“文件”

00-aa-84-fb12 32 FF CD12 0012_32_FF_CD1200d5e68a
/// <summary>Reads a hex string into bytes</summary>public static IEnumerable<byte> HexadecimalStringToBytes(string hex) {if (hex == null)throw new ArgumentNullException(nameof(hex));
char c, c1 = default(char);bool hasc1 = false;unchecked   {for (int i = 0; i < hex.Length; i++) {c = hex[i];bool isValid = 'A' <= c && c <= 'f' || 'a' <= c && c <= 'f' || '0' <= c && c <= '9';if (!hasc1) {if (isValid) {hasc1 = true;}} else {hasc1 = false;if (isValid) {yield return (byte)((GetHexVal(c1) << 4) + GetHexVal(c));}}
c1 = c;}}}
/// <summary>Reads a hex string into a byte array</summary>public static byte[] HexadecimalStringToByteArray(string hex){if (hex == null)throw new ArgumentNullException(nameof(hex));
var bytes = new List<byte>(hex.Length / 2);foreach (var item in HexadecimalStringToBytes(hex)) {bytes.Add(item);}
return bytes.ToArray();}
private static byte GetHexVal(char val){return (byte)(val - (val < 0x3A ? 0x30 : val < 0x5B ? 0x37 : 0x57));//                   ^^^^^^^^^^^^^^^^^   ^^^^^^^^^^^^^^^^^   ^^^^//                       digits 0-9       upper char A-Z     a-z}

复制时请参考完整代码。包括单元测试。

有些人可能会说它对额外的字符太宽容了。所以不要依赖此代码来执行验证(或更改它)。

    // a safe version of the lookup solution:
public static string ByteArrayToHexViaLookup32Safe(byte[] bytes, bool withZeroX){if (bytes.Length == 0){return withZeroX ? "0x" : "";}
int length = bytes.Length * 2 + (withZeroX ? 2 : 0);StateSmall stateToPass = new StateSmall(bytes, withZeroX);return string.Create(length, stateToPass, (chars, state) =>{int offset0x = 0;if (state.WithZeroX){chars[0] = '0';chars[1] = 'x';offset0x += 2;}
Span<uint> charsAsInts = MemoryMarshal.Cast<char, uint>(chars.Slice(offset0x));int targetLength = state.Bytes.Length;for (int i = 0; i < targetLength; i += 1){uint val = Lookup32[state.Bytes[i]];charsAsInts[i] = val;}});}
private struct StateSmall{public StateSmall(byte[] bytes, bool withZeroX){Bytes = bytes;WithZeroX = withZeroX;}
public byte[] Bytes;public bool WithZeroX;}

支持的最短方式和. net核心:

    public static string BytesToString(byte[] ba) =>ba.Aggregate(new StringBuilder(32), (sb, b) => sb.Append(b.ToString("X2"))).ToString();

有一个简单的单行解决方案尚未提到,它将十六进制字符串转换为字节数组(我们不关心这里的负解释,因为它无关紧要):

BigInteger.Parse(str, System.Globalization.NumberStyles.HexNumber).ToByteArray().Reverse().ToArray();

对于Java8,我们可以使用Byte.toUnsignedInt

public static String convertBytesToHex(byte[] bytes) {StringBuilder result = new StringBuilder();for (byte byt : bytes) {int decimal = Byte.toUnsignedInt(byt);String hex = Integer.toHexString(decimal);result.append(hex);}return result.toString();}

将几个答案组合成一个类,以便我以后复制和粘贴方便:

/// <summary>/// Extension methods to quickly convert byte array to string and back./// </summary>public static class HexConverter{/// <summary>/// Map values to hex digits/// </summary>private static readonly char[] HexDigits ={'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
/// <summary>/// Map 56 characters between ['0', 'F'] to their hex equivalents, and set invalid characters/// such that they will overflow byte to fail conversion./// </summary>private static readonly ushort[] HexValues ={0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x000A, 0x000B,0x000C, 0x000D, 0x000E, 0x000F};
/// <summary>/// Empty byte array/// </summary>private static readonly byte[] Empty = new byte[0];
/// <summary>/// Convert a byte array to a hexadecimal string./// </summary>/// <param name="bytes">/// The input byte array./// </param>/// <returns>/// A string of hexadecimal digits./// </returns>public static string ToHexString(this byte[] bytes){var c = new char[bytes.Length * 2];for (int i = 0, j = 0; i < bytes.Length; i++){c[j++] = HexDigits[bytes[i] >> 4];c[j++] = HexDigits[bytes[i] & 0x0F];}
return new string(c);}
/// <summary>/// Parse a string of hexadecimal digits into a byte array./// </summary>/// <param name="hexadecimalString">/// The hexadecimal string./// </param>/// <returns>/// The parsed <see cref="byte[]"/> array./// </returns>/// <exception cref="ArgumentException">/// The input string either contained invalid characters, or was of an odd length./// </exception>public static byte[] ToByteArray(string hexadecimalString){if (!TryParse(hexadecimalString, out var value)){throw new ArgumentException("Invalid hexadecimal string", nameof(hexadecimalString));}
return value;}
/// <summary>/// Parse a hexadecimal string to bytes/// </summary>/// <param name="hexadecimalString">/// The hexadecimal string, which must be an even number of characters./// </param>/// <param name="value">/// The parsed value if successful./// </param>/// <returns>/// True if successful./// </returns>public static bool TryParse(string hexadecimalString, out byte[] value){if (hexadecimalString.Length == 0){value = Empty;return true;}
if (hexadecimalString.Length % 2 != 0){value = Empty;return false;}
try{
value = new byte[hexadecimalString.Length / 2];for (int i = 0, j = 0; j < hexadecimalString.Length; i++){value[i] = (byte)((HexValues[hexadecimalString[j++] - '0'] << 4)| HexValues[hexadecimalString[j++] - '0']);}
return true;}catch (OverflowException){value = Empty;return false;}}}

从. NET 5 RC2开始,您可以使用:

  • #0返回string并且
  • #0返回byte[]

可以使用跨度参数的重载。

老学校的人最快的方法…想念你的指针

    static public byte[] HexStrToByteArray(string str){byte[] res = new byte[(str.Length % 2 != 0 ? 0 : str.Length / 2)]; //check and allocate memoryfor (int i = 0, j = 0; j < res.Length; i += 2, j++) //convert loopres[j] = (byte)((str[i] % 32 + 9) % 25 * 16 + (str[i + 1] % 32 + 9) % 25);return res;}

. NET 5添加了转换为HexString方法。

对于那些使用旧版本的. 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,}

从. NET存储库改编https://github.com/dotnet/runtime/blob/v5.0.3/src/libraries/System.Private.CoreLib/src/System/Convert.cshttps://github.com/dotnet/runtime/blob/v5.0.3/src/libraries/Common/src/System/HexConverter.cs

测试:十六进制字符串到字节数组

我注意到大多数测试都是在将字节数组转换为十六进制字符串的函数上执行的。因此,在这篇文章中,我将关注另一方面:将十六进制字符串转换为字节数组的函数。如果你只对结果感兴趣,你可以跳到总结部分。测试代码文件在文章末尾提供。

标签

我想从接受的答案中命名函数(由Tomalak)StringToByteArrayV1,或者将其快捷键设置为V1。其余函数将以相同的方式命名:V2、V3、V4、…等。

参与函数索引

测试正确性

我通过传递所有256个可能的1字节值来测试正确性,然后检查输出是否正确。结果:

  • V18的问题是字符串以“00”开头(参见Roger Stewart评论)。除此之外,它通过了所有测试。
  • 如果十六进制字符串字母大写:所有函数都成功通过
  • 如果十六进制字符串字母为小写,则以下函数失败:V5_1,V5_2,v7,V8,V15,V19

注:V5_3解决了这个问题(V5_1和V5_2)

性能测试

我已经使用Stopwatch类进行了性能测试。

  • 长字符串的性能
input length: 10,000,000 bytesruns: 100average elapsed time per run:V1 = 136.4msV2 = 104.5msV3 = 22.0msV4 = 9.9msV5_1 = 10.2msV5_2 = 9.0msV5_3 = 9.3msV6 = 18.3msV7 = 9.8msV8 = 8.8msV9 = 10.2msV10 = 19.0msV11 = 12.2msV12 = 27.4msV13 = 21.8msV14 = 12.0msV15 = 14.9msV16 = 15.3msV17 = 9.5msV18 got excluded from this test, because it was very slow when using very long stringV19 = 222.8msV20 = 66.0msV21 = 15.4ms
V1 average ticks per run: 1363529.4V2 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)
  • 长弦的V18性能
V18 took long time at the previous test,so let's decrease length for it:input length: 1,000,000 bytesruns: 100average elapsed time per run: V1 = 14.1ms , V18 = 146.7msV1 average ticks per run: 140630.3V18 is more SLOW than V1 by: 10.4 times (ticks ratio)
  • 短弦的性能
input length: 100 byteruns: 1,000,000V1 average ticks per run: 14.6V2 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)

测试代码

在使用以下代码之前,最好先阅读这篇文章中的免责声明部分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;}

免责声明

警告:我没有适当的测试知识。这些原始测试的主要目的是快速概述所有发布的函数中哪些可能是好的。如果您需要准确的结果,请使用适当的测试工具。

最后,我想说我是新来活跃在stackoverflow,对不起,如果我的帖子是缺乏的。评论,以提高这篇文章将不胜感激。

dotnet 5更新

要从byte[](字节数组)转换为十六进制string,请使用:

System.Convert.ToHexString

var myBytes = new byte[100];var myString = System.Convert.ToHexString(myBytes);

要从十六进制string转换为byte[],请使用:

System.Convert.FromHexString

var myString  = "E10B116E8530A340BCC7B3EAC208487B";var myBytes = System.Convert.FromHexString(myString);

这是我的纯二进制解决方案,不需要库查找,并且还支持大写/小写:

public static String encode(byte[] bytes, boolean uppercase) {char[] result = new char[2 * bytes.length];for (int i = 0; i < bytes.length; i++) {byte word = bytes[i];byte left = (byte) ((0XF0 & word) >>> 4);byte right = (byte) ((byte) 0X0F & word);
int resultIndex = i * 2;result[resultIndex] = encode(left, uppercase);result[resultIndex + 1] = encode(right, uppercase);}return new String(result);}
public static char encode(byte value, boolean uppercase) {int characterCase = uppercase ? 0 : 32;if (value > 15 || value < 0) {return '0';}if (value > 9) {return (char) (value + 0x37 | characterCase);}return (char) (value + 0x30);}

将byte[]转换为十六进制字符串-性能分析

更新时间:2022-04-17

从. NET 5开始,您应该使用转换。十六进制字符串(bytes[])

using System;string result = Convert.ToHexString(bytesToConvert);

关于这个排行榜和基准

胸腺嘧啶的比较似乎是过时的和不完整的,特别是在. NET 5与它的Convert.ToHexString之后,所以我决定~~落入字节到十六进制字符串兔子洞~~创建一个新的,更新的比较与更多的方法从答案到两者这些两个问题。

我使用了BenchamrkDotNet而不是定制的基准测试脚本,希望这会使结果更准确。
请记住,微基准测试永远不会代表实际情况,您应该进行测试。

我在Linux内核5.15.32上运行了这些基准测试,在AMD Ryzen 5800H2x8 GB DDR4@2133 MHz上运行了这些基准测试。
请注意,整个基准测试可能需要很多时间才能完成-在我的机器上大约40分钟。

大写(大写)与小写输出

所有提到的方法(除非另有说明)只关注大写输出。这意味着输出看起来像#0,而不是b33f69

Convert.ToHexString的输出总是大写。尽管如此,幸运的是,与ToLower()配对时没有任何显着的性能下降,尽管如果您担心这一点,unsafe方法都会更快。

在某些方法中(尤其是那些具有位运算符魔法的方法),使字符串小写有效可能是一个挑战,但在大多数情况下,将参数X2更改为x2或将映射中的字母从大写更改为小写就足够了。

排行榜

它按Mean N=100排序。参考点是每个字节的字符串生成器方法。

方法(以纳秒为单位)平均值N=10比率N=10平均值N=100比率N=100平均值N=500比率N=500平均值N=1k比率N=1k平均值N=10k比率N=10k平均值N=100k比率N=100k
字符串生成器聚合字节附加格式364.921.483,680.001.7418 928.331.8638,362.941.87380 994.74美元1.7242,618,861.571.62
每个附加格式的字符串生成器309.591.263 203.111.5220 775.072.0441,398.072.02426,839.961.9337,220,750.151.41
加入选择310.841.262,765.911.3113 549.121.3328 691.161.40304 163 971.3863,541,601.122.41
连接选择301.341.222 733.641.2914 449.531.4229 174.831.42307,196.941.3932,877,994.951.25
字符串连接数组转换所有279.211.132 608.711.2313305.961.3027 207.121.32295,589.611.3462 950 871.382.39
字符串生成器聚合字节附加276.181.122 599.621.2312 788.111.2526 043.541.27255,389.061.1627 664 344.411.05
String Con cat Array全部转换244.810.992361.081.1211,881.181.1623,709.211.15265,197.331.2056 044 744.442.12
每个字节的字符串生成器246.091.002 112.771.0010 200.361.0020 540.771.00220 993.95美元1.0026 387 941.131.00
预分配的每个字节的字符串生成器213.850.871,897.190.909340.660.9219 142.270.93204,968.880.9324 902 075.810.94
比特币兑换140.090.571 207.740.576170.460.6012 438.230.61145 022.350.6617 719 082.72美元0.67
查找权限63.780.26421.750.201 978.220.193 957.580.1935,358.210.164,993,649.910.19
查找和切换53.220.22311.560.151,461.150.142 924.110.1426 180.110.123 771 827.620.14
查询属性41.830.17308.590.151 473.100.142 925.660.1428 440.280.135,060,341.100.19
查找和移位字母数组37.060.15290.960.141,387.010.143 087.860.1529 883.540.145,136,607.610.19
字节操作小数35.290.14251.690.121 180.380.122 347.560.1122,731.550.104 645 593.050.18
字节操作十六进制乘法35.450.14235.220.111 342.500.132661.250.1325 810.540.127,833,116.680.30
字节操作十六进制增量36.430.15234.310.111 345.380.132 737.890.1326 413.920.127,820,224.570.30
查找位置42.030.17223.590.111,016.930.101 979.240.1019 360.070.094 150 234.710.16
查找和移位字母跨度30.000.12216.510.101 020.650.102 316.990.1122,357.130.104 580 277.95美元0.17
查找和移位字母跨度乘法29.040.12207.380.10985.940.102 259.290.1122,287.120.104 563 518.130.17
查找权限32.450.13205.840.10951.300.091 906.270.0918 311.030.083,908,692.660.15
每个ByteS泛的查找跨度25.690.10184.290.09863.790.082 035.550.1019 448.300.094,086,961.290.15
LookupPerByteSpan业务线27.030.11184.260.09866.030.082 005.340.1019,760.550.094 192 457.140.16
查找32跨度不安全直接16.900.0799.200.05436.660.04895.230.048,266.690.041 506 058.050.06
查找32不安全直拨16.510.0798.640.05436.490.04878.280.048 278.180.041 753 655.670.07
转换为十六进制字符串19.270.0864.830.03295.150.03585.860.035 445.730.021 478 363.320.06
转换为十六进制字符串。降低()45.66-175.16-787.86-1 516.65-13 939.71-2 620 046.76-

结论

方法ConvertToHexString无疑是最快的,在我看来,如果你有选择,它应该总是被使用-它快速而干净。

using System;
string result = Convert.ToHexString(bytesToConvert);

如果没有,我决定在下面强调我认为值得的另外两种方法。我决定不突出显示unsafe方法,因为这样的代码可能不仅是不安全,而且我使用过的大多数项目都不允许这样的代码。

值得一提

第一个是LookupPerByteSpan
代码与回答LookupPerByte by混沌代码中的代码几乎相同。这是最快的非unsafe方法基准测试。原始和这个之间的区别在于对较短的输入(最多512字节)使用堆栈分配。这使得这种方法在这些输入上快了大约10%,但在较大的输入上慢了大约5%。由于我使用的大多数数据都短于大,我选择了这个。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);}

第二个是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);}

源代码

所有方法的源代码、基准测试和这个答案都可以在在我的GitHub上作为Gist中找到。