c# - दशमलव - आप एक बाइट सरणी को हेक्साडेसिमल स्ट्रिंग में कैसे परिवर्तित करते हैं, और इसके विपरीत?




हेक्साडेसीमल नंबर को प्रयोग करने के लाभ (20)

क्षमता का परिक्षण

नोट: 2015-08-20 के रूप में नए नेता।

मैंने कुछ क्रूड Stopwatch प्रदर्शन परीक्षण, एक यादृच्छिक वाक्य (एन = 61, 1000 पुनरावृत्तियों) के साथ एक रन और एक परियोजना गुटेनबर्ग टेक्स्ट (एन = 1,238,957, 150 पुनरावृत्तियों) के साथ एक रन के माध्यम से विभिन्न रूपांतरण विधियों में से प्रत्येक भाग लिया। यहां परिणाम हैं, मोटे तौर पर सबसे तेज़ से सबसे धीमे। सभी माप टिक में हैं ( 10,000 टिक = 1 एमएस ) और सभी सापेक्ष नोटों की तुलना [धीमी] StringBuilder कार्यान्वयन से की जाती है। इस्तेमाल किए गए कोड के लिए, नीचे देखें या टेस्ट फ्रेमवर्क रेपो जहां मैं इसे चलाने के लिए कोड को बनाए रखता हूं।

अस्वीकरण

चेतावनी: किसी भी ठोस के लिए इन आंकड़ों पर भरोसा न करें; वे नमूना डेटा का एक नमूना रन हैं। यदि आपको वास्तव में शीर्ष प्रदर्शन की आवश्यकता है, तो कृपया अपने उत्पादन आवश्यकताओं के पर्यावरण प्रतिनिधि में इन विधियों का परीक्षण करें जो आप उपयोग करेंगे।

परिणाम

  • बाइट असुर द्वारा लुकअप (कोड्सइनकोस के माध्यम से) ( airbreather द्वारा टेस्ट रेपो में जोड़ा गया)
    • पाठ: 4,727.85 (105.2 एक्स)
    • वाक्य: 0.28 (99.7 एक्स)
  • बाइट द्वारा लुकअप (CodeInChaos के माध्यम से)
    • पाठ: 10,853.9 6 (45.8 एक्स तेज)
    • वाक्य: 0.65 (42.7 एक्स तेज)
  • बाइट मैनिपुलेशन 2 (कोड्सइनकोस के माध्यम से)
    • पाठ: 12, 9 67.6 9 (38.4 एक्स तेज)
    • वाक्य: 0.73 (37.9एक्स तेज)
  • बाइट मैनिपुलेशन (वालीड एसा के माध्यम से)
    • पाठ: 16,856.64 (2 9 .5 एक्स तेज)
    • वाक्य: 0.70 (3 9 .5 एक्स तेज)
  • लुकअप / शिफ्ट (नाथन मोइन्वाज़ीरी के माध्यम से)
    • पाठ: 23,201.23 (21.4 एक्स तेज)
    • वाक्य: 1.24 (22.3 एक्स तेज)
  • निबल द्वारा लुकअप (ब्रायन लैम्बर्ट के माध्यम से)
    • टेक्स्ट: 23,879.41 (20.8 एक्स तेज)
    • वाक्य: 1.15 (23.9एक्स तेज)
  • BitConverter ( BitConverter माध्यम से)
    • टेक्स्ट: 113,26 9.34 (4.4 एक्स तेज)
    • वाक्य: 9.98 (2.8 एक्स तेज)
  • {SoapHexBinary}.ToString (Mykroft के माध्यम से)
    • पाठ: 178,601.3 9 (2.8 एक्स तेज)
    • वाक्य: 10.68 (2.6X तेज)
  • {byte}.ToString("X2") ( foreach का उपयोग) (विल डीन के जवाब से व्युत्पन्न)
    • टेक्स्ट: 308,805.38 (2.4 एक्स तेज)
    • वाक्य: 16.8 9 (2.4 एक्स तेज)
  • {byte}.ToString("X2") ( {IEnumerable}.Aggregate का उपयोग करके {IEnumerable}.Aggregate , {IEnumerable}.Aggregate आवश्यकता है) (मार्क के माध्यम से)
    • पाठ: 352,828.20 (2.1X तेज)
    • वाक्य: 16.87 (2.4 एक्स तेज)
  • Array.ConvertAll ( string.Join का उपयोग string.Join ) (विल डीन के माध्यम से)
    • पाठ: 675,451.57 (1.1 एक्स तेज)
    • वाक्य: 17.9 5 (2.2 एक्स तेज)
  • Array.ConvertAll ( string.Concat का उपयोग string.Concat , .NET 4.0 की आवश्यकता है) (विल डीन के माध्यम से)
    • पाठ: 752,078.70 (1.0 एक्स तेज)
    • वाक्य: 18.28 (2.2 एक्स तेज)
  • {StringBuilder}.AppendFormat ( foreach का उपयोग करके) (Tomalak के माध्यम से)
    • पाठ: 672,115.77 (1.1 एक्स तेज)
    • वाक्य: 36.82 (1.1 एक्स तेज)
  • {StringBuilder}.AppendFormat ( {IEnumerable}.Aggregate का उपयोग करके {IEnumerable}.Aggregate , {IEnumerable}.Aggregate आवश्यकता है) (टॉमलाक के उत्तर से व्युत्पन्न)
    • टेक्स्ट: 718,380.63 (1.0 एक्स तेज)
    • वाक्य: 39.71 (1.0 एक्स तेज)

लुकअप टेबल ने बाइट मैनिपुलेशन पर लीड ली है। असल में, precomputing का कुछ रूप है जो किसी दिए गए निबल या बाइट हेक्स में होगा। फिर, जैसे ही आप डेटा के माध्यम से चीरते हैं, आप यह देखने के लिए अगले भाग को देखते हैं कि हेक्स स्ट्रिंग क्या होगा। उस मान को तब कुछ फ़ैशन में परिणामी स्ट्रिंग आउटपुट में जोड़ा जाता है। लंबे समय तक बाइट मैनिपुलेशन के लिए, कुछ डेवलपर्स द्वारा संभावित रूप से पढ़ने के लिए कठिन, शीर्ष प्रदर्शन करने वाला दृष्टिकोण था।

आपकी सबसे अच्छी शर्त अभी भी कुछ प्रतिनिधि डेटा ढूंढ रही है और उत्पादन-जैसे वातावरण में इसे आजमा रही है। यदि आपके पास अलग-अलग मेमोरी बाधाएं हैं, तो आप कम आवंटन वाले एक विधि को प्राथमिकता दे सकते हैं जो तेजी से होगा लेकिन अधिक स्मृति का उपभोग करेगा।

परीक्षण कोड

मैंने उपयोग किए गए परीक्षण कोड के साथ खेलने के लिए स्वतंत्र महसूस करें। यहां एक संस्करण शामिल है लेकिन रेपो क्लोन करने और अपनी खुद की विधियों को जोड़ने के लिए स्वतंत्र महसूस करें। अगर आपको कुछ दिलचस्प लगता है या परीक्षण फ्रेमवर्क में सुधार करने में मदद करना चाहते हैं तो कृपया पुल अनुरोध सबमिट करें।

  1. नई स्थैतिक विधि ( Func<byte[], string> ) को /Tests/ConvertByteArrayToHexString/Test.cs में जोड़ें।
  2. उसी विधि में TestCandidates रिटर्न वैल्यू में उस विधि का नाम जोड़ें।
  3. सुनिश्चित करें कि आप उसी वर्ग में जेनरेटटेस्ट इनपुट में टिप्पणियों को टॉगल करके, इच्छित इनपुट संस्करण, वाक्य या टेक्स्ट चला रहे हैं।
  4. F5 दबाएं और आउटपुट के लिए प्रतीक्षा करें (एक HTML डंप भी / बिन फ़ोल्डर में उत्पन्न होता है)।
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();
}

अद्यतन (2010-01-13)

विश्लेषण के लिए Waleed का जवाब जोड़ा गया। जल्दी।

अपडेट (2011-10-05)

जोड़ा गया string.Concat Array.ConvertAll पूर्ण संस्करण के लिए सभी संस्करण (.NET 4.0 की आवश्यकता है)। string.Join संस्करण के बराबर पर।

अपडेट (2012-02-05)

टेस्ट रेपो में StringBuilder.Append(b.ToString("X2")) जैसे अधिक प्रकार शामिल हैं। StringBuilder.Append(b.ToString("X2")) । कोई भी परिणाम परेशान नहीं करता है। foreach {IEnumerable}.Aggregate से तेज़ है। उदाहरण के लिए, लेकिन BitConverter अभी भी जीतता है।

अपडेट (2012-04-03)

विश्लेषण के लिए Mykroft के SoapHexBinary जवाब जोड़ा गया, जो तीसरे स्थान पर ले लिया।

अद्यतन (2013-01-15)

कोड्सइंहॉस के बाइट मैनिपुलेशन उत्तर को जोड़ा गया, जो पहले स्थान पर ले गया (टेक्स्ट के बड़े ब्लॉक पर बड़े मार्जिन द्वारा)।

अद्यतन (2013-05-23)

नेथन मोइनवाज़ीरी के लुकअप उत्तर और ब्रायन लैम्बर्ट के ब्लॉग से संस्करण जोड़ा गया। दोनों तेजी से, लेकिन परीक्षण मशीन पर लीड नहीं लेते थे (एएमडी फेनोम 9 750)।

अद्यतन (2014-07-31)

जोड़ा गया @ CodeInChaos का नया बाइट-आधारित लुकअप उत्तर। ऐसा लगता है कि वाक्य परीक्षण और पूर्ण-पाठ परीक्षण दोनों में अग्रणी रहा है।

अद्यतन (2015-08-20)

इस उत्तर के रेपो में airbreather अनुकूलन और unsafe संस्करण जोड़ा गया। यदि आप असुरक्षित गेम में खेलना चाहते हैं, तो आप छोटे स्ट्रिंग्स और बड़े ग्रंथों पर किसी भी पूर्व शीर्ष विजेताओं में से कुछ पर कुछ शानदार प्रदर्शन लाभ प्राप्त कर सकते हैं।

https://code.i-harness.com

आप एक बाइट सरणी को हेक्साडेसिमल स्ट्रिंग में कैसे परिवर्तित कर सकते हैं, और इसके विपरीत?



एक अन्य लुकअप टेबल आधारित दृष्टिकोण। यह एक प्रतिबिंब तालिका के बजाय प्रत्येक बाइट के लिए केवल एक लुकअप टेबल का उपयोग करता है।

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

मैंने लुकअप टेबल में ushort , struct{char X1, X2} , struct{byte X1, X2} का उपयोग करके ushort रूपों का भी परीक्षण किया।

संकलन लक्ष्य (x86, X64) के आधार पर उन लोगों के पास लगभग समान प्रदर्शन था या इस संस्करण से थोड़ा धीमा था।

और यहां तक ​​कि उच्च प्रदर्शन के लिए, इसके unsafe भाई:

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

या यदि आप सीधे स्ट्रिंग में लिखने के लिए स्वीकार्य मानते हैं:

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

कोई एक:

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

या:

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

ऐसा करने के लिए और भी अधिक रूप हैं, उदाहरण के लिए here

रिवर्स रूपांतरण इस तरह होगा:

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

Substring का उपयोग Convert.ToByte साथ संयोजन में सबसे अच्छा विकल्प है। अधिक जानकारी के लिए यह उत्तर देखें। यदि आपको बेहतर प्रदर्शन की आवश्यकता है, तो आपको SubString को छोड़ने से पहले SubString करना चाहिए। SubString


मुझे आज भी एक ही समस्या का सामना करना पड़ा, और मैं इस कोड में आया:

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

स्रोत: फोरम पोस्ट here हेरे here (PZahra द्वारा पोस्ट देखें)। मैंने 0x उपसर्ग को हटाने के लिए कोड को थोड़ा सा संशोधित किया।

मैंने कोड में कुछ प्रदर्शन परीक्षण किया और यह बिटकोनवर्टर का उपयोग करने से लगभग आठ गुना तेज था। टॉस्ट्रिंग () (पैट्रिज के पोस्ट के अनुसार सबसे तेज़)।


यदि आप BitConverter से अधिक लचीलापन चाहते हैं, लेकिन उन BitConverter 1 99 0-शैली के स्पष्ट लूप नहीं चाहते हैं, तो आप यह कर सकते हैं:

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

या, यदि आप .NET 4.0 का उपयोग कर रहे हैं:

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

(उत्तरार्द्ध मूल पोस्ट पर एक टिप्पणी से।)


SoapHexBinary नामक एक वर्ग है जो वही करता है जो आप चाहते हैं।

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

Extension methods (disclaimer: completely untested code, 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();
    }
}

etc.. Use either of Tomalak's three solutions (with the last one being an extension method on a string).


And for inserting into an SQL string (if you're not using command parameters):

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

Another way is by using stackalloc to reduce GC memory pressure:

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

For performance I would go with drphrozens solution. A tiny optimization for the decoder could be to use a table for either char to get rid of the "<< 4".

Clearly the two method calls are costly. If some kind of check is made either on input or output data (could be CRC, checksum or whatever) the if (b == 255)... could be skipped and thereby also the method calls altogether.

Using offset++ and offset instead of offset and offset + 1 might give some theoretical benefit but I suspect the compiler handles this better than me.

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

This is just off the top of my head and has not been tested or benchmarked.


From Microsoft's developers, a nice, simple conversion:

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

While the above is clean an compact, performance junkies will scream about it using enumerators. You can get peak performance with an improved version of Tomolak's original answer:

public static string ByteArrayToString(byte[] ba)   
{   
   StringBuilder hex = new StringBuilder(ba.Length * 2);   

   for(int i=0; i < ga.Length; i++)       // <-- Use for loop is faster than foreach   
       hex.Append(ba[i].ToString("X2"));   // <-- ToString is faster than AppendFormat   

   return hex.ToString();   
} 

This is the fastest of all the routines I've seen posted here so far. Don't just take my word for it... performance test each routine and inspect its CIL code for yourself.


I did not get the code you suggested to work, Olipro. hex[i] + hex[i+1] apparently returned an int .

I did, however have some success by taking some hints from Waleeds code and hammering this together. It's ugly as hell but it seems to work and performs at 1/3 of the time compared to the others according to my tests (using patridges testing mechanism). Depending on input size. Switching around the ?:s to separate out 0-9 first would probably yield a slightly faster result since there are more numbers than letters.

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

I'll enter this bit fiddling competition as I have an answer that also uses bit-fiddling to decode hexadecimals. Note that using character arrays may be even faster as calling StringBuilder methods will take time as well.

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

Converted from Java code.


In terms of speed, this seems to be better than anything here:

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

Inverse function for Waleed Eissa code (Hex String To Byte Array):

    public static byte[] HexToBytes(this string hexString)        
    {
        byte[] b = new byte[hexString.Length / 2];            
        char c;
        for (int i = 0; i < hexString.Length / 2; i++)
        {
            c = hexString[i * 2];
            b[i] = (byte)((c < 0x40 ? c - 0x30 : (c < 0x47 ? c - 0x37 : c - 0x57)) << 4);
            c = hexString[i * 2 + 1];
            b[i] += (byte)(c < 0x40 ? c - 0x30 : (c < 0x47 ? c - 0x37 : c - 0x57));
        }

        return b;
    }

Waleed Eissa function with lower case support:

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

Not to pile on to the many answers here, but I found a fairly optimal (~4.5x better than accepted), straightforward implementation of the hex string parser. First, output from my tests (the first batch is my implementation):

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

The base64 and 'BitConverter'd' lines are there to test for correctness. Note that they are equal.

The implementation:

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

I tried some stuff with unsafe and moving the (clearly redundant) character-to-nibble if sequence to another method, but this was the fastest it got.

(I concede that this answers half the question. I felt that the string->byte[] conversion was underrepresented, while the byte[]->string angle seems to be well covered. Thus, this answer.)


This is a great post. I like Waleed's solution. I haven't run it through patridge's test but it seems to be quite fast. I also needed the reverse process, converting a hex string to a byte array, so I wrote it as a reversal of Waleed's solution. Not sure if it's any faster than Tomalak's original solution. Again, I did not run the reverse process through patridge's test either.

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

Two mashups which folds the two nibble operations into one.

Probably pretty efficient version:

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

Decadent linq-with-bit-hacking version:

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

And reverse:

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

Why make it complex? This is simple in Visual Studio 2008:

सी#:

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

VB:

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







hex