ホームページ >Java >&#&チュートリアル >Java で Unicode エージェント プログラミングを使用する方法

Java で Unicode エージェント プログラミングを使用する方法

PHPz
PHPz転載
2023-05-06 20:43:18804ブラウズ

シーケンシャル アクセス

シーケンシャル アクセスは、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>Java 1.5 では、例 1-2 の 3 つの問題をそれぞれ処理する codePointCount()、codePointAt()、および offsetByCodePoints() メソッドが提供されています。リスト 3 では、次のメソッドを使用して、このアルゴリズムの読みやすさを向上させています。 </p><p></p>リスト 3. 基本サポート<p><strong><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 倍の時間がかかります。 。

例 1-4: codePointBefore() の使用

offsetByCodePoints() が 2 番目のパラメーターとして負の数値を受け取ると、コードの先頭からの距離を計算できます。文字列の絶対オフセット値。次に、codePointBefore() は、指定されたインデックスの前のコード ポイント値を返すことができます。これらのメソッドは、リスト 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;  }
を使用した基本サポートこの例の処理時間&mdash ;例 1-1 より 2.72 倍長く、例 1-3 よりわずかに高速です。通常、ゼロ以外の値ではなくゼロを比較すると、JVM 内のコード サイズが小さくなり、パフォーマンスが向上する場合があります。ただし、小さな改善は読みやすさを犠牲にするほどの価値はないかもしれません。

例 1-5: charCount() の使用

例 1-3 および 1-4 は、基本的なサロゲート ペアのサポートを提供します。これらは一時変数を必要とせず、堅牢なコーディング方法です。処理時間を短縮するには、offsetByCodePoints() の代わりに charCount() を使用するのが効果的ですが、リスト 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 倍に短縮されます。 <p><strong></strong>例 1-6: char 配列へのアクセス</p><p></p>リスト 6 例 1-5 に示す最適化を使用しながら、char 型の配列に直接アクセスする:<p> <strong></strong>リスト 6. char 配列の使用のサポートの最適化</p><p></p><pre class="brush:php;toolbar:false">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 クラスが提供する便利なメソッドも使用できません。ただし、このアルゴリズムは大量のデータを処理する場合に役立ちます。 <p><strong></strong>例 1-7: オブジェクト指向アルゴリズム</p><p></p>この例のオブジェクト指向アルゴリズムでは、リスト 7 に示すように CharBuffer クラスを使用します。 <p> <strong>リスト 7. CharSequence を使用したオブジェクト指向アルゴリズム</strong></p><pre class="brush:php;toolbar:false">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 クラスは、CharSequence インターフェイスを通じて CharBuffers を処理する静的メソッド codePointCount() および codePointAt() を提供します。 CharBuffer は常に現在位置を CharSequence の先頭に設定します。したがって、codePointAt() が呼び出されるとき、2 番目のパラメーターは常に 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 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はyisu.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。