java对象序列化

 java对象序列化


 java对象序列化概述


  序列化的过程就是对象写入字节流和从字节流中读取对象。


  将对象状态转换成字节流之后,可以用java.io包中的各种字节流类将其保存在文件中,管道到另一线程中或通过网络连接将对象数据发送到另一主机。


  对象序列化功能可应用在RMI/Socket/JMS/EJB


  简化了对象持久化(Persistence)的实现


  XML序列化指的是把对象转换成XML数据,传输到目的地后再把XML数据反序列化为对象。


对象序列化实用意义


  对象序列化可以实现分布式对象。RMI要利用对象序列化运行远程主机上的服务,就像在本地机上运行对象时一样。


  java对象序列化不仅保留一个对象的数据,而且递归保存对象引用的每个对象的数据。可以将整个对象层次写入字节流中,可以保存在文件中或在网络连接上传递。利用对象序列化可以进行对象的“深复制”,即复制对象本身及引用的对象本身。序列化一个对象可能得到整个对象序列。


java序列化的实现


  java序列化比较简单,实现Serializable接口的类对象可以转换成字节流或从字节流恢复,不需要在类中增加任何代码。只有极少数情况下才需要定制代码保存或恢复对象状态。


  Serializable是标志性接口,不具有方法。注意:不是每个类都可以序列化,有些类是不能序列化的,例如涉及线程的类与特定JVM有非常复杂的关系。


  java库中的类,如String,都实现了Serializable接口,所以他们都可进行serialization操作。


  对一个对象进行Serialize操作时,不仅会把对象在内存中的数据保存下来,还会把对象中所包含的可serialize成员对象也保存下来。如果对象中包含了没有实现Serializable接口的成员对象,那将在尝试对对象进行Serialize操作时,将发生错误。


  对一个Serializable对象进行次第读取时,并不会调用任何构造函数。这是因为对象中的所有数据都是通过InputStream读取的数据来恢复的,所有不用通过构造函数来进行初始化。


  进行序列读取时,在执行读取操作的class 中一定要能找到相应的class文件。


处理对象流


  java.io包有两个序列化对象的类。


   A. ObjectOutputStream


     将对象写入字节流


     存储对象writeObject()


   B. ObjectInputStream


     从字节流重构对象


     恢复对象readObject()


ObjectOutputStream类


  ObjectOutputStream扩展DataOutput接口writeObject()方法用语对象序列化。如果对象包含其它对象的引用,则writeObject()方法递归序列化这些对象,每个ObjectOutputStream维护序列化的对象引用表,防止发送同一对象的多个拷贝。由于writeObject()可以序列化整组交叉引用的对象,因此同一ObjectOutputStream实例可能不小心被请求序列化同一对象。这时,进行反引用序列化,而不是再次写入对象字节流。
注:经证明,如果一个对象包含两个引用,但引用的是同一个对象,那么确实不会发送两份。如果没有显式指定serialVersionUID,就算添加一个静态变量,也会造成对象在序列化时生成不同的serialVersionUID值,这个值是会被序列化的哦!为了更好地控制序列化,我们最好还是显式指定serialVersionUID。






序列化today's date到一个文件中。


FileOutputStream f = new FileOutputStream("tmp");
ObjectOutputStream s = new ObjectOutputStream(f);
s.writeObject("Today");
s.writeObject(new Date());
s.flush(); //flush()在方法返回前显示清空输出缓冲区


ObjectInputStream类


现在,让我们来了解ObjectInputStream这个类。它与ObjectOutputStream相似。它扩展DataInput接口。ObjectInputStream中的方法镜像DataInputStream中读取Java基本数据类型的公开方法。readObject()方法从字节流中反序列化对象。每次调用readObject()方法都返回流中下一个Object。对象字节流并不传输类的字节码,而是包括类名及其签名。readObject()收到对象时,JVM装入头中指定的类。如果找不到这个类,则readObject()抛出ClassNotFoundException,如果需要传输对象数据和字节码,则可以用RMI框架。ObjectInputStream的其余方法用于定制反序列化过程。
例子如下:


//从文件中反序列化 string 对象和 date 对象    
FileInputStream in = new FileInputStream("tmp");    
ObjectInputStream s = new ObjectInputStream(in);    
String today = (String)s.readObject();
Date date = (Date)s.readObject();




定制序列化过程:


序列化通常可以自动完成,但有时可能要对这个过程进行控制。java可以将类声明为Serializable,但仍可手工控制声明为static或transient的数据成员。
例子:一个非常简单的序列化类。


public class simpleSerializableClass implements Serializable{ 
    String sToday="Today:";
    transient Date dtToday=new Date();
}


序列化时,类的所有数据成员应可序列化除了声明为transient或static的成员。将变量声明为transient告诉JVM我们会负责将变元序列化。将数据成员声明为transient后,序列化过程就无法将其加进对象字节流中,没有从transient数据成员发送的数据。后面数据反序列化时,要重建数据成员(因为它是类定义的一部分),但不包含任何数据,因为这个数据成员不向流中写入任何数据。记住,对象流不序列化static或transient。我们的类要用writeObject()与readObject()方法以处理这些数据成员。使用writeObject()与readObject()方法时,还要注意按写入的顺序读取这些数据成员。
关于如何使用定制序列化的部分代码如下:


//重写writeObject()方法以便处理transient的成员。
private void writeObject(ObjectOutputStream outputStream) throws IOException{        
    outputStream.defaultWriteObject();   //使定制的writeObject()方法可以利用自动序列化中内置的逻辑。
    outputStream.writeObject(oSocket.getInetAddress());
    outputStream.writeInt(oSocket.getPort());
}
//重写readObject()方法以便接收transient的成员。
private void readObject(ObjectInputStream inputStream) throws IOException,ClassNotFoundException{
    inputStream.defaultReadObject();   //defaultReadObject()补充自动序列化 
    InetAddress oAddress=(InetAddress)inputStream.readObject();
    int iPort =inputStream.readInt();
    oSocket = new Socket(oAddress,iPort);
}




完全定制序列化过程:


如果一个类要完全负责自己的序列化,则实现Externalizable接口而不是Serializable接口。Externalizable接口定义包括两个方法writeExternal()与readExternal()。利用这些方法可以控制对象数据成员如何写入字节流。类实现Externalizable时,头写入对象流中,然后类完全负责序列化和恢复数据成员,除了头以外,根本没有自动序列化。这里要注意了。声明类实现Externalizable接口会有重大的安全风险。writeExternal()与readExternal()方法声明为public,恶意类可以用这些方法读取和写入对象数据。如果对象包含敏感信息,则要格外小心。这包括使用安全套接或加密整个字节流。到此为至,我们学习了序列化的基础部分知识。关于序列化的高级教程,以后再述。


http://blog.csdn.net/passren/archive/2004/06/30/30421.aspx
http://blog.csdn.net/luofengjava/archive/2007/01/05/1475282.aspx



import java.io.*;
class Person implements Serializable {


private static final long serialVersionUID = 0L;

String name;
String age;


public Person() {
}


public Person(String name, String age) {
this.name = name;
this.age = age;
}


public void writeObject(ObjectOutputStream out) throws IOException {
out.writeObject(name);
out.writeObject(age);
}


public void readObject(ObjectInputStream in) throws IOException,
ClassNotFoundException {
name = (String) in.readObject();
age = (String) in.readObject();
}


public String toString() {
return "name: " + this.name + "age: " + this.age;
}
}


import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;


public class Test {
Person p1 = new Person("java01", "15");
Person p2 = new Person("java02", "30");
Person p = new Person();


public void write() {
try {
File f = new File("a.txt");
FileOutputStream fos = new FileOutputStream(f);
ObjectOutputStream os = new ObjectOutputStream(fos);
p1.writeObject(os);
p2.writeObject(os);
os.close();
fos.close();


} catch (Exception e) {
e.printStackTrace();
}
}


public void read() {


try {


File f = new File("a.txt");
FileInputStream fis = new FileInputStream(f);
ObjectInputStream os = new ObjectInputStream(fis);


while (fis.available() > 0) {
p.readObject(os);
System.out.println(p);
}
os.close();
fis.close();
} catch (Exception e) {
e.printStackTrace();
}


}


public static void main(String[] args) {
Test t = new Test();
t.write();
t.read();


}
}
这是一个从网上抄的代码,实际上,它不是正规的用法,如果这样完全就和MFC中的方式一样了,此时,如果你想保存Person类本身的信息,你就没有方便的办法了。实际上Person不从Serializable继承都没有任何问题,呵呵。还有就是调用defaultWriteObject时会抛NotActiveException,虽然看起来是在writeObject中调用的defaultWriteObject,但实际上是假的,因为writeObject可以取任意名字。


正规的做法writeObject和readObject是私有的,调用时仍然是oos.writeObject(myclass);而不是myclass.writeObject(oos);
具体参考下面这篇文章:


在Java中使用Serialization相当简单。如果你有一些对象想要进行序列化,你只需实现Serializable接口。然后,你可以使用 ObjectOutputStream将该对象保存至文件或发送到其他主机。所有的non-transient和non-static字段都将被序列化,并且由反序列化重构造出一模一样的对象联系图(譬如许多引用都指向该对象)。但有时你可能想实现你自己的对象序列化和反序列化。那么你可以在某些特定情形下得到更多的控制。来看下面的简单例子。
Java代码


   1. class SessionDTO implements Serializable {  
   2.     private static final long serialVersionUID = 1L;  
   3.     private int data; // Stores session data  
   4.   
   5.     // Session activation time (creation, deserialization)  
   6.     private long activationTime;   
   7.   
   8.     public SessionDTO(int data) {  
   9.         this.data = data;  
  10.         this.activationTime = System.currentTimeMillis();  
  11.     }  
  12.   
  13.     public int getData() {  
  14.         return data;  
  15.     }  
  16.   
  17.     public long getActivationTime() {  
  18.         return activationTime;  
  19.     }  
  20. }  


class SessionDTO implements Serializable {
    private static final long serialVersionUID = 1L;
    private int data; // Stores session data


    // Session activation time (creation, deserialization)
    private long activationTime; 


    public SessionDTO(int data) {
        this.data = data;
        this.activationTime = System.currentTimeMillis();
    }


    public int getData() {
        return data;
    }


    public long getActivationTime() {
        return activationTime;
    }
}




以下是序列化上述class到文件和其反序列化的主函数。
Java代码


   1. public class SerializeTester implements Serializable {  
   2.     public static void main(String... strings) throws Exception {  
   3.         File file = new File("out.ser");  
   4.   
   5.         ObjectOutputStream oos = new ObjectOutputStream(  
   6.             new FileOutputStream(file));  
   7.         SessionDTO dto = new SessionDTO(1);  
   8.         oos.writeObject(dto);  
   9.         oos.close();  
  10.   
  11.         ObjectInputStream ois = new ObjectInputStream(  
  12.             new FileInputStream(file));  
  13.         SessionDTO dto = (SessionDTO) ois.readObject();  
  14.   
  15.         System.out.println("data : " + dto.getData()  
  16.             + " activation time : " + dto.getActivationTime());  
  17.         ois.close();  
  18.     }  
  19. }  


public class SerializeTester implements Serializable {
    public static void main(String... strings) throws Exception {
        File file = new File("out.ser");


        ObjectOutputStream oos = new ObjectOutputStream(
            new FileOutputStream(file));
        SessionDTO dto = new SessionDTO(1);
        oos.writeObject(dto);
        oos.close();


        ObjectInputStream ois = new ObjectInputStream(
            new FileInputStream(file));
        SessionDTO dto = (SessionDTO) ois.readObject();


        System.out.println("data : " + dto.getData()
            + " activation time : " + dto.getActivationTime());
        ois.close();
    }
}




类SessionDTO展现的是要在两个服务器之间传输的session。它包含了一些信息在字段data上,该字段需要被序列化。但是它还有另外一个字段activationTime,该字段应该是session对象第一次出现在任意服务器上的时间。它不在我们想要传输的信息之列。这个字段应该在反序列化之后在赋值。进一步来说,没必要把它放在stream中在服务器中传递,因为它占据了不必要的空间。


解决这种情况可以使用writeObject和readObject。有可能你们有一些人没有听说过它们,那是因为它们在许多Java书籍中给忽略了,而且它们们也不是众多流行Java考试的一部分。让我们用这些方法来重写SessionDTO:
Java代码


   1. class SessionDTO implements Serializable {  
   2.     private static final long serialVersionUID = 1L;  
   3.     private transient int data; // Stores session data  
   4.   
   5.     //Session activation time (creation, deserialization)  
   6.     private transient long activationTime;   
   7.   
   8.     public SessionDTO(int data) {  
   9.         this.data = data;  
  10.         this.activationTime = System.currentTimeMillis();  
  11.     }  
  12.   
  13.     private void writeObject(ObjectOutputStream oos) throws IOException {  
  14.         oos.defaultWriteObject();  
  15.         oos.writeInt(data);  
  16.         System.out.println("session serialized");  
  17.     }  
  18.   
  19.     private void readObject(ObjectInputStream ois) throws IOException,  
  20.             ClassNotFoundException {  
  21.         ois.defaultReadObject();  
  22.         data = ois.readInt();  
  23.         activationTime = System.currentTimeMillis();  
  24.         System.out.println("session deserialized");  
  25.     }  
  26.   
  27.     public int getData() {  
  28.         return data;  
  29.     }  
  30.   
  31.     public long getActivationTime() {  
  32.         return activationTime;  
  33.     }  
  34. }  


class SessionDTO implements Serializable {
    private static final long serialVersionUID = 1L;
    private transient int data; // Stores session data


    //Session activation time (creation, deserialization)
    private transient long activationTime; 


    public SessionDTO(int data) {
        this.data = data;
        this.activationTime = System.currentTimeMillis();
    }


    private void writeObject(ObjectOutputStream oos) throws IOException {
        oos.defaultWriteObject();
        oos.writeInt(data);
        System.out.println("session serialized");
    }


    private void readObject(ObjectInputStream ois) throws IOException,
            ClassNotFoundException {
        ois.defaultReadObject();
        data = ois.readInt();
        activationTime = System.currentTimeMillis();
        System.out.println("session deserialized");
    }


    public int getData() {
        return data;
    }


    public long getActivationTime() {
        return activationTime;
    }
}




方法writeObject处理对象的序列化。如果声明该方法,它将会被ObjectOutputStream调用而不是默认的序列化进程。如果你是第一次看见它,你会很惊奇尽管它们被外部类调用但事实上这是两个private的方法。并且它们既不存在于java.lang.Object,也没有在Serializable中声明。那么ObjectOutputStream如何使用它们的呢?这个吗,ObjectOutputStream使用了反射来寻找是否声明了这两个方法。因为ObjectOutputStream使用getPrivateMethod,所以这些方法不得不被声明为 priate以至于供ObjectOutputStream来使用。


在两个方法的开始处,你会发现调用了defaultWriteObject()和defaultReadObject()。它们做的是默认的序列化进程,就像写/读所有的non-transient和 non-static字段(但他们不会去做serialVersionUID的检查).通常说来,所有我们想要自己处理的字段都应该声明为 transient。这样的话,defaultWriteObject/defaultReadObject便可以专注于其余字段,而我们则可为这些特定的字段(译者:指transient)定制序列化。使用那两个默认的方法并不是强制的,而是给予了处理复杂应用时更多的灵活性。


你可以从这里或这里读到更多有关于序列化的知识。


自己再补充一些:


1.Write的顺序和read的顺序需要对应,譬如有多个字段都用wirteInt一一写入流中,那么readInt需要按照顺序将其赋值;


2.Externalizable,该接口是继承于Serializable ,所以实现序列化有两种方式。区别在于Externalizable多声明了两个方法readExternal和writeExternal,子类必须实现二者。Serializable是内建支持的也就是直接implement即可,但Externalizable的实现类必须提供 readExternal和writeExternal实现。对于Serializable来说,Java自己建立对象图和字段进行对象序列化,可能会占用更多空间。而Externalizable则完全需要程序员自己控制如何写/读,麻烦但可以有效控制序列化的存储的内容。
注: 经验证,如果继承Externalizable后,虽然相当于也继承了Serializable,但是就算类实现writeObject和readObject,oos.writeObject(myclass);时也不会调用了,仅仅是把类的基本信息序列化,而所有的成员都不会被序列化,需要我们自己动手了。


3.正如Effectvie Java中提到的,序列化就如同另外一个构造函数,只不过是有由stream进行创建的。如果字段有一些条件限制的,特别是非可变的类定义了可变的字段会反序列化可能会有问题。可以在readObject方法中添加条件限制,也可以在readResolve中做。参考56条“保护性的编写 readObject”和“提供一个readResolve方法”。


4.当有非常复杂的对象需要提供deep clone时,可以考虑将其声明为可序列化,不过缺点也显而易见,性能开销。






综上,虽然Java中的序列化道理和MFC中差不多,但是它封装得更多一些,至少,我们任何时候都不用取调用对象的writeObject和readObject函数。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值