1. 程式人生 > >淺析若干Java序列化工具

淺析若干Java序列化工具

  在Java中socket傳輸資料時,資料型別往往比較難選擇。可能要考慮頻寬、跨語言、版本的相容等問題。比較常見的做法有:

  1. 採用java物件的序列化和反序列化
  2. 把物件包裝成JSON字串傳輸
  3. Google工具protoBuf的開源

  為了便於說明各個做法的區別,分別對這三種做法進行闡述。 對UserVo物件進行序列化,class UserVo如下:

package serialize;
import java.util.List;

public classUserVo
{
    private String name;
    private int age;
    private
List<UserVo> friends; //此處省略Getter和Setter方法 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

  初始化一個UserVo例項:

UserVo user = new UserVo();
user.setName("zzh");
user.setAge(18);

UserVo f1 = new UserVo();
f1.setName("jj");
f1.setAge(17);
UserVo f2 = new UserVo();
f2.setName("qq");
f2.setAge(19);

List<UserVo> friends = new ArrayList<UserVo>();
friends.add(f1); friends.add(f2); user.setFriends(friends);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

採用java物件的序列化和反序列化

        ByteArrayOutputStream os = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(os);
        oos.writeObject(user);
        oos.flush
(); oos.close(); System.out.println(os.toByteArray().length);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

  序列化大小:205.
  優點:java原生支援,不需要提供第三方的類庫,使用比較簡單。缺點:無法跨語言,位元組數佔用比較大,某些情況下對於物件屬性的變化比較敏感。

把物件包裝成JSON字串傳輸

  JSON工具類有許多種,這裡列出三個比較流行的json工具類:Jackson,Gson,FastJson.

1.開源的Jackson

  Jackson社群相對比較活躍,更新速度也比較快。Jackson對於複雜型別的json轉換bean會出現問題,一些集合Map,List的轉換出現問題。Jackson對於複雜型別的bean轉換Json,轉換的json格式不是標準的Json格式。

package serialize.json;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.codehaus.jackson.JsonEncoding;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.map.ObjectMapper;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import serialize.UserVo;

public classJacksonTest
{
    private UserVo user = null;
    private JsonGenerator jsonGenerator = null;
    private ObjectMapper objectMapper = null;

    @Before
    public void init()
    {
        user = new UserVo();
        user.setName("zzh");
        user.setAge(18);

        UserVo f1 = new UserVo();
        f1.setName("jj");
        f1.setAge(17);
        UserVo f2 = new UserVo();
        f2.setName("qq");
        f2.setAge(19);

        List<UserVo> friends = new ArrayList<UserVo>();
        friends.add(f1);
        friends.add(f2);
        user.setFriends(friends);

        objectMapper = new ObjectMapper();
        try
        {
            jsonGenerator = objectMapper.getJsonFactory().createJsonGenerator(System.out,JsonEncoding.UTF8);
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
    }

    @After
    public void destory()
    {
        try
        {
            if(jsonGenerator != null)
            {
                jsonGenerator.flush();
            }
            if(!jsonGenerator.isClosed())
            {
                jsonGenerator.close();
            }
            jsonGenerator = null;
            objectMapper = null;
            user = null;
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
    }

    @Test
    public void writeJson()
    {
        try
        {
            jsonGenerator.writeObject(user);
            System.out.println();
            System.out.println(objectMapper.writeValueAsBytes(user).length);
           // System.out.println(objectMapper.writeValueAsString(user).length());
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
    }


    @Test 
    public void readJson()
    {
        String serString = "{\"name\":\"zzh\",\"age\":18,\"friends\":[{\"name\":\"jj\",\"age\":17,\"friends\":null},{\"name\":\"qq\",\"age\":19,\"friends\":null}]}";
        UserVo uservo = null;
        try
        {
            uservo = objectMapper.readValue(serString, UserVo.class);
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        System.out.println(uservo.getName());
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108

  序列化大小:111.
  注意到這裡Jackson會輸出null,在Jackson的2.x版本中可以通過設定而使其不輸出null的欄位。

2. Google的Gson

  Gson是目前功能最全的Json解析神器,Gson當初是為因應Google公司內部需求而由Google自行研發而來,但自從在2008年五月公開發布第一版後已被許多公司或使用者應用。Gson的應用主要為toJson與fromJson兩個轉換函式,無依賴,不需要例外額外的jar,能夠直接跑在JDK上。而在使用這種物件轉換之前需先建立好物件的型別以及其成員才能成功的將JSON字串成功轉換成相對應的物件。類裡面只要有get和set方法,Gson完全可以將複雜型別的json到bean或bean到json的轉換,是JSON解析的神器。Gson在功能上面無可挑剔,但是效能上面比FastJson有所差距。

package serialize.json;

import static org.junit.Assert.*;

import java.util.ArrayList;
import java.util.List;

import org.junit.Before;
import org.junit.Test;

import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;

import serialize.UserVo;

public class GsonTest
{
    private UserVo user = null;

    @Before
    public void init()
    {
        user = new UserVo();
        user.setName("zzh");
        user.setAge(18);

        UserVo f1 = new UserVo();
        f1.setName("jj");
        f1.setAge(17);
        UserVo f2 = new UserVo();
        f2.setName("qq");
        f2.setAge(19);

        List<UserVo> friends = new ArrayList<UserVo>();
        friends.add(f1);
        friends.add(f2);
        user.setFriends(friends);
    }

    @Test
    public void writeJson()
    {
        try
        {
            String str = Gson.class.newInstance().toJson(user);//一行就可以搞定!!!
            System.out.println(str);
            System.out.println(str.length());
        }
        catch (InstantiationException | IllegalAccessException e)
        {
            e.printStackTrace();
        }
    }

    @Test public void readJson()
    {
        String serString = "{\"name\":\"zzh\",\"age\":18,\"friends\":[{\"name\":\"jj\",\"age\":17},{\"name\":\"qq\",\"age\":19}]}";
        try
        {
            UserVo userVo = Gson.class.newInstance().fromJson(serString, UserVo.class);
            System.out.println(userVo.getName());
        }
        catch (JsonSyntaxException | InstantiationException | IllegalAccessException e)
        {
            e.printStackTrace();
        }
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70

  序列化大小:81.

Gson和Jackson的區別是:如果你的應用經常會處理大的JSON檔案,那麼Jackson應該是你的菜。GSON在大檔案上表現得相當吃力。如果你主要是處理小檔案請求,比如某個微服務或者分散式架構的初始化,那麼GSON當是首選。Jackson在小檔案上的表現則不如人意。

3. 阿里巴巴的FastJson

  Fastjson是一個Java語言編寫的高效能的JSON處理器,由阿里巴巴公司開發。無依賴,不需要例外額外的jar,能夠直接跑在JDK上。
FastJson在複雜型別的Bean轉換Json上會出現一些問題,可能會出現引用的型別,導致Json轉換出錯,需要制定引用。FastJson採用獨創的演算法,將parse的速度提升到極致,超過所有json庫。

 package serialize.json;

import static org.junit.Assert.*;

import java.util.ArrayList;
import java.util.List;

import org.junit.Before;
import org.junit.Test;

import com.alibaba.fastjson.JSON;

import serialize.UserVo;

public class FastJsonTest
{
    private UserVo user = null;

    @Before
    public void init()
    {
        user = new UserVo();
        user.setName("zzh");
        user.setAge(18);

        UserVo f1 = new UserVo();
        f1.setName("jj");
        f1.setAge(17);
        UserVo f2 = new UserVo();
        f2.setName("qq");
        f2.setAge(19);

        List<UserVo> friends = new ArrayList<UserVo>();
        friends.add(f1);
        friends.add(f2);
        user.setFriends(friends);
    }

    @Test public void writeJson()
    {
        String str = JSON.toJSONString(user);
        System.out.println(str);
        System.out.println(str.length());
    }

    @Test public void readJson()
    {
        String serString = "{\"name\":\"zzh\",\"age\":18,\"friends\":[{\"name\":\"jj\",\"age\":17},{\"name\":\"qq\",\"age\":19}]}";
        UserVo userVo = JSON.parseObject(serString,UserVo.class);
        System.out.println(userVo.getName());
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

注:如果只是功能要求,沒有效能要求,可以使用google的Gson,如果有效能上面的要求可以使用Gson將bean轉換json確保資料的正確,使用FastJson將Json轉換Bean。

Google工具protoBuf

  protocol buffers 是google內部得一種傳輸協議,目前專案已經開源。它定義了一種緊湊得可擴充套件得二進位制協議格式,適合網路傳輸,並且針對多個語言有不同得版本可供選擇。
  protoBuf優點:1.效能好,效率高;2.程式碼生成機制,資料解析類自動生成;3.支援向前相容和向後相容;4.支援多種程式語言;5.位元組數很小,適合網路傳輸節省io。缺點:1.應用不夠廣;2.二進位制格式導致可讀性差;3.缺乏自描述;
  protoBuf是需要編譯工具的,這裡用的是window的系統。需要下載proto.exe和protobuf-java-2.4.1.jar;
  將proto.exe放在當前工程目錄下,然後編輯.proto檔案,命名為UserVo.proto,如下所示:

package serialize.protobuf;

option java_package = "serialize.protobuf";
option java_outer_classname="UserVoProtos";

message UserVo
{
    optional string name = 1;
    optional int32 age = 2;
    repeated serialize.protobuf.UserVo friends = 3;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

  在命令列中利用protoc工具生成builder類:
這裡寫圖片描述
  看到生成了UserVoProtos.java,由於這個java檔案有1千行左右,篇幅限制不便羅列。
  序列化和反序列化測試程式碼:

package serialize.protobuf;

import org.junit.Before;
import org.junit.Test;

import com.google.protobuf.InvalidProtocolBufferException;

public class ProtoBufTest
{
    UserVoProtos.UserVo.Builder user = null;

    @Before public void init()
    {
        user = UserVoProtos.UserVo.newBuilder();
        user.setName("zzh");
        user.setAge(18);

        UserVoProtos.UserVo.Builder f1 = UserVoProtos.UserVo.newBuilder();
        f1.setName("jj");
        f1.setAge(17);

        UserVoProtos.UserVo.Builder f2 = UserVoProtos.UserVo.newBuilder();
        f2.setName("qq");
        f2.setAge(19);

        user.addFriends(f1);
        user.addFriends(f2);
    }

    @Test public void doSeri()
    {
        UserVoProtos.UserVo vo = user.build();
        byte[] v = vo.toByteArray();
        for(byte b:v)
        {
            System.out.printf("%02X ",b);
        }
        System.out.println();
        System.out.println(v.length);
    }

    @Test public void doDeSeri()
    {
        byte[] v = new byte[]{0x0A, 0x03, 0x7A, 0x7A, 0x68, 0x10, 0x12, 0x1A, 0x06, 0x0A, 0x02, 0x6A, 0x6A, 0x10, 0x11, 0x1A, 0x06, 0x0A, 0x02, 0x71, 0x71, 0x10, 0x13};
        try
        {
            UserVoProtos.UserVo uvo = UserVoProtos.UserVo.parseFrom(v);
            System.out.println(uvo.getName());
        }
        catch (InvalidProtocolBufferException e)
        {
            e.printStackTrace();
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55

  序列化大小:23.
  工作機制:proto檔案是對資料的一個描述,包括欄位名稱,型別,位元組中的位置。protoc工具讀取proto檔案生成對應builder程式碼的類庫。protoc xxxxx –java_out=xxxxxx 生成java類庫。builder類根據自己的演算法把資料序列化成位元組流,或者把位元組流根據反射的原理反序列化成物件。官方的示例:https://developers.google.com/protocol-buffers/docs/javatutorial。proto檔案中的欄位型別和java中的對應關係:詳見:https://developers.google.com/protocol-buffers/docs/proto.

.proto Type java Type c++ Type
double double double
float float float
int32 int int32
int64 long int64
uint32 int uint32
unint64 long uint64
sint32 int int32
sint64 long int64
fixed32 int uint32
fixed64 long uint64
sfixed32 int int32
sfixed64 long int64
bool boolean bool
string String string
bytes byte string

注:protobuf的一個缺點是需要資料結構的預編譯過程,首先要編寫.proto格式的配置檔案,再通過protobuf提供的工具生成各種語言響應的程式碼。由於java具有反射和動態程式碼生成的能力,這個預編譯過程不是必須的,可以在程式碼執行時來實現。有個protostuff(http://code.google.com/p/protostuff/)已經實現了這個功能。protostuff基於Google protobuf,但是提供了更多的功能和更簡易的用法。其中,protostuff-runtime實現了無需預編譯對Java bean進行protobuf序列化/反序列化的能力。protostuff-runtime的侷限是序列化前需預先傳入schema,反序列化不負責物件的建立只負責複製,因而必須提供預設建構函式。此外,protostuff還可以按照protobuf的配置序列化成json/yaml/xml等格式。這裡不做詳述,有興趣的朋友可以參考相關資料。

總結:

方式 優點 缺點
JSON 跨語言、格式清晰一目瞭然 位元組數比較大,需要第三方類庫
Object Serialize java原生方法不依賴外部類庫 位元組數大,不能跨語言
Google protobuf 跨語言、位元組數比較少 編寫.proto配置用protoc工具生成對應的程式碼