序列化相关知识总结

目录

  • 一、序列化
    • 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;@Overridepublic 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;@Overridepublic 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;@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age=" + age +'}';}@Overridepublic void writeExternal(ObjectOutput out) throws IOException {out.writeObject(name);out.writeInt(age);}@Overridepublic 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;@Overridepublic 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;@Overridepublic 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;}@Overridepublic 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;@Overridepublic 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;}@Overridepublic 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());
//        }@Overridepublic 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;}@Overridepublic 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;@Overridepublic 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");}@Overridepublic 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>() {@Overridepublic User createFromParcel(Parcel in) {return new User(in);}@Overridepublic User[] newArray(int size) {return new User[size];}};@Overridepublic int describeContents() {return 0;}@Overridepublic 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();

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ARMv8/ARMv9架构入门到精通-学习方法

目录 1、学习ARM基础知识2、学习ARM异常(中断)3、学习MMU4、学习Cache5、学习Trustzone和安全架构6、学习ARM架构和各类IP推荐 本文转自 周贺贺&#xff0c;baron&#xff0c;代码改变世界ctw&#xff0c;Arm精选&#xff0c; 资深安全架构专家&#xff0c;11年手机安全/SOC底层…

SpringMVC06、数据处理

6、数据处理 6.1、处理提交数据 1、提交的域名称和处理方法的参数名一致 提交数据 : http://localhost:8080/hello?namekuangshen 处理方法 : RequestMapping("/hello") public String hello(String name){System.out.println(name);return "hello";…

javase day02笔记

第二天课堂笔记 源文件的组成部分★★ 源文件外部结构 class 类名{}main方法 public static void main(String [] args){}main方法可有可无 没有main的情况&#xff0c;编译成功&#xff0c;运行失败&#xff0c;没有程序入口 多个main情况&#xff0c;编译报错&#xff0c;…

半监督 伪标签

什么是半监督学习 半监督学习也是一类更接近于人类学习方法的机器学习范式。试想这样一个场景&#xff0c;我们小时候学习识别小猫、小狗、汽车等等物品时&#xff0c;往往只需要父母进行一两次的指导&#xff0c;我们就能很准确地辨认出什么是猫狗。这背后有一个重要原因是&am…

抖音素材网站去哪下载?给你推荐六个抖音自媒体网站

各位抖音视频创作达人们&#xff0c;是否在苦苦寻觅那些能够点燃观众热情&#xff0c;让视频内容跃然屏上的素材宝库呢&#xff1f;此刻&#xff0c;你们的寻觅之旅将迎来终点&#xff01;我将向你们隆重推荐10个精心挑选的视频素材库&#xff0c;它们定能让你们的抖音视频如同…

Windows下安装pip

一、下载pip 官网地址&#xff1a;https://pypi.org/project/pip/#files 1.1、pip工具查找方法 单击官网首页“PyPi”选项 在弹出来的搜索框中输入“pip” 选择最新的pip版本&#xff0c;点进去 下载pip安装包包 二、安装pip 解压“pip-24.0.tar.gz”&#xff0c;进…

【Linux】常用操作命令

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

java通过poi-tl生成word

我看公司之前做电子合同&#xff0c;使用TIBCO jaspersoft做的报表模板&#xff0c;如果是给自己公司开发或者给客户做项目&#xff0c;这个也没有什么&#xff0c;因为反正模板是固定的&#xff0c;一次性开发&#xff0c;不用担心后续的问题。即使后期有调整&#xff0c;改一…

计算两帧雷达数据之间的变换矩阵

文章目录 package.xmlCMakeLists.txtpoint_cloud_registration.cc运行结果 package.xml <?xml version"1.0"?> <package format"2"><name>point_cloud_registration</name><version>0.0.0</version><descriptio…

九州金榜|孩子厌学的因素及解决办法

孩子在学习的过程中&#xff0c;遇到厌学这种情况非常容易见到&#xff0c;这也是孩子在成长的过程中经常遇到的烦恼。面对孩子的厌学&#xff0c;作为家长这时候不要慌乱&#xff0c;要做到分析孩子产生厌学的原因&#xff0c;在去寻找解决孩子厌学的办法。下面九州金榜家庭教…

【漏洞复现】大华ICC智能物联综合管理平台任意文件读取漏洞

Nx01 产品简介 大华智能物联综合管理平台 iConnection Center&#xff08;以下简称&#xff1a;ICC平台&#xff09;&#xff0c;是一套基于智能物联的综合业务管理平台软件&#xff0c;具备强大的后台服务能力&#xff0c;配套了B/S管理员端、C/S客户端、移动APP终端、小程序等…

vue中性能优化

目录 1. 编码优化 2. 源码优化 3. 打包优化 4. 利用 Vue Devtools 总结 Vue.js 作为一个强大的前端框架&#xff0c;提供了丰富的功能和工具来帮助开发者构建高效的 Web 应用。然而&#xff0c;在开发过程中&#xff0c;性能优化仍然是一个需要关注的问题。以下是对 Vue.j…

华为OD机考-C卷

文章目录 攀登者问题停车场最短路径 攀登者问题 24/03/09 20:50~23:10 攀登者喜欢寻找各种地图&#xff0c;并且尝试攀登到最高的山峰。地图表示为一维数组&#xff0c;数组的索引代表水平位置&#xff0c;数组的元素代表相对海拔高度。其中数组元素0代表地面。一个山脉可能有多…

GTH手册学习注解

CPLL的动态配置 终于看到有这个复位功能了 QPLL SWITCHing需要复位 器件级RESET没发现有管脚引出来 两种复位方式&#xff0c;对应全复位和器件级复位 对应的复位功能管脚 改那个2分频的寄存器说明段&#xff0c;复位是自动发生的&#xff1f;说明可能起效了&#xff0c;但是分…

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

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

leetcode必刷题 96.不同的二叉搜索树

一、问题描述&#xff1a; 给你一个整数 n &#xff0c;求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种&#xff1f;返回满足题意的二叉搜索树的种数。 二、解题思路&#xff1a; 二叉树是由根节点&#xff0c;左右子树组成的&#xff0c;二叉搜索树要…

提高驾驶安全性 | 基于ACM32 MCU的胎压监测仪方案

概述 胎压监测系统 作为车辆的基础部件&#xff0c;轮胎是影响行车安全不可忽视的因素之一。据统计&#xff0c;中国每年由胎压问题引起轮胎爆炸的交通事故约占 30%&#xff0c;其中 50%的高速交通事故是由车辆胎压异常引起。因此&#xff0c;准确实时地监测车辆在行驶过程中…

Curriculum Manager for Source Selection in Multi-Source Domain Adaptation

GRL: gradient reversal layer&#xff0c;CM: Curriculum Manager 辅助信息 作者未提供代码

2024/3/10打卡借教室——二分+差分

题目 在大学期间&#xff0c;经常需要租借教室。 大到院系举办活动&#xff0c;小到学习小组自习讨论&#xff0c;都需要向学校申请借教室。 教室的大小功能不同&#xff0c;借教室人的身份不同&#xff0c;借教室的手续也不一样。  面对海量租借教室的信息&#xff0c;我们自…

IDEA打开项目文件目录不见了

偶尔发生新拉下来的代码&#xff0c;或者旧代码修改了包名&#xff0c;项目名称等&#xff0c;idea左侧project一栏不显示代码的文件目录。例如下面此时不要慌张&#xff0c;不用删除项目重新拉取&#xff0c;通过以下方式解决&#xff1a; 本人尝试能够解决&#xff0c;如果无…