วิธีที่เร็วที่สุดในการแยกสตริงแบบมีตัวคั่นใน Java


10

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

นี่เป็นวิธีที่มีประสิทธิภาพดีที่สุดในการแปลงสตริงดิบเป็นอาร์เรย์สตริงหรือไม่ ฉันจะเรียงลำดับแถวนับล้านดังนั้นฉันคิดว่าวิธีการที่สำคัญ

ดูเหมือนว่าจะทำงานได้ดีและง่ายมาก แต่ไม่แน่ใจว่ามีวิธีที่เร็วกว่าใน java

นี่คือวิธีการเรียงลำดับใน Comparator ของฉัน:

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

หลังจากทำการเปรียบเทียบวิธีการต่าง ๆ เชื่อหรือไม่วิธีการแยกเป็นวิธีที่เร็วที่สุดโดยใช้จาวารุ่นล่าสุด คุณสามารถดาวน์โหลดเครื่องมือเปรียบเทียบที่สมบูรณ์ได้ที่นี่: https://sourceforge.net/projects/multicolumnrowcomparator/


5
ฉันจะชี้ให้เห็นว่าธรรมชาติของคำตอบสำหรับคำถามนี้ขึ้นอยู่กับการดำเนินการของ jvm พฤติกรรมของสตริง (การแชร์อาร์เรย์สำรองทั่วไปใน OpenJDK แต่ไม่ใช่ใน OracleJDK) จะแตกต่างกันไป ความแตกต่างนี้อาจส่งผลกระทบอย่างมีนัยสำคัญต่อการแยกสตริงและการสร้างสตริงย่อยพร้อมกับการรวบรวมขยะและการรั่วไหลของหน่วยความจำ อาร์เรย์เหล่านี้มีขนาดใหญ่เท่าใด ตอนนี้คุณเป็นยังไงบ้าง คุณจะพิจารณาคำตอบที่ทำให้เป็นประเภท Stringish ใหม่แทนที่จะเป็น Java Strings จริงหรือไม่?

1
ในรูปลักษณ์โดยเฉพาะอย่างยิ่งที่StringTokenizer nextTokenซึ่งท้ายที่สุดก็เรียกแพคเกจคอนสตรัค String เอกชน เปรียบเทียบสิ่งนี้กับการเปลี่ยนแปลงที่บันทึกไว้ในการเปลี่ยนแปลงการแสดงสตริงภายในที่ทำใน Java 1.7.0_06

ขนาดอาร์เรย์ขึ้นอยู่กับจำนวนคอลัมน์ดังนั้นจึงเป็นตัวแปร ตัวเปรียบเทียบหลายคอลัมน์นี้ถูกส่งผ่านเป็นพารามิเตอร์ดังนี้: ExternalSort.mergeSortedFiles (fileList, ไฟล์ใหม่ ("BigFile.csv"), _comparator, Charset.defaultCharset (), เท็จ); ชุดคำสั่งการเรียงลำดับภายนอกจะเรียงลำดับสตริงแถวทั้งหมดเป็นจริงแล้วตัวเปรียบเทียบที่แยกและเรียงลำดับตามคอลัมน์เรียงลำดับ
Constantin

ฉันจะพิจารณาดูโทเค็นของลูซีน Lucene สามารถใช้เป็นเพียงคลังการวิเคราะห์ข้อความที่มีประสิทธิภาพซึ่งทำงานได้ดีสำหรับทั้งงานที่เรียบง่ายและซับซ้อน
Doug T.

พิจารณา Apache StringUtils.split[PreserveAllTokens](text, delimiter)คอมมอนส์แลง
Reinstate Monica

คำตอบ:


19

ฉันได้เขียนแบบทดสอบเกณฑ์มาตรฐานที่รวดเร็วและสกปรกสำหรับสิ่งนี้ มันเปรียบเทียบ 7 วิธีที่แตกต่างกันซึ่งบางอย่างต้องการความรู้เฉพาะของข้อมูลที่ถูกแบ่ง

สำหรับการแยกวัตถุประสงค์ทั่วไปขั้นพื้นฐาน Guava Splitter นั้นเร็วกว่าการแยกสตริง # 3.5 () และฉันขอแนะนำให้ใช้ Stringtokenizer เร็วกว่านั้นเล็กน้อยและแยกตัวเองด้วย indexOf เร็วขึ้นเป็นสองเท่า

สำหรับรหัสและข้อมูลเพิ่มเติมดูhttp://demeranville.com/battle-of-the-tokenizers-delimited-text-parser-performance/


ฉันแค่อยากรู้ว่าคุณใช้ JDK อะไร ... และถ้าเป็น 1.6 ฉันจะสนใจดูผลลัพธ์ของคุณใน 1.7

1
เป็น 1.6 ฉันคิดว่า รหัสจะมีเป็นการทดสอบ JUnit ถ้าคุณต้องการเรียกใช้ใน 1.7 หมายเหตุ String.split ทำการจับคู่ regex ซึ่งจะช้ากว่าการแยกอักขระที่กำหนดไว้เสมอ
Tom

1
อย่างไรก็ตามสำหรับ 1.6 รหัส StringTokenizer (และคล้ายกัน) เรียก String.substring () ที่สร้าง O (1) ของสตริงใหม่โดยใช้อาร์เรย์สำรองเดียวกัน สิ่งนี้ถูกเปลี่ยนใน 1.7 เพื่อทำสำเนาส่วนที่จำเป็นของอาร์เรย์สำรองแทนที่จะเป็น O (n) สิ่งนี้อาจมีผลกระทบที่แปลกประหลาดในผลลัพธ์ของคุณซึ่งจะทำให้ความแตกต่างระหว่างตัวแยกและ StringTokenizer น้อยลง (ทำให้ทุกอย่างที่ใช้ซับสตริงก่อนหน้าช้าลง)

1
เป็นเรื่องจริงแน่นอน สิ่งที่เป็นวิธีการทำงานของ StringTokenizer ได้หายไปจาก "เพื่อสร้างสตริงใหม่กำหนดจำนวนเต็ม 3 จำนวน" เพื่อ "เพื่อสร้างสตริงใหม่ทำสำเนาอาร์เรย์ของข้อมูล" ซึ่งจะเปลี่ยนวิธีส่วนที่รวดเร็วคือ ความแตกต่างระหว่างวิธีการต่าง ๆ อาจน้อยลงในขณะนี้และมันจะน่าสนใจ (ถ้าไม่มีเหตุผลอื่นที่น่าสนใจ) เพื่อติดตามด้วย Java 1.7

1
ขอบคุณสำหรับบทความนี้! มีประโยชน์มากและจะใช้เป็นเกณฑ์มาตรฐานสำหรับวิธีการต่างๆ
Constantin

5

ดังที่ @Tom เขียนวิธีการชนิด indexOf นั้นเร็วกว่าString.split()เนื่องจากหลังนั้นเกี่ยวข้องกับนิพจน์ทั่วไปและมีค่าใช้จ่ายเพิ่มเติมเป็นจำนวนมากสำหรับพวกเขา

อย่างไรก็ตามการเปลี่ยนแปลงอัลกอริทึมหนึ่งที่อาจทำให้คุณมีความเร็วมากขึ้น สมมติว่าเปรียบเทียบนี้จะถูกนำมาใช้ในการจัดเรียง ~ 100,000 Strings Comparator<String>ของคุณไม่ได้เขียน เพราะในการเรียงลำดับของคุณสตริงที่เหมือนกันอาจจะถูกเปรียบเทียบหลายครั้งดังนั้นคุณจะแยกมันหลายครั้ง ฯลฯ

แยกสตริงทั้งหมดหนึ่งครั้งเป็นสตริง [] s และComparator<String[]>เรียงลำดับสตริง [] จากนั้นในตอนท้ายคุณสามารถรวมทั้งหมดเข้าด้วยกัน

หรือคุณสามารถใช้แผนที่เพื่อแคชสตริง -> สตริง [] หรือในทางกลับกัน เช่น (ร่าง) นอกจากนี้ยังทราบว่าคุณกำลังซื้อขายหน่วยความจำสำหรับความเร็วหวังว่าคุณจะมี 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
}

นี่เป็นจุดที่ดี
ทอม

มันจะต้องมีการแก้ไขรหัส External Sort ซึ่งสามารถพบได้ที่นี่: code.google.com/p/externalsortinginjava
Constantin

1
น่าจะง่ายที่สุดในการใช้แผนที่แล้ว ดูการแก้ไข
user949300

ระบุว่านี่เป็นส่วนหนึ่งของเอ็นจิ้นการจัดเรียงภายนอก (เพื่อจัดการกับข้อมูลที่มากเกินกว่าจะเก็บไว้ในหน่วยความจำที่มีอยู่) ได้ฉันจึงทำการ "แยก" อย่างมีประสิทธิภาพ (ใช่มันเป็นการสิ้นเปลืองที่จะแยกสตริงเดียวกันซ้ำ ๆ ต้องทำสิ่งนี้ให้เร็วที่สุดเท่าที่จะทำได้)
Constantin

เรียกดูรหัส ExternalSort สั้น ๆ ดูเหมือนว่าหากคุณล้างแคชในตอนท้าย (หรือเริ่ม) ทุกการsortAndSave()โทรคุณไม่ควรเรียกใช้หน่วยความจำหมดเนื่องจากมีแคชมาก IMO โค้ดควรมี hooks พิเศษสองสามอย่างเช่นการเผาเหตุการณ์หรือการเรียกใช้วิธีการที่ไม่มีการป้องกันที่ผู้ใช้เช่นคุณสามารถแทนที่ (นอกจากนี้ไม่ควรเป็นวิธีคงที่ทั้งหมดเพื่อให้สามารถทำได้ ) คุณอาจต้องการติดต่อผู้เขียนและยื่นคำขอ
user949300

2

ตามมาตรฐานนี้ StringTokenizer จะเร็วกว่าสำหรับการแยกสตริง แต่มันจะไม่ส่งกลับอาร์เรย์ซึ่งทำให้สะดวกน้อยลง

หากคุณต้องการเรียงลำดับแถวเป็นล้าน ๆ แถวฉันขอแนะนำให้ใช้ RDBMS


3
นั่นคือภายใต้ JDK 1.6 - สิ่งต่าง ๆ ในสายอักขระนั้นแตกต่างกันโดยพื้นฐานใน 1.7 - ดูjava-performance.info/changes-to-string-java-1-7-0_06 (โดยเฉพาะการสร้าง substring ไม่ใช่ O (1) อีกต่อไป แต่ ค่อนข้าง O (n) ลิงค์ตั้งข้อสังเกตว่าใน 1.6 Pattern.split ใช้การสร้างสตริงที่แตกต่างจาก String.substring ()) - ดูรหัสที่ลิงค์ในความคิดเห็นด้านบนเพื่อติดตาม StringTokenizer.nextToken () และคอนสตรัคเตอร์ส่วนตัวของแพคเกจที่มีการเข้าถึง

1

นี่เป็นวิธีที่ฉันใช้ในการแยกวิเคราะห์ไฟล์ที่มีตัวคั่นขนาดใหญ่ (1GB +) มันมีค่าใช้จ่ายน้อยกว่าString.split()มาก แต่ถูก จำกัด ให้charเป็นตัวคั่น หากใครมีวิธีที่เร็วกว่านี้ฉันอยากจะเห็นมัน สิ่งนี้สามารถทำได้CharSequenceและCharSequence.subSequenceต้องดำเนินการCharSequence.indexOf(char)(อ้างถึงวิธีการแพคเกจString.indexOf(char[] source, int sourceOffset, int sourceCount, char[] target, int targetOffset, int targetCount, int fromIndex)ถ้าสนใจ)

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

คุณเคยเปรียบเทียบกับ vs String.split () นี้หรือยัง ถ้าเป็นเช่นนั้นจะเปรียบเทียบได้อย่างไร
Jay Elston

@JayElston ในไฟล์ 900MB มันลดเวลาแบ่งจาก 7.7 วินาทีเหลือ 6.2 วินาทีดังนั้นเร็วขึ้นประมาณ 20% มันยังคงเป็นส่วนที่ช้าที่สุดของการแยกเมทริกซ์ทศนิยมของฉัน ฉันเดาว่าเวลาที่เหลืออยู่ส่วนใหญ่คือการจัดสรรอาเรย์ อาจเป็นไปได้ที่จะตัดการจัดสรรเมทริกซ์โดยใช้วิธีที่ใช้ tokenizer กับอ็อฟเซ็ตในวิธีการ - ซึ่งจะเริ่มมีลักษณะคล้ายกับวิธีที่ฉันอ้างถึงรหัสข้างต้น
vallismortis
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.