Trình quét so với StringTokenizer so với String.Split


155

Tôi mới tìm hiểu về lớp Scanner của Java và bây giờ tôi đang tự hỏi làm thế nào nó so sánh / cạnh tranh với StringTokenizer và String.Split. Tôi biết rằng StringTokenizer và String.Split chỉ hoạt động trên Chuỗi, vậy tại sao tôi muốn sử dụng Trình quét cho Chuỗi? Có phải Scanner chỉ nhằm mục đích mua sắm một lần để chia tách?

Câu trả lời:


240

Chúng chủ yếu là ngựa cho các khóa học.

  • Scannerđược thiết kế cho các trường hợp bạn cần phân tích một chuỗi, lấy ra các dữ liệu thuộc các loại khác nhau. Nó rất linh hoạt, nhưng có thể cho rằng không cung cấp cho bạn API đơn giản nhất chỉ đơn giản là nhận được một chuỗi các chuỗi được phân tách bằng một biểu thức cụ thể.
  • String.split()Pattern.split()cung cấp cho bạn một cú pháp dễ dàng để thực hiện cái sau, nhưng về cơ bản đó là tất cả những gì họ làm. Nếu bạn muốn phân tích chuỗi kết quả hoặc thay đổi dấu phân cách giữa chừng tùy thuộc vào một mã thông báo cụ thể, họ sẽ không giúp bạn điều đó.
  • StringTokenizerthậm chí còn hạn chế hơn String.split(), và cũng hơi khó sử dụng hơn. Nó chủ yếu được thiết kế để kéo ra các mã thông báo được phân định bởi các chuỗi cố định. Do hạn chế này, nó nhanh gấp đôi String.split(). (Xem phần so sánhString.split()StringTokenizer của tôi .) Nó cũng có trước API biểu thức chính quy, String.split()là một phần.

Bạn sẽ lưu ý từ thời gian của tôi String.split()vẫn có thể mã hóa hàng ngàn chuỗi trong vài mili giây trên một máy thông thường. Ngoài ra, nó có lợi thế hơn StringTokenizerlà nó cung cấp cho bạn đầu ra dưới dạng một chuỗi chuỗi, thường là những gì bạn muốn. Sử dụng một Enumeration, như được cung cấp bởi StringTokenizer, hầu hết thời gian là "quá phức tạp". Từ quan điểm này StringTokenizer, ngày nay hơi lãng phí không gian và bạn cũng có thể sử dụng String.split().


8
Cũng rất thú vị khi xem kết quả của Scanner trong cùng các bài kiểm tra bạn đã chạy trên String.Split và StringTokenizer.
Dave

2
Đã cho tôi một câu trả lời cho một câu hỏi khác: "tại sao việc sử dụng StringTokenizer không được khuyến khích, như đã nêu trong các ghi chú API Java?". Từ văn bản này, có vẻ như câu trả lời sẽ là "vì String.split () đủ nhanh".
Chân

1
Vậy bây giờ StringTokenizer có bị phản đối không?
Steve the Maker

Sử dụng cái gì thay vì nó? Máy quét?
Adrian

4
Tôi nhận ra đó là một câu trả lời cho một câu hỏi cũ, nhưng nếu tôi cần chia một luồng văn bản khổng lồ thành token, thì đó StringTokenizervẫn không phải là lựa chọn tốt nhất của tôi vì String.split()đơn giản là sẽ hết bộ nhớ?
Sergei Tachenov

57

Hãy bắt đầu bằng cách loại bỏ StringTokenizer . Nó đang già đi và thậm chí không hỗ trợ các biểu thức thông thường. Tài liệu của nó nêu:

StringTokenizerlà một lớp kế thừa được giữ lại vì lý do tương thích mặc dù việc sử dụng nó không được khuyến khích trong mã mới. Chúng tôi khuyên mọi người tìm kiếm chức năng này nên sử dụngsplit phương pháp Stringhoặc java.util.regexgói.

Vì vậy, hãy ném nó ra ngay lập tức. Những chiếc lá đósplit()Scanner. Sự khác biệt giữa chúng là gì?

Đối với một điều, split()chỉ cần trả về một mảng, giúp dễ dàng sử dụng vòng lặp foreach:

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

Scanner được xây dựng giống như một luồng:

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

hoặc là

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

(Nó có một API lớn , vì vậy đừng nghĩ rằng nó luôn bị hạn chế ở những thứ đơn giản như vậy.)

Giao diện kiểu luồng này có thể hữu ích để phân tích tệp văn bản đơn giản hoặc đầu vào bảng điều khiển, khi bạn không có (hoặc không thể nhận) tất cả đầu vào trước khi bắt đầu phân tích cú pháp.

Cá nhân, lần duy nhất tôi có thể nhớ sử dụng Scannerlà cho các dự án trường học, khi tôi phải lấy đầu vào của người dùng từ dòng lệnh. Nó làm cho loại hoạt động dễ dàng. Nhưng nếu tôi có một thứ Stringmà tôi muốn tách ra, thì nó gần như không có trí tuệ để đi cùng split().


20
StringTokenizer nhanh gấp 2 lần String.split (). Nếu bạn KHÔNG CẦN sử dụng các biểu thức thông thường, ĐỪNG!
Alex Worden

Tôi chỉ sử dụng Scannerđể phát hiện các ký tự dòng mới trong một cho trước String. Vì các ký tự dòng mới có thể thay đổi từ nền tảng này sang nền tảng khác (hãy xem Patternjavadoc!) chuỗi đầu vào KHÔNG được đảm bảo tuân thủ System.lineSeparator(), tôi thấy Scannerphù hợp hơn vì nó đã biết những ký tự dòng mới nào cần tìm khi gọi nextLine(). Vì String.splittôi sẽ phải cung cấp theo mẫu regex chính xác để phát hiện các dấu tách dòng mà tôi không tìm thấy được lưu trữ ở bất kỳ vị trí tiêu chuẩn nào (cách tốt nhất tôi có thể làm là sao chép nó từ nguồn của Scannerlớp).
ADTC

9

StringTokenizer luôn ở đó. Nó là nhanh nhất trong tất cả, nhưng thành ngữ giống như liệt kê có thể trông không thanh lịch như những người khác.

sự phân chia đã tồn tại trên JDK 1.4. Chậm hơn so với tokenizer nhưng dễ sử dụng hơn, vì nó có thể gọi được từ lớp String.

Máy quét đã xuất hiện trên JDK 1.5. Đây là phần mềm linh hoạt nhất và lấp đầy khoảng trống lâu dài trên API Java để hỗ trợ tương đương với họ hàm quét Cs nổi tiếng.


6

Nếu bạn có một đối tượng String mà bạn muốn token hóa, hãy ưu tiên sử dụng phương thức phân tách của String trên StringTokenizer. Nếu bạn đang phân tích dữ liệu văn bản từ một nguồn bên ngoài chương trình của bạn, như từ tệp hoặc từ người dùng, thì đó là nơi Máy quét có ích.


5
Chỉ cần như vậy, không biện minh, không có lý do?
jan.supol

6

Split chậm, nhưng không chậm như Scanner. StringTokenizer nhanh hơn tách. Tuy nhiên, tôi thấy rằng tôi có thể tăng gấp đôi tốc độ, bằng cách giao dịch linh hoạt, để tăng tốc độ, điều mà tôi đã làm tại JFastParser https://github.com/hughperkins/jfastparser

Thử nghiệm trên một chuỗi chứa một triệu nhân đôi:

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

Một số Javadoc sẽ rất tuyệt, và nếu bạn muốn phân tích thứ gì khác ngoài dữ liệu số thì sao?
NickJ

Vâng, nó được thiết kế cho tốc độ, không phải vẻ đẹp. Nó khá đơn giản, chỉ một vài dòng, vì vậy bạn có thể thêm một vài tùy chọn để phân tích cú pháp văn bản nếu bạn muốn.
Hugh Perkins

4

String.split dường như chậm hơn nhiều so với StringTokenizer. Lợi thế duy nhất với sự phân chia là bạn có được một loạt các mã thông báo. Ngoài ra, bạn có thể sử dụng bất kỳ biểu thức thông thường trong tách. org.apache.commons.lang.StringUtils có một phương thức phân tách hoạt động nhanh hơn nhiều so với bất kỳ hai viz nào. StringTokenizer hoặc String.split. Nhưng việc sử dụng CPU cho cả ba gần như nhau. Vì vậy, chúng ta cũng cần một phương pháp ít tốn CPU hơn mà tôi vẫn không thể tìm thấy.


3
Câu trả lời này hơi vô lý. Bạn nói rằng bạn đang tìm kiếm thứ gì đó nhanh hơn nhưng "ít CPU hơn". Bất kỳ chương trình nào được thực thi bởi CPU. Nếu một chương trình không sử dụng CPU của bạn 100%, thì nó phải chờ một thứ khác, như I / O. Điều đó không bao giờ là một vấn đề khi thảo luận về mã thông báo chuỗi, trừ khi bạn đang truy cập đĩa trực tiếp (điều đáng chú ý là chúng tôi không làm ở đây).
Jolta

4

Gần đây tôi đã thực hiện một số thử nghiệm về hiệu suất kém của String.split () trong các tình huống nhạy cảm hiệu năng cao. Bạn có thể thấy điều này hữu ích.

http://eblog. synconsystems.com/hidden-evils-of-javas-stringsplit-and-opesr

Ý chính là String.split () biên dịch một mẫu Biểu thức chính quy mỗi lần và do đó có thể làm chậm chương trình của bạn, so với nếu bạn sử dụng một đối tượng Mẫu được biên dịch trước và sử dụng trực tiếp để hoạt động trên Chuỗi.


4
Trên thực tế, String.split () không phải lúc nào cũng biên dịch mẫu. Nhìn vào nguồn nếu 1.7 java, bạn sẽ thấy có một kiểm tra nếu mẫu đó là một ký tự đơn và không phải là một ký tự thoát, nó sẽ phân tách chuỗi mà không cần regrec, vì vậy nó sẽ khá nhanh.
Krzysztof Krasnoyń

1

Đối với các kịch bản mặc định, tôi cũng sẽ đề xuất Pattern.split () nhưng nếu bạn cần hiệu suất tối đa (đặc biệt là trên Android, tất cả các giải pháp tôi đã kiểm tra đều khá chậm) và bạn chỉ cần phân tách bằng một char duy nhất, giờ đây tôi sử dụng phương pháp của riêng mình:

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

Sử dụng "abc" .toCharArray () để lấy mảng char cho Chuỗi. Ví dụ:

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

1

Một sự khác biệt quan trọng là cả String.split () và Scanner đều có thể tạo ra các chuỗi rỗng nhưng StringTokenizer không bao giờ làm điều đó.

Ví dụ:

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

Đầu ra:

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

Điều này là do dấu phân cách cho String.split () và Scanner.useD006iter () không chỉ là một chuỗi, mà là một biểu thức thông thường. Chúng ta có thể thay thế dấu phân cách "" bằng "+" trong ví dụ trên để làm cho chúng hoạt động giống như StringTokenizer.


-5

String.split () hoạt động rất tốt nhưng có ranh giới riêng, như nếu bạn muốn tách một chuỗi như dưới đây dựa trên biểu tượng ống đơn hoặc kép (|), nó không hoạt động. Trong tình huống này, bạn có thể sử dụng StringTokenizer.

ABC | IJK


12
Thực tế, bạn có thể chia ví dụ của mình chỉ bằng "ABC | IJK" .split ("\\ |");
Tomo

"ABC | | DEF ||" .split ("\\ |") không thực sự hoạt động mặc dù vì nó sẽ bỏ qua hai giá trị trống, khiến cho việc phân tích cú pháp trở nên phức tạp hơn mức cần thiết.
Armand
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.