如何在C#中进行最轻量级的十六进制转字节?

14

可能是重复问题:
如何将字节数组转换为十六进制字符串,反之亦然?

我需要一种高效快速的方法来进行此转换。我已经尝试过两种不同的方法,但它们对我来说不够高效。是否有其他快速的方法可以在实时应用程序中处理大量数据?

  public byte[] StringToByteArray(string hex)
    {
        return Enumerable.Range(0, hex.Length / 2).Select(x => Byte.Parse(hex.Substring(2 * x, 2), NumberStyles.HexNumber)).ToArray(); 
    }

对我来说,上面那一个感觉更有效率。

 public static byte[] stringTobyte(string hexString)
    {
        try
        {
            int bytesCount = (hexString.Length) / 2;
            byte[] bytes = new byte[bytesCount];
            for (int x = 0; x < bytesCount; ++x)
            {
                bytes[x] = Convert.ToByte(hexString.Substring(x * 2, 2), 16);
            }
            return bytes;
        }
        catch
        {
            throw;
        }

5
另一个问题虽然表面上涉及双向转换,但最终集中在将字节转换为十六进制。而这个问题则是关于在另一个方向上进行最佳转换的,因此就我个人而言,我认为它能够增加一些内容。 - JasonD
1
这绝对不是重复的,因为它提供了针对特定问题的非常专注的一组答案。 - Charles Okwuagwu
3个回答

28

如果你真的需要效率,则:

  • 不要创建子字符串
  • 不要创建迭代器

或者,为了简单而不是效率,摆脱只有一个重新抛出异常的catch块的try块。

这将是一个相当高效的版本:

public static byte[] ParseHex(string hexString)
{
    if ((hexString.Length & 1) != 0)
    {
        throw new ArgumentException("Input must have even number of characters");
    }
    int length = hexString.Length / 2;
    byte[] ret = new byte[length];
    for (int i = 0, j = 0; i < length; i++)
    {
        int high = ParseNybble(hexString[j++]);
        int low = ParseNybble(hexString[j++]);
        ret[i] = (byte) ((high << 4) | low);
    }

    return ret;
}

private static int ParseNybble(char c)
{
    // TODO: Benchmark using if statements instead
    switch (c)
    {
        case '0': case '1': case '2': case '3': case '4':
        case '5': case '6': case '7': case '8': case '9':
            return c - '0';
        case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
            return c - ('a' - 10);
        case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
            return c - ('A' - 10);
        default:
            throw new ArgumentException("Invalid nybble: " + c);
    }
    return c;
}

TODO是指像这样的替代方案。我还没有测量哪个更快。

private static int ParseNybble(char c)
{
    if (c >= '0' && c <= '9')
    {
        return c - '0';
    }
    c = (char) (c & ~0x20);
    if (c >= 'A' && c <= 'F')
    {
        return c - ('A' - 10);
    }
    throw new ArgumentException("Invalid nybble: " + c);
}

1
if变体中,您可以通过使用c&〜0x20折叠大写和小写的测试。 - CodesInChaos
看起来修订版7引入了一个微妙的错误——它正在比较'A'...'F'(0x41...0x46),但是减去的是'a'(0x61)(而在编辑之前是减去的'A')... - johnny
@RoyiNamir:我不确定你在谈论哪一部分代码,或者你所说的“没有检查条件”是哪一部分。鉴于我的答案中有几个条件,请非常具体。 - Jon Skeet
@JonSkeet 我指的是这个 - Royi Namir
是的,我太傻了。我只看了那几行代码而没有看整个方法。 - Royi Namir
显示剩余18条评论

5
作为 Jon 的基于 if 的 ParseNybble 的一种变体:

作为 Jon 基于 if 的 ParseNybble 的一种变体:

public static byte[] ParseHex(string hexString)
{
    if ((hexString.Length & 1) != 0)
    {
        throw new ArgumentException("Input must have even number of characters");
    }
    byte[] ret = new byte[hexString.Length / 2];
    for (int i = 0; i < ret.Length; i++)
    {
        int high = ParseNybble(hexString[i*2]);
        int low = ParseNybble(hexString[i*2+1]);
        ret[i] = (byte) ((high << 4) | low);
    }

    return ret;
}

private static int ParseNybble(char c)
{
    unchecked
    {
        uint i = (uint)(c - '0');
        if(i < 10)
            return (int)i;
        i = ((uint)c & ~0x20u) - 'A';
        if(i < 6)
            return (int)i+10;
        throw new ArgumentException("Invalid nybble: " + c);
    }
}

5
我从这个问题中获取了基准测试代码,并对其进行修改以测试此处提供的十六进制转字节数组方法:
HexToBytesJon: 36979.7 average ticks (over 150 runs)
HexToBytesJon2: 35886.4 average ticks (over 150 runs)
HexToBytesJonCiC: 31230.2 average ticks (over 150 runs)
HexToBytesJase: 15359.1 average ticks (over 150 runs)
HexToBytesJonJon的第一个版本,HexToBytesJon2是第二个变体。 HexToBytesJonCiC是Jon的版本,其中包含CodesInChaos建议的代码。 HexToBytesJase是我基于以上两者尝试的结果,但采用了替代的nybble转换方法,避免了错误检查和分支。
    public static byte[] HexToBytesJase(string hexString)
    {
        if ((hexString.Length & 1) != 0)
        {
            throw new ArgumentException("Input must have even number of characters");
        }
        byte[] ret = new byte[hexString.Length/2];
        for (int i = 0; i < ret.Length; i++)
        {
            int high = hexString[i*2];
            int low = hexString[i*2+1];
            high = (high & 0xf) + ((high & 0x40) >> 6) * 9;
            low = (low & 0xf) + ((low & 0x40) >> 6) * 9;

            ret[i] = (byte)((high << 4) | low);
        }

        return ret;
    }

奇怪的是,在我的测试中,我的代码比Jon的快,而Jon的两个解决方案基本上具有相同的速度。你是没有附加调试器运行吗? - CodesInChaos
是的,我已经拿到了你更新后的代码,看起来大致相同,但我会更新数字。 - JasonD
1
据我所知,这个程序无法检测无效数据。这可能是操作者需要的,也可能不是,但我认为这与性能相关。(此代码中分支要少得多。) - Jon Skeet
@JonSkeet 正确 - 我在帖子中指出了这一点,以防错误检查实际上很重要(而且可能更好的是对较大的块进行校验和哈希,而不是在每个字节上出错)。顺便说一句,我已经去掉了其他人的错误/异常处理,并且我记得它们在中间某个地方。 - JasonD
@JasonD hi和lo的计算消除了对nybble查找的需求。请问你有相反的计算方法吗?这样可以简化并可能加快byte2hex的解决方案。 - Charles Okwuagwu
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接