注:如果您发现任何不正确的内容,或者您想要分享有关本文主题的更多信息,请撰写评论或联系我 15340938921@163.com。
以下整理文章参考《java编程思想第四版》《EffectiveJava》oracle java官网 以及部分博客
以下环境基于 windows 10 java version “1.8.0_171”
文中代码可能不太严谨,直供研究测试使用!
http://zjlgd.cn 请关注我的网站,大家一起进步!
是什么
java的序列化是java I/O流内容的一部分。
序列化是将类的实例(类的对象)转换为字节流的过程。然后,此字节流可以作为文件存储在磁盘上,也可以通过网络发送到另一台计算机(使用另一台JVM 实例化的对象)。
序列化对象时 仅会序列化 类的默认可序列化字段被定义为非瞬态和非静态字段。 也就是非
static
以及非transient
修饰字段
当程序关闭或者休眠时,使用 Serialization 将对象状态保存在磁盘的某个文件上,当程序重新启动时可以访问该文件将对象重新new出来,这个过程可以称之为 反序列化
。(术语可能不太标准,仅供理解)
当一个类从序列化状态恢复时,反序列化会绕过构造函数包括无参构造。
为什么
从何而来?
java的对象必须在内存在才能工作,一旦断电或者java程序中断,java产生的所有对象都烟消云散。
那么为什么不能跳过内存,直接使用硬盘来操作对象?
1. 按照 冯·诺依曼体系要求,数据必须在内存中,cpu才能操作。
2. 磁盘速度相比内存和cpu是非常慢的。
json不是一样可以达到持久性的效果吗,为什么要用java serializable?
将对象的值 转为 json类型格式存储在文件,或者将对象值存储与数据库同样可以达到 java 序列化,持久性的效果。java的序列化看你的选择,功能就摆在那里,用不用是你的事。用java自己写的当然更加坚挺,持久!
但是在使万物都成为对象的精神中,如果能够将一个对象声明为是“持久性”的,并为我们处理掉所有细节,那将会显得十分方便。
---- 摘自《java编程思想第四版》 18.12
- java序列话可以将对象转化为字节序列,且这个序列可以恢复为原来的对象。在windows 实例化 的对象通过序列化将字节序列发给Unix系统,不必担心数据在不同机器的不同表示,以及字节顺序等等,java反序列化能够准确的重新组装。(支持RMI)
- 利用java的序列化可以实现轻量级的持久性
还有等等·····
怎么样
注意:
如果父类已实现Serializable接口,则子类不需要实现它,反之亦然。
仅通过序列化过程保存非静态数据成员(not static)
静态数据成员和临时数据成员(transient)不通过序列化过程保存。因此,如果您不想保存非静态数据成员的值,则将其设置为瞬态。
反序列化对象时,永远不会调用对象的构造函数。
关联对象必须实现Serializable接口。
import java.io.Serializable;
public class Box implements Serializable {
private static final long serialVersionUID = 61485867982808238L;
private int width;
private int height;
public Box(int width, int height) {
this.width = width;
this.height = height;
}
public Box() {
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
// 为了方便观察结果,重写toString方法
@Override
public String toString() {
return "Box{" +
"width=" + width +
", height=" + height +
'}';
}
}
- serialVersionUID 流的唯一标识符(序列版本) 适用于java序列化机制。简单来说,JAVA序列化的机制是通过判断类的serialVersionUID来验证的版本一致的。
- 在进行反序列化时,JVM会把传来的字节流中的serialVersionUID于本地相应实体类的serialVersionUID进行比较。如果相同说明是一致的,可以进行反序列化,否则会出现反序列化版本一致的异常,即是InvalidCastException。
我可以不写serialVersionUID吗?
建议是 显式声明serialVersionUID。并且不为 1L!
如果没有显式的声明私有的,静态的,最终的,long类型的serialVersionUID ,系统会自动根据这个类的相关数据,运行时生成标识。如果类的名称或成员变量,实现继承类有改变自动生成的标识也会改变。为了避免这方面的问题,应该显式声明serialVersionUID,并且他应该是唯一的,而不是 1L。
取自 《Effective Java》第二版
关于 版本id 不同导致的详细情况 请参考 https://blog.youkuaiyun.com/u014750606/article/details/80040130
A 序列化 B 反序列化
- 版本号不一致 —> 报错 InvalidCastException
- 版本号一致,A 多字段 —> A多 字段丢失
- 版本号一致,B 少字段 —> A多 字段丢失.
- 版本号一致,B 多字段 —> B 多字段被赋予默认值
serializable序列化
serializable 接口是标记接口。这意味着如果您的类派生自此接口,则不必实现任何方法。这只是一个标记,Java运行时,在尝试序列化类时,只会检查类中是否存在此接口。如果类继承层次结构中存在Serializable
接口,则Java运行时将负责类的序列化。 序列化我们需要执行以下几个步骤
1. 创建一个文件的新 FileOutputStream,我们想要序列化该类到指定的文件
2. 创建 ObjectOutputStream 有参构造入参为 第一步创建的FileOutputStream
3. 将对象写入 ObjectOutputStream
4. 关闭所有流
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class Test {
public static void SerializeToFile(Object classObject, String fileName) {
try {
// 这里的FileOutputStream 只是为了方便观察,将序列化后的结构写入文件。
FileOutputStream fileStream = new FileOutputStream(fileName);
// ObjectOutputStream继承OutputStream,序列化基于字节因要使用InputStream/OutputStream继承 // 层次结构
ObjectOutputStream objectStream = new ObjectOutputStream(fileStream);
objectStream.writeObject(classObject);
objectStream.close();
fileStream.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String[] args) {
Box rect = new Box(18, 78);
// 我是windows 10 环境
SerializeToFile(rect, "G:\\rectSerialized");
}
}
执行下面这步将会将对象序列化
objectStream.writeObject(classObject);
在执行writeObject方法时,会校验要序列化的类是否标记serializable,如果不是将会抛出 NotSerializableException。
// remaining cases
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 {
if (extendedDebugInfo) {
throw new NotSerializableException(
cl.getName() + "\n" + debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());
}
}
对象的序列化会对对象内包含的引用进行跟踪,并且保存他。
执行main函数之后,在我的G盘根目录下生成了一个 rectSerialized 文件,大小为1KB,我尝试使用notepad++打开,但是显示的内容看不懂。。。
注意:
序列化只是序列化对象的变量(可序列化的),并不涉及方法
serializable反序列化
反序列化即:将InputStream封装在ObjectInputStream中,调用readObject(),得到的是一个向上转型的 Object,需要向下转型 强转为指定对象。
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;
public class Test {
public static Object DeSerializeFromFileToObject(String fileName) {
try {
FileInputStream fileStream = new FileInputStream(new File(fileName));
ObjectInputStream objectStream = new ObjectInputStream(fileStream);
Object deserializeObject = objectStream.readObject();
objectStream.close();
fileStream.close();
return deserializeObject;
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
public static void main(String[] args) {
Box example =(Box)DeSerializeFromFileToObject("G:\\rectSerialized");
System.out.println(example.toString());
}
}
执行main函数结果如下:
Box{width=18, height=78}
Externalizable
序列化时会将本对象的某个子对象的某一部分进行序列化,这种情况可以实现Externalizable 接口。用法请自行参考。
谁用了Externalizable
哪个东西在哪里使用了序列化
TODO
- 为啥,数据必须在内存中,cpu才能操作。Why can’t CPUs get data directly from Hard drives?