FileInputStream,FileOutputStream和RandomAccessFile 獲取的FileChannel

package io;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.NonReadableChannelException;
import java.nio.channels.NonWritableChannelException;

/**
 * 
 * FileChanel是NIO中的類,NIO稱爲新 I/O,使用了更接近操作系統執行I/O的方式:通道和緩衝器.
 * 
 * 即先打開一個通道,然後創建一個緩衝器,將通道和緩衝器關聯.通道是雙向的,既可以讀也可以寫.
 * 從通道中讀取數據時,先把數據讀入緩衝器,然後從緩衝器中獲取數據;
 * 往通道中寫數據時,先把數據寫入緩衝器,然後再從緩衝器中將數據寫入通道.
 * 
 * 新I/O出來後,還修改了舊IO包中的三個類,FileInputStream,FileOutputStream和RandomAccessFile.
 * 通過這三個類的getChannel()返回一個FileChannel通道.
 * 
 * 注意,上文說了Channel是雙向的,即,可以讀,也可以寫(FileChannel有read和write方法).
 * 這個對於RandomAccessFile返回的FileChannel來說,不是很奇怪.
 * 
 * 但是對於 使用 FileInputStream 返回的FileChannel來寫(write),
 * 或者使用FileOutputStream返回的FileChannel來讀(read)
 * 的話,就有些奇怪了.
 * 
 * 爲了排除疑問,使用下面的代碼測試之.
 * 測試結果:
 * (1)FileInputStream 生成的FileChannel是隻讀的,嘗試寫入是拋異常:NonWritableChannelException
 * (2)FileOutputStream 生成的FileChannel只能寫入,並且一旦建立這個對象,則與之關聯的文件內容被清空.
 * 如果嘗試讀取獲取的FileChannel的內容,則拋異常:NonReadableChannelException
 * (3)由RandomAccessFile獲取的FileChannel則仍舊可讀可寫.
 *
 */

public class TestFileChanel
{
  public static void main(String[] args) throws IOException
  {
    boolean bInputChannel = false;
    boolean bOutputchannel = false;
    boolean bRandomAccessChannel = true;

    // 首先,使用RandomAccessFile創建一個4*4字節的文件(相當於存入4個int型的0)
    File f = new File("D:\\D\\test_channel.dat");
    // 如果文件存在,先刪除之,比較暴力,需要確保D:\\D\\test_channel.dat 這個文件不存在
    if (f.exists())
    {
      f.delete();
    }

    RandomAccessFile rf = new RandomAccessFile(f, "rw");
    rf.setLength(4 * 4);  //4 個 int 的容量
    rf.writeInt(1); //前4個字節 寫入整型的 1
    rf.seek(3 * 4); //position移動到第3個整型(3*4 = 12)之後,
    rf.writeInt(5);// 最後4個字節 寫入一個整型的5
    rf.close();
    // 完成之後,使用UE打開這個二進制文件,可見文件結構如下(|分隔符不存在的,這裏爲了方便閱讀):
    // 00 00 00 01 | 00 00 00 00 | 00 00 00 00 | 00 00 00 05

    FileChannel fc = new RandomAccessFile(f, "rw").getChannel();
    if (bRandomAccessChannel)
    {
      System.out.println("------使用RandomAccessFile返回的FileChannel--------");
      // 測試使用FileOutputStream返回的FileChannel 從 上面文件的文件中 讀取 一個int(4字節)
      fc = new RandomAccessFile(f, "rw").getChannel();
      try
      {
        fc.position(4);// 文件通道位置設置到第5個字節之前,第4個字節之後
        //00 00 00 01 $ 00 00 00 00 | 00 00 00 00 | 00 00 00 05
        //此時position 在上面的$處,注意 這裏是文件的position
        
        ByteBuffer ib = ByteBuffer.allocate(8);//分配一個buffer 8字節(capacity = 8),且全部初始化爲0了
        //可以認爲此時的buffe結構向下面這個樣子
        // $ 00 00 00 01 | 00 00 00 00
        // $爲初始化後position位置, | 是分隔符 便於閱讀,此時limit等於capacity
        
        System.out.printf("buffer ByteBuffer.allocate(8) 初始化完畢之後%nposition=%1$d,limit=%2$d %n",ib.position(),ib.limit());

        // 先寫一個整數7
        System.out.printf("buffer 在put之前,position=%1$d %n",ib.position());
        ib.asIntBuffer().put(7);
        System.out.printf("buffer 在put之後,position=%1$d %n",ib.position());
        //此時buffer格式爲:
        //$ 00 00 00 07 | 00 00 00 00
        //注意:此時buffer的position並沒有因爲put而向前移動了,仍然在開頭位置.
        
        ib.limit(ib.position() + 4);
        //限制只寫入buffer中的00 00 00 07 這個數據,即寫入整型的7到文件
        fc.write(ib);
        System.out.println("寫入後,buffer的position:" + ib.position());//寫入後,buffer的position:4
        //此時buffer格式爲:
        // 00 00 00 07 $ 00 00 00 00
        // 注意,因Channel 將Buffer中的的數據寫入,
        //導致寫入後buffer的position從之前的位置(0)移動了一個limit的位置(4)
        
        fc.force(true);//強制將所有對此通道的文件更新寫入包含該文件的存儲設備中。
        System.out.printf("寫入完成後,文件通道位置爲:%1$d,buffer位置爲:%2$d %n" , fc.position(),ib.position());
        //寫入完成後,文件通道位置爲:8,buffer位置爲:4
        //寫入後,文件的二進制數據如下:
        //00 00 00 01 | 00 00 00 07 $ 00 00 00 00 | 00 00 00 05
        //寫入完成後,buffer數據如下:
        //00 00 00 07 $ 00 00 00 00

        System.out.printf("調用clear()之 前,buffer位置爲:%1$d,limit爲:%2$d %n" ,ib.position(),ib.limit());
        ib.clear(); //清空buffer,將位置設置爲 0,將限制設置爲容量,並丟棄標記
        
        // 測試從通道中讀一個整數
        System.out.printf("調用clear()之後,buffer位置爲:%1$d,limit爲:%2$d %n" ,ib.position(),ib.limit());
        //調用clear()之後,buffer位置爲:0,limit爲:8
        //此時buffer數據如下:
        // $ 00 00 00 00 | 00 00 00 00
        fc.read(ib);
        
        
        //read的時候從Channel(fc)的當前位置開始read,此時Channel(fc)的位置爲8
        System.out.printf("調用fc.read(ib)讀取之後,buffer位置爲:%1$d %n" ,ib.position());
        //調用fc.read(ib)讀取之後,buffer位置爲:8
        //注意read之後,如果文件內容大於buffer,read limit - position 個字節,並從position填充到limit
        //此時buffer內容爲:
        // 00 00 00 00 | 00 00 00 05 $
        
        System.out.printf("調用fc.read(ib)讀取之後,文件通道fc的位置爲:%1$d %n" ,fc.position());
        //
        
        ib.position(4);
        //將buffer的position提前4,讀取最後一個整數,否則報java.nio.BufferUnderflowException 異常
        //此時buffer內容爲:
        // 00 00 00 00 $ 00 00 00 05
        int iTmp = ib.asIntBuffer().get();//讀取了00 00 00 05
        System.out.printf("調用ib.asIntBuffer().get()之後,buffer位置爲:%1$d %n" ,ib.position());
        //調用ib.asIntBuffer().get()之後,buffer位置爲:4 
        System.out.println(iTmp); //輸出5

      }
      catch (NonReadableChannelException e)
      {
        System.out.println("通道不可讀");
      }
      finally
      {
        fc.close();
      }
    }
    
    if (bInputChannel)
    {
      System.out.println("------使用FileInputStream返回的FileChannel--------");
      // 接下來,測試使用FileInputStream返回的FileChannel往上面文件的文件中寫入一個int(4字節)
      fc = new FileInputStream(f).getChannel();
      try
      {
        fc.position(4);// 通道位置設置到第5個字節之前,第4個字節之後
        ByteBuffer ib = ByteBuffer.allocate(4);

        // 測試從通道中讀一個整數
        int iTmp = ib.asIntBuffer().get();
        System.out.println(iTmp); // 可以讀出一個int

        ib.clear();

        ib.asIntBuffer().put(7);
        fc.write(ib); // 執行時報錯,拋NonWritableChannelException異常,
        //說明FileInputStream(f).getChannel()方法獲取的通道是不可寫的.

      }
      catch (NonWritableChannelException e)
      {
        System.out.println("通道不可寫");
      }
      finally
      {
        fc.close();
      }
    }
    
    if (bOutputchannel)
    {
      System.out.println("------FileOutputStream返回的FileChannel--------");
      fc = new FileOutputStream(f).getChannel();//調用這個之後,原來的文件內容被清空了,等着write
      
      // 測試使用FileOutputStream返回的FileChannel 從 上面文件的文件中 讀取 一個int(4字節)
      try
      {
        fc.position(4);// 通道位置設置到第5個字節之前,第4個字節之後
        ByteBuffer ib = ByteBuffer.allocate(16);

        // 先寫一個整數7
        ib.asIntBuffer().put(7);
        fc.write(ib);
        fc.force(true);
        ib.flip();

        ib.clear();
        // 測試從通道中讀一個整數
        fc.read(ib); // 拋出異常 NonReadableChannelException
      }
      catch (NonReadableChannelException e)
      {
        System.out.println("通道不可讀");
      }
      finally
      {
        fc.close();
      }
    }
  }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章