浅谈Java中的序列化
2016-02-20 12:19
633 查看
Java序列化
概念
什么是序列化和反序列化Serialization(序列化)是一种
将对象以一连串的字节描述的过程;反序列化deserialization是一种
将这些字节重建成一个对象的过程。
什么情况下需要序列化
a)当你想把的
内存中的对象保存到一个文件中或者数据库中时候;
b)当你想用
套接字Socket在网络上传送对象的时候;
c)当你想通过
跨进程通信传输对象的时候;
相当于在两端传输数据的协议,约定好怎么序列化然后怎么正确的反序列化,Java序列化机制就是为了解决这个问题而产生
如何实现序列化
将需要序列化的类实现Serializable接口就可以了,Serializable接口中没有任何方法,可以理解为一个标记,即表明这个类可以序列化。
如果我们想要序列化一个对象,首先要创建某些OutputStream(如FileOutputStream、ByteArrayOutputStream等),然后将这些OutputStream封装在一个ObjectOutputStream中。这时候,只需要调用writeObject()方法就可以将对象序列化,并将其发送给OutputStream(记住:对象的序列化是基于字节的,不能使用Reader和Writer等基于字符的层次结构)。而反序列的过程(即将一个序列还原成为一个对象),需要将一个InputStream(如FileInputstream、ByteArrayInputStream等)封装在ObjectInputStream内,然后调用readObject()即可。
public class MyTest implements Serializable { private static final long serialVersionUID = 1L; private String name = "LiaBin"; private transient String home = "China"; private static int age = 24; private static Test test1 = new Test(10); private Test test2 = new Test(10); public static void main(String[] args) {// 以下代码实现序列化 MyTest myTest = new MyTest(); try { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("myout.txt")); // 序列化到硬盘文件 // 输出流保存的文件名为my.out ObjectOutputStream能把Object输出成Byte流 oos.writeObject(myTest); oos.flush(); // 缓冲流 oos.close(); // 关闭流 ByteArrayOutputStream bo = new ByteArrayOutputStream(); //序列化到内存 ObjectOutputStream oo = new ObjectOutputStream(bo); oo.writeObject(new MyTest()); oo.flush(); oo.close(); byte[] bytes = bo.toByteArray(); for (Byte b : bytes) { // 此时跟myout.txt中用16进制格式查看的内容是一样的,只是这里的表示是2进制 System.out.print(b + " "); } System.out.println(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } myTest.fan(myTest);// 调用下面的 反序列化 代码 } public void fan(MyTest myTest) { // 反序列的过程 ObjectInputStream oin = null;// 局部变量必须要初始化 try { oin = new ObjectInputStream(new FileInputStream("myout.txt")); } catch (FileNotFoundException e1) { e1.printStackTrace(); } catch (IOException e1) { e1.printStackTrace(); } name = "BinJing"; age = 1; test1.setLevel(20);//静态变量生效 test2.setLevel(20);//无效 MyTest mts = null; try { mts = (MyTest) oin.readObject();// 由Object对象向下转型为MyTest对象 } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } // false,说明是深拷贝,堆中不同对象 System.out.println("myTest==mts: " + (myTest == mts)); // name=LiaBin System.out.println("name=" + mts.name); // home=null transient修饰变量,不序列化,所以是默认值null System.out.println("home=" + mts.home); // age=1 序列化会忽略静态字段,因为他们不属于对象的任何状态,打印静态变量当然会去静态变量全局区查找,所以此时是修改之后的值1 System.out.println("age=" + mts.age); // test1.level:20 test1是个静态属性不被系列化 System.out.println("test1.level:" + mts.test1.getLevel()); // test2.level:10 一起序列化,如果Test没实现Serializable接口,异常退出 System.out.println("test2.level:" + mts.test2.getLevel()); } } public class Test implements Serializable{ private int level; public Test(int level) { super(); this.level = level; } public int getLevel() { return level; } public void setLevel(int level) { this.level = level; } }
总结一下:
当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口;
当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化,注意是深拷贝而不是浅拷贝;
static,transient后的变量不能被序列化;
serialVersionUID作用
如果没有设置这个值,你在序列化一个对象之后,改动了该类的字段或者方法名之类的,那如果你再反序列化想取出之前的那个对象时就可能会抛出异常,因为你改动了类中间的信息,serialVersionUID是根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段,当修改后的类去反序列化的时候发现该类的serialVersionUID值和之前保存在问价中的serialVersionUID值不一致,所以就会抛出异常。而显示的设置serialVersionUID值就可以保证版本的兼容性,如果你在类中写上了这个值,
就算类变动了,它反序列化的时候也能和文件中的原值匹配上。而新增的值则会设置成null,删除的值则不会显示。
所以如果没设置serialVersionUID的话,类如果发生变动,那么最新序列化的serialVersionUID该值根据最新属性值计算得来,所以跟文件中的序列化值就对应不上了,反序列化失败。
注意方法的改变不影响,因为序列化只序列化属性,跟方法无关
如果你不在类中声明SerialVersionUID的话,
Java会在运行时替你生成一个,不过这个生成的过程会受到类元数据包括字段数,字段类型,字段的访问限制符,类实现的接口等因素的影响.
Java的序列化机制会替你生成一个的。它的生成机制受很多因素的影响,包括类中的字段,还有访问限制符,类实现的接口,甚至是不同的编译器实现,任何类的修改或者使用了不同的编译器生成SerialVersionUID都各不相同,很可能最终导致重新加载序列化的数据中止
Eclipse会根据类元数据计算得出serialVersionUID值
private static final long serialVersionUID = 7539660831609822000L; //默认根据所有的属性值计算出来的,如果没定义serialVersionUID,那么默认使用的就是通过所有属性值计算出来的该值
一般情况下,值直接申明为1L就行了
private static final long serialVersionUID = 1L;
举个例子
public class Test implements Serializable { private String home = "China"; //此时默认的serialVersionUID是-7477377630168477287L public static void main(String args[]) { Test myTest = new Test(); try { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("myout.txt")); oos.writeObject(myTest); oos.flush(); oos.close(); } catch (IOException e) { e.printStackTrace(); } fan(); } public static void fan() { ObjectInputStream oin = null; try { oin = new ObjectInputStream(new FileInputStream("myout.txt")); } catch (FileNotFoundException e1) { e1.printStackTrace(); } catch (IOException e1) { e1.printStackTrace(); } Test mts = null; try { mts = (Test) oin.readObject();// 由Object对象向下转型为Test对象 } catch (Exception e) { e.printStackTrace(); } System.out.println("home=" + mts.home); } }
此时没错,没有定义serialVersionUID值,那么按照默认序列化行为
后续开发中,新添了一个新的age属性,然后直接从文件中反序列化,代码如下
public class Test implements Serializable { private String home = "China"; //此时默认的serialVersionUID是-7477377630168477287L private int age = 20; //添加age属性之后,默认的serialVersionUID值是-3342077822325805408 public static void main(String args[]) { fan(); } public static void fan() { ObjectInputStream oin = null; try { oin = new ObjectInputStream(new FileInputStream("myout.txt")); } catch (FileNotFoundException e1) { e1.printStackTrace(); } catch (IOException e1) { e1.printStackTrace(); } Test mts = null; try { mts = (Test) oin.readObject();// 由Object对象向下转型为Test对象 } catch (Exception e) { e.printStackTrace(); } System.out.println("home=" + mts.home); } }
那么此时报异常,因为两者的serialVersionUID不一致
java.io.InvalidClassException: Test; local class incompatible: stream classdesc serialVersionUID = -3342077822325805408, local class serialVersionUID = 7477377630168477287
解决方案:添加如下行即可
private static final long serialVersionUID = 1L;
自定义序列化
当进行序列化的时候:1. 首先JVM会先调用
writeReplace方法,在这个阶段,我们可以进行张冠李戴,将需要进行序列化的对象换成我们指定的对象.
2. 跟着JVM将调用
writeObject方法,来将对象中的属性一个个进行序列化,我们可以在这个方法中控制住哪些属性需要序列化.
当反序列化的时候:
1. JVM会调用
readObject方法,将我们刚刚在writeObject方法序列化好的属性,反序列化回来.
2. 然后在
readResolve方法中,我们也可以指定JVM返回我们特定的对象(不是刚刚序列化回来的对象).
注意到在writeReplace和readResolve,我们可以严格控制singleton的对象,在同一个JVM中完完全全只有唯一的对象,控制不让singleton对象产生副本.
注:writeReplace调用在writeObject前;readResolve调用在readObject之后
单例的序列化
为了使一个单例类变成可序列化的,仅仅在声明中添加“implements Serializable”是不够的。因为一个串行化的对象在每次反序列化的时候,都会创建一个新的对象,而不仅仅是一个对原有对象的引用。为了防止这种情况,可以在单例类中加入
readResolve方法
public class AnoTest implements Serializable { private static final long serialVersionUID = 1L; private static final AnoTest INSTANCE = new AnoTest(); private AnoTest() { } public static AnoTest getInstance() { return INSTANCE; } //单例序列化,定义该方法 private Object readResolve() throws ObjectStreamException { return INSTANCE; } public static void main(String args[]) { AnoTest anoTest = getInstance();// 此时把堆中的AnoTest对象序列化,但是INSTANCE是个静态变量,所以不会重复序列化 try { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("myout.txt")); oos.writeObject(anoTest); oos.flush(); oos.close(); } catch (IOException e) { e.printStackTrace(); } fan(); } public static void fan() { ObjectInputStream oin = null; try { oin = new ObjectInputStream(new FileInputStream("myout.txt")); } catch (FileNotFoundException e1) { e1.printStackTrace(); } catch (IOException e1) { e1.printStackTrace(); } AnoTest mts = null; try { mts = (AnoTest) oin.readObject(); } catch (Exception e) { e.printStackTrace(); } System.out.println("mts==INSTANCE ? " + (mts == INSTANCE)); //定义readResolve方法,返回true //System.out.println("mts==INSTANCE ? " + (mts == INSTANCE)); //注释readResolve方法,返回false } }
这样当JVM从内存中反序列化地”组装”一个新对象时,就会自动调用这个readResolve方法来返回我们指定好的对象了, 单例规则也就得到了保证。
子类/父类序列化行为
参考Java自定义序列化行为解析一个可序列化的类继承自一个非序列化的有状态超类
class AbstractSerializeDemo { private int x, y; public void init(int x, int y) { this.x = x; this.y = y; } public int getX() { return x; } public int getY() { return y; } public void printXY() { System.out.println("x:" + x + ";y:" + y); } } public class SerializeDemo extends AbstractSerializeDemo implements Serializable { private int z; public SerializeDemo() { super.init(10, 50); z = 100; } public void printZ() { super.printXY(); System.out.println("z:" + z); } public static void main(String[] args) throws IOException, ClassNotFoundException { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(bos); SerializeDemo sd = new SerializeDemo(); sd.printZ(); out.writeObject(sd); ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray())); SerializeDemo sd2 = (SerializeDemo) in.readObject(); sd2.printZ(); } }
输出结果:
x:10;y:50
z:100
x:0;y:0
z:100
子类的值域保留下来了,但是超类的值域丢失了,这对jvm来说是正常的,因为超类不可序列化;
为了解决这个问题,只能自定义序列化行为,具体做法是在SerializeDemo里加入以下代码:
private void writeObject(ObjectOutputStream os) throws IOException { os.defaultWriteObject();//java对象序列化默认操作 os.writeInt(getX()); os.writeInt(getY()); } private void readObject(ObjectInputStream is) throws IOException, ClassNotFoundException { is.defaultReadObject();//java对象反序列化默认操作 int x=is.readInt(); int y=is.readInt(); super.init(x,y); }
writeObject和readObject方法为JVM会在序列化和反序列化java对象时会分别调用的两个方法,修饰符都是private
我们在序列化的默认动作之后将超类里的两个值域x和y也写入object流;与之对应在反序列化的默认操作之后读入x和y两个值,然后调用超类的初始化方法。
再次执行程序之后的输出为:
x:10;y:50
z:100
x:10;y:50
z:100
Android中的序列化
Android中的新的序列化机制
在Android系统中,针对内存受限的移动设备,因此对性能要求更高,Android系统采用了新的IPC(进程间通信)机制,要求使用性能更出色的对象传输方式。因此Parcel类被设计出来,其定位就是轻量级的高效的对象序列化和反序列化机制。Parcel的序列化和反序列化的读写全是在内存中进行,所以效率比JAVA序列化中使用外部存储器会高很多
Android Serializable与Parcelable原理与区别 这里可以看到Parcelable的原理,读写全在内存,速度快
Android中启动一个activity都是通过AMS来进行的,我们知道AMS在系统进程中,所以此时跨进程通信的话,如果需要传输对象,那么就需要序列化了
如何选择
Parcelable的性能比Serializable好,在内存开销方面较小,所以在内存间数据传输时推荐使用Parcelable,Serializable在序列化的时候会产生大量的临时变量,从而引起频繁的GC。如activity间传输数据
Serializable可将数据持久化方便保存,所以在需要保存在本地硬盘文件或网络传输数据时选择Serializable,因为android不同版本Parcelable可能不同,所以不能很好的保存数据的持续性在外界有变化的情况下,不推荐使用Parcelable进行数据持久化
相关文章推荐
- C#自定义序列化ISerializable的实现方法
- c#数据的序列化和反序列化(推荐版)
- c#序列化详解示例
- c#对象反序列化与对象序列化示例详解
- .net实现序列化与反序列化实例解析
- C#实现复杂XML的序列化与反序列化
- C#中Serializable序列化实例详解
- asp.net类序列化生成xml文件实例详解
- C#实现的json序列化和反序列化代码实例
- C#序列化与反序列化(Serialize,Deserialize)实例详解
- C#二进制序列化实例分析
- jQuery与Ajax以及序列化
- asp.net 序列化and反序列化演示
- Json序列化和反序列化方法解析
- 深入理解Java对象的序列化与反序列化的应用
- 详解Java编程中对象的序列化
- 使用 ServiceStack.Text 序列化 json的实现代码
- 详解JavaScript对象序列化
- php 中序列化和json使用介绍
- Android中的序列化浅析