From db5a16d39dd6473c83addc1406806d607e53ad03 Mon Sep 17 00:00:00 2001 From: CyC2018 <1029579233@qq.com> Date: Wed, 28 Feb 2018 15:50:25 +0800 Subject: [PATCH] auto commit --- notes/2016 校招真题题解.md | 161 ++--- notes/HTTP.md | 477 +++++++------- notes/JVM.md | 669 +++++++++---------- notes/Java IO.md | 301 ++++----- notes/Java 容器.md | 143 ++--- notes/Leetcode 题解.md | 1241 ++++++++++++++++++------------------ notes/Linux.md | 851 +++++++++++++------------ notes/MySQL.md | 407 ++++++------ notes/SQL 语法.md | 503 +++++++-------- notes/代码可读性.md | 193 +++--- notes/剑指 offer 题解.md | 563 ++++++++-------- notes/算法.md | 733 ++++++++++----------- notes/计算机操作系统.md | 657 +++++++++---------- notes/计算机网络.md | 941 +++++++++++++-------------- notes/设计模式.md | 501 +++++++-------- notes/重构.md | 763 +++++++++++----------- notes/面向对象思想.md | 251 ++++---- 17 files changed, 4686 insertions(+), 4669 deletions(-) diff --git a/notes/2016 校招真题题解.md b/notes/2016 校招真题题解.md index 0285debe..da6709ab 100644 --- a/notes/2016 校招真题题解.md +++ b/notes/2016 校招真题题解.md @@ -1,33 +1,34 @@ -* [ǰ](#ǰ) -* [1. С-СGit](#1-С-Сgit) -* [2. С-](#2-С-) -* [3. С-йţ](#3-С-йţ) -* [4. ΢-LUCKY STRING](#4-΢-lucky-string) -* [5. ΢-Numeric Keypad](#5-΢-numeric-keypad) -* [6. ΢-Spring Outing](#6-΢-spring-outing) -* [7. ΢-S-expression](#7-΢-s-expression) -* [8. Ϊ-߷Ƕ](#8-Ϊ-߷Ƕ) -* [9. Ϊ-򵥴¼](#9-Ϊ-򵥴¼) -* [10. Ϊ-˿ƴС](#10-Ϊ-˿ƴС) -* [11. ȥĶ-ֲ](#11-ȥĶ-ֲ) -* [12. ȥĶ-׸ظַ](#12-ȥĶ-׸ظַ) -* [13. ȥĶ-ѰCoder](#13-ȥĶ-Ѱcoder) -* [14. -ֵ](#14--ֵ) -* [15. -ӷת](#15--ӷת) -* [16. -ݷ](#16--ݷ) -* [17. -ֱͼ](#17--ֱͼ) -* [18. -ַ](#18--ַ) -* [19. -ƽ](#19--ƽ) -* [20. ٶ-ﷸת](#20-ٶ-ﷸת) -* [22. ٶ-üֽ](#22-ٶ-üֽ) -* [23. ٶ-](#23-ٶ-) -* [24. ٶ-Ģ](#24-ٶ-Ģ) +* [前言](#前言) +* [1. 小米-小米Git](#1-小米-小米git) +* [2. 小米-懂二进制](#2-小米-懂二进制) +* [3. 小米-中国牛市](#3-小米-中国牛市) +* [4. 微软-LUCKY STRING](#4-微软-lucky-string) +* [5. 微软-Numeric Keypad](#5-微软-numeric-keypad) +* [6. 微软-Spring Outing](#6-微软-spring-outing) +* [7. 微软-S-expression](#7-微软-s-expression) +* [8. 华为-最高分是多少](#8-华为-最高分是多少) +* [9. 华为-简单错误记录](#9-华为-简单错误记录) +* [10. 华为-扑克牌大小](#10-华为-扑克牌大小) +* [11. 去哪儿-二分查找](#11-去哪儿-二分查找) +* [12. 去哪儿-首个重复字符](#12-去哪儿-首个重复字符) +* [13. 去哪儿-寻找Coder](#13-去哪儿-寻找coder) +* [14. 美团-最大差值](#14-美团-最大差值) +* [15. 美团-棋子翻转](#15-美团-棋子翻转) +* [16. 美团-拜访](#16-美团-拜访) +* [17. 美团-直方图内最大矩形](#17-美团-直方图内最大矩形) +* [18. 美团-字符串计数](#18-美团-字符串计数) +* [19. 美团-平均年龄](#19-美团-平均年龄) +* [20. 百度-罪犯转移](#20-百度-罪犯转移) +* [22. 百度-裁减网格纸](#22-百度-裁减网格纸) +* [23. 百度-钓鱼比赛](#23-百度-钓鱼比赛) +* [24. 百度-蘑菇阵](#24-百度-蘑菇阵) -# ǰ -ʡԵĴ룺 +# 前言 + +省略的代码: ```java import java.util.*; @@ -48,10 +49,10 @@ public class Main { } ``` -# 1. С-СGit +# 1. 小米-小米Git -- ؽ -- ʹ LCA +- 重建多叉树 +- 使用 LCA ```java private class TreeNode { @@ -65,7 +66,7 @@ private class TreeNode { public int getSplitNode(String[] matrix, int indexA, int indexB) { int n = matrix.length; - boolean[][] linked = new boolean[n][n]; // ؽڽӾ + boolean[][] linked = new boolean[n][n]; // 重建邻接矩阵 for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { linked[i][j] = matrix[i].charAt(j) == '1'; @@ -80,7 +81,7 @@ private TreeNode constructTree(boolean[][] linked, int root) { TreeNode tree = new TreeNode(root); for (int i = 0; i < linked[root].length; i++) { if (linked[root][i]) { - linked[i][root] = false; // ΪĿڽӾ˫ģҪתΪ + linked[i][root] = false; // 因为题目给的邻接矩阵是双向的,在这里需要把它转为单向的 tree.childs.add(constructTree(links, i)); } } @@ -102,9 +103,9 @@ private TreeNode LCA(TreeNode root, TreeNode p, TreeNode q) { } ``` -# 2. С- +# 2. 小米-懂二进制 -򣬽ĶƱʾΪ 1 һλͬλ +对两个数进行异或,结果的二进制表示为 1 的那一位就是两个数不同的位。 ```java public int countBitDiff(int m, int n) { @@ -112,11 +113,11 @@ public int countBitDiff(int m, int n) { } ``` -# 3. С-йţ +# 3. 小米-中国牛市 -⣬һСΪ 2 ı +背包问题,可以设一个大小为 2 的背包。 -״̬תƷ£ +状态转移方程如下: ```html dp[i, j] = max(dp[i, j-1], prices[j] - prices[jj] + dp[i-1, jj]) { jj in range of [0, j-1] } = max(dp[i, j-1], prices[j] + max(dp[i-1, jj] - prices[jj])) @@ -137,10 +138,10 @@ public int calculateMax(int[] prices) { } ``` -# 4. ΢-LUCKY STRING +# 4. 微软-LUCKY STRING -- 쳲пԤ㣻 -- ͷβַḶ́ÿһѭʹһ Set i j ֵַ Set ַ֤ͬ Set ĴСDzַͬĸ +- 斐波那契数列可以预计算; +- 从头到尾遍历字符串的过程,每一轮循环都使用一个 Set 来保存从 i 到 j 出现的字符,并且 Set 保证了字符都不同,因此 Set 的大小就是不同字符的个数。 ```java Set fibSet = new HashSet<>(Arrays.asList(1, 2, 3, 5, 8, 13, 21, 34, 55, 89)); @@ -165,7 +166,7 @@ for (String s : arr) { } ``` -# 5. ΢-Numeric Keypad +# 5. 微软-Numeric Keypad ```java private static int[][] canReach = { @@ -209,17 +210,17 @@ public static void main(String[] args) { } ``` -# 6. ΢-Spring Outing +# 6. 微软-Spring Outing - N = 3K = 4 ۡ +下面以 N = 3,K = 4 来进行讨论。 -ʼʱ 0 طΪص㣬ҲǴڼ +初始时,令第 0 个地方成为待定地点,也就是呆在家里。 -ӵ 4 ص㿪ʼͶƱÿֻҪȽϵ 4 ط͵ 0 طȼѡ˵ 4 طôµ 4 طΪص㡣 +从第 4 个地点开始投票,每个人只需要比较第 4 个地方和第 0 个地方的优先级,里,如果超过半数的人选择了第 4 个地方,那么更新第 4 个地方成为待定地点。 -Ӻǰظϲ裬ϸ´ص㣬ֱеطѾͶƱ +从后往前不断重复以上步骤,不断更新待定地点,直到所有地方都已经投票。 -У 0 صΪص㣬ΪĻ 4 صֻҪصбȽϣÿʼ 1 صΪص㣬ôڶԵ 2 صͶƱʱÿ˲Ҫǵ 2 ص 1 صȼҲҪͶƱصȼ +上面的讨论中,先令第 0 个地点成为待定地点,是因为这样的话第 4 个地点就只需要和这个地点进行比较,而不用考虑其它情况。如果最开始先令第 1 个地点成为待定地点,那么在对第 2 个地点进行投票时,每个人不仅要考虑第 2 个地点与第 1 个地点的优先级,也要考虑与其后投票地点的优先级。 ```java int N = in.nextInt(); @@ -246,9 +247,9 @@ for (int place = K; place > 0; place--) { System.out.println(ret == 0 ? "otaku" : ret); ``` -# 7. ΢-S-expression +# 7. 微软-S-expression -# 8. Ϊ-߷Ƕ +# 8. 华为-最高分是多少 ```java int N = in.nextInt(); @@ -280,7 +281,7 @@ for (int i = 0; i < M; i++) { } ``` -# 9. Ϊ-򵥴¼ +# 9. 华为-简单错误记录 ```java HashMap map = new LinkedHashMap<>(); @@ -300,7 +301,7 @@ for (int i = 0; i < 8 && i < list.size(); i++) { } ``` -# 10. Ϊ-˿ƴС +# 10. 华为-扑克牌大小 ```java public class Main { @@ -391,12 +392,12 @@ public class Main { } ``` -# 11. ȥĶ-ֲ +# 11. 去哪儿-二分查找 -ظԪص飬ֲҪעҪ㣺 +对于有重复元素的有序数组,二分查找需要注意以下要点: - if (val <= A[m]) h = m; -- Ϊ h ĸֵΪ m m - 1 while ѭҲΪ l < h m - 1 ѭΪ l <= h +- 因为 h 的赋值为 m 而不是 m - 1,因此 while 循环的条件也就为 l < h。(如果是 m - 1 循环条件为 l <= h) ```java public int getPos(int[] A, int n, int val) { @@ -410,7 +411,7 @@ public int getPos(int[] A, int n, int val) { } ``` -# 12. ȥĶ-׸ظַ +# 12. 去哪儿-首个重复字符 ```java public char findFirstRepeat(String A, int n) { @@ -424,7 +425,7 @@ public char findFirstRepeat(String A, int n) { } ``` -# 13. ȥĶ-ѰCoder +# 13. 去哪儿-寻找Coder ```java public String[] findCoder(String[] A, int n) { @@ -450,7 +451,7 @@ public String[] findCoder(String[] A, int n) { return ret; } -// ţ޷ javafx.util.PairԼʵһ Pair +// 牛客网无法导入 javafx.util.Pair,这里就自己实现一下 Pair 类 private class Pair { T t; K k; @@ -470,9 +471,9 @@ private class Pair { } ``` -# 14. -ֵ +# 14. 美团-最大差值 -̰IJԡ +贪心策略。 ```java public int getDis(int[] A, int n) { @@ -486,7 +487,7 @@ public int getDis(int[] A, int n) { } ``` -# 15. -ӷת +# 15. 美团-棋子翻转 ```java public int[][] flipChess(int[][] A, int[][] f) { @@ -502,7 +503,7 @@ public int[][] flipChess(int[][] A, int[][] f) { } ``` -# 16. -ݷ +# 16. 美团-拜访 ```java private Set paths; @@ -554,7 +555,7 @@ private void backtracking(int[][] map, int n, int m, int r, int c, int[][] direc } ``` -# 17. -ֱͼ +# 17. 美团-直方图内最大矩形 ```java public int countArea(int[] A, int n) { @@ -570,15 +571,15 @@ public int countArea(int[] A, int n) { } ``` -# 18. -ַ +# 18. 美团-字符串计数 -ַСдַ԰ַ 26 ơֵıȽϺͨȽϲͬǴҽбȽϣ "ac" "abc"ֵıȽϽΪ "ac" > "abc"ȽϣΪ "abc" λȻ +字符串都是小写字符,可以把字符串当成是 26 进制。但是字典序的比较和普通的整数比较不同,是从左往右进行比较,例如 "ac" 和 "abc",字典序的比较结果为 "ac" > "abc",如果按照整数方法比较,因为 "abc" 是三位数,显然更大。 -ַijȿܲȣ s1 հײֺ s2 ӦֽбȽʱӦð s1 Ŀհײֿ 'a' ַġ +由于两个字符串的长度可能不想等,在 s1 空白部分和 s2 对应部分进行比较时,应该把 s1 的空白部分看成是 'a' 字符进行填充的。 -һҪעǣs1 s2 Ϊ leni ַֻȽǰ i ַ 'aaa' 'bbb' Ϊ 2 ĸΪ 'aa' 'bb' ַҪǺ沿ֵַ +还有一点要注意的是,s1 到 s2 长度为 leni 的字符串个数只比较前面 i 个字符。例如 'aaa' 和 'bbb' ,长度为 2 的个数为 'aa' 到 'bb' 的字符串个数,不需要考虑后面部分的字符。 -ͳƸʱ len1 ʼһֱϷȣÿѭͳƳΪ i ַ +在统计个数时,从 len1 开始一直遍历到最大合法长度,每次循环都统计长度为 i 的子字符串个数。 ```java String s1 = in.next(); @@ -601,7 +602,7 @@ for (int i = len1; i <= len; i++) { System.out.println(ret - 1); ``` -# 19. -ƽ +# 19. 美团-平均年龄 ```java int W = in.nextInt(); @@ -609,15 +610,15 @@ double Y = in.nextDouble(); double x = in.nextDouble(); int N = in.nextInt(); while (N-- > 0) { - Y++; // Աÿ䶼Ҫ 1 + Y++; // 老员工每年年龄都要加 1 Y += (21 - Y) * x; } System.out.println((int) Math.ceil(Y)); ``` -# 20. ٶ-ﷸת +# 20. 百度-罪犯转移 -ֺ⣬ÿIJֺͻ +部分和问题,将每次求的部分和缓存起来。 ```java int n = in.nextInt(); @@ -640,7 +641,7 @@ for (int s = 0, e = c - 1; e < n; s++, e++) { System.out.println(cnt); ``` -# 22. ٶ-üֽ +# 22. 百度-裁减网格纸 ```java int n = in.nextInt(); @@ -658,11 +659,11 @@ for (int i = 0; i < n; i++) { System.out.println((int) Math.pow(Math.max(maxX - minX, maxY - minY), 2)); ``` -# 23. ٶ- +# 23. 百度-钓鱼比赛 -P ( ٵһ ) = 1 - P ( һҲ ) +P ( 至少钓一条鱼 ) = 1 - P ( 一条也钓不到 ) -ӣȡʾʱҪһһнжȡֱ in.nextDouble() +坑:读取概率矩阵的时候,需要一行一行进行读取,而不能直接用 in.nextDouble()。 ```java public static void main(String[] args) { @@ -673,11 +674,11 @@ public static void main(String[] args) { int x = in.nextInt(); int y = in.nextInt(); int t = in.nextInt(); - in.nextLine(); // + in.nextLine(); // 坑 double pcc = 0.0; double sum = 0.0; for (int i = 1; i <= n; i++) { - String[] token = in.nextLine().split(" "); // + String[] token = in.nextLine().split(" "); // 坑 for (int j = 1; j <= m; j++) { double p = Double.parseDouble(token[j - 1]); // double p = in.nextDouble(); @@ -701,13 +702,13 @@ private static double computePOfIRT(double p, int t) { } ``` -# 24. ٶ-Ģ +# 24. 百度-蘑菇阵 -ûݻᳬʱҪ DP +这题用回溯会超时,需要用 DP。 -dp[i][j] ʾ (i,j) λòᴥĢĸʡ N\*M i == N || j == Mô (i,j) ֻһƶƶ +dp[i][j] 表示到达 (i,j) 位置不会触碰蘑菇的概率。对于 N\*M 矩阵,如果 i == N || j == M,那么 (i,j) 只能有一个移动方向;其它情况下能有两个移动方向。 -¾е 3 к͵ 3 ֻһƶλÿƶ +考虑以下矩阵,其中第 3 行和第 3 列只能往一个方向移动,而其它位置可以有两个方向移动。 ```java diff --git a/notes/HTTP.md b/notes/HTTP.md index 02c65e9b..c2afd12a 100644 --- a/notes/HTTP.md +++ b/notes/HTTP.md @@ -1,84 +1,85 @@ -* [](#) - * [Web ](#web-) +* [基础概念](#基础概念) + * [Web 基础](#web-基础) * [URL](#url) - * [Ӧ](#Ӧ) -* [HTTP ](#http-) - * [GETȡԴ](#getȡԴ) - * [POSTʵ](#postʵ) - * [HEADȡײ](#headȡײ) - * [PUTϴļ](#putϴļ) - * [DELETEɾļ](#deleteɾļ) - * [OPTIONSѯֵ֧ķ](#optionsѯֵ֧ķ) - * [TRACE׷·](#trace׷·) - * [CONNECTҪЭӴ](#connectҪЭӴ) -* [HTTP ״̬](#http-״̬) - * [2XX ɹ](#2xx-ɹ) - * [3XX ض](#3xx-ض) - * [4XX ͻ˴](#4xx-ͻ˴) - * [5XX ](#5xx-) -* [HTTP ײ](#http-ײ) - * [ͨײֶ](#ͨײֶ) - * [ײֶ](#ײֶ) - * [Ӧײֶ](#Ӧײֶ) - * [ʵײֶ](#ʵײֶ) -* [Ӧ](#Ӧ) + * [请求和响应报文](#请求和响应报文) +* [HTTP 方法](#http-方法) + * [GET:获取资源](#get获取资源) + * [POST:传输实体主体](#post传输实体主体) + * [HEAD:获取报文首部](#head获取报文首部) + * [PUT:上传文件](#put上传文件) + * [DELETE:删除文件](#delete删除文件) + * [OPTIONS:查询支持的方法](#options查询支持的方法) + * [TRACE:追踪路径](#trace追踪路径) + * [CONNECT:要求用隧道协议连接代理](#connect要求用隧道协议连接代理) +* [HTTP 状态码](#http-状态码) + * [2XX 成功](#2xx-成功) + * [3XX 重定向](#3xx-重定向) + * [4XX 客户端错误](#4xx-客户端错误) + * [5XX 服务器错误](#5xx-服务器错误) +* [HTTP 首部](#http-首部) + * [通用首部字段](#通用首部字段) + * [请求首部字段](#请求首部字段) + * [响应首部字段](#响应首部字段) + * [实体首部字段](#实体首部字段) +* [具体应用](#具体应用) * [Cookie](#cookie) - * [](#) - * [־](#־) - * [](#) - * [ֿ鴫](#ֿ鴫) - * [ಿֶ󼯺](#ಿֶ󼯺) - * [Χ](#Χ) - * [Э](#Э) - * [](#) - * [ͨת](#ͨת) + * [缓存](#缓存) + * [持久连接](#持久连接) + * [编码](#编码) + * [分块传输](#分块传输) + * [多部分对象集合](#多部分对象集合) + * [范围请求](#范围请求) + * [内容协商](#内容协商) + * [虚拟主机](#虚拟主机) + * [通信数据转发](#通信数据转发) * [HTTPs](#https) - * [](#) - * [֤](#֤) - * [](#) -* [HTTP/1.0 HTTP/1.1 ](#http10--http11-) + * [加密](#加密) + * [认证](#认证) + * [完整性](#完整性) +* [HTTP/1.0 与 HTTP/1.1 的区别](#http10-与-http11-的区别) -# -## Web +# 基础概念 -- HTTPHyperText Transfer ProtocolΪЭ飩 -- WWWWord Wide WebּHTMLHTTPURL -- RFCRequest for Comments飩ĵ +## Web 基础 + +- HTTP(HyperText Transfer Protocol,超为本传输协议)。 +- WWW(Word Wide Web)的三种技术:HTML、HTTP、URL。 +- RFC(Request for Comments,征求修正意见书),互联网的设计文档。 ## URL -- URIUniform Resource IndentifierͳһԴʶ -- URLUniform Resource LocatorͳһԴλ -- URNUniform Resource NameͳһԴƣ urn:isbn:0-486-27557-4 +- URI(Uniform Resource Indentifier,统一资源标识符) +- URL(Uniform Resource Locator,统一资源定位符) +- URN(Uniform Resource Name,统一资源名称),例如 urn:isbn:0-486-27557-4 。 -URI URL URNĿǰ WEB ֻ URL ȽУԼĻ URL +URI 包含 URL 和 URN,目前 WEB 只有 URL 比较流行,所以见到的基本都是 URL。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/4102b7d0-39b9-48d8-82ae-ac4addb7ebfb.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//4102b7d0-39b9-48d8-82ae-ac4addb7ebfb.jpg) -## Ӧ +## 请求和响应报文 -**** +**请求报文** -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/9dbb5fc2-936b-4c6d-b3a7-9617aae45080.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//9dbb5fc2-936b-4c6d-b3a7-9617aae45080.jpg) -**Ӧ** +**响应报文** -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/c634b5ed-a14b-4302-b40e-3ee387dd3c8a.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//c634b5ed-a14b-4302-b40e-3ee387dd3c8a.jpg) -# HTTP +# HTTP 方法 -ͻ˷͵ĵһΪУ˷ֶΡ +客户端发送的请求报文第一行为请求行,包含了方法字段。 -## GETȡԴ +## GET:获取资源 -## POSTʵ +## POST:传输实体主体 -POST ҪĿIJǻȡԴǴʵݡ +POST 主要目的不是获取资源,而是传输实体主体数据。 -GET POST ʹöIJ GET IJԲѯַ URLУ POST IJ洢ʵ岿֡ +GET 和 POST 的请求都能使用额外的参数,但是 GET 的参数是以查询字符串出现在 URL中,而 POST 的参数存储在实体主体部分。 ``` GET /test/demo_form.asp?name1=value1&name2=value2 HTTP/1.1 @@ -89,318 +90,318 @@ Host: w3schools.com name1=value1&name2=value2 ``` -GET Ĵηʽ POST ȫԽϲΪ GET IJ URL ǿɼģܻй¶˽Ϣ GET ֻ֧ ASCII ַΪܻ룬 POST ֱ֧׼ַ +GET 的传参方式相比于 POST 安全性较差,因为 GET 传的参数在 URL 是可见的,可能会泄露私密信息。并且 GET 只支持 ASCII 字符,如果参数为中文则可能会出现乱码,而 POST 支持标准字符集。 -## HEADȡײ +## HEAD:获取报文首部 - GET һDzرʵ岿֡ +和 GET 方法一样,但是不返回报文实体主体部分。 -Ҫȷ URL ЧԼԴµʱȡ +主要用于确认 URL 的有效性以及资源更新的日期时间等。 -## PUTϴļ +## PUT:上传文件 -֤ƣκ˶ϴļ˴ڰȫ⣬һ WEB վʹø÷ +由于自身不带验证机制,任何人都可以上传文件,因此存在安全性问题,一般 WEB 网站不使用该方法。 -## DELETEɾļ +## DELETE:删除文件 - PUT ෴֤ͬơ +与 PUT 功能相反,并且同样不带验证机制。 -## OPTIONSѯֵ֧ķ +## OPTIONS:查询支持的方法 -ѯָ URL ֵܹ֧ķ +查询指定的 URL 能够支持的方法。 -᷵ Allow: GET, POST, HEAD, OPTIONS ݡ +会返回 Allow: GET, POST, HEAD, OPTIONS 这样的内容。 -## TRACE׷· +## TRACE:追踪路径 -Ὣͨ·ظͻˡ +服务器会将通信路径返回给客户端。 -ʱ Max-Forwards ײֵֶÿһͻ 1ֵΪ 0 ʱֹͣ䡣 +发送请求时,在 Max-Forwards 首部字段中填入数值,每经过一个服务器就会减 1,当数值为 0 时就停止传输。 -TRACE һ㲻ʹãܵ XST Cross-Site Tracingվ׷٣˸ȥʹ +TRACE 一般不会使用,并且它容易受到 XST 攻击(Cross-Site Tracing,跨站追踪),因此更不会去使用它。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/ca711108-e937-4d7d-99aa-61b325c61f1a.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//ca711108-e937-4d7d-99aa-61b325c61f1a.jpg) -## CONNECTҪЭӴ +## CONNECT:要求用隧道协议连接代理 -Ҫʹ SSLSecure Sokets Layerȫ׽֣ TLSTransport Layer Security㰲ȫЭͨݼܺ䡣 +主要使用 SSL(Secure Sokets Layer,安全套接字)和 TLS(Transport Layer Security,传输层安全)协议把通信内容加密后经网络隧道传输。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/d8355d56-aa2b-4452-8001-8475cc095af1.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//d8355d56-aa2b-4452-8001-8475cc095af1.jpg) -# HTTP ״̬ +# HTTP 状态码 -صӦеһΪ״̬У״̬Լԭ֪ͻĽ +服务器返回的响应报文中第一行为状态行,包含了状态码以及原因短语,来告知客户端请求的结果。 -| ״̬ | | ԭ | +| 状态码 | 类别 | 原因短语 | | --- | --- | --- | -| 1XX | InformationalϢ״̬룩 | յڴ | -| 2XX | Successɹ״̬룩 | | -| 3XX | Redirectionض״̬룩 | ҪиӲ | -| 4XX | Client Errorͻ˴״̬룩 | ޷ | -| 5XX | Server Error״̬룩 | | +| 1XX | Informational(信息性状态码) | 接收的请求正在处理 | +| 2XX | Success(成功状态码) | 请求正常处理完毕 | +| 3XX | Redirection(重定向状态码) | 需要进行附加操作以完成请求 | +| 4XX | Client Error(客户端错误状态码) | 服务器无法处理请求 | +| 5XX | Server Error(服务器错误状态码) | 服务器处理请求出错 | -## 2XX ɹ +## 2XX 成功 - **200 OK** -- **204 No Content**ѾɹǷصӦIJʵ岿֡һֻҪӿͻϢҪʱʹá +- **204 No Content**:请求已经成功处理,但是返回的响应报文不包含实体的主体部分。一般在只需要从客户端往服务器发送信息,而不需要返回数据时使用。 - **206 Partial Content** -## 3XX ض +## 3XX 重定向 -- **301 Moved Permanently**ض +- **301 Moved Permanently**:永久性重定向 -- **302 Found**ʱض +- **302 Found**:临时性重定向 - **303 See Other** -- עȻ HTTP Э涨 301302 ״̬ضʱ POST ij GET Ǵ 301302 303 ״̬µض POST ij GET +- 注:虽然 HTTP 协议规定 301、302 状态下重定向时不允许把 POST 方法改成 GET 方法,但是大多数浏览器都会 在 301、302 和 303 状态下的重定向把 POST 方法改成 GET 方法。 -- **304 Not Modified**ײһЩ磺If-MatchIf-ModifiedSinceIf-None-MatchIf-RangeIf-Unmodified-SinceDz᷵ 304 ״̬롣 +- **304 Not Modified**:如果请求报文首部包含一些条件,例如:If-Match,If-ModifiedSince,If-None-Match,If-Range,If-Unmodified-Since,但是不满足条件,则服务器会返回 304 状态码。 -- **307 Temporary Redirect**ʱض 302 ĺƣ 307 Ҫض POST ij GET +- **307 Temporary Redirect**:临时重定向,与 302 的含义类似,但是 307 要求浏览器不会把重定向请求的 POST 方法改成 GET 方法。 -## 4XX ͻ˴ +## 4XX 客户端错误 -- **400 Bad Request**д﷨ +- **400 Bad Request**:请求报文中存在语法错误 -- **401 Unauthorized**״̬ʾ͵Ҫͨ HTTP ֤BASIC ֤DIGEST ֤֤Ϣ֮ǰѽйһʾû֤ʧܡ +- **401 Unauthorized**:该状态码表示发送的请求需要有通过 HTTP 认证(BASIC 认证、DIGEST 认证)的认证信息。如果之前已进行过一次请求,则表示用户认证失败。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/b1b4cf7d-c54a-4ff1-9741-cd2eea331123.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//b1b4cf7d-c54a-4ff1-9741-cd2eea331123.jpg) -- **403 Forbidden**󱻾ܾûбҪܾϸɡ +- **403 Forbidden**:请求被拒绝,服务器端没有必要给出拒绝的详细理由。 - **404 Not Found** -## 5XX +## 5XX 服务器错误 -- **500 Internal Server Error**ִʱ +- **500 Internal Server Error**:服务器正在执行请求时发生错误 -- **503 Service Unavilable**״̬ʱڳػڽͣά޷ +- **503 Service Unavilable**:该状态码表明服务器暂时处于超负载或正在进行停机维护,现在无法处理请求。 -# HTTP ײ +# HTTP 首部 - 4 ͵ײֶΣͨײֶΡײֶΡӦײֶκʵײֶΡ +有 4 种类型的首部字段:通用首部字段、请求首部字段、响应首部字段和实体首部字段。 -ײֶμ京£Ҫȫǣģ +各种首部字段及其含义如下(不需要全记,仅供查阅): -## ͨײֶ +## 通用首部字段 -| ײֶ | ˵ | +| 首部字段名 | 说明 | | -- | -- | -| Cache-Control | ƻΪ | -| Connection | ײ ӵĹ | -| Date | ĵʱ | -| Pragma | ָ | -| Trailer | ĩ˵ײһ | -| Transfer-Encoding | ָĴ뷽ʽ | -| Upgrade | ΪЭ | -| Via | Ϣ | -| Warning | ֪ͨ | +| Cache-Control | 控制缓存的行为 | +| Connection | 逐跳首部、 连接的管理 | +| Date | 创建报文的日期时间 | +| Pragma | 报文指令 | +| Trailer | 报文末端的首部一览 | +| Transfer-Encoding | 指定报文主体的传输编码方式 | +| Upgrade | 升级为其他协议 | +| Via | 代理服务器的相关信息 | +| Warning | 错误通知 | -## ײֶ +## 请求首部字段 -| ײֶ | ˵ | +| 首部字段名 | 说明 | | -- | -- | -| Accept | ûɴý | -| Accept-Charset | ȵַ | -| Accept-Encoding | ȵݱ | -| Accept-Language | ȵԣȻԣ | -| Authorization | Web֤Ϣ | -| Expect | ڴضΪ | -| From | ûĵַ | -| Host | Դڷ | -| If-Match | ȽʵǣETag | -| If-Modified-Since | ȽԴĸʱ | -| If-None-Match | Ƚʵǣ If-Match ෴ | -| If-Range | Դδʱʵ Byte ķΧ | -| If-Unmodified-Since | ȽԴĸʱ䣨If-Modified-Since෴ | -| Max-Forwards | | -| Proxy-Authorization | Ҫͻ˵֤Ϣ | -| Range | ʵֽڷΧ | -| Referer | URI ԭʼȡ | -| TE | ȼ | -| User-Agent | HTTP ͻ˳Ϣ | +| Accept | 用户代理可处理的媒体类型 | +| Accept-Charset | 优先的字符集 | +| Accept-Encoding | 优先的内容编码 | +| Accept-Language | 优先的语言(自然语言) | +| Authorization | Web认证信息 | +| Expect | 期待服务器的特定行为 | +| From | 用户的电子邮箱地址 | +| Host | 请求资源所在服务器 | +| If-Match | 比较实体标记(ETag) | +| If-Modified-Since | 比较资源的更新时间 | +| If-None-Match | 比较实体标记(与 If-Match 相反) | +| If-Range | 资源未更新时发送实体 Byte 的范围请求 | +| If-Unmodified-Since | 比较资源的更新时间(与If-Modified-Since相反) | +| Max-Forwards | 最大传输逐跳数 | +| Proxy-Authorization | 代理服务器要求客户端的认证信息 | +| Range | 实体的字节范围请求 | +| Referer | 对请求中 URI 的原始获取方 | +| TE | 传输编码的优先级 | +| User-Agent | HTTP 客户端程序的信息 | -## Ӧײֶ +## 响应首部字段 -| ײֶ | ˵ | +| 首部字段名 | 说明 | | -- | -- | -| Accept-Ranges | ǷֽڷΧ | -| Age | Դʱ | -| ETag | ԴƥϢ | -| Location | ͻضָURI | -| Proxy-Authenticate | Կͻ˵֤Ϣ | -| Retry-After | ٴηʱҪ | -| Server | HTTPİװϢ | -| Vary | ĹϢ | -| WWW-Authenticate | Կͻ˵֤Ϣ | +| Accept-Ranges | 是否接受字节范围请求 | +| Age | 推算资源创建经过时间 | +| ETag | 资源的匹配信息 | +| Location | 令客户端重定向至指定URI | +| Proxy-Authenticate | 代理服务器对客户端的认证信息 | +| Retry-After | 对再次发起请求的时机要求 | +| Server | HTTP服务器的安装信息 | +| Vary | 代理服务器缓存的管理信息 | +| WWW-Authenticate | 服务器对客户端的认证信息 | -## ʵײֶ +## 实体首部字段 -| ײֶ | ˵ | +| 首部字段名 | 说明 | | -- | -- | -| Allow | Դֵ֧HTTP | -| Content-Encoding | ʵõı뷽ʽ | -| Content-Language | ʵȻ | -| Content-Length | ʵĴСλ ֽڣ | -| Content-Location | ӦԴURI | -| Content-MD5 | ʵıժҪ | -| Content-Range | ʵλ÷Χ | -| Content-Type | ʵý | -| Expires | ʵڵʱ | -| Last-Modified | Դ޸ʱ | +| Allow | 资源可支持的HTTP方法 | +| Content-Encoding | 实体主体适用的编码方式 | +| Content-Language | 实体主体的自然语言 | +| Content-Length | 实体主体的大小(单位: 字节) | +| Content-Location | 替代对应资源的URI | +| Content-MD5 | 实体主体的报文摘要 | +| Content-Range | 实体主体的位置范围 | +| Content-Type | 实体主体的媒体类型 | +| Expires | 实体主体过期的日期时间 | +| Last-Modified | 资源的最后修改日期时间 | -# Ӧ +# 具体应用 ## Cookie -HTTP Э״̬ģҪΪ HTTP Э龡ܼ򵥣ʹܹHTTP/1.1 Cookie ״̬Ϣ +HTTP 协议是无状态的,主要是为了让 HTTP 协议尽可能简单,使得它能够处理大量事务。HTTP/1.1 引入 Cookie 来保存状态信息。 -͵Ӧİ Set-Cookie ֶΣͻ˵õӦĺ Cookie ݱ浽С´ٷʱж Cookie ֵа Cookie ֶΣ֪ͻ˵״̬ϢˡCookie ״̬ϢڿͻУǷϡ +服务器发送的响应报文包含 Set-Cookie 字段,客户端得到响应报文后把 Cookie 内容保存到浏览器中。下次再发送请求时,从浏览器中读出 Cookie 值,在请求报文中包含 Cookie 字段,这样服务器就知道客户端的状态信息了。Cookie 状态信息保存在客户端浏览器中,而不是服务器上。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/ff17c103-750a-4bb8-9afa-576327023af9.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//ff17c103-750a-4bb8-9afa-576327023af9.png) -Set-Cookie ֶԣ +Set-Cookie 字段有以下属性: -| | ˵ | +| 属性 | 说明 | | -- | -- | -| NAME=VALUE | Cookie ƺֵ | -| expires=DATE | Cookie ЧڣȷָĬΪرǰΪֹ | -| path=PATH | ϵļĿ¼Ϊ Cookie öָĬΪĵڵļĿ¼ | -| domain= | Ϊ Cookie öָĬΪ Cookie ķ | -| Secure | HTTPS ȫͨʱŻᷢ Cookie | -| HttpOnly | ƣʹ Cookie ܱ JavaScript ű | +| NAME=VALUE | 赋予 Cookie 的名称和其值(必需项) | +| expires=DATE | Cookie 的有效期(若不明确指定则默认为浏览器关闭前为止) | +| path=PATH | 将服务器上的文件目录作为 Cookie 的适用对象(若不指定则默认为文档所在的文件目录) | +| domain=域名 | 作为 Cookie 适用对象的域名(若不指定则默认为创建 Cookie 的服务器的域名) | +| Secure | 仅在 HTTPS 安全通信时才会发送 Cookie | +| HttpOnly | 加以限制,使 Cookie 不能被 JavaScript 脚本访问 | -**Session Cookie ** +**Session 和 Cookie 区别** -Session ǷûһֶΣÿ Session һΨһʶSession IDһ Session ʱͻ˷͵ӦľͰ Set-Cookie ֶΣһΪ sid ļֵԣֵԾ Session IDͻյͰ Cookie У֮͵Ķ Session IDHTTP ͨ Session Cookie ַʽһʵָû״̬ģSession ڷˣCookie ڿͻˡ +Session 是服务器用来跟踪用户的一种手段,每个 Session 都有一个唯一标识:Session ID。当服务器创建了一个 Session 时,给客户端发送的响应报文就包含了 Set-Cookie 字段,其中有一个名为 sid 的键值对,这个键值对就是 Session ID。客户端收到后就把 Cookie 保存在浏览器中,并且之后发送的请求报文都包含 Session ID。HTTP 就是通过 Session 和 Cookie 这两种方式一起合作来实现跟踪用户状态的,Session 用于服务器端,Cookie 用于客户端。 -** Cookie ** +**浏览器禁用 Cookie 的情况** -ʹ URL д URL sid=xxx +会使用 URL 重写技术,在 URL 后面加上 sid=xxx 。 -**ʹ Cookie ʵûԶд** +**使用 Cookie 实现用户名和密码的自动填写** -վűԶ Cookie жȡû룬ӶʵԶд +网站脚本会自动从 Cookie 中读取用户名和密码,从而实现自动填写。 -## +## 缓存 -ֻ淽ôлÿͻл档 +有两种缓存方法:让代理服务器进行缓存和让客户端浏览器进行缓存。 -Cache-Control ڿƻΪ +Cache-Control 用于控制缓存的行为。 -Cache-Control: no-cache ֺ壬ǿͻ򻺴͵киָʾͻ˲ҪԴԴ򻺴͵ӦкиָʾܶԴл档 +Cache-Control: no-cache 有两种含义,如果是客户端向缓存服务器发送的请求报文中含有该指令,表示客户端不想要缓存的资源;如果是源服务器向缓存服务器发送的响应报文中含有该指令,表示缓存服务器不能对资源进行缓存。 -Expires ֶοڸ֪Դʲôʱڡײֶ Cache-Control ָ max-age ָʱײֶ Expiresȴ max-age ָ +Expires 字段可以用于告知缓存服务器该资源什么时候会过期。当首部字段 Cache-Control 有指定 max-age 指令时,比起首部字段 Expires,会优先处理 max-age 指令。 -## ־ +## 持久连接 -һͼƬ HTML ҳʱ HTML ҳԴͼƬԴÿһ HTTP ͨžҪϿһ TCP ӣӽͶϿĿܴ**־** ֻҪһ TCP Ӿܽж HTTP ͨšHTTP/1.1 ʼеĬ϶dz־ӡ +当浏览器访问一个包含多张图片的 HTML 页面时,除了请求访问 HTML 页面资源,还会请求图片资源,如果每进行一次 HTTP 通信就要断开一次 TCP 连接,连接建立和断开的开销会很大。**持久连接** 只需要进行一次 TCP 连接就能进行多次 HTTP 通信。HTTP/1.1 开始,所有的连接默认都是持久连接。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/c73a0b78-5f46-4d2d-a009-dab2a999b5d8.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//c73a0b78-5f46-4d2d-a009-dab2a999b5d8.jpg) -־Ҫʹ Connection ײֶνйHTTP/1.1 ʼ HTTP Ĭdz־ûӵģҪϿ TCP ӣҪɿͻ˻߷Ͽʹ Connection: close HTTP/1.1 ֮ǰĬǷdz־ûӵģҪάֳӣҪʹ Keep-Alive +持久连接需要使用 Connection 首部字段进行管理。HTTP/1.1 开始 HTTP 默认是持久化连接的,如果要断开 TCP 连接,需要由客户端或者服务器端提出断开,使用 Connection: close;而在 HTTP/1.1 之前默认是非持久化连接的,如果要维持持续连接,需要使用 Keep-Alive。 -߻ʽͬʱͶӦҪһȻȴӦ֮ٷһ +管线化方式可以同时发送多个请求和响应,而不需要发送一个请求然后等待响应之后再发下一个请求。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/6943e2af-5a70-4004-8bee-b33d60f39da3.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//6943e2af-5a70-4004-8bee-b33d60f39da3.jpg) -## +## 编码 -루EncodingҪΪ˶ʵѹõıУgzipcompressdeflateidentity identity ʾִѹıʽ +编码(Encoding)主要是为了对实体进行压缩。常用的编码有:gzip、compress、deflate、identity,其中 identity 表示不执行压缩的编码格式。 -## ֿ鴫 +## 分块传输 -ֿ鴫䣨Chunked Transfer Coding԰ݷָɶ飬ʾҳ档 +分块传输(Chunked Transfer Coding)可以把数据分割成多块,让浏览器逐步显示页面。 -## ಿֶ󼯺 +## 多部分对象集合 -һݱڿɺж͵ʵͬʱͣÿ֮ boundary ֶζķָзָÿֶײֶΡ +一份报文主体内可含有多类型的实体同时发送,每个部分之间用 boundary 字段定义的分隔符进行分隔;每个部分都可以有首部字段。 -磬ϴʱʹ·ʽ +例如,上传多个表单时可以使用如下方式: -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/decb0936-e83c-4a55-840a-fe8aa101ac61.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//decb0936-e83c-4a55-840a-fe8aa101ac61.png) -## Χ +## 范围请求 -жϣֻһݣΧʹÿͻֻܹδ͵DzݣӶ·ݡ +如果网络出现中断,服务器只发送了一部分数据,范围请求使得客户端能够只请求未发送的那部分数据,从而避免服务器端重新发送所有数据。 -ײ Range ֶΣȻָķΧ Range : bytes = 5001-10000ɹĻ 206 Partial Content ״̬ +在请求报文首部中添加 Range 字段,然后指定请求的范围,例如 Range : bytes = 5001-10000。请求成功的话服务器发送 206 Partial Content 状态。 -## Э +## 内容协商 -ͨЭ̷ʵݣĬѡ񷵻Ľ滹ӢĽ档 +通过内容协商返回最合适的内容,例如根据浏览器的默认语言选择返回中文界面还是英文界面。 -漰ײֶΣAcceptAccept-CharsetAccept-EncodingAccept-LanguageContent-Language +涉及以下首部字段:Accept、Accept-Charset、Accept-Encoding、Accept-Language、Content-Language。 -## +## 虚拟主机 -ʹʹһ̨ӵж߼ϿԿɶ +使用虚拟主机技术,使得一台服务器拥有多个域名,并且在逻辑上可以看成多个服务器。 -## ͨת +## 通信数据转发 -**** +**代理** -ܿͻ˵󣬲תһ͸ģı URL +代理服务器接受客户端的请求,并且转发给其它服务器。代理服务器一般是透明的,不会改变 URL。 -ʹôҪĿǣ桢ʿԼ¼־ +使用代理的主要目的是:缓存、网络访问控制以及记录访问日志。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/c07035c3-a9ba-4508-8e3c-d8ae4c6ee9ee.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//c07035c3-a9ba-4508-8e3c-d8ae4c6ee9ee.jpg) -**** +**网关** -ͬǣطὫ HTTP תΪЭͨţӶ HTTP ķ +与代理服务器不同的是,网关服务器会将 HTTP 转化为其它协议进行通信,从而请求其它非 HTTP 服务器的服务。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/81375888-6be1-476f-9521-42eea3e3154f.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//81375888-6be1-476f-9521-42eea3e3154f.jpg) -**** +**隧道** -ʹ SSL ȼֶΣΪͻ˺ͷ֮佨һȫͨ· +使用 SSL 等加密手段,为客户端和服务器之间建立一条安全的通信线路。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/64b95403-d976-421a-8b45-bac89c0b5185.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//64b95403-d976-421a-8b45-bac89c0b5185.jpg) # HTTPs -HTTP °ȫ⣺ +HTTP 有以下安全性问题: -1. ͨʹģݿܻᱻ -2. ֤ͨŷݣпαװ -3. ޷֤ĵԣп۸ġ +1. 通信使用明文,内容可能会被窃听; +2. 不验证通信方的身份,因此有可能遭遇伪装; +3. 无法证明报文的完整性,所以有可能已遭篡改。 -HTTPs Э飬 HTTP Ⱥ SSLSecure Socket Layerͨţ SSL TCP ͨšͨʹ SSLHTTPs ṩ˼ܡ֤Ա +HTTPs 并不是新协议,而是 HTTP 先和 SSL(Secure Socket Layer)通信,再由 SSL 和 TCP 通信。通过使用 SSL,HTTPs 提供了加密、认证和完整性保护。 -## +## 加密 -ּܷʽԳԿܺ͹ԿܡԳԿܵļܺͽʹͬһԿԿʹһԿڼܺͽܣֱΪԿ˽ԿԿ˶ԻãͨŷͷýշĹԿ֮󣬾ͿʹùԿмܣշյͨݺʹ˽Կܡ +有两种加密方式:对称密钥加密和公开密钥加密。对称密钥加密的加密和解密使用同一密钥,而公开密钥加密使用一对密钥用于加密和解密,分别为公开密钥和私有密钥。公开密钥所有人都可以获得,通信发送方获得接收方的公开密钥之后,就可以使用公开密钥进行加密,接收方收到通信内容后使用私有密钥解密。 -ԳԿܵȱ㣺޷ȫԿԿܵȱ㣺˵ʱ +对称密钥加密的缺点:无法安全传输密钥;公开密钥加密的缺点:相对来说更耗时。 -HTTPs **ϵļܻ**ʹùԿڴԳԿ֮ʹöԳԿܽͨšͼУԿԳԿ +HTTPs 采用 **混合的加密机制**,使用公开密钥加密用于传输对称密钥,之后使用对称密钥加密进行通信。(下图中,共享密钥即对称密钥) -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/110b1a9b-87cd-45c3-a21d-824623715b33.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//110b1a9b-87cd-45c3-a21d-824623715b33.jpg) -## ֤ +## 认证 -ͨʹ **֤** ͨŷ֤֤йԿݣ֤ԿȷͨŷģôͿȷͨŷǿɿġ +通过使用 **证书** 来对通信方进行认证。证书中有公开密钥数据,如果可以验证公开密钥的确属于通信方的,那么就可以确定通信方是可靠的。 -֤֤CACertificate Authority䷢ĹԿ֤飬ͨ CA ֤ +数字证书认证机构(CA,Certificate Authority)颁发的公开密钥证书,可以通过 CA 对其进行验证。 - HTTPs ͨʱ֤鷢͸ͻˣͻȡеĹԿ֮󣬾ͿԿʼ̡ܹ +进行 HTTPs 通信时,服务器会把证书发送给客户端,客户端取得其中的公开密钥之后,就可以开始加密过程。 -ʹ OpenSSL ׿Դÿ˶ԹһԼ֤ӶԼԼ䷢֤顣ڷʸ÷ʱʾ޷ȷӰȫԡ򡰸վİȫ֤⡱ȾϢ +使用 OpenSSL 这套开源程序,每个人都可以构建一套属于自己的认证机构,从而自己给自己颁发服务器证书。浏览器在访问该服务器时,会显示“无法确认连接安全性”或“该网站的安全证书存在问题”等警告消息。 -ͻ֤ҪûаװֻҵҪdzߵİȫʱʹÿͻ֤飬С +客户端证书需要用户自行安装,只有在业务需要非常高的安全性时才使用客户端证书,例如网上银行。 -## +## 完整性 -SSL ṩժҪ֤ԡ +SSL 提供摘要功能来验证完整性。 -# HTTP/1.0 HTTP/1.1 +# HTTP/1.0 与 HTTP/1.1 的区别 -- HTTP/1.1 Ĭdzӣ -- HTTP/1.1 ṩ˷Χܣ -- HTTP/1.1 ṩĹܣ -- HTTP/1.1 һЩ洦ֶΣ -- HTTP/1.1 һЩ״̬룻 +- HTTP/1.1 默认是长连接; +- HTTP/1.1 提供了范围请求功能; +- HTTP/1.1 提供了虚拟主机的功能; +- HTTP/1.1 多了一些缓存处理字段; +- HTTP/1.1 多了一些状态码; diff --git a/notes/JVM.md b/notes/JVM.md index 597eda7c..cb7ced4e 100644 --- a/notes/JVM.md +++ b/notes/JVM.md @@ -1,166 +1,167 @@ -* [ڴģ](#ڴģ) - * [1. ](#1-) - * [2. Java ջ](#2-java-ջ) - * [3. طջ](#3-طջ) - * [4. Java ](#4-java-) - * [5. ](#5-) - * [6. ʱ](#6-ʱ) - * [7. ֱڴ](#7-ֱڴ) -* [ռ](#ռ) - * [1. жһǷɻ](#1-жһǷɻ) - * [1.1 ü](#11-ü) - * [1.2 ɴ](#12-ɴ) - * [1.3 ](#13-) - * [1.3.1 ǿ](#131-ǿ) - * [1.3.2 ](#132-) - * [1.3.3 ](#133-) - * [1.3.4 ](#134-) - * [1.3 Ļ](#13-Ļ) +* [内存模型](#内存模型) + * [1. 程序计数器](#1-程序计数器) + * [2. Java 虚拟机栈](#2-java-虚拟机栈) + * [3. 本地方法栈](#3-本地方法栈) + * [4. Java 堆](#4-java-堆) + * [5. 方法区](#5-方法区) + * [6. 运行时常量池](#6-运行时常量池) + * [7. 直接内存](#7-直接内存) +* [垃圾收集](#垃圾收集) + * [1. 判断一个对象是否可回收](#1-判断一个对象是否可回收) + * [1.1 引用计数](#11-引用计数) + * [1.2 可达性](#12-可达性) + * [1.3 引用类型](#13-引用类型) + * [1.3.1 强引用](#131-强引用) + * [1.3.2 软引用](#132-软引用) + * [1.3.3 弱引用](#133-弱引用) + * [1.3.4 虚引用](#134-虚引用) + * [1.3 方法区的回收](#13-方法区的回收) * [1.4 finalize()](#14-finalize) - * [2. ռ㷨](#2-ռ㷨) - * [2.1 - 㷨](#21----㷨) - * [2.2 㷨](#22-㷨) - * [2.3 - 㷨](#23----㷨) - * [2.4 ִռ㷨](#24-ִռ㷨) - * [3. ռ](#3-ռ) - * [3.1 Serial ռ](#31-serial-ռ) - * [3.2 ParNew ռ](#32-parnew-ռ) - * [3.3 Parallel Scavenge ռ](#33-parallel-scavenge-ռ) - * [3.4 Serial Old ռ](#34-serial-old-ռ) - * [3.5 Parallel Old ռ](#35-parallel-old-ռ) - * [3.6 CMS ռ](#36-cms-ռ) - * [3.7 G1 ռ](#37-g1-ռ) - * [3.8 ռıȽ](#38-ռıȽ) - * [4. ڴղ](#4-ڴղ) - * [4.1 Eden ](#41--eden-) - * [4.2 ֱӽ](#42-ֱӽ) - * [4.3 ڴĶ](#43-ڴĶ) - * [4.4 ̬ж](#44-̬ж) - * [4.5 ռ䵣](#45-ռ䵣) - * [4.6 Full GC Ĵ](#46-full-gc-Ĵ) - * [4.6.1 System.gc()](#461--systemgc) - * [4.6.2 ռ䲻](#462-ռ䲻) - * [4.6.3 ռ䵣ʧ](#463-ռ䵣ʧ) - * [4.6.4 JDK 1.7 ǰôռ䲻](#464-jdk-17-ǰôռ䲻) + * [2. 垃圾收集算法](#2-垃圾收集算法) + * [2.1 标记 - 清除算法](#21-标记---清除算法) + * [2.2 复制算法](#22-复制算法) + * [2.3 标记 - 整理算法](#23-标记---整理算法) + * [2.4 分代收集算法](#24-分代收集算法) + * [3. 垃圾收集器](#3-垃圾收集器) + * [3.1 Serial 收集器](#31-serial-收集器) + * [3.2 ParNew 收集器](#32-parnew-收集器) + * [3.3 Parallel Scavenge 收集器](#33-parallel-scavenge-收集器) + * [3.4 Serial Old 收集器](#34-serial-old-收集器) + * [3.5 Parallel Old 收集器](#35-parallel-old-收集器) + * [3.6 CMS 收集器](#36-cms-收集器) + * [3.7 G1 收集器](#37-g1-收集器) + * [3.8 七种垃圾收集器的比较](#38-七种垃圾收集器的比较) + * [4. 内存分配与回收策略](#4-内存分配与回收策略) + * [4.1 优先在 Eden 分配](#41-优先在-eden-分配) + * [4.2 大对象直接进入老年代](#42-大对象直接进入老年代) + * [4.3 长期存活的对象进入老年代](#43-长期存活的对象进入老年代) + * [4.4 动态对象年龄判定](#44-动态对象年龄判定) + * [4.5 空间分配担保](#45-空间分配担保) + * [4.6 Full GC 的触发条件](#46-full-gc-的触发条件) + * [4.6.1 调用 System.gc()](#461-调用-systemgc) + * [4.6.2 老年代空间不足](#462-老年代空间不足) + * [4.6.3 空间分配担保失败](#463-空间分配担保失败) + * [4.6.4 JDK 1.7 及以前的永久代空间不足](#464-jdk-17-及以前的永久代空间不足) * [4.6.5 Concurrent Mode Failure](#465-concurrent-mode-failure) -* [ػ](#ػ) - * [1 ](#1-) - * [2. ʼʱ](#2-ʼʱ) - * [3. ع](#3-ع) - * [3.1 ](#31-) - * [3.2 ֤](#32-֤) - * [3.3 ׼](#33-׼) - * [3.4 ](#34-) - * [3.5 ʼ](#35-ʼ) - * [4. ](#4-) - * [4.1 ](#41-) - * [4.2 ](#42-) - * [4.3 ˫ίģ](#43-˫ίģ) -* [JVM ](#jvm-) - * [GC Ż](#gc-Ż) - * [GC ](#gc-) +* [类加载机制](#类加载机制) + * [1 类的生命周期](#1-类的生命周期) + * [2. 类初始化时机](#2-类初始化时机) + * [3. 类加载过程](#3-类加载过程) + * [3.1 加载](#31-加载) + * [3.2 验证](#32-验证) + * [3.3 准备](#33-准备) + * [3.4 解析](#34-解析) + * [3.5 初始化](#35-初始化) + * [4. 类加载器](#4-类加载器) + * [4.1 类与类加载器](#41-类与类加载器) + * [4.2 类加载器分类](#42-类加载器分类) + * [4.3 双亲委派模型](#43-双亲委派模型) +* [JVM 参数](#jvm-参数) + * [GC 优化配置](#gc-优化配置) + * [GC 类型设置](#gc-类型设置) -# ڴģ -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/dc695f48-4189-4fc7-b950-ed25f6c80f82.jpg) +# 内存模型 -עɫΪ߳˽еģɫΪ̹߳ġ +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//dc695f48-4189-4fc7-b950-ed25f6c80f82.jpg) -## 1. +注:白色区域为线程私有的,蓝色区域为线程共享的。 -¼ִеָֽĵִַе Native Ϊգ +## 1. 程序计数器 -## 2. Java ջ +记录正在执行的虚拟机字节码指令的地址(如果正在执行的是 Native 方法则为空)。 -ÿ Java ִеͬʱᴴһջ֡ڴ洢ֲջ̬ӡڵϢÿһӵֱִɵḶ́ͶӦһջ֡ Java ջջͳջĹ̡ +## 2. Java 虚拟机栈 -׳쳣 +每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。 -1. ߳ջȳֵ׳ StackOverflowError 쳣 -2. ջж̬չʱ޷뵼㹻ڴ棬׳ OutOfMemoryError 쳣 +该区域可能抛出以下异常: -## 3. طջ +1. 当线程请求的栈深度超过最大值,会抛出 StackOverflowError 异常; +2. 栈进行动态扩展时如果无法申请导足够内存,会抛出 OutOfMemoryError 异常。 - Java ջƣֻ֮DZطջΪط +## 3. 本地方法栈 -## 4. Java +与 Java 虚拟机栈类似,它们之间的区别只不过是本地方法栈为本地方法服务。 -жʵڴ档 +## 4. Java 堆 -ռҪ"GC "ռDz÷ִռ㷨Java ѻԷֳɣԷֳ Eden ռ䡢From Survivor ռ䡢To Survivor ռȣ +所有对象实例都在这里分配内存。 -Ҫڴ棬ͨ -Xmx -Xms ƶ̬չڴС̬չʧܻ׳ OutOfMemoryError 쳣 +这块区域是垃圾收集器管理的主要区域("GC 堆 ")。现在收集器基本都是采用分代收集算法,Java 堆还可以分成:新生代和老年代(新生代还可以分成 Eden 空间、From Survivor 空间、To Survivor 空间等)。 -## 5. +不需要连续内存,可以通过 -Xmx 和 -Xms 来控制动态扩展内存大小,如果动态扩展失败会抛出 OutOfMemoryError 异常。 -ڴѱصϢ̬ʱĴݡ +## 5. 方法区 - Java һҪڴ棬ҿԶ̬չ̬չʧһ׳ OutOfMemoryError 쳣 +用于存放已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。 -յҪĿǶԳصĻպͶжأһȽʵ֣HotSpot ôա +和 Java 堆一样不需要连续的内存,并且可以动态扩展,动态扩展失败一样会抛出 OutOfMemoryError 异常。 -## 6. ʱ +对这块区域进行垃圾回收的主要目标是对常量池的回收和对类的卸载,但是一般比较难实现,HotSpot 虚拟机把它当成永久代来进行垃圾回收。 -ʱǷһ֡ +## 6. 运行时常量池 -غClass ļеijأڴűɵĸͷãͻᱻŵ +运行时常量池是方法区的一部分。 -ڼҲù String intern() µij +类加载后,Class 文件中的常量池(用于存放编译期生成的各种字面量和符号引用)就会被放到这个区域。 -## 7. ֱڴ +在运行期间也可以用过 String 类的 intern() 方法将新的常量放入该区域。 - JDK 1.4 ¼ NIO ࣬һֻͨChannel뻺Buffer I/O ʽʹ Native ֱӷڴ棬Ȼͨһ洢 Java DirectByteBuffer ΪڴýвһЩܣΪ Java Ѻ Native ظݡ +## 7. 直接内存 -# ռ +在 JDK 1.4 中新加入了 NIO 类,引入了一种基于通道(Channel)与缓冲区(Buffer)的 I/O 方式,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆里的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆中来回复制数据。 -ջͱطջ߳˽еģ̵ֻ߳ڣ֮߳̽Ҳʧ˲Ҫա +# 垃圾收集 -Ҫ Java ѺͷС +程序计数器、虚拟机栈和本地方法栈这三个区域属于线程私有的,只存在于线程的生命周期内,线程结束之后也会消失,因此不需要对这三个区域进行垃圾回收。 -## 1. жһǷɻ +垃圾回收主要是针对 Java 堆和方法区进行。 -### 1.1 ü +## 1. 判断一个对象是否可回收 -һüһʱ 1ʧЧʱ 1 +### 1.1 引用计数 -üΪ 0 Ķɱա +给对象添加一个引用计数器,当对象增加一个引用时计数器加 1,引用失效时计数器减 1。 -ѭ⣬ʱüԶΪ 0 GC ռ޷ա +引用计数为 0 的对象可被回收。 + +两个对象会出现循环引用问题,此时引用计数器永远不为 0,导致 GC 收集器无法回收。 ```java objA.instance = objB; objB.instance = objA; ``` -### 1.2 ɴ +### 1.2 可达性 -ͨ GC Roots ΪʼܹﵽĶǶǿõģɴĶɱա +通过 GC Roots 作为起始点进行搜索,能够到达到的对象都是都是可用的,不可达的对象可被回收。 -GC Roots һݣ +GC Roots 一般包含以下内容: -1. ջõĶ -2. ྲ̬õĶ -3. еijõĶ -4. طջõĶ +1. 虚拟机栈中引用的对象 +2. 方法区中类静态属性引用的对象 +3. 方法区中的常量引用的对象 +4. 本地方法栈中引用的对象 -### 1.3 +### 1.3 引用类型 -ͨü㷨ж϶ͨɴԷ㷨ж϶ǷɴжǷ롰áйء +无论是通过引用计算算法判断对象的引用数量,还是通过可达性分析算法判断对象的引用链是否可达,判定独享是否存活都与“引用”有关。 -#### 1.3.1 ǿ +#### 1.3.1 强引用 -ֻҪǿôڣԶյõĶ +只要强引用存在,垃圾回收器永远不会回收调掉被引用的对象。 ```java Object obj = new Object(); ``` -#### 1.3.2 +#### 1.3.2 软引用 -DZãڴ֮ǰлա +非必须引用,内存溢出之前进行回收。 ```java Object obj = new Object(); @@ -169,14 +170,14 @@ obj = null; sf.get(); ``` -sf Ƕ obj һãͨ sf.get() ȡ󣬵Ȼ󱻱ΪҪյĶʱ򷵻 null +sf 是对 obj 的一个软引用,通过 sf.get() 方法可以取到这个对象,当然,当这个对象被标记为需要回收的对象时,则返回 null; -ҪûʵƻĹܣڴ㹻ֱͨȡֵӷæʵԴѯݣٶȣڴ治ʱԶɾⲿֻݣԴѯЩݡ +软引用主要用户实现类似缓存的功能,在内存足够的情况下直接通过软引用取值,无需从繁忙的真实来源查询数据,提升速度;当内存不足时,自动删除这部分缓存数据,从真正的来源查询这些数据。 -#### 1.3.3 +#### 1.3.3 弱引用 -ֻ浽һռ֮ǰռʱ۵ǰڴǷ㹻ᱻա +只能生存到下一次垃圾收集发生之前,当垃圾收集器工作时,无论当前内存是否足够,都会被回收。 ```java Object obj = new Object(); @@ -186,9 +187,9 @@ wf.get(); wf.isEnQueued(); ``` -#### 1.3.4 +#### 1.3.4 虚引用 -ֳΪû߻ӰãһǷõĴڣȫʱ乹Ӱ죬Ҳ޷ͨȡһʵΪһùΨһĿľռʱյһϵͳ֪ͨ +又称为幽灵引用或者幻影引用,一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。 ```java Object obj = new Object(); @@ -198,367 +199,367 @@ pf.get(); pf.isEnQueued(); ``` -### 1.3 Ļ +### 1.3 方法区的回收 -ڷҪǶԳصĻպͶжء +在方法区主要是对常量池的回收和对类的卸载。 -صĻպͶжơ +常量池的回收和堆中对象回收类似。 -жܶ࣬ҪҲһᱻжأ +类的卸载条件很多,需要满足以下三个条件,并且满足了也不一定会被卸载: -1. еʵѾգҲ Java вڸκʵ -2. ظ ClassLoader Ѿա -3. Ӧ java.lang.Class ûκεطãҲ޷κεطͨʸ෽ +1. 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。 +2. 加载该类的 ClassLoader 已经被回收。 +3. 该类对应的 java.lang.Class 对象没有在任何地方被引用,也就无法在任何地方通过反射访问该类方法。 -ͨ -Xnoclassgc Ƿжء +可以通过 -Xnoclassgc 参数来控制是否对类进行卸载。 -ڴʹ÷䡢̬CGLib ByteCode ܡ̬ JSP Լ OSGo ƵԶ ClassLoader ijҪ߱жعܣԱ֤ڴ +在大量使用反射、动态代理、CGLib 等 ByteCode 框架、动态生成 JSP 以及 OSGo 这类频繁自定义 ClassLoader 的场景都需要虚拟机具备类卸载功能,以保证不会出现内存溢出。 ### 1.4 finalize() -һɱʱöбҪִ finalize() ôпܿͨڸ÷ö±ãӶʵԾȡ +当一个对象可被回收时,如果该对象有必要执行 finalize() 方法,那么就有可能可能通过在该方法中让对象重新被引用,从而实现自救。 -finalize() C++ 鹹رⲿԴȹ try-finally ȷʽĸãҸ÷д۸߰ȷԴ޷֤ĵ˳òҪʹá +finalize() 类似 C++ 的虚构函数,用来做关闭外部资源等工作。但是 try-finally 等方式可以做的更好,并且该方法运行代价高昂,不确定性大,无法保证各个对象的调用顺序,因此最好不要使用。 -## 2. ռ㷨 +## 2. 垃圾收集算法 -### 2.1 - 㷨 +### 2.1 标记 - 清除算法 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/a4248c4b-6c1d-4fb8-a557-86da92d3a294.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//a4248c4b-6c1d-4fb8-a557-86da92d3a294.jpg) -ҪյĶбǣȻ +将需要回收的对象进行标记,然后清除。 -㣺 +不足: -1. ǺЧʶ -2. Ƭ +1. 标记和清除过程效率都不高 +2. 会产生大量碎片 -֮㷨ǻڸ㷨иĽ +之后的算法都是基于该算法进行改进。 -### 2.2 㷨 +### 2.2 复制算法 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/e6b733ad-606d-4028-b3e8-83c3a73a3797.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//e6b733ad-606d-4028-b3e8-83c3a73a3797.jpg) -ڴ滮ΪСȵ飬ÿֻʹһ飬һڴ˾ͽĶƵһ棬Ȼٰʹùڴռһ +将内存划分为大小相等的两块,每次只使用其中一块,当这一块内存用完了就将还存活的对象复制到另一块上面,然后再把使用过的内存空间进行一次清理。 -Ҫֻʹڴһ롣 +主要不足是只使用了内存的一半。 -ڵҵռ㷨Dzǽڴ滮ΪСȵ飬ǷΪһϴ Eden ռС Survior ռ䣬ÿʹ Eden ռһ Survivorڻʱ Eden Survivor лŵĶһԸƵһ Survivor ռϣ Eden SurvivorHotSpot Eden Survivor ĴСĬΪ 8:1֤ڴʴﵽ 90 %ÿλж 10% Ķôһ Survivor ռͲˣҪз䵣ҲǽĿռ䡣 +现在的商业虚拟机都采用这种收集算法来回收新生代,但是并不是将内存划分为大小相等的两块,而是分为一块较大的 Eden 空间和两块较小的 Survior 空间,每次使用 Eden 空间和其中一块 Survivor。在回收时,将 Eden 和 Survivor 中还存活着的对象一次性复制到另一块 Survivor 空间上,最后清理 Eden 和 Survivor。HotSpot 虚拟机的 Eden 和 Survivor 的大小比例默认为 8:1,保证了内存的利用率达到 90 %。如果每次回收有多于 10% 的对象存活,那么一块 Survivor 空间就不够用了,需要依赖于老年代进行分配担保,也就是借用老年代的空间。 -### 2.3 - 㷨 +### 2.3 标记 - 整理算法 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/902b83ab-8054-4bd2-898f-9a4a0fe52830.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//902b83ab-8054-4bd2-898f-9a4a0fe52830.jpg) -дĶһƶȻֱ˱߽ڴ档 +让所有存活的对象都向一段移动,然后直接清理掉端边界以外的内存。 -### 2.4 ִռ㷨 +### 2.4 分代收集算法 -ڵҵ÷ִռ㷨ʹǰܵļռ㷨ݶڽڴ滮Ϊ飬ͬʵռ㷨 +现在的商业虚拟机采用分代收集算法,它使用了前面介绍的几种收集算法,根据对象存活周期将内存划分为几块,不同块采用适当的收集算法。 -һ㽫 Java ѷΪ +一般将 Java 堆分为新生代和老年代。 -1. ʹã㷨 -2. ʹã - - 㷨 +1. 新生代使用:复制算法 +2. 老年代使用:标记 - 清理 或者 标记 - 整理 算法。 -## 3. ռ +## 3. 垃圾收集器 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/c625baa0-dde6-449e-93df-c3a67f2f430f.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//c625baa0-dde6-449e-93df-c3a67f2f430f.jpg) - HotSpot е 7 ռ߱ʾռʹá +以上是 HotSpot 虚拟机中的 7 个垃圾收集器,连线表示垃圾收集器可以配合使用。 -### 3.1 Serial ռ +### 3.1 Serial 收集器 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/22fda4ae-4dd5-489d-ab10-9ebfdad22ae0.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//22fda4ae-4dd5-489d-ab10-9ebfdad22ae0.jpg) -ǵ̵߳ռζֻʹһ߳̽ռҪڽռʱ̣ͣ߳ɹĵȴʱ䡣 +它是单线程的收集器,不仅意味着只会使用一个线程进行垃圾收集工作,更重要的是它在进行垃圾收集时,必须暂停所有其他工作线程,往往造成过长的等待时间。 -ŵǼ򵥸Чڵ CPU ˵û߳̽Ŀӵߵĵ߳ռЧʡ +它的优点是简单高效,对于单个 CPU 环境来说,由于没有线程交互的开销,因此拥有最高的单线程收集效率。 - Client ӦóУڴһ˵ܴ󣬸ռռʮһ׵ͣʱԿһٶڣֻҪ̫ƵͣǿԽܵġ +在 Client 应用场景中,分配给虚拟机管理的内存一般来说不会很大,该收集器收集几十兆甚至一两百兆的新生代停顿时间可以控制在一百多毫秒以内,只要不是太频繁,这点停顿是可以接受的。 -### 3.2 ParNew ռ +### 3.2 ParNew 收集器 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/81538cd5-1bcf-4e31-86e5-e198df1e013b.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//81538cd5-1bcf-4e31-86e5-e198df1e013b.jpg) - Serial ռĶ̰߳汾 +它是 Serial 收集器的多线程版本。 - Server ģʽµѡռԭ⣬ҪΪ Serial ռֻ CMS ռϹ +是 Server 模式下的虚拟机首选新生代收集器,除了性能原因外,主要是因为除了 Serial 收集器,只有它能与 CMS 收集器配合工作。 -ĬϿʼ߳ CPU ͬʹ -XX:ParallelGCThreads ߳ +默认开始的线程数量与 CPU 数量相同,可以使用 -XX:ParallelGCThreads 参数来设置线程数。 -### 3.3 Parallel Scavenge ռ +### 3.3 Parallel Scavenge 收集器 -DzеĶ߳ռ +是并行的多线程收集器。 -ռעǾռʱû̵߳ͣʱ䣬ĿǴﵽһɿƵΪȡռָ CPU ûʱռʱıֵ +其它收集器关注点是尽可能缩短垃圾收集时用户线程的停顿时间,而它的目标是达到一个可控制的吞吐量,它被称为“吞吐量优先”收集器。这里的吞吐量指 CPU 用于运行用户代码的时间占总时间的比值。 -ͣʱԽ̾ԽʺҪûijõӦٶû顣ԸЧʵ CPU ʱ䣬ɳҪʺں̨Ҫཻ̫ +停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户体验。而高吞吐量则可以高效率地利用 CPU 时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。 -ṩھȷֱǿռͣʱ -XX:MaxGCPauseMillis ԼֱС -XX:GCTimeRatio ֵΪ 0 С 100 ͣʱռȡģռСձƵ½ +提供了两个参数用于精确控制吞吐量,分别是控制最大垃圾收集停顿时间 -XX:MaxGCPauseMillis 参数以及直接设置吞吐量大小的 -XX:GCTimeRatio 参数(值为大于 0 且小于 100 的整数)。缩短停顿时间是以牺牲吞吐量和新生代空间来换取的:新生代空间变小,垃圾回收变得频繁,导致吞吐量下降。 -ṩһ -XX:+UseAdaptiveSizePolicyһز򿪲󣬾ͲҪָֹĴС-XmnEden Survivor ı-XX:SurvivorRatio䣨-XX:PretenureSizeThresholdϸڲˣݵǰϵͳռܼϢ̬ЩṩʵͣʱַʽΪ GC ӦĵڲԣGC ErgonomicsӦڲҲ ParNew ռһҪ +还提供了一个参数 -XX:+UseAdaptiveSizePolicy,这是一个开关参数,打开参数后,就不需要手工指定新生代的大小(-Xmn)、Eden 和 Survivor 区的比例(-XX:SurvivorRatio)、晋升老年代对象年龄(-XX:PretenureSizeThreshold)等细节参数了,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量,这种方式称为 GC 自适应的调节策略(GC Ergonomics)。自适应调节策略也是它与 ParNew 收集器的一个重要区别。 -### 3.4 Serial Old ռ +### 3.4 Serial Old 收集器 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/08f32fd3-f736-4a67-81ca-295b2a7972f2.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//08f32fd3-f736-4a67-81ca-295b2a7972f2.jpg) -Serial Old Serial ռ汾ҲǸ Client ģʽµʹá Server ģʽ£; +Serial Old 是 Serial 收集器的老年代版本,也是给 Client 模式下的虚拟机使用。如果用在 Server 模式下,它有两大用途: -1. JDK 1.5 Լ֮ǰ汾Parallel Old ǰ Parallel Scavenge ռʹá -2. Ϊ CMS ռĺԤڲռ Concurrent Mode Failure ʱʹá +1. 在 JDK 1.5 以及之前版本(Parallel Old 诞生以前)中与 Parallel Scavenge 收集器搭配使用。 +2. 作为 CMS 收集器的后备预案,在并发收集发生 Concurrent Mode Failure 时使用。 -### 3.5 Parallel Old ռ +### 3.5 Parallel Old 收集器 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/278fe431-af88-4a95-a895-9c3b80117de3.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//278fe431-af88-4a95-a895-9c3b80117de3.jpg) - Parallel Scavenge ռ汾 +是 Parallel Scavenge 收集器的老年代版本。 -עԼ CPU Դеijϣȿ Parallel Scavenge Parallel Old ռ +在注重吞吐量以及 CPU 资源敏感的场合,都可以优先考虑 Parallel Scavenge 加 Parallel Old 收集器。 -### 3.6 CMS ռ +### 3.6 CMS 收集器 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/62e77997-6957-4b68-8d12-bfd609bb2c68.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//62e77997-6957-4b68-8d12-bfd609bb2c68.jpg) -CMSConcurrent Mark Sweep Mark Sweep ֪ǻ - 㷨ʵֵġ +CMS(Concurrent Mark Sweep),从 Mark Sweep 可以知道它是基于 标记 - 清除 算法实现的。 -ص㣺ռͣ١ +特点:并发收集、低停顿。 -Ϊĸ̣ +分为以下四个流程: -1. ʼǣֻDZһ GC Roots ֱӹĶٶȺܿ죬Ҫͣ١ -2. ǣ GC Roots Tracing Ḷ́չкʱҪͣ١ -3. ±ǣΪڼû±Dz䶯һֶıǼ¼Ҫͣ١ -4. Ҫͣ١ +1. 初始标记:仅仅只是标记一下 GC Roots 能直接关联到的对象,速度很快,需要停顿。 +2. 并发标记:进行 GC Roots Tracing 的过程,它在整个回收过程中耗时最长,不需要停顿。 +3. 重新标记:为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,需要停顿。 +4. 并发清除:不需要停顿。 -кʱIJǺͲУռ̶߳û߳һҪͣ١ +在整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,不需要进行停顿。 -ȱ㣺 +具有以下缺点: -1. CPU ԴСCMS ĬĻ߳ (CPU + 3) / 4 CPU 4 ʱCMS ûӰͿܱúܴ CPU ؾͱȽϴ󣬻Ҫֳһȥִռ̣߳ͿܵûִٶȺȻ 50%ʵҲ޷ܡҵͣʱΪ۵ģ CPU ʱ͡ +1. 对 CPU 资源敏感。CMS 默认启动的回收线程数是 (CPU 数量 + 3) / 4,当 CPU 不足 4 个时,CMS 对用户程序的影响就可能变得很大,如果本来 CPU 负载就比较大,还要分出一半的运算能力去执行收集器线程,就可能导致用户程序的执行速度忽然降低了 50%,其实也让人无法接受。并且低停顿时间是以牺牲吞吐量为代价的,导致 CPU 利用率变低。 -2. ޷ڲ׶û̻߳ţȻͻµϲһڱǹ֮CMS ޷ڵռдǣֻһ GC ʱһͱΪҲռ׶û̻߳ҪУҲͻҪԤ㹻ڴռû߳ʹãռȵȫٽռҪԤһֿռṩռʱijʹáʹ -XX:CMSInitiatingOccupancyFraction ֵı䴥ռڴռðٷֱȣJDK 1.5 Ĭ¸ֵΪ 68Ҳǵʹ 68% Ŀռ֮ᴥռֵõ̫ߣ¸޷棬ôͻ Concurrent Mode FailureʱԤʱ Serial Old ռ½ռ +2. 无法处理浮动垃圾。由于并发清理阶段用户线程还在运行着,伴随程序运行自然就还会有新的垃圾不断产生。这一部分垃圾出现在标记过程之后,CMS 无法在当次收集中处理掉它们,只好留到下一次 GC 时再清理掉,这一部分垃圾就被称为“浮动垃圾”。也是由于在垃圾收集阶段用户线程还需要运行,那也就还需要预留有足够的内存空间给用户线程使用,因此它不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,需要预留一部分空间提供并发收集时的程序运作使用。可以使用 -XX:CMSInitiatingOccupancyFraction 的值来改变触发收集器工作的内存占用百分比,JDK 1.5 默认设置下该值为 68,也就是当老年代使用了 68% 的空间之后会触发收集器工作。如果该值设置的太高,导致浮动垃圾无法保存,那么就会出现 Concurrent Mode Failure,此时虚拟机将启动后备预案:临时启用 Serial Old 收集器来重新进行老年代的垃圾收集。 -3. - 㷨µĿռƬܴ鷳ռʣ࣬޷ҵ㹻ռ䵱ǰ󣬲òǰһ Full GC +3. 标记 - 清除算法导致的空间碎片,给大对象分配带来很大麻烦,往往出现老年代空间剩余,但无法找到足够大连续空间来分配当前对象,不得不提前出发一次 Full GC。 -### 3.7 G1 ռ +### 3.7 G1 收集器 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/f99ee771-c56f-47fb-9148-c0036695b5fe.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//f99ee771-c56f-47fb-9148-c0036695b5fe.jpg) -G1Garbage-Firstռǵռչǰصijɹ֮һһӦõռHotSpot ŶӸʹǣڱȽϳڵģδ滻 JDK 1.5 з CMS ռ +G1(Garbage-First)收集器是当今收集器技术发展最前沿的成果之一,它是一款面向服务端应用的垃圾收集器,HotSpot 开发团队赋予它的使命是(在比较长期的)未来可以替换掉 JDK 1.5 中发布的 CMS 收集器。 -߱ص㣺 +具备如下特点: -- 벢ܳö CPU µӲƣʹö CPU ͣʱ䣻 -- ִռִȻԱȻҪռϾܶ GC ѣܹòͬʽȥ´ĶѴһʱ䡢 GC ľɶȡõռЧ -- ռϣǻڡ - 㷨ʵֵռӾֲ Region ֮䣩ǻڡơ㷨ʵֵģζڼ䲻ڴռƬ -- Ԥͣ٣ CMS һƣͣʱ G1 CMS ͬĹע㣬 G1 ˽ͣ⣬ܽԤͣʱģͣʹȷָһΪ M ʱƬڣ GC ϵʱ䲻ó N 룬⼸Ѿʵʱ JavaRTSJռˡ +- 并行与并发:能充分利用多 CPU 环境下的硬件优势,使用多个 CPU 来缩短停顿时间; +- 分代收集:分代概念依然得以保留,虽然它不需要其它收集器配合就能独立管理整个 GC 堆,但它能够采用不同方式去处理新创建的对象和已存活一段时间、熬过多次 GC 的旧对象来获取更好的收集效果。 +- 空间整合:整体来看是基于“标记 - 整理”算法实现的收集器,从局部(两个 Region 之间)上来看是基于“复制”算法实现的,这意味着运行期间不会产生内存空间碎片。 +- 可预测的停顿:这是它相对 CMS 的一大优势,降低停顿时间是 G1 和 CMS 共同的关注点,但 G1 除了降低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为 M 毫秒的时间片段内,消耗在 GC 上的时间不得超过 N 毫秒,这几乎已经是实时 Java(RTSJ)的垃圾收集器的特征了。 - G1 ֮ǰռռķΧ G1 Java ѵڴ沼ռкܴ𣬽 Java ѻΪСȵĶRegionȻĸˣһ RegionҪļϡ +在 G1 之前的其他收集器进行收集的范围都是整个新生代或者老生代,而 G1 不再是这样,Java 堆的内存布局与其他收集器有很大区别,将整个 Java 堆划分为多个大小相等的独立区域(Region)。虽然还保留新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,而都是一部分 Region(不需要连续)的集合。 -֮ܽԤͣʱģͣΪмƻر Java нȫռٸ Region ѻļֵСõĿռСԼʱľֵں̨άһбÿθռʱ䣬Ȼռֵ RegionҲ Garbage-First Ƶɣʹ Region ڴռԼȼշʽ֤޵ʱڿԻȡܸߵռЧʡ +之所以能建立可预测的停顿时间模型,是因为它可以有计划地避免在整个 Java 堆中进行全区域的垃圾收集。它跟踪各个 Region 里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的 Region(这也就是 Garbage-First 名称的来由)。这种使用 Region 划分内存空间以及有优先级的区域回收方式,保证了它在有限的时间内可以获取尽可能高的收集效率。 -Region ǹģһij Region У Java ĶùϵɴԷȷǷʱҪɨ Java Ѳܱ֤׼ȷԣȻǶ GC Чʵļ˺Ϊ˱ȫɨķÿ Region άһ֮Ӧ Remembered Setֳڶ Reference ͵ݽдʱһ Write Barrier ʱжд Reference õĶǷڲͬ Region ֮Уǣͨ CardTable Ϣ¼ö Region Remembered Set ֮Сڴʱ GC ڵöٷΧм Remembered Set ɱ֤ȫɨҲ© +Region 不可能是孤立的,一个对象分配在某个 Region 中,可以与整个 Java 堆任意的对象发生引用关系。在做可达性分析确定对象是否存活的时候,需要扫描整个 Java 堆才能保证准确性,这显然是对 GC 效率的极大伤害。为了避免全堆扫描的发生,每个 Region 都维护了一个与之对应的 Remembered Set。虚拟机发现程序在对 Reference 类型的数据进行写操作时,会产生一个 Write Barrier 暂时中断写操作,检查 Reference 引用的对象是否处于不同的 Region 之中,如果是,便通过 CardTable 把相关引用信息记录到被引用对象所属的 Region 的 Remembered Set 之中。当进行内存回收时,在 GC 根节点的枚举范围中加入 Remembered Set 即可保证不对全堆扫描也不会有遗漏。 -ά Remembered Set IJG1 ռ¿ɻΪ¼裺 +如果不计算维护 Remembered Set 的操作,G1 收集器的运作大致可划分为以下几个步骤: -1. ʼ -2. -3. ձǣΪڲڼû±Dz䶯һֱǼ¼ʱ仯¼̵߳ Remembered Set Logs 棬ձǽ׶Ҫ Remembered Set Logs ݺϲ Remembered Set С׶Ҫ̣ͣ߳ǿɲִС -4. ɸѡգȶԸ Region еĻռֵͳɱ򣬸û GC ͣʱƶռƻ˽׶ʵҲûһ𲢷ִУΪֻһ RegionʱûɿƵģͣû߳̽ռЧʡ +1. 初始标记 +2. 并发标记 +3. 最终标记:为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程的 Remembered Set Logs 里面,最终标记阶段需要把 Remembered Set Logs 的数据合并到 Remembered Set 中。这阶段需要停顿线程,但是可并行执行。 +4. 筛选回收:首先对各个 Region 中的回收价值和成本进行排序,根据用户所期望的 GC 停顿是时间来制定回收计划。此阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分 Region,时间是用户可控制的,而且停顿用户线程将大幅度提高收集效率。 -### 3.8 ռıȽ +### 3.8 七种垃圾收集器的比较 -| ռ | С or | / | 㷨 | Ŀ | ó | +| 收集器 | 串行、并行 or 并发 | 新生代 / 老年代 | 算法 | 目标 | 适用场景 | | --- | --- | --- | --- | --- | --- | -| **Serial** | | | 㷨 | Ӧٶ | CPU µ Client ģʽ | -| **Serial Old** | | | - | Ӧٶ | CPU µ Client ģʽCMS ĺԤ | -| **ParNew** | | | 㷨 | Ӧٶ | CPU ʱ Server ģʽ CMS | -| **Parallel Scavenge** | | | 㷨 | | ں̨Ҫཻ̫ | -| **Parallel Old** | | | - | | ں̨Ҫཻ̫ | -| **CMS** | | | - | Ӧٶ | ڻվ B/S ϵͳϵ Java Ӧ | -| **G1** | | both | - + 㷨 | Ӧٶ | Ӧã滻 CMS | +| **Serial** | 串行 | 新生代 | 复制算法 | 响应速度优先 | 单 CPU 环境下的 Client 模式 | +| **Serial Old** | 串行 | 老年代 | 标记 - 整理 | 响应速度优先 | 单 CPU 环境下的 Client 模式、CMS 的后备预案 | +| **ParNew** | 并行 | 新生代 | 复制算法 | 响应速度优先 | 多 CPU 环境时在 Server 模式下与 CMS 配合 | +| **Parallel Scavenge** | 并行 | 新生代 | 复制算法 | 吞吐量优先 | 在后台运算而不需要太多交互的任务 | +| **Parallel Old** | 并行 | 老年代 | 标记 - 整理 | 吞吐量优先 | 在后台运算而不需要太多交互的任务 | +| **CMS** | 并发 | 老年代 | 标记 - 清除 | 响应速度优先 | 集中在互联网站或 B/S 系统服务端上的 Java 应用 | +| **G1** | 并发 | both | 标记 - 整理 + 复制算法 | 响应速度优先 | 面向服务端应用,将来替换 CMS | -## 4. ڴղ +## 4. 内存分配与回收策略 -### 4.1 Eden +### 4.1 优先在 Eden 分配 -£ Eden 䣬 Eden ռ䲻ʱ Minor GC +大多数情况下,对象在新生代 Eden 区分配,当 Eden 区空间不够时,发起 Minor GC; -### 4.2 ֱӽ +### 4.2 大对象直接进入老年代 -ṩ -XX:PretenureSizeThreshold ڴֵĶֱ䣬 Eden Survivor ֮Ĵڴ渴ƣ -### 4.3 ڴĶ +提供 -XX:PretenureSizeThreshold 参数,大于此值的对象直接在老年代分配,避免在 Eden 区和 Survivor 区之间的大量内存复制; +### 4.3 长期存活的对象进入老年代 -JVM Ϊ Minor GC Ȼұ Survivor ɵģƶ Survivor 1ÿһ Minor GC 1ӵһƶĬ 15 ꣬ͨ -XX:MaxTenuringThreshold ã +JVM 为对象定义年龄计数器,经过 Minor GC 依然存活且被 Survivor 区容纳的,移动到 Survivor 区,年龄加 1,每经历一次 Minor GC 不被清理则年龄加 1,增加到一定年龄则移动到老年区(默认 15 岁,通过 -XX:MaxTenuringThreshold 设置); -### 4.4 ̬ж +### 4.4 动态对象年龄判定 - Survivor ͬжСܺʹ Survivor ռһ룬ڵڸĶֱӽ +若 Survivor 区中同年龄所有对象大小总和大于 Survivor 空间一半,则年龄大于等于该年龄的对象可以直接进入老年代; -### 4.5 ռ䵣 +### 4.5 空间分配担保 -ڷ Minor GC ֮ǰJVM ȼռǷжܿռ䣬Ļ Minor GC ȷǰȫģռǷνƽСڵĻ Minor GCСڵĻ Full GC +在发生 Minor GC 之前,JVM 先检查老年代最大可用连续空间是否大于新生代所有对象总空间,成立的话 Minor GC 确认是安全的;否则继续检查老年代最大可用连续空间是否大于历次晋升到老年代对象的平均大小,大于的话进行 Minor GC,小于的话进行 Full GC。 -## 4.6 Full GC Ĵ +## 4.6 Full GC 的触发条件 - Minor GC䴥dz򵥣 Eden ռʱͽһ Minor GC Full GC Ըӣ +对于 Minor GC,其触发条件非常简单,当 Eden 区空间满时,就将触发一次 Minor GC。而 Full GC 则相对复杂,有以下条件: -### 4.6.1 System.gc() +### 4.6.1 调用 System.gc() -˷ĵǽ JVM Full GCȻֻǽһܶᴥ Full GCӶ Full GC ƵʣҲ˼ЪͣٵĴǿҽܲʹô˷ͲҪʹãԼȥڴ棬ͨ -XX:+ DisableExplicitGC ֹ RMI System.gc() +此方法的调用是建议 JVM 进行 Full GC,虽然只是建议而非一定,但很多情况下它会触发 Full GC,从而增加 Full GC 的频率,也即增加了间歇性停顿的次数。因此强烈建议能不使用此方法就不要使用,让虚拟机自己去管理它的内存,可通过 -XX:+ DisableExplicitGC 来禁止 RMI 调用 System.gc()。 -### 4.6.2 ռ䲻 +### 4.6.2 老年代空间不足 -ռ䲻ijΪǰĴֱӽڴĶȣִ Full GC ռȻ㣬׳´ Java.lang.OutOfMemoryError: Java heap space Ϊ״ Full GCʱӦö Minor GC ׶αաöһʱ估ҪĶ顣 +老年代空间不足的常见场景为前文所讲的大对象直接进入老年代、长期存活的对象进入老年代等,当执行 Full GC 后空间仍然不足,则抛出如下错误: Java.lang.OutOfMemoryError: Java heap space 为避免以上两种状况引起的 Full GC,调优时应尽量做到让对象在 Minor GC 阶段被回收、让对象在新生代多存活一段时间及不要创建过大的对象及数组。 -### 4.6.3 ռ䵣ʧ +### 4.6.3 空间分配担保失败 -ʹø㷨 Minor GC Ҫڴռ HandlePromotionFailure ʧܣᴥ Full GC +使用复制算法的 Minor GC 需要老年代的内存空间作担保,如果出现了 HandlePromotionFailure 担保失败,则会触发 Full GC。 -### 4.6.4 JDK 1.7 ǰôռ䲻 +### 4.6.4 JDK 1.7 及以前的永久代空间不足 - JDK 1.7 ǰHotSpot еķôʵֵģôдŵΪһЩ class Ϣ̬ݣϵͳҪصࡢ͵õķ϶ʱPermanet Generation ܻᱻռδΪ CMS GC Ҳִ Full GC Full GC Ȼղˣô JVM ׳´Ϣjava.lang.OutOfMemoryError: PermGen space Ϊ PermGen ռ Full GC 󣬿ɲõķΪ PermGen ռתΪʹ CMS GC +在 JDK 1.7 及以前,HotSpot 虚拟机中的方法区是用永久代实现的,永久代中存放的为一些 class 的信息、常量、静态变量等数据,当系统中要加载的类、反射的类和调用的方法较多时,Permanet Generation 可能会被占满,在未配置为采用 CMS GC 的情况下也会执行 Full GC。如果经过 Full GC 仍然回收不了,那么 JVM 会抛出如下错误信息:java.lang.OutOfMemoryError: PermGen space 为避免 PermGen 占满造成 Full GC 现象,可采用的方法为增大 PermGen 空间或转为使用 CMS GC。 - JDK 1.8 Ԫռ滻ôΪʵ֣ԪռDZڴ棬˼һ Full GC Ŀԡ +在 JDK 1.8 中用元空间替换了永久代作为方法区的实现,元空间是本地内存,因此减少了一种 Full GC 触发的可能性。 ### 4.6.5 Concurrent Mode Failure -ִ CMS GC ĹͬʱжҪʱռ䲻㣨ʱ򡰿ռ䲻㡱 CMS GC ʱǰĸർʱԵĿռ䲻㴥 Full GCᱨ Concurrent Mode Failure 󣬲 Full GC +执行 CMS GC 的过程中同时有对象要放入老年代,而此时老年代空间不足(有时候“空间不足”是 CMS GC 时当前的浮动垃圾过多导致暂时性的空间不足触发 Full GC),便会报 Concurrent Mode Failure 错误,并触发 Full GC。 -# ػ +# 类加载机制 -ڼ䶯̬صġ +类是在运行期间动态加载的。 -## 1 +## 1 类的生命周期 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/32b8374a-e822-4720-af0b-c0f485095ea2.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//32b8374a-e822-4720-af0b-c0f485095ea2.jpg) - 7 ׶Σ +包括以下 7 个阶段: -- **أLoading** -- **֤Verification** -- **׼Preparation** -- **Resolution** -- **ʼInitialization** -- ʹãUsing -- жأUnloading +- **加载(Loading)** +- **验证(Verification)** +- **准备(Preparation)** +- **解析(Resolution)** +- **初始化(Initialization)** +- 使用(Using) +- 卸载(Unloading) -нijЩ¿ڳʼ׶֮ٿʼΪ֧ Java Ķ̬󶨡 +其中解析过程在某些情况下可以在初始化阶段之后再开始,这是为了支持 Java 的动态绑定。 -## 2. ʼʱ +## 2. 类初始化时机 -淶вûǿԼʱмأǹ淶ϸ涨ֻгʼ( ء֤׼ŷ ) +虚拟机规范中并没有强制约束何时进行加载,但是规范严格规定了有且只有下列五种情况必须对类进行初始化:( 加载、验证、准备都会随着发生 ) -1. newgetstaticputstaticinvokestatic ָֽʱûнйʼȴʼ 4 ָijǣʹ new ؼʵʱ򣻶ȡһľֶ̬Σ final Ρڱѽ볣صľֶ̬γ⣩ʱԼһľ̬ʱ +1. 遇到 new、getstatic、putstatic、invokestatic 这四条字节码指令时,如果类没有进行过初始化,则必须先触发其初始化。最常见的生成这 4 条指令的场景是:使用 new 关键字实例化对象的时候;读取或设置一个类的静态字段(被 final 修饰、已在编译器把结果放入常量池的静态字段除外)的时候;以及调用一个类的静态方法的时候。 -2. ʹ java.lang.reflect ķзõʱûнгʼҪȴʼ +2. 使用 java.lang.reflect 包的方法对类进行反射调用的时候,如果类没有进行初始化,则需要先触发其初始化。 -3. ʼһʱ丸໹ûнйʼҪȴ丸ijʼ +3. 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。 -4. ʱûҪָһҪִеࣨ main() Ǹࣩȳʼࣻ +4. 当虚拟机启动时,用户需要指定一个要执行的主类(包含 main() 方法的那个类),虚拟机会先初始化这个主类; -5. ʹ jdk1.7 Ķ̬֧ʱһ java.lang.invoke.MethodHandle ʵĽΪ REF_getStatic, REF_putStatic, REF_invokeStatic ķӦûнйʼҪȴʼ +5. 当使用 jdk1.7 的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果为 REF_getStatic, REF_putStatic, REF_invokeStatic 的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化; - 5 ֳеΪΪһá֮⣬ķʽᴥʼΪáõijӰ +以上 5 种场景中的行为称为对一个类进行主动引用。除此之外,所有引用类的方式都不会触发初始化,称为被动引用。被动引用的常见例子包括: -1\. ͨøľֶ̬Σᵼʼ +1\. 通过子类引用父类的静态字段,不会导致子类初始化。 ```java -System.out.println(SubClass.value); // value ֶ SuperClass ж +System.out.println(SubClass.value); // value 字段在 SuperClass 中定义 ``` -2\. ͨ鶨࣬ᴥijʼù̻гʼһԶɵġֱӼ̳ Object ࣬аԺͷ +2\. 通过数组定义来引用类,不会触发此类的初始化。该过程会对数组类进行初始化,数组类是一个由虚拟机自动生成的、直接继承自 Object 的子类,其中包含了数组的属性和方法。 ```java SuperClass[] sca = new SuperClass[10]; ``` -3\. ڱ׶λijУϲûֱõ峣࣬˲ᴥ峣ijʼ +3\. 常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化。 ```java System.out.println(ConstClass.HELLOWORLD); ``` -## 3. ع +## 3. 类加载过程 -˼ء֤׼ͳʼ 5 ׶Ρ +包含了加载、验证、准备、解析和初始化这 5 个阶段。 -### 3.1 +### 3.1 加载 -صһ׶ΣעⲻҪ +加载是类加载的一个阶段,注意不要混淆。 -ع£ +加载过程完成以下三件事: -1. ͨһȫ޶ȡĶֽ -2. ֽľ̬洢ṹתΪʱ洢ṹ -3. ڴһ Class Ϊĸݵķڡ +1. 通过一个类的全限定名来获取定义此类的二进制字节流。 +2. 将这个字节流所代表的静态存储结构转化为方法区的运行时存储结构。 +3. 在内存中生成一个代表这个类的 Class 对象,作为方法区这个类的各种数据的访问入口。 -жֽԴ·ʽлȡ +其中二进制字节流可以从以下方式中获取: -- ZIP ȡܳճΪպ JAREARWAR ʽĻ -- лȡֳ͵Ӧ Applet -- ʱɣֳʹõþǶ̬ java.lang.reflect.Proxy У ProxyGenerator.generateProxyClass ĴĶֽ -- ļɣͳ JSP Ӧã JSP ļɶӦ Class ࡣ -- ݿȡֳټЩм SAP NetweaverѡѳװݿɳڼȺķַ +- 从 ZIP 包读取,这很常见,最终成为日后 JAR、EAR、WAR 格式的基础。 +- 从网络中获取,这种场景最典型的应用是 Applet。 +- 运行时计算生成,这种场景使用得最多得就是动态代理技术,在 java.lang.reflect.Proxy 中,就是用了 ProxyGenerator.generateProxyClass 的代理类的二进制字节流。 +- 由其他文件生成,典型场景是 JSP 应用,即由 JSP 文件生成对应的 Class 类。 +- 从数据库读取,这种场景相对少见,例如有些中间件服务器(如 SAP Netweaver)可以选择把程序安装到数据库中来完成程序代码在集群间的分发。 ... -### 3.2 ֤ +### 3.2 验证 -ȷ Class ļֽаϢϵǰҪ󣬲ҲΣİȫ +确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。 -Ҫ 4 ׶Σ +主要有以下 4 个阶段: -1. ļʽ֤ -2. Ԫֽ֤Ϣ -3. ֽ֤ͨͿȷǺϷ߼ģķУ -4. ֤ +1. 文件格式验证 +2. 元数据验证(对字节码描述的信息进行语义分析) +3. 字节码验证(通过数据流和控制流分析,确保程序语义是合法、符合逻辑的,将对类的方法体进行校验分析) +4. 符号引用验证 -### 3.3 ׼ +### 3.3 准备 -DZ static εı׼׶Ϊڴ沢óʼֵʹõǷڴ档 +类变量是被 static 修饰的变量,准备阶段为类变量分配内存并设置初始值,使用的是方法区的内存。 -ʵ׶ηڴ棬ڶʵʱŶһ Java С +实例变量不会在这阶段分配内存,它将会在对象实例化时随着对象一起分配在 Java 堆中。 -ʼֵһΪ 0 ֵ value ʼΪ 0 123 +初始值一般为 0 值,例如下面的类变量 value 被初始化为 0 而不是 123。 ```java public static int value = 123; ``` -dzôᰴձʽгʼǸֵΪ 0 +如果类变量是常量,那么会按照表达式来进行初始化,而不是赋值为 0。 ```java public static final int value = 123; ``` -### 3.4 +### 3.4 解析 -صķ滻ΪֱõĹ̡ +将常量池的符号引用替换为直接引用的过程。 -### 3.5 ʼ +### 3.5 初始化 -ʼ׶μִ๹ <clinit>() Ĺ̡ +初始化阶段即虚拟机执行类构造器 <clinit>() 方法的过程。 -׼׶ΣѾһϵͳҪijʼֵڳʼ׶ΣݳԱͨƶۼƻȥʼԴ +在准备阶段,类变量已经赋过一次系统要求的初始值,而在初始化阶段,根据程序员通过程序制定的主观计划去初始化类变量和其它资源。 -<clinit>() ص㣺 +<clinit>() 方法具有以下特点: -- ɱԶռĸֵ;̬飨static{} 飩еϲģռ˳Դļгֵ˳رעǣֻ̬ܷʵ֮ǰֵֻܸܷ֮ʡ´룺 +- 是由编译器自动收集类中所有类变量的赋值动作和静态语句块(static{} 块)中的语句合并产生的,编译器收集的顺序由语句在源文件中出现的顺序决定。特别注意的是,静态语句块只能访问到定义在它之前的类变量,定义在它之后的类变量只能赋值,不能访问。例如以下代码: ```java public class Test { static { - i = 0; // ֵͨ - System.out.print(i); // ʾǷǰá + i = 0; // 给变量赋值可以正常编译通过 + System.out.print(i); // 这句编译器会提示“非法向前引用” } static int i = 1; } ``` -- Ĺ캯˵ʵ <init>()ͬҪʽĵøĹԶ֤ <clinit>() ֮ǰ <clinit>() Ѿִнеһִ <clinit>() ϶Ϊ java.lang.Object +- 与类的构造函数(或者说实例构造器 <init>())不同,不需要显式的调用父类的构造器。虚拟机会自动保证在子类的 <clinit>() 方法运行之前,父类的 <clinit>() 方法已经执行结束。因此虚拟机中第一个执行 <clinit>() 方法的类肯定为 java.lang.Object。 -- ڸ <clinit>() ִУҲζŸжľ̬Ҫıֵ´룺 +- 由于父类的 <clinit>() 方法先执行,也就意味着父类中定义的静态语句块要优于子类的变量赋值操作。例如以下代码: ```java static class Parent { @@ -573,53 +574,53 @@ static class Sub extends Parent { } public static void main(String[] args) { - System.out.println(Sub.B); // Ǹеľֵ̬ AҲ 2 + System.out.println(Sub.B); // 输出结果是父类中的静态变量值 A,也就是 2 } ``` -- <clinit>() ӿڲDZģһв̬飬ҲûжĸֵԲΪ <clinit>() +- <clinit>() 方法对于类或接口不是必须的,如果一个类中不包含静态语句块,也没有对类变量的赋值操作,编译器可以不为该类生成 <clinit>() 方法。 -- ӿвʹþ̬飬Ȼʼĸֵ˽ӿһ <clinit>() ӿ಻ͬǣִнӿڵ <clinit>() Ҫִиӿڵ <clinit>() ֻеӿжıʹʱӿڲŻʼ⣬ӿڵʵڳʼʱҲһִнӿڵ <clinit>() +- 接口中不可以使用静态语句块,但仍然有类变量初始化的赋值操作,因此接口与类一样都会生成 <clinit>() 方法。但接口与类不同的是,执行接口的 <clinit>() 方法不需要先执行父接口的 <clinit>() 方法。只有当父接口中定义的变量使用时,父接口才会初始化。另外,接口的实现类在初始化时也一样不会执行接口的 <clinit>() 方法。 -- ᱣ֤һ <clinit>() ڶ̻߳±ȷļͬ߳ͬʱʼһֻ࣬һִ߳ <clinit>() ̶߳ȴֱִ߳ <clinit>() ϡһ <clinit>() кʱIJͿɶʵʹдΡ +- 虚拟机会保证一个类的 <clinit>() 方法在多线程环境下被正确的加锁和同步,如果多个线程同时初始化一个类,只会有一个线程执行这个类的 <clinit>() 方法,其它线程都会阻塞等待,直到活动线程执行 <clinit>() 方法完毕。如果在一个类的 <clinit>() 方法中有耗时的操作,就可能造成多个进程阻塞,在实际过程中此种阻塞很隐蔽。 -## 4. +## 4. 类加载器 -ŶӰؽ׶еġͨһȫ޶ȡĶֽ ( ֽ )ŵ Java ⲿȥʵ֣ԱӦóԼȥȡҪࡣʵĴģΪ +虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流 ( 即字节码 )”这个动作放到 Java 虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为“类加载器”。 -### 4.1 +### 4.1 类与类加载器 -һ࣬Ҫɼ౾һͬȷ Java еΨһԣÿһӵһƿռ䡣ͨ׶ԣȽǷȡָġȡ Class equals() isAssignableFrom() isInstance() ķؽҲʹ instanceof() ؼֶϵжֻʱͬһصǰ²壬򣬼ʹԴͬһ Class ļͬһأֻҪǵͬͱضȡ +对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在 Java 虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。通俗而言:比较两个类是否“相等”(这里所指的“相等”,包括类的 Class 对象的 equals() 方法、isAssignableFrom() 方法、isInstance() 方法的返回结果,也包括使用 instanceof() 关键字对做对象所属关系判定等情况),只有在这两个类时由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个 Class 文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。 -### 4.2 +### 4.2 类加载器分类 - Java ĽǶֲֻͬ +从 Java 虚拟机的角度来讲,只存在以下两种不同的类加载器: -һBootstrap ClassLoader C++ ʵ֣һ֣һ־ļЩ Java ʵ֣ⲿȫ̳Գ java.lang.ClassLoader +一种是启动类加载器(Bootstrap ClassLoader),这个类加载器用 C++ 实现,是虚拟机自身的一部分;另一种就是所有其他类的加载器,这些类由 Java 实现,独立于虚拟机外部,并且全都继承自抽象类 java.lang.ClassLoader。 - Java ԱĽǶȿԻֵøϸһЩ +从 Java 开发人员的角度看,类加载器可以划分得更细致一些: -- Bootstrap ClassLoader 𽫴 \lib Ŀ¼еģ߱ -Xbootclasspath ָ·еģʶģļʶ rt.jarֲϵ⼴ʹ lib Ŀ¼ҲᱻأصڴС ޷ Java ֱãûڱдԶʱҪѼίɸֱʹ null 漴ɡ +- 启动类加载器(Bootstrap ClassLoader) 此类加载器负责将存放在 \lib 目录中的,或者被 -Xbootclasspath 参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如 rt.jar,名字不符合的类库即使放在 lib 目录中也不会被加载)类库加载到虚拟机内存中。 启动类加载器无法被 Java 程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给引导类加载器,直接使用 null 代替即可。 -- չExtension ClassLoader ExtClassLoadersun.misc.Launcher$ExtClassLoaderʵֵġ /lib/ext ߱ java.ext.dir ϵͳָ·еصڴУ߿ֱʹչ +- 扩展类加载器(Extension ClassLoader) 这个类加载器是由 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的。它负责将 /lib/ext 或者被 java.ext.dir 系统变量所指定路径中的所有类库加载到内存中,开发者可以直接使用扩展类加载器。 -- ӦóApplication ClassLoader AppClassLoadersun.misc.Launcher$AppClassLoaderʵֵġ ClassLoader е getSystemClassLoader() ķֵһΪϵͳû·ClassPathָ⣬߿ֱʹӦóûԶԼһdzĬϵ +- 应用程序类加载器(Application ClassLoader) 这个类加载器是由 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。由于这个类加载器是 ClassLoader 中的 getSystemClassLoader() 方法的返回值,因此一般称为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。 -### 4.3 ˫ίģ +### 4.3 双亲委派模型 -Ӧó໥ϽмصģбҪԼԼͼչʾ֮IJιϵΪ˫ίģͣParents Delegation ModelģҪ˶⣬ӦԼĸ֮ĸӹϵһͨϣCompositionϵʵ֣̳ͨУInheritanceĹϵʵ֡ +应用程序都是由三种类加载器相互配合进行加载的,如果有必要,还可以加入自己定义的类加载器。下图展示的类加载器之间的层次关系,称为类加载器的双亲委派模型(Parents Delegation Model)。该模型要求除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器,这里类加载器之间的父子关系一般通过组合(Composition)关系来实现,而不是通过继承(Inheritance)的关系实现。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/2cdc3ce2-fa82-4c22-baaa-000c07d10473.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//2cdc3ce2-fa82-4c22-baaa-000c07d10473.jpg) -**** +**工作过程** -һյصȲԼȥԼأǰίɸÿһεļˣεݹ飬еļնӦô͵УֻеԼ޷ɴ˼ΧûҵࣩʱӼŻ᳢Լء +如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载,而是把这个请求委派给父类加载器,每一个层次的加载器都是如此,依次递归,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成此加载请求(它搜索范围中没有找到所需类)时,子加载器才会尝试自己加载。 -**ô** +**好处** -ʹ˫ίģ֮֯Ĺϵʹ Java һ߱һִȼIJιϵ java.lang.Object rt.jar УĸҪ࣬նίɸģ˵мأ Object ڳĸжͬһࡣ෴û˫ίģͣɸмصĻûдһΪjava.lang.Object ࣬ڳ ClassPath Уϵͳнֶͬ Object ࣬򽫱һƬҡ߳Աдһ rt.jar Java ֿ࣬ᷢ룬Զ޷С +使用双亲委派模型来组织类加载器之间的关系,使得 Java 类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类 java.lang.Object,它存放再 rt.jar 中,无论哪个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此 Object 类在程序的各种类加载器环境中都是同一个类。相反,如果没有双亲委派模型,由各个类加载器自行加载的话,如果用户编写了一个称为`java.lang.Object 的类,并放在程序的 ClassPath 中,那系统中将会出现多个不同的 Object 类,程序将变得一片混乱。如果开发者尝试编写一个与 rt.jar 类库中已有类重名的 Java 类,将会发现可以正常编译,但是永远无法被加载运行。 -**ʵ** +**实现** ```java protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException{ @@ -646,27 +647,27 @@ protected synchronized Class loadClass(String name, boolean resolve) throws C } ``` -# JVM +# JVM 参数 -## GC Ż +## GC 优化配置 -| | | +| 配置 | 描述 | | --- | --- | -| -Xms | ʼڴС | -| -Xmx | ڴֵ | -| -Xmn | С | -| -XX:PermSize | ʼôС | -| -XX:MaxPermSize | ô | +| -Xms | 初始化堆内存大小 | +| -Xmx | 堆内存最大值 | +| -Xmn | 新生代大小 | +| -XX:PermSize | 初始化永久代大小 | +| -XX:MaxPermSize | 永久代最大容量 | -## GC +## GC 类型设置 -| | | +| 配置 | 描述 | | --- | --- | -| -XX:+UseSerialGC | | -| -XX:+UseParallelGC | | -| -XX:+UseConcMarkSweepGC | ɨ | -| -XX:ParallelCMSThreads= | ɨ = Ϊʹõ߳ | -| -XX:+UseG1GC | G1 | +| -XX:+UseSerialGC | 串行垃圾回收器 | +| -XX:+UseParallelGC | 并行垃圾回收器 | +| -XX:+UseConcMarkSweepGC | 并发标记扫描垃圾回收器 | +| -XX:ParallelCMSThreads= | 并发标记扫描垃圾回收器 = 为使用的线程数量 | +| -XX:+UseG1GC | G1 垃圾回收器 | ```java java -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseSerialGC -jar java-application.jar diff --git a/notes/Java IO.md b/notes/Java IO.md index c4aa5d39..1737ca9d 100644 --- a/notes/Java IO.md +++ b/notes/Java IO.md @@ -1,129 +1,130 @@ -* [](#) -* [̲](#̲) -* [ֽڲ](#ֽڲ) -* [ַ](#ַ) -* [](#) -* [](#) +* [概览](#概览) +* [磁盘操作](#磁盘操作) +* [字节操作](#字节操作) +* [字符操作](#字符操作) +* [对象操作](#对象操作) +* [网络操作](#网络操作) * [1. InetAddress](#1-inetaddress) * [2. URL](#2-url) * [3. Sockets](#3-sockets) * [4. Datagram](#4-datagram) * [NIO](#nio) - * [1. ](#1-) - * [2. ͨ뻺](#2-ͨ뻺) - * [2.1 ͨ](#21-ͨ) - * [2.2 ](#22-) - * [3. ״̬](#3-״̬) - * [4. дļʵ](#4-дļʵ) - * [5. ](#5-) - * [5.1 ʽ I/O](#51-ʽ-io) - * [5.2 ʽ I/O](#52-ʽ-io) - * [6. ׽ʵ](#6-׽ʵ) + * [1. 流与块](#1-流与块) + * [2. 通道与缓冲区](#2-通道与缓冲区) + * [2.1 通道](#21-通道) + * [2.2 缓冲区](#22-缓冲区) + * [3. 缓冲区状态变量](#3-缓冲区状态变量) + * [4. 读写文件实例](#4-读写文件实例) + * [5. 阻塞与非阻塞](#5-阻塞与非阻塞) + * [5.1 阻塞式 I/O](#51-阻塞式-io) + * [5.2 非阻塞式 I/O](#52-非阻塞式-io) + * [6. 套接字实例](#6-套接字实例) * [6.1 ServerSocketChannel](#61-serversocketchannel) * [6.2 Selectors](#62-selectors) - * [6.3 ѭ](#63-ѭ) - * [6.4 ](#64-) - * [6.5 µ](#65-µ) - * [6.6 ɾ SelectionKey](#66-ɾ-selectionkey) - * [6.7 I/O](#67--io) -* [ο](#ο) + * [6.3 主循环](#63-主循环) + * [6.4 监听新连接](#64-监听新连接) + * [6.5 接受新的连接](#65-接受新的连接) + * [6.6 删除处理过的 SelectionKey](#66-删除处理过的-selectionkey) + * [6.7 传入的 I/O](#67-传入的-io) +* [参考资料](#参考资料) -# -Java I/O ſԷֳ¼ +# 概览 -1. ̲File -2. ֽڲInputStream OutputStream -3. ַReader Writer -4. Serializable -5. Socket -6. ʽ IONIO +Java 的 I/O 大概可以分成以下几类 -# ̲ +1. 磁盘操作:File +2. 字节操作:InputStream 和 OutputStream +3. 字符操作:Reader 和 Writer +4. 对象操作:Serializable +5. 网络操作:Socket +6. 非阻塞式 IO:NIO -File ڱʾļĿ¼ֻڱʾļϢʾļݡ +# 磁盘操作 -# ֽڲ +File 类可以用于表示文件和目录,但是它只用于表示文件的信息,而不表示文件的内容。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/8143787f-12eb-46ea-9bc3-c66d22d35285.jpg) +# 字节操作 -Java I/O ʹװģʽʵ֡ InputStream ΪInputStream dzFileInputStream InputStream ࣬ھṩֽFilterInputStream ڳװߣװװΪṩĹܣ BufferedInputStream Ϊ FileInputStream ṩĹܡʵһл湦ֽܵʱֻҪ FileInputStream һ BufferedInputStream 󼴿ɡ +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//8143787f-12eb-46ea-9bc3-c66d22d35285.jpg) + +Java I/O 使用了装饰者模式来实现。以 InputStream 为例,InputStream 是抽象组件,FileInputStream 是 InputStream 的子类,属于具体组件,提供了字节流的输入操作。FilterInputStream 属于抽象装饰者,装饰者用于装饰组件,为组件提供额外的功能,例如 BufferedInputStream 为 FileInputStream 提供缓存的功能。实例化一个具有缓存功能的字节流对象时,只需要在 FileInputStream 对象上再套一层 BufferedInputStream 对象即可。 ```java BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file)); ``` -DataInputStream װṩ˶ԸͽIJ intdouble Ȼ͡ +DataInputStream 装饰者提供了对更多数据类型进行输入的操作,比如 int、double 等基本类型。 -ļеݵֽ +批量读入文件中的内容到字节数组中 ```java byte[] buf = new byte[20*1024]; int bytes = 0; -// ȡ buf.length ֽڣصʵʶȡĸ -1 ʱʾ eofļβ +// 最多读取 buf.length 个字节,返回的是实际读取的个数,返回 -1 的时候表示读到 eof,即文件尾 while((bytes = in.read(buf, 0 , buf.length)) != -1) { // ... } ``` -# ַ +# 字符操作 -Ǵ̻紫䣬СĴ洢Ԫֽڣַ I/O ĶֽڶַڳвַͨʽҪṩַвķ +不管是磁盘还是网络传输,最小的存储单元都是字节,而不是字符,所以 I/O 操作的都是字节而不是字符。但是在程序中操作的数据通常是字符形式,因此需要提供对字符进行操作的方法。 -InputStreamReader ʵִıļַֽOutputStreamWriter ʵַΪıļֽǶ̳ Reader Writer +InputStreamReader 实现从文本文件的字节流解码成字符流;OutputStreamWriter 实现字符流编码成为文本文件的字节流。它们都继承自 Reader 和 Writer。 -ǰַתΪֽڣǰֽϳַ +编码就是把字符转换为字节,而解码是把字节重新组合成字符。 ```java -byte[] bytes = str.getBytes(encoding); // -String str = new String(bytes, encoding) // +byte[] bytes = str.getBytes(encoding); // 编码 +String str = new String(bytes, encoding); // 解码 ``` -GBK Уռ 2 ֽڣӢռ 1 ֽڣUTF-8 Уռ 3 ֽڣӢռ 1 ֽڣJava ʹ˫ֽڱ UTF-16beĺӢĶռ 2 ֽڡ +GBK 编码中,中文占 2 个字节,英文占 1 个字节;UTF-8 编码中,中文占 3 个字节,英文占 1 个字节;Java 使用双字节编码 UTF-16be,中文和英文都占 2 个字节。 -ͽʹòͬı뷽ʽôͳ롣 +如果编码和解码过程使用不同的编码方式那么就出现了乱码。 -# +# 对象操作 -лǽһתֽУ洢ʹ䡣 +序列化就是将一个对象转换成字节序列,方便存储和传输。 -лObjectOutputStream.writeObject() +序列化:ObjectOutputStream.writeObject() -лObjectInputStream.readObject() +反序列化:ObjectInputStream.readObject() -лҪʵ Serializable ӿڣֻһ׼ûκηҪʵ֡ +序列化的类需要实现 Serializable 接口,它只是一个标准,没有任何方法需要实现。 -transient ؼֿʹһЩԲᱻл +transient 关键字可以使一些属性不会被序列化。 -**ArrayList лͷлʵ**ArrayList д洢ݵ transient εģΪǶ̬չģеĿռ䶼ʹã˾ͲҪеݶлͨдлͷлʹÿֻлݵDzݡ +**ArrayList 序列化和反序列化的实现**:ArrayList 中存储数据的数组是用 transient 修饰的,因为这个数组是动态扩展的,并不是所有的空间都被使用,因此就不需要所有的内容都被序列化。通过重写序列化和反序列化方法,使得可以只序列化数组中有内容的那部分数据。 ``` private transient Object[] elementData; ``` -# +# 网络操作 -Java е֧֣ +Java 中的网络支持: -1. InetAddressڱʾϵӲԴ IP ַ -2. URLͳһԴλͨ URL ֱӶȡдϵݣ -3. Socketsʹ TCP Эʵͨţ -4. Datagramʹ UDP Эʵͨš +1. InetAddress:用于表示网络上的硬件资源,即 IP 地址; +2. URL:统一资源定位符,通过 URL 可以直接读取或者写入网络上的数据; +3. Sockets:使用 TCP 协议实现网络通信; +4. Datagram:使用 UDP 协议实现网络通信。 ## 1. InetAddress -ûйй캯ֻ̬ͨʵ InetAddress.getByName(String host)InetAddress.getByAddress(byte[] addr) +没有公有构造函数,只能通过静态方法来创建实例,比如 InetAddress.getByName(String host)、InetAddress.getByAddress(byte[] addr)。 ## 2. URL -ֱӴ URL жȡֽ +可以直接从 URL 中读取字节流数据 ```java URL url = new URL("http://www.baidu.com"); -InputStream is = url.openStream(); // ֽ -InputStreamReader isr = new InputStreamReader(is, "utf-8"); // ַ +InputStream is = url.openStream(); // 字节流 +InputStreamReader isr = new InputStreamReader(is, "utf-8"); // 字符流 BufferedReader br = new BufferedReader(isr); String line = br.readLine(); while (line != null) { @@ -137,56 +138,56 @@ is.close(); ## 3. Sockets -Socket ͨģ +Socket 通信模型 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/fa4101d7-19ce-4a69-a84f-20bbe64320e5.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//fa4101d7-19ce-4a69-a84f-20bbe64320e5.jpg) -- ServerSocket -- Socketͻ +- ServerSocket:服务器端类 +- Socket:客户端类 -Ϳͻͨ InputStream OutputStream +服务器和客户端通过 InputStream 和 OutputStream 进行输入输出。 ## 4. Datagram -- DatagramPacketݰ -- DatagramSocketͨ +- DatagramPacket:数据包类 +- DatagramSocket:通信类 # NIO -NIO ʱ I/O ( ȡ ) תƻزϵͳ ҪԱȥƾͿԼٶȡ +NIO 将最耗时的 I/O 操作 ( 即填充和提取缓冲区 ) 转移回操作系统,因而 不需要程序员去控制就可以极大地提高运行速度。 -## 1. +## 1. 流与块 -I/O NIO ҪݴʹķʽǰᵽģI/O ķʽݣ NIO Կķʽݡ +I/O 与 NIO 最重要的区别是数据打包和传输的方式。正如前面提到的,I/O 以流的方式处理数据,而 NIO 以块的方式处理数据。 - I/O һһֽڽдݣһһֽڵݣһһֽڵݡΪʽݴdzףӼԱÿֻ𵥸ӴƵһ֣ҲԼ򵥵ġһǣ I/O ͨ൱ +面向流的 I/O 一次一个字节进行处理数据,一个输入流产生一个字节的数据,一个输出流消费一个字节的数据。为流式数据创建过滤器非常容易,链接几个过滤器,以便每个过滤器只负责单个复杂处理机制的一部分,这样也是相对简单的。不利的一面是,面向流的 I/O 通常相当慢。 -һ I/O ϵͳԿʽݣÿһһвһݿ顣鴦ݱȰҪöࡣ I/O ȱһЩ I/O еԺͼԡ +一个面向块的 I/O 系统以块的形式处理数据,每一个操作都在一步中产生或者消费一个数据块。按块处理数据比按流处理数据要快得多。但是面向块的 I/O 缺少一些面向流的 I/O 所具有的优雅性和简单性。 -I/O NIO Ѿܺõؼˣjava.io.\* Ѿ NIO Ϊʵˣ NIO һЩԡ磬 java.io.\* еһЩԿʽдݵķʹüʹڸϵͳУٶҲ졣 +I/O 包和 NIO 已经很好地集成了,java.io.\* 已经以 NIO 为基础重新实现了,所以现在它可以利用 NIO 的一些特性。例如, java.io.\* 包中的一些类包含以块的形式读写数据的方法,这使得即使在更面向流的系统中,处理速度也会更快。 -## 2. ͨ뻺 +## 2. 通道与缓冲区 -### 2.1 ͨ +### 2.1 通道 -ͨ Channel Ƕԭ I/O еģ⣬ͨȡдݡ +通道 Channel 是对原 I/O 包中的流的模拟,可以通过它读取和写入数据。 -ͨIJ֮ͬڣֻһƶ(һ InputStream OutputStream ) ͨ˫ģڶдͬʱڶд +通道与流的不同之处在于,流只能在一个方向上移动,(一个流必须是 InputStream 或者 OutputStream 的子类), 而通道是双向的,可以用于读、写或者同时用于读写。 -ͨͣ +通道包括以下类型: -- FileChannelļждݣ -- DatagramChannelͨ UDP дݣ -- SocketChannelͨ TCP дݣ -- ServerSocketChannelԼ½ TCP ӣÿһ½Ӷᴴһ SocketChannel +- FileChannel:从文件中读写数据; +- DatagramChannel:通过 UDP 读写网络中数据; +- SocketChannel:通过 TCP 读写网络中数据; +- ServerSocketChannel:可以监听新进来的 TCP 连接,对每一个新进来的连接都会创建一个 SocketChannel。 -### 2.2 +### 2.2 缓冲区 -͸һͨж󶼱ȷŵУͬأͨжȡκݶҪСҲ˵ֱӶͨждݣȾ +发送给一个通道的所有对象都必须首先放到缓冲区中;同样地,从通道中读取的任何数据都要读到缓冲区中。也就是说,不会直接对通道进行读写数据,而是先经过缓冲区。 -ʵһ飬һ顣ṩ˶ݵĽṹʣһԸϵͳĶ/д̡ +缓冲区实质上是一个数组,但它不仅仅是一个数组。缓冲区提供了对数据的结构化访问,而且还可以跟踪系统的读/写进程。 -ͣ +缓冲区包括以下类型: - ByteBuffer - CharBuffer @@ -197,50 +198,50 @@ I/O - DoubleBuffer -## 3. ״̬ +## 3. 缓冲区状态变量 -- capacity -- positionǰѾдֽ -- limitԶдֽ +- capacity:最大容量; +- position:当前已经读写的字节数; +- limit:还可以读写的字节数。 -״̬ĸı̣ +状态变量的改变过程: -1\. ½һСΪ 8 ֽڵĻʱ position Ϊ 0 limit == capacity == 9capacity ı䣬ۻ +1\. 新建一个大小为 8 个字节的缓冲区,此时 position 为 0,而 limit == capacity == 9。capacity 变量不会改变,下面的讨论会忽略它。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/1bea398f-17a7-4f67-a90b-9e2d243eaa9a.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//1bea398f-17a7-4f67-a90b-9e2d243eaa9a.png) -2\. ͨжȡ 3 ֽд뻺Уʱ position ƶΪ 3limit ֲ䡣 +2\. 从输入通道中读取 3 个字节数据写入缓冲区中,此时 position 移动设为 3,limit 保持不变。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/4628274c-25b6-4053-97cf-d1239b44c43d.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//4628274c-25b6-4053-97cf-d1239b44c43d.png) -3\. ڽд֮ͨǰҪȵ flip() limit Ϊǰ position position Ϊ 0 +3\. 在将缓冲区的数据写到输出通道之前,需要先调用 flip() 方法,这个方法将 limit 设置为当前 position,并将 position 设置为 0。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/952e06bd-5a65-4cab-82e4-dd1536462f38.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//952e06bd-5a65-4cab-82e4-dd1536462f38.png) -4\. ӻȡ 4 ֽڵУʱ position Ϊ 4 +4\. 从缓冲区中取 4 个字节到输出缓冲中,此时 position 设为 4。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/b5bdcbe2-b958-4aef-9151-6ad963cb28b4.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//b5bdcbe2-b958-4aef-9151-6ad963cb28b4.png) -5\. Ҫ clear() ջʱ position limit Ϊλá +5\. 最后需要调用 clear() 方法来清空缓冲区,此时 position 和 limit 都被设置为最初位置。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/67bf5487-c45d-49b6-b9c0-a058d8c68902.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//67bf5487-c45d-49b6-b9c0-a058d8c68902.png) -## 4. дļʵ +## 4. 读写文件实例 -1\. ΪҪȡļ FileInputStream֮ͨ FileInputStream ȡ FileChannel +1\. 为要读取的文件创建 FileInputStream,之后通过 FileInputStream 获取输入 FileChannel; ```java FileInputStream fin = new FileInputStream("readandshow.txt"); FileChannel fic = fin.getChannel(); ``` -2\. һΪ 1024 Buffer +2\. 创建一个容量为 1024 的 Buffer ```java ByteBuffer buffer = ByteBuffer.allocate(1024); ``` -3\. ݴ FileChannel д뵽 Buffer УûݵĻ read() ᷵ -1 +3\. 将数据从输入 FileChannel 写入到 Buffer 中,如果没有数据的话, read() 方法会返回 -1 ```java int r = fcin.read(buffer); @@ -249,86 +250,86 @@ if (r == -1) { } ``` -4\. ΪҪдļ FileOutputStream֮ͨ FileOutputStream ȡ FileChannel +4\. 为要写入的文件创建 FileOutputStream,之后通过 FileOutputStream 获取输出 FileChannel ```java FileOutputStream fout = new FileOutputStream("writesomebytes.txt"); FileChannel foc = fout.getChannel(); ``` -5\. flip() лд +5\. 调用 flip() 切换读写 ```java buffer.flip(); ``` -6\. Buffer еݶȡ FileChannel +6\. 把 Buffer 中的数据读取到输出 FileChannel 中 ```java foc.write(buffer); ``` -7\. clear() û +7\. 最后调用 clear() 重置缓冲区 ```java buffer.clear(); ``` -## 5. +## 5. 阻塞与非阻塞 -Ӧע⣬FileChannel лģʽ׽ Channel ԡ +应当注意,FileChannel 不能切换到非阻塞模式,套接字 Channel 可以。 -### 5.1 ʽ I/O +### 5.1 阻塞式 I/O -ʽ I/O ڵ InputStream.read() ʱһֱȵݵʱʱŻ᷵أڵ ServerSocket.accept() ʱҲһֱпͻӲŻ᷵أÿͻӹ󣬷˶һ߳ȥÿͻ˵ +阻塞式 I/O 在调用 InputStream.read() 方法时会一直等到数据到来时(或超时)才会返回,在调用 ServerSocket.accept() 方法时,也会一直阻塞到有客户端连接才会返回,每个客户端连接过来后,服务端都会启动一个线程去处理该客户端的请求。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/edc23f99-c46c-4200-b64e-07516828720d.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//edc23f99-c46c-4200-b64e-07516828720d.jpg) -### 5.2 ʽ I/O +### 5.2 非阻塞式 I/O -һרŵ߳е I/O ¼ַ +由一个专门的线程来处理所有的 I/O 事件,并负责分发。 -¼ƣ¼ʱ򴥷ͬȥ¼ +事件驱动机制:事件到的时候触发,而不是同步的去监视事件。 -߳ͨţ֮߳ͨ wait()notify() ȷʽͨţ֤ÿлģν߳л +线程通信:线程之间通过 wait()、notify() 等方式通信,保证每次上下文切换都是有意义的,减少无谓的线程切换。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/7fcb2fb0-2cd9-4396-bc2d-282becf963c3.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//7fcb2fb0-2cd9-4396-bc2d-282becf963c3.jpg) -## 6. ׽ʵ +## 6. 套接字实例 ### 6.1 ServerSocketChannel -ÿһ˿ڶҪһ ServerSocketChannel ӡ +每一个端口都需要有一个 ServerSocketChannel 用来监听连接。 ```java ServerSocketChannel ssc = ServerSocketChannel.open(); -ssc.configureBlocking(false); // Ϊ +ssc.configureBlocking(false); // 设置为非阻塞 ServerSocket ss = ssc.socket(); InetSocketAddress address = new InetSocketAddress(ports[i]); -ss.bind(address); // 󶨶˿ں +ss.bind(address); // 绑定端口号 ``` ### 6.2 Selectors -첽 I/O ͨ Selector עض I/O ¼Ȥ D ɶݵĵµ׽ӵȵȣڷ¼ʱϵͳ֪ᷢͨ +异步 I/O 通过 Selector 注册对特定 I/O 事件的兴趣 ― 可读的数据的到达、新的套接字连接等等,在发生这样的事件时,系统将会发送通知。 - Selectors ֮󣬾ͿԶԲͬͨ register() register() ĵһ Selectorڶ OP_ACCEPTָҪ accept ¼Ҳµӽʱ¼ +创建 Selectors 之后,就可以对不同的通道对象调用 register() 方法。register() 的第一个参数总是这个 Selector。第二个参数是 OP_ACCEPT,这里它指定我们想要监听 accept 事件,也就是在新的连接建立时所发生的事件。 -SelectionKey ͨڴ Selector ϵעᡣij Selector ֪ͨij¼ʱͨṩӦڸ¼ SelectionKey еġSelectionKey ȡͨעᡣ +SelectionKey 代表这个通道在此 Selector 上的这个注册。当某个 Selector 通知您某个传入事件时,它是通过提供对应于该事件的 SelectionKey 来进行的。SelectionKey 还可以用于取消通道的注册。 ```java Selector selector = Selector.open(); SelectionKey key = ssc.register(selector, SelectionKey.OP_ACCEPT); ``` -### 6.3 ѭ +### 6.3 主循环 -ȣǵ Selector select() ֱһע¼һ߸¼ʱ select() ¼ +首先,我们调用 Selector 的 select() 方法。这个方法会阻塞,直到至少有一个已注册的事件发生。当一个或者更多的事件发生时, select() 方法将返回所发生的事件的数量。 -ǵ Selector selectedKeys() ط¼ SelectionKey һ +接下来,我们调用 Selector 的 selectedKeys() 方法,它返回发生了事件的 SelectionKey 对象的一个 集合 。 -ͨ SelectionKeys δÿ SelectionKey ¼ÿһ SelectionKeyȷʲô I/O ¼Լ¼ӰЩ I/O +我们通过迭代 SelectionKeys 并依次处理每个 SelectionKey 来处理事件。对于每一个 SelectionKey,您必须确定发生的是什么 I/O 事件,以及这个事件影响哪些 I/O 对象。 ```java int num = selector.select(); @@ -342,9 +343,9 @@ while (it.hasNext()) { } ``` -### 6.4 +### 6.4 监听新连接 -ִеǽע ServerSocketChannelҽעǡա¼Ϊȷһ㣬Ƕ SelectionKey readyOps() 鷢ʲô͵¼ +程序执行到这里,我们仅注册了 ServerSocketChannel,并且仅注册它们“接收”事件。为确认这一点,我们对 SelectionKey 调用 readyOps() 方法,并检查发生了什么类型的事件: ```java if ((key.readyOps() & SelectionKey.OP_ACCEPT) @@ -354,39 +355,39 @@ if ((key.readyOps() & SelectionKey.OP_ACCEPT) } ``` -Կ϶˵ readOps() Ǹ¼µӡ +可以肯定地说, readOps() 方法告诉我们该事件是新的连接。 -### 6.5 µ +### 6.5 接受新的连接 -Ϊ֪׽һڵȴԿ԰ȫؽҲ˵õ accept() +因为我们知道这个服务器套接字上有一个传入连接在等待,所以可以安全地接受它;也就是说,不用担心 accept() 操作会阻塞: ```java ServerSocketChannel ssc = (ServerSocketChannel)key.channel(); SocketChannel sc = ssc.accept(); ``` -һǽӵ SocketChannel ΪġڽӵĿΪ˶ȡ׽ֵݣǻ뽫 SocketChannel עᵽ Selectorϣʾ +下一步是将新连接的 SocketChannel 配置为非阻塞的。而且由于接受这个连接的目的是为了读取来自套接字的数据,所以我们还必须将 SocketChannel 注册到 Selector上,如下所示: ```java sc.configureBlocking( false ); SelectionKey newKey = sc.register( selector, SelectionKey.OP_READ ); ``` -עʹ register() OP_READ SocketChannel ע ȡ ӡ +注意我们使用 register() 的 OP_READ 参数,将 SocketChannel 注册用于 读取 而不是 接受 新连接。 -### 6.6 ɾ SelectionKey +### 6.6 删除处理过的 SelectionKey -ڴ SelectionKey ֮ǼԷѭˡDZȽ SelectionKey ѡļɾûɾļôȻһļ֣ᵼdzٴδǵõ remove() ɾ SelectionKey +在处理 SelectionKey 之后,我们几乎可以返回主循环了。但是我们必须首先将处理过的 SelectionKey 从选定的键集合中删除。如果我们没有删除处理过的键,那么它仍然会在主集合中以一个激活的键出现,这会导致我们尝试再次处理它。我们调用迭代器的 remove() 方法来删除处理过的 SelectionKey: ```java it.remove(); ``` -ǿԷѭܴһ׽д(һ I/O ¼)ˡ +现在我们可以返回主循环并接受从一个套接字中传入的数据(或者一个传入的 I/O 事件)了。 -### 6.7 I/O +### 6.7 传入的 I/O -һ׽ֵݵʱᴥһ I/O ¼ᵼѭе Selector.select()һ߶ I/O ¼һΣ SelectionKey Ϊ OP_READ ¼ʾ +当来自一个套接字的数据到达时,它会触发一个 I/O 事件。这会导致在主循环中调用 Selector.select(),并返回一个或者多个 I/O 事件。这一次, SelectionKey 将被标记为 OP_READ 事件,如下所示: ```java } else if ((key.readyOps() & SelectionKey.OP_READ) @@ -398,9 +399,9 @@ it.remove(); ``` -# ο +# 参考资料 -- Eckel B, ˶ , , . Java ˼ [M]. еҵ , 2002. -- [IBM: NIO ](https://www.ibm.com/developerworks/cn/education/java/j-nio/j-nio.html) -- [ Java I/O Ĺ ](https://www.ibm.com/developerworks/cn/java/j-lo-javaio/index.html) -- [NIO 봫ͳ IO ](http://blog.csdn.net/shimiso/article/details/24990499) +- Eckel B, 埃克尔 , 昊鹏 , 等 . Java 编程思想 [M]. 机械工业出版社 , 2002. +- [IBM: NIO 入门](https://www.ibm.com/developerworks/cn/education/java/j-nio/j-nio.html) +- [ 深入分析 Java I/O 的工作机制 ](https://www.ibm.com/developerworks/cn/java/j-lo-javaio/index.html) +- [NIO 与传统 IO 的区别 ](http://blog.csdn.net/shimiso/article/details/24990499) diff --git a/notes/Java 容器.md b/notes/Java 容器.md index 8bbc9b45..ffc1cba8 100644 --- a/notes/Java 容器.md +++ b/notes/Java 容器.md @@ -1,78 +1,79 @@ -* [](#) +* [概览](#概览) * [1. List](#1-list) * [2. Set](#2-set) * [3. Queue](#3-queue) * [4. Map](#4-map) - * [5. Java 1.0/1.1 ](#5-java-1011-) -* [еģʽ](#еģʽ) - * [1. ģʽ](#1-ģʽ) - * [2. ģʽ](#2-ģʽ) -* [ɢ](#ɢ) -* [Դ](#Դ) + * [5. Java 1.0/1.1 容器](#5-java-1011-容器) +* [容器中的设计模式](#容器中的设计模式) + * [1. 迭代器模式](#1-迭代器模式) + * [2. 适配器模式](#2-适配器模式) +* [散列](#散列) +* [源码分析](#源码分析) * [1. ArraList](#1-arralist) - * [2. Vector Stack](#2-vector--stack) + * [2. Vector 与 Stack](#2-vector-与-stack) * [3. LinkedList](#3-linkedlist) * [4. TreeMap](#4-treemap) * [5. HashMap](#5-hashmap) * [6. LinkedHashMap](#6-linkedhashmap) * [7. ConcurrentHashMap](#7-concurrenthashmap) -* [ο](#ο) +* [参考资料](#参考资料) -# -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/ebf03f56-f957-4435-9f8f-0f605661484d.jpg) +# 概览 -Ҫ Collection Map ֣Collection ְ ListSet Լ Queue +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//ebf03f56-f957-4435-9f8f-0f605661484d.jpg) + +容器主要包括 Collection 和 Map 两种,Collection 又包含了 List、Set 以及 Queue。 ## 1. List -- ArrayListڶ̬ʵ֣֧ʣ +- ArrayList:基于动态数组实现,支持随机访问; -- LinkedList˫ѭʵֻ֣˳ʣǿԿٵмɾԪءˣLinkedList ջк˫˶С +- LinkedList:基于双向循环链表实现,只能顺序访问,但是可以快速地在链表中间插入和删除元素。不仅如此,LinkedList 还可以用作栈、队列和双端队列。 ## 2. Set -- HashSet Hash ʵֿ֣֧ٲңʧȥԣ +- HashSet:基于 Hash 实现,支持快速查找,但是失去有序性; -- TreeSetںʵ֣򣬵DzЧʲ HashSet +- TreeSet:基于红黑树实现,保持有序,但是查找效率不如 HashSet; -- LinkedListHashSet HashSet IJЧʣڲʹάԪصIJ˳˾ԡ +- LinkedListHashSet:具有 HashSet 的查找效率,且内部使用链表维护元素的插入顺序,因此具有有序性。 ## 3. Queue -ֻʵ֣LinkedList PriorityQueue LinkedList ֧˫УPriorityQueue ǻڶѽṹʵ֡ +只有两个实现:LinkedList 和 PriorityQueue,其中 LinkedList 支持双向队列,PriorityQueue 是基于堆结构实现。 ## 4. Map -- HashMap Hash ʵ +- HashMap:基于 Hash 实现 -- LinkedHashMapʹάԪص˳˳Ϊ˳ʹãLRU˳ +- LinkedHashMap:使用链表来维护元素的顺序,顺序为插入顺序或者最近最少使用(LRU)顺序 -- TreeMapںʵ +- TreeMap:基于红黑树实现 -- ConcurrentHashMap̰߳ȫ Map漰 HashTable ͬ +- ConcurrentHashMap:线程安全 Map,不涉及类似于 HashTable 的同步加锁 -## 5. Java 1.0/1.1 +## 5. Java 1.0/1.1 容器 -ھɵǾӦʹǣֻҪǽ˽⡣ +对于旧的容器,我们决不应该使用它们,只需要对它们进行了解。 -- Vector ArrayList ƣ̰߳ȫ +- Vector:和 ArrayList 类似,但它是线程安全的 -- HashTable HashMap ƣ̰߳ȫ +- HashTable:和 HashMap 类似,但它是线程安全的 -# еģʽ +# 容器中的设计模式 -## 1. ģʽ +## 1. 迭代器模式 -ӸͼԿÿ඼һ Iterator 󣬿ͨеԪء +从概览图可以看到,每个集合类都有一个 Iterator 对象,可以通过这个迭代器对象来遍历集合中的元素。 -[Java еĵģʽ ](https://github.com/CyC2018/InterviewNotes/blob/master/notes/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F.md#92-java-%E5%86%85%E7%BD%AE%E7%9A%84%E8%BF%AD%E4%BB%A3%E5%99%A8) +[Java 中的迭代器模式 ](https://github.com/CyC2018/InterviewNotes/blob/master/notes/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F.md#92-java-%E5%86%85%E7%BD%AE%E7%9A%84%E8%BF%AD%E4%BB%A3%E5%99%A8) -## 2. ģʽ +## 2. 适配器模式 -java.util.Arrays#asList() ԰תΪ List ͡ +java.util.Arrays#asList() 可以把数组类型转换为 List 类型。 ```java List list = Arrays.asList(1, 2, 3); @@ -80,44 +81,44 @@ java.util.Arrays#asList() list = Arrays.asList(arr); ``` -# ɢ +# 散列 -ʹ hasCode() ɢֵʹõǶĵַ +使用 hasCode() 来返回散列值,使用的是对象的地址。 - equals() жǷȵģȵɢֵһҪͬɢֵͬһȡ +而 equals() 是用来判断两个对象是否相等的,相等的两个对象散列值一定要相同,但是散列值相同的两个对象不一定相等。 -ȱʣ +相等必须满足以下五个性质: -1. Է -2. Գ -3. -4. һԣε x.equals(y)䣩 -5. κβ null Ķ x x.equals(nul) Ϊ false +1. 自反性 +2. 对称性 +3. 传递性 +4. 一致性(多次调用 x.equals(y),结果不变) +5. 对任何不是 null 的对象 x 调用 x.equals(nul) 结果都为 false -# Դ +# 源码分析 -Ķ [ 㷨 - ](https://github.com/CyC2018/InterviewNotes/blob/master/notes/%E7%AE%97%E6%B3%95.md#%E7%AC%AC%E4%B8%89%E7%AB%A0-%E6%9F%A5%E6%89%BE) ֣ԼԴкܴ +建议先阅读 [ 算法 - 查找 ](https://github.com/CyC2018/InterviewNotes/blob/master/notes/%E7%AE%97%E6%B3%95.md#%E7%AC%AC%E4%B8%89%E7%AB%A0-%E6%9F%A5%E6%89%BE) 部分,对集合类源码的理解有很大帮助。 -Դأ[OpenJDK 1.7](http://download.java.net/openjdk/jdk7) +源码下载:[OpenJDK 1.7](http://download.java.net/openjdk/jdk7) ## 1. ArraList [ArraList.java](https://github.com/CyC2018/InterviewNotes/blob/master/notes/src/ArrayList.java) -ʵ RandomAccess ӿڣ֧ʣȻģΪ ArrayList ǻʵֵġ +实现了 RandomAccess 接口,因此支持随机访问,这是理所当然的,因为 ArrayList 是基于数组实现的。 ```java public class ArrayList extends AbstractList implements List, RandomAccess, Cloneable, java.io.Serializable ``` -ʵ֣Ԫصʹ transient ΣΪ鲻һλöռԪأҲûҪȫлҪд writeObject() readObject() +基于数组实现,保存元素的数组使用 transient 修饰,这是因为该数组不一定所有位置都占满元素,因此也就没必要全部都进行序列化。需要重写 writeObject() 和 readObject()。 ```java private transient Object[] elementData; ``` -ĬϴСΪ 10 +数组的默认大小为 10 ```java public ArrayList(int initialCapacity) { @@ -132,7 +133,7 @@ public ArrayList() { } ``` -ɾԪʱ System.arraycopy() ԪؽиƣɾɱܸߣڴʱָŵСٸƲִд +删除元素时调用 System.arraycopy() 对元素进行复制,因此删除操作成本很高,最好在创建时就指定大概的容量大小,减少复制操作的执行次数。 ```java public E remove(int index) { @@ -150,9 +151,9 @@ public E remove(int index) { } ``` -Ԫʱʹ ensureCapacity() ֤㹻ʱҪݣʹΪ 1.5 +添加元素时使用 ensureCapacity() 方法来保证容量足够,如果不够时,需要进行扩容,使得新容量为旧容量的 1.5 倍。 -modCount ¼ ArrayList 仯ĴΪÿڽ add() addAll() ʱҪ ensureCapacity()ֱ ensureCapacity() ж modCount ޸ġ +modCount 用来记录 ArrayList 发生变化的次数,因为每次在进行 add() 和 addAll() 时都需要调用 ensureCapacity(),因此直接在 ensureCapacity() 中对 modCount 进行修改。 ```java public void ensureCapacity(int minCapacity) { @@ -190,7 +191,7 @@ private static int hugeCapacity(int minCapacity) { } ``` -ڽлߵȲʱҪȽϲǰ modCount Ƿı䣬ıҪ׳ ConcurrentModificationException +在进行序列化或者迭代等操作时,需要比较操作前后 modCount 是否改变,如果改变了需要抛出 ConcurrentModificationException。 ```java private void writeObject(java.io.ObjectOutputStream s) @@ -213,20 +214,20 @@ private void writeObject(java.io.ObjectOutputStream s) } ``` -** Vector ** +**和 Vector 的区别** -1. Vector ArrayList ȫͬģΨһ Vector ͬģ˿ͱ ArrayList Ҫ󣬷Ҫʹ ArrayList VectorΪͬȫɳԱԼƣ -2. Vector ÿС 2 ռ䣬 ArrayList 1.5 +1. Vector 和 ArrayList 几乎是完全相同的,唯一的区别在于 Vector 是同步的,因此开销就比 ArrayList 要大,访问要慢。最好使用 ArrayList 而不是 Vector,因为同步完全可以由程序员自己来控制; +2. Vector 每次扩容请求其大小的 2 倍空间,而 ArrayList 是 1.5 倍。 -Ϊʹ̰߳ȫ ArrayListʹ Collections.synchronizedList(new ArrayList<>()); һ̰߳ȫ ArrayListҲʹ concurrent µ CopyOnWriteArrayList ࣻ +为了使用线程安全的 ArrayList,可以使用 Collections.synchronizedList(new ArrayList<>()); 返回一个线程安全的 ArrayList,也可以使用 concurrent 并发包下的 CopyOnWriteArrayList 类; -** LinkedList ** +**和 LinkedList 的区别** -1. ArrayList ڶ̬ʵ֣LinkedList ˫ѭʵ֣ -2. ArrayList ֧ʣLinkedList ֧֣ -3. LinkedList λɾԪظ졣 +1. ArrayList 基于动态数组实现,LinkedList 基于双向循环链表实现; +2. ArrayList 支持随机访问,LinkedList 不支持; +3. LinkedList 在任意位置添加删除元素更快。 -## 2. Vector Stack +## 2. Vector 与 Stack [Vector.java](https://github.com/CyC2018/InterviewNotes/blob/master/notes/src/Vector.java) @@ -242,13 +243,13 @@ private void writeObject(java.io.ObjectOutputStream s) [HashMap.java](https://github.com/CyC2018/InterviewNotes/blob/master/notes/src/HashMap.java) -ʹͻ +使用拉链法来解决冲突。 -Ĭ capacity Ϊ 16Ҫע뱣֤Ϊ 2 Ĵη Entry[] table ijȣsize ʵʹ +默认容量 capacity 为 16,需要注意的是容量必须保证为 2 的次方。容量就是 Entry[] table 数组的长度,size 是数组的实际使用量。 -threshold 涨һ size ٽֵsize С thresholdڵڣͱݲ +threshold 规定了一个 size 的临界值,size 必须小于 threshold,如果大于等于,就必须进行扩容操作。 -threshold = capacity * load_factor load_factor Ϊ table ܹʹõıload_factor ᵼ¾۴صij֣ӶӰѯͲЧʣ㷨ʼǡ +threshold = capacity * load_factor,其中 load_factor 为 table 数组能够使用的比例,load_factor 过大会导致聚簇的出现,从而影响查询和插入的效率,详见算法笔记。 ```java static final int DEFAULT_INITIAL_CAPACITY = 16; @@ -268,7 +269,7 @@ final float loadFactor; transient int modCount; ``` -ԪشпԿҪʱ capacity Ϊԭ +从下面的添加元素代码中可以看出,当需要扩容时,令 capacity 为原来的两倍。 ```java void addEntry(int hash, K key, V value, int bucketIndex) { @@ -279,7 +280,7 @@ void addEntry(int hash, K key, V value, int bucketIndex) { } ``` -Entry ʾһֵԪأе next ָлʱʹá +Entry 用来表示一个键值对元素,其中的 next 指针在序列化时会使用。 ```java static class Entry implements Map.Entry { @@ -290,7 +291,7 @@ static class Entry implements Map.Entry { } ``` -get() Ҫֳkey Ϊ null Ϊ nullпԿ HashMap null Ϊ +get() 操作需要分成两种情况,key 为 null 和 不为 null,从中可以看出 HashMap 允许插入 null 作为键。 ```java public V get(Object key) { @@ -306,7 +307,7 @@ public V get(Object key) { } ``` -put() ҲҪ key ǷΪ null ͬĴҪעû key Ϊ null ļֵԣ²һ key Ϊ null ļֵʱĬǷ 0 λãΪ null ܼ hash ֵҲ޷֪Ӧ÷ĸϡ +put() 操作也需要根据 key 是否为 null 做不同的处理,需要注意的是如果本来没有 key 为 null 的键值对,新插入一个 key 为 null 的键值对时默认是放在数组的 0 位置,这是因为 null 不能计算 hash 值,也就无法知道应该放在哪个链表上。 ```java public V put(K key, V value) { @@ -354,8 +355,8 @@ private V putForNullKey(V value) { [ConcurrentHashMap.java](https://github.com/CyC2018/InterviewNotes/blob/master/notes/src/HashMap.java) -[ ̽ ConcurrentHashMap ߲Եʵֻ ](https://www.ibm.com/developerworks/cn/java/java-lo-concurrenthashmap/) +[ 探索 ConcurrentHashMap 高并发性的实现机制 ](https://www.ibm.com/developerworks/cn/java/java-lo-concurrenthashmap/) -# ο +# 参考资料 -- Java ˼ +- Java 编程思想 diff --git a/notes/Leetcode 题解.md b/notes/Leetcode 题解.md index e7c0d9a2..8af37740 100644 --- a/notes/Leetcode 题解.md +++ b/notes/Leetcode 题解.md @@ -1,66 +1,67 @@ -* [㷨˼](#㷨˼) - * [ֲ](#ֲ) - * [̰˼](#̰˼) - * [˫ָ](#˫ָ) - * [](#) - * [ѡ](#ѡ) - * [](#) - * [Ͱ](#Ͱ) - * [](#) +* [算法思想](#算法思想) + * [二分查找](#二分查找) + * [贪心思想](#贪心思想) + * [双指针](#双指针) + * [排序](#排序) + * [快速选择](#快速选择) + * [堆排序](#堆排序) + * [桶排序](#桶排序) + * [搜索](#搜索) * [BFS](#bfs) * [DFS](#dfs) * [Backtracking](#backtracking) - * [](#) - * [̬滮](#̬滮) - * [ָ](#ָ) - * [·](#·) - * [쳲](#쳲) - * [](#) - * [ϵ](#ϵ) - * [0-1 ](#0-1-) - * [](#) - * [ַ༭](#ַ༭) - * [](#) - * [ѧ](#ѧ) - * [](#) - * [Լ](#Լ) - * [ת](#ת) - * [׳](#׳) - * [ַӷ](#ַӷ) - * [](#) - * [ͶƱ](#ͶƱ) - * [](#) -* [ݽṹ](#ݽṹ) - * [ջͶ](#ջͶ) - * [ϣ](#ϣ) - * [ַ](#ַ) - * [](#) - * [](#) - * [](#) - * [](#) - * [ݹ](#ݹ) - * [α](#α) - * [ǰк](#ǰк) + * [分治](#分治) + * [动态规划](#动态规划) + * [分割整数](#分割整数) + * [矩阵路径](#矩阵路径) + * [斐波那契数列](#斐波那契数列) + * [最长递增子序列](#最长递增子序列) + * [最长公共子系列](#最长公共子系列) + * [0-1 背包](#0-1-背包) + * [数组区间](#数组区间) + * [字符串编辑](#字符串编辑) + * [其它问题](#其它问题) + * [数学](#数学) + * [素数](#素数) + * [最大公约数](#最大公约数) + * [进制转换](#进制转换) + * [阶乘](#阶乘) + * [字符串加法减法](#字符串加法减法) + * [相遇问题](#相遇问题) + * [多数投票问题](#多数投票问题) + * [其它](#其它) +* [数据结构相关](#数据结构相关) + * [栈和队列](#栈和队列) + * [哈希表](#哈希表) + * [字符串](#字符串) + * [数组与矩阵](#数组与矩阵) + * [有序矩阵](#有序矩阵) + * [链表](#链表) + * [树](#树) + * [递归](#递归) + * [层次遍历](#层次遍历) + * [前中后序遍历](#前中后序遍历) * [BST](#bst) * [Trie](#trie) - * [ͼ](#ͼ) - * [λ](#λ) -* [ο](#ο) + * [图](#图) + * [位运算](#位运算) +* [参考资料](#参考资料) -# 㷨˼ -## ֲ +# 算法思想 -ֲ˼򵥣ʵʱһЩҪעϸڣ +## 二分查找 -1. ڼ mid ʱʹ mid = (l + h) / 2 ַʽΪ l + h ܻᵼ¼ӷӦʹ mid = l + (h - l) / 2 +二分查找思想简单,但是在实现时有一些需要注意的细节: -2. h ĸֵѭйأѭΪ l <= h ʱh = mid - 1ѭΪ l < h ʱh = mid -£ѭΪ l <= h ʱ h = midѭ޷˳ l = 1h = 1ʱ mid Ҳ 1ʱִ h = midôͻѭѭΪ l < h h = mid - 1ҵ [1,2,3]Ҫ 1ʼ l = 0h = 2mid = 1ж key < arr[mid] ִ h = mid - 1 = 0ʱѭ˳ֱӰѲҵˡ +1. 在计算 mid 时不能使用 mid = (l + h) / 2 这种方式,因为 l + h 可能会导致加法溢出,应该使用 mid = l + (h - l) / 2。 -3. l ĸֵһ㶼Ϊ l = mid + 1 +2. 对 h 的赋值和循环条件有关,当循环条件为 l <= h 时,h = mid - 1;当循环条件为 l < h 时,h = mid。 +解释如下:在循环条件为 l <= h 时,如果 h = mid,会出现循环无法退出的情况,例如 l = 1,h = 1,此时 mid 也等于 1,如果此时继续执行 h = mid,那么就会无限循环;在循环条件为 l < h,如果 h = mid - 1,会错误跳过查找的数,例如对于数组 [1,2,3],要查找 1,最开始 l = 0,h = 2,mid = 1,判断 key < arr[mid] 执行 h = mid - 1 = 0,此时循环退出,直接把查找的数跳过了。 + +3. l 的赋值一般都为 l = mid + 1。 ```java public int search(int key, int[] arr) { @@ -75,11 +76,11 @@ public int search(int key, int[] arr) { } ``` -**󿪷** +**求开方** [Leetcode : 69. Sqrt(x) (Easy)](https://leetcode.com/problems/sqrtx/description/) -һ x Ŀ sqrt һ 0 \~ x ֮䣬 sqrt == x / sqrt öֲ 0 \~ x ֮ sqrt +一个数 x 的开方 sqrt 一定在 0 \~ x 之间,并且满足 sqrt == x / sqrt 。可以利用二分查找在 0 \~ x 之间查找 sqrt。 ```java public int mySqrt(int x) { @@ -96,7 +97,7 @@ public int mySqrt(int x) { } ``` -**Ӳ** +**摆硬币** [Leetcode : 441. Arranging Coins (Easy)](https://leetcode.com/problems/arranging-coins/description/) @@ -104,17 +105,17 @@ public int mySqrt(int x) { n = 8 The coins can form the following rows: - - - - +¤ +¤ ¤ +¤ ¤ ¤ +¤ ¤ Because the 4th row is incomplete, we return 3. ``` -Ŀ i а i ͳܹڵ +题目描述:第 i 行摆 i 个,统计能够摆的行数。 - h lΪڵӲһвȥ +返回 h 而不是 l,因为摆的硬币最后一行不能算进去。 ```java public int arrangeCoins(int n) { @@ -130,7 +131,7 @@ public int arrangeCoins(int n) { } ``` -Բöֲңֱ۵Ľⷨ£ +可以不用二分查找,更直观的解法如下: ```java public int arrangeCoins(int n) { @@ -143,18 +144,18 @@ public int arrangeCoins(int n) { } ``` -** Single Element** +**有序数组的 Single Element** [Leetcode : 540. Single Element in a Sorted Array (Medium)](https://leetcode.com/problems/single-element-in-a-sorted-array/description/) -ĿһֻһΣҳ +题目描述:一个有序数组只有一个数不出现两次,找出这个数。 ```java public int singleNonDuplicate(int[] nums) { int l = 0, h = nums.length - 1; while(l < h) { int m = l + (h - l) / 2; - if(m % 2 == 1) m--; // ֤ l/h/m żλʹòСһֱ + if(m % 2 == 1) m--; // 保证 l/h/m 都在偶数位,使得查找区间大小一直都是奇数 if(nums[m] == nums[m + 1]) l = m + 2; else h = m; } @@ -162,19 +163,19 @@ public int singleNonDuplicate(int[] nums) { } ``` -## ̰˼ +## 贪心思想 -̰˼뱣֤ÿβǾֲŵģõĽȫŵġ +贪心思想保证每次操作都是局部最优的,并且最后得到的结果是全局最优的。 -**** +**分配饼干** [Leetcode : 455. Assign Cookies (Easy)](https://leetcode.com/problems/assign-cookies/description/) -ĿÿӶһȣÿɶһСֻбɵĴСһӵȣúӲŻ㡣Իĺ +题目描述:每个孩子都有一个满足度,每个饼干都有一个大小,只有饼干的大小大于一个孩子的满足度,该孩子才会获得满足。求解最多可以获得满足的孩子数量。 -ΪСĺ׵õ㣬СӡһӵıӦСúӣɾȱȽϴĺӡ +因为最小的孩子最容易得到满足,因此先满足最小孩子。给一个孩子的饼干应当尽量小又能满足该孩子,这样大饼干就能拿来给满足度比较大的孩子。 -֤ijѡУ̰IJѡ i ӷ m ɣҵ i С m Ϊ i ӵСɣ̰IJտ k ӡŲѡи i ӷ n ɣɴڵ m ɡǷʹõ m ȥ n ȫӰĽ˲ڱ̰IJԸŵIJԣ̰IJԾŲԡ +证明:假设在某次选择中,贪心策略选择给第 i 个孩子分配第 m 个饼干,并且第 i 个孩子满足度最小,第 m 个饼干为可以满足第 i 个孩子的最小饼干,利用贪心策略最终可以满足 k 个孩子。假设最优策略在这次选择中给 i 个孩子分配第 n 个饼干,并且这个饼干大于第 m 个饼干。我们发现使用第 m 个饼干去替代第 n 个饼干完全不影响后续的结果,因此不存在比贪心策略更优的策略,即贪心策略就是最优策略。 ```java public int findContentChildren(int[] g, int[] s) { @@ -189,7 +190,7 @@ public int findContentChildren(int[] g, int[] s) { } ``` -**Ͷڴ** +**投飞镖刺破气球** [Leetcode : 452. Minimum Number of Arrows to Burst Balloons (Medium)](https://leetcode.com/problems/minimum-number-of-arrows-to-burst-balloons/description/) @@ -201,12 +202,12 @@ Output: 2 ``` -Ŀһˮƽϰڷţصڴֱᣬʹ·ϵ򶼻ơСͶڴʹ򶼱ơ +题目描述:气球在一个水平数轴上摆放,可以重叠,飞镖垂直射向坐标轴,使得路径上的气球都会刺破。求解最小的投飞镖次数使所有气球都被刺破。 -ͶڣÿͶʱ +从左往右投飞镖,并且在每次投飞镖时满足以下条件: -1. Ѿûˣ -2. Ͷܹ +1. 左边已经没有气球了; +2. 本次投飞镖能够刺破最多的气球。 ```java public int findMinArrowShots(int[][] points) { @@ -225,13 +226,13 @@ public int findMinArrowShots(int[][] points) { } ``` -**Ʊ** +**股票的最大收益** [Leetcode : 122. Best Time to Buy and Sell Stock II (Easy)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-ii/description/) -Ŀһνװ֮䲻ܽС +题目描述:一次交易包含买入和卖出,多个交易之间不能交叉进行。 - [a, b, c, d] a <= b <= c <= d ôΪ d - a d - a = (d - c) + (c - b) + (b - a) ˵ʵһ prices[i] prices[i] - prices[i-1] > 0ôͰ prices[i] - prices[i-1] ӼӵУӶھֲŵҲ֤ȫš +对于 [a, b, c, d],如果有 a <= b <= c <= d ,那么最大收益为 d - a。而 d - a = (d - c) + (c - b) + (b - a) ,因此当访问到一个 prices[i] 且 prices[i] - prices[i-1] > 0,那么就把 prices[i] - prices[i-1] 添加加到收益中,从而在局部最优的情况下也保证全局最优。 ```java public int maxProfit(int[] prices) { @@ -243,7 +244,7 @@ public int maxProfit(int[] prices) { } ``` -**ֲ** +**种植花朵** [Leetcode : 605. Can Place Flowers (Easy)](https://leetcode.com/problems/can-place-flowers/description/) @@ -252,7 +253,7 @@ Input: flowerbed = [1,0,0,0,1], n = 1 Output: True ``` -Ŀ֮Ҫһλļ +题目描述:花朵之间至少需要一个单位的间隔。 ```java public boolean canPlaceFlowers(int[] flowerbed, int n) { @@ -270,13 +271,13 @@ public boolean canPlaceFlowers(int[] flowerbed, int n) { } ``` -**޸һΪǵݼ** +**修改一个数成为非递减数组** [Leetcode : 665. Non-decreasing Array (Easy)](https://leetcode.com/problems/non-decreasing-array/description/) -Ŀжһֻܲ޸һͳΪǵݼ顣 +题目描述:判断一个数组能不能只修改一个数就成为非递减数组。 -ڳ nums[i] < nums[i - 1] ʱҪǵӦ޸ĸʹñ޸ʹ i ֮ǰΪǵݼ飬 **ӰIJ**ȿ nums[i - 1] = nums[i]Ϊ޸ nums[i] = nums[i - 1] Ļô nums[i] ôпܱ nums[i + 1] 󣬴ӶӰ˺һȽر nums[i] < nums[i - 2]ֻ޸ nums[i - 1] = nums[i] Ϊǵݼֻͨ޸ nums[i] = nums[i - 1] С +在出现 nums[i] < nums[i - 1] 时,需要考虑的是应该修改数组的哪个数,使得本次修改能使 i 之前的数组成为非递减数组,并且 **不影响后续的操作**。优先考虑令 nums[i - 1] = nums[i],因为如果修改 nums[i] = nums[i - 1] 的话,那么 nums[i] 这个数会变大,那么就有可能比 nums[i + 1] 大,从而影响了后续操作。还有一个比较特别的情况就是 nums[i] < nums[i - 2],只修改 nums[i - 1] = nums[i] 不能令数组成为非递减,只能通过修改 nums[i] = nums[i - 1] 才行。 ```java public boolean checkPossibility(int[] nums) { @@ -292,7 +293,7 @@ public boolean checkPossibility(int[] nums) { } ``` -**жǷΪӴ** +**判断是否为子串** [Leetcode : 392. Is Subsequence (Medium)](https://leetcode.com/problems/is-subsequence/description/) @@ -311,7 +312,7 @@ public boolean isSubsequence(String s, String t) { } ``` -**ַָʹַͬһ** +**分隔字符串使同种字符出现在一起** [Leetcode : 763. Partition Labels (Medium)](https://leetcode.com/problems/partition-labels/description/) @@ -344,7 +345,7 @@ public List partitionLabels(String S) { } ``` -**ߺ** +**根据身高和序号重组队列** [Leetcode : 406. Queue Reconstruction by Height(Medium)](https://leetcode.com/problems/queue-reconstruction-by-height/description/) @@ -356,11 +357,11 @@ Output: [[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]] ``` -Ŀһѧ (h, k) h ʾߣk ʾǰ k ѧ߱߻ߺһߡ +题目描述:一个学生用两个分量 (h, k) 描述,h 表示身高,k 表示排在前面的有 k 个学生的身高比他高或者和他一样高。 -ΪÿβʱӰIJ߽ϸߵѧӦ߽Сѧԭȷ k λÿܻɵ k+1 λá +为了在每次插入操作时不影响后续的操作,身高较高的学生应该先做插入操作,否则身高较小的学生原先正确插入第 k 个位置可能会变成第 k+1 个位置。 -߽k ֵȻź˳еĵ k λС +身高降序、k 值升序,然后按排好序的顺序插入队列的第 k 个位置中。 ```java public int[][] reconstructQueue(int[][] people) { @@ -388,17 +389,17 @@ public int[][] reconstructQueue(int[][] people) { } ``` -## ˫ָ +## 双指针 -˫ָҪڱ飬ָָͬԪأӶЭͬ +双指针主要用于遍历数组,两个指针指向不同的元素,从而协同完成任务。 -**һѾвҳʹǵĺΪ 0** +**从一个已经排序的数组中查找出两个数,使它们的和为 0** -[Leetcode 167. Two Sum II - Input array is sorted (Easy)](https://leetcode.com/problems/two-sum-ii-input-array-is-sorted/description/) +[Leetcode :167. Two Sum II - Input array is sorted (Easy)](https://leetcode.com/problems/two-sum-ii-input-array-is-sorted/description/) -ʹ˫ָ룬һָָԪؽСֵһָָԪؽϴֵָСԪصָͷβָϴԪصָβͷ +使用双指针,一个指针指向元素较小的值,一个指针指向元素较大的值。指向较小元素的指针从头向尾遍历,指向较大元素的指针从尾向头遍历。 -ָָԪصĺ sum == targetôõҪĽ sum > targetƶϴԪأʹ sum СһЩ sum < targetƶСԪأʹ sum һЩ +如果两个指针指向元素的和 sum == target,那么得到要求的结果;如果 sum > target,移动较大的元素,使 sum 变小一些;如果 sum < target,移动较小的元素,使 sum 变大一些。 ```java public int[] twoSum(int[] numbers, int target) { @@ -413,11 +414,11 @@ public int[] twoSum(int[] numbers, int target) { } ``` -**תַеԪַ** +**反转字符串中的元音字符** [Leetcode : 345. Reverse Vowels of a String (Easy)](https://leetcode.com/problems/reverse-vowels-of-a-string/description/) -ʹ˫ָ룬ָתԪַһָͷβһָβͷ +使用双指针,指向待反转的两个元音字符,一个指针从头向尾遍历,一个指针从尾到头遍历。 ```java private HashSet vowels = new HashSet<>(Arrays.asList('a','e','i','o','u','A','E','I','O','U')); @@ -446,11 +447,11 @@ public String reverseVowels(String s) { } ``` -**ƽ** +**两数平方和** [Leetcode : 633. Sum of Square Numbers (Easy)](https://leetcode.com/problems/sum-of-square-numbers/description/) -ĿжһǷΪƽͣ 5 = 12 + 22 +题目描述:判断一个数是否为两个数的平方和,例如 5 = 12 + 22。 ```java public boolean judgeSquareSum(int c) { @@ -465,11 +466,11 @@ public boolean judgeSquareSum(int c) { } ``` -**ַ** +**回文字符串** [Leetcode : 680. Valid Palindrome II (Easy)](https://leetcode.com/problems/valid-palindrome-ii/description/) -ĿַɾһַжǷܹɻַ +题目描述:字符串可以删除一个字符,判断是否能构成回文字符串。 ```java public boolean validPalindrome(String s) { @@ -495,15 +496,15 @@ private boolean isPalindrome(String s, int l, int r){ } ``` -**鲢** +**归并两个有序数组** [Leetcode : 88. Merge Sorted Array (Easy)](https://leetcode.com/problems/merge-sorted-array/description/) -Ŀѹ鲢浽һ +题目描述:把归并结果存到第一个数组上 ```java public void merge(int[] nums1, int m, int[] nums2, int n) { - int i = m - 1, j = n - 1; // Ҫβʼ nums1 Ϲ鲢õֵḲǻδй鲢Ƚϵֵ + int i = m - 1, j = n - 1; // 需要从尾开始遍历,否则在 nums1 上归并得到的值会覆盖还未进行归并比较的值 int idx = m + n - 1; while(i >= 0 || j >= 0){ if(i < 0) nums1[idx] = nums2[j--]; @@ -515,11 +516,11 @@ public void merge(int[] nums1, int m, int[] nums2, int n) { } ``` -**жǷڻ** +**判断链表是否存在环** [Leetcode : 141. Linked List Cycle (Easy)](https://leetcode.com/problems/linked-list-cycle/description/) -ʹ˫ָ룬һָÿƶһڵ㣬һָÿƶڵ㣬ڻôָһ +使用双指针,一个指针每次移动一个节点,一个指针每次移动两个节点,如果存在环,那么这两个指针一定会相遇。 ```java public boolean hasCycle(ListNode head) { @@ -535,7 +536,7 @@ public boolean hasCycle(ListNode head) { } ``` -**** +**最长子序列** [Leetcode : 524. Longest Word in Dictionary through Deleting (Medium)](https://leetcode.com/problems/longest-word-in-dictionary-through-deleting/description/) @@ -547,7 +548,7 @@ Output: "apple" ``` -Ŀɾ s еһЩַʹΪַб d еһַҪ d ҵַ +题目描述:可以删除 s 中的一些字符,使得它成为字符串列表 d 中的一个字符串。要求在 d 中找到满足条件的最长字符串。 ```java public String findLongestWord(String s, List d) { @@ -567,23 +568,23 @@ public String findLongestWord(String s, List d) { } ``` -## +## 排序 -### ѡ +### 快速选择 -һ **Kth Element** ⣬ O(n) ʱ临ӶȣO(1) ռ临Ӷ⹤ +一般用于求解 **Kth Element** 问题,可以在 O(n) 时间复杂度,O(1) 空间复杂度完成求解工作。 -һѡһҪȴ飬ʱ临ӶΪ O(n2) +与快速排序一样,快速选择一般需要先打乱数组,否则最坏情况下时间复杂度为 O(n2)。 -### +### 堆排序 - **TopK Elements** ⣬ͨάһСΪ K ĶѣеԪؾ TopK ElementsȻҲ Kth Element ⣬ΪѵǸԪؾ Kth ElementѡҲ TopK Elements ⣬Ϊҵ Kth Element ֮ٱһ飬Сڵ Kth Element Ԫض TopK ElementsԿѡͶ򶼿 Kth Element TopK Elements ⡣ +堆排序用于求解 **TopK Elements** 问题,通过维护一个大小为 K 的堆,堆中的元素就是 TopK Elements。当然它也可以用于求解 Kth Element 问题,因为最后出堆的那个元素就是 Kth Element。快速选择也可以求解 TopK Elements 问题,因为找到 Kth Element 之后,再遍历一次数组,所有小于等于 Kth Element 的元素都是 TopK Elements。可以看到,快速选择和堆排序都可以求解 Kth Element 和 TopK Elements 问题。 **Kth Element** [Leetocde : 215. Kth Largest Element in an Array (Medium)](https://leetcode.com/problems/kth-largest-element-in-an-array/description/) -****ʱ临Ӷ O(nlgn)ռ临Ӷ O(1) ⷨ +**排序**:时间复杂度 O(nlgn),空间复杂度 O(1) 解法 ```java public int findKthLargest(int[] nums, int k) { @@ -593,7 +594,7 @@ public int findKthLargest(int[] nums, int k) { } ``` -****ʱ临Ӷ O(nlgk)ռ临Ӷ O(k) +**堆排序**:时间复杂度 O(nlgk),空间复杂度 O(k) ```java public int findKthLargest(int[] nums, int k) { @@ -608,7 +609,7 @@ public int findKthLargest(int[] nums, int k) { } ``` -**ѡ**ʱ临Ӷ O(n)ռ临Ӷ O(1) +**快速选择**:时间复杂度 O(n),空间复杂度 O(1) ```java public int findKthLargest(int[] nums, int k) { @@ -655,9 +656,9 @@ public int findKthLargest(int[] nums, int k) { } ``` -### Ͱ +### 桶排序 -**ҳƵ k ** +**找出出现频率最多的 k 个数** [Leetcode : 347. Top K Frequent Elements (Medium)](https://leetcode.com/problems/top-k-frequent-elements/description/) @@ -686,28 +687,28 @@ public List topKFrequent(int[] nums, int k) { } ``` -## +## 搜索 -͹㷺ͼУǵӦԶԶֹˡ +深度优先搜索和广度优先搜索广泛运用于树和图中,但是它们的应用远远不止如此。 ### BFS -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/4ff355cf-9a7f-4468-af43-e5b02038facc.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//4ff355cf-9a7f-4468-af43-e5b02038facc.jpg) -еһһؽбӽڵ 0 621 5 ĸ½ڵ㡣 +广度优先搜索的搜索过程有点像一层一层地进行遍历:从节点 0 出发,遍历到 6、2、1 和 5 这四个新节点。 - 6 ʼõڵ 4 2 ʼûһڵ㣻 1 ʼûһڵ㣻 5 ʼõ 3 4 ڵ㡣һܹõ½ڵ㣺4 3 +继续从 6 开始遍历,得到节点 4 ;从 2 开始遍历,没有下一个节点;从 1 开始遍历,没有下一个节点;从 5 开始遍历,得到 3 和 4 节点。这一轮总共得到两个新节点:4 和 3 。 -½ڵı +反复从新节点出发进行上述的遍历操作。 -ԿÿһֱĽڵ㶼ڵ·ͬ di ʾ i ڵڵ·ȣƵһۣȱĽڵ i Ľڵ j di<=djۣ· **Ž** ⣺һαĿĽڵ㣬·Ϊ·֮ٱĿĽڵ㣬·Ͳ· +可以看到,每一轮遍历的节点都与根节点路径长度相同。设 di 表示第 i 个节点与根节点的路径长度,推导出一个结论:对于先遍历的节点 i 与后遍历的节点 j,有 di<=dj。利用这个结论,可以求解最短路径 **最优解** 问题:第一次遍历到目的节点,其所经过的路径为最短路径,如果继续遍历,之后再遍历到目的节点,所经过的路径就不是最短路径。 -ڳʵ BFS ʱҪ⣺ +在程序实现 BFS 时需要考虑以下问题: -- У洢ÿһֱĽڵ -- ǣڱýڵ㣬Ӧýǣֹظ +- 队列:用来存储每一轮遍历的节点 +- 标记:对于遍历过得节点,应该将它标记,防止重复遍历; -**дԭ㵽ض·** +**计算在网格中从原点到特定点的最短路径长度** ```html [[1,1,0,1], @@ -749,18 +750,18 @@ private class Position { ### DFS -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/f7f7e3e5-7dd4-4173-9999-576b9e2ac0a2.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//f7f7e3e5-7dd4-4173-9999-576b9e2ac0a2.png) -һһÿһ½ڵ㣬Ҫöȴ洢Աһʱٱڱһ½ڵʱ½ڵбӽڵ 0 ʼõ½ڵ 6 ʱ½ڵ 6 бõ½ڵ 4˷ַʽ½ڵ㣬ֱû½ڵˣʱءصڵ 0 ǣԸڵ 0 бõ½ڵ 2Ȼϲ衣 +广度优先搜索一层一层遍历,每一层遍历到的所有新节点,要用队列先存储起来以备下一层遍历的时候再遍历;而深度优先搜索在遍历到一个新节点时立马对新节点进行遍历:从节点 0 出发开始遍历,得到到新节点 6 时,立马对新节点 6 进行遍历,得到新节点 4;如此反复以这种方式遍历新节点,直到没有新节点了,此时返回。返回到根节点 0 的情况是,继续对根节点 0 进行遍历,得到新节点 2,然后继续以上步骤。 -һڵʹ DFS һͼбʱܹĽڵ㶼ǴӳʼڵɴģDFS **ɴ** ⡣ +从一个节点出发,使用 DFS 对一个图进行遍历时,能够遍历到的节点都是从初始节点可达的,DFS 常用来求解这种 **可达性** 问题。 -ڳʵ DFS ʱҪ⣺ +在程序实现 DFS 时需要考虑以下问题: -- ջջ浱ǰڵϢ½ڵ㷵ʱܹǰڵ㡣Ҳʹõݹջ -- ǣ BFS һͬҪѾýڵбǡ +- 栈:用栈来保存当前节点信息,当遍历新节点返回时能够继续遍历当前节点。也可以使用递归栈。 +- 标记:和 BFS 一样同样需要对已经遍历过得节点进行标记。 -**ͨ** +**查找最大的连通面积** [Leetcode : 695. Max Area of Island (Easy)](https://leetcode.com/problems/max-area-of-island/description/) @@ -796,7 +797,7 @@ private int dfs(int[][] grid, int i, int j){ } ``` -**ͼͨ** +**图的连通分量** [Leetcode : 547. Friend Circles (Medium)](https://leetcode.com/problems/friend-circles/description/) @@ -835,7 +836,7 @@ private void dfs(int[][] M, int i, boolean[] hasFind) { } ``` -**еͨ** +**矩阵中的连通区域数量** [Leetcode : 200. Number of Islands (Medium)](https://leetcode.com/problems/number-of-islands/description/) @@ -876,7 +877,7 @@ private void dfs(char[][] grid, int i, int j) { } ``` -**дӸҶӵ·** +**输出二叉树中所有从根到叶子的路径** [Leetcode : 257. Binary Tree Paths (Easy)](https://leetcode.com/problems/binary-tree-paths/description/) @@ -911,7 +912,7 @@ private void dfs(TreeNode root, String prefix, List ret){ } ``` -**** +**填充封闭区域** [Leetcode : 130. Surrounded Regions (Medium)](https://leetcode.com/problems/surrounded-regions/description/) @@ -929,9 +930,9 @@ X X X X X O X X ``` -Ŀʹñ 'X' 'O' תΪ 'X' +题目描述:使得被 'X' 的 'O' 转换为 'X'。 -࣬ʣµľˡ +先填充最外侧,剩下的就是里侧了。 ```java private int[][] direction = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}}; @@ -966,26 +967,26 @@ private void dfs(char[][] board, int r, int c) { } ``` -**ܵ** +**从两个方向都能到达的区域** [Leetcode : 417. Pacific Atlantic Water Flow (Medium)](https://leetcode.com/problems/pacific-atlantic-water-flow/description/) ```html Given the following 5x5 matrix: - Pacific \~ \~ \~ \~ \~ - \~ 1 2 2 3 (5) * - \~ 3 2 3 (4) (4) * - \~ 2 4 (5) 3 1 * - \~ (6) (7) 1 4 5 * - \~ (5) 1 1 2 4 * + Pacific ~ ~ ~ ~ ~ + ~ 1 2 2 3 (5) * + ~ 3 2 3 (4) (4) * + ~ 2 4 (5) 3 1 * + ~ (6) (7) 1 4 5 * + ~ (5) 1 1 2 4 * * * * * * Atlantic Return: [[0, 4], [1, 3], [1, 4], [2, 2], [3, 0], [3, 1], [4, 0]] (positions with parentheses in above matrix). ``` -Ŀߺϱ̫ƽұߺ±ǴڲִΣθߵĵطˮܹ͵ĵطˮܹ̫ƽʹλá +题目描述:左边和上边是太平洋,右边和下边是大西洋,内部的数字代表海拔,海拔高的地方的水能够流到低的地方,求解水能够流到太平洋和大西洋的所有位置。 ```java private int m, n; @@ -1031,23 +1032,23 @@ private void dfs(int r, int c, boolean[][] canReach) { } ``` -**N ʺ** +**N 皇后** [Leetcode : 51. N-Queens (Hard)](https://leetcode.com/problems/n-queens/description/) -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/1f080e53-4758-406c-bb5f-dbedf89b63ce.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//1f080e53-4758-406c-bb5f-dbedf89b63ce.jpg) -Ŀ n\*n ľаڷ n ʺ󣬲ÿʺͬһУͬһУͬһԽϣҪе n ʺ⡣ +题目描述:在 n\*n 的矩阵中摆放 n 个皇后,并且每个皇后不能在同一行,同一列,同一对角线上,要求解所有的 n 皇后解。 -һһеذڷţȷһеǸʺӦðһʱҪȷijһǷϷֱΪб顢45 ȶԽ߱ 135 ȶԽ߱顣 +一行一行地摆放,在确定一行中的那个皇后应该摆在哪一列时,需要用三个标记数组来确定某一列是否合法,这三个标记数组分别为:列标记数组、45 度对角线标记数组和 135 度对角线标记数组。 -45 ȶԽ߱άΪ 2\*n - 1ͨͼȷ (r,c) λڵ±Ϊ r + c +45 度对角线标记数组的维度为 2\*n - 1,通过下图可以明确 (r,c) 的位置所在的数组下标为 r + c。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/85583359-1b45-45f2-9811-4f7bb9a64db7.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//85583359-1b45-45f2-9811-4f7bb9a64db7.jpg) -135 ȶԽ߱άҲ 2\*n - 1(r,c) λڵ±Ϊ n - 1 - (r - c) +135 度对角线标记数组的维度也是 2\*n - 1,(r,c) 的位置所在的数组下标为 n - 1 - (r - c)。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/9e80f75a-b12b-4344-80c8-1f9ccc2d5246.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//9e80f75a-b12b-4344-80c8-1f9ccc2d5246.jpg) ```java private List> ret; @@ -1096,15 +1097,15 @@ private void backstracking(int row) { ### Backtracking - DFS һ֣ڱͼĽڵϣ **** ⣬ { 'a','b','c' } ַַеõַ +回溯是 DFS 的一种,它不是用在遍历图的节点上,而是用于求解 **排列组合** 问题,例如有 { 'a','b','c' } 三个字符,求解所有由这三个字符排列得到的字符串。 -ڳʵʱҪעԪؽбǵ⡣ʹõݹʵֵĻݣڷһԪؽµĵݹãʱҪԪرΪѾʣڼݹʱظʸԪأڵݹ鷵ʱҪԪرΪδʣΪֻҪ֤һݹвͬʱһԪأڲͬĵݹǿԷѾʹDzڵǰݹеԪء +在程序实现时,回溯需要注意对元素进行标记的问题。使用递归实现的回溯,在访问一个新元素进入新的递归调用,此时需要将新元素标记为已经访问,这样才能在继续递归调用时不用重复访问该元素;但是在递归返回时,需要将该元素标记为未访问,因为只需要保证在一个递归链中不同时访问一个元素,而在不同的递归链是可以访问已经访问过但是不在当前递归链中的元素。 -**ּ** +**数字键盘组合** [Leetcode : 17. Letter Combinations of a Phone Number (Medium)](https://leetcode.com/problems/letter-combinations-of-a-phone-number/description/) -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/a3f34241-bb80-4879-8ec9-dff2d81b514e.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//a3f34241-bb80-4879-8ec9-dff2d81b514e.jpg) ```html Input:Digit string "23" @@ -1134,7 +1135,7 @@ private void combination(String prefix, String digits, int offset, List } ``` -**ھѰַ** +**在矩阵中寻找字符串** [Leetcode : 79. Word Search (Medium)](https://leetcode.com/problems/word-search/description/) @@ -1189,7 +1190,7 @@ private boolean dfs(char[][] board, String word, int start, int r, int c) { } ``` -**IP ַ** +**IP 地址划分** [Leetcode : 93. Restore IP Addresses(Medium)](https://leetcode.com/problems/restore-ip-addresses/description/) @@ -1224,7 +1225,7 @@ private void doRestore(int k, String path, String s) { } ``` -**** +**排列** [Leetcode : 46. Permutations (Medium)](https://leetcode.com/problems/permutations/description/) @@ -1266,7 +1267,7 @@ private void backtracking(List permuteList, boolean[] visited, int[] nu } ``` -**ͬԪ** +**含有相同元素求排列** [Leetcode : 47. Permutations II (Medium)](https://leetcode.com/problems/permutations-ii/description/) @@ -1275,9 +1276,9 @@ private void backtracking(List permuteList, boolean[] visited, int[] nu [[1,1,2], [1,2,1], [2,1,1]] ``` -ĿԪؿܺͬԪأʱпܳظУҪظֻһ +题目描述:数组元素可能含有相同的元素,进行排列时就有可能出先重复的排列,要求重复的排列只返回一个。 -ʵϣ Permutations ͬҪȻһԪʱжԪǷǰһԪأڣǰһԪػδʣôԪء +在实现上,和 Permutations 不同的是要先排序,然后在添加一个元素时,判断这个元素是否等于前一个元素,如果等于,并且前一个元素还未访问,那么就跳过这个元素。 ```java public List> permuteUnique(int[] nums) { @@ -1307,7 +1308,7 @@ private void backtracking(List permuteList, boolean[] visited, int[] nu } ``` -**** +**组合** [Leetcode : 77. Combinations (Medium)](https://leetcode.com/problems/combinations/description/) @@ -1333,20 +1334,20 @@ public List> combine(int n, int k) { private void backtracking(int start, int n, int k, List combineList, List> ret){ if(k == 0){ - ret.add(new ArrayList(combineList)); // Ҫ¹һ List + ret.add(new ArrayList(combineList)); // 这里要重新构造一个 List return; } - for(int i = start; i <= n - k + 1; i++){ // ֦ + for(int i = start; i <= n - k + 1; i++){ // 剪枝 - combineList.add(i); // i Ϊѷ + combineList.add(i); // 把 i 标记为已访问 backtracking(i + 1, n, k - 1, combineList, ret); - combineList.remove(combineList.size() - 1); // i Ϊδ + combineList.remove(combineList.size() - 1); // 把 i 标记为未访问 } } ``` -**** +**组合求和** [Leetcode : 39. Combination Sum (Medium)](https://leetcode.com/problems/combination-sum/description/) @@ -1380,7 +1381,7 @@ A solution set is: } ``` -**ͬԪص** +**含有相同元素的求组合求和** [Leetcode : 40. Combination Sum II (Medium)](https://leetcode.com/problems/combination-sum-ii/description/) @@ -1423,11 +1424,11 @@ private void doCombination(int[] candidates, int target, int start, List> ret; @@ -1450,13 +1451,13 @@ private void backtracking(int startIdx, int size, int[] nums) { for (int i = startIdx; i < nums.length; i++) { subsetList.add(nums[i]); - backtracking(i + 1, size, nums); // startIdx ΪһԪأʹ subset еԪض + backtracking(i + 1, size, nums); // startIdx 设为下一个元素,使 subset 中的元素都递增排序 subsetList.remove(subsetList.size() - 1); } } ``` -**ͬԪӼ** +**含有相同元素求子集** [Leetcode : 90. Subsets II (Medium)](https://leetcode.com/problems/subsets-ii/description/) @@ -1507,7 +1508,7 @@ private void backtracking(int startIdx, int size, int[] nums) { } ``` -**ַָʹÿֶǻ** +**分割字符串使得每部分都是回文数** [Leetcode : 131. Palindrome Partitioning (Medium)](https://leetcode.com/problems/palindrome-partitioning/description/) @@ -1542,11 +1543,11 @@ private boolean isPalindrome(String s, int begin, int end) { } ``` -**** +**数独** [Leetcode : 37. Sudoku Solver (Hard)](https://leetcode.com/problems/sudoku-solver/description/) -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/1ca52246-c443-48ae-b1f8-1cafc09ec75c.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//1ca52246-c443-48ae-b1f8-1cafc09ec75c.png) ```java private boolean[][] rowsUsed = new boolean[9][10]; @@ -1598,9 +1599,9 @@ private int cubeNum(int i, int j) { } ``` -## +## 分治 -**ʽ** +**给表达式加括号** [Leetcode : 241. Different Ways to Add Parentheses (Medium)](https://leetcode.com/problems/different-ways-to-add-parentheses/description/) @@ -1638,17 +1639,17 @@ public List diffWaysToCompute(String input) { } ``` -## ̬滮 +## 动态规划 -ݹͶ̬滮ǽԭɶȻ⣬֮ʵǣ̬滮Ľ⡣ +递归和动态规划都是将原问题拆成多个子问题然后求解,他们之间最本质的区别是,动态规划保存了子问题的解。 -### ָ +### 分割整数 -**ָ˻** +**分割整数的最大乘积** [Leetcode : 343. Integer Break (Medim)](https://leetcode.com/problems/integer-break/description/) -ĿFor example, given n = 2, return 1 (2 = 1 + 1); given n = 10, return 36 (10 = 3 + 3 + 4). +题目描述:For example, given n = 2, return 1 (2 = 1 + 1); given n = 10, return 36 (10 = 3 + 3 + 4). ```java public int integerBreak(int n) { @@ -1663,15 +1664,15 @@ public int integerBreak(int n) { } ``` -**ƽָ** +**按平方数来分割整数** [Leetcode : 279. Perfect Squares(Medium)](https://leetcode.com/problems/perfect-squares/description/) -ĿFor example, given n = 12, return 3 because 12 = 4 + 4 + 4; given n = 13, return 2 because 13 = 4 + 9. +题目描述:For example, given n = 12, return 3 because 12 = 4 + 4 + 4; given n = 13, return 2 because 13 = 4 + 9. ```java public int numSquares(int n) { - List squares = new ArrayList<>(); // 洢С n ƽ + List squares = new ArrayList<>(); // 存储小于 n 的平方数 int diff = 3; while(square <= n) { squares.add(square); @@ -1691,11 +1692,11 @@ public int numSquares(int n) { } ``` -**ָĸַ** +**分割整数构成字母字符串** [Leetcode : 91. Decode Ways (Medium)](https://leetcode.com/problems/decode-ways/description/) -ĿGiven encoded message "12", it could be decoded as "AB" (1 2) or "L" (12). +题目描述:Given encoded message "12", it could be decoded as "AB" (1 2) or "L" (12). ```java public int numDecodings(String s) { @@ -1715,13 +1716,13 @@ public int numDecodings(String s) { } ``` -### · +### 矩阵路径 -**·** +**矩阵的总路径数** [Leetcode : 62. Unique Paths (Medium)](https://leetcode.com/problems/unique-paths/description/) -ĿͳƴӾϽǵ½ǵ·ÿֻƶ +题目描述:统计从矩阵左上角到右下角的路径总数,每次只能向左和向下移动。 ```java public int uniquePaths(int m, int n) { @@ -1736,11 +1737,11 @@ public int uniquePaths(int m, int n) { } ``` -**С·** +**矩阵的最小路径和** [Leetcode : 64. Minimum Path Sum (Medium)](https://leetcode.com/problems/minimum-path-sum/description/) -ĿӾϽǵ½ǵС·ͣÿֻƶ +题目描述:求从矩阵的左上角到右下角的最小路径和,每次只能向左和向下移动。 ```java public int minPathSum(int[][] grid) { @@ -1758,27 +1759,27 @@ public int minPathSum(int[][] grid) { } ``` -### 쳲 +### 斐波那契数列 -**¥** +**爬楼梯** [Leetcode : 70. Climbing Stairs (Easy)](https://leetcode.com/problems/climbing-stairs/description/) -Ŀ N ¥ݣÿοһ׻ףж¥ݵķ +题目描述:有 N 阶楼梯,每次可以上一阶或者两阶,求有多少种上楼梯的方法。 -һ dp 洢¥ݵķΪ˷ۣ± 1 ʼdp[i] ʾߵ i ¥ݵķĿ i ¥ݿԴӵ i-1 i-2 ¥һߵ i ¥ݵķΪߵ i-1 ͵ i-2 ¥ݵķ֮͡ +定义一个数组 dp 存储上楼梯的方法数(为了方便讨论,数组下标从 1 开始),dp[i] 表示走到第 i 个楼梯的方法数目。第 i 个楼梯可以从第 i-1 和 i-2 个楼梯再走一步到达,走到第 i 个楼梯的方法数为走到第 i-1 和第 i-2 个楼梯的方法数之和。 -![](http://latex.codecogs.com/gif.latex?\\\\dp[i]=dp[i-1]+dp[i-2]) +
-dp[N] Ϊ +dp[N] 即为所求。 -ǵ dp[i] ֻ dp[i - 1] dp[i - 2] йأ˿ֻ洢 dp[i - 1] dp[i - 2] ɣʹԭ O(n) ռ临ӶŻΪ O(1) Ӷȡ +考虑到 dp[i] 只与 dp[i - 1] 和 dp[i - 2] 有关,因此可以只用两个变量来存储 dp[i - 1] 和 dp[i - 2] 即可,使得原来的 O(n) 空间复杂度优化为 O(1) 复杂度。 ```java public int climbStairs(int n) { if(n == 1) return 1; if(n == 2) return 2; - // ǰһ¥ݡһ¥ + // 前一个楼梯、后一个楼梯 int pre1 = 2, pre2 = 1; for(int i = 2; i < n; i++){ int cur = pre1 + pre2; @@ -1789,27 +1790,27 @@ public int climbStairs(int n) { } ``` -**ĸţ** +**母牛生产** -[Աָ-P181](#) +[程序员代码面试指南-P181](#) -Ŀũгĸţÿ궼 1 ͷСĸţԶһ 1 ֻСĸţӵڶ꿪ʼĸţʼСĸţÿֻСĸţ 3 ֿ֮Сĸţ N N ţ +题目描述:假设农场中成熟的母牛每年都会生 1 头小母牛,并且永远不会死。第一年有 1 只小母牛,从第二年开始,母牛开始生小母牛。每只小母牛 3 年之后成熟又可以生小母牛。给定整数 N,求 N 年后牛的数量。 - i ţΪ +第 i 年成熟的牛的数量为: -![](http://latex.codecogs.com/gif.latex?\\\\dp[i]=dp[i-1]+dp[i-3]) +
-**ǿ** +**强盗抢劫** [Leetcode : 198. House Robber (Easy)](https://leetcode.com/problems/house-robber/description/) -ĿһסDzڽס +题目描述:抢劫一排住户,但是不能抢邻近的住户,求最大抢劫量。 - dp 洢 dp[i] ʾ i סʱڲڽס˵ i סôֻ i - 2 i - 3 ס +定义 dp 数组用来存储最大的抢劫量,其中 dp[i] 表示抢到第 i 个住户时的最大抢劫量。由于不能抢劫邻近住户,因此如果抢劫了第 i 个住户那么只能抢劫 i - 2 和 i - 3 的住户,所以 -![](http://latex.codecogs.com/gif.latex?\\\\dp[i]=max(dp[i-2],dp[i-3])+nums[i]) +
-O(n) ռ临Ӷʵַ +O(n) 空间复杂度实现方法: ```java public int rob(int[] nums) { @@ -1828,7 +1829,7 @@ public int rob(int[] nums) { } ``` -O(1) ռ临Ӷʵַ +O(1) 空间复杂度实现方法: ```java public int rob(int[] nums) { @@ -1847,7 +1848,7 @@ public int rob(int[] nums) { } ``` -**ǿڻν** +**强盗在环形街区抢劫** [Leetcode : 213. House Robber II (Medium)](https://leetcode.com/problems/house-robber-ii/description/) @@ -1875,39 +1876,39 @@ private int rob(int[] nums, int s, int e) { ``` -**ż** +**信件错排** -Ŀ N ŷ⣬DZңװŵķʽ +题目描述:有 N 个 信 和 信封,它们被打乱,求错误装信的方式数量。 -һ dp 洢ʽdp[i] ʾǰ i źŷĴʽ i װ j ŷ棬 j װ k ŷ档 i k Ƿȣ +定义一个数组 dp 存储错误方式数量,dp[i] 表示前 i 个信和信封的错误方式数量。假设第 i 个信装到第 j 个信封里面,而第 j 个信装到第 k 个信封里面。根据 i 和 k 是否相等,有两种情况: - i==k i k źǵźŷȷλã i-2 dp[i-2] ִװŵķʽ j i-1 ȡֵ˹ (i-1)\*dp[i-2] ִװŷʽ +① i==k,交换 i 和 k 的信后,它们的信和信封在正确的位置,但是其余 i-2 封信有 dp[i-2] 种错误装信的方式。由于 j 有 i-1 种取值,因此共有 (i-1)\*dp[i-2] 种错误装信方式。 - i != k i j ź󣬵 i źŷȷλã i-1 dp[i-1] ִװŷʽ j i-1 ȡֵ˹ (n-1)\*dp[i-1] ִװŷʽ +② i != k,交换 i 和 j 的信后,第 i 个信和信封在正确的位置,其余 i-1 封信有 dp[i-1] 种错误装信方式。由于 j 有 i-1 种取值,因此共有 (n-1)\*dp[i-1] 种错误装信方式。 -װʽΪ +综上所述,错误装信数量方式数量为: -![](http://latex.codecogs.com/gif.latex?\\\\dp[i]=(i-1)*dp[i-2]+(i-1)*dp[i-1]) +
-dp[N] Ϊ +dp[N] 即为所求。 -¥һdp[i] ֻ dp[i-1] dp[i-2] йأҲֻ洢 dp[i-1] dp[i-2] +和上楼梯问题一样,dp[i] 只与 dp[i-1] 和 dp[i-2] 有关,因此也可以只用两个变量来存储 dp[i-1] 和 dp[i-2]。 -###  +### 最长递增子序列 -֪һ {S1, S2,...,Sn} ȡµ {Si1, Si2,..., Sim} i1i2 ... im ֵиȻԭеȺ˳򣬳Ϊԭеһ**** +已知一个序列 {S1, S2,...,Sn} ,取出若干数组成新的序列 {Si1, Si2,..., Sim},其中 i1、i2 ... im 保持递增,即新序列中各个数仍然保持原数列中的先后顺序,称新序列为原序列的一个**子序列**。 -У± ix > iy ʱSix > SiyΪԭеһ**** +如果在子序列中,当下标 ix > iy 时,Six > Siy,称子序列为原序列的一个**递增子序列**。 -һ dp 洢еijȣdp[n] ʾ Sn βегȡһ {Si1, Si2,...,Sim} im < n Sim < Sn ʱ {Si1, Si2,..., Sim, Sn} ΪһУеij 1ĵУǸоҪҵģڳĵϼ Sn ͹ Sn ΪβС dp[n] = max{ dp[i]+1 | Si < Sn && i < n} +定义一个数组 dp 存储最长递增子序列的长度,dp[n] 表示以 Sn 结尾的序列的最长递增子序列长度。对于一个递增子序列 {Si1, Si2,...,Sim},如果 im < n 并且 Sim < Sn ,此时 {Si1, Si2,..., Sim, Sn} 为一个递增子序列,递增子序列的长度增加 1。满足上述条件的递增子序列中,长度最长的那个递增子序列就是要找的,在长度最长的递增子序列上加上 Sn 就构成了以 Sn 为结尾的最长递增子序列。因此 dp[n] = max{ dp[i]+1 | Si < Sn && i < n} 。 -Ϊ dp[n] ʱ޷ҵһĵУʱ {Sn} ͹˵УҪǰⷽ޸ģ dp[n] СΪ 1 +因为在求 dp[n] 时可能无法找到一个满足条件的递增子序列,此时 {Sn} 就构成了递增子序列,因此需要对前面的求解方程做修改,令 dp[n] 最小为 1,即: -![](http://latex.codecogs.com/gif.latex?\\\\dp[n]=max\{1,dp[i]+1|S_i -һΪ N Увһ SN Ϊβ dp[N] ееijȣҪ dp ҳֵҪĽ max{ dp[i] | 1 <= i <= N} Ϊ +对于一个长度为 N 的序列,最长子序列并不一定会以 SN 为结尾,因此 dp[N] 不是序列的最长递增子序列的长度,需要遍历 dp 数组找出最大值才是所要的结果,即 max{ dp[i] | 1 <= i <= N} 即为所求。 -**** +**最长递增子序列** [Leetcode : 300. Longest Increasing Subsequence (Medium)](https://leetcode.com/problems/longest-increasing-subsequence/description/) @@ -1930,7 +1931,7 @@ public int lengthOfLIS(int[] nums) { } ``` -Ͻⷨʱ临ӶΪ O(n2) ʹöֲʹʱ临ӶȽΪ O(nlogn)һ tails 飬 tails[i] 洢Ϊ i + 1 еһԪأ [4,5,6,3] +以上解法的时间复杂度为 O(n2) ,可以使用二分查找使得时间复杂度降低为 O(nlogn)。定义一个 tails 数组,其中 tails[i] 存储长度为 i + 1 的最长递增子序列的最后一个元素,例如对于数组 [4,5,6,3],有 ```html len = 1 : [4], [5], [6], [3] => tails[0] = 3 @@ -1938,9 +1939,9 @@ len = 2 : [4, 5], [5, 6] => tails[1] = 5 len = 3 : [4, 5, 6] => tails[2] = 6 ``` -һԪ x tails еֵôӵ tails 棻 tails[i-1] < x <= tails[i]ô tails[i] = x +对于一个元素 x,如果它大于 tails 数组所有的值,那么把它添加到 tails 后面;如果 tails[i-1] < x <= tails[i],那么更新 tails[i] = x 。 -Կ tails 鱣ڲ Si λ tails λʱͿʹöֲҡ +可以看出 tails 数组保持有序,因此在查找 Si 位于 tails 数组的位置时就可以使用二分查找。 ```java public int lengthOfLIS(int[] nums) { @@ -1966,13 +1967,13 @@ private int binarySearch(int[] nums, int sIdx, int eIdx, int key){ } ``` -**ڶ** +**最长摆动子序列** [Leetcode : 376. Wiggle Subsequence (Medium)](https://leetcode.com/problems/wiggle-subsequence/description/) -Ҫʹ O(n) ʱ临Ӷ⡣ +要求:使用 O(n) 时间复杂度求解。 -ʹ״̬ up down +使用两个状态 up 和 down。 ```java public int wiggleMaxLength(int[] nums) { @@ -1987,35 +1988,35 @@ public int wiggleMaxLength(int[] nums) { } ``` -### ϵ +### 最长公共子系列 - S1 S2ҳĹС +对于两个子序列 S1 和 S2,找出它们最长的公共子序列。 -һά dp 洢еijȣ dp[i][j] ʾ S1 ǰ i ַ S2 ǰ j ַеijȡ S1i S2j ֵǷȣΪ +定义一个二维数组 dp 用来存储最长公共子序列的长度,其中 dp[i][j] 表示 S1 的前 i 个字符与 S2 的前 j 个字符最长公共子序列的长度。考虑 S1i 与 S2j 值是否相等,分为两种情况: - S1i==S2j ʱô S1 ǰ i-1 ַ S2 ǰ j-1 ַеĻټ S1i ֵгȼ 1 dp[i][j] = dp[i-1][j-1] + 1 +① 当 S1i==S2j 时,那么就能在 S1 的前 i-1 个字符与 S2 的前 j-1 个字符最长公共子序列的基础上再加上 S1i 这个值,最长公共子序列长度加 1 ,即 dp[i][j] = dp[i-1][j-1] + 1。 - S1i != S2j ʱʱΪ S1 ǰ i-1 ַ S2 ǰ j ַУ S1 ǰ i ַ S2 ǰ j-1 ַУǵߣ dp[i][j] = max{ dp[i-1][j], dp[i][j-1] } +② 当 S1i != S2j 时,此时最长公共子序列为 S1 的前 i-1 个字符和 S2 的前 j 个字符最长公共子序列,与 S1 的前 i 个字符和 S2 的前 j-1 个字符最长公共子序列,它们的最大者,即 dp[i][j] = max{ dp[i-1][j], dp[i][j-1] }。 -ϣϵе״̬תƷΪ +综上,最长公共子系列的状态转移方程为: -![](http://latex.codecogs.com/gif.latex?\\\\ +$$ dp[i][j]=\left\{ \begin{array}{rcl} -dp[i-1][j-1]&&{S1_i==S2_j}\\ -max(dp[i-1][j],dp[i][j-1])&&{S1_i<>S2_j} -\end{array}\right. -) +dp[i-1][j-1] && { S1_i==S2_j }\\ +max(dp[i-1][j], dp[i][j-1]) &&{ S1_i <> S2_j } +\end{array} \right. +$$ -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/1dc481cc-99f6-4fa8-8f68-fbd563995bf5.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//1dc481cc-99f6-4fa8-8f68-fbd563995bf5.png) -ڳΪ N S1 Ϊ M S2dp[N][M] S1 S2 гȡ +对于长度为 N 的序列 S1 和 长度为 M 的序列 S2,dp[N][M] 就是序列 S1 和序列 S2 的最长公共子序列长度。 -ȣ²ͬ㣺 +与最长递增子序列相比,最长公共子序列有以下不同点: - ԵУǵС - Уdp[i] ʾ Si Ϊβгȣб Si Уdp[i][j] ʾ S1 ǰ i ַ S2 ǰ j ַгȣһ S1i S2j - 2 սʱ dp[N][M] ս⣬ dp[N] ս⣬Ϊ SN ΪβвһУҪһ dp ҵߡ +① 针对的是两个序列,求它们的最长公共子序列。 +② 在最长递增子序列中,dp[i] 表示以 Si 为结尾的最长递增子序列长度,子序列必须包含 Si ;在最长公共子序列中,dp[i][j] 表示 S1 中前 i 个字符与 S2 中前 j 个字符的最长公共子序列长度,不一定包含 S1i 和 S2j 。 +③ 由于 2 ,在求最终解时,最长公共子序列中 dp[N][M] 就是最终解,而最长递增子序列中 dp[N] 不是最终解,因为以 SN 为结尾的最长递增子序列不一定是整个序列最长递增子序列,需要遍历一遍 dp 数组找到最大者。 ```java public int lengthOfLCS(int[] nums1, int[] nums2) { @@ -2031,20 +2032,20 @@ public int lengthOfLCS(int[] nums1, int[] nums2) { } ``` -### 0-1 +### 0-1 背包 -һΪ N ıҪװƷļֵЩƷԣ w ͼֵ v +有一个容量为 N 的背包,要用这个背包装下物品的价值最大,这些物品有两个属性:体积 w 和价值 v。 -һά dp 洢ֵ dp[i][j] ʾ j £ǰ i Ʒܴﵽֵ i ƷΪ wֵΪ vݵ i ƷǷӵУԷۣ +定义一个二维数组 dp 存储最大价值,其中 dp[i][j] 表示体积不超过 j 的情况下,前 i 件物品能达到的最大价值。设第 i 件物品体积为 w,价值为 v,根据第 i 件物品是否添加到背包中,可以分两种情况讨论: - i Ʒûӵ j ǰ i Ʒֵ j ǰ i-1 Ʒֵdp[i][j] = dp[i-1][j] - i ƷӵУdp[i][j] = dp[i-1][j-w] + v +① 第 i 件物品没添加到背包,总体积不超过 j 的前 i 件物品的最大价值就是总体积不超过 j 的前 i-1 件物品的最大价值,dp[i][j] = dp[i-1][j]。 +② 第 i 件物品添加到背包中,dp[i][j] = dp[i-1][j-w] + v。 - i ƷҲԲӣȡֵ +第 i 件物品可添加也可以不添加,取决于哪种情况下最大价值更大。 -ϣ0-1 ״̬תƷΪ +综上,0-1 背包的状态转移方程为: -![](http://latex.codecogs.com/gif.latex?\\\\dp[i][j]=max(dp[i-1][j],dp[i-1][j-w]+v)) +
```java public int knapsack(int W, int N, int[] weights, int[] values) { @@ -2064,17 +2065,17 @@ public int knapsack(int W, int N, int[] weights, int[] values) { } ``` -**ռŻ** +**空间优化** -ڳʵʱԶ 0-1 Ż۲״̬תƷ֪̿ǰ i Ʒ״̬ǰ i-1 Ʒ״̬йأ˿Խ dp Ϊһά飬 dp[j] ȿԱʾ dp[i-1][j] ҲԱʾ dp[i][j]ʱ +在程序实现时可以对 0-1 背包做优化。观察状态转移方程可以知道,前 i 件物品的状态仅由前 i-1 件物品的状态有关,因此可以将 dp 定义为一维数组,其中 dp[j] 既可以表示 dp[i-1][j] 也可以表示 dp[i][j]。此时, -![](http://latex.codecogs.com/gif.latex?\\\\dp[j]=max(dp[j],dp[j-w]+v)) +
-Ϊ dp[j-w] ʾ dp[i-1][j-w]˲ dp[i][j-w] ֹ dp[i-1][j-w] ǡҲ˵Ҫȼ dp[i][j] ټ dp[i][j-w]ڳʵʱҪѭ⡣ +因为 dp[j-w] 表示 dp[i-1][j-w],因此不能先求 dp[i][j-w] 防止将 dp[i-1][j-w] 覆盖。也就是说要先计算 dp[i][j] 再计算 dp[i][j-w],在程序实现时需要按倒序来循环求解。 -**޷ʹ̰㷨Ľ** +**无法使用贪心算法的解释** -0-1 ޷ʹ̰㷨⣬Ҳ˵ܰԼ۱ߵƷﵽţΪַʽɱռ˷ѣӶ޷ﵽšƷһΪ 5 ıƷ 0 Ʒ 1ôֻܴŵļֵΪ 16˷˴СΪ 2 Ŀռ䡣ŵķʽǴƷ 1 Ʒ 2ֵΪ 22. +0-1 背包问题无法使用贪心算法来求解,也就是说不能按照先添加性价比最高的物品来达到最优,这是因为这种方式可能造成背包空间的浪费,从而无法达到最优。考虑下面的物品和一个容量为 5 的背包,如果先添加物品 0 再添加物品 1,那么只能存放的价值为 16,浪费了大小为 2 的空间。最优的方式是存放物品 1 和物品 2,价值为 22. | id | w | v | v/w | | --- | --- | --- | --- | @@ -2082,23 +2083,23 @@ public int knapsack(int W, int N, int[] weights, int[] values) { | 1 | 2 | 10 | 5 | | 2 | 3 | 12 | 4 | -**** +**变种** -ȫƷ޸תΪ 0-1 ÿƷͼֵΪ 1/2/4... ǶһƷȻһƷֻһΡ +完全背包:物品可以无限个,可以转换为 0-1 背包,令每种物品的体积和价值变为 1/2/4... 倍数,把它们都当成一个新物品,然后一种物品只能添加一次。 -رƷƣͬתΪ 0-1 +多重背包:物品数量有限制,同样可以转换为 0-1 背包。 -άñƷͬʱơ +多维费用背包:物品不仅有重量,还有体积,同时考虑这两种限制。 -Ʒ֮໥Լ +其它:物品之间相互约束或者依赖。 -**Ϊȵ** +**划分数组为和相等的两部分** [Leetcode : 416. Partition Equal Subset Sum (Medium)](https://leetcode.com/problems/partition-equal-subset-sum/description/) -ԿһСΪ sum/2 0-1 ⣬Ҳвͬĵطûмֵԣұ뱻 +可以看成一个背包大小为 sum/2 的 0-1 背包问题,但是也有不同的地方,这里没有价值属性,并且背包必须被填满。 -ʵʹ˿ռŻ +以下实现使用了空间优化。 ```java public boolean canPartition(int[] nums) { @@ -2125,7 +2126,7 @@ public boolean canPartition(int[] nums) { } ``` -**ַбָ** +**字符串按单词列表分割** [Leetcode : 139. Word Break (Medium)](https://leetcode.com/problems/word-break/description/) @@ -2152,7 +2153,7 @@ public boolean wordBreak(String s, List wordDict) { } ``` -**ıһʹǵĺΪһ** +**改变一组数的正负号使得它们的和为一给定数** [Leetcode : 494. Target Sum (Medium)](https://leetcode.com/problems/target-sum/description/) @@ -2170,7 +2171,7 @@ Explanation: There are 5 ways to assign symbols to make the sum of nums be target 3. ``` -תΪ subset sum ⣬Ӷʹ 0-1 ķ⡣Խ֣P N P ʹţN ʹøţƵ +该问题可以转换为 subset sum 问题,从而使用 0-1 背包的方法来求解。可以将这组数看成两部分,P 和 N,其中 P 使用正号,N 使用负号,有以下推导: ```html sum(P) - sum(N) = target @@ -2178,7 +2179,7 @@ sum(P) + sum(N) + sum(P) - sum(N) = target + sum(P) + sum(N) 2 * sum(P) = target + sum(nums) ``` -ֻҪҵһӼǶȡţҺ͵ (target + sum(nums))/2֤ڽ⡣ +因此只要找到一个子集,令它们都取正号,并且和等于 (target + sum(nums))/2,就证明存在解。 ```java public int findTargetSumWays(int[] nums, int S) { @@ -2206,7 +2207,7 @@ private int subsetSum(int[] nums, int targetSum) { } ``` -**01ַַ** +**01字符构成最多的字符串** [Leetcode : 474. Ones and Zeroes (Medium)](https://leetcode.com/problems/ones-and-zeroes/description/) @@ -2214,10 +2215,10 @@ private int subsetSum(int[] nums, int targetSum) { Input: Array = {"10", "0001", "111001", "1", "0"}, m = 5, n = 3 Output: 4 -Explanation: This are totally 4 strings can be formed by the using of 5 0s and 3 1s, which are 10,0001,1,0 +Explanation: This are totally 4 strings can be formed by the using of 5 0s and 3 1s, which are “10,”0001”,”1”,”0” ``` -һάõ 0-1 ⣬С0 1 +这是一个多维费用的 0-1 背包问题,有两个背包大小,0 的数量和 1 的数量。 ```java public int findMaxForm(String[] strs, int m, int n) { @@ -2243,13 +2244,13 @@ public int findMaxForm(String[] strs, int m, int n) { } ``` -**Ǯ** +**找零钱** [Leetcode : 322. Coin Change (Medium)](https://leetcode.com/problems/coin-change/description/) -ĿһЩӲңҪЩӲɸǮʹӲ١Ӳҿظʹá +题目描述:给一些面额的硬币,要求用这些硬币来组成给定面额的钱数,并且使得硬币数量最少。硬币可以重复使用。 -һȫ⣬ȫ 0-1ʵΨһIJͬǣڶѭǴ 0 ʼģǴβʼ +这是一个完全背包问题,完全背包问题和 0-1背包问题在实现上唯一的不同是,第二层循环是从 0 开始的,而不是从尾部开始。 ```java public int coinChange(int[] coins, int amount) { @@ -2267,7 +2268,7 @@ public int coinChange(int[] coins, int amount) { } ``` -**ܺ** +**组合总和** [Leetcode : 377. Combination Sum IV (Medium)](https://leetcode.com/problems/combination-sum-iv/description/) @@ -2304,7 +2305,7 @@ public int combinationSum4(int[] nums, int target) { } ``` -**ֻܽεĹƱ** +**只能进行两次的股票交易** [Leetcode : 123. Best Time to Buy and Sell Stock III (Hard)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iii/description/) @@ -2322,7 +2323,7 @@ public int maxProfit(int[] prices) { } ``` -**ֻܽ k εĹƱ** +**只能进行 k 次的股票交易** [Leetcode : 188. Best Time to Buy and Sell Stock IV (Hard)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iv/description/) @@ -2353,13 +2354,13 @@ public int maxProfit(int k, int[] prices) { } ``` -### +### 数组区间 -**** +**数组区间和** [Leetcode : 303. Range Sum Query - Immutable (Easy)](https://leetcode.com/problems/range-sum-query-immutable/description/) - i \~ j ĺͣתΪ sum[j] - sum[i-1] sum[i] Ϊ 0 \~ j ĺ͡ +求区间 i \~ j 的和,可以转换为 sum[j] - sum[i-1],其中 sum[i] 为 0 \~ j 的和。 ```java class NumArray { @@ -2378,11 +2379,11 @@ class NumArray { } ``` -**ĺ** +**子数组最大的和** [Leetcode : 53. Maximum Subarray (Easy)](https://leetcode.com/problems/maximum-subarray/description/) - sum[i] Ϊ num[i] Ϊβĺͣ sum[i-1] õ sum[i] ֵ sum[i-1] С 0ô num[i] Ϊβ鲻ܰǰݣΪǰIJ֣ôһ num[i] С +令 sum[i] 为以 num[i] 为结尾的子数组最大的和,可以由 sum[i-1] 得到 sum[i] 的值,如果 sum[i-1] 小于 0,那么以 num[i] 为结尾的子数组不能包含前面的内容,因为加上前面的部分,那么和一定会比 num[i] 还小。 ```java public int maxSubArray(int[] nums) { @@ -2398,7 +2399,7 @@ public int maxSubArray(int[] nums) { } ``` -**еȲĸ** +**数组中等差递增子区间的个数** [Leetcode : 413. Arithmetic Slices (Medium)](https://leetcode.com/problems/arithmetic-slices/description/) @@ -2408,7 +2409,7 @@ A = [1, 2, 3, 4] return: 3, for 3 arithmetic slices in A: [1, 2, 3], [2, 3, 4] and [1, 2, 3, 4] itself. ``` - (1,2,3,4)ɵķʽ (1,2,3,4,5)ɵķʽ (1,2,3,4) ⻹һ֣ (1,2,3,4,5) dp[i] = dp[i - 1] + 1 +对于 (1,2,3,4),它有三种组成递增子区间的方式,而对于 (1,2,3,4,5),它组成递增子区间的方式除了 (1,2,3,4) 的三种外还多了一种,即 (1,2,3,4,5),因此 dp[i] = dp[i - 1] + 1。 ```java public int numberOfArithmeticSlices(int[] A) { @@ -2427,13 +2428,13 @@ public int numberOfArithmeticSlices(int[] A) { } ``` -### ַ༭ +### 字符串编辑 -**ɾַַʹ** +**删除两个字符串的字符使它们相等** [Leetcode : 583. Delete Operation for Two Strings (Medium)](https://leetcode.com/problems/delete-operation-for-two-strings/description/) -תΪַ⡣ +可以转换为求两个字符串的最长公共子序列问题。 ```java public int minDistance(String word1, String word2) { @@ -2451,20 +2452,20 @@ public int minDistance(String word1, String word2) { ``` -**޸һַΪһַ** // TODO +**修改一个字符串称为另一个字符串** // TODO [Leetcode : 72. Edit Distance (Hard)](https://leetcode.com/problems/edit-distance/description/) -### +### 其它问题 -**ҪȴڵĹƱ** +**需要冷却期的股票交易** [Leetcode : 309. Best Time to Buy and Sell Stock with Cooldown(Medium)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/description/) -Ŀ֮Ҫһȴʱ䡣 +题目描述:交易之后需要有一天的冷却时间。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/ac9b31ec-cef1-4880-a875-fc4571ca10e1.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//ac9b31ec-cef1-4880-a875-fc4571ca10e1.png) ```html s0[i] = max(s0[i - 1], s2[i - 1]); // Stay at s0, or rest from s2 @@ -2493,11 +2494,11 @@ public int maxProfit(int[] prices) { ``` -**ͳƴ 0 \~ n ÿĶƱʾ 1 ĸ** +**统计从 0 \~ n 每个数的二进制表示中 1 的个数** [Leetcode : 338. Counting Bits (Medium)](https://leetcode.com/problems/counting-bits/description/) - 6(110)Կ 2(10) ǰһ 1 dp[i] = dp[i&(i-1)] + 1; +对于数字 6(110),它可以看成是数字 2(10) 前面加上一个 1 ,因此 dp[i] = dp[i&(i-1)] + 1; ```java public int[] countBits(int num) { @@ -2509,11 +2510,11 @@ public int maxProfit(int[] prices) { } ``` -**һܹɵ** +**一组整数对能够构成的最长链** [Leetcode : 646. Maximum Length of Pair Chain (Medium)](https://leetcode.com/problems/maximum-length-of-pair-chain/description/) - (a, b) (c, d) b < cǿԹһ +对于 (a, b) 和 (c, d) ,如果 b < c,则它们可以构成一条链。 ```java public int findLongestChain(int[][] pairs) { @@ -2540,13 +2541,13 @@ public int findLongestChain(int[][] pairs) { } ``` -**۳Ʊ** +**买入和售出股票最大的收益** [Leetcode : 121. Best Time to Buy and Sell Stock (Easy)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock/description/) -ֻһνס +只进行一次交易。 -ֻҪ¼ǰС۸񣬽С۸Ϊ۸Ȼ󽫵ǰļ۸Ϊ۳۸񣬲鿴۸Ƿǵǰ۸ +只要记录前面的最小价格,将这个最小价格作为买入价格,然后将当前的价格作为售出价格,查看这个价格是否是当前的最大价格。 ```java public int maxProfit(int[] prices) { @@ -2562,7 +2563,7 @@ public int maxProfit(int[] prices) { } ``` -**ճַ** +**复制粘贴字符** [Leetcode : 650. 2 Keys Keyboard (Medium)](https://leetcode.com/problems/2-keys-keyboard/description/) @@ -2592,30 +2593,30 @@ public int minSteps(int n) { } ``` -## ѧ +## 数学 -### +### 素数 -**ֽ** +**素数分解** -ÿһԷֽij˻ 84 = 22 \* 31 \* 50 \* 71 \* 110 \* 130 \* 170 \* +每一个数都可以分解成素数的乘积,例如 84 = 22 \* 31 \* 50 \* 71 \* 110 \* 130 \* 170 \* … -**** +**整除** - x = 2m0 \* 3m1 \* 5m2 \* 7m3 \* 11m4 \* - y = 2n0 \* 3n1 \* 5n2 \* 7n3 \* 11n4 \* +令 x = 2m0 \* 3m1 \* 5m2 \* 7m3 \* 11m4 \* … +令 y = 2n0 \* 3n1 \* 5n2 \* 7n3 \* 11n4 \* … - x yy mod x == 0 imi <= ni +如果 x 整除 y(y mod x == 0),则对于所有 i,mi <= ni。 -x y **Լ** Ϊgcd(x,y) = 2min(m0,n0) \* 3min(m1,n1) \* 5min(m2,n2) \* ... +x 和 y 的 **最大公约数** 为:gcd(x,y) = 2min(m0,n0) \* 3min(m1,n1) \* 5min(m2,n2) \* ... -x y **С** Ϊlcm(x,y) = 2max(m0,n0) \* 3max(m1,n1) \* 5max(m2,n2) \* ... +x 和 y 的 **最小公倍数** 为:lcm(x,y) = 2max(m0,n0) \* 3max(m1,n1) \* 5max(m2,n2) \* ... -**** +**生成素数序列** [Leetcode : 204. Count Primes (Easy)](https://leetcode.com/problems/count-primes/description/) -˹ɸÿҵһʱܱų +埃拉托斯特尼筛法在每次找到一个素数时,将能被素数整除的数排除掉。 ```java public int countPrimes(int n) { @@ -2624,7 +2625,7 @@ public int countPrimes(int n) { for(int i = 2; i < n; i++){ if(notPrimes[i]) continue; cnt++; - // i * i ʼΪ k < iô k * i ֮ǰѾȥ + // 从 i * i 开始,因为如果 k < i,那么 k * i 在之前就已经被去除过了 for(long j = (long) i * i; j < n; j += i){ notPrimes[(int) j] = true; } @@ -2633,7 +2634,7 @@ public int countPrimes(int n) { } ``` -### Լ +### 最大公约数 ```java int gcd(int a, int b) { @@ -2642,7 +2643,7 @@ int gcd(int a, int b) { } ``` -󹫱Ϊij˻Լ +最大公倍数为两数的乘积除以最大公约数。 ```java int lcm(int a, int b){ @@ -2650,22 +2651,22 @@ int lcm(int a, int b){ } ``` -Լ⣬ΪҪ a % b DZȽϺʱģʹ [ ֮2.7]() ķüλ滻 +对于最大公约数问题,因为需要计算 a % b ,而这个操作是比较耗时的,可以使用 [ 编程之美:2.7]() 的方法,利用减法和移位操作来替换它。 - a b Լ f(a, b)У +对于 a 和 b 的最大公约数 f(a, b),有: -1\. a b Ϊżf(a, b) = 2\*f(a/2, b/2); -2\. a ż b f(a, b) = f(a/2, b); -3\. b ż a f(a, b) = f(a, b/2); -4\. a b Ϊf(a, b) = f(a, a-b); +1\. 如果 a 和 b 均为偶数,f(a, b) = 2\*f(a/2, b/2); +2\. 如果 a 是偶数 b 是奇数,f(a, b) = f(a/2, b); +3\. 如果 b 是偶数 a 是奇数,f(a, b) = f(a, b/2); +4\. 如果 a 和 b 均为奇数,f(a, b) = f(a, a-b); - 2 ͳ 2 תΪλ +乘 2 和除 2 都可以转换为移位操作。 -### ת +### 进制转换 -Java static String toString(int num, int radix) ԽһװΪ redix Ʊʾַ +Java 中 static String toString(int num, int radix) 可以将一个整数装换为 redix 进制表示的字符串。 -**7 ** +**7 进制** [Leetcode : 504. Base 7 (Easy)](https://leetcode.com/problems/base-7/description/) @@ -2681,7 +2682,7 @@ public String convertToBase7(int num) { } ``` -**16 ** +**16 进制** [Leetcode : 405. Convert a Number to Hexadecimal (Easy)](https://leetcode.com/problems/convert-a-number-to-hexadecimal/description/) @@ -2698,15 +2699,15 @@ public String toHex(int num) { } ``` -### ׳ +### 阶乘 -**ͳƽ׳βжٸ 0** +**统计阶乘尾部有多少个 0** [Leetcode : 172. Factorial Trailing Zeroes (Easy)](https://leetcode.com/problems/factorial-trailing-zeroes/description/) -β 0 2 * 5 2 Զ 5 ֻҪͳжٸ 5 ɡ +尾部的 0 由 2 * 5 得来,2 的数量明显多于 5 的数量,因此只要统计有多少个 5 即可。 -һ N 5 ĸΪN/5 + N/52 + N/53 + ... N/5 ʾ N 5 ıһ 5N/52 ʾ N 52 ıٹһ 5 ... +对于一个数 N,它所包含 5 的个数为:N/5 + N/52 + N/53 + ...,其中 N/5 表示不大于 N 的数中 5 的倍数贡献一个 5,N/52 表示不大于 N 的数中 52 的倍数再贡献一个 5 ...。 ```java public int trailingZeroes(int n) { @@ -2714,11 +2715,11 @@ public int trailingZeroes(int n) { } ``` -ͳƵ N! ĶƱʾλ 1 λãֻҪͳжٸ 2 ɣĿ [ ֮2.2](#) жٸ 5 һ2 ĸΪ N/2 + N/22 + N/23 + ... +如果统计的是 N! 的二进制表示中最低位 1 的位置,只要统计有多少个 2 即可,该题目出自 [ 编程之美:2.2](#) 。和求解有多少个 5 一样,2 的个数为 N/2 + N/22 + N/23 + ... -### ַӷ +### 字符串加法减法 -**Ƽӷ** +**二进制加法** [Leetcode : 67. Add Binary (Easy)](https://leetcode.com/problems/add-binary/description/) @@ -2737,11 +2738,11 @@ public String addBinary(String a, String b) { } ``` -**ַӷ** +**字符串加法** [Leetcode : 415. Add Strings (Easy)](https://leetcode.com/problems/add-strings/description/) -ĿֵַΪǸ +题目描述:字符串的值为非负整数 ```java public String addStrings(String num1, String num2) { @@ -2757,23 +2758,23 @@ public String addStrings(String num1, String num2) { } ``` -### +### 相遇问题 -**ıԪʹеԪض** +**改变数组元素使所有的数组元素都相等** [Leetcode : 462. Minimum Moves to Equal Array Elements II (Medium)](https://leetcode.com/problems/minimum-moves-to-equal-array-elements-ii/description/) -ĿÿοԶһԪؼһ߼һСĸı +题目描述:每次可以对一个数组元素加一或者减一,求最小的改变次数。 -Ǹ͵⣬ƶСķʽԪضƶλ£ +这是个典型的相遇问题,移动距离最小的方式是所有元素都移动到中位数。理由如下: - 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 λá +设数组长度为 N,则可以找到 N/2 对 a 和 b 的组合,使它们都移动到 m 的位置。 -**ⷨ 1** +**解法 1** -ʱ临ӶȣO(NlgN) +先排序,时间复杂度:O(NlgN) ```java public int minMoves2(int[] nums) { @@ -2789,9 +2790,9 @@ public int minMoves2(int[] nums) { } ``` -**ⷨ 2** +**解法 2** -ʹÿҵλʱ临Ӷ O(N) +使用快速排序找到中位数,时间复杂度 O(N) ```java public int minMoves2(int[] nums) { @@ -2823,13 +2824,13 @@ private void swap(int[] nums, int i, int j) { } ``` -### ͶƱ +### 多数投票问题 -**гִ n / 2 Ԫ** +**数组中出现次数多于 n / 2 的元素** [Leetcode : 169. Majority Element (Easy)](https://leetcode.com/problems/majority-element/description/) -ȶмǸִһ n / 2 +先对数组排序,最中间那个数出现次数一定多于 n / 2 ```java public int majorityElement(int[] nums) { @@ -2838,7 +2839,7 @@ public int majorityElement(int[] nums) { } ``` - Boyer-Moore Majority Vote Algorithm ⣬ʹʱ临ӶΪ O(n)ô㷨ʹ cnt ͳһԪسֵĴԪغͳԪزʱ cnt--ǰ i Ԫأ cnt == 0 ˵ǰ i Ԫû majority majoritydzֵĴ i / 2 Ϊ i / 2 Ļ cnt һΪ 0 ʱʣµ n - i ԪУmajority Ŀ (n - i) / 2˼Ҿҳ majority +可以利用 Boyer-Moore Majority Vote Algorithm 来解决这个问题,使得时间复杂度为 O(n)。可以这么理解该算法:使用 cnt 来统计一个元素出现的次数,当遍历到的元素和统计元素不想等时,令 cnt--。如果前面查找了 i 个元素,且 cnt == 0 ,说明前 i 个元素没有 majority,或者有 majority,但是出现的次数少于 i / 2 ,因为如果多于 i / 2 的话 cnt 就一定不会为 0 。此时剩下的 n - i 个元素中,majority 的数目多于 (n - i) / 2,因此继续查找就能找出 majority。 ```java public int majorityElement(int[] nums) { @@ -2855,16 +2856,16 @@ public int majorityElement(int[] nums) { } ``` -### +### 其它 -**ƽ** +**平方数** [Leetcode : 367. Valid Perfect Square (Easy)](https://leetcode.com/problems/valid-perfect-square/description/) -ƽУ1,4,9,16,.. -3,5,7,... +平方序列:1,4,9,16,.. +间隔:3,5,7,... -ΪȲУʹԿԵõ 1 ʼƽС +间隔为等差数列,使用这个特性可以得到从 1 开始的平方序列。 ```java public boolean isPerfectSquare(int num) { @@ -2877,7 +2878,7 @@ public boolean isPerfectSquare(int num) { } ``` -**3 n η** +**3 的 n 次方** [Leetcode : 326. Power of Three (Easy)](https://leetcode.com/problems/power-of-three/description/) @@ -2887,7 +2888,7 @@ public boolean isPowerOfThree(int n) { } ``` -**ҳеij˻** +**找出数组中的乘积最大的三个数** [Leetcode : 628. Maximum Product of Three Numbers (Easy)](https://leetcode.com/problems/maximum-product-of-three-numbers/description/) @@ -2917,13 +2918,13 @@ public int maximumProduct(int[] nums) { } ``` -**˻** +**乘积数组** [Leetcode : 238. Product of Array Except Self (Medium)](https://leetcode.com/problems/product-of-array-except-self/description/) -Ŀһ飬һ飬ÿԪΪԭʼг˸λϵԪ֮Ԫصij˻ +题目描述:给定一个数组,创建一个新数组,新数组的每个元素为原始数组中除了该位置上的元素之外所有元素的乘积。 -ĿҪʱ临ӶΪ O(n)Ҳʹó +题目要求:时间复杂度为 O(n),并且不能使用除法。 ```java public int[] productExceptSelf(int[] nums) { @@ -2942,13 +2943,13 @@ public int[] productExceptSelf(int[] nums) { } ``` -# ݽṹ +# 数据结构相关 -## ջͶ +## 栈和队列 -**ջʵֶ** +**用栈实现队列** -һջʵ֣ +一个栈实现: ```java class MyQueue { @@ -2979,7 +2980,7 @@ class MyQueue { } ``` -ջʵ֣ +两个栈实现: ```java class MyQueue { @@ -3014,7 +3015,7 @@ class MyQueue { } ``` -**öʵջ** +**用队列实现栈** [Leetcode : 225. Implement Stack using Queues (Easy)](https://leetcode.com/problems/implement-stack-using-queues/description/) @@ -3029,7 +3030,7 @@ class MyStack { public void push(int x) { queue.add(x); - for(int i = 1; i < queue.size(); i++){ // ת + for(int i = 1; i < queue.size(); i++){ // 翻转 queue.add(queue.remove()); } } @@ -3048,11 +3049,11 @@ class MyStack { } ``` -**Сֵջ** +**最小值栈** [Leetcode : 155. Min Stack (Easy)](https://leetcode.com/problems/min-stack/description/) -ջʵ֣һ洢ݣһ洢Сֵ +用两个栈实现,一个存储数据,一个存储最小值。 ```java class MinStack { @@ -3095,9 +3096,9 @@ class MinStack { } ``` -ʵСֵ⣬Ƚʹջʵ֣ȻͽתΪСֵջ ֮3.7 +对于实现最小值队列问题,可以先将队列使用栈来实现,然后就将问题转换为最小值栈,这个问题出现在 编程之美:3.7。 -**ջʵƥ** +**用栈实现括号匹配** [Leetcode : 20. Valid Parentheses (Easy)](https://leetcode.com/problems/valid-parentheses/description/) @@ -3127,7 +3128,7 @@ public boolean isValid(String s) { } ``` -**бȵǰԪشһԪصľ** +**数组中比当前元素大的下一个数组元素的距离** ```html Input: [73, 74, 75, 71, 69, 72, 76, 73] @@ -3136,7 +3137,7 @@ Output: [1, 1, 4, 2, 1, 1, 0, 0] [Leetcode : 739. Daily Temperatures (Medium)](https://leetcode.com/problems/daily-temperatures/description/) -ʹջ洢δԪءԱ֤ջԪصһijԪشԪؽջУǸԪѾҵԪأ˻ջ +使用栈来存储还未计算的元素。可以保证从栈顶向下元素递增,否则上面有一个比下面某个元素大的元素进入栈中,下面那个元素已经找到比它大的元素,因此会出栈。 ```java public int[] dailyTemperatures(int[] temperatures) { @@ -3154,7 +3155,7 @@ public int[] dailyTemperatures(int[] temperatures) { } ``` -**һȵǰ** +**数组中下一个比当前数大的数** [Leetcode : 496. Next Greater Element I (Easy)](https://leetcode.com/problems/next-greater-element-i/description/) @@ -3163,7 +3164,7 @@ Input: nums1 = [4,1,2], nums2 = [1,3,4,2]. Output: [-1,3,-1] ``` -ڱʱ Stack еǰջԪĴ˵ջԪصһǵǰԪء +在遍历数组时用 Stack 把数组中的数存起来,如果当前遍历的数比栈顶元素来的大,说明栈顶元素的下一个比它大的数就是当前元素。 ```java public int[] nextGreaterElement(int[] nums1, int[] nums2) { @@ -3184,7 +3185,7 @@ public int[] nextGreaterElement(int[] nums1, int[] nums2) { } ``` -**ѭһȵǰԪش** +**循环数组中下一个比当前元素大的数** [Leetcode : 503. Next Greater Element II (Medium)](https://leetcode.com/problems/next-greater-element-ii/description/) @@ -3204,28 +3205,28 @@ public int[] nextGreaterElements(int[] nums) { ``` -## ϣ +## 哈希表 - Hash Table ԿٲһԪǷڵ⣬ҪһĿռ洢ȿʱ临Ӷȵ£ Hash Table ֿռ任ȡʱ +利用 Hash Table 可以快速查找一个元素是否存在等问题,但是需要一定的空间来存储。在优先考虑时间复杂度的情况下,可以利用 Hash Table 这种空间换取时间的做法。 -Java е **HashSet** ڴ洢һϣ O(1) ʱ临ӶȲԪǷڼС +Java 中的 **HashSet** 用于存储一个集合,并以 O(1) 的时间复杂度查找元素是否在集合中。 -ԪҷΧôһ洢һԪǷڣֻСдַԪأͿһΪ 26 IJ洢һַϣʹÿռ临ӶȽΪ O(1) +如果元素有穷,并且范围不大,那么可以用一个布尔数组来存储一个元素是否存在,例如对于只有小写字符的元素,就可以用一个长度为 26 的布尔数组来存储一个字符集合,使得空间复杂度降低为 O(1)。 -Java е **HashMap** ҪӳϵӶԪϵ +Java 中的 **HashMap** 主要用于映射关系,从而把两个元素联系起来。 -ڶһݽѹתʱ HashMap ԰ԭʼݺתϵһ url ϵͳУ[Leetcdoe : 535. Encode and Decode TinyURL (Medium)](https://leetcode.com/problems/encode-and-decode-tinyurl/description/) HashMap ͿԴ洢 url ԭʼ url ӳ䣬ʹòʾ򻯵 urlҲԸݼ򻯵 url õԭʼ url ӶλȷԴ +在对一个内容进行压缩或者其它转换时,利用 HashMap 可以把原始内容和转换后的内容联系起来。例如在一个简化 url 的系统中([Leetcdoe : 535. Encode and Decode TinyURL (Medium)](https://leetcode.com/problems/encode-and-decode-tinyurl/description/)),利用 HashMap 就可以存储精简后的 url 到原始 url 的映射,使得不仅可以显示简化的 url,也可以根据简化的 url 得到原始 url 从而定位到正确的资源。 -HashMap ҲԪؽмͳƣʱΪԪأֵΪ HashSet ƣԪҷΧ󣬿ͳơ +HashMap 也可以用来对元素进行计数统计,此时键为元素,值为计数。和 HashSet 类似,如果元素有穷并且范围不大,可以用整型数组来进行统计。 -**еΪֵ** +**数组中的两个数和为给定值** [Leetcode : 1. Two Sum (Easy)](https://leetcode.com/problems/two-sum/description/) -ȶȻʹ˫ָ뷽߶ֲҷʱ临ӶΪ O(nlgn)ռ临ӶΪ O(1) +可以先对数组进行排序,然后使用双指针方法或者二分查找方法。这样做的时间复杂度为 O(nlgn),空间复杂度为 O(1)。 - HashMap 洢Ԫغӳ䣬ڷʵ nums[i] ʱж HashMap Ƿ target - nums[i] ˵ target - nums[i] ڵ i Ҫҵ÷ʱ临ӶΪ O(n)ռ临ӶΪ O(n)ʹÿռȡʱ䡣 +用 HashMap 存储数组元素和索引的映射,在访问到 nums[i] 时,判断 HashMap 中是否存在 target - nums[i] ,如果存在说明 target - nums[i] 所在的索引和 i 就是要找的两个数。该方法的时间复杂度为 O(n),空间复杂度为 O(n),使用空间来换取时间。 ```java public int[] twoSum(int[] nums, int target) { @@ -3238,9 +3239,9 @@ public int[] twoSum(int[] nums, int target) { } ``` -**г** +**最长和谐序列** -гСֻΪ 1 +和谐序列中最大数和最小数只差正好为 1 [Leetcode : 594. Longest Harmonious Subsequence (Easy)](https://leetcode.com/problems/longest-harmonious-subsequence/description/) @@ -3260,15 +3261,15 @@ public int findLHS(int[] nums) { } ``` -## ַ +## 字符串 -**ַİַǷȫͬ** +**两个字符串的包含的字符是否完全相同** [Leetcode : 242. Valid Anagram (Easy)](https://leetcode.com/problems/valid-anagram/description/) -ַֻСдַܹ 26 Сдַ Hash Table ӳִַΪֵΧС˿ӳ䡣 +字符串只包含小写字符,总共有 26 个小写字符。可以用 Hash Table 来映射字符与出现次数,因为键值范围很小,因此可以用数组来进行映射。 -ʹóΪ 26 ֵַַͳƣȽֵַַǷͬ +使用长度为 26 的整型数组对字符串出现的字符进行统计,比较两个字符串出现的字符数量是否相同。 ```java public boolean isAnagram(String s, String t) { @@ -3280,13 +3281,13 @@ public boolean isAnagram(String s, String t) { } ``` -**ַͬ** +**字符串同构** [Leetcode : 205. Isomorphic Strings (Easy)](https://leetcode.com/problems/isomorphic-strings/description/) - "egg" "add" ַͬ +例如 "egg" 和 "add" 就属于同构字符串。 -¼һַϴγֵλãַijַϴγֵλһôͬ +记录一个字符上次出现的位置,如果两个字符串中某个字符上次出现的位置一样,那么就属于同构。 ```java public boolean isIsomorphic(String s, String t) { @@ -3303,30 +3304,30 @@ public boolean isIsomorphic(String s, String t) { } ``` -**һַϿɵĻַ󳤶** +**计算一组字符集合可以组成的回文字符串的最大长度** [Leetcode : 409. Longest Palindrome](https://leetcode.com/problems/longest-palindrome/description/) -ʹóΪ 128 ͳÿֵַĸÿַżɻַΪַмǸַԵ֣еַͰŵм䡣 +使用长度为 128 的整型数组来统计每个字符出现的个数,每个字符有偶数个可以用来构成回文字符串。因为回文字符串最中间的那个字符可以单独出现,所以如果有单独的字符就把它放到最中间。 ```java public int longestPalindrome(String s) { - int[] cnts = new int[128]; // ascii ܹ 128 + int[] cnts = new int[128]; // ascii 码总共 128 个 for(char c : s.toCharArray()) cnts[c]++; int ret = 0; for(int cnt : cnts) ret += (cnt / 2) * 2; - if(ret < s.length()) ret ++; // s һеδʹõַڣ԰ַŵĵм + if(ret < s.length()) ret ++; // 这个条件下 s 中一定有单个未使用的字符存在,可以把这个字符放到回文的最中间 return ret; } ``` -**жһǷǻ** +**判断一个整数是否是回文数** [Leetcode : 9. Palindrome Number (Easy)](https://leetcode.com/problems/palindrome-number/description/) -Ҫʹöռ䣬ҲͲܽתΪַжϡ +要求不能使用额外空间,也就不能将整数转换为字符串进行判断。 -ֳ֣ұDzҪתãȻжǷȡ +将整数分成左右两部分,右边那部分需要转置,然后判断这两部分是否相等。 ```java public boolean isPalindrome(int x) { @@ -3342,18 +3343,18 @@ public boolean isPalindrome(int x) { } ``` -**ַ** +**回文子字符串** [Leetcode : 647. Palindromic Substrings (Medium)](https://leetcode.com/problems/palindromic-substrings/description/) -Ǵַijһλʼȥչַ +解决方案是从字符串的某一位开始,尝试着去扩展子字符串。 ```java private int cnt = 0; public int countSubstrings(String s) { for(int i = 0; i < s.length(); i++) { - extendSubstrings(s, i, i); // - extendSubstrings(s, i, i + 1); // ż + extendSubstrings(s, i, i); // 奇数长度 + extendSubstrings(s, i, i + 1); // 偶数长度 } return cnt; } @@ -3367,7 +3368,7 @@ private void extendSubstrings(String s, int start, int end) { } ``` -**ͳƶַ 1 0 ַͬ** +**统计二进制字符串中连续 1 和 连续 0 数量相同的子字符串个数** ```html Input: "00110011" @@ -3393,40 +3394,40 @@ public int countBinarySubstrings(String s) { } ``` -**ַѭλ** +**字符串循环移位包含** -[ ֮3.1](#) +[ 编程之美:3.1](#) -ַ s1 s2 Ҫж s2 Ƿܹ s1 ѭλõַ +给定两个字符串 s1 和 s2 ,要求判定 s2 是否能够被 s1 做循环移位得到的字符串包含。 ```html s1 = AABCD, s2 = CDAA Return : true ``` -s1 ѭλĽ s1s1 ַֻҪж s2 Ƿ s1s1 ַɡ +s1 进行循环移位的结果是 s1s1 的子字符串,因此只要判断 s2 是否是 s1s1 的子字符串即可。 -**ַѭλ** +**字符串循环移位** -[ ֮2.17](#) +[ 编程之美:2.17](#) -ַѭƶ k λ +将字符串向右循环移动 k 位。 - abcd123 ƶ 3 λ õ 123abcd +例如 abcd123 向右移动 3 位 得到 123abcd - abcd123 е abcd 123 򣬵õ dcba321Ȼַ򣬵õ123abcd +将 abcd123 中的 abcd 和 123 单独逆序,得到 dcba321,然后对整个字符串进行逆序,得到123abcd。 -**ַеʵķת** +**字符串中单词的翻转** -[Աָ](#) +[程序员代码面试指南](#) -罫 "I am a student" ת "student a am I" +例如将 "I am a student" 翻转成 "student a am I" -ÿȻַ +将每个单词逆序,然后将整个字符串逆序。 -## +## 数组与矩阵 -**е 0 Ƶĩβ** +**把数组中的 0 移到末尾** [Leetcode : 283. Move Zeroes (Easy)](https://leetcode.com/problems/move-zeroes/description/) @@ -3443,20 +3444,20 @@ s1 } ``` -**һԪ [1, n] ֮䣬һ滻Ϊһҳʧظ** +**一个数组元素在 [1, n] 之间,其中一个数被替换为另一个数,找出丢失的数和重复的数** [Leetcode : 645. Set Mismatch (Easy)](https://leetcode.com/problems/set-mismatch/description/) -ֱӵķȶַʱ临ӶΪ O(nlogn) O(n) ʱ临ӶȡO(1) ռ临Ӷ⡣ +最直接的方法是先对数组进行排序,这种方法时间复杂度为 O(nlogn),本题可以以 O(n) 的时间复杂度、O(1) 空间复杂度来求解。 -Ҫ˼ͨԪأʹϵԪȷλϡ +主要思想是让通过交换数组元素,使得数组上的元素在正确的位置上。 -飬 i λϵԪز i + 1 ôͽ i λ nums[i] - 1 λϵԪأʹ num[i] - 1 ԪΪ nums[i] ҲǸλԪȷġҪѭУΪһνû취ʹõ i λϵԪȷġҪԪؿܾظԪأôѭͿԶȥֹѭķǼ nums[i] != nums[nums[i] - 1 +遍历数组,如果第 i 位上的元素不是 i + 1 ,那么就交换第 i 位 和 nums[i] - 1 位上的元素,使得 num[i] - 1 的元素为 nums[i] ,也就是该位的元素是正确的。交换操作需要循环进行,因为一次交换没办法使得第 i 位上的元素是正确的。但是要交换的两个元素可能就是重复元素,那么循环就可能永远进行下去,终止循环的方法是加上 nums[i] != nums[nums[i] - 1 条件。 -Ŀ +类似题目: -- [Leetcode :448. Find All Numbers Disappeared in an Array (Easy)](https://leetcode.com/problems/find-all-numbers-disappeared-in-an-array/description/)ѰжʧԪ -- [Leetcode : 442. Find All Duplicates in an Array (Medium)](https://leetcode.com/problems/find-all-duplicates-in-an-array/description/)ѰظԪء +- [Leetcode :448. Find All Numbers Disappeared in an Array (Easy)](https://leetcode.com/problems/find-all-numbers-disappeared-in-an-array/description/),寻找所有丢失的元素 +- [Leetcode : 442. Find All Duplicates in an Array (Medium)](https://leetcode.com/problems/find-all-duplicates-in-an-array/description/),寻找所有重复的元素。 ```java public int[] findErrorNums(int[] nums) { @@ -3478,11 +3479,11 @@ private void swap(int[] nums, int i, int j){ } ``` -**ҳظֵ [0, n-1] ֮** +**找出数组中重复的数,数组值在 [0, n-1] 之间** [Leetcode : 287. Find the Duplicate Number (Medium)](https://leetcode.com/problems/find-the-duplicate-number/description/) -ֲҽⷨ +二分查找解法: ```java public int findDuplicate(int[] nums) { @@ -3500,7 +3501,7 @@ public int findDuplicate(int[] nums) { } ``` -˫ָⷨлҳڣ +双指针解法,类似于有环链表中找出环的入口: ```java public int findDuplicate(int[] nums) { @@ -3519,11 +3520,11 @@ public int findDuplicate(int[] nums) { } ``` -### +### 有序矩阵 -ָкзֱľ +有序矩阵指的是行和列分别有序的矩阵。 -һʹöֲҷ +一般可以利用有序性使用二分查找方法。 ```html [ @@ -3533,7 +3534,7 @@ public int findDuplicate(int[] nums) { ] ``` -**** +**有序矩阵查找** [Leetocde : 240. Search a 2D Matrix II (Medium)](https://leetcode.com/problems/search-a-2d-matrix-ii/description/) @@ -3551,7 +3552,7 @@ public boolean searchMatrix(int[][] matrix, int target) { } ``` -** Kth Element** +**有序矩阵的 Kth Element** [Leetcode : 378. Kth Smallest Element in a Sorted Matrix ((Medium))](https://leetcode.com/problems/kth-smallest-element-in-a-sorted-matrix/description/) @@ -3566,9 +3567,9 @@ k = 8, return 13. ``` -ο[Share my thoughts and Clean Java Code](https://leetcode.com/problems/kth-smallest-element-in-a-sorted-matrix/discuss/85173) +解题参考:[Share my thoughts and Clean Java Code](https://leetcode.com/problems/kth-smallest-element-in-a-sorted-matrix/discuss/85173) -ֲҽⷨ +二分查找解法: ```java public int kthSmallest(int[][] matrix, int k) { @@ -3589,14 +3590,14 @@ public int kthSmallest(int[][] matrix, int k) { } ``` -ѽⷨ +堆解法: ```java public int kthSmallest(int[][] matrix, int k) { int m = matrix.length, n = matrix[0].length; PriorityQueue pq = new PriorityQueue(); for(int j = 0; j < n; j++) pq.offer(new Tuple(0, j, matrix[0][j])); - for(int i = 0; i < k - 1; i++) { // Сѣȥ k - 1 ѶԪأʱѶԪؾǵ k + for(int i = 0; i < k - 1; i++) { // 小根堆,去掉 k - 1 个堆顶元素,此时堆顶元素就是第 k 的数 Tuple t = pq.poll(); if(t.x == m - 1) continue; pq.offer(new Tuple(t.x + 1, t.y, matrix[t.x + 1][t.y])); @@ -3617,25 +3618,25 @@ class Tuple implements Comparable { } ``` -## +## 链表 -**жĽ** +**判断两个链表的交点** [Leetcode : 160. Intersection of Two Linked Lists](https://leetcode.com/problems/intersection-of-two-linked-lists/description/) ```html -A: a1 a2 - K - c1 c2 c3 - J -B: b1 b2 b3 +A: a1 → a2 + ↘ + c1 → c2 → c3 + ↗ +B: b1 → b2 → b3 ``` -Ҫʱ临ӶΪ O(n) ռ临ӶΪ O(1) +要求:时间复杂度为 O(n) 空间复杂度为 O(1) - A ijΪ a + cB ijΪ b + c c Ϊβֳȣ֪ a + c + b = b + c + a +设 A 的长度为 a + c,B 的长度为 b + c,其中 c 为尾部公共部分长度,可知 a + c + b = b + c + a。 - A ָʵβʱ B ͷʼ Bͬأ B ָʵβʱ A ͷʼ AܿƷ A B ָͬʱʵ㡣 +当访问 A 链表的指针访问到链表尾部时,令它从链表 B 的头部开始访问链表 B;同样地,当访问 B 链表的指针访问到链表尾部时,令它从链表 A 的头部开始访问链表 A。这样就能控制访问 A 和 B 两个链表的指针能同时访问到交点。 ```java public ListNode getIntersectionNode(ListNode headA, ListNode headB) { @@ -3649,19 +3650,19 @@ public ListNode getIntersectionNode(ListNode headA, ListNode headB) { } ``` -ֻжǷڽ㣬ôһ⣬ ֮3.6 ⡣ֽⷨѵһĽβӵڶĿͷڶǷڻֱӱȽϵһһڵ͵ڶһڵǷͬ +如果只是判断是否存在交点,那么就是另一个问题,即 编程之美:3.6 的问题。有两种解法:把第一个链表的结尾连接到第二个链表的开头,看第二个链表是否存在环;或者直接比较第一个链表最后一个节点和第二个链表最后一个节点是否相同。 -**ת** +**链表反转** [Leetcode : 206. Reverse Linked List](https://leetcode.com/problems/reverse-linked-list/description/) -ͷ巨ܹ򹹽 +头插法能够按逆序构建链表。 ```java public ListNode reverseList(ListNode head) { - ListNode newHead = null; // Ϊ null ΪĽβ + ListNode newHead = null; // 设为 null ,作为新链表的结尾 while(head != null){ ListNode nextNode = head.next; head.next = newHead; @@ -3672,11 +3673,11 @@ public ListNode reverseList(ListNode head) { } ``` -**鲢** +**归并两个有序的链表** [Leetcode : 21. Merge Two Sorted Lists](https://leetcode.com/problems/merge-two-sorted-lists/description/) -һõݹ鷽ʽ壺ǿսڵ㣬һֵһָһָ룬˺ܶõݹ +链表和树一样,可以用递归方式来定义:链表是空节点,或者有一个值和一个指向下一个链表的指针,因此很多链表问题可以用递归来处理。 ```java public ListNode mergeTwoLists(ListNode l1, ListNode l2) { @@ -3694,7 +3695,7 @@ public ListNode mergeTwoLists(ListNode l1, ListNode l2) { } ``` -**ɾظڵ** +**从有序链表中删除重复节点** [Leetcode : 83. Remove Duplicates from Sorted List (Easy)](https://leetcode.com/problems/remove-duplicates-from-sorted-list/description/) @@ -3706,11 +3707,11 @@ public ListNode deleteDuplicates(ListNode head) { } ``` -**** +**回文链表** [Leetcode : 234. Palindrome Linked List (Easy)](https://leetcode.com/problems/palindrome-linked-list/description/) -г룬ѺηתȻȽǷȡ +切成两半,把后半段反转,然后比较两半是否相等。 ```java public boolean isPalindrome(ListNode head) { @@ -3721,11 +3722,11 @@ public boolean isPalindrome(ListNode head) { fast = fast.next.next; } - if(fast != null){ // żڵ㣬 slow ָһڵ + if(fast != null){ // 偶数节点,让 slow 指向下一个节点 slow = slow.next; } - cut(head, slow); // г + cut(head, slow); // 切成两个链表 ListNode l1 = head, l2 = slow; l2 = reverse(l2); return isEqual(l1, l2); @@ -3757,18 +3758,18 @@ private boolean isEqual(ListNode l1, ListNode l2){ } ``` -**ɾڵ** +**从链表中删除节点** -[֮3.4]() +[编程之美:3.4]() -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/2c968ec5-0967-49ce-ac06-f8f5c9ab33bc.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//2c968ec5-0967-49ce-ac06-f8f5c9ab33bc.jpg) ```java B.val = C.val; B.next = C.next; ``` -**Ԫذżۼ** +**链表元素按奇偶聚集** [Leetcode : 328. Odd Even Linked List (Medium)](https://leetcode.com/problems/odd-even-linked-list/description/) @@ -3789,13 +3790,13 @@ public ListNode oddEvenList(ListNode head) { } ``` -## +## 树 -### ݹ +### 递归 -һҪôǿҪôָ룬ÿָָһһֵݹṹܶʹõݹ +一棵树要么是空树,要么有两个指针,每个指针指向一棵树。树是一种递归结构,很多树的问题可以使用递归来处理。 -**ĸ߶** +**树的高度** [Leetcode : 104. Maximum Depth of Binary Tree (Easy)](https://leetcode.com/problems/maximum-depth-of-binary-tree/description/) @@ -3806,21 +3807,21 @@ public int maxDepth(TreeNode root) { } ``` -**ת** +**翻转树** [Leetcode : 226. Invert Binary Tree (Easy)](https://leetcode.com/problems/invert-binary-tree/description/) ```java public TreeNode invertTree(TreeNode root) { if(root == null) return null; - TreeNode left = root.left; // IJı left ָ룬ȱ + TreeNode left = root.left; // 后面的操作会改变 left 指针,因此先保存下来 root.left = invertTree(root.right); root.right = invertTree(left); return root; } ``` -**鲢** +**归并两棵树** [Leetcode : 617. Merge Two Binary Trees (Easy)](https://leetcode.com/problems/merge-two-binary-trees/description/) @@ -3836,11 +3837,11 @@ public TreeNode mergeTrees(TreeNode t1, TreeNode t2) { } ``` -**ж·Ƿһ** +**判断路径和是否等于一个数** [Leetcdoe : 112. Path Sum (Easy)](https://leetcode.com/problems/path-sum/description/) -Ŀ·ͶΪ root leaf нڵĺ +题目描述:路径和定义为从 root 到 leaf 的所有节点的和 ```java public boolean hasPathSum(TreeNode root, int sum) { @@ -3850,13 +3851,13 @@ public boolean hasPathSum(TreeNode root, int sum) { } ``` -**ͳ·͵һ·** +**统计路径和等于一个数的路径数量** [Leetcode : 437. Path Sum III (Easy)](https://leetcode.com/problems/path-sum-iii/description/) -Ŀ·һ root ͷ leaf βDZ +题目描述:路径不一定以 root 开头并以 leaf 结尾,但是必须连续 -pathSumStartWithRoot() ͳijڵ㿪ͷ· +pathSumStartWithRoot() 方法统计以某个节点开头的路径个数。 ```java public int pathSum(TreeNode root, int sum) { @@ -3874,7 +3875,7 @@ private int pathSumStartWithRoot(TreeNode root, int sum){ } ``` -**ĶԳ** +**树的对称** [Leetcode : 101. Symmetric Tree (Easy)](https://leetcode.com/problems/symmetric-tree/description/) @@ -3892,11 +3893,11 @@ private boolean isSymmetric(TreeNode t1, TreeNode t2){ } ``` -**ƽ** +**平衡树** [Leetcode : 110. Balanced Binary Tree (Easy)](https://leetcode.com/problems/balanced-binary-tree/description/) -Ŀ߶ȲǷСڵ 1 +题目描述:左右子树高度差是否都小于等于 1 ```java private boolean result = true; @@ -3915,11 +3916,11 @@ public int maxDepth(TreeNode root) { } ``` -**С·** +**最小路径** [Leetcode : 111. Minimum Depth of Binary Tree (Easy)](https://leetcode.com/problems/minimum-depth-of-binary-tree/description/) -Ŀĸڵ㵽ҶӽڵС +题目描述:树的根节点到叶子节点的最小长度 ```java public int minDepth(TreeNode root) { @@ -3931,7 +3932,7 @@ public int minDepth(TreeNode root) { } ``` -**ͳҶӽڵĺ** +**统计左叶子节点的和** [Leetcode : 404. Sum of Left Leaves (Easy)](https://leetcode.com/problems/sum-of-left-leaves/description/) @@ -3948,11 +3949,11 @@ private boolean isLeaf(TreeNode node){ } ``` -**޼һ** +**修剪一棵树** [Leetcode : 669. Trim a Binary Search Tree (Easy)](https://leetcode.com/problems/trim-a-binary-search-tree/description/) -Ŀֵֻ L \~ R ֮Ľڵ +题目描述:只保留值在 L \~ R 之间的节点 ```java public TreeNode trimBST(TreeNode root, int L, int R) { @@ -3965,7 +3966,7 @@ public TreeNode trimBST(TreeNode root, int L, int R) { } ``` -**** +**子树** [Leetcode : 572. Subtree of Another Tree (Easy)](https://leetcode.com/problems/subtree-of-another-tree/description/) @@ -3985,11 +3986,11 @@ private boolean isSame(TreeNode s, TreeNode t){ } ``` -**й** +**从有序数组中构造二叉查找树** [Leetcode : 108. Convert Sorted Array to Binary Search Tree (Easy)](https://leetcode.com/problems/convert-sorted-array-to-binary-search-tree/description/) -BSTڵڵнڵ㣬Сڵнڵ㡣 +二叉查找树(BST):根节点大于等于左子树所有节点,小于等于右子树所有节点。 ```java public TreeNode sortedArrayToBST(int[] nums) { @@ -4006,7 +4007,7 @@ private TreeNode toBST(int[] nums, int sIdx, int eIdx){ } ``` -**ڵ·** +**两节点的最长路径** ```html 1 @@ -4037,7 +4038,7 @@ private int depth(TreeNode root) { } ``` -**ҳеڶСĽڵ** +**找出二叉树中第二小的节点** [Leetcode : 671. Second Minimum Node In a Binary Tree (Easy)](https://leetcode.com/problems/second-minimum-node-in-a-binary-tree/description/) @@ -4052,7 +4053,7 @@ Input: Output: 5 ``` -һڵҪô 0 2 ӽڵ㣬ӽڵ㣬ôڵСĽڵ㡣 +一个节点要么具有 0 个或 2 个子节点,如果有子节点,那么根节点是最小的节点。 ```java public int findSecondMinimumValue(TreeNode root) { @@ -4068,7 +4069,7 @@ public int findSecondMinimumValue(TreeNode root) { } ``` -**Ѱڵ** +**寻找两个节点的最近公共祖先** [Leetcode : 235. Lowest Common Ancestor of a Binary Search Tree (Easy)](https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-search-tree/description/) @@ -4080,7 +4081,7 @@ public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { } ``` -**** +**最近公共祖先** [Leetcode : 236. Lowest Common Ancestor of a Binary Tree (Medium) ](https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-tree/description/) @@ -4093,7 +4094,7 @@ public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { } ``` -**ͬڵֵ·** +**最大相同节点值的路径长度** [Leetcode : 687. Longest Univalue Path (Easy)](https://pomotodo.com/app/) @@ -4125,7 +4126,7 @@ private int dfs(TreeNode root){ } ``` -**** +**间隔遍历** [Leetcode : 337. House Robber III (Medium)](https://leetcode.com/problems/house-robber-iii/description/) @@ -4144,11 +4145,11 @@ public int rob(TreeNode root) { } ``` -### α +### 层次遍历 -ʹ BFSҪʹֱ洢ǰĽڵһĽڵ㣬 ΪڿʼһĽڵʱǰеĽڵǵǰĽڵֻҪƱôڵܱ֤αĶǵǰĽڵ㡣 +使用 BFS,不需要使用两个队列来分别存储当前层的节点和下一层的节点, 因为在开始遍历一层的节点时,当前队列中的节点数就是当前层的节点数,只要控制遍历这么多节点数,就能保证这次遍历的都是当前层的节点。 -**һÿڵƽ** +**计算一棵树每层节点的平均数** [637. Average of Levels in Binary Tree (Easy)](https://leetcode.com/problems/average-of-levels-in-binary-tree/description/) @@ -4173,7 +4174,7 @@ public List averageOfLevels(TreeNode root) { } ``` -**õ½ǵĽڵ** +**得到左下角的节点** [Leetcode : 513. Find Bottom Left Tree Value (Easy)](https://leetcode.com/problems/find-bottom-left-tree-value/description/) @@ -4190,7 +4191,7 @@ public int findBottomLeftValue(TreeNode root) { } ``` -### ǰк +### 前中后序遍历 ```html 1 @@ -4200,16 +4201,16 @@ public int findBottomLeftValue(TreeNode root) { 4 5 6 ``` -α˳[1 2 3 4 5 6] -ǰ˳[1 2 4 5 3 6] -˳[4 2 5 1 3 6] -˳[4 5 2 6 3 1] +层次遍历顺序:[1 2 3 4 5 6] +前序遍历顺序:[1 2 4 5 3 6] +中序遍历顺序:[4 2 5 1 3 6] +后序遍历顺序:[4 5 2 6 3 1] -αʹ BFS ʵ֣õľ BFS һһԣǰ򡢺 DFS ʵ֡ +层次遍历使用 BFS 实现,利用的就是 BFS 一层一层遍历的特性;而前序、中序、后序遍历利用了 DFS 实现。 -ǰ򡢺ֻڶԽڵʵ˳һ㲻ͬͬ +前序、中序、后序遍只是在对节点访问的顺序有一点不同,其它都相同。 - ǰ +① 前序 ```java void dfs(TreeNode root){ @@ -4219,7 +4220,7 @@ void dfs(TreeNode root){ } ``` - +② 中序 ```java void dfs(TreeNode root){ @@ -4229,7 +4230,7 @@ void dfs(TreeNode root){ } ``` - +③ 后序 ```java void dfs(TreeNode root){ @@ -4239,7 +4240,7 @@ void dfs(TreeNode root){ } ``` -**ǵݹʵֶǰ** +**非递归实现二叉树的前序遍历** [Leetcode : 144. Binary Tree Preorder Traversal (Medium)](https://leetcode.com/problems/binary-tree-preorder-traversal/description/) @@ -4253,17 +4254,17 @@ public List preorderTraversal(TreeNode root) { TreeNode node = stack.pop(); ret.add(node.val); if (node.right != null) stack.push(node.right); - if (node.left != null) stack.push(node.left); // Ϊջ + if (node.left != null) stack.push(node.left); // 先添加右子树再添加左子树,这样是为了让左子树在栈顶 } return ret; } ``` -**ǵݹʵֶĺ** +**非递归实现二叉树的后续遍历** [Leetcode : ### 145. Binary Tree Postorder Traversal (Medium)](https://leetcode.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 postorderTraversal(TreeNode root) { @@ -4282,7 +4283,7 @@ public List postorderTraversal(TreeNode root) { } ``` -**ǵݹʵֶ** +**非递归实现二叉树的中序遍历** [Leetcode : 94. Binary Tree Inorder Traversal (Medium)](https://leetcode.com/problems/binary-tree-inorder-traversal/description/) @@ -4292,7 +4293,7 @@ public List inorderTraversal(TreeNode root) { Stack stack = new Stack<>(); TreeNode cur = root; while(cur != null || !stack.isEmpty()) { - while(cur != null) { // ģݹջIJ + while(cur != null) { // 模拟递归栈的不断深入 stack.add(cur); cur = cur.left; } @@ -4304,19 +4305,19 @@ public List inorderTraversal(TreeNode root) { } ``` -**ʹǰؽ** //TODO +**使用中序遍历和前序遍历序列重建二叉树** //TODO ### BST -Ҫ BST ص㡣 +主要利用 BST 中序遍历有序的特点。 -** BST Ѱڵ㣬ʹǵĺΪһֵ** +**在 BST 中寻找两个节点,使它们的和为一个给定值。** [653. Two Sum IV - Input is a BST](https://leetcode.com/problems/two-sum-iv-input-is-a-bst/description/) -ʹõ֮˫ָвҡ +使用中序遍历得到有序数组之后,再利用双指针对数组进行查找。 -Ӧע⵽һⲻ÷ֱ˼룬ΪĽڵֱܷС +应该注意到,这一题不能用分别在左右子树两部分来处理这种思想,因为两个待求的节点可能分别在左右子树中。 ```java public boolean findTarget(TreeNode root, int k) { @@ -4340,11 +4341,11 @@ private void inOrder(TreeNode root, List nums){ } ``` -** BST вСڵ֮ľֵ** +**在 BST 中查找最小的两个节点之差的绝对值** [Leetcode : 530. Minimum Absolute Difference in BST (Easy)](https://leetcode.com/problems/minimum-absolute-difference-in-bst/description/) - BST Ϊʣٽڵ֮ľֵȡСֵ +利用 BST 的中序遍历为有序的性质,计算中序遍历中临近的两个节点之差的绝对值,取最小值。 ```java private int minDiff = Integer.MAX_VALUE; @@ -4364,11 +4365,11 @@ private void inorder(TreeNode node){ } ``` -** BST ÿڵֵϱĽڵֵ** +**把 BST 每个节点的值都加上比它大的节点的值** [Leetcode : Convert BST to Greater Tree (Easy)](https://leetcode.com/problems/convert-bst-to-greater-tree/description/) -ȱ +先遍历右子树。 ```java private int sum = 0; @@ -4393,7 +4394,7 @@ private void traver(TreeNode root) { } ``` -**Ѱ BST гִĽڵ** +**寻找 BST 中出现次数最多的节点** ```java private int cnt = 1; @@ -4431,11 +4432,11 @@ private void inorder(TreeNode node){ } ``` -**Ѱ BST ĵ k Ԫ** +**寻找 BST 的第 k 个元素** [Leetcode : 230. Kth Smallest Element in a BST (Medium)](https://leetcode.com/problems/kth-smallest-element-in-a-bst/description/) -ݹⷨ +递归解法: ```java public int kthSmallest(TreeNode root, int k) { @@ -4451,7 +4452,7 @@ private int count(TreeNode node) { } ``` -ⷨ +中序遍历解法: ```java private int cnt = 0; @@ -4477,11 +4478,11 @@ private void inorder(TreeNode node, int k) { ### Trie -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/5c638d59-d4ae-4ba4-ad44-80bdc30f38dd.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//5c638d59-d4ae-4ba4-ad44-80bdc30f38dd.jpg) -Trieֳǰ׺ֵжַǷڻǷijַǰ׺ +Trie,又称前缀树或字典树,用于判断字符串是否存在或者是否具有某种字符串前缀。 -**ʵһ Trie** +**实现一个 Trie** [Leetcode : 208. Implement Trie (Prefix Tree) (Medium)](https://leetcode.com/problems/implement-trie-prefix-tree/description/) @@ -4541,7 +4542,7 @@ class Trie { } ``` -**ʵһ Trieǰ׺** +**实现一个 Trie,用来求前缀和** [Leetcode : 677. Map Sum Pairs (Medium)](https://leetcode.com/problems/map-sum-pairs/description/) @@ -4598,47 +4599,47 @@ class MapSum { } ``` -## ͼ +## 图 -## λ +## 位运算 -**1. ԭ** +**1. 基本原理** -0s ʾ һ 0 1s ʾһ 1 +0s 表示 一串 0 ,1s 表示一串 1。 ``` x ^ 0s = x x & 0s = 0 x | 0s = x -x ^ 1s = \~x x & 1s = x x | 1s = 1s +x ^ 1s = ~x x & 1s = x x | 1s = 1s x ^ x = 0 x & x = x x | x = x ``` - x ^ 1s = \~x ص㣬Խλʾת x ^ x = 0 ص㣬Խظȥֻһ - x & 0s = 0 x & 1s = x ص㣬ʵһ num mask 00111100 λֻ num mask 1 Ӧλ - x | 0s = x x | 1s = 1s ص㣬ʵòһ num mask00111100 λ num mask 1 ӦλΪ 1 +① 利用 x ^ 1s = \~x 的特点,可以将位级表示翻转;利用 x ^ x = 0 的特点,可以将三个数中重复的两个数去除,只留下另一个数; +② 利用 x & 0s = 0 和 x & 1s = x 的特点,可以实现掩码操作。一个数 num 与 mask :00111100 进行位与操作,只保留 num 中与 mask 的 1 部分相对应的位; +③ 利用 x | 0s = x 和 x | 1s = 1s 的特点,可以实现设置操作。一个数 num 与 mask:00111100 进行位或操作,将 num 中与 mask 的 1 部分相对应的位都设置为 1 。 -\>\> n Ϊƣ൱ڳ 2n -\>\>\> n Ϊ޷ƣ߻Ჹ 0 -<< n Ϊƣ൱ڳ 2n +\>\> n 为算术右移,相当于除以 2n; +\>\>\> n 为无符号右移,左边会补上 0。 +<< n 为算术左移,相当于乘以 2n。 -n&(n-1) λȥ n λʾ͵һλڶƱʾ 10110**100**ȥ 1 õ 10110**011**õ 10110**000** +n&(n-1) 该位运算是去除 n 的位级表示中最低的那一位。例如对于二进制表示 10110**100**,减去 1 得到 10110**011**,这两个数相与得到 10110**000**。 -n-n&(\~n+1) ȥ n λʾߵһλ +n-n&(\~n+1) 概运算是去除 n 的位级表示中最高的那一位。 -n&(-n) õ n λʾ͵һλ-n õ n ķ 1ڶƱʾ 10110**100**-n õ 01001**100**õ 00000**100** +n&(-n) 该运算得到 n 的位级表示中最低的那一位。-n 得到 n 的反码加 1,对于二进制表示 10110**100**,-n 得到 01001**100**,相与得到 00000**100** -**2. mask ** +**2. mask 计算** -Ҫȡ 111111111 0 ȡɣ\~0 +要获取 111111111,将 0 取反即可,\~0。 -Ҫõֻе i λΪ 1 mask 1 ƶ i λɣ1<<i 1<<5 õֻе 5 λΪ 1 mask 00010000 +要得到只有第 i 位为 1 的 mask,将 1 向左移动 i 位即可,1<<i 。例如 1<<5 得到只有第 5 位为 1 的 mask :00010000。 -Ҫõ 1 i λΪ 1 mask1<<(i+1)-1 ɣ罫 1<<(4+1)-1 = 00010000-1 = 00001111 +要得到 1 到 i 位为 1 的 mask,1<<(i+1)-1 即可,例如将 1<<(4+1)-1 = 00010000-1 = 00001111。 -Ҫõ 1 i λΪ 0 maskֻ轫 1 i λΪ 1 mask ȡ \~(1<<(i+1)-1) +要得到 1 到 i 位为 0 的 mask,只需将 1 到 i 位为 1 的 mask 取反,即 \~(1<<(i+1)-1)。 -**3. λ** +**3. 位操作举例** - ȡ i λ +① 获取第 i 位 num & 00010000 != 0 @@ -4646,7 +4647,7 @@ num & 00010000 != 0 (num & (1 << i)) != 0; ``` - i λΪ 1 +② 将第 i 位设置为 1 num | 00010000 @@ -4654,15 +4655,15 @@ num | 00010000 num | (1 << i); ``` - i λΪ 0 +③ 将第 i 位清除为 0 num & 11101111 ```java -num & (\~(1 << i)) +num & (~(1 << i)) ``` - λ i λΪ 0 +④ 将最高位到第 i 位清除为 0 num & 00001111 @@ -4670,35 +4671,35 @@ num & 00001111 num & ((1 << i) - 1); ``` - 0 λ i λΪ 0 +⑤ 将第 0 位到第 i 位清除为 0 num & 11110000 ```java -num & (\~((1 << (i+1)) - 1)); +num & (~((1 << (i+1)) - 1)); ``` - i λΪ 0 1 +⑥ 将第 i 位设置为 0 或者 1 -Ƚ i λ㣬Ȼ v i λִСλ㡣 +先将第 i 位清零,然后将 v 左移 i 位,执行“位或”运算。 ```java (num & (1 << i)) | (v << i); ``` -**4. Java еλ** +**4. Java 中的位操作** ```html -static int Integer.bitCount() // ͳ 1 -static int Integer.highestOneBit() // λ -static String toBinaryString(int i) // תλƱʾַ +static int Integer.bitCount() // 统计 1 的数量 +static int Integer.highestOneBit() // 获得最高位 +static String toBinaryString(int i) // 转换位二进制表示的字符串 ``` -**ͳĶƱʾжλͬ** +**统计两个数的二进制表示有多少位不同** [Leetcode : 461. Hamming Distance (Easy)](https://leetcode.com/problems/hamming-distance/) -ͬһλΪ 1 ͳжٸ 1 ɡ +对两个数进行异或操作,不同的那一位结果为 1 ,统计有多少个 1 即可。 ```java public int hammingDistance(int x, int y) { @@ -4712,7 +4713,7 @@ public int hammingDistance(int x, int y) { } ``` -ʹ Integer.bitcount() ͳ 1 ĸ +可以使用 Integer.bitcount() 来统计 1 个的个数。 ```java public int hammingDistance(int x, int y) { @@ -4720,7 +4721,7 @@ public int hammingDistance(int x, int y) { } ``` -**תһıλ** +**翻转一个数的比特位** [Leetcode : 190. Reverse Bits (Easy)](https://leetcode.com/problems/reverse-bits/description/) @@ -4736,9 +4737,9 @@ public int reverseBits(int n) { } ``` -**ö** +**不用额外变量交换两个整数** -[Աָ P317](#) +[程序员代码面试指南 :P317](#) ```java a = a ^ b; @@ -4746,13 +4747,13 @@ b = a ^ b; a = a ^ b; ``` - c = a ^ bô b ^ c = b ^ b ^ a = aa ^ c = a ^ a ^ b = b +将 c = a ^ b,那么 b ^ c = b ^ b ^ a = a,a ^ c = a ^ a ^ b = b。 -**жһDz 4 n η** +**判断一个数是不是 4 的 n 次方** [Leetcode : 342. Power of Four (Easy)](https://leetcode.com/problems/power-of-four/) -ƱʾֻһλΪ 1 ĶΪ 0 16 10000ÿΰ 1 ƶ 2 λܹ֣ȻȽϹҪжϵǷͬ +该数二进制表示有且只有一个奇数位为 1 ,其余的都为 0 ,例如 16 : 10000。可以每次把 1 向左移动 2 位,就能构造出这种数字,然后比较构造出来的数与要判断的数是否相同。 ```java public boolean isPowerOfFour(int num) { @@ -4765,7 +4766,7 @@ public boolean isPowerOfFour(int num) { } ``` -Ҳ Java Integer.toString() תΪ 4 ʽַȻжַǷ 1 ͷ +也可以用 Java 的 Integer.toString() 方法将该数转换为 4 进制形式的字符串,然后判断字符串是否以 1 开头。 ```java public boolean isPowerOfFour(int num) { @@ -4773,11 +4774,11 @@ public boolean isPowerOfFour(int num) { } ``` -**жһDz 2 n η** +**判断一个数是不是 2 的 n 次方** [Leetcode : 231. Power of Two (Easy)](https://leetcode.com/problems/power-of-two/description/) -ͬ Power of Four ķ 2 n η⣬ĶƱʾֻһ 1 ڡ +同样可以用 Power of Four 的方法,但是 2 的 n 次方更特殊,它的二进制表示只有一个 1 存在。 ```java public boolean isPowerOfTwo(int n) { @@ -4785,7 +4786,7 @@ public boolean isPowerOfTwo(int n) { } ``` - 1000 & 0111 == 0 ʣõ½ⷨ +利用 1000 & 0111 == 0 这种性质,得到以下解法: ```java public boolean isPowerOfTwo(int n) { @@ -4793,13 +4794,13 @@ public boolean isPowerOfTwo(int n) { } ``` -**ΨһһظԪ** +**数组中唯一一个不重复的元素** [Leetcode : 136. Single Number (Easy)](https://leetcode.com/problems/single-number/description/) -ͬĽΪ 0ĽǵֵǸ +两个相同的数异或的结果为 0,对所有数进行异或操作,最后的结果就是单独出现的那个数。 -ƵУ[Leetcode : 389. Find the Difference (Easy)](https://leetcode.com/problems/find-the-difference/description/)ַһַͬʹ O(1) Ŀռ临Ӷ⣬Ҫʹ HashSet +类似的有:[Leetcode : 389. Find the Difference (Easy)](https://leetcode.com/problems/find-the-difference/description/),两个字符串仅有一个字符不相同,使用异或操作可以以 O(1) 的空间复杂度来求解,而不需要使用 HashSet。 ```java public int singleNumber(int[] nums) { @@ -4809,22 +4810,22 @@ public int singleNumber(int[] nums) { } ``` -**вظԪ** +**数组中不重复的两个元素** [Leetcode : 260. Single Number III (Medium)](https://leetcode.com/problems/single-number-iii/description/) -ȵԪλʾϱضһλڲͬ +两个不相等的元素在位级表示上必定会有一位存在不同。 -ԪõĽΪظԪĽ +将数组的所有元素异或得到的结果为不存在重复的两个元素异或的结果。 -diff &= -diff õ diff Ҳ಻Ϊ 0 λҲDzظԪλʾҲ಻ͬһλһλͿԽԪֿ +diff &= -diff 得到出 diff 最右侧不为 0 的位,也就是不存在重复的两个元素在位级表示上最右侧不同的那一位,利用这一位就可以将两个元素区分开来。 ```java public int[] singleNumber(int[] nums) { int diff = 0; for(int num : nums) diff ^= num; - // õһλ + // 得到最右一位 diff &= -diff; int[] ret = new int[2]; for(int num : nums) { @@ -4835,11 +4836,11 @@ public int[] singleNumber(int[] nums) { } ``` -**жһλʾǷ񲻻 0 1** +**判断一个数的位级表示是否不会出现连续的 0 和 1** [Leetcode : 693. Binary Number with Alternating Bits (Easy)](https://leetcode.com/problems/binary-number-with-alternating-bits/description/) - 10101 λʾƶ 1 λõ 1010 ÿλͬõĽΪ 11111 +对于 10101 这种位级表示的数,把它向右移动 1 位得到 1010 ,这两个数每个位都不同,因此异或得到的结果为 11111。 ```java public boolean hasAlternatingBits(int n) { @@ -4848,13 +4849,13 @@ public boolean hasAlternatingBits(int n) { } ``` -**һIJ** +**求一个数的补码** [Leetcode : 476. Number Complement (Easy)](https://leetcode.com/problems/number-complement/description/) -ǶƱʾе 0 +不考虑二进制表示中的首 0 部分 - 00000101ҪԽ 00000111 ôתΪ 00000111 +对于 00000101,要求补码可以将它与 00000111 进行异或操作。那么问题就转换为求掩码 00000111。 ```java public int findComplement(int num) { @@ -4866,7 +4867,7 @@ public int findComplement(int num) { } ``` - Java Integer.highestOneBit() ú 1 +可以利用 Java 的 Integer.highestOneBit() 方法来获得含有首 1 的数。 ```java public int findComplement(int num) { @@ -4877,7 +4878,7 @@ public int findComplement(int num) { } ``` - 10000000 Ҫչ 11111111· +对于 10000000 这样的数要扩展成 11111111,可以利用以下方法: ```html mask |= mask >> 1 11000000 @@ -4897,11 +4898,11 @@ public int findComplement(int num) { } ``` -**ʵļӷ** +**实现整数的加法** [Leetcode : 371. Sum of Two Integers (Easy)](https://leetcode.com/problems/sum-of-two-integers/description/) -a ^ b ʾûпǽλĺͣ(a & b) << 1 ǽλݹֹԭ (a & b) << 1 ұ߻һ 0ôݹ飬λұߵ 0 ࣬λΪ 0ݹֹ +a ^ b 表示没有考虑进位的情况下两数的和,(a & b) << 1 就是进位。递归会终止的原因是 (a & b) << 1 最右边会多一个 0,那么继续递归,进位最右边的 0 会慢慢增多,最后进位会变为 0,递归终止。 ```java public int getSum(int a, int b) { @@ -4909,13 +4910,13 @@ public int getSum(int a, int b) { } ``` -**ַ˻** +**字符串数组最大乘积** [Leetcode : 318. Maximum Product of Word Lengths (Medium)](https://leetcode.com/problems/maximum-product-of-word-lengths/description/) -ĿַַֻСдַַַȵ˻Ҫַַܺͬ +题目描述:字符串数组的字符串只含有小写字符。求解字符串数组中两个字符串长度的最大乘积,要求这两个字符串不能含有相同字符。 -˼·ҪжַǷַַֻͬСдַܹ 26 λ˿һ 32 λ洢ÿַǷֹ +解题思路:本题主要问题是判断两个字符串是否含相同字符,由于字符串只含有小写字符,总共 26 位,因此可以用一个 32 位的整数来存储每个字符是否出现过。 ```java public int maxProduct(String[] words) { @@ -4939,12 +4940,12 @@ public int maxProduct(String[] words) { } ``` -# ο +# 参考资料 - [Leetcode](https://leetcode.com/problemset/algorithms/?status=Todo) -- Weiss M A, ˴. ݽṹ㷨C [J]. 2004. +- Weiss M A, 冯舜玺. 数据结构与算法分析——C 语言描述[J]. 2004. - Sedgewick R. Algorithms[M]. Pearson Education India, 1988. -- κ, ʦ. ָ Offer: Թپͱ[M]. ӹҵ, 2014. -- ֮С. ֮[M]. ӹҵ, 2008. -- . Աָ[M]. ӹҵ, 2015. +- 何海涛, 软件工程师. 剑指 Offer: 名企面试官精讲典型编程题[M]. 电子工业出版社, 2014. +- 《编程之美》小组. 编程之美[M]. 电子工业出版社, 2008. +- 左程云. 程序员代码面试指南[M]. 电子工业出版社, 2015. diff --git a/notes/Linux.md b/notes/Linux.md index 60773fda..2981c0cd 100644 --- a/notes/Linux.md +++ b/notes/Linux.md @@ -1,41 +1,41 @@ -* [òԼ](#òԼ) - * [](#) - * [ػ](#ػ) - * [鿴](#鿴) - * [鿴˿](#鿴˿) +* [常用操作以及概念](#常用操作以及概念) + * [求助](#求助) + * [关机](#关机) + * [查看进程](#查看进程) + * [查看端口](#查看端口) * [PATH](#path) - * [еȼ](#еȼ) + * [运行等级](#运行等级) * [sudo](#sudo) * [GNU](#gnu) - * [](#) - * [а汾](#а汾) -* [](#) - * [̵ļ](#̵ļ) - * [](#) + * [包管理工具](#包管理工具) + * [常见发行版本](#常见发行版本) +* [分区](#分区) + * [磁盘的文件名](#磁盘的文件名) + * [分区表](#分区表) * [1. MBR](#1-mbr) * [2. GPT](#2-gpt) - * [](#) + * [开机检测程序](#开机检测程序) * [1. BIOS](#1-bios) * [2. UEFI](#2-uefi) - * [](#) -* [ļȨĿ¼](#ļȨĿ¼) - * [ļȨ޸](#ļȨ޸) - * [ļԼȨ޵޸](#ļԼȨ޵޸) - * [1. ޸ļȺ](#1-޸ļȺ) - * [2. ޸ļӵ](#2-޸ļӵ) - * [3. ޸Ȩ](#3-޸Ȩ) - * [Ŀ¼Ȩ](#Ŀ¼Ȩ) - * [ļĬȨ](#ļĬȨ) - * [Ŀ¼](#Ŀ¼) -* [ļĿ¼](#ļĿ¼) - * [ļʱ](#ļʱ) - * [ļĿ¼Ļ](#ļĿ¼Ļ) + * [挂载](#挂载) +* [文件权限与目录配置](#文件权限与目录配置) + * [文件权限概念](#文件权限概念) + * [文件属性以及权限的修改](#文件属性以及权限的修改) + * [1. 修改文件所属群组](#1-修改文件所属群组) + * [2. 修改文件拥有者](#2-修改文件拥有者) + * [3. 修改权限](#3-修改权限) + * [目录的权限](#目录的权限) + * [文件默认权限](#文件默认权限) + * [目录配置](#目录配置) +* [文件与目录](#文件与目录) + * [文件时间](#文件时间) + * [文件与目录的基本操作](#文件与目录的基本操作) * [1. ls](#1-ls) * [2. cp](#2-cp) * [3. rm](#3-rm) * [4. mv](#4-mv) - * [ȡļ](#ȡļ) + * [获取文件内容](#获取文件内容) * [1. cat](#1-cat) * [2. tac](#2-tac) * [3. more](#3-more) @@ -44,97 +44,98 @@ * [6. tail](#6-tail) * [7. od](#7-od) * [8. touch](#8-touch) - * [ָļ](#ָļ) + * [指令与文件搜索](#指令与文件搜索) * [1. which](#1-which) * [2. whereis](#2-whereis) * [3. locate](#3-locate) * [4. find](#4-find) - * [4.1 ʱйصѡ](#41-ʱйصѡ) - * [4.2 ļӵߺȺйصѡ](#42-ļӵߺȺйصѡ) - * [4.3 ļȨ޺йصѡ](#43-ļȨ޺йصѡ) -* [ļϵͳ](#ļϵͳ) - * [ļϵͳ](#ļϵͳ) + * [4.1 与时间有关的选项](#41-与时间有关的选项) + * [4.2 与文件拥有者和所属群组有关的选项](#42-与文件拥有者和所属群组有关的选项) + * [4.3 与文件权限和名称有关的选项](#43-与文件权限和名称有关的选项) +* [磁盘与文件系统](#磁盘与文件系统) + * [文件系统的组成](#文件系统的组成) * [inode](#inode) - * [Ŀ¼ inode block](#Ŀ¼-inode--block) - * [ʵ](#ʵ) - * [1. ʵ](#1-ʵ) - * [2. ](#2-) -* [ѹ](#ѹ) - * [ѹ](#ѹ) + * [目录的 inode 与 block](#目录的-inode-与-block) + * [实体链接与符号链接](#实体链接与符号链接) + * [1. 实体链接](#1-实体链接) + * [2. 符号链接](#2-符号链接) +* [压缩与打包](#压缩与打包) + * [压缩](#压缩) * [1. gzip](#1-gzip) * [2. bzip2](#2-bzip2) * [3. xz](#3-xz) - * [](#) + * [打包](#打包) * [Bash](#bash) - * [Bash ](#bash-) - * [](#) - * [ָ˳](#ָ˳) - * [ض](#ض) - * [ָ](#ָ) - * [1. ȡָcut](#1-ȡָcut) - * [2. sortuniq](#2-sortuniq) - * [3. ˫ضtee](#3-˫ضtee) - * [4. ַתָtrcolexpandjoinpaste](#4-ַתָtrcolexpandjoinpaste) - * [5. ָsplit](#5-ָsplit) -* [ʾļʽ](#ʾļʽ) + * [Bash 特性](#bash-特性) + * [变量操作](#变量操作) + * [指令搜索顺序](#指令搜索顺序) + * [数据流重定向](#数据流重定向) + * [管线指令](#管线指令) + * [1. 提取指令:cut](#1-提取指令cut) + * [2. 排序命令:sort、uniq](#2-排序命令sortuniq) + * [3. 双向输出重定向:tee](#3-双向输出重定向tee) + * [4. 字符转换指令:tr、col、expand、join、paste](#4-字符转换指令trcolexpandjoinpaste) + * [5. 分区指令:split](#5-分区指令split) +* [正规表示法与文件格式化处理](#正规表示法与文件格式化处理) * [grep](#grep) * [printf](#printf) * [awk](#awk) -* [vim ģʽ](#vim-ģʽ) -* [ο](#ο) +* [vim 三个模式](#vim-三个模式) +* [参考资料](#参考资料) -# òԼ -## +# 常用操作以及概念 + +## 求助 **1. --help** -ָĻ÷ѡܡ +指令的基本用法与选项介绍。 **2. man** -man manual дָľϢʾ +man 是 manual 的缩写,将指令的具体信息显示出来。 -ִ man date ʱ DATE(1) ֣еִָͣõּ£ +当执行 man date 时,有 DATE(1) 出现,其中的数字代表指令的类型,常用的数字及其类型如下: -| | | +| 代号 | 类型 | | -- | -- | -| 1 | û shell пԲָ߿ִļ | -| 5 | ļ | -| 8 | ϵͳԱʹõĹָ | +| 1 | 用户在 shell 环境中可以操作的指令或者可执行文件 | +| 5 | 配置文件 | +| 8 | 系统管理员可以使用的管理指令 | **3. info** -info man ƣ info ĵֳһҳ棬ÿҳԽת +info 与 man 类似,但是 info 将文档分成一个个页面,每个页面可以进行跳转。 -## ػ +## 关机 **1. sync** -Ϊ˼ӿԴļĶдٶȣλڴеļݲͬϣ˹ػ֮ǰҪȽ sync ͬ +为了加快对磁盘上文件的读写速度,位于内存中的文件数据不会立即同步到磁盘上,因此关机之前需要先进行 sync 同步操作。 **2. shutdown** ```html -# /sbin/shutdown [-krhc] [ʱ] [ѶϢ] --k ػֻǷ;ѶϢ֪ͨߵû --r ϵͳķͣ --h ϵͳķͣػ --c ȡѾڽе shutdown ָ +# /sbin/shutdown [-krhc] [时间] [警告讯息] +-k : 不会关机,只是发送警告讯息,通知所有在线的用户 +-r : 将系统的服务停掉后就重新启动 +-h : 将系统的服务停掉后就立即关机 +-c : 取消已经在进行的 shutdown 指令内容 ``` -**3. ػָ** +**3. 其它关机指令** -reboothaltpoweroff +reboot、halt、poweroff。 -## 鿴 +## 查看进程 ```html ps aux | grep threadx ``` -## 鿴˿ +## 查看端口 ```html @@ -143,329 +144,329 @@ netstat -anp | grep 80 ## PATH -ڻ PATH ִļ··֮ : ָ +可以在环境变量 PATH 中声明可执行文件的路径,路径之间用 : 分隔。 ```html /usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin ``` -## еȼ +## 运行等级 -- 0ػģʽ -- 1ûģʽƽroot룩 -- 2ֵ֧Ķûģʽ -- 3ֵ֧Ķûģʽıģʽõģʽ -- 4δʹ -- 5ֵ֧ X-windows ûģʽ棩 -- 6ϵͳ +- 0:关机模式 +- 1:单用户模式(可用于破解root密码) +- 2:无网络支持的多用户模式 +- 3:有网络支持的多用户模式(文本模式,工作中最常用的模式) +- 4:保留,未使用 +- 5:有网络支持的 X-windows 多用户模式(桌面) +- 6:重新引导系统,即重启 ## sudo -ʹ sudo һûʹ root ִеֻ /etc/sudoers ļӵûʹøָ +使用 sudo 允许一般用户使用 root 可执行的命令,只有在 /etc/sudoers 配置文件中添加的用户才能使用该指令。 ## GNU -GNU ƻΪūƻĿǴһȫɵIJϵͳΪ GNUȫ GPL ʽ GPL ȫΪ GNU ͨùЭ飬ݣ +GNU 计划,又译为革奴计划,它的目标是创建一套完全自由的操作系统,称为 GNU,其内容软件完全以 GPL 方式发布。其中 GPL 全称为 GNU 通用公共许可协议,包含了以下内容: -- κĿд˳ɣ -- ٸƵɣ -- Ľ˳򣬲Ľɡ +- 以任何目的运行此程序的自由; +- 再复制的自由; +- 改进此程序,并公开发布改进的自由。 -## +## 包管理工具 -RPM DPKG ΪߡRPM ȫΪ Redhat Package Manager Red Hat ˾ƶʵʩ GNU ԴϵͳܲΪܶ Linux ϵͳ (RHEL) ļȶ׼ RPM оǻ Debian ϵͳ (UBUNTU) DEB ߣ DPKGȫΪ Debian Packageܷ RPM ơ +RPM 和 DPKG 为最常见的两类软件包管理工具。RPM 全称为 Redhat Package Manager,最早由 Red Hat 公司制定实施,随后被 GNU 开源操作系统接受并成为很多 Linux 系统 (RHEL) 的既定软件标准。与 RPM 进行竞争的是基于 Debian 操作系统 (UBUNTU) 的 DEB 软件包管理工具- DPKG,全称为 Debian Package,功能方面与 RPM 相似。 -YUM RPM ߣܣĹܡ +YUM 基于 RPM 包管理工具,具有依赖管理功能,并具有软件升级的功能。 -## а汾 +## 常见发行版本 -Linux а Linux ں˼Ӧļɰ汾 +Linux 发行版是 Linux 内核及各种应用软件的集成版本。 -| ڵİ | ҵа | а | +| 基于的包管理工具 | 商业发行版 | 社区发行版 | | --- | --- | --- | | DPKG | Ubuntu | Debian | | RPM | Red Hat | Fedora / CentOS | -# +# 分区 -## ̵ļ +## 磁盘的文件名 -Linux ÿӲһļ +Linux 中每个硬件都被当做一个文件。 -̵ļ +常见磁盘的文件名: -- SCSI/SATA/USB ̣/dev/sd[a-p] -- IDE ̣/dev/hd[a-d] +- SCSI/SATA/USB 磁盘:/dev/sd[a-p] +- IDE 磁盘:/dev/hd[a-d] -ļŵȷ̲˳йأIJλ޹ء +其中文件名后面的序号的确定与磁盘插入的顺序有关,而与磁盘所插入的插槽位置无关。 -## +## 分区表 -̷Ҫָʽһƽ϶ MBR һǽƽٵ GPT +磁盘分区表主要有两种格式,一种是限制较多的 MBR 分区表,一种是较新且限制较少的 GPT 分区表。 ### 1. MBR -MBR УһҪУҪ¼Master boot record, MBRpartition table MBR ռ 446 bytespartition table ռ 64 bytes +MBR 中,第一个扇区最重要,里面有:主要开机记录(Master boot record, MBR)及分区表(partition table),其中 MBR 占 446 bytes,partition table 占 64 bytes。 -ֻ 64 bytesֻܴ洢 4 4 ΪPrimaryչExtendedչֻһռ¼Լ¼ķͨչԷֳ֣ЩΪ߼ +分区表只有 64 bytes,最多只能存储 4 个分区,这 4 个分区为主分区(Primary)和扩展分区(Extended)。其中扩展分区只有一个,它将其它空间用来记录分区表,可以记录更多的分区,因此通过扩展分区可以分出更多区分,这些分区称为逻辑分区。 -Linux ҲѷļļʽΪļ+ţ /dev/sda1ע⣬߼ıŴ 5 ʼ +Linux 也把分区当成文件,分区文件的命名方式为:磁盘文件名+编号,例如 /dev/sda1。注意,逻辑分区的编号从 5 开始。 ### 2. GPT -ͬĴвͬС 512 bytes ´̵ 4kGPT Ϊ˼д̣ڶʹ߼ַLogical Block Address, LBA +不同的磁盘有不同的扇区大小,例如 512 bytes 和最新磁盘的 4k。GPT 为了兼容所有磁盘,在定义扇区上使用逻辑区块地址(Logical Block Address, LBA)。 -GPT 1 ¼ MBR 33 ¼Ϣ 33 ڶԷϢбݡ +GPT 第 1 个区块记录了 MBR,紧接着是 33 个区块记录分区信息,并把最后的 33 个区块用于对分区信息进行备份。 -GPT ûչԷ 128 +GPT 没有扩展分区概念,都是主分区,最多可以分 128 个分区。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/a5c25452-6fa5-49e7-9322-823077442775.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//a5c25452-6fa5-49e7-9322-823077442775.jpg) -## +## 开机检测程序 ### 1. BIOS -BIOS ǿʱִеĵһ֪ԿĴ̣ȡ̵һ MBR MBR ִеĿĻزϵͳĺļ +BIOS 是开机的时候计算机执行的第一个程序,这个程序知道可以开机的磁盘,并读取磁盘第一个扇区的 MBR,由 MBR 执行其中的开机管理程序,这个开机管理程序的会加载操作系统的核心文件。 -MBR еĿṩ¹ܣѡļԼתתܿʵ˶ֻҪһϵͳĿװϣ MBR еĿʱͿѡǰIJϵͳתӶһϵͳ +MBR 中的开机管理程序提供以下功能:选单、载入核心文件以及转交其它开机管理程序。转交这个功能可以用来实现了多重引导,只需要将另一个操作系统的开机管理程序安装在其它分区的启动扇区上,在启动 MBR 中的开机管理程序时,就可以选择启动当前的操作系统或者转交给其它开机管理程序从而启动另一个操作系统。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/f900f266-a323-42b2-bc43-218fdb8811a8.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//f900f266-a323-42b2-bc43-218fdb8811a8.jpg) -װȰװ Windows ٰװ LinuxΪװ Windows ʱḲǵ MBR Linux ѡ񽫿װ MBR ҿÿѡ +安装多重引导,最好先安装 Windows 再安装 Linux。因为安装 Windows 时会覆盖掉 MBR,而 Linux 可以选择将开机管理程序安装在 MBR 或者其它分区的启动扇区,并且可以设置开机管理程序的选单。 ### 2. UEFI -UEFI BIOS ˵ܸΪȫ棬ҲΪȫ +UEFI 相比于 BIOS 来说功能更为全面,也更为安全。 -## +## 挂载 -Ŀ¼ΪĽ㣬Ҳ˵Ŀ¼֮ͿԶȡݡ +挂载利用目录作为分区的进入点,也就是说,进入目录之后就可以读取分区的数据。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/249f3bb1-feee-4805-a259-a72699d638ca.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//249f3bb1-feee-4805-a259-a72699d638ca.jpg) -# ļȨĿ¼ +# 文件权限与目录配置 -## ļȨ޸ +## 文件权限概念 -ûΪ֣ļӵߡȺԼˣԲͬûвͬļȨޡ +把用户分为三种:文件拥有者、群组以及其它人,对不同的用户有不同的文件权限。 -ʹ ls 鿴һļʱʾһļϢ drwxr-xr-x. 3 root root 17 May 6 00:14 .configϢĽ£ +使用 ls 查看一个文件时,会显示一个文件的信息,例如 drwxr-xr-x. 3 root root 17 May 6 00:14 .config,对这个信息的解释如下: -- drwxr-xr-xļԼȨޣ 1 λΪļֶΣ 9 λΪļȨֶΡ -- 3 -- rootļӵߣ -- rootȺ飻 -- 17ļС -- May 6 00:14ļ޸ĵʱ䣻 -- .configļ +- drwxr-xr-x:文件类型以及权限,第 1 位为文件类型字段,后 9 位为文件权限字段。 +- 3:链接数; +- root:文件拥有者; +- root:所属群组; +- 17:文件大小; +- May 6 00:14:文件最后被修改的时间; +- .config:文件名。 -ļͼ京У +常见的文件类型及其含义有: -- dĿ¼ -- -ļ -- lļ +- d:目录; +- -:文件; +- l:链接文件; -9 λļȨֶУÿ 3 Ϊһ飬 3 飬ÿһֱļӵߡȺԼ˵ļȨޡһȨе 3 λֱΪ rwx ȨޣʾɶдִС +9 位的文件权限字段中,每 3 个为一组,共 3 组,每一组分别代表对文件拥有者、所属群组以及其它人的文件权限。一组权限中的 3 位分别为 r、w、x 权限,表示可读、可写、可执行。 -## ļԼȨ޵޸ +## 文件属性以及权限的修改 -### 1. ޸ļȺ +### 1. 修改文件所属群组 ```html # chgrp [-R] groupname dirname/filename --Rݹ޸ +-R:递归修改 ``` -### 2. ޸ļӵ +### 2. 修改文件拥有者 -޸ļӵߣҲ޸ļȺ顣 +不仅可以修改文件拥有者,也可以修改文件所属群组。 ```html -# chown [-R] û:Ⱥ dirname/filename +# chown [-R] 用户名:群组名 dirname/filename ``` -### 3. ޸Ȩ +### 3. 修改权限 -ԽһȨʾʱһȨ޵ 3 λֵλÿλȨֵΪ 421ÿȨ޶ӦȨֵΪ r4w2x1 +可以将一组权限用数字来表示,此时一组权限的 3 个位当做二进制数字的位,从左到右每个位的权值为 4、2、1,即每个权限对应的数字权值为 r:4、w:2、x:1。 ```html # chmod [-R] xyz dirname/filename ``` - .bashrc ļȨ޸Ϊ -rwxr-xr-- +范例:将 .bashrc 文件的权限修改为 -rwxr-xr--。 ```html # chmod 754 .bashrc ``` -Ҳʹ÷趨Ȩޡ +也可以使用符号来设定权限。 ```html # chmod [ugoa] [+-=] [rwx] dirname/filename -- uӵ -- gȺ -- o -- a -- +Ȩ -- -ƳȨ -- =趨Ȩ +- u:拥有者 +- g:所属群组 +- o:其他人 +- a:所有人 +- +:添加权限 +- -:移除权限 +- =:设定权限 ``` -Ϊ .bashrc ļûдȨޡ +范例:为 .bashrc 文件的所有用户添加写权限。 ```html # chmod a+w .bashrc ``` -## Ŀ¼Ȩ +## 目录的权限 -ļǴ洢һļУǴ洢һļڵĿ¼Сˣӵļ w Ȩ޲ܶļ޸ġ +文件名不是存储在一个文件的内容中,而是存储在一个文件所在的目录中。因此,拥有文件的 w 权限并不能对文件名进行修改。 -Ŀ¼洢ļбһĿ¼ȨҲǶļбȨޡˣĿ¼ r ȨޱʾԶȡļбw Ȩޱʾ޸ļб˵ɾļļ޸ģx Ȩ޿øĿ¼ΪĿ¼x Ȩ r w Ȩ޵ĻʹһĿ¼ΪĿ¼Ҳû취ȡļбԼļб޸ˡ +目录存储文件列表,一个目录的权限也就是对其文件列表的权限。因此,目录的 r 权限表示可以读取文件列表;w 权限表示可以修改文件列表,具体来说,就是添加删除文件,对文件名进行修改;x 权限可以让该目录成为工作目录,x 权限是 r 和 w 权限的基础,如果不能使一个目录成为工作目录,也就没办法读取文件列表以及对文件列表进行修改了。 -## ļĬȨ +## 文件默认权限 -ļĬȨޣļĬûпִȨޣΪ 666Ҳ -rw-rw-rw- -Ŀ¼ĬȨޣĿ¼Ҫܹ룬ҲDZӵпִȨޣΪ 777 Ҳ drwxrwxrwx +文件默认权限:文件默认没有可执行权限,因此为 666,也就是 -rw-rw-rw- 。 +目录默认权限:目录必须要能够进入,也就是必须拥有可执行权限,因此为 777 ,也就是 drwxrwxrwx。 -ͨ umask û߲鿴ļĬȨޣͨʽʾ 002 ʾûȨȥһ 2 ȨޣҲдȨޣ˽ļʱĬϵȨΪ -rw-rw-r-- +可以通过 umask 设置或者查看文件的默认权限,通常以掩码的形式来表示,例如 002 表示其它用户的权限去除了一个 2 的权限,也就是写权限,因此建立新文件时默认的权限为 -rw-rw-r-- 。 -## Ŀ¼ +## 目录配置 -Ϊʹͬ Linux а汾Ŀ¼ṹһԣFilesystem Hierarchy Standard (FHS) 涨 Linux Ŀ¼ṹĿ¼£ +为了使不同 Linux 发行版本的目录结构保持一致性,Filesystem Hierarchy Standard (FHS) 规定了 Linux 的目录结构。最基础的三个目录如下: -- / (root, Ŀ¼) -- /usr (unix software resource)ϵͳĬᰲװĿ¼ -- /var (variable)ϵͳйеļ +- / (root, 根目录) +- /usr (unix software resource):所有系统默认软件都会安装到这个目录; +- /var (variable):存放系统或程序运行过程中的数据文件。 -Ŀ¼£ +完整的目录树如下: -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/27ace615-558f-4dfb-8ad4-7ac769c10118.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//27ace615-558f-4dfb-8ad4-7ac769c10118.jpg) -# ļĿ¼ +# 文件与目录 -## ļʱ +## 文件时间 -1. modification time (mtime)ļݸ¾ͻ£ -2. status time (ctime)ļ״̬Ȩޡԣ¾ͻ£ -3. access time (atime)ȡļʱͻ¡ +1. modification time (mtime):文件的内容更新就会更新; +2. status time (ctime):文件的状态(权限、属性)更新就会更新; +3. access time (atime):读取文件时就会更新。 -## ļĿ¼Ļ +## 文件与目录的基本操作 ### 1. ls -гļĿ¼ϢĿ¼Ϣаļ +列出文件或者目录的信息,目录的信息就是其中包含的文件。 ```html # ls [-aAdfFhilnrRSt] file|dir --a гȫļ --d гĿ¼ --l ԳݴгļȨ޵ȵ +-a :列出全部的文件 +-d :仅列出目录本身 +-l :以长数据串行列出,包含文件的属性与权限等等数据 ``` ### 2. cp -Ʋ +复制操作。 -ԴļϣĿļһҪĿ¼С +如果源文件有两个以上,则目的文件一定要是目录才行。 ```html cp [-adfilprsu] source destination --a ൱ -dr --preserve=all ˼ dr ο˵ --d ԴļΪļļԶļ --i ĿļѾʱڸǰѯ --p ͬļһƹȥ --r ݹ --u destination source ɲŸ destination destination ڵ²Ÿ ---preserve=all -p Ȩز⣬ SELinux , links, xattr Ҳ +-a :相当于 -dr --preserve=all 的意思,至于 dr 请参考下列说明 +-d :若来源文件为链接文件,则复制链接文件属性而非文件本身 +-i :若目标文件已经存在时,在覆盖前会先询问 +-p :连同文件的属性一起复制过去 +-r :递归持续复制 +-u :destination 比 source 旧才更新 destination,或 destination 不存在的情况下才复制 +--preserve=all :除了 -p 的权限相关参数外,还加入 SELinux 的属性, links, xattr 等也复制了 ``` ### 3. rm -Ƴ +移除操作。 ```html -# rm [-fir] ļĿ¼ --r ݹɾ +# rm [-fir] 文件或目录 +-r :递归删除 ``` ### 4. mv -ƶ +移动操作。 ```html # mv [-fiu] source destination # mv [options] source1 source2 source3 .... directory --f force ǿƵ˼ĿļѾڣѯʶֱӸ +-f : force 强制的意思,如果目标文件已经存在,不会询问而直接覆盖 ``` -## ȡļ +## 获取文件内容 ### 1. cat -ȡļݡ +取得文件内容。 ```html # cat [-AbEnTv] filename --n ӡкţͬհҲкţ-b +-n :打印出行号,连同空白行也会有行号,-b 不会 ``` ### 2. tac - cat ķһпʼӡ +是 cat 的反向操作,从最后一行开始打印。 ### 3. more -һҳһҳ鿴ļݣı༭ơ +可以一页一页查看文件内容,和文本编辑器类似。 ### 4. less - more ơ +和 more 类似。 ### 5. head -ȡļǰС +可以取得文件前几行。 ```html # head [-n number] filename --n ֣ʾе˼ +-n :后面接数字,代表显示几行的意思 ``` ### 6. tail - head ķֻȡǺС +是 head 的反向操作,只是取得是后几行。 ### 7. od -ַʮƵʽʾļ +可以以字符或者十六进制的形式显示二进制文件。 ### 8. touch -޸ļʱ߽ļ +修改文件时间或者建立新文件。 ```html # touch [-acdmt] filename --a atime --c ctimeļ򲻽ļ --m mtime --d ԽµڶõǰڣҲʹ --date="ڻʱ" --t Խµʱõǰʱ䣬ʽΪ[YYYYMMDDhhmm] +-a : 更新 atime +-c : 更新 ctime,若该文件不存在则不建立新文件 +-m : 更新 mtime +-d : 后面可以接欲更新的日期而不用当前的日期,也可以使用 --date="日期或时间" +-t :后面可以接欲更新的时间而不用当前的时间,格式为[YYYYMMDDhhmm] ``` -## ָļ +## 指令与文件搜索 ### 1. which -ָ +指令搜索。 ```html # which [-a] command --a ָгֻеһ +-a :将所有指令列出,而不是只列第一个 ``` ### 2. whereis -whereis ļٶȱȽϿ죬ΪֻضĿ¼ +whereis 搜索文件的速度比较快,因为它只搜索几个特定的目录。 ```html # whereis [-bmsu] dirname/filename @@ -473,114 +474,114 @@ whereis ### 3. locate -locate ùؼֻʽ +locate 可以用关键字或者正则表达式进行搜索。 -locate ʹ /var/lib/mlocate/ ݿ洢ڴУÿһΣ޷ locate ½ļʹ updatedb ݿ⡣ +locate 使用 /var/lib/mlocate/ 这个数据库来进行搜索,它存储在内存中,并且每天更新一次,所以无法用 locate 搜索新建的文件。可以使用 updatedb 来立即更新数据库。 ```html # locate [-ir] keyword --rʽ +-r:接正则表达式 ``` ### 4. find -find ʹļԺȨ޽ +find 可以使用文件的属性和权限进行搜索。 ```html # find filename [option] ``` -#### 4.1 ʱйصѡ +#### 4.1 与时间有关的选项 ```html --mtime n г n ǰһ޸Ĺݵļ --mtime +n г n ֮ǰ( n 챾)޸Ĺݵļ --mtime -n г n ֮( n 챾)޸Ĺݵļ --newer file г file µļ +-mtime n :列出在 n 天前的那一天修改过内容的文件 +-mtime +n :列出在 n 天之前(不含 n 天本身)修改过内容的文件 +-mtime -n :列出在 n 天之内(含 n 天本身)修改过内容的文件 +-newer file : 列出比 file 更新的文件 ``` -+44 -4 ָʾʱ䷶Χ£ ++4、4 和 -4 的指示的时间范围如下: -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/658fc5e7-79c0-4247-9445-d69bf194c539.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//658fc5e7-79c0-4247-9445-d69bf194c539.png) -#### 4.2 ļӵߺȺйصѡ +#### 4.2 与文件拥有者和所属群组有关的选项 ```html -uid n -gid n -user name -group name --nouser ӵ߲ /etc/passwd ļ --nogroupȺ鲻 /etc/group ļ +-nouser :搜索拥有者不存在 /etc/passwd 的文件 +-nogroup:搜索所属群组不存在于 /etc/group 的文件 ``` -#### 4.3 ļȨ޺йصѡ +#### 4.3 与文件权限和名称有关的选项 ```html -name filename --size [+-]SIZEѰ SIZE Ҫ(+)С(-)ļ SIZE ĹУc: bytek: 1024bytesԣҪұ 50KB Ҫļ -size +50k +-size [+-]SIZE:搜寻比 SIZE 还要大(+)或小(-)的文件。这个 SIZE 的规格有:c: 代表 byte,k: 代表 1024bytes。所以,要找比 50KB 还要大的文件,就是 -size +50k -type TYPE --perm mode Ȩ޵ mode ļ --perm -mode Ȩް mode ļ --perm /mode Ȩްһ mode ļ +-perm mode :搜索权限等于 mode 的文件 +-perm -mode :搜索权限包含 mode 的文件 +-perm /mode :搜索权限包含任一 mode 的文件 ``` -# ļϵͳ +# 磁盘与文件系统 -## ļϵͳ +## 文件系统的组成 -ԷиʽΪڷϽļϵͳһֻܸͨʽΪһļϵͳǴеȼԽһʽΪļϵͳֻļϵͳܱأܱء +对分区进行格式化是为了在分区上建立文件系统。一个分区通常只能格式化为一个文件系统,但是磁盘阵列等技术可以将一个分区格式化为多个文件系统,因此只有文件系统能被挂载,而分区不能被挂载。 -ļϵͳṹ +文件系统有以下三个结构: -1. superblock¼ļϵͳϢ inode block ʹʣԼļϵͳĸʽϢȣ -2. inodeһļռһ inode¼ļԣͬʱ¼ļڵ block 룻 -3. block¼ļݣļ̫ʱռö block +1. superblock:记录文件系统的整体信息,包括 inode 和 block 的总量、使用量、剩余量,以及文件系统的格式与相关信息等; +2. inode:一个文件占用一个 inode,记录文件的属性,同时记录此文件的内容所在的 block 号码; +3. block:记录文件的内容,文件太大时,会占用多个 block。 -Ҫȡһļʱ inode ȥļڵ blockȻ block ݶ +当要读取一个文件的内容时,先在 inode 中去查找文件内容所在的所有 block,然后把所有 block 的内容读出来。 -Ƭָһļڵ block ڷɢ +磁盘碎片是指一个文件内容所在的 block 过于分散。 -Ext2 ļϵͳʹļṹڴ֮ϼ block ȺĸҲǽһļϵͳΪ block Ⱥ飬 +Ext2 文件系统使用了上述的文件结构,并在此之上加入了 block 群组的概念,也就是将一个文件系统划分为多个 block 群组,方便管理。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/1974a836-aa6b-4fb8-bce1-6eb11969284a.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//1974a836-aa6b-4fb8-bce1-6eb11969284a.jpg) ## inode -Ext2 ļϵͳֵ֧ block С 1k2k 4k ֣ͬ block С˵һļĴСÿ inode Сǹ̶Ϊ 128 bytes +Ext2 文件系统支持的 block 大小有 1k、2k 和 4k 三种,不同的 block 大小限制了单一文件的大小。而每个 inode 大小是固定为 128 bytes。 -inode м¼ļڵ blockÿ block dzСһļ㶼Ҫʮ blockһ inode Сޣ޷ֱô block˼ӡ˫ӡáָ inode ¼ block 鵱 inode ¼Ϣ +inode 中记录了文件内容所在的 block,但是每个 block 非常小,一个大文件随便都需要几十万的 block,而一个 inode 大小有限,无法直接引用这么多 block。因此引入了间接、双间接、三间接引用。间接引用是指,让 inode 记录的引用 block 块当成 inode 用来记录引用信息。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/89091427-7b2b-4923-aff6-44681319a8aa.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//89091427-7b2b-4923-aff6-44681319a8aa.jpg) -inode Ϣ +inode 具体包含以下信息: -- ļĴȡģʽ(read/write/excute) -- ļӵȺ(owner/group) -- ļ -- ļ״̬ıʱ(ctime) -- һεĶȡʱ(atime) -- ޸ĵʱ(mtime) -- ļԵ(flag) SetUID... -- ļݵָ (pointer) +- 该文件的存取模式(read/write/excute); +- 该文件的拥有者与群组(owner/group); +- 该文件的容量; +- 该文件建立或状态改变的时间(ctime); +- 最近一次的读取时间(atime); +- 最近修改的时间(mtime); +- 定义文件特性的旗标(flag),如 SetUID...; +- 该文件真正内容的指向 (pointer)。 -## Ŀ¼ inode block +## 目录的 inode 与 block -һĿ¼ʱһ inode һ blockblock ¼Ŀ¼ļ inode ԼļԿļ inode ¼ļļ¼Ŀ¼УļɾļļЩĿ¼ w Ȩйء +建立一个目录时,会分配一个 inode 与至少一个 block。block 记录的内容是目录下所有文件的 inode 编号以及文件名。可以看出文件的 inode 本身不记录文件名,文件名记录在目录中,因此新增文件、删除文件、更改文件名这些操作与目录的 w 权限有关。 -## ʵ +## 实体链接与符号链接 ```html # ln [-sf] source_filename dist_filename --s Ĭ hard link -s Ϊ symbolic link --f ĿļʱɾĿļ +-s :默认是 hard link,加 -s 为 symbolic link +-f :如果目标文件存在时,先删除目标文件 ``` -### 1. ʵ +### 1. 实体链接 -hard link ֻijĿ¼һĿʹĿӵļ inode ϡɾһĿļǴڣֻҪΪ 0 +hard link 只是在某个目录下新增一个条目,使得新增的条目链接到文件的 inode 上。删除任意一个条目,文件还是存在,只要引用数量不为 0。 -ƣܿԽ File SystemܶĿ¼ӡ +有以下限制:不能跨越 File System;不能对目录进行链接。 ```html # ln /etc/crontab . @@ -589,11 +590,11 @@ hard link ֻ 34474855 -rw-r--r--. 2 root root 451 Jun 10 2014 /etc/crontab ``` -### 2. +### 2. 符号链接 -symbolic link Ϊ Windows ĿݷʽͨһļļݵĶȡָӵǸļԴļɾˣļʹ򲻿ˡ +symbolic link 可以理解为 Windows 的快捷方式,通过建立一个独立的文件,这个文件的数据的读取指向链接的那个文件。当源文件被删除了,链接文件就打不开了。 -symbolic link ΪĿ¼ӡ +symbolic link 可以为目录建立链接。 ```html # ll -i /etc/crontab /root/crontab2 @@ -601,121 +602,121 @@ symbolic link 53745909 lrwxrwxrwx. 1 root root 12 Jun 23 22:31 /root/crontab2 -> /etc/crontab ``` -# ѹ +# 压缩与打包 -## ѹ +## 压缩 -Linux кܶѹļչ£ +Linux 底下有很多压缩文件的扩展名,常见的如下: -| չ | ѹ | +| 扩展名 | 压缩程序 | | -- | -- | | \*.Z | compress | |\*.zip | zip | |\*.gz | gzip| |\*.bz2 | bzip2 | |\*.xz | xz | -|\*.tar | tar ݣûоѹ | -|\*.tar.gz | tar ļ gzip ѹ | -|\*.tar.bz2 | tar ļ bzip2 ѹ | -|\*.tar.xz | tar ļ xz ѹ | +|\*.tar | tar 程序打包的数据,没有经过压缩 | +|\*.tar.gz | tar 程序打包的文件,经过 gzip 的压缩 | +|\*.tar.bz2 | tar 程序打包的文件,经过 bzip2 的压缩 | +|\*.tar.xz | tar 程序打包的文件,经过 xz 的压缩 | ### 1. gzip -gzip Linux ʹѹָԽ⿪ compresszip gzip ѹļ +gzip 是 Linux 使用最广的压缩指令,可以解开 compress、zip 与 gzip 所压缩的文件。 - gzip ѹԴļͲˡ +经过 gzip 压缩过,源文件就不存在了。 - 9 ͬѹȼʹá +有 9 个不同的压缩等级可以使用。 -ʹ zcatzmorezless ȡѹļݡ +可以使用 zcat、zmore、zless 来读取压缩文件的内容。 ```html $ gzip [-cdtv#] filename --c ѹĻ --d ѹ --t ѹļǷ --v ʾѹȵϢ --# # Ϊֵ˼ѹȼԽѹԽߣĬΪ6 +-c :将压缩的数据输出到屏幕上 +-d :解压缩 +-t :检验压缩文件是否出错 +-v :显示压缩比等信息 +-# : # 为数字的意思,代表压缩等级,数字越大压缩比越高,默认为6 ``` ### 2. bzip2 -ṩ gzip ߵѹȡ +提供比 gzip 更高的压缩比。 -鿴bzcatbzmorebzlessbzgrep +查看命令:bzcat、bzmore、bzless、bzgrep。 ```html $ bzip2 [-cdkzv#] filename --k Դļ +-k :保留源文件 ``` ### 3. xz -ṩ bzip2 ѵѹȡ +提供比 bzip2 更佳的压缩比。 -Կgzipbzip2xz ѹȲŻҪע⣬ѹԽߣѹʱҲԽ +可以看到,gzip、bzip2、xz 的压缩比不断优化。不过要注意,压缩比越高,压缩的时间也越长。 -鿴xzcatxzmorexzlessxzgrep +查看命令:xzcat、xzmore、xzless、xzgrep。 ```html $ xz [-dtlkc#] filename ``` -## +## 打包 -ѹָֻܶһļѹܹļһļtar ڴҲʹ gipbzip2xz ļѹ +压缩指令只能对一个文件进行压缩,而打包能够将多个文件打包成一个大文件。tar 不仅可以用于打包,也可以使用 gip、bzip2、xz 将打包文件进行压缩。 ```html -$ tar [-z|-j|-J] [cv] [-f ½tarļ] filename... ==ѹ -$ tar [-z|-j|-J] [tv] [-f еtarļ] ==鿴 -$ tar [-z|-j|-J] [xv] [-f еtarļ] [-C Ŀ¼] ==ѹ --z ʹzip --j ʹbzip2 --J ʹxz --c ½ļ --t 鿴ļЩļ --x ѹĹܣ --v ѹ/ѹĹУʾڴļ --f : filenameҪļ --C Ŀ¼ ضĿ¼ѹ +$ tar [-z|-j|-J] [cv] [-f 新建的tar文件] filename... ==打包压缩 +$ tar [-z|-j|-J] [tv] [-f 已有的tar文件] ==查看 +$ tar [-z|-j|-J] [xv] [-f 已有的tar文件] [-C 目录] ==解压缩 +-z :使用zip; +-j :使用bzip2; +-J :使用xz; +-c :新建打包文件; +-t :查看打包文件里面有哪些文件; +-x :解打包或解压缩的功能; +-v :在压缩/解压缩的过程中,显示正在处理的文件名; +-f : filename:要处理的文件; +-C 目录 : 在特定目录解压缩。 ``` -| ʹ÷ʽ | | +| 使用方式 | 命令 | | --- | --- | -| ѹ | tar -jcv -f filename.tar.bz2 ҪѹļĿ¼ | -| | tar -jtv -f filename.tar.bz2 | -| ѹ | tar -jxv -f filename.tar.bz2 -C ҪѹĿ¼ | +| 打包压缩 | tar -jcv -f filename.tar.bz2 要被压缩的文件或目录名称 | +| 查 看 | tar -jtv -f filename.tar.bz2 | +| 解压缩 | tar -jxv -f filename.tar.bz2 -C 要解压缩的目录 | # Bash -ͨ Shell ںṩBash Shell һ֡ +可以通过 Shell 请求内核提供服务,Bash 正是 Shell 的一种。 -## Bash +## Bash 特性 -**1. ʷ** +**1. 命令历史** -¼ʹùε¼ִеʱŵڴУ \~/.bash_history ļм¼ǰһε¼ִй +记录使用过的命令。本次登录所执行的命令都会暂时存放到内存中, \~/.bash_history 文件中记录的是前一次登录所执行过的命令。 -**2. ļȫ** +**2. 命令与文件补全** -ݼtab +快捷键:tab -**3. ** +**3. 命名别名** - lm ls -al ı +例如 lm 是 ls -al 的别名。 **4. shell scripts** -**5. ͨ** +**5. 通配符** - ls -l /usr/bin/X\* г /usr/bin X ͷļ +例如 ls -l /usr/bin/X\* 列出 /usr/bin 下面所有以 X 开头的文件。 -## +## 变量操作 -- һֱֵʹ = -- ԱȡҪڱǰ \$ Ҳ \${} ʽ -- ʹ echo  +- 对一个变量赋值直接使用 = ; +- 对变量取用需要在变量前加上 \{} 的形式; +- 输出变量使用 echo 命令。 ```bash $ var=abc @@ -723,24 +724,24 @@ $ echo $var $ echo ${var} ``` -пոҪʹ˫Żߵš˫ڵַԱԭԣvar="lang is \$LANG" var ֵΪ lang is zh_TW.UTF-8ڵַַ var='lang is \$LANG' var ֵΪ lang is \$LANG +变量内容如果有空格,需要使用双引号或者单引号。双引号内的特殊字符可以保留原本特性,例如var="lang is \(uname -r),则 version 的值为 3.10.0-229.el7.x86_64。 -ʹ export Զתɻӳʹãνӳɵǰ Bash Bash +可以使用 export 命令将自定义变量转成环境变量,环境变量可以在子程序中使用,所谓子程序就是由当前 Bash 而产生的子 Bash。 -Bash ıΪ֡עûиĬַ͡ʹ declare  +Bash 的变量可以声明为数组和整数数字。注意数字类型没有浮点数。如果不进行声明,默认是字符串类型。变量的声明使用 declare 命令: ```html $ declare [-aixr] variable --a Ϊ --i Ϊ --x Ϊ --r Ϊreadonly +-a : 定义为数组类型 +-i : 定义为整数类型 +-x : 定义为环境变量 +-r : 定义为readonly类型 ``` -ʹ [ ] в +使用 [ ] 来对数组进行操作: ```bash $ array[1]=a @@ -748,54 +749,54 @@ $ array[2]=b $ echo ${array[1]} ``` -## ָ˳ +## 指令搜索顺序 -1. ԾԻ·ִָ /bin/ls ./ls -2. ɱҵִָУ -3. Bash ڽִָУ -4. \$PATH ָ·˳ҵһִָС +1. 以绝对或相对路径来执行指令,例如 /bin/ls 或者 ./ls ; +2. 由别名找到该指令来执行; +3. 由 Bash 内建的指令来执行; +4. 按 \$PATH 变量指定的搜索路径的顺序找到第一个指令来执行。 -## ض +## 数据流重定向 -ضʹļ׼롢׼ͱ׼ +重定向就是使用文件代替标准输入、标准输出和标准错误输出。 -1. ׼(stdin)       Ϊ 0 ʹ < << -2. ׼(stdout)     Ϊ 1 ʹ > >> -3. ׼(stderr)Ϊ 2 ʹ 2> 2>> +1. 标准输入(stdin)       :代码为 0 ,使用 < 或 << ; +2. 标准输出(stdout)     :代码为 1 ,使用 > 或 >> ; +3. 标准错误输出(stderr):代码为 2 ,使用 2> 或 2>> ; -УһͷıʾԸǵķʽض򣬶ͷıʾ׷ӵķʽض +其中,有一个箭头的表示以覆盖的方式重定向,而有两个箭头的表示以追加的方式重定向。 -ԽҪı׼Լ׼ض /dev/null൱ӽ䡣 +可以将不需要的标准输出以及标准错误输出重定向到 /dev/null,相当于扔进垃圾箱。 -Ҫ׼Լ׼ͬʱضһļҪijתΪһ 2>&1 ʾ׼תΪ׼ +如果需要将标准输出以及标准错误输出同时重定向到一个文件,需要将某个输出转换为另一个输出,例如 2>&1 表示将标准错误输出转换为标准输出。 ```bash $ find /home -name .bashrc > list 2>&1 ``` -## ָ +## 管线指令 -ǽһı׼Ϊһı׼룬ҪĴ֮ܵõҪĸʽʱͿʹùߡ֮ʹ | ָ +管线是将一个命令的标准输出作为另一个命令的标准输入,在数据需要经过多个步骤的处理之后才能得到我们想要的格式时就可以使用管线。在命令之间使用 | 分隔各个管线命令。 ```bash $ ls -al /etc | less ``` -### 1. ȡָcut +### 1. 提取指令:cut -ȡһһеؽС +提取过程一行一行地进行。 -cut ݽз֣ȡҪIJ֡ +cut 对数据进行切分,取出想要的部分。 ```html $ cut --d ָ --f -d ָʹ -f n ȡ n --c ַΪλȡ +-d :分隔符 +-f :经过 -d 分隔后,使用 -f n 取出第 n 个区间 +-c :以字符为单位取出区间 ``` - 1last ʾĵߵϢҪʾû +范例 1:last 将显示的登入者的信息,要求仅显示用户名。 ```html $ last @@ -806,7 +807,7 @@ root pts/1 192.168.201.254 Thu Feb 5 22:37 - 23:53 (01:16) $ last | cut -d ' ' -f 1 ``` - 2 export ѶϢȡõ 12 ַԺַ +范例 2:将 export 输出的讯息,取得第 12 字符以后的所有字符串。 ```html $ export @@ -814,28 +815,28 @@ declare -x HISTCONTROL="ignoredups" declare -x HISTSIZE="1000" declare -x HOME="/home/dmtsai" declare -x HOSTNAME="study.centos.vbird" -.....(ʡ)..... +.....(其他省略)..... $ export | cut -c 12 ``` -### 2. sortuniq +### 2. 排序命令:sort、uniq -**sort** +**sort** 进行排序。 ```html $ sort [-fbMnrtuk] [file or stdin] --f ԴСд --b ǰĿո --M ·ݵ JANDEC --n ʹ --r --u ൱ uniqueظֻһ --t ָĬΪ tab --k ָ +-f :忽略大小写 +-b :忽略最前面的空格 +-M :以月份的名字来排序,例如 JAN,DEC +-n :使用数字 +-r :反向排序 +-u :相当于 unique,重复的内容只出现一次 +-t :分隔符,默认为 tab +-k :指定排序的区间 ``` -/etc/passwd : ָģԵ +范例:/etc/passwd 内容是以 : 来分隔的,以第三栏来排序。 ```html $ cat /etc/passwd | sort -t ':' -k 3 @@ -845,15 +846,15 @@ alex:x:1001:1002::/home/alex:/bin/bash arod:x:1002:1003::/home/arod:/bin/bash ``` -**uniq** Խظֻȡһ +**uniq** 可以将重复的数据只取一个。 ```html $ uniq [-ic] --i ԴСд --c м +-i :忽略大小写 +-c :进行计数 ``` -ȡÿ˵ĵ¼ܴ +范例:取得每个人的登录总次数 ```html $ last | cut -d ' ' -f 1 | sort | uniq -c @@ -865,88 +866,88 @@ $ last | cut -d ' ' -f 1 | sort | uniq -c 1 wtmp ``` -### 3. ˫ضtee +### 3. 双向输出重定向:tee -ضὫضļУ **tee** ܹܣܱĻϵҲ˵ʹ tee ָһͬʱ͵ļĻϡ +输出重定向会将输出内容重定向到文件中,而 **tee** 不仅能够完成这个功能,还能保留屏幕上的输出。也就是说,使用 tee 指令,一个输出会同时传送到文件和屏幕上。 ```html $ tee [-a] file ``` -### 4. ַתָtrcolexpandjoinpaste +### 4. 字符转换指令:tr、col、expand、join、paste - **tr** ɾһеַ߶ַ滻 + **tr** 用来删除一行中的字符,或者对字符进行替换。 ```html $ tr [-ds] SET1 ... --d ɾ SET1 ַ +-d : 删除行中 SET1 这个字符串 ``` - last ϢСдתΪд +范例,将 last 输出的信息所有小写转换为大写。 ```html $ last | tr '[a-z]' '[A-Z]' ``` - **col** tab ַתΪոַ + **col** 将 tab 字符转为空格字符。 ```html $ col [-xb] --x tab תɶԵȵĿո +-x : 将 tab 键转换成对等的空格键 ``` -**expand** tab תһĿոĬ 8 +**expand** 将 tab 转换一定数量的空格,默认是 8 个。 ```html $ expand [-t] file --t tab תΪո +-t :tab 转为空格的数量 ``` -**join** ͬݵһкϲһ +**join** 将有相同数据的那一行合并在一起。 ```html $ join [-ti12] file1 file2 --t ָĬΪո --i ԴСдIJ --1 һļõıȽֶ --2 ڶļõıȽֶ +-t :分隔符,默认为空格 +-i :忽略大小写的差异 +-1 :第一个文件所用的比较字段 +-2 :第二个文件所用的比较字段 ``` -**paste** ֱӽճһ +**paste** 直接将两行粘贴在一起。 ```html $ paste [-d] file1 file2 --d ָĬΪ tab +-d :分隔符,默认为 tab ``` -### 5. ָsplit +### 5. 分区指令:split -**split** һļֳɶļ +**split** 将一个文件划分成多个文件。 ```html $ split [-bl] file PREFIX --b ԴСзɼӵλ b, k, m --l з -- PREFIX ļǰ +-b :以大小来进行分区,可加单位,例如 b, k, m 等 +-l :以行数来进行分区。 +- PREFIX :分区文件的前导名称 ``` -# ʾļʽ +# 正规表示法与文件格式化处理 ## grep -ʹʾʽƥȡ +使用正则表示式把匹配的行提取出来。 ```html -$ grep [-acinv] [--color=auto] Ѱַ filename --a binary ļ text ļķʽѰ --c ҵ --i ԴСд --n к --v ѡ༴ʾû Ѱַ ݵһ ---color=auto ҵĹؼּɫʾ +$ grep [-acinv] [--color=auto] 搜寻字符串 filename +-a : 将 binary 文件以 text 文件的方式进行搜寻 +-c : 计算找到个数 +-i : 忽略大小写 +-n : 输出行号 +-v : 反向选择,亦即显示出没有 搜寻字符串 内容的那一行 +--color=auto :找到的关键字加颜色显示 ``` -Ѻ the ַȡעĬϻ --color=auto ѡ Linux ɫʾ the ַ +范例:把含有 the 字符串的行提取出来(注意默认会有 --color=auto 选项,因此以下内容在 Linux 中有颜色显示 the 字符串) ```html $ grep -n 'the' regular_express.txt @@ -957,7 +958,7 @@ $ grep -n 'the' regular_express.txt 18:google is the best tools for search keyword ``` -Ϊ { } ķ shell ģ˱Ҫʹʹתַת塣 +因为 { 与 } 的符号在 shell 是有特殊意义的,因此必须要使用使用转义字符进行转义。 ```html $ grep -n 'go\{2,5\}g' regular_express.txt @@ -965,9 +966,9 @@ $ grep -n 'go\{2,5\}g' regular_express.txt ## printf -ڸʽ +用于格式化输出。 -ڹܵڸ printf ʱҪʹ $( ) ʽ +它不属于管道命令,在给 printf 传数据时需要使用 $( ) 形式。 ```html $ printf '%10s %5i %5i %5i %8.2f \n' $(cat printf.txt) @@ -979,12 +980,12 @@ $ printf '%10s %5i %5i %5i %8.2f \n' $(cat printf.txt) ## awk ```html -$ awk '1{1} 2{2} ...' filename +$ awk '条件类型1{动作1} 条件类型2{动作2} ...' filename ``` -awk ÿδһУСλֶΣÿֶεʽΪ\$nn Ϊֶκţ 1 ʼ\$0 ʾһС +awk 每次处理一行,处理的最小单位是字段,每个字段的命名方式为:\0 表示一整行。 - 1ȡ¼ûû ip +范例 1:取出登录用户的用户名和 ip ```html $ last -n 5 @@ -997,15 +998,15 @@ dmtsai tty1 Fri May 29 11:55 - 12:11 (00:15) $ last -n 5 | awk '{print $1 "\t" $3} ``` -awk +awk 变量: -| | | +| 变量名称 | 代表意义 | | -- | -- | -| NF | ÿһӵеֶ | -| NR | Ŀǰǵڼ | -| FS | ĿǰķַָĬǿո | +| NF | 每一行拥有的字段总数 | +| NR | 目前所处理的是第几行数据 | +| FS | 目前的分隔字符,默认是空格键 | - 2ڴкţʾÿһжֶ +范例 2:输出正在处理的行号,并显示每一行有多少字段 ```html $ last -n 5 | awk '{print $1 "\t lines: " NR "\t columns: " NF}' @@ -1016,9 +1017,9 @@ dmtsai lines: 4 columns: 10 dmtsai lines: 5 columns: 9 ``` -ʹôڵ߼еʹ == +可以使用大于等于逻辑,其中等于使用 ==。 - 3/etc/passwd ļֶΪ UID UID С 10 ݽд +范例 3:/etc/passwd 文件第三个字段为 UID,对 UID 小于 10 的数据进行处理。 ```text cat /etc/passwd | awk 'BEGIN {FS=":"} $3 < 10 {print $1 "\t " $3}' @@ -1027,24 +1028,24 @@ bin 1 daemon 2 ``` -# vim ģʽ +# vim 三个模式 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/341c632a-1fc1-4068-9b9f-bf7ef68ebb4c.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//341c632a-1fc1-4068-9b9f-bf7ef68ebb4c.jpg) -ָģʽ£뿪ߴ洢ļ +在指令列模式下,有以下命令用于离开或者存储文件。 -| | | +| 命令 | 作用 | | -- | -- | -| :w | д| -| :w! | ļΪֻʱǿд̡ܲд룬ûԸļȨй | -| :q | 뿪| -| :q! | ǿ뿪| -| :wq | д̺뿪| -| :wq!| ǿд̺뿪| +| :w | 写入磁盘| +| :w! | 当文件为只读时,强制写入磁盘。到底能不能写入,与用户对该文件的权限有关 | +| :q | 离开| +| :q! | 强制离开不保存| +| :wq | 写入磁盘后离开| +| :wq!| 强制写入磁盘后离开| -# ο +# 参考资料 -- . Linux ˽ ƪ [J]. 2009. -- [Linux ƽ̨ϵ](https://www.ibm.com/developerworks/cn/linux/l-cn-rpmdpkg/index.html) +- 鸟哥. 鸟 哥 的 Linux 私 房 菜 基 础 篇 第 三 版[J]. 2009. +- [Linux 平台上的软件包管理](https://www.ibm.com/developerworks/cn/linux/l-cn-rpmdpkg/index.html) diff --git a/notes/MySQL.md b/notes/MySQL.md index 4a86f910..e7feb5e2 100644 --- a/notes/MySQL.md +++ b/notes/MySQL.md @@ -1,226 +1,227 @@ -* [洢](#洢) +* [存储引擎](#存储引擎) * [1. InnoDB](#1-innodb) * [2. MyISAM](#2-myisam) - * [3. InnoDB MyISAM ıȽ](#3-innodb--myisam-ıȽ) -* [](#) - * [1. ](#1-) - * [2. ](#2-) - * [3. ַ](#3-ַ) - * [4. ʱ](#4-ʱ) -* [](#) - * [1. ](#1-) - * [1.1 B-Tree ](#11-b-tree-) - * [1.2 ϣ](#12-ϣ) - * [1.3. ռݣR-Tree](#13-ռr-tree) - * [1.4 ȫ](#14-ȫ) - * [2. ŵ](#2-ŵ) - * [3. Ż](#3-Ż) - * [3.1 ](#31-) - * [3.2 ǰ׺](#32-ǰ׺) - * [3.3 ](#33-) - * [3.4 е˳](#34-е˳) - * [3.5 ۴](#35-۴) - * [3.6 ](#36-) - * [4. B-Tree B+Tree ԭ](#4-b-tree--b+tree-ԭ) + * [3. InnoDB 与 MyISAM 的比较](#3-innodb-与-myisam-的比较) +* [数据类型](#数据类型) + * [1. 整型](#1-整型) + * [2. 浮点数](#2-浮点数) + * [3. 字符串](#3-字符串) + * [4. 时间和日期](#4-时间和日期) +* [索引](#索引) + * [1. 索引分类](#1-索引分类) + * [1.1 B-Tree 索引](#11-b-tree-索引) + * [1.2 哈希索引](#12-哈希索引) + * [1.3. 空间索引数据(R-Tree)](#13-空间索引数据r-tree) + * [1.4 全文索引](#14-全文索引) + * [2. 索引的优点](#2-索引的优点) + * [3. 索引优化](#3-索引优化) + * [3.1 独立的列](#31-独立的列) + * [3.2 前缀索引](#32-前缀索引) + * [3.3 多列索引](#33-多列索引) + * [3.4 索引列的顺序](#34-索引列的顺序) + * [3.5 聚簇索引](#35-聚簇索引) + * [3.6 覆盖索引](#36-覆盖索引) + * [4. B-Tree 和 B+Tree 原理](#4-b-tree-和-b+tree-原理) * [4. 1 B-Tree](#4-1-b-tree) * [4.2 B+Tree](#42-b+tree) - * [4.3 ˳ָ B+Tree](#43-˳ָ-b+tree) - * [4.4 Ϊʲôʹ B-Tree B+Tree](#44-Ϊʲôʹ-b-tree--b+tree) -* [ѯŻ](#ѯŻ) + * [4.3 带有顺序访问指针的 B+Tree](#43-带有顺序访问指针的-b+tree) + * [4.4 为什么使用 B-Tree 和 B+Tree](#44-为什么使用-b-tree-和-b+tree) +* [查询性能优化](#查询性能优化) * [1. Explain](#1-explain) - * [2. ٷص](#2-ٷص) - * [3. ٷص](#3-ٷص) - * [4. ִ DELETE INSERT ](#4-ִ-delete--insert-) -* [ֱֿ](#ֱֿ) -* [תƺ͹ϻָ](#תƺ͹ϻָ) - * [1. ת](#1-ת) - * [2. ϻָ](#2-ϻָ) -* [ο](#ο) + * [2. 减少返回的列](#2-减少返回的列) + * [3. 减少返回的行](#3-减少返回的行) + * [4. 拆分大的 DELETE 或 INSERT 语句](#4-拆分大的-delete-或-insert-语句) +* [分库与分表](#分库与分表) +* [故障转移和故障恢复](#故障转移和故障恢复) + * [1. 故障转移](#1-故障转移) + * [2. 故障恢复](#2-故障恢复) +* [参考资料](#参考资料) -# 洢 + +# 存储引擎 ## 1. InnoDB -InnoDB MySQL Ĭ棬ֻҪ InnoDB ֵ֧ʱſʹ洢档 +InnoDB 是 MySQL 的默认事务型引擎,只有在需要 InnoDB 不支持的特性时,才考虑使用其它存储引擎。 - MVCC ָ֧߲ʵĸ׼ĸ뼶Ĭϼǿظ +采用 MVCC 来支持高并发,并且实现了四个标准的隔离级别,默认级别是可重复读。 -ǻھ۴ģIJѯкܸߵ +表是基于聚簇索引建立的,它对主键的查询性能有很高的提升。 -ڲ˺ܶŻӴ̶ȡʱõĿԤԶܹԶڴд hash ԼٶӦϣԼܹٲIJ뻺ȡ +内部做了很多优化,包括从磁盘读取数据时采用的可预测性读,能够自动在内存中创建 hash 索引以加速读操作的自适应哈希索引,以及能够加速插入操作的插入缓冲区等。 -ͨһЩƺ͹֧ȱݡ +通过一些机制和工具支持真正的热备份。 ## 2. MyISAM -MyISAM ṩ˴ԣȫѹռ亯GISȡ MyISAM ֧мұ޷ȫָ +MyISAM 提供了大量的特性,包括全文索引、压缩、空间函数(GIS)等。但 MyISAM 不支持事务和行级锁,而且奔溃后无法安全恢复。 -ֻܶűС +只能对整张表加锁,而不是针对行。 -ֹԶִм޸ǺָԼָͬܵһЩݶʧ޸Ƿdzġ +可以手工或者自动执行检查和修复操作,但是和事务恢复以及奔溃恢复不同,可能导致一些数据丢失,而且修复操作是非常慢的。 -԰̬߾̬С +可以包含动态或者静态的行。 -ָ DELAY_KEY_WRITE ѡÿ޸ִʱ޸ĵд̣ǻдڴеļֻ߹رձʱŻὫӦдַ̡ʽԼдܣݿʱ𻵣Ҫִ޸ +如果指定了 DELAY_KEY_WRITE 选项,在每次修改执行完成时,不会立即将修改的索引数据写入磁盘,而是会写到内存中的键缓冲区,只有在清理键缓冲区或者关闭表的时候才会将对应的索引块写入磁盘。这种方式可以极大的提升写入性能,但是在数据库或者主机奔溃时会造成索引损坏,需要执行修复操作。 -ڴԺ󣬲ٽ޸IJôıʺϲ MyISAM ѹ +如果表在创建并导入数据以后,不会再进行修改操作,那么这样的表适合采用 MyISAM 压缩表。 -ֻݣ߱ȽС޸ȻԼʹ MyISAM +对于只读数据,或者表比较小、可以容忍修复操作,则依然可以继续使用 MyISAM。 -MyISAM Ƽ򵥣Խܸʽ洢ijЩܺܺá +MyISAM 设计简单,数据以紧密格式存储,所以在某些场景下性能很好。 -## 3. InnoDB MyISAM ıȽ +## 3. InnoDB 与 MyISAM 的比较 -**** +**事务** -InnoDB ͵ġ +InnoDB 是事务型的。 -**** +**备份** -InnoDB ֧ȱݡ +InnoDB 支持在线热备份。 -**ָ** +**奔溃恢复** -MyISAM 𻵵ĸʱ InnoDB ߺܶ࣬һָٶҲ +MyISAM 奔溃后发生损坏的概率比 InnoDB 高很多,而且恢复的速度也更慢。 -**** +**并发** -MyISAM ֱֻ֧ InnoDB ֧м +MyISAM 只支持表级锁,而 InnoDB 还支持行级锁。 -**** +**其它特性** -MyISAM ֧ȫռ +MyISAM 支持全文索引,地理空间索引; -# +# 数据类型 -## 1. +## 1. 整型 -TINYINT, SMALLINT, MEDIUMINT, INT, BIGINT ֱʹ 8, 16, 24, 64 λ洢ռ䣬һԽСԽá +TINYINT, SMALLINT, MEDIUMINT, INT, BIGINT 分别使用 8, 16, 24, 64 位存储空间,一般情况下越小的列越好。 -INT(11) еֻǹ涨˽ʾַĸڴ洢ͼ˵ûġ +INT(11) 中的数字只是规定了交互工具显示字符的个数,对于存储和计算来说是没有意义的。 -## 2. +## 2. 浮点数 -FLOAT DOUBLE ΪͣDECIMAL Ϊ߾С͡CPU ԭָ֧㣬Dz֧ DECIMAl ͵ļ㣬 DECIMAL ļȸҪߵĴۡ +FLOAT 和 DOUBLE 为浮点类型,DECIMAL 为高精度小数类型。CPU 原生支持浮点运算,但是不支持 DECIMAl 类型的计算,因此 DECIMAL 的计算比浮点类型需要更高的代价。 -FLOATDOUBLE DECIMAL ָп DECIMAL(18, 9) ʾܹ 18 λȡ 9 λ洢С֣ʣ 9 λ洢֡ +FLOAT、DOUBLE 和 DECIMAL 都可以指定列宽,例如 DECIMAL(18, 9) 表示总共 18 位,取 9 位存储小数部分,剩下 9 位存储整数部分。 -## 3. ַ +## 3. 字符串 -Ҫ CHAR VARCHAR ͣһǶģһDZ䳤ġ +主要有 CHAR 和 VARCHAR 两种类型,一种是定长的,一种是变长的。 -VARCHAR ֱ䳤ܹʡռ䣬ΪֻҪ洢Ҫݡִ UPDATE ʱܻʹбñԭһҳɵĴСʱҪִжIJMyISAM ὫвɲͬƬδ洢 InnoDB ҪҳʹзŽҳڡ +VARCHAR 这种变长类型能够节省空间,因为只需要存储必要的内容。但是在执行 UPDATE 时可能会使行变得比原来长,当超出一个页所能容纳的大小时,就要执行额外的操作,MyISAM 会将行拆成不同的片段存储,而 InnoDB 则需要分裂页来使行放进页内。 -VARCHAR ᱣַĩβĿո񣬶 CHAR ɾ +VARCHAR 会保留字符串末尾的空格,而 CHAR 会删除。 -## 4. ʱ +## 4. 时间和日期 -MySQL ṩƵʱͣDATATIME TIMESTAMP +MySQL 提供了两种相似的日期时间类型:DATATIME 和 TIMESTAMP。 **DATATIME** -ܹ 1001 굽 9999 ںʱ䣬Ϊ룬ʹ 8 ֽڵĴ洢ռ䡣 +能够保存从 1001 年到 9999 年的日期和时间,精度为秒,使用 8 字节的存储空间。 -ʱ޹ء +它与时区无关。 -Ĭ£MySQL һֿġĸʽʾ DATATIME ֵ硰2008-01016 22:37:08 ANSI ׼ںʱʾ +默认情况下,MySQL 以一种可排序的、无歧义的格式显示 DATATIME 值,例如“2008-01016 22:37:08”,这是 ANSI 标准定义的日期和时间表示方法。 **TIMESTAMP** - UNIX ʱͬ 1970 1 1 ҹʱ䣩ʹ 4 ֽڣֻܱʾ 1970 2038 ꡣ +和 UNIX 时间戳相同,保存从 1970 年 1 月 1 日午夜(格林威治时间)以来的秒数,使用 4 个字节,只能表示从 1970 年 到 2038 年。 -ʱйء +它和时区有关。 -MySQL ṩ FROM_UNIXTIME() Unxi ʱתΪڣṩ UNIX_TIMESTAMP() תΪ Unix ʱ +MySQL 提供了 FROM_UNIXTIME() 函数把 Unxi 时间戳转换为日期,并提供了 UNIX_TIMESTAMP() 函数把日期转换为 Unix 时间戳。 -Ĭ£ʱûָ TIMESTAMP еֵὫֵΪǰʱ䡣 +默认情况下,如果插入时没有指定 TIMESTAMP 列的值,会将这个值设置为当前时间。 -Ӧþʹ TIMESTAMPΪ DATETIME ռЧʸߡ +应该尽量使用 TIMESTAMP,因为它比 DATETIME 空间效率更高。 -# +# 索引 -ڴ洢ʵֵģڷʵֵģԲͬ洢вͬͺʵ֡ +索引是在存储引擎层实现的,而不是在服务器层实现的,所以不同存储引擎具有不同的索引类型和实现。 -ܹ׽ѯ +索引能够轻易将查询性能提升几个数量级。 -ڷdzСı󲿷¼򵥵ȫɨȽЧе͵ıͷdzЧǶش͵ıʹĴ۽֮£ҪõһֱֳּҪѯһݣһ¼һ¼ƥ䣬ʹ÷ +对于非常小的表、大部分情况下简单的全表扫描比建立索引更高效。对于中到大型的表,索引就非常有效。但是对于特大型的表,建立和使用索引的代价将会随之增长。这种情况下,需要用到一种技术可以直接区分出需要查询的一组数据,而不是一条记录一条记录地匹配,例如可以使用分区技术。 -## 1. +## 1. 索引分类 -### 1.1 B-Tree +### 1.1 B-Tree 索引 -B-Tree Ǵ MySQL 洢Ĭ͡ +B-Tree 索引是大多数 MySQL 存储引擎的默认索引类型。 -ΪҪȫɨ裬ֻҪɣ˲ٶȿܶࡣ +因为不再需要进行全表扫描,只需要对树进行搜索即可,因此查找速度快很多。 -ָΪУйͬɼB-Tree ȫֵֵΧͼǰ׺ңмǰ׺ֻǰ׺ҡ +可以指定多个列作为索引列,多个索引列共同组成键。B-Tree 索引适用于全键值、键值范围和键前缀查找,其中键前缀查找只适用于最左前缀查找。 -ڲңͷ顣 +除了用于查找,还可以用于排序和分组。 -ǰе˳вң޷ʹ +如果不是按照索引列的顺序进行查找,则无法使用索引。 -### 1.2 ϣ +### 1.2 哈希索引 -ڹϣʵ֣ŵDzҷdz졣 +基于哈希表实现,优点是查找非常快。 - MySQL ֻ Memory ʽֹ֧ϣ +在 MySQL 中只有 Memory 引擎显式支持哈希索引。 -InnoDB һĹܽСӦϣijֵʹõķdzƵʱ B-Tree ֮ٴһϣ B-Tree йϣһЩŵ㣬ٵĹϣҡ +InnoDB 引擎有一个特殊的功能叫“自适应哈希索引”,当某个索引值被使用的非常频繁时,会在 B-Tree 索引之上再创建一个哈希索引,这样就让 B-Tree 索引具有哈希索引的一些优点,比如快速的哈希查找。 -ƣϣֻϣֵָ룬洢ֵֶԲʹеֵȡСڴееٶȺܿ죬Դ󲿷һӰ첢ԣ޷ڷֻ֧־ȷң޷ڲֲҺͷΧңϣͻܶ࣬ٶȻú +限制:哈希索引只包含哈希值和行指针,而不存储字段值,所以不能使用索引中的值来避免读取行。不过,访问内存中的行的速度很快,所以大部分情况下这一点对性能影响并不明显;无法用于分组与排序;只支持精确查找,无法用于部分查找和范围查找;如果哈希冲突很多,查找速度会变得很慢。 -### 1.3. ռݣR-Tree +### 1.3. 空间索引数据(R-Tree) -MyISAM 洢ֿ֧ռڵݴ洢 +MyISAM 存储引擎支持空间索引,可以用于地理数据存储。 -ռάݣЧʹάϲѯ +空间索引会从所有维度来索引数据,可以有效地使用任意维度来进行组合查询。 -### 1.4 ȫ +### 1.4 全文索引 -MyISAM 洢֧ȫڲıеĹؼʣֱӱȽеֵ +MyISAM 存储引擎支持全文索引,用于查找文本中的关键词,而不是直接比较索引中的值。 -ʹ MATCH AGAINSTͨ WHERE +使用 MATCH AGAINST,而不是普通的 WHERE。 -## 2. ŵ +## 2. 索引的优点 -- ˷Ҫɨ +- 大大减少了服务器需要扫描的数据量; -- ʹʱ +- 帮助服务器避免进行排序和创建临时表; -- I/O Ϊ˳ I/O +- 将随机 I/O 变为顺序 I/O。 -## 3. Ż +## 3. 索引优化 -### 3.1 +### 3.1 独立的列 -ڽвѯʱвDZʽһ֣ҲǺIJ޷ʹ +在进行查询时,索引列不能是表达式的一部分,也不能是函数的参数,否则无法使用索引。 -IJѯʹ actor_id е +例如下面的查询不能使用 actor_id 列的索引: ```sql SELECT actor_id FROM sakila.actor WHERE actor_id + 1 = 5; ``` -### 3.2 ǰ׺ +### 3.2 前缀索引 - BLOBTEXT VARCHAR ͵Уʹǰ׺ֻʼIJַ +对于 BLOB、TEXT 和 VARCHAR 类型的列,必须使用前缀索引,只索引开始的部分字符。 -ǰ׺ȵѡȡҪ **ѡ** ȷظֵͼ¼ıֵѡԽߣѯЧҲԽߡֵΪ 1 ʱÿ¼ΨһӦ +对于前缀长度的选取需要根据 **索引选择性** 来确定:不重复的索引值和记录总数的比值。选择性越高,查询效率也越高。最大值为 1 ,此时每个记录都有唯一的索引与其对应。 -### 3.3 +### 3.3 多列索引 -ҪʹöΪвѯʱʹöʹöܸáУð actor_id file_id Ϊ +在需要使用多个列作为条件进行查询时,使用多列索引比使用多个单列索引性能更好。例如下面的语句中,最好把 actor_id 和 file_id 设置为多列索引。 ```sql SELECT file_id, actor_ id FROM sakila.film_actor WhERE actor_id = 1 OR film_id = 1; ``` -### 3.4 е˳ +### 3.4 索引列的顺序 -ѡǿзǰ棬ʾĽ customer_id ѡԱ staff_id ߣð customer_id зڶǰ档 +让选择性最强的索引列放在前面,例如下面显示的结果中 customer_id 的选择性比 staff_id 更高,因此最好把 customer_id 列放在多列索引的前面。 ```sql SELECT COUNT(DISTINCT staff_id)/COUNT(*) AS staff_id_selectivity, @@ -235,107 +236,107 @@ customer_id_selectivity: 0.0373 COUNT(*): 16049 ``` -### 3.5 ۴ +### 3.5 聚簇索引 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/b9e9ae8c-e216-4c01-b267-a50dbeb98fa4.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//b9e9ae8c-e216-4c01-b267-a50dbeb98fa4.jpg) -۴һͣһݴ洢ʽ +聚簇索引并不是一种索引类型,而是一种数据存储方式。 -۴ءʾкڵļֵܵش洢һInnoDB ľ۴д B-Tree ҶҳС +术语“聚簇”表示数据行和相邻的键值紧密地存储在一起,InnoDB 的聚簇索引的数据行存放在 B-Tree 的叶子页中。 -Ϊ޷дͬĵطһֻһ۴ +因为无法把数据行存放在两个不同的地方,所以一个表只能有一个聚簇索引。 -**ŵ** +**优点** -1. ԰ݱһ𣬼 I/O -2. Ϊݱ B-Tree Уݷʸ졣 +1. 可以把相关数据保存在一起,减少 I/O 操作; +2. 因为数据保存在 B-Tree 中,因此数据访问更快。 -**ȱ** +**缺点** -1. ۴޶ I/O ܼӦõܣȫڴ棬ûҪþ۴ -2. ٶڲ˳򣬰˳ġ -3. ²ۺܸߣΪÿµжƶµλá -4. 뵽ijҳУ洢ὫҳѳҳɸУҳѻᵼ±ռøĴ̿ռ䡣 -5. бȽϡ裬ҳѵݴ洢ʱ۴ܵȫɨٶȱ +1. 聚簇索引最大限度提高了 I/O 密集型应用的性能,但是如果数据全部放在内存,就没必要用聚簇索引。 +2. 插入速度严重依赖于插入顺序,按主键的顺序插入是最快的。 +3. 更新操作代价很高,因为每个被更新的行都会移动到新的位置。 +4. 当插入到某个已满的页中,存储引擎会将该页分裂成两个页面来容纳该行,页分裂会导致表占用更多的磁盘空间。 +5. 如果行比较稀疏,或者由于页分裂导致数据存储不连续时,聚簇索引可能导致全表扫描速度变慢。 -### 3.6 +### 3.6 覆盖索引 -Ҫѯֶεֵ +索引包含所有需要查询的字段的值。 -## 4. B-Tree B+Tree ԭ +## 4. B-Tree 和 B+Tree 原理 ### 4. 1 B-Tree -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/5ed71283-a070-4b21-85ae-f2cbfd6ba6e1.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//5ed71283-a070-4b21-85ae-f2cbfd6ba6e1.jpg) -Ϊ B-Treeȶһݼ¼ΪһԪ [key, data]key Ϊ¼ļdata Ϊݼ¼ key ݡ +为了描述 B-Tree,首先定义一条数据记录为一个二元组 [key, data],key 为记录的键,data 为数据记录除 key 外的数据。 -B-Tree ݽṹ +B-Tree 是满足下列条件的数据结构: -- ҶڵͬȣҲ˵ B-Tree ƽģ -- һڵе key ҷǵݼУ -- ijָ key ֱ keyi keyi+1ҲΪ nullָָڵ key keyi С keyi+1 +- 所有叶节点具有相同的深度,也就是说 B-Tree 是平衡的; +- 一个节点中的 key 从左到右非递减排列; +- 如果某个指针的左右相邻 key 分别是 keyi 和 keyi+1,且不为 null,则该指针指向节点的所有 key 大于 keyi 且小于 keyi+1。 - B-Tree а key ݵ㷨dzֱۣȴӸڵжֲңҵ򷵻ضӦڵ dataӦָָĽڵݹвңֱҵڵҵ null ָ룬ǰ߲ҳɹ߲ʧܡ +在 B-Tree 中按 key 检索数据的算法非常直观:首先从根节点进行二分查找,如果找到则返回对应节点的 data,否则对相应区间的指针指向的节点递归进行查找,直到找到节点或找到 null 指针,前者查找成功,后者查找失败。 -ڲɾµݼ¼ƻ B-Tree ʣڲɾʱҪһѡϲתƵȲԱ B-Tree ʡ +由于插入删除新的数据记录会破坏 B-Tree 的性质,因此在插入删除时,需要对树进行一个分裂、合并、转移等操作以保持 B-Tree 性质。 ### 4.2 B+Tree -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/63cd5b50-d6d8-4df6-8912-ef4a1dd5ba13.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//63cd5b50-d6d8-4df6-8912-ef4a1dd5ba13.jpg) - B-Tree ȣB+Tree ²ͬ㣺 +与 B-Tree 相比,B+Tree 有以下不同点: -- ÿڵָΪ 2d 2d+1 -- ڽڵ㲻洢 dataֻ洢 keyҶӽڵ㲻洢ָ롣 +- 每个节点的指针上限为 2d 而不是 2d+1; +- 内节点不存储 data,只存储 key,叶子节点不存储指针。 -### 4.3 ˳ָ B+Tree +### 4.3 带有顺序访问指针的 B+Tree -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/1ee5f0a5-b8df-43b9-95ab-c516c54ec797.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//1ee5f0a5-b8df-43b9-95ab-c516c54ec797.jpg) -һݿϵͳļϵͳʹõ B+Tree ṹھ B+Tree ϽŻҶӽڵ˳ָ룬ŻĿΪʵܡ +一般在数据库系统或文件系统中使用的 B+Tree 结构都在经典 B+Tree 基础上进行了优化,在叶子节点增加了顺序访问指针,做这个优化的目的是为了提高区间访问的性能。 -### 4.4 Ϊʲôʹ B-Tree B+Tree +### 4.4 为什么使用 B-Tree 和 B+Tree -ݽṹҲʵļϵͳݿϵͳձ B-/+Tree Ϊṹ +红黑树等数据结构也可以用来实现索引,但是文件系统及数据库系统普遍采用 B-/+Tree 作为索引结构。 -ҳǼ洢߼飬Ӳϵͳʹ̴洢ָΪĴСȵĿ飬ÿ洢ΪһҳϵͳУҳôСͨΪ 4kʹҳΪλݡ +页是计算机管理存储器的逻辑块,硬件及操作系统往往将主存和磁盘存储区分割为连续的大小相等的块,每个存储块称为一页(在许多操作系统中,页得大小通常为 4k),主存和磁盘以页为单位交换数据。 -һ˵Ҳܴ󣬲ȫ洢ڴУļʽ洢ĴϡΪ˼ٴ I/OϸȡÿζԤǼѧľֲԭһݱõʱ丽Ҳͨϱʹáݿϵͳ˴ԤԭһڵĴСΪһҳÿڵֻҪһ I/O Ϳȫ롣B-Tree һμҪ h-1 I/Oڵ㳣פڴ棩ӶΪ O(h)=O(logdN)һʵӦУ d Ƿdz֣ͨ 100 h dzСͨ 3ֽṹh ҪĶࡣ߼ϺܽĽڵ㣨ӣϿܺԶ޷þֲԣЧԱ B-Tree ܶࡣ +一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。为了减少磁盘 I/O,磁盘往往不是严格按需读取,而是每次都会预读。这样做的理论依据是计算机科学中著名的局部性原理:当一个数据被用到时,其附近的数据也通常会马上被使用。数据库系统的设计者巧妙利用了磁盘预读原理,将一个节点的大小设为等于一个页,这样每个节点只需要一次 I/O 就可以完全载入。B-Tree 中一次检索最多需要 h-1 次 I/O(根节点常驻内存),渐进复杂度为 O(h)=O(logdN)。一般实际应用中,出度 d 是非常大的数字,通常超过 100,因此 h 非常小(通常不超过 3)。而红黑树这种结构,h 明显要深的多。并且于逻辑上很近的节点(父子)物理上可能很远,无法利用局部性,效率明显比 B-Tree 差很多。 -B+Tree ʺԭڽڵ d йء B+Tree ڽڵȥ data ˿ӵиijȣӵиõܡ +B+Tree 更适合外存索引,原因和内节点出度 d 有关。由于 B+Tree 内节点去掉了 data 域,因此可以拥有更大的出度,拥有更好的性能。 -# ѯŻ +# 查询性能优化 ## 1. Explain - SQL 䣬бȽҪֶУ +用来分析 SQL 语句,分析结果中比较重要的字段有: -- select_type : ѯͣм򵥲ѯϲѯӲѯ +- select_type : 查询类型,有简单查询、联合查询和子查询 -- key : ʹõ +- key : 使用的索引 -- rows : ɨ +- rows : 扫描的行数 -## 2. ٷص +## 2. 减少返回的列 -ѯҪΪ˹ݣ˷ʹ֮⣬ҲʹС +慢查询主要是因为访问了过多数据,除了访问过多行之外,也包括访问过多列。 -òҪʹ SELECT * 䣬ҪҪѡѯС +最好不要使用 SELECT * 语句,要根据需要选择查询的列。 -## 3. ٷص +## 3. 减少返回的行 -ʹ LIMIT ȡҪЩС +最好使用 LIMIT 语句来取出想要的那些行。 -Խȫɨ衣䣬Ҫȫɨ裬ʹֻҪɨ輸м¼ɣʹ Explain ͨ۲ rows ֲֶ졣 +还可以建立索引来减少条件语句的全表扫描。例如对于下面的语句,不适用索引的情况下需要进行全表扫描,而使用索引只需要扫描几行记录即可,使用 Explain 语句可以通过观察 rows 字段来看出这种差异。 ```sql SELECT * FROM sakila.film_actor WHERE film_id = 1; ``` -## 4. ִ DELETE INSERT +## 4. 拆分大的 DELETE 或 INSERT 语句 -һִеĻһסܶݡռ־ľϵͳԴܶСĵҪIJѯ +如果一次性执行的话,可能一次锁住很多数据、占满整个事务日志、耗尽系统资源、阻塞很多小的但重要的查询。 ```sql DELEFT FROM messages WHERE create < DATE_SUB(NOW(), INTERVAL 3 MONTH); @@ -348,74 +349,74 @@ do { } while rows_affected > 0 ``` -# ֱֿ +# 分库与分表 -**1. ֱIJͬ** +**1. 分表与分区的不同** -ֱǽһűֳɶСЩСӵвͬıǽһűݷΪ飬ЩԴ洢ͬһϣҲԴ洢ڲͬĴϣַʽ±Ȼֻһ +分表,就是讲一张表分成多个小表,这些小表拥有不同的表名;而分区是将一张表的数据分为多个区块,这些区块可以存储在同一个磁盘上,也可以存储在不同的磁盘上,这种方式下表仍然只有一个。 -**2. ʹ÷ֱֿԭ** +**2. 使用分库与分表的原因** -ʱҵķչݿеıԽԽ࣬ұеҲԽԽôдĿҲ +随着时间和业务的发展,数据库中的表会越来越多,并且表中的数据量也会越来越大,那么读写操作的开销也会随着增大。 -**3. ֱз** +**3. 垂直切分** -ģ顢ϵг̶Ȼֳ𵽲ͬĿϡ磬ǻὨƷݿ payDBûݿ userDB ȣֱ洢ĿƷйصıûйصı +将表按功能模块、关系密切程度划分出来,部署到不同的库上。例如,我们会建立商品数据库 payDB、用户数据库 userDB 等,分别用来存储项目与商品有关的表和与用户有关的表。 -**4. ˮƽз** +**4. 水平切分** -ѱеݰijֹ洢ṹͬıУ簴 id ɢֵԱȽл֣ +把表中的数据按照某种规则存储到多个结构相同的表中,例如按 id 的散列值、性别等进行划分, -**5. ֱзˮƽзֵѡ** +**5. 垂直切分与水平切分的选择** -ݿеı̫࣬Ŀҵ߼ôֱзѡ +如果数据库中的表太多,并且项目各项业务逻辑清晰,那么垂直切分是首选。 -ݿı࣬ǵܴӦѡˮƽз֡ +如果数据库的表不多,但是单表的数据量很大,应该选择水平切分。 -**6. ˮƽзֵʵַʽ** +**6. 水平切分的实现方式** -򵥵ʹ merge 洢档 +最简单的是使用 merge 存储引擎。 -**7. ֱֿڵ** +**7. 分库与分表存在的问题** -(1) +(1) 事务问题 -ִзֱֿ֮ݴ洢˲ͬĿϣݿѡݿⱾķֲʽȥִ񣬽ܴۣ߰ӦóȥЭƣγɳ߼ϵֻɱ̷ĸ +在执行分库分表之后,由于数据存储到了不同的库上,数据库事务管理出现了困难。如果依赖数据库本身的分布式事务管理功能去执行事务,将付出高昂的性能代价;如果由应用程序去协助控制,形成程序逻辑上的事务,又会造成编程方面的负担。 -(2) +(2) 跨库跨表连接问题 -ִ˷ֱֿ֮ԱὫԭ߼ԺǿݻֵͬıͬĿϡʱӲܵƣ޷λڲֿͬıҲ޷ӷֱȲͬıԭֻҪһβѯܹɵҵҪжβɡ +在执行了分库分表之后,难以避免会将原本逻辑关联性很强的数据划分到不同的表、不同的库上。这时,表的连接操作将受到限制,我们无法连接位于不同分库的表,也无法连接分表粒度不同的表,导致原本只需要一次查询就能够完成的业务需要进行多次才能完成。 -# תƺ͹ϻָ +# 故障转移和故障恢复 -תҲлֹʱл⣬ʹΪ⡣ϻָ˼Ǵӹлָұ֤ݵȷԡ +故障转移也叫做切换,当主库出现故障时就切换到备库,使备库成为主库。故障恢复顾名思义就是从故障中恢复过来,并且保证数据的正确性。 -## 1. ת +## 1. 故障转移 -**1.1 лɫ** +**1.1 提升备库或切换角色** -һ̨Ϊ⣬һ-ƽṹеͱɫ +提升一台备库为主库,或者在一个主-主复制结构中调整主动和被动角色。 -**1.2 IP ַ IP й** +**1.2 虚拟 IP 地址和 IP 托管** -Ϊ MySQL ʵָһ߼ IP ַ MySQL ʵʧЧʱԽ IP ַתƵһ̨ MySQL ϡ +为 MySQL 实例指定一个逻辑 IP 地址,当 MySQL 实例失效时,可以将 IP 地址转移到另一台 MySQL 服务器上。 -**1.3 м** +**1.3 中间件解决方案** -ͨ·ʹõķϡ +通过代理,可以路由流量到可以使用的服务器上。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/fabd5fa0-b75e-48d0-9e2c-31471945ceb9.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//fabd5fa0-b75e-48d0-9e2c-31471945ceb9.jpg) -**1.4 Ӧдת** +**1.4 在应用中处理故障转移** -תϵӦпܵӦñ̫׾ +将故障转移整合到应用中可能导致应用变得太过笨拙。 -## 2. ϻָ +## 2. 故障恢复 -# ο +# 参考资料 -- MySQL -- [MySQL ݽṹ㷨ԭ ](http://blog.codinglabs.org/articles/theory-of-mysql-index.html) -- [MySQL Żȫ ](http://www.runoob.com/w3cnote/mysql-index.html) -- [20+ MySQL ŻѾ ](https://www.jfox.info/20-tiao-mysql-xing-nen-you-hua-de-zui-jia-jing-yan.html) +- 高性能 MySQL +- [MySQL 索引背后的数据结构及算法原理 ](http://blog.codinglabs.org/articles/theory-of-mysql-index.html) +- [MySQL 索引优化全攻略 ](http://www.runoob.com/w3cnote/mysql-index.html) +- [20+ 条 MySQL 性能优化的最佳经验 ](https://www.jfox.info/20-tiao-mysql-xing-nen-you-hua-de-zui-jia-jing-yan.html) diff --git a/notes/SQL 语法.md b/notes/SQL 语法.md index b128542b..eea75f44 100644 --- a/notes/SQL 语法.md +++ b/notes/SQL 语法.md @@ -1,52 +1,53 @@ -* [](#) -* [ѯ](#ѯ) -* [](#) -* [](#) -* [ͨ](#ͨ) -* [ֶ](#ֶ) -* [](#) - * [ı](#ı) - * [ںʱ䴦](#ںʱ䴦) - * [ֵ](#ֵ) - * [](#) -* [](#) -* [Ӳѯ](#Ӳѯ) -* [](#) - * [](#) - * [](#) - * [Ȼ](#Ȼ) - * [](#) -* [ϲѯ](#ϲѯ) -* [](#) -* [](#) -* [ɾ](#ɾ) -* [](#) -* [޸ı](#޸ı) -* [ͼ](#ͼ) -* [洢](#洢) -* [α](#α) -* [](#) -* [](#) -* [ַ](#ַ) -* [Ȩ޹](#Ȩ޹) +* [基础](#基础) +* [查询](#查询) +* [排序](#排序) +* [过滤](#过滤) +* [通配符](#通配符) +* [计算字段](#计算字段) +* [函数](#函数) + * [文本处理](#文本处理) + * [日期和时间处理](#日期和时间处理) + * [数值处理](#数值处理) + * [汇总](#汇总) +* [分组](#分组) +* [子查询](#子查询) +* [连接](#连接) + * [内连接](#内连接) + * [自连接](#自连接) + * [自然连接](#自然连接) + * [外连接](#外连接) +* [组合查询](#组合查询) +* [插入](#插入) +* [更新](#更新) +* [删除](#删除) +* [创建表](#创建表) +* [修改表](#修改表) +* [视图](#视图) +* [存储过程](#存储过程) +* [游标](#游标) +* [触发器](#触发器) +* [事务处理](#事务处理) +* [字符集](#字符集) +* [权限管理](#权限管理) -# -ģʽδ洢洢ʲôԼηֽϢݿͱģʽ +# 基础 -ֵ޸ģҲãʹѾɾֵе +模式:定义了数据如何存储、存储什么样的数据以及数据如何分解等信息,数据库和表都有模式。 -SQLStructured Query Language)׼ SQL ANSI ׼ίԱӶΪ ANSI SQL DBMS Լʵ֣ PL/SQLTransact-SQL ȡ +主键的值不允许修改,也不允许复用(不能使用已经删除的主键值赋给新数据行的主键)。 -# ѯ +SQL(Structured Query Language),标准 SQL 由 ANSI 标准委员会管理,从而称为 ANSI SQL,各个 DBMS 都有自己的实现,如 PL/SQL、Transact-SQL 等。 -SQL 䲻ִСдݿֵǷھ DBMS Լá +# 查询 + +SQL 语句不区分大小写,但是数据库表名、列名和值是否区分依赖于具体的 DBMS 以及配置。 **DISTINCT** -ֵֻͬһΡУҲ˵еֵͬͬ +相同值只会出现一次。它作用于所有列,也就是说所有列的值都相同才算相同。 ```sql SELECT DISTINCT col1, col2 @@ -55,9 +56,9 @@ FROM mytable; **LIMIT** -ƷصһΪʼУ 0 ʼڶΪص +限制返回的行数。可以有两个参数,第一个参数为起始行,从 0 开始;第二个参数为返回的总行数。 -ǰ 5 е SQL +返回前 5 行的 SQL: ```sql SELECT * @@ -71,7 +72,7 @@ FROM mytable LIMIT 0, 5; ``` -ص 3 \~ 5 У +返回第 3 \~ 5 行: ```sql SELECT * @@ -79,22 +80,22 @@ FROM mytable LIMIT 2, 3; ``` -**ע** +**注释** ```sql -# ע +# 注释 SELECT * -FROM mytable -- ע -/* ע1 - ע2 */ +FROM mytable -- 注释 +/* 注释1 + 注释2 */ ``` -# +# 排序 -**ASC**Ĭϣ -**DESC** +**ASC**:升序(默认) +**DESC**:降序 -԰н +可以按多个列进行排序: ```sql SELECT * @@ -102,9 +103,9 @@ FROM mytable ORDER BY col1 DESC, col2 ASC; ``` -# +# 过滤 -ӦòҲԹݣDzڷ˽й˵ݷdz󣬵ͨ紫˺ܶݣӶ˷ +在应用层也可以过滤数据,但是不在服务器端进行过滤的数据非常大,导致通过网络传输了很多多余的数据,从而浪费了网络带宽。 ```sql SELECT * @@ -112,155 +113,155 @@ FROM mytable WHERE col IS NULL; ``` -±ʾ WHERE ӾõIJ +下表显示了 WHERE 子句可用的操作符 -| | ˵ | +| 操作符 | 说明 | | ------------ | ------------ | -| = < > | С | -| <> != | | -| <= !> | Сڵ | -| >= !< | ڵ | -| BETWEEN | ֵ֮ | -| IS NULL | ΪNULLֵ | +| = < > | 等于 小于 大于 | +| <> != | 不等于 | +| <= !> | 小于等于 | +| >= !< | 大于等于 | +| BETWEEN | 在两个值之间 | +| IS NULL | 为NULL值 | -Ӧע⵽NULL 0 ַͬ +应该注意到,NULL 与 0 、空字符串都不同。 -**AND OR** Ӷȴ AND˵һ˱ʽ漰 AND OR ʱӦʹ () ȼ +**AND OR** 用于连接多个过滤条件。优先处理 AND,因此当一个过滤表达式涉及到多个 AND 和 OR 时,应当使用 () 来决定优先级。 -**IN** ƥһֵҲԽһ SELECT Ӿ䣬ӶƥӲѯõһֵ +**IN** 操作符用于匹配一组值,其后也可以接一个 SELECT 子句,从而匹配子查询得到的一组值。 -**NOT** ڷһ +**NOT** 操作符用于否定一个条件。 -# ͨ +# 通配符 -ͨҲڹУֻıֶΡ +通配符也是用在过滤语句中,只能用于文本字段。 -- **%** ƥ >=0 ַ \* +- **%** 匹配 >=0 个任意字符,类似于 \*; -- **\_** ƥ ==1 ַ \. +- **\_** 匹配 ==1 个任意字符,类似于 \.; -- **[ ]** ƥ伯ڵַַ ^ Զз +- **[ ]** 可以匹配集合内的字符,用脱字符 ^ 可以对其进行否定 -ʹ Like ͨƥ䡣 +使用 Like 来进行通配符匹配。 ```sql SELECT * FROM mytable -WHERE col LIKE '[^AB]%' -- ABͷı +WHERE col LIKE '[^AB]%' -- 不以AB开头的任意文本 ``` -Ҫͨͨλڿͷƥdz +不要滥用通配符,通配符位于开头处匹配会非常慢。 -# ֶ +# 计算字段 -ݿݵת͸ʽĹȿͻϿö࣬ת͸ʽٵĻԼͨ +在数据库服务器上完成数据的转换和格式化的工作往往比客户端上快得多,并且转换和格式化后的数据量更少的话可以减少网络通信量。 -ֶͨҪʹ **AS** ȡʱֶΪʽ +计算字段通常需要使用 **AS** 来取别名,否则输出的时候字段名为计算表达式。 ```sql SELECT col1*col2 AS alias FROM mytable ``` -**Concat()** ֶΡݿʹÿոһֵΪпӵĽһЩҪĿոʹ **TRIM()** ȥβո +**Concat()** 用于连接两个字段。许多数据库会使用空格把一个值填充为列宽,因此连接的结果会出现一些不必要的空格,使用 **TRIM()** 可以去除首尾空格。 ```sql SELECT Concat(TRIM(col1), ' (', TRIM(col2), ')') FROM mytable ``` -# +# 函数 - DBMS ĺDzͬģ˲ֲ +各个 DBMS 的函数都是不相同的,因此不可移植。 -## ı +## 文本处理 -| | ˵ | +| 函数 | 说明 | | ------------ | ------------ | -| LEFT() RIGHT() | ߻ұߵַ | -| LOWER() UPPER() | תΪСдߴд | -| LTRIM() RTIM() | ȥ߻ұߵĿո | -| LENGTH() | | -| SUNDEX() | תΪֵ | +| LEFT() RIGHT() | 左边或者右边的字符 | +| LOWER() UPPER() | 转换为小写或者大写 | +| LTRIM() RTIM() | 去除左边或者右边的空格 | +| LENGTH() | 长度 | +| SUNDEX() | 转换为语音值 | -У**SOUNDEX()** ǽһַתΪʾĸģʽ㷨ǸݷĸȽϡ +其中,**SOUNDEX()** 是将一个字符串转换为描述其语音表示的字母数字模式的算法,它是根据发音而不是字母比较。 ```sql SELECT * FROM mytable WHERE SOUNDEX(col1) = SOUNDEX('apple') ``` -## ںʱ䴦 +## 日期和时间处理 -ڸʽYYYY-MM-DD +日期格式:YYYY-MM-DD -ʱʽHH:MM:SS +时间格式:HH:MM:SS -| | ˵ | +|函 数 | 说 明| | --- | --- | -| AddDate() | һڣ졢ܵȣ| -| AddTime() | һʱ䣨ʱֵȣ| -| CurDate() | صǰ | -| CurTime() | صǰʱ | -|Date() |ʱڲ| -|DateDiff() |֮| -|Date_Add() |߶㺯| -|Date_Format() |һʽڻʱ䴮| -|Day()| һڵ| -|DayOfWeek() |һڣضӦڼ| -|Hour() |һʱСʱ| -|Minute() |һʱķӲ| -|Month() |һڵ·ݲ| -|Now() |صǰںʱ| -|Second() |һʱ벿| -|Time() |һʱʱ䲿| -|Year() |һڵݲ| +| AddDate() | 增加一个日期(天、周等)| +| AddTime() | 增加一个时间(时、分等)| +| CurDate() | 返回当前日期 | +| CurTime() | 返回当前时间 | +|Date() |返回日期时间的日期部分| +|DateDiff() |计算两个日期之差| +|Date_Add() |高度灵活的日期运算函数| +|Date_Format() |返回一个格式化的日期或时间串| +|Day()| 返回一个日期的天数部分| +|DayOfWeek() |对于一个日期,返回对应的星期几| +|Hour() |返回一个时间的小时部分| +|Minute() |返回一个时间的分钟部分| +|Month() |返回一个日期的月份部分| +|Now() |返回当前日期和时间| +|Second() |返回一个时间的秒部分| +|Time() |返回一个日期时间的时间部分| +|Year() |返回一个日期的年份部分| ```sql mysql> SELECT NOW(); -> '2017-06-28 14:01:52' ``` -## ֵ +## 数值处理 -| | ˵ | +| 函数 | 说明 | | --- | --- | -| SIN() | | -|COS() | | -| TAN() | | -| ABS() | ֵ | -| SQRT() | ƽ| -| MOD() | | -| EXP() | ָ| -| PI() | Բ| -|RAND() | | +| SIN() | 正弦 | +|COS() | 余弦 | +| TAN() | 正切 | +| ABS() | 绝对值 | +| SQRT() | 平方根| +| MOD() | 余数| +| EXP() | 指数| +| PI() | 圆周率| +|RAND() | 随机数| -## +## 汇总 -| |˵ | +|函 数 |说 明| | --- | --- | -|AVG() |ijеƽֵ| -|COUNT()| ijе| -|MAX()| ijеֵ| -|MIN()| ijеСֵ| -|SUM() |ijֵ֮| +|AVG() |返回某列的平均值| +|COUNT()| 返回某列的行数| +|MAX()| 返回某列的最大值| +|MIN()| 返回某列的最小值| +|SUM() |返回某列值之和| -AVG() NULL С +AVG() 会忽略 NULL 行。 -DISTINCT ؼֵֻֻܲͬ +DISTINCT 关键字会只汇总不同的值。 ```sql SELECT AVG(DISTINCT col1) AS avg_col FROM mytable ``` -# +# 分组 -ǰͬݷͬһС +分组就是把相同的数据放在同一组中。 -Զÿʹûܺдÿƽֵȡ +可以对每组数据使用汇总函数进行处理,例如求每组数的平均值等。 - col 򲢷ݣ +按 col 排序并分组数据: ```sql SELECT col, COUNT(*) AS num @@ -268,7 +269,7 @@ FROM mytable GROUP BY col; ``` -WHERE УHAVING ˷飬йӦˣ +WHERE 过滤行,HAVING 过滤分组,行过滤应当先与分组过滤; ```sql SELECT col, COUNT(*) AS num @@ -278,7 +279,7 @@ GROUP BY col HAVING COUNT(*) >= 2; ``` -GROUP BY ΪֶΣ ORDER BY ҲԾۼֶ +GROUP BY 的排序结果为分组字段,而 ORDER BY 也可以以聚集字段来进行排序。 ```sql SELECT col, COUNT(*) AS num @@ -287,18 +288,18 @@ GROUP BY col ORDER BY num; ``` -涨 +分组规定: -1. GROUP BY Ӿ WHERE Ӿ֮ORDER BY Ӿ֮ǰ -2. ˻ܼ֮⣬SELECT еÿһж GROUP BY Ӿи -3. NULL лᵥΪһ飻 -4. SQL ʵֲ֧ GROUP BY опɱ䳤ȵ͡ +1. GROUP BY 子句出现在 WHERE 子句之后,ORDER BY 子句之前; +2. 除了汇总计算语句之外,SELECT 语句中的每一列都必须在 GROUP BY 子句中给出; +3. NULL 的行会单独分为一组; +4. 大多数 SQL 实现不支持 GROUP BY 列具有可变长度的数据类型。 -# Ӳѯ +# 子查询 -ӲѯֻܷһС +子查询中只能返回一个列。 -ԽӲѯĽΪ WHRER Ĺ +可以将子查询的结果作为 WHRER 语句的过滤条件: ``` SELECT * @@ -307,7 +308,7 @@ WHERE col1 IN (SELECT col2 FROM mytable2); ``` -ԼͻĶӲѯԼÿͻִһΣ +下面的语句可以检索出客户的订单数量。子查询语句会对检索出的每个客户执行一次: ```sql SELECT cust_name, (SELECT COUNT(*) @@ -318,17 +319,17 @@ FROM Customers ORDER BY cust_name; ``` -# +# 连接 -Ӷʹ JOIN ؼ֣ʹ ON +连接用于连接多个表,使用 JOIN 关键字,并且条件语句使用 ON。 -ӿ滻ӲѯұӲѯЧһ졣 +连接可以替换子查询,并且比子查询的效率一般会更快。 - AS ֶκͱȡȡΪ˼ SQL Լͬ +可以用 AS 给列名、计算字段和表名取别名,给表名取别名是为了简化 SQL 语句以及连接相同表。 -## +## 内连接 -ֳƵֵӣʹ INNER JOIN ؼ֡ +内连接又称等值连接,使用 INNER JOIN 关键字。 ``` select a, b, c @@ -336,7 +337,7 @@ from A inner join B on A.key = B.key ``` -Բȷʹ INNER JOINʹͨѯ WHERE нҪӵõֵ +可以不明确使用 INNER JOIN,而使用普通查询并在 WHERE 中将两个表中要连接的列用等值方法连接起来。 ``` select a, b, c @@ -344,15 +345,15 @@ from A, B where A.key = B.key ``` -û·صѿ +在没有条件语句的情况下返回笛卡尔积。 -## +## 自连接 -ӿԿӵһֻ֣ӵıѡ +自连接可以看成内连接的一种,只是连接的表是自身而已。 -һԱԱԱţҪҳ Jim ͬһŵԱ +一张员工表,包含员工姓名和员工所属部门,要找出与 Jim 处在同一部门的所有员工姓名。 -**Ӳѯ汾** +**子查询版本** ``` select name @@ -363,7 +364,7 @@ where department = ( where name = "Jim"); ``` -**Ӱ汾** +**自连接版本** ``` select name @@ -372,24 +373,24 @@ where e1.department = e2.department and e1.name = "Jim"; ``` -һӲѯЧʸߡ +连接一般比子查询的效率高。 -## Ȼ +## 自然连接 -Ȼǰֵͬͨģͬпж +自然连接是把同名列通过等值测试连接起来的,同名列可以有多个。 -ӺȻӵṩӵУȻԶͬУȻӡ +内连接和自然连接的区别:内连接提供连接的列,而自然连接自动连接所有同名列;内连接属于自然连接。 ``` select * from employee natural join department; ``` -## +## 外连接 -ӱûйЩСΪӣԼȫӣӾDZС +外连接保留了没有关联的那些行。分为左外连接,右外连接以及全外连接,左外连接就是保留左表的所有行。 -й˿͵ĶϢûжϢĹ˿͡ +检索所有顾客的订单信息,包括还没有订单信息的顾客。 ``` select Customers.cust_id, Orders.order_num @@ -397,7 +398,7 @@ select Customers.cust_id, Orders.order_num on Customers.cust_id = Orders.curt_id ``` -Ҫͳƹ˿͵Ķʹþۼ +如果需要统计顾客的订单数,使用聚集函数。 ``` select Customers.cust_id, @@ -407,13 +408,13 @@ on Customers.cust_id = Orders.curt_id group by Customers.cust_id ``` -# ϲѯ +# 组合查询 -ʹ **UNION** ѯÿѯͬСʽ߾ۼ +使用 **UNION** 来连接两个查询,每个查询必须包含相同的列、表达式或者聚集函数。 -ĬϻȥͬУҪͬУʹ UNION ALL +默认会去除相同行,如果需要保留相同行,使用 UNION ALL 。 -ֻܰһ ORDER BY Ӿ䣬ұλ +只能包含一个 ORDER BY 子句,并且必须位于语句的最后。 ```sql SELECT col @@ -425,16 +426,16 @@ FROM mytable WHERE col =2; ``` -# +# 插入 -**ͨ** +**普通插入** ```sql INSERT INTO mytable(col1, col2) VALUES(val1, val2); ``` -**** +**插入检索出来的数据** ```sql INSERT INTO mytable1(col1, col2) @@ -442,14 +443,14 @@ SELECT col1, col2 FROM mytable2; ``` -**һݸƵһ±** +**将一个表的内容复制到一个新表** ```sql CREATE TABLE newtable AS SELECT * FROM mytable; ``` -# +# 更新 ```sql UPDATE mytable @@ -457,18 +458,18 @@ SET col = val WHERE id = 1; ``` -# ɾ +# 删除 ```sql DELETE FROM mytable WHERE id = 1; ``` -**TRUNCATE TABLE** ձҲɾС +**TRUNCATE TABLE** 可以清空表,也就是删除所有行。 -ʹøºɾʱһҪ WHERE Ӿ䣬Ȼűݶƻ SELECT вԣֹɾ +使用更新和删除操作时一定要用 WHERE 子句,不然会把整张表的数据都破坏。可以先用 SELECT 语句进行测试,防止错误删除。 -# +# 创建表 ```sql CREATE TABLE mytable ( @@ -479,38 +480,38 @@ CREATE TABLE mytable ( PRIMARY KEY (`id`)); ``` -# ޸ı +# 修改表 -**** +**添加列** ```sql ALTER TABLE mytable ADD col CHAR(20); ``` -**ɾ** +**删除列** ```sql ALTER TABLE mytable DROP COLUMN col; ``` -**ɾ** +**删除表** ```sql DROP TABLE mytable; ``` -# ͼ +# 视图 -ͼıݣҲͲܶͼIJͶͨIJһ +视图是虚拟的表,本身不包含数据,也就不能对其进行索引操作。对视图的操作和对普通表的操作一样。 -ͼºô +视图具有如下好处: -1. 򻯸ӵ SQL 縴ӵ᣻ -2. ֻʹʵʱһݣ -3. ֻͨûͼȨޣ֤ݵİȫԣ -4. ݸʽͱʾ +1. 简化复杂的 SQL 操作,比如复杂的联结; +2. 只使用实际表的一部分数据; +3. 通过只给用户访问视图的权限,保证数据的安全性; +4. 更改数据格式和表示。 ```sql CREATE VIEW myview AS @@ -519,25 +520,25 @@ FROM mytable WHERE col5 = val; ``` -# 洢 +# 存储过程 -洢̿ԿǶһϵ SQL +存储过程可以看成是对一系列 SQL 操作的批处理; -**ʹô洢̵ĺô** +**使用存储过程的好处** -1. ʵַװ˴洢У򵥣Ҳ֤˰ȫԣ -2. Ըô룻 -3. Ԥȱ룬˾кܸߵܡ +1. 把实现封装在了存储过程中,不仅简单,也保证了安全性; +2. 可以复用代码; +3. 由于是预先编译,因此具有很高的性能。 -**洢** +**创建存储过程** -д洢ҪԶָΪ ; Ϊ洢Ҳ˷ֺţ˻ⲿַֺŵǽ﷨ +命令行中创建存储过程需要自定义分隔符,因为命令行是以 ; 为结束符,而存储过程中也包含了分号,因此会错误把这部分分号当成是结束符,造成语法错误。 - inout inout ֲ +包含 in、out 和 inout 三种参数。 -ֵҪ select into 䡣 +给变量赋值都需要用 select into 语句。 -ÿֻܸһֵּ֧ϵIJ +每次只能给一个变量赋值,不支持集合的操作。 ```sql delimiter // @@ -558,18 +559,18 @@ call myprocedure(@ret); select @ret; ``` -# α +# 游标 -ڴ洢ʹαԶһƶ +在存储过程中使用游标可以对一个结果集进行移动遍历。 -αҪڽʽӦãûҪݼен޸ġ +游标主要用于交互式应用,其中用户需要对数据集中的任意行进行浏览和修改。 -**ʹαĸ裺** +**使用游标的四个步骤:** -1. α꣬ûʵʼݣ -2. αꣻ -3. ȡݣ -4. رαꣻ +1. 声明游标,这个过程没有实际检索出数据; +2. 打开游标; +3. 取出数据; +4. 关闭游标; ```sql delimiter // @@ -579,7 +580,7 @@ create procedure myprocedure(out ret int) declare mycursor cursor for select col1 from mytable; - # һcontinue handler sqlstate '02000' ʱִ set done = 1 + # 定义了一个continue handler,当 sqlstate '02000' 这个条件出现时,会执行 set done = 1 declare continue handler for sqlstate '02000' set done = 1; open mycursor; @@ -594,43 +595,43 @@ create procedure myprocedure(out ret int) delimiter ; ``` -# +# 触发器 -ijִʱԶִУDELETEINSERTUPDATE +触发器会在某个表执行以下语句时而自动执行:DELETE、INSERT、UPDATE -ִָ֮ǰ֮ԶִУ֮ǰִʹ BEFORE ؼִ֣֮ʹ AFTER ؼ֡BEFORE ֤; +触发器必须指定在语句执行之前还是之后自动执行,之前执行使用 BEFORE 关键字,之后执行使用 AFTER 关键字。BEFORE 用于数据验证和净化。 -INSERT һΪ NEW +INSERT 触发器包含一个名为 NEW 的虚拟表。 ```sql CREATE TRIGGER mytrigger AFTER INSERT ON mytable FOR EACH ROW SELECT NEW.col; ``` -DELETE һΪ OLD ֻġ +DELETE 触发器包含一个名为 OLD 的虚拟表,并且是只读的。 -UPDATE һΪ NEW һΪ OLD NEW ǿԱ޸ĵأ OLD ֻġ +UPDATE 触发器包含一个名为 NEW 和一个名为 OLD 的虚拟表,其中 NEW 是可以被修改地,而 OLD 是只读的。 -ʹôƸ٣޸ļ¼һűС +可以使用触发器来进行审计跟踪,把修改记录到另外一张表中。 -MySQL ڴʹ CALL ҲDzܵô洢̡ +MySQL 不允许在触发器中使用 CALL 语句 ,也就是不能调用存储过程。 -# +# 事务处理 -**** +**基本术语** -1. transactionָһ SQL 䣻 -2. ˣrollbackָָ SQL Ḷ́ -3. ύcommitָδ洢 SQL дݿ -4. 㣨savepointָõʱռλplaceholderԶˣͬ +1. 事务(transaction)指一组 SQL 语句; +2. 回退(rollback)指撤销指定 SQL 语句的过程; +3. 提交(commit)指将未存储的 SQL 语句结果写入数据库表; +4. 保留点(savepoint)指事务处理中设置的临时占位符(placeholder),你可以对它发布回退(与回退整个事务处理不同)。 -ܻ SELECT 䣬 SELECT Ҳû壻Ҳܻ CRETE DROP 䡣 +不能回退 SELECT 语句,回退 SELECT 语句也没意义;也不能回退 CRETE 和 DROP 语句。 -MySQL ύĬʽύҲÿִһͻύһΡ START TRANSACTION ʱرʽύ COMMIT ROLLBACK ִкԶرգ»ָʽύ +MySQL 的事务提交默认是隐式提交,也就是每执行一条语句就会提交一次。当出现 START TRANSACTION 语句时,会关闭隐式提交;当 COMMIT 或 ROLLBACK 语句执行后,事务会自动关闭,重新恢复隐式提交。 -ͨ autocommit Ϊ 0 ȡԶύֱ autocommit Ϊ 1 Żύautocommit ÿӶԷġ +通过设置 autocommit 为 0 可以取消自动提交,直到 autocommit 被设置为 1 才会提交;autocommit 标记是针对每个连接而不是针对服务器的。 -ûñ㣬ROLLBACK ˵ START TRANSACTION 䴦˱㣬 ROLLBACK ָñ㣬˵ñ㡣 +如果没有设置保留点,ROLLBACK 会回退到 START TRANSACTION 语句处;如果设置了保留点,并且在 ROLLBACK 中指定该保留点,则会回退到该保留点。 ```sql START TRANSACTION @@ -642,15 +643,15 @@ ROLLBACK TO delete1 COMMIT ``` -# ַ +# 字符集 -**** +**基本术语** -1. ַΪĸͷŵļϣ -2. ΪijַԱڲʾ -3. УַָαȽϣҪͷ顣 +1. 字符集为字母和符号的集合; +2. 编码为某个字符集成员的内部表示; +3. 校对字符指定如何比较,主要用于排序和分组。 -˸ַָУ⣬ҲԸָ +除了给表指定字符集和校对外,也可以给列指定: ```sql CREATE TABLE mytable @@ -658,7 +659,7 @@ CREATE TABLE mytable DEFAULT CHARACTER SET hebrew COLLATE hebrew_general_ci; ``` -򡢷ʱָУԣ +可以在排序、分组时指定校对: ```sql SELECT * @@ -666,67 +667,67 @@ FROM mytable ORDER BY col COLLATE latin1_general_ci; ``` -# Ȩ޹ +# 权限管理 -MySQL ˻Ϣ mysql ݿС +MySQL 的账户信息保存在 mysql 这个数据库中。 ```sql USE mysql; SELECT user FROM user; ``` -**˻** +**创建账户** ```sql CREATE USER myuser IDENTIFIED BY 'mypassword'; ``` -´˻ûκȨޡ +新创建的账户没有任何权限。 -**޸˻** +**修改账户名** ```sql RENAME myuser TO newuser; ``` -**ɾ˻** +**删除账户** ```sql DROP USER myuser; ``` -**鿴Ȩ** +**查看权限** ```sql SHOW GRANTS FOR myuser; ``` -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/c73aa08e-a987-43c9-92be-adea4a884c25.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//c73aa08e-a987-43c9-92be-adea4a884c25.png) -˻ username@host ʽ壬username@% ʹõĬ +账户用 username@host 的形式定义,username@% 使用的是默认主机名。 -**Ȩ** +**授予权限** ```sql GRANT SELECT, INSERT ON mydatabase.* TO myuser; ``` -**ɾȨ** +**删除权限** ```sql REVOKE SELECT, INSERT ON mydatabase.* FROM myuser; ``` -GRANT REVOKE ڼϿƷȨޣ +GRANT 和 REVOKE 可在几个层次上控制访问权限: -- ʹ GRANT ALL REVOKE ALL -- ݿ⣬ʹ ON database.\* -- ضıʹ ON database.table -- ضУ -- ضĴ洢̡ +- 整个服务器,使用 GRANT ALL和 REVOKE ALL; +- 整个数据库,使用 ON database.\*; +- 特定的表,使用 ON database.table; +- 特定的列; +- 特定的存储过程。 -**** +**更改密码** -ʹ Password() +必须使用 Password() 函数 ```sql SET PASSWROD FOR myuser = Password('newpassword'); diff --git a/notes/代码可读性.md b/notes/代码可读性.md index 2bf6982e..47af4412 100644 --- a/notes/代码可读性.md +++ b/notes/代码可读性.md @@ -1,90 +1,91 @@ -* [ɶԵҪ](#ɶԵҪ) -* [ֱ뺬](#ֱ뺬) -* [ֲܴ](#ֲܴ) -* [õĴ](#õĴ) -* [дע](#дע) -* [αдע](#αдע) -* [߿Ŀɶ](#߿Ŀɶ) -* [ֳʽ](#ֳʽ) -* [ɶ](#ɶ) -* [ȡ](#ȡ) -* [һֻһ](#һֻһ) -* [ȻԱ](#ȻԱ) -* [ٴ](#ٴ) +* [可读性的重要性](#可读性的重要性) +* [用名字表达代码含义](#用名字表达代码含义) +* [名字不能带来歧义](#名字不能带来歧义) +* [良好的代码风格](#良好的代码风格) +* [编写注释](#编写注释) +* [如何编写注释](#如何编写注释) +* [提高控制流的可读性](#提高控制流的可读性) +* [拆分长表达式](#拆分长表达式) +* [变量与可读性](#变量与可读性) +* [抽取函数](#抽取函数) +* [一次只做一件事](#一次只做一件事) +* [用自然语言表述代码](#用自然语言表述代码) +* [减少代码量](#减少代码量) -# ɶԵҪ -кܴһʱĶ룬ҪĶԼĴ룬ҪĶ˵Ĵ롣ˣɶõĴܹ߱Чʡ +# 可读性的重要性 -ɶõĴôܹãΪԱԸȥ޸ⲿִ룬Ҳ޸ġ +编程有很大一部分时间是在阅读代码,不仅要阅读自己的代码,而且要阅读别人的代码。因此,可读性良好的代码能够大大提高编程效率。 -ֻںΪЧʲſԷɶԣɶǵһλ +可读性良好的代码往往会让代码架构更好,因为程序员更愿意去修改这部分代码,而且也更容易修改。 -# ֱ뺬 +只有在核心领域为了效率才可以放弃可读性,否则可读性是第一位。 -һЩȽбĵʣ +# 用名字表达代码含义 -| | | +一些比较有表达力的单词: + +| 单词 | 可替代单词 | | --- | --- | -| send | deliverdispatchannouncedistributeroute | -| find | searchextractlocaterecover | -| start| launchcreatebeginopen| -| make | createset upbuildgeneratecomposeaddnew | +| send | deliver、dispatch、announce、distribute、route | +| find | search、extract、locate、recover | +| start| launch、create、begin、open| +| make | create、set up、build、generate、compose、add、new | -ʹ ijk Ϊѭֹڼ򵥣user_imember_i ֻбΪѭԽ࣬Խ⣬бĵֿɶԻ +使用 i、j、k 作为循环迭代器的名字过于简单,user_i、member_i 这种名字会更有表达力。因为循环层次越多,代码越难理解,有表达力的迭代器名字可读性会更高 -ΪݴʵϢָбҲ䳤̵ֳ׼ǣԽԽֻڶʹһЩ֡ +为名字添加形容词等信息能让名字更具有表达力,但是名字也会变长。名字长短的准则是:作用域越大,名字越长。因此只有在短作用域才能使用一些简单名字。 -# ֲܴ +# 名字不能带来歧义 -Ҫ˼һ±˻кν᲻ԭĺ塣 +起完名字要思考一下别人会对这个名字有何解读,会不会误解了原本想表达的含义。 - minmax ʾΧ firstlast ʾʿռİΧbeginend ʾʿռųΧ end β +用 min、max 表示数量范围;用 first、last 表示访问空间的包含范围,begin、end 表示访问空间的排除范围,即 end 不包含尾部。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/05907ab4-42c5-4b5e-9388-6617f6c97bea.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//05907ab4-42c5-4b5e-9388-6617f6c97bea.jpg) -ص iscanshouldhas ǰ׺ +布尔相关的命名加上 is、can、should、has 等前缀。 -# õĴ +# 良好的代码风格 -ʵĿк +适当的空行和缩进。 -עͣ +排列整齐的注释: ``` -int a = 1; // ע -int b = 11; // ע -int c = 111; // ע +int a = 1; // 注释 +int b = 11; // 注释 +int c = 111; // 注释 ``` -˳⣬ html ıĸֵӦúͱ html е˳һ£ +语句顺序不能随意,比如与 html 表单相关联的变量的赋值应该和表单在 html 中的顺序一致; -صĴ밴֯һ +把相关的代码按块组织起来放在一起。 -# дע +# 编写注释 -ĶȻע⵽עͣעû̫ãôͻ˷ѴĶʱ䡣ЩֱӿĴ벻ҪдעͣرDzҪΪÿעͣЩ򵥵 getter setter ΪЩдעͷôɶԸ +阅读代码首先会注意到注释,如果注释没太大作用,那么就会浪费代码阅读的时间。那些能直接看出含义的代码不需要写注释,特别是并不需要为每个方法都加上注释,比如那些简单的 getter 和 setter 方法,为这些方法写注释反而让代码可读性更差。 -Ϊע;֣ȡֶдע͡ +不能因为有注释就随便起个名字,而是争取起个好名字而不写注释。 -ע¼õǰ취˼̣Ӷö߸롣 +可以用注释来记录采用当前解决办法的思考过程,从而让读者更容易理解代码。 -עһЩ +注释用来提醒一些特殊情况。 - TODO ǣ +用 TODO 等做标记: -| | ÷ | +| 标记 | 用法 | |---|---| -|TODO| | -|FIXME| ޸ | -|HACH| ֲڵĽ | -|XXX| ΣգҪ | +|TODO| 待做 | +|FIXME| 待修复 | +|HACH| 粗糙的解决方案 | +|XXX| 危险!这里有重要的问题 | -# αдע +# 如何编写注释 -ˣ +尽量简洁明了: ``` // The first String is student's name @@ -97,7 +98,7 @@ Map scoreMap = new HashMap<>(); Map scoreMap = new HashMap<>(); ``` -Ӳ˵ +添加测试用例来说明: ``` //... @@ -107,7 +108,7 @@ int add(int x, int y) { } ``` -ںܸӵĺжÿ֣ +在很复杂的函数调用中对每个参数标上名字: ``` int a = 1; @@ -115,37 +116,37 @@ int b = 2; int num = add(\* x = *\ a, \* y = *\ b); ``` -ʹרҵ̸ϵĽͣģʽ˵롣 +使用专业名词来缩短概念上的解释,比如用设计模式名来说明代码。 -# ߿Ŀɶ +# 提高控制流的可读性 -ʽУDZҲdzһȷ +条件表达式中,左侧是变量,右侧是常数。比如下面第一个语句正确: ``` if(len < 10) if(10 > len) ``` -if / else 䣬߼Ĵ˳Ϊ ߼ ؼ߼ ߼ +if / else 条件语句,逻辑的处理顺序为:① 正逻辑;② 关键逻辑;③ 简单逻辑。 ``` if(a == b) { - // ߼ + // 正逻辑 } else{ - // ߼ + // 反逻辑 } ``` -ֻ߼򵥵ʹ ? : ĿʹգӦòֳ if / else +只有在逻辑简单的情况下使用 ? : 三目运算符来使代码更紧凑,否则应该拆分成 if / else; -do / while ں棬ˣһһЩԻĵطʹ while 档 +do / while 的条件放在后面,不够简单明了,并且会有一些迷惑的地方,最好使用 while 来代替。 -ֻһ goto Ŀ꣬ô goto һܽܣǹڸӵ goto ôɶرӦñʹ goto +如果只有一个 goto 目标,那么 goto 尚且还能接受,但是过于复杂的 goto 会让代码可读性特别差,应该避免使用 goto。 -Ƕ׵ѭУһЩ return ܼǶ׵IJ +在嵌套的循环中,用一些 return 语句往往能减少嵌套的层数。 -# ֳʽ +# 拆分长表达式 -ʽĿɶԺܲһЩͱӶֱʽ +长表达式的可读性很差,可以引入一些解释变量从而拆分表达式: ``` if line.split(':')[0].strip() == "root": @@ -157,7 +158,7 @@ if username == "root": ... ``` -ʹĦһЩ߼ʽ +使用摩根定理简化一些逻辑表达式: ``` if(!a && !b) { @@ -170,9 +171,9 @@ if(a || b) { } ``` -# ɶ +# 变量与可读性 -**ȥ**ѭͨʹ break return Լٿʹá +**去除控制流变量**。在循环中通过使用 break 或者 return 可以减少控制流变量的使用。 ``` boolean done = false; @@ -193,9 +194,9 @@ while(/* condition */) { } ``` -**С**ԽСԽ׶λʹõĵط +**减小变量作用域**。作用域越小,越容易定位到变量所有使用的地方。 -JavaScript ñհС´ submit_form Ǻsubmitted ƺᱻύΡһʵ submitted ȫֱڶʵְ submitted ŵУӶΧ +JavaScript 可以用闭包减小作用域。以下代码中 submit_form 是函数变量,submitted 变量控制函数不会被提交两次。第一个实现中 submitted 是全局变量,第二个实现把 submitted 放到匿名函数中,从而限制了起作用域范围。 ``` submitted = false; @@ -216,16 +217,16 @@ var submit_form = (function() { } submitted = true; } -}()); // () ʹִ +}()); // () 使得外层匿名函数立即执行 ``` -JavaScript û var ıȫֱȫֱԻӦ var +JavaScript 中没有用 var 声明的变量都是全局变量,而全局变量很容易造成迷惑,因此应当总是用 var 来声明变量。 -λӦʹõλ +变量定义的位置应当离它使用的位置最近。 -**ʵ** +**实例解析** -һҳıֶΣ +在一个网页中有以下文本输入字段: ``` @@ -234,7 +235,7 @@ JavaScript ``` -Ҫһַŵһյ input ֶУʼʵ£ +现在要接受一个字符串并把它放到第一个空的 input 字段中,初始实现如下: ``` var setFirstEmptyInput = function(new_alue) { @@ -254,11 +255,11 @@ var setFirstEmptyInput = function(new_alue) { } ``` -ʵ⣺ +以上实现有以下问题: -- found ȥ -- elem -- for ѭ while ѭ +- found 可以去除; +- elem 作用域过大; +- 可以用 for 循环代替 while 循环; ``` var setFirstEmptyInput = function(new_value) { @@ -275,13 +276,13 @@ var setFirstEmptyInput = function(new_value) { }; ``` -# ȡ +# 抽取函数 -ѧǰѴֳСٰЩĽŻһ +工程学就是把大问题拆分成小问题再把这些问题的解决方案放回一起。 -Ӧȷһĸ߲Ŀ꣬ȻڲֱΪĿ깤Ĵ룬ȡŵĺС +首先应该明确一个函数的高层次目标,然后对于不是直接为了这个目标工作的代码,抽取出来放到独立的函数中。 -ԵĴ룺 +介绍性的代码: ``` int findClostElement(int[] arr) { @@ -302,7 +303,7 @@ int findClostElement(int[] arr) { } ``` -ϴѭҪ룬ⲿֲڴ߲Ŀ߲꣬ĿѰСֵ˿԰ⲿִȡĺСҲһĺôУԵвԡԿҵ޸ġ +以上代码中循环部分主要计算距离,这部分不属于代码高层次目标,高层次目标是寻找最小距离的值,因此可以把这部分代替提取到独立的函数中。这样做也带来一个额外的好处有:可以单独进行测试、可以快速找到程序错误并修改。 ``` public int findClostElement(int[] arr) { @@ -319,22 +320,22 @@ public int findClostElement(int[] arr) { } ``` -ǺȡԽԽãȡ࣬ĶʱҪȥֻڵǰҪȥ˽ijһϸڶܹʱȡӺǺõġ +并不是函数抽取的越多越好,如果抽取过多,在阅读代码的时候可能需要不断跳来跳去。只有在当前函数不需要去了解某一块代码细节而能够表达其内容时,把这块代码抽取成子函数才是好的。 -ȡҲڼСࡣ +函数抽取也用于减小代码的冗余。 -# һֻһ +# 一次只做一件事 -ֻһµĴ֪Ҫ£ +只做一件事的代码很容易让人知道其要做的事; -̣г񣻰ÿֵͬĺ߲ͬĶ䡣 +基本流程:列出代码所做的所有任务;把每个任务拆分到不同的函数,或者不同的段落。 -# ȻԱ +# 用自然语言表述代码 -Ȼд߼Ҳα룬Ȼд룬߼ +先用自然语言书写代码逻辑,也就是伪代码,然后再写代码,这样代码逻辑会更清晰。 -# ٴ +# 减少代码量 -Ҫƣ̻кܶ仯Ƶݵõġ +不要过度设计,编码过程会有很多变化,过度设计的内容到最后往往是无用的。 -ñ׼ʵ֡ +多用标准库实现。 diff --git a/notes/剑指 offer 题解.md b/notes/剑指 offer 题解.md index 581ba92a..a2fd402c 100644 --- a/notes/剑指 offer 题解.md +++ b/notes/剑指 offer 题解.md @@ -1,97 +1,98 @@ -* [ڶ ҪĻ֪ʶ](#ڶ-ҪĻ֪ʶ) - * [2. ʵ Singleton](#2-ʵ-singleton) - * [3. ظ](#3-ظ) - * [4. άеIJ](#4-άеIJ) - * [5. 滻ո](#5-滻ո) - * [6. βͷӡ](#6-βͷӡ) - * [7. ؽ](#7-ؽ) - * [8. һ](#8-һ) - * [9. ջʵֶ](#9-ջʵֶ) - * [10.1 쳲](#101-쳲) - * [10.2 ̨](#102-̨) - * [10.3 ̨̬](#103-̨̬) - * [10.4 θ](#104-θ) - * [11. תС](#11-תС) - * [12. е·](#12-е·) - * [13. ˵˶Χ](#13-˵˶Χ) - * [14. ](#14-) - * [15. 1 ĸ](#15--1-ĸ) -* [ Ĵ](#-Ĵ) - * [16. ֵη](#16-ֵη) - * [18.2 ɾظĽ](#182-ɾظĽ) - * [19. ʽƥ](#19-ʽƥ) - * [20. ʾֵַ](#20-ʾֵַ) - * [21. ˳ʹλżǰ](#21-˳ʹλżǰ) - * [22. е k ](#22-е-k-) - * [23. лڽ](#23-лڽ) - * [24. ת](#24-ת) - * [25. ϲ](#25-ϲ) - * [26. ӽṹ](#26-ӽṹ) -* [ ˼·](#-˼·) - * [27. ľ](#27-ľ) - * [28.1 ԳƵĶ](#281-ԳƵĶ) - * [28.2 ƽ](#282-ƽ) - * [29. ˳ʱӡ](#29-˳ʱӡ) - * [30. min ջ](#30--min-ջ) - * [31. ջѹ롢](#31-ջѹ뵯) - * [32.1 ´ӡ](#321-´ӡ) - * [32.3 Ѷӡɶ](#323--Ѷӡɶ) - * [32.3 ֮˳ӡ](#323-֮˳ӡ) - * [33. ĺ](#33-ĺ) - * [34. кΪijһֵ·](#34-кΪijһֵ·) - * [35. ĸ](#35-ĸ) - * [36. ˫](#36-˫) - * [37. л](#37-л) - * [38. ַ](#38-ַ) -* [ ŻʱͿռЧ](#-ŻʱͿռЧ) - * [39. гִһ](#39-гִһ) - * [40. С K ](#40-С-k-) - * [41.1 еλ](#411-еλ) - * [14.2 ַеһظַ](#142-ַеһظַ) - * [42. ](#42-) - * [43. 1 n 1 ֵĴ](#43--1--n--1-ֵĴ) - * [45. ųС](#45-ųС) - * [49. ](#49-) - * [50. һֻһεַλ](#50-һֻһεַλ) - * [51. е](#51-е) - * [52. ĵһ](#52-ĵһ) -* [ еĸ](#-еĸ) - * [53 гֵĴ](#53-гֵĴ) - * [54. ĵ k ](#54-ĵ-k-) - * [55 ](#55-) - * [56. ֻһε](#56-ֻһε) - * [57.1 Ϊ S ](#571-Ϊ-s-) - * [57.2 Ϊ S ](#572-Ϊ-s-) - * [58.1 ת˳](#581-ת˳) - * [58.2 תַ](#582-תַ) - * [59. ڵֵ](#59-ڵֵ) - * [61. ˿˳](#61-˿˳) - * [62. ԲȦʣµ](#62-ԲȦʣµ) - * [63. Ʊ](#63-Ʊ) - * [64. 1+2+3+...+n](#64--1+2+3++n) - * [65. üӼ˳ӷ](#65-üӼ˳ӷ) - * [66. ˻](#66-˻) -* [ ԰](#-԰) - * [67. ַת](#67-ַת) - * [68. ڵ͹](#68-ڵ͹) +* [第二章 面试需要的基础知识](#第二章-面试需要的基础知识) + * [2. 实现 Singleton](#2-实现-singleton) + * [3. 数组中重复的数字](#3-数组中重复的数字) + * [4. 二维数组中的查找](#4-二维数组中的查找) + * [5. 替换空格](#5-替换空格) + * [6. 从尾到头打印链表](#6-从尾到头打印链表) + * [7. 重建二叉树](#7-重建二叉树) + * [8. 二叉树的下一个结点](#8-二叉树的下一个结点) + * [9. 用两个栈实现队列](#9-用两个栈实现队列) + * [10.1 斐波那契数列](#101-斐波那契数列) + * [10.2 跳台阶](#102-跳台阶) + * [10.3 变态跳台阶](#103-变态跳台阶) + * [10.4 矩形覆盖](#104-矩形覆盖) + * [11. 旋转数组的最小数字](#11-旋转数组的最小数字) + * [12. 矩阵中的路径](#12-矩阵中的路径) + * [13. 机器人的运动范围](#13-机器人的运动范围) + * [14. 剪绳子](#14-剪绳子) + * [15. 二进制中 1 的个数](#15-二进制中-1-的个数) +* [第三章 高质量的代码](#第三章-高质量的代码) + * [16. 数值的整数次方](#16-数值的整数次方) + * [18.2 删除链表中重复的结点](#182-删除链表中重复的结点) + * [19. 正则表达式匹配](#19-正则表达式匹配) + * [20. 表示数值的字符串](#20-表示数值的字符串) + * [21. 调整数组顺序使奇数位于偶数前面](#21-调整数组顺序使奇数位于偶数前面) + * [22. 链表中倒数第 k 个结点](#22-链表中倒数第-k-个结点) + * [23. 链表中环的入口结点](#23-链表中环的入口结点) + * [24. 反转链表](#24-反转链表) + * [25. 合并两个排序的链表](#25-合并两个排序的链表) + * [26. 树的子结构](#26-树的子结构) +* [第四章 解决面试题的思路](#第四章-解决面试题的思路) + * [27. 二叉树的镜像](#27-二叉树的镜像) + * [28.1 对称的二叉树](#281-对称的二叉树) + * [28.2 平衡二叉树](#282-平衡二叉树) + * [29. 顺时针打印矩阵](#29-顺时针打印矩阵) + * [30. 包含 min 函数的栈](#30-包含-min-函数的栈) + * [31. 栈的压入、弹出序列](#31-栈的压入弹出序列) + * [32.1 从上往下打印二叉树](#321-从上往下打印二叉树) + * [32.3 把二叉树打印成多行](#323--把二叉树打印成多行) + * [32.3 按之字形顺序打印二叉树](#323-按之字形顺序打印二叉树) + * [33. 二叉搜索树的后序遍历序列](#33-二叉搜索树的后序遍历序列) + * [34. 二叉树中和为某一值的路径](#34-二叉树中和为某一值的路径) + * [35. 复杂链表的复制](#35-复杂链表的复制) + * [36. 二叉搜索树与双向链表](#36-二叉搜索树与双向链表) + * [37. 序列化二叉树](#37-序列化二叉树) + * [38. 字符串的排列](#38-字符串的排列) +* [第五章 优化时间和空间效率](#第五章-优化时间和空间效率) + * [39. 数组中出现次数超过一半的数字](#39-数组中出现次数超过一半的数字) + * [40. 最小的 K 个数](#40-最小的-k-个数) + * [41.1 数据流中的中位数](#411-数据流中的中位数) + * [14.2 字符流中第一个不重复的字符](#142-字符流中第一个不重复的字符) + * [42. 连续子数组的最大和](#42-连续子数组的最大和) + * [43. 从 1 到 n 整数中 1 出现的次数](#43-从-1-到-n-整数中-1-出现的次数) + * [45. 把数组排成最小的数](#45-把数组排成最小的数) + * [49. 丑数](#49-丑数) + * [50. 第一个只出现一次的字符位置](#50-第一个只出现一次的字符位置) + * [51. 数组中的逆序对](#51-数组中的逆序对) + * [52. 两个链表的第一个公共结点](#52-两个链表的第一个公共结点) +* [第六章 面试中的各项能力](#第六章-面试中的各项能力) + * [53 数字在排序数组中出现的次数](#53-数字在排序数组中出现的次数) + * [54. 二叉搜索树的第 k 个结点](#54-二叉搜索树的第-k-个结点) + * [55 二叉树的深度](#55-二叉树的深度) + * [56. 数组中只出现一次的数字](#56-数组中只出现一次的数字) + * [57.1 和为 S 的两个数字](#571-和为-s-的两个数字) + * [57.2 和为 S 的连续正数序列](#572-和为-s-的连续正数序列) + * [58.1 翻转单词顺序列](#581-翻转单词顺序列) + * [58.2 左旋转字符串](#582-左旋转字符串) + * [59. 滑动窗口的最大值](#59-滑动窗口的最大值) + * [61. 扑克牌顺子](#61-扑克牌顺子) + * [62. 圆圈中最后剩下的数](#62-圆圈中最后剩下的数) + * [63. 股票的最大利润](#63-股票的最大利润) + * [64. 求 1+2+3+...+n](#64-求-1+2+3++n) + * [65. 不用加减乘除做加法](#65-不用加减乘除做加法) + * [66. 构建乘积数组](#66-构建乘积数组) +* [第七章 两个面试案例](#第七章-两个面试案例) + * [67. 把字符串转换成整数](#67-把字符串转换成整数) + * [68. 树中两个节点的最低公共祖先](#68-树中两个节点的最低公共祖先) -# ڶ ҪĻ֪ʶ -## 2. ʵ Singleton +# 第二章 面试需要的基础知识 -[ģʽ](https://github.com/CyC2018/InterviewNotes/blob/master/notes/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F.md#%E7%AC%AC-5-%E7%AB%A0-%E5%8D%95%E4%BB%B6%E6%A8%A1%E5%BC%8F) +## 2. 实现 Singleton -## 3. ظ +[单例模式](https://github.com/CyC2018/InterviewNotes/blob/master/notes/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F.md#%E7%AC%AC-5-%E7%AB%A0-%E5%8D%95%E4%BB%B6%E6%A8%A1%E5%BC%8F) -**Ŀ** +## 3. 数组中重复的数字 -һΪ n ֶ 0 n-1 ķΧڡijЩظģ֪мظġҲ֪ÿظΡҳһظ֡磬볤Ϊ 7 {2, 3, 1, 0, 2, 5, 3}ôӦǵһظ 2 +**题目描述** -**˼·** +在一个长度为 n 的数组里的所有数字都在 0 到 n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。例如,如果输入长度为 7 的数组 {2, 3, 1, 0, 2, 5, 3},那么对应的输出是第一个重复的数字 2。 -Ԫ [0, n-1] Χڵ⣬ԽֵΪ i Ԫطŵ i λϡ +**解题思路** + +这种数组元素在 [0, n-1] 范围内的问题,可以将值为 i 的元素放到第 i 个位置上。 ```java public boolean duplicate(int numbers[], int length, int[] duplication) { @@ -115,15 +116,15 @@ private void swap(int[] numbers, int i, int j) { } ``` -## 4. άеIJ +## 4. 二维数组中的查找 -**Ŀ** +**题目描述** -һάУÿһжմҵ˳ÿһжմϵµ˳һһάһжǷи +在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。 -**˼·** +**解题思路** -ϽǿʼҡΪеһߵС±ߵĴˣϽǿʼңͿԸ target ͵ǰԪصĴСϵıке±꣬ӶС䡣 +从右上角开始查找。因为矩阵中的一个数,它左边的数都比它来的小,下边的数都比它来的大。因此,从右上角开始查找,就可以根据 target 和当前元素的大小关系来改变行和列的下标,从而缩小查找区间。 ```java public boolean Find(int target, int [][] array) { @@ -139,25 +140,25 @@ public boolean Find(int target, int [][] array) { } ``` -## 5. 滻ո +## 5. 替换空格 -**Ŀ** +**题目描述** -ʵһһַеĿո滻ɡ%20磬ַΪ We Are Happy. 򾭹滻ַ֮Ϊ We%20Are%20Happy +请实现一个函数,将一个字符串中的空格替换成“%20”。例如,当字符串为 We Are Happy. 则经过替换之后的字符串为 We%20Are%20Happy。 -**ĿҪ** +**题目要求** - O(1) Ŀռ临ӶȺ O(n) Ŀռ临Ӷ⡣ +以 O(1) 的空间复杂度和 O(n) 的空间复杂度来求解。 -**˼·** +**解题思路** -Ӻǰıַ +从后向前改变字符串。 ```java public String replaceSpace(StringBuffer str) { int n = str.length(); for (int i = 0; i < n; i++) { - if (str.charAt(i) == ' ') str.append(" "); // β + if (str.charAt(i) == ' ') str.append(" "); // 尾部填充两个 } int idxOfOriginal = n - 1; @@ -176,9 +177,9 @@ public String replaceSpace(StringBuffer str) { } ``` -## 6. βͷӡ +## 6. 从尾到头打印链表 -Ȼ Collections.reverse() +正向遍历然后调用 Collections.reverse()。 ```java public ArrayList printListFromTailToHead(ListNode listNode) { @@ -192,7 +193,7 @@ public ArrayList printListFromTailToHead(ListNode listNode) { } ``` -ʹ Stack +使用 Stack ```java public ArrayList printListFromTailToHead(ListNode listNode) { @@ -209,7 +210,7 @@ public ArrayList printListFromTailToHead(ListNode listNode) { } ``` -ݹ +递归 ```java public ArrayList printListFromTailToHead(ListNode listNode) { @@ -222,11 +223,11 @@ public ArrayList printListFromTailToHead(ListNode listNode) { } ``` -ʹÿ⺯Ҳʹõݹĵʵ֣ͷ巨Ϊԡ +不使用库函数,并且不使用递归的迭代实现,利用链表的头插法为逆序的特性。 ```java public ArrayList printListFromTailToHead(ListNode listNode) { - ListNode head = new ListNode(-1); // ͷ + ListNode head = new ListNode(-1); // 头结点 ListNode cur = listNode; while (cur != null) { ListNode next = cur.next; @@ -244,11 +245,11 @@ public ArrayList printListFromTailToHead(ListNode listNode) { } ``` -## 7. ؽ +## 7. 重建二叉树 -**Ŀ** +**题目描述** -ݶǰĽؽö +根据二叉树的前序遍历和中序遍历的结果,重建出该二叉树。 ```java public TreeNode reConstructBinaryTree(int[] pre, int[] in) { @@ -268,18 +269,18 @@ private TreeNode reConstructBinaryTree(int[] pre, int preL, int preR, int[] in, } ``` -## 8. һ +## 8. 二叉树的下一个结点 -**Ŀ** +**题目描述** -һеһ㣬ҳ˳һ㲢ҷءע⣬еĽ㲻ӽ㣬ͬʱָ򸸽ָ롣 +给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。 -**˼·** +**解题思路** -- һڵΪգôýڵһڵڵ㣻 -- ҵһָýڵȽڵ㡣 +- 如果一个节点有右子树不为空,那么该节点的下一个节点是右子树的最左节点; +- 否则,向上找第一个左链接指向的树包含该节点的祖先节点。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/6fec7f56-a685-4232-b03e-c92a8dfba486.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//6fec7f56-a685-4232-b03e-c92a8dfba486.png) ```java public TreeLinkNode GetNext(TreeLinkNode pNode) { @@ -300,11 +301,11 @@ public TreeLinkNode GetNext(TreeLinkNode pNode) { } ``` -## 9. ջʵֶ +## 9. 用两个栈实现队列 -**˼·** +**解题思路** -ӵջе˳ᱻתηתôõȻġˣӵҪͬʱѹջ֮ܳջܱ֤ջ˳ΪȽȳ +添加到栈中的序列顺序会被反转,如果进行两次反转,那么得到的序列依然是正向的。因此,添加的数据需要同时压入两个栈之后才能出栈,这样就能保证出栈的顺序为先进先出。 ```java Stack stack1 = new Stack(); @@ -324,7 +325,7 @@ public int pop() { } ``` -## 10.1 쳲 +## 10.1 斐波那契数列 ```java private int[] fib = new int[40]; @@ -342,7 +343,7 @@ public int Fibonacci(int n) { } ``` -## 10.2 ̨ +## 10.2 跳台阶 ```java public int JumpFloor(int target) { @@ -357,7 +358,7 @@ public int JumpFloor(int target) { } ``` -## 10.3 ̨̬ +## 10.3 变态跳台阶 ```java public int JumpFloorII(int target) { @@ -372,11 +373,11 @@ public int JumpFloorII(int target) { } ``` -## 10.4 θ +## 10.4 矩形覆盖 -**Ŀ** +**题目描述** -ǿ 2\*1 СκŻȥǸľΡ n 2\*1 Сصظһ 2\*n ĴΣܹжַ +我们可以用 2\*1 的小矩形横着或者竖着去覆盖更大的矩形。请问用 n 个 2\*1 的小矩形无重叠地覆盖一个 2\*n 的大矩形,总共有多少种方法? ```java public int RectCover(int target) { @@ -385,14 +386,14 @@ public int RectCover(int target) { } ``` -## 11. תС +## 11. 旋转数组的最小数字 -**Ŀ** +**题目描述** -һʼɸԪذᵽĩβdz֮ΪתһǵݼһתתСԪء {3, 4, 5, 1, 2} Ϊ {1, 2, 3, 4, 5} һתСֵΪ 1NOTEԪض 0СΪ 0뷵 0 +把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。例如数组 {3, 4, 5, 1, 2} 为 {1, 2, 3, 4, 5} 的一个旋转,该数组的最小值为 1。NOTE:给出的所有元素都大于 0,若数组大小为 0,请返回 0。 -O(N) ʱ临ӶȽⷨ +O(N) 时间复杂度解法: ```java public int minNumberInRotateArray(int[] array) { @@ -404,7 +405,7 @@ public int minNumberInRotateArray(int[] array) { } ``` -O(lgN) ʱ临ӶȽⷨ +O(lgN) 时间复杂度解法: ```java public int minNumberInRotateArray(int[] array) { @@ -421,11 +422,11 @@ public int minNumberInRotateArray(int[] array) { } ``` -## 12. е· +## 12. 矩阵中的路径 -**Ŀ** +**题目描述** -һжһǷһijַַ··ԴӾеһӿʼÿһھңϣƶһӡһ·˾еijһӣ·ٽøӡ a b c e s f c s a d e e аһַ "bcced" ·Ǿв "abcb" ·Ϊַĵһַ b ռ˾еĵһеڶ֮·ٴνøӡ +请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。例如 a b c e s f c s a d e e 矩阵中包含一条字符串 "bcced" 的路径,但是矩阵中不包含 "abcb" 路径,因为字符串的第一个字符 b 占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。 ```java private int[][] next = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}}; @@ -436,7 +437,7 @@ public boolean hasPath(char[] matrix, int rows, int cols, char[] str) { if (rows == 0 || cols == 0) return false; this.rows = rows; this.cols = cols; - // һάؽά + // 一维数组重建二维矩阵 char[][] newMatrix = new char[rows][cols]; for (int i = 0, idx = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { @@ -467,11 +468,11 @@ private boolean backtracking(char[][] matrix, char[] str, boolean[][] used, int ``` -## 13. ˵˶Χ +## 13. 机器人的运动范围 -**Ŀ** +**题目描述** -һ m к n еķһ˴ (0,0) ĸӿʼƶÿһֻĸƶһ񣬵Dzܽλ֮ʹ k ĸӡ磬 k Ϊ 18 ʱܹ뷽35,37Ϊ 3+5+3+7=18ǣܽ뷽35,38Ϊ 3+5+3+8=19ʸûܹﵽٸӣ +地上有一个 m 行和 n 列的方格。一个机器人从坐标 (0,0) 的格子开始移动,每一次只能向左右上下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于 k 的格子。例如,当 k 为 18 时,机器人能够进入方格(35,37),因为 3+5+3+7=18。但是,它不能进入方格(35,38),因为 3+5+3+8=19。请问该机器人能够达到多少个格子? ```java private int cnt = 0; @@ -513,19 +514,19 @@ private void initDigitSum(int rows, int cols) { } ``` -## 14. +## 14. 剪绳子 -**Ŀ** +**题目描述** -һӼɶΣʹÿεijȳ˻ +把一根绳子剪成多段,并且使得每段的长度乘积最大。 -**̬滮ⷨ** +**动态规划解法** -[ָ](https://github.com/CyC2018/InterviewNotes/blob/master/notes/Leetcode%20%E9%A2%98%E8%A7%A3.md#%E5%88%86%E5%89%B2%E6%95%B4%E6%95%B0) +[分割整数](https://github.com/CyC2018/InterviewNotes/blob/master/notes/Leetcode%20%E9%A2%98%E8%A7%A3.md#%E5%88%86%E5%89%B2%E6%95%B4%E6%95%B0) -**̰Ľⷨ** +**贪心解法** -ܶüΪ 3 ӣҲгΪ 1 ӳ֣ˣʹѾкóΪ 3 óһ볤Ϊ 1 ϣгγΪ 2 ӡ +尽可能多得剪长度为 3 的绳子,并且不允许有长度为 1 的绳子出现,如果出现了,就从已经切好长度为 3 的绳子中拿出一段与长度为 1 的绳子重新组合,把它们切成两段长度为 2 的绳子。 ```java int maxProductAfterCuttin(int length) { @@ -539,9 +540,9 @@ int maxProductAfterCuttin(int length) { } ``` -## 15. 1 ĸ +## 15. 二进制中 1 的个数 -ʹÿ⺯ +使用库函数: ```java public int NumberOf1(int n) { @@ -549,9 +550,9 @@ public int NumberOf1(int n) { } ``` -O(lgM) ʱ临ӶȽⷨ M ʾ 1 ĸ +O(lgM) 时间复杂度解法,其中 M 表示 1 的个数: -n&(n-1) λȥ n λʾ͵һλڶƱʾ 10110100ȥ 1 õ 10110011õ 10110000 +n&(n-1) 该位运算是去除 n 的位级表示中最低的那一位。例如对于二进制表示 10110100,减去 1 得到 10110011,这两个数相与得到 10110000。 ```java public int NumberOf1(int n) { @@ -564,9 +565,9 @@ public int NumberOf1(int n) { } ``` -# Ĵ +# 第三章 高质量的代码 -## 16. ֵη +## 16. 数值的整数次方 ```java public double Power(double base, int exponent) { @@ -583,7 +584,7 @@ public double Power(double base, int exponent) { } ``` -## 18.2 ɾظĽ +## 18.2 删除链表中重复的结点 ```java public ListNode deleteDuplication(ListNode pHead) { @@ -602,11 +603,11 @@ public ListNode deleteDuplication(ListNode pHead) { } ``` -## 19. ʽƥ +## 19. 正则表达式匹配 -**Ŀ** +**题目描述** -ʵһƥ '.' '\*' ʽģʽеַ '.' ʾһַ '\*' ʾǰַԳΣ 0 Σ ڱУƥַַָƥģʽ磬ַ "aaa" ģʽ "a.a" "ab\*ac\*a" ƥ䣬 "aa.a" "ab\*a" ƥ +请实现一个函数用来匹配包括 '.' 和 '\*' 的正则表达式。模式中的字符 '.' 表示任意一个字符,而 '\*' 表示它前面的字符可以出现任意次(包含 0 次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串 "aaa" 与模式 "a.a" 和 "ab\*ac\*a" 匹配,但是与 "aa.a" 和 "ab\*a" 均不匹配 ```java public boolean match(char[] str, char[] pattern) { @@ -629,11 +630,11 @@ public boolean match(char[] str, char[] pattern) { } ``` -## 20. ʾֵַ +## 20. 表示数值的字符串 -**Ŀ** +**题目描述** -ʵһжַǷʾֵС磬ַ "+100","5e2","-123","3.1416" "-1E-16" ʾֵ "12e","1a3.14","1.2.3","+-5" "12e+4.3" ǡ +请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串 "+100","5e2","-123","3.1416" 和 "-1E-16" 都表示数值。 但是 "12e","1a3.14","1.2.3","+-5" 和 "12e+4.3" 都不是。 ```java public boolean isNumeric(char[] str) { @@ -642,14 +643,14 @@ public boolean isNumeric(char[] str) { } ``` -## 21. ˳ʹλżǰ +## 21. 调整数组顺序使奇数位于偶数前面 -**ĿҪ** +**题目要求** -֤żż֮λò䣬鱾̫һ +保证奇数和奇数,偶数和偶数之间的相对位置不变,这和书本不太一样。 -ʱ临Ӷ : O(n2) -ռ临Ӷ : O(1) +时间复杂度 : O(n2) +空间复杂度 : O(1) ```java public void reOrderArray(int[] array) { @@ -669,8 +670,8 @@ public void reOrderArray(int[] array) { } ``` -ʱ临Ӷ : O(n) -ռ临Ӷ : O(n) +时间复杂度 : O(n) +空间复杂度 : O(n) ```java public void reOrderArray(int[] array) { @@ -685,7 +686,7 @@ public void reOrderArray(int[] array) { } ``` -## 22. е k +## 22. 链表中倒数第 k 个结点 ```java public ListNode FindKthToTail(ListNode head, int k) { @@ -702,7 +703,7 @@ public ListNode FindKthToTail(ListNode head, int k) { } ``` -## 23. лڽ +## 23. 链表中环的入口结点 ```java public ListNode EntryNodeOfLoop(ListNode pHead) { @@ -724,7 +725,7 @@ public ListNode EntryNodeOfLoop(ListNode pHead) { } ``` -## 24. ת +## 24. 反转链表 ```java public ListNode ReverseList(ListNode head) { @@ -739,7 +740,7 @@ public ListNode ReverseList(ListNode head) { } ``` -## 25. ϲ +## 25. 合并两个排序的链表 ```java public ListNode Merge(ListNode list1, ListNode list2) { @@ -761,7 +762,7 @@ public ListNode Merge(ListNode list1, ListNode list2) { } ``` -## 26. ӽṹ +## 26. 树的子结构 ```java public boolean HasSubtree(TreeNode root1, TreeNode root2) { @@ -778,9 +779,9 @@ private boolean isSubtree(TreeNode root1, TreeNode root2) { } ``` -# ˼· +# 第四章 解决面试题的思路 -## 27. ľ +## 27. 二叉树的镜像 ```java public void Mirror(TreeNode root) { @@ -793,7 +794,7 @@ public void Mirror(TreeNode root) { } ``` -## 28.1 ԳƵĶ +## 28.1 对称的二叉树 ```java boolean isSymmetrical(TreeNode pRoot) { @@ -809,7 +810,7 @@ boolean isSymmetrical(TreeNode t1, TreeNode t2) { } ``` -## 28.2 ƽ +## 28.2 平衡二叉树 ```java private boolean isBalanced = true; @@ -828,7 +829,7 @@ private int height(TreeNode root) { } ``` -## 29. ˳ʱӡ +## 29. 顺时针打印矩阵 ```java public ArrayList printMatrix(int[][] matrix) { @@ -845,7 +846,7 @@ public ArrayList printMatrix(int[][] matrix) { } ``` -## 30. min ջ +## 30. 包含 min 函数的栈 ```java private Stack stack = new Stack<>(); @@ -873,7 +874,7 @@ public int min() { } ``` -## 31. ջѹ롢 +## 31. 栈的压入、弹出序列 ```java public boolean IsPopOrder(int[] pushA, int[] popA) { @@ -890,7 +891,7 @@ public boolean IsPopOrder(int[] pushA, int[] popA) { } ``` -## 32.1 ´ӡ +## 32.1 从上往下打印二叉树 ```java public ArrayList PrintFromTopToBottom(TreeNode root) { @@ -911,7 +912,7 @@ public ArrayList PrintFromTopToBottom(TreeNode root) { } ``` -## 32.3 Ѷӡɶ +## 32.3 把二叉树打印成多行 ```java ArrayList> Print(TreeNode pRoot) { @@ -934,7 +935,7 @@ ArrayList> Print(TreeNode pRoot) { } ``` -## 32.3 ֮˳ӡ +## 32.3 按之字形顺序打印二叉树 ```java public ArrayList> Print(TreeNode pRoot) { @@ -965,7 +966,7 @@ public ArrayList> Print(TreeNode pRoot) { ``` -## 33. ĺ +## 33. 二叉搜索树的后序遍历序列 ```java public boolean VerifySquenceOfBST(int[] sequence) { @@ -988,7 +989,7 @@ private boolean verify(int[] sequence, int start, int end) { } ``` -## 34. кΪijһֵ· +## 34. 二叉树中和为某一值的路径 ```java private ArrayList> ret = new ArrayList<>(); @@ -1012,29 +1013,29 @@ private void dfs(TreeNode node, int target, int curSum, ArrayList path) } ``` -## 35. ĸ +## 35. 复杂链表的复制 -**Ŀ** +**题目描述** -һÿڵнڵֵԼָ룬һָһڵ㣬һָָһڵ㣩ؽΪƺ headע⣬벻ҪزеĽڵãֱӷؿգ +输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的 head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空) -һÿڵĺ븴ƵĽڵ㡣 +第一步,在每个节点的后面插入复制的节点。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/f8b12555-967b-423d-a84e-bc9eff104b8b.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//f8b12555-967b-423d-a84e-bc9eff104b8b.jpg) -ڶԸƽڵ random ӽиֵ +第二步,对复制节点的 random 链接进行赋值。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/7b877a2a-8fd1-40d8-a34c-c445827300b8.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//7b877a2a-8fd1-40d8-a34c-c445827300b8.jpg) -֡ +第三步,拆分。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/b2b6253c-c701-4b30-aff4-bc3c713542a7.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//b2b6253c-c701-4b30-aff4-bc3c713542a7.jpg) ```java public RandomListNode Clone(RandomListNode pHead) { if (pHead == null) return null; - // ½ڵ + // 插入新节点 RandomListNode cur = pHead; while (cur != null) { RandomListNode node = new RandomListNode(cur.label); @@ -1042,7 +1043,7 @@ public RandomListNode Clone(RandomListNode pHead) { cur.next = node; cur = node.next; } - // random + // 建立 random 链接 cur = pHead; while (cur != null) { RandomListNode clone = cur.next; @@ -1051,7 +1052,7 @@ public RandomListNode Clone(RandomListNode pHead) { } cur = clone.next; } - // + // 拆分 RandomListNode pCloneHead = pHead.next; cur = pHead; while (cur.next != null) { @@ -1063,11 +1064,11 @@ public RandomListNode Clone(RandomListNode pHead) { } ``` -## 36. ˫ +## 36. 二叉搜索树与双向链表 -**Ŀ** +**题目描述** -һööתһ˫ҪܴκµĽ㣬ֻܵнָָ +输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。 ```java private TreeNode pre = null; @@ -1088,7 +1089,7 @@ private void inOrder(TreeNode node) { } ``` -## 37. л +## 37. 序列化二叉树 ```java private String serizeString = ""; @@ -1121,11 +1122,11 @@ private TreeNode Deserialize() { } ``` -## 38. ַ +## 38. 字符串的排列 -**Ŀ** +**题目描述** -һַ , ֵӡַַСַ abc, ӡַ a, b, c гַ abc, acb, bac, bca, cab cba +输入一个字符串 , 按字典序打印出该字符串中字符的所有排列。例如输入字符串 abc, 则打印出由字符 a, b, c 所能排列出来的所有字符串 abc, acb, bac, bca, cab 和 cba。 ```java private ArrayList ret = new ArrayList<>(); @@ -1145,7 +1146,7 @@ private void backtracking(char[] chars, boolean[] used, String s) { } for (int i = 0; i < chars.length; i++) { if (used[i]) continue; - if (i != 0 && chars[i] == chars[i - 1] && !used[i - 1]) continue; // ֤ظ + if (i != 0 && chars[i] == chars[i - 1] && !used[i - 1]) continue; // 保证不重复 used[i] = true; backtracking(chars, used, s + chars[i]); used[i] = false; @@ -1153,9 +1154,9 @@ private void backtracking(char[] chars, boolean[] used, String s) { } ``` -# ŻʱͿռЧ +# 第五章 优化时间和空间效率 -## 39. гִһ +## 39. 数组中出现次数超过一半的数字 ```java public int MoreThanHalfNum_Solution(int[] array) { @@ -1177,12 +1178,12 @@ public int MoreThanHalfNum_Solution(int[] array) { ``` -## 40. С K +## 40. 最小的 K 个数 -СΪ k Сѡ +构建大小为 k 的小顶堆。 -ʱ临ӶȣO(nlgk) -ռ临ӶȣO(k) +时间复杂度:O(nlgk) +空间复杂度:O(k) ```java public ArrayList GetLeastNumbers_Solution(int[] input, int k) { @@ -1199,10 +1200,10 @@ public ArrayList GetLeastNumbers_Solution(int[] input, int k) { } ``` -ÿѡ +利用快速选择 -ʱ临ӶȣO(n) -ռ临ӶȣO(1) +时间复杂度:O(n) +空间复杂度:O(1) ```java public ArrayList GetLeastNumbers_Solution(int[] input, int k) { @@ -1257,22 +1258,22 @@ private boolean less(int v, int w) { } ``` -## 41.1 еλ +## 41.1 数据流中的中位数 -**Ŀ** +**题目描述** -εõһеλжֵôλֵ֮λмֵжżֵôλֵ֮мƽֵ +如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。 ```java -private PriorityQueue maxHeap = new PriorityQueue<>((o1, o2) -> o2-o1); // ʵ߲ -private PriorityQueue minHeep = new PriorityQueue<>(); // ʵұ߲֣ұ߲Ԫش߲ +private PriorityQueue maxHeap = new PriorityQueue<>((o1, o2) -> o2-o1); // 实现左边部分 +private PriorityQueue minHeep = new PriorityQueue<>(); // 实现右边部分,右边部分所有元素大于左边部分 private int cnt = 0; public void Insert(Integer num) { - // Ҫ֤Ѵƽ״̬ + // 插入要保证两个堆存于平衡状态 if(cnt % 2 == 0) { - // Ϊż²뵽СѣȾɸѡܱ֤еԪضССеԪ + // 为偶数的情况下插入到最小堆,先经过最大堆筛选,这样就能保证最大堆中的元素都小于最小堆中的元素 maxHeap.add(num); minHeep.add(maxHeap.poll()); } else { @@ -1291,11 +1292,11 @@ public Double GetMedian() { } ``` -## 14.2 ַеһظַ +## 14.2 字符流中第一个不重复的字符 -**Ŀ** +**题目描述** -ʵһҳַеһֻһεַ磬ַֻǰַ "go" ʱһֻһεַ "g"Ӹַжǰַgoogle" ʱһֻһεַ "l" +请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符 "go" 时,第一个只出现一次的字符是 "g"。当从该字符流中读出前六个字符“google" 时,第一个只出现一次的字符是 "l"。 ```java //Insert one char from stringstream @@ -1318,7 +1319,7 @@ public char FirstAppearingOnce() { ``` -## 42. +## 42. 连续子数组的最大和 ```java public int FindGreatestSumOfSubArray(int[] array) { @@ -1334,9 +1335,9 @@ public int FindGreatestSumOfSubArray(int[] array) { } ``` -## 43. 1 n 1 ֵĴ +## 43. 从 1 到 n 整数中 1 出现的次数 -ο[Leetcode : 233. Number of Digit One](https://leetcode.com/problems/number-of-digit-one/discuss/64381/4+-lines-O(log-n)-C++JavaPython) +解题参考:[Leetcode : 233. Number of Digit One](https://leetcode.com/problems/number-of-digit-one/discuss/64381/4+-lines-O(log-n)-C++JavaPython) ```java public int NumberOf1Between1AndN_Solution(int n) { @@ -1349,11 +1350,11 @@ public int NumberOf1Between1AndN_Solution(int n) { } ``` -## 45. ųС +## 45. 把数组排成最小的数 -**Ŀ** +**题目描述** -һ飬ƴųһӡƴӳСһ {332321}ӡųɵСΪ 321323 +输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组 {3,32,321},则打印出这三个数字能排成的最小数字为 321323。 ```java public String PrintMinNumber(int[] numbers) { @@ -1367,11 +1368,11 @@ public String PrintMinNumber(int[] numbers) { } ``` -## 49. +## 49. 丑数 -**Ŀ** +**题目描述** -ֻ 23 5 Ugly Number 68 dz 14 ǣΪ 7 ϰǰ 1 ǵһ󰴴С˳ĵ N +把只包含因子 2、3 和 5 的数称作丑数(Ugly Number)。例如 6、8 都是丑数,但 14 不是,因为它包含因子 7。 习惯上我们把 1 当做是第一个丑数。求按从小到大的顺序的第 N 个丑数。 ```java public int GetUglyNumber_Solution(int index) { @@ -1392,7 +1393,7 @@ public int GetUglyNumber_Solution(int index) { } ``` -## 50. һֻһεַλ +## 50. 第一个只出现一次的字符位置 ```java public int FirstNotRepeatingChar(String str) { @@ -1403,7 +1404,7 @@ public int FirstNotRepeatingChar(String str) { } ``` -## 51. е +## 51. 数组中的逆序对 ```java private long cnt = 0; @@ -1430,7 +1431,7 @@ private void merge(int[] a, int start, int mid, int end) { else if (a[i] < a[j]) tmp[k] = a[i++]; else { tmp[k] = a[j++]; - this.cnt += mid - i + 1; // a[i] > a[j] ˵ a[i...mid] a[j] + this.cnt += mid - i + 1; // a[i] > a[j] ,说明 a[i...mid] 都大于 a[j] } k++; } @@ -1441,7 +1442,7 @@ private void merge(int[] a, int start, int mid, int end) { } ``` -## 52. ĵһ +## 52. 两个链表的第一个公共结点 ```java public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) { @@ -1456,9 +1457,9 @@ public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) { } ``` -# еĸ +# 第六章 面试中的各项能力 -## 53 гֵĴ +## 53 数字在排序数组中出现的次数 ```java public int GetNumberOfK(int[] array, int k) { @@ -1474,7 +1475,7 @@ public int GetNumberOfK(int[] array, int k) { } ``` -## 54. ĵ k +## 54. 二叉搜索树的第 k 个结点 ```java TreeNode ret; @@ -1495,7 +1496,7 @@ private void inorder(TreeNode root, int k) { } ``` -## 55 +## 55 二叉树的深度 ```java public int TreeDepth(TreeNode root) { @@ -1504,25 +1505,25 @@ public int TreeDepth(TreeNode root) { } ``` -## 56. ֻһε +## 56. 数组中只出现一次的数字 -**Ŀ** +**题目描述** -һ֮⣬ֶΣҳ +一个整型数组里除了两个数字之外,其他的数字都出现了两次,找出这两个数。 -**˼·** +**解题思路** -ȵԪλʾϱضһλڲͬ +两个不相等的元素在位级表示上必定会有一位存在不同。 -ԪõĽΪظԪĽ +将数组的所有元素异或得到的结果为不存在重复的两个元素异或的结果。 -diff &= -diff õ diff Ҳ಻Ϊ 0 λҲDzظԪλʾҲ಻ͬһλһλͿԽԪֿ +diff &= -diff 得到出 diff 最右侧不为 0 的位,也就是不存在重复的两个元素在位级表示上最右侧不同的那一位,利用这一位就可以将两个元素区分开来。 ```java public void FindNumsAppearOnce(int[] array, int num1[], int num2[]) { int diff = 0; for (int num : array) diff ^= num; - // õһλ + // 得到最右一位 diff &= -diff; for (int num : array) { if ((num & diff) == 0) num1[0] ^= num; @@ -1531,11 +1532,11 @@ public void FindNumsAppearOnce(int[] array, int num1[], int num2[]) { } ``` -## 57.1 Ϊ S +## 57.1 和为 S 的两个数字 -**Ŀ** +**题目描述** -һһ Sвǵǵĺ Sжֵĺ͵ Sij˻Сġ +输入一个递增排序的数组和一个数字 S,在数组中查找两个数,是的他们的和正好是 S,如果有多对数字的和等于 S,输出两个数的乘积最小的。 ```java public ArrayList FindNumbersWithSum(int[] array, int sum) { @@ -1550,11 +1551,11 @@ public ArrayList FindNumbersWithSum(int[] array, int sum) { } ``` -## 57.2 Ϊ S +## 57.2 和为 S 的连续正数序列 -**Ŀ** +**题目描述** -Ϊ 100 18, 19, 20, 21, 22 +和为 100 的连续序列有 18, 19, 20, 21, 22 ```java public ArrayList> FindContinuousSequence(int sum) { @@ -1585,13 +1586,13 @@ public ArrayList> FindContinuousSequence(int sum) { } ``` -## 58.1 ת˳ +## 58.1 翻转单词顺序列 -**Ŀ** +**题目描述** -룺"I am a student." +输入:"I am a student." -"student. a am I" +输出:"student. a am I" ```java public String ReverseSentence(String str) { @@ -1621,11 +1622,11 @@ private void reverse(char[] c, int start, int end) { } ``` -## 58.2 תַ +## 58.2 左旋转字符串 -**Ŀ** +**题目描述** -һַ Sѭ K λ磬ַ S=abcXYZdef, Ҫѭ 3 λĽXYZdefabc +对于一个给定的字符序列 S,请你把其循环左移 K 位后的序列输出。例如,字符序列 S=”abcXYZdef”, 要求输出循环左移 3 位后的结果,即“XYZdefabc”。 ```java public String LeftRotateString(String str, int n) { @@ -1648,11 +1649,11 @@ private void reverse(char[] c, int i, int j) { } ``` -## 59. ڵֵ +## 59. 滑动窗口的最大值 -**Ŀ** +**题目描述** -һͻڵĴСҳлֵֵ磬 {2, 3, 4, 2, 6, 2, 5, 1} ڵĴС 3ôһ 6 ڣǵֱֵΪ {4, 4, 6, 6, 6, 5} +给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组 {2, 3, 4, 2, 6, 2, 5, 1} 及滑动窗口的大小 3,那么一共存在 6 个滑动窗口,他们的最大值分别为 {4, 4, 6, 6, 6, 5}; ```java public ArrayList maxInWindows(int[] num, int size) { @@ -1670,11 +1671,11 @@ public ArrayList maxInWindows(int[] num, int size) { } ``` -## 61. ˿˳ +## 61. 扑克牌顺子 -**Ŀ** +**题目描述** -ƣдСΪӣСΪ 0жǷ˳ӡ +五张牌,其中大小鬼为癞子,牌面大小为 0。判断是否能组成顺子。 ```java public boolean isContinuous(int[] numbers) { @@ -1692,15 +1693,15 @@ public boolean isContinuous(int[] numbers) { } ``` -## 62. ԲȦʣµ +## 62. 圆圈中最后剩下的数 -**Ŀ** +**题目描述** -СΧһȦȻ , ָһ m, ñΪ 0 Сѿʼÿκ m-1 ǸСҪг׸ , ȻƷѡ , ҲٻصȦ , һСѿʼ , 0...m-1 .... ȥ .... ֱʣһС , Բñݡ +让小朋友们围成一个大圈。然后 , 他随机指定一个数 m, 让编号为 0 的小朋友开始报数。每次喊到 m-1 的那个小朋友要出列唱首歌 , 然后可以在礼品箱中任意的挑选礼物 , 并且不再回到圈中 , 从他的下一个小朋友开始 , 继续 0...m-1 报数 .... 这样下去 .... 直到剩下最后一个小朋友 , 可以不用表演。 -**˼·** +**解题思路** -Լɪ +约瑟夫环 ```java public int LastRemaining_Solution(int n, int m) { @@ -1710,11 +1711,11 @@ public int LastRemaining_Solution(int n, int m) { } ``` -## 63. Ʊ +## 63. 股票的最大利润 -**Ŀ** +**题目描述** -һһǰ档 +可以有一次买入和一次卖出,买入必须在前。求最大收益。 ```java public int maxProfit(int[] prices) { @@ -1730,11 +1731,11 @@ public int maxProfit(int[] prices) { } ``` -## 64. 1+2+3+...+n +## 64. 求 1+2+3+...+n -**Ŀ** +**题目描述** - 1+2+3+...+nҪʹó˳forwhileifelseswitchcase ȹؼּж䣨A?B:C +求 1+2+3+...+n,要求不能使用乘除法、for、while、if、else、switch、case 等关键字及条件判断语句(A?B:C) ```java public int Sum_Solution(int n) { @@ -1744,9 +1745,9 @@ public int Sum_Solution(int n) { } ``` -## 65. üӼ˳ӷ +## 65. 不用加减乘除做加法 -a ^ b ʾûпǽλĺͣ(a & b) << 1 ǽλݹֹԭ (a & b) << 1 ұ߻һ 0ôݹ飬λұߵ 0 ࣬λΪ 0ݹֹ +a ^ b 表示没有考虑进位的情况下两数的和,(a & b) << 1 就是进位。递归会终止的原因是 (a & b) << 1 最右边会多一个 0,那么继续递归,进位最右边的 0 会慢慢增多,最后进位会变为 0,递归终止。 ```java public int Add(int num1, int num2) { @@ -1755,11 +1756,11 @@ public int Add(int num1, int num2) { } ``` -## 66. ˻ +## 66. 构建乘积数组 -**Ŀ** +**题目描述** -һ A[0, 1,..., n-1], 빹һ B[0, 1,..., n-1], B еԪ B[i]=A[0]\*A[1]\*...\*A[i-1]\*A[i+1]\*...\*A[n-1]ʹó +给定一个数组 A[0, 1,..., n-1], 请构建一个数组 B[0, 1,..., n-1], 其中 B 中的元素 B[i]=A[0]\*A[1]\*...\*A[i-1]\*A[i+1]\*...\*A[n-1]。不能使用除法。 ```java public int[] multiply(int[] A) { @@ -1784,9 +1785,9 @@ public int[] multiply(int[] A) { } ``` -# ԰ +# 第七章 两个面试案例 -## 67. ַת +## 67. 把字符串转换成整数 ```java public int StrToInt(String str) { @@ -1803,9 +1804,9 @@ public int StrToInt(String str) { } ``` -## 68. ڵ͹ +## 68. 树中两个节点的最低公共祖先 -Ƕ͹⣺ +树是二叉查找树的最低公共祖先问题: ```java public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { diff --git a/notes/算法.md b/notes/算法.md index 90a01a29..0a3613af 100644 --- a/notes/算法.md +++ b/notes/算法.md @@ -1,96 +1,97 @@ -* [һ ](#һ-) - * [ջ](#ջ) - * [](#) - * [㷨](#㷨) - * [1. ת](#1-ת) - * [2. ѧģ](#2-ѧģ) +* [第一章 基础](#第一章-基础) + * [栈](#栈) + * [队列](#队列) + * [算法分析](#算法分析) + * [1. 函数转换](#1-函数转换) + * [2. 数学模型](#2-数学模型) * [3. ThreeSum](#3-threesum) - * [4. ʵ](#4-ʵ) - * [5. ע](#5-ע) + * [4. 倍率实验](#4-倍率实验) + * [5. 注意事项](#5-注意事项) * [union-find](#union-find) - * [1. quick-find 㷨](#1-quick-find-㷨) - * [2. quick-union 㷨](#2-quick-union-㷨) - * [3. Ȩ quick-union 㷨](#3-Ȩ-quick-union-㷨) - * [4. ·ѹļȨ quick-union 㷨](#4-·ѹļȨ-quick-union-㷨) - * [5. union-find 㷨ıȽ](#5--union-find-㷨ıȽ) -* [ڶ ](#ڶ-) - * [㷨](#㷨) - * [1. Լ](#1-Լ) - * [2. ѡ](#2-ѡ) - * [3. ](#3-) - * [4. ѡͲıȽ](#4-ѡͲıȽ) - * [5. ϣ](#5-ϣ) - * [鲢](#鲢) - * [1. 鲢](#1-鲢) - * [2. Զ¹鲢](#2-Զ¹鲢) - * [3. ԵϹ鲢](#3-ԵϹ鲢) - * [](#) - * [1. 㷨](#1-㷨) - * [2. з](#2-з) - * [3. ܷ](#3-ܷ) - * [4. 㷨Ľ](#4-㷨Ľ) - * [4.1 л](#41-л) - * [4.2 ȡ](#42-ȡ) - * [4.3 з](#43-з) - * [ȶ](#ȶ) - * [1. ](#1-) - * [2. ϸ³](#2-ϸ³) - * [3. Ԫ](#3-Ԫ) - * [4. ɾԪ](#4-ɾԪ) - * [5. ](#5-) - * [6. ](#6-) - * [Ӧ](#Ӧ) - * [1. 㷨ıȽ](#1-㷨ıȽ) - * [2. Java 㷨ʵ](#2-java-㷨ʵ) - * [3. зֵĿѡ㷨](#3-зֵĿѡ㷨) -* [ ](#-) - * [ű](#ű) - * [1. ű](#1-ű) - * [2. ű](#2-ű) - * [3. ֲʵű](#3-ֲʵű) - * [](#) + * [1. quick-find 算法](#1-quick-find-算法) + * [2. quick-union 算法](#2-quick-union-算法) + * [3. 加权 quick-union 算法](#3-加权-quick-union-算法) + * [4. 路径压缩的加权 quick-union 算法](#4-路径压缩的加权-quick-union-算法) + * [5. 各种 union-find 算法的比较](#5-各种-union-find-算法的比较) +* [第二章 排序](#第二章-排序) + * [初级排序算法](#初级排序算法) + * [1. 约定](#1-约定) + * [2. 选择排序](#2-选择排序) + * [3. 插入排序](#3-插入排序) + * [4. 选择排序和插入排序的比较](#4-选择排序和插入排序的比较) + * [5. 希尔排序](#5-希尔排序) + * [归并排序](#归并排序) + * [1. 归并方法](#1-归并方法) + * [2. 自顶向下归并排序](#2-自顶向下归并排序) + * [3. 自底向上归并排序](#3-自底向上归并排序) + * [快速排序](#快速排序) + * [1. 基本算法](#1-基本算法) + * [2. 切分](#2-切分) + * [3. 性能分析](#3-性能分析) + * [4. 算法改进](#4-算法改进) + * [4.1 切换到插入排序](#41-切换到插入排序) + * [4.2 三取样](#42-三取样) + * [4.3 三向切分](#43-三向切分) + * [优先队列](#优先队列) + * [1. 堆](#1-堆) + * [2. 上浮和下沉](#2-上浮和下沉) + * [3. 插入元素](#3-插入元素) + * [4. 删除最大元素](#4-删除最大元素) + * [5. 堆排序](#5-堆排序) + * [6. 分析](#6-分析) + * [应用](#应用) + * [1. 排序算法的比较](#1-排序算法的比较) + * [2. Java 的排序算法实现](#2-java-的排序算法实现) + * [3. 基于切分的快速选择算法](#3-基于切分的快速选择算法) +* [第三章 查找](#第三章-查找) + * [符号表](#符号表) + * [1. 无序符号表](#1-无序符号表) + * [2. 有序符号表](#2-有序符号表) + * [3. 二分查找实现有序符号表](#3-二分查找实现有序符号表) + * [二叉查找树](#二叉查找树) * [1. get()](#1-get) * [2. put()](#2-put) - * [3. ](#3-) + * [3. 分析](#3-分析) * [4. floor()](#4-floor) * [5. rank()](#5-rank) * [6. min()](#6-min) * [7. deleteMin()](#7-deletemin) * [8. delete()](#8-delete) * [9. keys()](#9-keys) - * [10. ܷ](#10-ܷ) - * [ƽ](#ƽ) - * [2-3 ](#2-3-) - * [1. ](#1-) - * [2. ](#2-) - * [ڶ](#ڶ) - * [1. ת](#1-ת) - * [2. ת](#2-ת) - * [3. ɫת](#3-ɫת) - * [4. ](#4-) - * [5. ɾС](#5-ɾС) - * [6. ](#6-) - * [ɢб](#ɢб) - * [1. ɢк](#1-ɢк) - * [2. ɢб](#2-ɢб) - * [3. ̽ⷨɢб](#3-̽ⷨɢб) - * [3.1 ](#31-) - * [3.2 ](#32-) - * [3.3 ɾ](#33-ɾ) - * [3.4 С](#34-С) - * [Ӧ](#Ӧ) - * [1. ַűʵֵıȽ](#1-ַűʵֵıȽ) - * [2. Java ķűʵ](#2-java-ķűʵ) - * [3. ](#3-) - * [4. ϡ˷](#4-ϡ˷) + * [10. 性能分析](#10-性能分析) + * [平衡查找树](#平衡查找树) + * [2-3 查找树](#2-3-查找树) + * [1. 插入操作](#1-插入操作) + * [2. 性质](#2-性质) + * [红黑二叉查找树](#红黑二叉查找树) + * [1. 左旋转](#1-左旋转) + * [2. 右旋转](#2-右旋转) + * [3. 颜色转换](#3-颜色转换) + * [4. 插入](#4-插入) + * [5. 删除最小键](#5-删除最小键) + * [6. 分析](#6-分析) + * [散列表](#散列表) + * [1. 散列函数](#1-散列函数) + * [2. 基于拉链法的散列表](#2-基于拉链法的散列表) + * [3. 基于线性探测法的散列表](#3-基于线性探测法的散列表) + * [3.1 查找](#31-查找) + * [3.2 插入](#32-插入) + * [3.3 删除](#33-删除) + * [3.4 调整数组大小](#34-调整数组大小) + * [应用](#应用) + * [1. 各种符号表实现的比较](#1-各种符号表实现的比较) + * [2. Java 的符号表实现](#2-java-的符号表实现) + * [3. 集合类型](#3-集合类型) + * [4. 稀疏向量乘法](#4-稀疏向量乘法) -# һ -## ջ +# 第一章 基础 -**ʵ** +## 栈 + +**数组实现** ```java public class ResizeArrayStack implements Iterable { @@ -112,7 +113,7 @@ public class ResizeArrayStack implements Iterable { return item; } - // Сʹջ + // 调整数组大小,使得栈具有伸缩性 private void resize(int size) { Item[] tmp = (Item[]) new Object[size]; for (int i = 0; i < N; i++) { @@ -131,7 +132,7 @@ public class ResizeArrayStack implements Iterable { @Override public Iterator iterator() { - // Ҫĵ + // 需要返回逆序遍历的迭代器 return new ReverseArrayIterator(); } @@ -151,15 +152,15 @@ public class ResizeArrayStack implements Iterable { } ``` -ʵʹ˷ͣJava ֱӴ飬ֻʹת +上面实现使用了泛型,Java 不能直接创建泛型数组,只能使用转型来创建。 ```java Item[] arr = (Item[]) new Object[N]; ``` -**ʵ** +**链表实现** -Ҫʹͷ巨ʵ֣Ϊͷ巨ѹջԪĿͷ next ָָǰһѹջԪأڵԪʹͿǰһѹջԪسΪջԪء +需要使用链表的头插法来实现,因为头插法中最后压入栈的元素在链表的开头,它的 next 指针指向前一个压入栈的元素,在弹出元素使就可以让前一个压入栈的元素称为栈顶元素。 ```java public class Stack { @@ -197,11 +198,11 @@ public class Stack { } ``` -## +## 队列 -Ƕеʵ֣Ҫά first last ڵָ룬ֱָ׺Ͷβ +下面是队列的链表实现,需要维护 first 和 last 节点指针,分别指向队首和队尾。 -Ҫĸָָͷڵ㣬ĸָָβڵ㡣ΪвҪöԪصһԪسΪףҪ׻ȡһԪأͷڵ next ָָһԪأöָ first ָĿͷ +这里需要考虑让哪个指针指针链表头部节点,哪个指针指向链表尾部节点。因为出队列操作需要让队首元素的下一个元素成为队首,就需要容易获取下一个元素,而链表的头部节点的 next 指针指向下一个元素,因此让队首指针 first 指针链表的开头。 ```java public class Queue { @@ -221,7 +222,7 @@ public class Queue { return N; } - // + // 入队列 public void enqueue(Item item){ Node newNode = new Node(); newNode.item = item; @@ -236,7 +237,7 @@ public class Queue { N++; } - // + // 出队列 public Item dequeue(){ Node node = first; first = first.next; @@ -246,41 +247,41 @@ public class Queue { } ``` -## 㷨 +## 算法分析 -### 1. ת +### 1. 函数转换 -ָתΪԺӶںͼʾĸֱۡ +指数函数可以转换为线性函数,从而在函数图像上显示的更直观。 -T(N)=aN3 תΪ lg(T(N))=3lgN+lga +T(N)=aN3 转换为 lg(T(N))=3lgN+lga -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/5510045a-8f32-487f-a756-463e51a6dab0.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//5510045a-8f32-487f-a756-463e51a6dab0.png) -### 2. ѧģ +### 2. 数学模型 -**** +**近似** -ʹ \~f(N) ʾ N f(N) Ľ 1 ĺ , N3/6-N2/2+N/3 \~ N3/6 +使用 \~f(N) 来表示所有随着 N 的增大除以 f(N) 的结果趋近于 1 的函数 , 例如 N3/6-N2/2+N/3 \~ N3/6。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/ca3a793e-06e5-4ff3-b28e-a9c20540d164.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//ca3a793e-06e5-4ff3-b28e-a9c20540d164.png) -**** +**增长数量级** -㷨ʵָ뿪һ㷨Ϊ N3 Ƿ Java ʵ֣Ƿض޹ء +增长数量级将算法与它的实现隔离开来,一个算法的增长数量级为 N3 与它是否用 Java 实现,是否运行与特定计算机上无关。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/1ea4dc9a-c4dd-46b5-bb11-49f98d57ded1.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//1ea4dc9a-c4dd-46b5-bb11-49f98d57ded1.png) -**ѭ** +**内循环** -ִƵָ˳ִеʱ䣬ЩָΪѭ +执行最频繁的指令决定了程序执行的总时间,把这些指令称为程序的内循环。 -**ɱģ** +**成本模型** -ʹóɱģ㷨ķʴһֳɱģ͡ +使用成本模型来评估算法,例如数组的访问次数就是一种成本模型。 ### 3. ThreeSum -ThreeSum ͳһԪĺΪ 0 +ThreeSum 程序用于统计一个数组中三元组的和为 0 的数量。 ```java public class ThreeSum { @@ -301,13 +302,13 @@ public class ThreeSum { } ``` -óѭΪ if (a[i] + a[j] + a[k] == 0) 䣬ִܹеĴΪ N3/6-N2/2+N/3ĽִдΪ \~N3/6Ϊ N3 +该程序的内循环为 if (a[i] + a[j] + a[k] == 0) 语句,总共执行的次数为 N3/6-N2/2+N/3,因此它的近似执行次数为 \~N3/6,增长数量级为 N3。 -**Ľ** +**改进** -ͨ򣬶ԪͣöֲҷǷڸú͵෴ڣ˵ԪĺΪ 0 +通过将数组先排序,对两个元素求和,并用二分查找方法查找是否存在该和的相反数,如果存在,就说明存在三元组的和为 0。 -÷Խ ThreeSum 㷨Ϊ N2logN +该方法可以将 ThreeSum 算法增长数量级降低为 N2logN。 ```java public class ThreeSumFast { @@ -318,7 +319,7 @@ public class ThreeSumFast { for (int i = 0; i < N; i++) { for (int j = i + 1; j < N; j++) { for (int k = j + 1; k < N; k++) { - // rank() Ԫе±꣬Ԫزڣ᷵ -1Ӧע± jͲظͳˡ + // rank() 方法返回元素在数组中的下标,如果元素不存在,这里会返回 -1。应该注意这里的下标必须大于 j,这样就不会重复统计了。 if (BinarySearch.rank(-a[i] - a[j], a) > j) { cnt++; } @@ -330,53 +331,53 @@ public class ThreeSumFast { } ``` -### 4. ʵ +### 4. 倍率实验 - T(N) \~ aNblgNô T(2N)/T(N) \~ 2bڱ ThreeSum 㷨ʱΪ \~N3/6бʵõ½ +如果 T(N) \~ aNblgN,那么 T(2N)/T(N) \~ 2b,例如对于暴力方法的 ThreeSum 算法,近似时间为 \~N3/6,对它进行倍率实验得到如下结果: -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/6f5ed46f-86d7-4852-a34f-c1cf1b6343a0.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//6f5ed46f-86d7-4852-a34f-c1cf1b6343a0.png) -ɼ T(2N)/T(N)\~23Ҳ b Ϊ 3 +可见 T(2N)/T(N)\~23,也就是 b 为 3。 -### 5. ע +### 5. 注意事项 -**** +**大常数** -ʱͼijϵܴôƵĽǴġ +在求近似时,如果低级项的常数系数很大,那么近似的结果就是错误的。 -**** +**缓存** -ϵͳʹû漼֯ڴ棬ڵԪػȷʲڵԪؿܶࡣ +计算机系统会使用缓存技术来组织内存,访问数组相邻的元素会比访问不相邻的元素快很多。 -**µܵı֤** +**对最坏情况下的性能的保证** -ں˷ӦѡɲеµʮҪġ +在核反应堆、心脏起搏器或者刹车控制器中的软件,最坏情况下的性能是十分重要的。 -**㷨** +**随机化算法** -ͨ룬ȥ㷨 +通过打乱输入,去除算法对输入的依赖。 -**̯** +**均摊分析** -вܳɱԲɱ̯һջ N push() ҪԪΪ N+4+8+16+...+2N=5N-4N дԪأĶǵСʱиҪķ̯ÿβƽΪ +将所有操作的总成本所以操作总数来将成本均摊。例如对一个空栈进行 N 次连续的 push() 调用需要访问数组的元素为 N+4+8+16+...+2N=5N-4(N 是向数组写入元素,其余的都是调整数组大小时进行复制需要的访问数组操作),均摊后每次操作访问数组的平均次数为常数。 ## union-find -**** +**概览** -ڽ̬ͨ⣬̬ܶ㣬жǷӡ +用于解决动态连通性问题,能动态连接两个点,并且判断两个点是否连接。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/5d387d02-6f96-44d6-b5d0-4538349f868e.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//5d387d02-6f96-44d6-b5d0-4538349f868e.png) **API** -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/a9b91b7d-65d7-4aa3-8ef6-21876b05ad16.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//a9b91b7d-65d7-4aa3-8ef6-21876b05ad16.png) -**ݽṹ** +**基本数据结构** ```java public class UF { - // ʹ id ͨϢ + // 使用 id 数组来保存点的连通信息 private int[] id; public UF(int N) { @@ -392,11 +393,11 @@ public class UF { } ``` -### 1. quick-find 㷨 +### 1. quick-find 算法 -֤ͬһͨд id ֵȡ +保证在同一连通分量的所有触点的 id 值相等。 -ַԿȡһ id ֵжǷͨ union IJȴܸߣҪһͨенڵ id ֵ޸Ϊһڵ id ֵ +这种方法可以快速取得一个触点的 id 值,并且判断两个触点是否连通,但是 union 的操作代价却很高,需要将其中一个连通分量中的所有节点 id 值都修改为另一个节点的 id 值。 ```java public int find(int p) { @@ -413,11 +414,11 @@ public class UF { } ``` -### 2. quick-union 㷨 +### 2. quick-union 算法 - union ʱֻ id ֵָһ id ֱֵ id 洢ͨ͹һõνṹڵҪָԼڽвһڵͨʱҪһֱϲֱڵ㣬ʹøڵ id ֵΪͨ idֵ +在 union 时只将触点的 id 值指向另一个触点 id 值,不直接用 id 来存储所属的连通分量。这样就构成一个倒置的树形结构,根节点需要指向自己。在进行查找一个节点所属的连通分量时,要一直向上查找直到根节点,并使用根节点的 id 值作为本连通分量的 id值。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/9192dc0a-a7cd-4030-8df6-e388600644cf.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//9192dc0a-a7cd-4030-8df6-e388600644cf.jpg) ```java public int find(int p) { @@ -433,22 +434,22 @@ public class UF { } ``` -ַԿٽ union find ߳ȣĸ߶ΪĿ +这种方法可以快速进行 union 操作,但是 find 操作和树高成正比,最坏的情况下树的高度为触点的数目。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/d206d090-d911-4263-a1fe-d6f63f5d1776.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//d206d090-d911-4263-a1fe-d6f63f5d1776.png) -### 3. Ȩ quick-union 㷨 +### 3. 加权 quick-union 算法 -Ϊ˽ quick-union ܸͨߵ⣬Ȩ quick-union union ʱýСӽϴ档 +为了解决 quick-union 的树通常会很高的问题,加权 quick-union 在 union 操作时会让较小的树连接较大的树上面。 -о֤Ȩ quick-union 㷨಻ lgN +理论研究证明,加权 quick-union 算法构造的树深度最多不超过 lgN。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/8d6af5ac-74eb-4e07-99aa-654b9f21f1d3.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//8d6af5ac-74eb-4e07-99aa-654b9f21f1d3.jpg) ```java public class WeightedQuickUnionUF { private int[] id; - // ڵϢ + // 保存节点的数量信息 private int[] sz; public WeightedQuickUnionUF(int N) { @@ -484,25 +485,25 @@ public class WeightedQuickUnionUF { } ``` -### 4. ·ѹļȨ quick-union 㷨 +### 4. 路径压缩的加权 quick-union 算法 -ڼڵͬʱֱӵڵ㣬ֻҪ find һѭɡ +在检查节点的同时将它们直接链接到根节点,只需要在 find 中添加一个循环即可。 -### 5. union-find 㷨ıȽ +### 5. 各种 union-find 算法的比较 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/e5baeb38-0ec9-4ad7-8374-1cdb0dba74a6.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//e5baeb38-0ec9-4ad7-8374-1cdb0dba74a6.jpg) -# ڶ +# 第二章 排序 -## 㷨 +## 初级排序算法 -### 1. Լ +### 1. 约定 -ԪҪʵ Java Comparable ӿڣýӿ compareTo() +待排序的元素需要实现 Java 的 Comparable 接口,该接口有 compareTo() 方法。 -о㷨ijɱģʱDZȽϺͽĴ +研究排序算法的成本模型时,计算的是比较和交换的次数。 -ʹø less() exch() бȽϺͽIJʹôĿɶԺͿֲԸá +使用辅助函数 less() 和 exch() 来进行比较和交换的操作,使得代码的可读性和可移植性更好。 ```java private boolean less(Comparable v, Comparable w){ @@ -516,11 +517,11 @@ private void exch(Comparable[] a, int i, int j){ } ``` -### 2. ѡ +### 2. 选择排序 -ҵеСԪأȻĵһԪؽλáȻٴʣµԪҵСԪأĵڶԪؽλáϽIJֱ +找到数组中的最小元素,然后将它与数组的第一个元素交换位置。然后再从剩下的元素中找到最小的元素,将它与数组的第二个元素交换位置。不断进行这样的操作,直到将整个数组排序。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/222768a7-914f-4d64-b874-d98f3b926fb6.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//222768a7-914f-4d64-b874-d98f3b926fb6.jpg) ```java public class Selection { @@ -537,13 +538,13 @@ public class Selection { } ``` -ѡҪ \~N2/2 αȽϺ \~N νʱ޹أصʹһѾҲҪôıȽϺͽ +选择排序需要 \~N2/2 次比较和 \~N 次交换,它的运行时间与输入无关,这个特点使得它对一个已经排序的数组也需要这么多的比较和交换操作。 -### 3. +### 3. 插入排序 -һԪز뵽Уʹò֮ҲġҲÿԪأÿβ֮󲿵ġ +将一个元素插入到已排序的数组中,使得插入之后的数组也是有序的。插入排序从左到右插入每个元素,每次插入之后左部的子数组是有序的。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/065c3bbb-3ea0-4dbf-8f26-01d0e0ba7db7.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//065c3bbb-3ea0-4dbf-8f26-01d0e0ba7db7.png) ```java public class Insertion { @@ -558,23 +559,23 @@ public class Insertion { } ``` -ĸӶȡijʼ˳Ѿˣôܿ졣ƽ²Ҫ \~N2/4 ȽԼ \~N2/4 νҪ \~N2/2 ȽԼ \~N2/2 νģõҪ N-1 αȽϺ 0 νõѾˡ +插入排序的复杂度取决于数组的初始顺序,如果数组已经部分有序了,那么插入排序会很快。平均情况下插入排序需要 \~N2/4 比较以及 \~N2/4 次交换,最坏的情况下需要 \~N2/2 比较以及 \~N2/2 次交换,最坏的情况是数组是逆序的;而最好的情况下需要 N-1 次比较和 0 次交换,最好的情况就是数组已经有序了。 -ڲСģرЧ +插入排序对于部分有序数组和小规模数组特别高效。 -### 4. ѡͲıȽ +### 4. 选择排序和插入排序的比较 -ظ飬ѡʱƽģ֮һСij +对于随机排序的无重复主键的数组,插入排序和选择排序的运行时间是平方级别的,两者之比是一个较小的常数。 -### 5. ϣ +### 5. 希尔排序 -ڴģ飬ΪֻܽڵԪأҪԪشһƵһˣҪܶβ +对于大规模的数组,插入排序很慢,因为它只能交换相邻的元素,如果要把元素从一端移到另一端,就需要很多次操作。 -ϣij־Ϊ˸Ľ־ԣͨڵԪأʹԪظƵȷλϡ +希尔排序的出现就是为了改进插入排序的这种局限性,它通过交换不相邻的元素,使得元素更快的移到正确的位置上。 -ϣʹòԼ h н h ܴôԪؾܺܿƵԶĵطͨϼС h h=1Ϳʹġ +希尔排序使用插入排序对间隔 h 的序列进行排序,如果 h 很大,那么元素就能很快的移到很远的地方。通过不断减小 h,最后令 h=1,就可以使得整个数组是有序的。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/8320bad6-3f91-4a15-8e3d-68e8f39649b5.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//8320bad6-3f91-4a15-8e3d-68e8f39649b5.png) ```java public class Shell { @@ -596,15 +597,15 @@ public class Shell { } ``` -ϣʱﲻƽʹõ 1, 4, 13, 40, ... ϣҪıȽϴᳬ N ɱڵеijȡܵĸ߼㷨ֻϣҡ +希尔排序的运行时间达不到平方级别,使用递增序列 1, 4, 13, 40, ... 的希尔排序所需要的比较次数不会超过 N 的若干倍乘于递增序列的长度。后面介绍的高级排序算法只会比希尔排序快两倍左右。 -## 鲢 +## 归并排序 -鲢˼ǽֱֳ֣Ȼ鲢 +归并排序的思想是将数组分成两部分,分别进行排序,然后归并起来。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/dcf265ad-fe35-424d-b4b7-d149cdf239f4.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//dcf265ad-fe35-424d-b4b7-d149cdf239f4.png) -### 1. 鲢 +### 1. 归并方法 ```java public class MergeSort { @@ -614,20 +615,20 @@ public class MergeSort { int i = lo, j = mid + 1; for (int k = lo; k <= hi; k++) { - aux[k] = a[k]; // ݸƵ + aux[k] = a[k]; // 将数据复制到辅助数组 } for (int k = lo; k <= hi; k++) { if (i > mid) a[k] = aux[j++]; else if (j > hi) a[k] = aux[i++]; - else if (aux[i].compareTo(a[j]) < 0) a[k] = aux[i++]; // Ƚһ֤ȶ + else if (aux[i].compareTo(a[j]) < 0) a[k] = aux[i++]; // 先进行这一步,保证稳定性 else a[k] = aux[j++]; } } } ``` -### 2. Զ¹鲢 +### 2. 自顶向下归并排序 ```java public static void sort(Comparable[] a) { @@ -644,19 +645,19 @@ private static void sort(Comparable[] a, int lo, int hi) { } ``` -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/6468a541-3a9a-4008-82b6-03a0fe941d2a.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//6468a541-3a9a-4008-82b6-03a0fe941d2a.png) -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/c7665f73-c52f-4ce4-aed3-592bbd76265b.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//c7665f73-c52f-4ce4-aed3-592bbd76265b.png) -׿㷨ʱ临ӶΪ O(NlgN) +很容易看出该排序算法的时间复杂度为 O(NlgN)。 -ΪСĵݹƵʹòС齫øߵܡ +因为小数组的递归操作会过于频繁,因此使用插入排序来处理小数组将会获得更高的性能。 -### 3. ԵϹ鲢 +### 3. 自底向上归并排序 -ȹ鲢Щ΢飬ȻɶԹ鲢õ顣 +先归并那些微型数组,然后成对归并得到的子数组。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/c7b9b4c8-83d1-4eb0-8408-ea6576a9ed90.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//c7b9b4c8-83d1-4eb0-8408-ea6576a9ed90.png) ```java public static void busort(Comparable[] a) { @@ -670,13 +671,13 @@ public static void busort(Comparable[] a) { } ``` -## +## 快速排序 -### 1. 㷨 +### 1. 基本算法 -鲢Ϊֱ򣬲鲢ʹ򣻿ͨһзԪؽΪ飬СڵзԪأڵзԪأҲͽˡ +归并排序将数组分为两个子数组分别排序,并将有序的子数组归并使得整个数组排序;快速排序通过一个切分元素将数组分为两个子数组,左子数组小于等于切分元素,右子数组大于等于切分元素,将这两个子数组排序也就将整个数组排序了。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/61b4832d-71f3-413c-84b6-237e219b9fdc.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//61b4832d-71f3-413c-84b6-237e219b9fdc.png) ```java public class QuickSort { @@ -694,11 +695,11 @@ public class QuickSort { } ``` -### 2. з +### 2. 切分 -ȡ a[lo] ΪзԪأȻɨֱҵһڵԪأٴҶɨҵһСڵԪأԪأϼ̣ͿԱָ֤ԪضзԪأָ j ҲԪضСзԪءָʱзԪ a[lo] ҲԪ a[j] Ȼ󷵻 j ɡ +取 a[lo] 作为切分元素,然后从数组的左端向右扫描直到找到第一个大于等于它的元素,再从数组的右端向左扫描找到第一个小于等于它的元素,交换这两个元素,并不断继续这个过程,就可以保证左指针的左侧元素都不大于切分元素,右指针 j 的右侧元素都不小于切分元素。当两个指针相遇时,将切分元素 a[lo] 和左子数组最右侧的元素 a[j] 交换然后返回 j 即可。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/e198c201-f386-4491-8ad6-f7e433bf992d.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//e198c201-f386-4491-8ad6-f7e433bf992d.png) ```java private static int partition(Comparable[] a, int lo, int hi) { @@ -715,31 +716,31 @@ private static int partition(Comparable[] a, int lo, int hi) { } ``` -### 3. ܷ +### 3. 性能分析 -ԭ򣬲Ҫ飬ǵݹҪջ +快速排序是原地排序,不需要辅助数组,但是递归调用需要辅助栈。 -õÿζܽ԰֣ݹôٵġ±ȽϴΪ CN=2CN/2+NҲǸӶΪ O(NlgN) +快速排序最好的情况下是每次都正好能将数组对半分,这样递归调用次数才是最少的。这种情况下比较次数为 CN=2CN/2+N,也就是复杂度为 O(NlgN)。 -£һδСԪз֣ڶδӵڶСԪз֣㡣ҪȽ N2/2Ϊ˷ֹʼģڽпʱҪ顣 +最坏的情况下,第一次从最小的元素切分,第二次从第二小的元素切分,如此这般。因此最坏的情况下需要比较 N2/2。为了防止数组最开始就是有序的,在进行快速排序时需要随机打乱数组。 -### 4. 㷨Ľ +### 4. 算法改进 -#### 4.1 л +#### 4.1 切换到插入排序 -ΪСҲԼС飬ȿܸãСпл +因为快速排序在小数组中也会调用自己,对于小数组,插入排序比快速排序的性能更好,因此在小数组中可以切换到插入排序。 -#### 4.2 ȡ +#### 4.2 三取样 -õÿζȡλΪзԪأǼλĴۺܸߡǷȡ 3 ԪزСеԪΪзԪصЧá +最好的情况下是每次都能取数组的中位数作为切分元素,但是计算中位数的代价很高。人们发现取 3 个元素并将大小居中的元素作为切分元素的效果最好。 -#### 4.3 з +#### 4.3 三向切分 -дظԪص飬ԽзΪֱ֣ӦСڡںʹзԪء +对于有大量重复元素的数组,可以将数组切分为三部分,分别对应小于、等于和大于切分元素。 -зֻֿɲͬʱ +三向切分快速排序对于只有若干不同主键的随机数组可以在线性时间内完成排序。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/9d2226dc-c4a3-40ec-9b3e-a46bf86af499.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//9d2226dc-c4a3-40ec-9b3e-a46bf86af499.png) ```java public class Quick3Way { @@ -759,17 +760,17 @@ public class Quick3Way { } ``` -## ȶ +## 优先队列 -ȶҪڴԪء +优先队列主要用于处理最大元素。 -### 1. +### 1. 堆 -壺һŶÿڵ㶼ڵӽڵ㡣 +定义:一颗二叉树的每个节点都大于等于它的两个子节点。 -ѿʾΪһȫȫ׾ʹ洢Сλ k ĽڵĸڵλΪ k/2ӽڵλ÷ֱΪ 2k 2k+1DzʹΪ 0 λãΪ˸ڵĹϵ +堆可以用数组来表示,因为堆是一种完全二叉树,而完全二叉树很容易就存储在数组中。位置 k 的节点的父节点位置为 k/2,而它的两个子节点的位置分别为 2k 和 2k+1。这里我们不使用数组索引为 0 的位置,是为了更清晰地理解节点的关系。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/a9b6c1db-0f4a-4e91-8ac8-6b19bd106b51.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//a9b6c1db-0f4a-4e91-8ac8-6b19bd106b51.png) ```java public class MaxPQ { @@ -800,9 +801,9 @@ public class MaxPQ { } ``` -### 2. ϸ³ +### 2. 上浮和下沉 -ڶУһڵȸڵôҪڵ㡣󻹿ܱµĸڵҪϵؽбȽϺͽֲΪϸ +在堆中,当一个节点比父节点大,那么需要交换这个两个节点。交换后还可能比它新的父节点大,因此需要不断地进行比较和交换操作。把这种操作称为上浮。 ```java private void swim(int k) { @@ -813,7 +814,7 @@ private void swim(int k) { } ``` -ƵأһڵӽڵСҲҪϵ±ȽϺͽֲΪ³һڵӽڵ㣬Ӧӽڵôڵн +类似地,当一个节点比子节点来得小,也需要不断的向下比较和交换操作,把这种操作称为下沉。一个节点有两个子节点,应当与两个子节点中最大那么节点进行交换。 ```java private void sink(int k) { @@ -827,9 +828,9 @@ private void sink(int k) { } ``` -### 3. Ԫ +### 3. 插入元素 -ԪطŵĩβȻϸʵλá +将新元素放到数组末尾,然后上浮到合适的位置。 ```java public void insert(Key v) { @@ -838,9 +839,9 @@ public void insert(Key v) { } ``` -### 4. ɾԪ +### 4. 删除最大元素 -鶥ɾԪأһԪطŵˣԪ³ʵλá +从数组顶端删除最大的元素,并将数组的最后一个元素放到顶端,并让这个元素下沉到合适的位置。 ```java public Key delMax() { @@ -852,15 +853,15 @@ public Key delMax() { } ``` -### 5. +### 5. 堆排序 -ڶѿԺ׵õԪزɾϵؽֲԵõһݼСԪغ͵ǰһԪؽλãҲɾôͿԵõһβͷĵݼУһС˺ʹö򣬲Ҷԭ򣬲ռöռ䡣 +由于堆可以很容易得到最大的元素并删除它,不断地进行这种操作可以得到一个递减序列。如果把最大元素和当前堆中数组的最后一个元素交换位置,并且不删除它,那么就可以得到一个从尾到头的递减序列,从正向来看就是一个递增序列。因此很容易使用堆来进行排序,并且堆排序是原地排序,不占用额外空间。 -Ҫ׶Σһ׶ǰ齨һѣڶ׶ǽԪغ͵ǰѵһԪأҽ³άֶѵ״̬ +堆排序要分两个阶段,第一个阶段是把无序数组建立一个堆;第二个阶段是交换最大元素和当前堆的数组最后一个元素,并且进行下沉操作维持堆的有序状态。 -齨ֱӵķǴұ飬ȻϸһЧķǴ³һڵڵ㶼ѾǶô³ʹڵΪڵĶҶӽڵ㲻Ҫ³˿ԺҶӽڵԪأֻҪһԪؼɡ +无序数组建立堆最直接的方法是从左到右遍历数组,然后进行上浮操作。一个更高效的方法是从右至左进行下沉操作,如果一个节点的两个节点都已经是堆有序,那么进行下沉操作可以使得这个节点为根节点的堆有序。叶子节点不需要进行下沉操作,因此可以忽略叶子节点的元素,因此只需要遍历一半的元素即可。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/a2670745-a7b1-497b-90a4-dbddc4e2006d.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//a2670745-a7b1-497b-90a4-dbddc4e2006d.jpg) ```java public static void sort(Comparable[] a){ @@ -875,33 +876,33 @@ public static void sort(Comparable[] a){ } ``` -### 6. +### 6. 分析 -һѵĸ߶Ϊ lgNڶвԪغɾԪصĸӶȶΪ lgN +一个堆的高度为 lgN,因此在堆中插入元素和删除最大元素的复杂度都为 lgN。 -ڶҪ N ڵ³˸ӶΪ NlgN +对于堆排序,由于要对 N 个节点进行下沉操作,因此复杂度为 NlgN。 -ʱһԭûöĿռ䡣 +堆排序时一种原地排序,没有利用额外的空间。 -ִϵͳʹöΪ޷û棬ҲԪغٺڵԪؽбȽϡ +现代操作系统很少使用堆排序,因为它无法利用缓存,也就是数组元素很少和相邻的元素进行比较。 -## Ӧ +## 应用 -### 1. 㷨ıȽ +### 1. 排序算法的比较 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/be53c00b-2534-4dc6-ad03-c55995c47db9.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//be53c00b-2534-4dc6-ad03-c55995c47db9.jpg) -ʱͨ㷨ѭָ٣û棬Ϊ˳طݡʱΪ \~cNlgN c Զ㷨ҪСʹз֮ʵӦпֵܳijЩֲܹﵽԼ𣬶㷨ȻҪԶʱ䡣 +快速排序时最快的通用排序算法,它的内循环的指令很少,而且它还能利用缓存,因为它总是顺序地访问数据。它的运行时间增长数量级为 \~cNlgN,这里的 c 比其他线性对数级别的排序算法都要小。使用三向切分之后,实际应用中可能出现的某些分布的输入能够达到线性级别,而其它排序算法仍然需要线性对数时间。 -### 2. Java 㷨ʵ +### 2. Java 的排序算法实现 -Java ϵͳеҪ򷽷Ϊ java.util.Arrays.sort()ԭʼʹзֵĿ򣬶ʹù鲢 +Java 系统库中的主要排序方法为 java.util.Arrays.sort(),对于原始数据类型使用三向切分的快速排序,对于引用类型使用归并排序。 -### 3. зֵĿѡ㷨 +### 3. 基于切分的快速选择算法 - partition() Ὣ a[lo] a[hi] 򲢷һ j ʹ a[lo..j-1] Сڵ a[j] a[j+1..hi] ڵ a[j]ô j=ka[j] ǵ k +快速排序的 partition() 方法,会将数组的 a[lo] 至 a[hi] 重新排序并返回一个整数 j 使得 a[lo..j-1] 小于等于 a[j],且 a[j+1..hi] 大于等于 a[j]。那么如果 j=k,a[j] 就是第 k 个数。 -㷨ԼģΪÿý֣ôȽϵܴΪ (N+N/2+N/4+..)ֱҵ k ԪأȻС 2N +该算法是线性级别的,因为每次正好将数组二分,那么比较的总次数为 (N+N/2+N/4+..),直到找到第 k 个元素,这个和显然小于 2N。 ```java public static Comparable select(Comparable[] a, int k) { @@ -916,33 +917,33 @@ public static Comparable select(Comparable[] a, int k) { } ``` -# +# 第三章 查找 -ʹ־ʵָЧķűɢб +本章使用三种经典的数据类型来实现高效的符号表:二叉查找树、红黑树和散列表。 -## ű +## 符号表 -### 1. ű +### 1. 无序符号表 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/b69d7184-ab62-4957-ba29-fb4fa25f9b65.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//b69d7184-ab62-4957-ba29-fb4fa25f9b65.jpg) -### 2. ű +### 2. 有序符号表 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/ba6ae411-82da-4d86-a434-6776d1731e8e.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//ba6ae411-82da-4d86-a434-6776d1731e8e.jpg) -űļҪʵ Comparable ӿڡ +有序符号表的键需要实现 Comparable 接口。 -ҵijɱģͣıȽϴڲбȽʱʹķʴ +查找的成本模型:键的比较次数,在不进行比较时使用数组的访问次数。 -### 3. ֲʵű +### 3. 二分查找实现有序符号表 -ʹһƽ飬һ洢һ洢ֵ +使用一对平行数组,一个存储键一个存储值。 -Ҫһ Key ͵ Comparable һ Value ͵ Object 顣 +需要创建一个 Key 类型的 Comparable 对象数组和一个 Value 类型的 Object 对象数组。 -rank() Ҫڱʱܹ֪üλãڱʱҲ֪ںδ¼ +rank() 方法至关重要,当键在表中时,它能够知道该键的位置;当键不在表中时,它也能知道在何处插入新键。 -ӶȣֲҪ lgN+1 αȽϣʹöֲʵֵķűIJҲҪʱǶġDzҪƶԪأԼġ +复杂度:二分查找最多需要 lgN+1 次比较,使用二分查找实现的符号表的查找操作所需要的时间最多是对数级别的。但是插入操作需要移动数组元素,是线性级别的。 ```java public class BinarySearchST, Value> { @@ -1001,15 +1002,15 @@ public class BinarySearchST, Value> { } ``` -## +## 二叉查找树 -**** ΪһӣһӵĽڵ㣬ÿӶָһӶ +**二叉树** 定义为一个空链接,或者是一个有左右两个链接的节点,每个链接都指向一颗子二叉树。 -****BSTһŶÿڵļеڵļСڵļ +**二叉查找树**(BST)是一颗二叉树,并且每个节点的键都大于其左子树中的任意节点的键而小于右子树的任意节点的键。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/25226bb2-92cc-40cb-9e7f-c44e79fbb64a.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//25226bb2-92cc-40cb-9e7f-c44e79fbb64a.jpg) -IJҲÿεһ룬Ͷֲơ +二叉查找树的查找操作每次迭代都会让区间减少一半,和二分查找类似。 ```java public class BST, Value> { @@ -1019,7 +1020,7 @@ public class BST, Value> { private Key key; private Value val; private Node left, right; - // ԸýڵΪнڵ + // 以该节点为根的子树中节点总数 private int N; public Node(Key key, Value val, int N) { @@ -1042,7 +1043,7 @@ public class BST, Value> { ### 1. get() -ǿյģδУҵļ͸ڵļȣУݹвңҵļСвңϴвҡ +如果树是空的,则查找未命中;如果被查找的键和根节点的键相等,查找命中,否则递归地在子树中查找:如果被查找的键较小就在左子树中查找,较大就在右子树中查找。 ```java public Value get(Key key) { @@ -1059,7 +1060,7 @@ private Value get(Node x, Key key) { ### 2. put() -ļУҪһ½ڵ㣬ҸϲڵʹøýڵȷӵС +当插入的键不存在于树中,需要创建一个新节点,并且更新上层节点的链接使得该节点正确链接到树中。 ```java public void put(Key key, Value val) { @@ -1076,17 +1077,17 @@ private Node put(Node x, Key key, Value val) { } ``` -### 3. +### 3. 分析 -㷨ʱȡ״״ȡڼȺ˳õȫƽģÿӺ͸ڵľ붼Ϊ lgN£ĸ߶Ϊ N +二叉查找树的算法运行时间取决于树的形状,而树的形状又取决于键被插入的先后顺序。最好的情况下树是完全平衡的,每条空链接和根节点的距离都为 lgN。在最坏的情况下,树的高度为 N。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/73a3983d-dd18-4373-897e-64b706a7e370.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//73a3983d-dd18-4373-897e-64b706a7e370.jpg) -ӶȣҺͲΪ +复杂度:查找和插入操作都为对数级别。 ### 4. floor() - key Сڸڵ keyôСڵ key ڵһУ key ڸڵ keyֻеڵдСڵ key Ľڵ㣬Сڵ key ڵУڵСڵ key ڵ㡣 +如果 key 小于根节点的 key,那么小于等于 key 的最大键节点一定在左子树中;如果 key 大于根节点的 key,只有当根节点右子树中存在小于等于 key 的节点,小于等于 key 的最大键节点才在右子树中,否则根节点就是小于等于 key 的最大键节点。 ```java public Key floor(Key key) { @@ -1134,9 +1135,9 @@ private Node min(Node x) { ### 7. deleteMin() -ָСڵָСڵ +令指向最小节点的链接指向最小节点的右子树。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/6e2cb20a-8d2a-46fe-9ac7-68a2126b7bd5.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//6e2cb20a-8d2a-46fe-9ac7-68a2126b7bd5.jpg) ```java public void deleteMin() { @@ -1152,9 +1153,9 @@ public Node deleteMin(Node x) { ### 8. delete() -ɾĽڵֻһôֻҪָɾڵָΨһɣСڵ滻ýڵ㡣 +如果待删除的节点只有一个子树,那么只需要让指向待删除节点的链接指向唯一的子树即可;否则,让右子树的最小节点替换该节点。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/b488282d-bfe0-464f-9e91-1f5b83a975bd.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//b488282d-bfe0-464f-9e91-1f5b83a975bd.jpg) ```java public void delete(Key key) { @@ -1180,7 +1181,7 @@ private Node delete(Node x, Key key) { ### 9. keys() -öĽΪеص㡣 +利用二叉查找树中序遍历的结果为有序序列的特点。 ```java public Iterable keys(Key lo, Key hi) { @@ -1198,44 +1199,44 @@ private void keys(Node x, Queue queue, Key lo, Key hi) { } ``` -### 10. ܷ +### 10. 性能分析 -ӶȣвҪʱ䶼ĸ߶ȳȡ +复杂度:二叉查找树所有操作在最坏的情况下所需要的时间都和树的高度成正比。 -## ƽ +## 平衡查找树 -### 2-3 +### 2-3 查找树 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/2548f2ec-7b00-4ec7-b286-20fc3022e084.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//2548f2ec-7b00-4ec7-b286-20fc3022e084.jpg) -һƽ 2-3 пӵڵľӦͬġ +一颗完美平衡的 2-3 查找树的所有空链接到根节点的距离应该是相同的。 -#### 1. +#### 1. 插入操作 -֮һʱ 4- ڵʱҪ 4- ڵѳ 3 2- ڵ㣬м 2- ڵƵϲڵСƲʱ 4- ڵһֱзƣֱʱ 4- ڵ㡣 +当插入之后产生一个临时 4- 节点时,需要将 4- 节点分裂成 3 个 2- 节点,并将中间的 2- 节点移到上层节点中。如果上移操作继续产生临时 4- 节点则一直进行分裂上移,直到不存在临时 4- 节点。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/912174d8-0786-4222-b7ef-a611d36e5db9.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//912174d8-0786-4222-b7ef-a611d36e5db9.jpg) -#### 2. +#### 2. 性质 -2-3 ı任ǾֲģصĽڵ֮ⲻ޸Ļ߼֣Щֲ任ӰȫԺƽԡ +2-3 查找树插入操作的变换都是局部的,除了相关的节点和链接之外不必修改或者检查树的其它部分,而这些局部变换不会影响树的全局有序性和平衡性。 -2-3 IJҺͲӶȺͲ˳ **޹**²ҺͲʵĽڵȻ logN 10 ڸڵ 2-3 ֻҪ 30 ڵܽIJҺͲ +2-3 查找树的查找和插入操作复杂度和插入顺序 **无关**,在最坏的情况下查找和插入操作访问的节点必然不超过 logN 个,含有 10 亿个节点的 2-3 查找树最多只需要访问 30 个节点就能进行任意的查找和插入操作。 -### ڶ +### 红黑二叉查找树 -2-3 Ҫõ 2- ڵ 3- ڵ㣬ʹúʵ 3- ڵ㡣ָһڵɫΪɫôڵϲڵʾһ 3- ڵ㣬ɫͨӡ +2-3 查找树需要用到 2- 节点和 3- 节点,红黑树使用红链接来实现 3- 节点。指向一个节点的链接颜色如果为红色,那么这个节点和上层节点表示的是一个 3- 节点,而黑色则是普通链接。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/7080a928-06ba-4e10-9792-b8dd190dc8e2.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//7080a928-06ba-4e10-9792-b8dd190dc8e2.jpg) -ʣ +红黑树具有以下性质: -1. ӶΪӣ -2. ɫƽ⣬ӵڵ·ϵĺͬ +1. 红链接都为左链接; +2. 完美黑色平衡,即任意空链接到根节点的路径上的黑链接数量相同。 -ʱԽӻƽ +画红黑树时可以将红链接画平。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/62077f5d-a06d-4129-9b43-78715b82cb03.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//62077f5d-a06d-4129-9b43-78715b82cb03.png) ```java public class RedBlackBST, Value> { @@ -1265,13 +1266,13 @@ public class RedBlackBST, Value> { } ``` -#### 1. ת +#### 1. 左旋转 -ΪϷĺӶΪӣΪӣôҪת +因为合法的红链接都为左链接,如果出现右链接为红链接,那么就需要进行左旋转操作。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/33a4e822-2dd0-481e-ac89-7f6161034402.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//33a4e822-2dd0-481e-ac89-7f6161034402.jpg) -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/5e0cef33-4087-4f21-a428-16d5fddda671.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//5e0cef33-4087-4f21-a428-16d5fddda671.jpg) ```java public Node rotateLeft(Node h) { @@ -1286,13 +1287,13 @@ public Node rotateLeft(Node h) { } ``` -#### 2. ת +#### 2. 右旋转 -תΪתӣ֮IJ̽֡ +进行右旋转是为了转换两个连续的左红链接,这会在之后的插入过程中探讨。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/dfd078b2-aa4f-4c50-8319-232922d822b8.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//dfd078b2-aa4f-4c50-8319-232922d822b8.jpg) -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/3f8d8c9d-a9a9-4d7a-813c-2de05ee5a97e.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//3f8d8c9d-a9a9-4d7a-813c-2de05ee5a97e.jpg) ```java public Node rotateRight(Node h) { @@ -1306,13 +1307,13 @@ public Node rotateRight(Node h) { } ``` -#### 3. ɫת +#### 3. 颜色转换 -һ 4- ڵںбΪһڵӽڵ㶼Ǻɫġ 4- ڵҪӽڵɫɺ֮⣬ͬʱҪڵɫɺڱ죬 2-3 ĽǶȿǽмڵƵϲڵ㡣 +一个 4- 节点在红黑树中表现为一个节点的左右子节点都是红色的。分裂 4- 节点除了需要将子节点的颜色由红变黑之外,同时需要将父节点的颜色由黑变红,从 2-3 树的角度看就是将中间节点移到上层节点。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/de7c5a31-55f5-4e9d-92ec-4ed5b2ec3828.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//de7c5a31-55f5-4e9d-92ec-4ed5b2ec3828.jpg) -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/e5ad625e-729d-4a8d-923a-7c3df5773e1c.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//e5ad625e-729d-4a8d-923a-7c3df5773e1c.jpg) ```java void flipColors(Node h){ @@ -1322,15 +1323,15 @@ void flipColors(Node h){ } ``` -#### 4. +#### 4. 插入 -Ƚһڵ㰴ķ뵽ȷλãȻٽɫ +先将一个节点按二叉查找树的方法插入到正确位置,然后再进行如下颜色操作: -- ӽڵǺɫĶӽڵǺɫģת -- ӽڵǺɫӽڵҲǺɫģת -- ӽڵΪɫģɫת +- 如果右子节点是红色的而左子节点是黑色的,进行左旋转; +- 如果左子节点是红色的且它的左子节点也是红色的,进行右旋转; +- 如果左右子节点均为红色的,进行颜色转换。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/40639782-5df2-4e96-a4f3-f9dd664d0ca1.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//40639782-5df2-4e96-a4f3-f9dd664d0ca1.jpg) ```java public void put(Key key, Value val) { @@ -1354,55 +1355,55 @@ private Node put(Node x, Key key, Value val) { } ``` -Կò BST IJƣֻתɫ任ɡ +可以看到该插入操作和 BST 的插入操作类似,只是在最后加入了旋转和颜色变换操作即可。 -ڵһΪɫΪڵûϲڵ㣬Ҳûϲڵָڵ㡣flipColors() пܻʹøڵɫΪɫÿڵɺɫɺɫʱĺӸ߶ȼ 1. +根节点一定为黑色,因为根节点没有上层节点,也就没有上层节点的左链接指向根节点。flipColors() 有可能会使得根节点的颜色变为红色,每当根节点由红色变成黑色时树的黑链接高度加 1. -#### 5. ɾС +#### 5. 删除最小键 -Сһ 2- ڵУôɾüһӣƻƽԣҪȷС 2- ڵС 2- ڵת 3- ڵ 4- ڵַһϲڵһ keyһֵܽڵһ keyϲڵ 2- ڵ㣬ôû취ϲڵ key ˣҪ֤ɾ·ϵнڵ㶼 2- ڵ㡣ɾĹУ֤֮һ +如果最小键在一个 2- 节点中,那么删除该键会留下一个空链接,就破坏了平衡性,因此要确保最小键不在 2- 节点中。将 2- 节点转换成 3- 节点或者 4- 节点有两种方法,一种是向上层节点拿一个 key,一种是向兄弟节点拿一个 key。如果上层节点是 2- 节点,那么就没办法从上层节点拿 key 了,因此要保证删除路径上的所有节点都不是 2- 节点。在向下删除的过程中,保证以下情况之一发生: -1. ǰڵӽڵ㲻 2- ڵ㣬ɣ -2. ǰڵӽڵ 2- ڵֵܽڵ㲻 2- ڵ㣬ֵܽڵһ key -3. ǰڵӽڵֵܽڵ㶼 2- ڵ㣬ӽڵ㡢ڵеСֵܽڵϲΪһ 4- ڵ㡣 +1. 如果当前节点的左子节点不是 2- 节点,完成; +2. 如果当前节点的左子节点是 2- 节点而它的兄弟节点不是 2- 节点,向兄弟节点拿一个 key 过来; +3. 如果当前节点的左子节点和它的兄弟节点都是 2- 节点,将左子节点、父节点中的最小键和最近的兄弟节点合并为一个 4- 节点。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/b001fa64-307c-49af-b4b2-2043fc26154e.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//b001fa64-307c-49af-b4b2-2043fc26154e.png) -õһС 3- ڵ 4- ڵ㣬ֱӴɾȻٴͷֽʱ 4- ڵ㡣 +最后得到一个含有最小键的 3- 节点或者 4- 节点,直接从中删除。然后再从头分解所有临时的 4- 节点。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/70b66757-755c-4e17-a7b7-5ce808023643.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//70b66757-755c-4e17-a7b7-5ce808023643.png) -#### 6. +#### 6. 分析 -һŴСΪ N ĺĸ߶Ȳᳬ 2lgNӦ 2-3 йߵ·ڵȫ 3- ڵ඼ 2- ڵ㡣 +一颗大小为 N 的红黑树的高度不会超过 2lgN。最坏的情况下是它所对应的 2-3 树中构成最左边的路径节点全部都是 3- 节点而其余都是 2- 节点。 -IJҪʱ䶼Ƕġ +红黑树大多数的操作所需要的时间都是对数级别的。 -## ɢб +## 散列表 -ɢб飬԰ɢбɢֵֵɢбͷԪһ٣ڳʱʵֲҺͲķű +散列表类似于数组,可以把散列表的散列值看成数组的索引值。访问散列表和访问数组元素一样快速,它可以在常数时间内实现查找和插入的符号表。 -޷ͨɢֵ֪ĴСϵɢб޷ʵԲ +由于无法通过散列值知道键的大小关系,因此散列表无法实现有序性操作。 -### 1. ɢк +### 1. 散列函数 -һСΪ M ɢбɢкܹתΪ [0, M-1] ڵΪ hash ֵ +对于一个大小为 M 的散列表,散列函数能够把任意键转换为 [0, M-1] 内的正整数,该正整数即为 hash 值。 -ɢбгͻĴڣҲͬļͬ hash ֵ +散列表有冲突的存在,也就是两个不同的键可能有相同的 hash 值。 -ɢкӦ +散列函数应该满足以下三个条件: -1. һԣȵļӦȵ hash ֵ -2. ЧԣӦ㣬бҪĻ԰ hash ֵڵ hash ʱֱӷء -3. ԣм hash ֵӦȵطֲ [0, M-1] ֮䣬ҪֱӰ쵽ɢбܡ +1. 一致性:相等的键应当有相等的 hash 值。 +2. 高效性:计算应当简便,有必要的话可以把 hash 值缓存起来,在调用 hash 函数时直接返回。 +3. 均匀性:所有键的 hash 值应当均匀地分布到 [0, M-1] 之间,这个条件至关重要,直接影响到散列表的性能。 -Խɢе [0, M-1] ֮䣬һ k k%M ȿɵõһ [0, M-1] ֮ hash ֵע M һ޷üϢ M Ϊ 10kôֻüĺ k λ +除留余数法可以将整数散列到 [0, M-1] 之间,例如一个正整数 k,计算 k%M 既可得到一个 [0, M-1] 之间的 hash 值。注意 M 必须是一个素数,否则无法利用键包含的所有信息。例如 M 为 10k,那么只能利用键的后 k 位。 -ԽתʽȻóڸԽʾɶʽȻʹöʽֵг +对于其它数,可以将其转换成整数的形式,然后利用除留余数法。例如对于浮点数,可以将其表示成二进制形式,然后使用二进制形式的整数值进行除留余数法。 -жಿϵļÿֶҪ hash ֵϲʱҪÿ hash ֵͬҪĵλԽü R ƵÿֶвͬȨֵ +对于有多部分组合的键,每部分都需要计算 hash 值,并且最后合并时需要让每部分 hash 值都具有同等重要的地位。可以将该键看成 R 进制的整数,键中每部分都具有不同的权值。 -磬ַɢкʵ +例如,字符串的散列函数实现如下 ```java int hash = 0; @@ -1410,21 +1411,21 @@ for(int i = 0; i < s.length(); i++) hash = (R * hash + s.charAt(i)) % M; ``` -ٱ磬ӵжԱԶĹϣ +再比如,拥有多个成员的自定义类的哈希函数如下 ```java int hash = (((day * R + month) % M) * R + year) % M; ``` -R ֵǺҪͨȡ 31 +R 的值不是很重要,通常取 31。 -Java е hashCode() ʵ hash Ĭʹöڴֵַʹ hashCode() ʱӦϳʹáΪڴַ 32 λֻҪ 31 λķǸӦηλ֮ʹó +Java 中的 hashCode() 实现了 hash 函数,但是默认使用对象的内存地址值。在使用 hashCode() 函数时,应当结合除留余数法来使用。因为内存地址是 32 位整数,我们只需要 31 位的非负整数,因此应当屏蔽符号位之后再使用除留余数法。 ```java int hash = (x.hashCode() & 0x7fffffff) % M; ``` -ʹ Java Դ HashMap ԴĹϣʵʱֻҪȥʵ Key ͵ hashCode() ɡJava 涨 hashCode() ܹȷֲе 32 λJava е StringInteger ȶ hashCode() ʵһ㡣չʾԶʵ hashCode() +使用 Java 自带的 HashMap 等自带的哈希表实现时,只需要去实现 Key 类型的 hashCode() 函数即可。Java 规定 hashCode() 能够将键均匀分布于所有的 32 位整数,Java 中的 String、Integer 等对象的 hashCode() 都能实现这一点。以下展示了自定义类型如何实现 hashCode()。 ```java public class Transaction{ @@ -1442,19 +1443,19 @@ public class Transaction{ } ``` -### 2. ɢб +### 2. 基于拉链法的散列表 -ʹ洢 hash ֵͬļӶͻʱҪȲ Key ڵȻ˳ҡ +拉链法使用链表来存储 hash 值相同的键,从而解决冲突。此时查找需要分两步,首先查找 Key 所在的链表,然后在链表中顺序查找。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/540133af-aaaf-4208-8f7f-33cb89ac9621.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//540133af-aaaf-4208-8f7f-33cb89ac9621.png) - N M (N>M)ϣܹԵÿĴС N/MδеIJҺͲҪıȽϴΪ \~N/M +对于 N 个键,M 条链表 (N>M),如果哈希函数能够满足均匀性的条件,每条链表的大小趋向于 N/M,因此未命中的查找和插入操作所需要的比较次数为 \~N/M。 -### 3. ̽ⷨɢб +### 3. 基于线性探测法的散列表 -̽ⷨʹÿλͻͻʱǰ̽һλ洢ͻļʹ߳̽ⷨĴС M Ӧڼĸ NM>N) +线性探测法使用空位来解决冲突,当冲突发生时,向前探测一个空位来存储冲突的键。使用线程探测法,数组的大小 M 应当大于键的个数 N(M>N)。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/2b3410f1-9559-4dd1-bc3d-e3e572247be2.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//2b3410f1-9559-4dd1-bc3d-e3e572247be2.png) ```java public class LinearProbingHashST { @@ -1483,7 +1484,7 @@ public class LinearProbingHashST { } ``` -#### 3.1 +#### 3.1 查找 ```java public Value get(Key key) { @@ -1496,7 +1497,7 @@ public Value get(Key key) { } ``` -#### 3.2 +#### 3.2 插入 ```java public void put(Key key, Value val) { @@ -1514,9 +1515,9 @@ public void put(Key key, Value val) { } ``` -#### 3.3 ɾ +#### 3.3 删除 -ɾӦҲڵļֵ²ɢбС +删除操作应当将右侧所有相邻的键值重新插入散列表中。 ```java public void delete(Key key) { @@ -1542,15 +1543,15 @@ public void delete(Key key) { } ``` -#### 3.4 С +#### 3.4 调整数组大小 -̽ⷨijɱȡĿijȣĿҲо۴ء۴غܳʱڲҺͲʱҲҪкܶ̽⡣ +线性探测法的成本取决于连续条目的长度,连续条目也叫聚簇。当聚簇很长时,在查找和插入时也需要进行很多次探测。 - = N/M Ϊʡ֤ С 1/2 ʱ̽Ԥƴֻ 1.5 2.5 ֮䡣 +α = N/M,把 α 称为利用率。理论证明,当 α 小于 1/2 时探测的预计次数只在 1.5 到 2.5 之间。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/0ddebc5c-7c24-46b1-98db-4fa5e54db16b.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//0ddebc5c-7c24-46b1-98db-4fa5e54db16b.png) -Ϊ˱֤ɢбܣӦĴСʹ [1/4, 1/2] ֮䡣 +为了保证散列表的性能,应当调整数组的大小,使得 α 在 [1/4, 1/2] 之间。 ```java private void resize() { @@ -1571,29 +1572,29 @@ private void resize(int cap) { } ``` -Ȼÿµ鶼Ҫ°ÿֵԲ뵽ɢбǴ̯ĽǶҪĴȴǺСġͼԿÿ鳤ȼӱۼƽֵ 1ΪÿҪ¼ɢֵƽֵ½ +虽然每次重新调整数组都需要重新把每个键值对插入到散列表,但是从摊还分析的角度来看,所需要的代价却是很小的。从下图可以看出,每次数组长度加倍后,累计平均值都会增加 1,因为表中每个键都需要重新计算散列值,但是随后平均值会下降。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/01658047-0d86-4a7a-a8ca-7ea20fa1fdde.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//01658047-0d86-4a7a-a8ca-7ea20fa1fdde.png) -## Ӧ +## 应用 -### 1. ַűʵֵıȽ +### 1. 各种符号表实现的比较 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/9ee83c8c-1165-476c-85a6-e6e434e5307a.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//9ee83c8c-1165-476c-85a6-e6e434e5307a.jpg) -ӦȿɢбҪԲʱʹú +应当优先考虑散列表,当需要有序性操作时使用红黑树。 -### 2. Java ķűʵ +### 2. Java 的符号表实现 -Java java.util.TreeMap java.util.HashMap ֱǻںɢбķűʵ֡ +Java 的 java.util.TreeMap 和 java.util.HashMap 分别是基于红黑树和拉链法的散列表的符号表实现。 -### 3. +### 3. 集合类型 -˷űҲʹãֻмûֵü洢һϵеļȻжһǷڼС +除了符号表,集合类型也经常使用,它只有键没有值,可以用集合类型来存储一系列的键然后判断一个键是否在集合中。 -### 4. ϡ˷ +### 4. 稀疏向量乘法 -Ϊϡʱʹ÷ű洢еķ 0 ֵʹó˷ֻҪЩ 0 Ԫؽмɡ +当向量为稀疏向量时,可以使用符号表来存储向量中的非 0 索引和值,使得乘法运算只需要对那些非 0 元素进行即可。 ```java import java.util.HashMap; diff --git a/notes/计算机操作系统.md b/notes/计算机操作系统.md index 2bbd65c1..19820759 100644 --- a/notes/计算机操作系统.md +++ b/notes/计算机操作系统.md @@ -1,248 +1,249 @@ -* [һ ](#һ-) - * [ϵͳ](#ϵͳ) - * [1. ](#1-) - * [2. ](#2-) - * [3. ](#3-) - * [4. 첽](#4-첽) - * [ϵͳ](#ϵͳ) - * [жϷ](#жϷ) - * [1. ж](#1-ж) - * [2. 쳣](#2-쳣) - * [3. ](#3-) - * [ں˺΢ں](#ں˺΢ں) - * [1. ں](#1-ں) - * [2. ΢ں](#2-΢ں) -* [ڶ ̹](#ڶ-̹) - * [߳](#߳) - * [1. ](#1-) - * [2. ߳](#2-߳) - * [3. ](#3-) - * [״̬л](#״̬л) - * [㷨](#㷨) - * [1. ϵͳеĵ](#1-ϵͳеĵ) - * [1.1 ȷ](#11-ȷ) - * [1.2 ҵ](#12-ҵ) - * [1.3 ʣʱ](#13-ʣʱ) - * [2. ʽϵͳеĵ](#2-ʽϵͳеĵ) - * [2.1 Ȩ](#21-Ȩ) - * [2.2 ʱƬת](#22-ʱƬת) - * [2.3 ༶](#23-༶) - * [2.4 ̽](#24-̽) - * [3. ʵʱϵͳеĵ](#3-ʵʱϵͳеĵ) - * [ͬ](#ͬ) - * [1. ٽ](#1-ٽ) - * [2. ͬ뻥](#2-ͬ뻥) - * [3. ź](#3-ź) - * [4. ܳ](#4-ܳ) - * [ͨ](#ͨ) - * [1. ܵ](#1-ܵ) - * [2. ź](#2-ź) - * [3. Ϣ](#3-Ϣ) - * [4. ź](#4-ź) - * [5. ڴ](#5-ڴ) - * [6. ׽](#6-׽) - * [ͬ](#ͬ) - * [1. -д](#1--д) - * [2. ѧҽ](#2-ѧҽ) -* [ ](#-) - * [](#) - * [Ĵ](#Ĵ) - * [1. ](#1-) - * [2. Ԥ](#2-Ԥ) - * [2.1 ƻ](#21-ƻ) - * [2.2 ƻ뱣](#22-ƻ뱣) - * [2.3 ƻռ](#23-ƻռ) - * [2.4 ƻ·ȴ](#24-ƻ·ȴ) - * [3. ](#3-) - * [3.1 ȫ״̬](#31-ȫ״̬) - * [3.2 Դм㷨](#32-Դм㷨) - * [3.3 Դм㷨](#33-Դм㷨) - * [4. ָ](#4-ָ) - * [4.1 㷨](#41-㷨) - * [4.2 ָ](#42-ָ) -* [ 洢](#-洢) - * [ڴ](#ڴ) - * [ҳֶ](#ҳֶ) - * [1. ҳ](#1-ҳ) - * [2. ֶ](#2-ֶ) - * [3. ҳʽ](#3-ҳʽ) - * [4. ҳֶ](#4-ҳֶ) - * [ҳû㷨](#ҳû㷨) - * [1. ѣOptimal](#1-optimal) - * [2. ȽȳFIFO](#2-Ƚȳfifo) - * [3. δʹãLRU, Least Recently Used](#3-δʹlru,-least-recently-used) - * [4. ʱӣClock](#4-ʱclock) -* [ 豸](#-豸) - * [̵㷨](#̵㷨) - * [1. ȷFCFS, First Come First Serverd](#1-ȷfcfs,-first-come-first-serverd) - * [2. ѰʱȣSSTF, Shortest Seek Time First](#2-Ѱʱsstf,-shortest-seek-time-first) - * [3. ɨ㷨SCAN](#3-ɨ㷨scan) - * [4. ѭɨ㷨CSCAN](#4-ѭɨ㷨cscan) -* [ο](#ο) +* [第一章 概述](#第一章-概述) + * [操作系统基本特征](#操作系统基本特征) + * [1. 并发](#1-并发) + * [2. 共享](#2-共享) + * [3. 虚拟](#3-虚拟) + * [4. 异步](#4-异步) + * [系统调用](#系统调用) + * [中断分类](#中断分类) + * [1. 外中断](#1-外中断) + * [2. 异常](#2-异常) + * [3. 陷入](#3-陷入) + * [大内核和微内核](#大内核和微内核) + * [1. 大内核](#1-大内核) + * [2. 微内核](#2-微内核) +* [第二章 进程管理](#第二章-进程管理) + * [进程与线程](#进程与线程) + * [1. 进程](#1-进程) + * [2. 线程](#2-线程) + * [3. 区别](#3-区别) + * [进程状态的切换](#进程状态的切换) + * [调度算法](#调度算法) + * [1. 批处理系统中的调度](#1-批处理系统中的调度) + * [1.1 先来先服务](#11-先来先服务) + * [1.2 短作业优先](#12-短作业优先) + * [1.3 最短剩余时间优先](#13-最短剩余时间优先) + * [2. 交互式系统中的调度](#2-交互式系统中的调度) + * [2.1 优先权优先](#21-优先权优先) + * [2.2 时间片轮转](#22-时间片轮转) + * [2.3 多级反馈队列](#23-多级反馈队列) + * [2.4 短进程优先](#24-短进程优先) + * [3. 实时系统中的调度](#3-实时系统中的调度) + * [进程同步](#进程同步) + * [1. 临界区](#1-临界区) + * [2. 同步与互斥](#2-同步与互斥) + * [3. 信号量](#3-信号量) + * [4. 管程](#4-管程) + * [进程通信](#进程通信) + * [1. 管道](#1-管道) + * [2. 信号量](#2-信号量) + * [3. 消息队列](#3-消息队列) + * [4. 信号](#4-信号) + * [5. 共享内存](#5-共享内存) + * [6. 套接字](#6-套接字) + * [经典同步问题](#经典同步问题) + * [1. 读者-写者问题](#1-读者-写者问题) + * [2. 哲学家进餐问题](#2-哲学家进餐问题) +* [第三章 死锁](#第三章-死锁) + * [死锁的条件](#死锁的条件) + * [死锁的处理方法](#死锁的处理方法) + * [1. 鸵鸟策略](#1-鸵鸟策略) + * [2. 死锁预防](#2-死锁预防) + * [2.1 破坏互斥条件](#21-破坏互斥条件) + * [2.2 破坏请求与保持条件](#22-破坏请求与保持条件) + * [2.3 破坏不可抢占条件](#23-破坏不可抢占条件) + * [2.4 破坏环路等待](#24-破坏环路等待) + * [3. 死锁避免](#3-死锁避免) + * [3.1 安全状态](#31-安全状态) + * [3.2 单个资源的银行家算法](#32-单个资源的银行家算法) + * [3.3 多个资源的银行家算法](#33-多个资源的银行家算法) + * [4. 死锁检测与死锁恢复](#4-死锁检测与死锁恢复) + * [4.1 死锁检测算法](#41-死锁检测算法) + * [4.2 死锁恢复](#42-死锁恢复) +* [第四章 存储器管理](#第四章-存储器管理) + * [虚拟内存](#虚拟内存) + * [分页与分段](#分页与分段) + * [1. 分页](#1-分页) + * [2. 分段](#2-分段) + * [3. 段页式](#3-段页式) + * [4. 分页与分段区别](#4-分页与分段区别) + * [页面置换算法](#页面置换算法) + * [1. 最佳(Optimal)](#1-最佳optimal) + * [2. 先进先出(FIFO)](#2-先进先出fifo) + * [3. 最近最久未使用(LRU, Least Recently Used)](#3-最近最久未使用lru,-least-recently-used) + * [4. 时钟(Clock)](#4-时钟clock) +* [第五章 设备管理](#第五章-设备管理) + * [磁盘调度算法](#磁盘调度算法) + * [1. 先来先服务(FCFS, First Come First Serverd)](#1-先来先服务fcfs,-first-come-first-serverd) + * [2. 最短寻道时间优先(SSTF, Shortest Seek Time First)](#2-最短寻道时间优先sstf,-shortest-seek-time-first) + * [3. 扫描算法(SCAN)](#3-扫描算法scan) + * [4. 循环扫描算法(CSCAN)](#4-循环扫描算法cscan) +* [参考资料](#参考资料) -# һ -## ϵͳ +# 第一章 概述 -### 1. +## 操作系统基本特征 -ָһʱͬʱж򣬶ָͬһʱжָ +### 1. 并发 -ҪӲ֧֣ˮ߻߶ദ +并发性是指宏观上在一段时间内能同时运行多个程序,而并行性则指同一时刻能运行多个指令。 -ϵͳ̺̣ͨ߳ʹóܹС +并行需要硬件支持,如多流水线或者多处理器。 -### 2. +操作系统通过引入进程和线程,使得程序能够并发运行。 -ָϵͳеԴԹ̹ͬʹá +### 2. 共享 -ֹʽ⹲ͬʱ +共享是指系统中的资源可以供多个并发进程共同使用。 -⹲ԴΪٽԴӡȣͬһʱֻһ̷ʣִҪͬʵֶٽԴķʡ +有两种共享方式:互斥共享和同时共享。 -### 3. +互斥共享的资源称为临界资源,例如打印机等,在同一时间只允许一个进程访问,否则会出现错误,需要用同步机制来实现对临界资源的访问。 -⼼һʵתΪ߼ʵ塣Ҫ⼼ʱָüͿշָüͬһϲִʹʱָüÿռдÿִֻһСʱƬл +### 3. 虚拟 -### 4. 첽 +虚拟技术把一个物理实体转换为多个逻辑实体。主要有两种虚拟技术:时分复用技术和空分复用技术,例如多个进程能在同一个处理器上并发执行使用了时分复用技术,让每个进程轮流占有处理器,每次只执行一小个时间片并快速切换。 -첽ָ̲һִϣͣͣԲ֪ٶǰƽ +### 4. 异步 -## ϵͳ +异步是指进程不是一次性执行完毕,而是走走停停,以不可知的速度向前推进。 -һû̬ҪõϵͳһЩܣҪʹϵͳôӶںˣɲϵͳΪɡ +## 系统调用 -ϵͳĹ豸ļ̹ͨš洢ȡ +如果一个进程在用户态需要用到操作系统的一些功能,就需要使用系统调用从而陷入内核,由操作系统代为完成。 -## жϷ +可以由系统调用请求的功能有设备管理、文件管理、进程管理、进程通信、存储器管理等。 -### 1. ж +## 中断分类 - CPU ִָ¼ I/O жϣʾ豸/Ѿɣܹһ/󡣴⻹ʱжϡ̨жϵȡ +### 1. 外中断 -### 2. 쳣 +由 CPU 执行指令以外的事件引起,如 I/O 结束中断,表示设备输入/输出处理已经完成,处理器能够发送下一个输入/输出请求。此外还有时钟中断、控制台中断等。 - CPU ִָڲ¼Ƿ롢ַԽ硢ȡ +### 2. 异常 -### 3. +由 CPU 执行指令的内部事件引起,如非法操作码、地址越界、算术溢出等。 -ûʹϵͳá +### 3. 陷入 -## ں˺΢ں +在用户程序中使用系统调用。 -### 1. ں +## 大内核和微内核 -ںǽϵͳΪһܽϵŵںˣڸģ鹲Ϣкܸߵܡ +### 1. 大内核 -### 2. ΢ں +大内核是将操作系统功能作为一个紧密结合的整体放到内核,由于各模块共享信息,因此有很高的性能。 -ڲϵͳϸӣ˽һֲϵͳƳںˣӶں˵ĸԡƳIJָݷֲԭ򻮷ֳɷ໥ҪƵû̬ͺ̬֮лһʧ +### 2. 微内核 -# ڶ ̹ +由于操作系统不断复杂,因此将一部分操作系统功能移出内核,从而降低内核的复杂性。移出的部分根据分层的原则划分成若干服务,相互独立。但是需要频繁地在用户态和核心态之间进行切换,会有一定的性能损失。 -## ߳ +# 第二章 进程管理 -### 1. +## 进程与线程 -DzϵͳԴĻλ +### 1. 进程 -̿ƿ (Process Control Block, PCB) ̵ĻϢ״̬νĴ̺ͳָ̣ PCB IJ +进程是操作系统进行资源分配的基本单位。 -### 2. ߳ +进程控制块 (Process Control Block, PCB) 描述进程的基本信息和运行状态,所谓的创建进程和撤销进程,都是指对 PCB 的操作。 -һпж̣߳߳ǶȵĻλͬһеĶ֮߳ԲִУǹԴ +### 2. 线程 -### 3. +一个进程中可以有多个线程,线程是独立调度的基本单位。同一个进程中的多个线程之间可以并发执行,它们共享进程资源。 -- ӵԴԴĻλ̲߳ӵԴ߳̿Է̵Դ +### 3. 区别 -- ȣ߳ǶȵĻλͬһУ̵߳ллһڵ߳лһе߳ʱл +- 拥有资源:进程是资源分配的基本单位,但是线程不拥有资源,线程可以访问率属进程的资源。 -- ϵͳڴʱϵͳҪΪ֮Դڴռ䡢I/O 豸ȣ˲ϵͳĿԶڴ߳ʱĿƵأڽнлʱ漰ǰִн CPU ı漰µȽ CPU á߳лʱֻ豣ĴݣС +- 调度:线程是独立调度的基本单位,在同一进程中,线程的切换不会引起进程切换,从一个进程内的线程切换到另一个进程中的线程时,会引起进程切换。 -- ͨŷ棺̼ͨ (IPC) ҪͬͻֶεĸԱ֤ݵһԣֱ̼߳ͨӶ/дͬһеݶΣȫֱͨš +- 系统开销:由于创建或撤销进程时,系统都要为之分配或回收资源,如内存空间、I/O 设备等,因此操作系统所付出的开销远大于创建或撤销线程时的开销。类似地,在进行进程切换时,涉及当前执行进程 CPU 环境的保存及新调度进程 CPU 环境的设置。而线程切换时只需保存和设置少量寄存器内容,开销很小。 -QQ ̣к̣ܶ߳ HTTP ̡߳¼Ӧ̡߳Ⱦ̵߳ȵȣ̵߳IJִʹеһӴӶ HTTP ʱӦû¼ +- 通信方面:进程间通信 (IPC) 需要进程同步和互斥手段的辅助,以保证数据的一致性,而线程间可以通过直接读/写同一进程中的数据段(如全局变量)来进行通信。 -## ״̬л +举例:QQ 和浏览器是两个进程,浏览器进程里面有很多线程,例如 HTTP 请求线程、事件响应线程、渲染线程等等,线程的并发执行使得在浏览器中点击一个新链接从而发起 HTTP 请求时,浏览器还可以响应用户的其它事件。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/1706ce58-a081-4fed-9b36-c3c0d7e22b3a.jpg) +## 进程状态的切换 -״̬ȱҪԴӶ״̬תǸԴ CPUȱ CPU ý̴̬תΪ̬ +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//1706ce58-a081-4fed-9b36-c3c0d7e22b3a.jpg) -ֻо̬̬໥תĶǵת״̬Ľͨ㷨Ӷ CPU ʱ䣬תΪ״̬״̬Ḷ̌ڷ CPU ʱƬ֮ͻתΪ״̬ȴһεȡ +阻塞状态是缺少需要的资源从而由运行状态转换而来,但是该资源不包括 CPU,缺少 CPU 会让进程从运行态转换为就绪态。 -## 㷨 +只有就绪态和运行态可以相互转换,其它的都是单向转换。就绪状态的进程通过调度算法从而获得 CPU 时间,转为运行状态;而运行状态的进程,在分配给它的 CPU 时间片用完之后就会转为就绪状态,等待下一次调度。 -ҪԲͬ۵㷨 +## 调度算法 -### 1. ϵͳеĵ +需要针对不同环境来讨论调度算法。 -#### 1.1 ȷ +### 1. 批处理系统中的调度 -first-come first-serverdFCFS +#### 1.1 先来先服务 -Ƚеҵ +first-come first-serverd(FCFS)。 -ڳҵڶҵΪҵһֱȴǰijҵִϲִУҵҪִкܳʱ䣬˶ҵȴʱ +调度最先进入就绪队列的作业。 -#### 1.2 ҵ +有利于长作业,但不利于短作业,因为短作业必须一直等待前面的长作业执行完毕才能执行,而长作业又需要执行很长时间,造成了短作业等待时间过长。 -shortest job firstSJF +#### 1.2 短作业优先 -ȹʱ̵ҵ +shortest job first(SJF)。 -ҵпܻһֱȴҵִϵ״̬һֱжҵôҵԶòȡ +调度估计运行时间最短的作业。 -#### 1.3 ʣʱ +长作业有可能会饿死,处于一直等待短作业执行完毕的状态。如果一直有短作业到来,那么长作业永远得不到调度。 -shortest remaining time nextSRTN +#### 1.3 最短剩余时间优先 -### 2. ʽϵͳеĵ +shortest remaining time next(SRTN)。 -#### 2.1 Ȩ +### 2. 交互式系统中的调度 -˿ֶȨ֮⣬԰ӦΪȨֵȷʽӦȵ㷨 +#### 2.1 优先权优先 -Ӧ = (ȴʱ + Ҫʱ) / Ҫʱ = Ӧʱ / Ҫʱ +除了可以手动赋予优先权之外,还可以把响应比作为优先权,这种调度方式叫做高响应比优先调度算法。 -ֵ㷨ҪΪ˽ SJF гҵܻ⣬ΪŵȴʱӦҲԽԽߡ +响应比 = (等待时间 + 要求服务时间) / 要求服务时间 = 响应时间 / 要求服务时间 -#### 2.2 ʱƬת +这种调度算法主要是为了解决 SJF 中长作业可能会饿死的问题,因为随着等待时间的增长,响应比也会越来越高。 -о̰ FCFS ԭųһУÿεʱ CPU ׽̣ýִ̿һʱƬʱƬʱɼʱʱжϣȳֹͣý̵ִУеĩβͬʱ CPU ׵Ľ̡ +#### 2.2 时间片轮转 -ʱƬת㷨ЧʺʱƬĴСкܴϵΪÿνлҪ̵Ϣ½̵ϢʱƬ̫Сл̫ƵڽлϾͻỨʱ䡣 +将所有就绪进程按 FCFS 的原则排成一个队列,每次调度时,把 CPU 分配给队首进程,该进程可以执行一个时间片。当时间片用完时,由计时器发出时钟中断,调度程序便停止该进程的执行,并将它送往就绪队列的末尾,同时继续把 CPU 分配给队首的进程。 -#### 2.3 ༶ +时间片轮转算法的效率和时间片的大小有很大关系。因为每次进程切换都要保存进程的信息并且载入新进程的信息,如果时间片太小,进程切换太频繁,在进程切换上就会花过多时间。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/042cf928-3c8e-4815-ae9c-f2780202c68f.png) +#### 2.3 多级反馈队列 -1. öУΪи費ͬȼһеȼߣڶд֮еȨ͡㷨нִʱƬĴСҲͬȨԽߵĶУΪÿ涨ִʱƬԽС +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//042cf928-3c8e-4815-ae9c-f2780202c68f.png) -2. һ½̽ڴȽһеĩβ FCFS ԭŶӵȴȡֵýִʱڸʱƬɣ׼ϵͳһʱƬʱδɣȳ㽫ýתһеĶβ +1. 设置多个就绪队列,并为各个队列赋予不同的优先级。第一个队列的优先级最高,第二个队列次之,其余各队列的优先权逐个降低。该算法赋予各个队列中进程执行时间片的大小也各不相同,在优先权越高的队列中,为每个进程所规定的执行时间片就越小。 -3. ǰ i -1 оʱŻȵ i еĽ̡ +2. 当一个新进程进入内存后,首先将它放入第一队列的末尾,按 FCFS 原则排队等待调度。当轮到该进程执行时,如它能在该时间片内完成,便可准备撤离系统;如果它在一个时间片结束时尚未完成,调度程序便将该进程转入下一个队列的队尾。 -ŵ㣺ʵʱԺãͬʱʺжҵͳҵ +3. 仅当前 i -1 个队列均空时,才会调度第 i 个队列中的进程。 -#### 2.4 ̽ +优点:实时性好,同时适合运行短作业和长作业。 -### 3. ʵʱϵͳеĵ +#### 2.4 短进程优先 -ʵʱϵͳҪһһȷʱڵõӦ +### 3. 实时系统中的调度 -ΪӲʵʱʵʱǰ߱ԵĽֹʱ䣬߿һijʱ +实时系统要一个服务请求在一个确定时间内得到响应。 -## ͬ +分为硬实时和软实时,前者必须满足绝对的截止时间,后者可以容忍一定的超时。 -### 1. ٽ +## 进程同步 -ٽԴзʵǶδΪٽ +### 1. 临界区 -Ϊ˻ٽԴÿڽٽ֮ǰҪȽм顣 +对临界资源进行访问的那段代码称为临界区。 + +为了互斥访问临界资源,每个进程在进入临界区之前,需要先进行检查。 ```html // entry section @@ -250,44 +251,44 @@ shortest remaining time next // exit section ``` -### 2. ͬ뻥 +### 2. 同步与互斥 -ָ̰ͬһ˳ִУָͬһʱֻһܽٽ +同步指多个进程按一定顺序执行;互斥指多个进程在同一时刻只有一个进程能进入临界区。 -ͬڶٽʵĻϣͨʵʵġ +同步是在对临界区互斥访问的基础上,通过其它机制来实现有序访问的。 -### 3. ź +### 3. 信号量 -**źSamaphore** һͱԶִ down up Ҳdz P V +**信号量(Samaphore)** 是一个整型变量,可以对其执行 down 和 up 操作,也就是常见的 P 和 V 操作。 -- **down** : ź 0 ִ -1 ź 0˯ߣȴź 0 -- **up**źִ +1 һ˯ߵḶ̌ý down +- **down** : 如果信号量大于 0 ,执行 -1 操作;如果信号量等于 0,将进程睡眠,等待信号量大于 0; +- **up**:对信号量执行 +1 操作,并且唤醒睡眠的进程,让进程完成 down 操作。 -down up ҪƳԭɷָִͨЩʱжϡ +down 和 up 操作需要被设计成原语,不可分割,通常的做法是在执行这些操作的时候屏蔽中断。 -źȡֵֻΪ 0 1ôͳΪ**Mutex**0 ʾٽѾ1 ʾٽ +如果信号量的取值只能为 0 或者 1,那么就成为了**互斥量(Mutex)**,0 表示临界区已经加锁,1 表示临界区解锁。 ```c typedef int samaphore; samaphore mutex = 1; void P1() { down(mutex); - // ٽ + // 临界区 up(mutex); } void P2() { down(mutex); - // ٽ + // 临界区 up(mutex); } ``` -**ʹźʵ-** +**使用信号量实现生产者-消费者问题** -ʹһ mutex ٽԴзʣempty ¼ջfull ¼ +使用一个互斥量 mutex 来对临界资源进行访问;empty 记录空缓冲区的数量,full 记录满缓冲区的数量。 -ע⣬ִ down ûٽΪȶٽȻִ down ôܻ߶ٽִ down(empty) empty = 0ʱ˯ߡߴʱܽٽΪ߶ٽˣҲ޷ִ up(empty) ôߺ߾ͻһֱȴȥ +注意,必须先执行 down 操作再用互斥量对临界区加锁,否则会出现死锁。因为如果都先对临界区加锁,然后再执行 down 操作,那么可能会出现这种情况:生产者对临界区加锁后,执行 down(empty) 操作,发现 empty = 0,此时生成者睡眠。消费者此时不能进入临界区,因为生产者对临界区加锁了,也就无法执行 up(empty) 操作,那么生产者和消费者就会一直等待下去。 ```c #define N 100 @@ -319,11 +320,11 @@ void consumer() { } ``` -### 4. ܳ +### 4. 管程 -ʹźʵֵҪͻ˴ܶƣ̰ܳѿƵĴ׳Ҳʹÿͻ˴øס +使用信号量机制实现的生产者消费者问题需要客户端代码做很多控制,而管程把控制的代码独立出来,不仅不容易出错,也使得客户端代码调用更容易。 -c Բֹ֧̣ܳʾʹ Pascal ̡ܳʾеĹܳṩ insert() remove() ͻ˴ͨ-⡣ +c 语言不支持管程,下面的示例代码使用了类 Pascal 语言来描述管程。示例代码中的管程提供了 insert() 和 remove() 方法,客户端代码通过调用这两个方法来解决生产者-消费者问题。 ```pascal monitor ProducerConsumer @@ -342,11 +343,11 @@ monitor ProducerConsumer end monitor; ``` -ܳһҪԣһʱֻһʹù̡ܳ޷ִеʱһֱռụ̀ܳԶʹù̡ܳ +管程有一个重要特性:在一个时刻只能有一个进程使用管程。进程在无法继续执行的时候不能一直占用管程,否者其它进程永远不能使用管程。 -ܳ **** ԼصIJ**wait()** **signal()** ʵִͬ wait() ᵼµýѹܳóһ̳Сsignal() ڻѱĽ̡ +管程引入了 **条件变量** 以及相关的操作:**wait()** 和 **signal()** 来实现同步操作。对条件变量执行 wait() 操作会导致调用进程阻塞,把管程让出来让另一个进程持有。signal() 操作用于唤醒被阻塞的进程。 -**ʹùܳʵ-** +**使用管程实现生成者-消费者问题** ```pascal monitor ProducerConsumer @@ -390,53 +391,53 @@ begin end; ``` -## ͨ +## 进程通信 -ͨſԿDz̼ͬ߳ͨţͬһ̵߳ͨŷʽҪʹźͬơ +进程通信可以看成是不同进程间的线程通信,对于同一个进程内线程的通信方式,主要使用信号量、条件变量等同步机制。 -### 1. ܵ +### 1. 管道 -ܵǵġȽȳġ޽ṹġ̶Сֽһ̵ı׼һ̵ı׼һдڹܵβдݣڹܵ׶˶ݡݶ󽫴ӹܵߣ̶ٶЩݡ +管道是单向的、先进先出的、无结构的、固定大小的字节流,它把一个进程的标准输出和另一个进程的标准输入连接在一起。写进程在管道的尾端写入数据,读进程在管道的首端读出数据。数据读出后将从管道中移走,其它读进程都不能再读到这些数据。 -ܵṩ˼򵥵ƻƣͼչܵʱдܵǰ̽һֱͬأܵѾʱͼд̴ܵӹ֮ܵǰд̽һֱ +管道提供了简单的流控制机制,进程试图读空管道时,在有数据写入管道前,进程将一直阻塞。同样地,管道已经满时,进程再试图写管道,在其它进程从管道中移走数据之前,写进程将一直阻塞。 -Linux йܵͨļʵ֡ +Linux 中管道是通过空文件来实现。 -֣ܵ +管道有三种: -1. ͨܵƣһְֻ֧˫ͨŷʽֻܵ䣻ֻڸӽ֮ʹã -2. ܵȥһƣ֧˫䣻 -3. ܵȥڶƣڲؽ֮ͨš +1. 普通管道:有两个限制:一是只支持半双工通信方式,即只能单向传输;二是只能在父子进程之间使用; +2. 流管道:去除第一个限制,支持双向传输; +3. 命名管道:去除第二个限制,可以在不相关进程之间进行通信。 -### 2. ź +### 2. 信号量 -źһƶ̶ԹԴķʡΪһƣֹijڷʹԴʱҲʸԴˣҪΪ̼Լͬһڲֶ֮ͬ߳ͬΡ +信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其它进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。 -### 3. Ϣ +### 3. 消息队列 -Ϣп˷źŴϢ١ֻܵܳ޸ʽֽԼС޵ȱ㡣 +消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。 -### 4. ź +### 4. 信号 -źһֱȽϸӵͨŷʽ֪ͨսij¼Ѿ +信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。 -### 5. ڴ +### 5. 共享内存 -ڴӳһܱʵڴ棬ιڴһ̴̶Էʡڴ IPC ʽ IPC ЧʵͶרƵġͨŻƣźʹãʵֽ̼ͬͨš +共享内存就是映射一段能被其它进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其它 IPC 运行效率低而专门设计的。它往往与其它通信机制(如信号量)配合使用,来实现进程间的同步和通信。 -### 6. ׽ +### 6. 套接字 -׽Ҳһֽ̼ͨŻƣͨŻƲͬǣڲͬĽͨš +套接字也是一种进程间通信机制,与其它通信机制不同的是,它可用于不同机器间的进程通信。 -## ͬ +## 经典同步问题 -ߺǰѾ۹ +生产者和消费者问题前面已经讨论过。 -### 1. -д +### 1. 读者-写者问题 -ͬʱݽжDzдԼддͬʱ +允许多个进程同时对数据进行读操作,但是不允许读和写以及写和写操作同时发生。 -һͱ count ¼ڶݽжĽһ count_mutex ڶ count һ data_mutex ڶԶдݼ +一个整型变量 count 记录在对数据进行读操作的进程数量,一个互斥量 count_mutex 用于对 count 加锁,一个互斥量 data_mutex 用于对读写的数据加锁。 ```c typedef int semaphore; @@ -448,7 +449,7 @@ void reader() { while(TRUE) { down(count_mutex); count++; - if(count == 1) down(data_mutex); // һҪݽмֹд̷ + if(count == 1) down(data_mutex); // 第一个读者需要对数据进行加锁,防止写进程访问 up(count_mutex); read(); down(count_mutex); @@ -467,13 +468,13 @@ void writer() { } ``` -### 2. ѧҽ +### 2. 哲学家进餐问题 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/a9077f06-7584-4f2b-8c20-3a8e46928820.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//a9077f06-7584-4f2b-8c20-3a8e46928820.jpg) -ѧΧһԲܣÿѧǰŷѧҵֽԷԼ˼һѧҳԷʱҪһһߵĿӡ +五个哲学家围着一张圆周,每个哲学家面前放着饭。哲学家的生活有两种交替活动:吃饭以及思考。当一个哲学家吃饭时,需要先一根一根拿起左右两边的筷子。 -һִĽⷨǵÿѧͬʱֱߵĿӣô޷ֱߵĿӣ +下面是一种错误的解法,考虑到如果每个哲学家同时拿起左手边的筷子,那么就无法拿起右手边的筷子,造成死锁。 ```c #define N 5 @@ -494,7 +495,7 @@ void philosopher(int i) { } ``` -Ϊ˷ֹķԼһƣֻͬʱߵĿӣһӵǶδ +为了防止死锁的发生,可以加一点限制,只允许同时拿起左右两边的筷子,方法是引入一个互斥量,对拿起两个筷子的那段代码加锁。 ```c semaphore mutex = 1; @@ -515,220 +516,220 @@ void philosopher(int i) { } ``` -# +# 第三章 死锁 -## +## 死锁的条件 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/c037c901-7eae-4e31-a1e4-9d41329e5c3e.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//c037c901-7eae-4e31-a1e4-9d41329e5c3e.png) -1. -2. 뱣 -3. ռ -4. ·ȴ +1. 互斥 +2. 请求与保持 +3. 不可抢占 +4. 环路等待 -У뱣ָһԴʱѻõԴֲš +其中,请求与保持是指一个进程因请求资源而阻塞时,对已获得的资源保持不放。 -## Ĵ +## 死锁的处理方法 -### 1. +### 1. 鸵鸟策略 -ͷɳװû⡣ +把头埋在沙子里,假装根本没发生问题。 -ֲԲȡ +这种策略不可取。 -### 2. Ԥ +### 2. 死锁预防 -ڳ֮ǰԤ +在程序运行之前预防发生死锁。 -#### 2.1 ƻ +#### 2.1 破坏互斥条件 -ѻӡɸͬʱΨһӡĽǴӡػ̡ +例如假脱机打印机技术允许若干个进程同时输出,唯一真正请求物理打印机的进程是打印机守护进程。 -#### 2.2 ƻ뱣 +#### 2.2 破坏请求与保持条件 -һʵַʽǹ涨нڿʼִǰҪȫԴ +一种实现方式是规定所有进程在开始执行前请求所需要的全部资源。 -#### 2.3 ƻռ +#### 2.3 破坏不可抢占条件 -#### 2.4 ƻ·ȴ +#### 2.4 破坏环路等待 -Դͳһţֻܰ˳Դ +给资源统一编号,进程只能按编号顺序来请求资源。 -### 3. +### 3. 死锁避免 -ڳʱⷢ +在程序运行时避免发生死锁。 -#### 3.1 ȫ״̬ +#### 3.1 安全状态 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/ed523051-608f-4c3f-b343-383e2d194470.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//ed523051-608f-4c3f-b343-383e2d194470.png) -ͼ a ĵڶ has ʾӵеԴ max ʾܹҪԴfree ʾпʹõԴͼ a ʼ B ӵԴнͷ Bʱ free Ϊ 4ͬķʽ C Aʹн̶ܳɹУ˿Գͼ a ʾ״̬ʱȫġ +图 a 的第二列 has 表示已拥有的资源数,第三列 max 表示总共需要的资源数,free 表示还有可以使用的资源数。从图 a 开始出发,先让 B 拥有所需的所有资源,运行结束后释放 B,此时 free 变为 4;接着以同样的方式运行 C 和 A,使得所有进程都能成功运行,因此可以称图 a 所示的状态时安全的。 -壺ûҼʹнͻȻԴҲȻijֵȴܹʹÿһϣƸ״̬ǰȫġ +定义:如果没有死锁发生,并且即使所有进程突然请求对资源的最大需求,也仍然存在某种调度次序能够使得每一个进程运行完毕,则称该状态是安全的。 -#### 3.2 Դм㷨 +#### 3.2 单个资源的银行家算法 -һСмңһȺͻֱŵһĴȣ㷨Ҫж϶Ƿ벻ȫ״̬ǣ;ܾ󣻷Է䡣 +一个小城镇的银行家,他向一群客户分别承诺了一定的贷款额度,算法要做的是判断对请求的满足是否会进入不安全状态,如果是,就拒绝请求;否则予以分配。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/d160ec2e-cfe2-4640-bda7-62f53e58b8c0.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//d160ec2e-cfe2-4640-bda7-62f53e58b8c0.png) -ͼ c Ϊȫ״̬㷨ܾ֮ǰ󣬴Ӷͼ c е״̬ +上图 c 为不安全状态,因此算法会拒绝之前的请求,从而避免进入图 c 中的状态。 -#### 3.3 Դм㷨 +#### 3.3 多个资源的银行家算法 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/62e0dd4f-44c3-43ee-bb6e-fedb9e068519.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//62e0dd4f-44c3-43ee-bb6e-fedb9e068519.png) -ͼ̣ĸԴߵͼʾѾԴұߵͼʾҪԴұߵ EP Լ A ֱʾԴѷԴԼԴעΪǾֵ A=(1020)ʾ 4 Դֱʣ 1/0/2/0 +上图中有五个进程,四个资源。左边的图表示已经分配的资源,右边的图表示还需要分配的资源。最右边的 E、P 以及 A 分别表示:总资源、已分配资源以及可用资源,注意这三个为向量,而不是具体数值,例如 A=(1020),表示 4 个资源分别还剩下 1/0/2/0。 -һ״̬Ƿȫ㷨£ +检查一个状态是否安全的算法如下: -- ұߵľǷһСڵ AУôϵͳᷢ״̬Dzȫġ -- ҵһУý̱ΪֹѷԴӵ A С -- ظֱн̶Ϊֹ״̬ʱȫġ +- 查找右边的矩阵是否存在一行小于等于向量 A。如果不存在这样的行,那么系统将会发生死锁,状态是不安全的。 +- 假若找到这样一行,将该进程标记为终止,并将其已分配资源加到 A 中。 +- 重复以上两步,直到所有进程都标记为终止,则状态时安全的。 -### 4. ָ +### 4. 死锁检测与死锁恢复 -ͼֹǵ⵽ʱȡʩлָ +不试图阻止死锁,而是当检测到死锁发生时,采取措施进行恢复。 -#### 4.1 㷨 +#### 4.1 死锁检测算法 -Ļ˼ǣһԴܹ㣬ôִУͷӵеԴȻĽִС +死锁检测的基本思想是,如果一个进程所请求的资源能够被满足,那么就让它执行,释放它拥有的所有资源,然后让其它能满足条件的进程执行。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/e1eda3d5-5ec8-4708-8e25-1a04c5e11f48.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//e1eda3d5-5ec8-4708-8e25-1a04c5e11f48.png) -ͼУĸԴÿݴĺ£ +上图中,有三个进程四个资源,每个数据代表的含义如下: -- E Դ -- A Դʣ -- C ÿӵеԴÿһжһӵԴ -- R ÿԴ +- E 向量:资源总量 +- A 向量:资源剩余量 +- C 矩阵:每个进程所拥有的资源数量,每一行都代表一个进程拥有资源的数量 +- R 矩阵:每个进程请求的资源数量 - P1 P2 Դò㣬ֻн P3 ԣ P3 ִУ֮ͷ P3 ӵеԴʱ A = (2 2 2 0)P1 ִУִкͷ P1 ӵеԴA = (4 2 2 2) P2 ҲִСн̶˳ִУû +进程 P1 和 P2 所请求的资源都得不到满足,只有进程 P3 可以,让 P3 执行,之后释放 P3 拥有的资源,此时 A = (2 2 2 0)。P1 可以执行,执行后释放 P1 拥有的资源,A = (4 2 2 2) ,P2 也可以执行。所有进程都可以顺利执行,没有死锁。 -㷨ܽ£ +算法总结如下: -ÿʼʱǣִйпܱǡ㷨ʱκûбǵĽ̶̡ +每个进程最开始时都不被标记,执行过程有可能被标记。当算法结束时,任何没有被标记的进程都是死锁进程。 -1. ѰһûбǵĽ PiԴСڵ A -2. ҵһ̣ô C ĵ i ӵ A УǸỵ́ת 1 -3. ûһ̣㷨ֹ +1. 寻找一个没有标记的进程 Pi,它所请求的资源小于等于 A。 +2. 如果找到了这样一个进程,那么将 C 矩阵的第 i 行向量加到 A 中,标记该进程,并转回 1。 +3. 如果有没有这样一个进程,算法终止。 -#### 4.2 ָ +#### 4.2 死锁恢复 -- ռָ -- ɱ +- 利用抢占恢复 +- 杀死进程 -# 洢 +# 第四章 存储器管理 -## ڴ +## 虚拟内存 -ÿӵԼĵַռ䣬ַռ䱻ָɶ飬ÿһΪһҳЩҳӳ䵽ڴ棬Ҫӳ䵽ڴ棬ҲҪҳڴС +每个程序拥有自己的地址空间,这个地址空间被分割成多个块,每一块称为一页。这些页被映射到物理内存,但不需要映射到连续的物理内存,也不需要所有页都必须在物理内存中。 -õһڴеĵַռʱӲִбҪӳ䣬ȱʧIJװڴ沢ִʧָܵ +当程序引用到一部分在物理内存中的地址空间时,由硬件执行必要的映射,将缺失的部分装入物理内存并重新执行失败的指令。 -## ҳֶ +## 分页与分段 -### 1. ҳ +### 1. 分页 -ûĵַռ䱻Ϊɹ̶С򣬳ΪҳӦأڴռֳɸ飬ҳͿĴСȡɽûһҳڴһУʵɢ䣬һҳά֮ӳϵ +用户程序的地址空间被划分为若干固定大小的区域,称为“页”。相应地,内存空间分成若干个物理块,页和块的大小相等。可将用户程序的任一页放在内存的任一块中,实现了离散分配,由一个页表来维护它们之间的映射关系。 -### 2. ֶ +### 2. 分段 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/22de0538-7c6e-4365-bd3b-8ce3c5900216.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//22de0538-7c6e-4365-bd3b-8ce3c5900216.png) -ͼΪһڱнĶ 4 Ƕ̬ģʹ÷ҳϵͳһάַռ䣬̬صᵼ¸ij֡ +上图为一个编译器在编译过程中建立的多个表,有 4 个表是动态增长的,如果使用分页系统的一维地址空间,动态递增的特点会导致覆盖问题的出现。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/e0900bb2-220a-43b7-9aa9-1d5cd55ff56e.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//e0900bb2-220a-43b7-9aa9-1d5cd55ff56e.png) -ֶεǰÿֳɶΣһιһĵַռ䡣ÿεijȿԲͬҿԶ̬ +分段的做法是把每个表分成段,一个段构成一个独立的地址空间。每个段的长度可以不同,并且可以动态增长。 -ÿζҪԱ֡ +每个段都需要程序员来划分。 -### 3. ҳʽ +### 3. 段页式 -÷ֶη͹洢ĵַռ䰴߼λֳɻĶΣÿһԼĶٰÿηֳɹ̶Сҳ +用分段方法来分配和管理虚拟存储器。程序的地址空间按逻辑单位分成基本独立的段,而每一段有自己的段名,再把每段分成固定大小的若干页。 -÷ҳ͹ʵ档ֳҳСȵĴ洢飬װҵκһҳ +用分页方法来分配和管理实存。即把整个主存分成与上述页大小相等的存储块,可装入作业的任何一页。 -ڴĵǰҳеģֿɰʵֹͱ +程序对内存的调入或调出是按页进行的,但它又可按段实现共享和保护。 -### 4. ҳֶ +### 4. 分页与分段区别 -- ԳԱ͸ԣҳ͸ǷֶҪԱʾÿΡ +- 对程序员的透明性:分页透明,但是分段需要程序员显示划分每个段。 -- ַռάȣҳһάַռ䣬ֶǶάġ +- 地址空间的维度:分页是一维地址空间,分段是二维的。 -- СǷԸı䣺ҳĴСɱ䣬εĴСԶ̬ı䡣 +- 大小是否可以改变:页的大小不可变,段的大小可以动态改变。 -- ֵԭ򣺷ҳҪʵڴ棬Ӷøĵַռ䣻ֶҪΪʹݿԱΪ߼϶ĵַռ䲢ڹͱ +- 出现的原因:分页主要用于实现虚拟内存,从而获得更大的地址空间;分段主要是为了使程序和数据可以被划分为逻辑上独立的地址空间并且有助于共享和保护。 -## ҳû㷨 +## 页面置换算法 -ڳйУҪʵҳ治ڴҪǵڴ棬ڴ޿пռʱϵͳڴеһҳ浽̶ԻУҽҪҳڴСҳû㷨ҪĿʹҳûƵͣҲ˵ȱҳͣ +在程序运行过程中,若其所要访问的页面不在内存而需要把它们调入内存,但是内存已无空闲空间时,系统必须从内存中调出一个页面到磁盘对换区中,并且将程序所需要的页面调入内存中。页面置换算法的主要目标是使页面置换频率最低(也可以说缺页率最低)。 -### 1. ѣOptimal +### 1. 最佳(Optimal) -ѡıҳ潫ʱڲٱʣͨԱ֤͵ȱҳʡ +所选择的被换出的页面将是最长时间内不再被访问,通常可以保证获得最低的缺页率。 -һϵ㷨Ϊ޷֪һҳ೤ʱᱻٷʵ +是一种理论上的算法,因为无法知道一个页面多长时间会被再访问到。 -һϵͳΪij̷飬ҳУ +举例:一个系统为某进程分配了三个物理块,并有如下页面引用序列: -70120304230321201701 +7,0,1,2,0,3,0,4,2,3,0,3,2,1,2,0,1,7,0,1 -ʱȽ 7,0,1 ҳװڴ档Ҫҳ 2 ʱȱҳжϣὫҳ 7 Ϊҳ 7 ٴαʵʱ +进程运行时,先将 7,0,1 三个页面装入内存。当进程要访问页面 2 时,产生缺页中断,会将页面 7 换出,因为页面 7 再次被访问的时间最长。 -### 2. ȽȳFIFO +### 2. 先进先出(FIFO) -ѡ񻻳ҳȽҳ档 +所选择换出的页面是最先进入的页面。 -㷨ὫЩʵҳҲӶʹȱҳߡ +该算法会将那些经常被访问的页面也被换出,从而使缺页率升高。 -### 3. δʹãLRU, Least Recently Used +### 3. 最近最久未使用(LRU, Least Recently Used) -Ȼ޷֪Ҫʹõҳǿ֪ȥʹҳLRU δʹõҳ滻 +虽然无法知道将来要使用的页面情况,但是可以知道过去使用页面的情况。LRU 将最近最久未使用的页面换出。 -ջʵָ㷨ջд洢ҳҳš̷һҳʱҳҳŴջƳѹջʵҳҳջδʹõҳҳջס +可以用栈来实现该算法,栈中存储页面的页面号。当进程访问一个页面时,将该页面的页面号从栈移除,并将它压入栈顶。这样,最近被访问的页面的页面号总是在栈顶,而最近最久未使用的页面的页面号总是在栈底。 -47071012126 +4,7,0,7,1,0,1,2,1,2,6 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/eb859228-c0f2-4bce-910d-d9f76929352b.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//eb859228-c0f2-4bce-910d-d9f76929352b.png) -### 4. ʱӣClock +### 4. 时钟(Clock) -Clock ҳû㷨Ҫõһλһҳ汻ʱΪΪ 1 +Clock 页面置换算法需要用到一个访问位,当一个页面被访问时,将访问为置为 1。 -ȣڴеҳӳһѭУȱҳжϷʱ鵱ǰָָҳķλλΪ 0ͽҳ滻򽫸ҳķλΪ 0ҳڶεĻᣬƶָ顣 +首先,将内存中的所有页面链接成一个循环队列,当缺页中断发生时,检查当前指针所指向页面的访问位,如果访问位为 0,就将该页面换出;否则将该页的访问位设置为 0,给该页面第二次的机会,移动指针继续检查。 -# 豸 +# 第五章 设备管理 -## ̵㷨 +## 磁盘调度算法 -ͬʱʴʱҪд̵ƶԴ̵ķʡ̵ȵҪĿʹ̵ƽѰʱ١ +当多个进程同时请求访问磁盘时,需要进行磁盘调度来控制对磁盘的访问。磁盘调度的主要目标是使磁盘的平均寻道时间最少。 -### 1. ȷFCFS, First Come First Serverd +### 1. 先来先服务(FCFS, First Come First Serverd) -ݽʴ̵Ⱥеȡŵǹƽͼ򵥣ȱҲԣΪδѰκŻʹƽѰʱܽϳ +根据进程请求访问磁盘的先后次序来进行调度。优点是公平和简单,缺点也很明显,因为未对寻道做任何优化,使平均寻道时间可能较长。 -### 2. ѰʱȣSSTF, Shortest Seek Time First +### 2. 最短寻道时间优先(SSTF, Shortest Seek Time First) -ҪʵĴŵ뵱ǰͷڴŵȽеȡ㷨ܱ֤ƽѰʱ̣DZ FCFS úܶࡣ +要求访问的磁道与当前磁头所在磁道距离最近的优先进行调度。这种算法并不能保证平均寻道时间最短,但是比 FCFS 好很多。 -### 3. ɨ㷨SCAN +### 3. 扫描算法(SCAN) -SSTF ֽм󡣿½ʵĴŵͷڴŵľDZһڵȴĽĽôȴĽ̻һֱȴȥ +SSTF 会出现进行饥饿现象。考虑以下情况,新进程请求访问的磁道与磁头所在磁道的距离总是比一个在等待的进程来的近,那么等待的进程会一直等待下去。 -SCAN 㷨 SSTF 㷨֮Ͽ˴ͷƶҪʵĴŵڴͷǰƶϲܹõȡΪƶôһʵĴŵһõȡ +SCAN 算法在 SSTF 算法之上考虑了磁头的移动方向,要求所请求访问的磁道在磁头当前移动方向上才能够得到调度。因为考虑了移动方向,那么一个进程请求访问的磁道一定会得到调度。 -һͷƶʱƵıƶΪƶĹڵݵУֳ SCAN 㷨Ϊݵ㷨 +当一个磁头自里向外移动时,移到最外侧会改变移动方向为自外向里,这种移动的规律类似于电梯的运行,因此又常称 SCAN 算法为电梯调度算法。 -### 4. ѭɨ㷨CSCAN +### 4. 循环扫描算法(CSCAN) -CSCAN SCAN ˸ĶҪͷʼһƶ +CSCAN 对 SCAN 进行了改动,要求磁头始终沿着一个方向移动。 -# ο +# 参考资料 - Tanenbaum A S, Bos H. Modern operating systems[M]. Prentice Hall Press, 2014. -- , ܷ, С. ϵͳ[M]. ӿƼѧ, 2001. -- Bryant, R. E., & OHallaron, D. R. (2004). ϵͳ. -- [̼ļͨŷʽ](http://blog.csdn.net/yufaw/article/details/7409596) +- 汤子瀛, 哲凤屏, 汤小丹. 计算机操作系统[M]. 西安电子科技大学出版社, 2001. +- Bryant, R. E., & O’Hallaron, D. R. (2004). 深入理解计算机系统. +- [进程间的几种通信方式](http://blog.csdn.net/yufaw/article/details/7409596) diff --git a/notes/计算机网络.md b/notes/计算机网络.md index 203d481f..5e56d39c 100644 --- a/notes/计算机网络.md +++ b/notes/计算机网络.md @@ -1,839 +1,840 @@ -* [һ ](#һ-) - * [](#) +* [第一章 概述](#第一章-概述) + * [网络的网络](#网络的网络) * [ISP](#isp) - * [](#) - * [֮ͨŷʽ](#֮ͨŷʽ) - * [·齻](#·齻) - * [1. ·](#1-·) - * [2. Ľ](#2-Ľ) - * [3. 齻](#3-齻) - * [ʱ](#ʱ) - * [1. ʱ](#1-ʱ) - * [2. ʱ](#2-ʱ) - * [3. ʱ](#3-ʱ) - * [4. Ŷʱ](#4-Ŷʱ) - * [ϵṹ*](#ϵṹ) - * [1. ߲Э](#1-߲Э) - * [2. Э](#2-Э) - * [3. ڸ֮Ĵݹ](#3-ڸ֮Ĵݹ) - * [4. TCP/IP ϵṹ](#4-tcpip-ϵṹ) -* [ڶ ](#ڶ-) - * [ͨŷʽ](#ͨŷʽ) - * [ͨ](#ͨ) - * [ŵü](#ŵü) - * [1. Ƶָáʱָ](#1-Ƶָʱָ) - * [2. ͳʱָ](#2-ͳʱָ) - * [3. ָ](#3-ָ) - * [4. ָ](#4-ָ) -* [ ·](#-·) - * [](#) - * [1. װ֡](#1-װ֡) - * [2. ͸](#2-͸) - * [3. ](#3-) - * [Եŵ - PPP Э](#Եŵ---ppp-Э) - * [](#) - * [㲥ŵ - CSMA/CD Э*](#㲥ŵ---csmacd-Э) - * [](#) - * [MAC ](#mac-) - * [](#) -* [ *](#-) - * [Э IP ](#Э-ip-) - * [IP ݱʽ](#ip-ݱʽ) - * [IP ַַ](#ip-ַַ) - * [1. ](#1-) - * [2. ](#2-) - * [3. ޷](#3-޷) - * [IP ַ MAC ַ](#ip-ַ-mac-ַ) - * [ַЭ ARP](#ַЭ-arp) - * [·Ľṹ](#·Ľṹ) - * [·](#·) - * [·ת](#·ת) - * [·ѡЭ](#·ѡЭ) - * [1. ڲЭ RIP](#1-ڲЭ-rip) - * [2. ڲЭ OSPF](#2-ڲЭ-ospf) - * [3. ⲿЭ BGP](#3-ⲿЭ-bgp) - * [ʿƱЭ ICMP](#ʿƱЭ-icmp) - * [̽ PING](#̽-ping) - * [IP ಥ](#ip-ಥ) - * [ר VPN](#ר-vpn) - * [ַת NAT](#ַת-nat) -* [ *](#-) - * [UDP TCP ص](#udp--tcp-ص) - * [UDP ײʽ](#udp-ײʽ) - * [TCP ײʽ](#tcp-ײʽ) - * [TCP ](#tcp-) - * [TCP Ĵλ](#tcp-Ĵλ) - * [TCP ](#tcp-) - * [TCP ɿ](#tcp-ɿ) - * [TCP ](#tcp-) - * [TCP ӵ](#tcp-ӵ) - * [ʼӵ](#ʼӵ) - * [شָ](#شָ) -* [ Ӧò*](#-Ӧò) - * [ϵͳ DNS](#ϵͳ-dns) - * [1. νṹ](#1-νṹ) - * [2. ](#2-) - * [ļЭ FTP](#ļЭ-ftp) - * [ԶնЭ TELNET](#ԶնЭ-telnet) - * [ά WWW](#ά-www) - * [ʼЭ](#ʼЭ) + * [互联网的组成](#互联网的组成) + * [主机之间的通信方式](#主机之间的通信方式) + * [电路交换与分组交换](#电路交换与分组交换) + * [1. 电路交换](#1-电路交换) + * [2. 报文交换](#2-报文交换) + * [3. 分组交换](#3-分组交换) + * [时延](#时延) + * [1. 发送时延](#1-发送时延) + * [2. 传播时延](#2-传播时延) + * [3. 处理时延](#3-处理时延) + * [4. 排队时延](#4-排队时延) + * [计算机网络体系结构*](#计算机网络体系结构) + * [1. 七层协议](#1-七层协议) + * [2. 五层协议](#2-五层协议) + * [3. 数据在各层之间的传递过程](#3-数据在各层之间的传递过程) + * [4. TCP/IP 体系结构](#4-tcpip-体系结构) +* [第二章 物理层](#第二章-物理层) + * [通信方式](#通信方式) + * [带通调制](#带通调制) + * [信道复用技术](#信道复用技术) + * [1. 频分复用、时分复用](#1-频分复用时分复用) + * [2. 统计时分复用](#2-统计时分复用) + * [3. 波分复用](#3-波分复用) + * [4. 码分复用](#4-码分复用) +* [第三章 数据链路层](#第三章-数据链路层) + * [三个基本问题](#三个基本问题) + * [1. 封装成帧](#1-封装成帧) + * [2. 透明传输](#2-透明传输) + * [3. 差错检测](#3-差错检测) + * [点对点信道 - PPP 协议](#点对点信道---ppp-协议) + * [局域网的拓扑](#局域网的拓扑) + * [广播信道 - CSMA/CD 协议*](#广播信道---csmacd-协议) + * [集线器](#集线器) + * [MAC 层](#mac-层) + * [虚拟局域网](#虚拟局域网) +* [第四章 网络层*](#第四章-网络层) + * [网际协议 IP 概述](#网际协议-ip-概述) + * [IP 数据报格式](#ip-数据报格式) + * [IP 地址编址](#ip-地址编址) + * [1. 分类](#1-分类) + * [2. 子网划分](#2-子网划分) + * [3. 无分类](#3-无分类) + * [IP 地址和 MAC 地址](#ip-地址和-mac-地址) + * [地址解析协议 ARP](#地址解析协议-arp) + * [路由器的结构](#路由器的结构) + * [交换机与路由器的区别](#交换机与路由器的区别) + * [路由器分组转发流程](#路由器分组转发流程) + * [路由选择协议](#路由选择协议) + * [1. 内部网关协议 RIP](#1-内部网关协议-rip) + * [2. 内部网关协议 OSPF](#2-内部网关协议-ospf) + * [3. 外部网关协议 BGP](#3-外部网关协议-bgp) + * [网际控制报文协议 ICMP](#网际控制报文协议-icmp) + * [分组网间探测 PING](#分组网间探测-ping) + * [IP 多播](#ip-多播) + * [虚拟专用网 VPN](#虚拟专用网-vpn) + * [网络地址转换 NAT](#网络地址转换-nat) +* [第五章 运输层*](#第五章-运输层) + * [UDP 和 TCP 的特点](#udp-和-tcp-的特点) + * [UDP 首部格式](#udp-首部格式) + * [TCP 首部格式](#tcp-首部格式) + * [TCP 的三次握手](#tcp-的三次握手) + * [TCP 的四次挥手](#tcp-的四次挥手) + * [TCP 滑动窗口](#tcp-滑动窗口) + * [TCP 可靠传输](#tcp-可靠传输) + * [TCP 流量控制](#tcp-流量控制) + * [TCP 拥塞控制](#tcp-拥塞控制) + * [慢开始与拥塞避免](#慢开始与拥塞避免) + * [快重传与快恢复](#快重传与快恢复) +* [第六章 应用层*](#第六章-应用层) + * [域名系统 DNS](#域名系统-dns) + * [1. 层次结构](#1-层次结构) + * [2. 解析过程](#2-解析过程) + * [文件传输协议 FTP](#文件传输协议-ftp) + * [远程终端协议 TELNET](#远程终端协议-telnet) + * [万维网 WWW](#万维网-www) + * [电子邮件协议](#电子邮件协议) * [POP3](#pop3) * [IMAP](#imap) * [SMTP](#smtp) - * [̬Э DHCP](#̬Э-dhcp) - * [Ե㴫 P2P](#Ե㴫-p2p) - * [Web ҳ](#web-ҳ) - * [ö˿](#ö˿) -* [ο](#ο) + * [动态主机配置协议 DHCP](#动态主机配置协议-dhcp) + * [点对点传输 P2P](#点对点传输-p2p) + * [Web 页面请求过程](#web-页面请求过程) + * [常用端口](#常用端口) +* [参考资料](#参考资料) -# һ -## +# 第一章 概述 -ǰѶֲͬ˻硣 +## 网络的网络 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/04ff7ae6-7bee-4cf8-82f8-dfe2ba1f3616.jpg) +网络把主机连接起来,而互联网是把多种不同的网络连接起来,因此互联网是网络的网络。 + +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//04ff7ae6-7bee-4cf8-82f8-dfe2ba1f3616.jpg) ## ISP -ṩ ISP Դӻ IP ַͬʱӵͨ·Լ·豸˻ ISP һķþͿԽ뻥 +互联网服务提供商 ISP 可以从互联网管理机构获得许多 IP 地址,同时拥有通信线路以及路由器等联网设备,个人或机构向 ISP 缴纳一定的费用就可以接入互联网。 -ĿǰĻһֶ ISP ṹISP ݸĴСΪ ISP ISP ͱ ISP +目前的互联网是一种多层次 ISP 结构,ISP 根据覆盖面积的大小分为主干 ISP、地区 ISP 和本地 ISP。 - IXP ISP ֱþ ISP +互联网交换点 IXP 允许两个 ISP 直接相连而不用经过第三个 ISP。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/17d807ef-03bf-4824-a97c-ea5fb58ec61d.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//17d807ef-03bf-4824-a97c-ea5fb58ec61d.jpg) -## +## 互联网的组成 -1. Ե֣ڻϵûֱʹã -2. IJ֣ɴЩ·ɣΪԵֵṩ +1. 边缘部分:所有连接在互联网上的主机,用户可以直接使用; +2. 核心部分:由大量的网络和连接这些网络的路由器组成,为边缘部分的主机提供服务。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/005d83c2-e64a-41f0-bbdd-51c71d494a18.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//005d83c2-e64a-41f0-bbdd-51c71d494a18.jpg) -## ֮ͨŷʽ +## 主机之间的通信方式 -**1. ͻ - C/S** +**1. 客户 - 服务器(C/S)** -ͻǷ󷽣Ƿṩ +客户即是服务请求方,服务器是服务提供方。 -**2. ԵȣP2P** +**2. 对等(P2P)** -ֿͻͷ +不区分客户和服务器。 -## ·齻 +## 电路交换与分组交换 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/b97958dd-3e43-45f7-97f5-3ec20f3f8b88.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//b97958dd-3e43-45f7-97f5-3ec20f3f8b88.jpg) -### 1. · +### 1. 电路交换 -·ڵ绰ͨϵͳûҪ֮ͨǰҪһרõ·ͨŹʼռø·ͨŵĹвһֱʹô·˵··ʺܵͣ 10% +电路交换用于电话通信系统,两个用户要通信之前需要建立一条专用的物理链路,并且在整个通信过程中始终占用该链路。由于通信的过程中不可能一直在使用传输线路,因此电路交换对线路的利用率很低,往往不到 10%。 -### 2. Ľ +### 2. 报文交换 -Ľʾͨϵͳʾֽյһݱ֮ȴ洢ȻͬĿĵصıһתһĿĵأ̾Ǵ洢ת̡ +报文交换用于邮局通信系统,邮局接收到一份报文之后,先存储下来,然后把相同目的地的报文一起转发到下一个目的地,这个过程就是存储转发过程。 -### 3. 齻 +### 3. 分组交换 -齻Ҳʹ˴洢תתǷDZġݳΪһģһĿܺܳҪȽз֣ܴĴСÿзֵǰײ֮ͳΪ˷飬ײĿĵַԴַȿϢ +分组交换也使用了存储转发,但是转发的是分组而不是报文。把整块数据称为一个报文,由于一个报文可能很长,需要先进行切分,来满足分组能处理的大小。在每个切分的数据前面加上首部之后就成为了分组,首部包含了目的地址和源地址等控制信息。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/6f4af159-8b03-4246-8d0e-222db65bb83c.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//6f4af159-8b03-4246-8d0e-222db65bb83c.jpg) -洢תһ·ϴͶķ飬˲Ҫռö˵˵·Դ +存储转发允许在一条传输线路上传送多个主机的分组,因此不需要占用端到端的线路资源。 -ڱĽڷȱĸС洢תٶҲ͸졣 +相比于报文交换,由于分组比报文更小,存储转发的速度也就更快。 -## ʱ +## 时延 -ʱ = ʱ + ʱ + ʱ + Ŷʱ +总时延 = 发送时延 + 传播时延 + 处理时延 + 排队时延 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/ceee91c2-da26-4169-94c3-e4608b46b9ac.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//ceee91c2-da26-4169-94c3-e4608b46b9ac.png) -### 1. ʱ +### 1. 发送时延 -·֡Ҫʱ䡣 +主机或路由器发送数据帧所需要的时间。 -![](http://latex.codecogs.com/gif.latex?\\\\delay=\frac{l(bit)}{v(bit/s)}) +
- l ʾ֡ijȣv ʾʡ +其中 l 表示数据帧的长度,v 表示发送速率。 -### 2. ʱ +### 2. 传播时延 -ŲŵдһľҪѵʱ䣬ŲٶȽӽ١ +电磁波在信道中传播一定的距离需要花费的时间,电磁波传播速度接近光速。 -![](http://latex.codecogs.com/gif.latex?\\\\delay=\frac{l(m)}{v(m/s)}) +
- l ʾŵȣv ʾŲŵϵĴʡ +其中 l 表示信道长度,v 表示电磁波在信道上的传播速率。 -### 3. ʱ +### 3. 处理时延 -·յʱдҪʱ䣬ײӷȡݲֵȡ +主机或路由器收到分组时进行处理所需要的时间,例如分析首部,从分组中提取数据部分等。 -### 4. Ŷʱ +### 4. 排队时延 -·кŶӵȴʱ䣬ȡ統ǰͨ +分组在路由器的输入队列和输出队列中排队等待的时间,取决于网络当前的通信量。 -## ϵṹ* +## 计算机网络体系结构* -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/1005dc9d-9049-4b06-9524-6171e56ebd8c.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//1005dc9d-9049-4b06-9524-6171e56ebd8c.png) -### 1. ߲Э +### 1. 七层协议 -ͼ a ʾбʾͻỰ;£ +如图 a 所示,其中表示层和会话层用途如下: -1. ʾ㣺Ϣ﷨ԼǵĹܽܡת롢ѹѹ -2. Ự㣺ͬϵû֮佨Ự +1. 表示层:信息的语法语义以及它们的关联,如加密解密、转换翻译、压缩解压缩; +2. 会话层:不同机器上的用户之间建立及管理会话。 -### 2. Э +### 2. 五层协议 -1. Ӧò㣺ΪضӦóṩݴ HTTPDNS ȡݵλΪġ +1. 应用层:为特定应用程序提供数据传输服务,例如 HTTP、DNS 等。数据单位为报文。 -2. 㣺ṩǽ̼ͨݴӦòЭܶ࣬ͨõЭͿֲ֧ӦòЭ顣Э飺Э TCPṩӡɿݴݵλΪĶΣûݱЭ UDPṩӡŬݴݵλΪûݱTCP ҪṩԷUDP ҪṩʱԷ +2. 运输层:提供的是进程间的通用数据传输服务。由于应用层协议很多,定义通用的运输层协议就可以支持不断增多的应用层协议。运输层包括两种协议:传输控制协议 TCP,提供面向连接、可靠的数据传输服务,数据单位为报文段;用户数据报协议 UDP,提供无连接、尽最大努力的数据传输服务,数据单位为用户数据报。TCP 主要提供完整性服务,UDP 主要提供及时性服务。 -3. 㣺Ϊ֮ṩ񣬶ЭΪеĽṩıĶλûݱװɷд䡣 +3. 网络层:为主机之间提供服务,而不是像运输层协议那样是为主机中的进程提供服务。网络层把运输层产生的报文段或者用户数据报封装成分组来进行传输。 -4. ·㣺ԵĻ֮䣬֮кܶ··ЭΪڽ֮ṩ·㴫ķװ֡ +4. 数据链路层:网络层针对的还是主机之间,而主机之间可以有很多链路,链路层协议就是为相邻结点之间提供服务。数据链路层把网络层传来的分组封装成帧。 -5. 㣺ǵڴýϴݱָĴý塣ǾδýֶͨεIJ죬ʹϵ·оЩ졣 +5. 物理层:考虑的是怎样在传输媒体上传输数据比特流,而不是指具体的传输媒体。物理层的作用是尽可能屏蔽传输媒体和通信手段的差异,使物理层上的数据链路层感觉不到这些差异。 -### 3. ڸ֮Ĵݹ +### 3. 数据在各层之间的传递过程 -µĹУҪ²ЭҪײβϵĹвϲײβ +在向下的过程中,需要添加下层协议所需要的首部或者尾部,而在向上的过程中不断拆开首部和尾部。 -·ֻЭ飬Ϊ·λУҪΪ̻ӦóṩҲͲҪӦò㡣 +路由器只有下面三层协议,因为路由器位于网络核心中,不需要为进程或者应用程序提供服务,因此也就不需要运输层和应用层。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/f7d5da89-2d75-4d8f-85e7-6b608865dc00.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//f7d5da89-2d75-4d8f-85e7-6b608865dc00.jpg) -### 4. TCP/IP ϵṹ +### 4. TCP/IP 体系结构 -ֻIJ㣬൱Э·ϲΪӿڲ㡣 +它只有四层,相当于五层协议中数据链路层和物理层合并为网络接口层。 -ڵ TCP/IP ϵṹϸѭ OSI ֲӦòֱܻʹ IP ӿڲ㡣 +现在的 TCP/IP 体系结构不严格遵循 OSI 分层概念,应用层可能会直接使用 IP 层或者网络接口层。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/3e2200b3-1c18-4853-ae42-7788e8e1f939.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//3e2200b3-1c18-4853-ae42-7788e8e1f939.png) -TCP/IP Эһɳ©״мСߴIP Эռþصĵλ +TCP/IP 协议族是一种沙漏形状,中间小两边大,IP 协议在其中占用举足轻重的地位。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/9ecaebee-670e-4cb2-9cdb-3029c00f33bd.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//9ecaebee-670e-4cb2-9cdb-3029c00f33bd.png) -# ڶ +# 第二章 物理层 -## ͨŷʽ +## 通信方式 -1. ͨţֳΪͨţ -2. ˫ͨţֳΪ˫ͨţ -3. ˫ͬʱͨţֳΪȫ˫ͨš +1. 单向通信,又称为单工通信; +2. 双向交替通信,又称为半双工通信; +3. 双向同时通信,又称为全双工通信。 -## ͨ +## 带通调制 -ģźźţźɢźšͨưźתΪģźš +模拟信号是连续的信号,数字信号是离散的信号。带通调制把数字信号转换为模拟信号。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/d2c55c84-aa1f-43c1-bd97-457bcb7816b3.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//d2c55c84-aa1f-43c1-bd97-457bcb7816b3.png) -## ŵü +## 信道复用技术 -### 1. Ƶָáʱָ +### 1. 频分复用、时分复用 -ƵָõûͬʱռòͬƵʴԴʱָõûڲͬʱռͬƵʴԴ +频分复用的所有用户在相同的时间占用不同的频率带宽资源;时分复用的所有用户在不同的时间占用相同的频率带宽资源。 -ʹַʽͨţͨŵĹûһֱռһŵԴڼݵͻʣûҪһֱռŵԴóûʹãַʽŵʶߡ +使用这两种方式进行通信,在通信的过程中用户会一直占用一部分信道资源。但是由于计算机数据的突发性质,没必要一直占用信道资源而不让出给其它用户使用,因此这两种方式对信道的利用率都不高。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/543d47a1-f0dd-414f-b23c-0c142c814854.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//543d47a1-f0dd-414f-b23c-0c142c814854.png) -### 2. ͳʱָ +### 2. 统计时分复用 -ǶʱָõһָĽ̶ÿûʱָ֡еλãֻҪݾͼʱָ֡Ȼ͡ +是对时分复用的一种改进,不固定每个用户在时分复用帧中的位置,只要有数据就集中起来组成时分复用帧然后发送。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/29058e09-bb72-4040-a73d-4c497895e9ce.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//29058e09-bb72-4040-a73d-4c497895e9ce.jpg) -### 3. ָ +### 3. 波分复用 -ƵָáڹƵʺܸߣϰòƵʾʹõĹز +光的频分复用。由于光的频率很高,因此习惯上用波长而不是频率来表示所使用的光载波。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/78534153-88d1-4f83-a6e0-59064dbdc43a.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//78534153-88d1-4f83-a6e0-59064dbdc43a.png) -### 4. ָ +### 4. 码分复用 -Ϊÿû m bit ƬеƬƬ $\vec{S}$ $\vec{T}$ +为每个用户分配 m bit 的码片,并且所有的码片正交,对于任意两个码片 有 -![](http://latex.codecogs.com/gif.latex?\\\\\vec{S}\cdot\vec{T}=0) +
-Ϊ˷㣬ȡ m=8Ƭ $\vec{S}$ Ϊ 00011011ӵиƬûͱ 1 ʱͷ͸Ƭͱ 0 ʱͷ͸Ƭķ 11100100 +为了方便,取 m=8,设码片 为 00011011。在拥有该码片的用户发送比特 1 时就发送该码片,发送比特 0 时就发送该码片的反码 11100100。 -ڼʱ 00011011 (-1 -1 -1 +1 +1 -1 +1 +1)Եõ +在计算时将 00011011 记作 (-1 -1 -1 +1 +1 -1 +1 +1),可以得到 -![](http://latex.codecogs.com/gif.latex?\\\\\frac{1}{m}\vec{S}\cdot\vec{S}=1) +
-![](http://latex.codecogs.com/gif.latex?\\\\\frac{1}{m}\vec{S}\cdot\vec{S'}=-1) +
- $\vec{S'}$ Ϊ $\vec{S}$ ķ롣 +其中 的反码。 -ʽ֪նʹƬ $\vec{S}$ ԽյݽڻʱΪ 0 û͵ݣΪ 1 û͵ı 1Ϊ -1 û͵ı 0 +利用上面的式子我们知道,当接收端使用码片 对接收到的数据进行内积运算时,结果为 0 的是其它用户发送的数据,结果为 1 的是用户发送的比特 1,结果为 -1 的是用户发送的比特 0。 -ָҪ͵Ϊԭȵ m +码分复用需要发送的数据量为原先的 m 倍。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/0042edad-8e3b-4279-bd93-6906fcd1b640.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//0042edad-8e3b-4279-bd93-6906fcd1b640.jpg) -# · +# 第三章 数据链路层 -## +## 三个基本问题 -### 1. װ֡ +### 1. 封装成帧 -㴫ķײβڱ֡Ŀʼͽ +将网络层传下来的分组添加首部和尾部,用于标记帧的开始和结束。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/ea5ed9b2-6d9f-48fb-b890-0288caf9088a.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//ea5ed9b2-6d9f-48fb-b890-0288caf9088a.jpg) -### 2. ͸ +### 2. 透明传输 -͸ʾһʵʴڵ￴񲻴һ +透明表示一个实际存在的事物看起来好像不存在一样。 -֡ײβ֡ݲֺкײβͬݣô֡ĿʼͽλþͻᱻжҪгײβͬǰתַҪþתַôתַǰټӸתַڽն˽д֮Իԭԭʼݡ͸תַûתַĴڡ +帧中有首部和尾部,如果帧的数据部分含有和首部尾部相同的内容,那么帧的开始和结束位置就会被错误的判定。需要在数据中出现首部尾部相同的内容前面插入转义字符,如果需要传输的内容正好就是转义字符,那么就在转义字符前面再加个转义字符,在接收端进行处理之后可以还原出原始数据。这个过程透明传输的内容是转义字符,用户察觉不到转义字符的存在。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/44e1d90e-3fe6-4dd6-8dce-6daab12e7663.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//44e1d90e-3fe6-4dd6-8dce-6daab12e7663.jpg) -### 3. +### 3. 差错检测 -Ŀǰ·㷺ʹѭ飨CRCز +目前数据链路层广泛使用了循环冗余检验(CRC)来检查比特差错。 - CRCеļ鷽磺 +除了 CRC,还有的检验方法比如: -- żУ飺 1 λʹ 1 λΪżһĶάżУ鷽ԼԾִ -- ͷο UDP ͼ㷽 +- 奇偶校验:添加 1 位使 1 的位数为奇数或偶数,更进一步的二维奇偶校验方法,不但可以检错还可以纠错部分错误。 +- 检验和方法:具体参考 UDP 检验和计算方法; -## Եŵ - PPP Э +## 点对点信道 - PPP 协议 -ûͨҪӵij ISP ֮ܽ뵽PPP Эû ISP ͨʱʹõ·Э顣 +互联网用户通常需要连接到某个 ISP 之后才能接入到互联网,PPP 协议就是用户计算机和 ISP 进行通信时所使用的数据链路层协议。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/8b5bd2c8-8425-4a8b-89db-235c95800de9.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//8b5bd2c8-8425-4a8b-89db-235c95800de9.jpg) - PPP ֡УF ֶΪ֡ĶA C ʱû塣FCS ʹ CRC ļСϢֵijȲ 1500 +在 PPP 的帧中,F 字段为帧的定界符,A 和 C 暂时没有意义。FCS 是使用 CRC 的检验序列。信息部分的长度不超过 1500。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/a5fa89e7-54b9-4e2f-8c48-a35712d7b2f5.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//a5fa89e7-54b9-4e2f-8c48-a35712d7b2f5.jpg) -## +## 局域网的拓扑 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/8b15e36f-69b4-46b6-a07c-7234ac7c7927.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//8b15e36f-69b4-46b6-a07c-7234ac7c7927.jpg) -## 㲥ŵ - CSMA/CD Э* +## 广播信道 - CSMA/CD 协议* -ڹ㲥ŵϣͬһʱֻһ̨ݡ +在广播信道上,同一时间只能允许一台计算机发送数据。 -CSMA/CD ʾز / ײ⡣ +CSMA/CD 表示载波监听多点接入 / 碰撞检测。 -- ****˵磬Զķʽӵϡ -- **ز**ÿվ벻ͣؼŵڷǰŵʹãͱȴ -- **ײ**ڷУŵվڷݣͱʾײȻÿһվڷ֮ǰѾŵΪУڵŲĴʱӵĴڣпܻᷢײ +- **多点接入**:说明这是总线型网络,许多计算机以多点的方式连接到总线上。 +- **载波监听**:每个站都必须不停地检听信道。在发送前,如果检听信道正在使用,就必须等待。 +- **碰撞检测**:在发送中,如果检听信道已有其它站正在发送数据,就表示发生了碰撞。虽然每一个站在发送数据之前都已经检听信道为空闲,但是由于电磁波的传播时延的存在,还是有可能会发生碰撞。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/f9ed4da5-0032-41e6-991a-36d995ec28fd.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//f9ed4da5-0032-41e6-991a-36d995ec28fd.png) -Ƕ˵˵ĴʱΪ ӣȷ͵վྭ 2 Ϳ֪Ƿײ 2 Ϊ ****ֻо֮ûм⵽ײܿ϶ηͲᷢײ +记端到端的传播时延为 τ,最先发送的站点最多经过 2τ 就可以知道是否发生了碰撞,称 2τ 为 **争用期**。只有经过争用期之后还没有检测到碰撞,才能肯定这次发送不会发生碰撞。 -ײʱվҪֹͣͣȴһʱٷ͡ʱ **ض϶ָ˱㷨** ȷɢ {0, 1, .., (2k-1)} ȡһ rȻȡ r Ϊشȴʱ䡣 +当发生碰撞时,站点要停止发送,等待一段时间再发送。这个时间采用 **截断二进制指数退避算法** 来确定,从离散的整数集合 {0, 1, .., (2k-1)} 中随机取出一个数,记作 r,然后取 r 倍的争用期作为重传等待时间。 -## +## 集线器 -ӱϿʹüľһǼʹõģʵߵĹ߼һϵͳһͳ̫С +从表面上看,使用集线器的局域网在物理上是一个星型网。但是集线器使用电子器件来模拟实际缆线的工作,逻辑上仍是一个总线网,整个系统仍像一个传统以太网那样运行。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/897a4f4e-2683-44e1-a26a-c0d0234dc576.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//897a4f4e-2683-44e1-a26a-c0d0234dc576.jpg) -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/40c3f8e5-3a20-45b6-a60c-77b9b952e104.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//40c3f8e5-3a20-45b6-a60c-77b9b952e104.jpg) -## MAC +## MAC 层 -MAC ַ 6 ֽڣ48 λĵַΨһʾһ̨ӵжٸжٸ MAC ַʼDZձ +MAC 地址是 6 字节(48 位)的地址,用于唯一表示网络适配器(网卡),一台主机拥有多少个适配器就有多少个 MAC 地址,例如笔记本电脑普遍存在无线网络适配器和有线网络适配器。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/50d38e84-238f-4081-8876-14ef6d7938b5.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//50d38e84-238f-4081-8876-14ef6d7938b5.jpg) -- ****ϲʹõЭ飻 -- **** 46-1500 ֮䣬̫СҪ䣻 -- **FCS**֡Уʹõ CRC 鷽 -- **ǰͬ**ֻΪ˼ FCS ʱģ֮ᶪ +- **类型**:标记上层使用的协议; +- **数据**:长度在 46-1500 之间,如果太小则需要填充; +- **FCS**:帧检验序列,使用的是 CRC 检验方法; +- **前同步码**:只是为了计算 FCS 临时加入的,计算结束之后会丢弃。 -## +## 虚拟局域网 -Խλ޹ص߼飬ֻͬһеijԱŻյ·㲥Ϣͼ (A1, A2, A3, A4) һA1 ͵Ĺ㲥ᱻ A2A3A4 յվղ +虚拟局域网可以建立与物理位置无关的逻辑组,只有在同一个虚拟局域网中的成员才会收到链路层广播信息,例如下图中 (A1, A2, A3, A4) 属于一个虚拟局域网,A1 发送的广播会被 A2、A3、A4 收到,而其它站点收不到。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/a74b70ac-323a-4b31-b4d5-90569b8a944b.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//a74b70ac-323a-4b31-b4d5-90569b8a944b.png) -# * +# 第四章 网络层* -## Э IP +## 网际协议 IP 概述 -ΪĺģӦ㾡ܼ򵥡ֻṩġӵġŬݱ +因为网络层是整个互联网的核心,因此应当让网络层尽可能简单。网络层向上只提供简单灵活的、无连接的、尽最大努力交互的数据报服务。 -ʹ IP Э飬԰칹ʹ㿴һͳһ硣 +使用 IP 协议,可以把异构的物理网络连接起来,使得在网络层看起来好像是一个统一的网络。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/fe3d224c-8ffd-40f9-85b1-86ffe1393f6c.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//fe3d224c-8ffd-40f9-85b1-86ffe1393f6c.jpg) - IP ЭʹõĻЭ飺 +与 IP 协议配套使用的还有三个协议: -1. ַЭ ARPAddress Resolution Protocol -2. ʿƱЭ ICMPInternet Control Message Protocol -3. Э IGMPInternet Group Management Protocol +1. 地址解析协议 ARP(Address Resolution Protocol) +2. 网际控制报文协议 ICMP(Internet Control Message Protocol) +3. 网际组管理协议 IGMP(Internet Group Management Protocol) -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/163cf8b4-5f30-46c9-af00-316a71b3c890.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//163cf8b4-5f30-46c9-af00-316a71b3c890.jpg) -## IP ݱʽ +## IP 数据报格式 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/8681db55-0873-434b-aa98-83d07e8392ae.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//8681db55-0873-434b-aa98-83d07e8392ae.jpg) -- **汾** : 4IPv4 6IPv6ֵ +- **版本** : 有 4(IPv4)和 6(IPv6)两个值; -- **ײ** : ռ 4 λֵΪ 15ֵΪ 1 ʾ 1 32 λֵijȣҲ 4 ֽڡΪײ̶Ϊ 20 ֽڣ˸ֵСΪ 5ѡֵijȲ 4 ֽڵβ䲿䡣 +- **首部长度** : 占 4 位,因此最大值为 15。值为 1 表示的是 1 个 32 位字的长度,也就是 4 字节。因为首部固定长度为 20 字节,因此该值最小为 5。如果可选部分的长度不是 4 字节的整数倍,就用尾部的填充部分来填充。 -- **ַ** : øõķһ²ʹá +- **区分服务** : 用来获得更好的服务,一般情况下不使用。 -- **ܳ** : ײȺݲֳȡ +- **总长度** : 包括首部长度和数据部分长度。 -- **ʶ** : ݱȹӶƬ£ͬݱIJͬƬͬıʶ +- **标识** : 在数据报长度过长从而发生分片的情况下,相同数据报的不同分片具有相同的标识符。 -- **Ƭƫ** : ͱʶһڷƬƬƫƵĵλΪ 8 ֽڡ +- **片偏移** : 和标识符一起,用于发生分片的情况。片偏移的单位为 8 字节。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/45c86855-9b18-4cf4-a9a7-f8b6eb78d133.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//45c86855-9b18-4cf4-a9a7-f8b6eb78d133.png) -- **ʱ** TTLĴΪ˷ֹ޷ݱڻв϶Ȧӡ·Ϊλ TTL Ϊ 0 ʱͶݱ +- **生存时间** :TTL,它的存在是为了防止无法交付的数据报在互联网中不断兜圈子。以路由器跳数为单位,当 TTL 为 0 时就丢弃数据报。 -- **Э**ָЯӦϽĸЭд ICMPTCPUDP ȡ +- **协议**:指出携带的数据应该上交给哪个协议进行处理,例如 ICMP、TCP、UDP 等。 -- **ײ**Ϊݱÿһ·Ҫ¼ͣ˼ͲݲֿԼټĹ +- **首部检验和**:因为数据报每经过一个路由器,都要重新计算检验和,因此检验和不包含数据部分可以减少计算的工作量。 -## IP ַַ +## IP 地址编址 -IP ַıַʽʷ׶Σ +IP 地址的编址方式经历了三个历史阶段: -1. ࣻ -2. ֣ -3. ޷ࡣ +1. 分类; +2. 子网划分; +3. 无分类。 -### 1. +### 1. 分类 -ɣźţвͬвͬųȣǹ̶ġ +由两部分组成,网络号和主机号,其中不同类别具有不同的网络号长度,并且是固定的。 -IP ַ ::= {< >, < >} +IP 地址 ::= {< 网络号 >, < 主机号 >} -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/2ddd6132-60be-4a72-9daa-3d9756191f4a.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//2ddd6132-60be-4a72-9daa-3d9756191f4a.png) -### 2. +### 2. 子网划分 -ֶͨһΪţ IP ַΪ IP ַע⣬ⲿ翴Ĵڡ +通过在网络号字段中拿一部分作为子网号,把两级 IP 地址划分为三级 IP 地址。注意,外部网络看不到子网的存在。 -IP ַ ::= {< >, < >, < >} +IP 地址 ::= {< 网络号 >, < 子网号 >, < 主机号 >} -Ҫʹ롣һ B ַĬΪ 255.255.0.0 B ַռأôΪ 11111111 11111111 11000000 000000Ҳ 255.255.192.0 +要使用子网,必须配置子网掩码。一个 B 类地址的默认子网掩码为 255.255.0.0,如果 B 类地址的子网占两个比特,那么子网掩码为 11111111 11111111 11000000 000000,也就是 255.255.192.0。 -### 3. ޷ +### 3. 无分类 -޷ַ CIDR ˴ͳ A ࡢB C ַԼĸʹǰ׺ IP ַб룬ǰ׺ijȿԸҪ仯 +无分类编址 CIDR 消除了传统 A 类、B 类和 C 类地址以及划分子网的概念,使用网络前缀和主机号来对 IP 地址进行编码,网络前缀的长度可以根据需要变化。 -IP ַ ::= {< ǰ׺ >, < >} +IP 地址 ::= {< 网络前缀号 >, < 主机号 >} -CIDR ļǷϲ IP ַǰ׺ȵķ 128.14.35.7/20 ʾǰ 20 λΪǰ׺ +CIDR 的记法上采用在 IP 地址后面加上网络前缀长度的方法,例如 128.14.35.7/20 表示前 20 位为网络前缀。 -CIDR ĵַԼΪ룬 1 Ϊǰ׺ijȡ +CIDR 的地址掩码可以继续称为子网掩码,子网掩码首 1 长度为网络前缀的长度。 -һ CIDR ַкַܶһ CIDR ʾͿԱʾԭĺܶ磬·ɱֻҪһ·ɾͿԴԭĶ·ɣ·ɱͨʹǰ׺·ɱķʽΪ·ɾۺϣҲΪ **ɳ** +一个 CIDR 地址块中有很多地址,一个 CIDR 表示的网络就可以表示原来的很多个网络,并且在路由表中只需要一个路由就可以代替原来的多个路由,减少了路由表项的数量。把这种通过使用网络前缀来减少路由表项的方式称为路由聚合,也称为 **构成超网**。 -·ɱеĿɡǰ׺͡һַɣڲʱܻõֹһƥӦǰ׺ƥȷӦƥһ +在路由表中的项目由“网络前缀”和“下一跳地址”组成,在查找时可能会得到不止一个匹配结果,应当采用最长前缀匹配来确定应该匹配哪一个。 -## IP ַ MAC ַ +## IP 地址和 MAC 地址 -ʵ֮ͨţ·ʵ־ÿ·֮ͨšͨŹУIP ݱԴַĿĵַʼղ䣬 MAC ַ·ĸıı䡣 +网络层实现主机之间的通信,而链路层实现具体每段链路之间的通信。因此在通信过程中,IP 数据报的源地址和目的地址始终不变,而 MAC 地址随着链路的改变而改变。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/86b71296-0d1e-4a63-bcd9-54955b6b781b.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//86b71296-0d1e-4a63-bcd9-54955b6b781b.jpg) -## ַЭ ARP +## 地址解析协议 ARP -ʵ IP ַõ MAC ַ +实现由 IP 地址得到 MAC 地址。 -ÿһ ARP ٻ棬ӳһ IP ַ MAC ַӳ䲻ڸñУͨ㲥ķʽ ARP 飬ƥ IP ַᷢ ARP Ӧ֪ MAC ַ +每个主机都有一个 ARP 高速缓存,存放映射表。如果一个 IP 地址到 MAC 地址的映射不在该表中,主机通过广播的方式发送 ARP 请求分组,匹配 IP 地址的主机会发送 ARP 响应分组告知其 MAC 地址。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/8bc6fc2c-d198-4759-b06c-18d94d851e97.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//8bc6fc2c-d198-4759-b06c-18d94d851e97.png) -## ·Ľṹ +## 路由器的结构 -·ӹϿԻΪ󲿷֣·ѡͷת +路由器从功能上可以划分为两大部分:路由选择和分组转发。 -תɣṹһ˿ںһ˿ڡ +分组转发部分由三部分组成:交换结构、一组输入端口和一组输出端口。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/3a676c54-b559-4466-9b21-eb10f1e25879.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//3a676c54-b559-4466-9b21-eb10f1e25879.jpg) -ṹĽʵַʽ +交换结构的交换网络有以下三种实现方式: -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/7f82fd18-7f16-4125-ada6-bb6b795b4fda.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//7f82fd18-7f16-4125-ada6-bb6b795b4fda.png) -## · +## 交换机与路由器的区别 -- ·㣬ʶ MAC ַ MAC ַת·֡ѧά IP ַ MAC ַӳ䡣 -- ·λ㣬ʶ IP ַ IP ַת顣ά·ɱ·ɱѡ·ߡ +- 交换机工作于数据链路层,能识别 MAC 地址,根据 MAC 地址转发链路层数据帧。具有自学机制来维护 IP 地址与 MAC 地址的映射。 +- 路由器位于网络层,能识别 IP 地址并根据 IP 地址转发分组。维护着路由表,根据路由表选择最佳路线。 -## ·ת +## 路由器分组转发流程 -1. ݱײȡĿ IP ַ DõĿַ N·ɱŶ IP ַ·ɱĿ -2. N ·ֱijֱַӽ -3. ·ɱĿĵַΪ D ض·ɣݱ͸ָһ· -4. ·ɱе N ·ɣݱ͸·ɱָһ· -5. ·ɱһĬ·ɣݱ͸·ɱָĬ· -6. ת +1. 从数据报的首部提取目的主机的 IP 地址 D,得到目的网络地址 N。(路由表项是网络号而不是 IP 地址,这样做大大减少了路由表条目数量) +2. 若 N 就是与此路由器直接相连的某个网络地址,则进行直接交付; +3. 若路由表中有目的地址为 D 的特定主机路由,则把数据报传送给表中所指明的下一跳路由器; +4. 若路由表中有到达网络 N 的路由,则把数据报传送给路由表中所指明的下一跳路由器; +5. 若路由表中有一个默认路由,则把数据报传送给路由表中所指明的默认路由器; +6. 报告转发分组出错。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/8d211911-0e62-4190-ab00-d8610adec4a0.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//8d211911-0e62-4190-ab00-d8610adec4a0.jpg) -## ·ѡЭ +## 路由选择协议 -ʹõ·ѡЭ鶼Ӧģͨ˽ṹı仯Ӧؽе +互联网使用的路由选择协议都是自适应的,能随着网络通信量和拓扑结构的变化而自适应地进行调整。 -ԻΪСϵͳ ASһ AS ʹһֺͱ AS ͬ·ѡЭ顣 +互联网可以划分为许多较小的自治系统 AS,一个 AS 可以使用一种和别的 AS 不同的路由选择协议。 -԰·ѡЭ黮Ϊࣺ +可以把路由选择协议划分为两大类: -1. ڲЭ IGPInterior Gateway Protocol AS ڲʹã RIP OSPF -2. ⲿЭ EGPExternal Gateway Protocol AS ֮ʹã BGP +1. 内部网关协议 IGP(Interior Gateway Protocol):在 AS 内部使用,如 RIP 和 OSPF。 +2. 外部网关协议 EGP(External Gateway Protocol):在 AS 之间使用,如 BGP。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/e0be6970-5b0e-44a2-bc71-df4d61c42b8f.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//e0be6970-5b0e-44a2-bc71-df4d61c42b8f.jpg) -### 1. ڲЭ RIP +### 1. 内部网关协议 RIP -RIP һֲַʽĻھ·ѡЭ顣ֱָ·Ϊ 1Ϊ 15 15 ʾɴ +RIP 是一种分布式的基于距离向量的路由选择协议。距离是指跳数,直接相连的路由器跳数为 1,跳数最多为 15,超过 15 表示不可达。 -RIP ̶ʱ·Լ·ɱɴν֮·ջ֪ﱾϵͳκһ̾һ·ַ +RIP 按固定的时间间隔仅和相邻路由器交换自己的路由表,经过若干次交换之后,所有路由器最终会知道到达本自治系统中任何一个网络的最短距离和下一跳路由器地址。 -㷨 +距离向量算法: -1. ԵַΪ X · RIP ģ޸ıеĿһֶеĵַΪ Xеľֶμ 1 -2. ޸ĺ RIP еÿһĿ²裺 - - ԭ·ɱûĿ NѸĿӵ·ɱУ - - һ·ַ XյĿ滻ԭ·ɱеĿյĿеľ d С·ɱеľ룬и£ԭʼ·ɱΪ Net2, 5, P±Ϊ Net2, 4, X£ʲôҲ -3. 3 ӻûյ·ĸ·ɱѸ·ΪɴѾΪ 16 +1. 对地址为 X 的相邻路由器发来的 RIP 报文,先修改报文中的所有项目,把下一跳字段中的地址改为 X,并把所有的距离字段加 1; +2. 对修改后的 RIP 报文中的每一个项目,进行以下步骤: + - 若原来的路由表中没有目的网络 N,则把该项目添加到路由表中; + - 否则:若下一跳路由器地址是 X,则把收到的项目替换原来路由表中的项目;否则:若收到的项目中的距离 d 小于路由表中的距离,则进行更新(例如原始路由表项为 Net2, 5, P,新表项为 Net2, 4, X,则更新);否则什么也不做。 +3. 若 3 分钟还没有收到相邻路由器的更新路由表,则把该相邻路由器标为不可达,即把距离置为 16。 -RIP Эʵּ򵥣С RIP ʹõΪ 15ĹģҵֹʱҪȽϳʱܽϢ͵· +RIP 协议实现简单,开销小,但是 RIP 能使用的最大距离为 15,限制了网络的规模。并且当网络出现故障时,要经过比较长的时间才能将此消息传送到所有路由器。 -### 2. ڲЭ OSPF +### 2. 内部网关协议 OSPF -· OSPFΪ˿˷ RIP ȱġ +开放最短路径优先 OSPF,是为了克服 RIP 的缺点而开发出来的。 -űʾ OSPF ijһҳ̿ƣǹģ·ȱʾʹ Dijkstra ·㷨 SPF +开放表示 OSPF 不受某一家厂商控制,而是公开发表的;最短路径优先表示使用了 Dijkstra 提出的最短路径算法 SPF。 -OSPF ص㣺 +OSPF 具有以下特点: -- ϵͳе·ϢַǺ鷺 -- ͵Ϣ··״̬·״̬Щ·Լ·Ķ÷á롢ʱӡʾ -- ֻе·״̬仯ʱ·ŻᷢϢ +- 向本自治系统中的所有路由器发送信息,这种方法是洪泛法。 +- 发送的信息就是与相邻路由器的链路状态,链路状态包括与哪些路由器相连以及链路的度量,度量用费用、距离、时延、带宽等来表示。 +- 只有当链路状态发生变化时,路由器才会发送信息。 -·ȫ˽ṹͼһµġ RIPOSPF ĸ¹ĺܿ졣 +所有路由器都具有全网的拓扑结构图,并且是一致的。相比于 RIP,OSPF 的更新过程收敛的很快。 -### 3. ⲿЭ BGP +### 3. 外部网关协议 BGP -AS ֮·ѡѣҪǻģܴ󡣲Ҹ AS ڲʹòͬ·ѡЭ飬޷׼ȷ·Ķ AS ֮·ѡ뿼йصIJԣЩ AS Ը AS +AS 之间的路由选择很困难,主要是互联网规模很大。并且各个 AS 内部使用不同的路由选择协议,就无法准确定义路径的度量。并且 AS 之间的路由选择必须考虑有关的策略,比如有些 AS 不愿意让其它 AS 经过。 -BGP ֻѰһȽϺõ·ɣ·ɡ··ѡЭ顣 +BGP 只能寻找一条比较好的路由,而不是最佳路由。它采用路径向量路由选择协议。 -ÿ AS BGP ˣͨ BGP ֮佨 TCP ·Ϣ +每个 AS 都必须配置 BGP 发言人,通过在两个相邻 BGP 发言人之间建立 TCP 连接来交换路由信息。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/eb6271de-22c9-4f4b-8b31-eab1f560efac.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//eb6271de-22c9-4f4b-8b31-eab1f560efac.png) -## ʿƱЭ ICMP +## 网际控制报文协议 ICMP -ICMP Ϊ˸Чת IP ݱ߽ɹĻᡣװ IP ݱУDzڸ߲Э顣 +ICMP 是为了更有效地转发 IP 数据报和提高交付成功的机会。它封装在 IP 数据报中,但是不属于高层协议。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/9b5e0fa0-9274-4219-a3a9-84fbb509c735.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//9b5e0fa0-9274-4219-a3a9-84fbb509c735.jpg) -ICMP ķΪ汨ĺѯʱġ +ICMP 报文分为差错报告报文和询问报文。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/6e11b122-95ce-4869-bf7d-3b0d7591707e.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//6e11b122-95ce-4869-bf7d-3b0d7591707e.jpg) -## ̽ PING +## 分组网间探测 PING -PING ICMP һҪӦãҪ̨֮ͨԡ +PING 是 ICMP 的一个重要应用,主要用来测试两台主机之间的连通性。 -PING Ḷ́ +PING 的过程: -1. PING ͬһεĿ MAC ַȻֱӽ޷ҵ MAC ַҪһ ARP -2. PING ͬε͵תͬҪ͵ҲҪͨص MAC ַ MAC ַת +1. PING 同一个网段的主机:查找目的主机的 MAC 地址,然后直接交付。如果无法查找到 MAC 地址,就要进行一次 ARP 请求。 +2. PING 不同网段的主机:发送到网关让其进行转发。同样要发送到网关也需要通过查找网关的 MAC 地址,根据 MAC 地址进行转发。 -## IP ಥ +## IP 多播 -һԶͨУಥҪ鸴ƶݣӶԼԴ +在一对多的通信中,多播不需要将分组复制多份,从而大大节约网络资源。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/c77b6a18-dfac-42a2-ac89-7e99481275dc.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//c77b6a18-dfac-42a2-ac89-7e99481275dc.jpg) -## ר VPN +## 虚拟专用网 VPN - IP ַĽȱһ뵽 IP ַԶСڱӵеһҪе뵽ⲿĻУڵļʹýڱЧ IP ַרõַ +由于 IP 地址的紧缺,一个机构能申请到的 IP 地址数往往远小于本机构所拥有的主机数。并且一个机构并不需要把所有的主机接入到外部的互联网中,机构内的计算机可以使用仅在本机构有效的 IP 地址(专用地址)。 -רõַ飺 +有三个专用地址块: 1. 10.0.0.0 \~ 10.255.255.255 2. 172.16.0.0 \~ 172.31.255.255 3. 192.168.0.0 \~ 192.168.255.255 -VPN ʹùõĻΪר֮ͨ塣רָڵֻ뱾ڵͨţָǡʵϲǣоõĻ +VPN 使用公用的互联网作为本机构各专用网之间的通信载体。专用指机构内的主机只与本机构内的其它主机通信;虚拟指“好像是”,而实际上并不是,它有经过公用的互联网。 -ͼУ A B ͨŲ A X Ҫһ B Y ͨţIP ݱԴַ 10.1.0.1Ŀĵַ 10.2.0.3ݱȷ͵뻥· R1R1 ڲݽмܣȻ¼ݱײԴַ· R1 ȫַ 125.1.2.3Ŀĵַ· R2 ȫַ 194.4.5.6· R2 յݱݲֽнܣָԭݱʱĿĵַΪ 10.2.0.3ͽ Y +下图中,场所 A 和 B 的通信部经过互联网,如果场所 A 的主机 X 要和另一个场所 B 的主机 Y 通信,IP 数据报的源地址是 10.1.0.1,目的地址是 10.2.0.3。数据报先发送到与互联网相连的路由器 R1,R1 对内部数据进行加密,然后重新加上数据报的首部,源地址是路由器 R1 的全球地址 125.1.2.3,目的地址是路由器 R2 的全球地址 194.4.5.6。路由器 R2 收到数据报后将数据部分进行解密,恢复原来的数据报,此时目的地址为 10.2.0.3,就交付给 Y。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/bf4ed077-d481-4db7-9e7a-85d841a5a8c3.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//bf4ed077-d481-4db7-9e7a-85d841a5a8c3.jpg) -## ַת NAT +## 网络地址转换 NAT -רڲʹñ IP ַͻϵͨʱʹ NAT IP תΪȫ IP +专用网内部的主机使用本地 IP 地址又想和互联网上的主机通信时,可以使用 NAT 来将本地 IP 转换为全球 IP。 -ǰNAT IP ȫ IP һһӦַʽӵ n ȫ IP ַרֻͬʱ n ̨뻥Ϊ˸Чȫ IP ַڳõ NAT תĶ˿ںҲˣʹöרڲһȫ IP ַʹö˿ںŵ NAT Ҳַ˿ת NAPT +在以前,NAT 将本地 IP 和全球 IP 一一对应,这种方式下拥有 n 个全球 IP 地址的专用网内最多只可以同时有 n 台主机接入互联网。为了更有效地利用全球 IP 地址,现在常用的 NAT 转换表把运输层的端口号也用上了,使得多个专用网内部的主机共用一个全球 IP 地址。使用端口号的 NAT 也叫做网络地址与端口转换 NAPT。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/0f31bc7a-d60b-48a6-8e3f-597708369e52.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//0f31bc7a-d60b-48a6-8e3f-597708369e52.png) -# * +# 第五章 运输层* -ֻѷ鷢͵ĿͨŵIJеĽ̡ +网络层只把分组发送到目的主机,但是真正通信的并不是主机而是主机中的进程。 -ṩӦý̼߼ͨš߲ûĺϸڣʹӦó򿴼ĺʵ֮һ˵˵߼ͨŵ +运输层提供了应用进程间的逻辑通信。运输层向高层用户屏蔽了下面网络层的核心细节,使应用程序看见的好像在两个运输层实体之间有一条端到端的逻辑通信信道。 -## UDP TCP ص +## UDP 和 TCP 的特点 -- ûݰЭ UDPUser Datagram ProtocolӵģܽûӵƣģӦóıIJϲҲֻ֣ UDP ײ +- 用户数据包协议 UDP(User Datagram Protocol)是无连接的,尽最大可能交付,没有拥塞控制,面向报文(对于应用程序传下来的报文不合并也不拆分,只是添加 UDP 首部)。 -- Э TCPTransmission Control Protocol ӵģṩɿƣӵƣṩȫ˫ͨţֽӦò㴫ıĿֽֽ֯ɴСȵݿ飩 +- 传输控制协议 TCP(Transmission Control Protocol) 是面向连接的,提供可靠交付,有流量控制,拥塞控制,提供全双工通信,面向字节流(把应用层传下来的报文看成字节流,把字节流组织成大小不等的数据块) -## UDP ײʽ +## UDP 首部格式 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/bd6c05f3-02ee-4c8a-b374-40c87154a898.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//bd6c05f3-02ee-4c8a-b374-40c87154a898.jpg) -ײֶֻ 8 ֽڣԴ˿ڡĿĶ˿ڡȡ͡12 ֽڵαײΪ˼Ͷʱӵġ +首部字段只有 8 个字节,包括源端口、目的端口、长度、检验和。12 字节的伪首部是为了计算检验和而临时添加的。 -## TCP ײʽ +## TCP 首部格式 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/21a00b02-c0a6-4bcd-9af0-5ec6bb66e34c.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//21a00b02-c0a6-4bcd-9af0-5ec6bb66e34c.jpg) -- **** ڶֽбţΪ 301ʾһֽڵıΪ 301ЯݳΪ 100 ֽڣôһĶεӦΪ 401 +- **序号** :用于对字节流进行编号,例如序号为 301,表示第一个字节的编号为 301,如果携带的数据长度为 100 字节,那么下一个报文段的序号应为 401。 -- **ȷϺ** յһĶεš B ȷյ A һĶΣΪ 501ЯݳΪ 200 ֽڣ B һĶεΪ 701B ͸ A ȷϱĶȷϺžΪ 701 +- **确认号** :期望收到的下一个报文段的序号。例如 B 正确收到 A 发送来的一个报文段,序号为 501,携带的数据长度为 200 字节,因此 B 期望下一个报文段的序号为 701,B 发送给 A 的确认报文段中确认号就为 701。 -- **ƫ** ָݲ־뱨Ķʼƫʵָײijȡ +- **数据偏移** :指的是数据部分距离报文段起始处的偏移量,实际上指的是首部的长度。 -- **ȷ ACK** ACK=1 ʱȷϺֶЧЧTCP 涨ӽд͵ıĶζ ACK 1 +- **确认 ACK** :当 ACK=1 时确认号字段有效,否则无效。TCP 规定,在连接建立后所有传送的报文段都必须把 ACK 置 1。 -- **ͬ SYN** ӽʱͬš SYN=1ACK=0 ʱʾһĶΡԷͬ⽨ӣӦ SYN=1ACK=1 +- **同步 SYN** :在连接建立时用来同步序号。当 SYN=1,ACK=0 时表示这是一个连接请求报文段。若对方同意建立连接,则响应报文中 SYN=1,ACK=1。 -- **ֹ FIN** ͷһӣ FIN=1 ʱʾ˱ĶεķͷѷϣҪͷӡ +- **终止 FIN** :用来释放一个连接,当 FIN=1 时,表示此报文段的发送方的数据已发送完毕,并要求释放运输连接。 -- **** ֵΪշ÷ͷ䷢ʹڵݡ֮ҪƣΪշݻռ޵ġ +- **窗口** :窗口值作为接收方让发送方设置其发送窗口的依据。之所以要有这个限制,是因为接收方的数据缓存空间是有限的。 -## TCP +## TCP 的三次握手 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/086871db-5871-460f-97b7-126cd738bb0e.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//086871db-5871-460f-97b7-126cd738bb0e.jpg) - A ΪͻˣB Ϊˡ +假设 A 为客户端,B 为服务器端。 -1. B LISTEN״̬ȴͻ -2. A B ĶΣSYN=1ACK=0ѡһʼ x -3. B յĶΣͬ⽨ӣ A ȷϱĶΣSYN=1ACK=1ȷϺΪ x+1ͬʱҲѡһʼ y -4. A յ B ȷϱĶκ󣬻Ҫ B ȷϣȷϺΪ y+1Ϊ x+1 -5. B յ A ȷϺӽ +1. 首先 B 处于 LISTEN(监听)状态,等待客户的连接请求。 +2. A 向 B 发送连接请求报文段,SYN=1,ACK=0,选择一个初始的序号 x。 +3. B 收到连接请求报文段,如果同意建立连接,则向 A 发送连接确认报文段,SYN=1,ACK=1,确认号为 x+1,同时也选择一个初始的序号 y。 +4. A 收到 B 的连接确认报文段后,还要向 B 发出确认,确认号为 y+1,序号为 x+1。 +5. B 收到 A 的确认后,连接建立。 -## TCP Ĵλ +## TCP 的四次挥手 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/78f65456-666b-4044-b4ee-f7692dbbc0d3.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//78f65456-666b-4044-b4ee-f7692dbbc0d3.jpg) -źȷϺţΪźȷϺŵĹȽϼ򵥡Ҳ ACKΪ ACK ӽ֮Ϊ 1 +以下描述不讨论序号和确认号,因为序号和确认号的规则比较简单。并且不讨论 ACK,因为 ACK 在连接建立之后都为 1。 -1. A ͷűĶΣFIN=1 -2. B յ֮󷢳ȷϣʱ TCP ڰر״̬B A ݵ A B ݣ -3. B ҪҪʱͷĶΣFIN=1 -4. A յ󷢳ȷϣʱͷš +1. A 发送连接释放报文段,FIN=1; +2. B 收到之后发出确认,此时 TCP 属于半关闭状态,B 能向 A 发送数据但是 A 不能向 B 发送数据; +3. 当 B 要不再需要连接时,发送连接释放请求报文段,FIN=1; +4. A 收到后发出确认,此时连接释放。 **TIME_WAIT** -ͻ˽յ˵ FIN ĺ״̬ʱֱӽ CLOSED ״̬Ҫȴһʱʱõʱ䡣ôɣ +客户端接收到服务器端的 FIN 报文后进入此状态,此时并不是直接进入 CLOSED 状态,还需要等待一个时间计时器设置的时间。这么做有两个理由: -1. ȷһȷϱĶܹ B ûյ A ȷϱĶΣôͻ·ͷĶΣA ȴһʱΪ˴ķ -2. ܴڡʧЧĶΡΪ˷ֱֹĶγڱ֮⣬Ҫȴһʱ䡣 +1. 确保最后一个确认报文段能够到达。如果 B 没收到 A 发送来的确认报文段,那么就会重新发送连接释放请求报文段,A 等待一段时间就是为了处理这种情况的发生。 +2. 可能存在“已失效的连接请求报文段”,为了防止这种报文段出现在本次连接之外,需要等待一段时间。 -## TCP +## TCP 滑动窗口 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/223fc26e-2fd6-484c-bcb7-443cac134f15.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//223fc26e-2fd6-484c-bcb7-443cac134f15.jpg) -ǻһ֣ʱֽͷͽշһڣշͨ TCP ĶеĴֶθ߷ͷԼĴڴСͷֵϢԼĴڴС +窗口是缓存的一部分,用来暂时存放字节流。发送方和接收方各有一个窗口,接收方通过 TCP 报文段中的窗口字段告诉发送方自己的窗口大小,发送方根据这个值和其它信息设置自己的窗口大小。 -ʹڵֽڶͣմڵֽڶաʹ󲿵ֽѾͲյȷϣôͽʹһһ룬ֱ󲿵һֽڲѷͲȷϵ״̬մڵĻƣմֽѾȷϲһմڡ +发送窗口内的字节都允许被发送,接收窗口内的字节都允许被接收。如果发送窗口左部的字节已经发送并且收到了确认,那么就将发送窗口向右滑动一定距离,直到左部第一个字节不是已发送并且已确认的状态;接收窗口的滑动类似,接收窗口左部字节已经发送确认并交付主机,就向右滑动接收窗口。 -մֻԴһ򵽴ֽڽȷϣմѾյֽΪ {31, 32, 34, 35} {31, 32} 򵽴 {34, 35} Ͳǣֻֽ 32 ȷϡͷõһֽڵȷ֮󣬾ֽ֪֮ǰֽڶѾա +接收窗口只会对窗口内最后一个按序到达的字节进行确认,例如接收窗口已经收到的字节为 {31, 32, 34, 35},其中 {31, 32} 按序到达,而 {34, 35} 就不是,因此只对字节 32 进行确认。发送方得到一个字节的确认之后,就知道这个字节之前的所有字节都已经被接收。 -## TCP ɿ +## TCP 可靠传输 -TCP ʹóʱشʵֿɿ䣺һѾ͵ıĶڳʱʱûյȷϣôشĶΡ +TCP 使用超时重传来实现可靠传输:如果一个已经发送的报文段在超时时间内没有收到确认,那么就重传这个报文段。 -һĶδӷٵյȷʱΪʱ RTTȨƽʱ RTTs £ +一个报文段从发送再到接收到确认所经过的时间称为往返时间 RTT,加权平均往返时间 RTTs 计算如下: -![](http://latex.codecogs.com/gif.latex?\\\\RTTs=(1-a)*(RTTs)+a*RTT) +
-ʱʱ RTO ӦԴ RRTsTCP ʹõijʱʱ£ +超时时间 RTO 应该略大于 RRTs,TCP 使用的超时时间计算如下: -![](http://latex.codecogs.com/gif.latex?\\\\RTO=RTTs+4*RTT_d) +
- RTTd Ϊƫµ RRT RRTs йء +其中 RTTd 为偏差,它与新的 RRT 和 RRTs 有关。 -## TCP +## TCP 流量控制 -Ϊ˿Ʒͷʣ֤շüա +流量控制是为了控制发送方发送速率,保证接收方来得及接收。 -շ͵ȷϱеĴֶοƷͷڴСӶӰ췢ͷķʡ罫ֶΪ 0ͷܷݡ +接收方发送的确认报文中的窗口字段可以用来控制发送方窗口大小,从而影响发送方的发送速率。例如将窗口字段设置为 0,则发送方不能发送数据。 -## TCP ӵ +## TCP 拥塞控制 -ӵ齫ᶪʧʱͷشӶӵ̶ȸߡ˵ӵʱӦƷͷʡһƺ񣬵dz㲻ͬΪýշüܣӵΪ˽ӵ̶ȡ +如果网络出现拥塞,分组将会丢失,此时发送方会继续重传,从而导致网络拥塞程度更高。因此当出现拥塞时,应当控制发送方的速率。这一点和流量控制很像,但是出发点不同。流量控制是为了让接收方能来得及接受,而拥塞控制是为了降低整个网络的拥塞程度。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/a69af9bb-b5ad-4896-862d-697e5ee4feb1.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//a69af9bb-b5ad-4896-862d-697e5ee4feb1.png) -TCP Ҫͨ㷨ӵƣʼӵ⡢شָͷҪάһӵڣcwnd״̬עӵ뷢ͷڵӵֻһ״̬ʵʾͷܷͶݵǷͷڡ +TCP 主要通过四种算法来进行拥塞控制:慢开始、拥塞避免、快重传、快恢复。发送方需要维护有一个叫做拥塞窗口(cwnd)的状态变量。注意拥塞窗口与发送方窗口的区别,拥塞窗口只是一个状态变量,实际决定发送方能发送多少数据的是发送方窗口。 -Ϊ˱ۣ¼裺 +为了便于讨论,做如下假设: -1. շ㹻Ľջ棬˲ᷢƣ -2. Ȼ TCP Ĵڻֽڣ贰ڵĴСλΪĶΡ +1. 接收方有足够大的接收缓存,因此不会发生流量控制; +2. 虽然 TCP 的窗口基于字节,但是这里设窗口的大小单位为报文段。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/346244ff-98c1-4f12-9a87-d0832e8c04cf.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//346244ff-98c1-4f12-9a87-d0832e8c04cf.jpg) -### ʼӵ +### 慢开始与拥塞避免 -͵ִʼ cwnd=1ͷֻܷ 1 ĶΣյȷϺ󣬽 cwnd ӱ֮ͷܹ͵ıĶΪ248 ... +发送的最初执行慢开始,令 cwnd=1,发送方只能发送 1 个报文段;当收到确认后,将 cwnd 加倍,因此之后发送方能够发送的报文段为:2、4、8 ... -ע⵽ʼÿִζ cwnd ӱ cwnd ٶȷdz죬Ӷʹ÷ͷ͵ٶٶȹ죬ӵĿҲ͸ߡһʼ ssthresh cwnd >= ssthresh ʱӵ⣬ÿִֻ cwnd 1 +注意到慢开始每个轮次都将 cwnd 加倍,这样会让 cwnd 增长速度非常快,从而使得发送方发送的速度增长速度过快,网络拥塞的可能也就更高。设置一个慢开始门限 ssthresh,当 cwnd >= ssthresh 时,进入拥塞避免,每个轮次只将 cwnd 加 1。 -˳ʱ ssthresh = cwnd / 2Ȼִʼ +如果出现了超时,则令 ssthresh = cwnd / 2,然后重新执行慢开始。 -### شָ +### 快重传与快恢复 -ڽշҪÿνյĶζӦ÷ͶյĶεȷϣѾյ M1 M2ʱյ M4ӦͶ M2 ȷϡ +在接收方,要求每次接收到报文段都应该发送对已收到有序报文段的确认,例如已经接收到 M1 和 M2,此时收到 M4,应当发送对 M2 的确认。 -ڷͷյظȷϣôȷһĶζʧյ M2 M3 ʧʱִпششһĶΡ +在发送方,如果收到三个重复确认,那么可以确认下一个报文段丢失,例如收到三个 M2 ,则 M3 丢失。此时执行快重传,立即重传下一个报文段。 -£ֻǶʧĶΣӵִпָ ssthresh = cwnd / 2 cwnd = ssthreshע⵽ʱֱӽӵ⡣ +在这种情况下,只是丢失个别报文段,而不是网络拥塞,因此执行快恢复,令 ssthresh = cwnd / 2 ,cwnd = ssthresh,注意到此时直接进入拥塞避免。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/b18d679b-c8e2-4564-88ee-7600090e46da.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//b18d679b-c8e2-4564-88ee-7600090e46da.jpg) -# Ӧò* +# 第六章 应用层* -## ϵͳ DNS +## 域名系统 DNS -Ϊ IP ַ +把主机名解析为 IP 地址。 -Ƴɷֲʽϵͳ +被设计成分布式系统。 -### 1. νṹ +### 1. 层次结构 -һɶιɣϲ㵽²ֱΪԼļԻһ +一个域名由多个层次构成,从上层到下层分别为顶级域名、二级域名、三级域名以及四级域名。所有域名可以画成一颗域名树。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/c2117f61-1177-4768-bf33-cf4f950d911c.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//c2117f61-1177-4768-bf33-cf4f950d911c.png) -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/a4b162e5-db2a-4a27-b213-1fe481c5a06a.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//a4b162e5-db2a-4a27-b213-1fe481c5a06a.png) -ԷΪࣺ +域名服务器可以分为以下四类: -1. -2. -3. Ȩڵ -4. ҲΪĬøٻ档 +1. 根域名服务器:解析顶级域名; +2. 顶级域名服务器:解析二级域名; +3. 权限域名服务器:解析区内的域名; +4. 本地域名服务器:也称为默认域名服务器。可以在其中配置高速缓存。 -ĸͬһлֶͼ b abc.com лabc.com y.abc.com +区和域的概念不同,可以在一个域中划分多个区。图 b 在域 abc.com 中划分了两个区:abc.com 和 y.abc.com -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/fc0c6b2d-68c7-4de8-aaaa-97355a4f0472.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//fc0c6b2d-68c7-4de8-aaaa-97355a4f0472.jpg) -˾ҪȨ +因此就需要两个权限域名服务器: -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/8b335d94-c1ca-42e1-ad48-bb179d28a4f1.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//8b335d94-c1ca-42e1-ad48-bb179d28a4f1.jpg) -### 2. +### 2. 解析过程 -򱾵Ĺ̲õݹ飬ʹõݹ͵ַʽ +主机向本地域名服务器解析的过程采用递归,而本地域名服务器向其它域名服务器解析可以使用递归和迭代两种方式。 -ķʽ£һ֮󣬽صȻ󱾵ݹطʽ£ֱӷصģǼǰĽŻ᷵ء +迭代的方式下,本地域名服务器向一个域名服务器解析请求解析之后,结果返回到本地域名服务器,然后本地域名服务器继续向其它域名服务器请求解析;而递归地方式下,结果不是直接返回的,而是继续向前请求解析,最后的结果才会返回。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/6bc61bb8-3b1c-4dc8-ac25-cef925ace0eb.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//6bc61bb8-3b1c-4dc8-ac25-cef925ace0eb.jpg) -## ļЭ FTP +## 文件传输协议 FTP -FTP ʹ TCPҪе TCP ӣӺӡỰڼһֱִ򿪣ݴ֮͹رաʹö˿ں 21ʹö˿ں 20 +FTP 在运输层使用 TCP,并且需要建立两个并行的 TCP 连接:控制连接和数据连接。控制连接在整个会话期间一直保持打开,而数据连接在数据传送完毕之后就关闭。控制连接使用端口号 21,数据连接使用端口号 20。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/58633775-8584-4a01-ad3f-eee4d9a466e1.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//58633775-8584-4a01-ad3f-eee4d9a466e1.jpg) -## ԶնЭ TELNET +## 远程终端协议 TELNET -TELNET ڵ¼ԶϣԶϵҲ᷵ء +TELNET 用于登录到远程主机上,并且远程主机上的输出也会返回。 -TELNET ӦͲϵͳIJ죬粻ͬϵͳϵͳĻз塣 +TELNET 可以适应许多计算机和操作系统的差异,例如不同操作系统系统的换行符定义。 -## ά WWW +## 万维网 WWW [HTTP](https://github.com/CyC2018/InterviewNotes/blob/master/notes/HTTP.md) -## ʼЭ +## 电子邮件协议 -һʼϵͳɣûʼԼʼЭͶȡЭ顣зЭ鳣 SMTPȡЭ鳣 POP3 IMAP +一个电子邮件系统由三部分组成:用户代理、邮件服务器以及邮件发送协议和读取协议。其中发送协议常用 SMTP,读取协议常用 POP3 和 IMAP。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/de1e46d2-748f-4da3-a29e-7de7bc840366.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//de1e46d2-748f-4da3-a29e-7de7bc840366.jpg) ### POP3 -POP3 صֻҪûӷ϶ȡʼͰѸʼɾ +POP3 的特点是只要用户从服务器上读取了邮件,就把该邮件删除。 ### IMAP -IMAP Эпͻ˺ͷϵʼͬȥֶɾʼôϵʼҲᱻɾIMAP ûʱȥʷϵʼIMAP ЭҲִ֧ԶļС +IMAP 协议中客户端和服务器上的邮件保持同步,如果不去手动删除邮件,那么服务器上的邮件也不会被删除。IMAP 这种做法可以让用户随时随地去访问服务器上的邮件。IMAP 协议也支持创建自定义的文件夹。 ### SMTP -SMTP ֻܷ ASCII 룬ʼ MIME ԷͶļMIME ûиĶȡ SMTPʼĽṹ˷ ASCII ı +SMTP 只能发送 ASCII 码,而互联网邮件扩充 MIME 可以发送二进制文件。MIME 并没有改动或者取代 SMTP,而是增加邮件主题的结构,定义了非 ASCII 码的编码规则。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/ed5522bb-3a60-481c-8654-43e7195a48fe.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//ed5522bb-3a60-481c-8654-43e7195a48fe.png) -## ̬Э DHCP +## 动态主机配置协议 DHCP -DHCP ṩ˼弴õʽûҪȥֶ IP ַϢ +DHCP 提供了即插即用的连网方式,用户不再需要去手动配置 IP 地址等信息。 -DHCP õݲ IP ַ롢Ĭ· IP ַ IP ַ +DHCP 配置的内容不仅是 IP 地址,还包括子网掩码、默认路由器 IP 地址、域名服务器的 IP 地址。 -ʽ£Ҫ IP ַ㲥 DHCP ֱģĿĵַΪȫ 1 255.255.255.255:67ԴַΪȫ 0 0.0.0.0:68DHCP յֱ֮ IP ַȡһַ DHCP ṩĸ +工作方式如下:需要 IP 地址的主机广播发送 DHCP 发现报文(将目的地址置为全 1,即 255.255.255.255:67,源地址设置为全 0,即 0.0.0.0:68),DHCP 服务器收到发现报文之后,则在 IP 地址池中取一个地址,发送 DHCP 提供报文给该主机。 -## Ե㴫 P2P +## 点对点传输 P2P -ijļַжԵȼϳΪһļݵԪΪļ飬ĴСǹ̶ġһµĶԵȷijһʼûļ飬ܹԵȷ𽥵صһЩļ飬ͬʱҲΪĶԵȷϴһЩļ顣 +把某个文件分发的所有对等集合称为一个洪流。文件的数据单元称为文件块,它的大小是固定的。一个新的对等方加入某个洪流,一开始并没有文件块,但是能够从其它对等方中逐渐地下载到一些文件块,与此同时,它也为别的对等方上传一些文件块。 -ÿһʩΪ׷һԵȷʱ׷ǼǣԵ֪ͨ׷ںСκʱ˳ij +每个洪流都有一个基础设施,称为追踪器。当一个对等方加入洪流时,必须向追踪器登记,并周期性地通知追踪器它仍在洪流中。可以在任何时间加入和退出某个洪流。 -һµĶԵȷʱ׷ӺѡɸԵȷ¶ԵȷЩԵȷӣЩԵȷΪڶԵȷպͷļ鶼ڶԵȷнС +一个新的对等方加入洪流时,追踪器会随机从洪流中选择若干个对等方,并让新对等方与这些对等方建立连接,把这些对等方称为相邻对等方。接收和发送文件块都是在相邻对等方中进行。 -һԵȷҪܶļʱͨʹϡȵIJȡļ飬ҲһļڶԵȷи٣ôļ顣 +当一个对等方需要很多文件块时,通过使用最稀有优先的策略来取得文件块,也就是一个文件块在相邻对等方中副本最少,那么就优先请求这个文件块。 -ܶԵȷͬһԵȷļʱöԵȷѡ䷢ļĶԵȷ +当很多对等方向同一个对等方请求文件块时,该对等方优先选择以最高速率向其发送文件块的对等方。 -P2P һֲʽϵͳκʱжԵȷ˳ʹ÷ֲʽɢб DHTԲҺеԴ IP ַӳ䡣 +P2P 是一个分布式系统,任何时候都有对等方加入或者退出。使用分布式散列表 DHT,可以查找洪流中的资源和 IP 地址映射。 -## Web ҳ +## Web 页面请求过程 -1. DNS DNS ѯ +1. 向 DNS 服务器发送 DNS 查询报文来解析域名。 -2. ʼ HTTP ỰҪȽ TCP ӡ +2. 开始进行 HTTP 会话,需要先建立 TCP 连接。 -3. ĴУHTTP ıװ TCP СHTTP ʹö˿ں 80Ϊ 80 ˿ڡӽ֮󣬷һ˿ںŸضĿͻˣ֮ TCP 䶼Ķ˿ںš +3. 在运输层的传输过程中,HTTP 报文被封装进 TCP 中。HTTP 请求报文使用端口号 80,因为服务器监听的是 80 端口。连接建立之后,服务器会随机分配一个端口号给特定的客户端,之后的 TCP 传输都是用这个分配的端口号。 -4. ĴУTCP Ķλᱻװ IP УIP 龭·ѡ󵽴Ŀĵء +4. 在网络层的传输过程中,TCP 报文段会被封装进 IP 分组中,IP 分组经过路由选择,最后到达目的地。 -5. ·㣬IP ᱻװ MAC ֡УIP ַ MAC ַҪʹ ARP +5. 在链路层,IP 分组会被封装进 MAC 帧中,IP 地址解析成 MAC 地址需要使用 ARP。 -6. ͻ˷ HTTP ģȡҳ档 +6. 客户端发送 HTTP 请求报文,请求获取页面。 -7. HTTP Ӧģͻ˴Ӷȡҳ档 +7. 服务器发送 HTTP 相应报文,客户端从而获取该页面。 -8. õҳ֮󣬽Ⱦûչʾҳ档 +8. 浏览器得到页面内容之后,解析并渲染,向用户展示页面。 -## ö˿ +## 常用端口 -| ӦòЭ | ˿ں | Э | +| 应用层协议 | 端口号 | 运输层协议 | | -- | -- | -- | | DNS | 53 | UDP | -| FTP | 21 20 | TCP | +| FTP | 控制连接 21,数据连接 20 | TCP | | TELNET | 23 | TCP | | DHCP | 67 68 | UDP | | HTTP | 80 | TCP | @@ -841,7 +842,7 @@ P2P | POP3 | 110 | TCP | | IMAP | 143 | TCP | -# ο +# 参考资料 -- ߰ -- Զ· +- 计算机网络 第七版 +- 计算机网络 自顶向下方法 diff --git a/notes/设计模式.md b/notes/设计模式.md index 1876532c..eeba2a3e 100644 --- a/notes/设计模式.md +++ b/notes/设计模式.md @@ -1,72 +1,73 @@ -* [ 1 ģʽ](#-1--ģʽ) -* [ 2 ۲ģʽ](#-2--۲ģʽ) -* [ 3 װģʽ](#-3--װģʽ) -* [ 4 ģʽ](#-4--ģʽ) - * [4.1 򵥹](#41-򵥹) - * [4.2 ģʽ](#42-ģʽ) - * [4.3 󹤳ģʽ](#43-󹤳ģʽ) -* [ 5 ģʽ](#-5--ģʽ) -* [ 6 ģʽ](#-6--ģʽ) -* [ 7 ģʽģʽ](#-7--ģʽģʽ) - * [7.1 ģʽ](#71-ģʽ) - * [7.2 ģʽ](#72-ģʽ) -* [ 8 ģ巽ģʽ](#-8--ģ巽ģʽ) -* [ 9 ģʽ](#-9--ģʽ) - * [9.1 ģʽ](#91-ģʽ) - * [9.2 Java õĵ](#92-java-õĵ) - * [9.3 ģʽ](#93-ģʽ) -* [ 10 ״̬ģʽ](#-10--״̬ģʽ) -* [ 11 ģʽ](#-11--ģʽ) -* [ 12 ģʽ](#-12--ģʽ) +* [第 1 章 设计模式入门](#第-1-章-设计模式入门) +* [第 2 章 观察者模式](#第-2-章-观察者模式) +* [第 3 章 装饰模式](#第-3-章-装饰模式) +* [第 4 章 工厂模式](#第-4-章-工厂模式) + * [4.1 简单工厂](#41-简单工厂) + * [4.2 工厂方法模式](#42-工厂方法模式) + * [4.3 抽象工厂模式](#43-抽象工厂模式) +* [第 5 章 单件模式](#第-5-章-单件模式) +* [第 6 章 命令模式](#第-6-章-命令模式) +* [第 7 章 适配器模式与外观模式](#第-7-章-适配器模式与外观模式) + * [7.1 适配器模式](#71-适配器模式) + * [7.2 外观模式](#72-外观模式) +* [第 8 章 模板方法模式](#第-8-章-模板方法模式) +* [第 9 章 迭代器和组合模式](#第-9-章-迭代器和组合模式) + * [9.1 迭代器模式](#91-迭代器模式) + * [9.2 Java 内置的迭代器](#92-java-内置的迭代器) + * [9.3 组合模式](#93-组合模式) +* [第 10 章 状态模式](#第-10-章-状态模式) +* [第 11 章 代理模式](#第-11-章-代理模式) +* [第 12 章 复合模式](#第-12-章-复合模式) * [12.1 MVC](#121-mvc) -* [ 13 ģʽദ](#-13--ģʽദ) -* [ 14 ʣµģʽ](#-14--ʣµģʽ) +* [第 13 章 与设计模式相处](#第-13-章-与设计模式相处) +* [第 14 章 剩下的模式](#第-14-章-剩下的模式) -# 1 ģʽ -**1. ģʽ** +# 第 1 章 设计模式入门 -ģʽǴ룬ǽķѧϰеģʽ鸴á +**1. 设计模式概念** -ӵģʽʻ㣬ڹͨʱøٵĴʻۣҲҪ˽ײϸڡ +设计模式不是代码,而是解决问题的方案,学习现有的设计模式可以做到经验复用。 -**2. ** +拥有设计模式词汇,在沟通时就能用更少的词汇来讨论,并且不需要了解底层细节。 -ƲͬѼӵвͬĽͷзʽ +**2. 问题描述** -**3. ʵַ** +设计不同种类的鸭子拥有不同的叫声和飞行方式。 -ʹü̳еĽ£ַ޷ãѼӵͬķзʽظĴ롣 +**3. 简单实现方案** -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/144d28a0-1dc5-4aba-8961-ced5bc88428a.jpg) +使用继承的解决方案如下,这种方案代码无法复用,如果两个鸭子类拥有同样的飞行方式,就有两份重复的代码。 -**4. ԭ** +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//144d28a0-1dc5-4aba-8961-ced5bc88428a.jpg) -**װ仯**仯ѼӽкͷеΪʽ +**4. 设计原则** -**Խӿڱ̣ʵֱ** Ϊ࣬Ǿijࡣеķʵֲڸ࣬ڸࡣʱԶ̬ıָ͡ +**封装变化**在这里变化的是鸭子叫和飞行的行为方式。 -һԭ򣬽кͷеΪʵֲֶͬĽкͷе࣬ȥʵ־Ľкͷзʽ +**针对接口编程,而不是针对实现编程** 变量声明的类型为父类,而不是具体的某个子类。父类中的方法实现不在父类,而是在各个子类。程序在运行时可以动态改变变量所指向的子类类型。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/1c8ccf5c-7ecd-4b8a-b160-3f72a510ce26.png) +运用这一原则,将叫和飞行的行为抽象出来,实现多种不同的叫和飞行的子类,让子类去实现具体的叫和飞行方式。 -**ϣü̳** Ҳ has-a ϵͨϣʱ̬ıʵֻ֣Ҫͨı丸ָĸ༴ɡ̳оͲЩ̳ϵڴʱѾȷ +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//1c8ccf5c-7ecd-4b8a-b160-3f72a510ce26.png) -һԭ Duck FlyBehavior QuackBehavior ࣬performQuack() performFly() ίиȥַͨʽһ Duck ԸҪȥʵ FlyBehavior QuackBehavior 󣬲ҲԶ̬ؽиı䡣 +**多用组合,少用继承** 组合也就是 has-a 关系,通过组合,可以在运行时动态改变实现,只要通过改变父类对象具体指向哪个子类即可。而继承就不能做到这些,继承体系在创建类时就已经确定。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/29574e6f-295c-444e-83c7-b162e8a73a83.jpg) +运用这一原则,在 Duck 类中组合 FlyBehavior 和 QuackBehavior 类,performQuack() 和 performFly() 方法委托给这两个类去处理。通过这种方式,一个 Duck 子类可以根据需要去实例化 FlyBehavior 和 QuackBehavior 的子类对象,并且也可以动态地进行改变。 -**5. ͼ** +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//29574e6f-295c-444e-83c7-b162e8a73a83.jpg) -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/e13833c8-e215-462e-855c-1d362bb8d4a0.jpg) +**5. 整体设计图** -**6. ģʽ** +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//e13833c8-e215-462e-855c-1d362bb8d4a0.jpg) -**ģʽ** 㷨壬ֱװ֮Ի滻ģʽ㷨ı仯ʹ㷨Ŀͻ +**6. 模式定义** -**7. ʵִ** +**策略模式** :定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。 + +**7. 实现代码** ```java public abstract class Duck { @@ -162,42 +163,42 @@ public class MiniDuckSimulator { } } ``` -ִн +执行结果 ```html QuackBehavior.Quack FlyBehavior.FlyWithWings FlyBehavior.FlyNoWay ``` -# 2 ۲ģʽ +# 第 2 章 观察者模式 -**1. ģʽ** +**1. 模式定义** -˶֮һԶһı״̬ʱ߶֪ܵͨԶ¡⣨SubjectDZ۲Ķ󣬶ߣObserverΪ۲ߡ +定义了对象之间的一对多依赖,当一个对象改变状态时,它的所有依赖者都会受到通知并自动更新。主题(Subject)是被观察的对象,而其所有依赖者(Observer)成为观察者。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/26cb5e7e-6fa3-44ad-854e-fe24d1a5278c.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//26cb5e7e-6fa3-44ad-854e-fe24d1a5278c.jpg) -**2. ģʽͼ** +**2. 模式类图** -оעƳ۲ߣ֪ͨעߵĹܣͨάһŹ۲бʵЩġ +主题中具有注册和移除观察者,并通知所有注册者的功能,主题是通过维护一张观察者列表来实现这些操作的。 -۲ӵһãΪעᡢƳݶ⵱УͨӦܡ +观察者拥有一个主题对象的引用,因为注册、移除还有数据都在主题当中,必须通过操作主题才能完成相应功能。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/5c558190-fccd-4b5e-98ed-1896653fc97f.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//5c558190-fccd-4b5e-98ed-1896653fc97f.jpg) -**3. ** +**3. 问题描述** -ݲϢıʱݣжڽӡ +天气数据布告板会在天气信息发生改变时更新其内容,布告板有多个,并且在将来会继续增加。 -**4. ͼ** +**4. 解决方案类图** -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/760a5d63-d96d-4dd9-bf9a-c3d126b2f401.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//760a5d63-d96d-4dd9-bf9a-c3d126b2f401.jpg) -**5. ԭ** +**5. 设计原则** -**Ϊ֮ƶŬ** ֮ϣȻԽDz̫˴˵ϸڡϵ֮以̶ȺܵͣϵͳеԣܹӦԱ仯 +**为交互对象之间的松耦合设计而努力** 当两个对象之间松耦合,它们依然可以交互,但是不太清楚彼此的细节。由于松耦合的两个对象之间互相依赖程度很低,因此系统具有弹性,能够应对变化。 -**6. ʵִ** +**6. 实现代码** ```java public interface Subject { @@ -295,7 +296,7 @@ public class WeatherStation { } } ``` -ִн +执行结果 ```html CurrentConditionsDisplay.update:0.0 0.0 0.0 StatisticsDisplay.update:0.0 0.0 0.0 @@ -303,39 +304,39 @@ CurrentConditionsDisplay.update:1.0 1.0 1.0 StatisticsDisplay.update:1.0 1.0 1.0 ``` -# 3 װģʽ +# 第 3 章 装饰模式 -**1. ** +**1. 问题描述** -ƲͬϣÿϿԶ̬µIJϣţ̡һϵļ۸ +设计不同种类的饮料,并且每种饮料可以动态添加新的材料,比如可以添加牛奶。计算一种饮料的价格。 -**2. ģʽ** +**2. 模式定义** -̬ؽθӵϡչϣװṩ˱ȼ̳иеԵ +动态地将责任附加到对象上。在扩展功能上,装饰者提供了比继承更有弹性的替代方案。 -ͼ DarkRoast Mocha Mocha ֱ Whip Ƕ̳ͬ࣬ cost() cost() ʵֵڲ cost() ˣҪ DarkRoast MochaôֻҪ Mocha DarkRoastҪ Whip Whip Mocha cost() ֶܰļ۸񶼰ȥ +下图中 DarkRoast 对象被 Mocha 包裹,Mocha 对象又被 Whip 包裹,并且他们都继承自相同父类,都有 cost() 方法,但是外层对象的 cost() 方法实现调用了内层对象的 cost() 方法。因此,如果要在 DarkRoast 上添加 Mocha,那么只需要用 Mocha 包裹 DarkRoast,如果还需要 Whip ,就用 Whip 包裹 Mocha,最后调用 cost() 方法能把三种对象的价格都包含进去。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/41a4cb30-f393-4b3b-abe4-9941ccf8fa1f.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//41a4cb30-f393-4b3b-abe4-9941ccf8fa1f.jpg) -**3. ģʽͼ** +**3. 模式类图** -װߺ;̳ͣоķʵֲҪ󣬶װӵһͶװװ߻߾νװΣǰװڱװεĶ֮⣬Ӷ̬չװߵĹܡװߵķһԼģĹܣȻñװߵķʵ֣ӶҲ˱װߵĹܡԿӦװβεͲ㣬ΪֻоֱʵֶҪίиȥ +装饰者和具体组件都继承自组件类型,其中具体组件的方法实现不需要依赖于其它对象,而装饰者拥有一个组件类型对象,这样它可以装饰其它装饰者或者具体组件。所谓装饰,就是把这个装饰者套在被装饰的对象之外,从而动态扩展被装饰者的功能。装饰者的方法有一部分是自己的,这属于它的功能,然后调用被装饰者的方法实现,从而也保留了被装饰者的功能。可以看到,具体组件应当是装饰层次的最低层,因为只有具体组件有直接实现而不需要委托给其它对象去处理。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/3dc454fb-efd4-4eb8-afde-785b2182caeb.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//3dc454fb-efd4-4eb8-afde-785b2182caeb.jpg) -**4. ͼ** +**4. 问题解决方案的类图** -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/9c997ac5-c8a7-44fe-bf45-2c10eb773e53.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//9c997ac5-c8a7-44fe-bf45-2c10eb773e53.jpg) -**5. ԭ** +**5. 设计原则** -**Ӧöչţ޸Ĺرա** Ҳ¹ʱҪ޸Ĵ롣ڱиԭڣµIJϣҪȥ޸ϵĴ롣۲ģʽҲԭ򡣲඼ʵԭӦѸԭӦпܸıĵط +**类应该对扩展开放,对修改关闭。** 也就是添加新功能时不需要修改代码。在本章问题中该原则体现在,在饮料中添加新的材料,而不需要去修改饮料的代码。观察则模式也符合这个原则。不可能所有类都能实现这个原则,应当把该原则应用于设计中最有可能改变的地方。 -**6. Java I/O еװģʽ** +**6. Java I/O 中的装饰者模式** -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/2a40042a-03c8-4556-ad1f-72d89f8c555c.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//2a40042a-03c8-4556-ad1f-72d89f8c555c.jpg) -**7. ʵ** +**7. 代码实现** ```java public interface Beverage { @@ -400,32 +401,32 @@ public class StartbuzzCoffee { } ``` - +输出 ```html 3.0 ``` -# 4 ģʽ +# 第 4 章 工厂模式 -## 4.1 򵥹 +## 4.1 简单工厂 -**1. ** +**1. 问题描述** -вͬ Pizzaݲͬòͬʵһ Pizza +有不同的 Pizza,根据不同的情况用不同的子类实例化一个 Pizza 对象。 -**2. ** +**2. 定义** -򵥹ģʽһֱϰߡʵһĶʱʵҪݾʹĸࡣ£ʵIJŵУùӦĸʵѿͻ;ʵֽͻҪ֪ЩԼʵĸࡣΪͻжʹü򵥹ôеĿͻ඼Ҫ֪ϸڣһ෢ı䣬࣬ôеĿͻ඼Ҫı䡣 +简单工厂不是设计模式,更像是一种编程习惯。在实例化一个超类的对象时,可以用它的所有子类来进行实例化,要根据具体需求来决定使用哪个子类。在这种情况下,把实例化的操作放到工厂来中,让工厂类来决定应该用哪个子类来实例化。这样做把客户对象和具体子类的实现解耦,客户对象不再需要知道有哪些子类以及实例化哪个子类。因为客户类往往有多个,如果不使用简单工厂,那么所有的客户类都要知道所有子类的细节,一旦子类发生改变,例如增加子类,那么所有的客户类都要发生改变。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/c470eb9b-fb05-45c5-8bb7-1057dc3c16de.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//c470eb9b-fb05-45c5-8bb7-1057dc3c16de.jpg) -**3. ͼ** +**3. 解决方案类图** -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/dc3e704c-7c57-42b8-93ea-ddd068665964.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//dc3e704c-7c57-42b8-93ea-ddd068665964.jpg) -**4. ʵ** +**4. 代码实现** ```java public interface Pizza { @@ -471,35 +472,35 @@ public class PizzaStore { } ``` -н +运行结果 ```java CheesePizza ``` -## 4.2 ģʽ +## 4.2 工厂方法模式 -**1. ** +**1. 问题描述** -ÿ Pizza ȻͬǶԼķζҪ֡磬һͻŦԼ cheese Pizza ֥Ӹͬ Pizza Dzͬġ +每个地区的 Pizza 店虽然种类相同,但是都有自己的风味,需要单独区分。例如,一个客户点了纽约的 cheese 种类的 Pizza 和在芝加哥点的相同种类的 Pizza 是不同的。 -**2. ģʽ** +**2. 模式定义** -һĽӿڣҪʵһʵƳٵࡣ +定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。 -**3. ģʽͼ** +**3. 模式类图** -ڼ򵥹Уһ࣬ڹУͼУCreator һ anOperation() ҪõһƷ࣬Ʒÿ +在简单工厂中,创建对象的是另一个类,而在工厂方法中,是由子类来创建对象。下图中,Creator 有一个 anOperation() 方法,这个方法需要用到一组产品类,这组产品类由每个子类来创建。 -Ϊÿഴļ򵥹ÿһƷ࣬ǰѼ򵥹дĴŵԼĿΪ಻DzƷ࣬ȫô +可以为每个子类创建单独的简单工厂来创建每一个产品类,但是把简单工厂中创建对象的代码放到子类中来可以减少类的数目,因为子类不算是产品类,因此完全可以这么做。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/903093ec-acc8-4f9b-bf2c-b990b9a5390c.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//903093ec-acc8-4f9b-bf2c-b990b9a5390c.jpg) -**4. ͼ** +**4. 解决方案类图** -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/664f8901-5dc7-4644-a072-dad88cc5133a.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//664f8901-5dc7-4644-a072-dad88cc5133a.jpg) -**5. ʵ** +**5. 代码实现** ```java public interface Pizza { @@ -588,36 +589,36 @@ public class PizzaTestDrive { } ``` -н +运行结果 ```html NYStyleCheesePizza is making.. ChicagoStyleCheesePizza is making.. ``` -## 4.3 󹤳ģʽ +## 4.3 抽象工厂模式 -**1. ԭ** +**1. 设计原则** -**ԭ**Ҫ󣬲ҪࡣԽӿڱ̣ʵֱ̣ԭ˵ˣø߲ײңܸ߲ײ߶Ӧڳ磬ͼ PizzaStore ڸ߲ Pizza ij࣬ͿԲù Pizza ľʵϸڡ +**依赖倒置原则**:要依赖抽象,不要依赖具体类。听起来像是针对接口编程,不针对实现编程,但是这个原则说明了:不能让高层组件依赖底层组件,而且,不管高层或底层组件,两者都应该依赖于抽象。例如,下图中 PizzaStore 属于高层组件,它依赖的是 Pizza 的抽象类,这样就可以不用关心 Pizza 的具体实现细节。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/ddf72ca9-c0be-49d7-ab81-57a99a974c8e.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//ddf72ca9-c0be-49d7-ab81-57a99a974c8e.jpg) -**2. ģʽ** +**2. 模式定义** -ṩһӿڣڴػļ壬Ҫȷָࡣ +提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。 -**3. ģʽͼ** +**3. 模式类图** -󹤳ģʽǶ壬ҲǺܶһ󣬲ЩصģҲ˵һ𴴽ģʽֻڴһͳ󹤳ģʽкܴͬң󹤳ģʽҲõ˹ģʽһͼ󲿣AbstractFactory е CreateProductA CreateProductB ʵ֣ڴһϹģʽĶ塣ڴļһ Client ֣Client Ҫͨ AbstractFactory ͬʱкܴԣClient ҪЭ񡣴Ӹ߲󹤳ʹϣ Cilent AbstractFactory ģʽʹ˼̳С +抽象工厂模式创建的是对象家族,也就是很多对象而不是一个对象,并且这些对象是相关的,也就是说必须一起创建出来。而工厂模式只是用于创建一个对象,这和抽象工厂模式有很大不同。并且,抽象工厂模式也用到了工厂模式来创建单一对象,在类图左部,AbstractFactory 中的 CreateProductA 和 CreateProductB 方法都是让子类来实现,这两个方法单独来看就是在创建一个对象,这符合工厂模式的定义。至于创建对象的家族这一概念是在 Client 体现,Client 要通过 AbstractFactory 同时调用两个方法来创建出两个对象,在这里这两个对象就有很大的相关性,Client 需要这两个对象的协作才能完成任务。从高层次来看,抽象工厂使用了组合,即 Cilent 组合了 AbstractFactory ,而工厂模式使用了继承。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/d301774f-e0d2-41f3-95f4-bfe39859b52e.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//d301774f-e0d2-41f3-95f4-bfe39859b52e.jpg) -**4. ͼ** +**4. 解决方案类图** -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/8785dabd-1285-4bd0-b3aa-b05cc060a24a.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//8785dabd-1285-4bd0-b3aa-b05cc060a24a.jpg) -**5. ʵ** +**5. 代码实现** ```java public interface Dough { @@ -719,28 +720,28 @@ public class NYPizzaStoreTestDrive { } ``` -н +运行结果 ```html ThickCrustDough MarinaraSauce ``` -# 5 ģʽ +# 第 5 章 单件模式 -**1. ģʽ** +**1. 模式定义** -ȷһֻһʵṩһȫַʵ㡣 +确保一个类只有一个实例,并提供了一个全局访问点。 -**2. ģʽͼ** +**2. 模式类图** -ģʽ Java ʵһ˽йһ˽о̬Լһо̬ú˽бʹͨúȡĶָΨһ˽о̬ +单件模式的 Java 实现用一个私有构造器、一个私有静态变量以及一个公有静态函数,该函数返回私有变量,使得所有通过该函数获取的对象都指向这个唯一的私有静态变量。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/59aff6c1-8bc5-48e4-9e9c-082baeb2f274.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//59aff6c1-8bc5-48e4-9e9c-082baeb2f274.jpg) -**3. ʵ** +**3. 经典实现** -ʵУ˽о̬ӳٻʵĺôǣûõ࣬ôͲᴴ˽о̬ӶԼԴʵڶ̻߳DzȫģΪܹ߳ͬʱ if(uniqueInstance == null) ڵ飬ôͻʵ uniqueInstance ˽о̬ +以下实现中,私有静态变量被延迟化实例化,这样做的好处是,如果没有用到该类,那么就不会创建该私有静态变量,从而节约资源。这个实现在多线程环境下是不安全的,因为多个线程能够同时进入 if(uniqueInstance == null) 内的语句块,那么就会多次实例化 uniqueInstance 私有静态变量。 ```java public class Singleton { @@ -759,9 +760,9 @@ public class Singleton { } ``` -**4. ̲߳ȫĽһ** +**4. 线程不安全问题的解决方案一** -ֻҪ getUniqueInstance() ø÷һֻһ̷߳ʣӶ˶ uniqueInstance жʵ⡣һһֻһ߳̽룬ϻһ˷ѡ +只需要对 getUniqueInstance() 方法加锁,就能让该方法一次只能一个线程访问,从而避免了对 uniqueInstance 变量进行多次实例化的问题。但是这样有一个问题是一次只能一个线程进入,性能上会有一定的浪费。 ```java public static synchronized Singleton getUniqueInstance() { @@ -772,17 +773,17 @@ public class Singleton { } ``` -**5. ̲߳ȫĽ** +**5. 线程不安全问题的解决方案二** -ӳʵֱʵ +不用延迟实例化,采用直接实例化。 ```java private static Singleton uniqueInstance = new Singleton(); ``` -**6. ̲߳ȫĽ** +**6. 线程不安全问题的解决方案三** -ǵһֱӶ getUniqueInstance() мʵֻҪ uniqueInstance = new Singleton(); ɡʹж uniqueInstance ǷѾʵûʵҪ +考虑第一个解决方案,它是直接对 getUniqueInstance() 方法进行加锁,而实际上只需要对 uniqueInstance = new Singleton(); 这条语句加锁即可。使用两个条件语句来判断 uniqueInstance 是否已经实例化,如果没有实例化才需要加锁。 ```java public class Singleton { @@ -805,31 +806,31 @@ public class Singleton { } ``` -# 6 ģʽ +# 第 6 章 命令模式 -**1. ** +**1. 问题描述** -һңкܶఴťÿťԷһһҵӦзdzļҵ磬ҲӼҵ硣 +设计一个遥控器,它有很多按钮,每个按钮可以发起一个命令,让一个家电完成相应操作。有非常多的家电,并且也会增加家电。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/7b8f0d8e-a4fa-4c9d-b9a0-3e6a11cb3e33.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//7b8f0d8e-a4fa-4c9d-b9a0-3e6a11cb3e33.jpg) -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/c3ca36b2-8459-4cf1-98b0-cc95a0e94f20.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//c3ca36b2-8459-4cf1-98b0-cc95a0e94f20.jpg) -**2. ģʽ** +**2. 模式定义** -װɶԱʹòͬ +将命令封装成对象,以便使用不同的命令来参数化其它对象。 -**3. ģʽͼ** +**3. 模式类图** -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/1e09d75f-6268-4425-acf8-8ecd1b4a0ef3.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//1e09d75f-6268-4425-acf8-8ecd1b4a0ef3.jpg) -**4. ͼ** +**4. 解决方案类图** -Invoker ңҵ execute() Receiver ǵƣִߡConcreteCommand һ Receiver ִίи Receiver Ҳ LightOnCommand excute ίи Light Light ͨ on() ɲInvoker Client ΪĴ Invoker ɵģҪ Client Щ +Invoker 是遥控器,它可以设置命令,并且调用命令对象的 execute() 方法。Receiver 是电灯,是命令真正的执行者。ConcreteCommand 类组合了一个 Receiver 对象,命令的执行委托给 Receiver 对象来处理,也就是 LightOnCommand 命令的 excute 方法委托给 Light 对象来处理,Light 对象通过调用 on() 方法来完成操作。Invoker 不是 Client 对象,是因为命令的创建不是在 Invoker 中完成的,因此需要额外的 Client 对象来处理这些操作。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/5ef94f62-98ce-464d-a646-842d9c72c8b8.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//5ef94f62-98ce-464d-a646-842d9c72c8b8.jpg) -**5. ʵ** +**5. 代码实现** ```java public interface Command { @@ -864,7 +865,7 @@ public class LightOnCommand implements Command{ ``` ```java /** - * ң + * 遥控器类 */ public class SimpleRemoteControl { Command slot; @@ -895,39 +896,39 @@ public class RemoteLoader { } ``` - +输出 ```html Light is on! ``` -# 7 ģʽģʽ +# 第 7 章 适配器模式与外观模式 -## 7.1 ģʽ +## 7.1 适配器模式 -**1. ģʽ** +**1. 模式定义** -һĽӿڣתΪͻһӿڡԭݵԺ޼䡣 +将一个类的接口,转换为客户期望的另一个接口。适配器让原本不兼容的类可以合作无间。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/8e8ba824-7a9e-4934-a212-e6a41dcc1602.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//8e8ba824-7a9e-4934-a212-e6a41dcc1602.jpg) -**2. ģʽͼ** +**2. 模式类图** -ģʽʵ֣һǶʽһ෽ʽʽͨϵķࣨAdapterӵһĶAdapteeӶӦĴίиĶ෽ʽõؼ̳УAdapter Կ Target Adaptee ͣȰ Adaptee Ȼʵһ Adapter ٰ Target ͵ģ Client Ϳ԰󵱳 Target Ķ +有两种适配器模式的实现,一种是对象方式,一种是类方式。对象方式是通过组合的方法,让适配器类(Adapter)拥有一个待适配的对象(Adaptee),从而把相应的处理委托给待适配的对象。类方式用到多重继承,Adapter 可以看成 Target 和 Adaptee 类型,先把它当成 Adaptee 类型然后实例化一个 Adapter 对象,再把它当成 Target 类型的,这样 Client 就可以把这个对象当成 Target 的对象来处理。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/253bd869-ea48-4092-9aed-6906ccb2f3b0.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//253bd869-ea48-4092-9aed-6906ccb2f3b0.jpg) -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/a797959a-0ed5-475b-8d97-df157c672019.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//a797959a-0ed5-475b-8d97-df157c672019.jpg) -**3. ** +**3. 问题描述** -ѼӣDuck𼦣TurkeyDuck quack() Turkey ֻ gobble() ҲҪ Turkey Ҳ Duck quack() +让鸭子(Duck)适配火鸡(Turkey),Duck 有 quack() 方法,而 Turkey 只有 gobble() 方法。也就是要让 Turkey 也有 Duck 的 quack() 方法。 -**4. ͼ** +**4. 解决方案类图** -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/1a511c76-bb6b-40ab-b8aa-39eeb619d673.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//1a511c76-bb6b-40ab-b8aa-39eeb619d673.jpg) -**5. ʵ** +**5. 代码实现** ```java public interface Duck { @@ -984,69 +985,69 @@ public class DuckTestDrive { } ``` -н +运行结果 ```html gobble! fly! ``` -## 7.2 ģʽ +## 7.2 外观模式 -**1. ģʽ** +**1. 模式定义** -ṩһͳһĽӿڣϵͳеһȺӿڡ۶һ߲ӿڣϵͳʹá +提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用。 -**2. ģʽͼ** +**2. 模式类图** -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/78f2314e-2643-41df-8f3d-b7e28294094b.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//78f2314e-2643-41df-8f3d-b7e28294094b.jpg) -**3. ** +**3. 问题描述** -ͥӰԺڶҪйۿӰʱҪԺܶвҪЩʹüͥӰԺֻṩһ򻯵ĽӿڣṩһӰĽӿڶþڶ +家庭影院中有众多电器,当要进行观看电影时需要对很多电器进行操作。要求简化这些操作,使得家庭影院类只提供一个简化的接口,例如提供一个看电影的接口而不用具体操作众多电器。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/106f5585-b2e7-4718-be5d-3b322d1ef42a.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//106f5585-b2e7-4718-be5d-3b322d1ef42a.jpg) -**4. ͼ** +**4. 解决方案类图** -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/25387681-89f8-4365-a2fa-83b86449ee84.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//25387681-89f8-4365-a2fa-83b86449ee84.jpg) -**5. ԭ** +**5. 设计原则** -**֪ʶԭ**̸ֻҲӦʹÿͻҪĶӦ١ +**最少知识原则**:只和你的密友谈话。也就是应当使得客户对象所需要交互的对象应当尽可能少。 -# 8 ģ巽ģʽ +# 第 8 章 模板方法模式 -**1. ģʽ** +**1. 模式定义** -һжһ㷨ĹǼܣһЩӳٵС +在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。 -ģ巽ʹڲı㷨ṹ£¶㷨еijЩ衣 +模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。 -**2. ģʽͼ** +**2. 模式类图** -ģ巽 templateMethod() 㷨ĹǼܣȷ primitiveOperation1() primitiveOperation2() ִе˳򣬶 primitiveOperation1() primitiveOperation2() ȥʵ֡ +模板方法 templateMethod() 定义了算法的骨架,确定了 primitiveOperation1() 和 primitiveOperation2() 方法执行的顺序,而 primitiveOperation1() 和 primitiveOperation2() 让子类去具体实现。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/ed62f400-192c-4185-899b-187958201f0c.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//ed62f400-192c-4185-899b-187958201f0c.jpg) -**3. ** +**3. 问题描述** -忧Ⱥͳ趼Ƶ̣ijЩе㲻һ +冲咖啡和冲茶都有类似的流程,但是某些步骤会有点不一样。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/d8f873fc-00bc-41ee-a87c-c1b4c0172844.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//d8f873fc-00bc-41ee-a87c-c1b4c0172844.png) -**4. ͼ** +**4. 解决方案类图** -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/aa20c123-b6b5-432a-83d3-45dc39172192.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//aa20c123-b6b5-432a-83d3-45dc39172192.jpg) -**5. ԭ** +**5. 设计原则** -**ԭ**ã绰ǣǻã绰㡣һԭԷֹܣֹ߲ײײ߲ԭģ巽Ϊֻи࣬಻øࡣ +**好莱坞原则**:别调用(打电话给)我们,我们会调用(打电话给)你。这一原则可以防止依赖腐败,即防止高层组件依赖底层组件,底层组件又依赖高层组件。该原则在模板方法的体现为,只有父类会调用子类,子类不会调用父类。 -**6. ** +**6. 钩子** -ӣhockijЩڲͬʵппޣȶһʲôķӵģ巽 templteMethod() УҪ͸ĬʵֲԼʵ֡ +钩子(hock):某些步骤在不同实现中可有可无,可以先定义一个什么都不做的方法,把它加到模板方法 templteMethod() 中,如果子类需要它就覆盖默认实现并加上自己的实现。 -**7. ʵ** +**7. 代码实现** ```java public abstract class CaffeineBeverage { @@ -1109,7 +1110,7 @@ public class CaffeineBeverageTestDrive { } ``` -н +运行结果 ```html boilWater @@ -1123,21 +1124,21 @@ pourInCup Tea.addCondiments ``` -# 9 ģʽ +# 第 9 章 迭代器和组合模式 -## 9.1 ģʽ +## 9.1 迭代器模式 -**1. ģʽ** +**1. 模式定义** -ṩһ˳һۺ϶еĸԪصķֲ¶ڲıʾ +提供一种顺序访问一个聚合对象中的各个元素的方法,而又不暴露其内部的表示。 -**2. ģʽͼ** +**2. 模式类图** -ͻӵһۺ϶͵󣬵Ǿۺ϶ɵġֻҪƶIJͿþۺ϶ܹ˳ +客户类拥有一个聚合对象和迭代器对象,迭代器对象是聚合对象生成的。只需要迭代器定义好移动的操作,就可以让聚合对象能够顺序遍历。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/439deca7-fed0-4c89-87e5-7088d10f1fdb.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//439deca7-fed0-4c89-87e5-7088d10f1fdb.jpg) -**3. ʵ** +**3. 代码实现** ```java public class Aggregate { @@ -1195,7 +1196,7 @@ public class Client { } } ``` -н +运行结果 ```html 0 1 @@ -1209,13 +1210,13 @@ public class Client { 9 ``` -## 9.2 Java õĵ +## 9.2 Java 内置的迭代器 -**1. ʵֽӿ** +**1. 实现接口** -Java Ѿ Iterator ӿڣʹ Java ʵʱҪþۺ϶ʵ Iterable ӿڣýӿһ iterator() ᷵һ Iterator ʹ Java õĵʵ֣ͻʹ foreach ѭۺ϶еÿԪء +Java 中已经有了 Iterator 接口,在使用 Java 实现时,需要让聚合对象实现 Iterable 接口,该接口有一个 iterator() 方法会返回一个 Iterator 对象。使用 Java 内置的迭代器实现,客户对象可以使用 foreach 循环来遍历聚合对象中的每个元素。 -**2. ʵ** +**2. 代码实现** ```java import java.util.Iterator; @@ -1271,25 +1272,25 @@ public class Client { } ``` -## 9.3 ģʽ +## 9.3 组合模式 -**1. ԭ** +**1. 设计原则** -һӦֻһıԭ +一个类应该只有一个引起改变的原因。 -**2. ģʽ** +**2. 模式定义** -ϳνṹ֡ / ֡νṹ +允许将对象组合成树形结构来表现“整体 / 部分”层次结构。 -ÿͻһµķʽԼϡ +组合能让客户以一致的方式处理个别对象以及对象组合。 -**3. ģʽͼ** +**3. 模式类图** -ϣCompositeӵһComponent϶λνṹм䣬Լ󣬲ľ͡ +由于组合(Composite)类拥有一个组件(Component)对象,因此组合对象位于树形结构的中间,它还可以继续操作这个组件对象,并忽略组件对象的具体类型。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/f99c019e-7e91-4c2e-b94d-b031c402dcb5.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//f99c019e-7e91-4c2e-b94d-b031c402dcb5.jpg) -**4. ʵ** +**4. 代码实现** ```java public abstract class Component { @@ -1316,7 +1317,7 @@ public class Leaf extends Component { @Override public void addChild(Component component) { - throw new UnsupportedOperationException(); // ͸Իȡһְԭ , ͲÿҶӽڵ㻹Ͻڵ + throw new UnsupportedOperationException(); // 牺牲透明性换取单一职责原则 , 这样就不用考虑是叶子节点还是组合节点 } @Override @@ -1378,7 +1379,7 @@ public class Client { } } ``` -н +运行结果 ```html Composite:root @@ -1390,56 +1391,56 @@ Composite:root --left:3 ``` -# 10 ״̬ģʽ +# 第 10 章 状态模式 -**1. ģʽ** +**1. 模式定义** -ڲ״̬ıʱıΪ޸ࡣ +允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。 -״̬ģʽͼͲģʽһҶܹ̬ıΪ״̬ģʽͨ״̬״̬תıͻϵ״̬󣬶ģʽͨͻľıϵIJԶ磬״̬ģʽ£ͻί״̬һô״̬пܷ״̬תƣʹÿͻӵе״̬ı䡣״̬˿ͻ״̬ת״̬ͨıͻϵ״̬ʵֵġ +状态模式的类图和策略模式一样,并且都是能够动态改变对象的行为。但是状态模式是通过状态对象的状态转移来改变客户对象组合的状态对象,而策略模式是通过客户对象本身的决策来改变组合的策略对象。例如,状态模式下,客户对象委托状态对象进行一个处理操作,那么状态对象有可能发生状态转移,使得客户对象拥有的状态对象发生改变。状态对象组合了客户对象,状态转移是状态对象通过改变客户对象所组合的状态对象实现的。 -**2. ģʽͼ** +**2. 模式类图** -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/c28fd93a-0d55-4a19-810f-72652feee00d.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//c28fd93a-0d55-4a19-810f-72652feee00d.jpg) -**3. ** +**3. 问题描述** -ǹۻж״̬ÿ״̬ۻвͬΪ״̬ԷתƣʹۻΪҲı䡣 +糖果销售机有多种状态,每种状态下销售机有不同的行为,状态可以发生转移,使得销售机的行为也发生改变。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/f7d880c9-740a-4a16-ac6d-be502281b4b2.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//f7d880c9-740a-4a16-ac6d-be502281b4b2.jpg) -**4. ֱӽ** +**4. 直接解决方案** -ǹÿ棬жϵǰ״̬ݲͬ״̬вͬĴҷͬ״̬תơֽеʵϸڶŵͻ࣬״̬ʱҪȥ޸ĿͻĴ롣 +在糖果机的每个操作函数里面,判断当前的状态,根据不同的状态进行不同的处理,并且发生不同的状态转移。这种解决方案把所有的实现细节都放到客户类,这样在新增状态的时候就要去修改客户类的代码。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/62ebbb63-8fd7-4488-a866-76a9dc911662.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//62ebbb63-8fd7-4488-a866-76a9dc911662.png) -**5. ʹ״̬ģʽĽ** +**5. 使用状态模式的解决方案** -״̬תƱƵ״̬棬ͻÿֻҪίи״̬༴ɣҪ֪ǰʲô״̬Լ״̬ʱνתƵġ +状态的转移被移到状态类里面,客户类的每个操作只需要委托给状态类即可,而不需要知道当前是什么状态以及状态时如何进行转移的。 -**6. ʵ** +**6. 代码实现** ```java public interface State { /** - * Ͷ25 Ǯ + * 投入25 分钱 */ void insertQuarter(); /** - * ˻25 Ǯ + * 退回25 分钱 */ void ejectQuarter(); /** - * ת + * 转动曲柄 */ void turnCrank(); /** - * ǹ + * 发放糖果 */ void dispense(); } @@ -1667,7 +1668,7 @@ public class GumballMachineTestDrive { } } ``` -н +运行结果 ```html You insert a quarter You turned... @@ -1696,34 +1697,34 @@ You turned, but there are no gumballs No gumball dispensed ``` -# 11 ģʽ +# 第 11 章 代理模式 -# 12 ģʽ +# 第 12 章 复合模式 ## 12.1 MVC -**ͳ MVC** +**传统 MVC** -ͼʹģʽģʹ˹۲ģʽʹ˲ģʽ +视图使用组合模式,模型使用了观察者模式,控制器使用了策略模式。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/4f67611d-492f-4958-9fa0-4948010e345f.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//4f67611d-492f-4958-9fa0-4948010e345f.jpg) -**Web е MVC** +**Web 中的 MVC** -ģʽʹù۲ģʽ +模式不再使用观察者模式。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/1dd56e61-2970-4d27-97c2-6e81cee86978.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//1dd56e61-2970-4d27-97c2-6e81cee86978.jpg) -# 13 ģʽദ +# 第 13 章 与设计模式相处 -壺ij **龳** £ij **** ij **** +定义:在某 **情境** 下,针对某 **问题** 的某种 **解决方案**。 -ʹģʽܵ´뱻ȹ̻Ӧ򵥵ĽɹҪģʽĵطʹ +过度使用设计模式可能导致代码被过度工程化,应该总是用最简单的解决方案完成工作,并在真正需要模式的地方才使用它。 -ģʽõĽһ⡣ҪΪ˾治ҪʹЩ +反模式:不好的解决方案来解决一个问题。主要作用是为了警告不要使用这些解决方案。 -ģʽࣺ +模式分类: -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/524a237c-ffd7-426f-99c2-929a6bf4c847.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//524a237c-ffd7-426f-99c2-929a6bf4c847.jpg) -# 14 ʣµģʽ +# 第 14 章 剩下的模式 diff --git a/notes/重构.md b/notes/重构.md index a7d15340..a25e566b 100644 --- a/notes/重构.md +++ b/notes/重构.md @@ -1,300 +1,301 @@ -* [һ һ](#һ-һ) -* [ڶ عԭ](#ڶ-عԭ) -* [ Ļζ](#-Ļζ) - * [1. Duplicated Codeظ룩](#1-duplicated-codeظ) - * [2. Long Method](#2-long-method) - * [3. Large Classࣩ](#3-large-class) - * [4. Long Parameter ListIJУ](#4-long-parameter-listIJ) - * [5. Divergent Changeɢʽ仯](#5-divergent-changeɢʽ仯) - * [6. Shotgun Surgeryɢʽ޸ģ](#6-shotgun-surgeryɢʽ޸) - * [7. Feature Envyᣩ](#7-feature-envy) - * [8. Data Clumpsţ](#8-data-clumps) - * [9. Primitive Obsessionƫִ](#9-primitive-obsessionƫִ) - * [10. Switch Statementsswitch ](#10-switch-statementsswitch-) - * [11. Parallel Inheritance Hierarchiesƽм̳ϵ](#11-parallel-inheritance-hierarchiesƽм̳ϵ) - * [12. Lazy Classࣩ](#12-lazy-class) - * [13. Speculative Generality̸δԣ](#13-speculative-generality̸δ) - * [14. Temporary FieldԻʱֶΣ](#14-temporary-fieldԻʱֶ) - * [15. Message ChainsϵϢ](#15-message-chainsϵϢ) - * [16. Middle Manмˣ](#16-middle-manм) - * [17. Inappropriate Intimacyǹϵ](#17-inappropriate-intimacyǹϵ) - * [18. Alernative Classes with Different Interfacesࣩͬ](#18-alernative-classes-with-different-interfacesͬ) - * [19. Incomplete Library Class⣩](#19-incomplete-library-class) - * [20. Data Classɵࣩ](#20-data-classɵ) - * [21. Refused Bequestܾ](#21-refused-bequestܾ) - * [22. Commentsעͣ](#22-commentsע) -* [ ϵ](#-ϵ) -* [ عб](#-عб) -* [ ֯](#-֯) - * [1. Extract Method](#1-extract-method) - * [2. Inline Method](#2-inline-method) - * [3. Inline Tempʱ](#3-inline-tempʱ) - * [4. Replace Temp with QueryԲѯȡʱ](#4-replace-temp-with-queryԲѯȡʱ) - * [5. Introduce Explaining Variableͱ](#5-introduce-explaining-variableͱ) - * [6. Split Temporary Variableֽʱ](#6-split-temporary-variableֽʱ) - * [7. Remove Assigments to ParametersƳԲĸֵ](#7-remove-assigments-to-parametersƳԲĸֵ) - * [8. Replace Method with Method ObjectԺȡ](#8-replace-method-with-method-objectԺȡ) - * [9. Subsititute Algorithn滻㷨](#9-subsititute-algorithn滻㷨) -* [ ڶ֮](#-ڶ֮) - * [1. Move Methodƺ](#1-move-methodƺ) - * [2. Move FieldֶΣ](#2-move-fieldֶ) - * [3. Extract Classࣩ](#3-extract-class) - * [4. Inline Class](#4-inline-class) - * [5. Hide Delegateءίйϵ](#5-hide-delegateءίйϵ) - * [6. Remove Middle ManƳмˣ](#6-remove-middle-manƳм) - * [7. Introduce Foreign MethodӺ](#7-introduce-foreign-methodӺ) - * [8. Introduce Local Extension뱾չ](#8-introduce-local-extension뱾չ) -* [ڰ ֯](#ڰ-֯) - * [1. Self Encapsulate FieldԷװֶΣ](#1-self-encapsulate-fieldԷװֶ) - * [2. Replace Data Value with ObjectԶȡֵ](#2-replace-data-value-with-objectԶȡֵ) - * [3. Change Value to Referenceֵijö](#3-change-value-to-referenceֵijö) - * [4. Change Reference to valueöΪֵ](#4-change-reference-to-valueöΪֵ) - * [5. Replace Array with ObjectԶȡ飩](#5-replace-array-with-objectԶȡ) - * [6. Duplicate Observed Dataֵݡ](#6-duplicate-observed-dataֵݡ) - * [7. Change Unidirectional Association to BidirectionalΪ˫](#7-change-unidirectional-association-to-bidirectionalΪ˫) - * [8. Change Bidirectional Association to Unidirectional˫Ϊ](#8-change-bidirectional-association-to-unidirectional˫Ϊ) - * [9. Replace Magic Number with Symbolic Constant泣ȡħ](#9-replace-magic-number-with-symbolic-constant泣ȡħ) - * [10. Encapsulate FieldװֶΣ](#10-encapsulate-fieldװֶ) - * [11. Encapsulate Collectionװϣ](#11-encapsulate-collectionװ) - * [12. Replace Record with Data Classȡ¼](#12-replace-record-with-data-classȡ¼) - * [13. Replace Type Code with Classȡ룩](#13-replace-type-code-with-classȡ) - * [14. Replace Type Code with Subcalssesȡ룩](#14-replace-type-code-with-subcalssesȡ) - * [15. Replace Type Code with State/Strategy State/Strategy ȡ룩](#15-replace-type-code-with-statestrategy--statestrategy-ȡ) - * [16. Replace Subclass with Fieldsֶȡࣩ](#16-replace-subclass-with-fieldsֶȡ) -* [ھ ʽ](#ھ-ʽ) - * [1. Decompose Conditionalֽʽ](#1-decompose-conditionalֽʽ) - * [2. Consolidate Conditional Expressionϲʽ](#2-consolidate-conditional-expressionϲʽ) - * [3. Consolidate Duplicate Conditional Fragments ϲظƬΣ](#3-consolidate-duplicate-conditional-fragments-ϲظƬ) - * [4. Remove Control FlagƳƱǣ](#4-remove-control-flagƳƱ) - * [5. Replace Nested Conditional with Guard Clauses ȡǶʽ](#5-replace-nested-conditional-with-guard-clauses-ȡǶʽ) - * [6. Replace Conditional with Polymorphism Զ̬ȡʽ](#6-replace-conditional-with-polymorphism-Զ̬ȡʽ) - * [7. Introduce Null ObjectNull](#7-introduce-null-objectnull) - * [8. Introduce Assertionԣ](#8-introduce-assertion) -* [ʮ 򻯺](#ʮ-򻯺) - * [1. Rename Method](#1-rename-method) - * [2. Add ParameterӲ](#2-add-parameterӲ) - * [3. Remove ParameterƳ](#3-remove-parameterƳ) - * [4. Separate Query from Modifierѯ޸ĺ룩](#4-separate-query-from-modifierѯ޸ĺ) - * [5. Parameterize MethodЯ](#5-parameterize-methodЯ) - * [6. Replace Parameter with Explicit Methodsȷȡ](#6-replace-parameter-with-explicit-methodsȷȡ) - * [7. Preserve Whole Objectֶ](#7-preserve-whole-objectֶ) - * [8. Replace Parameter with MethodsԺȡ](#8-replace-parameter-with-methodsԺȡ) - * [9. Introduce Parameter Object](#9-introduce-parameter-object) - * [10. Remove Setting MethodƳֵ](#10-remove-setting-methodƳֵ) - * [11. Hide Methodغ](#11-hide-methodغ) - * [12. Replace Constructor with Factory Method Թȡ캯](#12-replace-constructor-with-factory-method-Թȡ캯) - * [13. Encapsulate Downcastװתͣ](#13-encapsulate-downcastװת) - * [14. Replace Error Code with Exception 쳣ȡ룩](#14-replace-error-code-with-exception-쳣ȡ) - * [15. Replace Exception with TestԲȡ쳣](#15-replace-exception-with-testԲȡ쳣) -* [ʮһ ϵ](#ʮһ-ϵ) - * [1. Pull Up Fieldֶƣ](#1-pull-up-fieldֶ) - * [2. Pull Up Methodƣ](#2-pull-up-method) - * [3. Pull Up Constructor Body캯ƣ](#3-pull-up-constructor-body캯) - * [4. Push Down Methodƣ](#4-push-down-method) - * [5. Push Down Fieldֶƣ](#5-push-down-fieldֶ) - * [6. Extract Subclassࣩ](#6-extract-subclass) - * [7. Extract Superclassࣩ](#7-extract-superclass) - * [8. Extract Interfaceӿڣ](#8-extract-interfaceӿ) - * [9. Collapse Hierarchy۵̳ϵ](#9-collapse-hierarchy۵̳ϵ) - * [10. Form Template Methodģ庯](#10-form-template-methodģ庯) - * [11. Replace Inheritance with Delegation ίȡ̳У](#11-replace-inheritance-with-delegation-ίȡ̳) - * [12. Replace Delegation with Inheritance Լ̳ȡίУ](#12-replace-delegation-with-inheritance-Լ̳ȡί) +* [第一章 第一个案例](#第一章-第一个案例) +* [第二章 重构原则](#第二章-重构原则) +* [第三章 代码的坏味道](#第三章-代码的坏味道) + * [1. Duplicated Code(重复代码)](#1-duplicated-code重复代码) + * [2. Long Method(过长函数)](#2-long-method过长函数) + * [3. Large Class(过大的类)](#3-large-class过大的类) + * [4. Long Parameter List(过长的参数列)](#4-long-parameter-list过长的参数列) + * [5. Divergent Change(发散式变化)](#5-divergent-change发散式变化) + * [6. Shotgun Surgery(散弹式修改)](#6-shotgun-surgery散弹式修改) + * [7. Feature Envy(依恋情结)](#7-feature-envy依恋情结) + * [8. Data Clumps(数据泥团)](#8-data-clumps数据泥团) + * [9. Primitive Obsession(基本类型偏执)](#9-primitive-obsession基本类型偏执) + * [10. Switch Statements(switch 惊悚现身)](#10-switch-statementsswitch-惊悚现身) + * [11. Parallel Inheritance Hierarchies(平行继承体系)](#11-parallel-inheritance-hierarchies平行继承体系) + * [12. Lazy Class(冗余类)](#12-lazy-class冗余类) + * [13. Speculative Generality(夸夸其谈未来性)](#13-speculative-generality夸夸其谈未来性) + * [14. Temporary Field(令人迷惑的暂时字段)](#14-temporary-field令人迷惑的暂时字段) + * [15. Message Chains(过度耦合的消息链)](#15-message-chains过度耦合的消息链) + * [16. Middle Man(中间人)](#16-middle-man中间人) + * [17. Inappropriate Intimacy(狎昵关系)](#17-inappropriate-intimacy狎昵关系) + * [18. Alernative Classes with Different Interfaces(异曲同工的类)](#18-alernative-classes-with-different-interfaces异曲同工的类) + * [19. Incomplete Library Class(不完美的类库)](#19-incomplete-library-class不完美的类库) + * [20. Data Class(幼稚的数据类)](#20-data-class幼稚的数据类) + * [21. Refused Bequest(被拒绝的馈赠)](#21-refused-bequest被拒绝的馈赠) + * [22. Comments(过多的注释)](#22-comments过多的注释) +* [第四章 构筑测试体系](#第四章-构筑测试体系) +* [第五章 重构列表](#第五章-重构列表) +* [第六章 重新组织函数](#第六章-重新组织函数) + * [1. Extract Method(提炼函数)](#1-extract-method提炼函数) + * [2. Inline Method(内联函数)](#2-inline-method内联函数) + * [3. Inline Temp(内联临时变量)](#3-inline-temp内联临时变量) + * [4. Replace Temp with Query(以查询取代临时变量)](#4-replace-temp-with-query以查询取代临时变量) + * [5. Introduce Explaining Variable(引起解释变量)](#5-introduce-explaining-variable引起解释变量) + * [6. Split Temporary Variable(分解临时变量)](#6-split-temporary-variable分解临时变量) + * [7. Remove Assigments to Parameters(移除对参数的赋值)](#7-remove-assigments-to-parameters移除对参数的赋值) + * [8. Replace Method with Method Object(以函数对象取代函数)](#8-replace-method-with-method-object以函数对象取代函数) + * [9. Subsititute Algorithn(替换算法)](#9-subsititute-algorithn替换算法) +* [第七章 在对象之间搬移特性](#第七章-在对象之间搬移特性) + * [1. Move Method(搬移函数)](#1-move-method搬移函数) + * [2. Move Field(搬移字段)](#2-move-field搬移字段) + * [3. Extract Class(提炼类)](#3-extract-class提炼类) + * [4. Inline Class(将类内联化)](#4-inline-class将类内联化) + * [5. Hide Delegate(隐藏“委托关系”)](#5-hide-delegate隐藏“委托关系”) + * [6. Remove Middle Man(移除中间人)](#6-remove-middle-man移除中间人) + * [7. Introduce Foreign Method(引入外加函数)](#7-introduce-foreign-method引入外加函数) + * [8. Introduce Local Extension(引入本地扩展)](#8-introduce-local-extension引入本地扩展) +* [第八章 重新组织数据](#第八章-重新组织数据) + * [1. Self Encapsulate Field(自封装字段)](#1-self-encapsulate-field自封装字段) + * [2. Replace Data Value with Object(以对象取代数据值)](#2-replace-data-value-with-object以对象取代数据值) + * [3. Change Value to Reference(将值对象改成引用对象)](#3-change-value-to-reference将值对象改成引用对象) + * [4. Change Reference to value(将引用对象改为值对象)](#4-change-reference-to-value将引用对象改为值对象) + * [5. Replace Array with Object(以对象取代数组)](#5-replace-array-with-object以对象取代数组) + * [6. Duplicate Observed Data(赋值“被监视数据”)](#6-duplicate-observed-data赋值“被监视数据”) + * [7. Change Unidirectional Association to Bidirectional(将单向关联改为双向关联)](#7-change-unidirectional-association-to-bidirectional将单向关联改为双向关联) + * [8. Change Bidirectional Association to Unidirectional(将双向关联改为单向关联)](#8-change-bidirectional-association-to-unidirectional将双向关联改为单向关联) + * [9. Replace Magic Number with Symbolic Constant(以字面常量取代魔法数)](#9-replace-magic-number-with-symbolic-constant以字面常量取代魔法数) + * [10. Encapsulate Field(封装字段)](#10-encapsulate-field封装字段) + * [11. Encapsulate Collection(封装集合)](#11-encapsulate-collection封装集合) + * [12. Replace Record with Data Class(以数据类取代记录)](#12-replace-record-with-data-class以数据类取代记录) + * [13. Replace Type Code with Class(以类取代类型码)](#13-replace-type-code-with-class以类取代类型码) + * [14. Replace Type Code with Subcalsses(以子类取代类型码)](#14-replace-type-code-with-subcalsses以子类取代类型码) + * [15. Replace Type Code with State/Strategy (以 State/Strategy 取代类型码)](#15-replace-type-code-with-statestrategy-以-statestrategy-取代类型码) + * [16. Replace Subclass with Fields(以字段取代子类)](#16-replace-subclass-with-fields以字段取代子类) +* [第九章 简化条件表达式](#第九章-简化条件表达式) + * [1. Decompose Conditional(分解条件表达式)](#1-decompose-conditional分解条件表达式) + * [2. Consolidate Conditional Expression(合并条件表达式)](#2-consolidate-conditional-expression合并条件表达式) + * [3. Consolidate Duplicate Conditional Fragments (合并重复的条件片段)](#3-consolidate-duplicate-conditional-fragments-合并重复的条件片段) + * [4. Remove Control Flag(移除控制标记)](#4-remove-control-flag移除控制标记) + * [5. Replace Nested Conditional with Guard Clauses (以卫语句取代嵌套条件表达式)](#5-replace-nested-conditional-with-guard-clauses-以卫语句取代嵌套条件表达式) + * [6. Replace Conditional with Polymorphism (以多态取代条件表达式)](#6-replace-conditional-with-polymorphism-以多态取代条件表达式) + * [7. Introduce Null Object(引入Null对象)](#7-introduce-null-object引入null对象) + * [8. Introduce Assertion(引入断言)](#8-introduce-assertion引入断言) +* [第十章 简化函数调用](#第十章-简化函数调用) + * [1. Rename Method(函数改名)](#1-rename-method函数改名) + * [2. Add Parameter(添加参数)](#2-add-parameter添加参数) + * [3. Remove Parameter(移除参数)](#3-remove-parameter移除参数) + * [4. Separate Query from Modifier(将查询函数和修改函数分离)](#4-separate-query-from-modifier将查询函数和修改函数分离) + * [5. Parameterize Method(令函数携带参数)](#5-parameterize-method令函数携带参数) + * [6. Replace Parameter with Explicit Methods(以明确函数取代参数)](#6-replace-parameter-with-explicit-methods以明确函数取代参数) + * [7. Preserve Whole Object(保持对象完整)](#7-preserve-whole-object保持对象完整) + * [8. Replace Parameter with Methods(以函数取代参数)](#8-replace-parameter-with-methods以函数取代参数) + * [9. Introduce Parameter Object(引入参数对象)](#9-introduce-parameter-object引入参数对象) + * [10. Remove Setting Method(移除设值函数)](#10-remove-setting-method移除设值函数) + * [11. Hide Method(隐藏函数)](#11-hide-method隐藏函数) + * [12. Replace Constructor with Factory Method (以工厂函数取代构造函数)](#12-replace-constructor-with-factory-method-以工厂函数取代构造函数) + * [13. Encapsulate Downcast(封装向下转型)](#13-encapsulate-downcast封装向下转型) + * [14. Replace Error Code with Exception (以异常取代错误码)](#14-replace-error-code-with-exception-以异常取代错误码) + * [15. Replace Exception with Test(以测试取代异常)](#15-replace-exception-with-test以测试取代异常) +* [第十一章 处理概括关系](#第十一章-处理概括关系) + * [1. Pull Up Field(字段上移)](#1-pull-up-field字段上移) + * [2. Pull Up Method(函数上移)](#2-pull-up-method函数上移) + * [3. Pull Up Constructor Body(构造函数本体上移)](#3-pull-up-constructor-body构造函数本体上移) + * [4. Push Down Method(函数下移)](#4-push-down-method函数下移) + * [5. Push Down Field(字段下移)](#5-push-down-field字段下移) + * [6. Extract Subclass(提炼子类)](#6-extract-subclass提炼子类) + * [7. Extract Superclass(提炼超类)](#7-extract-superclass提炼超类) + * [8. Extract Interface(提炼接口)](#8-extract-interface提炼接口) + * [9. Collapse Hierarchy(折叠继承体系)](#9-collapse-hierarchy折叠继承体系) + * [10. Form Template Method(塑造模板函数)](#10-form-template-method塑造模板函数) + * [11. Replace Inheritance with Delegation (以委托取代继承)](#11-replace-inheritance-with-delegation-以委托取代继承) + * [12. Replace Delegation with Inheritance (以继承取代委托)](#12-replace-delegation-with-inheritance-以继承取代委托) -# һ һ -㷢ԼҪΪһԣṹʹ޷ܷشĿģǾع +# 第一章 第一个案例 -عǰҪȹÿɿIJԻȷȫع +如果你发现自己需要为程序添加一个特性,而代码结构使你无法很方便地达成目的,那就先重构这个程序。 -ع΢СIJ޸ij´󣬺ױԷ +在重构前,需要先构建好可靠的测试环境,确保安全地重构。 -**** +重构是以微小的步伐修改程序,如果犯下错误,很容易便可以发现它。 -ӰƬӦó򣬰ࣺMovieRental CustomerRental ޵ Movie Լ +**案例分析** -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/a758c8b2-0ac7-438f-90c2-3923ffad6328.png) +影片出租店应用程序,包括三个类:Movie、Rental 和 Customer,Rental 包含租赁的 Movie 以及天数。 -ʼʵǰеļƷѴ붼 Customer Уڱ仯ʱҪⲿִиġпܷı仯УһļƷѷʽı䣻µĵӰ𡣿ǵƷѴܴڶദһıʱҪмƷѴ޸ġ +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//a758c8b2-0ac7-438f-90c2-3923ffad6328.png) -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/9e5e3cc6-3107-4051-b584-8ff077638fe6.png) +最开始的实现是把所有的计费代码都放在 Customer 类中,在变化发生时,需要对这部分代码进行更改。本案例中可能发生的变化有:一种类别的计费方式发生改变;添加新的电影类别。考虑到计费代码可能存在于多处,一旦发生改变时,就需要对所有计费代码进行修改。 -Ǽ̳ Movie Ķ̬һ Movie ᶯ̬ı䣬ַС +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//9e5e3cc6-3107-4051-b584-8ff077638fe6.png) -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/2a502516-5d34-4eef-8f39-916298a60035.png) +以下是继承 Movie 的多态方案。但是由于一部 Movie 的类别会动态改变,因此这种方案不可行。 - Price ӦϢͨϵķʽ Movie м Price ÿļƷѷʽװڲͬ Price У Movie ҲԶ̬ıַʽԺܺõӦᵽı仯 +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//2a502516-5d34-4eef-8f39-916298a60035.png) -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/c02a83b8-a6b9-4d00-a509-6f0516beaf5e.png) +引入 Price 来反应类别信息,通过组合的方式在 Movie 中加入 Price 对象,这样每种类别的计费方式都封装在不同的 Price 子类中,并且 Movie 对象也可以动态改变类别。这种方式可以很好地适应上述提到的变化。 -عʱͼͼ +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//c02a83b8-a6b9-4d00-a509-6f0516beaf5e.png) -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/95f4559c-3d2a-4176-b365-4fbc46c76cf1.png) +重构后的时序图和类图: -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/293b9326-02fc-4ad8-8c79-b4a7b5ba60d3.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//95f4559c-3d2a-4176-b365-4fbc46c76cf1.png) -# ڶ عԭ +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//293b9326-02fc-4ad8-8c79-b4a7b5ba60d3.png) -عǶڲṹһֵĿڲıɹ۲Ϊǰ£ԣ޸ijɱ +# 第二章 重构原则 -عĺôĽƣʹ⣻ҵ bug߱ٶȡ +重构是对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。 -η򣺵һijʱֻȥڶʱȥƵ£Ӧع +重构的好处:改进软件设计;使软件更容易理解;帮助找到 bug;提高编程速度。 -ӲعѧеĺܶͨһӲӲ¼ֵ߼ֿͼʵ֣仯װ߼عΪʵλòӲԼڲҪʱƳӲ㡣 +三次法则:第一次做某件事时只管去做;第二次做类似事情时可以去做;第三次再做类似的事,就应该重构。 -޸ĽӿڣԱɽӿڣþɽӿȥ½ӿڣʹ Java ṩ @deprecation ɽӿڱΪáбҪҪӿڣҲҪ緢ӿڡ +间接层与重构:计算机科学中的很多问题可以通过增加一个间接层来解决,间接层具有以下价值:允许逻辑共享;分开解释意图和实现;隔离变化;封装条件逻辑。重构可以理解为在适当的位置插入间接层以及在不需要时移除间接层。 -дڻʱӦдعһеİ취ǣװһȻԸдعľ +修改接口:可以保留旧接口,让旧接口去调用新接口,并且使用 Java 提供的 @deprecation 将旧接口标记为弃用。除非真有必要,不要发布接口,并且不要过早发布接口。 -޷ԤƣΪкܶ仯ʼܶǽȥعԼƣعһ򵥵Ͻ޸ĸģ仯ʱһķʽȥӦԱ仯õơ +当现有代码过于混乱时,应当重写而不是重构。一个折中的办法是,将代码封装成一个个组件,然后对各个组件做重写或者重构的决定。 -Ϊ⣬عܻᵼܼ͡ڱдʱöܹעֻŻ׶ٿ⡣ӦֻעؼܣΪֻһСֵĴǹؼ롣 +软件开发无法预先设计,因为开发过程有很多变化发生,在最开始不可能都把所有情况考虑进去。重构可以简化设计,重构在一个简单的设计上进行修修改改,当变化发生时,以一种灵活的方式去应对变化,进而带来更好的设计。 -# Ļζ +为了软代码更容易理解,重构可能会导致性能减低。在编写代码时,不用对性能过多关注,只有在最后性能优化阶段再考虑性能问题。应当只关注关键代码的性能,因为只有一小部分的代码是关键代码。 -## 1. Duplicated Codeظ룩 +# 第三章 代码的坏味道 -ͬһͬʽ Extract Method ȡظ룻 +## 1. Duplicated Code(重复代码) -Ϊֵܵຬͬıʽʹ Extract MethodȻȡĺ Pull Up Method 볬ࡣ +同一个类的两个函数有相同表达式,则用 Extract Method 提取出重复代码; -ֻDzͬ Extract Method ƲֺͲ첿֣Ȼʹ Form Template Method ģ巽ģʽ +两个互为兄弟的子类含有相同的表达式,先使用 Extract Method,然后把提取出来的函数 Pull Up Method 推入超类。 -صظ룬ʹ Extract Class ظȡһС +如果只是部分相同,用 Extract Method 分离出相似部分和差异部分,然后使用 Form Template Method 这种模板方法设计模式。 -## 2. Long Method +如果两个毫不相关的类出现重复代码,则使用 Extract Class 方法将重复代码提取到一个独立类中。 -Ӳļֵѡ +## 2. Long Method(过长函数) -ֽ⺯ԭ򣺵Ҫע˵һδʱҪⲿִдһĺС +间接层的价值:解释能力、共享能力、选择能力; -Extract Method Ѻܶʱ Replace Temp with Query ʱIntroduce Parameter Object Preserve Whole Object ԽIJбøࡣ +分解函数的原则:当需要用注释来说明一段代码时,就需要把这部分代码写入一个独立的函数中。 -ѭҲҪȡµĺС +Extract Method 会把很多参数和临时变量都当做参数,可以用 Replace Temp with Query 消除临时变量,Introduce Parameter Object 和 Preserve Whole Object 可以将过长的参数列变得更简洁。 -## 3. Large Classࣩ +条件和循环往往也需要提取到新的函数中。 -˹飬Ҫʹ Extract Class Extract Subclass +## 3. Large Class(过大的类) -ȷͻʹǣȻ Extract Interface Ϊÿһʹ÷ʽȡһӿڡ +过大的类做了过多事情,需要使用 Extract Class 或 Extract Subclass。 -## 4. Long Parameter ListIJУ +先确定客户端如何使用它们,然后运用 Extract Interface 为每一种使用方式提取出一个接口。 -## 5. Divergent Changeɢʽ仯 +## 4. Long Parameter List(过长的参数列) -һֱܵ仯Ӱ죻 +## 5. Divergent Change(发散式变化) -ijԭı仯ʹ Extract Class һС +一个类受到多种变化的影响; -## 6. Shotgun Surgeryɢʽ޸ģ +针对某种原因的变化,使用 Extract Class 将它提炼到一个类中。 -һ仯޸ģ +## 6. Shotgun Surgery(散弹式修改) -ʹ Move Method Move Field Ҫ޸ĵشŵͬһС +一个变化引起多个类修改; -## 7. Feature Envyᣩ +使用 Move Method 和 Move Field 把所有需要修改地代码放到同一个类中。 -һijȤڶԼȤͨǹݡ +## 7. Feature Envy(依恋情结) -ʹ Move Method ƵȥĵطԶ඼ Feature Envy Extract Method ȡ +一个函数对某个类的兴趣高于对自己所处类的兴趣,通常是过多访问其它类的数据。 -## 8. Data Clumpsţ +使用 Move Method 将它移到该去的地方,如果对多个类都有 Feature Envy,先用 Extract Method 提取出多个函数。 -Щݾһֶ֣ͬΡຯͬIJʹ Extract Class Ƿһ +## 8. Data Clumps(数据泥团) -## 9. Primitive Obsessionƫִ +有些数据经常一起出现,比如两个类具有相同的字段、许多函数有相同的参数。使用 Extract Class 将它们放在一起。 -ʹʹû͸ãʹ Replace Data Value with Object ֵ滻Ϊ +## 9. Primitive Obsession(基本类型偏执) -## 10. Switch Statementsswitch +使用类往往比使用基本类型更好,使用 Replace Data Value with Object 将数据值替换为对象。 -## 11. Parallel Inheritance Hierarchiesƽм̳ϵ +## 10. Switch Statements(switch 惊悚现身) -ÿΪijһ࣬ҲΪһӦһࡣ +## 11. Parallel Inheritance Hierarchies(平行继承体系) -ֽһЩظԣظԵһԣһ̳ϵʵһ̳ϵʵ +每当为某个类增加一个子类,必须也为另一个类相应增加一个子类。 -## 12. Lazy Classࣩ +这种结果会带来一些重复性,消除重复性的一般策略:让一个继承体系的实例引用另一个继承体系的实例。 -һû㹻ĹӦʧ +## 12. Lazy Class(冗余类) -## 13. Speculative Generality̸δԣ +如果一个类没有做足够多的工作,就应该消失。 -Щδܷı仯ϵͳάԤδܷĸıܿܺʼ෴ˣDZҪͲҪô +## 13. Speculative Generality(夸夸其谈未来性) -## 14. Temporary FieldԻʱֶΣ +有些内容是用来处理未来可能发生的变化,但是往往会造成系统难以理解和维护,并且预测未来可能发生的改变很可能和最开始的设想相反。因此,如果不是必要,就不要这么做。 -ijֶνΪijض裬Ĵ벻⣬ΪͨΪʱҪֶΡ +## 14. Temporary Field(令人迷惑的暂时字段) -ֶκضĴʹ Extract Class һС +某个字段仅为某种特定情况而设,这样的代码不易理解,因为通常认为对象在所有时候都需要它的所有字段。 -## 15. Message ChainsϵϢ +把这种字段和特定情况的处理操作使用 Extract Class 提炼到一个独立类中。 -һһȻһȻ...Ϣַʽζſͻ뽫Ĺϵϡ +## 15. Message Chains(过度耦合的消息链) -úúίһ +一个对象请求另一个对象,然后再向后者请求另一个对象,然后...,这就是消息链。采用这种方式,意味着客户代码将与对象间的关系紧密耦合。 -## 16. Middle Manмˣ +改用函数链,用函数委托另一个对象来处理。 -м˸ίиIJһйĺίи࣬ǾǹίУӦ Remove Middle Manֱ븺Ķ򽻵 +## 16. Middle Man(中间人) -## 17. Inappropriate Intimacyǹϵ +中间人负责处理委托给它的操作,如果一个类中有过多的函数都委托给其它类,那就是过度运用委托,应当 Remove Middle Man,直接与负责的对象打交道。 -ܣ̫ʱȥֱ̽˴˵ private ɷ֡ +## 17. Inappropriate Intimacy(狎昵关系) -## 18. Alernative Classes with Different Interfacesࣩͬ +两个类多于亲密,花费太多时间去探讨彼此的 private 成分。 -## 19. Incomplete Library Class⣩ +## 18. Alernative Classes with Different Interfaces(异曲同工的类) -߲Ƴ⣬ҪһЩ޸ʱʹַֻ޸һʹ Introduce Foreign MethodҪһѶΪʹ Introduce Local Extension +## 19. Incomplete Library Class(不完美的类库) -## 20. Data Classɵࣩ +类库的设计者不可能设计出完美的类库,当我们需要对类库进行一些修改时,可以使用以下两种方法:如果只是修改一两个函数,使用 Introduce Foreign Method;如果要添加一大堆额外行为,使用 Introduce Local Extension。 -ֻӵһЩֶΡ +## 20. Data Class(幼稚的数据类) -ҳֶʹõĵطȻӦIJƵ Data Class С +它只拥有一些数据字段。 -## 21. Refused Bequestܾ +找出字段使用的地方,然后把相应的操作移到 Data Class 中。 -̳гкݣֻҪһ֡ +## 21. Refused Bequest(被拒绝的馈赠) -Ϊ½һֵ࣬Ҫĺʹ Push Down Method Push Down Field ƸǸֵܡ +子类继承超类的所有函数和数据,但是它只想要一部分。 -## 22. Commentsעͣ +为子类新建一个兄弟类,不需要的函数或数据使用 Push Down Method 和 Push Down Field 下推给那个兄弟。 -ʹ Extract Method Ҫע͵IJ֣ȻúͺΪ +## 22. Comments(过多的注释) -# ϵ +使用 Extract Method 提炼出需要注释的部分,然后用函数名来解释函数的行为。 -Java ʹ Junit еԪԡ +# 第四章 构筑测试体系 -ԪԵĶķܲԿͻĽǶȱ֤С +Java 可以使用 Junit 进行单元测试。 -ӦвԿܳı߽ +单元测试的对象是类的方法,而功能测以客户的角度保证软件正常运行。 -# عб +应当集中测试可能出错的边界条件。 -СǰƵԡ +# 第五章 重构列表 -# ֯ +小步前进,频繁测试。 -## 1. Extract Method +# 第六章 重新组织函数 -δŽһУúƽ͸ú; +## 1. Extract Method(提炼函数) -## 2. Inline Method +将这段代码放进一个独立函数中,并让函数名称解释该函数的用途。 -һıͬ׶ +## 2. Inline Method(内联函数) -ںõ뺯壬ȻƳú +一个函数的本体与名称同样清楚易懂。 -## 3. Inline Tempʱ +在函数调用点插入函数本体,然后移除该函数。 -һʱֻ򵥱ʽֵһΣعַ +## 3. Inline Temp(内联临时变量) -жԸñ滻ΪֵǸʽ +一个临时变量,只被简单表达式赋值一次,而它妨碍了其它重构手法。 + +将所有对该变量的引用替换为对它赋值的那个表达式自身。 ```java double basePrice = anOrder.basePrice(); @@ -305,9 +306,9 @@ return basePrice > 1000; return anOrder.basePrice() > 1000; ``` -## 4. Replace Temp with QueryԲѯȡʱ +## 4. Replace Temp with Query(以查询取代临时变量) -ʱijһʽʽһУжʱõ滻ΪºĵáReplace Temp with Query Extract Method ֮ǰزٵһ裬Ϊֲʹ +以临时变量保存某一表达式的运算结果,将这个表达式提炼到一个独立函数中,将所有对临时变量的引用点替换为对新函数的调用。Replace Temp with Query 往往是 Extract Method 之前必不可少的一个步骤,因为局部变量会使代码难以提炼。 ```java double basePrice = quantity * itemPrice; @@ -329,9 +330,9 @@ double basePrice(){ } ``` -## 5. Introduce Explaining Variableͱ +## 5. Introduce Explaining Variable(引起解释变量) -ӱʽһ֣ĽŽһʱԴ˱ͱʽ; +将复杂表达式(或其中一部分)的结果放进一个临时变量,以此变量名称来解释表达式用途。 ```java if((platform.toUpperCase().indexOf("MAC") > -1) && @@ -351,15 +352,15 @@ if(isMacOS && isIEBrower && wasInitialized() && wasResized) { } ``` -## 6. Split Temporary Variableֽʱ +## 6. Split Temporary Variable(分解临时变量) -ijʱֵһΣȲѭҲռ +某个临时变量被赋值超过一次,它既不是循环变量,也不是用于收集计算结果。 -ÿθֵһӦʱÿʱֻеһΡ +针对每次赋值,创造一个独立、对应的临时变量,每个临时变量只承担一个责任。 -## 7. Remove Assigments to ParametersƳԲĸֵ +## 7. Remove Assigments to Parameters(移除对参数的赋值) -һʱȡԸòĸֵ +以一个临时变量取代对该参数的赋值。 ```java int discount (int inputVal, int quentity, int yearToDate){ @@ -372,39 +373,39 @@ int discount (int inputVal, int quentity, int yearToDate){ if (inputVal > 50) result -= 2; ``` -## 8. Replace Method with Method ObjectԺȡ +## 8. Replace Method with Method Object(以函数对象取代函数) -һͺ Extract Method ʱڰ˾ֲʹúѽиò +当对一个大型函数采用 Extract Method 时,由于包含了局部变量使得很难进行该操作。 -ŽһУһֲͳ˶ڵֶΡȻͬһнͺֽΪСͺ +将这个函数放进一个单独对象中,如此一来局部变量就成了对象内的字段。然后可以在同一个对象中将这个大型函数分解为多个小型函数。 -## 9. Subsititute Algorithn滻㷨 +## 9. Subsititute Algorithn(替换算法) -# ڶ֮ +# 第七章 在对象之间搬移特性 -## 1. Move Methodƺ +## 1. Move Method(搬移函数) -еijһиཻú߻߱ߵá +类中的某个函数与另一个类进行更多交流:调用后者或者被后者调用。 -ƵһС +将这个函数搬移到另一个类中。 -## 2. Move FieldֶΣ +## 2. Move Field(搬移字段) -еijֶαһõõָȡֵֵӦѸֶƵһС +类中的某个字段被另一个类更多地用到,这里的用到是指调用取值设值函数,应当把该字段移到另一个类中。 -## 3. Extract Classࣩ +## 3. Extract Class(提炼类) -ijӦ¡ +某个类做了应当由两个类做的事。 -Ӧһ࣬صֶκͺӾƵࡣ +应当建立一个新类,将相关的字段和函数从旧类搬移到新类。 -## 4. Inline Class +## 4. Inline Class(将类内联化) - Extract Class ෴ +与 Extract Class 相反。 -## 5. Hide Delegateءίйϵ +## 5. Hide Delegate(隐藏“委托关系”) -ĺίйϵ +建立所需的函数,隐藏委托关系。 ```java class Person{ @@ -424,13 +425,13 @@ class Department{ } ``` -ͻϣ֪ij˵ľ˭ Department ͶԿͻ¶ Department Ĺԭ +如果客户希望知道某人的经理是谁,必须获得 Department 对象,这样就对客户揭露了 Department 的工作原理。 ```java Person manager = john.getDepartment().getManager(); ``` -ͨΪ Peron һίйϵ +通过为 Peron 建立一个函数来隐藏这种委托关系。 ```java public Person getManager(){ @@ -438,61 +439,61 @@ public Person getManager(){ } ``` -## 6. Remove Middle ManƳмˣ +## 6. Remove Middle Man(移除中间人) - Hide Delegate ෴ҪƳίкÿͻֱӵίࡣ +与 Hide Delegate 相反,本方法需要移除委托函数,让客户直接调用委托类。 -Hide Delegate кܴôĴǣÿͻҪʹʱͱڷһ򵥵ίкίеԽԽ࣬ȫһмˡ +Hide Delegate 有很大好处,但是它的代价是:每当客户要使用受托类的新特性时,就必须在服务器端添加一个简单的委托函数。随着受委托的特性越来越多,服务器类完全变成了一个“中间人”。 -## 7. Introduce Foreign MethodӺ +## 7. Introduce Foreign Method(引入外加函数) -ҪΪṩһ޷޸ࡣ +需要为提供服务的类添加一个函数,但是无法修改这个类。 -ڿͻнһԵһʽһʵÿͻϷʵ +可以在客户类中建立一个函数,并以第一参数形式传入一个服务类的实例,让客户类组合服务器实例。 -## 8. Introduce Local Extension뱾չ +## 8. Introduce Local Extension(引入本地扩展) - Introduce Foreign Method Ŀһ Introduce Local Extension ͨµʵַ֡ʽ߰װ̳࣬ͨʵ֣װͨʵ֡ +和 Introduce Foreign Method 目的一样,但是 Introduce Local Extension 通过建立新的类来实现。有两种方式:子类或者包装类,子类就是通过继承实现,包装类就是通过组合实现。 -# ڰ ֯ +# 第八章 重新组织数据 -## 1. Self Encapsulate FieldԷװֶΣ +## 1. Self Encapsulate Field(自封装字段) -Ϊֶνȡֵ/ֵЩֶΡֻеʳһֶΣнֶηʸΪһֵʹַʽֱӷֶεķʽˡ +为字段建立取值/设值函数,并用这些函数来访问字段。只有当子类想访问超类的一个字段,又想在子类中将对这个字段访问改为一个计算后的值,才使用这种方式,否则直接访问字段的方式简洁明了。 -## 2. Replace Data Value with ObjectԶȡֵ +## 2. Replace Data Value with Object(以对象取代数据值) -ڿڣü򵥵ʾ򵥵ſĽУһЩһЩΪһʼѵ绰ֵַ绰Ҫʽȡš֮Ϊ +在开发初期,往往会用简单的数据项表示简单的情况,但是随着开发的进行,一些简单数据项会具有一些特殊行为。比如一开始会把电话号码存成字符串,但是随后发现电话号码需要“格式化”、“抽取区号”之类的特殊行为。 -## 3. Change Value to Referenceֵijö +## 3. Change Value to Reference(将值对象改成引用对象) -˴ȵʵ滻ΪͬһҪһΨһ󣬹ҪһѾбҪһʱȲбǷѾڸöڣ򷵻бе󣻷½һӵбУظö +将彼此相等的实例替换为同一个对象。这就要用一个工厂来创建这种唯一对象,工厂类中需要保留一份已经创建对象的列表,当要创建一个对象时,先查找这份列表中是否已经存在该对象,如果存在,则返回列表中的这个对象;否则,新建一个对象,添加到列表中,并返回该对象。 -## 4. Change Reference to valueöΪֵ +## 4. Change Reference to value(将引用对象改为值对象) - Change Value to Reference ෴ֵиdzҪԣDzɱģɱʾҪı󣬱һµĶ滻ɶ󣬶޸ľɶ +以 Change Value to Reference 相反。值对象有个非常重要的特性:它是不可变的,不可变表示如果要改变这个对象,必须用一个新的对象来替换旧对象,而不是修改旧对象。 -ҪΪֵʵ equals() hashCode() +需要为值对象实现 equals() 和 hashCode() 方法 -## 5. Replace Array with ObjectԶȡ飩 +## 5. Replace Array with Object(以对象取代数组) -һ飬еԪظԴͬĶ +有一个数组,其中的元素各自代表不同的东西。 -Զ滻飬еÿԪأһֶʾҲ⡣ +以对象替换数组,对于数组中的每个元素,以一个字段来表示,这样方便操作,也更容易理解。 -## 6. Duplicate Observed Dataֵݡ +## 6. Duplicate Observed Data(赋值“被监视数据”) -һЩ GUI ؼУҪЩݡ +一些领域数据置身于 GUI 控件中,而领域函数需要访问这些数据。 -ݸֵһУһ Oberver ģʽͬ GUI ڵظݡ +将该数据赋值到一个领域对象中,建立一个 Oberver 模式,用以同步领域对象和 GUI 对象内的重复数据。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/e024bd7e-fb4e-4239-9451-9a6227f50b00.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//e024bd7e-fb4e-4239-9451-9a6227f50b00.jpg) -## 7. Change Unidirectional Association to BidirectionalΪ˫ +## 7. Change Unidirectional Association to Bidirectional(将单向关联改为双向关联) -඼ҪԷʱʹ˫ +当两个类都需要对方的特性时,可以使用双向关联。 -ֱ࣬Ϊ Order Ϳͻ CustomerOrder CustomerCustomer ҲҪ Order 鿴ж顣 +有两个类,分别为订单 Order 和客户 Customer,Order 引用了 Customer,Customer 也需要引用 Order 来查看其所有订单详情。 ```java class Order{ @@ -517,59 +518,59 @@ class Curstomer{ } ``` -ע⵽ Curstomer ƹϵԭĸƹϵijһIJôɺ߸ƹϵһԶϵɵһһƹϵ +注意到,这里让 Curstomer 类来控制关联关系。有以下原则来决定哪个类来控制关联关系:如果某个对象是组成另一个对象的部件,那么由后者负责控制关联关系;如果是一对多关系,则由单一引用那一方来控制关联关系。 -## 8. Change Bidirectional Association to Unidirectional˫Ϊ +## 8. Change Bidirectional Association to Unidirectional(将双向关联改为单向关联) - Change Unidirectional Association to Bidirectiona Ϊ +和 Change Unidirectional Association to Bidirectiona 为反操作。 -˫άɱߣҲ⡣˫Ӻɡʬ󡱣ijѾˣȴϵͳУΪûûȫȫ +双向关联维护成本高,并且也不易于理解。大量的双向连接很容易造成“僵尸对象”:某个对象本身已经死亡了,却保留在系统中,因为它的引用还没有全部完全清除。 -## 9. Replace Magic Number with Symbolic Constant泣ȡħ +## 9. Replace Magic Number with Symbolic Constant(以字面常量取代魔法数) -һΪ泣λ +创建一个常量,根据其意义为它命名,并将字面常量换位这个常量。 -## 10. Encapsulate FieldװֶΣ +## 10. Encapsulate Field(封装字段) -public ֶӦΪ privateṩӦķʺ +public 字段应当改为 private,并提供相应的访问函数。 -## 11. Encapsulate Collectionװϣ +## 11. Encapsulate Collection(封装集合) -ؼϵһֻṩ/ƳԪصĺؼû޸ļݶӵȴһ֪ +函数返回集合的一个只读副本,并在这个类中提供添加/移除集合元素的函数。如果函数返回集合自身,会让用户得以修改集合内容而集合拥有者却一无所知。 -## 12. Replace Record with Data Classȡ¼ +## 12. Replace Record with Data Class(以数据类取代记录) -## 13. Replace Type Code with Classȡ룩 +## 13. Replace Type Code with Class(以类取代类型码) -һֵ룬ӰΪһ滻ֵ롣 switch УҪʹ Replace Conditional with Polymorphism ȥ switchȱ Replace Type Code with Subcalss Replace Type Code with State/Strategy ȥ롣 +类中有一个数值类型码,但它并不影响类的行为,就用一个新类替换该数值类型码。如果类型码出现在 switch 语句中,需要使用 Replace Conditional with Polymorphism 去掉 switch,首先必须运用 Replace Type Code with Subcalss 或 Replace Type Code with State/Strategy 去掉类型码。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/27c2e0b3-8f95-453d-bedc-6398a8566ce9.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//27c2e0b3-8f95-453d-bedc-6398a8566ce9.jpg) -## 14. Replace Type Code with Subcalssesȡ룩 +## 14. Replace Type Code with Subcalsses(以子类取代类型码) -һɱ룬ӰΪȡ롣 +有一个不可变的类型码,它会影响类的行为,以子类取代这个类型码。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/c41d3977-e0e7-4ee4-93e1-d84f1ae3e20e.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//c41d3977-e0e7-4ee4-93e1-d84f1ae3e20e.jpg) -## 15. Replace Type Code with State/Strategy State/Strategy ȡ룩 +## 15. Replace Type Code with State/Strategy (以 State/Strategy 取代类型码) -һɱ룬ӰΪ״̬ȡ롣 +有一个可变的类型码,它会影响类的行为,以状态对象取代类型码。 - Replace Type Code with Subcalsses Replace Type Code with State/Strategy Ƕ̬ɱģǰ̳ͨеķʽʵ֣ͨϵķʽʵ֡Ϊɱ䣬̳ͨеķʽһһı䣬ôҪıµĶȡɶ󣬶ͻԸıµĶ󡣵ͨϵķʽıõ״̬Ǻ׵ġ +和 Replace Type Code with Subcalsses 的区别是 Replace Type Code with State/Strategy 的类型码是动态可变的,前者通过继承的方式来实现,后者通过组合的方式来实现。因为类型码可变,如果通过继承的方式,一旦一个对象的类型码改变,那么就要改变用新的对象来取代旧对象,而客户端难以改变新的对象。但是通过组合的方式,改变引用的状态类是很容易的。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/81fd1d6f-a3b2-4160-9a0a-1f7cb50ba440.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//81fd1d6f-a3b2-4160-9a0a-1f7cb50ba440.jpg) -## 16. Replace Subclass with Fieldsֶȡࣩ +## 16. Replace Subclass with Fields(以字段取代子类) -Ψһֻڡسݡĺϡ +各个子类的唯一差别只在“返回常量数据”的函数上。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/f2e0cee9-ecdc-4a96-853f-d9f6a1ad6ad1.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//f2e0cee9-ecdc-4a96-853f-d9f6a1ad6ad1.jpg) -# ھ ʽ +# 第九章 简化条件表达式 -## 1. Decompose Conditionalֽʽ +## 1. Decompose Conditional(分解条件表达式) -һӵ䣬Դ ifthenelse зֱ +对于一个复杂的条件语句,可以从 if、then、else 三个段落中分别提炼出独立函数。 ```java if(data.befor(SUMMER_START) || data.after(SUMMER_END)) @@ -583,11 +584,11 @@ if(notSummer(date)) else charge = summerCharge(quantity); ``` -## 2. Consolidate Conditional Expressionϲʽ +## 2. Consolidate Conditional Expression(合并条件表达式) -һϵԣõͬ +有一系列条件测试,都得到相同结果。 -ЩԺϲΪһʽʽΪһ +将这些测试合并为一个条件表达式,并将这个条件表达式提炼成为一个独立函数。 ```java double disabilityAmount(){ @@ -604,11 +605,11 @@ double disabilityAmount(){ } ``` -## 3. Consolidate Duplicate Conditional Fragments ϲظƬΣ +## 3. Consolidate Duplicate Conditional Fragments (合并重复的条件片段) -ʽÿ֧ͬһδ롣 +在条件表达式的每个分支上有着相同的一段代码。 -ظƵʽ֮⡣ +将这段重复代码搬移到条件表达式之外。 ```java if (isSpecialDeal()){ @@ -629,17 +630,17 @@ if (isSpecialDeal()) { send(); ``` -## 4. Remove Control FlagƳƱǣ +## 4. Remove Control Flag(移除控制标记) -һϵвʽУijСƱǡá +在一系列布尔表达式中,某个变量带有“控制标记”的作用。 - break return ȡƱǡ +用 break语 句或 return 语句来取代控制标记。 -## 5. Replace Nested Conditional with Guard Clauses ȡǶʽ +## 5. Replace Nested Conditional with Guard Clauses (以卫语句取代嵌套条件表达式) -ij亱ӦõڸΪʱ̴Ӻзأĵ鳣Ϊ䡱guard clauses +如果某个条件极其罕见,就应该单独检查该条件,并在该条件为真时立刻从函数中返回,这样的单独检查常常被称为“卫语句”(guard clauses)。 -ʽֱͨʽһʽǣз֧ΪڶʽǣʽṩĴֻһΪDzʹ +条件表达式通常有两种表现形式。第一种形式是:所有分支都属于正常行为。第二种形式则是:条件表达式提供的答案中只有一种是正常行为,其他都是不常见的情况,可以使用卫语句表现所有特殊情况。 ```java double getPayAmount() { @@ -665,9 +666,9 @@ double getPayAmount() { }; ``` -## 6. Replace Conditional with Polymorphism Զ̬ȡʽ +## 6. Replace Conditional with Polymorphism (以多态取代条件表达式) -ʽÿ֧ŽһڵĸдУȻԭʼΪҪʹ Replace Type Code with Subclass Replace Type Code with State/Strategy ̳н +将这个条件表达式的每个分支放进一个子类内的覆写函数中,然后将原始函数声明为抽象函数。需要先使用 Replace Type Code with Subclass 或 Replace Type Code with State/Strategy 来建立继承结果。 ```java double getSpeed() { @@ -683,20 +684,20 @@ double getSpeed() { } ``` -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/1c8432c8-2552-457f-b117-1da36c697221.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//1c8432c8-2552-457f-b117-1da36c697221.jpg) -## 7. Introduce Null ObjectNull +## 7. Introduce Null Object(引入Null对象) - null ֵ滻Ϊ null ĺôڣҪѯʶǷΪգֱӵþС +将 null 值替换为 null 对象。这样做的好处在于,不需要询问对象是否为空,直接调用就行。 ```java if (customer == null) plan = BillingPlan.basic(); else plan = customer.getPlan(); ``` -## 8. Introduce Assertionԣ +## 8. Introduce Assertion(引入断言) -Զȷijּ衣ֻڿУƷвжԡ +以断言明确表现某种假设。断言只能用于开发过程中,产品代码中不会有断言。 ```java double getExpenseLimit() { @@ -712,25 +713,25 @@ double getExpenseLimit() { } ``` -# ʮ 򻯺 +# 第十章 简化函数调用 -## 1. Rename Method +## 1. Rename Method(函数改名) -ʹܽͺ; +使函数名能解释函数的用途。 -## 2. Add ParameterӲ +## 2. Add Parameter(添加参数) -ʹҪͨûijϢ +使函数不需要通过调用获得某个信息。 -## 3. Remove ParameterƳ +## 3. Remove Parameter(移除参数) - Add Parameter ෴õõķʽijϢ +与 Add Parameter 相反,改用调用的方式来获得某个信息。 -## 4. Separate Query from Modifierѯ޸ĺ룩 +## 4. Separate Query from Modifier(将查询函数和修改函数分离) -ijض״ֵ̬޸Ķ״̬ +某个函数即返回对象状态值,又修改对象状态。 -Ӧͬĺһѯһ޸ġκзֵĺӦпõĸá +应当建立两个不同的函数,其中一个负责查询,另一个负责修改。任何有返回值的函数,都不应该有看得到的副作用。 ```java getTotalOutstandingAndSetReadyForSummaries(); @@ -741,11 +742,11 @@ getTotalOutstanding(); setReadyForSummaries(); ``` -## 5. Parameterize MethodЯ +## 5. Parameterize Method(令函数携带参数) -ɺƵĹںȴ˲ֵͬ +若干函数做了类似的工作,但在函数本体中却包含了不同的值。 -һԲЩֵͬ +建立单一函数,以参数表达那些不同的值。 ```java fivePercentRaise(); @@ -755,11 +756,11 @@ tenPercentRaise(); raise(percentage); ``` -## 6. Replace Parameter with Explicit Methodsȷȡ +## 6. Replace Parameter with Explicit Methods(以明确函数取代参数) -һȫȡڲֵȡͬΪ +有一个函数,完全取决于参数值而采取不同行为。 -Ըòÿһֵһ +针对该参数的每一个可能值,建立一个独立函数。 ```java void setValue(String name, int value){ @@ -784,11 +785,11 @@ void setWidth(int arg){ } ``` -## 7. Preserve Whole Objectֶ +## 7. Preserve Whole Object(保持对象完整) -ijȡֵΪijһκʱIJ +从某个对象中取出若干值,将它们作为某一次函数调用时的参数。 -Ϊ +改为传递整个对象。 ```java int low = daysTempRange().getLow(); @@ -800,11 +801,11 @@ withinPlan = plan.withinRange(low,high); withinPlan = plan.withinRange(daysTempRange()); ``` -## 8. Replace Parameter with MethodsԺȡ +## 8. Replace Parameter with Methods(以函数取代参数) -ijýΪݸһܸòĺҲܹǰһ +对象调用某个函数,并将所得结果作为参数,传递给另一个函数。而接受该参数的函数本身也能够调用前一个函数。 -òȥֱӵǰһ +让参数接收者去除该项参数,而是直接调用前一个函数。 ```java int basePrice = _quantity * _itemPrice; @@ -817,37 +818,37 @@ int basePrice = _quantity * _itemPrice; double finalPrice = discountedPrice (basePrice); ``` -## 9. Introduce Parameter Object +## 9. Introduce Parameter Object(引入参数对象) -ijЩǺȻͬʱ֣Щ Data Clumps +某些参数总是很自然地同时出现,这些参数就是 Data Clumps。 -һȡЩ +以一个对象取代这些参数。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/08738dd0-ae8e-404a-ba78-a6b1b7d225b3.jpg) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//08738dd0-ae8e-404a-ba78-a6b1b7d225b3.jpg) -## 10. Remove Setting MethodƳֵ +## 10. Remove Setting Method(移除设值函数) -еijֶӦڶ󴴽ʱֵȻͲٸı䡣 +类中的某个字段应该在对象创建时被设值,然后就不再改变。 -ȥֶεֵֶΪ final +去掉该字段的所有设值函数,并将该字段设为 final。 -## 11. Hide Methodغ +## 11. Hide Method(隐藏函数) -һûбκõ +有一个函数,从来没有被其他任何类用到。 -޸Ϊ private +将这个函数修改为 private。 -## 12. Replace Constructor with Factory Method Թȡ캯 +## 12. Replace Constructor with Factory Method (以工厂函数取代构造函数) -ϣڴʱ򵥵Ľ +希望在创建对象时不仅仅是做简单的建构动作。 -캯滻Ϊ +将构造函数替换为工厂函数。 -## 13. Encapsulate Downcastװתͣ +## 13. Encapsulate Downcast(封装向下转型) -ijصĶҪɺִתͣdowncast +某个函数返回的对象,需要由函数调用者执行向下转型(downcast)。 -תͶƵС +将向下转型动作移到函数中。 ```java Object lastReading(){ @@ -860,17 +861,17 @@ Reading lastReading(){ } ``` -## 14. Replace Error Code with Exception 쳣ȡ룩 +## 14. Replace Error Code with Exception (以异常取代错误码) -ijһضĴ룬Աʾijִ +某个函数返回一个特定的代码,用以表示某种错误情况。 -쳣쳣ͨʹֿʹ⡣ +改用异常,异常将普通程序和错误处理分开,使代码更容易理解。 -## 15. Replace Exception with TestԲȡ쳣 +## 15. Replace Exception with Test(以测试取代异常) -һ߿Ԥȼ׳һ쳣 +面对一个调用者可以预先检查的条件,你抛出了一个异常。 -޸ĵߣʹڵú֮ǰ顣 +修改调用者,使它在调用函数之前先做检查。 ```java double getValueForPeriod(int periodNumber) { @@ -887,25 +888,25 @@ double getValueForPeriod(int periodNumber) { return values[periodNumber]; ``` -# ʮһ ϵ +# 第十一章 处理概括关系 -## 1. Pull Up Fieldֶƣ +## 1. Pull Up Field(字段上移) -ӵֶͬΡ +两个子类拥有相同的字段。 -ֶࡣ +将该字段移至超类。 -## 2. Pull Up Methodƣ +## 2. Pull Up Method(函数上移) -ЩڸвȫͬĽ +有些函数,在各个子类中产生完全相同的结果。 -úࡣ +将该函数移至超类。 -## 3. Pull Up Constructor Body캯ƣ +## 3. Pull Up Constructor Body(构造函数本体上移) -ڸӵһЩ캯ǵı弸ȫһ¡ +你在各个子类中拥有一些构造函数,它们的本体几乎完全一致。 -ڳ½һ캯๹캯е +在超类中新建一个构造函数,并在子类构造函数中调用它。 ```java class Manager extends Employee... @@ -924,56 +925,56 @@ public Manager(String name, String id, int grade) { } ``` -## 4. Push Down Methodƣ +## 4. Push Down Method(函数下移) -еijֻ벿йء +超类中的某个函数只与部分子类有关。 -ƵصЩȥ +将这个函数移到相关的那些子类去。 -## 5. Push Down Fieldֶƣ +## 5. Push Down Field(字段下移) -еijֶֻõ +超类中的某个字段只被部分子类用到。 -ֶƵҪЩȥ +将这个字段移到需要它的那些子类去。 -## 6. Extract Subclassࣩ +## 6. Extract Subclass(提炼子类) -еijЩֻijЩʵõ +类中的某些特性只被某些实例用到。 -½һ࣬˵һƵС +新建一个子类,将上面所说的那一部分特性移到子类中。 -## 7. Extract Superclassࣩ +## 7. Extract Superclass(提炼超类) -ԡ +两个类有相似特性。 -Ϊཨһ࣬ͬࡣ +为这两个类建立一个超类,将相同特性移至超类。 -## 8. Extract Interfaceӿڣ +## 8. Extract Interface(提炼接口) -ɿͻʹӿеͬһӼĽӿвͬ +若干客户使用类接口中的同一子集,或者两个类的接口有部分相同。 -ͬӼһӿС +将相同的子集提炼到一个独立接口中。 -## 9. Collapse Hierarchy۵̳ϵ +## 9. Collapse Hierarchy(折叠继承体系) -̫֮ +超类和子类之间无太大区别。 -ǺΪһ塣 +将它们合为一体。 -## 10. Form Template Methodģ庯 +## 10. Form Template Method(塑造模板函数) -һЩ࣬ӦijЩͬ˳ִƵIJϸͬ +你有一些子类,其中相应的某些函数以相同顺序执行类似的操作,但各个操作的细节上有所不同。 -ЩֱŽУǶͬǩԭҲͱͬˡȻԭࡣ(ģ巽ģʽ) +将这些操作分别放进独立函数中,并保持它们都有相同的签名,于是原函数也就变得相同了。然后将原函数上移至超类。(模板方法模式) -## 11. Replace Inheritance with Delegation ίȡ̳У +## 11. Replace Inheritance with Delegation (以委托取代继承) -ijֻʹóӿеһ֣ǸҪ̳жݡ +某个子类只使用超类接口中的一部分,或是根本不需要继承而来的数据。 -½һֶԱ泬࣬ຯĶίг࣬Ȼȥ֮ļ̳йϵ +在子类中新建一个字段用以保存超类,调整子类函数,令它改而委托超类,然后去掉两者之间的继承关系。 -## 12. Replace Delegation with Inheritance Լ̳ȡίУ +## 12. Replace Delegation with Inheritance (以继承取代委托) -֮ʹίйϵΪӿڱд༫򵥵ίк +你在两个类之间使用委托关系,并经常为整个接口编写许多极简单的委托函数。 -ί̳ࡣ +让委托类继承受托类。 diff --git a/notes/面向对象思想.md b/notes/面向对象思想.md index 0403888d..6e758727 100644 --- a/notes/面向对象思想.md +++ b/notes/面向对象思想.md @@ -1,75 +1,76 @@ * [S.O.L.I.D](#solid) - * [1. һԭ](#1-һԭ) - * [2. ŷԭ](#2-ŷԭ) - * [3. 滻ԭ](#3-滻ԭ) - * [4. ӿڷԭ](#4-ӿڷԭ) - * [5. ԭ](#5-ԭ) -* [װ̳С̬](#װ̳ж̬) - * [1. װ](#1-װ) - * [2. ̳](#2-̳) - * [3. ̬](#3-̬) + * [1. 单一责任原则](#1-单一责任原则) + * [2. 开放封闭原则](#2-开放封闭原则) + * [3. 里氏替换原则](#3-里氏替换原则) + * [4. 接口分离原则](#4-接口分离原则) + * [5. 依赖倒置原则](#5-依赖倒置原则) +* [封装、继承、多态](#封装继承多态) + * [1. 封装](#1-封装) + * [2. 继承](#2-继承) + * [3. 多态](#3-多态) * [UML](#uml) - * [1. ͼ](#1-ͼ) - * [2. ʱͼ](#2-ʱͼ) -* [ο](#ο) + * [1. 类图](#1-类图) + * [2. 时序图](#2-时序图) +* [参考资料](#参考资料) + # S.O.L.I.D -S.O.L.I.Dƺͱ(OOD&OOP)мҪԭ(Programming Priciple)ĸд +S.O.L.I.D是面向对象设计和编程(OOD&OOP)中几个重要编码原则(Programming Priciple)的首字母缩写。 -|д |ȫƴ |ķ| +|简写 |全拼 |中文翻译| | -- | -- | -- | -|SRP| The Single Responsibility Principle |һԭ| -|OCP| The Open Closed Principle | ŷԭ| -|LSP| The Liskov Substitution Principle |滻ԭ| -|ISP| The Interface Segregation Principle |ӿڷԭ| -|DIP| The Dependency Inversion Principle |ԭ| +|SRP| The Single Responsibility Principle |单一责任原则| +|OCP| The Open Closed Principle | 开放封闭原则| +|LSP| The Liskov Substitution Principle |里氏替换原则| +|ISP| The Interface Segregation Principle |接口分离原则| +|DIP| The Dependency Inversion Principle |依赖倒置原则| -## 1. һԭ +## 1. 单一责任原则 -Ҫ޸ijʱԭֻһ仰˵һֻһΣҪе͵εʱ򣬾Ҫֽࡣ +当需要修改某个类的时候原因有且只有一个。换句话说就是让一个类只做一种类型责任,当这个类需要承当其他类型的责任的时候,就需要分解这个类。 -## 2. ŷԭ +## 2. 开放封闭原则 -ʵӦǿչ޸ĵġҲ˵չǿŵģ޸Ƿյġ +软件实体应该是可扩展,而不可修改的。也就是说,对扩展是开放的,而对修改是封闭的。 -## 3. 滻ԭ +## 3. 里氏替换原则 -һʵӦܹ滻κ䳬ʵʱ֮ž is-a ϵ +当一个子类的实例应该能够替换任何其超类的实例时,它们之间才具有 is-a 关系。 -## 4. ӿڷԭ +## 4. 接口分离原则 -ǿûȥЩDzʹõĽӿڡ仰˵ʹöרŵĽӿڱʹõһܽӿҪá +不能强迫用户去依赖那些他们不使用的接口。换句话说,使用多个专门的接口比使用单一的总接口总要好。 -## 5. ԭ +## 5. 依赖倒置原则 -1. ߲ģ鲻ӦڵͲģ飬߶Ӧڳ -2. ӦϸڣϸӦڳ +1. 高层模块不应该依赖于低层模块,二者都应该依赖于抽象 +2. 抽象不应该依赖于细节,细节应该依赖于抽象 -# װ̳С̬ +# 封装、继承、多态 -װ̳С̬ԡ +封装、继承、多态是面向对象的三大特性。 -## 1. װ +## 1. 封装 -óͽݺͻݵIJװһʹ乹һɷָĶʵ壬ݱڳ͵ڲܵڲϸڣֻһЩӿʹ֮ⲿϵû֪ڲϸڣͨöṩĽӿʸö +利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体,数据被保护在抽象数据类型的内部,尽可能地隐藏内部的细节,只保留一些对外接口使之与外部发生联系。用户是无需知道对象内部的细节,但可以通过该对象对外的提供的接口来访问该对象。 -װô +封装有三大好处: -1. õķװܹϡ +1. 良好的封装能够减少耦合。 -2. ڲĽṹ޸ġ +2. 类内部的结构可以自由修改。 -3. ԶԳԱиȷĿơ +3. 可以对成员进行更精确的控制。 -4. Ϣʵϸڡ +4. 隐藏信息,实现细节。 - Person װ namegenderage ԣֻͨ get() ȡһ Person name Ժ gender ԣ޷ȡ age ԣ age ԿԹ work() ʹá +以下 Person 类封装 name、gender、age 等属性,外界只能通过 get() 方法获取一个 Person 对象的 name 属性和 gender 属性,而无法获取 age 属性,但是 age 属性可以供 work() 方法使用。 -ע⵽ gender ʹ int ͽд洢װʹûעⲻʵϸڡҪ޸ʹõʱҲڲӰͻ˴½С +注意到 gender 属性使用 int 数据类型进行存储,封装使得用户注意不到这种实现细节。并且在需要修改使用的数据类型时,也可以在不影响客户端代码的情况下进行。 ```java public class Person { @@ -95,25 +96,25 @@ public class Person { } ``` -## 2. ̳ +## 2. 继承 -̳ʵ **is-a** ϵ Cat Animal һ is-a ϵ˿Խ Cat ̳ AnimalӶ Animal private Ժͷ +继承实现了 **is-a** 关系,例如 Cat 和 Animal 就是一种 is-a 关系,因此可以将 Cat 继承自 Animal,从而获得 Animal 非 private 的属性和方法。 -Cat Ե Animal ʹãҲǿʹ Animal Cat תΪΪ **ת** +Cat 可以当做 Animal 来使用,也就是可以使用 Animal 引用 Cat 对象,这种子类转换为父类称为 **向上转型**。 -̳Ӧѭ滻ԭ򣺵һʵӦܹ滻κ䳬ʵʱ֮ž is-a ϵ +继承应该遵循里氏替换原则:当一个子类的实例应该能够替换任何其超类的实例时,它们之间才具有 is-a 关系。 ```java Animal animal = new Cat(); ``` -## 3. ̬ +## 3. 多态 -̬Ϊʱ̬ʱ̬ʱ̬Ҫָװʱָ̬жĶָľڼȷ +多态分为编译时多态和运行时多态。编译时多态主要指方法的重装,运行时多态指程序中定义的对象引用所指向的具体类型在运行期间才确定。 -̬1. ̳У2. Ǹ෽3. ת͡ +多态有三个条件:1. 继承;2. 覆盖父类方法;3. 向上转型。 -ĴУࣨInstrumentࣺWind PercussionǶ play() main() ʹø Instrument Wind Percussion Instrument õ play() ʱִʵö play() Instrument ķ +下面的代码中,乐器类(Instrument)有两个子类:Wind 和 Percussion,它们都覆盖了 play() 方法,并且在 main() 方法中使用父类 Instrument 来引用 Wind 和 Percussion 对象。在 Instrument 引用调用 play() 方法时,会执行实际引用对象所在类的 play() 方法,而不是 Instrument 类的方法。 ```java public class Instrument { @@ -151,157 +152,157 @@ public class Music { # UML -## 1. ͼ +## 1. 类图 -**1.1 ̳** +**1.1 继承相关** -̳ʽ: generalizeʵ֣realizeΪ is-a ϵ +继承有两种形式: 泛化(generalize)和实现(realize),表现为 is-a 关系。 - ϵ(generalization) +① 泛化关系(generalization) -Ӿм̳ +从具体类中继承 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/29badd92-109f-4f29-abb9-9857f5973928.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//29badd92-109f-4f29-abb9-9857f5973928.png) - ʵֹϵ(realize) +② 实现关系(realize) -ӳ߽ӿм̳ +从抽象类或者接口中继承 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/4b16e1d3-3a60-472c-9756-2f31b1c48abe.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//4b16e1d3-3a60-472c-9756-2f31b1c48abe.png) -**1.2 Ͳ** +**1.2 整体和部分** - ۺϹϵ(aggregation) +① 聚合关系(aggregation) -ʾɲɣͲֲǿģ岻˲ֻǻڡ±ʾ B A ɣ +表示整体由部分组成,但是整体和部分不是强依赖的,整体不存在了部分还是会存在。以下表示 B 由 A 组成: -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/34259bb8-ca3a-4872-8771-9e946782d9c3.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//34259bb8-ca3a-4872-8771-9e946782d9c3.png) - Ϲϵ(composition) +② 组合关系(composition) -;ۺϲͬͲǿģ岻˲Ҳˡ繫˾Ͳţ˾û˲žͲˡǹ˾ԱھۺϹϵˣΪ˾ûԱڡ +和聚合不同,组合中整体和部分是强依赖的,整体不存在了部分也不存在了。比如公司和部门,公司没了部门就不存在了。但是公司和员工就属于聚合关系了,因为公司没了员工还在。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/7dda050d-ac35-4f47-9f51-18f18ed6fa9a.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//7dda050d-ac35-4f47-9f51-18f18ed6fa9a.png) -**1.3 ໥ϵ** +**1.3 相互联系** - ϵ(association) +① 关联关系(association) -ʾ֮ͬйһ־̬ϵй̵״̬޹أʼͿȷҲ 1 1 1ԶֹϵʾѧѧУһֹϵһѧУкܶѧһѧֻһѧУһֶһĹϵпʼ֮ǰͿȷ +表示不同类对象之间有关联,这是一种静态关系,与运行过程的状态无关,在最开始就可以确定。因此也可以用 1 对 1、多对 1、多对多这种关联关系来表示。比如学生和学校就是一种关联关系,一个学校可以有很多学生,但是一个学生只属于一个学校,因此这是一种多对一的关系,在运行开始之前就可以确定。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/4ccd294c-d6b2-421b-839e-d88336ff5fb7.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//4ccd294c-d6b2-421b-839e-d88336ff5fb7.png) - ϵ(dependency) +② 依赖关系(dependency) -͹ϵͬ, ϵйõġһΪĹ߷IJ롣˫ʱһֲõơ +和关联关系不同的是, 依赖关系是在运行过程中起作用的。一般依赖作为类的构造器或者方法的参数传入。双向依赖时一种不好的设计。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/47ca2614-509f-476e-98fc-50ec9f9d43c0.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//47ca2614-509f-476e-98fc-50ec9f9d43c0.png) -## 2. ʱͼ +## 2. 时序图 -**2.1 ** +**2.1 定义** -ʱͼ˶֮䴫Ϣʱ˳ʾΪ˳ҪͨĽעǶ󣩣ӶѰIJ +时序图描述了对象之间传递消息的时间顺序,它用来表示用例的行为顺序。它的主要作用是通过对象间的交互来描述用例(注意是对象),从而寻找类的操作。 -**2.2 ֮սʱͼ** +**2.2 赤壁之战时序图** -ߴ±ʾʱƽ +从虚线从上往下表示时间的推进。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/80c5aff8-fc46-4810-aeaa-215b5c60a003.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//80c5aff8-fc46-4810-aeaa-215b5c60a003.png) -ɼͨʱͼ֪ÿ² +可见,通过时序图可以知道每个类具有以下操作: ```java -publc class { - public void Ӧս(); +publc class 刘备 { + public void 应战(); } -publc class { - public void ⶨ(); - public void Ȩ(); - private void 趫(); +publc class 孔明 { + public void 拟定策略(); + public void 联合孙权(); + private void 借东风火攻(); } -public class { - public void G(); +public class 关羽 { + public void 防守荊州(); } -public class ŷ { - public void ؾǰ(); +public class 张飞 { + public void 防守荆州前线(); } -public class Ȩ { - public void (); +public class 孙权 { + public void 领兵相助(); } ``` -**2.3 ͼʱͼ֮Ĺϵ** +**2.3 活动图、时序图之间的关系** -ͼʾûĽǶ +活动图示从用户的角度来描述用例; -ʱͼǴӼĽǶȣĽ +时序图是从计算机的角度(对象间的交互)描述用例。 -**2.4 ͼʱͼĹϵ** +**2.4 类图与时序图的关系** -ͼϵͳľ̬ṹʱͼϵͳĶ̬Ϊ +类图描述系统的静态结构,时序图描述系统的动态行为。 -**2.5 ʱͼ** +**2.5 时序图的组成** - +① 对象 -ֱʽ +有三种表现形式 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/25b8adad-2ef6-4f30-9012-c306b4e49897.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//25b8adad-2ef6-4f30-9012-c306b4e49897.png) -ڻͼʱӦѭԭ +在画图时,应该遵循以下原则: -1. ѽƵĶ󾡿ܵؿ£ +1. 把交互频繁的对象尽可能地靠拢。 -2. ѳʼĶʱһߣߡ +2. 把初始化整个交互活动的对象(有时是一个参与者)放置在最左边。 - +② 生命线 -ߴӶĴʼʱֹ +生命线从对象的创建开始到对象销毁时终止 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/b7b0eac6-e7ea-4fb6-8bfb-95fec6f235e2.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//b7b0eac6-e7ea-4fb6-8bfb-95fec6f235e2.png) - Ϣ +③ 消息 -֮ĽʽͨϢʵֵġ +对象之间的交互式通过发送消息来实现的。 -Ϣ4ͣ +消息有4种类型: -1\. Ϣͬ첽 +1\. 简单消息,不区分同步异步。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/a13b62da-0fa8-4224-a615-4cadacc08871.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//a13b62da-0fa8-4224-a615-4cadacc08871.png) -2\. ͬϢϢ֮ҪͣȴӦ +2\. 同步消息,发送消息之后需要暂停活动来等待回应。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/33821037-dc40-4266-901c-e5b38e618426.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//33821037-dc40-4266-901c-e5b38e618426.png) -3\. 첽ϢϢ֮Ҫȴ +3\. 异步消息,发送消息之后不需要等待。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/dec6c6cc-1b5f-44ed-b8fd-464fcf849dac.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//dec6c6cc-1b5f-44ed-b8fd-464fcf849dac.png) -4\. Ϣѡ +4\. 返回消息,可选。 - +④ 激活 -ϵķʾ״̬ʱ䴦״̬ +生命线上的方框表示激活状态,其它时间处于休眠状态。 -![](https://github.com/CyC2018/InterviewNotes/blob/master/pics/6ab5de9b-1c1e-4118-b2c3-fb6c7ed7de6f.png) +![](https://github.com/CyC2018/InterviewNotes/blob/master/pics//6ab5de9b-1c1e-4118-b2c3-fb6c7ed7de6f.png) -# ο +# 参考资料 -- Java ˼ +- Java 编程思想 -- [ƵSOLIDԭ](http://www.cnblogs.com/shanyou/archive/2009/09/21/1570716.html) +- [面向对象设计的SOLID原则](http://www.cnblogs.com/shanyou/archive/2009/09/21/1570716.html) -- [UMLͼʱͼ](http://design-patterns.readthedocs.io/zh_CN/latest/read_uml.html#generalization) +- [看懂UML类图和时序图](http://design-patterns.readthedocs.io/zh_CN/latest/read_uml.html#generalization) -- [UMLϵСʱͼ˳ͼsequence diagram](http://www.cnblogs.com/wolf-sun/p/UML-Sequence-diagram.html) +- [UML系列——时序图(顺序图)sequence diagram](http://www.cnblogs.com/wolf-sun/p/UML-Sequence-diagram.html) -- [------װ̳С̬](http://blog.csdn.net/jianyuerensheng/article/details/51602015) +- [面向对象编程三大特性------封装、继承、多态](http://blog.csdn.net/jianyuerensheng/article/details/51602015)