1. 流Stream的概念和分類
一個流(Stream)可以理解為一個數(shù)據(jù)的序列,是從某種介質(zhì)流向其他介質(zhì)的數(shù)據(jù)序列,比如從程序(內(nèi)存)保存數(shù)據(jù)至硬盤,從硬盤讀取數(shù)據(jù)至程序(內(nèi)存)。從程序向網(wǎng)絡(luò)發(fā)送消息,從網(wǎng)絡(luò)接收消息至程序等。
- 按照流向分為輸入流和輸出流
(1)輸入流:從其他介質(zhì)進入程序(內(nèi)存)
(2)輸出流:從程序(內(nèi)存)離開進入其他介質(zhì)中 - 按照處理單位分為字節(jié)流和字符流
(1)字節(jié)流:處理單位為字節(jié)(byte),主要用于處理二進制數(shù)據(jù)。比如,計算機上的所有文件中的內(nèi)容基本都可以看做是二進制數(shù)據(jù)。
(2)字符流:處理單位為字符(char),主要用于處理文本數(shù)據(jù)。比如計算機上的txt文檔中的內(nèi)容可以看做是文本數(shù)據(jù)。 - 按照功能分為原始流和處理流
(1)原始流:提供基礎(chǔ)功能的流
(2)處理流:提供對基礎(chǔ)功能進行功能加強的流
按照流向和單位,流的父類如下
| 輸入 | 輸出 | |
|---|---|---|
| 字節(jié) | InputStream | OutputStream |
| 字符 | Reader | Writer |
這些流的子類以上述的名字作為類名后綴。
比如:
- FileInputStream: 關(guān)于文件操作的字節(jié)輸入流
- FileInputOutputStream: 關(guān)于文件操作的字節(jié)輸出流
- FileReader: 關(guān)于文件操作的字符輸入流
- FileWriter: 關(guān)于文件操作的字符輸出流
原始流在使用后要進行關(guān)閉
2. 字節(jié)流(文件流為例)
以下是代碼的功能是復制c:/1.jpg至e:/1.jpg。復制的過程僅僅操作內(nèi)容復制,無需更改內(nèi)容,直接使用字節(jié)流完成。
可以理解為:
(1)將c:/1.jpg的數(shù)據(jù)輸入至內(nèi)存
(2)將輸入至內(nèi)存的數(shù)據(jù)輸出至e:/1.jpg
public class Test1 {
public static void main(String[] args) {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("c:/1.jpg");// 創(chuàng)建輸入流讀取源文件內(nèi)容
fos = new FileOutputStream("e://1.jpg");// 創(chuàng)建輸出流寫出文件內(nèi)容
byte[] b = new byte[2048]; //2KB區(qū)域,一次讀取源文件中的2KB數(shù)據(jù)
// 從源文件中讀取2KB數(shù)據(jù)至b數(shù)組中
int len = fis.read(b);
/* len表示的是在源文件中讀取字節(jié)長度
len是-1表示讀取結(jié)束。最后一次讀取不一定讀了2KB數(shù)據(jù),
需要用len確定讀了多少數(shù)據(jù),寫出時以免寫出冗余
*/
while(len != -1) {
//將讀取到b數(shù)組中的數(shù)據(jù)寫出至目標文件
//第二個參數(shù)表示偏移量,從數(shù)組的下標幾開始輸出
//第三個參數(shù)表示輸出字節(jié)個數(shù)
fos.write(b, 0, len);
fos.flush(); //將數(shù)據(jù)真正刷出至目標文件中
//再次讀入下一組數(shù)據(jù)至b中
len = fis.read(b);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 關(guān)閉流
try {
if(fis != null) {
fis.close();
}
if(fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
3. 字符流(文件流為例)
讀取c:/1.txt文檔中的內(nèi)容,將其中所有的字符'哈'替換為'*',并輸出至e:/1.txt中。其原理與文件復制一致,不過操作涉及對內(nèi)容的修改,需要在內(nèi)存中完成對字符的替換,所以使用字符流進行操作
public class Test2 {
public static void main(String[] args) {
FileReader fr = null;
FileWriter fw = null;
try {
fr = new FileReader("D://1.txt");
fw = new FileWriter("E://1.txt");
char[] c = new char[1024]; //一次讀取1024個字符,字符流一次處理一個char,所以用char數(shù)組不使用byte數(shù)組
int len = fr.read(c); //將文本文件中的文字讀入的c數(shù)組中
while(len != -1) {
String temp = new String(c); //利用字符數(shù)組c構(gòu)造成一個字符串
temp = temp.replaceAll("哈", "*"); //將字符串中的“哈”替換為“*”
c = temp.toCharArray();//將替換后的字符串變回字符數(shù)組
fw.write(c, 0, len);//將替換后的字符數(shù)組寫出至目標位置
fw.flush();
len = fr.read(c); //繼續(xù)讀取下一組字符
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(fr != null) {
fr.close();
}
if(fw != null) {
fw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
4. 處理流(緩沖流為例)
處理流本質(zhì)上不提供任何功能,它提供了對原始流功能的加強。
以字符緩沖輸入流為例,原始的字符輸入流只能以字符數(shù)組 (char[])讀取數(shù)據(jù),如果現(xiàn)在提出要讀取txt文件中整行數(shù)據(jù),原始的字符輸入流沒有相應的方法完成這個功能,但利用緩沖流提供的方法,可以容易的達到目的。
示例:以行(line)為單位,操作txt文檔的復制
public class Test1 {
public static void main(String[] args) {
FileReader fr = null; //原始的字符輸入流
BufferedReader br = null; //緩沖字符輸入流
FileWriter fw = null; //原始的字符輸出流
BufferedWriter bw = null; //緩沖字符輸出流
try {
fr = new FileReader("d://1.txt");
br = new BufferedReader(fr);
fw = new FileWriter("d://2.txt");
bw = new BufferedWriter(fw);
String line = br.readLine(); //讀取源文件中的一行文本
while(line != null) { //如果不為null則認為讀取到了文本
System.out.println(line); //在控制臺上顯示文本
bw.write(line); //在目標文件中寫該行文本
bw.newLine(); //目標文件新起一行
bw.flush(); //刷入目標文件
line = br.readLine(); //讀取源文件的下一行文本
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(fr != null) {
fr.close();
}
if(fw != null) {
fw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
5. 序列化
序列化是將對象以二進制數(shù)據(jù)的形式轉(zhuǎn)儲。比如將系統(tǒng)中的二進制數(shù)據(jù)暫時保存。
5.1 可被序列化的類
一個類如果可以被序列化,它需要實現(xiàn)java.io.Serializable接口,并生成一個常量值的序列碼,這個序列碼可以理解為是某個類的身份證號。
public class Student implements Serializable{
private static final long serialVersionUID = 4188262972381648549L;
private int sno;
private String sname;
private Date birthday;
public Student() {}
public Student(int sno, String sname, Date birthday) {
this.sno = sno;
this.sname = sname;
this.birthday = birthday;
}
public int getSno() {
return sno;
}
public void setSno(int sno) {
this.sno = sno;
}
public String getSname() {
return sname;
}
public void setSname(String sname) {
this.sname = sname;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
}
如果類中的某個屬性需要是其他類的對象并且也需要序列化,那么這個所謂的“其他類”也需要實現(xiàn)java.io.Serializable接口。比如上面這個類中的Date birthday屬性,java.util.Date類也實現(xiàn)了java.io.Serializable接口,系統(tǒng)中大部分的常用類都實現(xiàn)了java.io.Serializable接口,比如包裝類,集合類等。
5.2 序列化
將對象從內(nèi)存序列化至其他介質(zhì),本章示例將其序列化至c:/1.abc文件中
public class Test1 {
/**
* 序列化操作 將對象從內(nèi)存轉(zhuǎn)入其他介質(zhì)中
* @param args
*/
public static void main(String[] args) {
Student s1 = new Student(101, "趙四", new Date());
Student s2 = new Student(102, "哈哈", new Date());
//序列化操作: 將s1對象保存在c:/1.abc文件中
FileOutputStream fos = null;
ObjectOutputStream oos = null;
try {
fos = new FileOutputStream("c:/1.abc");
oos = new ObjectOutputStream(fos);//典型的處理流 操作oos相當于操作了fos
oos.writeObject(s1); //將s1對象寫入到fos對應的文件中
oos.writeObject(s2); //將s2對象寫入到fos對應的文件中
oos.flush();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
if(fos != null) {
fos.close();//關(guān)閉原始流即可
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
5.3 反序列化
將對象從文件中讀取回內(nèi)存
public class Test2 {
/**
* 反序列化:將對象從其他介質(zhì)轉(zhuǎn)入到內(nèi)存中
* @param args
*/
public static void main(String[] args) {
FileInputStream fis = null;
ObjectInputStream ois = null;
try {
fis = new FileInputStream("c:/1.abc");
ois = new ObjectInputStream(fis);
Student s1 = (Student)ois.readObject();
Student s2 = (Student)ois.readObject();
System.out.println(s1.getSno()+"\t"+s1.getSname()+"\t"+s1.getBirthday());
System.out.println(s2.getSno()+"\t"+s2.getSname()+"\t"+s2.getBirthday());
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
if(fis != null) {
fis.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
5.4 transient關(guān)鍵字
該關(guān)鍵字用于修飾屬性,被這個關(guān)鍵字修飾的屬性不參與序列化和反序列化。比如
將Student類的birthday屬性加上transient關(guān)鍵字修飾
public class Student implements Serializable{
private static final long serialVersionUID = 4188262972381648549L;
private int sno;
private String sname;
private transient Date birthday;
public Student() {}
public Student(int sno, String sname, Date birthday) {
this.sno = sno;
this.sname = sname;
this.birthday = birthday;
}
public int getSno() {
return sno;
}
public void setSno(int sno) {
this.sno = sno;
}
public String getSname() {
return sname;
}
public void setSname(String sname) {
this.sname = sname;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
}
再次進行測驗會發(fā)現(xiàn)序列化后,反序列化回來的數(shù)據(jù)中birthday都是null。相當于birthday沒有參與序列化過程。