-
底层实现细节
ArrayList底层使用动态数组,Linked底层使用的是双向链表。
ArrayList的构造方法有3个:
// 传入集合长度 public ArrayList(int initialCapacity) { // 长度大于0则初始化对应长度大小的集合 if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; // 长度等于0则指定一个空集合 } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; // 长度非负,否则会抛出异常 } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } }
// 指定一个空集合 public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
// 传入一个集合 public ArrayList(Collection<? extends E> c) { // 将传参直接赋给elementData elementData = c.toArray(); // 然后进一步判断传参的大小,这一步是为了修复官方的bug if ((size = elementData.length) != 0) { // c.toArray might (incorrectly) not return Object[] (see 6260652) // 1. toArray()是接口Collection中的方法,接口方法返回的类型,取决于子类实例化时返回的具体类型。 // 2. 子类实现和父类同名的方法,仅仅返回值不一致时,默认调用的是子类的那个实现方法(有疑问可以参考java的多态实现思想)。 if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // replace with empty array. this.elementData = EMPTY_ELEMENTDATA; } }
bug-6260652示例如下
interface Father { Object[] getData(); } static class Son implements Father { @Override public String[] getData() { return new String[]{"I'm Son!"}; } } public static void main(String[] args) { // 编译运行通过并且此处返回的是字符串数组类型:class [Ljava.lang.String; System.out.println(new Son().getData().getClass()); }
LinkedList的构造方法有2个:
// 延迟初始化 public LinkedList() { }
public LinkedList(Collection<? extends E> c) { this(); // 调用addAll方法,同时初始化双向链表 addAll(c); }
-
get
ArrayList的get方法执行流程
public E get(int index) { // 检查传入的指针是否超过了当前集合的长度,如果超过则直接抛出数组越界异常IndexOutOfBoundsException rangeCheck(index); // 数组直接定位,速度最快,O(1) return elementData(index); }
LinkedList的get方法执行流程
public E get(int index) { // 检查传入的位置是否超过了当前集合的长度,如果超过则直接抛出数组越界异常IndexOutOfBoundsException checkElementIndex(index); // 链表遍历查询,速度慢,O(n) return node(index).item; } Node<E> node(int index) { // assert isElementIndex(index); // 如果index位置小于当前集合长度的一半,则从头部开始遍历 if (index < (size >> 1)) { Node<E> x = first; for (int i = 0; i < index; i++) x = x.next; return x; } else { // 如果index位置大于当前集合长度的一半,则从尾部开始遍历 Node<E> x = last; for (int i = size - 1; i > index; i--) x = x.prev; return x; } }
test
-
add
ArrayList的add方法执行流程
public boolean add(E e) { // 数组扩容 ensureCapacityInternal(size + 1); // Increments modCount!! // 添加元素 elementData[size++] = e; return true; } private void ensureCapacityInternal(int minCapacity) { // 执行之前的延迟初始化操作并且判断是否需要扩容 ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); } private static int calculateCapacity(Object[] elementData, int minCapacity) { // 如果之前没有初始化,是空集合则赋予默认长度10 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity; } private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code // minCapacity是当前集合中有数据的长度+1,elementData是当前集合的总长度 // minCapacity - elementData.length > 0说明集合需要扩容 if (minCapacity - elementData.length > 0) grow(minCapacity); } private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; // 默认扩容机制为1.5倍 int newCapacity = oldCapacity + (oldCapacity >> 1); // 如果扩容1.5倍还是不能满足长度,则取minCapacity if (newCapacity - minCapacity < 0) newCapacity = minCapacity; // 如果长度大于了(Integer.MAX_VALUE - 8) if (newCapacity - MAX_ARRAY_SIZE > 0) // 长度如果大于(Integer.MAX_VALUE - 8)则最大为Integer.MAX_VALUE,否则取(Integer.MAX_VALUE - 8) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: // 拷贝数据,浅拷贝,复制指针 elementData = Arrays.copyOf(elementData, newCapacity); }
LinkedList的add方法执行流程
// 将指定的元素追加到此列表的末尾。 public boolean add(E e) { linkLast(e); return true; } void linkLast(E e) { // 获取链表的末尾 final Node<E> l = last; // 初始化要插入的元素为node节点 final Node<E> newNode = new Node<>(l, e, null); // 将指定的元素设置此列表的末尾。 last = newNode; // 如果l为空,说明当前链表还没有元素,则头结点尾结点指向同一个 if (l == null) first = newNode; // 否则将指定的元素追加到此列表的末尾。 else l.next = newNode; size++; modCount++; }
-
addALl
ArrayList的addAll执行流程
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; }
LinkedList的addAll执行流程
public boolean addAll(int index, Collection<? extends E> c) { // 检查index是否在0到size之间 checkPositionIndex(index); Object[] a = c.toArray(); int numNew = a.length; if (numNew == 0) return false; // pred为要插入元素的位置,succ帮助定位指定元素插入的位置 Node<E> pred, succ; // index==size说明在链表尾部插入 if (index == size) { succ = null; pred = last; } else { // 否则遍历查找指定位置 succ = node(index); pred = succ.prev; } //遍历要插入的集合数据 for (Object o : a) { @SuppressWarnings("unchecked") E e = (E) o; Node<E> newNode = new Node<>(pred, e, null); // pred == null说明链表为空,则把当前数据设置为头部节点 if (pred == null) first = newNode; else pred.next = newNode; pred = newNode; } // 遍历介绍再次将插入数据与链表中的数据绑定 if (succ == null) { last = pred; } else { pred.next = succ; succ.prev = pred; } size += numNew; modCount++; return true; }
-
remove
ArrayList的remove方法执行流程
public E remove(int index) { // 检查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; }
LinkedList的remove方法执行流程
public E remove(int index) { // 检查index是否有效 checkElementIndex(index); return unlink(node(index)); }
E unlink(Node<E> x) { // assert x != null; final E element = x.item; final Node<E> next = x.next; final Node<E> prev = x.prev; // prev == null 说明要删除的是头部节点 if (prev == null) { first = next; } else { // 否则将要删除的节点的前向节点的后向节点设置为要删除节点的后向节点 // 同时取消要删除节点与前向节点的关系 prev.next = next; x.prev = null; } // prev == null 说明要删除的是尾部节点 if (next == null) { last = prev; } else { // 否则将要删除的节点的后向节点的前向节点设置为要删除节点的前向节点 // 同时取消要删除节点与后向节点的关系 next.prev = prev; x.next = null; } x.item = null; size--; modCount++; return element; }
-
总结
- 对LinkedList而言,在链表末尾增加一个元素所花的开销都是固定的,速度快。对ArrayList而言,在内部数组中增加一项,指向所添加的元素,可能会导致对数组重新拷贝移动位置,速度慢。
- ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间。
- ArrayList因为是数组,查询效率快O(1),LinkedList需要遍历查询,效率慢O(n)。
ArrayList与LinkedList
最新推荐文章于 2024-06-22 18:14:23 发布