Làm thế nào để bạn 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?


1372

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?


8
Câu trả lời được chấp nhận dưới đây dường như phân bổ số lượng chuỗi khủng khiếp trong chuỗi chuyển đổi thành byte. Tôi đang tự hỏi làm thế nào điều này ảnh hưởng đến hiệu suất
Wim Coenen

9
Lớp SoapHexBinary thực hiện chính xác những gì bạn muốn tôi nghĩ.
Mykroft

Dường như với tôi rằng việc hỏi 2 câu hỏi trong 1 bài viết không hoàn toàn chuẩn.
SandRock

Câu trả lời:


1353

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 Substringlà 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.ToBytetrước khi bạn có thể thả SubString.


24
Bạn đang sử dụng SubString. Không phải vòng lặp này phân bổ một lượng lớn các đối tượng chuỗi?
Wim Coenen

30
Thành thật mà nói - cho đến khi nó giảm hiệu suất đáng kể, tôi sẽ có xu hướng bỏ qua điều này và tin tưởng vào Runtime và GC để chăm sóc nó.
Tomalak

87
Bởi vì một byte là hai nibble, bất kỳ chuỗi hex nào đại diện hợp lệ cho một mảng byte phải có số ký tự chẵn. Không nên thêm 0 vào bất cứ nơi nào - để thêm một người sẽ đưa ra giả định về dữ liệu không hợp lệ có khả năng gây nguy hiểm. Nếu bất cứ điều gì, phương thức StringToByteArray sẽ ném FormatException nếu chuỗi hex chứa một số ký tự lẻ.
David Boike

7
@ 00jt Bạn phải đưa ra giả định rằng F == 0F. Nó giống như 0F, hoặc đầu vào bị cắt và F thực sự là khởi đầu của thứ bạn chưa nhận được. Tùy thuộc vào bối cảnh của bạn để đưa ra các giả định đó, nhưng tôi tin rằng một hàm mục đích chung sẽ loại bỏ các ký tự lẻ là không hợp lệ thay vì đưa ra giả định đó cho mã gọi.
David Boike

11
@DavidBoike Câu hỏi KHÔNG CÓ gì để làm với "cách xử lý các giá trị luồng có thể bị cắt" Nó nói về một Chuỗi. Chuỗi myValue = 10.ToString ("X"); myValue là "A" chứ không phải "0A". Bây giờ hãy đọc chuỗi đó trở lại thành byte, rất tiếc bạn đã phá vỡ nó.
00jt

488

Phân tích hiệu suất

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ố Stopwatchthử 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 StringBuilderthự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.

Khước từ

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.

Các kết quả

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.

Mã kiểm tra

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.

  1. Thêm phương thức tĩnh mới ( Func<byte[], string>) vào /Tests/ConvertByteArrayToHexString/Test.cs.
  2. Thêm tên của phương thức đó vào TestCandidatesgiá trị trả về trong cùng một lớp.
  3. Hãy chắc chắn rằng bạn đang chạy phiên bản đầu vào mà bạn muốn, câu hoặc văn bản, bằng cách chuyển đổi các nhận xét trong GenerateTestInputcùng một lớp.
  4. Nhấn F5và đợi đầu ra (kết xuất HTML cũng được tạo trong thư mục / bin).
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();
}

Cập nhật (2010-01-13)

Đã thêm câu trả lời của Waleed để phân tích. Khá nhanh.

Cập nhật (2011-10-05)

Đã thêm string.Concat Array.ConvertAllbiến thể cho tính đầy đủ (yêu cầu .NET 4.0). Ngang bằng với string.Joinphiên bản.

Cập nhật (2012 / 02-05)

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. foreachnhanh hơn {IEnumerable}.Aggregate, ví dụ, nhưng BitConvertervẫn thắng.

Cập nhật (2012-04-03)

Đã thêm SoapHexBinarycâu trả lời của Mykroft để phân tích, chiếm vị trí thứ ba.

Cập nhật (2013-01-15)

Đã 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).

Cập nhật (2013-05-23)

Đã 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).

Cập nhật (2014-07-31)

Đã 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.

Cập nhật (2015-08-20)

Đã 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.


Bạn có muốn kiểm tra mã từ câu trả lời của Waleed không? Có vẻ như rất nhanh. stackoverflow.com/questions/311165/ cường
Cristian Diaconescu

5
Mặc dù làm cho mã có sẵn để bạn tự làm điều bạn yêu cầu, tôi đã cập nhật mã kiểm tra để bao gồm câu trả lời Waleed. Tất cả gắt gỏng sang một bên, nó nhanh hơn nhiều.
patridge

2
@CodesInChaos Xong. Và nó đã thắng trong các bài kiểm tra của tôi khá nhiều. Tôi không giả vờ hiểu đầy đủ một trong những phương pháp hàng đầu, nhưng chúng dễ bị ẩn khỏi sự tương tác trực tiếp.
patridge

6
Câu trả lời này không có ý định trả lời câu hỏi "tự nhiên" hay phổ biến là gì. Mục tiêu là cung cấp cho mọi người một số điểm chuẩn hiệu suất cơ bản vì khi bạn cần thực hiện các chuyển đổi này, bạn có xu hướng thực hiện chúng rất nhiều. Nếu ai đó cần tốc độ thô, họ chỉ cần chạy điểm chuẩn với một số dữ liệu thử nghiệm thích hợp trong môi trường máy tính mong muốn của họ. Sau đó, nhét phương thức đó vào một phương thức mở rộng mà bạn không bao giờ nhìn lại cách thực hiện của nó (ví dụ bytes.ToHexStringAtLudicrousSpeed():).
patridge

2
Chỉ cần sản xuất một bảng tra cứu hiệu suất cao dựa trên thực hiện. Biến thể an toàn của nó nhanh hơn khoảng 30% so với người dẫn đầu hiện tại trên CPU của tôi. Các biến thể không an toàn thậm chí còn nhanh hơn. stackoverflow.com/a/24343727/445517
CodeInChaos

244

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

35
SoapHexBinary có sẵn từ .NET 1.0 và có trong mscorlib. Mặc dù không gian tên buồn cười, nó thực hiện chính xác những gì câu hỏi.
Sly Gryphon

4
Tuyệt vời tìm thấy! Lưu ý rằng bạn sẽ cần phải đệm các chuỗi lẻ với số 0 đứng đầu cho GetStringToBytes, giống như giải pháp khác.
Carter Medlin

Bạn đã thấy những suy nghĩ thực hiện? Câu trả lời được chấp nhận có một IMHO tốt hơn.
mfloryan

6
Thật thú vị khi xem triển khai Mono tại đây: github.com/mono/mono/blob/master/mcs/ class / corlib / trộm
Jeremy

1
SoapHexBinary không được hỗ trợ trong .NET Core / .NET Standard ...
juFo

141

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:

  1. bytes[i] >> 4trích xuất nibble cao của một byte
    bytes[i] & 0xFtrích xuất nibble thấp của một byte
  2. b - 10
    < 0các giá trị b < 10, mà sẽ trở thành một chữ số thập phân
    >= 0cho các giá trị b > 10, mà sẽ trở thành một lá thư từ Ađến F.
  3. Sử dụng i >> 31trê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à -1cho i < 00cho i >= 0.
  4. Kết hợp 2) và 3), cho thấy (b-10)>>31sẽ 0dành cho chữ cái và -1chữ số.
  5. Nhìn vào trường hợp cho các chữ cái, triệu hồi cuối cùng trở thành 0bnằ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).
  6. Nhìn vào trường hợp cho các chữ số, chúng tôi muốn điều chỉnh triệu hồi cuối để nó ánh xạ btừ 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).
    Bây giờ chúng ta chỉ có thể nhân với 7. Nhưng vì -1 được biểu thị bằng tất cả các bit là 1, thay vào đó chúng ta có thể sử dụng & -7kể từ (0 & -7) == 0(-1 & -7) == -7.

Một số cân nhắc thêm:

  • Tôi đã không sử dụng biến vòng lặp thứ hai để lập chỉ mục c, vì phép đo cho thấy tính toán từ đó irẻ hơn.
  • Sử dụng chính xác i < bytes.Lengthnhư 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ể đó.
  • Tạo bmột int cho phép chuyển đổi không cần thiết từ và sang byte.

10
hex stringđể byte[] array?
AaA

15
+1 để trích dẫn chính xác nguồn của bạn sau khi gọi một chút ma thuật đen. Tất cả mưa đá Cthulhu.
Edward

4
Điều gì về chuỗi đến byte []?
Nizam Yahya Syaiful 6/11/13

9
Đẹp! Đối với những người cần đầu ra chữ thường, biểu thức rõ ràng thay đổi thành87 + b + (((b-10)>>31)&-39)
eXavier

2
@AaA Bạn đã nói " 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.
CoolOppo

97

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


21
Thậm chí ngắn hơn: String.Concat (Array.Convert ALL (byte, x => x.ToString ("X2"))
Nestor

14
Thậm chí ngắn hơn: String.Concat (byte.Select (b => b.ToString ("X2"))) [.NET4]
Allon Guralnek

14
Chỉ trả lời một nửa câu hỏi.
Sly Gryphon

1
Tại sao cái thứ hai cần .Net 4? String.Concat nằm trong .Net 2.0.
Polyfun

2
các vòng lặp "kiểu 90" nói chung nhanh hơn, nhưng với số lượng không đáng kể đủ để nó không quan trọng trong hầu hết các bối cảnh. Vẫn còn đáng nói
Austin_Anderson

69

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

Tại sao việc tạo bảng tra cứu trong phiên bản không an toàn lại trao đổi các nibble của byte được tính toán trước? Tôi nghĩ rằng endianness chỉ thay đổi thứ tự của các thực thể được hình thành từ nhiều byte.
Raif Atef

@RaifAtef Điều quan trọng ở đây không phải là thứ tự của các món ăn. Nhưng thứ tự của các từ 16 bit trong một số nguyên 32 bit. Nhưng tôi đang xem xét viết lại nó để cùng một mã có thể chạy bất kể tuổi thọ.
CodeInChaos

Đọc lại mã, tôi nghĩ rằng bạn đã làm điều này bởi vì khi bạn chuyển char * sau đó thành một uint * và gán nó (khi tạo hex char), bộ thực thi / CPU sẽ lật các byte (vì uint không được xử lý giống như 2 ký tự 16 bit riêng biệt) để bạn lật trước chúng để bù lại. Tôi có đúng không Endianness là khó hiểu :-).
Raif Atef

4
Điều này chỉ trả lời một nửa câu hỏi ... Làm thế nào từ chuỗi hex đến byte?
Narvalex

3
@CodesInChaos Tôi tự hỏi nếu Spanbây giờ có thể được sử dụng thay vì unsafe??
Konrad

64

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


14
Chỉ trả lời một nửa câu hỏi.
Sly Gryphon

3
Đâu là phần thứ hai của câu trả lời?
Sawan

56

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


không đề cập đến việc này sử dụng ít bộ nhớ nhất Không có chuỗi trung gian tạo ra bất cứ điều gì.
Chochos

8
Chỉ trả lời một nửa câu hỏi.
Sly Gryphon

Điều này thật tuyệt vì nó hoạt động trên cơ bản bất kỳ phiên bản nào của NET, bao gồm cả NETMF. Ngươi chiên thăng!
Jonesome phục hồi

1
Câu trả lời được chấp nhận cung cấp 2 phương pháp HexToByteArray tuyệt vời, đại diện cho nửa còn lại của câu hỏi. Giải pháp của Waleed trả lời cho câu hỏi đang chạy về cách thực hiện điều này mà không tạo ra một số lượng lớn các chuỗi trong quy trình.
Brendten Eickstaedt

Có chuỗi mới (c) sao chép và phân bổ lại hoặc nó đủ thông minh để biết khi nào nó có thể chỉ đơn giản là bọc char []?
jjxtra

19

Đâ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.ToByteString.Substringnế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.ToBytenế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.Substringkế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ẽ 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 StringReaderthay 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.ToBytebấ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.Readhai 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 ( jví 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 , tất cả những gì nó làm là gọi String.CopyTovà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 CopyTocho 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 jchỉ số tăng theo các bước của hai song song ikhông? Tất nhiên là không, chỉ cần nhân ihai (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.Substringcũ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.Substringlạ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 CopyTotrực tiếp trong đó để tránh cuộc gọi trên cao.

String.Substring

  • Trường hợp xấu nhất: Một phân bổ nhanh, một bản sao nhanh.
  • Trường hợp tốt nhất: Không phân bổ, không sao chép.

Phương pháp thủ công

  • Trường hợp xấu nhất: Hai phân bổ bình thường, một bản sao bình thường, một bản sao nhanh.
  • Trường hợp tốt nhất: Một phân bổ bình thường, một bản sao bình thườ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.ToByteString.Substringlà 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.)


lỗi: {"Không thể tìm thấy bất kỳ chữ số nào có thể nhận ra."}
Priya Jagtap

17

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 0bằ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''A'trong bảng ASCII ( ...456789:;<=>?@ABCD...).


16

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:

  • Bảng mã hóa 512 byte hoặc 1024 byte (gấp đôi kích thước nếu cần cả chữ hoa và chữ thường)
  • Bảng giải mã 256 byte hoặc 64 KiB (có thể tra cứu char đơn hoặc tra cứu char kép)

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ã.

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

Mã hóa

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

So sánh

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

* giải pháp này

Ghi chú

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.


2
Việc sử dụng bộ nhớ 256 byte là không đáng kể khi bạn chạy mã trên CLR.
heo

9

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

Mã này giả sử chuỗi hex sử dụng ký tự alpha chữ hoa và thổi lên nếu chuỗi hex sử dụng chữ thường alpha. Có thể muốn thực hiện chuyển đổi "chữ hoa" trên chuỗi đầu vào để an toàn.
Marc Novakowski

Đó là một quan sát sắc sảo Marc. Mã được viết để đảo ngược giải pháp của Waleed. Cuộc gọi ToUpper sẽ làm chậm một số thuật toán, nhưng sẽ cho phép nó xử lý các ký tự alpha chữ thường.
Chris F

3
Convert.ToByte (topChar + bottomChar) có thể được viết dưới dạng (byte) (topChar + bottomChar)
Amir Rezaei

Để xử lý cả hai trường hợp mà không bị phạt hiệu suất lớn,hexString[i] &= ~0x20;
Ben Voigt

9

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("-", "")

2
Lý do là hiệu suất, khi bạn cần giải pháp hiệu suất cao. :)
Ricky

7

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 unsafevà di chuyển chuỗi ký tự (rõ ràng dư thừa) ifsang 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.)


1
Đối với những người theo Knuth: Tôi đã làm điều này bởi vì tôi cần phân tích vài nghìn chuỗi hex mỗi vài phút hoặc lâu hơn, vì vậy điều quan trọng là nó phải nhanh nhất có thể (trong vòng lặp bên trong, như vậy). Giải pháp của Tomalak không chậm hơn đáng kể nếu nhiều phân tích như vậy không xảy ra.
Ben Mosher

5

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


5

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

4

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


Bạn có thể nên kiểm tra mã trước khi bạn đưa ra một câu hỏi như thế này.
jww

3

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ó.


2
Trình vòng lặp không phải là vấn đề chính của mã này. Bạn nên điểm chuẩn b.ToSting("X2").
heo

2

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

nếu Source == nullhoặc Source.Length == 0chúng tôi có một vấn đề thưa ông!
Andrei Krasnutski

2

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

2

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

2

Phiên bản ByteArrayToHexViaByteManipulation này có thể nhanh hơn.

Từ báo cáo của tôi:

  • ByteArrayToHexViaByteManipulation3: 1,68 tick trung bình (hơn 1000 lượt chạy), 17,5X
  • ByteArrayToHexViaByteManipulation2: 1,73 tick trung bình (hơn 1000 lượt chạy), 16,9X
  • ByteArrayToHexViaByteQuản lý: 2.90 tick trung bình (hơn 1000 lượt chạy), 10,1X
  • ByteArrayToHexViaLookupAndShift: 3,22 tick trung bình (hơn 1000 lượt chạy), 9,1X
  • ...

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

2

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ì StringBuildercá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.


Hmm, tôi thực sự nên tối ưu hóa điều này cho Char[]và sử dụng Charnội bộ thay vì ints ...
Maarten Bodewes

Đối với C #, việc khởi tạo các biến nơi chúng được sử dụng, thay vì bên ngoài vòng lặp, có lẽ được ưu tiên để cho trình biên dịch tối ưu hóa. Tôi có được hiệu suất tương đương một trong hai cách.
Peteter

2

Đố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++offsetthay vì offsetoffset + 1có 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.


1

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

1

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

1

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

1
HexStringToByteArray ("09") trả về 0x02 rất tệ
CoperNick

1

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

1

Đâ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:

  1. Các hình phạt cho việc lập chỉ mục thành một chuỗi để thực hiện tra cứu so với mảng char có ý nghĩa trong thử nghiệm tệp lớn.
  2. Các hình phạt cho việc sử dụng StringBuilder có dung lượng đã biết so với một mảng char có kích thước đã biết để tạo chuỗi thậm chí còn có ý nghĩa hơn.

Đâ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



1

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;
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.