ArrayList继承那些类,实现那些接口
我们可以通过源码看到 ArrayList 继承AbstractList 类,实现了List、RandomAccess、Cloneable,java.io.Serializable 4个接口。
实现了List接口,可随机访问,可克隆,可序列化。
List相对于Collection多了那些方法?
List,是一个不允许null,有序,允许重复元素的接口。
方法 | 描述 |
---|---|
E get(int) | 根据 int 下标获取元素 |
E set(int , E) | 设置 int 下标的元素 |
Void add(int , E) | 在 int 位置下添加对象 E |
E remove(int ) | 删除 int 下标的元素 |
int indexOf( Object ) | 获得在 List 中第一个此对象的下标位置 |
int lastIndexOf( Object ) | 获得在 List 中最后一个此对象的下标位置 |
Listiterator listIterator() | 返回ListIterator迭代器 |
ListIterator listIterator(int) | 从指定位置开始,这是ListIterator next()方法的第一个值 |
List subList(int fromIndex,int toIndex) | 返回此列表中指定的 fromIndex (含)和 toIndex之间的视图。 |
Spliterator spliterator() | 在此列表中的元素上创建一个Spliterator |
允许null么?有序么?允许重复么?
允许为空,让我们看一下add()方法的源码,
并没有判断这个对象是否为空就直接插入了。
有序,插入的方法,默认是尾部添加。
允许重复,再插入的时候,并没有检索数组中的元素,判断是否有重复。
ArrayList底层实现以及常用方法
ArrayList的底层的数据结构是基于数组实现的,并且默认初始大小为
10。
构造器:
ArrayList()
直接将内部的空数组成员变量赋给容器,在第一次add数据的时候初始化数组,容量为10
ArrayList(int initialCapacity)
以传入的initialCapacity作为初始化大小,初始化容量如果为0,直接将空数组赋给elements,如果初始容量小于0,直接抛出异常。
ArrayList(Collection<? extends E> c)
构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序。
如果使用这种方式,那么在底层ArrayList会直接将 c 集合先转成数组,赋给elementData,然后对长度进行判断,如果为0,那么直接将空数组赋给elementData。
扩容:1.5倍扩容,并且容器的最大值为 Integer.MAX_VALUE - 8 。
在这里minCapacity指的是size+1,我们可以发现newCapacity也就是我们的新容量变成了1.5倍。
如果newCapacity减去minCapacity <0也就是说我新的容量不满足现在的最小容量,那么就直接把minCapacity赋给newCapacity。
并且还要判断这个新的容量是是否大于容器容量的最大值,如果大于了,那么就把最大值赋给newCapacity。
增:
boolean add(E e)
先检查容量是否需要扩容,再将 e 插入到尾部,返回true。
void add(int index , E e)
先调用RangeCheckForAdd(index)检查下标合法性,不合法直接抛出下标越界异常。
检查+1后的容量是否需要扩容,使用System.arraycopy()方法进行移动,再将 e 放置到index下标
(tips:System,arraycopy(Object src, int srcPos, Object dest, int destPos, int length); src为源数组,srcPos为复制的开始下标,dest为目标数组,destPos从这个下标开始粘贴,int length为复制的长度。)
删:
E remove(int index) 删除指定下标的元素
ModCount++,先通过elementData(index)获取到此下标的元素并保存,再通过System.arraycopy()去调整数组。–size = null,之后返回删除的元素。
public boolean remove(Object o) 删除指定元素
遍历数组,如果找到该元素直接调用FastRemove(index)。删除成功返回true;
private void fastRemove(int index) 快速删除指定下标的元素
(这个私有删除方法直接跳过了容量检查,并且不返回原有元素) ModCount++, 使用System.arraycopy()调整数组。
改:
public E set(int index, E element)改动指定位置的元素,返回原有元素
先调用rangeCheck(index)检查下标,如果下标大于了size 直接抛出下标越界异常。
调用ElementData(index)保存原有元素,将 element 放置到 index 下标,返回原有元素。
查:
public int indexOf(Object o),从前向后查询在数组中第一个 o 对象所对应的下标
找不到,则返回-1。
public int lastIndexOf(Object o) 从后向前查询,找到数组中第一个 o 对象所对应的下标
找不到,则返回-1
E elementData(int index) 直接返回数组所对应下标的元素
public boolean contains(Object o)
查询在ArrayList中是否有这个对象,有则返回true。
底层直接调用了indexOf(o)方法获取索引,,只要索引下标大于等于0就证明存在
快速失败机制
我们会发现在这些方法中,都会有 ModCount++的操作,这个是为什么呢?
为什么要对ModCount++进行操作呢
我们来看一下那些方法进行了ModCount++操作。
ensureExplicitCapacity(),判断是否需要扩容,
remove(int index) ,删除指定下标位置
remove(Object o)和fastRemove(int index)(remove 调用fastRemove所以两个放在一起),删除指定元素。
clear(),清空数组;
void removeRange(int fromIndex, int toIndex),删除从fromindex到toIndex的元素。
那么还有一个方法就是 迭代器 Iterator
首先简单介绍一下迭代器接口中的最主要的三个方法。
next(); 返回下一个元素
hasnext(); 是否有下一个元素?
remove();从基础集合中移除此迭代器返回的最后一个元素。
在ArryaList的迭代器中有两个变量
游标cursor,这个变量存放下一个元素,也就是next()方法的元素下标
最后返回的数据的下标lastRet = -1 ,从-1开始。
我们简单来说ArrayList的快速失败机制
fail-fast 机制是Java集合(Collection)中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast(快速失败)事件。为什么,假如两个线程同时对ArrayList进行操作,那么就很有可能导致ArrayList数据出错,当删除的时候我们相当于改变了这个ArrayList的结构,所以我们采用fail-fast机制来解决这样的问题。
当ArrayList 使用 Iterator去遍历数组时,会将ModCount传进去,这个参数变成了exceptedModCount,当我们遍历的时候就会检查当前的ModCount和execeptedModCount是否一致,如果不一致直接抛出异常,因为在遍历时,遍历的文件的结构发生了改变,很有可能导致遍历出错。
但是,无论fail-fast机制再怎么完善,ArrayList依旧是非线程安全的集合