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.
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
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
.)
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();
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).
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++;
}
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
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.
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
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.
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. "