ArrayList源码深度分析

一、集合体系图介绍

对上面的接口,抽象类,实现类,进行说明

1.所有集合类都位于java.util包下。Java的集合类主要由两个接口派生而出:Collection和Map,Collection和Map是Java集合框架的根接口,这两个接口又包含了一些子接口或实现类。

2. 集合接口:6个接口(短虚线表示),表示不同集合类型,是集合框架的基础。

3. 抽象类:5个抽象类(长虚线表示),对集合接口的部分实现。可扩展为自定义集合类。

4. 实现类:8个实现类(实线表示),对接口的具体实现。

5. Collection 接口是一组允许重复的对象。

6. Set 接口继承 Collection,集合元素不重复。

7. List 接口继承 Collection,允许重复,维护元素插入顺序。

8. Map接口是键-值对象,与Collection接口没有什么关系。

9.Set、List和Map可以看做集合的三大类:

List集合是有序集合,集合中的元素可以重复,访问集合中的元素可以根据元素的索引来访问。

Set集合是无序集合,集合中的元素不可以重复,访问集合中的元素只能根据元素本身来访问(也是集合里元素不允许重复的原因)。

Map集合中保存Key-value对形式的元素,访问时只能根据每项元素的key来访问其value。

二、java中的运算符

<<      :   左移运算符,num << 1,相当于num乘以2

 

>>      :     右移运算符,num >> 1,相当于num除以2

 

>>>    :     无符号右移,忽略符号位,空位都以0补齐

上面这个一定要搞清楚,因为我们在下面手写ArrayList的时候会用到扩容机制,JDK的是实现方式就是通过位运算来1.5倍扩容数组的,这个我们下面将详细介绍

三、数组拷贝基础知识

Arrays.copyOf功能是实现数组的复制,返回复制后的数组。参数是被复制的数组和复制的长度

System.arraycopy System.arraycopy方法:如果是数组比较大,那么使用System.arraycopy会比较有优势,因为其使用的是内存复制,省去了大量的数组寻址访问等时间

复制指定源数组src到目标数组dest。复制从src的srcPos索引开始,复制的个数是length,复制到dest的索引从destPos开始。

 

src:源数组;    srcPos:源数组要复制的起始位置;

dest:目的数组; destPos:目的数组放置的起始位置; length:复制的长度。

注意:src and dest都必须是同类型或者可以进行转换类型的数组.

有趣的是这个函数可以实现自己到自己复制,比如:

int[] fun ={0,1,2,3,4,5,6};

System.arraycopy(fun,0,fun,3,3);

则结果为:{0,1,2,0,1,2,6};

实现过程是这样的,先生成一个长度为length的临时数组,将fun数组中srcPos

到srcPos+length-1之间的数据拷贝到临时数组中,再执行System.arraycopy(临时数组,0,fun,3,3).

结果:

结果:

注意:System.arraycopy 效率很高,底层走的C语言的方法

四、纯手写ArrayList底层源码(超详细)

前期准备:

  1. 底层使用数组实现
  2. 怎样保证集合中存放无限大小,###数组的扩容技术
  3. JDk1.7之后,数组默认大小扩容代码存放在add方法(JDK1.6初始化的时候默认构造函数初始化elementData大小)
  4. ArrayList底层数据初始化默认值为10
  5. ArrayList底层采用数组实现数组名称elementData

定义一个存储数组和实际的数组大小

//定义数组存储的数组
    private transient Object[] elementData;
    //定义实际数组的大小
    private int size;

编写构造方法实现初始化容量

 //定义初始化容量大小为10
    private static final int DEFAULT_CAPACITY = 10;

    //创建初始化容量,放在构造方法中
    public NewArrayList() {
        // 默认初始容量为10
        size=DEFAULT_CAPACITY;
    }
    public NewArrayList(int initialCapacity){
        //判断值是否为零
        if (initialCapacity < 0) {
            throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
        }
        // 初始化数组容量
        elementData = new Object[initialCapacity];
    }

添加元素后大于当前数组的长度,则进行扩容,将数组的长度增加原来数组的一半。

 //扩容数组
    private void ensureCapacityInternal(int minCapacity) {
        // 如果存入的数据,超出了默认数组初始容量 就开始实现扩容
        if (size == elementData.length) {
            // 获取原来数组的长度 2
            int oldCapacity = elementData.length;
            // oldCapacity >> 1 理解成 oldCapacity/2 新数组的长度是原来长度1.5倍
            int newCapacity = oldCapacity + (oldCapacity >> 1); // 3
            if (newCapacity < minCapacity) {
                // 最小容量比新容量要小的,则采用初始容量minCapacity
                newCapacity = minCapacity;
            }
            //注意:源码中还有对 最大容量和最小容量的判断,我们在这个就不进行判断了
            elementData = Arrays.copyOf(elementData, newCapacity);
        }
    }

判断是否越界,然后添加,通过系统提供的System.arrayCopy方法

//添加方法
    public void add(E e) {
        //判断进行是否进行扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //默认添加到数组的后面
        elementData[size++] = e;
    }
    public void add(int index, Object object) {
        //判断数组是否越界
        rangeCheck(index);
        //判断是否扩容
        ensureCapacityInternal(size + 1);
        //通过系统提供输的数组拷贝
        System.arraycopy(elementData, index, elementData, index + 1, size - index);
        //覆盖我们通过上面的方法,移出来的位置index  ,我们进行覆盖就达到了添加的功能
        elementData[index] = object;
        //数组的实际长度增加
        size++;
    }
    //判断数组是否越界
    private void rangeCheck(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException("数组越界");
    }

添加的原理图分析:

删除元素:两种形式id删除和对象删除

 //删除 通过id
    public Object remove(int index) {
        //通过id获得该数据
        Object object = get(index);
        //计算需要移动的距离
        int numMoved = elementData.length - index - 1;
        //判断移动距离是否大于0  ,如果不大于0 就是数组的最后一个数
        if (numMoved > 0)
            System.arraycopy(elementData, index + 1, elementData, index, numMoved);
        //移动后,最后一个置为空
        elementData[--size] = null;
        return object;
    }
    //删除对象
    public Object remove(Object object) {
        for (int i = 0; i < elementData.length; i++) {
            Object element = elementData[i];
            //通过数组遍历查询相等的对象
            if (element.equals(object)) {
                //相等就删除  注意:默认删除第一个
                remove(i);
                return true;
            }
        }
        return false;
    }

画图分析:

五、Vector和ArrayList的区别以及Vector底层实现原理

Vector是线程安全的,但是性能比ArrayList要低。

ArrayList,Vector主要区别为以下几点:

(1):Vector是线程安全的,源码中有很多的synchronized可以看出,而ArrayList不是。导致Vector效率无法和ArrayList相比;

(2):ArrayList和Vector都采用线性连续存储空间,当存储空间不足的时候,ArrayList默认增加为原来的50%,Vector默认增加为原来的一倍;

(3):Vector可以设置capacityIncrement,而ArrayList不可以,从字面理解就是capacity容量,Increment增加,容量增长的参数。

  (4)   :初始化容量为10

与ArrayList的不同之处我们看看:

下一节手写LinkedList底层实现过程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

奋斗的小巍

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值