深入学习java之ArrayList 实现原理(一)

最近有时间整理了一下ArrayLIst的实现原理,因为在开发项目的应用的比较多,比如在处理比较复杂的业务时候,需要遍历来查找数据和操作数据,有些时候还有到数据进行排序等,所以对经常处理业务比较多的童鞋非常熟悉,下面我就说说我理解和使用

一、先说说ArrayList

  • ArrayList是List接口的可变数组的实现。
  • 实现了所有可选列表操作,并允许包括 null 在内的所有元素。
  • 除了实现 List接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小。
  • 每个ArrayList实例都有一个容量,该容量是指用来存储列表元素的数组的大小。
  • 它总是至少等于列表的大小。
  • 随着向ArrayList中不断添加元素,其容量也自动增长。
  • 自动增长会带来数据向新数组的重新拷贝,因此,如果可预知数据量的多少,可在构造ArrayList时指定其容量。

二、ArrayList比较常用实现

对于ArrayList而言,它实现List接口、底层使用数组保存所有元素。其操作基本上是对数组的操作。下面我们来分析ArrayList的源代码:

(1)底层使用数组

不要惊讶,它就是用数组实现的。

这里写图片描述

其中transient解释:transient是Java语言的关键字,用来表示一个域不是该对象串行化的一部分。当一个对象被串行化的时候,transient型变量的值不包括在串行化的表示中,然而非transient型的变量是被包括进去的。

(2)构造方法

ArrayList提供了三种方式的构造器

  1. 可以构造一个默认初始容量为10的空列表、
  2. 构造一个指定初始容量的空列表
  3. 构造一个包含指定collection的元素的列表,这些元素按照该collection的迭代器返回它们的顺序排列的。

第一种
这里写图片描述
这里发现它是初始化了一个默认的数组,但是
DEFAULTCAPACITY_EMPTY_ELEMENTDATA这个东西它定义的时候是空

这里写图片描述

这里小伙伴都惊呆了
不用怕,java怎么可能犯这样的错误,下面咱们慢慢道来。

第二种
这里写图片描述

这里就没什么好说的,就是初始化了数组。判断了一下初始化容量是否合法。

第三种
这里写图片描述

对java不熟悉的小伙伴,可能对Collection有疑惑,知道的就忽略这句话
Collection解释:类集,是一组对象。类集的增加使得许多 java.util中的成员在结构和体系结构上发生根本的改变。它也扩展了包可以被应用的任务范围。(网上查的)

(3)存储数据
ArrayList提供了5种存储数据的方法

 1. set(int index, E element)、 
 2. add(E e)、
 3. add(int index, E element)、
 4. addAll(Collection<? extends E> c)、 
 5. addAll(int index, Collection<?extends E> c)

第一种:替换

这里写图片描述

替换指定位置上的元素,并且返回替换之前的指定位置的元素
其中rangeCheck(index)方法是检查指定位置是否大于等于列表长度,如果大于则抛出异常。elemntData(index)方法是返回替换之前的指定位置的元素。

这里写图片描述

第二种:添加元素

这里写图片描述

添加元素到此列表的尾部
其中ensureCapacityInternal(size+1)方法:是检查当前容量是否满足需要(查看添加的位置是否已经超出),如果不能够满足当前需要,则需要重新规划容量,并且把已经存在的值拷贝过去。

这里要注意了ensureCapacityInternal方法,在上面说到初始化,为啥初始化的时候是一个空值呢?

这里写图片描述

从上面的代码可以看到,如果你是第一次使用,并且请求的最小容量小于默认容量,则取默认容量,但是如果一下子你就存储超过默认的容量,比如调用addAll()方法,则不会是默认大小了。

具体实现“容量确认”的逻辑下面再说。

第三种:元素插入到指定位置

这里写图片描述

将元素插入到列表中指定的位置 如果当前位置有元素,则向后移动当前位置的元素以及所有后续的元素(将其索引加+1)
其中rangeCheckForAdd(index)方法:检查插入的位置是否大于当前列表的长度,则抛出异常,允许在列表末尾插入元素;ensureCapacityInternal(index)方法:检查当前当前容量是否能够满足需求,如果不满足则重新规划,System.arraycop()方法:将指定位置以及后续的元素向后移动一位。

第四种:添加集合

这里写图片描述

按照collection的迭代器返回的元素顺序,将collection中的所有元素添加到列表尾部。其中ensureCapacityInternal()方法计算容量是否那种需求,system.arraycopy()方法将collection的元素添加到列表尾部。

第五种:添加集合到指定位置

这里写图片描述

从指定位置开始,将指定collection的所有元素插入次列表中。
其中rangeCheckForAdd(index)方法检测输入位置是否大于列表位置,ensureCapcityIntanal()方法确认容量是否满足需要,如插入位置小于列表长度,则需要将列的指定位置以及后续的元素都移动到插入元素长度以后。

(4)读取

这里写图片描述

返回指定位置的元素
其中rangeCheck(index)方法,检测位置是否越界

(5)删除
remove(int index)
remove(Object o)

第一种:删除指定位置的元素

这里写图片描述

第二种:移除此列表首次出现的指定元素(如果存在),ArrayList可以存放重复的元素。由于可以存储Null,所以要分情况处理。其中fastremove()方法:将移除的位置以后的元素向前移动。并且将最后一位置null

这里写图片描述

这里写图片描述

(6)调整数组容量

上面讲到想ArrayList中存储元素的时候,都会坚持添加的元素是否超出当前长度,并且初始化数组长度(所以),以满足添加数据的需求。
数组扩容通过一个公开的方法ensureCapacity(int minCapacity)来实现。在实际添加大量元素前,我也可以使用ensureCapacityIntanal来手动增加ArrayList实例的容量,以减少递增式再分配的数量。

这里写图片描述

这里写图片描述

这个方法就是确认内部容量,这里可以初始化数组容量为10,然后在调用ensureExplicitCapacity方法来明确容量

这里写图片描述

然后判断是否需要扩容

这里写图片描述

从上述代码中可以看出,数组进行扩容时,会将老数组中的元素重新拷贝一份到新的数组中,每次数组容量的增长都会进行判断,最低都会增长1.5,如果容量过大,则会赋值最大的Interage值。这种操作的代价是很高的,

因此在实际使用时,我们应该尽量避免数组容量的扩张。当我们可预知要保存的元素的多少时,要在构造ArrayList实例时,就指定其容量,以避免数组扩容的发生。或者根据实际需求,通过调用ensureCapacity方法来手动增加ArrayList实例的容量。

ArrayList还给我们提供了将底层数组的容量调整为当前列表保存的实际元素的大小的功能。它可以通过trimToSize方法来实现。代码如下:

这里写图片描述

总结:在实际使用过程中,对ArrayList使用要注意以下几点:

  1. 初始化ArrayList的时候,尽量预先定义好数组数量
  2. 当操作完一个列表后,最后要确认长度,不然可能会有容量浪费
  • 5
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值