Pemindai vs. StringTokenizer vs. String.Split

155

Saya baru saja belajar tentang kelas Scanner Java dan sekarang saya bertanya-tanya bagaimana membandingkan / bersaing dengan StringTokenizer dan String.Split. Saya tahu bahwa StringTokenizer dan String.Split hanya berfungsi pada Strings, jadi mengapa saya ingin menggunakan Scanner untuk String? Apakah Scanner hanya dimaksudkan sebagai one-stop-shopping untuk pemisahan?

Dave
sumber

Jawaban:

240

Mereka pada dasarnya adalah kuda untuk kursus.

  • Scannerdirancang untuk kasus di mana Anda perlu mengurai string, mengeluarkan data dari berbagai jenis. Ini sangat fleksibel, tetapi bisa dibilang tidak memberi Anda API paling sederhana untuk sekadar mendapatkan serangkaian string yang dibatasi oleh ekspresi tertentu.
  • String.split()dan Pattern.split()memberi Anda sintaks mudah untuk melakukan yang terakhir, tetapi pada dasarnya itulah yang mereka lakukan. Jika Anda ingin mengurai string yang dihasilkan, atau mengubah pembatas setengah jalan tergantung pada token tertentu, mereka tidak akan membantu Anda dengan itu.
  • StringTokenizerbahkan lebih membatasi daripada String.split(), dan juga sedikit lebih fiddlier untuk digunakan. Ini pada dasarnya dirancang untuk menarik token yang dibatasi oleh substring tetap. Karena pembatasan ini, sekitar dua kali lebih cepat String.split(). (Lihat perbandinganString.split()StringTokenizer saya tentang dan .) Itu juga ada sebelum API ekspresi reguler, yang String.split()merupakan bagiannya.

Anda akan mencatat dari timing saya yang String.split()masih dapat menandai ribuan string dalam beberapa milidetik pada mesin biasa. Selain itu, ia memiliki keunggulan dibandingkan StringTokenizermemberi Anda output sebagai array string, yang biasanya Anda inginkan. Menggunakan Enumeration, seperti yang disediakan oleh StringTokenizer, terlalu "rewel secara sintaksis" sebagian besar waktu. Dari sudut pandang ini, StringTokenizersedikit membuang-buang ruang saat ini, dan Anda mungkin juga hanya menggunakan String.split().

Neil Coffey
sumber
8
Juga menarik untuk melihat hasil Pemindai pada pengujian yang sama dengan yang Anda jalankan pada String.Split dan StringTokenizer.
Dave
2
Memberi saya jawaban untuk pertanyaan lain: "mengapa penggunaan StringTokenizer tidak disarankan, seperti yang tercantum dalam catatan Java API?". Dari teks ini tampaknya jawabannya adalah "karena String.split () cukup cepat".
Kaki
1
Jadi apakah StringTokenizer sudah tidak digunakan lagi sekarang?
Steve the Maker
apa yang harus digunakan daripada itu? Pemindai?
Adrian
4
Saya menyadari ini adalah jawaban untuk pertanyaan lama, tetapi jika saya perlu membagi aliran teks besar menjadi token dengan cepat, bukankah StringTokenizermasih taruhan terbaik saya karena String.split()hanya akan kehabisan memori?
Sergei Tachenov
57

Mari kita mulai dengan menghilangkan StringTokenizer. Semakin tua dan bahkan tidak mendukung ekspresi reguler. Dokumentasinya menyatakan:

StringTokenizeradalah kelas lawas yang dipertahankan karena alasan kompatibilitas meskipun penggunaannya tidak disarankan dalam kode baru. Disarankan bahwa siapa pun yang mencari fungsionalitas ini menggunakan splitmetode Stringatau java.util.regexpaket sebagai gantinya.

Jadi mari kita membuangnya segera. Itu pergi split()dan Scanner. Apa perbedaan di antara mereka?

Untuk satu hal, split()cukup kembalikan array, yang membuatnya mudah untuk menggunakan foreach loop:

for (String token : input.split("\\s+") { ... }

Scanner dibangun lebih seperti aliran:

while (myScanner.hasNext()) {
    String token = myScanner.next();
    ...
}

atau

while (myScanner.hasNextDouble()) {
    double token = myScanner.nextDouble();
    ...
}

(Ini memiliki API yang agak besar , jadi jangan berpikir bahwa itu selalu terbatas pada hal-hal sederhana seperti itu.)

Antarmuka gaya aliran ini dapat berguna untuk mem-parsing file teks sederhana atau input konsol, ketika Anda tidak memiliki (atau tidak bisa mendapatkan) semua input sebelum mulai mengurai.

Secara pribadi, satu-satunya waktu saya bisa ingat menggunakan Scanneradalah untuk proyek sekolah, ketika saya harus mendapatkan input pengguna dari baris perintah. Itu membuat operasi semacam itu mudah. Tetapi jika saya memiliki sesuatu Stringyang ingin saya pisahkan, hampir tidak ada alasan untuk pergi split().

Michael Myers
sumber
20
StringTokenizer 2x lebih cepat dari String.split (). Jika Anda TIDAK PERLU menggunakan ekspresi reguler, JANGAN!
Alex Worden
Saya hanya digunakan Scanneruntuk mendeteksi karakter baris baru dalam suatu pemberian String. Karena karakter baris baru dapat bervariasi dari platform ke platform (lihat di Patternjavadoc!) Dan string input TIDAK dijamin sesuai System.lineSeparator(), saya merasa Scannerlebih cocok karena sudah tahu karakter baris baru apa yang harus dicari ketika menelepon nextLine(). Karena String.splitsaya harus memberi makan dalam pola regex yang benar untuk mendeteksi pemisah baris, yang saya temukan tidak disimpan di lokasi standar (yang terbaik yang bisa saya lakukan adalah menyalinnya dari sumber Scannerclass ').
ADTC
9

StringTokenizer selalu ada di sana. Ini adalah yang tercepat dari semuanya, tetapi idiom seperti enumerasi mungkin tidak terlihat seanggun yang lain.

perpecahan muncul di JDK 1.4. Lebih lambat daripada tokenizer tetapi lebih mudah digunakan, karena dapat dipanggil dari kelas String.

Pemindai berada di JDK 1.5. Ini adalah yang paling fleksibel dan mengisi jeda panjang pada Java API untuk mendukung yang setara dengan keluarga fungsi Cs scanf yang terkenal.

H Marcelo Morales
sumber
6

Jika Anda memiliki objek String yang ingin Anda tokenize, nikmatilah menggunakan metode split String atas StringTokenizer. Jika Anda mem-parsing data teks dari sumber di luar program Anda, seperti dari file, atau dari pengguna, di situlah Scanner berguna.

Bill the Lizard
sumber
5
Sama seperti itu, tidak ada pembenaran, tidak ada alasan?
jan.supol
6

Split lambat, tapi tidak selambat Scanner. StringTokenizer lebih cepat daripada split. Namun, saya menemukan bahwa saya dapat memperoleh dua kali lipat kecepatan, dengan memperdagangkan beberapa fleksibilitas, untuk mendapatkan peningkatan kecepatan, yang saya lakukan di JFastParser https://github.com/hughperkins/jfastparser

Menguji string yang berisi satu juta ganda:

Scanner: 10642 ms
Split: 715 ms
StringTokenizer: 544ms
JFastParser: 290ms
Hugh Perkins
sumber
Beberapa Javadoc pasti menyenangkan, dan bagaimana jika Anda ingin menguraikan sesuatu selain data numerik?
NickJ
Yah, ini dirancang untuk kecepatan, bukan kecantikan. Ini cukup sederhana, hanya beberapa baris, sehingga Anda bisa menambahkan beberapa opsi lagi untuk penguraian teks jika Anda mau.
Hugh Perkins
4

String.split tampaknya jauh lebih lambat daripada StringTokenizer. Satu-satunya keuntungan dengan split adalah Anda mendapatkan berbagai token. Anda juga dapat menggunakan ekspresi reguler apa pun secara terpisah. org.apache.commons.lang.StringUtils memiliki metode split yang bekerja jauh lebih cepat daripada salah satu dari dua yaitu. StringTokenizer atau String.split. Namun pemanfaatan CPU untuk ketiganya hampir sama. Jadi kita juga memerlukan metode yang kurang intensif CPU, yang saya masih belum dapat menemukannya.

Manish
sumber
3
Jawaban ini sedikit tidak masuk akal. Anda mengatakan Anda mencari sesuatu yang lebih cepat tetapi "kurang CPU intensif". Program apa pun dijalankan oleh CPU. Jika suatu program tidak memanfaatkan CPU Anda 100%, maka itu harus menunggu sesuatu yang lain, seperti I / O. Itu seharusnya tidak pernah menjadi masalah ketika membahas tokenization string, kecuali Anda sedang melakukan akses disk langsung (yang kami terutama tidak lakukan di sini).
Jolta
4

Baru-baru ini saya melakukan beberapa percobaan tentang kinerja buruk String.split () dalam situasi yang sangat sensitif terhadap kinerja. Anda mungkin menemukan ini berguna.

http://eblog.chrononsystems.com/hidden-evils-of-javas-stringsplit-and-stringr

Intinya adalah bahwa String.split () mengkompilasi pola Ekspresi Reguler setiap kali dan dengan demikian dapat memperlambat program Anda, dibandingkan dengan jika Anda menggunakan objek Pola yang dikompilasi dan menggunakannya secara langsung untuk beroperasi pada String.

pdeva
sumber
4
Sebenarnya String.split () tidak selalu mengkompilasi pola. Lihatlah sumbernya jika 1,7 java, Anda akan melihat bahwa ada tanda centang jika polanya adalah karakter tunggal dan bukan yang lolos, itu akan membagi string tanpa regexp, jadi itu harus cukup cepat.
Krzysztof Krasoń
1

Untuk skenario default, saya akan menyarankan Pattern.split () juga tetapi jika Anda membutuhkan kinerja maksimum (terutama pada Android semua solusi yang saya uji cukup lambat) dan Anda hanya perlu dipisah dengan satu karakter, saya sekarang menggunakan metode saya sendiri:

public static ArrayList<String> splitBySingleChar(final char[] s,
        final char splitChar) {
    final ArrayList<String> result = new ArrayList<String>();
    final int length = s.length;
    int offset = 0;
    int count = 0;
    for (int i = 0; i < length; i++) {
        if (s[i] == splitChar) {
            if (count > 0) {
                result.add(new String(s, offset, count));
            }
            offset = i + 1;
            count = 0;
        } else {
            count++;
        }
    }
    if (count > 0) {
        result.add(new String(s, offset, count));
    }
    return result;
}

Gunakan "abc" .toCharArray () untuk mendapatkan array char untuk sebuah String. Sebagai contoh:

String s = "     a bb   ccc  dddd eeeee  ffffff    ggggggg ";
ArrayList<String> result = splitBySingleChar(s.toCharArray(), ' ');
Simon
sumber
1

Satu perbedaan penting adalah bahwa baik String.split () dan Scanner dapat menghasilkan string kosong tetapi StringTokenizer tidak pernah melakukannya.

Sebagai contoh:

String str = "ab cd  ef";

StringTokenizer st = new StringTokenizer(str, " ");
for (int i = 0; st.hasMoreTokens(); i++) System.out.println("#" + i + ": " + st.nextToken());

String[] split = str.split(" ");
for (int i = 0; i < split.length; i++) System.out.println("#" + i + ": " + split[i]);

Scanner sc = new Scanner(str).useDelimiter(" ");
for (int i = 0; sc.hasNext(); i++) System.out.println("#" + i + ": " + sc.next());

Keluaran:

//StringTokenizer
#0: ab
#1: cd
#2: ef
//String.split()
#0: ab
#1: cd
#2: 
#3: ef
//Scanner
#0: ab
#1: cd
#2: 
#3: ef

Ini karena pembatas untuk String.split () dan Scanner.useDelimiter () bukan hanya string, tetapi ekspresi reguler. Kita dapat mengganti pembatas "" dengan "+" pada contoh di atas untuk membuat mereka berperilaku seperti StringTokenizer.

John29
sumber
-5

String.split () berfungsi sangat baik tetapi memiliki batasannya sendiri, seperti jika Anda ingin membagi string seperti yang ditunjukkan di bawah ini berdasarkan simbol pipa tunggal atau ganda (|), itu tidak berfungsi. Dalam situasi ini Anda dapat menggunakan StringTokenizer.

ABC | IJK

Mujahid shaik
sumber
12
Sebenarnya, Anda dapat membagi contoh hanya dengan "ABC | IJK" .split ("\\ |");
Tomo
"ABC || DEF ||" .split ("\\ |") tidak benar-benar bekerja karena itu akan mengabaikan dua nilai kosong yang tertinggal, yang membuat parsing lebih rumit daripada yang seharusnya.
Armand