一.ArrayList

1.数据结构

ArrayList底层数据结构是一个Object[]数组,底层数组的默认初始化容量为10。

    // 初始容量为 10
    private static final int DEFAULT_CAPACITY = 10;
    //空数组
    private static final Object[] EMPTY_ELEMENTDATA = {};
    //默认的空数组
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    //存放元素的对象数组
    transient Object[] elementData;
    //集合中元素的个数
    private int size;
    //最大容量
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

1.在jdk1.8中ArrayList底层先创建一个长度为0的数组

//如果在创建集合的时候指定了长度,那么就会创建一个指定长度的数组。
public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
        }
    }
//如果没有指定,则是一个空的数组。
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

2.在第一次添加元素时,会初始化一个长度为10的数组

2.扩容

  1. 扩容会传入一个minCapacity(最小容量),若当前有size个元素,有元素的最后一个下标是size-1,所以我们当前一定要保证下标为size的空间是存在的,还要保证size+1 后的空间是存在的,所以最小容量的定义为size+1。
  2. 因为当ArrayList容量使用完之后,就会自动创建当前容量1.5倍的新数组,并将原数组中所有元素拷贝过去,但是,如果新数组的长度小于最小容量,那么将以最小容量作为新数组的长度,如果新数组的长度大于Integer的最大值-8,就会以Integer的最大值作为新数组的长度,这会导致效率降低。
  3. 优化:可以使用构造方法ArrayList(int initialCapacity)或者ensureCapacity(int initialCapacity)直接提供一个初始化容量,避免刚开始就一直扩容,造成效率降低。

3.构造方法

//创建一个指定初始化容量为 initialCapactity 的空列表
public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
        }
    }
//创建一个空列表,第一次添加会将容量初始化为10
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
//创建一个包含指定集合中所有元素的列表
    public ArrayList(Collection<? extends E> c) {
        // Collection 转为数组赋给 elementData
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            //如果数组不是空,将元素复制进集合
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            //否则存入一个空数组.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

4.特点

(1).优点
  1. 向ArrayList末尾添加元素(使用add()方法)时,效率较高。
  2. 因为底层是数组的原因所以查询效率高。
(2).缺点
  1. 扩容会造成效率降低(可以通过指定初始化容量,在一定程度上得到改善)
  2. 数组很难存储大数据量(很难找到一块很大的连续内存空间)
  3. 如果向ArrayList中间添加元素,需要移动元素,会影响效率。

5.常用内部方法

(1).add方法
//直接将元素添加到数组的末尾
public boolean add(E e) {
        //容量校验
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //从数组的末尾存入元素
        elementData[size++] = e;
        return true;
    }
//将元素添加到下标为index的位置
public void add(int index, E element) {
        //判断index是否合理
        rangeCheckForAdd(index);
        //容量校验
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //将index后的元素后移,将index的位置空出来,插入数据
        System.arraycopy(elementData, index, elementData, index + 1,size - index);
        elementData[index] = element;
        size++;
    }
//
private void ensureCapacityInternal(int minCapacity) {
/*
首先是判断现在的ArrayList是不是空的。
如果是空的,最小容量就取默认容量与最小容量的最大值
*/
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        //判断是否扩容
        ensureExplicitCapacity(minCapacity);
    }
//判断是否需要扩容
private void ensureExplicitCapacity(int minCapacity) {
        //操作次数++
        modCount++;
        // overflow-conscious code
        //minCapactity为size+1,如果他超过了底层数组的长度,就会触发扩容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
(2).grow方法
private void grow(int minCapacity) {
        // overflow-conscious code
        //暂存存储数组的长度
        int oldCapacity = elementData.length;
        /*
        定义扩容后数组的长度
        长度为旧数组长度+旧数组长度/2 也就是扩容1.5倍。
        */
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //如果扩容后的数组长度比最小容量还要小,那么直接让最小容量作为扩容后数组长度
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        /*
        MAX_ARRAY_SIZE为Integer的最大值-8
        如果扩容后的数组长度+8比Integer的最大值还大
        那么调用hugeCapacity方法
        */
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        //将旧数组的元素拷贝到扩容后的数组中
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
(3).hugeCapacity方法
private static int hugeCapacity(int minCapacity) {
        //最小容量是不能小于0的
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        //如果最小容量大于Integer的最大值-8就取Integer的最大值,否则就取Integer的最大值-8;
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }
(4).indexOf方法
//用以判断一个元素是否在集合中,存在就返回该元素索引,否则返回-1
public int indexOf(Object o) {
        //如果元素为null
        if (o == null) {
            for (int i = 0; i < size; i++)
                //找到第一个为null的元素返回他的索引
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = 0; i < size; i++)
                //如果不为null,则返回第一个值匹配的索引
                if (o.equals(elementData[i]))
                    return i;
        }
        //如果找不到就返回-1
        return -1;
    }
//与indexof相似,lastIndexOf是反向遍历返回的是最后出现元素的索引
public int lastIndexOf(Object o) {
        if (o == null) {
            for (int i = size-1; i >= 0; i--)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = size-1; i >= 0; i--)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }
(5).get方法
public E get(int index) {
        //检验index是否合理
        rangeCheck(index);
        //获取元素
        return elementData(index);
    }
//检验index是否合理
private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
//获取元素
E elementData(int index) {
        return (E) elementData[index];
    }
(6).set方法
//将索引为index的元素修改为element,并返回旧元素
public E set(int index, E element) {
        //检验index是否合理
        rangeCheck(index);
        //暂存索引为index的元素
        E oldValue = elementData(index);
        //修改索引为index的元素
        elementData[index] = element;
        //返回旧元素
        return oldValue;
    }
(7).remove方法
//通过索引删除,并且返回删除值
public E remove(int index) {
        //检验index是否合理
        rangeCheck(index);
        //操作次数++
        modCount++;
        //暂存删除数据
        E oldValue = elementData(index);
        //计算需要移动多少位数组
        int numMoved = size - index - 1;
        //如果需要移动的次数大于0,就将index后一位的元素集体前移一位
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,numMoved);
        //清空最后一位
        elementData[--size] = null; // clear to let GC do its work
        //删除删除的元素
        return oldValue;
    }
//通过元素删除,返回是否删除成功
public boolean remove(Object o) {
        if (o == null) {
        //如果元素为null,删除第一个为null的元素
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                //调用fastRemove删除
                    fastRemove(index);
                    return true;
                }
        } else {
        //从头遍历,删除数组中第一个等于o的元素。
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        //数组中没有该元素返回false
        return false;
    }
//跳过边界查找,没有返回值的快速删除
private void fastRemove(int index) {
        //操作次数++
        modCount++;
        //计算需要移动多少位数组
        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
    }

二.LinkedList

1.数据结构

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

2.特点

  1. LinkedList也有下标,但是它的底层是使用的链表,所以内存不一定是连续的。
  2. 可以使用get(int index)方法,返回链表中第 index 个元素,每次查找都是从头/尾结点开始遍历
  3. 增删效率较高,查询效率较低,多用于增删操作较多的场景。

3.常用内部方法

(1).get方法
public E get(int index) {
        //判断index是否合理
        checkElementIndex(index);
        //调用node方法返回第index个元素的值
        return node(index).item;
    }
//node方法
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;
        }
    }
(2).add方法
public boolean add(E e) {
        linkLast(e);//使用尾插法添加元素e
        return true;
    }
//尾插法
void linkLast(E e) {
        //保存最后一个元素的指针
        final Node<E> l = last;
        //新建一个newNode结点作为尾结点
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        //当链表为空时,newNode就是第一个结点first
        if (l == null)
            first = newNode;
        //当链表不为空,最后一个结点的next指向newNode;
        else
            l.next = newNode;
        //链表长度++
        size++;
        //修改次数++
        modCount++;
    }
(3).addAll方法
public boolean addAll(Collection<? extends E> c) {
        return addAll(size, c);
    }
public boolean addAll(int index, Collection<? extends E> c) {
        //校验index是否合理
        checkPositionIndex(index);
        //调用toArray方法,将集合拷贝为一个数组
        Object[] a = c.toArray();
        //获取数组的长度
        int numNew = a.length;
        //如果数组是空的,返回
        if (numNew == 0)
            return false;
        /*
        定义两个结点
        pred 来存储前一个节点的引用
        succ存储当前节点
        */
        Node<E> pred, succ;
        //如果索引index等于链表长度,那么直接从末尾添加结点
        if (index == size) {
            succ = null;
            pred = last;
        } else {
        //否则就从索引处添加结点
            succ = node(index);
            pred = succ.prev;
        }
        //遍历数组元素进行添加操作
        for (Object o : a) {
            @SuppressWarnings("unchecked") E e = (E) o;
            //将集合元素 的prev指向pred
            Node<E> newNode = new Node<>(pred, e, null);
            if (pred == null)
            //如果是空链表,那么newNode就是头结点
                first = newNode;
            else
            //否者pred的后继就是newNode
                pred.next = newNode;
            pred = newNode;
        }
        //如果当前这个结点是空的
        if (succ == null) {
            //pred结点为最后一个结点
            last = pred;
        } else {
            //否则pred的下一个引用指向succ
            pred.next = succ;
            //尾节点的上一个引用指向pred
            succ.prev = pred;
        }
        //原来的size+新添加的数组长度 就是新链表的size
        size += numNew;
        //操作次数++
        modCount++;
        return true;
    }
//内部结点类构造方法
Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
(4).remove方法
//根据索引删除结点
public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }
//根据元素删除结点
public boolean remove(Object o) {
        //单独处理null
        if (o == null) {
            for (Node<E> x = first; x != null; x = x.next) {
            //遍历到为null的元素,移除掉
                if (x.item == null) {
                    unlink(x);
                    return true;
                }
            }
        } else {
            for (Node<E> x = first; x != null; x = x.next) {
            //遍历到值等于o的结点直接移除掉
                if (o.equals(x.item)) {
                    unlink(x);
                    return true;
                }
            }
        }
        return false;
    }
//使用unlink方法拿掉结点
E unlink(Node<E> x) {
        //断言x不为空
        // assert x != null;
        final E element = x.item; //暂存x结点
        final Node<E> next = x.next;//暂存x的后继结点
        final Node<E> prev = x.prev;//暂存x的前驱结点
        if (prev == null) {
        //如果x是头结点,把x的后继结点设为头节点
            first = next;
        } else {
            //跳过x结点
            prev.next = next;
            //断开x结点的前驱
            x.prev = null;
        }
        if (next == null) {
        //如果x结点是尾结点,将x的前驱结点设置为尾结点
            last = prev;
        } else {
            //跳过x结点
            next.prev = prev;
            //断开x结点的后继
            x.next = null;
        }
        //将x结点的值清空
        x.item = null;
        //链表长度--
        size--;
        //操作次数++
        modCount++;
        //之前有做暂存,将被删除的结点返回回去
        return element;
    }
(5).set方法
public E set(int index, E element) {
        //校验index是否合理
        checkElementIndex(index);
        //找到索引为index的结点
        Node<E> x = node(index);
        //保存x结点的值
        E oldVal = x.item;
        //将x结点的值设为element
        x.item = element;
        //将之前的值返回
        return oldVal;
    }

每个不起眼的日子,都是反败为胜的资本