Java序列化输入输出流和单例模式

序列化和反序列化
概念
  • 序列化:将Java对象转换为字节序列的过程【持久化存到硬盘上】
  • 反序列化:把字节序列恢复为Java对象的过程
用途
  • 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;(持久化对象)
  • 在网络上传送对象的字节序列。(网络传输对象)
使用

想要一个对象序列化,则需要继承两个接口

  • Seralizable
  • Externalizable
Seralizable接口
  • 一个对象想要被序列化,那么它的类就要实现此接口或者它的子接口。

  • 这个对象的所有属性(包括private属性、包括其引用的对象)都可以被序列化和反序列化来保存、传递。

  • 不想序列化的字段可以使用transient修饰。【比如密码等重要的信息】,但是反序列化时无法获得该属性的值。

  • 由于Serializable对象完全以它存储的二进制位为基础来构造,因此并不会调用任何构造函数,因此Serializable类无需默认构造函数

  • 但是当Serializable类的父类没有实现Serializable接口时,反序列化过程会调用父类的默认构造函数,因此该父类必需有默认构造函数,否则会抛异常。

  • 使用transient关键字阻止序列化虽然简单方便,但被它修饰的属性被完全隔离在序列化机制之外,导致了在反序列化时无法获取该属性的值,而通过在需要序列化的对象的Java类里加入writeObject()方法与readObject()方法可以控制如何序列化各属性,甚至完全不序列化某些属性或者加密序列化某些属性。

Externalizable 接口
  • 它是Serializable接口的子类,用户要实现的writeExternal()和readExternal() 方法,用来决定如何序列化和反序列化。

  • 因为序列化和反序列化方法需要自己实现,因此可以指定序列化哪些属性,而transient在这里无效。

  • 对Externalizable对象反序列化时,会先调用类的无参构造方法,这是有别于默认反序列方式的。如果把类的不带参数的构造方法删除,或者把该构造方法的访问权限设置为private、默认或protected级别,会抛出java.io.InvalidException: no valid constructor异常,因此Externalizable对象必须有默认构造函数,而且必需是public的。

序列化版本

serialVersionUID字段,下面是ArrayList源码里面的写法。

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private static final long serialVersionUID = 8683452581122892189L;
    ...
}
  • 一个对象数据,在反序列化过程中,如果序列化串中的serialVersionUID与当前对象值不同,则反序列化失败,否则成功
  • 如果serialVersionUID没有显式生成,系统就会自动生成一个。生成的输入有:类名、类及其属性修饰符、接口及接口顺序、属性、静态初始化、构造器。任何一项的改变都会导致serialVersionUID变化。
  • 当修改属性的值时,反序列化就不会成功了,因此一般都会显示声明 serialVersionUID
  • 保持 serialVersionUID 的相同,在反序列化时对新增的属性会填入默认值【null、0】,缺少的则直接忽略掉
  • 显示定义 serialVersionUID 的用处
    • 希望类的不同版本对序列化兼容时,需要确保类的不同版本具有相同的serialVersionUID
    • 不希望类的不同版本对序列化兼容时,需要确保类的不同版本具有不同的serialVersionUID
对象输入输出流
  • 对象输出流:ObjectOutputStream 包装一个文件输出流类,用对象输出流的 writeObject(Object obj) 方法写对象,也是输出可序列化对象
  • 对象输入流:ObjectInputStream 包装一个文件输入流,通过 readObject() 读取并返回Object类对象【强转】,反序列化
实例
  • MyBatis的源码中使用序列化与反序列化比较多【ORM框架】
package com.bigdata.practice1008;

import java.io.*;

public class SerrlizableTest {
    /**
     * 1.文件输出流
     * 2.对象输出流
     * 3.创建被序列化的对象
     * 3.序列化对象到文件
     * 4.关闭流
     */
    public static void main(String[] args) {
        serDept();
        deptSer();
    }

    /**
     * 1.创建文件输入流【异常处理】和对象输入流
     * 2.用对象接收 readObject() 方法读取的对象信息
     * 3.打印查看
     * 4.关闭流
     */
    private static void deptSer() {
        FileInputStream fis = null;
        ObjectInputStream ois = null;

        try {
            fis = new FileInputStream("D:"+File.separator+"kaifamiao"+File.separator+"java se"+File.separator+"io"+File.separator+"dept.txt");
            ois = new ObjectInputStream(fis);
            Dept dept = (Dept) ois.readObject();
            System.out.println(dept);
            System.out.println("反序列化完成!");
        } catch (FileNotFoundException e) {
            System.out.println("文件没有找到!");
            e.printStackTrace();
        } catch (IOException e) {
            System.out.println("操作文件失败!");
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally{
            if (ois != null){
                try {
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fis != null){
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private static void serDept() {
        FileOutputStream fos = null;
        ObjectOutputStream obs = null;

        try {
            fos = new FileOutputStream("D:"+File.separator+"kaifamiao"+File.separator+"java se"+File.separator+"io"+File.separator+"dept.txt");
            obs = new ObjectOutputStream(fos);
            Dept dept = new Dept("NO001", "开发部", "西安");
            obs.writeObject(dept);
            System.out.println("序列化完成!");
        } catch (FileNotFoundException e) {
            System.out.println("文件没有找到!");
            e.printStackTrace();
        } catch (IOException e) {
            System.out.println("操作文件失败!");
            e.printStackTrace();
        } finally{
            if (obs != null){
                try {
                    obs.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fos != null){
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
单例模式
概述
1.概念

单例模式需要确保某个类只有一个实例,并且提供全局访问点。属于设计模式中的创建型模式。

2.特点
  • 只有一个实例
  • 自我实例化
  • 构造是私有的
  • 提供全局访问点【static】
优缺点
优点
  • 单例模式只有一个实例,所以能够节约系统资源,减少性能开销,提高系统的效率,同时也能严格的控制使用者对它的访问
缺点
  • 单例类的职责过重,违反”单一职责原则“, 同时没有抽象类,所以扩展起来比较困难

  • 想实例化一个单例类时 无法用 new 必须要记住使用相应的获取对象的方法

使用场景:
工具类对象、
频繁访问数据库或者文件的对象、
创建对象时耗时耗费资源又经常用不到的对象、
频繁创建和销毁的对象
常见实现方式
  • 饿汉式【静态私有属性和代码块】,
  • 懒汉式【不加锁、方法头加锁、实例化加锁双重判null】,
  • 静态内部类式
  • 枚举单例
饿汉式【静态私有常量】
  • 优点:写法简单、类加载时就完成实例化、避免了线程同步的问题

  • 缺点:不能延迟加载、如果没用这个类则造成内存的浪费

  • 还可用【静态私有代码块】去做

    package com.bigdata.practice1008;
    
    /**
     * 单例模式[私有构造方法]
     * 饿汉式:调用就有 早已经创建好了
     * 1.私有化公有的构造方法
     * 2.建立私有的静态的该类的类属性并实例化该类赋值给该私有属性
     * 3.建立共有的获得实例的方法 返回私有静态的属性
     * 
     * 类初始化时就加载这个对象[加载类较慢 获取类对象速度较快]
     */
    public class SingleMode {
        private String name;
        private static SingleMode instance = new SingleMode();
    	//private static SingleModel instance; 
        private SingleMode(){
        }
    	
        //static{
        //	SingleMode instance = new SingleMode();
    	//}
        
        // 方法没有加同步锁 效率较高
        public static SingleMode getInstance(){
            return instance;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    
懒汉式
  • 优点:在调用的时候再实例化,延迟加载,不用不浪费内存

  • 缺点:线程不安全,多线程不可用

    package com.bigdata.practice1008;
    
    /**
     * 懒汉式:运行时加载对象 加载类比较块 对象获取速度较慢 线程不安全 需要线程安全 则性能降低 加关键字:synchornized
     * 1.共有构造私有化
     * 2.建立一个静态私有的该类类型的属性 并赋值成null
     * 3.建立共有的获取实例的方法 判断 静态私有的该类类型的属性 是否为null null则实例化 最后返回 静态私有的该类类型的属性
     */
    public class SingleModel1 {
        private String name;
        private static SingleModel1 instance = null;
    
        private SingleModel1(){
    
        }
    
        // 在运行的时候再去初始化 加载 对象 而不是像饿汉式 加载类的时候就已经实例化好了
        public SingleModel1 getInstance(){
            if (instance == null) {
                instance = new SingleModel1();
            }
            return instance;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    
  • 懒汉式双重加锁【也可以只在方法上加锁,但是也不适用】

    • 线程安全,推荐使用,同步效率太低

    • 注意关键字 volatile

    • 注意 双重判 null

    package com.bigdata.practice1008;
    
    /**
     *懒汉式双重锁:和懒汉式一样 但加了双重锁 线程安全 但效率很低
     */
    public class SingleModel2 {
        private String name;
        private static volatile SingleModel2 instance = null;
        // java初始化时有可能进行指令重排 加关键字 volatile
            // 初始化:堆中开辟对象空间,分配地址、类加载的初始化顺序去初始化、将内存地址返回给栈中的引用变量
            // java内存模型允许 ”无序写入“ 有些编译器会重排上面2,3步骤 导致线程1分配后线程2也分配 【对象初始化没完成】
        // 双重判空:有可能两次初始化【两个线程同时初始化】
    
        private SingleModel2(){
    
        }
    
        public SingleModel2 getInstance(){
            if (instance == null){
                synchronized(SingleModel2.class){
                    if (instance == null){
                        instance = new SingleModel2();
                    }
                }
            }
            return instance;
        }
    }
    
静态内部类
  • 优点:避免线程不安全、延迟加载、效率高

    package com.bigdata.practice1008;
    
    /**
     * 静态内部类单例
     * 1.共有构造私有化
     * 2.私有静态内部类实例化对象
     * 3.共有方法返回类点属性
     */
    public class SingleModel3 {
        private String name;
    
        private SingleModel3(){
    
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        private static class singleInstance{
            private static SingleModel3 instance = new SingleModel3();
        }
    
        public static SingleModel3 getInstance(){
            return singleInstance.instance;
        }
    }
    
枚举
  • jdk1.5添加枚举来实现单例模式,避免多线程问题,防止反序列化重新创建对象

    package com.bigdata.practice1008;
    
    /**
     * 枚举类单例模式
     *
     */
    public enum SingleModelEnum {
        INSTANCE; // 实例
        public SingleModelEnum getInstance(){
            return INSTANCE;
        }
    
        private String name;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public static void main(String[] args) {
            SingleModelEnum.INSTANCE.getInstance().setName("gu");
            System.out.println(SingleModelEnum.INSTANCE.getInstance().getName());
            SingleModelEnum.INSTANCE.getInstance().setName("cn");
            System.out.println(SingleModelEnum.INSTANCE.getInstance().getName());
            System.out.println(SingleModelEnum.INSTANCE == SingleModelEnum.INSTANCE);
        }
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值