diff --git a/README.md b/README.md index ed5960b5ad2a89d11b27afb2e6c01010571bfc41..89625ec37326f38469cc00c8e46865b4d2ba0053 100644 --- a/README.md +++ b/README.md @@ -147,7 +147,7 @@ Google 开源项目的代码风格规范。 **授权相关** -虽然没有加开源协议,但是默认允许非商业使用。 +虽然没有加开源协议,但是允许非商业性使用。 转载使用请注明出处,谢谢! @@ -157,7 +157,7 @@ Google 开源项目的代码风格规范。 进行 Markdown 文档转换是因为 Github 使用的 GFM 不支持 MathJax 公式和 TOC 标记,所以需要替换 MathJax 公式为 CodeCogs 的云服务和重新生成 TOC 目录。 -这里提供了笔者实现的 GFM 文档转换工具的下载:[GFM-Converter](https://github.com/CyC2018/GFM-Converter)。 +这里提供了笔者实现的 GFM 文档转换工具的链接:[GFM-Converter](https://github.com/CyC2018/GFM-Converter)。 **排版指南** @@ -165,7 +165,7 @@ Google 开源项目的代码风格规范。 笔记不使用 `![]()` 这种方式来引用图片,而是用 `` 标签。一方面是为了能够控制图片以合适的大小显示,另一方面是因为 GFM 不支持 `
![]()
` 让图片居中显示,只能使用 `
` 达到居中的效果。 -这里提供了笔者实现的中英混排文档在线排版工具:[Text-Typesetting](https://github.com/CyC2018/Markdown-Typesetting)。 +这里提供了笔者实现的中英混排文档在线排版工具的链接:[Text-Typesetting](https://github.com/CyC2018/Markdown-Typesetting)。 **声明** diff --git a/notes/Socket.md b/notes/Socket.md index 13f1eaf5ab7ad9050680623ee41f4605c8b89b24..99073da311e61075be96713a22ceac05ebb23c8d 100644 --- a/notes/Socket.md +++ b/notes/Socket.md @@ -12,7 +12,7 @@ * [poll](#poll) * [epoll](#epoll) * [select 和 poll 比较](#select-和-poll-比较) - * [eopll 工作模式](#eopll-工作模式) + * [epoll 工作模式](#epoll-工作模式) * [应用场景](#应用场景) * [参考资料](#参考资料) @@ -303,7 +303,7 @@ poll 和 select 在速度上都很慢。 几乎所有的系统都支持 select,但是只有比较新的系统支持 poll。 -## eopll 工作模式 +## epoll 工作模式 epoll_event 有两种触发模式:LT(level trigger)和 ET(edge trigger)。 diff --git "a/notes/\346\225\260\346\215\256\345\272\223\347\263\273\347\273\237\345\216\237\347\220\206.md" "b/notes/\346\225\260\346\215\256\345\272\223\347\263\273\347\273\237\345\216\237\347\220\206.md" index 4aaa9044998cdf90a0b2d1f4ada4fad21879399a..d7e3d8d6f55022baaa696c1109201f61e2aa00bc 100644 --- "a/notes/\346\225\260\346\215\256\345\272\223\347\263\273\347\273\237\345\216\237\347\220\206.md" +++ "b/notes/\346\225\260\346\215\256\345\272\223\347\263\273\347\273\237\345\216\237\347\220\206.md" @@ -25,7 +25,7 @@ * [快照读与当前读](#快照读与当前读) * [六、Next-Key Locks](#六next-key-locks) * [Record Locks](#record-locks) - * [Grap Locks](#grap-locks) + * [Gap Locks](#gap-locks) * [Next-Key Locks](#next-key-locks) * [七、关系数据库设计理论](#七关系数据库设计理论) * [函数依赖](#函数依赖) diff --git "a/notes/\347\256\227\346\263\225.md" "b/notes/\347\256\227\346\263\225.md" index fab6475701716f7b39fce34dc0053c7416327281..df5a80497bdb95283c74b5fe902dd587e2c6b914 100644 --- "a/notes/\347\256\227\346\263\225.md" +++ "b/notes/\347\256\227\346\263\225.md" @@ -24,8 +24,7 @@ * [堆排序](#堆排序) * [小结](#小结) * [六、查找](#六查找) - * [链表实现无序符号表](#链表实现无序符号表) - * [二分查找实现有序符号表](#二分查找实现有序符号表) + * [初级实现](#初级实现) * [二叉查找树](#二叉查找树) * [2-3 查找树](#2-3-查找树) * [红黑树](#红黑树) @@ -1178,7 +1177,9 @@ public interface OrderedST, Value> { } ``` -## 链表实现无序符号表 +## 初级实现 + +### 1. 链表实现无序符号表 ```java public class ListUnorderedST implements UnorderedST { @@ -1253,7 +1254,7 @@ public class ListUnorderedST implements UnorderedST { } ``` -## 二分查找实现有序符号表 +### 2. 二分查找实现有序符号表 使用一对平行数组,一个存储键一个存储值。 @@ -1308,7 +1309,7 @@ public class BinarySearchOrderedST, Value> implement @Override public void put(Key key, Value value) { int index = rank(key); - // 如果找到已经存在的节点键位 key,就更新这个节点的值为 value + // 如果找到已经存在的节点键为 key,就更新这个节点的值为 value if (index < N && keys[index].compareTo(key) == 0) { values[index] = value; return; @@ -1391,14 +1392,14 @@ public class BST, Value> implements OrderedST
@@ -1454,7 +1455,9 @@ private Node put(Node x, Key key, Value value) { ### 3. 分析 -二叉查找树的算法运行时间取决于树的形状,而树的形状又取决于键被插入的先后顺序。最好的情况下树是完全平衡的,每条空链接和根节点的距离都为 logN。 +二叉查找树的算法运行时间取决于树的形状,而树的形状又取决于键被插入的先后顺序。 + +最好的情况下树是完全平衡的,每条空链接和根节点的距离都为 logN。

@@ -1467,8 +1470,7 @@ private Node put(Node x, Key key, Value value) { floor(key):小于等于键的最大键 - 如果键小于根节点的键,那么 floor(key) 一定在左子树中; -- 如果键大于根节点的键,需要先判断右子树中是否存在 floor(key),如果存在就找到,否则根节点就是 floor(key)。 - +- 如果键大于根节点的键,需要先判断右子树中是否存在 floor(key),如果存在就返回,否则根节点就是 floor(key)。 ```java public Key floor(Key key) { @@ -1497,7 +1499,7 @@ rank(key) 返回 key 的排名。 - 如果键和根节点的键相等,返回左子树的节点数; - 如果小于,递归计算在左子树中的排名; -- 如果大于,递归计算在右子树中的排名,并加上左子树的节点数,再加上 1(根节点)。 +- 如果大于,递归计算在右子树中的排名,加上左子树的节点数,再加上 1(根节点)。 ```java @Override @@ -1793,15 +1795,15 @@ private Node put(Node x, Key key, Value value) { - 一致性:相等的键应当有相等的 hash 值,两个键相等表示调用 equals() 返回的值相等。 - 高效性:计算应当简便,有必要的话可以把 hash 值缓存起来,在调用 hash 函数时直接返回。 -- 均匀性:所有键的 hash 值应当均匀地分布到 [0, M-1] 之间,这个条件至关重要,直接影响到散列表的性能。 +- 均匀性:所有键的 hash 值应当均匀地分布到 [0, M-1] 之间,如果不能满足这个条件,有可能产生很多冲突,从而导致散列表的性能下降。 除留余数法可以将整数散列到 [0, M-1] 之间,例如一个正整数 k,计算 k%M 既可得到一个 [0, M-1] 之间的 hash 值。注意 M 必须是一个素数,否则无法利用键包含的所有信息。例如 M 为 10k,那么只能利用键的后 k 位。 -对于其它数,可以将其转换成整数的形式,然后利用除留余数法。例如对于浮点数,可以将其表示成二进制形式,然后使用二进制形式的整数值进行除留余数法。 +对于其它数,可以将其转换成整数的形式,然后利用除留余数法。例如对于浮点数,可以将其的二进制形式转换成整数。 -对于有多部分组合的键,每部分都需要计算 hash 值,并且最后合并时需要让每部分 hash 值都具有同等重要的地位。可以将该键看成 R 进制的整数,键中每部分都具有不同的权值。 +对于多部分组合的类型,每个部分都需要计算 hash 值,这些 hash 值都具有同等重要的地位。为了达到这个目的,可以将该类型看成 R 进制的整数,每个部分都具有不同的权值。 -例如,字符串的散列函数实现如下 +例如,字符串的散列函数实现如下: ```java int hash = 0; @@ -1823,7 +1825,7 @@ Java 中的 hashCode() 实现了 hash 函数,但是默认使用对象的内存 int hash = (x.hashCode() & 0x7fffffff) % M; ``` -使用 Java 自带的 HashMap 等自带的哈希表实现时,只需要去实现 Key 类型的 hashCode() 函数即可。Java 规定 hashCode() 能够将键均匀分布于所有的 32 位整数,Java 中的 String、Integer 等对象的 hashCode() 都能实现这一点。以下展示了自定义类型如何实现 hashCode()。 +使用 Java 自带的 HashMap 等自带的哈希表实现时,只需要去实现 Key 类型的 hashCode() 函数即可。Java 规定 hashCode() 能够将键均匀分布于所有的 32 位整数,Java 中的 String、Integer 等对象的 hashCode() 都能实现这一点。以下展示了自定义类型如何实现 hashCode(): ```java public class Transaction { @@ -1850,15 +1852,19 @@ public class Transaction { ### 2. 基于拉链法的散列表 -拉链法使用链表来存储 hash 值相同的键,从而解决冲突。此时查找需要分两步,首先查找 Key 所在的链表,然后在链表中顺序查找。 +拉链法使用链表来存储 hash 值相同的键,从而解决冲突。 -

+查找需要分两步,首先查找 Key 所在的链表,然后在链表中顺序查找。 -对于 N 个键,M 条链表 (N>M),如果哈希函数能够满足均匀性的条件,每条链表的大小趋向于 N/M,因此未命中的查找和插入操作所需要的比较次数为 \~N/M。 +对于 N 个键,M 条链表 (N>M),如果 hash 函数能够满足均匀性的条件,每条链表的大小趋向于 N/M,因此未命中的查找和插入操作所需要的比较次数为 \~N/M。 + +

### 3. 基于线性探测法的散列表 -线性探测法使用空位来解决冲突,当冲突发生时,向前探测一个空位来存储冲突的键。使用线性探测法,数组的大小 M 应当大于键的个数 N(M>N)。 +线性探测法使用空位来解决冲突,当冲突发生时,向前探测一个空位来存储冲突的键。 + +使用线性探测法,数组的大小 M 应当大于键的个数 N(M>N)。

@@ -1962,9 +1968,7 @@ public void delete(Key key) {

-α = N/M,把 α 称为利用率。理论证明,当 α 小于 1/2 时探测的预计次数只在 1.5 到 2.5 之间。 - -为了保证散列表的性能,应当调整数组的大小,使得 α 在 [1/4, 1/2] 之间。 +α = N/M,把 α 称为使用率。理论证明,当 α 小于 1/2 时探测的预计次数只在 1.5 到 2.5 之间。为了保证散列表的性能,应当调整数组的大小,使得 α 在 [1/4, 1/2] 之间。 ```java private void resize() { @@ -2044,7 +2048,9 @@ public class SparseVector { 2. 将 1 个圆盘从 from -> to 3. 将 n-1 个圆盘从 buffer -> to -如果只有一个圆盘,那么只需要进行一次移动操作,从上面的移动步骤可以知道,n 圆盘需要移动 (n-1)+1+(n-1) = 2n-1 次。 +如果只有一个圆盘,那么只需要进行一次移动操作。 + +从上面的讨论可以知道,n 圆盘需要移动 (n-1)+1+(n-1) = 2n-1 次。

@@ -2093,7 +2099,7 @@ from H1 to H3 - c : 40 - d : 80 -可以将每种字符转换成二进制编码,例如将 a 转换为 00,b 转换为 01,c 转换为 10,d 转换为 11。这是最简单的一种编码方式,没有考虑各个字符的权值(出现频率)。而哈夫曼编码能让出现频率最大的字符编码最短,从而保证最终的编码长度最短。 +可以将每种字符转换成二进制编码,例如将 a 转换为 00,b 转换为 01,c 转换为 10,d 转换为 11。这是最简单的一种编码方式,没有考虑各个字符的权值(出现频率)。而哈夫曼编码能让出现频率最高的字符的编码最短,从而保证整体的编码长度最短。 首先生成一颗哈夫曼树,每次生成过程中选取频率最少的两个节点,生成一个新节点作为它们的父节点,并且新节点的频率为两个节点的和。选取频率最少的原因是,生成过程使得先选取的节点在树的最底层,那么需要的编码长度更长,频率更少可以使得总编码长度更少。 diff --git "a/notes/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" "b/notes/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" index fc123bd0471f0391c9d6398852454fe6a020631b..1c38cc0a66f0f9632ae33543bb3b589d66c4b851 100644 --- "a/notes/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" +++ "b/notes/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" @@ -199,7 +199,7 @@ TCP/IP 协议族是一种沙漏形状,中间小两边大,IP 协议在其中 透明表示一个实际存在的事物看起来好像不存在一样。 -帧使用首部和尾部进行定界,如果帧的数据部分含有和首部尾部相同的内容,那么帧的开始和结束位置就会被错误的判定。需要在数据部分出现首部尾部相同的内容前面插入转义字符,如果出现转义字符,那么就在转义字符前面再加个转义字符,在接收端进行处理之后可以还原出原始数据。这个过程透明传输的内容是转义字符,用户察觉不到转义字符的存在。 +帧使用首部和尾部进行定界,如果帧的数据部分含有和首部尾部相同的内容,那么帧的开始和结束位置就会被错误的判定。需要在数据部分出现首部尾部相同的内容前面插入转义字符。如果数据部分出现转义字符,那么就在转义字符前面再加个转义字符。在接收端进行处理之后可以还原出原始数据。这个过程透明传输的内容是转义字符,用户察觉不到转义字符的存在。

@@ -279,11 +279,11 @@ CSMA/CD 表示载波监听多点接入 / 碰撞检测。 - **多点接入** :说明这是总线型网络,许多主机以多点的方式连接到总线上。 - **载波监听** :每个主机都必须不停地监听信道。在发送前,如果监听到信道正在使用,就必须等待。 -- **碰撞检测** :在发送中,如果监听到信道已有其它主机正在发送数据,就表示发生了碰撞。虽然每一个主机在发送数据之前都已经监听到信道为空闲,但是由于电磁波的传播时延的存在,还是有可能会发生碰撞。 +- **碰撞检测** :在发送中,如果监听到信道已有其它主机正在发送数据,就表示发生了碰撞。虽然每个主机在发送数据之前都已经监听到信道为空闲,但是由于电磁波的传播时延的存在,还是有可能会发生碰撞。 记端到端的传播时延为 τ,最先发送的站点最多经过 2τ 就可以知道是否发生了碰撞,称 2τ 为 **争用期** 。只有经过争用期之后还没有检测到碰撞,才能肯定这次发送不会发生碰撞。 -当发生碰撞时,站点要停止发送,等待一段时间再发送。这个时间采用 **截断二进制指数退避算法** 来确定,从离散的整数集合 {0, 1, .., (2k-1)} 中随机取出一个数,记作 r,然后取 r 倍的争用期作为重传等待时间。 +当发生碰撞时,站点要停止发送,等待一段时间再发送。这个时间采用 **截断二进制指数退避算法** 来确定。从离散的整数集合 {0, 1, .., (2k-1)} 中随机取出一个数,记作 r,然后取 r 倍的争用期作为重传等待时间。

@@ -293,7 +293,7 @@ CSMA/CD 表示载波监听多点接入 / 碰撞检测。

-在 PPP 的帧中: +PPP 的帧格式: - F 字段为帧的定界符 - A 和 C 字段暂时没有意义 @@ -304,15 +304,17 @@ CSMA/CD 表示载波监听多点接入 / 碰撞检测。 ## MAC 地址 -MAC 地址是链路层地址,长度为 6 字节(48 位),用于唯一标识网络适配器(网卡)。一台主机拥有多少个适配器就有多少个 MAC 地址。例如笔记本电脑普遍存在无线网络适配器和有线网络适配器,因此就有两个 MAC 地址。 +MAC 地址是链路层地址,长度为 6 字节(48 位),用于唯一标识网络适配器(网卡)。 + +一台主机拥有多少个适配器就有多少个 MAC 地址。例如笔记本电脑普遍存在无线网络适配器和有线网络适配器,因此就有两个 MAC 地址。 ## 局域网 -局域网是典型的一种广播信道,主要特点是网络为一个单位所拥有,且地理范围和站点数目均有限。 +局域网是一种典型的广播信道,主要特点是网络为一个单位所拥有,且地理范围和站点数目均有限。 -主要有以太网、令牌环网、FDDI 和 ATM 等局域网技术,目前以太网占领着现有的有线局域网市场。 +主要有以太网、令牌环网、FDDI 和 ATM 等局域网技术,目前以太网占领着有线局域网市场。 -可以按照网络拓扑对局域网进行分类: +可以按照网络拓结构扑对局域网进行分类:

@@ -320,11 +322,11 @@ MAC 地址是链路层地址,长度为 6 字节(48 位),用于唯一标 以太网是一种星型拓扑结构局域网。 -早期使用集线器进行连接,它是一种物理层设备,作用于比特而不是帧,当一个比特到达接口时,集线器重新生成这个比特,并将其能量强度放大,从而扩大网络的传输距离。之后再将这个比特向其它所有接口。特别是,如果集线器同时收到同时从两个不同接口的帧,那么就发生了碰撞。 +早期使用集线器进行连接。集线器是一种物理层设备,作用于比特而不是帧,当一个比特到达接口时,集线器重新生成这个比特,并将其能量强度放大,从而扩大网络的传输距离。之后再将这个比特发送到其它所有接口。如果集线器同时收到同时从两个不同接口的帧,那么就发生了碰撞。 -目前以太网使用交换机替代了集线器,它不会发生碰撞,能根据 MAC 地址进行存储转发。 +目前以太网使用交换机替代了集线器。交换机是一种链路层设备,它不会发生碰撞,能根据 MAC 地址进行存储转发。 -以太网帧: +以太网帧格式: - **类型** :标记上层使用的协议; - **数据** :长度在 46-1500 之间,如果太小则需要填充; @@ -335,17 +337,17 @@ MAC 地址是链路层地址,长度为 6 字节(48 位),用于唯一标 ## 交换机* -交换机具有自学习能力,学习的是交换表的内容。交换表中存储着 MAC 地址到接口的映射。下图中,交换机有 4 个接口,主机 A 向主机 B 发送数据帧时,交换机把主机 A 到接口 1 的映射写入交换表中。为了发送数据帧到 B,先查交换表,此时没有主机 B 的表项,那么主机 A 就发送广播帧,主机 C 和主机 D 会丢弃该帧。主机 B 收下之后,查找交换表得到主机 A 映射的接口为 1,就发送数据帧到接口 1,同时交换机添加主机 B 到接口 3 的映射。 +交换机具有自学习能力,学习的是交换表的内容,交换表中存储着 MAC 地址到接口的映射。正是由于这种自学习能力,因此交换机是一种即插即可即用设备,不需要网络管理员手动配置交换表内容。 -

+下图中,交换机有 4 个接口,主机 A 向主机 B 发送数据帧时,交换机把主机 A 到接口 1 的映射写入交换表中。为了发送数据帧到 B,先查交换表,此时没有主机 B 的表项,那么主机 A 就发送广播帧,主机 C 和主机 D 会丢弃该帧。主机 B 收下之后,查找交换表得到主机 A 映射的接口为 1,就发送数据帧到接口 1,同时交换机添加主机 B 到接口 3 的映射。 -它是一种即插即用急用的设备,不需要网络管理员干预。 +

## 虚拟局域网 -虚拟局域网可以建立与物理位置无关的逻辑组,只有在同一个虚拟局域网中的成员才会收到链路层广播信息,例如下图中 (A1, A2, A3, A4) 属于一个虚拟局域网,A1 发送的广播会被 A2、A3、A4 收到,而其它站点收不到。 +虚拟局域网可以建立与物理位置无关的逻辑组,只有在同一个虚拟局域网中的成员才会收到链路层广播信息。例如下图中 (A1, A2, A3, A4) 属于一个虚拟局域网,A1 发送的广播会被 A2、A3、A4 收到,而其它站点收不到。 -使用 VLAN 干线连接来建立虚拟局域网,每台交换机上的一个特殊端口被设置为干线端口,以互连 VLAN 交换机。IEEE 定义了一种扩展的以太网帧格式——802.1Q,用于跨 VLAN 干线的帧,它在标准以太网帧上加进了 4 字节首部 VLAN 标签,用于表示该帧属于哪一个虚拟局域网。 +使用 VLAN 干线连接来建立虚拟局域网,每台交换机上的一个特殊端口被设置为干线端口,以互连 VLAN 交换机。IEEE 定义了一种扩展的以太网帧格式 802.1Q,它在标准以太网帧上加进了 4 字节首部 VLAN 标签,用于表示该帧属于哪一个虚拟局域网。

@@ -526,7 +528,7 @@ VPN 使用公用的互联网作为本机构各专用网之间的通信载体。 ### 1. 内部网关协议 RIP -RIP 是一种基于距离向量的路由选择协议。距离是指跳数,直接相连的路由器跳数为 1,跳数最多为 15,超过 15 表示不可达。 +RIP 是一种基于距离向量的路由选择协议。距离是指跳数,直接相连的路由器跳数为 1。跳数最多为 15,超过 15 表示不可达。 RIP 按固定的时间间隔仅和相邻路由器交换自己的路由表,经过若干次交换之后,所有路由器最终会知道到达本自治系统中任何一个网络的最短距离和下一跳路由器地址。 @@ -558,7 +560,11 @@ OSPF 具有以下特点: BGP(Border Gateway Protocol,边界网关协议) -AS 之间的路由选择很困难,主要是因为互联网规模很大。并且各个 AS 内部使用不同的路由选择协议,就无法准确定义路径的度量。并且 AS 之间的路由选择必须考虑有关的策略,比如有些 AS 不愿意让其它 AS 经过。 +AS 之间的路由选择很困难,主要是由于: + +- 互联网规模很大; +- 各个 AS 内部使用不同的路由选择协议,无法准确定义路径的度量; +- AS 之间的路由选择必须考虑有关的策略,比如有些 AS 不愿意让其它 AS 经过。 BGP 只能寻找一条比较好的路由,而不是最佳路由。 @@ -620,7 +626,7 @@ BGP 只能寻找一条比较好的路由,而不是最佳路由。 第三次握手是为了防止失效的连接请求到达服务器,让服务器错误打开连接。 -失效的连接请求是指,客户端发送的连接请求在网络中滞留,客户端因为没及时收到服务器端发送的连接确认,因此就重新发送了连接请求。滞留的连接请求并不是丢失,之后还是会到达服务器。如果不进行第三次握手,那么服务器会误认为客户端重新请求连接,然后打开了连接。但是并不是客户端真正打开这个连接,因此客户端不会给服务器发送数据,这个连接就白白浪费了。 +客户端发送的连接请求如果在网络中滞留,那么就会隔很长一段时间才能收到服务器端发回的连接确认。客户端等待一个超时重传时间之后,就会重新请求连接。但是这个滞留的连接请求最后还是会到达服务器,如果不进行三次握手,那么服务器就会打开两个连接。如果有第三次握手,客户端会忽略服务器之后发送的对滞留连接请求的连接确认,不进行第三次握手,因此就不会再次打开连接。 ## TCP 的四次挥手 @@ -634,7 +640,7 @@ BGP 只能寻找一条比较好的路由,而不是最佳路由。 - 当 B 不再需要连接时,发送连接释放请求报文段,FIN=1。 -- A 收到后发出确认,进入 TIME-WAIT 状态,等待 2 MSL 时间后释放连接。 +- A 收到后发出确认,进入 TIME-WAIT 状态,等待 2 MSL(最大报文存活时间)后释放连接。 - B 收到 A 的确认后释放连接。 @@ -682,7 +688,7 @@ TCP 使用超时重传来实现可靠传输:如果一个已经发送的报文 ## TCP 拥塞控制 -如果网络出现拥塞,分组将会丢失,此时发送方会继续重传,从而导致网络拥塞程度更高。因此当出现拥塞时,应当控制发送方的速率。这一点和流量控制很像,但是出发点不同。流量控制是为了让接收方能来得及接受,而拥塞控制是为了降低整个网络的拥塞程度。 +如果网络出现拥塞,分组将会丢失,此时发送方会继续重传,从而导致网络拥塞程度更高。因此当出现拥塞时,应当控制发送方的速率。这一点和流量控制很像,但是出发点不同。流量控制是为了让接收方能来得及接收,而拥塞控制是为了降低整个网络的拥塞程度。

@@ -707,11 +713,11 @@ TCP 主要通过四种算法来进行拥塞控制:慢开始、拥塞避免、 ### 2. 快重传与快恢复 -在接收方,要求每次接收到报文段都应该发送对已收到有序报文段的确认,例如已经接收到 M1 和 M2,此时收到 M4,应当发送对 M2 的确认。 +在接收方,要求每次接收到报文段都应该对最后一个已收到的有序报文段进行确认。例如已经接收到 M1 和 M2,此时收到 M4,应当发送对 M2 的确认。 -在发送方,如果收到三个重复确认,那么可以确认下一个报文段丢失,例如收到三个 M2 ,则 M3 丢失。此时执行快重传,立即重传下一个报文段。 +在发送方,如果收到三个重复确认,那么可以知道下一个报文段丢失,此时执行快重传,立即重传下一个报文段。例如收到三个 M2,则 M3 丢失,立即重传 M3。 -在这种情况下,只是丢失个别报文段,而不是网络拥塞,因此执行快恢复,令 ssthresh = cwnd/2 ,cwnd = ssthresh,注意到此时直接进入拥塞避免。慢开始和快恢复的快慢指的是 cwnd 的设定值,而不是 cwnd 的增长速率。慢开始 cwnd 设定为 1,而快恢复 cwnd 设定为 ssthresh。 +在这种情况下,只是丢失个别报文段,而不是网络拥塞。因此执行快恢复,令 ssthresh = cwnd/2 ,cwnd = ssthresh,注意到此时直接进入拥塞避免。慢开始和快恢复的快慢指的是 cwnd 的设定值,而不是 cwnd 的增长速率。慢开始 cwnd 设定为 1,而快恢复 cwnd 设定为 ssthresh。

diff --git "a/notes/\350\256\276\350\256\241\346\250\241\345\274\217.md" "b/notes/\350\256\276\350\256\241\346\250\241\345\274\217.md" index def098bb463502b69f8889cfd92e7ae4e3c4c6a5..ae44213e46c10c11c12c2b4bcdd4b9223420758c 100644 --- "a/notes/\350\256\276\350\256\241\346\250\241\345\274\217.md" +++ "b/notes/\350\256\276\350\256\241\346\250\241\345\274\217.md" @@ -151,29 +151,29 @@ uniqueInstance 采用 volatile 关键字修饰也是很有必要的。`uniqueIns 使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。 - (五)静态内部类实现 -这种实现方式的原理:当静态类加载时,静态内部类没有加载进内存。只有当外部调用`getInstance()`方法,静态内部类才会被加载并实例化一个单例的对象,之后每次 -调用`getInstance()`都会获取相同的对象。此方法不需要同步。 +当 Singleton 类加载时,静态内部类 SingletonHolder 没有被加载进内存。只有当调用 `getUniqueInstance()` 方法从而触发 `SingletonHolder.INSTANCE` 时 SingletonHolder 才会被加载,此时初始化 INSTANCE 实例。 -```java +这种方式不仅具有延迟初始化的好处,而且由虚拟机提供了对线程安全的支持。 + +```source-java public class Singleton { private Singleton() { } - private static class SingletonHelper { - private static final Singleton INSTANCE = new Singleton(); + private static class SingletonHolder { + private static final Singleton INSTANCE = new Singleton(); } public static Singleton getUniqueInstance() { - return SingletonHelper.INSTANCE; + return SingletonHolder.INSTANCE; } } ``` -(六)枚举实现 +(五)枚举实现 这是单例模式的最佳实践,它实现简单,并且在面对复杂的序列化或者反射攻击的时候,能够防止实例化多次。