Java 数据结构与算法 ArrayList扩容机制分析与LinkedList源码分析

ArrayList

        JDK8中,ArayList的底层是一个数组,类型是Object类型。同时没有synchronized之类的关键字,说明它是线程不安全的。

常量 

//默认的容量大小为10
private static final int DEFAULT_CAPACITY = 10;

//EMPTY_ELEMENTDATA 空对象数组(传了参数的时候使用该数组)
private static final Object[] EMPTY_ELEMENTDATA = {};

//DEFAULTCAPACITY_EMPTY_ELEMENTDATA 缺省对象数组(没有传参数的时候使用该数组)
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

//实际中数组存放的元素的个数,默认时为0个元素
private int size;

//最大的数组大小为Integer.MAX_VALUE - 8
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

构造器

带int参数

  • 如果传入的参数是大于0的数字,那么底层数组就会创建一个Object数组;
  • 如果传入的数字是一个0(相当于没有传参)那么将 EMPTY_ELEMENTDATA 这个空的数组赋值给底层数组,没有创建大小;
  • 否则抛出异常。

不带参数

  • 将空的数组 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 赋值给底层数组,仍然没有创建大小。

没有传参或参数为0的构造方法中,什么时候才会真正的创建数组呢?在第一次调用add方法的时候。

部分方法

重点的方法就是add方法。其他的方法我们看看源码就可以了。

add方法

        add方法支持两种:第一种直接add,第二种是像数组那样在index下标add。add方法中,首先通过ensureCapacityInternal方法确定是不是要扩容。然后当前的size下标插入元素e后,让size(插入的元素个数+1)。那ensureCapacityInternal方法中发生了什么?

ensureCapacityInternal方法

        假设没有传入参数或参数为0,刚分析源码得知没有创建数组,进入该方法。size表示数组的下标,在+1就是数组的大小,传入size+1是合理的。

进入该方法后,先calculateCapacity数组,在ensureExplicitCapacity刚刚的结果。

calculateCapacity方法

在该方法中,计算要初始化的大小。如果数组是个没有传参的数组,返回默认数组大小10和刚传进来的size + 1;否则返回构造的时候定义的数组大小。总体来说,这个方法用来计算数组的容量。

ensureExplicitCapacity方法

ensureExplicitCapacity方法拿到calculateCapacity方法的容量后,进行判断。如果判断的容量比数组的原来容量大,就扩容,进入grow方法。

grow方法

        进入该方法后,先将数组的大小定义为旧的容量,新的容量就是旧的容量的1.5倍。然后进行判断。minCapacity是根据当前有多少个元素来计算的,newCapacity是直接根据数组大小来计算的。

        如果新的容量比计算的容量小,新的容量就是计算的容量;如果新的容量超过MAX_ARRAY_SIZE(一个很大的数),就进入hugeCapacity方法。否则,直接创建一个newCapacity大小的数组,拷贝到当前的elementData数组中。

综上,我们就可以得知,在jdk8中使用ArrayList,并不是立即就创建数组,而是第一次调用add方法后才创建。

set方法

remove方法

总结

优点:

(1)根据下标遍历元素和访问元素效率较高。

(2)插入和删除元素的效率不高。

(3)根据元素的值查找元素的下标需要遍历整个元素数组,效率不高。

(4)线程不安全。

LinkedList

        JDK8中,LinkedList底层是一个双向的链表。除了可以作为链表之外,还能当作栈和队列来使用。

常量

//链表的长度/大小
transient int size = 0;
//指向第一个节点的指针
transient Node<E> first;
//指向最后一个节点的指针
transient Node<E> last;

构造器

LinkedList构造器中不需要传参数,说明不存在容量这类的问题。

有参数的构造方法,先调用无参数的构造方法,在使用addAll将集合添加到链表的尾部。 

add方法

        默认添加到LinkedList的尾节点。首先先将last节点赋值给l。新建newNode节点,其中l节点作为prev,null作为next。让last节点指向新创建的节点。如果l节点是空,说明是第一次创建,让first节点指向该newNode,否则l节点的下一个就是newNode节点。最后长度++。

总结

(1)经常使用到插入或删除,可以使用LinkedList,但是它的查找效率低;

(2)LinkedList基于双向循环链表实现,线程不安全。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ArrayList扩容机制码如下: ```java private void ensureCapacityInternal(int minCapacity) { // 如果当前容量不足,则需要进行扩容操作 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { // 如果当前容量是默认值,则需要将其扩容为默认容量或者minCapacity minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { modCount++; // 如果需要进行扩容,则进行扩容操作 if (minCapacity - elementData.length > 0) grow(minCapacity); } private void grow(int minCapacity) { // 当前容量 int oldCapacity = elementData.length; // 扩容后的容量 int newCapacity = oldCapacity + (oldCapacity >> 1); // 如果扩容后的容量仍然小于需要的最小容量,则直接使用需要的最小容量 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; // 如果扩容后的容量超过了ArrayList最大容量,则进行特殊处理 if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // 创建新的数组,并将原数组中的元素复制到新数组中 elementData = Arrays.copyOf(elementData, newCapacity); } private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; } ``` 在上述代码中,`ensureCapacityInternal`方法首先判断当前容量是否足够,如果不足,则调用`ensureExplicitCapacity`方法进行扩容操作。`ensureExplicitCapacity`方法会比较需要的最小容量和当前容量的差值,如果超过了当前容量,则进行扩容操作。`grow`方法是扩容的核心方法,它会首先计算扩容后的容量,然后根据扩容后的容量创建新的数组,并将原数组中的元素复制到新数组中。如果扩容后的容量超过了ArrayList最大容量,则进行特殊处理。`hugeCapacity`方法用于计算需要的最大容量,如果超过了最大容量,则抛出OutOfMemoryError异常。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值