diff --git a/BOOKLIST.md b/BOOKLIST.md index 4805a7dc..e88ba706 100644 --- a/BOOKLIST.md +++ b/BOOKLIST.md @@ -54,7 +54,7 @@ - [STL 源码剖析](https://book.douban.com/subject/1110934/) - [深度探索 C++ 对象模型](https://book.douban.com/subject/1091086/) -# 网站架构/分布式 +# 系统设计 - [大规模分布式存储系统](https://book.douban.com/subject/25723658/) - [从 Paxos 到 Zookeeper](https://book.douban.com/subject/26292004/) diff --git a/README.md b/README.md index 0f3ef86f..07ec9899 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ | Ⅰ | Ⅱ | Ⅲ | Ⅳ | Ⅴ | Ⅵ | Ⅶ | Ⅷ | Ⅸ | Ⅹ | | :--------: | :---------: | :---------: | :---------: | :---------: | :---------:| :---------: | :-------: | :-------:| :------:| -| 算法[:pencil2:](#算法-pencil2) | 操作系统[:computer:](#操作系统-computer)|网络[:cloud:](#网络-cloud) | 面向对象[:couple:](#面向对象-couple) |数据库[:floppy_disk:](#数据库-floppy_disk)| Java [:coffee:](#java-coffee)| 系统设计[:bulb:](#系统设计-bulb)| 工具[:hammer:](#工具-hammer)| 编码实践[:speak_no_evil:](#编码实践-speak_no_evil)| 后记[:memo:](#后记-memo) | +| 算法[:pencil2:](#pencil2-算法) | 操作系统[:computer:](#computer-操作系统)|网络[:cloud:](#cloud-网络) | 面向对象[:couple:](#couple-面向对象) |数据库[:floppy_disk:](#floppy_disk-数据库)| Java [:coffee:](#coffee-java)| 系统设计[:bulb:](#bulb-系统设计)| 工具[:hammer:](#hammer-工具)| 编码实践[:speak_no_evil:](#speak_no_evil-编码实践)| 后记[:memo:](#memo-后记) |
@@ -31,7 +31,7 @@ 排序、并查集、栈和队列、红黑树、散列表。 -## 操作系统 :computer: +### :computer: 操作系统 > [计算机操作系统](notes/计算机操作系统.md) @@ -41,7 +41,7 @@ 基本实现原理以及基本操作。 -## 网络 :cloud: +### :cloud: 网络 > [计算机网络](notes/计算机网络.md) @@ -55,7 +55,7 @@ I/O 模型、I/O 多路复用。 -## 面向对象 :couple: +### :couple: 面向对象 > [设计模式](notes/设计模式.md) @@ -65,7 +65,7 @@ 三大原则(继承、封装、多态)、类图、设计原则。 -## 数据库 :floppy_disk: +### :floppy_disk: 数据库 > [数据库系统原理](notes/数据库系统原理.md) @@ -87,7 +87,7 @@ 五种数据类型、字典和跳跃表数据结构、使用场景、和 Memcache 的比较、淘汰策略、持久化、文件事件的 Reactor 模式、复制。 -## Java :coffee: +### :coffee: Java > [Java 基础](notes/Java%20基础.md) @@ -109,7 +109,7 @@ NIO 的原理以及实例。 -## 系统设计 :bulb: +### :bulb: 系统设计 > [系统设计基础](notes/系统设计基础.md) @@ -135,7 +135,7 @@ 消息处理模型、使用场景、可靠性 -## 工具 :hammer: +### :hammer: 工具 > [Git](notes/Git.md) @@ -153,7 +153,7 @@ 构建工具的基本概念、主流构建工具介绍。 -## 编码实践 :speak_no_evil: +### :speak_no_evil: 编码实践 > [重构](notes/重构.md) @@ -167,9 +167,9 @@ Google 开源项目的代码风格规范。 -## 后记 :memo: +### :memo: 后记 -### About +#### About 这个仓库是笔者的一个学习笔记,主要总结一些比较重要的知识点,希望对大家有所帮助。 @@ -177,7 +177,7 @@ [BOOKLIST](BOOKLIST.md),这个书单是笔者至今看的一些比较好的技术书籍,虽然没有全都看完,但每本书多多少少都看了一部分。 -### How To Contribute +#### How To Contribute 笔记内容是笔者一个字一个字打上去的,难免会有一些笔误,如果发现笔误可直接在相应文档进行编辑修改。 @@ -185,7 +185,7 @@ 欢迎在 Issue 中提交对本仓库的改进建议~ -### Typesetting +#### Typesetting 笔记内容按照 [中文文案排版指北](http://mazhuang.org/wiki/chinese-copywriting-guidelines/) 进行排版,以保证内容的可读性。 @@ -193,7 +193,7 @@ 笔者将自己实现的文档排版功能提取出来,放在 Github Page 中,无需下载安装即可免费使用:[Text-Typesetting](https://github.com/CyC2018/Markdown-Typesetting)。 -### Uploading +#### Uploading 笔者在本地使用为知笔记软件进行书写,为了方便将本地笔记内容上传到 Github 上,实现了一整套自动化上传方案,包括文本文件的导出、提取图片、Markdown 文档转换、Git 同步。 @@ -201,21 +201,15 @@ 笔者将自己实现文档转换功能提取出来,方便大家在需要将本地 Markdown 上传到 Github,或者制作项目 README 文档时生成目录时使用:[GFM-Converter](https://github.com/CyC2018/GFM-Converter)。 -### License - -在对本作品进行演绎时,请署名并以相同方式共享。 - -知识共享许可协议 - -### Statement +#### Statement 本仓库不参与商业行为,不向读者收取任何费用。(This repository is not engaging in business activities, and does not charge readers any fee.) -### Logo +#### Logo Power by [logomakr](https://logomakr.com/). -### Acknowledgements +#### Acknowledgements 感谢以下人员对本仓库做出的贡献,当然不仅仅只有这些贡献者,这里就不一一列举了。如果你希望被添加到这个名单中,并且提交过 Issue 或者 PR,请与笔者联系。 @@ -241,5 +235,9 @@ Power by [logomakr](https://logomakr.com/). +#### License +在对本作品进行演绎时,请署名并以相同方式共享。 + +知识共享许可协议 diff --git a/code/src/main/java/com/raorao/java/althorithm/union/QuickFindUF.java b/code/src/main/java/com/raorao/java/althorithm/union/QuickFindUF.java new file mode 100644 index 00000000..0cd4073d --- /dev/null +++ b/code/src/main/java/com/raorao/java/althorithm/union/QuickFindUF.java @@ -0,0 +1,41 @@ +package com.raorao.java.althorithm.union; + +/** + * 查找优先的并查集. + * + * @author Xiong Raorao + * @since 2018-08-27-9:47 + */ +public class QuickFindUF extends UF { + + public QuickFindUF(int N) { + super(N); + } + + + @Override + public int find(int p) { + if (p >= id.length) { + return -1; + } + return id[p]; + } + + + @Override + public void union(int p, int q) { + + int pID = find(p); + int qID = find(q); + + if (pID == qID) { + return; + } + + for (int i = 0; i < id.length; i++) { + if (id[i] == pID) { + id[i] = qID; + } + } + } +} diff --git a/code/src/main/java/com/raorao/java/althorithm/union/QuickUnionUF.java b/code/src/main/java/com/raorao/java/althorithm/union/QuickUnionUF.java new file mode 100644 index 00000000..69d82ba5 --- /dev/null +++ b/code/src/main/java/com/raorao/java/althorithm/union/QuickUnionUF.java @@ -0,0 +1,36 @@ +package com.raorao.java.althorithm.union; + +/** + * 合并优先. + * + * @author Xiong Raorao + * @since 2018-08-27-9:50 + */ +public class QuickUnionUF extends UF { + + public QuickUnionUF(int N) { + super(N); + } + + @Override + public int find(int p) { + if (p >= id.length) { + return -1; + } + while (p != id[p]) { + p = id[p]; + } + return p; + } + + @Override + public void union(int p, int q) { + + int pRoot = find(p); + int qRoot = find(q); + + if (pRoot != qRoot) { + id[pRoot] = qRoot; + } + } +} diff --git a/code/src/main/java/com/raorao/java/althorithm/union/UF.java b/code/src/main/java/com/raorao/java/althorithm/union/UF.java new file mode 100644 index 00000000..0c572cca --- /dev/null +++ b/code/src/main/java/com/raorao/java/althorithm/union/UF.java @@ -0,0 +1,27 @@ +package com.raorao.java.althorithm.union; + +/** + * 并查集. + * + * @author Xiong Raorao + * @since 2018-08-27-9:46 + */ +public abstract class UF { + + protected int[] id; + + public UF(int N) { + id = new int[N]; + for (int i = 0; i < N; i++) { + id[i] = i; + } + } + + public boolean connected(int p, int q) { + return find(p) == find(q); + } + + public abstract int find(int p); + + public abstract void union(int p, int q); +} diff --git a/code/src/main/java/com/raorao/java/althorithm/union/UFMain.java b/code/src/main/java/com/raorao/java/althorithm/union/UFMain.java new file mode 100644 index 00000000..0c65f44f --- /dev/null +++ b/code/src/main/java/com/raorao/java/althorithm/union/UFMain.java @@ -0,0 +1,33 @@ +package com.raorao.java.althorithm.union; + +/** + * 并查集测试. + * + * @author Xiong Raorao + * @since 2018-08-27-9:55 + */ +public class UFMain { + + public static void main(String[] args) { + UF uf = new QuickFindUF(10); + uf.union(1, 2); + uf.union(3, 4); + uf.union(0, 9); + uf.union(4, 7); + uf.union(6, 5); + uf.union(5, 8); + uf.union(3, 9); + uf.union(1, 8); + + System.out.println(uf.find(0)); // 9 + System.out.println(uf.find(1)); // 5 + System.out.println(uf.find(2)); // 5 + System.out.println(uf.find(3)); // 9 + System.out.println(uf.find(4)); // 9 + System.out.println(uf.find(5)); // 5 + System.out.println(uf.find(6)); // 5 + System.out.println(uf.find(7)); // 9 + System.out.println(uf.find(8)); // 5 + System.out.println(uf.find(9)); // 9 + } +} diff --git a/code/src/main/java/com/raorao/java/althorithm/union/WeightedQuickUnionUF.java b/code/src/main/java/com/raorao/java/althorithm/union/WeightedQuickUnionUF.java new file mode 100644 index 00000000..41bbcb32 --- /dev/null +++ b/code/src/main/java/com/raorao/java/althorithm/union/WeightedQuickUnionUF.java @@ -0,0 +1,51 @@ +package com.raorao.java.althorithm.union; + +/** + * 加权快速合并. + * + * @author Xiong Raorao + * @since 2018-08-27-9:52 + */ +public class WeightedQuickUnionUF extends UF { + + // 保存节点的数量信息 + private int[] sz; + + public WeightedQuickUnionUF(int N) { + super(N); + this.sz = new int[N]; + for (int i = 0; i < N; i++) { + this.sz[i] = 1; + } + } + + @Override + public int find(int p) { + if (p >= id.length) { + return -1; + } + while (p != id[p]) { + p = id[p]; + } + return p; + } + + @Override + public void union(int p, int q) { + + int i = find(p); + int j = find(q); + + if (i == j) { + return; + } + + if (sz[i] < sz[j]) { + id[i] = j; + sz[j] += sz[i]; + } else { + id[j] = i; + sz[i] += sz[j]; + } + } +} diff --git a/code/src/main/java/com/raorao/leetcode/q039/CombinationSum.java b/code/src/main/java/com/raorao/leetcode/q039/CombinationSum.java new file mode 100644 index 00000000..4a1d9356 --- /dev/null +++ b/code/src/main/java/com/raorao/leetcode/q039/CombinationSum.java @@ -0,0 +1,45 @@ +package com.raorao.leetcode.q039; + +import java.util.ArrayList; +import java.util.List; + +/** + * 组合求和. + * + * @author Xiong Raorao + * @since 2018-08-27-10:44 + */ +public class CombinationSum { + + public static void main(String[] args) { + int[] candidates = new int[] {2, 3, 6, 7}; + int target = 7; + List> res = new CombinationSum().combinationSum(candidates, target); + res.forEach(e -> { + e.forEach(f -> System.out.print(f + " ")); + System.out.println(); + }); + } + + + public List> combinationSum(int[] candidates, int target) { + List> sums = new ArrayList<>(); + backTracking(candidates, new ArrayList<>(), sums, 0, target); + return sums; + } + + private void backTracking(int[] candidates, ArrayList tempList, List> sums, + int start, int target) { + if (target == 0) { + sums.add(new ArrayList<>(tempList)); + return; + } + for (int i = start; i < candidates.length; i++) { + if (candidates[i] <= target) { + tempList.add(candidates[i]); + backTracking(candidates, tempList, sums, i , target - candidates[i]); + tempList.remove(tempList.size() - 1); + } + } + } +} diff --git a/code/src/main/java/com/raorao/leetcode/q077/Combinations.java b/code/src/main/java/com/raorao/leetcode/q077/Combinations.java new file mode 100644 index 00000000..4872da22 --- /dev/null +++ b/code/src/main/java/com/raorao/leetcode/q077/Combinations.java @@ -0,0 +1,43 @@ +package com.raorao.leetcode.q077; + +import java.util.ArrayList; +import java.util.List; + +/** + * 组合数. + * + * @author Xiong Raorao + * @since 2018-08-27-10:29 + */ +public class Combinations { + + public static void main(String[] args) { + List> res = new Combinations().combine(4, 2); + res.forEach(e -> { + e.forEach(f -> System.out.print(f + " ")); + System.out.println(); + }); + + } + + public List> combine(int n, int k) { + List> combinations = new ArrayList<>(); + List combineList = new ArrayList<>(); + backtracking(combineList, combinations, 1, k, n); + return combinations; + } + + private void backtracking(List combineList, List> combinations, int start, + int k, final int n) { + if (k == 0) { + combinations.add(new ArrayList<>(combineList)); + return; + } + for (int i = start; i <= n - k + 1; i++) { // 剪枝 + combineList.add(i); + backtracking(combineList, combinations, i + 1, k - 1, n); + combineList.remove(combineList.size() - 1); + } + } + +} diff --git a/interview/note.md b/interview/note.md index 5d1c176a..e9a29c06 100644 --- a/interview/note.md +++ b/interview/note.md @@ -41,8 +41,9 @@ Java后端开发(大数据、分布式应用等) 华为 |8.1 | 8.21 百度 | 8.22 | 8.22(新投递) | 网易 | 8.22 | 8.22(新投递) | -腾讯 | 8.22 | 8.22(新投递) +腾讯 | 8.22 | 8.22(新投递) | 电面 | 一面已经挂了 Intel | 8.23 | 8.23(新投递) | 电面 | 8.24一面 +美团点评 | 8.6 | 8.27 | # 2 复习内容 diff --git a/notes/Docker.md b/notes/Docker.md index f230f762..80108896 100644 --- a/notes/Docker.md +++ b/notes/Docker.md @@ -30,15 +30,15 @@ Docker 主要解决环境配置问题,它是一种虚拟化技术,对进程 ## 启动速度 -启动虚拟机需要启动虚拟机的操作系统,再启动相应的应用,这个过程会非常慢; +启动虚拟机需要启动虚拟机的操作系统,再启动应用,这个过程非常慢; 而启动 Docker 相当于启动宿主操作系统上的一个进程。 ## 占用资源 -虚拟机是一个完整的操作系统,需要占用大量的磁盘空间、内存和 CPU,一台机器只能开启几十个的虚拟机。 +虚拟机是一个完整的操作系统,需要占用大量的磁盘、内存和 CPU,一台机器只能开启几十个的虚拟机。 -而 Docker 只是一个进程,只需要将应用以及相应的组件打包,在运行时占用很少的资源,一台机器可以开启成千上万个 Docker。 +而 Docker 只是一个进程,只需要将应用以及相关的组件打包,在运行时占用很少的资源,一台机器可以开启成千上万个 Docker。 参考资料: @@ -58,7 +58,7 @@ Docker 使用分层技术和镜像,使得应用可以更容易复用重复部 ## 更容易扩展 -可以使用基础镜像进一步扩展得到新的镜像,并且官方和开源社区提供了大量的镜像,通过扩展这些镜像得到我们想要的镜像非常容易。 +可以使用基础镜像进一步扩展得到新的镜像,并且官方和开源社区提供了大量的镜像,通过扩展这些镜像可以非常容易得到我们想要的镜像。 参考资料: @@ -91,7 +91,7 @@ Docker 轻量级的特点使得它很适合用于部署、维护、组合微服 镜像包含着容器运行时所需要的代码以及其它组件,它是一种分层结构,每一层都是只读的(read-only layers)。构建镜像时,会一层一层构建,前一层是后一层的基础。镜像的这种分层存储结构很适合镜像的复用以及定制。 -在构建容器时,通过在镜像的基础上添加一个可写层(writable layer),用来保存着容器运行过程中的修改。 +构建容器时,通过在镜像的基础上添加一个可写层(writable layer),用来保存着容器运行过程中的修改。

@@ -100,3 +100,4 @@ Docker 轻量级的特点使得它很适合用于部署、维护、组合微服 - [How to Create Docker Container using Dockerfile](https://linoxide.com/linux-how-to/dockerfile-create-docker-container/) - [理解 Docker(2):Docker 镜像](http://www.cnblogs.com/sammyliu/p/5877964.html) + diff --git a/notes/Java 基础.md b/notes/Java 基础.md index 44ca873a..5b689cf8 100644 --- a/notes/Java 基础.md +++ b/notes/Java 基础.md @@ -6,7 +6,8 @@ * [概览](#概览) * [不可变的好处](#不可变的好处) * [String, StringBuffer and StringBuilder](#string,-stringbuffer-and-stringbuilder) - * [String.intern()](#stringintern) + * [String Pool](#string-pool) + * [new String("abc")](#new-string"abc") * [三、运算](#三运算) * [参数传递](#参数传递) * [float 与 double](#float-与-double) @@ -64,7 +65,7 @@ int y = x; // 拆箱 new Integer(123) 与 Integer.valueOf(123) 的区别在于: -- new Integer(123) 每次都会新建一个对象 +- new Integer(123) 每次都会新建一个对象; - Integer.valueOf(123) 会使用缓存池中的对象,多次调用会取得同一个对象的引用。 ```java @@ -193,33 +194,90 @@ String 不可变性天生具备线程安全,可以在多个线程中安全地 [StackOverflow : String, StringBuffer, and StringBuilder](https://stackoverflow.com/questions/2971315/string-stringbuffer-and-stringbuilder) -## String.intern() +## String Pool -使用 String.intern() 可以保证相同内容的字符串变量引用同一的内存对象。 +字符串常量池(String Poll)保存着所有字符串字面量(literal strings),这些字面量在编译时期就确定。不仅如此,还可以使用 String 的 intern() 方法在运行过程中将字符串添加到 String Poll 中。 -下面示例中,s1 和 s2 采用 new String() 的方式新建了两个不同对象,而 s3 是通过 s1.intern() 方法取得一个对象引用。intern() 首先把 s1 引用的对象放到 String Pool(字符串常量池)中,然后返回这个对象引用。因此 s3 和 s1 引用的是同一个字符串常量池的对象。 +当一个字符串调用 intern() 方法时,如果 String Poll 中已经存在一个字符串和该字符串值相等(使用 equals() 方法进行确定),那么就会返回 String Poll 中字符串的引用;否则,就会在 String Poll 中添加一个新的字符串,并返回这个新字符串的引用。 + +下面示例中,s1 和 s2 采用 new String() 的方式新建了两个不同字符串,而 s3 和 s4 是通过 s1.intern() 方法取得一个字符串引用。intern() 首先把 s1 引用的字符串放到 String Pool 中,然后返回这个字符串引用。因此 s3 和 s4 引用的是同一个字符串。 ```java String s1 = new String("aaa"); String s2 = new String("aaa"); System.out.println(s1 == s2); // false String s3 = s1.intern(); -System.out.println(s1.intern() == s3); // true +String s4 = s1.intern(); +System.out.println(s3 == s4); // true ``` -如果是采用 "bbb" 这种使用双引号的形式创建字符串实例,会自动地将新建的对象放入 String Pool 中。 +如果是采用 "bbb" 这种字面量的形式创建字符串,会自动地将字符串放入 String Pool 中。 ```java -String s4 = "bbb"; String s5 = "bbb"; +String s6 = "bbb"; System.out.println(s4 == s5); // true ``` -在 Java 7 之前,字符串常量池被放在运行时常量池中,它属于永久代。而在 Java 7,字符串常量池被移到 Native Method 中。这是因为永久代的空间有限,在大量使用字符串的场景下会导致 OutOfMemoryError 错误。 +在 Java 7 之前,String Poll 被放在运行时常量池中,它属于永久代。而在 Java 7,String Poll 被移到堆中。这是因为永久代的空间有限,在大量使用字符串的场景下会导致 OutOfMemoryError 错误。 - [StackOverflow : What is String interning?](https://stackoverflow.com/questions/10578984/what-is-string-interning) - [深入解析 String#intern](https://tech.meituan.com/in_depth_understanding_string_intern.html) +## new String("abc") + +使用这种方式一共会创建两个字符串对象(前提是 String Poll 中还没有 "abc" 字符串对象)。 + +- "abc" 属于字符串字面量,因此编译时期会在 String Poll 中创建一个字符串对象,指向这个 "abc" 字符串字面量; +- 而使用 new 的方式会在堆中创建一个字符串对象。 + +创建一个测试类,其 main 方法中使用这种方式来创建字符串对象。 + +```java +public class NewStringTest { + public static void main(String[] args) { + String s = new String("abc"); + } +} +``` + +使用 javap -verbose 进行反编译,得到以下内容: + +```java +// ... +Constant pool: +// ... + #2 = Class #18 // java/lang/String + #3 = String #19 // abc +// ... + #18 = Utf8 java/lang/String + #19 = Utf8 abc +// ... + + public static void main(java.lang.String[]); + descriptor: ([Ljava/lang/String;)V + flags: ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=2, args_size=1 + 0: new #2 // class java/lang/String + 3: dup + 4: ldc #3 // String abc + 6: invokespecial #4 // Method java/lang/String."":(Ljava/lang/String;)V + 9: astore_1 +// ... +``` + +在 Constant Poll 中,#19 存储这字符串字面量 "abc",#3 是 String Poll 的字符串对象,它指向 #19 这个字符串字面量。在 main 方法中,0: 行使用 new #2 在堆中创建一个字符串对象,并且使用 ldc #3 将 String Poll 中的字符串对象作为 String 构造函数的参数。 + +以下是 String 构造函数的源码,可以看到,在将一个字符串对象作为另一个字符串对象的构造函数参数时,并不会完全复制 value 数组内容,而是都会指向同一个 value 数组。 + +```java +public String(String original) { + this.value = original.value; + this.hash = original.hash; +} +``` + # 三、运算 ## 参数传递 @@ -403,6 +461,7 @@ public class AccessExample { ```java public class AccessWithInnerClassExample { + private class InnerClass { int x; } @@ -468,6 +527,7 @@ ac2.func1(); ```java public interface InterfaceExample { + void func1(); default void func2(){ @@ -519,7 +579,7 @@ System.out.println(InterfaceExample.x); - 需要能控制继承来的成员的访问权限,而不是都为 public。 - 需要继承非静态和非常量字段。 -在很多情况下,接口优先于抽象类,因为接口没有抽象类严格的类层次结构要求,可以灵活地为一个类添加行为。并且从 Java 8 开始,接口也可以有默认的方法实现,使得修改接口的成本也变的很低。 +在很多情况下,接口优先于抽象类。因为接口没有抽象类严格的类层次结构要求,可以灵活地为一个类添加行为。并且从 Java 8 开始,接口也可以有默认的方法实现,使得修改接口的成本也变的很低。 - [深入理解 abstract class 和 interface](https://www.ibm.com/developerworks/cn/java/l-javainterface-abstract/) - [When to Use Abstract Class and Interface](https://dzone.com/articles/when-to-use-abstract-class-and-intreface) @@ -660,7 +720,7 @@ x.equals(y) == x.equals(y); // true x.equals(null); // false; ``` -**2. equals() 与 ==** +**2. 等价与相等** - 对于基本类型,== 判断两个值是否相等,基本类型没有 equals() 方法。 - 对于引用类型,== 判断两个变量是否引用同一个对象,而 equals() 判断引用的对象是否等价。 @@ -723,7 +783,7 @@ set.add(e2); System.out.println(set.size()); // 2 ``` -理想的散列函数应当具有均匀性,即不相等的对象应当均匀分布到所有可能的散列值上。这就要求了散列函数要把所有域的值都考虑进来,可以将每个域都当成 R 进制的某一位,然后组成一个 R 进制的整数。R 一般取 31,因为它是一个奇素数,如果是偶数的话,当出现乘法溢出,信息就会丢失,因为与 2 相乘相当于向左移一位。 +理想的散列函数应当具有均匀性,即不相等的对象应当均匀分布到所有可能的散列值上。这就要求了散列函数要把所有域的值都考虑进来。可以将每个域都当成 R 进制的某一位,然后组成一个 R 进制的整数。R 一般取 31,因为它是一个奇素数,如果是偶数的话,当出现乘法溢出,信息就会丢失,因为与 2 相乘相当于向左移一位。 一个数与 31 相乘可以转换成移位和减法:`31*x == (x<<5)-x`,编译器会自动进行这个优化。 @@ -828,6 +888,7 @@ public class CloneExample implements Cloneable { ```java public class ShallowCloneExample implements Cloneable { + private int[] arr; public ShallowCloneExample() { @@ -870,6 +931,7 @@ System.out.println(e2.get(2)); // 222 ```java public class DeepCloneExample implements Cloneable { + private int[] arr; public DeepCloneExample() { @@ -917,6 +979,7 @@ System.out.println(e2.get(2)); // 2 ```java public class CloneConstructorExample { + private int[] arr; public CloneConstructorExample() { @@ -982,7 +1045,7 @@ private 方法隐式地被指定为 final,如果在子类中定义的方法和 **1. 静态变量** -- 静态变量:又称为类变量,也就是说这个变量属于类的,类所有的实例都共享静态变量,可以直接通过类名来访问它;静态变量在内存中只存在一份。 +- 静态变量:又称为类变量,也就是说这个变量属于类的,类所有的实例都共享静态变量,可以直接通过类名来访问它。静态变量在内存中只存在一份。 - 实例变量:每创建一个实例就会产生一个实例变量,它与该实例同生共死。 ```java @@ -1001,7 +1064,7 @@ public class A { **2. 静态方法** -静态方法在类加载的时候就存在了,它不依赖于任何实例。所以静态方法必须有实现,也就是说它不能是抽象方法(abstract)。 +静态方法在类加载的时候就存在了,它不依赖于任何实例。所以静态方法必须有实现,也就是说它不能是抽象方法。 ```java public abstract class A { @@ -1124,7 +1187,7 @@ public InitialOrderTest() { 每个类都有一个 **Class** 对象,包含了与类有关的信息。当编译一个新类时,会产生一个同名的 .class 文件,该文件内容保存着 Class 对象。 -类加载相当于 Class 对象的加载。类在第一次使用时才动态加载到 JVM 中,可以使用 `Class.forName("com.mysql.jdbc.Driver")` 这种方式来控制类的加载,该方法会返回一个 Class 对象。 +类加载相当于 Class 对象的加载,类在第一次使用时才动态加载到 JVM 中。也可以使用 `Class.forName("com.mysql.jdbc.Driver")` 这种方式来控制类的加载,该方法会返回一个 Class 对象。 反射可以提供运行时的类信息,并且这个类可以在运行时才加载进来,甚至在编译时期该类的 .class 不存在也可以加载进来。 @@ -1221,7 +1284,7 @@ Java 注解是附加在代码中的一些元信息,用于一些工具在编译 - Java 没有指针,它的引用可以理解为安全指针,而 C++ 具有和 C 一样的指针。 - Java 支持自动垃圾回收,而 C++ 需要手动回收。 - Java 不支持多重继承,只能通过实现多个接口来达到相同目的,而 C++ 支持多重继承。 -- Java 不支持操作符重载,虽然可以对两个 String 对象支持加法运算,但是这是语言内置支持的操作,不属于操作符重载,而 C++ 可以。 +- Java 不支持操作符重载,虽然可以对两个 String 对象执行加法运算,但是这是语言内置支持的操作,不属于操作符重载,而 C++ 可以。 - Java 的 goto 是保留字,但是不可用,C++ 可以使用 goto。 - Java 不支持条件编译,C++ 通过 #ifdef #ifndef 等预处理命令从而实现条件编译。 diff --git a/notes/Java 容器.md b/notes/Java 容器.md index 001f864c..57e5f475 100644 --- a/notes/Java 容器.md +++ b/notes/Java 容器.md @@ -126,7 +126,91 @@ public class ArrayList extends AbstractList private static final int DEFAULT_CAPACITY = 10; ``` -### 2. 序列化 +### 2. 扩容 + +添加元素时使用 ensureCapacityInternal() 方法来保证容量足够,如果不够时,需要使用 grow() 方法进行扩容,新容量的大小为 `oldCapacity + (oldCapacity >> 1)`,也就是旧容量的 1.5 倍。 + +扩容操作需要调用 `Arrays.copyOf()` 把原数组整个复制到新数组中,这个操作代价很高,因此最好在创建 ArrayList 对象时就指定大概的容量大小,减少扩容操作的次数。 + +```java +public boolean add(E e) { + ensureCapacityInternal(size + 1); // Increments modCount!! + elementData[size++] = e; + return true; +} + +private void ensureCapacityInternal(int minCapacity) { + if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { + minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); + } + ensureExplicitCapacity(minCapacity); +} + +private void ensureExplicitCapacity(int minCapacity) { + modCount++; + // overflow-conscious code + if (minCapacity - elementData.length > 0) + grow(minCapacity); +} + +private void grow(int minCapacity) { + // overflow-conscious code + int oldCapacity = elementData.length; + int newCapacity = oldCapacity + (oldCapacity >> 1); + if (newCapacity - minCapacity < 0) + newCapacity = minCapacity; + if (newCapacity - MAX_ARRAY_SIZE > 0) + newCapacity = hugeCapacity(minCapacity); + // minCapacity is usually close to size, so this is a win: + elementData = Arrays.copyOf(elementData, newCapacity); +} +``` + +### 3. 删除元素 + +需要调用 System.arraycopy() 将 index+1 后面的元素都复制到 index 位置上,该操作的时间复杂度为 O(N),可以看出 ArrayList 删除元素的代价是非常高的。 + +```java +public E remove(int index) { + rangeCheck(index); + modCount++; + E oldValue = elementData(index); + int numMoved = size - index - 1; + if (numMoved > 0) + System.arraycopy(elementData, index+1, elementData, index, numMoved); + elementData[--size] = null; // clear to let GC do its work + return oldValue; +} +``` + +### 4. Fail-Fast + +modCount 用来记录 ArrayList 结构发生变化的次数。结构发生变化是指添加或者删除至少一个元素的所有操作,或者是调整内部数组的大小,仅仅只是设置元素的值不算结构发生变化。 + +在进行序列化或者迭代等操作时,需要比较操作前后 modCount 是否改变,如果改变了需要抛出 ConcurrentModificationException。 + +```java +private void writeObject(java.io.ObjectOutputStream s) + throws java.io.IOException{ + // Write out element count, and any hidden stuff + int expectedModCount = modCount; + s.defaultWriteObject(); + + // Write out size as capacity for behavioural compatibility with clone() + s.writeInt(size); + + // Write out all elements in the proper order. + for (int i=0; i> 1)`,也就是旧容量的 1.5 倍。 - -扩容操作需要调用 `Arrays.copyOf()` 把原数组整个复制到新数组中,这个操作代价很高,因此最好在创建 ArrayList 对象时就指定大概的容量大小,减少扩容操作的次数。 - -```java -public boolean add(E e) { - ensureCapacityInternal(size + 1); // Increments modCount!! - elementData[size++] = e; - return true; -} - -private void ensureCapacityInternal(int minCapacity) { - if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { - minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); - } - ensureExplicitCapacity(minCapacity); -} - -private void ensureExplicitCapacity(int minCapacity) { - modCount++; - // overflow-conscious code - if (minCapacity - elementData.length > 0) - grow(minCapacity); -} - -private void grow(int minCapacity) { - // overflow-conscious code - int oldCapacity = elementData.length; - int newCapacity = oldCapacity + (oldCapacity >> 1); - if (newCapacity - minCapacity < 0) - newCapacity = minCapacity; - if (newCapacity - MAX_ARRAY_SIZE > 0) - newCapacity = hugeCapacity(minCapacity); - // minCapacity is usually close to size, so this is a win: - elementData = Arrays.copyOf(elementData, newCapacity); -} -``` - -### 4. 删除元素 - -需要调用 System.arraycopy() 将 index+1 后面的元素都复制到 index 位置上,该操作的时间复杂度为 O(N),可以看出 ArrayList 删除元素的代价是非常高的。 - -```java -public E remove(int index) { - rangeCheck(index); - modCount++; - E oldValue = elementData(index); - int numMoved = size - index - 1; - if (numMoved > 0) - System.arraycopy(elementData, index+1, elementData, index, numMoved); - elementData[--size] = null; // clear to let GC do its work - return oldValue; -} -``` - -### 5. Fail-Fast - -modCount 用来记录 ArrayList 结构发生变化的次数。结构发生变化是指添加或者删除至少一个元素的所有操作,或者是调整内部数组的大小,仅仅只是设置元素的值不算结构发生变化。 - -在进行序列化或者迭代等操作时,需要比较操作前后 modCount 是否改变,如果改变了需要抛出 ConcurrentModificationException。 - -```java -private void writeObject(java.io.ObjectOutputStream s) - throws java.io.IOException{ - // Write out element count, and any hidden stuff - int expectedModCount = modCount; - s.defaultWriteObject(); - - // Write out size as capacity for behavioural compatibility with clone() - s.writeInt(size); - - // Write out all elements in the proper order. - for (int i=0; i implements Map.Entry { public final String toString() { return getKey() + "=" + getValue(); } - - /** - * This method is invoked whenever the value in an entry is - * overwritten by an invocation of put(k,v) for a key k that's already - * in the HashMap. - */ - void recordAccess(HashMap m) { - } - - /** - * This method is invoked whenever the entry is - * removed from the table. - */ - void recordRemoval(HashMap m) { - } } ``` @@ -929,7 +914,7 @@ JDK 1.8 使用了 CAS 操作来支持更高的并发度,在 CAS 操作失败 public class LinkedHashMap extends HashMap implements Map ``` -内存维护了一个双向链表,用来维护插入顺序或者 LRU 顺序。 +内部维护了一个双向链表,用来维护插入顺序或者 LRU 顺序。 ```java /** diff --git a/notes/Java 并发.md b/notes/Java 并发.md index 64f9e7d6..1403f764 100644 --- a/notes/Java 并发.md +++ b/notes/Java 并发.md @@ -184,10 +184,7 @@ public static void main(String[] args) { mt.start(); } ``` -## 三种实现方式的比较 -- 实现Runnable接又可以避免Java单继承特性而带来的局限;增强程序的健壮性,代码能够被多个线程共享,代码与数据是独立的;适合多个相同程序代码的线程区处理同一资源的情况。 -- 继承Thread类和实现Runnable方法启动线 程都是使用start方法,然后JVM虚拟机将此线程放到就绪队列中,如果有处理机可用, 则执行run方法。 -- 实现Callable接又要实现call方法,并且线 程执行完毕后会有返回值。其他的两种都是 重写run方法,没有返回值。 + ## 实现接口 VS 继承 Thread 实现接口会更好一些,因为: @@ -1023,7 +1020,7 @@ ForkJoinPool 实现了工作窃取算法来提高 CPU 的利用率。每个线 如果多个线程对同一个共享数据进行访问而不采取同步操作的话,那么操作的结果是不一致的。 -以下代码演示了 1000 个线程同时对 cnt 执行自增操作,操作结束之后它的值为 997 而不是 1000。 +以下代码演示了 1000 个线程同时对 cnt 执行自增操作,操作结束之后它的值有可能小于 1000。 ```java public class ThreadUnsafeExample { diff --git a/notes/Java 虚拟机.md b/notes/Java 虚拟机.md index a4a4ba12..2b565cb5 100644 --- a/notes/Java 虚拟机.md +++ b/notes/Java 虚拟机.md @@ -38,7 +38,7 @@ ## Java 虚拟机栈 -每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息,从调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。 +每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息。从方法调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。

@@ -55,27 +55,21 @@ java -Xss512M HackTheJava ## 本地方法栈 -本地方法一般是用其它语言(C、C++ 或汇编语言等)编写的,并且被编译为基于本机硬件和操作系统的程序,对待这些方法需要特别处理。 - 本地方法栈与 Java 虚拟机栈类似,它们之间的区别只不过是本地方法栈为本地方法服务。 +本地方法一般是用其它语言(C、C++ 或汇编语言等)编写的,并且被编译为基于本机硬件和操作系统的程序,对待这些方法需要特别处理。 +

## 堆 所有对象都在这里分配内存,是垃圾收集的主要区域("GC 堆")。 -现代的垃圾收集器基本都是采用分代收集算法,针对不同类型的对象采取不同的垃圾回收算法,可以将堆分成两块: +现代的垃圾收集器基本都是采用分代收集算法,其主要的思想是针对不同类型的对象采取不同的垃圾回收算法,可以将堆分成两块: - 新生代(Young Generation) - 老年代(Old Generation) -新生代可以继续划分成以下三个空间: - -- Eden(伊甸园) -- From Survivor(幸存者) -- To Survivor - 堆不需要连续内存,并且可以动态增加其内存,增加失败会抛出 OutOfMemoryError 异常。 可以通过 -Xms 和 -Xmx 两个虚拟机参数来指定一个程序的堆内存大小,第一个参数设置初始值,第二个参数设置最大值。 @@ -92,7 +86,7 @@ java -Xms1M -Xmx2M HackTheJava 对这块区域进行垃圾回收的主要目标是对常量池的回收和对类的卸载,但是一般比较难实现。 -JDK 1.7 之前,HotSpot 虚拟机把它当成永久代来进行垃圾回收。但是从 JDK 1.7 开始,已经把原本放在永久代的字符串常量池移到 Native Method 中。 +HotSpot 虚拟机把它当成永久代来进行垃圾回收。但是很难确定永久代的大小,因为它受到很多因素影响,并且每次 Full GC 之后永久代的大小都会改变,所以经常会抛出 OutOfMemoryError 异常。为了更容易管理方法区,从 JDK 1.8 开始,移除永久代,并把方法区移至元空间,它位于本地内存中,而不是虚拟机内存中。 ## 运行时常量池 @@ -104,7 +98,7 @@ Class 文件中的常量池(编译器生成的各种字面量和符号引用 ## 直接内存 -在 JDK 1.4 中新加入了 NIO 类,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆里的 DirectByteBuffer 对象作为这块内存的引用进行操作。 +在 JDK 1.4 中新加入了 NIO 类,它可以使用 Native 函数库直接分配堆外内存(Native 堆),然后通过一个存储在 Java 堆里的 DirectByteBuffer 对象作为这块内存的引用进行操作。 这样能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆中来回复制数据。 @@ -142,8 +136,6 @@ public class ReferenceCountingGC { 通过 GC Roots 作为起始点进行搜索,能够到达到的对象都是存活的,不可达的对象可被回收。 -

- Java 虚拟机使用该算法来判断对象是否可被回收,在 Java 中 GC Roots 一般包含以下内容: - 虚拟机栈中引用的对象 @@ -151,6 +143,8 @@ Java 虚拟机使用该算法来判断对象是否可被回收,在 Java 中 GC - 方法区中类静态属性引用的对象 - 方法区中的常量引用的对象 +

+ ### 3. 方法区的回收 因为方法区主要存放永久代对象,而永久代对象的回收率比新生代低很多,因此在方法区上进行回收性价比不高。 @@ -171,13 +165,13 @@ Java 虚拟机使用该算法来判断对象是否可被回收,在 Java 中 GC finalize() 类似 C++ 的析构函数,用来做关闭外部资源等工作。但是 try-finally 等方式可以做的更好,并且该方法运行代价高昂,不确定性大,无法保证各个对象的调用顺序,因此最好不要使用。 -当一个对象可被回收时,如果需要执行该对象的 finalize() 方法,那么就有可能通过在该方法中让对象重新被引用,从而实现自救。自救只能进行一次,如果回收的对象之前调用了 finalize() 方法自救,后面回收时不会调用 finalize() 方法。 +当一个对象可被回收时,如果需要执行该对象的 finalize() 方法,那么就有可能在该方法中让对象重新被引用,从而实现自救。自救只能进行一次,如果回收的对象之前调用了 finalize() 方法自救,后面回收时不会调用 finalize() 方法。 ## 引用类型 无论是通过引用计算算法判断对象的引用数量,还是通过可达性分析算法判断对象是否可达,判定对象是否可被回收都与引用有关。 -Java 具有四种强度不同的引用类型。 +Java 提供了四种强度不同的引用类型。 ### 1. 强引用 @@ -286,7 +280,7 @@ Serial 翻译为串行,也就是说它以串行的方式执行。 它的优点是简单高效,对于单个 CPU 环境来说,由于没有线程交互的开销,因此拥有最高的单线程收集效率。 -它是 Client 模式下的默认新生代收集器,因为在用户的桌面应用场景下,分配给虚拟机管理的内存一般来说不会很大。Serial 收集器收集几十兆甚至一两百兆的新生代停顿时间可以控制在一百多毫秒以内,只要不是太频繁,这点停顿是可以接受的。 +它是 Client 模式下的默认新生代收集器,因为在该应用场景下,分配给虚拟机管理的内存一般来说不会很大。Serial 收集器收集几十兆甚至一两百兆的新生代停顿时间可以控制在一百多毫秒以内,只要不是太频繁,这点停顿是可以接受的。 ### 2. ParNew 收集器 @@ -304,11 +298,11 @@ Serial 翻译为串行,也就是说它以串行的方式执行。 其它收集器关注点是尽可能缩短垃圾收集时用户线程的停顿时间,而它的目标是达到一个可控制的吞吐量,它被称为“吞吐量优先”收集器。这里的吞吐量指 CPU 用于运行用户代码的时间占总时间的比值。 -停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户体验。而高吞吐量则可以高效率地利用 CPU 时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。 +停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户体验。而高吞吐量则可以高效率地利用 CPU 时间,尽快完成程序的运算任务,适合在后台运算而不需要太多交互的任务。 缩短停顿时间是以牺牲吞吐量和新生代空间来换取的:新生代空间变小,垃圾回收变得频繁,导致吞吐量下降。 -可以通过一个开关参数打卡 GC 自适应的调节策略(GC Ergonomics),就不需要手工指定新生代的大小(-Xmn)、Eden 和 Survivor 区的比例、晋升老年代对象年龄等细节参数了。虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量,这种方式称为 。 +可以通过一个开关参数打开 GC 自适应的调节策略(GC Ergonomics),就不需要手工指定新生代的大小(-Xmn)、Eden 和 Survivor 区的比例、晋升老年代对象年龄等细节参数了。虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量。 ### 4. Serial Old 收集器 @@ -378,8 +372,6 @@ G1 把堆划分成多个大小相等的独立区域(Region),新生代和 - 空间整合:整体来看是基于“标记 - 整理”算法实现的收集器,从局部(两个 Region 之间)上来看是基于“复制”算法实现的,这意味着运行期间不会产生内存空间碎片。 - 可预测的停顿:能让使用者明确指定在一个长度为 M 毫秒的时间片段内,消耗在 GC 上的时间不得超过 N 毫秒。 -更详细内容请参考:[Getting Started with the G1 Garbage Collector](http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/index.html) - # 三、内存分配与回收策略 ## Minor GC 和 Full GC @@ -539,7 +531,7 @@ public class Test { - 与类的构造函数(或者说实例构造器 <init>())不同,不需要显式的调用父类的构造器。虚拟机会自动保证在子类的 <clinit>() 方法运行之前,父类的 <clinit>() 方法已经执行结束。因此虚拟机中第一个执行 <clinit>() 方法的类肯定为 java.lang.Object。 -- 由于父类的 <clinit>() 方法先执行,也就意味着父类中定义的静态语句块要优先于子类的变量赋值操作。例如以下代码: +- 由于父类的 <clinit>() 方法先执行,也就意味着父类中定义的静态语句块的执行要优先于子类。例如以下代码: ```java static class Parent { @@ -744,6 +736,7 @@ public class FileSystemClassLoader extends ClassLoader { - 周志明. 深入理解 Java 虚拟机 [M]. 机械工业出版社, 2011. - [Chapter 2. The Structure of the Java Virtual Machine](https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.5.4) - [Jvm memory](https://www.slideshare.net/benewu/jvm-memory) +[Getting Started with the G1 Garbage Collector](http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/index.html) - [JNI Part1: Java Native Interface Introduction and “Hello World” application](http://electrofriends.com/articles/jni/jni-part1-java-native-interface/) - [Memory Architecture Of JVM(Runtime Data Areas)](https://hackthejava.wordpress.com/2015/01/09/memory-architecture-by-jvmruntime-data-areas/) - [JVM Run-Time Data Areas](https://www.programcreek.com/2013/04/jvm-run-time-data-areas/) diff --git a/notes/Leetcode 题解.md b/notes/Leetcode 题解.md index a6345741..8c77bd5d 100644 --- a/notes/Leetcode 题解.md +++ b/notes/Leetcode 题解.md @@ -3601,7 +3601,7 @@ public int countPrimes(int n) { ```java int gcd(int a, int b) { - return b == 0 ? a : gcd(b, a% b); + return b == 0 ? a : gcd(b, a % b); } ``` @@ -3672,7 +3672,7 @@ public String convertToBase7(int num) { } ``` -Java 中 static String toString(int num, int radix) 可以将一个整数转换为 redix 进制表示的字符串。 +Java 中 static String toString(int num, int radix) 可以将一个整数转换为 radix 进制表示的字符串。 ```java public String convertToBase7(int num) { @@ -6402,7 +6402,6 @@ public int maxChunksToSorted(int[] arr) { } ``` - ## 图 ### 二分图 @@ -6619,6 +6618,7 @@ public int[] findRedundantConnection(int[][] edges) { } private class UF { + private int[] id; UF(int N) { @@ -6664,7 +6664,7 @@ x ^ x = 0 x & x = x x | x = x ``` - 利用 x ^ 1s = \~x 的特点,可以将位级表示翻转;利用 x ^ x = 0 的特点,可以将三个数中重复的两个数去除,只留下另一个数。 -- 利用 x & 0s = 0 和 x & 1s = x 的特点,可以实现掩码操作。一个数 num 与 mask :00111100 进行位与操作,只保留 num 中与 mask 的 1 部分相对应的位。 +- 利用 x & 0s = 0 和 x & 1s = x 的特点,可以实现掩码操作。一个数 num 与 mask:00111100 进行位与操作,只保留 num 中与 mask 的 1 部分相对应的位。 - 利用 x | 0s = x 和 x | 1s = 1s 的特点,可以实现设值操作。一个数 num 与 mask:00111100 进行位或操作,将 num 中与 mask 的 1 部分相对应的位都设置为 1。 位与运算技巧: @@ -6909,7 +6909,6 @@ public boolean isPowerOfFour(int num) { } ``` - **判断一个数的位级表示是否不会出现连续的 0 和 1** [693. Binary Number with Alternating Bits (Easy)](https://leetcode.com/problems/binary-number-with-alternating-bits/description/) diff --git a/notes/Linux.md b/notes/Linux.md index 8ff3e2c6..73a2e9ec 100644 --- a/notes/Linux.md +++ b/notes/Linux.md @@ -1169,7 +1169,7 @@ dmtsai lines: 5 columns: 9 | S | interruptible sleep (waiting for an event to complete) | | Z | zombie (terminated but not reaped by its parent) | | T | stopped (either by a job control signal or because it is being traced) | - +

## SIGCHLD @@ -1179,12 +1179,12 @@ dmtsai lines: 5 columns: 9 - 得到 SIGCHLD 信号; - waitpid() 或者 wait() 调用会返回。 -

- 其中子进程发送的 SIGCHLD 信号包含了子进程的信息,包含了进程 ID、进程状态、进程使用 CPU 的时间等。 在子进程退出时,它的进程描述符不会立即释放,这是为了让父进程得到子进程信息,父进程通过 wait() 和 waitpid() 来获得一个已经退出的子进程的信息。 +

+ ## wait() ```c diff --git a/notes/MySQL.md b/notes/MySQL.md index 54067977..bf795d34 100644 --- a/notes/MySQL.md +++ b/notes/MySQL.md @@ -4,7 +4,7 @@ * [MySQL 索引](#mysql-索引) * [索引优化](#索引优化) * [索引的优点](#索引的优点) - * [索引的使用场景](#索引的使用场景) + * [索引的使用条件](#索引的使用条件) * [二、查询性能优化](#二查询性能优化) * [使用 Explain 进行分析](#使用-explain-进行分析) * [优化数据访问](#优化数据访问) @@ -58,13 +58,13 @@ B+ Tree 是基于 B Tree 和叶子节点顺序访问指针进行实现,它具 平衡树查找操作的时间复杂度等于树高 h,而树高大致为 O(h)=O(logdN),其中 d 为每个节点的出度。 -红黑树的出度为 2,而 B+ Tree 的出度一般都非常大,所以红黑树的树高 h 很明显比 B+ Tree 大非常多,检索的次数也就更多。 +红黑树的出度为 2,而 B+ Tree 的出度一般都非常大,所以红黑树的树高 h 很明显比 B+ Tree 大非常多,查找的次数也就更多。 -(二)利用计算机预读特性 +(二)利用磁盘预读特性 -为了减少磁盘 I/O,磁盘往往不是严格按需读取,而是每次都会预读。预读过程中,磁盘进行顺序读取,顺序读取不需要进行磁盘寻道,并且只需要很短的旋转时间,因此速度会非常快。 +为了减少磁盘 I/O,磁盘往往不是严格按需读取,而是每次都会预读。预读过程中,磁盘进行顺序读取,顺序读取不需要进行磁盘寻道,并且只需要很短的旋转时间,速度会非常快。 -操作系统一般将内存和磁盘分割成固态大小的块,每一块称为一页,内存与磁盘以页为单位交换数据。数据库系统将索引的一个节点的大小设置为页的大小,使得一次 I/O 就能完全载入一个节点,并且可以利用预读特性,相邻的节点也能够被预先载入。 +操作系统一般将内存和磁盘分割成固态大小的块,每一块称为一页,内存与磁盘以页为单位交换数据。数据库系统将索引的一个节点的大小设置为页的大小,使得一次 I/O 就能完全载入一个节点。并且可以利用预读特性,相邻的节点也能够被预先载入。 ## MySQL 索引 @@ -74,15 +74,15 @@ B+ Tree 是基于 B Tree 和叶子节点顺序访问指针进行实现,它具 是大多数 MySQL 存储引擎的默认索引类型。 -因为不再需要进行全表扫描,只需要对树进行搜索即可,因此查找速度快很多。除了用于查找,还可以用于排序和分组。 +因为不再需要进行全表扫描,只需要对树进行搜索即可,所以查找速度快很多。 + +除了用于查找,还可以用于排序和分组。 可以指定多个列作为索引列,多个索引列共同组成键。 适用于全键值、键值范围和键前缀查找,其中键前缀查找只适用于最左前缀查找。如果不是按照索引列的顺序进行查找,则无法使用索引。 -InnoDB 的 B+Tree 索引分为主索引和辅助索引。 - -主索引的叶子节点 data 域记录着完整的数据记录,这种索引方式被称为聚簇索引。因为无法把数据行存放在两个不同的地方,所以一个表只能有一个聚簇索引。 +InnoDB 的 B+Tree 索引分为主索引和辅助索引。主索引的叶子节点 data 域记录着完整的数据记录,这种索引方式被称为聚簇索引。因为无法把数据行存放在两个不同的地方,所以一个表只能有一个聚簇索引。

@@ -92,7 +92,7 @@ InnoDB 的 B+Tree 索引分为主索引和辅助索引。 ### 2. 哈希索引 -哈希索引能以 O(1) 时间进行查找,但是失去了有序性,它具有以下限制: +哈希索引能以 O(1) 时间进行查找,但是失去了有序性: - 无法用于排序与分组; - 只支持精确查找,无法用于部分查找和范围查找。 @@ -101,9 +101,11 @@ InnoDB 存储引擎有一个特殊的功能叫“自适应哈希索引”,当 ### 3. 全文索引 -MyISAM 存储引擎支持全文索引,用于查找文本中的关键词,而不是直接比较是否相等。查找条件使用 MATCH AGAINST,而不是普通的 WHERE。 +MyISAM 存储引擎支持全文索引,用于查找文本中的关键词,而不是直接比较是否相等。 -全文索引一般使用倒排索引实现,它记录着关键词到其所在文档的映射。 +查找条件使用 MATCH AGAINST,而不是普通的 WHERE。 + +全文索引使用倒排索引实现,它记录着关键词到其所在文档的映射。 InnoDB 存储引擎在 MySQL 5.6.4 版本中也开始支持全文索引。 @@ -136,7 +138,9 @@ WHERE actor_id = 1 AND film_id = 1; ### 3. 索引列的顺序 -让选择性最强的索引列放在前面,索引的选择性是指:不重复的索引值和记录总数的比值。最大值为 1,此时每个记录都有唯一的索引与其对应。选择性越高,查询效率也越高。 +让选择性最强的索引列放在前面。 + +索引的选择性是指:不重复的索引值和记录总数的比值。最大值为 1,此时每个记录都有唯一的索引与其对应。选择性越高,查询效率也越高。 例如下面显示的结果中 customer_id 的选择性比 staff_id 更高,因此最好把 customer_id 列放在多列索引的前面。 @@ -173,14 +177,16 @@ customer_id_selectivity: 0.0373 - 大大减少了服务器需要扫描的数据行数。 -- 帮助服务器避免进行排序和分组,也就不需要创建临时表(B+Tree 索引是有序的,可以用于 ORDER BY 和 GROUP BY 操作。临时表主要是在排序和分组过程中创建,因为不需要排序和分组,也就不需要创建临时表)。 +- 帮助服务器避免进行排序和分组,以及避免创建临时表(B+Tree 索引是有序的,可以用于 ORDER BY 和 GROUP BY 操作。临时表主要是在排序和分组过程中创建,因为不需要排序和分组,也就不需要创建临时表)。 -- 将随机 I/O 变为顺序 I/O(B+Tree 索引是有序的,也就将相邻的数据都存储在一起)。 +- 将随机 I/O 变为顺序 I/O(B+Tree 索引是有序的,会将相邻的数据都存储在一起)。 -## 索引的使用场景 +## 索引的使用条件 + +- 对于非常小的表、大部分情况下简单的全表扫描比建立索引更高效; + +- 对于中到大型的表,索引就非常有效; -- 对于非常小的表、大部分情况下简单的全表扫描比建立索引更高效。 -- 对于中到大型的表,索引就非常有效。 - 但是对于特大型的表,建立和维护索引的代价将会随之增长。这种情况下,需要用到一种技术可以直接区分出需要查询的一组数据,而不是一条记录一条记录地匹配,例如可以使用分区技术。 # 二、查询性能优化 @@ -348,17 +354,17 @@ MySQL 提供了 FROM_UNIXTIME() 函数把 UNIX 时间戳转换为日期,并提 ## 垂直切分 -

- 垂直切分是将一张表按列切分成多个表,通常是按照列的关系密集程度进行切分,也可以利用垂直切分将经常被使用的列和不经常被使用的列切分到不同的表中。 在数据库的层面使用垂直切分将按数据库中表的密集程度部署到不同的库中,例如将原来的电商数据库垂直切分成商品数据库、用户数据库等。 +

+ ## Sharding 策略 -- 哈希取模:hash(key) % NUM_DB -- 范围:可以是 ID 范围也可以是时间范围 -- 映射表:使用单独的一个数据库来存储映射关系 +- 哈希取模:hash(key) % N; +- 范围:可以是 ID 范围也可以是时间范围; +- 映射表:使用单独的一个数据库来存储映射关系。 ## Sharding 存在的问题及解决方案 @@ -376,20 +382,15 @@ MySQL 提供了 FROM_UNIXTIME() 函数把 UNIX 时间戳转换为日期,并提 - 为每个分片指定一个 ID 范围 - 分布式 ID 生成器 (如 Twitter 的 Snowflake 算法) -更多内容请参考: - -- [How Sharding Works](https://medium.com/@jeeyoungk/how-sharding-works-b4dec46b3f6) -- [大众点评订单系统分库分表实践](https://tech.meituan.com/dianping_order_db_sharding.html) - # 六、复制 ## 主从复制 主要涉及三个线程:binlog 线程、I/O 线程和 SQL 线程。 -- **binlog 线程** :负责将主服务器上的数据更改写入二进制日志中。 -- **I/O 线程** :负责从主服务器上读取二进制日志,并写入从服务器的中继日志中。 -- **SQL 线程** :负责读取中继日志并重放其中的 SQL 语句。 +- **binlog 线程** :负责将主服务器上的数据更改写入二进制日志(Binary log)中。 +- **I/O 线程** :负责从主服务器上读取二进制日志,并写入从服务器的重放日志(Replay log)中。 +- **SQL 线程** :负责读取重放日志并重放其中的 SQL 语句。

@@ -417,3 +418,5 @@ MySQL 提供了 FROM_UNIXTIME() 函数把 UNIX 时间戳转换为日期,并提 - [SQL Azure Federation – Introduction](http://geekswithblogs.net/shaunxu/archive/2012/01/07/sql-azure-federation-ndash-introduction.aspx "Title of this entry.") - [MySQL 索引背后的数据结构及算法原理](http://blog.codinglabs.org/articles/theory-of-mysql-index.html) - [MySQL 性能优化神器 Explain 使用分析](https://segmentfault.com/a/1190000008131735) +- [How Sharding Works](https://medium.com/@jeeyoungk/how-sharding-works-b4dec46b3f6) +- [大众点评订单系统分库分表实践](https://tech.meituan.com/dianping_order_db_sharding.html) diff --git a/notes/Redis.md b/notes/Redis.md index 8f231382..629fcdd8 100644 --- a/notes/Redis.md +++ b/notes/Redis.md @@ -549,7 +549,7 @@ def main(): # 十二、Sentinel -Sentinel(哨兵)可以监听主服务器,并在主服务器进入下线状态时,自动从从服务器中选举出新的主服务器。 +Sentinel(哨兵)可以监听集群中的服务器,并在主服务器进入下线状态时,自动从从服务器中选举出新的主服务器。 # 十三、分片 diff --git a/notes/分布式.md b/notes/分布式.md index c048b43e..e98f2860 100644 --- a/notes/分布式.md +++ b/notes/分布式.md @@ -19,10 +19,11 @@ * [五、Paxos](#五paxos) * [执行过程](#执行过程) * [约束条件](#约束条件) -* [五、Raft](#五raft) +* [六、Raft](#六raft) * [单个 Candidate 的竞选](#单个-candidate-的竞选) * [多个 Candidate 竞选](#多个-candidate-竞选) - * [日志复制](#日志复制) + * [数据同步](#数据同步) +* [参考](#参考) @@ -35,7 +36,7 @@ - 互斥量为 1 表示有其它进程在使用锁,此时处于锁定状态; - 互斥量为 0 表示未锁定状态。 -1 和 0 可以用一个整型值表示,也可以用某个数据存在或者不存在表示,存在表示互斥量为 1。 +1 和 0 可以用一个整型值表示,也可以用某个数据是否存在表示,存在表示互斥量为 1。 ## 数据库的唯一索引 @@ -43,7 +44,7 @@ 存在以下几个问题: -- 锁没有失效时间,解锁失败的话其它进程无法再获得锁。 +- 锁没有失效时间,解锁失败的话其它进程无法再获得该锁。 - 只能是非阻塞锁,插入失败直接就报错了,无法重试。 - 不可重入,已经获得锁的进程也必须重新获取锁。 @@ -59,7 +60,7 @@ EXPIRE 指令可以为一个键值对设置一个过期时间,从而避免了 使用了多个 Redis 实例来实现分布式锁,这是为了保证在发生单点故障时仍然可用。 -- 尝试从 N 个相互独立 Redis 实例获取锁,如果一个实例不可用,应该尽快尝试下一个; +- 尝试从 N 个相互独立 Redis 实例获取锁; - 计算获取锁消耗的时间,只有当这个时间小于锁的过期时间,并且从大多数(N / 2 + 1)实例上获取了锁,那么就认为锁获取成功了; - 如果锁获取失败,就到每个实例上释放锁。 @@ -67,7 +68,7 @@ EXPIRE 指令可以为一个键值对设置一个过期时间,从而避免了 ### 1. Zookeeper 抽象模型 -Zookeeper 提供了一种树形结构级的命名空间,/app1/p_1 节点表示它的父节点为 /app1。 +Zookeeper 提供了一种树形结构级的命名空间,/app1/p_1 节点的父节点为 /app1。

@@ -96,21 +97,15 @@ Zookeeper 提供了一种树形结构级的命名空间,/app1/p_1 节点表示 一个节点未获得锁,需要监听自己的前一个子节点,这是因为如果监听所有的子节点,那么任意一个子节点状态改变,其它所有子节点都会收到通知(羊群效应),而我们只希望它的后一个子节点收到通知。 -参考: - -- [Distributed locks with Redis](https://redis.io/topics/distlock) -- [浅谈分布式锁](http://www.linkedkeeper.com/detail/blog.action?bid=1023) -- [基于 Zookeeper 的分布式锁](http://www.dengshenyu.com/java/%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F/2017/10/23/zookeeper-distributed-lock.html) - # 二、分布式事务 -指事务的操作位于不同的节点上,需要保证事务的 AICD 特性。例如在下单场景下,库存和订单如果不在同一个节点上,就涉及分布式事务。 +指事务的操作位于不同的节点上,需要保证事务的 AICD 特性。 + +例如在下单场景下,库存和订单如果不在同一个节点上,就涉及分布式事务。 ## 本地消息表 -### 1. 原理 - -本地消息表与业务数据表处于同一个数据库中,这样就能利用本地事务来保证在对这两个表的操作满足事务特性。 +本地消息表与业务数据表处于同一个数据库中,这样就能利用本地事务来保证在对这两个表的操作满足事务特性,并且使用了消息队列来保证最终一致性。 1. 在分布式事务操作的一方完成写业务数据的操作之后向本地消息表发送一个消息,本地事务能保证这个消息一定会被写入本地消息表中。 2. 之后将本地消息表中的消息转发到 Kafka 等消息队列中,如果转发成功则将消息从本地消息表中删除,否则继续重新转发。 @@ -118,23 +113,19 @@ Zookeeper 提供了一种树形结构级的命名空间,/app1/p_1 节点表示

-### 2. 分析 - -本地消息表利用了本地事务来实现分布式事务,并且使用了消息队列来保证最终一致性。 - ## 2PC 两阶段提交(Two-phase Commit,2PC),通过引入协调者(Coordinator)来协调参与者的行为,并最终决定这些参与者是否要真正执行事务。 ### 1. 运行过程 -(一)准备阶段 +#### 1.1 准备阶段 协调者询问参与者事务是否执行成功,参与者发回事务执行结果。

-(二)提交阶段 +#### 1.2 提交阶段 如果事务在每个参与者上都执行成功,事务协调者发送通知让参与者提交事务;否则,协调者发送通知让参与者回滚事务。 @@ -144,28 +135,22 @@ Zookeeper 提供了一种树形结构级的命名空间,/app1/p_1 节点表示 ### 2. 存在的问题 -(一)同步阻塞 +#### 2.1 同步阻塞 所有事务参与者在等待其它参与者响应的时候都处于同步阻塞状态,无法进行其它操作。 -(二)单点问题 +#### 2.2 单点问题 协调者在 2PC 中起到非常大的作用,发生故障将会造成很大影响。特别是在阶段二发生故障,所有参与者会一直等待状态,无法完成其它操作。 -(三)数据不一致 +#### 2.3 数据不一致 在阶段二,如果协调者只发送了部分 Commit 消息,此时网络发生异常,那么只有部分参与者接收到 Commit 消息,也就是说只有部分参与者提交了事务,使得系统数据不一致。 -(四)太过保守 +#### 2.4 太过保守 任意一个节点失败就会导致整个事务失败,没有完善的容错机制。 -参考: - -- [聊聊分布式事务,再说说解决方案](https://www.cnblogs.com/savorboard/p/distributed-system-transaction-consistency.html) -- [分布式系统的事务处理](https://coolshell.cn/articles/10910.html) -- [深入理解分布式事务](https://juejin.im/entry/577c6f220a2b5800573492be) - # 三、CAP 分布式系统不可能同时满足一致性(C:Consistency)、可用性(A:Availability)和分区容忍性(P:Partition Tolerance),最多只能同时满足其中两项。 @@ -184,7 +169,7 @@ Zookeeper 提供了一种树形结构级的命名空间,/app1/p_1 节点表示 可用性指分布式系统在面对各种异常时可以提供正常服务的能力,可以用系统可用时间占总时间的比值来衡量,4 个 9 的可用性表示系统 99.99% 的时间是可用的。 -在可用性条件下,系统提供的服务一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。 +在可用性条件下,要求系统提供的服务一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。 ## 分区容忍性 @@ -198,16 +183,11 @@ Zookeeper 提供了一种树形结构级的命名空间,/app1/p_1 节点表示 可用性和一致性往往是冲突的,很难都使它们同时满足。在多个节点之间进行数据同步时, -* 为了保证一致性(CP),就需要让所有节点下线成为不可用的状态,等待同步完成; -* 为了保证可用性(AP),在同步过程中允许读取所有节点的数据,但是数据可能不一致。 +- 为了保证一致性(CP),就需要让所有节点下线成为不可用的状态,等待同步完成; +- 为了保证可用性(AP),在同步过程中允许读取所有节点的数据,但是数据可能不一致。

-参考: - -- 倪超. 从 Paxos 到 ZooKeeper : 分布式一致性原理与实践 [M]. 电子工业出版社, 2015. -- [What is CAP theorem in distributed database system?](http://www.colooshiki.com/index.php/2017/04/20/what-is-cap-theorem-in-distributed-database-system/) - # 四、BASE BASE 是基本可用(Basically Available)、软状态(Soft State)和最终一致性(Eventually Consistent)三个短语的缩写。 @@ -292,66 +272,69 @@ Acceptor 接收到接受请求时,如果序号大于等于该 Acceptor 承诺 Paxos 协议能够让 Proposer 发送的提议朝着能被大多数 Acceptor 接受的那个提议靠拢,因此能够保证可终止性。 -参考: +# 六、Raft -- [NEAT ALGORITHMS - PAXOS](http://harry.me/blog/2014/12/27/neat-algorithms-paxos/) -- [Paxos By Example](https://angus.nyc/2012/paxos-by-example/) - -# 五、Raft - -Raft 和 Paxos 类似,但是更容易理解,也更容易实现。 - -Raft 主要是用来竞选主节点。 +Raft 也是分布式一致性协议,主要是用来竞选主节点。 ## 单个 Candidate 的竞选 有三种节点:Follower、Candidate 和 Leader。Leader 会周期性的发送心跳包给 Follower。每个 Follower 都设置了一个随机的竞选超时时间,一般为 150ms\~300ms,如果在这个时间内没有收到 Leader 的心跳包,就会变成 Candidate,进入竞选阶段。 -* 下图表示一个分布式系统的最初阶段,此时只有 Follower,没有 Leader。Follower A 等待一个随机的竞选超时时间之后,没收到 Leader 发来的心跳包,因此进入竞选阶段。 +- 下图展示一个分布式系统的最初阶段,此时只有 Follower 没有 Leader。Node A 等待一个随机的竞选超时时间之后,没收到 Leader 发来的心跳包,因此进入竞选阶段。

-* 此时 A 发送投票请求给其它所有节点。 +- 此时 Node A 发送投票请求给其它所有节点。

-* 其它节点会对请求进行回复,如果超过一半的节点回复了,那么该 Candidate 就会变成 Leader。 +- 其它节点会对请求进行回复,如果超过一半的节点回复了,那么该 Candidate 就会变成 Leader。

-* 之后 Leader 会周期性地发送心跳包给 Follower,Follower 接收到心跳包,会重新开始计时。 +- 之后 Leader 会周期性地发送心跳包给 Follower,Follower 接收到心跳包,会重新开始计时。

## 多个 Candidate 竞选 -* 如果有多个 Follower 成为 Candidate,并且所获得票数相同,那么就需要重新开始投票,例如下图中 Candidate B 和 Candidate D 都获得两票,因此需要重新开始投票。 +- 如果有多个 Follower 成为 Candidate,并且所获得票数相同,那么就需要重新开始投票。例如下图中 Node B 和 Node D 都获得两票,需要重新开始投票。

-* 当重新开始投票时,由于每个节点设置的随机竞选超时时间不同,因此能下一次再次出现多个 Candidate 并获得同样票数的概率很低。 +- 由于每个节点设置的随机竞选超时时间不同,因此下一次再次出现多个 Candidate 并获得同样票数的概率很低。

-## 日志复制 +## 数据同步 -* 来自客户端的修改都会被传入 Leader。注意该修改还未被提交,只是写入日志中。 +- 来自客户端的修改都会被传入 Leader。注意该修改还未被提交,只是写入日志中。

-* Leader 会把修改复制到所有 Follower。 +- Leader 会把修改复制到所有 Follower。

-* Leader 会等待大多数的 Follower 也进行了修改,然后才将修改提交。 +- Leader 会等待大多数的 Follower 也进行了修改,然后才将修改提交。

-* 此时 Leader 会通知的所有 Follower 让它们也提交修改,此时所有节点的值达成一致。 +- 此时 Leader 会通知的所有 Follower 让它们也提交修改,此时所有节点的值达成一致。

-参考: +# 参考 +- 倪超. 从 Paxos 到 ZooKeeper : 分布式一致性原理与实践 [M]. 电子工业出版社, 2015. +- [Distributed locks with Redis](https://redis.io/topics/distlock) +- [浅谈分布式锁](http://www.linkedkeeper.com/detail/blog.action?bid=1023) +- [基于 Zookeeper 的分布式锁](http://www.dengshenyu.com/java/%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F/2017/10/23/zookeeper-distributed-lock.html) - [Raft: Understandable Distributed Consensus](http://thesecretlivesofdata.com/raft) +- [聊聊分布式事务,再说说解决方案](https://www.cnblogs.com/savorboard/p/distributed-system-transaction-consistency.html) +- [分布式系统的事务处理](https://coolshell.cn/articles/10910.html) +- [深入理解分布式事务](https://juejin.im/entry/577c6f220a2b5800573492be) +- [What is CAP theorem in distributed database system?](http://www.colooshiki.com/index.php/2017/04/20/what-is-cap-theorem-in-distributed-database-system/) +- [NEAT ALGORITHMS - PAXOS](http://harry.me/blog/2014/12/27/neat-algorithms-paxos/) +- [Paxos By Example](https://angus.nyc/2012/paxos-by-example/) diff --git a/notes/剑指 offer 题解.md b/notes/剑指 offer 题解.md index 1bac6bbb..d88b6168 100644 --- a/notes/剑指 offer 题解.md +++ b/notes/剑指 offer 题解.md @@ -1082,7 +1082,7 @@ false ```java public boolean isNumeric(char[] str) { - if (str == null) + if (str == null || str.length == 0) return false; return new String(str).matches("[+-]?\\d*(\\.\\d+)?([eE][+-]?\\d+)?"); } @@ -2779,19 +2779,25 @@ public List> dicesSum(int n) { ```java public boolean isContinuous(int[] nums) { + if (nums.length < 5) return false; + Arrays.sort(nums); + + // 统计癞子数量 int cnt = 0; - for (int num : nums) /* 统计癞子数量 */ + for (int num : nums) if (num == 0) cnt++; + // 使用癞子去补全不连续的顺子 for (int i = cnt; i < nums.length - 1; i++) { if (nums[i + 1] == nums[i]) return false; - cnt -= nums[i + 1] - nums[i] - 1; /* 使用癞子去补全不连续的顺子 */ + cnt -= nums[i + 1] - nums[i] - 1; } + return cnt >= 0; } ``` @@ -2824,7 +2830,7 @@ public int LastRemaining_Solution(int n, int m) { ## 题目描述 -可以有一次买入和一次卖出,买入必须在前。求最大收益。 +可以有一次买入和一次卖出,那么买入必须在前。求最大收益。 ## 解题思路 diff --git a/notes/攻击技术.md b/notes/攻击技术.md index d5a735ba..46dd4616 100644 --- a/notes/攻击技术.md +++ b/notes/攻击技术.md @@ -207,7 +207,7 @@ ResultSet rs = stmt.executeQuery(); 拒绝服务攻击(denial-of-service attack,DoS),亦称洪水攻击,其目的在于使目标电脑的网络或系统资源耗尽,使服务暂时中断或停止,导致其正常用户无法访问。 -分布式拒绝服务攻击(distributed denial-of-service attack,DDoS),指攻击者使用网络上两个或以上被攻陷的电脑作为“僵尸”向特定的目标发动“拒绝服务”式攻击。 +分布式拒绝服务攻击(distributed denial-of-service attack,DDoS),指攻击者使用两个或以上被攻陷的电脑作为“僵尸”向特定的目标发动“拒绝服务”式攻击。 # 参考资料 diff --git a/notes/构建工具.md b/notes/构建工具.md index c0e4acd5..ce157934 100644 --- a/notes/构建工具.md +++ b/notes/构建工具.md @@ -15,7 +15,7 @@ ## 运行单元测试 -不再需要在项目代码中添加测试代码,从而污染项目代码。 +不再需要在项目代码中添加测试代码,从而避免了污染项目代码。 ## 将源代码转化为可执行文件 diff --git a/notes/消息队列.md b/notes/消息队列.md index c5687e8a..209d962a 100644 --- a/notes/消息队列.md +++ b/notes/消息队列.md @@ -9,6 +9,7 @@ * [三、可靠性](#三可靠性) * [发送端的可靠性](#发送端的可靠性) * [接收端的可靠性](#接收端的可靠性) +* [参考资料](#参考资料) @@ -29,15 +30,10 @@ 发布与订阅模式和观察者模式有以下不同: - 观察者模式中,观察者和主题都知道对方的存在;而在发布与订阅模式中,发布者与订阅者不知道对方的存在,它们之间通过频道进行通信。 -- 观察者模式是同步的,当事件触发时,主题会去调用观察者的方法,然后等待方法返回;而发布与订阅模式是异步的,发布者向频道发送一个消息之后,就不需要关心订阅者何时去订阅这个消息。 +- 观察者模式是同步的,当事件触发时,主题会调用观察者的方法,然后等待方法返回;而发布与订阅模式是异步的,发布者向频道发送一个消息之后,就不需要关心订阅者何时去订阅这个消息,可以立即返回。

-参考: - -- [Observer vs Pub-Sub](http://developers-club.com/posts/270339/) -- [消息队列中点对点与发布订阅区别](https://blog.csdn.net/lizhitao/article/details/47723105) - # 二、使用场景 ## 异步处理 @@ -78,3 +74,8 @@ - 保证接收端处理消息的业务逻辑具有幂等性:只要具有幂等性,那么消费多少次消息,最后处理的结果都是一样的。 - 保证消息具有唯一编号,并使用一张日志表来记录已经消费的消息编号。 + +# 参考资料 + +- [Observer vs Pub-Sub](http://developers-club.com/posts/270339/) +- [消息队列中点对点与发布订阅区别](https://blog.csdn.net/lizhitao/article/details/47723105) diff --git a/notes/算法.md b/notes/算法.md index 30467b42..a9f9e689 100644 --- a/notes/算法.md +++ b/notes/算法.md @@ -23,7 +23,7 @@ * [五、栈和队列](#五栈和队列) * [栈](#栈) * [队列](#队列) -* [六、查找](#六查找) +* [六、符号表](#六符号表) * [初级实现](#初级实现) * [二叉查找树](#二叉查找树) * [2-3 查找树](#2-3-查找树) @@ -507,7 +507,7 @@ public class QuickSort> extends Sort { 取 a[l] 作为切分元素,然后从数组的左端向右扫描直到找到第一个大于等于它的元素,再从数组的右端向左扫描找到第一个小于等于它的元素,交换这两个元素。不断进行这个过程,就可以保证左指针 i 的左侧元素都不大于切分元素,右指针 j 的右侧元素都不小于切分元素。当两个指针相遇时,将切分元素 a[l] 和 a[j] 交换位置。 -

+

```java private int partition(T[] nums, int l, int h) { @@ -770,7 +770,7 @@ public class HeapSort> extends Sort { | 算法 | 稳定 | 时间复杂度 | 空间复杂度 | 备注 | | :---: | :---: |:---: | :---: | :---: | -| 选择排序 | √| N2 | 1 | | +| 选择排序 | × | N2 | 1 | | | 冒泡排序 | √ | N2 | 1 | | | 插入排序 | √ | N \~ N2 | 1 | 时间复杂度和初始顺序有关 | | 希尔排序 | × | N 的若干倍乘于递增序列的长度 | 1 | | @@ -1290,7 +1290,7 @@ public class ListQueue implements MyQueue { -# 六、查找 +# 六、符号表 符号表(Symbol Table)是一种存储键值对的数据结构,可以支持快速查找操作。 @@ -1411,9 +1411,9 @@ public class ListUnorderedST implements UnorderedST { 使用一对平行数组,一个存储键一个存储值。 -rank() 方法至关重要,当键在表中时,它能够知道该键的位置;当键不在表中时,它也能知道在何处插入新键。 +二分查找的 rank() 方法至关重要,当键在表中时,它能够知道该键的位置;当键不在表中时,它也能知道在何处插入新键。 -复杂度:二分查找最多需要 logN+1 次比较,使用二分查找实现的符号表的查找操作所需要的时间最多是对数级别的。但是插入操作需要移动数组元素,是线性级别的。 +二分查找最多需要 logN+1 次比较,使用二分查找实现的符号表的查找操作所需要的时间最多是对数级别的。但是插入操作需要移动数组元素,是线性级别的。 ```java public class BinarySearchOrderedST, Value> implements OrderedST { @@ -2254,7 +2254,7 @@ from H1 to H3 - c : 40 - d : 80 -可以将每种字符转换成二进制编码,例如将 a 转换为 00,b 转换为 01,c 转换为 10,d 转换为 11。这是最简单的一种编码方式,没有考虑各个字符的权值(出现频率)。而哈夫曼编码能让出现频率最高的字符的编码最短,从而保证整体的编码长度最短。 +可以将每种字符转换成二进制编码,例如将 a 转换为 00,b 转换为 01,c 转换为 10,d 转换为 11。这是最简单的一种编码方式,没有考虑各个字符的权值(出现频率)。而哈夫曼编码采用了贪心策略,使出现频率最高的字符的编码最短,从而保证整体的编码长度最短。 首先生成一颗哈夫曼树,每次生成过程中选取频率最少的两个节点,生成一个新节点作为它们的父节点,并且新节点的频率为两个节点的和。选取频率最少的原因是,生成过程使得先选取的节点在树的最底层,那么需要的编码长度更长,频率更少可以使得总编码长度更少。 diff --git a/notes/系统设计基础.md b/notes/系统设计基础.md index 805764a9..b6a3ddf5 100644 --- a/notes/系统设计基础.md +++ b/notes/系统设计基础.md @@ -68,7 +68,7 @@ 应用服务器只要不具有状态,那么就可以很容易地通过负载均衡器向集群中添加新的服务器。 -关系型数据库的伸缩性通过 Sharding 来实现,将数据按一定的规则分布到不同的节点上,从而解决单台存储服务器存储空间限制。 +关系型数据库的伸缩性通过 Sharding 来实现,将数据按一定的规则分布到不同的节点上,从而解决单台存储服务器的存储空间限制。 对于非关系型数据库,它们天生就是为海量数据而诞生,对伸缩性的支持特别好。 @@ -78,7 +78,7 @@ 实现可扩展主要有两种方式: -- 使用消息队列进行解耦,应用之间通过消息传递的方式进行通信; +- 使用消息队列进行解耦,应用之间通过消息传递进行通信; - 使用分布式服务将业务和可复用的服务分离开来,业务使用分布式服务框架调用可复用的服务。新增的产品可以通过调用可复用的服务来实现业务逻辑,对其它产品没有影响。 # 四、可用性 @@ -87,7 +87,7 @@ 保证高可用的主要手段是使用冗余,当某个服务器故障时就请求其它服务器。 -应用服务器的冗余比较容易实现,只要保证应用服务器不具有状态,那么某个应用服务器故障时,负载均衡器将该应用服务器原先的用户请求转发到另一个应用服务器上不会对用户有任何影响。 +应用服务器的冗余比较容易实现,只要保证应用服务器不具有状态,那么某个应用服务器故障时,负载均衡器将该应用服务器原先的用户请求转发到另一个应用服务器上,不会对用户有任何影响。 存储服务器的冗余需要使用主从复制来实现,当主服务器故障时,需要提升从服务器为主服务器,这个过程称为切换。 @@ -97,7 +97,7 @@ ## 服务降级 -服务器降级是系统为了应对大量的请求,主动关闭部分功能,从而保证核心功能可用。 +服务降级是系统为了应对大量的请求,主动关闭部分功能,从而保证核心功能可用。 # 五、安全性 diff --git a/notes/缓存.md b/notes/缓存.md index 9a9f2810..ed834253 100644 --- a/notes/缓存.md +++ b/notes/缓存.md @@ -6,6 +6,7 @@ * [五、缓存问题](#五缓存问题) * [六、数据分布](#六数据分布) * [七、一致性哈希](#七一致性哈希) +* [参考资料](#参考资料) @@ -29,10 +30,6 @@ - LRU(Least Recently Used):最近最久未使用策略,优先淘汰最久未使用的数据,也就是上次被访问时间距离现在最远的数据。该策略可以保证内存中的数据都是热点数据,也就是经常被访问的数据,从而保证缓存命中率。 -参考资料: - -- [缓存那些事](https://tech.meituan.com/cache_about.html) - # 二、LRU 以下是一个基于 双向链表 + HashMap 的 LRU 算法实现,对算法的解释如下: @@ -143,10 +140,6 @@ public class LRU implements Iterable { } ``` -源代码: - -- [CyC2018/Algorithm](https://github.com/CyC2018/Algorithm/tree/master/Caching) - # 三、缓存位置 ## 浏览器 @@ -169,7 +162,7 @@ public class LRU implements Iterable { 使用 Redis、Memcache 等分布式缓存将数据缓存在分布式缓存系统中。 -相对于本地缓存来说,分布式缓存单独部署,可以根据需求分配硬件资源。不仅如此,服务器集群都可以访问分布式缓存。而本地缓存需要在服务器集群之间进行同步,实现和性能开销上都非常大。 +相对于本地缓存来说,分布式缓存单独部署,可以根据需求分配硬件资源。不仅如此,服务器集群都可以访问分布式缓存,而本地缓存需要在服务器集群之间进行同步,实现和性能开销上都非常大。 ## 数据库缓存 @@ -177,7 +170,7 @@ MySQL 等数据库管理系统具有自己的查询缓存机制来提高 SQL 查 # 四、CDN -内容分发网络(Content distribution network,CDN)是一种通过互连的网络系统,利用更靠近用户的服务器更快更可靠地将 HTML、CSS、JavaScript、音乐、图片、视频等静态资源分发给用户。 +内容分发网络(Content distribution network,CDN)是一种互连的网络系统,它利用更靠近用户的服务器从而更快更可靠地将 HTML、CSS、JavaScript、音乐、图片、视频等静态资源分发给用户。 CDN 主要有以下优点: @@ -187,11 +180,6 @@ CDN 主要有以下优点:

-参考资料: - -- [内容分发网络](https://zh.wikipedia.org/wiki/%E5%85%A7%E5%AE%B9%E5%82%B3%E9%81%9E%E7%B6%B2%E8%B7%AF) -- [How Aspiration CDN helps to improve your website loading speed?](https://www.aspirationhosting.com/aspiration-cdn/) - # 五、缓存问题 ## 缓存穿透 @@ -213,7 +201,7 @@ CDN 主要有以下优点: - 为了防止缓存在同一时间大面积过期导致的缓存雪崩,可以通过观察用户行为,合理设置缓存过期时间来实现; - 为了防止缓存服务器宕机出现的缓存雪崩,可以使用分布式缓存,分布式缓存中每一个节点只缓存部分的数据,当某个节点宕机时可以保证其它节点的缓存仍然可用。 -- 也可以在进行缓存预热,避免在系统刚启动不久由于还未将大量数据进行缓存而导致缓存雪崩。 +- 也可以进行缓存预热,避免在系统刚启动不久由于还未将大量数据进行缓存而导致缓存雪崩。 ## 缓存一致性 @@ -243,10 +231,6 @@ CDN 主要有以下优点: - 能保持数据原有的顺序; - 并且能够准确控制每台服务器存储的数据量,从而使得存储空间的利用率最大。 -参考资料: - -- 大规模分布式存储系统 - # 七、一致性哈希 Distributed Hash Table(DHT) 是一种哈希分布方式,其目的是为了克服传统哈希分布在服务器节点数量变化时大量数据失效的问题。 @@ -267,6 +251,10 @@ Distributed Hash Table(DHT) 是一种哈希分布方式,其目的是为了 数据不均匀主要是因为节点在哈希环上分布的不均匀,这种情况在节点数量很少的情况下尤其明显。解决方式是通过增加虚拟节点,然后将虚拟节点映射到真实节点上。虚拟节点的数量比真实节点来得大,那么虚拟节点在哈希环上分布的均匀性就会比原来的真实节点好,从而使得数据分布也更加均匀。 -参考资料: +# 参考资料 +- 大规模分布式存储系统 +- [缓存那些事](https://tech.meituan.com/cache_about.html) - [一致性哈希算法](https://my.oschina.net/jayhu/blog/732849) +- [内容分发网络](https://zh.wikipedia.org/wiki/%E5%85%A7%E5%AE%B9%E5%82%B3%E9%81%9E%E7%B6%B2%E8%B7%AF) +- [How Aspiration CDN helps to improve your website loading speed?](https://www.aspirationhosting.com/aspiration-cdn/) diff --git a/pics/766aedd0-1b00-4065-aa2b-7d31138df84f.png b/pics/766aedd0-1b00-4065-aa2b-7d31138df84f.png new file mode 100644 index 00000000..e8a69bff Binary files /dev/null and b/pics/766aedd0-1b00-4065-aa2b-7d31138df84f.png differ