学习一段时间后看源码是必不可少的一步,源码的学习通常是将常用的方法的实现理解透彻并能合理的使用,掌握其特性。对于Java需要掌握的就是各种容器,今天首先开始学习了ArrayList的源码,将自己的学习记录下来,接下来会陆续学习剩下的源码
源码学习之ArrayList
一、ArrayList基础知识
- ArrayList 的底层是基于数组实现,且初始数组为空。由于被final和static修饰,该成员变量不可以被更改也属于类。在ArrayList中可以装载对象,其中当我们装载的是:int、byte、short、long、float、char 等基本数据类型时,实际上我们是存储了它们对应的包装类。其底层是通过
Object[] elementData
实现的。
- ArrayList 如构造时不指定参数,数组的默认容量为 10。在使用时我们可以通过 size 这个私有的成员变量得到这个容器中的元素容量。
- ArrayList是线程不安全的,线程安全版本的数组容器是Vector,可以看作是将 ArrayList的所有方法都加上synchronized 关键字。
- 同样是遍历整个所有元素,ArrayList会比LinkedList快,因为ArrayList的元素占用的是连续的物理内存,方便CPU读取数据
二、常用方法实现
1. 构造方法
在源码中提供了两种构造方法,分别为无参的和有参的。
- 在无参的构造函数中,将该容器的底层数组引用
elementalData
指向未初始化的数组的成员变量。此时底层数组长度默认为10。 - 在有参的构造函数中,传入的
initialCapacity
是底层数组elementalData
的长度,如果传入的参数是负数,则会抛出异常。如果传入容量是0,则引用类中的一个空的对象,若容量大于0,则构造一个该长度的相应对象类型的数组。
注意!这里有一个易混淆的点就是——无论哪种构造函数都不会初始化ArrayList的大小。
源码之中表示的大小是length
,也就是底层数组elementalData
的长度,而ArrayList的大小是size
,是多次调用add方法加入元素的个数。所以初始化指定的大小是数组的大小而非ArrayList的大小。
看一个例子来直观理解一下:
若我们不添加元素就调用size()
方法,得到的结果为0。若调用get(0)
、set(0,1)
等方法,都会抛出java.lang.IndexOutOfBoundsException
的异常表示数组越界。
2. ensureCapacity(int minCapacity)
通过调用该方法,来保证ArrayList实例的容量。其中minCapacity
可以看作所需的最小容量,minExpand
就是当前容器的初始化容量。如果所需要的容量更大些,就调用方法进行扩容。
接下来就是调用了ensureExplicitCapacity(int minCapacity)
方法,该方法判断所需要的容量如果比当前容器的容量大,说明容量不够,就调用grow(minCapacity)
方法扩容。
再进行计算新的ArrayList容器的长度时,使用的位运算的右移运算就是除以2。新的容器长度为旧长度的1.5倍。而扩容也是一个简单的过程:申请新数组 ,调用Array类中的copyOf方法将旧数组中的元素拷贝到新数组中。
3.contains(Object o)
该方法用于判断元素O是否存在于该容器中,在方法中再次调用indexOf(Object o)
方法,来判断其返回值。而indexOf(Object o)
方法是返回指定元素在该列表中第一次出现的位置索引。其实现方法也很简单,只是对进行整个ArrayList 中元素进行遍历。
4.size()
返回私有的成员属性 size
,这样可以确保size
属性不轻易被修改。
5. isEmpty()
判断ArrayList容器是否为空,函数中为判断size
的值是否为0
6.toArray()
该方法通过数组的拷贝来返回包含所有容器中元素的数组。
7.get(int index)
首先检查要获取下标的合法性,然后直接根据索引访问
8. set(int index, E element)
该方法把指定的index
下标的元素进行修改为element
,首先判断下标是否合理,后保存原来的值修改后返回。
9.add(E e)
- 尾插操作:对ArrayList中进行插入新的元素时,首先一定要判断在新增一个元素后容量是否够,若不够就进行扩容操作,然后再尾插进元素e
- 指定位置插入:重点操作就是复制,调用
arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
方法将底层数组elementData
中index
位置到结尾的元素拷贝到index + 1
位置处,为element
留出index
处的位置,再进行修改该处的元素为element
。
10.remove(int index)
通过阅读源码我们可以发现,删除方法也是通过数组拷贝实现的。删除事实上也可以看成是将index
位置处的元素覆盖了,arraycopy实现的功能就是将index+1
位置以后的元素全部向前“移动”一位后,将最后一位的元素置为空,且size--
。
11.clear()
清空所有的元素,就是遍历然后将每一个元素都置为null,且zise置为0
有任何问题欢迎小伙伴们随时批评指正