Java 数据结构之 List

26 阅读5分钟

本文只要是简单介绍一下 ArrayList 以及 LinkedList

ArrayList

ArrayList 的底层是数组。与 Java 中的数组相比,它的容量能动态增长。在添加大量元素前,应用程序可以使用 ensureCapacity 操作来增加 ArrayList 实例的容量,这可以减少递增式再分配的数量。

ArrayList 继承于 AbstractList ,实现了 List, RandomAccess, Cloneable, java.io.Serializable 这些接口。


public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable{

  }

数据结构

我们首先看一下 ArrayList 的数据结构。

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneabl{
    
    ...
    transient Object[] elementData; 
    ...
}

从代码中可以看出数据都存储在 elementData 这个数组中,所以后面的初始化以及增删改查都会围绕着这个数组展开。

初始化

我们看一下 ArrayList 无参构造函数。

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneabl{
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    ...
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
​
    }
    ...
}

通过代码我们可以看到无参构造函数中直接将空数组(DEFAULTCAPACITY_EMPTY_ELEMENTDATA)赋值给 elementData,也就是说这时 ArrayList 大小是 0

ArrayList 的查询其实很简单,根据 index 直接从 elementData 里面查找。代码如下:

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneabl{
 
    public E get(int index) {
        // 校验index是否合法
        rangeCheck(index);
​
        return elementData(index);
    }
    
    
    E elementData(int index) {
        return (E) elementData[index];
    }
}

ArrayList 的更改操作最主要的是 elementData[index] = element;,代码如下:

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneabl{
 
    public E set(int index, E element) {
        rangeCheck(index);
        // 获取历史数据
        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }
    
    
    E elementData(int index) {
        return (E) elementData[index];
    }
}

我们首先来看一下增加数据的代码:

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneabl{
 
  private static final int DEFAULT_CAPACITY = 10;
    
  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++;
​
        // 如果当前的容量小于最小容量则需要扩容
        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);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // 数组拷贝
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
}

通过代码分析我们就很清楚的了解到 ArrayList 是怎样新增数据的。

  • 判断是否需要扩容
  • 当数组为空数组时,新增时会将容量调整到 DEFAULT_CAPACITY 也就是 10。如果需要扩容,扩容后的容量是之前的 1.5 倍。
  • 最后使用 elementData[size++] = e 进行新增。

删除操作会判断删除的数是不是最后一个,如果不是则会将 index 后面的数据向前挪一格,让后让最后一个数据指向 null

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneabl{
 
     public E remove(int index) {
       // 检验index是否合法
       rangeCheck(index);
​
        modCount++;
        E oldValue = elementData(index);
​
        int numMoved = size - index - 1;
        // 如果不是最后一个数则会将index 后面的数据向前移动一格
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
​
        return oldValue;
    }
}

LinkedList

数据结构

LinkeList 最重要的数据结构就是 Node,它代表了链表的一个节点。从 Node 中可以看出这个链表是双向链表,它既有指向下一个节点的 next 指针,也有指向上一个节点的 prev 指针。

    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;
        }
    }

我们再看一下从链表的结构中我们可以看出它既有指向首节点的 first 指针,也有指向尾结点的 last 指针,说明它可以从链尾向前遍历。

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    transient int size = 0;

    /**
     * Pointer to first node.
     * Invariant: (first == null && last == null) ||
     *            (first.prev == null && first.item != null)
     */
    // 首节点
    transient Node<E> first;

    /**
     * Pointer to last node.
     * Invariant: (first == null && last == null) ||
     *            (last.next == null && last.item != null)
     */
    // 尾结点
    transient Node<E> last;
    ...
}

在查询的时候实现了一个小优化,如果要查询的 index 小于当前链表 size 的一半,就会从前往后查,否则就会从后向前查,这样来提高查询效率。

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    
    public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }

    Node<E> node(int index) {
        
        // 如果index小于size的一半,就会从前往后查,否则就会从后向前查
        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;
        }
    }    
}

修改操作比较简单,首先查找到 index 节点然后更新这个节点的值。

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    public E set(int index, E element) {
        checkElementIndex(index);
        Node<E> x = node(index);
        E oldVal = x.item;
        x.item = element;
        return oldVal;
    }
}

新增操作主要分为三种,分别是从头结点向前插入,从尾结点向后插入,从指定的节点向后插入。从头结点向前插入可以在实现栈时使用,而从尾结点向后插入可以在实现队列时使用。

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{

    /**
     * 从头结点向前插入
     */
    private void linkFirst(E e) {
        final Node<E> f = first;
        final Node<E> newNode = new Node<>(null, e, f);
        first = newNode;
        if (f == null)
            last = newNode;
        else
            f.prev = newNode;
        size++;
        modCount++;
    }

    /**
     * 从尾结点向后插入
     */
    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
            l.next = newNode;
        size++;
        modCount++;
    }

    /**
     * 从指定的节点向后插入
     */
    void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        final Node<E> pred = succ.prev;
        final Node<E> newNode = new Node<>(pred, e, succ);
        succ.prev = newNode;
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
    }
}

跟新增对应的,删除也有三种方式,这里就不再赘述了。

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{


        /**
         * 删除第一个节点
         */
       private E unlinkFirst(Node<E> f) {
        // assert f == first && f != null;
        final E element = f.item;
        final Node<E> next = f.next;
        f.item = null;
        f.next = null; // help GC
        first = next;
        if (next == null)
            last = null;
        else
            next.prev = null;
        size--;
        modCount++;
        return element;
    }

    /**
     * 删除最后一个节点
     */
    private E unlinkLast(Node<E> l) {
        // assert l == last && l != null;
        final E element = l.item;
        final Node<E> prev = l.prev;
        l.item = null;
        l.prev = null; // help GC
        last = prev;
        if (prev == null)
            first = null;
        else
            prev.next = null;
        size--;
        modCount++;
        return element;
    }

    /**
     * 删除指定节点
     */
    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;

        if (prev == null) {
            first = next;
        } else {
            prev.next = next;
            x.prev = null;
        }

        if (next == null) {
            last = prev;
        } else {
            next.prev = prev;
            x.next = null;
        }

        x.item = null;
        size--;
        modCount++;
        return element;
    }
}