基础夯实系列-集合篇(上)

60 阅读3分钟

基础夯实系列-集合篇(上)

基于java1.8源码分析

ArrayList

构造函数

// 可以看出,ArrayList 存储数据的方式是一个数组
transient Object[] elementData;

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

添加元素源码分析

分析都写在注释里了

public boolean add(E e) {
	// 处理扩容逻辑, size 是数组中真实的数据个数
    ensureCapacityInternal(size + 1);
    // 在数组尾部添加元素
    elementData[size++] = e;
    return true;
}

// add(E e) 中的方法调用该方法
private void ensureCapacityInternal(int minCapacity) {
     ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

// 当存错数据的数组不为空时,直接返回了minCapacity,既最小容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
	// 当存储数据的数组是空时,返回10 ,minCapacity 中的最大值
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    	   // DEFAULT_CAPACITY 是 10
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return 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);
    // 得到的新容量小于最小容量,将新容量赋值为最小容量
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
     // 得到的新容量大于最大容量,将新容量赋值为最大容量
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // 将旧的数据拷贝到新建的数组上
    elementData = Arrays.copyOf(elementData, newCapacity);
}

// 中间省略了一个方法,这个是最终调用的方法
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
    @SuppressWarnings("unchecked")
    // 已newLength为长度的新的数组
    T[] copy = ((Object)newType == (Object)Object[].class)
        ? (T[]) new Object[newLength]
        : (T[]) Array.newInstance(newType.getComponentType(), newLength);
 	// native 方法实现copy 
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
    return copy;
}

####添加元素操作总结

  1. 初始容量。如果指定了就是指定的数值,没指定的话就是10。

  2. 添加操作:

    • 线程不安全,时间复杂度O(1)
    • 扩容。1.5倍
    • 扩容后会产生新的数组。
    • 可以添加空元素。且数据可重复
  3. 插入,删除操作不再分析源码。时间复杂度都是 O(n)

LinkedList

先看下LinkedList的继承关系

/**
* 继承了 双端队列 Deque
**/
public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
}

LinkedList的插入操作源码。

// 链表节点的实体类
private static class Node<E> {
    E item; // 当前节点
    Node<E> next; // 前驱节点
    Node<E> prev; // 后继节点

    Node(Node<E> prev, E element, Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}

// 在指定位置插入指定元素
public void add(int index, E element) {
	// 判断索引是否越界
    checkPositionIndex(index);

    if (index == size)
    	// 如果插入的位置等于size ,直接插在链表尾部
        linkLast(element);
    else
    	// 插入到指定位置。node,linkBefore 的分析在下面
        linkBefore(element, node(index));
}

// 将数据插入链表的尾部
void linkLast(E e) {
    final Node<E> l = last;
    final Node<E> newNode = new Node<>(l, e, null);
    // 新插入的元素成为新的尾指针
    last = newNode;
    if (l == null)
        first = newNode;
    else
    	// 旧的尾指针的next指向新插入的元素
        l.next = newNode;
    size++;
    modCount++;
}

// 获取指定索引位置的数据
Node<E> node(int index) {
    // assert isElementIndex(index);

	// 如果指定的索引小于数组的一半,从头指针开始查找
    if (index < (size >> 1)) {
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
      	//返回指定索引位置的数据 
        return x;
    } else {
    	// 如果指定的索引大于数组的一半,从尾指针开始查找
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

// 将当前元素e,插入到节点succ之前
void linkBefore(E e, Node<E> succ) {
    // assert succ != null;
    final Node<E> pred = succ.prev;
    // 构建当前元素E的Node节点,并分配前驱,后继
    final Node<E> newNode = new Node<>(pred, e, succ);
    // 插入位置的节点的前驱指向新的节点
    succ.prev = newNode;
    if (pred == null)
        first = newNode;
    else
    	// 插入位置的前驱节点的后继指向新的节点
        pred.next = newNode;
    size++;
    modCount++;
}
    

插入数据总结

  1. 线程不安全,时间复杂度 O(N)。分情况,如果插头,插位,时间复杂度O(1)
  2. 没有容量限制操作。可添加空元素,相同元素。数据可重复
  3. 查找复杂度O(1)

总结

  1. ArrayList, LinkedList 都是线程不安全的
  2. ArrayList基于数组,LinkedList基于双向链表
  3. ArrayList 查找时间复杂度低。LinkedList 插入时间复杂度低。(特殊情况除外)
  4. ArrayList有扩容逻辑。LinkedList没有