50

这是我的应用程序中的当前代码:

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

在分析应用程序时,字符串拆分花费了不可忽略的时间。此外,该split方法采用正则表达式,这在这里是多余的。

我可以使用什么替代方法来优化字符串拆分?StringUtils.split更快吗?

(我会尝试并测试自己,但分析我的应用程序需要很多时间。)

4

10 回答 10

56

String.split(String)如果您的模式只有一个字符长,则不会创建正则表达式。按单个字符拆分时,它将使用非常有效的专用代码。StringTokenizer在这种特殊情况下并没有快多少。

这是在 OpenJDK7/OracleJDK7 中引入的。这是一个错误报告一个提交。我在这里做了一个简单的基准测试


$ 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
于 2012-06-12T18:13:33.737 回答
22

如果您可以使用第三方库,则Guava Splitter在您不要求时不会产生正则表达式的开销,并且通常非常快。(披露:我为 Guava 做出了贡献。)

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

(此外,Splitter通常比.更可预测String.split。)

于 2012-06-12T17:10:40.350 回答
9

StringTokenizer对于像这样的简单解析来说要快得多(我不久前做了一些基准测试,你得到了巨大的加速)。

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

如果您想获得更多性能,也可以手动执行:

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();
于 2012-06-12T17:04:48.947 回答
6

看到我正在大规模工作,我认为提供更多基准测试会有所帮助,包括我自己的一些实现(我按空格分开,但这应该说明一般需要多长时间):

我正在处理一个 426 MB 的文件,有 2622761 行。唯一的空白是普通空格 (" ") 和行 ("\n")。

首先,我用空格替换所有行,并对一大行进行基准分析:

.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

然后我逐行对分割进行基准测试(这意味着函数和循环被多次完成,而不是一次全部完成):

.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

这是代码:

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

这就是我使用 StringTokenizer 的方式:

    StringTokenizer tokenizer = new StringTokenizer(file.getCurrentString());
    words = new String[tokenizer.countTokens()];
    int i = 0;
    while (tokenizer.hasMoreTokens()) {
        words[i] = tokenizer.nextToken();
        i++;
    }
于 2017-03-10T21:46:39.757 回答
3

java.util.StringTokenizer(String str, String delim)根据这篇文章,速度大约是原来的两倍。

但是,除非您的应用程序规模庞大,否则对您来说split应该没问题(参见同一篇文章,它在几毫秒内引用了数千个字符串)。

于 2012-06-12T17:05:22.460 回答
2

Guava 有一个Splitter,它比该String.split()方法更灵活,并且不(必然)使用正则表达式。OTOH,String.split()已在 Java 7 中进行了优化,以在分隔符为单个字符时避免使用正则表达式机制。所以性能在 Java 7 中应该是相似的。

于 2012-06-12T17:11:03.003 回答
1

StringTokenizer 比任何其他拆分方法都快,但是让标记器返回分隔符以及标记化的字符串可以将性能提高 50% 左右。这是通过使用构造函数来实现的java.util.StringTokenizer.StringTokenizer(String str, String delim, boolean returnDelims)。这里有一些关于这个问题的其他见解:StringTokenizer 类的性能与 Java 中的 split 方法

于 2013-08-29T01:50:17.947 回答
0

String 的 split 方法可能是更安全的选择。至少从 java 6 开始(尽管 api 参考是针对 7 的),他们基本上说不鼓励使用 StringTokenizer。他们的措辞引述如下。

" StringTokenizer 是一个遗留类,出于兼容性原因保留,但不鼓励在新代码中使用它。建议任何寻求此功能的人使用 String 的 split 方法或 java.util.regex 包。 "

于 2012-06-12T17:11:57.273 回答
0

您可以自己编写拆分函数,这将是最快的。这是证明它的链接,它也对我有用,将我的代码优化了 6 倍

StringTokenizer - 用整数读取行

拆分:366 毫秒 IndexOf:50 毫秒 StringTokenizer:89 毫秒 GuavaSplit:109 毫秒 IndexOf2(上述问题中给出了一些超级优化的解决方案):14 毫秒 CsvMapperSplit(逐行映射):326 毫秒 CsvMapperSplit_DOC(构建一个文档并一次性映射所有行):177 毫秒

于 2016-11-04T00:56:49.920 回答
0

使用 Apache Commons Lang » 3.0 的

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

如果您需要非正则表达式拆分并且想要字符串数组中的结果,那么使用 StringUtils,我将 StringUtils.splitByWholeSeparator 与 Guava 的 Splitter 和 Java 的 String 拆分进行了比较,发现 StringUtils 更快。

  1. StringUtils - 8ms
  2. 字符串 - 11 毫秒
  3. 拆分器 - 1 毫秒(但返回 Iterable/Iterator 并将它们转换为字符串数组总共需要 54 毫秒)
于 2017-09-21T12:06:05.350 回答