CODE-縮短版GUID字串

頗特殊的需求: 一個跨平台整合在傳遞以GUID為Primary Key資料時,對方的參數欄位只接受最長30個字元,即使使用16進位數字表示法(例如: 4854c292c333480890f916d1a062b8e3),GUID字串也長達32字元,超出限制。另外想一種不會重複的識別編號法則是種解法,但要做到GUID等級的唯一性得付出不少代價。因此,另一個思考方向是如何用較短的字串長度表示GUID,評估是較省力的做法。

要比16進位表示法更簡短,最簡便的做法是將其GUID先轉為byte[],再用Base64編碼轉為字串。例如: 4854c292c333480890f916d1a062b8e3 可以轉換成 ksJUSDPDCEiQ+RbRoGK44w==,只要24個字元。但有個問題,對方系統的資料庫定序(Collation)被設為不分大小寫,而Base64編碼需大小寫有別,唯一限制及查詢比對可能出問題。

原本想拿出自己在VB6時代發明的36進位表示去(使用A-Z及0-9共36個英數元表示二進位資料),但轉念一想,如果有標準可循,還是別自己造輪子好。查詢後,真的有所謂的Base32編碼,而且還是RFC標準(RFC 4648)! Base32只用A-Z及2-7共32個字元表示,編碼結果比Base64長約20%,但好處是:

  • 適用於不分大小寫的情境
  • 數字段避開0, 1, 8,可避免與字母O, I, B混淆
  • 使用於URL時完全不需要UrlEncoding

只是Base32的應用不若Base64普遍,在.NET基本類別庫無現成可用的函數編解碼。所幸,既是公開標準,就不難找到前輩先進寫好的範例。我在stackoverflow找到一個實作範例,但測試發現它在預估字串長度時計算有誤差,會有多餘的Padding字元"=",但這只需要小小調整就可改善: [已留言建議]

原本是 charCount = (int)Math.Ceiling(input.Length / 5d) * 8;

應改為 charCount = (int)Math.Ceiling(input.Length / 5d * 8);

以下是測試範例,建立10,000個GUID進行編碼及還原,逐筆驗證還原結果無誤並計算執行時間。(同時支援Base64、Base32兩種格式)

    class Program
    {
        static void Main(string[] args)
        {
 
            List<Guid> guidPool = new List<Guid>();
            for (int i = 0; i < 10000; i++)
            {
                guidPool.Add(Guid.NewGuid());
            }
 
            string str = null;
            Guid restored = Guid.Empty;
            Stopwatch sw = new Stopwatch();
            //Base64 version test
            sw.Start();
            foreach (Guid uid in guidPool)
            {
                str = GetShortGuidString(uid);
                restored = ParseShortGuidString(str);
                if (!uid.Equals(restored))
                    throw new ApplicationException("Test Failed!");
            }
            sw.Stop();
            Console.WriteLine("Base64 Version in {0:N}ms,\n  {1:N} -> {2}",
                sw.ElapsedMilliseconds, restored, str);
 
            //Base32 version test
            sw.Restart();
            foreach (Guid uid in guidPool)
            {
                str = GetShortGuidString(uid, true);
                restored = ParseShortGuidString(str, true);
                if (!uid.Equals(restored))
                    throw new ApplicationException("Test Failed!");
            }
            sw.Stop();
            Console.WriteLine("Base32 Version in {0:N}ms,\n  {1:N} -> {2}",
                sw.ElapsedMilliseconds, restored, str);
 
            Console.Read();
        }
 
        static string GetShortGuidString(Guid uid, bool useBase32 = false)
        {
            if (useBase32)
                return Base32Encoding.ToString(uid.ToByteArray());
            else
                return Convert.ToBase64String(uid.ToByteArray());
        }
 
        static Guid ParseShortGuidString(string s, bool useBase32 = false)
        {
            if (useBase32)
                return new Guid(Base32Encoding.ToBytes(s));
            else
                return new Guid(Convert.FromBase64String(s));
        }
    }

測試結果如下:

Base64 Version in 13.00ms,
  4854c292c333480890f916d1a062b8e3 -> ksJUSDPDCEiQ+RbRoGK44w==
Base32 Version in 38.00ms,
  4854c292c333480890f916d1a062b8e3 –> SLBFISBTYMEEREHZC3I2AYVY4M

推測目前找到的Base32編碼邏輯,最佳化程度不及.NET內建的Convert.ToBase64String,故Base32執行速度較Base64慢3倍,但1萬筆只需0.04秒,在整合應用時成為瓶頸的可能性不高,測試驗證可行。

歡迎推文分享:
Published 29 January 2013 10:44 PM 由 Jeffrey
Filed under: ,



意見

# Robin said on 29 January, 2013 10:50 PM

感謝分享~~

# 豬公峰 said on 03 March, 2013 10:19 PM

另可考慮用Alphanumerics, 就是把16進位從F擴充到Z

就有36進位(不分大小寫)資料長度比Base32短

分大小寫的話是62進位資料, 比Base64大一點, 但好處是都是文數字

你的看法呢?

(必要的) 
(必要的) 
(選擇性的)
(必要的) 
(提醒: 因快取機制,您的留言幾分鐘後才會顯示在網站,請耐心稍候)

5 + 3 =

搜尋

Go

<January 2013>
SunMonTueWedThuFriSat
303112345
6789101112
13141516171819
20212223242526
272829303112
3456789
 
RSS
【工商服務】
OrcsWeb: Windows Server Hosting
twMVC
最新回應

Tags 分類檢視
關於作者

一個醉心技術又酷愛分享的Coding魔人,十年的IT職場生涯,寫過系統、管過專案, 也帶過團隊,最後還是無怨無悔地選擇了技術鑽研這條路,近年來則以做一個"有為的中年人"自許。

文章典藏
其他功能

這個部落格


Syndication