java序列化总结

为什么需要序列化

  • 数据持久化(如session信息存储到redis)或在网络上传输(如RPC远程调用)

序列化要考虑的因素

  • 性能:速度越快越好
  • 序列化后字节大小:字节越小越好,节省带宽和存储器空间
  • 兼容性:类的信息发生变化,旧的序列化数据是否能正常用新类反序列化,或者反之。如果序列化内容是放在内存并且每次发版(停服发版)都会清空,那么可以不考虑兼容,否则兼容性就要考虑。灰度发布之类的也要考虑兼容性。这里说的兼容性是指加减字段,不包括更改字段类型。

常见序列化方式

  • JDK自带的ObjectInputStream和ObjectOutputStream,需要实现Serializable,需要兼容的话要写死servialVersionUID。性能低、体积大。
  • 各类json(jackson, gson, fastjson),性能比jdk稍高,体积也稍小,对人友好,基本所有主流语言都支持,跨语言性非常好。兼容性好。但是类和字段的信息没有序列化进去,在反序列化的时候需要指定类名。
  • hessian:性能和字节比jdk好,兼容性差。如果子类和父类有相同的属性名,那么在反序列化后会丢失字节,原因:hessian先写子类Field,再写父类Field,写值的顺序也一样,因为父类Field的值一般都是null,所以在反序列化的时候,总是把最后的父类的null值覆盖掉子类的值,具体原因参考:https://www.cnblogs.com/yfyzy/p/7197679.html。hessian的一些类不是public,不能继承,如果要改的话只能改源码了。
  • hessian-lite:阿里dubbo项目里默认用的序列化协议,改自hessian,他解决了字节丢失问题,就是在获取所有Field后做下reverse操作,颠倒了Field的顺序。但是经过测试发现heissian-lite速度太慢了,见issue:https://github.com/dubbo/hessian-lite/issues/10
  • kryo:速度和性能都很好,默认不兼容,不过通过设置CompatibleFieldSerializcer就能支持兼容,但是也不允许父类和子类有相同名字的属性,可以通过继承过滤掉同名属性。kryo可以参考官方文档,https://github.com/EsotericSoftware/kryo#compatiblefieldserializer-settings,很详细的。
  • fst:性能和字节大小都是最优的,可惜兼容性要在字段上加@Version,只能增字段不能删,对业务开发侵入太大,如果不考虑兼容的话可以考虑用fst。参考:https://blog.csdn.net/dutlxq2014/article/details/86698268。wiki:https://github.com/RuedigerMoeller/fast-serialization/wiki
  • 需要静态编译的,如果protobuf, thrift,适合内部系统之间RPC,本文不涉及这部分。

kryo目前的bug

性能和字节大小对比

SerializeBenchmarkTest3测试类,对几种序列化方式进行了测试:
测试数据:

private Person getPerson() {
        Person person = new Person();
        person.setId(123L);
        person.setName("你好啊");
        person.setMarried(true);
        person.setAge(22);
        person.setDigits(Arrays.asList(1L, 3L, 100L));
        Map<String, Double> scoreMap = new LinkedHashMap<>();
        scoreMap.put("chinese", 90d);
        scoreMap.put("english", 80.5d);
        person.setScores(scoreMap);
        Book book = new Book();
        book.setId(99L);
        book.setName("代码大全");
        book.setPrice(56.00d);
        person.setBook(book);

        int friendsCount = 1000;
        List<Person> friends = new ArrayList<>(friendsCount);
        for (int i = 0; i < friendsCount; i++) {
            Person friend = new Person();
            friend.setId(Long.valueOf(i));
            friend.setName(String.valueOf("我的朋友" + i));
            friend.setMarried(i % 2 == 0 ? true : false);
            friends.add(friend);
        }
        person.setFriends(friends);
        return person;
    }

test1方法测试了序列化后自己大小和md5,测试结果如下:

2019-03-23 15:22:04,916 WARN  [main] c.y.o.s.SerializeBenchmarkTest3.jdkPerformance(54) - jdk序列化后长度:52797, 前后长度一致:true, md5一致:true,对象equals:true
2019-03-23 15:22:05,226 WARN  [main] c.y.o.s.SerializeBenchmarkTest3.jsonPerformance(71) - json序列化后长度:59457, 前后长度一致:true, md5一致:true,对象equals:true
2019-03-23 15:22:05,309 WARN  [main] c.y.o.s.SerializeBenchmarkTest3.hessian2Performance(88) - hessian2序列化后长度:26124, 前后长度一致:false, md5一致:false,对象equals:false
2019-03-23 15:22:05,380 WARN  [main] c.y.o.s.SerializeBenchmarkTest3.hessianLitePerformance(105) - hessian-lite序列化后长度:26144, 前后长度一致:false, md5一致:false,对象equals:true
2019-03-23 15:22:05,661 WARN  [main] c.y.o.s.SerializeBenchmarkTest3.kryoPerformance(122) - kryo序列化后长度:28101, 前后长度一致:true, md5一致:true,对象equals:true
2019-03-23 15:22:05,696 WARN  [main] c.y.o.s.SerializeBenchmarkTest3.fstPerformance(139) - fst序列化后长度:33839, 前后长度一致:true, md5一致:true,对象equals:true

由于Person类继承了Human类,2个类都有同名属性id,hessian2在序列化的时候存在bug导致丢失数据,奇怪的是hessian-lite虽然解决了这个bug,但是前后序列化字节长度却不相等。
从上面结果可以看出,在小数据量场景下,hessian2及hessian-lite在体积上占有小优势,kryo、fst次之,jdk和json最差。

然后对上面的数据做10000次序列化和反序列化,结果如下:

14次YGC
13.658: [GC (Allocation Failure) [PSYoungGen: 682646K->233K(691200K)] 686763K->4350K(2089472K), 0.0018965 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
2019-03-23 15:31:22,073 WARN  [main] c.y.o.s.SerializeBenchmarkTest3.jdkPerformance(61) - jdk序列化、反序列化10000次耗时13699

9次YGC
19.258: [GC (Allocation Failure) [PSYoungGen: 687902K->318K(693248K)] 692530K->4946K(2091520K), 0.0006095 secs] [Times: user=0.06 sys=0.00, real=0.00 secs] 
2019-03-23 15:31:27,321 WARN  [main] c.y.o.s.SerializeBenchmarkTest3.jsonPerformance(78) - json序列化、反序列化10000次耗时5245

5次YGC
24.824: [GC (Allocation Failure) [PSYoungGen: 689776K->121K(694784K)] 694539K->4909K(2093056K), 0.0007943 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
2019-03-23 15:31:32,671 WARN  [main] c.y.o.s.SerializeBenchmarkTest3.hessian2Performance(95) - hessian2序列化、反序列化10000次耗时5349

11次YGC
35.886: [GC (Allocation Failure) [PSYoungGen: 694368K->64K(696320K)] 699315K->5011K(2094592K), 0.0073285 secs] [Times: user=0.05 sys=0.00, real=0.01 secs] 
2019-03-23 15:31:43,688 WARN  [main] c.y.o.s.SerializeBenchmarkTest3.hessianLitePerformance(112) - hessian-lite序列化、反序列化10000次耗时11017


3次YGC
39.634: [GC (Allocation Failure) [PSYoungGen: 694880K->64K(696832K)] 700003K->5203K(2095104K), 0.0007231 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
2019-03-23 15:31:48,061 WARN  [main] c.y.o.s.SerializeBenchmarkTest3.kryoPerformance(129) - kryo序列化、反序列化10000次耗时4373


3次YGC
43.816: [GC (Allocation Failure) [PSYoungGen: 694945K->193K(696832K)] 700308K->5556K(2095104K), 0.0007227 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
2019-03-23 15:31:51,626 WARN  [main] c.y.o.s.SerializeBenchmarkTest3.fstPerformance(146) - fst序列化、反序列化10000次耗时3564

jvm参数:-Xms2g -Xmx2g -XX:+PrintGCTimeStamps -XX:+PrintGCDetails

可以看出fst最快,kryo次之,json、hessian2速度还不错,但是hessian-lite和jdk基本上一样慢。

最佳实践

  • 序列化的类最好实现Serializable接口,并写死serialVersionUID
  • 序列化的类可以加减字段,但是最好不要改字段类型
  • 如果是开放出去的api,最好采用可读性好、适合web的json,兼容性也好,和语言没有耦合,就是浪费带宽
  • 如果是内部RPC,可以采用fst和kryo,或者protobuf, thrift。如果要兼容多版本,fst就不太适合
  • 如果有持久化需求,需要考虑到兼容性,可以采用kryo, json

序列化工具类:

static MzKryoPool<Kryo> kryoPool = new MzKryoPool<Kryo>(100);
    static FSTConfiguration fst = FSTConfiguration.createDefaultConfiguration();

    public static <T> byte[] serializeWithJdk(T object) {
        try {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(4096);
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
            objectOutputStream.writeObject(object);
            byte[] bytes = byteArrayOutputStream.toByteArray();
            objectOutputStream.close();
            return bytes;
        } catch (IOException e) {
            throw new OperationException("serialize with jdk fail: " + e.getMessage(), e);
        }
    }

    public static Object deserializeWithJdk(byte[] bytes) {
        try {
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
            ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
            Object object = objectInputStream.readObject();
            objectInputStream.close();
            return object;
        } catch (ClassNotFoundException | IOException e) {
            throw new OperationException("deserialize with jdk fail: " + e.getMessage(), e);
        }
    }

    public static byte[] serializeWithJson(Object object) {
        return JSON.toJSONBytes(object);
    }

    public static <T> T deserializeWithJson(byte[] bytes, Class<T> cls) {
        return JSON.parseObject(bytes, cls);
    }

    public static byte[] serializeWithHessian2(Object object) {
        try {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(4096);
            Hessian2Output hessianOutput = new Hessian2Output(byteArrayOutputStream);
            hessianOutput.startMessage();
            hessianOutput.writeObject(object);
            hessianOutput.completeMessage();
            hessianOutput.close();
            return byteArrayOutputStream.toByteArray();
        } catch (IOException e) {
            throw new OperationException("serialize with hessian2 fail: " + e.getMessage(), e);
        }
    }

    public static Object deserializeWithHessian2(byte[] bytes) {
        try {
            Hessian2Input hessian2Input = new Hessian2Input(new ByteArrayInputStream(bytes));
            hessian2Input.startMessage();
            Object o = hessian2Input.readObject();
            hessian2Input.completeMessage();
            hessian2Input.close();
            return o;
        } catch (IOException e) {
            throw new OperationException("deserialize with hessian2 fail: " + e.getMessage(), e);
        }
    }

    public static byte[] serializeWithHessianLite(Object object) {
        try {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(4096);
            com.alibaba.com.caucho.hessian.io.Hessian2Output hessian2Output = new com.alibaba.com.caucho.hessian.io.Hessian2Output(byteArrayOutputStream);
            hessian2Output.startMessage();
            hessian2Output.writeObject(object);
            hessian2Output.completeMessage();
            hessian2Output.close();
            return byteArrayOutputStream.toByteArray();
        } catch (IOException e) {
            throw new OperationException("serialize with hessian-lite fail: " + e.getMessage(), e);
        }
    }

    public static Object deserializeWithHessianLite(byte[] bytes) {
        try {
            com.alibaba.com.caucho.hessian.io.Hessian2Input hessian2Input = new com.alibaba.com.caucho.hessian.io.Hessian2Input(new ByteArrayInputStream(bytes));
            hessian2Input.startMessage();
            Object o = hessian2Input.readObject();
            hessian2Input.completeMessage();
            hessian2Input.close();
            return o;
        } catch (IOException e) {
            throw new OperationException("deserialize with hessian-lite fail: " + e.getMessage(), e);
        }
    }

    public static byte[] serializeWithKryo(Object obj) {
        Kryo kryo = kryoPool.obtain();
        //initial 4k, max 10M
        try (Output output = new Output(4096, 10 * 1024 * 1024);) {
            kryo.writeClassAndObject(output, obj);
            return output.toBytes();
        } catch (Exception e) {
            throw new OperationException("deserialize with kryo fail: " + e.getMessage(), e);
        } finally {
            kryoPool.free(kryo);
        }
    }

    public static Object deserializeWithKryo(byte[] bytes) {
        Kryo kryo = kryoPool.obtain();
        try (Input input = new Input(bytes)) {
            return kryo.readClassAndObject(input);
        } catch (Exception e) {
            throw new OperationException("deserialize with kryo fail: " + e.getMessage(), e);
        } finally {
            kryoPool.free(kryo);
        }
    }

    public static byte[] serializeWithFst(Object obj) {
        return fst.asByteArray(obj);
    }

    public static Object deserializeWithFst(byte[] bytes) {
        return fst.asObject(bytes);
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容

  • Java序列化总结 序列化就是指将Java对象转换成一系列的字节,反序列化即使将一系列的字节恢复成Java对象。序...
    ObadiObada阅读 503评论 0 2
  • Dubbo + Kryo 实现高速序列化 本节视频 【视频】Dubbo 实现微服务架构-Dubbo-使用 Kryo...
    撸帝阅读 4,164评论 0 10
  • 什么是序列化与反序列化 序列化是指把对象转换为字节序列的过程(Encoding an object as a by...
    小X感悟阅读 880评论 0 4
  • JAVA序列化机制的深入研究 对象序列化的最主要的用处就是在传递,和保存对象(object)的时候,保证对象的完整...
    时待吾阅读 10,834评论 0 24
  • #笔墨里的时光#第三期2 说说抱怨 前几天,几个好友在一起吃饭,说起我们共同的好友一直在朋友圈里抱怨生活。我红着脸...
    诗怡1978阅读 560评论 5 9