Table of Contents
问题:ArrayList和LinkedList有什么区别,它们是怎么扩容的?
问题:Array和ArrayList有何区别?什么时候更适合用Array?
本篇分析基于jdk1.8。
数组与链表的基本知识在这里不作展开,属于数据结构与算法的重点内容。
ArrayList
ArrayList是一个容器,容器是用来存储对象的,ArrayList当中存储对象的变量是elementData,elementData的定义如下:
/**
* 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
非常简单,就是一个对象数组而已。注意,它使用了transient关键字来修饰,这意味着ArrayList不会被序列化。
直接上几个ArrayList里的方法。
Add 方法
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
忽略返回值做了两件事情。
- 确保当前数组的容量
- 将需要添加的元素,也就是这里的e加到数组的末尾。size当前数组的大小,size++自然就是当前数组的后一位。
其他值得注意的是,此方法并没有做任何同步处理。并且,事实上整个Arraylist里的所有方法都没有做任何同步处理。因此这些涉及数据操作的方法都不是线程安全的。
Get方法
/**
* Returns the element at the specified position in this list.
*
* @param index index of the element to return
* @return the element at the specified position in this list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
做了两件事情
- 检测传入的参数,即数组的下标,是否越界
- 若通过检测,则返回对应位置的元素即可,因为数组是有序排列的。
remove方法
/**
* Removes the first occurrence of the specified element from this list,
* if it is present. If the list does not contain the element, it is
* unchanged. More formally, removes the element with the lowest index
* <tt>i</tt> such that
* <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>
* (if such an element exists). Returns <tt>true</tt> if this list
* contained the specified element (or equivalently, if this list
* changed as a result of the call).
*
* @param o element to be removed from this list, if present
* @return <tt>true</tt> if this list contained the specified element
*/
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
这个实现很有意思的是它判断了一下要删除的对象是否是null,为什么要做这个判断呢,因为他们判断对象是否相等的方法不一样。null直接用两个等号判断,而非null对象,则是用equals方法判断相等。
遍历数组,找到相等对象,确定下标,然后删除当前数组中该下标的元素。删除方法为fastRemove,实现如下
/*
* Private remove method that skips bounds checking and does not
* return the value removed.
*/
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
由于是删除,所以不需要做越界检查。结构变化数modCount加一,计算需要移动的数据元素,为当前数组大小,减去删除元素位置,再减去1,为什么要减去1因为数组下标从0开始,很好理解,做个草图如下:
当前数组元素一共9个,然后假设我要删除的是第6个元素。即size=9,index=5(从0开始)。要删除的元素是6,需要移动的元素即为7,4,8一共三个。
size-index-1 = 9 - 5 -1 = 3.
计算正确,一个测试用例诞生~。
然后将五个参数传入了System类下的arraycopy方法。这个方法的作用就是根据需要移动的元素,重新构造一个没有要删除元素的新数组,也即实现了删除的功能。这里额外插一句,从clean code的角度来讲,这个方法的参数已经太多了,clean code的建议是三个最多,只能说凡事无绝对吧。或者也可以说jdk的代码也并非从各个角度来看都完美无缺。
方法arraycopy在System类看不到实现,它是一个native方法。java众多的底层方法,实现几乎都是native方法,也就是用别的语言实现的。作为一个现代的程序员,或者说作为一个流水线上的程序员,大多数人真的一辈子也许也不需要知道这个方法到底如何实现的。
Vector
那么,如果要使用线程安全的容器,应该使用哪一个呢。答案就是Vector。
Vector的add方法如下:
/**
* Appends the specified element to the end of this Vector.
*
* @param e element to be appended to this Vector
* @return {@code true} (as specified by {@link Collection#add})
* @since 1.2
*/
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
用了synchronized直接修饰这个方法。除此之外实现逻辑和ArrayList几乎一样。modCount从注释来看这个变量代表的是这个容器的结构变化次数,所谓结构变化很好理解,就是数组中元素增加了一个,减少了一个,就是结构的变化。这里把modCount放在这里做计算,是一个和ArrayList不一样的地方,ArrayList是在check的时候来做。但这仅仅是代码风格的不统一。</