यह देखते हुए कि स्ट्रिंग्स .NET में अपरिवर्तनीय हैं, मैं सोच रहा हूं कि उन्हें ऐसे क्यों डिज़ाइन किया गया है जो इसके बजाय string.Substring()
O ( substring.Length
) समय लेता हैO(1)
?
यानी ट्रेडऑफ क्या थे, यदि कोई हो
यह देखते हुए कि स्ट्रिंग्स .NET में अपरिवर्तनीय हैं, मैं सोच रहा हूं कि उन्हें ऐसे क्यों डिज़ाइन किया गया है जो इसके बजाय string.Substring()
O ( substring.Length
) समय लेता हैO(1)
?
यानी ट्रेडऑफ क्या थे, यदि कोई हो
जवाबों:
अद्यतन: मुझे यह सवाल बहुत पसंद आया, मैंने इसे अभी-अभी ब्लॉग किया है। स्ट्रिंग्स, अपरिवर्तनशीलता और दृढ़ता देखें
संक्षिप्त उत्तर है: O (n) O (1) है यदि n बड़ा नहीं होता है। अधिकांश लोग छोटे तारों से छोटे पदार्थों को निकालते हैं, इसलिए जटिलता कैसे बढ़ती है यह पूरी तरह से अप्रासंगिक है ।
लंबा उत्तर है:
एक अपरिवर्तनीय डेटा संरचना इस तरह से निर्मित होती है कि उदाहरण पर परिचालन केवल एक छोटी राशि (आमतौर पर O (1) या O (lg n)) की प्रतिलिपि के साथ मूल की मेमोरी के पुनः उपयोग की अनुमति देता है या नए आवंटन को "लगातार" कहा जाता है अपरिवर्तनीय डेटा संरचना। .NET में स्ट्रिंग्स अपरिवर्तनीय हैं; आपका प्रश्न अनिवार्य रूप से "वे लगातार क्यों नहीं हैं"?
क्योंकि जब आप उन ऑपरेशनों को देखते हैं जो आम तौर पर .NET प्रोग्राम्स में स्ट्रिंग्स पर किए जाते हैं , तो यह हर प्रासंगिक तरीके से होता है, जो कि पूरी तरह से नया स्ट्रिंग बनाने के लिए बिल्कुल भी बदतर नहीं होता है ।एक जटिल सतत डेटा संरचना के निर्माण की लागत और कठिनाई खुद के लिए भुगतान नहीं करती है।
लोग आमतौर पर "स्ट्रिंग" का उपयोग एक छोटी स्ट्रिंग निकालने के लिए करते हैं - कहते हैं, दस या बीस अक्षर - कुछ हद तक स्ट्रिंग से - शायद कुछ सौ वर्ण। आपके पास अल्पविराम-अलग फ़ाइल में पाठ की एक पंक्ति है और आप तीसरे फ़ील्ड को निकालना चाहते हैं, जो एक अंतिम नाम है। लाइन शायद एक दो सौ वर्णों वाली होगी, नाम एक दर्जन होगा। स्ट्रिंग आवंटन और मेमोरी की पचास बाइट्स की नकल आधुनिक हार्डवेयर पर आश्चर्यजनक रूप से तेज है । यह एक नया डेटा संरचना बनाता है जिसमें एक मौजूदा स्ट्रिंग के बीच में एक पॉइंटर होता है और साथ ही एक लंबाई भी आश्चर्यजनक रूप से तेजी से अप्रासंगिक होती है; "फास्ट पर्याप्त" काफी तेजी से परिभाषा है।
निकाले गए पदार्थ आम तौर पर आकार में छोटे और जीवनकाल में छोटे होते हैं; कचरा संग्रहकर्ता जल्द ही उन्हें पुनः प्राप्त करने जा रहा है, और उन्होंने पहले स्थान पर ढेर पर ज्यादा जगह नहीं ली। इसलिए एक दृढ़ रणनीति का उपयोग करना जो अधिकांश स्मृति के पुन: उपयोग को प्रोत्साहित करता है, एक जीत भी नहीं है; आपके द्वारा किए गए सभी कामों से आपका कचरा संग्रहकर्ता धीमा हो जाता है क्योंकि अब इसे आंतरिक बिंदुओं को संभालने के बारे में चिंता करना पड़ता है।
यदि स्ट्रिंग करने वाले लोग आमतौर पर स्ट्रिंग्स पर काम करते हैं, तो वे पूरी तरह से अलग होते हैं, तो यह लगातार दृष्टिकोण के साथ जाने के लिए समझ में आता है। यदि लोगों के पास आम तौर पर मिलियन-कैरेक्टर स्ट्रिंग्स होते थे, और हजारों-हजार ओवरलैपिंग सब्सट्रिंग को आकार में सौ-हज़ार-कैरेक्टर रेंज में निकालते थे, और वे सबस्ट्रिंग ढेर पर लंबे समय तक रहते थे, तो यह लगातार समझ के साथ जाने के लिए सही समझ में आता है दृष्टिकोण; यह बेकार और मूर्खतापूर्ण होगा। लेकिन अधिकांश लाइन-ऑफ-बिजनेस प्रोग्रामर उन चीजों की तरह अस्पष्ट रूप से भी कुछ नहीं करते हैं। .NET एक ऐसा प्लेटफ़ॉर्म नहीं है जो मानव जीनोम प्रोजेक्ट की जरूरतों के अनुसार बनाया गया हो; डीएनए विश्लेषण प्रोग्रामर को हर दिन उन स्ट्रिंग उपयोग विशेषताओं के साथ समस्याओं को हल करना होगा; ऑड्स अच्छे हैं कि आप नहीं करते हैं। जो अपने स्वयं के लगातार डेटा संरचनाओं का निर्माण करते हैं जो उनके उपयोग परिदृश्यों के साथ निकटता से मेल खाते हैं।
उदाहरण के लिए, मेरी टीम ऐसे प्रोग्राम लिखती है जो आपके टाइप करते ही C # और VB कोड का विश्लेषण करते हैं। उन कोड फ़ाइलों में से कुछ बहुत बड़ी हैं और इस प्रकार हम सबस्ट्रिंग निकालने या वर्णों को निकालने या हटाने के लिए O (n) स्ट्रिंग हेरफेर नहीं कर सकते हैं। हम एक पाठ बफर जल्दी से और कुशलता मौजूदा स्ट्रिंग डेटा के थोक का फिर से उपयोग करने की अनुमति है कि करने के लिए संपादन प्रतिनिधित्व करने के लिए लगातार अपरिवर्तनीय डेटा संरचनाओं के एक समूह का निर्माण किया है और एक ठेठ संपादन पर मौजूदा शाब्दिक और वाक्यात्मक विश्लेषण। यह हल करने के लिए एक कठिन समस्या थी और इसका समाधान सी # और वीबी कोड संपादन के विशिष्ट डोमेन के अनुरूप था। हमारे लिए इस समस्या को हल करने के लिए अंतर्निहित स्ट्रिंग प्रकार की अपेक्षा करना अवास्तविक होगा।
string contents = File.ReadAllText(filename); foreach (string line in content.Split("\n")) ...
या इसके अन्य संस्करण। मेरा मतलब है कि एक पूरी फ़ाइल पढ़ें, फिर विभिन्न भागों की प्रक्रिया करें। उस प्रकार का कोड काफी तेज होगा और यदि स्ट्रिंग लगातार थी तो कम मेमोरी की आवश्यकता होती है; आपके पास प्रत्येक पंक्ति की प्रतिलिपि बनाने के बजाय हमेशा फ़ाइल की एक प्रति मेमोरी में होगी, फिर प्रत्येक पंक्ति के भाग आपकी प्रक्रिया के रूप में। हालांकि, जैसे एरिक ने कहा - यह विशिष्ट उपयोग का मामला नहीं है।
String
को एक सतत डेटा संरचना के रूप में लागू किया जाता है (यह मानकों में निर्दिष्ट नहीं है, लेकिन सभी कार्यान्वयन मुझे पता है)।
सटीक रूप से क्योंकि स्ट्रिंग्स अपरिवर्तनीय हैं, .Substring
उन्हें मूल स्ट्रिंग के कम से कम एक हिस्से की प्रतिलिपि बनाना होगा। N बाइट्स की एक प्रति बनाते समय O (n) समय लेना चाहिए।
आपको कैसे लगता है कि आप निरंतर समय में बाइट्स का एक गुच्छा कॉपी करेंगे ?
EDIT: मेहरदाद सुझाव देता है कि स्ट्रिंग को बिल्कुल भी कॉपी न करें, लेकिन इसके एक टुकड़े का संदर्भ रखते हुए।
.Net, एक बहु-मेगाबाइट स्ट्रिंग पर विचार करें, जिस पर कोई कॉल करता है .SubString(n, n+3)
(स्ट्रिंग के बीच में किसी भी n के लिए)।
अब, एनटीआईआरई स्ट्रिंग को गारबेज कलेक्ट नहीं किया जा सकता है क्योंकि एक संदर्भ 4 वर्णों पर टिका है? यह अंतरिक्ष की एक हास्यास्पद बर्बादी की तरह लगता है।
इसके अलावा, सब्सट्रिंग (जो कि सब्सट्रिंग के अंदर भी हो सकता है) के संदर्भ को ट्रैक करता है, और GC को पराजित करने से बचने के लिए इष्टतम समय पर कॉपी करने की कोशिश करता है (जैसा कि ऊपर वर्णित है), अवधारणा को एक बुरा सपना बनाता है। यह बहुत सरल है, और अधिक विश्वसनीय है, जिस पर नकल करना .SubString
, और सीधे अपरिवर्तनीय मॉडल को बनाए रखना है।
संपादित करें: यहाँ बड़े स्ट्रिंग्स के भीतर सबस्ट्रिंग के संदर्भों को रखने के खतरे के बारे में अच्छा पढ़ा गया है।
memcpy
जो अभी भी O (n) है।
char*
।
NULL
। जैसा कि लिपर्ट के पोस्ट में बताया गया है , पहले 4 बाइट्स में स्ट्रिंग की लंबाई होती है। इसीलिए, जैसा कि स्कीट बताते हैं, वे \0
वर्ण शामिल कर सकते हैं ।
जावा (जैसा कि .NET के विपरीत) करने के दो तरीके प्रदान करता है Substring()
, आप इस पर विचार कर सकते हैं कि क्या आप केवल एक संदर्भ रखना चाहते हैं या किसी नए मेमोरी लोकेशन में पूरे विकल्प को कॉपी कर सकते हैं।
साधारण .substring(...)
शेयरों में char
मूल स्ट्रिंग ऑब्जेक्ट के साथ आंतरिक रूप से उपयोग की जाने वाली सरणी है, जिसे आप तब new String(...)
एक नए सरणी में कॉपी कर सकते हैं, यदि आवश्यक हो (मूल एक के कचरा संग्रह में बाधा से बचने के लिए)।
मुझे लगता है कि इस तरह का लचीलापन एक डेवलपर के लिए सबसे अच्छा विकल्प है।
.substring(...)
।
जावा बड़े तार का संदर्भ देता था, लेकिन:
मुझे लगता है कि हालांकि इसमें सुधार किया जा सकता है: सिर्फ सशर्त रूप से नकल क्यों नहीं?
यदि विकल्प अभिभावक के कम से कम आधे आकार का है, तो कोई अभिभावक को संदर्भित कर सकता है। अन्यथा कोई सिर्फ एक प्रति बना सकता है। यह एक महत्वपूर्ण लाभ प्रदान करते हुए बहुत सी मेमोरी को लीक करने से बचाती है।
char[]
एक नया बनाने के लिए एक ही आधार (अलग-अलग बिंदुओं के साथ शुरू और अंत) का उपयोग करने से बदल गया है String
। यह स्पष्ट रूप से दिखाता है कि लागत-लाभ विश्लेषण को नए के निर्माण के लिए प्राथमिकता दिखानी चाहिए String
।
यहां कोई भी उत्तर "ब्रैकेटिंग समस्या" को संबोधित नहीं करता है, जो यह कहना है कि .NET में तार एक BStr ("पॉइंटर" से पहले मेमोरी में संग्रहीत लंबाई) और CStr (स्ट्रिंग समाप्त होता है) के संयोजन के रूप में दर्शाए जाते हैं '\ 0')।
स्ट्रिंग "हेलो वहाँ" को इस प्रकार दर्शाया गया है
0B 00 00 00 48 00 65 00 6C 00 6F 00 20 00 74 00 68 00 65 00 72 00 65 00 00 00
(अगर एक करने के लिए आवंटित char*
एक में fixed
-statement सूचक 0x48 को इंगित होगा।)
यह संरचना एक स्ट्रिंग की लंबाई (कई संदर्भों में उपयोगी) की तेजी से देखने की अनुमति देती है और पॉइंटर को P / Invoke में Win32 (या अन्य) एपीआई में पारित करने की अनुमति देती है जो अशक्त-समाप्त स्ट्रिंग की उम्मीद करते हैं।
जब आप Substring(0, 5)
"ओह करते हैं, लेकिन मैंने वादा किया था कि अंतिम चरित्र के बाद एक अशक्त चरित्र होगा" नियम कहता है कि आपको एक प्रतिलिपि बनाने की आवश्यकता है। यहां तक कि अगर आपको अंत में सबस्ट्रिंग मिला है, तो अन्य चर को भ्रष्ट किए बिना लंबाई डालने के लिए कोई जगह नहीं होगी।
कभी-कभी, हालांकि, आप वास्तव में "स्ट्रिंग के मध्य" के बारे में बात करना चाहते हैं, और आप जरूरी नहीं कि पी / इनवॉइस व्यवहार के बारे में परवाह करते हैं। हाल ही में जोड़ी गई ReadOnlySpan<T>
संरचना का उपयोग बिना कॉपी के प्रतिस्थापन के लिए किया जा सकता है:
string s = "Hello there";
ReadOnlySpan<char> hello = s.AsSpan(0, 5);
ReadOnlySpan<char> ell = hello.Slice(1, 3);
ReadOnlySpan<char>
"सबस्ट्रिंग" भंडार लंबाई स्वतंत्र रूप से, और यह गारंटी मूल्य के अंत के बाद यह है कि वहाँ एक '\ 0' नहीं है। इसका उपयोग कई तरह से किया जा सकता है "एक स्ट्रिंग की तरह", लेकिन यह "स्ट्रिंग" नहीं है क्योंकि इसमें या तो BStr या CStr विशेषताएँ नहीं हैं (दोनों में बहुत कम)। यदि आप कभी भी (सीधे) पी / इनवोक नहीं करते हैं, तो बहुत अंतर नहीं है (जब तक कि जिस एपीआई को आप कॉल करना चाहते हैं उसके पास ReadOnlySpan<char>
अधिभार नहीं है )।
ReadOnlySpan<char>
संदर्भ प्रकार के क्षेत्र के रूप में उपयोग नहीं किया जा सकता है, इसलिए वहां भी ReadOnlyMemory<char>
( s.AsMemory(0, 5)
) है, जो कि एक अप्रत्यक्ष तरीका है ReadOnlySpan<char>
, इसलिए समान अंतर- string
मौजूद हैं।
पिछले उत्तरों पर दिए गए कुछ उत्तरों / टिप्पणियों के बारे में बात की गई कि कचरा इकट्ठा करने वाले के लिए यह बेकार है कि आपको 5 वर्णों के बारे में बात करना जारी रखने के दौरान एक मिलियन-वर्ण स्ट्रिंग रखना होगा। यह ठीक वही व्यवहार है जो आप ReadOnlySpan<char>
दृष्टिकोण के साथ प्राप्त कर सकते हैं । यदि आप केवल छोटी संगणनाएँ कर रहे हैं, तो ReadOnlySpan दृष्टिकोण शायद बेहतर है। यदि आपको इसे थोड़ी देर के लिए जारी रखने की आवश्यकता है और आप मूल स्ट्रिंग का केवल एक छोटा प्रतिशत रखने जा रहे हैं, तो उचित प्रतिस्थापन (अतिरिक्त डेटा को ट्रिम करने के लिए) करना बेहतर है। बीच में कहीं एक संक्रमण बिंदु है, लेकिन यह आपके विशिष्ट उपयोग पर निर्भर करता है।