源码那些事超详细的ArrayList底层源码+经典面试题
Posted 温文艾尔
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了源码那些事超详细的ArrayList底层源码+经典面试题相关的知识,希望对你有一定的参考价值。
🙏相见即是有缘,如果对你有帮助,给博主一个免费的点赞以示鼓励把QAQ☺
这里是温文艾尔の源码学习之路
- 🙏作者水平欠佳,如果发现任何错误,欢迎批评指正
- 👋博客主页 温文艾尔の学习小屋
- 👋更多文章请关注温文艾尔主页
- 👋超详细ArrayList解析!
- 👋更多文章:
- 👋HashMap底层源码解析上(超详细图解+面试题)
- 👋HashMap底层源码解析下(超详细图解)
- 👋HashMap底层红黑树原理(超详细图解)+手写红黑树代码
文章目录
- 👋ArrayList底层源码
- 👋1 ArrayList继承关系
- 👋1.1 Cloneable的底层实现
- 👋1.2 RandomAccess标记接口
- 👋2 ArrayList构造方法源码解析
- 👋2.1 ArrayList()构造方法
- 👋2.2 ArrayList(int initialCapacity)构造方法
- 👋2.3 ArrayList(Collection<? extends E> c)构造方法
- 👋3 ArrayList基础方法
- 👋3.05 ArrayList中的各个变量
- 👋3.1添加方法
- 👋3.1.1 add(E e)
- 👋3.1.2 add(int index, E element)
- 👋3.1.3 addAll(Collection<? extends E> c)
- 👋3.1.4 addAll(int index, Collection<? extends E> c)
- 👋3.1.5 set(int index, E element)
- 👋3.1.6 get(int index)
- 👋3.1.7 remove(Object o)
- 👋3.1.8 clear()
- 👋4 扩展
- 👋4.1 转换方法toString()
- 👋4.2 迭代器Iterator iterator()
- 👋5 ArrayList相关面试题
- 👋5.1 ArrayList是如何扩容的
- 👋5.2 ArrayList频繁扩容导致添加性能急剧下降,如何处理?
- 👋5.3 ArrayList插入或删除元素一定比LinkedList慢吗
- 👋5.4 ArrayList是线程安全的吗
- 👋5.5 如何复制某个ArrayList到另一个ArrayList中去
- 👋5.6 已知成员变量集合存储N多用户名称,在多线程的环境下,使用迭代器在读取集合数据的同时如何保证还可以正常写入数据到集合中
- 👋5.7 ArrayList和LinkedList区别
👋ArrayList底层源码
ArrayList的类图示:
- ArrayList集合介绍
- List底层基于
动态数组
实现
- List底层基于
- 数组结构介绍
增删慢
:每次删除元素都需要更改数组长度,拷贝以及移动元素位置查询快
:由于数组在内存中是一块连续空间
,因此可以根据地址+索引
的方式快速获取对应位置上的元素
👋1 ArrayList继承关系
Serializable标记性接口
介绍 类的序列化由实现java.io.Serializable
接口的类启用。不实现此接口的类将不会使任何状态序列化
或反序列化
。可序列化类的所有子类型都是可序列化的,序列化接口没有方法或字段,仅用于标识可串行化的语义
- 序列化:将对象的数据写入到文件(写对象)
- 反序列化:将文件中对象的数据读取出来(读对象)
Cloneable标记性接口
- 介绍 一个类实现Cloneable接口来指示
Object.clone()
方法,该方法对于该类的实例进行字段的复制是合法的。在不实现Cloneable接口的实例上调用对象的克隆方法会导致异常CloneNotSupportedException
被抛出。简言之:克隆就是依据已有的数据,创造一份新的完全一样的数据拷贝 - 克隆的前提条件
- 被克隆对象所在的类必须
实现Cloneable接口
- 必须重写
clone()
方法
- 被克隆对象所在的类必须
Serializable接口
- 由List实现使用,以表明它们支持
快速
(通常为恒定时间)随机访问
- 此接口的主要目的是允许算法更改其行为,以便在应用于随机访问列表或顺序访问列表时提供良好的性能
👋1.1 Cloneable的底层实现
/**
* Returns a shallow copy of this <tt>ArrayList</tt> instance. (The
* elements themselves are not copied.)
*
* @return a clone of this <tt>ArrayList</tt> instance
*/
public Object clone()
try
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
catch (CloneNotSupportedException e)
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
可以看到通过调用clone()方法,返回了一个ArrayList对象
v.elementData = Arrays.copyOf(elementData, size);
是我们能完成复制的关键,点进去
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType)
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
我们原来数组中的数据通过System.arraycopy方法被拷贝到copy数组中,并将这个数组返回
克隆牵扯到浅拷贝和深拷贝,这里简述一下浅拷贝
和深拷贝
的区别
首先创建一个Student类
public class Student implements Cloneable
private String name;
private Integer age;
public Student()
public Student(String name, Integer age)
this.name = name;
this.age = age;
public String getName()
return name;
public void setName(String name)
this.name = name;
public Integer getAge()
return age;
public void setAge(Integer age)
this.age = age;
@Override
public Object clone() throws CloneNotSupportedException
return super.clone();
@Override
public String toString()
return "Student" +
"name='" + name + '\\'' +
", age=" + age +
'';
浅拷贝实例
package ArrayListProject.CloneTest;
import java.util.ArrayList;
import java.util.List;
/**
* Description
* User:
* Date:
* Time:
*/
public class Test
public static void main(String[] args)
ArrayList list = new ArrayList();
Student stu = new Student("zs",18);
list.add("123");
list.add(stu);
System.out.println("未拷贝之前的数组:"+list);
ArrayList clone = (ArrayList)list.clone();
System.out.println("拷贝之后的数组:"+clone);
System.out.println("两个数组地址是否相等:"+(list==clone));
System.out.println("两个数组中的stu对象是否相等:");
System.out.println("list中的stu对象:"+list.get(1));
System.out.println("修改stu对象之前,clone中的stu对象:"+clone.get(1));
Student stu1 = (Student)list.get(1);
stu1.setName("ls");
System.out.println("修改stu对象之后,clone中的stu对象:"+clone.get(1));
System.out.println("两者stu对象的地址是否相同"+(list.get(1)==clone.get(1)));
未拷贝之前的数组:[123, Studentname='zs', age=18]
拷贝之后的数组:[123, Studentname='zs', age=18]
两个数组地址是否相等:false
两个数组中的stu对象是否相等:
list中的stu对象:Studentname='zs', age=18
修改stu对象之前,clone中的stu对象:Studentname='zs', age=18
修改stu对象之后,clone中的stu对象:Studentname='ls', age=18
两者stu对象的地址是否相同true
由此我们得出
- 克隆之后的对象是一个新对象,
list==clone为false
可知二者地址不相同 - 由list.get(1)==clone.get(1)为true可知,两者中的
stu对象为同一对象
- 修改list中的stu对象,clone中的stu对象也会随之改变可知,克隆的是stu
对象的地址
,并没有创建新的对象,它仅仅是拷贝了一份引用地址 - 基本数据类型可以达到
完全复制
,而引用数据类型则不可以
深拷贝和浅拷贝不同,深拷贝中拷贝的对象是一个完全新的对象,他拷贝的并不是引用地址,而是实实在在的创建了一个对象
👋1.2 RandomAccess标记接口
- 由List实现使用,以表明它们支持快速(通常为恒定时间)随机访问
- 此接口的主要目的是允许算法更改其行为,以便在应用于随机访问列表或顺序访问列表时提供良好的性能
因此List实现RandomAccess
在执行随机访问
时,性能会比顺序访问更快
public interface RandomAccess
我们通过一个案例来证明
我们以1000000个数据作为测试
package ArrayListProject.CloneTest;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Test02
public static void main(String[] args)
//创建ArrayList集合
List<String> list = new ArrayList<>();
//添加100W条数据
for (int i = 0; i < 1000000; i++)
list.add(i+"a");
//测试随机访问时间
long startTime = System.currentTimeMillis();
for (int i = 0; i < list.size(); i++)
//取出集合的每一个元素
list.get(i);
long endTime = System.currentTimeMillis();
System.out.println("随机访问耗时:"+(endTime-startTime));
//测试顺序访问时间
long startTime2 = System.currentTimeMillis();
Iterator<String> it = list.iterator();
while (it.hasNext())
it.next();
long endTime2 = System.currentTimeMillis();
System.out.println("顺序访问耗时:"+(endTime2-startTime2));
随机访问耗时:3
顺序访问耗时:4
由此可见,在数据量极大的情况下,ArrayList随机访问的效率远高于顺序访问
而LinkedList的数据结构是链表,且未实现RandomAccess接口,他的效率和ArrayList相比如何呢,我们来做一个
测试
package ArrayListProject.CloneTest;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
public class Test03
public static void main(String[] args)
List<String> list1 = new LinkedList<>();
for (int i = 0; i < 100000; i++)
list1.add(i+"a");
//测试随机访问时间
long startTime = System.currentTimeMillis();
for (int i = 0; i < list1.size(); i++)
//取出集合的每一个元素
list1.get(i);
long endTime = System.currentTimeMillis();
System.out.println("随机访问耗时:"+(endTime-startTime));
//测试顺序访问时间
long startTime2 = System.currentTimeMillis();
Iterator<String> it = list1.iterator();
while (it.hasNext())
it.next();
long endTime2 = System.currentTimeMillis();
System.out.println("顺序访问耗时:"+(endTime2-startTime2));
随机访问耗时:11676
顺序访问耗时:4
由此可见,没有实现RandomAccess接口的LinkedList集合的随机访问速度远远小于顺序访问
👋2 ArrayList构造方法源码解析
Constructor | Constructor描述 |
---|---|
ArrayList() | 构造一个初始容量为10的空列表 |
ArrayList(int initialCapacity) | 构造具有指定初始容量的空列表 |
ArrayList(Collection<? extends E> c) | 构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序 |
👋2.1 ArrayList()构造方法
/**
* Constructs an empty list with an initial capacity of ten.
*/
//构造一个初始容量为10的数组
//DEFAULTCAPACITY_EMPTY_ELEMENTDATA:默认的空容量的数组
//elementData:集合真正存储数据的容器
public ArrayList()
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
👋2.2 ArrayList(int initialCapacity)构造方法
/**
* Constructs an empty list with the specified initial capacity.
*
* @param initialCapacity the initial capacity of the list
* @throws IllegalArgumentException if the specified initial capacity
* is negative
*/
public ArrayList(int initialCapacity)
if (initialCapacity > 0)
//如果传进来的变量大于0,则初始化一个指定容量的空数组
this.elementData = new Object[initialCapacity];
else if (initialCapacity == 0)
//传进来的变量=0,则不去创建新的数组,直接将已创建的EMPTY_ELEMENTDATA空数组传给
//ArrayList
this.elementData = EMPTY_ELEMENTDATA;
else
//传进来的指定数组容量不能<0
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
👋2.3 ArrayList(Collection<? extends E> c)构造方法
/**
* Constructs a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
*
* @param c the collection whose elements are to be placed into this list
* @throws NullPointerException if the specified collection is null
*/
public ArrayList(Collection<? extends E> c)
//将构造方法中的参数转换成数组形式,其底层是调用了System.arraycopy()
elementData = c.toArray();
//将数组的长度赋值给size
if ((size = elementData.length) != 0)
//检查elementData是不是Object[]类型,不是的话将其转换成Object[].class类型
if (elementData.getClass() != Object[].class)
//数组的创建与拷贝
elementData = Arrays.copyOf(elementData, size, Object[].class);
else
//size为0,则把已创建好的空数组直接给它
this.elementData = EMPTY_ELEMENTDATA;
👋3 ArrayList基础方法
👋3.05 ArrayList中的各个变量
//长度为0的空数组
private static final Object[] EMPTY_ELEMENTDATA = ;
//默认容量为0的空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = ;
//集合存元素的数组
transient Object[] elementData;
//集合的长度
private int size;
//集合默认容量
private static final int DEFAULT_CAPACITY = 10;
👋3.1添加方法
方法名 | 描述 |
---|---|
add(E e) | 将指定的元素追加到此列表的末尾 |
add(int index, E element) | 在此列表中的指定位置插入指定的元素 |
addAll(Collection<? extends E> c) | 按指定集合的Iterator返回的顺序将指定集合中的所有元素追加到此列表的末尾 |
addAll(int index, Collection<? extends E> c) | 将指定集合中的所有元素插入到此列表中,从指定的位置开始 |
👋3.1.1 add(E e)
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by @link Collection#add)
*/
//将指定的元素追加到此列表的末尾
public boolean add(E e)
//每增加1个元素,数组所需容量+1,并检查增加数组容量后是否要扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
//添加元素
elementData[size++] = e;
return true;
private void ensureCapacityInternal(int minCapacity)
//如果是默认的DE以上是关于源码那些事超详细的ArrayList底层源码+经典面试题的主要内容,如果未能解决你的问题,请参考以下文章