1. Stream(流) Category(分類)
- 數據單位不同:字節(jié)流(8 bit,處理字節(jié),如圖片視頻)/字符流(16 bit,處理文本)
- 流的流向不同:輸入流/輸出流(在程序的角度看待流的流向)
- 流的角色不同:節(jié)點流(也叫“文件流”,直接作用于文件上)/處理流(在節(jié)點流之上又裝飾一層流)
| 抽象基類 | 字節(jié)流 | 字符流 |
|---|---|---|
| 輸入流 | InputStream | Reader |
| 輸出流 | OutputStream | Writer |
2. 節(jié)點流
直接作用在文件上的流就是節(jié)點流,也叫做文件流,java.io包下有四個類是節(jié)點流。
FileInputStream
FileOutputStream
FileReader
FileWriter
3. 處理流
處理流作用在基本流之上,可以額外的添加一些功能,比如一開始使用FileReader讀入文件,如果在FileReader之上再包裝一層緩沖流BufferReader可以增加我呢見的讀取速度。這種嵌套包裝的方式稱作裝飾者模式。
4. 標準輸入、輸出流
在不同編程語言中,標準輸入/輸出都是指鍵盤(標準輸入)、顯示器或控制臺(標準輸出)。再Java中標準輸入輸出對應了兩個流:
- 標準輸入:
System.in->InputStream - 標準輸出:
System.out->OutputStream
那么標準輸入輸出有什么用呢?Java編程中我們通過要獲取鍵盤輸出,有兩種方式:
- Scanner
- 使用標準輸入流
方式二代碼:
public class test1 {
public static void main(String[] args) {
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(isr);
try {
// br.readLine()方法會阻塞住,直到有鍵盤輸入
String s = br.readLine();
System.out.println(s);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
isr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
3. flush()方法作用
flush()方法是在類java.io.OutputStream中的,所有的輸出流都有這個方法,作用是:刷新緩沖區(qū)。

上圖中,web服務器通過一個輸出流向客戶端發(fā)送300字節(jié)的信息,但是緩沖區(qū)的大小有1024字節(jié),默認情況下輸出流會等待緩沖區(qū)填滿后再一次性把緩沖區(qū)全部數據發(fā)送給客戶端,所有此時300字節(jié)的信息太小,服務器就不會發(fā)送,客戶端也就接受不帶消息。而調用輸出流的
flush()函數會強制刷新緩沖區(qū),將緩沖區(qū)的數據發(fā)送出去。
4. 對象流實現Java對象序列化和反序列化
/**
* @description: 序列化目標類
* @author: sanjin
* @date: 2019/7/6 18:59
*/
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private Integer age;
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
/**
* @description: 進行序列化
* @author: sanjin
* @date: 2019/7/6 18:59
*/
public class 對象序列化 {
public static void main(String[] args) {
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream("P:\\Projects\\Java\\Demos\\spring-framework\\my-spring-demo\\src\\main\\java\\com\\sanjin\\blog02\\java對象序列化\\person.txt"));
Person perosn = new Person("小白", 11);
oos.writeObject(perosn);
oos.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (oos!=null) {
oos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* @description: 從文本反序列化出對象
* @author: sanjin
* @date: 2019/7/6 19:08
*/
public class 反序列化對象 {
public static void main(String[] args) {
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream("P:\\Projects\\Java\\Demos\\spring-framework\\my-spring-demo\\src\\main\\java\\com\\sanjin\\blog02\\java對象序列化\\person.txt"));
Person object = (Person) ois.readObject();
System.out.println(object);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
} finally {
if (ois !=null){
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
序列化的意義在于通過序列化將對象能夠持久化到硬盤中,在需要的時候在通過反序列化將對象從硬盤中加載到內存,比如我們在使用redis,MQ時候就需要考慮序列化問題。
序列化中的serialVersionUID
如果要序列化某個對象,該對象就必須實現java.io.Serializable接口才能被序列化,
使用該接口后,我們通常還會添加一個字段:
private static final long serialVersionUID = 1L// 值可以改變
那么這個字段有什么作用呢?
serialVersionUID用來表明類的不同版本之間的兼容性。
簡單來說,就是為了解決這么一個問題?
之前我們的Person類是這樣的:
/**
* @description: 序列化目標類
* @author: sanjin
* @date: 2019/7/6 18:59
*/
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private Integer age;
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
我們顯示的聲明了serialVersionUID,然后使用ObjectOutputStream對其進行序列化,然后下一步我們修改這個類,修改后如下:
/**
* @description: 序列化目標類
* @author: sanjin
* @date: 2019/7/6 18:59
*/
public class Person implements Serializable {
private static final long serialVersionUID = 545641L;
private String name;
private Integer age;
private int id;
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
public Person(String name, Integer age, int id) {
this.name = name;
this.age = age;
this.id = id;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", id=" + id +
'}';
}
}
可以看到我在Person類中添加了一個Id字段,并且為這個字段添加了一個三個參數的構造器。那么此時,我們還能反序列化出這個Persion對象么?如果能正確序列化,那么Person對象有沒有這個id值呢?
結果如下:

可以看到,我們在對Person添加了一個id字段,還是可以完成反序列化,并且id字段的值是int類型的默認值。這是我們手動給定了一個serialVersionUID的情況,那么我們不手動指定會出現什么情況呢?
再次修改Person類:
/**
* @description: 序列化目標類
* @author: sanjin
* @date: 2019/7/6 18:59
*/
public class Person implements Serializable {
private String name;
private Integer age;
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
可以看到我僅僅只是刪除了serailVersionUID字段,實現序列化接口后,如果不手動指定serialVersionUID,那么Java在運行時環(huán)境會根據類的內部細節(jié)自動生成,這就意味著我們修改Person類會生成不同的serialVersionUID。
修改Person后進行序列化。
再次修改Person類,添加id字段。
/**
* @description: 序列化目標類
* @author: sanjin
* @date: 2019/7/6 18:59
*/
public class Person implements Serializable {
private String name;
private Integer age;
private int id;
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
public Person(String name, Integer age, int id) {
this.name = name;
this.age = age;
this.id = id;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", id=" + id +
'}';
}
}
再次進行反序列化:

可以看到,在反序列化時出現了異常,總結一下serialVersionUID作用:
Java序列化機制通過運行時判斷類的serialVersionUID來驗證版本的一致性。在進行反序列化時,JVM從字節(jié)流中讀取到的serialVersionUID是Person(沒有id字段),JVM會將這個serialVersionUID與本地Person(添加了id字段)的SerialVersionUID進行對比,如果一致,則進行序列化,若不一致則拋出
java.io.InvalidClassException異常。一句話總結:手動指定serialVersionUID則可以兼容進行序列化,若由JVM自動生成如果修改了類字段或者其他內部信息,其生成的serialVersionUID就會與第一次不同,序列化就會拋出異常。所以在實現了序列化接口后最好指定一下serialVersionUID。