जावा आईओ इतना खराब क्यों है?

आईओ (इनपुट और आउटपुट) एपीआई एक जावा एपीआई है जो डेवलपर्स के लिए स्ट्रीम के साथ काम करना आसान बनाता है। मान लीजिए कि हम कुछ डेटा प्राप्त करते हैं (उदाहरण के लिए, पहला नाम, मध्य नाम, अंतिम नाम) और हमें इसे एक फ़ाइल में लिखने की आवश्यकता है - समय आ गया है कि java.io का उपयोग किया जाए

Java.io पुस्तकालय की संरचना

लेकिन Java IO में इसकी कमियां हैं, तो चलिए उनमें से प्रत्येक के बारे में बारी-बारी से बात करते हैं:

  1. इनपुट/आउटपुट के लिए एक्सेस ब्लॉक करना। समस्या यह है कि जब कोई डेवलपर Java IO का उपयोग करके फ़ाइल में कुछ पढ़ने या लिखने की कोशिश करता है, तो वह फ़ाइल को लॉक कर देता है और काम पूरा होने तक उस तक पहुंच को ब्लॉक कर देता है।
  2. वर्चुअल फाइल सिस्टम के लिए कोई समर्थन नहीं।
  3. लिंक के लिए कोई समर्थन नहीं।
  4. बहुत सारे और बहुत सारे चेक किए गए अपवाद।

फ़ाइलों के साथ काम करना हमेशा अपवादों के साथ काम करता है: उदाहरण के लिए, पहले से मौजूद एक नई फ़ाइल बनाने का प्रयास करने से IOException फेंक दी जाएगी । इस स्थिति में, एप्लिकेशन चालू रहना चाहिए और उपयोगकर्ता को सूचित किया जाना चाहिए कि फ़ाइल क्यों नहीं बनाई जा सकी।


try {
	File.createTempFile("prefix", "");
} catch (IOException e) {
	// Handle the IOException
}

/**
 * Creates an empty file in the default temporary-file directory 
 * any exceptions will be ignored. This is typically used in finally blocks. 
 * @param prefix 
 * @param suffix 
 * @throws IOException - If a file could not be created
 */
public static File createTempFile(String prefix, String suffix) 
throws IOException {
...
}

यहाँ हम देखते हैं कि createTempFile मेथड एक IOException को फेंकता है जब फाइल नहीं बनाई जा सकती। इस अपवाद को ठीक से संभाला जाना चाहिए। यदि हम इस विधि को ट्राइ-कैच ब्लॉक के बाहर कॉल करने का प्रयास करते हैं, तो कंपाइलर एक त्रुटि उत्पन्न करेगा और इसे ठीक करने के लिए दो विकल्प सुझाएगा: विधि को ट्राइ-कैच ब्लॉक में लपेटें या वह विधि बनाएं जो File.createTempFile को IOException फेंकती है ( इसलिए इसे उच्च स्तर पर संभाला जा सकता है)।

Java NIO पर पहुंचना और इसकी तुलना Java IO से कैसे की जाती है

Java NIO , या Java नॉन-ब्लॉकिंग I/O (या कभी-कभी Java New I/O) को उच्च-प्रदर्शन I/O संचालन के लिए डिज़ाइन किया गया है।

आइए जावा आईओ विधियों और उन्हें बदलने वाले तरीकों की तुलना करें।

सबसे पहले, जावा आईओ के साथ काम करने के बारे में बात करते हैं :

इनपुटस्ट्रीम क्लास


try(FileInputStream fin = new FileInputStream("C:/codegym/file.txt")){
    System.out.printf("File size: %d bytes \n", fin.available());
    int i=-1;
    while((i=fin.read())!=-1) {
        System.out.print((char)i);
    }   
} catch(IOException ex) {
    System.out.println(ex.getMessage());
}

FileInputStream क्लास फ़ाइल से डेटा पढ़ने के लिए है यह InputStream वर्ग को इनहेरिट करता है और इसलिए इसके सभी तरीकों को लागू करता है। यदि फ़ाइल नहीं खोली जा सकती है, तो FileNotFoundException को फेंक दिया जाता है।

आउटपुटस्ट्रीम क्लास


String text = "Hello world!"; // String to write
try(FileOutputStream fos = new FileOutputStream("C:/codegym/file.txt")){
    // Convert our string into bytes
    byte[] buffer = text.getBytes();
    fos.write(buffer, 0, buffer.length);
    System.out.println("The file has been written");
} catch(IOException ex) {
    System.out.println(ex.getMessage());
}

फ़ाइल में बाइट लिखने के लिए FileOutputStream क्लास । यह OutputStream क्लास से निकला है।

पाठक और लेखक वर्ग

FileReader वर्ग हमें स्ट्रीम से वर्ण डेटा पढ़ने देता है, और FileWriter वर्ग का उपयोग वर्ण स्ट्रीम लिखने के लिए किया जाता है निम्न कोड दिखाता है कि फ़ाइल से कैसे लिखना और पढ़ना है:


        String fileName = "c:/codegym/Example.txt";

        // Create a FileWriter object
        try (FileWriter writer = new FileWriter(fileName)) {

            // Write content to file
            writer.write("This is a simple example\nin which we\nwrite to a file\nand read from a file\n");
            writer.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }

        // Create a FileReader object
        try (FileReader fr = new FileReader(fileName)) {
            char[] a = new char[200]; // Number of characters to read
            fr.read(a); // Read content into an array
            for (char c : a) {
                System.out.print(c); // Display characters one by one
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

अब बात करते हैं Java NIO की :

चैनल

Java IO में उपयोग की जाने वाली धाराओं के विपरीत , चैनल दो-तरफ़ा इंटरफ़ेस है, अर्थात यह पढ़ और लिख सकता है। एक Java NIO चैनल ब्लॉकिंग और नॉन-ब्लॉकिंग मोड दोनों में अतुल्यकालिक डेटा प्रवाह का समर्थन करता है।


RandomAccessFile aFile = new RandomAccessFile("C:/codegym/file.txt", "rw");
FileChannel inChannel = aFile.getChannel();

ByteBuffer buf = ByteBuffer.allocate(100);
int bytesRead = inChannel.read(buf);

while (bytesRead != -1) {
  System.out.println("Read: " + bytesRead);
  buf.flip();
	  while(buf.hasRemaining()) {
	      System.out.print((char) buf.get());
	  }
  buf.clear();
  bytesRead = inChannel.read(buf);
}
aFile.close();

यहाँ हमने एक FileChannel का उपयोग किया । हम फ़ाइल से डेटा पढ़ने के लिए फ़ाइल चैनल का उपयोग करते हैं। फ़ाइल चैनल ऑब्जेक्ट केवल फ़ाइल ऑब्जेक्ट पर getChannel() विधि को कॉल करके बनाया जा सकता है - फ़ाइल चैनल ऑब्जेक्ट को सीधे बनाने का कोई तरीका नहीं है।

FileChannel के अतिरिक्त , हमारे पास अन्य चैनल कार्यान्वयन हैं:

  • FileChannel — फाइलों के साथ काम करने के लिए

  • डेटाग्राम चैनल - यूडीपी कनेक्शन पर काम करने के लिए एक चैनल

  • सॉकेटचैनल — टीसीपी कनेक्शन पर काम करने के लिए एक चैनल

  • ServerSocketChannel में एक सॉकेट चैनल होता है और यह वेब सर्वर के काम करने के तरीके के समान है

कृपया ध्यान दें: FileChannel को नॉन-ब्लॉकिंग मोड में स्विच नहीं किया जा सकता है। Java NIO का नॉन-ब्लॉकिंग मोड आपको एक चैनल से डेटा पढ़ने का अनुरोध करने देता है और केवल वही प्राप्त करता है जो वर्तमान में उपलब्ध है (या अभी तक कोई डेटा उपलब्ध नहीं होने पर कुछ भी नहीं)। उस ने कहा, SelectableChannel और इसके कार्यान्वयन को कनेक्ट () विधि का उपयोग करके गैर-अवरुद्ध मोड में रखा जा सकता है।

चयनकर्ता

Java NIO ने एक थ्रेड बनाने की क्षमता पेश की जो जानता है कि कौन सा चैनल डेटा लिखने और पढ़ने के लिए तैयार है और उस विशेष चैनल को प्रोसेस कर सकता है। यह क्षमता चयनकर्ता वर्ग का उपयोग करके कार्यान्वित की जाती है।

एक चयनकर्ता से चैनल कनेक्ट करना


Selector selector = Selector.open();
channel.configureBlocking(false); // Non-blocking mode
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);

इसलिए हम अपना चयनकर्ता बनाते हैं और इसे SelectableChannel से जोड़ते हैं ।

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

यहां हम उल्लेख करते हैं कि हमारे उदाहरण में SelectionKey संचालन का एक सेट है जिसे एक चैनल पर निष्पादित किया जा सकता है। चयन कुंजी से हमें चैनल की स्थिति का पता चलता है।

चयन कुंजी के प्रकार

  • SelectionKey.OP_CONNECT एक चैनल को दर्शाता है जो सर्वर से जुड़ने के लिए तैयार है।

  • SelectionKey.OP_ACCEPT एक ऐसा चैनल है जो आने वाले कनेक्शनों को स्वीकार करने के लिए तैयार है।

  • SelectionKey.OP_READ एक ऐसे चैनल को दर्शाता है जो डेटा पढ़ने के लिए तैयार है।

  • SelectionKey.OP_WRITE एक ऐसे चैनल को दर्शाता है जो डेटा लिखने के लिए तैयार है।

बफर

आगे की प्रक्रिया के लिए डेटा को बफर में पढ़ा जाता है। एक डेवलपर बफ़र पर आगे और पीछे जा सकता है, जो डेटा को संसाधित करते समय हमें थोड़ा अधिक लचीलापन देता है। उसी समय, हमें यह जांचने की आवश्यकता है कि बफर में सही प्रसंस्करण के लिए आवश्यक डेटा की मात्रा है या नहीं। साथ ही, बफर में डेटा पढ़ते समय सुनिश्चित करें कि आप मौजूदा डेटा को नष्ट नहीं करते हैं जो अभी तक संसाधित नहीं हुआ है।


ByteBuffer buf = ByteBuffer.allocate (2048); 
int bytesRead = channel.read(buf);
buf.flip(); // Change to read mode
while (buf.hasRemaining()) { 
	byte data = buf.get(); // There are methods for primitives 
}

buf.clear(); // Clear the buffer - now it can be reused

बफर के मूल गुण:

मूल गुण
क्षमता बफर आकार, जो सरणी की लंबाई है।
पद डेटा के साथ काम करने के लिए शुरुआती स्थिति।
आप LIMIT संचालन सीमा। रीड ऑपरेशंस के लिए, सीमा डेटा की मात्रा है जिसे पढ़ा जा सकता है, लेकिन राइट ऑपरेशंस के लिए, यह लिखने के लिए उपलब्ध क्षमता या कोटा है।
निशान रीसेट () विधि को कॉल करने पर उस मान का सूचकांक जिस पर स्थिति पैरामीटर रीसेट हो जाएगा ।

अब बात करते हैं कि Java NIO.2 में नया क्या है ।

पथ

पथ फ़ाइल सिस्टम में पथ का प्रतिनिधित्व करता है। इसमें एक फ़ाइल का नाम और निर्देशिकाओं की एक सूची होती है जो इसके पथ को परिभाषित करती है।


Path relative = Paths.get("Main.java");
System.out.println("File: " + relative);
// Get the file system
System.out.println(relative.getFileSystem());

पथ एक स्थिर विधि के साथ एक बहुत ही सरल वर्ग है: get() । यह पूरी तरह सेपारित स्ट्रिंग या यूआरआई से पथ वस्तु प्राप्त करने के लिए बनाया गया था।


Path path = Paths.get("c:\\data\\file.txt");

फ़ाइलें

फ़ाइलें एक उपयोगिता वर्ग है जो हमें सीधे फ़ाइल का आकार प्राप्त करने, फ़ाइलों की प्रतिलिपि बनाने और बहुत कुछ करने देती है।


Path path = Paths.get("files/file.txt");
boolean pathExists = Files.exists(path);

फाइल सिस्टम

FileSystem फ़ाइल सिस्टम को एक इंटरफ़ेस प्रदान करता है। FileSystem विभिन्न वस्तुओं को बनाने के लिए एक कारखाने की तरह काम करता है (पथ,पाथमैचर,फ़ाइलें). यह फाइल सिस्टम में फाइलों और अन्य वस्तुओं तक पहुंचने में हमारी मदद करता है।


try {
      FileSystem filesystem = FileSystems.getDefault();
      for (Path rootdir : filesystem.getRootDirectories()) {
          System.out.println(rootdir.toString());
      }
  } catch (Exception e) {
      e.printStackTrace();
  }

प्रदर्शन का परीक्षण

इस परीक्षण के लिए, आइए दो फाइलें लें। पहली एक छोटी टेक्स्ट फ़ाइल है, और दूसरी एक बड़ी वीडियो है।

हम एक फ़ाइल बनाएंगे और उसमें कुछ शब्द और वर्ण जोड़ेंगे:

% स्पर्श text.txt

हमारी फाइल मेमोरी में कुल 42 बाइट्स रखती है:

अब कोड लिखते हैं जो हमारी फाइल को एक फोल्डर से दूसरे फोल्डर में कॉपी करेगा। आइए IO और NIO और NIO.2 की गति की तुलना करने के लिए छोटी और बड़ी फाइलों पर इसका परीक्षण करें

कॉपी करने के लिए कोड, Java IO का उपयोग करके लिखा गया है :


public static void main(String[] args) {
        long currentMills = System.currentTimeMillis();
        long startMills = currentMills;
        File src = new File("/Users/IdeaProjects/testFolder/text.txt");
        File dst = new File("/Users/IdeaProjects/testFolder/text1.txt");
        copyFileByIO(src, dst);
        currentMills = System.currentTimeMillis();
        System.out.println("Execution time in milliseconds: " + (currentMills - startMills));
    }

    public static void copyFileByIO(File src, File dst) {
        try(InputStream inputStream = new FileInputStream(src);
            OutputStream outputStream = new FileOutputStream(dst)){

            byte[] buffer = new byte[1024];
            int length;
            // Read data into a byte array and then output to an OutputStream
            while((length = inputStream.read(buffer)) > 0) {
                outputStream.write(buffer, 0, length);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

और यहाँ Java NIO के लिए कोड है :


public static void main(String[] args) {
        long currentMills = System.currentTimeMillis();
        long startMills = currentMills;

        File src = new File("/Users/IdeaProjects/testFolder/text.txt");
        File dst = new File("/Users/IdeaProjects/testFolder/text2.txt");
        // Code for copying using NIO
        copyFileByChannel(src, dst);
        currentMills = System.currentTimeMillis();
        System.out.println("Execution time in milliseconds: " + (currentMills - startMills));
    }

    public static void copyFileByChannel(File src, File dst) {
        // 1. Get a FileChannel for the source file and the target file
        try(FileChannel srcFileChannel  = new FileInputStream(src).getChannel();
            FileChannel dstFileChannel = new FileOutputStream(dst).getChannel()){
            // 2. Size of the current FileChannel
            long count = srcFileChannel.size();
            while(count > 0) {
                /**=============================================================
                 * 3. Write bytes from the source file's FileChannel to the target FileChannel
                 * 1. srcFileChannel.position(): the starting position in the source file, cannot be negative
                 * 2. count: the maximum number of bytes transferred, cannot be negative
                 * 3. dstFileChannel: the target file
                 *==============================================================*/
                long transferred = srcFileChannel.transferTo(srcFileChannel.position(),
                        count, dstFileChannel);
                // 4. After the transfer is complete, change the position of the original file to the new one
                srcFileChannel.position(srcFileChannel.position() + transferred);
                // 5. Calculate how many bytes are left to transfer
                count -= transferred;
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

जावा NIO.2 के लिए कोड :


public static void main(String[] args) {
  long currentMills = System.currentTimeMillis();
  long startMills = currentMills;

  Path sourceDirectory = Paths.get("/Users/IdeaProjects/testFolder/test.txt");
  Path targetDirectory = Paths.get("/Users/IdeaProjects/testFolder/test3.txt");
  Files.copy(sourceDirectory, targetDirectory);

  currentMills = System.currentTimeMillis();
  System.out.println("Execution time in milliseconds: " + (currentMills - startMills));
}

चलिए छोटी फाइल से शुरू करते हैं।

Java IO का निष्पादन समय औसतन 1 मिलीसेकंड था। कई बार परीक्षण चलाने से हमें 0 से 2 मिलीसेकंड तक परिणाम मिलते हैं।

मिलीसेकंड में निष्पादन समय: 1

Java NIO का निष्पादन समय बहुत लंबा है। औसत समय 11 मिलीसेकंड है। परिणाम 9 से 16 के बीच थे। ऐसा इसलिए है क्योंकि जावा आईओ हमारे ऑपरेटिंग सिस्टम से अलग तरीके से काम करता है। IO फाइलों को एक-एक करके मूव और प्रोसेस करता है, लेकिन ऑपरेटिंग सिस्टम डेटा को एक बड़े हिस्से में भेजता है। NIO ने खराब प्रदर्शन किया क्योंकि यह बफर-ओरिएंटेड है, IO की तरह स्ट्रीम-ओरिएंटेड नहीं है ।

मिलीसेकंड में निष्पादन समय: 12

और चलिए Java NIO.2 के लिए अपना परीक्षण भी चलाते हैं । NIO.2 ने Java NIO की तुलना में फ़ाइल प्रबंधन में सुधार किया है । यही कारण है कि अद्यतन पुस्तकालय ऐसे भिन्न परिणाम उत्पन्न करता है:

मिलीसेकंड में निष्पादन समय: 3

आइए अब हमारी बड़ी फ़ाइल, एक 521 एमबी वीडियो का परीक्षण करने का प्रयास करें। कार्य बिल्कुल वही होगा: फ़ाइल को किसी अन्य फ़ोल्डर में कॉपी करें। देखना!

जावा आईओ के लिए परिणाम :

मिलीसेकंड में निष्पादन समय: 1866

और यहाँ Java NIO का परिणाम है :

मिलीसेकंड में निष्पादन समय: 205

Java NIO ने पहले टेस्ट में 9 गुना तेजी से फाइल को हैंडल किया। दोहराए गए परीक्षणों ने लगभग समान परिणाम दिखाए।

और हम Java NIO.2 पर अपना परीक्षण भी आजमाएंगे :

मिलीसेकंड में निष्पादन समय: 360

यह परिणाम क्यों? केवल इसलिए कि हमारे लिए उनके बीच प्रदर्शन की तुलना करने का कोई मतलब नहीं है, क्योंकि वे अलग-अलग उद्देश्यों की पूर्ति करते हैं। NIO अधिक अमूर्त निम्न-स्तर I/O है, जबकि NIO.2 फ़ाइल प्रबंधन की ओर उन्मुख है।

सारांश

हम सुरक्षित रूप से कह सकते हैं कि ब्लॉक के अंदर उपयोग करने के लिए फ़ाइलों के साथ काम करते समय जावा एनआईओ काफी अधिक कुशल है। एक और प्लस यह है कि NIO लाइब्रेरी को दो भागों में बांटा गया है: एक फाइलों के साथ काम करने के लिए, दूसरा नेटवर्क के साथ काम करने के लिए।

फ़ाइलों के साथ काम करने के लिए Java NIO.2 का नया API कई उपयोगी सुविधाएँ प्रदान करता है:

  • पथ का उपयोग करते हुए कहीं अधिक उपयोगी फ़ाइल सिस्टम एड्रेसिंग ,

  • कस्टम फ़ाइल सिस्टम प्रदाता का उपयोग करके ZIP फ़ाइलों की हैंडलिंग में काफी सुधार हुआ है,

  • विशेष फ़ाइल विशेषताओं तक पहुंच,

  • कई सुविधाजनक तरीके, जैसे कि एक ही स्टेटमेंट के साथ एक पूरी फाइल को पढ़ना, एक ही स्टेटमेंट के साथ एक फाइल को कॉपी करना, आदि।

यह सब फाइलों और फाइल सिस्टम के बारे में है, और यह सब काफी उच्च स्तर का है।

आज की वास्तविकता यह है कि जावा एनआईओ फाइलों के साथ लगभग 80-90% काम करता है, हालांकि जावा आईओ का हिस्सा अभी भी महत्वपूर्ण है।

💡 PS ये परीक्षण MacBook Pro 14" 16/512 पर चलाए गए थे। परीक्षण के परिणाम ऑपरेटिंग सिस्टम और वर्कस्टेशन विनिर्देशों के आधार पर भिन्न हो सकते हैं।