it-swarm.com.de

Was ist der einfachste/beste/beste Weg, die Zeichen einer Zeichenfolge in Java zu durchlaufen?

StringTokenizer? Konvertieren Sie die String in einen char[] und iterieren Sie darüber? Etwas anderes?

259
Paul Wicks

Ich benutze eine for-Schleife, um die Zeichenfolge zu durchlaufen und benutze charAt(), um jedes Zeichen zu ermitteln, um es zu untersuchen. Da der String mit einem Array implementiert wird, handelt es sich bei der charAt()-Methode um eine konstante Zeitoperation.

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

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

Das würde ich tun. Es scheint mir am einfachsten.

Was die Richtigkeit angeht, glaube ich nicht, dass es hier existiert. Alles hängt von Ihrem persönlichen Stil ab.

287
jjnguy

Zwei Optionen

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

oder

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

Der erste ist wahrscheinlich schneller, der zweite ist wahrscheinlich lesbarer. 

173
Dave Cheney

Beachten Sie, dass die meisten anderen hier beschriebenen Techniken nicht funktionieren, wenn Sie Zeichen außerhalb der BMP (Unicode Basic Multilingual Plane ) verwenden, dh Codepunkte , die sich außerhalb des u0000- befinden. uFFFF-bereich. Dies wird nur selten vorkommen, da die außerhalb liegenden Codepunkte meist toten Sprachen zugeordnet sind. Es gibt jedoch einige nützliche Zeichen, zum Beispiel einige Codepunkte, die für die mathematische Notation verwendet werden, und andere, um Eigennamen auf Chinesisch zu codieren.

In diesem Fall lautet Ihr Code:

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

Die Character.charCount(int)-Methode erfordert Java 5+.

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

87
sk.

Ich bin damit einverstanden, dass StringTokenizer hier übertrieben ist. Eigentlich habe ich die obigen Vorschläge ausprobiert und mir die Zeit genommen. 

Mein Test war ziemlich einfach: Erstellen Sie einen StringBuilder mit etwa einer Million Zeichen, konvertieren Sie ihn in einen String und durchqueren Sie jeden von ihnen mit charAt ()/nach dem Konvertieren in ein Char-Array/mit einem CharacterIterator tausend Mal (natürlich stellen Sie sicher, dass Tun Sie etwas an der Zeichenkette, damit der Compiler die gesamte Schleife nicht optimieren kann :-)).

Das Ergebnis auf meinem 2,6-GHz-Powerbook (das ist ein Mac :-)) und JDK 1.5:

  • Test 1: charAt + String -> 3138 ms
  • Test 2: Zeichenfolge in Array konvertiert -> 9568 ms 
  • Test 3: StringBuilder-Zeichen -> 3536 ms 
  • Test 4: CharacterIterator und String -> 12151 ms

Da sich die Ergebnisse erheblich unterscheiden, scheint der einfachste Weg auch der schnellste zu sein. Interessanterweise scheint charAt () eines StringBuilder etwas langsamer zu sein als String.

Übrigens schlage ich vor, CharacterIterator nicht zu verwenden, da ich den Missbrauch des '\ uFFFF'-Zeichens als "Ende der Iteration" für einen wirklich schrecklichen Hack halte. In großen Projekten gibt es immer zwei Typen, die dieselbe Art von Hack für zwei verschiedene Zwecke verwenden, und der Code stürzt wirklich auf mysteriöse Weise ab. 

Hier ist einer der Tests:

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

Dafür gibt es einige spezielle Klassen:

import Java.text.*;

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

Wenn Sie Guava in Ihrem Klassenpfad haben, ist das Folgende eine ziemlich lesbare Alternative. Guava hat sogar eine ziemlich sinnvolle benutzerdefinierte Listenimplementierung für diesen Fall, daher sollte dies nicht ineffizient sein.

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

UPDATE: Wie @Alex feststellt, ist mit Java 8 auch CharSequence#chars zu verwenden. Sogar der Typ ist IntStream, also kann er Zeichen wie folgt zugeordnet werden:

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

Wenn Sie die Codepunkte einer String durchlaufen müssen (siehe answer ), können Sie die in Java 8 hinzugefügte Methode CharSequence#codePoints kürzer/lesbarer verwenden:

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

oder verwenden Sie den Stream direkt anstelle einer for-Schleife:

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

Es gibt auch CharSequence#chars , wenn Sie einen Stream der Zeichen wünschen (obwohl es sich um eine IntStream handelt, da es keine CharStream gibt).

12
Alex

In Java 8 können wir es lösen als:

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

Die Methode chars () gibt ein IntStream zurück, wie in doc erwähnt.

Gibt einen Strom von int zurück, der die Zeichenwerte aus diesem Wert erweitert Sequenz. Jedes Zeichen, das einem Ersatzcode-Punkt zugeordnet ist, wird übergeben durch nicht interpretiert. Wenn die Sequenz mutiert ist, während der Stream .__ ist. Wenn gelesen wird, ist das Ergebnis undefiniert.

Die Methode codePoints() gibt auch ein IntStream gemäß doc zurück:

Gibt einen Strom von Codepunktwerten aus dieser Sequenz zurück. Irgendein Ersatzpaare, die in der Sequenz gefunden werden, werden wie mit .__ kombiniert. Character.toCodePoint und das Ergebnis wird an den Stream übergeben. Irgendein andere Codeeinheiten, einschließlich gewöhnlicher Zeichen BMP, nicht gepaart Surrogate und nicht definierte Codeeinheiten werden um Nullwerte auf int-Werte erweitert die dann an den Strom übergeben werden.

Wie unterscheiden sich char und code? Wie in this article erwähnt:

Unicode 3.1 fügte zusätzliche Zeichen hinzu, die die Gesamtzahl .__ ergeben. Zeichen mit mehr als 216 Zeichen, die .__ sein können. unterschieden durch ein einzelnes 16-Bit char. Daher ein char-Wert Nr länger hat eine Eins-zu-Eins-Zuordnung zu der grundlegenden semantischen Einheit in Unicode. JDK 5 wurde aktualisiert, um den größeren Zeichensatz .__ zu unterstützen. Werte. Anstatt die Definition des char-Typs zu ändern, werden einige Die neuen Zusatzzeichen werden durch ein Ersatzpaar dargestellt von zwei char-Werten. Ein Code-Punkt wird .__ sein, um Verwirrung bei der Benennung zu vermeiden. Wird verwendet, um auf die Nummer zu verweisen, die einen bestimmten Unicode darstellt Charakter, einschließlich ergänzender.

Warum forEachOrdered und nicht forEach?

Das Verhalten von forEach ist explizit nicht deterministisch, wobei forEachOrdered für jedes Element dieses Streams eine Aktion in der Begegnungsreihenfolge des Streams ausführt, wenn der Stream eine definierte Begegnungsreihenfolge hat. forEach garantiert daher nicht, dass die Bestellung eingehalten wird. Überprüfen Sie auch diese Frage für mehr.

Für Unterschied zwischen einem Zeichen, einem Codepunkt, einer Glyphe und einem Graphem überprüfen Sie diese Frage .

11
i_am_zero

Ich würde StringTokenizer nicht verwenden, da dies eine der Klassen im JDK ist, die ein Erbe sind.

Der Javadoc sagt:

StringTokenizer ist eine ältere Klasse, die wird aus Kompatibilitätsgründen beibehalten obwohl seine Verwendung in neuen Code. Es wird empfohlen, dass jeder Suchen Sie nach dieser Funktion, verwenden Sie die Split-Methode von String oder Java.util.regex Paket stattdessen.

3
Alan

Wenn Sie Leistung benötigen, müssen Sie auf Ihrer Umgebung testen . Kein anderer Weg.

Hier Beispielcode:

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

Am Java online bekomme ich:

1 10349420
2 526130
3 484200
0

Auf Android x86 API 17 bekomme ich:

1 9122107
2 13486911
3 12700778
0
1
Enyby

StringTokenizer ist völlig ungeeignet für die Aufteilung einer Zeichenfolge in einzelne Zeichen. Mit String#split() können Sie dies leicht tun, indem Sie einen Regex verwenden, der mit nichts übereinstimmt, z.

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

StringTokenizer verwendet jedoch keine regulären Ausdrücke, und es gibt keine Begrenzungszeichenfolge, die Sie angeben können, die mit dem Nichts zwischen den Zeichen übereinstimmt. Ist ein süßer kleiner Hack, den Sie verwenden können, um dasselbe zu erreichen: Verwenden Sie die Zeichenfolge selbst als Trennzeichenfolge (wobei jedes Zeichen darin als Trennzeichen definiert wird), und lassen Sie die Trennzeichen zurückgeben:

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

Ich erwähne diese Optionen jedoch nur, um sie abzuweisen. Beide Techniken unterteilen die ursprüngliche Zeichenfolge in Zeichenfolgen mit einem Zeichen anstelle von primitiven Zeichen, und beide erfordern einen erheblichen Mehraufwand in Form der Objekterstellung und der Bearbeitung von Zeichenfolgen. Vergleichen Sie das mit dem Aufruf von charAt () in einer for-Schleife, die praktisch keinen Overhead verursacht. 

0
Alan Moore

Dieser Beispielcode hilft Ihnen dabei!

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
devDeejay

Ausarbeiten von dieser Antwort und dieser Antwort .

Die obigen Antworten weisen auf das Problem vieler Lösungen hier hin, die nicht nach Codepunkten iterieren - sie hätten Probleme mit Ersatzzeichen . Die Java-Dokumente beschreiben auch die Ausgabe hier (siehe "Unicode-Zeichendarstellungen"). Jedenfalls ist hier ein Code, der einige tatsächliche Ersatzzeichen aus dem zusätzlichen Unicode-Satz verwendet und back in einen String konvertiert. Beachten Sie, dass .toChars () ein Array von Zeichen zurückgibt: Wenn Sie mit Surrogaten arbeiten, haben Sie notwendigerweise zwei Zeichen. Dieser Code sollte für any Unicode-Zeichen funktionieren.

    String supplementary = "Some Supplementary: ????????????????";
    supplementary.codePoints().forEach(cp -> 
            System.out.print(new String(Character.toChars(cp))));
0
Hawkeye Parker

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

Geben Sie die Länge in int len ein und verwenden Sie die for-Schleife.

0
Eugene Yokota