Java nio記錄
記憶體對映檔案
JAVA處理大檔案,一般用BufferedReader,BufferedInputStream這類帶緩衝的IO類,不過如果檔案超大的話,更快的方式是採用MappedByteBuffer。
MappedByteBuffer是NIO引入的檔案記憶體對映方案,讀寫效能極高。NIO最主要的就是實現了對非同步操作的支援。其中一種通過把一個套接字通道(SocketChannel)註冊到一個選擇器(Selector)中,不時呼叫後者的選擇(select)方法就能返回滿足的選擇鍵(SelectionKey),鍵中包含了SOCKET事件資訊。這就是select模型。
SocketChannel的讀寫是通過一個類叫ByteBuffer來操作的.這個類本身的設計是不錯的,比直接操作byte[]方便多了. ByteBuffer有兩種模式:直接/間接.間接模式最典型(也只有這麼一種)的就是HeapByteBuffer,即操作堆記憶體 (byte[]).但是記憶體畢竟有限,如果我要傳送一個1G的檔案怎麼辦?不可能真的去分配1G的記憶體.這時就必須使用”直接”模式,即 MappedByteBuffer,檔案對映.
先中斷一下,談談作業系統的記憶體管理.一般作業系統的記憶體分兩部分:實體記憶體;虛擬記憶體.虛擬記憶體一般使用的是頁面映像檔案,即硬碟中的某個(某些)特殊的檔案.作業系統負責頁面檔案內容的讀寫,這個過程叫”頁面中斷/切換”. MappedByteBuffer也是類似的,你可以把整個檔案(不管檔案有多大)看成是一個ByteBuffer.MappedByteBuffer 只是一種特殊的ByteBuffer,即是ByteBuffer的子類。 MappedByteBuffer 將檔案直接對映到記憶體(這裡的記憶體指的是虛擬記憶體,並不是實體記憶體)。通常,可以對映整個檔案,如果檔案比較大的話可以分段進行對映,只要指定檔案的那個部分就可以。
概念
FileChannel提供了map方法來把檔案影射為記憶體映像檔案: MappedByteBuffer map(int mode,long position,long size); 可以把檔案的從position開始的size大小的區域對映為記憶體映像檔案,mode指出了 可訪問該記憶體映像檔案的方式:
- READ_ONLY,(只讀): 試圖修改得到的緩衝區將導致丟擲 ReadOnlyBufferException.(MapMode.READ_ONLY)
- READ_WRITE(讀/寫): 對得到的緩衝區的更改最終將傳播到檔案;該更改對對映到同一檔案的其他程式不一定是可見的。 (MapMode.READ_WRITE)
- PRIVATE(專用): 對得到的緩衝區的更改不會傳播到檔案,並且該更改對對映到同一檔案的其他程式也不是可見的;相反,會建立緩衝區已修改部分的專用副本。 (MapMode.PRIVATE)
MappedByteBuffer是ByteBuffer的子類,其擴充了三個方法:
- force():緩衝區是READ_WRITE模式下,此方法對緩衝區內容的修改強行寫入檔案;
- load():將緩衝區的內容載入記憶體,並返回該緩衝區的引用;
- isLoaded():如果緩衝區的內容在實體記憶體中,則返回真,否則返回假;
案例對比
這裡通過採用ByteBuffer和MappedByteBuffer分別讀取大小約為5M的檔案”src/1.ppt”來比較兩者之間的區別,method3()是採用MappedByteBuffer讀取的,method4()對應的是ByteBuffer。
public static void method4(){
RandomAccessFile aFile = null;
FileChannel fc = null;
try{
aFile = new RandomAccessFile("src/1.ppt","rw");
fc = aFile.getChannel();
long timeBegin = System.currentTimeMillis();
ByteBuffer buff = ByteBuffer.allocate((int) aFile.length());
buff.clear();
fc.read(buff);
//System.out.println((char)buff.get((int)(aFile.length()/2-1)));
//System.out.println((char)buff.get((int)(aFile.length()/2)));
//System.out.println((char)buff.get((int)(aFile.length()/2)+1));
long timeEnd = System.currentTimeMillis();
System.out.println("Read time: "+(timeEnd-timeBegin)+"ms");
}catch(IOException e){
e.printStackTrace();
}finally{
try{
if(aFile!=null){
aFile.close();
}
if(fc!=null){
fc.close();
}
}catch(IOException e){
e.printStackTrace();
}
}
}
public static void method3(){
RandomAccessFile aFile = null;
FileChannel fc = null;
try{
aFile = new RandomAccessFile("src/1.ppt","rw");
fc = aFile.getChannel();
long timeBegin = System.currentTimeMillis();
MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_ONLY, 0, aFile.length());
// System.out.println((char)mbb.get((int)(aFile.length()/2-1)));
// System.out.println((char)mbb.get((int)(aFile.length()/2)));
//System.out.println((char)mbb.get((int)(aFile.length()/2)+1));
long timeEnd = System.currentTimeMillis();
System.out.println("Read time: "+(timeEnd-timeBegin)+"ms");
}catch(IOException e){
e.printStackTrace();
}finally{
try{
if(aFile!=null){
aFile.close();
}
if(fc!=null){
fc.close();
}
}catch(IOException e){
e.printStackTrace();
}
}
}
method3();
System.out.println("=============");
method4();
輸出結果(執行在普通PC機上):
Read time: 2ms
=============
Read time: 12ms
通過輸出結果可以看出彼此的差別,一個例子也許是偶然,那麼下面把5M大小的檔案替換為200M的檔案,輸出結果:
Read time: 1ms
=============
Read time: 407ms
可以看到差距拉大。
MappedByteBuffer有資源釋放的問題:被MappedByteBuffer開啟的檔案只有在垃圾收集時才會被關閉,而這個點是不確定的。在Javadoc中這裡描述:A mapped byte buffer and the file mapping that it represents remian valid until the buffer itself is garbage-collected。
Scatter/Gatter
分散(scatter)從Channel中讀取是指在讀操作時將讀取的資料寫入多個buffer中。因此,Channel將從Channel中讀取的資料“分散(scatter)”到多個Buffer中。
聚集(gather)寫入Channel是指在寫操作時將多個buffer的資料寫入同一個Channel,因此,Channel 將多個Buffer中的資料“聚集(gather)”後傳送到Channel。
scatter / gather經常用於需要將傳輸的資料分開處理的場合,例如傳輸一個由訊息頭和訊息體組成的訊息,你可能會將訊息體和訊息頭分散到不同的buffer中,這樣你可以方便的處理訊息頭和訊息體。
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.Channel;
import java.nio.channels.FileChannel;
public class ScattingAndGather
{
public static void main(String args[]){
gather();
}
public static void gather()
{
ByteBuffer header = ByteBuffer.allocate(10);
ByteBuffer body = ByteBuffer.allocate(10);
byte [] b1 = {'0', '1'};
byte [] b2 = {'2', '3'};
header.put(b1);
body.put(b2);
ByteBuffer [] buffs = {header, body};
try
{
FileOutputStream os = new FileOutputStream("src/scattingAndGather.txt");
FileChannel channel = os.getChannel();
channel.write(buffs);
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
transferFrom & transferTo
FileChannel的transferFrom()方法可以將資料從源通道傳輸到FileChannel中。
public static void method1(){
RandomAccessFile fromFile = null;
RandomAccessFile toFile = null;
try
{
fromFile = new RandomAccessFile("src/fromFile.xml","rw");
FileChannel fromChannel = fromFile.getChannel();
toFile = new RandomAccessFile("src/toFile.txt","rw");
FileChannel toChannel = toFile.getChannel();
long position = 0;
long count = fromChannel.size();
System.out.println(count);
toChannel.transferFrom(fromChannel, position, count);
}
catch (IOException e)
{
e.printStackTrace();
}
finally{
try{
if(fromFile != null){
fromFile.close();
}
if(toFile != null){
toFile.close();
}
}
catch(IOException e){
e.printStackTrace();
}
}
}
方法的輸入引數position表示從position處開始向目標檔案寫入資料,count表示最多傳輸的位元組數。如果源通道的剩餘空間小於 count 個位元組,則所傳輸的位元組數要小於請求的位元組數。此外要注意,在SoketChannel的實現中,SocketChannel只會傳輸此刻準備好的資料(可能不足count位元組)。因此,SocketChannel可能不會將請求的所有資料(count個位元組)全部傳輸到FileChannel中。
transferTo()方法將資料從FileChannel傳輸到其他的channel中。
public static void method2()
{
RandomAccessFile fromFile = null;
RandomAccessFile toFile = null;
try
{
fromFile = new RandomAccessFile("src/fromFile.txt","rw");
FileChannel fromChannel = fromFile.getChannel();
toFile = new RandomAccessFile("src/toFile.txt","rw");
FileChannel toChannel = toFile.getChannel();
long position = 0;
long count = fromChannel.size();
System.out.println(count);
fromChannel.transferTo(position, count,toChannel);
}
catch (IOException e)
{
e.printStackTrace();
}
finally{
try{
if(fromFile != null){
fromFile.close();
}
if(toFile != null){
toFile.close();
}
}
catch(IOException e){
e.printStackTrace();
}
}
}
相關文章
- Java NIOJava
- JAVA NIO BufferJava
- JAVA 探究NIOJava
- Java NIO SocketChannelJava
- Java NIO - BufferJava
- Java NIO - 群聊Java
- Java NIO:通道Java
- Java NIO filesJava
- Java IO學習筆記五:BIO到NIOJava筆記
- Java雜記10—BIO,BIO和NIO的區別Java
- Java NIO之SelectorJava
- Java NIO之BufferJava
- Java IO 和 NIOJava
- java NIO SocketClinet ServerSocketJavaServer
- Java NIO 概覽Java
- Java Socket 之 NIOJava
- 詳解 Java NIOJava
- 淺析Java NIOJava
- Asyncdb(三):Java NIOJava
- Java IO之NIOJava
- Java NIO Channel 使用Java
- Java BIO,NIO,AIOJavaAI
- 【譯】Java NIO 簡明教程系列之 NIO 概述Java
- Java IO學習筆記六:NIO到多路複用Java筆記
- day2 Java NIOJava
- 深入的聊聊 Java NIOJava
- Java NIO 程式碼示例Java
- java BIO、NIO學習Java
- Java NIO 之 Channel(通道)Java
- Java NIO - Channel 與 SelectorJava
- Java NIO - 零拷貝Java
- Java NIO - 零複製Java
- Java NIO:選擇器Java
- Java NIO:緩衝區Java
- Java NIO Selector 的使用Java
- Java-NIO之SelectorJava
- JAVA學習記錄Java
- 【譯】Java NIO 簡明教程系列之 NIO 簡介Java