¿Cuál es la forma más fácil / mejor / más correcta de recorrer los caracteres de una cadena en Java?


340

StringTokenizer? Convertir el Stringque una char[]y iterar sobre eso? ¿Algo más?




1
Consulte también stackoverflow.com/questions/8894258/… Los puntos de referencia muestran que String.charAt () es el más rápido para cadenas pequeñas, y el uso de la reflexión para leer el conjunto de caracteres directamente es más rápido para cadenas grandes.
Jonathan


Respuestas:


362

Utilizo un bucle for para iterar la cadena y utilizo charAt()para que cada personaje lo examine. Como la cadena se implementa con una matriz, el charAt()método es una operación de tiempo constante.

String s = "...stuff...";

for (int i = 0; i < s.length(); i++){
    char c = s.charAt(i);        
    //Process char
}

Eso es lo que yo haría. Me parece lo más fácil.

En lo que respecta a la corrección, no creo que exista aquí. Todo se basa en tu estilo personal.


3
¿El compilador incorpora el método length ()?
Uri

77
podría en línea length (), es decir, izar el método detrás de esa llamada unos pocos cuadros, pero es más eficiente hacer esto para (int i = 0, n = s.length (); i <n; i ++) {char c = s.charAt (i); }
Dave Cheney el

32
Desordenar su código para una pequeña ganancia de rendimiento. Evite esto hasta que decida que esta área de código es crítica para la velocidad.
delgado

31
Tenga en cuenta que esta técnica le proporciona caracteres , no puntos de código , lo que significa que puede obtener sustitutos.
Gabe

2
@ikh charAt no es O (1) : ¿Cómo es eso? El código para String.charAt(int)es simplemente hacer value[index]. Creo que estás confundiendo chatAt()con algo más que te da puntos de código.
antak

209

Dos opciones

for(int i = 0, n = s.length() ; i < n ; i++) { 
    char c = s.charAt(i); 
}

o

for(char c : s.toCharArray()) {
    // process c
}

El primero es probablemente más rápido, luego el segundo es probablemente más legible.


26
más uno para colocar s.length () en la expresión de inicialización. Si alguien no sabe por qué, es porque eso solo se evalúa una vez donde si se colocó en la declaración de terminación como i <s.length (), entonces se llamaría s.length () cada vez que se repite.
Dennis

57
Pensé que la optimización del compilador se encargó de eso por ti.
Rhyous

44
@Matthias Puede usar el desensamblador de clases Javap para ver que se evitan las llamadas repetidas a s.length () en la expresión de terminación de bucle. Tenga en cuenta que en el código OP publicado, la llamada a s.length () está en la expresión de inicialización, por lo que la semántica del lenguaje ya garantiza que se invocará solo una vez.
prasopes

3
@prasopes Sin embargo, tenga en cuenta que la mayoría de las optimizaciones de Java ocurren en el tiempo de ejecución, NO en los archivos de clase. Incluso si vio repetidas llamadas a length () que no indica una penalización de tiempo de ejecución, necesariamente.
Isaac

2
@Lasse, la supuesta razón es la eficiencia: su versión llama al método length () en cada iteración, mientras que Dave lo llama una vez en el inicializador. Dicho esto, es muy probable que el optimizador JIT ("justo a tiempo") optimice la llamada adicional, por lo que es probable que solo sea una diferencia de legibilidad sin ganancia real.
Steve

90

Tenga en cuenta que la mayoría de las otras técnicas descritas aquí se desglosan si se trata de caracteres fuera del BMP ( Plano multilingüe básico Unicode ), es decir , puntos de código que están fuera del rango u0000-uFFFF. Esto solo ocurrirá en raras ocasiones, ya que los puntos de código fuera de esto se asignan principalmente a idiomas muertos. Pero hay algunos caracteres útiles fuera de esto, por ejemplo, algunos puntos de código utilizados para la notación matemática, y algunos utilizados para codificar nombres propios en chino.

En ese caso su código será:

String str = "....";
int offset = 0, strLen = str.length();
while (offset < strLen) {
  int curChar = str.codePointAt(offset);
  offset += Character.charCount(curChar);
  // do something with curChar
}

El Character.charCount(int)método requiere Java 5+.

Fuente: http://mindprod.com/jgloss/codepoint.html


1
No entiendo cómo usas nada más que el plano multilingüe básico aquí. curChar todavía tiene 16 bits?
contrato del Prof. Falken incumplió

2
Puede usar un int para almacenar el punto de código completo o bien, cada carácter solo almacenará uno de los dos pares sustitutos que definen el punto de código.
sk.

1
Creo que necesito leer sobre puntos de código y pares sustitutos. ¡Gracias!
contrato del Prof. Falken incumplió

66
+1 ya que esta parece ser la única respuesta correcta para caracteres Unicode fuera del BMP
Jason S

Escribió un código para ilustrar el concepto de iterar sobre puntos de código (en lugar de caracteres): gist.github.com/EmmanuelOga/…
Emmanuel Oga

26

Estoy de acuerdo en que StringTokenizer es excesivo aquí. En realidad probé las sugerencias anteriores y me tomé el tiempo.

Mi prueba fue bastante simple: crear un StringBuilder con aproximadamente un millón de caracteres, convertirlo en una Cadena y atravesar cada uno de ellos con charAt () / después de convertirlo en una matriz de caracteres / con un CharacterIterator mil veces (por supuesto asegurándose de hacer algo en la cadena para que el compilador no pueda optimizar todo el ciclo :-)).

El resultado en mi Powerbook de 2.6 GHz (eso es un mac :-)) y JDK 1.5:

  • Prueba 1: charAt + String -> 3138msec
  • Prueba 2: Cadena convertida a matriz -> 9568mseg
  • Prueba 3: Charing StringBuilder -> 3536mseg
  • Prueba 4: CharacterIterator y String -> 12151msec

Como los resultados son significativamente diferentes, la forma más directa también parece ser la más rápida. Curiosamente, charAt () de un StringBuilder parece ser un poco más lento que el de String.

Por cierto, sugiero no usar CharacterIterator ya que considero que su abuso del carácter '\ uFFFF' como "final de la iteración" es un truco realmente horrible. En los grandes proyectos siempre hay dos tipos que usan el mismo tipo de pirateo para dos propósitos diferentes y el código se bloquea realmente misteriosamente.

Aquí hay una de las pruebas:

    int count = 1000;
    ...

    System.out.println("Test 1: charAt + String");
    long t = System.currentTimeMillis();
    int sum=0;
    for (int i=0; i<count; i++) {
        int len = str.length();
        for (int j=0; j<len; j++) {
            if (str.charAt(j) == 'b')
                sum = sum + 1;
        }
    }
    t = System.currentTimeMillis()-t;
    System.out.println("result: "+ sum + " after " + t + "msec");

1
Esto tiene el mismo problema descrito aquí: stackoverflow.com/questions/196830/…
Emmanuel Oga

22

En Java 8 podemos resolverlo como:

String str = "xyz";
str.chars().forEachOrdered(i -> System.out.print((char)i));
str.codePoints().forEachOrdered(i -> System.out.print((char)i));

El método chars () devuelve un IntStreamcomo se menciona en doc :

Devuelve una secuencia de int cero que extiende los valores de caracteres de esta secuencia. Cualquier carácter que se asigne a un punto de código sustituto se pasa sin interpretación. Si la secuencia está mutada mientras se lee la secuencia, el resultado no está definido.

El método codePoints()también devuelve un IntStreamsegún el documento:

Devuelve una secuencia de valores de puntos de código de esta secuencia. Cualquier par sustituto encontrado en la secuencia se combina como si fuera por Character.toCodePoint y el resultado se pasa a la secuencia. Cualquier otra unidad de código, incluidos los caracteres BMP ordinarios, los sustitutos no apareados y las unidades de código indefinidas, se extienden a cero a valores int que luego se pasan a la secuencia.

¿En qué se diferencian char y code point? Como se menciona en este artículo:

Unicode 3.1 agregó caracteres suplementarios, elevando el número total de caracteres a más de 216 caracteres que se pueden distinguir por un solo 16 bits char . Por lo tanto, un charvalor ya no tiene un mapeo uno a uno a la unidad semántica fundamental en Unicode. JDK 5 se actualizó para admitir el conjunto más grande de valores de caracteres. En lugar de cambiar la definición del chartipo, algunos de los nuevos caracteres suplementarios están representados por un par sustituto de dos charvalores. Para reducir la confusión de nombres, se usará un punto de código para referirse al número que representa un carácter Unicode particular, incluidos los complementarios.

Finalmente por qué forEachOrderedy no forEach?

El comportamiento de forEaches explícitamente no determinista donde, a medida que forEachOrderedrealiza una acción para cada elemento de esta secuencia, en el orden de encuentro de la secuencia si la secuencia tiene un orden de encuentro definido. Por forEachlo tanto , no garantiza que se mantenga el pedido. También revise esta pregunta para más.

Para ver la diferencia entre un personaje, un punto de código, un glifo y un grafema, consulte esta pregunta .


21

Hay algunas clases dedicadas para esto:

import java.text.*;

final CharacterIterator it = new StringCharacterIterator(s);
for(char c = it.first(); c != CharacterIterator.DONE; c = it.next()) {
   // process c
   ...
}

77
Parece una exageración para algo tan simple como iterar sobre una matriz de caracteres inmutable.
ddimitrov

1
No veo por qué esto es exagerado. Los iteradores son la forma más java-ish de hacer cualquier cosa ... iterativa. El StringCharacterIterator seguramente aprovechará al máximo la inmutabilidad.
delgado

2
De acuerdo con @ddimitrov: esto es exagerado. La única razón para usar un iterador sería aprovechar foreach, que es un poco más fácil de "ver" que un bucle for. Si va a escribir un bucle for convencional de todos modos, entonces podría usar charAt ()
Rob Gilliam

3
El uso del iterador de caracteres es probablemente la única forma correcta de iterar sobre los caracteres, porque Unicode requiere más espacio del que charproporciona Java . Un Java charcontiene 16 bits y puede contener caracteres Unicode hasta U + FFFF, pero Unicode especifica caracteres hasta U + 10FFFF. El uso de 16 bits para codificar Unicode da como resultado una codificación de caracteres de longitud variable. La mayoría de las respuestas en esta página asumen que la codificación Java es una codificación de longitud constante, lo cual es incorrecto.
ceving

3
@ceving No parece que un iterador de caracteres lo ayude con caracteres que no son BMP: oracle.com/us/technologies/java/supplementary-142654.html
Bruno De Fraine

18

Si tienes guayaba en tu classpath, la siguiente es una alternativa bastante legible. La guayaba incluso tiene una implementación de Lista personalizada bastante sensata para este caso, por lo que esto no debería ser ineficiente.

for(char c : Lists.charactersOf(yourString)) {
    // Do whatever you want     
}

ACTUALIZACIÓN: Como señaló @Alex, con Java 8 también hay CharSequence#charsque usarlo. Incluso el tipo es IntStream, por lo que se puede asignar a caracteres como:

yourString.chars()
        .mapToObj(c -> Character.valueOf((char) c))
        .forEach(c -> System.out.println(c)); // Or whatever you want

Si necesita hacer algo complejo, vaya con el bucle for + guava ya que no puede mutar variables (por ejemplo, enteros y cadenas) definidas fuera del alcance de forEach dentro de forEach. Lo que esté dentro de forEach tampoco puede arrojar excepciones marcadas, por lo que a veces eso también es molesto.
sabujp

13

Si necesita iterar a través de los puntos de código de a String(vea esta respuesta ), una forma más corta / más legible es usar el CharSequence#codePointsmétodo agregado en Java 8:

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

o usando la secuencia directamente en lugar de un bucle for:

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

También existe CharSequence#charssi desea una secuencia de los caracteres (aunque es un IntStream, ya que no hay CharStream).


3

No lo usaría, StringTokenizerya que es una de las clases en el JDK que es legado.

El javadoc dice:

StringTokenizeres una clase heredada que se conserva por razones de compatibilidad, aunque se desaconseja su uso en el nuevo código. Se recomienda que cualquiera que busque esta funcionalidad use el método dividido Stringo el java.util.regexpaquete en su lugar.


El tokenizador de cadenas es una forma perfectamente válida (y más eficiente) para iterar sobre tokens (es decir, palabras en una oración). Definitivamente es una exageración para iterar sobre caracteres. Estoy rechazando su comentario como engañoso.
ddimitrov

3
ddimitrov: No entiendo cómo señalar que StringTokenizer no se recomienda INCLUYENDO una cita del JavaDoc ( java.sun.com/javase/6/docs/api/java/util/StringTokenizer.html ) para indicar que tal es engañoso. Upvoted para compensar.
Powerlord

1
Gracias Sr. Bemrose ... Supongo que la cita de bloque citada debería haber sido clara, donde probablemente se debería inferir que las correcciones de errores activas no se comprometerán con StringTokenizer.
Alan

2

Si necesita rendimiento, debe probar en su entorno. Ninguna otra manera.

Aquí el código de ejemplo:

int tmp = 0;
String s = new String(new byte[64*1024]);
{
    long st = System.nanoTime();
    for(int i = 0, n = s.length(); i < n; i++) {
        tmp += s.charAt(i);
    }
    st = System.nanoTime() - st;
    System.out.println("1 " + st);
}

{
    long st = System.nanoTime();
    char[] ch = s.toCharArray();
    for(int i = 0, n = ch.length; i < n; i++) {
        tmp += ch[i];
    }
    st = System.nanoTime() - st;
    System.out.println("2 " + st);
}
{
    long st = System.nanoTime();
    for(char c : s.toCharArray()) {
        tmp += c;
    }
    st = System.nanoTime() - st;
    System.out.println("3 " + st);
}
System.out.println("" + tmp);

En Java en línea obtengo:

1 10349420
2 526130
3 484200
0

En Android x86 API 17 obtengo:

1 9122107
2 13486911
3 12700778
0

0

Consulte los Tutoriales de Java: cadenas .

public class StringDemo {
    public static void main(String[] args) {
        String palindrome = "Dot saw I was Tod";
        int len = palindrome.length();
        char[] tempCharArray = new char[len];
        char[] charArray = new char[len];

        // put original string in an array of chars
        for (int i = 0; i < len; i++) {
            tempCharArray[i] = palindrome.charAt(i);
        } 

        // reverse array of chars
        for (int j = 0; j < len; j++) {
            charArray[j] = tempCharArray[len - 1 - j];
        }

        String reversePalindrome =  new String(charArray);
        System.out.println(reversePalindrome);
    }
}

Ponga la longitud int leny use el forbucle.


1
Estoy empezando a sentirme un poco spammer ... si existe esa palabra :). Pero esta solución también tiene el problema descrito aquí: tiene el mismo problema descrito aquí: stackoverflow.com/questions/196830/…
Emmanuel Oga

0

StringTokenizer es totalmente inadecuado para la tarea de dividir una cadena en sus caracteres individuales. Con String#split()usted puede hacerlo fácilmente utilizando una expresión regular que no coincida con nada, por ejemplo:

String[] theChars = str.split("|");

Pero StringTokenizer no usa expresiones regulares, y no hay una cadena delimitadora que pueda especificar que no coincida con la nada entre los caracteres. No es un lindo truco que puede utilizar para lograr lo mismo: utilizar la cadena como la cadena delimitadora (haciendo todos los personajes en un delimitador) y tienen que devolver los delimitadores:

StringTokenizer st = new StringTokenizer(str, str, true);

Sin embargo, solo menciono estas opciones con el propósito de descartarlas. Ambas técnicas dividen la cadena original en cadenas de un carácter en lugar de primitivas de caracteres, y ambas implican una gran sobrecarga en forma de creación de objetos y manipulación de cadenas. Compare eso con llamar a charAt () en un bucle for, que prácticamente no genera gastos generales.


0

Elaborando sobre esta respuesta y esta respuesta .

Las respuestas anteriores señalan el problema de muchas de las soluciones aquí que no iteran por el valor del punto de código: tendrían problemas con cualquier carácter sustituto . Los documentos de Java también describen el problema aquí (consulte "Representaciones de caracteres Unicode"). De todos modos, aquí hay un código que usa algunos caracteres sustitutos reales del conjunto Unicode suplementario, y los convierte de nuevo en una Cadena. Tenga en cuenta que .toChars () devuelve una serie de caracteres: si se trata de sustitutos, necesariamente tendrá dos caracteres. Este código debería funcionar para cualquier carácter Unicode.

    String supplementary = "Some Supplementary: 𠜎𠜱𠝹𠱓";
    supplementary.codePoints().forEach(cp -> 
            System.out.print(new String(Character.toChars(cp))));

0

¡Este código de ejemplo te ayudará!

import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;

public class Solution {
    public static void main(String[] args) {
        HashMap<String, Integer> map = new HashMap<String, Integer>();
        map.put("a", 10);
        map.put("b", 30);
        map.put("c", 50);
        map.put("d", 40);
        map.put("e", 20);
        System.out.println(map);

        Map sortedMap = sortByValue(map);
        System.out.println(sortedMap);
    }

    public static Map sortByValue(Map unsortedMap) {
        Map sortedMap = new TreeMap(new ValueComparator(unsortedMap));
        sortedMap.putAll(unsortedMap);
        return sortedMap;
    }

}

class ValueComparator implements Comparator {
    Map map;

    public ValueComparator(Map map) {
        this.map = map;
    }

    public int compare(Object keyA, Object keyB) {
        Comparable valueA = (Comparable) map.get(keyA);
        Comparable valueB = (Comparable) map.get(keyB);
        return valueB.compareTo(valueA);
    }
}

0

Por lo general, hay dos formas de iterar a través de una cadena en java que ya ha sido respondida por varias personas aquí en este hilo, solo agregando mi versión de él Primero está usando

String s = sc.next() // assuming scanner class is defined above
for(int i=0; i<s.length; i++){
     s.charAt(i)   // This being the first way and is a constant time operation will hardly add any overhead
  }

char[] str = new char[10];
str = s.toCharArray() // this is another way of doing so and it takes O(n) amount of time for copying contents from your string class to character array

Si el rendimiento está en juego, recomendaré usar el primero en tiempo constante, si no es así, ir con el segundo hace que su trabajo sea más fácil teniendo en cuenta la inmutabilidad con las clases de cadena en java.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.