1.1 相關概念
Java IO
Java IO即Java 輸入輸出系統。不管我們編寫何種應用,都難免和各種輸入輸出相關的媒介打交道,其實和媒介進行IO的過程是十分複雜的,這要考慮的因素特别多,比如我們要考慮和哪種媒介進行IO(檔案、控制台、網絡),我們還要考慮具體和它們的通信方式(順序、随機、二進制、按字元、按字、按行等等)。Java類庫的設計者通過設計大量的類來攻克這些難題,這些類就位于java.io包中。
在JDK1.4之後,為了提高Java IO的效率,Java又提供了一套新的IO,Java New IO簡稱Java NIO。它在标準java代碼中提供了高速的面向塊的IO操作。本篇文章重點介紹Java IO,關于Java NIO請參考我的另兩篇文章:
Java NIO詳解(一) Java NIO詳解(二)流
在Java IO中,流是一個核心的概念。流從概念上來說是一個連續的資料流。你既可以從流中讀取資料,也可以往流中寫資料。流與資料源或者資料流向的媒介相關聯。在Java IO中流既可以是位元組流(以位元組為機關進行讀寫),也可以是字元流(以字元為機關進行讀寫)。
IO相關的媒介
Java的IO包主要關注的是從原始資料源的讀取以及輸出原始資料到目标媒介。以下是最典型的資料源和目标媒介:
- 檔案
- 管道
- 網絡連接配接
- 記憶體緩存
- System.in, System.out, System.error(注:Java标準輸入、輸出、錯誤輸出)
二、Java IO類庫的架構
2.1 Java IO的類型
雖然java IO類庫龐大,但總體來說其架構還是很清楚的。從是讀媒介還是寫媒介的次元看,Java IO可以分為:
- 輸入流:InputStream和Reader
- 輸出流:OutputStream和Writer
而從其處理流的類型的次元上看,Java IO又可以分為:
- 位元組流:InputStream和OutputStream
- 字元流:Reader和Writer
下面這幅圖就清晰的描述了JavaIO的分類:
- | 位元組流 | 字元流 |
---|---|---|
輸入流 | InputStream | Reader |
輸出流 | OutputStream | Writer |
我們的程式需要通過InputStream或Reader從資料源讀取資料,然後用OutputStream或者Writer将資料寫入到目标媒介中。其中,InputStream和Reader與資料源相關聯,OutputStream和writer與目标媒介相關聯。 以下的圖說明了這一點:
這裡寫圖檔描述
2.2 IO 類庫
上面我們介紹了Java IO中的四各類:InputStream、OutputStream、Reader、Writer,其實在我們的實際應用中,我們用到的一般是它們的子類,之是以設計這麼多子類,目的就是讓每一個類都負責不同的功能,以友善我們開發各種應用。各類用途彙總如下:
- 檔案通路
- 網絡通路
- 記憶體緩存通路
- 線程内部通信(管道)
- 緩沖
- 過濾
- 解析
- 讀寫文本 (Readers / Writers)
- 讀寫基本類型資料 (long, int etc.)
- 讀寫對象
下面我們就通過兩張圖來大體了解一下這些類的繼承關系及其作用
圖1:java io 類的內建關系
圖2:java io中各個類所負責的媒介
三、Java IO的基本用法
3.1 Java IO :位元組流
通過上面的介紹我們已經知道,位元組流對應的類應該是InputStream和OutputStream,而在我們實際開發中,我們應該根據不同的媒介類型選用相應的子類來處理。下面我們就用位元組流來操作檔案媒介:
例1,用位元組流寫檔案
public static void writeByteToFile() throws IOException{
String hello= new String( "hello word!");
byte[] byteArray= hello.getBytes();
File file= new File( "d:/test.txt");
//因為是用位元組流來寫媒介,是以對應的是OutputStream
//又因為媒介對象是檔案,是以用到子類是FileOutputStream
OutputStream os= new FileOutputStream( file);
os.write( byteArray);
os.close();
}
例2,用位元組流讀檔案
public static void readByteFromFile() throws IOException{
File file= new File( "d:/test.txt");
byte[] byteArray= new byte[( int) file.length()];
//因為是用位元組流來讀媒介,是以對應的是InputStream
//又因為媒介對象是檔案,是以用到子類是FileInputStream
InputStream is= new FileInputStream( file);
int size= is.read( byteArray);
System. out.println( "大小:"+size +";内容:" +new String(byteArray));
is.close();
}
3.2 Java IO :字元流
同樣,字元流對應的類應該是Reader和Writer。下面我們就用字元流來操作檔案媒介:
例3,用字元流讀檔案
public static void writeCharToFile() throws IOException{
String hello= new String( "hello word!");
File file= new File( "d:/test.txt");
//因為是用字元流來讀媒介,是以對應的是Writer,又因為媒介對象是檔案,是以用到子類是FileWriter
Writer os= new FileWriter( file);
os.write( hello);
os.close();
}
例4,用字元流寫檔案
public static void readCharFromFile() throws IOException{
File file= new File( "d:/test.txt");
//因為是用字元流來讀媒介,是以對應的是Reader
//又因為媒介對象是檔案,是以用到子類是FileReader
Reader reader= new FileReader( file);
char [] byteArray= new char[( int) file.length()];
int size= reader.read( byteArray);
System. out.println( "大小:"+size +";内容:" +new String(byteArray));
reader.close();
}
3.3 Java IO :位元組流轉換為字元流
位元組流可以轉換成字元流,java.io包中提供的InputStreamReader類就可以實作,當然從其命名上就可以看出它的作用。其實這涉及到另一個概念,IO流的組合,後面我們詳細介紹。下面看一個簡單的例子:
例5 ,位元組流轉換為字元流
public static void convertByteToChar() throws IOException{
File file= new File( "d:/test.txt");
//獲得一個位元組流
InputStream is= new FileInputStream( file);
//把位元組流轉換為字元流,其實就是把字元流和位元組流組合的結果。
Reader reader= new InputStreamReader( is);
char [] byteArray= new char[( int) file.length()];
int size= reader.read( byteArray);
System. out.println( "大小:"+size +";内容:" +new String(byteArray));
is.close();
reader.close();
}
3.4 Java IO :IO類的組合
從上面位元組流轉換成字元流的例子中我們知道了IO流之間可以組合(或稱嵌套),其實組合的目的很簡單,就是把多種類的特性融合在一起以實作更多的功能。組合使用的方式很簡單,通過把一個流放入另一個流的構造器中即可實作,兩個流之間可以組合,三個或者更多流之間也可組合到一起。當然,并不是任意流之間都可以組合。關于組合就不過多介紹了,後面的例子中有很多都用到了組合,大家好好體會即可。
3.5 Java IO:檔案媒介操作
File是Java IO中最常用的讀寫媒介,那麼我們在這裡就對檔案再做進一步介紹。
3.5.1 File媒介
例6 ,File操作
public class FileDemo {
public static void main(String[] args) {
//檢查檔案是否存在
File file = new File( "d:/test.txt");
boolean fileExists = file.exists();
System. out.println( fileExists);
//建立檔案目錄,若父目錄不存在則傳回false
File file2 = new File( "d:/fatherDir/subDir");
boolean dirCreated = file2.mkdir();
System. out.println( dirCreated);
//建立檔案目錄,若父目錄不存則連同父目錄一起建立
File file3 = new File( "d:/fatherDir/subDir2");
boolean dirCreated2 = file3.mkdirs();
System. out.println( dirCreated2);
File file4= new File( "d:/test.txt");
//判斷長度
long length = file4.length();
//重命名檔案
boolean isRenamed = file4.renameTo( new File("d:/test2.txt"));
//删除檔案
boolean isDeleted = file4.delete();
File file5= new File( "d:/fatherDir/subDir");
//是否是目錄
boolean isDirectory = file5.isDirectory();
//列出檔案名
String[] fileNames = file5.list();
//列出目錄
File[] files = file4.listFiles();
}
}
3.5.3 随機讀取File檔案
通過上面的例子我們已經知道,我們可以用FileInputStream(檔案字元流)或FileReader(檔案位元組流)來讀檔案,這兩個類可以讓我們分别以字元和位元組的方式來讀取檔案内容,但是它們都有一個不足之處,就是隻能從檔案頭開始讀,然後讀到檔案結束。
但是有時候我們隻希望讀取檔案的一部分,或者是說随機的讀取檔案,那麼我們就可以利用RandomAccessFile。RandomAccessFile提供了
seek()
方法,用來定位将要讀寫檔案的指針位置,我們也可以通過調用
getFilePointer()
方法來擷取目前指針的位置,具體看下面的例子:
例7,随機讀取檔案
public static void randomAccessFileRead() throws IOException {
// 建立一個RandomAccessFile對象
RandomAccessFile file = new RandomAccessFile( "d:/test.txt", "rw");
// 通過seek方法來移動讀寫位置的指針
file.seek(10);
// 擷取目前指針
long pointerBegin = file.getFilePointer();
// 從目前指針開始讀
byte[] contents = new byte[1024];
file.read( contents);
long pointerEnd = file.getFilePointer();
System. out.println( "pointerBegin:" + pointerBegin + "\n" + "pointerEnd:" + pointerEnd + "\n" + new String(contents));
file.close();
}
例8,随機寫入檔案
public static void randomAccessFileWrite() throws IOException {
// 建立一個RandomAccessFile對象
RandomAccessFile file = new RandomAccessFile( "d:/test.txt", "rw");
// 通過seek方法來移動讀寫位置的指針
file.seek(10);
// 擷取目前指針
long pointerBegin = file.getFilePointer();
// 從目前指針位置開始寫
file.write( "HELLO WORD".getBytes());
long pointerEnd = file.getFilePointer();
System. out.println( "pointerBegin:" + pointerBegin + "\n" + "pointerEnd:" + pointerEnd + "\n" );
file.close();
}
3.6 Java IO:管道媒介
管道主要用來實作同一個虛拟機中的兩個線程進行交流。是以,一個管道既可以作為資料源媒介也可作為目标媒介。
需要注意的是java中的管道和Unix/Linux中的管道含義并不一樣,在Unix/Linux中管道可以作為兩個位于不同空間程序通信的媒介,而在java中,管道隻能為同一個JVM程序中的不同線程進行通信。和管道相關的IO類為:PipedInputStream和PipedOutputStream,下面我們來看一個例子:
例9,讀寫管道
public class PipeExample {
public static void main(String[] args) throws IOException {
final PipedOutputStream output = new PipedOutputStream();
final PipedInputStream input = new PipedInputStream(output);
Thread thread1 = new Thread( new Runnable() {
@Override
public void run() {
try {
output.write( "Hello world, pipe!".getBytes());
} catch (IOException e) {
}
}
});
Thread thread2 = new Thread( new Runnable() {
@Override
public void run() {
try {
int data = input.read();
while( data != -1){
System. out.print(( char) data);
data = input.read();
}
} catch (IOException e) {
} finally{
try {
input.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
});
thread1.start();
thread2.start();
}
3.7 Java IO:網絡媒介
關于Java IO面向網絡媒介的操作即Java 網絡程式設計,其核心是Socket,同磁盤操作一樣,java網絡程式設計對應着兩套API,即Java IO和Java NIO,關于這部分我會準備專門的文章進行介紹。
3.8 Java IO:BufferedInputStream和BufferedOutputStream
BufferedInputStream顧名思義,就是在對流進行寫入時提供一個buffer來提高IO效率。在進行磁盤或網絡IO時,原始的InputStream對資料讀取的過程都是一個位元組一個位元組操作的,而BufferedInputStream在其内部提供了一個buffer,在讀資料時,會一次讀取一大塊資料到buffer中,這樣比單位元組的操作效率要高的多,特别是程序磁盤IO和對大量資料進行讀寫的時候。
使用BufferedInputStream十分簡單,隻要把普通的輸入流和BufferedInputStream組合到一起即可。我們把上面的例2改造成用BufferedInputStream進行讀檔案,請看下面例子:
例10 ,用緩沖流讀檔案
public static void readByBufferedInputStream() throws IOException {
File file = new File( "d:/test.txt");
byte[] byteArray = new byte[( int) file.length()];
//可以在構造參數中傳入buffer大小
InputStream is = new BufferedInputStream( new FileInputStream(file),2*1024);
int size = is.read( byteArray);
System. out.println( "大小:" + size + ";内容:" + new String(byteArray));
is.close();
}
關于如何設定buffer的大小,我們應根據我們的硬體狀況來确定。對于磁盤IO來說,如果硬碟每次讀取4KB大小的檔案塊,那麼我們最好設定成這個大小的整數倍。因為磁盤對于順序讀的效率是特别高的,是以如果buffer再設定的大寫可能會帶來更好的效率,比如設定成44KB或84KB。
還需要注意一點的就是磁盤本身就會有緩存,在這種情況下,BufferedInputStream會一次讀取磁盤緩存大小的資料,而不是分多次的去讀。是以要想得到一個最優的buffer值,我們必須得知道磁盤每次讀的塊大小和其緩存大小,然後根據多次試驗的結果來得到最佳的buffer大小。
BufferedOutputStream的情況和BufferedInputStream一緻,在這裡就不多做描述了。
3.9 Java IO:BufferedReader和BufferedWriter
BufferedReader、BufferedWriter
的作用基本和BufferedInputStream、BufferedOutputStream一緻,具體用法和原理都差不多 ,隻不過一個是面向字元流一個是面向位元組流。同樣,我們将改造字元流中的例4,給其加上buffer功能,看例子:
public static void readByBufferedReader() throws IOException {
File file = new File( "d:/test.txt");
// 在字元流基礎上用buffer流包裝,也可以指定buffer的大小
Reader reader = new BufferedReader( new FileReader(file),2*1024);
char[] byteArray = new char[( int) file.length()];
int size = reader.read( byteArray);
System. out.println( "大小:" + size + ";内容:" + new String(byteArray));
reader.close();
}
原文連結:
http://blog.csdn.net/suifeng3051/article/details/48344587