ArrayList

学习ArrayList,首先一般这个应该是从上往下的,上层定义规则,下层是具体实现,越上层特征越明显,所以应该先清楚ArrayList的继承体系。ArrayList继承AbstractList,实现List,RandomAccess,Cloneable,Serializable,关键是List和AbstractList,List继承Collection,AbstractList继承AbstractCollection,实现Colletion。具体关系如下图:

在学习ArrayList的时候,先要明白什么是Colletion,具有那些特征和能力,List在Colletion的基础上添加了哪些特性,ArrayList是如何具体实现的。AbstractColletion和AbstractList没有具体实现,充当了什么样的角色,一层一层往下剥吧。

Collection

Collection是用于存储一组元素的对象,存储方式可以是有序,也可以是无序的,里面的元素可以重复,也可以不重复。所以就衍生出了List,Set等具有某些特性的容器。

容器具有哪些特性,容器的作用是存储,可以想象它的功能,添加元素,删除元素,查看元素,判断元素是否存在,遍历,那Colletion里面也差不多就是这些方法了。

Colletion接口里面的方法大致有:

增加:add,addAll

删除:remove,removeAll,removeIf,retainAll,clear

查看:size

判断:isEmpty,contains,containsAll,

遍历:iterator,Colletion继承了Iterable接口,Iterable主要就是返回一个用于遍历的Iterator接口的对象,还有提供了加强型的for循环,就是说必须为容器提供一个可以用于遍历的Iterator实现。

转换:两个toArray方法,用于转换为数组。

Colletion描述了一个容易基本的样子,也不具备什么特征。

AbstractColletion

看到AbstractXXX的时候,首先这是抽象类,没有具体实现,类似与模板方法,将具体实现延迟到子类,提供一些模板方法。size,add,Iterator需要具体实现,然后借助这三个方法实现其余的方法,toArray方法主要是Arrays.cory实现,Arrays.cory通过system.copyarray实现,hugeCapacity方法主要是判断是否已达到最大容量,如果超过Integer.MAX_VALUE则抛出异常。

List

List特点就是有序的,所以在Colletion的基础上,他所有的特征都是围绕有序展开的,就是每一个元素都有自己的序号,方法代表一个对象的能力和特性,List相对于Collection增加的方法也是围绕这一个特性展开的。

增加了一个可以根据位置添加元素的add方法,以及一个addAll方法

根据位置获取元素的get方法

根据元素位置的index和lastIndex方法

根据位置替换元素的set方法

还有就是提供双向遍历的ListIterator方法

因为是List是有序的,添加了一个sort方法

根据起始位置的subList方法

AbstractList

功能类似与AbstractColletion,构造器类型为protect,那么在非子类,想要new它的时候,就要实现它。

具体操作的size,add,set,remove,get方法还是延迟到具体实现中。

和AbstractCollection不同的是,Iterator的实现,为什么AbstractCollletion没有,而这里有,在看完其他类的Iterator实现之前,我不知道,但是Iterator的实现是需要和聚合对象本来操作方法相关联的,应该需要根据具体类型的特征来实现,List的特点是有序,这里实现Iterator主要是使用了cursor,lastRet,游标和上一次遍历的位置,通过size,get,remove方法来实现hasNext,next,remove方法,在延迟加载的情况下,是可以提供实现的。自己也可以根据需要自定义Iterator,提供方法返回这个Iterator,就可以使用了。

ListIterator继承Iterator的实现,增加向前遍历的功能。无惨的ListIterator是有参的ListIterator的特例,为什么Iterator不提供参数,而这里有,因为ListIterator是双向遍历的,参数的作用就是控制cursor的值。

新增一个modCount属性,通过和expectedModCount的比较,用于检测并发修改的情况。//TODE可以模拟一个并发的场景,加深理解。

subList方法,其实这个SubList还是List本身,只不过是加了前后界限。通过代码可以发现,将fromIndex复值给offset,在原有List上面发生偏移,toIndex-fronIndex为size,进行约束。

Cloneable

Object提供了clone方法,实现该接口,可以进行浅克隆,不然会抛出CloneNotSupportExeption。

RandomAccess

实现该接口,实现快速随机访问的功能。

Serializable

序列化

ArrayList

看ArrayList的时候,首先它的特点就是Array,ArrayList是数组实现的。马上就应该想到的是,它内部有一个数组,但是数组的特点是固定大小,而前面看到的Array_MAX_SIZE是Integet.MAX_VALUE - 8,ArrayList不可能初始化程这个大小,那么ArrayList需要一个扩容的能力。ArrayList在扩容的时候不可能每次都扩容,所以需要一个size定义ArrayList的大小。下面是ArrayList的属性

 

modCount(继承自夫类)

elementData数组,ArrayList的具体实现

size,ArrayList使用该数组的大小

DEFAULT_CAPACITY默认容量

EMPTY_ELEMENTDATA

DEFAULECAPACITY_EMPTY_ELEMENTDATA

MAX_ARRAY_SIZE = Integer.MAX_SIZE - 8

 

DEFAULECAPACITY_EMPTY_ELEMENTDATA用于无参构造函数初始化,EMPTY_ELEMENTDATA用于有参

构造函数为0和Colletion的size0时初始化。

ArrayList继承自AbstractList,在不考虑本身特性的情况下,有size,get,add,set,remove方法需要实现。这

其中的关键就是elementData和size。我们需要在每一次结构发生改变的情况下重新定义elementData和size,有了这

两个属性,就可以实现上述5个方法了。

按步骤来看ArrayList是怎么具体实现的。

1.初始化

有3个构造函数

有参:

 

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

在有参的情况下,将elementData定义为参数大小的Object[]数组,size为0(默认为0)

 

无参:

 

public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

在无参的情况下,将elementData定义为0的Object[]数组,size为0(默认为0)

 

Collection构造函数

 

public ArrayList(Collection<? extends E> c) {
        elementData = 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;
        }
    }

给elementData赋值,定义size大小

 

三个构造函数其实就是为了初始化elementData和size

2.扩容

扩容的方法核心其实就是grow方法

 

private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        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);
    }

现实将扩容值设为原来的1.5倍,如果传入值比这个值大,则改为传入值,如果这个值大于MAX_ARRAY_SIZE则

 

调用hugeCapacity方法。这里是唯一调用到hugeCapacity方法的地方,如果越界则抛出异常,不然就是返回

Integer.MAX_VALUE,最后copy数组。这里并没有比较minCapacity和elementData.length的大小,因为huge的调用者

是ensureExpliciteCapacity,在ensureExpliciteCapacity中判断更为合适。

 

private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

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

调用该方法是,结构moCount++,判断是否扩容。

 

ensureExplicitCapacity在内部并不是直接被调用的,而是包裹在ensureCapacityInternal中被调用,

private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

该方法中重新定义传入的参数。

 

而ensureExplicitCapacity内部的调用者主要为4个add方法。

剩下的方法就不再介绍了,应该都能看明白了。

看了这些代码之后,对什么时候该抛异常,应该也可以加深理解。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值