NIO 之 FileChannel
概述
文件通道總是阻塞式的,因此不能被置于非阻塞模式。現代操作系統都有復雜的緩存和預取機制,使得本地磁盤 I/O 操作延遲很少。網絡文件系統一般而言延遲會多些,不過卻也因該優化而受益。 面向流的 I/O 的非阻塞范例對于面向文件的操作并無多大意義,這是由文件 I/O 本質上的不同性質造成的。對于文件 I/O,最強大之處在于異步 I/O( asynchronous I/O),它允許一個進程可以從操作系統請求一個或多個 I/O 操作而不必等待這些操作的完成。發起請求的進程之后會收到它請求的 I/O 操作已完成的通知。
異步 I/O 是在JDK 1.7 才被加入的 java.nio.channels.AsynchronousFileChannel 。
FileChannel
FileChannel對象不能直接創建。一個FileChannel實例只能通過在一個打開的file對象( RandomAccessFile、 FileInputStream或 FileOutputStream)上調用getChannel( )方法
獲取。調用getChannel( )方法會返回一個連接到相同文件的FileChannel對象且該FileChannel對象具有與file對象相同的訪問權限,然后您就可以使用該通道對象來利用強大的FileChannel API了。
FileChannel 線程安全
FileChannel 是線程安全的類,支持多個線程同時并發訪問,但不是所有的方法都能多線程同時并發訪問,比如,文件大小,file postion 等,該方法要想獲取正確的值,只能加鎖訪問。
FileChannel 類結構
public abstract class FileChannel extends AbstractInterruptibleChannelimplements SeekableByteChannel, GatheringByteChannel, ScatteringByteChannel {protected FileChannel() {}public static FileChannel open(Path path, OpenOption... options) throws IOException {}public abstract int read(ByteBuffer dst) throws IOException;public abstract int write(ByteBuffer src) throws IOException;public abstract long position() throws IOException;public abstract FileChannel position(long newPosition) throws IOException;public abstract long size() throws IOException;public abstract FileChannel truncate(long size) throws IOException;public abstract void force(boolean metaData) throws IOException;public abstract long transferTo(long position, long count, WritableByteChannel target) throws IOException;public abstract long transferFrom(ReadableByteChannel src, long position, long count) throws IOException;public abstract int read(ByteBuffer dst, long position) throws IOException;public abstract int write(ByteBuffer src, long position) throws IOException;public static class MapMode {public static final MapMode READ_ONLY = new MapMode("READ_ONLY");public static final MapMode READ_WRITE = new MapMode("READ_WRITE");public static final MapMode PRIVATE = new MapMode("PRIVATE");}public abstract MappedByteBuffer map(MapMode mode, long position, long size) throws IOException;public abstract FileLock lock(long position, long size, boolean shared) throws IOException;public final FileLock lock() throws IOException {}public abstract FileLock tryLock(long position, long size, boolean shared) throws IOException;public final FileLock tryLock() throws IOException {}}open() 方法
public static FileChannel open(Path path, Set<? extends OpenOption> options,FileAttribute<?>... attrs) throws IOException {} public static FileChannel open(Path path, OpenOption... options) throws IOException {}position() 方法
每個 FileChannel 都有一個叫“file position”的概念。這個 position 值決定文件中哪一處的數據接下來將被讀或者寫。
FileChannel 類為我們提供了兩種position( )方法。
public abstract long position() throws IOException; public abstract FileChannel position(long newPosition) throws IOException;- 第一種,不帶參數的,返回當前文件的position值。返回值是一個長整型( long),表示文件中的當前字節位置
- 第二種形式的 position( )方法帶一個 long(長整型) 參數并將通道的 position 設置為指定值。
position 必須是大于等于0的整數。但是 postion 的大小可以超出文件的大小。
當 position 的位置大于文件的長度時分以下兩種情況:
文件空洞
當磁盤上一個文件的分配空間小于它的文件大小時會出現“文件空洞”。對于內容稀疏的文件,大多數現代文件系統只為實際寫入的數據分配磁盤空間(更準確地說,只為那些寫入數據的文件系統頁分配空間)。假如數據被寫入到文件中非連續的位置上,這將導致文件出現在邏輯上不包含數據的區域( 即“空洞”)。
例如:文件大小為 10 byte,現在把position 設置為100,然后調用 write 方法寫入10個字節,現在的文件大小為 110 字節。而文件系統為了優化而磁盤中實際只占用了20字節,其它90個字節未分配空間。當真正寫入的時候才分配磁盤空間。
是否產生文件空洞,取決與文件系統的實現。
truncate() 方法
當需要減少一個文件的 size 時, truncate( )方法會砍掉您所指定的新 size 值之外的所有數據。如果 truncate 的 size 大于文件的 size 該文件不會修改。如果truncate 的 size 小于或等于當前的文件 size 值,該文件會把 truncate 的 size 后面的數據刪掉。如果postion的值大于 truncate size 的值,在 truncate 后會把 postion的值修改為 truncate size 的值。
public static void main(String[] args) throws Exception {RandomAccessFile fis = new RandomAccessFile(new File("d:\\a.txt"),"rw");FileChannel fs =fis.getChannel();ByteBuffer bb = ByteBuffer.allocate(10);fs.position(100);fs.write(bb);fs.position(1000);System.out.println("原始size:"+fs.size() + "\tposition:"+fs.position());fs.truncate(200);System.out.println("truncate 200\tsize:"+fs.size()+ "\tposition:"+fs.position());fs.truncate(100);System.out.println("truncate 100\tsize:"+fs.size()+ "\tposition:"+fs.position());} 原始size:110 position:1000 truncate 200 size:110 position:200 truncate 100 size:100 position:100force() 方法
該方法告訴 FileChannel,強制把所有修改的數據全部寫如到磁盤上。
文件系統可能為了性能,把要修改的數據先寫入緩存,等緩存寫滿后一塊同步寫入到磁盤中,使用緩存來提高文件的讀寫速度。
transferTo() 和 transferFrom() 方法
transferTo( )和 transferFrom( )方法允許將一個通道交叉連接到另一個通道,而不需要通過一個中間緩沖區來傳遞數據。只有 FileChannel 類有這兩個方法,因此 channel-to-channel 傳輸中通道之一必須是 FileChannel。您不能在 socket 通道之間直接傳輸數據,不過 socket 通道實現WritableByteChannel 和 ReadableByteChannel 接口,因此文件的內容可以用 transferTo( )方法傳輸給一個 socket 通道,或者也可以用 transferFrom( )方法將數據從一個 socket 通道直接讀取到一個文件中。
直接的通道傳輸不會更新與某個 FileChannel 關聯的 position 值。請求的數據傳輸將從position 參數指定的位置開始,傳輸的字節數不超過 count 參數的值。實際傳輸的字節數會由方法返回,可能少于您請求的字節數。
對于傳輸數據來源是一個文件的 transferTo( )方法,如果 position + count 的值大于文件
的 size 值,傳輸會在文件尾的位置終止。假如傳輸的目的地是一個非阻塞模式的 socket 通道,那么當發送隊列( send queue) 滿了之后傳輸就可能終止,并且如果輸出隊列( output queue)已滿的話可能不會發送任何數據。類似地,對于 transferFrom( )方法:如果來源 src 是另外一個 FileChannel并且已經到達文件尾,那么傳輸將提早終止;如果來源 src 是一個非阻塞 socket 通道,只有當前處于隊列中的數據才會被傳輸(可能沒有數據)。由于網絡數據傳輸的非確定性,阻塞模式的socket 也可能會執行部分傳輸,這取決于操作系統。許多通道實現都是提供它們當前隊列中已有的數據而不是等待您請求的全部數據都準備好。
注意:
NIO,非阻塞通道,不要使用 transferTo 或 tranferFrom 來傳輸數據,傳輸的數據可能會不完整。
map() 方法
map( )的方法,該方法可以在一個打開的文件和一個特殊類型的 ByteBuffer 之間建立一個虛擬內存映射。在 FileChannel 上調用 map( )方法會創建一個由磁盤文件支持的虛擬內存映射( virtual memory mapping)并在那塊虛擬內存空間外部封裝一個 MappedByteBuffer 對象。
由 map( )方法返回的 MappedByteBuffer 對象(直接內存)的行為在多數方面類似一個基于內存的緩沖區,只不過該對象的數據元素存儲在磁盤上的一個文件中。調用 get( )方法會從磁盤文件中獲取數據。通過文件映射看到的數據同您用常規方法讀取文件看到的內容是完全一樣的。相似地,對映射的緩沖區實現一個 put( )會更新磁盤上的那個文件,并且您做的修改對于該文件的其他閱讀者也是可見的。
通過內存映射機制來訪問一個文件會比使用常規方法讀寫高效得多,甚至比使用通道的效率都高。因為不需要做明確的系統調用,那會很消耗時間。更重要的是,操作系統的虛擬內存可以自動緩存內存頁( memory page)。這些頁是用系統內存來緩存的,所以不會消耗 Java 虛擬機內存堆( memory heap)。
lock() 方法
調用帶參數的 Lock( )方法會指定文件內部鎖定區域的開始 position 以及鎖定區域的 size。第三個參數 shared 表示您想獲取的鎖是共享的(參數值為 true)還是獨占的(參數值為 false)。要獲得一個共享鎖,您必須先以只讀權限打開文件,而請求獨占鎖時則需要寫權限。另外,您提供的 position和 size 參數的值不能是負數。
FileChannel 的 lock 支持獲取共享鎖和獨占鎖(lock 方法的第三個參賽 shared)。是否支持共享鎖還得依賴本地的操作系統實現。并非所有的操作系統和文件系統都支持共享文件鎖。對于那些不支持的,對一個共享鎖的請求會被自動提升為對獨占鎖的請求。這可以保證準確性卻可能嚴重影響性能。
鎖的對象是文件而不是通道或線程,如果在同一個進程使用多線程獲取文件鎖,只要一個能獲取到鎖,那么其它的所遇咸菜都可以獲取到鎖。
鎖定區域的范圍不一定要限制在文件的 size 值以內,鎖可以擴展從而超出文件尾。因此,我們可以提前把待寫入數據的區域鎖定,我們也可以鎖定一個不包含任何文件內容的區域,比如文件最后一個字節以外的區域。如果之后文件增長到達那塊區域,那么您的文件鎖就可以保護該區域的文件內容了。相反地,如果您鎖定了文件的某一塊區域,然后文件增長超出了那塊區域,那么新增加的文件內容將不會受到您的文件鎖的保護。
不帶參數的 lock() 方法,默認獲取的是獨占鎖,并且鎖定的文件區域是 0 到 Long.MAX_VALUE。
public final FileLock lock() throws IOException {return lock(0L, Long.MAX_VALUE, false); }文件鎖使用,詳見文章:JAVA 文件鎖 FileLock
想了解更多精彩內容請關注我的公眾號
本人簡書blog地址:http://www.jianshu.com/u/1f0067e24ff8????
點擊這里快速進入簡書
GIT地址:http://git.oschina.net/brucekankan/
點擊這里快速進入GIT
總結
以上是生活随笔為你收集整理的NIO 之 FileChannel的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mysql 启动出错问题排查
- 下一篇: 路由器 和 交换机 傻傻分不清楚