当前位置: 首页 > news >正文

Java基础进阶-序列化

概念

序列化的概念:

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

反序列化

将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程
在这里插入图片描述

持久化

把数据结构或对象存储起来(硬盘)

序列化方案

  1. Serializable Java的序列化方案
  2. Parcelable Android独有
  3. json,xml,protbuf … 广义的序列化

如何选择合理的序列化方案通用性

  • 强健性 / 鲁棒性
  • 可调试性 / 可读性
  • 性能
  • 可扩展性 / 兼容性
  • 安全性 / 访问限制

Serializable接口

是Java提供的序列化接口,它是一个空接口:
在这里插入图片描述
空的接口如何实现序列化?
ObjectOutput
ObjectStreamClass: 描叙一个对象的结构
Serializable用来标识当前类可以被ObjectOutputStream序列化,以及被ObjectInputStream反序列化。通过对象的IO流来辅助实现序列化和反序列化。

代码示例:

    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",18);
        SerializeableUtils.saveObject(user,tmp+"a.out");
        System.out.println("1: " + user);
        user = SerializeableUtils.readObject(tmp+"a.out");
        System.out.println("反序列化: 2: " + user);
    }
    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;
    }
    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;
    }

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

Externalizable接口

在这里插入图片描述

/**
 * Externalizable 接口的作用
 */
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:\\";

    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();
        }
    }

}

注意点:
1.读写顺序要求一致,读写的成员变量的个数一致,否则会报错java.io.EOFException。
2.必须要一个public的无参构造函数,否则Java.io.InvalidClassException: com.example.xuliehuademo01.demo06$User; no valid constructor
3. serialVersionUID 更新

     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());
        }

关于序列化的几个问题

1.什么是 serialVersionUID ?如果你不定义这个, 会发生什么?

2. 假设你有一个类,它序列化并存储在持久性中, 然后修改了该类以添加新字段。如果对已序列化的对象进行反序列化, 会发生什么情况?

serialVersionUID 是一个 private static final long 型 ID, 当它被印在对象上时, 它通常是 对象的哈希码,你可以使用 serialver 这个 JDK 工具来查看序列化对象的 serialVersionUID。 SerialVerionUID 用于对象的版本控制。也可以在类文件中指定 serialVersionUID。不指定 serialVersionUID的后果是,当你添加或修改类中的任何字段时, 则已序列化类将无法恢复, 因为 为新类和旧序列化对象生成的 serialVersionUID 将有所不同。Java 序列化过程依赖于正确的序 列化对象恢复状态的, ,并在序列化对象序列版本不匹配的情况下引发 java.io.InvalidClassException 无效类异常。
在这里插入图片描述
在这里插入图片描述

3.序列化时,你希望某些成员不要序列化?你如何实现它?

有时候也会变着形式问,比如问什么是瞬态 trasient 变量, 瞬态和静态变量会不会得到序列化等,所以,如果你不希望任何字段是对象的状态的一部分, 然后声明它静态或瞬态根据你的需要, 这样就不会是在 Java 序列化过程中被包含在内

4.如果类中的一个成员未实现可序列化接口, 会发生什么情况?

如果尝试序列化实现可序列化的类的对象,但该对象包含对不可序列化类的引用,则在运行时将引发不可序列化异常 NotSerializableException

5.如果类是可序列化的, 但其超类不是, 则反序列化后从超级类继承的实例变量的状态如何

Java 序列化过程仅在对象层次都是可序列化结构中继续, 即实现 Java 中的可序列化接口, 并且从超级类继承的实例变量的值将通过调用构造函数初始化, 在反序列化过程中不可序列化的超级类
可以调用writeObject和readObject可以实现对父类的序列化和反序列化:

/**
 * 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:\\xuliehuaTest\\";

    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();
        }
    }

}

6. 是否可以自定义序列化过程, 或者是否可以覆盖 Java 中的默认序列化过程?

7. 假设新类的超级类实现可序列化接口, 如何避免新类被序列化?

对于序列化一个对象需调用ObjectOutputStream.writeObject(saveThisObject), 并用
ObjectInputStream.readObject() 读取对象, 但 Java 虚拟机为你提供的还有一件事, 是定义这两个方法。如果在类中定义这两种方法, 则 JVM 将调用这两种方法, 而不是应用默认序列化机制。你可以在此处通过执行任何类型的预处理或后处理任务来自定义对象序列化和反序列化的行为。

/**
 * 父类可序列化,如何阻止子类可序列化
 * 1. transient?
 * 2. 实现writeObject,readObject,readObjectNoData 在这些方法里面抛出运行时异常
 */
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(java.io.ObjectOutputStream out) throws IOException {
            throw new NotSerializableException("Can not serialize this class");
        }

        private void readObject(java.io.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:\\xuliehuaTest\\";

    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();
        }
    }

}

8. 在 Java 中的序列化和反序列化过程中使用哪些方法?

考察你是否熟悉 readObject() 的用法、writeObject()、readExternal() 和 writeExternal()。Java 序列化由java.io.ObjectOutputStream类完成。该类是一个筛选器流, 它封装在较低级别的字节流中, 以处理序列化机制。要通过序列化机制存储任何对象, 我们调用ObjectOutputStream.writeObject(savethisobject), 并反序列化该对象, 我们称之为ObjectInputStream.readObject()方法。调用以 writeObject() 方法在 java 中触发序列化过程。关于 readObject() 方法, 需要注意的一点很重要一点是, 它用于从持久性读取字节, 并从这些字节创建对象, 并返回一个对象, 该对象需要类型强制转换为正确的类型

序列化流程在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
枚举

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));
    }

}

单例

//Q: -当单例类被多个类加载器加载,如何还能保持单例?
//A: 用多个类加载器的父类来加载单例类

public class SingletonTest {



}

/**
 * 单例类防止反序列化
 */
class Singleton implements Serializable{
    public static Singleton INSTANCE = new Singleton();

    private Singleton(){}

    private Object readResolve(){
        return INSTANCE;
    }
}

/**
 * 单例类如何防止反射
 */
class Singleton1 {

    private static boolean flag = false;

    private Singleton1(){
        synchronized(Singleton.class){
            if(flag == false){
                flag = !flag;
            } else {
                throw new RuntimeException("单例模式被侵犯!");
            }
        }
    }

    private  static class SingletonHolder {
        private static final Singleton1 INSTANCE = new Singleton1();
    }

    public static Singleton1 getInstance(){
        return SingletonHolder.INSTANCE;
    }
}

Android的Parcelable

Binder

在这里插入图片描述

Parcel简介

在这里插入图片描述

public class User implements Parcelable {

    private String name;
    private int age;

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

    }

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

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

    public static final Parcelable.Creator<User> CREATEOR = new Parcelable.Creator<User>(){

        @Override
        public User createFromParcel(Parcel source) {
            return new User(source);
        }

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

Parcelable与Serializable的区别

在这里插入图片描述

面试相关

1.反序列化后的对象会重新调用构造函数吗?
答:不会, 因为是从二进制直接解析出来的. 适用的是 Object 进行接收再强转, 因此不是原来的那个对象

2.序列化与反序列化后的对象是什么关系?
答:是一个深拷贝, 前后对象的引用地址不同,除了枚举

3.Android 为什么要设计 bundle 而不是使用 HashMap 结构?
答:bundle 内部适用的是 ArrayMap, ArrayMap 相比 Hashmap 的优点是, 扩容方便, 每次扩容是原容量的一半, 在[百量] 级别, 通过二分法查找 key 和 value (ArrayMap 有两个数组, 一个存放 key 的 hashcode, 一个存放 key+value 的 Entry) 的效率要比 hashmap 快很多, 由于在内存中或者 Android 内部传输中一般数据量较小, 因此用 bundle 更为合适

4.serializableVersionUID 的作用是?
用于数据的版本控制, 如果反序列化后发现 ID 不一样, 认为不是之前序列化的对象

5.Android 中 intent/bundle 的通信原理以及大小限制?
答: Android 中的 bundle 实现了 parcelable 的序列化接口, 目的是为了在进程间进行通讯, 不同的进程共享一片固定大 小的内存, parcelable 利用 parcel 对象的 read/write 方法, 对需要传递的数据进行内存读写, 因此这一块共享内存不能 过大, 在利用 bundle 进行传输时, 会初始化一个 BINDER_VM_SIZE 的大小 = 1 * 1024 * 1024 - 4096 * 2, 即便通过 修改 Framework 的代码, bundle 内核的映射只有 4M, 最大只能扩展到 4M.

6.为何 Intent 不能直接在组件间传递对象而要通过序列化机制?
答: 因为 Activity 启动过程是需要与 AMS 交互, AMS 与 UI 进程是不同一个的, 因此进程间需要交互数据, 就必须序列化

7.序列化与持久化的关系和区别?
答: 序列化是为了进程间数据交互而设计的, 持久化是为了把数据存储下来而设计的

相关文章:

  • 02.3 线性代数
  • java毕业设计木材产销系统的生产管理模块mybatis+源码+调试部署+系统+数据库+lw
  • Nuxt - 自定义页面布局,<Nuxt /> 个性化多套模板(一个项目内既要有用户正常浏览的普通页面,又要存在后台管理系统布局的页面)
  • java毕业设计拉萨旅游自助民宿平台mybatis+源码+调试部署+系统+数据库+lw
  • 微信公众号查题搜题功能系统平台
  • 王道计算机组成原理课本课后习题错题总结
  • cefsharp 最新稳定版104.4.240 (chromium-104.0.5112.102)
  • Unity开发3
  • spark(day02)
  • 【我不熟悉的css】05. csc中使用变量,:root伪元素的应用
  • 03.2 线性回归的从零开始实现
  • js深复制一个数组
  • 03.3线性回归的简洁实现
  • 基于51单片机简易十字路口交通灯_5s全黄闪烁
  • 高并发、高性能、高可用的理解及处理方式
  • [译] 理解数组在 PHP 内部的实现(给PHP开发者的PHP源码-第四部分)
  • 《Java编程思想》读书笔记-对象导论
  • 230. Kth Smallest Element in a BST
  • Javascript弹出层-初探
  • java正则表式的使用
  • mysql innodb 索引使用指南
  • mysql_config not found
  • MYSQL如何对数据进行自动化升级--以如果某数据表存在并且某字段不存在时则执行更新操作为例...
  • Quartz实现数据同步 | 从0开始构建SpringCloud微服务(3)
  • spring cloud gateway 源码解析(4)跨域问题处理
  • 阿里云应用高可用服务公测发布
  • 马上搞懂 GeoJSON
  • 如何编写一个可升级的智能合约
  • 我建了一个叫Hello World的项目
  • 线上 python http server profile 实践
  • 中文输入法与React文本输入框的问题与解决方案
  • 看到一个关于网页设计的文章分享过来!大家看看!
  • RDS-Mysql 物理备份恢复到本地数据库上
  • 京东物流联手山西图灵打造智能供应链,让阅读更有趣 ...
  • 小白应该如何快速入门阿里云服务器,新手使用ECS的方法 ...
  • #stm32驱动外设模块总结w5500模块
  • (06)金属布线——为半导体注入生命的连接
  • (1)(1.19) TeraRanger One/EVO测距仪
  • (ibm)Java 语言的 XPath API
  • (zhuan) 一些RL的文献(及笔记)
  • (附源码)spring boot火车票售卖系统 毕业设计 211004
  • (每日持续更新)信息系统项目管理(第四版)(高级项目管理)考试重点整理第3章 信息系统治理(一)
  • (十)c52学习之旅-定时器实验
  • .bat批处理(八):各种形式的变量%0、%i、%%i、var、%var%、!var!的含义和区别
  • .NET Core 中的路径问题
  • .NET Framework杂记
  • .NET I/O 学习笔记:对文件和目录进行解压缩操作
  • .NET MAUI学习笔记——2.构建第一个程序_初级篇
  • .Net MVC + EF搭建学生管理系统
  • .NET MVC 验证码
  • .Net开发笔记(二十)创建一个需要授权的第三方组件
  • .Net下使用 Geb.Video.FFMPEG 操作视频文件
  • @Import注解详解
  • @德人合科技——天锐绿盾 | 图纸加密软件有哪些功能呢?
  • [ JavaScript ] JSON方法