Cara tercepat untuk memisahkan String yang dibatasi di Jawa

10

Saya sedang membangun Comparator yang menyediakan kemampuan mengurutkan multi-kolom pada String yang dibatasi. Saat ini saya menggunakan metode split dari kelas String sebagai pilihan pilihan saya untuk memisahkan String mentah menjadi token.

Apakah ini cara berkinerja terbaik untuk mengubah String mentah menjadi array String? Saya akan menyortir jutaan baris jadi saya pikir pendekatan itu penting.

Tampaknya berjalan dengan baik dan sangat mudah, tetapi tidak yakin apakah ada cara yang lebih cepat di java.

Berikut ini cara kerja sortir di Comparator saya:

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

Setelah membandingkan berbagai pendekatan, percaya atau tidak, metode split adalah yang tercepat menggunakan versi terbaru java. Anda dapat mengunduh komparator lengkap saya di sini: https://sourceforge.net/projects/multicolumnrowcomparator/

Konstantin
sumber
5
Saya akan menunjukkan bahwa sifat dari jawaban untuk pertanyaan ini tergantung pada implementasi jvm. Perilaku string (berbagi array dukungan umum di OpenJDK, tetapi tidak dalam OracleJDK) berbeda. Perbedaan ini dapat memiliki dampak signifikan pada pemisahan string dan pembuatan substring, bersama dengan pengumpulan sampah dan kebocoran memori. Seberapa besar susunan ini? Bagaimana kabarmu sekarang? Apakah Anda mempertimbangkan jawaban yang membuat tipe Stringish baru daripada Java Strings yang sebenarnya?
1
Khususnya melihat StringTokenizer nextToken yang akhirnya memanggil paket private String constructor . Bandingkan ini dengan perubahan yang didokumentasikan dalam Perubahan ke representasi internal yang dibuat di Java 1.7.0_06
Ukuran array tergantung pada jumlah kolom sehingga variabel. Comparator multi-kolom ini dilewatkan sebagai parameter seperti: ExternalSort.mergeSortedFiles (fileList, File baru ("BigFile.csv"), _comparator, Charset.defaultCharset (), false); Rutin penyortiran eksternal akan menyortir seluruh string baris, sebenarnya pembanding yang melakukan pemisahan dan penyortiran berdasarkan kolom penyortiran
Constantin
Saya akan mempertimbangkan melihat tokenizers lucene. Lucene dapat digunakan hanya sebagai pustaka analisis teks yang kuat yang berkinerja baik untuk tugas
Doug T.
Pertimbangkan Apache Commons Lang StringUtils.split[PreserveAllTokens](text, delimiter).
Pasang kembali Monica

Jawaban:

19

Saya telah menulis tes benchmark cepat dan kotor untuk ini. Ini membandingkan 7 metode yang berbeda, beberapa di antaranya memerlukan pengetahuan khusus tentang data yang dipecah.

Untuk pemisahan tujuan umum dasar, Guava Splitter 3.5x lebih cepat dari String # split () dan saya akan merekomendasikan menggunakannya. Stringtokenizer sedikit lebih cepat dari itu dan membelah diri Anda dengan indexOf dua kali lebih cepat lagi.

Untuk kodenya dan info lebih lanjut lihat http://demeranville.com/battle-of-the-tokenizers-delimited-text-parser-performance/

tom
sumber
Saya hanya ingin tahu apa JDK yang Anda gunakan ... dan jika itu 1,6, saya akan sangat tertarik melihat rekap hasil Anda di 1,7.
1
itu 1,6 saya pikir. Kode tersebut ada sebagai tes JUnit jika Anda ingin menjalankannya di 1.7. Catatan String.split melakukan pencocokan regex, yang akan selalu lebih lambat daripada pemisahan pada karakter tunggal yang ditentukan.
tom
1
Yap, namun untuk 1.6, kode StringTokenizer (dan sejenisnya) memanggil String.substring () yang membuat O (1) membuat string baru dengan menggunakan array backing yang sama. Ini diubah pada 1.7 untuk membuat salinan dari bagian yang diperlukan dari array dukungan bukan untuk O (n). Ini bisa berdampak kecil pada hasil Anda membuat perbedaan antara pemisahan dan StringTokenizer lebih sedikit (memperlambat semua yang menggunakan substring sebelumnya).
1
Tentu benar. Masalahnya adalah cara kerja StringTokenizer berubah dari "untuk membuat string baru menetapkan 3 integer" menjadi "untuk membuat string baru, lakukan salinan array data" yang akan mengubah seberapa cepat bagian itu. Perbedaan antara berbagai pendekatan mungkin lebih sedikit sekarang dan akan menarik (jika tidak ada alasan lain selain menarik) untuk melakukan tindak lanjut dengan Java 1.7.
1
Terima kasih untuk artikelnya! Sangat berguna dan akan digunakan untuk membandingkan berbagai pendekatan.
Constantin
5

Seperti @Tom menulis, pendekatan tipe indexOf lebih cepat daripada String.split(), karena yang terakhir berurusan dengan ekspresi reguler dan memiliki banyak overhead tambahan untuk mereka.

Namun, satu perubahan algoritma yang mungkin memberi Anda speedup super. Dengan asumsi bahwa Pembanding ini akan digunakan untuk mengurutkan ~ 100.000 String Anda, jangan menulis Comparator<String>. Karena, dalam proses pengurutan Anda, String yang sama kemungkinan akan dibandingkan beberapa kali, jadi Anda akan membaginya beberapa kali, dll ...

Membagi semua Strings sekali dalam String [] s, dan memiliki Comparator<String[]>semacam String []. Kemudian, pada akhirnya, Anda bisa menggabungkan semuanya.

Atau, Anda juga bisa menggunakan Peta untuk menyimpan String -> String [] atau sebaliknya. misalnya (samar) Juga perhatikan, Anda berdagang memori untuk kecepatan, harap Anda memiliki RAM lotsa

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
}
pengguna949300
sumber
ini adalah poin yang bagus.
tom
Diperlukan modifikasi pada kode Sortir Eksternal yang dapat ditemukan di sini: code.google.com/p/externalsortinginjava
Constantin
1
Mungkin paling mudah menggunakan Peta. Lihat edit.
user949300
Mengingat bahwa ini adalah bagian dari mesin pengurutan eksternal (untuk menangani lebih banyak data daripada yang dapat ditampung dalam memori yang tersedia), saya benar-benar mencari "splitter" yang efisien (ya, boros untuk memisahkan String yang sama berulang kali, karena itu saya kebutuhan asli untuk melakukan ini secepat mungkin)
Constantin
Menjelajah sebentar kode ExternalSort, sepertinya jika Anda membersihkan cache di akhir (atau mulai) dari setiap sortAndSave()panggilan maka Anda tidak boleh kehabisan memori karena cache yang besar. IMO, kode tersebut harus memiliki beberapa kait tambahan seperti acara pembakaran atau memanggil metode yang tidak dilindungi apa pun yang dapat ditimpa oleh pengguna seperti Anda. (Juga, seharusnya tidak semua metode statis sehingga mereka dapat melakukan ini) Anda mungkin ingin menghubungi penulis dan mengajukan permintaan.
user949300
2

Menurut tolok ukur ini , StringTokenizer lebih cepat untuk memisahkan string tetapi tidak mengembalikan array yang membuatnya kurang nyaman.

Jika Anda perlu mengurutkan jutaan baris, saya sarankan menggunakan RDBMS.

Tulains Córdova
sumber
3
Itu di bawah JDK 1.6 - hal-hal dalam string secara fundamental berbeda di 1,7 - lihat java-performance.info/changes-to-string-java-1-7-0_06 (khususnya, membuat substring bukan O (1) lagi tetapi bukan O (n)). Tautan mencatat bahwa dalam 1.6 Pattern.split menggunakan pembuatan String yang berbeda dari String.substring ()) - lihat kode yang terhubung dalam komentar di atas untuk mengikuti StringTokenizer.nextToken () dan konstruktor paket privat yang dapat diaksesnya.
1

Ini adalah metode yang saya gunakan untuk mem-parsing file-file besar yang dibatasi tab (1GB +). Ini memiliki overhead jauh lebih sedikit daripada String.split(), tetapi terbatas charsebagai pembatas. Jika ada yang memiliki metode yang lebih cepat, saya ingin melihatnya. Ini juga dapat dilakukan berulang CharSequencedan CharSequence.subSequence, tetapi itu membutuhkan implementasi CharSequence.indexOf(char)(rujuk ke metode paket String.indexOf(char[] source, int sourceOffset, int sourceCount, char[] target, int targetOffset, int targetCount, int fromIndex)jika tertarik).

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;
}
vallismortis
sumber
Sudahkah Anda membandingkan ini dengan String.split ()? Jika demikian, bagaimana perbandingannya?
Jay Elston
@JayElston Pada file 900MB, ini mengurangi waktu split dari 7,7 detik menjadi 6,2 detik, jadi sekitar 20% lebih cepat. Ini masih merupakan bagian paling lambat dari penguraian matriks floating-point saya. Saya menduga bahwa sebagian besar waktu yang tersisa adalah alokasi array. Dimungkinkan untuk memotong alokasi matriks dengan menggunakan pendekatan berbasis tokenizer dengan offset dalam metode - yang akan mulai terlihat lebih seperti metode yang saya kutip di atas kode.
vallismortis