Bagaimana Anda mengonversi array byte ke string heksadesimal, dan sebaliknya?

1372

Bagaimana Anda bisa mengubah array byte ke string heksadesimal, dan sebaliknya?

alextansc
sumber
8
Jawaban yang diterima di bawah ini muncul untuk mengalokasikan sejumlah string yang mengerikan dalam konversi string ke byte. Saya bertanya-tanya bagaimana ini berdampak kinerja
Wim Coenen
9
Kelas SoapHexBinary melakukan persis seperti yang Anda inginkan.
Mykroft
Sepertinya saya yang mengajukan 2 pertanyaan dalam 1 posting tidak cukup standar.
SandRock

Jawaban:

1353

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 untuk melakukannya, misalnya di 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 Substringadalah pilihan terbaik dalam kombinasi dengan Convert.ToByte. Lihat jawaban ini untuk informasi lebih lanjut. Jika Anda membutuhkan kinerja yang lebih baik, Anda harus menghindari Convert.ToBytesebelum Anda dapat jatuh SubString.

Tomalak
sumber
24
Anda menggunakan SubString. Bukankah loop ini mengalokasikan sejumlah besar objek string?
Wim Coenen
30
Jujur - sampai itu meruntuhkan kinerja secara dramatis, saya akan cenderung mengabaikan ini dan percaya pada Runtime dan GC untuk mengatasinya.
Tomalak
87
Karena byte adalah dua nibble, setiap string hex yang secara sah mewakili array byte harus memiliki jumlah karakter genap. Nilai 0 tidak boleh ditambahkan di mana pun - menambahkannya akan membuat asumsi tentang data tidak valid yang berpotensi berbahaya. Jika ada, metode StringToByteArray harus melempar FormatException jika string hex berisi jumlah karakter ganjil.
David Boike
7
@ 00jt Anda harus membuat asumsi bahwa F == 0F. Entah itu sama dengan 0F, atau input terpotong dan F sebenarnya adalah awal dari sesuatu yang belum Anda terima. Terserah konteks Anda untuk membuat asumsi itu, tapi saya percaya fungsi tujuan umum harus menolak karakter aneh sebagai tidak valid daripada membuat asumsi untuk kode panggilan.
David Boike
11
@ Davidvido Pertanyaannya TIDAK ada hubungannya dengan "bagaimana menangani nilai stream yang mungkin terpotong" Ini berbicara tentang sebuah String. String myValue = 10.ToString ("X"); myValue adalah "A" bukan "0A". Sekarang baca string itu kembali menjadi byte, oops Anda memecahkannya.
00jt
488

Analisis Kinerja

Catatan: pemimpin baru pada 2015-08-20.

Saya menjalankan masing-masing dari berbagai metode konversi melalui beberapa Stopwatchpengujian kinerja kasar , menjalankan dengan kalimat acak (n = 61, 1000 iterasi) dan menjalankan dengan teks Project Gutenburg (n = 1.238.957, 150 iterasi). Inilah hasilnya, kira-kira dari yang tercepat hingga yang terlambat Semua pengukuran dalam kutu ( 10.000 kutu = 1 ms ) dan semua catatan relatif dibandingkan dengan StringBuilderimplementasi [paling lambat] . Untuk kode yang digunakan, lihat di bawah ini atau repo framework tes di mana saya sekarang memelihara kode untuk menjalankan ini.

Penolakan

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

Hasil

Tabel pencarian telah mengambil alih manipulasi byte atas. Pada dasarnya, ada beberapa bentuk precomputing apa yang diberikan nibble atau byte dalam hex. Kemudian, saat Anda merobek data, Anda cukup mencari bagian selanjutnya untuk melihat apa string hex itu. Nilai itu kemudian ditambahkan ke output string yang dihasilkan dalam beberapa cara. Untuk manipulasi byte yang lama, yang berpotensi lebih sulit untuk dibaca oleh beberapa pengembang, adalah pendekatan dengan performa terbaik.

Taruhan terbaik Anda masih akan menemukan beberapa data representatif dan mencobanya di lingkungan seperti produksi. Jika Anda memiliki batasan memori yang berbeda, Anda dapat memilih metode dengan alokasi yang lebih sedikit daripada yang lebih cepat tetapi mengonsumsi lebih banyak memori.

Kode Pengujian

Jangan ragu untuk bermain dengan kode pengujian yang saya gunakan. Versi disertakan di sini tetapi jangan ragu untuk mengkloning repo dan menambahkan metode Anda sendiri. 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 kembali di kelas yang sama.
  3. Pastikan Anda menjalankan versi input yang Anda inginkan, kalimat atau teks, dengan mengaktifkan komentar di GenerateTestInput kelas yang sama.
  4. Tekan F5dan 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)

Menambahkan jawaban Waleed untuk analisis. Cukup cepat.

Pembaruan (2011-10-05)

string.Concat Array.ConvertAllVarian ditambahkan untuk kelengkapan (memerlukan .NET 4.0). Setara denganstring.Join versi.

Pembaruan (2012-02-05)

Repo uji mencakup lebih banyak varian seperti StringBuilder.Append(b.ToString("X2")). Tidak ada yang mengecewakan hasilnya. foreachlebih cepat daripada {IEnumerable}.Aggregate, misalnya, tetapi BitConvertermasih menang.

Pembaruan (2012-04-03)

Menambahkan SoapHexBinaryjawaban Mykroft untuk analisis, yang mengambil alih tempat ketiga.

Pembaruan (2013-01-15)

Menambahkan jawaban manipulasi byte CodesInChaos, yang mengambil alih tempat pertama (dengan margin besar pada blok teks 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).

Pembaruan (2014-07-31)

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

Pembaruan (2015-08-20)

Menambahkan pengoptimalan dan varian airbreatherunsafe ke repo jawaban ini . Jika Anda ingin bermain di permainan yang tidak aman, Anda bisa mendapatkan beberapa keuntungan kinerja besar dibandingkan pemenang teratas sebelumnya baik dalam string pendek maupun teks besar.

patridge
sumber
Maukah Anda menguji kode dari jawaban Waleed? Sepertinya sangat cepat. stackoverflow.com/questions/311165/…
Cristian Diaconescu
5
Meskipun membuat kode tersedia bagi Anda untuk melakukan hal yang Anda minta sendiri, saya memperbarui kode pengujian untuk menyertakan jawaban Waleed. Selain kesal, itu jauh lebih cepat.
Patridge
2
@CodesInChaos Selesai. Dan itu menang dalam tes saya dengan sedikit juga. Saya belum berpura-pura memahami sepenuhnya salah satu metode teratas, tetapi mereka mudah disembunyikan dari interaksi langsung.
Patridge
6
Jawaban ini tidak memiliki niat untuk menjawab pertanyaan tentang apa yang "alami" atau biasa. Tujuannya adalah memberi orang tolok ukur kinerja dasar karena, ketika Anda perlu melakukan konversi ini, Anda cenderung sering melakukannya. Jika seseorang membutuhkan kecepatan mentah, mereka hanya menjalankan benchmark dengan beberapa data uji yang sesuai di lingkungan komputasi yang diinginkan. Kemudian, selipkan metode itu ke dalam metode ekstensi di mana Anda tidak pernah melihat implementasinya lagi (misalnya,bytes.ToHexStringAtLudicrousSpeed() ).
patridge
2
Baru saja menghasilkan implementasi berbasis tabel pencarian kinerja tinggi. Varian yang aman sekitar 30% lebih cepat dari pemimpin saat ini di CPU saya. Varian yang tidak aman bahkan lebih cepat. stackoverflow.com/a/24343727/445517
CodesInChaos
244

Ada kelas yang disebut SoapHexBinary yang melakukan 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();
}
Mykroft
sumber
35
SoapHexBinary tersedia dari .NET 1.0 dan di mscorlib. Meskipun namespace lucu, itu tidak persis apa yang ditanyakan.
Sly Gryphon
4
Great ditemukan! Perhatikan bahwa Anda perlu membuat string aneh dengan 0 terkemuka untuk GetStringToBytes, seperti solusi lainnya.
Carter Medlin
Pernahkah Anda melihat pemikiran implementasi? Jawaban yang diterima memiliki IMHO yang lebih baik.
mfloryan
6
Menarik untuk melihat implementasi Mono di sini: github.com/mono/mono/blob/master/mcs/class/corlib/…
Jeremy
1
SoapHexBinary tidak didukung dalam .NET Core / .NET Standard ...
juFo
141

Saat menulis kode kripto, adalah hal biasa untuk menghindari pencarian cabang dan tabel yang bergantung pada data untuk memastikan runtime tidak bergantung pada data, karena pengaturan 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


Buang semua harapan, kamu yang masuk ke sini

Penjelasan dari si aneh yang mengutak-atik:

  1. bytes[i] >> 4 mengekstrak nibble byte yang tinggi
    bytes[i] & 0xF yang rendah
  2. b - 10
    adalah < 0untuk nilai b < 10, yang akan menjadi angka desimal
    adalah >= 0untuk nilai b > 10, yang akan menjadi surat dariA ke F.
  3. Menggunakan i >> 31pada integer 32 bit yang ditandatangani mengekstrak tanda, terima kasih untuk menandatangani ekstensi. Itu akan -1untuk i < 0dan 0untuk i >= 0.
  4. Menggabungkan 2) dan 3), menunjukkan bahwa (b-10)>>31akan 0untuk huruf dan -1untuk angka.
  5. Melihat case untuk surat-surat, penjumlahan terakhir menjadi 0, dan bberada dalam kisaran 10 hingga 15. Kami ingin memetakannya ke A(65) hingga F(70), yang berarti menambahkan 55 ( 'A'-10).
  6. Melihat case untuk digit, kami ingin mengadaptasi puncak terakhir sehingga memetakan bdari rentang 0 hingga 9 ke kisaran 0(48) hingga 9(57). Ini berarti harus menjadi -7 ( '0' - 55).
    Sekarang kita bisa mengalikannya dengan 7. Tapi karena -1 diwakili oleh semua bit menjadi 1, kita bisa menggunakan & -7sejak (0 & -7) == 0dan (-1 & -7) == -7.

Beberapa pertimbangan lebih lanjut:

  • Saya tidak menggunakan variabel loop kedua untuk mengindeks c , karena pengukuran menunjukkan bahwa menghitung itu ilebih murah.
  • Menggunakan persis i < bytes.Lengthseperti batas atas dari loop memungkinkan JITter untuk menghilangkan batas cek bytes[i], jadi saya memilih varian itu.
  • Membuat bint memungkinkan konversi yang tidak perlu dari dan ke byte.
CodesInChaos
sumber
10
Dan hex stringuntuk byte[] array?
AaA
15
1 untuk mengutip sumber Anda dengan benar setelah menerapkan sedikit ilmu hitam itu. Semua memuji Cthulhu.
Edward
4
Bagaimana dengan string ke byte []?
Syaiful Nizam Yahya
9
Bagus! Bagi mereka yang membutuhkan output huruf kecil, ekspresi jelas berubah menjadi87 + b + (((b-10)>>31)&-39)
eXavier
2
@AaA Anda berkata " byte[] array", yang secara harfiah berarti array array byte, atau byte[][]. Saya hanya mengolok-olok.
CoolOppo
97

Jika Anda menginginkan lebih banyak fleksibilitas daripada BitConverter, tetapi tidak ingin loop eksplisit gaya 1990-an yang kikuk, maka Anda dapat melakukannya:

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 pada posting asli.)

Will Dean
sumber
21
Bahkan lebih pendek: String.Concat (Array.ConvertAll (byte, x => x.ToString ("X2"))
Nestor
14
Bahkan lebih pendek: String.Compat (bytes.Select (b => b.ToString ("X2"))) [.NET4]
Allon Guralnek
14
Hanya menjawab setengah pertanyaan.
Sly Gryphon
1
Mengapa yang kedua membutuhkan .Net 4? String.Compat ada di .Net 2.0.
Polyfun
2
loop "gaya 90-an" itu umumnya lebih cepat, tetapi dengan jumlah yang cukup kecil sehingga tidak masalah dalam sebagian besar konteks. Masih layak disebutkan
Austin_Anderson
69

Pendekatan lain berdasarkan tabel pencarian. Yang ini hanya menggunakan satu tabel pencarian untuk setiap byte, bukan tabel pencarian per gigitan.

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 diuji varian ini menggunakan ushort, struct{char X1, X2},struct{byte X1, X2} di tabel.

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


Dan untuk kinerja yang lebih tinggi, unsafesaudara kandungnya:

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 dapat langsung menulis ke string:

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;
}
CodesInChaos
sumber
Mengapa membuat tabel pencarian dalam versi yang tidak aman menukar camilan dari byte yang sudah dihitung sebelumnya? Saya pikir endianness hanya mengubah urutan entitas yang terbentuk dari banyak byte.
Raif Atef
@ RaifAtef Yang penting di sini bukan urutan camilan. Namun urutan kata 16 bit dalam integer 32 bit. Tapi saya sedang mempertimbangkan untuk menulis ulang sehingga kode yang sama dapat berjalan terlepas dari endianness.
CodesInChaos
Membaca ulang kode, saya pikir Anda melakukan ini karena ketika Anda melemparkan char * kemudian ke uint * dan menetapkannya (ketika menghasilkan hex char), runtime / CPU akan membalik byte (karena Anda tidak diperlakukan sama dengan 2 karakter 16-bit yang terpisah) sehingga Anda melakukan pra-flipping untuk mengkompensasi. Apakah saya benar ? Endianness membingungkan :-).
Raif Atef
4
Ini hanya menjawab setengah dari pertanyaan ... Bagaimana dengan dari string hex ke byte?
Narvalex
3
@CodesInChaos Saya bertanya-tanya apakah Spandapat digunakan sekarang bukan unsafe??
Konrad
64

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

Baget
sumber
14
Hanya menjawab setengah pertanyaan.
Sly Gryphon
3
Di mana bagian kedua dari jawabannya?
Sawan
56

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: Forum post byte [] Array ke Hex String (lihat posting oleh PZahra). Saya memodifikasi kode sedikit untuk menghapus awalan 0x.

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

Waleed Eissa
sumber
belum lagi bahwa ini menggunakan memori paling sedikit. Tidak ada string antara yang dibuat.
Chochos
8
Hanya menjawab setengah pertanyaan.
Sly Gryphon
Ini bagus karena pada dasarnya berfungsi pada semua versi NET, termasuk NETMF. Seorang pemenang!
Jonesome Reinstate Monica
1
Jawaban yang diterima menyediakan 2 metode HexToByteArray yang sangat baik, yang mewakili setengah dari pertanyaan lainnya. Solusi Waleed menjawab pertanyaan berjalan tentang bagaimana melakukan ini tanpa membuat sejumlah besar string dalam proses.
Brendten Eickstaedt
Apakah string baru (c) menyalin dan mengalokasikan kembali atau cukup pintar untuk mengetahui kapan itu hanya dapat membungkus karakter []?
jjxtra
19

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

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

tl; dr: Cukup gunakan Convert.ToBytedan String.Substringjika Anda sedang terburu-buru ("Kode asli" di bawah), itu kombinasi terbaik jika Anda tidak ingin menerapkan kembali Convert.ToByte. Gunakan sesuatu yang lebih maju (lihat jawaban lain) yang tidak digunakan Convert.ToBytejika Anda membutuhkan kinerja. Jangan tidak menggunakan apa-apa lagi selain String.Substringdalam kombinasi denganConvert.ToByte , kecuali seseorang memiliki sesuatu yang menarik untuk mengatakan tentang ini di komentar dari jawaban ini.

peringatan: Jawaban ini mungkin menjadi usang jika suatu Convert.ToByte(char[], Int32)kelebihan diimplementasikan dalam rangka. Ini tidak mungkin terjadi segera.

Sebagai aturan umum, saya tidak suka mengatakan "jangan optimalkan secara prematur", 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 tidak, maka terlalu cepat, tunggu sampai proyek Anda lebih matang atau sampai Anda membutuhkan kinerja (jika ada kebutuhan nyata, maka Anda akan meluangkan 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 menghindari String.Substringdan menggunakan StringReadergantinya. Alasan yang diberikan adalah:

Sunting: Anda dapat meningkatkan kinerja untuk string panjang dengan menggunakan pengurai pass tunggal, seperti:

Nah, melihat kode referensiString.Substring , itu sudah jelas "single-pass"; dan mengapa tidak? Ini beroperasi pada level byte, bukan pada pasangan pengganti.

Namun memang mengalokasikan string baru, tetapi kemudian Anda harus mengalokasikan satu untuk lewat Convert.ToByte. Selanjutnya, solusi yang disediakan dalam revisi mengalokasikan objek lain pada setiap iterasi (array dua-char); Anda dapat dengan aman meletakkan alokasi itu di luar loop dan menggunakan kembali array untuk menghindarinya.

    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 oktet tunggal menggunakan dua digit (simbol).

Tapi, mengapa menelepon StringReader.Readdua kali? Panggil saja kelebihan kedua dan minta untuk membaca dua karakter dalam array dua karakter sekaligus; dan kurangi 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 dengan Anda adalah pembaca string yang hanya menambahkan "nilai" adalah indeks paralel (internal _pos) yang bisa Anda nyatakan sendiri (seperti jmisalnya), variabel panjang redundan (internal _length), dan referensi redundan untuk input string (internal _s). Dengan kata lain, itu tidak berguna.

Jika Anda bertanya-tanya bagaimana Read"membaca", lihat saja kode , yang dilakukannya hanyalah memanggil String.CopyTostring input. Sisanya hanya biaya pembukuan untuk mempertahankan nilai-nilai yang tidak kita butuhkan.

Jadi, lepaskan pembaca string, dan panggil CopyTodiri Anda 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 jindeks yang bertambah dalam langkah dua paralel i? Tentu saja tidak, hanya kalikan idengan dua (yang harus dioptimalkan oleh kompiler untuk 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 di awal, hanya alih-alih menggunakan String.Substringuntuk mengalokasikan string dan menyalin data ke dalamnya, Anda menggunakan array perantara tempat Anda menyalin angka heksadesimal, kemudian mengalokasikan string sendiri dan menyalin data lagi dari array dan ke dalam string (ketika Anda meneruskannya di konstruktor string). Salinan kedua mungkin dioptimalkan-keluar jika string sudah ada di kolam magang, tetapi kemudian String.Substringjuga akan dapat menghindarinya dalam kasus ini.

Bahkan, jika Anda melihat String.Substringlagi, Anda melihat bahwa ia menggunakan beberapa pengetahuan internal tingkat rendah tentang bagaimana string dibangun untuk mengalokasikan string lebih cepat dari yang biasanya Anda lakukan, dan itu menguraikan kode yang sama yang digunakan CopyTosecara 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

  • Kasus terburuk: Dua alokasi normal, satu salinan normal, satu salinan cepat.
  • Kasing terbaik: Satu alokasi normal, satu salinan normal.

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

Perhatikan bahwa menggunakan Convert.ToBytedan String.Substringmerupakan pilihan yang sangat valid jika Anda tidak membutuhkan kinerja ekstrem. Ingat: hanya memilih alternatif jika Anda memiliki waktu dan sumber daya untuk menyelidiki cara kerjanya dengan benar.

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

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

Penafian: Saya belum mendekompilasi versi terbaru kerangka kerja untuk memverifikasi bahwa sumber referensi terbaru, saya anggap benar.

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!

Props to Partridge untuk kerangka kerja bangku, mudah diretas. Input yang digunakan adalah hash SHA-1 berikut yang diulangi 5000 kali untuk menghasilkan string sepanjang 100.000 byte.

209113288F93A9AB8E474EA78D899AFDBB874355

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

mentega
sumber
galat: {"Tidak dapat menemukan angka yang dikenali."}
Priya Jagtap
17

Lengkapi untuk menjawab dengan @CodesInChaos (metode terbalik)

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

Penjelasan:

& 0x0f adalah untuk mendukung juga huruf kecil

hi = hi + 10 + ((hi >> 31) & 7); sama dengan:

hi = ch-65 + 10 + (((ch-65) >> 31) & 7);

Untuk '0' .. '9' itu sama dengan hi = ch - 65 + 10 + 7;yang hi = ch - 48(ini karena 0xffffffff & 7).

Untuk 'A' .. 'F' itu hi = ch - 65 + 10;(ini karena 0x00000000 & 7).

Untuk 'a' .. 'f' kita harus berjumlah besar sehingga kita harus mengurangi 32 dari versi default dengan membuat beberapa bit 0dengan menggunakan & 0x0f.

65 adalah kode untuk 'A'

48 adalah kode untuk '0'

7 adalah jumlah huruf antara '9'dan 'A'dalam tabel ASCII ( ...456789:;<=>?@ABCD...).

CoperNick
sumber
16

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

  • Tabel enkoder 512 byte atau 1024 byte (dua kali ukuran jika dibutuhkan huruf besar dan kecil)
  • Tabel dekoder 256 byte atau 64 KiB (baik pencarian karakter tunggal atau pencarian karakter ganda)

Solusi saya menggunakan 1024 byte untuk tabel penyandian, 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]));
}

Pengkodean

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 mendekode IOException dan IndexOutOfRangeException dapat terjadi (jika karakter memiliki nilai terlalu tinggi> 256). Metode untuk de / encoding stream atau array harus diimplementasikan, ini hanya bukti konsep.

drphrozen
sumber
2
Penggunaan memori 256 byte diabaikan ketika Anda menjalankan kode pada CLR.
dolmen
9

Ini adalah pos yang bagus. Saya suka solusi Waleed. Saya belum menjalankannya melalui tes patridge tetapi tampaknya cukup cepat. Saya juga membutuhkan proses sebaliknya, mengubah string hex menjadi array byte, jadi saya menulisnya sebagai pembalikan dari solusi Waleed. Tidak yakin apakah itu lebih cepat daripada solusi asli Tomalak. Sekali lagi, saya juga tidak menjalankan proses sebaliknya melalui tes 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;
}
Chris F
sumber
Kode ini mengasumsikan string hex menggunakan karakter alpha huruf besar, dan meledak jika string hex menggunakan huruf kecil alpha. Mungkin ingin melakukan konversi "huruf besar" pada string input agar aman.
Marc Novakowski
Itu pengamatan cerdas Marc. Kode ini ditulis untuk membalikkan solusi Waleed. Panggilan ToUpper akan memperlambat beberapa algoritma, tetapi akan memungkinkannya untuk menangani karakter alpha huruf kecil.
Chris F
3
Convert.ToByte (topChar + bottomChar) dapat ditulis sebagai (byte) (topChar + bottomChar)
Amir Rezaei
Untuk menangani kedua kasus tanpa penalti kinerja yang besar,hexString[i] &= ~0x20;
Ben Voigt
9

Mengapa membuatnya rumit? Ini sederhana di Visual Studio 2008:

C #:

string hex = BitConverter.ToString(YourByteArray).Replace("-", "");

VB:

Dim hex As String = BitConverter.ToString(YourByteArray).Replace("-", "")
Craig Poulton
sumber
2
alasannya adalah kinerja, ketika Anda membutuhkan solusi kinerja tinggi. :)
Ricky
7

Bukan untuk menumpuk banyak jawaban di sini, tapi saya menemukan yang cukup optimal (~ 4.5x lebih baik daripada diterima), implementasi langsung dari pengurai string hex. Pertama, output dari tes saya (batch pertama adalah implementasi saya):

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

Baris base64 dan 'BitConverter'd' ada untuk menguji kebenarannya. Perhatikan bahwa keduanya sama.

Pelaksanaan:

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

Saya mencoba beberapa hal unsafe dan memindahkan urutan karakter-ke-gigitan ifke metode lain, tapi ini adalah yang tercepat yang didapatnya.

(Saya mengakui bahwa ini menjawab setengah dari pertanyaan. Saya merasa bahwa konversi string-> byte [] kurang terwakili, sedangkan sudut string byte [] -> tampaknya tercakup dengan baik. Jadi, jawaban ini.)

Ben Mosher
sumber
1
Untuk pengikut Knuth: Saya melakukan ini karena saya perlu mengurai beberapa ribu string hex setiap beberapa menit, jadi penting bahwa itu harus secepat mungkin (dalam lingkaran dalam, seolah-olah). Solusi Tomalak tidak terlalu lambat jika banyak parses seperti itu tidak terjadi.
Ben Mosher
5

Versi aman:

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

Versi tidak aman Bagi mereka yang lebih suka kinerja dan tidak takut ketidakamanan. Tentang 35% lebih cepat ToHex dan 10% lebih cepat DariHex.

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 Untuk pengujian benchmark menginisialisasi alfabet setiap kali fungsi konversi yang dipanggil salah, alfabet harus berupa konst (untuk string) atau hanya baca statis (untuk karakter []). Kemudian konversi byte [] berbasis string ke string menjadi secepat versi manipulasi byte.

Dan tentu saja tes harus dikompilasi dalam Rilis (dengan optimasi) dan dengan opsi debug "Suppress JIT optimization" dimatikan (sama untuk "Aktifkan Just My Code" jika kode harus debuggable).

Maratius
sumber
5

Fungsi terbalik untuk kode Waleed Eissa (Hex String To 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;
    }

Waleed Eissa berfungsi dengan dukungan huruf kecil:

    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);
    }
Geografi
sumber
4

Metode ekstensi (penafian: kode yang sama sekali belum diuji, 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();
    }
}

dll. Gunakan salah satu dari tiga solusi Tomalak (dengan yang terakhir menjadi metode ekstensi pada string).

Pure.Krome
sumber
Anda mungkin harus menguji kode sebelum menawarkannya untuk pertanyaan seperti ini.
jww
3

Dari pengembang Microsoft, konversi yang bagus dan sederhana:

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

Sementara di atas bersih dan kompak, pecandu kinerja akan berteriak tentang hal itu menggunakan enumerator. Anda bisa mendapatkan kinerja puncak dengan versi perbaikan dari jawaban asli 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();   
} 

Ini adalah yang tercepat dari semua rutinitas yang saya lihat diposting di sini sejauh ini. Jangan hanya mengambil kata-kata saya untuk itu ... tes kinerja setiap rutin dan periksa kode CIL untuk Anda sendiri.

Tandai
sumber
2
Iterator bukan masalah utama kode ini. Anda harus patokan b.ToSting("X2").
dolmen
2

Dan untuk memasukkan ke dalam string SQL (jika Anda tidak menggunakan parameter perintah):

public static String ByteArrayToSQLHexString(byte[] Source)
{
    return = "0x" + BitConverter.ToString(Source).Replace("-", "");
}
Jack Straw
sumber
jika Source == nullatau Source.Length == 0kita punya masalah pak!
Andrei Krasutski
2

Dalam hal kecepatan, ini tampaknya lebih baik daripada apa pun di sini:

  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);
  }
Alexey Borzenkov
sumber
2

Saya tidak mendapatkan kode yang Anda sarankan untuk bekerja, Olipro. hex[i] + hex[i+1]ternyata mengembalikan sebuah int.

Namun, saya berhasil dengan mengambil beberapa petunjuk dari kode Waleeds dan memalu ini bersama-sama. Itu jelek sekali tetapi tampaknya bekerja dan bekerja pada 1/3 waktu dibandingkan dengan yang lain menurut tes saya (menggunakan mekanisme pengujian patridges). Tergantung pada ukuran input. Beralih di sekitar?: S untuk memisahkan 0-9 terlebih dahulu mungkin akan menghasilkan hasil yang sedikit lebih cepat karena ada lebih banyak angka daripada huruf.

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;
}
Fredrik Hu
sumber
2

Versi ByteArrayToHexViaByteManipulation ini bisa lebih cepat.

Dari laporan saya:

  • ByteArrayToHexViaByteManipulation3: 1,68 ticks rata-rata (lebih dari 1000 berjalan), 17,5X
  • ByteArrayToHexViaByteManipulation2: 1,73 ticks rata-rata (lebih dari 1000 berjalan), 16,9X
  • ByteArrayToHexViaByteManipulasi: 2,90 kutu rata-rata (lebih dari 1000 kali berjalan), 10,1X
  • ByteArrayToHexViaLookupAndShift: 3,22 kutu rata-rata (lebih dari 1000 berjalan), 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);
    }

Dan saya pikir ini adalah optimasi:

    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);
    }
JoseH
sumber
2

Saya akan memasuki kompetisi bit fiddling ini karena saya punya jawaban yang juga menggunakan bit-fiddling untuk memecahkan kode hexadecimal. Perhatikan bahwa menggunakan array karakter mungkin lebih cepat karena StringBuildermetode panggilan juga akan memakan waktu.

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

Dikonversi dari kode Java.

Maarten Bodewes
sumber
Hmm, saya benar-benar harus mengoptimalkan ini untuk Char[]dan menggunakan Charinternal daripada ints ...
Maarten Bodewes
Untuk C #, menginisialisasi variabel tempat mereka digunakan, daripada di luar loop, mungkin lebih disukai untuk membiarkan kompilator mengoptimalkan. Saya mendapatkan kinerja yang setara.
Peteter
2

Untuk kinerja saya akan pergi dengan solusi drphrozens. Pengoptimalan kecil untuk dekoder dapat menggunakan tabel untuk salah satu karakter untuk menghilangkan "<< 4".

Jelas kedua panggilan metode ini mahal. Jika semacam pemeriksaan dilakukan baik pada input atau data keluaran (bisa CRC, checksum atau apa pun) yang if (b == 255)...dapat dilewati dan dengan demikian juga metode panggilan sama sekali.

Menggunakan offset++dan offsetbukannya offsetdan offset + 1mungkin memberikan beberapa manfaat teoretis tetapi saya menduga kompiler menangani ini lebih baik daripada saya.

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

Ini hanya dari atas kepala saya dan belum diuji atau dibandingkan.

ClausAndersen
sumber
1

Variasi lain untuk keragaman:

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;          
}
Stas Makutin
sumber
1

Tidak dioptimalkan untuk kecepatan, tetapi lebih banyak LINQy daripada sebagian besar jawaban (.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
MCattle
sumber
1

Dua mashup yang melipat dua operasi menggigit menjadi satu.

Mungkin versi yang sangat efisien:

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

Versi linq-dengan-bit-peretasan dekaden:

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

Dan membalikkan:

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;
}
JJJ
sumber
1
HexStringToByteArray ("09") mengembalikan 0x02 yang buruk
CoperNick
1

Cara lain adalah dengan menggunakan stackallocuntuk mengurangi tekanan memori 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);
}
Kel
sumber
1

Ini kesempatan saya. Saya telah membuat sepasang kelas ekstensi untuk memperluas string dan byte. Pada tes file besar, kinerjanya sebanding dengan Byte Manipulation 2.

Kode di bawah ini untuk ToHexString adalah implementasi yang dioptimalkan dari algoritma pencarian dan pergeseran. Itu hampir identik dengan yang oleh Behrooz, tetapi ternyata menggunakan foreachuntuk mengulangi dan penghitung lebih cepat daripada pengindeksan eksplisit for.

Muncul di tempat ke-2 di belakang Byte Manipulation 2 di komputer saya dan kode yang sangat mudah dibaca. Hasil tes berikut juga menarik:

ToHexStringCharArrayWithCharArrayLookup: 41.589.69 kutu rata-rata (lebih dari 1000 berjalan), 1.5X ToHexStringCharArrayWithStringLookup: 50.764.06 kutu rata-rata (lebih dari 1000 berjalan), 1.2X ToHexStringStringBuilderLebih rata-rata160 berjalan

Berdasarkan hasil di atas, tampaknya aman untuk menyimpulkan bahwa:

  1. Hukuman untuk pengindeksan ke string untuk melakukan pencarian vs array char adalah signifikan dalam pengujian file besar.
  2. Hukuman untuk menggunakan StringBuilder dengan kapasitas yang diketahui vs. array char dengan ukuran yang diketahui untuk membuat string bahkan lebih signifikan.

Berikut kodenya:

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

Di bawah ini adalah hasil pengujian yang saya dapatkan ketika saya meletakkan kode saya di proyek pengujian @ patridge di mesin saya. Saya juga menambahkan tes untuk mengkonversi ke array byte dari heksadesimal. Tes berjalan yang menggunakan kode saya adalah ByteArrayToHexViaOptimizedLookupAndShift dan HexToByteArrayViaByteManipulation. HexToByteArrayViaConvertToByte diambil dari XXXX. HexToByteArrayViaSoapHexBinary adalah jawaban dari @ Mykroft.

Prosesor Intel Pentium III Xeon

    Cores: 4 <br/>
    Current Clock Speed: 1576 <br/>
    Max Clock Speed: 3092 <br/>

Mengubah array byte menjadi representasi string heksadesimal


ByteArrayToHexViaByteManipulation2: 39,366.64 ticks rata-rata (lebih dari 1000 berjalan), 22.4X

ByteArrayToHexViaOptimizedLookupAndShift: 41,588.64 ticks rata-rata (lebih dari 1000 berjalan), 21.2X

ByteArrayToHexViaLookup: rata-rata 55,509,56 kutu (lebih dari 1000 kali berjalan), 15,9X

ByteArrayToHexViaByteManipulasi: Kutu rata-rata 65.349,12 (lebih dari 1000 kali berjalan), 13,5X

ByteArrayToHexViaLookupAndShift: kutu rata-rata 86.926,87 (lebih dari 1000 berjalan), 10.2X

ByteArrayToHexStringViaBitConverter: 139.353,73 kutu rata-rata (lebih dari 1000 berjalan), 6.3X

ByteArrayToHexViaSoapHexBinary: 314.598,77 ticks rata-rata (lebih dari 1000 berjalan), 2.8X

ByteArrayToHexStringViaStringBuilderForEachByteToString: 344.264,63 kutu rata-rata (lebih dari 1000 berjalan), 2.6X

ByteArrayToHexStringViaStringBuilderAggregateByteToString: 382.623,44 kutu rata-rata (lebih dari 1000 berjalan), 2.3X

ByteArrayToHexStringViaStringBuilderForEachAppendFormat: 818.111,95 ticks rata-rata (lebih dari 1000 berjalan), 1.1X

ByteArrayToHexStringViaStringConcatArrayConvertAll: 839.244,84 kutu rata-rata (lebih dari 1000 berjalan), 1.1X

ByteArrayToHexStringViaStringBuilderAggregateAppendFormat: 867.303,98 kutu rata-rata (lebih dari 1000 berjalan), 1.0X

ByteArrayToHexStringViaStringJoinArrayConvertAll: 882.710,28 kutu rata-rata (lebih dari 1000 berjalan), 1.0X


JamieSee
sumber
1

Fungsi cepat lainnya ...

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