Java文字列のUnicodeコードポイントをどのように反復できますか?


105

だから私はについて知っていますが、コードポイントのオフセットではなくString#codePointAt(int)charオフセットによってインデックスが付けられます。

私は次のようなことを試みることを考えています:

  • インデックスでのString#charAt(int)取得に使用char
  • char高代理の範囲にあるかどうかのテスト
    • もしそうなら、を使用String#codePointAt(int)してコードポイントを取得し、インデックスを2増やします
    • そうでない場合は、指定されたchar値をコードポイントとして使用し、インデックスを1増やします

しかし、私の懸念は

  • 自然に高代理変数の範囲にあるコードポイントが2つのchar値として格納されるのか、1 つの値として格納されるのかわかりません
  • これは、キャラクターを反復処理するための非常に高価な方法のようです
  • 誰かがもっと良いものを考え出したに違いない。

回答:


143

はい、Javaは文字列の内部表現にUTF-16風のエンコーディングを使用し、はい、基本的な多言語プレーン(BMP代理スキームを使用して)のます。

BMP外の文字を処理することがわかっている場合は、Java文字列の文字を反復処理する標準的な方法を次に示します。

final int length = s.length();
for (int offset = 0; offset < length; ) {
   final int codepoint = s.codePointAt(offset);

   // do something with the codepoint

   offset += Character.charCount(codepoint);
}

2
「高額」かどうかについては、Javaに組み込まれている方法は他にありません。しかし、ラテン語/ヨーロッパ語/キリル語/ギリシャ語/ヘブライ語/アラビア語のスクリプトのみを扱っている場合は、s.charAt()を使用するだけで十分です。:)
ジョナサンファインバーグ、

24
しかし、そうすべきではありません。たとえば、プログラムがXMLを出力し、誰かが不明瞭な数学演算子を与えた場合、突然XMLが無効になる可能性があります。
メカニカルカタツムリ

2
使っていoffset = s.offsetByCodePoints(offset, 1);ただろう。offset += Character.charCount(codepoint);代わりに使用することにいくつかの利点はありますか?
Paul Groke、2015年

3
@Mechanicalsnailコメントを理解できません。XMLを出力すると、なぜこの答えが誤動作するのでしょうか?
ギリ

3
@Gili答えは結構です。彼は@Jonathan Feinbergのコメントを参照してcharAt()いましたが、これは悪い考えです
RecursiveExceptionException

72

コードポイントを含むCharSequence#codePointsを返すJava 8が追加されましたIntStream。ストリームを直接使用してそれらを反復できます。

string.codePoints().forEach(c -> ...);

または、ストリームを配列に収集してforループを使用します。

for(int c : string.codePoints().toArray()){
    ...
}

これらの方法は、おそらくJonathan Feinbergsのソリューションよりも高価ですが、読み取り/書き込みが高速で、パフォーマンスの違いは通常わずかです。


3
for (int c : (Iterable<Integer>) () -> string.codePoints().iterator())も動作します。
saka1029 2017

2
:■コード:@ saka1029のやや短いバージョンfor (int c : (Iterable<Integer>) string.codePoints()::iterator) ...
LII


7

foreachループ(ref)で動作する回避策メソッドを追加すると思いますが、Java 8に移動すると、Java 8の新しいString#codePointsメソッドに簡単に変換できます。

次のようにforeachで使用できます。

 for(int codePoint : codePoints(myString)) {
   ....
 }

これがヘルパーmthodです。

public static Iterable<Integer> codePoints(final String string) {
  return new Iterable<Integer>() {
    public Iterator<Integer> iterator() {
      return new Iterator<Integer>() {
        int nextIndex = 0;
        public boolean hasNext() {
          return nextIndex < string.length();
        }
        public Integer next() {
          int result = string.codePointAt(nextIndex);
          nextIndex += Character.charCount(result);
          return result;
        }
        public void remove() {
          throw new UnsupportedOperationException();
        }
      };
    }
  };
}

または、文字列をintの配列に変換するだけの場合(上記のアプローチよりも多くのRAMを使用する可能性があります):

 public static List<Integer> stringToCodePoints(String in) {
    if( in == null)
      throw new NullPointerException("got null");
    List<Integer> out = new ArrayList<Integer>();
    final int length = in.length();
    for (int offset = 0; offset < length; ) {
      final int codepoint = in.codePointAt(offset);
      out.add(codepoint);
      offset += Character.charCount(codepoint);
    }
    return out;
  }

ありがたいことに "codePoints"を使用して、UTF-16(Javaの内部文字列表現)のサロゲートペアネスを安全に処理します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.