Skip to content

Commit 67c8e52

Browse files
committed
change image
1 parent 029a406 commit 67c8e52

15 files changed

Lines changed: 720 additions & 565 deletions

docs/ArrayList.md

Lines changed: 91 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
下面是主要是自己看了很多Java容器类相关的博客,以及很多面经中涉及到的Java容器相关的面试题后,自己全部手写的解答,也花了一些流程图,之后会继续更新这一部分。
44

55
#### [1.ArrayList与LinkedList的区别是什么?](#ArrayList与LinkedList的区别是什么?)
6+
67
#### [2.怎么使ArrayList,LinkedList变成线程安全的呢?](#怎么使ArrayList,LinkedList变成线程安全的呢?)
78
#### [3.ArrayList遍历时删除元素有哪些方法?](#ArrayList遍历时删除元素有哪些方法?)
89
#### [4.ConcurrentModificationException是什么?](#ConcurrentModificationException是什么?)
@@ -13,7 +14,38 @@
1314

1415
每次遇到一个好看的小姐姐,我们一般都是会去看她的外貌,身材,大小(咳咳,我们指的是年龄大小)等等。同样的,我们在分析Java容器之间的区别时,我们也可以从继承树,底层数据结构,线程安全,执行效率来进行分析。
1516

16-
#### 1.继承树
17+
#### 1.底层使用的数据结构
18+
19+
* Arraylist 底层使用的是Object数组,初始化时就会指向的会是一个static修饰的空数组,数组长度一开始为**0**,插入第一个元素时数组长度会初始化为**10**,之后每次数组空间不够进行扩容时都是增加为原来的**1.5倍**。ArrayList的空间浪费主要体现在在list列表的结尾会预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间(因为要存放直接后继和直接前驱以及数据)
20+
21+
* LinkedList 底层使用的是双向链表数据结构,每个节点保存了指向前驱节点和后继结点的指针。初始化时,不执行任何操作,添加第一个元素时,再去构造链表中的节点。
22+
23+
#### 2.是否保证线程安全:
24+
25+
ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全。
26+
27+
因为ArrayList的插入元素的方法就是裸奔的,直接将原数组index及后面的元素拷贝到index+1及后面的位置上,然后将index位置设置为插入的值,并发修改时保证不了数据安全性,所以也不允许并发修改,一旦检测到并发修改,会抛出ConcurrentModificationException异常。
28+
29+
```
30+
//ArrayList的插入元素的方法
31+
public void add(int index, E element) {
32+
rangeCheckForAdd(index);
33+
ensureCapacityInternal(size + 1); // Increments modCount!!
34+
System.arraycopy(elementData, index, elementData, index + 1,
35+
size - index);//将原数组index之后的元素拷贝到原数组index+1后面的位置上
36+
elementData[index] = element;
37+
size++;
38+
}
39+
```
40+
41+
42+
43+
#### 3.插入和删除的复杂度:
44+
45+
* ArrayList 采用数组存储,元素的物理存储地址是连续的,支持以O(1)的时间复杂度对元素快速访问。插入和删除元素后,需要将后面的元素进行移动,所以插入和删除元素的时间复杂度受元素位置的影响。复杂度是 O(n),
46+
* LinkedList 采用链表存储,所以不能快速随机访问。所以首尾插入,删除元素时间复杂度不受元素位置的影响,都是近似 O(1)(如果是插入到中间位置还需要考虑寻找插入位置的时间复杂度)。而数组为近似 O(n)。
47+
48+
#### 4.继承树
1749

1850
* ArrayList继承于AbstractList抽象类,实现了List, RandomAccess, Cloneable, java.io.Serializable接口 。
1951
* LinkedList继承自AbstractSequentialList 实现List, Deque, Cloneable, java.io.Serializable接口。
@@ -30,26 +62,25 @@ RandomAccess是一个标示性接口,代表ArrayList支持快速访问,而Li
3062

3163
Deque接口是双端队列的意思,代表LinkedList支持两端元素插入和移除。
3264

33-
#### 2.底层使用的数据结构
34-
35-
* Arraylist 底层使用的是Object数组,初始化时就会指向的会是一个static修饰的空数组,数组长度一开始为0,插入第一个元素时变为10,之后每次数组空间不够进行扩容时都是增加为原来的1.5倍。ArrayList的空间浪费主要体现在在list列表的结尾会预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间(因为要存放直接后继和直接前驱以及数据)
36-
37-
* LinkedList 底层使用的是双向链表数据结构,每个节点保存了指向前驱节点和后继结点的指针。初始化时,不执行任何操作,添加第一个元素时,再去构造链表中的节点。
38-
39-
#### 3.是否保证线程安全:
65+
### 怎么使ArrayList,LinkedList变成线程安全的呢?
4066

41-
ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全。
67+
#### 1.使用SynchronizedList
4268

43-
#### 4.插入和删除的复杂度:
69+
SynchronizedList是一个线程安全的包装类。继承于SynchronizedCollection,SynchronizedCollection实现了Collection接口,SynchronizedList包含一个List对象,对List的访问修改方法进行了一些封装,在封装的方法中会对list使用同步锁加锁,然后再进行存取和修改操作。
4470

45-
* ArrayList 采用数组存储,元素的物理存储地址是连续的,支持以O(1)的时间复杂度对元素快速访问。插入和删除元素后,需要将后面的元素进行移动,所以插入和删除元素的时间复杂度受元素位置的影响。复杂度是 O(n),
46-
* LinkedList 采用链表存储,所以不能快速随机访问。所以首尾插入,删除元素时间复杂度不受元素位置的影响,都是近似 O(1)(如果是插入到中间位置还需要考虑寻找插入位置的时间复杂度)。而数组为近似 O(n)。
71+
使用方法如下
4772

48-
### 怎么使ArrayList,LinkedList变成线程安全的呢?
73+
```
74+
LinkedList<Integer> linkedList = new LinkedList<Integer>();
75+
//调用Collections的synchronizedList方法,传入一个linkedList,会返回一个SynchronizedList实例对象
76+
List<Integer> synchronizedList = Collections.synchronizedList(linkedList);
4977
50-
#### 1.使用SynchronizedList
78+
//调用Collections的synchronizedList方法,ArrayList,返回一个SynchronizedRandomAccessList实例对象
79+
ArrayList<Integer> arrayList = new ArrayList<Integer>();
80+
List<Integer> synchronizedRandomAccessList = Collections.synchronizedList(linkedList);
81+
```
5182

52-
SynchronizedList是一个线程安全的包装类。继承于SynchronizedCollection,SynchronizedCollection实现了Collection接口,SynchronizedList包含一个List对象,对List的访问修改方法进行了一些封装,在封装的方法中会对list使用同步锁加锁,然后再进行存取和修改操作。(ArrayList也是如此,只不过返回的是SynchronizedRandomAccessList对象,SynchronizedRandomAccessList是SynchronizedList的子类,只是会多一个以线程安全的方式获取子数组的方法)。
83+
(Collections.synchronizedList()方法会判断传入的对象是否实现了 RandomAccess接口,是的话,会返回一个SynchronizedRandomAccessList对象,SynchronizedRandomAccessList是SynchronizedList的子类,只是会多一个以线程安全的方式获取子数组的方法)。
5384

5485
SynchronizedList类的部分代码如下:
5586

@@ -58,8 +89,15 @@ SynchronizedList类的部分代码如下:
5889
extends SynchronizedCollection<E>
5990
implements List<E> {
6091
final List<E> list;//源list
92+
final Object mutex;
93+
94+
SynchronizedCollection(Collection<E> c) {
95+
this.c = Objects.requireNonNull(c);
96+
mutex = this;//mutex就是SynchronizedList实例自己,作为同步锁使用
97+
}
98+
6199
public E get(int index) {
62-
synchronized (mutex) {//mutex加同步锁的对象,
100+
synchronized (mutex) {
63101
是父类中的成员变量,在父类中会将list赋值给mutex
64102
return list.get(index);
65103
}
@@ -154,7 +192,7 @@ SynchronizedList是通过对读写方法使用synchronized修饰来实现同步
154192
}
155193
```
156194

157-
#### 第1种方法 - 普通for循环正序删除(结果:会漏掉元素判断
195+
#### 第1种方法 - 普通for循环正序删除(结果:会漏掉对后一个元素的判断
158196

159197
```java
160198
for (int i = 0; i < arrayList.size(); i++) {
@@ -207,6 +245,8 @@ for (int i = 0; i < arrayList.size(); i++) {
207245

208246
### 第3种方法 - for-each循环删除(结果:抛出异常)
209247

248+
抛出异常的根本原因在于for-each是使用Iterator来实现遍历的,调用ArrayList.remove()方法会将modCount+1,而Iterator内部的expectedModCount确没有更新,这样在进行下次循环时调用Iterator.next()会对modCount和expectedModCount进行比较,不一致就会抛出ConcurrentModificationException异常。
249+
210250
```java
211251
public static void removeWayThree(ArrayList<Integer> arrayList) {
212252
for (Integer value : arrayList) {
@@ -291,17 +331,47 @@ private void fastRemove(int index) {
291331
System.arraycopy(elementData, index+1, elementData, index,numMoved);
292332
elementData[--size] = null; // clear to let GC do its work
293333
}
294-
295-
296334
```
297335

298336
而当删除完元素后,进行下一次循环时,会调用下面源码中Itr.next()方法获取下一个元素,会调用checkForComodification()方法对ArrayList进行校验,判断在遍历ArrayList是否已经被修改,由于之前对modCount+1,而expectedModCount还是初始化时ArrayList.Itr对象时赋的值,所以会不相等,然后抛出ConcurrentModificationException异常。
299337

300338
##### 那么有什么办法可以让expectedModCount及时更新呢?
301339

302-
可以看到下面Itr的源码中,在Itr.remove()方法中删除元素后会对 expectedModCount更新,所以我们在使用删除元素时使用Itr.remove()方法来删除元素就可以保证expectedModCount的更新了,具体看第5种方法
340+
可以看到下面Itr的源码中,在Itr.remove()方法中删除元素后会对 expectedModCount更新,所以我们在使用删除元素时使用Itr.remove()方法来删除元素就可以保证expectedModCount的更新了,具体看**第5种**方法
303341

304342
```java
343+
//使用Iterator遍历元素的方法
344+
/*
345+
Iterator遍历时使用next()方法返回下一个元素,主要通过将游标cursor+1,获得下一个元素
346+
347+
调用remove()删除元素时,主要删除lastRet下标对应的元素,并且将cursor设置为lastRet的值,因为后面的元素向前面的空位移动了一位
348+
349+
Iterator遍历过程中,在一次循环中也不能多次调用remove()方法,因为每次remove()后就会将lastRet设置为-1,本次循环中再remove就会抛异常,必须等调用next()方法后对lastRet重新赋值。
350+
*/
351+
public void tranverse() {
352+
ArrayList<Integer> list = new ArrayList<Integer>();
353+
list.add(1);
354+
list.add(2);
355+
list.add(3);
356+
list.add(3);
357+
list.add(4);
358+
list.add(5);
359+
Iterator<Integer> iterator = list.iterator();
360+
while (iterator.hasNext()) {
361+
Integer value = iterator.next();
362+
System.out.println(value);
363+
if (value.equals(3)) {
364+
iterator.remove();
365+
iterator.remove();//在循环中多次调用iterator的remove方法会抛出异常
366+
iterator.remove();
367+
}
368+
}
369+
}
370+
```
371+
372+
**Iterator的源代码**
373+
374+
```
305375
private class Itr implements Iterator<E> {
306376
int cursor; // 游标
307377
int lastRet = -1; // index of last element returned; -1 if no such
@@ -369,7 +439,7 @@ Exception in thread "main" java.util.ConcurrentModificationException
369439
at com.test.ArrayListTest1.main(ArrayListTest1.java:25)
370440
```
371441

372-
第3种方法在编译后的代码,其实是跟第4种是一样的,所以第四种写法也会抛出ConcurrentModificationException异常。这种需要注意的是,每次调用iterator的next()方法,会导致游标向右移动,从而达到遍历的目的。所以在单次循环中不能多次调用next()方法,不然会导致每次循环时跳过一些元素,我在一些博客里面看到了一些错误的写法,比如这一篇[《在ArrayList的循环中删除元素,会不会出现问题?》](https://juejin.im/post/5b92844a6fb9a05d290ed46c)文章中:
442+
第4种方法其实是第3种方法在编译后的代码,所以第四种写法也会抛出ConcurrentModificationException异常。这种需要注意的是,每次调用iterator的next()方法,会导致游标向右移动,从而达到遍历的目的。所以在单次循环中不能多次调用next()方法,不然会导致每次循环时跳过一些元素,我在一些博客里面看到了一些错误的写法,比如这一篇[《在ArrayList的循环中删除元素,会不会出现问题?》](https://juejin.im/post/5b92844a6fb9a05d290ed46c)文章中:
373443

374444
![image-20200101124822998](/Users/ruiwendaier/Library/Application Support/typora-user-images/image-20200101124822998.png)
375445

docs/JVMBook.md

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -273,9 +273,11 @@ GC Roots对象只有包括以下几种:
273273

274274
#### 分代收集算法
275275

276-
新生代存活率低,使用标记-复制算法
276+
就是现在的系统一般都比较复杂,堆中的对象也会比较多,如果使用对所有对象都分析是否需要回收,那么效率会比较低,所以有了分代收集算法,就是对熬过垃圾回收次数不同的对象进行分类,分为新生代和老年代,采用不同回收策略
277277

278-
老年代存活率高,使用标记-清除算法,或者标记-整理算法。(一般是多数时间采用标记-清除算法,内存碎片化程度较高时,使用标记-整理算法收集一次)。
278+
新生代存活率低,使用标记-复制算法。新生代发生的垃圾收集交Minor GC,发生频率较高
279+
280+
老年代存活率高,使用标记-清除算法,或者标记-整理算法。(一般是多数时间采用标记-清除算法,内存碎片化程度较高时,使用标记-整理算法收集一次)。老年代内存满时会触发Major GC(Full GC),一般触发的频率比较低。
279281

280282
### HotSpot的算法实现
281283

@@ -333,21 +335,21 @@ CPU中的缓存系统是以缓存行为单位存储的,一个缓存行会包
333335

334336
[Java虚拟机(五)经典的垃圾收集器]([http://antarctica.gitee.io/blog/2020/01/12/Java%E8%99%9A%E6%8B%9F%E6%9C%BA5/#%E5%9E%83%E5%9C%BE%E6%94%B6%E9%9B%86%E5%99%A8](http://antarctica.gitee.io/blog/2020/01/12/Java虚拟机5/#垃圾收集器))
335337

336-
#### Serial收集器
338+
#### Serial收集器(标记-复制算法)
337339

338-
就是最简单的垃圾收集器,也是目前jvm默认的垃圾收集器,在进行垃圾收集时会停止用户线程,然后使用一个收集线程进行垃圾收集。主要用于新生代,使用标记-复制算法。
340+
就是最简单的垃圾收集器,也是目前 JVM 在 Client 模式默认的垃圾收集器,在进行垃圾收集时会停止用户线程,然后使用一个收集线程进行垃圾收集。主要用于新生代,使用标记-复制算法。
339341

340-
优点是简单高效(与其他收集器的单线程比),内存占用小。
342+
优点是简单高效(与其他收集器的单线程比),内存占用小,因为垃圾回收时就暂停所有用户线程,然后使用一个单线程进行垃圾回收,不用进行线程切换
341343

342344
缺点是收集时必须停止其他用户线程。
343345

344-
#### Serial Old收集器
346+
#### Serial Old收集器(标记-整理算法)
345347

346-
跟Serial收集器一样,不过是应用于老年代,使用标记整理算法
348+
跟Serial收集器一样,不过是应用于老年代,使用标记-整理算法
347349

348350
![image-20200228184419205](../static/image-20200228184419205.png)
349351

350-
#### ParNew收集器
352+
#### ParNew收集器(标记-复制算法)
351353

352354
ParNew收集器是Serial收集器的多线程并行版本,在进行垃圾收集时可以使用多个线程进行垃圾收集。
353355

@@ -365,7 +367,7 @@ ParNew收集器是Serial收集器的多线程并行版本,在进行垃圾收
365367

366368
![image-20200228191619466](../static/image-20200228191619466.png)
367369

368-
#### CMS 收集器(并发低停顿收集器
370+
#### CMS 收集器(老年代并发低停顿收集器
369371

370372
CMS收集器是第一个支持并发收集的垃圾收集器,在垃圾收集时,用户线程可以和收集线程一起工作,它的执行目标是达到最短回收停顿时间,以获得更好的用户体验。
371373

@@ -381,7 +383,7 @@ CMS英文是Concurrent Mark Sweep,是基于标记-清除法实现的,步骤
381383

382384
![image-20200228195544758](../static/image-20200228195544758.png)
383385

384-
#### G1收集器(Garbage First收集器)
386+
#### G1收集器(Garbage First收集器,标记-整理算法,老年代,新生代都进行回收
385387

386388
目标是在延迟可控(用户设定的延迟时间)的情况下获得尽可能高的吞吐量。
387389

@@ -419,15 +421,15 @@ Serial收集器中,
419421

420422
分为Eden,From Survivor,To Survivor,8:1:1
421423

422-
Eden用来分配新对象,
424+
Eden用来分配新对象,满了时会触发Minor GC。
423425

424426
From Survivor是上次Minor GC后存活的对象。
425427

426428
To Survivor是用于下次Minor GC时存放存活的对象。
427429

428430
##### 老年代
429431

430-
用于存放存活时间比较长的对象,大的对象,当容量满时会触发Major GC
432+
用于存放存活时间比较长的对象,大的对象,当容量满时会触发Major GC(Full GC)
431433

432434
##### 1.对象优先在Eden分配
433435

docs/JavaBasic.md

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -677,4 +677,28 @@ OutClass out = new OutClass();
677677
OutClass.InnerClass object = out.new InnerClass();
678678
```
679679
### Java中的注解是什么?
680-
Java中的注解其实是
680+
Java中的注解其实是继承于annotation接口的一个接口,根据@Retention修饰的作用范围,注解可以是作用于源码层面,字节码文件层面,运行时层面。
681+
682+
```
683+
// 如果@Retention的值是RetentionPolicy.Source 那么在编译时注解就失效了,不会编译进class文件
684+
// 如果@Retention的值是RetentionPolicy.CLASS 那么会编译进class文件,但是JVM加载class文件时就会丢弃这个注解,在运行时注解就失效了
685+
// 如果@Retention的值是RetentionPolicy.RUNTIME 在运行时注解同样有效
686+
```
687+
688+
根据@Retention的值,注解主要分为编译时扫描和运行时扫描,例如@Override注解是作用于源码层面的,只是在编译时,编译器会去检验method是否是真正重写了父类的某个方法。
689+
690+
```
691+
@Override
692+
public void method() {
693+
694+
}
695+
696+
697+
@Target(ElementType.METHOD)
698+
@Retention(RetentionPolicy.SOURCE)
699+
public @interface Override {
700+
701+
}
702+
```
703+
704+
除了几个JDK自带的注解以外,通常情况下,我们使用的注解都是运行时注解,在运行时,JVM在运行时会针对注解生成一个动态代理类,通过反射获取注解时,实际上返回的是Java运行时生成的动态代理对象$Proxy1,而Proxy类就是我们注解(接口)的具体实现类。

docs/JavaJVM.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,4 +143,5 @@ UseParallelGC参数会使用Parallel Scavenge+Serial Old的收集器组合,进
143143

144144
另外这个命令可以查询到更加详细的信息
145145

146-
java -XX:+PrintFlagsFinal -version | grep GC
146+
java -XX:+PrintFlagsFinal -version | grep GC
147+

0 commit comments

Comments
 (0)