文章目录
序列化和反序列化
概念
- 序列化:将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); } }