1. 程式人生 > >幾種Java序列化方式的實現

幾種Java序列化方式的實現

0、前言

本文主要對幾種常見Java序列化方式進行實現。包括Java原生以流的方法進行的序列化、Json序列化、FastJson序列化、Protobuff序列化。

1、Java原生序列化

Java原生序列化方法即通過Java原生流(InputStream和OutputStream之間的轉化)的方式進行轉化。需要注意的是JavaBean實體類必須實現Serializable介面,否則無法序列化。Java原生序列化程式碼示例如下所示:

package serialize;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream
; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.ArrayList; import java.util.List; /** * * @author liqqc * */ public class JavaSerialize { public static void main(String[] args) throws ClassNotFoundException, IOException { new JavaSerialize().start
(); } public void start() throws IOException, ClassNotFoundException { 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(); ByteArrayOutputStream out = new ByteArrayOutputStream(); ObjectOutputStream obj = new ObjectOutputStream(out); for(int i = 0; i<10; i++) { obj.writeObject(u); } System.out.println("java serialize: " +(System.currentTimeMillis() - t1) + "ms; 總大小:" + out.toByteArray().length ); Long t2 = System.currentTimeMillis(); ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(new java.io.ByteArrayInputStream(out.toByteArray()))); User user = (User) ois.readObject(); System.out.println("java deserialize: " + (System.currentTimeMillis() - t2) + "ms; User: " + user); } }

執行結果:

java serialize: 8ms; 總大小:420
java deserialize: 1ms; 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]]]

2、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]]]

3、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都替我們做了這些事情了。

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]]]

5、總結

我們通過Main方法來進行對比測試,(但是通過測試發現少量資料無法準確顯示每種序列化方式的優劣,故這裡無法給出比較好的答案,僅供參考)。示例程式碼如下所示:

package serialize;

import java.io.IOException;

/**
 * @author liqqc
 */
public class Main {

    public static void main(String[] args) throws IOException, ClassNotFoundException {

        ProtoBuffSerialize protoBuffSerialize = new ProtoBuffSerialize();
        protoBuffSerialize.start();

        System.err.println();
        System.err.println();

        JavaSerialize javaSerialize = new JavaSerialize();
        javaSerialize.start();
        System.err.println();

        JsonSerialize jsonSerialize = new JsonSerialize();
        jsonSerialize.start();
        System.err.println();

        FastJsonSerialize fastJsonSerialize = new FastJsonSerialize();
        fastJsonSerialize.start();
    }
}

執行結果:

jprotobuf序列化耗時:7ms; 總大小:148
jprotobuf反序列化耗時:0ms

java serialize: 6ms; 總大小:420
java deserialize: 1ms

json serialize: 37ms; 總大小:341
json deserialize: 27ms

fastJson serialize: 173ms; 總大小:269
fastJson serialize: 35ms

上面的測試僅供參考,並不能代表通過大量資料進行測試的結果。可以發現:序列化後物件的所佔大小上:protobuff序列化所佔總大小是最少的;其次是fastJson序列化;最後是json序列化和java原生序列化。對於序列化耗時,上面的測試不準。

本文僅僅簡單介紹了下幾種序列化方式的實現,並未經過大量測試對其進行對比分析,待後續有時間和精力在進行補充。