ArrayList源码解读

ArrayList源码解读

    • 一.问题提出
    • 二.携带问题走查代码
    • 三.总结

  正文开始
  ArrayList,Java中集合框架的一种,内部使用数组实现,大小可以自动改变.具体使用方法请自行百度,本文主要结合部分源码分析ArrayList的实现.

一.问题提出

  在开始走查代码前,我们最好做一些问题的提出,然后我们再带着问题去走查代码,这样效果肯定会更好.

  1. ArrayList内部是使用数组实现的,但是数组的大小固定的,那么ArrayList是如何做到大小可变的.
  2. ArrayList是如何增删改查数据的,也就是ArrayList的常用API做了什么事情?怎么做的?

二.携带问题走查代码

  查看ArrayList的定义,我们发现ArrayList有如下几个关键的属性.

// 内部用来存储数据的数组
transient Object[] elementData;
// ArrayList的大小
private int size;
// 默认大小
private static final int DEFAULT_CAPACITY = 10; 

  以上没什么可解释的,内部维护了一个Object[]的数组,和一个int类型的大小字段.
  接下来我们看构造方法是如何初始化上面这些字段的.

有参构造

public ArrayList(int initialCapacity) {
        // 对入参进行判断
        if (initialCapacity > 0) {
            // 初始化数组
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            // 空数组  {}
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            // 入参小于0,抛异常
            throw new IllegalArgumentException("Illegal Capacity: " +
                    initialCapacity);
        }
}

无参构造

public ArrayList() {
		// 赋值数组为空数组
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

  肯定有人会有这样的猜想:空参数构造器不应该把数组初始化成默认大小(上面提到的一个属性DEFAULT_CAPACITY = 10)的吗,空参数构造其实确实会把数组初始化成默认的大小,只不过不是在构造器,而是在第一次执行add(E e)方法的时候.下文讲到add方法时我会说明.

add(E e) 方法

public boolean add(E e) {
		// 关键方法,涉及到需要的最小容量的计算,如果最小容量大于当前数组的size,还会自动扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        // 给数组中指定下标的元素赋值
        elementData[size++] = e;
        return true;
}

ensureCapacityInternal(int minCapacity) 方法

// 有两个方法,下面分别介绍
private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

calculateCapacity(elementData, minCapacity) 方法,主要确认当前Arraylist的容量大小,主要是最小容量minCapacity和默认容量之间的选择.如果是空参构造来的,那么会在这个地方将容量设置成默认容量(上面介绍空参构造器时提到过这点).

private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        	// 如果是空参构造器来的,则会进入这里,那么会返回DEFAULT_CAPACITY
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        // 直接返回minCapacity
        return minCapacity;
}

ensureExplicitCapacity(int minCapacity) 方法,主要处理自动扩容

private void ensureExplicitCapacity(int minCapacity) {
		// 这个参数大家先别管,主要是并发修改时用的,ConcurrentModificationException异常的抛出就和这个字段有关,
		// 后面这个话题可能会专门开一篇来说明
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
        	// 如果需要的最小容量比当前数组的容量大,则进行扩容
            grow(minCapacity);
}

grow(minCapacity) 方法

private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        // 新容量=旧容量 + 旧容量/2
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // 新容量不能小于 允许的最小容量
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        // 新容量如果大于 Integer.MAX_VALUE - 8,则对新容量做一些处理
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // 拷贝旧数组到新数组,并增加新数组的容量
        elementData = Arrays.copyOf(elementData, newCapacity);
}

hugeCapacity(minCapacity) 方法,当minCapacity特别大的时候的处理

private static int hugeCapacity(int minCapacity) {
		// minCapacity<0,说明已经超过了int的最大值
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        // 返回最大的Integer 或者 最大的Integer-8
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
}

  这里对MAX_ARRAY_SIZE=Integer.MAX_VALUE - 8做一个简单的解释,java中创建数组最大的长度只能是Integer.MAX_VALUE - 8,比这个数字再大就会报错,注意,这个错并不是内存溢出.

get(int index) 方法,这个方法很简单

public E get(int index) {
	// 检查范围,如果比size(这个size不是数组的size)大,就抛IndexOutOfBoundsException
    rangeCheck(index);
	// 从数组中取值
    return elementData(index);
}

  这里面涉及的两个方法都很简单,就不展开讲了,如果想知道,点进去自己看吧.

remove(int index) 方法

public E remove(int index) {
		// 同上检查index
        rangeCheck(index);
		// 上面说过,先不要关注这个字段
        modCount++;
        // 记录旧值,用于返回
        E oldValue = elementData(index);
		// index后面需要移动的元素的个数
        int numMoved = size - index - 1;
        if (numMoved > 0)
        	// 将index后的元素向前移动一位
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        // 移动后,将size处置为null,并将size-1
        elementData[--size] = null; // clear to let GC do its work
		// 返回删除前的值
        return oldValue;
    }

remove(Object o) 方法

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;
    }

fastRemove(int index) 方法

private void fastRemove(int index) {
		// 先不管这个参数
        modCount++;
        // 同上,需要移动的参数个数
        int numMoved = size - index - 1;
        if (numMoved > 0)
        	// 移动index后的的元素
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        // 将size处的元素置空并将size-1
        elementData[--size] = null; // clear to let GC do its work
    }

indexOf(Object o) 方法

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还是比较简单的,关键的方法在add上,重点关注扩容.就先到这吧

你可能感兴趣的:(Java基础,arraylist,java)