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


当前回答

Dotnet 5更新

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

System.Convert.ToHexString

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

要将十六进制字符串转换为字节[],请使用:

System.Convert.FromHexString

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

其他回答

支持最短路径和.net核心:

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

我将参加这个比特拨弄比赛,因为我有一个同样使用比特拨弄来解码十六进制的答案。请注意,使用字符数组可能会更快,因为调用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代码转换而来。

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

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

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

然而,我确实从Waleeds代码中得到了一些提示,并将其结合在一起,取得了一些成功。这很难看,但根据我的测试(使用patricges测试机制),与其他测试相比,它似乎有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版的回答。

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

tl;dr:如果你很着急,只需使用Convert.ToByte和String.Substring(下面的“原始代码”),如果你不想重新实现Convert.ToBByte,这是最好的组合。如果你需要性能,请使用不使用Convert.ToByte的更高级的(请参阅其他答案)。不要将String.Substring与Convert.ToByte组合使用,除非有人在这个答案的注释中对此有一些有趣的说法。

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

一般来说,我不太喜欢说“不要过早优化”,因为没有人知道“过早”是什么时候。在决定是否优化时,你必须考虑的唯一一件事是:“我有时间和资源来适当地研究优化方法吗?”。如果你不这样做,那么现在就太早了,等到你的项目更加成熟或者你需要表现(如果有真正的需要,那么你会腾出时间)。同时,做最简单的事情,可能会奏效。

原始代码:

    public static byte[] HexadecimalStringToByteArray_Original(string input)
    {
        var outputLength = input.Length / 2;
        var output = new byte[outputLength];
        for (var i = 0; i < outputLength; i++)
            output[i] = Convert.ToByte(input.Substring(i * 2, 2), 16);
        return output;
    }

第4版:

    public static byte[] HexadecimalStringToByteArray_Rev4(string input)
    {
        var outputLength = input.Length / 2;
        var output = new byte[outputLength];
        using (var sr = new StringReader(input))
        {
            for (var i = 0; i < outputLength; i++)
                output[i] = Convert.ToByte(new string(new char[2] { (char)sr.Read(), (char)sr.Read() }), 16);
        }
        return output;
    }

修订版避免了String.Substring,而是使用StringReader。给出的原因是:

编辑:您可以通过使用单个传递解析器,如下所示:

好吧,看看String.Substring的参考代码,它显然已经是“单次传递”了;为什么不应该呢?它在字节级运行,而不是在代理对上运行。

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

    public static byte[] HexadecimalStringToByteArray(string input)
    {
        var outputLength = input.Length / 2;
        var output = new byte[outputLength];
        var numeral = new char[2];
        using (var sr = new StringReader(input))
        {
            for (var i = 0; i < outputLength; i++)
            {
                numeral[0] = (char)sr.Read();
                numeral[1] = (char)sr.Read();
                output[i] = Convert.ToByte(new string(numeral), 16);
            }
        }
        return output;
    }

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

但是,为什么要调用StringReader。读两遍?只需调用它的第二个重载,并要求它一次读取两个字符数组中的两个字符;并将呼叫量减少两次。

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

剩下的是一个字符串读取器,其唯一添加的“值”是一个并行索引(internal_pos),您可以声明自己(例如j)、一个冗余长度变量(internal_length)和一个输入字符串的冗余引用(internal_s)。换句话说,这是无用的。

如果您想知道Read是如何“读取”的,只需看看代码,它所做的就是对输入字符串调用String.CopyTo。剩下的只是记账开销,以维持我们不需要的价值。

因此,已经删除字符串读取器,并自己调用CopyTo;它更简单、更清晰、更高效。

    public static byte[] HexadecimalStringToByteArray(string input)
    {
        var outputLength = input.Length / 2;
        var output = new byte[outputLength];
        var numeral = new char[2];
        for (int i = 0, j = 0; i < outputLength; i++, j += 2)
        {
            input.CopyTo(j, numeral, 0, 2);
            output[i] = Convert.ToByte(new string(numeral), 16);
        }
        return output;
    }

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

    public static byte[] HexadecimalStringToByteArray_BestEffort(string input)
    {
        var outputLength = input.Length / 2;
        var output = new byte[outputLength];
        var numeral = new char[2];
        for (int i = 0; i < outputLength; i++)
        {
            input.CopyTo(i * 2, numeral, 0, 2);
            output[i] = Convert.ToByte(new string(numeral), 16);
        }
        return output;
    }

现在的解决方案是什么样子的?与一开始的情况完全一样,只是没有使用String.Substring来分配字符串并将数据复制到其中,而是使用了一个中间数组,将十六进制数字复制到该数组中,然后自己分配字符串并再次将数据从数组复制到字符串中(当您在字符串构造函数中传递它时)。如果字符串已经在实习池中,则第二个副本可能会被优化,但在这些情况下,string.Substring也可以避免。

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

字符串.子字符串

最坏的情况:一次快速分配,一次快速复制。最佳情况:无分配,无复制。

手动方法

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

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

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

如果有Convert.ToByte(char[],Int32),情况当然会有所不同(可以执行上面描述的操作,完全避免使用String)。

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

免责声明:我没有反编译框架的最新版本,以验证参考源是否是最新的,我想是的。

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

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

Yes!

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

209113288F93A9AB8E474EA78D899AFDBB874355

玩得高兴(但要适度优化。)