JDK1.8源码-----ArrayList源码分析

ArrayList源码分析:

一、前言

跟很多人一样,第一个使用的集合就是ArrayList,但是用了很久的集合类,从来没有系统的理解内部到底是怎么实现的。

二、ArrayList数据结构

每次面试的时候,都会有人问,集合的底层数据结构是什么,底层是怎么存储实现的。 ArrayList的数据结构如下: ArrayList的数据结构 ArrayList的底层数据结构就是一个Object类型的数组,可以存放所有类型数据(可由下面的源码分析得出)。当你对ArrayList进行操作,相当于你对数组进行操作。

三、源码分析

1.类的继承关系

ArrayList的继承关系
说明:

  • 实现了List接口是一个数组队列拥有了List基本的增删改查功能
  • 实现了RandomAccess接口拥有随机读写的功能
  • 实现了Cloneable接口可以被克隆
  • 实现了Serializable接口并重写了序列化和反序列化方法,使得ArrayList可以拥有更好的序列化的性能

2.ArrayList的成员变量

	/**
	 * 定义序列化ID
	 */
    private static final long serialVersionUID = 8683452581122892189L;
 
    /**
     * 默认的数组存储容量,当你没定义ArrayList数组容量大小时,默认为10
     */
    private static final int DEFAULT_CAPACITY = 10;
 
    /**
     * 当指定数组的容量为0,使用这个常量赋值(与下面空参定义一样)
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};
 
    /**
     * 默认空参构造函数时使用这个常量赋值
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
 
    /**
     * 真正存放数据的对象数组
     */
    transient Object[] elementData; // non-private to simplify nested class access
 
    /**
     * 数组中的真实元素个数,该值小于或等于elementData.length
     */
    private int size;
    /**
     * 修改次数
     */
    protected transient int modCount = 0;

3.构造方法

  /**
    * 构造函数一:指定了容量的大小
    * @param initialCapacity
    */
    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);
        }
    }
 
    /**
     * 构造函数二:默认空参构造函数
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
 
    /**
     * 构造函数三:传入集合参数的构造函数
     *
     * @param c the collection whose elements are to be placed into this list
     * @throws NullPointerException if the specified collection is null
     */
    public ArrayList(Collection<? extends E> c) {
        elementData = ((ArrayList<?>) 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;
        }
}

构造函数有三种:

  1. 如果不传入参数,则使用默认无参构建方法创建ArrayList对象,此时我们创建的ArrayList对象中的elementData中的长度是0,size是0,当进行第一次add的时候,elementData将会变成默认的长度:10.
  2. 如果传入int类型参数,则代表指定ArrayList的初始数组长度,传入参数如果是大于等于0,则使用用户的参数初始化,如果用户传入的参数小于0,则抛出异常。
  3. 如果传入带Collection对象,将collection对象转换成数组,然后将数组的地址的赋给elementData。更新size的值,同时判断size的大小,如果是size等于0,直接将空对象EMPTY_ELEMENTDATA的地址赋给elementData。如果size的值大于0,则执行Arrays.copy方法,把collection对象的内容(可以理解为深拷贝)copy到elementData中。
  • 注意:this.elementData = arg0.toArray(); 这里执行的简单赋值时浅拷贝,所以要执行Arrays,copy做深拷贝

4.常用的方法

- 添加元素
(面试可常问问题,ArrayList的添加元素实现原理,他的扩容机制)

	 /**
   	 * add(E e):添加元素方法
   	 */
	public boolean add(E e) {
	        ensureCapacityInternal(size + 1);  // Increments modCount!!
	        elementData[size++] = e;
	        return true;
	    }
    /**
   	 * 如果原来的空数组,则比较加入的个数与默认个数(10)比较,取较大值
   	 */
	private void ensureCapacityInternal(int minCapacity) {
	        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
	    }
	    
	private static int calculateCapacity(Object[] elementData, int minCapacity) {
	          // 初始化数组的大小
			  if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
			    // 取minCapacity和DEFAULT_CAPACITY中较大的那个
			    minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
			  }
			  // 检查有没有扩容的必要
			  ensureExplicitCapacity(minCapacity);
	        }

	private void ensureExplicitCapacity(int minCapacity) {
	        modCount++;
	        /**
	         * 判断数组真实元素个数加1后的长度与当前数组长度大小关系        
	         * 如果小于0,返回,如果大于0    
	         * 则调用grow(minCapacity)方法
	         */
	        if (minCapacity - elementData.length > 0)
	            grow(minCapacity);
	    }
	/**
   	 * 扩容方法,当数组容量不足以存储数据时,通过grow方法,将当前数组进行扩容,并通过Arrays的
   	 * CopyOf方法将旧数组拷贝到新的大小数组
   	 */ 
	private void grow(int minCapacity) {
	        // 记录旧的length
	        int oldCapacity = elementData.length;
	        // 扩容1.5倍, 位运算符效率更高
	        int newCapacity = oldCapacity + (oldCapacity >> 1);
	        // 判断有没有溢出
	        if (newCapacity - minCapacity < 0)
	            newCapacity = minCapacity;
	       // 判断有没有超过最大的数组大小
	        if (newCapacity - MAX_ARRAY_SIZE > 0)
	         	//计算最大的容量
	            newCapacity = hugeCapacity(minCapacity);
	         // 旧数组拷贝到新的大小数组
	        elementData = Arrays.copyOf(elementData, newCapacity);
	    }

	// 最大的容量
	private static int hugeCapacity(int minCapacity) {
	  // 大小溢出
	  if (minCapacity < 0)
	    throw new OutOfMemoryError();
	  // 需要的最小容量 > 数组最大的长度,则取Integer的最大值,否则取数组最大长度
	  return (minCapacity > MAX_ARRAY_SIZE) ?
	    Integer.MAX_VALUE :
	  MAX_ARRAY_SIZE;
	}

添加元素的流程:

  1. ensureCapacityInternal(size+1)方法,在该方法中首先判断了当前数组是否是空数组,如果是则比较加入的个数与默认个数(10)比较,取较大值,调用2方法。
  2. ensureExplicitCapacity(int minCapacity)方法,在该方法中首先是对modCount+1,判断数组真实元素个数加1后的长度与当前数组长度大小关系,如果小于0,返回,如果大于0,则调用3方法。
  3. grow(minCapacity)方法,使用 oldCapacity + (oldCapacity >> 1)是当前数组的长度变为原来的1.5倍,再与扩容后的长度以及扩容的上限值进行对比,然后调用4方法。
  4. Arrays.copyOf(elementData, newCapacity)方法,该方法的底层就是调System.arraycopy(original, 0, copy, 0,Math.min(original.length, newLength))方法,把旧数据的数据拷贝到扩容后的新数组里面,返回新数组
  5. 然后再把新添加的元素赋值给扩容后的size+1的位置里面。
    一句话总结
    ArrayList添加元素的流程为通过ensureCapacityInternal方法先判断是否为空数组添加元素,是则先添加元素,在对比加入个数与默认个数(10)比较,取最大值。不是则通过ensureExplicitCapacity方法判断当前数组大小在元素个数加1后的长度与当前数组长度大小关系,当数组元素添加个数小于数组容量时,则直接放回。当数组元素添加个数大于数组容量时,则通过grow方法将数组进行1.5倍进行扩容的方式扩容,在通过Arrays.copyOf方法,将旧数据的数据拷贝到扩容后的新数组里面,返回新数组。

- 移除

public E remove(int index) {
        rangeCheck(index);
        //记录修改的次数
        modCount++;
        E oldValue = elementData(index);
        //获取要移动元素的个数
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        //把size-1的位置的元素赋值为null,方便GC回收
        elementData[--size] = null; 
 
 
        return oldValue;
    }
public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

- 查询

 public E get(int index) {
    	/**
    	 * 检查是否越界
    	 */
        rangeCheck(index);
        /**
         * 返回指定位置上的元素
         */
        return elementData(index);
    }
// 位置访问操作
 
    @SuppressWarnings("unchecked")
    E elementData(int index) {
        return (E) elementData[index];
    }

- 修改

public E set(int index, E element) {
    	/**
    	 * 检查是否越界
    	 */
        rangeCheck(index);
        /**
         * 获取旧的元素值
         */
        E oldValue = elementData(index);
        /**
         * 新元素赋值
         */
        elementData[index] = element;
        /**
         * 返回旧的元素值
         */
        return oldValue;
    }

- 是否包含

	public boolean contains(Object o) {
	        return indexOf(o) >= 0;
	    }
	/**
	* 该方法分两种情况:null值和非null的遍历
	* 如果查询到就返回下标位置,否则就返回-1
	* 然后与0比较,大于0就存在,小于0就不存在。   
	*/ 
	public int indexOf(Object o) {
	        if (o == null) {
	            for (int i = 0; i < size; i++)
	                if (elementData[i]==null)
	                    return i;
	        } else {
	            for (int i = 0; i < size; i++)
	                if (o.equals(elementData[i]))
	                    return i;
	        }
	        return -1;
	    }

四、总结

ArrayList常常被问到有底层数据结构,添加元素流程以及扩容机制等,ArrayList在随机访问的时候,数组的结构导致访问效率比较高,但是在指定位置插入,以及删除的时候,需要移动大量的元素,导致效率低下,在使用的时候要根据场景特点来选择,另外注意循环访问的方式选择。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值