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();
}
}
}
}