I/O流的概念
I/O流 即輸入Input流/ 輸出Output流的縮寫,常見(jiàn)的是電腦屏幕輸出設(shè)備和鍵盤鼠標(biāo)輸入設(shè)備,其廣義上的定義就是:數(shù)據(jù)在內(nèi)部存儲(chǔ)器和外部存儲(chǔ)器或其他周邊設(shè)備之間的輸入和輸出;即:數(shù)據(jù)/ 輸入/ 輸出。
流是一個(gè)抽象但形象的概念,Java中把不同的輸入/輸出源(鍵盤,文件,網(wǎng)絡(luò)連接等)抽象的表述為 “流”(Stream)。可以簡(jiǎn)單理解成一個(gè)數(shù)據(jù)的序列,輸入流表示從一個(gè)源讀取數(shù)據(jù),輸出流則表示向一個(gè)目標(biāo)寫數(shù)據(jù),在Java程序中,對(duì)于數(shù)據(jù)的輸入和輸出都是采用 “流” 這樣的方式進(jìn)行的。

?
I/O流的分類
按流向分:輸入流,輸出流
輸入流:只能從中讀取數(shù)據(jù),不能寫入數(shù)據(jù)
輸出流,只能向其寫入數(shù)據(jù),不能讀取數(shù)據(jù)
這里的輸入輸出是相對(duì)而言的,在本地用程序讀寫文件時(shí),始終是以程序?yàn)橹行?,即程序從文件中讀取時(shí)就是硬盤向內(nèi)存中輸入,程序向文件中寫入就是內(nèi)存向硬盤輸出。
但對(duì)于服務(wù)器和客戶端來(lái)說(shuō),服務(wù)器將數(shù)據(jù)輸出到網(wǎng)絡(luò)中,這是Sever端程序的輸出流??蛻舳藦木W(wǎng)絡(luò)中讀取數(shù)據(jù),這是Client端的輸入流。
按操作單元分:字節(jié)流和字符流
字節(jié)流:圖片、視頻文件中存儲(chǔ)的都是二進(jìn)制的字節(jié)(byte)。直觀的想法,以一個(gè)字節(jié)單位來(lái)運(yùn)輸?shù)?,比如一杯一杯的取水?/p>
以InputStream和OutputStream作為基類
字符流:一般文本文件中存放都是有明確含義的,可供人閱讀理解的字符(char)。直觀的想法,以多個(gè)字節(jié)來(lái)運(yùn)輸?shù)?,比如一桶一桶的取水,一桶水又可以分為幾杯水?/p>
以Reader和Writer作為基類

?
不管是文本、還是圖書、視頻最終在磁盤上的時(shí)候都是按照byte存儲(chǔ)的。因此,Java要提供基于字符流的機(jī)制,就要處理字節(jié)和字符的相互轉(zhuǎn)化,這里就涉及字符集合字符編碼的問(wèn)題。
字節(jié)流和字符流的區(qū)別:
字節(jié)流讀取單個(gè)字節(jié),字符流讀取單個(gè)字符(一個(gè)字符根據(jù)編碼的不同,對(duì)應(yīng)的字節(jié)也不同,如 UTF-8 編碼是 3 個(gè)字節(jié),中文編碼是 2 個(gè)字節(jié)。)字節(jié)流用來(lái)處理二進(jìn)制文件(圖片、MP3、視頻文件)。
字符流用來(lái)處理文本文件(可以看做是特殊的二進(jìn)制文件,使用了某種編碼,人可以閱讀)。
簡(jiǎn)而言之,字節(jié)是個(gè)計(jì)算機(jī)看的,字符才是給人看的。
其實(shí)現(xiàn)子類:FileInputStream和FileOutputStream可以對(duì)任意類型的文件進(jìn)行讀寫操作,但 FileReader和FileWriter只能對(duì)純文本文件進(jìn)行操作。
從網(wǎng)上找來(lái)一張圖,方便對(duì)Java I/O有個(gè)總統(tǒng)的認(rèn)識(shí)。從這張圖可以很清楚的看清Java I/O的字節(jié)流和字符流的整體情況。

IO體系的基類
InputStream/Reader,OutputStream/Writer
InputStream和Reader是所有輸入流的抽象基類,本身并不能創(chuàng)建實(shí)例來(lái)執(zhí)行輸入,但它們將成為所有輸入流的模板,所以他們的方法是所有輸入流都可使用的方法。
- InputStream 讀文件方法
int read():從輸入流中讀取單個(gè)字節(jié),返回所讀取的字節(jié)數(shù)據(jù)(字節(jié)數(shù)據(jù)可直接轉(zhuǎn)為int型)
int read(byte []):從輸入流中最多讀取b.length個(gè)字節(jié)的數(shù)據(jù),并將其存儲(chǔ)在字節(jié)數(shù)組b中,返回實(shí)際讀取的字節(jié)數(shù)。
int read(byte[], int off, int len): 從輸入流中最多讀取len個(gè)字節(jié)的數(shù)據(jù),并將其存儲(chǔ)在數(shù)組b中,放入b數(shù)組時(shí),并不是從數(shù)組起點(diǎn)開(kāi)始,而是從off位置開(kāi)始,返回實(shí)際讀取的字節(jié)數(shù)
- Reader讀文件方法
int read():從輸入流中讀取單個(gè)字符,返回所讀取的字符數(shù)據(jù)(可直接轉(zhuǎn)為int類型)
int read(char[]):從輸入流中最多讀取b.length個(gè)字符的數(shù)據(jù),并將其存儲(chǔ)在數(shù)組b中,返回實(shí)際讀取的字符數(shù)。
int read(char[] b, int off, int len):從輸入流中最多讀取len個(gè)字符的數(shù)據(jù),并將其存儲(chǔ)在數(shù)組b中,放入數(shù)組b時(shí),并不是從數(shù)組七點(diǎn)開(kāi)始,而是從off位置開(kāi)始,返回實(shí)際讀取的字符數(shù)。
- OutputStream寫文件方法
void write(int c):將指定的字節(jié)/字符輸出到輸出流中,c可以代表字節(jié),也可以代表字符
void write(byte[]/byte[] buf):將字節(jié)數(shù)組,字符數(shù)組的數(shù)據(jù)輸出到指定輸出流中
void write(byte[]/byte[] buf, int off, int len):將字節(jié)數(shù)組/字符數(shù)組從off位置開(kāi)始,長(zhǎng)度為len的字節(jié)/字符數(shù)組輸出到輸出流中
- Writer寫文件方法
因?yàn)樽址髦苯右宰址麨椴僮鲉挝?,所以Writer可以用字符串代替字符數(shù)組,即以String對(duì)象為參數(shù)。Writer中還包括這兩個(gè)方法:
void write(String str):將str字符串里包含的字符輸出到指定輸出流中
void write(String str, int off, int len):將str字符串里從off位置開(kāi)始,長(zhǎng)度為len的字符輸出到指定輸出流中
IO常用類
- 文件流:FileInputStream/FileOutputStream, FileReader/FileWriter
FileInputStream/FileOutputStream, FileReader/FileWriter是專門操作文件流的,用法高度相似,區(qū)別在于前面兩個(gè)是操作字節(jié)流,后面兩個(gè)是操作字符流。它們都會(huì)直接操作文件流,直接與OS底層交互。因此他們也被稱為節(jié)點(diǎn)流。
使用這幾個(gè)流的對(duì)象之后,需要關(guān)閉流對(duì)象,因?yàn)閖ava垃圾回收器不會(huì)主動(dòng)回收。
不過(guò)在Java7之后,可以在 try() 括號(hào)中打開(kāi)流,最后程序會(huì)自動(dòng)關(guān)閉流對(duì)象,不再需要顯示的close。
下面演示這四個(gè)流對(duì)象的基本用法
//FileInputStream一個(gè)一個(gè)字節(jié)讀取
public class FileInputStreamDemo1 {
public static void main(String[] args) throws IOException{
FileInputStream fis = new FileInputStream("c:\\a.txt");
//讀取一個(gè)字節(jié),調(diào)用方法read 返回int
//使用循環(huán)方式,讀取文件, 循環(huán)結(jié)束的條件 read()方法返回-1
int len = 0;//接受read方法的返回值
while( (len = fis.read()) != -1){
System.out.print((char)len);
}
//關(guān)閉資源
fis.close();
}
}
//FileInputStream讀取字節(jié)數(shù)組
public class FileInputStreamDemo2 {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("c:\\a.txt");
//創(chuàng)建字節(jié)數(shù)組
byte[] b = new byte[1024];
int len = 0 ;
while( (len = fis.read(b)) !=-1){
System.out.print(new String(b,0,len));
}
fis.close();
}
}
//FileOutputStream寫單個(gè)字節(jié)
public class FileOutputStreamDemo {
public static void main(String[] args)throws IOException {
FileOutputStream fos = new FileOutputStream("c:\\a.txt");
//流對(duì)象的方法write寫數(shù)據(jù)
//寫1個(gè)字節(jié)
fos.write(97);
//關(guān)閉資源
fos.close();
}
}
//FileOutputStream寫字節(jié)數(shù)組
public class FileOutputStreamDemo {
public static void main(String[] args)throws IOException {
FileOutputStream fos = new FileOutputStream("c:\\a.txt");
//流對(duì)象的方法write寫數(shù)據(jù)
//寫字節(jié)數(shù)組
byte[] bytes = {65,66,67,68};
fos.write(bytes);
//寫字節(jié)數(shù)組的一部分,開(kāi)始索引,寫幾個(gè)
fos.write(bytes, 1, 2);
//寫入字節(jié)數(shù)組的簡(jiǎn)便方式
//寫字符串
fos.write("hello".getBytes());
//關(guān)閉資源
fos.close();
}
}
//字符流復(fù)制文本
//FileReader讀取數(shù)據(jù)源,FileWriter寫入到數(shù)據(jù)目的
public class Copy {
public static void main(String[] args) {
FileReader fr = null;
FileWriter fw = null;
try{
fr = new FileReader("c:\\1.txt");
fw = new FileWriter("d:\\1.txt");
char[] cbuf = new char[1024];
int len = 0 ;
while(( len = fr.read(cbuf))!=-1){
fw.write(cbuf, 0, len);
fw.flush();
}
}catch(IOException ex){
System.out.println(ex);
throw new RuntimeException("復(fù)制失敗");
}finally{
try{
if(fw!=null)
fw.close();
}catch(IOException ex){
throw new RuntimeException("釋放資源失敗");
}finally{
try{
if(fr!=null)
fr.close();
}catch(IOException ex){
throw new RuntimeException("釋放資源失敗");
}
}
}
}
}

- 轉(zhuǎn)換流:InputStreamReader/OutputStreamWriter
InputStreamReader/OutputStreamWriter可以將字節(jié)流轉(zhuǎn)換成字符流,被稱為字節(jié)流與字符流之間的橋梁。經(jīng)常在讀取鍵盤輸入(System.in)或網(wǎng)絡(luò)通信的時(shí)候,需要使用這兩個(gè)類。
轉(zhuǎn)換流作用:
1 . 是字符流和字節(jié)流之間的橋梁
2 . 可對(duì)讀取到的字節(jié)數(shù)據(jù)經(jīng)過(guò)指定編碼換成字符
3 . 可對(duì)讀取到的字符數(shù)據(jù)經(jīng)過(guò)指定編碼轉(zhuǎn)成字節(jié)
/*
* 轉(zhuǎn)換流對(duì)象OutputStreamWriter寫文本
* 采用UTF-8編碼表寫入
*/
public static void writeUTF()throws IOException{
//創(chuàng)建字節(jié)輸出流,綁定文件
FileOutputStream fos = new FileOutputStream("c:\\utf.txt");
//創(chuàng)建轉(zhuǎn)換流對(duì)象,構(gòu)造方法保證字節(jié)輸出流,并指定編碼表是UTF-8
OutputStreamWriter osw = new OutputStreamWriter(fos,"UTF-8");
osw.write("你好");
osw.close();
}
/*
* 轉(zhuǎn)換流對(duì)象 OutputStreamWriter寫文本
* 文本采用GBK的形式寫入
*/
public static void writeGBK()throws IOException{
//創(chuàng)建字節(jié)輸出流,綁定數(shù)據(jù)文件
FileOutputStream fos = new FileOutputStream("c:\\gbk.txt");
//創(chuàng)建轉(zhuǎn)換流對(duì)象,構(gòu)造方法,綁定字節(jié)輸出流,使用GBK編碼表
OutputStreamWriter osw = new OutputStreamWriter(fos);
//轉(zhuǎn)換流寫數(shù)據(jù)
osw.write("你好");
osw.close();
}
/*
* 轉(zhuǎn)換流,InputSteamReader讀取文本
* 采用UTF-8編碼表,讀取文件utf
*/
public static void readUTF()throws IOException{
//創(chuàng)建自己輸入流,傳遞文本文件
FileInputStream fis = new FileInputStream("c:\\utf.txt");
//創(chuàng)建轉(zhuǎn)換流對(duì)象,構(gòu)造方法中,包裝字節(jié)輸入流,同時(shí)寫編碼表名
InputStreamReader isr = new InputStreamReader(fis,"UTF-8");
char[] ch = new char[1024];
int len = isr.read(ch);
System.out.println(new String(ch,0,len));
isr.close();
}
/*
* 轉(zhuǎn)換流,InputSteamReader讀取文本
* 采用系統(tǒng)默認(rèn)編碼表,讀取GBK文件
*/
public static void readGBK()throws IOException{
//創(chuàng)建自己輸入流,傳遞文本文件
FileInputStream fis = new FileInputStream("c:\\gbk.txt");
//創(chuàng)建轉(zhuǎn)換流對(duì)象,構(gòu)造方法,包裝字節(jié)輸入流
InputStreamReader isr = new InputStreamReader(fis);
char[] ch = new char[1024];
int len = isr.read(ch);
System.out.println(new String(ch,0,len));
isr.close();
}

- 緩沖流:BufferedReader/BufferedWriter , BufferedInputStream/BufferedOutputStream
沒(méi)有經(jīng)過(guò)Buffered處理的IO, 意味著每一次讀和寫的請(qǐng)求都會(huì)由OS底層直接處理,這會(huì)導(dǎo)致非常低效的問(wèn)題。
經(jīng)過(guò)Buffered處理過(guò)的輸入流將會(huì)從一個(gè)buffer內(nèi)存區(qū)域讀取數(shù)據(jù),本地API只會(huì)在buffer空了之后才會(huì)被調(diào)用(可能一次調(diào)用會(huì)填充很多數(shù)據(jù)進(jìn)buffer)。
經(jīng)過(guò)Buffered處理過(guò)的輸出流將會(huì)把數(shù)據(jù)寫入到buffer中,本地API只會(huì)在buffer滿了之后才會(huì)被調(diào)用。
BufferedReader/BufferedWriter可以將字符流(Reader)包裝成緩沖流,這是最常見(jiàn)用的做法。
另外,BufferedReader提供一個(gè)readLine()可以方便地讀取一行,因此BufferedReader也被稱為行讀取器。而FileInputStream和FileReader只能讀取一個(gè)字節(jié)或者一個(gè)字符。
在原有的節(jié)點(diǎn)流對(duì)象外部包裝緩沖流,為IO流增加了內(nèi)存緩沖區(qū),增加緩沖區(qū)的兩個(gè)目的:
1 .允許IO一次不止操作一個(gè)字符,這樣提高整個(gè)系統(tǒng)的性能
2 .由于有緩沖區(qū),使得在流上執(zhí)行skip,mark和reset方法都成為可能
緩沖流要套接在節(jié)點(diǎn)流之上,對(duì)讀寫的數(shù)據(jù)提供了緩沖功能,增加了讀寫的效率,同時(shí)增加了一些新的方法。例如:BufferedReader中的readLine方法,BufferedWriter中的newLine方法。
//字符輸入流
BufferedReader(Reader in)//創(chuàng)建一個(gè)32字節(jié)的緩沖區(qū)
BufferedReader(Reader in, int size)//size為自定義緩存區(qū)的大小
//字符輸出流
BufferedWriter(Writer out)
BufferedWriter(Writer out, int size)
//字節(jié)輸入流
BufferedInputStream(InputStream in)
BufferedInputStream(InputStream in, int size)
//字節(jié)輸出流
BufferedOutputStream(OutputStream in)
BufferedOutputStream(OutputStream in, int size)

對(duì)于輸出的緩沖流,BufferedWriter和BufferedOutputStream會(huì)先在內(nèi)存中緩存,使用flush方法會(huì)使內(nèi)存中的數(shù)據(jù)立刻寫出。
//字節(jié)輸出流緩沖流BufferedOutputStream
public class BufferedOutputStreamDemo {
public static void main(String[] args)throws IOException {
//創(chuàng)建字節(jié)輸出流,綁定文件
//FileOutputStream fos = new FileOutputStream("c:\\buffer.txt");
//創(chuàng)建字節(jié)輸出流緩沖流的對(duì)象,構(gòu)造方法中,傳遞字節(jié)輸出流
BufferedOutputStream bos = new
BufferedOutputStream(new FileOutputStream("c:\\buffer.txt"));
bos.write(55);
byte[] bytes = "HelloWorld".getBytes();
bos.write(bytes);
bos.write(bytes, 3, 2);
bos.close();
}
}
//字節(jié)輸入流緩沖流BufferedInputStream
public class BufferedInputStreamDemo {
public static void main(String[] args) throws IOException{
//創(chuàng)建字節(jié)輸入流的緩沖流對(duì)象,構(gòu)造方法中包裝字節(jié)輸入流,包裝文件
BufferedInputStream bis = new
BufferedInputStream(new FileInputStream("c:\\buffer.txt"));
byte[] bytes = new byte[10];
int len = 0 ;
while((len = bis.read(bytes))!=-1){
System.out.print(new String(bytes,0,len));
}
bis.close();
}
}
//字符流緩沖區(qū)流復(fù)制文本文件
*
* 使用緩沖區(qū)流對(duì)象,復(fù)制文本文件
* 數(shù)據(jù)源 BufferedReader+FileReader 讀取
* 數(shù)據(jù)目的 BufferedWriter+FileWriter 寫入
* 讀取文本行, 讀一行,寫一行,寫換行
*/
public class Copy_1 {
public static void main(String[] args) throws IOException{
BufferedReader bfr = new BufferedReader(new FileReader("c:\\w.log"));
BufferedWriter bfw = new BufferedWriter(new FileWriter("d:\\w.log"));
//讀取文本行, 讀一行,寫一行,寫換行
String line = null;
while((line = bfr.readLine())!=null){
bfw.write(line);
bfw.newLine();
bfw.flush();
}
bfw.close();
bfr.close();
}
}

- 對(duì)象流(ObjectInputStream/ObjectOutputStream)
如果我們要寫入文件內(nèi)的數(shù)據(jù)不是基本數(shù)據(jù)類型(使用DataInputStream),也不是字符型或字節(jié)型數(shù)據(jù),而是一個(gè)對(duì)象,應(yīng)該怎么寫?這個(gè)時(shí)候就用到了處理流中的對(duì)象流。
對(duì)象的序列化
對(duì)象中的數(shù)據(jù),以流的形式,寫入到文件中保存過(guò)程稱為寫出對(duì)象,對(duì)象的序列化
ObjectOutputStream將對(duì)象寫道文件中,實(shí)現(xiàn)序列化
對(duì)象的反序列化
在文件中,以流的形式,將對(duì)象讀出來(lái),讀取對(duì)象,對(duì)象的反序列化
ObjectInputStream 將文件對(duì)象讀取出來(lái)
注意:
先序列化后反序列化; 反序列化順序必須與序列化一致
不是所有對(duì)象都可以序列化 必須實(shí)現(xiàn) java.io.Serializable
-
不是所有屬性都需要序列化 不需要序列化的屬性 要加 transient
public class Person implements Serializable { private String name; private int age; private transient String intro; //不需要序列化 public Person(String name, int age, String intro) { this.name = name; this.age = age; this.intro = intro; } public String getName() { return name; } public int getAge() { return age; } public String getIntro() { return intro; } public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } public void setIntro(String intro) { this.intro = intro; } public String toString(){ return name + " " + age + " " + intro; } }image.gif進(jìn)行序列化和反序列化后:
public class WriteObject { public static void main(String[] args) throws IOException, ClassNotFoundException { ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream( new File("d:\\test.txt")))); oos.writeObject(new Person("shi",13,"A")); oos.writeObject(new Person("zhang",15,"B")); oos.writeObject(new Person("hao",18,"C")); oos.close(); ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream( new FileInputStream(new File("d:\\test.txt")))); Person p1 = (Person) ois.readObject(); Person p2 = (Person) ois.readObject(); Person p3 = (Person) ois.readObject(); ois.close(); System.out.println(p1); System.out.println(p2); System.out.println(p3); } }image.gif可以發(fā)現(xiàn),自定義類中被transient 標(biāo)記的數(shù)據(jù)將不被序列化和反序列化,而且自定義類也必須要實(shí)現(xiàn)Serializable接口。
注意:
ObjectOutputStream 對(duì)JAVA對(duì)象進(jìn)行序列化處理,處理后的對(duì)象不是文本數(shù)據(jù)。所以數(shù)據(jù)保存到文件中后,用記事本、寫字板、Word等文本編輯器打開(kāi),是無(wú)法識(shí)別的,一定會(huì)顯示亂碼。只有使用相同版本的Java的ObjectInputStream進(jìn)行讀取操作,方可獲取文件中的對(duì)象內(nèi)容。
序列化時(shí)的參數(shù)類型和反序列化時(shí)的返回類型都是Object類型,所以在反序列化接收類對(duì)象數(shù)據(jù)時(shí)要用強(qiáng)制類型轉(zhuǎn)換。
總結(jié)上面幾種流的應(yīng)用場(chǎng)景:
- FileInputStream/FileOutputStream 需要逐個(gè)字節(jié)處理原始二進(jìn)制流的時(shí)候使用,效率低下
- FileReader/FileWriter 需要組個(gè)字符處理的時(shí)候使用
- StringReader/StringWriter 需要處理字符串的時(shí)候,可以將字符串保存為字符數(shù)組
- PrintStream/PrintWriter 用來(lái)包裝FileOutputStream 對(duì)象,方便直接將String字符串寫入文件
- Scanner 用來(lái)包裝System.in流,很方便地將輸入的String字符串轉(zhuǎn)換成需要的數(shù)據(jù)類型
- InputStreamReader/OutputStreamReader , 字節(jié)和字符的轉(zhuǎn)換橋梁,在網(wǎng)絡(luò)通信或者處理鍵盤輸入的時(shí)候用
- BufferedReader/BufferedWriter , BufferedInputStream/BufferedOutputStream , 緩沖流用來(lái)包裝字節(jié)流后者字符流,提升IO性能,BufferedReader還可以方便地讀取一行,簡(jiǎn)化編程。
參考文章:

