ArrayList 和 LinkedList 区别

ArrayList 和 LinkedList 区别 

1、非线程安全的

2、ArrayList 底层是线性表顺序存储结构(数组)数据结构,查询速度快。

3、LinkedList 底层是线性表链式存储结构(双向链表)数据结构,增加、删除速度快。

 

ArrayList add(E e) 方法底层实现过程剖析

1、List list = new ArrayList() 的时候会通过构造函数声明一个空的 Object 类型的数组对象

private static final Object[] EMPTY_ELEMENTDATA = {};
privatetransient Object[] elementData;
public ArrayList() {
    super();
    this.elementData = EMPTY_ELEMENTDATA;
}

2、数组初始化大小、扩容都在 add() 方法中完成。在 add() 方法中调用 ensureCapacityInternal 方法进行进行初始化操作,在调用ensureCapacityInternal 方法的时候传入一个参数(当前数组长度+1)。代码如下

public boolean add(E e ) {
    ensureCapacityInternal(size + 1);  // size 是当前 arrayList 中元素的个数
    elementData[size ++] = e;
    return true;
}

3、在 ensureCapacityInternal 方法中判断,如果当前数组对象是空数组的话,就给数组默认的长度为10。(EMPTY_ELEMENTDATA)是一个空数组

// jdk 1.7 的实现方式
// ensure 确保;  capacity  容量;   internal  内部
private void ensureCapacityInternal(int minCapacity) {
    if(elementData == EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY , minCapacity); // DEFAULT_CAPACITY 为 10,max 方法就是选择出两个参数中最大的数
    }
    ensureExplicitCapacity(minCapacity);
}

// jdk 1.8 的实现方式
// elementData 是当前数组的最大长度, minCapacity 是当前数组的长度
private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

4、JDK 1.8 有这一步,和JDK 1.7 中第 3 步判断是一样的功能

// 默认为空
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

5、然后调用 ensureExplicitCapacity 方法,在 ensureExplicitCapacity 方法内部来决定是否扩容,判断条件是当前ArrayList长度+1 是否大于数组的最大长度,不大于就不扩容,大于就执行 grow() 方法进行扩容。

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // overflow-conscious code
    if (minCapacity - elementData. length > 0)
        grow(minCapacity);
}

6、初始化和扩容的时候,在 grow() 方法内部先获取当前数组的大小、根据当前数组大小通过位运算符 >> 右移动 1 位来计算本次扩容的大小。计算完成之后调用 Arrays.copyOf 将旧的数组对象完全复制到新的数组中来完成扩容。

// 给数组进行扩容
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;    // 当前数组的长度
    int newCapacity = oldCapacity + (oldCapacity >> 1);    // 通过位运算符右移1位进行扩容
    if(newCapacity - minCapacity < 0)
        newCapacity = minCapacity ;
    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);
}

private static int hugeCapacity(int minCapacity ) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer. MAX_VALUE :
        MAX_ARRAY_SIZE ;
}

ArrayList add(int index, E element) 方法底层实现过程剖析

在插入之前先检查插入的下标是否在数组的范围之内,然后再调用 ensureCapacityInternal() 方法进行数组扩容,插入过程同 add(E e) 方法

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;

    // 数组长度加1
    size++;
}

// 检查插入的下标是否在数组范围内
private void rangeCheckForAdd(int index) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

ArrayList get(int index) 方法底层实现过程剖析

1、先调用 rangeCheck(int index) 检查下标是否大于数组最大长度,大于等于数组最大长度就抛出下标越界的异常。检查通过之后调用 elementData(int index)  方法获取数据

public E get(int index) {
    rangeCheck(index);
    // 检查通过之后 调用
    return elementData(index);
}

private void rangeCheck(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

2、调用 elementData() 获取数据,直接获取数组 elementData 中对应下标的元素

/**
 * The array buffer into which the elements of the ArrayList are stored.
 * The capacity of the ArrayList is the length of this array buffer. Any
 * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
 * will be expanded to DEFAULT_CAPACITY when the first element is added.
 */
transient Object[] elementData;E elementData(int index) {
    return (E) elementData[index];
}

ArrayList remove(int index) 方法底层实现过程剖析

remove(int index) 方法跟 add 方法中关于数组扩容、缩容的逻辑类似,不同之处是 remove() 方法在移除元素之前先判断下标是否大于数组的长度,然后获取到删除的数据,然后再从要删除的下标开始将后面的数据向前移动一位

public E remove(int index) {

    // 检查下标,检查方法同上面 get() 方法内部的检查方法
    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 add(E e) 方法底层实现过程剖析

1、List list = new LinkedList() 什么也不操作

2、LinkedList 底层是双向链表实现,所以不需要初始化大小和扩容,add() 方法只是调用 linkLast() 方法向链表中添加 Node 元素。Node 就是链表中的数据元素,Node 中包括前驱、后继元素。

public boolean add(E e ) {
    linkLast(e);
    return true;
}

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

3、在 linkLast() 方法中,先获取链表中最后一个元素,然后再构造我们要增加的元素。在 Node 的构造方法中就将我们要增加的元素的前驱、后继元素都设置好,然后再将我们新构造的这个数据元素放到链表中最后的位置,最后将数组的 size+1。

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

LinkedList add(int index, E element) 方法底层实现过程剖析

1、new LinkedList() 什么也不操作,LinkedList 底层是双向链表实现,所以不需要初始化大小和扩容

2、add(index, element),先判断插入的下标是否大于链表的最大长度,如果大于链表的长度就在链表最后面追加一个 Node 元素,如果不大于就执行插入操作。Node 就是链表中的数据元素,Node 中包括前驱、后继元素。

public void add(int index, E element) {
    // 检查下标
    checkPositionIndex(index);

    // 插入的位置整好和链表的长度相等,就在链表最后增加元素即可
    if (index == size)
        linkLast(element);
    else
        // 插入元素
        linkBefore(element, node(index));
}

// 判断索引
private void checkPositionIndex(int index) {
    if (!isPositionIndex(index))
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

// 判断插入的索引位置是否大于当前链表的长度
private boolean isPositionIndex(int index) {
    return index >= 0 && index <= size;
}

3、linkBefore(element, node(index)) 这个方法执行插入操作。插入之前用 node() 方法查找出插入目标位置相邻的 node 元素并返回该 node。node() 方法查找目标元素是通过插入的位置如果大于当前链表的一半长度,就正序查找距离最近的元素,否则就反序查找距离最近的位置。这样做的目的是为了减少遍历整个链表,增加插入的效率。

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

// 查找出插入目标位置相邻的 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;
    }
}

LinkedList get(int index) 方法底层实现过程剖析

LinkedList 底层是通过双向链表实现

1、get(int index) 首先检查获取的下标是否大于链表的长度

public E get(int index) {
    // 检查下标是否大于链表的长度
    checkElementIndex(index);
    return node(index).item;
}


private void checkElementIndex(int index) {
    // 大于链表长度就抛出异常
    if (!isElementIndex(index))
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}


// 检查下标是否大于数组
private boolean isElementIndex(int index) {
    return index >= 0 && index < size;
}

2、检查通过之后调用 node() 方法获取对应的节点,node.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;
    }
}

LinkedList remove(int index, E element) 方法底层实现过程剖析

remove 之前先检查下标是否大于链表的长度,检查方法同 get 方法的检查过程,检查通过之后调用 unlink() 方法删除节点

public E remove(int index) {
    checkElementIndex(index);
    return unlink(node(index));
}

// 删除数据,x 是 node(int 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;  //  当前要删除节点的后一个节点

    // 上一个节点为空,说明当前删除的节点是第一个节点
    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--;  // 链表长度减 1
    modCount++;  // 链表操作次数加 1
    return element;
}

PS:本人理解有限,理解有不到位的地方,还望大神不吝赐教,非常感谢!

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值