เหตุใด Java IO จึงแย่
API IO (อินพุตและเอาต์พุต) เป็น Java API ที่ช่วยให้นักพัฒนาทำงานกับสตรีมได้ง่าย สมมติว่าเราได้รับข้อมูลบางอย่าง (เช่น ชื่อ ชื่อกลาง นามสกุล) และเราจำเป็นต้องเขียนลงในไฟล์ — ถึงเวลาแล้วที่จะใช้java.io

โครงสร้างของไลบรารี java.io
แต่Java IOก็มีข้อเสีย ดังนั้นเรามาพูดถึงแต่ละข้อกัน:
- การบล็อกการเข้าถึงอินพุต/เอาต์พุต ปัญหาคือเมื่อนักพัฒนาพยายามอ่านหรือเขียนบางอย่างลงในไฟล์โดยใช้Java IOมันจะล็อกไฟล์และบล็อกการเข้าถึงจนกว่างานจะเสร็จสิ้น
- ไม่รองรับระบบไฟล์เสมือน
- ไม่รองรับลิงค์
- ข้อยกเว้นที่ตรวจสอบจำนวนมากและมากมาย
การทำงาน กับไฟล์เกี่ยวข้องกับการทำงานโดยมีข้อยกเว้นเสมอ ตัวอย่างเช่น การพยายามสร้างไฟล์ใหม่ที่มีอยู่แล้วจะโยน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เมื่อไม่สามารถสร้างไฟล์ได้ ข้อยกเว้นนี้ต้องได้รับการจัดการอย่างเหมาะสม หากเราพยายามเรียกใช้เมธอดนี้นอก บล็อก try-catchคอมไพเลอร์จะสร้างข้อผิดพลาดและแนะนำสองตัวเลือกสำหรับการแก้ไข: ล้อมเมธอดไว้ในบล็อกtry-catchหรือทำให้เมธอดที่เรียกFile.createTempFileโยนIOException ( จึงจะจัดการได้ในระดับที่สูงขึ้น)
มาถึง Java NIO และวิธีเปรียบเทียบกับ Java IO
Java NIOหรือ Java Non-Blocking I/O (หรือบางครั้ง Java New I/O) ได้รับการออกแบบมาสำหรับการดำเนินการ I/O ที่มีประสิทธิภาพสูง
ลองเปรียบเทียบ เมธอด Java IOกับเมธอดที่มาแทนที่
ก่อนอื่นเรามาพูดถึงการทำงานกับJava IO กัน :
คลาส InputStream
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 — สำหรับการทำงานกับไฟล์
-
DatagramChannel — ช่องทางสำหรับการทำงานผ่านการเชื่อมต่อ UDP
-
SocketChannel — ช่องทางสำหรับการทำงานผ่านการเชื่อมต่อ TCP
-
ServerSocketChannelมี SocketChannelและคล้ายกับวิธีการทำงานของเว็บเซิร์ฟเวอร์

โปรดทราบ: ไม่สามารถเปลี่ยนFileChannel เป็นโหมดไม่ปิดกั้นได้ โหมดไม่บล็อกของ Java NIO ช่วยให้คุณขอข้อมูลการอ่านจากช่องสัญญาณและรับเฉพาะสิ่งที่มีอยู่ในปัจจุบัน (หรือไม่มีอะไรเลยหากยังไม่มีข้อมูล) ที่กล่าวว่าSelectableChannelและการใช้งานสามารถใส่ในโหมดไม่ปิดกั้นโดยใช้เมธอดconnect()
ตัวเลือก
Java NIOนำเสนอความสามารถในการสร้างเธรดที่รู้ว่าแชนเนลใดพร้อมที่จะเขียนและอ่านข้อมูลและสามารถประมวลผลแชนเนลนั้น ความสามารถนี้ใช้งานโดยใช้คลาส Selector

การเชื่อมต่อช่องสัญญาณกับตัวเลือก
Selector selector = Selector.open();
channel.configureBlocking(false); // Non-blocking mode
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
ดังนั้นเราจึงสร้างSelector ของ เราและเชื่อมต่อกับSelectableChannel
หากต้องการใช้กับตัวเลือก ช่องสัญญาณจะต้องอยู่ในโหมดไม่ปิดกั้น ซึ่งหมายความว่าคุณไม่สามารถใช้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() |
ตอนนี้เรามาพูดถึงสิ่งใหม่ๆ ในJava NIO.2 กันสักหน่อย
เส้นทาง
เส้นทางแสดงเส้นทางในระบบไฟล์ ประกอบด้วยชื่อไฟล์และรายการไดเร็กทอรีที่กำหนดพาธไปยังไฟล์นั้น
Path relative = Paths.get("Main.java");
System.out.println("File: " + relative);
// Get the file system
System.out.println(relative.getFileSystem());
Pathsเป็นคลาสที่ง่ายมากด้วยเมธอดสแตติกเดียว: get( ) มันถูกสร้างขึ้นเพื่อรับ วัตถุ เส้นทางจากสตริงหรือ URI ที่ส่งผ่าน
Path path = Paths.get("c:\\data\\file.txt");
ไฟล์
Filesคือคลาสยูทิลิตี้ที่ช่วยให้เราได้ขนาดไฟล์ คัดลอกไฟล์ และอื่นๆ ได้โดยตรง
Path path = Paths.get("files/file.txt");
boolean pathExists = Files.exists(path);
ระบบไฟล์
FileSystemมีส่วนต่อประสานกับระบบไฟล์ ระบบไฟล์ทำงานเหมือนโรงงานสำหรับสร้างวัตถุต่างๆ (เส้นทาง,PathMatcher,ไฟล์). ช่วยให้เราเข้าถึงไฟล์และวัตถุอื่นๆ ในระบบไฟล์
try {
FileSystem filesystem = FileSystems.getDefault();
for (Path rootdir : filesystem.getRootDirectories()) {
System.out.println(rootdir.toString());
}
} catch (Exception e) {
e.printStackTrace();
}
การทดสอบประสิทธิภาพ
สำหรับการทดสอบนี้ เราจะใช้สองไฟล์ ไฟล์แรกเป็นไฟล์ข้อความขนาดเล็ก และไฟล์ที่สองเป็นวิดีโอขนาดใหญ่
เราจะสร้างไฟล์และเพิ่มคำและอักขระสองสามตัว:

ไฟล์ของเราใช้หน่วยความจำทั้งหมด 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();
}
}
รหัสสำหรับJava 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 มิลลิวินาที
เวลาดำเนินการสำหรับJava NIOนั้นนานกว่ามาก เวลาเฉลี่ยอยู่ที่ 11 มิลลิวินาที ผลลัพธ์มีค่าตั้งแต่ 9 ถึง 16 เนื่องจากJava IOทำงานแตกต่างจากระบบปฏิบัติการของเรา IOย้ายและประมวลผลไฟล์ทีละไฟล์ แต่ระบบปฏิบัติการส่งข้อมูลเป็นกลุ่มใหญ่ NIO ทำงานได้ ไม่ดีเพราะเน้นบัฟเฟอร์ ไม่ใช่สตรีมเหมือนIO
และเรียกใช้การทดสอบJava NIO.2 ของเรา ด้วย NIO.2 ได้ปรับปรุงการ จัดการไฟล์เมื่อเทียบกับJava NIO นี่คือสาเหตุที่ไลบรารีที่อัปเดตสร้างผลลัพธ์ที่แตกต่างกันดังกล่าว:
ตอนนี้มาลองทดสอบไฟล์ขนาดใหญ่ของเรา ซึ่งเป็นวิดีโอขนาด 521 MB งานจะเหมือนกันทุกประการ: คัดลอกไฟล์ไปยังโฟลเดอร์อื่น ดู!
ผลลัพธ์สำหรับJava IO :
และนี่คือผลลัพธ์สำหรับJava NIO :
Java NIOจัดการไฟล์เร็วขึ้น 9 เท่าในการทดสอบครั้งแรก การทดสอบซ้ำ ๆ แสดงให้เห็นผลลัพธ์ที่เหมือนกันโดยประมาณ
และเราจะลองทดสอบกับJava NIO.2 :
ทำไมผลลัพธ์นี้ เพียงเพราะมันไม่มีเหตุผลที่เราจะเปรียบเทียบประสิทธิภาพระหว่างพวกเขา เนื่องจากพวกเขาตอบสนองวัตถุประสงค์ที่แตกต่างกัน NIOเป็น I/O ระดับต่ำที่เป็นนามธรรมมากกว่า ในขณะที่NIO.2มุ่งเน้นไปที่การจัดการไฟล์
สรุป
เราสามารถพูดได้อย่างปลอดภัยว่าJava NIOมีประสิทธิภาพมากกว่าอย่างมากเมื่อทำงานกับไฟล์ ต้องขอบคุณการใช้งานภายในบล็อก ข้อดีอีกอย่างคือ ไลบรารี NIOแบ่งออกเป็นสองส่วน: ส่วนหนึ่งสำหรับการทำงานกับไฟล์และอีกส่วนหนึ่งสำหรับการทำงานกับเครือข่าย
API ใหม่ของ Java NIO.2 สำหรับการทำงานกับไฟล์มีคุณสมบัติที่มีประโยชน์มากมาย:
-
การกำหนดที่อยู่ระบบไฟล์ที่มีประโยชน์มากกว่าโดยใช้Path ,
-
ปรับปรุงการจัดการไฟล์ ZIP อย่างมีนัยสำคัญโดยใช้ผู้ให้บริการระบบไฟล์แบบกำหนดเอง
-
เข้าถึงแอตทริบิวต์ไฟล์พิเศษ
-
วิธีที่สะดวกมากมาย เช่น การอ่านไฟล์ทั้งหมดด้วยคำสั่งเดียว การคัดลอกไฟล์ด้วยคำสั่งเดียว เป็นต้น
มันคือทั้งหมดที่เกี่ยวกับไฟล์และระบบไฟล์ และทั้งหมดนี้อยู่ในระดับสูงทีเดียว
ความจริงในปัจจุบันคือJava NIOคิดเป็นประมาณ 80-90% ของงานกับไฟล์ แม้ว่าส่วนแบ่งของJava IO จะยังมีนัยสำคัญ
💡 ป.ล. การทดสอบเหล่านี้ดำเนินการบน MacBook Pro 14" 16/512 ผลการทดสอบอาจแตกต่างกันไปตามข้อกำหนดของระบบปฏิบัติการและเวิร์กสเตชัน
GO TO FULL VERSION