From 4f3fa83fc27e18a371b5940e937cdc34c0ef0bea Mon Sep 17 00:00:00 2001 From: CyC2018 <1029579233@qq.com> Date: Sat, 25 Aug 2018 23:12:21 +0800 Subject: [PATCH] auto commit --- notes/Docker.md | 11 +-- notes/Java 基础.md | 2 +- notes/Java 容器.md | 2 +- notes/Java 并发.md | 4 +- notes/Java 虚拟机.md | 2 +- notes/Leetcode 题解.md | 189 ++++++++++++++++++--------------------- notes/Linux.md | 26 +++--- notes/Redis.md | 2 +- notes/分布式.md | 105 +++++++++------------- notes/剑指 offer 题解.md | 14 ++- notes/攻击技术.md | 2 +- notes/构建工具.md | 2 +- notes/消息队列.md | 13 +-- notes/算法.md | 14 +-- notes/系统设计基础.md | 8 +- notes/缓存.md | 30 ++----- notes/设计模式.md | 76 ++++++++-------- notes/面向对象思想.md | 15 ++-- 18 files changed, 248 insertions(+), 269 deletions(-) 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 b329e0bc..44ca873a 100644 --- a/notes/Java 基础.md +++ b/notes/Java 基础.md @@ -707,7 +707,7 @@ public class EqualExample { ## hashCode() -hasCode() 返回散列值,而 equals() 是用来判断两个对象是否等价。等价的两个对象散列值一定相同,但是散列值相同的两个对象不一定等价。 +hashCode() 返回散列值,而 equals() 是用来判断两个对象是否等价。等价的两个对象散列值一定相同,但是散列值相同的两个对象不一定等价。 在覆盖 equals() 方法时应当总是覆盖 hashCode() 方法,保证等价的两个对象散列值也相等。 diff --git a/notes/Java 容器.md b/notes/Java 容器.md index 9959e2ca..001f864c 100644 --- a/notes/Java 容器.md +++ b/notes/Java 容器.md @@ -742,7 +742,7 @@ new capacity : 00100000 对于一个 Key, -- 它的哈希值如果在第 6 位上为 0,那么取模得到的结果和之前一样; +- 它的哈希值如果在第 5 位上为 0,那么取模得到的结果和之前一样; - 如果为 1,那么得到的结果为原来的结果 +16。 ### 7. 扩容-计算数组容量 diff --git a/notes/Java 并发.md b/notes/Java 并发.md index 9cd5955b..1403f764 100644 --- a/notes/Java 并发.md +++ b/notes/Java 并发.md @@ -168,6 +168,8 @@ public static void main(String[] args) throws ExecutionException, InterruptedExc 同样也是需要实现 run() 方法,因为 Thread 类也实现了 Runable 接口。 +当调用 start() 方法启动一个线程时,虚拟机会将该线程放入就绪队列中等待被调度,当一个线程被调度时会执行该线程的 run() 方法。 + ```java public class MyThread extends Thread { public void run() { @@ -1018,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..1bba557b 100644 --- a/notes/Java 虚拟机.md +++ b/notes/Java 虚拟机.md @@ -92,7 +92,7 @@ java -Xms1M -Xmx2M HackTheJava 对这块区域进行垃圾回收的主要目标是对常量池的回收和对类的卸载,但是一般比较难实现。 -JDK 1.7 之前,HotSpot 虚拟机把它当成永久代来进行垃圾回收。但是从 JDK 1.7 开始,已经把原本放在永久代的字符串常量池移到 Native Method 中。 +HotSpot 虚拟机把它当成永久代来进行垃圾回收。但是很难确定永久代的大小,因为它受到很多因素影响,并且每次 Full GC 之后永久代的大小都会改变,所以经常会抛出 OutOfMemoryError 异常。为了更容易管理方法区,从 JDK 1.8 开始,移除永久代,并把方法区移至元空间,它位于本地内存中,而不是虚拟机内存中。 ## 运行时常量池 diff --git a/notes/Leetcode 题解.md b/notes/Leetcode 题解.md index 44f3d9dc..04a751c7 100644 --- a/notes/Leetcode 题解.md +++ b/notes/Leetcode 题解.md @@ -2389,8 +2389,6 @@ private void backtracking(int row) {

-dp[N] 即为所求。 - 考虑到 dp[i] 只与 dp[i - 1] 和 dp[i - 2] 有关,因此可以只用两个变量来存储 dp[i - 1] 和 dp[i - 2],使得原来的 O(N) 空间复杂度优化为 O(1) 复杂度。 ```java @@ -2468,16 +2466,6 @@ private int rob(int[] nums, int first, int last) { } ``` -**母牛生产** - -[程序员代码面试指南-P181](#) - -题目描述:假设农场中成熟的母牛每年都会生 1 头小母牛,并且永远不会死。第一年有 1 只小母牛,从第二年开始,母牛开始生小母牛。每只小母牛 3 年之后成熟又可以生小母牛。给定整数 N,求 N 年后牛的数量。 - -第 i 年成熟的牛的数量为: - -

- **信件错排** 题目描述:有 N 个 信 和 信封,它们被打乱,求错误装信方式的数量。 @@ -2491,7 +2479,15 @@ private int rob(int[] nums, int first, int last) {

-dp[N] 即为所求。 +**母牛生产** + +[程序员代码面试指南-P181](#) + +题目描述:假设农场中成熟的母牛每年都会生 1 头小母牛,并且永远不会死。第一年有 1 只小母牛,从第二年开始,母牛开始生小母牛。每只小母牛 3 年之后成熟又可以生小母牛。给定整数 N,求 N 年后牛的数量。 + +第 i 年成熟的牛的数量为: + +

### 矩阵路径 @@ -2517,10 +2513,8 @@ public int minPathSum(int[][] grid) { int[] dp = new int[n]; for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { - if (j == 0) { - dp[j] = dp[j]; // 只能从上侧走到该位置 - } else if (i == 0) { - dp[j] = dp[j - 1]; // 只能从左侧走到该位置 + if (i == 0) { + dp[j] = dp[j - 1]; } else { dp[j] = Math.min(dp[j - 1], dp[j]); } @@ -2584,17 +2578,18 @@ sumRange(0, 5) -> -3 ```java class NumArray { + private int[] sums; public NumArray(int[] nums) { - sums = new int[nums.length]; - for (int i = 0; i < nums.length; i++) { - sums[i] = i == 0 ? nums[0] : sums[i - 1] + nums[i]; + sums = new int[nums.length + 1]; + for (int i = 1; i <= nums.length; i++) { + sums[i] = sums[i - 1] + nums[i - 1]; } } public int sumRange(int i, int j) { - return i == 0 ? sums[j] : sums[j] - sums[i - 1]; + return sums[j + 1] - sums[i]; } } ``` @@ -2634,7 +2629,7 @@ return: 3, for 3 arithmetic slices in A: [1, 2, 3], [2, 3, 4] and [1, 2, 3, 4] i dp[i] 表示以 A[i] 为结尾的等差递增子区间的个数。 -如果 A[i] - A[i - 1] == A[i - 1] - A[i - 2],表示 [A[i - 2], A[i - 1], A[i]] 是一个等差递增子区间。如果 [A[i - 3], A[i - 2], A[i - 1]] 是一个等差递增子区间,那么 [A[i - 3], A[i - 2], A[i - 1], A[i]] 也是。因此在这个条件下,dp[i] = dp[i-1] + 1。 +在 A[i] - A[i - 1] == A[i - 1] - A[i - 2] 的条件下,{A[i - 2], A[i - 1], A[i]} 是一个等差递增子区间。如果 {A[i - 3], A[i - 2], A[i - 1]} 是一个等差递增子区间,那么 {A[i - 3], A[i - 2], A[i - 1], A[i]} 也是等差递增子区间,dp[i] = dp[i-1] + 1。 ```java public int numberOfArithmeticSlices(int[] A) { @@ -2747,17 +2742,17 @@ public int numDecodings(String s) { ### 最长递增子序列 -已知一个序列 {S1, S2,...,Sn} ,取出若干数组成新的序列 {Si1, Si2,..., Sim},其中 i1、i2 ... im 保持递增,即新序列中各个数仍然保持原数列中的先后顺序,称新序列为原序列的一个 **子序列** 。 +已知一个序列 {S1, S2,...,Sn},取出若干数组成新的序列 {Si1, Si2,..., Sim},其中 i1、i2 ... im 保持递增,即新序列中各个数仍然保持原数列中的先后顺序,称新序列为原序列的一个 **子序列** 。 如果在子序列中,当下标 ix > iy 时,Six > Siy,称子序列为原序列的一个 **递增子序列** 。 -定义一个数组 dp 存储最长递增子序列的长度,dp[n] 表示以 Sn 结尾的序列的最长递增子序列长度。对于一个递增子序列 {Si1, Si2,...,Sim},如果 im < n 并且 Sim < Sn ,此时 {Si1, Si2,..., Sim, Sn} 为一个递增子序列,递增子序列的长度增加 1。满足上述条件的递增子序列中,长度最长的那个递增子序列就是要找的,在长度最长的递增子序列上加上 Sn 就构成了以 Sn 为结尾的最长递增子序列。因此 dp[n] = max{ dp[i]+1 | Si < Sn && i < n} 。 +定义一个数组 dp 存储最长递增子序列的长度,dp[n] 表示以 Sn 结尾的序列的最长递增子序列长度。对于一个递增子序列 {Si1, Si2,...,Sim},如果 im < n 并且 Sim < Sn,此时 {Si1, Si2,..., Sim, Sn} 为一个递增子序列,递增子序列的长度增加 1。满足上述条件的递增子序列中,长度最长的那个递增子序列就是要找的,在长度最长的递增子序列上加上 Sn 就构成了以 Sn 为结尾的最长递增子序列。因此 dp[n] = max{ dp[i]+1 | Si < Sn && i < n} 。 因为在求 dp[n] 时可能无法找到一个满足条件的递增子序列,此时 {Sn} 就构成了递增子序列,需要对前面的求解方程做修改,令 dp[n] 最小为 1,即:

-对于一个长度为 N 的序列,最长递增子序列并不一定会以 SN 为结尾,因此 dp[N] 不是序列的最长递增子序列的长度,需要遍历 dp 数组找出最大值才是所要的结果,即 max{ dp[i] | 1 <= i <= N} 即为所求。 +对于一个长度为 N 的序列,最长递增子序列并不一定会以 SN 为结尾,因此 dp[N] 不是序列的最长递增子序列的长度,需要遍历 dp 数组找出最大值才是所要的结果,max{ dp[i] | 1 <= i <= N} 即为所求。 **最长递增子序列** @@ -2790,7 +2785,7 @@ for (int i = 0; i < n; i++) { return ret; ``` -以上解法的时间复杂度为 O(N2) ,可以使用二分查找将时间复杂度降低为 O(NlogN)。 +以上解法的时间复杂度为 O(N2),可以使用二分查找将时间复杂度降低为 O(NlogN)。 定义一个 tails 数组,其中 tails[i] 存储长度为 i + 1 的最长递增子序列的最后一个元素。对于一个元素 x, @@ -2915,19 +2910,19 @@ public int wiggleMaxLength(int[] nums) { 定义一个二维数组 dp 用来存储最长公共子序列的长度,其中 dp[i][j] 表示 S1 的前 i 个字符与 S2 的前 j 个字符最长公共子序列的长度。考虑 S1i 与 S2j 值是否相等,分为两种情况: -- 当 S1i==S2j 时,那么就能在 S1 的前 i-1 个字符与 S2 的前 j-1 个字符最长公共子序列的基础上再加上 S1i 这个值,最长公共子序列长度加 1 ,即 dp[i][j] = dp[i-1][j-1] + 1。 -- 当 S1i != S2j 时,此时最长公共子序列为 S1 的前 i-1 个字符和 S2 的前 j 个字符最长公共子序列,与 S1 的前 i 个字符和 S2 的前 j-1 个字符最长公共子序列,它们的最大者,即 dp[i][j] = max{ dp[i-1][j], dp[i][j-1] }。 +- 当 S1i==S2j 时,那么就能在 S1 的前 i-1 个字符与 S2 的前 j-1 个字符最长公共子序列的基础上再加上 S1i 这个值,最长公共子序列长度加 1,即 dp[i][j] = dp[i-1][j-1] + 1。 +- 当 S1i != S2j 时,此时最长公共子序列为 S1 的前 i-1 个字符和 S2 的前 j 个字符最长公共子序列,或者 S1 的前 i 个字符和 S2 的前 j-1 个字符最长公共子序列,取它们的最大者,即 dp[i][j] = max{ dp[i-1][j], dp[i][j-1] }。 综上,最长公共子序列的状态转移方程为:

-对于长度为 N 的序列 S1 和 长度为 M 的序列 S2,dp[N][M] 就是序列 S1 和序列 S2 的最长公共子序列长度。 +对于长度为 N 的序列 S1 和长度为 M 的序列 S2,dp[N][M] 就是序列 S1 和序列 S2 的最长公共子序列长度。 与最长递增子序列相比,最长公共子序列有以下不同点: - 针对的是两个序列,求它们的最长公共子序列。 -- 在最长递增子序列中,dp[i] 表示以 Si 为结尾的最长递增子序列长度,子序列必须包含 Si ;在最长公共子序列中,dp[i][j] 表示 S1 中前 i 个字符与 S2 中前 j 个字符的最长公共子序列长度,不一定包含 S1i 和 S2j 。 +- 在最长递增子序列中,dp[i] 表示以 Si 为结尾的最长递增子序列长度,子序列必须包含 Si ;在最长公共子序列中,dp[i][j] 表示 S1 中前 i 个字符与 S2 中前 j 个字符的最长公共子序列长度,不一定包含 S1i 和 S2j。 - 在求最终解时,最长公共子序列中 dp[N][M] 就是最终解,而最长递增子序列中 dp[N] 不是最终解,因为以 SN 为结尾的最长递增子序列不一定是整个序列最长递增子序列,需要遍历一遍 dp 数组找到最大者。 ```java @@ -2956,9 +2951,7 @@ public int lengthOfLCS(int[] nums1, int[] nums2) { - 第 i 件物品没添加到背包,总体积不超过 j 的前 i 件物品的最大价值就是总体积不超过 j 的前 i-1 件物品的最大价值,dp[i][j] = dp[i-1][j]。 - 第 i 件物品添加到背包中,dp[i][j] = dp[i-1][j-w] + v。 -第 i 件物品可添加也可以不添加,取决于哪种情况下最大价值更大。 - -综上,0-1 背包的状态转移方程为: +第 i 件物品可添加也可以不添加,取决于哪种情况下最大价值更大。因此,0-1 背包的状态转移方程为:

@@ -2981,11 +2974,11 @@ public int knapsack(int W, int N, int[] weights, int[] values) { **空间优化** -在程序实现时可以对 0-1 背包做优化。观察状态转移方程可以知道,前 i 件物品的状态仅由前 i-1 件物品的状态有关,因此可以将 dp 定义为一维数组,其中 dp[j] 既可以表示 dp[i-1][j] 也可以表示 dp[i][j]。此时, +在程序实现时可以对 0-1 背包做优化。观察状态转移方程可以知道,前 i 件物品的状态仅与前 i-1 件物品的状态有关,因此可以将 dp 定义为一维数组,其中 dp[j] 既可以表示 dp[i-1][j] 也可以表示 dp[i][j]。此时,

-因为 dp[j-w] 表示 dp[i-1][j-w],因此不能先求 dp[i][j-w],以防止将 dp[i-1][j-w] 覆盖。也就是说要先计算 dp[i][j] 再计算 dp[i][j-w],在程序实现时需要按倒序来循环求解。 +因为 dp[j-w] 表示 dp[i-1][j-w],因此不能先求 dp[i][j-w],以防将 dp[i-1][j-w] 覆盖。也就是说要先计算 dp[i][j] 再计算 dp[i][j-w],在程序实现时需要按倒序来循环求解。 ```java public int knapsack(int W, int N, int[] weights, int[] values) { @@ -3205,7 +3198,7 @@ public int findMaxForm(String[] strs, int m, int n) { } ``` -**找零钱的方法数** +**找零钱的最少硬币数** [322. Coin Change (Medium)](https://leetcode.com/problems/coin-change/description/) @@ -3286,60 +3279,6 @@ public int combinationSum4(int[] nums, int target) { } ``` -**只能进行 k 次的股票交易** - -[188. Best Time to Buy and Sell Stock IV (Hard)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iv/description/) - -```java -public int maxProfit(int k, int[] prices) { - int n = prices.length; - if (k >= n / 2) { // 这种情况下该问题退化为普通的股票交易问题 - int maxProfit = 0; - for (int i = 1; i < n; i++) { - if (prices[i] > prices[i - 1]) { - maxProfit += prices[i] - prices[i - 1]; - } - } - return maxProfit; - } - int[][] maxProfit = new int[k + 1][n]; - for (int i = 1; i <= k; i++) { - int localMax = maxProfit[i - 1][0] - prices[0]; - for (int j = 1; j < n; j++) { - maxProfit[i][j] = Math.max(maxProfit[i][j - 1], prices[j] + localMax); - localMax = Math.max(localMax, maxProfit[i - 1][j] - prices[j]); - } - } - return maxProfit[k][n - 1]; -} -``` - -**只能进行两次的股票交易** - -[123. Best Time to Buy and Sell Stock III (Hard)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iii/description/) - -```java -public int maxProfit(int[] prices) { - int firstBuy = Integer.MIN_VALUE, firstSell = 0; - int secondBuy = Integer.MIN_VALUE, secondSell = 0; - for (int curPrice : prices) { - if (firstBuy < -curPrice) { - firstBuy = -curPrice; - } - if (firstSell < firstBuy + curPrice) { - firstSell = firstBuy + curPrice; - } - if (secondBuy < firstSell - curPrice) { - secondBuy = firstSell - curPrice; - } - if (secondSell < secondBuy + curPrice) { - secondSell = secondBuy + curPrice; - } - } - return secondSell; -} -``` - ### 股票交易 **需要冷却期的股票交易** @@ -3432,6 +3371,60 @@ public int maxProfit(int[] prices) { } ``` +**只能进行两次的股票交易** + +[123. Best Time to Buy and Sell Stock III (Hard)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iii/description/) + +```java +public int maxProfit(int[] prices) { + int firstBuy = Integer.MIN_VALUE, firstSell = 0; + int secondBuy = Integer.MIN_VALUE, secondSell = 0; + for (int curPrice : prices) { + if (firstBuy < -curPrice) { + firstBuy = -curPrice; + } + if (firstSell < firstBuy + curPrice) { + firstSell = firstBuy + curPrice; + } + if (secondBuy < firstSell - curPrice) { + secondBuy = firstSell - curPrice; + } + if (secondSell < secondBuy + curPrice) { + secondSell = secondBuy + curPrice; + } + } + return secondSell; +} +``` + +**只能进行 k 次的股票交易** + +[188. Best Time to Buy and Sell Stock IV (Hard)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iv/description/) + +```java +public int maxProfit(int k, int[] prices) { + int n = prices.length; + if (k >= n / 2) { // 这种情况下该问题退化为普通的股票交易问题 + int maxProfit = 0; + for (int i = 1; i < n; i++) { + if (prices[i] > prices[i - 1]) { + maxProfit += prices[i] - prices[i - 1]; + } + } + return maxProfit; + } + int[][] maxProfit = new int[k + 1][n]; + for (int i = 1; i <= k; i++) { + int localMax = maxProfit[i - 1][0] - prices[0]; + for (int j = 1; j < n; j++) { + maxProfit[i][j] = Math.max(maxProfit[i][j - 1], prices[j] + localMax); + localMax = Math.max(localMax, maxProfit[i - 1][j] - prices[j]); + } + } + return maxProfit[k][n - 1]; +} +``` + ### 字符串编辑 **删除两个字符串的字符使它们相等** @@ -3450,11 +3443,8 @@ Explanation: You need one step to make "sea" to "ea" and another step to make "e public int minDistance(String word1, String word2) { int m = word1.length(), n = word2.length(); int[][] dp = new int[m + 1][n + 1]; - for (int i = 0; i <= m; i++) { - for (int j = 0; j <= n; j++) { - if (i == 0 || j == 0) { - continue; - } + for (int i = 1; i <= m; i++) { + for (int j = 1; j <= n; j++) { if (word1.charAt(i - 1) == word2.charAt(j - 1)) { dp[i][j] = dp[i - 1][j - 1] + 1; } else { @@ -3612,7 +3602,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); } ``` @@ -3683,7 +3673,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) { @@ -6413,7 +6403,6 @@ public int maxChunksToSorted(int[] arr) { } ``` - ## 图 ### 二分图 @@ -6630,6 +6619,7 @@ public int[] findRedundantConnection(int[][] edges) { } private class UF { + private int[] id; UF(int N) { @@ -6675,7 +6665,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。 位与运算技巧: @@ -6920,7 +6910,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 14d3b317..73a2e9ec 100644 --- a/notes/Linux.md +++ b/notes/Linux.md @@ -129,15 +129,16 @@ info 与 man 类似,但是 info 将文档分成一个个页面,每个页面 /usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin ``` -env 命令可以获取当前终端的环境变量。 - ## sudo sudo 允许一般用户使用 root 可执行的命令,不过只有在 /etc/sudoers 配置文件中添加的用户才能使用该指令。 ## 包管理工具 -RPM 和 DPKG 为最常见的两类软件包管理工具。RPM 全称为 Redhat Package Manager,最早由 Red Hat 公司制定实施,随后被 GNU 开源操作系统接受并成为很多 Linux 系统 (RHEL) 的既定软件标准。与 RPM 进行竞争的是基于 Debian 操作系统 (UBUNTU) 的 DEB 软件包管理工具 DPKG,全称为 Debian Package,功能方面与 RPM 相似。 +RPM 和 DPKG 为最常见的两类软件包管理工具: + +- RPM 全称为 Redhat Package Manager,最早由 Red Hat 公司制定实施,随后被 GNU 开源操作系统接受并成为很多 Linux 系统 (RHEL) 的既定软件标准。 +- 与 RPM 进行竞争的是基于 Debian 操作系统 (Ubuntu) 的 DEB 软件包管理工具 DPKG,全称为 Debian Package,功能方面与 RPM 相似。 YUM 基于 RPM,具有依赖管理功能,并具有软件升级的功能。 @@ -194,13 +195,13 @@ IDE(ATA)全称 Advanced Technology Attachment,接口速度最大为 133MB/ ### 2. SATA -SATA 全称 Serial ATA,也就是使用串口的 ATA 接口,因抗干扰性强,且对数据线的长度要求比 ATA 低很多,支持热插拔等功能,SATA-II 的接口速度为 300MiB/s,而新的 SATA-III 标准可达到 600MiB/s 的传输速度。SATA 的数据线也比 ATA 的细得多,有利于机箱内的空气流通,整理线材也比较方便。 +SATA 全称 Serial ATA,也就是使用串口的 ATA 接口,抗干扰性强,且对数据线的长度要求比 ATA 低很多,支持热插拔等功能。SATA-II 的接口速度为 300MiB/s,而新的 SATA-III 标准可达到 600MiB/s 的传输速度。SATA 的数据线也比 ATA 的细得多,有利于机箱内的空气流通,整理线材也比较方便。

### 3. SCSI -SCSI 全称是 Small Computer System Interface(小型机系统接口),经历多代的发展,从早期的 SCSI-II,到目前的 Ultra320 SCSI 以及 Fiber-Channel(光纤通道),接口型式也多种多样。SCSI 硬盘广为工作站级个人电脑以及服务器所使用,因此会使用较为先进的技术,如碟片转速 15000rpm 的高转速,且资料传输时 CPU 占用率较低,但是单价也比相同容量的 ATA 及 SATA 硬盘更加昂贵。 +SCSI 全称是 Small Computer System Interface(小型机系统接口),经历多代的发展,从早期的 SCSI-II 到目前的 Ultra320 SCSI 以及 Fiber-Channel(光纤通道),接口型式也多种多样。SCSI 硬盘广为工作站级个人电脑以及服务器所使用,因此会使用较为先进的技术,如碟片转速 15000rpm 的高转速,且传输时 CPU 占用率较低,但是单价也比相同容量的 ATA 及 SATA 硬盘更加昂贵。

@@ -229,7 +230,7 @@ Linux 中每个硬件都被当做一个文件,包括磁盘。磁盘以磁盘 MBR 中,第一个扇区最重要,里面有主要开机记录(Master boot record, MBR)及分区表(partition table),其中主要开机记录占 446 bytes,分区表占 64 bytes。 -分区表只有 64 bytes,最多只能存储 4 个分区,这 4 个分区为主分区(Primary)和扩展分区(Extended)。其中扩展分区只有一个,它将其它扇区用来记录分区表,因此通过扩展分区可以分出更多分区,这些分区称为逻辑分区。 +分区表只有 64 bytes,最多只能存储 4 个分区,这 4 个分区为主分区(Primary)和扩展分区(Extended)。其中扩展分区只有一个,它使用其它扇区用记录额外的分区表,因此通过扩展分区可以分出更多分区,这些分区称为逻辑分区。 Linux 也把分区当成文件,分区文件的命名方式为:磁盘文件名 + 编号,例如 /dev/sda1。注意,逻辑分区的编号从 5 开始。 @@ -251,10 +252,10 @@ MBR 不支持 2.2 TB 以上的硬盘,GPT 则最多支持到 233 TB BIOS(Basic Input/Output System,基本输入输出系统),它是一个固件(嵌入在硬件中的软件),BIOS 程序存放在断电后内容不会丢失的只读内存中。 -BIOS 是开机的时候计算机执行的第一个程序,这个程序知道可以开机的磁盘,并读取磁盘第一个扇区的主要开机记录(MBR),由主要开机记录(MBR)执行其中的开机管理程序,这个开机管理程序会加载操作系统的核心文件。 -

+BIOS 是开机的时候计算机执行的第一个程序,这个程序知道可以开机的磁盘,并读取磁盘第一个扇区的主要开机记录(MBR),由主要开机记录(MBR)执行其中的开机管理程序,这个开机管理程序会加载操作系统的核心文件。 + 主要开机记录(MBR)中的开机管理程序提供以下功能:选单、载入核心文件以及转交其它开机管理程序。转交这个功能可以用来实现了多重引导,只需要将另一个操作系统的开机管理程序安装在其它分区的启动扇区上,在启动开机管理程序时,就可以通过选单选择启动当前的操作系统或者转交给其它开机管理程序从而启动另一个操作系统。 下图中,第一扇区的主要开机记录(MBR)中的开机管理程序提供了两个选单:M1、M2,M1 指向了 Windows 操作系统,而 M2 指向其它分区的启动扇区,里面包含了另外一个开机管理程序,提供了一个指向 Linux 的选单。 @@ -283,11 +284,10 @@ BIOS 不可以读取 GPT 分区表,而 UEFI 可以。 除此之外还包括: - superblock:记录文件系统的整体信息,包括 inode 和 block 的总量、使用量、剩余量,以及文件系统的格式与相关信息等; -- block bitmap:记录 block 是否被使用的位域; +- block bitmap:记录 block 是否被使用的位域。

- ## 文件读取 对于 Ext2 文件系统,当要读取一个文件的内容时,先在 inode 中去查找文件内容所在的所有 block,然后把所有 block 的内容读出来。 @@ -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/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/notes/设计模式.md b/notes/设计模式.md index b8f4dc9b..531595e3 100644 --- a/notes/设计模式.md +++ b/notes/设计模式.md @@ -60,9 +60,9 @@ (一)懒汉式-线程不安全 -以下实现中,私有静态变量 uniqueInstance 被延迟化实例化,这样做的好处是,如果没有用到该类,那么就不会实例化 uniqueInstance,从而节约资源。 +以下实现中,私有静态变量 uniqueInstance 被延迟实例化,这样做的好处是,如果没有用到该类,那么就不会实例化 uniqueInstance,从而节约资源。 -这个实现在多线程环境下是不安全的,如果多个线程能够同时进入 `if (uniqueInstance == null)` ,并且此时 uniqueInstance 为 null,那么多个线程会执行 `uniqueInstance = new Singleton();` 语句,这将导致多次实例化 uniqueInstance。 +这个实现在多线程环境下是不安全的,如果多个线程能够同时进入 `if (uniqueInstance == null)` ,并且此时 uniqueInstance 为 null,那么会有多个线程执行 `uniqueInstance = new Singleton();` 语句,这将导致多次实例化 uniqueInstance。 ```java public class Singleton { @@ -81,11 +81,21 @@ public class Singleton { } ``` -(二)懒汉式-线程安全 +(二)饿汉式-线程安全 -只需要对 getUniqueInstance() 方法加锁,那么在一个时间点只能有一个线程能够进入该方法,从而避免了对 uniqueInstance 进行多次实例化的问题。 +线程不安全问题主要是由于 uniqueInstance 被多次实例化,采取直接实例化 uniqueInstance 的方式就不会产生线程不安全问题。 -但是这样有一个问题,就是当一个线程进入该方法之后,其它线程试图进入该方法都必须等待,因此性能上有一定的损耗。 +但是直接实例化的方式也丢失了延迟实例化带来的节约资源的好处。 + +```java +private static Singleton uniqueInstance = new Singleton(); +``` + +(三)懒汉式-线程安全 + +只需要对 getUniqueInstance() 方法加锁,那么在一个时间点只能有一个线程能够进入该方法,从而避免了多次实例化 uniqueInstance 的问题。 + +但是当一个线程进入该方法之后,其它试图进入该方法的线程都必须等待,因此性能上有一定的损耗。 ```java public static synchronized Singleton getUniqueInstance() { @@ -96,16 +106,6 @@ public static synchronized Singleton getUniqueInstance() { } ``` -(三)饿汉式-线程安全 - -线程不安全问题主要是由于 uniqueInstance 被实例化了多次,如果 uniqueInstance 采用直接实例化的话,就不会被实例化多次,也就不会产生线程不安全问题。 - -但是直接实例化的方式也丢失了延迟实例化带来的节约资源的好处。 - -```java -private static Singleton uniqueInstance = new Singleton(); -``` - (四)双重校验锁-线程安全 uniqueInstance 只需要被实例化一次,之后就可以直接使用了。加锁操作只需要对实例化那部分的代码进行,只有当 uniqueInstance 没有被实例化时,才需要进行加锁。 @@ -175,7 +175,7 @@ public class Singleton { } ``` -(五)枚举实现 +(六)枚举实现 这是单例模式的最佳实践,它实现简单,并且在面对复杂的序列化或者反射攻击的时候,能够防止实例化多次。 @@ -231,27 +231,9 @@ public class Singleton implements Serializable { 简单工厂不是设计模式,更像是一种编程习惯。它把实例化的操作单独放到一个类中,这个类就成为简单工厂类,让简单工厂类来决定应该用哪个具体子类来实例化。 -

- 这样做能把客户类和具体子类的实现解耦,客户类不再需要知道有哪些子类以及应当实例化哪个子类。因为客户类往往有多个,如果不使用简单工厂,所有的客户类都要知道所有子类的细节。而且一旦子类发生改变,例如增加子类,那么所有的客户类都要进行修改。 -如果存在下面这种代码,就需要使用简单工厂将对象实例化的部分放到简单工厂中。 - -```java -public class Client { - public static void main(String[] args) { - int type = 1; - Product product; - if (type == 1) { - product = new ConcreteProduct1(); - } else if (type == 2) { - product = new ConcreteProduct2(); - } else { - product = new ConcreteProduct(); - } - } -} -``` +

### 实现 @@ -275,6 +257,27 @@ public class ConcreteProduct2 implements Product { } ``` +以下的 Client 类中包含了实例化的代码,这是一种错误的实现,如果在客户类中存在实例化代码,就需要将代码放到简单工厂中。 + +```java +public class Client { + public static void main(String[] args) { + int type = 1; + Product product; + if (type == 1) { + product = new ConcreteProduct1(); + } else if (type == 2) { + product = new ConcreteProduct2(); + } else { + product = new ConcreteProduct(); + } + // do something with the product + } +} +``` + +以下的 SimpleFactory 是简单工厂实现,它被所有需要进行实例化的客户类调用。 + ```java public class SimpleFactory { public Product createProduct(int type) { @@ -293,6 +296,7 @@ public class Client { public static void main(String[] args) { SimpleFactory simpleFactory = new SimpleFactory(); Product product = simpleFactory.createProduct(1); + // do something with the product } } ``` @@ -2906,7 +2910,7 @@ Java 利用缓存来加速大量小对象的访问时间。 - 远程代理(Remote Proxy):控制对远程对象(不同地址空间)的访问,它负责将请求及其参数进行编码,并向不同地址空间中的对象发送已经编码的请求。 - 虚拟代理(Virtual Proxy):根据需要创建开销很大的对象,它可以缓存实体的附加信息,以便延迟对它的访问,例如在网站加载一个很大图片时,不能马上完成,可以用虚拟代理缓存图片的大小信息,然后生成一张临时图片代替原始图片。 - 保护代理(Protection Proxy):按权限控制对象的访问,它负责检查调用者是否具有实现一个请求所必须的访问权限。 -- 智能代理(Smart Reference):取代了简单的指针,它在访问对象时执行一些附加操作:记录对象的引用次数,比如智能智能;当第一次引用一个持久化对象时,将它装入内存;在访问一个实际对象前,检查是否已经锁定了它,以确保其它对象不能改变它。 +- 智能代理(Smart Reference):取代了简单的指针,它在访问对象时执行一些附加操作:记录对象的引用次数;当第一次引用一个持久化对象时,将它装入内存;在访问一个实际对象前,检查是否已经锁定了它,以确保其它对象不能改变它。

diff --git a/notes/面向对象思想.md b/notes/面向对象思想.md index 9b59e43d..203c7ebf 100644 --- a/notes/面向对象思想.md +++ b/notes/面向对象思想.md @@ -37,6 +37,7 @@ ```java public class Person { + private String name; private int gender; private int age; @@ -63,17 +64,20 @@ public class Person { 继承实现了 **IS-A** 关系,例如 Cat 和 Animal 就是一种 IS-A 关系,因此 Cat 可以继承自 Animal,从而获得 Animal 非 private 的属性和方法。 +继承应该遵循里氏替换原则,子类对象必须能够替换掉所有父类对象。 + Cat 可以当做 Animal 来使用,也就是说可以使用 Animal 引用 Cat 对象。父类引用指向子类对象称为 **向上转型** 。 ```java Animal animal = new Cat(); ``` -继承应该遵循里氏替换原则,子类对象必须能够替换掉所有父类对象。 - ## 多态 -多态分为编译时多态和运行时多态。编译时多态主要指方法的重载,运行时多态指程序中定义的对象引用所指向的具体类型在运行期间才确定。 +多态分为编译时多态和运行时多态: + +- 编译时多态主要指方法的重载 +- 运行时多态指程序中定义的对象引用所指向的具体类型在运行期间才确定 运行时多态有三个条件: @@ -116,7 +120,7 @@ public class Music { # 二、类图 -以下类图使用 [PlantUML](https://www.planttext.com/) 绘制,更多语法及使用请参考:http://plantuml.com/ +以下类图使用 [PlantUML](https://www.planttext.com/) 绘制,更多语法及使用请参考:http://plantuml.com/ 。 ## 泛化关系 (Generalization) @@ -327,7 +331,7 @@ Vihicle .. N ### 2. 合成复用原则 -尽量使用对象组合,而不是继承来达到复用的目的。 +尽量使用对象组合,而不是通过继承来达到复用的目的。 ### 3. 共同封闭原则 @@ -349,3 +353,4 @@ Vihicle .. N - [看懂 UML 类图和时序图](http://design-patterns.readthedocs.io/zh_CN/latest/read_uml.html#generalization) - [UML 系列——时序图(顺序图)sequence diagram](http://www.cnblogs.com/wolf-sun/p/UML-Sequence-diagram.html) - [面向对象编程三大特性 ------ 封装、继承、多态](http://blog.csdn.net/jianyuerensheng/article/details/51602015) +