Skip to content

Commit f370fd8

Browse files
Finkykyyanglbme
andauthored
提交关于ArrayList的源码分析文章 (doocs#100)
* 提交关于ArrayList的源码分析文章 * Update ArrayList.md Co-authored-by: Yang Libin <contact@yanglibin.info>
1 parent 1c0fbfc commit f370fd8

3 files changed

Lines changed: 388 additions & 1 deletion

File tree

docs/JDK/collection/ArrayList.md

Lines changed: 388 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,388 @@
1-
努力编写中...
1+
# 一文直接带你吃透 ArrayList
2+
3+
> ArrayList 是日常开发中相当常见、面试也相当常考的一种 JDK 集合类,了解并熟悉、甚至能实现一个 ArrayList 对面试、提升自己编码功底大有益处。
4+
5+
## 一、写给小白 ArrayList 简单使用技巧
6+
7+
这部分是 ArrayList 的简单使用技巧,主要是介绍 ArrayList 的几个常见方法。
8+
9+
```java
10+
/**
11+
* 编写一个ArrayList的简单实用demo
12+
* ArrayList 的常见方法包括:
13+
* add(element):添加元素
14+
* get(index):获取下标元素
15+
* remove(index):移除下标对应元素
16+
* set(index,element):将index处的元素修改为element
17+
*/
18+
public class arrayList {
19+
public static void main(String[] args) {
20+
// 创建 ArrayList 的对象
21+
ArrayList al = new ArrayList();
22+
// 添加元素
23+
al.add("finky");
24+
// 构造随机数并进行添加
25+
Random rnd = new Random();
26+
for (int i = 0; i < 20; i++) {
27+
al.add(rnd.nextInt(1000));
28+
}
29+
// 取出ArrayList里的元素进行打印
30+
for (int i = 0; i < al.size(); i++) {
31+
System.out.print(al.get(i) + " ");
32+
}
33+
// 修改0号index成的元素为doocs
34+
System.out.println();
35+
al.set(0, "doocs");
36+
System.out.println(al.get(0));
37+
// 移除“doocs”元素
38+
al.remove(0);
39+
System.out.println(al.get(0));
40+
}
41+
}
42+
```
43+
44+
```java
45+
// 这是上面打印后的demo,可以看到第0处下标元素先是修改成了doocs,进行移除后,第0处下标元素变成了912
46+
finky 912 922 284 305 675 565 159 109 73 298 491 920 296 397 358 145 610 190 839 845
47+
doocs
48+
912
49+
```
50+
51+
## 二、ArrayList 的源码分析
52+
53+
我们来看看 ArrayList 的源码:
54+
55+
#### 1、来看看 ArrayList 的初始化:
56+
57+
```java
58+
// ArrayList 初始化时默认大小为10
59+
private static final int DEFAULT_CAPACITY = 10;
60+
61+
// 直接初始化的话一个空数组
62+
private static final Object[] EMPTY_ELEMENTDATA = {};
63+
64+
// 初始化ArrayList,传入初始化时的大小
65+
public ArrayList(int initialCapacity) {
66+
if (initialCapacity > 0) {
67+
this.elementData = new Object[initialCapacity];
68+
} else if (initialCapacity == 0) {
69+
this.elementData = EMPTY_ELEMENTDATA;
70+
} else {
71+
throw new IllegalArgumentException("Illegal Capacity: "+
72+
initialCapacity);
73+
}
74+
}
75+
// 如果不传入大小的话就默认大小是10,那么这里就有一个问题:我们上面插入的元素超过了10,继续插入元素就会进行拷贝扩容,性能不是特别高。所以我们一般情况下初始化时给定一个比较靠谱的数组大小,避免到时候导致元素不断拷贝
76+
public ArrayList() {
77+
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
78+
}
79+
80+
```
81+
82+
总结一下 ArrayList 初始化:我们创建 ArrayList 对象时,如果没有传入对应的大小,就会默认创建一个元素大小为 10 的数组,下次插入元素超过 10 时,会进行数组的拷贝扩容,这样性能消耗太高,所以建议就是在初始化时给定一个不要太小的容量大小。==
83+
84+
#### 2、 ArrayList 的 add 方法:
85+
86+
先上`add` 方法的代码:
87+
88+
```java
89+
public boolean add(E e) {
90+
ensureCapacityInternal(size + 1); // Increments modCount!!
91+
elementData[size++] = e;
92+
return true;
93+
}
94+
95+
public void add(int index, E element) {
96+
rangeCheckForAdd(index);
97+
ensureCapacityInternal(size + 1); // Increments modCount!!
98+
System.arraycopy(elementData, index, elementData, index + 1,
99+
size - index);
100+
elementData[index] = element;
101+
size++;
102+
}
103+
104+
public void add(E e) {
105+
checkForComodification();
106+
try {
107+
int i = cursor;
108+
ArrayList.this.add(i, e);
109+
cursor = i + 1;
110+
lastRet = -1;
111+
expectedModCount = modCount;
112+
} catch (IndexOutOfBoundsException ex) {
113+
throw new ConcurrentModificationException();
114+
}
115+
}
116+
117+
private void rangeCheck(int index) {
118+
if (index < 0 || index >= this.size)
119+
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
120+
}
121+
}
122+
```
123+
124+
![arraylist添加集合的方法](../../../images/JDK1.8/arraylist的add方法.png)
125+
126+
先判断当前数组元素是否满了,如果塞满了就会进行数组扩容,随后进行数组拷贝。
127+
128+
再然后插入元素,同时对应的 index++。
129+
130+
#### 3、瞧瞧 ArrayList 的 set 方法:
131+
132+
```java
133+
public E set(int index, E element) {
134+
rangeCheck(index);
135+
E oldValue = elementData(index);
136+
elementData[index] = element;
137+
return oldValue;
138+
}
139+
140+
public void set(E e) {
141+
if (lastRet < 0)
142+
throw new IllegalStateException();
143+
checkForComodification();
144+
145+
try {
146+
ArrayList.this.set(lastRet, e);
147+
} catch (IndexOutOfBoundsException ex) {
148+
throw new ConcurrentModificationException();
149+
}
150+
}
151+
```
152+
153+
1、先进行 index 判断是否越界,如果没有越界的话获取原来的旧的值
154+
155+
2、进行替换并返回该位置原来的旧的值
156+
157+
#### 4、ArrayList 的 get 方法:
158+
159+
```java
160+
public E get(int index) {
161+
rangeCheck(index);
162+
checkForComodification();
163+
return ArrayList.this.elementData(offset + index);
164+
}
165+
```
166+
167+
进行 index 是否越界的判断,然后去取对应下标的值。
168+
169+
#### 5、ArrayList 的 remove 方法:
170+
171+
```java
172+
public void remove() {
173+
if (lastRet < 0)
174+
throw new IllegalStateException();
175+
checkForComodification();
176+
177+
try {
178+
ArrayList.this.remove(lastRet);
179+
cursor = lastRet;
180+
lastRet = -1;
181+
expectedModCount = modCount;
182+
} catch (IndexOutOfBoundsException ex) {
183+
throw new ConcurrentModificationException();
184+
}
185+
}
186+
187+
public E remove(int index) {
188+
// 进行index是否越界的判断
189+
rangeCheck(index);
190+
checkForComodification();
191+
E result = parent.remove(parentOffset + index);
192+
this.modCount = parent.modCount;
193+
this.size--;
194+
return result;
195+
}
196+
197+
public E remove(int index) {
198+
rangeCheck(index);
199+
modCount++;
200+
E oldValue = elementData(index);
201+
int numMoved = size - index - 1;
202+
if (numMoved > 0)
203+
System.arraycopy(elementData, index+1, elementData, index,
204+
numMoved);
205+
elementData[--size] = null;
206+
return oldValue;
207+
}
208+
```
209+
210+
![arrayList删除元素的过程.png](../../../images/JDK1.8/arrayList删除元素的过程.png)
211+
212+
1、先进行下标是否越界的判断,获取 index 处的元素值(这是要删除的值)
213+
214+
2、然后进行元素拷贝,把 index 后面的元素往前拷贝
215+
216+
#### 6、关于 ArrayList 动态扩容和数组拷贝:
217+
218+
```java
219+
private void ensureCapacityInternal(int minCapacity) {
220+
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
221+
}
222+
223+
private void ensureExplicitCapacity(int minCapacity) {
224+
modCount++;
225+
if (minCapacity - elementData.length > 0)
226+
grow(minCapacity);
227+
}
228+
229+
private void grow(int minCapacity) {
230+
// overflow-conscious code
231+
int oldCapacity = elementData.length;
232+
// 扩容的代码:这里做了位运算,相当于数组扩容了1.5倍
233+
int newCapacity = oldCapacity + (oldCapacity >> 1);
234+
if (newCapacity - minCapacity < 0)
235+
newCapacity = minCapacity;
236+
if (newCapacity - MAX_ARRAY_SIZE > 0)
237+
newCapacity = hugeCapacity(minCapacity);
238+
// 随后进行元素拷贝
239+
elementData = Arrays.copyOf(elementData, newCapacity);
240+
}
241+
242+
```
243+
244+
现在假定场景:arraylist 中已经有 10 个元素类,要放第 11 个元素。
245+
246+
此时进行容量检测,出现问题:空间大小不够。
247+
248+
解决方法:此时进行数组扩容右位移 1(**相当于总容量多加 1.5 倍**)扩容,老的大小+老大小的一半,进行元素拷贝
249+
250+
## 三、来仿照 JDK 源码写一个自己的 ArrayList 把
251+
252+
```java
253+
public class OwnArrayList<E> {
254+
private E data[];
255+
private int size;
256+
257+
public OwnArrayList(int capacity) {
258+
data = (E[]) new Object[capacity];
259+
size = 0;
260+
}
261+
// 初始化是默认设置大小为20
262+
public OwnArrayList() {
263+
this(20);
264+
}
265+
266+
// 获取数组容量
267+
public int getCapacity() {
268+
return data.length;
269+
}
270+
271+
// 获取数组元素个数
272+
public int getSize() {
273+
return size;
274+
}
275+
276+
// 判断数组是否为空
277+
public boolean isEmpity() {
278+
return size == 0;
279+
}
280+
281+
// 获取index索引位置的元素
282+
public E get(int index) {
283+
if (index < 0 || index >= size)
284+
throw new IllegalArgumentException("add failed,the index should >= 0 or <= size");
285+
return data[index];
286+
}
287+
288+
// 修改index索引位置的元素为e
289+
public void set(int index, E e) {
290+
if (index < 0 || index >= size)
291+
throw new IllegalArgumentException("add failed,the index should >= 0 or <= size");
292+
data[index] = e;
293+
}
294+
295+
// 在数组中间插入一个元素
296+
public void add(int index, E element) {
297+
if (size == data.length) {
298+
throw new IllegalArgumentException("AddLast failed,array has already full");
299+
}
300+
if (index < 0 || index > size) {
301+
throw new IllegalArgumentException("add failed,the index should >= 0 or <= size");
302+
}
303+
for (int i = size - 1; i >= index; i--) {
304+
data[i + 1] = data[i];
305+
}
306+
data[index] = element;
307+
size++;
308+
}
309+
310+
// 向数组元素末尾添加一个元素
311+
public void addLast(E element) {
312+
add(size,element);
313+
}
314+
315+
// 在数组头部插入一个元素
316+
public void addFirst(E element) {
317+
add(0, element);
318+
}
319+
320+
// 判断是否含有元素
321+
public boolean contains(E e) {
322+
for (int i = 0; i < size; i++)
323+
if (data[i] == e)
324+
return true;
325+
return false;
326+
}
327+
328+
// 查找元素e的位置
329+
public int find(E e) {
330+
for (int i = 0; i < size; i++) {
331+
if (data[i] == e) {
332+
return i;
333+
}
334+
}
335+
return -1;
336+
}
337+
338+
// 删除index位置的元素
339+
public E remove(int index) {
340+
if (index < 0 || index > size) {
341+
throw new IllegalArgumentException("index should be 0 to size");
342+
}
343+
E remove_element = data[index];
344+
for (int i = index + 1; i < size; i++) {
345+
data[i - 1] = data[i];
346+
}
347+
size--;
348+
return remove_element;
349+
}
350+
351+
// 删除末尾元素
352+
// 注意:这是逻辑删除,但是size的大小已经做了相应的减少,所以从实际意义上我们外界并不能访问到末尾元素的值
353+
public E removelast() {
354+
return remove(size - 1);
355+
}
356+
357+
// 删除开头元素
358+
public E removeFirst() {
359+
return remove(0);
360+
}
361+
362+
// 将数组空间的容量变成newCapacity大小
363+
private void resize(int newCapacity) {
364+
newCapacity = getCapacity()*2;
365+
E[] newData = (E[]) new Object[newCapacity];
366+
for (int i = 0; i < size; i++)
367+
newData[i] = data[i];
368+
data = newData;
369+
}
370+
}
371+
```
372+
373+
## 四、面试时关于 ArrayList 要说的事
374+
375+
如果有人问你 ArrayList 知多少,我觉得可以从这几个方面出发:
376+
377+
ArrayList 的底层是基于数组进行的,进行随机位置的插入和删除、以及扩容时性能很差,但进行随机的读和取时速度却很快。
378+
379+
接着可以从源码的角度分析 add、remove、set、get、数组扩容拷贝的过程场景。
380+
381+
最后也是特别重要的一点,就是要积极掌握主动性,延伸出 LinkedList 的特点、源码、两者间的对比等。
382+
383+
注:当需要动态数组时我们通常使用 ArrayList 而不是使用类似的 vector,这里有一点说明一下,就是尽管 Vector 的方法都是线程安全的,但其在单线程下需要花费的时间更多,而 ArrayList 尽管不是线程安全的,但其花费的时间很少。
384+
385+
## 终:参考资料
386+
387+
1. JDK 集合框架 ArrayList 源码
388+
2. 《Core.Java.Volume.I.Fundamentals.11th.Edition》
35 KB
Loading
56.6 KB
Loading

0 commit comments

Comments
 (0)