java.lang.Appendable
public interface Appendable {
Appendable append(CharSequence csq) throws IOException;
Appendable append(CharSequence csq, int start, int end) throws IOException;
Appendable append(char c) throws IOException;
}
Appendable接口表达可追加字符的能力,一般用在流相关的类上,在java.lang包下会有StringBuffer与StringBuilder实现,这两个类区别于String的不变性,可支持追加。
java.lang.CharSequence
public interface CharSequence {
int length();
char charAt(int index);
CharSequence subSequence(int start, int end);
public String toString();
public default IntStream chars() {
...
}
public default IntStream codePoints() {
...
}
}
CharSequence接口表达字符序列,包含指定位置获取字符,截取区域字符,字符长度方法。java.lang包下String,StringBuffer,StringBuilder都实现了该接口。
java.util.RandomAccess
RandomAccess接口是一个空接口,单纯就是为了区分集合底层的实现方式的,列表的底层实现方式可以简单的分为两种,数组实现和链表实现。
数组实现的列表将会对使用index访问的操作效率较高;而链表实现的列表则在使用index访问元素效率不高,相反在特定位置上插入,删除元素效率很高。因此会导致在遍历的时候,可以采用的方式也会有两种:一种是使用过index下标访问,另一种是使用迭代器访问。RandomAccess就是为了标识出那些底层是使用数组实现的类,在不同实现下使用不同的实现方法。如下方法,选自Collections源码:
public static <T> int binarySearch(List<? extends Comparable<? super T>> list, T key) {
if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
return Collections.indexedBinarySearch(list, key);
else
return Collections.iteratorBinarySearch(list, key);
}
java.util.Interable
Interable的存在是为了让集合对象可以用for循环进行迭代遍历所含元素。其源码如下:
public interface Iterable <T>{
//(1)返回迭代器
Iterator<T> iterator();
// (2) 遍历所有元素
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
default Spliterator<T> spliterator() {
return Spliterators.spliteratorUnknownSize(iterator(), 0);
}
}
public interface Iterator<E> {
boolean hasNext();
E next();
default void remove() {
throw new UnsupportedOperationException("remove");
}
default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
}
java中的集合类型都会实现Collection接口,而collection接口会继承迭代器接口,意味着所有集合都可以使用迭代器进行遍历操作。
public interface Collection<E> extends Iterable<E> {}
这里写个测试case来描述迭代器是如何使用的:
@Test
public void test001(){
Collection<String> c = Arrays.asList("1","2","3");
Iterator<String> iterator = c.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
迭代器模式在语法糖层面也有关联,例如:
@Test
public void test002(){
Collection<String> c = Arrays.asList("1","2","3");
for(String s : c){
System.out.println(s);
}
}
获得此方法的字节码:
0 iconst_3
1 anewarray #2 <java/lang/String>
4 dup
5 iconst_0
6 ldc #3 <1>
8 aastore
9 dup
10 iconst_1
11 ldc #4 <2>
13 aastore
14 dup
15 iconst_2
16 ldc #5 <3>
18 aastore
19 invokestatic #6 <java/util/Arrays.asList : ([Ljava/lang/Object;)Ljava/util/List;>
22 astore_1
23 aload_1
24 invokeinterface #7 <java/util/Collection.iterator : ()Ljava/util/Iterator;> count 1
29 astore_2
30 aload_2
31 invokeinterface #8 <java/util/Iterator.hasNext : ()Z> count 1
36 ifeq 59 (+23)
39 aload_2
40 invokeinterface #10 <java/util/Iterator.next : ()Ljava/lang/Object;> count 1
45 checkcast #2 <java/lang/String>
48 astore_3
49 getstatic #9 <java/lang/System.out : Ljava/io/PrintStream;>
52 aload_3
53 invokevirtual #11 <java/io/PrintStream.println : (Ljava/lang/String;)V>
56 goto 30 (-26)
59 return
可以看到24-40行字节码内实际上是针对其迭代器进行执行。
java.util.Collection
Collection接口可以说为集合框架定下一个基调,所有的集合都应该有这个类定义的行为
public interface Collection<E> extends Iterable<E> {
// 集合大小及基础判定
int size();
boolean isEmpty();
boolean contains(Object o);
// 集合遍历能力
Iterator<E> iterator();
//转换操作
Object[] toArray();
<T> T[] toArray(T[] a);
//操作集合内元素相关,并集 交集实现
boolean add(E e);
boolean addAll(Collection<? extends E> c);
boolean remove(Object o);
boolean removeAll(Collection<?> c);
boolean containsAll(Collection<?> c);
boolean retainAll(Collection<?> c);
// 清除集合内元素
void clear();
// 1.8新加默认实现方法,基于迭代器遍历实现
default boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter);
boolean removed = false;
final Iterator<E> each = iterator();
while (each.hasNext()) {
if (filter.test(each.next())) {
each.remove();
removed = true;
}
}
return removed;
}
// 以下是流式编程的默认方法
@Override
default Spliterator<E> spliterator() {
return Spliterators.spliterator(this, 0);
}
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
default Stream<E> parallelStream() {
return StreamSupport.stream(spliterator(), true);
}
boolean equals(Object o);
int hashCode();
}
可以看到这个接口内定义的是集合相关操作,这里进行分组描述:
- 描述当前集合的状态
- 当前集合的大小
- 当前集合是否为空
- 当前集合是否包含某个元素(集合领域的 ‘属于’ 操作)
- 当前集合是否包含另一个集合(集合领域的 ‘是否为子集’ 操作)
- 集合内元素操作:
- 增加 一个/多个 元素
- 删除 一个/多个 元素
- 在某一条件下进行删除元素
- 针对当前集合的操作
- 遍历能力:使用迭代器遍历
- 对当前集合进行清理
- 转换当前集合内元素为数组
- 针对多个集合间的操作
- 两个集合进行交集
- 两个集合进行并集(数学意义上需要进行去重,这里的集合概念可以进行不去重)
这里将一些常用的AbstractCollection的源码拿出来看看是怎么实现的:
public boolean contains(Object o) {
Iterator<E> it = iterator();
if (o==null) {
while (it.hasNext())
if (it.next()==null)
return true;
} else {
while (it.hasNext())
if (o.equals(it.next()))
return true;
}
return false;
}
public boolean containsAll(Collection<?> c) {
for (Object e : c)
if (!contains(e))
return false;
return true;
}
public boolean remove(Object o) {
Iterator<E> it = iterator();
if (o==null) {
while (it.hasNext()) {
if (it.next()==null) {
it.remove();
return true;
}
}
} else {
while (it.hasNext()) {
if (o.equals(it.next())) {
it.remove();
return true;
}
}
}
return false;
}
public Object[] toArray() {
Object[] r = new Object[size()];
Iterator<E> it = iterator();
for (int i = 0; i < r.length; i++) {
r[i] = it.next();
}
return r;
}
都是一些非常简单的方法,而且实现相当粗暴,可以看到都是使用迭代器进行操作的。如果你的私有实现需要进行高效率操作可以进行覆盖。
java.util.Comparator
public interface Comparator<T> {
int compare(T o1, T o2);
// .. 略去1.8加入的各种default方法
}
Comparator是个比较器接口,int 返回只有三种状态:
- 一个正数,表达o1 > o2,
- 0 表示o1与o2相等
- 一个负数,表达o1 < o2
同这个接口可以配合使用的是,Comparable接口,标记当前对象是个可以比较的对象:
public interface Comparable<T> {
public int compareTo(T o);
}
Comparator与Comparable该接口一般用在排序上,JDK内也有一些实现,例如Arrays内部类:
static final class NaturalOrder implements Comparator<Object> {
@SuppressWarnings("unchecked")
public int compare(Object first, Object second) {
return ((Comparable<Object>)first).compareTo(second);
}
static final NaturalOrder INSTANCE = new NaturalOrder();
}
java.util.List
List接口继承Collection接口,从接口定义上来说很多都和Collection的一些方法重合。
public interface List<E> extends Collection<E> {
int size();
boolean isEmpty();
boolean contains(Object o);
Iterator<E> iterator();
Object[] toArray();
<T> T[] toArray(T[] a);
boolean add(E e);
boolean remove(Object o);
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);
boolean removeAll(Collection<?> c);
boolean retainAll(Collection<?> c);
//新加的
boolean addAll(int index, Collection<? extends E> c);
//新加
default void replaceAll(UnaryOperator<E> operator) {
Objects.requireNonNull(operator);
final ListIterator<E> li = this.listIterator();
while (li.hasNext()) {
li.set(operator.apply(li.next()));
}
}
@SuppressWarnings({"unchecked", "rawtypes"})
default void sort(Comparator<? super E> c) {
Object[] a = this.toArray();
Arrays.sort(a, (Comparator) c);
ListIterator<E> i = this.listIterator();
for (Object e : a) {
i.next();
i.set((E) e);
}
}
void clear();
boolean equals(Object o);
int hashCode();
E get(int index);
E set(int index, E element);
void add(int index, E element);
E remove(int index);
int indexOf(Object o);
int lastIndexOf(Object o);
ListIterator<E> listIterator();
ListIterator<E> listIterator(int index);
List<E> subList(int fromIndex, int toIndex);
@Override
default Spliterator<E> spliterator() {
return Spliterators.spliterator(this, Spliterator.ORDERED);
}
}
List表达的是顺序列表,相当于在原先集合的基础上增加了顺序,因此在api层面出现了很多包含index的方法。
java.io.Serializable
java.io.Serializable接口是为了序列化而存在的。这个接口仅仅是个空接口,类似于java.lang.Cloneable,仅打上了一个标记,告知JVM。序列化机制可以解决很多实际问题,例如:
- 有些数据很占用内存但是暂时是用不上的,纯粹是在浪费空间,那么就可以先存在硬盘上,等到需要的时候再拿出来使用
- 包括大数据,分布式处理的场景下,需要多个节点进行协同工作,那么就可以把这个机子上的object生成流,在网络上进行传输,这样就可以做到多个节点之间的交流通信。
实现序列化事实上我们有很多手段。这回我们聊的是JDK自带的序列化方式。
1. 原始序列化方式
我们先看最早前辈们是如何解决序列化的。这里主要利用了DataInputStream/DataOutputStream。
import java.io.*;
/**
* DataOutputStream和DataInputStream的用法
*/
public class DataStreamTest {
public static void main(String[] args) throws IOException {
// 创建FileOutputStream对象
FileOutputStream f = new FileOutputStream("./DataStreamTest.txt");
// 创建DataOutputStream对象
DataOutputStream d = new DataOutputStream(f);
// 写入磁盘文件数据
d.writeUTF("使用writeUTF向磁盘写入数据");
d.writeChars("使用writeChars向磁盘写入数据");
d.writeBytes("使用writeByte向磁盘写入数据");
// 将流关闭
d.close();
// 创建FileInputStream对象
FileInputStream fi = new FileInputStream("./DataStreamTest.txt");
// 创建DtatInputStream对象
DataInputStream da = new DataInputStream(fi);
String str = da.readUTF();
System.out.println(str);
}
}
从上面的代码中,你可以看出,最早的序列化方式其实就是使用流 DataInput/OutputStream 将对象中字段的值逐个的写入文件,完成所谓的"序列化操作"。恢复对象的时候也必须按照写入的顺序一个字段一个字段的读取,如果一个类有一百个字段,那就意味着我们需要手动写入一百次,这样太费劲了。
2. 标准序列化机制
为了满足开发者更舒服地使用序列化,JDK内置了一套标准的序列化机制。JDK使用java.io.Serializable接口与ObjectOutputStream/ObjectInputStream进行配合来支持序列化。以下是其案例代码:
import lombok.Data;
import org.junit.Test;
import java.io.*;
public class SerializableTest {
@Test
public void test0() throws IOException, ClassNotFoundException {
//初始化一个User实例,并填充一部分数据
User user = new User();
user.setName("aa");
user.setAge(23);
System.out.println(user);
//使用ObjectOutputStream开始写入一个文件
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
oos.writeObject(user);
oos.close();
//从文件内反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("tempFile")));
User newUser = (User) ois.readObject();
System.out.println(newUser);
}
}
@Data
class User implements Serializable {
private String name;
private int age;
}
为了实现序列化,只需要让序列化类继承 java.io.Serializable 接口即可。虚拟机执行序列化指令的时候会检查,如果序列化的对象所对应的类型没有挂上 java.io.Serializable 接口则抛出异常。
ObjectOutputStream 用于将对象转换为字节流写出,其内部所有的字节流操作都依赖我们构造实例时传入的 OutputStream 实例。ObjectInputStream 则是用于从磁盘读取并恢复一个 Java 对象。
2.1 transient 关键字
当我们在序列化一个对象时,默认除了静态字段之外都将会进行序列化。有些属性我们不想序列化,那么我们可以使用 transient 关键字进行标记。
在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。以下是它的源码:
private static ObjectStreamField[] getDefaultSerialFields(Class<?> cl) {
Field[] clFields = cl.getDeclaredFields();
ArrayList<ObjectStreamField> list = new ArrayList<>();
int mask = Modifier.STATIC | Modifier.TRANSIENT;
int size = list.size();
return (size == 0) ? NO_FIELDS :
list.toArray(new ObjectStreamField[size]);
}
2.2 serialVersionUID号
虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致(就是 private static final long serialVersionUID)
serialVersionUID可以理解为是当前类型的一个唯一标识,每个对象在序列化时都会写入外部类型的这个版本号,反序列化时首先就会检查二进制文件中的版本号与目标类型中的版本号是否一样,如果不一样将拒绝反序列化。
如果一份序列化文件中存在对同一对象的多次序列化,Java 也只会保存一份对象数据,后面的都用引用指向这里。序列化 ID 在 ide 下提供了两种生成策略:
- 固定的 1L
- 随机生成一个不重复的 long 类型数据
根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段
如果没有特殊需求,就是用默认的 1L 就可以,这样可以确保代码一致时反序列化成功。那么随机生成的序列化 ID 有什么作用呢,有些时候,通过改变序列化 ID 可以用来限制某些用户的使用。简单来说,Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体(类)的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。(InvalidCastException)
当实现java.io.Serializable接口的实体(类)没有显式地定义一个名为serialVersionUID,类型为long的变量时,Java序列化机制会根据编译的class信息自动生成一个serialVersionUID作序列化版本比较用,这种情况下,如果class文件没有发生变化,就算再编译多次serialVersionUID也不会变化的。
如果我们不希望通过编译来强制划分软件版本,即实现序列化接口的实体能够兼容先前版本,未作更改的类,就需要显式地定义一个名为serialVersionUID,类型为long的变量,不修改这个变量值的序列化实体都可以相互进行串行化和反串行化。
如果你没有考虑到兼容性问题时,就把它关掉,不过有这个功能是好的,只要任何类别实现了Serializable. 这个接口的话,如果没有加入 serialVersionUID,Eclipse都会给你warning提示,这个serialVersionUID为了让该类别 Serializable向后兼容。
假设有A服务端和B服务端,两个服务端都存在 Test类,A服务将Test的一个实例进行序列化,B服务将字节流进行反序列化。那么假定A,B服务上的 Test类 的序列号一致,但是类本身组成不一致的情况下,会有以下几种情况:
- A服务Test类相比B服务多一个字段,则B服务序列化时自动丢失新增字段的信息。
- A服务Test类相比B服务少一个字段,则B服务序列化时自动补全丢失字段的信息,赋值为对应的字段类型初始值。
2.3 父类的序列化问题
一个子类实现了 Serializable接口,它的父类都没有实现 Serializable 接口,序列化该子类对象,然后反序列化后得到的实例将会丢失父类定义的某变量的数值。
因此,如果想将父类对象也序列化,就需要让父类也实现Serializable 接口。如果父类不实现的话的,就需要有默认的无参的构造函数。
在父类没有实现 Serializable 接口时,虚拟机是不会序列化父对象的,而一个 Java 对象的构造必须先有父对象,才有子对象,反序列化也不例外。所以反序列化时,为了构造父对象,只能调用父类的无参构造函数作为默认的父对象。因此当我们取父对象的变量值时,它的值是调用父类无参构造函数后的值。如果你考虑到这种序列化的情况,在父类无参构造函数中对变量进行初始化,否则的话,父类变量值都是默认声明的值。
2.4 对象重复引用问题
我们先来一个代码:
import java.io.*;
import comm.Employee;
import comm.Manager;
public class ObjectStreamTest {
public static void main(String[] args) {
Employee harry = new Employee("Harry Hacker", 50000, 1989, 10, 1);
Manager carl = new Manager("Carl Cracker", 8000, 1987, 12, 15);
Manager tony = new Manager("Tony Tester", 40000, 1990, 3, 15);
//两个Manager公用一个秘书(Employee)
carl.setSecretary(harry);
tony.setSecretary(harry);
Employee[] staff = new Employee[3];
staff[0] = harry;
staff[1] = carl;
staff[2] = tony;
try {
//将对象序列化到文件
ObjectOutputStream objOut = new ObjectOutputStream(new FileOutputStream("employee.dat"));
objOut.writeObject(staff);
objOut.close();
//将对象反序列化出来
ObjectInputStream objIn = new ObjectInputStream(new FileInputStream("employee.dat"));
Employee[] newStaff = (Employee[]) objIn.readObject();
objIn.close();
//修改第一个对象的属性值,从结果中可以看出,反序列化以后的对象依然保持着原来的引用关系
newStaff[0].raseSalay(10);
//打印
for(Employee e : newStaff){
System.out.println(e);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
获得到的结果是:
Employee [name=Harry Hacker, salay=50010.0, hireDay=Sun Oct 01 00:00:00 CST 1989]
Manager [Name=Carl Cracker, Salay=8000.0, HireDay=Tue Dec 15 00:00:00 CST 1987, secretary=Employee [name=Harry Hacker, salay=50010.0, hireDay=Sun Oct 01 00:00:00 CST 1989]]
Manager [Name=Tony Tester, Salay=40000.0, HireDay=Thu Mar 15 00:00:00 CST 1990, secretary=Employee [name=Harry Hacker, salay=50010.0, hireDay=Sun Oct 01
上面的代码中,你会发现,Employee是被共享的,对secretary对象而言,我们不能够简单粗暴的保存一个内存地址,对不同的机器而言,一个内存地址是没有意义的。
这里需要揭示一个重要的序列化机制:
- 对于对象的引用是按照下面的步骤进行处理的:
- 序列化过程中,对每一个遇到的object reference都分配一个序列号(serial number)
- 当一个对象引用是第一次遇见的时候,就保存其对象数据(object data)
- 如果某个对象引用不是第一次遇见,就将其标记为"和serial number x对象一样"
- 从文件(或者是网络文件)中反序列化的时候按照下面的步骤处理:
- 当第一次读取到一个特定对象的时候,首先是构建一个对象,然后用流数据初始化它,同时记录下serial number和内存地址之间的联系
- 当遇到标记为“和serial number x对象一样”的对象的时候,就用serial number x对应对象的内存地址初始化这个引用;
所以,从上面的过程可以看出,通过反序列化操作得到的对象和序列化之前的对象保持了一种同样的引用关系。
2.3 writeObject()与readObject()方法
虽然 java.io.Serializable 接口的序列化机制可以帮我们快速实现保存对象的功能,但是这中间产生字节流的过程开发者没有办法精确控制。为了满足开发者的精确控制的需求,JDK提供覆盖writeObject()与readObject()方法来控制序列化过程的机制。
用户自定义的 writeObject 和 readObject 方法可以允许用户控制序列化的过程,比如可以在序列化的过程中动态改变序列化的数值。当然,这个机制是需要开发者配合的,writeObject()与readObject()这两个方法的声明必须固定:
private void writeObject(java.io.ObjectOutputStream s) throws IOException;
private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException;
在序列化过程中,如果被序列化的类中定义了writeObject 和 readObject 方法,虚拟机会试图调用对象类里的 writeObject 和 readObject 方法,进行用户自定义的序列化和反序列化。如果没有这样的方法,则默认调用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。
这个机制在JDK源码的ArrayList内有所体现,ArrayList下存放数据的真实数组定义为
transient Object[] elementData; // non-private to simplify nested class access
这个数组里面很可能有部分是空的,如果直接进行序列化的话,会浪费空间。ArrayList为了序列化之后的数据要尽量纯粹,使用覆盖上述方法的方式进行序列化的控制。
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out element count, and any hidden stuff
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();
}
}
/**
* Reconstitute the <tt>ArrayList</tt> instance from a stream (that is,
* deserialize it).
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA;
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in capacity
s.readInt(); // ignored
if (size > 0) {
// be like clone(), allocate array based upon size not capacity
ensureCapacityInternal(size);
Object[] a = elementData;
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}
2.4 resolve方法
考虑一个问题:序列化会不会破坏单例?我们写一段代码试试:
import java.io.*;
/*
* Orientation类只有一个private属性的构造器,因此不能通过new来构造一个对象,只能通过Orientation.HORIZONTAL的方式获取对象
*/
class Orientation implements Serializable{
private static final long serialVersionUID = 1L;
public static final Orientation HORIZONTAL = new Orientation(1);
public static final Orientation VERTICAL = new Orientation(2);
private int val;
private Orientation(int val) {
this.val = val;
}
public int getVal() {
return val;
}
}
public class ObjectStreamTest {
public static void main(String[] args) {
try {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("temp"));
//序列化一个单体Orientation.HORIZONTAL
out.writeObject(Orientation.HORIZONTAL);
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("temp"));
Orientation orient = (Orientation) in.readObject();
in.close();
//验证反序列化得到一个单体orient和Orientation.HORIZONTAL是否为同一个对象
System.out.println(orient == Orientation.HORIZONTAL);
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行,得到的结果是 false。意味着反序列化得到的单体和本地的单体并不是同一个对象。这完全不能接受。
发生这个现象的原因是:在readObject()方法中会构造一个新的对象,然后返回。我们在单体类中加入一个readResolve方法,在这个方法中设法让不同的对象变得相同。这个解决策略的本质因素是:如果实现了Serializable接口的类中具有一个readResolve()方法,那么这个方法会在反序列化完成之后调用。我么就可以在这个方法中做点儿手脚。
修正上面的代码之后:
import java.io.*;
/*
* Orientation类只有一个private属性的构造器,因此不能通过new来构造一个对象,只能通过Orientation.HORIZONTAL的方式获取对象
*/
class Orientation implements Serializable{
private static final long serialVersionUID = 1L;
public static final Orientation HORIZONTAL = new Orientation(1);
public static final Orientation VERTICAL = new Orientation(2);
private int val;
private Orientation(int val) {
this.val = val;
}
public int getVal() {
return val;
}
protected Object readResolve() throws ObjectStreamException {
if(val == 1) return Orientation.HORIZONTAL;
if(val == 2) return Orientation.VERTICAL;
return null;
}
}
public class ObjectStreamTest {
public static void main(String[] args) {
try {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("temp"));
//序列化一个单体Orientation.HORIZONTAL
out.writeObject(Orientation.HORIZONTAL);
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("temp"));
Orientation orient = (Orientation) in.readObject();
in.close();
//验证反序列化得到一个单体orient和Orientation.HORIZONTAL是否为同一个对象
System.out.println(orient == Orientation.HORIZONTAL);
} catch (Exception e) {
e.printStackTrace();
}
}
}
再次运行,获得结果:true
因此我们得出结论,所有的单例类、枚举类在实现序列化时都应该提供readResolve()方法,如此可保证反序列化的对象依然正常
3. 自主控制序列化机制
使用Serializable序列化有一些缺点是无法避免的,例如:
- Serializable序列化的自定义过程范围狭窄,主要功能还是完全由默认序列化完成,在某些业务场景下,无法取得更好的灵活性。
- Serializable序列化过程是完全递归,且相对较缓慢。
- 对于需要禁止序列化的变量,需要增加transient关键字修饰,对于属性变量较多的类,操作较复杂。
- 无法控制字段的序列化和反序列化方式。
- Serializable序列化过程在创建对象时不会调用构造方法,因此会缺失构造方法内的逻辑。
整体来说,Serializable序列化方式的维护、运行效率并不是很高。因此JDK提供了 java.io.Externalizable 接口来满足序列化过程完全自定义化的机制。 java.io.Externalizable 是Serializable的子类,若要使用Externalizable序列化方式,只需让序列化类继承Externalizable接口,并实现writeExternal和readExternal方法。他们的方法声明是:
public void readExternal(ObjectInputStream in) throws IOException, ClassNotFoundException;
public void writeExternal(ObjectOutputStream out) throws IOException;
Externalizable的优先级比Serializable的优先级要高。
假如某个类同时实现了两个接口,那么优先使用Externalizable机制
在使用Externalizable进行序列化的时候,在读取对象时,会调用被序列化类的无参构造器去创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中。所以,实现Externalizable接口的类必须要提供一个public的无参的构造器。
import java.io.*;
/**
* 该类实现Externalizable接口,同时重载两个方法。对于实现了该接口的类而言,
* 在序列化和反序列化的过程中,会用readExternal和writeExternal两个方法
* <b>完全负责</b>读取和保存对象的操作,<b>包括对其父类的数据</b>。
*/
public class Car implements Externalizable{
public String brand;
/*
* 在实现了Externalizable接口的序列化类中,变量关键字transient
* 将会不起任何作用。关键字transient是和Serializable接口有关,在对实现
* 该接口的类进行默认序列化操作的时候,会自动忽略使用了transient关键字的变量。
*/
public transient Double price;
//必须要有一个无参的构造器,否则会报异常
public Car() {}
public Car(String brand, Double price) {
super();
this.brand = brand;
this.price = price;
}
@Override
public String toString() {
return "Car [brand=" + brand + ", price=" + price + "]";
}
@Override
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
//将类中的属性按顺序从流中读出,然后赋值给当前对象的属性
this.brand = in.readUTF();
this.price = in.readDouble();
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
//将当前对象的属性值按顺序写入到流中
out.writeUTF(this.brand);
out.writeDouble(this.price);
}
}
4. 对象流源码解析
对象的序列化过程通过ObjectOutputStream和ObjectInputputStream来实现的。这些类的源码量大且复杂,我们只取一瓢饮。
4.1 ObjectOutputStream的类定义
先来看ObjectOutputStream的类定义:
public class ObjectOutputStream extends OutputStream
implements ObjectOutput, ObjectStreamConstants {}
ObjectOutputStream继承了OutputStream类,实现了ObjectOutput接口和ObjectStreamConstants接口。
其中ObjectStreamConstants接口并没有定义方法,其内部定义了很多byte类型常量,表示序列化后的单个字节数据的含义。
4.2 ObjectOutputStream的构造方法
public ObjectOutputStream(OutputStream out) throws IOException {
//检查继承权限
verifySubclass();
//构造一个BlockDataOutputStream用于向out写入序列化数据
bout = new BlockDataOutputStream(out);
//构造一个大小为10,负载因子为3的HandleTable和ReplaceTable
handles = new HandleTable(10, (float) 3.00);
subs = new ReplaceTable(10, (float) 3.00);
//恒为false,除非子类调用protected构造方法
enableOverride = false;
writeStreamHeader();
//将缓存模式打开,写入数据时先写入缓冲区
bout.setBlockDataMode(true);
if (extendedDebugInfo) {
debugInfoStack = new DebugTraceInfoStack();
} else {
debugInfoStack = null;
}
}
BlockDataOutputStream是ObjectOutputStream的内部类,它将构造ObjectOutputStream传入的OutputStream实例包装起来,当外部类ObjectOutputStream需要向这个OutputStream写入序列化数据时,就由这个类来完成实际的写入操作。
第一步:构造方法首先调用verifySubclass方法分析现在构造的是不是ObjectOutputStream的子类,即:
private void verifySubclass() {
Class<?> cl = getClass();
//如果构造的不是ObjectOutputStream的子类则直接返回
if (cl == ObjectOutputStream.class)
return;
//否则获取安全管理器检查是否有继承ObjectOutputStream的权限
SecurityManager sm = System.getSecurityManager();
if (sm == null)
return;
//移除Caches中已经失去引用的Class对象
processQueue(Caches.subclassAuditsQueue, Caches.subclassAudits);
//将ObjectOutputStream的子类存入Caches
WeakClassKey key = new WeakClassKey(cl, Caches.subclassAuditsQueue);
Boolean result = Caches.subclassAudits.get(key);
if (result == null) {
result = Boolean.valueOf(auditSubclass(cl));
Caches.subclassAudits.putIfAbsent(key, result);
}
if (result.booleanValue())
return;
//如果没有权限则抛出SecurityException异常
sm.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
private static class Caches {
static final ConcurrentMap<WeakClassKey,Boolean> subclassAudits = new ConcurrentHashMap<>();
static final ReferenceQueue<Class<?>> subclassAuditsQueue = new ReferenceQueue<>();
}
该方法如果识别到构造的是ObjectOutputStream的子类,则会检查是否拥有SUBCLASS_IMPLEMENTATION_PERMISSION权限,否则抛出SecurityException异常。
第二步:在进行完ObjectOutputStream的类型检查后,构造方法会随之构建一个BlockDataOutputStream用于向传入的OutputStream写入对象信息,并构建长度为10,负载因子为3的HandleTable和ReplaceTable。
第三步:将魔数(0xACED)和版本标识符(0x0005)写入文件头,反序列化的时候用来检测是不是一个序列化对象。
protected void writeStreamHeader() throws IOException {
bout.writeShort(STREAM_MAGIC); //写入两个字节:0xAC和0xED
bout.writeShort(STREAM_VERSION); //写入两个字节:0x00和0x05
}
第四步:根据sun.io.serialization.extendedDebugInfo配置信息决定是否启用调式信息栈。
private static final boolean extendedDebugInfo =
java.security.AccessController.doPrivileged(
new sun.security.action.GetBooleanAction(
"sun.io.serialization.extendedDebugInfo")).booleanValue();
如果extendedDebugInfo为true,则构造方法会构造一个DebugTraceInfoStack,否则置为null。
4.3 writeObject方法
构造完ObjectOutputStream对象后,我们一般会随之调用writeObject(Object)方法将对象写入
public final void writeObject(Object obj) throws IOException {
//在ObjectOutputStream中这个变量恒为false,只有子类为true
if (enableOverride) {
//实现为空,供子类重写用
writeObjectOverride(obj);
return;
}
try {
writeObject0(obj, false);
} catch (IOException ex) {
if (depth == 0)
writeFatalException(ex);
throw ex;
}
}
writeObject方法首先会检查是否是ObjectOutputStream的子类,如果是则调用writeObjectOverride方法,这个方法默认实现为空,需要子类根据实际业务需求定制序列化方法。
4.4 writeObject0()方法
真正调用写入的是writeObject0方法。源码如下:
private void writeObject0(Object obj, boolean unshared) throws IOException {
//关闭缓冲模式,直接向目标OutputStream写入数据
boolean oldMode = bout.setBlockDataMode(false);
depth++;
try {
int h;
//处理以前写过的和不可替换的对象
//如果obj为null(只有当obj为null时才会返回null)
if ((obj = subs.lookup(obj)) == null) {
writeNull();
return;
} else if (!unshared && (h = handles.lookup(obj)) != -1) {
writeHandle(h);
return;
} else if (obj instanceof Class) {
writeClass((Class) obj, unshared);
return;
} else if (obj instanceof ObjectStreamClass) {
writeClassDesc((ObjectStreamClass) obj, unshared);
return;
}
Object orig = obj;
Class<?> cl = obj.getClass();
//序列化对象对应的Class对象的详细信息
ObjectStreamClass desc;
for (;;) {
Class<?> repCl;
//获取序列化对象对应的Class对象详细信息
desc = ObjectStreamClass.lookup(cl, true);
//直接break,因为最后(repCl=obj.getClass())==null恒等于true
if (!desc.hasWriteReplaceMethod() ||
(obj = desc.invokeWriteReplace(obj)) == null ||
(repCl = obj.getClass()) == cl)
break;
cl = repCl;
}
if (enableReplace) {
//replaceObject用来替换这个对象进行序列化,默认实现为空,一般用于子类重写实现序列化的定制
Object rep = replaceObject(obj);
//如果对象被替换了
if (rep != obj && rep != null) {
cl = rep.getClass();
//重新查找对应的ObjectStreamClass
desc = ObjectStreamClass.lookup(cl, true);
}
obj = rep;
}
//如果对象被替换了(非ObjectOutputStream子类不会发生)
if (obj != orig) {
subs.assign(orig, obj);
if (obj == null) {
writeNull();
return;
} else if (!unshared && (h = handles.lookup(obj)) != -1) {
writeHandle(h);
return;
} else if (obj instanceof Class) {
writeClass((Class) obj, unshared);
return;
} else if (obj instanceof ObjectStreamClass) {
writeClassDesc((ObjectStreamClass) obj, unshared);
return;
}
}
//序列化对象类型为String、数组、枚举时,调用定制的写入方法
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());
}
} finally {
//结束方法前将方法栈深减去1
depth--;
bout.setBlockDataMode(oldMode);
}
}
ObjectStreamClass存储了一个Class对象的信息,其实例变量包括:Class对象,Class名称,serialVersionUID,实现了Serializable接口还是 Externalizable接口,非transient修饰的变量,自定义的writeObject和readObject的Method对象。
4.4.1 ObjectStreamClass.lookup
ObjectStreamClass存储了一个Class对象的信息,其实例变量包括:Class对象,Class名称,serialVersionUID,实现了Serializable接口还是 Externalizable接口,非transient修饰的变量,自定义的writeObject和readObject的Method对象。
static ObjectStreamClass lookup(Class<?> cl, boolean all) {
//如果all为false且cl并没有实现Serializable接口则直接返回null
if (!(all || Serializable.class.isAssignableFrom(cl))) {
return null;
}
//清除失去Class引用的ObjectStreamClass缓存
//(缓存的用途是避免反复对同一个Class创建ObjectStreamClass对象)
processQueue(Caches.localDescsQueue, Caches.localDescs);
//创建一个临时的WeakClassKey用于从缓存中查找对应的ObjectStreamClass或EntryFuture
WeakClassKey key = new WeakClassKey(cl, Caches.localDescsQueue);
//获取保存有ObjectStreamClass或EntryFuture的引用
Reference<?> ref = Caches.localDescs.get(key);
Object entry = null;
//如果引用不为null则直接获取其中的对象给entry
if (ref != null) {
entry = ref.get();
}
EntryFuture future = null;
//如果引用的对象被GC
if (entry == null) {
//创建一个EntryFuture对象并将软引用newRef指向它
EntryFuture newEntry = new EntryFuture();
Reference<?> newRef = new SoftReference<>(newEntry);
do {
//从缓存中删除这个失去引用的键值对
if (ref != null)
Caches.localDescs.remove(key, ref);
//将被newRef引用的EntryFuture添加到缓存(这里使用putIfAbsent而不是put可能是为了防止有其它线程已经添加了)
ref = Caches.localDescs.putIfAbsent(key, newRef);
if (ref != null)
entry = ref.get();
//循环直到ref为null或entry不为null
} while (ref != null && entry == null);
//如果entry为null
if (entry == null)
future = newEntry;
}
//如果从缓存中拿到了ObjectStreamClass
if (entry instanceof ObjectStreamClass) {
return (ObjectStreamClass) entry;
}
//如果从缓存中得到了EntryFuture
if (entry instanceof EntryFuture) {
future = (EntryFuture) entry;
//如果创建这个EntryFuture的线程就是当前线程,即这个EntryFuture
//是在前面的代码ref = Caches.localDescs.putIfAbsent(key, newRef);语句中设置的
if (future.getOwner() == Thread.currentThread()) {
entry = null;
} else {
entry = future.get();
}
}
//如果entry为null那么就创建一个新的ObjectStreamClass对象并加入缓存
if (entry == null) {
try {
entry = new ObjectStreamClass(cl);
} catch (Throwable th) {
entry = th;
}
//设置这个ObjectStreamClass实例
if (future.set(entry)) {
Caches.localDescs.put(key, new SoftReference<Object>(entry));
} else {
entry = future.get();
}
}
//最后如果entry为ObjectOutputStream则直接返回,否则抛出异常
if (entry instanceof ObjectStreamClass) {
return (ObjectStreamClass) entry;
} else if (entry instanceof RuntimeException) {
throw (RuntimeException) entry;
} else if (entry instanceof Error) {
throw (Error) entry;
} else {
throw new InternalError("unexpected entry: " + entry);
}
}
在ObjectStreamClass类的内部类Caches中,存在一个类型为ConcurrentMap的静态成员变量localDescs:
private static class Caches {
/** cache mapping local classes -> descriptors */
static final ConcurrentMap<WeakClassKey,Reference<?>> localDescs =
new ConcurrentHashMap<>();
/** cache mapping field group/local desc pairs -> field reflectors */
static final ConcurrentMap<FieldReflectorKey,Reference<?>> reflectors =
new ConcurrentHashMap<>();
/** queue for WeakReferences to local classes */
private static final ReferenceQueue<Class<?>> localDescsQueue =
new ReferenceQueue<>();
/** queue for WeakReferences to field reflectors keys */
private static final ReferenceQueue<Class<?>> reflectorsQueue =
new ReferenceQueue<>();
}
ObjectStreamClass引入这个缓存主要是为了提高获取类信息的速度,如果反复对一个类的实例们进行序列化操作,那么只需要实例化一个ObjectStreamClass实例并导入这个缓存。WeakClassKey继承WeakReference,将一个弱引用指向这个Class对象,当对应的ClassLoader失去引用时,不至于导致垃圾回收器无法回收这个Class对象。引用队列localDescsQueue主要用于processQueue方法清除localDescs中无用的缓存。
private static class EntryFuture {
private static final Object unset = new Object();
private final Thread owner = Thread.currentThread();
private Object entry = unset;
//entry是ObjectStreamClass实例
synchronized boolean set(Object entry) {
if (this.entry != unset)
return false;
this.entry = entry;
//entry已被设置,唤醒正在调用get方法的线程
notifyAll();
return true;
}
synchronized Object get() {
boolean interrupted = false;
while (entry == unset) {
try {
//等待到entry被set为止
wait();
} catch (InterruptedException ex) {
interrupted = true;
}
}
//如果被强制打断则返回null
if (interrupted) {
AccessController.doPrivileged(
new PrivilegedAction<Void>() {
public Void run() {
Thread.currentThread().interrupt();
return null;
}
});
}
//如果是正常被set方法唤醒的则直接返回设置好的ObjectStreamClass
return entry;
}
//返回创建这个EntryFuture的线程
Thread getOwner() {
return owner;
}
}
4.4.2 writeOrdinaryObject
在获取到ObjectStreamClass对象后,会判断需要序列化的类是哪种类型。这里只看writeOrdinaryObject方法:
private void writeOrdinaryObject(Object obj, ObjectStreamClass desc,
boolean unshared) throws IOException {
if (extendedDebugInfo)
debugInfoStack.push(
(depth == 1 ? "root " : "") + "object (class \"" +
obj.getClass().getName() + "\", " + obj.toString() + ")");
try {
//检查ObjectStreamClass对象
desc.checkSerialize();
//写入字节0x73
bout.writeByte(TC_OBJECT);
//写入对应的Class对象的信息
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();
}
}
}
writeSerialData方法会将这个实例及其父类基本数据类型写入文件,如果检测到有引用类型,那么会继续调用writeObject0方法写入,直到将这个对象包含的所有信息全部序列化为止。
writeExternalData和writeSerialData(Object, ObjectStreamClass)这里有个上下文的操作,目的是保证序列化操作同一时间只能由一个线程调用。前者直接调用writeExternal,后者如果重写了writeObject则调用它,否则调用defaultWriteFields。
private void writeExternalData(Externalizable obj) throws IOException {
PutFieldImpl oldPut = curPut;
curPut = null;
if (extendedDebugInfo) {
debugInfoStack.push("writeExternal data");
}
SerialCallbackContext oldContext = curContext;//存储上下文
try {
curContext = null;
if (protocol == PROTOCOL_VERSION_1) {
obj.writeExternal(this);
} else {//默认协议是2,所以会使用块输出流
bout.setBlockDataMode(true);
obj.writeExternal(this);//这里取决于类的方法怎么实现
bout.setBlockDataMode(false);
bout.writeByte(TC_ENDBLOCKDATA);
}
} finally {
curContext = oldContext;//恢复上下文
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
curPut = oldPut;
}
private void writeSerialData(Object obj, ObjectStreamClass desc)
throws IOException
{
ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
for (int i = 0; i < slots.length; i++) {
ObjectStreamClass slotDesc = slots[i].desc;
if (slotDesc.hasWriteObjectMethod()) {//重写了writeObject方法
PutFieldImpl oldPut = curPut;
curPut = null;
SerialCallbackContext oldContext = curContext;
if (extendedDebugInfo) {
debugInfoStack.push(
"custom writeObject data (class \"" +
slotDesc.getName() + "\")");
}
try {
curContext = new SerialCallbackContext(obj, slotDesc);
bout.setBlockDataMode(true);
slotDesc.invokeWriteObject(obj, this);//调用writeObject方法
bout.setBlockDataMode(false);
bout.writeByte(TC_ENDBLOCKDATA);
} finally {
curContext.setUsed();
curContext = oldContext;
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
curPut = oldPut;
} else {
defaultWriteFields(obj, slotDesc);//如果没有重写writeObject则输出默认内容
}
}
}
private void defaultWriteFields(Object obj, ObjectStreamClass desc)
throws IOException
{
Class<?> cl = desc.forClass();
if (cl != null && obj != null && !cl.isInstance(obj)) {
throw new ClassCastException();
}
desc.checkDefaultSerialize();
int primDataSize = desc.getPrimDataSize();
if (primVals == null || primVals.length < primDataSize) {
primVals = new byte[primDataSize];
}
desc.getPrimFieldValues(obj, primVals);//将基本类型数据的字段值存入缓冲区
bout.write(primVals, 0, primDataSize, false);//输出缓冲区内容
ObjectStreamField[] fields = desc.getFields(false);
Object[] objVals = new Object[desc.getNumObjFields()];//获取非基本数据类型对象
int numPrimFields = fields.length - objVals.length;
desc.getObjFieldValues(obj, objVals);
for (int i = 0; i < objVals.length; i++) {
if (extendedDebugInfo) {
debugInfoStack.push(
"field (class \"" + desc.getName() + "\", name: \"" +
fields[numPrimFields + i].getName() + "\", type: \"" +
fields[numPrimFields + i].getType() + "\")");
}
try {
writeObject0(objVals[i],
fields[numPrimFields + i].isUnshared());//递归输出
} finally {
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
}
}
defaultWriteFields会先输出基本数据类型,对于非基本数据类型的部分会再递归调用writeObject0,所以这里也就会增加递归深度depth。