Java NIO 簡介
Java NIO,即Java New IO,是Java IO的2.0版本,since from JDK1.4。JDK1.4以前提供的都是傳統的IO,即我們經常使用的InputStream/OutputStream/Reader/Writer等。對於傳統IO,我們可以利用流的裝飾功能,使其具有Buffer功能,本質上是利用byte[]完成的。而Java NIO單獨把Buffer的功能抽取出來,而且還提供了很多特性,下面我們一起來看看吧~
Buffer家族
看下java.nio.Buffer的子類:
對於基本數據類型,基本上都有與之對應的Buffer類,而ByteBuffer最常用,ByteBuffer下面又有2個特殊的子類。
ByteBuffer
先來一段代碼,有點感性認識吧:
public static void main(String[] args) {
//分配Buffer緩沖區大小 其本質是指定byte[]大小
ByteBuffer buffer = ByteBuffer.allocate(10);
printBuffer(buffer);
buffer.put((byte)1);
printBuffer(buffer);
buffer.flip();
printBuffer(buffer);
}
public static void printBuffer(Buffer buffer){
System.out.println("--------");
System.out.println("position : " + buffer.position());
System.out.println("limit : " + buffer.limit());
System.out.println("capacity : " + buffer.capacity());
System.out.println("--------");
}
既然要使用ByteBuffer,必然要知道如何創建它!常見的創建ByteBuffer的方式有如下幾種:
分配堆內存的方式
public static ByteBuffer allocate(int capacity) {
if (capacity < 0)
throw new IllegalArgumentException();
return new HeapByteBuffer(capacity, capacity);
}
分配直接內存,即C HEAP的方式
public static ByteBuffer allocateDirect(int capacity) {
return new DirectByteBuffer(capacity);
}
需要注意的是,DirectByteBuffer是MappedByteBuffer的子類!
直接包裝byte[]形成ByteBuffer
public static ByteBuffer wrap(byte[] array,
int offset, int length){
try {
return new HeapByteBuffer(array, offset, length);
} catch (IllegalArgumentException x) {
throw new IndexOutOfBoundsException();
}
}
由於ByteBuffer是atstract class,因此我們使用的都是它的2個具體子類:HeapByteBuffer/MappedByteBuffer。
我們可以跟蹤下HeapByteBuffer/DirectByteBuffer的構造方法,發現它們其實就做了一件事:
初始化byte[]以及一些屬性,比如mark,position,limit,capacity。
position vs limit vs capacity
Java NIO中ByteBuffer除了有byte[]之外,還提供了一些屬性,這樣相比傳統IO,操作更加靈活方便。
首先來說,capacity是byte[]的容量大小,一般是初始化好後,就不會在變化了的,而position,limit這2個屬性,會隨著對緩沖區的read/write操作而發生變化。
position:下一個應該讀取的位置
limit:在byte[]中有效讀取位置的最大值
下面,我們來做一個例子具體說明:利用ByteBuffer來拷貝文件
public static void closeStream(Closeable... closeable){
for(Closeable c : closeable){
if(c != null){
try {
c.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws IOException {
FileInputStream srcFile = new FileInputStream("E:\\tmp\\Shell學習筆記.pdf");
FileOutputStream destFile = new FileOutputStream("E:\\tmp\\Shell學習筆記COPY.pdf");
ByteBuffer byteBuffer = ByteBuffer.allocate(1024 * 1024);
FileChannel in = srcFile.getChannel();
FileChannel out = destFile.getChannel();
while(in.read(byteBuffer) != -1){
byteBuffer.flip();
out.write(byteBuffer);
byteBuffer.clear();
}
closeStream(srcFile,destFile);
}
其實,通過上面的代碼,我們已經揭示了Java NIO的3個核心概念中的2個:緩沖區與通道
以前,對於傳統IO,我們面對的是流,操作的是一個個字節,而NIO,我們面對的是緩沖區,操作的將是一個個塊。
具體來說,是這樣的:
讀取輸入,比如讀取文件,那麼應該通過FileInputStream/RandomAccessFile進行獲取通道;創建緩沖區buffer;然後調用通道的read操作,將數據讀入buffer。寫操作,則相反。
上面代碼中,調用了buffer的2個重要方法:flip()/clear(),他們是干嘛的呢?
直接看源碼:
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
flip並沒有做什麼,只是將limit的位置設置為position,而position的位置回到0
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
clear則更加簡單,回到最初的狀態!
為什麼要調用他們來改變limit/positon呢?
要知道,如果channel.read(buffer),這個操作,是要改變position的;如果我們繼續otherchannel.write(buffer),那麼將寫入的是未知數據。最好的方式,是將position現在所處的位置交給limit,而position置為0,這樣就到達了將緩沖區的內容重新讀出!而調用clear的目的,就更加單純,就是希望在read(buffer)的時候從0開始!
mark是來做什麼的?
在buffer中,mark默認是被置為-1的。我們先來看看與mark有直接關系的2個方法:
public final Buffer mark() {
mark = position;
return this;
}
public final Buffer reset() {
int m = mark;
if (m < 0)
throw new InvalidMarkException();
position = m;
return this;
}
通過mark()我們利用mark記住了position,而通過reset()我們將position的值還原到mark。
那麼事實就清楚了,我們可以先調用mark()記住當前POSITION的位置,然後我們去做其他的一些事情,最後通過reset()在找回POSITION的位置開始下一步!
allocateDirect
allocateDirect方式創建的是一個DirectByteBuffer,直接內存,這是用來做什麼的呢?
我們可以先來看看常規的IO操作流程:
很顯然,JVM只是普通的用戶進程,能夠和IO設備打交道的是KERNEL空間,JVM需要從KERNEL拷進INPUT DATA,拷出OUTPUT DATA到KERNEL。當然,頻繁的拷進拷出操作是費時的。而DirectBuffer將跳過JVM拷進拷出這一層。
MappedByteBuffer:內存映射IO
我們經常是在內存中分配一段空間,操作完畢後,寫入磁盤;那麼能不能在磁盤上直接分配一段空間,供我們進行IO操作呢?MappedByteBuffer就是這樣的,它會在磁盤上分配一段緩沖區,對緩存區的操作就是對磁盤的操作!
來看看“高性能”的拷貝文件方式:利用MappedByteBuffer
public static void main(String[] args) throws IOException {
//FileInputStream fis = new FileInputStream("E:\\tmp\\Shell學習筆記.pdf");
//FileOutputStream fos = new FileOutputStream("E:\\tmp\\Shell學習筆記COPY.pdf");
RandomAccessFile fis = new RandomAccessFile("E:\\tmp\\Shell學習筆記.pdf","r");
RandomAccessFile fos = new RandomAccessFile("E:\\tmp\\Shell學習筆記COPY.pdf","rw");
FileChannel in = fis.getChannel();
FileChannel out = fos.getChannel();
long size = in.size();
ByteBuffer buffer = out.map(MapMode.READ_WRITE, 0, size);
in.read(buffer);
closeStream(fis,fos);
}
可以看得出,先利用FileChannel的map方法獲取一個可讀、可寫的position=0,大小為size的MappedByteBuffer,對這個buffer的操作就將直接反映到磁盤上!
【注意到,利用FileInputStream獲取到的CHANNEL是只讀的,利用FileOutputStream獲取到的CHANNEL是只寫的,而map獲取BUFFER需要讀寫權限,因此要利用RandromAccessFile來進行讀寫設置!】
到這裡,JAVA NIO就介紹了一部分內容了,我也從知道有NIO,到開始實踐NIO了,HAPPY.....