ArryList底层数据结构以及常用方法源码理解

ArryList底层数据结构以及常用方法源码理解

1.先看一段测试代码

package com.heyuanhang.conllention_;

import java.util.ArrayList;

/**
 * @Author 何远航
 * @Date: 2021/4/17 12:57
 * @Version 1.8
 */
public class ArrayListYuanMa {
    public static void main(String[] args){
        //无参构造器,定义一个ArrayList对象时,会初始化一个空数组
        //当第一次加入数据元素时会申请一个10个长度的数组
        ArrayList list=new ArrayList();
        list.add("heyuanhang");
    }
}

(1)查看ArrayList的无参构造

 public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

注意:上述的DEFAULTCAPACITY_EMPTY_ELEMENTDATA是ArryList的一个属性,很显然这个属性是一个空的Object类型的数组。

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

看到这儿就已经知道了,原来当执行ArryList的无参构造时,是将ArryList底层维护的elementData数组初始化为{}即空的数组。

(2)好了,现在已经有一个空数组了,但是其长度却为,现在执行add方法开始添加元素.

 public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

这儿的ensureCapacityInternal(size + 1);方法是干嘛的?不妨进入看一下:

 private void ensureCapacityInternal(int minCapacity) {
 
        //这儿判断一下elementData是否是{}
        
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

由此就可以知道,原来这个方法是判断一下,elementData的长度是不是0,如果是0的话,就赋值10给minCapacity利用ensureExplicitCapacity(minCapacity)方法进行扩容。

(3)进入ensureExplicitCapacity(minCapacity)方法,源码如下:

private void ensureExplicitCapacity(int minCapacity) {
 
        //记录一下这个数组被修改的次数
        
        modCount++;

        // overflow-conscious code
        //看一下添加一个元素后的数组长度是否超过已有的数组元素
        //有就开始扩容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

注意:上面的minCapacity这个形参最开始传入的值是,size+1即就是添加一个元素以后的值

(4)进入真正的扩容方法grow方法:

private void grow(int minCapacity) {
        // overflow-conscious code
        /*
        获取已有的数组长度
        */
        int oldCapacity = elementData.length;
        
        //将已有长度的1.5倍赋值给newCapacity
        
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //如果进行1.5倍扩容后长度任然小于minCapacity
        //就将minCapacity赋值给newCapacity进行扩容
        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);
    }

由上述代码可以知道,原来ArryList的容量不够以后就进行1.5倍扩容。

(5)到此为止,ArryList就已经扩容好了,第一次添加元素时,扩容大小是10.

(6)最后一步就开始进行数据的存储。elementData[size++] = e;

由此,就会得出一个结论:

当使用ArryList无参构造时,第一次添加元素时会分配10个大小的空间,当容量不够以后再进行1.5倍的扩容。

这儿有必要看一下,ArryList的有参构造:

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

(上述代码可知,当传过来的容量大于0时,直接分配指定大小的空间,如果等于0时,作用等同于无参构造,否则的话就抛出异常)

二、向指定位置添加元素add(int index,Object obj)

测试代码:

package com.heyuanhang.conllention_;

import java.util.ArrayList;

/**
 * @Author 何远航
 * @Date: 2021/4/17 12:57
 * @Version 1.8
 */
public class ArrayListYuanMa {
    public static void main(String[] args){
        ArrayList list = new ArrayList(8);
        list.add(0, 100);
        System.out.println(list.toString());
    }
}

采用了,有参构造,指定扩容的长度为8,同样当满8后,依旧会进行1.5倍扩容。

(这儿值得注意的是,指定位置的插入的位置index不是随便的,而是有一定规范的,不能认为只要在申请数组大小之内就行了,这样的认为是错误的,必须明确的是index的值必须小于已有元素所占有的长度,就像上面现在申请了8个大小的,但是还未添加数据时,里面的有效数据长度size的值就是0,因此当index大于或小于0时都会抛出异常)

(1)现在进入add的一个重载方法add(int index,Object obj)

public void add(int index, E element) {
       //这个方法就是判断index即想插入的位置是否合理
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

(2)执行rangeCheckForAdd(index);

private void rangeCheckForAdd(int index) {

        //如果超过有效长度
        //或者小于0时
        //都会抛出异常
        
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

(3)检测完index的合法性之后,就开始进入 ensureCapacityInternal(size + 1)方法进行判断elementData是否是空数组{},这个方法的作用上面,已经讲过,这儿就不多讲了。

(4)然后进入ensureExplicitCapacity(minCapacity);方法确认一下是否需要进行扩容。
(5)最终进行数据的存储就行了。

三、删除指定位置的元素remove(int index)

测试源码:

package com.heyuanhang.conllention_;

import java.util.ArrayList;

/**
 * @Author 何远航
 * @Date: 2021/4/17 12:57
 * @Version 1.8
 */
public class ArrayListYuanMa {
    public static void main(String[] args){
        ArrayList list = new ArrayList(1);
        list.add(100);
        list.add(100);
        list.add(100);
        list.add(100);
        list.remove(0);
        System.out.println(list.toString());
    }
}

(1)进入remove方法

 public E remove(int index) {
        //判断一下index是否合法
        rangeCheck(index);
        //记录一下数组被修改的次数
        modCount++;
        //将待删除位置的元素提取出来
        E oldValue = elementData(index);
         //这儿numMoved的值其实就是index位置之后的元素个数
         
         //举个例子,如果elementData的有效长度是3
         
         //当想删除第二个位置的元素即index=1时,此时位置1后还剩下3-1-1=1个元素
        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;
    }

针对上述的代码中的System.arraycopy(elementData, index+1, elementData, index, numMoved);方法参数的说明

elementData :源数组
index+1:元素组的起始位置
 elementData:目标数组
 index:目标数组的起始位置
 numMoved:要赋值元素的长度

(2)好了,到这儿进入rangeCheck(index);方法看一下:

private void rangeCheck(int index) {
        
        //如果超过有效长度,抛出异常
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

(3)进入E elementData(int index)方法

E elementData(int index) {
         //返回指定位置的元素
        return (E) elementData[index];
    }

(4)好了,到此为止,删除方法已经完毕了。该方法返回的就是删除的元素值。

四、将一个集合添加到当前集合的方法boolean addAll(Collection<? extends E> c)方法

测试代码:
将list1集合中的元素添加到list集合中

package com.heyuanhang.conllention_;

import java.util.ArrayList;

/**
 * @Author 何远航
 * @Date: 2021/4/17 12:57
 * @Version 1.8
 */
public class ArrayListYuanMa {
    public static void main(String[] args){
        ArrayList list = new ArrayList();
        ArrayList list1 = new ArrayList();
        list.add(100);
        list.add(100);
        list.add(100);
        list.add(100);
        list1.add("何远航");
        list1.add("heyuanhang");
        list.addAll(list1);
        System.out.println(list.toString());
    }
}

(1)进入boolean addAll(Collection<? extends E> c)方法:

 public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }

对上述方法功能的简要概述:

1.先将传递过来的集合转换成一个Object类型的数组

2.然后获得该数组的长度

3.在利用ensureCapacityInternal方法进行判断一下当前的elementData是否为{},然后调用void ensureExplicitCapacity(int minCapacity)方法判断一下是否需要调用grow进行扩容。
4.再进行数组的复制就OK了。

这就是对ArryList底层数据结构以及常用方法源码理解。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值