it-swarm.dev

Java-Split-Zeichenfolgen

Hier ist der aktuelle Code in meiner Anwendung:

String[] ids = str.split("/");

Beim Profilieren der Anwendung ist mir aufgefallen, dass eine nicht zu vernachlässigende Zeit zum Teilen der Zeichenfolge aufgewendet wird.

Ich habe auch gelernt, dass split tatsächlich einen regulären Ausdruck annimmt, was hier für mich unbrauchbar ist.

Meine Frage ist also welche Alternative kann ich verwenden, um die Stringaufteilung zu optimieren? Ich habe StringUtils.split gesehen, aber ist es schneller?

(Ich hätte es selbst getestet und erprobt, aber das Profiling meiner Anwendung nimmt viel Zeit in Anspruch.

36
Matthieu Napoli

String.split(String) erzeugt keinen Ausdruck, wenn Ihr Muster nur ein Zeichen lang ist. Beim Aufteilen nach einzelnen Zeichen wird spezieller Code verwendet, der ziemlich effizient ist. StringTokenizer ist in diesem speziellen Fall nicht viel schneller.

Dies wurde in OpenJDK7/OracleJDK7 eingeführt. Hier ist ein Fehlerbericht und ein Commit . Ich habe hier einen einfachen Benchmark gemacht .


$ Java -version
Java version "1.8.0_20"
Java(TM) SE Runtime Environment (build 1.8.0_20-b26)
Java HotSpot(TM) 64-Bit Server VM (build 25.20-b23, mixed mode)

$ Java Split
split_banthar: 1231
split_tskuzzy: 1464
split_tskuzzy2: 1742
string.split: 1291
StringTokenizer: 1517
36
Piotr Praszmo

Wenn Sie Bibliotheken von Drittanbietern verwenden können, verursacht GuavasSplitter nicht den Aufwand regulärer Ausdrücke, wenn Sie nicht danach fragen, und ist im Allgemeinen sehr schnell. (Offenlegung: Ich trage zu Guave bei.)

Iterable<String> split = Splitter.on('/').split(string);

(Auch Splitter ist in der Regel viel vorhersagbarer als String.split.)

17
Louis Wasserman

StringTokenizer ist für das einfache Parsing wie folgt viel schneller (Ich habe vor einiger Zeit ein Benchmarking durchgeführt, und Sie erhalten große Beschleunigungen).

StringTokenizer st = new StringTokenizer("1/2/3","/");
String[] arr = st.countTokens();
arr[0] = st.nextToken();

Wenn Sie mehr Leistung erzielen möchten, können Sie dies auch manuell tun:

String s = "1/2/3"
char[] c = s.toCharArray();
LinkedList<String> ll = new LinkedList<String>();
int index = 0;

for(int i=0;i<c.length;i++) {
    if(c[i] == '/') {
        ll.add(s.substring(index,i));
        index = i+1;
    }
}

String[] arr = ll.size();
Iterator<String> iter = ll.iterator();
index = 0;

for(index = 0; iter.hasNext(); index++)
    arr[index++] = iter.next();
8
tskuzzy

Java.util.StringTokenizer(String str, String delim) ist nach diesem Beitrag etwa doppelt so schnell.

Wenn Ihre Anwendung jedoch nicht von gigantischem Ausmaß ist, sollte split für Sie in Ordnung sein (siehe denselben Beitrag, es werden Tausende von Zeichenfolgen in wenigen Millisekunden angegeben).

3
purtip31

Da ich im großen Stil arbeite, dachte ich, dass es helfen würde, mehr Benchmarking zu erstellen, einschließlich einiger meiner eigenen Implementierungen (ich spaltete die Bereiche auf, aber dies sollte veranschaulichen, wie lange es im Allgemeinen dauert):

Ich arbeite mit einer 426 MB-Datei mit 2622761 Zeilen. Die einzigen Leerzeichen sind normale Leerzeichen ("") und Zeilen ("\ n").

Zuerst ersetze ich alle Zeilen durch Leerzeichen und Benchmarking beim Parsen einer großen Zeile:

.split(" ")
Cumulative time: 31.431366952 seconds

.split("\s")
Cumulative time: 52.948729489 seconds

splitStringChArray()
Cumulative time: 38.721338004 seconds

splitStringChList()
Cumulative time: 12.716065893 seconds

splitStringCodes()
Cumulative time: 1 minutes, 21.349029036000005 seconds

splitStringCharCodes()
Cumulative time: 23.459840685 seconds

StringTokenizer
Cumulative time: 1 minutes, 11.501686094999997 seconds

Dann setze ich Benchmark für Zeile für Zeile (das bedeutet, dass die Funktionen und Schleifen viele Male statt alle gleichzeitig ausgeführt werden):

.split(" ")
Cumulative time: 3.809014174 seconds

.split("\s")
Cumulative time: 7.906730124 seconds

splitStringChArray()
Cumulative time: 4.06576739 seconds

splitStringChList()
Cumulative time: 2.857809996 seconds

Bonus: splitStringChList(), but creating a new StringBuilder every time (the average difference is actually more like .42 seconds):
Cumulative time: 3.82026621 seconds

splitStringCodes()
Cumulative time: 11.730249921 seconds

splitStringCharCodes()
Cumulative time: 6.995555826 seconds

StringTokenizer
Cumulative time: 4.500008172 seconds

Hier ist der Code:

// Use a char array, and count the number of instances first.
public static String[] splitStringChArray(String str, StringBuilder sb) {
    char[] strArray = str.toCharArray();
    int count = 0;
    for (char c : strArray) {
        if (c == ' ') {
            count++;
        }
    }
    String[] splitArray = new String[count+1];
    int i=0;
    for (char c : strArray) {
        if (c == ' ') {
            splitArray[i] = sb.toString();
            sb.delete(0, sb.length());
        } else {
            sb.append(c);
        }
    }
    return splitArray;
}

// Use a char array but create an ArrayList, and don't count beforehand.
public static ArrayList<String> splitStringChList(String str, StringBuilder sb) {
    ArrayList<String> words = new ArrayList<String>();
    words.ensureCapacity(str.length()/5);
    char[] strArray = str.toCharArray();
    int i=0;
    for (char c : strArray) {
        if (c == ' ') {
            words.add(sb.toString());
            sb.delete(0, sb.length());
        } else {
            sb.append(c);
        }
    }
    return words;
}

// Using an iterator through code points and returning an ArrayList.
public static ArrayList<String> splitStringCodes(String str) {
    ArrayList<String> words = new ArrayList<String>();
    words.ensureCapacity(str.length()/5);
    IntStream is = str.codePoints();
    OfInt it = is.iterator();
    int cp;
    StringBuilder sb = new StringBuilder();
    while (it.hasNext()) {
        cp = it.next();
        if (cp == 32) {
            words.add(sb.toString());
            sb.delete(0, sb.length());
        } else {
            sb.append(cp);
        }
    }

    return words;
}

// This one is for compatibility with supplementary or surrogate characters (by using Character.codePointAt())
public static ArrayList<String> splitStringCharCodes(String str, StringBuilder sb) {
    char[] strArray = str.toCharArray();
    ArrayList<String> words = new ArrayList<String>();
    words.ensureCapacity(str.length()/5);
    int cp;
    int len = strArray.length;
    for (int i=0; i<len; i++) {
        cp = Character.codePointAt(strArray, i);
        if (cp == ' ') {
            words.add(sb.toString());
            sb.delete(0, sb.length());
        } else {
            sb.append(cp);
        }
    }

    return words;
}

So habe ich StringTokenizer verwendet:

    StringTokenizer tokenizer = new StringTokenizer(file.getCurrentString());
    words = new String[tokenizer.countTokens()];
    int i = 0;
    while (tokenizer.hasMoreTokens()) {
        words[i] = tokenizer.nextToken();
        i++;
    }
2
Joshua Mathias

StringTokenizer ist schneller als jede andere Aufteilungsmethode, aber wenn der Tokenizer die Trennzeichen zusammen mit dem Token-String zurückgibt, wird die Leistung um etwa 50% verbessert. Dies wird durch die Verwendung des Konstruktors Java.util.StringTokenizer.StringTokenizer(String str, String delim, boolean returnDelims) erreicht. Hier einige andere Erkenntnisse: Leistung der StringTokenizer-Klasse vs. Split-Methode in Java

2
cristianoms

Guava hat einen Splitter , der flexibler ist als die String.split()-Methode, und verwendet nicht (unbedingt) einen Regex. OTOH, String.split() wurde in Java 7 optimiert, um die Regex-Maschinen zu vermeiden, wenn das Trennzeichen ein einzelnes Zeichen ist. Daher sollte die Leistung in Java 7 ähnlich sein.

1
JB Nizet

Sie können die Split-Funktion selbst schreiben, die die schnellste sein wird ..__ Hier ist der Link, der es beweist, es hat auch für mich funktioniert, meinen Code durch 6X optimiert

StringTokenizer - Zeilen mit ganzen Zahlen lesen

Split: 366ms IndexOf: 50ms StringTokenizer: 89ms GuavaSplit: 109ms IndexOf2 (einige superoptimierte Lösung in der obigen Frage): 14ms CsvMapperSplit (zeilenweise Zuordnung): 326ms CsvMapperSplit_DOC (Erstellen eines Dokuments und Mappen aller Zeilen in einem Durchgang): 177 ms

0
sanketshah

Verwenden Sie Apache Commons Lang »3.0 

StringUtils.splitByWholeSeparator("ab-!-cd-!-ef", "-!-") = ["ab", "cd", "ef"]

Wenn Sie eine nicht reguläre Ausdehnung benötigen und die Ergebnisse in einem String-Array anzeigen möchten, verwenden Sie StringUtils. Ich habe StringUtils.splitByWholeSeparator mit Guavas Splitter und Javas String-Split verglichen.

  1. StringUtils - 8ms
  2. Zeichenfolge - 11ms 
  3. Splitter - 1ms (gibt jedoch Iterable/Iterator zurück und die Konvertierung in ein String-Array dauert insgesamt 54ms.)
0
Bruce

Die Aufteilungsmethode des Strings ist wahrscheinlich eine sicherere Wahl. Ab Java 6 (obwohl die API-Referenz für 7 steht) Grundsätzlich wird davon abgeraten, den StringTokenizer zu verwenden. Ihr Wortlaut wird im Folgenden zitiert.

" StringTokenizer ist eine Legacy-Klasse, die aus Kompatibilitätsgründen beibehalten wird, obwohl von ihrer Verwendung in neuem Code abgeraten wird. Es wird empfohlen, dass jeder, der diese Funktionalität sucht, die split-Methode von String oder Java.util verwendet. Regex-Paket statt. "

0
John Kane