Java进阶之源码阅读ArrayList、LinkedList与Vector
读源码还真的能提高对java的理解的。比如读源码的过程中,可以看见很多以前没碰到过的关键字的用法,以及数据结构的算法,最重要的是明白why,而不是what。
只有真正理解了内部实现原理、体系结构,才能更好地去使用。
一、 ArrayList
1.一些变量与关键字的作用:
1)serialVersionUID
arraylist implement 了 Serializable接口,并定义了serialVersionUID。
序列化的作用就是能将对象转化为字节码保存在在磁盘中持久化存储和转化为字节流进行网络传输。serialVersionUID的作用呢?顾名思义,就是判断序列化的版本是否一致的标志。也就是说,序列化时,会将serialVersionUID写入到序列化文件中,当反序列化时,会检查serialVersionUID是否与当前类的serialVersionUID一致,一致则可以反序列化,反之则否。如果A 、B两处的serialVersionUID的相同,但是B与A的字段不一样,当在A序列化,在B反序列化时,会按照B的对象内容,进行序列化成功。
也就是说serialVersionUID是控制序列化版本能否向后兼容的,如果我们在A中增加了一个字段变成B,为了使B依旧序列化成A,也就是能向后兼容,就不要改变B中的serialVersionUID,也就是将B与A的serialVersionUID设为一致的就行;反之如果想不要向后兼容,则改变serialVersionUID就行。
2) transient关键字
transient关键字的作用,简单地说,就是让某些被修饰的成员属性变量不被序列化。
什么时候需要将对象的域不被序列化呢?
一个类中可能存在某些敏感的信息,我们是不想在网络中传输的,这时候我们就需要借助 transient 关键字了,被 transient 关键字标识的 field,不会进行序列化
或者,类中的某些域可以由其他域推断出来。则为了节省空间,就可以不需要序列化。
例如:arratlsit中的 elementData 和 modCount 设置为了 transient 。
那么为什么要将 elementData 和 modCount 设为 transient 呢?
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access
elementData 设为 transient
a) elementData不总是满的,每次都序列化,会浪费时间和空间
b) 重写了writeObject 保证序列化的时候虽然不序列化全部, 但是有的元素都序列化
不是不序列化 而是不全部序列化
/**
* Save the state of the <tt>ArrayList</tt> instance to a stream (that
* is, serialize it).
*
* @serialData The length of the array backing the <tt>ArrayList</tt>
* instance is emitted (int), followed by all of its elements
* (each an <tt>Object</tt>) in the proper order.
*/
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();
}
}
transient Object[] elementData;
为什么要将modCount 设为 transient 呢?
modCount的作用,注意这个域在HashMap 中也有哦!(而且也是为 trainsient的)
在使用迭代器遍历的时候,用来检查列表中的元素是否发生结构性变化(列表元素数量发生改变)了,主要在多线程环境下需要使用,防止一个线程正在迭代遍历,另一个线程修改了这个列表的结构。好好认识下这个异常:ConcurrentModificationException。所以modCount的作用是迭代器在遍历时做线程安全检查的;注意ArrayList是非线程安全的!
回过头来看,modCount主要用于判断集合是否被修改,对于这种变量,一开始可以为任何值,0当然也是可以(new出来、反序列化出来、或者克隆clone出来的时候都是为0的),没必要持久化其值。
2. ArrayList中的组合类
在看设计模式时,说对象的复合优于继承,接口优于抽象类。有一个implements 了Iterator类Itr,ListLtr继承了Itr。
让我们看看List集合这块的类之间的关系:
3.ArrayList中的关键思想与实现:
1)扩容机制:因为ArrayList是动态增长的,动态增长的一般都需要有一个很好的扩容机制。那么ArrayList是怎么实现的呢?
看看容量是怎噩梦增长的:
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
newCapacity = oldCapacity + (oldCapacity >> 1) : 扩容的长度是在原长度基础上加二分之一
2)性能分析:
底层数组实现,使用默认构造方法初始化出来的容量是10;
实现RandomAccess接口,get性能很快;add和remove因为要复制数组,性能差。
二、 LinkedList
先看看定义:
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable
LinkedList是一个实现了List Deque等接口的双向链表 => 可以被当作堆栈、队列或双端队列进行操作。
1.包含的组合类:
LinkedList也是线程不安全的。