前言 在Java中,我们知道可以随意创建对象,只要对象未被GC回收,我们都可以继续在程序里使用,但这些对象只是存在于JVM内存中的,我们JVM一旦停止,这些对象就消失不见了。
经常有些时候,我们需要把这些对象持久化下来,再次需要时,再重新把对象读取出来,Java中有一种机制,对象序列化机制(object serialization)便可以帮我们完成相关功能。
对象序列化,可以方便的把对象状态保存为字节数组,可以通过字节流进行远程网络传输等,接收到字节流,通过反序列化机制,可以将字节数组转换为相关对象。
常说的RPC远程调用,相关传输对象的生成类就必须实现序列化以便在网络间传输。
正文 Serializable接口 在Java中,我们最常用的实现序列化和反序列化的方法就是相关类实现 java.io.Serializable 接口了,这也是Java给我们提供的一个方便的API。
我们创建一个Apple类,实现序列化接口,通过测试,可以看到相关对象生成的字节码和反序列化后的对象。
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 public class Apple implements Serializable { private String color; private int weight; public String getColor () { return color; } public void setColor (String color) { this .color = color; } public int getWeight () { return weight; } public void setWeight (int weight) { this .weight = weight; } public Apple (String color, int weight) { this .color = color; this .weight = weight; } @Override public String toString () { return "Apple{" + "color='" + color + '\'' + ", weight=" + weight + '}' ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class Test { public static void main (String[] args) throws IOException,ClassNotFoundException { byte [] bytes = null ; Apple apple = new Apple("red" ,150 ); try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos) ){ oos.writeObject(apple); bytes = baos.toByteArray(); for (byte b : baos.toByteArray()) { System.out.print(Byte.toString(b) + " " ); } } System.out.println(); try ( ByteArrayInputStream bais = new ByteArrayInputStream(bytes); ObjectInputStream ois = new ObjectInputStream(bais) ) { System.out.println(ois.readObject().toString()); } } }
测试输出结果:
当我们去掉Apple类的Serializable接口后,执行测试会抛出异常,说明对象无法被序列化。
序列化ID(serialVersionUID) JVM虚拟机是否可以对某个对象进行反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的点是两个类的序列化ID是否一致(就是 private static final long serialVersionUID)。
序列化ID有两种生成策略,一个是固定的 1L,一个是随机生成一个不重复的 long 类型数据(实际上是使用 JDK 工具生成),在这里有一个建议,如果没有特殊需求,就是用默认的 1L 就可以,这样可以确保代码一致时反序列化成功。那么随机生成的序列化ID有什么作用呢,有些时候,通过改变序列化 ID 可以用来限制某些用户的使用。
序列化的实现方式 在Java中,我们还可以利用其它方式对对象进行序列化,我总结了几种序列化方式如下。
让我们一起来看一下:
我们提供一个序列化与反序列化通用接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public interface Serializer { String name () ; byte [] serialize(Object obj) throws IOException ; Object deserialize (byte [] bytes) throws IOException ; }
标准的Java序列化 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 public class JavaSerializer implements Serializer { @Override public String name () { return "java" ; } @Override public byte [] serialize(Object obj) throws IOException { try ( ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); ){ oos.writeObject(obj); return baos.toByteArray(); } } @Override public Object deserialize (byte [] bits) throws IOException { if (bits == null || bits.length == 0 ) { return null ; } try ( ByteArrayInputStream bais = new ByteArrayInputStream(bits); ObjectInputStream ois = new ObjectInputStream(bais); ){ return ois.readObject(); }catch (ClassNotFoundException e){ throw new RuntimeException(e); } } }
可以看到序列化就是我们将对象通过ObjectOutputStream转化为ByteArrayOutputStream字节流,反序列化就是将字节流转换为对象流并读取。
FST实现序列化 需要引入相关jar包
1 2 3 4 5 <dependency> <groupId>de.ruedigermoeller</groupId> <artifactId>fst</artifactId> <version>2.57</version> </dependency>
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 public class FSTSerializer implements Serializer { @Override public String name () { return "fst" ; } @Override public byte [] serialize(Object obj) throws IOException { try ( ByteArrayOutputStream out = new ByteArrayOutputStream(); FSTObjectOutput fout = new FSTObjectOutput(out); ){ fout.writeObject(obj); fout.flush(); return out.toByteArray(); } } @Override public Object deserialize (byte [] bytes) throws IOException { if (bytes == null || bytes.length == 0 ) { return null ; } try ( FSTObjectInput in = new FSTObjectInput(new ByteArrayInputStream(bytes)); ){ return in.readObject(); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } }
Kryo实现序列化 需要引入相关jar包
1 2 3 4 5 <dependency> <groupId>com.esotericsoftware</groupId> <artifactId>kryo</artifactId> <version>5.0.0-RC4</version> </dependency>
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 public class KryoSerializer implements Serializer { private final static Kryo kryo = new Kryo(); @Override public String name () { return "kryo" ; } @Override public byte [] serialize(Object obj) throws IOException { try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); Output output = new Output(baos)) { kryo.register(obj.getClass()); kryo.writeClassAndObject(output, obj); output.flush(); return baos.toByteArray(); } } @Override public Object deserialize (byte [] bits) throws IOException { if (bits == null || bits.length == 0 ) { return null ; } try (ByteArrayInputStream bais = new ByteArrayInputStream(bits); Input ois = new Input(bais) ) { return kryo.readClassAndObject(ois); } } }
KryoPool实现序列化 由于kryo创建的代价相对较高,我们可以使用一个KryoPool池来管理Kryo,使用空间换取时间,提高运行效率。
我们使用一个双端队列来对Kryo进行管理,相关代码如下:
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 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 public class KryoPoolSerializer implements Serializer { private static class KryoHolder { private Kryo kryo; static final int BUFFER_SIZE = 1024 ; private Output output = new Output(BUFFER_SIZE, -1 ); private Input input = new Input(); KryoHolder(Kryo kryo) { this .kryo = kryo; } } interface KryoPool { KryoHolder get () ; void offer (KryoHolder kryo) ; } public static class KryoPoolImpl implements KryoPool { private final Deque<KryoHolder> kryoHolderDeque=new ConcurrentLinkedDeque<KryoHolder>(); private KryoPoolImpl () { } public static KryoPool getInstance () { return Singleton.pool; } @Override public KryoHolder get () { KryoHolder kryoHolder = kryoHolderDeque.pollFirst(); return kryoHolder == null ? creatInstnce() : kryoHolder; } public KryoHolder creatInstnce () { Kryo kryo = new Kryo(); kryo.setReferences(false ); return new KryoHolder(kryo); } @Override public void offer (KryoHolder kryoHolder) { kryoHolderDeque.addLast(kryoHolder); } private static class Singleton { private static final KryoPool pool = new KryoPoolImpl(); } } @Override public String name () { return "Kryo_Pool" ; } @Override public byte [] serialize(Object obj) throws IOException { KryoHolder kryoHolder = null ; if (obj == null ){ throw new RuntimeException("obj can not be null" ); } try { kryoHolder = KryoPoolImpl.getInstance().get(); kryoHolder.kryo.register(obj.getClass()); kryoHolder.output.reset(); kryoHolder.kryo.writeClassAndObject(kryoHolder.output, obj); return kryoHolder.output.toBytes(); } catch (RuntimeException e) { e.printStackTrace(); throw new RuntimeException("Serialize obj exception" ); } finally { KryoPoolImpl.getInstance().offer(kryoHolder); obj = null ; } } @Override public Object deserialize (byte [] bytes) throws IOException { KryoHolder kryoHolder = null ; if (bytes == null ){ throw new RuntimeException("bytes can not be null" ); } try { kryoHolder = KryoPoolImpl.getInstance().get(); kryoHolder.input.setBuffer(bytes, 0 , bytes.length); return kryoHolder.kryo.readClassAndObject(kryoHolder.input); } catch (RuntimeException e) { throw new RuntimeException("Deserialize bytes exception" ); } finally { KryoPoolImpl.getInstance().offer(kryoHolder); bytes = null ; } } }
Jackson 实现序列化和反序列化 Jackson也可以实现相关序列化和反序列化功能,需要引入jackson 的jar包。
使用writeValueAsBytes和readValue方法即可完成相关功能。
1 2 3 4 5 <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.5.3</version> </dependency>
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class JacksonSerializer implements Serializer { private static final ObjectMapper mapper = new ObjectMapper(); @Override public String name () { return "Jackson" ; } @Override public byte [] serialize(Object obj) throws IOException { return mapper.writeValueAsBytes(obj); } @Override public Object deserialize (byte [] bytes) throws IOException { return mapper.readValue(bytes,Object.class); } }
FastJson实现序列化和反序列化 FastJson实现序列化与反序列化,需要引入相关jar包,如下:
1 2 3 4 5 <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.58</version> </dependency>
相关代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class FastJsonSerializer implements Serializer { @Override public String name () { return "FastJson" ; } @Override public byte [] serialize(Object obj) throws IOException { return JSON.toJSONString(obj, SerializerFeature.WriteClassName).getBytes(); } @Override public Object deserialize (byte [] bytes) throws IOException { return JSON.parse(new String(bytes), Feature.SupportAutoType); } }
以上的序列化与反序列化的对象都需要实现Serializable接口。
我们对上述代码进行相关测试:
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 public class SerializationUtils { public static void main (String[] args) throws IOException { Apple apple =new Apple(); apple.setColor("red" ); apple.setWeight(100 ); printData(new JavaSerializer(),apple); printData(new FSTSerializer(),apple); printData(new KryoSerializer(),apple); printData(new KryoPoolSerializer(),apple); printData(new JacksonSerializer(),apple); printData(new FastJsonSerializer(),apple); } public static void printData (Serializer serializer,Apple apple) throws IOException { long start = System.currentTimeMillis(); byte [] bits = serializer.serialize(apple); System.out.println(serializer.name()+"序列化所需时间:" +(System.currentTimeMillis()-start)+"ms" ); System.out.println(serializer.name()+"序列化后字节码长度:" +bits.length); long start1 = System.currentTimeMillis(); Object obj = serializer.deserialize(bits); System.out.println(serializer.name()+"反序列化所需时间:" +(System.currentTimeMillis()-start1)+"ms" ); System.out.println(serializer.name()+"反序列化后对象:" +obj.toString()); } }
可以看到输出后的结果:
根据结果判断正确性后,也大致能看出各种序列化方式的一些优点和缺点。
JavaSerializer 明显的优点是不用引用包,也是Java程序默认的序列化方式,但是其序列化后占用空间是几种序列化方式里最大的,如果遇到大对象序列化,处理起来可能就比较力不从心了。
FSTSerializer、JacksonSerializer、FastJsonSerializer 它们是一种比较适中的序列化方式,序列化后的字节比Java方法少,时间也差不多。
KryoSerializer 是一种比较优异的序列化方式,可以看到它的序列化后的字节很短,占用空间少,且序列化和反序列化时间短。
KryoPoolSerializer 这种相当于KryoSerializer的改进版,利用了一部分内存空间,进一步降低了序列化和反序列化的时间。
正因为Kryo如此高效的序列化和反序列化性能,因此在大数据领域应用广泛。如Apache的spark、hive等。
如果需要更准确的结果比较各种序列化方式的性能,可以创建大量对象并对它们进行序列化记录时间等参数比较,这儿就不过多讨论了。
结语 通过对序列化和反序列化的简单介绍,并比较了一些常用的序列化方式,我们对对象的序列化与反序列化有了更进一步的认知。