這是幾年前寫的舊文,此前發布Wordpress小站上,現在又重新整理。算是溫故知新,后續會繼續整理。如有錯誤望及時指出,在此感謝。
需求描述
在跨平台跨網絡的接口訪問中,數據的傳輸往往伴隨着序列化和壓縮。
在實際項目中,產品與產品之間,服務與服務之間,往往采用不同的技術方案,兩者進行數據傳輸也會因為平台的技術方案不同而出現雞同鴨講的情況,數據序列化就是解決這個問題的方案。
序列化的方案很多有JSON,有二進制。
需求目標
- 使用Jdk的Deflater進行數據的對序列化及壓縮;
- 使用Protobuf進行數據的序列化及壓縮;
- 對比兩者的差異;
需求實現
准備了一個應用場景,學校班級里的學生信息進行二進制傳輸保存。
環境准備
JDK1.8
Maven依賴
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.22</version>
</dependency>
<dependency>
<groupId>io.protostuff</groupId>
<artifactId>protostuff-runtime</artifactId>
<version>1.7.4</version>
</dependency>
<dependency>
<groupId>io.protostuff</groupId>
<artifactId>protostuff-core</artifactId>
<version>1.7.4</version>
</dependency>
Deflater方案
import java.io.*;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.Inflater;
public class JavaCompressTest5 {
public static void main(String[] args) {
try {
Class clazzObj = productClass();
System.out.println("class對象信息:" + clazzObj.toString());
byte[] objectBytes = object2Byte(clazzObj);
System.out.println("class對象原始大小:" + objectBytes.length);
//壓縮
byte[] clazz_data = compresser(objectBytes);
System.out.println("JavaCompress序列化后大小:" + clazz_data.length);
//解壓
byte[] decompresser = decompresser(clazz_data);
Class decompressObj = (Class) bytes2Object(decompresser);
System.out.println("decompresser后class對象信息:" + decompressObj.toString());
// class對象信息:Class(id=c_01, name=xiaoban01, students=[Student(id=1, name=zhangsan, age=18), Student(id=2, name=lisi, age=18), Student(id=3, name=wanwu, age=18), Student(id=4, name=zhaoliu, age=18), Student(id=5, name=liuqi, age=18), Student(id=6, name=qianba, age=18), Student(id=6, name=zhoujiu, age=18)])
// class對象原始大小:472
// JavaCompress序列化后大小:320
// decompresser后class對象信息:Class(id=c_01, name=xiaoban01, students=[Student(id=1, name=zhangsan, age=18), Student(id=2, name=lisi, age=18), Student(id=3, name=wanwu, age=18), Student(id=4, name=zhaoliu, age=18), Student(id=5, name=liuqi, age=18), Student(id=6, name=qianba, age=18), Student(id=6, name=zhoujiu, age=18)])
} catch (UnsupportedEncodingException ex) {
// handle
} catch (DataFormatException ex) {
// handle
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 壓縮
*
* @param inputBytes
* @return
*/
public static byte[] compresser(byte[] inputBytes) {
// Compress the bytes
byte[] outputBytes;//存放壓縮后的數據
Deflater compresser = new Deflater(Deflater.DEFAULT_COMPRESSION, false);//jdk自帶的壓縮類Deflater
compresser.setInput(inputBytes);
compresser.finish();
ByteArrayOutputStream baos = new ByteArrayOutputStream(inputBytes.length);
byte[] buf = new byte[100];
try {
while (!compresser.finished()) {
int compressedDataLength = compresser.deflate(buf);//壓縮數據寫入結果數組的字節數
baos.write(buf, 0, compressedDataLength);
}
outputBytes = baos.toByteArray();
} finally {
baos.hashCode();
compresser.end();
}
return outputBytes;
}
/**
* 解壓
*
* @param inputBytes
* @return
*/
public static byte[] decompresser(byte[] inputBytes) throws DataFormatException, IOException {
byte[] outputBytes;//存放解壓后的實際數據的數組
Inflater decompresser = new Inflater(false);//解壓
decompresser.setInput(inputBytes);//從結果數組中,讀取壓縮數據
ByteArrayOutputStream baos = new ByteArrayOutputStream(inputBytes.length);
byte[] buf = new byte[100];
try {
while (!decompresser.finished()) {
//實際解壓后的結果大小
int inflaterLength = decompresser.inflate(buf);
baos.write(buf, 0, inflaterLength);
}
outputBytes = baos.toByteArray();
} finally {
baos.close();
decompresser.end();
}
return outputBytes;
}
}
@Data
@Builder
class Student implements Serializable {
private String id;
private String name;
private int age;
}
@Data
@Builder
class Class implements Serializable {
private String id;
private String name;
private List<Student> students;
}
Protostuff方案
import lombok.Builder;
import lombok.Data;
import java.io.*;
import java.util.Arrays;
import java.util.List;
public class ProtostuffTest1 {
public static void main(String[] args) {
Class clazzObj = productClass();
System.out.println("class對象信息:" + clazzObj.toString());
byte[] objectBytes = object2Byte(clazzObj);
System.out.println("class對象原始大小:" + objectBytes.length);
//使用ProtostuffUtils序列化
byte[] clazz_data = ProtostuffUtils.serialize(clazzObj);
//System.out.println("pb序列化后:" + Arrays.toString(clazz_data));
System.out.println("Protostuff序列化后大小:" + clazz_data.length);
Class result = ProtostuffUtils.deserialize(clazz_data, Class.class);
System.out.println("Protostuff反序列化后class對象信息:" + result.toString());
// class對象信息:Class(id=c_01, name=xiaoban01, students=[Student(id=1, name=zhangsan, age=18), Student(id=2, name=lisi, age=18), Student(id=3, name=wanwu, age=18), Student(id=4, name=zhaoliu, age=18), Student(id=5, name=liuqi, age=18), Student(id=6, name=qianba, age=18), Student(id=6, name=zhoujiu, age=18)])
// class對象原始大小:472
// Protostuff序列化后大小:122
// Protostuff反序列化后class對象信息:Class(id=c_01, name=xiaoban01, students=[Student(id=1, name=zhangsan, age=18), Student(id=2, name=lisi, age=18), Student(id=3, name=wanwu, age=18), Student(id=4, name=zhaoliu, age=18), Student(id=5, name=liuqi, age=18), Student(id=6, name=qianba, age=18), Student(id=6, name=zhoujiu, age=18)])
}
public static Class productClass() {
Student zhangsan = Student.builder().id("1").name("zhangsan").age(18).build();
Student lisi = Student.builder().id("2").name("lisi").age(18).build();
Student wanwu = Student.builder().id("3").name("wanwu").age(18).build();
Student zhaoliu = Student.builder().id("4").name("zhaoliu").age(18).build();
Student liuqi = Student.builder().id("5").name("liuqi").age(18).build();
Student qianba = Student.builder().id("6").name("qianba").age(18).build();
Student zhoujiu = Student.builder().id("6").name("zhoujiu").age(18).build();
Class clazz = Class.builder().id("c_01").name("xiaoban01").students(Arrays.asList(
zhangsan,
lisi,
wanwu,
zhaoliu,
liuqi,
qianba,
zhoujiu
)).build();
return clazz;
}
public static byte[] object2Byte(java.lang.Object obj) {
byte[] bytes = null;
try {
// object to bytearray
ByteArrayOutputStream bo = new ByteArrayOutputStream();
ObjectOutputStream oo = new ObjectOutputStream(bo);
oo.writeObject(obj);
bytes = bo.toByteArray();
bo.close();
oo.close();
} catch (Exception e) {
System.out.println("translation" + e.getMessage());
e.printStackTrace();
}
return bytes;
}
public static Object bytes2Object(byte[] bytes) {
try {
// object to bytearray
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bais);
Object obj = ois.readObject();
bais.close();
ois.close();
return obj;
} catch (Exception e) {
System.out.println("translation" + e.getMessage());
e.printStackTrace();
}
return bytes;
}
}
ProtostuffUtils工具類
import io.protostuff.LinkedBuffer;
import io.protostuff.ProtostuffIOUtil;
import io.protostuff.Schema;
import io.protostuff.runtime.RuntimeSchema;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
public class ProtostuffUtils {
/**
* 避免每次序列化都重新申請Buffer空間
*/
private static LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
/**
* 緩存Schema
*/
private static Map<Class<?>, Schema<?>> schemaCache = new ConcurrentHashMap<Class<?>, Schema<?>>();
/**
* 序列化方法,把指定對象序列化成字節數組
*
* @param obj
* @param <T>
* @return
*/
@SuppressWarnings("unchecked")
public static <T> byte[] serialize(T obj) {
Class<T> clazz = (Class<T>) obj.getClass();
Schema<T> schema = getSchema(clazz);
byte[] data;
try {
data = ProtostuffIOUtil.toByteArray(obj, schema, buffer);
} finally {
buffer.clear();
}
return data;
}
/**
* 反序列化方法,將字節數組反序列化成指定Class類型
*
* @param data
* @param clazz
* @param <T>
* @return
*/
public static <T> T deserialize(byte[] data, Class<T> clazz) {
Schema<T> schema = getSchema(clazz);
T obj = schema.newMessage();
ProtostuffIOUtil.mergeFrom(data, obj, schema);
return obj;
}
@SuppressWarnings("unchecked")
private static <T> Schema<T> getSchema(Class<T> clazz) {
Schema<T> schema = (Schema<T>) schemaCache.get(clazz);
if (Objects.isNull(schema)) {
//這個schema通過RuntimeSchema進行懶創建並緩存
//所以可以一直調用RuntimeSchema.getSchema(),這個方法是線程安全的
schema = RuntimeSchema.getSchema(clazz);
if (Objects.nonNull(schema)) {
schemaCache.put(clazz, schema);
}
}
return schema;
}
}
結論
首先這里沒有對序列化本身所花費的時長進行統計,有興趣的同學可以自行統計。
單從結果來看,Protostuff序列化后的大小是Jdk的Deflater的2.6倍,如果數據樣本再多些,可能還會更高。
自此,我們使用Protostuff幫我們完成了數據的序列化和壓縮兩個問題。
希望對有同樣需求的同學有幫助。