字节数组转十六进制字符串

381

如何将 byte[] 转换为 string?每次尝试时,我都会得到

System.Byte[]

而不是值。

此外,如何获取十六进制值而不是十进制值?


16
每次我尝试时,你听起来好像有时候它实际上可能会起作用。 - AgentFire
15
合理推测提问者尝试过不同的方法。 - cja
19个回答

703

有一个内置的方法可以做到这一点:

byte[] data = { 1, 2, 4, 8, 16, 32 };

string hex = BitConverter.ToString(data);

结果:01-02-04-08-10-20

如果您想去掉破折号,只需将它们删除即可:

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

结果:010204081020

如果您想要更紧凑的表示形式,您可以使用Base64:

string base64 = Convert.ToBase64String(data);

结果:AQIECBAg


9
没关系,我想我找到了它。Convert.FromBase64String(...) - ala
1
仅适用于Windows 8 / Windows Phone 8.1,有一个CryptographicBuffer.EncodeToHexString用于没有破折号的情况。 - Sevenate
2
@Grungondola:听起来很奇怪,所有其他将字节转换为字符串的方法都会慢得多。你没有使用+=来连接字符串,是吗? - Guffa
4
这很可能是性能不佳的原因。如果在循环中使用+=对于非常短的循环来说效果很好,但是它的扩展性非常差。每多一个迭代,执行时间大致会加倍,因此在大约20次迭代时就会出现性能问题。每增加10个迭代将使循环时间变慢约1000倍。 - Guffa
1
允许BitConverter.ToString在没有破折号的情况下进行格式化。 · Issue #519 · dotnet/corefx - lindexi
显示剩余6条评论

112

.Net5.0 更新

感谢 @antoninkriz 的 基准比较,我们可以看到 Convert.ToHexString 今天是明显的赢家

如果你不使用 Convert.ToHexString,那么你会很愚蠢。在我看来,它在可读性、性能和安全性方面都表现出色。


以下内容摘自2012年4月10日之前:

我想试图比较这里列出的每种方法的速度,只是为了好玩。我基于这个速度测试代码。

结果表明, BitConverter+String.Replace 似乎比大多数其他简单的方式更快。但是,采用像 Nathan Moinvaziri的ByteArrayToHexString 或 Kurt's ToHex 这样的算法可以提高速度。

我还发现有趣的是,在长字符串上,string.Concat 和 string.Join 的速度比StringBuilder实现慢得多,但对于较短的数组来说则相似。这可能是由于StringBuilder在长字符串上会扩展,因此设置初始大小应该能抵消这种差异。

  • 从这里的一个答案中获取每个代码位:
  • BitConvertRep = Guffa的答案,使用BitConverter和String.Replace (我建议大多数情况下使用,[编辑:]您无法使用Convert.ToHexString时)
  • StringBuilder = Quintin Robinson的答案,使用foreach char StringBuilder.Append
  • LinqConcat = Michael Buen的答案,使用Linq构建数组的string.Concat
  • LinqJoin = mloskot的答案,使用Linq构建数组的string.Join
  • LinqAgg = Matthew Whited的答案,使用StringBuilder的IEnumerable.Aggregate
  • ToHex = Kurt的答案,将字符设置为数组,使用字节值获取十六进制
  • ByteArrayToHexString = Nathan Moinvaziri的答案,与上面的ToHex速度几乎相同,并且可能更容易阅读(我建议在速度方面使用,[编辑:]您无法使用Convert.ToHexString时)
  • ToHexFromTable = Nathan Moinvaziri的答案链接,对我来说,这几乎与上述两个速度相同,但需要始终存在256个字符串的数组
使用:LONG_STRING_LENGTH = 1000 * 1024;
  • BitConvertRep计算时间为27,202毫秒(最快的内置/简单方法)
  • StringBuilder计算时间为75,723毫秒(StringBuilder不重新分配内存)
  • LinqConcat计算时间为182,094毫秒
  • LinqJoin计算时间为181,142毫秒
  • LinqAgg计算时间为93,087毫秒(带重新分配内存的StringBuilder)
  • ToHex计算时间为19,167毫秒(最快的方法)
使用:LONG_STRING_LENGTH = 100 * 1024;,结果类似
  • BitConvertReplace计算时间为3431毫秒
  • StringBuilder计算时间为8289毫秒
  • LinqConcat计算时间为21512毫秒
  • LinqJoin计算时间为19433毫秒
  • LinqAgg计算时间为9230毫秒
  • ToHex计算时间为1976毫秒
使用:int MANY_STRING_COUNT = 1000; int MANY_STRING_LENGTH = 1024; (与第一个测试中的字节数相同,但在不同的数组中)
  • BitConvertReplace计算时间为25,680毫秒
  • StringBuilder计算时间为78,411毫秒
  • LinqConcat计算时间为101,233毫秒
  • LinqJoin计算时间为99,311毫秒
  • LinqAgg计算时间为84,660毫秒
  • ToHex计算时间为18,221毫秒
使用:int MANY_STRING_COUNT = 2000; int MANY_STRING_LENGTH = 20;
  • BitConvertReplace计算时间为1347毫秒
  • StringBuilder计算时间为3234毫秒
  • LinqConcat计算时间为5013毫秒
  • LinqJoin计算时间为4826毫秒
  • LinqAgg计算时间为3589毫秒
  • ToHex计算时间为772毫秒

我使用的测试代码:

void Main()
{
    int LONG_STRING_LENGTH = 100 * 1024;
    int MANY_STRING_COUNT = 1024;
    int MANY_STRING_LENGTH = 100;

    var source = GetRandomBytes(LONG_STRING_LENGTH);

    List<byte[]> manyString = new List<byte[]>(MANY_STRING_COUNT);
    for (int i = 0; i < MANY_STRING_COUNT; ++i)
    {
        manyString.Add(GetRandomBytes(MANY_STRING_LENGTH));
    }

    var algorithms = new Dictionary<string,Func<byte[], string>>();
    algorithms["BitConvertReplace"] = BitConv;
    algorithms["StringBuilder"] = StringBuilderTest;
    algorithms["LinqConcat"] = LinqConcat;
    algorithms["LinqJoin"] = LinqJoin;
    algorithms["LinqAgg"] = LinqAgg;
    algorithms["ToHex"] = ToHex;
    algorithms["ByteArrayToHexString"] = ByteArrayToHexString;

    Console.WriteLine(" === Long string test");
    foreach (var pair in algorithms) {
        TimeAction(pair.Key + " calculation", 500, () =>
        {
            pair.Value(source);
        });
    }

    Console.WriteLine(" === Many string test");
    foreach (var pair in algorithms) {
        TimeAction(pair.Key + " calculation", 500, () =>
        {
            foreach (var str in manyString)
            {
                pair.Value(str);
            }
        });
    }
}

// Define other methods and classes here
static void TimeAction(string description, int iterations, Action func) {
    var watch = new Stopwatch();
    watch.Start();
    for (int i = 0; i < iterations; i++) {
        func();
    }
    watch.Stop();
    Console.Write(description);
    Console.WriteLine(" Time Elapsed {0} ms", watch.ElapsedMilliseconds);
}

//static byte[] GetRandomBytes(int count) {
//  var bytes = new byte[count];
//  (new Random()).NextBytes(bytes);
//  return bytes;
//}
static Random rand = new Random();
static byte[] GetRandomBytes(int count) {
    var bytes = new byte[count];
    rand.NextBytes(bytes);
    return bytes;
}


static string BitConv(byte[] data)
{
    return BitConverter.ToString(data).Replace("-", string.Empty);
}
static string StringBuilderTest(byte[] data)
{
    StringBuilder sb = new StringBuilder(data.Length*2);
    foreach (byte b in data)
        sb.Append(b.ToString("X2"));

    return sb.ToString();
}
static string LinqConcat(byte[] data)
{
    return string.Concat(data.Select(b => b.ToString("X2")).ToArray());
}
static string LinqJoin(byte[] data)
{
    return string.Join("",
        data.Select(
            bin => bin.ToString("X2")
            ).ToArray());
}
static string LinqAgg(byte[] data)
{
    return data.Aggregate(new StringBuilder(),
                               (sb,v)=>sb.Append(v.ToString("X2"))
                              ).ToString();
}
static string ToHex(byte[] bytes)
{
    char[] c = new char[bytes.Length * 2];

    byte b;

    for(int bx = 0, cx = 0; bx < bytes.Length; ++bx, ++cx)
    {
        b = ((byte)(bytes[bx] >> 4));
        c[cx] = (char)(b > 9 ? b - 10 + 'A' : b + '0');

        b = ((byte)(bytes[bx] & 0x0F));
        c[++cx] = (char)(b > 9 ? b - 10 + 'A' : b + '0');
    }

    return new string(c);
}
public static string ByteArrayToHexString(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();
}

还有另一个类似过程的答案, 我还没有比较我们的结果。


回答不错,但你的一行代码让我感到不舒服。为什么要使用十六进制值而不是字符?为什么不这样做(char)(b > 9 ? b - 10 + 'A' : b + '0'); <-- 这也更容易通过将'A'变成'a'来改变大写字母为小写字母 - user34537
1
说实话,我只是从Kurt的回答中复制了那一部分,当时甚至没有费心去弄清楚它是如何工作的... - Thymine
1
更好的性能:"0123456789ABCDEF"[b] - Jonathan Gilbert
@JonathanGilbert 看起来像是 ByteArrayToHexString 方法的不完整版本,这个方法是我的总结推荐用于提高速度的。 - Thymine
哦,你说得对,出于某种原因我没有看到那个变量。不确定我是怎么错过它的。 :-) - Jonathan Gilbert
1
只是提供信息,我已经在这里发布了更新的基准测试,包括Convert.ToHexString和其他一些方法:https://dev59.com/Y3VC5IYBdhLWcg3wYQEp#71904920 - antoninkriz

81

十六进制,Linq-fu:

string.Concat(ba.Select(b => b.ToString("X2")).ToArray())

跟上时代的更新

正如@RubenBartelink所指出的那样,在4.0之前,没有将IEnumerable<string>转换为数组的代码:ba.Select(b => b.ToString("X2"))是无法工作的,但现在同样的代码可以在4.0上运行。

这段代码...

byte[] ba = { 1, 2, 4, 8, 16, 32 };

string s = string.Concat(ba.Select(b => b.ToString("X2")));
string t = string.Concat(ba.Select(b => b.ToString("X2")).ToArray());

Console.WriteLine (s);
Console.WriteLine (t);

在 .NET 4.0 之前,输出结果为:

System.Linq.Enumerable+<CreateSelectIterator>c__Iterator10`2[System.Byte,System.String]
010204081020

从.NET 4.0开始,string.Concat有一个重载可以接受IEnumerable。因此,在4.0上,以上代码对于变量s和t将具有相同的输出。

010204081020
010204081020

在4.0版本之前,ba.Select(b => b.ToString("X2"))会使用重载 (object arg0)。为了让 IEnumerable<string> 调用正确的重载函数,即 (params string[] values),我们需要将 IEnumerable<string> 转换为字符串数组。在4.0版本之前,string.Concat有10个重载函数,但是在4.0版本中增加到了12个。


Michael,你需要在Select上加上.ToArray(),否则(如所示)你会得到一个{System.Linq.Enumerable.WhereSelectArrayIterator<byte,string>},而String.Join将其转换为String[]。 - Aussie Craig
5
您可以使用更简洁的解决方案来使用 Concat。String.Concat(ba.Select(b => b.ToString("X2"))) (该代码行将字节数组 ba 中的每个元素转换为其两位十六进制表示形式,并使用 Concat 方法将它们连接成一个字符串。) - Tomáš Linhart
1
@AussieCraig 在.NET 4.0之前,您只需要ToArray。 -Michael 我认为 string.Concat(from b in ba select b.ToString("X2")) 更漂亮 - 您能否将其作为第二行进行编辑,并提供它依赖于.NET 4.0的String.Concat(IEnumerable<string>)重载的条件? - Ruben Bartelink

42
这里还有另一种方法:
public static string ByteArrayToHexString(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();
}

public static byte[] HexStringToByteArray(string Hex)
{
    byte[] Bytes = new byte[Hex.Length / 2];
    int[] HexValue = new int[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 
       0x06, 0x07, 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
       0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F };

    for (int x = 0, i = 0; i < Hex.Length; i += 2, x += 1)
    {
        Bytes[x] = (byte)(HexValue[Char.ToUpper(Hex[i + 0]) - '0'] << 4 |
                          HexValue[Char.ToUpper(Hex[i + 1]) - '0']);
    }

    return Bytes;
}

另外,您还可以像这样预先构建翻译表格,以实现更快的结果:

http://blogs.msdn.com/b/blambert/archive/2009/02/22/blambert-codesnip-fast-byte-array-to-hex-string-conversion.aspx


在我的测试中,使用StringBuilder Result = new StringBuilder(Bytes.Length * 2);会使这个过程稍微快一点。 - Thymine
有人能解释一下HexValue数组中0x09和0x0A之间的额外0x00是什么吗?先谢谢了! - cyber-monk
1
额外的0x00是基于从'9'到'A'的距离。如果你在Windows上运行charmap(或者你所用平台的等效工具),你会看到它们之间有7个字符。因此,需要7个0x00。 - Joel
如果我想以字节数组形式返回十六进制值,该怎么办? - Kush

24

我喜欢使用扩展方法进行转换,即使它们只是包装标准库方法。在十六进制转换的情况下,我使用以下手动调整(即快速)算法:

public static string ToHex(this byte[] bytes)
{
    char[] c = new char[bytes.Length * 2];

    byte b;

    for(int bx = 0, cx = 0; bx < bytes.Length; ++bx, ++cx) 
    {
        b = ((byte)(bytes[bx] >> 4));
        c[cx] = (char)(b > 9 ? b + 0x37 + 0x20 : b + 0x30);

        b = ((byte)(bytes[bx] & 0x0F));
        c[++cx]=(char)(b > 9 ? b + 0x37 + 0x20 : b + 0x30);
    }

    return new string(c);
}

public static byte[] HexToBytes(this string str)
{
    if (str.Length == 0 || str.Length % 2 != 0)
        return new byte[0];

    byte[] buffer = new byte[str.Length / 2];
    char c;
    for (int bx = 0, sx = 0; bx < buffer.Length; ++bx, ++sx)
    {
        // Convert first half of byte
        c = str[sx];
        buffer[bx] = (byte)((c > '9' ? (c > 'Z' ? (c - 'a' + 10) : (c - 'A' + 10)) : (c - '0')) << 4);

        // Convert second half of byte
        c = str[++sx];
        buffer[bx] |= (byte)(c > '9' ? (c > 'Z' ? (c - 'a' + 10) : (c - 'A' + 10)) : (c - '0'));
    }

    return buffer;
}

22

好的,我不经常将字节转换为十六进制,所以我必须说我不知道是否有比这更好的方法,但是下面是一种方法。

StringBuilder sb = new StringBuilder();
foreach (byte b in myByteArray)
    sb.Append(b.ToString("X2"));

string hexString = sb.ToString();

看起来差不多正确。这似乎是应该包含在框架中的东西,我发誓人们总是在寻找一种内置的方式来做到这一点。不确定为什么还没有已经存在的东西。哦,好吧。 - TJB
1
有一种内置的方法可以做到这一点,在BitConverter类中。 - Guffa
9
请将StringBuilder的容量指定为myByteArray.Length*2,这样在循环过程中就不需要重新分配内存空间。 - Guffa

12

非常快的扩展方法(包括反转):

public static class ExtensionMethods {
    public static string ToHex(this byte[] data) {
        return ToHex(data, "");
    }
    public static string ToHex(this byte[] data, string prefix) {
        char[] lookup = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
        int i = 0, p = prefix.Length, l = data.Length;
        char[] c = new char[l * 2 + p];
        byte d;
        for(; i < p; ++i) c[i] = prefix[i];
        i = -1;
        --l;
        --p;
        while(i < l) {
            d = data[++i];
            c[++p] = lookup[d >> 4];
            c[++p] = lookup[d & 0xF];
        }
        return new string(c, 0, c.Length);
    }
    public static byte[] FromHex(this string str) {
        return FromHex(str, 0, 0, 0);
    }
    public static byte[] FromHex(this string str, int offset, int step) {
        return FromHex(str, offset, step, 0);
    }
    public static byte[] FromHex(this string str, int offset, int step, int tail) {
        byte[] b = new byte[(str.Length - offset - tail + step) / (2 + step)];
        byte c1, c2;
        int l = str.Length - tail;
        int s = step + 1;
        for(int y = 0, x = offset; x < l; ++y, x += s) {
            c1 = (byte)str[x];
            if(c1 > 0x60) c1 -= 0x57;
            else if(c1 > 0x40) c1 -= 0x37;
            else c1 -= 0x30;
            c2 = (byte)str[++x];
            if(c2 > 0x60) c2 -= 0x57;
            else if(c2 > 0x40) c2 -= 0x37;
            else c2 -= 0x30;
            b[y] = (byte)((c1 << 4) + c2);
        }
        return b;
    }
}

在上面的速度测试中,ToHexPatrick计算在长字符串测试和多字符串测试中都比其他方法快。

=== 长字符串测试
BitConvertReplace计算时间为2415毫秒
StringBuilder计算时间为5668毫秒
LinqConcat计算时间为11826毫秒
LinqJoin计算时间为9323毫秒
LinqAgg计算时间为7444毫秒
ToHexTable计算时间为1028毫秒
ToHexAcidzombie计算时间为1035毫秒
ToHexPatrick计算时间为814毫秒
ToHexKurt计算时间为1604毫秒
ByteArrayToHexString计算时间为1330毫秒

=== 多字符串测试
BitConvertReplace计算时间为2238毫秒
StringBuilder计算时间为5393毫秒
LinqConcat计算时间为9043毫秒
LinqJoin计算时间为9131毫秒
LinqAgg计算时间为7324毫秒
ToHexTable计算时间为968毫秒
ToHexAcidzombie计算时间为969毫秒
ToHexPatrick计算时间为956毫秒
ToHexKurt计算时间为1547毫秒
ByteArrayToHexString计算时间为1277毫秒


2
[d >> 4],[d & 0xf] 比 [d / 0x10],[d % 0x10] 更快。 - palota
注意。使用默认参数值而不是重载也可以改进它。但很遗憾,我现在没有时间重新运行速度测试。 - Patrick
ToHex 方法中的前缀参数仅会导致该字符串被包含在结果之前,对吗?我很好奇为什么要这样做,似乎调用者进行连接同样容易。也许是为了节省内存分配? - Frank Schwieterman
通过使用新的Span<T>,这个速度可以进一步加快吗? - Adam Knights

12

我想我应该提供一个答案。从我的测试来看,这种方法是最快的。

public static class Helper
{
    public static string[] HexTbl = Enumerable.Range(0, 256).Select(v => v.ToString("X2")).ToArray();
    public static string ToHex(this IEnumerable<byte> array)
    {
        StringBuilder s = new StringBuilder();
        foreach (var v in array)
            s.Append(HexTbl[v]);
        return s.ToString();
    }
    public static string ToHex(this byte[] array)
    {
        StringBuilder s = new StringBuilder(array.Length*2);
        foreach (var v in array)
            s.Append(HexTbl[v]);
        return s.ToString();
    }
}

1
如果您需要削减执行时间,那么这就是您要使用的内容,但请考虑它会增加启动时间和分配5-6千字节的数据的代价。 - Guffa
@Guffa:从记忆中可以看出这个算法的显著之处在于它一次性添加了两个字母,而不是一个一个地添加。但无论如何,所有解决方案都很快,但这个算法似乎比其他算法明显更快。我会给出实际的数字,但我不记得它们或者我保存测试的地方了。 - user34537

7

再添加一个答案,有一个 System.Runtime.Remoting.Metadata.W3cXsd2001.SoapHexBinary 类可以将字节转换为十六进制并反之:

string hex = new SoapHexBinary(bytes).ToString();
byte[] bytes = SoapHexBinary.Parse(hex).Value;

不确定它与其他实现相比如何(基准测试),但在我看来,它非常简单--特别是将十六进制转换回字节时。


5

使用:

byte[] data = new byte[] { 0x01, 0x02, 0x03, 0x0D, 0x0E, 0x0F };
string hex = string.Empty;
data.ToList().ForEach(b => hex += b.ToString("x2"));
// use "X2" for uppercase hex letters
Console.WriteLine(hex);

Result: 0102030d0e0f


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