在Java中分割定界字符串的最快方法


10

我正在构建一个在分隔字符串上提供多列排序功能的比较器。我目前使用String类的split方法作为将原始String拆分为标记的首选。

这是将原始String转换为String数组的最佳方法吗?我将对数百万行进行排序,因此我认为方法很重要。

它似乎运行良好并且非常简单,但是不确定Java中是否有更快的方法。

这是我的比较器中排序方式的工作方式:

public int compare(String a, String b) {

    String[] aValues = a.split(_delimiter, _columnComparators.length);
    String[] bValues = b.split(_delimiter, _columnComparators.length);
    int result = 0;

    for( int index : _sortColumnIndices ) {
        result = _columnComparators[index].compare(aValues[index], bValues[index]);
        if(result != 0){
            break;
        }
    }
    return result;
}

在对各种方法进行基准测试之后,不管您信不信,使用最新版本的Java最快的方法是split方法。您可以在这里下载我完成的比较器:https : //sourceforge.net/projects/multicolumnrowcomparator/


5
我将指出,此问题的答案的性质取决于jvm的实现。字符串的行为(在OpenJDK中共享公用的后备数组,而在OracleJDK中则没有)是不同的。这种差异会对拆分字符串和创建子字符串以及垃圾回收和内存泄漏产生重大影响。这些数组有多大?你现在怎么样?您是否会考虑采用一种新的Stringish类型而不是实际的Java Strings的答案?

1
特别要看一下StringTokenizer nextToken,它最终将调用包私有String构造函数。将此与Java 1.7.0_06

数组大小取决于列数,因此它是可变的。将此多列比较器作为参数传递,如下所示:ExternalSort.mergeSortedFiles(fileList,new File(“ BigFile.csv”),_comparator,Charset.defaultCharset(),false); 外部排序例程将对整个行字符串进行排序,实际上是由比较器根据排序列进行拆分和排序
Constantin

我考虑考虑使用Lucene的标记器。Lucene可以用作功能强大的文本分析库,可以很好地执行简单和复杂的任务
Doug T.

考虑一下Apache Commons Lang的StringUtils.split[PreserveAllTokens](text, delimiter)
恢复莫妮卡

Answers:


19

我为此编写了一个快速而肮脏的基准测试。它比较了7种不同的方法,其中一些方法需要特定的数据拆分知识。

对于基本的通用拆分,番石榴拆分器比String#split()快3.5倍,我建议使用它。Stringtokenizer的速度略快于此,使用indexOf进行自我拆分的速度是其两倍。

有关代码和更多信息,请参见http://demeranville.com/battle-of-the-tokenizers-delimited-text-parser-performance/


我很好奇您使用的是什么JDK ...如果是1.6,我最想看到1.7中的结果摘要。

1
我认为是1.6。如果要在1.7中运行,则可以将代码作为JUnit测试进行。注意String.split执行正则表达式匹配,这总是比在单个定义的字符上分割要慢。
汤姆

1
是的,但是对于1.6,StringTokenizer(和类似的代码)调用String.substring(),通过使用相同的后备数组来O(1)创建新字符串。在1.7中对此进行了更改,以复制后备数组的必要部分,而不是O(n)。这可能会对您的结果产生重大影响,从而使split和StringTokenizer之间的差异变小(放慢之前使用子字符串的所有内容)。

1
当然可以。问题是StringTokenizer的工作方式已从“创建一个新的字符串,分配3个整数”变为“创建一个新的字符串,对数据进行数组复制”,这将改变该部分的速度。现在,各种方法之间的差异可能会越来越小,并且对Java 1.7进行跟踪将很有意思(如果出于其他原因,除了其有趣之处)。

1
感谢您的文章!非常有用,将用于对各种方法进行基准测试。
康斯坦丁

5

正如@Tom所写,indexOf类型方法比的速度更快String.split(),因为后者处理正则表达式,并且对它们有很多额外的开销。

但是,一种算法更改可能会为您带来超级加速。假设将使用该Comparator对〜100,000个字符串进行排序,请不要编写Comparator<String>。因为,在你的排序过程中,相同的字符串将有可能比较的时间,所以你把它分解时间,等...

将所有Strings 一次拆分为String [] s,并对Comparator<String[]>String [] 进行排序。然后,最后可以将它们组合在一起。

另外,您也可以使用Map来缓存String-> String [],反之亦然。例如(粗略)另外请注意,您正在以内存换取速度,希望您有很多RAM

HashMap<String, String[]> cache = new HashMap();

int compare(String s1, String s2) {
   String[] cached1 = cache.get(s1);
   if (cached1  == null) {
      cached1 = mySuperSplitter(s1):
      cache.put(s1, cached1);
   }
   String[] cached2 = cache.get(s2);
   if (cached2  == null) {
      cached2 = mySuperSplitter(s2):
      cache.put(s2, cached2);
   }

   return compareAsArrays(cached1, cached2);  // real comparison done here
}

这是个好的观点。
tom

它将需要修改“外部排序”代码,该代码可以在以下位置找到:code.google.com/p/externalsortinginjava
Constantin

1
可能最容易使用Map了。参见编辑。
user949300 2013年

鉴于这是外部排序引擎的一部分(处理的数据远远超出可用内存中可能容纳的数据),我实际上是在追求高效的“分割器”(是的,重复分割相同的String是浪费的,因此我最初需要尽快执行此操作)
君士坦丁

简短地浏览ExternalSort代码,看起来如果您在每个sortAndSave()调用的末尾(或开始)清除了缓存,那么由于缓存巨大,您不应耗尽内存。IMO,代码中应该有一些额外的钩子,例如触发事件或调用用户可以覆盖的不执行任何操作的受保护方法。(此外,它也不应该是所有静态方法,以便它们可以执行此操作。)您可能想联系作者并提出请求。
user949300 2013年

2

根据此基准,StringTokenizer用于拆分字符串的速度更快,但它不返回数组,因此使用起来不太方便。

如果您需要对数百万行进行排序,我建议您使用RDBMS。


3
那是在JDK 1.6下-字符串中的内容与1.7中的根本不同-请参阅java-performance.info/changes-to-string-java-1-7-0_06(特别是,创建子字符串不再是O(1)了,但是而不是O(n))。链接指出,在1.6 Pattern.split中使用的String创建与String.substring())不同-请参阅上面的注释中链接的代码,以遵循StringTokenizer.nextToken()及其可访问的包私有构造函数。

1

这是我用于解析大型(1GB +)制表符分隔文件的方法。它的开销远小于String.split(),但仅限于char用作分隔符。如果有人有更快的方法,我想看看。这也可以通过CharSequence和来完成CharSequence.subSequence,但这需要实现CharSequence.indexOf(char)String.indexOf(char[] source, int sourceOffset, int sourceCount, char[] target, int targetOffset, int targetCount, int fromIndex)如果感兴趣,请参考package方法)。

public static String[] split(final String line, final char delimiter)
{
    CharSequence[] temp = new CharSequence[(line.length() / 2) + 1];
    int wordCount = 0;
    int i = 0;
    int j = line.indexOf(delimiter, 0); // first substring

    while (j >= 0)
    {
        temp[wordCount++] = line.substring(i, j);
        i = j + 1;
        j = line.indexOf(delimiter, i); // rest of substrings
    }

    temp[wordCount++] = line.substring(i); // last substring

    String[] result = new String[wordCount];
    System.arraycopy(temp, 0, result, 0, wordCount);

    return result;
}

您是否已对此String.split()进行了基准测试?如果是这样,它如何比较?
杰伊·埃尔斯顿

@JayElston在900MB的文件上,它将拆分时间从7.7秒减少到6.2秒,因此快了大约20%。它仍然是我的浮点矩阵解析中最慢的部分。我猜剩下的大部分时间是数组分配。通过使用基于令牌化器的方法,并在方法中使用偏移量,可以减少矩阵分配,这看起来似乎更像我在代码上方引用的方法。
vallismortis
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.