您好,登錄后才能下訂單哦!
本篇內(nèi)容介紹了“Java如何實(shí)現(xiàn)Unicode代理編程”的有關(guān)知識,在實(shí)際案例的操作過程中,不少人都會(huì)遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
順序訪問是在 Java 語言中處理字符串的一個(gè)基本操作。在這種方法下,輸入字符串中的每個(gè)字符從頭至尾按順序訪問,或者有時(shí)從尾至頭訪問。本小節(jié)討論使用順序訪問方法從一個(gè)字符串創(chuàng)建一個(gè) 32 位碼位數(shù)組的 7 個(gè)技術(shù)示例,并估計(jì)它們的處理時(shí)間。
示例 1-1:基準(zhǔn)測試(不支持代理對)
清單 1 將 16 位 char 類型值直接分配給 32 位碼位值,完全沒有考慮代理對:
清單 1. 不支持代理對
int[] toCodePointArray(String str) { // Example 1-1 int len = str.length(); // the length of str int[] acp = new int[len]; // an array of code points for (int i = 0, j = 0; i < len; i++) { acp[j++] = str.charAt(i); } return acp; }
盡管這個(gè)示例不支持代理對,但它提供了一個(gè)處理時(shí)間基準(zhǔn)來比較后續(xù)順序訪問示例。
示例 1-2:使用 isSurrogatePair()
清單 2 使用 isSurrogatePair() 來計(jì)算代理對總數(shù)。計(jì)數(shù)之后,它分配足夠的內(nèi)存以便一個(gè)碼位數(shù)組存儲(chǔ)這個(gè)值。然后,它進(jìn)入一個(gè)順序訪問循環(huán),使用 isHighSurrogate() 和 isLowSurrogate() 確定每個(gè)代理對字符是高代理還是低代理。當(dāng)它發(fā)現(xiàn)一個(gè)高代理后面帶一個(gè)低代理時(shí),它使用 toCodePoint() 將該代理對轉(zhuǎn)換為一個(gè)碼位值并將當(dāng)前索引值增加 2。否則,它將這個(gè) char 類型值直接分配給一個(gè)碼位值并將當(dāng)前索引值增加 1。這個(gè)示例的處理時(shí)間比 示例 1-1 長 1.38 倍。
清單 2. 有限支持
int[] toCodePointArray(String str) { // Example 1-2 int len = str.length(); // the length of str int[] acp; // an array of code points int surrogatePairCount = 0; // the count of surrogate pairs for (int i = 1; i < len; i++) { if (Character.isSurrogatePair(str.charAt(i - 1), str.charAt(i))) { surrogatePairCount++; i++; } } acp = new int[len - surrogatePairCount]; for (int i = 0, j = 0; i < len; i++) { char ch0 = str.charAt(i); // the current char if (Character.isHighSurrogate(ch0) && i + 1 < len) { char ch2 = str.charAt(i + 1); // the next char if (Character.isLowSurrogate(ch2)) { acp[j++] = Character.toCodePoint(ch0, ch2); i++; continue; } } acp[j++] = ch0; } return acp; }
清單 2 中更新軟件的方法很幼稚。它比較麻煩,需要大量修改,使得生成的軟件很脆弱且今后難以更改。具體而言,這些問題是:
◆需要計(jì)算碼位的數(shù)量以分配足夠的內(nèi)存
◆很難獲得字符串中的指定索引的正確碼位值
◆很難為下一個(gè)處理步驟正確移動(dòng)當(dāng)前索引
一個(gè)改進(jìn)后的算法出現(xiàn)在下一個(gè)示例中。
示例:基本支持
Java 1.5 提供了 codePointCount()、codePointAt() 和 offsetByCodePoints() 方法來分別處理 示例 1-2 的 3 個(gè)問題。清單 3 使用這些方法來改善這個(gè)算法的可讀性:
清單 3. 基本支持
int[] toCodePointArray(String str) { // Example 1-3 int len = str.length(); // the length of str int[] acp = new int[str.codePointCount(0, len)]; for (int i = 0, j = 0; i < len; i = str.offsetByCodePoints(i, 1)) { acp[j++] = str.codePointAt(i); } return acp; }
但是,清單 3 的處理時(shí)間比 清單 1 長 2.8 倍。
示例 1-4:使用 codePointBefore()
當(dāng) offsetByCodePoints() 接收一個(gè)負(fù)數(shù)作為第二個(gè)參數(shù)時(shí),它就能計(jì)算一個(gè)距離字符串頭的絕對偏移值。接下來,codePointBefore() 能夠返回一個(gè)指定索引前面的碼位值。這些方法用于清單 4 中從尾至頭遍歷字符串:
清單 4. 使用 codePointBefore() 的基本支持
int[] toCodePointArray(String str) { // Example 1-4 int len = str.length(); // the length of str int[] acp = new int[str.codePointCount(0, len)]; int j = acp.length; // an index for acp for (int i = len; i > 0; i = str.offsetByCodePoints(i, -1)) { acp[--j] = str.codePointBefore(i); } return acp; }
這個(gè)示例的處理時(shí)間 — 比 示例 1-1 長 2.72 倍 — 比 示例 1-3 快一些。通常,當(dāng)您比較零而不是非零值時(shí),JVM 中的代碼大小要小一些,這有時(shí)會(huì)提高性能。但是,微小的改進(jìn)可能不值得犧牲可讀性。
示例 1-5:使用 charCount()
示例 1-3 和 1-4 提供基本的代理對支持。他們不需要任何臨時(shí)變量,是健壯的編碼方法。要獲取更短的處理時(shí)間,使用 charCount() 而不是 offsetByCodePoints() 是有效的,但需要一個(gè)臨時(shí)變量來存放碼位值,如清單 5 所示:
清單 5. 使用 charCount() 的優(yōu)化支持
int[] toCodePointArray(String str) { // Example 1-5 int len = str.length(); // the length of str int[] acp = new int[str.codePointCount(0, len)]; int j = 0; // an index for acp for (int i = 0, cp; i < len; i += Character.charCount(cp)) { cp = str.codePointAt(i); acp[j++] = cp; } return acp; }
清單 5 的處理時(shí)間降低到比 示例 1-1 長 1.68 倍。
示例 1-6:訪問一個(gè) char 數(shù)組
清單 6 在使用 示例 1-5 中展示的優(yōu)化的同時(shí)直接訪問一個(gè) char 類型數(shù)組:
清單 6. 使用一個(gè) char 數(shù)組的優(yōu)化支持
int[] toCodePointArray(String str) { // Example 1-6 char[] ach = str.toCharArray(); // a char array copied from str int len = ach.length; // the length of ach int[] acp = new int[Character.codePointCount(ach, 0, len)]; int j = 0; // an index for acp for (int i = 0, cp; i < len; i += Character.charCount(cp)) { cp = Character.codePointAt(ach, i); acp[j++] = cp; } return acp; }
char 數(shù)組是使用 toCharArray() 從字符串復(fù)制而來的。性能得到改善,因?yàn)閷?shù)組的直接訪問比通過一個(gè)方法的間接訪問要快。處理時(shí)間比 示例 1-1 長 1.51 倍。但是,當(dāng)調(diào)用時(shí),toCharArray() 需要一些開銷來創(chuàng)建一個(gè)新數(shù)組并將數(shù)據(jù)復(fù)制到數(shù)組中。String 類提供的那些方便的方法也不能被使用。但是,這個(gè)算法在處理大量數(shù)據(jù)時(shí)有用。
示例 1-7:一個(gè)面向?qū)ο蟮乃惴?/strong>
這個(gè)示例的面向?qū)ο笏惴ㄊ褂?CharBuffer 類,如清單 7 所示:
清單 7. 使用 CharSequence 的面向?qū)ο笏惴?/strong>
int[] toCodePointArray(String str) { // Example 1-7 CharBuffer cBuf = CharBuffer.wrap(str); // Buffer to wrap str IntBuffer iBuf = IntBuffer.allocate( // Buffer to store code points Character.codePointCount(cBuf, 0, cBuf.capacity())); while (cBuf.remaining() > 0) { int cp = Character.codePointAt(cBuf, 0); // the current code point iBuf.put(cp); cBuf.position(cBuf.position() + Character.charCount(cp)); } return iBuf.array(); }
與前面的示例不同,清單 7 不需要一個(gè)索引來持有當(dāng)前位置以便進(jìn)行順序訪問。相反,CharBuffer 在內(nèi)部跟蹤當(dāng)前位置。Character 類提供靜態(tài)方法 codePointCount() 和 codePointAt(),它們能通過 CharSequence 接口處理 CharBuffer。CharBuffer 總是將當(dāng)前位置設(shè)置為 CharSequence 的頭。因此,當(dāng) codePointAt() 被調(diào)用時(shí),第二個(gè)參數(shù)總是設(shè)置為 0。處理時(shí)間比 示例 1-1 長 2.15 倍。
這些順序訪問示例的計(jì)時(shí)測試使用了一個(gè)包含 10,000 個(gè)代理對和 10,000 個(gè)非代理對的樣例字符串。碼位數(shù)組從這個(gè)字符串創(chuàng)建 10,000 次。測試環(huán)境包括:
◆OS:Microsoft Windows® XP Professional SP2
◆Java:IBM Java 1.5 SR7
◆CPU:Intel® Core 2 Duo CPU T8300 @ 2.40GHz
◆Memory:2.97GB RAM
表 1 展示了示例 1-1 到 1-7 的絕對和相對處理時(shí)間以及關(guān)聯(lián)的 API:
表 1. 順序訪問示例的處理時(shí)間和 API
隨機(jī)訪問是直接訪問一個(gè)字符串中的任意位置。當(dāng)字符串被訪問時(shí),索引值基于 16 位 char 類型的單位。但是,如果一個(gè)字符串使用 32 位碼位,那么它不能使用一個(gè)基于 32 位碼位的單位的索引訪問。必須使用 offsetByCodePoints() 來將碼位的索引轉(zhuǎn)換為 char 類型的索引。如果算法設(shè)計(jì)很糟糕,這會(huì)導(dǎo)致很差的性能,因?yàn)?offsetByCodePoints() 總是通過使用第二個(gè)參數(shù)從第一個(gè)參數(shù)計(jì)算字符串的內(nèi)部。在這個(gè)小節(jié)中,我將比較三個(gè)示例,它們通過使用一個(gè)短單位來分割一個(gè)長字符串。
示例 2-1:基準(zhǔn)測試(不支持代理對)
清單 8 展示如何使用一個(gè)寬度單位來分割一個(gè)字符串。這個(gè)基準(zhǔn)測試留作后用,不支持代理對。
清單 8. 不支持代理對
String[] sliceString(String str, int width) { // Example 2-1 // It must be that "str != null && width > 0". List<String> slices = new ArrayList<String>(); int len = str.length(); // (1) the length of str int sliceLimit = len - width; // (2) Do not slice beyond here. int pos = 0; // the current position per char type while (pos < sliceLimit) { int begin = pos; // (3) int end = pos + width; // (4) slices.add(str.substring(begin, end)); pos += width; // (5) } slices.add(str.substring(pos)); // (6) return slices.toArray(new String[slices.size()]); }
sliceLimit 變量對分割位置有所限制,以避免在剩余的字符串不足以分割當(dāng)前寬度單位時(shí)拋出一個(gè) IndexOutOfBoundsException 實(shí)例。這種算法在當(dāng)前位置超出 sliceLimit 時(shí)從 while 循環(huán)中跳出后再處理最后的分割。
示例 2-2:使用一個(gè)碼位索引
清單 9 展示了如何使用一個(gè)碼位索引來隨機(jī)訪問一個(gè)字符串:
清單 9. 糟糕的性能
String[] sliceString(String str, int width) { // Example 2-2 // It must be that "str != null && width > 0". List<String> slices = new ArrayList<String>(); int len = str.codePointCount(0, str.length()); // (1) code point count [Modified] int sliceLimit = len - width; // (2) Do not slice beyond here. int pos = 0; // the current position per code point while (pos < sliceLimit) { int begin = str.offsetByCodePoints(0, pos); // (3) [Modified] int end = str.offsetByCodePoints(0, pos + width); // (4) [Modified] slices.add(str.substring(begin, end)); pos += width; // (5) } slices.add(str.substring(str.offsetByCodePoints(0, pos))); // (6) [Modified] return slices.toArray(new String[slices.size()]); }
清單 9 修改了 清單 8 中的幾行。首先,在 Line (1) 中,length() 被 codePointCount() 替代。其次,在 Lines (3)、(4) 和 (6) 中,char 類型的索引通過 offsetByCodePoints() 用碼位索引替代。
基本的算法流與 示例 2-1 中的看起來幾乎一樣。但處理時(shí)間根據(jù)字符串長度與示例 2-1 的比率同比增加,因?yàn)?offsetByCodePoints() 總是從字符串頭到指定索引計(jì)算字符串內(nèi)部。
示例 2-3:減少的處理時(shí)間
可以使用清單 10 中展示的方法來避免 示例 2-2 的性能問題:
清單 10. 改進(jìn)的性能
String[] sliceString(String str, int width) { // Example 2-3 // It must be that "str != null && width > 0". List<String> slices = new ArrayList<String>(); int len = str.length(); // (1) the length of str int sliceLimit // (2) Do not slice beyond here. [Modified] = (len >= width * 2 || str.codePointCount(0, len) > width) ? str.offsetByCodePoints(len, -width) : 0; int pos = 0; // the current position per char type while (pos < sliceLimit) { int begin = pos; // (3) int end = str.offsetByCodePoints(pos, width); // (4) [Modified] slices.add(str.substring(begin, end)); pos = end; // (5) [Modified] } slices.add(str.substring(pos)); // (6) return slices.toArray(new String[slices.size()]); }
首先,在 Line (2) 中,(清單 9 中的)表達(dá)式 len-width 被 offsetByCodePoints(len,-width) 替代。但是,當(dāng) width 的值大于碼位的數(shù)量時(shí),這會(huì)拋出一個(gè) IndexOutOfBoundsException 實(shí)例。必須考慮邊界條件以避免異常,使用一個(gè)帶有 try/catch 異常處理程序的子句將是另一個(gè)解決方案。如果表達(dá)式 len>width*2 為 true,則可以安全地調(diào)用 offsetByCodePoints(),因?yàn)榧词顾写a位都被轉(zhuǎn)換為代理對,碼位的數(shù)量仍會(huì)超過 width 的值?;蛘?,如果 codePointCount(0,len)>width 為 true,也可以安全地調(diào)用 offsetByCodePoints()。如果是其他情況,sliceLimit 必須設(shè)置為 0。
在 Line (4) 中,清單 9 中的表達(dá)式 pos + width 必須在 while 循環(huán)中使用 offsetByCodePoints(pos,width) 替換。需要計(jì)算的量位于 width 的值中,因?yàn)榈谝粋€(gè)參數(shù)指定當(dāng) width 的值。接下來,在 Line (5) 中,表達(dá)式 pos+=width 必須使用表達(dá)式 pos=end 替換。這避免兩次調(diào)用 offsetByCodePoints() 來計(jì)算相同的索引。源代碼可以被進(jìn)一步修改以最小化處理時(shí)間。
圖 1 和圖 2 展示了示例 2-1、2-2 和 2-3 的處理時(shí)間。樣例字符串包含相同數(shù)量的代理對和非代理對。當(dāng)字符串的長度和 width 的值被更改時(shí),樣例字符串被切割 10,000 次。
圖 1. 一個(gè)分段的常量寬度
圖 2. 分段的常量計(jì)數(shù)
示例 2-1 和 2-3 按照長度比例增加了它們的處理時(shí)間,但 示例 2-2 按照長度的平方比例增加了處理時(shí)間。當(dāng)字符串長度和 width 的值增加而分段的數(shù)量固定時(shí),示例 2-1 擁有一個(gè)常量處理時(shí)間,而示例 2-2 和 2-3 以 width 的值為比例增加了它們的處理時(shí)間。
大多數(shù)處理代理的信息 API 擁有兩種名稱相同的方法。一種接收 16 位 char 類型參數(shù),另一種接收 32 為碼位參數(shù)。表 2 展示了每個(gè) API 的返回值。第三列針對 U+53F1,第 4 列針對 U+20B9F,最后一列針對 U+D842(即高代理),而 U+20B9F 被轉(zhuǎn)換為 U+D842 加上 U+DF9F 的代理對。如果程序不能處理代理對,則值 U+D842 而不是 U+20B9F 將導(dǎo)致意想不到的結(jié)果(在表 2 中以粗斜體表示)。
表 2. 用于代理的信息 API
本小節(jié)介紹前面的小節(jié)中沒有討論的代理對相關(guān) API。表 3 展示所有這些剩余的 API。所有代理對 API 都包含在表 1、2 和 3 中。
表 3. 其他代理 API
清單 11 展示了從一個(gè)碼位創(chuàng)建一個(gè)字符串的 5 種方法。用于測試的碼位是 U+53F1 和 U+20B9F,它們在一個(gè)字符串中重復(fù)了 100 億次。清單 11 中的注釋部分顯示了處理時(shí)間:
清單 11. 從一個(gè)碼位創(chuàng)建一個(gè)字符串的 5 種方法
int cp = 0x20b9f; // CJK Ideograph Extension B String str1 = new String(new int[]{cp}, 0, 1); // processing time: 206ms String str2 = new String(Character.toChars(cp)); // 187ms String str3 = String.valueOf(Character.toChars(cp)); // 195ms String str4 = new StringBuilder().appendCodePoint(cp).toString(); // 269ms String str5 = String.format("%c", cp); // 3781ms
str1、str2、str3 和 str4 的處理時(shí)間沒有明顯不同。相反,創(chuàng)建 str5 花費(fèi)的時(shí)間要長得多,因?yàn)樗褂?String.format(),該方法支持基于本地和格式化信息的靈活輸出。str5 方法應(yīng)該只用于程序的末尾來輸出文本。
“Java如何實(shí)現(xiàn)Unicode代理編程”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。