Java基本IO操作(流)
一、 流的概述
1、什麼是流
流根據方向不同分為輸入流與輸出流,參照點為當前程式。輸入流用來讀取資料,輸出流用來寫資料。
2、流的分類(2類)
- 節點流(也叫低階流)
是真實負責讀寫資料的流,讀寫操作中必須要有低階流。資料來源明確。 - 處理流(也叫高階流)
讀寫可以沒有高階流,高階流也不能獨立存在,必須用於處理其他流,處理其他流的目的是簡化讀寫資料中的操作。
3、低階流和高階流的關係
低階流可以類比為水管,而高階流則可以類比為加在水管上的熱水器和淨水器。所以會得出以下的結論:
- 低階流可以單獨存在;而高階流必須依賴於低階流而存在。因為低階流是真實讀寫資料的流,高階流都是處理資料的。
- 高階流是用於簡化資料讀寫的,所以我們用高階流的時候,需要想一下,這個高階流有什麼樣的功能。
- 高階流處理其他流就形成了流的連線。並且有效的組合不同的高階流可以得到疊加的效果。
- 有高階流的時候,直接關閉高階流就可以了,它會自動把所依賴的低階流關閉(就好像你只需要關閉熱水器的開關就可以了)。
- 下面會分別介紹幾個低階流和高階流
4、兩個抽象類
InputStream
抽象類,是所有位元組輸入流的父類,定義了輸入流的讀取位元組的方法,常用的方法如下:int read()
讀取 一個位元組,以int形式返回,該int值得“低八位”有效,若返回值為-1,則表示EOFint read(byte[])
嘗試最多讀取給定陣列的length個位元組並存入該陣列,返回值為實際讀取到的位元組量
OutputStream
抽象類,是所有位元組輸出流的父類,定義了輸出流的寫入位元組的方法,常用的方法如下:void write(int d)
寫出一個位元組,寫的是給定的int的“低八位”void write(byte[] d)
將給定的位元組陣列中的所有位元組全部寫出
二、低階流
1、檔案流
FileInputStream
檔案的位元組輸入流,是一個低階流,該流可以以位元組為單位從檔案中讀取資料。構造方法如下:FileInputStream(File file)
建立一個從指定File物件表示的檔案中讀取資料的檔案輸入流FileInputStream(String name)
建立用於讀取給定的檔案系統中的路徑名name所指定的檔案的檔案輸入流
FileOutputStream
檔案的位元組輸出流,是一個低階流,該流可以以位元組為單位將資料寫入檔案中。構造方法如下:FileOutputStream(File file)
建立一個向指定File物件表示的檔案中寫出資料的檔案輸出流。(如果指定的檔案已經包含內容,會將原有資料全部清除)FileOutputStream(String name)
建立一個向具有指定名稱的檔案中寫出資料的檔案輸出流。(如果指定的檔案已經包含內容,會將原有資料全部清除)FileOutputStream(File file, boolean append)
建立一個向指定File物件表示的檔案中寫出資料的檔案輸出流。(如果第二個引數為true,則不會清除原資料,而是在檔案末尾追加)FileOutputStream(String name, boolean append)
建立一個向具有指定名稱的檔案中寫出資料的檔案輸出流。(如果第二個引數為true,則不會清除原資料,而是在檔案末尾追加)
基本操作:
int read()
讀取 一個位元組,以int形式返回,該int值得“低八位”有效,若返回值為-1,則表示EOF
int read(byte[] b)
嘗試最多讀取給定陣列的length個位元組並存入該陣列,返回值為實際讀取到的位元組量
void write(int d)
寫出一個位元組,寫的是給定的int的“低八位”
void write(byte[] d)
將給定的位元組陣列中的所有位元組全部寫出
void write(byte[] d, int offset, int len)
將指定byte陣列中從偏移量offset開始的len個位元組寫入此檔案輸出流
示例程式碼:
輸入流
public class FISDemo {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("fis.txt");
byte[] data = new byte[100];
int len = fis.read(data);
//String str = new String(data, "UTF-8").trim();
String str = new String(data, 0, len, "UTF-8");
System.out.println(str);
fis.close();
}
}
輸出流
public class FOSDemo {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("fos.txt");
String str = "我愛java";
/*
* String -> byte[]
* byte getBytes()
* 將當前字串按照系統預設字符集轉換為一組位元組
*
* byte getBytes(String csn)
* 按照給定的字符集將當前字串轉換為一組位元組
*/
byte[] data = str.getBytes("UTF-8");
fos.write(data);
fos.close();
System.out.println("寫出完畢");
}
}
複製檔案
public class CopyDemo1 {
public static void main(String[] args) throws IOException {
/*
* 使用檔案輸入流讀取原檔案,再使用檔案輸出流向目標檔案中寫。
* 順序從原檔案中讀取每個位元組並寫入到目標檔案即可完成複製
*/
FileInputStream src = new FileInputStream("1.jpg");
FileOutputStream desc = new FileOutputStream("1_cp_cp.jpg");
byte[] buf = new byte[1024*10];
int len = -1;
while((len=src.read(buf))!=-1){
desc.write(buf, 0, len);
}
System.out.println("複製完畢");
src.close();
desc.close();
}
三、高階流
1、緩衝流
- BufferedInputStream
在讀取資料時,若以位元組為單位讀取資料,會導致讀取次數過於頻繁,從而大大的降低讀取效率。為此我們可以通過提高一次讀取的位元組數量減少讀寫次數來提高讀取的效率。(不用再自己去維護一個buffer,像上述程式碼中的那樣)。
BufferedInputStream是緩衝位元組輸入流。其內部維護著一個緩衝區(位元組陣列,預設為8192個位元組),使用該流在讀取一個位元組時,該流會盡可能多的一次性讀取若干位元組並存入緩衝區,然後逐一的將位元組返回,直到緩衝區中的資料被全部讀取完畢,會再次讀取若干位元組從而反覆。這樣就減少了讀取的次數,從而提高了讀取效率。
示例程式碼:
public class CopyDemo2 {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("1.ts");
BufferedInputStream bis = new BufferedInputStream(fis);
FileOutputStream fos = new FileOutputStream("1_cp_cp.ts");
BufferedOutputStream bos = new BufferedOutputStream(fos);
int d = -1;
/*
* 緩衝流內部有一個緩衝區
* 當bis.read方法讀取第一個位元組時,
* 實際上BIS會一次性讀取一組位元組 並存入內部的位元組陣列中,
* 然後將第一個位元組返回,當再次呼叫read方法時,BIS直接
* 從位元組陣列中將第二個位元組返回,直到位元組陣列中所有位元組全部
* 返回後,才會再次讀取一組位元組。
* 所以緩衝流也是依靠提高一次讀寫的資料量減少讀寫次數來達到提高讀寫的效率的
*/
while((d = bis.read())!=-1){
bos.write(d);
}
bis.close();
bos.close();
System.out.println("複製完畢");
}
}
- BufferedOutputStream
在做寫出操作時,增大寫出次數無疑會降低寫出效率。為此我們可以使用緩衝輸出流來一次性批量寫出若干資料減少寫出次數來提高寫出效率。
BufferedOutputStream緩衝輸出流內部維護著一個緩衝區,每當我們向該流寫資料時,都會先將資料存入緩衝區,當緩衝區已滿時,緩衝流會將資料一次性全部寫出。
使用緩衝輸出流可以提高寫出效率,但是也存在一個問題,就是寫出資料缺乏即時性,有時候我們需要在執行完某些操作後,就希望將這些資料缺失寫出,而非在緩衝區中儲存直到緩衝區滿後才寫出。這時,我們就可以使用緩衝流的一個方法 void flush(),該方法會清空緩衝區,將緩衝區中的資料強制寫出。
示例程式碼:
public class BOSDemo {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("box.txt");
BufferedOutputStream bos = new BufferedOutputStream(fos);
bos.write("你好".getBytes("utf-8"));
//強制將緩衝區中的位元組一次性寫出
bos.flush();
System.out.println("寫出完畢!");
bos.close();
}
}
2、物件流
物件是存在於記憶體中的。有時候我們需要將物件儲存到硬碟上,或者需要將物件傳輸到另一臺計算機上等等這樣的操作。這時我們需要將物件轉換為一個位元組序列,而這個過程就稱為物件的序列化。相反,我們有這樣一個位元組序列需要將其轉換為對應的物件,這個過程就稱為物件的反序列化。
ObjectOutputStream
是用來對物件進行序列化的輸出流。- 其實現物件序列化的方法為: void writeObject(Object o)
該方法可以將給定的物件轉換為一個位元組序列後寫出。
- 其實現物件序列化的方法為: void writeObject(Object o)
ObjectInputStream
是用來對物件進行反序列化的輸入流。- 其實現物件反序列化的方法為: Object readObject()
該方法可以從流中讀取位元組並轉換為對應的物件。
- 其實現物件反序列化的方法為: Object readObject()
示例程式碼:
/**
* 該類用於測試作為物件流讀寫物件使用
*
* 當一個類需要被物件流讀寫,那麼該類必須實現java.io.Serializable介面
*/
public class Person implements Serializable{
/**
* 當一個類實現了Serializable介面後
* 應當新增一個常量:serialVersionUID
* 該常量為當前類的序列化版本號,若不定義,系統會根據當前類的結構生成,
* 但是隻要類的結構發生改變,版本號也會相應發生改變.
*
* 版本號影響著反序列化的結果。即:
* 當OIS對一個物件進行反序列化時,會檢查該物件與類的版本是否一致:
* 若一致:反序列化成功,但是若該物件與類的結構不一致,則採用相容模式,能還原的屬性都還原。
* 若不一致:反序列化直接丟擲版本不一致異常( InvalidClassException )
*/
private static final long serialVersionUID = 1L;
private String name;
private int age;
private String gender;
/*
* transient 關鍵字用來修飾屬性
* 當被修飾後,該類例項在使用OOS進行物件序列化時,該屬性值被忽略。
* 從而達到物件“瘦身”的目的
*/
private transient List<String> otherInfo;
public Person(){
}
public Person(String name, int age, String gender, List<String> otherInfo) {
super();
this.name = name;
this.age = age;
this.gender = gender;
this.otherInfo = otherInfo;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public List<String> getOtherInfo() {
return otherInfo;
}
public void setOtherInfo(List<String> otherInfo) {
this.otherInfo = otherInfo;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", gender=" + gender + ", otherInfo=" + otherInfo + "]";
}
}
物件序列化
/**
* 物件流
* 物件流是一對高階流,作用是方便讀寫java中的物件。
* java.io.ObjectOutputStream
* 物件輸出流,可以將給定的物件轉換為一組位元組後寫出.
*/
public class OOSDemo {
public static void main(String[] args) throws IOException {
Person p = new Person();
p.setName("小明");
p.setAge(20);
p.setGender("男");
List<String> otherInfo = new ArrayList<String>();
otherInfo.add("是一名演員");
otherInfo.add("喜歡運動");
otherInfo.add("喜歡看電影");
p.setOtherInfo(otherInfo);
FileOutputStream fos = new FileOutputStream("person.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
/*
* ObjectOutputStream的writeObject方法可以將給定物件轉換為一組位元組
* 後寫出.這些位元組比該物件實際內容要大,因為除了資料外還有結構等描述資訊。
*
* 下面的程式碼實際上經歷了兩個操作:
* 1:將Person物件轉換為一組位元組
* 將一個物件轉換一組位元組的過程稱為:物件序列化
* 2:再通過fos將這組位元組寫入到硬碟
* 將該物件轉換的位元組寫入到硬碟做長久儲存的過程稱為:物件持久化
*/
oos.writeObject(p);
oos.close();
System.out.println("寫出物件完畢");
}
}
反序列化
/**
* java.io.ObjectInputStream
* 物件輸入流,作用是可以進行物件反序列化,讀取一組位元組並還原為物件
* OIS讀取的位元組必須是由OOS將物件序列化得到的位元組,否則會丟擲異常
*/
public class OISDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
FileInputStream fis = new FileInputStream("person.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
//物件反序列化
Person p = (Person)ois.readObject();
ois.close();
System.out.println(p);
}
}