Câu hỏi Làm thế nào để bạn chuyển đổi một mảng byte thành 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 chuỗi thập lục phân và ngược lại?


1118
2018-03-08 21:56


gốc


Câu trả lời được chấp nhận dưới đây xuất hiện để phân bổ một số lượng khủng khiếp của chuỗi trong chuỗi để chuyển đổi byte. Tôi tự hỏi làm thế nào điều này tác động đến hiệu suất - Wim Coenen
Lớp SoapHexBinary thực hiện chính xác những gì bạn muốn tôi nghĩ. - Mykroft
Xem thêm stackoverflow.com/a/14332574/22656 - Jon Skeet
câu trả lời này stackoverflow.com/a/14333437/1586797 nên đủ cho hiệu suất. - bronze man


Các 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 để thực hiện nó, ví dụ đây.

Việc chuyển đổi ngược lại sẽ 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.


1075



Bạn đang sử dụng SubString. Không vòng lặp này phân bổ một số lượng khủng khiếp của các đối tượng chuỗi? - Wim Coenen
Trung thực - cho đến khi nó rơi xuống hiệu suất đáng kể, tôi sẽ có xu hướng bỏ qua điều này và tin tưởng Runtime và GC để chăm sóc nó. - Tomalak
Bởi vì một byte là hai nibbles, bất kỳ chuỗi hex nào hợp lệ đại diện cho một mảng byte phải có số ký tự chẵn. Một 0 không nên được thêm vào bất cứ nơi nào - để thêm một sẽ được thực hiện một giả định về dữ liệu không hợp lệ đó là nguy hiểm tiềm tàng. Nếu bất cứ điều gì, phương thức StringToByteArray sẽ ném một FormatException nếu chuỗi hex chứa một số lẻ các ký tự. - David Boike
@ 00jt Bạn phải giả định rằng F == 0F. Hoặc là nó giống như 0F, hoặc đầu vào đã được cắt bớt và F thực sự là sự khởi đầu của một cái gì đó bạn đã không nhận được. Đó là vào bối cảnh của bạn để làm cho những giả định, nhưng tôi tin rằng một chức năng mục đích chung nên từ chối ký tự lẻ là không hợp lệ thay vì làm cho rằng giả định cho mã gọi. - David Boike
@DavidBoike Câu hỏi KHÔNG có gì để làm với "cách xử lý các giá trị dòng có thể cắt bớt" Nó nói về một Chuỗi. Chuỗi myValue = 10.ToString ("X"); myValue là "A" không phải là "0A". Bây giờ đi đọc chuỗi đó trở lại thành byte, oops bạn đã phá vỡ nó. - 00jt


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

Lưu ý: nhà lãnh đạo mới kể từ 2015-08-20.

Tôi chạy từng phương pháp chuyển đổi khác nhau thông qua một số thô Stopwatch kiểm tra hiệu năng, 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). Đây là kết quả, khoảng từ nhanh nhất đến chậm nhất. Tất cả các phép đo đều có trong ve (10.000 ve = 1 mili giây) và tất cả các ghi chú tương đối được so sánh với [chậm nhất] StringBuilder thực hiện. Đối với mã được sử dụng, hãy xem bên dưới hoặc kiểm tra khung repo nơi tôi bây giờ duy trì mã để chạy điều này.

Tuyên bố từ chối trách nhiệm

CẢNH BÁO: Không dựa vào các số liệu thống kê này cho bất kỳ thứ gì cụ thể; chúng đơ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, hãy kiểm tra các phương pháp này trong một môi trường đại diện 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ả

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 precomputing những gì bất kỳ nibble hoặc byte sẽ được trong hex. Sau đó, khi bạn trích xuất dữ liệu, bạn chỉ cần tìm 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, có khả năng khó đọc hơn bởi một số nhà phát triển, là phương pháp hoạt động hiệu quả nhất.

Đặt cược tốt nhất của bạn vẫn sẽ tìm thấy 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 về bộ nhớ khác nhau, bạn có thể thích một phương thức có phân bổ ít hơn cho một phân bổ sẽ nhanh hơn nhưng tiêu thụ nhiều bộ nhớ hơn.

Mã thử nghiệm

Vui lòng chơi với mã thử nghiệm mà tôi đã sử dụng. Một phiên bản được bao gồm ở đây nhưng cảm thấy tự do để sao chép repo và thêm các phương pháp 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 kỳ điều gì thú vị hoặc muốn giúp cải thiện khung kiểm tra 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 TestCandidates giá trị trả về trong cùng một lớp đó.
  3. Đảm bảo bạn đang chạy phiên bản nhập 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 bằng GenerateTestInput trong cùng một lớp.
  4. Đánh F5 và đợi đầu ra (một 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.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.

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

Kiểm tra repo bao gồm nhiều biến thể hơn như StringBuilder.Append(b.ToString("X2")). Không có bất kỳ kết quả nào. foreach nhanh hơn {IEnumerable}.Aggregate, ví dụ, nhưng BitConverter vẫn thắng.

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

Đã thêm Mykroft's SoapHexBinary trả lời để phân tích, chiếm vị trí thứ ba.

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

Các mã đã thêmTrong câu trả lời thao tác byte của CMS, nó 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 @ CodesInChaos. Nó dường như đã dẫn đầu trên cả hai bài kiểm tra câu và các bài kiểm tra toàn văn.

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

Thêm airbreather's tối ưu hóa và unsafe biến thể này trả lời của repo. Nếu bạn muốn chơi trong các trò chơi không an toàn, bạn có thể nhận được một số lợi ích hiệu suất rất lớn so với bất kỳ người chiến thắng hàng đầu trước đó trên cả hai chuỗi ngắn và các văn bản lớn.


407



Bạn có quan tâm để kiểm tra mã từ câu trả lời của Waleed? Nó có vẻ rất nhanh. stackoverflow.com/questions/311165/… - Cristi Diaconescu
Mặc dù làm cho mã có sẵn cho bạn để làm những điều bạn yêu cầu một mình, tôi đã cập nhật mã thử nghiệm để 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
@CodesInChaos Xong. Và nó đã thắng trong các bài kiểm tra của tôi một chút. Tôi không giả vờ hoàn toàn hiểu được một trong những phương pháp hàng đầu, nhưng chúng dễ dàng bị ẩn khỏi tương tác trực tiếp. - patridge
Câu trả lời này không có ý định trả lời câu hỏi về những gì là "tự nhiên" hoặc phổ biến. 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 kể từ 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 các điểm chuẩn với một số dữ liệu thử nghiệm thích hợp trong môi trường tính toán mong muốn của họ. Sau đó, đưa phương thức đó vào một phương thức tiện ích mở rộng nơi bạn không bao giờ xem lại phương pháp triển khai của nó (ví dụ: bytes.ToHexStringAtLudicrousSpeed()). - patridge
Chỉ cần tạo ra một bảng tra cứu hiệu suất cao dựa trên bảng 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 - CodesInChaos


Có một lớp được 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();
}

210



SoapHexBinary có sẵn từ .NET 1.0 và có trong mscorlib. Mặc dù đó là không gian tên hài hước, nó thực hiện chính xác câu hỏi được đặt ra. - Sly Gryphon
Tuyệt vời tìm! Lưu ý rằng bạn sẽ cần phải đệm các chuỗi lẻ với 0 hàng đầu cho GetStringToBytes, giống như giải pháp khác. - Carter Medlin
Bạn đã thấy suy nghĩ thực hiện chưa? Câu trả lời được chấp nhận có IMHO tốt hơn. - mfloryan
Bạn có nghĩa là việc thực hiện SoapHexBinary? Nếu vậy nó làm gì làm cho nó tồi tệ hơn việc thực hiện trong câu trả lời được chấp nhận? - Mykroft
Thú vị khi thấy triển khai Mono tại đây: github.com/mono/mono/blob/master/mcs/class/corlib/… - Jeremy


Khi viết mã mật mã, việc tránh các chi nhánh và bảng tra cứu dữ liệu phụ thuộc vào dữ liệu để đả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 bên kênh.

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 của các bit kỳ lạ fiddling:

  1. bytes[i] >> 4 chiết xuất cao nibble của một byte
    bytes[i] & 0xF chiết xuất các nibble thấp của một byte
  2. b - 10
    < 0 cho các giá trị b < 10, sẽ trở thành chữ số thập phân
    >= 0 cho các giá trị b > 10, sẽ trở thành một lá thư từ A đến F.
  3. Sử dụng i >> 31 trên số nguyên 32 bit đã ký, trích xuất dấu, nhờ dấu mở rộng. Nó sẽ là -1 cho i < 0 và 0 cho i >= 0.
  4. Kết hợp 2) và 3), cho thấy rằng (b-10)>>31 sẽ là 0 cho chữ cái và -1 cho chữ số.
  5. Nhìn vào trường hợp của các chữ cái, summon cuối cùng sẽ trở thành 0b nằm trong khoảng từ 10 đến 15. Chúng tôi muốn lập bản đồ A(65) đến F(70), ngụ ý thêm 55 ('A'-10).
  6. Nhìn vào trường hợp chữ số, chúng tôi muốn điều chỉnh bản tóm tắt cuối cùng để bản đồ b từ khoảng 0 đến 9 đến 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 diễn bằng tất cả các bit là 1, chúng ta có thể sử dụng & -7 kể từ đó (0 & -7) == 0 và (-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ừ i rẻ hơn.
  • Sử dụng chính xác i < bytes.Length như trên ràng buộc của vòng lặp cho phép JITter để loại bỏ giới hạn kiểm tra trên bytes[i], vì vậy tôi đã chọn biến thể đó.
  • Chế tạo b một int cho phép các chuyển đổi không cần thiết từ và đến byte.

124



Và hex string đến byte[] array? - AaA
1 để trích dẫn đúng nguồn của bạn sau khi gọi ra một chút ma thuật đen. Tất cả mưa đá Cthulhu. - Edward
Điều gì về chuỗi để byte []? - Syaiful Nizam Yahya
Tốt đẹp! Đối với những người cần đầu ra chữ thường, biểu thức rõ ràng sẽ thay đổi thành 87 + b + (((b-10)>>31)&-39) - eXavier
@AaA Bạn nói "byte[] array", nghĩa đen có nghĩa là một mảng các mảng byte hoặc byte[][]. Tôi chỉ đùa thôi. - CoolOppo


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 kiểu thập niên 90, sau đó 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")));

(Sau này từ nhận xét về bài đăng gốc.)


83



Thậm chí ngắn hơn: String.Concat (Array.ConvertAll (bytes, x => x.ToString ("X2")) - Nestor
Chỉ cần một lưu ý rằng kỹ thuật tốt đẹp của maxc không cần .net 4.0 - Will Dean
Thậm chí ngắn hơn: String.Concat (bytes.Select (b => b.ToString ("X2"))) [.NET4] - Allon Guralnek
Chỉ trả lời một nửa câu hỏi. - Sly Gryphon
các vòng lặp "kiểu 90" thường 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 ngữ cảnh. Vẫn đáng nói đến - Austin_Anderson


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


56



Chỉ trả lời một nửa câu hỏi. - Sly Gryphon
Phần thứ hai của câu trả lời là ở đâu? - Sawan


Một cách tiếp cận dựa trên bảng tra cứu khác. Điều này chỉ sử dụng một bảng tra cứu cho mỗi byte, thay vì một bảng tra cứu cho 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 các biến thể này bằng cách 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), chúng có hiệu suất tương tự hoặc chậm hơn một chút so với biến thể này.


Và cho hiệu suất cao hơn, unsafe anh chị em:

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 xem xét nó có thể chấp nhận để viết vào chuỗi trực tiếp:

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

53



Tại sao tạo bảng tra cứu trong phiên bản không an toàn trao đổi các nibbles của byte precomputed? Tôi nghĩ rằng endianness chỉ thay đổi thứ tự của các thực thể được tạo 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 nibbles. 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ó để mã giống nhau có thể chạy bất kể tính cuối cùng. - CodesInChaos
Đọc lại mã, tôi nghĩ rằng bạn đã làm điều này bởi vì khi bạn cast char * sau đó thành uint * và gán nó (khi tạo ra hex char), thời gian chạy / CPU sẽ lật các byte (vì uint không được xử lý tương tự như 2 ký tự 16 bit riêng biệt), do đó bạn đã lật trước chúng để bù lại. Tôi có đúng không? Endianness là khó hiểu :-). - Raif Atef
Được rồi, tôi sẽ cắn - có lợi thế gì để ghim _lookup32Unsafe vô thời hạn thay vì chỉ làm một phần ba fixedtuyên bố và cho phép GC chuyển mảng thành nội dung trái tim của nó bất cứ khi nào phương pháp này không chạy? - Joe Amenta
@JoeAmenta Không chắc chắn nếu có bất kỳ lợi thế đo lường được là trường hợp này. Có lẽ tôi chỉ đơn giản là không nghĩ về điều đó thay thế khi viết mã này. - CodesInChaos


Tôi vừa gặp phải vấn đề tương tự ngày hôm nay, và tôi đã xem xét 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: Bài đăng trên diễn đàn byte [] Mảng thành chuỗi Hex (xem bài viết 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ó đã gần như nhanh hơn 8 lần so với sử dụng BitConverter.ToString () (nhanh nhất theo bài đăng của patridge).


52



chưa kể rằng điều này sử dụng bộ nhớ ít nhất. Không có chuỗi trung gian nào được tạo ra. - Chochos
Chỉ trả lời một nửa câu hỏi. - Sly Gryphon
Điều này là tuyệt vời bởi 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
Câu trả lời được chấp nhận cung cấp 2 phương thức HexToByteArray xuất sắc, đạ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 câu hỏi đang chạy về cách thực hiện điều này mà không cần tạo một số lượng lớn các chuỗi trong quá trình này. - Brendten Eickstaedt
Có chuỗi mới (c) sao chép và tái phân bổ hoặc là nó đủ thông minh để biết khi nào nó có thể chỉ đơn giản là bọc char []? - jjxtra


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ẽ yêu cầu một lượng nhỏ bộ nhớ tĩnh cho cả bộ mã hóa và bộ giải mã. Tuy nhiên, phương pháp này sẽ nhanh chóng:

  • Bảng mã hóa 512 byte hoặc 1024 byte (hai lần kích thước nếu cả chữ hoa và chữ thường là cần thiết)
  • Bộ giải mã bảng 256 byte hoặc 64 KiB (hoặc một lần tìm kiếm đơn char hoặc tra cứu kép char)

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

chú thích

Trong khi giải mã IOException và IndexOutOfRangeException có thể xảy ra (nếu một ký tự có giá trị quá cao> 256). Phương pháp cho de / mã hóa suối hoặc mảng nên được thực hiện, đây chỉ là một bằng chứng về khái niệm.


15



Sử dụng bộ nhớ 256 byte là không đáng kể khi bạn chạy mã trên CLR. - dolmen


Đây là câu trả lời cho phiên bản 4 của Câu trả lời rất nổi tiếng của Tomalak (và các chỉnh sửa tiếp theo).

Tôi sẽ làm cho trường hợp sửa đổi này là sai, và giải thích 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 hoặc hai điều về một số nội bộ, và xem một ví dụ khác về việc 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ỉ dùng Convert.ToByte và String.Substring nếu bạn đang vội vàng ("Mã ban đầu" bên dưới), đó là sự kết hợp tốt nhất nếu bạn không muốn triển khai lại Convert.ToByte. Sử dụng một cái gì đó nâng cao hơn (xem câu trả lời khác) mà không sử dụng Convert.ToByte nếu bạn nhu cầu hiệu suất. Làm không phải sử dụng bất cứ thứ gì khác ngoài String.Substring kết hợp với Convert.ToByte, trừ khi ai đó có điều gì đó thú vị để nói về điều này trong phần bình luậ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ột Convert.ToByte(char[], Int32) quá tải được thực hiện trong khuôn khổ. Điều này khó xảy ra sớm.

Theo nguyên tắc chung, tôi không thích nói "đừng tối ưu hóa sớm", bởi vì không ai biết khi nào "sớm". Điều duy nhất bạn phải cân nhắc khi quyết định có tối ưu hóa hay không là: "Tôi có thời gian và nguồn lực để điều tra phương pháp tối ưu hóa đúng cách không?". Nếu không, thì quá sớm, đợ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ẽ chế tạo thời gian). Trong khi chờ đợi, 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;
    }

Bản 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 nhất định 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   vượt qua trình phân tích cú pháp, như vậy:

Vâng, nhìn vào mã tham chiếu cho String.Substring, rõ ràng là "single-pass" rồi; và tại sao nó không? Nó hoạt động ở cấp độ byte, không phải trên các cặp thay thế.

Nó phân bổ một chuỗi mới tuy nhiên, nhưng sau đó bạn cần phải phân bổ một chuỗi để chuyển đến Convert.ToByte dù sao. 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 mảng); bạn có thể an toàn đặt phân bổ đó ra ngoài vòng lặp và tái sử dụng 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 hệ thập lục phân numeral đại diện cho một octet đơn sử dụng hai chữ số (ký hiệu).

Nhưng sau đó, tại sao lại 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 đến 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 mà chỉ thêm "giá trị" là một chỉ mục song song (nội bộ _pos) mà bạn có thể tự khai báo (như j ví dụ), biến độ dài dư thừa (nội bộ _length) và tham chiếu dự phòng cho chuỗi đầu vào (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 mã, tất cả những gì nó làm là gọi String.CopyTo trên 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, hãy xóa trình đọc chuỗi và gọi CopyTo bản thân bạn; nó đơn giả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 với i? Tất nhiên là không, chỉ nhân i bởi hai (mà trình biên dịch sẽ có thể tối ưu hóa để 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;
    }

Các giải pháp trông như thế nào bây giờ? Chính xác như lúc đầu, chỉ thay vì sử dụng String.Substring để phân bổ chuỗi và sao chép dữ liệu vào chuỗi, bạn đang sử dụng mảng trung gian mà bạn sao chép các số thập lục phân sang, sau đó phân bổ chuỗi và sao chép dữ liệu lần nữa từ mảng và vào chuỗi (khi bạn truyền nó vào 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 hồ bơi thực tập, nhưng sau đó String.Substring cũng sẽ có thể tránh nó trong những trường hợp này.

Thực tế, nếu bạn nhìn vào String.Substring một lần nữa, bạn 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 chuỗi được xây dựng để phân bổ chuỗi nhanh hơn bình thường bạn có thể làm, và nó inline cùng một mã được sử dụng bởi CopyTo trực tiếp trong đó để tránh chi phí cuộc gọi.

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ụng Convert.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 trong vòng tròn, tái phát minh ra bánh xe (chỉ với các vật liệu tối ưu).

Lưu ý rằng việc 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 cực đoan. Hãy nhớ: chỉ chọn cách thay thế nếu bạn có thời gian và tài nguyên để điều tra cách hoạt động đúng cách.

Nếu có Convert.ToByte(char[], Int32), mọi thứ sẽ khác nhau tất nhiên (nó sẽ 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), bạn nên thực sự làm gì nếu bạn cần hiệu suất. Nhìn vào vô số câu trả lời khác để khám phá tất cả các phương pháp khác nhau để làm điều đó.

Disclaimer: Tôi đã không biên dịch phiên bản mới nhất của khuôn khổ để xác minh rằng nguồn tham khảo là up-to-date, tôi giả sử nó được.

Bây giờ, tất cả âm thanh tốt và hợp lý, hy vọng thậm chí hiển nhiên nếu bạn đã xoay xở để có được cho đế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

Vâng!

Đạo cụ để Partridge cho khuôn khổ băng ghế dự bị, thật dễ dàng để hack. Dữ liệu đầu vào được sử dụng là hàm băm SHA-1 sau 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 với kiểm duyệt.)


14





Bổ sung cho câu trả lời bằng @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ợ các 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; đó là hi = ch - 48 (điều này bởi vì 0xffffffff & 7).

Đối với 'A' .. 'F' nó là hi = ch - 65 + 10; (điều này bởi vì 0x00000000 & 7).

Đối với 'a' .. 'f', chúng ta phải có số lượng 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ố chữ cái giữa '9' và 'A' trong bảng ASCII (...456789:;<=>?@ABCD...).


12