目录
1 .List接口实现和继承结构图
2 .ArrayList , Vector ,LinkendList的区别
3 .ArrayList和Vector的区别
4 .ArrayList
4.1 初始化
4.2 add
4.3 set
4.4 indexof
5 .LinkedList
5.1 LinkedList初始化
5.2 LinkedList总结
6 .Vector
6.1 Vector总结
想起前几天面试的时候,被问到List的时候,我居然不会。天天用,却没有了解原理,这就很伤心。今天就花了一些时间,把几个常用的List源码全部看了一遍。一些常用方法做了总结,对于这篇文章,我认为几个总结是非常重要的,可以应对常用的实践问题。
List接口实现和继承结构图
java集合分为两大类,一个是Collection下,一个是map下的结构。由下图可知,List是继承Collection集合而来。(图由idea自动生成的,看的不清晰,请见谅)
我们就只详细介绍常用的LinkedList,Vector,ArrayList。
ArrayList , Vector ,LinkendList的区别
ArrayList:底层数据结构为数组,查询速度快,增删改速度慢,线程不安全的
Vector:底层数据结构为数组,线程安全的,底层方法都加了Synchronize关键字
LinkedList:底层数据结构为双向链表,增删速度快,查询稍慢
ArrayList和Vector的区别:
ArrayList:初始容量为10,超过10的时候会new一个50%的空间,将原来的放入这150%的空间内。
Vector:初始容量也是为10,超过10的时候会new一个100%的空间。
如果要实现Arraylist线程同步,可以通过下面方式:
1.如果多个线程同时访问一个 ArrayList 实例,而其中至少一个线程从结构上修改了列表,那么它必须 保持外部同步。(结构上的修改是指任何添加或删除一个或多个元素的操作,或者显式调整底层数组的大小;仅仅设置元素的值不是结构上的修改。)这一般通过对自然封装该列表的对象进行同步操作来完成。如果不存在这样的对象,则应该使用Collections.synchronizedList 方法将该列表“包装”起来。这最好在创建时完成,以防止意外对列表进行不同步的访问:
List list = Collections.synchronizedList(new ArrayList(...));
2.如果集合中的元素数量大于当前集合数组的长度时,Vector的增长率是目前数组长度的100%,而ArryaList增长率为目前数组长度的50%。所以,如果集合中使用数据量比较大的数据,用Vector有一定优势。
List接口对Collection进行了简单的扩充,它的具体实现类常用的有ArrayList和LinkedList。你可以将任何东西放到一个List容器中,并在需要时从中取出。ArrayList从其命名中可以看出它是一种类似数组的形式进行存储,因此它的随机访问速度极快,而LinkedList的内部实现是链表,它适合于在链表中间需要频繁进行插入和删除操作。在具体应用时可以根据需要自由选择。前面说的Iterator只能对容器进行向前遍历,而ListIterator则继承了Iterator的思想,并提供了对List进行双向遍历的方法。
ArrayList
ArrayList就是动态数组,用MSDN中的说法,就是Array的复杂版本,它提供了动态的增加和减少元素,实现了Collection和List接口,可以灵活的设置数组的大小。要注意的是ArrayList并不是线程安全的,因此一般建议在单线程中使用ArrayList。
由结构图可知ArrayList继承AbstractList 并且实现了List和RandomAccess,Cloneable, Serializable接口。
ArrayList也采用了快速失败的机制,通过记录modCount参数来实现。在面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。
初始化
//初始化容量为10
private static final int DEFAULT_CAPACITY = 10;
//初始化底层数据结构,为数组
transient Object[] elementData;
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) { //如果new一个实例,参数大于0,就new一个默认容量
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {//等于0,就new了一个空数组
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
//没有指定,就默认new一个空数组
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* Constructs a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
*
* @param c the collection whose elements are to be placed into this list
* @throws NullPointerException if the specified collection is null
*/
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// 当c.toArray返回的不是object类型的数组时,进行下面转化。
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
//用一个空数组替换
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
并且用户可以往ArrayList中传入一个容器只要这个容器是Collection类型的。调用ArrayList(Collection<? extends E> c)接口的时候会将容器数组化处理并将这个数组值赋给Object数组。
add
public boolean add(E e) {
//检查满容了没有,满了就扩
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
public void add(int index, E element) {
rangeCheckForAdd(index); // 判断index合不合法
ensureCapacityInternal(size + 1); // Increments modCount!!
//把index后面的所有元素后移一位
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
//指定从index位置开始,将集合的所有元素插入
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
set
//覆盖之前的元素,返回之前的元素
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
indexof
//返回第一个出现该元素的索引
public int indexOf(Object o) {
if (o == null) { //因为ArrayList允许值为null
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
LinkedList
- LinkedList继承于AbstractSequentialList,并且实现了Dequeue接口。
LinkedList包含两个重要的成员:header 和 size
header
header是双向链表的表头,它是双向链表节点所对应的类Entry的实例。 Entry中包含成员变量: previous, next, element。其中,previous是该节 点的上一个节点,next是该节点的下一个节点,element是该节点所包含的值。
size
是双向链表中节点的个数
双向链表,那么它的顺序访问会非常高效,而随机访问效率比较低
既然LinkedList是通过双向链表的,但是它也实现了List接口{也就是说,它实现了get(int location)、remove(int location)等“根据索引值来获取、删除节点的函数”}。LinkedList是如何实现List的这些接口的,如何将“双向链表和索引值联系起来的”?
实际原理非常简单,它就是通过一个计数索引值来实现的。例如,当我们调用get(int
location)时,首先会比较“location”和“双向链表长度的1/2”;若前者大,则从链表头
开始往后查找,直到location位置;否则,从链表末尾开始先前查找,直到location位置。
LinkedList初始化
transient int size = 0;
transient Node<E> first;
transient Node<E> last;
LinkedList初始化其实很简单,就是将节点个数设为0,定义一个指向头的frist的指针,一个指向尾的last指针。所以LinkedList底层其实是一个具有双端性质的链表
LinkedList的插入,删除就是一个具有双端性质链表的操作,可以参考我详细解释双端性质的双向链表的博文:双向链表代码实现和详解—java实现
LinkedList的查找,对输入的索引参数进行判断,当索引小于一半容量时,从头开始遍历。否则从尾部开始遍历。
LinkedList总结
- 从LinkedList的实现方式中可以发现,它不存在LinkedList容量不足的问题。
- LinkedList的克隆函数,即是将全部元素克隆到一个新的LinkedList对象中。
- LinkedList实现java.io.Serializable。当写入到输出流时,先写入“容量”,再依次写入“每一个节点保护的值”;当读出输入流时,先读取“容量”,再依次读取“每一个元素”。
- 由于LinkedList实现了Deque,而Deque接口定义了在双端队列两端访问元素的方法。提供插入、移除和检查元素的方法。每种方法都存在两种形式:一种形式在操作失败时抛出异常,另一种形式返回一个特殊值(null 或 false,具体取决于操作)。
LinkedList可以作为先进先出的队列,也可以作为后进先出的栈,对方法适当的调用。
切记,不可以随机访问LinkedList的元素,那样的花销太大
Vector
Vector初始化,各种方法跟ArrayList实现的方法思想几乎一样,只是Vector在各方法中加了同步锁synchronized。所以Vector是线程安全的。
跟ArrayList还有点不同的地方就是,Vector在创建的时候,如果没有使用泛型创建,那Vector的每个元素可以是不同的类型。
还有ArrayList还有点不同的地方就是,在扩容的时候,扩容的大小是new一个100%的空间
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);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
Vector总结
1 . 创建了一个向量类的对象后,可以往其中随意地插入不同的类的对象,既不需顾及类型也不需预先选定向量的容量,并可方便地进行查找。对于预先不知或不愿预先定义数组大小,并需频繁进行查找、插入和删除工作的情况,可以考虑使用向量类。
2 .Vector的构造器
public vector()
public vector(intinitialcapacity,int capacityIncrement)
public vector(intinitialcapacity)
使用第一种方法,系统会自动对向量对象进行管理。若使用后两种方法,则系统将根据参数initialcapacity设定向量对象的容量(即向量对象可存储数据的大小),当真正存放的数据个数超过容量时,系统会扩充向量对象的储存容量。
3,参数capacityIncrement给定了每次扩充的扩充值。当capacityIncrement为0时,则每次扩充一倍。利用这个功能可以优化存储。在Vector类中提供了各种方法方便用户使用。