FileChannel指南


推薦關注公眾號:鍋外的大佬

每日推送國外技術好文,幫助每位開發者更優秀地成長

原文鏈接:https://www.baeldung.com/java-filechannel

作者:baeldung

譯者:Leesen

1.概述

在這篇速學教程中,我們將研究Java NIO庫中提供的FileChannel類,討論如何使用FileChannelByteBuffer讀寫數據,探討使用FileChannel以及其他文件操作特性的優點。

2.FileChannel的優點

FileChannel的優點包括:

  • 在文件特定位置進行讀寫操作
  • 將文件一部分直接加載到內存,這樣效率更高
  • 以更快的速度將文件數據從一個通道傳輸到另一個通道
  • 鎖定文件的某一部分來限制其他線程訪問
  • 為了避免數據丟失,強制立即將更新寫入文件並存儲

3.FileChannel讀操作

當我們讀取一個大文件時,FileChannel標准I/O執行得更快。需要注意,雖然FileChannelJava NIO的一部分,但是FileChannel操作是阻塞的,並且沒有非阻塞模式。

3.1.使用FileChannel讀取文件

先了解如何使用FileChannel讀取一個文件,該文件包含:

 Hello world 

下面測試讀取文件,並檢查是否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); } } 

這里使用FileChannelRandomAccessFileByteBuffer從文件中讀取字節。還應該注意,多個並發線程可以安全地使用FileChannel。但是,每次只允許一個線程執行涉及更新通道位置(channel position)或更改其文件大小的操作。這會阻止其他試圖執行類似操作的線程,直到前一個操作完成。
但是,顯式提供通道位置的操作可以並發運行且不會被阻塞。

3.2.打開FileChannel

為了使用FileChannel讀取文件,我們必須打開它(Open FileChannel)。看看如何使用RandomAccessFile打開FileChannel:

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

模式“r”表示通道僅為“只讀“,注意,關閉RandomAccessFile也將關閉與之關聯的通道。
接下來,使用FileInputStream打開一個FileChannel來讀取文件:

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

同樣的,關閉FileInputStream也會關閉與之相關的通道。

3.3.從FileChannel中讀取數據

為了讀取數據,我們可以使用只讀模式。接下來看看如何讀取字節序列,我們將使用ByteBuffer來保存數據:

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

然后,我們將看到如何從文件某個位置開始讀取一個字節序列:

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

我們應該注意:需要使用字符集(Charset)將字節數組解碼為字符串
我們指定原始編碼字節的字符集。沒有它,我們可能會以斷章取義的文字結束。特別是像UTF-8UTF-16這樣的多字節編碼可能無法解碼文件的任意部分,因為一些多字節字符可能是不完整的。

4.FileChannel寫操作

4.1.使用FileChannel寫入文件

我們來探究下如何使用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.打開FileChannel

要使用FileChannel寫入文件,必須先打開它。使用RandomAccessFile打開一個FileChannel:

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

模式“rw”表示通道為“讀寫”。
使用FileOutputStream打開FileChannel:

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

4.3.FileChannel寫入數據

使用FileChannel寫數據,可以使用其中的某個寫方法。
我們來看下如何寫一個字節序列,使用ByteBuffer來存儲數據:

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

接下來,我們將看到如何從文件某個位置開始寫一個字節序列:

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

5.當前位置

FileChannel允許我們獲得和改變讀或寫的位置(position)。獲得當前的位置:

long originalPosition = channel.position(); 

設置位置:

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

6.獲取文件大小

使用FileChannel.size方法獲取文件大小(以字節為單位):

@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.截斷文件

使用FileChannel.truncate方法將文件截斷為給定的大小(以字節為單位):

@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方法:

channel.force(true); 

僅當文件存儲在本地設備上時,才能保證該方法有效。

9.將文件部分加載到內存

使用FileChannel.map方法將文件的部分加載到內存中。使用FileChannel.MapMode.READ_ONLY以只讀模式打開文件:

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

類似地,可以使用FileChannel.MapMode.READ_WRITE以讀寫模式打開文件。還可以使用FileChannel.MapMode.PRIVATE模式,該模式下,更改不應用於原始文件。

10.鎖定文件部分

來看下如何鎖定文件某一部分,使用FileChannel.tryLock方法阻止對文件某一部分進行高並發訪問。

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

tryLock方法嘗試獲取文件部分(file section)上的鎖。如果請求的文件部分已被另一個線程阻塞,它將拋出一個OverlappingFileLockException異常。此方法還接受Boolean參數來請求共享鎖或獨占鎖。
我們應該注意到,有些操作系統可能不允許共享鎖,默認情況下是獨占鎖。

11.FileChannel關閉

最后,當使用FileChannel時,必須關閉它。在示例中,我們使用了try-with-resources
如果有必要,我們可以直接使用FileChannel.close方法:

channel.close();

12.總結

在本教程中,我們了解了如何使用FileChannel讀取和寫入文件。此外,我們還研究了如何讀取和更改文件大小及其當前讀/寫位置,並研究了如何在並發應用程序或數據關鍵應用程序中使用FileChannel
與往常一樣,示例的源代碼可以在GitHub上找到。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM