备战Java面试「JDK集合源码系列」——ArrayList源码解析

  因为热爱坚持所以,因为热爱所以热爱。熬过你无戏可演的日子,终于换来了人生的春天,共勉!!!

  1.ArrayList继承体系

  ArrayList中又称动态数组,底层是基于数组实现的列表,与数组的区别在于,其具备动态扩展能力从继承体系图中可看出。ArrayList中:

  公共类 ArrayList 扩展 AbstractList

  实现List < E >、RandomAccess、Cloneable、java.io。可序列化{

  ...

  }

  复制代码

  实现了List、RandomAccess、Cloneable、java.io.Serializable等接口

  实现了列表,具备基础的添加、删除、遍历等操作实现了RandomAccess,拥有随机访问的能力实现了Cloneable,可以被克隆(浅拷贝) list.clone()实现了Serializable,可以被序列化2. ArrayList 属性

  /**

  * 默认初始容量。

  *默认容量

  */

  私有静态最终 int DEFAULT_CAPACITY=10 ;

  /**

  ·共享空 阵列 实例用于为 空的情况。

  * 空数组,如果上限的容量为0时使用

  */

  私有静态最终对象[] EMPTY_ELEMENTDATA={};

  /**

  ·共享空 阵列 实例用于为 默认尺寸空实例。

  * 我们将其与 EMPTY_ELEMENTDATA 区分开来,以了解添加第一个元素时要膨胀多少。

  *默认空容量的数组,长度为0,第一个容量时间使用,一个时间会初始添加为默认容量大小

  */

  私有静态最终对象[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA={};

  /**

  *该阵列到其中的元件缓冲器的存储ArrayList中。

  *容量的该ArrayList是长度的此阵列缓冲器。任何

  *带有 elementData==DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的空ArrayList

  * 将在添加第一个元素时扩展为DEFAULT_CAPACITY 。

  * 集合中真正存储数据元素的数组容器

  */

  瞬态对象[] elementData; // 非私有以简化嵌套类访问

  /**

  *大小的该ArrayList(数量的它包含的元素)。

  *集合中元素的个数

  * @串行

  */

  私有整数大小;

  复制代码DEFAULT_CAPACITY:集合的默认容量,默认为10,通过new ArrayList()创建List集合实例时的默认容量为10。EMPTY_ELEMENTDATA:空数组,通过new ArrayList(0)创建List集合实例时用的是这个空数组。DEFAULTCAPACITY_EMPTY_ELEMENTDATA:这个默认容量空数组,这种是通过new ArrayList()无参构造方法创建集合时用的是这个空数组,与EMPTY_ELEMENTDATA的区别是在添加第一个元素时使用空数组的会初始化为DEFAULT_CAPACITY (10)个元素。elementData存储数据元素的数组,使用瞬态响应,该定位知识化。大小:存储数据元素的个数,元素数据数组的长度非存储数据元素的个数。\3.ArrayList构造方法有参构造 ArrayList(int initialCapacity)

  /**

  * 构造一个具有指定初始容量的空列表。

  * 构造具有指定初始能力的空数组

  *

  * @param initialCapacity 列表初始容量列表的初始容量

  * @throws IllegalArgumentException 如果指定的初始容量为负

  *

  *对应初始初始容量,如果大于0就初始化元素数据为大小,如果等于0就使用EMPTY_ELEMENTDATA空数组,

  * 如果小于0 异常。

  */

  // ArrayList(int initialCapacity)构造方法

  public ArrayList ( int initialCapacity) {

  if (initialCapacity > 0 ) {

  this .elementData=new Object[initialCapacity];

  } else if (initialCapacity==0 ) {

  this .elementData=EMPTY_ELEMENTDATA;

  } else {

  throw new IllegalArgumentException( "不合理的初识能力:" +

  初始容量);

  }

  }

  复制代码无参构造ArrayList()

  /**

  * 构造一个初始容量为 10 的空列表。

  * 构造一个最终能力为10的空数组

  *

  * 不传初始空,初始化为DEFAULTCAPACITY_EMPTY_ELEMENTDATA数组,

  * 会在添加第一个元素的时候扩容为默认的大小,即10。

  */

  public ArrayList () {

  this .elementData=DEFAULTCAPACITY_EMPTY_ELEMENTDATA;

  }

  复制代码有参构造ArrayList(Collection c)

  /**

  * 构造一个包含指定元素的列表

  * 集合,按照集合返回的顺序

  * 迭代器。

  * 把集合的元素初始化到ArrayList中

  *

  * @param c 其元素将被放入此列表的集合

  * @throws NullPointerException 如果指定的集合为空

  */

  public ArrayList(Collection c) {

  // 将构造方法中的集合参数转换成数组

  elementData=c.toArray();

  if (( size=elementData.length) !=0 ) {

  // 检查类型c.toArray()返回不是Object[],如果不是,重拷贝成Object[].class类型

  if (elementData.getClass() !=Object[]. class )

  //

  数组的创建与拷贝elementData=Arrays.copyOf(elementData, size , Object[]. class );

  } else {

  // 如果c是空的集合,则初始化为空数组EMPTY_ELEMENTDATA

  this .elementData=EMPTY_ELEMENTDATA;

  }

  }

  复制代码4.ArrayList相关操作方法

  添加操作

  1.add(E e)添加元素到集合中添加元素到更新,平均时间复杂度为O(1):

  /**

  * 将指定的元素附加到此列表的末尾。

  *添加元素到细节,平均时间复杂度为O(1)

  * * @param e 要附加到此列表的元素

  * @return true (由 { @link Collection#add}指定)

  */

  public boolean add (E e) {

  //每加入一个元素,minCapacity大小+1,并检查是否需要扩容

  ensureCapacityInternal(size + 1 ); // 增加 modCount!!

  // 把元素插入到最后一位

  elementData[size++]=e;

  返回 真;

  }

  // 计算最小容量

  private static int calculateCapacity (Object[] elementData, int minCapacity) {

  // 如果是空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA

  if (elementData==DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {

  // 返回DEFAULT_CAPACITY 和minCapacity 的一方

  返回Math.max( DEFAULTCAPACITY_EMPTY_ELEMENTDATA )最小容量);

  }

  返回minCapacity;

  }

  //检查是否需要扩容

  private void ensureCapacityInternal ( int minCapacity) {

  ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));

  }

  private void ensureExplicitCapacity ( int minCapacity) {

  modCount++; // 数组结构被修改的次数+1

  // 有溢出意识的代码存储元素的数据长度减少需要的最大容量时

  if (minCapacity - elementData.length > 0 )

  // 扩容

  增长(最小容量);

  }

  /**

  *扩容

  * 增加容量以确保它可以保持至少

  * 由最小容量 arg 指定的元素数量

  * 增加容量以确保能够尽可能增加容量参数指定的元素数量

  * @param minCapacity 所需的最小容量

  * /

  私有 空隙 成长(INT minCapacity) {

  //原来的容量

  INT oldCapacity=elementData.length;

  // 新容量为旧容量的1.5倍

  int newCapacity=oldCapacity + (oldCapacity >> 1 );

  // 如果新容量发现比需要的容量还小,则需要的容量

  只有 if (newCapacity - minCapacity < 0 )

  newCapacity=minCapacity;

  // 如果新容量已经超过最大容量了,则使用最大容量

  if (newCapacity - MAX_ARRAY_SIZE > 0 )

  newCapacity=巨大的Capacity(minCapacity);

  // 以新容量复制出来一个新数组

  elementData=Arrays.copyOf(elementData, newCapacity);

  }

  // 使用最大容量

  private static int hugeCapacity ( int minCapacity) {

  if (minCapacity < 0 ) // 溢出

  throw new OutOfMemoryError();

  返回(minCapacity > MAX_ARRAY_SIZE) ?

  Integer.MAX_VALUE :

  MAX_ARRAY_SIZE;

  }

  复制代码执行流程:检查是否需要扩容;如果elementData等于DEFAULTCAPACITY_EMPTY_ELEMENTDATA则初始化容量大小为DEFAULT_CAPACITY;新容量是老容量的1.5倍(oldCapacity + (oldCapacity >> 1)),如果加了这么多容量发现比需要的容量还小,那么就以需要的容量为基础;创建新容量的数组并把老数组拷贝到新数组;2.add(int index,E element)添加元素到指定位置添加元素到指定位置,平均时间复杂度为O(n):

  /**

  * 在此指定位置插入指定元素

  * 列表。移动当前在该位置的元素(如果有)和

  * 右边的任何后续元素(在它们的索引上加一)。

  *添加元素到指定位置,平均时间添加度为O(n)。

  * * @param index 指定元素要插入的索引

  * @param element 要插入的元素

  *抛出: IndexOutOfBoundsException异常{ @inheritDoc }

  */

  public void add ( int index, E element) {

  // 检查是否越界

  rangeCheckForAdd(index);

  //检查是否需要扩容

  ensureCapacityInternal(size + 1 ); // 增加 modCount!!

  // 将inex及其之后的元素往后挪位,则索引位置处就空出来了

  // **进行了大小-索引索引次操作**

  System.arraycopy(elementData, index, elementData, index + 1 ,

  大小 - 索引);

  // 将元素插入到索引的位置

  元素数据[索引]=元素;

  // 元素数量增1

  尺寸++;

  }

  /**

  * add 和 addAll 使用的 rangeCheck 版本。

  * add和addAll方法使用的rangeCheck版本

  */

  // 检查是否越界

  private void rangeCheckForAdd ( int index) {

  if (index > size || index < 0 )

  throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

  }

  复制代码执行流程:检查是否越界;检查是否需要扩容;把插入位置后的元素都往后移动一位;在插入位置插入的元素;元素数量增1;3.addAll(Collection c)添加所有集合参数中的所有元素求两个集合的并集:

  /**

  * 将指定集合中的所有元素追加到末尾

  * 这个列表,按照它们返回的顺序

  * 指定集合的迭代器。此操作的行为是

  * undefined 如果指定的集合在操作时被修改

  * 正在处理。(这意味着这个调用的行为是

  * undefined 如果指定的集合是这个列表,并且这个

  * 列表非空。)

  * 将集合c中所有元素添加到当前ArrayList中

  * * @param c 包含要添加到此列表的元素的集合

  * @return true 如果此列表因调用而更改

  * @throws NullPointerException 如果指定的集合为空

  */

  public boolean addAll(Collection c) {

  // 将集合c转为数组

  Object[] a=c.toArray();

  int numNew=a.length;

  //检查是否需要扩容

  ensureCapacityInternal( size + numNew); // Increments modCount

  // 将c中元素全部拷贝到数组的最后

  System.arraycopy(a, 0 , elementData, size , numNew);

  //集合中元素的大小增加?的大小

  尺寸+=numNew;

  // 如果c不为空就返回true,否则返回false

  return numNew!=0 ;

  }

  复制代码执行流程:拷贝c中的元素到数组a中;检查是否需要扩容;把数组中的元素拷贝到元素数据的尾部;

  访问操作

  4.get(int index)获取指定位置的元素获取指定位置的元素,时间更新度为O(1)。

  /**

  * 返回此列表中指定位置的元素。

  * * @param index 要返回的元素的索引

  * @return列表中指定位置的元素

  *抛出: IndexOutOfBoundsException异常{ @inheritDoc }

  */

  public E get ( int index) {

  // 检查是否越界

  范围检查(索引);

  // 返回数组索引位置的元素

  return elementData(index);

  }

  /**

  * 检查给定的索引是否在范围内。如果不是,则抛出一个适当的

  * 运行时异常。此方法*不*检查索引是否为

  * 否定:它总是在数组访问之前立即使用,

  * 如果 index 为负,则抛出 ArrayIndexOutOfBoundsException。

  *检查给定的索引是否在集合有效元素数量范围内

  */

  private void rangeCheck ( int index) {

  if (index >=size)

  throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

  }

  @SuppressWarnings ( "unchecked" )

  E elementData ( int index) {

  return (E) elementData[index];

  }

  复制代码执行流程:检查是否越界,只检查是否越上界,如果越上界抛出IndexOutOfBounds异常异常,如果越下界抛出是ArrayIndexOutOfBoundsException异常。返回索引位置处的元素;

  删除操作

  5.remove(int index)删除指定索引位置的元素,删除指定位置的元素,时间复杂度为O(n)。

  /**

  * 删除此列表中指定位置的元素。

  * 将任何后续元素向左移动(从它们的元素中减去一个

  * 指数)。

  *删除指定位置的元素,时间复杂度为O(n)。

  * * @param index 要删除的元素的索引

  * @return从列表中移除的元素

  *抛出: IndexOutOfBoundsException异常{ @inheritDoc }

  */

  public E remove ( int index) {

  // 检查是否越界

  范围检查(索引);

  // 集合数组结构修改次数+1

  modCount++;

  // 获取索引位置的元素

  E oldValue=elementData(index);

  int numMoved=大小 - 索引 - 1 ;

  // 如果索引不是最后一位,则将索引之后的元素往前

  移位if (numMoved > 0 )

  // **进行了大小-索引索引-1次操作**

  System.arraycopy(elementData, index + 1、元素数据、索引、

  numMoved);

  // 将最后一个元素删除,帮助GC

  elementData[--size]=null ; // 清除让 GC 做它的工作

  // 返回旧值

  return oldValue;

  }

  复制代码执行流程:检查是否越界;获取指定位置的元素;如果删除的不是最后一位,则其他元素往前移一位;将最后一个位置为null,方便GC回收;返回删除的元素。注意:从源码中结果,ArrayList 删除元素的时候并没有缩容。

  6.remove(Object o)删除指定元素值的元素

  删除指定元素值的元素,时间增加度为O(n)。

  /**

  * 从此列表中删除第一次出现的指定元素,

  * 如果存在。如果列表不包含该元素,则为

  * 不变。更正式地,删除具有最低索引的元素

  * i 使得

  * (o==null ? get(i)==null : o.equals(get(i)))

  *(如果存在这样的元素)。如果此列表,则返回 true

  * 包含指定的元素(或等效地,如果此列表

  * 因调用而更改)。

  * 删除指定元素值的元素,时间增加度为O(n)。

  * * @param o 要从此列表中删除的元素(如果存在)

  * 要报告列表中删除的元素(如果存在的话)

  * @return true 如果此列表包含指定的元素

  */

  public boolean remove(Object o) {

  if (o==null ) {

  // 遍历整个数组,找到元素第一次出现的位置,标记其快速删除

  for ( int index=0 ; index < size ; index++ )

  // 如果要删除的元素为null,则以null进行比较,使用== if (elementData[index]==null ) {

  快速删除(索引);

  返回 真;

  }

  } else {

  // 遍历整个数组,找到元素第一次出现的位置,标出其快速删除

  for ( int index=0 ; index < size ; index++)

  // 如果要删除的元素不为空,则进行比较,使用equals()方法

  if (o.equals(elementData[index])) {

  快速删除(索引);

  返回 真;

  }

  }

  返回 假;

  }

  /*

  * 跳过边界检查的私有删除方法并且不

  * 返回移除的值。

  * 专属的移除方法,最后检查检查,并且不会返回删除的值。

  */

  private void fastRemove( int index) {

  // 少了一个越界的检查

  modCount++;

  //如果index不是最后一位,则将index之后的元素往前挪位

  int numMoved=size - index - 1 ;

  如果(numMoved > 0 )

  System.arraycopy(elementData, index + 1 , elementData, index,

  numMoved);

  // 将最后一个元素删除,帮助GC

  elementData[-- size ]=null ; //清除让GC做它的工作

  }

  复制代码执行流程:找到第一个相同的元素值的元素;删除快速,进行数组的移动动;最后一个元素置为null,通过GC去处理

  其他运算

  7.retainAll(Collection c)两个求集合的交集\

  /**

  * 求两个集合的交集

  * 仅保留此列表中包含在

  * 指定集合。换句话说,从这个列表中删除所有

  * 不包含在指定集合中的元素。

  * * @param c 集合包含要保留在此列表中的元素

  * @return { @code true} 如果此列表因调用而更改

  * @throws ClassCastException 如果此列表的元素的类

  * 与指定的集合不兼容

  * 或者如果指定的集合为空

  * @see Collection#contains(Object)

  */

  public boolean removeAll(Collection c) {

  //集合c不能为空

  Objects.requireNonNull(c);

  //调用时装删除方法,元素

  填充cfalse,表示删除包含在中的return batchRemove(c, false );

  }

  复制代码与retainAll(Collection c)方法类似,只是这里保留不在c中的元素。\5.ArrayList所使用的toString()方法分析

  我们都知道ArrayList集合是可以直接使用toStrin()的,那么我们来挖方法一下ArrayList的toString如何实现的:

  在ArrayList源码中并没有直接的toString()方法,我们需要到其父类AbstractList的父类AbstractCollection中寻找:

  /**

  * 返回此集合的字符串表示形式。字符串

  * 表示由集合中的元素列表组成

  * 顺序由其迭代器返回,括在方括号中

  * ("[]")。相邻元素由字符分隔

  * ", "(逗号和空格)。元素被转换为字符串

  * 通过 {@link String#valueOf(Object)}。

  *

  * @return 这个集合的字符串表示

  */

  公共字符串 toString() {

  Iterator it=iterator(); //获取迭代器

  ,如果(it.hasNext()!)//如果为空直接返回

  返回 “[]” ;

  // StringBuilder 进行字符串排列

  StringBuilder sb=new StringBuilder();

  某人 追加( '[' );

  for (;;) { // 无限循环==while(true)

  E e=it. 下一个(); // 迭代器 next 方法取元素,绘制发光后移

  sb。追加(e==this ? "(this Collection)" : e); // 三元判断

  if (!it.hasNext())

  return sb. 追加( ']' ).toString(); //没有元素了,则拼接右括号

  某人。追加(',')。追加( '' ); // 还有元素存在

  }

  }

  复制代码6.关于ArrayList的问题(重点)1edList插入删除数据速度比ArrayList高吗?

  从受访者数据结构上说:

  ArrayList是实现了基于动态数组的数据结构LinkedList基于链表的数据结构。在尾部插入数据,数据量时LinkedList比较快,因为ArrayList要大扩容,当数据量快时ArrayList比较,因为ArrayList扩容是当前容量*1.5,大容量扩容一次会提供很多空间,当ArrayList不需容时效率比LinkedList高,因为直接元素扩展不需新节点在首部插入数据,LinkedList,因为LinkedList遍历插入更快速的位置读取时间,而ArrayList需要数组所有元素进行一次System.arraycopy插入位置越往中间,LinkedList插入效率越低,因为它遍历获取插入位置是从两头往中间搜索,索引越往中间遍历越久,因此ArrayList 插入效率可能会比LinkedList 高插入位置越往后,ArrayList 效率移向后,因为数组复制后的数据少了,System.arraycopy 就快了,因此在首部插入数据LinkedList 效率比ArrayList 高,需要增加数据ArrayList 效率比LinkedList 高LinkedList 可以实现这是,栈等数据结构,它的优势2.ArrayList 是线程安全的吗?

  因此,得出结论,ArrayList 非线程安全的集合!如果需要保证线程安全

  1.使用Vector集合,其是线程安全的,但是相对于ArrayList来说,效率比较低。

  而向量相对于数组列表是一个生命安全的,就在于其add()为集合添加元素的:

  // 可以引入Vector的add方法加上了synchronized 同步关键字

  public synchronized void addElement (E obj) {

  modCount++;

  ensureCapacityHelper(elementCount + 1 );

  elementData[elementCount++]=obj;

  }

  复制代码2.使用Collections.synchronizedList()

  List < String > list=new ArrayList<>();

  List < String >synchronizedList=Collections.synchronizedList(list);

  复制代码3.使用CopyOnWriteArrayList

  CopyOnWriteArrayList< Integer >cowaList=new CopyOnWriteArrayList< Integer >();

  复制代码注意:什么情况下不用给ArrayList加同步锁呢?

  ,在单线程情况下第一次加锁,为问题考虑!

  第二,当ArrayList作为局部变量的时候,不会加锁,因为局部变量是焦点,而我们上述例子作为成员变量来使用,成员变量的集合是需要所有线程共享的,这是需要加锁!

  3.如何复制一个ArrayList到另外一个ArrayList中去呢?你能几种?

  使用ArrayList构造方法,ArrayList(Collection c)

  使用addAll(Collection c)方法

  自己写循环去一个add()

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值