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


当前回答

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

static string ByteToHexBitFiddle(byte[] bytes)
{
        var c = stackalloc char[bytes.Length * 2 + 1];
        int b; 
        for (int i = 0; i < bytes.Length; ++i)
        {
            b = bytes[i] >> 4;
            c[i * 2] = (char)(55 + b + (((b - 10) >> 31) & -7));
            b = bytes[i] & 0xF;
            c[i * 2 + 1] = (char)(55 + b + (((b - 10) >> 31) & -7));
        }
        c[bytes.Length * 2 ] = '\0';
        return new string(c);
}

其他回答

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

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

它也很快。

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'naph Cthulhu R'lieh wgah'nagl fhtagn公司


你们这些进入这里的人,放弃一切希望吧

一个奇怪的拨弄解释:

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

进一步考虑:

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

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

下面还通过允许本机小写选项扩展了这里的优秀答案,并且还处理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);
    }

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