Java源码分析系列(一)-ArrayList源码浅析

前言

ArrayList是我们常用到的一个重要的java数据结构。从本质上说ArrayList就是一个数组,可以包含万物的数组,同时它又是一个动态增长的数组。下面就简单的分析下它的一些机制。(本文分析基于JDK1.8)

一.总体介绍

ArrayList的继承关系:

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

ArrayList主要是继承AbstractList类和List接口
这里有一个奇怪的接口RandomAccess,其实这个是有很大的用处的,后面将详细的介绍下该接口的使用。
ArrayList与Collection关系如下图:
这里写图片描述

二.ArrayList源码浅析

2.1 初始化

从本质上ArrayList是对Object[]数组的封装,ArrayList的设计是为了解决普通的数组只能固定长度。普通数组在使用中一方面可能会出现内存的浪费,另一方面可能会造成数组容量不够。

ArrayList的主要构造函数:

// 默认构造函数
ArrayList()

// capacity是ArrayList的默认容量大小。当由于增加数据导致容量不足时,容量会添加上一次容量大小的一半。
ArrayList(int capacity)

// 创建一个包含collection的ArrayList
ArrayList(Collection<? extends E> collection)

上面的是ArrayList的主要构造函数,这里的E是ArrayList的泛型。

/**
 *有参构造函数
 *1.根据你输入的initialCapacity生成initialCapacity长度的Object数组
 *2.如果你输入的initialCapacity=0,会使用默认的长度为0的数组
 *3.如果小于0直接非法参数异常
 */
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的数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA
 */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// 默认长度为0的Object数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; 

 
/**
 * 有参构造函数
 * 将Collection的数据作为ArrayList的一部分
 * 通过toArray转换为Object[]的数组,判断长度是否为0。
 */
public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

这个Object[] elementData的Object数组是整个ArrayList的真正的容器,ArrayList的addremoveget等API操作都是针对该数组操作的。

2.2 add

ArrayList之所以被称为动态数组,在于它可以进行扩容,扩容的秘密藏于add方法中
ArrayList的add方法有两个方法分别是add(int index, E element)add(E e)
先来看add(E e)方法。

2.2.1 add(E e)

//add方法
public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // 添加一个元素,在原有的size基础上增加1,但是注意这里成员变量size的值没有被改变  
        elementData[size++] = e;           //添加一个元素
        return true;
}
  
//ensureCapacityInternal方法
private void ensureCapacityInternal(int minCapacity) {
		//判断是否是ArrayList内部提供的默认数组
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        	//如果是,算出minCapacity,取出DEFAULT_CAPACITY和传入的参数minCapacity的最大值
        	// 同时这种写法也是避免出现minCapacity出现非正数的情况
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        //调用ensureExplicitCapacity方法,这时候minCapacity的值一定是大于0的
        ensureExplicitCapacity(minCapacity);
}
  
private void ensureExplicitCapacity(int minCapacity) {
        //modCount是在对list,Iterator迭代的时候判断是否改变了整体的数据结构的一种标记
        modCount++;                      
                     
        //minCapacity > elementData.length才会调用grow方法
        if (minCapacity - elementData.length > 0)      
            grow(minCapacity);
}
  
private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        //扩容!在原来的数组长度的基础上增加原长度的一半
        int newCapacity = oldCapacity + (oldCapacity >> 1); 
        // 扩容后数组容量小于minCapacity的情况
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        // 扩容后的新数组超过MAX_ARRAY_SIZE(ArrayList所能容纳的最大数值)的容错机制
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        //使用Arrays.copyOf方法来增加原数组的的长度
        elementData = Arrays.copyOf(elementData, newCapacity);
}

add方法中调用了ensureCapacityInternal方法,add方法的主要作用是在满足判断条件minCapacity - elementData.length > 0的情况下,将数组的长度增加原长度的一半。

请注意这句代码:

oldCapacity >> 1

它表示oldCapacity除以2的一次方,实际上就是除以2。这种写法相对于直接写成"oldCapacity /2"的效率更高,java的源码中大量使用了这种写法。分析到现在,实际上还没有增加Object[]数组的容量,那么如何增加已经固定长度的Object[]数组大小呢?秘密就在Arrays类的copyOf方法。

2.2.2 Arrays.copyOf

    public static <T> T[] copyOf(T[] original, int newLength) {
        return (T[]) copyOf(original, newLength, original.getClass());
    }

	public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
		// 保证copy数组是Object[]类型的
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
         
         // 重头戏
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }

System.arraycopy参数的注释。同时,arraycopy函数是一个native的方法。

// Object src : 原数组
// int srcPos : 原数组的起始位置
// Object dest :目标数组
// int destPos :目标数组的起始位置
// int length  :要copy的数组的长度

public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)

public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

通过上面的add方法分析,扩容实际上是重新创建了一个比原来数组大二分之一的新数组,再通过System.arraycopy拷贝原数组数据到新数组中。相当于elementData数组扩大了原来数组的二分之一。

2.2.3 add(int index, E element)

这个方法不仅可以向Object数组添加数据,还能在指定位置添加数据。接下来,我们来尝试分析一下。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

rockyou666

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

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

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

打赏作者

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

抵扣说明:

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

余额充值