Panduan untuk Java FileChannel

1. Gambaran keseluruhan

Dalam tutorial ringkas ini, kita akan melihat kelas FileChannel yang disediakan di perpustakaan Java NIO . Kami akan membincangkan cara membaca dan menulis data menggunakan FileChannel dan ByteBuffer .

Kami juga akan meneroka kelebihan menggunakan FileChannel dan beberapa ciri manipulasi failnya yang lain.

2. Kelebihan FileChannel

Kelebihan FileChannel merangkumi:

  • Membaca dan menulis pada kedudukan tertentu dalam fail
  • Memuat bahagian fail terus ke memori, yang boleh menjadi lebih cekap
  • Kami dapat memindahkan data fail dari satu saluran ke saluran lain dengan kadar yang lebih cepat
  • Kita boleh mengunci bahagian fail untuk menyekat akses oleh utas lain
  • Untuk mengelakkan kehilangan data, kami dapat memaksa penulisan kemas kini ke fail ke penyimpanan

3. Membaca dengan FileChannel

FileChannel berkinerja lebih pantas daripada I / O standard apabila kita membaca fail besar.

Kita harus perhatikan bahawa walaupun merupakan sebahagian dari Java NIO , operasi FileChannel sedang menyekat dan tidak mempunyai mod yang tidak menyekat.

3.1. Membaca Fail Menggunakan FileChannel

Mari kita fahami cara membaca fail menggunakan FileChannel pada fail yang mengandungi:

Hello world

Ujian ini membaca fail dan memastikan ia dibaca ok:

@Test public void givenFile_whenReadWithFileChannelUsingRandomAccessFile_thenCorrect() throws IOException { try (RandomAccessFile reader = new RandomAccessFile("src/test/resources/test_read.in", "r"); FileChannel channel = reader.getChannel(); ByteArrayOutputStream out = new ByteArrayOutputStream()) { int bufferSize = 1024; if (bufferSize > channel.size()) { bufferSize = (int) channel.size(); } ByteBuffer buff = ByteBuffer.allocate(bufferSize); while (channel.read(buff) > 0) { out.write(buff.array(), 0, buff.position()); buff.clear(); } String fileContent = new String(out.toByteArray(), StandardCharsets.UTF_8); assertEquals("Hello world", fileContent); } }

Di sini kita membaca bait dari fail menggunakan FileChannel , RandomAccessFile , dan ByteBuffer.

Kita juga harus memperhatikan bahawa beberapa utas serentak dapat menggunakan FileChannels dengan selamat . Namun, hanya satu utas pada satu waktu yang dibenarkan operasi yang melibatkan pengemaskinian kedudukan saluran atau mengubah ukuran failnya. Ini menyekat utas lain yang mencuba operasi serupa sehingga operasi sebelumnya selesai.

Walau bagaimanapun, operasi yang menyediakan kedudukan saluran eksplisit dapat berjalan serentak tanpa disekat.

3.2. Membuka Saluran Fail

Untuk membaca fail menggunakan FileChannel , kita mesti membukanya.

Mari lihat cara membuka FileChannel menggunakan RandomAccessFile :

RandomAccessFile reader = new RandomAccessFile(file, "r"); FileChannel channel = reader.getChannel();

Mode 'r' menunjukkan bahawa saluran itu 'terbuka untuk membaca' sahaja. Kita harus perhatikan bahawa menutup RandomAccessFile juga akan menutup saluran yang berkaitan.

Seterusnya, kita akan melihat membuka FileChannel untuk membaca fail menggunakan FileInputStream :

FileInputStream fin= new FileInputStream(file); FileChannel channel = fin.getChannel();

Begitu juga, menutup FileInputStream juga menutup saluran yang berkaitan dengannya.

3.3. Membaca Data dari FileChannel

Untuk membaca data, kita boleh menggunakan salah satu kaedah membaca .

Mari lihat bagaimana membaca urutan bait. Kami akan menggunakan ByteBuffer untuk menyimpan data:

ByteBuffer buff = ByteBuffer.allocate(1024); int noOfBytesRead = channel.read(buff); String fileContent = new String(buff.array(), StandardCharsets.UTF_8); assertEquals("Hello world", fileContent);

Seterusnya, kita akan melihat cara membaca urutan bait, bermula pada kedudukan fail:

ByteBuffer buff = ByteBuffer.allocate(1024); int noOfBytesRead = channel.read(buff, 5); String fileContent = new String(buff.array(), StandardCharsets.UTF_8); assertEquals("world", fileContent);

Kita harus perhatikan perlunya Charset menyahkod array byte ke dalam String .

Kami menentukan Charset yang mana baitnya dikodkan pada asalnya. Tanpa itu , kita mungkin akan berakhir dengan teks yang rosak. Khususnya, pengekodan pelbagai byte seperti UTF-8 dan UTF-16 mungkin tidak dapat menyahkod bahagian fail yang sewenang-wenangnya, kerana beberapa watak berbilang bait mungkin tidak lengkap.

4. Menulis dengan FileChannel

4.1. Menulis ke dalam Fail Menggunakan FileChannel

Mari terokai bagaimana menulis menggunakan FileChannel :

@Test public void whenWriteWithFileChannelUsingRandomAccessFile_thenCorrect() throws IOException { String file = "src/test/resources/test_write_using_filechannel.txt"; try (RandomAccessFile writer = new RandomAccessFile(file, "rw"); FileChannel channel = writer.getChannel()){ ByteBuffer buff = ByteBuffer.wrap("Hello world".getBytes(StandardCharsets.UTF_8)); channel.write(buff); // verify RandomAccessFile reader = new RandomAccessFile(file, "r"); assertEquals("Hello world", reader.readLine()); reader.close(); } }

4.2. Membuka Saluran Fail

Untuk menulis ke dalam fail menggunakan FileChannel , kita mesti membukanya.

Mari lihat cara membuka FileChannel menggunakan RandomAccessFile :

RandomAccessFile writer = new RandomAccessFile(file, "rw"); FileChannel channel = writer.getChannel();

Mode 'rw' menunjukkan bahawa saluran tersebut 'terbuka untuk membaca dan menulis'.

Mari lihat juga cara membuka FileChannel menggunakan FileOutputStream :

FileOutputStream fout = new FileOutputStream(file); FileChannel channel = fout.getChannel(); 

4.3. Menulis Data dengan FileChannel

Untuk menulis data dengan FileChannel , kita boleh menggunakan salah satu kaedah menulis .

Mari lihat bagaimana menulis urutan bait, menggunakan ByteBuffer untuk menyimpan data:

ByteBuffer buff = ByteBuffer.wrap("Hello world".getBytes(StandardCharsets.UTF_8)); channel.write(buff); 

Seterusnya, kita akan melihat cara menulis urutan bait, bermula dari kedudukan fail:

ByteBuffer buff = ByteBuffer.wrap("Hello world".getBytes(StandardCharsets.UTF_8)); channel.write(buff, 5); 

5. Kedudukan Semasa

FileChannel membolehkan kita mendapatkan dan mengubah kedudukan di mana kita membaca atau menulis.

Let's see how to get the current position:

long originalPosition = channel.position();

Next, let's see how to set the position:

channel.position(5); assertEquals(originalPosition + 5, channel.position());

6. Get the Size of a File

Let's see how to use the FileChannel.size method to get the size of a file in bytes:

@Test public void whenGetFileSize_thenCorrect() throws IOException { RandomAccessFile reader = new RandomAccessFile("src/test/resources/test_read.in", "r"); FileChannel channel = reader.getChannel(); // the original file size is 11 bytes. assertEquals(11, channel.size()); channel.close(); reader.close(); }

7. Truncate a File

Let's understand how to use the FileChannel.truncate method to truncate a file to given size in bytes:

@Test public void whenTruncateFile_thenCorrect() throws IOException { String input = "this is a test input"; FileOutputStream fout = new FileOutputStream("src/test/resources/test_truncate.txt"); FileChannel channel = fout.getChannel(); ByteBuffer buff = ByteBuffer.wrap(input.getBytes()); channel.write(buff); buff.flip(); channel = channel.truncate(5); assertEquals(5, channel.size()); fout.close(); channel.close(); } 

8. Force File Update into Storage

An operating system may cache file changes for performance reasons, and data may be lost if the system crashes. To force file content and metadata to write to disk continuously we can use the force method:

channel.force(true);

This method is guaranteed only when the file resides on a local device.

9. Load a Section of a File into Memory

Let's see how to load a section of a file in memory using FileChannel.map. We use FileChannel.MapMode.READ_ONLY to open the file in read-only mode:

@Test public void givenFile_whenReadAFileSectionIntoMemoryWithFileChannel_thenCorrect() throws IOException { try (RandomAccessFile reader = new RandomAccessFile("src/test/resources/test_read.in", "r"); FileChannel channel = reader.getChannel(); ByteArrayOutputStream out = new ByteArrayOutputStream()) { MappedByteBuffer buff = channel.map(FileChannel.MapMode.READ_ONLY, 6, 5); if(buff.hasRemaining()) { byte[] data = new byte[buff.remaining()]; buff.get(data); assertEquals("world", new String(data, StandardCharsets.UTF_8)); } } }

Similarly, we can use FileChannel.MapMode.READ_WRITE to open the file into both read and write mode.

We can also useFileChannel.MapMode.PRIVATE mode, where changes do not apply to the original file.

10. Lock a Section of a File

Let's understand how to lock a section of a file to prevent concurrent access of a section using the FileChannel.tryLock method:

@Test public void givenFile_whenWriteAFileUsingLockAFileSectionWithFileChannel_thenCorrect() throws IOException { try (RandomAccessFile reader = new RandomAccessFile("src/test/resources/test_read.in", "rw"); FileChannel channel = reader.getChannel(); FileLock fileLock = channel.tryLock(6, 5, Boolean.FALSE )){ //do other operations... assertNotNull(fileLock); } }

The tryLock method attempts to acquire a lock on the file section. If the requested file section is already blocked by another thread, it throws an OverlappingFileLockException exception. This method also takes a boolean parameter to request either a shared lock or an exclusive lock.

We should note that some operating systems may not allow a shared lock, defaulting instead to an exclusive lock.

11. Closing a FileChannel

Finally, when we are done using a FileChannel, we must close it. In our examples we have used try-with-resources.

If necessary, we can close the FileChannel directly with the close method:

channel.close();

12. Conclusion

Dalam tutorial ini, kita telah melihat cara menggunakan FileChannel untuk membaca dan menulis fail . Di samping itu, kami telah meneroka cara membaca dan mengubah saiz fail dan lokasi membaca / menulisnya sekarang dan melihat bagaimana menggunakan FileChannels dalam aplikasi kritikal serentak atau data.

Seperti biasa, kod sumber untuk contoh boleh didapati di GitHub.