Java分裂字符串表现

Mat*_*oli 41 java string optimization performance split

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

String[] ids = str.split("/");
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

Pio*_*zmo 41

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
Run Code Online (Sandbox Code Playgroud)

  • 感谢这个基准。不过,您的代码是“不公平的”,因为 StringTokenizer 部分避免创建列表并将其转换为数组......不过这是一个很好的起点! (2认同)
  • 为了避免在`split`方法中创建正则表达式,有一个char长模式是不够的.这个char也不能是正则表达式元字符之一".$ |()[{^?*+ \\"例如`split(".")`将创建/编译正则表达式模式.(至少在jdk8上验证过) (2认同)

Lou*_*man 19

如果你可以使用第三方库,那么当你不要求它时,Guava Splitter不会产生正则表达式的开销,并且作为一般规则非常快.(披露:我向番石榴捐款.)

Iterable<String> split = Splitter.on('/').split(string);
Run Code Online (Sandbox Code Playgroud)

(另外,Splitter是作为一项规则更具可预测性String.split.)

  • 这篇文章建议不使用Iterable,即使是Guava的团队负责人这么说...... http://alexruiz.developerblogs.com/?p = 2519 (3认同)
  • 当我在大文件的行中使用它时,这对我来说是非常重要的区别。 (2认同)

tsk*_*zzy 8

StringTokenizer 对于像这样的简单解析来说要快得多(我做了一些基准测试并且你获得了巨大的加速).

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

如果你想要获得更多的性能,你也可以手动完成:

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();
Run Code Online (Sandbox Code Playgroud)

  • StringTokenizer是一个遗留类,出于兼容性原因而保留,尽管在新代码中不鼓励使用它.建议任何寻求此功能的人都使用String的split方法或java.util.regex包. (4认同)
  • 仅仅因为它的遗产并不意味着它没用.事实上,这个特殊的类实际上对于额外的性能提升非常有用,所以我实际上反对这个"遗留"标签. (3认同)
  • `String`和`java.util.regex`包的split方法会产生使用正则表达式的巨大开销.`StringTokenizer`没有. (3认同)
  • @tskuzzy你是否反对"遗产"标签并不重要,正如javadoc所说:它的使用不鼓励. (2认同)
  • @NandkumarTekale你显然不明白我的观点.但是如果你想避免使用"遗留"类来支持你选择的"慢"类. (2认同)

Jos*_*a M 8

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

我正在处理一个 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
Run Code Online (Sandbox Code Playgroud)

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

.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
Run Code Online (Sandbox Code Playgroud)

这是代码:

// 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;
}
Run Code Online (Sandbox Code Playgroud)

这就是我使用 StringTokenizer 的方式:

    StringTokenizer tokenizer = new StringTokenizer(file.getCurrentString());
    words = new String[tokenizer.countTokens()];
    int i = 0;
    while (tokenizer.hasMoreTokens()) {
        words[i] = tokenizer.nextToken();
        i++;
    }
Run Code Online (Sandbox Code Playgroud)

  • splitStringChList 丢弃最后一个字符串。在返回之前添加: ```java if (sb.length() &gt; 0) Words.add(sb.toString()); ``` 另外: - 替换 sb.delete(0, sb.length()); 与 sb.setLength(0); - 删除未使用的 int i=0; (3认同)