1、序列化
Java 提供了一种对象序列化的机制,该机制中,一个对象可以被表示为一个字节序列,该字节序列包括该对象的数据、有关对象的类型的信息和存储在对象中数据的类型。
将序列化对象写入文件之后,可以从文件中读取出来,并且对它进行反序列化,也就是说,对象的类型信息、对象的数据,还有对象中的数据类型可以用来在内存中新建对象。
整个过程都是 Java 虚拟机(JVM)独立的,也就是说,在一个平台上序列化的对象可以在另一个完全不同的平台上反序列化该对象。
类 ObjectInputStream 和 ObjectOutputStream 是高层次的数据流,它们包含反序列化和序列化对象的方法。
ObjectOutputStream负责将对象写入字节流,ObjectInputStream从字节流重构对象。
ObjectOutputStream类扩展DataOutput接口。writeObject()方法是最重要的方法,用于对象序列化。如果对象包含其他对象的引用,则writeObject()方法递归序列化这些对象。每个ObjectOutputStream维护序列化的对象引用表,防止发送同一对象的多个拷贝。(这点很重要)由于writeObject()可以序列化整组交叉引用的对象,因此同一ObjectOutputStream实例可能不小心被请求序列化同一对象。这时,进行反引用序列化,而不是再次写入对象字节流。
ObjectInputStream类与ObjectOutputStream相似。它扩展DataInput接口。ObjectInputStream中的方法镜像DataInputStream中读取Java基本数据类型的公开方法。readObject()方法从字节流中反序列化对象。每次调用readObject()方法都返回流中下一个Object。对象字节流并不传输类的字节码,而是包括类名及其签名。readObject()收到对象时,JVM装入头中指定的类。如果找不到这个类,则readObject()抛出ClassNotFoundException,如果需要传输对象数据和字节码,则可以用RMI框架。ObjectInputStream的其余方法用于定制反序列化过程。
一个栗子:
FileOutputStream f=new FileOutputStream("tep");
ObjectOutputStream s=new ObjectOutputStream(f);
s.writeObject("today"); s.writeObject(new Date());
s.flush();
FileInputStream in=new FileInputStream("tep");
ObjectInputStream ss=new ObjectInputStream(in);
String today=(String)ss.readObject();
Date date= (Date) ss.readObject();
System.out.println(today+" "+date);
输出:
2、一个类的对象要想序列化成功,必须满足两个条件:
该类必须实现 java.io.Serializable 接口。
该类的所有属性必须是可序列化的。如果有一个属性不是可序列化的,则该属性必须注明是短暂的。
如果你想知道一个 Java 标准类是否是可序列化的,请查看该类的文档。检验一个类的实例是否能序列化十分简单, 只需要查看该类有没有实现 java.io.Serializable接口。
3、定制序列化
序列化通常可以自动完成,但有时可能要对这个过程进行控制。java可以将类声明为serializable,但仍可手工控制声明为static或transient的数据成员。
序列化时,类的所有数据成员应可序列化除了声明为transient或static的成员。将变量声明为transient告诉JVM我们会负责将变元序列化。将数据成员声明为transient后,序列化过程就无法将其加进对象字节流中,没有从transient数据成员发送的数据。后面数据反序列化时,要重建数据成员(因为它是类定义的一部分),但不包含任何数据,因为这个数据成员不向流中写入任何数据。记住,对象流不序列化static或transient。我们的类要用writeObject()与readObject()方法以处理这些数据成员。使用writeObject()与readObject()方法时,还要注意按写入的顺序读取这些数据成员。
完全定制序列化过程:
如果一个类要完全 负责自己的序列化,则实现Externalizable接口而不是Serializable接口。 Externalizable接口定义包括两个方法writeExternal()与readExternal()。利用这些方法可以控制对象数据成员如何写入字节流.类实现Externalizable时,头写入对象流中,然后类完全负责序列化和恢复数据成员,除了头以外,根本没有自动序列化。这里要注意了 。声明类实现Externalizable接口会有重大的安全风险。writeExternal()与readExternal()方法声明为public,恶意类可以用这些方法读取和写入对象数据 。如果对象包含敏感信息,则要格外小心。这包括使用安全套接或加密整个字节流。
4、持久化与序列化
1、持久 化(Persistence),即把 数据(如内存中的对象)保存到可永久保存的存储设备中(如磁盘)。持久化的主要应用是将内存中的对象存储在关系型的数据库中,当然也可以存储在磁盘文件中、XML数据文件中等等。
持久化是将程序数据在持久状态和瞬时状态间转换的机制。
JDBC就是一种持久化机制。文件IO也是一种持久化机制。
持久化是一种对象服务,就是把内存中的对象保存到外存中,让以后能够取回。需要实现至少3个接口:
void Save(object o) 把一个对象保存到外存中
Object Load(object oid) 通过对象标识从外存中取回对象
bool Exists(object oid) 检查外存中是否存在某个对象
2、区别:
持久化(Persistence)与序列化(Serialization)相似的地方都是企图将对象和其他东西进行转化,不同之处在于持久化是希望能把它持久保存起来并在需要的时候再得到而序列化关心的是如何把对象变为字节流,实质上从实用角度上来讲,两者有很大程度的相互覆盖,序列化机制是将类的值转化为一个一般的(即连续的)字节流,然后将该流写到磁盘文件或任何其他流化目标上的一个过程,这本身也可以称之为持久化。当然所谓反序列化,顾名思义就是将序列化过程颠倒过来,由流目标得到对象的过程了。
5、关键字static和transient
被transient关键字修饰导致不被序列化,其优点是可以节省存储空间。优化程序!随之而来的是会导致被transient修饰的字段会重新计算,初始化!
1、变量被transient修饰,变量将不会被序列化
2、transient关键字只能修饰变量,而不能修饰方法和类。
3、被static关键字修饰的变量不参与序列化,一个静态static变量不管是否被transient修饰,均不能被序列化。
4、final变量值参与序列化,final transient同时修饰变量,final不会影响transient,一样不会参与序列化
5、本地变量是不能被transient关键字修饰的。变量如果是用户自定义类变量,则该类需要实现Serializable接口
6、反序列化后类中static型变量的值实际上是当前JVM中对应static变量的值,这个值是JVM中的并不是反序列化得出的。
被transient修饰的变量经过序列化和反序列化后会变成null或0,transient修饰的变量不会被序列化,因此该变量经过反序列化之后会变成原始值。
被static修饰的变量经过序列化之后的值虽然没有变化,但是该变量却并没有经过序列化,因为static修饰的成员属性优于非静态成员属性加载到内存中,同时静态也优于对象进入到内存中,序列化的都是对象,静态变量不属于对象的一部分,因此它并不参与序列化,反序列化之后的类中的static型变量的值实际上是当前JVM中对应的static变量的值,这个值是JVM中的并不是反序列化中得出的。(在对象被序列化之后修改静态变量的值,再次反序列化之后会发现该静态变量的值更改了。)
使用transient:
public class UserInfo implements Serializable {
private String name;
private transient String password;
public UserInfo(String name, String password) {
this.name = name;
this.password = password;
}
@Override
public String toString() {
return "UserInfo{" +
"name='" + name + '\'' +
", password='" + password + '\'' +
'}';
}
}
public class test2 {
public static void main(String[] args) throws ClassNotFoundException, IOException {
UserInfo userInfo=new UserInfo("小明","123");
System.out.println("序列化之前的信息:"+userInfo);
//序列化
ObjectOutputStream outputStream=new ObjectOutputStream(new FileOutputStream("user"));
outputStream.writeObject(userInfo);
outputStream.close();
//反序列化
ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("user"));
System.out.println("序列化之后的信息:"+inputStream.readObject());
}
}
输出:
使用static:
public class UserInfo implements Serializable {
private static String name;
private String password;
public UserInfo(String name, String password) {
this.name = name;
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "UserInfo{" +
"name='" + name + '\'' +
", password='" + password + '\'' +
'}';
}
}
public class test2 {
public static void main(String[] args) throws ClassNotFoundException, IOException {
UserInfo userInfo=new UserInfo("小明","123");
System.out.println("序列化之前的信息:"+userInfo);
//序列化
ObjectOutputStream outputStream=new ObjectOutputStream(new FileOutputStream("user"));
outputStream.writeObject(userInfo);
outputStream.close();
userInfo.setName("小红"); //更改被static修饰的变量的值
//反序列化
ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("user"));
System.out.println("序列化之后的信息:"+inputStream.readObject());
}
}
输出: