byte[] 之 36 進位表示法 - 使用 JavaScript 與 C#
| | 1 | |
講到用文字表示二進位資料,一般不外乎會用 16 進位數字串或 Base64 編碼。
以長度 60 的 byte[] [0xe5,0xa6,0x82,0xe6,0x9e,0x9c,0xe6,0x98,0xaf,0xe5,0x8b,0x87,0xe8,0x80,0x85,0xe6,0xac,0xa3,0xe6,0xa2,0x85,0xe7,0x88,0xbe,0xe7,0x9a,0x84,0xe8,0xa9,0xb1,0xef,0xbc,0x8c,0xe4,0xbb,0x96,0xe4,0xb8,0x80,0xe5,0xae,0x9a,0xe4,0xb9,0x9f,0xe6,0x9c,0x83,0xe9,0x80,0x99,0xe9,0xba,0xbc,0xe5,0x81,0x9a,0xe7,0x9a,0x84]
為例。
轉成 16 進位數字串會是 E5A682E69E9CE698AFE58B87E88085E6ACA3E6A285E788BEE79A84E8A9B1EFBC8CE4BB96E4B880E5AE9AE4B99FE69C83E98099E9BABCE5819AE79A84
(長度 60 x 2 = 120)。
Base64 編碼則是 5aaC5p6c5piv5YuH6ICF5qyj5qKF54i+55qE6Kmx77yM5LuW5LiA5a6a5Lmf5pyD6YCZ6bq85YGa55E
(長度 60 / 3 x 4 = 80)。
Base64 壓縮效果不錯(只有 byte[] 長度的 1.33 倍),且運算速度快(64 是 2 的次方,可用 CPU 擅長的 Bit 位移取代除法),相容性高(只用英文大小寫、數字跟兩個符號,純英文環境嘛 A 通),加上各語言、平台幾乎都內建 API,算得上是十分完美的解決方案,硬要說缺點,大概只有必須區分大小寫這點要求,若用在不分大小寫的資料庫欄位或 Windows 檔名,會有誤判相等及意外覆寫困擾。
最近手邊有個應用想用檔案內容的 SHA256 當成檔名,用 16 進位數字表示需 64 碼 (如:A8A9C38853456614BE646C1EB659A197D1FD3E7F5B430D96579ADE41AE034EFF),Base64 後為 qKnDiFNFZhS+ZGwetlmhl9H9Pn9bQw2WV5reQa4DTv8=
45 碼,但當成 Windows 檔名需考慮大小寫問題及避開符號「+」,於是我想起多年前用過的 36 進位表示法。 (補充:想走正規一點的做法,則可考慮 Base32 編碼 / RFC 4648)
現代瀏覽器都有內建 BigInt 別,它類似 C# 的 BigInteger,可執行無限位數整數的基本數學運算(只要你有夠大的記憶體),而 BigInt.toString() 有個特異功能,toString(radix) 內建支援 2 到 36 進位表示法。
var bi = BigInt('0xA8A9C38853456614BE646C1EB659A197D1FD3E7F5B430D96579ADE41AE034EFF')
console.log(bi.toString(16));
console.log(bi.toString(36));
於是,這下連金魚也能輕鬆轉出 36 進位囉~
將場景搬到 .NET,C# BigInteger 沒有內建 36 進位轉換,但要自己寫倒也不難。
using System.Numerics;
using System;
// 注意:Parse 時前方記得加 0 確保 BigInteger.Parse() 解析成正值
var bi = BigInteger.Parse(
"0" + "A8A9C38853456614BE646C1EB659A197D1FD3E7F5B430D96579ADE41AE034EFF",
System.Globalization.NumberStyles.HexNumber);
var b36 = BigIntegerToBase36(bi);
Console.WriteLine(b36);
Console.WriteLine(b36.ToLower());
static string BigIntegerToBase36(BigInteger value)
{
const string chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
char[] data = new char[(int)Math.Ceiling(BigInteger.Log(value, 36))];
int i = data.Length - 1;
while (value > 0)
{
data[i--] = chars[(int)(value % 36)];
value /= 36;
}
return new string(data);
}
薑薑薑講~~~~ 得到跟 JavaScript 相同的結果。
不過要提醒,不管 JavaScript 或 C# 都是靠 BigInt / BigInteger 運算,執行效能無法跟 Base64 相比,不適合重度大量應用情境。
Converting binary data is typically done using hexadecimal strings or Base64 encoding. This blog introduces converting to base 36 in JavaScript and C# wigh BigInt/BigInteger.
Comments
# by yoyo
>> 最近手邊有個應用想用檔案內容的 SHA256 當成檔名,用 16 進位數字表示需 64 碼 聽起來類似git object (SHA-256) 的做法,不清楚是否有必要再編碼成36進位表示法? 我的想法是能保持簡單,就沒必要再多一步驟。 當然在能直接用byte[]的情境下,也沒必要轉成base64字串 電腦直接處理binary是最快的