CodeGym /Java Blog /यादृच्छिक /जावा मध्ये सीरियलायझेशन आणि डीसीरियलायझेशन
John Squirrels
पातळी 41
San Francisco

जावा मध्ये सीरियलायझेशन आणि डीसीरियलायझेशन

यादृच्छिक या ग्रुपमध्ये प्रकाशित केले
हाय! आजच्या धड्यात, आपण जावा मधील सीरियलायझेशन आणि डीसीरियलायझेशनबद्दल बोलू. आपण एका साध्या उदाहरणाने सुरुवात करू. कल्पना करा की तुम्ही संगणक गेम डेव्हलपर आहात. जर तुम्ही ९० च्या दशकात मोठे झाले असाल आणि त्या काळातील गेम कन्सोल आठवत असाल, तर तुम्हाला कदाचित माहित असेल की त्यांच्यात काहीतरी कमी आहे जे आज आम्ही गृहीत धरतो — गेम जतन करण्याची आणि लोड करण्याची क्षमता :) नसल्यास, याची कल्पना करा!जावा मधील सीरियलायझेशन आणि डीसीरियलायझेशन - १मला भीती वाटते की आज या क्षमतेशिवाय खेळ नशिबात येईल! असो, गेम 'जतन करणे' आणि 'लोड करणे' म्हणजे काय? बरं, आम्हाला दैनंदिन अर्थ समजतो: आम्ही जिथे सोडला होता तिथून आम्हाला खेळ सुरू ठेवायचा आहे. हे करण्यासाठी, आम्ही एक विशिष्ट 'चेक पॉइंट' तयार करतो जो आम्ही नंतर गेम लोड करण्यासाठी वापरतो. पण कॅज्युअल गेमरऐवजी प्रोग्रामरला याचा अर्थ काय आहे? उत्तर सोपे आहे: आम्ही आमच्या प्रोग्रामची स्थिती जतन करतो. समजा तुम्ही स्ट्रॅटेजी गेममध्ये स्पेन खेळत आहात. तुमच्या गेममध्ये राज्य आहे: प्रत्येकाकडे कोणते प्रदेश आहेत, प्रत्येकाकडे किती संसाधने आहेत, कोणती युती अस्तित्वात आहे आणि कोणासोबत आहे, कोण युद्ध करत आहे, इत्यादी. ही माहिती, आमच्या प्रोग्रामची स्थिती, डेटा पुनर्संचयित करण्यासाठी आणि गेम सुरू ठेवण्यासाठी जतन करणे आवश्यक आहे. जसे ते घडते, Java मधील सीरियलायझेशन ही बाइट्सच्या क्रमानुसार ऑब्जेक्टची स्थिती जतन करण्याची प्रक्रिया आहे. Java मधील डिसिरियलायझेशन ही या बाइट्समधून ऑब्जेक्ट पुनर्संचयित करण्याची प्रक्रिया आहे. कोणतीही Java ऑब्जेक्ट बाइट अनुक्रमात रूपांतरित केले जाऊ शकते. आम्हाला याची गरज का आहे? आम्ही वारंवार सांगितले आहे की प्रोग्राम स्वतः अस्तित्वात नाहीत. बर्‍याचदा, ते एकमेकांशी संवाद साधतात, डेटाची देवाणघेवाण करतात इ. यासाठी बाइट फॉरमॅट सोयीस्कर आणि कार्यक्षम आहे. उदाहरणार्थ, आम्ही आमच्या SavedGame चे ऑब्जेक्ट रूपांतरित करू शकतोबाइट्सच्या क्रमवारीत वर्ग करा, हे बाइट्स नेटवर्कवरून दुसर्‍या संगणकावर हस्तांतरित करा आणि नंतर दुसर्‍या संगणकावर हे बाइट्स परत Java ऑब्जेक्टमध्ये रूपांतरित करा! हे अवघड वाटतं, हं? हे सर्व घडवून आणणे कठीण होईल असे दिसते: / आनंदाने, तसे नाही! :) Java मध्ये, सीरिअलायझेशन प्रक्रियेसाठी सिरियलाइज करण्यायोग्य इंटरफेस जबाबदार आहे. हा इंटरफेस अत्यंत सोपा आहे: तो वापरण्यासाठी तुम्हाला एकच पद्धत लागू करण्याची गरज नाही! गेम सेव्ह करण्याचा आमचा वर्ग किती सोपा आहे ते पहा:

import java.io.Serializable;
import java.util.Arrays;

public class SavedGame implements Serializable {

   private static final long serialVersionUID = 1L;

   private String[] territoryInfo;
   private String[] resourceInfo;
   private String[] diplomacyInfo;

   public SavedGame(String[] territoryInfo, String[] resourceInfo, String[] diplomacyInfo){
       this.territoryInfo = territoryInfo;
       this.resourceInfo = resourceInfo;
       this.diplomacyInfo = diplomacyInfo;
   }

   public String[] getTerritoryInfo() {
       return territoryInfo;
   }

   public void setTerritoryInfo(String[] territoryInfo) {
       this.territoryInfo = territoryInfo;
   }

   public String[] getResourceInfo() {
       return resourceInfo;
   }

   public void setResourceInfo(String[] resourceInfo) {
       this.resourceInfo = resourceInfo;
   }

   public String[] getDiplomacyInfo() {
       return diplomacyInfo;
   }

   public void setDiplomacyInfo(String[] diplomacyInfo) {
       this.diplomacyInfo = diplomacyInfo;
   }

   @Override
   public String toString() {
       return "SavedGame{" +
               "territoryInfo=" + Arrays.toString(territoryInfo) +
               ", resourceInfo=" + Arrays.toString(resourceInfo) +
               ", diplomacyInfo=" + Arrays.toString(diplomacyInfo) +
               '}';
   }
}
प्रदेश, संसाधने आणि मुत्सद्दीपणाबद्दलच्या माहितीसाठी तीन अॅरे जबाबदार आहेत आणि सीरिअलायझ करण्यायोग्य इंटरफेस जावा मशीनला सांगतो: ' या वर्गातील वस्तू अनुक्रमित केल्या गेल्यास सर्वकाही ठीक आहे '.' एका इंटरफेसशिवाय इंटरफेस विचित्र दिसत आहे :/ ते का आवश्यक आहे? त्या प्रश्नाचे उत्तर वर दिले आहे: फक्त जावा मशीनला आवश्यक माहिती प्रदान करणे आवश्यक आहे. मागील धड्यात, आम्ही मार्कर इंटरफेसचा थोडक्यात उल्लेख केला आहे. हे विशेष माहितीचे इंटरफेस आहेत जे आमच्या वर्गांना फक्त अतिरिक्त माहितीसह चिन्हांकित करतात जे भविष्यात Java मशीनसाठी उपयुक्त ठरतील. त्यांच्याकडे तुम्हाला अंमलात आणण्याच्या कोणत्याही पद्धती नाहीत. येथे आहे सीरिअलाइज करण्यायोग्य — असाच एक इंटरफेस. येथे आणखी एक महत्त्वाचा मुद्दा आहे: आम्हाला याची आवश्यकता का आहेखाजगी स्थिर अंतिम लांब सिरीयलVersionUID व्हेरिएबल जे आम्ही वर्गात परिभाषित केले आहे? या फील्डमध्ये अनुक्रमित वर्गाचा अद्वितीय आवृत्ती अभिज्ञापक आहे. अनुक्रमे करण्यायोग्य इंटरफेस लागू करणार्‍या प्रत्येक वर्गाकडे आवृत्ती अभिज्ञापक असतो. हे वर्गाच्या सामग्रीच्या आधारे निर्धारित केले जाते — फील्ड आणि त्यांची घोषणा क्रम, आणि पद्धती आणि त्यांच्या घोषणा क्रम. आणि जर आम्ही फील्ड प्रकार आणि/किंवा आमच्या वर्गातील फील्डची संख्या बदलली तर आवृत्ती ओळखकर्ता त्वरित बदलतो. जेव्हा वर्ग क्रमवारी लावला जातो तेव्हा serialVersionUID देखील लिहिले जाते. जेव्हा आपण डीसीरियलाइज करण्याचा प्रयत्न करतो, म्हणजे बाइट अनुक्रमातून ऑब्जेक्ट पुनर्संचयित करण्याचा प्रयत्न करतो, तेव्हा serialVersionUID चे मूल्य serialVersionUID च्या मूल्याशी तुलना केली जाते.आमच्या कार्यक्रमातील वर्गाचा. मूल्ये जुळत नसल्यास, java.io.InvalidClassException टाकला जाईल. याचे उदाहरण आपण खाली पाहू. अशा परिस्थिती टाळण्यासाठी, आम्ही आमच्या वर्गासाठी व्यक्तिचलितपणे आवृत्ती अभिज्ञापक सेट करतो. आमच्या बाबतीत, ते फक्त 1 च्या बरोबरीचे असेल (तुम्ही तुम्हाला आवडणारी कोणतीही दुसरी संख्या वापरू शकता). बरं, आमच्या सेव्हडगेम ऑब्जेक्टला क्रमवारी लावण्याचा प्रयत्न करण्याची आणि काय होते ते पाहण्याची वेळ आली आहे!

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class Main {

   public static void main(String[] args) throws IOException {

       // Create our object
       String[] territoryInfo = {"Spain has 6 provinces", "Russia has 10 provinces", "France has 8 provinces"};
       String[] resourceInfo = {"Spain has 100 gold", "Russia has 80 gold", "France has 90 gold"};
       String[] diplomacyInfo = {"France is at war with Russia, Spain has taken a neutral position"};

       SavedGame savedGame = new SavedGame(territoryInfo, resourceInfo, diplomacyInfo);

       // Create 2 streams to serialize the object and save it to a file
       FileOutputStream outputStream = new FileOutputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);

       // Save the game to a file
       objectOutputStream.writeObject(savedGame);

       // Close the stream and release resources
       objectOutputStream.close();
   }
}
तुम्ही बघू शकता, आम्ही 2 प्रवाह तयार केले आहेत: FileOutputStream आणि ObjectOutputStream . पहिल्याला फाईलमध्ये डेटा कसा लिहायचा हे माहित आहे आणि दुसरा ऑब्जेक्ट्स बाइट्समध्ये रूपांतरित करतो. तुम्ही आधीपासून समान नेस्टेड रचना पाहिल्या आहेत, उदाहरणार्थ, नवीन BufferedReader(नवीन InputStreamReader(...)) , मागील धड्यांमध्ये, त्यामुळे त्यांनी तुम्हाला घाबरू नये :) दोन प्रवाहांची ही साखळी तयार करून, आम्ही दोन्ही कार्ये करतो: आम्ही SavedGame ऑब्जेक्टला बाइट्सच्या अनुक्रमात रूपांतरित करतो आणि writeObject() पद्धत वापरून फाइलमध्ये सेव्ह करतो . आणि, तसे, आम्हाला काय मिळाले ते आम्ही पाहिले नाही! फाइल पाहण्याची वेळ आली आहे! *टीप: फाइल आगाऊ तयार करणे आवश्यक नाही. जर निर्दिष्ट नावाची फाइल अस्तित्वात नसेल, तर ती आपोआप तयार होईल* आणि त्याची सामग्री येथे आहे: ¬н sr SavedGame [ diplomacyInfot [Ljava/lang/String; [ resourceInfoq ~ [ territoryInfoq ~ xpur [Ljava.lang. स्ट्रिंग;¬ТVзй{G xp t pФрнция РІРССЋРµС, СЃ РРссией, Р˜СЃРїР°РЅРСЏ + РµС,Р°uq ~ t "РЈ Р˜СЃРїР°РЅРё 100 Р·РслоС,Р°t РЈ Р РѕСЃСЃСЃРё 80 Р·РѕР»СРё°09 »Рстаuq ~ t &РЈ Р˜СЃРїР°РЅРё 6 провинцийt %РЈ Р РсСЃСЃРёРё 10 РїСЂРЅ анции ८ ओह, ओह :( असे दिसते की आमचा प्रोग्राम कार्य करत नाही : ( खरं तर, त्याने कार्य केले. तुम्हाला आठवत असेल की आम्ही फाईलमध्ये केवळ ऑब्जेक्ट किंवा मजकूर नव्हे तर बाइट्सचा क्रम पाठवला आहे? बरं, हा बाइटचा क्रम आहे. असे दिसते :) हा आमचा सेव्ह केलेला गेम आहे! जर आम्हाला आमचा मूळ ऑब्जेक्ट पुनर्संचयित करायचा असेल, म्हणजे आम्ही जिथे सोडला होता तो खेळ सुरू आणि सुरू ठेवायचा असेल, तर आम्हाला उलट प्रक्रिया आवश्यक आहे: डीसीरियलायझेशन. आमच्यासाठी हे असे दिसते:

import java.io.*;

public class Main {

   public static void main(String[] args) throws IOException, ClassNotFoundException {

       FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);

       SavedGame savedGame = (SavedGame) objectInputStream.readObject();

       System.out.println(savedGame);
   }
}
आणि येथे परिणाम आहे! SavedGame{territoryInfo=[स्पेनमध्ये 6 प्रांत आहेत, रशियामध्ये 10 प्रांत आहेत, फ्रान्समध्ये 8 प्रांत आहेत], resourceInfo=[स्पेनकडे 100 सोने आहेत, रशियाकडे 80 सोने आहेत, फ्रान्सकडे 90 सोने आहेत], diplomacyInfo=[फ्रान्स रशियाशी युद्ध करत आहे, स्पेनने तटस्थ स्थिती घेतली आहे]} उत्कृष्ट! आम्ही प्रथम आमच्या गेमची स्थिती फाईलमध्ये जतन करण्यात आणि नंतर फाइलमधून पुनर्संचयित करण्यात व्यवस्थापित केले. आता आपण तेच करण्याचा प्रयत्न करूया, परंतु आम्ही आमच्या SavedGame वर्गातून आवृत्ती अभिज्ञापक काढून टाकू. आम्ही आमचे दोन्ही वर्ग पुन्हा लिहिणार नाही. त्यांचा कोड एकच असेल. आम्ही फक्त SavedGame वर्गातून खाजगी स्थिर अंतिम लांब सिरीयलVersionUID काढून टाकू . सीरियलायझेशन नंतर आमचे ऑब्जेक्ट येथे आहे: ¬н sr जतन केलेला गेम €MіuOM‰ [ डिप्लोमसीइन्फोट [Ljava/lang/String;[ resourceInfoq ~ [ territoryInfoq ~ xpur [Ljava.lang.String;¬ТVзй{G xp t pФранСРСЏ ЃРёРµР№, Р˜СЃРїР°ЅРёСЏ Р·°РЅСЏР»° र टी ЅС†РёР№t %РЈ Р ССЃСЃРёРё 10 првинцийt &РЈ Фр°РЅС†РёРё 8 РїСЂРѕІРёРЅС†РёР№ पण लोकल क्लासमध्ये केव्हा घडेल ते पहा: कॉममध्ये काय होईल ते पहा. patible: प्रवाह classdesc serialVersionUID = -196410440475012755, स्थानिक वर्ग serialVersionUID = -6675950253085108747 तसे, आम्ही काहीतरी महत्त्वाचे गमावले. साहजिकच, स्ट्रिंग्स आणि प्रिमिटिव्ह्ज सहजपणे अनुक्रमित केले जातात: Java मध्ये निश्चितपणे यासाठी काही अंगभूत यंत्रणा आहे. पण जर आमच्या क्रमिक वर्गामध्ये आदिम नसून इतर वस्तूंचे संदर्भ असतील तर? उदाहरणार्थ, आमच्या SavedGame वर्गासह कार्य करण्यासाठी स्वतंत्र TerritoryInfo , ResourceInfo आणि DiplomacyInfo वर्ग तयार करू .

public class TerritoryInfo {

   private String info;

   public TerritoryInfo(String info) {
       this.info = info;
   }

   public String getInfo() {
       return info;
   }

   public void setInfo(String info) {
       this.info = info;
   }

   @Override
   public String toString() {
       return "TerritoryInfo{" +
               "info='" + info + '\'' +
               '}';
   }
}

public class ResourceInfo {

   private String info;

   public ResourceInfo(String info) {
       this.info = info;
   }

   public String getInfo() {
       return info;
   }

   public void setInfo(String info) {
       this.info = info;
   }

   @Override
   public String toString() {
       return "ResourceInfo{" +
               "info='" + info + '\'' +
               '}';
   }
}

public class DiplomacyInfo {

   private String info;

   public DiplomacyInfo(String info) {
       this.info = info;
   }

   public String getInfo() {
       return info;
   }

   public void setInfo(String info) {
       this.info = info;
   }

   @Override
   public String toString() {
       return "DiplomacyInfo{" +
               "info='" + info + '\'' +
               '}';
   }
}
आणि आता आम्हाला एका प्रश्नाचा सामना करावा लागतो: जर आम्हाला आमच्या सेव्हेड गेम क्लासला क्रमवारी लावायची असेल तर हे सर्व वर्ग अनुक्रमे करण्यायोग्य असले पाहिजेत ?

import java.io.Serializable;
import java.util.Arrays;

public class SavedGame implements Serializable {

   private TerritoryInfo territoryInfo;
   private ResourceInfo resourceInfo;
   private DiplomacyInfo diplomacyInfo;

   public SavedGame(TerritoryInfo territoryInfo, ResourceInfo resourceInfo, DiplomacyInfo diplomacyInfo) {
       this.territoryInfo = territoryInfo;
       this.resourceInfo = resourceInfo;
       this.diplomacyInfo = diplomacyInfo;
   }

   public TerritoryInfo getTerritoryInfo() {
       return territoryInfo;
   }

   public void setTerritoryInfo(TerritoryInfo territoryInfo) {
       this.territoryInfo = territoryInfo;
   }

   public ResourceInfo getResourceInfo() {
       return resourceInfo;
   }

   public void setResourceInfo(ResourceInfo resourceInfo) {
       this.resourceInfo = resourceInfo;
   }

   public DiplomacyInfo getDiplomacyInfo() {
       return diplomacyInfo;
   }

   public void setDiplomacyInfo(DiplomacyInfo diplomacyInfo) {
       this.diplomacyInfo = diplomacyInfo;
   }

   @Override
   public String toString() {
       return "SavedGame{" +
               "territoryInfo=" + territoryInfo +
               ", resourceInfo=" + resourceInfo +
               ", diplomacyInfo=" + diplomacyInfo +
               '}';
   }
}
ठीक आहे, मग! चला ते चाचणी करूया! आत्तासाठी, आम्ही सर्वकाही जसे आहे तसे सोडू आणि सेव्हडगेम ऑब्जेक्टला क्रमबद्ध करण्याचा प्रयत्न करू:

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class Main {

   public static void main(String[] args) throws IOException {

       // Create our object
       TerritoryInfo territoryInfo = new TerritoryInfo("Spain has 6 provinces, Russia has 10 provinces, France has 8 provinces");
       ResourceInfo resourceInfo = new ResourceInfo("Spain has 100 gold, Russia has 80 gold, France has 90 gold");
       DiplomacyInfo diplomacyInfo =  new DiplomacyInfo("France is at war with Russia, Spain has taken a neutral position");


       SavedGame savedGame = new SavedGame(territoryInfo, resourceInfo, diplomacyInfo);

       FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);

       objectOutputStream.writeObject(savedGame);

       objectOutputStream.close();
   }
}
परिणाम: "मुख्य" थ्रेडमधील अपवाद java.io.NotSerializableException: DiplomacyInfo हे कार्य करत नाही! तर, आमच्या प्रश्नाचे उत्तर येथे आहे. जेव्हा एखादी वस्तू अनुक्रमित केली जाते, तेव्हा त्याच्या उदाहरण व्हेरिएबल्सद्वारे संदर्भित सर्व ऑब्जेक्ट्स अनुक्रमित केले जातात. आणि जर त्या वस्तू इतर वस्तूंचा संदर्भ देत असतील, तर त्या देखील अनुक्रमित केल्या जातात. आणि वर आणि कायमचे. या साखळीतील सर्व वर्ग अनुक्रमे करण्यायोग्य असले पाहिजेत , अन्यथा त्यांना अनुक्रमित करणे अशक्य होईल आणि अपवाद टाकला जाईल. तसे, यामुळे रस्त्यावर समस्या निर्माण होऊ शकतात. उदाहरणार्थ, क्रमवारी दरम्यान वर्गाचा भाग आवश्यक नसल्यास आपण काय करावे? किंवा लायब्ररीचा भाग म्हणून आम्हाला आमचा TerritoryInfo वर्ग 'वारसा द्वारे' मिळाला तर ? आणि पुढे समजा की ते आहे'आणि, त्यानुसार, आम्ही ते बदलू शकत नाही. याचा अर्थ असा होतो की आम्ही आमच्या SavedGame वर्गात TerritoryInfo फील्ड जोडू शकत नाही , कारण नंतर संपूर्ण SavedGame वर्ग अप्रमाणित होईल! ही एक समस्या आहे: / Java मध्ये, या प्रकारच्या समस्येचे निराकरण क्षणिक कीवर्डद्वारे केले जाते. तुम्ही तुमच्या वर्गाच्या फील्डमध्ये हा कीवर्ड जोडल्यास, ते फील्ड अनुक्रमित केले जाणार नाही. चला आमच्या SavedGame क्लासच्या फील्डपैकी एक क्षणिक बनवण्याचा प्रयत्न करूया , आणि नंतर आम्ही एक ऑब्जेक्ट सीरियल आणि रिस्टोअर करू. जावा मधील सीरियलायझेशन आणि डीसीरियलायझेशन - 2

import java.io.Serializable;

public class SavedGame implements Serializable {

   private transient TerritoryInfo territoryInfo;
   private ResourceInfo resourceInfo;
   private DiplomacyInfo diplomacyInfo;

   public SavedGame(TerritoryInfo territoryInfo, ResourceInfo resourceInfo, DiplomacyInfo diplomacyInfo) {
       this.territoryInfo = territoryInfo;
       this.resourceInfo = resourceInfo;
       this.diplomacyInfo = diplomacyInfo;
   }

   // ...getters, setters, toString()...
}



import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class Main {

   public static void main(String[] args) throws IOException {

       // Create our object
       TerritoryInfo territoryInfo = new TerritoryInfo("Spain has 6 provinces, Russia has 10 provinces, France has 8 provinces");
       ResourceInfo resourceInfo = new ResourceInfo("Spain has 100 gold, Russia has 80 gold, France has 90 gold");
       DiplomacyInfo diplomacyInfo =  new DiplomacyInfo("France is at war with Russia, Spain has taken a neutral position");


       SavedGame savedGame = new SavedGame(territoryInfo, resourceInfo, diplomacyInfo);

       FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);

       objectOutputStream.writeObject(savedGame);

       objectOutputStream.close();
   }
}


import java.io.*;

public class Main {

   public static void main(String[] args) throws IOException, ClassNotFoundException {

       FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);

       SavedGame savedGame = (SavedGame) objectInputStream.readObject();

       System.out.println(savedGame);

       objectInputStream.close();


   }
}
आणि हा निकाल आहे: SavedGame{territoryInfo=null, resourceInfo=ResourceInfo{info='स्पेनकडे 100 सोने, रशियाकडे 80 सोने, फ्रान्सकडे 90 सोने'}, diplomacyInfo=DiplomacyInfo{info='फ्रान्सचे रशिया, स्पेनशी युद्ध सुरू आहे ने तटस्थ स्थिती घेतली आहे'}} असे म्हटले आहे की, क्षणिक फील्डला कोणते मूल्य नियुक्त केले जाईल या प्रश्नाचे उत्तर आम्हाला मिळाले . हे डीफॉल्ट मूल्य नियुक्त केले आहे. वस्तूंसाठी, हे शून्य आहे . 'हेड-फर्स्ट जावा' या पुस्तकात तुम्ही या विषयावरील एक उत्कृष्ट प्रकरण वाचू शकता, त्याकडे लक्ष द्या :)
टिप्पण्या
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION