IO va NIO

All lectures for UZ purposes
Daraja, Dars
Mavjud

Java IO nimasi bilan yomon?

IO (Input & Output) API — bu Java API, ishlab chiquvchilarga oqimlar bilan ishlashni osonlashtiradi. Masalan, biz biror ma'lumot olganmiz (masalan, familiya, ism va otasining ismi) va uni faylga yozishimiz kerak — mana shu vaqtda java.io foydalansa bo'ladi.

java.io kutubxonasi tuzilishi

Lekin Java IOning o'ziga xos kamchiliklari bor, shuning uchun ularni birma-bir ko'rib chiqamiz:

  1. Ma'lumotlar kiritish/chiqarishda to'suvchi kirish. Muammo shundaki, ishlab chiquvchi faylni o'qish yoki unga nimadir yozishga harakat qilganida, Java IOdan foydalanganda, u faylni bloklaydi va uni bajarish tugamaguncha kirish imkoni yo'q.
  2. Virtual fayl tizimlarini qo'llab-quvvatlash yo'q.
  3. Havolalarni qo'llab-quvvatlash yo'q.
  4. Judayam ko'p checked istisnolar.

Fayllar bilan ishlash doim istisnolar bilan ishlashni ham o'z ichiga oladi: masalan, yangi fayl yaratishga urinish, agar u allaqachon mavjud bo'lsa, IOException yuzaga keladi. Bu holda ilova ishini davom ettirish kerak va foydalanuvchi uchun faylni yaratib bo'lmasligi sababi haqida xabar berilishi kerak.


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

/**
 * Ish joyidagi vaqtinchalik fayl yaratadi
 * istisnolar e'tiborga olinmaydi. Bu odatda finally bloklarida ishlatiladi.
 * @param prefix
 * @param suffix
 * @throws IOException - Faylni yaratib bo'lmasa, bu istisnoni chiqaradi
 */
public static File createTempFile(String prefix, String suffix) 
throws IOException {
...
}

Bu yerda biz createTempFile metodi faylni yaratib bo'lmaganda IOException chiqarishini ko'rishimiz mumkin. Bu istisno tegishli tarzda amalga oshirilishi kerak. Agar bu metodni try-catch blokidan tashqarida chaqirishga harakat qilsak, kompilyator xato chiqaradi va bizga ikki variantni taklif qiladi: metodni try-catch blokiga o'rash yoki File.createTempFile chaqirilgan metodni IOException istisnosini chiqaradigan qilib qilish (yuqori darajada ko'rib chiqish uchun).

Java NIO'ga kelish va Java IO bilan taqqoslash

Java NIO, yoki Java Non-blocking I/O (ba'zida — Java New I/O, “yangi kiritish/chiqarish”) yuqori samaradorlikdagi kiritish/chiqarish operatsiyalarini amalga oshirish uchun mo'ljallangan.

Keling, Java IO metodlarini ularga almashtirilganlari bilan taqqoslab ko'raylik.

Birinchi bo'lib, Java IO bilan ishlashni gaplashaylik:

InputStream Klasi


try(FileInputStream fin = new FileInputStream("C:/javarush/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 klasi fayldan ma'lumotlarni o'qish uchun mo'ljallangan. U InputStream klasining vorisi bo'lgani uchun, uning barcha metodlarini amalga oshiradi. Agar faylni ochib bo'lmasa, FileNotFoundException istisnosi yuzaga keladi.

OutputStream Klasi


String text = "Hello world!"; // yozuv uchun qator
try(FileOutputStream fos = new FileOutputStream("C:/javarush/file.txt")){
    // qatorimizni baytlarga o'tkazamiz
    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 klasi faylga baytlarni yozish uchun mo'ljallangan. U OutputStream klasining vorisi.

Reader va Writer Klaslari

FileReader klasi oqimlardan belgi ma'lumotlarini o'qishga imkon beradi, FileWriter esa simvolli oqimlarni yozish uchun ishlatiladi. Fayldan o'qish va unga yozish amalga oshirilishini quyida keltiramiz:


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

        // FileWriter ob'ektini yaratish
        try (FileWriter writer = new FileWriter(fileName)) {

            // Faylga mazmuni yozish
            writer.write("Bu oddiy misol,\n unda biz amalga oshiramiz\n Java tilidan foydalanib\n faylga yozish\n va fayldan o'qish\n");
            writer.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }

        // FileReader ob'ektini yaratish
        try (FileReader fr = new FileReader(fileName)) {
            char[] a = new char[200];// O'qiladigan belgilar soni
            fr.read(a);   // Mazmunini massivga o'qish
            for (char c : a) {
                System.out.print(c); // Belgilarini bir-biridan chiqarish
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

Endi Java NIO haqida gaplashamiz:

Channel

Java IO'da ishlatiladigan oqimlarga qarama-qarshi ravishda, Channel ikki tomonlama, ya'ni u o'qishi va yozishi mumkin. Java NIO kanali asinxron ma'lumot oqimini blokli va bloksiz rejimda qo'llab-quvvatlaydi.


RandomAccessFile aFile = new RandomAccessFile("C:/javarush/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();

Bu yerda biz FileChannel'ni amalga oshirdik. Fayldan ma'lumotlarni o'qish uchun fayl kanalidan foydalanamiz. Fayl kanali ob'ekti faqat fayl ob'ekti uchun getChannel() metodini chaqirish bilan yaratilishi mumkin, chunki biz fayl kanali ob'ektini to'g'ridan-to'g'ri yarata olmaymiz.

FileChannel'dan tashqari bizda boshqa kanal implementatsiyalari ham bor:

  • FileChannel — fayllar bilan ishlash

  • DatagramChannel — UDP-aloqada ishlash uchun kanal

  • SocketChannel — TCP-aloqada ishlash uchun kanal

  • ServerSocketChannel o'zida SocketChannel'ni saqlaydi va veb-server bilan ishlash prinsipiga o'xshaydi

Diqqat qiling: FileChannel'ni bloklamaydigan rejimga tushirib bo'lmaydi. Java NIOning bloklamaydigan rejimi kanal (channel)dan o'qiladigan ma'lumotlarni so'rash va hozirgi paytda mavjud bo'lganlarni olish yoki hech narsa olmaslik imkonini beradi agar mavjud ma'lumotlar hozircha bo'lmasa. Shu bilan birga SelectableChannel va uning implementatsiyalari connect() metodi bilan bloklamaydigan rejimga qo'yilishi mumkin.

Selector

Java NIO'da bir kanal tayyorligini biladigan va bu kanalni ishlab chiqadigan oqim yaratish imkoniyati paydo bo'ldi. Bu imkoniyat Selector klasi yordamida amalga oshiriladi.

Kanal va selektor o'rtasidagi aloqaning amalga oshirilishi


Selector selector = Selector.open();
channel.configureBlocking(false); // bloklamaydigan rejim
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);

Shunday qilib, biz o'zimizning Selectorni yaratyapmiz va uni SelectableChannel bilan bog'lanyapmiz.

Selektor bilan ishlatish uchun kanal bloklamaydigan rejimda bo'lishi kerak. Bu FileChannelni selektor bilan ishlata olmasligingizni anglatadi, chunki FileChannel bloklamaydigan rejimga o'zgartirilishi mumkin emas. Socket kanallar yaxshi ishlaydi.

Bu yerda bizning misolimizda SelectionKey — kanal bilan bajariladigan operatsiyalar to'plamidir. Biz kanal holatini tanlash tugmasi yordamida bilib olishimiz mumkin.

SelectionKey turlari:

  • SelectionKey.OP_CONNECT — serverga ulanishga tayyor kanal.

  • SelectionKey.OP_ACCEPT — kiruvchi ulanishlarni qabul qilishga tayyor kanal.

  • SelectionKey.OP_READ — ma'lumotlarni o'qishga tayyor kanal.

  • SelectionKey.OP_WRITE — ma'lumotlarni yozishga tayyor kanal.

Buffer

Ma'lumotlar keyingi ishlov berish uchun buffergacha o'qiladi. Dasturchi bufferni oldinga va orqaga harakat qila oladi, bu esa ma'lumotlarni ishlashda bizga biroz ko'proq moslashuvchanlik beradi. Shu bilan birga, bufferni to'g'ri ishlash uchun zarur bo'lgan ma'lumotlar hajmi bor yoki yo'qligini tekshirish kerak. Shuningdek, buffergacha ma'lumotlarni o'qishda hali ishlov berilmagan ma'lumotlarni yo'q qilmaslik uchun nazorat qilishni unutmaslik kerak.


ByteBuffer buf = ByteBuffer.allocate (2048); 
int bytesRead = channel.read(buf);
buf.flip(); // o'qish rejimiga o'tkazamiz
while (buf.hasRemaining()) { 
    byte data = buf.get(); // primitivlar uchun metodlar mavjud
}

buf.clear(); // tozalandik va yana qayta ishlatish mumkin

Bufferning asosiy xususiyatlari:

Asosiy atributlar
capacity Buffer hajmi, bu esa massivning uzunligi.
position Ma'lumotlar bilan ishlash boshlanish poziyasi.
limit Amaliy limit. O'qish operatsiyalari uchun cheklov operatsion rejimga joylashtirilishi mumkin bo'lgan ma'lumotlar hajmi, yozish operatsiyalari uchun esa pastda ko'rsatilgan hajm yoki yozish uchun mavjud kvota.
mark position parametrii chaqirilganda qaytarilganda reset() metodidan oldingi qiymatni belgisi.

Endi Java NIO2da nimalar yangilanganligi haqida gaplashamiz.

Path

Path fayl tizimida yo'lni ifodalaydi. U fayl nomini va unga yo'lni aniqlaydigan kataloglar ro'yxatini o'z ichiga oladi.


Path relative = Paths.get("Main.java");
System.out.println("Fayl: " + relative);
//fayl tizimini olish
System.out.println(relative.getFileSystem());

Paths — bu faqat bitta statik metod get() bilan juda oddiy klas. Uni yaratishdan maqsad, kiruvchi satrdan yoki URIdan Path turidagi ob'ektni olish.


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

Files

Files — bu utilitar klas, biz unga to'g'ridan-to'g'ri fayl hajmini olishimiz, nusxalashimiz va boshqalarni amalga oshirishimiz mumkin.


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

FileSystem

FileSystem fayl tizimiga interfeysni taqdim etadi. Fayl tizimi turli ob'ektlarni (Path, PathMatcher, Files) yaratishga xizmat qiladi. Ushbu ob'ektlar yordamida fayllarga va boshqa ob'ektlarga fayl tizimida kirish olish mumkin.


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

Performance Testi

Bu test uchun ikkita fayl olamiz. Birinchi — kichik matnli fayl, ikkinchi — katta video.

Fayl yaratamiz va unga bir oz so'zlar va belgilar qo'shamiz:

% touch text.txt

Natijada, faylimiz xotirada 42 bayt joy egallaydi:

Endi faylimizni bir katalogdan boshqasiga nusxalaydigan kod yozamiz. Bu kodni kichik va katta fayllarda sinchiklab ko'rib chiqamiz, shuning bilan IO, NIO va NIO2 tezligini taqqoslaymiz.

Java IOda yozilgan nusxalash kodi:


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("Bajarilish vaqti millisekundlarda: " + (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;
            // Ma'lumotlarni baytli massivga o'qish va OutStreamga chiqarish
            while((length = inputStream.read(buffer)) > 0){
                outputStream.write(buffer, 0, length);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

Va Java NIO uchun kod:


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");
        // nio nusxasi
        copyFileByChannel(src, dst);
        currentMills = System.currentTimeMillis();
        System.out.println("Bajarilish vaqti millisekundlarda: " + (currentMills - startMills));
    }

    public static void copyFileByChannel(File src, File dst){
        // 1. Manba fayl va maqsad faylni olish uchun FileChannel
        try(FileChannel srcFileChannel  = new FileInputStream(src).getChannel();
            FileChannel dstFileChannel = new FileOutputStream(dst).getChannel()){
            // 2. Joriy FileChannel hajmi
            long count = srcFileChannel.size();
            while(count > 0){
                /**=============================================================
                 * 3. Manba fayldan maqsad faylga FileChannel orqali baytlarni yozish
                 * 1. srcFileChannel.position (): manba faylda boshlanish pozitsiyasi manfiy bo'lmasligi kerak
                 * 2. count: o'tkazilgan maksimal baytlar soni, manfiy bo'lmasligi kerak
                 * 3. dstFileChannel: maqsad fayl
                 *==============================================================*/
                long transferred = srcFileChannel.transferTo(srcFileChannel.position(),
                        count, dstFileChannel);
                // 4. Transfer tugagandan so'ng, boshlanish pozitsiyasini yangi joyga o'zgartirish
                srcFileChannel.position(srcFileChannel.position() + transferred);
                // 5. Qancha baytni transfer qilish kerak bo'lganini hisoblash
                count -= transferred;
            }

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

Java NIO2 kodi:


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("Bajarilish vaqti millisekundlarda: " + (currentMills - startMills));
}

Kichik fayldan boshlaymiz.

Java IO orqali bajarilish vaqti o'rtacha 1 millisekundni tashkil etdi. Testni bir necha marta ishlatib, 0 dan 2 millisekundgacha natija oldik.

Bajarilish vaqti millisekundlarda: 1

Java NIO orqali bajarilish vaqti ancha katta. O'rtacha vaqt — 11 millisekund. Natijalar 9 dan 16 gacha edi. Bu Java IO bizning operatsion tizimimiz kabi ishlamasligi bilan bog'liq. IO fayllarni birma-bir ko'chiradi va ishlaydi, operatsion tizim esa ma'lumotlarni katta shaklda yuboradi. NIO yaxshi natija bermadi, chunki u oqimga emas, balki buferga qaratilgan.

Bajarilish vaqti millisekundlarda: 12

Java NIO2 testini ham ishlatamiz. NIO2 fayllarni boshqarishda Java NIO'dan yaxshiroq boshqaruvga ega. Shuning uchun yangilangan kutubxona natijalari shunday farq qiladi:

Bajarilish vaqti millisekundlarda: 3

Endi, 521 MB hajmdagi katta video fayli bilan testni sinab ko'ramiz. Vaziya bir xil: boshqa papkaga nusxalash. Natijalarga qaraylik!

Java IO natijalari:

Bajarilish vaqti millisekundlarda: 1866

Java NIO natijasi:

Bajarilish vaqti millisekundlarda: 205

Java NIO faylni 9 marta tezroq bajardi birinchi testda. Takrroriy testlar shunga o'xshash natijalarni ko'rsatdi.

Java NIO2 testini ham sinab ko'ramiz:

Bajarilish vaqti millisekundlarda: 360

Nima uchun bunday natija? Oddiygina shuning uchun, ular turli maqsadlarga xizmat qilgani uchun ularning samaradorligini taqqoslashning mantiqi yo'q. NIO ma'lumotlarni kiritish-chiqarishni yuqori abstrakt past darajada amalga oshiradi, NIO2 esa fayllarni boshqarishga qararangan.

Xulosa

Java NIO fayllar bilan ishlashda samaradorlikni sezilarli darajada oshiradi, chunki u bloklar ichida ishlaydi. Yana bir afzalligi shundaki, NIO kutubxonasi ikki qismga bo'linadi: biri fayllar bilan ishlash uchun, ikkinchisi esa tarmoqqa ishlash uchun.

Java NIO2da fayllar bilan ishlash uchun ishlatiladigan yangi API juda ko'p foydali funksiyalar taklif etadi:

  • fayl tizimini belgilashning ancha foydali ikki tonli usuli — Path,

  • ZIP-fayllar bilan ishlashning sezilarli darajada yaxshilangan boshqaruvi,

  • faylning maxsus atributlariga kirish,

  • masalan, butun faylni bitta komanda bilan o'qish, bitta komanda bilan faylni nusxalash kabi ko'plab qulay metodlar taqdim etilgan

Bularning barchasi fayl va fayl tizimi bilan bog'liq va barchasi yuqori darajadir.

Zamonaviy sharoitlarda Java NIO fayllar bilan ishlashda taxminan 80-90% tashkil etadi, Java IO hissasi ham hali muhim.

💡 P.S. Testlar MacBook Pro 14’ 16/512da o'tkazildi. Test natijalari operatsion tizim va dasturchining ish mashinasi parametrlariga qarab farq qilishi mumkin.

Izohlar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION