ArrayList初探

总结:

  • ArrayList底层是基于数组

  • 无参数是实例的时候容器大小是为0,添加元素的时候容器大小最小为10

  • add方法是先检查是否扩容,然后加入到数组最后

  • add(index ,e)方法是先检查index的值是否符合要求,然后再扩容,接着把index到最后的所有元素都移到自己后一位的位置,最后再把需要加入的值加入到index位置中

  • 扩容机制为首先扩容为原始容量的 1.5 倍。如果1.5倍太小的话,则将我们所需的容量大小赋值给 newCapacity,如果1.5倍太大或者我们需要的容量太大,那就直接拿 newCapacity = (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE 来扩容。扩容之后是通过数组的拷贝来确保元素的准确性的,所以尽可能减少扩容操作。 ArrayList 的最大存储能力:Integer.MAX_VALUE。

  • 如果需要边遍历边删除,则需要使用迭代器,并且使用迭代器的remove方法

1. 继承

ArrayList继承AbstractList,List,RandomAccess,Cloneable,Serializable,可能有人会奇怪了,AbstractList已经继承了List,为什么ArrayList又继承List。这个设计很巧妙,不仅仅是ArrayList,HashMap,LinkedList等等都是这样子的,那为什么要这样设计呢?其实这是为了反射。我们来举个例子:

interface ITest {

    void test();
}

public class ParentClass implements ITest{
    @Override
    public void test() {
        System.out.println("parentClass test");
        Class[] classes=this.getClass().getInterfaces();
        System.out.println(classes);
    }
}

public class SubClass extends ParentClass {
    @Override
    public void test() {
        System.out.println("SubClass` interfaces:");
        Class[] classes=this.getClass().getInterfaces();
        System.out.println(Arrays.toString(classes));
    }
}


public class InheritClass extends ParentClass implements ITest {
    @Override
    public void test() {
        System.out.println("InheritClass interfaces");
        Class[] classes=this.getClass().getInterfaces();
        System.out.println(Arrays.toString(classes));
    }
}

 public static void main(String[] strings){
    SubClass subClass=new SubClass();
    InheritClass interitClass=new InheritClass();
    subClass.test();
    interitClass.test();
 
 }

执行结果:

SubClass` interfaces:
[]
InheritClass interfaces:
[interface javat.inherit.ITest]

这里SubClass与InheritClass的区别就是InheritClass多继承了ITest的接口

2. 构造函数

ArrayList主要是3个构造方法,其实都是为了初始化容器,我们就来看看最简单的方法吧:

    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
 
     private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};   

其实就是设定容器,一个大小为0的空容器,这里其实还有个常量,跟DEFAULTCAPACITY_EMPTY_ELEMENTDATA,这两个常量有什么区别呢:简单来讲就是第一次添加元素时知道该 elementData 从空的构造函数还是有参构造函数被初始化的。以便确认如何扩容。

3. 主要方法

1. ensureCapacityInternal(int minCapacity):

这个方法主要是设定容器的上下线【下限是10】,以及扩容的step【当前容量的1.5倍】

//就直接调用这个
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));

//设定当前的容量,最小值为10
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
    //为空的时候
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        //DEFAULT_CAPACITY=10
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
    
  private void ensureExplicitCapacity(int minCapacity) {
  //修改的次数,不管增加元素还是减少元素都会增加
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    } 
    

    private void grow(int minCapacity) {
        // overflow-conscious code
        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);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

2.add(E e):

往其添加元素:

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;

就先看需不需要扩容,然后直接把需要加入的元素放入到数组中。

3.add(int index, E element):

往特定的位置加入元素:

//确定当前插入的位置是否在容器的范围内
        rangeCheckForAdd(index);
            
        ensureCapacityInternal(size + 1);   
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;

这里的过程其实就是先扩容,然后把index及其后面的所有元素一一对应赋值给其后面一个位置,最后再将要放入的元素放入到index位置中,System.arraycopy() 方法将旧数组元素拷贝至一个新的数组中去

4.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;


    private void fastRemove(int index) {
        modCount++;
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
    }

这个方法看上去也比较简单,主要做的就是找到该元素所处的位置,然后根据这个位置index及其后面的元素相当于往前面移动一个单元(其实是复制)然后再把最后一个元素置为null,这里提一下,remove(int index)跟fastRemove(int index)方法非常相似,remove(int index)跟remove(Object o)返回值是不同的

get和set方法就不说了,源码也非常简单,其实就是对数组的操作。

4.注意

有使用过集合的都知道,在用遍历集合的时候是不可以对集合进行 remove操作的,因为 remove 操作会改变集合的大小。从而容易造成结果不准确甚至数组下标越界,更严重者还会抛出 ConcurrentModificationException。我们来写个例子:

   private static void test4(){
        ArrayList<Integer> testList=new ArrayList<>();
        testList.add(1);
        testList.add(2);
        testList.add(3);
        testList.add(4);
        Iterator<Integer> iterator=testList.iterator();
        while(iterator.hasNext()){
            Integer i=iterator.next();
            if(i==2)----2
                testList.remove(i);---1
        }
    }

结果:

Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
	at java.util.ArrayList$Itr.next(ArrayList.java:859)

我们先找到出现这个错误的地方在哪,在Itr,即ArrayList的iterator中的next方法中有个checkForComodification方法,该方法会触发该异常,从源码中可以看到,在Itr初始化的时候expectedModCount = modCount,modCount是ArrayList中的,在ArrayList的remove方法会改变modCount的值,上面也说了modCount是记录操作ArrayList的次数,为了避免上面出现的这种异常,需要吧1处改成iterator.remove()就行了

说到这里,其实这里还有个问题,如果把上面的实例代码2处修改成i==3,该程序将会正常的运行,可以试一试,如果按照这样的操作,只要是删除倒数第二个,都能正常运行。一开始我还是以为是expectedModCount是被修改了,但仔细看过源码,并不是自己想像的这样,expectedModCount还是未被修改,但通过断点调试,发现最后一次会卡在iterator.hasNext()

 public boolean hasNext() {
    return cursor != size;
  }

我们知道next方法会使得cursor+1,然后ArrayList的remove方法,size会-1,这样删除了倒数第二个后,会使得cursor==size,也就不会执行next方法了,从而造成了这种错觉。

5.参考

https://blog.51cto.com/sihai/2073367

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值