簡單的講,序列化就是將java對象轉化成二進制保存到磁盤中去,反序列化就是從磁盤中讀取文件流而後轉成java對象。java
JDK提供了下面兩種方式實現序列化:安全
Serializable
接口Externalizable
下面分別實例演示兩種實現方式: 假設本文全部的序列化對象爲User
,其擁有下面屬性:bash
/**
* 序列化對象
*
* @Author jiawei huang
* @Since 2020年1月2日
* @Version 1.0
*/
public class User {
private String userName;
private String address;
// ....setter/getter
}
複製代碼
咱們在上面User
對象的基礎上實現Serializable
接口,代碼以下:網絡
public class User implements Serializable {
// 序列化ID
private static final long serialVersionUID = 1L;
複製代碼
序列化和反序列化代碼爲:ide
// 序列化方法
public static void serialize(User user) {
ObjectOutputStream outputStream = null;
try {
outputStream = new ObjectOutputStream(new FileOutputStream("C:\\Users\\Administrator\\Desktop\\data.txt"));
outputStream.writeObject(user);
} catch (Exception e) {
e.printStackTrace();
} finally {
// close stream
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
// 反序列化方法
public static void deserialize() {
ObjectInputStream objectInputStream = null;
try {
objectInputStream = new ObjectInputStream(
new FileInputStream("C:\\Users\\Administrator\\Desktop\\data.txt"));
User user = (User) objectInputStream.readObject();
System.out.println(user.getAddress());
System.out.println(user.getUserName());
} catch (Exception e) {
// error
e.printStackTrace();
} finally {
// close stream
if (objectInputStream != null) {
try {
objectInputStream.close();
} catch (IOException e) {
// error
e.printStackTrace();
}
}
}
}
複製代碼
測試代碼以下:測試
public static void main(String[] args) {
User user = new User();
user.setAddress("廣東深圳");
user.setUserName("hjw");
serialize(user);
deserialize();
}
複製代碼
輸出以下:ui
廣東深圳
hjw
複製代碼
User
實現Serializable
接口是必須的嗎?是的,是必須的,不然報下面異常:spa
java.io.NotSerializableException: ex.serializable.User
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
at ex.serializable.Main.serialize(Main.java:41)
at ex.serializable.Main.main(Main.java:33)
複製代碼
由於在ObjectOutputStream
中執行了以下代碼限制: 設計
這也說明,jdk並無默認支持對象的序列化,爲何默認不支持呢?由於java的安全機制限制,咱們設想一下,假設對象都默認支持序列化,那麼就像上面那個User
對象,其私有private
修飾屬性也被序列化了,那麼不符合private
的設計語義code
Externalizable
接口繼承自Serializable
接口Externalizable
接口容許咱們自定義對象屬性的序列化Externalizable
接口必須重寫writeExternal
和readExternal
方法咱們從新新建一個User1
對象以下:
public class User1 implements Externalizable {
private String userName;
private String address;
// setter、getter
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(userName);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
System.out.println(in.readObject());
}
}
複製代碼
測試代碼以下:
User1 user1 = new User1();
user1.setAddress("廣東深圳");
user1.setUserName("hjw");
user1.writeExternal(
new ObjectOutputStream(new FileOutputStream("C:\\Users\\Administrator\\Desktop\\data.txt")));
user1.readExternal(new ObjectInputStream(new FileInputStream("C:\\Users\\Administrator\\Desktop\\data.txt")));
複製代碼
輸出以下:
hjw
複製代碼
兩種實現方式的區別在於Externalizable
容許咱們自定義序列化規則,Externalizable
接口會初始化一次無參構造器,而Serializable
不會
經過實現Externalizable
接口咱們能夠自定義序列化的屬性,一樣的道理,關鍵字transient
也能夠達到相同的效果,可是二者之間仍是有一些區別的。
transient
修飾的變量,即便使用private
修飾也會被序列化private
屬性不被序列化,則可使用Externalizable
static
修飾的成員變量屬於類全局屬性,其值在JDK1.8存儲於元數據區(1.8之前叫方法區),不屬於對象範圍,將不會被序列化。
下面驗證static
不會被序列化: 修改User
對象userName
屬性爲static
修飾
User user = new User();
user.setAddress("廣東深圳");
user.setUserName("hjw");
serialize(user);
user.setUserName("mike");
deserialize();
複製代碼
輸出以下:
廣東深圳
mike
複製代碼
咱們將hjw
序列化到了磁盤文件,結果反序列化以後獲得的值倒是mike
java可否反序列化成功,取決於serialVersionUID
是否一致,咱們能夠把它理解成爲版本號,一旦版本號不一致,將會報序列化出錯。咱們能夠爲對象聲明一個自定義serialVersionUID
,也可使用默認的1L
。
總結就是:
當咱們新增一個類實現Serializable
接口時,建議咱們爲其新增一個serialVersionUID
,由於假設咱們沒有聲明serialVersionUID
,那麼後面假設咱們修改了該類的接口(新增字段)時,當咱們再次反序列化時,就會報錯。由於java會拿編譯器根據類信息自動生成一個id1和反序列化獲得的id2進行比較,若是類有改動,id2和id1確定不一致啦。
Q1:既然static
修飾的不會被序列化,而java又是如何經過serialVersionUID
進行比較的呢?
serialVersionUID
應該是一個特殊字段能夠被序列化,應該能夠從源碼中找到答案,小編沒找到,歡迎評論區留言。
深度克隆即把引用類型的成員也給克隆了,基於上面例子,咱們新增一個類Car
,而且User
擁有一個Car
類型對象。
public class Car implements Serializable {
private static final long serialVersionUID = 1L;
private String carName;
private int price;
// ......
}
複製代碼
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String userName;
//
private Car car;
private String address;
@Override
public String toString() {
return "User [userName=" + userName + ", carName=[" + car.getCarName() + "],price=[" + car.getPrice() + "]"
+ ", address=" + address + "]";
}
// ......
}
複製代碼
克隆方法
public static <T extends Serializable> T cloneObject(T obj) throws IOException {
ObjectOutputStream oos = null;
ByteArrayOutputStream baos = null;
byte[] bytes = null;
try {
// 序列化
baos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
bytes = baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (baos != null) {
baos.close();
}
if (oos != null) {
oos.close();
}
}
ByteArrayInputStream bais = null;
ObjectInputStream ois = null;
try {
// 反序列化
bais = new ByteArrayInputStream(bytes);
ois = new ObjectInputStream(bais);
return (T) ois.readObject();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (bais != null) {
baos.close();
}
if (oos != null) {
ois.close();
}
}
return null;
}
複製代碼
測試代碼及輸出以下:
User user = new User();
user.setAddress("廣東深圳");
user.setUserName("hjw");
Car car = new Car();
car.setCarName("單車");
car.setPrice(300);
user.setCar(car);
User clonedUser = cloneObject(user);
System.out.println(clonedUser);
複製代碼
User [userName=hjw, carName=[單車],price=[300], address=廣東深圳]
複製代碼
Serializable
接口,另外一種是實現Externalizable
接口,後者容許咱們自定義序列化規則