Java学习之序列化

序列化

Java提供了一种对象序列化的机制,该机制中,一个对象可以被表示为一个字节序列,该字节序列包括该对象的数据、有关对象的类型的信息和存储在对象中的数据的类型。

序列化存储数据的优点

序列化的文件更容易让程序回复到原来的状态,同时序列化的文件比较安全,因为它不是原本的内容,而是经过了编码处理的。序列化的对象方便在JVM之间转移,这些JVM可以是同一台计算机的,也可以是远程的。

使用序列化的场合

如果需要该对象在网上传送,或保存在磁盘上,以便以后再某个时刻再访问,这种情况便可将其序列化。如果对象的内部状态不适合输出到二级存储设备(硬盘)或通过网络传送,就需要谨慎使用序列化。使用对象序列化是为了支持两种特性:Java的远程方法调用和Java Bean。

Java中序列化的实现

Java中实现序列化和反序列化的类分别是ObjectOutputStream和ObjectInputStream。

ObjectOutputStream序列化方法:

public final void writeObject(Object x) throws IOException

ObjectInputStream反序列方法:

public final Object readObject() throws IOException,ClassNotFoundException

该方法从流中取出下一个对象,并将对象反序列化。它的返回值是Object,因此,你需要将它转换成合适的数据类型。

代码举例:

Employee类

package JavaReview.FileIO;

import java.io.Serializable;

public class Employee implements Serializable {
    public String name;
    public String address;
    public transient int SSN;
    public int number;
    public Task ta;

    public Employee(String name, String address, int SSN, int number, Task ta) {
        this.name = name;
        this.address = address;
        this.SSN = SSN;
        this.number = number;
        this.ta = new Task(ta);
    }

    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                ", SSN=" + SSN +
                ", number=" + number +
                ", ta=" + ta.toString() +
                '}';
    }
}

class Task implements Serializable{
    public String taskName;
    public String taskContent;

    public Task(Task ta) {
        this.taskName = ta.taskName;
        this.taskContent = ta.taskContent;
    }

    public Task(String taskName,String taskContent ){
        this.taskContent=taskContent;
        this.taskName=taskName;
    }
    @Override
    public String toString() {
        return "Task{" +
                "taskName='" + taskName + '\'' +
                ", taskContent='" + taskContent + '\'' +
                '}';
    }
}


实现序列化和反序列化

package JavaReview.FileIO;

import java.io.*;


public class SerializeDemo {
    public static void main(String[] args) {
        Task ta=new Task("项目XXX","完成XXX");
        Employee ee=new Employee("张三","中国北京市",12312,111,ta);
        Employee ee2=new Employee("李四","中国上海市",122222,222,ta);
        
        try {
        
           //开始序列化对象ee,ee2
            FileOutputStream outfileStream=new FileOutputStream("example.ser");
            ObjectOutputStream os=new ObjectOutputStream(outfileStream);
            os.writeObject(ee);
            os.writeObject(ee2);
            os.close();
            outfileStream.close();
            System.out.println("写入文件成功");

			//开始反序列化对象ee,ee2
            FileInputStream infileStream=new FileInputStream("example.ser");
            ObjectInputStream is=new ObjectInputStream(infileStream);
            Employee e=null;
            Employee e2=null;
            e=(Employee) is.readObject();
            e2=(Employee) is.readObject();
            System.out.println(e.toString());
            System.out.println(e2.toString());
            infileStream.close();
            is.close();
            
        }catch (IOException e) {
            e.printStackTrace();
        }catch (ClassNotFoundException e)
        {
            e.printStackTrace();
        }
    }
}


运行结果

扫描二维码关注公众号,回复: 12806541 查看本文章
写入文件成功
Employee{name='张三', address='中国北京市', SSN=0, number=111, ta=Task{taskName='项目XXX', taskContent='完成XXX'}}
Employee{name='李四', address='中国上海市', SSN=0, number=222, ta=Task{taskName='项目XXX', taskContent='完成XXX'}}

一个类的对象要想序列化成功,必须满足两个条件:

  1. 该类必须实现 java.io.Serializable 对象,Serializable作为接口,并没有任何方法需要实现,它的唯一作用就是声明有实现它的类是可以被序列化的 。
  2. 该类的所有属性必须是可序列化的。如果有一个属性不是可序列化的,则该属性必须用transient关键词注明是短暂的。

上述例子中,当对象被序列化时,属性 SSN 的值为 111222333,但是因为该属性是短暂的,该值没有被发送到输出流。所以反序列化后 Employee 对象的 SSN 属性为 0。同时序列化对象中存在一个对象引用ta,ta所在的类也是继承了Serializable 接口的,所以ta变量本身以及ta所指向的对象内容也是能够且会被序列化的。如果把Task的接口继承删掉,就会报出没有继承接口的错误。如下:

java.io.NotSerializableException: JavaReview.FileIO.Task

下列特地说明几点:

  1. 序列化和反序列化都需要放到try/catch中进行操作。反序列化要考虑读取的文件不存在的情况,所以需要添加ClassNotFoundException异常。

  2. FileOutputStream/FileInputStream能够将字节写入/读出文件中,但是我们通常不会直接写字节,而是以对象层次的观点来写入,所以需要高层的连接串流,类ObjectInputStream
    和 ObjectOutputStream 就是高层次的数据流,能够将对象转变成字节流。

  3. 有些变量是不能被序列化的。比如这个数据是动态的,即只有在执行时求出而不能或不必存储,他们可能在执行期当场创建才有意义,一旦程序关闭之后,联机本身就不在只用,下次执行时需要重新创建出来。

  4. 如果对象在继承树上有个不可序列化的祖先类,则该不可序列化类以及在它之上的类的构造函数(就算是可序列化也一样)就会执行。一旦构造函数连锁启动之后将无法停止,也就是说,从第一个不可序列化的父类开始,全部都会重新初始化状态。

  5. transient变量会被赋值null的对象引用或primitive主数据类型的默认为0、false等值。

  6. 反序列化的时候,新的对象会被配置在堆上,但是构造函数不会执行。因为是要返回存储时的状态,所以如果执行构造函数则是变成了一个全新的状态。

  7. static变量不会被序列化,因为static是类变量,而不是对象变量,即对象的存储不会影响它的值。

  8. 反序列化时,读取对象的顺序与写入的顺序相同。

  9. 对象序列化的一个限制就是它只能解决Java的解决方案,只有Java程序才能给这个对象去序列化。一种更具有互操作性的解决方案是把数据转换成XML格式,这样就能够被各种各样的平台和语言使用。

序列化优化

如果需要读写大量特定的类对象,就应该考虑使用E接口。

Externalizable 实例类的唯一特性是可以被写入序列化流中,该类负责保存和恢复实例内容。 若某个要完全控制某一对象及其超类型的流格式和内容,则它要实现 Externalizable 接口的 writeExternal 和 readExternal 方法。这些方法必须显式与超类型进行协调以保存其状态。这些方法将代替定制的 writeObject 和 readObject 方法实现。

Serialization 对象将使用 Serializable 和 Externalizable 接口。对象持久性机制也可以使用它们。要存储的每个对象都需要检测是否支持 Externalizable 接口。如果对象支持 Externalizable,则调用 writeExternal 方法。如果对象不支持 Externalizable 但实现了 Serializable,则使用 ObjectOutputStream 保存该对象。

使用Externalizable 接口的要求:

  1. 必须实现该接口
  2. 必须实现writeExternal 方法,以便保存对象的状态。而且它必须显示地与它的上层类协作以保存其状态。
  3. 必须实现readExternal 方法,以便从流中读取writeExternal 方法写入的数据,回复对象的状态。而且它必须显示地与它的上层类协作以保存其状态。

参考资料:http://www.runoob.com/java/java-serialization.html
参考资料:《Head First Java》

猜你喜欢

转载自blog.csdn.net/dypnlw/article/details/82769345