首頁  >  文章  >  Java  >  Java中如何使用Unicode代理程式設計

Java中如何使用Unicode代理程式設計

PHPz
PHPz轉載
2023-05-06 20:43:18775瀏覽

順序存取

順序存取是在 Java 語言中處理字串的基本操作。在這種方法下,輸入字串中的每個字元從頭到尾按順序訪問,或有時從尾至頭訪問。本小節將討論使用順序存取方法從一個字串建立一個 32 位元碼位數群組的 7 個技術範例,並估計它們的處理時間。

範例1-1:基準測試(不支援代理對)

#清單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 <p>#儘管這個範例不支援代理對,但它提供了一個處理時間基準來比較後續順序訪問範例。 </p><p><strong>範例 1-2:使用 isSurrogatePair()</strong></p><p>清單 2 使用 isSurrogatePair() 來計算代理程式對總數。計數之後,它分配足夠的記憶體以便一個碼位數組儲存這個值。然後,它進入一個順序存取循環,使用 isHighSurrogate() 和 isLowSurrogate() 確定每個代理程式對字元是高代理還是低代理。當它發現一個高代理後面帶一個低代理時,它使用 toCodePoint() 將該代理對轉換為一個碼位值並將當前索引值增加 2。否則,它將這個 char 類型值直接指派給一個碼位值並將當前索引值增加 1。這個範例的處理時間比 範例 1-1 長 1.38 倍。 </p><p><strong>清單 2. 有限支援</strong></p><pre class="brush:php;toolbar:false">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 <p>清單 2 中更新軟體的方法很幼稚。它比較麻煩,需要大量修改,使得產生的軟體很脆弱且今後難以更改。具體而言,這些問題是:</p><p>◆需要計算碼位的數量以分配足夠的記憶體</p><p>◆很難取得字串中的指定索引的正確碼位值</p> <p>◆很難為下一個處理步驟正確移動目前索引</p><p>一個改進後的演算法出現在下一個範例中。 </p><p><strong>範例:基本支援</strong></p>##Java 1.5 提供了 codePointCount()、codePointAt() 和 offsetByCodePoints() 方法來分別處理 範例 1-2 的 3 個問題。清單3 使用這些方法來改善這個演算法的可讀性:<p></p><p>#清單3. 基本支援<strong></strong></p><pre class="brush:php;toolbar:false">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 但是,清單3 的處理時間比清單1 長2.8倍。 <p></p><p>範例1-4:使用codePointBefore()<strong></strong></p>當offsetByCodePoints() 接收負數作為第二個參數時,它就能計算出一個距離字串頭的絕對偏移值。接下來,codePointBefore() 能夠傳回一個指定索引前面的碼位值。這些方法用於清單4 中從尾至頭遍歷字串:<p></p><p>清單4. 使用codePointBefore() 的基本支援<strong></strong></p><pre class="brush:php;toolbar:false">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;  }
這個範例的處理時間&mdash ; 比範例1-1 長2.72 倍— 比範例1-3 快一些。通常,當您比較零而不是非零值時,JVM 中的程式碼大小會小一些,有時會提高效能。但是,微小的改進可能不值得犧牲可讀性。

範例 1-5:使用 charCount()

範例 1-3 和 1-4 提供基本的代理對支援。他們不需要任何臨時變量,是健壯的編碼方法。要取得更短的處理時間,使用charCount() 而不是offsetByCodePoints() 是有效的,但需要一個臨時變數來存放碼位值,如清單5 所示:

##清單5.使用charCount() 的最佳化支援

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 
清單5 的處理時間降低到比範例1-1 長1.68 倍。

範例1-6:存取一個char 陣列

清單6 在使用範例1-5 中展示的最佳化的同時直接存取一個char 類型陣列:

清單6. 使用一個char 陣列的最佳化支援

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 
#char 陣列是使用toCharArray() 從字串複製而來的。效能得到改善,因為對數組的直接存取比透過一個方法的間接存取要快。處理時間比 範例 1-1 長 1.51 倍。但是,當呼叫時,toCharArray() 需要一些開銷來建立一個新陣列並將資料複製到陣列中。 String 類別提供的那些方便的方法也不能被使用。但是,這個演算法在處理大量資料時有用。

範例1-7:一個物件導向的演算法

這個範例的物件導向演算法使用CharBuffer 類,如清單7 所示:

清單7. 使用CharSequence 的物件導向演算法

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 不需要一個索引來持有目前位置以便進行順序存取。相反,CharBuffer 在內部追蹤當前位置。 Character 類別提供靜態方法 codePointCount() 和 codePointAt(),它們能透過 CharSequence 介面處理 CharBuffer。 CharBuffer 總是將目前位置設定為 CharSequence 的頭。因此,當 codePointAt() 被呼叫時,第二個參數總是設定為 0。處理時間比 範例 1-1 長 2.15 倍。

处理时间比较

这些顺序访问示例的计时测试使用了一个包含 10,000 个代理对和 10,000 个非代理对的样例字符串。码位数组从这个字符串创建 10,000 次。测试环境包括:

◆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 的绝对和相对处理时间以及关联的 API:

表 1. 顺序访问示例的处理时间和 API

Java中如何使用Unicode代理程式設計

随机访问

随机访问是直接访问一个字符串中的任意位置。当字符串被访问时,索引值基于 16 位 char 类型的单位。但是,如果一个字符串使用 32 位码位,那么它不能使用一个基于 32 位码位的单位的索引访问。必须使用 offsetByCodePoints() 来将码位的索引转换为 char 类型的索引。如果算法设计很糟糕,这会导致很差的性能,因为 offsetByCodePoints() 总是通过使用第二个参数从第一个参数计算字符串的内部。在这个小节中,我将比较三个示例,它们通过使用一个短单位来分割一个长字符串。

示例 2-1:基准测试(不支持代理对)

清单 8 展示如何使用一个宽度单位来分割一个字符串。这个基准测试留作后用,不支持代理对。

清单 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 <p>sliceLimit 变量对分割位置有所限制,以避免在剩余的字符串不足以分割当前宽度单位时抛出一个 IndexOutOfBoundsException 实例。这种算法在当前位置超出 sliceLimit 时从 while 循环中跳出后再处理最后的分割。</p>
<p><strong>示例 2-2:使用一个码位索引</strong></p>
<p>清单 9 展示了如何使用一个码位索引来随机访问一个字符串:</p>
<p><strong>清单 9. 糟糕的性能</strong></p>
<pre class="brush:php;toolbar:false">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 <p>清单 9 修改了 清单 8 中的几行。首先,在 Line (1) 中,length() 被 codePointCount() 替代。其次,在 Lines (3)、(4) 和 (6) 中,char 类型的索引通过 offsetByCodePoints() 用码位索引替代。</p>
<p>基本的算法流与 示例 2-1 中的看起来几乎一样。但处理时间根据字符串长度与示例 2-1 的比率同比增加,因为 offsetByCodePoints() 总是从字符串头到指定索引计算字符串内部。</p>
<p><strong>示例 2-3:减少的处理时间</strong></p>
<p>可以使用清单 10 中展示的方法来避免 示例 2-2 的性能问题:</p>
<p><strong>清单 10. 改进的性能</strong></p>
<pre class="brush:php;toolbar:false">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 <p>首先,在 Line (2) 中,(清单 9 中的)表达式 len-width 被 offsetByCodePoints(len,-width) 替代。但是,当 width 的值大于码位的数量时,这会抛出一个 IndexOutOfBoundsException 实例。必须考虑边界条件以避免异常,使用一个带有 try/catch 异常处理程序的子句将是另一个解决方案。如果表达式 len>width*2 为 true,则可以安全地调用 offsetByCodePoints(),因为即使所有码位都被转换为代理对,码位的数量仍会超过 width 的值。或者,如果 codePointCount(0,len)>width 为 true,也可以安全地调用 offsetByCodePoints()。如果是其他情况,sliceLimit 必须设置为 0。</p>
<p>在 Line (4) 中,清单 9 中的表达式 pos + width 必须在 while 循环中使用 offsetByCodePoints(pos,width) 替换。需要计算的量位于 width 的值中,因为第一个参数指定当 width 的值。接下来,在 Line (5) 中,表达式 pos+=width 必须使用表达式 pos=end 替换。这避免两次调用 offsetByCodePoints() 来计算相同的索引。源代码可以被进一步修改以最小化处理时间。</p>
<h3 id="yisu3h-to116">处理时间比较</h3>
<p>图 1 和图 2 展示了示例 2-1、2-2 和 2-3 的处理时间。样例字符串包含相同数量的代理对和非代理对。当字符串的长度和 width 的值被更改时,样例字符串被切割 10,000 次。</p>
<p><img src="https://img.php.cn/upload/article/000/000/164/168337700040575.png" alt="Java中如何使用Unicode代理程式設計"></p>
<p><strong>图 1. 一个分段的常量宽度</strong></p>
<p><img src="https://img.php.cn/upload/article/000/000/164/168337700041405.png" alt="Java中如何使用Unicode代理程式設計"></p>
<p><strong>图 2. 分段的常量计数</strong></p>
<p>示例 2-1 和 2-3 按照长度比例增加了它们的处理时间,但 示例 2-2 按照长度的平方比例增加了处理时间。当字符串长度和 width 的值增加而分段的数量固定时,示例 2-1 拥有一个常量处理时间,而示例 2-2 和 2-3 以 width 的值为比例增加了它们的处理时间。</p>
<h3 id="yisu3h-to127">信息 API</h3>
<p>大多数处理代理的信息 API 拥有两种名称相同的方法。一种接收 16 位 char 类型参数,另一种接收 32 为码位参数。表 2 展示了每个 API 的返回值。第三列针对 U+53F1,第 4 列针对 U+20B9F,最后一列针对 U+D842(即高代理),而 U+20B9F 被转换为 U+D842 加上 U+DF9F 的代理对。如果程序不能处理代理对,则值 U+D842 而不是 U+20B9F 将导致意想不到的结果(在表 2 中以粗斜体表示)。</p>
<p><strong>表 2. 用于代理的信息 API</strong></p>
<p><img src="https://img.php.cn/upload/article/000/000/164/168337700086359.gif" alt="Java中如何使用Unicode代理程式設計"></p>
<h3 id="yisu3h-to133">其他 API</h3>
<p>本小节介绍前面的小节中没有讨论的代理对相关 API。表 3 展示所有这些剩余的 API。所有代理对 API 都包含在表 1、2 和 3 中。</p>
<p><strong>表 3. 其他代理 API</strong></p>
<p><img src="https://img.php.cn/upload/article/000/000/164/168337700025390.gif" alt="Java中如何使用Unicode代理程式設計"></p>
<p>清单 11 展示了从一个码位创建一个字符串的 5 种方法。用于测试的码位是 U+53F1 和 U+20B9F,它们在一个字符串中重复了 100 亿次。清单 11 中的注释部分显示了处理时间:</p>
<p><strong>清单 11. 从一个码位创建一个字符串的 5 种方法</strong></p>
<pre class="brush:php;toolbar:false">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 的处理时间没有明显不同。相反,创建 str5 花费的时间要长得多,因为它使用 String.format(),该方法支持基于本地和格式化信息的灵活输出。str5 方法应该只用于程序的末尾来输出文本。

以上是Java中如何使用Unicode代理程式設計的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:yisu.com。如有侵權,請聯絡admin@php.cn刪除