Java分裂字符串表现

时间:2012-06-12 17:02:02

标签: java string performance optimization split

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

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

在分析应用程序时,我注意到分割字符串花费了不可忽略的时间。

我还了解到split实际上是一个正则表达式,这对我来说没用。

所以我的问题是,我可以使用哪种替代方案来优化字符串拆分?我见过StringUtils.split但它更快?

(我会自己尝试和测试,但是分析我的应用程序需要花费很多时间,所以如果有人已经知道答案已经节省了一些时间)

10 个答案:

答案 0 :(得分:39)

如果您的模式只有一个字符长,

String.split(String)将不会创建正则表达式。当按单个字符拆分时,它将使用非常有效的专用代码。在这种特殊情况下,StringTokenizer的速度并不快。

这是在OpenJDK7 / OracleJDK7中引入的。 Here's a bug reporta commit。我做了simple benchmark here


$ 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

答案 1 :(得分:18)

如果您可以使用第三方库,Guava's Splitter在您不提出要求时不会产生正则表达式的开销,并且作为一般规则非常快。 (披露:我向Guava捐款。)

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

(另外,Splitter通常为much more predictable而不是String.split。)

答案 2 :(得分:8)

对于像这样的简单解析,

StringTokenizer要快得多(我在一段时间内做了一些基准测试,你获得了巨大的加速)。

StringTokenizer st = new StringTokenizer("1/2/3","/");
String[] arr = 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();

答案 3 :(得分:3)

根据{{​​3}},

java.util.StringTokenizer(String str, String delim)大约快两倍。

但是,除非您的应用程序规模巨大,否则split应该没问题(相反,相同的帖子,它会在几毫秒内引用数千个字符串)。

答案 4 :(得分:3)

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

我使用的是426 MB文件,有2622761行。唯一的空格是普通空格(&#34;&#34;)和线条(&#34; \ n&#34;)。

首先,我用空格替换所有行,并基准解析一个巨大的行:

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

答案 5 :(得分:2)

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

答案 6 :(得分:2)

StringTokenizer比任何其他分割方法都快,但让tokenizer返回分隔符以及标记化字符串可以将性能提高50%。这是通过使用构造函数java.util.StringTokenizer.StringTokenizer(String str, String delim, boolean returnDelims)实现的。这里有一些其他见解:Performance of StringTokenizer class vs. split method in Java

答案 7 :(得分:0)

String的split方法可能是一个更安全的选择。 As of at least java 6(尽管api引用是7),他们基本上说不鼓励使用StringTokenizer。他们的措辞引用如下。

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

答案 8 :(得分:0)

您可以自己编写分割功能,这将是最快的。 这是证明它的链接,它也适用于我,优化我的代码6X

StringTokenizer - reading lines with integers

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

答案 9 :(得分:0)

使用Apache Commons Lang»3.0的

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

如果你需要非正则表达式拆分并希望结果在String数组中,那么使用StringUtils,我将StringUtils.splitByWholeSeparator与Guava的Splitter和Java的String拆分进行比较,发现StringUtils更快。

  1. StringUtils - 8ms
  2. String - 11ms
  3. Splitter - 1ms(但返回Iterable / Iterator并将它们转换为字符串数组总共需要54ms)