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

How do you convert a byte array to a hexadecimal string, and vice versa?

提问人: 提问时间:11/22/2008 最后编辑:8 revs, 5 users 53%alextansc 更新时间:6/22/2023 访问量:1165463

问:

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

C# 数组十六 进制

评论


答:

1702赞 14 revs, 10 users 50%Tomalak #1

可以从 .NET 5 开始使用 Convert.ToHexString
还有一个反向操作的方法:Convert.FromHexString


对于旧版本的 .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;
}

使用是与 结合使用的最佳选择。有关详细信息,请参阅此答案。如果您需要更好的性能,则必须避免,然后才能丢弃。SubstringConvert.ToByteConvert.ToByteSubString

评论

31赞 Wim Coenen 3/7/2009
您正在使用 SubString。这个循环不是分配了数量可怕的字符串对象吗?
33赞 Tomalak 3/7/2009
老实说 - 除非它大幅降低性能,否则我倾向于忽略这一点,并相信运行时和 GC 会处理它。
94赞 David Boike 3/10/2010
由于一个字节是两个半字节,因此任何有效表示字节数组的十六进制字符串都必须具有偶数字符数。不应在任何地方添加 0 - 添加 0 将假设存在潜在危险的无效数据。如果有的话,如果十六进制字符串包含奇数个字符,则 StringToByteArray 方法应引发 FormatException。
8赞 David Boike 1/28/2013
@00jt 您必须假设 F == 0F。要么它与 0F 相同,要么输入被削波,而 F 实际上是您尚未收到的内容的开始。做出这些假设取决于您的上下文,但我相信通用函数应该拒绝无效的奇数字符,而不是对调用代码做出这种假设。
12赞 00jt 1/31/2013
@DavidBoike 这个问题与“如何处理可能被剪裁的流值”无关,它谈论的是字符串。字符串 myValue = 10.ToString(“X”);myValue 是“A”而不是“0A”。现在去把那个字符串读回字节,哎呀,你把它弄坏了。
7赞 3 revs, 3 users 71%Pure.Krome #2

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

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 的三种解决方案中的任何一种(最后一种是字符串上的扩展方法)。

评论

1赞 jww 2/17/2017
在为这样的问题提供代码之前,您可能应该测试代码。
79赞 5 revs, 5 users 69%Baget #3

可以使用 BitConverter.ToString 方法:

byte[] bytes = {0, 1, 2, 4, 8, 16, 32, 64, 128, 255};
Console.WriteLine( BitConverter.ToString(bytes));

输出:

00-01-02-04-08-10-20-40-80-FF

详细信息: BitConverter.ToString 方法 (Byte[])

评论

17赞 Sly Gryphon 6/28/2011
只回答了一半的问题。
4赞 Saw 12/25/2012
答案的第二部分在哪里?
2赞 Franz D. 5/14/2021
我希望 256 转换为“FF”的事实只是一个错别字......
119赞 5 revs, 4 users 55%Will Dean #4

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

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

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

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

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

评论

23赞 Nestor 11/25/2009
更短:String.Concat(Array.ConvertAll(bytes, x => x.ToString(“X2”))
18赞 Allon Guralnek 6/16/2011
更短:String.Concat(bytes.Select(b => b.ToString(“X2”))) [.NET4]
14赞 Sly Gryphon 6/28/2011
只回答了一半的问题。
2赞 Polyfun 10/17/2014
为什么第二个需要 .Net 4?String.Concat 位于 .Net 2.0 中。
5赞 Austin_Anderson 10/25/2017
那些“90 年代风格”的循环通常更快,但可以忽略不计,在大多数情况下都无关紧要。不过还是值得一提的
551赞 patridge 3/9/2009 #5

性能分析

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

我通过一些粗略的性能测试运行了每种不同的转换方法,一次是随机句子的运行(n=61,1000 次迭代),另一种是 Project Gutenburg 文本的运行(n=1,238,957,150 次迭代)。以下是结果,大致从最快到最慢。所有测量值均以刻度为单位(10,000 个刻度 = 1 毫秒),并将所有相关注释与 [最慢] 实现进行比较。对于使用的代码,请参阅下文或测试框架存储库,我现在在其中维护运行此代码的代码。StopwatchStringBuilder

免責聲明

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

结果

查找表在字节操作方面处于领先地位。基本上,有某种形式的预计算任何给定的半字节或字节在十六进制中是什么。然后,当你翻阅数据时,你只需查找下一部分,看看它会是什么十六进制字符串。然后,该值以某种方式添加到生成的字符串输出中。在很长一段时间里,字节操作(某些开发人员可能更难读取)是性能最好的方法。

最好的办法仍然是找到一些有代表性的数据,并在类似生产的环境中进行尝试。如果具有不同的内存约束,则可能更喜欢分配较少的方法,而不是速度更快但消耗更多内存的方法。

测试代码

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

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

添加了 Waleed 对分析的回答。相当快。

更新 (2011-10-05)

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

更新 (2012-02-05)

测试存储库包含更多变体,例如 .没有一个扰乱结果。 例如,比 更快,但仍然获胜。StringBuilder.Append(b.ToString("X2"))foreach{IEnumerable}.AggregateBitConverter

更新 (2012-04-03)

添加了Mykroft的分析答案,该答案占据了第三名。SoapHexBinary

更新 (2013-01-15)

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

更新 (2013-05-23)

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

更新 (2014-07-31)

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

更新 (2015-08-20)

airbreather 的优化和变体添加到此答案的 repo 中。如果你想玩不安全的游戏,你可以在短字符串和大文本上获得比之前任何顶级赢家更大的性能提升。unsafe

评论

7赞 patridge 1/14/2010
尽管使代码可供您自己执行您请求的操作,但我还是更新了测试代码以包含 Waleed 答案。撇开所有的脾气暴躁不谈,它要快得多。
2赞 patridge 1/16/2013
@CodesInChaos完成。在我的测试中,它也赢了不少。我还没有假装完全理解任何一种顶级方法,但它们很容易隐藏在直接交互中。
7赞 patridge 4/9/2013
这个答案无意回答什么是“自然的”或司空见惯的问题。目标是为人们提供一些基本的性能基准,因为当您需要进行这些转换时,您往往会做很多。如果有人需要原始速度,他们只需在所需的计算环境中使用一些适当的测试数据运行基准测试即可。然后,将该方法塞进扩展方法中,在该扩展方法中,您再也不用查看其实现(例如,)。bytes.ToHexStringAtLudicrousSpeed()
2赞 CodesInChaos 6/22/2014
刚刚产生了一个基于高性能查找表的实现。它的安全变体比我 CPU 上的当前领导者快约 30%。不安全的变体甚至更快。stackoverflow.com/a/24343727/445517
3赞 GilesDMiddleton 8/10/2018
@Goodies 在我的测试中,我发现简单的 Convert.ToBase64String() 非常快(比按字节查找(通过 CodesInChaos)更快)——所以如果有人不关心输出是十六进制的,这是一个快速的单行替换。
62赞 2 revs, 2 users 85%Waleed Eissa #6

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

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 的帖子,这是最快的)。

评论

0赞 Chochos 10/17/2009
更不用说这使用最少的内存。没有创建任何中间字符串。
9赞 Sly Gryphon 6/28/2011
只回答了一半的问题。
0赞 Jonesome Reinstate Monica 2/6/2012
这很棒,因为它基本上适用于任何版本的 NET,包括 NETMF。赢家!
2赞 Brendten Eickstaedt 10/11/2012
公认的答案提供了 2 个优秀的 HexToByteArray 方法,它们代表了问题的另一半。Waleed 的解决方案回答了一个长期存在的问题,即如何在不在此过程中创建大量字符串的情况下做到这一点。
0赞 jjxtra 10/16/2013
新字符串(c)是否复制并重新分配,或者它是否足够聪明,知道何时可以简单地包装char[]?
4赞 Jack Straw #7

对于插入 SQL 字符串(如果不使用命令参数):

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

评论

0赞 Andrei Krasutski 6/8/2019
如果或我们有问题先生!Source == nullSource.Length == 0
0赞 3 revs, 3 users 40%Olipro #8

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

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

12赞 Chris F #9

这是一篇很棒的文章。我喜欢 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;
}

评论

1赞 Marc Novakowski 1/27/2010
此代码假定十六进制字符串使用大写的 alpha 字符,如果十六进制字符串使用小写的 alpha,则会爆炸。为了安全起见,可能希望对输入字符串进行“大写”转换。
0赞 Chris F 1/27/2010
这是一个敏锐的观察,马克。编写代码是为了反转 Waleed 的解决方案。ToUpper 调用会减慢算法的速度,但允许它处理小写的 alpha 字符。
3赞 Amir Rezaei 2/13/2011
Convert.ToByte(topChar + bottomChar) 可以写成 (byte)(topChar + bottomChar)
0赞 Ben Voigt 8/1/2014
为了在不造成重大性能损失的情况下处理这两种情况,hexString[i] &= ~0x20;
262赞 4 revs, 4 users 89%Mykroft #10

有一个名为 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();
}

评论

40赞 Sly Gryphon 6/28/2011
SoapHexBinary 可从 .NET 1.0 获得,位于 mscorlib 中。尽管它的命名空间很有趣,但它完全符合问题提出的要求。
4赞 Carter Medlin 11/1/2011
很棒的发现!请注意,与另一个解决方案一样,您需要在奇数字符串中填充 GetStringToBytes 的前导 0。
0赞 mfloryan 1/26/2012
你看到实现思路了吗?恕我直言,接受的答案有一个更好的答案。
7赞 Jeremy 4/29/2012
有趣的是,在这里看到 Mono 实现:github.com/mono/mono/blob/master/mcs/class/corlib/......
12赞 juFo 3/11/2020
SoapHexBinary 在 .NET Core/.NET Standard 中不受支持...
4赞 Fredrik Hu #11

我没有得到你建议工作的代码,Olipro。 显然返回了一个.hex[i] + hex[i+1]int

然而,我确实取得了一些成功,从 Waleeds 代码中获取了一些提示并将其锤炼在一起。它丑陋得要命,但根据我的测试(使用 patridges 测试机制),与其他测试相比,它似乎在 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;
}
4赞 Alexey Borzenkov #12

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

  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);
  }
9赞 4 revs, 3 users 85%Mark #13

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

虽然以上内容干净紧凑,但性能迷会使用枚举器对此大喊大叫。您可以通过 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();   
} 

这是迄今为止我在这里看到的所有例程中最快的。不要只相信我的话......对每个例程进行性能测试,并自行检查其 CIL 代码。

评论

3赞 dolmen 8/21/2013
迭代器不是此代码的主要问题。您应该对 .b.ToSting("X2")
14赞 2 revs, 2 users 91%Craig Poulton #14

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

C#:

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

VB格式:

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

评论

3赞 Ricky 8/4/2016
原因是性能,当您需要高性能解决方案时。:)
1赞 Thomas Levesque #15

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

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

它大约快 2.5 倍,大约 7 倍 + 去除“-”字符。BitConverter.ToStringBitConverter.ToString

评论

5赞 dolmen 8/21/2013
如果性能很重要,您就不会将一个字节拆分为两个半字节。Math.DivRem
0赞 Thomas Levesque 8/21/2013
@dolmen,您是否在有和没有的情况下运行了性能测试?我严重怀疑它对性能有任何影响:的实现正是您手动执行的操作,并且方法非常简单,因此它始终由 JIT 内联(实际上它旨在内联,正如应用于它的属性所建议的那样)Math.DivRemMath.DivRemTargetedPatchingOptOut
1赞 Søren Boisen 8/4/2016
@ThomasLevesque DivRem 的实现执行模运算和除法。为什么你认为这些操作正是你手动执行的操作?对我来说,自然的实现是 github.com/patridge/PerformanceStubs/blob/master/...,它执行位移和逻辑和。即使在现代处理器上,这些运算也比模数和除法便宜得多。
20赞 3 revs, 2 users 82%drphrozen #16

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

  • 编码器表 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)。应该实现对流或数组进行解码/编码的方法,这只是一个概念证明。

评论

2赞 dolmen 8/21/2013
在 CLR 上运行代码时,256 字节的内存使用量可以忽略不计。
4赞 3 revs, 2 users 73%ClausAndersen #17

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

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

使用 and 代替 and 可能会带来一些理论上的好处,但我怀疑编译器比我处理得更好。offset++offsetoffsetoffset + 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]));
}

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

2赞 Stas Makutin #18

多样性的另一种变化:

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;          
}
-3赞 John Craig #19

我怀疑这种速度会让大多数其他测试都失望......

Public Function BufToHex(ByVal buf() As Byte) As String
    Dim sB As New System.Text.StringBuilder
    For i As Integer = 0 To buf.Length - 1
        sB.Append(buf(i).ToString("x2"))
    Next i
    Return sB.ToString
End Function

评论

3赞 Brian Reichle 12/2/2011
是什么让你这么想?为缓冲区中的每个字节创建一个新的字符串对象,并且不会预先调整字符串生成器的大小(这可能导致缓冲区在大型数组上多次调整大小)。
0赞 Behrooz 12/17/2012
简明的英语字节转换:)
9赞 3 revs, 2 users 89%Ben Mosher #20

这里不一一列举,但我发现了一个相当最佳(比公认的要好 ~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;
}

我尝试了一些东西,并将(明显多余的)字符到半字节序列移动到另一种方法,但这是它得到的最快速度。unsafeif

(我承认这回答了一半的问题。我觉得字符串->byte[] 转换没有得到充分体现,而 byte[]->string 角度似乎得到了很好的覆盖。因此,这个答案。

评论

1赞 Ben Mosher 5/23/2012
对于 Knuth 的追随者:我这样做是因为我需要每隔几分钟左右解析几千个十六进制字符串,所以尽可能快地(在内部循环中)很重要。如果没有发生许多这样的解析,Tomalak 的解决方案不会明显变慢。
1赞 Rick #21

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

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();
    }
1赞 Behrooz #22

我想它的速度值得额外的 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);
    }

评论

3赞 CodesInChaos 1/15/2013
它实际上比其他基于表查找的方法慢(至少在我的测试中)。使用而不是中断一些 JIT 优化模式,额外的计数器似乎也很昂贵。!=<Offset
161赞 7 revs, 4 users 58%CodesInChaos #23

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

它的速度也相当快。

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
    是 表示值 ,它将成为十进制数字
    是表示值 ,它将变成一个字母 从 到 。
    < 0b < 10>= 0b > 10AF
  3. 使用 on a signed 32 位整数提取符号,这要归功于符号扩展。 这将是 for 和 for .i >> 31-1i < 00i >= 0
  4. 将 2) 和 3) 组合在一起,表明这将用于字母和数字。(b-10)>>310-1
  5. 查看字母的大小写,最后一个总和变为 ,并且在 10 到 15 的范围内。我们想将其映射到 (65) 到 (70),这意味着添加 55 ()。0bAF'A'-10
  6. 查看数字的大小写,我们想要调整最后一个求和,以便它从 0 到 9 的范围映射到 (48) 到 (57) 的范围。这意味着它需要变为 -7 ()。
    现在我们可以乘以 7。但是由于 -1 由所有位均为 1 表示,因此我们可以改用 since 和 。
    b09'0' - 55& -7(0 & -7) == 0(-1 & -7) == -7

一些进一步的考虑:

  • 我没有使用第二个循环变量来索引,因为测量表明计算它更便宜。ci
  • 使用完全作为循环的上限允许 JITter 消除对 的边界检查,所以我选择了该变体。i < bytes.Lengthbytes[i]
  • 创建 int 允许在字节之间进行不必要的转换。b

使用新函数可以实现同样的事情,这样就不必分配单独的数组。string.Createchar[]

  • 我们还可以将每个半字节转换为函数。
  • 添加应允许该函数从 JIT 中消失。AggressiveInlining
  • 我们可以调整以获得小写的结果。32
  • 我们也可以使用代替数组,这允许更广泛的内存缓冲区(包括数组)。Memory<byte>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static string ByteToHexBitFiddle(Memory<byte> bytes, bool lowercase = false) =>
    lowercase
    ? string.Create(bytes.Length * 2, bytes, LowercaseFillHex)
    : string.Create(bytes.Length * 2, bytes, UppercaseFillHex);

static void UppercaseFillHex(Span<char> span, Memory<byte> mem)
{
    var bytes = mem.Span;
    for (int i = 0; i < bytes.Length; i++)
    {
        span[i * 2] = ConvertNibble(bytes[i] >> 4, 0);
        span[i * 2 + 1] = ConvertNibble(bytes[i] & 0xF, 0);
    }
}

static void LowercaseFillHex(Span<char> span, Memory<byte> mem)
{
    var bytes = mem.Span;
    for (int i = 0; i < bytes.Length; i++)
    {
        span[i * 2] = ConvertNibble(bytes[i] >> 4, 32);
        span[i * 2 + 1] = ConvertNibble(bytes[i] & 0xF, 32);
    }
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
static char ConvertNibble(int nibble, int adjust) =>
    (char)(55 + adjust + nibble + (((nibble - 10) >> 31) & (-7 - adjust)));

评论

12赞 AaA 1/18/2013
和 ?hex stringbyte[] array
18赞 Edward 8/3/2013
+1 在调用那一点黑魔法后正确引用您的来源。所有人都向克苏鲁致敬。
6赞 Syaiful Nizam Yahya 11/6/2013
字符串到byte[]呢?
10赞 eXavier 1/7/2014
好!对于那些需要小写输出的人来说,表达式显然更改为87 + b + (((b-10)>>31)&-39)
2赞 CoolOppo 6/10/2015
@AaA 你说“”,字面意思是字节数组的数组,或者 .我只是在开玩笑。byte[] arraybyte[][]
3赞 3 revsJJJ #24

两个混搭,将两个半字节操作合二为一。

可能非常有效的版本:

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-hacking 版本:

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

评论

1赞 CoperNick 7/29/2013
HexStringToByteArray(“09”) 返回0x02,这是错误的
3赞 16 revs, 3 users 81%JamieSee #25

这是我的想法。我创建了一对扩展类来扩展字符串和字节。在大文件测试中,性能可与 Byte Manipulation 2 相媲美。

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

它在我的机器上仅次于 Byte Manipulation 2 排名第二,并且是非常可读的代码。以下测试结果也值得关注:

ToHexStringCharArrayWithCharArrayLookup:41,589.69 个平均分时(超过 1000 次运行),1.5 倍 ToHexStringCharArrayWithStringLookup:50,764.06 个平均分时(超过 1000 次运行),1.2 倍 ToHexStringStringBuilderWithCharArrayLookup:62,812.87 个平均分时(超过 1000 次运行),1.0 倍

基于上述结果,似乎可以得出结论:

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

以下是我将代码放入计算机上的 @patridge 测试项目中时得到的测试结果。我还添加了一个从十六进制转换为字节数组的测试。执行代码的测试运行是 ByteArrayToHexViaOptimizedLookupAndShift 和 HexToByteArrayViaByteManipulation。HexToByteArrayViaConvertToByte 取自 XXXX。HexToByteArrayViaSoapHexBinary 是 @Mykroft 的答案。

Intel Pentium III Xeon 处理器

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

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


ByteArrayToHexViaByteManipulation2:39,366.64 次平均分时(超过 1000 次运行),22.4 倍

ByteArrayToHexViaOptimizedLookupAndShift:41,588.64 个平均分时 (超过 1000 次运行),21.2 倍

ByteArrayToHexViaLookup:55,509.56 个平均分时(超过 1000 次运行),15.9 倍

ByteArrayToHexViaByteManipulation:65,349.12 次平均即时报价(超过 1000 次运行),13.5 倍

ByteArrayToHexViaLookupAndShift:86,926.87 个平均分时(超过 1000 个 运行),10.2 倍

ByteArrayToHexStringViaBitConverter:平均 139,353.73 刻度(超过 1000 次运行),6.3 倍

ByteArrayToHexViaSoapHexBinary:314,598.77 个平均分时(超过 1000 次运行),2.8 倍

ByteArrayToHexStringViaStringBuilderForEachByteToString:344,264.63 平均滴答声(超过 1000 次运行),2.6 倍

ByteArrayToHexStringViaStringBuilderAggregateByteToString:382,623.44 平均滴答声(超过 1000 次运行),2.3 倍

ByteArrayToHexStringViaStringBuilderForEachAppendFormat:818,111.95 平均滴答声(超过 1000 次运行),1.1 倍

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

ByteArrayToHexStringViaStringBuilderAggregateAppendFormat:867,303.98 平均滴答声(超过 1000 次运行),1.0 倍

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


22赞 3 revs, 2 users 93%CoperNick #26

用@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' 它与 which is 相同(这是因为 )。hi = ch - 65 + 10 + 7;hi = ch - 480xffffffff & 7

对于“A”..'F' 是 (这是因为 )。hi = ch - 65 + 10;0x00000000 & 7

对于'a'..'f' 我们必须使用大数字,因此我们必须通过使用 .0& 0x0f

65 是'A'

48 是'0'

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

4赞 JoseH #27

此版本的 ByteArrayToHexViaByteManipulation 可能会更快。

从我的报告中:

  • ByteArrayToHexViaByteManipulation3:1.68 次平均分时(超过 1000 次运行),17.5 倍
  • ByteArrayToHexViaByteManipulation2:1.73 次平均滴答(超过 1000 次运行),16.9 倍
  • ByteArrayToHexViaByteManipulation:2.90 次平均即时报价(超过 1000 次运行),10.1 倍
  • ByteArrayToHexViaLookupAndShift:3.22 个平均分时(超过 1000 次运行),9.1 倍
  • ...

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

我认为这是一个优化:

    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);
    }
3赞 MCattle #28

未针对速度进行优化,但比大多数答案 (.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" & hex
    Return Enumerable.Range(0, hex.Length \ 2).Select(Function(i) Convert.ToByte(hex.Substring(i * 2, 2), 16)).ToArray
End Function

<Extension()>
Public Function ToHexString(bytes As IEnumerable(Of Byte)) As String
    Return String.Concat(bytes.Select(Function(b) b.ToString("X2")))
End Function
3赞 2 revs, 2 users 70%spacepille #29

另一个快速功能...

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;
}
1赞 2 revs, 2 users 92%astrada #30

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

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

    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());
    }
9赞 3 revsMaratius #31

安全版本:

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

顺便说一句对于每次调用转换函数错误时初始化字母表的基准测试,字母表必须是常量(对于字符串)或静态只读(对于 char[])。然后,基于字母的 byte[] 到字符串的转换变得与字节操作版本一样快。

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

4赞 Maarten Bodewes #32

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

public static String ToHex (byte[] data)
{
    int dataLength = data.Length;
    // pre-create the stringbuilder using the length of the data * 2, precisely enough
    StringBuilder 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 letter
        int 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 character
        int code = '0' + ((b >> 4) & 0xF) + isLetter * ('A' - '9' - 1);
        // now append the result, after casting the code point to a character
        sb.Append ((Char)code);

        // do the same with the lower (less significant) tuple
        isLetter = (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 array
    int 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 based
        value = ((c & 0xF) + isLetter * (-1 + 10)) << 4;

        // check validity of all the other bits
        validity |= 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) tuple
        c = hex [hexOffset + 1];
        isLetter = (c >> 6) & 1;
        value ^= (c & 0xF) + isLetter * (-1 + 10);
        result [i] = (byte)value;

        // check validity of all the other bits
        validity |= 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 代码转换而来。

评论

1赞 Maarten Bodewes 1/21/2014
嗯,我真的应该优化它并在内部使用而不是 ints......Char[]Char
1赞 Peteter 6/13/2019
对于 C#,最好在使用变量的地方初始化变量,而不是在循环之外初始化变量,以便让编译器进行优化。无论哪种方式,我都能获得同等的性能。
88赞 2 revsCodesInChaos #33

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

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

评论

1赞 Raif Atef 11/5/2014
为什么在不安全版本中创建查找表会交换预先计算的字节的半字节?我认为字节序只是改变了由多个字节组成的实体的顺序。
1赞 CodesInChaos 11/7/2014
@RaifAtef 这里重要的不是小食的顺序。但是 16 位字在 32 位整数中的顺序。但我正在考虑重写它,以便无论字节序如何,都可以运行相同的代码。
2赞 Joe Amenta 1/9/2016
好吧,我会咬一口 - 无限期固定而不是只做第三个语句并让 GC 在这种方法不运行时将数组重新定位到其核心内容有什么好处?_lookup32Unsafefixed
7赞 Narvalex 3/9/2017
这只是回答了一半的问题......从十六进制字符串到字节怎么样?
8赞 Konrad 12/4/2019
@CodesInChaos我想知道现在是否可以使用而不是??Spanunsafe
26赞 3 revs, 2 users 97%tne #34

这是对托马拉克广受欢迎的答案(以及后续编辑)的修订版 4 的回答。

我将说明此编辑是错误的,并解释为什么可以恢复它。在此过程中,您可能会了解一些内部结构,并看到另一个例子,说明过早优化到底是什么,以及它如何咬你。

tl;dr:只需使用,如果您赶时间(下面的“原始代码”),如果您不想重新实现,这是最好的组合。使用更高级的东西(见其他答案),如果你需要性能,就不用了。除了与 结合使用外,不要使用任何其他内容,除非有人在此答案的评论中对此有有趣的看法。Convert.ToByteString.SubstringConvert.ToByteConvert.ToByteString.SubstringConvert.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.SubstringStringReader

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

好吧,看看 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;
    }

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

但是,为什么要打两次电话呢?只需调用它的第二个重载,并要求它一次读取 two-char 数组中的两个字符;并将呼叫量减少 2 次。StringReader.Read

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

剩下的是一个字符串读取器,它唯一添加的“值”是一个并行索引(内部),你可以自己声明(例如),一个冗余长度变量(内部)和对输入字符串的冗余引用(内部)。换句话说,它是无用的。_posj_length_s

如果你想知道“读取”的方式,只需查看代码,它所做的只是调用输入字符串。剩下的只是簿记开销,以维持我们不需要的价值。ReadString.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;
    }

你真的需要一个以两个平行步长递增的索引吗?当然不是,只需乘以 2(编译器应该能够优化为加法)。jii

    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.SubstringString.Substring

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

String.Substring

  • 最坏情况:一次快速分配,一次快速拷贝。
  • 最佳情况:无分配,无副本。

手动方式

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

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

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

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

我怀疑那些通过“避免”报告更好表现的人也会避免,如果你无论如何都需要表现,你真的应该这样做。看看无数其他答案,发现所有不同的方法来做到这一点。String.SubstringConvert.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

是的!

Partridge 的长凳框架,很容易破解。使用的输入是以下 SHA-1 哈希重复 5000 次,形成一个 100,000 字节长的字符串。

209113288F93A9AB8E474EA78D899AFDBB874355

玩得愉快!(但要适度优化。

评论

1赞 Priya Jagtap 4/22/2020
错误:{“找不到任何可识别的数字。
1赞 2 revsNicholas Petersen #35

下面通过允许本机小写选项来扩展此处的出色答案,并且还处理 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);
    }
7赞 Geograph #36

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);
    }
3赞 Kel #37

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

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);
}
1赞 Tommaso Ercole #38
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 性能影响。我想重写它以获得更多信息,但我真的没有时间。

0赞 cahit beyaz #39

支持扩展的基本解决方案

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();
1赞 SandRock #40

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

00-aa-84-fb
12 32 FF CD
12 00
12_32_FF_CD
1200d5e68a
/// <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
}

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

有人可能会说它对额外字符的容忍度太高了。因此,不要依赖此代码来执行验证(或更改它)。

1赞 2 revstomasz_kajetan_stanczak #41
    // 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;
    }
3赞 Erçin Dedeoğlu #42

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

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

评论

1赞 Andy 3/9/2021
如果是 16 字节或更少,这很好。但是,它应该是有效地处理任何长度的字节数组。banew StringBuilder(ba.Length * 2)
2赞 Gregory Morse #43

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

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

评论

0赞 Roger Stewart 3/11/2020
这不会保留前导字节 0。例如,字符串的结果是一个单字节数组,而不是预期的三字节数组"000080"{ 0x80 }{ 0x00, 0x00, 0x80 }
0赞 Gregory Morse 3/12/2020
是的,我想类似于 Enumerable.Repeat<byte>(0, (len(str) / 2 - len(bigIntBytes))。Concat(bigIntBytes)。在这种情况下,需要 ToArray()
0赞 Simon Bondo 4/17/2020
您可以使数组处于大端模式,而不是反转数组:.ToByteArray(isBigEndian: true)
0赞 Gregory Morse 4/19/2020
谢谢西蒙,我不知道或至少忘记了这个方便的参数!
-2赞 ravthiru #44

在 Java 8 中,我们可以使用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();
}

评论

1赞 Maarten Bodewes 5/2/2020
不正确,可能只返回一个字符而不是两个字符。toHexString
1赞 Paul #45

将一些答案合并到一个类中,以便我以后复制和粘贴:

/// <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;
        }
    }
}
38赞 2 revs, 2 users 77%balrob #46

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

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

评论

3赞 MÇT 8/26/2021
在 .NET 6 中,在 CPU 上使用 SSSE3 指令集,因此它不仅像在 .NET 5 中那样方便使用,而且对于超过 3 个字节的输入也更高性能。随着输入大小的增加,性能差异更加明显。Convert.ToHexString
7赞 user5583316 #47

老派人士最快的方法......想念你的指针

    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;
    }
7赞 2 revsAli Zahid #48

.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,
}

改编自 .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

6赞 7 revs, 2 users 97%Rosei #49

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

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

标签

我想从接受的答案(由 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)

性能测试

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

  • 长字符串的性能
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)
  • V18 长串性能
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)
  • 短字符串的性能
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)

测试代码

在使用以下代码中的任何内容之前,最好先阅读本文中的免责声明部分 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 的新手,如果我的帖子缺少,很抱歉。 为加强这篇文章而发表的评论将不胜感激。

评论

1赞 JosephDoggie 7/8/2021
哇,这真是太费力了!
25赞 TrustworthySystems #50

Dotnet 5 更新

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

System.Convert.ToHexString

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

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

System.Convert.FromHexString

var myString  = "E10B116E8530A340BCC7B3EAC208487B";
var myBytes = System.Convert.FromHexString(myString);
0赞 Ben #51

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

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);
}
27赞 5 revs, 2 users 96%antoninkriz #52

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


更新日期: 2022-04-17


从 .NET 5 开始,您应该使用 Convert.ToHexString(bytes[])

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

关于此排行榜和基准

胸腺嘧啶的比较似乎已经过时且不完整,尤其是在 .NET 5 及其 之后,所以我决定~~落入字节到十六进制字符串兔子洞~~创建一个新的、更新的比较,其中包含来自这两个问题的答案的更多方法。Convert.ToHexString

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

我在内核为 5.15.32Linux 上运行了这些基准测试,该 Linux 在具有 2x8 GB DDR4 @ 2133 MHzAMD Ryzen 5800H 上运行。
请注意,整个基准测试可能需要很长时间才能完成 - 在我的机器上大约需要 40 分钟。

UPPERCASE(大写)与小写输出

提到的所有方法(除非另有说明)仅关注 UPPERCASE 输出。这意味着输出将类似于 B33F69,而不是 .b33f69

from 的输出始终为大写。不过,值得庆幸的是,与 配对时没有任何明显的性能下降,尽管如果您担心这两种方法,这两种方法都会更快。Convert.ToHexStringToLower()unsafe

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

排行榜

它按 排序。引用点是 StringBuilderForEachByte 方法。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
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
字符串JoinSelect 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
StringConcatSelect 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
字符串JoinArrayConvertAll 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
StringBuilderForEachByte 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
StringBuilderForEachBytePreAllocated 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
LookupPerNibble 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
WhilePropertyLookup 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
LookupAndShift字母数组 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
ByteManipulationDecimal 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
ByteManipulationHexMultiply 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
ByteManipulationHexIncrement 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
而 LocalLookup 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
LookupAndShiftAlphabetSpan 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
LookupAndShiftAlphabetSpanMultiply 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
LookupPerByte 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
LookupPerByteSpan 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
查找32SpanUnsafeDirect 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
Lookup32不安全直接 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
ConvertToHexString 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);

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

值得一提

第一个是.
该代码与此答案CodesInChaos 的代码几乎相同。这是最快的非方法基准测试。原始版本和本版本之间的区别在于使用堆栈分配来获得更短的输入(最多 512 字节)。这使得这种方法在这些输入上快了大约 10%,但在较大的输入上慢了大约 5%。由于我处理的大多数数据都比较大的数据短,因此我选择了这个。 速度也非常快,但与所有其他方法相比,其映射的代码大小太大。
LookupPerByteSpanLookupPerByteunsafeLookupSpanPerByteSpanReadOnlySpan<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);
}

第二个是. 首先,我想提一下,这是我的创作。但是,我相信这种方法不仅非常快,而且易于理解。 速度来自 C# 7.3 中发生的更改,其中返回常量数组初始化的声明方法 - - 被编译为程序的静态数据,因此省略了冗余内存分配。[资源]LookupAndShiftAlphabetSpanMultiplyReadOnlySpan<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 找到。

评论

1赞 user4779 6/20/2022
ToHexString 方法非常有用。似乎FromHexString进行了相反的操作
0赞 Rui Caramalho 6/13/2023
为什么我会收到编译错误?谢谢cannot convert 'System.Span<char>' to 'char*'return new string(res)
0赞 antoninkriz 6/14/2023
@RuiCaramalho 由于您使用的是 .Net Framework 而不是 .NET,因此应该使用 .可能还需要安装 NuGet 包以支持 。请注意,此代码是使用 .NET 而不是 .Net Framework 测试的,因此性能可能会(也可能不会)存在一些差异。res.ToString()new string(res)System.MemorySystem.Span
0赞 Rui Caramalho 6/14/2023
@antoninkriz 谢谢
0赞 mhapps #53

扩展 BigInteger 方法(Gregory Morse 在上面提到过)。我不能评论效率,它使用 System.Linq.Reverse(),但它很小且内置。

        // To hex
        byte[] bytes = System.Text.Encoding.UTF8.GetBytes("Test String!£");
        string hexString = new System.Numerics.BigInteger(bytes.Reverse().ToArray()).ToString("x2");

        // From hex
        byte[] fromHexBytes = System.Numerics.BigInteger.Parse(hexString, System.Globalization.NumberStyles.HexNumber).ToByteArray().Reverse().ToArray();

        // Unit test
        CollectionAssert.AreEqual(bytes, fromHexBytes);