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:
- 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.
- Virtual fayl tizimlarini qo'llab-quvvatlash yo'q.
- Havolalarni qo'llab-quvvatlash yo'q.
- 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:
|
|
|
|---|---|
| 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:
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.
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.
Java NIO2 testini ham ishlatamiz. NIO2 fayllarni boshqarishda Java NIO'dan yaxshiroq boshqaruvga ega. Shuning uchun yangilangan kutubxona natijalari shunday farq qiladi:
Endi, 521 MB hajmdagi katta video fayli bilan testni sinab ko'ramiz. Vaziya bir xil: boshqa papkaga nusxalash. Natijalarga qaraylik!
Java IO natijalari:
Java NIO natijasi:
Java NIO faylni 9 marta tezroq bajardi birinchi testda. Takrroriy testlar shunga o'xshash natijalarni ko'rsatdi.
Java NIO2 testini ham sinab ko'ramiz:
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.
GO TO FULL VERSION