- import java.nio.*;
- import java.nio.channel.*;
- import java.io.*;
- public static void copy(File source, File dest) throws IOException {
- FileChannel in = null, out = null;
- try {
- in = new FileInputStream(source).getChannel();
- out = new FileOutputStream(dest).getChannel();
- long size = in.size();
- MappedByteBuffer buf = in.map(FileChannel.MapMode.READ_ONLY, 0, size);
- out.write(buf);
- if (in != null) in.close();
- if (out != null) out.close();
- }
- }
其實在其他語言中,包括在最原始的SOCKET實現(BSD SOCKET),這是一個早有的功能:異步回調讀/寫事件,通過選擇器動態選擇感興趣的事件,等等.不過好在SUN終於也開始支持它了.我想這也是開放的好處之一吧(NIO是作爲JSR-51項目引入的).
這裏簡單講一下操作流程:
通過把一個套接字通道(SocketChannel)註冊到一個選擇器(Selector)中,不時調用後者的選擇(select)方法就能返回滿足的選擇鍵(SelectionKey),鍵中包含了SOCKET事件信息.
異步套接字對服務器程序來說更具吸引力.一般同步SOCKET服務器的實現都是採用線程池來處理客戶請求的,基於請求超時時間和併發線程數目的限制,如果 併發處理能力能夠達到上千就已經是不錯了.異步服務器的能力則至少是它的數倍(有人測試一個簡單的ECHO服務程序,說可以達到上萬個併發,不知道是否真 的能達到).
SocketChannel的讀寫是通過一個類叫ByteBuffer(java.nio.ByteBuffer)來操作的.這個類本身的設計是不錯的,比直接操作byte[]方便多了.
ByteBuffer有兩種模式:直接/間接.間接模式最典型(也只有這麼一種)的就是HeapByteBuffer,即操作堆內存(byte[]).但 是內存畢竟有限,如果我要發送一個1G的文件怎麼辦?不可能真的去分配1G的內存.這時就必須使用"直接"模式,即MappedByteBuffer,文 件映射.
先中斷一下,談談操作系統的內存管理.一般操作系統的內存分兩部分:物理內存;虛擬內存.虛擬內存一般使用的是頁面映像文件,即硬盤中的某個(某些)特殊的文件.操作系統負責頁面文件內容的讀寫,這個過程叫"頁面中斷/切換".
MappedByteBuffer也是類似的,你可以把整個文件(不管文件有多大)看成是一個ByteBuffer.這是一個很好的設計,除了一點,令人頭疼的一點.
MappedByteBuffer只能通過調用FileChannel的map()取得,再沒有其他方式.但是令人奇怪的是,SUN提供了map()卻沒有提供unmap().這樣會導致什麼後果呢?
舉個例子,文件test.tmp是一個臨時構建的文件,在業務處理(通過SocketChannel發送)完之後將不再有效.一般的做法都是這樣的:
(1)File file = new File("test.tmp");
FileInputStream in = new FileInputStream(file);
FileChannel ch = in.getChannel();
MappedByteBuffer buf = ch.map(FileChannel.MapMode.READ_ONLY, 0, file.length());
(2)SocketChannel sch = 已經構造好了;
while (buf.hasRemaining())
sch.write(buf);
(3)ch.close();
in.close();
file.delete();
上面的操作都會正常的完成,除了最後一步:文件無法刪除!即使你通過資源管理器直接強制刪除也不行,說"文件正在使用".
爲什麼會出現這種情況?
說"文件正在使用",說明文件句柄沒有清零,還有在使用它的地方---就是被MappedByteBuffer佔用了!儘管 FileChannel,FileInputStream都已經關閉了,但是在map裏還打開着一個文件句柄.但是在外部看不見也無法操作它.那麼這個句 柄在什麼時候纔會正常地關閉呢?根據JAVADOC的說明,是在垃圾收集的時候.而衆所周知垃圾收集是程序根本無法控制的.
既然MappedByteBuffer是從FileChannel中map()出來的,爲什麼它又不提供unmap()呢?SUN自己也沒有講清楚爲什 麼.O'Reilly的<<Java NIO>>中說是因爲"安全"的原因,但是到底unmap()會怎麼不安全,作者也沒有講清楚.
在SUN的BUG庫中,這個問題在02年就有人提交了BUG報告,但是SUN自己不認爲是BUG,而只是一個RFE(Request For Enhancement),有待改進.
好在網上牛人多.在BUG報告(http://bugs.sun.com/bugdatabase /view_bug.do?bug_id=4724038)中,有網友提出了一個解決的辦法(具體參看上面的URL),可行,至少我在 WINDOWS2000下測試是可以的.唯一的不足是並不是每次都能馬上生效(文件徹底被刪除),有的時候要延遲一會再試.
再抱怨兩句.對於網友們的BUG報告,SUN似乎不怎麼重視.粗看一下上面的BUG報告,會發現居然上世紀90年代的報告還赫然在列.有興趣的朋友不妨仔細研究研究.
還有一點忘了說了.ByteBuffer是無法派生的.因爲這個抽象類中定義了幾個包抽象方法,即實現類只能位於java.nio包中.本來自己實現 MappedByteBuffer也不難,只是效率比SUN實現的肯定要低一些.畢竟後者是可以直接與操作系統打交道的.而要是自己實現的化,只能通過一 箇中間的堆緩衝區進行過渡.
我不知道爲什麼SUN不提供ByteBuffer的派生.畢竟這是一個很實用的類,如果允許派生,那麼我就可以操作的就不僅僅限於堆內存和文件了,我可以擴展到任何存儲設備.
- public boolean copyTo(String strSourceFileName, String strDestDir) {
- File fileSource = new File(strSourceFileName);
- File fileDest = new File(strDestDir);
- // 如果源文件不存或源文件是文件夾
- if (!fileSource.exists() || !fileSource.isFile()) {
- System.out.println("錯誤: FileOperator.java copyTo函數,/n原因: 源文件["
- + strSourceFileName + "],不存在或是文件夾!");
- return false;
- }
- // 如果目標文件夾不存在
- if (!fileDest.isDirectory() || !fileDest.exists()) {
- if (!fileDest.mkdirs()) {
- System.out.println("錯誤: FileOperator.java copyTo函數,/n原因:目錄文件夾不存,在創建目標文件夾時失敗!");
- return false;
- }
- }
- try {
- String strAbsFilename = strDestDir + File.separator + fileSource.getName();
- FileInputStream fileInput = new FileInputStream(strSourceFileName);
- FileOutputStream fileOutput = new FileOutputStream(strAbsFilename);
- int i = 0;
- int count = -1;
- long nWriteSize = 0;
- long nFileSize = fileSource.length();
- byte[] data = new byte[BUFFER];
- while (-1 != (count = fileInput.read(data, 0, BUFFER))) {
- fileOutput.write(data, 0, count);
- nWriteSize += count;
- long size = (nWriteSize * 100) / nFileSize;
- long t = nWriteSize;
- String msg = null;
- if (size <= 100 && size >= 0) {
- msg = "/r拷貝文件進度: " + size + "% /t" + "/t 已拷貝: " + t;
- } else if (size > 100) {
- msg = "/r拷貝文件進度: " + 100 + "% /t" + "/t 已拷貝: " + t;
- }
- }
- fileInput.close();
- fileOutput.close();
- System.out.println("/n拷貝文件成功!");
- return true;
- } catch (Exception e) {
- System.out.println("異常信息:[");
- e.printStackTrace();
- return false;
- }
- }
將那位仁兄的代碼貼在以下:
- public static void clean(final Object buffer) throws Exception {
- 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(buffer, new Object[0]);
- cleaner.clean();
- } catch (Exception e) {
- e.printStackTrace();
- }
- return null;
- }
- });
- }