जावा स्ट्रिंग इंटर्निंग क्या है?


234

जावा में स्ट्रिंग इंटरस्टिंग क्या है , जब मुझे इसका उपयोग करना चाहिए, और क्यों ?



2
अगर String a = new String("abc"); String b = new String("abc"); तबa.intern() == b.intern()
असांका सिरीवर्देना

चेकआउट स्ट्रिंग इंटर्निंग
रौनक पोरिया

क्या अर्थ String.intern()पर निर्भर करता ClassLoaderहै, क्या अलग-अलग क्लास लोडर "अलग" बना रहे हैं String, अलग-अलग internएस?
AlikElzin-kilaka

1
@ AlikElzin-kilaka नहीं, स्ट्रिंग इंटर्निंग के लिए क्लास लोडर पूरी तरह से अप्रासंगिक हैं। अगली बार जब आपके पास कोई प्रश्न हो, तो कृपया एक अलग प्रश्न के लिए एक टिप्पणी के रूप में पोस्ट करने के बजाय एक नया प्रश्न खोलें
होल्गर

जवाबों:


233

http://docs.oracle.com/javase/7/docs/api/java/lang/String.html#intern ()

मूल रूप से स्ट्रिंग की एक श्रृंखला पर String.intern () करने से यह सुनिश्चित होगा कि समान सामग्री वाले सभी तार समान मेमोरी साझा करते हैं। इसलिए यदि आपके पास उन नामों की सूची है, जहां 'जॉन' 1000 बार दिखाई देता है, तो यह सुनिश्चित करके कि आप वास्तव में केवल एक 'जॉन' आवंटित करते हैं।

यह आपके प्रोग्राम की मेमोरी आवश्यकताओं को कम करने के लिए उपयोगी हो सकता है। लेकिन ध्यान रखें कि कैश को स्थायी मेमोरी पूल में जेवीएम द्वारा बनाए रखा जाता है जो आमतौर पर ढेर की तुलना में आकार में सीमित होता है, इसलिए यदि आपको बहुत अधिक डुप्लिकेट मान नहीं हैं तो आपको इंटर्न का उपयोग नहीं करना चाहिए।


इंटर्न का उपयोग करने की स्मृति बाधाओं पर अधिक ()

एक तरफ, यह सच है कि आप स्ट्रिंग डुप्लिकेट को आंतरिक रूप से हटा सकते हैं। समस्या यह है कि आंतरिक तार स्थायी पीढ़ी पर जाते हैं, जो जेवीएम का एक क्षेत्र है जो गैर-उपयोगकर्ता ऑब्जेक्ट्स के लिए आरक्षित है, जैसे क्लास, मेथड और अन्य आंतरिक जेवीएम ऑब्जेक्ट। इस क्षेत्र का आकार सीमित है, और आमतौर पर ढेर की तुलना में बहुत छोटा है। कॉलिंग इंटर्न () को एक स्ट्रिंग पर इसे स्थायी पीढ़ी में ढेर से बाहर ले जाने का प्रभाव पड़ता है, और आप पर्मगेन अंतरिक्ष से बाहर चलने का जोखिम उठाते हैं।

- http://www.codeinstructions.com/2009/01/busting-javalangstringintern-myths.html


जेडीके 7 (हॉटस्पॉट में मेरा मतलब है) से, कुछ बदल गया है।

JDK 7 में, इंटर्न स्ट्रिंग्स को अब जावा हीप की स्थायी पीढ़ी में आवंटित नहीं किया जाता है, बल्कि उन्हें जावा हीप के मुख्य भाग (युवा और पुरानी पीढ़ी के रूप में जाना जाता है) के साथ-साथ एप्लिकेशन द्वारा बनाई गई अन्य वस्तुओं के साथ आवंटित किया जाता है। । इस परिवर्तन के परिणामस्वरूप मुख्य जावा हीप में अधिक डेटा रहता है, और स्थायी पीढ़ी में कम डेटा, और इस प्रकार ढेर आकार को समायोजित करने की आवश्यकता हो सकती है। अधिकांश अनुप्रयोग इस परिवर्तन के कारण ढेर उपयोग में केवल अपेक्षाकृत छोटे अंतर देखेंगे, लेकिन कई अनुप्रयोग जो कई कक्षाओं को लोड करते हैं या String.intern () विधि का भारी उपयोग करते हैं, वे अधिक महत्वपूर्ण अंतर देखेंगे।

- से जावा एसई 7 सुविधाओं और संवर्द्धन से

अद्यतन: आंतरिक तार जावा 7 से मुख्य ढेर में संग्रहीत किए जाते हैं। http://www.oracle.com/technetwork/java/javase/jdk7-relnotes-418459.html#jdk7changes


1
"लेकिन ध्यान रखें कि कैश को स्थायी मेमोरी पूल में जेवीएम द्वारा बनाए रखा जाता है जो आमतौर पर आकार में सीमित होता है ......" क्या आप इसे समझा सकते हैं? मुझे समझ में नहीं आया
saplingPro

2
"इंटर्न" तार JVM में एक विशेष मेमोरी क्षेत्र में संग्रहीत किए जाते हैं। इस मेमोरी क्षेत्र में आम तौर पर एक निश्चित आकार होता है, और नियमित रूप से जावा हीप का हिस्सा नहीं होता है जहां अन्य डेटा संग्रहीत होता है। निश्चित आकार के कारण, ऐसा हो सकता है कि यह स्थायी मेमोरी क्षेत्र आपके सभी तारों से भर जाता है, जिससे बदसूरत समस्याएं (कक्षाएं लोड नहीं हो सकती हैं और अन्य सामान) हो सकती हैं।
सेलो

@ लेस्लो, तो क्या यह कैशिंग के समान है?
सपलिंगप्रो

8
@grassPro: हाँ, यह एक तरह का कैशिंग है, जो कि मूल रूप से JVM द्वारा प्रदान किया जाता है। एक नोट के रूप में, Sun / Oracle JVM और JRockit के विलय के कारण, JVM इंजीनियरों ने JDK 8 ( openjdk.java.net/jeps/122 ) में स्थायी मेमोरी क्षेत्र से छुटकारा पाने की कोशिश की , इसलिए वहाँ नहीं होगा भविष्य में किसी भी आकार की सीमा।
सेलो

9
प्रोग्रामर को यह भी पता होना चाहिए कि स्ट्रिंग इंटर्न के सुरक्षा निहितार्थ हो सकते हैं। यदि आपके पास संवेदनशील पाठ जैसे पासवर्ड स्मृति में तार के रूप में हैं, तो यह बहुत लंबे समय तक मेमोरी में रह सकता है, भले ही वास्तविक स्ट्रिंग ऑब्जेक्ट लंबे समय तक GC'd रहे हों। अगर किसी को बुरे लोगों को मेमोरी डंप तक पहुंच मिलती है तो यह परेशानी हो सकती है। यह समस्या इंटर्निंग के बिना भी मौजूद है (चूंकि जीसी आदि के साथ शुरू करने के लिए गैर-नियतात्मक है), लेकिन यह इसे कुछ हद तक बदतर बनाता है। संवेदनशील टेक्स्ट के char[]बजाय इसका उपयोग करना हमेशा अच्छा होता है Stringऔर इसे जल्द से जल्द शून्य करना चाहिए।
क्रिस 11

71

कुछ "आकर्षक साक्षात्कार" प्रश्न हैं, जैसे कि आपको बराबरी क्यों मिलती है! यदि आप नीचे दिए गए कोड को निष्पादित करते हैं।

String s1 = "testString";
String s2 = "testString";
if(s1 == s2) System.out.println("equals!");

यदि आप स्ट्रिंग्स की तुलना करना चाहते हैं तो आपको उपयोग करना चाहिए equals()। ऊपर प्रिंट बराबर होगा क्योंकिtestString कंपाइलर द्वारा आपके लिए पहले से ही इंटर्न किया गया है। जैसा कि पिछले उत्तरों में दिखाया गया है, आप इंटर्न विधि का उपयोग करके अपने आप को तार कर सकते हैं ...।


5
आपका उदाहरण मुश्किल है क्योंकि यह equalsविधि का उपयोग करने पर भी उसी प्रिंट के परिणामस्वरूप होगा । आप new String()अंतर को अधिक स्पष्ट रूप से दिखाने के लिए एक तुलना जोड़ना चाह सकते हैं ।
गियान्निस क्रिस्टोफ़ाकिस

@giannischristofakis लेकिन अगर हम नए स्ट्रिंग () का उपयोग करते हैं, तो == विफल नहीं होगा? क्या जावा स्वचालित रूप से नए तार भी आंतरिक करता है?
दीपक सेल्वकुमार

यदि आप नए स्ट्रिंग () का उपयोग करते हैं तो यह निश्चित रूप से == पर विफल रहेगा। लेकिन नया स्ट्रिंग (...)। इंटर्न () == पर विफल नहीं होगा क्योंकि इंटर्न एक ही स्ट्रिंग लौटाएगा। सरल
धारणा

42

JLS

JLS 7 3.10.5 इसे परिभाषित करता है और एक व्यावहारिक उदाहरण देता है:

इसके अलावा, एक स्ट्रिंग शाब्दिक हमेशा कक्षा स्ट्रिंग के एक ही उदाहरण को संदर्भित करता है। ऐसा इसलिए है क्योंकि स्ट्रिंग शाब्दिक - या, अधिक सामान्यतः, स्ट्रिंग्स जो निरंतर अभिव्यक्तियों (.215.28) के मान हैं - "इंटर्नड" हैं ताकि अद्वितीय उदाहरणों को साझा करें, विधि String.intern का उपयोग करके।

उदाहरण 3.10.5-1। स्ट्रिंग साहित्य

संकलन इकाई (consisting7.3) से मिलकर कार्यक्रम:

package testPackage;
class Test {
    public static void main(String[] args) {
        String hello = "Hello", lo = "lo";
        System.out.print((hello == "Hello") + " ");
        System.out.print((Other.hello == hello) + " ");
        System.out.print((other.Other.hello == hello) + " ");
        System.out.print((hello == ("Hel"+"lo")) + " ");
        System.out.print((hello == ("Hel"+lo)) + " ");
        System.out.println(hello == ("Hel"+lo).intern());
    }
}
class Other { static String hello = "Hello"; }

और संकलन इकाई:

package other;
public class Other { public static String hello = "Hello"; }

उत्पादन का उत्पादन:

true true true true false true

JVMs

JVMS 7 5.1 का कहना है कि इंटर्निंग को एक समर्पित CONSTANT_String_infoसंरचना के साथ जादुई और कुशलता से लागू किया जाता है (अधिकांश अन्य वस्तुओं के विपरीत जो अधिक सामान्य प्रतिनिधित्व करते हैं):

एक स्ट्रिंग शाब्दिक वर्ग स्ट्रिंग के उदाहरण के लिए एक संदर्भ है, और एक वर्ग या इंटरफ़ेस के द्विआधारी प्रतिनिधित्व में एक CONSTANT_String_info संरचना (fo4.4.3) से लिया गया है। CONSTANT_String_info संरचना यूनिकोड कोड बिंदुओं के अनुक्रम को स्ट्रिंग शाब्दिक रूप देती है।

जावा प्रोग्रामिंग लैंग्वेज के लिए आवश्यक है कि समान स्ट्रिंग शाब्दिक (यानी, शाब्दिक, जिसमें कोड अंकों का समान अनुक्रम हो) को क्लास स्ट्रिंग (JLS §3.10.5) के समान उदाहरण को संदर्भित करना होगा। इसके अलावा, अगर String.intern को किसी भी स्ट्रिंग पर विधि कहा जाता है, तो परिणाम उसी वर्ग उदाहरण के लिए एक संदर्भ है जो कि यदि स्ट्रिंग एक शाब्दिक के रूप में दिखाई देता है तो उसे वापस कर दिया जाएगा। इस प्रकार, निम्नलिखित अभिव्यक्ति का मूल्य सही होना चाहिए:

("a" + "b" + "c").intern() == "abc"

एक स्ट्रिंग शाब्दिक प्राप्त करने के लिए, Java वर्चुअल मशीन CONSTANT_String_info संरचना द्वारा दिए गए कोड बिंदुओं के अनुक्रम की जांच करता है।

  • यदि विधि String.intern को पहले क्लास स्ट्रिंग के एक उदाहरण पर बुलाया गया है, जिसमें CONSTANT_String_info संरचना द्वारा दिए गए यूनिकोड कोड बिंदुओं का एक अनुक्रम है, तो स्ट्रिंग शाब्दिक व्युत्पत्ति का परिणाम कक्षा स्ट्रिंग के उसी उदाहरण का संदर्भ है।

  • अन्यथा, क्लास स्ट्रिंग का एक नया उदाहरण CONSTANT_String_ffo संरचना द्वारा दिए गए यूनिकोड कोड बिंदुओं के अनुक्रम से बना है; उस वर्ग उदाहरण का एक संदर्भ स्ट्रिंग शाब्दिक व्युत्पत्ति का परिणाम है। अंत में, नए स्ट्रिंग उदाहरण का आंतरिक तरीका लागू किया जाता है।

बाईटकोड

चलो कुछ OpenJDK 7 bytecode को कार्रवाई में इंटर्न करने के लिए विघटित करते हैं।

अगर हम सड़ जाते हैं:

public class StringPool {
    public static void main(String[] args) {
        String a = "abc";
        String b = "abc";
        String c = new String("abc");
        System.out.println(a);
        System.out.println(b);
        System.out.println(a == c);
    }
}

हमारे पास निरंतर पूल है:

#2 = String             #32   // abc
[...]
#32 = Utf8               abc

और main:

 0: ldc           #2          // String abc
 2: astore_1
 3: ldc           #2          // String abc
 5: astore_2
 6: new           #3          // class java/lang/String
 9: dup
10: ldc           #2          // String abc
12: invokespecial #4          // Method java/lang/String."<init>":(Ljava/lang/String;)V
15: astore_3
16: getstatic     #5          // Field java/lang/System.out:Ljava/io/PrintStream;
19: aload_1
20: invokevirtual #6          // Method java/io/PrintStream.println:(Ljava/lang/String;)V
23: getstatic     #5          // Field java/lang/System.out:Ljava/io/PrintStream;
26: aload_2
27: invokevirtual #6          // Method java/io/PrintStream.println:(Ljava/lang/String;)V
30: getstatic     #5          // Field java/lang/System.out:Ljava/io/PrintStream;
33: aload_1
34: aload_3
35: if_acmpne     42
38: iconst_1
39: goto          43
42: iconst_0
43: invokevirtual #7          // Method java/io/PrintStream.println:(Z)V

नोट कैसे:

  • 0और 3: वहीldc #2 निरंतर लोड किया जाता है (शाब्दिक)
  • 12: एक नया स्ट्रिंग इंस्टेंस बनाया गया है (के साथ) #2 तर्क के रूप में)
  • 35: aऔर cनियमित वस्तुओं के साथ तुलना की जाती हैif_acmpne

निरंतर तार का प्रतिनिधित्व बाइटकोड पर काफी जादू है:

  • इसमें नियमित वस्तुओं (जैसे) के विपरीत एक समर्पित CONSTANT_String_info संरचना हैnew String ) के
  • एक CONSTANT_Utf8_info संरचना को इंगित करता है जिसमें डेटा होता है। यह स्ट्रिंग का प्रतिनिधित्व करने के लिए केवल आवश्यक डेटा है।

और ऊपर दिए गए JVMS उद्धरण से लगता है कि जब भी यूटीएफ 8 को इंगित किया जाता है, तब समान उदाहरणों को लोड किया जाता है ldc

मैंने खेतों के लिए इसी तरह के परीक्षण किए हैं, और:

  • static final String s = "abc" के माध्यम से निरंतर तालिका को इंगित करता है कॉन्स्टेंटवैल्यू एट्रीब्यूट के
  • गैर-अंतिम फ़ील्ड में वह विशेषता नहीं होती है, लेकिन फिर भी उसके साथ आरंभ किया जा सकता है ldc

निष्कर्ष : स्ट्रिंग पूल के लिए प्रत्यक्ष बाइटकोड समर्थन है, और मेमोरी प्रतिनिधित्व कुशल है।

बोनस: इसकी तुलना इंटेगर पूल से करें , जिसमें डायरेक्ट बायटेकोड सपोर्ट नहीं है (यानी कोई CONSTANT_String_infoएनालॉग नहीं )।


19

जावा 8 या प्लस के लिए अपडेट करें । Java 8 में, PermGen (परमानेंट जेनरेशन) स्पेस को हटा दिया जाता है और मेटा स्पेस द्वारा प्रतिस्थापित किया जाता है। स्ट्रिंग पूल मेमोरी को JVM के ढेर में ले जाया जाता है।

जावा 7 की तुलना में, स्टेयरिंग पूल का आकार ढेर में बढ़ा है। इसलिए, आपके पास आंतरिक स्ट्रिंग्स के लिए अधिक स्थान है, लेकिन आपके पास पूरे एप्लिकेशन के लिए कम मेमोरी है।

एक और बात, आप पहले से ही जानते हैं कि जावा में 2 ( ==रेफरेंस) ऑब्जेक्ट्स की तुलना करते समय, ' equals' का उपयोग ऑब्जेक्ट के संदर्भ की तुलना करने के लिए किया जाता है, ' ' का उपयोग ऑब्जेक्ट की सामग्री की तुलना करने के लिए किया जाता है।

आइए इस कोड की जाँच करें:

String value1 = "70";
String value2 = "70";
String value3 = new Integer(70).toString();

परिणाम:

value1 == value2 ---> सच है

value1 == value3 ---> असत्य

value1.equals(value3) ---> सच है

value1 == value3.intern() ---> सच है

इसलिए आपको equals2 स्ट्रिंग वस्तुओं की तुलना करने के लिए ' ' का उपयोग करना चाहिए । और यह है कि कैसे intern()उपयोगी है।


2

स्ट्रिंग इंटर्निंग कंपाइलर द्वारा एक अनुकूलन तकनीक है। यदि आपके पास एक संकलन इकाई में दो समान स्ट्रिंग शाब्दिक हैं तो उत्पन्न कोड यह सुनिश्चित करता है कि विधानसभा के भीतर उस शाब्दिक (दोहरे कोट्स में संलग्न वर्ण) के सभी उदाहरणों के लिए केवल एक स्ट्रिंग ऑब्जेक्ट बनाया गया है।

मैं C # बैकग्राउंड से हूं, इसलिए मैं इससे एक उदाहरण देकर समझा सकता हूं:

object obj = "Int32";
string str1 = "Int32";
string str2 = typeof(int).Name;

निम्नलिखित तुलनाओं का उत्पादन:

Console.WriteLine(obj == str1); // true
Console.WriteLine(str1 == str2); // true    
Console.WriteLine(obj == str2); // false !?

नोट 1 : वस्तुओं की तुलना संदर्भ से की जाती है।

नोट 2 : टाइपोफ़ (इंट)। नाम का मूल्यांकन परावर्तन विधि द्वारा किया जाता है, ताकि इसका संकलन समय पर मूल्यांकन न हो सके। यहाँ ये तुलनाएँ संकलन के समय की जाती हैं।

परिणामों का विश्लेषण: 1) यह सच है क्योंकि वे दोनों एक ही शाब्दिक हैं और इसलिए उत्पन्न कोड में केवल एक ही ऑब्जेक्ट "Int32" होगा। नोट 1 देखें

2) सही है क्योंकि दोनों मूल्य की सामग्री की जाँच की जाती है जो समान है।

3) FALSE क्योंकि str2 और obj में एक ही शाब्दिक नहीं है। नोट 2 देखें ।


3
यह उससे ज्यादा मजबूत है। समान स्ट्रिंगर द्वारा लोड किया गया कोई भी स्ट्रिंग शाब्दिक उसी स्ट्रिंग को संदर्भित करेगा। जेएलएस और जेवीएम विशिष्टता देखें।
लोर्न

1
@ user207421 वास्तव में, यह भी अप्रासंगिक है कि स्ट्रिंग शाब्दिक का संबंध किस क्लास लोडर से है।
होल्गर

1
Java interning() method basically makes sure that if String object is present in SCP, If yes then it returns that object and if not then creates that objects in SCP and return its references

for eg: String s1=new String("abc");
        String s2="abc";
        String s3="abc";

s1==s2// false, because 1 object of s1 is stored in heap and other in scp(but this objects doesn't have explicit reference) and s2 in scp
s2==s3// true

now if we do intern on s1
s1=s1.intern() 

//JVM checks if there is any string in the pool with value “abc” is present? Since there is a string object in the pool with value “abc”, its reference is returned.
Notice that we are calling s1 = s1.intern(), so the s1 is now referring to the string pool object having value abc”.
At this point, all the three string objects are referring to the same object in the string pool. Hence s1==s2 is returning true now.

0

OCP Java SE 11 प्रोग्रामर देशमुख पुस्तक से मुझे इंटर्निंग के लिए सबसे आसान स्पष्टीकरण मिला, जो निम्नानुसार है: चूंकि तार ऑब्जेक्ट हैं और चूंकि जावा में सभी ऑब्जेक्ट्स हमेशा केवल हीप स्पेस में संग्रहीत होते हैं, सभी स्ट्रिंग्स को ढेर स्थान में संग्रहीत किया जाता है। हालांकि, जावा ढेर स्थान के एक विशेष क्षेत्र में नए कीवर्ड का उपयोग किए बिना तार बनाए रखता है, जिसे "स्ट्रिंग पूल" कहा जाता है। जावा नियमित हीप स्पेस में नए कीवर्ड का उपयोग करके बनाए गए तार रखता है।

स्ट्रिंग पूल का उद्देश्य अद्वितीय तार का एक सेट बनाए रखना है। जब भी आप नए कीवर्ड का उपयोग किए बिना एक नया स्ट्रिंग बनाते हैं, तो जावा जाँचता है कि क्या स्ट्रिंग में वही स्ट्रिंग पहले से मौजूद है। यदि ऐसा होता है, तो जावा उसी स्ट्रिंग वस्तु का संदर्भ देता है और यदि ऐसा नहीं होता है, तो जावा स्ट्रिंग स्ट्रिंग में एक नया स्ट्रिंग ऑब्जेक्ट बनाता है और इसका संदर्भ देता है। इसलिए, उदाहरण के लिए, यदि आप अपने कोड में दो बार स्ट्रिंग "हैलो" का उपयोग करते हैं जैसा कि नीचे दिखाया गया है, तो आपको उसी स्ट्रिंग का संदर्भ मिलेगा। हम वास्तव में इस सिद्धांत का परीक्षण कर सकते हैं = = ऑपरेटर के उपयोग से दो अलग-अलग संदर्भ चर की तुलना करके जैसा कि निम्नलिखित कोड में दिखाया गया है:

String str1 = "hello";
String str2 = "hello";
System.out.println(str1 == str2); //prints true

String str3 = new String("hello");
String str4 = new String("hello");

System.out.println(str1 == str3); //prints false
System.out.println(str3 == str4); //prints false 

== ऑपरेटर बस यह जांचता है कि क्या दो संदर्भ एक ही वस्तु को इंगित करते हैं या नहीं और यदि वे करते हैं तो सही है या नहीं। उपरोक्त कोड में, str2 को उसी स्ट्रिंग ऑब्जेक्ट का संदर्भ मिलता है जो पहले बनाया गया था। हालांकि, str3 और str4 को दो पूरी तरह से अलग स्ट्रिंग वस्तुओं के संदर्भ मिलते हैं। इसीलिए str1 == str2 रिटर्न सही है लेकिन str1 == str3 और str3 == str4 रिटर्न गलत है। वास्तव में, जब आप नया स्ट्रिंग ("हैलो") करते हैं; दो स्ट्रिंग वस्तुओं को केवल एक के बजाय बनाया जाता है यदि यह पहली बार स्ट्रिंग "हैलो" का उपयोग प्रोग्राम में कहीं भी किया जाता है - एक स्ट्रिंग स्ट्रिंग के उपयोग के कारण स्ट्रिंग पूल में, और एक नियमित रूप से ढेर जगह में क्योंकि नए कीवर्ड का उपयोग।

स्ट्रिंग पूलिंग एक ही मान वाले कई स्ट्रिंग ऑब्जेक्ट्स के निर्माण से बचकर प्रोग्राम मेमोरी को बचाने का जावा का तरीका है। स्ट्रिंग के आंतरिक विधि का उपयोग करके नए कीवर्ड का उपयोग करके बनाए गए स्ट्रिंग के लिए स्ट्रिंग पूल से स्ट्रिंग प्राप्त करना संभव है। इसे स्ट्रिंग ऑब्जेक्ट्स का "इंटर्निंग" कहा जाता है। उदाहरण के लिए,

String str1 = "hello";
String str2 = new String("hello");
String str3 = str2.intern(); //get an interned string obj

System.out.println(str1 == str2); //prints false
System.out.println(str1 == str3); //prints true
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.