Javaで区切られた文字列を分割する最も簡単な方法


10

区切られた文字列で複数列のソート機能を提供するコンパレータを構築しています。私は現在、生の文字列をトークンに分割するための好ましい選択肢として、Stringクラスのsplitメソッドを使用しています。

これは、生の文字列を文字列配列に変換するのに最適な方法ですか?何百万もの行を並べ替えるので、アプローチが重要だと思います。

それはうまく動作するようで非常に簡単ですが、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;
}

さまざまなアプローチのベンチマークを行った後、信じられないかもしれませんが、splitメソッドは最新バージョンのJavaを使用するのが最も速かったです。ここに私の完成したコンパレータをダウンロードできます:https : //sourceforge.net/projects/multicolumnrowcomparator/


5
この質問に対する答えの性質は、jvmの実装に依存することを指摘しておきます。文字列の動作(OpenJDKでは共通のバッキング配列を共有しますが、OracleJDKでは共有しません)は異なります。この違いは、ガベージコレクションとメモリリークとともに、文字列の分割と部分文字列の作成に大きな影響を与える可能性があります。これらの配列はどのくらいの大きさですか?今はどうですか?実際のJava文字列ではなく、新しい文字列型になる答えを考えますか?


配列のサイズは列の数に依存するため、可変です。この複数列のコンパレータは、次のようにパラメータとして渡されます。ExternalSort.mergeSortedFiles(fileList、new File( "BigFile.csv")、_comparator、Charset.defaultCharset()、false); 外部並べ替えルーチンは行文字列全体を並べ替えます。実際には、並べ替え列に基づいて分割と並べ替えを行うコンパレータです
Constantin

luceneのトークナイザーを検討することを検討します。Luceneは、単純なタスクと複雑なタスクの両方でうまく機能する強力なテキスト分析ライブラリとして使用できます
Doug T.

Apache Commons Langを検討してくださいStringUtils.split[PreserveAllTokens](text, delimiter)
モニカを

回答:


19

私はこのための迅速で汚れたベンチマークテストを作成しました。7つの異なる方法を比較します。そのうちのいくつかは、分割されるデータの特定の知識を必要とします。

基本的な汎用分割では、Guava SplitterはString#split()より3.5倍高速であり、私はそれを使用することをお勧めします。Stringtokenizerはそれよりもわずかに高速で、indexOfを使用した自分自身の分割は、2倍高速です。

コードと詳細については、http://demeranville.com/battle-of-the-tokenizers-delimited-text-parser-performance/を参照してください


私が使用しているJDKに興味があるだけです...そしてそれが1.6だった場合、1.7で結果の要約を確認することに最も興味があります。

1
1.6だと思います。コードを1.7で実行する場合は、JUnitテストとして存在します。注String.splitは、regexマッチングを実行します。これは、定義された単一の文字での分割よりも常に遅くなります。
tom

1
はい、ただし1.6では、StringTokenizer(および類似の)コードは、同じバッキング配列を使用して新しい文字列のO(1)作成を行うString.substring()を呼び出します。これは1.7で変更され、O(n)ではなくバッキング配列の必要な部分のコピーを作成します。これは、結果に重大な影響を与える可能性があり、splitとStringTokenizerの違いが少なくなります(以前にサブストリングを使用していたすべての速度が低下します)。

1
確かにそうだ。実際のところ、StringTokenizerの動作方法は、「新しい文字列を作成して3つの整数を割り当てる」から「新しい文字列を作成してデータの配列コピーを行う」になり、その部分の速度が変わります。さまざまなアプローチの違いは今では少なくなり、Java 1.7でフォローアップを行うことは興味深いことです(興味深いこと以外に理由がない場合)。

1
その記事をありがとう!非常に便利で、さまざまなアプローチのベンチマークに使用します。
コンスタンティン

5

@Tomが書いているように、String.split()後者は正規表現を扱い、余分なオーバーヘッドが多いため、indexOfタイプのアプローチはよりも高速です。

ただし、1つのアルゴリズムの変更により、速度が大幅に向上する場合があります。このコンパレータが〜100,000文字列のソートに使用されると想定している場合は、を記述しないでくださいComparator<String>。お使いの一種の過程で、同じ文字列の可能性が高いと比較になり、そのため、複数のあなたがそれに分割されますので、回複数回など、...

すべての文字列を一度 String [] sに分割し、String []でComparator<String[]>ソートします。その後、最後に、それらをすべて組み合わせることができます。

または、マップを使用してString-> String []をキャッシュすることも、その逆も可能です。例(sketchy)また、速度とメモリを交換していることに注意してください。lotsaRAMがあることを願っています

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


1
おそらくマップを使用するのが最も簡単です。編集を参照してください。
user949300 2013

これが外部ソートエンジンの一部であることを考えると(使用可能なメモリに収まりきらないデータをはるかに処理するため)、効率的な「スプリッター」を求めていました(そうです、同じ文字列を繰り返し分割することは無駄です。オリジナルはこれを可能な限り速く行う必要があります)
コンスタンティン

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.substring())とは異なるStringの作成を使用したことを示しています-上記のコメントにリンクされているコードを参照して、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)興味がある場合はパッケージメソッドを参照してください)。

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()を比較しましたか?もしそうなら、それはどのように比較しますか?
ジェイエルストン2017

@JayElston 900MBのファイルでは、分割時間が7.7秒から6.2秒に短縮され、約20%高速になりました。それはまだ私の浮動小数点行列解析の最も遅い部分です。残りの時間の多くは配列の割り当てだと思います。メソッドにオフセットを指定したトークナイザーベースのアプローチを使用することで、マトリックスの割り当てを削減できる可能性があります。これにより、コードの上で引用したメソッドのように見えるようになります。
vallismortis 2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.