千家信息网

何为序列化

发表于:2025-01-16 作者:千家信息网编辑
千家信息网最后更新 2025年01月16日,这篇文章主要讲解了"何为序列化",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"何为序列化"吧!何为序列化 / 反序列化序列化序列化就是一种处理对象流的机
千家信息网最后更新 2025年01月16日何为序列化

这篇文章主要讲解了"何为序列化",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"何为序列化"吧!

何为序列化 / 反序列化

序列化

序列化就是一种处理对象流的机制。

即将对象的内容流化,将数据转化成字节流,以便存储在文件中或用于在网络中传输,当然用的最多的肯定就是网络传输

反序列化

文件中或网络上获得序列化后的对象字节流后,根据字节流中所保存的对象状态及描述信息,通过反序列化重建对象

大家所常见的序列化/反序列化的方式:Serializable

谁敢说没见过Serializable,给我拖出的吊树上打

JDK提供的Serializable速度较慢,原因比如:因为加入了序列化版本号,类名等信息,所以导致码流变大,速度变慢等等。

所以我们要来学一些速度快的序列化方式。不仅速度快,占用的空间还小。

这么好的技术,那个妹子没学到,真是可惜了。

序列化选型标准

  • 通用性:是否只能用于java间序列化/反序列化,是否跨语言、跨平台

  • 性能:分为空间开销和时间开销,序列化后的数据一般用于存储或网络传输,其大小是很重要的一个参数;

当然解析的时间也影响了序列化协议的选择

  • 易用性:API使用是否复杂,是否影响开发效率

  • 可扩展性:实体类的属性变更会不会导致反序列化异常,这通常会在系统升级时会产生,参考性不是很大

Kryo序列化入门

Kryo 是一个快速序列化/反序列化工具,其使用了字节码生成机制(底层依赖了 ASM 库),因此具有比较好的运行速度。

Kryo 序列化出来的结果,是其自定义的、独有的一种格式,不再是 JSON 或者其他现有的通用格式;

而且,其序列化出来的结果是二进制的(即 byte[];而 JSON 本质上是字符串 String);

二进制数据显然体积更小,序列化、反序列化时的速度也更快。

Kryo 一般只用来进行序列化(然后作为缓存,或者落地到存储设备之中)、反序列化,而不用于在多个系统、甚至多种语言间进行数据交换 -- 目前 kryo 也只有 java 实现。

像Redis这样的存储工具,是可以安全的存储二进制数据,所以一般项目中可使用Kryo来替代JDK序列化进行存储。

使用场景:(数据交换或数据持久化)比如使用kryo把对象序列化成字节数组发送给消息队列或者放到redis等nosql中等等应用场景。

pom配置

com.esotericsoftware
kryo
4.0.1

需要注意的是,由于kryo使用了较高版本的asm,可能会与业务现有依赖的asm产生冲突,这是一个比较常见的问题。只需将依赖改成:


com.esotericsoftware
kryo-shaded
4.0.2

代码实现

注意:由于kryo不是线程安全的,针对多线程情况下的使用,要对kryo进行一个简单的封装设计,从而可以多线程安全的使用序列化和反序列化

接口
/**
* 序列化工具(程序调用该接口来实现obj<->byte[]之间的序列化/反序列化)
*/
public interface Serializer{

/**
* 序列化
*/
public void serialize(Object t,byte[] bytes);

/**
* 序列化
*/
public void serialize(Object obj, byte[] bytes, int offset, int count);

/**
* 反序列化
*/
public T deserialize(byte[] bytes);

/**
* 反序列化
*/
public T deserialize(byte[] bytes, int offset, int count);
}
实现类
/**
* 基于kyro的序列化/反序列化工具
*/
public class kryoSerializer implements Serializer {

// 由于kryo不是线程安全的,所以每个线程都使用独立的kryo
final ThreadLocal kryoLocal = new ThreadLocal() {
@Override
protected Kryo initialValue() {
Kryo kryo = new Kryo();
kryo.register(ct, new BeanSerializer<>(kryo, ct));
return kryo;
}
};

// 序列化threadlocal
final ThreadLocal outputLocal = new ThreadLocal();

// 反序列化threadlocal
final ThreadLocal inputLocal = new ThreadLocal();

private Class ct = null;

public kryoSerializer(Class ct) {
this.ct = ct;
}

/**
* 序列化
*/
@Override
public void serialize(Object obj, byte[] bytes) {
Kryo kryo = getKryo();
Output output = getOutput(bytes);
kryo.writeObjectOrNull(output, obj, obj.getClass());
output.flush();
}

/**
* 序列化
*/
@Override
public void serialize(Object obj, byte[] bytes, int offset, int count) {
Kryo kryo = getKryo();
Output output = getOutput(bytes, offset, count);
kryo.writeObjectOrNull(output, obj, obj.getClass());
output.flush();
}

/**
* 反序列化
*/
@SuppressWarnings("unchecked")
@Override
public T deserialize(byte[] bytes, int offset, int count) {
Kryo kryo = getKryo();
Input input = getInput(bytes, offset, count);
return (T) kryo.readObjectOrNull(input, ct);
}

/**
* 反序列化
*/
@Override
public T deserialize(byte[] bytes) {
return deserialize(bytes, 0, bytes.length);
}

/**
* 获取kryo
*/
private Kryo getKryo() {
return kryoLocal.get();
}

/**
* 获取Output并设置初始数组
*/
private Output getOutput(byte[] bytes) {
Output output = null;
if ((output = outputLocal.get()) == null) {
output = new Output();
outputLocal.set(output);
}
if (bytes != null) {
output.setBuffer(bytes);
}
return output;
}

/**
* 获取Output
*/
private Output getOutput(byte[] bytes, int offset, int count) {
Output output = null;
if ((output = outputLocal.get()) == null) {
output = new Output();
outputLocal.set(output);
}
if (bytes != null) {
output.writeBytes(bytes, offset, count);
}
return output;
}

/**
* 获取Input
*/
private Input getInput(byte[] bytes, int offset, int count) {
Input input = null;
if ((input = inputLocal.get()) == null) {
input = new Input();
inputLocal.set(input);
}
if (bytes != null) {
input.setBuffer(bytes, offset, count);
}
return input;
}

public Class getCt() {
return ct;
}

public void setCt(Class ct) {
this.ct = ct;
}

Kryo的IO

Kryo致力以简单易用的API,序列化过程中主要核心有KryoOutputInput

Output和Input是Kryo的IO,他们支持以byte array或者stream的形式为序列化的dest和反序列化的source。

当使用stream形式进行写出写入时,需要close这些Output和Input。

写出时,当OutputDe buffer是满的时候,就会flush bytes到stream中。

写入时,会从stream中获取bytes到Input buffer中,当填充满时,进行反序列化。

Kryo的注册

和很多其他的序列化框架一样,Kryo为了提供性能和减小序列化结果体积,提供注册的序列化对象类的方式。

在注册时,会为该序列化类生成int ID,后续在序列化时使用int ID唯一标识该类型。

注册的方式如下:

kryo.register(SomeClass.class);

或者可以明确指定注册类的int ID,但是该ID必须大于等于0。如果不提供,内部将会使用int++的方式维护一个有序的int ID生成。

kryo.register(SomeClass.class, 1);

对象引用方面

这是对循环引用的支持,可以有效防止栈内存溢出,kryo默认会打开这个属性。

当你确定不会有循环引用发生的时候,可以通过以下代码关闭循环引用检测,从而提高一些性能,但不是很推荐

Kryo kryo = new Kryo();
kryo.setReferences(false);

Kryo的读和写的方式

  • 如果序列化的对象类型未知并且可能为空:

    kryo.writeClassAndObject(output, object);
    // ...
    Object object = kryo.readClassAndObject(input);
    if (object instanceof SomeClass) {
    // ...
    }
  • 如果对象类型已知并且可能为空:

    kryo.writeObjectOrNull(output, someObject);
    // ...
    SomeClass someObject = kryo.readObjectOrNull(input, SomeClass.class);
  • 如果对象类型已知并且不可能为空:

    kryo.writeObject(output, someObject);
    // ...
    SomeClass someObject = kryo.readObject(input, SomeClass.class);

序列化线程安全

Kryo默认是线程不安全的,有两种解决方式:

  • 一个是通过Threadlocal的方式为某个线程存储一个实例:

    private static final ThreadLocal kryoThreadLocal = new ThreadLocal() {
    protected Kryo initialValue() {
    Kryo kryo = new Kryo();
    // 这里可以增加一系列配置信息
    return kryo;
    }
    };
  • 另外一种是通过KryoPool的方式,该方式在性能上也好于ThreadLocal:

    public KryoPool createPool() {
    return new KryoPool.Builder(() -> {
    Kryo kryo = new Kryo();
    // 此处也可以进行一系列配置,可通过实现KryoFactory接口来满足动态注册,抽象该类
    return kryo;
    }).softReferences().build();
    }

kryo支持序列化的类型

booleanBooleanbyteBytechar
CharactershortShortintInteger
longLongfloatFloatdouble
Doublebyte[]StringBigIntegerBigDecimal
CollectionDateCollections.emptyListCollections.singletonMap
StringBuilderTreeMapCollections.emptyMapCollections.emptySetKryoSerializable
StringBufferClassCollections.singletonListCollections.singletonMapCurrency
CalendarTimeZoneEnumEnumSet

kryo优缺点总结

优点
  • 序列化的性能非常高
  • 序列化结果体积较小
  • 提供了简单易用的API
缺点
  • 跨语言支持较复杂

  • 不支持对象字段的增加/删除/修改

    如果更改了对象的字段,然后再从更改前序列化的bytes中反序列化,将会出错。

    当然如果想得到Add、Remove等操作的支持,可以使用FieldSerializer的其他扩展,如TaggedFieldSerializerVersionFieldSerializer等等。

kryo 与 JDK性能对比

Simple类
import java.io.Serializable;
import java.util.Map;

public class Simple implements Serializable {
private static final long serialVersionUID = -4914434736682797743L;
private String name;
private int age;
private Map map;

public Simple(){

}

public Simple(String name,int age,Map map){
this.name = name;
this.age = age;
this.map = map;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public Map getMap() {
return map;
}

public void setMap(Map map) {
this.map = map;
}
}

JDK性能测试

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.HashMap;
import java.util.Map;

public class OriginalSerializable {

public static void main(String[] args) throws IOException, ClassNotFoundException {
long start = System.currentTimeMillis();
setSerializableObject();
System.out.println("java原生序列化时间:" + (System.currentTimeMillis() - start) + " ms" );
start = System.currentTimeMillis();
getSerializableObject();
System.out.println("java原生反序列化时间:" + (System.currentTimeMillis() - start) + " ms");
}

public static void setSerializableObject() throws IOException{

FileOutputStream fo = new FileOutputStream("D:/file2.bin");

ObjectOutputStream so = new ObjectOutputStream(fo);

for (int i = 0; i < 100000; i++) {
Map map = new HashMap(2);
map.put("zhang0", i);
map.put("zhang1", i);
so.writeObject(new Simple("zhang"+i,(i+1),map));
}
so.flush();
so.close();
}

public static void getSerializableObject(){
FileInputStream fi;
try {
fi = new FileInputStream("D:/file2.bin");
ObjectInputStream si = new ObjectInputStream(fi);

Simple simple =null;
while((simple=(Simple)si.readObject()) != null){
//System.out.println(simple.getAge() + " " + simple.getName());
}
fi.close();
si.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
//e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}

kryo性能测试

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import org.objenesis.strategy.StdInstantiatorStrategy;

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.KryoException;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;

public class KyroSerializable {

public static void main(String[] args) throws IOException {
long start = System.currentTimeMillis();
setSerializableObject();
System.out.println("Kryo 序列化时间:" + (System.currentTimeMillis() - start) + " ms" );
start = System.currentTimeMillis();
getSerializableObject();
System.out.println("Kryo 反序列化时间:" + (System.currentTimeMillis() - start) + " ms");

}

public static void setSerializableObject() throws FileNotFoundException{

Kryo kryo = new Kryo();
kryo.setReferences(false);
kryo.setRegistrationRequired(false);
kryo.setInstantiatorStrategy(new StdInstantiatorStrategy());
kryo.register(Simple.class);
Output output = new Output(new FileOutputStream("D:/file1.bin"));
for (int i = 0; i < 100000; i++) {
Map map = new HashMap(2);
map.put("zhang0", i);
map.put("zhang1", i);
kryo.writeObject(output, new Simple("zhang"+i,(i+1),map));
}
output.flush();
output.close();
}


public static void getSerializableObject(){
Kryo kryo = new Kryo();
kryo.setReferences(false);
kryo.setRegistrationRequired(false);
kryo.setInstantiatorStrategy(new StdInstantiatorStrategy());
Input input;
try {
input = new Input(new FileInputStream("D:/file1.bin"));
Simple simple =null;
while((simple=kryo.readObject(input, Simple.class)) != null){
//System.out.println(simple.getAge() + " " + simple.getName() + " " + simple.getMap().toString());
}

input.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch(KryoException e){

}
}
}

测试结果

JDK
  • java原生序列化时间:6614 ms
  • java原生反序列化时间:8609 ms
kryo
  • Kryo 序列化时间:757 ms
  • Kryo 反序列化时间:307 ms

感谢各位的阅读,以上就是"何为序列化"的内容了,经过本文的学习后,相信大家对何为序列化这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是,小编将为大家推送更多相关知识点的文章,欢迎关注!

0