在了解集合的时候,都会学到不同集合之间的区别,比如ArrayList和LinkedList,其中ArrayList是类似于数组结构的,查询比较快速。而LinkedList则是链表结构,在插入和删除的时候效率较高。
通过研究源码,可以更深入的了解其内部实现,真的是ArrayList所有查询都快么? 真的是 LinkedList所有的插入和删除都更效率么?
废话不多说,上代码!
一、先摘抄一些ArrayList的代码吧,这里只分析一些关键的方法:
1、ArrayList的内部结构是怎么样的?
1public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { private static final Object[] EMPTY_ELEMENTDATA = {};
//这就是ArrayList集合内部结构,是一个object数组 private transient Object[] elementData;
//集合里面实际有值的长度 private int size; //构造函数,入参为初始化list长度 public ArrayList(int initialCapacity) { super(); if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); this.elementData = new Object[initialCapacity]; }
//默认构造函数,初始化数组大小为0 public ArrayList() { super(); this.elementData = EMPTY_ELEMENTDATA; }
//入参为集合将集合类型数据为数组,list长度为数组长度 public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); size = elementData.length; // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } }
2、对ArrayList中元素进行操作的实现?
get(index):其实就是根据index,直接获取对应数组下角标的值
\\根据下角标获取元素 public E get(int index) { \\检查下角标范围 rangeCheck(index); \\根据下角标返回数组元素 return elementData(index); } \\下角标范围必须小于数组有值的实际长度 private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); }
\\获取下角标元素
E elementData(int index) {
return (E) elementData[index];
}
set(int index, E element):这里需要稍微注意一下的是,set()方法返回的结果是对应index值的原始值。
public E set(int index, E element) { \\检查下角标 rangeCheck(index); \\原index 对应的值 E oldValue = elementData(index); \\设置新值 elementData[index] = element; \\返回原值 return oldValue; }
add(e):当添加一个元素时候,默认添加到list最后,并且对容器大小是否够容纳进行check,如果容器大小不够,则将原来容器
增大到1.5倍,在这种情况还不够的情况,则增加到最小要求容器长度。
\\添加一个元素 public boolean add(E e) { \\确保当前elementData容量是否足够 ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } \\确保当前elementData容量是否足够,入参:最小需要长度 private void ensureCapacityInternal(int minCapacity) { if (elementData == EMPTY_ELEMENTDATA) { \\如果elementData为空,则选择默认长度DEFAULT_CAPACITY或者最小长度 minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } // ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { //记录集合修改次数 modCount++; //如果现有容器长度不够用则添加容器长度 if (minCapacity - elementData.length > 0) grow(minCapacity); } private void grow(int minCapacity) { \\原始容器长度 int oldCapacity = elementData.length; \\新容器长度为原来长度的1.5倍 int newCapacity = oldCapacity + (oldCapacity >> 1); \\1.5倍的容器长度还比最小要求长度小 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; \\判断新容器长度超过最大限制 if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); //将原来数组的值copy新数组中去, ArrayList的引用指向新数组 //这儿会新创建数组,如果数据量很大,重复的创建的数组,那么还是会影 响效率, elementData = Arrays.copyOf(elementData, newCapacity); }
在最后需要通过Arrays.copyOf(elementData, newCapacity),复制整个数组的方式来实现添加数据,由此看来add(e)在需要扩容的时候添加元素,
效率是比较低的。
add(int index, E element):在指定位置后插入元素
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++; }
可以看到,在指定位置插入元素,首先需要进行容器长度check,如果不足,会进行一个数组copy,接下来还
将当前位于该位置的元素以及所有后续元素右移一个位置。效率更加低了!
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; // clear to let GC do its work return oldValue; }
首先是检查范围,修改modCount,保留将要被移除的元素,将移除位置之后的元素向前挪动一个位置,将list末尾元素置空(null),返回被移除的元素,
移除一个元素,除了最后一位,别的都需要将之后的元素前移一位,可见效率很低!
contains(o):直接通过遍历数组,来判断,说明遍历查找效率较高
//是否包含某元素,遍历整个list public boolean contains(Object o) { return indexOf(o) >= 0; } public int indexOf(Object o) { //直接通过遍历数组,来判断是否存在元素,效率较高 if (o == 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; }
------------------------------------
上面的源码能看出来,对于ArrayList是如何动态的控制长度,在插入元素、新增元素、删除元素的时候,效率是比较低的。而遍历查找元素,效率较高!
LinkedList,见下一篇吧,太困了!
23:57:43
2016/03/23 夜 杭州