2009-10-06 9 views
88

Así que sé acerca de String#codePointAt(int), pero está indexado por el char offset, no por el offset del punto de código.¿Cómo puedo iterar a través de los puntos de código Unicode de una cadena Java?

estoy pensando en probar algo como:

  • String#charAt(int) utilizando para obtener el char en un índice
  • comprobación de que el char está en el high-surrogates range
    • si es así, utilizar String#codePointAt(int) a obtener el punto de código, e incrementar el índice por 2
    • si no, utilice el char valor que el punto de código, e incrementar el índice de 1

Pero mis preocupaciones son

  • no estoy seguro de si los puntos de código que son naturalmente en las altas sustitutos gama se almacenarán como dos char valores o uno
  • esto parece una manera muy costosa de iterar a través de los caracteres
  • alguien debe haber encontrado algo mejor.

Respuesta

116

Sí, Java utiliza una codificación UTF-16-esque de representaciones internas de cuerdas, y, sí, que codifica caracteres fuera del plano básico multilingüe (BMP) utilizando el esquema de subrogación.

Si sabe que va a estar tratando con caracteres fuera del BMP, entonces aquí es la forma canónica para iterar sobre los caracteres de una cadena de 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

En cuanto a si es o no "caro", bueno ... no hay otra forma integrada en Java. Pero si solo está tratando con scripts latinos/europeos/cirílicos/griegos/hebreos/árabes, entonces simplemente se relaciona() con el contenido de su corazón. :) –

+18

Pero no deberías. Por ejemplo, si su programa genera XML y si alguien le da un oscuro operador matemático, de repente su XML puede ser inválido. –

+0

@Jonathan Feinberg Eso es lo que pensé. Pero aquí vino esa especial matemática E. UTF-16 funciona el 99% del tiempo, pero luego se vuelve realmente doloroso. Especialmente cuando los problemas permanecen ocultos durante mucho tiempo. – Martin

5

interactuando sobre los puntos de código se presente como un rasgo solicitar en Sun.

Ver Sun Bug Entry

También hay un ejemplo de cómo iterar sobre los puntos de código de Cuerda allí.

+3

Java 8 ahora tiene un método codePoints() integrado en String: http://docs.oracle.com /javase/8/docs/api/java/lang/CharSequence.html#codePoints –

+0

Consulte también mi respuesta para un método alternativo que puede usar en su lugar para java <8 mientras tanto http://stackoverflow.com/a/ 21791059/32453 – rogerdpack

4

que me gustaría añadir un método de solución que funciona con foreach (ref), además de que puede convertirlo en nueva cadena # puntos de código de método Java de 8 fácilmente cuando se mueve a 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(); 
     } 
     }; 
    } 
    }; 
} 

Entonces se puede usar con foreach así:

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

o, alternativamente, si lo que desea es convertir una cadena en una matriz de int (que podría utilizar más memoria RAM que el enfoque anterior):

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 agregó CharSequence#codePoints que devuelve un IntStream que contiene los puntos de código. Puede utilizar la corriente directamente a iterar sobre ellos:

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

o con un bucle mediante la recopilación de la corriente en una matriz:

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

Estas formas son probablemente más caro que Jonathan Feinbergs's solution, pero son más rápidos de leer/escribir y la diferencia de rendimiento generalmente será insignificante.

+0

'for (int c: (Iterable )() -> string.codePoints(). Iterator())' también funciona. – saka1029

Cuestiones relacionadas