最近在温习Java集合部分,花了三天时间读完了ArrayList与LinkedList以及Vector部分的源码。以前都是停留在简单使用ArrayList的API,读完源码看完很多文章后总算是对原理方面有了较清楚的认知。这部分文章整理基本都是这么一个套路:简单归纳,分析源码增删改查,总结成文。java
Java容器源码(精品):面试
https://blog.csdn.net/panweiwei1994/article/details/76555359#commentBox 数组
http://www.javashuo.com/article/p-uqkohdtn-dg.html安全
ArrayList动态扩容分析:数据结构
https://blog.csdn.net/zymx14/article/details/78324464dom
接下来整理了下关于List集合校招常见的面试题目:post
相同点:性能
都实现了List接口,容许元素重复和为null值。测试
底层都是数组,咱们能够按位置索引出某个元素spa
不一样点:
ArrayList是非同步的,在线程上不安全而Vector集合给它的每一个API都套上了synchronized,因此Vector线程上是安全的。
在添加元素实现扩容的时候,ArrayList集合存储空间是增加为原来的1.5倍。而Vecto集合是存储空间增长为原来的2倍
PS:其实不考虑线程问题,他两没啥区别。
ArrayList的构造方法有三种。当数据量比较大,这里又已经明确是一万条了,咱们应该在初始化的时候就给它设置好容量。
否则使用无参构造器初始容量只有10,后面要扩容,扩容又比较伤性能,由于涉及到数组的复制,将原来的数组复制到新的存储区域中去。
PS:ArrayList动态扩容是一个重点必定要理解好,附上传送门:https://blog.csdn.net/zymx14/article/details/78324464
作过测试,ArrayList要是一直使用add方法在尾部插入的话,当数据量很是大的时候,其效率是要比LinkedList快的。删除也是。
可是要是在头部插入就要比LinkedList效率低很多。
可见测试分析报告:http://www.javashuo.com/article/p-fjbcdnpr-cy.html
Arraylist遍历性能要好,不管是所有遍历仍是遍历某个元素都是这样。它底层使用数组,适合使用for循环按下标寻址。而LinkedList底层使用双向链表,虽然说它在遍历的时候会采起折半查找的策略来提高速度但仍是比不上ArrayList的速度。
若是是使用无参构造的话,初始容量是10,当超过了10个容量时,就须要扩容,每次扩容为以前的1.5倍。调用的是Arrays.copy(elementData,newCapacity)。因此扩容涉及到数组的复制和移动,咱们应该避免扩容。在初始化的时候预估容量大小。
ArrayList底层使用数组,LinkedList底层使用双向链表。结合数据结构的特色可知,数组在空间上占用一片连续的内存空间,查询快。链表在增删操做只须要修改链表指针结点就可,增删快。
因此在查询使用较多时,选择ArrayList集合,增删使用较多时,选择LinkedList集合。
把ArrayList看成参数传递到某个方法中去,涉及到一个浅拷贝深拷贝的问题。若是直接赋值给成员变量时,当成员变量发生改变时,其对应的传递过来的ArrayList也会改变
通常不直接用“=”赋值,这是将引用指向了同一片地址,一个修改了里面的内容另外一个也会跟着变更。
通常采用以下方式:
ArrayList A = new ArrayList();
//假设此时A集合已经有了数据。构造器法 ArrayList B = new ArrayList(A);
//clone法
ArrayList A = new ArrayList(); ArrayList B = (ArrayList) A.clone();
ArrayList A = new ArrayList(); //初始化时最好先预估容量大小 ArrayList B = new ArrayList(); B.addAll(A);
还能够调用Collections.copy()方法,参考博客:https://blog.csdn.net/tiantiandjava/article/details/51072173
查看源代码能够发现,当经过索引去增删元素的时候效率是比较低的,由于要频繁的进行数组的复制和移动,若是常常增删的话咱们能够去考虑其余的集合。
经过索引增长过程:1.检查索引是否越界?2.检查容量是否足够?3调用System.arrayCopy(......)操做,效率较低
经过索引删除元素:1.检查索引是否越界?2.调用System.arrayCopy(....)操做
源代码截图以下:
以上是常见的面试题:接下来总结一下本身看源码所学到的
ArrayList:
1.ArrayList继承了AbstractList类,实现了List接口,RadomAccess,Clonable,Serialize接口。结合List接口特色可知它是有序的,能够存储重复值且能够为null。实现了RadomAccess接口在加上它底层采用数组实现,因此遍历速度快且遍历方式推荐使用for循环遍历下标而不用for-each或者iterator迭代器遍历。
2.使用默认无参构造时,初始容量分配为10.当进行增操做时,容量不够会进行扩容操做。
理清ArrayList增删改查操做:
add(E e):数组末尾进行增。
1.判断容量是否足够
2.size++
因而可知在末尾进行增操做时,是偶尔须要进行一次扩容操做,扩容时候会调用Arrays.copyOf(elementData, newCapacity);也与上文所说的在末尾插入效率并不比LinkedList低
public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }
public void add(int index, E element):按索引增。可知按索引增每次都要进行数组复制,因此效率低
1.判断索引是否越界 2.判断容量是否足够 3.size++
public void add(int index, E element) { rangeCheckForAdd(index); ensureCapacityInternal(size + 1); // Increments modCount!! System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; }
public boolean addAll(Collection<? extends E> c):增长一个集合。
1.先将所要增长的集合转为数组 2.判断容量 3:size++
public boolean addAll(Collection<? extends E> c) { Object[] a = c.toArray(); int numNew = a.length; ensureCapacityInternal(size + numNew); // Increments modCount System.arraycopy(a, 0, elementData, size, numNew); size += numNew; return numNew != 0; }
public E remove(int index):按索引值删除。可知每次都要进行数组复制,效率同样不高
public E remove(int index) { rangeCheck(index); modCount++; E oldValue = elementData(index); int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // Let gc do its work return oldValue; }
public boolean remove(Object o):按值删除 调用了fastRemove(index)方法,其内部也是要进行数组的复制,效率依然不高。
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; }
LinkedList:
1.继承了AbstractSequentialList类,但AbstractSequentialList 只支持按次序访问,而不像 AbstractList 那样支持随机访问。这是LinkedList随机访问效率低的缘由之一。
2.实现了List接口,说明有序,能够存储重复值,容许元素为null。实现了Deque接口,说明能够像操做栈和队列同样操做它。另外它仍是能够被克隆和序列化的。
3.LinkedList底层是双向链表,链表在空间上是离散的,尽管Linked遍历时采用了折半查找,但效率依然较低。它的优点在于增删操做,尤为是在头结点。