জাভা আইও এত খারাপ কেন?

IO (ইনপুট এবং আউটপুট) API হল একটি Java API যা ডেভেলপারদের জন্য স্ট্রিমগুলির সাথে কাজ করা সহজ করে তোলে। ধরা যাক আমরা কিছু ডেটা পেয়েছি (উদাহরণস্বরূপ, প্রথম নাম, মধ্য নাম, শেষ নাম) এবং আমাদের এটি একটি ফাইলে লিখতে হবে — 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 Non-Blocking I/O (বা কখনও কখনও Java New I/O) উচ্চ-পারফরম্যান্স I/O অপারেশনের জন্য ডিজাইন করা হয়েছে।

আসুন Java IO পদ্ধতি এবং সেগুলি প্রতিস্থাপন করা পদ্ধতিগুলির তুলনা করি।

প্রথমে, আসুন Java IO এর সাথে কাজ করার বিষয়ে কথা বলি :

ইনপুটস্ট্রিম ক্লাস


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 ক্লাস একটি ফাইল থেকে ডেটা পড়ার জন্য এটি ইনপুটস্ট্রিম ক্লাসের উত্তরাধিকারী এবং তাই এর সমস্ত পদ্ধতি প্রয়োগ করে। যদি ফাইলটি খোলা না যায়, একটি 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 ক্লাস । এটি আউটপুট স্ট্রিম ক্লাস থেকে উদ্ভূত ।

পাঠক এবং লেখক ক্লাস

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();
        }

এখন জাভা NIO সম্পর্কে কথা বলা যাক :

চ্যানেল

Java IO তে ব্যবহৃত স্ট্রিমগুলির বিপরীতে , চ্যানেল হল দ্বি-মুখী ইন্টারফেস, অর্থাৎ, এটি পড়তে এবং লিখতে উভয়ই পারে। একটি জাভা 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 ছাড়াও , আমাদের অন্যান্য চ্যানেল বাস্তবায়ন রয়েছে:

  • ফাইলচ্যানেল — ফাইল নিয়ে কাজ করার জন্য

  • DatagramChannel — একটি UDP সংযোগের মাধ্যমে কাজ করার জন্য একটি চ্যানেল

  • SocketChannel — একটি TCP সংযোগের মাধ্যমে কাজ করার জন্য একটি চ্যানেল

  • ServerSocketChannel- এ একটি SocketChannel রয়েছে এবং একটি ওয়েব সার্ভার কীভাবে কাজ করে তার অনুরূপ

অনুগ্রহ করে মনে রাখবেন: ফাইলচ্যানেলকে নন-ব্লকিং মোডে স্যুইচ করা যাবে না। জাভা NIO- এর নন-ব্লকিং মোড আপনাকে একটি চ্যানেল থেকে ডেটা পড়ার অনুরোধ করতে দেয় এবং বর্তমানে যা পাওয়া যায় শুধুমাত্র তা গ্রহণ করতে দেয় (অথবা এখনও কোনও ডেটা উপলব্ধ না থাকলে কিছুই নয়)। এটি বলেছে, SelectableChannel এবং এর বাস্তবায়ন সংযোগ() পদ্ধতি ব্যবহার করে নন-ব্লকিং মোডে রাখা যেতে পারে ।

নির্বাচক

জাভা NIO একটি থ্রেড তৈরি করার ক্ষমতা চালু করেছে যা জানে কোন চ্যানেল ডেটা লিখতে এবং পড়তে প্রস্তুত এবং সেই নির্দিষ্ট চ্যানেলটি প্রক্রিয়া করতে পারে। এই ক্ষমতা নির্বাচক শ্রেণী ব্যবহার করে প্রয়োগ করা হয়.

একটি নির্বাচকের সাথে চ্যানেল সংযোগ করা হচ্ছে


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

তাই আমরা আমাদের নির্বাচক তৈরি করি এবং এটি একটি নির্বাচনযোগ্য চ্যানেলের সাথে সংযুক্ত করি ।

একটি নির্বাচকের সাথে ব্যবহার করার জন্য, একটি চ্যানেল অবশ্যই নন-ব্লকিং মোডে থাকতে হবে৷ এর মানে হল যে আপনি একটি নির্বাচকের সাথে FileChannel ব্যবহার করতে পারবেন না , যেহেতু FileChannel নন-ব্লকিং মোডে রাখা যাবে না। কিন্তু সকেট চ্যানেল ঠিক কাজ করবে।

এখানে উল্লেখ করা যাক যে আমাদের উদাহরণে SelectionKey হল অপারেশনের একটি সেট যা একটি চ্যানেলে সম্পাদিত হতে পারে। নির্বাচন কী আমাদের একটি চ্যানেলের অবস্থা জানতে দেয়।

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

বাফারের মৌলিক বৈশিষ্ট্য:

মৌলিক গুণাবলী
ক্ষমতা বাফারের আকার, যা অ্যারের দৈর্ঘ্য।
অবস্থান ডেটা নিয়ে কাজ করার জন্য শুরুর অবস্থান।
সীমা অপারেটিং সীমা। পঠন ক্রিয়াকলাপের জন্য, সীমা হল ডেটার পরিমাণ যা পড়া যায়, কিন্তু লেখার ক্রিয়াকলাপের জন্য, এটি লেখার জন্য উপলব্ধ ক্ষমতা বা কোটা।
চিহ্ন যখন reset() মেথড কল করা হয় তখন যে মানের ইনডেক্স পজিশন প্যারামিটার রিসেট করা হবে ।

এখন জাভা NIO.2- তে নতুন কি আছে সে সম্পর্কে একটু কথা বলা যাক ।

পথ

পাথ ফাইল সিস্টেমের একটি পাথ প্রতিনিধিত্ব করে। এটিতে একটি ফাইলের নাম এবং ডিরেক্টরিগুলির একটি তালিকা রয়েছে যা এটির পথ নির্ধারণ করে।


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

পাথগুলি একটি একক স্ট্যাটিক পদ্ধতি সহ একটি খুব সাধারণ ক্লাস: get() । এটি শুধুমাত্র পাস করা স্ট্রিং বা URI থেকে একটি পাথ অবজেক্ট পেতে তৈরি করা হয়েছিল


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

নথি পত্র

ফাইল হল একটি ইউটিলিটি ক্লাস যা আমাদের সরাসরি একটি ফাইলের আকার, ফাইল কপি এবং আরও অনেক কিছু পেতে দেয়।


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

নথি ব্যবস্থা

ফাইল সিস্টেম ফাইল সিস্টেমে একটি ইন্টারফেস প্রদান করে। ফাইলসিস্টেম বিভিন্ন বস্তু তৈরির জন্য একটি কারখানার মতো কাজ করে (পথ,পাথম্যাচার,নথি পত্র) এটি আমাদের ফাইল সিস্টেমে ফাইল এবং অন্যান্য বস্তু অ্যাক্সেস করতে সাহায্য করে।


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 এর গতির তুলনা করার জন্য ছোট এবং বড় ফাইলগুলিতে এটি পরীক্ষা করা যাক

কপি করার জন্য কোড, জাভা আইও ব্যবহার করে লেখা :


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();
        }
    }

এবং এখানে জাভা 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

জাভা NIO এর জন্য কার্যকর করার সময় অনেক বেশি। গড় সময় 11 মিলিসেকেন্ড। ফলাফল 9 থেকে 16 পর্যন্ত। এর কারণ হল জাভা আইও আমাদের অপারেটিং সিস্টেমের থেকে ভিন্নভাবে কাজ করে। IO ফাইলগুলিকে একের পর এক স্থানান্তরিত করে এবং প্রক্রিয়া করে, কিন্তু অপারেটিং সিস্টেম একটি বড় অংশে ডেটা পাঠায়। NIO খারাপভাবে পারফর্ম করেছে কারণ এটি বাফার-ভিত্তিক, IO- এর মতো স্ট্রিম-ভিত্তিক নয় ।

মিলিসেকেন্ডে কার্যকর করার সময়: 12

এবং জাভা NIO.2 এর জন্য আমাদের পরীক্ষা চালানো যাক । NIO.2 জাভা NIO- এর তুলনায় ফাইল ব্যবস্থাপনা উন্নত করেছে । এই কারণেই আপডেট করা লাইব্রেরি এমন বিভিন্ন ফলাফল তৈরি করে:

মিলিসেকেন্ডে সঞ্চালনের সময়: 3

এখন আমাদের বড় ফাইল, একটি 521 এমবি ভিডিও পরীক্ষা করার চেষ্টা করা যাক। কাজটি ঠিক একই হবে: ফাইলটি অন্য ফোল্ডারে অনুলিপি করুন। দেখো!

Java IO- এর জন্য ফলাফল :

মিলিসেকেন্ডে কার্যকর করার সময়: 1866

এবং এখানে জাভা NIO- এর ফলাফল রয়েছে :

মিলিসেকেন্ডে কার্যকর করার সময়: 205

জাভা NIO প্রথম পরীক্ষায় 9 গুণ দ্রুত ফাইলটি পরিচালনা করেছে। বারবার পরীক্ষা প্রায় একই ফলাফল দেখিয়েছে.

এবং আমরা জাভা NIO.2 এ আমাদের পরীক্ষাও চেষ্টা করব :

মিলিসেকেন্ডে সঞ্চালনের সময়: 360

কেন এই ফলাফল? কেবল কারণ তাদের মধ্যে পারফরম্যান্সের তুলনা করা আমাদের পক্ষে খুব বেশি অর্থপূর্ণ নয়, যেহেতু তারা বিভিন্ন উদ্দেশ্যে পরিবেশন করে। NIO হল আরও বিমূর্ত নিম্ন-স্তরের I/O, যখন NIO.2 ফাইল পরিচালনার দিকে ভিত্তিক।

সারসংক্ষেপ

আমরা নিরাপদে বলতে পারি যে ব্লকের ভিতরে ব্যবহার করার জন্য ফাইলগুলির সাথে কাজ করার সময় Java NIO উল্লেখযোগ্যভাবে আরও দক্ষ। আরেকটি প্লাস হল NIO লাইব্রেরি দুটি ভাগে বিভক্ত: একটি ফাইলের সাথে কাজ করার জন্য, আরেকটি নেটওয়ার্কের সাথে কাজ করার জন্য।

ফাইলগুলির সাথে কাজ করার জন্য Java NIO.2 এর নতুন API অনেকগুলি দরকারী বৈশিষ্ট্য অফার করে:

  • পাথ ব্যবহার করে অনেক বেশি দরকারী ফাইল সিস্টেম অ্যাড্রেসিং ,

  • একটি কাস্টম ফাইল সিস্টেম প্রদানকারী ব্যবহার করে জিপ ফাইলগুলির হ্যান্ডলিং উল্লেখযোগ্যভাবে উন্নত,

  • বিশেষ ফাইল বৈশিষ্ট্য অ্যাক্সেস,

  • অনেক সুবিধাজনক পদ্ধতি, যেমন একটি একক বিবৃতি সহ একটি সম্পূর্ণ ফাইল পড়া, একটি একক বিবৃতি সহ একটি ফাইল অনুলিপি করা ইত্যাদি।

এটা সব ফাইল এবং ফাইল সিস্টেম সম্পর্কে, এবং এটা সব বেশ উচ্চ স্তরের.

আজকের বাস্তবতা হল জাভা NIO ফাইলগুলির সাথে মোটামুটি 80-90% কাজ করে, যদিও Java IO এর শেয়ার এখনও উল্লেখযোগ্য।

💡 PS এই পরীক্ষাগুলি একটি MacBook Pro 14" 16/512-এ চালানো হয়েছিল৷ অপারেটিং সিস্টেম এবং ওয়ার্কস্টেশন স্পেসিফিকেশনের উপর ভিত্তি করে পরীক্ষার ফলাফলগুলি আলাদা হতে পারে৷