淺談Java序列化機制

1、序列化、反序列化概念及其使用場景

一、序列化、反序列化的概念

簡單的講,序列化就是將java對象轉化成二進制保存到磁盤中去,反序列化就是從磁盤中讀取文件流而後轉成java對象。java

二、使用場景

  • 一、網絡通信傳輸java對象數據
  • 二、永久保存java對象

2、實現序列化的方式有哪些?

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
}
複製代碼

一、基於Serializable接口

咱們在上面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
複製代碼
  • Q一、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接口

  • 一、Externalizable接口繼承自Serializable接口
  • 二、Externalizable接口容許咱們自定義對象屬性的序列化
  • 三、實現Externalizable接口必須重寫writeExternalreadExternal方法

咱們從新新建一個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不會

3、transient關鍵字和static成員變量

經過實現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

4、關於serialVersionUID

java可否反序列化成功,取決於serialVersionUID是否一致,咱們能夠把它理解成爲版本號,一旦版本號不一致,將會報序列化出錯。咱們能夠爲對象聲明一個自定義serialVersionUID,也可使用默認的1L

總結就是:

當咱們新增一個類實現Serializable接口時,建議咱們爲其新增一個serialVersionUID,由於假設咱們沒有聲明serialVersionUID,那麼後面假設咱們修改了該類的接口(新增字段)時,當咱們再次反序列化時,就會報錯。由於java會拿編譯器根據類信息自動生成一個id1和反序列化獲得的id2進行比較,若是類有改動,id2和id1確定不一致啦。

Q1:既然static修飾的不會被序列化,而java又是如何經過serialVersionUID進行比較的呢?

serialVersionUID應該是一個特殊字段能夠被序列化,應該能夠從源碼中找到答案,小編沒找到,歡迎評論區留言。

5、序列化、反序列化實現深度克隆

深度克隆即把引用類型的成員也給克隆了,基於上面例子,咱們新增一個類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=廣東深圳]
複製代碼

6、總結

  • 一、新增序列化類時,建議新增一個自定義id,防止後續版本之間不兼容
  • 二、static、transient修飾的字段不會被序列化,序列化一樣會序列化private屬性
  • 三、序列化能夠實現深度克隆
  • 四、實現序列化有兩種方式,一種是直接實現Serializable接口,另外一種是實現Externalizable接口,後者容許咱們自定義序列化規則
相關文章
相關標籤/搜索