Qual è il modo più semplice / migliore / più corretto per scorrere i caratteri di una stringa in Java?

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

Domanda

:

StringTokenizer ? Convertire String in char [] e iterare su quello? Qualcos'altro?

È stato utile?

Soluzione

Uso un ciclo for per iterare la stringa e usare charAt () per far esaminare ogni personaggio da ogni personaggio. Poiché la stringa è implementata con un array, il metodo charAt () è un'operazione a tempo costante.

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

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

Questo è quello che vorrei fare. Mi sembra il più semplice.

Per quanto riguarda la correttezza, non credo che esista qui. È tutto basato sul tuo stile personale.

Altri suggerimenti

Due opzioni

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

o

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

Il primo è probabilmente più veloce, quindi il secondo è probabilmente più leggibile.

Nota che la maggior parte delle altre tecniche descritte qui si guastano se hai a che fare con personaggi al di fuori del BMP (Unicode Piano multilingue di base ), ovvero punti di codice che si trovano al di fuori di u0000 gamma -FFFF. Questo accadrà solo raramente, poiché i punti di codice al di fuori di questo sono per lo più assegnati a lingue morte. Ma ci sono alcuni caratteri utili al di fuori di questo, ad esempio alcuni punti di codice usati per la notazione matematica e alcuni usati per codificare nomi propri in cinese.

In tal caso il tuo codice sarà:

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

Il metodo Character.charCount (int) richiede Java 5+.

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

Sono d'accordo che StringTokenizer è eccessivo qui. In realtà ho provato i suggerimenti sopra e mi sono preso il tempo.

Il mio test è stato abbastanza semplice: creare un StringBuilder con circa un milione di caratteri, convertirlo in una stringa e attraversare ciascuno di essi con charAt () / dopo la conversione in un array di caratteri / con un CharacterIterator mille volte (ovviamente assicurandoti di fare qualcosa sulla stringa in modo che il compilatore non possa ottimizzare l'intero ciclo :-)).

Il risultato sul mio Powerbook a 2,6 GHz (che è un mac :-)) e JDK 1.5:

  • Test 1: charAt + String - > 3138msec
  • Test 2: stringa convertita in array - > 9568msec
  • Test 3: StringBuilder charAt - > 3536msec
  • Test 4: CharacterIterator e String - > 12151msec

Dato che i risultati sono significativamente diversi, anche il modo più semplice sembra essere il più veloce. È interessante notare che charAt () di StringBuilder sembra essere leggermente più lento di quello di String.

A proposito, suggerisco di non usare CharacterIterator poiché considero l'abuso del carattere "\ uFFFF" come "fine dell'iterazione" un trucco davvero terribile. Nei grandi progetti ci sono sempre due ragazzi che usano lo stesso tipo di hack per due scopi diversi e il codice si arresta in modo davvero misterioso.

Ecco uno dei test:

    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");

Ci sono alcune lezioni dedicate per questo:

import java.text.*;

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

Se hai Guava sul tuo percorso di classe, la seguente è un'alternativa piuttosto leggibile . Guava ha anche un'implementazione dell'elenco personalizzata abbastanza ragionevole per questo caso, quindi questo non dovrebbe essere inefficiente.

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

AGGIORNAMENTO: Come notato da @Alex, con Java 8 c'è anche CharSequence # chars da usare. Anche il tipo è IntStream, quindi può essere mappato su caratteri come:

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

In Java 8 possiamo risolverlo come:

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

Il metodo chars () restituisce un IntStream come menzionato in doc :

  

Restituisce un flusso di int che estende a zero i valori dei caratteri da questo   sequenza. Viene passato qualsiasi carattere associato a un punto di codice surrogato   attraverso non interpretato. Se la sequenza viene modificata mentre lo stream è   in fase di lettura, il risultato non è definito.

Il metodo codePoints () restituisce anche un IntStream come da documento:

  

Restituisce un flusso di valori di punti di codice da questa sequenza. Qualunque   le coppie surrogate che si incontrano nella sequenza sono combinate come da   Character.toCodePoint e il risultato viene passato allo stream. Qualunque   altre unità di codice, inclusi i normali caratteri BMP, non accoppiati   i surrogati e le unità di codice non definite sono estese da zero a valori int   che vengono quindi passati allo stream.

In cosa differiscono i caratteri char e code? Come menzionato in questo articolo:

  

Unicode 3.1 ha aggiunto caratteri supplementari, portando il numero totale   di caratteri oltre i 216 caratteri che possono essere   distinto da un singolo char a 16 bit. Pertanto, un valore char n   ha più un mapping uno a uno all'unità semantica fondamentale in   Unicode. JDK 5 è stato aggiornato per supportare il set di caratteri più grande   valori. Invece di cambiare la definizione del tipo char , alcuni di   i nuovi personaggi supplementari sono rappresentati da una coppia surrogata   di due valori char . Per ridurre la confusione dei nomi, sarà un punto di codice   usato per fare riferimento al numero che rappresenta un particolare Unicode   carattere, compresi quelli supplementari.

Finalmente perché forEachOrdered e non forEach ?

Il comportamento di forEach è esplicitamente non deterministico laddove il forEachOrdered esegue un'azione per ciascun elemento di questo flusso, nell'ordine di incontro del flusso se lo stream ha un ordine di incontro definito. Quindi forEach non garantisce che l'ordine verrà mantenuto. Controlla anche questa domanda per ulteriori informazioni

Per differenza tra un carattere, un punto di codice, un glifo e un grafema controlla questo domanda .

Se è necessario scorrere i punti di codice di una stringa (consultare questa risposta ) un modo più breve / più leggibile consiste nell'utilizzare CharSequence # codePoints aggiunto in Java 8:

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

o utilizzando direttamente lo stream anziché un ciclo for:

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

Esistono anche CharSequence # chars se vuoi un flusso di caratteri (sebbene sia un IntStream , poiché non esiste un CharStream ).

Non userei StringTokenizer in quanto è una delle classi del JDK che è legacy.

Il javadoc dice:

  

StringTokenizer è una classe legacy che   viene conservato per motivi di compatibilità   sebbene il suo uso sia scoraggiato nel nuovo   codice. Si consiglia a chiunque   cercando questa funzionalità usa il   metodo split di String o   Pacchetto java.util.regex invece.

Se hai bisogno di prestazioni, allora devi testare sul tuo ambiente. Nessun altro modo.

Qui codice di esempio:

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

Su Java online ottengo:

1 10349420
2 526130
3 484200
0

Su Android x86 API 17 ottengo:

1 9122107
2 13486911
3 12700778
0

Vedi The Java Tutorials: Strings .

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

Inserisci la lunghezza in int len ?? e usa per loop.

StringTokenizer è totalmente inadatto al compito di spezzare una stringa nei suoi singoli caratteri. Con String # split () puoi farlo facilmente usando una regex che non corrisponde a nulla, ad es .:

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

Ma StringTokenizer non usa regex e non esiste una stringa delimitatore che puoi specificare che non corrisponderà al nulla tra i caratteri. c'è un piccolo hack carino che puoi usare per ottenere lo stesso risultato: usa la stringa stessa come stringa del delimitatore (rendendo ogni carattere in esso un delimitatore) e fai in modo che restituisca i delimitatori:

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

Tuttavia, cito solo queste opzioni allo scopo di respingerle. Entrambe le tecniche suddividono la stringa originale in stringhe di un carattere anziché in caratteri primitivi, ed entrambe comportano un notevole sovraccarico sotto forma di creazione di oggetti e manipolazione di stringhe. Confrontalo con la chiamata a charAt () in un ciclo for, che non comporta praticamente alcun sovraccarico.

Elaborazione su questa risposta e questa risposta .

Le risposte sopra evidenziano il problema di molte delle soluzioni qui che non ripetono il valore del punto di codice: avrebbero problemi con qualsiasi caratteri surrogati . I documenti java delineano anche il problema qui (vedi " Rappresentazioni di caratteri Unicode "). Ad ogni modo, ecco un codice che utilizza alcuni caratteri surrogati effettivi dal set Unicode supplementare e li converte indietro in una stringa. Nota che .toChars () restituisce una matrice di caratteri: se hai a che fare con surrogati, avrai necessariamente due caratteri. Questo codice dovrebbe funzionare per qualsiasi carattere Unicode.

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

Questo codice di esempio ti aiuterà!

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);
    }
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top