现在网上对于ArrayList和LinkedList的分析文章非常多,但是基本分析的都有一些错误。所以我想通过源码分析的角度才能正好的理解ArrayList和 LinkedList
ArrayList的基于数组,内部就是一个Object[]的数组。默认的capacity为10。
// 默认的数组容量为10 PRivate static final int DEFAULT_CAPACITY = 10; // Object[]数组 transient Object[] elementData; // 当前数组的大小 private int size; // 数组最大长度 private static final int MAX_ARRAY_SIZE = 2147483639;ArrayList的add操作,每次都需要检查是否扩容,capacity越小,扩容越频繁,扩容非常耗时。
public boolean add(E arg0) { // 检查是否需要扩容 this.ensureCapacityInternal(this.size + 1); this.elementData[this.size++] = arg0; return true; }ArrayList扩容机制,当添加一个元素,如果添加元素之后的长度大于原来数组最大长度,就需要扩容,每次扩容的长度为当前最大的一半
private void grow(int arg0) { // 获取数组的长度 int arg1 = this.elementData.length; // 数组长度右移1位(相当于除2) int arg2 = arg1 + (arg1 >> 1); if (arg2 - arg0 < 0) { arg2 = arg0; } // 默认最大长度为2147483639 if (arg2 - 2147483639 > 0) { arg2 = hugeCapacity(arg0); } // 通过Arrays.copyOf方法进行扩容 this.elementData = Arrays.copyOf(this.elementData, arg2); }LinkedList是一个双链表,有一个firstNode和一个LastNode。
// 链表长度 transient int size; // 头节点 transient LinkedList.Node<E> first; // 尾节点 transient LinkedList.Node<E> last;LinkedList的add操作:添加一个新的元素时,将创建一个新的Node节点,并设置Node的前节点,设置Node的后节点为null。
void linkLast(E arg0) { LinkedList.Node arg1 = this.last; // 设置arg2的前节点为arg1,后节点为null LinkedList.Node arg2 = new LinkedList.Node(arg1, arg0, (LinkedList.Node) null); // 将arg2节点作为最后一个节点 this.last = arg2; if (arg1 == null) { this.first = arg2; } else { // 设置arg1的后节点为arg2 arg1.next = arg2; } ++this.size; ++this.modCount; }Node类
private static class Node<E> { E item; LinkedList.Node<E> next; LinkedList.Node<E> prev; Node(LinkedList.Node<E> arg0, E arg1, LinkedList.Node<E> arg2) { this.item = arg1; this.next = arg2; this.prev = arg0; } }ArrayList的get操作:检查数组是否越界,然后根据下标获取结果
public E get(int arg0) { // 检查是arg0是否超过的数组的长度,超过长度抛出IndexOutOfBoundsException this.rangeCheck(arg0); // 根据下标获取结果 return this.elementData(arg0); }LinkedList的get操作 :根据传入的值进行判断,如果传入的值在0~size/2,那么从头节点开始遍历,如果传入的值在size/2~size范围,那么从尾节点开始遍历。
LinkedList.Node<E> node(int arg0) { LinkedList.Node arg1; int arg2; // arg0是否小于链表的大小的一半 if (arg0 < this.size >> 1) { arg1 = this.first; // 从头节点开始遍历 for (arg2 = 0; arg2 < arg0; ++arg2) { arg1 = arg1.next; } return arg1; } else { arg1 = this.last; // 从尾节点开始遍历 for (arg2 = this.size - 1; arg2 > arg0; --arg2) { arg1 = arg1.prev; } return arg1; } }ArrayList的remove操作:判断传入的值是否超过数组最大长度,然后获取需要移除的元素的值,再判断被移除的元素是否是最后一位,如果不是,则将传入值之后的所有元素左移一位,最后将最后一位元素设为null。
public E remove(int arg0) { // 检查数组是否越界 this.rangeCheck(arg0); ++this.modCount; // 获取移除的元素值 Object arg1 = this.elementData(arg0); // 判断是否是最后一个元素,arg2等于0,代表最后一个元素 int arg2 = this.size - arg0 - 1; if (arg2 > 0) { // 将所有元素左移一位 System.arraycopy(this.elementData, arg0 + 1, this.elementData, arg0, arg2); } // 设置最后一位值为null,并且size-1 this.elementData[--this.size] = null; return arg1; }LinkedList的remove操作:先判断是否越界,然后通过get方法获取被移除的节点,判断是否是first节点和last节点,并设置移除节点所有属性为null和将被移除的节点前后节点连接。
// 在操作unlink之前需要通过get方法获取被移除的节点E unlink(LinkedList.Node<E> arg0) { Object arg1 = arg0.item; // 获取移除值的后节点 LinkedList.Node arg2 = arg0.next; // 获取移除值的前节点 LinkedList.Node arg3 = arg0.prev; // 判断移除的是否是第一个节点,如果是就讲移除节点的后节点设置为第一个节点 if (arg3 == null) { this.first = arg2; } else { arg3.next = arg2; arg0.prev = null; } // 判断移除的是否是最后一个节点 if (arg2 == null) { this.last = arg3; } else { arg2.prev = arg3; arg0.next = null; } arg0.item = null; --this.size; ++this.modCount; return arg1; }add:当数据小于5万以下,通过add操作,LinkedList比ArrayList速度明显快,但当数据逐渐再增大,ArrayList的数组明显比LinkedList快很多很多。
原因分析:ArrayList的默认capacity的值为10,当插入大量数据时(大约5万条),ArrayList需要多次扩容,所以影响ArrayList的速度。但是当数据量再逐渐增大,ArrayList的扩容大小按指数增张,后面扩容的次数会越少,然而每次扩容的容量却越大,所以导致最终ArrayList的总体速度反而更快。单纯从add操作上看,ArrayList的速度比LinkedList的速度快,但是因为ArrayList前期由于需要扩容的次数太多,从而拖慢了ArrayList的速度。
解决方案:使用ArrayList数据量很大时,最好指定ArrayList的数组大小。
get:ArrayList的get操作明显快于LinkedList的get操作。
原因分析:ArrayList通过下标直接获取值,而LinkedList需要通过移动指针(很费时)来获取对应值,每取一个数都要从头或者从尾部开始移动指针来获取对应的值。
remove:在数据量小的情况下,LinkedList的remove速度明显比ArrayList快,但是一旦数据量非常的大,并且删除的数据都是随机分布时,LinkedList的速度会非常的慢。
原因分析:ArrayList的remove删除元素后,需要左移一位(比较耗时),但是LinkedList的remove操作,先通过get方法获取指定的值,因为get方法需要通过移动指针来获取指定值,一旦数据量非常大时,get操作非常的耗时。所以导致一旦数据量很大,并且随机删除元素时,LinkedList的速度反而很慢。数据量小的情况下,LinkedList的remove的速度要快。
新闻热点
疑难解答