Java序列化

168 阅读5分钟

序列化

将数据结构或对象转换为二进制串的过程

Serializable接口

Java提供的序列化接口

public interface Serializable {  
}

Serializable本身没有什么需要实现的方法,只是用来标识当前类可以被 ObjectOutputStream 序列化,以及被 ObjectInputStream 反序列化。

使用

类定义

public class Student implements Serializable {  
	//serialVersionUID唯一标识了一个可序列化的类
    private static final long serialVersionUID = -2100492893943893602L;
    private String name;
    private String sax;
    private Integer age;
    //Course也需要实现Serializable接口
    private List<Course> courses;

    //用transient关键字标记的成员变量不参与序列化(在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null)
    private transient Date createTime;
    //静态成员变量属于类不属于对象,所以不会参与序列化(对象序列化保存的是对象的“状态”,也就是它的成员变量,因此序列化不会关注静态变量)
    private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat();

    public Student() {
        System.out.println("Student: empty");
    }

    public Student(String name, String sax, Integer age) {
        System.out.println("Student: " + name + " " + sax + " " + age);
        this.name = name;
        this.sax = sax;
        this.age = age;
    }
    ...
}

////Course也需要实现Serializable接口
public class Course implements Serializable {

    private static final long serialVersionUID = 667279791530738499L;
    private String name;
    private float score;

    ...
}

序列化

synchronized public static boolean saveObject(Object obj, String path) {
    if (obj == null) {
        return false;
    }
    ObjectOutputStream oos = null;
    try {
        // 创建序列化流对象
        oos = new ObjectOutputStream(new FileOutputStream(path));
        //序列化
        oos.writeObject(obj);
        oos.close();
        return true;
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (oos != null) {
            try {
                // 释放资源
                oos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    return false;
}

反序列化

synchronized public static <T> T readObject(String path) {
    ObjectInputStream ojs = null;
    try {
        // 创建反序列化对象
        ojs = new ObjectInputStream(new FileInputStream(path));
        // 还原对象
        return (T) ojs.readObject();
    } catch (IOException | ClassNotFoundException e) {
        e.printStackTrace();
    } finally {
        if(ojs!=null){
            try {
                // 释放资源
                ojs.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    return null;
}

serialVersionUID

serialVersionUID 用来表明类的不同版本间的兼容性,最好显式定义该属性(否则jvm会自行计算,在不同jvm上反序列化时可能会计算出不同的结果,导致无法正确反序列化)

序列化过程

序列化的过程就在ObjectOutputStream类的writeObject函数里

主要流程概述

  • 写入魔数(Magic Number)
  • 写入本类的描述信息
  • 向上递归写入各个parent类的描述信息
  • 从最终父类向下递归写入各个类中的基本类型的字段的数据
  • 写入对象类型的成员的所属类的描述信息(比如有个成员变量是MyClass obj; 那么就会写入MyClass类的描述信息)

writeObject流程

public final void writeObject(Object obj) throws IOException {
    if (enableOverride) {
        //如果继承了ObjectOutputStream,可以重写writeObjectOverride方法来实现的新的序列化逻辑
        //注意,writeObject是final的,无法被重写
        writeObjectOverride(obj);
        return;
    }
    try {
        //一般情况下直接调用这个方法
        writeObject0(obj, false);
    } catch (IOException ex) {
        if (depth == 0) {
            writeFatalException(ex);
        }
        throw ex;
    }
}
private void writeObject0(Object obj, boolean unshared)
    throws IOException
{
    boolean oldMode = bout.setBlockDataMode(false);
    depth++;
    try {
        // handle previously written and non-replaceable objects
        //处理几种特殊的对象,先跳过
        int h;
        if ((obj = subs.lookup(obj)) == null) {
            writeNull();
            return;
        } else if (!unshared && (h = handles.lookup(obj)) != -1) {
            writeHandle(h);
            return;
        } else if (obj instanceof Class) {
            writeClass((Class) obj, unshared);
            return;
        } else if (obj instanceof ObjectStreamClass) {
            writeClassDesc((ObjectStreamClass) obj, unshared);
            return;
        }

        // check for replacement object
        Object orig = obj;
        Class<?> cl = obj.getClass();
        ObjectStreamClass desc;
        for (;;) {
            // REMIND: skip this check for strings/arrays?
            Class<?> repCl;
            //创建描述类的信息
            //注意,这个desc里包含了各个父类的信息
            desc = ObjectStreamClass.lookup(cl, true);
            if (!desc.hasWriteReplaceMethod() ||
                (obj = desc.invokeWriteReplace(obj)) == null ||
                (repCl = obj.getClass()) == cl)
            {
                break;
            }
            cl = repCl;
        }
        if (enableReplace) {
            //如果有代理(这部分先跳过)
            Object rep = replaceObject(obj);
            if (rep != obj && rep != null) {
                cl = rep.getClass();
                desc = ObjectStreamClass.lookup(cl, true);
            }
            obj = rep;
        }

        // if object replaced, run through original checks a second time
        if (obj != orig) {
            subs.assign(orig, obj);
            if (obj == null) {
                writeNull();
                return;
            } else if (!unshared && (h = handles.lookup(obj)) != -1) {
                writeHandle(h);
                return;
            } else if (obj instanceof Class) {
                writeClass((Class) obj, unshared);
                return;
            } else if (obj instanceof ObjectStreamClass) {
                writeClassDesc((ObjectStreamClass) obj, unshared);
                return;
            }
        }

        // remaining cases
        if (obj instanceof String) {
            writeString((String) obj, unshared);
        } else if (cl.isArray()) {
            writeArray(obj, desc, unshared);
        } else if (obj instanceof Enum) {
            writeEnum((Enum<?>) obj, desc, unshared);
        } else if (obj instanceof Serializable) {
            //重点!如果实现了Serializable接口就调用writeOrdinaryObject写入信息
            writeOrdinaryObject(obj, desc, unshared);
        } else {
            if (extendedDebugInfo) {
                throw new NotSerializableException(
                    cl.getName() + "\n" + debugInfoStack.toString());
            } else {
                throw new NotSerializableException(cl.getName());
            }
        }
    } finally {
        depth--;
        bout.setBlockDataMode(oldMode);
    }
}
//删除了一些日志信息
private void writeOrdinaryObject(Object obj,
                                 ObjectStreamClass desc,
                                 boolean unshared)
    throws IOException
{
    ...
    try {
        desc.checkSerialize();

        //写入Object标志位
        bout.writeByte(TC_OBJECT);
        //写入类的描述信息
        //注意,这里会向上递归写入各个父类的信息
        writeClassDesc(desc, false);
        handles.assign(unshared ? null : obj);
        if (desc.isExternalizable() && !desc.isProxy()) {
            //如果实现了Externalizable接口
            writeExternalData((Externalizable) obj);
        } else {
            writeSerialData(obj, desc);
        }
    } finally {
        ...
    }
}
private void writeSerialData(Object obj, ObjectStreamClass desc)
    throws IOException
{
    //这个数组,从最终父类开始往下按顺序放(即实现了向下递归,先写入最终父类的实例数据)
    ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
    for (int i = 0; i < slots.length; i++) {
        ObjectStreamClass slotDesc = slots[i].desc;
        if (slotDesc.hasWriteObjectMethod()) {
            //如果实现了writeObject()方法,调用该方法写入实例数据
            PutFieldImpl oldPut = curPut;
            curPut = null;
            SerialCallbackContext oldContext = curContext;

            ...
                try {
                    curContext = new SerialCallbackContext(obj, slotDesc);
                    bout.setBlockDataMode(true);
                    slotDesc.invokeWriteObject(obj, this);
                    bout.setBlockDataMode(false);
                    bout.writeByte(TC_ENDBLOCKDATA);
                } finally {
                    curContext.setUsed();
                    curContext = oldContext;
                    ...
                }

                curPut = oldPut;
            } else {
            	// 调用默认的方法写入实例数据
                defaultWriteFields(obj, slotDesc);
            }
        }
    }
private void defaultWriteFields(Object obj, ObjectStreamClass desc)
    throws IOException
{
    Class<?> cl = desc.forClass();
    if (cl != null && obj != null && !cl.isInstance(obj)) {
        throw new ClassCastException();
    }

    desc.checkDefaultSerialize();

    int primDataSize = desc.getPrimDataSize();
    if (primVals == null || primVals.length < primDataSize) {
        primVals = new byte[primDataSize];
    }
    desc.getPrimFieldValues(obj, primVals);
    //写入基本数据类型的数据
    bout.write(primVals, 0, primDataSize, false);

    ObjectStreamField[] fields = desc.getFields(false);
    Object[] objVals = new Object[desc.getNumObjFields()];
    int numPrimFields = fields.length - objVals.length;
    desc.getObjFieldValues(obj, objVals);
    //遍历对象类型的字段
    for (int i = 0; i < objVals.length; i++) {
        ...
        try {
            //这里又调用了writeObject0,即列化对象类型的字段
            writeObject0(objVals[i],
                         fields[numPrimFields + i].isUnshared());
        } finally {
            ...
        }
    }
}

注意事项

  • 未实现Serializable 的属性状态无法被序列化,在反序列化过程中,对应的非可序列化字段将调用无参构造函数重新创建,因此这个字段所属的类必须有无参构造函数

  • 一个实现序列化的类,他的子类也是可序列化的

  • 如果子类实现了序列化接口,父类没有实现Serializable接口,那么必须为父类提供无参构造函数

  • 反序列化时,如果原来的类新添加了一个字段,该字段会被初始化为默认值,少一个字段也不会出现问题

  • 枚举类型序列化时只会保存名字,也就是值可能会改变,比如

    public enum Num {
        TWO, ONE, THREE;
        
        public void printValues() {
            System.out.println(ONE + " ONE.ordinal " + ONE.ordinal());
            System.out.println(TWO + " TWO.ordinal " + TWO.ordinal());
            System.out.println(THREE + " THREE.ordinal " + THREE.ordinal());
        }
    
        public static void testSerializable() throws Exception {
            File file = new File("p.dat");
    //        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
    //        oos.writeObject(Num.ONE);
    //        oos.close();
            Num.ONE.printValues();
            System.out.println("=========反序列化后=======");
    
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
            Num s1 = (Num) ois.readObject();
            s1.printValues();
            ois.close();
        }
    
        public static void main(String... args) throws Exception {
            testSerializable();
        }
    }
    

    反序列化时如果改变 TWOONE 的位置,那么对应的值也会发生改变

    ONE ONE.ordinal 1
    TWO TWO.ordinal 0
    THREE THREE.ordinal 2
    =========反序列化后=======
    //调换(ONE,TWO)的位置: TWO, ONE, THREE; ->ONE, TWO, THREE;
    ONE ONE.ordinal 0
    TWO TWO.ordinal 1
    THREE THREE.ordinal 2
    
  • 单例类实现Serializable接口,需要实现readResolve方法,方法单例模式被破坏

    public class School implements Serializable{  
    
        public String sName = null; 
        public String sId = null;
    
        public static final School instance= new School();
        
        // Singleton pattern requires constructors to be private.
        private School(){
            this.sName = "";
            this.depId = "";
        }
    
        // Singleton pattern requires constructors to be private.
        private School(String name, String id){
            this.sName = name;
            this.spId = id;
        }
        
        private Object readResolve(){
            return instance;
        }
    }
    

自定义序列化和反序列化

在序列化类的内部实现readObject,writeObject方法即可实现自定义序列化和反序列化,注意方法签名是有要求的

private void readObject(ObjectInputStream inputStream) throws ClassNotFoundException, IOException {
    //记得调用默认的读入方法
    inputStream.defaultReadObject();
    //读入字段,比如
    //name = (String)inputStream.readObject();
    //score = inputStream.readFloat();
}

private void writeObject(ObjectOutputStream outputStream) throws IOException {
    //记得调用默认的写出方法
    outputStream.defaultWriteObject();
    //写入字段,比如
    //outputStream.writeObject(name);
    //outputStream.writeFloat(score);
}

序列化代理

前面序列化流程里跳过了代理这部分,这里详细说说

序列化代理通过writeReplace方法实现,使用如下

//待序列化的类
public class Student implements Serializable {  

    private final static long serialVersionUID = 1L
 
    private String firstName = null;      
    private String lastName = null;  
    private Integer age = null;  
    // unserializable field
    private transient School School= null;
    
    public Student () { }  

    public Student (String fname, String lname, Integer age, School school) {  
        this.firstName = fname;  
        this.lastName = lname;  
        this.age = age;  
        this.school = school;
    }
    
    //调用代理类
    private Object writeReplace ( ) {
    	return new SerializationProxy4Student(this); // this:Student instance
	}
} 

//Student类的序列化代理类
private class SerializationProxy4Student implements Serializable {  

    private final static long serialVersionUID = 1L // Any number will do
 
    private String firstName = null;      
    private String lastName = null;  
    private Integer age = null;  
    private String schoolName = null;
    private String schoolId = null;
    
    SerializationProxy4Student (Student s) {
        this.firstname = s.firstname;
        this.lastname = s.lastname;
        this. age = s.age;
        this.schoolName = s.school.sName;
        this.schoolId = s.school.sId;
     }  
} 

使用代理类的好处主要有:

  • 不用在主类里实现readObject,writeObject

  • 将序列化的内容和类的结构分开。

    无论主类(比如Student类)如何修改和被继承,其序列化的格式都是代理类(SerializationProxy4Student类)的序列化格式

  • 便于在主类发生变化时支持多种序列化格式