文章目录

之前在阅读Java集合源码的过程中,发现在多种集合的背后,其基本上都是一个数组,而该数组也都会用transient关键字进行标识。例如,在HashMap中,transient Entry<K,V>[] table;,又例如在ArrayList中,private transient Object[] elementData;

自己之前对transient关键字有过了解,transient用来表示一个域不是该对象串行化的一部分。当一个对象被串行化的时候,transient型变量的值不包括在串行化的表示中,然而非transient型的变量是被包括进去的。所以一直以为存入集合中的数据在进行序列号和反序列化之后数据就不存在了,虽然有所怀疑,但没有深究。

今天又突然想到了这个问题,就自己做了个小测试,结果发现并不是自己之前理解的那样:存入集合中的数据在序列号和反序列化之后并不会丢失。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Main {

public static void main(String[] args) throws FileNotFoundException,
IOException, ClassNotFoundException {

List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
System.out.println(list.size());

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(
new File("d:/d.dat")));
oos.writeObject(list);
oos.flush();
oos.close();

ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
new File("d:/d.dat")));
List listtmp = (List) ois.readObject();
System.out.println(listtmp.size());
ois.close();
}
}

输出结果为

1
2
4
4

再去看源码,才发现在集合中,实现了writeObject(ObjectOutputStream)和readObject(ObjectInputStream)这两个方法。

在继续学习下去之前,我们先得补充一点关于序列化的知识点。

序列化可以通过两种方式,其一是实现Serializable接口,调用java.io.ObjectOutputStream的defaultWriteObject方法,将对象序列化。此时transient修饰的字段,不会被序列化。另一种是实现Serializable接口,但同时实现writeObject方法,在序列化时,会调用该类的writeObject方法。此时transient修饰的字段,是否会被序列化,取决于writeObject方法的实现。

了解了这些,我学习到在HashMap中,自己实现了writeObject方法,在该方法中将数据进行了序列化。这虽然解释通了我之前的测试,但让我联想到另一个问题,为什么要这么做?这不是多此一举么?

通过查阅stackoverflow还有segmentfault中的一些回答,得到了想要的答案。

一、HashMap 中的存储数据的数组数据成员中,数组还有很多的空间没有被使用,没有被使用到的空间被序列化没有意义。所以需要手动使用 writeObject() 方法,只序列化实际存储元素的数组。

二、由于不同的虚拟机对于相同 hashCode 产生的 Code 值可能是不一样的,如果你使用默认的序列化,那么反序列化后,元素的位置和之前的是保持一致的,可是由于 hashCode 的值不一样了,那么定位函数 indexOf()返回的元素下标就会不同,这样不是我们所想要的结果

打个比方说, 向HashMap存一个entry, key为 字符串”STRING”, 在第一个java程序里, “STRING”的hashcode()为1, 存入第1号bucket; 在第二个java程序里, “STRING”的hashcode()有可能就是2, 存入第2号bucket。如果用默认的串行化(Entry[] table不用transient), 那么这个HashMap从第一个java程序里通过串行化导入第二个java程序后, 其内存分布是一样的. 这就不对了. HashMap现在的readObject和writeObject是把内容 输出/输入, 把HashMap重新生成出来。

附上在Effective Java 2nd的一段话:For example, consider the case of a hash table. The physical representation is a sequence of hash buckets containing key-value entries. The bucket that an entry resides in is a function of the hash code of its key, which is not, in general, guaranteed to be the same from JVM implementation to JVM implementation. In fact, it isn’t even guaranteed to be the same from run to run. Therefore, accepting the default serialized form for a hash table would constitute a serious bug. Serializing and deserializing the hash table could yield an object whose invariants were seriously corrupt.

文章目录