• 博客园logo
  • 会员
  • 周边
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • YouClaw
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
风吹花落泪如雨
博客园    首页    新随笔    联系   管理    订阅  订阅

JAVA输入/输出(三)-----对象序列化

一、序列化和反序列化的概念

把对象转换为字节序列的过程称为对象的序列化,把字节序列恢复为对象的过程称为对象的反序列化。
  对象的序列化主要有两种用途:
  1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
  2) 在网络上传送对象的字节序列。

  在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,入住物理硬盘,以便长期保存。比如最常见的是Web服务器中的Session对象,当有 10万用户并发访问,就有可能出现10万个Session对象,内存可能吃不消,于是Web容器就会把一些seesion先序列化到硬盘中,等要用了,再把保存在硬盘中的对象还原到内存中。

       当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象。

 

二、使用对象流实现序列化

import java.io.*;

public class WriteObject {
    public static void main(String[] args) {
        try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("aaa.txt")))
        {
            Person p1 = new Person("Person的名称name1");
            Person p2 = new Person("Person的名称name2");
            oos.writeObject(p1);
            oos.writeObject(p2);
        }  catch (IOException e) {
            e.printStackTrace();
        }
        
        try(ObjectInputStream ois = new ObjectInputStream(new FileInputStream("aaa.txt")))
        {
            Person p1 = (Person)ois.readObject();
            Person p2 = (Person)ois.readObject();
            System.out.println(p1.name);
            System.out.println(p2.name);
        }  catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 

三、对象引用的序列化

如果某个类的成员变量的类型表示基本类型或String类型,而是另一种引用类型,那么这个引用类必须是可序列化的,否则拥有该类型成员变量的类也是不可序列化的。

Java序列化机制采用了一种特殊的序列化算法:

  • 所有保存到磁盘中的对象都有一个序列化编号
  • 当程序试图序列化一个对象时,程序将先检查该对象是否已经被序列化过
  • 如果某个对象已经序列化过,程序只是直接输出一个序列化编号,而不是再次重新序列化该对象

注意:当使用Java序列化机制序列化可变对象时一定要注意,只有第一次调用writeObject()方法输出对象时才会将对象转换成字节序列,并写入到ObjectOutputStream;在后面程序中即使该对象的实例变量发生了改变,再次调用writeObject()方法输出该对象时,改变后的实例变量也不会被输出。

 

四、Java9增加的过滤功能

当程序通过ObjectInputStream反序列化对象时,过滤器的checkInput()方法会被自动激活,用于检查序列化数据是否有效。

import java.io.*;

public class FilterTest {
    public static void main(String[] args){
        try(ObjectInputStream ois = new ObjectInputStream(new FileInputStream("aaa.txt")))
        {
            ois.setObjectInputFilter((info) -> {
                System.out.println("===执行数据过滤===");
                ObjectInputFilter serialFilter = ObjectInputFilter.Config.getSerialFilter();
                if(serialFilter != null) {
                    //首先使用ObjectInputFilter执行默认的检查
                    ObjectInputFilter.Status status = serialFilter.checkInput(info);
                    //如果默认检查的结果不是Statuc.UNDECIDEd
                    if(status != ObjectInputFilter.Status.UNDECIDED) {
                        //直接返回结果
                        return status;
                    }
                }
                //如果要恢复的对象不是1个
                if(info.references() != 1) {
                    //不允许回复对象
                    return ObjectInputFilter.Status.REJECTED;
                }
                   //如果恢复的不是Person类
                if(info.serialClass() != null && info.serialClass() != Person.class) {
                    //不允许恢复对象
                    return ObjectInputFilter.Status.REJECTED;
                }
                return ObjectInputFilter.Status.UNDECIDED;
            });
            Person p = (Person)ois.readObject();
            System.out.println("名字为: " + p.name);
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 

五、自定义序列化

通过在变量前面使用transient关键字修饰,可以指定Java序列化时无需理会该实例变量。transient只能用于修饰实例变量。

//反序列化时输出0
private transient int age;

Java还提供了一种自定义序列化机制:

private void writeObject(java.io.ObjectOutputStream out)throws IOException:默认调用out.defaultWriteObject来保存Java对象的各实例变量

private void readObject(java.io.ObjectInputStream in)throws IOException,ClassNotFoundException:默认调用in.defaultReadObject来恢复Java对象的非瞬态实例变量

private void readObjectNoData()throws ObjectStreamException:当序列化流不完整时,该方法可以用来正确地初始化反序列化的对象

import java.io.*;

public class Person2 implements Serializable{
    private String name;
    private int age;
    public Person2(String name,int age) {
        this.name = name;
        this.age = age;
    }
    
    private void writeObject(ObjectOutputStream out) throws IOException {
        //将name包装成StringBuffer,并将其字符序列反转后写入。
        out.writeObject(new StringBuffer(name).reverse());
        out.writeInt(age);
    }
    //writeObject()方法存储的实例变量的顺序应和readObject()方法中恢复实例变量的顺序一致,否则将不能正常恢复该Java对象
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        //先转换成StringBuffer,再将其反转。
        this.name = ((StringBuffer)in.readObject()).reverse().toString();
        this.age = in.readInt();
    }
}

还有一种更彻底的自定义机制,甚至可以在序列化对象时将该对象替换成其他对象

private(私有)/protected(受保护的)/package-private(包私有)   Object writeReplace()throws ObjectStreamException

import java.io.*;
import java.util.ArrayList;

public class Person3 implements Serializable{
    private String name;
    private int age;
    public Person3(String name,int age) {
        this.name = name;
        this.age = age;
    }
    //重写writeReplace方法,程序在序列化该对象之前,先调用该方法
    private Object writeReplace() {
        ArrayList<Object> list = new ArrayList<Object>();
        list.add(name);
        list.add(age);
        return list;
    }
    
    public static void main(String[] args){
        try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("aaa.txt"));
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("aaa.txt")))
        {
            Person3 per = new Person3("孙悟空",500);
            //系统将per对象转换成字节序列并输出
            oos.writeObject(per);
            //反序列化读取得到的是ArrayList
            ArrayList<Object> list = (ArrayList)ois.readObject();
            System.out.println(list);
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
}

系统在序列化某个对象之前,会先调用该对象的writeReplace()和writeObject()两个方法。先调用writeReplace()方法,若返回另一个对象,则再次调用另一个对象的writeReplace()方法……直到该方法不再返回另一个对象为止,程序最后将调用该对象的writeObject()方法来保存该对象的状态。

序列化机制还有一种特殊方法,可以实现保护性复制整个对象:

private(私有)/protected(受保护的)/package-private(包私有)   Object readResolve()throws ObjectStreamExceptio

这个方法会紧接着readObject()之后调用,该方法的返回值会代替原来反序列化的对象。

 

六、另一种自定义序列化(Externalizable)

void readExternal(ObjectInput in):该方法调用DataInput(ObjectInput的父接口)的方法来恢复基本类型的实例变量值,调用ObjectInput()的readObject()方法来恢复引用类型的实例变量值。

void writeExternal(ObjectOutput out):该方法调用DataOutput(ObjectOutput的父接口)的方法来保存基本类型的实例变量值,调用ObjectOutput()的writeObject()方法来保存引用类型的实例变量值。

import java.io.*;

public class Person4 implements Externalizable{
    public String name;
    public int age;
    //反序列对象时,程序会先使用无参构造器创建实例,然后才执行readExternal()方法进行反序列化,因此必须提供无参构造器
    public Person4() {};
    public Person4(String name,int age) {
        this.name = name;
        this.age = age;
    }

    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(new StringBuffer(name).reverse());
        out.writeInt(age);
    }
    public void readExternal(ObjectInput in) throws ClassNotFoundException, IOException {
        this.name = ((StringBuffer)in.readObject()).reverse().toString();
        this.age = in.readInt();
    }
}

 

 

七、版本

 

对象的类型、实例变量都会被序列化;

方法,类变量,transient实例变量都不会被序列化

反序列化对象必须要有序列化对象的class文件。

Java序列化机制为序列化类提供了一个private static final 的serialVersionUID值,用于标识该Java类的序列化版本。

 

 

posted @ 2018-08-20 14:37  风吹花落泪如雨  阅读(191)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2026
浙公网安备 33010602011771号 浙ICP备2021040463号-3