2009-10-06 2 views
88

Also ich weiß über String#codePointAt(int), aber es wird durch den char Offset, nicht durch den Codepunkt Offset indiziert.Wie kann ich die Unicode-Codepunkte einer Java-Zeichenfolge durchlaufen?

Ich denke, zu versuchen, so etwas wie:

Aber meine Bedenken sind

  • Ich bin nicht sicher, ob Codepunkte, die als gespeichert werden natürlich in den High-Surrogate Bereich zwei char Werte oder eine
  • das scheint wie eine schrecklich teure Art und Weise, um durch die Zeichen
  • jemand muss sich etwas besseres einfallen lassen.

Antwort

116

Ja, Java verwendet eine UTF-16-artige Kodierung für interne Darstellungen von Strings, und, ja, es Zeichen außerhalb der Basic Multilingual Plane kodiert (BMP) das surrogacy Schema verwendet.

Wenn Sie wissen, dass Sie außerhalb der BMP mit Zeichen zu tun haben, dann ist hier der übliche Weg über die Charaktere eines Java-String iterieren:

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

Ob es "teuer" ist, naja ... es gibt keinen anderen Weg, der in Java eingebaut ist. Aber wenn du nur mit lateinischen/europäischen/kyrillischen/griechischen/hebräischen/arabischen Schriften arbeitest, dann gehst du einfach an charAt() nach Herzenslust. :) –

+18

Aber du solltest nicht. Wenn Ihr Programm beispielsweise XML ausgibt und jemand einen obskuren mathematischen Operator angibt, ist Ihr XML-Code möglicherweise ungültig. –

+0

@ Jonathan Feinberg Das habe ich mir gedacht. Aber hier kam der spezielle mathematische E. UTF-16 zu 99% der Zeit - aber dann wird es wirklich schmerzhaft. Vor allem, wenn die Probleme lange Zeit verborgen bleiben. – Martin

5

Iterieren über Codepunkte als Feature eingereicht Anfrage bei Sun.

Siehe Sun Bug Entry

Es gibt auch ein Beispiel dafür, wie es String-Codepunkte iterieren.

+3

Java 8 hat jetzt eine in String integrierte CodePoints() -Methode: http://docs.oracle.com /javase/8/docs/api/java/lang/CharSequence.html#codePoints –

+0

Siehe auch meine Antwort für eine Problemumgehungsmethode, die Sie in der Zwischenzeit für Java <8 verwenden können http://stackoverflow.com/a/ 21791059/32453 – rogerdpack

4

Gedanken würde ich eine Abhilfe Methode hinzufügen, die mit foreach-Schleifen (ref) arbeitet, und Sie können es neue String # Codepoints java 8er leicht Methode konvertieren, wenn Sie zu Java bewegen 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(); 
     } 
     }; 
    } 
    }; 
} 

Dann

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

oder alternativ, wenn Sie nur einen String in ein Array von int konvertiert werden soll (die mehr RAM als der obige Ansatz verwenden könnten): Sie können es mit foreach wie folgt verwenden

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 hinzugefügt CharSequence#codePoints, die eine IntStream zurückgibt, die die Codepunkte enthält. können Sie den Stream verwenden, um direkt über ihnen zu wechseln:

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

oder mit einer for-Schleife durch den Strom in einem Array zu sammeln:

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

Diese Wege sind wahrscheinlich teurer als Jonathan Feinbergs's solution, aber sie sind schneller zu lesen/schreiben und der Leistungsunterschied wird normalerweise unbedeutend sein.

+0

'für (int c: (Iterable ))() -> string.codePoints(). Iterator())' funktioniert auch. – saka1029