Fork me on GitHub

Java从零开始学三十九(对象序列化)

一、序列化

将对象的状态存储到特定存储介质中的过程

对象序列化,就是把一个对象变为二进制的数据流的一种方法,通过对象序列化可以方便的实现对象的传输或存储。
 
序列化保存对象的“全景图”,构建对象的“全景天窗”.
如果一个类的对象想被序列化,则对象所在的类必须实现java.io.Serializable接口

二、对象的序列化和反序列化

要想完成对象的输入或输出,还必须依靠对象输出流(ObjectOutputStream)和对象输入流(ObjectInputStream)
使用对象输出流输出序列化对象的步骤,有时也称为序列化,而使用对象输入流读入对象的过程,有时也称为反序列化
序列化步骤:
  1. 创建一个对象输出流ObjectOutputStream
  2. writeObject()方法输出序列化对象

反序列化步骤:

  1. 创建一个对象输入流ObjectInputStream
  2. readObject()方法读取流中的对象

 三、对象输出流(ObjectOutputStream)和对象输入流(ObjectInputStream)

3.1、对象输出流:ObjectOutputStream

No.
方法或常量
类型
描述
1
public ObjectOutputStream(OutputStream out) throws IOException
构造
传入输出的对象
2
public final void writeObject(Object obj) throws IOException
普通
输出对象
此类的使用形式与PrintStream非常的相似,在实例化时也需要传入一个OutputStream的子类对象,之后根据传入的OutputStream子类的对象不同,输出的位置也不同。

3.2、对象输入流:ObjectInputStream

No.
方法或常量
类型
描述
1
public ObjectInputStream(InputStream in) throws IOException
构造
构造输入对象
2
public final Object readObject() throws IOException, ClassNotFoundException
普通
从指定位置读取对象
此类也是InputStream的子类,与PrintStream类的使用类似,此类同样需要接收InputStream类的实例才可以实例化

四、transient关键字

当使用Serializable接口实现序列化操作时,如果一个对象中的某个属性不希望被序列化的话,则可以使用transient关键字进行声明
 
import java.io.Serializable;
public class Person implements Serializable {     // 此类的对象可以被序列化
    private transient String name;            // 此属性将不被序列化
    private int age;                // 此属性将被序列化
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String toString() {            // 覆盖toString(),输出信息
        return "姓名:" + this.name + ";年龄:" + this.age;
    }
}

五、实现反序列化注意事项

  • 反序列化过程无需要使用构造器生成对象
  • 按顺序反序列化恢复对象
  • 父类Serializable或者存在无参数构造方法

 六、例子

package com.pb.serializable;

import java.io.Serializable;

/*
 * 学生类 并实现接口Serializable接口,使用之可以序列化
 */
/*
 * 序列化
 * 1.创建一个对象,这个对象将被序列化,并写入文件中,前提是这个类实现了Serializable接口
 * 2.实例化ObjectOutputStream的对象
 * 3.调用ObjectOutputStream的writerObject()方法,将对象写入流中
 * 4.关闭流
 * transient关键字
 * 可以保护对象中的敏感信息不被写入到文件中
 * 反序列化
 * 1.实例化ObjectInputStream对象
 * 2.调用ObjectInputStream的readObject()方法,获取对象时,要进行强制类型转换
 * 3.关闭流
 */
public class Student implements Serializable {
    private String name; // 姓名
    private int age; // 年龄
    private String gender; // 性别
    private transient String password; // 密码属性不能被序列化

    // 构造方法
    public Student() {
        // 无参
    }

    // 有参数
    public Student(String name, int age, String gender, String password) {

        this.name = name;
        this.age = age;
        this.gender = gender;
        this.password = password;
    }
    //getter、setter方法

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        if(age>0&&age<150)
        this.age = age;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        if(gender.equals("男") || gender.equals("女"))
        this.gender = gender;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    // 自我介绍
    public void say() {
        System.out.println("姓名:" + this.name + "\t年龄:" + this.age + "\t性别:"
                + this.gender+"\t密码 : "+this.password);
    }

}

序列化:

package com.pb.serializable;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;

/*
 * 序列化
 * 1.创建一个对象,这个对象将被序列化,并写入文件中,前提是这个类实现了Serializable接口
 * 2.实例化ObjectOutputStream的对象
 * 3.调用ObjectOutputStream的writerObject()方法,将对象写入流中
 * 4.关闭流
 * transient关键字
 * 可以保护对象中的敏感信息不被写入到文件中
 */
public class SerializableObj {

    public static void main(String[] args) {

            try {
                //1.声明一个文件输出流
                FileOutputStream    fos = new FileOutputStream("d:/test/obj.txt");
                //2.声明ObjectOutputStream流对象
                ObjectOutputStream oos=new ObjectOutputStream(fos);
                //也可以2步合一步
                //ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("d:/test/obj.txt"));
                Student stu1=new Student("张三", 23, "男", "123456");
                Student stu2=new Student("李四", 24, "女", "123123");
                Student stu3=new Student("王五", 25, "男", "123321");
                Student stu4=new Student("赵六", 26, "男", "999999");
                //创建集合并添加
                List<Student> list=new ArrayList<Student>();
                list.add(stu1);
                list.add(stu2);
                list.add(stu3);
                list.add(stu4);
                //3.将student对象序列化,写入输出oos流
                oos.writeObject(stu1);
                oos.writeObject(stu2);
                oos.writeObject(stu3);
                oos.writeObject(stu4);
                oos.writeObject(list);
                //4.关闭流
                oos.close();
                fos.close();
                System.out.println("=======序列化完成======");
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
            
        
    }

}

反序列化:

package com.pb.serializable;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.List;

/*
 * 反序列化
 * 1.实例化ObjectInputStream对象
 * 2.调用ObjectInputStream的readObject()方法,获取对象时,要进行强制类型转换
 * 3.关闭流
 */
public class ReadSerializableObj {

    public static void main(String[] args) {
        
        try {
            //1.声明一个文件输出流
            FileInputStream    fis = new FileInputStream("d:/test/obj.txt");
            //2.实例化ObjectInputStream
            ObjectInputStream ois=new ObjectInputStream(fis);
            //也可以全2为一
            //ObjectInputStream ois=new ObjectInputStream(new FileInputStream("d:/test/obj.txt"));
            //3.声明一个Student对象和集合
            System.out.println("=====反序列化学生对象=======");
            Student stu1=    (Student) ois.readObject();
            stu1.say();
            Student stu2=    (Student) ois.readObject();
            stu2.say();
            Student stu3=    (Student) ois.readObject();
            stu3.say();
            Student stu4=    (Student) ois.readObject();
            stu4.say();
            System.out.println("=====反序列化集合对象=======");
            List<Student> list=(List<Student>) ois.readObject();
            for (Student s : list) {
                s.say();
            }
            
            //4.关闭流
            ois.close();
            fis.close();
            System.out.println("====反序列完成====");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

    }

}

结果:

=====反序列化学生对象=======
姓名:张三    年龄:23    性别:男    密码 : null
姓名:李四    年龄:24    性别:女    密码 : null
姓名:王五    年龄:25    性别:男    密码 : null
姓名:赵六    年龄:26    性别:男    密码 : null
=====反序列化集合对象=======
姓名:张三    年龄:23    性别:男    密码 : null
姓名:李四    年龄:24    性别:女    密码 : null
姓名:王五    年龄:25    性别:男    密码 : null
姓名:赵六    年龄:26    性别:男    密码 : null
====反序列完成====

从结果中可以看到:password使用transient关键字后,没有被序列化,反序列化中,读出的是默认字段类型的默认值null

七、包含引用类型属性的对象序列化

引用类必须也为可序列化

 

public class Teacher implements Serializable {
    private String name; // 姓名
    private int age; // 年龄
    private String gender; // 性别
    private transient String password; // 密码属性不能被序列化
    
    private Student stu;          //这里的Student类必须也是可以序列化的,

}

这里有个Teacher类,但属性中有一个Student类的对象,这时Teacher要实现序列时,Student必须也要实现Serializable接口才可以

 7.1、序列化算法

  • 对象分配序列号
  • 当程序试图序列化一个对象时,将会检查是否已经被序列化,只有序列化后的对象才能被转换成字节序列输出
  • 如果对象已经被序列化,则程序直接输出一个序列化编号,而不在重新序列化

7.2、序列化机制

Teacher类:

package com.pb.serializable;

import java.io.Serializable;
/*
 * 教师类实例Serializable接口
 */
public class Teacher implements Serializable {
    private String name; // 姓名
    private int age; // 年龄
    private String gender; // 性别
    private transient String password; // 密码属性不能被序列化
    
    private Student stu;          //这里的Student类必须也是可以序列化的,

    //构造方法
    public Teacher() {
        //无参数
    }

    public Teacher(String name, int age, String gender, String password,
            Student stu) {
        //有参数
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.password = password;
        this.stu = stu;
    }

    //getter、setter方法
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Student getStu() {
        return stu;
    }

    public void setStu(Student stu) {
        this.stu = stu;
    }

    // 自我介绍
        public void say() {
            System.out.println("姓名:" + this.name + "\t年龄:" + this.age + "\t性别:"
                    + this.gender+"\t密码 : "+this.password);
        }
    
    
    
}

序列化和化序列化类:

package com.pb.serializable;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

/*
 * 老师类的序列化和反序列化 2个老师引用同一个学生对象
 */
public class WriterTeacherObj {

    public static void main(String[] args) {
        
        /*
         * 序列化
         * 1.序列化时时候,会查找当前对象的序列化编号是否存在,不存在,保存该对象
         * 2.写入操作时,当对象的序列化编号存在时候,会输出一个当前对象的序列化编号,而不是当前对象
         * 3.反序列化对象时, 程序会自动查找,当前对象的序列化编号,之后如果,发现当前的对象的序列化编号被其它对象引用时,则返回同一个对象
         */
        try {
            //1.实例化ObjectOutputStream
            ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("d:/test/teacher.txt"));
            //2.实例化老师对象
            Student student=new Student("马达", 23, "男", "123123");
            //2个老师引用同一个学生对象
            Teacher teacher_han=new Teacher("韩冰", 22, "女", "123456789", student);
            Teacher teacher_zhang=new Teacher("张三丰", 108, "男", "123456789", student);
            //3.对象序列
            oos.writeObject(teacher_han);
            oos.writeObject(teacher_zhang);
            //4.关闭流
            oos.close();
            System.out.println("====================序列化完成=================");
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        /*
         * 反序列化
         */
        System.out.println("==================开始反序列化================");
        try {
            //1.实例化ObjectInputStream对象
            ObjectInputStream ois=new ObjectInputStream(new FileInputStream("d:/test/teacher.txt"));
            //2.反序列声明一个Teacher对象强制类型转换
            Teacher teacher_han=(Teacher) ois.readObject();
            Teacher teacher_zhang=(Teacher) ois.readObject();
            //3.输入Teacher中的信息
            System.out.println("==================老师信息================");
            teacher_han.say();
            teacher_zhang.say();
            System.out.println("=================老师中学生信息=============");
            teacher_han.getStu().say();
            teacher_zhang.getStu().say();
            //4.关闭流
            ois.close();
            System.out.println("===========反序列化完成=========");
            
            
            //判断2个老师的学生是否为同一个
            if(teacher_han.getStu()==teacher_zhang.getStu()){
                System.out.println("张老师和韩老师教的是同一个学生");
            }else{
                System.out.println("张老师和韩老师教的不是同一个学生");
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

    }

}

结果:

====================序列化完成=================
==================开始反序列化================
==================老师信息================
姓名:韩冰    年龄:22    性别:女    密码 : null
姓名:张三丰    年龄:108    性别:男    密码 : null
=================老师中学生信息=============
姓名:马达    年龄:23    性别:男    密码 : null
姓名:马达    年龄:23    性别:男    密码 : null
===========反序列化完成=========
张老师和韩老师教的是同一个学生

 

posted @ 2015-03-04 01:08  森林森  阅读(665)  评论(0编辑  收藏  举报