Bir bayt dizisini onaltılık bir dizeye ve tersi nasıl dönüştürürsünüz?


1371

Bir bayt dizisini onaltılık bir dizeye veya tam tersini nasıl dönüştürebilirsiniz?


8
Aşağıdaki kabul edilen yanıt, dizede korkunç miktarda dizeyi bayt dönüştürmeye ayırıyor gibi görünüyor. Bunun performansı nasıl etkilediğini merak ediyorum
Wim Coenen

9
SoapHexBinary sınıfı tam olarak düşündüğüm şeyi yapıyor.
Mykroft

Bana öyle geliyor ki 1 yazıda 2 soru sormak oldukça standart değil.
SandRock

Yanıtlar:


1353

Ya:

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

veya:

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

Bunu yapmanın daha fazla çeşidi vardır, örneğin burada .

Ters dönüşüm şöyle olur:

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

İle birlikte kullanmak Substringen iyi seçenektir Convert.ToByte. Daha fazla bilgi için bu cevaba bakınız . Daha iyi performansa ihtiyacınız varsa, Convert.ToBytedüşmeden önce kaçınmalısınız SubString.


24
SubString kullanıyorsunuz. Bu döngü korkunç miktarda dize nesnesi ayırmıyor mu?
Wim Coenen

30
Dürüst olmak gerekirse - performansı dramatik bir şekilde yıkana kadar, bunu görmezden gelmeye ve onunla ilgilenmek için Çalışma Zamanı'na ve GC'ye güvenmeye eğilimliydim.
Tomalak

87
Bir bayt iki öğüttüğünden, geçerli olarak bir bayt dizisini temsil eden herhangi bir onaltılık dize çift karakter sayısına sahip olmalıdır. Hiçbir yere 0 eklenmemelidir - bir tanesi eklemek, potansiyel olarak tehlikeli olabilecek geçersiz veriler hakkında bir varsayım yapar. Eğer herhangi bir şey varsa, hex dizesi tek sayıda karakter içeriyorsa StringToByteArray yöntemi bir FormatException oluşturmalıdır.
David Boike

7
@ 00jt F == 0F olduğunu varsaymalısınız. Ya 0F ile aynıdır veya giriş kırpılmıştır ve F aslında almadığınız bir şeyin başlangıcıdır. Bu varsayımları yapmak bağlamınıza bağlıdır, ancak genel amaçlı bir fonksiyonun, arama kodu için bu varsayımı yapmak yerine tek karakterleri geçersiz olarak reddetmesi gerektiğine inanıyorum.
David Boike

11
@DavidBoike Soru "muhtemelen kırpılmış akış değerleri nasıl ele alınır" ile ilgisi YOK vardı Bir String hakkında konuşmak. String myValue = 10.ToString ("X"); myValue "A" değil "0A" dır. Şimdi gidip o dizeyi baytlara okuyunuz, ayy kırdınız.
00jt

488

Performans analizi

Not: 2015-08-20 itibariyle yeni lider.

Çeşitli dönüşüm yöntemlerinin her birini bir kaba Stopwatchperformans testi, rastgele bir cümleyle (n = 61, 1000 yineleme) ve bir Project Gutenburg metniyle (n = 1,238,957, 150 yineleme) çalıştırdım. İşte sonuçlar, kabaca en hızlıdan en yavaşına. Tüm ölçümler kenelerdedir ( 10.000 keneler = 1 ms ) ve tüm göreli notlar [en yavaş] StringBuilderuygulama ile karşılaştırılır. Kullanılan kod için, aşağıya bakın veya şimdi bunu çalıştırmak için kodunu koruduğum test çerçevesi repo .

feragat

UYARI: Somut bir şey için bu istatistiklere güvenmeyin; bunlar sadece örnek verilerin örnek bir çalışmasıdır. Gerçekten birinci sınıf performansa ihtiyacınız varsa, lütfen bu yöntemleri üretim gereksinimlerinizi temsil eden bir ortamda kullanacağınız veri temsilcisi ile test edin.

Sonuçlar

Arama tabloları, bayt manipülasyonunda öncülük etti. Temel olarak, herhangi bir nibble veya baytın onaltılık olarak ne olacağını önceden hesaplamanın bir biçimi vardır. Sonra, verileri kopyalarken, hangi hex dizesinin olacağını görmek için bir sonraki bölüme bakmanız yeterlidir. Bu değer daha sonra elde edilen dize çıktısına bir şekilde eklenir. Uzun zamandır bazı geliştiriciler tarafından okunması daha zor olan bayt manipülasyonu, en iyi performans gösteren yaklaşımdı.

En iyi bahsiniz hala bazı temsili veriler bulmak ve bunu üretim benzeri bir ortamda denemek olacak. Farklı bellek kısıtlamalarınız varsa, daha hızlı ancak daha fazla bellek tüketen bir taneye daha az tahsis edilen bir yöntemi tercih edebilirsiniz.

Test Kodu

Kullandığım test koduyla oynamaktan çekinmeyin. Bir versiyon buraya dahil ancak klonlamak için çekinmeyin olan repo ve kendi yöntemlerini ekleyin. İlginç bir şey bulursanız veya kullandığı test çerçevesinin iyileştirilmesine yardımcı olmak istiyorsanız lütfen bir çekme isteği gönderin.

  1. Yeni statik yöntemi ( Func<byte[], string>) /Tests/ConvertByteArrayToHexString/Test.cs dosyasına ekleyin.
  2. Aynı yöntemin adını TestCandidates, aynı sınıftaki dönüş değerine ekleyin .
  3. GenerateTestInputAynı sınıftaki yorumları değiştirerek, istediğiniz cümle veya metin giriş sürümünü çalıştırdığınızdan emin olun .
  4. Çıktıya basın F5ve bekleyin (/ bin klasöründe bir HTML dökümü de oluşturulur).
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();
}

Güncelleme (2010-01-13)

Waleed'in analize cevabı eklendi. Oldukça hızlı.

Güncelleme (2011-10-05)

string.Concat Array.ConvertAllTamlık için varyant eklendi (.NET 4.0 gerektirir). string.JoinSürümü ile eşit .

Güncelleme (2012-02-05)

Test repou gibi daha fazla varyant içerir StringBuilder.Append(b.ToString("X2")). Hiçbiri sonuçları bozmaz. örneğin foreachdaha hızlıdır {IEnumerable}.Aggregate, ancak BitConverteryine de kazanır.

Güncelleme (2012-04-03)

Mykroft'un SoapHexBinaryüçüncü sırada yer alan analize yanıtı eklendi .

Güncelleme (2013-01-15)

Kodlar İnChaos'un ilk sırada yer alan bayt manipülasyon cevabı (büyük metin bloklarında büyük bir farkla).

Güncelleme (2013-05-23)

Nathan Moinvaziri'nin arama yanıtı ve Brian Lambert'ın blogundaki varyant eklendi. Her ikisi de oldukça hızlı, ancak kullandığım test makinesine liderlik etmiyor (AMD Phenom 9750).

Güncelleme (2014-07-31)

@ CodesInChaos'un yeni bayt tabanlı arama yanıtı eklendi. Hem cümle sınavlarına hem de tam metin sınavlarına öncülük etmiş gibi görünüyor.

Güncelleme (2015-08-20)

Bu yanıtın deposuna airbreather'ın optimizasyonları ve unsafevaryantı eklendi . Güvensiz oyunda oynamak istiyorsanız, hem kısa iplerde hem de büyük metinlerde önceki en iyi kazananlardan herhangi birinde büyük performans kazanımları elde edebilirsiniz.


Kodu Waleed'in cevabından test etmek ister misiniz? Çok hızlı gözüküyor. stackoverflow.com/questions/311165/…
Cristian Diaconescu

5
İstediğiniz şeyi kendi başınıza yapabilmeniz için kodun kullanılabilir olmasına rağmen, test kodunu Waleed yanıtını içerecek şekilde güncelledim. Bütün huysuzluk bir yana, çok daha hızlı.
patridge

2
@CodesInChaos Tamamlandı. Ve testlerimde biraz kazandı. Henüz en iyi yöntemlerden hiçbirini tam olarak anlamıyorum gibi davranmıyorum, ancak doğrudan etkileşimden kolayca gizleniyorlar.
patridge

6
Bu cevabın neyin "doğal" ya da sıradan olduğu sorusunu cevaplama niyeti yoktur. Amaç, insanlara bazı temel performans ölçütleri vermektir, çünkü bu dönüşümü yapmanız gerektiğinde, bunları çok yapma eğilimindesiniz. Birisinin ham hıza ihtiyacı varsa, ölçütleri istediği bilgisayar ortamında bazı uygun test verileriyle çalıştırır. Daha sonra, bu yöntemi bir daha asla uygulamasına bakmayacağınız bir uzantı yöntemine sıkıştırın (örn bytes.ToHexStringAtLudicrousSpeed().).
patridge

2
Sadece yüksek performanslı bir arama tablosu tabanlı uygulama üretti. Güvenli varyantı CPU'mdaki mevcut liderden yaklaşık% 30 daha hızlı. Güvenli olmayan varyantlar daha da hızlıdır. stackoverflow.com/a/24343727/445517
KodlarInChaos

244

SoapHexBinary adında tam olarak istediğinizi yapan bir sınıf var .

using System.Runtime.Remoting.Metadata.W3cXsd2001;

public static byte[] GetStringToBytes(string value)
{
    SoapHexBinary shb = SoapHexBinary.Parse(value);
    return shb.Value;
}

public static string GetBytesToString(byte[] value)
{
    SoapHexBinary shb = new SoapHexBinary(value);
    return shb.ToString();
}

35
SoapHexBinary .NET 1.0 sürümünden edinilebilir ve mscorlib biçimindedir. Komik bir isim alanı olmasına rağmen, sorunun tam olarak sorduğunu soruyor.
Sly Gryphon

4
Büyük bulmak! Diğer çözüm gibi, GetStringToBytes için tek bir dizeyi önde 0 ile doldurmanız gerekeceğini unutmayın.
Carter Medlin

Uygulama düşüncesini gördünüz mü? Kabul edilen cevabın daha iyi bir IMHO'su var.
mfloryan

6
Mono uygulamasını burada görmek ilginç: github.com/mono/mono/blob/master/mcs/class/corlib/…
Jeremy

1
SoapHexBinary .NET Core / .NET Standard'da desteklenmemektedir ...
juFo

141

Kripto kodu yazarken, çalışma zamanının verilere bağlı olmadığından emin olmak için veriye bağımlı dallardan ve tablo aramalarından kaçınmak yaygındır, çünkü veriye bağlı zamanlama yan kanal saldırılarına neden olabilir.

Aynı zamanda oldukça hızlı.

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


Bütün umutları terk et, buraya giren siz

Tuhaf bitlerle ilgili bir açıklama:

  1. bytes[i] >> 4bir baytın yüksek kırıntısını
    bytes[i] & 0xFçıkarır bir baytın düşük kırıntısını çıkarır
  2. b - 10
    olduğu < 0değerler için b < 10bir ondalık haneli olacak,
    olan >= 0değerler için b > 10bir mektup olacak, Aiçin F.
  3. i >> 31İşaretli bir 32 bit tam sayı üzerinde kullanmak , işaret uzantısı sayesinde işareti ayıklar. Olacak -1için i < 0ve 0için i >= 0.
  4. 2) ve 3) birleştiren, Şekil (b-10)>>31olacak 0harfler ve -1basamak için.
  5. Harfler durumuna baktığımızda, son summand olur 0ve b10 ila 15 aralığındadır. Bunu A(65) ila F(70) ile eşleştirmek istiyoruz , bu da 55 ( 'A'-10) eklemeyi ima ediyor .
  6. Rakamlar için duruma baktığımızda, son toplamı b0 ila 9 aralığından 0(48) ila 9(57) aralığına eşleyecek şekilde uyarlamak istiyoruz . Bu, -7 ( '0' - 55) olması gerektiği anlamına gelir .
    Şimdi sadece 7 ile çarpabiliriz. Fakat -1 tüm bitlerin 1 olduğu gösterildiğinden, ve yerine & -7beri kullanabiliriz .(0 & -7) == 0(-1 & -7) == -7

Dikkat edilmesi gereken bazı noktalar:

  • cÖlçümün hesaplanmasının idaha ucuz olduğunu gösterdiğinden , indekslemek için ikinci bir döngü değişkeni kullanmadım .
  • i < bytes.LengthDöngünün üst sınırı olarak kullanmak JITter'ın sınır kontrollerini ortadan kaldırmasına izin verir bytes[i], bu yüzden bu varyantı seçtim.
  • bBir int yapmak , byte ve byte arasında gereksiz dönüşümlere izin verir.

10
Ve hex stringhiç byte[] array?
AaA

15
Bu kara kara büyüyü çektikten sonra kaynağınızı doğru bir şekilde göstermek için +1. Hepsi Cthulhu'ya selam olsun.
Edward

4
String to byte [] ne olacak?
Syaiful Nizam Yahya

9
Güzel! Küçük harf çıktısına ihtiyaç duyanlar için ifade açıkça87 + b + (((b-10)>>31)&-39)
eXavier

2
@AaA byte[] arrayKelimenin tam anlamıyla bir dizi bayt dizisi anlamına gelen " " dediniz veya byte[][]. Sadece alay ediyordum.
CoolOppo

97

Daha fazla esneklik istiyorsanız BitConverter, ancak 1990'ların bu karmaşık açık döngülerini istemiyorsanız, şunları yapabilirsiniz:

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

Veya .NET 4.0 kullanıyorsanız:

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

(İkincisi, orijinal yayına yapılan bir yorumdan.)


21
Daha da kısa: String.Concat (Array.ConvertAll (bayt, x => x.ToString ("X2"))
Nestor

14
Daha da kısa: String.Concat (bytes.Select (b => b.ToString ("X2"))) [.NET4]
Allon Guralnek

14
Sorunun sadece yarısını cevaplıyor.
Sly Gryphon

1
İkincisinin neden .Net 4'e ihtiyacı var? String.Concat .Net 2.0 sürümündedir.
Polyfun

2
bu "90'ların tarzı" döngüler genellikle daha hızlıdır, ancak çoğu bağlamda önemli olmayacak kadar az miktardadır. Yine de
kayda

69

Başka bir arama tablosu tabanlı yaklaşım. Bu, bayt başına arama tablosu yerine her bayt için yalnızca bir arama tablosu kullanır.

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

Ben de kullanarak bu varyantlarını test ushort, struct{char X1, X2}, struct{byte X1, X2}arama tablosundaki.

Derleme hedefine (x86, X64) bağlı olarak, bunlar ya yaklaşık olarak aynı performansa sahipti ya da bu varyanttan biraz daha yavaştı.


Ve daha da yüksek performans için, unsafekardeşi:

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

Veya doğrudan dizeye yazmanın kabul edilebilir olduğunu düşünüyorsanız:

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

Güvenli olmayan sürümde arama tablosu oluşturmak neden önceden hesaplanmış baytın nibble'larını değiştiriyor? Endianitenin sadece birden çok bayttan oluşan varlıkların sırasını değiştirdiğini düşündüm.
Raif Atef

@RaifAtef Burada önemli olan nibble'ların sırası değil. Ancak 32 bit tam sayıdaki 16 bit kelimelerin sırası. Ama aynı kod endianness ne olursa olsun çalışabilir böylece yeniden yazma düşünüyorum.
CodesInChaos

Kodu yeniden okuma, bunu daha sonra bir uint * için char * döküm ve (onaltılık char oluştururken) atadığınızda, çalışma zamanı / CPU baytları çevirmek çünkü bunu yaptığınızı düşünüyorum (uint tedavi edilmediği için 2 ayrı 16 bit karakterle aynı), böylece telafi etmek için bunları önceden çevirin. Haklı mıyım? Endianness kafa karıştırıcı :-).
Raif Atef

4
Bu sadece sorunun yarısını cevaplıyor ... Altıgen dizeden bayta ne dersiniz?
Narvalex

3
@CodesInChaos Acaba Spanbunun yerine şimdi kullanılabilir mi acaba unsafe??
Konrad

64

BitConverter.ToString yöntemini kullanabilirsiniz:

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

Çıktı:

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

Daha fazla bilgi: BitConverter.ToString Yöntemi (Byte [])


14
Sorunun sadece yarısını cevaplıyor.
Sly Gryphon

3
Cevabın ikinci kısmı nerede?
Sawan

56

Bugün sadece aynı sorunla karşılaştım ve bu kodla karşılaştım:

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

Kaynak: Forum post byte [] Hex String dizisi dizisi (PZahra'nın gönderisine bakınız). 0x önekini kaldırmak için kodu biraz değiştirdim.

Kod için bazı performans testleri yaptım ve BitConverter.ToString () (patridge yazısına göre en hızlı) kullanmaktan neredeyse sekiz kat daha hızlıydı.


bunun en az hafızayı kullandığından bahsetmiyorum. Ara dizeler oluşturulmadı.
Chochos

8
Sorunun sadece yarısını cevaplıyor.
Sly Gryphon

Bu harika çünkü NETMF de dahil olmak üzere NET'in herhangi bir sürümü üzerinde çalışıyor. Kazanan!
Jonesome

1
Kabul edilen cevap, sorunun diğer yarısını temsil eden 2 mükemmel HexToByteArray yöntemi sağlar. Waleed'in çözümü, süreçte çok sayıda dizge oluşturmadan bunun nasıl yapılacağı sorusunu cevaplıyor.
Brendten Eickstaedt

Yeni dize (c) kopyalayıp yeniden tahsis ediyor mu veya char [] 'i ne zaman sarabileceğini bilmek yeterince akıllı mı?
jjxtra

19

Bu bir cevaptır revizyon 4 arasında Tomalak son derece popüler cevap (ve sonraki düzenlemeleri).

Bu düzenlemenin yanlış olduğunu ve neden geri alınabileceğini açıklayacağım. Yol boyunca, bazı iç öğeler hakkında bir iki şey öğrenebilir ve erken optimizasyonun gerçekte ne olduğunu ve sizi nasıl ısırdığını gösteren başka bir örnek görebilirsiniz.

tl; dr: Sadece kullanın Convert.ToByteve String.Substringaceleniz varsa (aşağıdaki "Orijinal kod"), yeniden uygulamak istemiyorsanız en iyi kombinasyon budur Convert.ToByte. Performansa ihtiyacınızConvert.ToByte varsa kullanmayan daha gelişmiş bir şey kullanın (diğer yanıtlara bakın) . Do not dışında başka bir şey kullanmak ile birlikte birisi bu cevabın yorumlarında bu konuda söylenecek bir şey ilginç olmadıkça,.String.SubstringConvert.ToByte

Uyarı: Bu cevap eskimiş hale gelebilir eğer bir Convert.ToByte(char[], Int32)aşırı yük çerçevesinde uygulanmaktadır. Bunun yakında gerçekleşmesi olası değildir.

Genel bir kural olarak, "zamanından önce optimizasyon yapma" demekten pek hoşlanmıyorum, çünkü "erken" ne zaman kimse bilmiyor. Optimizasyon yapıp yapmayacağınıza karar verirken göz önünde bulundurmanız gereken tek şey: "Optimizasyon yaklaşımlarını doğru bir şekilde araştıracak zamanım ve kaynakım var mı?". Bunu yapmazsanız proje daha olgun kadar, o zaman çok erken, bekleyecek ya da performansa ihtiyacınız kadar (gerçek bir ihtiyaç varsa, o zaman olacaktır yapmak zamanı). Bu arada, bunun yerine çalışabilecek en basit şeyi yapın.

Orijinal kod:

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

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

Düzeltme bundan kaçınır String.Substringve StringReaderbunun yerine kullanır . Verilen sebep:

Düzenleme: Tek geçişli ayrıştırıcı kullanarak uzun dizelerin performansını aşağıdaki gibi artırabilirsiniz:

Peki, referans kodunaString.Substring bakıldığında, zaten "tek geçişli" dir; ve neden olmasın? Vekil çiftlerde değil, bayt düzeyinde çalışır.

Ancak yeni bir dize ayırır, ancak Convert.ToByteyine de geçmek için bir tane ayırmanız gerekir. Ayrıca, revizyonda sağlanan çözüm her yinelemede başka bir nesne tahsis eder (iki karakter dizisi); bu ayırmayı güvenli bir şekilde döngü dışına koyabilir ve bundan kaçınmak için diziyi yeniden kullanabilirsiniz.

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

Her onaltılı numeral, iki basamak (sembol) kullanan tek bir sekizliyi temsil eder.

Ama sonra neden StringReader.Readiki kez aramalısınız ? Sadece ikinci aşırı yüklenmesini çağırın ve iki karakter dizisindeki iki karakteri aynı anda okumasını isteyin; ve arama miktarını iki azaltabilirsiniz.

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

Geriye kalan tek şey, yalnızca "değer" eklenen, _poskendinizi ( jörneğin,) bildirebileceğiniz paralel bir dizin (dahili ), gereksiz uzunluk değişkeni (dahili _length) ve girdiye gereksiz referans olan bir dize okuyucusudur. dize (dahili _s). Başka bir deyişle, işe yaramaz.

Read"Okuduğunu" merak ediyorsanız , koda bakmanız yeterlidir , tek yapmanız String.CopyTogereken girdi dizgisini aramaktır. Gerisi ihtiyacımız olmayan değerleri korumak için sadece kitap tutma yüküdür.

Yani, dize okuyucuyu zaten kaldırın ve CopyTokendinizi arayın ; daha basit, daha net ve daha verimlidir.

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

Gerçekten jparalel iki basamaklı bir dizine ihtiyacınız var imı? Tabii ki değil, sadece iiki ile çarpın (derleyici bir ek için optimize edebilmelidir).

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

Çözüm şimdi neye benziyor? O başındaydı Tam gibi sadece kullanmak yerine String.Substringdize tahsis ve ona veri kopyalamak, size karşı onaltılık rakamlarını kopyalamak hangi bir aracı dizi kullanıyorsanız, o zaman kendinizi dize tahsis ve veri kopyalamak tekrar dan dizi ve dizeye (dizgi yapıcısına ilettiğinizde). Dize zaten stajyer havuzundaysa ikinci kopya optimize edilebilir, ancak daha sonra String.Substringbu gibi durumlarda önlenebilir.

Aslında, String.Substringtekrar bakarsanız , dizeyi normalde yapabileceğinizden daha hızlı tahsis etmek için dizelerin nasıl oluşturulduğuna dair bazı düşük düzeyli dahili bilgiler kullandığını görürsünüz ve CopyToönlemek için doğrudan orada kullanılan kodun tamamını sıralar çağrı yükü.

String.Substring

  • En kötü durum: Bir hızlı ayırma, bir hızlı kopya.
  • En iyi durum: Tahsis yok, kopya yok.

Manuel yöntem

  • En kötü durum: İki normal ayırma, bir normal kopya, bir hızlı kopya.
  • En iyi durum: Bir normal ayırma, bir normal kopya.

Sonuç? Kullanmak istiyorsanızConvert.ToByte(String, Int32) (çünkü bu işlevselliği kendiniz yeniden uygulamak istemiyorsanız), yenmenin bir yolu yok gibi görünüyor String.Substring; tek yaptığınız tekerleği yeniden icat ederek daireler çizmek (sadece en uygun olmayan malzemelerle).

Aşırı performansa ihtiyacınız yoksa Convert.ToByteve String.Substringöğelerini kullanmanın tamamen geçerli bir seçim olduğunu unutmayın. Unutmayın: Bir alternatifi yalnızca bunun nasıl düzgün çalıştığını araştıracak zamanınız ve kaynaklarınız varsa seçin.

Eğer Convert.ToByte(char[], Int32)olsaydı, işler elbette farklı olurdu (yukarıda tarif ettiğim şeyi yapmak ve tamamen kaçınmak mümkün olurdu String).

Ben String.Substringde "kaçınarak " daha iyi performans rapor insanlar kaçının şüpheli, Convert.ToByte(String, Int32)yine de performans gerekiyorsa gerçekten yapmanız gerekir. Bunu yapmak için tüm farklı yaklaşımları keşfetmek için sayısız cevaplara bakın.

Yasal Uyarı: Referans kaynağının güncel olduğunu doğrulamak için çerçevenin en son sürümünü deşifre etmedim, sanırım öyle.

Şimdi, her şey iyi ve mantıklı geliyor, umarım şimdiye kadar ulaşmayı başarabilirseniz bile açıktır. Ama doğru mu?

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

Evet!

Tezgah çerçevesi için Partridge sahne, kesmek kolaydır. Kullanılan girdi, 100.000 bayt uzunluğunda bir dize oluşturmak için 5000 kez tekrarlanan aşağıdaki SHA-1 karmasıdır.

209113288F93A9AB8E474EA78D899AFDBB874355

İyi eğlenceler! (Ancak denetleme ile optimize edin.)


hata: {"Tanınabilir rakamlar bulunamadı."}
Priya Jagtap

17

@CodesInChaos tarafından yanıtlamanın tamamlayıcısı (ters yöntem)

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

Açıklama:

& 0x0f küçük harfleri de desteklemektir

hi = hi + 10 + ((hi >> 31) & 7); aynıdır:

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

'0' için .. '9' da aynıdır hi = ch - 65 + 10 + 7;olan hi = ch - 48(bu çünkü 0xffffffff & 7).

'A' .. 'F' için hi = ch - 65 + 10;(bu yüzden 0x00000000 & 7).

'A' .. 'f' için büyük sayılar kullanmalıyız, bu yüzden 0kullanarak bazı bitler yaparak 32'yi varsayılan sürümden çıkarmalıyız & 0x0f.

65 için kod 'A'

48 koddur '0'

Şekil 7, ASCII tablosu ( ) arasındaki '9've 'A'içindeki harflerin sayısıdır ...456789:;<=>?@ABCD....


16

Bu sorun, bir arama tablosu kullanılarak da çözülebilir. Bu, hem kodlayıcı hem de kod çözücü için az miktarda statik bellek gerektirir. Ancak bu yöntem hızlı olacaktır:

  • Kodlayıcı tablosu 512 bayt veya 1024 bayt (hem büyük hem de küçük harf gerekiyorsa, boyutun iki katı)
  • Kod çözücü tablosu 256 bayt veya 64 KiB (tek karakterli arama veya çift karakterli arama)

Benim çözüm kodlama tablosu için 1024 bayt ve kod çözme için 256 bayt kullanır.

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

Kodlama

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

karşılaştırma

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

* bu çözüm

Not

Kod çözme sırasında IOException ve IndexOutOfRangeException oluşabilir (bir karakterin değeri çok yüksekse> 256). Akımların veya dizilerin kodunun çözülmesi / kodlanması için yöntemler uygulanmalıdır, bu sadece bir kavram kanıtıdır.


2
CLR'de kod çalıştırdığınızda 256 bayt bellek kullanımı ihmal edilebilir.
dolmen

9

Bu harika bir gönderi. Waleed'in çözümünü seviyorum. Patridge testi ile çalıştırmadım ama oldukça hızlı görünüyor. Ben de bir hex dizisi bir bayt dizisine dönüştürerek, ters işlem gerekli, bu yüzden ben Waleed çözümünün bir ters olarak yazdı. Tomalak'ın orijinal çözümünden daha hızlı olup olmadığından emin değilim. Yine, patridge testi ile ters işlemi de yapmadım.

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

Bu kod, hex dizesinin büyük harfli alfa karakterleri kullandığını varsayar ve hex dizesi küçük harfli alfa kullanırsa patlar. Güvenli olması için giriş dizesinde "büyük harf" dönüşümü yapmak isteyebilir.
Marc Novakowski

Bu zekice bir gözlem Marc. Kod, Waleed'in çözümünü tersine çevirmek için yazılmıştır. ToUpper çağrısı algoritmayı bazılarını yavaşlatır, ancak küçük harfli alfa karakterlerini işlemesine izin verir.
Chris F

3
Convert.ToByte (topChar + bottomChar) (bayt) (topChar + bottomChar) olarak yazılabilir
Amir Rezaei

Her iki vakayı da büyük bir performans cezası olmadan ele almak için,hexString[i] &= ~0x20;
Ben Voigt

9

Neden karmaşık? Visual Studio 2008'de bu basittir:

C #:

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

VB:

Dim hex As String = BitConverter.ToString(YourByteArray).Replace("-", "")

2
Bunun nedeni, yüksek performanslı bir çözüme ihtiyaç duyduğunuzda performanstır. :)
Ricky

7

Burada birçok cevaba kazık değil, ama oldukça uygun (~ 4.5x kabul edilenden daha iyi), hex dize ayrıştırıcı basit bir uygulama bulundu. İlk olarak, testlerimden çıktı (ilk parti benim uygulamam):

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

Base64 ve 'BitConverter'd' hatları doğruluğu test etmek için orada. Eşit olduklarına dikkat edin.

Hayata geçirme:

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

Bazı şeyleri denedim unsafeve (açıkça gereksiz) karakter-nibble ifdizisini başka bir yönteme taşıdım , ancak bu en hızlıydı.

(Sorunun yarısını cevapladığını kabul ediyorum. String-> byte [] dönüşümünün yeterince temsil edilmediğini, byte [] -> string açısının iyi kapandığını hissettim. Bu nedenle, bu cevap.)


1
Knuth'un takipçileri için: Bunu yaptım çünkü her birkaç dakikada bir birkaç bin altıgen dizeyi ayrıştırmam gerekiyor, bu yüzden olabildiğince hızlı olması (iç döngüde olduğu gibi) önemlidir. Bu kadar çok sayıda ayrışmanın gerçekleşmemesi durumunda Tomalak'ın çözümü belirgin şekilde daha yavaş değildir.
Ben Mosher

5

Güvenli sürümler:

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

Güvensiz versiyonlar Performansı tercih eden ve güvensizlikten korkmayanlar için. Yaklaşık% 35 daha hızlı ToHex ve% 10 daha hızlı FromHex.

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 Dönüştürme işlevi çağrılan her yanlış olduğunda alfabeyi başlatan karşılaştırma testi için, alfabe const (string için) veya statik salt okunur (char []) olmalıdır. Daha sonra byte [] öğesinin alfabeye dayalı olarak dönüştürülmesi, bayt işleme sürümleri kadar hızlı olur.

Ve tabii ki test Release (optimizasyon ile) ve "JIT optimizasyonunu bastır" kapalı hata ayıklama seçeneği ile derlenmelidir (kod hata ayıklanabilir olması gerekiyorsa "Sadece Kodumu Etkinleştir" için de geçerlidir).


5

Waleed Eissa kodu (Hex String - Byte Array) için ters fonksiyon:

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

Küçük harf destekli Waleed Eissa işlevi:

    public static string BytesToHex(this byte[] barray, bool toLowerCase = true)
    {
        byte addByte = 0x37;
        if (toLowerCase) addByte = 0x57;
        char[] c = new char[barray.Length * 2];
        byte b;
        for (int i = 0; i < barray.Length; ++i)
        {
            b = ((byte)(barray[i] >> 4));
            c[i * 2] = (char)(b > 9 ? b + addByte : b + 0x30);
            b = ((byte)(barray[i] & 0xF));
            c[i * 2 + 1] = (char)(b > 9 ? b + addByte : b + 0x30);
        }

        return new string(c);
    }

4

Genişletme yöntemleri (sorumluluk reddi: tamamen denenmemiş kod, 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();
    }
}

vb. Tomalak'ın üç çözümünden birini kullanın (sonuncusu bir dizgide bir uzantı yöntemi).


Böyle bir soru sormadan önce muhtemelen kodu test etmelisiniz.
jww

3

Microsoft'un geliştiricilerinden, güzel ve basit bir dönüşüm:

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

Yukarıdakiler temiz ve kompakt olsa da, performans bağımlıları numaralandırıcılar kullanarak çığlık atacak. Tomalak'ın orijinal cevabının geliştirilmiş bir versiyonuyla en yüksek performansı elde edebilirsiniz :

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

Bu, şimdiye kadar burada gördüğüm tüm rutinlerin en hızlısı. Sadece benim sözüme güvenmeyin ... performans her rutini test edin ve CIL kodunu kendiniz kontrol edin.


2
Yineleyici, bu kodun ana sorunu değildir. Kıyaslama yapmalısınız b.ToSting("X2").
dolmen

2

Ve bir SQL dizesine eklemek için (komut parametrelerini kullanmıyorsanız):

public static String ByteArrayToSQLHexString(byte[] Source)
{
    return = "0x" + BitConverter.ToString(Source).Replace("-", "");
}

eğer Source == nullyoksa Source.Length == 0bir sorun var efendim!
Andrei Krasutski

2

Hız açısından, buradaki her şeyden daha iyi gibi görünüyor:

  public static string ToHexString(byte[] data) {
    byte b;
    int i, j, k;
    int l = data.Length;
    char[] r = new char[l * 2];
    for (i = 0, j = 0; i < l; ++i) {
      b = data[i];
      k = b >> 4;
      r[j++] = (char)(k > 9 ? k + 0x37 : k + 0x30);
      k = b & 15;
      r[j++] = (char)(k > 9 ? k + 0x37 : k + 0x30);
    }
    return new string(r);
  }

2

Çalışmayı önerdiğin kodu alamadım, Olipro. hex[i] + hex[i+1]görünüşe göre bir döndü int.

Ancak, Waleeds kodundan bazı ipuçları alarak ve bunu birlikte çekiçleyerek bazı başarılar elde ettim. Cehennem kadar çirkin ama testlerime göre (patenler test mekanizması kullanılarak) diğerlerine kıyasla 1/3 oranında çalışıyor ve çalışıyor gibi görünüyor. Giriş boyutuna bağlı olarak. İlk önce 0-9'u ayırmak için?: S arasında geçiş yapmak, harflerden daha fazla sayı olduğundan, muhtemelen biraz daha hızlı sonuç verir.

public static byte[] StringToByteArray2(string hex)
{
    byte[] bytes = new byte[hex.Length/2];
    int bl = bytes.Length;
    for (int i = 0; i < bl; ++i)
    {
        bytes[i] = (byte)((hex[2 * i] > 'F' ? hex[2 * i] - 0x57 : hex[2 * i] > '9' ? hex[2 * i] - 0x37 : hex[2 * i] - 0x30) << 4);
        bytes[i] |= (byte)(hex[2 * i + 1] > 'F' ? hex[2 * i + 1] - 0x57 : hex[2 * i + 1] > '9' ? hex[2 * i + 1] - 0x37 : hex[2 * i + 1] - 0x30);
    }
    return bytes;
}

2

ByteArrayToHexViaByteManipulation'ın bu sürümü daha hızlı olabilir.

Raporlarımdan:

  • ByteArrayToHexViaByteManipulation3: 1,68 ortalama keneler (1000'den fazla koşu), 17,5X
  • ByteArrayToHexViaByteManipulation2: 1,73 ortalama keneler (1000'den fazla çalışma), 16,9X
  • ByteArrayToHexViaByteMipipülasyon: 2,90 ortalama keneler (1000'den fazla çalışma), 10,1X
  • ByteArrayToHexViaLookupAndShift: 3,22 ortalama keneler (1000'den fazla koşu), 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);
    }

Ve bence bu bir optimizasyon:

    static private readonly char[] hexAlphabet = new char[]
        {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
    static string ByteArrayToHexViaByteManipulation4(byte[] bytes)
    {
        char[] c = new char[bytes.Length * 2];
        for (int i = 0, ptr = 0; i < bytes.Length; i++, ptr += 2)
        {
            byte b = bytes[i];
            c[ptr] = hexAlphabet[b >> 4];
            c[ptr + 1] = hexAlphabet[b & 0xF];
        }
        return new string(c);
    }

2

Onaltılık kodları çözmek için bit-kemancı kullanan bir cevabım olduğu için bu bit kıpır kıpır yarışmaya gireceğim . Arama dizileri de StringBuilderzaman alacağından , karakter dizilerinin kullanılmasının daha da hızlı olabileceğini unutmayın .

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

Java kodundan dönüştürüldü.


Hmm, bunu gerçekten optimize etmeli Char[]ve Charints yerine dahili olarak kullanmalıyım ...
Maarten Bodewes

C # için, döngü dışarısı yerine değişkenlerin kullanıldıkları yerde başlatılması derleyicinin en iyi duruma getirilmesi için muhtemelen tercih edilir. Her iki şekilde de eşdeğer bir performans elde ediyorum.
Peteter

2

Performans için drphrozens çözeltisi ile giderdim. Kod çözücü için küçük bir optimizasyon, her iki karakter için de "<< 4" den kurtulmak için bir tablo kullanmak olabilir.

Açıkça, iki yöntem çağrısı maliyetlidir. Giriş veya çıkış verilerinde bir tür kontrol yapılırsa (CRC, sağlama toplamı veya herhangi bir if (b == 255)...şey olabilir) atlanabilir ve böylece yöntem tamamen çağrılır.

Kullanılması offset++ve offsetyerine offsetve offset + 1bazı teorik fayda verebilir ama bu daha iyi benden daha derleyici kolları şüpheli.

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

Bu sadece başımın üstünde ve test edilmedi veya kıyaslanamadı.


1

Çeşitlilik için bir başka varyasyon:

public static byte[] FromHexString(string src)
{
    if (String.IsNullOrEmpty(src))
        return null;

    int index = src.Length;
    int sz = index / 2;
    if (sz <= 0)
        return null;

    byte[] rc = new byte[sz];

    while (--sz >= 0)
    {
        char lo = src[--index];
        char hi = src[--index];

        rc[sz] = (byte)(
            (
                (hi >= '0' && hi <= '9') ? hi - '0' :
                (hi >= 'a' && hi <= 'f') ? hi - 'a' + 10 :
                (hi >= 'A' && hi <= 'F') ? hi - 'A' + 10 :
                0
            )
            << 4 | 
            (
                (lo >= '0' && lo <= '9') ? lo - '0' :
                (lo >= 'a' && lo <= 'f') ? lo - 'a' + 10 :
                (lo >= 'A' && lo <= 'F') ? lo - 'A' + 10 :
                0
            )
        );
    }

    return rc;          
}

1

Hız için optimize edilmemiştir, ancak çoğu yanıttan daha fazla LINQy (.NET 4.0):

<Extension()>
Public Function FromHexToByteArray(hex As String) As Byte()
    hex = If(hex, String.Empty)
    If hex.Length Mod 2 = 1 Then hex = "0" & hex
    Return Enumerable.Range(0, hex.Length \ 2).Select(Function(i) Convert.ToByte(hex.Substring(i * 2, 2), 16)).ToArray
End Function

<Extension()>
Public Function ToHexString(bytes As IEnumerable(Of Byte)) As String
    Return String.Concat(bytes.Select(Function(b) b.ToString("X2")))
End Function

1

İki kemirme işlemini bire katlayan iki mashup.

Muhtemelen oldukça verimli versiyon:

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

Çökmüş linq-bit-hack versiyonu:

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

Ve tersi:

public static byte[] HexStringToByteArray( string s )
{
    byte[] ab = new byte[s.Length>>1];
    for( int i = 0; i < s.Length; i++ )
    {
        int b = s[i];
        b = (b - '0') + ((('9' - b)>>31)&-7);
        ab[i>>1] |= (byte)(b << 4*((i&1)^1));
    }
    return ab;
}

1
HexStringToByteArray ("09") kötü olan 0x02 döndürür
CoperNick

1

Başka bir yol stackallocGC bellek basıncını düşürmek için kullanmaktır :

static string ByteToHexBitFiddle(byte[] bytes)
{
        var c = stackalloc char[bytes.Length * 2 + 1];
        int b; 
        for (int i = 0; i < bytes.Length; ++i)
        {
            b = bytes[i] >> 4;
            c[i * 2] = (char)(55 + b + (((b - 10) >> 31) & -7));
            b = bytes[i] & 0xF;
            c[i * 2 + 1] = (char)(55 + b + (((b - 10) >> 31) & -7));
        }
        c[bytes.Length * 2 ] = '\0';
        return new string(c);
}

1

İşte benim çekimim. Dize ve bayt genişletmek için bir çift uzantı sınıfları oluşturdum. Büyük dosya testinde, performans Bayt Manipülasyon 2 ile karşılaştırılabilir.

ToHexString için aşağıdaki kod, arama ve kaydırma algoritmasının optimize edilmiş bir uygulamasıdır. Behrooz'unkiyle neredeyse aynı, ancak foreachyinelemek için bir kullanarak ve bir sayaç açıkça endekslemeden daha hızlıfor .

Makinemde Byte Manipulation 2'nin arkasında 2. sırada geliyor ve çok okunabilir bir kod. Aşağıdaki test sonuçları da ilgi çekicidir:

ToHexStringCharArrayWithCharArrayLookup: 41.589.69 ortalama keneler (1000'den fazla koşu), 1.5X ToHexStringCharArrayWithStringLookup: 50.764.06 ortalama keneler (1000'den fazla koşu), 1.2X ToHexStringSharingBuilderWu

Yukarıdaki sonuçlara dayanarak, şu sonuca varmak güvenli görünüyor:

  1. Bir karakter dizisine karşı arama yapmak için bir dizeye indeksleme cezaları büyük dosya testinde önemlidir.
  2. Dizeyi oluşturmak için bilinen kapasiteye sahip bir karakter dizisine karşı bilinen kapasiteye sahip bir StringBuilder kullanmanın cezaları daha da önemlidir.

İşte kod:

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

Kodumu @ patridge'in makinemdeki test projesine koyduğumda aldığım test sonuçları aşağıda. Ayrıca onaltılık bir bayt dizisine dönüştürmek için bir test ekledim. Kodumu kullanan test çalıştırmaları ByteArrayToHexViaOptfiedLookupAndShift ve HexToByteArrayViaByteManipulation'dır. HexToByteArrayViaConvertToByte XXXX'ten alınmıştır. HexToByteArrayViaSoapHexBinary @ Mykroft'un cevabından biridir.

Intel Pentium III Xeon işlemci

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

Bayt dizisini onaltılık dize temsiline dönüştürme


ByteArrayToHexViaByteManipulation2: 39,366,64 ortalama keneler (1000'den fazla çalışma), 22,4X

ByteArrayToHexViaOptfiedLookupAndShift: 41.588.64 ortalama keneler (1000'den fazla çalışma), 21.2X

ByteArrayToHexViaLookup: 55.509.56 ortalama keneler (1000'den fazla koşu), 15.9X

ByteArrayToHexViaByteMipipülasyon: 65.349.12 ortalama keneler (1000'den fazla çalışma), 13.5X

ByteArrayToHexViaLookupAndShift: 86.926,87 ortalama keneler (1000'den fazla çalışma), 10.2X

ByteArrayToHexStringViaBitConverter: 139.353,73 ortalama keneler (1000'den fazla çalışma), 6.3X

ByteArrayToHexViaSoapHexBinary: 314.598,77 ortalama keneler (1000'den fazla çalışma), 2.8X

ByteArrayToHexStringViaStringBuilderForEachByteToString: 344,264,63 ortalama keneler (1000'den fazla çalışma), 2.6X

ByteArrayToHexStringViaStringBuilderAggregateByteToString: 382,623,44 ortalama keneler (1000'den fazla çalışma), 2.3X

ByteArrayToHexStringViaStringBuilderForEachAppendFormat: 818,111,95 ortalama keneler (1000'den fazla çalışma), 1.1X

ByteArrayToHexStringViaStringConcatArrayConvertAll: 839,244.84 ortalama keneler (1000'den fazla çalışma), 1.1X

ByteArrayToHexStringViaStringBuilderAggregateAppendFormat: 867.303.98 ortalama keneler (1000'den fazla çalışma), 1.0X

ByteArrayToHexStringViaStringJoinArrayConvertAll: 882,710,28 ortalama keneler (1000'den fazla çalışma), 1.0X



1

Başka bir hızlı fonksiyon ...

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;
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.