天天看點

Unicode轉義(\uXXXX)的編碼和解碼

在涉及Web前端開發時, 有時會遇到<code>\uXXXX</code>格式表示的字元, 其中<code>XXXX</code>是16進制數字的字元串表示形式, 在js中這個叫Unicode轉義字元, 和<code>\n</code> <code>\r</code>同屬于轉義字元. 在其他語言中也有類似的, 可能還有其它變形的格式.

多數時候遇到需要解碼的情況多點, 是以會先介紹解碼decode, 後介紹編碼encode.

下文會提供Javascript C# Java三種語言下不同方法的實作和簡單說明, 會涉及到正則和位運算的典型用法.

<code>unescape</code>是用來處理<code>%uXXXX</code>這樣格式的字元串, 将<code>\uXXXX</code>替換成<code>%uXXXX</code>後<code>unescape</code>就可以處理了.

和解碼中相對應, 使用<code>escape</code>編碼, 然後将<code>%uXXXX</code>替換為<code>\uXXXX</code>, 因為<code>escape</code>還可能把一些字元編碼成<code>%XX</code>的格式, 是以這些字元還需要使用<code>unescape</code>還原回來.

<code>escape</code>編碼結果<code>%uXXXX</code>中的<code>XXXX</code>是大寫的, 是以後面的<code>replace</code>隻處理大寫的<code>A-F</code>.

不使用正則和<code>escape</code>

周遊字元串中的字元, 那些<code>charCode</code>大于256的會轉換成16進制字元串<code>c.toString(16)</code>, 如果不足4位則左邊補0<code>pad.substr(0, 4 - c.length)</code>. 結尾将周遊的結果合并成字元串傳回.

正則和js中的一樣, 将<code>XXXX</code>轉換以16進制<code>System.Globalization.NumberStyles.HexNumber</code>解析為<code>short</code>類型, 然後直接<code>(char)c</code>就能轉換成對應的字元, <code>"" + (char)c</code>用于轉換成字元串類型傳回.

由于正則中也有<code>\uXXXX</code>, 是以需要寫成<code>\\uXXXX</code>來表示比對字元串<code>\uXXXX</code>, 而不是具體的字元.

上面使用到了Lambda, 需要至少dotnet 4的SDK才能編譯通過, 可以在dotnet 2下運作.

和C#的解碼實作正好相反, 0-255之外的字元, 從<code>char</code>轉換成<code>short</code>, 然後<code>string.Format</code>以16進制, 至少輸出4位.

和C#相似的, 使用正則

Java語言沒有内嵌正則文法, 也沒有類似C#的<code>@"\u1234"</code>原始形式字元串的文法, 是以要表示正則中比對<code>\</code>, 就需要<code>\\\\</code>, 其中2個是用于Java中字元轉義, 2個是正則中的字元轉義.

Java語言中沒有設計函數或者委托的文法, 是以它的正則庫提供的是<code>find</code> <code>appendReplacement</code> <code>appendTail</code>這些方法的組合, 等價于js和C#中的<code>replace</code>.

這裡使用<code>StringBuffer</code>類型是由于<code>appendReplacement</code>隻接受這個類型, <code>StringBuffer</code>有線程安全的額外操作, 是以性能差一點. 也許第三方的正則庫能把API設計的更好用點.

<code>Integer.parseInt(m.group(1), 16)</code>用于解析為<code>int</code>類型, 之後再<code>(char)</code>, 以及<code>Character.toString</code>轉換成字元串.

因為<code>StringBuffer</code>的原因, 不使用正則的實作

手工做就是麻煩很多, 代碼中也一坨的符号.

周遊所有字元<code>chars</code>, 檢測到<code>\u</code>這樣的字元串, 檢測後續的4個字元是否是16進制數字的字元表示. 因為<code>Character.isDigit</code>會把一些其它語系的數字也算進來, 是以保險的做法<code>'0' &lt;= ch &amp;&amp; ch &lt;= '9'</code>.

<code>Character.digit</code>會把<code>0-9</code>傳回為<code>int</code>類型的0-9, 第2個參數是16時會把<code>a-f</code>傳回為<code>int</code>類型的10-15.

剩下的就是用<code>|=</code>把各個部分的數字合并到一起, 轉換成char類型. 還有一些調整周遊位置等.

考慮到Java正則的杯具, 還是繼續手工來吧, 相對解碼來說代碼少點.

對應于上文Java編碼的實作正好是反向的實作, 依舊周遊字元, 遇到大于256的字元, 用位運算提取出4部分并使用<code>Character.forDigit</code>轉換成16進制數對應的字元.

剩下就是<code>sb.toString()</code>傳回了.

編碼從邏輯上比解碼簡單點.

對付字元串, js還是最順手的, 也友善測試.

位運算的性能很高.

Java的正則庫設計的很不友善, 可以考慮第三方.

Java的文法設計現在看來呆闆, 落後, 也沒有js那種靈活.

上文Java的非正則實作可以寫成等價的C#代碼.