java序列化
来源:互联网 发布:今日头条 招聘 java 编辑:程序博客网 时间:2024/06/05 09:56
序列化概述
序列化,简单来讲,就是以“流”的方式来保存对象,至于保存的目标地址,可以是文件,可以是数据库,也可以是网络,即通过网络将对象从一个节点传递到另一个节点。
我们知道在Java的I/O结构中,有ObjectOutputStream和ObjectInputStream,它们可以实现将对象输出为二进制流,并从二进制流中获取对象,那为什么还需要序列化呢?这需要从Java变量的存储结构谈起,我们知道对Java来说,基础类型存储在栈上,复杂类型(引用类型)存储在堆中,对于基础类型来说,上述的操作时可行的,但对复杂类型来说,上述操作过程中,可能会产生重复的对象,造成错误。
而序列化的工作流程如下:
1)通过输出流保存的对象都有一个唯一的序列号。
2)当一个对象需要保存时,先对其序列号进行检查。
3)当保存的对象中已包含该序列号时,不需要再次保存,否则,进入正常保存的流程。
正是通过序列号的机制,序列化才可以完整准确的保存对象的各个状态。
序列化保存的是对象中的各个属性的值,而不是方法或者方法签名之类的信息。对于方法或者方法签名,只要JVM能够找到正确的ClassLoader,那么就可以invoke方法。
序列化不会保存类的静态变量,因为静态变量是作用于类型,而序列化作用于对象。
简单的序列化示例
序列化的完整过程包括两部分:
1)使用ObjectOutputStream将对象保存为二进制流,这一步叫做“序列化”。
2)使用ObjectInputStream将二进制流转换成对象,这一步叫做“反序列化”。
下面我们来演示一个简单的示例,首先定义一个Person对象,它包含name和age两个信息。
1 class Person implements Serializable 2 { 3 private String name; 4 private int age; 5 public void setName(String name) { 6 this.name = name; 7 } 8 public String getName() { 9 return name;10 }11 public void setAge(int age) {12 this.age = age;13 }14 public int getAge() {15 return age;16 }17 18 public String toString()19 {20 return "Name:" + name + "; Age:" + age;21 }22 }
然后是两个公共方法,用来完成读、写对象的操作:
1 private static void writeObject(Object obj, String filePath) 2 { 3 try 4 { 5 FileOutputStream fos = new FileOutputStream(filePath); 6 ObjectOutputStream os = new ObjectOutputStream(fos); 7 os.writeObject(obj); 8 os.flush(); 9 fos.flush();10 os.close();11 fos.close();12 System.out.println("序列化成功。");13 }14 catch(Exception ex)15 {16 ex.printStackTrace();17 }18 }19 20 private static Object readObject(String filePath)21 {22 try23 {24 FileInputStream fis = new FileInputStream(filePath);25 ObjectInputStream is = new ObjectInputStream(fis);26 27 Object temp = is.readObject();28 29 fis.close();30 is.close();31 32 if (temp != null)33 {34 System.out.println("反序列化成功。");35 return temp;36 }37 }38 catch(Exception ex)39 {40 ex.printStackTrace();41 }42 43 return null;44 }
这里,我们将对象保存的二进制流输出到磁盘文件中。
接下来,我们首先来看“序列化”的方法:
1 private static void serializeTest1()2 {3 Person person = new Person();4 person.setName("Zhang San");5 person.setAge(30);6 System.out.println(person);7 writeObject(person, "d:\\temp\\test\\person.obj");8 }
我们定义了一个Person实例,然后将其保存到d:\temp\test\person.obj中。
最后,是“反序列化”的方法:
1 private static void deserializeTest1()2 { 3 Person temp = (Person)readObject("d:\\temp\\test\\person.obj");4 5 if (temp != null)6 {7 System.out.println(temp);8 }9 }
它从d:\temp\test\person.obj中读取对象,然后进行输出。
上述两个方法的执行结果如下:
Name:Zhang San; Age:30序列化成功。反序列化成功。Name:Zhang San; Age:30
可以看出,读取的对象和保存的对象是完全一致的。
隐藏非序列化信息
有时,我们的业务对象中会包含很多属性,而有些属性是比较隐私的,例如年龄、银行卡号等,这些信息是不太适合进行序列化的,特别是在需要通过网络来传输对象信息时,这些敏感信息很容易被窃取。
Java使用transient关键字来处理这种情况,针对那些敏感的属性,我们只需使用该关键字进行修饰,那么在序列化时,对应的属性值就不会被保存。
我们还是看一个实例,这次我们定义一个新的Person2,其中age信息是我们不希望序列化的:
1 class Person2 implements Serializable 2 { 3 private String name; 4 private transient int age; 5 public void setName(String name) { 6 this.name = name; 7 } 8 public String getName() { 9 return name;10 }11 public void setAge(int age) {12 this.age = age;13 }14 public int getAge() {15 return age;16 }17 18 public String toString()19 {20 return "Name:" + name + "; Age:" + age;21 }22 }
注意age的声明语句:
1 private transient int age;
下面是“序列化”和“反序列化”的方法:
1 private static void serializeTest2() 2 { 3 Person2 person = new Person2(); 4 person.setName("Zhang San"); 5 person.setAge(30); 6 System.out.println(person); 7 writeObject(person, "d:\\temp\\test\\person2.obj"); 8 } 9 10 private static void deserializeTest2()11 { 12 Person2 temp = (Person2)readObject("d:\\temp\\test\\person2.obj");13 14 if (temp != null)15 {16 System.out.println(temp);17 }18 }
它的输出结果如下:
Name:Zhang San; Age:30序列化成功。反序列化成功。Name:Zhang San; Age:0
可以看到经过反序列化的对象,age的信息变成了Integer的默认值0。
自定义序列化过程
我们可以对序列化的过程进行定制,进行更细粒度的控制。
思路是在业务模型中添加readObject和writeObject方法。下面看一个实例,我们新建一个类型,叫Person3:
1 class Person3 implements Serializable 2 { 3 private String name; 4 private transient int age; 5 public void setName(String name) { 6 this.name = name; 7 } 8 public String getName() { 9 return name;10 }11 public void setAge(int age) {12 this.age = age;13 }14 public int getAge() {15 return age;16 }17 18 public String toString()19 {20 return "Name:" + name + "; Age:" + age;21 }22 23 private void writeObject(ObjectOutputStream os)24 {25 try26 {27 os.defaultWriteObject();28 os.writeObject(this.age);29 System.out.println(this);30 System.out.println("序列化成功。");31 }32 catch(Exception ex)33 {34 ex.printStackTrace();35 }36 }37 38 private void readObject(ObjectInputStream is)39 {40 try41 {42 is.defaultReadObject();43 this.setAge(((Integer)is.readObject()).intValue() - 1);44 System.out.println("反序列化成功。");45 System.out.println(this);46 }47 catch(Exception ex)48 {49 ex.printStackTrace();50 }51 }52 }
请注意观察readObject和writeObject方法,它们都是private的,接受的参数是ObjectStream,然后在方法体内调用了defaultReadObject或者defaultWriteObject方法。
这里age同样是transient的,但是在保存对象的过程中,我们单独对其进行了保存,在读取时,我们将age信息读取出来,并进行了减1处理。
下面是测试方法:
1 private static void serializeTest3() 2 { 3 Person3 person = new Person3(); 4 person.setName("Zhang San"); 5 person.setAge(30); 6 System.out.println(person); 7 try 8 { 9 FileOutputStream fos = new FileOutputStream("d:\\temp\\test\\person3.obj");10 ObjectOutputStream os = new ObjectOutputStream(fos);11 os.writeObject(person);12 fos.close();13 os.close();14 }15 catch(Exception ex)16 {17 ex.printStackTrace();18 }19 }20 21 private static void deserializeTest3()22 { 23 try24 {25 FileInputStream fis = new FileInputStream("d:\\temp\\test\\person3.obj");26 ObjectInputStream is = new ObjectInputStream(fis);27 is.readObject();28 fis.close();29 is.close();30 }31 catch(Exception ex)32 {33 ex.printStackTrace();34 }35 }
输出结果如下:
Name:Zhang San; Age:30序列化成功。反序列化成功。Name:Zhang San; Age:29
可以看到,经过反序列化得到的对象,其age属性已经减1。
探讨serialVersionUID
在上文中,我们描述序列化原理时,曾经提及每个对象都会有一个唯一的序列号,这个序列号,就是serialVersionUID。
当我们的对象实现Serializable接口时,该接口可以为我们生成serialVersionUID。
有两种方式来生成serialVersionUID,一种是固定值:1L,一种是经过JVM计算,不同的JVM采取的计算算法可能不同。
下面就是两个serialVersionUID的示例:
1 private static final long serialVersionUID = 1L;2 private static final long serialVersionUID = -2380764581294638541L;
第一行是采用固定值生成的;第二行是JVM经过计算得出的。
那么serialVersionUID还有其他用途吗?
我们可以使用它来控制版本兼容。如果采用JVM生成的方式,我们可以看到,当我们业务对象的代码保持不变时,多次生成的serialVersionUID也是不变的,当我们对属性进行修改时,重新生成的serialVersionUID会发生变化,当我们对方法进行修改时,serialVersionUID不变。这也从另一个侧面说明,序列化是作用于对象属性上的。
当我们先定义了业务对象,然后对其示例进行了“序列化”,这时根据业务需求,我们修改了业务对象,那么之前“序列化”后的内容还能经过“反序列化”返回到系统中吗?这取决于业务对象是否定义了serialVersionUID,如果定义了,那么是可以返回的,如果没有定义,会抛出异常。
来看下面的示例,定义新的类型Person4:
1 class Person4 implements Serializable 2 { 3 private String name; 4 private int age; 5 public void setName(String name) { 6 this.name = name; 7 } 8 public String getName() { 9 return name;10 }11 public void setAge(int age) {12 this.age = age;13 }14 public int getAge() {15 return age;16 }17 private void xxx(){}18 19 public String toString()20 {21 return "Name:" + name + "; Age:" + age;22 }23 }
然后运行下面的方法:
1 private static void serializeTest4()2 {3 Person4 person = new Person4();4 person.setName("Zhang San");5 person.setAge(30);6 7 writeObject(person, "d:\\temp\\test\\person4.obj");8 }
接下来修改Person4,追加address属性:
1 class Person4 implements Serializable 2 { 3 private String name; 4 private int age; 5 private String address; 6 public void setName(String name) { 7 this.name = name; 8 } 9 public String getName() {10 return name;11 }12 public void setAge(int age) {13 this.age = age;14 }15 public int getAge() {16 return age;17 }18 private void xxx(){}19 20 public String toString()21 {22 return "Name:" + name + "; Age:" + age;23 }24 public void setAddress(String address) {25 this.address = address;26 }27 public String getAddress() {28 return address;29 }30 }
然后运行“反序列化”方法:
1 private static void deserializeTest4()2 { 3 Person4 temp = (Person4)readObject("d:\\temp\\test\\person4.obj");4 5 if (temp != null)6 {7 System.out.println(temp);8 }9 }
可以看到,运行结果如下:
java.io.InvalidClassException: sample.serialization.Person4; local class incompatible: stream classdesc serialVersionUID = -2380764581294638541, local class serialVersionUID = -473458100724786987 at java.io.ObjectStreamClass.initNonProxy(Unknown Source) at java.io.ObjectInputStream.readNonProxyDesc(Unknown Source) at java.io.ObjectInputStream.readClassDesc(Unknown Source) at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source) at java.io.ObjectInputStream.readObject0(Unknown Source) at java.io.ObjectInputStream.readObject(Unknown Source) at sample.serialization.Sample.readObject(Sample.java:158) at sample.serialization.Sample.deserializeTest4(Sample.java:105) at sample.serialization.Sample.main(Sample.java:16)
但是当我们在Person4中添加serialVersionUID后,再次执行上述各步骤,得出的运行结果如下:
反序列化成功。Name:Zhang San; Age:30
有继承结构的序列化
业务对象会产生继承,这在管理系统中是经常看到的,如果我们有下面的业务对象:
1 class Person5 2 { 3 private String name; 4 private int age; 5 public void setName(String name) { 6 this.name = name; 7 } 8 public String getName() { 9 return name;10 }11 public void setAge(int age) {12 this.age = age;13 }14 public int getAge() {15 return age;16 }17 18 public String toString()19 {20 return "Name:" + name + "; Age:" + age;21 }22 23 public Person5(String name, int age)24 {25 this.name = name;26 this.age = age;27 }28 }29 30 class Employee extends Person5 implements Serializable31 {32 public Employee(String name, int age) {33 super(name, age);34 }35 36 private String companyName;37 38 public void setCompanyName(String companyName) {39 this.companyName = companyName;40 }41 42 public String getCompanyName() {43 return companyName;44 }45 46 public String toString()47 {48 return "Name:" + super.getName() + "; Age:" + super.getAge() + "; Company:" + this.companyName;49 }50 }
Employee继承在Person5,Employee实现了Serializable接口,Person5没有实现,那么运行下面的方法:
1 private static void serializeTest5() 2 { 3 Employee emp = new Employee("Zhang San", 30); 4 emp.setCompanyName("XXX"); 5 6 writeObject(emp, "d:\\temp\\test\\employee.obj"); 7 } 8 9 private static void deserializeTest5()10 { 11 Employee temp = (Employee)readObject("d:\\temp\\test\\employee.obj");12 13 if (temp != null)14 {15 System.out.println(temp);16 }17 }
会正常运行吗?事实上不会,它会抛出如下异常:
java.io.InvalidClassException: sample.serialization.Employee; no valid constructor at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(Unknown Source) at java.io.ObjectStreamClass.checkDeserialize(Unknown Source) at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source) at java.io.ObjectInputStream.readObject0(Unknown Source) at java.io.ObjectInputStream.readObject(Unknown Source) at sample.serialization.Sample.readObject(Sample.java:158) at sample.serialization.Sample.deserializeTest5(Sample.java:123) at sample.serialization.Sample.main(Sample.java:18)
原因:在有继承层次的业务对象,进行序列化时,如果父类没有实现Serializable接口,那么父类必须提供默认构造函数。
我们为Person5添加如下默认构造函数:
1 public Person5()2 {3 this.name = "Test";4 this.age = 1;5 }
再次运行上述代码,结果如下:
Name:Zhang San; Age:30; Company:XXX序列化成功。反序列化成功。Name:Test; Age:1; Company:XXX
可以看到,反序列化后的结果,父类中的属性,已经被父类构造函数中的赋值代替了!
因此,我们推荐在有继承层次的业务对象进行序列化时,父类也应该实现Serializable接口。我们对Person5进行修改,使其实现Serializable接口,执行结果如下:
Name:Zhang San; Age:30; Company:XXX序列化成功。反序列化成功。Name:Zhang San; Age:30; Company:XXX
这正是我们期望的结果。
实现序列化的第二种方式为实现接口Externalizable
Externlizable接口继承了java的序列化接口,并增加了两个方法:
void writeExternal(ObjectOutput out) throws IOException;
void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
首先,我们在序列化对象的时候,由于这个类实现了Externalizable 接口,在writeExternal()方法里定义了哪些属性可以序列化,
哪些不可以序列化,所以,对象在经过这里就把规定能被序列化的序列化保存文件,不能序列化的不处理,然后在反序列的时候自动调
用readExternal()方法,根据序列顺序挨个读取进行反序列,并自动封装成对象返回,然后在测试类接收,就完成了反序列。
所以说Exterinable的是Serializable的一个扩展。
为了更好的理解相关内容,请看下面的例子:
package
com.xiaohao.test;
import
java.io.Externalizable;
import
java.io.FileInputStream;
import
java.io.FileNotFoundException;
import
java.io.FileOutputStream;
import
java.io.IOException;
import
java.io.ObjectInput;
import
java.io.ObjectInputStream;
import
java.io.ObjectOutput;
import
java.io.ObjectOutputStream;
import
java.text.SimpleDateFormat;
import
java.util.Date;
/**
* 测试实体类
* @author 小浩
* @创建日期 2015-3-12
*/
class
Person
implements
Externalizable{
private
static
final
long
serialVersionUID = 1L;<br> String userName;
String password;
String age;
public
Person(String userName, String password, String age) {
super
();
this
.userName = userName;
this
.password = password;
this
.age = age;
}
public
Person() {
super
();
}
public
String getAge() {
return
age;
}
public
void
setAge(String age) {
this
.age = age;
}
public
String getUserName() {
return
userName;
}
public
void
setUserName(String userName) {
this
.userName = userName;
}
public
String getPassword() {
return
password;
}
public
void
setPassword(String password) {
this
.password = password;
}
/**
* 序列化操作的扩展类
*/
@Override
public
void
writeExternal(ObjectOutput out)
throws
IOException {
//增加一个新的对象
Date date=
new
Date();
out.writeObject(userName);
out.writeObject(password);
out.writeObject(date);
}
/**
* 反序列化的扩展类
*/
@Override
public
void
readExternal(ObjectInput in)
throws
IOException,
ClassNotFoundException {
//注意这里的接受顺序是有限制的哦,否则的话会出错的
// 例如上面先write的是A对象的话,那么下面先接受的也一定是A对象...
userName=(String) in.readObject();
password=(String) in.readObject();
SimpleDateFormat sdf=
new
SimpleDateFormat(
"yyyy-MM-dd"
);
Date date=(Date)in.readObject();
System.out.println(
"反序列化后的日期为:"
+sdf.format(date));
}
@Override
public
String toString() {
//注意这里的年龄是不会被序列化的,所以在反序列化的时候是读取不到数据的
return
"用户名:"
+userName+
"密 码:"
+password+
"年龄:"
+age;
}
}
/**
* 序列化和反序列化的相关操作类
* @author 小浩
* @创建日期 2015-3-12
*/
class
Operate{
/**
* 序列化方法
* @throws IOException
* @throws FileNotFoundException
*/
public
void
serializable(Person person)
throws
FileNotFoundException, IOException{
ObjectOutputStream outputStream=
new
ObjectOutputStream(
new
FileOutputStream(
"a.txt"
));
outputStream.writeObject(person);
}
/**
* 反序列化的方法
* @throws IOException
* @throws FileNotFoundException
* @throws ClassNotFoundException
*/
public
Person deSerializable()
throws
FileNotFoundException, IOException, ClassNotFoundException{
ObjectInputStream ois=
new
ObjectInputStream(
new
FileInputStream(
"a.txt"
));
return
(Person) ois.readObject();
}
}
/**
* 测试实体主类
* @author 小浩
* @创建日期 2015-3-12
*/
public
class
Test{
public
static
void
main(String[] args)
throws
FileNotFoundException, IOException, ClassNotFoundException {
Operate operate=
new
Operate();
Person person=
new
Person(
"小浩"
,
"123456"
,
"20"
);
System.out.println(
"为序列化之前的相关数据如下:\n"
+person.toString());
operate.serializable(person);
Person newPerson=operate.deSerializable();
System.out.println(
"-------------------------------------------------------"
);
System.out.println(
"序列化之后的相关数据如下:\n"
+newPerson.toString());
}
}
首先,我们在序列化UserInfo对象的时候,由于这个类实现了Externalizable 接口,在writeExternal()方法里定义了哪些属性可
以序列化,哪些不可以序列化,所以,对象在经过这里就把规定能被序列化的序列化保存文件,不能序列化的不处理,然后在反序列
的时候自动调用readExternal()方法,根据序列顺序挨个读取进行反序列,并自动封装成对象返回,然后在测试类接收,就完成了反
序列。
5.4 readResolve()方法
当我们使用Singleton模式时,应该是期望某个类的实例应该是唯一的,但如果该类是可序列化的,那么情况可能略有不同。此时对第2节使用的Person类进行修改,使其实现Singleton模式,如下所示:
- public class Person implements Serializable {
- private static class InstanceHolder {
- private static final Person instatnce = new Person("John", 31, Gender.MALE);
- }
- public static Person getInstance() {
- return InstanceHolder.instatnce;
- }
- private String name = null;
- private Integer age = null;
- private Gender gender = null;
- private Person() {
- System.out.println("none-arg constructor");
- }
- private Person(String name, Integer age, Gender gender) {
- System.out.println("arg constructor");
- this.name = name;
- this.age = age;
- this.gender = gender;
- }
- ...
- }
同时要修改SimpleSerial应用,使得能够保存/获取上述单例对象,并进行对象相等性比较,如下代码所示:
- public class SimpleSerial {
- public static void main(String[] args) throws Exception {
- File file = new File("person.out");
- ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream(file));
- oout.writeObject(Person.getInstance()); // 保存单例对象
- oout.close();
- ObjectInputStream oin = new ObjectInputStream(new FileInputStream(file));
- Object newPerson = oin.readObject();
- oin.close();
- System.out.println(newPerson);
- System.out.println(Person.getInstance() == newPerson); // 将获取的对象与Person类中的单例对象进行相等性比较
- }
- }
执行上述应用程序后会得到如下结果:
- arg constructor
- [John, 31, MALE]
- false
值得注意的是,从文件person.out中获取的Person对象与Person类中的单例对象并不相等。为了能在序列化过程仍能保持单例的特性,可以在Person类中添加一个readResolve()方法,在该方法中直接返回Person的单例对象,如下所示:
- public class Person implements Serializable {
- private static class InstanceHolder {
- private static final Person instatnce = new Person("John", 31, Gender.MALE);
- }
- public static Person getInstance() {
- return InstanceHolder.instatnce;
- }
- private String name = null;
- private Integer age = null;
- private Gender gender = null;
- private Person() {
- System.out.println("none-arg constructor");
- }
- private Person(String name, Integer age, Gender gender) {
- System.out.println("arg constructor");
- this.name = name;
- this.age = age;
- this.gender = gender;
- }
- private Object readResolve() throws ObjectStreamException {
- return InstanceHolder.instatnce;
- }
- ...
- }
再次执行本节的SimpleSerial应用后将如下输出:
- arg constructor
- [John, 31, MALE]
- true
无论是实现Serializable接口,或是Externalizable接口,当从I/O流中读取对象时,readResolve()方法都会被调用到。实际上就是用readResolve()中返回的对象直接替换在反序列化过程中创建的对象。
- Serializable java序列化
- Java对象序列化
- java序列化-Serializable
- Serializable java序列化
- Serializable java序列化
- Java对象序列化
- Java对象序列化
- Java对象序列化
- Serializable java序列化
- JAVA序列化Serializable
- java对象序列化
- Java 对象序列化
- DEMO-JAVA序列化
- Java 对象序列化
- java 序列化
- Java对象序列化
- Serializable java序列化
- java序列化介绍
- 231. Power of Two
- 输出字符的ASCII码总是多个10
- Marxico
- JAVA多线程编程——JAVA内存模型
- ubantu中安装jenkins
- java序列化
- 激活Django自动管理
- unity5.3+Easytouch4.3——EasyTouch及摇杆控件介绍
- 没有钱该如何做好新产品的网络营销推广
- 离散化(Matrix67)
- mysql不允许远程链接
- ra-02391:exceeded simultaneous sessions_per_user limit
- 前序遍历,中序遍历和后序遍历 & 已知两种遍历,求第三种遍历
- linux 下安装anaconda