年轻人不讲武德,一起聊聊List集合(三)

 

 


前言

业精于勤荒于嬉,行成于思毁于随;

在码农的大道上,唯有自己强才是真正的强者,求人不如求己,静下心来,开始思考…

今天一起来聊一聊 List集合,看到这里,笔者懂,大家莫慌,先来宝图镇楼 ~
在这里插入图片描述
年轻人,不讲武德,敢偷袭我老同志,耗子尾汁…

咳咳… 相信大家满脑子的ArrayList已被保国爷爷经典的画面以及台词冲淡了,那么,目的已达到,那我们言归正传,对于屏幕前帅气的猿友们来说,ArrayList,LinkedList,Vector,CopyOnWriteArrayList… 张口就来,闭眼能写,但是呢,我相信大部分的猿友们并没有刨根问底真正去看过其源码,此时,笔者帅气的脸庞似有似无洋溢起一抹微笑,毕竟是查看过源码的猿,就是那么的不讲武德,吃我一记闪电五连鞭,话不多说,来吧,展示…


 

一、List类图

List全家桶


 

二、源码剖析

 

1. Vector(此篇详解)

 

在讲Vector集合之前呢,有必要嘱咐屏幕前的猿友一声,其实呢,Vector集合与ArrayList集合基本类似,但也存在差异,重点在于其对应构造以及新增、获取、删除方法,一定要认真仔细观阅,希望再文章末尾,猿友们已自行找出其两者异同点;
 

  • 构造函数
    // Vector底层为数组
    protected Object[] elementData;
    
    // 自定义扩容增量
    protected int capacityIncrement;

    /**
     * 无参构造
     */
    public Vector() {
        this(10);
    }

    /**
     * 有参构造一
     * @param initialCapacity:指定数组初始容量
     */
    public Vector(int initialCapacity) {
        this(initialCapacity, 0);
    }


    /**
     * 有参构造二
     * @param initialCapacity
     * @param capacityIncrement:指定自定义扩容增量,后续扩容中有具体体现
     */
    public Vector(int initialCapacity, int capacityIncrement) {
        // 父类AbstractList无参构造 - 无具体实现
        super();

        // 数组初始容量校验
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                    initialCapacity);

        // 初始化数组 - length:10
        this.elementData = new Object[initialCapacity];
        
        // 设置自定义扩容增量
        this.capacityIncrement = capacityIncrement;
    }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

从源码中可以看出,上述构造方法中,不论是无参构造方法,还是有参构造方法一最终都会调用有参构造方法二,其包含两个参数(initialCapacity:数组初始容量,capacityIncrement:自定义扩容增量);

结论:
构造初始化对象,初始化数组,默认length为10,自定义扩容增量默认为0;
 

  • add() - 添加元素方法
    // 记录对Vector操作次数
    protected transient int modCount = 0;

    // 记录数组元素个数
    protected int elementCount;

    // Vector最大元素个数
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    /**
     * 入口 - synchronized修饰:线程安全
     */
    public synchronized boolean add(E e) {
        // 操作次数++
        modCount++;
        // 对数组进行扩容
        ensureCapacityHelper(elementCount + 1);
        // 数组添加元素
        elementData[elementCount++] = e;
        return true;
    }

    // 判断是否需要进行扩容 minCapacity:第一次add为(0+1)=1
    private void ensureCapacityHelper(int minCapacity) {
        // 最小容量-数组长度>0:需要进行扩容
        // 第1次add时:1-10 < 0,无需扩容
        // 第11次add时:11-10 > 0,需进行扩容
        if (minCapacity - elementData.length > 0) {
            // 具体扩容方法
            grow(minCapacity);
        }

    }

    // 具体扩容方法
    private void grow(int minCapacity) {
        // 获取数组长度
        int oldCapacity = elementData.length;
        // 计算新的数组容量;capacityIncrement:自定义扩容增量,用户可通过有参构造自定义其值,默认为0
        // 当用户自定义capacityIncrement且值大于0,扩容后数组容量为:数组长度+capacityIncrement
        // 反之(包含:用户自定义但值<=0 或 用户未定义),扩容后数组容量为:数组长度+数组长度,即扩容后为之前数组长度的2倍
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity);
        // 判断如果扩容后长度-最小容量<0,扩容后的长度为最小容量,此判断作用于第一次添加元素时
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        // 计算扩容后长度最大值,最大值为Integer的最大值(2^31-1)
        if (newCapacity - MAX_ARRAY_SIZE > 0) {
            if (minCapacity < 0)
                throw new OutOfMemoryError();
            newCapacity = (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
        }

        // 使用 Arrays.copyOf 对我们数组容量实现扩容
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56

从源码中可以看出,添加元素时且会对数组进行扩容;

知识点:
第一次扩容是在第11次add时,此时分为两种情况:

  • 1.用户自定义capacityIncrement且值大于0时:数组长度扩容为(当前数组长度+capacityIncrement),之后每次扩容遵循此规则;
  • 2.用户未定义capacityIncrement(代表默认情况下) 或 用户自定义capacityIncrement且值小于等于0时:数组长度扩容为(当前数组长度+当前数组长度),之后每次扩容遵循此规则;

结论:
默认情况下,每次扩容后为之前数组长度的2倍;
最大值:Integer最大值(2^31-1),最小值:10;

 

  • get() - 获取元素方法
    /**
     * 入口 - synchronized修饰:线程安全
     */
    public synchronized E get(int index) {
        // 校验是否越界
        if (index >= elementCount)
            throw new ArrayIndexOutOfBoundsException(index);

        // so easy:通过下标获取元素
        return elementData(index);
    }

    // 通过下标获取元素
    E elementData(int index) {
        return (E) elementData[index];
    }
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

从源码中可以看出,获取元素时就是获取数组元素,通过下标直接获取即可;

 

  • remove() - 删除元素方法
    /**
     * 入口 - synchronized修饰:线程安全
     */
    public synchronized E remove(int index) {
        // 操作次数++
        modCount++;

        // 校验下标是否越界
        if (index >= elementCount)
            throw new ArrayIndexOutOfBoundsException(index);

        // 获取要删除的元素
        E oldValue = elementData(index);

        /**
         * public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
         *  说明此方法参数作用:
         *      src:源数组
         *      srcPos:源数组要复制的起始位置
         *      dest:目的数组
         *      destPos:目的数组放置的起始位置
         *      length:复制的长度
         */
        // 对应参数中length
        int numMoved = elementCount - index - 1;
        // 删除元素其实就是一个数组整体移动的过程,再将最后一个元素置空即可
        if (numMoved > 0) {
            // 此每个参数都需各位猿友细品下,慢慢来,只是一个过程... 如此如此,这般这般,暖男的我在下方提供图,便于猿友们理解
            System.arraycopy(elementData, index+1, elementData, index, numMoved);
        }

        // 将最后一个元素置空,如只有一个元素,置空即可,便于GC工作
        elementData[--elementCount] = null; // Let gc do its work

        // 返回删除的元素值
        return oldValue;
    }
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

相信猿友们已经看出来了,Vector的删除元素方法与ArrayList的删除元素方法是一样的,而且除了删除元素方法,其增加元素方法、获取元素方法也都是很相似的;

从源码中可以看出,删除元素实则为数组移动覆盖的过程,已下图为例,便于大家理解:

  • 源数组:
    源数组
  • 目标数组(删除元素后的数组):
    目标数组
  • 删除下标为0的元素(不)

结合 arraycopy(Object src, int srcPos, Object dest, int destPos, int length)来讲,可得知:

  1. src:为上述源数组;
  2. srcPos:源数组要复制的起始位置为(index+1 = 0+1 = 1)
  3. dest:为上述目标数组
  4. destPos:目标数组放置的起始位置为(index=0);
  5. length:复制的长度为(size-index-1 = 4-0-1 = 3)
  • 过程演示:
    过程演示
  • 划重点:

相信之前没仔细研究过的猿友们,对Vector删除元素大概过程已有一些了解;

但对于有经验的开发猿来说,笔者大概能猜到两种,一种是一心追随本心道心坚固的猿友,另一种呢就是追求大道审视局势的猿友;

前者:看到这里,不论是从笔者的描述还是图文结合的理解,貌似有一定的道理,但当时的我看并不是如此,既然是arraycopy,那就不应该是移动覆盖,而是重新复制一个新数组。

后者:我当时查阅源码好像觉得也并不是这样的,记得也是复制一个新数组,而不是移动覆盖。但笔者描述确又很在理,难道…遗漏了什么?

邪魅一笑,嘴角微起,来吧,展示…
删除元素源码
其实嘛,大家说的都没错,实际上确实是复制新的数组,但Vector这里,源数组和目标数组是用一个呢.

哎…人生么,如此这般,细节决定成败。
 

  • Vector总结:
  1. 底层为数组;
  2. 构造初始化,数组为空数组,集合size为0,数组length为0;
    第一次扩容也就是第11次add时:默认情况下,数组长度length扩容为20,集合size为11;
    默认情况下,之后每次扩容遵循此规则,oldCapacity + oldCapacity,故每次扩容为之前数组长度的2倍;
    最大值:Integer最大值2147483647(2^31-1),最小值:10;
  3. 通过下标去获取元素,故查询效率高,增删效率低;
  4. 线程安全;
  5. 有modCount;

 

2. ArrayList

不讲武德,一起聊聊List集合之ArrayList


3. LinkedList

不讲武德,一起聊聊List集合之LinkedList


4. CopyOnWriteArrayList

不讲武德,一起聊聊List集合之CopyOnWriteArrayList


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值