Làm thế nào bạn có thể chuyển đổi một mảng byte thành một chuỗi thập lục phân và ngược lại?
Làm thế nào bạn có thể chuyển đổi một mảng byte thành một chuỗi thập lục phân và ngược lại?
Câu trả lời:
Hoặc:
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();
}
hoặc là:
public static string ByteArrayToString(byte[] ba)
{
return BitConverter.ToString(ba).Replace("-","");
}
Thậm chí còn có nhiều biến thể hơn để làm điều đó, ví dụ ở đây .
Việc chuyển đổi ngược lại sẽ diễn ra như sau:
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;
}
Sử dụng Substring
là lựa chọn tốt nhất kết hợp với Convert.ToByte
. Xem câu trả lời này để biết thêm thông tin. Nếu bạn cần hiệu suất tốt hơn, bạn phải tránh Convert.ToByte
trước khi bạn có thể thả SubString
.
Lưu ý: lãnh đạo mới kể từ 2015-08-20.
Tôi đã chạy từng phương thức chuyển đổi khác nhau thông qua một số Stopwatch
thử nghiệm hiệu năng thô , chạy với một câu ngẫu nhiên (n = 61, 1000 lần lặp) và chạy với văn bản Project Gutenburg (n = 1,238.957, 150 lần lặp). Dưới đây là kết quả, đại khái từ nhanh nhất đến chậm nhất. Tất cả các phép đo được tính bằng tích tắc ( 10.000 tick = 1 ms ) và tất cả các ghi chú tương đối được so sánh với việc StringBuilder
thực hiện [chậm nhất] . Đối với mã được sử dụng, xem bên dưới hoặc repo khung kiểm tra nơi tôi hiện đang duy trì mã để chạy mã này.
CẢNH BÁO: Không dựa vào các số liệu thống kê này cho bất cứ điều gì cụ thể; chúng chỉ đơn giản là một mẫu dữ liệu mẫu. Nếu bạn thực sự cần hiệu suất cao nhất, vui lòng kiểm tra các phương pháp này trong một đại diện môi trường cho nhu cầu sản xuất của bạn với đại diện dữ liệu về những gì bạn sẽ sử dụng.
unsafe
(thông qua CodeInChaos) (được thêm vào để kiểm tra repo bằng airbreather )
BitConverter
(thông qua Tomalak)
{SoapHexBinary}.ToString
(thông qua Mykroft)
{byte}.ToString("X2")
(sử dụng foreach
) (bắt nguồn từ câu trả lời của Will Dean)
{byte}.ToString("X2")
(sử dụng {IEnumerable}.Aggregate
, yêu cầu System.Linq) (thông qua Mark)
Array.ConvertAll
(sử dụng string.Join
) (thông qua Will Dean)
Array.ConvertAll
(sử dụng string.Concat
, yêu cầu .NET 4.0) (thông qua Will Dean)
{StringBuilder}.AppendFormat
(sử dụng foreach
) (thông qua Tomalak)
{StringBuilder}.AppendFormat
(sử dụng {IEnumerable}.Aggregate
, yêu cầu System.Linq) (xuất phát từ câu trả lời của Tomalak)
Các bảng tra cứu đã dẫn đầu về thao tác byte. Về cơ bản, có một số hình thức tính toán trước những gì mà nibble hoặc byte đã cho sẽ ở dạng hex. Sau đó, khi bạn trích xuất dữ liệu, bạn chỉ cần tra phần tiếp theo để xem chuỗi hex đó sẽ là gì. Giá trị đó sau đó được thêm vào đầu ra chuỗi kết quả trong một số thời trang. Đối với một thao tác byte thời gian dài, một số nhà phát triển có khả năng khó đọc hơn, là cách tiếp cận hiệu quả hàng đầu.
Đặt cược tốt nhất của bạn vẫn là tìm kiếm một số dữ liệu đại diện và thử nó trong một môi trường giống như sản xuất. Nếu bạn có các ràng buộc bộ nhớ khác nhau, bạn có thể thích một phương thức có ít phân bổ hơn cho một phương pháp sẽ nhanh hơn nhưng tiêu thụ nhiều bộ nhớ hơn.
Hãy chơi với mã thử nghiệm tôi đã sử dụng. Một phiên bản được bao gồm ở đây nhưng hãy thoải mái sao chép repo và thêm các phương thức của riêng bạn. Vui lòng gửi yêu cầu kéo nếu bạn thấy bất cứ điều gì thú vị hoặc muốn giúp cải thiện khung thử nghiệm mà nó sử dụng.
Func<byte[], string>
) vào /Tests/ConvertByteArrayToHexString/Test.cs.TestCandidates
giá trị trả về trong cùng một lớp.GenerateTestInput
cùng một lớp.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 readonly uint* _lookup32UnsafeP = (uint*)GCHandle.Alloc(_Lookup32, GCHandleType.Pinned).AddrOfPinnedObject();
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;
}
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();
}
Đã thêm câu trả lời của Waleed để phân tích. Khá nhanh.
Đã thêm string.Concat
Array.ConvertAll
biến thể cho tính đầy đủ (yêu cầu .NET 4.0). Ngang bằng với string.Join
phiên bản.
Repo thử nghiệm bao gồm nhiều biến thể hơn như StringBuilder.Append(b.ToString("X2"))
. Không làm đảo lộn kết quả nào. foreach
nhanh hơn {IEnumerable}.Aggregate
, ví dụ, nhưng BitConverter
vẫn thắng.
Đã thêm SoapHexBinary
câu trả lời của Mykroft để phân tích, chiếm vị trí thứ ba.
Đã thêm câu trả lời thao tác byte của CodeInChaos, chiếm vị trí đầu tiên (bằng một lề lớn trên các khối văn bản lớn).
Đã thêm câu trả lời tra cứu của Nathan Moinvaziri và biến thể từ blog của Brian Lambert. Cả hai đều khá nhanh, nhưng không dẫn đầu trên máy thử nghiệm mà tôi đã sử dụng (AMD Phenom 9750).
Đã thêm câu trả lời tra cứu dựa trên byte mới của @ CodeInChaos. Nó dường như đã dẫn đầu trong cả hai bài kiểm tra câu và bài kiểm tra toàn văn.
Đã thêm tối ưu hóa và biến thể của airbreatherunsafe
cho câu trả lời này . Nếu bạn muốn chơi trong trò chơi không an toàn, bạn có thể nhận được một số hiệu suất tăng lớn so với bất kỳ người chiến thắng hàng đầu nào trước đó trên cả chuỗi ngắn và văn bản lớn.
bytes.ToHexStringAtLudicrousSpeed()
:).
Có một lớp gọi là SoapHexBinary thực hiện chính xác những gì bạn muốn.
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();
}
Khi viết mã mật mã, việc tránh các nhánh phụ thuộc dữ liệu và tra cứu bảng để đảm bảo thời gian chạy không phụ thuộc vào dữ liệu, vì thời gian phụ thuộc dữ liệu có thể dẫn đến các cuộc tấn công kênh bên.
Nó cũng khá nhanh.
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
Hãy từ bỏ mọi hy vọng, các ngươi người nhập vào đây
Một lời giải thích về sự kỳ quặc một chút:
bytes[i] >> 4
trích xuất nibble cao của một byte bytes[i] & 0xF
trích xuất nibble thấp của một byteb - 10
< 0
các giá trị b < 10
, mà sẽ trở thành một chữ số thập phân >= 0
cho các giá trị b > 10
, mà sẽ trở thành một lá thư từ A
đến F
.i >> 31
trên một số nguyên 32 bit đã ký trích xuất ký hiệu, nhờ vào phần mở rộng dấu. Nó sẽ là -1
cho i < 0
và 0
cho i >= 0
.(b-10)>>31
sẽ 0
dành cho chữ cái và -1
chữ số.0
và b
nằm trong phạm vi từ 10 đến 15. Chúng tôi muốn ánh xạ nó tới A
(65) đến F
(70), nghĩa là thêm 55 ( 'A'-10
).b
từ phạm vi 0 đến 9 sang phạm vi 0
(48) đến 9
(57). Điều này có nghĩa là nó cần phải trở thành -7 ( '0' - 55
). & -7
kể từ (0 & -7) == 0
và (-1 & -7) == -7
.Một số cân nhắc thêm:
c
, vì phép đo cho thấy tính toán từ đó i
rẻ hơn.i < bytes.Length
như giới hạn trên của vòng lặp cho phép JITter loại bỏ kiểm tra giới hạn bytes[i]
, vì vậy tôi đã chọn biến thể đó.b
một int cho phép chuyển đổi không cần thiết từ và sang byte.hex string
để byte[] array
?
87 + b + (((b-10)>>31)&-39)
byte[] array
", có nghĩa đen là một mảng các mảng byte, hoặc byte[][]
. Tôi chỉ chọc thôi.
Nếu bạn muốn linh hoạt hơn BitConverter
, nhưng không muốn những vòng lặp rõ ràng theo phong cách thập niên 1990, thì bạn có thể làm:
String.Join(String.Empty, Array.ConvertAll(bytes, x => x.ToString("X2")));
Hoặc, nếu bạn đang sử dụng .NET 4.0:
String.Concat(Array.ConvertAll(bytes, x => x.ToString("X2")));
(Cái sau từ một bình luận trên bài viết gốc.)
Một cách tiếp cận dựa trên bảng tra cứu. Bảng này chỉ sử dụng một bảng tra cứu cho mỗi byte, thay vì bảng tra cứu trên mỗi nibble.
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);
}
Tôi cũng đã thử nghiệm phiên bản này sử dụng ushort
, struct{char X1, X2}
, struct{byte X1, X2}
trong bảng tra cứu.
Tùy thuộc vào mục tiêu biên dịch (x86, X64), những mục tiêu này có hiệu suất xấp xỉ bằng nhau hoặc chậm hơn một chút so với biến thể này.
Và để có hiệu suất cao hơn, unsafe
anh chị em của nó :
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);
}
Hoặc nếu bạn cho rằng có thể chấp nhận viết trực tiếp vào chuỗi:
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;
}
Span
bây giờ có thể được sử dụng thay vì unsafe
??
Bạn có thể sử dụng phương thức BitConverter.ToString:
byte[] bytes = {0, 1, 2, 4, 8, 16, 32, 64, 128, 256}
Console.WriteLine( BitConverter.ToString(bytes));
Đầu ra:
00-01 / 02-04-08-10-20-40-80-FF
Thêm thông tin: Phương thức BitConverter.ToString (Byte [])
Tôi vừa gặp phải vấn đề tương tự ngày hôm nay, và tôi đã gặp mã này:
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);
}
Nguồn: Diễn đàn bài viết byte [] Array to Hex String (xem bài đăng của PZahra). Tôi đã sửa đổi mã một chút để loại bỏ tiền tố 0x.
Tôi đã thực hiện một số thử nghiệm hiệu năng cho mã và nó nhanh hơn gần tám lần so với sử dụng BitConverter.ToString () (nhanh nhất theo bài đăng của patridge).
Đây là câu trả lời cho phiên bản 4 của câu trả lời rất phổ biến của Tomalak (và các lần chỉnh sửa tiếp theo).
Tôi sẽ làm cho trường hợp chỉnh sửa này là sai và giải thích lý do tại sao nó có thể được hoàn nguyên. Trên đường đi, bạn có thể tìm hiểu một vài điều về một số nội bộ, và xem một ví dụ khác về tối ưu hóa sớm thực sự là gì và làm thế nào nó có thể cắn bạn.
tl; dr: Chỉ cần sử dụng Convert.ToByte
và String.Substring
nếu bạn đang vội ("Mã gốc" bên dưới), đó là sự kết hợp tốt nhất nếu bạn không muốn thực hiện lại Convert.ToByte
. Sử dụng một cái gì đó nâng cao hơn (xem các câu trả lời khác) không sử dụng Convert.ToByte
nếu bạn cần hiệu suất. Đừng không sử dụng bất cứ điều gì khác ngoài String.Substring
kết hợp vớiConvert.ToByte
, trừ khi ai đó có cái gì thú vị để nói về vấn đề này trong các ý kiến của câu trả lời này.
cảnh báo: Câu trả lời này có thể trở nên lỗi thời nếu mộtConvert.ToByte(char[], Int32)
tình trạng quá tải được thực hiện trong khuôn khổ. Điều này khó có thể xảy ra sớm.
Theo nguyên tắc chung, tôi không muốn nói "không tối ưu hóa sớm", bởi vì không ai biết khi nào "sớm" là khi nào. Điều duy nhất bạn phải xem xét khi quyết định có nên tối ưu hóa hay không là: "Tôi có thời gian và nguồn lực để điều tra các phương pháp tối ưu hóa đúng cách không?". Nếu bạn không, thì quá sớm, hãy đợi cho đến khi dự án của bạn trưởng thành hơn hoặc cho đến khi bạn cần hiệu suất (nếu có nhu cầu thực sự, thì bạn sẽ có thời gian). Trong khi đó, hãy làm điều đơn giản nhất có thể làm việc thay thế.
Mã gốc:
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;
}
Sửa đổi 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;
}
Bản sửa đổi tránh String.Substring
và sử dụng StringReader
thay thế. Lý do được đưa ra là:
Chỉnh sửa: bạn có thể cải thiện hiệu suất cho các chuỗi dài bằng cách sử dụng một trình phân tích cú pháp vượt qua duy nhất, như vậy:
Vâng, nhìn vào mã tham chiếu choString.Substring
, rõ ràng là "một lượt" rồi; và tại sao không nên? Nó hoạt động ở mức byte, không phải trên các cặp thay thế.
Tuy nhiên, nó phân bổ một chuỗi mới, nhưng sau đó bạn cần phân bổ một chuỗi để chuyển sang Convert.ToByte
bất kỳ. Hơn nữa, giải pháp được cung cấp trong bản sửa đổi phân bổ một đối tượng khác trên mỗi lần lặp (mảng hai char); bạn có thể đặt phân bổ đó bên ngoài vòng lặp một cách an toàn và sử dụng lại mảng để tránh điều đó.
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;
}
Mỗi thập lục phân numeral
đại diện cho một octet duy nhất sử dụng hai chữ số (ký hiệu).
Nhưng sau đó, tại sao gọi StringReader.Read
hai lần? Chỉ cần gọi quá tải thứ hai của nó và yêu cầu nó đọc hai ký tự trong mảng hai char cùng một lúc; và giảm số lượng cuộc gọi bằng hai.
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;
}
Những gì bạn còn lại là một trình đọc chuỗi chỉ có "giá trị" được thêm vào là một chỉ mục song song (bên trong _pos
) mà bạn có thể tự khai báo ( j
ví dụ), một biến có độ dài dự phòng (nội bộ_length
) và tham chiếu dự phòng cho đầu vào chuỗi (nội bộ _s
). Nói cách khác, nó vô dụng.
Nếu bạn tự hỏi làm thế nào Read
"đọc", chỉ cần nhìn vào mã , tất cả những gì nó làm là gọi String.CopyTo
vào chuỗi đầu vào. Phần còn lại chỉ là chi phí giữ sách để duy trì các giá trị mà chúng tôi không cần.
Vì vậy, loại bỏ trình đọc chuỗi đã có, và gọi CopyTo
cho mình; nó đơn giản hơn, rõ ràng hơn và hiệu quả hơn.
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;
}
Bạn có thực sự cần một j
chỉ số tăng theo các bước của hai song song i
không? Tất nhiên là không, chỉ cần nhân i
hai (mà trình biên dịch sẽ có thể tối ưu hóa thành một bổ sung).
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;
}
Giải pháp hiện tại trông như thế nào? Chính xác như nó đã được ban đầu, chỉ thay vì sử dụng String.Substring
để phân bổ các chuỗi và sao chép dữ liệu vào nó, bạn đang sử dụng một mảng trung gian mà bạn sao chép các chữ số thập lục phân, sau đó phân bổ chuỗi bản thân và sao chép dữ liệu một lần nữa từ mảng và vào chuỗi (khi bạn chuyển nó trong hàm tạo chuỗi). Bản sao thứ hai có thể được tối ưu hóa nếu chuỗi đã có trong nhóm thực tập, nhưng sau đó String.Substring
cũng sẽ có thể tránh được nó trong những trường hợp này.
Trong thực tế, nếu bạn nhìn String.Substring
lại, bạn sẽ thấy rằng nó sử dụng một số kiến thức nội bộ cấp thấp về cách các chuỗi được xây dựng để phân bổ chuỗi nhanh hơn bạn có thể làm điều đó và nó sẽ điền vào cùng một mã được sử dụng CopyTo
trực tiếp trong đó để tránh cuộc gọi trên cao.
String.Substring
Phương pháp thủ công
Phần kết luận? Nếu bạn muốn sử dụngConvert.ToByte(String, Int32)
(vì bạn không muốn tự mình thực hiện lại chức năng đó), dường như không có cách nào để đánh bại String.Substring
; tất cả những gì bạn làm là chạy theo vòng tròn, phát minh lại bánh xe (chỉ với các vật liệu không tối ưu).
Lưu ý rằng sử dụng Convert.ToByte
và String.Substring
là một lựa chọn hoàn toàn hợp lệ nếu bạn không cần hiệu suất cao. Hãy nhớ rằng: chỉ chọn một giải pháp thay thế nếu bạn có thời gian và nguồn lực để điều tra cách thức hoạt động của nó.
Nếu có một Convert.ToByte(char[], Int32)
, tất nhiên mọi thứ sẽ khác (có thể làm những gì tôi đã mô tả ở trên và hoàn toàn tránh String
).
Tôi nghi ngờ rằng những người báo cáo hiệu suất tốt hơn bằng cách "tránh String.Substring
" cũng tránh Convert.ToByte(String, Int32)
, điều mà bạn thực sự nên làm nếu bạn cần hiệu suất nào. Nhìn vào vô số câu trả lời khác để khám phá tất cả các cách tiếp cận khác nhau để làm điều đó.
Tuyên bố miễn trừ trách nhiệm: Tôi chưa dịch ngược phiên bản mới nhất của khung để xác minh rằng nguồn tham chiếu đã được cập nhật, tôi cho rằng đó là.
Bây giờ, tất cả nghe có vẻ tốt và hợp lý, hy vọng thậm chí còn rõ ràng nếu bạn đã xoay sở được đến nay. Nhưng nó có đúng không?
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
Đúng!
Đạo cụ cho Partridge cho khung ghế, thật dễ dàng để hack. Đầu vào được sử dụng là hàm băm SHA-1 sau được lặp lại 5000 lần để tạo chuỗi dài 100.000 byte.
209113288F93A9AB8E474EA78D899AFDBB874355
Chúc vui vẻ! (Nhưng tối ưu hóa có chừng mực.)
Bổ sung để trả lời bởi @CodesInChaos (phương thức đảo ngược)
public static byte[] HexToByteUsingByteManipulation(string s)
{
byte[] bytes = new byte[s.Length / 2];
for (int i = 0; i < bytes.Length; i++)
{
int hi = s[i*2] - 65;
hi = hi + 10 + ((hi >> 31) & 7);
int lo = s[i*2 + 1] - 65;
lo = lo + 10 + ((lo >> 31) & 7) & 0x0f;
bytes[i] = (byte) (lo | hi << 4);
}
return bytes;
}
Giải trình:
& 0x0f
là để hỗ trợ chữ thường
hi = hi + 10 + ((hi >> 31) & 7);
giống như:
hi = ch-65 + 10 + (((ch-65) >> 31) & 7);
Đối với '0' .. '9', nó giống như hi = ch - 65 + 10 + 7;
vậy hi = ch - 48
(điều này là do 0xffffffff & 7
).
Đối với 'A' .. 'F' là vậy hi = ch - 65 + 10;
(điều này là do 0x00000000 & 7
).
Đối với 'a' .. 'f', chúng ta phải trừ số lớn vì vậy chúng ta phải trừ 32 từ phiên bản mặc định bằng cách tạo một số bit 0
bằng cách sử dụng & 0x0f
.
65 là mã cho 'A'
48 là mã cho '0'
7 là số lượng chữ cái giữa '9'
và 'A'
trong bảng ASCII ( ...456789:;<=>?@ABCD...
).
Vấn đề này cũng có thể được giải quyết bằng cách sử dụng bảng tra cứu. Điều này sẽ cần một lượng nhỏ bộ nhớ tĩnh cho cả bộ mã hóa và bộ giải mã. Phương pháp này tuy nhiên sẽ nhanh:
Giải pháp của tôi sử dụng 1024 byte cho bảng mã hóa và 256 byte để giải mã.
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 *
* giải pháp này
Trong quá trình giải mã IOException và IndexOutOfRangeException có thể xảy ra (nếu một ký tự có giá trị quá cao> 256). Các phương thức khử / luồng mã hóa nên được thực hiện, đây chỉ là một bằng chứng về khái niệm.
Đây là một bài viết tuyệt vời. Tôi thích giải pháp của Waleed. Tôi đã không chạy nó qua thử nghiệm của patridge nhưng dường như nó khá nhanh. Tôi cũng cần quá trình ngược lại, chuyển đổi một chuỗi hex thành một mảng byte, vì vậy tôi đã viết nó như một sự đảo ngược của giải pháp của Waleed. Không chắc nó có nhanh hơn giải pháp ban đầu của Tomalak không. Một lần nữa, tôi cũng không chạy quy trình ngược thông qua thử nghiệm của 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;
}
hexString[i] &= ~0x20;
Tại sao làm cho nó phức tạp? Điều này thật đơn giản trong Visual Studio 2008:
C #:
string hex = BitConverter.ToString(YourByteArray).Replace("-", "");
VB:
Dim hex As String = BitConverter.ToString(YourByteArray).Replace("-", "")
Không phụ thuộc vào nhiều câu trả lời ở đây, nhưng tôi thấy một cách khá tối ưu (tốt hơn 4,5 lần so với chấp nhận), thực hiện đơn giản trình phân tích cú pháp chuỗi hex. Đầu tiên, đầu ra từ các thử nghiệm của tôi (đợt đầu tiên là triển khai của tôi):
Give me that string:
04c63f7842740c77e545bb0b2ade90b384f119f6ab57b680b7aa575a2f40939f
Time to parse 100,000 times: 50.4192 ms
Result as base64: BMY/eEJ0DHflRbsLKt6Qs4TxGfarV7aAt6pXWi9Ak58=
BitConverter'd: 04-C6-3F-78-42-74-0C-77-E5-45-BB-0B-2A-DE-90-B3-84-F1-19-F6-AB-5
7-B6-80-B7-AA-57-5A-2F-40-93-9F
Accepted answer: (StringToByteArray)
Time to parse 100000 times: 233.1264ms
Result as base64: BMY/eEJ0DHflRbsLKt6Qs4TxGfarV7aAt6pXWi9Ak58=
BitConverter'd: 04-C6-3F-78-42-74-0C-77-E5-45-BB-0B-2A-DE-90-B3-84-F1-19-F6-AB-5
7-B6-80-B7-AA-57-5A-2F-40-93-9F
With Mono's implementation:
Time to parse 100000 times: 777.2544ms
Result as base64: BMY/eEJ0DHflRbsLKt6Qs4TxGfarV7aAt6pXWi9Ak58=
BitConverter'd: 04-C6-3F-78-42-74-0C-77-E5-45-BB-0B-2A-DE-90-B3-84-F1-19-F6-AB-5
7-B6-80-B7-AA-57-5A-2F-40-93-9F
With SoapHexBinary:
Time to parse 100000 times: 845.1456ms
Result as base64: BMY/eEJ0DHflRbsLKt6Qs4TxGfarV7aAt6pXWi9Ak58=
BitConverter'd: 04-C6-3F-78-42-74-0C-77-E5-45-BB-0B-2A-DE-90-B3-84-F1-19-F6-AB-5
7-B6-80-B7-AA-57-5A-2F-40-93-9F
Các dòng base64 và 'BitConverter'd' ở đó để kiểm tra tính chính xác. Lưu ý rằng chúng bằng nhau.
Việc thực hiện:
public static byte[] ToByteArrayFromHex(string hexString)
{
if (hexString.Length % 2 != 0) throw new ArgumentException("String must have an even length");
var array = new byte[hexString.Length / 2];
for (int i = 0; i < hexString.Length; i += 2)
{
array[i/2] = ByteFromTwoChars(hexString[i], hexString[i + 1]);
}
return array;
}
private static byte ByteFromTwoChars(char p, char p_2)
{
byte ret;
if (p <= '9' && p >= '0')
{
ret = (byte) ((p - '0') << 4);
}
else if (p <= 'f' && p >= 'a')
{
ret = (byte) ((p - 'a' + 10) << 4);
}
else if (p <= 'F' && p >= 'A')
{
ret = (byte) ((p - 'A' + 10) << 4);
} else throw new ArgumentException("Char is not a hex digit: " + p,"p");
if (p_2 <= '9' && p_2 >= '0')
{
ret |= (byte) ((p_2 - '0'));
}
else if (p_2 <= 'f' && p_2 >= 'a')
{
ret |= (byte) ((p_2 - 'a' + 10));
}
else if (p_2 <= 'F' && p_2 >= 'A')
{
ret |= (byte) ((p_2 - 'A' + 10));
} else throw new ArgumentException("Char is not a hex digit: " + p_2, "p_2");
return ret;
}
Tôi đã thử một số thứ với unsafe
và di chuyển chuỗi ký tự (rõ ràng dư thừa) if
sang phương thức khác, nhưng đây là cách nhanh nhất mà nó có được.
(Tôi thừa nhận rằng câu trả lời này trả lời một nửa câu hỏi. Tôi cảm thấy rằng chuyển đổi chuỗi-> byte [] không được thể hiện trong khi góc chuỗi [] -> dường như được bao phủ tốt. Vì vậy, câu trả lời này.)
Phiên bản an toàn:
public static class HexHelper
{
[System.Diagnostics.Contracts.Pure]
public static string ToHex(this byte[] value)
{
if (value == null)
throw new ArgumentNullException("value");
const string hexAlphabet = @"0123456789ABCDEF";
var chars = new char[checked(value.Length * 2)];
unchecked
{
for (int i = 0; i < value.Length; i++)
{
chars[i * 2] = hexAlphabet[value[i] >> 4];
chars[i * 2 + 1] = hexAlphabet[value[i] & 0xF];
}
}
return new string(chars);
}
[System.Diagnostics.Contracts.Pure]
public static byte[] FromHex(this string value)
{
if (value == null)
throw new ArgumentNullException("value");
if (value.Length % 2 != 0)
throw new ArgumentException("Hexadecimal value length must be even.", "value");
unchecked
{
byte[] result = new byte[value.Length / 2];
for (int i = 0; i < result.Length; i++)
{
// 0(48) - 9(57) -> 0 - 9
// A(65) - F(70) -> 10 - 15
int b = value[i * 2]; // High 4 bits.
int val = ((b - '0') + ((('9' - b) >> 31) & -7)) << 4;
b = value[i * 2 + 1]; // Low 4 bits.
val += (b - '0') + ((('9' - b) >> 31) & -7);
result[i] = checked((byte)val);
}
return result;
}
}
}
Phiên bản không an toàn Dành cho những người thích hiệu suất và không sợ sự không an toàn. ToHex nhanh hơn khoảng 35% và FromHex nhanh hơn 10%.
public static class HexUnsafeHelper
{
[System.Diagnostics.Contracts.Pure]
public static unsafe string ToHex(this byte[] value)
{
if (value == null)
throw new ArgumentNullException("value");
const string alphabet = @"0123456789ABCDEF";
string result = new string(' ', checked(value.Length * 2));
fixed (char* alphabetPtr = alphabet)
fixed (char* resultPtr = result)
{
char* ptr = resultPtr;
unchecked
{
for (int i = 0; i < value.Length; i++)
{
*ptr++ = *(alphabetPtr + (value[i] >> 4));
*ptr++ = *(alphabetPtr + (value[i] & 0xF));
}
}
}
return result;
}
[System.Diagnostics.Contracts.Pure]
public static unsafe byte[] FromHex(this string value)
{
if (value == null)
throw new ArgumentNullException("value");
if (value.Length % 2 != 0)
throw new ArgumentException("Hexadecimal value length must be even.", "value");
unchecked
{
byte[] result = new byte[value.Length / 2];
fixed (char* valuePtr = value)
{
char* valPtr = valuePtr;
for (int i = 0; i < result.Length; i++)
{
// 0(48) - 9(57) -> 0 - 9
// A(65) - F(70) -> 10 - 15
int b = *valPtr++; // High 4 bits.
int val = ((b - '0') + ((('9' - b) >> 31) & -7)) << 4;
b = *valPtr++; // Low 4 bits.
val += (b - '0') + ((('9' - b) >> 31) & -7);
result[i] = checked((byte)val);
}
}
return result;
}
}
}
BTW Để kiểm tra điểm chuẩn khởi tạo bảng chữ cái mỗi lần hàm chuyển đổi được gọi là sai, bảng chữ cái phải là const (đối với chuỗi) hoặc chỉ đọc tĩnh (đối với char []). Sau đó, chuyển đổi dựa trên bảng chữ cái của byte [] thành chuỗi trở nên nhanh như các phiên bản thao tác byte.
Và tất nhiên, kiểm tra phải được biên dịch trong Bản phát hành (có tối ưu hóa) và với tùy chọn gỡ lỗi "Bỏ tối ưu hóa JIT" (tương tự với "Kích hoạt chỉ mã của tôi" nếu mã phải được gỡ lỗi).
Hàm nghịch đảo cho mã Eissa bị xóa (Chuỗi Hex đến Byte Array):
public static byte[] HexToBytes(this string hexString)
{
byte[] b = new byte[hexString.Length / 2];
char c;
for (int i = 0; i < hexString.Length / 2; i++)
{
c = hexString[i * 2];
b[i] = (byte)((c < 0x40 ? c - 0x30 : (c < 0x47 ? c - 0x37 : c - 0x57)) << 4);
c = hexString[i * 2 + 1];
b[i] += (byte)(c < 0x40 ? c - 0x30 : (c < 0x47 ? c - 0x37 : c - 0x57));
}
return b;
}
Chức năng Eissa bị xóa với hỗ trợ chữ thường:
public static string BytesToHex(this byte[] barray, bool toLowerCase = true)
{
byte addByte = 0x37;
if (toLowerCase) addByte = 0x57;
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 + addByte : b + 0x30);
b = ((byte)(barray[i] & 0xF));
c[i * 2 + 1] = (char)(b > 9 ? b + addByte : b + 0x30);
}
return new string(c);
}
Các phương thức mở rộng (từ chối trách nhiệm: mã chưa được kiểm tra hoàn toàn, BTW ...):
public static class ByteExtensions
{
public static string ToHexString(this byte[] ba)
{
StringBuilder hex = new StringBuilder(ba.Length * 2);
foreach (byte b in ba)
{
hex.AppendFormat("{0:x2}", b);
}
return hex.ToString();
}
}
vv .. Sử dụng một trong ba giải pháp của Tomalak (với giải pháp cuối cùng là phương thức mở rộng trên chuỗi).
Từ các nhà phát triển của Microsoft, một chuyển đổi đơn giản, đẹp mắt:
public static string ByteArrayToString(byte[] ba)
{
// Concatenate the bytes into one long string
return ba.Aggregate(new StringBuilder(32),
(sb, b) => sb.Append(b.ToString("X2"))
).ToString();
}
Trong khi ở trên là sạch sẽ và nhỏ gọn, người nghiện hiệu suất sẽ hét lên về nó bằng cách sử dụng liệt kê. Bạn có thể có được hiệu suất cao nhất với phiên bản cải tiến của câu trả lời ban đầu của Tomalak :
public static string ByteArrayToString(byte[] ba)
{
StringBuilder hex = new StringBuilder(ba.Length * 2);
for(int i=0; i < ba.Length; i++) // <-- Use for loop is faster than foreach
hex.Append(ba[i].ToString("X2")); // <-- ToString is faster than AppendFormat
return hex.ToString();
}
Đây là cách nhanh nhất trong tất cả các thói quen tôi thấy được đăng ở đây cho đến nay. Đừng hiểu ý tôi ... hãy kiểm tra hiệu suất từng thói quen và tự kiểm tra mã CIL của nó.
b.ToSting("X2")
.
Và để chèn vào chuỗi SQL (nếu bạn không sử dụng tham số lệnh):
public static String ByteArrayToSQLHexString(byte[] Source)
{
return = "0x" + BitConverter.ToString(Source).Replace("-", "");
}
Source == null
hoặc Source.Length == 0
chúng tôi có một vấn đề thưa ông!
Về tốc độ, điều này dường như tốt hơn bất cứ điều gì ở đây:
public static string ToHexString(byte[] data) {
byte b;
int i, j, k;
int l = data.Length;
char[] r = new char[l * 2];
for (i = 0, j = 0; i < l; ++i) {
b = data[i];
k = b >> 4;
r[j++] = (char)(k > 9 ? k + 0x37 : k + 0x30);
k = b & 15;
r[j++] = (char)(k > 9 ? k + 0x37 : k + 0x30);
}
return new string(r);
}
Tôi không nhận được mã mà bạn đề xuất để làm việc, Olipro. hex[i] + hex[i+1]
hình như trả lại một int
.
Tôi đã làm, tuy nhiên đã có một số thành công bằng cách lấy một số gợi ý từ mã Walebed và kết hợp điều này với nhau. Nó xấu như địa ngục nhưng nó dường như hoạt động và thực hiện ở mức 1/3 thời gian so với các thử nghiệm khác của tôi (sử dụng cơ chế kiểm tra hộp mực). Tùy thuộc vào kích thước đầu vào. Chuyển đổi xung quanh ?: S để tách ra 0-9 trước tiên có thể sẽ mang lại kết quả nhanh hơn một chút vì có nhiều số hơn chữ cái.
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;
}
Phiên bản ByteArrayToHexViaByteManipulation này có thể nhanh hơn.
Từ báo cáo của tôi:
...
static private readonly char[] hexAlphabet = new char[]
{'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
static string ByteArrayToHexViaByteManipulation3(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] = hexAlphabet[b];
b = ((byte)(bytes[i] & 0xF));
c[i * 2 + 1] = hexAlphabet[b];
}
return new string(c);
}
Và tôi nghĩ rằng đây là một tối ưu hóa:
static private readonly char[] hexAlphabet = new char[]
{'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
static string ByteArrayToHexViaByteManipulation4(byte[] bytes)
{
char[] c = new char[bytes.Length * 2];
for (int i = 0, ptr = 0; i < bytes.Length; i++, ptr += 2)
{
byte b = bytes[i];
c[ptr] = hexAlphabet[b >> 4];
c[ptr + 1] = hexAlphabet[b & 0xF];
}
return new string(c);
}
Tôi sẽ tham gia cuộc thi đấu hơi khó khăn này vì tôi có một câu trả lời cũng sử dụng câu đố bit để giải mã các hình lục giác. Lưu ý rằng việc sử dụng mảng ký tự có thể còn nhanh hơn vì StringBuilder
các phương thức gọi cũng sẽ mất thời gian.
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;
}
Chuyển đổi từ mã Java.
Char[]
và sử dụng Char
nội bộ thay vì ints ...
Đối với hiệu suất, tôi sẽ đi với giải pháp drphro chục. Một tối ưu hóa nhỏ cho bộ giải mã có thể là sử dụng bảng cho char để loại bỏ "<< 4".
Rõ ràng hai cuộc gọi phương thức là tốn kém. Nếu một số loại kiểm tra được thực hiện trên dữ liệu đầu vào hoặc đầu ra (có thể là CRC, tổng kiểm tra hoặc bất cứ điều gì) thìif (b == 255)...
có thể bỏ qua và do đó phương thức này sẽ gọi hoàn toàn.
Sử dụng offset++
và offset
thay vì offset
và offset + 1
có thể mang lại một số lợi ích về mặt lý thuyết nhưng tôi nghi ngờ trình biên dịch xử lý việc này tốt hơn tôi.
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]));
}
Đây chỉ là trên đỉnh đầu của tôi và chưa được kiểm tra hoặc điểm chuẩn.
Một biến thể khác cho sự đa dạng:
public static byte[] FromHexString(string src)
{
if (String.IsNullOrEmpty(src))
return null;
int index = src.Length;
int sz = index / 2;
if (sz <= 0)
return null;
byte[] rc = new byte[sz];
while (--sz >= 0)
{
char lo = src[--index];
char hi = src[--index];
rc[sz] = (byte)(
(
(hi >= '0' && hi <= '9') ? hi - '0' :
(hi >= 'a' && hi <= 'f') ? hi - 'a' + 10 :
(hi >= 'A' && hi <= 'F') ? hi - 'A' + 10 :
0
)
<< 4 |
(
(lo >= '0' && lo <= '9') ? lo - '0' :
(lo >= 'a' && lo <= 'f') ? lo - 'a' + 10 :
(lo >= 'A' && lo <= 'F') ? lo - 'A' + 10 :
0
)
);
}
return rc;
}
Không được tối ưu hóa cho tốc độ, nhưng nhiều LINQy hơn hầu hết các câu trả lời (.NET 4.0):
<Extension()>
Public Function FromHexToByteArray(hex As String) As Byte()
hex = If(hex, String.Empty)
If hex.Length Mod 2 = 1 Then hex = "0" & hex
Return Enumerable.Range(0, hex.Length \ 2).Select(Function(i) Convert.ToByte(hex.Substring(i * 2, 2), 16)).ToArray
End Function
<Extension()>
Public Function ToHexString(bytes As IEnumerable(Of Byte)) As String
Return String.Concat(bytes.Select(Function(b) b.ToString("X2")))
End Function
Hai mashup gấp hai thao tác nibble thành một.
Có lẽ phiên bản khá hiệu quả:
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 );
}
Phiên bản hack linq-with-bit-decadent:
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))) );
}
Và ngược lại:
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;
}
Một cách khác là sử dụng stackalloc
để giảm áp lực bộ nhớ 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);
}
Đây là cú đánh của tôi vào nó. Tôi đã tạo một cặp lớp mở rộng để mở rộng chuỗi và byte. Trong bài kiểm tra tệp lớn, hiệu suất tương đương với Byte Manipulation 2.
Mã dưới đây cho ToHexString là một triển khai tối ưu hóa thuật toán tra cứu và dịch chuyển. Nó gần giống với cái của Behrooz, nhưng hóa ra là sử dụng foreach
để lặp lại và bộ đếm nhanh hơn so với lập chỉ mục rõ ràngfor
.
Nó đứng ở vị trí thứ 2 sau Byte Manipulation 2 trên máy của tôi và là mã rất dễ đọc. Các kết quả kiểm tra sau đây cũng được quan tâm:
ToHexStringCharArrayWithCharArrayLookup: 41,589,69 tick trung bình (hơn 1000 lượt chạy), 1,5X ToHexStringCharArrayWithStringLookup: 50,764,06 ticks trung bình (hơn 1000 lượt chạy), 1,2X ToHexString
Dựa trên các kết quả trên, có vẻ an toàn để kết luận rằng:
Đây là mã:
using System;
namespace ConversionExtensions
{
public static class ByteArrayExtensions
{
private readonly static char[] digits = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
public static string ToHexString(this byte[] bytes)
{
char[] hex = new char[bytes.Length * 2];
int index = 0;
foreach (byte b in bytes)
{
hex[index++] = digits[b >> 4];
hex[index++] = digits[b & 0x0F];
}
return new string(hex);
}
}
}
using System;
using System.IO;
namespace ConversionExtensions
{
public static class StringExtensions
{
public static byte[] ToBytes(this string hexString)
{
if (!string.IsNullOrEmpty(hexString) && hexString.Length % 2 != 0)
{
throw new FormatException("Hexadecimal string must not be empty and must contain an even number of digits to be valid.");
}
hexString = hexString.ToUpperInvariant();
byte[] data = new byte[hexString.Length / 2];
for (int index = 0; index < hexString.Length; index += 2)
{
int highDigitValue = hexString[index] <= '9' ? hexString[index] - '0' : hexString[index] - 'A' + 10;
int lowDigitValue = hexString[index + 1] <= '9' ? hexString[index + 1] - '0' : hexString[index + 1] - 'A' + 10;
if (highDigitValue < 0 || lowDigitValue < 0 || highDigitValue > 15 || lowDigitValue > 15)
{
throw new FormatException("An invalid digit was encountered. Valid hexadecimal digits are 0-9 and A-F.");
}
else
{
byte value = (byte)((highDigitValue << 4) | (lowDigitValue & 0x0F));
data[index / 2] = value;
}
}
return data;
}
}
}
Dưới đây là kết quả kiểm tra mà tôi nhận được khi đặt mã vào dự án thử nghiệm của @ patridge trên máy của mình. Tôi cũng đã thêm một bài kiểm tra để chuyển đổi thành một mảng byte từ hệ thập lục phân. Các bài kiểm tra đã thực thi mã của tôi là ByteArrayToHexViaOptimizedLookupAndShift và HexToByteArrayViaByteManipulation. HexToByteArrayViaConvertToByte được lấy từ XXXX. HexToByteArrayViaSoapHexBinary là câu trả lời của @ Mykroft.
Bộ xử lý Intel Pentium III Xeon
Cores: 4 <br/> Current Clock Speed: 1576 <br/> Max Clock Speed: 3092 <br/>
Chuyển đổi mảng byte thành biểu diễn chuỗi thập lục phân
ByteArrayToHexViaByteManipulation2: 39.366,64 tick trung bình (hơn 1000 lần chạy), 22,4X
ByteArrayToHexViaOptimizedLookupAndShift: 41,588,64 tick trung bình (hơn 1000 lượt chạy), 21,2X
ByteArrayToHexViaLookup: 55,509,56 tick trung bình (hơn 1000 lượt chạy), 15,9X
ByteArrayToHexViaByteQuản lý: 65.349,12 tick trung bình (hơn 1000 lượt chạy), 13,5X
ByteArrayToHexViaLookupAndShift: 86.926,87 tick trung bình (hơn 1000 lần chạy), 10,2 lần
ByteArrayToHexStringViaBitConverter: 139.353,73 tick trung bình (hơn 1000 lần chạy), 6.3X
ByteArrayToHexViaSoapHexBinary: 314,598,77 tick trung bình (hơn 1000 lượt chạy), 2,8X
ByteArrayToHexStringViaStringBuilderForEachByteToString: 344.264,63 tick trung bình (hơn 1000 lần chạy), 2,6X
ByteArrayToHexStringViaStringBuilderAggregateByteToString: 382.623,44 tick trung bình (hơn 1000 lần chạy), 2,3 lần
ByteArrayToHexStringViaStringBuilderForEachAppendFormat: 818.111,95 tick trung bình (hơn 1000 lần chạy), 1.1X
ByteArrayToHexStringViaStringConcatArrayConvert ALL: 839.244.84 tick trung bình (hơn 1000 lần chạy), 1.1X
ByteArrayToHexStringViaStringBuilderAggregateAppendFormat: 867.303,98 tick trung bình (hơn 1000 lần chạy), 1,0X
ByteArrayToHexStringViaStringJoinArrayConvert ALL: 882.710.28 tick trung bình (hơn 1000 lượt chạy), 1.0X
Một chức năng nhanh khác ...
private static readonly byte[] HexNibble = new byte[] {
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
0x8, 0x9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF
};
public static byte[] HexStringToByteArray( string str )
{
int byteCount = str.Length >> 1;
byte[] result = new byte[byteCount + (str.Length & 1)];
for( int i = 0; i < byteCount; i++ )
result[i] = (byte) (HexNibble[str[i << 1] - 48] << 4 | HexNibble[str[(i << 1) + 1] - 48]);
if( (str.Length & 1) != 0 )
result[byteCount] = (byte) HexNibble[str[str.Length - 1] - 48];
return result;
}