序列化相关知识总结

目录

  • 一、序列化
    • 1.1 基本概念
      • 1.1.1 序列化
      • 1.1.2 反序列化
      • 1.1.3 数据结构、对象与二进制串
      • 1.1.4 序列化/反序列化的目的
    • 1.2 几种常见的序列化和反序列化协议
      • 1.2.1 XML&SOAP
      • 1.2.2 JSON(Javascript Object Notation)
      • 1.2.3 Protobuf
  • 二、安卓下的序列化方案
    • 2.1 Serializable接口
    • 2.2 使用示例
      • 2.2.1 Serializable基础使用
      • 2.2.2 Externalizable基础使用
      • 2.2.3 serialVersionUID的使用
      • 2.2.4 瞬态 trasient 变量的作用
      • 2.2.5 类中的成员未实现可序列化接口
      • 2.2.6 父类未序列化,子类实现了序列化的情况
      • 2.2.7 父类实现序列化,子类控制自己是否实现序列化
      • 2.2.8 序列化枚举类
      • 2.2.9 序列化单例
    • 2.3 序列化流程
    • 2.4 安卓Parcelable接口
      • 2.4.1 重要概念和用法
        • 2.4.1.1 实现 Parcelable 接口
        • 2.4.1.2 内置数据类型支持
        • 2.4.1.3 性能优化
        • 2.4.1.4 使用场景
        • 2.4.1.5 注意事项
      • 2.4.2 Parcelable和Serializable的区别
      • 2.4.3 Parcelable基础使用


一、序列化

1.1 基本概念

  1. 由于在系统底层,数据的传输形式是简单的字节序列形式传递,即在底层,系统不认识对象,只认识字节序列,而为了达到进程通讯的目的,需要先将数据序列化,而序列化就是将对象转化字节序列的过程。相反地,当字节序列被运到相应的进程的时候,进程为了识别这些数据,就要将其反序列化,即把字节序列转化为对象
  2. 无论是在进程间通信、本地数据存储又或者是网络数据传输都离不开序列化的支持。而针对不同场景选择合适的序列化方案对于应用的性能有着极大的影响。
  3. 从广义上讲,数据序列化就是将数据结构或者是对象转换成我们可以存储或者传输的数据格式的一个过程,在序列化的过程中,数据结构或者对象将其状态信息写入到临时或者持久性的存储区中,而在对应的反序列化过程中,则可以说是生成的数据被还原成数据结构或对象的过程。
  4. 数据序列化相当于是将我们原先的对象序列化概念做出了扩展,在对象序列化和反序列化中,我们熟知的有两种方法,其一是Java语言中提供的Serializable接口,其二是Android提供的Parcelable接口。而在这里,因为我们对这个概念做出了扩展,因此也需要考虑几种专门针对数据结构进行序列化的方法,如现在那些个开放API一般返回的数据都是JSON格式的,又或者是我们Android原生的SQLite数据库来实现数据的本地存储,从广义上来说,这些都可以算做是数据的序列化。

1.1.1 序列化

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

1.1.2 反序列化

将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程

1.1.3 数据结构、对象与二进制串

不同的计算机语言中,数据结构,对象以及二进制串的表示方式并不相同。
数据结构和对象:对于类似 Java 这种完全面向对象的语言,工程师所操作的一切都是对象(Object),来自于类的实例化。在 Java 语言中最接近数据结构的概念,就是 POJO(Plain Old JavaObject)或者 Javabean--那些只有 setter/getter 方法的类。而在 C 二进制串:序列化所生成的二进制串指的是存储在内存中的一块数据。C 语言的字符串可以直接被传输层使用,因为其本质上就是以’0’结尾的存储在内存中的二进制串。在 Java 语言里面,二进制串的概念容易和 String 混淆。实际上String 是 Java 的一等公民,是一种特殊对象(Object)。对于跨语言间的通讯,序列化后的数据当然不能是某种语言的特殊数据类型。二进制串在 Java 里面所指的是 byte[ ],byte 是 Java 的 8 中原生数据类型之一(Primitive data types)

1.1.4 序列化/反序列化的目的

  • 序列化: 主要用于网络传输,数据持久化,一般序列化也称为编码(Encode)
  • 反序列化: 主要用于从网络,磁盘上读取字节数组还原成原始对象,一般反序列化也称为解码
    (Decode)
    具体的讲:
  • 永久的保存对象数据(将对象数据保存在文件当中,或者是磁盘中)
  • 通过序列化操作将对象数据在网络上进行传输(由于网络传输是以字节流的方式对数据进行传输的,因此序列化的目的是将对象数据转换成字节流的形式)
  • 将对象数据在进程之间进行传递(Activity之间传递对象数据时,需要在当前的Activity中对对象数据进行序列化操作,在另一个Activity中需要进行反序列化操作讲数据取出)
    Java平台允许我们在内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,即这些对象的生命周期不会比JVM的生命周期更长(即每个对象都在JVM中),但在现实应用中,就可能要停止JVM运行,但有要保存某些指定的对象,并在将来重新读取被保存的对象。这时Java对象序列化就能够实现该功能。(可选择入数据库、或文件的形式保存)
  • 序列化对象的时候只是针对变量进行序列化,不针对方法进行序列化。
  • 在Intent之间,基本的数据类型直接进行相关传递即可,但是一旦数据类型比较复杂的时候,就需要进行序列化操作了。

1.2 几种常见的序列化和反序列化协议

1.2.1 XML&SOAP

XML 是一种常用的序列化和反序列化协议,具有跨机器,跨语言等优点,SOAP(Simple Object Access protocol) 是一种被广泛应用的,基于 XML 为序列化和反序列化协议的结构化消息传递协议

1.2.2 JSON(Javascript Object Notation)

JSON 起源于弱类型语言 Javascript, 它的产生来自于一种称之为"Associative array"的概念,其本质是就是采用"Attribute-value"的方式来描述对象。实际上在 Javascript 和 PHP 等弱类型语言中,类的描述方式就是 Associative array。JSON 的如下优点,使得它快速成为最广泛使用的序列化协议之一。

  • 这种 Associative array 格式非常符合工程师对对象的理解。
  • 它保持了 XML 的人眼可读(Human-readable)的优点。
  • 相对于 XML 而言,序列化后的数据更加简洁。 研究表明:XML 所产生序列化之后文件的大小接近 JSON 的两倍
  • 它具备 Javascript 的先天性支持,所以被广泛应用于 Web browser 的应用常景中,是 Ajax 的事实标准协议。
  • 与 XML 相比,其协议比较简单,解析速度比较快。
  • 松散的 Associative array 使得其具有良好的可扩展性和兼容性

1.2.3 Protobuf

Protobuf 具备了优秀的序列化协议的所需的众多典型特征。

  • 标准的 IDL 和 IDL 编译器,这使得其对工程师非常友好。
  • 序列化数据非常简洁,紧凑,与 XML 相比,其序列化之后的数据量约为 1/3 到 1/10。
  • 解析速度非常快,比对应的 XML 快约 20-100 倍。
  • 提供了非常友好的动态库,使用非常方便,反序列化只需要一行代码。

二、安卓下的序列化方案

2.1 Serializable接口

public interface Serializable {
}
public interface Externalizable extends java.io.Serializable {

    void writeExternal(ObjectOutput out) throws IOException;

    void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}

2.2 使用示例

2.2.1 Serializable基础使用

public class demo01 {

    static class User implements Serializable {
        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public String name;
        public int age;

        public String nickName;

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

    static class User1 implements Serializable {

        private static final long serialVersionUID = 2;

        public User1(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public String name;
        public int age;
        public String nickName;

        @Override
        public String toString() {
            return "User1{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    ", nickName=" + nickName +
                    '}';
        }
    }
    public static String basePath = System.getProperty("user.dir") + "\\";
    public static String tmp = "D:\\henryTest\\";

    public static void main(String[] args) {
        NoSerialIdTest();
    //    HasSerialIdTest();
    }

    private static void NoSerialIdTest() {
        User user = new User("zero", 20);
        SerializeableUtils.saveObject(user,tmp+"a.out");
        System.out.println("1: " + user);
        user = SerializeableUtils.readObject(tmp + "a.out");
        System.out.println("反序列化: 2: " + user);
    }

    private static void HasSerialIdTest() {
        User1 user = new User1("zero", 18);
//        SerializeableUtils.saveObject(user,tmp+"b.out");
//        System.out.println("1: " + user);
        user = SerializeableUtils.readObject(tmp + "b.out");
        System.out.println("2: " + user);
    }
}
public class SerializeableUtils {

    public static  <T> byte[] serialize(T t) throws Exception{
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(out);
        oos.writeObject(t);
        return out.toByteArray();
    }

    public static <T> T deserialize(byte[] bytes)throws Exception{
        //TODO:
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
        T t = (T)ois.readObject();
        return t;
    }

    /**
     *
     * @param obj
     * @param path
     * @return
     */
    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;
    }

    /**
     * 反序列化对象
     *
     * @param path
     * @param <T>
     * @return
     */
    @SuppressWarnings("unchecked ")
    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;
    }

}

输出:
在这里插入图片描述
生成文件:
在这里插入图片描述

2.2.2 Externalizable基础使用

writeExternal(ObjectOutput out)
readExternal(ObjectOutput out)
注意点:

  • 读写顺序要求一致
  • 读写的成员变量的个数
  • 必须要有一个public的无参构造函数
public class demo06 {

    static class User implements Externalizable {
        //必须要一个public的无参构造函数
        public User() {
        }

        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public String name;
        public int age;


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

        @Override
        public void writeExternal(ObjectOutput out) throws IOException {
            out.writeObject(name);
            out.writeInt(age);
        }

        @Override
        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
            name = (String) in.readObject();
            age = in.readInt();
        }
    }

    public static String basePath = System.getProperty("user.dir") + "\\";
    public static String tmp = "D:\\henryTest\\";

    public static void main(String[] args) {
        ExternalableTest();
    }

    private static void ExternalableTest() {
        User user = new User("zero", 18);
        System.out.println("1: " + user);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ObjectOutputStream oos = null;
        byte[] userData = null;
        try {
            oos = new ObjectOutputStream(out);
            oos.writeObject(user);
            userData = out.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }


        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new ByteArrayInputStream(userData));
            user = (User) ois.readObject();
            System.out.println("反序列化后 2: " + user);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

输出:
在这里插入图片描述

2.2.3 serialVersionUID的使用

serialVersionUID 是一个 private static final long 型 ID, 当它被印在对象上时, 它通常是对象的哈希码,你可以使用 serialver 这个 JDK 工具来查看序列化对象的 serialVersionUID。SerialVerionUID 用于对象的版本控制。 也可以在类文件中指定 serialVersionUID。 不指定serialVersionUID的后果是,当你添加或修改类中的任何字段时, 则已序列化类将无法恢复, 因为为新类和旧序列化对象生成的serialVersionUID 将有所不同。

与2.2.1 基础使用对比:
不加序列化版本ID测试项,去掉nickName属性,去掉写入操作,直接读取报错。

......
    static class User implements Serializable {
        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public String name;
        public int age;

//        public String nickName;

        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
//                    ", nickName=" + nickName +
                    '}';
        }
    }
.......
    private static void NoSerialIdTest() {
        User user = new User("zero", 20);
//        SerializeableUtils.saveObject(user,tmp+"a.out");
//        System.out.println("1: " + user);
        user = SerializeableUtils.readObject(tmp + "a.out");
        System.out.println("反序列化: 2: " + user);
    }

报错:流对象和本地对象版本id不同
在这里插入图片描述

添加序列化版本ID测试项,去掉nickName属性,只要 serialVersionUID值不变不会发生此类报错 。这里不再添加代码和测试结果。

2.2.4 瞬态 trasient 变量的作用

在序列化过程中,有时候我们希望某些对象的字段不被序列化,这时可以使用关键字 transient 来标记这些字段。
当一个字段被标记为 transient 时,该字段的值不会被序列化,即在将对象转换为字节流时,这些字段的值不会被包含在序列化数据中。在反序列化时,这些字段会被赋予默认值

/**
 * 瞬态transient的作用
 */
public class demo02 {

    static class User implements Serializable {

        public User() {
            System.out.println("=============");
        }

        public User(String name, int age) {
            System.out.println("==============");
            this.name = name;
            this.age = age;
        }

        public String name;
        public int age;

        public transient String nickName;

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

    public static String basePath = System.getProperty("user.dir") + "\\";
    public static String tmp = "D:\\henryTest\\";

    public static void main(String[] args) {
        trasientTest();
    }

    private static void trasientTest() {
        User user = new User("zero", 18);
        user.nickName = "Zero老师";
        System.out.println("1: " + user);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ObjectOutputStream oos = null;
        byte[] userData = null;
        try {
            oos = new ObjectOutputStream(out);
            oos.writeObject(user);
            userData = out.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }

        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new ByteArrayInputStream(userData));
            user = (User) ois.readObject();
            System.out.println("反序列化后 2: " + user);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

输出:
在这里插入图片描述

2.2.5 类中的成员未实现可序列化接口

/**
 * 类中的一个成员未实现可序列化接口
 */
public class demo03 {


    static class NickName {
        private String firstName;
        private String lastName;

        public NickName(String firstName, String lastName) {
            this.firstName = firstName;
            this.lastName = lastName;
        }

        public String getFirstName() {
            return firstName;
        }

        public void setFirstName(String firstName) {
            this.firstName = firstName;
        }

        public String getLastName() {
            return lastName;
        }

        public void setLastName(String lastName) {
            this.lastName = lastName;
        }

        @Override
        public String toString() {
            return "NickName{" +
                    "firstName='" + firstName + '\'' +
                    ", lastName='" + lastName + '\'' +
                    '}';
        }
    }

    static class User implements Serializable {
        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public User(String name, int age, NickName nickName) {
            this.name = name;
            this.age = age;
            this.nickName = nickName;
        }

        public String getName() {
            return name;
        }

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

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }

        public NickName getNickName() {
            return nickName;
        }

        public void setNickName(NickName nickName) {
            this.nickName = nickName;
        }

        private String name;
        private int age;

        private NickName nickName;

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


    public static void main(String[] args) {
        NotSerializeTest();
    }

    private static void NotSerializeTest() {
        User user = new User("zero", 18, new NickName("Henry", "老师"));
        System.out.println("1: " + user);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ObjectOutputStream oos = null;
        byte[] userData = null;
        try {
            oos = new ObjectOutputStream(out);
            oos.writeObject(user);
            userData = out.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new ByteArrayInputStream(userData));
            user = (User) ois.readObject();
            System.out.println("反序列化后 2: " + user);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

会报未实现序列化异常:
在这里插入图片描述

2.2.6 父类未序列化,子类实现了序列化的情况

默认父类属性不会被序列化

/**
 * 1. 如果不需要保存父类的值,那么没什么问题,只不过序列化会丢失父类的值
 * 2. 如果在子类保存父类的值,则需要在父类提供一个无参构造,不然报错InvalidClassException
 * 子类在序列化的时候需要额外的序列化父类的域(如果有这个需要的话)。那么在反序列的时候,
 * 由于构建User实例的时候需要先调用父类的构造函数,然后才是自己的构造函数。反序列化时,为了构造父对象,只能调用父类的无参构造函数作为默认的父对象,因此当我们取父对象的变量值时,
 * 它的值是调用父类无参构造函数后的值。如果你考虑到这种序列化的情况,在父类无参构造函数中对变量进行初始化。或者在readObject方法中进行赋值。
 * 我们只需要在Person中添加一个空的构造函数即可
 * 3. 自定义序列化过程
 */
public class demo04 {



    static class Person{
        private String sex;
        private int id;

        public Person() {
        }

        public Person(String sex, int id) {
            this.sex = sex;
            this.id = id;
        }

        public String getSex() {
            return sex;
        }

        public void setSex(String sex) {
            this.sex = sex;
        }

        public int getId() {
            return id;
        }

        public void setId(int id) {
            this.id = id;
        }

        @Override
        public String toString() {
            return "Person{" +
                    "sex='" + sex + '\'' +
                    ", id=" + id +
                    '}';
        }
    }

    static class User extends Person implements Serializable {
        public User(String name, int age,String sex,int id) {
            super(sex,id);
            this.name = name;
            this.age = age;
        }

        public User(){
            super();
        };


        public String name;
        public int age;

//        private void writeObject(ObjectOutputStream out) throws IOException {//不是重写父类的方案
//            out.defaultWriteObject();
//            out.writeObject(getSex());
//            out.writeInt(getId());
//        }
//
//        private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
//            in.defaultReadObject();
//            setSex((String)in.readObject());
//            setId(in.readInt());
//        }


        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    "} " + super.toString();
        }
    }

    public static String basePath = System.getProperty("user.dir") + "\\";
    public static String tmp = "D:\\henryTest\\";

    public static void main(String[] args) {
        parentNotSerializeTest();
    }

    private static void parentNotSerializeTest() {

        User user = new User("zero", 18,"男",1);
        System.out.println("1: " + user);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ObjectOutputStream oos = null;
        byte[] userData = null;
        try {
            oos = new ObjectOutputStream(out);
            oos.writeObject(user);
            userData = out.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }


        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new ByteArrayInputStream(userData));
            user = (User)ois.readObject();
            System.out.println("反序列化后 2: " + user);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

输出:
在这里插入图片描述
如果想要父类属性写入序列化过程:子类重写方法并调用父类set方法设置父类值

        private void writeObject(ObjectOutputStream out) throws IOException {//不是重写父类的方案
            out.defaultWriteObject();
            out.writeObject(getSex());
            out.writeInt(getId());
        }

        private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
            in.defaultReadObject();
            setSex((String)in.readObject());
            setId(in.readInt());
        }

在这里插入图片描述

2.2.7 父类实现序列化,子类控制自己是否实现序列化

public class demo05 {

    static class Person implements Serializable {
        private static final long serialVersionUID = 5850510148907441688L;
        private String sex;
        private int id;

        public Person() {
        }

        public Person(String sex, int id) {
            this.sex = sex;
            this.id = id;
        }

        public String getSex() {
            return sex;
        }

        public void setSex(String sex) {
            this.sex = sex;
        }

        public int getId() {
            return id;
        }

        public void setId(int id) {
            this.id = id;
        }

        @Override
        public String toString() {
            return "Person{" +
                    "sex='" + sex + '\'' +
                    ", id=" + id +
                    '}';
        }
    }

    static class User extends Person {
        public User(String name, int age, String sex, int id) {
            super(sex, id);
            this.name = name;
            this.age = age;

        }

        public String name;
        public int age;

        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    "} " + super.toString();
        }
    }

    static class User1 extends Person {
        public User1(String name, int age, String sex, int id) {
            super(sex, id);
            this.name = name;
            this.age = age;
        }

        public String name;
        public int age;

        private void writeObject(ObjectOutputStream out) throws IOException {
            throw new NotSerializableException("Can not serialize this class");
        }

        private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
            throw new NotSerializableException("Can not serialize this class");
        }

        private void readObjectNoData() throws ObjectStreamException {
            throw new NotSerializableException("Can not serialize this class");
        }

        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    "} " + super.toString();
        }
    }

    public static String basePath = System.getProperty("user.dir") + "\\";
    public static String tmp = "D:\\henryTest\\";

    public static void main(String[] args) {
        ChildNotSerializeTest();
//          ChildNotSerializeTest1();
    }

    private static void ChildNotSerializeTest() {

        User user = new User("zero", 18, "男", 1);
        System.out.println("1: " + user);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ObjectOutputStream oos = null;
        byte[] userData = null;
        try {
            oos = new ObjectOutputStream(out);
            oos.writeObject(user);
            userData = out.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }

        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new ByteArrayInputStream(userData));
            user = (User) ois.readObject();
            System.out.println("反序列化后 2: " + user);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static void ChildNotSerializeTest1() {

        User1 user = new User1("zero", 18, "男", 1);
        System.out.println("1: " + user);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ObjectOutputStream oos = null;
        byte[] userData = null;
        try {
            oos = new ObjectOutputStream(out);
            oos.writeObject(user);
            userData = out.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }

        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new ByteArrayInputStream(userData));
            user = (User1) ois.readObject();
            System.out.println("反序列化后 2: " + user);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

输出:子类属性默认实现序列化
在这里插入图片描述
如何控制子类是否实现序列化?
重写writeObject readObject方法

......
    public static void main(String[] args) {
//        ChildNotSerializeTest();
        ChildNotSerializeTest1();
    }
......

输出:
在这里插入图片描述

2.2.8 序列化枚举类

Java 的序列化机制对枚举类型进行了特殊处理,主要是为了保证枚举类的序列化和反序列化的安全性和一致性:

  • 枚举常量的序列化:在序列化枚举类时,Java 只会序列化枚举常量的名称,而不是整个枚举对象。这是因为枚举常量在 JVM 中是单例的,可以通过枚举常量的名称来获取对应的枚举对象,所以只序列化名称即可。
  • 枚举类的特殊处理:在反序列化枚举类时,Java 会检查反序列化的对象是否是枚举类,并且会根据反序列化的名称来获取对应的枚举常量。这样可以确保反序列化后的对象仍然是枚举类的常量之一。
  • 枚举类的安全性:由于枚举常量是在编译时确定的,枚举类的序列化和反序列化过程中不会受到外部影响,从而确保了枚举类的安全性和一致性。
  • 枚举类的性能优化:由于枚举常量是单例的,序列化和反序列化时只需要处理枚举常量的名称,而不需要保存和还原整个对象,这样可以提高序列化和反序列化的性能。
package com.MyStudy.SerializationTest;
enum Num1{
    ONE,
    TWO,
    THREE;

    public void printValues(){
        System.out.println("ONE: "+ ONE.ordinal() + ", TWO: " + TWO.ordinal() + ", THREE: " + THREE.ordinal());
    }
}

/**
 * Java的序列化机制针对枚举类型是特殊处理的。简单来讲,在序列化枚举类型时,只会存储枚举类的引用和枚举常量的名称。随后的反序列化的过程中,
 * 这些信息被用来在运行时环境中查找存在的枚举类型对象。
 */
public class EnumSerializableTest {
//
    public static void main(String[] args) throws Exception {
        byte[] bs =SerializeableUtils.serialize(Num1.THREE);
        Num1.THREE.printValues();
        System.out.println("hashCode: " + Num1.THREE.hashCode());
        System.out.println("反序列化后");
        Num1 s1 = SerializeableUtils.deserialize(bs);
        s1.printValues();
        System.out.println("hashCode: " + s1.hashCode());
        System.out.println("== " + (Num1.THREE == s1));
    }

}

输出:
在这里插入图片描述

2.2.9 序列化单例

在 Java 中,单例模式是一种常见的设计模式,用于确保一个类只有一个实例,并提供一个全局访问点。在某些情况下,需要将单例对象序列化和反序列化,以便在不同的 JVM 实例间传输或保存状态。在这种情况下,需要特殊处理单例对象的序列化,以确保单例模式的特性不受影响。

1.序列化单例对象:在序列化单例对象时,需要注意以下几点:

  • 序列化过程中,只序列化单例对象的状态数据,而不序列化单例对象的类信息或构造方法。
  • 序列化过程中,需要实现 writeReplace() 方法,返回一个代理对象,以确保反序列化后仍然是同一个单例对象。
  • 序列化过程中,可以通过实现 readResolve() 方法,在反序列化时返回原始单例对象,以确保反序列化后仍然是同一个单例对象。

2.保护单例模式的特性:在序列化和反序列化过程中,需要确保单例对象的特性不受影响,例如确保只有一个实例存在、全局访问点等。通过特殊处理序列化和反序列化过程,可以保护单例模式的特性。
3.避免多次实例化:在反序列化时,需要确保只有一个实例被还原,并且不会创建额外的实例。通过特殊处理反序列化过程,可以避免多次实例化单例对象。
4.实现序列化接口:单例类需要实现 Serializable 接口,以便支持序列化和反序列化操作。

  private void writeObject0(Object obj, boolean unshared)
        throws IOException
    {
......
            Class repCl;
            desc = ObjectStreamClass.lookup(cl, true);
            if (desc.hasWriteReplaceMethod() &&
            //是否有WriteReplace方法
                (obj = desc.invokeWriteReplace(obj)) != null &&
                //通过反射调用
                (repCl = obj.getClass()) != cl)
            {
......
 private Object readOrdinaryObject(boolean unshared)
        throws IOException
  ......
        if (obj != null &&
            handles.lookupException(passHandle) == null &&
            desc.hasReadResolveMethod())
            //是否有readresolve方法
        {
            Object rep = desc.invokeReadResolve(obj);
            //通过反射调用readresolve方法
            if (unshared && rep.getClass().isArray()) {
                rep = cloneArray(rep);
            }
   ......
    }

示例:

class Singleton implements Serializable{
    public static Singleton INSTANCE = new Singleton();

    private Singleton(){}

    private Object readResolve(){
        return INSTANCE;
    }
}

2.3 序列化流程

上述的writeObject readObject方法是如何调用的?这就涉及到了序列化的流程
在这里插入图片描述

简单阐述writeObject具体调用的关键点,readObject同理。

 private void writeSerialData(Object obj, ObjectStreamClass desc)
        throws IOException
    {
        ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
        //获取ObjectStreamClass对象
        for (int i = 0; i < slots.length; i++) {
            ObjectStreamClass slotDesc = slots[i].desc;
            if (slotDesc.hasWriteObjectMethod()) {
            //类是否有WriteObjectMethod方法?
                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);
                    //貌似通过反射调用了WriteObject方法
                    bout.setBlockDataMode(false);
                    bout.writeByte(TC_ENDBLOCKDATA);
                } finally {
                    curContext.setUsed();
                    curContext = oldContext;
                    if (extendedDebugInfo) {
                        debugInfoStack.pop();
                    }
                }

                curPut = oldPut;
            } else {
                defaultWriteFields(obj, slotDesc);
            }
        }
    }

2.4 安卓Parcelable接口

在安卓开发中,Parcelable 是一种用于在不同组件之间传递复杂对象的接口。通过实现 Parcelable 接口,可以将对象序列化为字节流,以便在不同组件之间进行高效传输,如在 Activity 之间传递数据或在 Intent 中传递对象等。

  1. 高效传输对象:相比实现 Serializable 接口,实现 Parcelable 接口可以实现更高效的对象传输。Parcelable 的实现方式更为轻量,不会像 Serializable 那样产生大量的临时对象,因此在传输大量数据时更加高效。
  2. 自定义序列化:通过实现 Parcelable 接口,可以完全控制对象的序列化和反序列化过程。开发者可以精确地定义对象的序列化方式,提高了灵活性和性能。
  3. 性能优化:在安卓开发中,频繁地在组件之间传递大量数据会影响性能。使用 Parcelable 可以减少序列化和反序列化的开销,提升应用的性能。
  4. 支持内置数据类型:Parcelable 接口支持传递内置数据类型(如整型、字符串等)以及自定义对象,使得在安卓应用中传递复杂对象变得更加方便。
  5. 适用范围:Parcelable 接口通常用于在安卓应用内部传递对象,如在 Activity 之间传递数据、在 Fragment 之间传递参数等。对于需要跨进程传输的情况,也可以使用 Parcelable 接口。

2.4.1 重要概念和用法

2.4.1.1 实现 Parcelable 接口

要使一个类可序列化,需要让该类实现 Parcelable 接口,并实现以下方法:

describeContents():返回对象的特殊标识符,一般返回0即可。
writeToParcel(Parcel dest, int flags):将对象的数据写入 Parcel 对象,以便在不同组件之间传输。
Creator 接口:用于反序列化对象,包含 createFromParcel(Parcel source) 和 newArray(int size) 方法。

2.4.1.2 内置数据类型支持

Parcelable 支持传递内置数据类型(如整型、字符串等)和自定义对象。内置数据类型可以直接通过 Parcel 的写入和读取方法进行操作,而对于自定义对象,需要在 writeToParcel() 方法中逐个写入对象的字段,然后在 Creator 的 createFromParcel() 方法中逐个读取。

2.4.1.3 性能优化

实现 Parcelable 接口可以减少序列化和反序列化的开销,提高传输对象的效率。这对于在 Android 应用中频繁传输大量数据的场景尤为重要,可以提升应用的性能和用户体验。

2.4.1.4 使用场景

在不同 Activity 或 Fragment 之间传递数据。
在 Service 和 Activity 之间传递数据。
在 Intent 中传递自定义对象。

2.4.1.5 注意事项

应确保自定义对象中的每个字段都能正确序列化和反序列化。
序列化和反序列化的顺序应该保持一致。
避免在 Parcelable 中传递大量数据,以免影响性能。

2.4.2 Parcelable和Serializable的区别

SerializableParcelable
通过对IO硬盘操作,速度较慢直接在内存操作,效率高,性能好
大小不受限制一般不超过1M,修改内核也只能4M
大量使用反射,产生内存碎片

2.4.3 Parcelable基础使用

import android.os.Parcel;
import android.os.Parcelable;

public class User implements Parcelable {
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    protected User(Parcel in) {
        name = in.readString();
        age = in.readInt();
    }

    public static final Creator<User> CREATOR = new Creator<User>() {
        @Override
        public User createFromParcel(Parcel in) {
            return new User(in);
        }

        @Override
        public User[] newArray(int size) {
            return new User[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
        dest.writeInt(age);
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}
// 创建一个User对象
User user = new User("Alice", 25);

// 将User对象放入Intent中传递给另一个Activity
Intent intent = new Intent(this, SecondActivity.class);
intent.putExtra("user", user);
startActivity(intent);

// 在另一个Activity中获取传递的User对象
User receivedUser = getIntent().getParcelableExtra("user");
String userName = receivedUser.getName();
int userAge = receivedUser.getAge();

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/445603.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

RabbitMQ中4种交换机的Java连接代码

目录 1.直连交换机&#xff08;Direct&#xff09; 生产者代码示例 消费者代码示例 2.RabbitMQ连接工具类 3.Fanout交换机&#xff08;扇出交换机&#xff0c;广播&#xff09; 生产者 消费者 4.Topic交换机&#xff08;主题交换机&#xff09; 生产者 消费者 5.Hea…

数据库-第六/七章 关系数据理论和数据库设计【期末复习|考研复习】

前言 总结整理不易&#xff0c;希望大家点赞收藏。 给大家整理了一下数据库系统概论中的重点概念&#xff0c;以供大家期末复习和考研复习的时候使用。 参考资料是王珊老师和萨师煊老师的数据库系统概论(第五版)。 数据库系统概论系列文章传送门&#xff1a; 第一章 绪论 第二/…

【Docker】容器的概念

容器技术&#xff1a;容器技术是基于虚拟化技术的&#xff0c;它使应用程序从一个计算机环境快速可靠地转移到另一个计算机环境中&#xff0c;可以说是一个新型地虚拟化技术。 一、docker容器 Docker:是一个开源地容器引擎Docker 是一种轻量级的容器化技术&#xff0c;其主要原…

阿里云服务器租用多少钱一个月?9元1个月?

阿里云服务器租用多少钱一个月&#xff1f;9元1个月&#xff1f;已经降价到5元一个月了。阿里云服务器1个月最低5元/月起&#xff0c;阿里云服务器价格可以按年、按月和按小时购买&#xff0c;本文阿里云服务器网aliyunfuwuqi.com来详细说下阿里云服务器一个月收费价格表&#…

计算机系统结构-中断例题笔记

背景&#xff1a;计算机系统结构考试中&#xff0c;中断处理程序、运行程序的过程示意图是重要考点。 中断概念&#xff1a;CPU中止正在执行的程序&#xff0c;转去处理随机提出的请求&#xff0c;待处理完后&#xff0c;再回到原先被打断的程序继续恢复执行的过程。 考点1.设…

WPF 自定义彩色控制台功能

文章目录 前言环境流内容一个简单的控制台 自动添加数据无法添加数据模板代码添加参数简单的案例添加和清空功能完善代码 额外功能添加移动到底部添加样式 总结 前言 在WPF中添加模拟控制台&#xff0c;可以试试的看到最新的日志信息。但是普通的TextBlock只是纯粹的黑色&…

分布式执行引擎ray入门--(2)Ray Data

目录 一、overview 基础代码 核心API&#xff1a; 二、核心概念 2.1 加载数据 从S3上读 从本地读&#xff1a; 其他读取方式 读取分布式数据&#xff08;spark&#xff09; 从ML libraries 库中读取&#xff08;不支持并行读取&#xff09; 从sql中读取 2.2 变换数据…

html--彩虹马

文章目录 htmljscss 效果 html <!DOCTYPE html> <html lang"en" > <head> <meta charset"UTF-8"> <title>Rainbow Space Unicorn</title> <link rel"stylesheet" href"css/style.css"> &l…

TCP/IP 七层架构模型

传输控制协议&#xff08;TCP&#xff0c;Transmission Control Protocol&#xff09;是一种面向连接的、可靠的、基于字节流的传输层通信协议。 套接字&#xff08;socket&#xff09;是一个抽象层&#xff0c;应用程序可以通过它发送或接收数据&#xff0c;可对其进行像对文…

【Linux】常用操作命令

目录 基本命令关机和重启帮助命令 用户管理命令添加用户&#xff1a;useradd 命令修改密码&#xff1a;passwd 命令查看登录用户&#xff1a;who 命令查看登录用户详细信息 :w切换用户 目录操作命令cdpwd命令目录查看 ls [-al] 目录操作【增&#xff0c;删&#xff0c;改&#…

NUMA(Non-Uniform Memory Access)架构的介绍

1. NUMA由来 最早的CPU是以下面这种形式访问内存的&#xff1a; 在这种架构中&#xff0c;所有的CPU都是通过一条总线来访问内存&#xff0c;我们把这种架构叫做SMP架构&#xff08;Symmetric Multi-Processor&#xff09;&#xff0c;也就是对称多处理器结构。可以看出来&…

Uniapp开发模板unibest

&#x1f3e0;简介 unibest 是一个集成了多种工具和技术的 uniapp 开发模板&#xff0c;由 uniapp Vue3 Ts Vite4 UnoCss uv-ui VSCode 构建&#xff0c;模板具有代码提示、自动格式化、统一配置、代码片段等功能&#xff0c;并内置了许多常用的基本组件和基本功能&#…

【PowerMockito:编写单元测试过程中原方法使用@Value注解注入的属性出现空指针】

错误场景 执行到Value的属性时会出现空指针&#xff0c;因为Value的属性为null 解决方法 在测试类调用被测试方法前&#xff0c;提前设置属性值&#xff0c;属性可以先自己定义好 ReflectionTestUtils.setField(endpointConnectionService, "exportUdpList", lis…

Linux 之七:Linux 防火墙 和进程管理

防火墙 查看防火墙 查看 Centos7 的防火墙的状态 sudo systemctl status firewalld。 查看后&#xff0c;看到active(running)就意味着防火墙打开了。 关闭防火墙&#xff0c;命令为&#xff1a; sudo systemctl stop firewalld。 关闭后查看是否关闭成功&#xff0c;如果…

【机器学习】一文掌握逻辑回归全部核心点(上)。

逻辑回归核心点-上 1、引言2、逻辑回归核心点2.1 定义与目的2.2 模型原理2.2.1 定义解析2.2.2 公式2.2.3 代码示例 2.3 损失函数与优化2.3.1 定义解析2.3.2 公式2.3.3 代码示例 2.4 正则化2.4.1 分类2.4.2 L1正则化2.4.3 L2正则化2.4.4 代码示例 3、总结 1、引言 小屌丝&#…

从空白镜像创建Docker hello world

文章目录 写在前面基础知识方法一&#xff1a;使用echo工具方法二&#xff0c;使用c语言程序方法三&#xff0c;使用汇编语言小结 写在前面 尝试搞了下docker&#xff0c;网上的教程大多是让下载一个ubuntu这种完整镜像&#xff0c;寥寥几篇从空白镜像开始创建的&#xff0c;也…

Oracle VM VirtualBox安装Ubuntu桌面版

背景&#xff1a;学习Docker操作 虚拟机软件&#xff1a;Oracle VM VirtualBox 7.0 系统镜像&#xff1a;ubuntu-20.04.6-desktop-amd64.iso 在Oracle VM VirtualBox新建一个虚拟电脑 选择好安装的目录和选择系统环境镜像 设置好自定义的用户名、密码、主机名 选择一下运行内…

执行除法运算返回浮点数结果operator.truediv()返回商的整数部分operator.floordiv()

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 执行除法运算 返回浮点数结果 operator.truediv() 返回商的整数部分 operator.floordiv() 下列选项可以执行除法运算并得到浮点数结果的是&#xff08;&#xff09; import operator print(&…

凌鲨微应用架构

微应用是静态网页加上凌鲨提供的扩展能力而形成的一种应用&#xff0c;主要特点是开发便捷&#xff0c;安全。 微应用架构 组件说明 名称 说明 微应用 webview窗口&#xff0c;显示web服务器上的页面 接口过滤器 根据权限配置,屏蔽非授权接口访问 接口提供者 tauri注入…

文件操作上(c语言)

目录 1. 文件的作用2. 什么是文件2.1 程序文件2.2 数据文件2.3 文件名 3. 二进制文件和文本文件4. 文件的打开和关闭4.1 流和标准流4.1.1 流4.1.2 标准流 4.2 文件指针4.3 文件的打开与关闭4.3.1 文件的打开模式4.3.2 实例代码 1. 文件的作用 使用文件可以将数据进行持久化的保…