如何将字节数组转换为十六进制字符串,反之亦然?
当前回答
另一种基于查找表的方法。该方法只为每个字节使用一个查找表,而不是为每个半字节使用查找表。
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);
}
我还使用查找表中的ushort、struct{char X1,X2}、struct{byte X1,X2}测试了这个变体。
根据编译目标(x86、X64)的不同,它们要么具有大致相同的性能,要么稍慢于此变体。
为了获得更高的性能,其不安全的兄弟:
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;
}
其他回答
两个mashup,将两个半字节操作合并为一个。
可能非常有效的版本:
public static string ByteArrayToString2(byte[] ba)
{
char[] c = new char[ba.Length * 2];
for( int i = 0; i < ba.Length * 2; ++i)
{
byte b = (byte)((ba[i>>1] >> 4*((i&1)^1)) & 0xF);
c[i] = (char)(55 + b + (((b-10)>>31)&-7));
}
return new string( c );
}
Decadent linq与比特黑客版本:
public static string ByteArrayToString(byte[] ba)
{
return string.Concat( ba.SelectMany( b => new int[] { b >> 4, b & 0xF }).Select( b => (char)(55 + b + (((b-10)>>31)&-7))) );
}
并反转:
public static byte[] HexStringToByteArray( string s )
{
byte[] ab = new byte[s.Length>>1];
for( int i = 0; i < s.Length; i++ )
{
int b = s[i];
b = (b - '0') + ((('9' - b)>>31)&-7);
ab[i>>1] |= (byte)(b << 4*((i&1)^1));
}
return ab;
}
另一种方法是使用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);
}
可以使用从.NET 5开始的Convert.ToHexString。还有一个用于反向操作的方法:Convert.FromHexString。
对于较旧版本的.NET,您可以使用:
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();
}
or:
public static string ByteArrayToString(byte[] ba)
{
return BitConverter.ToString(ba).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。
为了方便以后复制和粘贴,将几个答案合并到一个类中:
/// <summary>
/// Extension methods to quickly convert byte array to string and back.
/// </summary>
public static class HexConverter
{
/// <summary>
/// Map values to hex digits
/// </summary>
private static readonly char[] HexDigits =
{
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
};
/// <summary>
/// Map 56 characters between ['0', 'F'] to their hex equivalents, and set invalid characters
/// such that they will overflow byte to fail conversion.
/// </summary>
private static readonly ushort[] HexValues =
{
0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x000A, 0x000B,
0x000C, 0x000D, 0x000E, 0x000F
};
/// <summary>
/// Empty byte array
/// </summary>
private static readonly byte[] Empty = new byte[0];
/// <summary>
/// Convert a byte array to a hexadecimal string.
/// </summary>
/// <param name="bytes">
/// The input byte array.
/// </param>
/// <returns>
/// A string of hexadecimal digits.
/// </returns>
public static string ToHexString(this byte[] bytes)
{
var c = new char[bytes.Length * 2];
for (int i = 0, j = 0; i < bytes.Length; i++)
{
c[j++] = HexDigits[bytes[i] >> 4];
c[j++] = HexDigits[bytes[i] & 0x0F];
}
return new string(c);
}
/// <summary>
/// Parse a string of hexadecimal digits into a byte array.
/// </summary>
/// <param name="hexadecimalString">
/// The hexadecimal string.
/// </param>
/// <returns>
/// The parsed <see cref="byte[]"/> array.
/// </returns>
/// <exception cref="ArgumentException">
/// The input string either contained invalid characters, or was of an odd length.
/// </exception>
public static byte[] ToByteArray(string hexadecimalString)
{
if (!TryParse(hexadecimalString, out var value))
{
throw new ArgumentException("Invalid hexadecimal string", nameof(hexadecimalString));
}
return value;
}
/// <summary>
/// Parse a hexadecimal string to bytes
/// </summary>
/// <param name="hexadecimalString">
/// The hexadecimal string, which must be an even number of characters.
/// </param>
/// <param name="value">
/// The parsed value if successful.
/// </param>
/// <returns>
/// True if successful.
/// </returns>
public static bool TryParse(string hexadecimalString, out byte[] value)
{
if (hexadecimalString.Length == 0)
{
value = Empty;
return true;
}
if (hexadecimalString.Length % 2 != 0)
{
value = Empty;
return false;
}
try
{
value = new byte[hexadecimalString.Length / 2];
for (int i = 0, j = 0; j < hexadecimalString.Length; i++)
{
value[i] = (byte)((HexValues[hexadecimalString[j++] - '0'] << 4)
| HexValues[hexadecimalString[j++] - '0']);
}
return true;
}
catch (OverflowException)
{
value = Empty;
return false;
}
}
}
为了提高性能,我会选择drphrozens解决方案。解码器的一个微小的优化可能是为任一字符使用一个表,以消除“<<4”。
显然,这两个方法调用代价高昂。如果对输入或输出数据进行某种检查(可以是CRC、校验和或其他),则If(b==255)。。。可以跳过,从而也可以完全调用方法。
使用offset++和offset代替offset和offset+1可能会带来一些理论上的好处,但我怀疑编译器比我更好地处理这一点。
private static readonly byte[] LookupTableLow = 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
};
private static readonly byte[] LookupTableHigh = 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, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xA0, 0xB0, 0xC0, 0xD0, 0xE0, 0xF0, 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, 0xA0, 0xB0, 0xC0, 0xD0, 0xE0, 0xF0, 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 LookupLow(char c)
{
var b = LookupTableLow[c];
if (b == 255)
throw new IOException("Expected a hex character, got " + c);
return b;
}
private static byte LookupHigh(char c)
{
var b = LookupTableHigh[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)(LookupHigh(chars[offset++]) | LookupLow(chars[offset]));
}
这只是我的头顶,没有经过测试或基准测试。
推荐文章
- 实体框架核心:在上一个操作完成之前,在此上下文中开始的第二个操作
- 如何为构造函数定制Visual Studio的私有字段生成快捷方式?
- 如何使用JSON确保字符串是有效的JSON。网
- 使用C返回一个数组
- AppSettings从.config文件中获取值
- 通过HttpClient向REST API发布一个空体
- 如何检查IEnumerable是否为空或空?
- 自动化invokerrequired代码模式
- 在c#代码中设置WPF文本框的背景颜色
- 在c#中,什么是单子?
- c#和Java中的泛型有什么不同?和模板在c++ ?
- 向对象数组添加属性
- c#线程安全快速(est)计数器
- 如何将此foreach代码转换为Parallel.ForEach?
- 如何分裂()一个分隔字符串到一个列表<字符串>