本篇文章不定期更新(但是肯定会分析完),不正确的地方还望指出
1.首先最常写的就是new ArrayList();所以我们先来由浅入深,了解一下ArrayList的无参构造器
elementData是用于存放元素的数组
2.常用的方法还有add(E e)
先讲一下大致意义,首先确保容量足够,然后size是指当前容量,将元素存进数组中。
现在来讲一下arrayList的ensureCapacityInternal()。
首先是判断elementData数组是不是刚初始化的,如果是的话,将最小的容量设置为默认容量值10;这一步主要是为了给与最小默认长度为10。然后进入ensureExplicitCapacity()方法,接着进去看。
modCount是指修改的次数,接着进入grow方法。
上面的主要一段话是说
(1)将容量扩充为1.5倍+1
(2)如果新的容量大于了Integer的arrayList的最大容量(Integer.MAX_VALUE - 8),将容量设为最大值
(3)将原来的数组通过util的Arrays.copyOf()方法将引用放入新的数组里。这里需要注意的是,是从第一个位置开始复制到每个位置上面,那么这就是arrayList的添加效率低的问题。
3.紧接着就是get(int index)方法了
首先看方法应该就是先判断是否越界,进去一看果然如此,如果index>=size的话就会抛出IndexOutOfBoundsException异常。
没越界的话就会返回元素
4.接着可以看contains(Object o)方法
进入indexOf(Object o)看,
可以看出,基本是依靠遍历寻找,那么时间复杂度就是O(n)了。这里需要注意的是,调用的是equals()方法,所以需要重写equals(),其实这里我自己写的话,我认为不用判断是否为null,直接写成elementData[i] .equals(o)可能会更加优雅(个人看法,如果不正确的话,望指出。)
5.接着看一下add(int index,E e)方法
(1)首先是确保index<=size
(2)计算是否需要扩容
(3)然后进入arraycopy()方法。这是JDK的native方法。根据实例,分析出,其主要是将index前面位置的数据不变,然后将index位置的数据依次向后移动。然后再在index位置插入数据
6.讲了add的两个方法,就来讲讲为什么其增慢。
(1)正常添加,你可能需要扩容。并且扩容之后需要拷贝数组。
(2)插入的话,你可能要扩容,并且需要移动角标。
(说个题外话,其实当数据量特别大的时候,例如几百万,arrayList的添加速度是快于LinkedList的,这点可以做实验验证)
7.讲一下remove(Object o)方法。
首先,还是先遍历寻找,如果找到的话,进入fastRemove()方法。
首先增加修改次数,然后一样的将数组后面的向前移动。最后将引用指向null,便于gc进行回收
8.接着是remove(int index)方法。返回的是被删除的对象
其实和remove(E e)的方法差不多。
接着补充,对于ArrayList的elementData分析
为什么会是transient修饰呢?因为如果让jvm自行进行序列化的话,那么假设现在只有一个数据,但是数组的长度是10,那么jvm就会序列化10长度的数组,降低了效率,也浪费了空间,因此考虑自己序列化。arrayList可是实现了Serializable接口的,那么这就意味这是可以序列化得,继续寻找,发现了arrayList的两个方法。
先是writeObject方法,就是自己通过对象流的方式写出去,因为arrayList不是线程同步的,所以采用了类似cas算法的方式,确保写出时,是最新的。
然后是readObject这个方法,首先将size读取到,然后确保容量,然后进行for循环赋值。
接下来研究一下重要的东西iterator接口,这是个顶级接口,Collection也是继承与iterator接口的。先讲一下iterator接口的作用,其主要是在遍历的时候用的,也因为list继承于iterator,所以我们可以对其子类使用foreach循环语法糖。
先进入切入点
首先是arraylist的iterator()方法,能够得到一个iterator的实现类Itr,
现在看一下Itr类的拥有的属性和方法
cursor:游标,由于没有赋值,但是是成员变量,默认为0
lastRet:上一个被返回的元素的下标,没有的话,默认-1
expectedModCount:修改次数
接着看,hasNext()方法。
就是一个判断,判断游标的位置和elementData的长度
接着进入next()方法
先大致理一下步骤
(1)检查在iterator过程当中,arrayList是否进行过修改,如果修改过,则会报错。这也就是为什么在遍历过程当中,你不能够使用list的remove(Object)方法,不然那就是报错的原因。
(2)判断游标是否超过数组容量,如果超过了,则会报错。
(3)游标向后移动一格。
(4)返回该元素。
最后看一下Itr的remove()方法。
(1)首先判断是否还有元素。
(2)检验arrayList是否被修改过。
(3)调用arrayList的remove(int index)方法,然后将游标向前移动一格,最重要的是将expectedModCount重新赋值。