auto commit
This commit is contained in:
@ -6,7 +6,7 @@
|
||||
|
||||
## 题目描述
|
||||
|
||||
求斐波那契数列的第 n 项,n <= 39。
|
||||
求斐波那契数列的第 n 项,n \<= 39。
|
||||
|
||||
<!--<div align="center"><img src="https://latex.codecogs.com/gif.latex?f(n)=\left\{\begin{array}{rcl}0&&{n=0}\\1&&{n=1}\\f(n-1)+f(n-2)&&{n>1}\end{array}\right." class="mathjax-pic"/></div> <br> -->
|
||||
|
||||
@ -67,10 +67,3 @@ public class Solution {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -40,10 +40,3 @@ public int RectCover(int n) {
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -38,10 +38,3 @@ public int JumpFloor(int n) {
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -58,10 +58,3 @@ public int JumpFloorII(int target) {
|
||||
return (int) Math.pow(2, target - 1);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -20,7 +20,7 @@
|
||||
|
||||
通过修改二分查找算法进行求解(l 代表 low,m 代表 mid,h 代表 high):
|
||||
|
||||
- 当 nums[m] <= nums[h] 时,表示 [m, h] 区间内的数组是非递减数组,[l, m] 区间内的数组是旋转数组,此时令 h = m;
|
||||
- 当 nums[m] \<= nums[h] 时,表示 [m, h] 区间内的数组是非递减数组,[l, m] 区间内的数组是旋转数组,此时令 h = m;
|
||||
- 否则 [m + 1, h] 区间内的数组是旋转数组,令 l = m + 1。
|
||||
|
||||
```java
|
||||
@ -65,10 +65,3 @@ private int minNumber(int[] nums, int l, int h) {
|
||||
return nums[l];
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -62,10 +62,3 @@ private char[][] buildMatrix(char[] array) {
|
||||
return matrix;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -56,10 +56,3 @@ private void initDigitSum() {
|
||||
this.digitSum[i][j] = digitSumOne[i] + digitSumOne[j];
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -22,17 +22,17 @@ return 36 (10 = 3 + 3 + 4)
|
||||
|
||||
尽可能得多剪长度为 3 的绳子,并且不允许有长度为 1 的绳子出现。如果出现了,就从已经切好长度为 3 的绳子中拿出一段与长度为 1 的绳子重新组合,把它们切成两段长度为 2 的绳子。以下为证明过程。
|
||||
|
||||
将绳子拆成 1 和 n-1,则 1(n-1)-n=-1<0,即拆开后的乘积一定更小,所以不能出现长度为 1 的绳子。
|
||||
将绳子拆成 1 和 n-1,则 1(n-1)-n=-1\<0,即拆开后的乘积一定更小,所以不能出现长度为 1 的绳子。
|
||||
|
||||
将绳子拆成 2 和 n-2,则 2(n-2)-n = n-4,在 n>=4 时这样拆开能得到的乘积会比不拆更大。
|
||||
将绳子拆成 2 和 n-2,则 2(n-2)-n = n-4,在 n\>=4 时这样拆开能得到的乘积会比不拆更大。
|
||||
|
||||
将绳子拆成 3 和 n-3,则 3(n-3)-n = 2n-9,在 n>=5 时效果更好。
|
||||
将绳子拆成 3 和 n-3,则 3(n-3)-n = 2n-9,在 n\>=5 时效果更好。
|
||||
|
||||
将绳子拆成 4 和 n-4,因为 4=2\*2,因此效果和拆成 2 一样。
|
||||
|
||||
将绳子拆成 5 和 n-5,因为 5=2+3,而 5<2\*3,所以不能出现 5 的绳子,而是尽可能拆成 2 和 3。
|
||||
将绳子拆成 5 和 n-5,因为 5=2+3,而 5\<2\*3,所以不能出现 5 的绳子,而是尽可能拆成 2 和 3。
|
||||
|
||||
将绳子拆成 6 和 n-6,因为 6=3+3,而 6<3\*3,所以不能出现 6 的绳子,而是拆成 3 和 3。这里 6 同样可以拆成 6=2+2+2,但是 3(n - 3) - 2(n - 2) = n - 5 >= 0,在 n>=5 的情况下将绳子拆成 3 比拆成 2 效果更好。
|
||||
将绳子拆成 6 和 n-6,因为 6=3+3,而 6\<3\*3,所以不能出现 6 的绳子,而是拆成 3 和 3。这里 6 同样可以拆成 6=2+2+2,但是 3(n - 3) - 2(n - 2) = n - 5 \>= 0,在 n\>=5 的情况下将绳子拆成 3 比拆成 2 效果更好。
|
||||
|
||||
继续拆成更大的绳子可以发现都比拆成 2 和 3 的效果更差,因此我们只考虑将绳子拆成 2 和 3,并且优先拆成 3,当拆到绳子长度 n 等于 4 时,也就是出现 3+1,此时只能拆成 2+2。
|
||||
|
||||
@ -65,10 +65,3 @@ public int integerBreak(int n) {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -25,10 +25,3 @@ public int NumberOf1(int n) {
|
||||
return cnt;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -44,10 +44,3 @@ private double pow(double x, int n) {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -38,10 +38,3 @@ private void printNumber(char[] number) {
|
||||
System.out.println();
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -35,10 +35,3 @@ public ListNode deleteNode(ListNode head, ListNode tobeDelete) {
|
||||
return head;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -23,10 +23,3 @@ public ListNode deleteDuplication(ListNode pHead) {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -38,10 +38,3 @@ public boolean match(char[] str, char[] pattern) {
|
||||
return dp[m][n];
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -47,10 +47,3 @@ public boolean isNumeric(char[] str) {
|
||||
return new String(str).matches("[+-]?\\d*(\\.\\d+)?([eE][+-]?\\d+)?");
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -60,10 +60,3 @@ private void swap(int[] nums, int i, int j) {
|
||||
nums[j] = t;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -25,10 +25,3 @@ public ListNode FindKthToTail(ListNode head, int k) {
|
||||
return P2;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -41,10 +41,3 @@ public ListNode EntryNodeOfLoop(ListNode pHead) {
|
||||
return slow;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -34,10 +34,3 @@ public ListNode ReverseList(ListNode head) {
|
||||
return newList.next;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -49,10 +49,3 @@ public ListNode Merge(ListNode list1, ListNode list2) {
|
||||
return head.next;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -27,10 +27,3 @@ private boolean isSubtreeWithRoot(TreeNode root1, TreeNode root2) {
|
||||
return isSubtreeWithRoot(root1.left, root2.left) && isSubtreeWithRoot(root1.right, root2.right);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -23,10 +23,3 @@ private void swap(TreeNode root) {
|
||||
root.right = t;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -25,10 +25,3 @@ boolean isSymmetrical(TreeNode t1, TreeNode t2) {
|
||||
return isSymmetrical(t1.left, t2.right) && isSymmetrical(t1.right, t2.left);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -14,7 +14,7 @@
|
||||
|
||||
## 解题思路
|
||||
|
||||
一层一层从外到里打印,观察可知每一层打印都有相同的处理步骤,唯一不同的是上下左右的边界不同了。因此使用四个变量 r1, r2, c1, c2 分别存储上下左右边界值,从而定义当前最外层。打印当前最外层的顺序:从左到右打印最上一行->从上到下打印最右一行->从右到左打印最下一行->从下到上打印最左一行。应当注意只有在 r1 != r2 时才打印最下一行,也就是在当前最外层的行数大于 1 时才打印最下一行,这是因为当前最外层只有一行时,继续打印最下一行,会导致重复打印。打印最左一行也要做同样处理。
|
||||
一层一层从外到里打印,观察可知每一层打印都有相同的处理步骤,唯一不同的是上下左右的边界不同了。因此使用四个变量 r1, r2, c1, c2 分别存储上下左右边界值,从而定义当前最外层。打印当前最外层的顺序:从左到右打印最上一行-\>从上到下打印最右一行-\>从右到左打印最下一行-\>从下到上打印最左一行。应当注意只有在 r1 != r2 时才打印最下一行,也就是在当前最外层的行数大于 1 时才打印最下一行,这是因为当前最外层只有一行时,继续打印最下一行,会导致重复打印。打印最左一行也要做同样处理。
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20201104010609223.png" width="500px"> </div><br>
|
||||
|
||||
@ -42,10 +42,3 @@ public ArrayList<Integer> printMatrix(int[][] matrix) {
|
||||
return ret;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -49,10 +49,3 @@ private void swap(int[] nums, int i, int j) {
|
||||
nums[j] = t;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -36,10 +36,3 @@ public int min() {
|
||||
return minStack.peek();
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -30,10 +30,3 @@ public boolean IsPopOrder(int[] pushSequence, int[] popSequence) {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -35,10 +35,3 @@ public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
|
||||
return ret;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -30,10 +30,3 @@ ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
|
||||
return ret;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -34,10 +34,3 @@ public ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
|
||||
return ret;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -32,10 +32,3 @@ private boolean verify(int[] sequence, int first, int last) {
|
||||
return verify(sequence, first, cutIndex - 1) && verify(sequence, cutIndex, last - 1);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -34,10 +34,3 @@ private void backtracking(TreeNode node, int target, ArrayList<Integer> path) {
|
||||
path.remove(path.size() - 1);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -65,10 +65,3 @@ public RandomListNode Clone(RandomListNode pHead) {
|
||||
return pCloneHead;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -32,10 +32,3 @@ private void inOrder(TreeNode node) {
|
||||
inOrder(node.right);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -37,10 +37,3 @@ private TreeNode Deserialize() {
|
||||
return t;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -38,10 +38,3 @@ private void backtracking(char[] chars, boolean[] hasUsed, StringBuilder s) {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -25,10 +25,3 @@ public int MoreThanHalfNum_Solution(int[] nums) {
|
||||
return cnt > nums.length / 2 ? majority : 0;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -47,10 +47,3 @@ public boolean Find(int target, int[][] matrix) {
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -15,7 +15,7 @@
|
||||
|
||||
应该使用大顶堆来维护最小堆,而不能直接创建一个小顶堆并设置一个大小,企图让小顶堆中的元素都是最小元素。
|
||||
|
||||
Java 的 PriorityQueue 实现了堆的能力,PriorityQueue 默认是小顶堆,可以在在初始化时使用 Lambda 表达式 (o1, o2) -> o2 - o1 来实现大顶堆。其它语言也有类似的堆数据结构。
|
||||
Java 的 PriorityQueue 实现了堆的能力,PriorityQueue 默认是小顶堆,可以在在初始化时使用 Lambda 表达式 (o1, o2) -\> o2 - o1 来实现大顶堆。其它语言也有类似的堆数据结构。
|
||||
|
||||
```java
|
||||
public ArrayList<Integer> GetLeastNumbers_Solution(int[] nums, int k) {
|
||||
@ -83,10 +83,3 @@ private void swap(int[] nums, int i, int j) {
|
||||
nums[j] = t;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -40,10 +40,3 @@ public Double GetMedian() {
|
||||
return (double) right.peek();
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -29,10 +29,3 @@ public char FirstAppearingOnce() {
|
||||
return queue.isEmpty() ? '#' : queue.peek();
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -22,10 +22,3 @@ public int FindGreatestSumOfSubArray(int[] nums) {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -15,11 +15,4 @@ public int NumberOf1Between1AndN_Solution(int n) {
|
||||
}
|
||||
```
|
||||
|
||||
> [Leetcode : 233. Number of Digit One](https://leetcode.com/problems/number-of-digit-one/discuss/64381/4+-lines-O(log-n)-C++JavaPython)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
\> [Leetcode : 233. Number of Digit One](https://leetcode.com/problems/number-of-digit-one/discuss/64381/4+-lines-O(log-n)-C++JavaPython)
|
||||
|
@ -52,10 +52,3 @@ private int getDigitAtIndex(int index, int place) {
|
||||
return number.charAt(count) - '0';
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -1,6 +1,8 @@
|
||||
# 45. 把数组排成最小的数
|
||||
|
||||
[NowCoder](https://www.nowcoder.com/practice/8fecd3f8ba334add803bf2a06af1b993?tpId=13&tqId=11185&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github)
|
||||
## 题目链接
|
||||
|
||||
[牛客网](https://www.nowcoder.com/practice/8fecd3f8ba334add803bf2a06af1b993?tpId=13&tqId=11185&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github)
|
||||
|
||||
## 题目描述
|
||||
|
||||
@ -8,7 +10,7 @@
|
||||
|
||||
## 解题思路
|
||||
|
||||
可以看成是一个排序问题,在比较两个字符串 S1 和 S2 的大小时,应该比较的是 S1+S2 和 S2+S1 的大小,如果 S1+S2 < S2+S1,那么应该把 S1 排在前面,否则应该把 S2 排在前面。
|
||||
可以看成是一个排序问题,在比较两个字符串 S1 和 S2 的大小时,应该比较的是 S1+S2 和 S2+S1 的大小,如果 S1+S2 \< S2+S1,那么应该把 S1 排在前面,否则应该把 S2 排在前面。
|
||||
|
||||
```java
|
||||
public String PrintMinNumber(int[] numbers) {
|
||||
@ -25,10 +27,3 @@ public String PrintMinNumber(int[] numbers) {
|
||||
return ret;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -29,10 +29,3 @@ public int numDecodings(String s) {
|
||||
return dp[n];
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -33,10 +33,3 @@ public int getMost(int[][] values) {
|
||||
return dp[n - 1];
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -27,10 +27,3 @@ public int longestSubStringWithoutDuplication(String str) {
|
||||
return maxLen;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -28,10 +28,3 @@ public int GetUglyNumber_Solution(int N) {
|
||||
return dp[N - 1];
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -23,7 +23,7 @@ Output:
|
||||
|
||||
② 令 P1 指向字符串原来的末尾位置,P2 指向字符串现在的末尾位置。P1 和 P2 从后向前遍历,当 P1 遍历到一个空格时,就需要令 P2 指向的位置依次填充 02%(注意是逆序的),否则就填充上 P1 指向字符的值。从后向前遍是为了在改变 P2 所指向的内容时,不会影响到 P1 遍历原来字符串的内容。
|
||||
|
||||
③ 当 P2 遇到 P1 时(P2 <= P1),或者遍历结束(P1 < 0),退出。
|
||||
③ 当 P2 遇到 P1 时(P2 \<= P1),或者遍历结束(P1 \< 0),退出。
|
||||
|
||||
|
||||
|
||||
@ -50,10 +50,3 @@ public String replaceSpace(StringBuffer str) {
|
||||
return str.toString();
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -51,10 +51,3 @@ public int FirstNotRepeatingChar2(String str) {
|
||||
return -1;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -46,10 +46,3 @@ private void merge(int[] nums, int l, int m, int h) {
|
||||
nums[k] = tmp[k];
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -22,10 +22,3 @@ public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
|
||||
return l1;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -38,7 +38,7 @@ public int binarySearch(int[] nums, int K) {
|
||||
}
|
||||
```
|
||||
|
||||
但是在查找第一个位置时,找到元素之后应该继续往前找。也就是当 nums[m]>=k 时,在左区间继续查找,左区间应该包含 m 位置。
|
||||
但是在查找第一个位置时,找到元素之后应该继续往前找。也就是当 nums[m]\>=k 时,在左区间继续查找,左区间应该包含 m 位置。
|
||||
|
||||
```java
|
||||
private int binarySearch(int[] nums, int K) {
|
||||
@ -71,10 +71,3 @@ nums = [2,2], k = 2
|
||||
```
|
||||
|
||||
如果 h 的取值为 nums.length - 1,那么在查找最后一个位置时,binarySearch(nums, k + 1) - 1 = 1 - 1 = 0。这是因为 binarySearch 只会返回 [0, nums.length - 1] 范围的值,对于 binarySearch([2,2], 3) ,我们希望返回 3 插入 nums 中的位置,也就是数组最后一个位置再往后一个位置,即 nums.length。所以我们需要将 h 取值为 nums.length,从而使得 binarySearch 返回的区间更大,能够覆盖 k 大于 nums 最后一个元素的情况。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -25,10 +25,3 @@ private void inOrder(TreeNode root, int k) {
|
||||
inOrder(root.right, k);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -15,10 +15,3 @@ public int TreeDepth(TreeNode root) {
|
||||
return root == null ? 0 : 1 + Math.max(TreeDepth(root.left), TreeDepth(root.right));
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -28,10 +28,3 @@ private int height(TreeNode root) {
|
||||
return 1 + Math.max(left, right);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -32,10 +32,3 @@ public void FindNumsAppearOnce(int[] nums, int num1[], int num2[]) {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -13,8 +13,8 @@
|
||||
使用双指针,一个指针指向元素较小的值,一个指针指向元素较大的值。指向较小元素的指针从头向尾遍历,指向较大元素的指针从尾向头遍历。
|
||||
|
||||
- 如果两个指针指向元素的和 sum == target,那么这两个元素即为所求。
|
||||
- 如果 sum > target,移动较大的元素,使 sum 变小一些;
|
||||
- 如果 sum < target,移动较小的元素,使 sum 变大一些。
|
||||
- 如果 sum \> target,移动较大的元素,使 sum 变小一些;
|
||||
- 如果 sum \< target,移动较小的元素,使 sum 变大一些。
|
||||
|
||||
```java
|
||||
public ArrayList<Integer> FindNumbersWithSum(int[] nums, int target) {
|
||||
@ -31,10 +31,3 @@ public ArrayList<Integer> FindNumbersWithSum(int[] nums, int target) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -41,10 +41,3 @@ public ArrayList<ArrayList<Integer>> FindContinuousSequence(int sum) {
|
||||
return ret;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -47,10 +47,3 @@ private void swap(char[] c, int i, int j) {
|
||||
c[j] = t;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -41,10 +41,3 @@ private void swap(char[] chars, int i, int j) {
|
||||
chars[j] = t;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -35,10 +35,3 @@ public ArrayList<Integer> maxInWindows(int[] num, int size) {
|
||||
return ret;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -14,7 +14,7 @@
|
||||
|
||||
### 1. 使用递归
|
||||
|
||||
要逆序打印链表 1->2->3(3,2,1),可以先逆序打印链表 2->3(3,2),最后再打印第一个节点 1。而链表 2->3 可以看成一个新的链表,要逆序打印该链表可以继续使用求解函数,也就是在求解函数中调用自己,这就是递归函数。
|
||||
要逆序打印链表 1-\>2-\>3(3,2,1),可以先逆序打印链表 2-\>3(3,2),最后再打印第一个节点 1。而链表 2-\>3 可以看成一个新的链表,要逆序打印该链表可以继续使用求解函数,也就是在求解函数中调用自己,这就是递归函数。
|
||||
|
||||
```java
|
||||
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
|
||||
@ -87,10 +87,3 @@ public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
|
||||
return ret;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -72,10 +72,3 @@ public List<Map.Entry<Integer, Double>> dicesSum(int n) {
|
||||
return ret;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -37,10 +37,3 @@ public boolean isContinuous(int[] nums) {
|
||||
return cnt >= 0;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -21,10 +21,3 @@ public int LastRemaining_Solution(int n, int m) {
|
||||
return (LastRemaining_Solution(n - 1, m) + m) % n;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -27,10 +27,3 @@ public int maxProfit(int[] prices) {
|
||||
return maxProfit;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -14,7 +14,7 @@
|
||||
|
||||
条件与 && 具有短路原则,即在第一个条件语句为 false 的情况下不会去执行第二个条件语句。利用这一特性,将递归的返回条件取非然后作为 && 的第一个条件语句,递归的主体转换为第二个条件语句,那么当递归的返回条件为 true 的情况下就不会执行递归的主体部分,递归返回。
|
||||
|
||||
本题的递归返回条件为 n <= 0,取非后就是 n > 0;递归的主体部分为 sum += Sum_Solution(n - 1),转换为条件语句后就是 (sum += Sum_Solution(n - 1)) > 0。
|
||||
本题的递归返回条件为 n \<= 0,取非后就是 n \> 0;递归的主体部分为 sum += Sum_Solution(n - 1),转换为条件语句后就是 (sum += Sum_Solution(n - 1)) \> 0。
|
||||
|
||||
```java
|
||||
public int Sum_Solution(int n) {
|
||||
@ -23,10 +23,3 @@ public int Sum_Solution(int n) {
|
||||
return sum;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -10,19 +10,12 @@
|
||||
|
||||
## 解题思路
|
||||
|
||||
a ^ b 表示没有考虑进位的情况下两数的和,(a & b) << 1 就是进位。
|
||||
a ^ b 表示没有考虑进位的情况下两数的和,(a & b) \<\< 1 就是进位。
|
||||
|
||||
递归会终止的原因是 (a & b) << 1 最右边会多一个 0,那么继续递归,进位最右边的 0 会慢慢增多,最后进位会变为 0,递归终止。
|
||||
递归会终止的原因是 (a & b) \<\< 1 最右边会多一个 0,那么继续递归,进位最右边的 0 会慢慢增多,最后进位会变为 0,递归终止。
|
||||
|
||||
```java
|
||||
public int Add(int a, int b) {
|
||||
return b == 0 ? a : Add(a ^ b, (a & b) << 1);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -24,10 +24,3 @@ public int[] multiply(int[] A) {
|
||||
return B;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -37,10 +37,3 @@ public int StrToInt(String str) {
|
||||
return isNegative ? -ret : ret;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
### 解题思路
|
||||
|
||||
在二叉查找树中,两个节点 p, q 的公共祖先 root 满足 root.val >= p.val && root.val <= q.val。
|
||||
在二叉查找树中,两个节点 p, q 的公共祖先 root 满足 root.val \>= p.val && root.val \<= q.val。
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/047faac4-a368-4565-8331-2b66253080d3.jpg" width="250"/> </div><br>
|
||||
|
||||
@ -46,10 +46,3 @@ public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
|
||||
return left == null ? right : right == null ? left : root;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -39,10 +39,3 @@ private TreeNode reConstructBinaryTree(int[] pre, int preL, int preR, int inL) {
|
||||
return root;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -65,10 +65,3 @@ public TreeLinkNode GetNext(TreeLinkNode pNode) {
|
||||
return null;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -33,10 +33,3 @@ public int pop() throws Exception {
|
||||
return out.pop();
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -1,14 +1,10 @@
|
||||
# Docker
|
||||
<!-- GFM-TOC -->
|
||||
* [一、解决的问题](#一解决的问题)
|
||||
* [二、与虚拟机的比较](#二与虚拟机的比较)
|
||||
* [三、优势](#三优势)
|
||||
* [四、使用场景](#四使用场景)
|
||||
* [五、镜像与容器](#五镜像与容器)
|
||||
* [参考资料](#参考资料)
|
||||
* [Docker](#docker)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
|
||||
# 一、解决的问题
|
||||
## 一、解决的问题
|
||||
|
||||
由于不同的机器有不同的操作系统,以及不同的库和组件,在将一个应用部署到多台机器上需要进行大量的环境配置操作。
|
||||
|
||||
@ -16,57 +12,57 @@ Docker 主要解决环境配置问题,它是一种虚拟化技术,对进程
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/011f3ef6-d824-4d43-8b2c-36dab8eaaa72-1.png" width="400px"/> </div><br>
|
||||
|
||||
# 二、与虚拟机的比较
|
||||
## 二、与虚拟机的比较
|
||||
|
||||
虚拟机也是一种虚拟化技术,它与 Docker 最大的区别在于它是通过模拟硬件,并在硬件上安装操作系统来实现。
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/be608a77-7b7f-4f8e-87cc-f2237270bf69.png" width="500"/> </div><br>
|
||||
|
||||
## 启动速度
|
||||
### 启动速度
|
||||
|
||||
启动虚拟机需要先启动虚拟机的操作系统,再启动应用,这个过程非常慢;
|
||||
|
||||
而启动 Docker 相当于启动宿主操作系统上的一个进程。
|
||||
|
||||
## 占用资源
|
||||
### 占用资源
|
||||
|
||||
虚拟机是一个完整的操作系统,需要占用大量的磁盘、内存和 CPU 资源,一台机器只能开启几十个的虚拟机。
|
||||
|
||||
而 Docker 只是一个进程,只需要将应用以及相关的组件打包,在运行时占用很少的资源,一台机器可以开启成千上万个 Docker。
|
||||
|
||||
# 三、优势
|
||||
## 三、优势
|
||||
|
||||
除了启动速度快以及占用资源少之外,Docker 具有以下优势:
|
||||
|
||||
## 更容易迁移
|
||||
### 更容易迁移
|
||||
|
||||
提供一致性的运行环境。已经打包好的应用可以在不同的机器上进行迁移,而不用担心环境变化导致无法运行。
|
||||
|
||||
## 更容易维护
|
||||
### 更容易维护
|
||||
|
||||
使用分层技术和镜像,使得应用可以更容易复用重复的部分。复用程度越高,维护工作也越容易。
|
||||
|
||||
## 更容易扩展
|
||||
### 更容易扩展
|
||||
|
||||
可以使用基础镜像进一步扩展得到新的镜像,并且官方和开源社区提供了大量的镜像,通过扩展这些镜像可以非常容易得到我们想要的镜像。
|
||||
|
||||
# 四、使用场景
|
||||
## 四、使用场景
|
||||
|
||||
## 持续集成
|
||||
### 持续集成
|
||||
|
||||
持续集成指的是频繁地将代码集成到主干上,这样能够更快地发现错误。
|
||||
|
||||
Docker 具有轻量级以及隔离性的特点,在将代码集成到一个 Docker 中不会对其它 Docker 产生影响。
|
||||
|
||||
## 提供可伸缩的云服务
|
||||
### 提供可伸缩的云服务
|
||||
|
||||
根据应用的负载情况,可以很容易地增加或者减少 Docker。
|
||||
|
||||
## 搭建微服务架构
|
||||
### 搭建微服务架构
|
||||
|
||||
Docker 轻量级的特点使得它很适合用于部署、维护、组合微服务。
|
||||
|
||||
# 五、镜像与容器
|
||||
## 五、镜像与容器
|
||||
|
||||
镜像是一种静态的结构,可以看成面向对象里面的类,而容器是镜像的一个实例。
|
||||
|
||||
@ -76,7 +72,7 @@ Docker 轻量级的特点使得它很适合用于部署、维护、组合微服
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/docker-filesystems-busyboxrw.png"/> </div><br>
|
||||
|
||||
# 参考资料
|
||||
## 参考资料
|
||||
|
||||
- [DOCKER 101: INTRODUCTION TO DOCKER WEBINAR RECAP](https://blog.docker.com/2017/08/docker-101-introduction-docker-webinar-recap/)
|
||||
- [Docker 入门教程](http://www.ruanyifeng.com/blog/2018/02/docker-tutorial.html)
|
||||
@ -87,10 +83,3 @@ Docker 轻量级的特点使得它很适合用于部署、维护、组合微服
|
||||
- [What is Docker](https://www.docker.com/what-docker)
|
||||
- [持续集成是什么?](http://www.ruanyifeng.com/blog/2015/09/continuous-integration.html)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
55
notes/Git.md
55
notes/Git.md
@ -1,19 +1,21 @@
|
||||
# Git
|
||||
<!-- GFM-TOC -->
|
||||
* [集中式与分布式](#集中式与分布式)
|
||||
* [中心服务器](#中心服务器)
|
||||
* [工作流](#工作流)
|
||||
* [分支实现](#分支实现)
|
||||
* [冲突](#冲突)
|
||||
* [Fast forward](#fast-forward)
|
||||
* [储藏(Stashing)](#储藏stashing)
|
||||
* [SSH 传输设置](#ssh-传输设置)
|
||||
* [.gitignore 文件](#gitignore-文件)
|
||||
* [Git 命令一览](#git-命令一览)
|
||||
* [参考资料](#参考资料)
|
||||
* [Git](#git)
|
||||
* [集中式与分布式](#集中式与分布式)
|
||||
* [中心服务器](#中心服务器)
|
||||
* [工作流](#工作流)
|
||||
* [分支实现](#分支实现)
|
||||
* [冲突](#冲突)
|
||||
* [Fast forward](#fast-forward)
|
||||
* [储藏(Stashing)](#储藏stashing)
|
||||
* [SSH 传输设置](#ssh-传输设置)
|
||||
* [.gitignore 文件](#gitignore-文件)
|
||||
* [Git 命令一览](#git-命令一览)
|
||||
* [参考资料](#参考资料)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
|
||||
# 集中式与分布式
|
||||
## 集中式与分布式
|
||||
|
||||
Git 属于分布式版本控制系统,而 SVN 属于集中式。
|
||||
|
||||
@ -27,13 +29,13 @@ Git 属于分布式版本控制系统,而 SVN 属于集中式。
|
||||
|
||||
分布式版本控制新建分支、合并分支操作速度非常快,而集中式版本控制新建一个分支相当于复制一份完整代码。
|
||||
|
||||
# 中心服务器
|
||||
## 中心服务器
|
||||
|
||||
中心服务器用来交换每个用户的修改,没有中心服务器也能工作,但是中心服务器能够 24 小时保持开机状态,这样就能更方便的交换修改。
|
||||
|
||||
Github 就是一个中心服务器。
|
||||
|
||||
# 工作流
|
||||
## 工作流
|
||||
|
||||
新建一个仓库之后,当前目录就成为了工作区,工作区下有一个隐藏目录 .git,它属于 Git 的版本库。
|
||||
|
||||
@ -55,7 +57,7 @@ Git 的版本库有一个称为 Stage 的暂存区以及最后的 History 版本
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191208200543923.png"/> </div><br>
|
||||
|
||||
# 分支实现
|
||||
## 分支实现
|
||||
|
||||
使用指针将每个提交连接成一条时间线,HEAD 指针指向当前分支指针。
|
||||
|
||||
@ -73,13 +75,13 @@ Git 的版本库有一个称为 Stage 的暂存区以及最后的 History 版本
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191208203010540.png"/> </div><br>
|
||||
|
||||
# 冲突
|
||||
## 冲突
|
||||
|
||||
当两个分支都对同一个文件的同一行进行了修改,在分支合并时就会产生冲突。
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191208203034705.png"/> </div><br>
|
||||
|
||||
Git 会使用 <<<<<<< ,======= ,>>>>>>> 标记出不同分支的内容,只需要把不同分支中冲突部分修改成一样就能解决冲突。
|
||||
Git 会使用 \<\<\<\<\<\<\< ,======= ,\>\>\>\>\>\>\> 标记出不同分支的内容,只需要把不同分支中冲突部分修改成一样就能解决冲突。
|
||||
|
||||
```
|
||||
<<<<<<< HEAD
|
||||
@ -89,7 +91,7 @@ Creating a new branch is quick AND simple.
|
||||
>>>>>>> feature1
|
||||
```
|
||||
|
||||
# Fast forward
|
||||
## Fast forward
|
||||
|
||||
"快进式合并"(fast-farward merge),会直接将 master 分支指向合并的分支,这种模式下进行分支合并会丢失分支信息,也就不能在分支历史上看出分支信息。
|
||||
|
||||
@ -101,7 +103,7 @@ $ git merge --no-ff -m "merge with no-ff" dev
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191208203639712.png"/> </div><br>
|
||||
|
||||
# 储藏(Stashing)
|
||||
## 储藏(Stashing)
|
||||
|
||||
在一个分支上操作之后,如果还没有将修改提交到分支上,此时进行切换分支,那么另一个分支上也能看到新的修改。这是因为所有分支都共用一个工作区的缘故。
|
||||
|
||||
@ -115,7 +117,7 @@ HEAD is now at 049d078 added the index file (To restore them type "git stash app
|
||||
|
||||
该功能可以用于 bug 分支的实现。如果当前正在 dev 分支上进行开发,但是此时 master 上有个 bug 需要修复,但是 dev 分支上的开发还未完成,不想立即提交。在新建 bug 分支并切换到 bug 分支之前就需要使用 git stash 将 dev 分支的未提交修改储藏起来。
|
||||
|
||||
# SSH 传输设置
|
||||
## SSH 传输设置
|
||||
|
||||
Git 仓库和 Github 中心仓库之间的传输是通过 SSH 加密。
|
||||
|
||||
@ -127,7 +129,7 @@ $ ssh-keygen -t rsa -C "youremail@example.com"
|
||||
|
||||
然后把公钥 id_rsa.pub 的内容复制到 Github "Account settings" 的 SSH Keys 中。
|
||||
|
||||
# .gitignore 文件
|
||||
## .gitignore 文件
|
||||
|
||||
忽略以下文件:
|
||||
|
||||
@ -137,22 +139,15 @@ $ ssh-keygen -t rsa -C "youremail@example.com"
|
||||
|
||||
不需要全部自己编写,可以到 [https://github.com/github/gitignore](https://github.com/github/gitignore) 中进行查询。
|
||||
|
||||
# Git 命令一览
|
||||
## Git 命令一览
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/7a29acce-f243-4914-9f00-f2988c528412.jpg" width=""> </div><br>
|
||||
|
||||
比较详细的地址:http://www.cheat-sheets.org/saved-copy/git-cheat-sheet.pdf
|
||||
|
||||
# 参考资料
|
||||
## 参考资料
|
||||
|
||||
- [Git - 简明指南](http://rogerdudler.github.io/git-guide/index.zh.html)
|
||||
- [图解 Git](http://marklodato.github.io/visual-git-guide/index-zh-cn.html)
|
||||
- [廖雪峰 : Git 教程](https://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000)
|
||||
- [Learn Git Branching](https://learngitbranching.js.org/)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
255
notes/HTTP.md
255
notes/HTTP.md
@ -1,64 +1,22 @@
|
||||
# HTTP
|
||||
<!-- GFM-TOC -->
|
||||
* [一 、基础概念](#一-基础概念)
|
||||
* [请求和响应报文](#请求和响应报文)
|
||||
* [URL](#url)
|
||||
* [二、HTTP 方法](#二http-方法)
|
||||
* [GET](#get)
|
||||
* [HEAD](#head)
|
||||
* [POST](#post)
|
||||
* [PUT](#put)
|
||||
* [PATCH](#patch)
|
||||
* [DELETE](#delete)
|
||||
* [OPTIONS](#options)
|
||||
* [CONNECT](#connect)
|
||||
* [TRACE](#trace)
|
||||
* [三、HTTP 状态码](#三http-状态码)
|
||||
* [1XX 信息](#1xx-信息)
|
||||
* [2XX 成功](#2xx-成功)
|
||||
* [3XX 重定向](#3xx-重定向)
|
||||
* [4XX 客户端错误](#4xx-客户端错误)
|
||||
* [5XX 服务器错误](#5xx-服务器错误)
|
||||
* [四、HTTP 首部](#四http-首部)
|
||||
* [通用首部字段](#通用首部字段)
|
||||
* [请求首部字段](#请求首部字段)
|
||||
* [响应首部字段](#响应首部字段)
|
||||
* [实体首部字段](#实体首部字段)
|
||||
* [五、具体应用](#五具体应用)
|
||||
* [连接管理](#连接管理)
|
||||
* [Cookie](#cookie)
|
||||
* [缓存](#缓存)
|
||||
* [内容协商](#内容协商)
|
||||
* [内容编码](#内容编码)
|
||||
* [范围请求](#范围请求)
|
||||
* [分块传输编码](#分块传输编码)
|
||||
* [多部分对象集合](#多部分对象集合)
|
||||
* [虚拟主机](#虚拟主机)
|
||||
* [通信数据转发](#通信数据转发)
|
||||
* [六、HTTPS](#六https)
|
||||
* [加密](#加密)
|
||||
* [认证](#认证)
|
||||
* [完整性保护](#完整性保护)
|
||||
* [HTTPS 的缺点](#https-的缺点)
|
||||
* [七、HTTP/2.0](#七http20)
|
||||
* [HTTP/1.x 缺陷](#http1x-缺陷)
|
||||
* [二进制分帧层](#二进制分帧层)
|
||||
* [服务端推送](#服务端推送)
|
||||
* [首部压缩](#首部压缩)
|
||||
* [八、HTTP/1.1 新特性](#八http11-新特性)
|
||||
* [九、GET 和 POST 比较](#九get-和-post-比较)
|
||||
* [作用](#作用)
|
||||
* [参数](#参数)
|
||||
* [安全](#安全)
|
||||
* [幂等性](#幂等性)
|
||||
* [可缓存](#可缓存)
|
||||
* [XMLHttpRequest](#xmlhttprequest)
|
||||
* [参考资料](#参考资料)
|
||||
* [HTTP](#http)
|
||||
* [一 、基础概念](#一-基础概念)
|
||||
* [二、HTTP 方法](#二http-方法)
|
||||
* [三、HTTP 状态码](#三http-状态码)
|
||||
* [四、HTTP 首部](#四http-首部)
|
||||
* [五、具体应用](#五具体应用)
|
||||
* [六、HTTPS](#六https)
|
||||
* [七、HTTP/2.0](#七http20)
|
||||
* [八、HTTP/1.1 新特性](#八http11-新特性)
|
||||
* [九、GET 和 POST 比较](#九get-和-post-比较)
|
||||
* [参考资料](#参考资料)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
|
||||
# 一 、基础概念
|
||||
## 一 、基础概念
|
||||
|
||||
## 请求和响应报文
|
||||
### 请求和响应报文
|
||||
|
||||
客户端发送一个请求报文给服务器,服务器根据请求报文中的信息进行处理,并将处理结果放入响应报文中返回给客户端。
|
||||
|
||||
@ -120,7 +78,7 @@ X-Cache: HIT
|
||||
|
||||
```
|
||||
|
||||
## URL
|
||||
### URL
|
||||
|
||||
http 使用 URL( **U** niform **R**esource **L**ocator,统一资源定位符)来定位资源,它可以认为是是 URI(**U**niform **R**esource **I**dentifier,统一资源标识符)的一个子集,URL 在 URI 的基础上增加了定位能力。URI 除了包含 URL 之外,还包含 URN(Uniform Resource Name,统一资源名称),它知识用来定义一个资源的名称,并不具备定位该资源的能力。例如 urn:isbn:0451450523 用来定义一个书籍,但是却没有表示怎么找到这本书。
|
||||
|
||||
@ -131,35 +89,35 @@ http 使用 URL( **U** niform **R**esource **L**ocator,统一资源定位符
|
||||
- [rfc2616:3.2.2 http URL](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.2.2)
|
||||
- [What is the difference between a URI, a URL and a URN?](https://stackoverflow.com/questions/176264/what-is-the-difference-between-a-uri-a-url-and-a-urn)
|
||||
|
||||
# 二、HTTP 方法
|
||||
## 二、HTTP 方法
|
||||
|
||||
客户端发送的 **请求报文** 第一行为请求行,包含了方法字段。
|
||||
|
||||
## GET
|
||||
### GET
|
||||
|
||||
> 获取资源
|
||||
\> 获取资源
|
||||
|
||||
当前网络请求中,绝大部分使用的是 GET 方法。
|
||||
|
||||
## HEAD
|
||||
### HEAD
|
||||
|
||||
> 获取报文首部
|
||||
\> 获取报文首部
|
||||
|
||||
和 GET 方法类似,但是不返回报文实体主体部分。
|
||||
|
||||
主要用于确认 URL 的有效性以及资源更新的日期时间等。
|
||||
|
||||
## POST
|
||||
### POST
|
||||
|
||||
> 传输实体主体
|
||||
\> 传输实体主体
|
||||
|
||||
POST 主要用来传输数据,而 GET 主要用来获取资源。
|
||||
|
||||
更多 POST 与 GET 的比较请见第九章。
|
||||
|
||||
## PUT
|
||||
### PUT
|
||||
|
||||
> 上传文件
|
||||
\> 上传文件
|
||||
|
||||
由于自身不带验证机制,任何人都可以上传文件,因此存在安全性问题,一般不使用该方法。
|
||||
|
||||
@ -172,9 +130,9 @@ Content-length: 16
|
||||
<p>New File</p>
|
||||
```
|
||||
|
||||
## PATCH
|
||||
### PATCH
|
||||
|
||||
> 对资源进行部分修改
|
||||
\> 对资源进行部分修改
|
||||
|
||||
PUT 也可以用于修改资源,但是只能完全替代原始资源,PATCH 允许部分修改。
|
||||
|
||||
@ -188,9 +146,9 @@ Content-Length: 100
|
||||
[description of changes]
|
||||
```
|
||||
|
||||
## DELETE
|
||||
### DELETE
|
||||
|
||||
> 删除文件
|
||||
\> 删除文件
|
||||
|
||||
与 PUT 功能相反,并且同样不带验证机制。
|
||||
|
||||
@ -198,17 +156,17 @@ Content-Length: 100
|
||||
DELETE /file.html HTTP/1.1
|
||||
```
|
||||
|
||||
## OPTIONS
|
||||
### OPTIONS
|
||||
|
||||
> 查询支持的方法
|
||||
\> 查询支持的方法
|
||||
|
||||
查询指定的 URL 能够支持的方法。
|
||||
|
||||
会返回 `Allow: GET, POST, HEAD, OPTIONS` 这样的内容。
|
||||
|
||||
## CONNECT
|
||||
### CONNECT
|
||||
|
||||
> 要求在与代理服务器通信时建立隧道
|
||||
\> 要求在与代理服务器通信时建立隧道
|
||||
|
||||
使用 SSL(Secure Sockets Layer,安全套接层)和 TLS(Transport Layer Security,传输层安全)协议把通信内容加密后经网络隧道传输。
|
||||
|
||||
@ -218,9 +176,9 @@ CONNECT www.example.com:443 HTTP/1.1
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/dc00f70e-c5c8-4d20-baf1-2d70014a97e3.jpg" width=""/> </div><br>
|
||||
|
||||
## TRACE
|
||||
### TRACE
|
||||
|
||||
> 追踪路径
|
||||
\> 追踪路径
|
||||
|
||||
服务器会将通信路径返回给客户端。
|
||||
|
||||
@ -230,7 +188,7 @@ CONNECT www.example.com:443 HTTP/1.1
|
||||
|
||||
- [rfc2616:9 Method Definitions](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html)
|
||||
|
||||
# 三、HTTP 状态码
|
||||
## 三、HTTP 状态码
|
||||
|
||||
服务器返回的 **响应报文** 中第一行为状态行,包含了状态码以及原因短语,用来告知客户端请求的结果。
|
||||
|
||||
@ -242,11 +200,11 @@ CONNECT www.example.com:443 HTTP/1.1
|
||||
| 4XX | Client Error(客户端错误状态码) | 服务器无法处理请求 |
|
||||
| 5XX | Server Error(服务器错误状态码) | 服务器处理请求出错 |
|
||||
|
||||
## 1XX 信息
|
||||
### 1XX 信息
|
||||
|
||||
- **100 Continue** :表明到目前为止都很正常,客户端可以继续发送请求或者忽略这个响应。
|
||||
|
||||
## 2XX 成功
|
||||
### 2XX 成功
|
||||
|
||||
- **200 OK**
|
||||
|
||||
@ -254,7 +212,7 @@ CONNECT www.example.com:443 HTTP/1.1
|
||||
|
||||
- **206 Partial Content** :表示客户端进行了范围请求,响应报文包含由 Content-Range 指定范围的实体内容。
|
||||
|
||||
## 3XX 重定向
|
||||
### 3XX 重定向
|
||||
|
||||
- **301 Moved Permanently** :永久性重定向
|
||||
|
||||
@ -268,7 +226,7 @@ CONNECT www.example.com:443 HTTP/1.1
|
||||
|
||||
- **307 Temporary Redirect** :临时重定向,与 302 的含义类似,但是 307 要求浏览器不会把重定向请求的 POST 方法改成 GET 方法。
|
||||
|
||||
## 4XX 客户端错误
|
||||
### 4XX 客户端错误
|
||||
|
||||
- **400 Bad Request** :请求报文中存在语法错误。
|
||||
|
||||
@ -278,19 +236,19 @@ CONNECT www.example.com:443 HTTP/1.1
|
||||
|
||||
- **404 Not Found**
|
||||
|
||||
## 5XX 服务器错误
|
||||
### 5XX 服务器错误
|
||||
|
||||
- **500 Internal Server Error** :服务器正在执行请求时发生错误。
|
||||
|
||||
- **503 Service Unavailable** :服务器暂时处于超负载或正在进行停机维护,现在无法处理请求。
|
||||
|
||||
# 四、HTTP 首部
|
||||
## 四、HTTP 首部
|
||||
|
||||
有 4 种类型的首部字段:通用首部字段、请求首部字段、响应首部字段和实体首部字段。
|
||||
|
||||
各种首部字段及其含义如下(不需要全记,仅供查阅):
|
||||
|
||||
## 通用首部字段
|
||||
### 通用首部字段
|
||||
|
||||
| 首部字段名 | 说明 |
|
||||
| :--: | :--: |
|
||||
@ -304,7 +262,7 @@ CONNECT www.example.com:443 HTTP/1.1
|
||||
| Via | 代理服务器的相关信息 |
|
||||
| Warning | 错误通知 |
|
||||
|
||||
## 请求首部字段
|
||||
### 请求首部字段
|
||||
|
||||
| 首部字段名 | 说明 |
|
||||
| :--: | :--: |
|
||||
@ -328,7 +286,7 @@ CONNECT www.example.com:443 HTTP/1.1
|
||||
| TE | 传输编码的优先级 |
|
||||
| User-Agent | HTTP 客户端程序的信息 |
|
||||
|
||||
## 响应首部字段
|
||||
### 响应首部字段
|
||||
|
||||
| 首部字段名 | 说明 |
|
||||
| :--: | :--: |
|
||||
@ -342,7 +300,7 @@ CONNECT www.example.com:443 HTTP/1.1
|
||||
| Vary | 代理服务器缓存的管理信息 |
|
||||
| WWW-Authenticate | 服务器对客户端的认证信息 |
|
||||
|
||||
## 实体首部字段
|
||||
### 实体首部字段
|
||||
|
||||
| 首部字段名 | 说明 |
|
||||
| :--: | :--: |
|
||||
@ -357,13 +315,13 @@ CONNECT www.example.com:443 HTTP/1.1
|
||||
| Expires | 实体主体过期的日期时间 |
|
||||
| Last-Modified | 资源的最后修改日期时间 |
|
||||
|
||||
# 五、具体应用
|
||||
## 五、具体应用
|
||||
|
||||
## 连接管理
|
||||
### 连接管理
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/HTTP1_x_Connections.png" width="800"/> </div><br>
|
||||
|
||||
### 1. 短连接与长连接
|
||||
#### 1. 短连接与长连接
|
||||
|
||||
当浏览器访问一个包含多张图片的 HTML 页面时,除了请求访问的 HTML 页面资源,还会请求图片资源。如果每进行一次 HTTP 通信就要新建一个 TCP 连接,那么开销会很大。
|
||||
|
||||
@ -372,13 +330,13 @@ CONNECT www.example.com:443 HTTP/1.1
|
||||
- 从 HTTP/1.1 开始默认是长连接的,如果要断开连接,需要由客户端或者服务器端提出断开,使用 `Connection : close`;
|
||||
- 在 HTTP/1.1 之前默认是短连接的,如果需要使用长连接,则使用 `Connection : Keep-Alive`。
|
||||
|
||||
### 2. 流水线
|
||||
#### 2. 流水线
|
||||
|
||||
默认情况下,HTTP 请求是按顺序发出的,下一个请求只有在当前请求收到响应之后才会被发出。由于受到网络延迟和带宽的限制,在下一个请求被发送到服务器之前,可能需要等待很长时间。
|
||||
|
||||
流水线是在同一条长连接上连续发出请求,而不用等待响应返回,这样可以减少延迟。
|
||||
|
||||
## Cookie
|
||||
### Cookie
|
||||
|
||||
HTTP 协议是无状态的,主要是为了让 HTTP 协议尽可能简单,使得它能够处理大量事务。HTTP/1.1 引入 Cookie 来保存状态信息。
|
||||
|
||||
@ -386,13 +344,13 @@ Cookie 是服务器发送到用户浏览器并保存在本地的一小块数据
|
||||
|
||||
Cookie 曾一度用于客户端数据的存储,因为当时并没有其它合适的存储办法而作为唯一的存储手段,但现在随着现代浏览器开始支持各种各样的存储方式,Cookie 渐渐被淘汰。新的浏览器 API 已经允许开发者直接将数据存储到本地,如使用 Web storage API(本地存储和会话存储)或 IndexedDB。
|
||||
|
||||
### 1. 用途
|
||||
#### 1. 用途
|
||||
|
||||
- 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息)
|
||||
- 个性化设置(如用户自定义设置、主题等)
|
||||
- 浏览器行为跟踪(如跟踪分析用户行为等)
|
||||
|
||||
### 2. 创建过程
|
||||
#### 2. 创建过程
|
||||
|
||||
服务器发送的响应报文包含 Set-Cookie 首部字段,客户端得到响应报文后把 Cookie 内容保存到浏览器中。
|
||||
|
||||
@ -413,7 +371,7 @@ Host: www.example.org
|
||||
Cookie: yummy_cookie=choco; tasty_cookie=strawberry
|
||||
```
|
||||
|
||||
### 3. 分类
|
||||
#### 3. 分类
|
||||
|
||||
- 会话期 Cookie:浏览器关闭之后它会被自动删除,也就是说它仅在会话期内有效。
|
||||
- 持久性 Cookie:指定过期时间(Expires)或有效期(max-age)之后就成为了持久性的 Cookie。
|
||||
@ -422,7 +380,7 @@ Cookie: yummy_cookie=choco; tasty_cookie=strawberry
|
||||
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT;
|
||||
```
|
||||
|
||||
### 4. 作用域
|
||||
#### 4. 作用域
|
||||
|
||||
Domain 标识指定了哪些主机可以接受 Cookie。如果不指定,默认为当前文档的主机(不包含子域名)。如果指定了 Domain,则一般包含子域名。例如,如果设置 Domain=mozilla.org,则 Cookie 也包含在子域名中(如 developer.mozilla.org)。
|
||||
|
||||
@ -432,7 +390,7 @@ Path 标识指定了主机下的哪些路径可以接受 Cookie(该 URL 路径
|
||||
- /docs/Web/
|
||||
- /docs/Web/HTTP
|
||||
|
||||
### 5. JavaScript
|
||||
#### 5. JavaScript
|
||||
|
||||
浏览器通过 `document.cookie` 属性可创建新的 Cookie,也可通过该属性访问非 HttpOnly 标记的 Cookie。
|
||||
|
||||
@ -442,7 +400,7 @@ document.cookie = "tasty_cookie=strawberry";
|
||||
console.log(document.cookie);
|
||||
```
|
||||
|
||||
### 6. HttpOnly
|
||||
#### 6. HttpOnly
|
||||
|
||||
标记为 HttpOnly 的 Cookie 不能被 JavaScript 脚本调用。跨站脚本攻击 (XSS) 常常使用 JavaScript 的 `document.cookie` API 窃取用户的 Cookie 信息,因此使用 HttpOnly 标记可以在一定程度上避免 XSS 攻击。
|
||||
|
||||
@ -450,11 +408,11 @@ console.log(document.cookie);
|
||||
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly
|
||||
```
|
||||
|
||||
### 7. Secure
|
||||
#### 7. Secure
|
||||
|
||||
标记为 Secure 的 Cookie 只能通过被 HTTPS 协议加密过的请求发送给服务端。但即便设置了 Secure 标记,敏感信息也不应该通过 Cookie 传输,因为 Cookie 有其固有的不安全性,Secure 标记也无法提供确实的安全保障。
|
||||
|
||||
### 8. Session
|
||||
#### 8. Session
|
||||
|
||||
除了可以将用户信息通过 Cookie 存储在用户浏览器中,也可以利用 Session 存储在服务器端,存储在服务器端的信息更加安全。
|
||||
|
||||
@ -469,29 +427,29 @@ Session 可以存储在服务器上的文件、数据库或者内存中。也可
|
||||
|
||||
应该注意 Session ID 的安全性问题,不能让它被恶意攻击者轻易获取,那么就不能产生一个容易被猜到的 Session ID 值。此外,还需要经常重新生成 Session ID。在对安全性要求极高的场景下,例如转账等操作,除了使用 Session 管理用户状态之外,还需要对用户进行重新验证,比如重新输入密码,或者使用短信验证码等方式。
|
||||
|
||||
### 9. 浏览器禁用 Cookie
|
||||
#### 9. 浏览器禁用 Cookie
|
||||
|
||||
此时无法使用 Cookie 来保存用户信息,只能使用 Session。除此之外,不能再将 Session ID 存放到 Cookie 中,而是使用 URL 重写技术,将 Session ID 作为 URL 的参数进行传递。
|
||||
|
||||
### 10. Cookie 与 Session 选择
|
||||
#### 10. Cookie 与 Session 选择
|
||||
|
||||
- Cookie 只能存储 ASCII 码字符串,而 Session 则可以存储任何类型的数据,因此在考虑数据复杂性时首选 Session;
|
||||
- Cookie 存储在浏览器中,容易被恶意查看。如果非要将一些隐私数据存在 Cookie 中,可以将 Cookie 值进行加密,然后在服务器进行解密;
|
||||
- 对于大型网站,如果用户所有的信息都存储在 Session 中,那么开销是非常大的,因此不建议将所有的用户信息都存储到 Session 中。
|
||||
|
||||
## 缓存
|
||||
### 缓存
|
||||
|
||||
### 1. 优点
|
||||
#### 1. 优点
|
||||
|
||||
- 缓解服务器压力;
|
||||
- 降低客户端获取资源的延迟:缓存通常位于内存中,读取缓存的速度更快。并且缓存服务器在地理位置上也有可能比源服务器来得近,例如浏览器缓存。
|
||||
|
||||
### 2. 实现方法
|
||||
#### 2. 实现方法
|
||||
|
||||
- 让代理服务器进行缓存;
|
||||
- 让客户端浏览器进行缓存。
|
||||
|
||||
### 3. Cache-Control
|
||||
#### 3. Cache-Control
|
||||
|
||||
HTTP/1.1 通过 Cache-Control 首部字段来控制缓存。
|
||||
|
||||
@ -544,7 +502,7 @@ Expires: Wed, 04 Jul 2012 08:26:05 GMT
|
||||
- 在 HTTP/1.1 中,会优先处理 max-age 指令;
|
||||
- 在 HTTP/1.0 中,max-age 指令会被忽略掉。
|
||||
|
||||
### 4. 缓存验证
|
||||
#### 4. 缓存验证
|
||||
|
||||
需要先了解 ETag 首部字段的含义,它是资源的唯一标识。URL 不能唯一表示资源,例如 `http://www.google.com/` 有中文和英文两个资源,只有 ETag 才能对这两个资源进行唯一标识。
|
||||
|
||||
@ -568,11 +526,11 @@ Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT
|
||||
If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT
|
||||
```
|
||||
|
||||
## 内容协商
|
||||
### 内容协商
|
||||
|
||||
通过内容协商返回最合适的内容,例如根据浏览器的默认语言选择返回中文界面还是英文界面。
|
||||
|
||||
### 1. 类型
|
||||
#### 1. 类型
|
||||
|
||||
**1.1 服务端驱动型**
|
||||
|
||||
@ -588,7 +546,7 @@ If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT
|
||||
|
||||
服务器返回 300 Multiple Choices 或者 406 Not Acceptable,客户端从中选出最合适的那个资源。
|
||||
|
||||
### 2. Vary
|
||||
#### 2. Vary
|
||||
|
||||
```html
|
||||
Vary: Accept-Language
|
||||
@ -598,7 +556,7 @@ Vary: Accept-Language
|
||||
|
||||
例如,一个客户端发送了一个包含 Accept-Language 首部字段的请求之后,源服务器返回的响应包含 `Vary: Accept-Language` 内容,缓存服务器对这个响应进行缓存之后,在客户端下一次访问同一个 URL 资源,并且 Accept-Language 与缓存中的对应的值相同时才会返回该缓存。
|
||||
|
||||
## 内容编码
|
||||
### 内容编码
|
||||
|
||||
内容编码将实体主体进行压缩,从而减少传输的数据量。
|
||||
|
||||
@ -606,11 +564,11 @@ Vary: Accept-Language
|
||||
|
||||
浏览器发送 Accept-Encoding 首部,其中包含有它所支持的压缩算法,以及各自的优先级。服务器则从中选择一种,使用该算法对响应的消息主体进行压缩,并且发送 Content-Encoding 首部来告知浏览器它选择了哪一种算法。由于该内容协商过程是基于编码类型来选择资源的展现形式的,响应报文的 Vary 首部字段至少要包含 Content-Encoding。
|
||||
|
||||
## 范围请求
|
||||
### 范围请求
|
||||
|
||||
如果网络出现中断,服务器只发送了一部分数据,范围请求可以使得客户端只请求服务器未发送的那部分数据,从而避免服务器重新发送所有数据。
|
||||
|
||||
### 1. Range
|
||||
#### 1. Range
|
||||
|
||||
在请求报文中添加 Range 首部字段指定请求的范围。
|
||||
|
||||
@ -630,7 +588,7 @@ Content-Length: 1024
|
||||
(binary content)
|
||||
```
|
||||
|
||||
### 2. Accept-Ranges
|
||||
#### 2. Accept-Ranges
|
||||
|
||||
响应首部字段 Accept-Ranges 用于告知客户端是否能处理范围请求,可以处理使用 bytes,否则使用 none。
|
||||
|
||||
@ -638,17 +596,17 @@ Content-Length: 1024
|
||||
Accept-Ranges: bytes
|
||||
```
|
||||
|
||||
### 3. 响应状态码
|
||||
#### 3. 响应状态码
|
||||
|
||||
- 在请求成功的情况下,服务器会返回 206 Partial Content 状态码。
|
||||
- 在请求的范围越界的情况下,服务器会返回 416 Requested Range Not Satisfiable 状态码。
|
||||
- 在不支持范围请求的情况下,服务器会返回 200 OK 状态码。
|
||||
|
||||
## 分块传输编码
|
||||
### 分块传输编码
|
||||
|
||||
Chunked Transfer Encoding,可以把数据分割成多块,让浏览器逐步显示页面。
|
||||
|
||||
## 多部分对象集合
|
||||
### 多部分对象集合
|
||||
|
||||
一份报文主体内可含有多种类型的实体同时发送,每个部分之间用 boundary 字段定义的分隔符进行分隔,每个部分都可以有首部字段。
|
||||
|
||||
@ -669,13 +627,13 @@ Content-Type: text/plain
|
||||
--AaB03x--
|
||||
```
|
||||
|
||||
## 虚拟主机
|
||||
### 虚拟主机
|
||||
|
||||
HTTP/1.1 使用虚拟主机技术,使得一台服务器拥有多个域名,并且在逻辑上可以看成多个服务器。
|
||||
|
||||
## 通信数据转发
|
||||
### 通信数据转发
|
||||
|
||||
### 1. 代理
|
||||
#### 1. 代理
|
||||
|
||||
代理服务器接受客户端的请求,并且转发给其它服务器。
|
||||
|
||||
@ -696,15 +654,15 @@ HTTP/1.1 使用虚拟主机技术,使得一台服务器拥有多个域名,
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/2d09a847-b854-439c-9198-b29c65810944.png" width=""/> </div><br>
|
||||
|
||||
### 2. 网关
|
||||
#### 2. 网关
|
||||
|
||||
与代理服务器不同的是,网关服务器会将 HTTP 转化为其它协议进行通信,从而请求其它非 HTTP 服务器的服务。
|
||||
|
||||
### 3. 隧道
|
||||
#### 3. 隧道
|
||||
|
||||
使用 SSL 等加密手段,在客户端和服务器之间建立一条安全的通信线路。
|
||||
|
||||
# 六、HTTPS
|
||||
## 六、HTTPS
|
||||
|
||||
HTTP 有以下安全性问题:
|
||||
|
||||
@ -718,9 +676,9 @@ HTTPS 并不是新协议,而是让 HTTP 先和 SSL(Secure Sockets Layer)
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/ssl-offloading.jpg" width="700"/> </div><br>
|
||||
|
||||
## 加密
|
||||
### 加密
|
||||
|
||||
### 1. 对称密钥加密
|
||||
#### 1. 对称密钥加密
|
||||
|
||||
对称密钥加密(Symmetric-Key Encryption),加密和解密使用同一密钥。
|
||||
|
||||
@ -729,7 +687,7 @@ HTTPS 并不是新协议,而是让 HTTP 先和 SSL(Secure Sockets Layer)
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/7fffa4b8-b36d-471f-ad0c-a88ee763bb76.png" width="600"/> </div><br>
|
||||
|
||||
### 2.非对称密钥加密
|
||||
#### 2.非对称密钥加密
|
||||
|
||||
非对称密钥加密,又称公开密钥加密(Public-Key Encryption),加密和解密使用不同的密钥。
|
||||
|
||||
@ -742,7 +700,7 @@ HTTPS 并不是新协议,而是让 HTTP 先和 SSL(Secure Sockets Layer)
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/39ccb299-ee99-4dd1-b8b4-2f9ec9495cb4.png" width="600"/> </div><br>
|
||||
|
||||
### 3. HTTPS 采用的加密方式
|
||||
#### 3. HTTPS 采用的加密方式
|
||||
|
||||
上面提到对称密钥加密方式的传输效率更高,但是无法安全地将密钥 Secret Key 传输给通信方。而非对称密钥加密方式可以保证传输的安全性,因此我们可以利用非对称密钥加密方式将 Secret Key 传输给通信方。HTTPS 采用混合的加密机制,正是利用了上面提到的方案:
|
||||
|
||||
@ -751,7 +709,7 @@ HTTPS 并不是新协议,而是让 HTTP 先和 SSL(Secure Sockets Layer)
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/How-HTTPS-Works.png" width="600"/> </div><br>
|
||||
|
||||
## 认证
|
||||
### 认证
|
||||
|
||||
通过使用 **证书** 来对通信方进行认证。
|
||||
|
||||
@ -763,7 +721,7 @@ HTTPS 并不是新协议,而是让 HTTP 先和 SSL(Secure Sockets Layer)
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/2017-06-11-ca.png" width=""/> </div><br>
|
||||
|
||||
## 完整性保护
|
||||
### 完整性保护
|
||||
|
||||
SSL 提供报文摘要功能来进行完整性保护。
|
||||
|
||||
@ -771,14 +729,14 @@ HTTP 也提供了 MD5 报文摘要功能,但不是安全的。例如报文内
|
||||
|
||||
HTTPS 的报文摘要功能之所以安全,是因为它结合了加密和认证这两个操作。试想一下,加密之后的报文,遭到篡改之后,也很难重新计算报文摘要,因为无法轻易获取明文。
|
||||
|
||||
## HTTPS 的缺点
|
||||
### HTTPS 的缺点
|
||||
|
||||
- 因为需要进行加密解密等过程,因此速度会更慢;
|
||||
- 需要支付证书授权的高额费用。
|
||||
|
||||
# 七、HTTP/2.0
|
||||
## 七、HTTP/2.0
|
||||
|
||||
## HTTP/1.x 缺陷
|
||||
### HTTP/1.x 缺陷
|
||||
|
||||
HTTP/1.x 实现简单是以牺牲性能为代价的:
|
||||
|
||||
@ -786,7 +744,7 @@ HTTP/1.x 实现简单是以牺牲性能为代价的:
|
||||
- 不会压缩请求和响应首部,从而导致不必要的网络流量;
|
||||
- 不支持有效的资源优先级,致使底层 TCP 连接的利用率低下。
|
||||
|
||||
## 二进制分帧层
|
||||
### 二进制分帧层
|
||||
|
||||
HTTP/2.0 将报文分成 HEADERS 帧和 DATA 帧,它们都是二进制格式的。
|
||||
|
||||
@ -800,13 +758,13 @@ HTTP/2.0 将报文分成 HEADERS 帧和 DATA 帧,它们都是二进制格式
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/af198da1-2480-4043-b07f-a3b91a88b815.png" width="600"/> </div><br>
|
||||
|
||||
## 服务端推送
|
||||
### 服务端推送
|
||||
|
||||
HTTP/2.0 在客户端请求一个资源时,会把相关的资源一起发送给客户端,客户端就不需要再次发起请求了。例如客户端请求 page.html 页面,服务端就把 script.js 和 style.css 等与之相关的资源一起发给客户端。
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/e3f1657c-80fc-4dfa-9643-bf51abd201c6.png" width="800"/> </div><br>
|
||||
|
||||
## 首部压缩
|
||||
### 首部压缩
|
||||
|
||||
HTTP/1.1 的首部带有大量信息,而且每次都要重复发送。
|
||||
|
||||
@ -816,7 +774,7 @@ HTTP/2.0 要求客户端和服务器同时维护和更新一个包含之前见
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/_u4E0B_u8F7D.png" width="600"/> </div><br>
|
||||
|
||||
# 八、HTTP/1.1 新特性
|
||||
## 八、HTTP/1.1 新特性
|
||||
|
||||
详细内容请见上文
|
||||
|
||||
@ -828,13 +786,13 @@ HTTP/2.0 要求客户端和服务器同时维护和更新一个包含之前见
|
||||
- 支持分块传输编码
|
||||
- 新增缓存处理指令 max-age
|
||||
|
||||
# 九、GET 和 POST 比较
|
||||
## 九、GET 和 POST 比较
|
||||
|
||||
## 作用
|
||||
### 作用
|
||||
|
||||
GET 用于获取资源,而 POST 用于传输实体主体。
|
||||
|
||||
## 参数
|
||||
### 参数
|
||||
|
||||
GET 和 POST 的请求都能使用额外的参数,但是 GET 的参数是以查询字符串出现在 URL 中,而 POST 的参数存储在实体主体中。不能因为 POST 参数存储在实体主体中就认为它的安全性更高,因为照样可以通过一些抓包工具(Fiddler)查看。
|
||||
|
||||
@ -850,7 +808,7 @@ Host: w3schools.com
|
||||
name1=value1&name2=value2
|
||||
```
|
||||
|
||||
## 安全
|
||||
### 安全
|
||||
|
||||
安全的 HTTP 方法不会改变服务器状态,也就是说它只是可读的。
|
||||
|
||||
@ -860,7 +818,7 @@ GET 方法是安全的,而 POST 却不是,因为 POST 的目的是传送实
|
||||
|
||||
不安全的方法除了 POST 之外还有 PUT、DELETE。
|
||||
|
||||
## 幂等性
|
||||
### 幂等性
|
||||
|
||||
幂等的 HTTP 方法,同样的请求被执行一次与连续执行多次的效果是一样的,服务器的状态也是一样的。换句话说就是,幂等方法不应该具有副作用(统计用途除外)。
|
||||
|
||||
@ -893,7 +851,7 @@ DELETE /idX/delete HTTP/1.1 -> Returns 404 as it just got deleted
|
||||
DELETE /idX/delete HTTP/1.1 -> Returns 404
|
||||
```
|
||||
|
||||
## 可缓存
|
||||
### 可缓存
|
||||
|
||||
如果要对响应进行缓存,需要满足以下条件:
|
||||
|
||||
@ -901,16 +859,16 @@ DELETE /idX/delete HTTP/1.1 -> Returns 404
|
||||
- 响应报文的状态码是可缓存的,包括:200, 203, 204, 206, 300, 301, 404, 405, 410, 414, and 501。
|
||||
- 响应报文的 Cache-Control 首部字段没有指定不进行缓存。
|
||||
|
||||
## XMLHttpRequest
|
||||
### XMLHttpRequest
|
||||
|
||||
为了阐述 POST 和 GET 的另一个区别,需要先了解 XMLHttpRequest:
|
||||
|
||||
> XMLHttpRequest 是一个 API,它为客户端提供了在客户端和服务器之间传输数据的功能。它提供了一个通过 URL 来获取数据的简单方式,并且不会使整个页面刷新。这使得网页只更新一部分页面而不会打扰到用户。XMLHttpRequest 在 AJAX 中被大量使用。
|
||||
\> XMLHttpRequest 是一个 API,它为客户端提供了在客户端和服务器之间传输数据的功能。它提供了一个通过 URL 来获取数据的简单方式,并且不会使整个页面刷新。这使得网页只更新一部分页面而不会打扰到用户。XMLHttpRequest 在 AJAX 中被大量使用。
|
||||
|
||||
- 在使用 XMLHttpRequest 的 POST 方法时,浏览器会先发送 Header 再发送 Data。但并不是所有浏览器会这么做,例如火狐就不会。
|
||||
- 而 GET 方法 Header 和 Data 会一起发送。
|
||||
|
||||
# 参考资料
|
||||
## 参考资料
|
||||
|
||||
- 上野宣. 图解 HTTP[M]. 人民邮电出版社, 2014.
|
||||
- [MDN : HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP)
|
||||
@ -939,10 +897,3 @@ DELETE /idX/delete HTTP/1.1 -> Returns 404
|
||||
- [Symmetric vs. Asymmetric Encryption – What are differences?](https://www.ssl2buy.com/wiki/symmetric-vs-asymmetric-encryption-what-are-differences)
|
||||
- [Web 性能优化与 HTTP/2](https://www.kancloud.cn/digest/web-performance-http2)
|
||||
- [HTTP/2 简介](https://developers.google.com/web/fundamentals/performance/http2/?hl=zh-cn)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
118
notes/Java IO.md
118
notes/Java IO.md
@ -1,37 +1,18 @@
|
||||
# Java IO
|
||||
<!-- GFM-TOC -->
|
||||
* [一、概览](#一概览)
|
||||
* [二、磁盘操作](#二磁盘操作)
|
||||
* [三、字节操作](#三字节操作)
|
||||
* [实现文件复制](#实现文件复制)
|
||||
* [装饰者模式](#装饰者模式)
|
||||
* [四、字符操作](#四字符操作)
|
||||
* [编码与解码](#编码与解码)
|
||||
* [String 的编码方式](#string-的编码方式)
|
||||
* [Reader 与 Writer](#reader-与-writer)
|
||||
* [实现逐行输出文本文件的内容](#实现逐行输出文本文件的内容)
|
||||
* [五、对象操作](#五对象操作)
|
||||
* [序列化](#序列化)
|
||||
* [Serializable](#serializable)
|
||||
* [transient](#transient)
|
||||
* [六、网络操作](#六网络操作)
|
||||
* [InetAddress](#inetaddress)
|
||||
* [URL](#url)
|
||||
* [Sockets](#sockets)
|
||||
* [Datagram](#datagram)
|
||||
* [七、NIO](#七nio)
|
||||
* [流与块](#流与块)
|
||||
* [通道与缓冲区](#通道与缓冲区)
|
||||
* [缓冲区状态变量](#缓冲区状态变量)
|
||||
* [文件 NIO 实例](#文件-nio-实例)
|
||||
* [选择器](#选择器)
|
||||
* [套接字 NIO 实例](#套接字-nio-实例)
|
||||
* [内存映射文件](#内存映射文件)
|
||||
* [对比](#对比)
|
||||
* [八、参考资料](#八参考资料)
|
||||
* [Java IO](#java-io)
|
||||
* [一、概览](#一概览)
|
||||
* [二、磁盘操作](#二磁盘操作)
|
||||
* [三、字节操作](#三字节操作)
|
||||
* [四、字符操作](#四字符操作)
|
||||
* [五、对象操作](#五对象操作)
|
||||
* [六、网络操作](#六网络操作)
|
||||
* [七、NIO](#七nio)
|
||||
* [八、参考资料](#八参考资料)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
|
||||
# 一、概览
|
||||
## 一、概览
|
||||
|
||||
Java 的 I/O 大概可以分成以下几类:
|
||||
|
||||
@ -42,7 +23,7 @@ Java 的 I/O 大概可以分成以下几类:
|
||||
- 网络操作:Socket
|
||||
- 新的输入/输出:NIO
|
||||
|
||||
# 二、磁盘操作
|
||||
## 二、磁盘操作
|
||||
|
||||
File 类可以用于表示文件和目录的信息,但是它不表示文件的内容。
|
||||
|
||||
@ -65,9 +46,9 @@ public static void listAllFiles(File dir) {
|
||||
|
||||
从 Java7 开始,可以使用 Paths 和 Files 代替 File。
|
||||
|
||||
# 三、字节操作
|
||||
## 三、字节操作
|
||||
|
||||
## 实现文件复制
|
||||
### 实现文件复制
|
||||
|
||||
```java
|
||||
public static void copyFile(String src, String dist) throws IOException {
|
||||
@ -89,7 +70,7 @@ public static void copyFile(String src, String dist) throws IOException {
|
||||
}
|
||||
```
|
||||
|
||||
## 装饰者模式
|
||||
### 装饰者模式
|
||||
|
||||
Java I/O 使用了装饰者模式来实现。以 InputStream 为例,
|
||||
|
||||
@ -108,9 +89,9 @@ BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStrea
|
||||
|
||||
DataInputStream 装饰者提供了对更多数据类型进行输入的操作,比如 int、double 等基本类型。
|
||||
|
||||
# 四、字符操作
|
||||
## 四、字符操作
|
||||
|
||||
## 编码与解码
|
||||
### 编码与解码
|
||||
|
||||
编码就是把字符转换为字节,而解码是把字节重新组合成字符。
|
||||
|
||||
@ -124,7 +105,7 @@ UTF-16be 中的 be 指的是 Big Endian,也就是大端。相应地也有 UTF-
|
||||
|
||||
Java 的内存编码使用双字节编码 UTF-16be,这不是指 Java 只支持这一种编码方式,而是说 char 这种类型使用 UTF-16be 进行编码。char 类型占 16 位,也就是两个字节,Java 使用这种双字节编码是为了让一个中文或者一个英文都能使用一个 char 来存储。
|
||||
|
||||
## String 的编码方式
|
||||
### String 的编码方式
|
||||
|
||||
String 可以看成一个字符序列,可以指定一个编码方式将它编码为字节序列,也可以指定一个编码方式将一个字节序列解码为 String。
|
||||
|
||||
@ -141,14 +122,14 @@ System.out.println(str2);
|
||||
byte[] bytes = str1.getBytes();
|
||||
```
|
||||
|
||||
## Reader 与 Writer
|
||||
### Reader 与 Writer
|
||||
|
||||
不管是磁盘还是网络传输,最小的存储单元都是字节,而不是字符。但是在程序中操作的通常是字符形式的数据,因此需要提供对字符进行操作的方法。
|
||||
|
||||
- InputStreamReader 实现从字节流解码成字符流;
|
||||
- OutputStreamWriter 实现字符流编码成为字节流。
|
||||
|
||||
## 实现逐行输出文本文件的内容
|
||||
### 实现逐行输出文本文件的内容
|
||||
|
||||
```java
|
||||
public static void readFileContent(String filePath) throws IOException {
|
||||
@ -168,9 +149,9 @@ public static void readFileContent(String filePath) throws IOException {
|
||||
}
|
||||
```
|
||||
|
||||
# 五、对象操作
|
||||
## 五、对象操作
|
||||
|
||||
## 序列化
|
||||
### 序列化
|
||||
|
||||
序列化就是将一个对象转换成字节序列,方便存储和传输。
|
||||
|
||||
@ -179,7 +160,7 @@ public static void readFileContent(String filePath) throws IOException {
|
||||
|
||||
不会对静态变量进行序列化,因为序列化只是保存对象的状态,静态变量属于类的状态。
|
||||
|
||||
## Serializable
|
||||
### Serializable
|
||||
|
||||
序列化的类需要实现 Serializable 接口,它只是一个标准,没有任何方法需要实现,但是如果不去实现它的话而进行序列化,会抛出异常。
|
||||
|
||||
@ -216,7 +197,7 @@ private static class A implements Serializable {
|
||||
}
|
||||
```
|
||||
|
||||
## transient
|
||||
### transient
|
||||
|
||||
transient 关键字可以使一些属性不会被序列化。
|
||||
|
||||
@ -226,7 +207,7 @@ ArrayList 中存储数据的数组 elementData 是用 transient 修饰的,因
|
||||
private transient Object[] elementData;
|
||||
```
|
||||
|
||||
# 六、网络操作
|
||||
## 六、网络操作
|
||||
|
||||
Java 中的网络支持:
|
||||
|
||||
@ -235,7 +216,7 @@ Java 中的网络支持:
|
||||
- Sockets:使用 TCP 协议实现网络通信;
|
||||
- Datagram:使用 UDP 协议实现网络通信。
|
||||
|
||||
## InetAddress
|
||||
### InetAddress
|
||||
|
||||
没有公有的构造函数,只能通过静态方法来创建实例。
|
||||
|
||||
@ -244,7 +225,7 @@ InetAddress.getByName(String host);
|
||||
InetAddress.getByAddress(byte[] address);
|
||||
```
|
||||
|
||||
## URL
|
||||
### URL
|
||||
|
||||
可以直接从 URL 中读取字节流数据。
|
||||
|
||||
@ -271,7 +252,7 @@ public static void main(String[] args) throws IOException {
|
||||
}
|
||||
```
|
||||
|
||||
## Sockets
|
||||
### Sockets
|
||||
|
||||
- ServerSocket:服务器端类
|
||||
- Socket:客户端类
|
||||
@ -279,16 +260,16 @@ public static void main(String[] args) throws IOException {
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/1e6affc4-18e5-4596-96ef-fb84c63bf88a.png" width="550px"> </div><br>
|
||||
|
||||
## Datagram
|
||||
### Datagram
|
||||
|
||||
- DatagramSocket:通信类
|
||||
- DatagramPacket:数据包类
|
||||
|
||||
# 七、NIO
|
||||
## 七、NIO
|
||||
|
||||
新的输入/输出 (NIO) 库是在 JDK 1.4 中引入的,弥补了原来的 I/O 的不足,提供了高速的、面向块的 I/O。
|
||||
|
||||
## 流与块
|
||||
### 流与块
|
||||
|
||||
I/O 与 NIO 最重要的区别是数据打包和传输的方式,I/O 以流的方式处理数据,而 NIO 以块的方式处理数据。
|
||||
|
||||
@ -298,9 +279,9 @@ I/O 与 NIO 最重要的区别是数据打包和传输的方式,I/O 以流的
|
||||
|
||||
I/O 包和 NIO 已经很好地集成了,java.io.\* 已经以 NIO 为基础重新实现了,所以现在它可以利用 NIO 的一些特性。例如,java.io.\* 包中的一些类包含以块的形式读写数据的方法,这使得即使在面向流的系统中,处理速度也会更快。
|
||||
|
||||
## 通道与缓冲区
|
||||
### 通道与缓冲区
|
||||
|
||||
### 1. 通道
|
||||
#### 1. 通道
|
||||
|
||||
通道 Channel 是对原 I/O 包中的流的模拟,可以通过它读取和写入数据。
|
||||
|
||||
@ -313,7 +294,7 @@ I/O 包和 NIO 已经很好地集成了,java.io.\* 已经以 NIO 为基础重
|
||||
- SocketChannel:通过 TCP 读写网络中数据;
|
||||
- ServerSocketChannel:可以监听新进来的 TCP 连接,对每一个新进来的连接都会创建一个 SocketChannel。
|
||||
|
||||
### 2. 缓冲区
|
||||
#### 2. 缓冲区
|
||||
|
||||
发送给一个通道的所有数据都必须首先放到缓冲区中,同样地,从通道中读取的任何数据都要先读到缓冲区中。也就是说,不会直接对通道进行读写数据,而是要先经过缓冲区。
|
||||
|
||||
@ -329,7 +310,7 @@ I/O 包和 NIO 已经很好地集成了,java.io.\* 已经以 NIO 为基础重
|
||||
- FloatBuffer
|
||||
- DoubleBuffer
|
||||
|
||||
## 缓冲区状态变量
|
||||
### 缓冲区状态变量
|
||||
|
||||
- capacity:最大容量;
|
||||
- position:当前已经读写的字节数;
|
||||
@ -357,7 +338,7 @@ I/O 包和 NIO 已经很好地集成了,java.io.\* 已经以 NIO 为基础重
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/67bf5487-c45d-49b6-b9c0-a058d8c68902.png"/> </div><br>
|
||||
|
||||
## 文件 NIO 实例
|
||||
### 文件 NIO 实例
|
||||
|
||||
以下展示了使用 NIO 快速复制文件的实例:
|
||||
|
||||
@ -401,7 +382,7 @@ public static void fastCopy(String src, String dist) throws IOException {
|
||||
}
|
||||
```
|
||||
|
||||
## 选择器
|
||||
### 选择器
|
||||
|
||||
NIO 常常被叫做非阻塞 IO,主要是因为 NIO 在网络通信中的非阻塞特性被广泛使用。
|
||||
|
||||
@ -415,13 +396,13 @@ NIO 实现了 IO 多路复用中的 Reactor 模型,一个线程 Thread 使用
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/093f9e57-429c-413a-83ee-c689ba596cef.png" width="350px"> </div><br>
|
||||
|
||||
### 1. 创建选择器
|
||||
#### 1. 创建选择器
|
||||
|
||||
```java
|
||||
Selector selector = Selector.open();
|
||||
```
|
||||
|
||||
### 2. 将通道注册到选择器上
|
||||
#### 2. 将通道注册到选择器上
|
||||
|
||||
```java
|
||||
ServerSocketChannel ssChannel = ServerSocketChannel.open();
|
||||
@ -453,7 +434,7 @@ public static final int OP_ACCEPT = 1 << 4;
|
||||
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
|
||||
```
|
||||
|
||||
### 3. 监听事件
|
||||
#### 3. 监听事件
|
||||
|
||||
```java
|
||||
int num = selector.select();
|
||||
@ -461,7 +442,7 @@ int num = selector.select();
|
||||
|
||||
使用 select() 来监听到达的事件,它会一直阻塞直到有至少一个事件到达。
|
||||
|
||||
### 4. 获取到达的事件
|
||||
#### 4. 获取到达的事件
|
||||
|
||||
```java
|
||||
Set<SelectionKey> keys = selector.selectedKeys();
|
||||
@ -477,7 +458,7 @@ while (keyIterator.hasNext()) {
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 事件循环
|
||||
#### 5. 事件循环
|
||||
|
||||
因为一次 select() 调用不能处理完所有的事件,并且服务器端有可能需要一直监听事件,因此服务器端处理事件的代码一般会放在一个死循环内。
|
||||
|
||||
@ -498,7 +479,7 @@ while (true) {
|
||||
}
|
||||
```
|
||||
|
||||
## 套接字 NIO 实例
|
||||
### 套接字 NIO 实例
|
||||
|
||||
```java
|
||||
public class NIOServer {
|
||||
@ -587,7 +568,7 @@ public class NIOClient {
|
||||
}
|
||||
```
|
||||
|
||||
## 内存映射文件
|
||||
### 内存映射文件
|
||||
|
||||
内存映射文件 I/O 是一种读和写文件数据的方法,它可以比常规的基于流或者基于通道的 I/O 快得多。
|
||||
|
||||
@ -599,14 +580,14 @@ public class NIOClient {
|
||||
MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, 0, 1024);
|
||||
```
|
||||
|
||||
## 对比
|
||||
### 对比
|
||||
|
||||
NIO 与普通 I/O 的区别主要有以下两点:
|
||||
|
||||
- NIO 是非阻塞的;
|
||||
- NIO 面向块,I/O 面向流。
|
||||
|
||||
# 八、参考资料
|
||||
## 八、参考资料
|
||||
|
||||
- Eckel B, 埃克尔, 昊鹏, 等. Java 编程思想 [M]. 机械工业出版社, 2002.
|
||||
- [IBM: NIO 入门](https://www.ibm.com/developerworks/cn/education/java/j-nio/j-nio.html)
|
||||
@ -618,10 +599,3 @@ NIO 与普通 I/O 的区别主要有以下两点:
|
||||
- [NIO 与传统 IO 的区别](http://blog.csdn.net/shimiso/article/details/24990499)
|
||||
- [Decorator Design Pattern](http://stg-tud.github.io/sedc/Lecture/ws13-14/5.3-Decorator.html#mode=document)
|
||||
- [Socket Multicast](http://labojava.blogspot.com/2012/12/socket-multicast.html)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
139
notes/Java 基础.md
139
notes/Java 基础.md
@ -1,48 +1,24 @@
|
||||
# Java 基础
|
||||
<!-- GFM-TOC -->
|
||||
* [一、数据类型](#一数据类型)
|
||||
* [基本类型](#基本类型)
|
||||
* [包装类型](#包装类型)
|
||||
* [缓存池](#缓存池)
|
||||
* [二、String](#二string)
|
||||
* [概览](#概览)
|
||||
* [不可变的好处](#不可变的好处)
|
||||
* [String, StringBuffer and StringBuilder ](#string-stringbuffer-and-stringbuilder )
|
||||
* [String Pool](#string-pool)
|
||||
* [new String("abc")](#new-stringabc)
|
||||
* [三、运算](#三运算)
|
||||
* [参数传递](#参数传递)
|
||||
* [float 与 double](#float-与-double)
|
||||
* [隐式类型转换](#隐式类型转换)
|
||||
* [switch](#switch)
|
||||
* [四、关键字](#四关键字)
|
||||
* [final](#final)
|
||||
* [static](#static)
|
||||
* [五、Object 通用方法](#五object-通用方法)
|
||||
* [概览](#概览)
|
||||
* [equals()](#equals)
|
||||
* [hashCode()](#hashcode)
|
||||
* [toString()](#tostring)
|
||||
* [clone()](#clone)
|
||||
* [六、继承](#六继承)
|
||||
* [访问权限](#访问权限)
|
||||
* [抽象类与接口](#抽象类与接口)
|
||||
* [super](#super)
|
||||
* [重写与重载](#重写与重载)
|
||||
* [七、反射](#七反射)
|
||||
* [八、异常](#八异常)
|
||||
* [九、泛型](#九泛型)
|
||||
* [十、注解](#十注解)
|
||||
* [十一、特性](#十一特性)
|
||||
* [Java 各版本的新特性](#java-各版本的新特性)
|
||||
* [Java 与 C++ 的区别](#java-与-c-的区别)
|
||||
* [JRE or JDK](#jre-or-jdk)
|
||||
* [参考资料](#参考资料)
|
||||
* [Java 基础](#java-基础)
|
||||
* [一、数据类型](#一数据类型)
|
||||
* [二、String](#二string)
|
||||
* [三、运算](#三运算)
|
||||
* [四、关键字](#四关键字)
|
||||
* [五、Object 通用方法](#五object-通用方法)
|
||||
* [六、继承](#六继承)
|
||||
* [七、反射](#七反射)
|
||||
* [八、异常](#八异常)
|
||||
* [九、泛型](#九泛型)
|
||||
* [十、注解](#十注解)
|
||||
* [十一、特性](#十一特性)
|
||||
* [参考资料](#参考资料)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
|
||||
# 一、数据类型
|
||||
## 一、数据类型
|
||||
|
||||
## 基本类型
|
||||
### 基本类型
|
||||
|
||||
- byte/8
|
||||
- char/16
|
||||
@ -58,7 +34,7 @@ boolean 只有两个值:true、false,可以使用 1 bit 来存储,但是
|
||||
- [Primitive Data Types](https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html)
|
||||
- [The Java® Virtual Machine Specification](https://docs.oracle.com/javase/specs/jvms/se8/jvms8.pdf)
|
||||
|
||||
## 包装类型
|
||||
### 包装类型
|
||||
|
||||
基本类型都有对应的包装类型,基本类型与其对应的包装类型之间的赋值使用自动装箱与拆箱完成。
|
||||
|
||||
@ -69,7 +45,7 @@ int y = x; // 拆箱 调用了 X.intValue()
|
||||
|
||||
- [Autoboxing and Unboxing](https://docs.oracle.com/javase/tutorial/java/data/autoboxing.html)
|
||||
|
||||
## 缓存池
|
||||
### 缓存池
|
||||
|
||||
new Integer(123) 与 Integer.valueOf(123) 的区别在于:
|
||||
|
||||
@ -152,9 +128,9 @@ System.out.println(m == n); // true
|
||||
[StackOverflow : Differences between new Integer(123), Integer.valueOf(123) and just 123
|
||||
](https://stackoverflow.com/questions/9030817/differences-between-new-integer123-integer-valueof123-and-just-123)
|
||||
|
||||
# 二、String
|
||||
## 二、String
|
||||
|
||||
## 概览
|
||||
### 概览
|
||||
|
||||
String 被声明为 final,因此它不可被继承。(Integer 等包装类也不能被继承)
|
||||
|
||||
@ -183,7 +159,7 @@ public final class String
|
||||
|
||||
value 数组被声明为 final,这意味着 value 数组初始化之后就不能再引用其它数组。并且 String 内部没有改变 value 数组的方法,因此可以保证 String 不可变。
|
||||
|
||||
## 不可变的好处
|
||||
### 不可变的好处
|
||||
|
||||
**1. 可以缓存 hash 值**
|
||||
|
||||
@ -205,7 +181,7 @@ String 不可变性天生具备线程安全,可以在多个线程中安全地
|
||||
|
||||
[Program Creek : Why String is immutable in Java?](https://www.programcreek.com/2013/04/why-string-is-immutable-in-java/)
|
||||
|
||||
## String, StringBuffer and StringBuilder
|
||||
### String, StringBuffer and StringBuilder
|
||||
|
||||
**1. 可变性**
|
||||
|
||||
@ -220,7 +196,7 @@ String 不可变性天生具备线程安全,可以在多个线程中安全地
|
||||
|
||||
[StackOverflow : String, StringBuffer, and StringBuilder](https://stackoverflow.com/questions/2971315/string-stringbuffer-and-stringbuilder)
|
||||
|
||||
## String Pool
|
||||
### String Pool
|
||||
|
||||
字符串常量池(String Pool)保存着所有字符串字面量(literal strings),这些字面量在编译时期就确定。不仅如此,还可以使用 String 的 intern() 方法在运行过程将字符串添加到 String Pool 中。
|
||||
|
||||
@ -250,7 +226,7 @@ System.out.println(s5 == s6); // true
|
||||
- [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")
|
||||
### new String("abc")
|
||||
|
||||
使用这种方式一共会创建两个字符串对象(前提是 String Pool 中还没有 "abc" 字符串对象)。
|
||||
|
||||
@ -304,9 +280,9 @@ public String(String original) {
|
||||
}
|
||||
```
|
||||
|
||||
# 三、运算
|
||||
## 三、运算
|
||||
|
||||
## 参数传递
|
||||
### 参数传递
|
||||
|
||||
Java 的参数是以值传递的形式传入方法中,而不是引用传递。
|
||||
|
||||
@ -374,7 +350,7 @@ public class PassByValueExample {
|
||||
|
||||
[StackOverflow: Is Java “pass-by-reference” or “pass-by-value”?](https://stackoverflow.com/questions/40480/is-java-pass-by-reference-or-pass-by-value)
|
||||
|
||||
## float 与 double
|
||||
### float 与 double
|
||||
|
||||
Java 不能隐式执行向下转型,因为这会使得精度降低。
|
||||
|
||||
@ -390,7 +366,7 @@ Java 不能隐式执行向下转型,因为这会使得精度降低。
|
||||
float f = 1.1f;
|
||||
```
|
||||
|
||||
## 隐式类型转换
|
||||
### 隐式类型转换
|
||||
|
||||
因为字面量 1 是 int 类型,它比 short 类型精度要高,因此不能隐式地将 int 类型向下转型为 short 类型。
|
||||
|
||||
@ -414,7 +390,7 @@ s1 = (short) (s1 + 1);
|
||||
|
||||
[StackOverflow : Why don't Java's +=, -=, *=, /= compound assignment operators require casting?](https://stackoverflow.com/questions/8710619/why-dont-javas-compound-assignment-operators-require-casting)
|
||||
|
||||
## switch
|
||||
### switch
|
||||
|
||||
从 Java 7 开始,可以在 switch 条件判断语句中使用 String 对象。
|
||||
|
||||
@ -447,9 +423,9 @@ switch 不支持 long,是因为 switch 的设计初衷是对那些只有少数
|
||||
[StackOverflow : Why can't your switch statement data type be long, Java?](https://stackoverflow.com/questions/2676210/why-cant-your-switch-statement-data-type-be-long-java)
|
||||
|
||||
|
||||
# 四、关键字
|
||||
## 四、关键字
|
||||
|
||||
## final
|
||||
### final
|
||||
|
||||
**1. 数据**
|
||||
|
||||
@ -475,7 +451,7 @@ private 方法隐式地被指定为 final,如果在子类中定义的方法和
|
||||
|
||||
声明类不允许被继承。
|
||||
|
||||
## static
|
||||
### static
|
||||
|
||||
**1. 静态变量**
|
||||
|
||||
@ -619,9 +595,9 @@ public InitialOrderTest() {
|
||||
- 子类(实例变量、普通语句块)
|
||||
- 子类(构造函数)
|
||||
|
||||
# 五、Object 通用方法
|
||||
## 五、Object 通用方法
|
||||
|
||||
## 概览
|
||||
### 概览
|
||||
|
||||
```java
|
||||
|
||||
@ -648,7 +624,7 @@ public final void wait(long timeout, int nanos) throws InterruptedException
|
||||
public final void wait() throws InterruptedException
|
||||
```
|
||||
|
||||
## equals()
|
||||
### equals()
|
||||
|
||||
**1. 等价关系**
|
||||
|
||||
@ -735,7 +711,7 @@ public class EqualExample {
|
||||
}
|
||||
```
|
||||
|
||||
## hashCode()
|
||||
### hashCode()
|
||||
|
||||
hashCode() 返回哈希值,而 equals() 是用来判断两个对象是否等价。等价的两个对象散列值一定相同,但是散列值相同的两个对象不一定等价,这是因为计算哈希值具有随机性,两个值不同的对象可能计算出相同的哈希值。
|
||||
|
||||
@ -757,7 +733,7 @@ System.out.println(set.size()); // 2
|
||||
|
||||
理想的哈希函数应当具有均匀性,即不相等的对象应当均匀分布到所有可能的哈希值上。这就要求了哈希函数要把所有域的值都考虑进来。可以将每个域都当成 R 进制的某一位,然后组成一个 R 进制的整数。
|
||||
|
||||
R 一般取 31,因为它是一个奇素数,如果是偶数的话,当出现乘法溢出,信息就会丢失,因为与 2 相乘相当于向左移一位,最左边的位丢失。并且一个数与 31 相乘可以转换成移位和减法:`31*x == (x<<5)-x`,编译器会自动进行这个优化。
|
||||
R 一般取 31,因为它是一个奇素数,如果是偶数的话,当出现乘法溢出,信息就会丢失,因为与 2 相乘相当于向左移一位,最左边的位丢失。并且一个数与 31 相乘可以转换成移位和减法:`31*x == (x\<\<5)-x`,编译器会自动进行这个优化。
|
||||
|
||||
```java
|
||||
@Override
|
||||
@ -770,7 +746,7 @@ public int hashCode() {
|
||||
}
|
||||
```
|
||||
|
||||
## toString()
|
||||
### toString()
|
||||
|
||||
默认返回 ToStringExample@4554617c 这种形式,其中 @ 后面的数值为散列码的无符号十六进制表示。
|
||||
|
||||
@ -794,7 +770,7 @@ System.out.println(example.toString());
|
||||
ToStringExample@4554617c
|
||||
```
|
||||
|
||||
## clone()
|
||||
### clone()
|
||||
|
||||
**1. cloneable**
|
||||
|
||||
@ -986,9 +962,9 @@ e1.set(2, 222);
|
||||
System.out.println(e2.get(2)); // 2
|
||||
```
|
||||
|
||||
# 六、继承
|
||||
## 六、继承
|
||||
|
||||
## 访问权限
|
||||
### 访问权限
|
||||
|
||||
Java 中有三个访问权限修饰符:private、protected 以及 public,如果不加访问修饰符,表示包级可见。
|
||||
|
||||
@ -1049,7 +1025,7 @@ public class AccessWithInnerClassExample {
|
||||
}
|
||||
```
|
||||
|
||||
## 抽象类与接口
|
||||
### 抽象类与接口
|
||||
|
||||
**1. 抽象类**
|
||||
|
||||
@ -1158,7 +1134,7 @@ System.out.println(InterfaceExample.x);
|
||||
- [Java 9 Private Methods in Interfaces](https://www.journaldev.com/12850/java-9-private-methods-interfaces)
|
||||
|
||||
|
||||
## super
|
||||
### super
|
||||
|
||||
- 访问父类的构造函数:可以使用 super() 函数访问父类的构造函数,从而委托父类完成一些初始化的工作。应该注意到,子类一定会调用父类的构造函数来完成初始化工作,一般是调用父类的默认构造函数,如果子类需要调用父类其它构造函数,那么就可以使用 super() 函数。
|
||||
- 访问父类的成员:如果子类重写了父类的某个方法,可以通过使用 super 关键字来引用父类的方法实现。
|
||||
@ -1210,7 +1186,7 @@ SuperExtendExample.func()
|
||||
|
||||
[Using the Keyword super](https://docs.oracle.com/javase/tutorial/java/IandI/super.html)
|
||||
|
||||
## 重写与重载
|
||||
### 重写与重载
|
||||
|
||||
**1. 重写(Override)**
|
||||
|
||||
@ -1227,7 +1203,7 @@ SuperExtendExample.func()
|
||||
下面的示例中,SubClass 为 SuperClass 的子类,SubClass 重写了 SuperClass 的 func() 方法。其中:
|
||||
|
||||
- 子类方法访问权限为 public,大于父类的 protected。
|
||||
- 子类的返回类型为 ArrayList<Integer>,是父类返回类型 List<Integer> 的子类。
|
||||
- 子类的返回类型为 ArrayList\<Integer\>,是父类返回类型 List\<Integer\> 的子类。
|
||||
- 子类抛出的异常类型为 Exception,是父类抛出异常 Throwable 的子类。
|
||||
- 子类重写方法使用 @Override 注解,从而让编译器自动检查是否满足限制条件。
|
||||
|
||||
@ -1342,7 +1318,7 @@ public static void main(String[] args) {
|
||||
}
|
||||
```
|
||||
|
||||
# 七、反射
|
||||
## 七、反射
|
||||
|
||||
每个类都有一个 **Class** 对象,包含了与类有关的信息。当编译一个新类时,会产生一个同名的 .class 文件,该文件内容保存着 Class 对象。
|
||||
|
||||
@ -1376,7 +1352,7 @@ Class 和 java.lang.reflect 一起对反射提供了支持,java.lang.reflect
|
||||
- [Trail: The Reflection API](https://docs.oracle.com/javase/tutorial/reflect/index.html)
|
||||
- [深入解析 Java 反射(1)- 基础](http://www.sczyh30.com/posts/Java/java-reflection-1/)
|
||||
|
||||
# 八、异常
|
||||
## 八、异常
|
||||
|
||||
Throwable 可以用来表示任何可以作为异常抛出的类,分为两种: **Error** 和 **Exception**。其中 Error 用来表示 JVM 无法处理的错误,Exception 分为两种:
|
||||
|
||||
@ -1388,7 +1364,7 @@ Throwable 可以用来表示任何可以作为异常抛出的类,分为两种
|
||||
- [Java 入门之异常处理](https://www.cnblogs.com/Blue-Keroro/p/8875898.html)
|
||||
- [Java Exception Interview Questions and Answers](https://www.journaldev.com/2167/java-exception-interview-questions-and-answersl)
|
||||
|
||||
# 九、泛型
|
||||
## 九、泛型
|
||||
|
||||
```java
|
||||
public class Box<T> {
|
||||
@ -1402,15 +1378,15 @@ public class Box<T> {
|
||||
- [Java 泛型详解](http://www.importnew.com/24029.html)
|
||||
- [10 道 Java 泛型面试题](https://cloud.tencent.com/developer/article/1033693)
|
||||
|
||||
# 十、注解
|
||||
## 十、注解
|
||||
|
||||
Java 注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。
|
||||
|
||||
[注解 Annotation 实现原理与自定义注解例子](https://www.cnblogs.com/acm-bingzi/p/javaAnnotation.html)
|
||||
|
||||
# 十一、特性
|
||||
## 十一、特性
|
||||
|
||||
## Java 各版本的新特性
|
||||
### Java 各版本的新特性
|
||||
|
||||
**New highlights in Java SE 8**
|
||||
|
||||
@ -1438,7 +1414,7 @@ Java 注解是附加在代码中的一些元信息,用于一些工具在编译
|
||||
- [Difference between Java 1.8 and Java 1.7?](http://www.selfgrowth.com/articles/difference-between-java-18-and-java-17)
|
||||
- [Java 8 特性](http://www.importnew.com/19345.html)
|
||||
|
||||
## Java 与 C++ 的区别
|
||||
### Java 与 C++ 的区别
|
||||
|
||||
- Java 是纯粹的面向对象语言,所有的对象都继承自 java.lang.Object,C++ 为了兼容 C 即支持面向对象也支持面向过程。
|
||||
- Java 通过虚拟机从而实现跨平台特性,但是 C++ 依赖于特定的平台。
|
||||
@ -1450,19 +1426,12 @@ Java 注解是附加在代码中的一些元信息,用于一些工具在编译
|
||||
|
||||
[What are the main differences between Java and C++?](http://cs-fundamentals.com/tech-interview/java/differences-between-java-and-cpp.php)
|
||||
|
||||
## JRE or JDK
|
||||
### JRE or JDK
|
||||
|
||||
- JRE:Java Runtime Environment,Java 运行环境的简称,为 Java 的运行提供了所需的环境。它是一个 JVM 程序,主要包括了 JVM 的标准实现和一些 Java 基本类库。
|
||||
- JDK:Java Development Kit,Java 开发工具包,提供了 Java 的开发及运行环境。JDK 是 Java 开发的核心,集成了 JRE 以及一些其它的工具,比如编译 Java 源码的编译器 javac 等。
|
||||
|
||||
# 参考资料
|
||||
## 参考资料
|
||||
|
||||
- Eckel B. Java 编程思想[M]. 机械工业出版社, 2002.
|
||||
- Bloch J. Effective java[M]. Addison-Wesley Professional, 2017.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
141
notes/Java 容器.md
141
notes/Java 容器.md
@ -1,32 +1,22 @@
|
||||
# Java 容器
|
||||
<!-- GFM-TOC -->
|
||||
* [一、概览](#一概览)
|
||||
* [Collection](#collection)
|
||||
* [Map](#map)
|
||||
* [二、容器中的设计模式](#二容器中的设计模式)
|
||||
* [迭代器模式](#迭代器模式)
|
||||
* [适配器模式](#适配器模式)
|
||||
* [三、源码分析](#三源码分析)
|
||||
* [ArrayList](#arraylist)
|
||||
* [Vector](#vector)
|
||||
* [CopyOnWriteArrayList](#copyonwritearraylist)
|
||||
* [LinkedList](#linkedlist)
|
||||
* [HashMap](#hashmap)
|
||||
* [ConcurrentHashMap](#concurrenthashmap)
|
||||
* [LinkedHashMap](#linkedhashmap)
|
||||
* [WeakHashMap](#weakhashmap)
|
||||
* [参考资料](#参考资料)
|
||||
* [Java 容器](#java-容器)
|
||||
* [一、概览](#一概览)
|
||||
* [二、容器中的设计模式](#二容器中的设计模式)
|
||||
* [三、源码分析](#三源码分析)
|
||||
* [参考资料](#参考资料)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
|
||||
# 一、概览
|
||||
## 一、概览
|
||||
|
||||
容器主要包括 Collection 和 Map 两种,Collection 存储着对象的集合,而 Map 存储着键值对(两个对象)的映射表。
|
||||
|
||||
## Collection
|
||||
### Collection
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191208220948084.png"/> </div><br>
|
||||
|
||||
### 1. Set
|
||||
#### 1. Set
|
||||
|
||||
- TreeSet:基于红黑树实现,支持有序性操作,例如根据一个范围查找元素的操作。但是查找效率不如 HashSet,HashSet 查找的时间复杂度为 O(1),TreeSet 则为 O(logN)。
|
||||
|
||||
@ -34,7 +24,7 @@
|
||||
|
||||
- LinkedHashSet:具有 HashSet 的查找效率,并且内部使用双向链表维护元素的插入顺序。
|
||||
|
||||
### 2. List
|
||||
#### 2. List
|
||||
|
||||
- ArrayList:基于动态数组实现,支持随机访问。
|
||||
|
||||
@ -42,13 +32,13 @@
|
||||
|
||||
- LinkedList:基于双向链表实现,只能顺序访问,但是可以快速地在链表中间插入和删除元素。不仅如此,LinkedList 还可以用作栈、队列和双向队列。
|
||||
|
||||
### 3. Queue
|
||||
#### 3. Queue
|
||||
|
||||
- LinkedList:可以用它来实现双向队列。
|
||||
|
||||
- PriorityQueue:基于堆结构实现,可以用它来实现优先队列。
|
||||
|
||||
## Map
|
||||
### Map
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20201101234335837.png"/> </div><br>
|
||||
|
||||
@ -61,9 +51,9 @@
|
||||
- LinkedHashMap:使用双向链表来维护元素的顺序,顺序为插入顺序或者最近最少使用(LRU)顺序。
|
||||
|
||||
|
||||
# 二、容器中的设计模式
|
||||
## 二、容器中的设计模式
|
||||
|
||||
## 迭代器模式
|
||||
### 迭代器模式
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191208225301973.png"/> </div><br>
|
||||
|
||||
@ -80,7 +70,7 @@ for (String item : list) {
|
||||
}
|
||||
```
|
||||
|
||||
## 适配器模式
|
||||
### 适配器模式
|
||||
|
||||
java.util.Arrays#asList() 可以把数组类型转换为 List 类型。
|
||||
|
||||
@ -102,16 +92,16 @@ List list = Arrays.asList(arr);
|
||||
List list = Arrays.asList(1, 2, 3);
|
||||
```
|
||||
|
||||
# 三、源码分析
|
||||
## 三、源码分析
|
||||
|
||||
如果没有特别说明,以下源码分析基于 JDK 1.8。
|
||||
|
||||
在 IDEA 中 double shift 调出 Search EveryWhere,查找源码文件,找到之后就可以阅读源码。
|
||||
|
||||
## ArrayList
|
||||
### ArrayList
|
||||
|
||||
|
||||
### 1. 概览
|
||||
#### 1. 概览
|
||||
|
||||
因为 ArrayList 是基于数组实现的,所以支持快速随机访问。RandomAccess 接口标识着该类支持快速随机访问。
|
||||
|
||||
@ -128,9 +118,9 @@ private static final int DEFAULT_CAPACITY = 10;
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191208232221265.png"/> </div><br>
|
||||
|
||||
### 2. 扩容
|
||||
#### 2. 扩容
|
||||
|
||||
添加元素时使用 ensureCapacityInternal() 方法来保证容量足够,如果不够时,需要使用 grow() 方法进行扩容,新容量的大小为 `oldCapacity + (oldCapacity >> 1)`,也就是旧容量的 1.5 倍。
|
||||
添加元素时使用 ensureCapacityInternal() 方法来保证容量足够,如果不够时,需要使用 grow() 方法进行扩容,新容量的大小为 `oldCapacity + (oldCapacity \>\> 1)`,也就是旧容量的 1.5 倍。
|
||||
|
||||
扩容操作需要调用 `Arrays.copyOf()` 把原数组整个复制到新数组中,这个操作代价很高,因此最好在创建 ArrayList 对象时就指定大概的容量大小,减少扩容操作的次数。
|
||||
|
||||
@ -168,7 +158,7 @@ private void grow(int minCapacity) {
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 删除元素
|
||||
#### 3. 删除元素
|
||||
|
||||
需要调用 System.arraycopy() 将 index+1 后面的元素都复制到 index 位置上,该操作的时间复杂度为 O(N),可以看到 ArrayList 删除元素的代价是非常高的。
|
||||
|
||||
@ -185,7 +175,7 @@ public E remove(int index) {
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 序列化
|
||||
#### 4. 序列化
|
||||
|
||||
ArrayList 基于数组实现,并且具有动态扩容特性,因此保存元素的数组不一定都会被使用,那么就没必要全部进行序列化。
|
||||
|
||||
@ -250,16 +240,16 @@ ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
|
||||
oos.writeObject(list);
|
||||
```
|
||||
|
||||
### 5. Fail-Fast
|
||||
#### 5. Fail-Fast
|
||||
|
||||
modCount 用来记录 ArrayList 结构发生变化的次数。结构发生变化是指添加或者删除至少一个元素的所有操作,或者是调整内部数组的大小,仅仅只是设置元素的值不算结构发生变化。
|
||||
|
||||
在进行序列化或者迭代等操作时,需要比较操作前后 modCount 是否改变,如果改变了需要抛出 ConcurrentModificationException。代码参考上节序列化中的 writeObject() 方法。
|
||||
|
||||
|
||||
## Vector
|
||||
### Vector
|
||||
|
||||
### 1. 同步
|
||||
#### 1. 同步
|
||||
|
||||
它的实现与 ArrayList 类似,但是使用了 synchronized 进行同步。
|
||||
|
||||
@ -279,7 +269,7 @@ public synchronized E get(int index) {
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 扩容
|
||||
#### 2. 扩容
|
||||
|
||||
Vector 的构造函数可以传入 capacityIncrement 参数,它的作用是在扩容时使容量 capacity 增长 capacityIncrement。如果这个参数的值小于等于 0,扩容时每次都令 capacity 为原来的两倍。
|
||||
|
||||
@ -320,12 +310,12 @@ public Vector() {
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 与 ArrayList 的比较
|
||||
#### 3. 与 ArrayList 的比较
|
||||
|
||||
- Vector 是同步的,因此开销就比 ArrayList 要大,访问速度更慢。最好使用 ArrayList 而不是 Vector,因为同步操作完全可以由程序员自己来控制;
|
||||
- Vector 每次扩容请求其大小的 2 倍(也可以通过构造函数设置增长的容量),而 ArrayList 是 1.5 倍。
|
||||
|
||||
### 4. 替代方案
|
||||
#### 4. 替代方案
|
||||
|
||||
可以使用 `Collections.synchronizedList();` 得到一个线程安全的 ArrayList。
|
||||
|
||||
@ -340,9 +330,9 @@ List<String> synList = Collections.synchronizedList(list);
|
||||
List<String> list = new CopyOnWriteArrayList<>();
|
||||
```
|
||||
|
||||
## CopyOnWriteArrayList
|
||||
### CopyOnWriteArrayList
|
||||
|
||||
### 1. 读写分离
|
||||
#### 1. 读写分离
|
||||
|
||||
写操作在一个复制的数组上进行,读操作还是在原始数组中进行,读写分离,互不影响。
|
||||
|
||||
@ -378,7 +368,7 @@ private E get(Object[] a, int index) {
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 适用场景
|
||||
#### 2. 适用场景
|
||||
|
||||
CopyOnWriteArrayList 在写操作的同时允许读操作,大大提高了读操作的性能,因此很适合读多写少的应用场景。
|
||||
|
||||
@ -389,9 +379,9 @@ CopyOnWriteArrayList 在写操作的同时允许读操作,大大提高了读
|
||||
|
||||
所以 CopyOnWriteArrayList 不适合内存敏感以及对实时性要求很高的场景。
|
||||
|
||||
## LinkedList
|
||||
### LinkedList
|
||||
|
||||
### 1. 概览
|
||||
#### 1. 概览
|
||||
|
||||
基于双向链表实现,使用 Node 存储链表节点信息。
|
||||
|
||||
@ -412,18 +402,18 @@ transient Node<E> last;
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191208233940066.png"/> </div><br>
|
||||
|
||||
### 2. 与 ArrayList 的比较
|
||||
#### 2. 与 ArrayList 的比较
|
||||
|
||||
ArrayList 基于动态数组实现,LinkedList 基于双向链表实现。ArrayList 和 LinkedList 的区别可以归结为数组和链表的区别:
|
||||
|
||||
- 数组支持随机访问,但插入删除的代价很高,需要移动大量元素;
|
||||
- 链表不支持随机访问,但插入删除只需要改变指针。
|
||||
|
||||
## HashMap
|
||||
### HashMap
|
||||
|
||||
为了便于理解,以下源码分析以 JDK 1.7 为主。
|
||||
|
||||
### 1. 存储结构
|
||||
#### 1. 存储结构
|
||||
|
||||
内部包含了一个 Entry 类型的数组 table。Entry 存储着键值对。它包含了四个字段,从 next 字段我们可以看出 Entry 是一个链表。即数组中的每个位置被当成一个桶,一个桶存放一个链表。HashMap 使用拉链法来解决冲突,同一个链表中存放哈希值和散列桶取模运算结果相同的 Entry。
|
||||
|
||||
@ -486,7 +476,7 @@ static class Entry<K,V> implements Map.Entry<K,V> {
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 拉链法的工作原理
|
||||
#### 2. 拉链法的工作原理
|
||||
|
||||
```java
|
||||
HashMap<String, String> map = new HashMap<>();
|
||||
@ -496,11 +486,11 @@ map.put("K3", "V3");
|
||||
```
|
||||
|
||||
- 新建一个 HashMap,默认大小为 16;
|
||||
- 插入 <K1,V1> 键值对,先计算 K1 的 hashCode 为 115,使用除留余数法得到所在的桶下标 115%16=3。
|
||||
- 插入 <K2,V2> 键值对,先计算 K2 的 hashCode 为 118,使用除留余数法得到所在的桶下标 118%16=6。
|
||||
- 插入 <K3,V3> 键值对,先计算 K3 的 hashCode 为 118,使用除留余数法得到所在的桶下标 118%16=6,插在 <K2,V2> 前面。
|
||||
- 插入 <K1,V1\> 键值对,先计算 K1 的 hashCode 为 115,使用除留余数法得到所在的桶下标 115%16=3。
|
||||
- 插入 <K2,V2\> 键值对,先计算 K2 的 hashCode 为 118,使用除留余数法得到所在的桶下标 118%16=6。
|
||||
- 插入 <K3,V3\> 键值对,先计算 K3 的 hashCode 为 118,使用除留余数法得到所在的桶下标 118%16=6,插在 <K2,V2\> 前面。
|
||||
|
||||
应该注意到链表的插入是以头插法方式进行的,例如上面的 <K3,V3> 不是插在 <K2,V2> 后面,而是插入在链表头部。
|
||||
应该注意到链表的插入是以头插法方式进行的,例如上面的 <K3,V3\> 不是插在 <K2,V2\> 后面,而是插入在链表头部。
|
||||
|
||||
查找需要分成两步进行:
|
||||
|
||||
@ -509,7 +499,7 @@ map.put("K3", "V3");
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191208235258643.png"/> </div><br>
|
||||
|
||||
### 3. put 操作
|
||||
#### 3. put 操作
|
||||
|
||||
```java
|
||||
public V put(K key, V value) {
|
||||
@ -588,7 +578,7 @@ Entry(int h, K k, V v, Entry<K,V> n) {
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 确定桶下标
|
||||
#### 4. 确定桶下标
|
||||
|
||||
很多操作都需要先确定一个键值对所在的桶下标。
|
||||
|
||||
@ -624,7 +614,7 @@ public final int hashCode() {
|
||||
|
||||
**4.2 取模**
|
||||
|
||||
令 x = 1<<4,即 x 为 2 的 4 次方,它具有以下性质:
|
||||
令 x = 1\<\<4,即 x 为 2 的 4 次方,它具有以下性质:
|
||||
|
||||
```
|
||||
x : 00010000
|
||||
@ -657,7 +647,7 @@ static int indexFor(int h, int length) {
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 扩容-基本原理
|
||||
#### 5. 扩容-基本原理
|
||||
|
||||
设 HashMap 的 table 长度为 M,需要存储的键值对数量为 N,如果哈希函数满足均匀性的要求,那么每条链表的长度大约为 N/M,因此查找的复杂度为 O(N/M)。
|
||||
|
||||
@ -736,7 +726,7 @@ void transfer(Entry[] newTable) {
|
||||
}
|
||||
```
|
||||
|
||||
### 6. 扩容-重新计算桶下标
|
||||
#### 6. 扩容-重新计算桶下标
|
||||
|
||||
在进行扩容时,需要把键值对重新计算桶下标,从而放到对应的桶上。在前面提到,HashMap 使用 hash%capacity 来确定桶下标。HashMap capacity 为 2 的 n 次方这一特点能够极大降低重新计算桶下标操作的复杂度。
|
||||
|
||||
@ -752,7 +742,7 @@ new capacity : 00100000
|
||||
- 为 0,那么 hash%00010000 = hash%00100000,桶位置和原来一致;
|
||||
- 为 1,hash%00010000 = hash%00100000 + 16,桶位置是原位置 + 16。
|
||||
|
||||
### 7. 计算数组容量
|
||||
#### 7. 计算数组容量
|
||||
|
||||
HashMap 构造函数允许用户传入的容量不是 2 的 n 次方,因为它可以自动地将传入的容量转换为 2 的 n 次方。
|
||||
|
||||
@ -785,20 +775,20 @@ static final int tableSizeFor(int cap) {
|
||||
}
|
||||
```
|
||||
|
||||
### 8. 链表转红黑树
|
||||
#### 8. 链表转红黑树
|
||||
|
||||
从 JDK 1.8 开始,一个桶存储的链表长度大于等于 8 时会将链表转换为红黑树。
|
||||
|
||||
### 9. 与 Hashtable 的比较
|
||||
#### 9. 与 Hashtable 的比较
|
||||
|
||||
- Hashtable 使用 synchronized 来进行同步。
|
||||
- HashMap 可以插入键为 null 的 Entry。
|
||||
- HashMap 的迭代器是 fail-fast 迭代器。
|
||||
- HashMap 不能保证随着时间的推移 Map 中的元素次序是不变的。
|
||||
|
||||
## ConcurrentHashMap
|
||||
### ConcurrentHashMap
|
||||
|
||||
### 1. 存储结构
|
||||
#### 1. 存储结构
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191209001038024.png"/> </div><br>
|
||||
|
||||
@ -845,7 +835,7 @@ final Segment<K,V>[] segments;
|
||||
static final int DEFAULT_CONCURRENCY_LEVEL = 16;
|
||||
```
|
||||
|
||||
### 2. size 操作
|
||||
#### 2. size 操作
|
||||
|
||||
每个 Segment 维护了一个 count 变量来统计该 Segment 中的键值对个数。
|
||||
|
||||
@ -918,7 +908,7 @@ public int size() {
|
||||
}
|
||||
```
|
||||
|
||||
### 3. JDK 1.8 的改动
|
||||
#### 3. JDK 1.8 的改动
|
||||
|
||||
JDK 1.7 使用分段锁机制来实现并发更新操作,核心类为 Segment,它继承自重入锁 ReentrantLock,并发度与 Segment 数量相等。
|
||||
|
||||
@ -926,9 +916,9 @@ JDK 1.8 使用了 CAS 操作来支持更高的并发度,在 CAS 操作失败
|
||||
|
||||
并且 JDK 1.8 的实现也在链表过长时会转换为红黑树。
|
||||
|
||||
## LinkedHashMap
|
||||
### LinkedHashMap
|
||||
|
||||
### 存储结构
|
||||
#### 存储结构
|
||||
|
||||
继承自 HashMap,因此具有和 HashMap 一样的快速查找特性。
|
||||
|
||||
@ -963,7 +953,7 @@ void afterNodeAccess(Node<K,V> p) { }
|
||||
void afterNodeInsertion(boolean evict) { }
|
||||
```
|
||||
|
||||
### afterNodeAccess()
|
||||
#### afterNodeAccess()
|
||||
|
||||
当一个节点被访问时,如果 accessOrder 为 true,则会将该节点移到链表尾部。也就是说指定为 LRU 顺序之后,在每次访问一个节点时,会将这个节点移到链表尾部,保证链表尾部是最近访问的节点,那么链表首部就是最近最久未使用的节点。
|
||||
|
||||
@ -994,7 +984,7 @@ void afterNodeAccess(Node<K,V> e) { // move node to last
|
||||
}
|
||||
```
|
||||
|
||||
### afterNodeInsertion()
|
||||
#### afterNodeInsertion()
|
||||
|
||||
在 put 等操作之后执行,当 removeEldestEntry() 方法返回 true 时会移除最晚的节点,也就是链表首部节点 first。
|
||||
|
||||
@ -1018,7 +1008,7 @@ protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
|
||||
}
|
||||
```
|
||||
|
||||
### LRU 缓存
|
||||
#### LRU 缓存
|
||||
|
||||
以下是使用 LinkedHashMap 实现的一个 LRU 缓存:
|
||||
|
||||
@ -1056,9 +1046,9 @@ public static void main(String[] args) {
|
||||
[3, 1, 4]
|
||||
```
|
||||
|
||||
## WeakHashMap
|
||||
### WeakHashMap
|
||||
|
||||
### 存储结构
|
||||
#### 存储结构
|
||||
|
||||
WeakHashMap 的 Entry 继承自 WeakReference,被 WeakReference 关联的对象在下一次垃圾回收时会被回收。
|
||||
|
||||
@ -1068,7 +1058,7 @@ WeakHashMap 主要用来实现缓存,通过使用 WeakHashMap 来引用缓存
|
||||
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V>
|
||||
```
|
||||
|
||||
### ConcurrentCache
|
||||
#### ConcurrentCache
|
||||
|
||||
Tomcat 中的 ConcurrentCache 使用了 WeakHashMap 来实现缓存功能。
|
||||
|
||||
@ -1115,7 +1105,7 @@ public final class ConcurrentCache<K, V> {
|
||||
```
|
||||
|
||||
|
||||
# 参考资料
|
||||
## 参考资料
|
||||
|
||||
- Eckel B. Java 编程思想 [M]. 机械工业出版社, 2002.
|
||||
- [Java Collection Framework](https://www.w3resource.com/java-tutorial/java-collections.php)
|
||||
@ -1129,10 +1119,3 @@ public final class ConcurrentCache<K, V> {
|
||||
- [Java 集合细节(二):asList 的缺陷](http://wiki.jikexueyuan.com/project/java-enhancement/java-thirtysix.html)
|
||||
- [Java Collection Framework – The LinkedList Class](http://javaconceptoftheday.com/java-collection-framework-linkedlist-class/)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
250
notes/Java 并发.md
250
notes/Java 并发.md
@ -1,66 +1,25 @@
|
||||
# Java 并发
|
||||
<!-- GFM-TOC -->
|
||||
* [一、使用线程](#一使用线程)
|
||||
* [实现 Runnable 接口](#实现-runnable-接口)
|
||||
* [实现 Callable 接口](#实现-callable-接口)
|
||||
* [继承 Thread 类](#继承-thread-类)
|
||||
* [实现接口 VS 继承 Thread](#实现接口-vs-继承-thread)
|
||||
* [二、基础线程机制](#二基础线程机制)
|
||||
* [Executor](#executor)
|
||||
* [Daemon](#daemon)
|
||||
* [sleep()](#sleep)
|
||||
* [yield()](#yield)
|
||||
* [三、中断](#三中断)
|
||||
* [InterruptedException](#interruptedexception)
|
||||
* [interrupted()](#interrupted)
|
||||
* [Executor 的中断操作](#executor-的中断操作)
|
||||
* [四、互斥同步](#四互斥同步)
|
||||
* [synchronized](#synchronized)
|
||||
* [ReentrantLock](#reentrantlock)
|
||||
* [比较](#比较)
|
||||
* [使用选择](#使用选择)
|
||||
* [五、线程之间的协作](#五线程之间的协作)
|
||||
* [join()](#join)
|
||||
* [wait() notify() notifyAll()](#wait-notify-notifyall)
|
||||
* [await() signal() signalAll()](#await-signal-signalall)
|
||||
* [六、线程状态](#六线程状态)
|
||||
* [新建(NEW)](#新建new)
|
||||
* [可运行(RUNABLE)](#可运行runable)
|
||||
* [阻塞(BLOCKED)](#阻塞blocked)
|
||||
* [无限期等待(WAITING)](#无限期等待waiting)
|
||||
* [限期等待(TIMED_WAITING)](#限期等待timed_waiting)
|
||||
* [死亡(TERMINATED)](#死亡terminated)
|
||||
* [七、J.U.C - AQS](#七juc---aqs)
|
||||
* [CountDownLatch](#countdownlatch)
|
||||
* [CyclicBarrier](#cyclicbarrier)
|
||||
* [Semaphore](#semaphore)
|
||||
* [八、J.U.C - 其它组件](#八juc---其它组件)
|
||||
* [FutureTask](#futuretask)
|
||||
* [BlockingQueue](#blockingqueue)
|
||||
* [ForkJoin](#forkjoin)
|
||||
* [九、线程不安全示例](#九线程不安全示例)
|
||||
* [十、Java 内存模型](#十java-内存模型)
|
||||
* [主内存与工作内存](#主内存与工作内存)
|
||||
* [内存间交互操作](#内存间交互操作)
|
||||
* [内存模型三大特性](#内存模型三大特性)
|
||||
* [先行发生原则](#先行发生原则)
|
||||
* [十一、线程安全](#十一线程安全)
|
||||
* [不可变](#不可变)
|
||||
* [互斥同步](#互斥同步)
|
||||
* [非阻塞同步](#非阻塞同步)
|
||||
* [无同步方案](#无同步方案)
|
||||
* [十二、锁优化](#十二锁优化)
|
||||
* [自旋锁](#自旋锁)
|
||||
* [锁消除](#锁消除)
|
||||
* [锁粗化](#锁粗化)
|
||||
* [轻量级锁](#轻量级锁)
|
||||
* [偏向锁](#偏向锁)
|
||||
* [十三、多线程开发良好的实践](#十三多线程开发良好的实践)
|
||||
* [参考资料](#参考资料)
|
||||
* [Java 并发](#java-并发)
|
||||
* [一、使用线程](#一使用线程)
|
||||
* [二、基础线程机制](#二基础线程机制)
|
||||
* [三、中断](#三中断)
|
||||
* [四、互斥同步](#四互斥同步)
|
||||
* [五、线程之间的协作](#五线程之间的协作)
|
||||
* [六、线程状态](#六线程状态)
|
||||
* [七、J.U.C - AQS](#七juc---aqs)
|
||||
* [八、J.U.C - 其它组件](#八juc---其它组件)
|
||||
* [九、线程不安全示例](#九线程不安全示例)
|
||||
* [十、Java 内存模型](#十java-内存模型)
|
||||
* [十一、线程安全](#十一线程安全)
|
||||
* [十二、锁优化](#十二锁优化)
|
||||
* [十三、多线程开发良好的实践](#十三多线程开发良好的实践)
|
||||
* [参考资料](#参考资料)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
|
||||
|
||||
# 一、使用线程
|
||||
## 一、使用线程
|
||||
|
||||
有三种使用线程的方法:
|
||||
|
||||
@ -70,7 +29,7 @@
|
||||
|
||||
实现 Runnable 和 Callable 接口的类只能当做一个可以在线程中运行的任务,不是真正意义上的线程,因此最后还需要通过 Thread 来调用。可以理解为任务是通过线程驱动从而执行的。
|
||||
|
||||
## 实现 Runnable 接口
|
||||
### 实现 Runnable 接口
|
||||
|
||||
需要实现接口中的 run() 方法。
|
||||
|
||||
@ -93,7 +52,7 @@ public static void main(String[] args) {
|
||||
}
|
||||
```
|
||||
|
||||
## 实现 Callable 接口
|
||||
### 实现 Callable 接口
|
||||
|
||||
与 Runnable 相比,Callable 可以有返回值,返回值通过 FutureTask 进行封装。
|
||||
|
||||
@ -115,7 +74,7 @@ public static void main(String[] args) throws ExecutionException, InterruptedExc
|
||||
}
|
||||
```
|
||||
|
||||
## 继承 Thread 类
|
||||
### 继承 Thread 类
|
||||
|
||||
同样也是需要实现 run() 方法,因为 Thread 类也实现了 Runable 接口。
|
||||
|
||||
@ -136,16 +95,16 @@ public static void main(String[] args) {
|
||||
}
|
||||
```
|
||||
|
||||
## 实现接口 VS 继承 Thread
|
||||
### 实现接口 VS 继承 Thread
|
||||
|
||||
实现接口会更好一些,因为:
|
||||
|
||||
- Java 不支持多重继承,因此继承了 Thread 类就无法继承其它类,但是可以实现多个接口;
|
||||
- 类可能只要求可执行就行,继承整个 Thread 类开销过大。
|
||||
|
||||
# 二、基础线程机制
|
||||
## 二、基础线程机制
|
||||
|
||||
## Executor
|
||||
### Executor
|
||||
|
||||
Executor 管理多个异步任务的执行,而无需程序员显式地管理线程的生命周期。这里的异步是指多个任务的执行互不干扰,不需要进行同步操作。
|
||||
|
||||
@ -165,7 +124,7 @@ public static void main(String[] args) {
|
||||
}
|
||||
```
|
||||
|
||||
## Daemon
|
||||
### Daemon
|
||||
|
||||
守护线程是程序运行时在后台提供服务的线程,不属于程序中不可或缺的部分。
|
||||
|
||||
@ -182,7 +141,7 @@ public static void main(String[] args) {
|
||||
}
|
||||
```
|
||||
|
||||
## sleep()
|
||||
### sleep()
|
||||
|
||||
Thread.sleep(millisec) 方法会休眠当前正在执行的线程,millisec 单位为毫秒。
|
||||
|
||||
@ -198,7 +157,7 @@ public void run() {
|
||||
}
|
||||
```
|
||||
|
||||
## yield()
|
||||
### yield()
|
||||
|
||||
对静态方法 Thread.yield() 的调用声明了当前线程已经完成了生命周期中最重要的部分,可以切换给其它线程来执行。该方法只是对线程调度器的一个建议,而且也只是建议具有相同优先级的其它线程可以运行。
|
||||
|
||||
@ -208,11 +167,11 @@ public void run() {
|
||||
}
|
||||
```
|
||||
|
||||
# 三、中断
|
||||
## 三、中断
|
||||
|
||||
一个线程执行完毕之后会自动结束,如果在运行过程中发生异常也会提前结束。
|
||||
|
||||
## InterruptedException
|
||||
### InterruptedException
|
||||
|
||||
通过调用一个线程的 interrupt() 来中断该线程,如果该线程处于阻塞、限期等待或者无限期等待状态,那么就会抛出 InterruptedException,从而提前结束该线程。但是不能中断 I/O 阻塞和 synchronized 锁阻塞。
|
||||
|
||||
@ -253,7 +212,7 @@ java.lang.InterruptedException: sleep interrupted
|
||||
at java.lang.Thread.run(Thread.java:745)
|
||||
```
|
||||
|
||||
## interrupted()
|
||||
### interrupted()
|
||||
|
||||
如果一个线程的 run() 方法执行一个无限循环,并且没有执行 sleep() 等会抛出 InterruptedException 的操作,那么调用线程的 interrupt() 方法就无法使线程提前结束。
|
||||
|
||||
@ -286,7 +245,7 @@ public static void main(String[] args) throws InterruptedException {
|
||||
Thread end
|
||||
```
|
||||
|
||||
## Executor 的中断操作
|
||||
### Executor 的中断操作
|
||||
|
||||
调用 Executor 的 shutdown() 方法会等待线程都执行完毕之后再关闭,但是如果调用的是 shutdownNow() 方法,则相当于调用每个线程的 interrupt() 方法。
|
||||
|
||||
@ -319,7 +278,7 @@ java.lang.InterruptedException: sleep interrupted
|
||||
at java.lang.Thread.run(Thread.java:745)
|
||||
```
|
||||
|
||||
如果只想中断 Executor 中的一个线程,可以通过使用 submit() 方法来提交一个线程,它会返回一个 Future<?> 对象,通过调用该对象的 cancel(true) 方法就可以中断线程。
|
||||
如果只想中断 Executor 中的一个线程,可以通过使用 submit() 方法来提交一个线程,它会返回一个 Future\<?\> 对象,通过调用该对象的 cancel(true) 方法就可以中断线程。
|
||||
|
||||
```java
|
||||
Future<?> future = executorService.submit(() -> {
|
||||
@ -328,11 +287,11 @@ Future<?> future = executorService.submit(() -> {
|
||||
future.cancel(true);
|
||||
```
|
||||
|
||||
# 四、互斥同步
|
||||
## 四、互斥同步
|
||||
|
||||
Java 提供了两种锁机制来控制多个线程对共享资源的互斥访问,第一个是 JVM 实现的 synchronized,而另一个是 JDK 实现的 ReentrantLock。
|
||||
|
||||
## synchronized
|
||||
### synchronized
|
||||
|
||||
**1. 同步一个代码块**
|
||||
|
||||
@ -450,7 +409,7 @@ public synchronized static void fun() {
|
||||
|
||||
作用于整个类。
|
||||
|
||||
## ReentrantLock
|
||||
### ReentrantLock
|
||||
|
||||
ReentrantLock 是 java.util.concurrent(J.U.C)包中的锁。
|
||||
|
||||
@ -486,7 +445,7 @@ public static void main(String[] args) {
|
||||
```
|
||||
|
||||
|
||||
## 比较
|
||||
### 比较
|
||||
|
||||
**1. 锁的实现**
|
||||
|
||||
@ -512,15 +471,15 @@ synchronized 中的锁是非公平的,ReentrantLock 默认情况下也是非
|
||||
|
||||
一个 ReentrantLock 可以同时绑定多个 Condition 对象。
|
||||
|
||||
## 使用选择
|
||||
### 使用选择
|
||||
|
||||
除非需要使用 ReentrantLock 的高级功能,否则优先使用 synchronized。这是因为 synchronized 是 JVM 实现的一种锁机制,JVM 原生地支持它,而 ReentrantLock 不是所有的 JDK 版本都支持。并且使用 synchronized 不用担心没有释放锁而导致死锁问题,因为 JVM 会确保锁的释放。
|
||||
|
||||
# 五、线程之间的协作
|
||||
## 五、线程之间的协作
|
||||
|
||||
当多个线程可以一起工作去解决某个问题时,如果某些部分必须在其它部分之前完成,那么就需要对线程进行协调。
|
||||
|
||||
## join()
|
||||
### join()
|
||||
|
||||
在线程中调用另一个线程的 join() 方法,会将当前线程挂起,而不是忙等待,直到目标线程结束。
|
||||
|
||||
@ -576,7 +535,7 @@ A
|
||||
B
|
||||
```
|
||||
|
||||
## wait() notify() notifyAll()
|
||||
### wait() notify() notifyAll()
|
||||
|
||||
调用 wait() 使得线程等待某个条件满足,线程在等待时会被挂起,当其他线程的运行使得这个条件满足时,其它线程会调用 notify() 或者 notifyAll() 来唤醒挂起的线程。
|
||||
|
||||
@ -624,7 +583,7 @@ after
|
||||
- wait() 是 Object 的方法,而 sleep() 是 Thread 的静态方法;
|
||||
- wait() 会释放锁,sleep() 不会。
|
||||
|
||||
## await() signal() signalAll()
|
||||
### await() signal() signalAll()
|
||||
|
||||
java.util.concurrent 类库中提供了 Condition 类来实现线程之间的协调,可以在 Condition 上调用 await() 方法使线程等待,其它线程调用 signal() 或 signalAll() 方法唤醒等待的线程。
|
||||
|
||||
@ -676,23 +635,23 @@ before
|
||||
after
|
||||
```
|
||||
|
||||
# 六、线程状态
|
||||
## 六、线程状态
|
||||
|
||||
一个线程只能处于一种状态,并且这里的线程状态特指 Java 虚拟机的线程状态,不能反映线程在特定操作系统下的状态。
|
||||
|
||||
## 新建(NEW)
|
||||
### 新建(NEW)
|
||||
|
||||
创建后尚未启动。
|
||||
|
||||
## 可运行(RUNABLE)
|
||||
### 可运行(RUNABLE)
|
||||
|
||||
正在 Java 虚拟机中运行。但是在操作系统层面,它可能处于运行状态,也可能等待资源调度(例如处理器资源),资源调度完成就进入运行状态。所以该状态的可运行是指可以被运行,具体有没有运行要看底层操作系统的资源调度。
|
||||
|
||||
## 阻塞(BLOCKED)
|
||||
### 阻塞(BLOCKED)
|
||||
|
||||
请求获取 monitor lock 从而进入 synchronized 函数或者代码块,但是其它线程已经占用了该 monitor lock,所以出于阻塞状态。要结束该状态进入从而 RUNABLE 需要其他线程释放 monitor lock。
|
||||
|
||||
## 无限期等待(WAITING)
|
||||
### 无限期等待(WAITING)
|
||||
|
||||
等待其它线程显式地唤醒。
|
||||
|
||||
@ -704,7 +663,7 @@ after
|
||||
| 没有设置 Timeout 参数的 Thread.join() 方法 | 被调用的线程执行完毕 |
|
||||
| LockSupport.park() 方法 | LockSupport.unpark(Thread) |
|
||||
|
||||
## 限期等待(TIMED_WAITING)
|
||||
### 限期等待(TIMED_WAITING)
|
||||
|
||||
无需等待其它线程显式地唤醒,在一定时间之后会被系统自动唤醒。
|
||||
|
||||
@ -718,17 +677,17 @@ after
|
||||
|
||||
调用 Thread.sleep() 方法使线程进入限期等待状态时,常常用“使一个线程睡眠”进行描述。调用 Object.wait() 方法使线程进入限期等待或者无限期等待时,常常用“挂起一个线程”进行描述。睡眠和挂起是用来描述行为,而阻塞和等待用来描述状态。
|
||||
|
||||
## 死亡(TERMINATED)
|
||||
### 死亡(TERMINATED)
|
||||
|
||||
可以是线程结束任务之后自己结束,或者产生了异常而结束。
|
||||
|
||||
[Java SE 9 Enum Thread.State](https://docs.oracle.com/javase/9/docs/api/java/lang/Thread.State.html)
|
||||
|
||||
# 七、J.U.C - AQS
|
||||
## 七、J.U.C - AQS
|
||||
|
||||
java.util.concurrent(J.U.C)大大提高了并发性能,AQS 被认为是 J.U.C 的核心。
|
||||
|
||||
## CountDownLatch
|
||||
### CountDownLatch
|
||||
|
||||
用来控制一个或者多个线程等待多个线程。
|
||||
|
||||
@ -760,7 +719,7 @@ public class CountdownLatchExample {
|
||||
run..run..run..run..run..run..run..run..run..run..end
|
||||
```
|
||||
|
||||
## CyclicBarrier
|
||||
### CyclicBarrier
|
||||
|
||||
用来控制多个线程互相等待,只有当多个线程都到达时,这些线程才会继续执行。
|
||||
|
||||
@ -812,7 +771,7 @@ public class CyclicBarrierExample {
|
||||
before..before..before..before..before..before..before..before..before..before..after..after..after..after..after..after..after..after..after..after..
|
||||
```
|
||||
|
||||
## Semaphore
|
||||
### Semaphore
|
||||
|
||||
Semaphore 类似于操作系统中的信号量,可以控制对互斥资源的访问线程数。
|
||||
|
||||
@ -847,11 +806,11 @@ public class SemaphoreExample {
|
||||
2 1 2 2 2 2 2 1 2 2
|
||||
```
|
||||
|
||||
# 八、J.U.C - 其它组件
|
||||
## 八、J.U.C - 其它组件
|
||||
|
||||
## FutureTask
|
||||
### FutureTask
|
||||
|
||||
在介绍 Callable 时我们知道它可以有返回值,返回值通过 Future<V> 进行封装。FutureTask 实现了 RunnableFuture 接口,该接口继承自 Runnable 和 Future<V> 接口,这使得 FutureTask 既可以当做一个任务执行,也可以有返回值。
|
||||
在介绍 Callable 时我们知道它可以有返回值,返回值通过 Future\<V\> 进行封装。FutureTask 实现了 RunnableFuture 接口,该接口继承自 Runnable 和 Future\<V\> 接口,这使得 FutureTask 既可以当做一个任务执行,也可以有返回值。
|
||||
|
||||
```java
|
||||
public class FutureTask<V> implements RunnableFuture<V>
|
||||
@ -901,7 +860,7 @@ other task is running...
|
||||
4950
|
||||
```
|
||||
|
||||
## BlockingQueue
|
||||
### BlockingQueue
|
||||
|
||||
java.util.concurrent.BlockingQueue 接口有以下阻塞队列的实现:
|
||||
|
||||
@ -965,7 +924,7 @@ public static void main(String[] args) {
|
||||
produce..produce..consume..consume..produce..consume..produce..consume..produce..consume..
|
||||
```
|
||||
|
||||
## ForkJoin
|
||||
### ForkJoin
|
||||
|
||||
主要用于并行计算中,和 MapReduce 原理类似,都是把大的计算任务拆分成多个小任务并行计算。
|
||||
|
||||
@ -1022,7 +981,7 @@ ForkJoinPool 实现了工作窃取算法来提高 CPU 的利用率。每个线
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/e42f188f-f4a9-4e6f-88fc-45f4682072fb.png" width="300px"> </div><br>
|
||||
|
||||
# 九、线程不安全示例
|
||||
## 九、线程不安全示例
|
||||
|
||||
如果多个线程对同一个共享数据进行访问而不采取同步操作的话,那么操作的结果是不一致的。
|
||||
|
||||
@ -1065,11 +1024,11 @@ public static void main(String[] args) throws InterruptedException {
|
||||
997
|
||||
```
|
||||
|
||||
# 十、Java 内存模型
|
||||
## 十、Java 内存模型
|
||||
|
||||
Java 内存模型试图屏蔽各种硬件和操作系统的内存访问差异,以实现让 Java 程序在各种平台下都能达到一致的内存访问效果。
|
||||
|
||||
## 主内存与工作内存
|
||||
### 主内存与工作内存
|
||||
|
||||
处理器上的寄存器的读写的速度比内存快几个数量级,为了解决这种速度矛盾,在它们之间加入了高速缓存。
|
||||
|
||||
@ -1083,7 +1042,7 @@ Java 内存模型试图屏蔽各种硬件和操作系统的内存访问差异,
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/15851555-5abc-497d-ad34-efed10f43a6b.png" width="600px"> </div><br>
|
||||
|
||||
## 内存间交互操作
|
||||
### 内存间交互操作
|
||||
|
||||
Java 内存模型定义了 8 个操作来完成主内存和工作内存的交互操作。
|
||||
|
||||
@ -1098,9 +1057,9 @@ Java 内存模型定义了 8 个操作来完成主内存和工作内存的交互
|
||||
- lock:作用于主内存的变量
|
||||
- unlock
|
||||
|
||||
## 内存模型三大特性
|
||||
### 内存模型三大特性
|
||||
|
||||
### 1. 原子性
|
||||
#### 1. 原子性
|
||||
|
||||
Java 内存模型保证了 read、load、use、assign、store、write、lock 和 unlock 操作具有原子性,例如对一个 int 类型的变量执行 assign 赋值操作,这个操作就是原子性的。但是 Java 内存模型允许虚拟机将没有被 volatile 修饰的 64 位数据(long,double)的读写操作划分为两次 32 位的操作来进行,即 load、store、read 和 write 操作可以不具备原子性。
|
||||
|
||||
@ -1192,7 +1151,7 @@ public static void main(String[] args) throws InterruptedException {
|
||||
1000
|
||||
```
|
||||
|
||||
### 2. 可见性
|
||||
#### 2. 可见性
|
||||
|
||||
可见性指当一个线程修改了共享变量的值,其它线程能够立即得知这个修改。Java 内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值来实现可见性的。
|
||||
|
||||
@ -1204,7 +1163,7 @@ public static void main(String[] args) throws InterruptedException {
|
||||
|
||||
对前面的线程不安全示例中的 cnt 变量使用 volatile 修饰,不能解决线程不安全问题,因为 volatile 并不能保证操作的原子性。
|
||||
|
||||
### 3. 有序性
|
||||
#### 3. 有序性
|
||||
|
||||
有序性是指:在本线程内观察,所有操作都是有序的。在一个线程观察另一个线程,所有操作都是无序的,无序是因为发生了指令重排序。在 Java 内存模型中,允许编译器和处理器对指令进行重排序,重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
|
||||
|
||||
@ -1212,75 +1171,75 @@ volatile 关键字通过添加内存屏障的方式来禁止指令重排,即
|
||||
|
||||
也可以通过 synchronized 来保证有序性,它保证每个时刻只有一个线程执行同步代码,相当于是让线程顺序执行同步代码。
|
||||
|
||||
## 先行发生原则
|
||||
### 先行发生原则
|
||||
|
||||
上面提到了可以用 volatile 和 synchronized 来保证有序性。除此之外,JVM 还规定了先行发生原则,让一个操作无需控制就能先于另一个操作完成。
|
||||
|
||||
### 1. 单一线程原则
|
||||
#### 1. 单一线程原则
|
||||
|
||||
> Single Thread rule
|
||||
\> Single Thread rule
|
||||
|
||||
在一个线程内,在程序前面的操作先行发生于后面的操作。
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/874b3ff7-7c5c-4e7a-b8ab-a82a3e038d20.png" width="180px"> </div><br>
|
||||
|
||||
### 2. 管程锁定规则
|
||||
#### 2. 管程锁定规则
|
||||
|
||||
> Monitor Lock Rule
|
||||
\> Monitor Lock Rule
|
||||
|
||||
一个 unlock 操作先行发生于后面对同一个锁的 lock 操作。
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/8996a537-7c4a-4ec8-a3b7-7ef1798eae26.png" width="350px"> </div><br>
|
||||
|
||||
### 3. volatile 变量规则
|
||||
#### 3. volatile 变量规则
|
||||
|
||||
> Volatile Variable Rule
|
||||
\> Volatile Variable Rule
|
||||
|
||||
对一个 volatile 变量的写操作先行发生于后面对这个变量的读操作。
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/942f33c9-8ad9-4987-836f-007de4c21de0.png" width="400px"> </div><br>
|
||||
|
||||
### 4. 线程启动规则
|
||||
#### 4. 线程启动规则
|
||||
|
||||
> Thread Start Rule
|
||||
\> Thread Start Rule
|
||||
|
||||
Thread 对象的 start() 方法调用先行发生于此线程的每一个动作。
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/6270c216-7ec0-4db7-94de-0003bce37cd2.png" width="380px"> </div><br>
|
||||
|
||||
### 5. 线程加入规则
|
||||
#### 5. 线程加入规则
|
||||
|
||||
> Thread Join Rule
|
||||
\> Thread Join Rule
|
||||
|
||||
Thread 对象的结束先行发生于 join() 方法返回。
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/233f8d89-31d7-413f-9c02-042f19c46ba1.png" width="400px"> </div><br>
|
||||
|
||||
### 6. 线程中断规则
|
||||
#### 6. 线程中断规则
|
||||
|
||||
> Thread Interruption Rule
|
||||
\> Thread Interruption Rule
|
||||
|
||||
对线程 interrupt() 方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过 interrupted() 方法检测到是否有中断发生。
|
||||
|
||||
### 7. 对象终结规则
|
||||
#### 7. 对象终结规则
|
||||
|
||||
> Finalizer Rule
|
||||
\> Finalizer Rule
|
||||
|
||||
一个对象的初始化完成(构造函数执行结束)先行发生于它的 finalize() 方法的开始。
|
||||
|
||||
### 8. 传递性
|
||||
#### 8. 传递性
|
||||
|
||||
> Transitivity
|
||||
\> Transitivity
|
||||
|
||||
如果操作 A 先行发生于操作 B,操作 B 先行发生于操作 C,那么操作 A 先行发生于操作 C。
|
||||
|
||||
# 十一、线程安全
|
||||
## 十一、线程安全
|
||||
|
||||
多个线程不管以何种方式访问某个类,并且在主调代码中不需要进行同步,都能表现正确的行为。
|
||||
|
||||
线程安全有以下几种实现方式:
|
||||
|
||||
## 不可变
|
||||
### 不可变
|
||||
|
||||
不可变(Immutable)的对象一定是线程安全的,不需要再采取任何的线程安全保障措施。只要一个不可变的对象被正确地构建出来,永远也不会看到它在多个线程之中处于不一致的状态。多线程环境下,应当尽量使对象成为不可变,来满足线程安全。
|
||||
|
||||
@ -1317,11 +1276,11 @@ public V put(K key, V value) {
|
||||
}
|
||||
```
|
||||
|
||||
## 互斥同步
|
||||
### 互斥同步
|
||||
|
||||
synchronized 和 ReentrantLock。
|
||||
|
||||
## 非阻塞同步
|
||||
### 非阻塞同步
|
||||
|
||||
互斥同步最主要的问题就是线程阻塞和唤醒所带来的性能问题,因此这种同步也称为阻塞同步。
|
||||
|
||||
@ -1329,11 +1288,11 @@ synchronized 和 ReentrantLock。
|
||||
|
||||
随着硬件指令集的发展,我们可以使用基于冲突检测的乐观并发策略:先进行操作,如果没有其它线程争用共享数据,那操作就成功了,否则采取补偿措施(不断地重试,直到成功为止)。这种乐观的并发策略的许多实现都不需要将线程阻塞,因此这种同步操作称为非阻塞同步。
|
||||
|
||||
### 1. CAS
|
||||
#### 1. CAS
|
||||
|
||||
乐观锁需要操作和冲突检测这两个步骤具备原子性,这里就不能再使用互斥同步来保证了,只能靠硬件来完成。硬件支持的原子性操作最典型的是:比较并交换(Compare-and-Swap,CAS)。CAS 指令需要有 3 个操作数,分别是内存地址 V、旧的预期值 A 和新值 B。当执行操作时,只有当 V 的值等于 A,才将 V 的值更新为 B。
|
||||
|
||||
### 2. AtomicInteger
|
||||
#### 2. AtomicInteger
|
||||
|
||||
J.U.C 包里面的整数原子类 AtomicInteger 的方法调用了 Unsafe 类的 CAS 操作。
|
||||
|
||||
@ -1370,17 +1329,17 @@ public final int getAndAddInt(Object var1, long var2, int var4) {
|
||||
}
|
||||
```
|
||||
|
||||
### 3. ABA
|
||||
#### 3. ABA
|
||||
|
||||
如果一个变量初次读取的时候是 A 值,它的值被改成了 B,后来又被改回为 A,那 CAS 操作就会误认为它从来没有被改变过。
|
||||
|
||||
J.U.C 包提供了一个带有标记的原子引用类 AtomicStampedReference 来解决这个问题,它可以通过控制变量值的版本来保证 CAS 的正确性。大部分情况下 ABA 问题不会影响程序并发的正确性,如果需要解决 ABA 问题,改用传统的互斥同步可能会比原子类更高效。
|
||||
|
||||
## 无同步方案
|
||||
### 无同步方案
|
||||
|
||||
要保证线程安全,并不是一定就要进行同步。如果一个方法本来就不涉及共享数据,那它自然就无须任何同步措施去保证正确性。
|
||||
|
||||
### 1. 栈封闭
|
||||
#### 1. 栈封闭
|
||||
|
||||
多个线程访问同一个方法的局部变量时,不会出现线程安全问题,因为局部变量存储在虚拟机栈中,属于线程私有的。
|
||||
|
||||
@ -1411,7 +1370,7 @@ public static void main(String[] args) {
|
||||
100
|
||||
```
|
||||
|
||||
### 2. 线程本地存储(Thread Local Storage)
|
||||
#### 2. 线程本地存储(Thread Local Storage)
|
||||
|
||||
如果一段代码中所需要的数据必须与其他代码共享,那就看看这些共享数据的代码是否能保证在同一个线程中执行。如果能保证,我们就可以把共享数据的可见范围限制在同一个线程之内,这样,无须同步也能保证线程之间不出现数据争用的问题。
|
||||
|
||||
@ -1482,7 +1441,7 @@ public class ThreadLocalExample1 {
|
||||
ThreadLocal.ThreadLocalMap threadLocals = null;
|
||||
```
|
||||
|
||||
当调用一个 ThreadLocal 的 set(T value) 方法时,先得到当前线程的 ThreadLocalMap 对象,然后将 ThreadLocal->value 键值对插入到该 Map 中。
|
||||
当调用一个 ThreadLocal 的 set(T value) 方法时,先得到当前线程的 ThreadLocalMap 对象,然后将 ThreadLocal-\>value 键值对插入到该 Map 中。
|
||||
|
||||
```java
|
||||
public void set(T value) {
|
||||
@ -1517,17 +1476,17 @@ ThreadLocal 从理论上讲并不是用来解决多线程并发问题的,因
|
||||
|
||||
在一些场景 (尤其是使用线程池) 下,由于 ThreadLocal.ThreadLocalMap 的底层数据结构导致 ThreadLocal 有内存泄漏的情况,应该尽可能在每次使用 ThreadLocal 后手动调用 remove(),以避免出现 ThreadLocal 经典的内存泄漏甚至是造成自身业务混乱的风险。
|
||||
|
||||
### 3. 可重入代码(Reentrant Code)
|
||||
#### 3. 可重入代码(Reentrant Code)
|
||||
|
||||
这种代码也叫做纯代码(Pure Code),可以在代码执行的任何时刻中断它,转而去执行另外一段代码(包括递归调用它本身),而在控制权返回后,原来的程序不会出现任何错误。
|
||||
|
||||
可重入代码有一些共同的特征,例如不依赖存储在堆上的数据和公用的系统资源、用到的状态量都由参数中传入、不调用非可重入的方法等。
|
||||
|
||||
# 十二、锁优化
|
||||
## 十二、锁优化
|
||||
|
||||
这里的锁优化主要是指 JVM 对 synchronized 的优化。
|
||||
|
||||
## 自旋锁
|
||||
### 自旋锁
|
||||
|
||||
互斥同步进入阻塞状态的开销都很大,应该尽量避免。在许多应用中,共享数据的锁定状态只会持续很短的一段时间。自旋锁的思想是让一个线程在请求一个共享数据的锁时执行忙循环(自旋)一段时间,如果在这段时间内能获得锁,就可以避免进入阻塞状态。
|
||||
|
||||
@ -1535,7 +1494,7 @@ ThreadLocal 从理论上讲并不是用来解决多线程并发问题的,因
|
||||
|
||||
在 JDK 1.6 中引入了自适应的自旋锁。自适应意味着自旋的次数不再固定了,而是由前一次在同一个锁上的自旋次数及锁的拥有者的状态来决定。
|
||||
|
||||
## 锁消除
|
||||
### 锁消除
|
||||
|
||||
锁消除是指对于被检测出不可能存在竞争的共享数据的锁进行消除。
|
||||
|
||||
@ -1563,13 +1522,13 @@ public static String concatString(String s1, String s2, String s3) {
|
||||
|
||||
每个 append() 方法中都有一个同步块。虚拟机观察变量 sb,很快就会发现它的动态作用域被限制在 concatString() 方法内部。也就是说,sb 的所有引用永远不会逃逸到 concatString() 方法之外,其他线程无法访问到它,因此可以进行消除。
|
||||
|
||||
## 锁粗化
|
||||
### 锁粗化
|
||||
|
||||
如果一系列的连续操作都对同一个对象反复加锁和解锁,频繁的加锁操作就会导致性能损耗。
|
||||
|
||||
上一节的示例代码中连续的 append() 方法就属于这类情况。如果虚拟机探测到由这样的一串零碎的操作都对同一个对象加锁,将会把加锁的范围扩展(粗化)到整个操作序列的外部。对于上一节的示例代码就是扩展到第一个 append() 操作之前直至最后一个 append() 操作之后,这样只需要加锁一次就可以了。
|
||||
|
||||
## 轻量级锁
|
||||
### 轻量级锁
|
||||
|
||||
JDK 1.6 引入了偏向锁和轻量级锁,从而让锁拥有了四个状态:无锁状态(unlocked)、偏向锁状态(biasble)、轻量级锁状态(lightweight locked)和重量级锁状态(inflated)。
|
||||
|
||||
@ -1589,7 +1548,7 @@ JDK 1.6 引入了偏向锁和轻量级锁,从而让锁拥有了四个状态:
|
||||
|
||||
如果 CAS 操作失败了,虚拟机首先会检查对象的 Mark Word 是否指向当前线程的虚拟机栈,如果是的话说明当前线程已经拥有了这个锁对象,那就可以直接进入同步块继续执行,否则说明这个锁对象已经被其他线程线程抢占了。如果有两条以上的线程争用同一个锁,那轻量级锁就不再有效,要膨胀为重量级锁。
|
||||
|
||||
## 偏向锁
|
||||
### 偏向锁
|
||||
|
||||
偏向锁的思想是偏向于让第一个获取锁对象的线程,这个线程在之后获取该锁就不再需要进行同步操作,甚至连 CAS 操作也不再需要。
|
||||
|
||||
@ -1599,7 +1558,7 @@ JDK 1.6 引入了偏向锁和轻量级锁,从而让锁拥有了四个状态:
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/390c913b-5f31-444f-bbdb-2b88b688e7ce.jpg" width="600"/> </div><br>
|
||||
|
||||
# 十三、多线程开发良好的实践
|
||||
## 十三、多线程开发良好的实践
|
||||
|
||||
- 给线程起个有意义的名字,这样可以方便找 Bug。
|
||||
|
||||
@ -1615,7 +1574,7 @@ JDK 1.6 引入了偏向锁和轻量级锁,从而让锁拥有了四个状态:
|
||||
|
||||
- 使用线程池而不是直接创建线程,这是因为创建线程代价很高,线程池可以有效地利用有限的线程来启动任务。
|
||||
|
||||
# 参考资料
|
||||
## 参考资料
|
||||
|
||||
- BruceEckel. Java 编程思想: 第 4 版 [M]. 机械工业出版社, 2007.
|
||||
- 周志明. 深入理解 Java 虚拟机 [M]. 机械工业出版社, 2011.
|
||||
@ -1632,10 +1591,3 @@ JDK 1.6 引入了偏向锁和轻量级锁,从而让锁拥有了四个状态:
|
||||
- [JAVA FORK JOIN EXAMPLE](http://www.javacreed.com/java-fork-join-example/ "Java Fork Join Example")
|
||||
- [聊聊并发(八)——Fork/Join 框架介绍](http://ifeve.com/talk-concurrency-forkjoin/)
|
||||
- [Eliminating SynchronizationRelated Atomic Operations with Biased Locking and Bulk Rebiasing](http://www.oracle.com/technetwork/java/javase/tech/biasedlocking-oopsla2006-preso-150106.pdf)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -1,44 +1,25 @@
|
||||
# Java 虚拟机
|
||||
<!-- GFM-TOC -->
|
||||
* [一、运行时数据区域](#一运行时数据区域)
|
||||
* [程序计数器](#程序计数器)
|
||||
* [Java 虚拟机栈](#java-虚拟机栈)
|
||||
* [本地方法栈](#本地方法栈)
|
||||
* [堆](#堆)
|
||||
* [方法区](#方法区)
|
||||
* [运行时常量池](#运行时常量池)
|
||||
* [直接内存](#直接内存)
|
||||
* [二、垃圾收集](#二垃圾收集)
|
||||
* [判断一个对象是否可被回收](#判断一个对象是否可被回收)
|
||||
* [引用类型](#引用类型)
|
||||
* [垃圾收集算法](#垃圾收集算法)
|
||||
* [垃圾收集器](#垃圾收集器)
|
||||
* [三、内存分配与回收策略](#三内存分配与回收策略)
|
||||
* [Minor GC 和 Full GC](#minor-gc-和-full-gc)
|
||||
* [内存分配策略](#内存分配策略)
|
||||
* [Full GC 的触发条件](#full-gc-的触发条件)
|
||||
* [四、类加载机制](#四类加载机制)
|
||||
* [类的生命周期](#类的生命周期)
|
||||
* [类加载过程](#类加载过程)
|
||||
* [类初始化时机](#类初始化时机)
|
||||
* [类与类加载器](#类与类加载器)
|
||||
* [类加载器分类](#类加载器分类)
|
||||
* [双亲委派模型](#双亲委派模型)
|
||||
* [自定义类加载器实现](#自定义类加载器实现)
|
||||
* [参考资料](#参考资料)
|
||||
* [Java 虚拟机](#java-虚拟机)
|
||||
* [一、运行时数据区域](#一运行时数据区域)
|
||||
* [二、垃圾收集](#二垃圾收集)
|
||||
* [三、内存分配与回收策略](#三内存分配与回收策略)
|
||||
* [四、类加载机制](#四类加载机制)
|
||||
* [参考资料](#参考资料)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
|
||||
本文大部分内容参考 **周志明《深入理解 Java 虚拟机》** ,想要深入学习的话请看原书。
|
||||
|
||||
# 一、运行时数据区域
|
||||
## 一、运行时数据区域
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/5778d113-8e13-4c53-b5bf-801e58080b97.png" width="400px"> </div><br>
|
||||
|
||||
## 程序计数器
|
||||
### 程序计数器
|
||||
|
||||
记录正在执行的虚拟机字节码指令的地址(如果正在执行的是本地方法则为空)。
|
||||
|
||||
## Java 虚拟机栈
|
||||
### Java 虚拟机栈
|
||||
|
||||
每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息。从方法调用直至执行完成的过程,对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。
|
||||
|
||||
@ -55,7 +36,7 @@ java -Xss2M HackTheJava
|
||||
- 当线程请求的栈深度超过最大值,会抛出 StackOverflowError 异常;
|
||||
- 栈进行动态扩展时如果无法申请到足够内存,会抛出 OutOfMemoryError 异常。
|
||||
|
||||
## 本地方法栈
|
||||
### 本地方法栈
|
||||
|
||||
本地方法栈与 Java 虚拟机栈类似,它们之间的区别只不过是本地方法栈为本地方法服务。
|
||||
|
||||
@ -63,7 +44,7 @@ java -Xss2M HackTheJava
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/66a6899d-c6b0-4a47-8569-9d08f0baf86c.png" width="300px"> </div><br>
|
||||
|
||||
## 堆
|
||||
### 堆
|
||||
|
||||
所有对象都在这里分配内存,是垃圾收集的主要区域("GC 堆")。
|
||||
|
||||
@ -80,7 +61,7 @@ java -Xss2M HackTheJava
|
||||
java -Xms1M -Xmx2M HackTheJava
|
||||
```
|
||||
|
||||
## 方法区
|
||||
### 方法区
|
||||
|
||||
用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
|
||||
|
||||
@ -92,7 +73,7 @@ HotSpot 虚拟机把它当成永久代来进行垃圾回收。但很难确定永
|
||||
|
||||
方法区是一个 JVM 规范,永久代与元空间都是其一种实现方式。在 JDK 1.8 之后,原来永久代的数据被分到了堆和元空间中。元空间存储类的元信息,静态变量和常量池等放入堆中。
|
||||
|
||||
## 运行时常量池
|
||||
### 运行时常量池
|
||||
|
||||
运行时常量池是方法区的一部分。
|
||||
|
||||
@ -100,17 +81,17 @@ Class 文件中的常量池(编译器生成的字面量和符号引用)会
|
||||
|
||||
除了在编译期生成的常量,还允许动态生成,例如 String 类的 intern()。
|
||||
|
||||
## 直接内存
|
||||
### 直接内存
|
||||
|
||||
在 JDK 1.4 中新引入了 NIO 类,它可以使用 Native 函数库直接分配堆外内存,然后通过 Java 堆里的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在堆内存和堆外内存来回拷贝数据。
|
||||
|
||||
# 二、垃圾收集
|
||||
## 二、垃圾收集
|
||||
|
||||
垃圾收集主要是针对堆和方法区进行。程序计数器、虚拟机栈和本地方法栈这三个区域属于线程私有的,只存在于线程的生命周期内,线程结束之后就会消失,因此不需要对这三个区域进行垃圾回收。
|
||||
|
||||
## 判断一个对象是否可被回收
|
||||
### 判断一个对象是否可被回收
|
||||
|
||||
### 1. 引用计数算法
|
||||
#### 1. 引用计数算法
|
||||
|
||||
为对象添加一个引用计数器,当对象增加一个引用时计数器加 1,引用失效时计数器减 1。引用计数为 0 的对象可被回收。
|
||||
|
||||
@ -135,7 +116,7 @@ public class Test {
|
||||
|
||||
在上述代码中,a 与 b 引用的对象实例互相持有了对象的引用,因此当我们把对 a 对象与 b 对象的引用去除之后,由于两个对象还存在互相之间的引用,导致两个 Test 对象无法被回收。
|
||||
|
||||
### 2. 可达性分析算法
|
||||
#### 2. 可达性分析算法
|
||||
|
||||
以 GC Roots 为起始点进行搜索,可达的对象都是存活的,不可达的对象可被回收。
|
||||
|
||||
@ -149,7 +130,7 @@ Java 虚拟机使用该算法来判断对象是否可被回收,GC Roots 一般
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/83d909d2-3858-4fe1-8ff4-16471db0b180.png" width="350px"> </div><br>
|
||||
|
||||
|
||||
### 3. 方法区的回收
|
||||
#### 3. 方法区的回收
|
||||
|
||||
因为方法区主要存放永久代对象,而永久代对象的回收率比新生代低很多,所以在方法区上进行回收性价比不高。
|
||||
|
||||
@ -163,19 +144,19 @@ Java 虚拟机使用该算法来判断对象是否可被回收,GC Roots 一般
|
||||
- 加载该类的 ClassLoader 已经被回收。
|
||||
- 该类对应的 Class 对象没有在任何地方被引用,也就无法在任何地方通过反射访问该类方法。
|
||||
|
||||
### 4. finalize()
|
||||
#### 4. finalize()
|
||||
|
||||
类似 C++ 的析构函数,用于关闭外部资源。但是 try-finally 等方式可以做得更好,并且该方法运行代价很高,不确定性大,无法保证各个对象的调用顺序,因此最好不要使用。
|
||||
|
||||
当一个对象可被回收时,如果需要执行该对象的 finalize() 方法,那么就有可能在该方法中让对象重新被引用,从而实现自救。自救只能进行一次,如果回收的对象之前调用了 finalize() 方法自救,后面回收时不会再调用该方法。
|
||||
|
||||
## 引用类型
|
||||
### 引用类型
|
||||
|
||||
无论是通过引用计数算法判断对象的引用数量,还是通过可达性分析算法判断对象是否可达,判定对象是否可被回收都与引用有关。
|
||||
|
||||
Java 提供了四种强度不同的引用类型。
|
||||
|
||||
### 1. 强引用
|
||||
#### 1. 强引用
|
||||
|
||||
被强引用关联的对象不会被回收。
|
||||
|
||||
@ -185,7 +166,7 @@ Java 提供了四种强度不同的引用类型。
|
||||
Object obj = new Object();
|
||||
```
|
||||
|
||||
### 2. 软引用
|
||||
#### 2. 软引用
|
||||
|
||||
被软引用关联的对象只有在内存不够的情况下才会被回收。
|
||||
|
||||
@ -197,7 +178,7 @@ SoftReference<Object> sf = new SoftReference<Object>(obj);
|
||||
obj = null; // 使对象只被软引用关联
|
||||
```
|
||||
|
||||
### 3. 弱引用
|
||||
#### 3. 弱引用
|
||||
|
||||
被弱引用关联的对象一定会被回收,也就是说它只能存活到下一次垃圾回收发生之前。
|
||||
|
||||
@ -209,7 +190,7 @@ WeakReference<Object> wf = new WeakReference<Object>(obj);
|
||||
obj = null;
|
||||
```
|
||||
|
||||
### 4. 虚引用
|
||||
#### 4. 虚引用
|
||||
|
||||
又称为幽灵引用或者幻影引用,一个对象是否有虚引用的存在,不会对其生存时间造成影响,也无法通过虚引用得到一个对象。
|
||||
|
||||
@ -223,9 +204,9 @@ PhantomReference<Object> pf = new PhantomReference<Object>(obj, null);
|
||||
obj = null;
|
||||
```
|
||||
|
||||
## 垃圾收集算法
|
||||
### 垃圾收集算法
|
||||
|
||||
### 1. 标记 - 清除
|
||||
#### 1. 标记 - 清除
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/005b481b-502b-4e3f-985d-d043c2b330aa.png" width="400px"> </div><br>
|
||||
|
||||
@ -240,7 +221,7 @@ obj = null;
|
||||
- 标记和清除过程效率都不高;
|
||||
- 会产生大量不连续的内存碎片,导致无法给大对象分配内存。
|
||||
|
||||
### 2. 标记 - 整理
|
||||
#### 2. 标记 - 整理
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/ccd773a5-ad38-4022-895c-7ac318f31437.png" width="400px"> </div><br>
|
||||
|
||||
@ -254,7 +235,7 @@ obj = null;
|
||||
|
||||
- 需要移动大量对象,处理效率比较低。
|
||||
|
||||
### 3. 复制
|
||||
#### 3. 复制
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/b2b77b9e-958c-4016-8ae5-9c6edd83871e.png" width="400px"> </div><br>
|
||||
|
||||
@ -266,7 +247,7 @@ obj = null;
|
||||
|
||||
HotSpot 虚拟机的 Eden 和 Survivor 大小比例默认为 8:1,保证了内存的利用率达到 90%。如果每次回收有多于 10% 的对象存活,那么一块 Survivor 就不够用了,此时需要依赖于老年代进行空间分配担保,也就是借用老年代的空间存储放不下的对象。
|
||||
|
||||
### 4. 分代收集
|
||||
#### 4. 分代收集
|
||||
|
||||
现在的商业虚拟机采用分代收集算法,它根据对象存活周期将内存划分为几块,不同块采用适当的收集算法。
|
||||
|
||||
@ -275,7 +256,7 @@ HotSpot 虚拟机的 Eden 和 Survivor 大小比例默认为 8:1,保证了内
|
||||
- 新生代使用:复制算法
|
||||
- 老年代使用:标记 - 清除 或者 标记 - 整理 算法
|
||||
|
||||
## 垃圾收集器
|
||||
### 垃圾收集器
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/c625baa0-dde6-449e-93df-c3a67f2f430f.jpg" width=""/> </div><br>
|
||||
|
||||
@ -284,7 +265,7 @@ HotSpot 虚拟机的 Eden 和 Survivor 大小比例默认为 8:1,保证了内
|
||||
- 单线程与多线程:单线程指的是垃圾收集器只使用一个线程,而多线程使用多个线程;
|
||||
- 串行与并行:串行指的是垃圾收集器与用户程序交替执行,这意味着在执行垃圾收集的时候需要停顿用户程序;并行指的是垃圾收集器和用户程序同时执行。除了 CMS 和 G1 之外,其它垃圾收集器都是以串行的方式执行。
|
||||
|
||||
### 1. Serial 收集器
|
||||
#### 1. Serial 收集器
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/22fda4ae-4dd5-489d-ab10-9ebfdad22ae0.jpg" width=""/> </div><br>
|
||||
|
||||
@ -296,7 +277,7 @@ Serial 翻译为串行,也就是说它以串行的方式执行。
|
||||
|
||||
它是 Client 场景下的默认新生代收集器,因为在该场景下内存一般来说不会很大。它收集一两百兆垃圾的停顿时间可以控制在一百多毫秒以内,只要不是太频繁,这点停顿时间是可以接受的。
|
||||
|
||||
### 2. ParNew 收集器
|
||||
#### 2. ParNew 收集器
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/81538cd5-1bcf-4e31-86e5-e198df1e013b.jpg" width=""/> </div><br>
|
||||
|
||||
@ -304,7 +285,7 @@ Serial 翻译为串行,也就是说它以串行的方式执行。
|
||||
|
||||
它是 Server 场景下默认的新生代收集器,除了性能原因外,主要是因为除了 Serial 收集器,只有它能与 CMS 收集器配合使用。
|
||||
|
||||
### 3. Parallel Scavenge 收集器
|
||||
#### 3. Parallel Scavenge 收集器
|
||||
|
||||
与 ParNew 一样是多线程收集器。
|
||||
|
||||
@ -316,7 +297,7 @@ Serial 翻译为串行,也就是说它以串行的方式执行。
|
||||
|
||||
可以通过一个开关参数打开 GC 自适应的调节策略(GC Ergonomics),就不需要手工指定新生代的大小(-Xmn)、Eden 和 Survivor 区的比例、晋升老年代对象年龄等细节参数了。虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量。
|
||||
|
||||
### 4. Serial Old 收集器
|
||||
#### 4. Serial Old 收集器
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/08f32fd3-f736-4a67-81ca-295b2a7972f2.jpg" width=""/> </div><br>
|
||||
|
||||
@ -325,7 +306,7 @@ Serial 翻译为串行,也就是说它以串行的方式执行。
|
||||
- 在 JDK 1.5 以及之前版本(Parallel Old 诞生以前)中与 Parallel Scavenge 收集器搭配使用。
|
||||
- 作为 CMS 收集器的后备预案,在并发收集发生 Concurrent Mode Failure 时使用。
|
||||
|
||||
### 5. Parallel Old 收集器
|
||||
#### 5. Parallel Old 收集器
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/278fe431-af88-4a95-a895-9c3b80117de3.jpg" width=""/> </div><br>
|
||||
|
||||
@ -333,7 +314,7 @@ Serial 翻译为串行,也就是说它以串行的方式执行。
|
||||
|
||||
在注重吞吐量以及 CPU 资源敏感的场合,都可以优先考虑 Parallel Scavenge 加 Parallel Old 收集器。
|
||||
|
||||
### 6. CMS 收集器
|
||||
#### 6. CMS 收集器
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/62e77997-6957-4b68-8d12-bfd609bb2c68.jpg" width=""/> </div><br>
|
||||
|
||||
@ -354,7 +335,7 @@ CMS(Concurrent Mark Sweep),Mark Sweep 指的是标记 - 清除算法。
|
||||
- 无法处理浮动垃圾,可能出现 Concurrent Mode Failure。浮动垃圾是指并发清除阶段由于用户线程继续运行而产生的垃圾,这部分垃圾只能到下一次 GC 时才能进行回收。由于浮动垃圾的存在,因此需要预留出一部分内存,意味着 CMS 收集不能像其它收集器那样等待老年代快满的时候再回收。如果预留的内存不够存放浮动垃圾,就会出现 Concurrent Mode Failure,这时虚拟机将临时启用 Serial Old 来替代 CMS。
|
||||
- 标记 - 清除算法导致的空间碎片,往往出现老年代空间剩余,但无法找到足够大连续空间来分配当前对象,不得不提前触发一次 Full GC。
|
||||
|
||||
### 7. G1 收集器
|
||||
#### 7. G1 收集器
|
||||
|
||||
G1(Garbage-First),它是一款面向服务端应用的垃圾收集器,在多 CPU 和大内存的场景下有很好的性能。HotSpot 开发团队赋予它的使命是未来可以替换掉 CMS 收集器。
|
||||
|
||||
@ -384,21 +365,21 @@ G1 把堆划分成多个大小相等的独立区域(Region),新生代和
|
||||
- 空间整合:整体来看是基于“标记 - 整理”算法实现的收集器,从局部(两个 Region 之间)上来看是基于“复制”算法实现的,这意味着运行期间不会产生内存空间碎片。
|
||||
- 可预测的停顿:能让使用者明确指定在一个长度为 M 毫秒的时间片段内,消耗在 GC 上的时间不得超过 N 毫秒。
|
||||
|
||||
# 三、内存分配与回收策略
|
||||
## 三、内存分配与回收策略
|
||||
|
||||
## Minor GC 和 Full GC
|
||||
### Minor GC 和 Full GC
|
||||
|
||||
- Minor GC:回收新生代,因为新生代对象存活时间很短,因此 Minor GC 会频繁执行,执行的速度一般也会比较快。
|
||||
|
||||
- Full GC:回收老年代和新生代,老年代对象其存活时间长,因此 Full GC 很少执行,执行速度会比 Minor GC 慢很多。
|
||||
|
||||
## 内存分配策略
|
||||
### 内存分配策略
|
||||
|
||||
### 1. 对象优先在 Eden 分配
|
||||
#### 1. 对象优先在 Eden 分配
|
||||
|
||||
大多数情况下,对象在新生代 Eden 上分配,当 Eden 空间不够时,发起 Minor GC。
|
||||
|
||||
### 2. 大对象直接进入老年代
|
||||
#### 2. 大对象直接进入老年代
|
||||
|
||||
大对象是指需要连续内存空间的对象,最典型的大对象是那种很长的字符串以及数组。
|
||||
|
||||
@ -406,41 +387,41 @@ G1 把堆划分成多个大小相等的独立区域(Region),新生代和
|
||||
|
||||
-XX:PretenureSizeThreshold,大于此值的对象直接在老年代分配,避免在 Eden 和 Survivor 之间的大量内存复制。
|
||||
|
||||
### 3. 长期存活的对象进入老年代
|
||||
#### 3. 长期存活的对象进入老年代
|
||||
|
||||
为对象定义年龄计数器,对象在 Eden 出生并经过 Minor GC 依然存活,将移动到 Survivor 中,年龄就增加 1 岁,增加到一定年龄则移动到老年代中。
|
||||
|
||||
-XX:MaxTenuringThreshold 用来定义年龄的阈值。
|
||||
|
||||
### 4. 动态对象年龄判定
|
||||
#### 4. 动态对象年龄判定
|
||||
|
||||
虚拟机并不是永远要求对象的年龄必须达到 MaxTenuringThreshold 才能晋升老年代,如果在 Survivor 中相同年龄所有对象大小的总和大于 Survivor 空间的一半,则年龄大于或等于该年龄的对象可以直接进入老年代,无需等到 MaxTenuringThreshold 中要求的年龄。
|
||||
|
||||
### 5. 空间分配担保
|
||||
#### 5. 空间分配担保
|
||||
|
||||
在发生 Minor GC 之前,虚拟机先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果条件成立的话,那么 Minor GC 可以确认是安全的。
|
||||
|
||||
如果不成立的话虚拟机会查看 HandlePromotionFailure 的值是否允许担保失败,如果允许那么就会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次 Minor GC;如果小于,或者 HandlePromotionFailure 的值不允许冒险,那么就要进行一次 Full GC。
|
||||
|
||||
## Full GC 的触发条件
|
||||
### Full GC 的触发条件
|
||||
|
||||
对于 Minor GC,其触发条件非常简单,当 Eden 空间满时,就将触发一次 Minor GC。而 Full GC 则相对复杂,有以下条件:
|
||||
|
||||
### 1. 调用 System.gc()
|
||||
#### 1. 调用 System.gc()
|
||||
|
||||
只是建议虚拟机执行 Full GC,但是虚拟机不一定真正去执行。不建议使用这种方式,而是让虚拟机管理内存。
|
||||
|
||||
### 2. 老年代空间不足
|
||||
#### 2. 老年代空间不足
|
||||
|
||||
老年代空间不足的常见场景为前文所讲的大对象直接进入老年代、长期存活的对象进入老年代等。
|
||||
|
||||
为了避免以上原因引起的 Full GC,应当尽量不要创建过大的对象以及数组。除此之外,可以通过 -Xmn 虚拟机参数调大新生代的大小,让对象尽量在新生代被回收掉,不进入老年代。还可以通过 -XX:MaxTenuringThreshold 调大对象进入老年代的年龄,让对象在新生代多存活一段时间。
|
||||
|
||||
### 3. 空间分配担保失败
|
||||
#### 3. 空间分配担保失败
|
||||
|
||||
使用复制算法的 Minor GC 需要老年代的内存空间作担保,如果担保失败会执行一次 Full GC。具体内容请参考上面的第 5 小节。
|
||||
|
||||
### 4. JDK 1.7 及以前的永久代空间不足
|
||||
#### 4. JDK 1.7 及以前的永久代空间不足
|
||||
|
||||
在 JDK 1.7 及以前,HotSpot 虚拟机中的方法区是用永久代实现的,永久代中存放的为一些 Class 的信息、常量、静态变量等数据。
|
||||
|
||||
@ -448,15 +429,15 @@ G1 把堆划分成多个大小相等的独立区域(Region),新生代和
|
||||
|
||||
为避免以上原因引起的 Full GC,可采用的方法为增大永久代空间或转为使用 CMS GC。
|
||||
|
||||
### 5. Concurrent Mode Failure
|
||||
#### 5. Concurrent Mode Failure
|
||||
|
||||
执行 CMS GC 的过程中同时有对象要放入老年代,而此时老年代空间不足(可能是 GC 过程中浮动垃圾过多导致暂时性的空间不足),便会报 Concurrent Mode Failure 错误,并触发 Full GC。
|
||||
|
||||
# 四、类加载机制
|
||||
## 四、类加载机制
|
||||
|
||||
类是在运行期间第一次使用时动态加载的,而不是一次性加载所有类。因为如果一次性加载,那么会占用很多的内存。
|
||||
|
||||
## 类的生命周期
|
||||
### 类的生命周期
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/335fe19c-4a76-45ab-9320-88c90d6a0d7e.png" width="600px"> </div><br>
|
||||
|
||||
@ -470,11 +451,11 @@ G1 把堆划分成多个大小相等的独立区域(Region),新生代和
|
||||
- 使用(Using)
|
||||
- 卸载(Unloading)
|
||||
|
||||
## 类加载过程
|
||||
### 类加载过程
|
||||
|
||||
包含了加载、验证、准备、解析和初始化这 5 个阶段。
|
||||
|
||||
### 1. 加载
|
||||
#### 1. 加载
|
||||
|
||||
加载是类加载的一个阶段,注意不要混淆。
|
||||
|
||||
@ -492,11 +473,11 @@ G1 把堆划分成多个大小相等的独立区域(Region),新生代和
|
||||
- 运行时计算生成,例如动态代理技术,在 java.lang.reflect.Proxy 使用 ProxyGenerator.generateProxyClass 的代理类的二进制字节流。
|
||||
- 由其他文件生成,例如由 JSP 文件生成对应的 Class 类。
|
||||
|
||||
### 2. 验证
|
||||
#### 2. 验证
|
||||
|
||||
确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
|
||||
|
||||
### 3. 准备
|
||||
#### 3. 准备
|
||||
|
||||
类变量是被 static 修饰的变量,准备阶段为类变量分配内存并设置初始值,使用的是方法区的内存。
|
||||
|
||||
@ -514,18 +495,18 @@ public static int value = 123;
|
||||
public static final int value = 123;
|
||||
```
|
||||
|
||||
### 4. 解析
|
||||
#### 4. 解析
|
||||
|
||||
将常量池的符号引用替换为直接引用的过程。
|
||||
|
||||
其中解析过程在某些情况下可以在初始化阶段之后再开始,这是为了支持 Java 的动态绑定。
|
||||
|
||||
### 5. 初始化
|
||||
#### 5. 初始化
|
||||
|
||||
<div data="modify -->"></div>
|
||||
初始化阶段才真正开始执行类中定义的 Java 程序代码。初始化阶段是虚拟机执行类构造器 <clinit>() 方法的过程。在准备阶段,类变量已经赋过一次系统要求的初始值,而在初始化阶段,根据程序员通过程序制定的主观计划去初始化类变量和其它资源。
|
||||
初始化阶段才真正开始执行类中定义的 Java 程序代码。初始化阶段是虚拟机执行类构造器 <clinit\>() 方法的过程。在准备阶段,类变量已经赋过一次系统要求的初始值,而在初始化阶段,根据程序员通过程序制定的主观计划去初始化类变量和其它资源。
|
||||
|
||||
<clinit>() 是由编译器自动收集类中所有类变量的赋值动作和静态语句块中的语句合并产生的,编译器收集的顺序由语句在源文件中出现的顺序决定。特别注意的是,静态语句块只能访问到定义在它之前的类变量,定义在它之后的类变量只能赋值,不能访问。例如以下代码:
|
||||
<clinit\>() 是由编译器自动收集类中所有类变量的赋值动作和静态语句块中的语句合并产生的,编译器收集的顺序由语句在源文件中出现的顺序决定。特别注意的是,静态语句块只能访问到定义在它之前的类变量,定义在它之后的类变量只能赋值,不能访问。例如以下代码:
|
||||
|
||||
```java
|
||||
public class Test {
|
||||
@ -537,7 +518,7 @@ public class Test {
|
||||
}
|
||||
```
|
||||
|
||||
由于父类的 <clinit>() 方法先执行,也就意味着父类中定义的静态语句块的执行要优先于子类。例如以下代码:
|
||||
由于父类的 <clinit\>() 方法先执行,也就意味着父类中定义的静态语句块的执行要优先于子类。例如以下代码:
|
||||
|
||||
```java
|
||||
static class Parent {
|
||||
@ -556,13 +537,13 @@ public static void main(String[] args) {
|
||||
}
|
||||
```
|
||||
|
||||
接口中不可以使用静态语句块,但仍然有类变量初始化的赋值操作,因此接口与类一样都会生成 <clinit>() 方法。但接口与类不同的是,执行接口的 <clinit>() 方法不需要先执行父接口的 <clinit>() 方法。只有当父接口中定义的变量使用时,父接口才会初始化。另外,接口的实现类在初始化时也一样不会执行接口的 <clinit>() 方法。
|
||||
接口中不可以使用静态语句块,但仍然有类变量初始化的赋值操作,因此接口与类一样都会生成 <clinit\>() 方法。但接口与类不同的是,执行接口的 <clinit\>() 方法不需要先执行父接口的 <clinit\>() 方法。只有当父接口中定义的变量使用时,父接口才会初始化。另外,接口的实现类在初始化时也一样不会执行接口的 <clinit\>() 方法。
|
||||
|
||||
虚拟机会保证一个类的 <clinit>() 方法在多线程环境下被正确的加锁和同步,如果多个线程同时初始化一个类,只会有一个线程执行这个类的 <clinit>() 方法,其它线程都会阻塞等待,直到活动线程执行 <clinit>() 方法完毕。如果在一个类的 <clinit>() 方法中有耗时的操作,就可能造成多个线程阻塞,在实际过程中此种阻塞很隐蔽。
|
||||
虚拟机会保证一个类的 <clinit\>() 方法在多线程环境下被正确的加锁和同步,如果多个线程同时初始化一个类,只会有一个线程执行这个类的 <clinit\>() 方法,其它线程都会阻塞等待,直到活动线程执行 <clinit\>() 方法完毕。如果在一个类的 <clinit\>() 方法中有耗时的操作,就可能造成多个线程阻塞,在实际过程中此种阻塞很隐蔽。
|
||||
|
||||
## 类初始化时机
|
||||
### 类初始化时机
|
||||
|
||||
### 1. 主动引用
|
||||
#### 1. 主动引用
|
||||
|
||||
虚拟机规范中并没有强制约束何时进行加载,但是规范严格规定了有且只有下列五种情况必须对类进行初始化(加载、验证、准备都会随之发生):
|
||||
|
||||
@ -576,7 +557,7 @@ public static void main(String[] args) {
|
||||
|
||||
- 当使用 JDK 1.7 的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果为 REF_getStatic, REF_putStatic, REF_invokeStatic 的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化;
|
||||
|
||||
### 2. 被动引用
|
||||
#### 2. 被动引用
|
||||
|
||||
以上 5 种场景中的行为称为对一个类进行主动引用。除此之外,所有引用类的方式都不会触发初始化,称为被动引用。被动引用的常见例子包括:
|
||||
|
||||
@ -598,13 +579,13 @@ SuperClass[] sca = new SuperClass[10];
|
||||
System.out.println(ConstClass.HELLOWORLD);
|
||||
```
|
||||
|
||||
## 类与类加载器
|
||||
### 类与类加载器
|
||||
|
||||
两个类相等,需要类本身相等,并且使用同一个类加载器进行加载。这是因为每一个类加载器都拥有一个独立的类名称空间。
|
||||
|
||||
这里的相等,包括类的 Class 对象的 equals() 方法、isAssignableFrom() 方法、isInstance() 方法的返回结果为 true,也包括使用 instanceof 关键字做对象所属关系判定结果为 true。
|
||||
|
||||
## 类加载器分类
|
||||
### 类加载器分类
|
||||
|
||||
从 Java 虚拟机的角度来讲,只存在以下两种不同的类加载器:
|
||||
|
||||
@ -614,13 +595,13 @@ System.out.println(ConstClass.HELLOWORLD);
|
||||
|
||||
从 Java 开发人员的角度看,类加载器可以划分得更细致一些:
|
||||
|
||||
- 启动类加载器(Bootstrap ClassLoader)此类加载器负责将存放在 <JRE_HOME>\lib 目录中的,或者被 -Xbootclasspath 参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如 rt.jar,名字不符合的类库即使放在 lib 目录中也不会被加载)类库加载到虚拟机内存中。启动类加载器无法被 Java 程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给启动类加载器,直接使用 null 代替即可。
|
||||
- 启动类加载器(Bootstrap ClassLoader)此类加载器负责将存放在 <JRE_HOME\>\lib 目录中的,或者被 -Xbootclasspath 参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如 rt.jar,名字不符合的类库即使放在 lib 目录中也不会被加载)类库加载到虚拟机内存中。启动类加载器无法被 Java 程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给启动类加载器,直接使用 null 代替即可。
|
||||
|
||||
- 扩展类加载器(Extension ClassLoader)这个类加载器是由 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的。它负责将 <JAVA_HOME>/lib/ext 或者被 java.ext.dir 系统变量所指定路径中的所有类库加载到内存中,开发者可以直接使用扩展类加载器。
|
||||
- 扩展类加载器(Extension ClassLoader)这个类加载器是由 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的。它负责将 <JAVA_HOME\>/lib/ext 或者被 java.ext.dir 系统变量所指定路径中的所有类库加载到内存中,开发者可以直接使用扩展类加载器。
|
||||
|
||||
- 应用程序类加载器(Application ClassLoader)这个类加载器是由 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。由于这个类加载器是 ClassLoader 中的 getSystemClassLoader() 方法的返回值,因此一般称为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
|
||||
|
||||
## 双亲委派模型
|
||||
### 双亲委派模型
|
||||
|
||||
应用程序是由三种类加载器互相配合从而实现类加载,除此之外还可以加入自己定义的类加载器。
|
||||
|
||||
@ -628,17 +609,17 @@ System.out.println(ConstClass.HELLOWORLD);
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/0dd2d40a-5b2b-4d45-b176-e75a4cd4bdbf.png" width="500px"> </div><br>
|
||||
|
||||
### 1. 工作过程
|
||||
#### 1. 工作过程
|
||||
|
||||
一个类加载器首先将类加载请求转发到父类加载器,只有当父类加载器无法完成时才尝试自己加载。
|
||||
|
||||
### 2. 好处
|
||||
#### 2. 好处
|
||||
|
||||
使得 Java 类随着它的类加载器一起具有一种带有优先级的层次关系,从而使得基础类得到统一。
|
||||
|
||||
例如 java.lang.Object 存放在 rt.jar 中,如果编写另外一个 java.lang.Object 并放到 ClassPath 中,程序可以编译通过。由于双亲委派模型的存在,所以在 rt.jar 中的 Object 比在 ClassPath 中的 Object 优先级更高,这是因为 rt.jar 中的 Object 使用的是启动类加载器,而 ClassPath 中的 Object 使用的是应用程序类加载器。rt.jar 中的 Object 优先级更高,那么程序中所有的 Object 都是这个 Object。
|
||||
|
||||
### 3. 实现
|
||||
#### 3. 实现
|
||||
|
||||
以下是抽象类 java.lang.ClassLoader 的代码片段,其中的 loadClass() 方法运行过程如下:先检查类是否已经加载过,如果没有则让父类加载器去加载。当父类加载器加载失败时抛出 ClassNotFoundException,此时尝试自己去加载。
|
||||
|
||||
@ -686,7 +667,7 @@ public abstract class ClassLoader {
|
||||
}
|
||||
```
|
||||
|
||||
## 自定义类加载器实现
|
||||
### 自定义类加载器实现
|
||||
|
||||
以下代码中的 FileSystemClassLoader 是自定义类加载器,继承自 java.lang.ClassLoader,用于加载文件系统上的类。它首先根据类的全名在文件系统上查找类的字节代码文件(.class 文件),然后读取该文件内容,最后通过 defineClass() 方法来把这些字节代码转换成 java.lang.Class 类的实例。
|
||||
|
||||
@ -735,7 +716,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)
|
||||
@ -751,10 +732,3 @@ public class FileSystemClassLoader extends ClassLoader {
|
||||
- [深入探讨 Java 类加载器](https://www.ibm.com/developerworks/cn/java/j-lo-classloader/index.html#code6)
|
||||
- [Guide to WeakHashMap in Java](http://www.baeldung.com/java-weakhashmap)
|
||||
- [Tomcat example source code file (ConcurrentCache.java)](https://alvinalexander.com/java/jwarehouse/apache-tomcat-6.0.16/java/org/apache/el/util/ConcurrentCache.java.shtml)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -1,10 +1,12 @@
|
||||
# Leetcode 题解 - 二分查找
|
||||
<!-- GFM-TOC -->
|
||||
* [1. 求开方](#1-求开方)
|
||||
* [2. 大于给定元素的最小元素](#2-大于给定元素的最小元素)
|
||||
* [3. 有序数组的 Single Element](#3-有序数组的-single-element)
|
||||
* [4. 第一个错误的版本](#4-第一个错误的版本)
|
||||
* [5. 旋转数组的最小数字](#5-旋转数组的最小数字)
|
||||
* [6. 查找区间](#6-查找区间)
|
||||
* [Leetcode 题解 - 二分查找](#leetcode-题解---二分查找)
|
||||
* [1. 求开方](#1-求开方)
|
||||
* [2. 大于给定元素的最小元素](#2-大于给定元素的最小元素)
|
||||
* [3. 有序数组的 Single Element](#3-有序数组的-single-element)
|
||||
* [4. 第一个错误的版本](#4-第一个错误的版本)
|
||||
* [5. 旋转数组的最小数字](#5-旋转数组的最小数字)
|
||||
* [6. 查找区间](#6-查找区间)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
|
||||
@ -75,12 +77,12 @@ public int binarySearch(int[] nums, int key) {
|
||||
该实现和正常实现有以下不同:
|
||||
|
||||
- h 的赋值表达式为 h = m
|
||||
- 循环条件为 l < h
|
||||
- 循环条件为 l \< h
|
||||
- 最后返回 l 而不是 -1
|
||||
|
||||
在 nums[m] >= key 的情况下,可以推导出最左 key 位于 [l, m] 区间中,这是一个闭区间。h 的赋值表达式为 h = m,因为 m 位置也可能是解。
|
||||
在 nums[m] \>= key 的情况下,可以推导出最左 key 位于 [l, m] 区间中,这是一个闭区间。h 的赋值表达式为 h = m,因为 m 位置也可能是解。
|
||||
|
||||
在 h 的赋值表达式为 h = m 的情况下,如果循环条件为 l <= h,那么会出现循环无法退出的情况,因此循环条件只能是 l < h。以下演示了循环条件为 l <= h 时循环无法退出的情况:
|
||||
在 h 的赋值表达式为 h = m 的情况下,如果循环条件为 l \<= h,那么会出现循环无法退出的情况,因此循环条件只能是 l \< h。以下演示了循环条件为 l \<= h 时循环无法退出的情况:
|
||||
|
||||
```text
|
||||
nums = {0, 1, 2}, key = 1
|
||||
@ -94,7 +96,7 @@ l m h
|
||||
|
||||
当循环体退出时,不表示没有查找到 key,因此最后返回的结果不应该为 -1。为了验证有没有查找到,需要在调用端判断一下返回位置上的值和 key 是否相等。
|
||||
|
||||
# 1. 求开方
|
||||
## 1. 求开方
|
||||
|
||||
69\. Sqrt(x) (Easy)
|
||||
|
||||
@ -111,7 +113,7 @@ Explanation: The square root of 8 is 2.82842..., and since we want to return an
|
||||
|
||||
一个数 x 的开方 sqrt 一定在 0 \~ x 之间,并且满足 sqrt == x / sqrt。可以利用二分查找在 0 \~ x 之间查找 sqrt。
|
||||
|
||||
对于 x = 8,它的开方是 2.82842...,最后应该返回 2 而不是 3。在循环条件为 l <= h 并且循环退出时,h 总是比 l 小 1,也就是说 h = 2,l = 3,因此最后的返回值应该为 h 而不是 l。
|
||||
对于 x = 8,它的开方是 2.82842...,最后应该返回 2 而不是 3。在循环条件为 l \<= h 并且循环退出时,h 总是比 l 小 1,也就是说 h = 2,l = 3,因此最后的返回值应该为 h 而不是 l。
|
||||
|
||||
```java
|
||||
public int mySqrt(int x) {
|
||||
@ -134,7 +136,7 @@ public int mySqrt(int x) {
|
||||
}
|
||||
```
|
||||
|
||||
# 2. 大于给定元素的最小元素
|
||||
## 2. 大于给定元素的最小元素
|
||||
|
||||
744\. Find Smallest Letter Greater Than Target (Easy)
|
||||
|
||||
@ -170,7 +172,7 @@ public char nextGreatestLetter(char[] letters, char target) {
|
||||
}
|
||||
```
|
||||
|
||||
# 3. 有序数组的 Single Element
|
||||
## 3. 有序数组的 Single Element
|
||||
|
||||
540\. Single Element in a Sorted Array (Medium)
|
||||
|
||||
@ -185,11 +187,11 @@ Output: 2
|
||||
|
||||
要求以 O(logN) 时间复杂度进行求解,因此不能遍历数组并进行异或操作来求解,这么做的时间复杂度为 O(N)。
|
||||
|
||||
令 index 为 Single Element 在数组中的位置。在 index 之后,数组中原来存在的成对状态被改变。如果 m 为偶数,并且 m + 1 < index,那么 nums[m] == nums[m + 1];m + 1 >= index,那么 nums[m] != nums[m + 1]。
|
||||
令 index 为 Single Element 在数组中的位置。在 index 之后,数组中原来存在的成对状态被改变。如果 m 为偶数,并且 m + 1 \< index,那么 nums[m] == nums[m + 1];m + 1 \>= index,那么 nums[m] != nums[m + 1]。
|
||||
|
||||
从上面的规律可以知道,如果 nums[m] == nums[m + 1],那么 index 所在的数组位置为 [m + 2, h],此时令 l = m + 2;如果 nums[m] != nums[m + 1],那么 index 所在的数组位置为 [l, m],此时令 h = m。
|
||||
|
||||
因为 h 的赋值表达式为 h = m,那么循环条件也就只能使用 l < h 这种形式。
|
||||
因为 h 的赋值表达式为 h = m,那么循环条件也就只能使用 l \< h 这种形式。
|
||||
|
||||
```java
|
||||
public int singleNonDuplicate(int[] nums) {
|
||||
@ -209,7 +211,7 @@ public int singleNonDuplicate(int[] nums) {
|
||||
}
|
||||
```
|
||||
|
||||
# 4. 第一个错误的版本
|
||||
## 4. 第一个错误的版本
|
||||
|
||||
278\. First Bad Version (Easy)
|
||||
|
||||
@ -219,7 +221,7 @@ public int singleNonDuplicate(int[] nums) {
|
||||
|
||||
如果第 m 个版本出错,则表示第一个错误的版本在 [l, m] 之间,令 h = m;否则第一个错误的版本在 [m + 1, h] 之间,令 l = m + 1。
|
||||
|
||||
因为 h 的赋值表达式为 h = m,因此循环条件为 l < h。
|
||||
因为 h 的赋值表达式为 h = m,因此循环条件为 l \< h。
|
||||
|
||||
```java
|
||||
public int firstBadVersion(int n) {
|
||||
@ -236,7 +238,7 @@ public int firstBadVersion(int n) {
|
||||
}
|
||||
```
|
||||
|
||||
# 5. 旋转数组的最小数字
|
||||
## 5. 旋转数组的最小数字
|
||||
|
||||
153\. Find Minimum in Rotated Sorted Array (Medium)
|
||||
|
||||
@ -262,7 +264,7 @@ public int findMin(int[] nums) {
|
||||
}
|
||||
```
|
||||
|
||||
# 6. 查找区间
|
||||
## 6. 查找区间
|
||||
|
||||
34\. Find First and Last Position of Element in Sorted Array
|
||||
|
||||
@ -312,10 +314,3 @@ nums = [2,2], target = 2
|
||||
```
|
||||
|
||||
如果 h 的取值为 nums.length - 1,那么 last = findFirst(nums, target + 1) - 1 = 1 - 1 = 0。这是因为 findLeft 只会返回 [0, nums.length - 1] 范围的值,对于 findFirst([2,2], 3) ,我们希望返回 3 插入 nums 中的位置,也就是数组最后一个位置再往后一个位置,即 nums.length。所以我们需要将 h 取值为 nums.length,从而使得 findFirst返回的区间更大,能够覆盖 target 大于 nums 最后一个元素的情况。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -1,22 +1,24 @@
|
||||
# Leetcode 题解 - 位运算
|
||||
<!-- GFM-TOC -->
|
||||
* [0. 原理](#0-原理)
|
||||
* [1. 统计两个数的二进制表示有多少位不同](#1-统计两个数的二进制表示有多少位不同)
|
||||
* [2. 数组中唯一一个不重复的元素](#2-数组中唯一一个不重复的元素)
|
||||
* [3. 找出数组中缺失的那个数](#3-找出数组中缺失的那个数)
|
||||
* [4. 数组中不重复的两个元素](#4-数组中不重复的两个元素)
|
||||
* [5. 翻转一个数的比特位](#5-翻转一个数的比特位)
|
||||
* [6. 不用额外变量交换两个整数](#6-不用额外变量交换两个整数)
|
||||
* [7. 判断一个数是不是 2 的 n 次方](#7-判断一个数是不是-2-的-n-次方)
|
||||
* [8. 判断一个数是不是 4 的 n 次方](#8--判断一个数是不是-4-的-n-次方)
|
||||
* [9. 判断一个数的位级表示是否不会出现连续的 0 和 1](#9-判断一个数的位级表示是否不会出现连续的-0-和-1)
|
||||
* [10. 求一个数的补码](#10-求一个数的补码)
|
||||
* [11. 实现整数的加法](#11-实现整数的加法)
|
||||
* [12. 字符串数组最大乘积](#12-字符串数组最大乘积)
|
||||
* [13. 统计从 0 \~ n 每个数的二进制表示中 1 的个数](#13-统计从-0-\~-n-每个数的二进制表示中-1-的个数)
|
||||
* [Leetcode 题解 - 位运算](#leetcode-题解---位运算)
|
||||
* [0. 原理](#0-原理)
|
||||
* [1. 统计两个数的二进制表示有多少位不同](#1-统计两个数的二进制表示有多少位不同)
|
||||
* [2. 数组中唯一一个不重复的元素](#2-数组中唯一一个不重复的元素)
|
||||
* [3. 找出数组中缺失的那个数](#3-找出数组中缺失的那个数)
|
||||
* [4. 数组中不重复的两个元素](#4-数组中不重复的两个元素)
|
||||
* [5. 翻转一个数的比特位](#5-翻转一个数的比特位)
|
||||
* [6. 不用额外变量交换两个整数](#6-不用额外变量交换两个整数)
|
||||
* [7. 判断一个数是不是 2 的 n 次方](#7-判断一个数是不是-2-的-n-次方)
|
||||
* [8. 判断一个数是不是 4 的 n 次方](#8--判断一个数是不是-4-的-n-次方)
|
||||
* [9. 判断一个数的位级表示是否不会出现连续的 0 和 1](#9-判断一个数的位级表示是否不会出现连续的-0-和-1)
|
||||
* [10. 求一个数的补码](#10-求一个数的补码)
|
||||
* [11. 实现整数的加法](#11-实现整数的加法)
|
||||
* [12. 字符串数组最大乘积](#12-字符串数组最大乘积)
|
||||
* [13. 统计从 0 \~ n 每个数的二进制表示中 1 的个数](#13-统计从-0-\~-n-每个数的二进制表示中-1-的个数)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
|
||||
# 0. 原理
|
||||
## 0. 原理
|
||||
|
||||
**基本原理**
|
||||
|
||||
@ -76,7 +78,7 @@ n-(n&(-n)) 则可以去除 n 的位级表示中最低的那一位 1,和 n&(n-1
|
||||
|
||||
**移位运算**
|
||||
|
||||
\>\> n 为算术右移,相当于除以 2n,例如 -7 \>\> 2 = -2。
|
||||
\\>\\> n 为算术右移,相当于除以 2n,例如 -7 \\>\\> 2 = -2。
|
||||
|
||||
```
|
||||
11111111111111111111111111111001 >> 2
|
||||
@ -84,7 +86,7 @@ n-(n&(-n)) 则可以去除 n 的位级表示中最低的那一位 1,和 n&(n-1
|
||||
11111111111111111111111111111110
|
||||
```
|
||||
|
||||
\>\>\> n 为无符号右移,左边会补上 0。例如 -7 \>\>\> 2 = 1073741822。
|
||||
\\>\\>\\> n 为无符号右移,左边会补上 0。例如 -7 \\>\\>\\> 2 = 1073741822。
|
||||
|
||||
```
|
||||
11111111111111111111111111111001 >>> 2
|
||||
@ -92,7 +94,7 @@ n-(n&(-n)) 则可以去除 n 的位级表示中最低的那一位 1,和 n&(n-1
|
||||
00111111111111111111111111111111
|
||||
```
|
||||
|
||||
<< n 为算术左移,相当于乘以 2n。-7 << 2 = -28。
|
||||
\<\< n 为算术左移,相当于乘以 2n。-7 \<\< 2 = -28。
|
||||
|
||||
```
|
||||
11111111111111111111111111111001 << 2
|
||||
@ -104,11 +106,11 @@ n-(n&(-n)) 则可以去除 n 的位级表示中最低的那一位 1,和 n&(n-1
|
||||
|
||||
要获取 111111111,将 0 取反即可,\~0。
|
||||
|
||||
要得到只有第 i 位为 1 的 mask,将 1 向左移动 i-1 位即可,1<<(i-1) 。例如 1<<4 得到只有第 5 位为 1 的 mask :00010000。
|
||||
要得到只有第 i 位为 1 的 mask,将 1 向左移动 i-1 位即可,1\<\<(i-1) 。例如 1\<\<4 得到只有第 5 位为 1 的 mask :00010000。
|
||||
|
||||
要得到 1 到 i 位为 1 的 mask,(1<<i)-1 即可,例如将 (1<<4)-1 = 00010000-1 = 00001111。
|
||||
要得到 1 到 i 位为 1 的 mask,(1\<\<i)-1 即可,例如将 (1\<\<4)-1 = 00010000-1 = 00001111。
|
||||
|
||||
要得到 1 到 i 位为 0 的 mask,只需将 1 到 i 位为 1 的 mask 取反,即 \~((1<<i)-1)。
|
||||
要得到 1 到 i 位为 0 的 mask,只需将 1 到 i 位为 1 的 mask 取反,即 \~((1\<\<i)-1)。
|
||||
|
||||
**Java 中的位操作**
|
||||
|
||||
@ -118,7 +120,7 @@ static int Integer.highestOneBit(); // 获得最高位
|
||||
static String toBinaryString(int i); // 转换为二进制表示的字符串
|
||||
```
|
||||
|
||||
# 1. 统计两个数的二进制表示有多少位不同
|
||||
## 1. 统计两个数的二进制表示有多少位不同
|
||||
|
||||
461. Hamming Distance (Easy)
|
||||
|
||||
@ -173,7 +175,7 @@ public int hammingDistance(int x, int y) {
|
||||
}
|
||||
```
|
||||
|
||||
# 2. 数组中唯一一个不重复的元素
|
||||
## 2. 数组中唯一一个不重复的元素
|
||||
|
||||
136\. Single Number (Easy)
|
||||
|
||||
@ -194,7 +196,7 @@ public int singleNumber(int[] nums) {
|
||||
}
|
||||
```
|
||||
|
||||
# 3. 找出数组中缺失的那个数
|
||||
## 3. 找出数组中缺失的那个数
|
||||
|
||||
268\. Missing Number (Easy)
|
||||
|
||||
@ -217,7 +219,7 @@ public int missingNumber(int[] nums) {
|
||||
}
|
||||
```
|
||||
|
||||
# 4. 数组中不重复的两个元素
|
||||
## 4. 数组中不重复的两个元素
|
||||
|
||||
260\. Single Number III (Medium)
|
||||
|
||||
@ -243,7 +245,7 @@ public int[] singleNumber(int[] nums) {
|
||||
}
|
||||
```
|
||||
|
||||
# 5. 翻转一个数的比特位
|
||||
## 5. 翻转一个数的比特位
|
||||
|
||||
190\. Reverse Bits (Easy)
|
||||
|
||||
@ -290,7 +292,7 @@ private int reverseByte(byte b) {
|
||||
}
|
||||
```
|
||||
|
||||
# 6. 不用额外变量交换两个整数
|
||||
## 6. 不用额外变量交换两个整数
|
||||
|
||||
[程序员代码面试指南 :P317](#)
|
||||
|
||||
@ -300,7 +302,7 @@ b = a ^ b;
|
||||
a = a ^ b;
|
||||
```
|
||||
|
||||
# 7. 判断一个数是不是 2 的 n 次方
|
||||
## 7. 判断一个数是不是 2 的 n 次方
|
||||
|
||||
231\. Power of Two (Easy)
|
||||
|
||||
@ -322,7 +324,7 @@ public boolean isPowerOfTwo(int n) {
|
||||
}
|
||||
```
|
||||
|
||||
# 8. 判断一个数是不是 4 的 n 次方
|
||||
## 8. 判断一个数是不是 4 的 n 次方
|
||||
|
||||
342\. Power of Four (Easy)
|
||||
|
||||
@ -344,7 +346,7 @@ public boolean isPowerOfFour(int num) {
|
||||
}
|
||||
```
|
||||
|
||||
# 9. 判断一个数的位级表示是否不会出现连续的 0 和 1
|
||||
## 9. 判断一个数的位级表示是否不会出现连续的 0 和 1
|
||||
|
||||
693\. Binary Number with Alternating Bits (Easy)
|
||||
|
||||
@ -371,7 +373,7 @@ public boolean hasAlternatingBits(int n) {
|
||||
}
|
||||
```
|
||||
|
||||
# 10. 求一个数的补码
|
||||
## 10. 求一个数的补码
|
||||
|
||||
476\. Number Complement (Easy)
|
||||
|
||||
@ -428,15 +430,15 @@ public int findComplement(int num) {
|
||||
}
|
||||
```
|
||||
|
||||
# 11. 实现整数的加法
|
||||
## 11. 实现整数的加法
|
||||
|
||||
371\. Sum of Two Integers (Easy)
|
||||
|
||||
[Leetcode](https://leetcode.com/problems/sum-of-two-integers/description/) / [力扣](https://leetcode-cn.com/problems/sum-of-two-integers/description/)
|
||||
|
||||
a ^ b 表示没有考虑进位的情况下两数的和,(a & b) << 1 就是进位。
|
||||
a ^ b 表示没有考虑进位的情况下两数的和,(a & b) \<\< 1 就是进位。
|
||||
|
||||
递归会终止的原因是 (a & b) << 1 最右边会多一个 0,那么继续递归,进位最右边的 0 会慢慢增多,最后进位会变为 0,递归终止。
|
||||
递归会终止的原因是 (a & b) \<\< 1 最右边会多一个 0,那么继续递归,进位最右边的 0 会慢慢增多,最后进位会变为 0,递归终止。
|
||||
|
||||
```java
|
||||
public int getSum(int a, int b) {
|
||||
@ -444,7 +446,7 @@ public int getSum(int a, int b) {
|
||||
}
|
||||
```
|
||||
|
||||
# 12. 字符串数组最大乘积
|
||||
## 12. 字符串数组最大乘积
|
||||
|
||||
318\. Maximum Product of Word Lengths (Medium)
|
||||
|
||||
@ -481,7 +483,7 @@ public int maxProduct(String[] words) {
|
||||
}
|
||||
```
|
||||
|
||||
# 13. 统计从 0 \~ n 每个数的二进制表示中 1 的个数
|
||||
## 13. 统计从 0 \~ n 每个数的二进制表示中 1 的个数
|
||||
|
||||
338\. Counting Bits (Medium)
|
||||
|
||||
@ -499,10 +501,3 @@ public int[] countBits(int num) {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -1,10 +1,12 @@
|
||||
# Leetcode 题解 - 分治
|
||||
<!-- GFM-TOC -->
|
||||
* [1. 给表达式加括号](#1-给表达式加括号)
|
||||
* [2. 不同的二叉搜索树](#2-不同的二叉搜索树)
|
||||
* [Leetcode 题解 - 分治](#leetcode-题解---分治)
|
||||
* [1. 给表达式加括号](#1-给表达式加括号)
|
||||
* [2. 不同的二叉搜索树](#2-不同的二叉搜索树)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
|
||||
# 1. 给表达式加括号
|
||||
## 1. 给表达式加括号
|
||||
|
||||
241\. Different Ways to Add Parentheses (Medium)
|
||||
|
||||
@ -51,7 +53,7 @@ public List<Integer> diffWaysToCompute(String input) {
|
||||
}
|
||||
```
|
||||
|
||||
# 2. 不同的二叉搜索树
|
||||
## 2. 不同的二叉搜索树
|
||||
|
||||
95\. Unique Binary Search Trees II (Medium)
|
||||
|
||||
@ -108,10 +110,3 @@ private List<TreeNode> generateSubtrees(int s, int e) {
|
||||
return res;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -1,53 +1,53 @@
|
||||
# Leetcode 题解 - 动态规划
|
||||
<!-- GFM-TOC -->
|
||||
* [斐波那契数列](#斐波那契数列)
|
||||
* [1. 爬楼梯](#1-爬楼梯)
|
||||
* [2. 强盗抢劫](#2-强盗抢劫)
|
||||
* [3. 强盗在环形街区抢劫](#3-强盗在环形街区抢劫)
|
||||
* [4. 信件错排](#4-信件错排)
|
||||
* [5. 母牛生产](#5-母牛生产)
|
||||
* [矩阵路径](#矩阵路径)
|
||||
* [1. 矩阵的最小路径和](#1-矩阵的最小路径和)
|
||||
* [2. 矩阵的总路径数](#2-矩阵的总路径数)
|
||||
* [数组区间](#数组区间)
|
||||
* [1. 数组区间和](#1-数组区间和)
|
||||
* [2. 数组中等差递增子区间的个数](#2-数组中等差递增子区间的个数)
|
||||
* [分割整数](#分割整数)
|
||||
* [1. 分割整数的最大乘积](#1-分割整数的最大乘积)
|
||||
* [2. 按平方数来分割整数](#2-按平方数来分割整数)
|
||||
* [3. 分割整数构成字母字符串](#3-分割整数构成字母字符串)
|
||||
* [最长递增子序列](#最长递增子序列)
|
||||
* [1. 最长递增子序列](#1-最长递增子序列)
|
||||
* [2. 一组整数对能够构成的最长链](#2-一组整数对能够构成的最长链)
|
||||
* [3. 最长摆动子序列](#3-最长摆动子序列)
|
||||
* [最长公共子序列](#最长公共子序列)
|
||||
* [1. 最长公共子序列](#1-最长公共子序列)
|
||||
* [0-1 背包](#0-1-背包)
|
||||
* [1. 划分数组为和相等的两部分](#1-划分数组为和相等的两部分)
|
||||
* [2. 改变一组数的正负号使得它们的和为一给定数](#2-改变一组数的正负号使得它们的和为一给定数)
|
||||
* [3. 01 字符构成最多的字符串](#3-01-字符构成最多的字符串)
|
||||
* [4. 找零钱的最少硬币数](#4-找零钱的最少硬币数)
|
||||
* [5. 找零钱的硬币数组合](#5-找零钱的硬币数组合)
|
||||
* [6. 字符串按单词列表分割](#6-字符串按单词列表分割)
|
||||
* [7. 组合总和](#7-组合总和)
|
||||
* [股票交易](#股票交易)
|
||||
* [1. 需要冷却期的股票交易](#1-需要冷却期的股票交易)
|
||||
* [2. 需要交易费用的股票交易](#2-需要交易费用的股票交易)
|
||||
* [3. 只能进行两次的股票交易](#3-只能进行两次的股票交易)
|
||||
* [4. 只能进行 k 次的股票交易](#4-只能进行-k-次的股票交易)
|
||||
* [字符串编辑](#字符串编辑)
|
||||
* [1. 删除两个字符串的字符使它们相等](#1-删除两个字符串的字符使它们相等)
|
||||
* [2. 编辑距离](#2-编辑距离)
|
||||
* [3. 复制粘贴字符](#3-复制粘贴字符)
|
||||
* [Leetcode 题解 - 动态规划](#leetcode-题解---动态规划)
|
||||
* [斐波那契数列](#斐波那契数列)
|
||||
* [1. 爬楼梯](#1-爬楼梯)
|
||||
* [2. 强盗抢劫](#2-强盗抢劫)
|
||||
* [3. 强盗在环形街区抢劫](#3-强盗在环形街区抢劫)
|
||||
* [4. 信件错排](#4-信件错排)
|
||||
* [5. 母牛生产](#5-母牛生产)
|
||||
* [矩阵路径](#矩阵路径)
|
||||
* [1. 矩阵的最小路径和](#1-矩阵的最小路径和)
|
||||
* [2. 矩阵的总路径数](#2-矩阵的总路径数)
|
||||
* [数组区间](#数组区间)
|
||||
* [1. 数组区间和](#1-数组区间和)
|
||||
* [2. 数组中等差递增子区间的个数](#2-数组中等差递增子区间的个数)
|
||||
* [分割整数](#分割整数)
|
||||
* [1. 分割整数的最大乘积](#1-分割整数的最大乘积)
|
||||
* [2. 按平方数来分割整数](#2-按平方数来分割整数)
|
||||
* [3. 分割整数构成字母字符串](#3-分割整数构成字母字符串)
|
||||
* [最长递增子序列](#最长递增子序列)
|
||||
* [1. 最长递增子序列](#1-最长递增子序列)
|
||||
* [2. 一组整数对能够构成的最长链](#2-一组整数对能够构成的最长链)
|
||||
* [3. 最长摆动子序列](#3-最长摆动子序列)
|
||||
* [最长公共子序列](#最长公共子序列)
|
||||
* [1. 最长公共子序列](#1-最长公共子序列)
|
||||
* [0-1 背包](#0-1-背包)
|
||||
* [1. 划分数组为和相等的两部分](#1-划分数组为和相等的两部分)
|
||||
* [2. 改变一组数的正负号使得它们的和为一给定数](#2-改变一组数的正负号使得它们的和为一给定数)
|
||||
* [3. 01 字符构成最多的字符串](#3-01-字符构成最多的字符串)
|
||||
* [4. 找零钱的最少硬币数](#4-找零钱的最少硬币数)
|
||||
* [5. 找零钱的硬币数组合](#5-找零钱的硬币数组合)
|
||||
* [6. 字符串按单词列表分割](#6-字符串按单词列表分割)
|
||||
* [7. 组合总和](#7-组合总和)
|
||||
* [股票交易](#股票交易)
|
||||
* [1. 需要冷却期的股票交易](#1-需要冷却期的股票交易)
|
||||
* [2. 需要交易费用的股票交易](#2-需要交易费用的股票交易)
|
||||
* [3. 只能进行两次的股票交易](#3-只能进行两次的股票交易)
|
||||
* [4. 只能进行 k 次的股票交易](#4-只能进行-k-次的股票交易)
|
||||
* [字符串编辑](#字符串编辑)
|
||||
* [1. 删除两个字符串的字符使它们相等](#1-删除两个字符串的字符使它们相等)
|
||||
* [2. 编辑距离](#2-编辑距离)
|
||||
* [3. 复制粘贴字符](#3-复制粘贴字符)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
|
||||
|
||||
|
||||
递归和动态规划都是将原问题拆成多个子问题然后求解,他们之间最本质的区别是,动态规划保存了子问题的解,避免重复计算。
|
||||
|
||||
# 斐波那契数列
|
||||
## 斐波那契数列
|
||||
|
||||
## 1. 爬楼梯
|
||||
### 1. 爬楼梯
|
||||
|
||||
70\. Climbing Stairs (Easy)
|
||||
|
||||
@ -80,7 +80,7 @@ public int climbStairs(int n) {
|
||||
}
|
||||
```
|
||||
|
||||
## 2. 强盗抢劫
|
||||
### 2. 强盗抢劫
|
||||
|
||||
198\. House Robber (Easy)
|
||||
|
||||
@ -108,7 +108,7 @@ public int rob(int[] nums) {
|
||||
}
|
||||
```
|
||||
|
||||
## 3. 强盗在环形街区抢劫
|
||||
### 3. 强盗在环形街区抢劫
|
||||
|
||||
213\. House Robber II (Medium)
|
||||
|
||||
@ -137,7 +137,7 @@ private int rob(int[] nums, int first, int last) {
|
||||
}
|
||||
```
|
||||
|
||||
## 4. 信件错排
|
||||
### 4. 信件错排
|
||||
|
||||
题目描述:有 N 个 信 和 信封,它们被打乱,求错误装信方式的数量。
|
||||
|
||||
@ -152,7 +152,7 @@ private int rob(int[] nums, int first, int last) {
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/da1f96b9-fd4d-44ca-8925-fb14c5733388.png" width="350px"> </div><br>
|
||||
|
||||
## 5. 母牛生产
|
||||
### 5. 母牛生产
|
||||
|
||||
[程序员代码面试指南-P181](#)
|
||||
|
||||
@ -164,9 +164,9 @@ private int rob(int[] nums, int first, int last) {
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/879814ee-48b5-4bcb-86f5-dcc400cb81ad.png" width="250px"> </div><br>
|
||||
|
||||
# 矩阵路径
|
||||
## 矩阵路径
|
||||
|
||||
## 1. 矩阵的最小路径和
|
||||
### 1. 矩阵的最小路径和
|
||||
|
||||
64\. Minimum Path Sum (Medium)
|
||||
|
||||
@ -204,7 +204,7 @@ public int minPathSum(int[][] grid) {
|
||||
}
|
||||
```
|
||||
|
||||
## 2. 矩阵的总路径数
|
||||
### 2. 矩阵的总路径数
|
||||
|
||||
62\. Unique Paths (Medium)
|
||||
|
||||
@ -241,9 +241,9 @@ public int uniquePaths(int m, int n) {
|
||||
}
|
||||
```
|
||||
|
||||
# 数组区间
|
||||
## 数组区间
|
||||
|
||||
## 1. 数组区间和
|
||||
### 1. 数组区间和
|
||||
|
||||
303\. Range Sum Query - Immutable (Easy)
|
||||
|
||||
@ -277,7 +277,7 @@ class NumArray {
|
||||
}
|
||||
```
|
||||
|
||||
## 2. 数组中等差递增子区间的个数
|
||||
### 2. 数组中等差递增子区间的个数
|
||||
|
||||
413\. Arithmetic Slices (Medium)
|
||||
|
||||
@ -336,9 +336,9 @@ public int numberOfArithmeticSlices(int[] A) {
|
||||
}
|
||||
```
|
||||
|
||||
# 分割整数
|
||||
## 分割整数
|
||||
|
||||
## 1. 分割整数的最大乘积
|
||||
### 1. 分割整数的最大乘积
|
||||
|
||||
343\. Integer Break (Medim)
|
||||
|
||||
@ -359,7 +359,7 @@ public int integerBreak(int n) {
|
||||
}
|
||||
```
|
||||
|
||||
## 2. 按平方数来分割整数
|
||||
### 2. 按平方数来分割整数
|
||||
|
||||
279\. Perfect Squares(Medium)
|
||||
|
||||
@ -397,7 +397,7 @@ private List<Integer> generateSquareList(int n) {
|
||||
}
|
||||
```
|
||||
|
||||
## 3. 分割整数构成字母字符串
|
||||
### 3. 分割整数构成字母字符串
|
||||
|
||||
91\. Decode Ways (Medium)
|
||||
|
||||
@ -431,7 +431,7 @@ public int numDecodings(String s) {
|
||||
}
|
||||
```
|
||||
|
||||
# 最长递增子序列
|
||||
## 最长递增子序列
|
||||
|
||||
已知一个序列 {S<sub>1</sub>, S<sub>2</sub>,...,S<sub>n</sub>},取出若干数组成新的序列 {S<sub>i1</sub>, S<sub>i2</sub>,..., S<sub>im</sub>},其中 i1、i2 ... im 保持递增,即新序列中各个数仍然保持原数列中的先后顺序,称新序列为原序列的一个 **子序列** 。
|
||||
|
||||
@ -447,7 +447,7 @@ public int numDecodings(String s) {
|
||||
|
||||
对于一个长度为 N 的序列,最长递增子序列并不一定会以 S<sub>N</sub> 为结尾,因此 dp[N] 不是序列的最长递增子序列的长度,需要遍历 dp 数组找出最大值才是所要的结果,max{ dp[i] | 1 <= i <= N} 即为所求。
|
||||
|
||||
## 1. 最长递增子序列
|
||||
### 1. 最长递增子序列
|
||||
|
||||
300\. Longest Increasing Subsequence (Medium)
|
||||
|
||||
@ -485,7 +485,7 @@ return ret;
|
||||
定义一个 tails 数组,其中 tails[i] 存储长度为 i + 1 的最长递增子序列的最后一个元素。对于一个元素 x,
|
||||
|
||||
- 如果它大于 tails 数组所有的值,那么把它添加到 tails 后面,表示最长递增子序列长度加 1;
|
||||
- 如果 tails[i-1] < x <= tails[i],那么更新 tails[i] = x。
|
||||
- 如果 tails[i-1] \< x \<= tails[i],那么更新 tails[i] = x。
|
||||
|
||||
例如对于数组 [4,3,6,5],有:
|
||||
|
||||
@ -531,7 +531,7 @@ private int binarySearch(int[] tails, int len, int key) {
|
||||
}
|
||||
```
|
||||
|
||||
## 2. 一组整数对能够构成的最长链
|
||||
### 2. 一组整数对能够构成的最长链
|
||||
|
||||
646\. Maximum Length of Pair Chain (Medium)
|
||||
|
||||
@ -543,7 +543,7 @@ Output: 2
|
||||
Explanation: The longest chain is [1,2] -> [3,4]
|
||||
```
|
||||
|
||||
题目描述:对于 (a, b) 和 (c, d) ,如果 b < c,则它们可以构成一条链。
|
||||
题目描述:对于 (a, b) 和 (c, d) ,如果 b \< c,则它们可以构成一条链。
|
||||
|
||||
```java
|
||||
public int findLongestChain(int[][] pairs) {
|
||||
@ -565,7 +565,7 @@ public int findLongestChain(int[][] pairs) {
|
||||
}
|
||||
```
|
||||
|
||||
## 3. 最长摆动子序列
|
||||
### 3. 最长摆动子序列
|
||||
|
||||
376\. Wiggle Subsequence (Medium)
|
||||
|
||||
@ -603,7 +603,7 @@ public int wiggleMaxLength(int[] nums) {
|
||||
}
|
||||
```
|
||||
|
||||
# 最长公共子序列
|
||||
## 最长公共子序列
|
||||
|
||||
对于两个子序列 S1 和 S2,找出它们最长的公共子序列。
|
||||
|
||||
@ -626,7 +626,7 @@ public int wiggleMaxLength(int[] nums) {
|
||||
- 在最长递增子序列中,dp[i] 表示以 S<sub>i</sub> 为结尾的最长递增子序列长度,子序列必须包含 S<sub>i</sub> ;在最长公共子序列中,dp[i][j] 表示 S1 中前 i 个字符与 S2 中前 j 个字符的最长公共子序列长度,不一定包含 S1<sub>i</sub> 和 S2<sub>j</sub>。
|
||||
- 在求最终解时,最长公共子序列中 dp[N][M] 就是最终解,而最长递增子序列中 dp[N] 不是最终解,因为以 S<sub>N</sub> 为结尾的最长递增子序列不一定是整个序列最长递增子序列,需要遍历一遍 dp 数组找到最大者。
|
||||
|
||||
## 1. 最长公共子序列
|
||||
### 1. 最长公共子序列
|
||||
|
||||
1143\. Longest Common Subsequence
|
||||
|
||||
@ -649,7 +649,7 @@ public int wiggleMaxLength(int[] nums) {
|
||||
}
|
||||
```
|
||||
|
||||
# 0-1 背包
|
||||
## 0-1 背包
|
||||
|
||||
有一个容量为 N 的背包,要用这个背包装下物品的价值最大,这些物品有两个属性:体积 w 和价值 v。
|
||||
|
||||
@ -730,7 +730,7 @@ public int knapsack(int W, int N, int[] weights, int[] values) {
|
||||
|
||||
- 其它:物品之间相互约束或者依赖
|
||||
|
||||
## 1. 划分数组为和相等的两部分
|
||||
### 1. 划分数组为和相等的两部分
|
||||
|
||||
416\. Partition Equal Subset Sum (Medium)
|
||||
|
||||
@ -772,7 +772,7 @@ private int computeArraySum(int[] nums) {
|
||||
}
|
||||
```
|
||||
|
||||
## 2. 改变一组数的正负号使得它们的和为一给定数
|
||||
### 2. 改变一组数的正负号使得它们的和为一给定数
|
||||
|
||||
494\. Target Sum (Medium)
|
||||
|
||||
@ -846,7 +846,7 @@ private int findTargetSumWays(int[] nums, int start, int S) {
|
||||
}
|
||||
```
|
||||
|
||||
## 3. 01 字符构成最多的字符串
|
||||
### 3. 01 字符构成最多的字符串
|
||||
|
||||
474\. Ones and Zeroes (Medium)
|
||||
|
||||
@ -886,7 +886,7 @@ public int findMaxForm(String[] strs, int m, int n) {
|
||||
}
|
||||
```
|
||||
|
||||
## 4. 找零钱的最少硬币数
|
||||
### 4. 找零钱的最少硬币数
|
||||
|
||||
322\. Coin Change (Medium)
|
||||
|
||||
@ -930,7 +930,7 @@ public int coinChange(int[] coins, int amount) {
|
||||
}
|
||||
```
|
||||
|
||||
## 5. 找零钱的硬币数组合
|
||||
### 5. 找零钱的硬币数组合
|
||||
|
||||
518\. Coin Change 2 (Medium)
|
||||
|
||||
@ -964,7 +964,7 @@ public int change(int amount, int[] coins) {
|
||||
}
|
||||
```
|
||||
|
||||
## 6. 字符串按单词列表分割
|
||||
### 6. 字符串按单词列表分割
|
||||
|
||||
139\. Word Break (Medium)
|
||||
|
||||
@ -1003,7 +1003,7 @@ public boolean wordBreak(String s, List<String> wordDict) {
|
||||
}
|
||||
```
|
||||
|
||||
## 7. 组合总和
|
||||
### 7. 组合总和
|
||||
|
||||
377\. Combination Sum IV (Medium)
|
||||
|
||||
@ -1046,9 +1046,9 @@ public int combinationSum4(int[] nums, int target) {
|
||||
}
|
||||
```
|
||||
|
||||
# 股票交易
|
||||
## 股票交易
|
||||
|
||||
## 1. 需要冷却期的股票交易
|
||||
### 1. 需要冷却期的股票交易
|
||||
|
||||
309\. Best Time to Buy and Sell Stock with Cooldown(Medium)
|
||||
|
||||
@ -1080,7 +1080,7 @@ public int maxProfit(int[] prices) {
|
||||
}
|
||||
```
|
||||
|
||||
## 2. 需要交易费用的股票交易
|
||||
### 2. 需要交易费用的股票交易
|
||||
|
||||
714\. Best Time to Buy and Sell Stock with Transaction Fee (Medium)
|
||||
|
||||
@ -1121,7 +1121,7 @@ public int maxProfit(int[] prices, int fee) {
|
||||
```
|
||||
|
||||
|
||||
## 3. 只能进行两次的股票交易
|
||||
### 3. 只能进行两次的股票交易
|
||||
|
||||
123\. Best Time to Buy and Sell Stock III (Hard)
|
||||
|
||||
@ -1149,7 +1149,7 @@ public int maxProfit(int[] prices) {
|
||||
}
|
||||
```
|
||||
|
||||
## 4. 只能进行 k 次的股票交易
|
||||
### 4. 只能进行 k 次的股票交易
|
||||
|
||||
188\. Best Time to Buy and Sell Stock IV (Hard)
|
||||
|
||||
@ -1179,9 +1179,9 @@ public int maxProfit(int k, int[] prices) {
|
||||
}
|
||||
```
|
||||
|
||||
# 字符串编辑
|
||||
## 字符串编辑
|
||||
|
||||
## 1. 删除两个字符串的字符使它们相等
|
||||
### 1. 删除两个字符串的字符使它们相等
|
||||
|
||||
583\. Delete Operation for Two Strings (Medium)
|
||||
|
||||
@ -1212,7 +1212,7 @@ public int minDistance(String word1, String word2) {
|
||||
}
|
||||
```
|
||||
|
||||
## 2. 编辑距离
|
||||
### 2. 编辑距离
|
||||
|
||||
72\. Edit Distance (Hard)
|
||||
|
||||
@ -1267,7 +1267,7 @@ public int minDistance(String word1, String word2) {
|
||||
}
|
||||
```
|
||||
|
||||
## 3. 复制粘贴字符
|
||||
### 3. 复制粘贴字符
|
||||
|
||||
650\. 2 Keys Keyboard (Medium)
|
||||
|
||||
@ -1311,10 +1311,3 @@ public int minSteps(int n) {
|
||||
return dp[n];
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -1,17 +1,19 @@
|
||||
# Leetcode 题解 - 双指针
|
||||
<!-- GFM-TOC -->
|
||||
* [1. 有序数组的 Two Sum](#1-有序数组的-two-sum)
|
||||
* [2. 两数平方和](#2-两数平方和)
|
||||
* [3. 反转字符串中的元音字符](#3-反转字符串中的元音字符)
|
||||
* [4. 回文字符串](#4-回文字符串)
|
||||
* [5. 归并两个有序数组](#5-归并两个有序数组)
|
||||
* [6. 判断链表是否存在环](#6-判断链表是否存在环)
|
||||
* [7. 最长子序列](#7-最长子序列)
|
||||
* [Leetcode 题解 - 双指针](#leetcode-题解---双指针)
|
||||
* [1. 有序数组的 Two Sum](#1-有序数组的-two-sum)
|
||||
* [2. 两数平方和](#2-两数平方和)
|
||||
* [3. 反转字符串中的元音字符](#3-反转字符串中的元音字符)
|
||||
* [4. 回文字符串](#4-回文字符串)
|
||||
* [5. 归并两个有序数组](#5-归并两个有序数组)
|
||||
* [6. 判断链表是否存在环](#6-判断链表是否存在环)
|
||||
* [7. 最长子序列](#7-最长子序列)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
|
||||
双指针主要用于遍历数组,两个指针指向不同的元素,从而协同完成任务。
|
||||
|
||||
# 1. 有序数组的 Two Sum
|
||||
## 1. 有序数组的 Two Sum
|
||||
|
||||
167\. Two Sum II - Input array is sorted (Easy)
|
||||
|
||||
@ -27,8 +29,8 @@ Output: index1=1, index2=2
|
||||
使用双指针,一个指针指向值较小的元素,一个指针指向值较大的元素。指向较小元素的指针从头向尾遍历,指向较大元素的指针从尾向头遍历。
|
||||
|
||||
- 如果两个指针指向元素的和 sum == target,那么得到要求的结果;
|
||||
- 如果 sum > target,移动较大的元素,使 sum 变小一些;
|
||||
- 如果 sum < target,移动较小的元素,使 sum 变大一些。
|
||||
- 如果 sum \> target,移动较大的元素,使 sum 变小一些;
|
||||
- 如果 sum \< target,移动较小的元素,使 sum 变大一些。
|
||||
|
||||
数组中的元素最多遍历一次,时间复杂度为 O(N)。只使用了两个额外变量,空间复杂度为 O(1)。
|
||||
|
||||
@ -52,7 +54,7 @@ public int[] twoSum(int[] numbers, int target) {
|
||||
}
|
||||
```
|
||||
|
||||
# 2. 两数平方和
|
||||
## 2. 两数平方和
|
||||
|
||||
633\. Sum of Square Numbers (Easy)
|
||||
|
||||
@ -92,7 +94,7 @@ Explanation: 1 * 1 + 2 * 2 = 5
|
||||
}
|
||||
```
|
||||
|
||||
# 3. 反转字符串中的元音字符
|
||||
## 3. 反转字符串中的元音字符
|
||||
|
||||
345\. Reverse Vowels of a String (Easy)
|
||||
|
||||
@ -137,7 +139,7 @@ public String reverseVowels(String s) {
|
||||
}
|
||||
```
|
||||
|
||||
# 4. 回文字符串
|
||||
## 4. 回文字符串
|
||||
|
||||
680\. Valid Palindrome II (Easy)
|
||||
|
||||
@ -185,7 +187,7 @@ private boolean isPalindrome(String s, int i, int j) {
|
||||
}
|
||||
```
|
||||
|
||||
# 5. 归并两个有序数组
|
||||
## 5. 归并两个有序数组
|
||||
|
||||
88\. Merge Sorted Array (Easy)
|
||||
|
||||
@ -221,7 +223,7 @@ public void merge(int[] nums1, int m, int[] nums2, int n) {
|
||||
}
|
||||
```
|
||||
|
||||
# 6. 判断链表是否存在环
|
||||
## 6. 判断链表是否存在环
|
||||
|
||||
141\. Linked List Cycle (Easy)
|
||||
|
||||
@ -246,7 +248,7 @@ public boolean hasCycle(ListNode head) {
|
||||
}
|
||||
```
|
||||
|
||||
# 7. 最长子序列
|
||||
## 7. 最长子序列
|
||||
|
||||
524\. Longest Word in Dictionary through Deleting (Medium)
|
||||
|
||||
@ -290,10 +292,3 @@ private boolean isSubstr(String s, String target) {
|
||||
return j == target.length();
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -1,8 +1,10 @@
|
||||
# Leetcode 题解 - 哈希表
|
||||
<!-- GFM-TOC -->
|
||||
* [1. 数组中两个数的和为给定值](#1-数组中两个数的和为给定值)
|
||||
* [2. 判断数组是否含有重复元素](#2-判断数组是否含有重复元素)
|
||||
* [3. 最长和谐序列](#3-最长和谐序列)
|
||||
* [4. 最长连续序列](#4-最长连续序列)
|
||||
* [Leetcode 题解 - 哈希表](#leetcode-题解---哈希表)
|
||||
* [1. 数组中两个数的和为给定值](#1-数组中两个数的和为给定值)
|
||||
* [2. 判断数组是否含有重复元素](#2-判断数组是否含有重复元素)
|
||||
* [3. 最长和谐序列](#3-最长和谐序列)
|
||||
* [4. 最长连续序列](#4-最长连续序列)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
|
||||
@ -15,7 +17,7 @@
|
||||
[Leetcode](https://leetcode.com/problems/encode-and-decode-tinyurl/description/),利用 HashMap 就可以存储精简后的 url 到原始 url 的映射,使得不仅可以显示简化的 url,也可以根据简化的 url 得到原始 url 从而定位到正确的资源<E8B584>) / [力扣](https://leetcode-cn.com/problems/encode-and-decode-tinyurl/description/),利用 HashMap 就可以存储精简后的 url 到原始 url 的映射,使得不仅可以显示简化的 url,也可以根据简化的 url 得到原始 url 从而定位到正确的资源<E8B584>)
|
||||
|
||||
|
||||
# 1. 数组中两个数的和为给定值
|
||||
## 1. 数组中两个数的和为给定值
|
||||
|
||||
1\. Two Sum (Easy)
|
||||
|
||||
@ -39,7 +41,7 @@ public int[] twoSum(int[] nums, int target) {
|
||||
}
|
||||
```
|
||||
|
||||
# 2. 判断数组是否含有重复元素
|
||||
## 2. 判断数组是否含有重复元素
|
||||
|
||||
217\. Contains Duplicate (Easy)
|
||||
|
||||
@ -55,7 +57,7 @@ public boolean containsDuplicate(int[] nums) {
|
||||
}
|
||||
```
|
||||
|
||||
# 3. 最长和谐序列
|
||||
## 3. 最长和谐序列
|
||||
|
||||
594\. Longest Harmonious Subsequence (Easy)
|
||||
|
||||
@ -85,7 +87,7 @@ public int findLHS(int[] nums) {
|
||||
}
|
||||
```
|
||||
|
||||
# 4. 最长连续序列
|
||||
## 4. 最长连续序列
|
||||
|
||||
128\. Longest Consecutive Sequence (Hard)
|
||||
|
||||
@ -131,10 +133,3 @@ private int maxCount(Map<Integer, Integer> countForNum) {
|
||||
return max;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -1,19 +1,21 @@
|
||||
# Leetcode 题解 - 图
|
||||
<!-- GFM-TOC -->
|
||||
* [二分图](#二分图)
|
||||
* [1. 判断是否为二分图](#1-判断是否为二分图)
|
||||
* [拓扑排序](#拓扑排序)
|
||||
* [1. 课程安排的合法性](#1-课程安排的合法性)
|
||||
* [2. 课程安排的顺序](#2-课程安排的顺序)
|
||||
* [并查集](#并查集)
|
||||
* [1. 冗余连接](#1-冗余连接)
|
||||
* [Leetcode 题解 - 图](#leetcode-题解---图)
|
||||
* [二分图](#二分图)
|
||||
* [1. 判断是否为二分图](#1-判断是否为二分图)
|
||||
* [拓扑排序](#拓扑排序)
|
||||
* [1. 课程安排的合法性](#1-课程安排的合法性)
|
||||
* [2. 课程安排的顺序](#2-课程安排的顺序)
|
||||
* [并查集](#并查集)
|
||||
* [1. 冗余连接](#1-冗余连接)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
|
||||
# 二分图
|
||||
## 二分图
|
||||
|
||||
如果可以用两种颜色对图中的节点进行着色,并且保证相邻的节点颜色不同,那么这个图就是二分图。
|
||||
|
||||
## 1. 判断是否为二分图
|
||||
### 1. 判断是否为二分图
|
||||
|
||||
785\. Is Graph Bipartite? (Medium)
|
||||
|
||||
@ -70,11 +72,11 @@ private boolean isBipartite(int curNode, int curColor, int[] colors, int[][] gra
|
||||
}
|
||||
```
|
||||
|
||||
# 拓扑排序
|
||||
## 拓扑排序
|
||||
|
||||
常用于在具有先序关系的任务规划中。
|
||||
|
||||
## 1. 课程安排的合法性
|
||||
### 1. 课程安排的合法性
|
||||
|
||||
207\. Course Schedule (Medium)
|
||||
|
||||
@ -134,7 +136,7 @@ private boolean hasCycle(boolean[] globalMarked, boolean[] localMarked,
|
||||
}
|
||||
```
|
||||
|
||||
## 2. 课程安排的顺序
|
||||
### 2. 课程安排的顺序
|
||||
|
||||
210\. Course Schedule II (Medium)
|
||||
|
||||
@ -147,7 +149,7 @@ There are a total of 4 courses to take. To take course 3 you should have finishe
|
||||
|
||||
使用 DFS 来实现拓扑排序,使用一个栈存储后序遍历结果,这个栈的逆序结果就是拓扑排序结果。
|
||||
|
||||
证明:对于任何先序关系:v->w,后序遍历结果可以保证 w 先进入栈中,因此栈的逆序结果中 v 会在 w 之前。
|
||||
证明:对于任何先序关系:v-\>w,后序遍历结果可以保证 w 先进入栈中,因此栈的逆序结果中 v 会在 w 之前。
|
||||
|
||||
```java
|
||||
public int[] findOrder(int numCourses, int[][] prerequisites) {
|
||||
@ -195,11 +197,11 @@ private boolean hasCycle(boolean[] globalMarked, boolean[] localMarked, List<Int
|
||||
}
|
||||
```
|
||||
|
||||
# 并查集
|
||||
## 并查集
|
||||
|
||||
并查集可以动态地连通两个点,并且可以非常快速地判断两个点是否连通。
|
||||
|
||||
## 1. 冗余连接
|
||||
### 1. 冗余连接
|
||||
|
||||
684\. Redundant Connection (Medium)
|
||||
|
||||
@ -263,10 +265,3 @@ private class UF {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -1,17 +1,19 @@
|
||||
# Leetcode 题解 - 字符串
|
||||
<!-- GFM-TOC -->
|
||||
* [1. 字符串循环移位包含](#1-字符串循环移位包含)
|
||||
* [2. 字符串循环移位](#2-字符串循环移位)
|
||||
* [3. 字符串中单词的翻转](#3-字符串中单词的翻转)
|
||||
* [4. 两个字符串包含的字符是否完全相同](#4-两个字符串包含的字符是否完全相同)
|
||||
* [5. 计算一组字符集合可以组成的回文字符串的最大长度](#5-计算一组字符集合可以组成的回文字符串的最大长度)
|
||||
* [6. 字符串同构](#6-字符串同构)
|
||||
* [7. 回文子字符串个数](#7-回文子字符串个数)
|
||||
* [8. 判断一个整数是否是回文数](#8-判断一个整数是否是回文数)
|
||||
* [9. 统计二进制字符串中连续 1 和连续 0 数量相同的子字符串个数](#9-统计二进制字符串中连续-1-和连续-0-数量相同的子字符串个数)
|
||||
* [Leetcode 题解 - 字符串](#leetcode-题解---字符串)
|
||||
* [1. 字符串循环移位包含](#1-字符串循环移位包含)
|
||||
* [2. 字符串循环移位](#2-字符串循环移位)
|
||||
* [3. 字符串中单词的翻转](#3-字符串中单词的翻转)
|
||||
* [4. 两个字符串包含的字符是否完全相同](#4-两个字符串包含的字符是否完全相同)
|
||||
* [5. 计算一组字符集合可以组成的回文字符串的最大长度](#5-计算一组字符集合可以组成的回文字符串的最大长度)
|
||||
* [6. 字符串同构](#6-字符串同构)
|
||||
* [7. 回文子字符串个数](#7-回文子字符串个数)
|
||||
* [8. 判断一个整数是否是回文数](#8-判断一个整数是否是回文数)
|
||||
* [9. 统计二进制字符串中连续 1 和连续 0 数量相同的子字符串个数](#9-统计二进制字符串中连续-1-和连续-0-数量相同的子字符串个数)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
|
||||
# 1. 字符串循环移位包含
|
||||
## 1. 字符串循环移位包含
|
||||
|
||||
[编程之美 3.1](#)
|
||||
|
||||
@ -24,7 +26,7 @@ Return : true
|
||||
|
||||
s1 进行循环移位的结果是 s1s1 的子字符串,因此只要判断 s2 是否是 s1s1 的子字符串即可。
|
||||
|
||||
# 2. 字符串循环移位
|
||||
## 2. 字符串循环移位
|
||||
|
||||
[编程之美 2.17](#)
|
||||
|
||||
@ -37,7 +39,7 @@ Return "123abcd"
|
||||
|
||||
将 abcd123 中的 abcd 和 123 单独翻转,得到 dcba321,然后对整个字符串进行翻转,得到 123abcd。
|
||||
|
||||
# 3. 字符串中单词的翻转
|
||||
## 3. 字符串中单词的翻转
|
||||
|
||||
[程序员代码面试指南](#)
|
||||
|
||||
@ -48,7 +50,7 @@ Return "student a am I"
|
||||
|
||||
将每个单词翻转,然后将整个字符串翻转。
|
||||
|
||||
# 4. 两个字符串包含的字符是否完全相同
|
||||
## 4. 两个字符串包含的字符是否完全相同
|
||||
|
||||
242\. Valid Anagram (Easy)
|
||||
|
||||
@ -81,7 +83,7 @@ public boolean isAnagram(String s, String t) {
|
||||
}
|
||||
```
|
||||
|
||||
# 5. 计算一组字符集合可以组成的回文字符串的最大长度
|
||||
## 5. 计算一组字符集合可以组成的回文字符串的最大长度
|
||||
|
||||
409\. Longest Palindrome (Easy)
|
||||
|
||||
@ -114,7 +116,7 @@ public int longestPalindrome(String s) {
|
||||
}
|
||||
```
|
||||
|
||||
# 6. 字符串同构
|
||||
## 6. 字符串同构
|
||||
|
||||
205\. Isomorphic Strings (Easy)
|
||||
|
||||
@ -144,7 +146,7 @@ public boolean isIsomorphic(String s, String t) {
|
||||
}
|
||||
```
|
||||
|
||||
# 7. 回文子字符串个数
|
||||
## 7. 回文子字符串个数
|
||||
|
||||
647\. Palindromic Substrings (Medium)
|
||||
|
||||
@ -178,7 +180,7 @@ private void extendSubstrings(String s, int start, int end) {
|
||||
}
|
||||
```
|
||||
|
||||
# 8. 判断一个整数是否是回文数
|
||||
## 8. 判断一个整数是否是回文数
|
||||
|
||||
9\. Palindrome Number (Easy)
|
||||
|
||||
@ -205,7 +207,7 @@ public boolean isPalindrome(int x) {
|
||||
}
|
||||
```
|
||||
|
||||
# 9. 统计二进制字符串中连续 1 和连续 0 数量相同的子字符串个数
|
||||
## 9. 统计二进制字符串中连续 1 和连续 0 数量相同的子字符串个数
|
||||
|
||||
696\. Count Binary Substrings (Easy)
|
||||
|
||||
@ -235,10 +237,3 @@ public int countBinarySubstrings(String s) {
|
||||
return count;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -1,22 +1,24 @@
|
||||
# Leetcode 题解 - 排序
|
||||
<!-- GFM-TOC -->
|
||||
* [快速选择](#快速选择)
|
||||
* [堆](#堆)
|
||||
* [1. Kth Element](#1-kth-element)
|
||||
* [桶排序](#桶排序)
|
||||
* [1. 出现频率最多的 k 个元素](#1-出现频率最多的-k-个元素)
|
||||
* [2. 按照字符出现次数对字符串排序](#2-按照字符出现次数对字符串排序)
|
||||
* [荷兰国旗问题](#荷兰国旗问题)
|
||||
* [1. 按颜色进行排序](#1-按颜色进行排序)
|
||||
* [Leetcode 题解 - 排序](#leetcode-题解---排序)
|
||||
* [快速选择](#快速选择)
|
||||
* [堆](#堆)
|
||||
* [1. Kth Element](#1-kth-element)
|
||||
* [桶排序](#桶排序)
|
||||
* [1. 出现频率最多的 k 个元素](#1-出现频率最多的-k-个元素)
|
||||
* [2. 按照字符出现次数对字符串排序](#2-按照字符出现次数对字符串排序)
|
||||
* [荷兰国旗问题](#荷兰国旗问题)
|
||||
* [1. 按颜色进行排序](#1-按颜色进行排序)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
|
||||
# 快速选择
|
||||
## 快速选择
|
||||
|
||||
用于求解 **Kth Element** 问题,也就是第 K 个元素的问题。
|
||||
|
||||
可以使用快速排序的 partition() 进行实现。需要先打乱数组,否则最坏情况下时间复杂度为 O(N<sup>2</sup>)。
|
||||
|
||||
# 堆
|
||||
## 堆
|
||||
|
||||
用于求解 **TopK Elements** 问题,也就是 K 个最小元素的问题。使用最小堆来实现 TopK 问题,最小堆使用大顶堆来实现,大顶堆的堆顶元素为当前堆的最大元素。实现过程:不断地往大顶堆中插入新元素,当堆中元素的数量大于 k 时,移除堆顶元素,也就是当前堆中最大的元素,剩下的元素都为当前添加过的元素中最小的 K 个元素。插入和移除堆顶元素的时间复杂度都为 log<sub>2</sub>N。
|
||||
|
||||
@ -26,7 +28,7 @@
|
||||
|
||||
可以看到,快速选择和堆排序都可以求解 Kth Element 和 TopK Elements 问题。
|
||||
|
||||
## 1. Kth Element
|
||||
### 1. Kth Element
|
||||
|
||||
215\. Kth Largest Element in an Array (Medium)
|
||||
|
||||
@ -102,9 +104,9 @@ private void swap(int[] a, int i, int j) {
|
||||
}
|
||||
```
|
||||
|
||||
# 桶排序
|
||||
## 桶排序
|
||||
|
||||
## 1. 出现频率最多的 k 个元素
|
||||
### 1. 出现频率最多的 k 个元素
|
||||
|
||||
347\. Top K Frequent Elements (Medium)
|
||||
|
||||
@ -151,7 +153,7 @@ public int[] topKFrequent(int[] nums, int k) {
|
||||
}
|
||||
```
|
||||
|
||||
## 2. 按照字符出现次数对字符串排序
|
||||
### 2. 按照字符出现次数对字符串排序
|
||||
|
||||
451\. Sort Characters By Frequency (Medium)
|
||||
|
||||
@ -198,7 +200,7 @@ public String frequencySort(String s) {
|
||||
}
|
||||
```
|
||||
|
||||
# 荷兰国旗问题
|
||||
## 荷兰国旗问题
|
||||
|
||||
荷兰国旗包含三种颜色:红、白、蓝。
|
||||
|
||||
@ -207,7 +209,7 @@ public String frequencySort(String s) {
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/7a3215ec-6fb7-4935-8b0d-cb408208f7cb.png"/> </div><br>
|
||||
|
||||
|
||||
## 1. 按颜色进行排序
|
||||
### 1. 按颜色进行排序
|
||||
|
||||
75\. Sort Colors (Medium)
|
||||
|
||||
@ -240,10 +242,3 @@ private void swap(int[] nums, int i, int j) {
|
||||
nums[j] = t;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -1,36 +1,38 @@
|
||||
# Leetcode 题解 - 搜索
|
||||
<!-- GFM-TOC -->
|
||||
* [BFS](#bfs)
|
||||
* [1. 计算在网格中从原点到特定点的最短路径长度](#1-计算在网格中从原点到特定点的最短路径长度)
|
||||
* [2. 组成整数的最小平方数数量](#2-组成整数的最小平方数数量)
|
||||
* [3. 最短单词路径](#3-最短单词路径)
|
||||
* [DFS](#dfs)
|
||||
* [1. 查找最大的连通面积](#1-查找最大的连通面积)
|
||||
* [2. 矩阵中的连通分量数目](#2-矩阵中的连通分量数目)
|
||||
* [3. 好友关系的连通分量数目](#3-好友关系的连通分量数目)
|
||||
* [4. 填充封闭区域](#4-填充封闭区域)
|
||||
* [5. 能到达的太平洋和大西洋的区域](#5-能到达的太平洋和大西洋的区域)
|
||||
* [Backtracking](#backtracking)
|
||||
* [1. 数字键盘组合](#1-数字键盘组合)
|
||||
* [2. IP 地址划分](#2-ip-地址划分)
|
||||
* [3. 在矩阵中寻找字符串](#3-在矩阵中寻找字符串)
|
||||
* [4. 输出二叉树中所有从根到叶子的路径](#4-输出二叉树中所有从根到叶子的路径)
|
||||
* [5. 排列](#5-排列)
|
||||
* [6. 含有相同元素求排列](#6-含有相同元素求排列)
|
||||
* [7. 组合](#7-组合)
|
||||
* [8. 组合求和](#8-组合求和)
|
||||
* [9. 含有相同元素的组合求和](#9-含有相同元素的组合求和)
|
||||
* [10. 1-9 数字的组合求和](#10-1-9-数字的组合求和)
|
||||
* [11. 子集](#11-子集)
|
||||
* [12. 含有相同元素求子集](#12-含有相同元素求子集)
|
||||
* [13. 分割字符串使得每个部分都是回文数](#13-分割字符串使得每个部分都是回文数)
|
||||
* [14. 数独](#14-数独)
|
||||
* [15. N 皇后](#15-n-皇后)
|
||||
* [Leetcode 题解 - 搜索](#leetcode-题解---搜索)
|
||||
* [BFS](#bfs)
|
||||
* [1. 计算在网格中从原点到特定点的最短路径长度](#1-计算在网格中从原点到特定点的最短路径长度)
|
||||
* [2. 组成整数的最小平方数数量](#2-组成整数的最小平方数数量)
|
||||
* [3. 最短单词路径](#3-最短单词路径)
|
||||
* [DFS](#dfs)
|
||||
* [1. 查找最大的连通面积](#1-查找最大的连通面积)
|
||||
* [2. 矩阵中的连通分量数目](#2-矩阵中的连通分量数目)
|
||||
* [3. 好友关系的连通分量数目](#3-好友关系的连通分量数目)
|
||||
* [4. 填充封闭区域](#4-填充封闭区域)
|
||||
* [5. 能到达的太平洋和大西洋的区域](#5-能到达的太平洋和大西洋的区域)
|
||||
* [Backtracking](#backtracking)
|
||||
* [1. 数字键盘组合](#1-数字键盘组合)
|
||||
* [2. IP 地址划分](#2-ip-地址划分)
|
||||
* [3. 在矩阵中寻找字符串](#3-在矩阵中寻找字符串)
|
||||
* [4. 输出二叉树中所有从根到叶子的路径](#4-输出二叉树中所有从根到叶子的路径)
|
||||
* [5. 排列](#5-排列)
|
||||
* [6. 含有相同元素求排列](#6-含有相同元素求排列)
|
||||
* [7. 组合](#7-组合)
|
||||
* [8. 组合求和](#8-组合求和)
|
||||
* [9. 含有相同元素的组合求和](#9-含有相同元素的组合求和)
|
||||
* [10. 1-9 数字的组合求和](#10-1-9-数字的组合求和)
|
||||
* [11. 子集](#11-子集)
|
||||
* [12. 含有相同元素求子集](#12-含有相同元素求子集)
|
||||
* [13. 分割字符串使得每个部分都是回文数](#13-分割字符串使得每个部分都是回文数)
|
||||
* [14. 数独](#14-数独)
|
||||
* [15. N 皇后](#15-n-皇后)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
|
||||
深度优先搜索和广度优先搜索广泛运用于树和图中,但是它们的应用远远不止如此。
|
||||
|
||||
# BFS
|
||||
## BFS
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/95903878-725b-4ed9-bded-bc4aae0792a9.jpg"/> </div><br>
|
||||
|
||||
@ -38,19 +40,19 @@
|
||||
|
||||
第一层:
|
||||
|
||||
- 0 -> {6,2,1,5}
|
||||
- 0 -\> {6,2,1,5}
|
||||
|
||||
第二层:
|
||||
|
||||
- 6 -> {4}
|
||||
- 2 -> {}
|
||||
- 1 -> {}
|
||||
- 5 -> {3}
|
||||
- 6 -\> {4}
|
||||
- 2 -\> {}
|
||||
- 1 -\> {}
|
||||
- 5 -\> {3}
|
||||
|
||||
第三层:
|
||||
|
||||
- 4 -> {}
|
||||
- 3 -> {}
|
||||
- 4 -\> {}
|
||||
- 3 -\> {}
|
||||
|
||||
每一层遍历的节点都与根节点距离相同。设 d<sub>i</sub> 表示第 i 个节点与根节点的距离,推导出一个结论:对于先遍历的节点 i 与后遍历的节点 j,有 d<sub>i</sub> <= d<sub>j</sub>。利用这个结论,可以求解最短路径等 **最优解** 问题:第一次遍历到目的节点,其所经过的路径为最短路径。应该注意的是,使用 BFS 只能求解无权图的最短路径,无权图是指从一个节点到另一个节点的代价都记为 1。
|
||||
|
||||
@ -59,7 +61,7 @@
|
||||
- 队列:用来存储每一轮遍历得到的节点;
|
||||
- 标记:对于遍历过的节点,应该将它标记,防止重复遍历。
|
||||
|
||||
## 1. 计算在网格中从原点到特定点的最短路径长度
|
||||
### 1. 计算在网格中从原点到特定点的最短路径长度
|
||||
|
||||
1091\. Shortest Path in Binary Matrix(Medium)
|
||||
|
||||
@ -110,7 +112,7 @@ public int shortestPathBinaryMatrix(int[][] grids) {
|
||||
}
|
||||
```
|
||||
|
||||
## 2. 组成整数的最小平方数数量
|
||||
### 2. 组成整数的最小平方数数量
|
||||
|
||||
279\. Perfect Squares (Medium)
|
||||
|
||||
@ -175,7 +177,7 @@ private List<Integer> generateSquares(int n) {
|
||||
}
|
||||
```
|
||||
|
||||
## 3. 最短单词路径
|
||||
### 3. 最短单词路径
|
||||
|
||||
127\. Word Ladder (Medium)
|
||||
|
||||
@ -273,7 +275,7 @@ private int getShortestPath(List<Integer>[] graphic, int start, int end) {
|
||||
}
|
||||
```
|
||||
|
||||
# DFS
|
||||
## DFS
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/74dc31eb-6baa-47ea-ab1c-d27a0ca35093.png"/> </div><br>
|
||||
|
||||
@ -288,7 +290,7 @@ private int getShortestPath(List<Integer>[] graphic, int start, int end) {
|
||||
- 栈:用栈来保存当前节点信息,当遍历新节点返回时能够继续遍历当前节点。可以使用递归栈。
|
||||
- 标记:和 BFS 一样同样需要对已经遍历过的节点进行标记。
|
||||
|
||||
## 1. 查找最大的连通面积
|
||||
### 1. 查找最大的连通面积
|
||||
|
||||
695\. Max Area of Island (Medium)
|
||||
|
||||
@ -337,7 +339,7 @@ private int dfs(int[][] grid, int r, int c) {
|
||||
}
|
||||
```
|
||||
|
||||
## 2. 矩阵中的连通分量数目
|
||||
### 2. 矩阵中的连通分量数目
|
||||
|
||||
200\. Number of Islands (Medium)
|
||||
|
||||
@ -388,7 +390,7 @@ private void dfs(char[][] grid, int i, int j) {
|
||||
}
|
||||
```
|
||||
|
||||
## 3. 好友关系的连通分量数目
|
||||
### 3. 好友关系的连通分量数目
|
||||
|
||||
547\. Friend Circles (Medium)
|
||||
|
||||
@ -434,7 +436,7 @@ private void dfs(int[][] M, int i, boolean[] hasVisited) {
|
||||
}
|
||||
```
|
||||
|
||||
## 4. 填充封闭区域
|
||||
### 4. 填充封闭区域
|
||||
|
||||
130\. Surrounded Regions (Medium)
|
||||
|
||||
@ -501,7 +503,7 @@ private void dfs(char[][] board, int r, int c) {
|
||||
}
|
||||
```
|
||||
|
||||
## 5. 能到达的太平洋和大西洋的区域
|
||||
### 5. 能到达的太平洋和大西洋的区域
|
||||
|
||||
417\. Pacific Atlantic Water Flow (Medium)
|
||||
|
||||
@ -579,7 +581,7 @@ private void dfs(int r, int c, boolean[][] canReach) {
|
||||
}
|
||||
```
|
||||
|
||||
# Backtracking
|
||||
## Backtracking
|
||||
|
||||
Backtracking(回溯)属于 DFS。
|
||||
|
||||
@ -591,7 +593,7 @@ Backtracking(回溯)属于 DFS。
|
||||
- 在访问一个新元素进入新的递归调用时,需要将新元素标记为已经访问,这样才能在继续递归调用时不用重复访问该元素;
|
||||
- 但是在递归返回时,需要将元素标记为未访问,因为只需要保证在一个递归链中不同时访问一个元素,可以访问已经访问过但是不在当前递归链中的元素。
|
||||
|
||||
## 1. 数字键盘组合
|
||||
### 1. 数字键盘组合
|
||||
|
||||
17\. Letter Combinations of a Phone Number (Medium)
|
||||
|
||||
@ -631,7 +633,7 @@ private void doCombination(StringBuilder prefix, List<String> combinations, fina
|
||||
}
|
||||
```
|
||||
|
||||
## 2. IP 地址划分
|
||||
### 2. IP 地址划分
|
||||
|
||||
93\. Restore IP Addresses(Medium)
|
||||
|
||||
@ -674,7 +676,7 @@ private void doRestore(int k, StringBuilder tempAddress, List<String> addresses,
|
||||
}
|
||||
```
|
||||
|
||||
## 3. 在矩阵中寻找字符串
|
||||
### 3. 在矩阵中寻找字符串
|
||||
|
||||
79\. Word Search (Medium)
|
||||
|
||||
@ -745,7 +747,7 @@ private boolean backtracking(int curLen, int r, int c, boolean[][] visited, fina
|
||||
}
|
||||
```
|
||||
|
||||
## 4. 输出二叉树中所有从根到叶子的路径
|
||||
### 4. 输出二叉树中所有从根到叶子的路径
|
||||
|
||||
257\. Binary Tree Paths (Easy)
|
||||
|
||||
@ -805,7 +807,7 @@ private String buildPath(List<Integer> values) {
|
||||
}
|
||||
```
|
||||
|
||||
## 5. 排列
|
||||
### 5. 排列
|
||||
|
||||
46\. Permutations (Medium)
|
||||
|
||||
@ -850,7 +852,7 @@ private void backtracking(List<Integer> permuteList, List<List<Integer>> permute
|
||||
}
|
||||
```
|
||||
|
||||
## 6. 含有相同元素求排列
|
||||
### 6. 含有相同元素求排列
|
||||
|
||||
47\. Permutations II (Medium)
|
||||
|
||||
@ -897,7 +899,7 @@ private void backtracking(List<Integer> permuteList, List<List<Integer>> permute
|
||||
}
|
||||
```
|
||||
|
||||
## 7. 组合
|
||||
### 7. 组合
|
||||
|
||||
77\. Combinations (Medium)
|
||||
|
||||
@ -936,7 +938,7 @@ private void backtracking(List<Integer> combineList, List<List<Integer>> combina
|
||||
}
|
||||
```
|
||||
|
||||
## 8. 组合求和
|
||||
### 8. 组合求和
|
||||
|
||||
39\. Combination Sum (Medium)
|
||||
|
||||
@ -972,7 +974,7 @@ private void backtracking(List<Integer> tempCombination, List<List<Integer>> com
|
||||
}
|
||||
```
|
||||
|
||||
## 9. 含有相同元素的组合求和
|
||||
### 9. 含有相同元素的组合求和
|
||||
|
||||
40\. Combination Sum II (Medium)
|
||||
|
||||
@ -1019,7 +1021,7 @@ private void backtracking(List<Integer> tempCombination, List<List<Integer>> com
|
||||
}
|
||||
```
|
||||
|
||||
## 10. 1-9 数字的组合求和
|
||||
### 10. 1-9 数字的组合求和
|
||||
|
||||
216\. Combination Sum III (Medium)
|
||||
|
||||
@ -1061,7 +1063,7 @@ private void backtracking(int k, int n, int start,
|
||||
}
|
||||
```
|
||||
|
||||
## 11. 子集
|
||||
### 11. 子集
|
||||
|
||||
78\. Subsets (Medium)
|
||||
|
||||
@ -1094,7 +1096,7 @@ private void backtracking(int start, List<Integer> tempSubset, List<List<Integer
|
||||
}
|
||||
```
|
||||
|
||||
## 12. 含有相同元素求子集
|
||||
### 12. 含有相同元素求子集
|
||||
|
||||
90\. Subsets II (Medium)
|
||||
|
||||
@ -1146,7 +1148,7 @@ private void backtracking(int start, List<Integer> tempSubset, List<List<Integer
|
||||
}
|
||||
```
|
||||
|
||||
## 13. 分割字符串使得每个部分都是回文数
|
||||
### 13. 分割字符串使得每个部分都是回文数
|
||||
|
||||
131\. Palindrome Partitioning (Medium)
|
||||
|
||||
@ -1194,7 +1196,7 @@ private boolean isPalindrome(String s, int begin, int end) {
|
||||
}
|
||||
```
|
||||
|
||||
## 14. 数独
|
||||
### 14. 数独
|
||||
|
||||
37\. Sudoku Solver (Hard)
|
||||
|
||||
@ -1253,7 +1255,7 @@ private int cubeNum(int i, int j) {
|
||||
}
|
||||
```
|
||||
|
||||
## 15. N 皇后
|
||||
### 15. N 皇后
|
||||
|
||||
51\. N-Queens (Hard)
|
||||
|
||||
@ -1320,10 +1322,3 @@ private void backtracking(int row) {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -1,50 +1,52 @@
|
||||
# Leetcode 题解 - 数学
|
||||
<!-- GFM-TOC -->
|
||||
* [素数分解](#素数分解)
|
||||
* [整除](#整除)
|
||||
* [最大公约数最小公倍数](#最大公约数最小公倍数)
|
||||
* [1. 生成素数序列](#1-生成素数序列)
|
||||
* [2. 最大公约数](#2-最大公约数)
|
||||
* [3. 使用位操作和减法求解最大公约数](#3-使用位操作和减法求解最大公约数)
|
||||
* [进制转换](#进制转换)
|
||||
* [1. 7 进制](#1-7-进制)
|
||||
* [2. 16 进制](#2-16-进制)
|
||||
* [3. 26 进制](#3-26-进制)
|
||||
* [阶乘](#阶乘)
|
||||
* [1. 统计阶乘尾部有多少个 0](#1-统计阶乘尾部有多少个-0)
|
||||
* [字符串加法减法](#字符串加法减法)
|
||||
* [1. 二进制加法](#1-二进制加法)
|
||||
* [2. 字符串加法](#2-字符串加法)
|
||||
* [相遇问题](#相遇问题)
|
||||
* [1. 改变数组元素使所有的数组元素都相等](#1-改变数组元素使所有的数组元素都相等)
|
||||
* [多数投票问题](#多数投票问题)
|
||||
* [1. 数组中出现次数多于 n / 2 的元素](#1-数组中出现次数多于-n--2-的元素)
|
||||
* [其它](#其它)
|
||||
* [1. 平方数](#1-平方数)
|
||||
* [2. 3 的 n 次方](#2-3-的-n-次方)
|
||||
* [3. 乘积数组](#3-乘积数组)
|
||||
* [4. 找出数组中的乘积最大的三个数](#4-找出数组中的乘积最大的三个数)
|
||||
* [Leetcode 题解 - 数学](#leetcode-题解---数学)
|
||||
* [素数分解](#素数分解)
|
||||
* [整除](#整除)
|
||||
* [最大公约数最小公倍数](#最大公约数最小公倍数)
|
||||
* [1. 生成素数序列](#1-生成素数序列)
|
||||
* [2. 最大公约数](#2-最大公约数)
|
||||
* [3. 使用位操作和减法求解最大公约数](#3-使用位操作和减法求解最大公约数)
|
||||
* [进制转换](#进制转换)
|
||||
* [1. 7 进制](#1-7-进制)
|
||||
* [2. 16 进制](#2-16-进制)
|
||||
* [3. 26 进制](#3-26-进制)
|
||||
* [阶乘](#阶乘)
|
||||
* [1. 统计阶乘尾部有多少个 0](#1-统计阶乘尾部有多少个-0)
|
||||
* [字符串加法减法](#字符串加法减法)
|
||||
* [1. 二进制加法](#1-二进制加法)
|
||||
* [2. 字符串加法](#2-字符串加法)
|
||||
* [相遇问题](#相遇问题)
|
||||
* [1. 改变数组元素使所有的数组元素都相等](#1-改变数组元素使所有的数组元素都相等)
|
||||
* [多数投票问题](#多数投票问题)
|
||||
* [1. 数组中出现次数多于 n / 2 的元素](#1-数组中出现次数多于-n--2-的元素)
|
||||
* [其它](#其它)
|
||||
* [1. 平方数](#1-平方数)
|
||||
* [2. 3 的 n 次方](#2-3-的-n-次方)
|
||||
* [3. 乘积数组](#3-乘积数组)
|
||||
* [4. 找出数组中的乘积最大的三个数](#4-找出数组中的乘积最大的三个数)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
|
||||
# 素数分解
|
||||
## 素数分解
|
||||
|
||||
每一个数都可以分解成素数的乘积,例如 84 = 2<sup>2</sup> \* 3<sup>1</sup> \* 5<sup>0</sup> \* 7<sup>1</sup> \* 11<sup>0</sup> \* 13<sup>0</sup> \* 17<sup>0</sup> \* …
|
||||
|
||||
# 整除
|
||||
## 整除
|
||||
|
||||
令 x = 2<sup>m0</sup> \* 3<sup>m1</sup> \* 5<sup>m2</sup> \* 7<sup>m3</sup> \* 11<sup>m4</sup> \* …
|
||||
|
||||
令 y = 2<sup>n0</sup> \* 3<sup>n1</sup> \* 5<sup>n2</sup> \* 7<sup>n3</sup> \* 11<sup>n4</sup> \* …
|
||||
|
||||
如果 x 整除 y(y mod x == 0),则对于所有 i,mi <= ni。
|
||||
如果 x 整除 y(y mod x == 0),则对于所有 i,mi \<= ni。
|
||||
|
||||
# 最大公约数最小公倍数
|
||||
## 最大公约数最小公倍数
|
||||
|
||||
x 和 y 的最大公约数为:gcd(x,y) = 2<sup>min(m0,n0)</sup> \* 3<sup>min(m1,n1)</sup> \* 5<sup>min(m2,n2)</sup> \* ...
|
||||
|
||||
x 和 y 的最小公倍数为:lcm(x,y) = 2<sup>max(m0,n0)</sup> \* 3<sup>max(m1,n1)</sup> \* 5<sup>max(m2,n2)</sup> \* ...
|
||||
|
||||
## 1. 生成素数序列
|
||||
### 1. 生成素数序列
|
||||
|
||||
204\. Count Primes (Easy)
|
||||
|
||||
@ -70,7 +72,7 @@ public int countPrimes(int n) {
|
||||
}
|
||||
```
|
||||
|
||||
## 2. 最大公约数
|
||||
### 2. 最大公约数
|
||||
|
||||
```java
|
||||
int gcd(int a, int b) {
|
||||
@ -86,7 +88,7 @@ int lcm(int a, int b) {
|
||||
}
|
||||
```
|
||||
|
||||
## 3. 使用位操作和减法求解最大公约数
|
||||
### 3. 使用位操作和减法求解最大公约数
|
||||
|
||||
[编程之美:2.7](#)
|
||||
|
||||
@ -120,9 +122,9 @@ public int gcd(int a, int b) {
|
||||
}
|
||||
```
|
||||
|
||||
# 进制转换
|
||||
## 进制转换
|
||||
|
||||
## 1. 7 进制
|
||||
### 1. 7 进制
|
||||
|
||||
504\. Base 7 (Easy)
|
||||
|
||||
@ -155,7 +157,7 @@ public String convertToBase7(int num) {
|
||||
}
|
||||
```
|
||||
|
||||
## 2. 16 进制
|
||||
### 2. 16 进制
|
||||
|
||||
405\. Convert a Number to Hexadecimal (Easy)
|
||||
|
||||
@ -190,7 +192,7 @@ public String toHex(int num) {
|
||||
}
|
||||
```
|
||||
|
||||
## 3. 26 进制
|
||||
### 3. 26 进制
|
||||
|
||||
168\. Excel Sheet Column Title (Easy)
|
||||
|
||||
@ -218,9 +220,9 @@ public String convertToTitle(int n) {
|
||||
}
|
||||
```
|
||||
|
||||
# 阶乘
|
||||
## 阶乘
|
||||
|
||||
## 1. 统计阶乘尾部有多少个 0
|
||||
### 1. 统计阶乘尾部有多少个 0
|
||||
|
||||
172\. Factorial Trailing Zeroes (Easy)
|
||||
|
||||
@ -238,9 +240,9 @@ public int trailingZeroes(int n) {
|
||||
|
||||
如果统计的是 N! 的二进制表示中最低位 1 的位置,只要统计有多少个 2 即可,该题目出自 [编程之美:2.2](#) 。和求解有多少个 5 一样,2 的个数为 N/2 + N/2<sup>2</sup> + N/2<sup>3</sup> + ...
|
||||
|
||||
# 字符串加法减法
|
||||
## 字符串加法减法
|
||||
|
||||
## 1. 二进制加法
|
||||
### 1. 二进制加法
|
||||
|
||||
67\. Add Binary (Easy)
|
||||
|
||||
@ -270,7 +272,7 @@ public String addBinary(String a, String b) {
|
||||
}
|
||||
```
|
||||
|
||||
## 2. 字符串加法
|
||||
### 2. 字符串加法
|
||||
|
||||
415\. Add Strings (Easy)
|
||||
|
||||
@ -292,9 +294,9 @@ public String addStrings(String num1, String num2) {
|
||||
}
|
||||
```
|
||||
|
||||
# 相遇问题
|
||||
## 相遇问题
|
||||
|
||||
## 1. 改变数组元素使所有的数组元素都相等
|
||||
### 1. 改变数组元素使所有的数组元素都相等
|
||||
|
||||
462\. Minimum Moves to Equal Array Elements II (Medium)
|
||||
|
||||
@ -317,7 +319,7 @@ Only two moves are needed (remember each move increments or decrements one eleme
|
||||
|
||||
这是个典型的相遇问题,移动距离最小的方式是所有元素都移动到中位数。理由如下:
|
||||
|
||||
设 m 为中位数。a 和 b 是 m 两边的两个元素,且 b > a。要使 a 和 b 相等,它们总共移动的次数为 b - a,这个值等于 (b - m) + (m - a),也就是把这两个数移动到中位数的移动次数。
|
||||
设 m 为中位数。a 和 b 是 m 两边的两个元素,且 b \> a。要使 a 和 b 相等,它们总共移动的次数为 b - a,这个值等于 (b - m) + (m - a),也就是把这两个数移动到中位数的移动次数。
|
||||
|
||||
设数组长度为 N,则可以找到 N/2 对 a 和 b 的组合,使它们都移动到 m 的位置。
|
||||
|
||||
@ -390,9 +392,9 @@ private void swap(int[] nums, int i, int j) {
|
||||
}
|
||||
```
|
||||
|
||||
# 多数投票问题
|
||||
## 多数投票问题
|
||||
|
||||
## 1. 数组中出现次数多于 n / 2 的元素
|
||||
### 1. 数组中出现次数多于 n / 2 的元素
|
||||
|
||||
169\. Majority Element (Easy)
|
||||
|
||||
@ -420,9 +422,9 @@ public int majorityElement(int[] nums) {
|
||||
}
|
||||
```
|
||||
|
||||
# 其它
|
||||
## 其它
|
||||
|
||||
## 1. 平方数
|
||||
### 1. 平方数
|
||||
|
||||
367\. Valid Perfect Square (Easy)
|
||||
|
||||
@ -450,7 +452,7 @@ public boolean isPerfectSquare(int num) {
|
||||
}
|
||||
```
|
||||
|
||||
## 2. 3 的 n 次方
|
||||
### 2. 3 的 n 次方
|
||||
|
||||
326\. Power of Three (Easy)
|
||||
|
||||
@ -462,7 +464,7 @@ public boolean isPowerOfThree(int n) {
|
||||
}
|
||||
```
|
||||
|
||||
## 3. 乘积数组
|
||||
### 3. 乘积数组
|
||||
|
||||
238\. Product of Array Except Self (Medium)
|
||||
|
||||
@ -495,7 +497,7 @@ public int[] productExceptSelf(int[] nums) {
|
||||
}
|
||||
```
|
||||
|
||||
## 4. 找出数组中的乘积最大的三个数
|
||||
### 4. 找出数组中的乘积最大的三个数
|
||||
|
||||
628\. Maximum Product of Three Numbers (Easy)
|
||||
|
||||
@ -531,10 +533,3 @@ public int maximumProduct(int[] nums) {
|
||||
return Math.max(max1*max2*max3, max1*min1*min2);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -1,20 +1,22 @@
|
||||
# Leetcode 题解 - 数组与矩阵
|
||||
<!-- GFM-TOC -->
|
||||
* [1. 把数组中的 0 移到末尾](#1-把数组中的-0-移到末尾)
|
||||
* [2. 改变矩阵维度](#2-改变矩阵维度)
|
||||
* [3. 找出数组中最长的连续 1](#3-找出数组中最长的连续-1)
|
||||
* [4. 有序矩阵查找](#4-有序矩阵查找)
|
||||
* [5. 有序矩阵的 Kth Element](#5-有序矩阵的-kth-element)
|
||||
* [6. 一个数组元素在 [1, n] 之间,其中一个数被替换为另一个数,找出重复的数和丢失的数](#6-一个数组元素在-[1-n]-之间,其中一个数被替换为另一个数,找出重复的数和丢失的数)
|
||||
* [7. 找出数组中重复的数,数组值在 [1, n] 之间](#7-找出数组中重复的数,数组值在-[1-n]-之间)
|
||||
* [8. 数组相邻差值的个数](#8-数组相邻差值的个数)
|
||||
* [9. 数组的度](#9-数组的度)
|
||||
* [10. 对角元素相等的矩阵](#10-对角元素相等的矩阵)
|
||||
* [11. 嵌套数组](#11-嵌套数组)
|
||||
* [12. 分隔数组](#12-分隔数组)
|
||||
* [Leetcode 题解 - 数组与矩阵](#leetcode-题解---数组与矩阵)
|
||||
* [1. 把数组中的 0 移到末尾](#1-把数组中的-0-移到末尾)
|
||||
* [2. 改变矩阵维度](#2-改变矩阵维度)
|
||||
* [3. 找出数组中最长的连续 1](#3-找出数组中最长的连续-1)
|
||||
* [4. 有序矩阵查找](#4-有序矩阵查找)
|
||||
* [5. 有序矩阵的 Kth Element](#5-有序矩阵的-kth-element)
|
||||
* [6. 一个数组元素在 [1, n] 之间,其中一个数被替换为另一个数,找出重复的数和丢失的数](#6-一个数组元素在-[1-n]-之间,其中一个数被替换为另一个数,找出重复的数和丢失的数)
|
||||
* [7. 找出数组中重复的数,数组值在 [1, n] 之间](#7-找出数组中重复的数,数组值在-[1-n]-之间)
|
||||
* [8. 数组相邻差值的个数](#8-数组相邻差值的个数)
|
||||
* [9. 数组的度](#9-数组的度)
|
||||
* [10. 对角元素相等的矩阵](#10-对角元素相等的矩阵)
|
||||
* [11. 嵌套数组](#11-嵌套数组)
|
||||
* [12. 分隔数组](#12-分隔数组)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
|
||||
# 1. 把数组中的 0 移到末尾
|
||||
## 1. 把数组中的 0 移到末尾
|
||||
|
||||
283\. Move Zeroes (Easy)
|
||||
|
||||
@ -38,7 +40,7 @@ public void moveZeroes(int[] nums) {
|
||||
}
|
||||
```
|
||||
|
||||
# 2. 改变矩阵维度
|
||||
## 2. 改变矩阵维度
|
||||
|
||||
566\. Reshape the Matrix (Easy)
|
||||
|
||||
@ -76,7 +78,7 @@ public int[][] matrixReshape(int[][] nums, int r, int c) {
|
||||
}
|
||||
```
|
||||
|
||||
# 3. 找出数组中最长的连续 1
|
||||
## 3. 找出数组中最长的连续 1
|
||||
|
||||
485\. Max Consecutive Ones (Easy)
|
||||
|
||||
@ -93,7 +95,7 @@ public int findMaxConsecutiveOnes(int[] nums) {
|
||||
}
|
||||
```
|
||||
|
||||
# 4. 有序矩阵查找
|
||||
## 4. 有序矩阵查找
|
||||
|
||||
240\. Search a 2D Matrix II (Medium)
|
||||
|
||||
@ -121,7 +123,7 @@ public boolean searchMatrix(int[][] matrix, int target) {
|
||||
}
|
||||
```
|
||||
|
||||
# 5. 有序矩阵的 Kth Element
|
||||
## 5. 有序矩阵的 Kth Element
|
||||
|
||||
378\. Kth Smallest Element in a Sorted Matrix ((Medium))
|
||||
|
||||
@ -189,7 +191,7 @@ class Tuple implements Comparable<Tuple> {
|
||||
}
|
||||
```
|
||||
|
||||
# 6. 一个数组元素在 [1, n] 之间,其中一个数被替换为另一个数,找出重复的数和丢失的数
|
||||
## 6. 一个数组元素在 [1, n] 之间,其中一个数被替换为另一个数,找出重复的数和丢失的数
|
||||
|
||||
645\. Set Mismatch (Easy)
|
||||
|
||||
@ -231,7 +233,7 @@ private void swap(int[] nums, int i, int j) {
|
||||
}
|
||||
```
|
||||
|
||||
# 7. 找出数组中重复的数,数组值在 [1, n] 之间
|
||||
## 7. 找出数组中重复的数,数组值在 [1, n] 之间
|
||||
|
||||
287\. Find the Duplicate Number (Medium)
|
||||
|
||||
@ -275,7 +277,7 @@ public int findDuplicate(int[] nums) {
|
||||
}
|
||||
```
|
||||
|
||||
# 8. 数组相邻差值的个数
|
||||
## 8. 数组相邻差值的个数
|
||||
|
||||
667\. Beautiful Arrangement II (Medium)
|
||||
|
||||
@ -305,7 +307,7 @@ public int[] constructArray(int n, int k) {
|
||||
}
|
||||
```
|
||||
|
||||
# 9. 数组的度
|
||||
## 9. 数组的度
|
||||
|
||||
697\. Degree of an Array (Easy)
|
||||
|
||||
@ -346,7 +348,7 @@ public int findShortestSubArray(int[] nums) {
|
||||
}
|
||||
```
|
||||
|
||||
# 10. 对角元素相等的矩阵
|
||||
## 10. 对角元素相等的矩阵
|
||||
|
||||
766\. Toeplitz Matrix (Easy)
|
||||
|
||||
@ -386,7 +388,7 @@ private boolean check(int[][] matrix, int expectValue, int row, int col) {
|
||||
}
|
||||
```
|
||||
|
||||
# 11. 嵌套数组
|
||||
## 11. 嵌套数组
|
||||
|
||||
565\. Array Nesting (Medium)
|
||||
|
||||
@ -422,7 +424,7 @@ public int arrayNesting(int[] nums) {
|
||||
}
|
||||
```
|
||||
|
||||
# 12. 分隔数组
|
||||
## 12. 分隔数组
|
||||
|
||||
769\. Max Chunks To Make Sorted (Medium)
|
||||
|
||||
@ -450,10 +452,3 @@ public int maxChunksToSorted(int[] arr) {
|
||||
return ret;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -1,14 +1,16 @@
|
||||
# Leetcode 题解 - 栈和队列
|
||||
<!-- GFM-TOC -->
|
||||
* [1. 用栈实现队列](#1-用栈实现队列)
|
||||
* [2. 用队列实现栈](#2-用队列实现栈)
|
||||
* [3. 最小值栈](#3-最小值栈)
|
||||
* [4. 用栈实现括号匹配](#4-用栈实现括号匹配)
|
||||
* [5. 数组中元素与下一个比它大的元素之间的距离](#5-数组中元素与下一个比它大的元素之间的距离)
|
||||
* [6. 循环数组中比当前元素大的下一个元素](#6-循环数组中比当前元素大的下一个元素)
|
||||
* [Leetcode 题解 - 栈和队列](#leetcode-题解---栈和队列)
|
||||
* [1. 用栈实现队列](#1-用栈实现队列)
|
||||
* [2. 用队列实现栈](#2-用队列实现栈)
|
||||
* [3. 最小值栈](#3-最小值栈)
|
||||
* [4. 用栈实现括号匹配](#4-用栈实现括号匹配)
|
||||
* [5. 数组中元素与下一个比它大的元素之间的距离](#5-数组中元素与下一个比它大的元素之间的距离)
|
||||
* [6. 循环数组中比当前元素大的下一个元素](#6-循环数组中比当前元素大的下一个元素)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
|
||||
# 1. 用栈实现队列
|
||||
## 1. 用栈实现队列
|
||||
|
||||
232\. Implement Queue using Stacks (Easy)
|
||||
|
||||
@ -50,7 +52,7 @@ class MyQueue {
|
||||
}
|
||||
```
|
||||
|
||||
# 2. 用队列实现栈
|
||||
## 2. 用队列实现栈
|
||||
|
||||
225\. Implement Stack using Queues (Easy)
|
||||
|
||||
@ -89,7 +91,7 @@ class MyStack {
|
||||
}
|
||||
```
|
||||
|
||||
# 3. 最小值栈
|
||||
## 3. 最小值栈
|
||||
|
||||
155\. Min Stack (Easy)
|
||||
|
||||
@ -132,7 +134,7 @@ class MinStack {
|
||||
|
||||
对于实现最小值队列问题,可以先将队列使用栈来实现,然后就将问题转换为最小值栈,这个问题出现在 编程之美:3.7。
|
||||
|
||||
# 4. 用栈实现括号匹配
|
||||
## 4. 用栈实现括号匹配
|
||||
|
||||
20\. Valid Parentheses (Easy)
|
||||
|
||||
@ -167,7 +169,7 @@ public boolean isValid(String s) {
|
||||
}
|
||||
```
|
||||
|
||||
# 5. 数组中元素与下一个比它大的元素之间的距离
|
||||
## 5. 数组中元素与下一个比它大的元素之间的距离
|
||||
|
||||
739\. Daily Temperatures (Medium)
|
||||
|
||||
@ -196,7 +198,7 @@ public int[] dailyTemperatures(int[] temperatures) {
|
||||
}
|
||||
```
|
||||
|
||||
# 6. 循环数组中比当前元素大的下一个元素
|
||||
## 6. 循环数组中比当前元素大的下一个元素
|
||||
|
||||
503\. Next Greater Element II (Medium)
|
||||
|
||||
@ -230,10 +232,3 @@ public int[] nextGreaterElements(int[] nums) {
|
||||
return next;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -1,48 +1,50 @@
|
||||
# Leetcode 题解 - 树
|
||||
<!-- GFM-TOC -->
|
||||
* [递归](#递归)
|
||||
* [1. 树的高度](#1-树的高度)
|
||||
* [2. 平衡树](#2-平衡树)
|
||||
* [3. 两节点的最长路径](#3-两节点的最长路径)
|
||||
* [4. 翻转树](#4-翻转树)
|
||||
* [5. 归并两棵树](#5-归并两棵树)
|
||||
* [6. 判断路径和是否等于一个数](#6-判断路径和是否等于一个数)
|
||||
* [7. 统计路径和等于一个数的路径数量](#7-统计路径和等于一个数的路径数量)
|
||||
* [8. 子树](#8-子树)
|
||||
* [9. 树的对称](#9-树的对称)
|
||||
* [10. 最小路径](#10-最小路径)
|
||||
* [11. 统计左叶子节点的和](#11-统计左叶子节点的和)
|
||||
* [12. 相同节点值的最大路径长度](#12-相同节点值的最大路径长度)
|
||||
* [13. 间隔遍历](#13-间隔遍历)
|
||||
* [14. 找出二叉树中第二小的节点](#14-找出二叉树中第二小的节点)
|
||||
* [层次遍历](#层次遍历)
|
||||
* [1. 一棵树每层节点的平均数](#1-一棵树每层节点的平均数)
|
||||
* [2. 得到左下角的节点](#2-得到左下角的节点)
|
||||
* [前中后序遍历](#前中后序遍历)
|
||||
* [1. 非递归实现二叉树的前序遍历](#1-非递归实现二叉树的前序遍历)
|
||||
* [2. 非递归实现二叉树的后序遍历](#2-非递归实现二叉树的后序遍历)
|
||||
* [3. 非递归实现二叉树的中序遍历](#3-非递归实现二叉树的中序遍历)
|
||||
* [BST](#bst)
|
||||
* [1. 修剪二叉查找树](#1-修剪二叉查找树)
|
||||
* [2. 寻找二叉查找树的第 k 个元素](#2-寻找二叉查找树的第-k-个元素)
|
||||
* [3. 把二叉查找树每个节点的值都加上比它大的节点的值](#3-把二叉查找树每个节点的值都加上比它大的节点的值)
|
||||
* [4. 二叉查找树的最近公共祖先](#4-二叉查找树的最近公共祖先)
|
||||
* [5. 二叉树的最近公共祖先](#5-二叉树的最近公共祖先)
|
||||
* [6. 从有序数组中构造二叉查找树](#6-从有序数组中构造二叉查找树)
|
||||
* [7. 根据有序链表构造平衡的二叉查找树](#7-根据有序链表构造平衡的二叉查找树)
|
||||
* [8. 在二叉查找树中寻找两个节点,使它们的和为一个给定值](#8-在二叉查找树中寻找两个节点,使它们的和为一个给定值)
|
||||
* [9. 在二叉查找树中查找两个节点之差的最小绝对值](#9-在二叉查找树中查找两个节点之差的最小绝对值)
|
||||
* [10. 寻找二叉查找树中出现次数最多的值](#10-寻找二叉查找树中出现次数最多的值)
|
||||
* [Trie](#trie)
|
||||
* [1. 实现一个 Trie](#1-实现一个-trie)
|
||||
* [2. 实现一个 Trie,用来求前缀和](#2-实现一个-trie,用来求前缀和)
|
||||
* [Leetcode 题解 - 树](#leetcode-题解---树)
|
||||
* [递归](#递归)
|
||||
* [1. 树的高度](#1-树的高度)
|
||||
* [2. 平衡树](#2-平衡树)
|
||||
* [3. 两节点的最长路径](#3-两节点的最长路径)
|
||||
* [4. 翻转树](#4-翻转树)
|
||||
* [5. 归并两棵树](#5-归并两棵树)
|
||||
* [6. 判断路径和是否等于一个数](#6-判断路径和是否等于一个数)
|
||||
* [7. 统计路径和等于一个数的路径数量](#7-统计路径和等于一个数的路径数量)
|
||||
* [8. 子树](#8-子树)
|
||||
* [9. 树的对称](#9-树的对称)
|
||||
* [10. 最小路径](#10-最小路径)
|
||||
* [11. 统计左叶子节点的和](#11-统计左叶子节点的和)
|
||||
* [12. 相同节点值的最大路径长度](#12-相同节点值的最大路径长度)
|
||||
* [13. 间隔遍历](#13-间隔遍历)
|
||||
* [14. 找出二叉树中第二小的节点](#14-找出二叉树中第二小的节点)
|
||||
* [层次遍历](#层次遍历)
|
||||
* [1. 一棵树每层节点的平均数](#1-一棵树每层节点的平均数)
|
||||
* [2. 得到左下角的节点](#2-得到左下角的节点)
|
||||
* [前中后序遍历](#前中后序遍历)
|
||||
* [1. 非递归实现二叉树的前序遍历](#1-非递归实现二叉树的前序遍历)
|
||||
* [2. 非递归实现二叉树的后序遍历](#2-非递归实现二叉树的后序遍历)
|
||||
* [3. 非递归实现二叉树的中序遍历](#3-非递归实现二叉树的中序遍历)
|
||||
* [BST](#bst)
|
||||
* [1. 修剪二叉查找树](#1-修剪二叉查找树)
|
||||
* [2. 寻找二叉查找树的第 k 个元素](#2-寻找二叉查找树的第-k-个元素)
|
||||
* [3. 把二叉查找树每个节点的值都加上比它大的节点的值](#3-把二叉查找树每个节点的值都加上比它大的节点的值)
|
||||
* [4. 二叉查找树的最近公共祖先](#4-二叉查找树的最近公共祖先)
|
||||
* [5. 二叉树的最近公共祖先](#5-二叉树的最近公共祖先)
|
||||
* [6. 从有序数组中构造二叉查找树](#6-从有序数组中构造二叉查找树)
|
||||
* [7. 根据有序链表构造平衡的二叉查找树](#7-根据有序链表构造平衡的二叉查找树)
|
||||
* [8. 在二叉查找树中寻找两个节点,使它们的和为一个给定值](#8-在二叉查找树中寻找两个节点,使它们的和为一个给定值)
|
||||
* [9. 在二叉查找树中查找两个节点之差的最小绝对值](#9-在二叉查找树中查找两个节点之差的最小绝对值)
|
||||
* [10. 寻找二叉查找树中出现次数最多的值](#10-寻找二叉查找树中出现次数最多的值)
|
||||
* [Trie](#trie)
|
||||
* [1. 实现一个 Trie](#1-实现一个-trie)
|
||||
* [2. 实现一个 Trie,用来求前缀和](#2-实现一个-trie,用来求前缀和)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
|
||||
# 递归
|
||||
## 递归
|
||||
|
||||
一棵树要么是空树,要么有两个指针,每个指针指向一棵树。树是一种递归结构,很多树的问题可以使用递归来处理。
|
||||
|
||||
## 1. 树的高度
|
||||
### 1. 树的高度
|
||||
|
||||
104\. Maximum Depth of Binary Tree (Easy)
|
||||
|
||||
@ -55,7 +57,7 @@ public int maxDepth(TreeNode root) {
|
||||
}
|
||||
```
|
||||
|
||||
## 2. 平衡树
|
||||
### 2. 平衡树
|
||||
|
||||
110\. Balanced Binary Tree (Easy)
|
||||
|
||||
@ -88,7 +90,7 @@ public int maxDepth(TreeNode root) {
|
||||
}
|
||||
```
|
||||
|
||||
## 3. 两节点的最长路径
|
||||
### 3. 两节点的最长路径
|
||||
|
||||
543\. Diameter of Binary Tree (Easy)
|
||||
|
||||
@ -123,7 +125,7 @@ private int depth(TreeNode root) {
|
||||
}
|
||||
```
|
||||
|
||||
## 4. 翻转树
|
||||
### 4. 翻转树
|
||||
|
||||
226\. Invert Binary Tree (Easy)
|
||||
|
||||
@ -139,7 +141,7 @@ public TreeNode invertTree(TreeNode root) {
|
||||
}
|
||||
```
|
||||
|
||||
## 5. 归并两棵树
|
||||
### 5. 归并两棵树
|
||||
|
||||
617\. Merge Two Binary Trees (Easy)
|
||||
|
||||
@ -174,7 +176,7 @@ public TreeNode mergeTrees(TreeNode t1, TreeNode t2) {
|
||||
}
|
||||
```
|
||||
|
||||
## 6. 判断路径和是否等于一个数
|
||||
### 6. 判断路径和是否等于一个数
|
||||
|
||||
Leetcdoe : 112. Path Sum (Easy)
|
||||
|
||||
@ -204,7 +206,7 @@ public boolean hasPathSum(TreeNode root, int sum) {
|
||||
}
|
||||
```
|
||||
|
||||
## 7. 统计路径和等于一个数的路径数量
|
||||
### 7. 统计路径和等于一个数的路径数量
|
||||
|
||||
437\. Path Sum III (Easy)
|
||||
|
||||
@ -246,7 +248,7 @@ private int pathSumStartWithRoot(TreeNode root, int sum) {
|
||||
}
|
||||
```
|
||||
|
||||
## 8. 子树
|
||||
### 8. 子树
|
||||
|
||||
572\. Subtree of Another Tree (Easy)
|
||||
|
||||
@ -299,7 +301,7 @@ private boolean isSubtreeWithRoot(TreeNode s, TreeNode t) {
|
||||
}
|
||||
```
|
||||
|
||||
## 9. 树的对称
|
||||
### 9. 树的对称
|
||||
|
||||
101\. Symmetric Tree (Easy)
|
||||
|
||||
@ -327,7 +329,7 @@ private boolean isSymmetric(TreeNode t1, TreeNode t2) {
|
||||
}
|
||||
```
|
||||
|
||||
## 10. 最小路径
|
||||
### 10. 最小路径
|
||||
|
||||
111\. Minimum Depth of Binary Tree (Easy)
|
||||
|
||||
@ -345,7 +347,7 @@ public int minDepth(TreeNode root) {
|
||||
}
|
||||
```
|
||||
|
||||
## 11. 统计左叶子节点的和
|
||||
### 11. 统计左叶子节点的和
|
||||
|
||||
404\. Sum of Left Leaves (Easy)
|
||||
|
||||
@ -374,7 +376,7 @@ private boolean isLeaf(TreeNode node){
|
||||
}
|
||||
```
|
||||
|
||||
## 12. 相同节点值的最大路径长度
|
||||
### 12. 相同节点值的最大路径长度
|
||||
|
||||
687\. Longest Univalue Path (Easy)
|
||||
|
||||
@ -409,7 +411,7 @@ private int dfs(TreeNode root){
|
||||
}
|
||||
```
|
||||
|
||||
## 13. 间隔遍历
|
||||
### 13. 间隔遍历
|
||||
|
||||
337\. House Robber III (Medium)
|
||||
|
||||
@ -435,7 +437,7 @@ public int rob(TreeNode root) {
|
||||
}
|
||||
```
|
||||
|
||||
## 14. 找出二叉树中第二小的节点
|
||||
### 14. 找出二叉树中第二小的节点
|
||||
|
||||
671\. Second Minimum Node In a Binary Tree (Easy)
|
||||
|
||||
@ -468,11 +470,11 @@ public int findSecondMinimumValue(TreeNode root) {
|
||||
}
|
||||
```
|
||||
|
||||
# 层次遍历
|
||||
## 层次遍历
|
||||
|
||||
使用 BFS 进行层次遍历。不需要使用两个队列来分别存储当前层的节点和下一层的节点,因为在开始遍历一层的节点时,当前队列中的节点数就是当前层的节点数,只要控制遍历这么多节点数,就能保证这次遍历的都是当前层的节点。
|
||||
|
||||
## 1. 一棵树每层节点的平均数
|
||||
### 1. 一棵树每层节点的平均数
|
||||
|
||||
637\. Average of Levels in Binary Tree (Easy)
|
||||
|
||||
@ -499,7 +501,7 @@ public List<Double> averageOfLevels(TreeNode root) {
|
||||
}
|
||||
```
|
||||
|
||||
## 2. 得到左下角的节点
|
||||
### 2. 得到左下角的节点
|
||||
|
||||
513\. Find Bottom Left Tree Value (Easy)
|
||||
|
||||
@ -533,7 +535,7 @@ public int findBottomLeftValue(TreeNode root) {
|
||||
}
|
||||
```
|
||||
|
||||
# 前中后序遍历
|
||||
## 前中后序遍历
|
||||
|
||||
```html
|
||||
1
|
||||
@ -582,7 +584,7 @@ void dfs(TreeNode root) {
|
||||
}
|
||||
```
|
||||
|
||||
## 1. 非递归实现二叉树的前序遍历
|
||||
### 1. 非递归实现二叉树的前序遍历
|
||||
|
||||
144\. Binary Tree Preorder Traversal (Medium)
|
||||
|
||||
@ -604,13 +606,13 @@ public List<Integer> preorderTraversal(TreeNode root) {
|
||||
}
|
||||
```
|
||||
|
||||
## 2. 非递归实现二叉树的后序遍历
|
||||
### 2. 非递归实现二叉树的后序遍历
|
||||
|
||||
145\. Binary Tree Postorder Traversal (Medium)
|
||||
|
||||
[Leetcode](https://leetcode.com/problems/binary-tree-postorder-traversal/description/) / [力扣](https://leetcode-cn.com/problems/binary-tree-postorder-traversal/description/)
|
||||
|
||||
前序遍历为 root -> left -> right,后序遍历为 left -> right -> root。可以修改前序遍历成为 root -> right -> left,那么这个顺序就和后序遍历正好相反。
|
||||
前序遍历为 root -\> left -\> right,后序遍历为 left -\> right -\> root。可以修改前序遍历成为 root -\> right -\> left,那么这个顺序就和后序遍历正好相反。
|
||||
|
||||
```java
|
||||
public List<Integer> postorderTraversal(TreeNode root) {
|
||||
@ -629,7 +631,7 @@ public List<Integer> postorderTraversal(TreeNode root) {
|
||||
}
|
||||
```
|
||||
|
||||
## 3. 非递归实现二叉树的中序遍历
|
||||
### 3. 非递归实现二叉树的中序遍历
|
||||
|
||||
94\. Binary Tree Inorder Traversal (Medium)
|
||||
|
||||
@ -654,13 +656,13 @@ public List<Integer> inorderTraversal(TreeNode root) {
|
||||
}
|
||||
```
|
||||
|
||||
# BST
|
||||
## BST
|
||||
|
||||
二叉查找树(BST):根节点大于等于左子树所有节点,小于等于右子树所有节点。
|
||||
|
||||
二叉查找树中序遍历有序。
|
||||
|
||||
## 1. 修剪二叉查找树
|
||||
### 1. 修剪二叉查找树
|
||||
|
||||
669\. Trim a Binary Search Tree (Easy)
|
||||
|
||||
@ -702,7 +704,7 @@ public TreeNode trimBST(TreeNode root, int L, int R) {
|
||||
}
|
||||
```
|
||||
|
||||
## 2. 寻找二叉查找树的第 k 个元素
|
||||
### 2. 寻找二叉查找树的第 k 个元素
|
||||
|
||||
230\. Kth Smallest Element in a BST (Medium)
|
||||
|
||||
@ -748,7 +750,7 @@ private int count(TreeNode node) {
|
||||
}
|
||||
```
|
||||
|
||||
## 3. 把二叉查找树每个节点的值都加上比它大的节点的值
|
||||
### 3. 把二叉查找树每个节点的值都加上比它大的节点的值
|
||||
|
||||
Convert BST to Greater Tree (Easy)
|
||||
|
||||
@ -787,7 +789,7 @@ private void traver(TreeNode node) {
|
||||
}
|
||||
```
|
||||
|
||||
## 4. 二叉查找树的最近公共祖先
|
||||
### 4. 二叉查找树的最近公共祖先
|
||||
|
||||
235\. Lowest Common Ancestor of a Binary Search Tree (Easy)
|
||||
|
||||
@ -813,7 +815,7 @@ public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
|
||||
}
|
||||
```
|
||||
|
||||
## 5. 二叉树的最近公共祖先
|
||||
### 5. 二叉树的最近公共祖先
|
||||
|
||||
236\. Lowest Common Ancestor of a Binary Tree (Medium)
|
||||
|
||||
@ -840,7 +842,7 @@ public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
|
||||
}
|
||||
```
|
||||
|
||||
## 6. 从有序数组中构造二叉查找树
|
||||
### 6. 从有序数组中构造二叉查找树
|
||||
|
||||
108\. Convert Sorted Array to Binary Search Tree (Easy)
|
||||
|
||||
@ -861,7 +863,7 @@ private TreeNode toBST(int[] nums, int sIdx, int eIdx){
|
||||
}
|
||||
```
|
||||
|
||||
## 7. 根据有序链表构造平衡的二叉查找树
|
||||
### 7. 根据有序链表构造平衡的二叉查找树
|
||||
|
||||
109\. Convert Sorted List to Binary Search Tree (Medium)
|
||||
|
||||
@ -904,7 +906,7 @@ private ListNode preMid(ListNode head) {
|
||||
}
|
||||
```
|
||||
|
||||
## 8. 在二叉查找树中寻找两个节点,使它们的和为一个给定值
|
||||
### 8. 在二叉查找树中寻找两个节点,使它们的和为一个给定值
|
||||
|
||||
653\. Two Sum IV - Input is a BST (Easy)
|
||||
|
||||
@ -950,7 +952,7 @@ private void inOrder(TreeNode root, List<Integer> nums) {
|
||||
}
|
||||
```
|
||||
|
||||
## 9. 在二叉查找树中查找两个节点之差的最小绝对值
|
||||
### 9. 在二叉查找树中查找两个节点之差的最小绝对值
|
||||
|
||||
530\. Minimum Absolute Difference in BST (Easy)
|
||||
|
||||
@ -990,7 +992,7 @@ private void inOrder(TreeNode node) {
|
||||
}
|
||||
```
|
||||
|
||||
## 10. 寻找二叉查找树中出现次数最多的值
|
||||
### 10. 寻找二叉查找树中出现次数最多的值
|
||||
|
||||
501\. Find Mode in Binary Search Tree (Easy)
|
||||
|
||||
@ -1043,13 +1045,13 @@ private void inOrder(TreeNode node, List<Integer> nums) {
|
||||
}
|
||||
```
|
||||
|
||||
# Trie
|
||||
## Trie
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/5c638d59-d4ae-4ba4-ad44-80bdc30f38dd.jpg"/> </div><br>
|
||||
|
||||
Trie,又称前缀树或字典树,用于判断字符串是否存在或者是否具有某种字符串前缀。
|
||||
|
||||
## 1. 实现一个 Trie
|
||||
### 1. 实现一个 Trie
|
||||
|
||||
208\. Implement Trie (Prefix Tree) (Medium)
|
||||
|
||||
@ -1113,7 +1115,7 @@ class Trie {
|
||||
}
|
||||
```
|
||||
|
||||
## 2. 实现一个 Trie,用来求前缀和
|
||||
### 2. 实现一个 Trie,用来求前缀和
|
||||
|
||||
677\. Map Sum Pairs (Medium)
|
||||
|
||||
@ -1180,10 +1182,3 @@ class MapSum {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -1,8 +1,10 @@
|
||||
# 前言
|
||||
# Leetcode 题解
|
||||
|
||||
## 前言
|
||||
|
||||
本文从 Leetcode 中精选大概 200 左右的题目,去除了某些繁杂但是没有多少算法思想的题目,同时保留了面试中经常被问到的经典题目。
|
||||
|
||||
# 算法思想
|
||||
## 算法思想
|
||||
|
||||
- [双指针](Leetcode%20题解%20-%20双指针.md)
|
||||
- [排序](Leetcode%20题解%20-%20排序.md)
|
||||
@ -13,7 +15,7 @@
|
||||
- [动态规划](Leetcode%20题解%20-%20动态规划.md)
|
||||
- [数学](Leetcode%20题解%20-%20数学.md)
|
||||
|
||||
# 数据结构相关
|
||||
## 数据结构相关
|
||||
|
||||
- [链表](Leetcode%20题解%20-%20链表.md)
|
||||
- [树](Leetcode%20题解%20-%20树.md)
|
||||
@ -24,8 +26,7 @@
|
||||
- [图](Leetcode%20题解%20-%20图.md)
|
||||
- [位运算](Leetcode%20题解%20-%20位运算.md)
|
||||
|
||||
# 参考资料
|
||||
|
||||
## 参考资料
|
||||
|
||||
- Leetcode
|
||||
- Weiss M A, 冯舜玺. 数据结构与算法分析——C 语言描述[J]. 2004.
|
||||
@ -33,10 +34,3 @@
|
||||
- 何海涛, 软件工程师. 剑指 Offer: 名企面试官精讲典型编程题[M]. 电子工业出版社, 2014.
|
||||
- 《编程之美》小组. 编程之美[M]. 电子工业出版社, 2008.
|
||||
- 左程云. 程序员代码面试指南[M]. 电子工业出版社, 2015.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
@ -31,10 +31,3 @@
|
||||
- 何海涛, 软件工程师. 剑指 Offer: 名企面试官精讲典型编程题[M]. 电子工业出版社, 2014.
|
||||
- 《编程之美》小组. 编程之美[M]. 电子工业出版社, 2008.
|
||||
- 左程云. 程序员代码面试指南[M]. 电子工业出版社, 2015.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user