java对象的序列化

序列化概念
对象序列化:将Java实体对象转化成二进制字节流的过程。
对象反序列化:将二进制字节流恢复为Java实体对象的过程。
主要用途
1. 持久化对象到磁盘
2. 网络传输
3. 对象的拷贝

序列化的实现

  1. 通过实现Serializable接口,java有默认的实现算法,我们也可以自定义序列化算法(用于实现一些特殊的需求),在反序列化时,不会调用该序列化的任何构造方法。
  2. 通过实现Externalizable接口,需要我们自己写序列化规则。

实现Serializable接口

package com.zd.test.java.ds;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class SerializableTest {

    //首先我们用这个类来检验,探究一下序列化的算法。
    static class Person implements Serializable{
        /**
         * 版本号
         */
        private static final long serialVersionUID = -1285437413613384110L;
        String name;
        Integer age;
        transient String address;
        Dog dog;
        public Person(String name, Integer age,String address,Dog dog) {
            super();
            this.name = name;
            this.age = age;
            this.address = address;
            this.dog = dog;
        }

        @Override
        public String toString() {
            return "Person [name=" + name + ", age=" + age + ", address="
                    + address + ", dog=" + dog + "]";
        }
    }

    static class Dog implements Serializable{
        private static final long serialVersionUID = 5581802748286331206L;
        String name;
        public Dog(String name) {
            super();
            this.name = name;
        }
    }
    //测试自定义的序列化方案
    static class Man implements Serializable{
        /**
         * 版本号
         */
        private static final long serialVersionUID = -1285437413613384110L;
        String name;
        Integer age;
        transient String address;
        Dog dog;
        transient String password;
        public Man(String name, Integer age,String address,String password,Dog dog) {
            super();
            this.name = name;
            this.age = age;
            this.address = address;
            this.dog = dog;
            this.password = password;
        }

        @Override
        public String toString() {
            return "Man [name=" + name + ", age=" + age + ", address="
                    + address + ", dog=" + dog+", password=" + password + "]";
        }
        //重写持久化规则
        private void writeObject(ObjectOutputStream out) throws IOException{
            out.defaultWriteObject(); //执行默认的写对象方法,会把这当前中非静态成员变量和非transient成员变量写到流中
            //在这里,我们把address这个属性先转换成大写,再持久化
            if (address != null) {
                out.writeUTF(address.toUpperCase());
            }
            if (password != null) {
                out.writeUTF(new StringBuffer(password).reverse().toString());
            }
            //注意这里无需关闭流,否则会失败,这只是一个普通的回调方法,java底层调用完此方法之后还会处理其他的逻辑。
            //out.close();
        }

        private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException{
            in.defaultReadObject();
            Object obj = in.readUTF();
            if (obj != null) {
                address = obj.toString();
            }
            obj = in.readUTF();
            if (obj != null) {
                password = new StringBuffer(obj.toString()).reverse().toString();
            }
        }
    }
    public static void main(String[] args) throws ClassNotFoundException, IOException {
        //1.测试一般用法,顺便测试一下transient关键字
        Person person = new Person("张三", 23, "北京市",new Dog("旺旺"));
        //如果person里面的Dog类没有实现Serializable接口,会报异常:java.io.NotSerializableException: com.zd.test.java.ds.SerializableTest$Dog
        Person p = deepCopy(person);
        System.out.println(p);
        //结果:Person [name=张三, age=23, address=null] 
        //结论:被transient关键字修饰的对象默认是不能够持久化的,但是我们可以重写持久化规则。
        //测试重写测试化规则,持久化被transient修饰的成员变量,加密重要隐私信息
        //需要按自定义方式序列化的成员变量,是否定义为transient类型无关紧要。
        Man man = new Man("张三", 23, "北京市", "admin",new Dog("旺旺"));
        Man m = deepCopy(man);
        System.out.println(m);
    }
    public static <T> T deepCopy(T t) throws IOException, ClassNotFoundException{
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(t);
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        @SuppressWarnings("unchecked")
        T clone = (T) objectInputStream.readObject();
        return clone;
    }
}

实现Externalizable接口

package com.zd.test.java.ds;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;

public class ExternalizableTest {
    static class Person implements Externalizable{
        String name;
        transient String password;
        Integer age;
        @Override
        public void writeExternal(ObjectOutput out) throws IOException {
            if (name != null) {
                out.writeUTF(name);
            }

            if (password != null) {
                out.writeUTF(new StringBuffer(password).reverse().toString());
            }

            if (age != null) {
                out.writeInt(age);
            }
        }

        @Override
        public void readExternal(ObjectInput in) throws IOException,
                ClassNotFoundException {
            Object obj = null;
            if ((obj = in.readUTF()) != null) {
                name = obj.toString();
            }

            if ((obj = in.readUTF()) != null) {
                password = new StringBuffer(obj.toString()).reverse().toString();
            }

            if ((obj = in.readInt()) != null) {
                age = (Integer) obj;
            }
        }

        public Person(String name, String password, Integer age) {
            super();
            this.name = name;
            this.password = password;
            this.age = age;
        }

        @Override
        public String toString() {
            return "Person [name=" + name + ", password=" + password + ", age="
                    + age + "]";
        }
        //java.io.InvalidClassException: com.zd.test.java.ds.ExternalizableTest$Person; no valid constructor
        public Person() { //给一个无参的构造器
        }
    }

    public static void main(String[] args) throws Exception{
        Person person = new Person("张三", "admin", 23);
        Person person2 = deepCopy(person);
        System.out.println(person2);
    }
    public static <T> T deepCopy(T t) throws IOException, ClassNotFoundException{
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(t);
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        @SuppressWarnings("unchecked")
        T clone = (T) objectInputStream.readObject();
        return clone;
    }
}

补充:
默认序列化方式的不足

  1. 不宜对对象中对安全要求比较高的敏感数据进行序列化,安全性极低。
  2. 不会检查对象的成员变量是否合乎正确的约束条件。
  3. 默认的序列化方式需要对对象图(继承等关系)进行递归遍历,如果对象图很复杂,会消耗很多空间和时间,甚至引起Java虚拟机堆栈溢出。

总结
一般我们需要掌握的自定义序列化方式:
1. 实现Serializable接口,并且提供private的writeObject()和readObject()。
2. 实现Externalizable接口,实现writeExternal()和readExternal()方法,必须提供public无参的构造方法,否则会报异常,上面案例有涉及到。
3. 关于默认的序列化方案和自定义序列化方案,根据需求而定。

另外关于serialVersionUID,主要是为了兼容序列化类的变化,便于项目后期对序列化类的扩充和修改。

当然,还有很多的序列化方案,比如我们在java业务中前台和后台使用的json形式就是序列化的一种,它有着可以跨语言的优势,做过JavaEE的都知道怎么使用。

更多的序列化方案可以可以下https://github.com/eishay/jvm-serializers/wiki里面的,里面有详细比较。
这里写图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值