X Tutup
Skip to content

Commit 0f24699

Browse files
committed
修改图片路径
1 parent 4d85b5e commit 0f24699

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+55
-55
lines changed

notes/JavaArchitecture/01-Java基础.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -445,13 +445,13 @@ Java 面向对象的基本思想之一是封装细节并且公开接口。Java
445445

446446
- **浅拷贝**:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅拷贝仅仅复制所拷贝的对象,而不复制它所引用的对象。
447447

448-
<div align="center"> <img src="../pics/shadow_copy2.jpg" width="550"/></div>
448+
<div align="center"> <img src="assets/shadow_copy2.jpg" width="550"/></div>
449449

450450

451451

452452
- **深拷贝**:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。
453453

454-
<div align="center"> <img src="../pics/deep_copy2.jpg" width="550"/></div>
454+
<div align="center"> <img src="assets/deep_copy2.jpg" width="550"/></div>
455455

456456

457457

@@ -735,7 +735,7 @@ public class Test {
735735
- **重载**:重载发生在同一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载。
736736
- **重写**:重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。根据不同的子类对象确定调用的那个方法。
737737

738-
<div align="center"> <img src="../pics/overloading-vs-overriding.png" width="700"/></div>
738+
<div align="center"> <img src="assets/overloading-vs-overriding.png" width="700"/></div>
739739

740740

741741

@@ -1528,7 +1528,7 @@ assert 的应用范围很多,主要包括:
15281528
- 每次访问变量时,总是获取主内存的最新值
15291529
- 每次修改变量后,立刻写回到主内存中
15301530

1531-
<div align="center"> <img src="../pics/../pics/java-volatile.png" width="400"/></div>
1531+
<div align="center"> <img src="assets/java-volatile.png" width="400"/></div>
15321532

15331533
参考资料:
15341534

@@ -1594,7 +1594,7 @@ strictfp 关键字可应用于类、接口或方法。使用 strictfp 关键字
15941594

15951595
native(即 JNI,Java Native Interface),凡是一种语言,都希望是纯。比如解决某一个方案都喜欢就单单这个语言来写即可。Java 平台有个用户和本地 C 代码进行互操作的 API,称为 Java Native Interface (Java本地接口)。
15961596

1597-
<div align="center"> <img src="../pics/java-native-interface.png" width="500"/></div><br/>
1597+
<div align="center"> <img src="assets/java-native-interface.png" width="500"/></div><br/>
15981598

15991599

16001600

@@ -2057,7 +2057,7 @@ public class BinaryIntegralLiteral {
20572057
- RuntimeException
20582058
- 常见的异常
20592059

2060-
<div align="center"><img src="../pics/exception_and_error.png" width="650"/></div>
2060+
<div align="center"><img src="assets/exception_and_error.png" width="650"/></div>
20612061

20622062

20632063

notes/JavaArchitecture/02-Java集合框架.md

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ public class ArrayList<E> extends AbstractList<E>
187187
private static final int DEFAULT_CAPACITY = 10;
188188
```
189189

190-
<div align="center"> <img src="../pics/ArrayList_base.png" width="600"/></div>
190+
<div align="center"> <img src="assets/ArrayList_base.png" width="600"/></div>
191191

192192

193193

@@ -363,7 +363,7 @@ List<String> synList = Collections.synchronizedList(list);
363363

364364
#### CopyOnWriteArrayList
365365

366-
<div align="center"> <img src="../pics/cow.png" width=""/></div><br/>
366+
<div align="center"> <img src="assets/cow.png" width=""/></div><br/>
367367

368368

369369

@@ -433,7 +433,7 @@ public E get(int index) {
433433

434434
## LinkedList
435435

436-
<div align="center"> <img src="../pics/LinkedList_base.png" width="600"/></div>
436+
<div align="center"> <img src="assets/LinkedList_base.png" width="600"/></div>
437437

438438
### 1. 概览
439439

@@ -466,7 +466,7 @@ LinkedList 的实现方式决定了所有跟下标相关的操作都是线性时
466466

467467
### 2. add()
468468

469-
<div align="center"> <img src="../pics/LinkedList_add.png" width="600"/></div>
469+
<div align="center"> <img src="assets/LinkedList_add.png" width="600"/></div>
470470

471471
add() 方法有两个版本,一个是 `add(E e)`,该方法在 LinkedList 的末尾插入元素,因为有 last 指向链表末尾,在末尾插入元素的花费是常数时间。只需要简单修改几个相关引用即可;另一个是 `add(int index, E element)`,该方法是在指定下表处插入元素,需要先通过线性查找找到具体位置,然后修改相关引用完成插入操作。
472472

@@ -523,7 +523,7 @@ private void checkPositionIndex(int index) {
523523

524524
remove() 方法也有两个版本,一个是删除跟指定元素相等的第一个元素 `remove(Object o)`,另一个是删除指定下标处的元素 `remove(int index)`
525525

526-
<div align="center"> <img src="../pics/LinkedList_remove.png" width="600"/></div>
526+
<div align="center"> <img src="assets/LinkedList_remove.png" width="600"/></div>
527527

528528
两个删除操作都要:
529529

@@ -602,7 +602,7 @@ HashMap 的内部功能实现很多,本文主要从根据 key 获取哈希桶
602602

603603
在 1.7 之前 JDK 采用「拉链法」来存储数据,即数组和链表结合的方式:
604604

605-
<div align="center"> <img src="../pics/hashmap-link.jpg" width="550"/></div>
605+
<div align="center"> <img src="assets/hashmap-link.jpg" width="550"/></div>
606606

607607
「拉链法」用专业点的名词来说叫做**链地址法**。简单来说,就是数组加链表的结合。在每个数组元素上存储的都是一个链表。
608608

@@ -618,13 +618,13 @@ JDK1.7 中新添加进来的元素总是放在数组相应的角标位置,而
618618

619619
对数据结构很在行的读者应该,知道红黑树是一种易于增删改查的二叉树,他对与数据的查询的时间复杂度是 `O(logn)` 级别,所以利用红黑树的特点就可以更高效的对 `HashMap` 中的元素进行操作。
620620

621-
<div align="center"> <img src="../pics/hashmap-rb-link.jpg" width="550"/></div><br/>
621+
<div align="center"> <img src="assets/hashmap-rb-link.jpg" width="550"/></div><br/>
622622

623623

624624

625625
从结构实现来讲,HashMap 是数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的,如下如所示。
626626

627-
<div align="center"> <img src="../pics/hashMap-datastruct.png" width="400"/></div>
627+
<div align="center"> <img src="assets/hashMap-datastruct.png" width="400"/></div>
628628

629629

630630

@@ -774,7 +774,7 @@ static int indexFor(int h, int length) {
774774

775775
  HashMap 的 put 方法执行过程可以通过下图来理解,自己有兴趣可以去对比源码更清楚地研究学习。
776776

777-
<div align="center"> <img src="../pics/hashmap-put.png" width=""/></div><br/>
777+
<div align="center"> <img src="assets/hashmap-put.png" width=""/></div><br/>
778778

779779
①.判断键值对数组 table[i] 是否为空或为 null,否则执行 resize() 进行扩容;
780780

@@ -897,19 +897,19 @@ newTable[i] 的引用赋给了 e.next,也就是使用了单链表的头插入
897897

898898
下面举个例子说明下扩容过程。假设了我们的 hash 算法就是简单的用 key mod 一下表的大小(也就是数组的长度)。其中的哈希桶数组 table 的 size=2, 所以 key = 3、7、5,put 顺序依次为 5、7、3。在 mod 2 以后都冲突在 table[1] 这里了。这里假设负载因子 loadFactor=1,即当键值对的实际大小 size 大于 table 的实际大小时进行扩容。接下来的三个步骤是哈希桶数组 resize 成 4,然后所有的 Node 重新 rehash 的过程。
899899

900-
<div align="center"> <img src="../pics/jdk1.7-resize.png" width="600"/></div>
900+
<div align="center"> <img src="assets/jdk1.7-resize.png" width="600"/></div>
901901

902902
下面我们讲解下 JDK1.8 做了哪些优化。经过观测可以发现,我们使用的是 2 次幂的扩展 (指长度扩为原来 2 倍),所以,元素的位置要么是在原位置,要么是在原位置再移动 2 次幂的位置。看下图可以明白这句话的意思,n 为 table 的长度,图(a)表示扩容前的 key1 和 key2 两种 key 确定索引位置的示例,图(b)表示扩容后 key1 和 key2 两种 key 确定索引位置的示例,其中 hash1 是 key1 对应的哈希与高位运算结果。
903903

904-
<div align="center"> <img src="../pics/hashMap-1.8-hash1.png" width="700"/></div><br/>
904+
<div align="center"> <img src="assets/hashMap-1.8-hash1.png" width="700"/></div><br/>
905905

906906
元素在重新计算 hash 之后,因为 n 变为 2 倍,那么 n-1 的 mask 范围在高位多 1bit (红色),因此新的 index 就会发生这样的变化:
907907

908-
<div align="center"> <img src="../pics/hashMap-1.8-hash2.png" width="600"/></div>
908+
<div align="center"> <img src="assets/hashMap-1.8-hash2.png" width="600"/></div>
909909

910910
因此,我们在扩充 HashMap 的时候,不需要像 JDK1.7 的实现那样重新计算 hash,只需要看看原来的 hash 值新增的那个 bit 是 1 还是 0 就好了,是 0 的话索引没变,是 1 的话索引变成“原索引+oldCap”,可以看看下图为 16 扩充为 32 的 resize 示意图:
911911

912-
<div align="center"> <img src="../pics/jdk1.8-resize.png" width="700"/></div><br/>
912+
<div align="center"> <img src="assets/jdk1.8-resize.png" width="700"/></div><br/>
913913

914914
这个设计确实非常的巧妙,既省去了重新计算 hash 值的时间,而且同时,由于新增的 1bit 是 0 还是 1 可以认为是随机的,因此 resize 的过程,均匀的把之前的冲突的节点分散到新的 bucket 了。这一块就是 JDK1.8 新增的优化点。有一点注意区别,JDK1.7 中 rehash 的时候,旧链表迁移新链表的时候,如果在新表的数组索引位置相同,则链表元素会倒置,但是从上图可以看出,JDK1.8 不会倒置。有兴趣的同学可以研究下 JDK1.8 的 resize源 码,写的很赞,如下:
915915

@@ -1029,23 +1029,23 @@ public class HashMapInfiniteLoop {
10291029
10301030
通过设置断点让线程1和线程2同时debug到transfer方法(3.3小节代码块)的首行。注意此时两个线程已经成功添加数据。放开thread1的断点至transfer方法的“Entry next = e.next;” 这一行;然后放开线程2的的断点,让线程2进行resize。结果如下图。
10311031
1032-
<div align="center"> <img src="../pics/jdk1.7-drop-dead-1.png" width="600"/></div>
1032+
<div align="center"> <img src="assets/jdk1.7-drop-dead-1.png" width="600"/></div>
10331033
10341034
注意,Thread1的 e 指向了key(3),而next指向了key(7),其在线程二rehash后,指向了线程二重组后的链表。
10351035
10361036
线程一被调度回来执行,先是执行 newTalbe[i] = e, 然后是e = next,导致了e指向了key(7),而下一次循环的next = e.next导致了next指向了key(3)。
10371037
1038-
<div align="center"> <img src="../pics/jdk1.7-drop-dead-2.png" width="600"/></div>
1038+
<div align="center"> <img src="assets/jdk1.7-drop-dead-2.png" width="600"/></div>
10391039
10401040
10411041
1042-
<div align="center"> <img src="../pics/jdk1.7-drop-dead-3.png" width="600"/></div>
1042+
<div align="center"> <img src="assets/jdk1.7-drop-dead-3.png" width="600"/></div>
10431043
10441044
e.next = newTable[i] 导致 key(3).next 指向了 key(7)。注意:此时的key(7).next 已经指向了key(3), 环形链表就这样出现了。
10451045
10461046
10471047
1048-
<div align="center"> <img src="../pics/jdk1.7-drop-dead-5.png" width="600"/></div>
1048+
<div align="center"> <img src="assets/jdk1.7-drop-dead-5.png" width="600"/></div>
10491049
10501050
于是,当我们用线程一调用map.get(11)时,悲剧就出现了——Infinite Loop。
10511051
@@ -1134,7 +1134,7 @@ public static void main(String[] args) {
11341134
11351135
在测试中会查找不同的值,然后度量花费的时间,为了计算getKey的平均时间,我们遍历所有的get方法,计算总的时间,除以key的数量,计算一个平均值,主要用来比较,绝对值可能会受很多环境因素的影响。结果如下:
11361136
1137-
<div align="center"> <img src="../pics/hashmap-compare1.png" width=""/></div><br/>
1137+
<div align="center"> <img src="assets/hashmap-compare1.png" width=""/></div><br/>
11381138
11391139
通过观测测试结果可知,JDK1.8的性能要高于JDK1.7 15%以上,在某些size的区域上,甚至高于100%。由于Hash算法较均匀,JDK1.8引入的红黑树效果不明显,下面我们看看Hash不均匀的的情况。
11401140
@@ -1156,7 +1156,7 @@ class Key implements Comparable<Key> {
11561156
11571157
仍然执行main方法,得出的结果如下表所示:
11581158
1159-
<div align="center"> <img src="../pics/hashmap-compare2.png" width=""/></div><br/>
1159+
<div align="center"> <img src="assets/hashmap-compare2.png" width=""/></div><br/>
11601160
11611161
从表中结果中可知,随着size的变大,JDK1.7的花费时间是增长的趋势,而JDK1.8是明显的降低趋势,并且呈现对数增长稳定。当一个链表太长的时候,HashMap会动态的将它替换成一个红黑树,这话的话会将时间复杂度从O(n)降为O(logn)。hash算法均匀和不均匀所花费的时间明显也不相同,这两种情况的相对比较,可以说明一个好的hash算法的重要性。
11621162
@@ -1204,11 +1204,11 @@ class Key implements Comparable<Key> {
12041204
12051205
  但是 HashTable 线程安全的策略实现代价却太大了,简单粗暴,get/put 所有相关操作都是 synchronized 的,这相当于给整个哈希表加了一把大锁,多线程访问时候,只要有一个线程访问或操作该对象,那其他线程只能阻塞,相当于将所有的操作串行化,在竞争激烈的并发场景中性能就会非常差。
12061206
1207-
<div align="center"> <img src="../pics/hashtable-ds.png" width="600"/></div>
1207+
<div align="center"> <img src="assets/hashtable-ds.png" width="600"/></div>
12081208
12091209
  HashTable 性能差主要是由于所有操作需要竞争同一把锁,而如果容器中有多把锁,每一把锁锁一段数据,这样在多线程访问时不同段的数据时,就不会存在锁竞争了,这样便可以有效地提高并发效率。这就是ConcurrentHashMap 所采用的 "**分段锁**" 思想。
12101210
1211-
<div align="center"> <img src="../pics/hashmap-ds.png" width="600"/></div><br/>
1211+
<div align="center"> <img src="assets/hashmap-ds.png" width="600"/></div><br/>
12121212
12131213
### 2. 存储结构
12141214
@@ -1479,7 +1479,7 @@ public class HashSet<E>
14791479
14801480
  LinkedHashMap 实现了 Map 接口,即允许放入 key 为 null 的元素,也允许插入 value 为 null 的元素。从名字上可以看出该容器是 LinkedList 和 HashMap 的混合体,也就是说它同时满足 HashMap 和 LinkedList 的某些特性。**可将 LinkedHashMap 看作采用 LinkedList 增强的 HashMap。**
14811481
1482-
<div align="center"> <img src="../pics/LinkedHashMap_base.png" width="650"/></div><br/>
1482+
<div align="center"> <img src="assets/LinkedHashMap_base.png" width="650"/></div><br/>
14831483
14841484
14851485
@@ -1521,7 +1521,7 @@ void foo(Map m) {
15211521
> 1. 从 table 的角度看,新的 entry 需要插入到对应的 bucket 里,当有哈希冲突时,采用头插法将新的 entry 插入到冲突链表的头部。
15221522
> 2. 从 header 的角度看,新的 entry 需要插入到双向链表的尾部。
15231523
1524-
<div align="center"> <img src="../pics/LinkedHashMap_addEntry.png" width="650"/></div>
1524+
<div align="center"> <img src="assets/LinkedHashMap_addEntry.png" width="650"/></div>
15251525
15261526
`addEntry()`代码如下:
15271527
@@ -1570,7 +1570,7 @@ private void addBefore(Entry<K,V> existingEntry) {
15701570
15711571
15721572
1573-
<div align="center"> <img src="../pics/LinkedList_remove.png" width="700"/></div><br/>
1573+
<div align="center"> <img src="assets/LinkedList_remove.png" width="700"/></div><br/>
15741574
15751575
`removeEntryForKey()` 对应的代码如下:
15761576
@@ -1655,7 +1655,7 @@ class FIFOCache<K, V> extends LinkedHashMap<K, V>{
16551655
16561656
## 迭代器模式
16571657
1658-
<div align="center"> <img src="../pics/Iterator-1.jpg" width=""/></div><br/>
1658+
<div align="center"> <img src="assets/Iterator-1.jpg" width=""/></div><br/>
16591659
16601660
Collection 实现了 Iterable 接口,其中的 iterator() 方法能够产生一个 Iterator 对象,通过这个对象就可以迭代遍历 Collection 中的元素。
16611661
@@ -1763,21 +1763,21 @@ List list = Arrays.asList(1,2,3);
17631763
17641764
假设我们现在 Hashtable 的容量为 5,已经存在了 (5,5),(13,13),(16,16),(17,17),(21,21) 这 5 个键值对,目前他们在 Hashtable 中的位置如下:
17651765
1766-
<div align="center"> <img src="../pics/hashtable1.png" width=""/></div><br/>
1766+
<div align="center"> <img src="assets/hashtable1.png" width=""/></div><br/>
17671767
17681768
17691769
17701770
现在,我们插入一个新的键值对,put(16,22),假设 key=16 的索引为 1.但现在索引 1 的位置有两个 Entry 了,所以程序会对链表进行迭代。迭代的过程中,发现其中有一个 Entry 的 key 和我们要插入的键值对的 key 相同,所以现在会做的工作就是将 newValue=22 替换 oldValue=16,然后返回 oldValue = 16.
17711771
17721772
17731773
1774-
<div align="center"> <img src="../pics/hashtable2.png" width=""/></div><br/>
1774+
<div align="center"> <img src="assets/hashtable2.png" width=""/></div><br/>
17751775
17761776
17771777
17781778
然后我们现在再插入一个,put(33,33),key=33 的索引为 3,并且在链表中也不存在 key=33 的 Entry,所以将该节点插入链表的第一个位置。
17791779
1780-
<div align="center"> <img src="../pics/hashtable3.png" width=""/></div><br/>
1780+
<div align="center"> <img src="assets/hashtable3.png" width=""/></div><br/>
17811781
17821782
17831783
@@ -1849,7 +1849,7 @@ public static void main(String[] args) {
18491849
18501850
  当客户端发送一个请求到服务器,如果该请求中带有参数,服务器端会将 参数名-参数值 作为 key-value 保存在 HashMap 中。如果有人恶意构造请求,在请求中加入大量相同 hash 值的 String 参数名(key),那么在服务器端用于存储这些 key-value 对的 HashMap 会被强行退化成链表,如图:
18511851
1852-
<div align="center"> <img src="../pics/hash-to-badlink.png" width=""/></div>
1852+
<div align="center"> <img src="assets/hash-to-badlink.png" width=""/></div>
18531853
18541854
如果数据量足够大,那么在查找,插入时会占用大量 CPU,达到拒绝服务攻击的目的。
18551855

notes/JavaArchitecture/03-Java并发编程.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2804,7 +2804,7 @@ synchronized 不仅保证可见性,而且还保证原子性,因为,只有
28042804

28052805
## 3. 什么是并发和并行
28062806

2807-
<div align="center"> <img src="../pics/concurrent_and_parallel.png" width=""/></div><br/>
2807+
<div align="center"> <img src="assets/concurrent_and_parallel.png" width=""/></div><br/>
28082808

28092809
### 并发
28102810

0 commit comments

Comments
 (0)
X Tutup