序列化分析

序列化原理

序列化就是把对象转化为可以持久化,可以网络传输字节数组,如果对象没有序列化,那么当进程停止的时候也就消失了

反序列化是序列化的逆向过程,将字节数组反序列化,恢复为原来对象

serialVersionUID 的作用

Java 的序列化机制是通过判断类的 serialVersionUID 来验证版本一致性的。在进行反序列化
时,JVM 会把传来的字节流中的 serialVersionUID 与本地相应实体类的 serialVersionUID 进
行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的
异常,即是 InvalidCastException。

因此,只要我们自己指定了 serialVersionUID,就可以在序列化后,去添加一
个字段,或者方法,而不会影响到后期的还原,还原后的对象照样可以使用,而且还多了方
法或者属性可以用

Transient 关键字

Transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变
量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是
0,对象型的是 null。

java 序列化的一些简单总结

  1. Java 序列化只是针对对象的状态进行保存,至于对象中的方法,序列化不关心
  2. 当一个父类实现了序列化,那么子类会自动实现序列化,不需要显示实现序列化接口
  3. 当一个对象的实例变量引用了其他对象,序列化这个对象的时候会自动把引用的对象也进
    行序列化(实现深度克隆)
  4. 当某个字段被申明为 transient 后,默认的序列化机制会忽略这个字段
  5. 被申明为 transient 的字段,如果需要序列化,可以添加两个私有方法:writeObject 和
    readObject

常见的序列化技术:

  • xml 案例: Webservice,就是采用 XML 格式对数据进行序列化的

  • json FastJson GSON

  • hessian dubbo

  • protobuf
    Protobuf 使用比较广泛,主要是空间开销小和性能比较好,非常适合用于公司内部对性能要
    求高的 RPC 调用。 另外由于解析性能比较高,序列化以后数据量相对较少,所以也可以应
    用在对象的持久化场景中
    但是要使用 Protobuf 会相对来说麻烦些,因为他有自己的语法,有自己的编译器,如果需要
    用到的话必须要去投入成本在这个技术的学习中

  • Avro 序列化 是一个数据序列化系统,设计用于支持大批量数据交换的应用
    kyro 序列化框架 是一种非常成熟的序列化实现,已经在 Hive、Storm)中使用得比较广泛,不过它不能
    跨语言. 目前 dubbo 已经在 2.6 版本支持 kyro 的序列化机制。它的性能要优于之前的
    hessian2

package com.reflect.singleton;

import java.io.Serializable;

/**
 * if (obj != null &&
 *             handles.lookupException(passHandle) == null &&
 *             desc.hasReadResolveMethod())
 *         {
 *             Object rep = desc.invokeReadResolve(obj);
 *             if (unshared && rep.getClass().isArray()) {
 *                 rep = cloneArray(rep);
 *             }
 *             if (rep != obj) {
 *                 handles.setObject(passHandle, obj = rep);
 *             }
 *         }
 *
 *         hasReadResolveMethod:如果实现了serializable 或者 externalizable接口的类中包含readResolve则返回true
 *
 * invokeReadResolve:通过反射的方式调用要被反序列化的类的readResolve方法。
 *
 * 所以,原理也就清楚了,主要在Singleton中定义readResolve方法,并在该方法中指定要返回的对象的生成策略,就可以防止单例被破坏。
 */

public class TestProtoy implements Serializable {


    private String name;

    private static final long serialVersionUID = -5615139646023391991L;


   // 再ObjectInputputStream 的readObject ,序列化会通过反射调用无参数的构造方法创建一个新的对象。
    /**
     *Object obj;
     * try {
     *     obj = desc.isInstantiable() ? desc.newInstance() : null;
     * } catch (Exception ex) {
     *     throw (IOException) new InvalidClassException(desc.forClass().getName(),"unable to create instance").initCause(ex);
     * }
     *
     * desc.newInstance() 这里最终也是通过构造方法反射出来一个对象
     */

    // 解决加入readResolve

    // 反序列时候


    private volatile static TestProtoy testProtoy;

    private TestProtoy(){}

    public static TestProtoy getInstance(){
        if(testProtoy != null){
            return testProtoy;
        }
        synchronized (TestProtoy.class){
            if(testProtoy != null){
                return testProtoy;
            }

            return testProtoy = new TestProtoy();
        }
    }

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

    public String getName() {
        return name;
    }


    private Object readResolve() {
        return getInstance();
    }
}

double check 单例模式中,变量为什么定义成volatile?

比如定义一个person: Person p = new Person().

这里大概涉及到三个操作。
第一:new person 在内存开辟空间。
第二:调用person构造方法
第三:将new person的引用复制给变量p.
由于jvm的内部优化,执行过程可能会指令重排。 也就是说,将变量的赋值操作和对象的构造方法操作,可能会重排序,那么double check在并发的时候,可能拿到的一个空的对象,也就是没有属性值的对象。
所以可以使用volatile,禁止指令重排序,相当于是加了一个内存屏障,数据每次读都是从主存中读,写会更新到主存,刷新缓存。
但是volatile不能保证原子,类似变量的自增操作这种,不是一个原子操作。只能保证每次读的都是最新数据,不会说存在线程A更新了,线程B却读的老的。

枚举

package com.reflect.singleton;

import java.io.Serializable;
import java.util.Date;

public enum Way implements Serializable {



    INSTANCE(0,0),
    up(1,2),
    down(3,3);

    private static final long serialVersionUID = -5615139646023391991L;

    private Date date;
    private String name;

    private int code;
    private int desc;

    Way(int code, int desc) {
        this.code = code;
        this.desc = desc;
    }

    private Way() {
    }

    public void dosomething(String name){

        System.out.println(this.hashCode()+"  "+name +"说:"+ this.getCode()+"   "+this.getDesc());
    }



    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }

    public String getName() {
        return name;
    }

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


    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public void setDesc(int desc) {
        this.desc = desc;
    }

    public int getDesc() {
        return desc;
    }

}

test

package com.reflect.singleton;

import java.io.FileInputStream;
import java.io.ObjectInputStream;

public class Test2 {
    public static void main(String[] args) throws Exception {
//
//        System.out.println(Way.up.getCode());
//        Way.up.setCode(99);
//        Way.up.setName("sfafa");
//
//        System.out.println(Way.up.getCode());
//
//        System.out.println(Way.up.getName());
//
//        Way.up.dosomething("小王");
//
//        Way way = Way.up;
//        System.out.println(way.hashCode());
//
//        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("e:/way"));
//        objectOutputStream.writeObject(way);
//        objectOutputStream.flush();
//        objectOutputStream.close();
//
//        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("e:/way"));
//
//        Way way1 = (Way)objectInputStream.readObject();
//
//        System.out.println("way1==:"+way1.hashCode());


//        // 检验枚举反射是否破坏单例
//        Way way = Way.up;
//        System.out.println(way.hashCode());
//        // 获取构造方法
//        Constructor<Way> constructor = Way.class.getDeclaredConstructor(String.class,int.class);
//        constructor.setAccessible(true);
//        Way way1 = constructor.newInstance(); // 这里会直接报错,因为进入这个方法可以发现
//        /**
//         *  if ((clazz.getModifiers() & Modifier.ENUM) != 0)
//         *             throw new IllegalArgumentException("Cannot reflectively create enum objects");
//         *
//         *            会检查这个类是否被ENUM修饰,如果是则抛出异常,反射失败
//         */
//        System.out.println(way1.hashCode());




//
        // double check 构造方法破坏单例
//        Constructor<TestProtoy>  constructor = TestProtoy.class.getDeclaredConstructor();
//        constructor.setAccessible(true);
//        TestProtoy testProtoy = constructor.newInstance();
//        System.out.println(testProtoy.hashCode());
//        System.out.println(TestProtoy.getInstance().hashCode());


//
//            TestProtoy testProtoy = TestProtoy.getInstance();
//        testProtoy.setName("发发发");
//            System.out.println(testProtoy.hashCode() + "   " + testProtoy.getName());
//
//
//        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("e:/testProtoy"));
//        objectOutputStream.writeObject(testProtoy);
//        objectOutputStream.flush();
//        objectOutputStream.close();
//
//
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("e:/testProtoy"));
//
        TestProtoy sfaga = (TestProtoy)objectInputStream.readObject();

//        System.out.println("sfaga==:"+sfaga.hashCode() + "  "+ sfaga.getName());



    }
}

内部类

public class InnerClassLazySingleton {

    // 构造方法私有化

    private InnerClassLazySingleton() {

        //作用防止通过反射破坏单例
        // 如果不加入这个判断,则可以通过反射机制,获取私有的构造方法,暴力破坏单例
        if (LazyInnerClass.LAZY != null) {
            throw new RuntimeException("不允许创建多个实例");
        }
    }

    // //默认使用 LazyInnerClassGeneral 的时候,会先初始化内部类
    // 如果没使用的话,内部类是不加载的

    // static 是为了使单例的空间共享
    public static InnerClassLazySingleton getInstance() {
        // //在返回结果以前,一定会先加载内部类
        return LazyInnerClass.LAZY;
    }

    //默认不加载
    private static class LazyInnerClass {
        public static final InnerClassLazySingleton LAZY = new InnerClassLazySingleton();

    }

}

Enum支持序列化吗

支持,在Java中,只要一个类实现了java.io.Serializable接口,那么它就可以被序列化。

Enum底层实现

当我们使用enmu来定义一个枚举类型的时候,编译器会自动帮我们创建一个final类型的类继承Enum类,所以枚举类型不能被继承,这个类中有几个属性和方法都是static类型的,因为static类型的属性会在类被加载之后被初始化,当一个Java类第一次被真正使用到的时候静态资源被初始化、Java类的加载和初始化过程都是线程安全的。所以,创建一个enum类型是线程安全的。

一旦实现了Serializable接口之后,就不再是单例得了,因为,每次调用 readObject()方法返回的都是一个新创建出来的对象,有一种解决办法就是使用readResolve()方法来避免此事发生。但是,为了保证枚举类型像Java规范中所说的那样,每一个枚举类型极其定义的枚举变量在JVM中都是唯一的,在枚举类型的序列化和反序列化上,Java做了特殊的规定。

在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。同时,编译器是不允许任何对这种序列化机制的定制的,因此禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。原文参考深度分析 Java 的枚举类型

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

EmineWang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值