Bagaimana cara saya melakukan iterasi melalui titik kode unicode dari String Java?

105

Jadi saya tahu String#codePointAt(int), tapi itu diindeks oleh charoffset, bukan oleh titik kode offset.

Saya sedang berpikir untuk mencoba sesuatu seperti:

Tapi kekhawatiran saya

  • Saya tidak yakin apakah titik kode yang secara alami berada dalam kisaran pengganti tinggi akan disimpan sebagai dua charnilai atau satu
  • ini sepertinya cara yang sangat mahal untuk mengulang melalui karakter
  • seseorang pasti telah menemukan sesuatu yang lebih baik.
rampion
sumber

Jawaban:

143

Ya, Java menggunakan pengkodean UTF-16-esque untuk representasi internal Strings, dan, ya, itu mengkodekan karakter di luar Basic Multilingual Plane ( BMP ) menggunakan skema pengganti.

Jika Anda tahu Anda akan berurusan dengan karakter di luar BMP, maka berikut adalah cara kanonik untuk mengulangi karakter String 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);
}
Jonathan Feinberg
sumber
2
Adapun apakah itu "mahal" atau tidak, yah ... tidak ada cara lain untuk membangun Java. Tetapi jika Anda hanya berurusan dengan skrip Latin / Eropa / Sirilik / Yunani / Ibrani / Arab, maka Anda tinggal s.charAt () sesuka hati Anda. :)
Jonathan Feinberg
24
Tapi sebaiknya tidak. Misalnya jika program Anda mengeluarkan XML dan jika seseorang memberikan beberapa operator matematika yang tidak jelas, tiba-tiba XML Anda mungkin tidak valid.
Siput mekanik
2
Saya akan menggunakan offset = s.offsetByCodePoints(offset, 1);. Apakah ada keuntungan dalam menggunakan offset += Character.charCount(codepoint);sebagai gantinya?
Paul Groke
3
@Mechanicalsnail Saya tidak mengerti komentar Anda. Mengapa mengeluarkan XML menyebabkan jawaban ini tidak berfungsi dengan baik?
Gili
3
@Gili jawabannya oke. Dia mengacu pada komentar @Jonathan Feinberg di mana dia menganjurkan untuk menggunakan charAt()yang merupakan ide yang buruk
RecursiveExceptionException
72

Java 8 ditambahkan CharSequence#codePointsyang mengembalikan yang IntStreamberisi poin kode. Anda dapat menggunakan aliran langsung untuk mengulanginya:

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

atau dengan perulangan for dengan mengumpulkan aliran ke dalam array:

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

Cara-cara ini mungkin lebih mahal daripada solusi Jonathan Feinbergs , tetapi lebih cepat untuk membaca / menulis dan perbedaan kinerjanya biasanya tidak signifikan.

Alex - GlassEditor.com
sumber
3
for (int c : (Iterable<Integer>) () -> string.codePoints().iterator())juga bekerja.
saka1029
2
Versi @ saka1029: s sedikit lebih pendek:for (int c : (Iterable<Integer>) string.codePoints()::iterator) ...
Lii
7

Iterasi atas poin kode diajukan sebagai permintaan fitur di Sun.

Lihat Sun Bug Entry

Ada juga contoh tentang bagaimana melakukan iterasi melalui String CodePoints di sana.

Alexander Egger
sumber
6
Java 8 sekarang memiliki metode codePoints () yang terpasang di String: docs.oracle.com/javase/8/docs/api/java/lang/…
Dov Wasserman
7

Pikir saya akan menambahkan metode solusi yang bekerja dengan foreach loop ( ref ), ditambah Anda dapat mengonversinya ke metode String # codePoints baru java 8 dengan mudah ketika Anda pindah ke java 8:

Anda dapat menggunakannya dengan foreach seperti ini:

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

Inilah helper 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();
        }
      };
    }
  };
}

Atau secara bergantian jika Anda hanya ingin mengonversi string menjadi larik int (yang mungkin menggunakan lebih banyak RAM daripada pendekatan di atas):

 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;
  }

Untungnya, menggunakan "codePoints" dengan aman menangani pasangan pengganti UTF-16 (representasi string internal java).

rogerdpack
sumber