集合是一种应用广泛的数据集合容器。是一种可以自动增长的添加重复元素的顺序存储的线性结构类型。先看一下List继承接口Conllection关系结构关系图:
Conllection接口是list 的最顶层接口,list 本身也是一个接口。
起源
除了 Vector ,Stack 是1.0版本,Queue是1.5版本外,其余均是在1.2版本添加的。
使用list 时 通常会联想到另外的一种数据结构,那就是数组array。它们使用起来真的相似,而且区别仅是使用数组有一个容量限制,而集合容量却没有限制。实际上list的子类 Arraylist,Vector 的内部实现就是使用的数组。LinkedList的内部使用的双向链表。
先看ArrayList,这是使用最频繁的List子类了。前面说了它的内部实现是数组,但是在使用的时候却不用担心容量大小的问题,那么它内部肯定存在某种扩容机制了。看list的源码就可以得到答案。
首先看的是它的添加元素的方法:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
ensureCapacityInternal方法里面就是处理了数组容量的问题,它最终会到这里:
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);
}
传入的参数就是前面的 size+1 了 ,不管当前的容量是否足够,先扩大为原容量的1.5倍再说。简单粗暴哦~。后面再根据新容量与原容量来决定是否使用新的容量大小。最后复制这个数组。完成数组容量处理,添加元素,方法结束。还有就是list的默认容量大小是10。
/** * Default initial capacity. */ private static final int DEFAULT_CAPACITY = 10;
接下来看下list 的删除方法,list是一个接口,里边的方法都由它的子类取实现,这看ArrayList的remove 方法。arraylist 的remove实现很普通,但是有一点要注意的:
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
}
可以看到,当删除一个元素后,整个list的size 会减少1的。如果不注意这点的话,处理这样一个需求:需要删除某个集合内的某个元素,而恰好这个元素又是连续的重复元素,而你做法是循环遍历,判断值是否相等,然后删除。到最后就会发现没有完全删除掉。根本原因就是这个,你在List里删除了一个元素后,整个List会往前推1位。而遍历却是往后走的,所以造成了删除不完全的情况。
LinkedList
前面有提到过LinkedList的内部使用了双向链表,而且链表之前有提到过大概。看下LinsedList内部的链表节点类:
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
每个节点都拥有数据域,前驱指针域,后继指针域。接下看它的添加的方法。
/** * Links e as last element. */ void linkLast(E e) { final Node<E> l = last; final Node<E> newNode = new Node<>(l, e, null); last = newNode; if (l == null) first = newNode; else l.next = newNode; size++; modCount++; }
LinkedList 的添加方法最终是调用linkLast 方法,从名字就可以得知,这就是链表里的尾插法。直接在末尾处添加元素,然后长度加1。
了解链表结构的话看linkedlist的增加,删除方法逻辑都不会太难理解,这里不一一介绍。看linkedlist的查找元素方法 get。
直接点get 方法:
public E get(int index) {
checkElementIndex(index); //------> 检测元素是否越界。
return node(index).item;
}
接着进入到node(index)方法里边 ,
/**
* Returns the (non-null) Node at the specified element index.
*/
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
emmmm 这个方法也是比较好理解的。先判断了当前要获取的元素的位置在整条链表的什么位置,是靠近头部,还是靠近尾部。以此决定是从头部遍历查找还是尾部查找。最后就是遍历寻址,返回对应index的那个节点出去了。
vector
vector跟arraylist一样,内部使用数组结构实现,甚至用法上都没有区别。特点就是vector是线程安全的,而arraylist是线程不安全。进入到vector的源码会发现很多方法都使用 synchronized 关键字来修饰。比如,添加方法:
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
可以看到跟arraylist 的添加方法几乎是一样的只不过添加了synchronized修饰。还有vector的扩容手段跟arraylist也是不同的。vector有一个专门用于扩容的属性: capacityIncrement
/**
* The amount by which the capacity of the vector is automatically
* incremented when its size becomes greater than its capacity. If
* the capacity increment is less than or equal to zero, the capacity
* of the vector is doubled each time it needs to grow.
*
* @serial
*/
protected int capacityIncrement;
什么意思呢?哈哈英文不是很好的去翻译翻译喽。大概意思就是这个int属性表示当vector添加元素后的容量大于它自己当前的容量时自动增长的量。如果它小于或者等于0时,vector增长的量是原容量的2倍。由代码也可以看出来:
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
vector 的初始容量也是10。虽说 capacityIncrement 加入了扩容的计算。但实际上capacityIncrement的值在初始化为0后就再也没有被赋值过了。因此vector都是以2倍的大小扩容。
总结
AraayList
数组实现,线程不安全,增删慢,查找快。单线程下查找操作频繁推荐使用;
LinkedList
链表实现,线程不安全,增删快速,查找慢。大量插入,删除操作下推荐使用;
Vector
数组实现,线程不安全,增删慢,查找快。不涉及到多线程情况下,能用Vector的地方就能用ArrayList代替;多线程情况下查找操作频繁推荐使用vector。