美文网首页
java序列化(Serializable)

java序列化(Serializable)

作者: 奔向学霸的路上 | 来源:发表于2020-04-24 18:09 被阅读0次

在程序运行中,所有的遍历都是在内存中,比如,定义一个 String name = "abc",可以随时修改变量,比如把name改"de",但是一旦程序结束,变量所占用的内存就会被操作系统回收。下次重新运行程序,遍历又会变为"abc"。

定义

序列化

我们把变量从内存中变成可存储或可传输的过程叫做序列化,java序列化:把Java对象转换为字节序列的过程。
序列化之后,就可以把序列化的内容写入磁盘,或者通过网络传输到别的机器。

JSON
如果我们要在不同的编程语言之间传递对象,就必须把对象序列化为标准格式,比如XML,但更好的方法是序列化为JSON,因为JSON表示出来就是一个字符串。

反序列化

把变量内存从序列化的对象重新读到内存里称为反序列化,java反序列化将字节序列转换为Java对象的过程。

image.png

序列化应用

  • 对象保存在硬盘上,不但减轻了内存的压力,而且达到了持久化的目的
  • 利用序列化实现远程通讯,比如dubbo框架

Java中序列化API

  • java.io.ObjectOutputStream
  • java.io.ObjectInputStream
  • java.io.Serializable
  • java.io.Externalizable

java.io.ObjectOutputStream类

对象输出流,它的writeObject(Object obj)方法可以对指定obj对象参数进行序列化,再把得到的字节序列写到一个目标输出流中。

java.io.ObjectInputStream类

表示对象输入流,
它的readObject()方法,从输入流中读取到字节序列,反序列化成为一个对象,最后将其返回。

Serializable接口

Serializable接口是一个标记接口,没有方法或字段。一旦实现了此接口,就标志该类的对象就是可序列化的。

public interface Serializable {
}

Externalizable 接口

Externalizable继承了Serializable接口,还定义了两个抽象方法:writeExternal()和readExternal(),如果开发人员使用Externalizable来实现序列化和反序列化,需要重写writeExternal()和readExternal()方法

public interface Externalizable extends java.io.Serializable {
    void writeExternal(ObjectOutput out) throws IOException;
    void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}

Serializable序列化底层

例子:

@Setter
@Getter
@Slf4j
public class Test implements Serializable {
    private String name;
    private Integer age;

    public static void main(String[] args) {
        try(ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:\\test.out"));) {
            Test test = new Test();
            test.setName("Li");
            test.setAge(27);
            objectOutputStream.writeObject(test);
        }catch (IOException e){
            log.error("", e);
        }
    }
}

结果:


image.png

为了验证Serializable的作用,我们删掉Serializable关键字

@Setter
@Getter
@Slf4j
public class Test{
    private String name;
    private Integer age;

    public static void main(String[] args) {
        try(ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:\\test.out"));) {
            Test test = new Test();
            test.setName("Li");
            test.setAge(27);
            objectOutputStream.writeObject(test);
        }catch (IOException e){
            log.error("", e);
        }
    }
}

结果:


image.png

跟随输出结果我们来分析一下源码:
首先进入java.io.ObjectOutputStream#writeObject方法

public final void writeObject(Object obj) throws IOException {
        //enableOverride在父类中恒为false,在子类中为true,供子类重写writeObject方法
        if (enableOverride) {
            writeObjectOverride(obj);
            return;
        }
        try {
            writeObject0(obj, false);
        } catch (IOException ex) {
            if (depth == 0) {
                writeFatalException(ex);
            }
            throw ex;
        }
    }

再进入java.io.ObjectOutputStream#writeObject0
writeObject0首先是缓存替换部分,第一次进来可以直接看writeOrdinaryObject,由于Test类是实现了Serializable接口,所以会进入这个分支。

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检查替代对象,要求对象重写了writeReplace方法
            Object orig = obj;
            Class<?> cl = obj.getClass();
            ObjectStreamClass desc;
            for (;;) {
                // REMIND: skip this check for strings/arrays?
                Class<?> repCl;
                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
            //String类型
            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);
            //Serializable实现序列化接口
            } else if (obj instanceof Serializable) {
                //由于类实现了Serialzable接口,第一次调用会先执行这个方法
                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);
        }
    }

writeOrdinaryObject这个方法首先会调用writeClassDesc(desc)写入该类的生成信息,接下来是在Externalizable和Serializable的接口出现分支,如果实现了Externalizable接口并且类描述符非动态代理,则执行writeExternalData,否则执行writeSerialData。

private void writeOrdinaryObject(Object obj,
                                     ObjectStreamClass desc,
                                     boolean unshared)
        throws IOException
    {
        if (extendedDebugInfo) {
            debugInfoStack.push(
                (depth == 1 ? "root " : "") + "object (class \"" +
                obj.getClass().getName() + "\", " + obj.toString() + ")");
        }
        try {
            desc.checkSerialize();//是否可以序列化的检查,这个方法不允许是序列化枚举常量

            bout.writeByte(TC_OBJECT);
            //调用ObjectStreamClass的写入方法
            writeClassDesc(desc, false);
            handles.assign(unshared ? null : obj);
            // 判断是否实现了Externalizable接口
            if (desc.isExternalizable() && !desc.isProxy()) {
                writeExternalData((Externalizable) obj);
            } else {
                //写入序列化数据
                writeSerialData(obj, desc);
            }
        } finally {
            if (extendedDebugInfo) {
                debugInfoStack.pop();
            }
        }
    }

接下来看writeSerialData(obj, desc)方法,首先判断是否重写了writeObject方法,如果没有,直接调用defaultWriteFields(obj, slotDesc)方法。

 /**
     * Writes instance data for each serializable class of given object, from
     * superclass to subclass.
     */
    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;
            
            //如果被序列化的对象自定义实现了writeObject()方法,则执行这个代码块
            if (slotDesc.hasWriteObjectMethod()) {
                PutFieldImpl oldPut = curPut;
                curPut = null;
                SerialCallbackContext oldContext = curContext;

                if (extendedDebugInfo) {
                    debugInfoStack.push(
                        "custom writeObject data (class \"" +
                        slotDesc.getName() + "\")");
                }
                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;
                    if (extendedDebugInfo) {
                        debugInfoStack.pop();
                    }
                }

                curPut = oldPut;
            } else {
                // 调用默认的方法写入实例数据
                defaultWriteFields(obj, slotDesc);
            }
        }
    }

defaultWriteFields(obj, slotDesc)方法,这个方法会递归输出writeObject0(objVals[i],
fields[numPrimFields + i].isUnshared())

/**
     * Fetches and writes values of serializable fields of given object to
     * stream.  The given class descriptor specifies which field values to
     * write, and in which order they should be written.
     */
    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];
        }
        // 获取类的基本数据类型数据,保存到primVals字节数组
        desc.getPrimFieldValues(obj, primVals);
        //primVals的基本类型数据写到底层字节容器
        bout.write(primVals, 0, primDataSize, false);
        
        // 获取对应类的所有字段对象
        ObjectStreamField[] fields = desc.getFields(false);
        Object[] objVals = new Object[desc.getNumObjFields()];
        int numPrimFields = fields.length - objVals.length;
        // 获取类的obj类型数据,保存到objVals字节数组
        desc.getObjFieldValues(obj, objVals);
        for (int i = 0; i < objVals.length; i++) {
            if (extendedDebugInfo) {
                debugInfoStack.push(
                    "field (class \"" + desc.getName() + "\", name: \"" +
                    fields[numPrimFields + i].getName() + "\", type: \"" +
                    fields[numPrimFields + i].getType() + "\")");
            }
            try {
                //递归调用writeObject0()方法,写入对应的数据
                writeObject0(objVals[i],
                             fields[numPrimFields + i].isUnshared());
            } finally {
                if (extendedDebugInfo) {
                    debugInfoStack.pop();
                }
            }
        }
    }

序列化需注意问题点

  • static静态变量和transient修饰的字段不会被序列化
  • serialVersionUID生成问题
  • 如果某个序列化类的成员变量是对象类型,那么该对象类型也必须序列化
  • 子类实现了序列化,父类没有实现,父类中的字段会丢失

static静态变量和transient修饰的字段不会被序列化

@Setter
@Getter
@Slf4j
public class Test implements Serializable {
    private static final long serialVersionUID = -2489167938558001207L;

    private transient Integer age;
    public static String name = "Li";

    @Override
    public String toString() {
        return "Test{" +
                "age=" + age +
                ","+
                "name=" + name +
                '}';
    }

    public static void main(String[] args) {
        Test test = new Test();
        test.setAge(27);
        System.out.println(test);
        try(ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:\\test.out"))) {
            objectOutputStream.writeObject(test);
        }catch (IOException e){
            log.error("", e);
        }

        //修改静态变量
        Test.name = "wang";

        Test test2 = null;
        try(ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D:\\test.out"))) {
            test2 = (Test) objectInputStream.readObject();
        }catch (IOException |ClassNotFoundException e){
            log.error("", e);
        }
        System.out.println(test2);
    }
}

输出结果:

Test{age=27,name=Li}
Test{age=null,name=wang}

可以得出如下结论:

  1. static成员变量属于类级别,序列化是针对对象的,所以不能被序列化
  2. transient关键字,能阻止字段被序列化。Integer会被转换为null,int会被转换为0

serialVersionUID问题

序列化版本Id,每一个实现Serializable接口的类,都有一个表示序列化版本标识符的静态变量,或者默认值1L,或者等于对象的哈希码。

private static final long serialVersionUID = -2489167938558001207L;

serialVersionUID的作用

Java序列化机制是通过判断serialVersionUID来校验是否一致,在进行反序列化时,JVM会把传过来的字节流中的serialVersionUID和本地实体类的serialVersionUID进行比较,如果相同,反序列成功,不同,抛出InvalidClassException异常。

比如,我们新增加一个a属性,注释掉序列化代码,拿原先序列的来进行反序列


image.png

如果某个序列化类的成员变量是对象类型,则该对象类型的类必须实现序列化

经过测试发现,如果不实例化Test1,实际上不会报异常;只有在实例化Test1后,会出现异常。


image.png
image.png

可以看出at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184) ~[na:1.8.0_161]抛出了异常,也就是在判断对象类型时,走到了else逻辑。


image.png

子类实现了Serializable,父类没有实现Serializable接口的话,父类不会被序列化。

会导致父类属性值丢失,因为父类没有实现Serializable,父类不会被序列化。

序列化常见问题总结

  1. 序列化的底层是怎么实现的?
    几个核心点在于,实现Serializable接口,writeObject几个核心方法,直接写入基本类型,获取obj类型数据,循环递归写入
  2. 序列化时,如何让某些成员不要序列化?
    可以用transient关键字修饰,它可以阻止修饰的字段被序列化到文件中,在被反序列化后,transient 字段的值被设为初始值,比如int型的值会被设置为 0,对象型初始值会被设置为null。
  3. 在Java中,Serializable 和 Externalizable 有什么区别
    Externalizable继承了Serializable,给我们提供 writeExternal() 和 readExternal() 方法, 让我们可以控制 Java的序列化机制, 不依赖于Java的默认序列化。正确实现 Externalizable 接口可以显著提高应用程序的性能。
  4. serialVersionUID有什么用?
    JAVA序列化的机制是通过判断类的serialVersionUID来验证版本是否一致的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID和本地相应实体类的serialVersionUID进行比较,如果相同,反序列化成功,如果不相同,就抛出InvalidClassException异常。
  5. 是否可以自定义序列化过程, 或者是否可以覆盖 Java 中的默认序列化过程?
    可以的。我们都知道,对于序列化一个对象需调用 ObjectOutputStream.writeObject(saveThisObject), 并用 ObjectInputStream.readObject() 读取对象, 但 Java 虚拟机为你提供的还有一件事, 是定义这两个方法。如果在类中定义这两种方法, 则 JVM 将调用这两种方法, 而不是应用默认序列化机制。同时,可以声明这些方法为私有方法,以避免被继承、重写或重载。
  6. 在Java序列化期间,哪些变量未序列化?
    static静态变量和transient 修饰的字段是不会被序列化的。静态(static)成员变量是属于类级别的,而序列化是针对对象的。transient关键字修饰字段,可以阻止该字段被序列化到文件中。

参考:https://mp.weixin.qq.com/s/XHABnQUPvQmhOv73vLlTdw

相关文章

网友评论

      本文标题:java序列化(Serializable)

      本文链接:https://www.haomeiwen.com/subject/oxroihtx.html