java處理大文本方案

轉載自:http://langgufu.iteye.com/blog/2107023html

java處理大文件,通常用BufferedReader,BufferedInputStream這類帶緩衝的Io類,不過若是文件超大的話,更快的方式是採用MappedByteBufferjava

 MappedByteBuffer是java nio引入的文件內存映射方案,讀寫性能極高。NIO最主要的就是實現了對異步操做的支持。其中一種經過把一個套接字通道(SocketChannel)註冊到一個選擇器(Selector)中,不時調用後者的選擇(select)方法就能返回知足的選擇鍵(SelectionKey),鍵中包含了SOCKET事件信息。這就是select模型。
    SocketChannel的讀寫是經過一個類叫ByteBuffer(java.nio.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,READ_WRITE,PRIVATE.                    
a. READ_ONLY,(只讀): 試圖修改獲得的緩衝區將致使拋出 ReadOnlyBufferException.(MapMode.READ_ONLY)
 b. READ_WRITE(讀/寫): 對獲得的緩衝區的更改最終將傳播到文件;該更改對映射到同一文件的其餘程序不必定是可見的。 (MapMode.READ_WRITE)
c. PRIVATE(專用): 對獲得的緩衝區的更改不會傳播到文件,而且該更改對映射到同一文件的其餘程序也不是可見的;相反,會建立緩衝區已修改部分的專用副本。 (MapMode.PRIVATE)

三個方法:

a. fore();緩衝區是READ_WRITE模式下,此方法對緩衝區內容的修改強行寫入文件
b. load()將緩衝區的內容載入內存,並返回該緩衝區的引用
c. isLoaded()若是緩衝區的內容在物理內存中,則返回真,不然返回假

三個特性:

    調用信道的map()方法後,便可將文件的某一部分或所有映射到內存中,映射內存緩衝區是個直接緩衝區,繼承自ByteBuffer,但相對於ByteBuffer,它有更多的優勢:

a. 讀取快
b. 寫入快
c. 隨時隨地寫入

下面來看代碼:api

複製代碼
 1 package study;  2 import java.io.FileInputStream;  3 import java.io.FileOutputStream;  4 import java.nio.ByteBuffer;  5 import java.nio.MappedByteBuffer;  6 import java.nio.channels.FileChannel;  7  8 public class MapMemeryBuffer {  9 10 public static void main(String[] args) throws Exception { 11 ByteBuffer byteBuf = ByteBuffer.allocate(1024 * 14 * 1024); 12 byte[] bbb = new byte[14 * 1024 * 1024]; 13 FileInputStream fis = new FileInputStream("e://data/other/UltraEdit_17.00.0.1035_SC.exe"); 14 FileOutputStream fos = new FileOutputStream("e://data/other/outFile.txt"); 15 FileChannel fc = fis.getChannel(); 16 long timeStar = System.currentTimeMillis();// 獲得當前的時間 17 fc.read(byteBuf);// 1 讀取 18 //MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()); 19 System.out.println(fc.size()/1024); 20 long timeEnd = System.currentTimeMillis();// 獲得當前的時間 21 System.out.println("Read time :" + (timeEnd - timeStar) + "ms"); 22 timeStar = System.currentTimeMillis(); 23 fos.write(bbb);//2.寫入 24 //mbb.flip(); 25 timeEnd = System.currentTimeMillis(); 26 System.out.println("Write time :" + (timeEnd - timeStar) + "ms"); 27  fos.flush(); 28  fc.close(); 29  fis.close(); 30  } 31 32 } 33 運行結果: 34 14235 35 Read time :24ms 36 Write time :21ms 37 咱們把標註1和2語句註釋掉,換成它們下面的被註釋的那條語句,再來看運行效果。14235 38 Read time :2ms 39 Write time :0ms 
複製代碼

能夠看出速度有了很大的提高。MappedByteBuffer的確快,但也存在一些問題,主要就是內存佔用和文件關閉等不肯定問題。被MappedByteBuffer打開的文件只有在垃圾收集時纔會被關閉,而這個點是不肯定的。在javadoc裏是這麼說的:A mapped byte buffer and the file mapping that it represents remain valid until the buffer itself  is garbage-collected.
這裏提供一種解決方案:安全

複製代碼
AccessController.doPrivileged(new PrivilegedAction() { public Object run() { try { Method getCleanerMethod = buffer.getClass().getMethod("cleaner", new Class[0]); getCleanerMethod.setAccessible(true); sun.misc.Cleaner cleaner = (sun.misc.Cleaner) getCleanerMethod.invoke(byteBuffer, new Object[0]); cleaner.clean(); } catch (Exception e) { e.printStackTrace(); } return null; } }); 
複製代碼

關於MappedByteBuffer資源釋放問題oracle

JDK1.4中加入了一個新的包:NIO(java.nio.*)。這個庫最大的功能(我認爲)就是增長了對異步套接字的支持。其實在 其餘語言中,包括在最原始的SOCKET實現(BSD SOCKET),這是一個早有的功能:異步回調讀/寫事件,經過選擇器動態選擇感興趣的事件,等等。
先談談操做系統的內存管理。通常操做系統的內存分兩部分:物理內存;虛擬內存。虛擬內存通常使用的是頁面映像文件,即硬盤中的某個(某些)特殊的文件.操做系統負責頁面文件內容的讀寫,這個過程叫"頁面中斷/切換"。
MappedByteBuffer也是相似的,你能夠把整個文件(無論文件有多大)當作是一個ByteBuffer。這是一個很好的設計,除了使人頭疼的一點在後面會講到。
java.lang.Object
    java.nio.Buffer
       java.nio.ByteBuffer
          java.nio.MappedByteBuffer
MappedByteBuffer是一個比較方便使用的類。其內容是文件的內存映射區域。映射的字節緩衝區是經過 FileChannel.map 方法建立的。映射的字節緩衝區和它所表示的文件映射關係在該緩衝區自己成爲垃圾回收緩衝區以前一直保持有效。此類用特定於內存映射文件區域的操做擴展  ByteBuffer 類。 這個類自己的設計是不錯的,比直接操做byte[]方便多了。
ByteBuffer有兩種模式:直接/間接。間接模式最典型(也只有這麼一種)的就是HeapByteBuffer,即操做堆內存(byte [])。可是內存畢竟有限,若是我要發送一個1G的文件怎麼辦?不可能真的去分配1G的內存.這時就必須使用"直接"模式,即 MappedByteBuffer,文件映射。
在JDK API文檔中這樣描述的:
所有或部分映射的字節緩衝區可能隨時成爲不可訪問的,例如,若是咱們截取映射的文件。試圖訪問映射的字節緩衝區的不可訪問區域將不會更改緩衝區 的內容,並致使在訪問時或訪問後的某個時刻拋出未指定的異常。所以強烈推薦採起適當的預防措施,以免此程序或另外一個同時運行的程序對映射的文件執行操做 (讀寫文件內容除外)。
MappedByteBuffer只能經過調用FileChannel的map()取得,再沒有其餘方式.可是使人奇怪的是,SUN提供了map()卻沒有提供unmap().這樣會致使什麼後果呢?
這樣,問題就出現了。經過MappedByteBuffer實現文件複製功能很是容易,能夠用如下方法來實現。
複製代碼
 1 //文件複製  2 public void copyFile(String filename,String srcpath,String destpath)throws IOException {  3 File source = new File(srcpath+"/"+filename);  4 File dest = new File(destpath+"/"+filename);  5 FileChannel in = null, out = null;  6 try {  7 in = new FileInputStream(source).getChannel();  8 out = new FileOutputStream(dest).getChannel();  9 long size = in.size(); 10 MappedByteBuffer buf = in.map(FileChannel.MapMode.READ_ONLY, 0, size); 11  out.write(buf); 12  in.close(); 13  out.close(); 14 source.delete();//文件複製完成後,刪除源文件 15 }catch(Exception e){ 16  e.printStackTrace(); 17 } finally { 18  in.close(); 19  out.close(); 20  } 21 }
複製代碼
可是若是要實現文件文件複製完成後,刪除源文件,以上方法就有問題。由於在source.delete()時,會返回false,刪除失敗,主 要緣由是變量buf仍然有源文件的句柄,文件處於不可刪除狀態。既然MappedByteBuffer是從FileChannel中map()出來的,爲 什麼它又不提供unmap()呢?SUN本身也沒有講清楚爲何。O'Reilly的<<Java NIO>>中說是由於"安全"的緣由,可是到底unmap()會怎麼不安全,做者也沒有講清楚。
在sun網站也有相應的BUG報告:bug id:4724038連接爲 http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4724038,可是sun本身不認爲是BUG,而只是一個RFE(Request For Enhancement),有待改進。
好在有個叫bellomi的網友提出了一個解決方法,我也測試過,能夠實現指望的功能。具體實現代碼以下:
複製代碼
 1 public static void clean(final Object buffer) throws Exception {  2 AccessController.doPrivileged(new PrivilegedAction() {  3 public Object run() {  4 try {  5 Method getCleanerMethod = buffer.getClass().getMethod("cleaner",new Class[0]);  6 getCleanerMethod.setAccessible(true);  7 sun.misc.Cleaner cleaner =(sun.misc.Cleaner)getCleanerMethod.invoke(buffer,new Object[0]);  8  cleaner.clean();  9 } catch(Exception e) { 10  e.printStackTrace(); 11  } 12 return null;}}); 13 14 }
複製代碼

不知道爲何SUN不提供ByteBuffer的派生。畢竟這是一個很實用的類,若是容許派生,那麼我就能夠操做的就不只僅限於堆內存和文件了,我能夠擴展到任何存儲設備。app

相關文章
相關標籤/搜索