流(Stream)的概念源自UNIX中管道的概念,管道是一條不間斷的字節流,用來實現程序或進程之間的通信。一個流必有源端和目的端(可以是內存、磁盤文件等。)流的源端和目的端可以簡單的看成字節的生產者和消費者。
根據
根據
根據
Java IO流的繼承結構圖如下: 
在講解流之前,先講解一下之后和流操作緊密相關的文件操作類
| No. | 方法 | 類型 | 描述 |
|---|---|---|---|
| 1 | public File(String pathName) | 構造 | 給定一個要操作文件的完整路徑 |
| 2 | public boolean exists() | 普通 | 判定給定路徑是否存在 |
| 3 | public File getParentFile() | 普通 | 找到一個指定路徑的父路徑 |
| 4 | public boolean mkdirs() | 普通 | 創建指定目錄 |
| 5 | public boolean createNewFile() | 普通 | 創建文件 |
| 6 | public boolean delete() | 普通 | 刪除文件 |
| 7 | public boolean isDirectory() | 普通 | 判斷給定路徑是否是文件夾 |
| 8 | public boolean isFile() | 普通 | 判斷給定路徑是否是文件 |
| 9 | public boolean isHidden() | 普通 | 判斷是否隱藏 |
| 10 | public boolean renameTo(File dest) | 普通 | 為文件重命名 |
| 11 | public long lastModified() | 普通 | 文件的最后一次修改時間 |
| 12 | public long length() | 普通 | 取得文件的大小 |
| 13 | public String getName() | 普通 | 取得文件名稱 |
| 14 | public [] File listFiles() | 普通 | 將目錄中的文件以File對象數組的方式返回 |
所有的
所有的
對于InputStream、OutputStream類而言,本身定義的是一個抽象類(abstract class),按照抽象類的使用原則來講,需要定義抽象類的子類,根據功能的不同,使用不同的子類完成。
文件操作流的常用方法
| 類名稱 | No. | 方法名稱 | 類型 | 描述 |
|---|---|---|---|---|
| 1 | public FileInputStream(File file) | 實例化FileInputStream,用于從指定文件中讀取數據 | ||
| 2 | public FileInputStream(Stirng name) | 實例化FileInputStream,用于從指定文件路徑中讀取數據 | ||
| InputStream | 3 | public void close() | 普通 | 關閉輸入流 |
| InputStream | 4 | public int read() | 普通 | 從指定輸入流中讀取一個字節數據 |
| InputStream | 5 | public int read(byte [] data) | 普通 | 從指定流中讀取多個字節 |
| InputStream | 6 | public int read(byte [] data, int off, int len) | 普通 | 從指定流中讀取指定多個字節 |
| 7 | public FileOutputStream(File file) | 實例化FileOuputStream,用于新建數據 | ||
| 8 | public FileOutputStream(File file,boolean append) | 實例化FileOutputStream,用于追加數據 | ||
| OutputStream | 9 | public void close() | 普通 | 關閉輸出流 |
| OutputStream | 10 | public abstract void write(int b) | 普通 | 向文件輸出單個字節 |
| OutputStream | 11 | public void write(byte [] b) | 普通 | 向文件輸出一組字節數據 |
| OutputStream | 12 | public void write(byte [] b, int off, int len) | 普通 | 向文件輸出部分字節數據 |
在以上的例子中,通過FileInputStream的read()方法從/home/linyimin/DaSi/java/InputStream.txt文件中讀取信息,通過FileOutputStream的write()方法向/home/linyimin/DaSi/java/InputStream.txt文件寫入“Hello World.”。在此操作中如果InputStream.txt已經存在,會先清空內容在寫入信息,如果不存在,會自動創建InputStream.txt文件后在寫入數據。如果希望在已存在的文件之后添加數據,應使用public FileOutputStream(fileOut,true)構造方法進行實例化對象,表示向已有文件中追加寫入數據而不是覆蓋已有數據。
在某個操作需要發生IO操作,但又不希望有一些臨時文件產生,可以使用內存操作流完成,即以內存為操作的終端,以發生IO操作關系。用于以IO流的方式來完成對字節數組內容的讀寫,來支持類似內存虛擬文件或者內存映射文件的讀寫。
ByteArrayInputStream:字節數組輸入流,繼承自InputStream,它會在內存中創建一個字節數組緩沖區,實例化后的字節數組會保存在此緩沖區中。也就是內存操作類的常用方法
| 類名稱 | No. | 方法 | 類型 | 描述 |
|---|---|---|---|---|
| 1 | public ByteArrayInputStream(byte [] buf) | 將字節流轉換稱輸入流 | ||
| 2 | public int read() | 普通 | 從輸入流中讀取一個字節數據 | |
| 3 | public int read(byte [] data) | 普通 | 從指定流中讀取多個字節 | |
| 4 | public int read(byte [] data, int off, int len) | 普通 | 從指定流中讀取指定多個字節 | |
| 5 | public ByteArrayOutputStream() | 創建一個32個字節 | ||
| 6 | public ByteArrayOutputStream(int size) | 根據參數指定大小創建緩沖區 | ||
| 7 | public void write(int b) | 普通 | 往輸出流中寫入一個字節數據 | |
| 8 | public void write(byte [] b) | 普通 | 往輸出流中寫入一個字節數組數據 | |
| 9 | public void write(byte [] b, int off, int len) | 普通 | 往輸出流中寫入指定多個字節數據 | |
| 10 | public String toString() | 普通 | 使用默認編碼將緩沖區數據編碼成字符串并返回 | |
| 11 | public byte [] toByteArray() | 普通 | 將緩沖區數據通過字節數組返回 |
程序運行結果: abcdefghijklmnopqrstuvwsyz a b c d e f g h i j k l m n o p q r s t u v w s y z ABCDEFGHIJKLMNOPQRSTUVWSYZ
在上面的例子中,我們通過字符串獲取字節數組將其作為ByteArrayInputStream的數據流來源,然后通過讀取ByteArrayInputStream的數據,將讀到的數據寫入到ByteArrayOutputStream中。
System.in是System類中的一個InputStream類型的常量,用于系統輸入。系統輸入針對標準的輸入設備——鍵盤,也就是俗稱的鍵盤輸入數據,由于System.in是InputStream類型數據,所以接收的數據是字節型。通過調用read()函數完成鍵盤輸入數據操作。
程序運行結果: 輸入多個字節數據: Hello 中國 Hello 中國
一個字節接一個字節輸入數據: Hello 中國 H E L L O ? ? - ? ? ?
在上面的例子中,可以發現從鍵盤輸入數據時,既可以多個字節數據同時輸入,也可以一個接一個字節輸入,但應注意的是,
BufferedInputStream是緩沖輸入流,繼承于FilterInputStream。作用是為其它輸入流提供緩沖功能。BufferedInputStream會將輸入流數據分批讀取,每次讀取一部分數據到緩沖中,操作完緩沖中的數據之后,在從輸入流中讀取下一部分的數據。即BufferedInputStream內部有一個字節數組緩沖區,每次執行read操作的時候就從這buf中讀取數據,從buf中讀取數據沒有多大的開銷。如果buf中已經沒有了要讀取的數據,那么就去執行其內部綁定的InputStream的read方法,而且是一次性讀取很大一塊數據,以便填充滿buf緩沖區。于我們在執行BufferedInputStream的read操作的時候,很多時候都是從緩沖區中讀取的數據,這樣就大大減少了實際執行其指定的InputStream的read操作的次數,也就提高了讀取的效率。
BufferedOutputStream是輸出緩沖流,與BufferedInputStream相對,繼承于FilterOutputStream。作用是為輸出流提供緩沖功能。BufferedOutputStream內部有一個字節緩沖區buf,在執行write操作時,將要寫入的數據先一起緩存在一起,將其存入字節緩沖區buf中,當buf被填充完畢的時候會調用BufferedOutputStream的flushBuffer方法,該方法會通過調用其綁定的OutputStream的write方法將buf中的數據進行實際的寫入操作并將buf的指向歸零(可以看做是將buf中的數據清空)。如果想讓緩存區buf中的數據理解真的被寫入OutputStream中,可以調用flush方法,flush方法內部會調用flushBuffer方法。由于buf的存在,會大大減少實際執行OutputStream的write操作的次數,優化了寫的效率。
緩沖操作的常用方法
| 類名稱 | No. | 方法 | 類型 | 描述 |
|---|---|---|---|---|
| 1 | public BufferedInputStream(InputStream in) | 實例化BufferedInputStream,使用默認緩沖區大小 | ||
| 2 | public BufferedInputStream(InputStream in, int size) | 實例化BufferedInputStream,指定緩沖區大小 | ||
| 3 | public int read() | 普通 | 從輸入流中讀取一個字節數據 | |
| 4 | public int read(byte [] data) | 普通 | 從指定流中讀取多個字節 | |
| 5 | public int read(byte [] data, int off, int len) | 普通 | 從指定流中讀取指定多個字節 | |
| 6 | public BufferedOutputStream(OutputStream out) | 實例化BufferedOutputStream,使用默認緩沖區大小 | ||
| 7 | public BufferedOutputStream(OutputStream out, int size) | 實例化BufferedOutputStream,指定緩沖區大小 | ||
| 8 | public void write(int b) | 普通 | 向輸出緩沖區寫入一個字節數據 | |
| 9 | public void write(byte [] data) | 普通 | 向輸出緩沖區寫入多個字節數據 | |
| 10 | public void write(byte [] data, int off, int len) | 普通 | 向輸出緩沖取寫入指定多個字節 | |
| 11 | public void flush() | 普通 | 清空緩沖區,緩存區buf中的數據理解真的被寫入OutputStream中 |
代碼示例
import java.io.BufferedInputStream;import java.io.BufferedOutputStream;import java.io.File;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;public class TestDemo{ public static void main(String [] args) throws IOException{ File fio = new File(File.separator + "home" + File.separator + "linyimin" + File.separator + "DaSi" + File.separator + "java" + File.separator + "InputStream.txt"); File fout = new File(File.separator + "home" + File.separator + "linyimin" + File.separator + "DaSi" + File.separator + "java" + File.separator + "OutputStream.txt"); // InputStream.txt不存在則創建文件 if(!fio.exists()){ fio.createNewFile(); } // OutputStream.txt不存在則創建文件 if(!fout.exists()){ fout.createNewFile(); } // 實例化BufferedInputStream BufferedInputStream ibuf = new BufferedInputStream(new FileInputStream(fio)); // 實例化BufferedOutputStream BufferedOutputStream obuf = new BufferedOutputStream(new FileOutputStream(fout)); byte [] data = new byte[1024]; // 從BufferedInputStream中多個字節數據 int len = ibuf.read(data); while(len != -1){ // 向BufferedOutputStream中寫入多個數據 obuf.write(data, 0, len); // 繼續從BufferedInputStream中讀取多個字節數據 len = ibuf.read(data); } // 清空輸出緩存區 obuf.flush(); // 關閉輸出輸入流 ibuf.close(); obuf.close(); }}程序運行結果: 程序運行前:OutputStream.txt文件不存在,InputStream.txt文件的內容為: Hello World. 1234567890 程序運行后:OutputStream.txt文件的內容為: Hello World. 1234567890
在上面的例子中,實現了將InputStream.txt拷貝到OutputStream.txt。其實不通過BufferedInputStream 和 BufferedOutputStream也可以完成這樣的工作,使用這個兩個類的好處是提升了文件拷貝的效率。下面進行驗證:
直接使用InputStream和OutputStream完成大文件賦值代碼
import java.io.File;import java.io.InputStream;import java.io.OutputStream;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.math.BigDecimal;public class TestDemo{ public static void main(String [] args) throws IOException{ File fio = new File("/home/linyimin/Downloads/linux_11gR2_database_2of2.z程序運行結果:
復制大小為:949.25M的文件共花費時間:49.048s
代碼
import java.io.File;import java.io.InputStream;import java.io.OutputStream;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.math.BigDecimal;import java.io.BufferedInputStream;import java.io.BufferedOutputStream;public class TestDemo{ public static void main(String [] args) throws IOException{ File fio = new File("/home/linyimin/Downloads/linux_11gR2_database_2of2.zip"); File fout = new File("/home/linyimin/Downloads/linux_11gR2_database_2of2-copy.zip"); InputStream in = null; OutputStream out = null; // 創建文件 try{ fout.createNewFile(); in = new BufferedInputStream(new FileInputStream(fio)); out = new BufferedOutputStream(new FileOutputStream(fout)); } catch(Exception e){ e.printStackTrace(); } // 獲取復制文件大小,并轉換成單位M,保留兩位小數 double length = new BigDecimal(fio.length() / 1024.0 / 1024).divide(new BigDecimal(1), 2, BigDecimal.ROUND_HALF_UP).doubleValue(); // 文件復制開始時間 long start = System.currentTimeMillis(); // 從輸入流中讀取多個字節數據 byte [] data = new byte[1024]; int len = in.read(data); while(len != -1){ // 將從輸入流中讀出的數據寫入輸出流中 out.write(data); // 繼續從輸入流中讀取數據 len = in.read(data); } // 文件復制結束時間 long end = System.currentTimeMillis(); // 將文件復制時間轉換成秒并保留三位小數 double time = new BigDecimal((end - start) / 1000.0).divide(new BigDecimal(1), 3, BigDecimal.ROUND_HALF_UP).doubleValue(); System.out.println("復制大小為:" + length + "M的文件共花費時間:" + time + "s"); }}程序運行結果:
復制大小為:949.25M的文件共花費時間:32.93s
比較以上兩個程序的執行結果,使用緩沖區操作的效率確實要提高不少。
管道流操作主要用于兩個線程之間進行管道通信。PipedInputStream和PipedOutputStream一般是結合使用的,一般在一個線程中執行PipedOutputStream 的write操作,而在另一個線程中執行PipedInputStream的read操作。單獨使用PipedInputStream或單獨使用PipedOutputStream時沒有任何意義的,必須將二者通過connect方法(或在構造函數中傳入對應的流)進行連接綁定,如果單獨使用其中的某一個類,就會觸發IOException: PipeNotConnected.
代碼示例
import java.io.IOException;import java.io.PipedInputStream;import java.io.PipedOutputStream;public class TestDemo{ public static void main(String [] args)throws Exception{ // 實例化PipedOutputStream final PipedOutputStream out = new PipedOutputStream(); final PipedInputStream in = new PipedInputStream(out); // 使用匿名內部類實現線程t1 Thread t1 = new Thread(new Runnable(){ @Override public void run(){ try { out.write("Hello Pipe.".getBytes()); } catch (IOException e) { e.printStackTrace(); } } }); // 使用匿名內部類實現線程t2 Thread t2 = new Thread(new Runnable(){ @Override public void run(){ int len = 0; try { // 從線程t1中的輸出流中讀取數據 while((len = in.read()) != -1){ System.out.print((char)len + " "); } } catch (IOException e) { } System.out.println(); } }); // 啟動線程 t1.start(); t2.start(); }}程序運行結果 H e l l o P i p e .
在上面的程序中,我們創建了兩個線程,并通過構造方法將PipedInputStream流和PipedOutputStream綁定。線程t1在運行時往輸出流中寫入字節數據,而線程t2在運行時阻塞式的執行read操作,等待獲取數據,并輸出。
注:
如果一個類已經實現了序列化接口,那么此類的對象就可以經過二進制數據流進行傳輸,但是還需要對象輸出流ObjectOutputStream和對象輸入流ObjectInputStream完成對象的輸入和輸出.
ObjectOutputStream具有一系列writeXXX方法,在其構造函數中可以傳入一個OutputStream,可以方便的向指定的輸出流中寫入基本類型數據以及String,比如writeBoolean、writeChar、writeInt、writeLong、writeFloat、writeDouble、writeCharts、writeUTF等,除此之外,ObjectOutputStream還具有writeObject方法。在執行writeObject操作時將對象進行序列化成流,并將其寫入指定的輸出流中。ObjectInputStream與ObjectOutputStream相對應,ObjectInputStream有與OutputStream中的writeXXX系列方法完全對應的readXXX系列方法,專門用于讀取OutputStream通過writeXXX寫入的數據。ObjectOutputStream和ObjectInputStream常用方法
| 類名稱 | No. | 方法 | 類型 | 描述 |
|---|---|---|---|---|
| 1 | public ObjectOutputStream(OutputStream out) | 實例化指定輸出流的對象 | ||
| 2 | public void writeObject(Object obj) | 普通 | 向指定輸流寫入數據 | |
| 3 | public ObjectInputStream(InputStream in) | 實例化指定輸入流的對象 | ||
| 4 | public void readObject(Object obj) | 普通 | 從指定輸入流讀出數據 |
代碼示例-序列化
import java.io.File;import java.io.Serializable;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectOutputStream;// 定義可以被序列化的類class Person implements Serializable{ private String name; private int age; public Person(String name, int age){ this.name = name; this.age = age; }}public class TestDemo{ public static void main(String [] args) throws FileNotFoundException, IOException{ // 實例化序列化對象 Person per = new Person("張三", 20); File file = new File("/home/linyimin/DaSi/java/serializable.txt"); // 實例化ObjectOutputStream對象 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file)); // 序列化對象 oos.writeObject(per); // 關閉輸出流 oos.close(); }}程序運行結果: 文件serializable.txtx中的內容: /AC/ED/00sr/00Person:H3/BC/B0/FC/00I/00ageL/00namet/00Ljava/lang/String;xp/00/00/00t/00張三
本程序使用ObjectOutputStream將一個已經實例化的對象序列化到文件中.
代碼示例-反序列化
import java.io.File;import java.io.Serializable;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;import java.io.ObjectInputStream;//定義可以被序列化的類class Person implements Serializable{ private String name; private int age; public Person(String name, int age){ this.name = name; this.age = age; } @Override public String toString(){ return "姓名:" + this.name + " 年齡:" + this.age; }}public class TestDemo{ public static void main(String [] args) throws FileNotFoundException, IOException, ClassNotFoundException{ // 實例化ObjectInputStream對象 File fio = new File("/home/linyimin/DaSi/java/serializable.txt"); ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fio)); // 實現反序列化 Person per = (Person) ois.readObject(); ois.close(); System.out.println(per); }}程序運行結果:
姓名:張三 年齡:20
本程序通過ObjectInputStream類將之前已經被序列化的數據反序列化為對象.
字符流和字節流十分相似,主要區別在于:
字節流是針對字節的操作,而字符流是針對字符的操作字節流在進行IO操作時,直接針對的是操作的數據終端(如文件),而字符流是針對緩存區(可以理解為內存)的操作,然后由緩沖區操作終端(如文件)下面主要介紹字符流較于字節流的區別之處:
使用Writer類進行輸出的最大方便之處在于:可以直接輸出字符串數據,而不像OutputStream類一樣需要調用getBytes()函數將字符串轉換成字節數組.
代碼示例
import java.io.File;import java.io.Writer;import java.io.FileWriter;public class TestDemo{ public static void main(String [] args)throws Exception{ File file = new File("/home/linyimin/DaSi/java/out.txt"); // 如果文件不不存在,創建文件 if(!file.exists()){ file.createNewFile(); } // 實例化FileWriter對象 Writer out = new FileWriter(file); String str = "Hello World."; // 直接向文件中輸出字符串 out.write(str); // 需要關閉輸出流,數據才會從緩存區寫到文件中 out.close(); }}程序運行結果
創建out.txt文件,并寫入”Hello World."
本程序通過FileWriter完成直接將字符串寫入指定文件中.
所謂轉換流指的是字節流向字符流轉換的操作流.由InputStreamReader和OutputStreamWriter兩個類完成.
InputStreamReader繼承自Reader: InputStream –> Reader(使用構造方法完成)OutputStreamWriter繼承自Writer:OutputStream –> Writer(使用構造方法完成)代碼示例
import java.io.File;import java.io.Writer;import java.io.OutputStream;import java.io.FileOutputStream;import java.io.IOException;import java.io.OutputStreamWriter;public class TestDemo{ public static void main(String [] args) throws IOException{ File file = new File("/home/linyimin/DaSi/java/out.txt"); // 實例化字節輸出流 OutputStream out = new FileOutputStream(file); // 將字節輸出流轉換成字符輸出流 Writer wout = new OutputStreamWriter(out); String str = "使用OutputStreamWriter完成輸出流轉換操作"; // 直接向文件輸出字符串 try { wout.write(str); } catch (IOException e) { e.printStackTrace(); } // 關閉輸出流,完成IO操作 wout.close(); }}程序運行結果
向文件out.txt中輸出字符串"使用OutputStreamWriter完成輸出流轉換操作"
注:
新聞熱點
疑難解答