जावा में एक सीमांकित स्ट्रिंग को विभाजित करने का सबसे तेज़ तरीका


10

मैं एक तुलनित्र का निर्माण कर रहा हूं जो एक सीमांकित स्ट्रिंग पर बहु-स्तंभ प्रकार की क्षमता प्रदान करता है। मैं वर्तमान में स्ट्रिंग वर्ग से विभाजित विधि का उपयोग कर रहा हूं क्योंकि कच्चे स्ट्रिंग को टोकन में विभाजित करने के लिए मेरी पसंदीदा पसंद है।

कच्चे स्ट्रिंग को एक स्ट्रिंग सरणी में बदलने के लिए यह सबसे अच्छा प्रदर्शन तरीका है? मैं लाखों पंक्तियों की छँटाई करूँगा ताकि मुझे लगता है कि दृष्टिकोण मायने रखता है।

यह ठीक लगता है और बहुत आसान है, लेकिन अनिश्चित है अगर जावा में तेज़ तरीका है।

यहाँ है कि कैसे मेरे तुलनित्र में काम करता है:

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 में नहीं) अलग है। यह अंतर बंटवारे के तारों और सब्सट्रिंग के निर्माण के साथ-साथ कचरा संग्रह और मेमोरी लीक पर महत्वपूर्ण प्रभाव डाल सकता है। ये सरणियाँ कितनी बड़ी हैं? अब आप इसे कैसे कर रहे हैं? क्या आप एक उत्तर पर विचार करेंगे जो वास्तविक जावा स्ट्रिंग्स के बजाय एक नए स्ट्रिंग प्रकार के लिए बनाता है?


सरणी का आकार स्तंभों की संख्या पर निर्भर करता है इसलिए यह परिवर्तनशील है। इस बहु-स्तंभ तुलनित्र को एक पैरामीटर के रूप में इस तरह पारित किया जाता है: ExternalSort.mergeSortedFiles (fileList, new File ("BigFile.csv"), _comparator, Charset.defaultCharset (), false); बाहरी सॉर्ट रूटीन पूरी पंक्ति स्ट्रिंग को सॉर्ट करेगा, यह वास्तव में तुलनित्र है जो सॉर्ट कॉलम के आधार पर विभाजन और छंटाई करता है
कॉन्स्टेंटिन

मैं ल्यूकेन के टोकनधारकों को देखकर विचार करूंगा। ल्यूसिन का उपयोग केवल एक शक्तिशाली पाठ विश्लेषण पुस्तकालय के रूप में किया जा सकता है जो सरल और जटिल दोनों कार्यों के लिए अच्छा प्रदर्शन करता है
डग टी।

अपाचे कॉमन्स लैंग का विचार करें StringUtils.split[PreserveAllTokens](text, delimiter)
मोनिका

जवाबों:


19

मैंने इसके लिए एक त्वरित और गंदा बेंचमार्क टेस्ट लिखा है। यह 7 विभिन्न तरीकों की तुलना करता है, जिनमें से कुछ को डेटा के विशिष्ट ज्ञान के विभाजन की आवश्यकता होती है।

बुनियादी सामान्य प्रयोजन के विभाजन के लिए, अमरूद फाड़नेवाला स्ट्रिंग # विभाजन () की तुलना में 3.5 गुना तेज है और मैं इसका उपयोग करने की सलाह दूंगा। स्ट्रिंगटोकाइनाइजर इससे थोड़ा तेज है और खुद को इंडेक्सऑफ के साथ विभाजित करना फिर से दोगुना तेज है।

कोड और अधिक जानकारी के लिए देखें http://demeranville.com/battle-of-the-tokenizers-delimited-text-parser-performance/


मैं उत्सुक हूं कि आप क्या JDK का उपयोग कर रहे थे ... और अगर यह 1.6 था, तो मुझे 1.7 में आपके परिणामों की पुनरावृत्ति देखने में सबसे ज्यादा दिलचस्पी होगी।

1
मुझे लगता है कि यह 1.6 था। यदि आप इसे 1.7 में चलाना चाहते हैं तो कोड ज्यूनिट परीक्षा के रूप में है। नोट String.split रेगेक्स मिलान करता है, जो हमेशा एकल परिभाषित वर्ण पर विभाजित होने की तुलना में धीमा होने वाला है।
टॉम

1
हां, हालांकि 1.6 के लिए, StringTokenizer (और समान) कोड String.substring () है कि एक ही बैकिंग सरणी का उपयोग करके नए स्ट्रिंग का O (1) निर्माण करता है। O (n) के बजाय बैकिंग सरणी के आवश्यक भाग की एक प्रतिलिपि बनाने के लिए इसे 1.7 में बदल दिया गया था। यह आपके परिणामों में एक एकल प्रभाव डाल सकता है जो विभाजन और स्ट्रिंगरोकेंनाइज़र के बीच अंतर को कम कर देता है (सब कुछ धीमा कर देता है जो पहले प्रतिस्थापन था)।

1
सही मे सच है। बात यह है कि StringTokenizer काम करता है "एक नया स्ट्रिंग बनाने के लिए" एक नया स्ट्रिंग असाइनमेंट 3 पूर्णांक बनाने के लिए "से गया है, डेटा की एक सरणी कॉपी करें" जो कि उस हिस्से को कितनी तेजी से बदल देगा। " विभिन्न दृष्टिकोणों के बीच अंतर अब कम हो सकता है और यह जावा 1.7 के लिए एक फॉलोअप करने के लिए दिलचस्प होगा (यदि इसके दिलचस्प के अलावा और कोई कारण नहीं है)।

1
उस लेख के लिए धन्यवाद! बहुत उपयोगी है और विभिन्न दृष्टिकोणों को बेंचमार्क करने के लिए उपयोग करेगा।
कांस्टेंटिन

5

जैसा कि @Tom लिखते हैं, एक indexOf प्रकार का दृष्टिकोण तेजी से होता है String.split(), क्योंकि बाद वाले नियमित अभिव्यक्तियों के साथ व्यवहार करते हैं और उनके लिए बहुत अधिक अतिरिक्त ओवरहेड होते हैं।

हालाँकि, एक एल्गोरिथ्म परिवर्तन जो आपको एक सुपर स्पीडअप दे सकता है। यह मानते हुए कि यह कंपैरिलेटर आपके ~ 100,000 स्ट्रिंग्स को सॉर्ट करने के लिए उपयोग किया जा रहा है, लिखिए नहीं Comparator<String>। क्योंकि, अपने प्रकार के पाठ्यक्रम में, एक ही स्ट्रिंग की संभावना कई बार होगी, इसलिए आप इसे कई बार विभाजित करेंगे , आदि ...

स्ट्रिंग [] s में एक बार सभी स्ट्रिंग्स को विभाजित करें , और एक Comparator<String[]>प्रकार का स्ट्रिंग [] है। फिर, अंत में, आप उन सभी को एक साथ जोड़ सकते हैं।

वैकल्पिक रूप से, आप स्ट्रिंग -> स्ट्रिंग [] या इसके विपरीत को कैश करने के लिए मैप का उपयोग कर सकते हैं। उदाहरण के लिए (स्केच) भी ध्यान दें, आप गति के लिए मेमोरी का व्यापार कर रहे हैं, आशा है कि आपके पास लोटा रैम है

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
}

यह लाभप्रद है।
टॉम

इसके लिए बाहरी सॉर्ट कोड को संशोधित करना होगा, जो यहां पाया जा सकता है: code.google.com/p/externalsortinginjava
कॉन्स्टैंटिन

1
मैप का उपयोग करने के लिए संभवतः सबसे आसान है। संपादित देखें।
user949300

यह देखते हुए कि यह एक बाहरी सॉर्ट इंजन का हिस्सा है (उपलब्ध स्मृति में फिट होने की तुलना में कहीं अधिक डेटा से निपटने के लिए), मैं वास्तव में एक कुशल "स्प्लिटर" के बाद जा रहा था (हाँ, यह एक ही स्ट्रिंग को बार-बार विभाजित करने के लिए बेकार है, इसलिए मेरा मूल रूप से इसे जितनी जल्दी हो सके करने की आवश्यकता है)
कॉन्स्टेंटिन

बाह्य रूप से बाहरी कोड को ब्राउज़ करते हुए, ऐसा लगता है कि यदि आपने हर sortAndSave()कॉल के अंत में अपना कैश साफ़ किया (या शुरू) तो आपको एक विशाल कैश के कारण मेमोरी से बाहर नहीं भागना चाहिए। IMO, कोड में कुछ अतिरिक्त हुक होना चाहिए जैसे कि फायरिंग इवेंट या कॉलिंग-कुछ भी नहीं संरक्षित तरीके जो आपके जैसे उपयोगकर्ता ओवरराइड कर सकते हैं। (इसके अलावा, यह सभी स्थिर तरीके नहीं होने चाहिए ताकि वे ऐसा कर सकें ) आप लेखकों से संपर्क कर सकते हैं और अनुरोध दर्ज कर सकते हैं।
user949300

2

इस मानदंड के अनुसार , स्ट्रिंग को विभाजित करने के लिए StringTokenizer तेज है लेकिन यह एक सरणी वापस नहीं करता है जो इसे कम सुविधाजनक बनाता है।

यदि आपको लाखों पंक्तियों को क्रमबद्ध करने की आवश्यकता है तो मैं आरडीबीएमएस का उपयोग करने की सलाह दूंगा।


3
यह JDK 1.6 के तहत था - स्ट्रिंग्स में चीजें 1.7 में मौलिक रूप से भिन्न होती हैं - java-performance.info/changes-to-string-java-1-7-0_06 देखें (विशेष रूप से, सबस्ट्रिंग बनाना O (1) अब नहीं है लेकिन बल्कि O (n)) है। लिंक नोट करता है कि 1.6 Pattern.split में String.substring ()) की तुलना में अलग-अलग String बनाने का उपयोग किया गया है - 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;
}

क्या आपने इसे बनाम String.split () बेंचमार्क किया है? यदि हां, तो यह तुलना कैसे करता है?
जे एलस्टन

@JayElston 900MB फाइल पर, इसने स्प्लिट टाइम को 7.7 सेकंड से घटाकर 6.2 सेकंड कर दिया, इसलिए यह लगभग 20% तेज है। यह अभी भी मेरे फ्लोटिंग-पॉइंट मैट्रिक्स पार्सिंग का सबसे धीमा हिस्सा है। मैं अनुमान लगा रहा हूं कि शेष समय का अधिकांश हिस्सा आबंटन है। विधि में ऑफसेट के साथ एक टोकन-आधारित दृष्टिकोण का उपयोग करके मैट्रिक्स आवंटन में कटौती करना संभव हो सकता है - जो कोड के ऊपर मेरे द्वारा उद्धृत विधि की तरह अधिक दिखना शुरू कर देगा।
vallismortis
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.