posicion - recorrer un string java
¿Cuál es la forma más fácil/mejor/más correcta de iterar a través de los caracteres de una cadena en Java? (13)
StringTokenizer
? ¿Convertir el String
a un char[]
y iterar sobre eso? ¿Algo más?
¡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);
}
}
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.
Elaborando sobre esta respuesta y esta respuesta .
Las respuestas anteriores señalan el problema de muchas de las soluciones aquí que no se repiten por valor de punto de código: tendrían problemas con los caracteres sustitutos . Los documentos de Java también describen el problema here (consulte "Representaciones de caracteres Unicode"). De todos modos, aquí hay algo de código que utiliza algunos caracteres sustitutos reales del conjunto complementario de Unicode y los convierte de nuevo en una cadena. Tenga en cuenta que .toChars () devuelve una serie de caracteres: si está tratando con 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))));
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 el CharSequence#chars :
Devuelve un flujo de int cero que extiende los valores de char de esta secuencia. Cualquier carácter que se asigne a un punto de código sustituto se pasa sin ser interpretado. Si la secuencia se muta mientras se lee la secuencia, el resultado no está definido.
El método codePoints()
también devuelve un IntStream
según doc:
Devuelve un flujo de valores de puntos de código de esta secuencia. Todos los pares sustitutos encontrados en la secuencia se combinan como 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 emparejados y las unidades de código no definidas, se extienden a cero a los valores int que luego se pasan a la secuencia.
¿En qué se diferencian el punto y el código? Como se menciona en this artículo:
Unicode 3.1 agregó caracteres suplementarios, llevando el número total de caracteres a más de los 216 caracteres que pueden distinguirse por un solo carácter de 16 bits. Por lo tanto, un valor
char
ya no tiene una asignación de uno a uno a la unidad semántica fundamental en Unicode. JDK 5 se actualizó para admitir el mayor conjunto de valores de caracteres. En lugar de cambiar la definición del tipochar
, algunos de los nuevos caracteres suplementarios están representados por un par sustituto de dos valoreschar
. Para reducir la confusión de nombres, se utilizará un punto de código para referirse al número que representa un carácter Unicode particular, incluidos los complementarios.
Finalmente, ¿ forEachOrdered
qué forEachOrdered
y no forEach
?
El comportamiento de forEach
es explícitamente no determinista, ya que como forEachOrdered
realiza una acción para cada elemento de este flujo, en el orden de encuentro del flujo si el flujo tiene un orden de encuentro definido. Así que forEach
no garantiza que el pedido se mantenga. También revise esta question para más.
Para ver la diferencia entre un carácter, un punto de código, un glifo y un grafema, consulte esta question .
Estoy de acuerdo en que StringTokenizer es una exageración 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 un String y recorrer cada uno de ellos con charAt () / después de convertir a un array de caracteres / con un CharacterIterator mil veces (por supuesto asegurándose de haga algo en la cadena para que el compilador no pueda optimizar todo el bucle :-)).
El resultado en mi Powerbook de 2.6 GHz (que es un mac :-)) y JDK 1.5:
- Prueba 1: charAt + String -> 3138msec
- Prueba 2: cadena convertida a matriz -> 9568msec
- Prueba 3: StringBuilder charAt -> 3536msec
- 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, el carácter () de un StringBuilder parece ser un poco más lento que el de String.
Por cierto, sugiero no utilizar CharacterIterator, ya que considero que el abuso del carácter ''/ uFFFF'' como "fin de 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 de forma misteriosa.
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
...
}
No usaría StringTokenizer
ya que es una de las clases en el JDK que es heredado.
El javadoc dice:
StringTokenizer
es una clase heredada que se conserva por razones de compatibilidad, aunque su uso no se recomienda en el nuevo código. Se recomienda que cualquiera que busque esta funcionalidad use el método split deString
o el paquetejava.util.regex
.
Si necesita recorrer los puntos de código de una String
(vea esta answer ), una forma más corta / más legible es usar el método CharSequence#codePoints
agregado en Java 8:
for(int c : string.codePoints().toArray()){
...
}
o usando el flujo 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
).
Si tiene Guava en su ruta de clase, 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 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 CharSequence#chars
para usar. Incluso el tipo es IntStream, por lo que puede asignarse a caracteres como:
yourString.chars()
.mapToObj(c -> Character.valueOf((char) c))
.forEach(c -> System.out.println(c)); // Or whatever you want
StringTokenizer es totalmente inadecuado para la tarea de dividir una cadena en sus caracteres individuales. Con String#split()
puede hacerlo fácilmente usando 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 de delimitador que pueda especificar que coincida con la nada entre los caracteres. Hay un pequeño truco que puedes usar para lograr lo mismo: usa la cadena en sí misma como la cadena delimitadora (haciendo de cada carácter 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 descartarlos. Ambas técnicas dividen la cadena original en cadenas de un solo carácter en lugar de primitivas char, y ambas implican una gran cantidad de sobrecarga en la 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.
Tenga en cuenta que la mayoría de las otras técnicas que se describen aquí se desglosan si está tratando con caracteres fuera del BMP (Unicode Basic Multilingual Plane ), es decir , los puntos de código que están fuera del rango u0000-uFFFF. Esto solo sucederá raramente, 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 se utilizan 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+.
Utilizo un bucle for para iterar la cadena y uso charAt()
para que cada personaje la 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 yo haría. Me parece lo más fácil.
En cuanto a la corrección, no creo que exista aquí. Todo está basado en tu estilo personal.
Ver 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 for
bucle.