¿Cómo puedo iterar a través de los puntos de código Unicode de una cadena Java?

105

Así que lo sé String#codePointAt(int), pero está indexado por el chardesplazamiento, no por el desplazamiento del punto de código.

Estoy pensando en probar algo como:

Pero mis preocupaciones son

  • No estoy seguro de si los puntos de código que están naturalmente en el rango de altos sustitutos se almacenarán como dos charvalores o uno
  • esto parece una forma terriblemente cara de iterar a través de los personajes
  • alguien debe haber ideado algo mejor.
rampion
fuente

Respuestas:

143

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

Si sabe que tratará con caracteres fuera del BMP, aquí está la forma canónica de iterar sobre los caracteres de una cadena 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
fuente
2
En cuanto a si es "caro" o no, bueno ... no hay otra forma integrada en Java. Pero si está tratando solo con escrituras latinas / europeas / cirílicas / griegas / hebreas / árabes, entonces simplemente use charAt () al contenido de su corazón. :)
Jonathan Feinberg
24
Pero no deberías. Por ejemplo, si su programa genera XML y si alguien le da algún operador matemático oscuro, de repente su XML puede no ser válido.
Caracol mecánico
2
Hubiera usado offset = s.offsetByCodePoints(offset, 1);. ¿Hay algún beneficio en usar en su offset += Character.charCount(codepoint);lugar?
Paul Groke
3
@Mechanicalsnail No entiendo tu comentario. ¿Por qué generar XML haría que esta respuesta se comportara mal?
Gili
3
@Gili la respuesta está bien. Se refería al comentario de @Jonathan Feinberg en el que aboga por usar lo charAt()que es una mala idea
RecursiveExceptionException
72

Java 8 agregado CharSequence#codePointsque devuelve un que IntStreamcontiene los puntos de código. Puede usar la secuencia directamente para iterar sobre ellos:

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

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

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

Estas formas son probablemente más caras que la solución de Jonathan Feinbergs , pero son más rápidas de leer / escribir y la diferencia de rendimiento normalmente será insignificante.

Alex - GlassEditor.com
fuente
3
for (int c : (Iterable<Integer>) () -> string.codePoints().iterator())también funciona.
saka1029
2
Versión un poco más corta de @ saka1029: código s:for (int c : (Iterable<Integer>) string.codePoints()::iterator) ...
Lii
7

Pensé que agregaría un método de solución que funcione con bucles foreach ( ref ), además de que puede convertirlo al nuevo método String # codePoints de java 8 fácilmente cuando se cambia a java 8:

Puedes usarlo con foreach así:

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

Aquí está el método de ayuda:

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

O alternativamente, si solo desea convertir una cadena en una matriz de int (que podría usar más 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;
  }

Afortunadamente, usa "codePoints" maneja de manera segura la pareja sustituta de UTF-16 (representación de cadena interna de Java).

Rogerdpack
fuente