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

StackOverflow https://stackoverflow.com/questions/196830

Pregunta

StringTokenizer ? Convierta el String en un char [] e itere sobre eso? ¿Algo más?

¿Fue útil?

Solución

Utilizo un bucle for para iterar la cadena y uso charAt () para que cada carácter lo examine. Como la cadena se implementa con una matriz, el método charAt () 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 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 su estilo personal.

Otros consejos

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.

Tenga en cuenta que la mayoría de las otras técnicas descritas aquí se desglosan si está tratando con caracteres fuera del BMP (Unicode Plano multilingüe básico ), es decir, puntos de código que están fuera del u0000 -uFFFF rango. 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 método Character.charCount (int) requiere Java 5+.

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

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 recorrer 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 - > 3138mseg
  • Prueba 2: Cadena convertida a matriz - > 9568mseg
  • Prueba 3: StringBuilder charAt - > 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 su abuso del carácter '\ uFFFF' como '' fin de iteración '' 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í está 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");

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

Si tiene Guava en su classpath, la siguiente es una alternativa bastante legible . La guayaba incluso tiene una implementación de Lista personalizada bastante razonable 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 # chars para usar. 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

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 IntStream como se menciona en doc :

  

Devuelve una secuencia de int cero-extendiendo los valores char de este   secuencia. Se pasa cualquier carácter que se asigne a un punto de código sustituto   a través de no interpretado. Si la secuencia está mutada mientras la secuencia está   siendo leído, el resultado no está definido.

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

  

Devuelve una secuencia de valores de puntos de código de esta secuencia. Ninguna   los pares sustitutos encontrados en la secuencia se combinan como si por   Character.toCodePoint y el resultado se pasa a la secuencia. Ninguna   otras unidades de código, incluidos los caracteres BMP normales, sin emparejar   los sustitutos y las unidades de código indefinidas se extienden por 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, obteniendo el número total   de caracteres a más de los 216 caracteres que pueden ser   distinguido por un solo char de 16 bits. Por lo tanto, un valor char no   ya no tiene un mapeo uno a uno a la unidad semántica fundamental en   Unicode JDK 5 se actualizó para admitir el conjunto de caracteres más grande   valores. En lugar de cambiar la definición del tipo char , algunos de   los nuevos caracteres suplementarios están representados por un par sustituto   de dos valores char . Para reducir la confusión de nombres, un punto de código será   se usa para referirse al número que representa un Unicode particular   carácter, incluidos los complementarios.

Finalmente, ¿por qué forEachOrdered y no forEach ?

El comportamiento de forEach es explícitamente no determinista, ya que forEachOrdered realiza 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 lo tanto, forEach no garantiza que se mantendrá el pedido. Consulte también esta pregunta para obtener más información.

Para la diferencia entre un personaje, un punto de código, un glifo y un grafema verifique esto pregunta .

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

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

o usando la transmisión directamente en lugar de un bucle for:

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

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

No usaría StringTokenizer ya que es una de las clases en el JDK que es heredada.

El javadoc dice:

  

StringTokenizer es una clase heredada que   se retiene por razones de compatibilidad   aunque se desaconseja su uso en nuevos   código. Se recomienda que cualquiera   buscando esta funcionalidad utiliza el   método dividido de String o el    java.util.regex paquete en su lugar.

Si necesita rendimiento, debe probar en su entorno. No hay 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

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 en int len ?? y use el bucle para .

StringTokenizer es totalmente inadecuado para la tarea de dividir una cadena en sus caracteres individuales. Con String # split () 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 caracteres. hay un pequeño truco lindo que puedes usar para lograr lo mismo: usa la cadena en sí como la cadena del delimitador (haciendo que cada personaje en él sea un delimitador) y haz que devuelva 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.

Elaborando esta respuesta y esta respuesta .

Las respuestas anteriores señalan el problema de muchas de las soluciones aquí que no se repiten por el valor del punto de código: tendrían problemas con cualquier caracteres sustitutos . Los documentos de Java también describen el problema aquí (ver "Representaciones de caracteres Unicode"). De todos modos, aquí hay un código que usa algunos caracteres sustitutos reales del conjunto suplementario Unicode, 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))));

¡Este código de ejemplo lo 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);
    }
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top