NIO入門
前段時間在公司裡處理一些大的數據,並對其進行分詞、提取關鍵字等。雖說任務基本完成了(效果也不是特別好),對於Java還沒入門的我來說前前後後花了2周的時間,我自己也是醉了。當然也有涉及到機器學習的知識,我想陸陸續續的記錄下我的這一次任務的過程,也算做一個總結。
首先,手上有這麼個達G級別的文件,按照Java普通I/O的方式肯定是不行的了,劃分文件的話,也不知何年何月才能讀完。所以後來上網查找了相關資料,才知道有這麼個神奇的NIO。
在Java編程中,I/O是用流的方式讀取文件,所有I/O都被視為單個的字節的移動,通過一個稱為Stream的對象一次移動一個字節。Java中新的輸入/輸出(NIO)庫是在JDK1.4中引入的。NIO彌補了原來I/O的不足,它在標准Java代碼中提供了高速、面向塊的I/O。通過定義包含數據的塊,以及通過以塊的形式來處理這些數據,NIO不用使用本機代碼就可以利用低級優化,這是原來的I/O包所無法做到的。
流與塊的比較
原來的I/O庫和NIO最重要的區別就是數據打包和傳輸的方式,原來的I/O以流的方式處理數據,而NIO以塊的方式處理數據。
面向流的I/O系統一次一個字節的處理數據,一個輸入流產生一個字節的數據,一個輸出流產生一個字節的數據。
一個面向塊的I/O系統以塊的形式處理數據。每一個操作都在一步中產生或者消費一個數據塊。按塊處理數據比按字節處理數據要快得多,即便它沒有面向流的I/O那樣的簡單性。
通道和緩沖區
通道和緩沖區是NIO中的核心對象,幾乎在每一個I/O操作中都要使用它們。
通道是對原I/O包中的流的模擬。到任何目的地或來自任何地方的所有數據都必須通過一個Channel對象。一個Buffer實質上是一個容器對象。發送給一個通道的所有對象都必須首先存放到緩沖區中;同樣的,從通道中讀取任何的數據都必須首先讀取到緩沖區裡。
什麼是緩沖區?
Buffer是一個對象,它包含一些要寫入或者剛讀出的數據。 在 NIO 中加入Buffer對象,體現了新庫與原 I/O 的一個重要區別。在面向流的 I/O 中,您將數據直接寫入或者將數據直接讀到Stream對象中。在 NIO 庫中,所有數據都是用緩沖區處理的。在讀取數據時,它是直接讀到緩沖區中的。在寫入數據時,它是寫入到緩沖區中的。任何時候訪問 NIO 中的數據,您都是將它放到緩沖區中。緩沖區實質上是一個數組。通常它是一個字節數組,但是也可以使用其他種類的數組。但是一個緩沖區不僅僅是一個數組。緩沖區提供了對數據的結構化訪問,而且還可以跟蹤系統的讀/寫進程。
緩沖區類型
最常用的緩沖區類型是ByteBuffer。一個ByteBuffer可以在其底層字節數組上進行 get/set 操作(即字節的獲取和設置)。ByteBuffer不是 NIO 中唯一的緩沖區類型。事實上,對於每一種基本 Java 類型都有一種緩沖區類型:
每一個Buffer類都是Buffer接口的一個實例。 除了ByteBuffer,每一個 Buffer 類都有完全一樣的操作,只是它們所處理的數據類型不一樣。因為大多數標准 I/O 操作都使用ByteBuffer,所以它具有所有共享的緩沖區操作以及一些特有的操作。
下面看一下FloatBuffer的簡單例子:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.channels.FileChannel;
// UseFloatBuffer
public class UseFloatBuffer {
public static void main(String[] args) throws Exception {
FloatBuffer fb=FloatBuffer.allocate(10);
for (int i=0; i<fb.capacity(); i++) {
float f=(float)((float)i/10*(2*Math.PI));
fb.put(f);
}
fb.flip();
while (fb.hasRemaining()){
float f=fb.get();
System.out.println(f);
}
}
}
什麼是通道?
Channel是一個對象,可以通過它讀取和寫入數據。拿 NIO 與原來的 I/O 做個比較,通道就像是流。正如前面提到的,所有數據都通過 Buffer 對象來處理。您永遠不會將字節直接寫入通道中,相反,您是將數據寫入包含一個或者多個字節的緩沖區。同樣,您不會直接從通道中讀取字節,而是將數據從通道讀入緩沖區,再從緩沖區獲取這個字節。簡而言之,就是NIO的大致流程為:輸入文件->緩沖區->通道->緩沖區->程序處理數據->緩沖區->通道->緩沖區->輸出文件;I/O的大致流程為:輸入文件->流->程序處理數據->流->輸出文件。
通道類型
通道與流的不同之處在於通道是雙向的。而流只是在一個方向上移動(一個流必須是InputStream或者OutputStream的子類),而通道可以用於讀、寫或者同時用於讀寫。
實踐起來:NIO 中的讀和寫
讀和寫是 I/O 的基本過程。從一個通道中讀取很簡單:只需創建一個緩沖區,然後讓通道將數據讀到這個緩沖區中;寫入也相當簡單:創建一個緩沖區,用數據填充它,然後讓通道用這些數據來執行寫入操作。
從文件中讀取
如果使用原來的 I/O,那麼我們只需創建一個FileInputStream並從它那裡讀取。而在 NIO 中,情況稍有不同:我們首先從FileInputStream獲取一個Channel對象,然後使用這個通道來讀取數據。
在 NIO 系統中,任何時候執行一個讀操作,您都是從通道中讀取,但是您不是直接從通道讀取。因為所有數據最終都駐留在緩沖區中,所以您是通過通道讀到緩沖區中的數據。
因此讀取文件涉及三個步驟:
(1) 從FileInputStream獲取Channel
(2) 創建Buffer
(3) 將數據從Channel讀到Buffer中。
1 FileInputStream fin=new FileInputStream("read.txt");
2 FileChannel fc=fin.getChannel();
3 ByteBuffer buffer=ByteBuffer.allocate(1024);
4 fc.read(buffer);
寫入文件
在 NIO 中寫入文件類似於從文件中讀取。首先從FileOutputStream獲取一個通道;下一步是創建一個緩沖區並在其中放入一些數據 - 在這裡,數據將從一個名為data的數組中取出,最後一步是寫入緩沖區中。
FileOutputStream fout=new FileOutputStream("write.txt");
FileChannel fc=fout.getChannel();
ByteBuffer buffer=ByteBuffer.allocate(1024);
for (int i=0; i<data.length; i++) {
buffer.put(data[i]);
}
buffer.flip();
fc.write(buffer);
實戰練習
我們以一個名為 CopyFile.java 的簡單程序作為這個練習的基礎,它將一個文件的所有內容拷貝到另一個文件中。CopyFile.java 執行三個基本操作:首先創建一個Buffer,然後從源文件中將數據讀到這個緩沖區中,然後將緩沖區寫入目標文件。這個程序不斷重復 ― 讀、寫、讀、寫 ― 直到源文件結束。
// CopyFile
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
public class CopyFile {
static public void main( String args[] ) throws Exception {
String infile="E:\\北京歡迎你.txt";
String outfile="E:\\out.txt";
FileInputStream fin=new FileInputStream(infile);
FileOutputStream fout=new FileOutputStream(outfile);
FileChannel fcin = fin.getChannel();
FileChannel fcout = fout.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (true) {
buffer.clear();
int r=fcin.read(buffer);
if (r == -1) {
break;
}
buffer.flip();
fcout.write(buffer);
}
}
}