Java 序列化,java序列化
Java 序列化,java序列化
前言
对于Java的序列化,一直只知道只需要实现Serializbale这个接口就可以了。停留在了表面,在项目中使用Dubbo时,发现对象需要实现Serializbale,涉及到了序列化问题。就补了一下Java序列化的底层实现。
1.What
- Java序列化 是指把Java对象保存为二进制字节码的过程;
- Java反序列化 是指把二进制字节码重新转换成Java对象的过程。
那么为什么需要序列化呢?
第一种情况是:一般情况下Java对象的声明周期都比Java虚拟机的要短,实际应用中我们希望在JVM停止运行之后能够持久化指定的对象,这时候就需要把对象进行序列化之后保存。
第二种情况是:需要把Java对象通过网络进行传输的时候。因为数据只能够以二进制的形式在网络中进行传输,因此当把对象通过网络发送出去之前需要先序列化成二进制数据,在接收端读到二进制数据之后反序列化成Java对象。
2.How
演示demo
使用一个demo演示一下:
import java.io.*;
public class Student implements Serializable {
private static final long serialVersionUID = -7067671538866851177L;
private Integer id;
private String name;
private Integer age;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public static void main(String[] args) throws Exception {
// 1. 序列化:对象->文件
FileOutputStream fileOutputStream = new FileOutputStream("student.out");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
Student writeStudent = new Student();
writeStudent.setId(1);
writeStudent.setName("xiao");
writeStudent.setAge(25);
objectOutputStream.writeObject(writeStudent);
objectOutputStream.flush();
objectOutputStream.close();
// 2.反序列化:文件->对象
FileInputStream fileInputStream = new FileInputStream("student.out");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
Student readStudent = (Student) objectInputStream.readObject();
System.out.println("sutudent id :" + readStudent.getId());
System.out.println("sutudent name :" + readStudent.getName());
System.out.println("sutudent age :" + readStudent.getAge());
}
}
输出结果:
sutudent id :1
sutudent name :xiao
sutudent age :25
查看二进制文件
使用二进制编辑器(如: Synalyze It Pro Mac)查看 student.out 文件
3.Why
1.调用ObjectOutputStream.writeObject()和ObjectInputStream.readObject()之后究竟做了什么?
1. Serializable 接口
查看 Serializable 接口,可以发现这个接口没有任何方法或字段。
/*
* @author unascribed
* @see java.io.ObjectOutputStream
* @see java.io.ObjectInputStream
* @see java.io.ObjectOutput
* @see java.io.ObjectInput
* @see java.io.Externalizable
* @since JDK1.1
*/
public interface Serializable {
}
查看注释可以看到:
The serialization interface has no methods or fields
and serves only to identify the semantics of being serializable.
翻译:序列化接口没有方法或字段,并仅用于识别可序列化的语义。
2. ObjectOutputStream
对象输出流
根据我们的demo代码,和根据Serializable
注释查看 java.io.ObjectOutputStream
类
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
在调用wroteObject()
进行序列化之前会先调用ObjectOutputStream
的构造函数生成一个ObjectOutputStream
对象,构造函数如下:
public ObjectOutputStream(OutputStream out) throws IOException {
verifySubclass();
// 1.创建一个bout,表示底层的块数据输出流容器
bout = new BlockDataOutputStream(out);
// ...
// 2.写入文件头
writeStreamHeader();
// 3.flush数据(提交数据)
bout.setBlockDataMode(true);
// ...
}
构造函数中首先会把bout对绑定到底层的字节数据容器,接着会调用writeStreamHeader()方法,该方法实现如下:
protected void writeStreamHeader() throws IOException {
bout.writeShort(STREAM_MAGIC);
bout.writeShort(STREAM_VERSION);
}
查看写入的字符:
/**
* Magic number 写入 stream header.
*/
final static short STREAM_MAGIC = (short)0xaced;
/**
* 版本号 写入 stream header.
*/
final static short STREAM_VERSION = 5;
在writeStreamHeader()方法中首先会往底层字节容器中写入表示序列化的Magic Number以及版本号。
到此时,流中已经有4个字节: AC ED (Magic number) 00 05 (版本号)。
3.writeObject()
写入对象
我们的demo代码:
objectOutputStream.writeObject(writeStudent);
查看 writeObject():
public final void writeObject(Object obj) throws IOException {
// ...
try {
// 正常调用 writeObject0() 方法进行序列化
writeObject0(obj, false);
} catch (IOException ex) {
// ...
}
}
查看 writeObject0() :
private void writeObject0(Object obj, boolean unshared)
throws IOException
{
// ...
try {
// ...
Object orig = obj;
// 获取要序列化的对象
Class<?> cl = obj.getClass();
ObjectStreamClass desc;
for (;;) {
Class<?> repCl;
// 创建描述cl的ObjectStreamClass对象
desc = ObjectStreamClass.lookup(cl, true);
// ...
}
if (enableReplace) {
Object rep = replaceObject(obj);
if (rep != obj && rep != null) {
cl = rep.getClass();
desc = ObjectStreamClass.lookup(cl, true);
}
obj = rep;
}
// ...
// 根据不同的类型,进行不同的写入操作
if (obj instanceof String) {
writeString((String) obj, unshared);
} else if (cl.isArray()) {
writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
writeEnum((Enum<?>) obj, desc, unshared);
} else if (obj instanceof Serializable) {
writeOrdinaryObject(obj, desc, unshared);
} else {
// ...
}
} finally {
depth--;
bout.setBlockDataMode(oldMode);
}
}
从上面代码里面可以看到,程序主要做了两件事:
这里解答了一个问题:
Serializbale 接口是个空的接口,并没有定义任何方法,为什么需要序列化的接口只要实现Serializbale 接口就能够进行序列化?
答案:
Serializable 接口这是一个标记,告诉程序所有实现了”Serializable”的对象都需要进行序列化。
接着查看 writeOrdinaryObject()
方法:
private void writeOrdinaryObject(Object obj,
ObjectStreamClass desc,
boolean unshared)
throws IOException
{
// ...
try {
desc.checkSerialize();
// 写入TC_OBJECT = (byte)0x73; 表示新对象
bout.writeByte(TC_OBJECT);
// 写入类元数据
writeClassDesc(desc, false);
handles.assign(unshared ? null : obj);
if (desc.isExternalizable() && !desc.isProxy()) {
writeExternalData((Externalizable) obj);
} else {
// 写入被序列化的对象的实例数据
writeSerialData(obj, desc);
}
} finally {
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
}
在这个方法中首先会往底层字节容器中写入TC_OBJECT,表示这是一个新的Object。
writeClassDesc()
方法
接下来会调用writeClassDesc()
方法写入被序列化对象的类的类元数据,writeClassDesc()
方法实现如下:
private void writeClassDesc(ObjectStreamClass desc, boolean unshared)
throws IOException
{
int handle;
if (desc == null) {
// 如果desc为null
writeNull();
} else if (!unshared && (handle = handles.lookup(desc)) != -1) {
writeHandle(handle);
} else if (desc.isProxy()) {
writeProxyDesc(desc, unshared);
} else {
writeNonProxyDesc(desc, unshared);
}
}
在这个方法中会先判断传入的desc是否为null,如果为null则调用writeNull()方法.
writeNull()
方法
/**
* 写入 null code 到 stream.
*/
private void writeNull() throws IOException {
// TC_NULL = (byte)0x70;
// 表示对一个Object引用的描述的结束
bout.writeByte(TC_NULL);
}
writeNonProxyDesc()
方法
如果不为null,则一般情况下接下来会调用writeNonProxyDesc()
方法,该方法实现如下:
private void writeNonProxyDesc(ObjectStreamClass desc, boolean unshared)
throws IOException
{
// TC_CLASSDESC = (byte)0x72;
// 表示一个新的Class描述符
bout.writeByte(TC_CLASSDESC);
handles.assign(unshared ? null : desc);
if (protocol == PROTOCOL_VERSION_1) {
// do not invoke class descriptor write hook with old protocol
desc.writeNonProxy(this);
} else {
writeClassDescriptor(desc);
}
Class<?> cl = desc.forClass();
bout.setBlockDataMode(true);
if (cl != null && isCustomSubclass()) {
ReflectUtil.checkPackageAccess(cl);
}
annotateClass(cl);
bout.setBlockDataMode(false);
bout.writeByte(TC_ENDBLOCKDATA);
writeClassDesc(desc.getSuperDesc(), false);
}
在这个方法中首先会写入一个字节的TC_CLASSDESC
,这个字节表示接下来的数据是一个新的Class描述符,接着会调用writeNonProxy()
方法写入实际的类元信息,writeNonProxy()
实现如下:
writeNonProxy()
方法:
void writeNonProxy(ObjectOutputStream out) throws IOException {
// 写入类的名字
out.writeUTF(name);
// 写入类的序列号
out.writeLong(getSerialVersionUID());
// 获取类的标识
byte flags = 0;
if (externalizable) {
flags |= ObjectStreamConstants.SC_EXTERNALIZABLE;
int protocol = out.getProtocolVersion();
if (protocol != ObjectStreamConstants.PROTOCOL_VERSION_1) {
flags |= ObjectStreamConstants.SC_BLOCK_DATA;
}
} else if (serializable) {
flags |= ObjectStreamConstants.SC_SERIALIZABLE;
}
if (hasWriteObjectData) {
flags |= ObjectStreamConstants.SC_WRITE_METHOD;
}
if (isEnum) {
flags |= ObjectStreamConstants.SC_ENUM;
}
// 写入类的flag
out.writeByte(flags);
// 写入对象的字段的个数
out.writeShort(fields.length);
for (int i = 0; i < fields.length; i++) {
ObjectStreamField f = fields[i];
out.writeByte(f.getTypeCode());
out.writeUTF(f.getName());
if (!f.isPrimitive()) {
// 如果不是原始类型,即是对象或者Interface
// 则会写入表示对象或者类的类型字符串
out.writeTypeString(f.getTypeString());
}
}
}
writeNonProxy()
方法中会按照以下几个过程来写入数据:
void writeUTF(String s, long utflen) throws IOException {
if (utflen > 0xFFFFL) {
throw new UTFDataFormatException();
}
// 写入两个字节的s的长度
writeShort((int) utflen);
if (utflen == (long) s.length()) {
writeBytes(s);
} else {
writeUTFBody(s);
}
}
对于本例中flag = 0x02表示只是Serializable类型。
2.sutdent.out 文件中的二进制分别代表什么意思?
aced Stream Magic
0005 序列化版本号
73 标志位:TC_OBJECT,表示接下来是个新的Object
72 标志位:TC_CLASSDESC,表示接下来是对Class的描述
001C 类名的长度为28
636F 6D2E 6A65 696B 6572 2E73 7368 2E6D 6F64 656C 2E53 7475 6465 6E74
com.jeiekr.ssh.model.Student(类名)
9DEA 962E 78B3 F297
序列号
02 flag,可序列化
0003 Student的字段的个数,为3
4C TypeCode:L,表示是个Class或者Interface
0003 字段名长度,占3个字节
61 67 65 字段名(age)
74 标志位:TC_STRING,表示后面的数据是个字符串
0013 类名长度,占19个字节
4C6A 6176 612F 6C61 6E67 2F49 6E74 6567 6572 3B
Ljava/lang/Integer;
4C TypeCode:L,表示是个Class或者Interface
0002 字段名长度,占2个字节
69 64 字段名(id)
71 标志位:TC_REFERENCE,表示数据类型已经写入流(Integer)
007E 指向已经写入过的对象类型
0001 指向1号对象->Ljava/lang/Integer;
4C TypeCode:L,表示是个Class或者Interface
0004 字段名长度,占4个字节
6E 61 6D 65 字段名(name)
74 标志位:TC_STRING,表示后面的数据是个字符串
0012 类名长度,占18个字节
4C6A 6176 612F 6C61 6E67 2F53 7472 696E 67 3B
Ljava/lang/String;
78 标志位:TC_ENDBLOCKDATA,对象的数据块描述的结束
70 标志位:TC_NULL,没有对象引用
73 标志位:TC_OBJECT,新对象
72 标志位:TC_CLASSDESC,新类描述
0011 类名长度,占17个字节
6A61 7661 2E6C 616E 672E 496E 7465 6765 72
java.lang.Integer
12E2 A0A4 F781 8738
序列号
02 flag,表示可序列化
0001 字段个数,1个
49 TypeCode,I,表示int类型
0005 字段名长度,5个字节
76 61 6C 75 65
value
78 标志位:TC_ENDBLOCKDATA,对象的数据块描述的结束
72 标志位:TC_CLASSDESC,新类描述
0010 类名长度,占16个字节
6A61 7661 2E6C 616E 672E 4E75 6D62 6572
java.lang.Number
86AC951D0B94E08B
序列号
02 flag,表示可序列化
0000 字段个数,0个
78 标志位:TC_ENDBLOCKDATA,对象的数据块描述的结束
70 标志位:TC_NULL,Null object reference.
0000 0019 age的值:25
73 标志位,TC_OBJECT:表示接下来是个新的Object
71 标志位:TC_REFERENCE,表示数据类型已经写入流(Integer)
007E 指向已经写入过的对象类型
0004 指向4号对象->java.lang.Number
0000 0001 id的值:1
74 标志位:TC_STRING,表示后面的数据是个字符串
0004 类名长度,占4个字节
7869 616F name的值:xiao
4.Other
static 和 transient 字段不能被序列化。
相关文章
- 暂无相关文章
用户点评