สแกนเนอร์กับ StringTokenizer เทียบกับ String.Split


155

ฉันเพิ่งเรียนรู้เกี่ยวกับคลาสเครื่องสแกนของ Java และตอนนี้ฉันสงสัยว่ามันเปรียบเทียบ / แข่งขันกับ StringTokenizer และ String.Split ได้อย่างไร ฉันรู้ว่า StringTokenizer และ String.Split ใช้งานได้กับ Strings เท่านั้นเหตุใดฉันจึงต้องการใช้สแกนเนอร์เป็นสตริง สแกนเนอร์ตั้งใจที่จะเป็นแหล่งช้อปปิ้งแบบครบวงจรเพื่อการแยกหรือไม่

คำตอบ:


240

พวกมันเป็นม้าสำหรับหลักสูตร

  • Scannerถูกออกแบบมาสำหรับกรณีที่คุณต้องการแยกสตริงดึงข้อมูลประเภทต่างๆ มีความยืดหยุ่นสูง แต่เนื้อหาไม่ได้ให้ API ที่ง่ายที่สุดสำหรับคุณในการรับอาร์เรย์ของสตริงที่คั่นด้วยนิพจน์เฉพาะ
  • String.split()และPattern.split()ทำให้คุณมีไวยากรณ์ที่ง่ายสำหรับการทำขั้นตอนหลัง แต่นั่นคือสิ่งที่พวกเขาทำ หากคุณต้องการแยกสตริงที่เกิดขึ้นหรือเปลี่ยนตัวคั่นครึ่งทางขึ้นอยู่กับโทเค็นที่เฉพาะเจาะจงพวกเขาจะไม่ช่วยคุณ
  • StringTokenizerมีข้อ จำกัด มากขึ้นกว่าเดิมString.split()และใช้เพียงเล็กน้อยเที่ยวเล่น มันถูกออกแบบมาสำหรับดึงโทเค็นที่คั่นด้วยสารตั้งต้นคงที่ เพราะข้อ จำกัด String.split()นี้มันเป็นเรื่องของสองครั้งเร็ว (ดูเปรียบเทียบString.split()StringTokenizerของฉันและ .) นอกจากนี้มันยังมีมาก่อนนิพจน์ปกติ API ซึ่งString.split()เป็นส่วนหนึ่ง

คุณจะสังเกตเห็นจากการตั้งเวลาของฉันที่String.split()ยังสามารถโทเค็นสตริงหลายพันรายการในไม่กี่มิลลิวินาทีในเครื่องทั่วไป นอกจากนี้ยังมีข้อได้เปรียบมากกว่าStringTokenizerที่จะให้ผลลัพธ์เป็นอาร์เรย์สตริงซึ่งโดยปกติแล้วเป็นสิ่งที่คุณต้องการ การใช้Enumerationตามที่ให้ไว้StringTokenizerก็เป็น "จุกจิก syntactically" ส่วนใหญ่เวลา จากมุมมองนี้เป็นบิตของเสียพื้นที่ในปัจจุบันและคุณอาจได้เป็นอย่างดีเพียงแค่ใช้StringTokenizerString.split()


8
น่าสนใจที่จะเห็นผลลัพธ์ของเครื่องสแกนในการทดสอบเดียวกันกับที่คุณรันบน String.Split และ StringTokenizer
เดฟ

2
ให้คำตอบกับคำถามอื่น: "ทำไมการใช้งาน StringTokenizer จึงทำให้หมดกำลังใจดังที่ระบุไว้ในบันทึกย่อของ Java API" จากข้อความนี้ดูเหมือนว่าคำตอบจะเป็น "เพราะ String.split () เร็วพอ"
ขา

1
StringTokenizer เลิกใช้แล้วหรือเปล่า?
Steve the Maker

สิ่งที่จะใช้แทนมันได้หรือไม่ เครื่องสแกนเนอร์?
Adrian

4
ฉันรู้ว่ามันเป็นคำตอบสำหรับคำถามเก่า แต่ถ้าฉันต้องการแยกสตรีมข้อความขนาดใหญ่เป็นโทเค็นได้ทันทีก็StringTokenizerยังเป็นทางออกที่ดีที่สุดของฉันไม่ได้เพราะString.split()หน่วยความจำจะหมด
Sergei Tachenov

57

มาเริ่มStringTokenizerกันเลยกำจัด มันเริ่มแก่แล้วและยังไม่รองรับการแสดงออกปกติ เอกสารประกอบของรัฐ:

StringTokenizerเป็นคลาสดั้งเดิมที่ถูกเก็บไว้เพื่อเหตุผลด้านความเข้ากันได้แม้ว่าการใช้งานจะไม่ได้รับการสนับสนุนในรหัสใหม่ ขอแนะนำให้ทุกคนที่แสวงหาฟังก์ชั่นนี้ใช้splitวิธีการStringหรือjava.util.regexแพคเกจแทน

ลองโยนมันออกไปทันที ที่ใบและsplit() Scannerความแตกต่างระหว่างพวกเขาคืออะไร

สำหรับสิ่งหนึ่งsplit()เพียงส่งกลับอาร์เรย์ซึ่งทำให้ง่ายต่อการใช้วนรอบ foreach:

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

Scanner สร้างขึ้นเหมือนสตรีม:

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

หรือ

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

(มันมีAPI ที่ค่อนข้างใหญ่ดังนั้นอย่าคิดว่ามัน จำกัด เฉพาะเรื่องง่าย ๆ อยู่เสมอ)

อินเตอร์เฟสสไตล์การสตรีมนี้มีประโยชน์สำหรับการวิเคราะห์ไฟล์ข้อความหรืออินพุตคอนโซลแบบง่ายเมื่อคุณไม่มีอินพุตทั้งหมด (หรือไม่สามารถรับ) ก่อนที่จะแยกวิเคราะห์

โดยส่วนตัวครั้งเดียวที่ฉันจำได้ว่าใช้Scannerสำหรับโครงการโรงเรียนเมื่อฉันต้องรับข้อมูลจากผู้ใช้จากบรรทัดคำสั่ง มันทำให้การดำเนินการนั้นง่าย แต่ถ้าฉันมีที่ฉันต้องการจะแยกมันเกือบจะไม่มีเกมง่ายๆที่จะไปกับStringsplit()


20
StringTokenizer เร็วกว่า 2x เป็น String.split () หากคุณไม่จำเป็นต้องใช้นิพจน์ทั่วไปอย่า!
อเล็กซ์ Worden

ฉันเพียงแค่ใช้ในการตรวจสอบตัวอักษรในบรรทัดใหม่ที่กำหนดScanner Stringเนื่องจากอักขระขึ้นบรรทัดใหม่อาจแตกต่างกันไปในแต่ละแพลตฟอร์ม (ดูที่Patternjavadoc!) และสตริงการป้อนข้อมูลไม่รับประกันว่าจะเป็นไปตามSystem.lineSeparator()นั้นฉันพบว่าScannerเหมาะสมกว่าเพราะรู้แล้วว่ามีอักขระบรรทัดใหม่ให้ค้นหาเมื่อโทรnextLine()อะไร สำหรับString.splitฉันจะต้องป้อนข้อมูลในรูปแบบ regex ที่ถูกต้องเพื่อตรวจจับตัวแยกบรรทัดซึ่งฉันไม่พบที่จัดเก็บในตำแหน่งมาตรฐานใด ๆ (ที่ดีที่สุดที่ฉันสามารถทำได้คือคัดลอกจากScannerแหล่งที่มาของคลาส)
ADTC

9

StringTokenizer อยู่ที่นั่นเสมอ มันเร็วที่สุดของทั้งหมด แต่สำนวนที่มีลักษณะคล้ายการแจงนับอาจดูไม่สง่างามเหมือนคนอื่น ๆ

แยกมาอยู่ใน JDK 1.4 ช้ากว่า tokenizer แต่ใช้ง่ายกว่าเนื่องจากสามารถเรียกได้จากคลาส String

สแกนเนอร์มาใน JDK 1.5 มันมีความยืดหยุ่นมากที่สุดและเติมเต็มช่องว่างที่ยาวนานของ Java API เพื่อสนับสนุนเทียบเท่ากับฟังก์ชั่น Cs scanf ที่มีชื่อเสียง


6

หากคุณมีวัตถุ String ที่คุณต้องการทำโทเค็นโปรดใช้วิธีการแยกของ String บน StringTokenizer หากคุณแยกวิเคราะห์ข้อมูลข้อความจากแหล่งนอกโปรแกรมเช่นจากไฟล์หรือจากผู้ใช้นั่นคือสิ่งที่เครื่องสแกนเนอร์มีประโยชน์


5
เช่นเดียวกับที่ไม่มีเหตุผลไม่มีเหตุผล?
jan.supol

6

แยกช้า แต่ไม่ช้าเท่ากับสแกนเนอร์ StringTokenizer เร็วกว่าการแยก อย่างไรก็ตามฉันพบว่าฉันสามารถเพิ่มความเร็วเป็นสองเท่าโดยการซื้อขายความยืดหยุ่นบางอย่างเพื่อรับการเร่งความเร็วซึ่งฉันทำได้ที่ JFastParser https://github.com/hughperkins/jfastparser

การทดสอบสตริงที่มีหนึ่งล้านคู่:

Scanner: 10642 ms
Split: 715 ms
StringTokenizer: 544ms
JFastParser: 290ms

Javadoc บางตัวน่าใช้แล้วถ้าคุณต้องการแยกคำอื่นที่ไม่ใช่ข้อมูลตัวเลข?
NickJ

มันถูกออกแบบมาเพื่อความเร็วไม่ใช่ความงาม มันค่อนข้างง่ายเพียงไม่กี่บรรทัดดังนั้นคุณสามารถเพิ่มตัวเลือกเพิ่มเติมสำหรับการแยกวิเคราะห์ข้อความหากคุณต้องการ
ฮิวจ์ Perkins

4

String.split ดูเหมือนจะช้ากว่า StringTokenizer มาก ข้อดีอย่างเดียวของการแบ่งคือคุณได้รับโทเค็นมากมาย นอกจากนี้คุณสามารถใช้การแสดงออกปกติใด ๆ ในการแยก org.apache.commons.lang.StringUtils มีวิธีการแยกซึ่งทำงานได้เร็วกว่าใด ๆ ของสอง ได้แก่ StringTokenizer หรือ String.split แต่การใช้งาน CPU ของทั้งสามตัวนั้นใกล้เคียงกัน ดังนั้นเราจึงต้องการวิธีที่ใช้ CPU น้อยกว่าซึ่งฉันก็ยังหาไม่เจอ


3
คำตอบนี้ไร้สาระเล็กน้อย คุณบอกว่าคุณกำลังมองหาบางสิ่งที่เร็วกว่า แต่ "ใช้ CPU น้อยลง" โปรแกรมใด ๆ ก็ตามจะถูกดำเนินการโดย CPU หากโปรแกรมไม่ใช้ CPU ของคุณ 100% ต้องรออย่างอื่นเช่น I / O นั่นไม่น่าจะเป็นปัญหาเมื่อพูดถึงการโทเค็นสตริงยกเว้นว่าคุณกำลังเข้าถึงดิสก์โดยตรง (ซึ่งเราไม่ได้ทำที่นี่)
Jolta

4

เมื่อเร็ว ๆ นี้ฉันได้ทำการทดลองบางอย่างเกี่ยวกับประสิทธิภาพที่ไม่ดีของ String.split () ในสถานการณ์ที่มีประสิทธิภาพสูง คุณอาจพบว่ามีประโยชน์นี้

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

สรุปสาระสำคัญคือ String.split () รวบรวมรูปแบบการแสดงออกปกติในแต่ละครั้งและสามารถทำให้โปรแกรมของคุณช้าลงเมื่อเทียบกับถ้าคุณใช้วัตถุรูปแบบที่คอมไพล์แล้วและใช้มันโดยตรงเพื่อทำงานบนสตริง


4
ที่จริงแล้ว String.split () ไม่ได้รวบรวมรูปแบบเสมอไป ดูซอร์สถ้า 1.7 java คุณจะเห็นว่ามีการตรวจสอบว่ารูปแบบเป็นอักขระตัวเดียวและไม่ใช่แบบที่มีการหลีกเลี่ยงมันจะแยกสตริงโดยไม่ต้องใช้ regexp ดังนั้นมันควรจะค่อนข้างเร็ว
Krzysztof Krasoń

1

สำหรับสถานการณ์เริ่มต้นฉันขอแนะนำ Pattern.split () เช่นกัน แต่ถ้าคุณต้องการประสิทธิภาพสูงสุด (โดยเฉพาะอย่างยิ่งบน Android โซลูชันทั้งหมดที่ฉันทดสอบค่อนข้างช้า) และคุณต้องแยกด้วยถ่านเดี่ยวตอนนี้ฉันใช้วิธีการของตัวเอง:

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

ใช้ "abc" .toCharArray () เพื่อรับอาร์เรย์ถ่านสำหรับสตริง ตัวอย่างเช่น:

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

1

ความแตกต่างที่สำคัญอย่างหนึ่งคือทั้ง String.split () และสแกนเนอร์สามารถสร้างสตริงที่ว่างเปล่า แต่ StringTokenizer จะไม่ทำเช่นนั้น

ตัวอย่างเช่น:

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());

เอาท์พุท:

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

นี่เป็นเพราะตัวคั่นสำหรับ String.split () และ Scanner.useDelimiter () ไม่ได้เป็นเพียงสตริง แต่เป็นนิพจน์ทั่วไป เราสามารถแทนที่ตัวคั่น "" ด้วย "+" ในตัวอย่างด้านบนเพื่อทำให้มันทำงานเหมือน StringTokenizer


-5

String.split () ทำงานได้ดีมาก แต่มีขอบเขตของตัวเองเช่นถ้าคุณต้องการแยกสตริงตามที่แสดงด้านล่างโดยใช้สัญลักษณ์เดียวหรือสองครั้ง (|) มันไม่ทำงาน ในสถานการณ์นี้คุณสามารถใช้ StringTokenizer

เอบีซี | IJK


12
ที่จริงคุณสามารถแยกตัวอย่างของคุณด้วย "ABC | IJK" .split ("\\ |");
โทโม

"ABC || DEF ||" .split ("\\ |") ใช้งานไม่ได้จริง ๆ เพราะมันจะไม่สนใจค่าว่างสองค่าที่ตามมาซึ่งทำให้การแยกวิเคราะห์เป็นเรื่องตลกมากกว่าที่ควรจะเป็น
อาร์มันด์
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.