CSharp - 你如何转换Byte Array到Hexadecimal String , 或者相反?

  显示原文与译文双语对照的内容

这可能是互联网上一个常见的问题,但我找不到一个能解释如何将字节数组转换成十六进制字符串的答案,反之亦然。

时间:

或者:


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

或者:


public static string ByteArrayToString(byte[] ba)
{
 string hex = BitConverter.ToString(ba);
 return hex.Replace("-","");
}

这样做的变量更多,例如这里的

反向转换将如下所示:


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

— 性能分析

注意:新领导者在2014-07-31之时。

我通过一些粗略的Stopwatch 性能测试来运行各种转换方法,一个随机语句( n=61,1000迭代) 和一个项目Gutenburg文本( n=1,238,957,150迭代) 运行。 下面是结果,大致从最快到最慢。 所有度量都以刻度( 10,000刻度= 1 ms ) 为单位,所有相对注释都与 [slowest] StringBuilder 实现相比较。 对于使用的代码,请参阅下面的或者测试框架存储库,其中我现在维护运行这里代码的代码。

免责声明

警告:不要依赖这些统计数据来做任何具体的事情;它们只是示例数据的样例运行。 如果你真的需要top-notch性能,请使用你所使用的数据代表在环境代表的环境中测试这些方法。

结果

查找表在字节操作中占主导地位。 基本上,有某种形式的precomputing,任何给定的字节或者字节都是十六进制的。 然后,当你在数据中翻录时,你只需查找下一部分,看看它是什么十六进制字符串。 然后,这个值会以某种方式添加到结果字符串输出。 对于长时间的字节操作,一些开发人员可能难以读取,是top-performing方法。

你最好的选择仍然是寻找一些有代表性的数据,并在production-like环境中尝试。 如果你有不同的内存限制,所以你需要一个方法用更少的分配,使一个,它能更迅速,但占用更多的内存

测试代码

请随意使用我使用的测试代码。 这里包含了一个版本,但你可以自由地克隆存储库,并添加你自己的方法。 请提交一个请求请求,如果你发现有兴趣或者想要帮助改进测试框架。

  1. 将新的静态方法( Func<byte[], string> ) 添加到/Tests/ConvertByteArrayToHexString/Test.cs.
  2. 将该方法的名称添加到同一个类中的TestCandidates 返回值。
  3. 确保你运行所需的输入版本,在下 GenerateTestInput 中的注释,即句子或者文本,通过切换类。
  4. 按 F5 键并等待输出( 在/bin 文件夹中也生成了一个HTML转储) 。

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 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 )

为分析添加了Waleed的答案。 相当快。

更新( 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 )

添加 SoapHexBinary mykroft回答的分析,这就接手了第三的位置。

更新( 2013-01-15 )

添加了CodesInChaos操作应答的字节,它占用了第一个位置( 大的文本块上的大边距) 。

更新( 2013-05-23 )

添加了moinvaziri查询的答案和lambert博客的变体。 两者都相当快,但没有在我使用的测试机器上取得领先。

更新( 2014-07-31 )

添加了 @CodesInChaos's 新的byte-based查找答案。 它似乎在句子测试和full-text测试中占据了主导地位。

有一个名为 SoapHexBinary的类,它可以精确地执行你想要的操作。


using System.Runtime.Remoting.Metadata.W3cXsd2001;

public byte[] GetStringToBytes(string value)
{
 SoapHexBinary shb = SoapHexBinary.Parse(value);
 return shb.Value;
}

public string GetBytesToString(byte[] value)
{
 SoapHexBinary shb = new SoapHexBinary(value);
 return shb.ToString();
}

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

它也非常快。


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


放弃所有希望在这里的人

关于奇怪的技巧的解释:

  1. bytes[i]>> 4 提取字节的高位字节
    bytes[i] & 0xF 提取字节的低位字节
  2. b - 10
    <0 为值 b <10,它将成为十进制数字
    >= 0的值 b> 10,它将成为 AF的字母。
  3. 上使用 i>> 31 有符号 32位整数中提取那些符号,多亏了符号扩展。 它将是 -1i <00 代表 i> = 0
  4. 结合 2 ) 和 3 ),表明 (b-10)>>31 将被用于为数字。字母和 -10
  5. 查看字母的大小写,最后一个分割线变成 0b 在 10到 15之间。 我们想将它映射到 A ( 65 ) 到 F ( 70 ),这意味着添加 55 ( 'A'-10 ) 。
  6. 查看数字的大小写,我们想调整最后一个分母,所以它将 b 从范围 0映射到范围 0 ( 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 更灵活,但不希望那些 clonky 90 s-style显式循环,那么你可以:


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

或者,如果你使用. NET 4.0:


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

( 后者来自原始帖子的评论)

我今天遇到了同样的问题,我遇到了以下代码:


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

源:http://social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/3928b8cb-3703-4672-8ccd-33718148d1e3/ ( 查看PZahra文章) 修改了代码,删除了 0 x 前缀

我做了一些性能测试到的代码和它几乎是 8倍使用 patridge BitConverter.ToString() ( 最快的根据帖子的)

你可以使用 BitConverter.ToString 方法:


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

输出:

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

更多信息:http://msdn.microsoft.com/en-us/library/3a733s97.aspx

这个问题也可以通过使用look-up表解决,这需要在编码器和解码器中使用少量静态内存,但是这个方法会很快:

  • 编码器表 512 B 或者 1024 ( 如果需要上下两种情况,则大小为两倍)
  • 解码器表 256或者 64 KiB ( 单个字符look-up或者双字符 look-up )

我的解决方案使用 1024进行编码表,用于解码。

解码


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

编码


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

比较


StringBuilderToStringFromBytes: 106148
BitConverterToStringFromBytes: 15783
ArrayConvertAllToStringFromBytes: 54290
ByteManipulationToCharArray: 8444
TableBasedToCharArray: 5651 *

* 这里解决方案

便笺

在解码IOException和IndexOutOfRangeException时可能发生( 如果字符的值太高> 256 ) 。 应该实现 de/编码流或者数组的方法,这只是概念的一个证明。

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


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 兄弟:


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

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


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

这是一篇伟大的文章。 我喜欢Waleed的解决方案。我还没有通过patridge测试,但它似乎相当快。 我还需要反向处理,将十六进制字符串转换为字节数组,所以我将它写成Waleed解决方案的反转。 不确定它是否比Tomalak解决方案的速度快。 同样,我没有通过patridge的测试来运行反向处理。


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

...