Java基础(十一)--Serializable和Externalizable接口实现序列化
序列化在日常开发中经常用到,特别是涉及到网络传输的时候,例如调用第三方接口,通过一个约定好的实体进行传输,这时你必须实现序列
化,这些都是大家都了解的内容,所以文章也会讲一下序列化的高级内容。
序列化与反序列化简单认知:
我们知道,对象在不具有可达性的时候,会被GC,这些对象都是保存在堆中,而现实中,我们可能需要将对象进行持久化,并且在需要的时候
进行读取转换,这就是序列化的工作。
1、序列化:
将一个对象转换成字节流或者说是字节数组,并且可以存储或传输的形式的过程。
存储:可以把一个对象存储到文件、数据库等
网络传输:可以转化成字节或XML进行网络传输
2、反序列化:
和序列化是一个相反的过程,在需要的时候,把字节数组转化成对象。
序列化广泛应用于远程调用等所有涉及网络传输的地方
序列化相关接口:
Serializable、Externalizable、ObjectOutput、ObjectInput、ObjectOutputStream、ObjectInputStream
Serializable接口:
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Student implements Serializable { private int id;
private String name;
private int sex;
private transient String addr;
}
public static void main(String[] args) throws Exception{
	Student student = new Student(1001, "sam", 1, "SH");
	File file = new File("D:\\a.txt");
	FileOutputStream fileOutputStream = new FileOutputStream(file);
	ObjectOutputStream outputStream = new ObjectOutputStream(fileOutputStream);
	outputStream.writeObject(student);
	outputStream.close();
	ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(file));
	Student student1 = (Student)inputStream.readObject();
	System.out.println(student1.toString());
	inputStream.close();
}
结果:
Student(id=1001, name=sam, sex=1, addr=null)
我们通过Binary Viewer查看这个二进制文件a.txt,下面二进制内容解释参考自:https://www.cnblogs.com/xrq730/p/4821958.html

第一部分:序列化文件头
1、AC ED:STREAM_MAGIC序列化协议
2、00 05:STREAM_VERSION序列化协议版本
3、73:TC_OBJECT声明这是一个新的对象
第二部分:序列化的类的描述,在这里是Student类
1、72:TC_CLASSDESC声明这里开始一个新的class
2、00 18:十进制的24,表示class名字的长度是24个字节
3、63 6F 6D ... 6E 74 20:表示的是“com.it.exception.Student”这一串字符,可以数一下确实是24个字节
4、00 00 00 00 00 00 00 01:SerialVersion,序列化ID,1
5、02:标记号,声明该对象支持序列化
6、00 03:该类所包含的域的个数为3个
第三部分:是对象中各个属性项的描述
1、49:int类型
2、00 02:十进制的2,表示字段长度
3、69 64:表示字段id
4、49:int类型
省略了sex、name属性,可以自行查看
5、74:TC_STRING,代表一个new String,用String来引用对象
第四部分:该对象父类的信息,如果没有父类就没有这部分。有父类和第2部分差不多
1、00 12:十进制的18,表示父类的长度
2、4C 6A 61 ... 6E 67 3B:“L/java/lang/String;”表示的是父类属性
3、78:TC_ENDBLOCKDATA,对象块结束的标志
4、70:TC_NULL,说明没有其他超类的标志
第五部分:输出对象的属性项的实际值,如果属性项是一个对象,这里还将序列化这个对象,规则和第2部分一样
1、00 03:十进制的3,属性的长度
2、73 61 6D:字符串"sam",name的属性值
以上是二进制文件的解析,可以得出结论:
1、序列化之后保存的是对象的信息
2、被声明为transient的属性不会被序列化,这就是transient关键字的作用,addr字段并没有保存
所以,我们得出结论,static字段也不会被序列化,因为static变量属于类的
Externalizable接口:
public interface Externalizable extends java.io.Serializable {
    void writeExternal(ObjectOutput out) throws IOException;
        restored cannot be found.
    void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}
使用样例:
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Student implements Externalizable{ private static final long serialVersionUID = 1L; private int id;
private String name;
private int sex; @Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(id);
out.writeObject(name);
out.writeObject(sex);
} @Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
id = (Integer)in.readObject();
name = (String)in.readObject();
sex = (Integer)in.readObject();
}
}
public class Test {
    public static void main(String[] args) throws Exception{
        Student student = new Student(1001, "sam", 1);
        File file = new File("D:\\a.txt");
        FileOutputStream fileOutputStream = new FileOutputStream(file);
        ObjectOutputStream outputStream = new ObjectOutputStream(fileOutputStream);
        outputStream.writeObject(student);
        outputStream.close();
        ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(file));
        Student student1 = (Student)inputStream.readObject();
        System.out.println(student1.toString());
        inputStream.close();
    }
}
结果:
Student(id=1001, name=sam, sex=1)
从结果看Externalizable接口同样可以实现序列化和反序列化,但是有些地方不太一样,需要注意
1、相关类必须有默认构造器,否则会抛出异常,是因为在读取对象时,会调用被序列化类的无参构造器去创建一个新的对象,然后再将被
保存对象的字段的值分别填充到新对象中。
2、需要重写writeExternal和readExternal方法,去控制序列化,并且写入字段顺序和读取顺序要保持一致,写入和读取支持多种类型,不必
一定使用object,这样不用类型转换
自定义序列化:
我们使用序列化的时候,一般情况都是使用默认的方式,而如果在一些特殊场景下我们需要进行特殊处理,例如字段加密,因为序列化是不安
全的。
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Student implements Serializable{ private static final long serialVersionUID = 1L; private int id;
private String name;
private int sex; private void writeObject(ObjectOutputStream outputStream) throws Exception {
outputStream.defaultWriteObject();
outputStream.writeBoolean(true);
} private void readObject(ObjectInputStream inputStream) throws Exception {
inputStream.defaultReadObject();
boolean flag = inputStream.readBoolean();
System.out.println("flag: " + flag);
}
}
测试代码不变
flag: true
Student(id=1001, name=sam, sex=1)
从代码上看和Externalizable接口几乎一样的,通过writeObject()和readObject()实现自定义的过程
原因:
虚拟机会首先试图调用对象里的writeObject()和readObject(),进行用户自定义的序列化和反序列化。如果没有这样的方法,那么默认调用的
是ObjectOutputStream的defaultWriteObject以及ObjectInputStream的defaultReadObject方法
我们在查看jdk集合的源码中可以看到,ArrayList、HashMap等在实现序列化的时候,都是自定义writeObject()和readObject()的
PS:虚拟机通过反射来调用writeObject()和readObject()
ArrayList序列化:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable { transient Object[] elementData; //通过数组保存集合数据 private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA; s.defaultReadObject(); s.readInt(); // ignored if (size > 0) {
ensureCapacityInternal(size); Object[] a = elementData;
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
int expectedModCount = modCount;
s.defaultWriteObject(); // Write out size as capacity for behavioural compatibility with clone()
s.writeInt(size); // Write out all elements in the proper order.
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
} if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
}
ArrayList这样实现的目的:
ArrayList是动态数组,数组中的数据在达到阀值就会扩容,如果数组扩容后长度设为100,而里面只存放了10条数据,那就会序列化90个
null元素。为了保证在序列化的时候不会将这么多null同时进行序列化,ArrayList把元素数组设置为transient,然后通过遍历数组讲数据进行
序列化和反序列化
总结:
  1、在Java中,只要一个类实现了Serializable接口,那么它就可以被序列化
  2、通过ObjectOutputStream和ObjectInputStream对对象进行序列化及反序列化
3、当父类继承Serializable接口,所有子类都可以被序列化
  4、子类实现了Serializable接口,如果想要父类的属性也能实现序列化,必须父类也实现Serializable 接口,否则父类中的属性不能序列
化(不报错,数据丢失),但是在子类中属性仍能正确序列化
5、如果序列化的属性是对象,则这个对象也必须实现Serializable接口,否则会报错
6、序列化并不保存静态变量
7、反序列化能否成功,要求:①.类路径相同,②.序列化ID保持一致(serialVersionUID),否则无法成功
8、反序列化时,如果对象的属性有修改或删减,则修改的部分属性会丢失,但不会报错
5、序列化数据如果比较敏感,可以采用加密的方式,增加一定安全性
内容参考:
https://www.cnblogs.com/xrq730/p/4821958.html
http://www.importnew.com/18024.html
https://www.ibm.com/developerworks/cn/java/j-lo-serial/
Java基础(十一)--Serializable和Externalizable接口实现序列化的更多相关文章
- Java基础(十一)  Stream I/O and Files
		
Java基础(十一) Stream I/O and Files 1. 流的概念 程序的主要任务是操纵数据.在Java中,把一组有序的数据序列称为流. 依据操作的方向,能够把流分为输入流和输出流两种.程 ...
 - Java基础十一--多态
		
Java基础十一--多态 一.多态定义 简单说:就是一个对象对应着不同类型. 多态在代码中的体现: 父类或者接口的引用指向其子类的对象. /* 对象的多态性. class 动物 {} class 猫 ...
 - Java基础学习(四)-- 接口、集合框架、Collection、泛型详解
		
接口 一.接口的基本概念 关键字为:Interface,在JAVA编程语言中是一个抽象类型,是抽象方法的集合.也是使用.java文件编写. 二.接口声明 命名规范:与类名的命名规范相同,通常情况下 ...
 - java基础(十一)  枚举类型
		
枚举类型Enum的简介 1.什么是枚举类型 枚举类型: 就是由一组具有名的值的有限集合组成新的类型.(即新的类). 好像还是不懂,别急,咱们先来看一下 为什么要引入枚举类型 在没有引入枚举类型前,当我 ...
 - Java 中,Serializable 与 Externalizable 的区别?
		
Serializable 接口是一个序列化 Java 类的接口,以便于它们可以在网络上传输 或者可以将它们的状态保存在磁盘上,是 JVM 内嵌的默认序列化方式,成本高. 脆弱而且不安全.Externa ...
 - java基础十一[远程部署的RMI](阅读Head First Java记录)
		
方法的调用都是发生在相同堆上的两个对象之间(同一台机器的Java虚拟机),如果想要调用另一台机器上的对象,可以通过Socket进行输入/输出. 远程过程调用需要创建出4种东西:服务器.客户端.服务器辅 ...
 - java基础知识总结--继承和接口
		
什么是继承?什么是接口?他们之间的区别和联系是什么? 什么是继承? 继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能.多个类中存在相同属性和行 ...
 - 【java基础 16】抽象类和接口的区别
		
导读:前两天闲着没事儿,看了本书,然后写了点代码,在接口里面写了默认方法实现,因为书上说这个特性是从java8开始的,我还特地给测了一下java7. 没过几天,就有一个技术分享会,刚好也是讲java8 ...
 - JAVA基础知识|Serializable
		
一.序列化和反序列化 序列化:把对象转换为字节序列的过程称为对象的序列化. 反序列化:把字节序列恢复为对象的过程称为对象的反序列化. 将内存中对象的信息保存下来,可以有很多种方式实现这一功能.其中ja ...
 
随机推荐
- CF 809 D Hitchhiking in the Baltic States —— 思路+DP(LIS)+splay优化
			
题目:http://codeforces.com/contest/809/problem/D 看题解,抄标程...发现自己连 splay 都快不会写了... 首先,题目就是要得到一个 LIS: 但与一 ...
 - WebDriverWait显示等待
			
等待页面加载完成,找到某个条件发生后再继续执行后续代码,如果超过设置时间检测不到则抛出异常 WebDriverWait(driver, timeout, poll_frequency=0.5, ign ...
 - ava Double: 四舍五入并设置小数点位数
			
public static void main(String[] args) { // 1. 先乘后四舍五入, 再除; double d = 62.31060027198647; double d2 ...
 - 【187】◀▶ 编辑博客的文本格式 & 装饰
			
参考:博客园页面设置 参考:共享一下我的自定义CSS博客皮肤(2012.3) 一.文字周围带框框 插入一个代码,要折叠式,如下图所示: 史蒂夫 示例 选中“示例”,将其拷贝,然后黏贴,就有如下的效 ...
 - Vijos P1951 玄武密码 (AC自动机)
			
描述 在美丽的玄武湖畔,鸡鸣寺边,鸡笼山前,有一块富饶而秀美的土地,人们唤作进香河.相传一日,一缕紫气从天而至,只一瞬间便消失在了进香河中.老人们说,这是玄武神灵将天书藏匿在此. 很多年后,人们终于在 ...
 - ubuntu中desktop与alternate版本的区别(转载)
			
转自:http://www.hyleong.com/ubuntu-desktop-alternate/ 今天ubuntu发布了11.04版本,但是下载的时候有desktop和alternate版本,他 ...
 - Luogu P1280 Niko的任务【线性dp】By cellur925
			
Nikonikoni~~ 题目传送门 这是当时学长讲dp的第一道例题,我还上去献了个丑,然鹅学长讲的方法我似董非董(??? 我当时说的怎么设计这道题的状态,但是好像说的是二维,本题数据范围均在1000 ...
 - 线程池之ThreadPoolExecutor使用
			
ThreadPoolExecutor机制 一.概述 1.ThreadPoolExecutor作为java.util.concurrent包对外提供基础实现,以内部线程池的形式对外提供管理任务执行,线 ...
 - windows 下使用命令行操作ftp
			
open 192.168.10.6 (连接到FTP主机) User allan\ftp (用户连接验证,注意这里的用户用到的是FTP服务器端创建的用户名) 123 ...
 - python的Template使用指南
			
本文主要讲解了python中Template使用方法以及使用技巧,非常实用,有需要的朋友可以参考下: Template无疑是一个好东西,可以将字符串的格式固定下来,重复利用.同时Template也可以 ...