Wie kann ich die Unicode-Codepunkte eines Java-Strings durchlaufen?

105

Ich weiß also Bescheid String#codePointAt(int), aber es wird durch den charVersatz indiziert , nicht durch den Codepunktversatz.

Ich denke darüber nach, etwas zu versuchen wie:

  • Verwenden Sie String#charAt(int), um die charan einem Index zu erhalten
  • Testen, ob der charim Bereich der hohen Surrogate liegt
    • Wenn ja, verwenden Sie String#codePointAt(int), um den Codepunkt abzurufen, und erhöhen Sie den Index um 2
    • Wenn nicht, verwenden Sie den angegebenen charWert als Codepunkt und erhöhen Sie den Index um 1

Aber meine Bedenken sind

  • Ich bin nicht sicher, ob Codepunkte, die sich natürlich im Bereich mit hohen Ersatzwerten befinden, als zwei charoder als ein Wert gespeichert werden
  • Dies scheint eine schrecklich teure Möglichkeit zu sein, Zeichen zu durchlaufen
  • jemand muss sich etwas Besseres ausgedacht haben.
Rampion
quelle

Antworten:

143

Ja, Java verwendet eine UTF-16-ähnliche Codierung für interne Darstellungen von Zeichenfolgen und codiert Zeichen außerhalb der BMP (Basic Multilingual Plane ) mithilfe des Leihmutterschaftsschemas.

Wenn Sie wissen, dass Sie mit Zeichen außerhalb des BMP zu tun haben, können Sie die Zeichen eines Java-Strings auf kanonische Weise durchlaufen:

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
quelle
2
Ob es "teuer" ist oder nicht, nun ... es gibt keinen anderen Weg, der in Java eingebaut ist. Aber wenn Sie sich nur mit lateinischen / europäischen / kyrillischen / griechischen / hebräischen / arabischen Skripten beschäftigen, dann tun Sie einfach nach Herzenslust an charAt (). :)
Jonathan Feinberg
24
Aber du solltest nicht. Wenn Ihr Programm beispielsweise XML ausgibt und jemand einen obskuren mathematischen Operator angibt, ist Ihr XML möglicherweise plötzlich ungültig.
Mechanische Schnecke
2
Ich hätte verwendet offset = s.offsetByCodePoints(offset, 1);. Gibt es einen Vorteil bei der Verwendung offset += Character.charCount(codepoint);stattdessen?
Paul Groke
3
@ Mechanicalsnail Ich verstehe deinen Kommentar nicht. Warum würde die Ausgabe von XML dazu führen, dass sich diese Antwort schlecht verhält?
Gili
3
@Gili die Antwort ist in Ordnung. Er bezog sich auf @Jonathan Feinbergs Kommentar, in dem er sich für charAt()eine schlechte Idee einsetzt
RecursiveExceptionException
72

Java 8 hinzugefügt, CharSequence#codePointsdas eine IntStreamenthält, die die Codepunkte enthält. Sie können den Stream direkt verwenden, um darüber zu iterieren:

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

oder mit einer for-Schleife durch Sammeln des Streams in einem Array:

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

Diese Methoden sind wahrscheinlich teurer als die Lösung von Jonathan Feinbergs , aber sie sind schneller zu lesen / schreiben und der Leistungsunterschied ist normalerweise unbedeutend.

Alex - GlassEditor.com
quelle
3
for (int c : (Iterable<Integer>) () -> string.codePoints().iterator())funktioniert auch.
saka1029
2
Etwas kürzere Version von @ saka1029: s Code:for (int c : (Iterable<Integer>) string.codePoints()::iterator) ...
Lii
7

Das Iterieren über Codepunkte wird als Feature-Anfrage bei Sun abgelegt.

Siehe Sun Bug Entry

Es gibt auch ein Beispiel, wie Sie dort über String CodePoints iterieren können.

Alexander Egger
quelle
6
In Java 8 ist jetzt eine codePoints () -Methode in String integriert: docs.oracle.com/javase/8/docs/api/java/lang/…
Dov Wasserman
7

Ich dachte, ich würde eine Workaround-Methode hinzufügen, die mit foreach-Schleifen ( ref ) funktioniert , und Sie können sie in die neuen String # codePoints von Java 8 konvertieren einfach in Methode von Java 8 , wenn Sie zu Java 8 wechseln:

Sie können es mit foreach wie folgt verwenden:

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

Hier ist der Helfer:

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

Oder alternativ, wenn Sie nur eine Zeichenfolge in ein Array von int konvertieren möchten (das möglicherweise mehr RAM als der oben beschriebene Ansatz benötigt):

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

Glücklicherweise verwendet "codePoints" sicher die Ersatzpaarung von UTF-16 (Java's interne String-Darstellung).

Rogerdpack
quelle