天天看點

java 序列化内容分析

網絡通信,資料持久化的時候經常會涉及到對象序列化問題,如果對序列化的内容大小或者序列化的效率比較敏感,可以選擇protobuf,hession等,如果不是特别在意這塊,選擇java預設的實作就可以了。java中通過調用ObjectOutputStream對象的writeObject()方法實作對象的序列化。來看段源碼:

// remaining cases
            if (obj instanceof String) {
                writeString((String) obj, unshared);
            } else if (cl.isArray()) {
                writeArray(obj, desc, unshared);
            } else if (obj instanceof Enum) {
                writeEnum((Enum) obj, desc, unshared);
            } else if (obj instanceof Serializable) {
                writeOrdinaryObject(obj, desc, unshared);
            } else {
                if (extendedDebugInfo) {
                    throw new NotSerializableException(
                        cl.getName() + "\n" + debugInfoStack.toString());
                } else {
                    throw new NotSerializableException(cl.getName());
                }
            }
           

ObjectOutputStream在寫入對象的時候對四種不同的類型做了不同的處理,除了字元串,數組類型,枚舉類型之外,其他的對象想要序列化必須實作Serializable這個辨別接口。看到這裡感覺好像基本資料類型(int,long之類)就不能寫入,其實不是的。ObjectOutputStream在寫入基本資料類型的時候會把它們轉化成對象類寫入,int的對象類就是Integer,而

Integer類的父類Number就是實作Serializable這個接口的。

經常看到網上說java中實作一個對象的序列化可以實作Serializable接口或者Externalizable接口,本質上Externalizable就是Serializable接口的一個子接口,但是實作Externalizable接口的類可以自定義寫入需要通信或者持久化的字段資訊。來看個例子:

public class ObjectTest {

	public static void main(String[] args) throws Exception {
		new ObjectTest().serialization();
	}
	
	public void serialization() throws Exception{
		URL url=ObjectTest.class.getResource("/config.properties");
		OutputStream output=new FileOutputStream(new File(url.getPath()));
		ObjectOutput objectOut=new ObjectOutputStream(output);
		objectOut.writeObject(new Object1());
		objectOut.close();
		output.close();
		
		URL url2=ObjectTest.class.getResource("/config2.properties");
		OutputStream output2=new FileOutputStream(new File(url2.getPath()));
		ObjectOutput objectOut2=new ObjectOutputStream(output2);
		objectOut2.writeObject(new Object2());
		objectOut2.close();
		objectOut2.close();
	}
}
           
package net.flyingfat.serialization;

import java.io.Serializable;

public class Object1 implements Serializable {
	private static final long serialVersionUID = 1L;
	private String bac="abc";
	private int a=123;
	
	public String getBac() {
		return bac;
	}

	public void setBac(String bac) {
		this.bac = bac;
	}

	public int getA() {
		return a;
	}

	public void setA(int a) {
		this.a = a;
	}
	
}
           
package net.flyingfat.serialization;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;

public class Object2  implements Externalizable {
	private static final long serialVersionUID = 1L;
	private String bac="123";
	private int a;

	public String getBac() {
		return bac;
	}

	public void setBac(String bac) {
		this.bac = bac;
	}

	@Override
	public void writeExternal(ObjectOutput out) throws IOException {
		out.write(bac.getBytes("utf-8"));
	}

	@Override
	public void readExternal(ObjectInput in) throws IOException,
			ClassNotFoundException {
		in.readObject();
	}

	public int getA() {
		return a;
	}

	public void setA(int a) {
		this.a = a;
	}
	
}
           

通過UE打開兩個檔案進行對比:

config.properties内容如下:

java 序列化内容分析

config2.properties内容如下:

2

java 序列化内容分析

截圖内容太密,沒法仔細标出每個位元組是幹嘛的,有興趣的直接看源碼吧,簡單說明下:

1.兩者開頭都一樣,寫入類的定義(net.flyingfat.serialization.Object1/Object2),緊接着的8個位元組就是 serialVersionUID(序列化id,它的作用應該都清楚)

2.後面具體對象的資訊就一樣了,實作Serializable接口的對象寫入的是每個字段的類型,名字,和值,而實作Externalizable接口的對象寫入的就是我們自定義的内容。

在來看另一個現象

public class Object1 extends Super  {
	private static final long serialVersionUID = 1L;
	private String bac="abc";
	private int a=123;
	
	public String getBac() {
		return bac;
	}

	public void setBac(String bac) {
		this.bac = bac;
	}

	public int getA() {
		return a;
	}

	public void setA(int a) {
		this.a = a;
	}
	
}

class Super implements Serializable  {
	
	private String s;
	
	public Super(String s) {
		super();
		this.s = s;
	}
	public Super() {}

	public String getS() {
		return s;
	}

	public void setS(String s) {
		this.s = s;
	}
	
}
           
public class Object2 extends Super implements Serializable {
	private static final long serialVersionUID = 1L;
	private String bac="abc";
	private int a=123;
	
	public String getBac() {
		return bac;
	}

	public void setBac(String bac) {
		this.bac = bac;
	}

	public int getA() {
		return a;
	}

	public void setA(int a) {
		this.a = a;
	}
	
}

class Super  {
	
	private String s;
	
	public Super(String s) {
		super();
		this.s = s;
	}
	public Super() {}

	public String getS() {
		return s;
	}

	public void setS(String s) {
		this.s = s;
	}
	
}
           

同時序列化這兩個對象,可以看到内容不一樣。

結論就是: 對象在序列化的時候,看該對象是直接還是間接實作Serializable接口,直接實作就隻序列化目前對象本身的字段資訊,如果是間接就會挨個往上找到父類,一并把父類的字段資訊也序列化了。