IO流主要是用來處理設備之間的數據傳輸。
流按操做的數據可分爲字符流和字節流。字節流用於處理二進制文件,好比音頻、視頻等;字符流用於處理帶有中文字符的文件,好比文本文件。html
流按照數據的流向可分爲輸入流和輸出流。輸入輸出是相對於內存來講的,輸入就是把某個地方(好比磁盤)的數據讀到內存中,輸出就是把數據從內存中寫到某個地方(好比磁盤)。
java中流的體系結構以下,因爲實現類實在太多,標記顏色的爲經常使用的類。java
FileOutputStream
咱們經常使用來向文件中寫入數據的流是FileOutputStream
,主要用於寫入諸如圖像數據之類的原始字節的流。git
構造函數以下:api
FileOutputStream(File file) 建立一個向指定 File 對象表示的文件中寫入數據的文件輸出流。 FileOutputStream(File file, boolean append) 建立一個向指定 File 對象表示的文件中寫入數據的文件輸出流。 FileOutputStream(FileDescriptor fdObj) 建立一個向指定文件描述符處寫入數據的輸出文件流,該文件描述符表示一個到文件系統中的某個實際文件的現有鏈接。 FileOutputStream(String name) 建立一個向具備指定名稱的文件中寫入數據的輸出文件流。 FileOutputStream(String name, boolean append) 建立一個向具備指定 name 的文件中寫入數據的輸出文件流。
寫入的方法有3個,具體以下:數組
void write(byte[] b) 將 b.length 個字節從指定 byte 數組寫入此文件輸出流中。 void write(byte[] b, int off, int len) 將指定 byte 數組中從偏移量 off 開始的 len 個字節寫入此文件輸出流。 void write(int b) 將指定字節寫入此文件輸出流。
舉個例子:app
String filePath = "D:\\Learning\\doc\\test.txt"; // 向test.txt文件中寫入數據 FileOutputStream fos=new FileOutputStream(filePath); fos.write("學習IO流".getBytes()); fos.close();
FileInputStream
和FileOutputStream對應的是FileInputStream
,用於讀取諸如圖像數據之類的原始字節流,文件不存在會建立。函數
構造函數以下:性能
FileInputStream(File file) 經過打開一個到實際文件的鏈接來建立一個 FileInputStream,該文件經過文件系統中的 File 對象 file 指定。 FileInputStream(FileDescriptor fdObj) 經過使用文件描述符 fdObj 建立一個 FileInputStream,該文件描述符表示到文件系統中某個實際文件的現有鏈接。 FileInputStream(String name) 經過打開一個到實際文件的鏈接來建立一個 FileInputStream,該文件經過文件系統中的路徑名 name 指定。
讀取的方法以下,read方法今後輸入流中讀取數據字節。若是沒有輸入可用,則此方法將阻塞。若是由於已經到達文件末尾而沒有更多的數據,則返回 -1
。學習
int read() 今後輸入流中讀取一個數據字節。 int read(byte[] b) 今後輸入流中將最多 b.length 個字節的數據讀入一個 byte 數組中。 int read(byte[] b, int off, int len) 今後輸入流中將最多 len 個字節的數據讀入一個 byte 數組中。
舉個例子:測試
String filePath = "D:\\Learning\\doc\\test1.txt"; //讀取test文件內容 FileInputStream fis=new FileInputStream(filePath);//方式一:一個字節一個字節的讀取 int ch=0; while ((ch=fis.read())!=-1) { System.out.print((char)ch); } //方式二:批量讀取到緩衝區,推薦使用方式 int len=0; byte[] buffer=new byte[1024]; while ((len=fis.read(buffer))!=-1){ System.out.print(new String(buffer,0,len)); } //方式三:一次性讀取,不建議使用,若是文件過大,那麼數組會很是大,可能內存不夠,建立數組也會很是耗時 //fis.available()獲取文件的字節數 byte[] buf = new byte[fis.available()]; fis.read(buf); System.out.println(new String(buf)); fis.close();
下面代碼演示瞭如何拷貝文件:
//需求:把vedio拷貝到vedio1 String sourceFilePath = "D:\\Learning\\doc\\vedio.avi"; String destFilePath = "D:\\Learning\\doc\\vedio1.avi"; //建立讀取文件對象 FileInputStream fis=new FileInputStream(sourceFilePath); //建立寫入文件對象 FileOutputStream fos=new FileOutputStream(destFilePath); //建立1M的緩衝區 byte[] buffer =new byte[1024*1024*1]; //每次讀取的長度,可能最後一次的讀取長度並無buffer.length那麼多 int len=0; while ((len=fis.read(buffer))!=-1){ fos.write(buffer,0,len); } fos.close(); fis.close();
固然,若是是很小的文件,也能夠直接向下面這樣,但對於大文件,禁止使用:
//需求:把vedio拷貝到vedio1 String sourceFilePath = "D:\\Learning\\doc\\vedio.avi"; String destFilePath = "D:\\Learning\\doc\\vedio1.avi"; //建立讀取文件對象 FileInputStream fis=new FileInputStream(sourceFilePath); //建立寫入文件對象 FileOutputStream fos=new FileOutputStream(destFilePath); byte[] buf = new byte[fis.available()]; fis.read(buf); fos.write(buf); fos.close(); fis.close();
上面咱們手動建立了緩衝區用來提升字節讀寫效率,java也提供了一樣的功能,即BufferInputStream,BufferOutputStream。
BufferedInputStream 爲另外一個輸入流添加一些功能,即緩衝輸入以及支持 mark 和 reset 方法的能力。在建立 BufferedInputStream 時,會建立一個內部緩衝區數組。
構造函數以下:
BufferedInputStream(InputStream in) 建立一個 BufferedInputStream 並保存其參數,即輸入流 in,以便未來使用。 BufferedInputStream(InputStream in, int size) 建立具備指定緩衝區大小的 BufferedInputStream 並保存其參數,即輸入流 in,以便未來使用。
讀取方法以下:
int read() 參見 InputStream 的 read 方法的常規協定。 int read(byte[] b, int off, int len) 今後字節輸入流中給定偏移量處開始將各字節讀取到指定的 byte 數組中。
該類實現緩衝的輸出流。經過設置這種輸出流,應用程序就能夠將各個字節寫入底層輸出流中,而沒必要針對每次字節寫入調用底層系統。
構造方法以下:
BufferedOutputStream(OutputStream out) 建立一個新的緩衝輸出流,以將數據寫入指定的底層輸出流。 BufferedOutputStream(OutputStream out, int size) 建立一個新的緩衝輸出流,以將具備指定緩衝區大小的數據寫入指定的底層輸出流。
write方法以下:
void write(byte[] b, int off, int len) 將指定 byte 數組中從偏移量 off 開始的 len 個字節寫入此緩衝的輸出流。 void write(int b) 將指定的字節寫入此緩衝的輸出流。
下面來看看如何使用:
//需求:把vedio拷貝到vedio1 String sourceFilePath = "D:\\Learning\\doc\\vedio.avi"; String destFilePath = "D:\\Learning\\doc\\vedio1.avi"; //建立讀取文件對象 FileInputStream fis=new FileInputStream(sourceFilePath); //建立寫入文件對象 FileOutputStream fos=new FileOutputStream(destFilePath); BufferedInputStream bis=new BufferedInputStream(fis); BufferedOutputStream bos=new BufferedOutputStream(fos); int len =0; while ((len=bis.read())!=-1){ bos.write(len); } //緩衝流是在普通流的基礎上多了一個緩衝區 , // 當讀或寫的數據達到必定量,再自動往文件裏進行讀寫,若是沒有裝滿緩衝區文件就已經讀(寫)完畢 , // 那麼這個時候已經讀寫到緩衝區的數據須要手動使其朝目標文件進行讀寫. //因此使用緩衝流必定要調用flush方法 bos.flush(); bis.close(); bos.close();
固然,也能夠本身再建立一個緩衝區進一步提升效率,也能夠在構造函數的第二個參數傳入緩衝區大小:
import java.io.*; class Test { public static void main(String[]args) throws IOException { //測試拷貝一個大小爲200M時的性能問題 test1(); test2(); test3(); } public static final int BUFFER_SIZE = 1024 * 1024 * 1; /** * 方式一:不加緩衝區的狀況 * * @throws IOException */ public static void test1() throws IOException { //需求:把vedio拷貝到vedio1 String sourceFilePath = "E:\\desktop.zip"; String destFilePath = "E:\\desktop1.zip"; //建立讀取文件對象 FileInputStream fis = new FileInputStream(sourceFilePath); //建立寫入文件對象 FileOutputStream fos = new FileOutputStream(destFilePath); BufferedInputStream bis = new BufferedInputStream(fis); BufferedOutputStream bos = new BufferedOutputStream(fos); long start = System.currentTimeMillis(); int len = 0; while ((len = bis.read()) != -1) { bos.write(len); } long end = System.currentTimeMillis(); System.out.println(end - start);//21365 bos.flush(); bis.close(); bos.close(); } /** * 方式二:加緩衝區的狀況 * @throws IOException */ public static void test2() throws IOException { //需求:把vedio拷貝到vedio1 String sourceFilePath = "E:\\desktop.zip"; String destFilePath = "E:\\desktop2.zip"; //建立讀取文件對象 FileInputStream fis = new FileInputStream(sourceFilePath); //建立寫入文件對象 FileOutputStream fos = new FileOutputStream(destFilePath); BufferedInputStream bis = new BufferedInputStream(fis); BufferedOutputStream bos = new BufferedOutputStream(fos); long start = System.currentTimeMillis(); byte[] buffer = new byte[BUFFER_SIZE]; int len = 0; while ((len = bis.read(buffer)) != -1) { bos.write(buffer, 0, len); } long end = System.currentTimeMillis(); System.out.println(end - start);//975 bos.flush(); bis.close(); bos.close(); } /** * 方式3:構造函數加入緩衝大小的狀況 * @throws IOException */ public static void test3() throws IOException { // 需求:把vedio拷貝到vedio1 String sourceFilePath = "E:\\desktop.zip"; String destFilePath = "E:\\desktop3.zip"; //建立讀取文件對象 FileInputStream fis = new FileInputStream(sourceFilePath); //建立寫入文件對象 FileOutputStream fos = new FileOutputStream(destFilePath); BufferedInputStream bis = new BufferedInputStream(fis, BUFFER_SIZE); BufferedOutputStream bos = new BufferedOutputStream(fos, BUFFER_SIZE); long start = System.currentTimeMillis(); int len = 0; while ((len = bis.read()) != -1) { bos.write(len); } long end = System.currentTimeMillis(); System.out.println(end - start);//3036 bos.flush(); bis.close(); bos.close(); } }
因而可知,手動加緩衝區的效率最高。
上面對於IO的異常處理是直接拋出,並未處理,下面介紹一下IO流的正確異常處理方式:
String filePath = "D:\\Learning\\doc\\test.txt"; FileOutputStream fos= null; try { fos = new FileOutputStream(filePath); fos.write("123456".getBytes()); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally {
if(fos!=null){ try { //必定要記得關閉流,不然文件可能一直被佔用而沒法操做 fos.close(); } catch (IOException e) { e.printStackTrace(); }
} }
上面介紹了字節流,若是咱們寫入的是中文,而後使用字節流讀取出來,就會出現亂碼。
String filePath = "D:\\Learning\\doc\\test.txt"; FileOutputStream fos= null; FileInputStream fis =null; try { fos = new FileOutputStream(filePath); fis=new FileInputStream(filePath); fos.write("學習IO流".getBytes()); int ch; while ((ch=fis.read())!=-1){ System.out.print((char)ch);//å¦ä¹ IOæµ } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally { try {
//忘了判斷null fis.close(); fos.close(); } catch (IOException e) { e.printStackTrace(); } } }
爲何會亂碼呢?由於java中的char存的是Unicode字符,佔用2個字節,而中文也是2個字節,上面的代碼每次去一個字節區unicode字符集中查找,確定找到的不是對應的中文,所以亂碼。下面對上面的代碼改造一下,手動指定編碼方式,就能夠解決亂碼問題。關於字符的編碼問題能夠參考這裏。
String filePath = "D:\\Learning\\doc\\test.txt"; FileOutputStream fos= null; FileInputStream fis =null; try { fos = new FileOutputStream(filePath); fis=new FileInputStream(filePath); fos.write("學習IO流".getBytes("UTF-8")); byte [] buffer =new byte[1024]; int len; while ((len=fis.read(buffer))!=-1){ System.out.print(new String(buffer,0,len,"UTF-8")); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally { try {
//忘了判斷null fis.close(); fos.close(); } catch (IOException e) { e.printStackTrace(); } } }
上面的代碼相對比較繁瑣,java給咱們提供了字符流,方便對中文等字符進行處理,下面來看看經常使用的FileReader和FileWriter。
FileWriter 用於寫入字符流。用來寫入字符文件的便捷類。使用平臺(操做系統)默認字符編碼。好比Windows簡體中文版操做系統使用的編碼爲gbk。
構造函數以下:
FileWriter(File file) 根據給定的 File 對象構造一個 FileWriter 對象。 FileWriter(File file, boolean append) 根據給定的 File 對象構造一個 FileWriter 對象。 FileWriter(FileDescriptor fd) 構造與某個文件描述符相關聯的 FileWriter 對象。 FileWriter(String fileName) 根據給定的文件名構造一個 FileWriter 對象。 FileWriter(String fileName, boolean append) 根據給定的文件名以及指示是否附加寫入數據的 boolean 值來構造 FileWriter 對象。
寫入方法以下:
void write(char[] cbuf) 寫入字符數組。 void write(int c) 寫入單個字符。 void write(String str) 寫入字符串。 void write(char[] cbuf, int off, int len) 寫入字符數組的某一部分。 void write(String str, int off, int len) 寫入字符串的某一部分。
舉個例子:
String filePath = "D:\\Learning\\doc\\test.txt"; FileWriter fw= null; try { fw = new FileWriter(filePath); fw.write("學習IO流"); } catch (IOException e) { e.printStackTrace(); }finally { try { //字符流若是不關閉,寫入不會成功,由於字符流關閉以前會先刷新一下 //也能夠手動刷新
//忘了判斷null fw.flush(); fw.close(); } catch (IOException e) { e.printStackTrace(); } }
FileReader
用於讀取字符流。用來讀取字符文件的便捷類。
構造方法以下:
FileReader(File file) 在給定從中讀取數據的 File 的狀況下建立一個新 FileReader。 FileReader(FileDescriptor fd) 在給定從中讀取數據的 FileDescriptor 的狀況下建立一個新 FileReader。 FileReader(String fileName) 在給定從中讀取數據的文件名的狀況下建立一個新 FileReader。
讀取方法以下:
int read() 讀取單個字符。 int read(char[] cbuf) 將字符讀入數組。 int read(char[] cbuf, int off, int len) 將字符讀入數組的某一部分。 int read(CharBuffer target) 試圖將字符讀入指定的字符緩衝區。
舉個例子:
String filePath = "D:\\Learning\\doc\\test.txt"; FileReader fr = null; try { fr = new FileReader(filePath); //方式一:一次讀取一個字符(2個字節) // int ch = 0; // while ((ch = fr.read()) != -1) {
//此處若是不轉爲字符串,會查找Unicode,而字符串也是使用的平臺默認編碼,因此須要轉成字符串纔不會亂碼 // System.out.print((char) ch + ""); // } //方式二:批量讀取字符到緩衝區 char[] buffer =new char[1024]; int len = 0; while ((len = fr.read(buffer)) != -1) { System.out.print(new String(buffer,0,len)); } } catch (IOException e) { e.printStackTrace(); } finally { try { if (fr != null) { fr.close(); } } catch (IOException e) { e.printStackTrace(); } }
下面代碼演示瞭如何複製文本文件:
//複製文本文件 String sourceFilePath = "D:\\Learning\\doc\\test.txt"; String destFilePath = "D:\\Learning\\doc\\test1.txt"; FileReader fr = null; FileWriter fw=null; try { fr = new FileReader(sourceFilePath); fw=new FileWriter(destFilePath); char[] buffer =new char[1024]; int len = 0; while ((len = fr.read(buffer)) != -1) { fw.write(buffer,0,len); } } catch (IOException e) { e.printStackTrace(); } finally { try { if (fr != null) { fr.close(); } if (fw != null) { fw.close(); } } catch (IOException e) { e.printStackTrace(); } }
上面咱們手動建立了緩衝區用來提升字符讀寫效率,java也提供了一樣的功能,即BufferReader,BufferWriter。
從字符輸入流中讀取文本,緩衝各個字符,從而實現字符、數組和行的高效讀取。能夠指定緩衝區的大小,或者可以使用默認的大小。大多數狀況下,默認值就足夠大了。
構造函數以下:
BufferedReader(Reader in) 建立一個使用默認大小輸入緩衝區的緩衝字符輸入流。 BufferedReader(Reader in, int sz) 建立一個使用指定大小輸入緩衝區的緩衝字符輸入流。
reader方法以下:
int read() 讀取單個字符。 int read(char[] cbuf, int off, int len) 將字符讀入數組的某一部分。 String readLine() 讀取一個文本行。
將文本寫入字符輸出流,緩衝各個字符,從而提供單個字符、數組和字符串的高效寫入。能夠指定緩衝區的大小,或者接受默認的大小。在大多數狀況下,默認值就足夠大了。
構造方法以下:
BufferedWriter(Writer out) 建立一個使用默認大小輸出緩衝區的緩衝字符輸出流。 BufferedWriter(Writer out, int sz) 建立一個使用給定大小輸出緩衝區的新緩衝字符輸出流。
writer方法以下:
void newLine() 寫入一個行分隔符。 void write(char[] cbuf, int off, int len) 寫入字符數組的某一部分。 void write(int c) 寫入單個字符。 void write(String s, int off, int len) 寫入字符串的某一部分。
接下來看看如何使用:
//複製文本文件 String sourceFilePath = "D:\\Learning\\doc\\git簡單操做.txt"; String destFilePath = "D:\\Learning\\doc\\test1.txt"; BufferedReader br = null; BufferedWriter bw = null; try { FileReader fr = new FileReader(sourceFilePath); FileWriter fw = new FileWriter(destFilePath); br = new BufferedReader(fr); bw = new BufferedWriter(fw); String line = null;//一行一行的讀取和寫入 while ((line = br.readLine()) != null) { bw.write(line); bw.newLine(); } //使用緩衝對象必定要flush bw.flush(); } catch (IOException e) { e.printStackTrace(); } finally { try { if (br != null) { br.close(); } if (bw != null) { bw.close(); } } catch (IOException e) { e.printStackTrace(); } }
跟蹤行號的緩衝字符輸入流。此類定義了方法 setLineNumber(int) 和 getLineNumber(),它們可分別用於設置和獲取當前行號。
默認狀況下,行編號從 0 開始。該行號隨數據讀取在每一個行結束符處遞增,而且能夠經過調用 setLineNumber(int) 更改行號。
但要注意的是,setLineNumber(int) 不會實際更改流中的當前位置;它只更改將由 getLineNumber() 返回的值。
下面看看簡單的使用方式:
String sourceFilePath = "D:\\Learning\\doc\\git簡單操做.txt"; FileReader fr = null; LineNumberReader lnr =null; try { fr = new FileReader(sourceFilePath); lnr = new LineNumberReader(fr); String line = null; lnr.setLineNumber(100);//設置起始行號爲100 while((line=lnr.readLine())!=null){ System.out.println(lnr.getLineNumber()+":"+line); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally { if(lnr!=null){ try { lnr.close(); } catch (IOException e) { e.printStackTrace(); } } }
字節流通向字符流的橋樑:它使用指定的 charset 讀取字節並將其解碼爲字符。它使用的字符集能夠由名稱指定或顯式給定,或者能夠接受平臺默認的字符集。
爲了達到最高效率,可要考慮在 BufferedReader 內包裝 InputStreamReader。例如:
BufferedReader in= new BufferedReader(new InputStreamReader(System.in));
構造方法以下:
InputStreamReader(InputStream in) 建立一個使用默認字符集的 InputStreamReader。 InputStreamReader(InputStream in, Charset cs) 建立使用給定字符集的 InputStreamReader。 InputStreamReader(InputStream in, CharsetDecoder dec) 建立使用給定字符集解碼器的 InputStreamReader。 InputStreamReader(InputStream in, String charsetName) 建立使用指定字符集的 InputStreamReader。
讀取方法以下:
int read() 讀取單個字符。 int read(char[] cbuf, int offset, int length) 將字符讀入數組中的某一部分。
舉個例子,好比獲取鍵盤輸入的內容,並打印在控制檯上,要求不能用Scanner對象:
//需求:把鍵盤輸入的內容打印在控制檯 InputStreamReader isr = new InputStreamReader(System.in); //方式一:直接讀取 // int ch = 0; // while ((ch=isr.read())!=-1){ // System.out.print((char) ch+""); // } //方式二:讀到緩衝區 int len = 0; char [] buffer =new char[1024]; while ((len=isr.read(buffer))!=-1){ System.out.print(new String(buffer,0,len)); } //方式三:使用緩衝流提供的便捷方法 BufferedReader br =new BufferedReader(isr) ; String line=null; while((line=br.readLine())!=null){ System.out.print(line); } }
是字符流通向字節流的橋樑:可以使用指定的 charset 將要寫入流中的字符編碼成字節。它使用的字符集能夠由名稱指定或顯式給定,不然將接受平臺默認的字符集。
爲了得到最高效率,可考慮將 OutputStreamWriter 包裝到 BufferedWriter 中,以免頻繁調用轉換器。例如:
Writer out = new BufferedWriter(new OutputStreamWriter(System.out));
構造方法以下:
OutputStreamWriter(OutputStream out) 建立使用默認字符編碼的 OutputStreamWriter。 OutputStreamWriter(OutputStream out, Charset cs) 建立使用給定字符集的 OutputStreamWriter。 OutputStreamWriter(OutputStream out, CharsetEncoder enc) 建立使用給定字符集編碼器的 OutputStreamWriter。 OutputStreamWriter(OutputStream out, String charsetName) 建立使用指定字符集的 OutputStreamWriter。
寫入方法以下:
void write(char[] cbuf, int off, int len) 寫入字符數組的某一部分。 void write(int c) 寫入單個字符。 void write(String str, int off, int len) 寫入字符串的某一部分。
舉個例子,好比把文本文件的內容打印在控制檯上,要求不能用print方法:
OutputStreamWriter osw=new OutputStreamWriter(System.out); String line="學習IO流"; //方式一:直接寫入 // osw.write(line); // osw.close(); //方式二:使用緩衝流高效寫入 BufferedWriter bw=new BufferedWriter(osw); bw.write(line); bw.close();
再來個例子,將鍵盤錄入的數據寫入到一個文件中:
String filePath="E:\\test.txt"; //需求:將鍵盤錄入的數據寫入到一個文件中。 BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in)); BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filePath))); String line = null; while((line=bufr.readLine())!=null){ if(":wq".equals(line)) break; bufw.write(line.toUpperCase()); bufw.newLine(); bufw.flush(); }
上面的例子舉得有點牽強,正在的用處在於上面這兩個對象能夠手動指定編碼,好比讀取文本文件,必須指定utf-8來編碼。用FileReader是作不到的。
InputStreamReader isr = new InputStreamReader(new FileInputStream("E:\\test.txt"),"utf-8");
參考文檔:http://tool.oschina.net/apidocs/apidoc?api=jdk-zh
參考代碼:畢老師35天