前言
点赞在看,养成习惯。
点赞收藏,人生辉煌。
点击关注【微信搜索公众号:编程背锅侠】,第一时间获得最新文章。
看源码血泪史
刚开始工作面试的时候,面试官经常问ArrayList源码相关的问题,基本上都是这部分很快结束战斗。
面试官:你看过ArrayList的源码吗?我:你肯定会说看过呀。面试官:那你来讲讲你对ArrayList源码的理解吧。我:底层的数据结构是object数组;增删快、查询慢等等,没说几句就完了。
其实看了ArrayList的源码以后,你会发现能说的点还是有很多的。 比如ArrayList的构造方法的底层数组真的是构造了一个长度为10的数组吗? Arrays.copy方法,grow扩容方法是怎么扩容的?等等都可以细说。 ArrayList的源码从工作到现在大概看了不下10遍,这其中包括看了半道放弃的。 刚开始看源码是在一些博客网站上看,看的稀里糊涂不是很明白,越看越想放弃。 后面看了一些公开课,跟着老师讲的视频看源码,看完之后感觉有点意思。但是看完之后,自己单独看还是有点吃力。 2020年4月份的时候看了一遍ArrayList源码并且每行都做了注释,整理在了有道上。 现在是七月初时隔两个月在再次看源码发现以前的笔记有部分是模糊、或者理解不正确的。 目前我发布出来的ArrayList源码是我一步一步DEBUG调试验证的源码。如果理解有问题看过之后,还请多多指教。
ArrayList系列文章
第一篇:ArrayList中的构造方法源码在面试中被问到了…抱歉没准备好!!!告辞 第二篇:面试官让我讲ArrayList中add、addAll方法的源码…我下次再来 第三篇:工作两年还没看过ArrayList中remove、removeAll、clear方法源码的都来报道吧 第四篇: 乱披风锤法锤炼ArrayList源码中的get、set、contains、isEmpty方法!!!肝起来
ArrayList集合底层数据结构
ArrayList集合介绍
List 接口的可调整大小的数组实现。
数组:一旦初始化长度就不可以发生改变 。
数组结构介绍
增删慢:每次删除元素,都需要更改数组长度、拷贝以及移动元素位置。
查询快:由于数组在内存中是一块连续空间,因此可以根据地址+索引的方式快速获取对应位置上的元素。
源码中定义量
默认的初始化容量
private static final int DEFAULT_CAPACITY
= 10;
空数组没有默认容量
private static final Object
[] EMPTY_ELEMENTDATA
= {};
默认容量的空数组
private static final Object
[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA
= {};
集合真正存储数组元素的数组。ArrayList的底层数据结构,transient表示该字段不进行序列化操作
transient Object
[] elementData
;
ArrayList的大小,就是集合中元素的个数
private int size
;
构造方法表格
ConstructorConstructor描述
ArrayList()构造一个初始容量为十的空列表。ArrayList(int initialCapacity)构造具有指定初始容量的空列表。ArrayList(Collection<? extends E> c)构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序。
空参构造ArrayList()
源码解析
public ArrayList() {
this.elementData
= DEFAULTCAPACITY_EMPTY_ELEMENTDATA
;
}
案例演示
@Test
public void test_get(){
List
<Integer> list
= new ArrayList<>();
}
验证是否构造一个初始容量为10的集合
@Test
public void test_get(){
try {
List
<String> list
= new ArrayList<>();
Field field
= list
.getClass().getDeclaredField("elementData");
field
.setAccessible(true);
int size
= ((Object
[]) field
.get(list
)).length
;
System
.out
.println(size
);
list
.add("1");
Field field2
= list
.getClass().getDeclaredField("elementData");
field2
.setAccessible(true);
int size2
= ((Object
[]) field2
.get(list
)).length
;
System
.out
.println(size2
);
} catch (Exception e
) {
e
.printStackTrace();
}
}
结论:通过以上的代码演示证明空参构造方法创建集合对象并未构造一个初始容量为十的空列表,仅仅将DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的地址赋值给elementData。只有添加第一个元素的时候才会将容量初始化为10
指定容量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("Illegal Capacity: "+ initialCapacity
);
}
}
代码演示
@Test
public void test_c(){
List
<Integer> list
= new ArrayList<>(5);
}
验证是否构造一个我们指定初始容量5的集合
@Test
public void test_get(){
try {
List
<String> list
= new ArrayList<>(5);
Field field
= list
.getClass().getDeclaredField("elementData");
field
.setAccessible(true);
int size
= ((Object
[]) field
.get(list
)).length
;
System
.out
.println(size
);
} catch (Exception e
) {
e
.printStackTrace();
}
}
结论:根据 ArrayList 构造方法参数创建指定长度的数组。
按照集合迭代器返回的顺序,构造一个list包含指定集合的元素。c参数:元素将被放到list中的集合指定的集合为null,将会抛出NullPointerException
指定集合ArrayList(Collection<? extends E> c)
源码解析
public ArrayList(Collection
<? extends E> c
) {
elementData
= c
.toArray();
if ((size
= elementData
.length
) != 0) {
if (elementData
.getClass() != Object
[].class)
elementData
= Arrays
.copyOf(elementData
, size
, Object
[].class);
} else {
this.elementData
= EMPTY_ELEMENTDATA
;
}
}
toArray()方法及其实现源码
Object
[] toArray();
public Object
[] toArray() {
return Arrays
.copyOf(elementData
, size
);
}
Arrays类中的copyOf方法源码
public static <T> T
[] copyOf(T
[] original
, int newLength
) {
return (T
[]) copyOf(original
, newLength
, original
.getClass());
}
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
;
}
arraycopy方法
public static native void arraycopy(Object src
, int srcPos
,
Object dest
, int destPos
,
int length
);
测试elementData.getClass()到底是什么类型
@Test
public void test_c(){
List
<Integer> list
= new ArrayList<>();
list
.add(1);
Class
<? extends Object[]> aClass
= list
.toArray().getClass();
System
.out
.println(aClass
);
if (aClass
!= Object
[].class){
System
.out
.println(false);
}else {
System
.out
.println(true);
}
}
上面分析了半天的Arrays.copyOf方法接下来验证一下
@Test
public void test_arrays_copy_of(){
Object
[] str
= new Object[]{"1", "2"};
Object
[] arr_
= Arrays
.copyOf(str
, 1);
System
.out
.println(Arrays
.toString(arr_
));
Object
[] str0
= new Object[]{"3", "4"};
Object
[] arr0_
= Arrays
.copyOf(str0
, 2);
System
.out
.println(Arrays
.toString(arr0_
));
String
[] str1
= new String[]{"5", "6"};
String
[] arr1_
= Arrays
.copyOf(str1
, 5);
System
.out
.println(Arrays
.toString(arr1_
));
}
创作不易, 非常欢迎大家的点赞、评论和关注(^_−)☆ 你的点赞、评论以及关注是对我最大的支持和鼓励,而你的支持和鼓励 我继续创作高质量博客的动力 !!!