[.NET]將 Byte Array 轉成 16 進位字串

Microsoft Monitoring Agent找出效能問題指向一個將Byte Array轉成16進位的字串!
轉16進位字串為何會有效能問題呢?

今天透過 Microsoft Monitoring Agent 2013 來找尋 Web AP的效能問題,使用方式可以參考「利用 Microsoft Monitoring Agent 來找出系統效能及異常問題」。

image

 

找到問題點是一個將 Byte Array 轉成 16 進位字串的 Method,它的Code如下,

public static string ByteArrayToHexStrOld(byte[] vabytData)
{
	string str;
	 
	string str2 = "";
	long num2 = vabytData.Length - 1;
	for (int i = 0; i <= num2; i++ )
	{
		string str3 = Convert.ToByte(vabytData[(int)i]).ToString("x");
		if (str3.Length == 1)
		{
			str3 = "0" + str3;
		}
		str2 = str2 + str3;
	}
	str = str2;
	 
	return str;
}

 

是一個Byte 一個Byte去轉(.NET 1.1 沒有 BitConverter ),而 .NET 2.0之後有提供 BitConverter 類別 可以達到相同的功能,程式修改如下,

public static string ByteArrayToHexStrNew(byte[] vabytData)
{
	return BitConverter.ToString(vabytData).Replace("-", string.Empty).ToLower();
}

//如果轉成 16 進位字串不需要轉小寫的話,或是去除 "-" 的話,會更快哦!

 

使用 BitConverter 來轉換,效能比較好哦!

另外,在「High performance C# byte array to hex string to byte array」這篇文章中,他的速度也蠻快的哦! 有興趣的話,也可以參考看看哦!

以執行 5百萬次 的時間來看,比較結果如下,

image

 

比較的測試程式如下,

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            int value = 1234567890;
            byte[] bytes = BitConverter.GetBytes(value);
            Console.WriteLine(ByteArrayToHexStrOld(bytes));
            Console.WriteLine(ByteArrayToHexStrNew(bytes));
            Console.WriteLine(Fast.ToHexString(bytes).ToLower());
            // using System.Diagnostics;
            Stopwatch stopWatch = new Stopwatch();
            long memory = 0;
            stopWatch = Stopwatch.StartNew();
            memory = GC.GetTotalMemory(true);
            for (int i = 0; i < 5000000; i++)
            {
                ByteArrayToHexStrOld(bytes);
            }
            stopWatch.Stop();
            memory = GC.GetTotalMemory(false) - memory;
            TimeSpan ts = stopWatch.Elapsed;
            string elapsedTime = String.Format("{0:00}:{1:00}:{2:00}.{3:00}",
                ts.Hours, ts.Minutes, ts.Seconds,
                ts.Milliseconds / 10);
            Console.WriteLine("ByteArrayToHexStrOld RunTime {0}, Mem:{1} ", elapsedTime, memory);


            stopWatch = Stopwatch.StartNew();
            memory = GC.GetTotalMemory(true);
            for (int i = 0; i < 5000000; i++)
            {
                ByteArrayToHexStrNew(bytes);
            }
            stopWatch.Stop();
            memory = GC.GetTotalMemory(false) - memory;
            ts = stopWatch.Elapsed;
            elapsedTime = String.Format("{0:00}:{1:00}:{2:00}.{3:00}",
                ts.Hours, ts.Minutes, ts.Seconds,
                ts.Milliseconds / 10);
            Console.WriteLine("ByteArrayToHexStrNew RunTime {0}, Mem:{1} ", elapsedTime, memory);


            stopWatch = Stopwatch.StartNew();
            memory = GC.GetTotalMemory(true);
            for (int i = 0; i < 5000000; i++)
            {
                Fast.ToHexString(bytes).ToLower();
            }
            stopWatch.Stop();
            memory = GC.GetTotalMemory(false) - memory;
            ts = stopWatch.Elapsed;
            elapsedTime = String.Format("{0:00}:{1:00}:{2:00}.{3:00}",
                ts.Hours, ts.Minutes, ts.Seconds,
                ts.Milliseconds / 10);
            Console.WriteLine("ByteArrayToHexFast RunTime {0}, Mem:{1} ", elapsedTime, memory);

            Console.ReadKey();
        }




        public static string ByteArrayToHexStrNew(byte[] vabytData)
        {
            return BitConverter.ToString(vabytData).Replace("-", string.Empty).ToLower();
        }

        public static string ByteArrayToHexStrOld(byte[] vabytData)
        {
            string str;

            string str2 = "";
            long num2 = vabytData.Length - 1;
            for (int i = 0; i <= num2; i++)
            {
                string str3 = Convert.ToByte(vabytData[(int)i]).ToString("x");
                if (str3.Length == 1)
                {
                    str3 = "0" + str3;
                }
                str2 = str2 + str3;
            }
            str = str2;
            return str;
        }
    }


    // class is sealed and not static in my personal complete version
    public unsafe sealed partial class Fast
    {
        #region from/to hex
        // assigned int values for bytes (0-255)
        static readonly int[] toHexTable = new int[] {
            3145776, 3211312, 3276848, 3342384, 3407920, 3473456, 3538992, 3604528, 3670064, 3735600,
            4259888, 4325424, 4390960, 4456496, 4522032, 4587568, 3145777, 3211313, 3276849, 3342385,
            3407921, 3473457, 3538993, 3604529, 3670065, 3735601, 4259889, 4325425, 4390961, 4456497,
            4522033, 4587569, 3145778, 3211314, 3276850, 3342386, 3407922, 3473458, 3538994, 3604530,
            3670066, 3735602, 4259890, 4325426, 4390962, 4456498, 4522034, 4587570, 3145779, 3211315,
            3276851, 3342387, 3407923, 3473459, 3538995, 3604531, 3670067, 3735603, 4259891, 4325427,
            4390963, 4456499, 4522035, 4587571, 3145780, 3211316, 3276852, 3342388, 3407924, 3473460,
            3538996, 3604532, 3670068, 3735604, 4259892, 4325428, 4390964, 4456500, 4522036, 4587572,
            3145781, 3211317, 3276853, 3342389, 3407925, 3473461, 3538997, 3604533, 3670069, 3735605,
            4259893, 4325429, 4390965, 4456501, 4522037, 4587573, 3145782, 3211318, 3276854, 3342390,
            3407926, 3473462, 3538998, 3604534, 3670070, 3735606, 4259894, 4325430, 4390966, 4456502,
            4522038, 4587574, 3145783, 3211319, 3276855, 3342391, 3407927, 3473463, 3538999, 3604535,
            3670071, 3735607, 4259895, 4325431, 4390967, 4456503, 4522039, 4587575, 3145784, 3211320,
            3276856, 3342392, 3407928, 3473464, 3539000, 3604536, 3670072, 3735608, 4259896, 4325432,
            4390968, 4456504, 4522040, 4587576, 3145785, 3211321, 3276857, 3342393, 3407929, 3473465,
            3539001, 3604537, 3670073, 3735609, 4259897, 4325433, 4390969, 4456505, 4522041, 4587577,
            3145793, 3211329, 3276865, 3342401, 3407937, 3473473, 3539009, 3604545, 3670081, 3735617,
            4259905, 4325441, 4390977, 4456513, 4522049, 4587585, 3145794, 3211330, 3276866, 3342402,
            3407938, 3473474, 3539010, 3604546, 3670082, 3735618, 4259906, 4325442, 4390978, 4456514,
            4522050, 4587586, 3145795, 3211331, 3276867, 3342403, 3407939, 3473475, 3539011, 3604547,
            3670083, 3735619, 4259907, 4325443, 4390979, 4456515, 4522051, 4587587, 3145796, 3211332,
            3276868, 3342404, 3407940, 3473476, 3539012, 3604548, 3670084, 3735620, 4259908, 4325444,
            4390980, 4456516, 4522052, 4587588, 3145797, 3211333, 3276869, 3342405, 3407941, 3473477,
            3539013, 3604549, 3670085, 3735621, 4259909, 4325445, 4390981, 4456517, 4522053, 4587589,
            3145798, 3211334, 3276870, 3342406, 3407942, 3473478, 3539014, 3604550, 3670086, 3735622,
            4259910, 4325446, 4390982, 4456518, 4522054, 4587590
        };

        public static string ToHexString(byte[] source)
        {
            return ToHexString(source, false);
        }

        // hexIndicator: use prefix ("0x") or not
        public static string ToHexString(byte[] source, bool hexIndicator)
        {
            // freeze toHexTable position in memory
            fixed (int* hexRef = toHexTable)
            // freeze source position in memory
            fixed (byte* sourceRef = source)
            {
                // take first parsing position of source - allow inline pointer positioning
                byte* s = sourceRef;
                // calculate result length
                int resultLen = (source.Length << 1);
                // use prefix ("Ox")
                if (hexIndicator)
                    // adapt result length
                    resultLen += 2;
                // initialize result string with any character expect '\0'
                string result = new string(' ', resultLen);
                // take the first character address of result
                fixed (char* resultRef = result)
                {
                    // pairs of characters explain the endianess of toHexTable
                    // move on by pairs of characters (2 x 2 bytes) - allow inline pointer positioning
                    int* pair = (int*)resultRef;
                    // use prefix ("Ox") ?
                    if (hexIndicator)
                        // set first pair value
                        *pair++ = 7864368;
                    // more to go
                    while (*pair != 0)
                        // set the value of the current pair and move to next pair and source byte
                        *pair++ = hexRef[*s++];
                    return result;
                }
            }
        }

        // values for '\0' to 'f' where 255 indicates invalid input character
        // starting from '\0' and not from '0' costs 48 bytes
        // but results 0 subtructions and less if conditions
        static readonly byte[] fromHexTable = new byte[] {
            255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
            255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
            255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
            255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
            255, 255, 255, 255, 255, 255, 255, 255, 0, 1,
            2, 3, 4, 5, 6, 7, 8, 9, 255, 255,
            255, 255, 255, 255, 255, 10, 11, 12, 13, 14, 
            15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
            255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
            255, 255, 255, 255, 255, 255, 255, 10, 11, 12,
            13, 14, 15
        };

        // same as above but valid values are multiplied by 16
        static readonly byte[] fromHexTable16 = new byte[] {
            255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
            255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
            255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
            255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
            255, 255, 255, 255, 255, 255, 255, 255, 0, 16,
            32, 48, 64, 80, 96, 112, 128, 144, 255, 255,
            255, 255, 255, 255, 255, 160, 176, 192, 208, 224, 
            240, 255, 255, 255, 255, 255, 255, 255, 255, 255,
            255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
            255, 255, 255, 255, 255, 255, 255, 160, 176, 192,
            208, 224, 240
        };

        public static byte[] FromHexString(string source)
        {
            // return an empty array in case of null or empty source
            if (string.IsNullOrEmpty(source))
                return new byte[0]; // you may change it to return null
            if (source.Length % 2 == 1) // source length must be even
                throw new ArgumentException();
            int
                index = 0, // start position for parsing source
                len = source.Length >> 1; // initial length of result
            // take the first character address of source
            fixed (char* sourceRef = source)
            {
                if (*(int*)sourceRef == 7864368) // source starts with "0x"
                {
                    if (source.Length == 2) // source must not be just a "0x")
                        throw new ArgumentException();
                    index += 2; // start position (bypass "0x")
                    len -= 1; // result length (exclude "0x")
                }
                byte add = 0; // keeps a fromHexTable value
                byte[] result = new byte[len]; // initialization of result for known length
                // freeze fromHexTable16 position in memory
                fixed (byte* hiRef = fromHexTable16)
                // freeze fromHexTable position in memory
                fixed (byte* lowRef = fromHexTable)
                // take the first byte address of result
                fixed (byte* resultRef = result)
                {
                    // take first parsing position of source - allow inremental memory position
                    char* s = (char*)&sourceRef[index];
                    // take first byte position of result - allow incremental memory position
                    byte* r = resultRef;
                    // source has more characters to parse
                    while (*s != 0)
                    {
                        // check for non valid characters in pairs
                        // you may split it if you don't like its readbility
                        if (
                            // check for character > 'f'
                            *s > 102 ||
                            // assign source value to current result position and increment source position
                            // and check if is a valid character
                            (*r = hiRef[*s++]) == 255 ||
                            // check for character > 'f'
                            *s > 102 ||
                            // assign source value to "add" parameter and increment source position
                            // and check if is a valid character
                            (add = lowRef[*s++]) == 255
                            )
                            throw new ArgumentException();
                        // set final value of current result byte and move pointer to next byte
                        *r++ += add;
                    }
                    return result;
                }
            }
        }
        #endregion
    }
}

 

因為 High performance C# byte array to hex string to byte array 使用到 unsafe 所以在建置專案時,要設定 Allow unsafe code 的選項哦!

image

 

以上提供Byte Array 轉16進位字串的方式,一般來說使用 BitConverter 來轉應該就可以了。大家請依自已的需求來決定使用那一種方式。

如果有更好的方式也請分享出來哦! 謝謝!

 

參考資料

BitConverter 類別

Stopwatch 類別

利用 Microsoft Monitoring Agent 來找出系統效能及異常問題

High performance C# byte array to hex string to byte array

Hi, 

亂馬客Blog已移到了 「亂馬客​ : Re:從零開始的軟體開發生活

請大家繼續支持 ^_^