jackson、fastjson、kryo、protostuff等序列化工具效能對比

子月生發表於2020-11-09

簡介

實際專案中,我們經常需要使用序列化工具來儲存和傳輸物件。目前用得比較多的序列化工具有:jackson、fastjson、kryo、protostuff、fst 等,本文將簡單對比這幾款工具序列化和反序列化的效能。

專案環境

本文使用 jmh 作為測試工具。

os:win 10

jdk:1.8.0_231

jmh:1.25

選擇的序列化工具及對應的版本如下:

fastjson:1.2.74

jackson:2.11.3

kryo:5.0.0

fst:2.57

protostuff:1.7.2

測試程式碼

為了公平,我儘量讓測試用例中對序列化工具的用法更貼近實際專案,例如,kryo 的Kryo物件不是執行緒安全的,實際專案中我們並不會每次使用就直接 new 一個新物件,而是使用 ThreadLocal 或者池來減少建立物件的開銷。

本文使用的 java bean 如下。一個使用者物件,一對一關聯部門物件和崗位物件,其中部門物件又存在自關聯。

public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    // 普通屬性--29個
    private String id;
    private String account;
    private String password;
    private Integer status;
    // ······
    
    /**
     * 所屬部門
     */
    private Department department;
    /**
     * 崗位
     */
    private Position position;
    
    // 以下省略setter/getter方法
}
public class Department implements Serializable {
    private static final long serialVersionUID = 1L;
    // 普通屬性--7個
    private String id;
    private String parentId;
    // ······
    /**
     * 子部門
     */
    private List<Department> children;
    
    // 以下省略setter/getter方法
}
public class Position implements Serializable {
    private static final long serialVersionUID = 1L;
    // 普通屬性--6個
    private String id;
    private String name;
    // ······
    // 以下省略setter/getter方法
}

下面展示部分測試程式碼,完整程式碼見末尾連結。

JDK 自帶的序列化工具

JDK 提供了ObjectOutputStream用於支援序列化,ObjectInputStream用於反序列化。注意,使用 JDK 自帶的序列化工具時,java bean 必須實現Serializable,否則會丟擲NotSerializableException異常 。使用關鍵字 transient 修飾的成員屬性不會被序列化。

    // 序列化
    @Benchmark
    public byte[] jdkSerialize(CommonState commonState) throws Exception {
        ByteArrayOutputStream byteArray = new ByteArrayOutputStream();
        ObjectOutputStream outputStream = new ObjectOutputStream(byteArray);
        outputStream.writeObject(commonState.user);
        outputStream.flush();
        outputStream.close();
        return byteArray.toByteArray();
    }
    // 反序列化
    @Benchmark
    public User jdkDeSerialize(JdkState jdkState) throws Exception {
        ByteArrayInputStream byteArray = new ByteArrayInputStream(jdkState.bytes);
        ObjectInputStream inputStream = new ObjectInputStream(byteArray);
        User user = (User)inputStream.readObject();
        inputStream.close();
        assert "zzs0".equals(user.getName());
        return user;
    }

fastjson

fastjson 由阿里團隊開發,是目前最快的Java 實現的 json 庫。 fastjson 的 API 非常簡潔,並且支援一定程度的定製(例如,註解 @JSONField、列舉Feature等定製序列化)。被人詬病的,可能是 fastjson 的 bug 比較多。

    // 序列化
    @Benchmark
    public byte[] fastJsonSerialize(CommonState commonState) {
        return JSON.toJSONBytes(commonState.user);
    }
    // 反序列化
    @Benchmark
    public User fastJsonDeSerialize(FastJsonState fastJsonState) {
        User user = JSON.parseObject(fastJsonState.bytes, User.class);
        assert "zzs0".equals(user.getName());
        return user;
    }

jackson

jackson 由 fasterxml 組織開發,相比 fastjson,有著更強大的功能、更高的穩定性、更好的擴充套件性、更豐富的定製支援。Spring 預設使用的 json 解析工具就是 jackson。

使用 jackson 需要注意,ObjectMapper物件是執行緒安全的,可以重複使用

    // 序列化
    @Benchmark
    public byte[] jacksonSerialize(CommonState commonState, JacksonState jacksonState) throws Exception {
        return jacksonState.objectMapper.writeValueAsBytes(commonState.user);
    }
    // 反序列化
    @Benchmark
    public User jacksonDeSerialize(JacksonState jacksonState) throws Exception {
        User user = jacksonState.objectMapper.readValue(jacksonState.bytes, User.class);
        assert "zzs0".equals(user.getName());
        return user;
    }

kryo

kryo 由 EsotericSoftware 組織開發,不相容 jdk 自帶序列化工具的資料,kryo 序列化後的資料要更小,至於 API 的簡潔性方面,我覺得還是差了一些,一不小心就會採坑。使用 kryo 需要注意以下幾點:

  1. Kryo物件不是執行緒安全的,可以使用ThreadLocal或池來獲取(本文使用池獲取);
  2. kryo 通過類註冊可以在序列化資料中寫入類的 class id,而不是類的全限定類名,從而減小序列化資料的大小。但是,我們很難保證同樣的類在不同的機器上註冊的 class id,所以,建議設定kryo.setRegistrationRequired(false);,因為同樣的 Class 在不同的機器上註冊編號很難保證一致;
  3. 當 java bean 出現迴圈引用時,使用 kryo 可能會出現棧記憶體溢位,這個時候可以通過設定kryo.setReferences(true);來避免。如果專案中不可能出現迴圈引用,則可以設定為 false 以提高效能。
	// 序列化
    @Benchmark
    public byte[] kryoSerialize(CommonState commonState, KryoState kryoState) {
        ByteArrayOutputStream byteArray = new ByteArrayOutputStream();
        Output output = new Output(byteArray);
        Kryo kryo = kryoState.kryoPool.obtain();
        kryo.writeClassAndObject(output, commonState.user);
        kryoState.kryoPool.free(kryo);
        output.flush();
        output.close();
        return byteArray.toByteArray();
    }
	//反序列化
    @Benchmark
    public User kryoDeSerialize(KryoState kryoState) throws Exception {
        ByteArrayInputStream byteArray = new ByteArrayInputStream(kryoState.bytes);
        Input input = new Input(byteArray);
        Kryo kryo = kryoState.kryoPool.obtain();
        User user = (User)kryo.readClassAndObject(input);
        kryoState.kryoPool.free(kryo);
        input.close();
        assert "zzs0".equals(user.getName());
        return user;
    }

fst

fst(fast-serialization)是由 RuedigerMoeller 開發,API 非常簡潔。使用時需要注意,FSTConfiguration物件可以重複使用

其實,fst 也支援以 json 形式序列化,但是這一塊的效能稍差而且用的人較少,這裡就不提及了。

	// 序列化
    @Benchmark
    public byte[] fstSerialize(CommonState commonState, FSTConfigurationState fSTConfigurationState) {
        return fSTConfigurationState.fSTConfiguration.asByteArray(commonState.user);
    }
	// 反序列化
    @Benchmark
    public User fstDeSerialize(FSTState fstState) throws Exception {
        User user = (User)fstState.fSTConfiguration.asObject(fstState.bytes);
        assert "zzs0".equals(user.getName());
        return user;
    }

protostuff

protostuff 是基於 google protobuf 開發而來(與 protobuf 相比,protostuff 在幾乎不損耗效能的情況下做到了不用寫.proto檔案來實現序列化),不相容 jdk 自帶序列化工具的資料,序列化後的資料要更小。使用 protostuff 需要注意幾點:

  1. protostuff 使用欄位的定義順序作為欄位的 tag,新增欄位時必須保證原欄位順序不變,否則舊資料可能會反序列化失敗;
  2. protostuff 不能直接序列化 Array、List、Map,如果需要序列化,需要先包裝成 java bean;
	// 序列化
    @Benchmark
    public byte[] protostuffSerialize(CommonState commonStateme) {
        Schema<User> schema = (Schema<User>)RuntimeSchema.getSchema(User.class);
        return ProtostuffIOUtil.toByteArray(commonStateme.user, schema, LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE));
    }
	// 反序列化
    @Benchmark
    public User protostuffDeSerialize(ProtostuffState protostuffState) throws Exception {
        User user = new User();
        Schema<User> schema = (Schema<User>)RuntimeSchema.getSchema(User.class);
        ProtostuffIOUtil.mergeFrom(protostuffState.bytes, user, schema);
        assert "zzs0".equals(user.getName());
        return user;
    }

測試結果

以下以吞吐量作為指標,相同條件下,吞吐量越大越好。

序列化

cmd 指令如下:

mvn clean package
java -ea -jar target/benchmarks.jar cn.zzs.serialize.SerializeTest -f 1 -t 1 -wi 10 -i 10

測試結果:

# JMH version: 1.25
# VM version: JDK 1.8.0_231, Java HotSpot(TM) 64-Bit Server VM, 25.231-b11
# VM invoker: D:\growUp\installation\jdk1.8.0_231\jre\bin\java.exe
# VM options: -ea
# Warmup: 10 iterations, 10 s each
# Measurement: 10 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
Benchmark                           Mode  Cnt    Score    Error   Units
SerializeTest.fastJsonSerialize    thrpt   10  449.474 ±  1.851  ops/ms
SerializeTest.fstSerialize         thrpt   10  694.716 ±  7.240  ops/ms
SerializeTest.jacksonSerialize     thrpt   10  330.610 ±  6.968  ops/ms
SerializeTest.jdkSerialize         thrpt   10  132.483 ±  0.379  ops/ms
SerializeTest.kryoSerialize        thrpt   10  609.969 ±  3.288  ops/ms
SerializeTest.protostuffSerialize  thrpt   10  762.737 ± 10.918  ops/ms

可以看到,序列化速度方面:protostuff > fst > kryo > fastjson > jackson > jdk

反序列化

cmd 指令如下:

mvn clean package
java -ea -jar target/benchmarks.jar cn.zzs.serialize.DeSerializeTest -f 1 -t 1 -wi 10 -i 10

測試結果:

# JMH version: 1.25
# VM version: JDK 1.8.0_231, Java HotSpot(TM) 64-Bit Server VM, 25.231-b11
# VM invoker: D:\growUp\installation\jdk1.8.0_231\jre\bin\java.exe
# VM options: -ea
# Warmup: 10 iterations, 10 s each
# Measurement: 10 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time

fastjson serialized data size:1044
fst serialized data size:607
jackson serialized data size:1060
jdk serialized data size:1700
kryo serialized data size:597
protostuff serialized data size:543

Benchmark                               Mode  Cnt    Score   Error   Units
DeSerializeTest.fastJsonDeSerialize    thrpt   10  397.069 ± 3.507  ops/ms
DeSerializeTest.fstDeSerialize         thrpt   10  465.813 ± 4.700  ops/ms
DeSerializeTest.jacksonDeSerialize     thrpt   10  226.412 ± 1.436  ops/ms
DeSerializeTest.jdkDeSerialize         thrpt   10   27.919 ± 0.352  ops/ms
DeSerializeTest.kryoDeSerialize        thrpt   10  448.978 ± 3.504  ops/ms
DeSerializeTest.protostuffDeSerialize  thrpt   10  499.328 ± 4.485  ops/ms

可以看到,反序列化速度方面:protostuff > fst > kryo > fastjson > jackson > jdk,該結果和序列化一致。

序列化資料的大小方面:protostuff < kryo < fst < fastjson < jackson < jdk

以上資料僅供參考。感謝閱讀。

相關原始碼請移步: serialize-tool-demo

本文為原創文章,轉載請附上原文出處連結:https://www.cnblogs.com/ZhangZiSheng001/p/13948414.html

相關文章