最近在處理字符過濾,重新研究了下字符、unicode和代碼點(diǎn)的相關(guān)知識(shí),首先要說一下編碼的基本知識(shí)unicode unicodeunicode是計(jì)算機(jī)科學(xué)領(lǐng)域里的一項(xiàng)業(yè)界標(biāo)準(zhǔn),包括字符集、編碼方案等。計(jì)算機(jī)采用八比特一個(gè)字節(jié),一個(gè)字節(jié)最大整數(shù)是255,還要表示中文一個(gè)字也是不夠的,至少需要兩個(gè)字節(jié),為了統(tǒng)一所有的文字編碼,unicode為每種語言中的每個(gè)字符設(shè)定了統(tǒng)一并且唯一的二進(jìn)制編碼,通常用兩個(gè)字節(jié)表示一個(gè)字符,所以u(píng)nicode每個(gè)平面可以組合出65535種不同的字符,一共17個(gè)平面。
由于英文符號(hào)只需要用到低8位,所以其高8位永遠(yuǎn)是0,因此保存英文文本時(shí)會(huì)多浪費(fèi)一倍的空間。 比如漢子“漢”的unicode,在java中輸出 System.out.println('\u5B57'); UTF-8unicode在計(jì)算機(jī)中如何存儲(chǔ)呢,就是用unicode字符集轉(zhuǎn)換格式,即我們常見的UTF-8、UTF-16等。 UTF-8就是以字節(jié)為單位對(duì)unicode進(jìn)行編碼,對(duì)不同范圍的字符使用不同長度的編碼。 Unicode | Utf-8 |
---|
000000-00007F | 0xxxxxxx | 000080-0007FF | 110xxxxx 10xxxxxx | 000800-00FFFF | 1110xxxx 10xxxxxx 10xxxxxx | 010000-10FFFF | 11110xxx10xxxxxx10xxxxxx10xxxxxx |
Java中的String對(duì)象就是一個(gè)unicode編碼的字符串。 java中想知道一個(gè)字符的unicode編碼我們可以通過Integer.toHexString()方法 StringBuffer sb = new StringBuffer(); char [] source_char = str.toCharArray(); 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);
對(duì)應(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,即“編”對(duì)應(yīng)的utf-8的編碼是e7 bc 96,占3個(gè)字節(jié) codepointunicode的范圍從000000 - 10FFFF,char的范圍只能是在\u0000到\uffff,也就是標(biāo)準(zhǔn)的 2 字節(jié)形式通常稱作 UCS-2,在Java中,char類型用UTF-16編碼描述一個(gè)代碼單元,但unicode大于0x10000的部分如何用char表示呢,比如一些emoji:? java的char類型占兩個(gè)字節(jié),想要表示?這個(gè)表情就需要2個(gè)char,看如下代碼 String testCode = 'ab\uD83D\uDE03cd'; int length = testCode.length(); int count = testCode.codePointCount(0, testCode.length());
第三個(gè)和第四個(gè)字符合起來代表?,是一個(gè)代碼點(diǎn), 如果我們想取到每個(gè)代碼點(diǎn)做一些判斷可以這么寫 String testCode = 'ab\uD83D\uDE03cd'; int cpCount = testCode.codePointCount(0, testCode.length()); for(int index = 0; index < cpCount; ++index) { 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)行字符的過濾等操作。 如果有個(gè)需求是既可以按照unicode值過濾字符,也能按照正則表達(dá)式過濾字符,并且還有白名單,應(yīng)該如何實(shí)現(xiàn)呢。 其實(shí)unicode過濾和正則表達(dá)式過濾并不沖突,自己實(shí)現(xiàn)自己的過濾就好了,如果需求加入了過濾白名單就會(huì)復(fù)雜一些,不能直接過濾,需要先檢驗(yàn)是否是白名單的index。 我的思路是記錄白名單char的index,正則表達(dá)式或其他過濾方式可以獲得違規(guī)char的index,unicode黑名單的codepointIndex可以轉(zhuǎn)換成char的index,在獲取codePont的index時(shí)可以判斷當(dāng)前字符是單char字符還是雙char字符,雙char字符需要添加2個(gè)下標(biāo),方法如下 int codepoint = testCode.codePointAt(i); //將unicode值轉(zhuǎn)換成char數(shù)組 char[] chars = Character.toChars(codepoint); charIndexs.add(pointIndex); //表示不是單char字符,記錄index時(shí)同時(shí)添加i+1 charIndexs.add(pointIndex + 1);
//例 String str = 'ab\uD83D\uDE03漢字'; 想處理emoji,那記錄的下標(biāo)就是2、3,最后和白名單下標(biāo)比較后統(tǒng)一刪除 如何區(qū)別char是一對(duì)還是單個(gè) 就之前的例子ab\uD83D\uDE03cd,換種寫法\u0061\u0062\uD83D\uDE0\u0063\u0064 程序是如何將\uD83D\uDE03解析成一個(gè)字符的呢。這就需要Surrogate這個(gè)概念,來自UTF-16。 UTF-16是16bit最多編碼65536,那大于65536如何編碼?Unicode 標(biāo)準(zhǔn)制定組想出的辦法是,從這65536個(gè)編碼里,拿出2048個(gè),規(guī)定他們是「Surrogates」,讓他們兩個(gè)為一組,來代表編號(hào)大于65536的那些字符。 編號(hào)為 U+D800 至 U+DBFF 的規(guī)定為「High Surrogates」,共1024個(gè)。 編號(hào)為 U+DC00 至 U+DFFF 的規(guī)定為「Low Surrogates」,也是1024個(gè)。 他們組合出現(xiàn),就又可以多表示1048576中字符。 看一下String.codePointAt這個(gè)方法, static int codePointAtImpl(char[] a, int index, int limit) { if (isHighSurrogate(c1) && ++index < limit) { if (isLowSurrogate(c2)) { return toCodePoint(c1, c2);
其中有兩個(gè)方法isHighSurrogate、isLowSurrogate。 第一個(gè)方法判斷是否為高代理項(xiàng)代碼單元,即在'\uD800'與'\uDBFF'之間, 第二個(gè)方法判斷是否為低代理項(xiàng)代碼單元,即在'\uDC00'與'\uDFFF'之間。 codePointAtImpl方法判斷當(dāng)前char是高代理項(xiàng)代碼單元,下一個(gè)是低代理項(xiàng)代碼單元,則這兩個(gè)char是一個(gè)codepoint。 再來看一下unicode轉(zhuǎn)UTF-16的方法 如果U<0x10000,U的UTF-16編碼就是U對(duì)應(yīng)的16位無符號(hào)整數(shù)(為書寫簡便,下文將16位無符號(hào)整數(shù)記作WORD)。 如果U≥0x10000,我們先計(jì)算U'=U-0x10000,然后將U'寫成二進(jìn)制形式:yyyy yyyy yyxx xxxx xxxx,U的UTF-16編碼(二進(jìn)制)就是:110110yyyyyyyyyy 110111xxxxxxxxxx。
還是以U+1F603這個(gè)?為例子,U'=U-0x10000=F603 寫成2進(jìn)制就是1111011000000011,不足20位前面補(bǔ)0, 變成0000111101-1000000011,替換y和x就是1101100000111101,1101111000000011,最后UTF-16編碼就是[d83d,de03] 和上面一樣。
|