Java序列化的幾種方式

 

序列化和反序列化
序列化:可以將對象轉化成一個字節序列,便於存儲。
反序列化:將序列化的字節序列還原
優點:可以實現對象的”持久性”, 所謂持久性就是指對象的生命週期不取決於程序。

原生序列化方式

序列化方式一: 實現Serializable接口(隱式序列化)

通過實現Serializable接口,這種是隱式序列化(不需要手動),這種是最簡單的序列化方式,會自動序列化所有非static和 transient關鍵字修飾的成員變量。

class Student implements Serializable{
	private String name;
	private int age;
	public static int QQ = 1234;
	private transient String address = "CHINA";
	
	Student(String name, int age ){
		this.name = name;
		this.age = age;
	}
	public String toString() {
		return "name: " + name + "\n"
				+"age: " + age + "\n"
				+"QQ: " + QQ + "\n" 
				+ "address: " + address;
				
	}
	public void SetAge(int age) {
		this.age = age;
	}
}
public class SerializableDemo {
	public static void main(String[] args) throws IOException, ClassNotFoundException {
		//創建可序列化對象
		System.out.println("原來的對象:");
		Student stu = new Student("Ming", 16);
		System.out.println(stu);
		//創建序列化輸出流
		ByteArrayOutputStream buff = new ByteArrayOutputStream();
		ObjectOutputStream out = new ObjectOutputStream(buff);
		//將序列化對象存入緩衝區
		out.writeObject(stu);
		//修改相關值
		Student.QQ = 6666; // 發現打印結果QQ的值被改變
		stu.SetAge(18);   //發現值沒有被改變
		//從緩衝區取回被序列化的對象
		ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(buff.toByteArray()));
		Student newStu = (Student) in.readObject();
		System.out.println("序列化後取出的對象:");
		System.out.println(newStu);
		
	}
}

打印結果:

原來的對象:
name: Ming
age: 16
QQ: 1234
address: CHINA


序列化後取出的對象:

name: Ming
age: 16
QQ: 6666
address: null

 

發現address(被transient)和QQ(被static)也沒有被序列化,中途修改QQ的值是爲了以防讀者誤會QQ被序列化了。因爲序列化可以保存對象的狀態,但是QQ的值被改變了,說明沒有被序列化。static成員不屬於對象實例,可能被別的對象修改沒辦法序列化,序列化是序列對象。對於address被反序列化後由於沒有對應的引用,所以爲null。而且Serializable不會調用構造方法。
PS:細心的可能發現序列化很誘人,可以保存對象的初始信息,在以後可以回到這個初始狀態。

序列化方式二:實現Externalizable接口。(顯式序列化)

Externalizable接口繼承自Serializable, 我們在實現該接口時,必須實現writeExternal()和readExternal()方法,而且只能通過手動進行序列化,並且兩個方法是自動調用的,因此,這個序列化過程是可控的,可以自己選擇哪些部分序列化

public class Blip implements Externalizable{
	private int i ;
	private String s;
	public Blip() {}
	public Blip(String x, int a) {
		System.out.println("Blip (String x, int a)");
		s = x;
		i = a;
	}
	public String toString() {
		return s+i;
	}
	@Override
	public void writeExternal(ObjectOutput out) throws IOException {
		// TODO Auto-generated method stub
		System.out.println("Blip.writeExternal");
		out.writeObject(s);
		out.writeInt(i);
//		
	}
	@Override
	public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
		// TODO Auto-generated method stub
		System.out.println("Blip.readExternal");
		s = (String)in.readObject();
		i = in.readInt();
	}
	public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
		System.out.println("Constructing objects");
		Blip b = new Blip("A Stirng", 47);
		System.out.println(b);
		ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("F://Demo//file1.txt"));
		System.out.println("保存對象");
		o.writeObject(b);
		o.close();
		//獲得對象
		System.out.println("獲取對象");
		ObjectInputStream in = new ObjectInputStream(new FileInputStream("F://Demo//file1.txt"));
		System.out.println("Recovering b");
		b = (Blip)in.readObject();
		System.out.println(b);
	}
 
}

打印結果爲:

Constructing objects
Blip (String x, int a)
A Stirng47
保存對象
Blip.writeExternal
獲取對象
Recovering b
Blip.readExternal
A Stirng47


當註釋掉writeExternal和readExternal方法後打印結果爲:

Constructing objects
Blip (String x, int a)
A Stirng47
保存對象
Blip.writeExternal
獲取對象
Recovering b
Blip.readExternal
null0


說明:Externalizable類會調用public的構造函數先初始化對象,在調用所保存的內容將對象還原。假如構造方法不是public則會出現運行時錯誤。

 

序列化方式三:實現Serializable接口+添加writeObject()和readObject()方法。(顯+隱序列化)

如果想將方式一和方式二的優點都用到的話,可以採用方式三, 先實現Serializable接口,並且添加writeObject()和readObject()方法。注意這裏是添加,不是重寫或者覆蓋。但是添加的這兩個方法必須有相應的格式。

  1. 方法必須要被private修飾 —–>才能被調用
  2. 第一行調用默認的defaultRead/WriteObject() —–>隱式序列化非static和transient
  3. 調用read/writeObject()將獲得的值賦給相應的值 —–>顯式序列化
public class SerDemo implements Serializable{
	public transient int age = 23;
	public String name ;
	public SerDemo(){
		System.out.println("默認構造器。。。");
	}
	public SerDemo(String name) {
		this.name = name;
	}
	private  void writeObject(ObjectOutputStream stream) throws IOException {
		stream.defaultWriteObject();
		stream.writeInt(age);
	}
	private void readObject(ObjectInputStream stream) throws ClassNotFoundException, IOException {
		stream.defaultReadObject();
		age = stream.readInt();
	}
	
	public String toString() {
		return "年齡" + age + "  " + name; 
	}
	public static void main(String[] args) throws IOException, ClassNotFoundException {
		SerDemo stu = new SerDemo("Ming");
		ByteArrayOutputStream bout = new ByteArrayOutputStream();
		ObjectOutputStream out = new ObjectOutputStream(bout);
		out.writeObject(stu);
		ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bout.toByteArray()));
		SerDemo stu1 = (SerDemo) in.readObject();
		System.out.println(stu1);
	}
}

打印結果爲:

年齡23  Ming


註釋掉stream.writeInt(age)和age= stream.readInt()後:

年齡0  Ming

 

方式三結合了顯式和隱式序列化,Ming被正常序列化,由於age被trancient修飾,所以需要顯式序列化。

Json序列化

Json序列化一般會使用jackson包,通過ObjectMapper類來進行一些操作,比如將對象轉化爲byte數組或者將json串轉化爲對象。現在的大多數公司都將json作爲服務器端返回的數據格式。比如調用一個服務器接口,通常的請求爲xxx.json?a=xxx&b=xxx的形式。Json序列化示例代碼如下所示

package serialize;
 
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
 
import com.fasterxml.jackson.databind.ObjectMapper;
/**
 * 
 * @author liqqc
 *
 */
public class JsonSerialize {
    public static void main(String[] args) throws IOException {
        new JsonSerialize().start();
    }
 
    public void start() throws IOException {
        User u = new User();
        List<User> friends = new ArrayList<>();
        u.setUserName("張三");
        u.setPassWord("123456");
        u.setUserInfo("張三是一個很牛逼的人");
        u.setFriends(friends);
 
        User f1 = new User();
        f1.setUserName("李四");
        f1.setPassWord("123456");
        f1.setUserInfo("李四是一個很牛逼的人");
 
        User f2 = new User();
        f2.setUserName("王五");
        f2.setPassWord("123456");
        f2.setUserInfo("王五是一個很牛逼的人");
 
        friends.add(f1);
        friends.add(f2);
 
        ObjectMapper mapper = new ObjectMapper();
        Long t1 = System.currentTimeMillis();
        byte[] writeValueAsBytes = null;
        for (int i = 0; i < 10; i++) {
            writeValueAsBytes = mapper.writeValueAsBytes(u);
        }
        System.out.println("json serialize: " + (System.currentTimeMillis() - t1) + "ms; 總大小:" + writeValueAsBytes.length);
        Long t2 = System.currentTimeMillis();
        User user = mapper.readValue(writeValueAsBytes, User.class);
        System.out.println("json deserialize: " + (System.currentTimeMillis() - t2) + "ms; User: " + user);
 
    }
}

運行結果

json serialize: 55ms; 總大小:341
json deserialize: 35ms; User: User [userId=null, userName=張三, passWord=123456, userInfo=張三是一個很牛逼的人, friends=[User [userId=null, userName=李四, passWord=123456, userInfo=李四是一個很牛逼的人, friends=null], User [userId=null, userName=王五, passWord=123456, userInfo=王五是一個很牛逼的人, friends=null]]]

 

FastJson序列化

fastjson 是由阿里巴巴開發的一個性能很好的Java 語言實現的 Json解析器和生成器。特點:速度快,測試表明fastjson具有極快的性能,超越任其他的java json parser。功能強大,完全支持java bean、集合、Map、日期、Enum,支持範型和自省。無依賴,能夠直接運行在Java SE 5.0以上版本
支持Android。使用時候需引入FastJson第三方jar包。FastJson序列化代碼示例如下所示

package serialize;
 
import java.util.ArrayList;
import java.util.List;
 
import com.alibaba.fastjson.JSON;
/**
 * 
 * @author liqqc
 *
 */
public class FastJsonSerialize {
 
    public static void main(String[] args) {
        new FastJsonSerialize().start();
    }
 
    public void start(){
        User u = new User();
        List<User> friends = new ArrayList<>();
        u.setUserName("張三");
        u.setPassWord("123456");
        u.setUserInfo("張三是一個很牛逼的人");
        u.setFriends(friends);
 
        User f1 = new User();
        f1.setUserName("李四");
        f1.setPassWord("123456");
        f1.setUserInfo("李四是一個很牛逼的人");
 
        User f2 = new User();
        f2.setUserName("王五");
        f2.setPassWord("123456");
        f2.setUserInfo("王五是一個很牛逼的人");
 
        friends.add(f1);
        friends.add(f2);
 
        //序列化  
        Long t1 = System.currentTimeMillis();
        String text = null;
        for(int i = 0; i<10; i++) {
            text = JSON.toJSONString(u); 
        }
        System.out.println("fastJson serialize: " +(System.currentTimeMillis() - t1) + "ms; 總大小:" + text.getBytes().length);
        //反序列化  
        Long t2 = System.currentTimeMillis();
        User user = JSON.parseObject(text, User.class);
        System.out.println("fastJson serialize: " + (System.currentTimeMillis() -t2) + "ms; User: " + user);
    }
}

運行結果

fastJson serialize: 284ms; 總大小:269
fastJson serialize: 26ms; User: User [userId=null, userName=張三, passWord=123456, userInfo=張三是一個很牛逼的人, friends=[User [userId=null, userName=李四, passWord=123456, userInfo=李四是一個很牛逼的人, friends=null], User [userId=null, userName=王五, passWord=123456, userInfo=王五是一個很牛逼的人, friends=null]]]

 

4、ProtoBuff序列化

ProtocolBuffer是一種輕便高效的結構化數據存儲格式,可以用於結構化數據序列化。適合做數據存儲或 RPC 數據交換格式。可用於通訊協議、數據存儲等領域的語言無關、平臺無關、可擴展的序列化結構數據格式。

優點:跨語言;序列化後數據佔用空間比JSON小,JSON有一定的格式,在數據量上還有可以壓縮的空間。

缺點:它以二進制的方式存儲,無法直接讀取編輯,除非你有 .proto 定義,否則無法直接讀出 Protobuffer的任何內容。

其與thrift的對比:兩者語法類似,都支持版本向後兼容和向前兼容,thrift側重點是構建跨語言的可伸縮的服務,支持的語言多,同時提供了全套RPC解決方案,可以很方便的直接構建服務,不需要做太多其他的工作。 Protobuffer主要是一種序列化機制,在數據序列化上進行性能比較,Protobuffer相對較好。

ProtoBuff序列化對象可以很大程度上將其壓縮,可以大大減少數據傳輸大小,提高系統性能。對於大量數據的緩存,也可以提高緩存中數據存儲量。原始的ProtoBuff需要自己寫.proto文件,通過編譯器將其轉換爲java文件,顯得比較繁瑣。百度研發的jprotobuf框架將Google原始的protobuf進行了封裝,對其進行簡化,僅提供序列化和反序列化方法。其實用上也比較簡潔,通過對JavaBean中的字段進行註解就行,不需要撰寫.proto文件和實用編譯器將其生成.java文件,百度的jprotobuf都替我們做了這些事情了。

一個帶有jprotobuf註解的JavaBean如下所示

package serialize;
 
import java.io.Serializable;
import java.util.List;
import com.baidu.bjf.remoting.protobuf.FieldType;
import com.baidu.bjf.remoting.protobuf.annotation.Protobuf;
 
public class User implements Serializable {
    private static final long serialVersionUID = -7890663945232864573L;
 
    @Protobuf(fieldType = FieldType.INT32, required = false, order = 1)
    private Integer userId;
 
    @Protobuf(fieldType = FieldType.STRING, required = false, order = 2)
    private String userName;
 
    @Protobuf(fieldType = FieldType.STRING, required = false, order = 3)
    private String passWord;
 
    @Protobuf(fieldType = FieldType.STRING, required = false, order = 4)
    private String userInfo;
 
    @Protobuf(fieldType = FieldType.OBJECT, required = false, order = 5)
    private List<User> friends;
 
    public Integer getUserId() {
        return userId;
    }
 
    public void setUserId(Integer userId) {
        this.userId = userId;
    }
 
    public String getUserName() {
        return userName;
    }
 
    public void setUserName(String userName) {
        this.userName = userName;
    }
 
    public String getPassWord() {
        return passWord;
    }
 
    public void setPassWord(String passWord) {
        this.passWord = passWord;
    }
 
    public String getUserInfo() {
        return userInfo;
    }
 
    public void setUserInfo(String userInfo) {
        this.userInfo = userInfo;
    }
 
    public List<User> getFriends() {
        return friends;
    }
 
    public void setFriends(List<User> friends) {
        this.friends = friends;
    }
 
    @Override
    public String toString() {
        return "User [userId=" + userId + ", userName=" + userName + ", passWord=" + passWord + ", userInfo=" + userInfo
                + ", friends=" + friends + "]";
    }
 
}

jprotobuf序列化代碼示例如下所示

package serialize;
 
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
 
import com.baidu.bjf.remoting.protobuf.Codec;
import com.baidu.bjf.remoting.protobuf.ProtobufProxy;
/**
 * 
 * @author liqqc
 *
 */
public class ProtoBuffSerialize {
 
    public static void main(String[] args) throws IOException {
        new ProtoBuffSerialize().start();
    }
 
    public void start() throws IOException {
        Codec<User> studentClassCodec = ProtobufProxy.create(User.class, false);
 
        User u2 = new User();
        List<User> friends = new ArrayList<>();
        u2.setUserName("張三");
        u2.setPassWord("123456");
        u2.setUserInfo("張三是一個很牛逼的人");
        u2.setFriends(friends);
 
        User f1 = new User();
        f1.setUserName("李四");
        f1.setPassWord("123456");
        f1.setUserInfo("李四是一個很牛逼的人");
 
        User f2 = new User();
        f2.setUserName("王五");
        f2.setPassWord("123456");
        f2.setUserInfo("王五是一個很牛逼的人");
        friends.add(f1);
        friends.add(f2);
 
        Long stime_jpb_encode = System.currentTimeMillis();
        byte[] bytes = null;
        for(int i = 0; i<10; i++) {
            bytes = studentClassCodec.encode(u2);
        }
        System.out.println("jprotobuf序列化耗時:" + (System.currentTimeMillis() - stime_jpb_encode) + "ms; 總大小:" + bytes.length);
 
        Long stime_jpb_decode = System.currentTimeMillis();
        User user = studentClassCodec.decode(bytes);
        Long etime_jpb_decode = System.currentTimeMillis();
        System.out.println("jprotobuf反序列化耗時:"+ (etime_jpb_decode-stime_jpb_decode) + "ms; User: " + user);
    }
 
}

運行結果

jprotobuf序列化耗時:9ms; 總大小:148
jprotobuf反序列化耗時:0ms; User: User [userId=null, userName=張三, passWord=123456, userInfo=張三是一個很牛逼的人, friends=[User [userId=null, userName=李四, passWord=123456, userInfo=李四是一個很牛逼的人, friends=null], User [userId=null, userName=王五, passWord=123456, userInfo=王五是一個很牛逼的人, friends=null]]]

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章