Java8源码-ArrayList

计划先对 Java8 容器的源码进行逐个逐个分析,就当是学习数据结构了。争取一个月看完。

数据结构的定义:
数据之间互相存在的一种或多种特定的关系的 元素的集合。
什么是数据:巧妇难为无米之炊。

数据对象中数据元素之间的关系(逻辑结构):
1、集合结构
2、线性结构
3、树形结构
4、图形结构
物理结构
1、顺序存储结构
2、链式存储结构

抽象数据类型
数据类型:一组性质相同的值的集合及定义在此集合上的一些操作的总称
抽象数据类型:一个数字模型及定义在该模型上的一组操作

线性表(List)

存储方式:
顺序存储方式:存储位置连续,可以很方便 查找元素,但是增加和删除元素比较慢。
在Java中最基础的顺序存储结构:数组
集合类中的ArrayList即为一种线性表。
在这里插入图片描述
obj1是 obj2的前驱,obj2是obj1的后继。

存储位置连续,可以很方便计算各个元素的地址

下面对ArrayList中的特性以及常用方法进行分析:

ArrayList源码注释的第一行是:
Resizable-array implementation of the List interface. //List接口的可变 数组实现。
可以看出List底层是 使用数组实现的

从ArrayList源码的顶部注释可以总结出以下几点:

  • 底层:ArrayList是List接口的大小可变数组的实现
  • 是否允许null:ArrayList允许null元素。
  • 时间复杂度:size、isEmpy、get、set、iterator方法都是以固定时间运行,时间复杂度为O(1).add和remove方法需要O(n)时间。
  • 容量:ArrayList的容量可以自动增长。
    - 是否同步:不同步

先来看看ArrayList的定义:

public class ArrayList extends AbstractList implements List, RandomAccess, Cloneable, java.io.Serializable

从上可以看出 :
ArrayList:支持泛型
extends AbstractList :继承了AbstractList类
implements List:实现了所有列表的功能
RandomAccess,:表明ArrayList支持快速(通常是固定时间)随机访问。
Cloeable:表明可以调用clone方法来返回实例的field-for-field拷贝。
implements java.io.Serilizable:表明该类具有序列化的功能。
从类的定义要能看出其可能继承和拥有的某些方法。

接下来看看在类中定义的一些 域值。

 /**
      初始化默认容量
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
       指定该ArrayList容量为0时,返回该空数组
     */
    private static final Object[] EMPTY_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 == EMPTY_ELEMENTDATA will be expanded to
     * DEFAULT_CAPACITY when the first element is added.
     */
    transient Object[] elementData; // non-private to simplify nested class access


    /**
    	ArrayList实际包含的元素
     */
    private int size;

构造方法
方法一: public ArrayList(int initialCapacity) ;Constructs an empty list with the specified initial capacity.
方法二:public ArrayList() ; Constructs an empty list with an initial capacity of ten.
方法三: public ArrayList(Collection<? extends E> c) ;Constructs a list containing the elements of the specified collection, in the order they are returned by the collection’s iterator.

下面分析下上述三种构造方法的源码:
方法一

 public ArrayList(int initialCapacity) {   //参数 initialCapacity :为赋予数组的初始长度,也就是ArrayList的初始长度
        super();
        if (initialCapacity < 0)                 //判断初始长度的正负
            throw new IllegalArgumentException("Illegal Capacity: "+  //如果小于0,抛出异常
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];   //若不是异常则,对该对象进行赋值
    }

这种方法,对 ArrayList 赋予 指定大小的长度,若小于0则抛出异常,否则 直接创造 大小为 InitCapcacity大小的数组(也就是ArrayList).

方法二

  public ArrayList() {  //无参的构造函数
        super();
        this.elementData = EMPTY_ELEMENTDATA;//直接将域中创建的空数组赋值给该      数组(ArratList)
    }

此种方法,无参的构造函数,直接将 ArrayList赋值为 空数组。

方法三

 public ArrayList(Collection<? extends E> c) {   //参数为Collection接口型的 
        elementData = c.toArray();                        //将集合转换为数组
        size = elementData.length;                      //求数组的大小
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class); //进行数组的拷贝
    }

核心方法

ArrayList中的核心方法主要 覆盖以下功能:
增、删、改、查(结合数据结构看)。

方法名时间复杂度
get(int index)O(1)
add(E e)O(1)
add(int index,E element)O(n)
remove(Int index)O(n)
set(int index)–O(1)

get(int index)

 public E get(int index) {
        rangeCheck(index);         //进行索引异常检测

        return elementData(index);  //这种其实就是数组的访问方式
    }


private void rangeCheck(int index) {  //进行索引大小的判断
        if (index >= size)          //假如索引大于等于 size;因为 数组的长度是从 0开始的
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); //抛出异常
    }

在这里插入图片描述
从代码可以看出,因为ArrayList底层是数组,所以它的get方法非常简单,先是判断一下有没有越界,之后就直接通过数组下标 来获取元素。

add(E e)

 public boolean add(E e) {   //e待添加的元素
 		//确认list容量,如果不够,容量加1.注意:只加1,保证资源不被浪费
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;  //将数组最后一个元素赋值为e
        return true;
    }

从源码中可以看出,add(E e)有两个步骤:
1、空间检查,如果 有需要进行扩容
2、插入元素

在这里插入图片描述
扩容 ensureCapacity等方法

//数组容量检查,不够时则进行扩容,只供类内部使
 private void ensureCapacityInternal(int minCapacity) {   //minCapacity:想要的最小容量
        if (elementData == EMPTY_ELEMENTDATA) {   //假如之前的arrayList是空的话
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); //取 10 与 minCapacity中的最大值赋值给minCapacity
        }

        ensureExplicitCapacity(minCapacity);
    }
 private void ensureExplicitCapacity(int minCapacity) {  
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)  //确保指定的最小容量>数组缓冲区当前的长度
            grow(minCapacity);                             //扩容
    }
//扩容,保证ArrayList至少能存储 minCapacity个元素,逻辑为 newCapacity = OldCapacity +(OldCapacity>>1);即在
//原有的容量基础上增加一半第一次扩容后,如果容量还是小于minCapacity,就将容量扩充为 minCapacity
private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;                       //获取当前数组容量
        int newCapacity = oldCapacity + (oldCapacity >> 1); //扩容。新的容量=当前容量+当前容量/2
        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              //如果minCapacity<0,抛出异常
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

看完代码,可以对扩容方法总结如下:
1、进行空间检查,决定是否进行扩容,以及确定最少需要的容量
2、如果确定扩容,就执行 grow(int minCapacity),minCapacity为最少需要的容量。
3、第一次扩容,逻辑为 newcapacity = oldCapacity + (oldCapacity>>1);即在原有的容量基础上增加一半
4、第一次扩容后,如果容量还是小于 minCapacity,就将容量扩充为 minCapacity
5、对扩容后的容量进行判断,如果大于允许的最大容量MAX_ARRAY_SIZE,则将容量再次调整为 MAX_ARRAY_SIZE。至此扩容操作结束。

add(int index,E element)

  public void add(int index, E element) {
  	//越界检查
        rangeCheckForAdd(index);
        //确定list容量,如果不够,容量加1.注意:只加1,保证资源不被浪费
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //对数组进行复制处理,目的就是空出index位置插入element,并将index后的元素位移一个位置
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
           //将指定的index位置赋值给element           
        elementData[index] = element;
        //实际容量+1
        size++;
    }

在这里插入图片描述

remove(int index)

 public E remove(int index) {
 //检查索引是否越界。如果参数指定索引index>=size,抛出越界异常
        rangeCheck(index);
        
//结构性修改次数+1
        modCount++;
        
//记录索引为index处的元素
        E oldValue = elementData(index);
        
//删除指定元素后,需要左移的元素的个数
        int numMoved = size - index - 1;
        
 //如果有需要左移的元素,就移动(移动后,该删除的元素就已经被覆盖了)
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
                             
   //size-1,然后将索引 为size-1处的元素置为null.为了让GC起作用,必须显示的为最后一个位置赋 null值                   
        elementData[--size] = null; // clear to let GC do its work

//返回被删除的元素
        return oldValue;
    }```

看完代码后,可以将ArrayList删除指定索引的元素的步骤总结为:

  1. 检查索引是否越界。如果参数指定索引 index>=size,抛出一个越界异常
  2. 将索引大于index的元素 左移一位(左移后,该删除的元素就被覆盖了,相当于被删除了)
  3. 将索引为size-1处的元素置为null(为了让GC起作用)

注意:为了让GC起作用,必须显示的为最后一个位置赋null值。
在这里插入图片描述

** public E set(int index, E element)**

   public E set(int index, E element) {
   
   //检查索引是否越界
        rangeCheck(index);
        
//记录被替换的元素
        E oldValue = elementData(index);
        
  //替换元素
        elementData[index] = element;
        
   //返回被替换的元素
        return oldValue;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值