java 对象序列化(ObjectInputstream)

IO中对对象进行序列化的流为ObjectInputstream,一般情况下,只能对基本的数据类型进行序列化。想自定义序列化自定义的对象,只能借助:Serializable,Externalizable进行完成。下面进行介绍。

序列化接口与android对比

android中使用的是:Parcelable和Serializable
具体参考:Android系统中Parcelable和Serializable的区别
而在java中,则使用的是:Externalizable接口与Serializable接口,当然在android可以使用Externalizable,但不知道为什么网上很少讲。下面看看Serializable和Externalizable的区别。

对象序列化(ObjectInputstream

将java对象转成与平台无关的二进制流,再保存磁盘或者进行网络传输。序列化进行传输,反序列化进行恢复java对象

两个接口:(对象序列化必须实现的接口)

  • Serializable
  • Externalizable

关键使用代码:

1
2
3
4
5
6
7
# 读取
ObjectInputStream ois=new ...;
Person p=(Person)ois.readObject;

# 写入
...
oos.writeObject(new Person);

注意:父类如果没有实现接口,则必须为无惨的构造函数

对象引用的序列化

当一个类中的元素存在其他类的引用时,此引用也必须实现接口,可序列化。

序列化算法

序列化的时候会检测该对象是否已序列化(检测序列化编号),如果存在则返回编号,不存在则创建。
如果改变对象的field,再进行write。最后结果将无法显示已改变的field

1
person per=new Person("孙",500);
oos.writeObject(per);
per.setName("珠");
oos.writeObject(per);
Person p1=ois.readObject();
Person p2=ois.readObject();
System.out.println(p1=p2);//将返回true;
System.out.println(p2.getName());//将输出"孙"

Serializable进行自定义序列化

一般情况下,直接implement Serializable后,我们就可以序列化了,但是为了提高安全性,我们进行自定义序列化对将要序列化的对象进行加密

自定义三个方法:

  • writeObject(ObjectOutstream)
  • readObject(ObjectInpustream)
  • readObjectNoData() 初始化序列化对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Person implements Serializable {
private String name;
private int age;
...

public void writeObject(ObjectOutputStream oos){
try {
oos.writeObject(new StringBuffer(name).reverse());//进行翻转
oos.writeInt(age);
} catch (IOException e) {
e.printStackTrace();
}
}
public void readObject(ObjectInputStream ois){
...//进行对应的反转
}

通过这样的方式,如上面的反转来加密字符串,提高序列化的安全性

序列化对象时将该对象替换成其他对象
Person类添加下列方法:

1
2
3
4
5
6
private Object wirteReplace(){
ArrayList<Object> list=new ArrayList<>();
list.add(name);
list.add(age);
return list;
}

java在序列化对象之前,会先调用改对象的writeReplace()方法,如果该方法返回另一个对象,那么系统为序列化另一个对象。

1
2
3
Person person = new Person("sun", 100);
oos.writeObject(person);
ArrayList<Object> list = (ArrayList) ois.readObject();

在序列化某个对象之前,先调用writeReplace(),再调用writeObject(),所以,能够转化为另一个对象。

保护复制整个对象的方法readResolve()方法。
这个方法在readObject之后调用。该方法的返回值将代替原来反序列化的对象

在java5前的枚举类(Orientation),使用序列化前后,将产生两个对象,即对象不匹配。使用readResolve()将可以解决。

1
2
3
4
5
6
7
8
9
10
11
12
13
//为枚举类增加readResolve()方法
private Object readResolve()throws ObjectStreamException
{

if (value == 1)
{
return HORIZONTAL;
}
if (value == 2)
{
return VERTICAL;
}
return null;
}

注意:
所有的单例和枚举类在实现序列化时应该提供readResolve()方法,这样才可保证反序列化正常。
缺点:需要总是加上readResolve方法,不可继承,否则得到的是父类的对象。

Externalizable简介

Defines an interface for classes that want to be serializable, but have their own binary representation.

结合源码知道,Externalizableimplement Serializable接口而来的。

Externalizable需要实现方法的模板代码

实现Externalizable接口需要实现两个方法:

  • readExternal(ObjectInput oi)
  • writeExternal(ObjectOutput oo)

这两个方法里面的实现方式和上面的writeObjectreadObject方法差不多,进行反转加密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
public void writeExternal(ObjectOutput out) throws IOException {
try {
out.writeObject(new StringBuffer(name).reverse());//对字符串进行翻转
out.writeInt(age);
} catch (IOException e) {
e.printStackTrace();
}
}

@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
try { this.name=((StringBuffer)in.readObject()).reverse().toString();
this.age=in.readInt();
} catch (Exception e) {
e.printStackTrace();
}
}

Externalizable接口与Serializable接口实现序列化的区别

序 号 区 别 Serializable Externalizable
1 实现复杂度 实现简单,Java对其有内建支持 实现复杂,由开发人员自己完成
2 执行效率 所有对象由Java统一保存,性能较低 开发人员决定哪个对象保存,可能造成速度提升
3 保存信息 保存时占用空间大 部分存储,可能造成空间减少

参考:Java中的Externalizable接口

扩展:不被序列化的情况

  • 使用transient修饰属性后,将不被序列化
    1
    2
    3
    4
    5
    6
    private transient int age;
    ...
    Person per=new Person("sun",500);
    oos.writeObject(per);
    Person p=(Person)ois.readObject();
    System.out.println(p.getAge());//将输出0

注意:此关键字只能跟Serializable接口搭配使用,否则作用会失效。
参考:# Java序列化——transient关键字和Externalizable接口

  • 和被static修饰属性,也不可被序列化

  • 实现Externalizable接口在需要的实现的两个方法中,指定一些属性不进行序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 部分代码
//Non-Default constructor
public LoginInfo2(String username, String password) {
this.userName = username;
this.password = password;
loginDate = new Date();
}
public String toString() {
return "UserName=" + userName + ", Password="
+ password + ", LoginDate=" + loginDate;
}

@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(loginDate);
out.writeUTF(userName);
}

@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
loginDate = (Date)in.readObject();
userName = (String)in.readUTF();
}

此时的Password不被序列化输出。

注意版本的兼容性

版本的兼容:

1
private static final long serialVersionUID=512L;