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:
publicstaticstringByteArrayToString(byte[] ba){StringBuilder hex =newStringBuilder(ba.Length*2);foreach(byte b in ba)
hex.AppendFormat("{0:x2}", b);return hex.ToString();}
Bahkan ada lebih banyak varian untuk melakukannya, misalnya di sini .
Konversi terbalik akan seperti ini:
publicstaticbyte[]StringToByteArray(String hex){intNumberChars= hex.Length;byte[] bytes =newbyte[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.
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.
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.
Tambahkan metode statis baru (Func<byte[], string> ) ke /Tests/ConvertByteArrayToHexString/Test.cs.
Tambahkan nama metode itu ke TestCandidates kembali di kelas yang sama.
Pastikan Anda menjalankan versi input yang Anda inginkan, kalimat atau teks, dengan mengaktifkan komentar di GenerateTestInput kelas yang sama.
Tekan F5dan tunggu hasilnya (dump HTML juga dihasilkan di folder / bin).
staticstringByteArrayToHexStringViaStringJoinArrayConvertAll(byte[] bytes){returnstring.Join(string.Empty,Array.ConvertAll(bytes, b => b.ToString("X2")));}staticstringByteArrayToHexStringViaStringConcatArrayConvertAll(byte[] bytes){returnstring.Concat(Array.ConvertAll(bytes, b => b.ToString("X2")));}staticstringByteArrayToHexStringViaBitConverter(byte[] bytes){string hex =BitConverter.ToString(bytes);return hex.Replace("-","");}staticstringByteArrayToHexStringViaStringBuilderAggregateByteToString(byte[] bytes){return bytes.Aggregate(newStringBuilder(bytes.Length*2),(sb, b)=> sb.Append(b.ToString("X2"))).ToString();}staticstringByteArrayToHexStringViaStringBuilderForEachByteToString(byte[] bytes){StringBuilder hex =newStringBuilder(bytes.Length*2);foreach(byte b in bytes)
hex.Append(b.ToString("X2"));return hex.ToString();}staticstringByteArrayToHexStringViaStringBuilderAggregateAppendFormat(byte[] bytes){return bytes.Aggregate(newStringBuilder(bytes.Length*2),(sb, b)=> sb.AppendFormat("{0:X2}", b)).ToString();}staticstringByteArrayToHexStringViaStringBuilderForEachAppendFormat(byte[] bytes){StringBuilder hex =newStringBuilder(bytes.Length*2);foreach(byte b in bytes)
hex.AppendFormat("{0:X2}", b);return hex.ToString();}staticstringByteArrayToHexViaByteManipulation(byte[] bytes){char[] c =newchar[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);}returnnewstring(c);}staticstringByteArrayToHexViaByteManipulation2(byte[] bytes){char[] c =newchar[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));}returnnewstring(c);}staticstringByteArrayToHexViaSoapHexBinary(byte[] bytes){SoapHexBinary soapHexBinary =newSoapHexBinary(bytes);return soapHexBinary.ToString();}staticstringByteArrayToHexViaLookupAndShift(byte[] bytes){StringBuilder result =newStringBuilder(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();}staticreadonlyuint* _lookup32UnsafeP =(uint*)GCHandle.Alloc(_Lookup32,GCHandleType.Pinned).AddrOfPinnedObject();staticstringByteArrayToHexViaLookup32UnsafeDirect(byte[] bytes){var lookupP = _lookup32UnsafeP;var result =newstring((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;}staticuint[]_Lookup32=Enumerable.Range(0,255).Select(i =>{string s = i.ToString("X2");return((uint)s[0])+((uint)s[1]<<16);}).ToArray();staticstringByteArrayToHexViaLookupPerByte(byte[] bytes){var result =newchar[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);}returnnewstring(result);}staticstringByteArrayToHexViaLookup(byte[] bytes){string[] hexStringTable =newstring[]{"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 =newStringBuilder(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.ConcatArray.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.
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;publicstaticbyte[]GetStringToBytes(stringvalue){SoapHexBinary shb =SoapHexBinary.Parse(value);return shb.Value;}publicstaticstringGetBytesToString(byte[]value){SoapHexBinary shb =newSoapHexBinary(value);return shb.ToString();}
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.
staticstringByteToHexBitFiddle(byte[] bytes){char[] c =newchar[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));}returnnewstring(c);}
bytes[i] >> 4 mengekstrak nibble byte yang tinggi bytes[i] & 0xF yang rendah
b - 10
adalah < 0untuk nilai b < 10, yang akan menjadi angka desimal
adalah >= 0untuk nilai b > 10, yang akan menjadi surat dariA ke F.
Menggunakan i >> 31pada integer 32 bit yang ditandatangani mengekstrak tanda, terima kasih untuk menandatangani ekstensi. Itu akan -1untuk i < 0dan 0untuk i >= 0.
Menggabungkan 2) dan 3), menunjukkan bahwa (b-10)>>31akan 0untuk huruf dan -1untuk angka.
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).
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.
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")));
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.
privatestaticreadonlyuint[] _lookup32 =CreateLookup32();privatestaticuint[]CreateLookup32(){var result =newuint[256];for(int i =0; i <256; i++){string s=i.ToString("X2");
result[i]=((uint)s[0])+((uint)s[1]<<16);}return result;}privatestaticstringByteArrayToHexViaLookup32(byte[] bytes){var lookup32 = _lookup32;var result =newchar[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);}returnnewstring(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:
privatestaticreadonlyuint[] _lookup32Unsafe =CreateLookup32Unsafe();privatestaticreadonlyuint* _lookup32UnsafeP =(uint*)GCHandle.Alloc(_lookup32Unsafe,GCHandleType.Pinned).AddrOfPinnedObject();privatestaticuint[]CreateLookup32Unsafe(){var result =newuint[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;}publicstaticstringByteArrayToHexViaLookup32Unsafe(byte[] bytes){var lookupP = _lookup32UnsafeP;var result =newchar[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]];}}returnnewstring(result);}
Atau jika Anda menganggap dapat langsung menulis ke string:
publicstaticstringByteArrayToHexViaLookup32UnsafeDirect(byte[] bytes){var lookupP = _lookup32UnsafeP;var result =newstring((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;}
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:
Saya baru saja mengalami masalah yang sama hari ini, dan saya menemukan kode ini:
privatestaticstringByteArrayToHex(byte[] barray){char[] c =newchar[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);}returnnewstring(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).
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 []?
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:
publicstaticbyte[]HexadecimalStringToByteArray_Original(string input){var outputLength = input.Length/2;var output =newbyte[outputLength];for(var i =0; i < outputLength; i++)
output[i]=Convert.ToByte(input.Substring(i *2,2),16);return output;}
Revisi 4:
publicstaticbyte[]HexadecimalStringToByteArray_Rev4(string input){var outputLength = input.Length/2;var output =newbyte[outputLength];
using (var sr =newStringReader(input)){for(var i =0; i < outputLength; i++)
output[i]=Convert.ToByte(newstring(newchar[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.
publicstaticbyte[]HexadecimalStringToByteArray(string input){var outputLength = input.Length/2;var output =newbyte[outputLength];var numeral =newchar[2];
using (var sr =newStringReader(input)){for(var i =0; i < outputLength; i++){
numeral[0]=(char)sr.Read();
numeral[1]=(char)sr.Read();
output[i]=Convert.ToByte(newstring(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.
publicstaticbyte[]HexadecimalStringToByteArray(string input){var outputLength = input.Length/2;var output =newbyte[outputLength];var numeral =newchar[2];
using (var sr =newStringReader(input)){for(var i =0; i < outputLength; i++){var read = sr.Read(numeral,0,2);Debug.Assert(read ==2);
output[i]=Convert.ToByte(newstring(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.
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).
publicstaticbyte[]HexadecimalStringToByteArray_BestEffort(string input){var outputLength = input.Length/2;var output =newbyte[outputLength];var numeral =newchar[2];for(int i =0; i < outputLength; i++){
input.CopyTo(i *2, numeral,0,2);
output[i]=Convert.ToByte(newstring(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.60GHzCores:8CurrentClockSpeed:2600MaxClockSpeed:2600--------------------Parsing hexadecimal stringinto an array of bytes
--------------------HexadecimalStringToByteArray_Original:7,777.09 average ticks (over 10000 runs),1.2XHexadecimalStringToByteArray_BestEffort:8,550.82 average ticks (over 10000 runs),1.1XHexadecimalStringToByteArray_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.)
galat: {"Tidak dapat menemukan angka yang dikenali."}
Priya Jagtap
17
Lengkapi untuk menjawab dengan @CodesInChaos (metode terbalik)
publicstaticbyte[]HexToByteUsingByteManipulation(string s){byte[] bytes =newbyte[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...).
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
privatestaticreadonlybyte[]LookupTable=newbyte[]{0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,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};privatestaticbyteLookup(char c){var b =LookupTable[c];if(b ==255)thrownewIOException("Expected a hex character, got "+ c);return b;}publicstaticbyteToByte(char[] chars,int offset){return(byte)(Lookup(chars[offset])<<4|Lookup(chars[offset +1]));}
Pengkodean
privatestaticreadonlychar[][]LookupTableUpper;privatestaticreadonlychar[][]LookupTableLower;staticHex(){LookupTableLower=newchar[256][];LookupTableUpper=newchar[256][];for(var i =0; i <256; i++){LookupTableLower[i]= i.ToString("x2").ToCharArray();LookupTableUpper[i]= i.ToString("X2").ToCharArray();}}publicstaticchar[]ToCharLower(byte[] b,int bOffset){returnLookupTableLower[b[bOffset]];}publicstaticchar[]ToCharUpper(byte[] b,int bOffset){returnLookupTableUpper[b[bOffset]];}
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.
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.
privatebyte[]HexStringToByteArray(string hexString){int hexStringLength = hexString.Length;byte[] b =newbyte[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;}
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:
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:04c63f7842740c77e545bb0b2ade90b384f119f6ab57b680b7aa575a2f40939fTime to parse 100,000 times:50.4192 ms
Resultas 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-57-B6-80-B7-AA-57-5A-2F-40-93-9FAccepted answer:(StringToByteArray)Time to parse 100000 times:233.1264msResultas 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-57-B6-80-B7-AA-57-5A-2F-40-93-9FWithMono's implementation:Time to parse 100000 times:777.2544msResultas 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-57-B6-80-B7-AA-57-5A-2F-40-93-9FWithSoapHexBinary:Time to parse 100000 times:845.1456msResultas 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-57-B6-80-B7-AA-57-5A-2F-40-93-9F
Baris base64 dan 'BitConverter'd' ada untuk menguji kebenarannya. Perhatikan bahwa keduanya sama.
Pelaksanaan:
publicstaticbyte[]ToByteArrayFromHex(string hexString){if(hexString.Length%2!=0)thrownewArgumentException("String must have an even length");vararray=newbyte[hexString.Length/2];for(int i =0; i < hexString.Length; i +=2){array[i/2]=ByteFromTwoChars(hexString[i], hexString[i +1]);}returnarray;}privatestaticbyteByteFromTwoChars(char p,char p_2){byte ret;if(p <='9'&& p >='0'){
ret =(byte)((p -'0')<<4);}elseif(p <='f'&& p >='a'){
ret =(byte)((p -'a'+10)<<4);}elseif(p <='F'&& p >='A'){
ret =(byte)((p -'A'+10)<<4);}elsethrownewArgumentException("Char is not a hex digit: "+ p,"p");if(p_2 <='9'&& p_2 >='0'){
ret |=(byte)((p_2 -'0'));}elseif(p_2 <='f'&& p_2 >='a'){
ret |=(byte)((p_2 -'a'+10));}elseif(p_2 <='F'&& p_2 >='A'){
ret |=(byte)((p_2 -'A'+10));}elsethrownewArgumentException("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.)
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:
publicstaticclassHexHelper{[System.Diagnostics.Contracts.Pure]publicstaticstringToHex(thisbyte[]value){if(value==null)thrownewArgumentNullException("value");conststring hexAlphabet =@"0123456789ABCDEF";var chars =newchar[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];}}returnnewstring(chars);}[System.Diagnostics.Contracts.Pure]publicstaticbyte[]FromHex(thisstringvalue){if(value==null)thrownewArgumentNullException("value");if(value.Length%2!=0)thrownewArgumentException("Hexadecimal value length must be even.","value");unchecked{byte[] result =newbyte[value.Length/2];for(int i =0; i < result.Length; i++){// 0(48) - 9(57) -> 0 - 9// A(65) - F(70) -> 10 - 15int 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.
publicstaticclassHexUnsafeHelper{[System.Diagnostics.Contracts.Pure]publicstaticunsafestringToHex(thisbyte[]value){if(value==null)thrownewArgumentNullException("value");conststring alphabet =@"0123456789ABCDEF";string result =newstring(' ',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]publicstaticunsafebyte[]FromHex(thisstringvalue){if(value==null)thrownewArgumentNullException("value");if(value.Length%2!=0)thrownewArgumentException("Hexadecimal value length must be even.","value");unchecked{byte[] result =newbyte[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 - 15int 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).
Fungsi terbalik untuk kode Waleed Eissa (Hex String To Byte Array):
publicstaticbyte[]HexToBytes(thisstring hexString){byte[] b =newbyte[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:
publicstaticstringBytesToHex(thisbyte[] barray,bool toLowerCase =true){byte addByte =0x37;if(toLowerCase) addByte =0x57;char[] c =newchar[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);}returnnewstring(c);}
Metode ekstensi (penafian: kode yang sama sekali belum diuji, BTW ...):
publicstaticclassByteExtensions{publicstaticstringToHexString(thisbyte[] ba){StringBuilder hex =newStringBuilder(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).
Anda mungkin harus menguji kode sebelum menawarkannya untuk pertanyaan seperti ini.
jww
3
Dari pengembang Microsoft, konversi yang bagus dan sederhana:
publicstaticstringByteArrayToString(byte[] ba){// Concatenate the bytes into one long stringreturn ba.Aggregate(newStringBuilder(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 :
publicstaticstringByteArrayToString(byte[] ba){StringBuilder hex =newStringBuilder(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.
jika Source == nullatau Source.Length == 0kita punya masalah pak!
Andrei Krasutski
2
Dalam hal kecepatan, ini tampaknya lebih baik daripada apa pun di sini:
publicstaticstringToHexString(byte[] data){byte b;int i, j, k;int l = data.Length;char[] r =newchar[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);}returnnewstring(r);}
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.
publicstaticbyte[]StringToByteArray2(string hex){byte[] bytes =newbyte[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;}
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
...
staticprivatereadonlychar[] hexAlphabet =newchar[]{'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};staticstringByteArrayToHexViaByteManipulation3(byte[] bytes){char[] c =newchar[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];}returnnewstring(c);}
Dan saya pikir ini adalah optimasi:
staticprivatereadonlychar[] hexAlphabet =newchar[]{'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};staticstringByteArrayToHexViaByteManipulation4(byte[] bytes){char[] c =newchar[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];}returnnewstring(c);}
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.
publicstaticStringToHex(byte[] data){int dataLength = data.Length;// pre-create the stringbuilder using the length of the data * 2, precisely enoughStringBuilder sb =newStringBuilder(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 letterint 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 characterint 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();}publicstaticbyte[]FromHex(String hex){// pre-create the arrayint resultLength = hex.Length/2;byte[] result =newbyte[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 basedvalue=((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){thrownewArgumentException("Hexadecimal encoding incorrect for input "+ hex);}return result;}
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.
privatestaticreadonlybyte[]LookupTableLow=newbyte[]{0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,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};privatestaticreadonlybyte[]LookupTableHigh=newbyte[]{0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,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};privatestaticbyteLookupLow(char c){var b =LookupTableLow[c];if(b ==255)thrownewIOException("Expected a hex character, got "+ c);return b;}privatestaticbyteLookupHigh(char c){var b =LookupTableHigh[c];if(b ==255)thrownewIOException("Expected a hex character, got "+ c);return b;}publicstaticbyteToByte(char[] chars,int offset){return(byte)(LookupHigh(chars[offset++])|LookupLow(chars[offset]));}
Ini hanya dari atas kepala saya dan belum diuji atau dibandingkan.
publicstaticbyte[]FromHexString(string src){if(String.IsNullOrEmpty(src))returnnull;int index = src.Length;int sz = index /2;if(sz <=0)returnnull;byte[] rc =newbyte[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;}
Dua mashup yang melipat dua operasi menggigit menjadi satu.
Mungkin versi yang sangat efisien:
publicstaticstringByteArrayToString2(byte[] ba){char[] c =newchar[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));}returnnewstring( c );}
Versi linq-dengan-bit-peretasan dekaden:
publicstaticstringByteArrayToString(byte[] ba){returnstring.Concat( ba.SelectMany( b =>newint[]{ b >>4, b &0xF}).Select( b =>(char)(55+ b +(((b-10)>>31)&-7))));}
Dan membalikkan:
publicstaticbyte[]HexStringToByteArray(string s ){byte[] ab =newbyte[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;}
HexStringToByteArray ("09") mengembalikan 0x02 yang buruk
CoperNick
1
Cara lain adalah dengan menggunakan stackallocuntuk mengurangi tekanan memori GC:
staticstringByteToHexBitFiddle(byte[] bytes){var c =stackallocchar[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';returnnewstring(c);}
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:
Hukuman untuk pengindeksan ke string untuk melakukan pencarian vs array char adalah signifikan dalam pengujian file besar.
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{publicstaticclassByteArrayExtensions{privatereadonlystaticchar[] digits =newchar[]{'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};publicstaticstringToHexString(thisbyte[] bytes){char[] hex =newchar[bytes.Length*2];int index =0;foreach(byte b in bytes){
hex[index++]= digits[b >>4];
hex[index++]= digits[b &0x0F];}returnnewstring(hex);}}}
using System;
using System.IO;
namespace ConversionExtensions{publicstaticclassStringExtensions{publicstaticbyte[]ToBytes(thisstring hexString){if(!string.IsNullOrEmpty(hexString)&& hexString.Length%2!=0){thrownewFormatException("Hexadecimal string must not be empty and must contain an even number of digits to be valid.");}
hexString = hexString.ToUpperInvariant();byte[] data =newbyte[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){thrownewFormatException("An invalid digit was encountered. Valid hexadecimal digits are 0-9 and A-F.");}else{bytevalue=(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.
Jawaban:
Antara:
atau:
Bahkan ada lebih banyak varian untuk melakukannya, misalnya di sini .
Konversi terbalik akan seperti ini:
Menggunakan
Substring
adalah pilihan terbaik dalam kombinasi denganConvert.ToByte
. Lihat jawaban ini untuk informasi lebih lanjut. Jika Anda membutuhkan kinerja yang lebih baik, Anda harus menghindariConvert.ToByte
sebelum Anda dapat jatuhSubString
.sumber
Analisis Kinerja
Catatan: pemimpin baru pada 2015-08-20.
Saya menjalankan masing-masing dari berbagai metode konversi melalui beberapa
Stopwatch
pengujian 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 denganStringBuilder
implementasi [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
unsafe
(via CodesInChaos) (ditambahkan untuk menguji repo oleh airbreather )BitConverter
(via Tomalak){SoapHexBinary}.ToString
(via Mykroft){byte}.ToString("X2")
(menggunakanforeach
) (berasal dari jawaban Will Dean){byte}.ToString("X2")
(menggunakan{IEnumerable}.Aggregate
, membutuhkan System.Linq) (melalui Mark)Array.ConvertAll
(menggunakanstring.Join
) (via Will Dean)Array.ConvertAll
(menggunakanstring.Concat
, membutuhkan .NET 4.0) (via Will Dean){StringBuilder}.AppendFormat
(menggunakanforeach
) (via Tomalak){StringBuilder}.AppendFormat
(menggunakan{IEnumerable}.Aggregate
, membutuhkan System.Linq) (berasal dari jawaban Tomalak)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.
Func<byte[], string>
) ke /Tests/ConvertByteArrayToHexString/Test.cs.TestCandidates
kembali di kelas yang sama.GenerateTestInput
kelas yang sama.Pembaruan (2010-01-13)
Menambahkan jawaban Waleed untuk analisis. Cukup cepat.
Pembaruan (2011-10-05)
string.Concat
Array.ConvertAll
Varian 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.foreach
lebih cepat daripada{IEnumerable}.Aggregate
, misalnya, tetapiBitConverter
masih menang.Pembaruan (2012-04-03)
Menambahkan
SoapHexBinary
jawaban 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 airbreather
unsafe
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.sumber
bytes.ToHexStringAtLudicrousSpeed()
).Ada kelas yang disebut SoapHexBinary yang melakukan apa yang Anda inginkan.
sumber
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.
Ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl fhtagn
Penjelasan dari si aneh yang mengutak-atik:
bytes[i] >> 4
mengekstrak nibble byte yang tinggibytes[i] & 0xF
yang rendahb - 10
adalah
< 0
untuk nilaib < 10
, yang akan menjadi angka desimaladalah
>= 0
untuk nilaib > 10
, yang akan menjadi surat dariA
keF
.i >> 31
pada integer 32 bit yang ditandatangani mengekstrak tanda, terima kasih untuk menandatangani ekstensi. Itu akan-1
untuki < 0
dan0
untuki >= 0
.(b-10)>>31
akan0
untuk huruf dan-1
untuk angka.0
, danb
berada dalam kisaran 10 hingga 15. Kami ingin memetakannya keA
(65) hinggaF
(70), yang berarti menambahkan 55 ('A'-10
).b
dari rentang 0 hingga 9 ke kisaran0
(48) hingga9
(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
& -7
sejak(0 & -7) == 0
dan(-1 & -7) == -7
.Beberapa pertimbangan lebih lanjut:
c
, karena pengukuran menunjukkan bahwa menghitung itui
lebih murah.i < bytes.Length
seperti batas atas dari loop memungkinkan JITter untuk menghilangkan batas cekbytes[i]
, jadi saya memilih varian itu.b
int memungkinkan konversi yang tidak perlu dari dan ke byte.sumber
hex string
untukbyte[] array
?87 + b + (((b-10)>>31)&-39)
byte[] array
", yang secara harfiah berarti array array byte, ataubyte[][]
. Saya hanya mengolok-olok.Jika Anda menginginkan lebih banyak fleksibilitas daripada
BitConverter
, tetapi tidak ingin loop eksplisit gaya 1990-an yang kikuk, maka Anda dapat melakukannya:Atau, jika Anda menggunakan .NET 4.0:
(Yang terakhir dari komentar pada posting asli.)
sumber
Pendekatan lain berdasarkan tabel pencarian. Yang ini hanya menggunakan satu tabel pencarian untuk setiap byte, bukan tabel pencarian per gigitan.
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,
unsafe
saudara kandungnya:Atau jika Anda menganggap dapat langsung menulis ke string:
sumber
Span
dapat digunakan sekarang bukanunsafe
??Anda dapat menggunakan metode BitConverter.ToString:
Keluaran:
Informasi lebih lanjut: Metode BitConverter.ToString (Byte [])
sumber
Saya baru saja mengalami masalah yang sama hari ini, dan saya menemukan kode ini:
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).
sumber
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.ToByte
danString.Substring
jika Anda sedang terburu-buru ("Kode asli" di bawah), itu kombinasi terbaik jika Anda tidak ingin menerapkan kembaliConvert.ToByte
. Gunakan sesuatu yang lebih maju (lihat jawaban lain) yang tidak digunakanConvert.ToByte
jika Anda membutuhkan kinerja. Jangan tidak menggunakan apa-apa lagi selainString.Substring
dalam 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:
Revisi 4:
Revisi menghindari
String.Substring
dan menggunakanStringReader
gantinya. Alasan yang diberikan adalah:Nah, melihat kode referensi
String.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.Setiap heksadesimal
numeral
mewakili oktet tunggal menggunakan dua digit (simbol).Tapi, mengapa menelepon
StringReader.Read
dua kali? Panggil saja kelebihan kedua dan minta untuk membaca dua karakter dalam array dua karakter sekaligus; dan kurangi jumlah panggilan dua.Yang tersisa dengan Anda adalah pembaca string yang hanya menambahkan "nilai" adalah indeks paralel (internal
_pos
) yang bisa Anda nyatakan sendiri (sepertij
misalnya), 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 memanggilString.CopyTo
string input. Sisanya hanya biaya pembukuan untuk mempertahankan nilai-nilai yang tidak kita butuhkan.Jadi, lepaskan pembaca string, dan panggil
CopyTo
diri Anda sendiri; lebih sederhana, lebih jelas, dan lebih efisien.Apakah Anda benar-benar membutuhkan
j
indeks yang bertambah dalam langkah dua paraleli
? Tentu saja tidak, hanya kalikani
dengan dua (yang harus dioptimalkan oleh kompiler untuk penambahan).Seperti apa solusinya sekarang? Persis seperti di awal, hanya alih-alih menggunakan
String.Substring
untuk 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 kemudianString.Substring
juga akan dapat menghindarinya dalam kasus ini.Bahkan, jika Anda melihat
String.Substring
lagi, 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 digunakanCopyTo
secara langsung di sana untuk menghindari panggilan overhead.String.Substring
Metode manual
Kesimpulan? Jika Anda ingin menggunakan
Convert.ToByte(String, Int32)
(karena Anda tidak ingin menerapkan kembali fungsi itu sendiri), sepertinya tidak ada cara untuk mengalahkanString.Substring
; yang Anda lakukan hanyalah berputar-putar, menciptakan kembali roda (hanya dengan bahan yang kurang optimal).Perhatikan bahwa menggunakan
Convert.ToByte
danString.Substring
merupakan 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 menghindariString
).Saya menduga bahwa orang yang melaporkan kinerja yang lebih baik dengan "menghindari
String.Substring
" juga menghindariConvert.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?
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.
Selamat bersenang-senang! (Tapi optimalkan dengan moderasi.)
sumber
Lengkapi untuk menjawab dengan @CodesInChaos (metode terbalik)
Penjelasan:
& 0x0f
adalah untuk mendukung juga huruf kecilhi = 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;
yanghi = ch - 48
(ini karena0xffffffff & 7
).Untuk 'A' .. 'F' itu
hi = ch - 65 + 10;
(ini karena0x00000000 & 7
).Untuk 'a' .. 'f' kita harus berjumlah besar sehingga kita harus mengurangi 32 dari versi default dengan membuat beberapa bit
0
dengan 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...
).sumber
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:
Solusi saya menggunakan 1024 byte untuk tabel penyandian, dan 256 byte untuk decoding.
Decoding
Pengkodean
Perbandingan
* 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.
sumber
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.
sumber
hexString[i] &= ~0x20;
Mengapa membuatnya rumit? Ini sederhana di Visual Studio 2008:
C #:
VB:
sumber
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):
Baris base64 dan 'BitConverter'd' ada untuk menguji kebenarannya. Perhatikan bahwa keduanya sama.
Pelaksanaan:
Saya mencoba beberapa hal
unsafe
dan memindahkan urutan karakter-ke-gigitanif
ke 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.)
sumber
Versi aman:
Versi tidak aman Bagi mereka yang lebih suka kinerja dan tidak takut ketidakamanan. Tentang 35% lebih cepat ToHex dan 10% lebih cepat DariHex.
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).
sumber
Fungsi terbalik untuk kode Waleed Eissa (Hex String To Byte Array):
Waleed Eissa berfungsi dengan dukungan huruf kecil:
sumber
Metode ekstensi (penafian: kode yang sama sekali belum diuji, BTW ...):
dll. Gunakan salah satu dari tiga solusi Tomalak (dengan yang terakhir menjadi metode ekstensi pada string).
sumber
Dari pengembang Microsoft, konversi yang bagus dan sederhana:
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 :
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.
sumber
b.ToSting("X2")
.Dan untuk memasukkan ke dalam string SQL (jika Anda tidak menggunakan parameter perintah):
sumber
Source == null
atauSource.Length == 0
kita punya masalah pak!Dalam hal kecepatan, ini tampaknya lebih baik daripada apa pun di sini:
sumber
Saya tidak mendapatkan kode yang Anda sarankan untuk bekerja, Olipro.
hex[i] + hex[i+1]
ternyata mengembalikan sebuahint
.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.
sumber
Versi ByteArrayToHexViaByteManipulation ini bisa lebih cepat.
Dari laporan saya:
...
Dan saya pikir ini adalah optimasi:
sumber
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
StringBuilder
metode panggilan juga akan memakan waktu.Dikonversi dari kode Java.
sumber
Char[]
dan menggunakanChar
internal daripada ints ...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++
danoffset
bukannyaoffset
danoffset + 1
mungkin memberikan beberapa manfaat teoretis tetapi saya menduga kompiler menangani ini lebih baik daripada saya.Ini hanya dari atas kepala saya dan belum diuji atau dibandingkan.
sumber
Variasi lain untuk keragaman:
sumber
Tidak dioptimalkan untuk kecepatan, tetapi lebih banyak LINQy daripada sebagian besar jawaban (.NET 4.0):
sumber
Dua mashup yang melipat dua operasi menggigit menjadi satu.
Mungkin versi yang sangat efisien:
Versi linq-dengan-bit-peretasan dekaden:
Dan membalikkan:
sumber
Cara lain adalah dengan menggunakan
stackalloc
untuk mengurangi tekanan memori GC:sumber
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
foreach
untuk mengulangi dan penghitung lebih cepat daripada pengindeksan eksplisitfor
.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:
Berikut kodenya:
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.
sumber
Fungsi cepat lainnya ...
sumber