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

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

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


指: </P >

1
2
3
4
5
6
7
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();
}

或: </P >

1
2
3
4
public static string ByteArrayToString(byte[] ba)
{
  return BitConverter.ToString(ba).Replace("-","");
}

这是部甚至更多的variants做它,例如在这里。 </P >

《逆向转换:一起去呢 </P >

1
2
3
4
5
6
7
8
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下拉。 </P >


性能分析

注:自2015年8月20日起新领导。好的。

我通过一些原始的Stopwatch性能测试运行了各种转换方法,一个随机语句运行(n=611000次迭代),一个项目gutenburg文本运行(n=1238957150次迭代)。下面是结果,大致从最快到最慢。所有测量都是以滴答(10000滴答=1毫秒)为单位的,并且所有相关的注释都会与[最慢]的StringBuilder实现进行比较。有关所使用的代码,请参阅下面的或测试框架repo,我现在在这里维护用于运行该代码的代码。好的。免责声明

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

  • 按字节unsafe查找(通过codesinchaos)(通过airbreather添加到测试repo)
    • 文本:4727.85(105.2X)
    • 句子:0.28(99.7x)
  • 按字节查找(通过codesinchaos)
    • 文本:10853.96(更快45.8倍)
    • 句子:0.65(快42.7倍)
  • 字节操作2(通过codesinchaos)
    • 文本:12967.69(更快38.4倍)
    • 句子:0.73(快37.9倍)
  • 字节操作(通过Waleed EISSA)
    • 文本:16856.64(快29.5倍)
    • 句子:0.70(快39.5倍)
  • 查找/移动(通过Nathan Moinvaziri)
    • 文本:23201.23(更快21.4倍)
    • 句子:1.24(快22.3倍)
  • 按一点查找(通过Brian Lambert)
    • 文本:23879.41(快20.8倍)
    • 句子:1.15(快23.9倍)
  • BitConverter(经Tomalak)
    • 文本:113269.34(更快4.4倍)
    • 句子:9.98(快2.8倍)
  • {SoapHexBinary}.ToString(通过mykroft)
    • 文本:178601.39(更快2.8倍)
    • 句子:10.68(快2.6倍)
  • {byte}.ToString("X2")(使用foreach(来源于威尔·迪恩的回答)
    • 文本:308805.38(更快2.4倍)
    • 句子:16.89(快2.4倍)
  • {byte}.ToString("X2")(使用{IEnumerable}.Aggregate,需要system.linq)(通过mark)
    • 文本:352828.20(更快2.1X)
    • 句子:16.87(快2.4倍)
  • Array.ConvertAll(使用string.Join(via will-dean)
    • 文本:675451.57(更快1.1X)
    • 句子:17.95(快2.2倍)
  • Array.ConvertAll(使用string.Concat,要求.NET 4.0)(via will-dean)
    • 文本:752078.70(快1.0倍)
    • 句子:18.28(快2.2倍)
  • {StringBuilder}.AppendFormat(使用foreach(通过tomalak)
    • 文本:672115.77(更快1.1倍)
    • 句子:36.82(快1.1倍)
  • {StringBuilder}.AppendFormat(使用{IEnumerable}.Aggregate,需要system.linq)(来源于tomalak的答案)
    • 文本:718380.63(快1.0倍)
    • 句子:39.71(快1.0倍)

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

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

请随意使用我使用的测试代码。这里包含一个版本,但是可以自由克隆repo并添加您自己的方法。如果您发现有什么有趣的或者想要帮助改进它使用的测试框架,请提交一个请求。好的。

  • 将新的静态方法(Func添加到/tests/convertbyearraytohexstring/test.cs。
  • 将该方法的名称添加到同一类中的TestCandidates返回值中。
  • 通过切换同一类中GenerateTestInput中的注释,确保运行的是所需的输入版本、句子或文本。
  • 点击f5并等待输出(在/bin文件夹中也会生成一个HTML转储)。
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    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)

    此外,Mykroff的SoapHexBinary回答了第三名的分析结果。好的。更新(2013-01-15)

    增加了codesinchaos的字节操作答案,它占据了第一位(在大的文本块上有很大的空白)。好的。更新(2013-05-23)

    添加了内森·莫尼瓦齐里的查找答案和布莱恩·兰伯特博客中的变体。两者都相当快,但没有在我使用的测试机上领先(AMD Phenom 9750)。好的。更新(2014-07-31)

    添加了@codesinchaos新的基于字节的查找答案。它似乎在句子测试和全文测试中都处于领先地位。好的。更新(2015-08-20)

    在这个答案的repo中添加了airbreather的优化和unsafe变体。如果你想在不安全的游戏中玩,你可以在短字符串和大文本上获得比以往任何一个顶级赢家都大的性能提升。好的。好啊。


    有一个类被称为soaphexbinary exactly那是你想要的。 </P >

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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();
    }


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

    速度也很快。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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

    Abandon all hope, ye who enter here

    一个奇怪的小摆弄的解释:

  • bytes[i] >> 4提取一个字节的高半字节bytes[i] & 0xF提取字节的低半字节
  • b - 10对于值b < 10,是< 0,它将成为十进制数字。为b > 10值的>= 0,将成为AF的字母。
  • 由于符号扩展,在有符号32位整数上使用i >> 31提取符号。对于i < 0-1,对于i >= 00
  • 结合2)和3),表明(b-10)>>31将是0代表字母,-1代表数字。
  • 从字母的大小写来看,最后一个和式变为0b在10到15之间。我们想把它映射到A(65)到F(70),这意味着增加55('A'-10)。
  • 考虑到数字的大小写,我们希望调整最后一个求和,因此它将b从0到9映射到0到EDOCX1(48)到9到57。这意味着它需要变成-7('0' - 55)。现在我们只需要乘以7。但由于-1的所有位都是1,所以我们可以使用& -7,因为(0 & -7) == 0(-1 & -7) == -7
  • 进一步考虑:

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


    如果你想更多的灵活性比BitConverter,但我不想让那些clunky 90年代风格的显式的循环,然后你可以做: </P >

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

    或者,如果你是使用.net 4.0: </P >

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

    (《latter虔诚的评论对原创文章。) </P >


    你可以用bitconverter.tostring方法: </P >

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

    输出: </P >

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

    更多信息:bitconverter.tostring方法(字节[ ]) </P >


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    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兄弟:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    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);
    }

    或者,如果您认为直接写入字符串是可以接受的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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;
    }


    现在的问题是一样的encountered今日,我在洞里传出这样的代码: </P >

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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);
    }

    源:论坛的帖子[ ]字节的十六进制字符串阵列"(见《后村pzahra)。在一个小的修改的代码去解除"0x前缀。 </P >

    在做了一些性能测试的代码和它是几乎八个时代比使用bitconverter.tostring(阿姨)(在fastest根据patridge的邮件)。 </P >


    这是对Tomalak高度流行的答案(以及随后的编辑)第4版的回答。好的。

    我将说明这个编辑是错误的,并解释为什么它可以被恢复。在这个过程中,您可能会学到一些关于内部的东西,并看到另外一个例子,说明什么是提前优化,以及它如何影响您。好的。

    tl;dr:只需使用Convert.ToByteString.Substring。如果您赶时间(下面的"原始代码"),如果您不想重新实现Convert.ToByte,这是最好的组合。如果您需要性能,请使用不使用Convert.ToByte的更高级的(请参阅其他答案)。除String.SubstringConvert.ToByte组合使用外,不要使用其他任何东西,除非有人在这个答案的评论中对此有兴趣。好的。

    警告:如果在框架中实现Convert.ToByte(char[], Int32)重载,则此答案可能会过时。这不太可能很快发生。好的。

    一般来说,我不太喜欢说"不要过早优化",因为没人知道"过早"是什么时候。在决定是否优化时,您必须考虑的唯一一件事是:"我是否有时间和资源来研究优化方法?"如果你不这样做,那就太早了,等到你的项目更成熟或者你需要性能(如果有真正的需求,那么你就有时间)。同时,做一些最简单的事情来代替。好的。

    原代码:好的。

    1
    2
    3
    4
    5
    6
    7
    8
        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:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
        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。给出的原因是:好的。

    Edit: you can improve performance for long strings by using a single
    pass parser, like so:

    Ok.

    好吧,看看String.Substring的参考代码,它显然已经是"单通"了,为什么不应该呢?它在字节级别运行,而不是在代理项对上。好的。

    不过,它确实分配了一个新字符串,但是无论如何,您需要分配一个字符串来传递给Convert.ToByte。此外,修订版中提供的解决方案在每次迭代中都会分配另一个对象(双字符数组);您可以安全地将该分配置于循环之外,并重用该数组以避免这种情况。好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
        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?只需调用它的第二个重载,并要求它同时读取两个字符数组中的两个字符;并将调用量减少两个。好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
        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;它更简单、更清晰、更高效。好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
        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平行的两步递增的j指数吗?当然不是,只需将i乘以2(编译器应该能够优化为加法)。好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
        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好的。

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

    手工法好的。

    • 最坏情况:两个正常分配,一个正常复制,一个快速复制。
    • 最佳情况:一个正常分配,一个正常拷贝。

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

    注意,如果您不需要极端的性能,使用Convert.ToByteString.Substring是一个完全有效的选择。记住:只有当你有时间和资源来调查它是如何正常工作的时候,才选择另一种方法。好的。

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

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

    免责声明:我还没有对框架的最新版本进行反编译,以验证引用源是否是最新的,我认为是最新的。好的。

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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

    对!好的。

    为板凳框架提供支撑,很容易破解。使用的输入是以下sha-1哈希重复5000次以生成100000字节长的字符串。好的。

    1
    209113288F93A9AB8E474EA78D899AFDBB874355

    玩得高兴!(但要适度优化。)好的。好啊。


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

    • 编码器表512字节或1024字节(两次大小写同时为大写和小写是需要的)
    • 解码器表256字节或64 kib(单字符查找或双字符查找)

    我的解决方案使用1024字节作为编码表,使用256字节进行解码。

    译码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    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]));
    }

    编码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    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]];
    }

    比较

    1
    2
    3
    4
    5
    StringBuilderToStringFromBytes:   106148
    BitConverterToStringFromBytes:     15783
    ArrayConvertAllToStringFromBytes:  54290
    ByteManipulationToCharArray:        8444
    TableBasedToCharArray:              5651 *

    *这个解决方案

    注释

    在解码ioexception和indexoutofrangeexception期间,可能会发生异常(如果字符的值太高>256)。应该实现流或数组的反/编码方法,这只是概念验证。


    由@codesinchaos补充答案(反向法)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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’,我们必须用大数字,所以我们必须从默认版本中减去32,通过使用& 0x0f生成一些位0

    65是'A'的代码

    48是'0'的代码

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


    这是一个大的邮件。在该类的溶液。在没有计算机patridge馏通的测试,但它表明quite是固定的。在"逆向的过程也必要的十六进制字符串,转换到一个字节数组,所以它作为一wrote逆转(Waleed的溶液。不确定的,如果它的任何阿姨比Tomalak的原始溶液。再次,在没有标注的逆向过程运行的测试patridge通指。 </P >

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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;
    }


    为什么让它配合物?这是简单的在视觉和nbsp;工作室及nbsp;2008年: </P >

    #:C </P >

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

    VB。 </P >

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


    这里并没有大量的答案,但是我发现了一个相当理想的十六进制字符串解析器的简单实现(比公认的要好大约4.5倍)。首先,我的测试输出(第一批是我的实现):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    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"行用于测试正确性。注意它们是相等的。

    实施情况:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    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的东西,将(明显冗余的)字符移动到nibble if序列到另一种方法,但这是它得到的最快的方法。

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


    安全版本:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    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;
            }
        }
    }

    不安全的版本,为那些喜欢性能和不怕不安全的人。六角速度约为35%,六角速度约为10%。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    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;
            }
        }
    }

    顺便说一句对于每次调用的转换函数错误时初始化字母表的基准测试,字母表必须是const(对于字符串)或static readonly(对于char[])。然后,基于字母表的字节[]到字符串的转换速度和字节操作版本一样快。

    当然,测试必须在发行版(带优化)中编译,并关闭调试选项"抑制JIT优化"(如果代码必须可调试,则与"仅启用我的代码"相同)。


    Waleed EISSA代码的反函数(十六进制字符串到字节数组):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
        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功能,支持小写:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
        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);
        }

    从微软的开发人员,一个好的,简单的转换: </P >

    1
    2
    3
    4
    5
    6
    7
    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();
    }

    在《冰上清洁的契约,绩效的人,会对它的使用enumerators尖叫。你可以只山羊的峰值性能和改进的版本的原始tomolak答案: </P >

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public static string ByteArrayToString(byte[] ba)  
    {  
       StringBuilder hex = new StringBuilder(ba.Length * 2);  

       for(int i=0; i < ga.Length; i++)       // <-- Use for loop is faster than foreach  
           hex.Append(ba[i].ToString("X2"));   // <-- ToString is faster than AppendFormat  

       return hex.ToString();  
    }

    这是fastest大学所有的套路,在我见过的父亲在这里发布"。不要把我的话,就用它。性能测试的实现和它的每inspect CIL代码为自己。 </P >


    扩展方法(disclaimer:完全untested代码,BTW:…………………) </P >

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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的解决方案(与一个负载的市场推广方法上的字符串)。 </P >


    在条款的速度表明,这是更好的比任何事情都在这里: </P >

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
      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);
      }


    我将进入这个有点摆弄的竞争,因为我有一个答案,也使用有点摆弄解码十六进制。注意,使用字符数组可能更快,因为调用StringBuilder方法也需要时间。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    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代码转换。


    在做注释你的代码suggested山羊到工作,olipro。hex[i] + hex[i+1]apparently中安int。 </P >

    在做,但是有一些成功的一些提示的完整信息村以虔诚的waleeds hammering法典和本在一起。它的丑陋的混蛋冰雹,但它表明到工作和performs AT 1 / 3的时间相比,给他人(根据我的测试,以patridges测试机制。depending在线输入尺寸。开关的方位吗?到单独的0~9的第一时间就可能收益(因为有咯阿姨的结果是更多的比数的信。 </P >

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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;
    }

    这个版本的BytearraytoHexviaBytemanipulation可以更快。

    从我的报告:

    • 按时间顺序排列:1,68平均滴答数(超过1000次),17,5x
    • 按时间顺序排列2:1,73平均滴答数(超过1000次),16,9x
    • 通过发射率:2,90平均滴答数(超过1000次),10,1X
    • 通过EarrayToHexViaLookupandShift:3,22平均计时周期(超过1000次),9,1X
    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      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);
      }

    我认为这是一个优化:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
        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);
        }

    对于性能,我将使用Drphrozens解决方案。解码器的一个微小的优化可能是使用一个表来为每个字符去掉"<<4"。

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    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]));
    }

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


    另一种多样性的变化是:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    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;          
    }


    和其他inserting到SQL字符串(如果你没有使用命令行参数): </P >

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

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

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

    在我的机器上,它排在字节操作2之后的第二位,是非常可读的代码。以下测试结果也很重要:

    tohexStringChararrayWithChararrayLookup:41589.69平均刻度(超过1000次运行),1.5xToHexStringCharArrayWithStringLookup:50764.06平均刻度(超过1000次运行),1.2xToHexStringBuilderWithCharArrayLookup:62812.87平均计时周期(超过1000次运行),1.0倍

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

  • 索引到字符串以执行查找的惩罚与char数组在大型文件测试中非常重要。
  • 使用已知容量的StringBuilder与使用char的惩罚创建字符串的已知大小的数组甚至更重要。
  • 代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    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;
            }
        }
    }

    下面是我在机器上把代码放入@patricge的测试项目时得到的测试结果。我还添加了一个从十六进制转换为字节数组的测试。执行我的代码的测试运行是由earraytohexviaoptimizedlookupandshift和hextobytearrayviabyemanipulation进行的。从XXXX中提取了转化成己糖。hextobytearrayviasaphexbinary是@mykroff的答案。

    Intel Pentium III Xeon processor

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

    Converting array of bytes into hexadecimal string representation

    ByteArrayToHexViaByteManipulation2: 39,366.64 average ticks (over 1000 runs), 22.4X

    ByteArrayToHexViaOptimizedLookupAndShift: 41,588.64 average ticks
    (over 1000 runs), 21.2X

    ByteArrayToHexViaLookup: 55,509.56 average ticks (over 1000 runs), 15.9X

    ByteArrayToHexViaByteManipulation: 65,349.12 average ticks (over 1000 runs), 13.5X

    ByteArrayToHexViaLookupAndShift: 86,926.87 average ticks (over 1000
    runs), 10.2X

    ByteArrayToHexStringViaBitConverter: 139,353.73 average
    ticks (over 1000 runs),6.3X

    ByteArrayToHexViaSoapHexBinary: 314,598.77 average ticks (over 1000 runs), 2.8X

    ByteArrayToHexStringViaStringBuilderForEachByteToString: 344,264.63
    average ticks (over 1000 runs), 2.6X

    ByteArrayToHexStringViaStringBuilderAggregateByteToString: 382,623.44
    average ticks (over 1000 runs), 2.3X

    ByteArrayToHexStringViaStringBuilderForEachAppendFormat: 818,111.95
    average ticks (over 1000 runs), 1.1X

    ByteArrayToHexStringViaStringConcatArrayConvertAll: 839,244.84 average
    ticks (over 1000 runs), 1.1X

    ByteArrayToHexStringViaStringBuilderAggregateAppendFormat: 867,303.98
    average ticks (over 1000 runs), 1.0X

    ByteArrayToHexStringViaStringJoinArrayConvertAll: 882,710.28 average
    ticks (over 1000 runs), 1.0X


    没有针对速度进行优化,但比大多数答案(0.NET 4.0)更具灵敏性:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <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

    另一个快速功能…

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    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;
    }

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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:

    1
    2
    3
    4
    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))) );
    }

    和反向:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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;
    }


    如果绩效因素,这里的其他改进方案: </P >

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
        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.ToString2.5时代的阿姨,阿姨和我约7时报BitConverter.ToString+去除""字符。 </P >


    它的作用是从字符串到字节数组…

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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个字节。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
        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);
        }


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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
        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());
        }

    下面通过允许本机小写选项扩展了这里的最佳答案,并处理空或空输入,使其成为扩展方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
        /// <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);
        }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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性能命中。我想重写它以获得更多的信息,但我没有真正的时间。


    如果你想山羊"4倍的速度增长wcoenen村"的报告,然后,如果它的注释:hex.Substring(i, 2)明显与hex[i]+hex[i+1]replace </P >

    你也可以把它一步步骑和山羊的i+=2i++都使用到的地方。 </P >


    带扩展支持的基本解决方案

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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("-","");
        }
    }

    像下面这样使用这个类

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

    我怀疑这样的速度会使大多数其他测试失败…

    1
    2
    3
    4
    5
    6
    7
    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