Cách nhanh nhất để phân tách một chuỗi phân tách trong Java


10

Tôi đang xây dựng Bộ so sánh cung cấp khả năng sắp xếp nhiều cột trên Chuỗi phân cách. Tôi hiện đang sử dụng phương thức phân tách từ lớp String làm lựa chọn ưa thích của tôi để tách Chuỗi thô thành mã thông báo.

Đây có phải là cách thực hiện tốt nhất để chuyển đổi Chuỗi thô thành mảng Chuỗi? Tôi sẽ sắp xếp hàng triệu hàng để tôi nghĩ cách tiếp cận là vấn đề.

Nó dường như chạy tốt và rất dễ dàng, nhưng không chắc chắn nếu có cách nhanh hơn trong java.

Đây là cách sắp xếp hoạt động trong Bộ so sánh của tôi:

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

Sau khi đánh giá các cách tiếp cận khác nhau, tin hay không, phương pháp phân tách là cách nhanh nhất bằng cách sử dụng phiên bản java mới nhất. Bạn có thể tải xuống bộ so sánh đã hoàn thành của tôi tại đây: https://sourceforge.net/projects/multicolumnrowcomparator/


5
Tôi sẽ chỉ ra rằng bản chất của câu trả lời cho câu hỏi này phụ thuộc vào việc thực hiện jvm. Hành vi của các chuỗi (chia sẻ một mảng sao lưu chung trong OpenJDK, nhưng không phải trong OracleJDK) khác nhau. Sự khác biệt này có thể có tác động đáng kể đến việc tách chuỗi và tạo chuỗi con, cùng với việc thu gom rác và rò rỉ bộ nhớ. Làm thế nào lớn là những mảng? Làm thế nào bạn đang làm điều đó bây giờ? Bạn có xem xét một câu trả lời tạo ra một kiểu Stringish mới thay vì các chuỗi Java thực tế không?

1
Cụ thể, hãy xem StringTokenizer nextToken mà cuối cùng gọi gói xây dựng Chuỗi riêng . So sánh điều này với các thay đổi được ghi lại trong Thay đổi đối với biểu diễn bên trong Chuỗi được thực hiện trong Java 1.7.0_06

Kích thước mảng phụ thuộc vào số lượng cột nên nó có thể thay đổi. Bộ so sánh nhiều cột này được truyền dưới dạng tham số như vậy: ExternalSort.mergeSorticFiles (fileList, new File ("BigFile.csv"), _comparator, Charset.defaultCharset (), false); Thói quen sắp xếp bên ngoài sẽ sắp xếp toàn bộ chuỗi hàng, nó thực sự là bộ so sánh thực hiện phân tách và sắp xếp dựa trên các cột sắp xếp
Constantin

Tôi sẽ xem xét việc xem xét các mã thông báo của lucene. Lucene có thể được sử dụng như một thư viện phân tích văn bản mạnh mẽ, hoạt động tốt cho cả các nhiệm vụ đơn giản và phức tạp
Doug T.

Hãy xem xét Apache Commons Lang StringUtils.split[PreserveAllTokens](text, delimiter).
Phục hồi

Câu trả lời:


19

Tôi đã viết một bài kiểm tra điểm chuẩn nhanh và bẩn cho việc này. Nó so sánh 7 phương pháp khác nhau, một số phương pháp đòi hỏi kiến ​​thức cụ thể về dữ liệu được phân chia.

Đối với phân tách mục đích chung cơ bản, Guava Splitter nhanh hơn 3,5 lần so với Chuỗi # split () và tôi khuyên bạn nên sử dụng điều đó. Stringtokenizer nhanh hơn một chút và chia tách bản thân với indexOf nhanh gấp đôi so với lần nữa.

Để biết mã và biết thêm thông tin, hãy xem http://demeranville.com/battle-of-the-tokenators-delrict-text-parser-performance/


Tôi chỉ tò mò về JDK mà bạn đang sử dụng ... và nếu là 1.6, tôi sẽ thích thú nhất khi xem bản tóm tắt kết quả của bạn trong 1.7.

1
Tôi nghĩ là 1.6. Mã ở đó dưới dạng thử nghiệm JUnit nếu bạn muốn chạy nó trong 1.7. Lưu ý String.split thực hiện khớp regex, điều này sẽ luôn chậm hơn so với việc tách trên một ký tự được xác định.
tom

1
Tuy nhiên, đối với 1.6, mã StringTokenizer (và tương tự) gọi String.sub chuỗi () thực hiện tạo O (1) của chuỗi mới bằng cách sử dụng cùng một mảng sao lưu. Điều này đã được thay đổi trong 1.7 để tạo một bản sao của phần cần thiết của mảng sao lưu thay cho O (n). Điều này có thể có tác động đơn lẻ trong kết quả của bạn làm cho sự khác biệt giữa phần tách và StringTokenizer giảm đi (làm chậm mọi thứ đã sử dụng chuỗi con trước đó).

1
Chắc chắn đúng. Vấn đề là cách StringTokenizer hoạt động đã đi từ "để tạo một chuỗi mới gán 3 số nguyên" thành "để tạo một chuỗi mới, thực hiện một bản sao dữ liệu" sẽ thay đổi tốc độ của phần đó. Sự khác biệt giữa các cách tiếp cận khác nhau có thể ít hơn bây giờ và sẽ rất thú vị (nếu không vì lý do nào khác ngoài sự thú vị của nó) để thực hiện theo dõi với Java 1.7.

1
Cảm ơn bài viết đó! Rất hữu ích và sẽ sử dụng để điểm chuẩn các phương pháp khác nhau.
Constantin

5

Như @Tom viết, một cách tiếp cận kiểu indexOf nhanh hơn String.split(), vì cách tiếp cận này xử lý các biểu thức chính quy và có rất nhiều chi phí phụ cho chúng.

Tuy nhiên, một thay đổi thuật toán có thể giúp bạn tăng tốc siêu tốc. Giả sử rằng Bộ so sánh này sẽ được sử dụng để sắp xếp ~ 100.000 Chuỗi của bạn, đừng viết Comparator<String>. Bởi vì, trong quá trình sắp xếp của bạn, cùng một Chuỗi có thể sẽ được so sánh nhiều lần, do đó bạn sẽ chia nó nhiều lần, v.v ...

Tách tất cả các Chuỗi một lần thành Chuỗi [] s và Comparator<String[]>sắp xếp Chuỗi []. Sau đó, cuối cùng, bạn có thể kết hợp tất cả chúng lại với nhau.

Ngoài ra, bạn cũng có thể sử dụng Bản đồ để lưu trữ Chuỗi -> Chuỗi [] hoặc ngược lại. ví dụ (sơ sài) Cũng lưu ý, bạn đang giao dịch bộ nhớ cho tốc độ, hy vọng bạn có rất nhiều RAM

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
}

đây là một quan điểm tốt.
tom

Nó sẽ yêu cầu sửa đổi mã Sắp xếp bên ngoài có thể tìm thấy ở đây: code.google.com/p/externalsortinginjava
Constantin

1
Có lẽ dễ nhất để sử dụng Bản đồ. Xem chỉnh sửa.
dùng949300

Cho rằng đây là một phần của công cụ sắp xếp bên ngoài (để xử lý nhiều dữ liệu hơn mức có thể phù hợp với bộ nhớ khả dụng), tôi đã thực sự theo đuổi một "bộ tách" hiệu quả (vâng, thật lãng phí khi phải tách chuỗi liên tục, do đó tôi ban đầu cần phải làm điều này càng nhanh càng tốt)
Constantin

Duyệt nhanh mã InternalSort, có vẻ như nếu bạn xóa bộ nhớ cache ở cuối (hoặc bắt đầu) của mỗi sortAndSave()cuộc gọi thì bạn không nên hết bộ nhớ do bộ nhớ cache lớn. IMO, mã nên có thêm một vài móc nối như bắn các sự kiện hoặc gọi các phương thức được bảo vệ không làm gì mà người dùng như bạn có thể ghi đè. (Ngoài ra, nó không phải là tất cả các phương thức tĩnh để chúng có thể thực hiện việc này) Bạn có thể muốn liên hệ với các tác giả và gửi yêu cầu.
user949300

2

Theo điểm chuẩn này , StringTokenizer nhanh hơn để chia chuỗi nhưng nó không trả về một mảng khiến cho nó không thuận tiện.

Nếu bạn cần sắp xếp hàng triệu hàng tôi khuyên bạn nên sử dụng RDBMS.


3
Đó là dưới JDK 1.6 - mọi thứ trong chuỗi khác nhau về cơ bản trong 1.7 - xem java-performance.info/changes-to-opes-java-1-7-0_06 (đặc biệt, tạo một chuỗi con không còn là O (1) nữa mà là đúng hơn là O (n)). Liên kết lưu ý rằng trong 1.6 Pattern.split đã sử dụng việc tạo Chuỗi khác với String.subopes ()) - xem mã được liên kết trong nhận xét ở trên để theo StringTokenizer.nextToken () và trình xây dựng riêng của gói mà nó có quyền truy cập.

1

Đây là phương pháp tôi sử dụng để phân tích các tệp được phân định bằng tab lớn (1GB +). Nó có ít chi phí hơn rất nhiều so với String.split(), nhưng được giới hạn charnhư là một dấu phân cách. Nếu bất cứ ai có một phương pháp nhanh hơn, tôi muốn xem nó. Điều này cũng có thể được thực hiện trên CharSequenceCharSequence.subSequence, nhưng điều đó đòi hỏi phải thực hiện CharSequence.indexOf(char)(tham khảo phương pháp gói String.indexOf(char[] source, int sourceOffset, int sourceCount, char[] target, int targetOffset, int targetCount, int fromIndex)nếu quan tâm).

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

Bạn đã điểm chuẩn này so với String.split () chưa? Nếu vậy, làm thế nào để so sánh?
Jay Elston

@JayElston Trên một tệp 900 MB, nó đã giảm thời gian phân tách từ 7,7 giây xuống còn 6,2 giây, do đó nhanh hơn khoảng 20%. Nó vẫn là phần chậm nhất trong phân tích cú pháp ma trận dấu phẩy động của tôi. Tôi đoán rằng phần lớn thời gian còn lại là phân bổ mảng. Có thể cắt bỏ phân bổ ma trận bằng cách sử dụng cách tiếp cận dựa trên mã thông báo với phần bù trong phương thức - điều đó sẽ bắt đầu giống với phương thức tôi đã trích dẫn bên trên mã.
vallismortis
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.