前言
点赞在看,养成习惯。
点赞收藏,人生辉煌。
点击关注【微信搜索公众号:编程背锅侠】,第一时间获得最新文章。
看源码血泪史
刚开始工作面试的时候,面试官经常问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的Iterator方法时竟然给我报ConcurrentModificationException异常,撸ta
ArrayList中的添加方法总结
方法名描述
public boolean add(E e)将指定的元素追加到此列表的末尾。public void add(int index, E element)在此列表中的指定位置插入指定的元素。public boolean addAll(Collection<? extends E> c)按指定集合的Iterator返回的顺序将指定集合中的所有元素 追加到此列表的末尾。public boolean addAll(i nt index, Collection<? extends E> c)将指定集合中的所有元素插入到此列表中,从指定的位置 开始。
public boolean add(E e) 添加单个元素
案例演示
@Test
public void test_add(){
ArrayList
<String> list
= new ArrayList<>();
list
.add("洛洛");
}
源码分析
public boolean add(E e
) {
ensureCapacityInternal(size
+ 1);
elementData
[size
++] = e
;
return true;
}
private static int calculateCapacity(Object
[] elementData
, int minCapacity
) {
if (elementData
== DEFAULTCAPACITY_EMPTY_ELEMENTDATA
) {
return Math
.max(DEFAULT_CAPACITY
, minCapacity
);
}
return minCapacity
;
}
private void ensureCapacityInternal(int minCapacity
) {
ensureExplicitCapacity(calculateCapacity(elementData
, minCapacity
));
}
private void ensureExplicitCapacity(int minCapacity
) {
modCount
++;
if (minCapacity
- elementData
.length
> 0)
grow(minCapacity
);
}
底层数组elementData中元素变化图解
添加2个元素以后底层数组中的元素 添加第三个元素查看究竟是如何添加的?
总结
使用该方法的话将导致指定位置后面的数组元素全部重新移动,即往后移动一位1)确保数插入的位置小于等于当前数组长度,并且不小于0,否则抛出异常2)确保数组已使用长度(size)加1之后足够存下 下一个数据3)修改次数(modCount)标识自增1,如果当前数组已使用长度(size)加1后的大于当前的数组长度,则调用grow方法,增长数组4)grow方法会将当前数组的长度变为原来容量的1.5倍。5)确保有足够的容量之后,使用System.arraycopy 将需要插入的位置(index)后面的元素统统往后移动一位。6)将新的数据内容存放到数组的指定位置(index)上
public void add(int index, E element) 在指定索引处添加元素
案例演示
@Test
public void test_add_c(){
ArrayList
<String> list
= new ArrayList<>();
list
.add("洛洛00");
list
.add("洛洛01");
list
.add(1, "洛洛05");
list
.forEach(System
.out
::println
);
}
源码分析
public void add(int index
, E element
) {
rangeCheckForAdd(index
);
ensureCapacityInternal(size
+ 1);
System
.arraycopy(elementData
, index
, elementData
, index
+ 1,
size
- index
);
elementData
[index
] = element
;
size
++;
}
private void rangeCheckForAdd(int index
) {
if (index
> size
|| index
< 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index
));
}
private static int calculateCapacity(Object
[] elementData
, int minCapacity
) {
if (elementData
== DEFAULTCAPACITY_EMPTY_ELEMENTDATA
) {
return Math
.max(DEFAULT_CAPACITY
, minCapacity
);
}
return minCapacity
;
}
private void ensureCapacityInternal(int minCapacity
) {
ensureExplicitCapacity(calculateCapacity(elementData
, minCapacity
));
}
private void ensureExplicitCapacity(int minCapacity
) {
modCount
++;
if (minCapacity
- elementData
.length
> 0)
grow(minCapacity
);
}
底层数组elementData中元素变化图解
指定位置添加元素前数组拷贝的准备 指定位置添加元素数组拷贝后的数组
结论
将指定的元素插入此列表中的指定位置,并将当前处于该位置的元素(如果有的话)和随后的任何元素向右移动(在其索引中增加一个)。
public boolean addAll(Collection<? extends E> c)` 将集合的所有元素一次性添加到集合
案例演示
@Test
public void test_addAll(){
ArrayList
<String> list
= new ArrayList<>();
list
.add("洛洛00");
list
.add("洛洛01");
list
.add("洛洛02");
list
.add("洛洛03");
list
.add("洛洛04");
ArrayList
<String> all
= new ArrayList<>();
all
.add("洛洛06");
all
.addAll(list
);
all
.forEach(System
.out
::println
);
}
源码分析
public boolean addAll(Collection
<? extends E> c
) {
Object
[] a
= c
.toArray();
int numNew
= a
.length
;
ensureCapacityInternal(size
+ numNew
);
System
.arraycopy(a
, 0, elementData
, size
, numNew
);
size
+= numNew
;
return numNew
!= 0;
}
private static int calculateCapacity(Object
[] elementData
, int minCapacity
) {
if (elementData
== DEFAULTCAPACITY_EMPTY_ELEMENTDATA
) {
return Math
.max(DEFAULT_CAPACITY
, minCapacity
);
}
return minCapacity
;
}
private void ensureCapacityInternal(int minCapacity
) {
ensureExplicitCapacity(calculateCapacity(elementData
, minCapacity
));
}
private void ensureExplicitCapacity(int minCapacity
) {
modCount
++;
if (minCapacity
- elementData
.length
> 0)
grow(minCapacity
);
}
底层数组elementData中元素变化图解
添加前数组中的元素 添加后的数组中的元素
结论
该方法是将指定的集合添加到集合的尾部。
public boolean addAll(int index, Collection<? extends E> c) 在指定的索引位置添加集合
案例演示
@Test
public void test_addAll_c(){
ArrayList
<String> list
= new ArrayList<>(2);
list
.add("洛洛00");
list
.add("洛洛01");
list
.add("洛洛02");
list
.add("洛洛03");
list
.add("洛洛04");
list
.add("洛洛05");
ArrayList
<String> all
= new ArrayList<>();
all
.add("洛洛06");
all
.add("洛洛07");
all
.add("洛洛08");
all
.addAll(1, list
);
all
.forEach(System
.out
::println
);
}
源码分析
public boolean addAll(int index
, Collection
<? extends E> c
) {
rangeCheckForAdd(index
);
Object
[] a
= c
.toArray();
int numNew
= a
.length
;
ensureCapacityInternal(size
+ numNew
);
int numMoved
= size
- index
;
if (numMoved
> 0)
System
.arraycopy(elementData
, index
, elementData
, index
+ numNew
,
numMoved
);
System
.arraycopy(a
, 0, elementData
, index
, numNew
);
size
+= numNew
;
return numNew
!= 0;
}
private void rangeCheckForAdd(int index
) {
if (index
> size
|| index
< 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index
));
}
private static int calculateCapacity(Object
[] elementData
, int minCapacity
) {
if (elementData
== DEFAULTCAPACITY_EMPTY_ELEMENTDATA
) {
return Math
.max(DEFAULT_CAPACITY
, minCapacity
);
}
return minCapacity
;
}
private void ensureCapacityInternal(int minCapacity
) {
ensureExplicitCapacity(calculateCapacity(elementData
, minCapacity
));
}
private void ensureExplicitCapacity(int minCapacity
) {
modCount
++;
if (minCapacity
- elementData
.length
> 0)
grow(minCapacity
);
}
底层数组elementData中元素变化图解
指定位置添加数组前的准备 指定位置添加数组后新的数组
结论
该方法是在指定位置添加一个集合。通过上面的两张图里面的elementData数组中的元素变化我们可以很清晰的了解是怎样将元素在数组中添加集合的。
grow扩容方法
private void grow(int minCapacity
) {
int oldCapacity
= elementData
.length
;
int newCapacity
= oldCapacity
+ (oldCapacity
>> 1);
if (newCapacity
- minCapacity
< 0)
newCapacity
= minCapacity
;
if (newCapacity
- MAX_ARRAY_SIZE
> 0)
newCapacity
= hugeCapacity(minCapacity
);
elementData
= Arrays
.copyOf(elementData
, newCapacity
);
}
hugeCapacity方法
private static int hugeCapacity(int minCapacity
)
if (minCapacity
< 0)
throw new OutOfMemoryError();
return (minCapacity
> MAX_ARRAY_SIZE
) ?
Integer
.MAX_VALUE
:
MAX_ARRAY_SIZE
;
}
Arrays.copyOf方法
ArrayList.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
);
针对上面的方法案例分析
不指定位置
arraycopy执行后添加到尾部
指定位置
arraycopy执行后拷贝的是插入位置后的元素,拷贝到目标数组,操作的还是一个数组
JDK`版本
源码以及案例演示都是在jdk1.8环境下。
创作不易, 非常欢迎大家的点赞、评论和关注(^_−)☆ 你的点赞、评论以及关注是对我最大的支持和鼓励,而你的支持和鼓励 我继续创作高质量博客的动力 !!!