Java集合之ArrayList源码分析笔记

ArrayList

  • 特点
  1. ArrayList 底层是一个数组,具有数组的优点:查询速度快,增加删除慢
  2. ArrayList 默认的初始容量是10,随着元素的不断添加,该数组会扩充为原来数组长度的1.5倍
  3. 适合频繁进行查询时使用
  4. 数组长度不可变但是集合长度可变

首先分析其构造函数


private static final Object[] EMPTY_ELEMENTDATA = {};
// 初始化定义一个默认Object的空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**被transient关键字定义的对象不能被序列化 **/
transient Object[] elementData;


// 空参构造函数(最常用)
public ArrayList() {
		//使用默认数组
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
//指定数组长度构造函数
public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {//如果传入数组长度大于0创建一个指定长度的数组
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {//如果传入数组长度等于0使用上述空数组
            this.elementData = EMPTY_ELEMENTDATA;
        } else {// 否则抛出异常非法参数
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }
//传入一个集合
public ArrayList(Collection<? extends E> c) {
		//把传入的集合变成一个数组赋给当前数组
        elementData = c.toArray();
        /**判断数组中是否有内容,在这里可能会疑惑,上一行代码已经给elementData赋值,
        为何还要判断重新拷贝赋值,这是jdk1.8防止出现一个bug而做的一个判断jdk1.9已经修复**/
        if ((size = elementData.length) != 0) {
            if (elementData.getClass() != Object[].class)
            	//拷贝一份
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
        	// 若为空则还是赋值一个空数组
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

其次分析它的方法实现

1. add方法插入数据一

点击查看数组在增加元素时的演变过程

public boolean add(E e) {
		// 检测是否需要扩容数组,size是元素个数
        ensureCapacityInternal(size + 1);
        //把新增加的数组加到数组末尾
        elementData[size++] = e;
        //成功添加
        return true;
    }
/**
*下面解释一下上述方法
*/

 private void ensureCapacityInternal(int minCapacity) {
		//继续调用calculateCapacity
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    
    
 private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        	//第一次执行添加时,elementData数组为空数组,返回DEFAULT_CAPACITY默认值10
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        //其后在执行添加操作时返回的就是minCapacity
        return minCapacity;
    }
 private void ensureExplicitCapacity(int minCapacity) {
 		//这算是一个计数器记录数组修改次数
        modCount++;

        // 如上述,若第一次添加返minCapacity的值为10,elementData.length=0,进行第一次扩容
        if (minCapacity - elementData.length > 0)
        	//执行扩容,扩容后数组长度为15(原数组长度的1.5倍),再次执行扩容时需要minCapacity值为16,minCapacity的值是数组元素加一
            grow(minCapacity);//此方法在后面会详细提到
    }

2. add方法插入数据二

	//指定数组下标添加
  public void add(int index, E element) {
  		//检测当前数组下标是否越界(不合法)
        rangeCheckForAdd(index);
		//同上检测是否需要扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //把index之后的元素向后移动一位,这是个本地方法(看不到源码)
        //例:移动前数组[1,2,3,4,5,0,0,0] index=2,移动后数组[1,2,0,3,4,5,0,0]
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        //将元素插入index下标下
        //例: 移动后数组[1,2,0,3,4,5,0,0] 插入element=2 插入后数组:[1,2,2,3,4,5,0,0]        
        elementData[index] = element;
        //元素个数加一
        size++;
    }
	这里分析一下它是如何实现扩容操作的
 private void grow(int minCapacity) {
 		//获取当前数组容量
        int oldCapacity = elementData.length;
        //扩容数组容量=当前数组容量+当前数组容量/2
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //扩容完肯定是大于0的
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        //数组长度的最大值是Integer.MAX_VALUE - 8,不能比这个大
        if (newCapacity - MAX_ARRAY_SIZE > 0)
        	//已经达到数组储存极限
            newCapacity = hugeCapacity(minCapacity);
        // 将当前容量数组数据拷贝到新的数组中,这也是ArrayList添加操作慢的原因,需要拷贝整个数组
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

3. remove方法删除数据一

//指定下标删除
public E remove(int index) {
		//判断index是否合法是否越界
        rangeCheck(index);
		//记录加一
        modCount++;
        //取出index处的元素
        E oldValue = elementData(index);
		//删除元素需要后面所有元素向前移动填补删除的空缺,这里获取每个元素移动几个位置
        int numMoved = size - index - 1;
        if (numMoved > 0)
        	//向前移动注意赋值,正好覆盖了要删除的元素
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        //数组个数减减,把最后一个数据清空(注意数组从0开始,size是从1开始,所以是先减操作)
        elementData[--size] = null;
		//返回已经删除的元素
        return oldValue;
    }

4. remove方法删除数据二

//指定元素删除
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;
                }
        }
        //若没有该元素直接返回false
        return false;
    }
/**
 * 用到方法解析
*/
  private void fastRemove(int index) {
  		//首先还是记录操作次数
        modCount++;
        //进行删除同上,不需要检测index是否越界问题,因为上述for循环已有条件限制
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; 
    }

5. 遍历元素迭代器遍历

遍历有很多种方法,增强for遍历(底层也是调用迭代器遍历)

  • 迭代器如何遍历
      public static void main(String[] args) {
        //无参构造
        ArrayList<Object> lists = new ArrayList<>();
        lists.add(1);
        lists.add(2);
        lists.add(3);
        lists.add(4);
        lists.add(5);
        lists.add(6);
        //返回的是iterator的一个实现类Itr
        Iterator<Object> iterator = lists.iterator();
       while (iterator.hasNext()){ 
           Object next = iterator.next();
           System.out.println(next);
       }
    }

//源码分析
    public Iterator<E> iterator() {//调用此方法返回一个实例
        return new Itr();
    }
    
    //初始化的三个参数
     int cursor;       // 记录当前操作的元素下标
     int lastRet = -1; // 记录结束时元素索引为-1
     int expectedModCount = modCount; //之前一直提的数组更改次数
        
    public boolean hasNext() {//当cursor 与size相等时证明遍历已经完成跳出循环
            return cursor != size;
    	}
    	
    public E next() {
            checkForComodification();//这里可能会抛出一个异常下文讲解
            int i = cursor;//把全局变量赋值局部变量
            if (i >= size)//因为有循环终止条件所以i小于size,反之报异常
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)//因为有循环终止条件所以i小于size,反之报异常
                throw new ConcurrentModificationException();
            cursor = i + 1;//全局变量cursor 加一
            return (E) elementData[lastRet = i];//返回数组下标的数据
        }

5. 遍历元素迭代器遍历时执行添加或者删除操作会报异常原因分析

 //上述提到以下方法,每执行一次next()方法都会先调用checkForComodification()方法
 checkForComodification();

 final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
/**这里解释一下,因为每一次操作增加或者删除元素时都会改变modCount的值,
而在创建迭代器的时候出初始化数据int expectedModCount = modCount
,一旦发现不相等则报处异常**/

//若需要遍历时删除元素,使用下面方法
  while (iterator.hasNext()){
            Integer next = (Integer) iterator.next();
            if (next==2){//通过iterator的remove方法删除,看下面注释
                iterator.remove();
            }
            System.out.println(next);
        }
        System.out.println(Arrays.toString(lists.toArray()));
        
public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);//这里其实还是调用了数组的删除方法
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;//在这里保证了这两个值的一致性
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

总结

ArrayList其实就是维护了一个数组,通过编码,实现了名义上的长度可变数组,
这是它的最大优势,借此通过查看源码也可以学到很多知识
发布了5 篇原创文章 · 获赞 5 · 访问量 175

猜你喜欢

转载自blog.csdn.net/weixin_44078653/article/details/104080952