一次性搞清楚unicode羽莺、codepoint、代碼點(diǎn)洞豁、UTF

最近在處理字符過濾,重新研究了下字符、unicode和代碼點(diǎn)的相關(guān)知識丈挟,首先要說一下編碼的基本知識unicode

unicode

unicode是計算機(jī)科學(xué)領(lǐng)域里的一項業(yè)界標(biāo)準(zhǔn)刁卜,包括字符集、編碼方案等曙咽。計算機(jī)采用八比特一個字節(jié)蛔趴,一個字節(jié)最大整數(shù)是255,還要表示中文一個字也是不夠的例朱,至少需要兩個字節(jié)孝情,為了統(tǒng)一所有的文字編碼,unicode為每種語言中的每個字符設(shè)定了統(tǒng)一并且唯一的二進(jìn)制編碼洒嗤,通常用兩個字節(jié)表示一個字符箫荡,所以unicode每個平面可以組合出65535種不同的字符羔挡,一共17個平面。

由于英文符號只需要用到低8位绞灼,所以其高8位永遠(yuǎn)是0低矮,因此保存英文文本時會多浪費(fèi)一倍的空間被冒。

比如漢子“漢”的unicode,在java中輸出

System.out.println("\u5B57");

UTF-8

unicode在計算機(jī)中如何存儲呢姆打,就是用unicode字符集轉(zhuǎn)換格式,即我們常見的UTF-8玛追、UTF-16等闲延。

UTF-8就是以字節(jié)為單位對unicode進(jìn)行編碼垒玲,對不同范圍的字符使用不同長度的編碼。

Unicode Utf-8
000000-00007F 0xxxxxxx
000080-0007FF 110xxxxx 10xxxxxx
000800-00FFFF 1110xxxx 10xxxxxx 10xxxxxx
010000-10FFFF 11110xxx10xxxxxx10xxxxxx10xxxxxx

Java中的String對象就是一個unicode編碼的字符串叮贩。

java中想知道一個字符的unicode編碼我們可以通過Integer.toHexString()方法

        String str = "編";
        StringBuffer sb = new StringBuffer();
        char [] source_char = str.toCharArray();
        String unicode = null;
        for (int i=0;i<source_char.length;i++) {
            unicode = Integer.toHexString(source_char[i]);
            if (unicode.length() <= 2) {
                unicode = "00" + unicode;
            }
            sb.append("\\u" + unicode);
        }
        System.out.println(sb);
        輸出\u7f16

對應(yīng)的utf-8編碼是什么呢?

7f16在0800-FFFF之間益老,所以要用3字節(jié)模板:1110xxxx 10xxxxxx 10xxxxxx捺萌。
7f16寫成二進(jìn)制是:0111 1111 0001 0110
按三字節(jié)模板分段方法分為0111 111100 010110,代替模板中的x酷誓,得到11100111 10111100 10010110态坦,即“編”對應(yīng)的utf-8的編碼是e7 bc 96,占3個字節(jié)

codepoint

unicode的范圍從000000 - 10FFFF驮配,char的范圍只能是在\u0000到\uffff壮锻,也就是標(biāo)準(zhǔn)的 2 字節(jié)形式通常稱作 UCS-2,在Java中灰殴,char類型用UTF-16編碼描述一個代碼單元牺陶,但unicode大于0x10000的部分如何用char表示呢辣之,比如一些emoji:??

java的char類型占兩個字節(jié),想要表示??這個表情就需要2個char狮鸭,看如下代碼

String testCode = "ab\uD83D\uDE03cd";
int length = testCode.length();
int count = testCode.codePointCount(0, testCode.length());
//length=6
//count=5

第三個和第四個字符合起來代表??歧蕉,是一個代碼點(diǎn),
如果我們想取到每個代碼點(diǎn)做一些判斷可以這么寫

        String testCode = "ab\uD83D\uDE03cd";
        int cpCount = testCode.codePointCount(0, testCode.length());
        for(int index = 0; index < cpCount; ++index) {
            //這里的i是字符的位置
            int i = testCode.offsetByCodePoints(0, index);
            int codepoint = testCode.codePointAt(i);
        }
      //輸出
      i:0 index: 0 codePoint: 97
      i:1 index: 1 codePoint: 98
      i:2 index: 2 codePoint: 128515
      i:4 index: 3 codePoint: 99
      i:5 index: 4 codePoint: 100

也就是按照codePointindex取字符惯退,0取到a催跪,1取到b,2取到\uD83D\uDE03也就是??叠荠,3取到c榛鼎,4取到d鳖孤;
按照String的index取字符,0取到a黄鳍,1取到b框沟,2取到\uD83D增炭,3取到\uDE03隙姿,4取到c,5取到d队丝。
這就是codePointIndex和char的index的區(qū)別欲鹏。

取到codePoint就可以按照unicode值進(jìn)行字符的過濾等操作。

如果有個需求是既可以按照unicode值過濾字符膘盖,也能按照正則表達(dá)式過濾字符衔憨,并且還有白名單袄膏,應(yīng)該如何實現(xiàn)呢。

其實unicode過濾和正則表達(dá)式過濾并不沖突沉馆,自己實現(xiàn)自己的過濾就好了,如果需求加入了過濾白名單就會復(fù)雜一些揖盘,不能直接過濾兽狭,需要先檢驗是否是白名單的index。

我的思路是記錄白名單char的index箕慧,正則表達(dá)式或其他過濾方式可以獲得違規(guī)char的index颠焦,unicode黑名單的codepointIndex可以轉(zhuǎn)換成char的index,在獲取codePont的index時可以判斷當(dāng)前字符是單char字符還是雙char字符粉渠,雙char字符需要添加2個下標(biāo)圾另,方法如下

        //取到unicode值           
        int codepoint = testCode.codePointAt(i);
        //將unicode值轉(zhuǎn)換成char數(shù)組
        char[] chars = Character.toChars(codepoint);
        charIndexs.add(pointIndex);
        if (chars.length > 1) {
            //表示不是單char字符盯捌,記錄index時同時添加i+1
           charIndexs.add(pointIndex + 1);
        }
     

//例
String str = "ab\uD83D\uDE03漢字";
想處理emoji,那記錄的下標(biāo)就是2箫攀、3幼衰,最后和白名單下標(biāo)比較后統(tǒng)一刪除

如何區(qū)別char是一對還是單個

就之前的例子ab\uD83D\uDE03cd渡嚣,換種寫法\u0061\u0062\uD83D\uDE0\u0063\u0064
程序是如何將\uD83D\uDE03解析成一個字符的呢。這就需要Surrogate這個概念绝葡,來自UTF-16藏畅。

UTF-16是16bit最多編碼65536,那大于65536如何編碼愉阎?Unicode 標(biāo)準(zhǔn)制定組想出的辦法是,從這65536個編碼里幽七,拿出2048個澡屡,規(guī)定他們是「Surrogates」,讓他們兩個為一組挪蹭,來代表編號大于65536的那些字符休偶。
編號為 U+D800 至 U+DBFF 的規(guī)定為「High Surrogates」踏兜,共1024個碱妆。
編號為 U+DC00 至 U+DFFF 的規(guī)定為「Low Surrogates」昔驱,也是1024個。
他們組合出現(xiàn)纳本,就又可以多表示1048576中字符繁成。

看一下String.codePointAt這個方法淑玫,

    static int codePointAtImpl(char[] a, int index, int limit) {
        char c1 = a[index];
        if (isHighSurrogate(c1) && ++index < limit) {
            char c2 = a[index];
            if (isLowSurrogate(c2)) {
                return toCodePoint(c1, c2);
            }
        }
        return c1;
    }

其中有兩個方法isHighSurrogate絮蒿、isLowSurrogate。
第一個方法判斷是否為高代理項代碼單元佛寿,即在'\uD800'與'\uDBFF'之間,
第二個方法判斷是否為低代理項代碼單元狗准,即在'\uDC00'與'\uDFFF'之間。

codePointAtImpl方法判斷當(dāng)前char是高代理項代碼單元袭祟,下一個是低代理項代碼單元捞附,則這兩個char是一個codepoint鸟召。

再來看一下unicode轉(zhuǎn)UTF-16的方法

如果U<0x10000欧募,U的UTF-16編碼就是U對應(yīng)的16位無符號整數(shù)(為書寫簡便,下文將16位無符號整數(shù)記作WORD)种冬。
如果U≥0x10000舔糖,我們先計算U'=U-0x10000金吗,然后將U'寫成二進(jìn)制形式:yyyy yyyy yyxx xxxx xxxx,U的UTF-16編碼(二進(jìn)制)就是:110110yyyyyyyyyy 110111xxxxxxxxxx旱物。

還是以U+1F603這個??為例子异袄,U'=U-0x10000=F603
寫成2進(jìn)制就是1111011000000011玛臂,不足20位前面補(bǔ)0迹冤,
變成0000111101-1000000011,替換y和x就是1101100000111101橱鹏,1101111000000011挑围,最后UTF-16編碼就是[d83d,de03] 和上面一樣杉辙。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蜘矢,一起剝皮案震驚了整個濱河市品腹,隨后出現(xiàn)的幾起案子舞吭,更是在濱河造成了極大的恐慌,老刑警劉巖镣典,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異锡溯,居然都是意外死亡祭饭,警方通過查閱死者的電腦和手機(jī)倡蝙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進(jìn)店門寺鸥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來胆建,“玉大人笆载,你說我怎么就攤上這事∧逡” “怎么了闯第?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蛛淋。 經(jīng)常有香客問我,道長褐荷,這世上最難降的妖魔是什么层宫? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任萌腿,我火速辦了婚禮抖苦,結(jié)果婚禮上毁菱,老公的妹妹穿的比我還像新娘。我一直安慰自己锌历,他們只是感情好贮庞,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著究西,像睡著了一般窗慎。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上卤材,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天遮斥,我揣著相機(jī)與錄音,去河邊找鬼商膊。 笑死伏伐,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的晕拆。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼末贾,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了乓旗?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤函匕,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蓬推,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡毅糟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年蝶防,在試婚紗的時候發(fā)現(xiàn)自己被綠了印荔。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嘿悬。...
    茶點(diǎn)故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖娶靡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情呻此,我是刑警寧澤放前,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布似扔,位于F島的核電站挽拔,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一升敲、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春蕉世,著一層夾襖步出監(jiān)牢的瞬間向楼,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人喂饥。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓氢哮,卻偏偏與公主長得像媳溺,于是被迫代替她去往敵國和親蝎困。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容