2009-10-06 9 views
88

Tak więc wiem o String#codePointAt(int), ale jest on indeksowany przez przesunięcie char, a nie przez offset kodu współrzędnych.Jak mogę iterować przez punkty kodowe Unicode ciągu Java?

myślę o próbuje coś takiego:

Ale moje obawy są

  • Nie jestem pewien, czy codepoints które są naturalnie w wysokich surogatów zasięg będą przechowywane jako dwie wartości lub jedna
  • wydaje się to bardzo kosztownym sposobem na przerabianie znaków. Ktoś musiał wymyślić coś lepszego.

Odpowiedz

116

Tak, Java wykorzystuje kodowanie UTF-16-owskiej dla wewnętrznych reprezentacji smyczki, i, tak, że koduje znaki spoza Basic Multilingual Plane (BMP) za pomocą programu macierzyństwa zastępczego.

Jeśli wiesz, że będziesz mieć do czynienia ze znakami spoza BMP, to tutaj jest kanoniczny sposób iteracyjne ciągu znaków Java wyrażenie:

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

Co do tego, czy jest "droga", czy ... nie ma innej możliwości wbudowania w Javę. Ale jeśli masz do czynienia tylko ze skryptami Latin/European/Cyrillic/Greek/Hebrew/Arabic, to po prostu s.charAt() do treści twojego serca. :) –

+18

Ale nie powinieneś. Na przykład, jeśli twój program wyprowadza XML i jeśli ktoś poda mu jakiś niejasny operator matematyczny, to nagle twój XML może być nieważny. –

+0

@ Jonathan Feinberg Tak myślałem. Ale przyszedł ten specjalny matematyczny E. UTF-16 działa 99% czasu - ale wtedy robi się naprawdę bolesny. Zwłaszcza, gdy problemy pozostają ukryte przez długi czas. – Martin

5

Iteracja nad punktów kodowych przekazany jest jako cecha żądanie w niedz.

Zobacz Sun Bug Entry

Jest też przykładem, w jaki sposób iteracyjne nad codepoints String tam.

+3

Java 8 ma teraz wbudowaną metodę codePoints() na ciąg: http://docs.oracle.com /javase/8/docs/api/java/lang/CharSequence.html#codePoints –

+0

Zobacz także moją odpowiedź na metodę obejścia problemu, której można użyć w tym miejscu dla języka Java <8 w międzyczasie http://stackoverflow.com/a/ 21791059/32453 – rogerdpack

4

myślałem, że dodać metodę obejście, które współpracuje z pętli foreach (ref), plus można przekonwertować go na nowy ciąg # codepoints metody Java 8 jest łatwo po przeniesieniu do java 8:

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

Następnie można go używać z foreach tak:

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

lub na przemian, jeśli po prostu chcesz przekonwertować ciąg do tablicy int (które może wykorzystać więcej pamięci RAM niż powyższego podejścia):

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

Java 8 dodano CharSequence#codePoints, która zwraca IntStream zawierający punkty kodowe. Można użyć strumienia bezpośrednio iteracyjne nad nimi:

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

lub z pętli for zbierając strumienia do tablicy:

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

Te sposoby są prawdopodobnie droższe niż Jonathan Feinbergs's solution, ale są szybsze do odczytu/zapisu, a różnica w wydajności zazwyczaj nie jest znacząca.

+0

'for (int c: (Iterable )() -> string.codePoints(). Iterator())' również działa. – saka1029

Powiązane problemy