Voici le code actuel dans mon application:
String[] ids = str.split("/");
Lors du profilage de l'application, j'ai remarqué qu'un temps non négligeable est utilisé pour scinder la chaîne.
J'ai aussi appris que split
prend en fait une expression régulière, ce qui est inutile pour moi ici.
Ma question est donc la suivante: quelle alternative puis-je utiliser pour optimiser le fractionnement des chaînes? J'ai vu StringUtils.split
, mais est-ce plus rapide?
(J'aurais déjà essayé et testé moi-même, mais profiler mon application prend beaucoup de temps, donc si quelqu'un connaît déjà la réponse, cela fait gagner du temps)
String.split(String)
ne créera pas d’expression rationnelle si votre modèle ne contient qu’un caractère. Lors de la scission par un seul caractère, il utilisera un code spécialisé qui est assez efficace. StringTokenizer
n'est pas beaucoup plus rapide dans ce cas particulier.
Cela a été introduit dans OpenJDK7/OracleJDK7. Voici un rapport de bogue et un commit . J'ai fait un repère simple ici .
$ 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
Si vous pouvez utiliser des bibliothèques tierces, les commandes de GuavaSplitter
n'entraînent pas la surcharge d'expressions régulières lorsque vous ne le demandez pas et sont très rapides en règle générale. (Divulgation: je contribue à la goyave.)
Iterable<String> split = Splitter.on('/').split(string);
(En outre, Splitter
est en règle générale beaucoup plus prévisible que String.split
.)
StringTokenizer
est beaucoup plus rapide pour une analyse simple comme celle-ci (j’ai fait quelques analyses comparatives il y a quelque temps et vous obtenez d’énormes accélérations).
StringTokenizer st = new StringTokenizer("1/2/3","/");
String[] arr = st.countTokens();
arr[0] = st.nextToken();
Si vous voulez gagner un peu plus en performance, vous pouvez aussi le faire manuellement:
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)
est environ deux fois plus rapide selon ce post .
Cependant, à moins que votre application ne soit d'une taille gigantesque, split
devrait vous convenir (même message, il cite des milliers de chaînes en quelques millisecondes).
Étant donné que je travaille à grande échelle, j'ai pensé qu'il serait utile de fournir un peu plus de benchmarking, y compris quelques-unes de mes propres implémentations (je me suis divisé sur des espaces, mais cela devrait illustrer le temps qu'il faut en général):
Je travaille avec un fichier de 426 Mo, avec 2622761 lignes. Les seuls espaces sont les espaces normaux ("") et les lignes ("\ n").
Tout d'abord, je remplace toutes les lignes par des espaces et analyse l'analyse d'une ligne énorme:
.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
Ensuite, je teste le fractionnement ligne par ligne (ce qui signifie que les fonctions et les boucles sont effectuées plusieurs fois, au lieu de tout à la fois):
.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
Voici le 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;
}
Voici comment j'ai utilisé StringTokenizer:
StringTokenizer tokenizer = new StringTokenizer(file.getCurrentString());
words = new String[tokenizer.countTokens()];
int i = 0;
while (tokenizer.hasMoreTokens()) {
words[i] = tokenizer.nextToken();
i++;
}
StringTokenizer est plus rapide que toute autre méthode de fractionnement, mais le fait de renvoyer les délimiteurs par le tokenizer avec la chaîne tokenisée améliore les performances de 50% environ. Ceci est réalisé en utilisant le constructeur Java.util.StringTokenizer.StringTokenizer(String str, String delim, boolean returnDelims)
. Voici quelques informations supplémentaires sur ce sujet: Performances de la classe StringTokenizer par rapport à la méthode split en Java
Guava a un Splitter qui est plus flexible que la méthode String.split()
, et n'utilise pas (nécessairement) une expression régulière. OTOH, String.split()
a été optimisé dans Java 7 pour éviter les machines à expression régulière si le séparateur est un seul caractère. Les performances devraient donc être similaires à Java 7.
Vous pouvez écrire la fonction split vous-même, ce qui va être le plus rapide ... Voici le lien qui le prouve, cela a fonctionné pour moi aussi, optimisé mon code de 6X
StringTokenizer - lire des lignes avec des entiers
Fractionné: 366ms IndexOf: 50ms StringTokenizer: 89ms GuavaSplit: 109ms IndexOf2 (une solution optimisée donnée dans la question ci-dessus): 14ms CsvMapperSplit (mappage ligne par ligne): 326ms CsvMapperSplit_DOC (construction d'un document et mappage de toutes les lignes en une fois): 177 ms
Utilisez Apache Commons Lang »3.0's
StringUtils.splitByWholeSeparator("ab-!-cd-!-ef", "-!-") = ["ab", "cd", "ef"]
Si vous avez besoin de split non regex et que vous voulez obtenir les résultats dans un tableau String, utilisez StringUtils, j'ai comparé StringUtils.splitByWholeSeparator avec le séparateur de Guava et le split de Java, et avons trouvé StringUtils plus rapide.
La méthode split de String est probablement un choix plus sûr. À partir d’au moins Java 6 (bien que la référence de l’API soit pour 7), ils disent en principe que l’utilisation de StringTokenizer est découragée. Leur libellé est cité ci-dessous.
"StringTokenizer est une classe héritée conservée pour des raisons de compatibilité, bien que son utilisation soit déconseillée dans le nouveau code. Il est recommandé à toute personne recherchant cette fonctionnalité d'utiliser la méthode split de String ou le package Java.util.regex."