Pertanyaan Bagaimana Anda mengonversi susunan byte ke string heksadesimal, dan sebaliknya?


Bagaimana Anda bisa mengonversi susunan byte ke string heksadesimal, dan sebaliknya?


1115
2018-03-08 21:56


asal


Jawaban:


Antara:

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

atau:

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

Bahkan ada lebih banyak varian yang melakukannya, misalnya sini.

Konversi terbalik akan seperti ini:

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

Menggunakan Substring adalah opsi terbaik dalam kombinasi dengan Convert.ToByte. Lihat jawaban ini untuk informasi lebih lanjut. Jika Anda membutuhkan kinerja yang lebih baik, Anda harus menghindari Convert.ToByte sebelum kamu bisa jatuh SubString.


1071



Analisis Kinerja

Catatan: pemimpin baru pada 2015-08-20.

Saya menjalankan masing-masing dari berbagai metode konversi melalui beberapa mentah Stopwatch pengujian kinerja, dijalankan dengan kalimat acak (n = 61, 1000 iterasi) dan dijalankan dengan teks Proyek Gutenburg (n = 1.238.957, 150 iterasi). Berikut hasilnya, kira-kira dari yang paling cepat sampai yang paling lambat. Semua pengukuran dalam ticks (10.000 ticks = 1 ms) dan semua catatan relatif dibandingkan dengan [paling lambat] StringBuilder pelaksanaan. Untuk kode yang digunakan, lihat di bawah atau kerangka uji repo di mana saya sekarang mempertahankan kode untuk menjalankan ini.

Penolakan

PERINGATAN: Jangan bergantung pada statistik ini untuk sesuatu yang konkret; mereka hanyalah contoh sampel data. Jika Anda benar-benar membutuhkan kinerja terbaik, silakan uji metode ini dalam lingkungan yang mewakili kebutuhan produksi Anda dengan perwakilan data dari apa yang akan Anda gunakan.

Hasil

Tabel pencarian telah mengambil alih manipulasi byte. Pada dasarnya, ada beberapa bentuk precomputing apa yang setiap nibble atau byte yang diberikan dalam bentuk hex. Kemudian, saat Anda merobek data, Anda hanya mencari bagian berikutnya untuk melihat string hex apa yang akan digunakan. Nilai itu kemudian ditambahkan ke output string yang dihasilkan dalam beberapa mode. Untuk manipulasi byte lama, berpotensi lebih sulit dibaca oleh beberapa pengembang, adalah pendekatan berkinerja terbaik.

Taruhan terbaik Anda masih akan menemukan beberapa data yang representatif dan mencobanya di lingkungan seperti produksi. Jika Anda memiliki kendala memori yang berbeda, Anda mungkin lebih memilih metode dengan alokasi lebih sedikit ke salah satu yang akan lebih cepat tetapi mengkonsumsi lebih banyak memori.

Kode Pengujian

Jangan ragu untuk bermain dengan kode pengujian yang saya gunakan. Versi A disertakan di sini tetapi merasa bebas untuk mengkloning repo dan tambahkan metode Anda sendiri. Harap kirimkan permintaan tarik jika Anda menemukan sesuatu yang menarik atau ingin membantu meningkatkan kerangka pengujian yang digunakannya.

  1. Tambahkan metode statis baru (Func<byte[], string>) ke /Tests/ConvertByteArrayToHexString/Test.cs.
  2. Tambahkan nama metode itu ke TestCandidates mengembalikan nilai dalam kelas yang sama.
  3. Pastikan Anda menjalankan versi input yang Anda inginkan, kalimat atau teks, dengan mengubah komentar di GenerateTestInput di kelas yang sama.
  4. Memukul F5 dan tunggu hasilnya (dump HTML juga dihasilkan di folder / 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();
}

Pembaruan (2010-01-13)

Ditambahkan jawaban Waleed untuk analisis. Cukup cepat.

Pembaruan (2011-10-05)

Ditambahkan string.Concat  Array.ConvertAll varian untuk kelengkapan (membutuhkan .NET 4.0). Setara dengan string.Join versi.

Perbarui (2012-02-05)

Tes repo mencakup lebih banyak varian seperti StringBuilder.Append(b.ToString("X2")). Tidak ada yang mengecewakan hasilnya. foreach lebih cepat daripada {IEnumerable}.Aggregate, misalnya, tapi BitConverter masih menang.

Perbarui (2012-04-03)

Menambahkan Mykroft SoapHexBinary jawaban untuk analisis, yang mengambil alih tempat ketiga.

Pembaruan (2013-01-15)

Menambahkan jawaban manipulasi CodesInChaos, yang mengambil alih posisi pertama (dengan margin besar pada blok teks yang besar).

Pembaruan (2013-05-23)

Ditambahkan jawaban pencarian Nathan Moinvaziri dan varian dari blog Brian Lambert. Keduanya agak cepat, tetapi tidak memimpin pada mesin uji yang saya gunakan (AMD Phenom 9750).

Perbarui (2014-07-31)

Menambahkan jawaban lookup berbasis byte @ CodesInChaos yang baru. Tampaknya telah memimpin pada kedua tes kalimat dan tes teks lengkap.

Perbarui (2015-08-20)

Ditambahkan airbreather optimasi dan unsafe Varian untuk ini repo jawaban. Jika Anda ingin bermain dalam permainan yang tidak aman, Anda bisa mendapatkan keuntungan performa yang sangat besar dibanding pemenang top sebelumnya baik pada string pendek maupun teks besar.


405



Ada kelas yang disebut SoapHexBinary itu persis apa yang Anda inginkan.

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



Saat menulis kode crypto, biasanya untuk menghindari cabang dan pencarian tabel yang bergantung pada data untuk memastikan runtime tidak bergantung pada data, karena waktu yang bergantung pada data dapat menyebabkan serangan saluran samping.

Ini juga cukup cepat.

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


Abaikan semua harapan, kamu yang masuk ke sini

Penjelasan tentang sedikit aneh mengutak-atik:

  1. bytes[i] >> 4 mengekstrak nibble byte yang tinggi
    bytes[i] & 0xF mengekstrak nibble byte rendah
  2. b - 10
    aku s < 0 untuk nilai-nilai b < 10, yang akan menjadi digit desimal
    aku s >= 0 untuk nilai-nilai b > 10, yang akan menjadi surat dari A untuk F.
  3. Menggunakan i >> 31 pada bilangan bulat 32 bit yang ditandatangani mengekstrak tanda, terima kasih untuk menandatangani ekstensi. Boleh jadi -1 untuk i < 0 dan 0 untuk i >= 0.
  4. Menggabungkan 2) dan 3), menunjukkan itu (b-10)>>31 akan 0 untuk huruf dan -1 untuk digit.
  5. Melihat kasus untuk huruf, ringkasan terakhir menjadi 0, dan b berada di kisaran 10 hingga 15. Kami ingin memetakannya A(65) ke F(70), yang berarti menambahkan 55 ('A'-10).
  6. Melihat kasus untuk digit, kami ingin menyesuaikan ringkasan terakhir sehingga memetakan b mulai dari rentang 0 hingga 9 hingga kisaran 0(48) ke 9(57). Ini berarti harus menjadi -7 ('0' - 55).
    Sekarang kita hanya dapat mengalikan dengan 7. Tapi karena -1 diwakili oleh semua bit menjadi 1, kita bisa menggunakannya & -7 sejak (0 & -7) == 0 dan (-1 & -7) == -7.

Beberapa pertimbangan lebih lanjut:

  • Saya tidak menggunakan variabel loop kedua untuk diindeks c, karena pengukuran menunjukkan bahwa menghitungnya dari i lebih murah.
  • Menggunakan dengan tepat i < bytes.Length sebagai batas atas dari loop memungkinkan JITter untuk menghilangkan batas pemeriksaan bytes[i], jadi saya memilih varian itu.
  • Membuat b sebuah int memungkinkan konversi yang tidak perlu dari dan ke byte.

123



Jika Anda menginginkan lebih banyak fleksibilitas daripada BitConverter, tetapi tidak ingin loop eksplisit bergaya tahun 1990-an yang kikuk itu, maka Anda dapat melakukan:

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

Atau, jika Anda menggunakan .NET 4.0:

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

(Yang terakhir dari komentar di pos asli.)


83



Anda dapat menggunakan metode BitConverter.ToString:

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

Keluaran:

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

Informasi lebih lanjut: Metode BitConverter.ToString (Byte [])


56



Pendekatan berdasarkan tabel pencarian lainnya. Yang ini hanya menggunakan satu tabel pencarian untuk setiap byte, bukan tabel pencarian per 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);
}

Saya juga menguji varian ini menggunakan ushort, struct{char X1, X2}, struct{byte X1, X2} di tabel pencarian.

Tergantung pada target kompilasi (x86, X64) yang memiliki performa yang kurang lebih sama atau sedikit lebih lambat dari varian ini.


Dan untuk kinerja yang lebih tinggi, itu unsafe saudara kandung:

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

Atau jika Anda menganggap itu dapat diterima untuk menulis ke string secara langsung:

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



Saya baru saja mengalami masalah yang sama hari ini, dan saya menemukan kode ini:

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

Sumber: Posting forum byte [] Array to Hex String (lihat posting oleh PZahra). Saya memodifikasi kode sedikit untuk menghapus awalan 0x.

Saya melakukan beberapa pengujian kinerja pada kode dan hampir delapan kali lebih cepat daripada menggunakan BitConverter.ToString () (tercepat menurut posting patridge).


52



Masalah ini juga bisa diselesaikan dengan menggunakan tabel pencarian. Ini akan membutuhkan sejumlah kecil memori statis untuk enkoder dan dekoder. Namun metode ini akan cepat:

  • Tabel encoder 512 byte atau 1024 byte (dua kali ukuran jika kedua huruf besar dan kecil diperlukan)
  • Tabel dekoder 256 byte atau 64 KiB (salah satu pencarian arang tunggal atau dual char look-up)

Solusi saya menggunakan 1024 byte untuk tabel encoding, dan 256 byte untuk decoding.

Decoding

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

Encoding

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

Perbandingan

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

* solusi ini

Catatan

Selama decoding IOException dan IndexOutOfRangeException dapat terjadi (jika karakter memiliki nilai yang terlalu tinggi> 256). Metode untuk de / encoding stream atau array harus dilaksanakan, ini hanya bukti konsep.


15



Ini adalah jawaban untuk revisi 4 dari Jawaban Tomalak yang sangat populer (dan suntingan selanjutnya).

Saya akan membuat kasus bahwa pengeditan ini salah, dan menjelaskan mengapa itu bisa dikembalikan. Sepanjang jalan, Anda mungkin belajar satu atau dua hal tentang beberapa internal, dan melihat contoh lain dari apa sebenarnya pengoptimalan prematur itu dan bagaimana ia dapat menggigit Anda.

tl; dr: Cukup gunakan Convert.ToByte dan String.Substring jika Anda terburu-buru ("Kode asli" di bawah), ini adalah kombinasi terbaik jika Anda tidak ingin menerapkan ulang Convert.ToByte. Gunakan sesuatu yang lebih canggih (lihat jawaban lain) yang tidak digunakan Convert.ToByte jika kamu perlu kinerja. Melakukan tidak gunakan yang lain selain String.Substring dalam kombinasi dengan Convert.ToByte, kecuali seseorang memiliki sesuatu yang menarik untuk dikatakan tentang ini di komentar jawaban ini.

PERINGATAN: Jawaban ini mungkin menjadi usang jika Sebuah Convert.ToByte(char[], Int32) beban berlebih diterapkan dalam kerangka kerja. Ini tidak mungkin terjadi segera.

Sebagai aturan umum, saya tidak suka mengatakan "jangan optimalkan sebelum waktunya", karena tidak ada yang tahu kapan "prematur" itu. Satu-satunya hal yang harus Anda pertimbangkan ketika memutuskan apakah akan mengoptimalkan atau tidak adalah: "Apakah saya punya waktu dan sumber daya untuk menyelidiki pendekatan optimasi dengan benar?". Jika Anda tidak, maka itu terlalu cepat, tunggu sampai proyek Anda lebih matang atau sampai Anda membutuhkan kinerja (jika ada kebutuhan nyata, maka Anda akan membuat waktu). Sementara itu, lakukan hal paling sederhana yang mungkin bisa berhasil.

Kode asli:

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

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

Revisi terhindar String.Substring dan menggunakan StringReader sebagai gantinya. Alasan yang diberikan adalah:

Edit: Anda dapat meningkatkan kinerja untuk string panjang dengan menggunakan single   lewat parser, seperti:

Yah, lihatlah kode referensi untuk String.Substring, itu jelas "single-pass" sudah; dan mengapa tidak? Ini beroperasi pada tingkat byte, bukan pada pasangan pengganti.

Itu mengalokasikan string baru namun, tetapi kemudian Anda perlu mengalokasikan satu untuk lulus ke Convert.ToByte bagaimanapun. Selanjutnya, solusi yang disediakan dalam revisi mengalokasikan objek lain pada setiap iterasi (array dua-char); Anda dapat dengan aman menempatkan alokasi di luar loop dan menggunakan kembali array untuk menghindari itu.

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

Setiap heksadesimal numeral mewakili satu oktet menggunakan dua digit (simbol).

Tetapi kemudian, mengapa menelepon StringReader.Read dua kali? Panggil saja kelebihan kedua dan minta untuk membaca dua karakter dalam array dua-char sekaligus; dan mengurangi jumlah panggilan dua.

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

Yang tersisa adalah pembaca string yang hanya menambahkan "nilai" adalah indeks paralel (internal _pos) yang bisa Anda nyatakan sendiri (sebagai j misalnya), variabel panjang redundan (internal _length), dan referensi berlebihan ke string input (internal _s). Dengan kata lain, itu tidak berguna.

Jika Anda bertanya-tanya bagaimana caranya Read "membaca", lihat saja Kode, semua yang dilakukannya adalah panggilan String.CopyTo pada string input. Sisanya, hanya overhead pembukuan untuk mempertahankan nilai yang tidak kita perlukan.

Jadi, lepaskan pembaca string sudah, dan panggil CopyTo dirimu sendiri; lebih sederhana, lebih jelas, dan lebih efisien.

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

Apakah Anda benar-benar membutuhkan j indeks yang bertambah dalam langkah dua paralel ke i? Tentu tidak, kalikan saja i oleh dua (yang kompilator harus dapat mengoptimalkan penambahan).

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

Seperti apa solusinya sekarang? Persis seperti itu di awal, hanya alih-alih menggunakan String.Substring untuk mengalokasikan string dan menyalin data ke dalamnya, Anda menggunakan array perantara yang Anda salin angka heksadesimal ke, kemudian mengalokasikan string sendiri dan menyalin data lagi dari array dan ke dalam string (ketika Anda menyebarkannya dalam konstruktor string). Salinan kedua mungkin dioptimalkan jika string sudah ada di kumpulan magang, tetapi kemudian String.Substring juga akan dapat menghindarinya dalam kasus ini.

Bahkan, jika Anda melihat String.Substring Sekali lagi, Anda melihat bahwa ia menggunakan beberapa pengetahuan internal tingkat rendah tentang bagaimana string dibangun untuk mengalokasikan string lebih cepat daripada yang biasanya dapat Anda lakukan, dan kode inline yang sama digunakan oleh CopyTo langsung di sana untuk menghindari panggilan overhead.

String.Substring

  • Kasus terburuk: Satu alokasi cepat, satu salinan cepat.
  • Kasus terbaik: Tidak ada alokasi, tidak ada salinan.

Metode manual

  • Worst-case: Dua alokasi normal, satu salinan normal, satu salinan cepat.
  • Kasus terbaik: Satu alokasi normal, satu salinan normal.

Kesimpulan? Jika ingin digunakan Convert.ToByte(String, Int32) (karena Anda tidak ingin menerapkan kembali fungsi itu sendiri), sepertinya tidak ada cara untuk mengalahkannya String.Substring; yang Anda lakukan hanyalah berputar-putar, menciptakan kembali roda (hanya dengan bahan yang tidak optimal).

Perhatikan bahwa menggunakan Convert.ToByte dan String.Substring adalah pilihan yang sangat valid jika Anda tidak membutuhkan kinerja ekstrim. Ingat: hanya pilih alternatif jika Anda memiliki waktu dan sumber daya untuk menyelidiki cara kerjanya dengan benar.

Jika ada Convert.ToByte(char[], Int32), hal-hal akan berbeda tentu saja (akan mungkin untuk melakukan apa yang saya jelaskan di atas dan benar-benar hindari String).

Saya menduga bahwa orang-orang yang melaporkan kinerja yang lebih baik dengan "menghindari String.Substring"Juga hindari Convert.ToByte(String, Int32), yang benar-benar harus Anda lakukan jika Anda membutuhkan kinerjanya. Lihatlah jawaban lain yang tak terhitung jumlahnya untuk menemukan semua pendekatan yang berbeda untuk melakukan itu.

Penafian: Saya belum mendekompilasi versi terbaru dari kerangka kerja untuk memverifikasi bahwa sumber referensi sudah diperbarui, saya berasumsi itu.

Sekarang, semuanya terdengar bagus dan logis, semoga bahkan jelas jika Anda sudah berhasil sejauh ini. Tetapi apakah itu benar?

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

Iya nih!

Alat peraga untuk Partridge untuk kerangka kerja bangku, mudah untuk diretas. Input yang digunakan adalah SHA-1 hash berikut yang diulang 5000 kali untuk membuat string panjang 100.000 byte.

209113288F93A9AB8E474EA78D899AFDBB874355

Selamat bersenang-senang! (Tapi optimalkan dengan moderasi.)


14