diff --git "a/docs/cs-basics/network/\350\260\242\345\270\214\344\273\201\350\200\201\345\270\210\347\232\204\343\200\212\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\343\200\213\345\206\205\345\256\271\346\200\273\347\273\223.md" "b/docs/cs-basics/network/\350\260\242\345\270\214\344\273\201\350\200\201\345\270\210\347\232\204\343\200\212\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\343\200\213\345\206\205\345\256\271\346\200\273\347\273\223.md"
new file mode 100644
index 00000000000..df0b07537fd
--- /dev/null
+++ "b/docs/cs-basics/network/\350\260\242\345\270\214\344\273\201\350\200\201\345\270\210\347\232\204\343\200\212\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\343\200\213\345\206\205\345\256\271\346\200\273\347\273\223.md"
@@ -0,0 +1,355 @@
+---
+title: 谢希仁老师的《计算机网络》内容总结
+category: 计算机基础
+tag:
+ - 计算机网络
+---
+
+
+本文是我在大二学习计算机网络期间整理, 大部分内容都来自于谢希仁老师的《计算机网络》这本书。
+
+为了内容更容易理解,我对之前的整理进行了一波重构,并配上了一些相关的示意图便于理解。
+
+
+
+
+
+
+
+- [1. 计算机网络概述](#1-计算机网络概述)
+ - [1.1. 基本术语](#11-基本术语)
+ - [1.2. 重要知识点总结](#12-重要知识点总结)
+- [2. 物理层(Physical Layer)](#2-物理层physical-layer)
+ - [2.1. 基本术语](#21-基本术语)
+ - [2.2. 重要知识点总结](#22-重要知识点总结)
+ - [2.3. 补充](#23-补充)
+ - [2.3.1. 物理层主要做啥?](#231-物理层主要做啥)
+ - [2.3.2. 几种常用的信道复用技术](#232-几种常用的信道复用技术)
+ - [2.3.3. 几种常用的宽带接入技术,主要是 ADSL 和 FTTx](#233-几种常用的宽带接入技术主要是-adsl-和-fttx)
+- [3. 数据链路层(Data Link Layer)](#3-数据链路层data-link-layer)
+ - [3.1. 基本术语](#31-基本术语)
+ - [3.2. 重要知识点总结](#32-重要知识点总结)
+ - [3.3. 补充](#33-补充)
+- [4. 网络层(Network Layer)](#4-网络层network-layer)
+ - [4.1. 基本术语](#41-基本术语)
+ - [4.2. 重要知识点总结](#42-重要知识点总结)
+- [5. 传输层(Transport Layer)](#5-传输层transport-layer)
+ - [5.1. 基本术语](#51-基本术语)
+ - [5.2. 重要知识点总结](#52-重要知识点总结)
+ - [5.3. 补充(重要)](#53-补充重要)
+- [6. 应用层(Application Layer)](#6-应用层application-layer)
+ - [6.1. 基本术语](#61-基本术语)
+ - [6.2. 重要知识点总结](#62-重要知识点总结)
+ - [6.3. 补充(重要)](#63-补充重要)
+
+
+
+
+## 1. 计算机网络概述
+
+### 1.1. 基本术语
+
+1. **结点 (node)** :网络中的结点可以是计算机,集线器,交换机或路由器等。
+2. **链路(link )** : 从一个结点到另一个结点的一段物理线路。中间没有任何其他交点。
+3. **主机(host)** :连接在因特网上的计算机。
+4. **ISP(Internet Service Provider)** :因特网服务提供者(提供商)。
+
+
+
+5. **IXP(Internet eXchange Point)** : 互联网交换点 IXP 的主要作用就是允许两个网络直接相连并交换分组,而不需要再通过第三个网络来转发分组。
+
+
+
+https://labs.ripe.net/Members/fergalc/ixp-traffic-during-stratos-skydive
+
+6. **RFC(Request For Comments)** :意思是“请求评议”,包含了关于 Internet 几乎所有的重要的文字资料。
+7. **广域网 WAN(Wide Area Network)** :任务是通过长距离运送主机发送的数据。
+8. **城域网 MAN(Metropolitan Area Network)**:用来将多个局域网进行互连。
+9. **局域网 LAN(Local Area Network)** : 学校或企业大多拥有多个互连的局域网。
+
+
+
+http://conexionesmanwman.blogspot.com/
+
+10. **个人区域网 PAN(Personal Area Network)** :在个人工作的地方把属于个人使用的电子设备用无线技术连接起来的网络 。
+
+
+
+https://www.itrelease.com/2018/07/advantages-and-disadvantages-of-personal-area-network-pan/
+
+12. **分组(packet )** :因特网中传送的数据单元。由首部 header 和数据段组成。分组又称为包,首部可称为包头。
+13. **存储转发(store and forward )** :路由器收到一个分组,先检查分组是否正确,并过滤掉冲突包错误。确定包正确后,取出目的地址,通过查找表找到想要发送的输出端口地址,然后将该包发送出去。
+
+
+
+14. **带宽(bandwidth)** :在计算机网络中,表示在单位时间内从网络中的某一点到另一点所能通过的“最高数据率”。常用来表示网络的通信线路所能传送数据的能力。单位是“比特每秒”,记为 b/s。
+15. **吞吐量(throughput )** :表示在单位时间内通过某个网络(或信道、接口)的数据量。吞吐量更经常地用于对现实世界中的网络的一种测量,以便知道实际上到底有多少数据量能够通过网络。吞吐量受网络的带宽或网络的额定速率的限制。
+
+### 1.2. 重要知识点总结
+
+1. **计算机网络(简称网络)把许多计算机连接在一起,而互联网把许多网络连接在一起,是网络的网络。**
+2. 小写字母 i 开头的 internet(互联网)是通用名词,它泛指由多个计算机网络相互连接而成的网络。在这些网络之间的通信协议(即通信规则)可以是任意的。大写字母 I 开头的 Internet(互联网)是专用名词,它指全球最大的,开放的,由众多网络相互连接而成的特定的互联网,并采用 TCP/IP 协议作为通信规则,其前身为 ARPANET。Internet 的推荐译名为因特网,现在一般流行称为互联网。
+3. 路由器是实现分组交换的关键构件,其任务是转发收到的分组,这是网络核心部分最重要的功能。分组交换采用存储转发技术,表示把一个报文(要发送的整块数据)分为几个分组后再进行传送。在发送报文之前,先把较长的报文划分成为一个个更小的等长数据段。在每个数据端的前面加上一些由必要的控制信息组成的首部后,就构成了一个分组。分组又称为包。分组是在互联网中传送的数据单元,正是由于分组的头部包含了诸如目的地址和源地址等重要控制信息,每一个分组才能在互联网中独立的选择传输路径,并正确地交付到分组传输的终点。
+4. 互联网按工作方式可划分为边缘部分和核心部分。主机在网络的边缘部分,其作用是进行信息处理。由大量网络和连接这些网络的路由器组成核心部分,其作用是提供连通性和交换。
+5. 计算机通信是计算机中进程(即运行着的程序)之间的通信。计算机网络采用的通信方式是客户-服务器方式(C/S 方式)和对等连接方式(P2P 方式)。
+6. 客户和服务器都是指通信中所涉及的应用进程。客户是服务请求方,服务器是服务提供方。
+7. 按照作用范围的不同,计算机网络分为广域网 WAN,城域网 MAN,局域网 LAN,个人区域网 PAN。
+8. **计算机网络最常用的性能指标是:速率,带宽,吞吐量,时延(发送时延,处理时延,排队时延),时延带宽积,往返时间和信道利用率。**
+9. 网络协议即协议,是为进行网络中的数据交换而建立的规则。计算机网络的各层以及其协议集合,称为网络的体系结构。
+10. **五层体系结构由应用层,运输层,网络层(网际层),数据链路层,物理层组成。运输层最主要的协议是 TCP 和 UDP 协议,网络层最重要的协议是 IP 协议。**
+
+
+
+下面的内容会介绍计算机网络的五层体系结构:**物理层+数据链路层+网络层(网际层)+运输层+应用层**。
+
+## 2. 物理层(Physical Layer)
+
+
+
+### 2.1. 基本术语
+
+1. **数据(data)** :运送消息的实体。
+2. **信号(signal)** :数据的电气的或电磁的表现。或者说信号是适合在传输介质上传输的对象。
+3. **码元( code)** :在使用时间域(或简称为时域)的波形来表示数字信号时,代表不同离散数值的基本波形。
+4. **单工(simplex )** : 只能有一个方向的通信而没有反方向的交互。
+5. **半双工(half duplex )** :通信的双方都可以发送信息,但不能双方同时发送(当然也就不能同时接收)。
+6. **全双工(full duplex)** : 通信的双方可以同时发送和接收信息。
+
+
+
+7. **失真**:失去真实性,主要是指接受到的信号和发送的信号不同,有磨损和衰减。影响失真程度的因素:1.码元传输速率 2.信号传输距离 3.噪声干扰 4.传输媒体质量
+
+
+
+8. **奈氏准则** : 在任何信道中,码元的传输的效率是有上限的,传输速率超过此上限,就会出现严重的码间串扰问题,使接收端对码元的判决(即识别)成为不可能。
+9. **香农定理** :在带宽受限且有噪声的信道中,为了不产生误差,信息的数据传输速率有上限值。
+10. **基带信号(baseband signal)** : 来自信源的信号。指没有经过调制的数字信号或模拟信号。
+11. **带通(频带)信号(bandpass signal)** :把基带信号经过载波调制后,把信号的频率范围搬移到较高的频段以便在信道中传输(即仅在一段频率范围内能够通过信道),这里调制过后的信号就是带通信号。
+12. **调制(modulation )** : 对信号源的信息进行处理后加到载波信号上,使其变为适合在信道传输的形式的过程。
+13. **信噪比(signal-to-noise ratio )** : 指信号的平均功率和噪声的平均功率之比,记为 S/N。信噪比(dB)=10\*log10(S/N)。
+14. **信道复用(channel multiplexing )** :指多个用户共享同一个信道。(并不一定是同时)。
+
+
+
+15. **比特率(bit rate )** :单位时间(每秒)内传送的比特数。
+16. **波特率(baud rate)** :单位时间载波调制状态改变的次数。针对数据信号对载波的调制速率。
+17. **复用(multiplexing)** :共享信道的方法。
+18. **ADSL(Asymmetric Digital Subscriber Line )** :非对称数字用户线。
+19. **光纤同轴混合网(HFC 网)** :在目前覆盖范围很广的有线电视网的基础上开发的一种居民宽带接入网
+
+### 2.2. 重要知识点总结
+
+1. **物理层的主要任务就是确定与传输媒体接口有关的一些特性,如机械特性,电气特性,功能特性,过程特性。**
+2. 一个数据通信系统可划分为三大部分,即源系统,传输系统,目的系统。源系统包括源点(或源站,信源)和发送器,目的系统包括接收器和终点。
+3. **通信的目的是传送消息。如话音,文字,图像等都是消息,数据是运送消息的实体。信号则是数据的电气或电磁的表现。**
+4. 根据信号中代表消息的参数的取值方式不同,信号可分为模拟信号(或连续信号)和数字信号(或离散信号)。在使用时间域(简称时域)的波形表示数字信号时,代表不同离散数值的基本波形称为码元。
+5. 根据双方信息交互的方式,通信可划分为单向通信(或单工通信),双向交替通信(或半双工通信),双向同时通信(全双工通信)。
+6. 来自信源的信号称为基带信号。信号要在信道上传输就要经过调制。调制有基带调制和带通调制之分。最基本的带通调制方法有调幅,调频和调相。还有更复杂的调制方法,如正交振幅调制。
+7. 要提高数据在信道上的传递速率,可以使用更好的传输媒体,或使用先进的调制技术。但数据传输速率不可能任意被提高。
+8. 传输媒体可分为两大类,即导引型传输媒体(双绞线,同轴电缆,光纤)和非导引型传输媒体(无线,红外,大气激光)。
+9. 为了有效利用光纤资源,在光纤干线和用户之间广泛使用无源光网络 PON。无源光网络无需配备电源,其长期运营成本和管理成本都很低。最流行的无源光网络是以太网无源光网络 EPON 和吉比特无源光网络 GPON。
+
+### 2.3. 补充
+
+#### 2.3.1. 物理层主要做啥?
+
+物理层主要做的事情就是 **透明地传送比特流**。也可以将物理层的主要任务描述为确定与传输媒体的接口的一些特性,即:机械特性(接口所用接线器的一些物理属性如形状和尺寸),电气特性(接口电缆的各条线上出现的电压的范围),功能特性(某条线上出现的某一电平的电压的意义),过程特性(对于不同功能的各种可能事件的出现顺序)。
+
+**物理层考虑的是怎样才能在连接各种计算机的传输媒体上传输数据比特流,而不是指具体的传输媒体。** 现有的计算机网络中的硬件设备和传输媒体的种类非常繁多,而且通信手段也有许多不同的方式。物理层的作用正是尽可能地屏蔽掉这些传输媒体和通信手段的差异,使物理层上面的数据链路层感觉不到这些差异,这样就可以使数据链路层只考虑完成本层的协议和服务,而不必考虑网络的具体传输媒体和通信手段是什么。
+
+#### 2.3.2. 几种常用的信道复用技术
+
+1. **频分复用(FDM)** :所有用户在同样的时间占用不同的带宽资源。
+2. **时分复用(TDM)** :所有用户在不同的时间占用同样的频带宽度(分时不分频)。
+3. **统计时分复用 (Statistic TDM)** :改进的时分复用,能够明显提高信道的利用率。
+4. **码分复用(CDM)** : 用户使用经过特殊挑选的不同码型,因此各用户之间不会造成干扰。这种系统发送的信号有很强的抗干扰能力,其频谱类似于白噪声,不易被敌人发现。
+5. **波分复用( WDM)** :波分复用就是光的频分复用。
+
+#### 2.3.3. 几种常用的宽带接入技术,主要是 ADSL 和 FTTx
+
+用户到互联网的宽带接入方法有非对称数字用户线 ADSL(用数字技术对现有的模拟电话线进行改造,而不需要重新布线。ADSL 的快速版本是甚高速数字用户线 VDSL。),光纤同轴混合网 HFC(是在目前覆盖范围很广的有线电视网的基础上开发的一种居民宽带接入网)和 FTTx(即光纤到······)。
+
+## 3. 数据链路层(Data Link Layer)
+
+
+
+### 3.1. 基本术语
+
+1. **链路(link)** :一个结点到相邻结点的一段物理链路。
+2. **数据链路(data link)** :把实现控制数据运输的协议的硬件和软件加到链路上就构成了数据链路。
+3. **循环冗余检验 CRC(Cyclic Redundancy Check)** :为了保证数据传输的可靠性,CRC 是数据链路层广泛使用的一种检错技术。
+4. **帧(frame)** :一个数据链路层的传输单元,由一个数据链路层首部和其携带的封包所组成协议数据单元。
+5. **MTU(Maximum Transfer Uint )** :最大传送单元。帧的数据部分的的长度上限。
+6. **误码率 BER(Bit Error Rate )** :在一段时间内,传输错误的比特占所传输比特总数的比率。
+7. **PPP(Point-to-Point Protocol )** :点对点协议。即用户计算机和 ISP 进行通信时所使用的数据链路层协议。以下是 PPP 帧的示意图:
+ 
+8. **MAC 地址(Media Access Control 或者 Medium Access Control)** :意译为媒体访问控制,或称为物理地址、硬件地址,用来定义网络设备的位置。在 OSI 模型中,第三层网络层负责 IP 地址,第二层数据链路层则负责 MAC 地址。因此一个主机会有一个 MAC 地址,而每个网络位置会有一个专属于它的 IP 地址 。地址是识别某个系统的重要标识符,“名字指出我们所要寻找的资源,地址指出资源所在的地方,路由告诉我们如何到达该处。”
+
+
+
+9. **网桥(bridge)** :一种用于数据链路层实现中继,连接两个或多个局域网的网络互连设备。
+10. **交换机(switch )** :广义的来说,交换机指的是一种通信系统中完成信息交换的设备。这里工作在数据链路层的交换机指的是交换式集线器,其实质是一个多接口的网桥
+
+### 3.2. 重要知识点总结
+
+1. 链路是从一个结点到相邻结点的一段物理链路,数据链路则在链路的基础上增加了一些必要的硬件(如网络适配器)和软件(如协议的实现)
+2. 数据链路层使用的主要是**点对点信道**和**广播信道**两种。
+3. 数据链路层传输的协议数据单元是帧。数据链路层的三个基本问题是:**封装成帧**,**透明传输**和**差错检测**
+4. **循环冗余检验 CRC** 是一种检错方法,而帧检验序列 FCS 是添加在数据后面的冗余码
+5. **点对点协议 PPP** 是数据链路层使用最多的一种协议,它的特点是:简单,只检测差错而不去纠正差错,不使用序号,也不进行流量控制,可同时支持多种网络层协议
+6. PPPoE 是为宽带上网的主机使用的链路层协议
+7. **局域网的优点是:具有广播功能,从一个站点可方便地访问全网;便于系统的扩展和逐渐演变;提高了系统的可靠性,可用性和生存性。**
+8. 计算机与外接局域网通信需要通过通信适配器(或网络适配器),它又称为网络接口卡或网卡。**计算器的硬件地址就在适配器的 ROM 中**。
+9. 以太网采用的无连接的工作方式,对发送的数据帧不进行编号,也不要求对方发回确认。目的站收到有差错帧就把它丢掉,其他什么也不做
+10. 以太网采用的协议是具有冲突检测的**载波监听多点接入 CSMA/CD**。协议的特点是:**发送前先监听,边发送边监听,一旦发现总线上出现了碰撞,就立即停止发送。然后按照退避算法等待一段随机时间后再次发送。** 因此,每一个站点在自己发送数据之后的一小段时间内,存在着遭遇碰撞的可能性。以太网上的各站点平等地争用以太网信道
+11. 以太网的适配器具有过滤功能,它只接收单播帧,广播帧和多播帧。
+12. 使用集线器可以在物理层扩展以太网(扩展后的以太网仍然是一个网络)
+
+### 3.3. 补充
+
+1. 数据链路层的点对点信道和广播信道的特点,以及这两种信道所使用的协议(PPP 协议以及 CSMA/CD 协议)的特点
+2. 数据链路层的三个基本问题:**封装成帧**,**透明传输**,**差错检测**
+3. 以太网的 MAC 层硬件地址
+4. 适配器,转发器,集线器,网桥,以太网交换机的作用以及适用场合
+
+## 4. 网络层(Network Layer)
+
+
+
+### 4.1. 基本术语
+
+1. **虚电路(Virtual Circuit)** : 在两个终端设备的逻辑或物理端口之间,通过建立的双向的透明传输通道。虚电路表示这只是一条逻辑上的连接,分组都沿着这条逻辑连接按照存储转发方式传送,而并不是真正建立了一条物理连接。
+2. **IP(Internet Protocol )** : 网际协议 IP 是 TCP/IP 体系中两个最主要的协议之一,是 TCP/IP 体系结构网际层的核心。配套的有 ARP,RARP,ICMP,IGMP。
+3. **ARP(Address Resolution Protocol)** : 地址解析协议。地址解析协议 ARP 把 IP 地址解析为硬件地址。
+4. **ICMP(Internet Control Message Protocol )** :网际控制报文协议 (ICMP 允许主机或路由器报告差错情况和提供有关异常情况的报告)。
+5. **子网掩码(subnet mask )** :它是一种用来指明一个 IP 地址的哪些位标识的是主机所在的子网以及哪些位标识的是主机的位掩码。子网掩码不能单独存在,它必须结合 IP 地址一起使用。
+6. **CIDR( Classless Inter-Domain Routing )**:无分类域间路由选择 (特点是消除了传统的 A 类、B 类和 C 类地址以及划分子网的概念,并使用各种长度的“网络前缀”(network-prefix)来代替分类地址中的网络号和子网号)。
+7. **默认路由(default route)** :当在路由表中查不到能到达目的地址的路由时,路由器选择的路由。默认路由还可以减小路由表所占用的空间和搜索路由表所用的时间。
+8. **路由选择算法(Virtual Circuit)** :路由选择协议的核心部分。因特网采用自适应的,分层次的路由选择协议。
+
+### 4.2. 重要知识点总结
+
+1. **TCP/IP 协议中的网络层向上只提供简单灵活的,无连接的,尽最大努力交付的数据报服务。网络层不提供服务质量的承诺,不保证分组交付的时限所传送的分组可能出错,丢失,重复和失序。进程之间通信的可靠性由运输层负责**
+2. 在互联网的交付有两种,一是在本网络直接交付不用经过路由器,另一种是和其他网络的间接交付,至少经过一个路由器,但最后一次一定是直接交付
+3. 分类的 IP 地址由网络号字段(指明网络)和主机号字段(指明主机)组成。网络号字段最前面的类别指明 IP 地址的类别。IP 地址是一种分等级的地址结构。IP 地址管理机构分配 IP 地址时只分配网络号,主机号由得到该网络号的单位自行分配。路由器根据目的主机所连接的网络号来转发分组。一个路由器至少连接到两个网络,所以一个路由器至少应当有两个不同的 IP 地址
+4. IP 数据报分为首部和数据两部分。首部的前一部分是固定长度,共 20 字节,是所有 IP 数据包必须具有的(源地址,目的地址,总长度等重要地段都固定在首部)。一些长度可变的可选字段固定在首部的后面。IP 首部中的生存时间给出了 IP 数据报在互联网中所能经过的最大路由器数。可防止 IP 数据报在互联网中无限制的兜圈子。
+5. **地址解析协议 ARP 把 IP 地址解析为硬件地址。ARP 的高速缓存可以大大减少网络上的通信量。因为这样可以使主机下次再与同样地址的主机通信时,可以直接从高速缓存中找到所需要的硬件地址而不需要再去以广播方式发送 ARP 请求分组**
+6. 无分类域间路由选择 CIDR 是解决目前 IP 地址紧缺的一个好办法。CIDR 记法在 IP 地址后面加上斜线“/”,然后写上前缀所占的位数。前缀(或网络前缀)用来指明网络,前缀后面的部分是后缀,用来指明主机。CIDR 把前缀都相同的连续的 IP 地址组成一个“CIDR 地址块”,IP 地址分配都以 CIDR 地址块为单位。
+7. 网际控制报文协议是 IP 层的协议。ICMP 报文作为 IP 数据报的数据,加上首部后组成 IP 数据报发送出去。使用 ICMP 数据报并不是为了实现可靠传输。ICMP 允许主机或路由器报告差错情况和提供有关异常情况的报告。ICMP 报文的种类有两种,即 ICMP 差错报告报文和 ICMP 询问报文。
+8. **要解决 IP 地址耗尽的问题,最根本的办法是采用具有更大地址空间的新版本 IP 协议-IPv6。** IPv6 所带来的变化有 ① 更大的地址空间(采用 128 位地址)② 灵活的首部格式 ③ 改进的选项 ④ 支持即插即用 ⑤ 支持资源的预分配 ⑥IPv6 的首部改为 8 字节对齐。
+9. **虚拟专用网络 VPN 利用公用的互联网作为本机构专用网之间的通信载体。VPN 内使用互联网的专用地址。一个 VPN 至少要有一个路由器具有合法的全球 IP 地址,这样才能和本系统的另一个 VPN 通过互联网进行通信。所有通过互联网传送的数据都需要加密。**
+10. MPLS 的特点是:① 支持面向连接的服务质量 ② 支持流量工程,平衡网络负载 ③ 有效的支持虚拟专用网 VPN。MPLS 在入口节点给每一个 IP 数据报打上固定长度的“标记”,然后根据标记在第二层(链路层)用硬件进行转发(在标记交换路由器中进行标记交换),因而转发速率大大加快。
+
+## 5. 传输层(Transport Layer)
+
+
+
+### 5.1. 基本术语
+
+1. **进程(process)** :指计算机中正在运行的程序实体。
+2. **应用进程互相通信** :一台主机的进程和另一台主机中的一个进程交换数据的过程(另外注意通信真正的端点不是主机而是主机中的进程,也就是说端到端的通信是应用进程之间的通信)。
+3. **传输层的复用与分用** :复用指发送方不同的进程都可以通过同一个运输层协议传送数据。分用指接收方的运输层在剥去报文的首部后能把这些数据正确的交付到目的应用进程。
+4. **TCP(Transmission Control Protocol)** :传输控制协议。
+5. **UDP(User Datagram Protocol)** :用户数据报协议。
+
+
+
+6. **端口(port)** :端口的目的是为了确认对方机器的哪个进程在与自己进行交互,比如 MSN 和 QQ 的端口不同,如果没有端口就可能出现 QQ 进程和 MSN 交互错误。端口又称协议端口号。
+7. **停止等待协议(stop-and-wait)** :指发送方每发送完一个分组就停止发送,等待对方确认,在收到确认之后在发送下一个分组。
+8. **流量控制** : 就是让发送方的发送速率不要太快,既要让接收方来得及接收,也不要使网络发生拥塞。
+9. **拥塞控制** :防止过多的数据注入到网络中,这样可以使网络中的路由器或链路不致过载。拥塞控制所要做的都有一个前提,就是网络能够承受现有的网络负荷。
+
+### 5.2. 重要知识点总结
+
+1. **运输层提供应用进程之间的逻辑通信,也就是说,运输层之间的通信并不是真正在两个运输层之间直接传输数据。运输层向应用层屏蔽了下面网络的细节(如网络拓补,所采用的路由选择协议等),它使应用进程之间看起来好像两个运输层实体之间有一条端到端的逻辑通信信道。**
+2. **网络层为主机提供逻辑通信,而运输层为应用进程之间提供端到端的逻辑通信。**
+3. 运输层的两个重要协议是用户数据报协议 UDP 和传输控制协议 TCP。按照 OSI 的术语,两个对等运输实体在通信时传送的数据单位叫做运输协议数据单元 TPDU(Transport Protocol Data Unit)。但在 TCP/IP 体系中,则根据所使用的协议是 TCP 或 UDP,分别称之为 TCP 报文段或 UDP 用户数据报。
+4. **UDP 在传送数据之前不需要先建立连接,远地主机在收到 UDP 报文后,不需要给出任何确认。虽然 UDP 不提供可靠交付,但在某些情况下 UDP 确是一种最有效的工作方式。 TCP 提供面向连接的服务。在传送数据之前必须先建立连接,数据传送结束后要释放连接。TCP 不提供广播或多播服务。由于 TCP 要提供可靠的,面向连接的传输服务,难以避免地增加了许多开销,如确认,流量控制,计时器以及连接管理等。这不仅使协议数据单元的首部增大很多,还要占用许多处理机资源。**
+5. 硬件端口是不同硬件设备进行交互的接口,而软件端口是应用层各种协议进程与运输实体进行层间交互的一种地址。UDP 和 TCP 的首部格式中都有源端口和目的端口这两个重要字段。当运输层收到 IP 层交上来的运输层报文时,就能够根据其首部中的目的端口号把数据交付应用层的目的应用层。(两个进程之间进行通信不光要知道对方 IP 地址而且要知道对方的端口号(为了找到对方计算机中的应用进程))
+6. 运输层用一个 16 位端口号标志一个端口。端口号只有本地意义,它只是为了标志计算机应用层中的各个进程在和运输层交互时的层间接口。在互联网的不同计算机中,相同的端口号是没有关联的。协议端口号简称端口。虽然通信的终点是应用进程,但只要把所发送的报文交到目的主机的某个合适端口,剩下的工作(最后交付目的进程)就由 TCP 和 UDP 来完成。
+7. 运输层的端口号分为服务器端使用的端口号(0˜1023 指派给熟知端口,1024˜49151 是登记端口号)和客户端暂时使用的端口号(49152˜65535)
+8. **UDP 的主要特点是 ① 无连接 ② 尽最大努力交付 ③ 面向报文 ④ 无拥塞控制 ⑤ 支持一对一,一对多,多对一和多对多的交互通信 ⑥ 首部开销小(只有四个字段:源端口,目的端口,长度和检验和)**
+9. **TCP 的主要特点是 ① 面向连接 ② 每一条 TCP 连接只能是一对一的 ③ 提供可靠交付 ④ 提供全双工通信 ⑤ 面向字节流**
+10. **TCP 用主机的 IP 地址加上主机上的端口号作为 TCP 连接的端点。这样的端点就叫做套接字(socket)或插口。套接字用(IP 地址:端口号)来表示。每一条 TCP 连接唯一地被通信两端的两个端点所确定。**
+11. 停止等待协议是为了实现可靠传输的,它的基本原理就是每发完一个分组就停止发送,等待对方确认。在收到确认后再发下一个分组。
+12. 为了提高传输效率,发送方可以不使用低效率的停止等待协议,而是采用流水线传输。流水线传输就是发送方可连续发送多个分组,不必每发完一个分组就停下来等待对方确认。这样可使信道上一直有数据不间断的在传送。这种传输方式可以明显提高信道利用率。
+13. 停止等待协议中超时重传是指只要超过一段时间仍然没有收到确认,就重传前面发送过的分组(认为刚才发送过的分组丢失了)。因此每发送完一个分组需要设置一个超时计时器,其重传时间应比数据在分组传输的平均往返时间更长一些。这种自动重传方式常称为自动重传请求 ARQ。另外在停止等待协议中若收到重复分组,就丢弃该分组,但同时还要发送确认。连续 ARQ 协议可提高信道利用率。发送维持一个发送窗口,凡位于发送窗口内的分组可连续发送出去,而不需要等待对方确认。接收方一般采用累积确认,对按序到达的最后一个分组发送确认,表明到这个分组位置的所有分组都已经正确收到了。
+14. TCP 报文段的前 20 个字节是固定的,后面有 4n 字节是根据需要增加的选项。因此,TCP 首部的最小长度是 20 字节。
+15. **TCP 使用滑动窗口机制。发送窗口里面的序号表示允许发送的序号。发送窗口后沿的后面部分表示已发送且已收到确认,而发送窗口前沿的前面部分表示不允许发送。发送窗口后沿的变化情况有两种可能,即不动(没有收到新的确认)和前移(收到了新的确认)。发送窗口的前沿通常是不断向前移动的。一般来说,我们总是希望数据传输更快一些。但如果发送方把数据发送的过快,接收方就可能来不及接收,这就会造成数据的丢失。所谓流量控制就是让发送方的发送速率不要太快,要让接收方来得及接收。**
+16. 在某段时间,若对网络中某一资源的需求超过了该资源所能提供的可用部分,网络的性能就要变坏。这种情况就叫拥塞。拥塞控制就是为了防止过多的数据注入到网络中,这样就可以使网络中的路由器或链路不致过载。拥塞控制所要做的都有一个前提,就是网络能够承受现有的网络负荷。拥塞控制是一个全局性的过程,涉及到所有的主机,所有的路由器,以及与降低网络传输性能有关的所有因素。相反,流量控制往往是点对点通信量的控制,是个端到端的问题。流量控制所要做到的就是抑制发送端发送数据的速率,以便使接收端来得及接收。
+17. **为了进行拥塞控制,TCP 发送方要维持一个拥塞窗口 cwnd 的状态变量。拥塞控制窗口的大小取决于网络的拥塞程度,并且动态变化。发送方让自己的发送窗口取为拥塞窗口和接收方的接受窗口中较小的一个。**
+18. **TCP 的拥塞控制采用了四种算法,即慢开始,拥塞避免,快重传和快恢复。在网络层也可以使路由器采用适当的分组丢弃策略(如主动队列管理 AQM),以减少网络拥塞的发生。**
+19. 运输连接的三个阶段,即:连接建立,数据传送和连接释放。
+20. **主动发起 TCP 连接建立的应用进程叫做客户,而被动等待连接建立的应用进程叫做服务器。TCP 连接采用三报文握手机制。服务器要确认用户的连接请求,然后客户要对服务器的确认进行确认。**
+21. TCP 的连接释放采用四报文握手机制。任何一方都可以在数据传送结束后发出连接释放的通知,待对方确认后进入半关闭状态。当另一方也没有数据再发送时,则发送连接释放通知,对方确认后就完全关闭了 TCP 连接
+
+### 5.3. 补充(重要)
+
+以下知识点需要重点关注:
+
+1. 端口和套接字的意义
+2. UDP 和 TCP 的区别以及两者的应用场景
+3. 在不可靠的网络上实现可靠传输的工作原理,停止等待协议和 ARQ 协议
+4. TCP 的滑动窗口,流量控制,拥塞控制和连接管理
+5. TCP 的三次握手,四次挥手机制
+
+## 6. 应用层(Application Layer)
+
+
+
+### 6.1. 基本术语
+
+1. **域名系统(DNS)** :域名系统(DNS,Domain Name System)将人类可读的域名 (例如,www.baidu.com) 转换为机器可读的 IP 地址 (例如,220.181.38.148)。我们可以将其理解为专为互联网设计的电话薄。
+
+
+
+https://www.seobility.net/en/wiki/HTTP_headers
+
+2. **文件传输协议(FTP)** :FTP 是 File Transfer Protocol(文件传输协议)的英文简称,而中文简称为“文传协议”。用于 Internet 上的控制文件的双向传输。同时,它也是一个应用程序(Application)。基于不同的操作系统有不同的 FTP 应用程序,而所有这些应用程序都遵守同一种协议以传输文件。在 FTP 的使用当中,用户经常遇到两个概念:"下载"(Download)和"上传"(Upload)。 "下载"文件就是从远程主机拷贝文件至自己的计算机上;"上传"文件就是将文件从自己的计算机中拷贝至远程主机上。用 Internet 语言来说,用户可通过客户机程序向(从)远程主机上传(下载)文件。
+
+
+
+3. **简单文件传输协议(TFTP)** :TFTP(Trivial File Transfer Protocol,简单文件传输协议)是 TCP/IP 协议族中的一个用来在客户机与服务器之间进行简单文件传输的协议,提供不复杂、开销不大的文件传输服务。端口号为 69。
+4. **远程终端协议(TELNET)** :Telnet 协议是 TCP/IP 协议族中的一员,是 Internet 远程登陆服务的标准协议和主要方式。它为用户提供了在本地计算机上完成远程主机工作的能力。在终端使用者的电脑上使用 telnet 程序,用它连接到服务器。终端使用者可以在 telnet 程序中输入命令,这些命令会在服务器上运行,就像直接在服务器的控制台上输入一样。可以在本地就能控制服务器。要开始一个 telnet 会话,必须输入用户名和密码来登录服务器。Telnet 是常用的远程控制 Web 服务器的方法。
+5. **万维网(WWW)** :WWW 是环球信息网的缩写,(亦作“Web”、“WWW”、“'W3'”,英文全称为“World Wide Web”),中文名字为“万维网”,"环球网"等,常简称为 Web。分为 Web 客户端和 Web 服务器程序。WWW 可以让 Web 客户端(常用浏览器)访问浏览 Web 服务器上的页面。是一个由许多互相链接的超文本组成的系统,通过互联网访问。在这个系统中,每个有用的事物,称为一样“资源”;并且由一个全局“统一资源标识符”(URI)标识;这些资源通过超文本传输协议(Hypertext Transfer Protocol)传送给用户,而后者通过点击链接来获得资源。万维网联盟(英语:World Wide Web Consortium,简称 W3C),又称 W3C 理事会。1994 年 10 月在麻省理工学院(MIT)计算机科学实验室成立。万维网联盟的创建者是万维网的发明者蒂姆·伯纳斯-李。万维网并不等同互联网,万维网只是互联网所能提供的服务其中之一,是靠着互联网运行的一项服务。
+6. **万维网的大致工作工程:**
+
+
+
+7. **统一资源定位符(URL)** :统一资源定位符是对可以从互联网上得到的资源的位置和访问方法的一种简洁的表示,是互联网上标准资源的地址。互联网上的每个文件都有一个唯一的 URL,它包含的信息指出文件的位置以及浏览器应该怎么处理它。
+8. **超文本传输协议(HTTP)** :超文本传输协议(HTTP,HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议。所有的 WWW 文件都必须遵守这个标准。设计 HTTP 最初的目的是为了提供一种发布和接收 HTML 页面的方法。1960 年美国人 Ted Nelson 构思了一种通过计算机处理文本信息的方法,并称之为超文本(hypertext),这成为了 HTTP 超文本传输协议标准架构的发展根基。
+
+HTTP 协议的本质就是一种浏览器与服务器之间约定好的通信格式。HTTP 的原理如下图所示:
+
+
+
+10. **代理服务器(Proxy Server)** : 代理服务器(Proxy Server)是一种网络实体,它又称为万维网高速缓存。 代理服务器把最近的一些请求和响应暂存在本地磁盘中。当新请求到达时,若代理服务器发现这个请求与暂时存放的的请求相同,就返回暂存的响应,而不需要按 URL 的地址再次去互联网访问该资源。代理服务器可在客户端或服务器工作,也可以在中间系统工作。
+11. **简单邮件传输协议(SMTP)** : SMTP(Simple Mail Transfer Protocol)即简单邮件传输协议,它是一组用于由源地址到目的地址传送邮件的规则,由它来控制信件的中转方式。 SMTP 协议属于 TCP/IP 协议簇,它帮助每台计算机在发送或中转信件时找到下一个目的地。 通过 SMTP 协议所指定的服务器,就可以把 E-mail 寄到收信人的服务器上了,整个过程只要几分钟。SMTP 服务器则是遵循 SMTP 协议的发送邮件服务器,用来发送或中转发出的电子邮件。
+
+
+
+https://www.campaignmonitor.com/resources/knowledge-base/what-is-the-code-that-makes-bcc-or-cc-operate-in-an-email/
+
+11. **搜索引擎** :搜索引擎(Search Engine)是指根据一定的策略、运用特定的计算机程序从互联网上搜集信息,在对信息进行组织和处理后,为用户提供检索服务,将用户检索相关的信息展示给用户的系统。搜索引擎包括全文索引、目录索引、元搜索引擎、垂直搜索引擎、集合式搜索引擎、门户搜索引擎与免费链接列表等。
+
+
+
+12. **垂直搜索引擎** :垂直搜索引擎是针对某一个行业的专业搜索引擎,是搜索引擎的细分和延伸,是对网页库中的某类专门的信息进行一次整合,定向分字段抽取出需要的数据进行处理后再以某种形式返回给用户。垂直搜索是相对通用搜索引擎的信息量大、查询不准确、深度不够等提出来的新的搜索引擎服务模式,通过针对某一特定领域、某一特定人群或某一特定需求提供的有一定价值的信息和相关服务。其特点就是“专、精、深”,且具有行业色彩,相比较通用搜索引擎的海量信息无序化,垂直搜索引擎则显得更加专注、具体和深入。
+13. **全文索引** :全文索引技术是目前搜索引擎的关键技术。试想在 1M 大小的文件中搜索一个词,可能需要几秒,在 100M 的文件中可能需要几十秒,如果在更大的文件中搜索那么就需要更大的系统开销,这样的开销是不现实的。所以在这样的矛盾下出现了全文索引技术,有时候有人叫倒排文档技术。
+14. **目录索引** :目录索引( search index/directory),顾名思义就是将网站分门别类地存放在相应的目录中,因此用户在查询信息时,可选择关键词搜索,也可按分类目录逐层查找。
+
+### 6.2. 重要知识点总结
+
+1. 文件传输协议(FTP)使用 TCP 可靠的运输服务。FTP 使用客户服务器方式。一个 FTP 服务器进程可以同时为多个用户提供服务。在进行文件传输时,FTP 的客户和服务器之间要先建立两个并行的 TCP 连接:控制连接和数据连接。实际用于传输文件的是数据连接。
+2. 万维网客户程序与服务器之间进行交互使用的协议是超文本传输协议 HTTP。HTTP 使用 TCP 连接进行可靠传输。但 HTTP 本身是无连接、无状态的。HTTP/1.1 协议使用了持续连接(分为非流水线方式和流水线方式)
+3. 电子邮件把邮件发送到收件人使用的邮件服务器,并放在其中的收件人邮箱中,收件人可随时上网到自己使用的邮件服务器读取,相当于电子邮箱。
+4. 一个电子邮件系统有三个重要组成构件:用户代理、邮件服务器、邮件协议(包括邮件发送协议,如 SMTP,和邮件读取协议,如 POP3 和 IMAP)。用户代理和邮件服务器都要运行这些协议。
+
+### 6.3. 补充(重要)
+
+以下知识点需要重点关注:
+
+1. 应用层的常见协议(重点关注 HTTP 协议)
+2. 域名系统-从域名解析出 IP 地址
+3. 访问一个网站大致的过程
+4. 系统调用和应用编程接口概念
diff --git a/docs/cs-basics/operating-system/images/Linux-Logo.png b/docs/cs-basics/operating-system/images/Linux-Logo.png
new file mode 100644
index 00000000000..40e75aaac8a
Binary files /dev/null and b/docs/cs-basics/operating-system/images/Linux-Logo.png differ
diff --git "a/docs/cs-basics/operating-system/images/Linux\344\271\213\347\210\266.png" "b/docs/cs-basics/operating-system/images/Linux\344\271\213\347\210\266.png"
new file mode 100644
index 00000000000..33145373b68
Binary files /dev/null and "b/docs/cs-basics/operating-system/images/Linux\344\271\213\347\210\266.png" differ
diff --git "a/docs/cs-basics/operating-system/images/Linux\346\235\203\351\231\220\345\221\275\344\273\244.png" "b/docs/cs-basics/operating-system/images/Linux\346\235\203\351\231\220\345\221\275\344\273\244.png"
new file mode 100644
index 00000000000..f59b2e638c6
Binary files /dev/null and "b/docs/cs-basics/operating-system/images/Linux\346\235\203\351\231\220\345\221\275\344\273\244.png" differ
diff --git "a/docs/cs-basics/operating-system/images/Linux\346\235\203\351\231\220\350\247\243\350\257\273.png" "b/docs/cs-basics/operating-system/images/Linux\346\235\203\351\231\220\350\247\243\350\257\273.png"
new file mode 100644
index 00000000000..1292c125aca
Binary files /dev/null and "b/docs/cs-basics/operating-system/images/Linux\346\235\203\351\231\220\350\247\243\350\257\273.png" differ
diff --git "a/docs/cs-basics/operating-system/images/Linux\347\233\256\345\275\225\346\240\221.png" "b/docs/cs-basics/operating-system/images/Linux\347\233\256\345\275\225\346\240\221.png"
new file mode 100644
index 00000000000..beef42034bf
Binary files /dev/null and "b/docs/cs-basics/operating-system/images/Linux\347\233\256\345\275\225\346\240\221.png" differ
diff --git a/docs/cs-basics/operating-system/images/linux.png b/docs/cs-basics/operating-system/images/linux.png
new file mode 100644
index 00000000000..20ead246915
Binary files /dev/null and b/docs/cs-basics/operating-system/images/linux.png differ
diff --git a/docs/cs-basics/operating-system/images/macos.png b/docs/cs-basics/operating-system/images/macos.png
new file mode 100644
index 00000000000..332945774ee
Binary files /dev/null and b/docs/cs-basics/operating-system/images/macos.png differ
diff --git a/docs/cs-basics/operating-system/images/unix.png b/docs/cs-basics/operating-system/images/unix.png
new file mode 100644
index 00000000000..0afabcd8621
Binary files /dev/null and b/docs/cs-basics/operating-system/images/unix.png differ
diff --git a/docs/cs-basics/operating-system/images/windows.png b/docs/cs-basics/operating-system/images/windows.png
new file mode 100644
index 00000000000..c2687dc72f7
Binary files /dev/null and b/docs/cs-basics/operating-system/images/windows.png differ
diff --git "a/docs/cs-basics/operating-system/images/\344\277\256\346\224\271\346\226\207\344\273\266\346\235\203\351\231\220.png" "b/docs/cs-basics/operating-system/images/\344\277\256\346\224\271\346\226\207\344\273\266\346\235\203\351\231\220.png"
new file mode 100644
index 00000000000..de9409410c8
Binary files /dev/null and "b/docs/cs-basics/operating-system/images/\344\277\256\346\224\271\346\226\207\344\273\266\346\235\203\351\231\220.png" differ
diff --git "a/docs/cs-basics/operating-system/images/\346\226\207\344\273\266inode\344\277\241\346\201\257.png" "b/docs/cs-basics/operating-system/images/\346\226\207\344\273\266inode\344\277\241\346\201\257.png"
new file mode 100644
index 00000000000..b47551e8314
Binary files /dev/null and "b/docs/cs-basics/operating-system/images/\346\226\207\344\273\266inode\344\277\241\346\201\257.png" differ
diff --git "a/docs/cs-basics/operating-system/images/\347\224\250\346\210\267\346\200\201\344\270\216\345\206\205\346\240\270\346\200\201.png" "b/docs/cs-basics/operating-system/images/\347\224\250\346\210\267\346\200\201\344\270\216\345\206\205\346\240\270\346\200\201.png"
new file mode 100644
index 00000000000..aa0dafc2f02
Binary files /dev/null and "b/docs/cs-basics/operating-system/images/\347\224\250\346\210\267\346\200\201\344\270\216\345\206\205\346\240\270\346\200\201.png" differ
diff --git a/docs/cs-basics/operating-system/linux-intro.md b/docs/cs-basics/operating-system/linux-intro.md
new file mode 100644
index 00000000000..723cd9b7e88
--- /dev/null
+++ b/docs/cs-basics/operating-system/linux-intro.md
@@ -0,0 +1,412 @@
+---
+title: 后端程序员必备的 Linux 基础知识总结
+category: 计算机基础
+tag:
+ - 操作系统
+ - Linux
+---
+
+简单介绍一下 Java 程序员必知的 Linux 的一些概念以及常见命令。
+
+_如果文章有任何需要改善和完善的地方,欢迎在评论区指出,共同进步!笔芯!_
+
+## 1. 从认识操作系统开始
+
+
+
+正式开始 Linux 之前,简单花一点点篇幅科普一下操作系统相关的内容。
+
+### 1.1. 操作系统简介
+
+我通过以下四点介绍什么是操作系统:
+
+1. **操作系统(Operating System,简称 OS)是管理计算机硬件与软件资源的程序,是计算机的基石。**
+2. **操作系统本质上是一个运行在计算机上的软件程序 ,用于管理计算机硬件和软件资源。** 举例:运行在你电脑上的所有应用程序都通过操作系统来调用系统内存以及磁盘等等硬件。
+3. **操作系统存在屏蔽了硬件层的复杂性。** 操作系统就像是硬件使用的负责人,统筹着各种相关事项。
+4. **操作系统的内核(Kernel)是操作系统的核心部分,它负责系统的内存管理,硬件设备的管理,文件系统的管理以及应用程序的管理**。
+
+> 内核(Kernel)在后文中会提到。
+
+
+
+### 1.2. 操作系统简单分类
+
+#### 1.2.1. Windows
+
+目前最流行的个人桌面操作系统 ,不做多的介绍,大家都清楚。界面简单易操作,软件生态非常好。
+
+_玩玩电脑游戏还是必须要有 Windows 的,所以我现在是一台 Windows 用于玩游戏,一台 Mac 用于平时日常开发和学习使用。_
+
+
+
+#### 1.2.2. Unix
+
+最早的多用户、多任务操作系统 。后面崛起的 Linux 在很多方面都参考了 Unix。
+
+目前这款操作系统已经逐渐逐渐退出操作系统的舞台。
+
+
+
+#### 1.2.3. Linux
+
+**Linux 是一套免费使用、开源的类 Unix 操作系统。** Linux 存在着许多不同的发行版本,但它们都使用了 **Linux 内核** 。
+
+> 严格来讲,Linux 这个词本身只表示 Linux 内核,在 GNU/Linux 系统中,Linux 实际就是 Linux 内核,而该系统的其余部分主要是由 GNU 工程编写和提供的程序组成。单独的 Linux 内核并不能成为一个可以正常工作的操作系统。
+>
+> **很多人更倾向使用 “GNU/Linux” 一词来表达人们通常所说的 “Linux”。**
+
+
+
+#### 1.2.4. Mac OS
+
+苹果自家的操作系统,编程体验和 Linux 相当,但是界面、软件生态以及用户体验各方面都要比 Linux 操作系统更好。
+
+
+
+### 1.3. 操作系统的内核(Kernel)
+
+我们先来看看维基百科对于内核的解释,我觉得总结的非常好!
+
+> **内核**(英语:Kernel,又称核心)在计算机科学中是一个用来管理软件发出的数据 I/O(输入与输出)要求的电脑程序,将这些要求转译为数据处理的指令并交由中央处理器(CPU)及电脑中其他电子组件进行处理,是现代操作系统中最基本的部分。它是为众多应用程序提供对计算机硬件的安全访问的一部分软件,这种访问是有限的,并由内核决定一个程序在什么时候对某部分硬件操作多长时间。 **直接对硬件操作是非常复杂的。所以内核通常提供一种硬件抽象的方法,来完成这些操作。有了这个,通过进程间通信机制及系统调用,应用进程可间接控制所需的硬件资源(特别是处理器及 IO 设备)。**
+>
+> 早期计算机系统的设计中,还没有操作系统的内核这个概念。随着计算机系统的发展,操作系统内核的概念才渐渐明晰起来了!
+
+简单概括两点:
+
+1. **操作系统的内核(Kernel)是操作系统的核心部分,它负责系统的内存管理,硬件设备的管理,文件系统的管理以及应用程序的管理。**
+2. **操作系统的内核是连接应用程序和硬件的桥梁,决定着操作系统的性能和稳定性。**
+
+### 1.4. 中央处理器(CPU,Central Processing Unit)
+
+关于 CPU 简单概括三点:
+
+1. **CPU 是一台计算机的运算核心(Core)+控制核心( Control Unit),可以称得上是计算机的大脑。**
+2. **CPU 主要包括两个部分:控制器+运算器。**
+3. **CPU 的根本任务就是执行指令,对计算机来说最终都是一串由“0”和“1”组成的序列。**
+
+### 1.5. CPU vs Kernel(内核)
+
+很多人容易无法区分操作系统的内核(Kernel)和中央处理器(CPU),你可以简单从下面两点来区别:
+
+1. 操作系统的内核(Kernel)属于操作系统层面,而 CPU 属于硬件。
+2. CPU 主要提供运算,处理各种指令的能力。内核(Kernel)主要负责系统管理比如内存管理,它屏蔽了对硬件的操作。
+
+下图清晰说明了应用程序、内核、CPU 这三者的关系。
+
+
+
+### 1.6. 系统调用
+
+介绍系统调用之前,我们先来了解一下用户态和系统态。
+
+根据进程访问资源的特点,我们可以把进程在系统上的运行分为两个级别:
+
+1. **用户态(user mode)** : 用户态运行的进程或可以直接读取用户程序的数据。
+2. **系统态(kernel mode)**: 可以简单的理解系统态运行的进程或程序几乎可以访问计算机的任何资源,不受限制。
+
+**说了用户态和系统态之后,那么什么是系统调用呢?**
+
+我们运行的程序基本都是运行在用户态,如果我们调用操作系统提供的系统态级别的子功能咋办呢?那就需要系统调用了!
+
+也就是说在我们运行的用户程序中,凡是与系统态级别的资源有关的操作(如文件管理、进程控制、内存管理等),都必须通过系统调用方式向操作系统提出服务请求,并由操作系统代为完成。
+
+这些系统调用按功能大致可分为如下几类:
+
+- **设备管理** :完成设备的请求或释放,以及设备启动等功能。
+- **文件管理** :完成文件的读、写、创建及删除等功能。
+- **进程控制** :完成进程的创建、撤销、阻塞及唤醒等功能。
+- **进程通信** :完成进程之间的消息传递或信号传递等功能。
+- **内存管理** :完成内存的分配、回收以及获取作业占用内存区大小及地址等功能。
+
+我在网上找了一个图,通过这个图可以很清晰的说明用户程序、系统调用、内核和硬件之间的关系。(_太难了~木有自己画_)
+
+
+
+## 2. 初探 Linux
+
+### 2.1. Linux 简介
+
+我们上面已经简单了 Linux,这里只强调三点。
+
+- **类 Unix 系统** : Linux 是一种自由、开放源码的类似 Unix 的操作系统
+- **Linux 本质是指 Linux 内核** : 严格来讲,Linux 这个词本身只表示 Linux 内核,单独的 Linux 内核并不能成为一个可以正常工作的操作系统。所以,就有了各种 Linux 发行版。
+- **Linux 之父(林纳斯·本纳第克特·托瓦兹 Linus Benedict Torvalds)** : 一个编程领域的传奇式人物,真大佬!我辈崇拜敬仰之楷模。他是 **Linux 内核** 的最早作者,随后发起了这个开源项目,担任 Linux 内核的首要架构师。他还发起了 Git 这个开源项目,并为主要的开发者。
+
+
+
+### 2.2. Linux 诞生
+
+1989 年,Linus Torvalds 进入芬兰陆军新地区旅,服 11 个月的国家义务兵役,军衔为少尉,主要服务于计算机部门,任务是弹道计算。服役期间,购买了安德鲁·斯图尔特·塔能鲍姆所著的教科书及 minix 源代码,开始研究操作系统。1990 年,他退伍后回到大学,开始接触 Unix。
+
+> **Minix** 是一个迷你版本的类 Unix 操作系统,由塔能鲍姆教授为了教学之用而创作,采用微核心设计。它启发了 Linux 内核的创作。
+
+1991 年,Linus Torvalds 开源了 Linux 内核。Linux 以一只可爱的企鹅作为标志,象征着敢作敢为、热爱生活。
+
+
+
+### 2.3. 常见 Linux 发行版本有哪些?
+
+Linus Torvalds 开源的只是 Linux 内核,我们上面也提到了操作系统内核的作用。一些组织或厂商将 Linux 内核与各种软件和文档包装起来,并提供系统安装界面和系统配置、设定与管理工具,就构成了 Linux 的发行版本。
+
+> 内核主要负责系统的内存管理,硬件设备的管理,文件系统的管理以及应用程序的管理。
+
+Linux 的发行版本可以大体分为两类:
+
+- 商业公司维护的发行版本,以著名的 Red Hat 为代表,比较典型的有 CentOS 。
+- 社区组织维护的发行版本,以 Debian 为代表,比较典型的有 Ubuntu、Debian。
+
+对于初学者学习 Linux ,推荐选择 CentOS 。
+
+## 3. Linux 文件系统概览
+
+### 3.1. Linux 文件系统简介
+
+**在 Linux 操作系统中,所有被操作系统管理的资源,例如网络接口卡、磁盘驱动器、打印机、输入输出设备、普通文件或是目录都被看作是一个文件。** 也就是说在 Linux 系统中有一个重要的概念:**一切都是文件**。
+
+其实这是 UNIX 哲学的一个体现,在 UNIX 系统中,把一切资源都看作是文件,Linux 的文件系统也是借鉴 UNIX 文件系统而来。
+
+### 3.2. inode 介绍
+
+**inode 是 linux/unix 文件系统的基础。那么,inode 是什么?有什么作用呢?**
+
+硬盘的最小存储单位是扇区(Sector),块(block)由多个扇区组成。文件数据存储在块中。块的最常见的大小是 4kb,约为 8 个连续的扇区组成(每个扇区存储 512 字节)。一个文件可能会占用多个 block,但是一个块只能存放一个文件。
+
+虽然,我们将文件存储在了块(block)中,但是我们还需要一个空间来存储文件的 **元信息 metadata** :如某个文件被分成几块、每一块在的地址、文件拥有者,创建时间,权限,大小等。这种 **存储文件元信息的区域就叫 inode**,译为索引节点:**i(index)+node**。 每个文件都有一个 inode,存储文件的元信息。
+
+可以使用 `stat` 命令可以查看文件的 inode 信息。每个 inode 都有一个号码,Linux/Unix 操作系统不使用文件名来区分文件,而是使用 inode 号码区分不同的文件。
+
+简单来说:inode 就是用来维护某个文件被分成几块、每一块在的地址、文件拥有者,创建时间,权限,大小等信息。
+
+简单总结一下:
+
+- **inode** :记录文件的属性信息,可以使用 stat 命令查看 inode 信息。
+- **block** :实际文件的内容,如果一个文件大于一个块时候,那么将占用多个 block,但是一个块只能存放一个文件。(因为数据是由 inode 指向的,如果有两个文件的数据存放在同一个块中,就会乱套了)
+
+
+
+### 3.3. Linux 文件类型
+
+Linux 支持很多文件类型,其中非常重要的文件类型有: **普通文件**,**目录文件**,**链接文件**,**设备文件**,**管道文件**,**Socket 套接字文件**等。
+
+- **普通文件(-)** : 用于存储信息和数据, Linux 用户可以根据访问权限对普通文件进行查看、更改和删除。比如:图片、声音、PDF、text、视频、源代码等等。
+- **目录文件(d,directory file)** :目录也是文件的一种,用于表示和管理系统中的文件,目录文件中包含一些文件名和子目录名。打开目录事实上就是打开目录文件。
+- **符号链接文件(l,symbolic link)** :保留了指向文件的地址而不是文件本身。
+- **字符设备(c,char)** :用来访问字符设备比如键盘。
+- **设备文件(b,block)** : 用来访问块设备比如硬盘、软盘。
+- **管道文件(p,pipe)** : 一种特殊类型的文件,用于进程之间的通信。
+- **套接字(s,socket)** :用于进程间的网络通信,也可以用于本机之间的非网络通信。
+
+### 3.4. Linux 目录树
+
+所有可操作的计算机资源都存在于目录树这个结构中,对计算资源的访问,可以看做是对这棵目录树的访问。
+
+**Linux 的目录结构如下:**
+
+Linux 文件系统的结构层次鲜明,就像一棵倒立的树,最顶层是其根目录:
+
+
+**常见目录说明:**
+
+- **/bin:** 存放二进制可执行文件(ls、cat、mkdir 等),常用命令一般都在这里;
+- **/etc:** 存放系统管理和配置文件;
+- **/home:** 存放所有用户文件的根目录,是用户主目录的基点,比如用户 user 的主目录就是/home/user,可以用~user 表示;
+- **/usr :** 用于存放系统应用程序;
+- **/opt:** 额外安装的可选应用程序包所放置的位置。一般情况下,我们可以把 tomcat 等都安装到这里;
+- **/proc:** 虚拟文件系统目录,是系统内存的映射。可直接访问这个目录来获取系统信息;
+- **/root:** 超级用户(系统管理员)的主目录(特权阶级^o^);
+- **/sbin:** 存放二进制可执行文件,只有 root 才能访问。这里存放的是系统管理员使用的系统级别的管理命令和程序。如 ifconfig 等;
+- **/dev:** 用于存放设备文件;
+- **/mnt:** 系统管理员安装临时文件系统的安装点,系统提供这个目录是让用户临时挂载其他的文件系统;
+- **/boot:** 存放用于系统引导时使用的各种文件;
+- **/lib :** 存放着和系统运行相关的库文件 ;
+- **/tmp:** 用于存放各种临时文件,是公用的临时文件存储点;
+- **/var:** 用于存放运行时需要改变数据的文件,也是某些大文件的溢出区,比方说各种服务的日志文件(系统启动日志等。)等;
+- **/lost+found:** 这个目录平时是空的,系统非正常关机而留下“无家可归”的文件(windows 下叫什么.chk)就在这里。
+
+## 4. Linux 基本命令
+
+下面只是给出了一些比较常用的命令。推荐一个 Linux 命令快查网站,非常不错,大家如果遗忘某些命令或者对某些命令不理解都可以在这里得到解决。
+
+Linux 命令大全:[http://man.linuxde.net/](http://man.linuxde.net/)
+
+### 4.1. 目录切换命令
+
+- **`cd usr`:** 切换到该目录下 usr 目录
+- **`cd ..(或cd../)`:** 切换到上一层目录
+- **`cd /`:** 切换到系统根目录
+- **`cd ~`:** 切换到用户主目录
+- **`cd -`:** 切换到上一个操作所在目录
+
+### 4.2. 目录的操作命令(增删改查)
+
+- **`mkdir 目录名称`:** 增加目录。
+- **`ls/ll`**(ll 是 ls -l 的别名,ll 命令可以看到该目录下的所有目录和文件的详细信息):查看目录信息。
+- **`find 目录 参数`:** 寻找目录(查)。示例:① 列出当前目录及子目录下所有文件和文件夹: `find .`;② 在`/home`目录下查找以.txt 结尾的文件名:`find /home -name "*.txt"` ,忽略大小写: `find /home -iname "*.txt"` ;③ 当前目录及子目录下查找所有以.txt 和.pdf 结尾的文件:`find . \( -name "*.txt" -o -name "*.pdf" \)`或`find . -name "*.txt" -o -name "*.pdf"`。
+- **`mv 目录名称 新目录名称`:** 修改目录的名称(改)。注意:mv 的语法不仅可以对目录进行重命名而且也可以对各种文件,压缩包等进行 重命名的操作。mv 命令用来对文件或目录重新命名,或者将文件从一个目录移到另一个目录中。后面会介绍到 mv 命令的另一个用法。
+- **`mv 目录名称 目录的新位置`:** 移动目录的位置---剪切(改)。注意:mv 语法不仅可以对目录进行剪切操作,对文件和压缩包等都可执行剪切操作。另外 mv 与 cp 的结果不同,mv 好像文件“搬家”,文件个数并未增加。而 cp 对文件进行复制,文件个数增加了。
+- **`cp -r 目录名称 目录拷贝的目标位置`:** 拷贝目录(改),-r 代表递归拷贝 。注意:cp 命令不仅可以拷贝目录还可以拷贝文件,压缩包等,拷贝文件和压缩包时不 用写-r 递归。
+- **`rm [-rf] 目录` :** 删除目录(删)。注意:rm 不仅可以删除目录,也可以删除其他文件或压缩包,为了增强大家的记忆, 无论删除任何目录或文件,都直接使用`rm -rf` 目录/文件/压缩包。
+
+### 4.3. 文件的操作命令(增删改查)
+
+- **`touch 文件名称`:** 文件的创建(增)。
+- **`cat/more/less/tail 文件名称`** :文件的查看(查) 。命令 `tail -f 文件` 可以对某个文件进行动态监控,例如 tomcat 的日志文件, 会随着程序的运行,日志会变化,可以使用 `tail -f catalina-2016-11-11.log` 监控 文 件的变化 。
+- **`vim 文件`:** 修改文件的内容(改)。vim 编辑器是 Linux 中的强大组件,是 vi 编辑器的加强版,vim 编辑器的命令和快捷方式有很多,但此处不一一阐述,大家也无需研究的很透彻,使用 vim 编辑修改文件的方式基本会使用就可以了。在实际开发中,使用 vim 编辑器主要作用就是修改配置文件,下面是一般步骤: `vim 文件------>进入文件----->命令模式------>按i进入编辑模式----->编辑文件 ------->按Esc进入底行模式----->输入:wq/q!` (输入 wq 代表写入内容并退出,即保存;输入 q!代表强制退出不保存)。
+- **`rm -rf 文件`:** 删除文件(删)。
+
+### 4.4. 压缩文件的操作命令
+
+**1)打包并压缩文件:**
+
+Linux 中的打包文件一般是以.tar 结尾的,压缩的命令一般是以.gz 结尾的。而一般情况下打包和压缩是一起进行的,打包并压缩后的文件的后缀名一般.tar.gz。
+命令:`tar -zcvf 打包压缩后的文件名 要打包压缩的文件` ,其中:
+
+- z:调用 gzip 压缩命令进行压缩
+- c:打包文件
+- v:显示运行过程
+- f:指定文件名
+
+比如:假如 test 目录下有三个文件分别是:aaa.txt bbb.txt ccc.txt,如果我们要打包 test 目录并指定压缩后的压缩包名称为 test.tar.gz 可以使用命令:**`tar -zcvf test.tar.gz aaa.txt bbb.txt ccc.txt` 或 `tar -zcvf test.tar.gz /test/`**
+
+**2)解压压缩包:**
+
+命令:`tar [-xvf] 压缩文件`
+
+其中:x:代表解压
+
+示例:
+
+- 将 /test 下的 test.tar.gz 解压到当前目录下可以使用命令:**`tar -xvf test.tar.gz`**
+- 将 /test 下的 test.tar.gz 解压到根目录/usr 下:**`tar -xvf test.tar.gz -C /usr`**(- C 代表指定解压的位置)
+
+### 4.5. Linux 的权限命令
+
+操作系统中每个文件都拥有特定的权限、所属用户和所属组。权限是操作系统用来限制资源访问的机制,在 Linux 中权限一般分为读(readable)、写(writable)和执行(excutable),分为三组。分别对应文件的属主(owner),属组(group)和其他用户(other),通过这样的机制来限制哪些用户、哪些组可以对特定的文件进行什么样的操作。
+
+通过 **`ls -l`** 命令我们可以 查看某个目录下的文件或目录的权限
+
+示例:在随意某个目录下`ls -l`
+
+
+
+第一列的内容的信息解释如下:
+
+
+
+> 下面将详细讲解文件的类型、Linux 中权限以及文件有所有者、所在组、其它组具体是什么?
+
+**文件的类型:**
+
+- d: 代表目录
+- -: 代表文件
+- l: 代表软链接(可以认为是 window 中的快捷方式)
+
+**Linux 中权限分为以下几种:**
+
+- r:代表权限是可读,r 也可以用数字 4 表示
+- w:代表权限是可写,w 也可以用数字 2 表示
+- x:代表权限是可执行,x 也可以用数字 1 表示
+
+**文件和目录权限的区别:**
+
+对文件和目录而言,读写执行表示不同的意义。
+
+对于文件:
+
+| 权限名称 | 可执行操作 |
+| :------- | --------------------------: |
+| r | 可以使用 cat 查看文件的内容 |
+| w | 可以修改文件的内容 |
+| x | 可以将其运行为二进制文件 |
+
+对于目录:
+
+| 权限名称 | 可执行操作 |
+| :------- | -----------------------: |
+| r | 可以查看目录下列表 |
+| w | 可以创建和删除目录下文件 |
+| x | 可以使用 cd 进入目录 |
+
+需要注意的是: **超级用户可以无视普通用户的权限,即使文件目录权限是 000,依旧可以访问。**
+
+**在 linux 中的每个用户必须属于一个组,不能独立于组外。在 linux 中每个文件有所有者、所在组、其它组的概念。**
+
+- **所有者(u)** :一般为文件的创建者,谁创建了该文件,就天然的成为该文件的所有者,用 `ls ‐ahl` 命令可以看到文件的所有者 也可以使用 chown 用户名 文件名来修改文件的所有者 。
+- **文件所在组(g)** :当某个用户创建了一个文件后,这个文件的所在组就是该用户所在的组用 `ls ‐ahl`命令可以看到文件的所有组也可以使用 chgrp 组名 文件名来修改文件所在的组。
+- **其它组(o)** :除开文件的所有者和所在组的用户外,系统的其它用户都是文件的其它组。
+
+> 我们再来看看如何修改文件/目录的权限。
+
+**修改文件/目录的权限的命令:`chmod`**
+
+示例:修改/test 下的 aaa.txt 的权限为文件所有者有全部权限,文件所有者所在的组有读写权限,其他用户只有读的权限。
+
+**`chmod u=rwx,g=rw,o=r aaa.txt`** 或者 **`chmod 764 aaa.txt`**
+
+
+
+**补充一个比较常用的东西:**
+
+假如我们装了一个 zookeeper,我们每次开机到要求其自动启动该怎么办?
+
+1. 新建一个脚本 zookeeper
+2. 为新建的脚本 zookeeper 添加可执行权限,命令是:`chmod +x zookeeper`
+3. 把 zookeeper 这个脚本添加到开机启动项里面,命令是:`chkconfig --add zookeeper`
+4. 如果想看看是否添加成功,命令是:`chkconfig --list`
+
+### 4.6. Linux 用户管理
+
+Linux 系统是一个多用户多任务的分时操作系统,任何一个要使用系统资源的用户,都必须首先向系统管理员申请一个账号,然后以这个账号的身份进入系统。
+
+用户的账号一方面可以帮助系统管理员对使用系统的用户进行跟踪,并控制他们对系统资源的访问;另一方面也可以帮助用户组织文件,并为用户提供安全性保护。
+
+**Linux 用户管理相关命令:**
+
+- `useradd 选项 用户名`:添加用户账号
+- `userdel 选项 用户名`:删除用户帐号
+- `usermod 选项 用户名`:修改帐号
+- `passwd 用户名`:更改或创建用户的密码
+- `passwd -S 用户名` :显示用户账号密码信息
+- `passwd -d 用户名`: 清除用户密码
+
+`useradd` 命令用于 Linux 中创建的新的系统用户。`useradd`可用来建立用户帐号。帐号建好之后,再用`passwd`设定帐号的密码.而可用`userdel`删除帐号。使用`useradd`指令所建立的帐号,实际上是保存在 `/etc/passwd`文本文件中。
+
+`passwd`命令用于设置用户的认证信息,包括用户密码、密码过期时间等。系统管理者则能用它管理系统用户的密码。只有管理者可以指定用户名称,一般用户只能变更自己的密码。
+
+### 4.7. Linux 系统用户组的管理
+
+每个用户都有一个用户组,系统可以对一个用户组中的所有用户进行集中管理。不同 Linux 系统对用户组的规定有所不同,如 Linux 下的用户属于与它同名的用户组,这个用户组在创建用户时同时创建。
+
+用户组的管理涉及用户组的添加、删除和修改。组的增加、删除和修改实际上就是对`/etc/group`文件的更新。
+
+**Linux 系统用户组的管理相关命令:**
+
+- `groupadd 选项 用户组` :增加一个新的用户组
+- `groupdel 用户组`:要删除一个已有的用户组
+- `groupmod 选项 用户组` : 修改用户组的属性
+
+### 4.8. 其他常用命令
+
+- **`pwd`:** 显示当前所在位置
+
+- `sudo + 其他命令`:以系统管理者的身份执行指令,也就是说,经由 sudo 所执行的指令就好像是 root 亲自执行。
+
+- **`grep 要搜索的字符串 要搜索的文件 --color`:** 搜索命令,--color 代表高亮显示
+
+- **`ps -ef`/`ps -aux`:** 这两个命令都是查看当前系统正在运行进程,两者的区别是展示格式不同。如果想要查看特定的进程可以使用这样的格式:**`ps aux|grep redis`** (查看包括 redis 字符串的进程),也可使用 `pgrep redis -a`。
+
+ 注意:如果直接用 ps((Process Status))命令,会显示所有进程的状态,通常结合 grep 命令查看某进程的状态。
+
+- **`kill -9 进程的pid`:** 杀死进程(-9 表示强制终止。)
+
+ 先用 ps 查找进程,然后用 kill 杀掉
+
+- **网络通信命令:**
+ - 查看当前系统的网卡信息:ifconfig
+ - 查看与某台机器的连接情况:ping
+ - 查看当前系统的端口使用:netstat -an
+- **net-tools 和 iproute2 :**
+ `net-tools`起源于 BSD 的 TCP/IP 工具箱,后来成为老版本 LinuxLinux 中配置网络功能的工具。但自 2001 年起,Linux 社区已经对其停止维护。同时,一些 Linux 发行版比如 Arch Linux 和 CentOS/RHEL 7 则已经完全抛弃了 net-tools,只支持`iproute2`。linux ip 命令类似于 ifconfig,但功能更强大,旨在替代它。更多详情请阅读[如何在 Linux 中使用 IP 命令和示例](https://linoxide.com/linux-command/use-ip-command-linux)
+- **`shutdown`:** `shutdown -h now`: 指定现在立即关机;`shutdown +5 "System will shutdown after 5 minutes"`:指定 5 分钟后关机,同时送出警告信息给登入用户。
+
+- **`reboot`:** **`reboot`:** 重开机。**`reboot -w`:** 做个重开机的模拟(只有纪录并不会真的重开机)。
diff --git a/docs/operating-system/Shell.md b/docs/cs-basics/operating-system/shell-intro.md
similarity index 78%
rename from docs/operating-system/Shell.md
rename to docs/cs-basics/operating-system/shell-intro.md
index 4a89061fc27..074f7bfbe55 100644
--- a/docs/operating-system/Shell.md
+++ b/docs/cs-basics/operating-system/shell-intro.md
@@ -1,33 +1,10 @@
-
-
-
-- [Shell 编程入门](#shell-编程入门)
- - [走进 Shell 编程的大门](#走进-shell-编程的大门)
- - [为什么要学Shell?](#为什么要学shell)
- - [什么是 Shell?](#什么是-shell)
- - [Shell 编程的 Hello World](#shell-编程的-hello-world)
- - [Shell 变量](#shell-变量)
- - [Shell 编程中的变量介绍](#shell-编程中的变量介绍)
- - [Shell 字符串入门](#shell-字符串入门)
- - [Shell 字符串常见操作](#shell-字符串常见操作)
- - [Shell 数组](#shell-数组)
- - [Shell 基本运算符](#shell-基本运算符)
- - [算数运算符](#算数运算符)
- - [关系运算符](#关系运算符)
- - [逻辑运算符](#逻辑运算符)
- - [布尔运算符](#布尔运算符)
- - [字符串运算符](#字符串运算符)
- - [文件相关运算符](#文件相关运算符)
- - [shell流程控制](#shell流程控制)
- - [if 条件语句](#if-条件语句)
- - [for 循环语句](#for-循环语句)
- - [while 语句](#while-语句)
- - [shell 函数](#shell-函数)
- - [不带参数没有返回值的函数](#不带参数没有返回值的函数)
- - [有返回值的函数](#有返回值的函数)
- - [带参数的函数](#带参数的函数)
-
-
+---
+title: Shell 编程入门
+category: 计算机基础
+tag:
+ - 操作系统
+ - Linux
+---
# Shell 编程入门
@@ -45,20 +22,19 @@
另外,了解 shell 编程也是大部分互联网公司招聘后端开发人员的要求。下图是我截取的一些知名互联网公司对于 Shell 编程的要求。
-
+
### 什么是 Shell?
简单来说“Shell编程就是对一堆Linux命令的逻辑化处理”。
-
W3Cschool 上的一篇文章是这样介绍 Shell的,如下图所示。
-
+
### Shell 编程的 Hello World
-学习任何一门编程语言第一件事就是输出HelloWord了!下面我会从新建文件到shell代码编写来说下Shell 编程如何输出Hello World。
+学习任何一门编程语言第一件事就是输出HelloWorld了!下面我会从新建文件到shell代码编写来说下Shell 编程如何输出Hello World。
(1)新建一个文件 helloworld.sh :`touch helloworld.sh`,扩展名为 sh(sh代表Shell)(扩展名并不影响脚本执行,见名知意就好,如果你用 php 写 shell 脚本,扩展名就用 php 好了)
@@ -80,7 +56,7 @@ shell中 # 符号表示注释。**shell 的第一行比较特殊,一般都会
(4) 运行脚本:`./helloworld.sh` 。(注意,一定要写成 `./helloworld.sh` ,而不是 `helloworld.sh` ,运行其它二进制的程序也一样,直接写 `helloworld.sh` ,linux 系统会去 PATH 里寻找有没有叫 helloworld.sh 的,而只有 /bin, /sbin, /usr/bin,/usr/sbin 等在 PATH 里,你的当前目录通常不在 PATH 里,所以写成 `helloworld.sh` 是会找不到命令的,要用`./helloworld.sh` 告诉系统说,就在当前目录找。)
-
+
## Shell 变量
@@ -91,18 +67,18 @@ shell中 # 符号表示注释。**shell 的第一行比较特殊,一般都会
**Shell编程中一般分为三种变量:**
1. **我们自己定义的变量(自定义变量):** 仅在当前 Shell 实例中有效,其他 Shell 启动的程序不能访问局部变量。
-2. **Linux已定义的环境变量**(环境变量, 例如:$PATH, $HOME 等..., 这类变量我们可以直接使用),使用 `env` 命令可以查看所有的环境变量,而set命令既可以查看环境变量也可以查看自定义变量。
+2. **Linux已定义的环境变量**(环境变量, 例如:`PATH`, `HOME` 等..., 这类变量我们可以直接使用),使用 `env` 命令可以查看所有的环境变量,而set命令既可以查看环境变量也可以查看自定义变量。
3. **Shell变量** :Shell变量是由 Shell 程序设置的特殊变量。Shell 变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了 Shell 的正常运行
**常用的环境变量:**
-> PATH 决定了shell将到哪些目录中寻找命令或程序
-HOME 当前用户主目录
-HISTSIZE 历史记录数
-LOGNAME 当前用户的登录名
-HOSTNAME 指主机的名称
-SHELL 当前用户Shell类型
-LANGUGE 语言相关的环境变量,多语言可以修改此环境变量
-MAIL 当前用户的邮件存放目录
+> PATH 决定了shell将到哪些目录中寻找命令或程序
+HOME 当前用户主目录
+HISTSIZE 历史记录数
+LOGNAME 当前用户的登录名
+HOSTNAME 指主机的名称
+SHELL 当前用户Shell类型
+LANGUAGE 语言相关的环境变量,多语言可以修改此环境变量
+MAIL 当前用户的邮件存放目录
PS1 基本提示符,对于root用户是#,对于普通用户是$
**使用 Linux 已定义的环境变量:**
@@ -118,7 +94,7 @@ hello="hello world"
echo $hello
echo "helloworld!"
```
-
+
**Shell 编程中的变量名的命名的注意事项:**
@@ -183,7 +159,7 @@ echo $greeting_2 $greeting_3
输出结果:
-
+
**获取字符串长度:**
@@ -234,13 +210,17 @@ echo ${str:0:10} #输出:SnailClimb
#!bin/bash
#author:amau
-var="http://www.runoob.com/linux/linux-shell-variable.html"
-
-s1=${var%%t*}#h
-s2=${var%t*}#http://www.runoob.com/linux/linux-shell-variable.h
-s3=${var%%.*}#http://www
-s4=${var#*/}#/www.runoob.com/linux/linux-shell-variable.html
-s5=${var##*/}#linux-shell-variable.html
+var="https://www.runoob.com/linux/linux-shell-variable.html"
+# %表示删除从后匹配, 最短结果
+# %%表示删除从后匹配, 最长匹配结果
+# #表示删除从头匹配, 最短结果
+# ##表示删除从头匹配, 最长匹配结果
+# 注: *为通配符, 意为匹配任意数量的任意字符
+s1=${var%%t*} #h
+s2=${var%t*} #https://www.runoob.com/linux/linux-shell-variable.h
+s3=${var%%.*} #http://www
+s4=${var#*/} #/www.runoob.com/linux/linux-shell-variable.html
+s5=${var##*/} #linux-shell-variable.html
```
### Shell 数组
@@ -262,7 +242,7 @@ echo $length2 #输出:5
echo ${array[2]} #输出:3
unset array[1]# 删除下标为1的元素也就是删除第二个元素
for i in ${array[@]};do echo $i ;done # 遍历数组,输出: 1 3 4 5
-unset arr_number; # 删除数组中的所有元素
+unset array; # 删除数组中的所有元素
for i in ${array[@]};do echo $i ;done # 遍历数组,数组元素为空,没有任何输出内容
```
@@ -281,7 +261,7 @@ for i in ${array[@]};do echo $i ;done # 遍历数组,数组元素为空,没
### 算数运算符
-
+
我以加法运算符做一个简单的示例(注意:不是单引号,是反引号):
@@ -298,7 +278,7 @@ echo "Total value : $val"
关系运算符只支持数字,不支持字符串,除非字符串的值是数字。
-
+
通过一个简单的示例演示关系运算符的使用,下面shell程序的作用是当score=100的时候输出A否则输出B。
@@ -322,7 +302,7 @@ B
### 逻辑运算符
-
+
示例:
@@ -336,18 +316,17 @@ echo $a;
### 布尔运算符
-
+
这里就不做演示了,应该挺简单的。
### 字符串运算符
-
+
简单示例:
```shell
-
#!/bin/bash
a="abc";
b="efg";
@@ -366,7 +345,7 @@ a 不等于 b
### 文件相关运算符
-
+
使用方式很简单,比如我们定义好了一个文件路径`file="/usr/learnshell/test.sh"` 如果我们想判断这个文件是否可读,可以这样`if [ -r $file ]` 如果想判断这个文件是否可写,可以这样`-w $file`,是不是很简单。
@@ -530,8 +509,6 @@ echo "输入的两个数字之和为 $?"
### 带参数的函数
-
-
```shell
#!/bin/bash
funWithParam(){
@@ -544,7 +521,6 @@ funWithParam(){
echo "作为一个字符串输出所有参数 $* !"
}
funWithParam 1 2 3 4 5 6 7 8 9 34 73
-
```
输出结果:
@@ -557,5 +533,4 @@ funWithParam 1 2 3 4 5 6 7 8 9 34 73
第十一个参数为 73 !
参数总数有 11 个!
作为一个字符串输出所有参数 1 2 3 4 5 6 7 8 9 34 73 !
-
```
diff --git "a/docs/cs-basics/operating-system/\346\223\215\344\275\234\347\263\273\347\273\237\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230&\347\237\245\350\257\206\347\202\271\346\200\273\347\273\223.md" "b/docs/cs-basics/operating-system/\346\223\215\344\275\234\347\263\273\347\273\237\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230&\347\237\245\350\257\206\347\202\271\346\200\273\347\273\223.md"
new file mode 100644
index 00000000000..db158c68faf
--- /dev/null
+++ "b/docs/cs-basics/operating-system/\346\223\215\344\275\234\347\263\273\347\273\237\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230&\347\237\245\350\257\206\347\202\271\346\200\273\347\273\223.md"
@@ -0,0 +1,373 @@
+---
+title: 操作系统常见面试题总结
+category: 计算机基础
+tag:
+ - 操作系统
+---
+
+大家好,我是 Guide 哥!
+
+很多读者抱怨计算操作系统的知识点比较繁杂,自己也没有多少耐心去看,但是面试的时候又经常会遇到。所以,我带着我整理好的操作系统的常见问题来啦!这篇文章总结了一些我觉得比较重要的操作系统相关的问题比如**进程管理**、**内存管理**、**虚拟内存**等等。
+
+文章形式通过大部分比较喜欢的面试官和求职者之间的对话形式展开。另外,Guide哥 也只是在大学的时候学习过操作系统,不过基本都忘了,为了写这篇文章这段时间看了很多相关的书籍和博客。如果文中有任何需要补充和完善的地方,你都可以在 issue 中指出!
+
+这篇文章只是对一些操作系统比较重要概念的一个概览,深入学习的话,建议大家还是老老实实地去看书。另外, 这篇文章的很多内容参考了《现代操作系统》第三版这本书,非常感谢。
+
+开始本文的内容之前,我们先聊聊为什么要学习操作系统。
+
+- **从对个人能力方面提升来说** :操作系统中的很多思想、很多经典的算法,你都可以在我们日常开发使用的各种工具或者框架中找到它们的影子。比如说我们开发的系统使用的缓存(比如 Redis)和操作系统的高速缓存就很像。CPU 中的高速缓存有很多种,不过大部分都是为了解决 CPU 处理速度和内存处理速度不对等的问题。我们还可以把内存看作外存的高速缓存,程序运行的时候我们把外存的数据复制到内存,由于内存的处理速度远远高于外存,这样提高了处理速度。同样地,我们使用的 Redis 缓存就是为了解决程序处理速度和访问常规关系型数据库速度不对等的问题。高速缓存一般会按照局部性原理(2-8 原则)根据相应的淘汰算法保证缓存中的数据是经常会被访问的。我们平常使用的 Redis 缓存很多时候也会按照 2-8 原则去做,很多淘汰算法都和操作系统中的类似。既说了 2-8 原则,那就不得不提命中率了,这是所有缓存概念都通用的。简单来说也就是你要访问的数据有多少能直接在缓存中直接找到。命中率高的话,一般表明你的缓存设计比较合理,系统处理速度也相对较快。
+- **从面试角度来说** :尤其是校招,对于操作系统方面知识的考察是非常非常多的。
+
+**简单来说,学习操作系统能够提高自己思考的深度以及对技术的理解力,并且,操作系统方面的知识也是面试必备。**
+
+关于如何学习操作系统,可以看这篇回答:[https://www.zhihu.com/question/270998611/answer/1640198217](https://www.zhihu.com/question/270998611/answer/1640198217)。
+
+## 一 操作系统基础
+
+面试官顶着蓬松的假发向我走来,只见他一手拿着厚重的 Thinkpad ,一手提着他那淡黄的长裙。
+
+
+
+### 1.1 什么是操作系统?
+
+👨💻**面试官** : 先来个简单问题吧!**什么是操作系统?**
+
+🙋 **我** :我通过以下四点向您介绍一下什么是操作系统吧!
+
+1. **操作系统(Operating System,简称 OS)是管理计算机硬件与软件资源的程序,是计算机的基石。**
+2. **操作系统本质上是一个运行在计算机上的软件程序 ,用于管理计算机硬件和软件资源。** 举例:运行在你电脑上的所有应用程序都通过操作系统来调用系统内存以及磁盘等等硬件。
+3. **操作系统存在屏蔽了硬件层的复杂性。** 操作系统就像是硬件使用的负责人,统筹着各种相关事项。
+4. **操作系统的内核(Kernel)是操作系统的核心部分,它负责系统的内存管理,硬件设备的管理,文件系统的管理以及应用程序的管理**。 内核是连接应用程序和硬件的桥梁,决定着系统的性能和稳定性。
+
+
+
+### 1.2 系统调用
+
+👨💻**面试官** :**什么是系统调用呢?** 能不能详细介绍一下。
+
+🙋 **我** :介绍系统调用之前,我们先来了解一下用户态和系统态。
+
+根据进程访问资源的特点,我们可以把进程在系统上的运行分为两个级别:
+
+1. 用户态(user mode) : 用户态运行的进程可以直接读取用户程序的数据。
+2. 系统态(kernel mode):可以简单的理解系统态运行的进程或程序几乎可以访问计算机的任何资源,不受限制。
+
+说了用户态和系统态之后,那么什么是系统调用呢?
+
+我们运行的程序基本都是运行在用户态,如果我们调用操作系统提供的系统态级别的子功能咋办呢?那就需要系统调用了!
+
+也就是说在我们运行的用户程序中,凡是与系统态级别的资源有关的操作(如文件管理、进程控制、内存管理等),都必须通过系统调用方式向操作系统提出服务请求,并由操作系统代为完成。
+
+这些系统调用按功能大致可分为如下几类:
+
+- 设备管理。完成设备的请求或释放,以及设备启动等功能。
+- 文件管理。完成文件的读、写、创建及删除等功能。
+- 进程控制。完成进程的创建、撤销、阻塞及唤醒等功能。
+- 进程通信。完成进程之间的消息传递或信号传递等功能。
+- 内存管理。完成内存的分配、回收以及获取作业占用内存区大小及地址等功能。
+
+## 二 进程和线程
+
+### 2.1 进程和线程的区别
+
+👨💻**面试官**: 好的!我明白了!那你再说一下: **进程和线程的区别**。
+
+🙋 **我:** 好的! 下图是 Java 内存区域,我们从 JVM 的角度来说一下线程和进程之间的关系吧!
+
+> 如果你对 Java 内存区域 (运行时数据区) 这部分知识不太了解的话可以阅读一下这篇文章:[《可能是把 Java 内存区域讲的最清楚的一篇文章》](https://snailclimb.gitee.io/javaguide/#/docs/java/jvm/Java内存区域)
+
+
+
+从上图可以看出:一个进程中可以有多个线程,多个线程共享进程的**堆**和**方法区 (JDK1.8 之后的元空间)**资源,但是每个线程有自己的**程序计数器**、**虚拟机栈** 和 **本地方法栈**。
+
+**总结:** 线程是进程划分成的更小的运行单位,一个进程在其执行的过程中可以产生多个线程。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。线程执行开销小,但不利于资源的管理和保护;而进程正相反。
+
+### 2.2 进程有哪几种状态?
+
+👨💻**面试官** : 那你再说说**进程有哪几种状态?**
+
+🙋 **我** :我们一般把进程大致分为 5 种状态,这一点和[线程](https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/Multithread/JavaConcurrencyBasicsCommonInterviewQuestionsSummary.md#6-%E8%AF%B4%E8%AF%B4%E7%BA%BF%E7%A8%8B%E7%9A%84%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E5%92%8C%E7%8A%B6%E6%80%81)很像!
+
+- **创建状态(new)** :进程正在被创建,尚未到就绪状态。
+- **就绪状态(ready)** :进程已处于准备运行状态,即进程获得了除了处理器之外的一切所需资源,一旦得到处理器资源(处理器分配的时间片)即可运行。
+- **运行状态(running)** :进程正在处理器上上运行(单核 CPU 下任意时刻只有一个进程处于运行状态)。
+- **阻塞状态(waiting)** :又称为等待状态,进程正在等待某一事件而暂停运行如等待某资源为可用或等待 IO 操作完成。即使处理器空闲,该进程也不能运行。
+- **结束状态(terminated)** :进程正在从系统中消失。可能是进程正常结束或其他原因中断退出运行。
+
+> 订正:下图中 running 状态被 interrupt 向 ready 状态转换的箭头方向反了。
+
+
+
+### 2.3 进程间的通信方式
+
+👨💻**面试官** :**进程间的通信常见的的有哪几种方式呢?**
+
+🙋 **我** :大概有 7 种常见的进程间的通信方式。
+
+> 下面这部分总结参考了:[《进程间通信 IPC (InterProcess Communication)》](https://www.jianshu.com/p/c1015f5ffa74) 这篇文章,推荐阅读,总结的非常不错。
+
+1. **管道/匿名管道(Pipes)** :用于具有亲缘关系的父子进程间或者兄弟进程之间的通信。
+1. **有名管道(Names Pipes)** : 匿名管道由于没有名字,只能用于亲缘关系的进程间通信。为了克服这个缺点,提出了有名管道。有名管道严格遵循**先进先出(first in first out)**。有名管道以磁盘文件的方式存在,可以实现本机任意两个进程通信。
+1. **信号(Signal)** :信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生;
+1. **消息队列(Message Queuing)** :消息队列是消息的链表,具有特定的格式,存放在内存中并由消息队列标识符标识。管道和消息队列的通信数据都是先进先出的原则。与管道(无名管道:只存在于内存中的文件;命名管道:存在于实际的磁盘介质或者文件系统)不同的是消息队列存放在内核中,只有在内核重启(即,操作系统重启)或者显式地删除一个消息队列时,该消息队列才会被真正的删除。消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取.比 FIFO 更有优势。**消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。**
+1. **信号量(Semaphores)** :信号量是一个计数器,用于多进程对共享数据的访问,信号量的意图在于进程间同步。这种通信方式主要用于解决与同步相关的问题并避免竞争条件。
+1. **共享内存(Shared memory)** :使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。可以说这是最有用的进程间通信方式。
+1. **套接字(Sockets)** : 此方法主要用于在客户端和服务器之间通过网络进行通信。套接字是支持 TCP/IP 的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。
+
+### 2.4 线程间的同步的方式
+
+👨💻**面试官** :**那线程间的同步的方式有哪些呢?**
+
+🙋 **我** :线程同步是两个或多个共享关键资源的线程的并发执行。应该同步线程以避免关键的资源使用冲突。操作系统一般有下面三种线程同步的方式:
+
+1. **互斥量(Mutex)** :采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问。比如 Java 中的 synchronized 关键词和各种 Lock 都是这种机制。
+1. **信号量(Semphares)** :它允许同一时刻多个线程访问同一资源,但是需要控制同一时刻访问此资源的最大线程数量。
+1. **事件(Event)** :Wait/Notify:通过通知操作的方式来保持多线程同步,还可以方便地实现多线程优先级的比较操作。
+
+### 2.5 进程的调度算法
+
+👨💻**面试官** :**你知道操作系统中进程的调度算法有哪些吗?**
+
+🙋 **我** :嗯嗯!这个我们大学的时候学过,是一个很重要的知识点!
+
+为了确定首先执行哪个进程以及最后执行哪个进程以实现最大 CPU 利用率,计算机科学家已经定义了一些算法,它们是:
+
+- **先到先服务(FCFS)调度算法** : 从就绪队列中选择一个最先进入该队列的进程为之分配资源,使它立即执行并一直执行到完成或发生某事件而被阻塞放弃占用 CPU 时再重新调度。
+- **短作业优先(SJF)的调度算法** : 从就绪队列中选出一个估计运行时间最短的进程为之分配资源,使它立即执行并一直执行到完成或发生某事件而被阻塞放弃占用 CPU 时再重新调度。
+- **时间片轮转调度算法** : 时间片轮转调度是一种最古老,最简单,最公平且使用最广的算法,又称 RR(Round robin)调度。每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间。
+- **多级反馈队列调度算法** :前面介绍的几种进程调度的算法都有一定的局限性。如**短进程优先的调度算法,仅照顾了短进程而忽略了长进程** 。多级反馈队列调度算法既能使高优先级的作业得到响应又能使短作业(进程)迅速完成。,因而它是目前**被公认的一种较好的进程调度算法**,UNIX 操作系统采取的便是这种调度算法。
+- **优先级调度** : 为每个流程分配优先级,首先执行具有最高优先级的进程,依此类推。具有相同优先级的进程以 FCFS 方式执行。可以根据内存要求,时间要求或任何其他资源要求来确定优先级。
+
+### 2.6 什么是死锁
+
+👨💻**面试官** :**你知道什么是死锁吗?**
+
+🙋 **我** :多个进程可以竞争有限数量的资源。当一个进程申请资源时,如果这时没有可用资源,那么这个进程进入等待状态。有时,如果所申请的资源被其他等待进程占有,那么该等待进程有可能再也无法改变状态。这种情况称为**死锁**。
+
+### 2.7 死锁的四个条件
+
+👨💻**面试官** :**产生死锁的四个必要条件是什么?**
+
+🙋 **我** :如果系统中以下四个条件同时成立,那么就能引起死锁:
+
+- **互斥**:资源必须处于非共享模式,即一次只有一个进程可以使用。如果另一进程申请该资源,那么必须等待直到该资源被释放为止。
+- **占有并等待**:一个进程至少应该占有一个资源,并等待另一资源,而该资源被其他进程所占有。
+- **非抢占**:资源不能被抢占。只能在持有资源的进程完成任务后,该资源才会被释放。
+- **循环等待**:有一组等待进程 `{P0, P1,..., Pn}`, `P0` 等待的资源被 `P1` 占有,`P1` 等待的资源被 `P2` 占有,......,`Pn-1` 等待的资源被 `Pn` 占有,`Pn` 等待的资源被 `P0` 占有。
+
+注意,只有四个条件同时成立时,死锁才会出现。
+
+## 三 操作系统内存管理基础
+
+### 3.1 内存管理介绍
+
+👨💻 **面试官**: **操作系统的内存管理主要是做什么?**
+
+🙋 **我:** 操作系统的内存管理主要负责内存的分配与回收(malloc 函数:申请内存,free 函数:释放内存),另外地址转换也就是将逻辑地址转换成相应的物理地址等功能也是操作系统内存管理做的事情。
+
+### 3.2 常见的几种内存管理机制
+
+👨💻 **面试官**: **操作系统的内存管理机制了解吗?内存管理有哪几种方式?**
+
+🙋 **我:** 这个在学习操作系统的时候有了解过。
+
+简单分为**连续分配管理方式**和**非连续分配管理方式**这两种。连续分配管理方式是指为一个用户程序分配一个连续的内存空间,常见的如 **块式管理** 。同样地,非连续分配管理方式允许一个程序使用的内存分布在离散或者说不相邻的内存中,常见的如**页式管理** 和 **段式管理**。
+
+1. **块式管理** : 远古时代的计算机操系统的内存管理方式。将内存分为几个固定大小的块,每个块中只包含一个进程。如果程序运行需要内存的话,操作系统就分配给它一块,如果程序运行只需要很小的空间的话,分配的这块内存很大一部分几乎被浪费了。这些在每个块中未被利用的空间,我们称之为碎片。
+2. **页式管理** :把主存分为大小相等且固定的一页一页的形式,页较小,相对相比于块式管理的划分力度更大,提高了内存利用率,减少了碎片。页式管理通过页表对应逻辑地址和物理地址。
+3. **段式管理** : 页式管理虽然提高了内存利用率,但是页式管理其中的页实际并无任何实际意义。 段式管理把主存分为一段段的,每一段的空间又要比一页的空间小很多 。但是,最重要的是段是有实际意义的,每个段定义了一组逻辑信息,例如,有主程序段 MAIN、子程序段 X、数据段 D 及栈段 S 等。 段式管理通过段表对应逻辑地址和物理地址。
+
+👨💻**面试官** : 回答的还不错!不过漏掉了一个很重要的 **段页式管理机制** 。段页式管理机制结合了段式管理和页式管理的优点。简单来说段页式管理机制就是把主存先分成若干段,每个段又分成若干页,也就是说 **段页式管理机制** 中段与段之间以及段的内部的都是离散的。
+
+🙋 **我** :谢谢面试官!刚刚把这个给忘记了~
+
+### 3.3 快表和多级页表
+
+👨💻**面试官** : 页表管理机制中有两个很重要的概念:快表和多级页表,这两个东西分别解决了页表管理中很重要的两个问题。你给我简单介绍一下吧!
+
+🙋 **我** :在分页内存管理中,很重要的两点是:
+
+1. 虚拟地址到物理地址的转换要快。
+2. 解决虚拟地址空间大,页表也会很大的问题。
+
+#### 快表
+
+为了解决虚拟地址到物理地址的转换速度,操作系统在 **页表方案** 基础之上引入了 **快表** 来加速虚拟地址到物理地址的转换。我们可以把快表理解为一种特殊的高速缓冲存储器(Cache),其中的内容是页表的一部分或者全部内容。作为页表的 Cache,它的作用与页表相似,但是提高了访问速率。由于采用页表做地址转换,读写内存数据时 CPU 要访问两次主存。有了快表,有时只要访问一次高速缓冲存储器,一次主存,这样可加速查找并提高指令执行速度。
+
+使用快表之后的地址转换流程是这样的:
+
+1. 根据虚拟地址中的页号查快表;
+2. 如果该页在快表中,直接从快表中读取相应的物理地址;
+3. 如果该页不在快表中,就访问内存中的页表,再从页表中得到物理地址,同时将页表中的该映射表项添加到快表中;
+4. 当快表填满后,又要登记新页时,就按照一定的淘汰策略淘汰掉快表中的一个页。
+
+看完了之后你会发现快表和我们平时经常在我们开发的系统使用的缓存(比如 Redis)很像,的确是这样的,操作系统中的很多思想、很多经典的算法,你都可以在我们日常开发使用的各种工具或者框架中找到它们的影子。
+
+#### 多级页表
+
+引入多级页表的主要目的是为了避免把全部页表一直放在内存中占用过多空间,特别是那些根本就不需要的页表就不需要保留在内存中。多级页表属于时间换空间的典型场景,具体可以查看下面这篇文章
+
+- 多级页表如何节约内存:[https://www.polarxiong.com/archives/多级页表如何节约内存.html](https://www.polarxiong.com/archives/多级页表如何节约内存.html)
+
+#### 总结
+
+为了提高内存的空间性能,提出了多级页表的概念;但是提到空间性能是以浪费时间性能为基础的,因此为了补充损失的时间性能,提出了快表(即 TLB)的概念。 不论是快表还是多级页表实际上都利用到了程序的局部性原理,局部性原理在后面的虚拟内存这部分会介绍到。
+
+### 3.4 分页机制和分段机制的共同点和区别
+
+👨💻**面试官** : **分页机制和分段机制有哪些共同点和区别呢?**
+
+🙋 **我** :
+
+1. **共同点** :
+ - 分页机制和分段机制都是为了提高内存利用率,减少内存碎片。
+ - 页和段都是离散存储的,所以两者都是离散分配内存的方式。但是,每个页和段中的内存是连续的。
+2. **区别** :
+ - 页的大小是固定的,由操作系统决定;而段的大小不固定,取决于我们当前运行的程序。
+ - 分页仅仅是为了满足操作系统内存管理的需求,而段是逻辑信息的单位,在程序中可以体现为代码段,数据段,能够更好满足用户的需要。
+
+### 3.5 逻辑(虚拟)地址和物理地址
+
+👨💻**面试官** :你刚刚还提到了**逻辑地址和物理地址**这两个概念,我不太清楚,你能为我解释一下不?
+
+🙋 **我:** em...好的嘛!我们编程一般只有可能和逻辑地址打交道,比如在 C 语言中,指针里面存储的数值就可以理解成为内存里的一个地址,这个地址也就是我们说的逻辑地址,逻辑地址由操作系统决定。物理地址指的是真实物理内存中地址,更具体一点来说就是内存地址寄存器中的地址。物理地址是内存单元真正的地址。
+
+### 3.6 CPU 寻址了解吗?为什么需要虚拟地址空间?
+
+👨💻**面试官** :**CPU 寻址了解吗?为什么需要虚拟地址空间?**
+
+🙋 **我** :这部分我真不清楚!
+
+于是面试完之后我默默去查阅了相关文档!留下了没有技术的泪水。。。
+
+> 这部分内容参考了 Microsoft 官网的介绍,地址:
+
+现代处理器使用的是一种称为 **虚拟寻址(Virtual Addressing)** 的寻址方式。**使用虚拟寻址,CPU 需要将虚拟地址翻译成物理地址,这样才能访问到真实的物理内存。** 实际上完成虚拟地址转换为物理地址转换的硬件是 CPU 中含有一个被称为 **内存管理单元(Memory Management Unit, MMU)** 的硬件。如下图所示:
+
+
+
+**为什么要有虚拟地址空间呢?**
+
+先从没有虚拟地址空间的时候说起吧!没有虚拟地址空间的时候,**程序都是直接访问和操作的都是物理内存** 。但是这样有什么问题呢?
+
+1. 用户程序可以访问任意内存,寻址内存的每个字节,这样就很容易(有意或者无意)破坏操作系统,造成操作系统崩溃。
+2. 想要同时运行多个程序特别困难,比如你想同时运行一个微信和一个 QQ 音乐都不行。为什么呢?举个简单的例子:微信在运行的时候给内存地址 1xxx 赋值后,QQ 音乐也同样给内存地址 1xxx 赋值,那么 QQ 音乐对内存的赋值就会覆盖微信之前所赋的值,这就造成了微信这个程序就会崩溃。
+
+**总结来说:如果直接把物理地址暴露出来的话会带来严重问题,比如可能对操作系统造成伤害以及给同时运行多个程序造成困难。**
+
+通过虚拟地址访问内存有以下优势:
+
+- 程序可以使用一系列相邻的虚拟地址来访问物理内存中不相邻的大内存缓冲区。
+- 程序可以使用一系列虚拟地址来访问大于可用物理内存的内存缓冲区。当物理内存的供应量变小时,内存管理器会将物理内存页(通常大小为 4 KB)保存到磁盘文件。数据或代码页会根据需要在物理内存与磁盘之间移动。
+- 不同进程使用的虚拟地址彼此隔离。一个进程中的代码无法更改正在由另一进程或操作系统使用的物理内存。
+
+## 四 虚拟内存
+
+### 4.1 什么是虚拟内存(Virtual Memory)?
+
+👨💻**面试官** :再问你一个常识性的问题!**什么是虚拟内存(Virtual Memory)?**
+
+🙋 **我** :这个在我们平时使用电脑特别是 Windows 系统的时候太常见了。很多时候我们使用点开了很多占内存的软件,这些软件占用的内存可能已经远远超出了我们电脑本身具有的物理内存。**为什么可以这样呢?** 正是因为 **虚拟内存** 的存在,通过 **虚拟内存** 可以让程序可以拥有超过系统物理内存大小的可用内存空间。另外,**虚拟内存为每个进程提供了一个一致的、私有的地址空间,它让每个进程产生了一种自己在独享主存的错觉(每个进程拥有一片连续完整的内存空间)**。这样会更加有效地管理内存并减少出错。
+
+**虚拟内存**是计算机系统内存管理的一种技术,我们可以手动设置自己电脑的虚拟内存。不要单纯认为虚拟内存只是“使用硬盘空间来扩展内存“的技术。**虚拟内存的重要意义是它定义了一个连续的虚拟地址空间**,并且 **把内存扩展到硬盘空间**。推荐阅读:[《虚拟内存的那点事儿》](https://juejin.im/post/59f8691b51882534af254317)
+
+维基百科中有几句话是这样介绍虚拟内存的。
+
+> **虚拟内存** 使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。与没有使用虚拟内存技术的系统相比,使用这种技术的系统使得大型程序的编写变得更容易,对真正的物理内存(例如 RAM)的使用也更有效率。目前,大多数操作系统都使用了虚拟内存,如 Windows 家族的“虚拟内存”;Linux 的“交换空间”等。From:
+
+### 4.2 局部性原理
+
+👨💻**面试官** :要想更好地理解虚拟内存技术,必须要知道计算机中著名的**局部性原理**。另外,局部性原理既适用于程序结构,也适用于数据结构,是非常重要的一个概念。
+
+🙋 **我** :局部性原理是虚拟内存技术的基础,正是因为程序运行具有局部性原理,才可以只装入部分程序到内存就开始运行。
+
+> 以下内容摘自《计算机操作系统教程》 第 4 章存储器管理。
+
+早在 1968 年的时候,就有人指出我们的程序在执行的时候往往呈现局部性规律,也就是说在某个较短的时间段内,程序执行局限于某一小部分,程序访问的存储空间也局限于某个区域。
+
+局部性原理表现在以下两个方面:
+
+1. **时间局部性** :如果程序中的某条指令一旦执行,不久以后该指令可能再次执行;如果某数据被访问过,不久以后该数据可能再次被访问。产生时间局部性的典型原因,是由于在程序中存在着大量的循环操作。
+2. **空间局部性** :一旦程序访问了某个存储单元,在不久之后,其附近的存储单元也将被访问,即程序在一段时间内所访问的地址,可能集中在一定的范围之内,这是因为指令通常是顺序存放、顺序执行的,数据也一般是以向量、数组、表等形式簇聚存储的。
+
+时间局部性是通过将近来使用的指令和数据保存到高速缓存存储器中,并使用高速缓存的层次结构实现。空间局部性通常是使用较大的高速缓存,并将预取机制集成到高速缓存控制逻辑中实现。虚拟内存技术实际上就是建立了 “内存一外存”的两级存储器的结构,利用局部性原理实现髙速缓存。
+
+### 4.3 虚拟存储器
+
+> **勘误:虚拟存储器又叫做虚拟内存,都是 Virtual Memory 的翻译,属于同一个概念。**
+
+👨💻**面试官** :~~都说了虚拟内存了。你再讲讲**虚拟存储器**把!~~
+
+🙋 **我** :
+
+> 这部分内容来自:[王道考研操作系统知识点整理](https://wizardforcel.gitbooks.io/wangdaokaoyan-os/content/13.html)。
+
+基于局部性原理,在程序装入时,可以将程序的一部分装入内存,而将其他部分留在外存,就可以启动程序执行。由于外存往往比内存大很多,所以我们运行的软件的内存大小实际上是可以比计算机系统实际的内存大小大的。在程序执行过程中,当所访问的信息不在内存时,由操作系统将所需要的部分调入内存,然后继续执行程序。另一方面,操作系统将内存中暂时不使用的内容换到外存上,从而腾出空间存放将要调入内存的信息。这样,计算机好像为用户提供了一个比实际内存大的多的存储器——**虚拟存储器**。
+
+实际上,我觉得虚拟内存同样是一种时间换空间的策略,你用 CPU 的计算时间,页的调入调出花费的时间,换来了一个虚拟的更大的空间来支持程序的运行。不得不感叹,程序世界几乎不是时间换空间就是空间换时间。
+
+### 4.4 虚拟内存的技术实现
+
+👨💻**面试官** :**虚拟内存技术的实现呢?**
+
+🙋 **我** :**虚拟内存的实现需要建立在离散分配的内存管理方式的基础上。** 虚拟内存的实现有以下三种方式:
+
+1. **请求分页存储管理** :建立在分页管理之上,为了支持虚拟存储器功能而增加了请求调页功能和页面置换功能。请求分页是目前最常用的一种实现虚拟存储器的方法。请求分页存储管理系统中,在作业开始运行之前,仅装入当前要执行的部分段即可运行。假如在作业运行的过程中发现要访问的页面不在内存,则由处理器通知操作系统按照对应的页面置换算法将相应的页面调入到主存,同时操作系统也可以将暂时不用的页面置换到外存中。
+2. **请求分段存储管理** :建立在分段存储管理之上,增加了请求调段功能、分段置换功能。请求分段储存管理方式就如同请求分页储存管理方式一样,在作业开始运行之前,仅装入当前要执行的部分段即可运行;在执行过程中,可使用请求调入中断动态装入要访问但又不在内存的程序段;当内存空间已满,而又需要装入新的段时,根据置换功能适当调出某个段,以便腾出空间而装入新的段。
+3. **请求段页式存储管理**
+
+**这里多说一下?很多人容易搞混请求分页与分页存储管理,两者有何不同呢?**
+
+请求分页存储管理建立在分页管理之上。他们的根本区别是是否将程序全部所需的全部地址空间都装入主存,这也是请求分页存储管理可以提供虚拟内存的原因,我们在上面已经分析过了。
+
+它们之间的根本区别在于是否将一作业的全部地址空间同时装入主存。请求分页存储管理不要求将作业全部地址空间同时装入主存。基于这一点,请求分页存储管理可以提供虚存,而分页存储管理却不能提供虚存。
+
+不管是上面那种实现方式,我们一般都需要:
+
+1. 一定容量的内存和外存:在载入程序的时候,只需要将程序的一部分装入内存,而将其他部分留在外存,然后程序就可以执行了;
+2. **缺页中断**:如果**需执行的指令或访问的数据尚未在内存**(称为缺页或缺段),则由处理器通知操作系统将相应的页面或段**调入到内存**,然后继续执行程序;
+3. **虚拟地址空间** :逻辑地址到物理地址的变换。
+
+### 4.5 页面置换算法
+
+👨💻**面试官** :虚拟内存管理很重要的一个概念就是页面置换算法。那你说一下 **页面置换算法的作用?常见的页面置换算法有哪些?**
+
+🙋 **我** :
+
+> 这个题目经常作为笔试题出现,网上已经给出了很不错的回答,我这里只是总结整理了一下。
+
+地址映射过程中,若在页面中发现所要访问的页面不在内存中,则发生缺页中断 。
+
+> **缺页中断** 就是要访问的**页**不在主存,需要操作系统将其调入主存后再进行访问。 在这个时候,被内存映射的文件实际上成了一个分页交换文件。
+
+当发生缺页中断时,如果当前内存中并没有空闲的页面,操作系统就必须在内存选择一个页面将其移出内存,以便为即将调入的页面让出空间。用来选择淘汰哪一页的规则叫做页面置换算法,我们可以把页面置换算法看成是淘汰页面的规则。
+
+- **OPT 页面置换算法(最佳页面置换算法)** :最佳(Optimal, OPT)置换算法所选择的被淘汰页面将是以后永不使用的,或者是在最长时间内不再被访问的页面,这样可以保证获得最低的缺页率。但由于人们目前无法预知进程在内存下的若千页面中哪个是未来最长时间内不再被访问的,因而该算法无法实现。一般作为衡量其他置换算法的方法。
+- **FIFO(First In First Out) 页面置换算法(先进先出页面置换算法)** : 总是淘汰最先进入内存的页面,即选择在内存中驻留时间最久的页面进行淘汰。
+- **LRU (Least Recently Used)页面置换算法(最近最久未使用页面置换算法)** :LRU算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间 T,当须淘汰一个页面时,选择现有页面中其 T 值最大的,即最近最久未使用的页面予以淘汰。
+- **LFU (Least Frequently Used)页面置换算法(最少使用页面置换算法)** : 该置换算法选择在之前时期使用最少的页面作为淘汰页。
+
+## Reference
+
+- 《计算机操作系统—汤小丹》第四版
+- [《深入理解计算机系统》](https://book.douban.com/subject/1230413/)
+- [https://zh.wikipedia.org/wiki/输入输出内存管理单元](https://zh.wikipedia.org/wiki/输入输出内存管理单元)
+- [https://baike.baidu.com/item/快表/19781679](https://baike.baidu.com/item/快表/19781679)
+- https://www.jianshu.com/p/1d47ed0b46d5
+-
+-
+-
+- 王道考研操作系统知识点整理: https://wizardforcel.gitbooks.io/wangdaokaoyan-os/content/13.html
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/data/java-recommended-books.md b/docs/data/java-recommended-books.md
deleted file mode 100644
index 7bdadce50be..00000000000
--- a/docs/data/java-recommended-books.md
+++ /dev/null
@@ -1,119 +0,0 @@
-
-
-
-- [Java](#java)
- - [基础](#基础)
- - [并发](#并发)
- - [JVM](#jvm)
- - [Java8 新特性](#java8-新特性)
- - [代码优化](#代码优化)
-- [网络](#网络)
-- [操作系统](#操作系统)
-- [数据结构与算法](#数据结构与算法)
-- [数据库](#数据库)
-- [系统设计](#系统设计)
- - [设计模式](#设计模式)
- - [常用框架](#常用框架)
- - [网站架构](#网站架构)
- - [软件底层](#软件底层)
-- [其他](#其他)
-
-
-## Java
-
-### 基础
-
-- [《Head First Java》](https://book.douban.com/subject/2000732/)(推荐,豆瓣评分 8.7,1.0K+人评价): 可以说是我的 Java 启蒙书籍了,特别适合新手读当然也适合我们用来温故 Java 知识点。
-- [《Java 核心技术卷 1+卷 2》](https://book.douban.com/subject/25762168/)(推荐): 很棒的两本书,建议有点 Java 基础之后再读,介绍的还是比较深入的,非常推荐。这两本书我一般也会用来巩固知识点,是两本适合放在自己身边的好书。
-- [《JAVA 网络编程 第 4 版》](https://book.douban.com/subject/26259017/): 可以系统的学习一下网络的一些概念以及网络编程在 Java 中的使用。
-- [《Java 编程思想 (第 4 版)》](https://book.douban.com/subject/2130190/)(推荐,豆瓣评分 9.1,3.2K+人评价):大部分人称之为Java领域的圣经,但我不推荐初学者阅读,有点劝退的味道。稍微有点基础后阅读更好。
-- [《Java性能权威指南》](https://book.douban.com/subject/26740520/)(推荐,豆瓣评分 8.2,0.1K+人评价):O'Reilly 家族书,性能调优的入门书,我个人觉得性能调优是每个 Java 从业者必备知识,这本书的缺点就是太老了,但是这本书可以作为一个实战书,尤其是 JVM 调优!不适合初学者。前置书籍:《深入理解 Java 虚拟机》
-
-### 并发
-
-- [《Java 并发编程之美》]( ) (推荐):2018 年 10 月出版的一本书,个人感觉非常不错,对每个知识点的讲解都很棒。
-- [《Java 并发编程的艺术》](https://book.douban.com/subject/26591326/)(推荐,豆瓣评分 7.2,0.2K+人评价): 这本书不是很适合作为 Java 并发入门书籍,需要具备一定的 JVM 基础。我感觉有些东西讲的还是挺深入的,推荐阅读。
-- [《实战 Java 高并发程序设计》](https://book.douban.com/subject/26663605/)(推荐,豆瓣评分 8.3): 书的质量没的说,推荐大家好好看一下。
-- [《Java 高并发编程详解》](https://book.douban.com/subject/30255689/)(豆瓣评分 7.6): 2018 年 6 月出版的一本书,内容很详细,但可能又有点过于啰嗦,不过这只是我的感觉。
-
-### JVM
-
-- [《深入理解 Java 虚拟机(第 2 版)周志明》](https://book.douban.com/subject/24722612/)(推荐,豆瓣评分 8.9,1.0K+人评价):建议多刷几遍,书中的所有知识点可以通过 JAVA 运行时区域和 JAVA 的内存模型与线程两个大模块罗列完全。
-- [《实战 JAVA 虚拟机》](https://book.douban.com/subject/26354292/)(推荐,豆瓣评分 8.0,1.0K+人评价):作为入门的了解 Java 虚拟机的知识还是不错的。
-
-### Java8 新特性
-
-- [《Java 8 实战》](https://book.douban.com/subject/26772632/) (推荐,豆瓣评分 9.2 ):面向 Java 8 的技能升级,包括 Lambdas、流和函数式编程特性。实战系列的一贯风格让自己快速上手应用起来。Java 8 支持的 Lambda 是精简表达在语法上提供的支持。Java 8 提供了 Stream,学习和使用可以建立流式编程的认知。
-- [《Java 8 编程参考官方教程》](https://book.douban.com/subject/26556574/) (推荐,豆瓣评分 9.2):也还不错吧。
-
-### 代码优化
-
-- [《重构_改善既有代码的设计》](https://book.douban.com/subject/4262627/)(推荐):豆瓣 9.1 分,重构书籍的开山鼻祖。
-- [《Effective java 》](https://book.douban.com/subject/3360807/)(推荐,豆瓣评分 9.0,1.4K+人评价):本书介绍了在 Java 编程中 78 条极具实用价值的经验规则,这些经验规则涵盖了大多数开发人员每天所面临的问题的解决方案。通过对 Java 平台设计专家所使用的技术的全面描述,揭示了应该做什么,不应该做什么才能产生清晰、健壮和高效的代码。本书中的每条规则都以简短、独立的小文章形式出现,并通过例子代码加以进一步说明。本书内容全面,结构清晰,讲解详细。可作为技术人员的参考用书。
-- [《代码整洁之道》](https://book.douban.com/subject/5442024/)(推荐,豆瓣评分 9.1):虽然是用 Java 语言作为例子,全篇都是在阐述 Java 面向对象的思想,但是其中大部分内容其它语言也能应用到。
-- **阿里巴巴 Java 开发手册(详尽版)** [https://github.com/alibaba/p3c/blob/master/阿里巴巴 Java 开发手册(详尽版).pdf](https://github.com/alibaba/p3c/blob/master/%E9%98%BF%E9%87%8C%E5%B7%B4%E5%B7%B4Java%E5%BC%80%E5%8F%91%E6%89%8B%E5%86%8C%EF%BC%88%E8%AF%A6%E5%B0%BD%E7%89%88%EF%BC%89.pdf)
-- **Google Java 编程风格指南:**
-
-
-## 网络
-
-- [《图解 HTTP》](https://book.douban.com/subject/25863515/)(推荐,豆瓣评分 8.1 , 1.6K+人评价): 讲漫画一样的讲 HTTP,很有意思,不会觉得枯燥,大概也涵盖也 HTTP 常见的知识点。因为篇幅问题,内容可能不太全面。不过,如果不是专门做网络方向研究的小伙伴想研究 HTTP 相关知识的话,读这本书的话应该来说就差不多了。
-- [《HTTP 权威指南》](https://book.douban.com/subject/10746113/) (推荐,豆瓣评分 8.6):如果要全面了解 HTTP 非此书不可!
-
-## 操作系统
-
-- [《鸟哥的 Linux 私房菜》](https://book.douban.com/subject/4889838/)(推荐,,豆瓣评分 9.1,0.3K+人评价):本书是最具知名度的 Linux 入门书《鸟哥的 Linux 私房菜基础学习篇》的最新版,全面而详细地介绍了 Linux 操作系统。全书分为 5 个部分:第一部分着重说明 Linux 的起源及功能,如何规划和安装 Linux 主机;第二部分介绍 Linux 的文件系统、文件、目录与磁盘的管理;第三部分介绍文字模式接口 shell 和管理系统的好帮手 shell 脚本,另外还介绍了文字编辑器 vi 和 vim 的使用方法;第四部分介绍了对于系统安全非常重要的 Linux 账号的管理,以及主机系统与程序的管理,如查看进程、任务分配和作业管理;第五部分介绍了系统管理员 (root) 的管理事项,如了解系统运行状况、系统服务,针对登录文件进行解析,对系统进行备份以及核心的管理等。
-
-## 数据结构与算法
-
-- [《大话数据结构》](https://book.douban.com/subject/6424904/)(推荐,豆瓣评分 7.9 , 1K+人评价):入门类型的书籍,读起来比较浅显易懂,适合没有数据结构基础或者说数据结构没学好的小伙伴用来入门数据结构。
-- [《数据结构与算法分析:C 语言描述》](https://book.douban.com/subject/1139426/)(推荐,豆瓣评分 8.9,1.6K+人评价):本书是《Data Structures and Algorithm Analysis in C》一书第 2 版的简体中译本。原书曾被评为 20 世纪顶尖的 30 部计算机著作之一,作者 Mark Allen Weiss 在数据结构和算法分析方面卓有建树,他的数据结构和算法分析的著作尤其畅销,并受到广泛好评.已被世界 500 余所大学用作教材。
-- [《算法图解》](https://book.douban.com/subject/26979890/)(推荐,豆瓣评分 8.4,0.6K+人评价):入门类型的书籍,读起来比较浅显易懂,适合没有算法基础或者说算法没学好的小伙伴用来入门。示例丰富,图文并茂,以让人容易理解的方式阐释了算法.读起来比较快,内容不枯燥!
-- [《算法 第四版》](https://book.douban.com/subject/10432347/)(推荐,豆瓣评分 9.3,0.4K+人评价):Java 语言描述,算法领域经典的参考书,全面介绍了关于算法和数据结构的必备知识,并特别针对排序、搜索、图处理和字符串处理进行了论述。书的内容非常多,可以说是 Java 程序员的必备书籍之一了。
-
-## 数据库
-
-- [《高性能 MySQL》](https://book.douban.com/subject/23008813/)(推荐,豆瓣评分 9.3,0.4K+人评价):mysql 领域的经典之作,拥有广泛的影响力。不但适合数据库管理员(dba)阅读,也适合开发人员参考学习。不管是数据库新手还是专家,相信都能从本书有所收获。
-- [《Redis 实战》](https://book.douban.com/subject/26612779/):如果你想了解 Redis 的一些概念性知识的话,这本书真的非常不错。
-- [《Redis 设计与实现》](https://book.douban.com/subject/25900156/)(推荐,豆瓣评分 8.5,0.5K+人评价):也还行吧!
-- [《MySQL 技术内幕-InnoDB 存储引擎》]( )(推荐,豆瓣评分 8.7):了解 InnoDB 存储引擎底层原理必备的一本书,比较深入。
-
-## 系统设计
-
-### 设计模式
-
-- [《设计模式 : 可复用面向对象软件的基础》](https://book.douban.com/subject/1052241/) (推荐,豆瓣评分 9.1):设计模式的经典!
-- [《Head First 设计模式(中文版)》](https://book.douban.com/subject/2243615/) (推荐,豆瓣评分 9.2):相当赞的一本设计模式入门书籍。用实际的编程案例讲解算法设计中会遇到的各种问题和需求变更(对的,连需求变更都考虑到了!),并以此逐步推导出良好的设计模式解决办法。
-- [《大话设计模式》](https://book.douban.com/subject/2334288/) (推荐,豆瓣评分 8.3):本书通篇都是以情景对话的形式,用多个小故事或编程示例来组织讲解GOF(即《设计模式 : 可复用面向对象软件的基础》这本书)),但是不像《设计模式 : 可复用面向对象软件的基础》难懂。但是设计模式只看书是不够的,还是需要在实际项目中运用,结合[设计模式](docs/system-design/设计模式.md)更佳!
-
-### 常用框架
-
-- [《深入分析 Java Web 技术内幕》](https://book.douban.com/subject/25953851/): 感觉还行,涉及的东西也蛮多。
-- [《Netty 实战》](https://book.douban.com/subject/27038538/)(推荐,豆瓣评分 7.8,92 人评价):内容很细,如果想学 Netty 的话,推荐阅读这本书!
-- [《从 Paxos 到 Zookeeper》](https://book.douban.com/subject/26292004/)(推荐,豆瓣评分 7.8,0.3K 人评价):简要介绍几种典型的分布式一致性协议,以及解决分布式一致性问题的思路,其中重点讲解了 Paxos 和 ZAB 协议。同时,本书深入介绍了分布式一致性问题的工业解决方案——ZooKeeper,并着重向读者展示这一分布式协调框架的使用方法、内部实现及运维技巧,旨在帮助读者全面了解 ZooKeeper,并更好地使用和运维 ZooKeeper。
-- [《Spring 实战(第 4 版)》](https://book.douban.com/subject/26767354/)(推荐,豆瓣评分 8.3,0.3K+人评价):不建议当做入门书籍读,入门的话可以找点国人的书或者视频看。这本定位就相当于是关于 Spring 的新华字典,只有一些基本概念的介绍和示例,涵盖了 Spring 的各个方面,但都不够深入。就像作者在最后一页写的那样:“学习 Spring,这才刚刚开始”。
-- [《RabbitMQ 实战指南》](https://book.douban.com/subject/27591386/):《RabbitMQ 实战指南》从消息中间件的概念和 RabbitMQ 的历史切入,主要阐述 RabbitMQ 的安装、使用、配置、管理、运维、原理、扩展等方面的细节。如果你想浅尝 RabbitMQ 的使用,这本书是你最好的选择;如果你想深入 RabbitMQ 的原理,这本书也是你最好的选择;总之,如果你想玩转 RabbitMQ,这本书一定是最值得看的书之一
-- [《Spring Cloud 微服务实战》](https://book.douban.com/subject/27025912/):从时下流行的微服务架构概念出发,详细介绍了 Spring Cloud 针对微服务架构中几大核心要素的解决方案和基础组件。对于各个组件的介绍,《Spring Cloud 微服务实战》主要以示例与源码结合的方式来帮助读者更好地理解这些组件的使用方法以及运行原理。同时,在介绍的过程中,还包含了作者在实践中所遇到的一些问题和解决思路,可供读者在实践中作为参考。
-- [《第一本 Docker 书》](https://book.douban.com/subject/26780404/):Docker 入门书籍!
-- [《Spring Boot编程思想(核心篇)》](https://book.douban.com/subject/33390560/)(推荐,豆瓣评分 6.2):SpringBoot深入书,不适合初学者。书尤其的厚,评分低的的理由是书某些知识过于拖沓,评分高的理由是书中对SpringBoot内部原理讲解很清楚。作者小马哥:Apache Dubbo PMC、Spring Cloud Alibaba项目架构师。B站作者地址:https://space.bilibili.com/327910845?from=search&seid=17095917016893398636。
-
-### 网站架构
-
-- [《大型网站技术架构:核心原理与案例分析+李智慧》](https://book.douban.com/subject/25723064/)(推荐):这本书我读过,基本不需要你有什么基础啊~读起来特别轻松,但是却可以学到很多东西,非常推荐了。另外我写过这本书的思维导图,关注我的微信公众号:“Java 面试通关手册”回复“大型网站技术架构”即可领取思维导图。
-- [《亿级流量网站架构核心技术》](https://book.douban.com/subject/26999243/)(推荐):一书总结并梳理了亿级流量网站高可用和高并发原则,通过实例详细介绍了如何落地这些原则。本书分为四部分:概述、高可用原则、高并发原则、案例实战。从负载均衡、限流、降级、隔离、超时与重试、回滚机制、压测与预案、缓存、池化、异步化、扩容、队列等多方面详细介绍了亿级流量网站的架构核心技术,让读者看后能快速运用到实践项目中。
-
-### 软件底层
-
-- [《深入剖析 Tomcat》](https://book.douban.com/subject/10426640/)(推荐,豆瓣评分 8.4,0.2K+人评价):本书深入剖析 Tomcat 4 和 Tomcat 5 中的每个组件,并揭示其内部工作原理。通过学习本书,你将可以自行开发 Tomcat 组件,或者扩展已有的组件。 读完这本书,基本可以摆脱背诵面试题的尴尬。
-- [《深入理解 Nginx(第 2 版)》](https://book.douban.com/subject/26745255/):作者讲的非常细致,注释都写的都很工整,对于 Nginx 的开发人员非常有帮助。优点是细致,缺点是过于细致,到处都是代码片段,缺少一些抽象。
-
-## 其他
-
-- [《黑客与画家》](https://read.douban.com/ebook/387525/?dcs=subject-rec&dcm=douban&dct=2243615):这本书是硅谷创业之父,Y Combinator 创始人 Paul Graham 的文集。之所以叫这个名字,是因为作者认为黑客(并非负面的那个意思)与画家有着极大的相似性,他们都是在创造,而不是完成某个任务。
-- [《图解密码技术》](https://book.douban.com/subject/26265544/)(推荐,豆瓣评分 9.1,0.3K+人评价):本书以**图配文**的形式,第一部分讲述了密码技术的历史沿革、对称密码、分组密码模式(包括ECB、CBC、CFB、OFB、CTR)、公钥、混合密码系统。第二部分重点介绍了认证方面的内容,涉及单向散列函数、消息认证码、数字签名、证书等。第三部分讲述了密钥、随机数、PGP、SSL/TLS 以及密码技术在现实生活中的应用。关键字:JWT 前置知识、区块链密码技术前置知识。属于密码知识入门书籍。
-
-
-
-
-
-
diff --git a/docs/data/spring-boot-practical-projects.md b/docs/data/spring-boot-practical-projects.md
deleted file mode 100644
index 046af88a271..00000000000
--- a/docs/data/spring-boot-practical-projects.md
+++ /dev/null
@@ -1,66 +0,0 @@
-最近经常被读者问到有没有 Spring Boot 实战项目可以学习,于是,我就去 Github 上找了 10 个我觉得还不错的实战项目。对于这些实战项目,有部分是比较适合 Spring Boot 刚入门的朋友学习的,还有一部分可能要求你对 Spring Boot 相关技术比较熟悉。需要的朋友可以根据个人实际情况进行选择。如果你对 Spring Boot 不太熟悉的话,可以看我最近开源的 springboot-guide:https://github.com/Snailclimb/springboot-guide 入门(还在持续更新中)。
-
-### mall
-
-- **Github地址**: [https://github.com/macrozheng/mall](https://github.com/macrozheng/mall)
-- **star**: 22.9k
-- **介绍**: mall项目是一套电商系统,包括前台商城系统及后台管理系统,基于SpringBoot+MyBatis实现。 前台商城系统包含首页门户、商品推荐、商品搜索、商品展示、购物车、订单流程、会员中心、客户服务、帮助中心等模块。 后台管理系统包含商品管理、订单管理、会员管理、促销管理、运营管理、内容管理、统计报表、财务管理、权限管理、设置等模块。
-
-### jeecg-boot
-
-- **Github地址**:[https://github.com/zhangdaiscott/jeecg-boot](https://github.com/zhangdaiscott/jeecg-boot)
-- **star**: 6.4k
-- **介绍**: 一款基于代码生成器的JAVA快速开发平台!采用最新技术,前后端分离架构:SpringBoot 2.x,Ant Design&Vue,Mybatis,Shiro,JWT。强大的代码生成器让前后端代码一键生成,无需写任何代码,绝对是全栈开发福音!! JeecgBoot的宗旨是提高UI能力的同时,降低前后分离的开发成本,JeecgBoot还独创在线开发模式,No代码概念,一系列在线智能开发:在线配置表单、在线配置报表、在线设计流程等等。
-
-### eladmin
-
-- **Github地址**:[https://github.com/elunez/eladmin](https://github.com/elunez/eladmin)
-- **star**: 3.9k
-- **介绍**: 项目基于 Spring Boot 2.1.0 、 Jpa、 Spring Security、redis、Vue的前后端分离的后台管理系统,项目采用分模块开发方式, 权限控制采用 RBAC,支持数据字典与数据权限管理,支持一键生成前后端代码,支持动态路由。
-
-### paascloud-master
-
-- **Github地址**:[https://github.com/paascloud/paascloud-master](https://github.com/paascloud/paascloud-master)
-- **star**: 5.9k
-- **介绍**: spring cloud + vue + oAuth2.0全家桶实战,前后端分离模拟商城,完整的购物流程、后端运营平台,可以实现快速搭建企业级微服务项目。支持微信登录等三方登录。
-
-### vhr
-
-- **Github地址**:[https://github.com/lenve/vhr](https://github.com/lenve/vhr)
-- **star**: 10.6k
-- **介绍**: 微人事是一个前后端分离的人力资源管理系统,项目采用SpringBoot+Vue开发。
-
-### One mall
-
-- **Github地址**:[https://github.com/YunaiV/onemall](https://github.com/YunaiV/onemall)
-- **star**: 1.2k
-- **介绍**: mall 商城,基于微服务的思想,构建在 B2C 电商场景下的项目实战。核心技术栈,是 Spring Boot + Dubbo 。未来,会重构成 Spring Cloud Alibaba 。
-
-### Guns
-
-- **Github地址**:[https://github.com/stylefeng/Guns](https://github.com/stylefeng/Guns)
-- **star**: 2.3k
-- **介绍**: Guns基于SpringBoot 2,致力于做更简洁的后台管理系统,完美整合springmvc + shiro + mybatis-plus + beetl!Guns项目代码简洁,注释丰富,上手容易,同时Guns包含许多基础模块(用户管理,角色管理,部门管理,字典管理等10个模块),可以直接作为一个后台管理系统的脚手架!
-
-### SpringCloud
-
-- **Github地址**:[https://github.com/YunaiV/onemall](https://github.com/YunaiV/onemall)
-- **star**: 1.2k
-- **介绍**: mall 商城,基于微服务的思想,构建在 B2C 电商场景下的项目实战。核心技术栈,是 Spring Boot + Dubbo 。未来,会重构成 Spring Cloud Alibaba 。
-
-### SpringBoot-Shiro-Vue
-
-- **Github地址**:[https://github.com/Heeexy/SpringBoot-Shiro-Vue](https://github.com/Heeexy/SpringBoot-Shiro-Vue)
-- **star**: 1.8k
-- **介绍**: 提供一套基于Spring Boot-Shiro-Vue的权限管理思路.前后端都加以控制,做到按钮/接口级别的权限。
-
-### newbee-mall
-
-最近开源的一个商城项目。
-
-- **Github地址**:[https://github.com/newbee-ltd/newbee-mall](https://github.com/newbee-ltd/newbee-mall)
-- **star**: 50
-- **介绍**: newbee-mall 项目是一套电商系统,包括 newbee-mall 商城系统及 newbee-mall-admin 商城后台管理系统,基于 Spring Boot 2.X 及相关技术栈开发。 前台商城系统包含首页门户、商品分类、新品上线、首页轮播、商品推荐、商品搜索、商品展示、购物车、订单结算、订单流程、个人订单管理、会员中心、帮助中心等模块。 后台管理系统包含数据面板、轮播图管理、商品管理、订单管理、会员管理、分类管理、设置等模块。
-
-
-
diff --git a/docs/dataStructures-algorithms/Backtracking-NQueens.md b/docs/dataStructures-algorithms/Backtracking-NQueens.md
deleted file mode 100644
index bac262d0fbb..00000000000
--- a/docs/dataStructures-algorithms/Backtracking-NQueens.md
+++ /dev/null
@@ -1,145 +0,0 @@
-# N皇后
-[51. N皇后](https://leetcode-cn.com/problems/n-queens/)
-### 题目描述
-> n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
->
-
->
-上图为 8 皇后问题的一种解法。
->
-给定一个整数 n,返回所有不同的 n 皇后问题的解决方案。
->
-每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。
-
-示例:
-
-```
-输入: 4
-输出: [
- [".Q..", // 解法 1
- "...Q",
- "Q...",
- "..Q."],
-
- ["..Q.", // 解法 2
- "Q...",
- "...Q",
- ".Q.."]
-]
-解释: 4 皇后问题存在两个不同的解法。
-```
-
-### 问题分析
-约束条件为每个棋子所在的行、列、对角线都不能有另一个棋子。
-
-使用一维数组表示一种解法,下标(index)表示行,值(value)表示该行的Q(皇后)在哪一列。
-每行只存储一个元素,然后递归到下一行,这样就不用判断行了,只需要判断列和对角线。
-### Solution1
-当result[row] = column时,即row行的棋子在column列。
-
-对于[0, row-1]的任意一行(i 行),若 row 行的棋子和 i 行的棋子在同一列,则有result[i] == column;
-若 row 行的棋子和 i 行的棋子在同一对角线,等腰直角三角形两直角边相等,即 row - i == Math.abs(result[i] - column)
-
-布尔类型变量 isValid 的作用是剪枝,减少不必要的递归。
-```
-public List> solveNQueens(int n) {
- // 下标代表行,值代表列。如result[0] = 3 表示第1行的Q在第3列
- int[] result = new int[n];
- List> resultList = new LinkedList<>();
- dfs(resultList, result, 0, n);
- return resultList;
-}
-
-void dfs(List> resultList, int[] result, int row, int n) {
- // 递归终止条件
- if (row == n) {
- List list = new LinkedList<>();
- for (int x = 0; x < n; ++x) {
- StringBuilder sb = new StringBuilder();
- for (int y = 0; y < n; ++y)
- sb.append(result[x] == y ? "Q" : ".");
- list.add(sb.toString());
- }
- resultList.add(list);
- return;
- }
- for (int column = 0; column < n; ++column) {
- boolean isValid = true;
- result[row] = column;
- /*
- * 逐行往下考察每一行。同列,result[i] == column
- * 同对角线,row - i == Math.abs(result[i] - column)
- */
- for (int i = row - 1; i >= 0; --i) {
- if (result[i] == column || row - i == Math.abs(result[i] - column)) {
- isValid = false;
- break;
- }
- }
- if (isValid) dfs(resultList, result, row + 1, n);
- }
-}
-```
-### Solution2
-使用LinkedList表示一种解法,下标(index)表示行,值(value)表示该行的Q(皇后)在哪一列。
-
-解法二和解法一的不同在于,相同列以及相同对角线的校验。
-将对角线抽象成【一次函数】这个简单的数学模型,根据一次函数的截距是常量这一特性进行校验。
-
-这里,我将右上-左下对角线,简称为“\”对角线;左上-右下对角线简称为“/”对角线。
-
-“/”对角线斜率为1,对应方程为y = x + b,其中b为截距。
-对于线上任意一点,均有y - x = b,即row - i = b;
-定义一个布尔类型数组anti_diag,将b作为下标,当anti_diag[b] = true时,表示相应对角线上已经放置棋子。
-但row - i有可能为负数,负数不能作为数组下标,row - i 的最小值为-n(当row = 0,i = n时),可以加上n作为数组下标,即将row -i + n 作为数组下标。
-row - i + n 的最大值为 2n(当row = n,i = 0时),故anti_diag的容量设置为 2n 即可。
-
-
-
-“\”对角线斜率为-1,对应方程为y = -x + b,其中b为截距。
-对于线上任意一点,均有y + x = b,即row + i = b;
-同理,定义数组main_diag,将b作为下标,当main_diag[row + i] = true时,表示相应对角线上已经放置棋子。
-
-有了两个校验对角线的数组,再来定义一个用于校验列的数组cols,这个太简单啦,不解释。
-
-**解法二时间复杂度为O(n!),在校验相同列和相同对角线时,引入三个布尔类型数组进行判断。相比解法一,少了一层循环,用空间换时间。**
-
-```
-List> resultList = new LinkedList<>();
-
-public List> solveNQueens(int n) {
- boolean[] cols = new boolean[n];
- boolean[] main_diag = new boolean[2 * n];
- boolean[] anti_diag = new boolean[2 * n];
- LinkedList result = new LinkedList<>();
- dfs(result, 0, cols, main_diag, anti_diag, n);
- return resultList;
-}
-
-void dfs(LinkedList result, int row, boolean[] cols, boolean[] main_diag, boolean[] anti_diag, int n) {
- if (row == n) {
- List list = new LinkedList<>();
- for (int x = 0; x < n; ++x) {
- StringBuilder sb = new StringBuilder();
- for (int y = 0; y < n; ++y)
- sb.append(result.get(x) == y ? "Q" : ".");
- list.add(sb.toString());
- }
- resultList.add(list);
- return;
- }
- for (int i = 0; i < n; ++i) {
- if (cols[i] || main_diag[row + i] || anti_diag[row - i + n])
- continue;
- result.add(i);
- cols[i] = true;
- main_diag[row + i] = true;
- anti_diag[row - i + n] = true;
- dfs(result, row + 1, cols, main_diag, anti_diag, n);
- result.removeLast();
- cols[i] = false;
- main_diag[row + i] = false;
- anti_diag[row - i + n] = false;
- }
-}
-```
\ No newline at end of file
diff --git "a/docs/dataStructures-algorithms/\345\205\254\345\217\270\347\234\237\351\242\230.md" "b/docs/dataStructures-algorithms/\345\205\254\345\217\270\347\234\237\351\242\230.md"
deleted file mode 100644
index c78ed8f3e5a..00000000000
--- "a/docs/dataStructures-algorithms/\345\205\254\345\217\270\347\234\237\351\242\230.md"
+++ /dev/null
@@ -1,254 +0,0 @@
-# 网易 2018
-
-下面三道编程题来自网易2018校招编程题,这三道应该来说是非常简单的编程题了,这些题目大家稍微有点编程和数学基础的话应该没什么问题。看答案之前一定要自己先想一下如果是自己做的话会怎么去做,然后再对照这我的答案看看,和你自己想的有什么区别?那一种方法更好?
-
-## 问题
-
-### 一 获得特定数量硬币问题
-
-小易准备去魔法王国采购魔法神器,购买魔法神器需要使用魔法币,但是小易现在一枚魔法币都没有,但是小易有两台魔法机器可以通过投入x(x可以为0)个魔法币产生更多的魔法币。
-
-魔法机器1:如果投入x个魔法币,魔法机器会将其变为2x+1个魔法币
-
-魔法机器2:如果投入x个魔法币,魔法机器会将其变为2x+2个魔法币
-
-小易采购魔法神器总共需要n个魔法币,所以小易只能通过两台魔法机器产生恰好n个魔法币,小易需要你帮他设计一个投入方案使他最后恰好拥有n个魔法币。
-
-**输入描述:** 输入包括一行,包括一个正整数n(1 ≤ n ≤ 10^9),表示小易需要的魔法币数量。
-
-**输出描述:** 输出一个字符串,每个字符表示该次小易选取投入的魔法机器。其中只包含字符'1'和'2'。
-
-**输入例子1:** 10
-
-**输出例子1:** 122
-
-### 二 求“相反数”问题
-
-为了得到一个数的"相反数",我们将这个数的数字顺序颠倒,然后再加上原先的数得到"相反数"。例如,为了得到1325的"相反数",首先我们将该数的数字顺序颠倒,我们得到5231,之后再加上原先的数,我们得到5231+1325=6556.如果颠倒之后的数字有前缀零,前缀零将会被忽略。例如n = 100, 颠倒之后是1.
-
-**输入描述:** 输入包括一个整数n,(1 ≤ n ≤ 10^5)
-
-**输出描述:** 输出一个整数,表示n的相反数
-
-**输入例子1:** 1325
-
-**输出例子1:** 6556
-
-### 三 字符串碎片的平均长度
-
-一个由小写字母组成的字符串可以看成一些同一字母的最大碎片组成的。例如,"aaabbaaac"是由下面碎片组成的:'aaa','bb','c'。牛牛现在给定一个字符串,请你帮助计算这个字符串的所有碎片的平均长度是多少。
-
-**输入描述:** 输入包括一个字符串s,字符串s的长度length(1 ≤ length ≤ 50),s只含小写字母('a'-'z')
-
-**输出描述:** 输出一个整数,表示所有碎片的平均长度,四舍五入保留两位小数。
-
-**如样例所示:** s = "aaabbaaac"
-所有碎片的平均长度 = (3 + 2 + 3 + 1) / 4 = 2.25
-
-**输入例子1:** aaabbaaac
-
-**输出例子1:** 2.25
-
-## 答案
-
-### 一 获得特定数量硬币问题
-
-#### 分析:
-
-作为该试卷的第一题,这道题应该只要思路正确就很简单了。
-
-解题关键:明确魔法机器1只能产生奇数,魔法机器2只能产生偶数即可。我们从后往前一步一步推回去即可。
-
-#### 示例代码
-
-注意:由于用户的输入不确定性,一般是为了程序高可用性使需要将捕获用户输入异常然后友好提示用户输入类型错误并重新输入的。所以下面我给了两个版本,这两个版本都是正确的。这里只是给大家演示如何捕获输入类型异常,后面的题目中我给的代码没有异常处理的部分,参照下面两个示例代码,应该很容易添加。(PS:企业面试中没有明确就不用添加异常处理,当然你有的话也更好)
-
-**不带输入异常处理判断的版本:**
-
-```java
-import java.util.Scanner;
-
-public class Main2 {
- // 解题关键:明确魔法机器1只能产生奇数,魔法机器2只能产生偶数即可。我们从后往前一步一步推回去即可。
-
- public static void main(String[] args) {
- System.out.println("请输入要获得的硬币数量:");
- Scanner scanner = new Scanner(System.in);
- int coincount = scanner.nextInt();
- StringBuilder sb = new StringBuilder();
- while (coincount >= 1) {
- // 偶数的情况
- if (coincount % 2 == 0) {
- coincount = (coincount - 2) / 2;
- sb.append("2");
- // 奇数的情况
- } else {
- coincount = (coincount - 1) / 2;
- sb.append("1");
- }
- }
- // 输出反转后的字符串
- System.out.println(sb.reverse());
-
- }
-}
-```
-
-**带输入异常处理判断的版本(当输入的不是整数的时候会提示重新输入):**
-
-```java
-import java.util.InputMismatchException;
-import java.util.Scanner;
-
-
-public class Main {
- // 解题关键:明确魔法机器1只能产生奇数,魔法机器2只能产生偶数即可。我们从后往前一步一步推回去即可。
-
- public static void main(String[] args) {
- System.out.println("请输入要获得的硬币数量:");
- Scanner scanner = new Scanner(System.in);
- boolean flag = true;
- while (flag) {
- try {
- int coincount = scanner.nextInt();
- StringBuilder sb = new StringBuilder();
- while (coincount >= 1) {
- // 偶数的情况
- if (coincount % 2 == 0) {
- coincount = (coincount - 2) / 2;
- sb.append("2");
- // 奇数的情况
- } else {
- coincount = (coincount - 1) / 2;
- sb.append("1");
- }
- }
- // 输出反转后的字符串
- System.out.println(sb.reverse());
- flag=false;//程序结束
- } catch (InputMismatchException e) {
- System.out.println("输入数据类型不匹配,请您重新输入:");
- scanner.nextLine();
- continue;
- }
- }
-
- }
-}
-
-```
-
-### 二 求“相反数”问题
-
-#### 分析:
-
-解决本道题有几种不同的方法,但是最快速的方法就是利用reverse()方法反转字符串然后再将字符串转换成int类型的整数,这个方法是快速解决本题关键。我们先来回顾一下下面两个知识点:
-
-**1)String转int;**
-
-在 Java 中要将 String 类型转化为 int 类型时,需要使用 Integer 类中的 parseInt() 方法或者 valueOf() 方法进行转换.
-
-```java
- String str = "123";
- int a = Integer.parseInt(str);
-```
-
- 或
-
-```java
- String str = "123";
- int a = Integer.valueOf(str).intValue();
-```
-
-**2)next()和nextLine()的区别**
-
-在Java中输入字符串有两种方法,就是next()和nextLine().两者的区别就是:nextLine()的输入是碰到回车就终止输入,而next()方法是碰到空格,回车,Tab键都会被视为终止符。所以next()不会得到带空格的字符串,而nextLine()可以得到带空格的字符串。
-
-#### 示例代码:
-
-```java
-import java.util.Scanner;
-
-/**
- * 本题关键:①String转int;②next()和nextLine()的区别
- */
-public class Main {
-
- public static void main(String[] args) {
-
- System.out.println("请输入一个整数:");
- Scanner scanner = new Scanner(System.in);
- String s=scanner.next();
- //将字符串转换成数字
- int number1=Integer.parseInt(s);
- //将字符串倒序后转换成数字
- //因为Integer.parseInt()的参数类型必须是字符串所以必须加上toString()
- int number2=Integer.parseInt(new StringBuilder(s).reverse().toString());
- System.out.println(number1+number2);
-
- }
-}
-```
-
-### 三 字符串碎片的平均长度
-
-#### 分析:
-
-这道题的意思也就是要求:(字符串的总长度)/(相同字母团构成的字符串的个数)。
-
-这样就很简单了,就变成了字符串的字符之间的比较。如果需要比较字符串的字符的话,我们可以利用charAt(i)方法:取出特定位置的字符与后一个字符比较,或者利用toCharArray()方法将字符串转换成字符数组采用同样的方法做比较。
-
-#### 示例代码
-
-**利用charAt(i)方法:**
-
-```java
-import java.util.Scanner;
-
-public class Main {
-
- public static void main(String[] args) {
-
- Scanner sc = new Scanner(System.in);
- while (sc.hasNext()) {
- String s = sc.next();
- //个数至少为一个
- float count = 1;
- for (int i = 0; i < s.length() - 1; i++) {
- if (s.charAt(i) != s.charAt(i + 1)) {
- count++;
- }
- }
- System.out.println(s.length() / count);
- }
- }
-
-}
-```
-
-**利用toCharArray()方法:**
-
-```java
-import java.util.Scanner;
-
-public class Main2 {
-
- public static void main(String[] args) {
-
- Scanner sc = new Scanner(System.in);
- while (sc.hasNext()) {
- String s = sc.next();
- //个数至少为一个
- float count = 1;
- char [] stringArr = s.toCharArray();
- for (int i = 0; i < stringArr.length - 1; i++) {
- if (stringArr[i] != stringArr[i + 1]) {
- count++;
- }
- }
- System.out.println(s.length() / count);
- }
- }
-
-}
-```
\ No newline at end of file
diff --git "a/docs/dataStructures-algorithms/\346\225\260\346\215\256\347\273\223\346\236\204.md" "b/docs/dataStructures-algorithms/\346\225\260\346\215\256\347\273\223\346\236\204.md"
deleted file mode 100644
index dfb5bc18586..00000000000
--- "a/docs/dataStructures-algorithms/\346\225\260\346\215\256\347\273\223\346\236\204.md"
+++ /dev/null
@@ -1,192 +0,0 @@
-下面只是简单地总结,给了一些参考文章,后面会对这部分内容进行重构。
-
-
-- [Queue](#queue)
- - [什么是队列](#什么是队列)
- - [队列的种类](#队列的种类)
- - [Java 集合框架中的队列 Queue](#java-集合框架中的队列-queue)
- - [推荐文章](#推荐文章)
-- [Set](#set)
- - [什么是 Set](#什么是-set)
- - [补充:有序集合与无序集合说明](#补充:有序集合与无序集合说明)
- - [HashSet 和 TreeSet 底层数据结构](#hashset-和-treeset-底层数据结构)
- - [推荐文章](#推荐文章-1)
-- [List](#list)
- - [什么是List](#什么是list)
- - [List的常见实现类](#list的常见实现类)
- - [ArrayList 和 LinkedList 源码学习](#arraylist-和-linkedlist-源码学习)
- - [推荐阅读](#推荐阅读)
-- [Map](#map)
-- [树](#树)
-
-
-
-
-## Queue
-
-### 什么是队列
-队列是数据结构中比较重要的一种类型,它支持 FIFO,尾部添加、头部删除(先进队列的元素先出队列),跟我们生活中的排队类似。
-
-### 队列的种类
-
-- **单队列**(单队列就是常见的队列, 每次添加元素时,都是添加到队尾,存在“假溢出”的问题也就是明明有位置却不能添加的情况)
-- **循环队列**(避免了“假溢出”的问题)
-
-### Java 集合框架中的队列 Queue
-
-Java 集合中的 Queue 继承自 Collection 接口 ,Deque, LinkedList, PriorityQueue, BlockingQueue 等类都实现了它。
-Queue 用来存放 等待处理元素 的集合,这种场景一般用于缓冲、并发访问。
-除了继承 Collection 接口的一些方法,Queue 还添加了额外的 添加、删除、查询操作。
-
-### 推荐文章
-
-- [Java 集合深入理解(9):Queue 队列](https://blog.csdn.net/u011240877/article/details/52860924)
-
-## Set
-
-### 什么是 Set
-Set 继承于 Collection 接口,是一个不允许出现重复元素,并且无序的集合,主要 HashSet 和 TreeSet 两大实现类。
-
-在判断重复元素的时候,HashSet 集合会调用 hashCode()和 equal()方法来实现;TreeSet 集合会调用compareTo方法来实现。
-
-### 补充:有序集合与无序集合说明
-- 有序集合:集合里的元素可以根据 key 或 index 访问 (List、Map)
-- 无序集合:集合里的元素只能遍历。(Set)
-
-
-### HashSet 和 TreeSet 底层数据结构
-
-**HashSet** 是哈希表结构,主要利用 HashMap 的 key 来存储元素,计算插入元素的 hashCode 来获取元素在集合中的位置;
-
-**TreeSet** 是红黑树结构,每一个元素都是树中的一个节点,插入的元素都会进行排序;
-
-
-### 推荐文章
-
-- [Java集合--Set(基础)](https://www.jianshu.com/p/b48c47a42916)
-
-## List
-
-### 什么是List
-
-在 List 中,用户可以精确控制列表中每个元素的插入位置,另外用户可以通过整数索引(列表中的位置)访问元素,并搜索列表中的元素。 与 Set 不同,List 通常允许重复的元素。 另外 List 是有序集合而 Set 是无序集合。
-
-### List的常见实现类
-
-**ArrayList** 是一个数组队列,相当于动态数组。它由数组实现,随机访问效率高,随机插入、随机删除效率低。
-
-**LinkedList** 是一个双向链表。它也可以被当作堆栈、队列或双端队列进行操作。LinkedList随机访问效率低,但随机插入、随机删除效率高。
-
-**Vector** 是矢量队列,和ArrayList一样,它也是一个动态数组,由数组实现。但是ArrayList是非线程安全的,而Vector是线程安全的。
-
-**Stack** 是栈,它继承于Vector。它的特性是:先进后出(FILO, First In Last Out)。相关阅读:[java数据结构与算法之栈(Stack)设计与实现](https://blog.csdn.net/javazejian/article/details/53362993)
-
-### ArrayList 和 LinkedList 源码学习
-
-- [ArrayList 源码学习](https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/collection/ArrayList.md)
-- [LinkedList 源码学习](https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/collection/LinkedList.md)
-
-### 推荐阅读
-
-- [java 数据结构与算法之顺序表与链表深入分析](https://blog.csdn.net/javazejian/article/details/52953190)
-
-
-## Map
-
-
-- [集合框架源码学习之 HashMap(JDK1.8)](https://juejin.im/post/5ab0568b5188255580020e56)
-- [ConcurrentHashMap 实现原理及源码分析](https://link.juejin.im/?target=http%3A%2F%2Fwww.cnblogs.com%2Fchengxiao%2Fp%2F6842045.html)
-
-## 树
- * ### 1 二叉树
-
- [二叉树](https://baike.baidu.com/item/%E4%BA%8C%E5%8F%89%E6%A0%91)(百度百科)
-
- (1)[完全二叉树](https://baike.baidu.com/item/%E5%AE%8C%E5%85%A8%E4%BA%8C%E5%8F%89%E6%A0%91)——若设二叉树的高度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第h层有叶子结点,并且叶子结点都是从左到右依次排布,这就是完全二叉树。
-
- (2)[满二叉树](https://baike.baidu.com/item/%E6%BB%A1%E4%BA%8C%E5%8F%89%E6%A0%91)——除了叶结点外每一个结点都有左右子叶且叶子结点都处在最底层的二叉树。
-
- (3)[平衡二叉树](https://baike.baidu.com/item/%E5%B9%B3%E8%A1%A1%E4%BA%8C%E5%8F%89%E6%A0%91/10421057)——平衡二叉树又被称为AVL树(区别于AVL算法),它是一棵二叉排序树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
-
- * ### 2 完全二叉树
-
- [完全二叉树](https://baike.baidu.com/item/%E5%AE%8C%E5%85%A8%E4%BA%8C%E5%8F%89%E6%A0%91)(百度百科)
-
- 完全二叉树:叶节点只能出现在最下层和次下层,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树。
- * ### 3 满二叉树
-
- [满二叉树](https://baike.baidu.com/item/%E6%BB%A1%E4%BA%8C%E5%8F%89%E6%A0%91)(百度百科,国内外的定义不同)
-
- 国内教程定义:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是(2^k) -1 ,则它就是满二叉树。
- * ### 堆
-
- [数据结构之堆的定义](https://blog.csdn.net/qq_33186366/article/details/51876191)
-
- 堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。
- * ### 4 二叉查找树(BST)
-
- [浅谈算法和数据结构: 七 二叉查找树](http://www.cnblogs.com/yangecnu/p/Introduce-Binary-Search-Tree.html)
-
- 二叉查找树的特点:
-
- 1. 若任意节点的左子树不空,则左子树上所有结点的 值均小于它的根结点的值;
- 2. 若任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 3. 任意节点的左、右子树也分别为二叉查找树;
- 4. 没有键值相等的节点(no duplicate nodes)。
-
- * ### 5 平衡二叉树(Self-balancing binary search tree)
-
- [ 平衡二叉树](https://baike.baidu.com/item/%E5%B9%B3%E8%A1%A1%E4%BA%8C%E5%8F%89%E6%A0%91)(百度百科,平衡二叉树的常用实现方法有红黑树、AVL、替罪羊树、Treap、伸展树等)
- * ### 6 红黑树
-
- - 红黑树特点:
- 1. 每个节点非红即黑;
- 2. 根节点总是黑色的;
- 3. 每个叶子节点都是黑色的空节点(NIL节点);
- 4. 如果节点是红色的,则它的子节点必须是黑色的(反之不一定);
- 5. 从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度)。
-
- - 红黑树的应用:
-
- TreeMap、TreeSet以及JDK1.8之后的HashMap底层都用到了红黑树。
-
- - 为什么要用红黑树
-
- 简单来说红黑树就是为了解决二叉查找树的缺陷,因为二叉查找树在某些情况下会退化成一个线性结构。详细了解可以查看 [漫画:什么是红黑树?](https://juejin.im/post/5a27c6946fb9a04509096248#comment)(也介绍到了二叉查找树,非常推荐)
-
- - 推荐文章:
- - [漫画:什么是红黑树?](https://juejin.im/post/5a27c6946fb9a04509096248#comment)(也介绍到了二叉查找树,非常推荐)
- - [寻找红黑树的操作手册](http://dandanlove.com/2018/03/18/red-black-tree/)(文章排版以及思路真的不错)
- - [红黑树深入剖析及Java实现](https://zhuanlan.zhihu.com/p/24367771)(美团点评技术团队)
- * ### 7 B-,B+,B*树
-
- [二叉树学习笔记之B树、B+树、B*树 ](https://yq.aliyun.com/articles/38345)
-
- [《B-树,B+树,B*树详解》](https://blog.csdn.net/aqzwss/article/details/53074186)
-
- [《B-树,B+树与B*树的优缺点比较》](https://blog.csdn.net/bigtree_3721/article/details/73632405)
-
- B-树(或B树)是一种平衡的多路查找(又称排序)树,在文件系统中有所应用。主要用作文件的索引。其中的B就表示平衡(Balance)
- 1. B+ 树的叶子节点链表结构相比于 B- 树便于扫库,和范围检索。
- 2. B+树支持range-query(区间查询)非常方便,而B树不支持。这是数据库选用B+树的最主要原因。
- 3. B\*树 是B+树的变体,B\*树分配新结点的概率比B+树要低,空间使用率更高;
- * ### 8 LSM 树
-
- [[HBase] LSM树 VS B+树](https://blog.csdn.net/dbanote/article/details/8897599)
-
- B+树最大的性能问题是会产生大量的随机IO
-
- 为了克服B+树的弱点,HBase引入了LSM树的概念,即Log-Structured Merge-Trees。
-
- [LSM树由来、设计思想以及应用到HBase的索引](http://www.cnblogs.com/yanghuahui/p/3483754.html)
-
-
-## 图
-
-
-
-
-## BFS及DFS
-
-- [《使用BFS及DFS遍历树和图的思路及实现》](https://blog.csdn.net/Gene1994/article/details/85097507)
-
diff --git "a/docs/dataStructures-algorithms/\347\256\227\346\263\225\345\255\246\344\271\240\350\265\204\346\272\220\346\216\250\350\215\220.md" "b/docs/dataStructures-algorithms/\347\256\227\346\263\225\345\255\246\344\271\240\350\265\204\346\272\220\346\216\250\350\215\220.md"
deleted file mode 100644
index 4c5df56a862..00000000000
--- "a/docs/dataStructures-algorithms/\347\256\227\346\263\225\345\255\246\344\271\240\350\265\204\346\272\220\346\216\250\350\215\220.md"
+++ /dev/null
@@ -1,52 +0,0 @@
-我比较推荐大家可以刷一下 Leetcode ,我自己平时没事也会刷一下,我觉得刷 Leetcode 不仅是为了能让你更从容地面对面试中的手撕算法问题,更可以提高你的编程思维能力、解决问题的能力以及你对某门编程语言 API 的熟练度。当然牛客网也有一些算法题,我下面也整理了一些。
-
-## LeetCode
-
-- [LeetCode(中国)官网](https://leetcode-cn.com/)
-
-- [如何高效地使用 LeetCode](https://leetcode-cn.com/articles/%E5%A6%82%E4%BD%95%E9%AB%98%E6%95%88%E5%9C%B0%E4%BD%BF%E7%94%A8-leetcode/)
-
-
-## 牛客网
-
-- [牛客网官网](https://www.nowcoder.com)
-- [剑指offer编程题](https://www.nowcoder.com/ta/coding-interviews)
-
-- [2017校招真题](https://www.nowcoder.com/ta/2017test)
-- [华为机试题](https://www.nowcoder.com/ta/huawei)
-
-
-## 公司真题
-
-- [ 网易2018校园招聘编程题真题集合](https://www.nowcoder.com/test/6910869/summary)
-- [ 网易2018校招内推编程题集合](https://www.nowcoder.com/test/6291726/summary)
-- [2017年校招全国统一模拟笔试(第五场)编程题集合](https://www.nowcoder.com/test/5986669/summary)
-- [2017年校招全国统一模拟笔试(第四场)编程题集合](https://www.nowcoder.com/test/5507925/summary)
-- [2017年校招全国统一模拟笔试(第三场)编程题集合](https://www.nowcoder.com/test/5217106/summary)
-- [2017年校招全国统一模拟笔试(第二场)编程题集合](https://www.nowcoder.com/test/4546329/summary)
-- [ 2017年校招全国统一模拟笔试(第一场)编程题集合](https://www.nowcoder.com/test/4236887/summary)
-- [百度2017春招笔试真题编程题集合](https://www.nowcoder.com/test/4998655/summary)
-- [网易2017春招笔试真题编程题集合](https://www.nowcoder.com/test/4575457/summary)
-- [网易2017秋招编程题集合](https://www.nowcoder.com/test/2811407/summary)
-- [网易有道2017内推编程题](https://www.nowcoder.com/test/2385858/summary)
-- [ 滴滴出行2017秋招笔试真题-编程题汇总](https://www.nowcoder.com/test/3701760/summary)
-- [腾讯2017暑期实习生编程题](https://www.nowcoder.com/test/1725829/summary)
-- [今日头条2017客户端工程师实习生笔试题](https://www.nowcoder.com/test/1649301/summary)
-- [今日头条2017后端工程师实习生笔试题](https://www.nowcoder.com/test/1649268/summary)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/docs/database/MySQL Index.md b/docs/database/MySQL Index.md
deleted file mode 100644
index b25589fc7d2..00000000000
--- a/docs/database/MySQL Index.md
+++ /dev/null
@@ -1,112 +0,0 @@
-
-# 思维导图-索引篇
-
-> 系列思维导图源文件(数据库+架构)以及思维导图制作软件—XMind8 破解安装,公众号后台回复:**“思维导图”** 免费领取!(下面的图片不是很清楚,原图非常清晰,另外提供给大家源文件也是为了大家根据自己需要进行修改)
-
-
-
-> **下面是我补充的一些内容**
-
-# 为什么索引能提高查询速度
-
-> 以下内容整理自:
-> 地址: https://juejin.im/post/5b55b842f265da0f9e589e79
-> 作者 :Java3y
-
-### 先从 MySQL 的基本存储结构说起
-
-MySQL的基本存储结构是页(记录都存在页里边):
-
-
-
-
-
- - **各个数据页可以组成一个双向链表**
- - **每个数据页中的记录又可以组成一个单向链表**
- - 每个数据页都会为存储在它里边儿的记录生成一个页目录,在通过主键查找某条记录的时候可以在页目录中使用二分法快速定位到对应的槽,然后再遍历该槽对应分组中的记录即可快速找到指定的记录
- - 以其他列(非主键)作为搜索条件:只能从最小记录开始依次遍历单链表中的每条记录。
-
-所以说,如果我们写select * from user where indexname = 'xxx'这样没有进行任何优化的sql语句,默认会这样做:
-
-1. **定位到记录所在的页:需要遍历双向链表,找到所在的页**
-2. **从所在的页内中查找相应的记录:由于不是根据主键查询,只能遍历所在页的单链表了**
-
-很明显,在数据量很大的情况下这样查找会很慢!这样的时间复杂度为O(n)。
-
-
-### 使用索引之后
-
-索引做了些什么可以让我们查询加快速度呢?其实就是将无序的数据变成有序(相对):
-
-
-
-要找到id为8的记录简要步骤:
-
-
-
-很明显的是:没有用索引我们是需要遍历双向链表来定位对应的页,现在通过 **“目录”** 就可以很快地定位到对应的页上了!(二分查找,时间复杂度近似为O(logn))
-
-其实底层结构就是B+树,B+树作为树的一种实现,能够让我们很快地查找出对应的记录。
-
-# 关于索引其他重要的内容补充
-
-> 以下内容整理自:《Java工程师修炼之道》
-
-
-### 最左前缀原则
-
-MySQL中的索引可以以一定顺序引用多列,这种索引叫作联合索引。如User表的name和city加联合索引就是(name,city),而最左前缀原则指的是,如果查询的时候查询条件精确匹配索引的左边连续一列或几列,则此列就可以被用到。如下:
-
-```
-select * from user where name=xx and city=xx ; //可以命中索引
-select * from user where name=xx ; // 可以命中索引
-select * from user where city=xx ; // 无法命中索引
-```
-这里需要注意的是,查询的时候如果两个条件都用上了,但是顺序不同,如 `city= xx and name =xx`,那么现在的查询引擎会自动优化为匹配联合索引的顺序,这样是能够命中索引的。
-
-由于最左前缀原则,在创建联合索引时,索引字段的顺序需要考虑字段值去重之后的个数,较多的放前面。ORDER BY子句也遵循此规则。
-
-### 注意避免冗余索引
-
-冗余索引指的是索引的功能相同,能够命中 就肯定能命中 ,那么 就是冗余索引如(name,city )和(name )这两个索引就是冗余索引,能够命中后者的查询肯定是能够命中前者的 在大多数情况下,都应该尽量扩展已有的索引而不是创建新索引。
-
-MySQL 5.7 版本后,可以通过查询 sys 库的 `schema_redundant_indexes` 表来查看冗余索引
-
-### Mysql如何为表字段添加索引???
-
-1.添加PRIMARY KEY(主键索引)
-
-```
-ALTER TABLE `table_name` ADD PRIMARY KEY ( `column` )
-```
-2.添加UNIQUE(唯一索引)
-
-```
-ALTER TABLE `table_name` ADD UNIQUE ( `column` )
-```
-
-3.添加INDEX(普通索引)
-
-```
-ALTER TABLE `table_name` ADD INDEX index_name ( `column` )
-```
-
-4.添加FULLTEXT(全文索引)
-
-```
-ALTER TABLE `table_name` ADD FULLTEXT ( `column`)
-```
-
-5.添加多列索引
-
-```
-ALTER TABLE `table_name` ADD INDEX index_name ( `column1`, `column2`, `column3` )
-```
-
-
-# 参考
-
-- 《Java工程师修炼之道》
-- 《MySQL高性能书籍_第3版》
-- https://juejin.im/post/5b55b842f265da0f9e589e79
-
diff --git a/docs/database/MySQL.md b/docs/database/MySQL.md
deleted file mode 100644
index 64dfad2fd79..00000000000
--- a/docs/database/MySQL.md
+++ /dev/null
@@ -1,328 +0,0 @@
-点击关注[公众号](#公众号)及时获取笔主最新更新文章,并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。
-
-- [书籍推荐](#书籍推荐)
-- [文字教程推荐](#文字教程推荐)
-- [视频教程推荐](#视频教程推荐)
-- [常见问题总结](#常见问题总结)
- - [什么是MySQL?](#什么是mysql)
- - [存储引擎](#存储引擎)
- - [一些常用命令](#一些常用命令)
- - [MyISAM和InnoDB区别](#myisam和innodb区别)
- - [字符集及校对规则](#字符集及校对规则)
- - [索引](#索引)
- - [查询缓存的使用](#查询缓存的使用)
- - [什么是事务?](#什么是事务)
- - [事物的四大特性(ACID)](#事物的四大特性acid)
- - [并发事务带来哪些问题?](#并发事务带来哪些问题)
- - [事务隔离级别有哪些?MySQL的默认隔离级别是?](#事务隔离级别有哪些mysql的默认隔离级别是)
- - [锁机制与InnoDB锁算法](#锁机制与innodb锁算法)
- - [大表优化](#大表优化)
- - [1. 限定数据的范围](#1-限定数据的范围)
- - [2. 读/写分离](#2-读写分离)
- - [3. 垂直分区](#3-垂直分区)
- - [4. 水平分区](#4-水平分区)
- - [一条SQL语句在MySQL中如何执行的](#一条sql语句在mysql中如何执行的)
- - [MySQL高性能优化规范建议](#mysql高性能优化规范建议)
- - [一条SQL语句执行得很慢的原因有哪些?](#一条sql语句执行得很慢的原因有哪些)
-
-
-
-## 书籍推荐
-
-- 《SQL基础教程(第2版)》 (入门级)
-- 《高性能MySQL : 第3版》 (进阶)
-
-## 文字教程推荐
-
-- [SQL Tutorial](https://www.w3schools.com/sql/default.asp) (SQL语句学习,英文)、[SQL Tutorial](https://www.w3school.com.cn/sql/index.asp)(SQL语句学习,中文)、[SQL语句在线练习](https://www.w3schools.com/sql/exercise.asp) (非常不错)
-- [Github-MySQL入门教程(MySQL tutorial book)](https://github.com/jaywcjlove/mysql-tutorial) (从零开始学习MySQL,主要是面向MySQL数据库管理系统初学者)
-- [官方教程](https://dev.mysql.com/doc/refman/5.7/)
-- [MySQL 教程(菜鸟教程)](http://www.runoob.com/MySQL/MySQL-tutorial.html)
-
-## 相关资源推荐
-
-- [中国5级行政区域mysql库](https://github.com/kakuilan/china_area_mysql)
-
-## 视频教程推荐
-
-**基础入门:** [与MySQL的零距离接触-慕课网](https://www.imooc.com/learn/122)
-
-**MySQL开发技巧:** [MySQL开发技巧(一)](https://www.imooc.com/learn/398) [MySQL开发技巧(二)](https://www.imooc.com/learn/427) [MySQL开发技巧(三)](https://www.imooc.com/learn/449)
-
-**MySQL5.7新特性及相关优化技巧:** [MySQL5.7版本新特性](https://www.imooc.com/learn/533) [性能优化之MySQL优化](https://www.imooc.com/learn/194)
-
-[MySQL集群(PXC)入门](https://www.imooc.com/learn/993) [MyCAT入门及应用](https://www.imooc.com/learn/951)
-
-## 常见问题总结
-
-### 什么是MySQL?
-
-MySQL 是一种关系型数据库,在Java企业级开发中非常常用,因为 MySQL 是开源免费的,并且方便扩展。阿里巴巴数据库系统也大量用到了 MySQL,因此它的稳定性是有保障的。MySQL是开放源代码的,因此任何人都可以在 GPL(General Public License) 的许可下下载并根据个性化的需要对其进行修改。MySQL的默认端口号是**3306**。
-
-### 存储引擎
-
-#### 一些常用命令
-
-**查看MySQL提供的所有存储引擎**
-
-```sql
-mysql> show engines;
-```
-
-
-
-从上图我们可以查看出 MySQL 当前默认的存储引擎是InnoDB,并且在5.7版本所有的存储引擎中只有 InnoDB 是事务性存储引擎,也就是说只有 InnoDB 支持事务。
-
-**查看MySQL当前默认的存储引擎**
-
-我们也可以通过下面的命令查看默认的存储引擎。
-
-```sql
-mysql> show variables like '%storage_engine%';
-```
-
-**查看表的存储引擎**
-
-```sql
-show table status like "table_name" ;
-```
-
-
-
-#### MyISAM和InnoDB区别
-
-MyISAM是MySQL的默认数据库引擎(5.5版之前)。虽然性能极佳,而且提供了大量的特性,包括全文索引、压缩、空间函数等,但MyISAM不支持事务和行级锁,而且最大的缺陷就是崩溃后无法安全恢复。不过,5.5版本之后,MySQL引入了InnoDB(事务性数据库引擎),MySQL 5.5版本后默认的存储引擎为InnoDB。
-
-大多数时候我们使用的都是 InnoDB 存储引擎,但是在某些情况下使用 MyISAM 也是合适的比如读密集的情况下。(如果你不介意 MyISAM 崩溃恢复问题的话)。
-
-**两者的对比:**
-
-1. **是否支持行级锁** : MyISAM 只有表级锁(table-level locking),而InnoDB 支持行级锁(row-level locking)和表级锁,默认为行级锁。
-2. **是否支持事务和崩溃后的安全恢复: MyISAM** 强调的是性能,每次查询具有原子性,其执行速度比InnoDB类型更快,但是不提供事务支持。但是**InnoDB** 提供事务支持事务,外部键等高级数据库功能。 具有事务(commit)、回滚(rollback)和崩溃修复能力(crash recovery capabilities)的事务安全(transaction-safe (ACID compliant))型表。
-3. **是否支持外键:** MyISAM不支持,而InnoDB支持。
-4. **是否支持MVCC** :仅 InnoDB 支持。应对高并发事务, MVCC比单纯的加锁更高效;MVCC只在 `READ COMMITTED` 和 `REPEATABLE READ` 两个隔离级别下工作;MVCC可以使用 乐观(optimistic)锁 和 悲观(pessimistic)锁来实现;各数据库中MVCC实现并不统一。推荐阅读:[MySQL-InnoDB-MVCC多版本并发控制](https://segmentfault.com/a/1190000012650596)
-5. ......
-
-《MySQL高性能》上面有一句话这样写到:
-
-> 不要轻易相信“MyISAM比InnoDB快”之类的经验之谈,这个结论往往不是绝对的。在很多我们已知场景中,InnoDB的速度都可以让MyISAM望尘莫及,尤其是用到了聚簇索引,或者需要访问的数据都可以放入内存的应用。
-
-一般情况下我们选择 InnoDB 都是没有问题的,但是某些情况下你并不在乎可扩展能力和并发能力,也不需要事务支持,也不在乎崩溃后的安全恢复问题的话,选择MyISAM也是一个不错的选择。但是一般情况下,我们都是需要考虑到这些问题的。
-
-### 字符集及校对规则
-
-字符集指的是一种从二进制编码到某类字符符号的映射。校对规则则是指某种字符集下的排序规则。MySQL中每一种字符集都会对应一系列的校对规则。
-
-MySQL采用的是类似继承的方式指定字符集的默认值,每个数据库以及每张数据表都有自己的默认值,他们逐层继承。比如:某个库中所有表的默认字符集将是该数据库所指定的字符集(这些表在没有指定字符集的情况下,才会采用默认字符集) PS:整理自《Java工程师修炼之道》
-
-详细内容可以参考: [MySQL字符集及校对规则的理解](https://www.cnblogs.com/geaozhang/p/6724393.html#MySQLyuzifuji)
-
-### 索引
-
-MySQL索引使用的数据结构主要有**BTree索引** 和 **哈希索引** 。对于哈希索引来说,底层的数据结构就是哈希表,因此在绝大多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快;其余大部分场景,建议选择BTree索引。
-
-MySQL的BTree索引使用的是B树中的B+Tree,但对于主要的两种存储引擎的实现方式是不同的。
-
-- **MyISAM:** B+Tree叶节点的data域存放的是数据记录的地址。在索引检索的时候,首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,则取出其 data 域的值,然后以 data 域的值为地址读取相应的数据记录。这被称为“非聚簇索引”。
-- **InnoDB:** 其数据文件本身就是索引文件。相比MyISAM,索引文件和数据文件是分离的,其表数据文件本身就是按B+Tree组织的一个索引结构,树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引。这被称为“聚簇索引(或聚集索引)”。而其余的索引都作为辅助索引,辅助索引的data域存储相应记录主键的值而不是地址,这也是和MyISAM不同的地方。**在根据主索引搜索时,直接找到key所在的节点即可取出数据;在根据辅助索引查找时,则需要先取出主键的值,再走一遍主索引。** **因此,在设计表的时候,不建议使用过长的字段作为主键,也不建议使用非单调的字段作为主键,这样会造成主索引频繁分裂。** PS:整理自《Java工程师修炼之道》
-
-**更多关于索引的内容可以查看文档首页MySQL目录下关于索引的详细总结。**
-
-### 查询缓存的使用
-
-> 执行查询语句的时候,会先查询缓存。不过,MySQL 8.0 版本后移除,因为这个功能不太实用
-
-my.cnf加入以下配置,重启MySQL开启查询缓存
-```properties
-query_cache_type=1
-query_cache_size=600000
-```
-
-MySQL执行以下命令也可以开启查询缓存
-
-```properties
-set global query_cache_type=1;
-set global query_cache_size=600000;
-```
-如上,**开启查询缓存后在同样的查询条件以及数据情况下,会直接在缓存中返回结果**。这里的查询条件包括查询本身、当前要查询的数据库、客户端协议版本号等一些可能影响结果的信息。因此任何两个查询在任何字符上的不同都会导致缓存不命中。此外,如果查询中包含任何用户自定义函数、存储函数、用户变量、临时表、MySQL库中的系统表,其查询结果也不会被缓存。
-
-缓存建立之后,MySQL的查询缓存系统会跟踪查询中涉及的每张表,如果这些表(数据或结构)发生变化,那么和这张表相关的所有缓存数据都将失效。
-
-**缓存虽然能够提升数据库的查询性能,但是缓存同时也带来了额外的开销,每次查询后都要做一次缓存操作,失效后还要销毁。** 因此,开启缓存查询要谨慎,尤其对于写密集的应用来说更是如此。如果开启,要注意合理控制缓存空间大小,一般来说其大小设置为几十MB比较合适。此外,**还可以通过sql_cache和sql_no_cache来控制某个查询语句是否需要缓存:**
-```sql
-select sql_no_cache count(*) from usr;
-```
-
-### 什么是事务?
-
-**事务是逻辑上的一组操作,要么都执行,要么都不执行。**
-
-事务最经典也经常被拿出来说例子就是转账了。假如小明要给小红转账1000元,这个转账会涉及到两个关键操作就是:将小明的余额减少1000元,将小红的余额增加1000元。万一在这两个操作之间突然出现错误比如银行系统崩溃,导致小明余额减少而小红的余额没有增加,这样就不对了。事务就是保证这两个关键操作要么都成功,要么都要失败。
-
-### 事物的四大特性(ACID)
-
-
-
-1. **原子性(Atomicity):** 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
-2. **一致性(Consistency):** 执行事务前后,数据保持一致,多个事务对同一个数据读取的结果是相同的;
-3. **隔离性(Isolation):** 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;
-4. **持久性(Durability):** 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
-
-### 并发事务带来哪些问题?
-
-在典型的应用程序中,多个事务并发运行,经常会操作相同的数据来完成各自的任务(多个用户对同一数据进行操作)。并发虽然是必须的,但可能会导致以下的问题。
-
-- **脏读(Dirty read):** 当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。
-- **丢失修改(Lost to modify):** 指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。 例如:事务1读取某表中的数据A=20,事务2也读取A=20,事务1修改A=A-1,事务2也修改A=A-1,最终结果A=19,事务1的修改被丢失。
-- **不可重复读(Unrepeatableread):** 指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。
-- **幻读(Phantom read):** 幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。
-
-**不可重复读和幻读区别:**
-
-不可重复读的重点是修改比如多次读取一条记录发现其中某些列的值被修改,幻读的重点在于新增或者删除比如多次读取一条记录发现记录增多或减少了。
-
-### 事务隔离级别有哪些?MySQL的默认隔离级别是?
-
-**SQL 标准定义了四个隔离级别:**
-
-- **READ-UNCOMMITTED(读取未提交):** 最低的隔离级别,允许读取尚未提交的数据变更,**可能会导致脏读、幻读或不可重复读**。
-- **READ-COMMITTED(读取已提交):** 允许读取并发事务已经提交的数据,**可以阻止脏读,但是幻读或不可重复读仍有可能发生**。
-- **REPEATABLE-READ(可重复读):** 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,**可以阻止脏读和不可重复读,但幻读仍有可能发生**。
-- **SERIALIZABLE(可串行化):** 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,**该级别可以防止脏读、不可重复读以及幻读**。
-
-------
-
-| 隔离级别 | 脏读 | 不可重复读 | 幻影读 |
-| :--------------: | :--: | :--------: | :----: |
-| READ-UNCOMMITTED | √ | √ | √ |
-| READ-COMMITTED | × | √ | √ |
-| REPEATABLE-READ | × | × | √ |
-| SERIALIZABLE | × | × | × |
-
-MySQL InnoDB 存储引擎的默认支持的隔离级别是 **REPEATABLE-READ(可重读)**。我们可以通过`SELECT @@tx_isolation;`命令来查看
-
-```sql
-mysql> SELECT @@tx_isolation;
-+-----------------+
-| @@tx_isolation |
-+-----------------+
-| REPEATABLE-READ |
-+-----------------+
-```
-
-这里需要注意的是:与 SQL 标准不同的地方在于 InnoDB 存储引擎在 **REPEATABLE-READ(可重读)**
-事务隔离级别下使用的是Next-Key Lock 锁算法,因此可以避免幻读的产生,这与其他数据库系统(如 SQL Server)
-是不同的。所以说InnoDB 存储引擎的默认支持的隔离级别是 **REPEATABLE-READ(可重读)** 已经可以完全保证事务的隔离性要求,即达到了
- SQL标准的 **SERIALIZABLE(可串行化)** 隔离级别。因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是 **READ-COMMITTED(读取提交内容)** ,但是你要知道的是InnoDB 存储引擎默认使用 **REPEAaTABLE-READ(可重读)** 并不会有任何性能损失。
-
-InnoDB 存储引擎在 **分布式事务** 的情况下一般会用到 **SERIALIZABLE(可串行化)** 隔离级别。
-
-### 锁机制与InnoDB锁算法
-
-**MyISAM和InnoDB存储引擎使用的锁:**
-
-- MyISAM采用表级锁(table-level locking)。
-- InnoDB支持行级锁(row-level locking)和表级锁,默认为行级锁
-
-**表级锁和行级锁对比:**
-
-- **表级锁:** MySQL中锁定 **粒度最大** 的一种锁,对当前操作的整张表加锁,实现简单,资源消耗也比较少,加锁快,不会出现死锁。其锁定粒度最大,触发锁冲突的概率最高,并发度最低,MyISAM和 InnoDB引擎都支持表级锁。
-- **行级锁:** MySQL中锁定 **粒度最小** 的一种锁,只针对当前操作的行进行加锁。 行级锁能大大减少数据库操作的冲突。其加锁粒度最小,并发度高,但加锁的开销也最大,加锁慢,会出现死锁。
-
-详细内容可以参考: MySQL锁机制简单了解一下:[https://blog.csdn.net/qq_34337272/article/details/80611486](https://blog.csdn.net/qq_34337272/article/details/80611486)
-
-**InnoDB存储引擎的锁的算法有三种:**
-
-- Record lock:单个行记录上的锁
-- Gap lock:间隙锁,锁定一个范围,不包括记录本身
-- Next-key lock:record+gap 锁定一个范围,包含记录本身
-
-**相关知识点:**
-
-1. innodb对于行的查询使用next-key lock
-2. Next-locking keying为了解决Phantom Problem幻读问题
-3. 当查询的索引含有唯一属性时,将next-key lock降级为record key
-4. Gap锁设计的目的是为了阻止多个事务将记录插入到同一范围内,而这会导致幻读问题的产生
-5. 有两种方式显式关闭gap锁:(除了外键约束和唯一性检查外,其余情况仅使用record lock) A. 将事务隔离级别设置为RC B. 将参数innodb_locks_unsafe_for_binlog设置为1
-
-### 大表优化
-
-当MySQL单表记录数过大时,数据库的CRUD性能会明显下降,一些常见的优化措施如下:
-
-#### 1. 限定数据的范围
-
-务必禁止不带任何限制数据范围条件的查询语句。比如:我们当用户在查询订单历史的时候,我们可以控制在一个月的范围内;
-
-#### 2. 读/写分离
-
-经典的数据库拆分方案,主库负责写,从库负责读;
-
-#### 3. 垂直分区
-
- **根据数据库里面数据表的相关性进行拆分。** 例如,用户表中既有用户的登录信息又有用户的基本信息,可以将用户表拆分成两个单独的表,甚至放到单独的库做分库。
-
- **简单来说垂直拆分是指数据表列的拆分,把一张列比较多的表拆分为多张表。** 如下图所示,这样来说大家应该就更容易理解了。
- 
-
-- **垂直拆分的优点:** 可以使得列数据变小,在查询时减少读取的Block数,减少I/O次数。此外,垂直分区可以简化表的结构,易于维护。
-- **垂直拆分的缺点:** 主键会出现冗余,需要管理冗余列,并会引起Join操作,可以通过在应用层进行Join来解决。此外,垂直分区会让事务变得更加复杂;
-
-#### 4. 水平分区
-
-**保持数据表结构不变,通过某种策略存储数据分片。这样每一片数据分散到不同的表或者库中,达到了分布式的目的。 水平拆分可以支撑非常大的数据量。**
-
- 水平拆分是指数据表行的拆分,表的行数超过200万行时,就会变慢,这时可以把一张的表的数据拆成多张表来存放。举个例子:我们可以将用户信息表拆分成多个用户信息表,这样就可以避免单一表数据量过大对性能造成影响。
-
-
-
-水平拆分可以支持非常大的数据量。需要注意的一点是:分表仅仅是解决了单一表数据过大的问题,但由于表的数据还是在同一台机器上,其实对于提升MySQL并发能力没有什么意义,所以 **水平拆分最好分库** 。
-
-水平拆分能够 **支持非常大的数据量存储,应用端改造也少**,但 **分片事务难以解决** ,跨节点Join性能较差,逻辑复杂。《Java工程师修炼之道》的作者推荐 **尽量不要对数据进行分片,因为拆分会带来逻辑、部署、运维的各种复杂度** ,一般的数据表在优化得当的情况下支撑千万以下的数据量是没有太大问题的。如果实在要分片,尽量选择客户端分片架构,这样可以减少一次和中间件的网络I/O。
-
-**下面补充一下数据库分片的两种常见方案:**
-
-- **客户端代理:** **分片逻辑在应用端,封装在jar包中,通过修改或者封装JDBC层来实现。** 当当网的 **Sharding-JDBC** 、阿里的TDDL是两种比较常用的实现。
-- **中间件代理:** **在应用和数据中间加了一个代理层。分片逻辑统一维护在中间件服务中。** 我们现在谈的 **Mycat** 、360的Atlas、网易的DDB等等都是这种架构的实现。
-
-详细内容可以参考: MySQL大表优化方案: [https://segmentfault.com/a/1190000006158186](https://segmentfault.com/a/1190000006158186)
-
-### 解释一下什么是池化设计思想。什么是数据库连接池?为什么需要数据库连接池?
-
-池话设计应该不是一个新名词。我们常见的如java线程池、jdbc连接池、redis连接池等就是这类设计的代表实现。这种设计会初始预设资源,解决的问题就是抵消每次获取资源的消耗,如创建线程的开销,获取远程连接的开销等。就好比你去食堂打饭,打饭的大妈会先把饭盛好几份放那里,你来了就直接拿着饭盒加菜即可,不用再临时又盛饭又打菜,效率就高了。除了初始化资源,池化设计还包括如下这些特征:池子的初始值、池子的活跃值、池子的最大值等,这些特征可以直接映射到java线程池和数据库连接池的成员属性中。——这篇文章对[池化设计思想](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485679&idx=1&sn=57dbca8c9ad49e1f3968ecff04a4f735&chksm=cea24724f9d5ce3212292fac291234a760c99c0960b5430d714269efe33554730b5f71208582&token=1141994790&lang=zh_CN#rd)介绍的还不错,直接复制过来,避免重复造轮子了。
-
-数据库连接本质就是一个 socket 的连接。数据库服务端还要维护一些缓存和用户权限信息之类的 所以占用了一些内存。我们可以把数据库连接池是看做是维护的数据库连接的缓存,以便将来需要对数据库的请求时可以重用这些连接。为每个用户打开和维护数据库连接,尤其是对动态数据库驱动的网站应用程序的请求,既昂贵又浪费资源。**在连接池中,创建连接后,将其放置在池中,并再次使用它,因此不必建立新的连接。如果使用了所有连接,则会建立一个新连接并将其添加到池中。**连接池还减少了用户必须等待建立与数据库的连接的时间。
-
-### 分库分表之后,id 主键如何处理?
-
-因为要是分成多个表之后,每个表都是从 1 开始累加,这样是不对的,我们需要一个全局唯一的 id 来支持。
-
-生成全局 id 有下面这几种方式:
-
-- **UUID**:不适合作为主键,因为太长了,并且无序不可读,查询效率低。比较适合用于生成唯一的名字的标示比如文件的名字。
-- **数据库自增 id** : 两台数据库分别设置不同步长,生成不重复ID的策略来实现高可用。这种方式生成的 id 有序,但是需要独立部署数据库实例,成本高,还会有性能瓶颈。
-- **利用 redis 生成 id :** 性能比较好,灵活方便,不依赖于数据库。但是,引入了新的组件造成系统更加复杂,可用性降低,编码更加复杂,增加了系统成本。
-- **Twitter的snowflake算法** :Github 地址:https://github.com/twitter-archive/snowflake。
-- **美团的[Leaf](https://tech.meituan.com/2017/04/21/mt-leaf.html)分布式ID生成系统** :Leaf 是美团开源的分布式ID生成器,能保证全局唯一性、趋势递增、单调递增、信息安全,里面也提到了几种分布式方案的对比,但也需要依赖关系数据库、Zookeeper等中间件。感觉还不错。美团技术团队的一篇文章:https://tech.meituan.com/2017/04/21/mt-leaf.html 。
-- ......
-
-### 一条SQL语句在MySQL中如何执行的
-
-[一条SQL语句在MySQL中如何执行的]()
-
-### MySQL高性能优化规范建议
-
-[MySQL高性能优化规范建议]()
-
-### 一条SQL语句执行得很慢的原因有哪些?
-
-[腾讯面试:一条SQL语句执行得很慢的原因有哪些?---不看后悔系列](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485185&idx=1&sn=66ef08b4ab6af5757792223a83fc0d45&chksm=cea248caf9d5c1dc72ec8a281ec16aa3ec3e8066dbb252e27362438a26c33fbe842b0e0adf47&token=79317275&lang=zh_CN#rd)
-
-## 公众号
-
-如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
-
-**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"Java面试突击"** 即可免费领取!
-
-**Java工程师必备学习资源:** 一些Java工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。
-
-
diff --git a/docs/database/Redis/Redis.md b/docs/database/Redis/Redis.md
deleted file mode 100644
index 53f8abca9f4..00000000000
--- a/docs/database/Redis/Redis.md
+++ /dev/null
@@ -1,319 +0,0 @@
-点击关注[公众号](#公众号)及时获取笔主最新更新文章,并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。
-
-
-
-- [redis 简介](#redis-简介)
-- [为什么要用 redis/为什么要用缓存](#为什么要用-redis为什么要用缓存)
-- [为什么要用 redis 而不用 map/guava 做缓存?](#为什么要用-redis-而不用-mapguava-做缓存)
-- [redis 和 memcached 的区别](#redis-和-memcached-的区别)
-- [redis 常见数据结构以及使用场景分析](#redis-常见数据结构以及使用场景分析)
- - [1.String](#1string)
- - [2.Hash](#2hash)
- - [3.List](#3list)
- - [4.Set](#4set)
- - [5.Sorted Set](#5sorted-set)
-- [redis 设置过期时间](#redis-设置过期时间)
-- [redis 内存淘汰机制(MySQL里有2000w数据,Redis中只存20w的数据,如何保证Redis中的数据都是热点数据?)](#redis-内存淘汰机制mysql里有2000w数据redis中只存20w的数据如何保证redis中的数据都是热点数据)
-- [redis 持久化机制(怎么保证 redis 挂掉之后再重启数据可以进行恢复)](#redis-持久化机制怎么保证-redis-挂掉之后再重启数据可以进行恢复)
-- [redis 事务](#redis-事务)
-- [缓存雪崩和缓存穿透问题解决方案](#缓存雪崩和缓存穿透问题解决方案)
-- [如何解决 Redis 的并发竞争 Key 问题](#如何解决-redis-的并发竞争-key-问题)
-- [如何保证缓存与数据库双写时的数据一致性?](#如何保证缓存与数据库双写时的数据一致性)
-
-
-
-### redis 简介
-
-简单来说 redis 就是一个数据库,不过与传统数据库不同的是 redis 的数据是存在内存中的,所以读写速度非常快,因此 redis 被广泛应用于缓存方向。另外,redis 也经常用来做分布式锁。redis 提供了多种数据类型来支持不同的业务场景。除此之外,redis 支持事务 、持久化、LUA脚本、LRU驱动事件、多种集群方案。
-
-### 为什么要用 redis/为什么要用缓存
-
-主要从“高性能”和“高并发”这两点来看待这个问题。
-
-**高性能:**
-
-假如用户第一次访问数据库中的某些数据。这个过程会比较慢,因为是从硬盘上读取的。将该用户访问的数据存在缓存中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快。如果数据库中的对应数据改变的之后,同步改变缓存中相应的数据即可!
-
-
-
-
-**高并发:**
-
-直接操作缓存能够承受的请求是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。
-
-
-
-
-
-### 为什么要用 redis 而不用 map/guava 做缓存?
-
-
->下面的内容来自 segmentfault 一位网友的提问,地址:https://segmentfault.com/q/1010000009106416
-
-缓存分为本地缓存和分布式缓存。以 Java 为例,使用自带的 map 或者 guava 实现的是本地缓存,最主要的特点是轻量以及快速,生命周期随着 jvm 的销毁而结束,并且在多实例的情况下,每个实例都需要各自保存一份缓存,缓存不具有一致性。
-
-使用 redis 或 memcached 之类的称为分布式缓存,在多实例的情况下,各实例共用一份缓存数据,缓存具有一致性。缺点是需要保持 redis 或 memcached服务的高可用,整个程序架构上较为复杂。
-
-### redis 的线程模型
-
-> 参考地址:https://www.javazhiyin.com/22943.html
-
-redis 内部使用文件事件处理器 `file event handler`,这个文件事件处理器是单线程的,所以 redis 才叫做单线程的模型。它采用 IO 多路复用机制同时监听多个 socket,根据 socket 上的事件来选择对应的事件处理器进行处理。
-
-文件事件处理器的结构包含 4 个部分:
-
-- 多个 socket
-- IO 多路复用程序
-- 文件事件分派器
-- 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)
-
-多个 socket 可能会并发产生不同的操作,每个操作对应不同的文件事件,但是 IO 多路复用程序会监听多个 socket,会将 socket 产生的事件放入队列中排队,事件分派器每次从队列中取出一个事件,把该事件交给对应的事件处理器进行处理。
-
-
-### redis 和 memcached 的区别
-
-对于 redis 和 memcached 我总结了下面四点。现在公司一般都是用 redis 来实现缓存,而且 redis 自身也越来越强大了!
-
-1. **redis支持更丰富的数据类型(支持更复杂的应用场景)**:Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。memcache支持简单的数据类型,String。
-2. **Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用,而Memecache把数据全部存在内存之中。**
-3. **集群模式**:memcached没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据;但是 redis 目前是原生支持 cluster 模式的.
-4. **Memcached是多线程,非阻塞IO复用的网络模型;Redis使用单线程的多路 IO 复用模型。**
-
-
-> 来自网络上的一张图,这里分享给大家!
-
-
-
-
-### redis 常见数据结构以及使用场景分析
-
-#### 1.String
-
-> **常用命令:** set,get,decr,incr,mget 等。
-
-
-String数据结构是简单的key-value类型,value其实不仅可以是String,也可以是数字。
-常规key-value缓存应用;
-常规计数:微博数,粉丝数等。
-
-#### 2.Hash
-> **常用命令:** hget,hset,hgetall 等。
-
-hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象,后续操作的时候,你可以直接仅仅修改这个对象中的某个字段的值。 比如我们可以 hash 数据结构来存储用户信息,商品信息等等。比如下面我就用 hash 类型存放了我本人的一些信息:
-
-```
-key=JavaUser293847
-value={
- “id”: 1,
- “name”: “SnailClimb”,
- “age”: 22,
- “location”: “Wuhan, Hubei”
-}
-
-```
-
-
-#### 3.List
-> **常用命令:** lpush,rpush,lpop,rpop,lrange等
-
-list 就是链表,Redis list 的应用场景非常多,也是Redis最重要的数据结构之一,比如微博的关注列表,粉丝列表,消息列表等功能都可以用Redis的 list 结构来实现。
-
-Redis list 的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销。
-
-另外可以通过 lrange 命令,就是从某个元素开始读取多少个元素,可以基于 list 实现分页查询,这个很棒的一个功能,基于 redis 实现简单的高性能分页,可以做类似微博那种下拉不断分页的东西(一页一页的往下走),性能高。
-
-#### 4.Set
-
-> **常用命令:**
-sadd,spop,smembers,sunion 等
-
-set 对外提供的功能与list类似是一个列表的功能,特殊之处在于 set 是可以自动排重的。
-
-当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的。可以基于 set 轻易实现交集、并集、差集的操作。
-
-比如:在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis可以非常方便的实现如共同关注、共同粉丝、共同喜好等功能。这个过程也就是求交集的过程,具体命令如下:
-
-```
-sinterstore key1 key2 key3 将交集存在key1内
-```
-
-#### 5.Sorted Set
-> **常用命令:** zadd,zrange,zrem,zcard等
-
-
-和set相比,sorted set增加了一个权重参数score,使得集合中的元素能够按score进行有序排列。
-
-**举例:** 在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等信息,适合使用 Redis 中的 Sorted Set 结构进行存储。
-
-
-### redis 设置过期时间
-
-Redis中有个设置时间过期的功能,即对存储在 redis 数据库中的值可以设置一个过期时间。作为一个缓存数据库,这是非常实用的。如我们一般项目中的 token 或者一些登录信息,尤其是短信验证码都是有时间限制的,按照传统的数据库处理方式,一般都是自己判断过期,这样无疑会严重影响项目性能。
-
-我们 set key 的时候,都可以给一个 expire time,就是过期时间,通过过期时间我们可以指定这个 key 可以存活的时间。
-
-如果假设你设置了一批 key 只能存活1个小时,那么接下来1小时后,redis是怎么对这批key进行删除的?
-
-**定期删除+惰性删除。**
-
-通过名字大概就能猜出这两个删除方式的意思了。
-
-- **定期删除**:redis默认是每隔 100ms 就**随机抽取**一些设置了过期时间的key,检查其是否过期,如果过期就删除。注意这里是随机抽取的。为什么要随机呢?你想一想假如 redis 存了几十万个 key ,每隔100ms就遍历所有的设置过期时间的 key 的话,就会给 CPU 带来很大的负载!
-- **惰性删除** :定期删除可能会导致很多过期 key 到了时间并没有被删除掉。所以就有了惰性删除。假如你的过期 key,靠定期删除没有被删除掉,还停留在内存里,除非你的系统去查一下那个 key,才会被redis给删除掉。这就是所谓的惰性删除,也是够懒的哈!
-
-
-但是仅仅通过设置过期时间还是有问题的。我们想一下:如果定期删除漏掉了很多过期 key,然后你也没及时去查,也就没走惰性删除,此时会怎么样?如果大量过期key堆积在内存里,导致redis内存块耗尽了。怎么解决这个问题呢? **redis 内存淘汰机制。**
-
-### redis 内存淘汰机制(MySQL里有2000w数据,Redis中只存20w的数据,如何保证Redis中的数据都是热点数据?)
-
-redis 配置文件 redis.conf 中有相关注释,我这里就不贴了,大家可以自行查阅或者通过这个网址查看: [http://download.redis.io/redis-stable/redis.conf](http://download.redis.io/redis-stable/redis.conf)
-
-**redis 提供 6种数据淘汰策略:**
-
-1. **volatile-lru**:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
-2. **volatile-ttl**:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
-3. **volatile-random**:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
-4. **allkeys-lru**:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(这个是最常用的)
-5. **allkeys-random**:从数据集(server.db[i].dict)中任意选择数据淘汰
-6. **no-eviction**:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。这个应该没人使用吧!
-
-4.0版本后增加以下两种:
-
-7. **volatile-lfu**:从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰
-8. **allkeys-lfu**:当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的key
-
-**备注: 关于 redis 设置过期时间以及内存淘汰机制,我这里只是简单的总结一下,后面会专门写一篇文章来总结!**
-
-
-### redis 持久化机制(怎么保证 redis 挂掉之后再重启数据可以进行恢复)
-
-很多时候我们需要持久化数据也就是将内存中的数据写入到硬盘里面,大部分原因是为了之后重用数据(比如重启机器、机器故障之后恢复数据),或者是为了防止系统故障而将数据备份到一个远程位置。
-
-Redis不同于Memcached的很重一点就是,Redis支持持久化,而且支持两种不同的持久化操作。**Redis的一种持久化方式叫快照(snapshotting,RDB),另一种方式是只追加文件(append-only file,AOF)**。这两种方法各有千秋,下面我会详细这两种持久化方法是什么,怎么用,如何选择适合自己的持久化方法。
-
-**快照(snapshotting)持久化(RDB)**
-
-Redis可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。Redis创建快照之后,可以对快照进行备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis主从结构,主要用来提高Redis性能),还可以将快照留在原地以便重启服务器的时候使用。
-
-快照持久化是Redis默认采用的持久化方式,在redis.conf配置文件中默认有此下配置:
-
-```conf
-
-save 900 1 #在900秒(15分钟)之后,如果至少有1个key发生变化,Redis就会自动触发BGSAVE命令创建快照。
-
-save 300 10 #在300秒(5分钟)之后,如果至少有10个key发生变化,Redis就会自动触发BGSAVE命令创建快照。
-
-save 60 10000 #在60秒(1分钟)之后,如果至少有10000个key发生变化,Redis就会自动触发BGSAVE命令创建快照。
-```
-
-**AOF(append-only file)持久化**
-
-与快照持久化相比,AOF持久化 的实时性更好,因此已成为主流的持久化方案。默认情况下Redis没有开启AOF(append only file)方式的持久化,可以通过appendonly参数开启:
-
-```conf
-appendonly yes
-```
-
-开启AOF持久化后每执行一条会更改Redis中的数据的命令,Redis就会将该命令写入硬盘中的AOF文件。AOF文件的保存位置和RDB文件的位置相同,都是通过dir参数设置的,默认的文件名是appendonly.aof。
-
-在Redis的配置文件中存在三种不同的 AOF 持久化方式,它们分别是:
-
-```conf
-appendfsync always #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度
-appendfsync everysec #每秒钟同步一次,显示地将多个写命令同步到硬盘
-appendfsync no #让操作系统决定何时进行同步
-```
-
-为了兼顾数据和写入性能,用户可以考虑 appendfsync everysec选项 ,让Redis每秒同步一次AOF文件,Redis性能几乎没受到任何影响。而且这样即使出现系统崩溃,用户最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候,Redis还会优雅的放慢自己的速度以便适应硬盘的最大写入速度。
-
-**Redis 4.0 对于持久化机制的优化**
-
-Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 `aof-use-rdb-preamble` 开启)。
-
-如果把混合持久化打开,AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。这样做的好处是可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据。当然缺点也是有的, AOF 里面的 RDB 部分是压缩格式不再是 AOF 格式,可读性较差。
-
-**补充内容:AOF 重写**
-
-AOF重写可以产生一个新的AOF文件,这个新的AOF文件和原有的AOF文件所保存的数据库状态一样,但体积更小。
-
-AOF重写是一个有歧义的名字,该功能是通过读取数据库中的键值对来实现的,程序无须对现有AOF文件进行任何读入、分析或者写入操作。
-
-在执行 BGREWRITEAOF 命令时,Redis 服务器会维护一个 AOF 重写缓冲区,该缓冲区会在子进程创建新AOF文件期间,记录服务器执行的所有写命令。当子进程完成创建新AOF文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新AOF文件的末尾,使得新旧两个AOF文件所保存的数据库状态一致。最后,服务器用新的AOF文件替换旧的AOF文件,以此来完成AOF文件重写操作
-
-**更多内容可以查看我的这篇文章:**
-
-- [Redis持久化](Redis持久化.md)
-
-
-### redis 事务
-
-Redis 通过 MULTI、EXEC、WATCH 等命令来实现事务(transaction)功能。事务提供了一种将多个命令请求打包,然后一次性、按顺序地执行多个命令的机制,并且在事务执行期间,服务器不会中断事务而改去执行其他客户端的命令请求,它会将事务中的所有命令都执行完毕,然后才去处理其他客户端的命令请求。
-
-在传统的关系式数据库中,常常用 ACID 性质来检验事务功能的可靠性和安全性。在 Redis 中,事务总是具有原子性(Atomicity)、一致性(Consistency)和隔离性(Isolation),并且当 Redis 运行在某种特定的持久化模式下时,事务也具有持久性(Durability)。
-
-补充内容:
-
-> 1. redis同一个事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚。(来自[issue:关于Redis事务不是原子性问题](https://github.com/Snailclimb/JavaGuide/issues/452) )
-
-### 缓存雪崩和缓存穿透问题解决方案
-
-**缓存雪崩**
-
-简介:缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。
-
-解决办法(中华石杉老师在他的视频中提到过,视频地址在最后一个问题中有提到):
-
-- 事前:尽量保证整个 redis 集群的高可用性,发现机器宕机尽快补上。选择合适的内存淘汰策略。
-- 事中:本地ehcache缓存 + hystrix限流&降级,避免MySQL崩掉
-- 事后:利用 redis 持久化机制保存的数据尽快恢复缓存
-
-
-
-
-**缓存穿透**
-
-简介:一般是黑客故意去请求缓存中不存在的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。
-
-解决办法: 有很多种方法可以有效地解决缓存穿透问题,最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。另外也有一个更为简单粗暴的方法(我们采用的就是这种),如果一个查询返回的数据为空(不管是数 据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。
-
-参考:
-
-- [https://blog.csdn.net/zeb_perfect/article/details/54135506](https://blog.csdn.net/zeb_perfect/article/details/54135506)
-
-### 如何解决 Redis 的并发竞争 Key 问题
-
-所谓 Redis 的并发竞争 Key 的问题也就是多个系统同时对一个 key 进行操作,但是最后执行的顺序和我们期望的顺序不同,这样也就导致了结果的不同!
-
-推荐一种方案:分布式锁(zookeeper 和 redis 都可以实现分布式锁)。(如果不存在 Redis 的并发竞争 Key 问题,不要使用分布式锁,这样会影响性能)
-
-基于zookeeper临时有序节点可以实现的分布式锁。大致思想为:每个客户端对某个方法加锁时,在zookeeper上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。 判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。 当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题。完成业务流程后,删除对应的子节点释放锁。
-
-在实践中,当然是从以可靠性为主。所以首推Zookeeper。
-
-参考:
-
-- https://www.jianshu.com/p/8bddd381de06
-
-### 如何保证缓存与数据库双写时的数据一致性?
-
-> 一般情况下我们都是这样使用缓存的:先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。这种方式很明显会存在缓存和数据库的数据不一致的情况。
-
-你只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何解决一致性问题?
-
-一般来说,就是如果你的系统不是严格要求缓存+数据库必须一致性的话,缓存可以稍微的跟数据库偶尔有不一致的情况,最好不要做这个方案,读请求和写请求串行化,串到一个内存队列里去,这样就可以保证一定不会出现不一致的情况
-
-串行化之后,就会导致系统的吞吐量会大幅度的降低,用比正常情况下多几倍的机器去支撑线上的一个请求。
-
-更多内容可以查看:https://github.com/doocs/advanced-java/blob/master/docs/high-concurrency/redis-consistence.md
-
-**参考:** Java工程师面试突击第1季(可能是史上最好的Java面试突击课程)-中华石杉老师!公众号后台回复关键字“1”即可获取该视频内容。
-
-## 公众号
-
-如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
-
-**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"Java面试突击"** 即可免费领取!
-
-**Java工程师必备学习资源:** 一些Java工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。
-
-
\ No newline at end of file
diff --git "a/docs/database/Redis/Redis\346\214\201\344\271\205\345\214\226.md" "b/docs/database/Redis/Redis\346\214\201\344\271\205\345\214\226.md"
deleted file mode 100644
index fbad95556a0..00000000000
--- "a/docs/database/Redis/Redis\346\214\201\344\271\205\345\214\226.md"
+++ /dev/null
@@ -1,118 +0,0 @@
-
-
-非常感谢《redis实战》真本书,本文大多内容也参考了书中的内容。非常推荐大家看一下《redis实战》这本书,感觉书中的很多理论性东西还是很不错的。
-
-为什么本文的名字要加上春夏秋冬又一春,哈哈 ,这是一部韩国的电影,我感觉电影不错,所以就用在文章名字上了,没有什么特别的含义,然后下面的有些配图也是电影相关镜头。
-
-
-
-**很多时候我们需要持久化数据也就是将内存中的数据写入到硬盘里面,大部分原因是为了之后重用数据(比如重启机器、机器故障之后回复数据),或者是为了防止系统故障而将数据备份到一个远程位置。**
-
-Redis不同于Memcached的很重一点就是,**Redis支持持久化**,而且支持两种不同的持久化操作。Redis的一种持久化方式叫**快照(snapshotting,RDB)**,另一种方式是**只追加文件(append-only file,AOF)**.这两种方法各有千秋,下面我会详细这两种持久化方法是什么,怎么用,如何选择适合自己的持久化方法。
-
-
-## 快照(snapshotting)持久化
-
-Redis可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。Redis创建快照之后,可以对快照进行备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis主从结构,主要用来提高Redis性能),还可以将快照留在原地以便重启服务器的时候使用。
-
-
-
-
-**快照持久化是Redis默认采用的持久化方式**,在redis.conf配置文件中默认有此下配置:
-```
-
-save 900 1 #在900秒(15分钟)之后,如果至少有1个key发生变化,Redis就会自动触发BGSAVE命令创建快照。
-
-save 300 10 #在300秒(5分钟)之后,如果至少有10个key发生变化,Redis就会自动触发BGSAVE命令创建快照。
-
-save 60 10000 #在60秒(1分钟)之后,如果至少有10000个key发生变化,Redis就会自动触发BGSAVE命令创建快照。
-```
-
-根据配置,快照将被写入dbfilename选项指定的文件里面,并存储在dir选项指定的路径上面。如果在新的快照文件创建完毕之前,Redis、系统或者硬件这三者中的任意一个崩溃了,那么Redis将丢失最近一次创建快照写入的所有数据。
-
-举个例子:假设Redis的上一个快照是2:35开始创建的,并且已经创建成功。下午3:06时,Redis又开始创建新的快照,并且在下午3:08快照创建完毕之前,有35个键进行了更新。如果在下午3:06到3:08期间,系统发生了崩溃,导致Redis无法完成新快照的创建工作,那么Redis将丢失下午2:35之后写入的所有数据。另一方面,如果系统恰好在新的快照文件创建完毕之后崩溃,那么Redis将丢失35个键的更新数据。
-
-**创建快照的办法有如下几种:**
-
-- **BGSAVE命令:** 客户端向Redis发送 **BGSAVE命令** 来创建一个快照。对于支持BGSAVE命令的平台来说(基本上所有平台支持,除了Windows平台),Redis会调用fork来创建一个子进程,然后子进程负责将快照写入硬盘,而父进程则继续处理命令请求。
-- **SAVE命令:** 客户端还可以向Redis发送 **SAVE命令** 来创建一个快照,接到SAVE命令的Redis服务器在快照创建完毕之前不会再响应任何其他命令。SAVE命令不常用,我们通常只会在没有足够内存去执行BGSAVE命令的情况下,又或者即使等待持久化操作执行完毕也无所谓的情况下,才会使用这个命令。
-- **save选项:** 如果用户设置了save选项(一般会默认设置),比如 **save 60 10000**,那么从Redis最近一次创建快照之后开始算起,当“60秒之内有10000次写入”这个条件被满足时,Redis就会自动触发BGSAVE命令。
-- **SHUTDOWN命令:** 当Redis通过SHUTDOWN命令接收到关闭服务器的请求时,或者接收到标准TERM信号时,会执行一个SAVE命令,阻塞所有客户端,不再执行客户端发送的任何命令,并在SAVE命令执行完毕之后关闭服务器。
-- **一个Redis服务器连接到另一个Redis服务器:** 当一个Redis服务器连接到另一个Redis服务器,并向对方发送SYNC命令来开始一次复制操作的时候,如果主服务器目前没有执行BGSAVE操作,或者主服务器并非刚刚执行完BGSAVE操作,那么主服务器就会执行BGSAVE命令
-
-如果系统真的发生崩溃,用户将丢失最近一次生成快照之后更改的所有数据。因此,快照持久化只适用于即使丢失一部分数据也不会造成一些大问题的应用程序。不能接受这个缺点的话,可以考虑AOF持久化。
-
-
-
-## **AOF(append-only file)持久化**
-与快照持久化相比,AOF持久化 的实时性更好,因此已成为主流的持久化方案。默认情况下Redis没有开启AOF(append only file)方式的持久化,可以通过appendonly参数开启:
-```
-appendonly yes
-```
-
-开启AOF持久化后每执行一条会更改Redis中的数据的命令,Redis就会将该命令写入硬盘中的AOF文件。AOF文件的保存位置和RDB文件的位置相同,都是通过dir参数设置的,默认的文件名是appendonly.aof。
-
-
-
-**在Redis的配置文件中存在三种同步方式,它们分别是:**
-
-```
-
-appendfsync always #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度
-appendfsync everysec #每秒钟同步一次,显示地将多个写命令同步到硬盘
-appendfsync no #让操作系统决定何时进行同步
-```
-
-**appendfsync always** 可以实现将数据丢失减到最少,不过这种方式需要对硬盘进行大量的写入而且每次只写入一个命令,十分影响Redis的速度。另外使用固态硬盘的用户谨慎使用appendfsync always选项,因为这会明显降低固态硬盘的使用寿命。
-
-为了兼顾数据和写入性能,用户可以考虑 **appendfsync everysec选项** ,让Redis每秒同步一次AOF文件,Redis性能几乎没受到任何影响。而且这样即使出现系统崩溃,用户最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候,Redis还会优雅的放慢自己的速度以便适应硬盘的最大写入速度。
-
-
-**appendfsync no** 选项一般不推荐,这种方案会使Redis丢失不定量的数据而且如果用户的硬盘处理写入操作的速度不够的话,那么当缓冲区被等待写入的数据填满时,Redis的写入操作将被阻塞,这会导致Redis的请求速度变慢。
-
-**虽然AOF持久化非常灵活地提供了多种不同的选项来满足不同应用程序对数据安全的不同要求,但AOF持久化也有缺陷——AOF文件的体积太大。**
-
-## 重写/压缩AOF
-
-AOF虽然在某个角度可以将数据丢失降低到最小而且对性能影响也很小,但是极端的情况下,体积不断增大的AOF文件很可能会用完硬盘空间。另外,如果AOF体积过大,那么还原操作执行时间就可能会非常长。
-
-为了解决AOF体积过大的问题,用户可以向Redis发送 **BGREWRITEAOF命令** ,这个命令会通过移除AOF文件中的冗余命令来重写(rewrite)AOF文件来减小AOF文件的体积。BGREWRITEAOF命令和BGSAVE创建快照原理十分相似,所以AOF文件重写也需要用到子进程,这样会导致性能问题和内存占用问题,和快照持久化一样。更糟糕的是,如果不加以控制的话,AOF文件的体积可能会比快照文件大好几倍。
-
-**文件重写流程:**
-
-
-和快照持久化可以通过设置save选项来自动执行BGSAVE一样,AOF持久化也可以通过设置
-
-```
-auto-aof-rewrite-percentage
-```
-
-选项和
-
-```
-auto-aof-rewrite-min-size
-```
-
-选项自动执行BGREWRITEAOF命令。举例:假设用户对Redis设置了如下配置选项并且启用了AOF持久化。那么当AOF文件体积大于64mb,并且AOF的体积比上一次重写之后的体积大了至少一倍(100%)的时候,Redis将执行BGREWRITEAOF命令。
-
-```
-auto-aof-rewrite-percentage 100
-auto-aof-rewrite-min-size 64mb
-```
-
-无论是AOF持久化还是快照持久化,将数据持久化到硬盘上都是非常有必要的,但除了进行持久化外,用户还必须对持久化得到的文件进行备份(最好是备份到不同的地方),这样才能尽量避免数据丢失事故发生。如果条件允许的话,最好能将快照文件和重新重写的AOF文件备份到不同的服务器上面。
-
-随着负载量的上升,或者数据的完整性变得 越来越重要时,用户可能需要使用到复制特性。
-
-## Redis 4.0 对于持久化机制的优化
-Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 `aof-use-rdb-preamble` 开启)。
-
-如果把混合持久化打开,AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。这样做的好处是可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据。当然缺点也是有的, AOF 里面的 RDB 部分就是压缩格式不再是 AOF 格式,可读性较差。
-
-参考:
-
-《Redis实战》
-
-[深入学习Redis(2):持久化](https://www.cnblogs.com/kismetv/p/9137897.html)
-
-
diff --git "a/docs/database/Redis/Redlock\345\210\206\345\270\203\345\274\217\351\224\201.md" "b/docs/database/Redis/Redlock\345\210\206\345\270\203\345\274\217\351\224\201.md"
deleted file mode 100644
index 86a15ff6faf..00000000000
--- "a/docs/database/Redis/Redlock\345\210\206\345\270\203\345\274\217\351\224\201.md"
+++ /dev/null
@@ -1,47 +0,0 @@
-这篇文章主要是对 Redis 官方网站刊登的 [Distributed locks with Redis](https://redis.io/topics/distlock) 部分内容的总结和翻译。
-
-## 什么是 RedLock
-
-Redis 官方站这篇文章提出了一种权威的基于 Redis 实现分布式锁的方式名叫 *Redlock*,此种方式比原先的单节点的方法更安全。它可以保证以下特性:
-
-1. 安全特性:互斥访问,即永远只有一个 client 能拿到锁
-2. 避免死锁:最终 client 都可能拿到锁,不会出现死锁的情况,即使原本锁住某资源的 client crash 了或者出现了网络分区
-3. 容错性:只要大部分 Redis 节点存活就可以正常提供服务
-
-## 怎么在单节点上实现分布式锁
-
-> SET resource_name my_random_value NX PX 30000
-
-主要依靠上述命令,该命令仅当 Key 不存在时(NX保证)set 值,并且设置过期时间 3000ms (PX保证),值 my_random_value 必须是所有 client 和所有锁请求发生期间唯一的,释放锁的逻辑是:
-
-```lua
-if redis.call("get",KEYS[1]) == ARGV[1] then
- return redis.call("del",KEYS[1])
-else
- return 0
-end
-```
-
-上述实现可以避免释放另一个client创建的锁,如果只有 del 命令的话,那么如果 client1 拿到 lock1 之后因为某些操作阻塞了很长时间,此时 Redis 端 lock1 已经过期了并且已经被重新分配给了 client2,那么 client1 此时再去释放这把锁就会造成 client2 原本获取到的锁被 client1 无故释放了,但现在为每个 client 分配一个 unique 的 string 值可以避免这个问题。至于如何去生成这个 unique string,方法很多随意选择一种就行了。
-
-## Redlock 算法
-
-算法很易懂,起 5 个 master 节点,分布在不同的机房尽量保证可用性。为了获得锁,client 会进行如下操作:
-
-1. 得到当前的时间,微秒单位
-2. 尝试顺序地在 5 个实例上申请锁,当然需要使用相同的 key 和 random value,这里一个 client 需要合理设置与 master 节点沟通的 timeout 大小,避免长时间和一个 fail 了的节点浪费时间
-3. 当 client 在大于等于 3 个 master 上成功申请到锁的时候,且它会计算申请锁消耗了多少时间,这部分消耗的时间采用获得锁的当下时间减去第一步获得的时间戳得到,如果锁的持续时长(lock validity time)比流逝的时间多的话,那么锁就真正获取到了。
-4. 如果锁申请到了,那么锁真正的 lock validity time 应该是 origin(lock validity time) - 申请锁期间流逝的时间
-5. 如果 client 申请锁失败了,那么它就会在少部分申请成功锁的 master 节点上执行释放锁的操作,重置状态
-
-## 失败重试
-
-如果一个 client 申请锁失败了,那么它需要稍等一会在重试避免多个 client 同时申请锁的情况,最好的情况是一个 client 需要几乎同时向 5 个 master 发起锁申请。另外就是如果 client 申请锁失败了它需要尽快在它曾经申请到锁的 master 上执行 unlock 操作,便于其他 client 获得这把锁,避免这些锁过期造成的时间浪费,当然如果这时候网络分区使得 client 无法联系上这些 master,那么这种浪费就是不得不付出的代价了。
-
-## 放锁
-
-放锁操作很简单,就是依次释放所有节点上的锁就行了
-
-## 性能、崩溃恢复和 fsync
-
-如果我们的节点没有持久化机制,client 从 5 个 master 中的 3 个处获得了锁,然后其中一个重启了,这是注意 **整个环境中又出现了 3 个 master 可供另一个 client 申请同一把锁!** 违反了互斥性。如果我们开启了 AOF 持久化那么情况会稍微好转一些,因为 Redis 的过期机制是语义层面实现的,所以在 server 挂了的时候时间依旧在流逝,重启之后锁状态不会受到污染。但是考虑断电之后呢,AOF部分命令没来得及刷回磁盘直接丢失了,除非我们配置刷回策略为 fsnyc = always,但这会损伤性能。解决这个问题的方法是,当一个节点重启之后,我们规定在 max TTL 期间它是不可用的,这样它就不会干扰原本已经申请到的锁,等到它 crash 前的那部分锁都过期了,环境不存在历史锁了,那么再把这个节点加进来正常工作。
diff --git a/docs/database/Redis/images/redis-all/redis-list.drawio b/docs/database/Redis/images/redis-all/redis-list.drawio
new file mode 100644
index 00000000000..afa767154b7
--- /dev/null
+++ b/docs/database/Redis/images/redis-all/redis-list.drawio
@@ -0,0 +1 @@
+7VlNc5swFPw1PiaDENjmmNjpx6Gdjt1J2lNHAzKoEYgRcmzn11cYyRgJT1w3DnScU3grIaHd5b2HM4CTdP2Rozz5wiJMB64TrQdwOnBdEEAo/5TIpkICx6mAmJNITaqBOXnGCtTTliTCRWOiYIwKkjfBkGUZDkUDQ5yzVXPagtHmrjmKsQXMQ0Rt9IFEIqnQsTuq8U+YxIneGQyDaiRFerI6SZGgiK32IHg3gBPOmKiu0vUE05I8zUt134cDo7sH4zgTx9wwu/+V/H4g/myezsD35/vZ56/ulVrlCdGlOrB6WLHRDHC2zCJcLgIG8HaVEIHnOQrL0ZXUXGKJSKkaLh6xCBMVLFgmlKJgJGO1F+YCrw8eAuyokZ7CLMWCb+QUdcOVp9hUdnKhile1OL62WLInzEhhSPkh3i1dUyYvFGt/waBrMbi9BD3lUfPm2bwFLbT556INttPWc/sB035d0+i10wj7TaM77hmNw5fTIM6im7KeyCikqChI2ORMHp1vfsjA0cHPMrh2fR1P1/uj042KjiAbR1aRMqiWVRHxGIuXUr0tSSNxHqacY4oEeWo+RpsOaodvjMgHrPM2gKbk4+YaBVvyEKvb9suYuZLpHdcwRUWEtdDWF7tzn26V0f9ula4s4A0N4cbOtX+aB3zfWupNPaCb01cxAXjPF0cofGq68AJjIT8wXXdusxzRY/fbLJ0VDbPbOj1j2PXnrVOG/Z3A82WRWFaQXZUwujHB2SOeMMq4RDKW4VJLQqkBIUrirHSQVA9L/Lbs0Yj8lL1RAymJonKb1vavbhCP9M0/dYA+OFDF95zltTjLLPav1gECu5PmOcvf5aloD7qWx7fkoRf8+siiYggEuhbI/oSil/v+2Pp0/gLZ3y18u20/BTqDJp5O8VoSp2tJxvYrgxcXpIjV351PERnWv7RXPV39/wp49wc=
\ No newline at end of file
diff --git a/docs/database/Redis/images/redis-all/redis-list.png b/docs/database/Redis/images/redis-all/redis-list.png
new file mode 100644
index 00000000000..4fb4e36cb49
Binary files /dev/null and b/docs/database/Redis/images/redis-all/redis-list.png differ
diff --git a/docs/database/Redis/images/redis-all/redis-rollBack.png b/docs/database/Redis/images/redis-all/redis-rollBack.png
new file mode 100644
index 00000000000..91f7f46d66d
Binary files /dev/null and b/docs/database/Redis/images/redis-all/redis-rollBack.png differ
diff --git a/docs/database/Redis/images/redis-all/redis-vs-memcached.png b/docs/database/Redis/images/redis-all/redis-vs-memcached.png
new file mode 100644
index 00000000000..23844d67e6f
Binary files /dev/null and b/docs/database/Redis/images/redis-all/redis-vs-memcached.png differ
diff --git a/docs/database/Redis/images/redis-all/redis4.0-more-thread.png b/docs/database/Redis/images/redis-all/redis4.0-more-thread.png
new file mode 100644
index 00000000000..e7e19e52e17
Binary files /dev/null and b/docs/database/Redis/images/redis-all/redis4.0-more-thread.png differ
diff --git "a/docs/database/Redis/images/redis-all/redis\344\272\213\344\273\266\345\244\204\347\220\206\345\231\250.png" "b/docs/database/Redis/images/redis-all/redis\344\272\213\344\273\266\345\244\204\347\220\206\345\231\250.png"
new file mode 100644
index 00000000000..fc280fffaba
Binary files /dev/null and "b/docs/database/Redis/images/redis-all/redis\344\272\213\344\273\266\345\244\204\347\220\206\345\231\250.png" differ
diff --git "a/docs/database/Redis/images/redis-all/redis\344\272\213\345\212\241.png" "b/docs/database/Redis/images/redis-all/redis\344\272\213\345\212\241.png"
new file mode 100644
index 00000000000..eb0c404cafd
Binary files /dev/null and "b/docs/database/Redis/images/redis-all/redis\344\272\213\345\212\241.png" differ
diff --git "a/docs/database/Redis/images/redis-all/redis\350\277\207\346\234\237\346\227\266\351\227\264.png" "b/docs/database/Redis/images/redis-all/redis\350\277\207\346\234\237\346\227\266\351\227\264.png"
new file mode 100644
index 00000000000..27df6ead8e4
Binary files /dev/null and "b/docs/database/Redis/images/redis-all/redis\350\277\207\346\234\237\346\227\266\351\227\264.png" differ
diff --git a/docs/database/Redis/images/redis-all/try-redis.png b/docs/database/Redis/images/redis-all/try-redis.png
new file mode 100644
index 00000000000..cd21a6518e4
Binary files /dev/null and b/docs/database/Redis/images/redis-all/try-redis.png differ
diff --git a/docs/database/Redis/images/redis-all/what-is-redis.png b/docs/database/Redis/images/redis-all/what-is-redis.png
new file mode 100644
index 00000000000..913881ac6cf
Binary files /dev/null and b/docs/database/Redis/images/redis-all/what-is-redis.png differ
diff --git "a/docs/database/Redis/images/redis-all/\344\275\277\347\224\250\347\274\223\345\255\230\344\271\213\345\220\216.png" "b/docs/database/Redis/images/redis-all/\344\275\277\347\224\250\347\274\223\345\255\230\344\271\213\345\220\216.png"
new file mode 100644
index 00000000000..2c73bd90276
Binary files /dev/null and "b/docs/database/Redis/images/redis-all/\344\275\277\347\224\250\347\274\223\345\255\230\344\271\213\345\220\216.png" differ
diff --git "a/docs/database/Redis/images/redis-all/\345\212\240\345\205\245\345\270\203\351\232\206\350\277\207\346\273\244\345\231\250\345\220\216\347\232\204\347\274\223\345\255\230\345\244\204\347\220\206\346\265\201\347\250\213.png" "b/docs/database/Redis/images/redis-all/\345\212\240\345\205\245\345\270\203\351\232\206\350\277\207\346\273\244\345\231\250\345\220\216\347\232\204\347\274\223\345\255\230\345\244\204\347\220\206\346\265\201\347\250\213.png"
new file mode 100644
index 00000000000..a2c2ed6906f
Binary files /dev/null and "b/docs/database/Redis/images/redis-all/\345\212\240\345\205\245\345\270\203\351\232\206\350\277\207\346\273\244\345\231\250\345\220\216\347\232\204\347\274\223\345\255\230\345\244\204\347\220\206\346\265\201\347\250\213.png" differ
diff --git "a/docs/database/Redis/images/redis-all/\345\215\225\344\275\223\346\236\266\346\236\204.png" "b/docs/database/Redis/images/redis-all/\345\215\225\344\275\223\346\236\266\346\236\204.png"
new file mode 100644
index 00000000000..648a404af8c
Binary files /dev/null and "b/docs/database/Redis/images/redis-all/\345\215\225\344\275\223\346\236\266\346\236\204.png" differ
diff --git "a/docs/database/Redis/images/redis-all/\347\274\223\345\255\230\347\232\204\345\244\204\347\220\206\346\265\201\347\250\213.png" "b/docs/database/Redis/images/redis-all/\347\274\223\345\255\230\347\232\204\345\244\204\347\220\206\346\265\201\347\250\213.png"
new file mode 100644
index 00000000000..11860ae1f02
Binary files /dev/null and "b/docs/database/Redis/images/redis-all/\347\274\223\345\255\230\347\232\204\345\244\204\347\220\206\346\265\201\347\250\213.png" differ
diff --git "a/docs/database/Redis/images/redis-all/\347\274\223\345\255\230\347\251\277\351\200\217\346\203\205\345\206\265.png" "b/docs/database/Redis/images/redis-all/\347\274\223\345\255\230\347\251\277\351\200\217\346\203\205\345\206\265.png"
new file mode 100644
index 00000000000..e7298c15ed6
Binary files /dev/null and "b/docs/database/Redis/images/redis-all/\347\274\223\345\255\230\347\251\277\351\200\217\346\203\205\345\206\265.png" differ
diff --git "a/docs/database/Redis/images/redis-all/\351\233\206\344\270\255\345\274\217\347\274\223\345\255\230\346\236\266\346\236\204.png" "b/docs/database/Redis/images/redis-all/\351\233\206\344\270\255\345\274\217\347\274\223\345\255\230\346\236\266\346\236\204.png"
new file mode 100644
index 00000000000..5aff414baa4
Binary files /dev/null and "b/docs/database/Redis/images/redis-all/\351\233\206\344\270\255\345\274\217\347\274\223\345\255\230\346\236\266\346\236\204.png" differ
diff --git "a/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-read.drawio" "b/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-read.drawio"
new file mode 100644
index 00000000000..bc4c6d0cca7
--- /dev/null
+++ "b/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-read.drawio"
@@ -0,0 +1 @@
+7Vpbc+o2EP41ekzHl/j2iAmk02nmdA6dpn3qCFsYTWSLY0Qg/fWVbMnYluE4B4jTQh4Se6WVVt+32pU2BvY43T3mcLV8ojEiwDLiHbAfgGWZluPzP0LyVkoCwygFSY5j2WkvmOF/kBSqbhsco3WjI6OUMLxqCiOaZShiDRnMc7ptdltQ0px1BROkCWYRJLr0GcdsWUp9y9vLf0Y4WaqZTTcoW1KoOsuVrJcwptuayJ4Ae5xTysqndDdGRICncCn1pgdaK8NylLE+Ck/Pf/xthHPraf7NePliLeLZ1+c7OcorJBu5YGkse1MI5HSTxUgMYgI73C4xQ7MVjETrlnPOZUuWEtm8wISMKaF5oWsvFgsrirh8zXL6gmotsTt3HVe0KFiEekRTzLs/GPw5IXC9ls/SSpQztDu4fLMClXsjoili+RvvIhVs5VHSEX3fKd+3e1pNxdWyRqkrZVB6UlINvQebP0i834G9db3YW0Njb2vYx5BBDX++ZNYEuQlmRjN0BHlIcJJxWcRxQrwxFChiHlxGsiHFcSzm6qR2T75RTaxiUME3zZgMmFbQIq1O5jkIvG8RqN5rBLod/NmX4u9e4y8TVN3468dflYWH4s+5mtjnGJ8t77jXi/3gece75Z13EWi3845OYFfcUjyfnT//lndO4W/wvBNcTexzg8+Wd9Ql4BrBHzzxmPpN/5Z5jjDoWZ/sxmPq5YJb6nkHgYOnHlOvOfxfw5/vf7rcoxcMrgb84XOPftu/5Z4jDAbtcunguUcvGdxyzzsIHD736HUHjTyUxSPxDzNBgIBGANKIeC38mgjbXIJ2mP0pe4vnvwSiPzny7WEnAS5e3hpoo1j7L1wLa24q3eQROrbIbk5qmDsdmCtZjghk+LVpRhcRcobfKOYGHiw1BSrjqCFK86XWns4eA7VqGQzmCWLaQIVfVMs+wVX0Esd/zFWGcgG/vevbzPV1gXYG/3AX6FElublAF3Nu+9Lxo1GgXUDQBrqwC6h1NFzAJUV2t/bK7reN+HwhBJN7EPrAD8DEAf4U+GPxwNcQmGASgMAG4QRMfDAywcivqdVdSgmF59ytC9cZ8Q62vdrVNdyknJDP8wACp3N4UnadClNlfymM8esJk8pB5vnpY3DrCluU9PSTlBSd9RBVv7IcOi/Vdrptn+f8ZPt+w/s9Vz8/OeZHHqAsvXz2PZcSFN5JNoQ/ELRgh/1hvYJZH68yne7twPcf3w5jsQt49uD37ckUhGMQjGYZxGRMcDrnnWEqSM7m61XTFcvZj3toKRXmnGDnBTCrBQQHjMYFFHzhngxFoVetu/z9iNlyM6/w+QW+wkfxkdkPwcGlJSK99/HF92e9vNDOxEKJ8hExEzsrEK8EzhEJYfSSFGO3AwxXrl3VjOKn0qJ5jPIOjSlMMREzPMIcpjSLlRkSGENS/aUyxTNKPiOcJb/TlTwiSMGvwgsakpAyRtOm7KsMA2eKQZ6qWakM7OhlFKsrBt1fLAbpFeDbsazXscw0vnep6nsuMw1v2LO5pVeRs41Q+9jAM2R1RefgcuUV/rr/QrfkcP+dsz35Fw==
\ No newline at end of file
diff --git "a/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-read.png" "b/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-read.png"
new file mode 100644
index 00000000000..f8b9589d6d7
Binary files /dev/null and "b/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-read.png" differ
diff --git "a/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-write.drawio" "b/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-write.drawio"
new file mode 100644
index 00000000000..6fddf10f064
--- /dev/null
+++ "b/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-write.drawio"
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git "a/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-write.png" "b/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-write.png"
new file mode 100644
index 00000000000..c976cc99b91
Binary files /dev/null and "b/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-write.png" differ
diff --git "a/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/read-through.drawio" "b/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/read-through.drawio"
new file mode 100644
index 00000000000..7f7bfd71641
--- /dev/null
+++ "b/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/read-through.drawio"
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git "a/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/read-through.png" "b/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/read-through.png"
new file mode 100644
index 00000000000..f8f457c7490
Binary files /dev/null and "b/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/read-through.png" differ
diff --git "a/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/write-through.drawio" "b/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/write-through.drawio"
new file mode 100644
index 00000000000..7626c8d1f50
--- /dev/null
+++ "b/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/write-through.drawio"
@@ -0,0 +1 @@
+7LzXsuvIkiX4NWk281Bl0AAfobXWeGmDIrTW+PoG9sm8om6WsqnqGevpI0gwgNDuy5d7OPkbTHcnP8djqQ5Z3v4GAdn5G8z8BkHgB4aft7fk+r0EAH4vKeYq+73srwV2ded/PPh76VZl+fJ3D67D0K7V+PeF6dD3ebr+XVk8z8Px9499h/bvex3jIv+HAjuN238s9atsLX+VEhD+13Ihr4ryj55B7PPrThf/8fDvM1nKOBuOvymC2d9geh6G9ddVd9J5+67eH+vyqx73r9z9y8DmvF//IxXONkwglAhsec5gD0g0PAf+Cfy9mT1ut99n/BuEtU+D1DLG/Tvs9fp9LbBpe8dKpUM7zL/B5HNzLpL/C32aeHqmn9e/u/y/32uY+ln0fv2nb9xV7fWr2tNW3I0/N2EYed67fJ6rI3+WLJ//4eZf2vyHO+P6DjCfq+9/olKRD3NRxf+JGmvVPSIIAX1+vHMeundh/sO1l3UZ+uJfr/D7+P92rZYfNXhXCsT/GR3Pf3H31468t/th7uL2L7fbfF3z+Z+enUurp8s/e2TNz/Wf4rYq+l+32/y7/v3Nqs9+JOq9C/xN3z831znul+/T5B+N9/lfHjiGOfv7vv+hepanwxyv1dD/Sf2sWsY2/l1Eqr6t/ubetx3i9W/r/CGMz1Xx+/uP1CZ/FID//BuL/kY8/4jfWOy3D/cbib44Eadl/hvL/UbRvxH0r09PJyzyG0X8RjK/XxDMW/n5+CHeiw/9G/lc4L99qN8o5G2OYN/m3nb/KPlgv1EA89dxzP9yZI9mJn9S9kvN/iiG/k7joHfRnvKjrNbcflb2LT4epH3KyrVrn0/gc/lKxe/I+bNebZzkLRWnTTEPW5/Rv/T1aQ/mfv78pae/BY8/gCCfn07/puh3MOHzocvX+Xoe+Qu4/w5sv0M7hv7++fgrTv7lmfJvMJL4vSz+HZqLvzT9V/R6Ln4HsD8HM4Nx21rgr4KFQJ8m6QlHmH/6Eyz7JQIk/bOv9G8f/Kfk2X38dxn4kFK8x/xrZf5k695azLv97CMTwNvUH7V+1XhlA/4RrD/fuL/ZpGWdhyb/Yy9+hPjZuapt/0XR77rJpM/OPGAIU++OVI9BIn+/0VVZ9nbzp1Lxs+H5u0j/hiD80fm/EJv389/ICvXz979GVmAY/mf076QFwf9RWh7A+Edpgf4LpCVjzfbT/o9o3ruE+R/5WhNF/WfS8i938V228T8+/b+wkDj5owXg31wWCP57FYKxP1Eh6E8WBUT+C1blzwnBv78qD5EZ38uq++FOfxFQ5ZU2Y1iqX/jOJMO6Dt2/iUffnz9/IuTr8EpzvIy/ON23Ol+hpn66JP8oBf4oea6zeI0f+/DrI8SNr8WlK4/SrQOQ+WIgnz+a7ZasWzxX6vvCiDQZPu/06stY/ly4K9CypmchwQZl9Nf+DaIg0BF23LIRc1IwLbKB2KKtdABH84q6CpPdjeNLVnT5QE80q1Rsu7cLdXRh74ga0GyujAIaYIbDsomf+YDG/sGDFX/Y44v9z7/819tCPBfU5+53nYh+blIgvAfPG74Tz2uEfj779kX3hUDvT89IkW3TtEnrJ49YvOy819vJicXmZI1ZpeTANraG5JVL2pL+NEBZKs2bDEolStlOAGvaoqiqK4s61ELbFpkkazuxpP0Ua2pG4k7ZkPYpRslaDmxhO6JI6dmBmz/FSBSvVMMWtKOIlp4eiVk0pn/9FLNsRTOMYunukZBNY/IQHsUlyTZPsSOYOoBEJLvYvI+HMSWyzUWTTp/qgPgW57w/mxElks11sU6v8oAosdyyvcUWJRbFcQFOq/KHJLHUukH+YFLSUwycALPqvEkpbNluEF/YlKQWBYs+kkS/xcI7ad60SUldSha9qZWmrX9/LXjgsc1UEdtJFYzEKG+Lbtys2+/Nbn51HEe/12d/qCQXI7vtUdiCfzLbzsS8m+ma0x8VpObtezkf89sR7ionj1RydtdV6juvKkFaxa6nu9F5vqmR5563g3oSEDMW+3bw9AlGSg7E9fepRxEdbYn36Rwj3pJvy251PgrPKc//5vlvKc2IdFjinse5BvapsMaeB0pDKJXBg1ntef7zFDqJ6TJPe3Qd6uSISiJ9E/qpTgE15nD7jU4PBFDLybQ1OsO7IerNxdzSSMIuQxSYvfdavnfYSqyW0JbaGYFJU9iRNrC9h4lp8hlbOHv6AcOPnLc17PVREVLSO8DLIqJLEMBv9t2mYb7HAHDZbvfRwKtDUCu2g+9FnQiuIyHSxAGmC/+gaX9FevJJqqsexeIEiHCGpq+cd5iMrsaEdwSeSyMxfXwq/MZK0V7EcueeJt0SB48unX1OvM7dpqatGmtFiWS3b7USLGR/O+PpUn21T36mHHxEmVKTOgQe0REQo35hRMz7O+efq3kzUelmzWaiA8EMCGOG6KBlvpRNfYAZs5pm50/IkcQSUNx2FwyQ1/mEeBcCFYlxshary0SJWCrv3bK2ro6S5nOS+CRhjUHIjSPvFqttUDhJRe1Un/vKeqNBHNRM32ZPG6n2CxiuaUJD2GpTRiFiwtfklT8rnr6Hb1u98vCCy71Xl/WZMRO3Calc5piYo4N7UB2ybngS0KbYuL3wQM/5Nqzdv1UGkOzxklvFbdqaC3QmEncJwkc62vtig2+5xXomQKtOd2+tYXmAr8hNnKR9nLZzwXKPQjt8RWdIyr63VRtVYztePGGlEx4INmVP5+zCvjz7POT7zRBdnfVcWqJSYvCdIB6yazipvpLiYkcwbdijfg/E9sF6zSGQo8YFysqzaWXE2DTIlfGS952gB7uOr3VOGo/XqWxcdwfOI8uv9aMzOB6SyqaCBjB5VsHqktoP0xNkeeupfUk5DMc+I83TcxNWtLRefUVZaRtL9/DoM2gYvW1aWbm08/VR5m/Gyfnp3Q/rDFPiHXu4C5DZolMWNhnqAD7C8gbg2M6RTBioeDJplXEa8Jr7Cpj5EW+xNNeB1m1JIDYsXXyeNRylSYuLeUCjfaxMY4ZwrYkgKE2QYj6TMOL6nlHjsfccTWVBPEQPuLEXLNi49Opz01u3bWUIFwTv413apnE9cLG4iTgDQi2/vvDEOqVGRW0hWiUUw/SXrqc6ttTTdZJfBkholkLEIuG5bENr5w00pq5OcEZSaAv1xn/G769SvbMDFr+jaULVodBKbfoMjiBVUXXfpr+i3BioxdZKKwSVM2YlwglJrvn8/Flja87uk2+hp12JHgKinr2vw2cumJu6ZrSAvZyon219gVuTrBSoxkhp2dvGSNm29SGOQhLECtd5h8pCKz1OnLK/Wi4U/metgYddkFZ0tewSTXs7mI8ofM3zVOZGdxVJYK9v1Qui9mCYu6R48njKj4FAsmAuy5ptgVQCAoKlQ6BwDgCB36UkTPhKVi+mTdKBeIqsocnDTaTEKQobPi523UeHbF4nC+2pL4n72H+ROTnYVdqSsR2dri98eRpKEX0ZXsUsjJqYYWEQ3w8v5p73Y8QujBvk4zU0lFpgIqiE5qPp3PBiMiZXtfHwqQXiYbrlY7j9TuCMPcRze9DkbAnVKYir91ymjIgo92t/Z5Tyu1eNk+UUGJZ2yThbaGCt/YkQiYgvltegVQlj6TytopsFUwnUUQo+KvVpgcbIdmuft86wZPa8LYN55tOeVJ4nYYdaSm3IqDFtuv1R2SkSo35K/d6bhnqCoC6bAWtk/W3qQdJwF9vBCKt0zvIWucBoyyt2WTfco9MCu/bDmqbZiBLjgbFQ4WOJplA4PdJHVUFQOrRlG62dyxTNaZC3156V+2BqUEa3sMUk36BqpF1UFJxrIGhbm5DDIoHwacbOAvhM3jdOhGZgcSwqNXsMT8QRJ809oAmUeobUIZevUQvaCZNsLniZ+JFY6JRSoVqcKGIvAW1dOM8GN/da3s8YKhXeIrlLhvD7YrD8aB45OUKH6HYFE8gh84jy8IVvCbQhtVfRRoGMvmRRamrD2chTHmP3rnxembLX+gff+KLcKbGViarHt0b17548Xjq4rI8sAaOMzivq2KqksFv0LHKrsVqcwZ8TAYVPgExvW/NlBJm7RGQ2pkstQSV5H1UfjUekx93IK/uJbnZE+/mB2GxBXl1a4vIldHmo2G0zEWtoMkCLvOxQKC9zVHqwnBdOgVbaUNoHD9Tg8YchyslkRzLACA+R9Pn44klL0FudbC+/kaKeWSnDah6I4DbXo0s81Wmm+mjj7BaX3ra7T1wpL9ZtQ5iP2szX0HZ9YEY5pyswPYEt2DYJ/JiQpJLxAGvNMcHsPIZEst6o0l4nm12Gi+GHV4dK3jTm3iZZQ/PSY4FIbLIU/aXqkl0SOBTiMaUL3buUuAQ2rz1sFEUb5RtF81bb5urKEKWY2K7w5RdTISL2Q/PjXefHifRAkCtKy8tu2g05zjrY1oFXKu2P2GcmMs3PNZ+0qbx9TLNUCnp/yGZ5DZ9hxz0ZP0zUPCUQFjsOCq7XFUMXNs1NBpE2YK6sCdG0+NKid1RnnIEVKeIfCQ2BQAebMko/HcWY2IclzCsA08X0EhZHKemFC3W1i3Pty9j8QOe+3q54dBsaC9n5XVtMf5lG+O4HcnLnUZkhohNH2CYi16Y8NArjYL7aAMItZvFYMz/YDCe+lUyPGeMoUVpMoqUEyPXnFrEC5CWjFDdjw9HYjXmiOXY1rAH3/qB9Tf0zUCRXfTcSslQhmJDIvSDWtyNmeA2DzNgHWfRBOUOGRoUhlGSY7jgOaDS/CMz0MI1eQ1nwhwqEvNuYAWVXbA3fF3+d5QQA3r17dXOZjF2BMPdio1VK+MMcBacbXDMtgUpZhVJnzutj1CztSunKL802uDjyFQN1Hsa0sPBaKtnGNb8yKb50xZG32NUHLjPefYQckcq/fHQq4m2IzzPEY0bdzNRYVjcXO7S0fPAowrZicVK0l+Xv7qfpzAYVaOiQH4ZVv05cWjnQqxbV43iZpWBu/QMNPKI+jPahpa/vpsel0uSqXfW2tfugqN4R9G0wHGY36349AjFlc4pFopVpL0fEy/6VX/GAB4u119Z5n6OaEglUWcRntWxhx0gltRoBWy0UdbCIDLO/K3L7iQM+zlqX96/k5d/dEyue4rBNfMf9uh4P+FDPsuqVAl+fWSp34jFSz3QnyjxXsKQkmRTerYegZzc/Nht5Ehb2zcTUsM/6DMBWbZsTjxsKvYYtmGO0YDsn6l0bVfAZWsuLQVBhZUStcz/RWISRDldjjblHH2kgAqdRHzYVCeLSB3pd323f+kdhLNOC3XpTA60U6ZW65Juqc9ZydyUFJr0I21WBp2HM8dF/fLkwuJfy1O6xasfJ8BL7sadUBu1zn9sjg9v9VK2ZYdeLWjoA8PrTXIp3n6/qf3xapHDctp3cPDr5zq2DVEFqZb9vE/6LdmzbSMc66dWs5o8vpQ965iE4Rng2ILrrC/83fCZQKREfO/fL5hvD7GzrVvjxBoUoLpsesJPOTUFmDHNot0CKyhtvqw6kPeklW6dycznbOq20eDV0n+5kMvd+n0aUuvciqc/aTutcoJXPzXLuZKjZsJC1drbrF+2pAX1353WPhvX57sCPH+jGK8pOpr/08Uegk/rSBwtpmeMVvqF92IUpRdzjeYRl797GFuW2qMXRnOX6gku9rvnOabyKmcJeSIuvP2kUGeF1/H3K8egBHf1h0lugV/PcFJtZNfU8Br8YaQT3L2H4Hnaj69Ov55r7gXL/cXkfGabgMqJZKTP98eM/2Cp5vLGBmVTsJqqeXJ+AuWcZnXT5YknrHm7Y38L7qqFj4ShebI5IsoXXMtRi0hLv3DRkAe3TKh0rpU3taR7VcyTn0VcWP6/TGlJaZwCeFKQCyr9Ugp5vEXk8L5qN1PhxDj0TaKAsIdg0cD9T97itlKZWHNxmDhT3DS4+4L1U/U1lRds+lMkS51utv8nSTA0YlTlMzcYhcYOExNtxLpqKTbGNvahrNpURQD9mWcfQ5R0NAPmlTbr5njYigNHQO6JmDKAPLiXCalgbyP74XT13OwproiRkz7XeN49VR0tFYVZ8oIwGsL+fqnxjQ2waU1GJAH4xnzJQAJm1hR1s4jlG2uhM25cEzu03kkVvPJZ3jCwRtPHTGwAbdhmrDqo/3qVfCQ5uzDw4iOb62OD4Wpoi57zNflyk9orynnV8xIM1/LRGYnllR1id2kb9TRr1XkKJwjrJ4jWdd4IbdJYKJXtC9vmAiMTN31IJd9sPhOtDW1RdUWRZSgZXnqnnh16Lu2bJKSqDBwYR9iEiWPB6sC/glMoMLlgD/njiFo2xPzKhLrPzKBsjVbHBwCjFdDSpoK6DC0yGKaQi5lpDeDRLIpJu34AtJim0kq+WmX012z4t/bI4S8IcflE/TqPSlBr28dSuY5ikt+hcXl82Lt3NhtwUPMxmhLczrYdx8NK1W1OEuYQyUT/Km+0LSQPXedbr1mYe6lJ9VF6pdB4Fa9zo98UCMObd5VMQEuMyXP9+2vh+5v0CvJg4osHLeLelWXY2SJdQO2Vyv8wlKLbtlZZRcoYkxm4OW1ivgemeuSISS0f74Ip5eUuLBOXAebpP+2uvftYoyLAe6Xprhz7o+hgJaB/7o2wvPXoMxeOCtFl3ioIJZK5sqMSIDwOu98vyOPw+U1ytfY+njI6vWYC0yHHfsd5b1b70VH2REEPAKXFzqcrXiAf26rVJmGooBmueD5tEmfKk9ZrLmDNkvMvHZn9fSemQOruvs6BE0iK4inHoHkSmgxBdUCPoCYscnIjt8JmNDjf14lSyMBqgBJyWRCWH7EYckUcGo/WCmyRB0Y5yhozMN+3hSm/gBOCJpMqTNDxtqp/d7nPcSTWyRnbVA/XwqwkeE4O1UU62J52pL89ngRytKlwT+gL2C5LesKDe72GgDT4q0HrigkEqrusn7uCRJPS4s3rD8p1ETnqjFsM6r9XlZHc9bQ82FrtaxPgt+SIw4Np+u4XKGPLlWpv8SC+Ba2Yvqgl/MLfsOmJR0Y77LKWXqc8rqTsAHGrqN2TjCSNpioPLX/IZ7i9yaHUV1DK+rNZr/zmW3dI+HVW670Sq10+g1NE60VReIkkOFfQTLe1ZHhcQ1VjBPMF511bEar/xnWIF/4nWqW1ycuXoReLDNtfWcu3bOxjUAr6UsQsFBG5c+4EFN7Vo5GEnwVeHSDFZIPRhl8WCvTZx6xOII61KuCbZVwuCHSXtGOKpW5+WlgJn/SEo9zPX7tCmbnJDNNK+g4+4P8QpSQ11e2fS7Mw3bXZX6uGE5QntNUwqn4VxA+Kl0B0jZuWM9TAcL6Kut8Z+ms2lBIrzjKMVcrWDBca+myZveYXyWPGkJKY77i0Jdr4116I/OSj8IHLLZhFsDIis9foeaObwGHuis23ddaTUXmCwtr9AOM6sa+LCJkqarFrLekuS8P2gsFW12lc9TDFNSaqJPXPUTovdM6HTzOaxIXwvhrkGlukbd2EcwrYP+njsmAvyiCBz/BuUzWFewcp3z4PsBZaxBd4g/lI/M+Nu+ie4Sk62MKC18HnsiexC6UD2ufdhqR4a0YzawSS3JqtnRjcuVTYEG/1VyWsA7XR+t6Se7EjK3bn7egzxJR0xMiYSGRZ0N2L4jeXY7ifNppbA0Mm0yAUp3Ty51om+hY1VfYdMLqiOjWE8hig5cWOq72IBdtGKvM0vWRbBtC+KAjIrRblyLeURvEbX3QIfijuX0Q8TARBt6138HQ12L7ZH+aPWqI9DMVo0rzFVusgJKzYkwQqNlPNSalw0lskj6Fhn7J9aI0ePx2F8zDRdexfIZWfGjHffYWpyVAARglS5K4stRnyOlKBWp3sO3Qbssw3IG5viuliiweN28En8MpMpfvndCzcrIpsrw8bL3HTTZIyHCRHds/8Uz5tzhm6aqQI6NvGHdKcnz/EKC5hUo2jUih63PRs8EpFyOzDkYJmu5C/b1yJZMvowbmc0ruWGzVAtIkDGsYtgYMnmlSBGO9VYAzt4IMDW+QVIh6PL96vTiToEzQcRyZIJHjd+44pMqw7+K0SE/ZB0rhhHEqI1SiveDX484Ph5TGLXVmKO8nAaj5UbL99QOqdNiYyUrLjcAJ0jh7EKWnCxUSxDR7YawdMBFnqNqKlRu1umUqvr9sFOUqkqZLZP8+SsjwNBnvcpOTVtRlGs2knmZCLF91ZJBzHDcyAevM6KJkorTpkpaqtyj6X9RGbYooRfoN9lQKQU4LYBfN1j0Buk5ew3ToklhF7OhEhXCDgpxPryAzp059TYZHx9xc9umWqULXJ/OW9ULQDiQA880CMRWm13nU6XOUNy5FWZbioISRRTKrw/dPdwjm9nDg7fCoWyK7FTJJaZ4MVP/ASbMrRRu4tj3dx3VT+oroBL7Md8vhryPIIkaO7ZyjStqSug/UtqwQdkqeT7vOjJN4AxMRRBtmH1dD9PJzJtW5RcytmD2apIadlw9A0rLLOIkg+5Po9Ho2Dy8aOy9PP1WyIqHn4YhZTzbt5g2aK8vB4N/lNpECWWnUj0nkAEJB8XKSPQr+8BYUF7YoRw9lupeHrUt3z/qaIWktSoAzm+oAG+ffrfLzE+lVi3oj0yRMCYSN6TuG1L9zl+q5iU3KgLPX7X95xM8I3g81Rw37M2j3RxEHv7S5TtVd99li/9p0Iqj1/teHoTAiMAxy8fso3N+6T7MgHs7W0W9pfAPlXeUzK50FNszbXDIBgwzJlP5f1y5Ln0PVUlbdfTLRmlQ1F8T3X/LGHg3zyA//ezCK6/TwX4d0/H/ytyBv70dBz7kwwT/CchBP5rwtD/N3NB/izbw/59kH+SS/j/cMf+SHBA/kj7+Js9w8H/hVke0P/7WR4o9C+yPP4sUQr5MznG/rvk+D+wKv/bZnk4QPPXLI8MOyDL6jkGrA7Kc9igpc/PEU5UsUNzzdrbxUGL6srmazWO6pzwcNiqULMGW78+JCbTe6hVtDn6LicsL//rVOXdkz3Df4JSUQtH8Pfx408Ix0HnfvzEe4z6ezuK7mQPcl3ZkMzq8LDVQwktleWIzKooxSIPMzhRnmSdU8tJNi2Zg9FEoUO29SEljkRzujYzN2uQlNozd+z6z/SlpaSEHC1RWiqKPKlQkBVNlWV9pCsM9SAo9jINDc1imhS7ixVT3Bc60adpVBc6t1ipg7SiSlY/N3M8fZAEzJ2TaFahSK7nKuZRiZBSM+SJfQEsaWmNGOKQaqhPH8KVCvpY2lRRVDcLWGgnFOJDvdBNmV3z7bh06vggTuYgHekgoDcuRdKJaFrwFuZvH+/kMotmGYriNmlgbtUgLbVh7jzwi4J+JkcLOliiFVcUm1OiraiY6WMnw6o20j8md0EeNPZ2vI7t/fJ08Q1JEz+coQTmj/KedG+NimdnYKTw9w0ZSp99hox3886BlpoiDmoBfoP+zspmkgeidw86EwFyHSVL1OMjgCHwBlJ5DecXvrnM+Qt9NsxMbHBoj8jt8rFL4Wesn8vN3oBGi9jpGa+1K3Y001O6GzfPVDEnLM2IRiGcYNwEaTB7s/cqGSlY/WbaT6QwAgoNlYEISV37otge2pgI7WuybYqb7GxnA52UR9G2CU1aoGA7WBWo2e02/3YAO9dhlKAhnWfu1GXoNGZkw0E0ZpO87NxvJDPn4iAYHIQdhGMWDOD6CYt3n95eKaW8xVTJjp0rYuVZLhYRHK+LfciBlxsBijRKilzpcqePIbWoCUli2iCSddkzXndBCdTSJt+IP2cjhEp+jGneDo+34YqXEMxascJGZ8xkJA3cgBYLk3c8kZ0hCxaDjpBLLh1Iiv3Lk3oXcSTf0R3f+SMAxdJ+A3BCUCQ2baTE/I4SOIPJZ//XaTaqACgasPylLOIoIUBGaTqAEodAqJLIDg3Ojmxul02BPc7Id+MGiDyiz89WtSL81USKLMz7py0q1sVCycjakTLSkyJWfKGgyRW0DJ8HsIfjgoptRm/lbWr4EjdvLpQJqT5nhmrnKpFYSEBqSRVPkVWbY/ooGkcfzPwemFmaRAoGdsI03dCnt0hYPqkPNwvGzMklrR0b0jRLRKQ+HZB+OUFdnLq+k6xy5zWIQeYRhNhWrFG/L+Kbm18HNXy94RHn83EFnBixWLeNUhaJ79IZXry78Pnsy9Nd3mKfaMAsAiYIvIMlWtY6XNOC4EDJ653hiCXnTZKR8Uike7Hqzke4i20J+/ppiY2Ptcjea3RO1aS0SayOsfQASFrS7+4PHEmz5fkYijxW0nsW5lQ7aV4W3dnTh+mINe22zZXdTLjpwxUnmbwznrXHbcDHvrSrz1VTPpJg83bSTgixfuRtnhr49DcN3zCwqC+p6YyLwrXNXOzUHEa4hQ6nzUCtUHAqFk2Om+VOF/bXR+2+oz2kbvNRKKHbRlbnGTlyPhVk1yMDQK4CbtG0sj7JSbXxhjYHn8xbDmG8a9j2c/IrVlw8k2CPAzQaMZO3xjGv9Xox4mDxkq4EoUIESGZvS9M1YxZOLhSPCTYDMy2uRYVPnWaGDiQoCvmag8w/YOxoL8kml/Brkw0ftnPTXnMQUL1yhOkzBp8pJCKn7aLl6znQVgEos4tG0wE8JMoo7/zH3Kgl2Ne803CBH9W0ciKmwIzyZX5yX/0EmWFCpFO2Nr9/elntcoPsRXFbYJYlKFeBWlnzXzM128ho3VPf/pwCfLuLNOg3fp+4xffKmm4RZ0ujDo44x23zeEN7dWN1aDSQjTui0in6Dl21++ZAgrh6Ukrtu4LRmBAtp+auzc+ctfp5rkbUvbYJ0Wo4lUkxmuSID+qw/E/iEkXUrLr4Vw1A5gdoqyDo5MD92hmoyjpP3LRO48n1hco3M+KzAsDiXjf+zA91NIbEna1wU2GCgL6HTpH6CvVMiSf/+fYuTcpbwmzkqnzerA/T7D9VOGdawzpkpWXYtxOjCCIYMjiFm4PkzibHWPRwiIXWrNmXbtb2wqRg+XUg7VPQxGpsCtmvuF20jwGWqu5A3mMA8QQlue2KyXxvfaWtOzvfyxJFFffHMwxvOdFY9ipiI7SP5D39NM8BbFyH1clagN6D3s2PaZQWg6sUqrrUiTdWtpIiuQTK07GNZmpDyrqY2RmUOrNpTqQfk3LRFFL2ZiRdQmVBx2GOiESFlL1IYjgGHBWyWsxPbrXIGwDFItnomLR78UfBxdbt3lOkBsTsSDRpEgRpx+u9ELRdd1ECQvMiwxP6gre/muRt2FARflxyj+s+dpzTDqXEjhEIcgz3LJYDXKZmEOq1QInShZ7JZhfIOyN15Y0pyq5/NcDCDsTXyGboFb33EKvYMV598V+gmBYAeL1VXWKdVSvkhNh4j+ActPqi4nkzhsjL1R5bOi3SNLW84VMZATZHFuEJ39M3n3SdtQEwraG8+AHemY/II8hpZvNhYp88F/MxqEnv9m1aUknJ59XPAl37K+w7JIt3ssAujCE3zkkYuJvjewowow4ka0cWGgaYABLyJuSCPwFbETdeFF/ewIYuHzNuol9MsF9//htM1cjucIIlUi9XwfhO9spRpLM+b0Kh+/JDu6TI9KU0lw8asCCKR/ESFOz4cnv4hRP1G51NUCSRlCtDu+Lz0RkpG2ymLTkJbZyczrB9SS8EhommrTeh494gn30eTkIZtMBWJlS6Yl3lfaLVCI0bIBKx1bw7HMXup1NV9GlbeCGCbfx1S63gM9sPWumIIw7/3PrZuJ0tSol9qo22vIFMhKK52rshZ6uMBQtrQvVfAbUe4Bs/H5SFzEcHHPorfwYjzgD1zS7wHLOwm7Mu3etB4L7d1cPFdYerOzLdUSP0PfOc0edl/VDsQlYyuN5CtIZS9565J4/y+jhmaWULtJ++lYbBQtle9SuHU24Nz8xVMpSq7x4+VIxx4wF9pQOFVFwWOwUy4YeNHD2Eoj3U0ZcASXfnUL7cj5iw1UMYrUXT6JJvRzJfFWpWjlcXntVMBOo+ZfWbNlZrh6cOf1K2rgKNR8EtDnIl4ey3tqpINwzvbzw4rogUeOArFS3pNNqt1/geXIa0ScLhMAixYpG+ldE3hYaB+uHuLXxsi87EIlBt/Cz9VKRmhfPI6+x3Ggu3Zsf++FpiwizBDTVW+FLCsEQfdkwV4dcslUZ5o1r50bojFlx2NnIAEDBLv3y/bOCI3W3Zhk7kvDlRNX0Pfg3zMM7axG6b4AjdN/zwmsU4loj6UL2hqUCxpRObcsckIzaR+K/pVFrAJ1mZ6rmkyDSXfWlO7tfNiNwI5R2Djyx7OeYHoKv6mdFHYY7Ugx8pv1OJJGMUEFBKdWHyUJ8J2/dKWNG0Jz4UqfDCLBDxSNNn73WYpvtICncFI5EKy5aJHYmxKyGZivOyZInhvQKhLooBF5uSFEej4orH3Y5oqt7Dy4N/Q6gd0pwz/UbGRhcyv3Pp0UcgELFI3QNWcgwxtLVGYJeYMx6dBjUi7QKVSVpXdIeNO94q/OQRNTzYcavytTbcgFToG5zyTemt/3D2Kx0e/JXYbNEGgqk48400mp4dXF4/G+fnRQuDlW9pcQKPp/SGiDcD4DGRLXomjad472+tPKzMnO2N6XRaYzEzy2A8NCs3EmIJavO0QWmYz3QEkRrP5h6pgw3aiN/4brSkF/TYW3agZt8pNmYwkAfUfVum3qzieFu/U5y/q5mplVpyamPRBHlbS9sgysLik7tuolkmMwOMBmt2mu4v4mWQfKn6jsLBQxjUeT3U5UWK5zMTprFGKbUJFCWDFgEjjvW4UleP6g6bgJQYmhUfR/ZbbmzOAgbPjiMEqoWSxvNDJ7juC/O9QrEN+a1KKmwf2y6OmM9lUqx9ouvSDrdJgXpMowmKaYmn8l94hMZiOjBSDjkA+emFECfRe2cROmDX5WDtlMY0lsecRM1Ffyxhmy4dAo+Fg4OQRkwqwsJ6Bu2K3aLkx7mzI2p7NSeZGtkHFcKVUsa3WnMhmk1GagqLRLIrRktmpz03TwHc5p0/E40wCzVNFg+8ZXAUt+z19wYWJcqASbctl/eWSr1Mlr6OLnwN1OzIOhQjOe/BsvM/fdSSSalTCvW4GQdkIP0EOIXUru3risJVgHjkEeTjdIMvCzlo741nPIQn2tdGwnNEPk8hrq0HKUiYwmKU5xbptEXIX0ug+dp73aOPJ76xElcTH03WUqdkmzyWkIf+hV9hk6avR+53mSOLgXAl64gbZRk0Cs6EmTJ0x9woNdi160iUfzYGcby525ymcNvEa6MfpOJBAmzcThTkYjAia/G2yAciDTWpOxrZnswQh+A4o1+gUw+9o+no4Hh1o88qBye4gn1xAZBlrpbqBGu/SiPQepx8NsBf52IhW8Gex9g0sDgUVfJFmmo+Wn0SnsV8cLi6UL2mXpa5k0hYiaKkPqayKlXzhA7a9+9ahKlCZPOXMnmPTr5ZKxSquY2lcJftciaLzZXndDdVsuqYHqwces2IYnBJAi2ZZb5w8BWLkjQ0f2j3elDHo+rWIuT5/H6DhH+oTv4JLoaH7YkO34MBJnudy8zXj0104f1cSElZpLUEq5e6RjdVwHeMtV+AdUbm4xewcY3DGlEZKQ0O9X6pp2Fl2awFxEumudzfDdeeYeZ5jcw9AhYMknAMWiSk8kbiOV5qj9K/g5/UpEOgHYGHajyh+L4RK4VAlsezqyopM1GlA2GkUzuMd2tKYbnQMdeumJ2idLyPqRPyMJHyjXJsqBFpTVLqRZGOvYFDrHiD1FRKsLZ8T+BLIwaamux0mny2hDqX9XN4dWdk38phS2ueMTDJVJe1e+0xc9r59VrT35BJce16c1Clyv2BFFOCdD1aX7/f88t5nBopVVF0SFLLc5mrNsCimWuWlK8690kxRPuh3BR8E9Ygm1M8qLPqCXW8zjpB1NZIdgeWHK2e6q87shwlra7N5me8ruMP++Oi9bpL1sq5xaw3Db273WYfi2GjTHgKEJ1SR7J96thEaK1qkQmLSx18OOrTsSa55SIPM68GcHI23+KBPG3WgryjyVeqqpZrtKIRcLPyvfFjsxAZgVKUu+1p0ST/JnooiVo+Hn0MtbQlJ2j9hposVshLCGYTwzXDhyNliqzjdD+GiPiemsiibyZjZqrDQwofl1glFtpE4wZ9c5BnIf2eW9g/rIuUChzVABhxiIgCHv/yG5lUBCW27/kG70PGM5TqKzZoAT6IXFDZbqNepafUHVwGePHValETVKkmiH6VcLMV3j563izp0qvrTMFSkyZmMRAxq8jsRGDHsBbUFCSmwq7zr6s6ERWMV2m3kqLrmF3YZ4Y4Nb1LWfG6rZt4Xmx6gjIJHINhcClSd3vThQEOzBu2mC0N0vWsgtLqEtIJSxIHOJVuqISJJuMdD2lyBR+e1jdj6DnL/fIu8zV37650MBxZP3z8s1Nnze9j33+iMmRNd8/c2MJ+PJ2CqXN7Tw6Lez2oL6iLM233d0aVgvfm51G+bv4ks1xTE1hLTygWyaq+sTmBvmg5jxDAHFuWgjevRs+VQKXcD9cp+sdwOu2BiTM7W7ql3/ACptiZEy9Zx783UZ/HIaNuJJY3bLC9Ma32Hqw+ZEp0YvOKlstopNNIsCVzaxe1sOTJ6zMqcwYr8+PplDTzIQ6ChbgyN/hdnHU4CCUkKWC7nwFI1abSppgI5cUBCepzhiq76N8vZTjLMI7Hgfd04xUFzdSnYSiuKVLx43dYzQ2NMk08+/fQA7YRDircyh+HKriqI/WOWfp8A5cV8Zonrgzr4x8YhlY2tN84Aieru+PJ3RDU18RF7l3o8Jup0AVcLoAfszKtwvtgJHir0pnH5WbWhd7/nrsC4iA2ekP7aIhtMXb96Jx1vsewcKpdqDKLg8DmKTQB8ztC20TMOGqK7qKkDzYX9q2WTFwsfl1u6SMrdyTVdGGXNdbOVjNrK3wmVSid6Znr5vWZa4aMvuqigvEFGoPGIiRmf0SRdxXrCJimF+h7LHN/mkDD8R6xwSQBEb92tBimf6fS628mj94xEO5ScP4S6A3Z4ohlJRqrJrO3g5nYFNKLA3B8NIb8bEMh7N9aLMptkOSRtS166dPi6kpFdb5FdzcgkaFkLeu99rp8lVN9jdRExbLZMeBDCUX94FjpA6MKTJHOPlTyTteNWGxxUTFOFFhCprerlrK2m/TN4vd7m+BYBmOe3Xd+fa2Zn5XppYkZObwx0EuydA59LrgVnZ7JvGFIiaTD2QaamRX9Xm1tKk1LXQ5dm5SnwCwiihMcubJdQl1YP1WCN8PIAMeIbGT89uk0r1yJaB7+92obHFNCV63EspAOZ1pgxyohUEwAnrkfRZNywKv7s1/clnhPqyt71A2atXjUymabTpQpoy2CVZZOtBnhBpYjEoXwQoydE0uLwLWVXHkivDcJPQOxqI5xOfZ4KS/rE3WVaO4sLlC2zoi3SU65DJ0za5Dv14winJpEsu2wU+cIXIdcyDlctrcl9s2usnjFbUWepiWR8dRr2zualWc0Qgsi9q/zrKFt7jcGuN1UJwRbAm/8i3VevrE8B83giCMt0nbHs3ReFOhl+p6xoLmm1JMok4/PwnltinGHpTZKiwCbPrznLW7h+d49BO/ZwxbUNoJVdEH+nzb+tTbeaI8qESfJZcw0/qQPkJRkuSg7N1JRFD8JBP+JHALoXzmk/U/lEPzp2et/2zfskT/JIcB++6C/UcDPL4swv5Hsm0xAkW9Wwf++yQT/8a37I5ng8w9b92c/L/LflkrwJxv3vzqV4J/Q/0AqAfin4oz+N4kz/P/jVAKL/psfjMiR4+tZgXCj/ZpUVfcFSNcNjkKaxMGHcfguPin/YUXXsaavzFyPc0FXeyHGlK0NFRUXq0B7BRBa9uxTF2lVqgAPQPBQEuImroX1GmMmAQAwOo20ca19zDb82FAIyuE8z04UNl5C1uzQjKInfu/f1IG+XQYj94GYBieStH09DZChbIp8y5lkAVGgiZCkTFLUwlwFaSqkMLBvFisVkOwVp8nOTNUz79GWNSuqEY5k3x8igHPGY02XtNk0rh3llk2KJLaGnxFkIFkSA7rK7VyCIiXJ0OASfJMogZni35D5/hmQepi0PoL89CQ+HPthjteDrhYLdj/shpbvr4hR9YC9X5joxinMY6+egilkTGo2wU033hNINKpvHMP0Xv0yg7WnZDU67zrFLSiHjR1EEaSqzXiIpCRnR5TUYD5DTlGRSB0W6vvFM05kv1oproNLqXx3+PQhvt9v5AIpskiRcRc2oFXuqtcEMysWWWO47frxyvoISJkhNdrSddKwIG3Rx012RhNS5dX3NEAAkscjf4jby/zxrPkYDnDrOriVrk9StplsbUvmfShV9Tmqk8+I58Ba1qp09JqcOTmQSDIRZlgCN38z/E9QEvSNOikuOhTIgzewtkzeUp+iaR2qKlanXQ45fb1dCvvzzIgzNE91kIgjyYJP7UvNYTCfNkZkKKFWXOhTBnH3ri8hLV/+rOJ+nN4DGD/nsdlD5EJy3H0XTiLVeAt4XvRoj9y2DEILfZbUflk0CyqDgnOlTrYaZcm5h7s3o3LK4QkYDHq9WgrE95xjUNyeHSHdgkJS9TAYP4uwY2TlyPee5+o8FpvKkYLE7YCV4Ui30s+5CD+6Jw6gGTDUELSnTkZa2aFDztpVe9c+p0/BTYQajQzyQp68nNznDgQuxZZgh6wawD6kDygOquliMCUwek9WEjOgghXfIGL4xtKpbeDHRH+Taa9QX8BcowLtsjOBMelGcJLVr03vfgDaGgCyJKt404sZQuVO6Ga3G+umsmFQgpTr5zCHCQujuLGgI9ROq43vJDlXtjKh7VKEYDhRwseBNiM6Xy+cTab810aiRexXOKZPFqFUh72WXuqi7ru1ifXcd+IEin1NQzQePziJgsGOnPEYDVESZ10vfiYDeCup6uXMKfIqOBZGHQX/DQE6hJH3OPWINfFSax7P9vd8CjPOLhkxhPDUvJxPNHH87aMmHxvbCiDKYZY9YnpnvPX9gu95vQGrEAW+qzKYvj4/Qy5mGCwU9UKw0OC9QqbNN+iMH17Btuz7yw/67HkFz5joSYT0NU6wTJ9cSBWAnn54clES9SoecSNpWvDHhhhOmd5T9DTxp72zkCqqBymWu4rVwdoXkxeasz/moETU27pKHjFppPRVmjTEnEtIAu9XBeAew1x9JrjXc8FZZkKDIoLHAVRn3nF8ZijMcLyi++Kr5UGeRpjA881QWN2N7rx49fukbS80EoI8SuWLKnkJa9cvorIOHEURL+IxQDNHRBFyQoUMdXg+txZkUSpd0Nng4ZVznbkuNX89zJa4z+UG7O/3hec+4JZSnXkiHQVnxgE+2HoggYqsLKpoj2KQGxR9aRZilfPp9xSI0R1aiZSqsGNGKOqSjqkk9BuVvEiZvkpucSYBnb0rlqrZA2c9uCPaMjYsB82Si6xwVBGet4DuDTMG10R1poAXwD76hJgWJLdq8e5ISkNBvuhRTWEOBdx9fuW5tCgLhA1NRZaNyCFer6hdup1P2m8mAGcCT6eXNp6PHeGKidBFvNYveGG60qZt5vN9HdO91cZc43rEPLiEegQMvDwp7LHSzUuHtqnHAKUW4KVKZI69LFEa6QCQtifs8KYos/MxmdezR+Gnn4rbdWU5JsF+qjBtxadDaTrAAvKZuxAT4NRqO7QBr7Fbw+9AHyiKLE8h6vAykzgFNUV5oW/hRDnW/d75AMM4Ypuy7aJNi5Vn9OsZMaU1InvBTA7T75tWgHqkLyqj2HxljnSKN5wkLN/zghUM71VGZAO3eAmT89pPJydMYlo90qTs6VWacNcbmZToLjtqlPN0snMyzg1sveWUtiItMh0KvoXDIZW+7+H3djyWCm3n0aVsZovL6s5+vpNugv2YPajYnRh4CwdTBX2+AILJdUZfyeLsK6jYk3yLESj9dY+3qTSjKAp9h5ZkC/QeMjjs6SDkKfcEraHug9AtqPUM8qXzyBIfHIRX0ZCuYSHzqEUCK2O1dmWpSvZMlX8Vixy/XBrdYBGy5GiAH+sIFNmg1jGzuffMVzicu3PJikQtUVP000aUo6KJI2YOm1BGciXpoRBAKJwGTZnrN4znNRRCyi7Hlnz1mI188edgYsENSrTWcZuBHGmzZOMop9KbGW4ifbeG320AYQrJXGRV8iaCZPxtUa5+ihFbHkmUer+2gPSe3TrvVwOYSt/BstpVjyYtRiAr7T2uN8d0imwsrIvXxE8wAfwE/B6lmViqVVqZJsF9xl+8bPGSwK1WX9xx4FjJnRdmr/Kq0sX3N2WCby9vb9iroZbuHBWX9rdNefYqMlkLxbfpDraULOuakxuNO+kyK+LH7mrFwZMv7vjw3CQk++x3/Al10Cb91+5OnjIRlSV9t+jqzet8rCW/+MzkDC9RUb+GMFQVdq+Avdki03DXdcStSjkIqt8rjoZI9p5/ol600nZY8GYzWGwnHnIsA3CNXGrANCGj3riI7hhpUxoe3kL+ffiujKnvz049FnU1RZObzQfB3u8mKLuf95AtSeoqP6RplcsocVHwPSOaQNz6JF5hkrHXM9CYbIk3z4zb2Z2mP8QOeTrYcHCQUxwSuocpuomPUnORyWxIA7vIaW8HRCnuw8L6kIjAJQygitKYBHnZbBw8CHQoxxeWTp0Jv0wcEuwjZbmW6JvbY37foJ8ZQ1hBRhSTumQM3ofAfWDFugUPXlOZZM5NaXX7Bbv31FQUViuGwd4KAlDHZlKlG3IpQymaGjI9UZ8oFLcsCt0sa7vS/IZk5dVHLG/xagmNH62dpUE25bMhVlIsuAJ8bFp45XbJmvQZVwO5RIpkXioZrp5L8mLbIBlPqvY0mczDV0KjNmnR81SqkjxVKAeKlbfuYRRz5xwAWVG+Y7rF/+nrX+krBihUyNhMtjzboS93opSCLcYeEvLSF9vbQPn/2d6VNTlqJOFf41cHiPuR+xIgQJxv3Ie4BRLw65dS93g8M23vRHi8a+86ol9oJSBlVX2ZWZmVn48+XuW40BHjCgy2j5mZHt6pekK73nUcu3xrssewnanwrMSPh+Vd4A6hOT2XeB+UTY1gbQKgOE30Yu7WxWbkvmNnYnm3f7ZjdyEFcYdNEQXez7dl8JmNeyiqMlWVqSInlL098j5Ir7Z3Iw9cV/xbDF83S7whbxvl+aRc8SQUeDSd9ZctmifOl3VQIzuuwT07P5c3ybc/4MOFUpYcNkmVPedVnupoVda1VmhIRP50ycczHii/v5oWc5dHtQa4b6etT0ZWKimZ/5wupLOxaFhmyuQtM4cFulDl+RkRQvp6resy8h8I0YHElYYt4IsaAI+0i4E5lkUpfkbDZ3GT0TK8Pqu1Dpvc5SxQy8RcMFYuTPSmtHlNWpcYLmqEOvySXEzijQiYbRQYesDihqcMr7KTS36GWJ4XKfnwma7giCsDw8tyuCMtWguVSw9yZnlYi9WZd/M3H2BN+WSM7k19AhKe3GY3OjeqYuQINZ6ZlQpUmNh3SWGBw+Rqu7ZjJYNmzE1IOfcI6niMmsuaW2+plMPPbLYCxwsubd5tqgzMvdcAn5RfqzzFH5Zx4HQZuaWQ1rrumZr8BLlXwTs1Gw2awHhFTOqS1HW31egGHFQK+UJ890x+35zbsGtxVE68igtjOoNOW2CHfmwark2zvq39+fRopGlPuCo51yAAiQOusAX1XnsQo9A7J2xVc27P7R0d3JznHrEzDilm6kdMsx6mU6PH2Z92rFqD3oVPotyKCDHToqnRux/hiN4mCnOKpnMoIToywcQFqR+PHCR6FMzwB1+aC21vxCAu3QOx2Q3ZiWkNb09eAa2HWFKQ924RR+fwPnaetuN+K8EcLI12LZiqPL4hqZvOrQETkUNyt2NjPrblwrmPr/fjqaVMtVVh5eI6gy7kqNW3r2CeVpqzd5swv5I3a9V8BLkG0az3yxYw8uiSbG3EEN7A082qsXq2NRFb5ySsEuuuVHwD5dJO3nXJckwDnOh353Cwy9lT7wPQc3Z4QsbkqzcPJSqv2OrFmKQEJNEDvRbpWvVTUEnme+sRFwbCLbYgVvd9fSl4yxzXBitIG7qKhi5NQcHxmzaBNNGrYTOIhRAWWielFIGSZJ922UowglPAbVXrTfjhefoBLcueab4a9djeKyfiGOH0KV6n9jl5bgMUPdmQQSesIxf6Ph8wChLC+ljEt1Y0DNFdsGRx/MqoZ67yqIvp18qT8DfGxtOADfD5nCcknHbr5o2ecqnVO2/R9cp7wWlfTXRVBJK96herEaGTK5GIKMRsUiZzDr/9mGN2NbQk5MbDHR3SBTGKf79oHWKh8wXfae9yTKfTFaIuVw2C74F6Z1pLgzhNg/F8v8FspBKs3DUonaTUuloortXSllUCMctdcoUOZ9PY74RhPQ1y3x9b40Y1+pgOV2c3jU7rdd6VRT7Ib8IEVhXmGwQ3vmJIy5gO1fC4di6e0fYMACL6nRw61xumjMt043NaaFg6JaXac5i3uM/HX4cDhg2jcakPm0nzTwsFYasBX/1ehcdrA2XmIRGdUaMt7uy9inRv9oi03TGkP3AUsa6xDRwsAmMQqg5OxtWBdZA6liKkvKg7UmjPAT/myd0sjlk+XPhj+UsZvqxHqF/NcSA6XFvEqtHaFHCJdbeBQgwEq1nCKJcTTPFBlY2TK8+tLtZ5v8EE2GUt8ZQmbeeMO2QrF5oiRiK62NUslnXzuFzovnLIqeFD5XUCVTs36BKTFBmAVl3d2SOxMlaeYjgShH6E7jGwJfAKWq46yFaCQpga1F1CQnQiDtdZq9JcNDN9A3Zzt6RNbTLEjdMGolO6qvrnTlB6upsLExx43vpazONdUkNWfqj/tMe9h+MZlqOphTGCIE5cL/GlfIYzAgK7b9D+QFRsWHIsPcXZOFyjlrWUwc8hLGnhK0PS996OrhYJ7UXKsxdhCI9Vs8vQeqYCDrdgqvOfDF8wYdgkcJoyPc0I7EPxwt02rlaJ0GjiFRNeLLRm2U8zMMSFWg3tsYYz051StNlNRrYKT+DF2IyMoEPXJj2duAWJ2MdoFLQiZ+whe0SLlAKWqDpLnLmoR+AJZh9jUAvR0TdaVdmqyaUAfysLkAIk48BZgEM8y9UnPYwLuO6KOlzqe7Ts516lhowWFOfpg73X2CqgaIQfMivv+/aqBnnQdujN0OGTVj0H6xJnjVOctfpGpPjI3GJIO0xiNscpW9xTg3uM55mxDy17+9IzIvcoFdAdQ+V1/liKHmcd8y5WhiowdfrMmyNEXteXXK3GZURhNhxftXkjzcwgKqOQbSJp9iMOLzZavgAs4y0k8aYIXvJ1ZaJlpY/l+/DrAdQx9oRxSwb98H3mI7pfd9c0SG9NaLnS7R6boT5dRag2bF7A0PKw2sX8ICE1lLa2icvCAOYukECXkAJ1aZY2GBMBvkQNneK6ZcKaqKtJe/D3Yb7qyq2BsYCUHx3YoVWohTRGmS7Ym4zCYpvfHJrlsFTWfWs1aZmBB12btOFJ0z94V3oQxJ5cSbZgzfw8MeOmgQCcSYunlWwW6IKSx88/mqREfyP985tJSuTbQ7MfZ3XgPytJSXyQpHxLSaIvHgQUECL87+Ymv3/EPuUm4Q8OOuPEfzA7SXwzHFlaZJ9+eD/NZV/0XdTwn//7leI+y5x7kE17jWCdzfP2rs5omfsvx/c3U533fpmS7N+nDedoKrL5d+Tenwd+y++Ow5Q10Vw9vmTt+eFK/nbO/6WVDH+nkk9/KSVT/w0lH7qdNh/cfyzk98vg159x6/vD366296s/fwX88MF5v/XSV938qy4FXxQWEF/j1NvXfL/n8wDT0xRtvxIbgMD9e9+Cf01Z9bX01xQzX8qDTerX+z/PtV/08Qd4QT5qjvAPJdY/lFh/e0qs088/fSLA+oLy6kOmK/onEgW0R7TwE8X9Qpj1JafWF9xX7w/+f6G/wr+jEgv/wGf/EexXH+PWR5VY/+DWP7j1t8ct5OcXXh2w8yqP/QZ5jk//n4j3kK+h5yPWsB8EPWAj7BeG0jcP6zPRK8L/Cw==
\ No newline at end of file
diff --git "a/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/write-through.png" "b/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/write-through.png"
new file mode 100644
index 00000000000..ecdbd6d2c2e
Binary files /dev/null and "b/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/write-through.png" differ
diff --git "a/docs/database/Redis/redis\351\233\206\347\276\244\344\273\245\345\217\212\345\272\224\347\224\250\345\234\272\346\231\257.md" "b/docs/database/Redis/redis\351\233\206\347\276\244\344\273\245\345\217\212\345\272\224\347\224\250\345\234\272\346\231\257.md"
deleted file mode 100644
index c858248ce33..00000000000
--- "a/docs/database/Redis/redis\351\233\206\347\276\244\344\273\245\345\217\212\345\272\224\347\224\250\345\234\272\346\231\257.md"
+++ /dev/null
@@ -1,149 +0,0 @@
-相关阅读:
-
-- [史上最全Redis高可用技术解决方案大全](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484850&idx=1&sn=3238360bfa8105cf758dcf7354af2814&chksm=cea24a79f9d5c36fb2399aafa91d7fb2699b5006d8d037fe8aaf2e5577ff20ae322868b04a87&token=1082669959&lang=zh_CN&scene=21#wechat_redirect)
-
-# Redis 集群以及应用
-
-## 集群
-### 主从复制
-#### 主从链(拓扑结构)
-
-
-
-
-#### 复制模式
-- 全量复制:master 全部同步到 slave
-- 部分复制:slave 数据丢失进行备份
-
-#### 问题点
-- 同步故障
- - 复制数据延迟(不一致)
- - 读取过期数据(Slave 不能删除数据)
- - 从节点故障
- - 主节点故障
-- 配置不一致
- - maxmemory 不一致:丢失数据
- - 优化参数不一致:内存不一致.
-- 避免全量复制
- - 选择小主节点(分片)、低峰期间操作.
- - 如果节点运行 id 不匹配(如主节点重启、运行 id 发送变化),此时要执行全量复制,应该配合哨兵和集群解决.
- - 主从复制挤压缓冲区不足产生的问题(网络中断,部分复制无法满足),可增大复制缓冲区( rel_backlog_size 参数).
-- 复制风暴
-
-### 哨兵机制
-#### 拓扑图
-
-
-#### 节点下线
-- 客观下线
- - 所有 Sentinel 节点对 Redis 节点失败要达成共识,即超过 quorum 个统一.
-- 主观下线
- - 即 Sentinel 节点对 Redis 节点失败的偏见,超出超时时间认为 Master 已经宕机.
-#### leader选举
-- 选举出一个 Sentinel 作为 Leader:集群中至少有三个 Sentinel 节点,但只有其中一个节点可完成故障转移.通过以下命令可以进行失败判定或领导者选举.
-- 选举流程
- 1. 每个主观下线的 Sentinel 节点向其他 Sentinel 节点发送命令,要求设置它为领导者.
- 1. 收到命令的 Sentinel 节点如果没有同意通过其他 Sentinel 节点发送的命令,则同意该请求,否则拒绝.
- 1. 如果该 Sentinel 节点发现自己的票数已经超过 Sentinel 集合半数且超过 quorum,则它成为领导者.
- 1. 如果此过程有多个 Sentinel 节点成为领导者,则等待一段时间再重新进行选举.
-#### 故障转移
-- 转移流程
- 1. Sentinel 选出一个合适的 Slave 作为新的 Master(slaveof no one 命令).
- 1. 向其余 Slave 发出通知,让它们成为新 Master 的 Slave( parallel-syncs 参数).
- 1. 等待旧 Master 复活,并使之称为新 Master 的 Slave.
- 1. 向客户端通知 Master 变化.
-- 从 Slave 中选择新 Master 节点的规则(slave 升级成 master 之后)
- 1. 选择 slave-priority 最高的节点.
- 1. 选择复制偏移量最大的节点(同步数据最多).
- 1. 选择 runId 最小的节点.
-#### 读写分离
-#### 定时任务
-- 每 1s 每个 Sentinel 对其他 Sentinel 和 Redis 执行 ping,进行心跳检测.
-- 每 2s 每个 Sentinel 通过 Master 的 Channel 交换信息(pub - sub).
-- 每 10s 每个 Sentinel 对 Master 和 Slave 执行 info,目的是发现 Slave 节点、确定主从关系.
-
-### 分布式集群(Cluster)
-#### 拓扑图
-
-
-
-#### 通讯
-##### 集中式
-> 将集群元数据(节点信息、故障等等)几种存储在某个节点上.
-- 优势
- 1. 元数据的更新读取具有很强的时效性,元数据修改立即更新
-- 劣势
- 1. 数据集中存储
-##### Gossip
-
-
-- [Gossip 协议](https://www.jianshu.com/p/8279d6fd65bb)
-
-#### 寻址分片
-##### hash取模
-- hash(key)%机器数量
-- 问题
- 1. 机器宕机,造成数据丢失,数据读取失败
- 1. 伸缩性
-##### 一致性hash
-- 
-
-- 问题
- 1. 一致性哈希算法在节点太少时,容易因为节点分布不均匀而造成缓存热点的问题。
- - 解决方案
- - 可以通过引入虚拟节点机制解决:即对每一个节点计算多个 hash,每个计算结果位置都放置一个虚拟节点。这样就实现了数据的均匀分布,负载均衡。
-##### hash槽
-- CRC16(key)%16384
--
-
-
-
-
-
-
-
-## 使用场景
-### 热点数据
-### 会话维持 session
-### 分布式锁 SETNX
-### 表缓存
-### 消息队列 list
-### 计数器 string
-
-
-
-
-
-## 缓存设计
-### 更新策略
-- LRU、LFU、FIFO 算法自动清除:一致性最差,维护成本低.
-- 超时自动清除(key expire):一致性较差,维护成本低.
-- 主动更新:代码层面控制生命周期,一致性最好,维护成本高.
-### 更新一致性
-- 读请求:先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应.
-- 写请求:先删除缓存,然后再更新数据库(避免大量地写、却又不经常读的数据导致缓存频繁更新).
-### 缓存粒度
-- 通用性:全量属性更好.
-- 占用空间:部分属性更好.
-- 代码维护成本.
-
-### 缓存穿透
-> 当大量的请求无命中缓存、直接请求到后端数据库(业务代码的 bug、或恶意攻击),同时后端数据库也没有查询到相应的记录、无法添加缓存.
- 这种状态会一直维持,流量一直打到存储层上,无法利用缓存、还会给存储层带来巨大压力.
->
-#### 解决方案
-1. 请求无法命中缓存、同时数据库记录为空时在缓存添加该 key 的空对象(设置过期时间),缺点是可能会在缓存中添加大量的空值键(比如遭到恶意攻击或爬虫),而且缓存层和存储层数据短期内不一致;
-1. 使用布隆过滤器在缓存层前拦截非法请求、自动为空值添加黑名单(同时可能要为误判的记录添加白名单).但需要考虑布隆过滤器的维护(离线生成/ 实时生成).
-### 缓存雪崩
-> 缓存崩溃时请求会直接落到数据库上,很可能由于无法承受大量的并发请求而崩溃,此时如果只重启数据库,或因为缓存重启后没有数据,新的流量进来很快又会把数据库击倒
->
-#### 出现后应对
-- 事前:Redis 高可用,主从 + 哨兵,Redis Cluster,避免全盘崩溃.
-- 事中:本地 ehcache 缓存 + hystrix 限流 & 降级,避免数据库承受太多压力.
-- 事后:Redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据.
-#### 请求过程
-1. 用户请求先访问本地缓存,无命中后再访问 Redis,如果本地缓存和 Redis 都没有再查数据库,并把数据添加到本地缓存和 Redis;
-1. 由于设置了限流,一段时间范围内超出的请求走降级处理(返回默认值,或给出友情提示).
-
-
-
diff --git "a/docs/database/Redis/\345\246\202\344\275\225\345\201\232\345\217\257\351\235\240\347\232\204\345\210\206\345\270\203\345\274\217\351\224\201\357\274\214Redlock\347\234\237\347\232\204\345\217\257\350\241\214\344\271\210.md" "b/docs/database/Redis/\345\246\202\344\275\225\345\201\232\345\217\257\351\235\240\347\232\204\345\210\206\345\270\203\345\274\217\351\224\201\357\274\214Redlock\347\234\237\347\232\204\345\217\257\350\241\214\344\271\210.md"
deleted file mode 100644
index 043df96566d..00000000000
--- "a/docs/database/Redis/\345\246\202\344\275\225\345\201\232\345\217\257\351\235\240\347\232\204\345\210\206\345\270\203\345\274\217\351\224\201\357\274\214Redlock\347\234\237\347\232\204\345\217\257\350\241\214\344\271\210.md"
+++ /dev/null
@@ -1,91 +0,0 @@
-本文是对 [Martin Kleppmann](https://martin.kleppmann.com/) 的文章 [How to do distributed locking](https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html) 部分内容的翻译和总结,上次写 Redlock 的原因就是看到了 Martin 的这篇文章,写得很好,特此翻译和总结。感兴趣的同学可以翻看原文,相信会收获良多。
-
-开篇作者认为现在 Redis 逐渐被使用到数据管理领域,这个领域需要更强的数据一致性和耐久性,这使得他感到担心,因为这不是 Redis 最初设计的初衷(事实上这也是很多业界程序员的误区,越来越把 Redis 当成数据库在使用),其中基于 Redis 的分布式锁就是令人担心的其一。
-
-Martin 指出首先你要明确你为什么使用分布式锁,为了性能还是正确性?为了帮你区分这二者,在这把锁 fail 了的时候你可以询问自己以下问题:
-1. **要性能的:** 拥有这把锁使得你不会重复劳动(例如一个 job 做了两次),如果这把锁 fail 了,两个节点同时做了这个 Job,那么这个 Job 增加了你的成本。
-2. **要正确性的:** 拥有锁可以防止并发操作污染你的系统或者数据,如果这把锁 fail 了两个节点同时操作了一份数据,结果可能是数据不一致、数据丢失、file 冲突等,会导致严重的后果。
-
-上述二者都是需求锁的正确场景,但是你必须清楚自己是因为什么原因需要分布式锁。
-
-如果你只是为了性能,那没必要用 Redlock,它成本高且复杂,你只用一个 Redis 实例也够了,最多加个从防止主挂了。当然,你使用单节点的 Redis 那么断电或者一些情况下,你会丢失锁,但是你的目的只是加速性能且断电这种事情不会经常发生,这并不是什么大问题。并且如果你使用了单节点 Redis,那么很显然你这个应用需要的锁粒度是很模糊粗糙的,也不会是什么重要的服务。
-
-那么是否 Redlock 对于要求正确性的场景就合适呢?Martin 列举了若干场景证明 Redlock 这种算法是不可靠的。
-
-## 用锁保护资源
-这节里 Martin 先将 Redlock 放在了一边而是仅讨论总体上一个分布式锁是怎么工作的。在分布式环境下,锁比 mutex 这类复杂,因为涉及到不同节点、网络通信并且他们随时可能无征兆的 fail 。
-Martin 假设了一个场景,一个 client 要修改一个文件,它先申请得到锁,然后修改文件写回,放锁。另一个 client 再申请锁 ... 代码流程如下:
-
-```java
-// THIS CODE IS BROKEN
-function writeData(filename, data) {
- var lock = lockService.acquireLock(filename);
- if (!lock) {
- throw 'Failed to acquire lock';
- }
-
- try {
- var file = storage.readFile(filename);
- var updated = updateContents(file, data);
- storage.writeFile(filename, updated);
- } finally {
- lock.release();
- }
-}
-```
-
-可惜即使你的锁服务非常完美,上述代码还是可能跪,下面的流程图会告诉你为什么:
-
-
-
-上述图中,得到锁的 client1 在持有锁的期间 pause 了一段时间,例如 GC 停顿。锁有过期时间(一般叫租约,为了防止某个 client 崩溃之后一直占有锁),但是如果 GC 停顿太长超过了锁租约时间,此时锁已经被另一个 client2 所得到,原先的 client1 还没有感知到锁过期,那么奇怪的结果就会发生,曾经 HBase 就发生过这种 Bug。即使你在 client1 写回之前检查一下锁是否过期也无助于解决这个问题,因为 GC 可能在任何时候发生,即使是你非常不便的时候(在最后的检查与写操作期间)。
-如果你认为自己的程序不会有长时间的 GC 停顿,还有其他原因会导致你的进程 pause。例如进程可能读取尚未进入内存的数据,所以它得到一个 page fault 并且等待 page 被加载进缓存;还有可能你依赖于网络服务;或者其他进程占用 CPU;或者其他人意外发生 SIGSTOP 等。
-
-... .... 这里 Martin 又增加了一节列举各种进程 pause 的例子,为了证明上面的代码是不安全的,无论你的锁服务多完美。
-
-## 使用 Fencing (栅栏)使得锁变安全
-修复问题的方法也很简单:你需要在每次写操作时加入一个 fencing token。这个场景下,fencing token 可以是一个递增的数字(lock service 可以做到),每次有 client 申请锁就递增一次:
-
-
-
-client1 申请锁同时拿到 token33,然后它进入长时间的停顿锁也过期了。client2 得到锁和 token34 写入数据,紧接着 client1 活过来之后尝试写入数据,自身 token33 比 34 小因此写入操作被拒绝。注意这需要存储层来检查 token,但这并不难实现。如果你使用 Zookeeper 作为 lock service 的话那么你可以使用 zxid 作为递增数字。
-但是对于 Redlock 你要知道,没什么生成 fencing token 的方式,并且怎么修改 Redlock 算法使其能产生 fencing token 呢?好像并不那么显而易见。因为产生 token 需要单调递增,除非在单节点 Redis 上完成但是这又没有高可靠性,你好像需要引进一致性协议来让 Redlock 产生可靠的 fencing token。
-
-## 使用时间来解决一致性
-Redlock 无法产生 fencing token 早该成为在需求正确性的场景下弃用它的理由,但还有一些值得讨论的地方。
-
-学术界有个说法,算法对时间不做假设:因为进程可能pause一段时间、数据包可能因为网络延迟延后到达、时钟可能根本就是错的。而可靠的算法依旧要在上述假设下做正确的事情。
-
-对于 failure detector 来说,timeout 只能作为猜测某个节点 fail 的依据,因为网络延迟、本地时钟不正确等其他原因的限制。考虑到 Redis 使用 gettimeofday,而不是单调的时钟,会受到系统时间的影响,可能会突然前进或者后退一段时间,这会导致一个 key 更快或更慢地过期。
-
-可见,Redlock 依赖于许多时间假设,它假设所有 Redis 节点都能对同一个 Key 在其过期前持有差不多的时间、跟过期时间相比网络延迟很小、跟过期时间相比进程 pause 很短。
-
-## 用不可靠的时间打破 Redlock
-这节 Martin 举了个因为时间问题,Redlock 不可靠的例子。
-
-1. client1 从 ABC 三个节点处申请到锁,DE由于网络原因请求没有到达
-2. C节点的时钟往前推了,导致 lock 过期
-3. client2 在CDE处获得了锁,AB由于网络原因请求未到达
-4. 此时 client1 和 client2 都获得了锁
-
-**在 Redlock 官方文档中也提到了这个情况,不过是C崩溃的时候,Redlock 官方本身也是知道 Redlock 算法不是完全可靠的,官方为了解决这种问题建议使用延时启动,相关内容可以看之前的[这篇文章](https://zhuanlan.zhihu.com/p/40915772)。但是 Martin 这里分析得更加全面,指出延时启动不也是依赖于时钟的正确性的么?**
-
-接下来 Martin 又列举了进程 Pause 时而不是时钟不可靠时会发生的问题:
-
-1. client1 从 ABCDE 处获得了锁
-2. 当获得锁的 response 还没到达 client1 时 client1 进入 GC 停顿
-3. 停顿期间锁已经过期了
-4. client2 在 ABCDE 处获得了锁
-5. client1 GC 完成收到了获得锁的 response,此时两个 client 又拿到了同一把锁
-
-**同时长时间的网络延迟也有可能导致同样的问题。**
-
-## Redlock 的同步性假设
-这些例子说明了,仅有在你假设了一个同步性系统模型的基础上,Redlock 才能正常工作,也就是系统能满足以下属性:
-
-1. 网络延时边界,即假设数据包一定能在某个最大延时之内到达
-2. 进程停顿边界,即进程停顿一定在某个最大时间之内
-3. 时钟错误边界,即不会从一个坏的 NTP 服务器处取得时间
-
-## 结论
-Martin 认为 Redlock 实在不是一个好的选择,对于需求性能的分布式锁应用它太重了且成本高;对于需求正确性的应用来说它不够安全。因为它对高危的时钟或者说其他上述列举的情况进行了不可靠的假设,如果你的应用只需要高性能的分布式锁不要求多高的正确性,那么单节点 Redis 够了;如果你的应用想要保住正确性,那么不建议 Redlock,建议使用一个合适的一致性协调系统,例如 Zookeeper,且保证存在 fencing token。
diff --git "a/docs/database/\344\270\200\345\215\203\350\241\214MySQL\345\221\275\344\273\244.md" b/docs/database/mysql/a-thousand-lines-of-mysql-study-notes.md
similarity index 98%
rename from "docs/database/\344\270\200\345\215\203\350\241\214MySQL\345\221\275\344\273\244.md"
rename to docs/database/mysql/a-thousand-lines-of-mysql-study-notes.md
index 1ddc9ade2c6..53350ec5ae7 100644
--- "a/docs/database/\344\270\200\345\215\203\350\241\214MySQL\345\221\275\344\273\244.md"
+++ b/docs/database/mysql/a-thousand-lines-of-mysql-study-notes.md
@@ -1,35 +1,15 @@
+---
+title: 一千行 MySQL 学习笔记
+category: 数据库
+tag:
+ - MySQL
+---
+
> 原文地址:https://shockerli.net/post/1000-line-mysql-note/ ,JavaGuide 对本文进行了简答排版,新增了目录。
> 作者:格物
非常不错的总结,强烈建议保存下来,需要的时候看一看。
-
-- [基本操作](#基本操作)
-- [数据库操作](#数据库操作)
-- [表的操作](#表的操作)
-- [数据操作](#数据操作)
-- [字符集编码](#字符集编码)
-- [数据类型(列类型)](#数据类型列类型)
-- [列属性(列约束)](#列属性列约束)
-- [建表规范](#建表规范)
-- [SELECT](#select)
-- [UNION](#union)
-- [子查询](#子查询)
-- [连接查询(join)](#连接查询join)
-- [TRUNCATE](#truncate)
-- [备份与还原](#备份与还原)
-- [视图](#视图)
-- [事务(transaction)](#事务transaction)
-- [锁表](#锁表)
-- [触发器](#触发器)
-- [SQL编程](#sql编程)
-- [存储过程](#存储过程)
-- [用户和权限管理](#用户和权限管理)
-- [表维护](#表维护)
-- [杂项](#杂项)
-
-
-
### 基本操作
```mysql
@@ -242,7 +222,7 @@ SET NAMES GBK; -- 相当于完成以上三个设置
utf8 最大为21844个字符,gbk 最大为32766个字符,latin1 最大为65532个字符
varchar 是变长的,需要利用存储空间保存 varchar 的长度,如果数据小于255个字节,则采用一个字节来保存长度,反之需要两个字节来保存。
varchar 的最大有效长度由最大行大小和使用的字符集确定。
- 最大有效长度是65532字节,因为在varchar存字符串时,第一个字节是空的,不存在任何数据,然后还需两个字节来存放字符串的长度,所以有效长度是64432-1-2=65532字节。
+ 最大有效长度是65532字节,因为在varchar存字符串时,第一个字节是空的,不存在任何数据,然后还需两个字节来存放字符串的长度,所以有效长度是65535-1-2=65532字节。
例:若一个表定义为 CREATE TABLE tb(c1 int, c2 char(30), c3 varchar(N)) charset=utf8; 问N的最大值是多少? 答:(65535-1-2-4-30*3)/3
-- b. blob, text ----------
blob 二进制字符串(字节字符串)
@@ -609,7 +589,7 @@ CREATE [OR REPLACE] [ALGORITHM = {UNDEFINED | MERGE | TEMPTABLE}] VIEW view_name
- 事务开始和结束时,外部数据一致
- 在整个事务过程中,操作是连续的
3. 隔离性(Isolation)
- 多个用户并发访问数据库时,一个用户的事务不能被其它用户的事物所干扰,多个并发事务之间的数据要相互隔离。
+ 多个用户并发访问数据库时,一个用户的事务不能被其它用户的事务所干扰,多个并发事务之间的数据要相互隔离。
4. 持久性(Durability)
一个事务一旦被提交,它对数据库中的数据改变就是永久性的。
-- 事务的实现
diff --git "a/docs/database/\344\270\200\346\235\241sql\350\257\255\345\217\245\345\234\250mysql\344\270\255\345\246\202\344\275\225\346\211\247\350\241\214\347\232\204.md" b/docs/database/mysql/how-sql-executed-in-mysql.md
similarity index 77%
rename from "docs/database/\344\270\200\346\235\241sql\350\257\255\345\217\245\345\234\250mysql\344\270\255\345\246\202\344\275\225\346\211\247\350\241\214\347\232\204.md"
rename to docs/database/mysql/how-sql-executed-in-mysql.md
index 261a0c0b975..07404d0e930 100644
--- "a/docs/database/\344\270\200\346\235\241sql\350\257\255\345\217\245\345\234\250mysql\344\270\255\345\246\202\344\275\225\346\211\247\350\241\214\347\232\204.md"
+++ b/docs/database/mysql/how-sql-executed-in-mysql.md
@@ -1,26 +1,15 @@
-本文来自[木木匠](https://github.com/kinglaw1204)投稿。
-
-
+---
+title: 一条 SQL 语句在 MySQL 中如何被执行的?
+category: 数据库
+tag:
+ - MySQL
+---
-- [一 MySQL 基础架构分析](#一-mysql-基础架构分析)
- - [1.1 MySQL 基本架构概览](#11-mysql-基本架构概览)
- - [1.2 Server 层基本组件介绍](#12-server-层基本组件介绍)
- - [1) 连接器](#1-连接器)
- - [2) 查询缓存(MySQL 8.0 版本后移除)](#2-查询缓存mysql-80-版本后移除)
- - [3) 分析器](#3-分析器)
- - [4) 优化器](#4-优化器)
- - [5) 执行器](#5-执行器)
-- [二 语句分析](#二-语句分析)
- - [2.1 查询语句](#21-查询语句)
- - [2.2 更新语句](#22-更新语句)
-- [三 总结](#三-总结)
-- [四 参考](#四-参考)
-
-
+本文来自[木木匠](https://github.com/kinglaw1204)投稿。
本篇文章会分析下一个 sql 语句在 MySQL 中的执行流程,包括 sql 的查询在 MySQL 内部会怎么流转,sql 语句的更新是怎么完成的。
-在分析之前我会先带着你看看 MySQL 的基础架构,知道了 MySQL 由那些组件组成已经这些组件的作用是什么,可以帮助我们理解和解决这些问题。
+在分析之前我会先带着你看看 MySQL 的基础架构,知道了 MySQL 由那些组件组成以及这些组件的作用是什么,可以帮助我们理解和解决这些问题。
## 一 MySQL 基础架构分析
@@ -31,12 +20,12 @@
先简单介绍一下下图涉及的一些组件的基本作用帮助大家理解这幅图,在 1.2 节中会详细介绍到这些组件的作用。
- **连接器:** 身份认证和权限相关(登录 MySQL 的时候)。
-- **查询缓存:** 执行查询语句的时候,会先查询缓存(MySQL 8.0 版本后移除,因为这个功能不太实用)。
-- **分析器:** 没有命中缓存的话,SQL 语句就会经过分析器,分析器说白了就是要先看你的 SQL 语句要干嘛,再检查你的 SQL 语句语法是否正确。
-- **优化器:** 按照 MySQL 认为最优的方案去执行。
-- **执行器:** 执行语句,然后从存储引擎返回数据。
+- **查询缓存:** 执行查询语句的时候,会先查询缓存(MySQL 8.0 版本后移除,因为这个功能不太实用)。
+- **分析器:** 没有命中缓存的话,SQL 语句就会经过分析器,分析器说白了就是要先看你的 SQL 语句要干嘛,再检查你的 SQL 语句语法是否正确。
+- **优化器:** 按照 MySQL 认为最优的方案去执行。
+
-
+
简单来说 MySQL 主要分为 Server 层和存储引擎层:
@@ -96,7 +85,7 @@ select * from tb_student A where A.age='18' and A.name=' 张三 ';
结合上面的说明,我们分析下这个语句的执行流程:
* 先检查该语句是否有权限,如果没有权限,直接返回错误信息,如果有权限,在 MySQL8.0 版本以前,会先查询缓存,以这条 sql 语句为 key 在内存中查询是否有结果,如果有直接缓存,如果没有,执行下一步。
-* 通过分析器进行词法分析,提取 sql 语句的关键元素,比如提取上面这个语句是查询 select,提取需要查询的表名为 tb_student,需要查询所有的列,查询条件是这个表的 id='1'。然后判断这个 sql 语句是否有语法错误,比如关键词是否正确等等,如果检查没问题就执行下一步。
+* 通过分析器进行词法分析,提取 sql 语句的关键元素,比如提取上面这个语句是查询 select,提取需要查询的表名为 tb_student,需要查询所有的列,查询条件是这个表的 id='1'。然后判断这个 sql 语句是否有语法错误,比如关键词是否正确等等,如果检查没问题就执行下一步。
* 接下来就是优化器进行确定执行方案,上面的 sql 语句,可以有两种执行方案:
a.先查询学生表中姓名为“张三”的学生,然后判断是否年龄是 18。
@@ -112,7 +101,7 @@ select * from tb_student A where A.age='18' and A.name=' 张三 ';
```
update tb_student A set A.age='19' where A.name=' 张三 ';
```
-我们来给张三修改下年龄,在实际数据库肯定不会设置年龄这个字段的,不然要被技术负责人打的。其实条语句也基本上会沿着上一个查询的流程走,只不过执行更新的时候肯定要记录日志啦,这就会引入日志模块了,MySQL 自带的日志模块式 **binlog(归档日志)** ,所有的存储引擎都可以使用,我们常用的 InnoDB 引擎还自带了一个日志模块 **redo log(重做日志)**,我们就以 InnoDB 模式下来探讨这个语句的执行流程。流程如下:
+我们来给张三修改下年龄,在实际数据库肯定不会设置年龄这个字段的,不然要被技术负责人打的。其实这条语句也基本上会沿着上一个查询的流程走,只不过执行更新的时候肯定要记录日志啦,这就会引入日志模块了,MySQL 自带的日志模块是 **binlog(归档日志)** ,所有的存储引擎都可以使用,我们常用的 InnoDB 引擎还自带了一个日志模块 **redo log(重做日志)**,我们就以 InnoDB 模式下来探讨这个语句的执行流程。流程如下:
* 先查询到张三这一条数据,如果有缓存,也是会用到缓存。
* 然后拿到查询的语句,把 age 改为 19,然后调用引擎 API 接口,写入这一行数据,InnoDB 引擎把数据保存在内存中,同时记录 redo log,此时 redo log 进入 prepare 状态,然后告诉执行器,执行完成了,随时可以提交。
@@ -121,7 +110,7 @@ update tb_student A set A.age='19' where A.name=' 张三 ';
**这里肯定有同学会问,为什么要用两个日志模块,用一个日志模块不行吗?**
-这是因为最开始 MySQL 并没与 InnoDB 引擎( InnoDB 引擎是其他公司以插件形式插入 MySQL 的) ,MySQL 自带的引擎是 MyISAM,但是我们知道 redo log 是 InnoDB 引擎特有的,其他存储引擎都没有,这就导致会没有 crash-safe 的能力(crash-safe 的能力即使数据库发生异常重启,之前提交的记录都不会丢失),binlog 日志只能用来归档。
+这是因为最开始 MySQL 并没有 InnoDB 引擎(InnoDB 引擎是其他公司以插件形式插入 MySQL 的),MySQL 自带的引擎是 MyISAM,但是我们知道 redo log 是 InnoDB 引擎特有的,其他存储引擎都没有,这就导致会没有 crash-safe 的能力(crash-safe 的能力即使数据库发生异常重启,之前提交的记录都不会丢失),binlog 日志只能用来归档。
并不是说只用一个日志模块不可以,只是 InnoDB 引擎就是通过 redo log 来支持事务的。那么,又会有同学问,我用两个日志模块,但是不要这么复杂行不行,为什么 redo log 要引入 prepare 预提交状态?这里我们用反证法来说明下为什么要这么做?
@@ -138,10 +127,10 @@ update tb_student A set A.age='19' where A.name=' 张三 ';
## 三 总结
-* MySQL 主要分为 Server 层和引擎层,Server 层主要包括连接器、查询缓存、分析器、优化器、执行器,同时还有一个日志模块(binlog),这个日志模块所有执行引擎都可以共用,redolog 只有 InnoDB 有。
+* MySQL 主要分为 Server 层和引擎层,Server 层主要包括连接器、查询缓存、分析器、优化器、执行器,同时还有一个日志模块(binlog),这个日志模块所有执行引擎都可以共用,redolog 只有 InnoDB 有。
* 引擎层是插件式的,目前主要包括,MyISAM,InnoDB,Memory 等。
-* 查询语句的执行流程如下:权限校验(如果命中缓存)---》查询缓存---》分析器---》优化器---》权限校验---》执行器---》引擎
-* 更新语句执行流程如下:分析器----》权限校验----》执行器---》引擎---redo log(prepare 状态---》binlog---》redo log(commit状态)
+* 查询语句的执行流程如下:权限校验(如果命中缓存)--->查询缓存--->分析器--->优化器--->权限校验--->执行器--->引擎
+* 更新语句执行流程如下:分析器---->权限校验---->执行器--->引擎---redo log(prepare 状态)--->binlog--->redo log(commit状态)
## 四 参考
diff --git a/docs/database/mysql/innodb-implementation-of-mvcc.md b/docs/database/mysql/innodb-implementation-of-mvcc.md
new file mode 100644
index 00000000000..96fa1f7982b
--- /dev/null
+++ b/docs/database/mysql/innodb-implementation-of-mvcc.md
@@ -0,0 +1,226 @@
+---
+title: InnoDB存储引擎对MVCC的实现
+category: 数据库
+tag:
+ - MySQL
+---
+
+## 一致性非锁定读和锁定读
+
+### 一致性非锁定读
+
+对于 [**一致性非锁定读(Consistent Nonlocking Reads)** ](https://dev.mysql.com/doc/refman/5.7/en/innodb-consistent-read.html)的实现,通常做法是加一个版本号或者时间戳字段,在更新数据的同时版本号 + 1 或者更新时间戳。查询时,将当前可见的版本号与对应记录的版本号进行比对,如果记录的版本小于可见版本,则表示该记录可见
+
+在 `InnoDB` 存储引擎中,[多版本控制 (multi versioning)](https://dev.mysql.com/doc/refman/5.7/en/innodb-multi-versioning.html) 就是对非锁定读的实现。如果读取的行正在执行 `DELETE` 或 `UPDATE` 操作,这时读取操作不会去等待行上锁的释放。相反地,`InnoDB` 存储引擎会去读取行的一个快照数据,对于这种读取历史数据的方式,我们叫它快照读 (snapshot read)
+
+在 `Repeatable Read` 和 `Read Committed` 两个隔离级别下,如果是执行普通的 `select` 语句(不包括 `select ... lock in share mode` ,`select ... for update`)则会使用 `一致性非锁定读(MVCC)`。并且在 `Repeatable Read` 下 `MVCC` 实现了可重复读和防止部分幻读
+
+### 锁定读
+
+如果执行的是下列语句,就是 [**锁定读(Locking Reads)**](https://dev.mysql.com/doc/refman/5.7/en/innodb-locking-reads.html)
+
+- `select ... lock in share mode`
+- `select ... for update`
+- `insert`、`update`、`delete` 操作
+
+在锁定读下,读取的是数据的最新版本,这种读也被称为 `当前读(current read)`。锁定读会对读取到的记录加锁:
+
+- `select ... lock in share mode`:对记录加 `S` 锁,其它事务也可以加`S`锁,如果加 `x` 锁则会被阻塞
+
+- `select ... for update`、`insert`、`update`、`delete`:对记录加 `X` 锁,且其它事务不能加任何锁
+
+在一致性非锁定读下,即使读取的记录已被其它事务加上 `X` 锁,这时记录也是可以被读取的,即读取的快照数据。上面说了,在 `Repeatable Read` 下 `MVCC` 防止了部分幻读,这边的 “部分” 是指在 `一致性非锁定读` 情况下,只能读取到第一次查询之前所插入的数据(根据 Read View 判断数据可见性,Read View 在第一次查询时生成)。但是!如果是 `当前读` ,每次读取的都是最新数据,这时如果两次查询中间有其它事务插入数据,就会产生幻读。所以, **`InnoDB` 在实现`Repeatable Read` 时,如果执行的是当前读,则会对读取的记录使用 `Next-key Lock` ,来防止其它事务在间隙间插入数据**
+
+## InnoDB 对 MVCC 的实现
+
+`MVCC` 的实现依赖于:**隐藏字段、Read View、undo log**。在内部实现中,`InnoDB` 通过数据行的 `DB_TRX_ID` 和 `Read View` 来判断数据的可见性,如不可见,则通过数据行的 `DB_ROLL_PTR` 找到 `undo log` 中的历史版本。每个事务读到的数据版本可能是不一样的,在同一个事务中,用户只能看到该事务创建 `Read View` 之前已经提交的修改和该事务本身做的修改
+
+### 隐藏字段
+
+在内部,`InnoDB` 存储引擎为每行数据添加了三个 [隐藏字段](https://dev.mysql.com/doc/refman/5.7/en/innodb-multi-versioning.html):
+
+- `DB_TRX_ID(6字节)`:表示最后一次插入或更新该行的事务 id。此外,`delete` 操作在内部被视为更新,只不过会在记录头 `Record header` 中的 `deleted_flag` 字段将其标记为已删除
+- `DB_ROLL_PTR(7字节)` 回滚指针,指向该行的 `undo log` 。如果该行未被更新,则为空
+- `DB_ROW_ID(6字节)`:如果没有设置主键且该表没有唯一非空索引时,`InnoDB` 会使用该 id 来生成聚簇索引
+
+### ReadView
+
+```c
+class ReadView {
+ /* ... */
+private:
+ trx_id_t m_low_limit_id; /* 大于等于这个 ID 的事务均不可见 */
+
+ trx_id_t m_up_limit_id; /* 小于这个 ID 的事务均可见 */
+
+ trx_id_t m_creator_trx_id; /* 创建该 Read View 的事务ID */
+
+ trx_id_t m_low_limit_no; /* 事务 Number, 小于该 Number 的 Undo Logs 均可以被 Purge */
+
+ ids_t m_ids; /* 创建 Read View 时的活跃事务列表 */
+
+ m_closed; /* 标记 Read View 是否 close */
+}
+```
+
+[`Read View`](https://github.com/facebook/mysql-8.0/blob/8.0/storage/innobase/include/read0types.h#L298) 主要是用来做可见性判断,里面保存了 “当前对本事务不可见的其他活跃事务”
+
+主要有以下字段:
+
+- `m_low_limit_id`:目前出现过的最大的事务 ID+1,即下一个将被分配的事务 ID。大于等于这个 ID 的数据版本均不可见
+- `m_up_limit_id`:活跃事务列表 `m_ids` 中最小的事务 ID,如果 `m_ids` 为空,则 `m_up_limit_id` 为 `m_low_limit_id`。小于这个 ID 的数据版本均可见
+- `m_ids`:`Read View` 创建时其他未提交的活跃事务 ID 列表。创建 `Read View`时,将当前未提交事务 ID 记录下来,后续即使它们修改了记录行的值,对于当前事务也是不可见的。`m_ids` 不包括当前事务自己和已提交的事务(正在内存中)
+- `m_creator_trx_id`:创建该 `Read View` 的事务 ID
+
+**事务可见性示意图**([图源](https://leviathan.vip/2019/03/20/InnoDB%E7%9A%84%E4%BA%8B%E5%8A%A1%E5%88%86%E6%9E%90-MVCC/#MVCC-1)):
+
+
+
+### undo-log
+
+`undo log` 主要有两个作用:
+
+- 当事务回滚时用于将数据恢复到修改前的样子
+- 另一个作用是 `MVCC` ,当读取记录时,若该记录被其他事务占用或当前版本对该事务不可见,则可以通过 `undo log` 读取之前的版本数据,以此实现非锁定读
+
+**在 `InnoDB` 存储引擎中 `undo log` 分为两种: `insert undo log` 和 `update undo log`:**
+
+1. **`insert undo log`** :指在 `insert` 操作中产生的 `undo log`。因为 `insert` 操作的记录只对事务本身可见,对其他事务不可见,故该 `undo log` 可以在事务提交后直接删除。不需要进行 `purge` 操作
+
+**`insert` 时的数据初始状态:**
+
+
+
+2. **`update undo log`** :`update` 或 `delete` 操作中产生的 `undo log`。该 `undo log`可能需要提供 `MVCC` 机制,因此不能在事务提交时就进行删除。提交时放入 `undo log` 链表,等待 `purge线程` 进行最后的删除
+
+**数据第一次被修改时:**
+
+
+
+**数据第二次被修改时:**
+
+
+
+不同事务或者相同事务的对同一记录行的修改,会使该记录行的 `undo log` 成为一条链表,链首就是最新的记录,链尾就是最早的旧记录。
+
+### 数据可见性算法
+
+在 `InnoDB` 存储引擎中,创建一个新事务后,执行每个 `select` 语句前,都会创建一个快照(Read View),**快照中保存了当前数据库系统中正处于活跃(没有 commit)的事务的 ID 号**。其实简单的说保存的是系统中当前不应该被本事务看到的其他事务 ID 列表(即 m_ids)。当用户在这个事务中要读取某个记录行的时候,`InnoDB` 会将该记录行的 `DB_TRX_ID` 与 `Read View` 中的一些变量及当前事务 ID 进行比较,判断是否满足可见性条件
+
+[具体的比较算法](https://github.com/facebook/mysql-8.0/blob/8.0/storage/innobase/include/read0types.h#L161)如下:[图源](https://leviathan.vip/2019/03/20/InnoDB%E7%9A%84%E4%BA%8B%E5%8A%A1%E5%88%86%E6%9E%90-MVCC/#MVCC-1)
+
+
+
+1. 如果记录 DB_TRX_ID < m_up_limit_id,那么表明最新修改该行的事务(DB_TRX_ID)在当前事务创建快照之前就提交了,所以该记录行的值对当前事务是可见的
+
+2. 如果 DB_TRX_ID >= m_low_limit_id,那么表明最新修改该行的事务(DB_TRX_ID)在当前事务创建快照之后才修改该行,所以该记录行的值对当前事务不可见。跳到步骤 5
+
+3. m_ids 为空,则表明在当前事务创建快照之前,修改该行的事务就已经提交了,所以该记录行的值对当前事务是可见的
+
+4. 如果 m_up_limit_id <= DB_TRX_ID < m_low_limit_id,表明最新修改该行的事务(DB_TRX_ID)在当前事务创建快照的时候可能处于“活动状态”或者“已提交状态”;所以就要对活跃事务列表 m_ids 进行查找(源码中是用的二分查找,因为是有序的)
+
+ - 如果在活跃事务列表 m_ids 中能找到 DB_TRX_ID,表明:① 在当前事务创建快照前,该记录行的值被事务 ID 为 DB_TRX_ID 的事务修改了,但没有提交;或者 ② 在当前事务创建快照后,该记录行的值被事务 ID 为 DB_TRX_ID 的事务修改了。这些情况下,这个记录行的值对当前事务都是不可见的。跳到步骤 5
+
+ - 在活跃事务列表中找不到,则表明“id 为 trx_id 的事务”在修改“该记录行的值”后,在“当前事务”创建快照前就已经提交了,所以记录行对当前事务可见
+
+5. 在该记录行的 DB_ROLL_PTR 指针所指向的 `undo log` 取出快照记录,用快照记录的 DB_TRX_ID 跳到步骤 1 重新开始判断,直到找到满足的快照版本或返回空
+
+## RC 和 RR 隔离级别下 MVCC 的差异
+
+在事务隔离级别 `RC` 和 `RR` (InnoDB 存储引擎的默认事务隔离级别)下,`InnoDB` 存储引擎使用 `MVCC`(非锁定一致性读),但它们生成 `Read View` 的时机却不同
+
+- 在 RC 隔离级别下的 **`每次select`** 查询前都生成一个`Read View` (m_ids 列表)
+- 在 RR 隔离级别下只在事务开始后 **`第一次select`** 数据前生成一个`Read View`(m_ids 列表)
+
+## MVCC 解决不可重复读问题
+
+虽然 RC 和 RR 都通过 `MVCC` 来读取快照数据,但由于 **生成 Read View 时机不同**,从而在 RR 级别下实现可重复读
+
+举个例子:
+
+
+
+### 在 RC 下 ReadView 生成情况
+
+1. **`假设时间线来到 T4 ,那么此时数据行 id = 1 的版本链为`:**
+
+ 
+
+由于 RC 级别下每次查询都会生成`Read View` ,并且事务 101、102 并未提交,此时 `103` 事务生成的 `Read View` 中活跃的事务 **`m_ids` 为:[101,102]** ,`m_low_limit_id`为:104,`m_up_limit_id`为:101,`m_creator_trx_id` 为:103
+
+- 此时最新记录的 `DB_TRX_ID` 为 101,m_up_limit_id <= 101 < m_low_limit_id,所以要在 `m_ids` 列表中查找,发现 `DB_TRX_ID` 存在列表中,那么这个记录不可见
+- 根据 `DB_ROLL_PTR` 找到 `undo log` 中的上一版本记录,上一条记录的 `DB_TRX_ID` 还是 101,不可见
+- 继续找上一条 `DB_TRX_ID`为 1,满足 1 < m_up_limit_id,可见,所以事务 103 查询到数据为 `name = 菜花`
+
+2. **`时间线来到 T6 ,数据的版本链为`:**
+
+ 
+
+因为在 RC 级别下,重新生成 `Read View`,这时事务 101 已经提交,102 并未提交,所以此时 `Read View` 中活跃的事务 **`m_ids`:[102]** ,`m_low_limit_id`为:104,`m_up_limit_id`为:102,`m_creator_trx_id`为:103
+
+- 此时最新记录的 `DB_TRX_ID` 为 102,m_up_limit_id <= 102 < m_low_limit_id,所以要在 `m_ids` 列表中查找,发现 `DB_TRX_ID` 存在列表中,那么这个记录不可见
+
+- 根据 `DB_ROLL_PTR` 找到 `undo log` 中的上一版本记录,上一条记录的 `DB_TRX_ID` 为 101,满足 101 < m_up_limit_id,记录可见,所以在 `T6` 时间点查询到数据为 `name = 李四`,与时间 T4 查询到的结果不一致,不可重复读!
+
+3. **`时间线来到 T9 ,数据的版本链为`:**
+
+
+
+重新生成 `Read View`, 这时事务 101 和 102 都已经提交,所以 **m_ids** 为空,则 m_up_limit_id = m_low_limit_id = 104,最新版本事务 ID 为 102,满足 102 < m_low_limit_id,可见,查询结果为 `name = 赵六`
+
+> **总结:** **在 RC 隔离级别下,事务在每次查询开始时都会生成并设置新的 Read View,所以导致不可重复读**
+
+### 在 RR 下 ReadView 生成情况
+
+**在可重复读级别下,只会在事务开始后第一次读取数据时生成一个 Read View(m_ids 列表)**
+
+1. **`在 T4 情况下的版本链为`:**
+
+
+
+在当前执行 `select` 语句时生成一个 `Read View`,此时 **`m_ids`:[101,102]** ,`m_low_limit_id`为:104,`m_up_limit_id`为:101,`m_creator_trx_id` 为:103
+
+此时和 RC 级别下一样:
+
+- 最新记录的 `DB_TRX_ID` 为 101,m_up_limit_id <= 101 < m_low_limit_id,所以要在 `m_ids` 列表中查找,发现 `DB_TRX_ID` 存在列表中,那么这个记录不可见
+- 根据 `DB_ROLL_PTR` 找到 `undo log` 中的上一版本记录,上一条记录的 `DB_TRX_ID` 还是 101,不可见
+- 继续找上一条 `DB_TRX_ID`为 1,满足 1 < m_up_limit_id,可见,所以事务 103 查询到数据为 `name = 菜花`
+
+2. **`时间点 T6 情况下`:**
+
+ 
+
+ 在 RR 级别下只会生成一次`Read View`,所以此时依然沿用 **`m_ids` :[101,102]** ,`m_low_limit_id`为:104,`m_up_limit_id`为:101,`m_creator_trx_id` 为:103
+
+- 最新记录的 `DB_TRX_ID` 为 102,m_up_limit_id <= 102 < m_low_limit_id,所以要在 `m_ids` 列表中查找,发现 `DB_TRX_ID` 存在列表中,那么这个记录不可见
+
+- 根据 `DB_ROLL_PTR` 找到 `undo log` 中的上一版本记录,上一条记录的 `DB_TRX_ID` 为 101,不可见
+
+- 继续根据 `DB_ROLL_PTR` 找到 `undo log` 中的上一版本记录,上一条记录的 `DB_TRX_ID` 还是 101,不可见
+
+- 继续找上一条 `DB_TRX_ID`为 1,满足 1 < m_up_limit_id,可见,所以事务 103 查询到数据为 `name = 菜花`
+
+3. **时间点 T9 情况下:**
+
+
+
+此时情况跟 T6 完全一样,由于已经生成了 `Read View`,此时依然沿用 **`m_ids` :[101,102]** ,所以查询结果依然是 `name = 菜花`
+
+## MVCC➕Next-key-Lock 防止幻读
+
+`InnoDB`存储引擎在 RR 级别下通过 `MVCC`和 `Next-key Lock` 来解决幻读问题:
+
+**1、执行普通 `select`,此时会以 `MVCC` 快照读的方式读取数据**
+
+在快照读的情况下,RR 隔离级别只会在事务开启后的第一次查询生成 `Read View` ,并使用至事务提交。所以在生成 `Read View` 之后其它事务所做的更新、插入记录版本对当前事务并不可见,实现了可重复读和防止快照读下的 “幻读”
+
+**2、执行 select...for update/lock in share mode、insert、update、delete 等当前读**
+
+在当前读下,读取的都是最新的数据,如果其它事务有插入新的记录,并且刚好在当前事务查询范围内,就会产生幻读!`InnoDB` 使用 [Next-key Lock](https://dev.mysql.com/doc/refman/5.7/en/innodb-locking.html#innodb-next-key-locks) 来防止这种情况。当执行当前读时,会锁定读取到的记录的同时,锁定它们的间隙,防止其它事务在查询范围内插入数据。只要我不让你插入,就不会发生幻读
+
+## 参考
+
+- **《MySQL 技术内幕 InnoDB 存储引擎第 2 版》**
+- [Innodb 中的事务隔离级别和锁的关系](https://tech.meituan.com/2014/08/20/innodb-lock.html)
+- [MySQL 事务与 MVCC 如何实现的隔离级别](https://blog.csdn.net/qq_35190492/article/details/109044141)
+- [InnoDB 事务分析-MVCC](https://leviathan.vip/2019/03/20/InnoDB%E7%9A%84%E4%BA%8B%E5%8A%A1%E5%88%86%E6%9E%90-MVCC/)
diff --git "a/docs/database/MySQL\351\253\230\346\200\247\350\203\275\344\274\230\345\214\226\350\247\204\350\214\203\345\273\272\350\256\256.md" b/docs/database/mysql/mysql-high-performance-optimization-specification-recommendations.md
similarity index 75%
rename from "docs/database/MySQL\351\253\230\346\200\247\350\203\275\344\274\230\345\214\226\350\247\204\350\214\203\345\273\272\350\256\256.md"
rename to docs/database/mysql/mysql-high-performance-optimization-specification-recommendations.md
index f079ad77593..7c2b7def8ad 100644
--- "a/docs/database/MySQL\351\253\230\346\200\247\350\203\275\344\274\230\345\214\226\350\247\204\350\214\203\345\273\272\350\256\256.md"
+++ b/docs/database/mysql/mysql-high-performance-optimization-specification-recommendations.md
@@ -1,58 +1,11 @@
-> 作者: 听风,原文地址: 。JavaGuide 已获得作者授权。
+---
+title: MySQL 高性能优化规范建议
+category: 数据库
+tag:
+ - MySQL
+---
-
-
-- [数据库命令规范](#数据库命令规范)
-- [数据库基本设计规范](#数据库基本设计规范)
- - [1. 所有表必须使用 Innodb 存储引擎](#1-所有表必须使用-innodb-存储引擎)
- - [2. 数据库和表的字符集统一使用 UTF8](#2-数据库和表的字符集统一使用-utf8)
- - [3. 所有表和字段都需要添加注释](#3-所有表和字段都需要添加注释)
- - [4. 尽量控制单表数据量的大小,建议控制在 500 万以内。](#4-尽量控制单表数据量的大小建议控制在-500-万以内)
- - [5. 谨慎使用 MySQL 分区表](#5-谨慎使用-mysql-分区表)
- - [6.尽量做到冷热数据分离,减小表的宽度](#6尽量做到冷热数据分离减小表的宽度)
- - [7. 禁止在表中建立预留字段](#7-禁止在表中建立预留字段)
- - [8. 禁止在数据库中存储图片,文件等大的二进制数据](#8-禁止在数据库中存储图片文件等大的二进制数据)
- - [9. 禁止在线上做数据库压力测试](#9-禁止在线上做数据库压力测试)
- - [10. 禁止从开发环境,测试环境直接连接生成环境数据库](#10-禁止从开发环境测试环境直接连接生成环境数据库)
-- [数据库字段设计规范](#数据库字段设计规范)
- - [1. 优先选择符合存储需要的最小的数据类型](#1-优先选择符合存储需要的最小的数据类型)
- - [2. 避免使用 TEXT,BLOB 数据类型,最常见的 TEXT 类型可以存储 64k 的数据](#2-避免使用-textblob-数据类型最常见的-text-类型可以存储-64k-的数据)
- - [3. 避免使用 ENUM 类型](#3-避免使用-enum-类型)
- - [4. 尽可能把所有列定义为 NOT NULL](#4-尽可能把所有列定义为-not-null)
- - [5. 使用 TIMESTAMP(4 个字节) 或 DATETIME 类型 (8 个字节) 存储时间](#5-使用-timestamp4-个字节-或-datetime-类型-8-个字节-存储时间)
- - [6. 同财务相关的金额类数据必须使用 decimal 类型](#6-同财务相关的金额类数据必须使用-decimal-类型)
-- [索引设计规范](#索引设计规范)
- - [1. 限制每张表上的索引数量,建议单张表索引不超过 5 个](#1-限制每张表上的索引数量建议单张表索引不超过-5-个)
- - [2. 禁止给表中的每一列都建立单独的索引](#2-禁止给表中的每一列都建立单独的索引)
- - [3. 每个 Innodb 表必须有个主键](#3-每个-innodb-表必须有个主键)
- - [4. 常见索引列建议](#4-常见索引列建议)
- - [5.如何选择索引列的顺序](#5如何选择索引列的顺序)
- - [6. 避免建立冗余索引和重复索引(增加了查询优化器生成执行计划的时间)](#6-避免建立冗余索引和重复索引增加了查询优化器生成执行计划的时间)
- - [7. 对于频繁的查询优先考虑使用覆盖索引](#7-对于频繁的查询优先考虑使用覆盖索引)
- - [8.索引 SET 规范](#8索引-set-规范)
-- [数据库 SQL 开发规范](#数据库-sql-开发规范)
- - [1. 建议使用预编译语句进行数据库操作](#1-建议使用预编译语句进行数据库操作)
- - [2. 避免数据类型的隐式转换](#2-避免数据类型的隐式转换)
- - [3. 充分利用表上已经存在的索引](#3-充分利用表上已经存在的索引)
- - [4. 数据库设计时,应该要对以后扩展进行考虑](#4-数据库设计时应该要对以后扩展进行考虑)
- - [5. 程序连接不同的数据库使用不同的账号,进制跨库查询](#5-程序连接不同的数据库使用不同的账号进制跨库查询)
- - [6. 禁止使用 SELECT * 必须使用 SELECT <字段列表> 查询](#6-禁止使用-select--必须使用-select-字段列表-查询)
- - [7. 禁止使用不含字段列表的 INSERT 语句](#7-禁止使用不含字段列表的-insert-语句)
- - [8. 避免使用子查询,可以把子查询优化为 join 操作](#8-避免使用子查询可以把子查询优化为-join-操作)
- - [9. 避免使用 JOIN 关联太多的表](#9-避免使用-join-关联太多的表)
- - [10. 减少同数据库的交互次数](#10-减少同数据库的交互次数)
- - [11. 对应同一列进行 or 判断时,使用 in 代替 or](#11-对应同一列进行-or-判断时使用-in-代替-or)
- - [12. 禁止使用 order by rand() 进行随机排序](#12-禁止使用-order-by-rand-进行随机排序)
- - [13. WHERE 从句中禁止对列进行函数转换和计算](#13-where-从句中禁止对列进行函数转换和计算)
- - [14. 在明显不会有重复值时使用 UNION ALL 而不是 UNION](#14-在明显不会有重复值时使用-union-all-而不是-union)
- - [15. 拆分复杂的大 SQL 为多个小 SQL](#15-拆分复杂的大-sql-为多个小-sql)
-- [数据库操作行为规范](#数据库操作行为规范)
- - [1. 超 100 万行的批量写 (UPDATE,DELETE,INSERT) 操作,要分批多次进行操作](#1-超-100-万行的批量写-updatedeleteinsert-操作要分批多次进行操作)
- - [2. 对于大表使用 pt-online-schema-change 修改表结构](#2-对于大表使用-pt-online-schema-change-修改表结构)
- - [3. 禁止为程序使用的账号赋予 super 权限](#3-禁止为程序使用的账号赋予-super-权限)
- - [4. 对于程序连接数据库账号,遵循权限最小原则](#4-对于程序连接数据库账号遵循权限最小原则)
-
-
+> 作者: 听风,原文地址: 。JavaGuide 已获得作者授权。
## 数据库命令规范
@@ -76,6 +29,8 @@ Innodb 支持事务,支持行级锁,更好的恢复性,高并发下性能
兼容性更好,统一字符集可以避免由于字符集转换产生的乱码,不同的字符集进行比较前需要进行转换会造成索引失效,如果数据库中有存储 emoji 表情的需要,字符集需要采用 utf8mb4 字符集。
+参考文章:[MySQL 字符集不一致导致索引失效的一个真实案例](https://blog.csdn.net/horses/article/details/107243447)
+
### 3. 所有表和字段都需要添加注释
使用 comment 从句添加表和列的备注,从一开始就进行数据字典的维护
@@ -166,7 +121,7 @@ MySQL 内存临时表不支持 TEXT、BLOB 这样的大数据类型,如果查
**2、TEXT 或 BLOB 类型只能使用前缀索引**
-因为[MySQL](http://mp.weixin.qq.com/s?__biz=MzI4Njc5NjM1NQ==&mid=2247487885&idx=1&sn=65b1bf5f7d4505502620179669a9c2df&chksm=ebd62ea1dca1a7b7bf884bcd9d538d78ba064ee03c09436ca8e57873b1d98a55afd6d7884cfc&scene=21#wechat_redirect) 对索引字段长度是有限制的,所以 TEXT 类型只能使用前缀索引,并且 TEXT 列上是不能有默认值的
+因为[MySQL](https://mp.weixin.qq.com/s?__biz=MzI4Njc5NjM1NQ==&mid=2247487885&idx=1&sn=65b1bf5f7d4505502620179669a9c2df&chksm=ebd62ea1dca1a7b7bf884bcd9d538d78ba064ee03c09436ca8e57873b1d98a55afd6d7884cfc&scene=21#wechat_redirect) 对索引字段长度是有限制的,所以 TEXT 类型只能使用前缀索引,并且 TEXT 列上是不能有默认值的
### 3. 避免使用 ENUM 类型
@@ -264,7 +219,7 @@ Innodb 是按照主键索引的顺序来组织表的
### 7. 对于频繁的查询优先考虑使用覆盖索引
-> 覆盖索引:就是包含了所有查询字段 (where,select,ordery by,group by 包含的字段) 的索引
+> 覆盖索引:就是包含了所有查询字段 (where,select,order by,group by 包含的字段) 的索引
**覆盖索引的好处:**
diff --git a/docs/database/mysql/mysql-index.md b/docs/database/mysql/mysql-index.md
new file mode 100644
index 00000000000..5dd4e526492
--- /dev/null
+++ b/docs/database/mysql/mysql-index.md
@@ -0,0 +1,266 @@
+---
+title: MySQL 索引详解
+category: 数据库
+tag:
+ - MySQL
+---
+
+
+
+## 何为索引?有什么作用?
+
+**索引是一种用于快速查询和检索数据的数据结构。常见的索引结构有: B 树, B+树和 Hash。**
+
+索引的作用就相当于目录的作用。打个比方: 我们在查字典的时候,如果没有目录,那我们就只能一页一页的去找我们需要查的那个字,速度很慢。如果有目录了,我们只需要先去目录里查找字的位置,然后直接翻到那一页就行了。
+
+## 索引的优缺点
+
+**优点** :
+
+- 使用索引可以大大加快 数据的检索速度(大大减少检索的数据量), 这也是创建索引的最主要的原因。
+- 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。
+
+**缺点** :
+
+- 创建索引和维护索引需要耗费许多时间。当对表中的数据进行增删改的时候,如果数据有索引,那么索引也需要动态的修改,会降低 SQL 执行效率。
+- 索引需要使用物理文件存储,也会耗费一定空间。
+
+但是,**使用索引一定能提高查询性能吗?**
+
+大多数情况下,索引查询都是比全表扫描要快的。但是如果数据库的数据量不大,那么使用索引也不一定能够带来很大提升。
+
+## 索引的底层数据结构
+
+### Hash表 & B+树
+
+哈希表是键值对的集合,通过键(key)即可快速取出对应的值(value),因此哈希表可以快速检索数据(接近 O(1))。
+
+**为何能够通过 key 快速取出 value呢?** 原因在于 **哈希算法**(也叫散列算法)。通过哈希算法,我们可以快速找到 value 对应的 index,找到了 index 也就找到了对应的 value。
+
+```java
+hash = hashfunc(key)
+index = hash % array_size
+```
+
+
+
+
+
+但是!哈希算法有个 **Hash 冲突** 问题,也就是说多个不同的 key 最后得到的 index 相同。通常情况下,我们常用的解决办法是 **链地址法**。链地址法就是将哈希冲突数据存放在链表中。就比如 JDK1.8 之前 `HashMap` 就是通过链地址法来解决哈希冲突的。不过,JDK1.8 以后`HashMap`为了减少链表过长的时候搜索时间过长引入了红黑树。
+
+
+
+为了减少 Hash 冲突的发生,一个好的哈希函数应该“均匀地”将数据分布在整个可能的哈希值集合中。
+
+既然哈希表这么快,**为什么MySQL 没有使用其作为索引的数据结构呢?**
+
+**1.Hash 冲突问题** :我们上面也提到过Hash 冲突了,不过对于数据库来说这还不算最大的缺点。
+
+**2.Hash 索引不支持顺序和范围查询(Hash 索引不支持顺序和范围查询是它最大的缺点:** 假如我们要对表中的数据进行排序或者进行范围查询,那 Hash 索引可就不行了。
+
+试想一种情况:
+
+```java
+SELECT * FROM tb1 WHERE id < 500;Copy to clipboardErrorCopied
+```
+
+在这种范围查询中,优势非常大,直接遍历比 500 小的叶子节点就够了。而 Hash 索引是根据 hash 算法来定位的,难不成还要把 1 - 499 的数据,每个都进行一次 hash 计算来定位吗?这就是 Hash 最大的缺点了。
+
+### B 树& B+树
+
+B 树也称 B-树,全称为 **多路平衡查找树** ,B+ 树是 B 树的一种变体。B 树和 B+树中的 B 是 `Balanced` (平衡)的意思。
+
+目前大部分数据库系统及文件系统都采用 B-Tree 或其变种 B+Tree 作为索引结构。
+
+**B 树& B+树两者有何异同呢?**
+
+- B 树的所有节点既存放键(key) 也存放 数据(data),而 B+树只有叶子节点存放 key 和 data,其他内节点只存放 key。
+- B 树的叶子节点都是独立的;B+树的叶子节点有一条引用链指向与它相邻的叶子节点。
+- B 树的检索的过程相当于对范围内的每个节点的关键字做二分查找,可能还没有到达叶子节点,检索就结束了。而 B+树的检索效率就很稳定了,任何查找都是从根节点到叶子节点的过程,叶子节点的顺序检索很明显。
+
+
+
+在 MySQL 中,MyISAM 引擎和 InnoDB 引擎都是使用 B+Tree 作为索引结构,但是,两者的实现方式不太一样。(下面的内容整理自《Java 工程师修炼之道》)
+
+MyISAM 引擎中,B+Tree 叶节点的 data 域存放的是数据记录的地址。在索引检索的时候,首先按照 B+Tree 搜索算法搜索索引,如果指定的 Key 存在,则取出其 data 域的值,然后以 data 域的值为地址读取相应的数据记录。这被称为“非聚簇索引”。
+
+InnoDB 引擎中,其数据文件本身就是索引文件。相比 MyISAM,索引文件和数据文件是分离的,其表数据文件本身就是按 B+Tree 组织的一个索引结构,树的叶节点 data 域保存了完整的数据记录。这个索引的 key 是数据表的主键,因此 InnoDB 表数据文件本身就是主索引。这被称为“聚簇索引(或聚集索引)”,而其余的索引都作为辅助索引,辅助索引的 data 域存储相应记录主键的值而不是地址,这也是和 MyISAM 不同的地方。在根据主索引搜索时,直接找到 key 所在的节点即可取出数据;在根据辅助索引查找时,则需要先取出主键的值,在走一遍主索引。 因此,在设计表的时候,不建议使用过长的字段作为主键,也不建议使用非单调的字段作为主键,这样会造成主索引频繁分裂。
+
+## 索引类型
+
+### 主键索引(Primary Key)
+
+数据表的主键列使用的就是主键索引。
+
+一张数据表有只能有一个主键,并且主键不能为 null,不能重复。
+
+在 MySQL 的 InnoDB 的表中,当没有显示的指定表的主键时,InnoDB 会自动先检查表中是否有唯一索引的字段,如果有,则选择该字段为默认的主键,否则 InnoDB 将会自动创建一个 6Byte 的自增主键。
+
+### 二级索引(辅助索引)
+
+**二级索引又称为辅助索引,是因为二级索引的叶子节点存储的数据是主键。也就是说,通过二级索引,可以定位主键的位置。**
+
+唯一索引,普通索引,前缀索引等索引属于二级索引。
+
+**PS:不懂的同学可以暂存疑,慢慢往下看,后面会有答案的,也可以自行搜索。**
+
+1. **唯一索引(Unique Key)** :唯一索引也是一种约束。**唯一索引的属性列不能出现重复的数据,但是允许数据为 NULL,一张表允许创建多个唯一索引。** 建立唯一索引的目的大部分时候都是为了该属性列的数据的唯一性,而不是为了查询效率。
+2. **普通索引(Index)** :**普通索引的唯一作用就是为了快速查询数据,一张表允许创建多个普通索引,并允许数据重复和 NULL。**
+3. **前缀索引(Prefix)** :前缀索引只适用于字符串类型的数据。前缀索引是对文本的前几个字符创建索引,相比普通索引建立的数据更小,
+ 因为只取前几个字符。
+4. **全文索引(Full Text)** :全文索引主要是为了检索大文本数据中的关键字的信息,是目前搜索引擎数据库使用的一种技术。Mysql5.6 之前只有 MYISAM 引擎支持全文索引,5.6 之后 InnoDB 也支持了全文索引。
+
+二级索引:
+
+
+## 聚集索引与非聚集索引
+
+### 聚集索引
+
+**聚集索引即索引结构和数据一起存放的索引。主键索引属于聚集索引。**
+
+在 Mysql 中,InnoDB 引擎的表的 `.ibd`文件就包含了该表的索引和数据,对于 InnoDB 引擎表来说,该表的索引(B+树)的每个非叶子节点存储索引,叶子节点存储索引和索引对应的数据。
+
+#### 聚集索引的优点
+
+聚集索引的查询速度非常的快,因为整个 B+树本身就是一颗多叉平衡树,叶子节点也都是有序的,定位到索引的节点,就相当于定位到了数据。
+
+#### 聚集索引的缺点
+
+1. **依赖于有序的数据** :因为 B+树是多路平衡树,如果索引的数据不是有序的,那么就需要在插入时排序,如果数据是整型还好,否则类似于字符串或 UUID 这种又长又难比较的数据,插入或查找的速度肯定比较慢。
+2. **更新代价大** : 如果对索引列的数据被修改时,那么对应的索引也将会被修改,
+ 而且况聚集索引的叶子节点还存放着数据,修改代价肯定是较大的,
+ 所以对于主键索引来说,主键一般都是不可被修改的。
+
+### 非聚集索引
+
+**非聚集索引即索引结构和数据分开存放的索引。**
+
+**二级索引属于非聚集索引。**
+
+> MYISAM 引擎的表的.MYI 文件包含了表的索引,
+> 该表的索引(B+树)的每个叶子非叶子节点存储索引,
+> 叶子节点存储索引和索引对应数据的指针,指向.MYD 文件的数据。
+>
+> **非聚集索引的叶子节点并不一定存放数据的指针,
+> 因为二级索引的叶子节点就存放的是主键,根据主键再回表查数据。**
+
+#### 非聚集索引的优点
+
+**更新代价比聚集索引要小** 。非聚集索引的更新代价就没有聚集索引那么大了,非聚集索引的叶子节点是不存放数据的
+
+#### 非聚集索引的缺点
+
+1. 跟聚集索引一样,非聚集索引也依赖于有序的数据
+2. **可能会二次查询(回表)** :这应该是非聚集索引最大的缺点了。 当查到索引对应的指针或主键后,可能还需要根据指针或主键再到数据文件或表中查询。
+
+这是 MySQL 的表的文件截图:
+
+
+
+聚集索引和非聚集索引:
+
+
+
+### 非聚集索引一定回表查询吗(覆盖索引)?
+
+**非聚集索引不一定回表查询。**
+
+> 试想一种情况,用户准备使用 SQL 查询用户名,而用户名字段正好建立了索引。
+
+```text
+ SELECT name FROM table WHERE name='guang19';
+```
+
+> 那么这个索引的 key 本身就是 name,查到对应的 name 直接返回就行了,无需回表查询。
+
+**即使是 MYISAM 也是这样,虽然 MYISAM 的主键索引确实需要回表,
+因为它的主键索引的叶子节点存放的是指针。但是如果 SQL 查的就是主键呢?**
+
+```text
+SELECT id FROM table WHERE id=1;
+```
+
+主键索引本身的 key 就是主键,查到返回就行了。这种情况就称之为覆盖索引了。
+
+## 覆盖索引
+
+如果一个索引包含(或者说覆盖)所有需要查询的字段的值,我们就称之为“覆盖索引”。我们知道在 InnoDB 存储引擎中,如果不是主键索引,叶子节点存储的是主键+列值。最终还是要“回表”,也就是要通过主键再查找一次。这样就会比较慢覆盖索引就是把要查询出的列和索引是对应的,不做回表操作!
+
+**覆盖索引即需要查询的字段正好是索引的字段,那么直接根据该索引,就可以查到数据了,
+而无需回表查询。**
+
+> 如主键索引,如果一条 SQL 需要查询主键,那么正好根据主键索引就可以查到主键。
+>
+> 再如普通索引,如果一条 SQL 需要查询 name,name 字段正好有索引,
+> 那么直接根据这个索引就可以查到数据,也无需回表。
+
+覆盖索引:
+
+
+## 创建索引的注意事项
+
+**1.选择合适的字段创建索引:**
+
+- **不为 NULL 的字段** :索引字段的数据应该尽量不为 NULL,因为对于数据为 NULL 的字段,数据库较难优化。如果字段频繁被查询,但又避免不了为 NULL,建议使用 0,1,true,false 这样语义较为清晰的短值或短字符作为替代。
+- **被频繁查询的字段** :我们创建索引的字段应该是查询操作非常频繁的字段。
+- **被作为条件查询的字段** :被作为 WHERE 条件查询的字段,应该被考虑建立索引。
+- **频繁需要排序的字段** :索引已经排序,这样查询可以利用索引的排序,加快排序查询时间。
+- **被经常频繁用于连接的字段** :经常用于连接的字段可能是一些外键列,对于外键列并不一定要建立外键,只是说该列涉及到表与表的关系。对于频繁被连接查询的字段,可以考虑建立索引,提高多表连接查询的效率。
+
+**2.被频繁更新的字段应该慎重建立索引。**
+
+虽然索引能带来查询上的效率,但是维护索引的成本也是不小的。
+如果一个字段不被经常查询,反而被经常修改,那么就更不应该在这种字段上建立索引了。
+
+**3.尽可能的考虑建立联合索引而不是单列索引。**
+
+因为索引是需要占用磁盘空间的,可以简单理解为每个索引都对应着一颗 B+树。如果一个表的字段过多,索引过多,那么当这个表的数据达到一个体量后,索引占用的空间也是很多的,且修改索引时,耗费的时间也是较多的。如果是联合索引,多个字段在一个索引上,那么将会节约很大磁盘空间,且修改数据的操作效率也会提升。
+
+**4.注意避免冗余索引** 。
+
+冗余索引指的是索引的功能相同,能够命中索引(a, b)就肯定能命中索引(a) ,那么索引(a)就是冗余索引。如(name,city )和(name )这两个索引就是冗余索引,能够命中前者的查询肯定是能够命中后者的 在大多数情况下,都应该尽量扩展已有的索引而不是创建新索引。
+
+**5.考虑在字符串类型的字段上使用前缀索引代替普通索引。**
+
+前缀索引仅限于字符串类型,较普通索引会占用更小的空间,所以可以考虑使用前缀索引带替普通索引。
+
+## 使用索引的一些建议
+
+- 对于中到大型表索引都是非常有效的,但是特大型表的话维护开销会很大,不适合建索引
+- 避免 where 子句中对字段施加函数,这会造成无法命中索引。
+- 在使用 InnoDB 时使用与业务无关的自增主键作为主键,即使用逻辑主键,而不要使用业务主键。
+- 删除长期未使用的索引,不用的索引的存在会造成不必要的性能损耗 MySQL 5.7 可以通过查询 sys 库的 schema_unused_indexes 视图来查询哪些索引从未被使用
+- 在使用 limit offset 查询缓慢时,可以借助索引来提高性能
+
+## MySQL 如何为表字段添加索引?
+
+1.添加 PRIMARY KEY(主键索引)
+
+```sql
+ALTER TABLE `table_name` ADD PRIMARY KEY ( `column` )
+```
+
+2.添加 UNIQUE(唯一索引)
+
+```sqlite
+ALTER TABLE `table_name` ADD UNIQUE ( `column` )
+```
+
+3.添加 INDEX(普通索引)
+
+```sql
+ALTER TABLE `table_name` ADD INDEX index_name ( `column` )
+```
+
+4.添加 FULLTEXT(全文索引)
+
+```sql
+ALTER TABLE `table_name` ADD FULLTEXT ( `column`)
+```
+
+5.添加多列索引
+
+```sql
+ALTER TABLE `table_name` ADD INDEX index_name ( `column1`, `column2`, `column3` )
+```
\ No newline at end of file
diff --git a/docs/database/mysql/mysql-logs.md b/docs/database/mysql/mysql-logs.md
new file mode 100644
index 00000000000..697cf30f6a1
--- /dev/null
+++ b/docs/database/mysql/mysql-logs.md
@@ -0,0 +1,289 @@
+---
+title: MySQL三大日志(binlog、redo log和undo log)详解
+category: 数据库
+tag:
+ - MySQL
+---
+
+
+
+> 本文来自公号程序猿阿星投稿,JavaGuide 对其做了补充完善。
+
+## 前言
+
+`MySQL` 日志 主要包括错误日志、查询日志、慢查询日志、事务日志、二进制日志几大类。其中,比较重要的还要属二进制日志 `binlog`(归档日志)和事务日志 `redo log`(重做日志)和 `undo log`(回滚日志)。
+
+
+
+今天就来聊聊 `redo log`(重做日志)、`binlog`(归档日志)、两阶段提交、`undo log` (回滚日志)。
+
+## redo log
+
+`redo log`(重做日志)是`InnoDB`存储引擎独有的,它让`MySQL`拥有了崩溃恢复能力。
+
+比如 `MySQL` 实例挂了或宕机了,重启时,`InnoDB`存储引擎会使用`redo log`恢复数据,保证数据的持久性与完整性。
+
+
+
+`MySQL` 中数据是以页为单位,你查询一条记录,会从硬盘把一页的数据加载出来,加载出来的数据叫数据页,会放入到 `Buffer Pool` 中。
+
+后续的查询都是先从 `Buffer Pool` 中找,没有命中再去硬盘加载,减少硬盘 `IO` 开销,提升性能。
+
+更新表数据的时候,也是如此,发现 `Buffer Pool` 里存在要更新的数据,就直接在 `Buffer Pool` 里更新。
+
+然后会把“在某个数据页上做了什么修改”记录到重做日志缓存(`redo log buffer`)里,接着刷盘到 `redo log` 文件里。
+
+
+
+理想情况,事务一提交就会进行刷盘操作,但实际上,刷盘的时机是根据策略来进行的。
+
+> 小贴士:每条 redo 记录由“表空间号+数据页号+偏移量+修改数据长度+具体修改的数据”组成
+
+### 刷盘时机
+
+`InnoDB` 存储引擎为 `redo log` 的刷盘策略提供了 `innodb_flush_log_at_trx_commit` 参数,它支持三种策略:
+
+- **0** :设置为 0 的时候,表示每次事务提交时不进行刷盘操作
+- **1** :设置为 1 的时候,表示每次事务提交时都将进行刷盘操作(默认值)
+- **2** :设置为 2 的时候,表示每次事务提交时都只把 redo log buffer 内容写入 page cache
+
+`innodb_flush_log_at_trx_commit` 参数默认为 1 ,也就是说当事务提交时会调用 `fsync` 对 redo log 进行刷盘
+
+另外,`InnoDB` 存储引擎有一个后台线程,每隔`1` 秒,就会把 `redo log buffer` 中的内容写到文件系统缓存(`page cache`),然后调用 `fsync` 刷盘。
+
+
+
+也就是说,一个没有提交事务的 `redo log` 记录,也可能会刷盘。
+
+**为什么呢?**
+
+因为在事务执行过程 `redo log` 记录是会写入`redo log buffer` 中,这些 `redo log` 记录会被后台线程刷盘。
+
+
+
+除了后台线程每秒`1`次的轮询操作,还有一种情况,当 `redo log buffer` 占用的空间即将达到 `innodb_log_buffer_size` 一半的时候,后台线程会主动刷盘。
+
+下面是不同刷盘策略的流程图。
+
+#### innodb_flush_log_at_trx_commit=0
+
+
+
+为`0`时,如果`MySQL`挂了或宕机可能会有`1`秒数据的丢失。
+
+#### innodb_flush_log_at_trx_commit=1
+
+
+
+为`1`时, 只要事务提交成功,`redo log`记录就一定在硬盘里,不会有任何数据丢失。
+
+如果事务执行期间`MySQL`挂了或宕机,这部分日志丢了,但是事务并没有提交,所以日志丢了也不会有损失。
+
+#### innodb_flush_log_at_trx_commit=2
+
+
+
+为`2`时, 只要事务提交成功,`redo log buffer`中的内容只写入文件系统缓存(`page cache`)。
+
+如果仅仅只是`MySQL`挂了不会有任何数据丢失,但是宕机可能会有`1`秒数据的丢失。
+
+### 日志文件组
+
+硬盘上存储的 `redo log` 日志文件不只一个,而是以一个**日志文件组**的形式出现的,每个的`redo`日志文件大小都是一样的。
+
+比如可以配置为一组`4`个文件,每个文件的大小是 `1GB`,整个 `redo log` 日志文件组可以记录`4G`的内容。
+
+它采用的是环形数组形式,从头开始写,写到末尾又回到头循环写,如下图所示。
+
+
+
+在个**日志文件组**中还有两个重要的属性,分别是 `write pos、checkpoint`
+
+- **write pos** 是当前记录的位置,一边写一边后移
+- **checkpoint** 是当前要擦除的位置,也是往后推移
+
+每次刷盘 `redo log` 记录到**日志文件组**中,`write pos` 位置就会后移更新。
+
+每次 `MySQL` 加载**日志文件组**恢复数据时,会清空加载过的 `redo log` 记录,并把 `checkpoint` 后移更新。
+
+`write pos` 和 `checkpoint` 之间的还空着的部分可以用来写入新的 `redo log` 记录。
+
+
+
+如果 `write pos` 追上 `checkpoint` ,表示**日志文件组**满了,这时候不能再写入新的 `redo log` 记录,`MySQL` 得停下来,清空一些记录,把 `checkpoint` 推进一下。
+
+
+
+### redo log 小结
+
+相信大家都知道 `redo log` 的作用和它的刷盘时机、存储形式。
+
+现在我们来思考一个问题: **只要每次把修改后的数据页直接刷盘不就好了,还有 `redo log` 什么事?**
+
+它们不都是刷盘么?差别在哪里?
+
+```java
+1 Byte = 8bit
+1 KB = 1024 Byte
+1 MB = 1024 KB
+1 GB = 1024 MB
+1 TB = 1024 GB
+```
+
+实际上,数据页大小是`16KB`,刷盘比较耗时,可能就修改了数据页里的几 `Byte` 数据,有必要把完整的数据页刷盘吗?
+
+而且数据页刷盘是随机写,因为一个数据页对应的位置可能在硬盘文件的随机位置,所以性能是很差。
+
+如果是写 `redo log`,一行记录可能就占几十 `Byte`,只包含表空间号、数据页号、磁盘文件偏移
+量、更新值,再加上是顺序写,所以刷盘速度很快。
+
+所以用 `redo log` 形式记录修改内容,性能会远远超过刷数据页的方式,这也让数据库的并发能力更强。
+
+> 其实内存的数据页在一定时机也会刷盘,我们把这称为页合并,讲 `Buffer Pool`的时候会对这块细说
+
+## binlog
+
+`redo log` 它是物理日志,记录内容是“在某个数据页上做了什么修改”,属于 `InnoDB` 存储引擎。
+
+而 `binlog` 是逻辑日志,记录内容是语句的原始逻辑,类似于“给 ID=2 这一行的 c 字段加 1”,属于`MySQL Server` 层。
+
+不管用什么存储引擎,只要发生了表数据更新,都会产生 `binlog` 日志。
+
+那 `binlog` 到底是用来干嘛的?
+
+可以说`MySQL`数据库的**数据备份、主备、主主、主从**都离不开`binlog`,需要依靠`binlog`来同步数据,保证数据一致性。
+
+
+
+`binlog`会记录所有涉及更新数据的逻辑操作,并且是顺序写。
+
+### 记录格式
+
+`binlog` 日志有三种格式,可以通过`binlog_format`参数指定。
+
+- **statement**
+- **row**
+- **mixed**
+
+指定`statement`,记录的内容是`SQL`语句原文,比如执行一条`update T set update_time=now() where id=1`,记录的内容如下。
+
+
+
+同步数据时,会执行记录的`SQL`语句,但是有个问题,`update_time=now()`这里会获取当前系统时间,直接执行会导致与原库的数据不一致。
+
+为了解决这种问题,我们需要指定为`row`,记录的内容不再是简单的`SQL`语句了,还包含操作的具体数据,记录内容如下。
+
+
+
+`row`格式记录的内容看不到详细信息,要通过`mysqlbinlog`工具解析出来。
+
+`update_time=now()`变成了具体的时间`update_time=1627112756247`,条件后面的@1、@2、@3 都是该行数据第 1 个~3 个字段的原始值(**假设这张表只有 3 个字段**)。
+
+这样就能保证同步数据的一致性,通常情况下都是指定为`row`,这样可以为数据库的恢复与同步带来更好的可靠性。
+
+但是这种格式,需要更大的容量来记录,比较占用空间,恢复与同步时会更消耗`IO`资源,影响执行速度。
+
+所以就有了一种折中的方案,指定为`mixed`,记录的内容是前两者的混合。
+
+`MySQL`会判断这条`SQL`语句是否可能引起数据不一致,如果是,就用`row`格式,否则就用`statement`格式。
+
+### 写入机制
+
+`binlog`的写入时机也非常简单,事务执行过程中,先把日志写到`binlog cache`,事务提交的时候,再把`binlog cache`写到`binlog`文件中。
+
+因为一个事务的`binlog`不能被拆开,无论这个事务多大,也要确保一次性写入,所以系统会给每个线程分配一个块内存作为`binlog cache`。
+
+我们可以通过`binlog_cache_size`参数控制单个线程 binlog cache 大小,如果存储内容超过了这个参数,就要暂存到磁盘(`Swap`)。
+
+`binlog`日志刷盘流程如下
+
+
+
+- **上图的 write,是指把日志写入到文件系统的 page cache,并没有把数据持久化到磁盘,所以速度比较快**
+- **上图的 fsync,才是将数据持久化到磁盘的操作**
+
+`write`和`fsync`的时机,可以由参数`sync_binlog`控制,默认是`0`。
+
+为`0`的时候,表示每次提交事务都只`write`,由系统自行判断什么时候执行`fsync`。
+
+
+
+虽然性能得到提升,但是机器宕机,`page cache`里面的 binglog 会丢失。
+
+为了安全起见,可以设置为`1`,表示每次提交事务都会执行`fsync`,就如同**binlog 日志刷盘流程**一样。
+
+最后还有一种折中方式,可以设置为`N(N>1)`,表示每次提交事务都`write`,但累积`N`个事务后才`fsync`。
+
+
+
+在出现`IO`瓶颈的场景里,将`sync_binlog`设置成一个比较大的值,可以提升性能。
+
+同样的,如果机器宕机,会丢失最近`N`个事务的`binlog`日志。
+
+## 两阶段提交
+
+`redo log`(重做日志)让`InnoDB`存储引擎拥有了崩溃恢复能力。
+
+`binlog`(归档日志)保证了`MySQL`集群架构的数据一致性。
+
+虽然它们都属于持久化的保证,但是侧重点不同。
+
+在执行更新语句过程,会记录`redo log`与`binlog`两块日志,以基本的事务为单位,`redo log`在事务执行过程中可以不断写入,而`binlog`只有在提交事务时才写入,所以`redo log`与`binlog`的写入时机不一样。
+
+
+
+回到正题,`redo log`与`binlog`两份日志之间的逻辑不一致,会出现什么问题?
+
+我们以`update`语句为例,假设`id=2`的记录,字段`c`值是`0`,把字段`c`值更新成`1`,`SQL`语句为`update T set c=1 where id=2`。
+
+假设执行过程中写完`redo log`日志后,`binlog`日志写期间发生了异常,会出现什么情况呢?
+
+
+
+由于`binlog`没写完就异常,这时候`binlog`里面没有对应的修改记录。因此,之后用`binlog`日志恢复数据时,就会少这一次更新,恢复出来的这一行`c`值是`0`,而原库因为`redo log`日志恢复,这一行`c`值是`1`,最终数据不一致。
+
+
+
+为了解决两份日志之间的逻辑一致问题,`InnoDB`存储引擎使用**两阶段提交**方案。
+
+原理很简单,将`redo log`的写入拆成了两个步骤`prepare`和`commit`,这就是**两阶段提交**。
+
+
+
+使用**两阶段提交**后,写入`binlog`时发生异常也不会有影响,因为`MySQL`根据`redo log`日志恢复数据时,发现`redo log`还处于`prepare`阶段,并且没有对应`binlog`日志,就会回滚该事务。
+
+
+
+再看一个场景,`redo log`设置`commit`阶段发生异常,那会不会回滚事务呢?
+
+
+
+并不会回滚事务,它会执行上图框住的逻辑,虽然`redo log`是处于`prepare`阶段,但是能通过事务`id`找到对应的`binlog`日志,所以`MySQL`认为是完整的,就会提交事务恢复数据。
+
+## undo log
+
+> 这部分内容为 JavaGuide 的补充:
+
+我们知道如果想要保证事务的原子性,就需要在异常发生时,对已经执行的操作进行**回滚**,在 MySQL 中,恢复机制是通过 **回滚日志(undo log)** 实现的,所有事务进行的修改都会先记录到这个回滚日志中,然后再执行相关的操作。如果执行过程中遇到异常的话,我们直接利用 **回滚日志** 中的信息将数据回滚到修改之前的样子即可!并且,回滚日志会先于数据持久化到磁盘上。这样就保证了即使遇到数据库突然宕机等情况,当用户再次启动数据库的时候,数据库还能够通过查询回滚日志来回滚将之前未完成的事务。
+
+另外,`MVCC` 的实现依赖于:**隐藏字段、Read View、undo log**。在内部实现中,`InnoDB` 通过数据行的 `DB_TRX_ID` 和 `Read View` 来判断数据的可见性,如不可见,则通过数据行的 `DB_ROLL_PTR` 找到 `undo log` 中的历史版本。每个事务读到的数据版本可能是不一样的,在同一个事务中,用户只能看到该事务创建 `Read View` 之前已经提交的修改和该事务本身做的修改
+
+## 总结
+
+> 这部分内容为 JavaGuide 的补充:
+
+MySQL InnoDB 引擎使用 **redo log(重做日志)** 保证事务的**持久性**,使用 **undo log(回滚日志)** 来保证事务的**原子性**。
+
+`MySQL`数据库的**数据备份、主备、主主、主从**都离不开`binlog`,需要依靠`binlog`来同步数据,保证数据一致性。
+
+## 站在巨人的肩膀上
+
+- 《MySQL 实战 45 讲》
+- 《从零开始带你成为 MySQL 实战优化高手》
+- 《MySQL 是怎样运行的:从根儿上理解 MySQL》
+- 《MySQL 技术 Innodb 存储引擎》
+
+## MySQL 好文推荐
+
+- [CURD 这么多年,你有了解过 MySQL 的架构设计吗?](https://mp.weixin.qq.com/s/R-1km7r0z3oWfwYQV8iiqA)
+- [浅谈 MySQL InnoDB 的内存组件](https://mp.weixin.qq.com/s/7Kab4IQsNcU_bZdbv_MuOg)
diff --git "a/docs/database/mysql/mysql\347\237\245\350\257\206\347\202\271&\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" "b/docs/database/mysql/mysql\347\237\245\350\257\206\347\202\271&\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md"
new file mode 100644
index 00000000000..b07247b20f5
--- /dev/null
+++ "b/docs/database/mysql/mysql\347\237\245\350\257\206\347\202\271&\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md"
@@ -0,0 +1,292 @@
+---
+title: MySQL知识点&面试题总结
+category: 数据库
+tag:
+ - MySQL
+ - 大厂面试
+---
+
+
+## MySQL 基础
+
+### 关系型数据库介绍
+
+顾名思义,关系型数据库就是一种建立在关系模型的基础上的数据库。关系模型表明了数据库中所存储的数据之间的联系(一对一、一对多、多对多)。
+
+关系型数据库中,我们的数据都被存放在了各种表中(比如用户表),表中的每一行就存放着一条数据(比如一个用户的信息)。
+
+
+
+大部分关系型数据库都使用 SQL 来操作数据库中的数据。并且,大部分关系型数据库都支持事务的四大特性(ACID)。
+
+**有哪些常见的关系型数据库呢?**
+
+MySQL、PostgreSQL、Oracle、SQL Server、SQLite(微信本地的聊天记录的存储就是用的 SQLite) ......。
+
+### MySQL 介绍
+
+
+
+**MySQL 是一种关系型数据库,主要用于持久化存储我们的系统中的一些数据比如用户信息。**
+
+由于 MySQL 是开源免费并且比较成熟的数据库,因此,MySQL 被大量使用在各种系统中。任何人都可以在 GPL(General Public License) 的许可下下载并根据个性化的需要对其进行修改。MySQL 的默认端口号是**3306**。
+
+## 存储引擎
+
+### 存储引擎相关的命令
+
+**查看 MySQL 提供的所有存储引擎**
+
+```sql
+mysql> show engines;
+```
+
+
+
+从上图我们可以查看出 MySQL 当前默认的存储引擎是 InnoDB,并且在 5.7 版本所有的存储引擎中只有 InnoDB 是事务性存储引擎,也就是说只有 InnoDB 支持事务。
+
+**查看 MySQL 当前默认的存储引擎**
+
+我们也可以通过下面的命令查看默认的存储引擎。
+
+```sql
+mysql> show variables like '%storage_engine%';
+```
+
+**查看表的存储引擎**
+
+```sql
+show table status like "table_name" ;
+```
+
+
+
+### MyISAM 和 InnoDB 的区别
+
+
+
+MySQL 5.5 之前,MyISAM 引擎是 MySQL 的默认存储引擎,可谓是风光一时。
+
+虽然,MyISAM 的性能还行,各种特性也还不错(比如全文索引、压缩、空间函数等)。但是,MyISAM 不支持事务和行级锁,而且最大的缺陷就是崩溃后无法安全恢复。
+
+5.5 版本之后,MySQL 引入了 InnoDB(事务性数据库引擎),MySQL 5.5 版本后默认的存储引擎为 InnoDB。小伙子,一定要记好这个 InnoDB ,你每次使用 MySQL 数据库都是用的这个存储引擎吧?
+
+言归正传!咱们下面还是来简单对比一下两者:
+
+**1.是否支持行级锁**
+
+MyISAM 只有表级锁(table-level locking),而 InnoDB 支持行级锁(row-level locking)和表级锁,默认为行级锁。
+
+也就说,MyISAM 一锁就是锁住了整张表,这在并发写的情况下是多么滴憨憨啊!这也是为什么 InnoDB 在并发写的时候,性能更牛皮了!
+
+**2.是否支持事务**
+
+MyISAM 不提供事务支持。
+
+InnoDB 提供事务支持,具有提交(commit)和回滚(rollback)事务的能力。
+
+**3.是否支持外键**
+
+MyISAM 不支持,而 InnoDB 支持。
+
+🌈 拓展一下:
+
+一般我们也是不建议在数据库层面使用外键的,应用层面可以解决。不过,这样会对数据的一致性造成威胁。具体要不要使用外键还是要根据你的项目来决定。
+
+**4.是否支持数据库异常崩溃后的安全恢复**
+
+MyISAM 不支持,而 InnoDB 支持。
+
+使用 InnoDB 的数据库在异常崩溃后,数据库重新启动的时候会保证数据库恢复到崩溃前的状态。这个恢复的过程依赖于 `redo log` 。
+
+🌈 拓展一下:
+
+- MySQL InnoDB 引擎使用 **redo log(重做日志)** 保证事务的**持久性**,使用 **undo log(回滚日志)** 来保证事务的**原子性**。
+- MySQL InnoDB 引擎通过 **锁机制**、**MVCC** 等手段来保证事务的隔离性( 默认支持的隔离级别是 **`REPEATABLE-READ`** )。
+- 保证了事务的持久性、原子性、隔离性之后,一致性才能得到保障。
+
+**5.是否支持 MVCC**
+
+MyISAM 不支持,而 InnoDB 支持。
+
+讲真,这个对比有点废话,毕竟 MyISAM 连行级锁都不支持。
+
+MVCC 可以看作是行级锁的一个升级,可以有效减少加锁操作,提供性能。
+
+### 关于 MyISAM 和 InnoDB 的选择问题
+
+大多数时候我们使用的都是 InnoDB 存储引擎,在某些读密集的情况下,使用 MyISAM 也是合适的。不过,前提是你的项目不介意 MyISAM 不支持事务、崩溃恢复等缺点(可是~我们一般都会介意啊!)。
+
+《MySQL 高性能》上面有一句话这样写到:
+
+> 不要轻易相信“MyISAM 比 InnoDB 快”之类的经验之谈,这个结论往往不是绝对的。在很多我们已知场景中,InnoDB 的速度都可以让 MyISAM 望尘莫及,尤其是用到了聚簇索引,或者需要访问的数据都可以放入内存的应用。
+
+一般情况下我们选择 InnoDB 都是没有问题的,但是某些情况下你并不在乎可扩展能力和并发能力,也不需要事务支持,也不在乎崩溃后的安全恢复问题的话,选择 MyISAM 也是一个不错的选择。但是一般情况下,我们都是需要考虑到这些问题的。
+
+因此,对于咱们日常开发的业务系统来说,你几乎找不到什么理由再使用 MyISAM 作为自己的 MySQL 数据库的存储引擎。
+
+## 锁机制与 InnoDB 锁算法
+
+**MyISAM 和 InnoDB 存储引擎使用的锁:**
+
+- MyISAM 采用表级锁(table-level locking)。
+- InnoDB 支持行级锁(row-level locking)和表级锁,默认为行级锁
+
+**表级锁和行级锁对比:**
+
+- **表级锁:** MySQL 中锁定 **粒度最大** 的一种锁,对当前操作的整张表加锁,实现简单,资源消耗也比较少,加锁快,不会出现死锁。其锁定粒度最大,触发锁冲突的概率最高,并发度最低,MyISAM 和 InnoDB 引擎都支持表级锁。
+- **行级锁:** MySQL 中锁定 **粒度最小** 的一种锁,只针对当前操作的行进行加锁。 行级锁能大大减少数据库操作的冲突。其加锁粒度最小,并发度高,但加锁的开销也最大,加锁慢,会出现死锁。
+
+**InnoDB 存储引擎的锁的算法有三种:**
+
+- Record lock:记录锁,单个行记录上的锁
+- Gap lock:间隙锁,锁定一个范围,不包括记录本身
+- Next-key lock:record+gap 临键锁,锁定一个范围,包含记录本身
+
+## 查询缓存
+
+执行查询语句的时候,会先查询缓存。不过,MySQL 8.0 版本后移除,因为这个功能不太实用
+
+`my.cnf` 加入以下配置,重启 MySQL 开启查询缓存
+
+```properties
+query_cache_type=1
+query_cache_size=600000
+```
+
+MySQL 执行以下命令也可以开启查询缓存
+
+```properties
+set global query_cache_type=1;
+set global query_cache_size=600000;
+```
+
+如上,**开启查询缓存后在同样的查询条件以及数据情况下,会直接在缓存中返回结果**。这里的查询条件包括查询本身、当前要查询的数据库、客户端协议版本号等一些可能影响结果的信息。(**查询缓存不命中的情况:(1)**)因此任何两个查询在任何字符上的不同都会导致缓存不命中。此外,(**查询缓存不命中的情况:(2)**)如果查询中包含任何用户自定义函数、存储函数、用户变量、临时表、MySQL 库中的系统表,其查询结果也不会被缓存。
+
+(**查询缓存不命中的情况:(3)**)**缓存建立之后**,MySQL 的查询缓存系统会跟踪查询中涉及的每张表,如果这些表(数据或结构)发生变化,那么和这张表相关的所有缓存数据都将失效。
+
+**缓存虽然能够提升数据库的查询性能,但是缓存同时也带来了额外的开销,每次查询后都要做一次缓存操作,失效后还要销毁。** 因此,开启查询缓存要谨慎,尤其对于写密集的应用来说更是如此。如果开启,要注意合理控制缓存空间大小,一般来说其大小设置为几十 MB 比较合适。此外,**还可以通过 sql_cache 和 sql_no_cache 来控制某个查询语句是否需要缓存:**
+
+```sql
+select sql_no_cache count(*) from usr;
+```
+
+## 事务
+
+### 何为事务?
+
+一言蔽之,**事务是逻辑上的一组操作,要么都执行,要么都不执行。**
+
+**可以简单举一个例子不?**
+
+事务最经典也经常被拿出来说例子就是转账了。假如小明要给小红转账 1000 元,这个转账会涉及到两个关键操作就是:
+
+1. 将小明的余额减少 1000 元
+2. 将小红的余额增加 1000 元。
+
+事务会把这两个操作就可以看成逻辑上的一个整体,这个整体包含的操作要么都成功,要么都要失败。
+
+这样就不会出现小明余额减少而小红的余额却并没有增加的情况。
+
+### 何为数据库事务?
+
+数据库事务在我们日常开发中接触的最多了。如果你的项目属于单体架构的话,你接触到的往往就是数据库事务了。
+
+平时,我们在谈论事务的时候,如果没有特指**分布式事务**,往往指的就是**数据库事务**。
+
+**那数据库事务有什么作用呢?**
+
+简单来说:数据库事务可以保证多个对数据库的操作(也就是 SQL 语句)构成一个逻辑上的整体。构成这个逻辑上的整体的这些数据库操作遵循:**要么全部执行成功,要么全部不执行** 。
+
+```sql
+# 开启一个事务
+START TRANSACTION;
+# 多条 SQL 语句
+SQL1,SQL2...
+## 提交事务
+COMMIT;
+```
+
+
+
+另外,关系型数据库(例如:`MySQL`、`SQL Server`、`Oracle` 等)事务都有 **ACID** 特性:
+
+
+
+### 何为 ACID 特性呢?
+
+1. **原子性**(`Atomicity`) : 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
+2. **一致性**(`Consistency`): 执行事务前后,数据保持一致,例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的;
+3. **隔离性**(`Isolation`): 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;
+4. **持久性**(`Durability`): 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
+
+**数据事务的实现原理呢?**
+
+我们这里以 MySQL 的 InnoDB 引擎为例来简单说一下。
+
+MySQL InnoDB 引擎使用 **redo log(重做日志)** 保证事务的**持久性**,使用 **undo log(回滚日志)** 来保证事务的**原子性**。
+
+MySQL InnoDB 引擎通过 **锁机制**、**MVCC** 等手段来保证事务的隔离性( 默认支持的隔离级别是 **`REPEATABLE-READ`** )。
+
+保证了事务的持久性、原子性、隔离性之后,一致性才能得到保障。
+
+### 并发事务带来哪些问题?
+
+在典型的应用程序中,多个事务并发运行,经常会操作相同的数据来完成各自的任务(多个用户对同一数据进行操作)。并发虽然是必须的,但可能会导致以下的问题。
+
+- **脏读(Dirty read):** 当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。
+- **丢失修改(Lost to modify):** 指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。 例如:事务 1 读取某表中的数据 A=20,事务 2 也读取 A=20,事务 1 修改 A=A-1,事务 2 也修改 A=A-1,最终结果 A=19,事务 1 的修改被丢失。
+- **不可重复读(Unrepeatable read):** 指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。
+- **幻读(Phantom read):** 幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。
+
+**不可重复读和幻读区别:**
+
+不可重复读的重点是修改比如多次读取一条记录发现其中某些列的值被修改,幻读的重点在于新增或者删除比如多次读取一条记录发现记录增多或减少了。
+
+### 事务隔离级别有哪些?
+
+SQL 标准定义了四个隔离级别:
+
+- **READ-UNCOMMITTED(读取未提交):** 最低的隔离级别,允许读取尚未提交的数据变更,**可能会导致脏读、幻读或不可重复读**。
+- **READ-COMMITTED(读取已提交):** 允许读取并发事务已经提交的数据,**可以阻止脏读,但是幻读或不可重复读仍有可能发生**。
+- **REPEATABLE-READ(可重复读):** 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,**可以阻止脏读和不可重复读,但幻读仍有可能发生**。
+- **SERIALIZABLE(可串行化):** 最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,**该级别可以防止脏读、不可重复读以及幻读**。
+
+---
+
+| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
+| :--------------: | :--: | :--------: | :--: |
+| READ-UNCOMMITTED | √ | √ | √ |
+| READ-COMMITTED | × | √ | √ |
+| REPEATABLE-READ | × | × | √ |
+| SERIALIZABLE | × | × | × |
+
+### MySQL 的默认隔离级别是什么?
+
+MySQL InnoDB 存储引擎的默认支持的隔离级别是 **REPEATABLE-READ(可重读)**。我们可以通过`SELECT @@tx_isolation;`命令来查看,MySQL 8.0 该命令改为`SELECT @@transaction_isolation;`
+
+```sql
+mysql> SELECT @@tx_isolation;
++-----------------+
+| @@tx_isolation |
++-----------------+
+| REPEATABLE-READ |
++-----------------+
+```
+
+~~这里需要注意的是:与 SQL 标准不同的地方在于 InnoDB 存储引擎在 **REPEATABLE-READ(可重读)** 事务隔离级别下使用的是 Next-Key Lock 锁算法,因此可以避免幻读的产生,这与其他数据库系统(如 SQL Server)是不同的。所以说 InnoDB 存储引擎的默认支持的隔离级别是 **REPEATABLE-READ(可重读)** 已经可以完全保证事务的隔离性要求,即达到了 SQL 标准的 **SERIALIZABLE(可串行化)** 隔离级别。~~
+
+🐛 问题更正:**MySQL InnoDB 的 REPEATABLE-READ(可重读)并不保证避免幻读,需要应用使用加锁读来保证。而这个加锁度使用到的机制就是 Next-Key Locks。**
+
+因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是 **READ-COMMITTED(读取提交内容)** ,但是你要知道的是 InnoDB 存储引擎默认使用 **REPEATABLE-READ(可重读)** 并不会有任何性能损失。
+
+InnoDB 存储引擎在 **分布式事务** 的情况下一般会用到 **SERIALIZABLE(可串行化)** 隔离级别。
+
+🌈 拓展一下(以下内容摘自《MySQL 技术内幕:InnoDB 存储引擎(第 2 版)》7.7 章):
+
+> InnoDB 存储引擎提供了对 XA 事务的支持,并通过 XA 事务来支持分布式事务的实现。分布式事务指的是允许多个独立的事务资源(transactional resources)参与到一个全局的事务中。事务资源通常是关系型数据库系统,但也可以是其他类型的资源。全局事务要求在其中的所有参与的事务要么都提交,要么都回滚,这对于事务原有的 ACID 要求又有了提高。另外,在使用分布式事务时,InnoDB 存储引擎的事务隔离级别必须设置为 SERIALIZABLE。
+
+## 参考
+
+- 《高性能 MySQL》
+- https://www.omnisci.com/technical-glossary/relational-database
diff --git a/docs/database/mysql/some-thoughts-on-database-storage-time.md b/docs/database/mysql/some-thoughts-on-database-storage-time.md
new file mode 100644
index 00000000000..ccad646e5d4
--- /dev/null
+++ b/docs/database/mysql/some-thoughts-on-database-storage-time.md
@@ -0,0 +1,162 @@
+---
+title: 关于数据库中如何存储时间的一点思考
+category: 数据库
+tag:
+ - MySQL
+---
+
+
+
+我们平时开发中不可避免的就是要存储时间,比如我们要记录操作表中这条记录的时间、记录转账的交易时间、记录出发时间等等。你会发现时间这个东西与我们开发的联系还是非常紧密的,用的好与不好会给我们的业务甚至功能带来很大的影响。所以,我们有必要重新出发,好好认识一下这个东西。
+
+这是一篇短小精悍的文章,仔细阅读一定能学到不少东西!
+
+### 1.切记不要用字符串存储日期
+
+我记得我在大学的时候就这样干过,而且现在很多对数据库不太了解的新手也会这样干,可见,这种存储日期的方式的优点还是有的,就是简单直白,容易上手。
+
+但是,这是不正确的做法,主要会有下面两个问题:
+
+1. 字符串占用的空间更大!
+2. 字符串存储的日期效率比较低(逐个字符进行比对),无法用日期相关的 API 进行计算和比较。
+
+### 2.Datetime 和 Timestamp 之间抉择
+
+Datetime 和 Timestamp 是 MySQL 提供的两种比较相似的保存时间的数据类型。他们两者究竟该如何选择呢?
+
+**通常我们都会首选 Timestamp。** 下面说一下为什么这样做!
+
+#### 2.1 DateTime 类型没有时区信息
+
+**DateTime 类型是没有时区信息的(时区无关)** ,DateTime 类型保存的时间都是当前会话所设置的时区对应的时间。这样就会有什么问题呢?当你的时区更换之后,比如你的服务器更换地址或者更换客户端连接时区设置的话,就会导致你从数据库中读出的时间错误。不要小看这个问题,很多系统就是因为这个问题闹出了很多笑话。
+
+**Timestamp 和时区有关**。Timestamp 类型字段的值会随着服务器时区的变化而变化,自动换算成相应的时间,说简单点就是在不同时区,查询到同一个条记录此字段的值会不一样。
+
+下面实际演示一下!
+
+建表 SQL 语句:
+
+```sql
+CREATE TABLE `time_zone_test` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT,
+ `date_time` datetime DEFAULT NULL,
+ `time_stamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+```
+
+插入数据:
+
+```sql
+INSERT INTO time_zone_test(date_time,time_stamp) VALUES(NOW(),NOW());
+```
+
+查看数据:
+
+```sql
+select date_time,time_stamp from time_zone_test;
+```
+
+结果:
+
+```
++---------------------+---------------------+
+| date_time | time_stamp |
++---------------------+---------------------+
+| 2020-01-11 09:53:32 | 2020-01-11 09:53:32 |
++---------------------+---------------------+
+```
+
+现在我们运行
+
+修改当前会话的时区:
+
+```sql
+set time_zone='+8:00';
+```
+
+再次查看数据:
+
+```
++---------------------+---------------------+
+| date_time | time_stamp |
++---------------------+---------------------+
+| 2020-01-11 09:53:32 | 2020-01-11 17:53:32 |
++---------------------+---------------------+
+```
+
+**扩展:一些关于 MySQL 时区设置的一个常用 sql 命令**
+
+```sql
+# 查看当前会话时区
+SELECT @@session.time_zone;
+# 设置当前会话时区
+SET time_zone = 'Europe/Helsinki';
+SET time_zone = "+00:00";
+# 数据库全局时区设置
+SELECT @@global.time_zone;
+# 设置全局时区
+SET GLOBAL time_zone = '+8:00';
+SET GLOBAL time_zone = 'Europe/Helsinki';
+```
+
+#### 2.2 DateTime 类型耗费空间更大
+
+Timestamp 只需要使用 4 个字节的存储空间,但是 DateTime 需要耗费 8 个字节的存储空间。但是,这样同样造成了一个问题,Timestamp 表示的时间范围更小。
+
+- DateTime :1000-01-01 00:00:00 ~ 9999-12-31 23:59:59
+- Timestamp: 1970-01-01 00:00:01 ~ 2037-12-31 23:59:59
+
+> Timestamp 在不同版本的 MySQL 中有细微差别。
+
+### 3 再看 MySQL 日期类型存储空间
+
+下图是 MySQL 5.6 版本中日期类型所占的存储空间:
+
+
+
+可以看出 5.6.4 之后的 MySQL 多出了一个需要 0 ~ 3 字节的小数位。DateTime 和 Timestamp 会有几种不同的存储空间占用。
+
+为了方便,本文我们还是默认 Timestamp 只需要使用 4 个字节的存储空间,但是 DateTime 需要耗费 8 个字节的存储空间。
+
+### 4.数值型时间戳是更好的选择吗?
+
+很多时候,我们也会使用 int 或者 bigint 类型的数值也就是时间戳来表示时间。
+
+这种存储方式的具有 Timestamp 类型的所具有一些优点,并且使用它的进行日期排序以及对比等操作的效率会更高,跨系统也很方便,毕竟只是存放的数值。缺点也很明显,就是数据的可读性太差了,你无法直观的看到具体时间。
+
+时间戳的定义如下:
+
+> 时间戳的定义是从一个基准时间开始算起,这个基准时间是「1970-1-1 00:00:00 +0:00」,从这个时间开始,用整数表示,以秒计时,随着时间的流逝这个时间整数不断增加。这样一来,我只需要一个数值,就可以完美地表示时间了,而且这个数值是一个绝对数值,即无论的身处地球的任何角落,这个表示时间的时间戳,都是一样的,生成的数值都是一样的,并且没有时区的概念,所以在系统的中时间的传输中,都不需要进行额外的转换了,只有在显示给用户的时候,才转换为字符串格式的本地时间。
+
+数据库中实际操作:
+
+```sql
+mysql> select UNIX_TIMESTAMP('2020-01-11 09:53:32');
++---------------------------------------+
+| UNIX_TIMESTAMP('2020-01-11 09:53:32') |
++---------------------------------------+
+| 1578707612 |
++---------------------------------------+
+1 row in set (0.00 sec)
+
+mysql> select FROM_UNIXTIME(1578707612);
++---------------------------+
+| FROM_UNIXTIME(1578707612) |
++---------------------------+
+| 2020-01-11 09:53:32 |
++---------------------------+
+1 row in set (0.01 sec)
+```
+
+### 5.总结
+
+MySQL 中时间到底怎么存储才好?Datetime?Timestamp? 数值保存的时间戳?
+
+好像并没有一个银弹,很多程序员会觉得数值型时间戳是真的好,效率又高还各种兼容,但是很多人又觉得它表现的不够直观。这里插一嘴,《高性能 MySQL 》这本神书的作者就是推荐 Timestamp,原因是数值表示时间不够直观。下面是原文:
+
+
+
+每种方式都有各自的优势,根据实际场景才是王道。下面再对这三种方式做一个简单的对比,以供大家实际开发中选择正确的存放时间的数据类型:
+
+
\ No newline at end of file
diff --git "a/docs/database/\344\272\213\345\212\241\351\232\224\347\246\273\347\272\247\345\210\253(\345\233\276\346\226\207\350\257\246\350\247\243).md" b/docs/database/mysql/transaction-isolation-level.md
similarity index 80%
rename from "docs/database/\344\272\213\345\212\241\351\232\224\347\246\273\347\272\247\345\210\253(\345\233\276\346\226\207\350\257\246\350\247\243).md"
rename to docs/database/mysql/transaction-isolation-level.md
index 2c8ef1c14e8..4be2f5fde44 100644
--- "a/docs/database/\344\272\213\345\212\241\351\232\224\347\246\273\347\272\247\345\210\253(\345\233\276\346\226\207\350\257\246\350\247\243).md"
+++ b/docs/database/mysql/transaction-isolation-level.md
@@ -1,20 +1,12 @@
-> 本文由 [SnailClimb](https://github.com/Snailclimb) 和 [BugSpeak](https://github.com/BugSpeak) 共同完成。
-
-
-- [事务隔离级别(图文详解)](#事务隔离级别图文详解)
- - [什么是事务?](#什么是事务)
- - [事务的特性(ACID)](#事务的特性acid)
- - [并发事务带来的问题](#并发事务带来的问题)
- - [事务隔离级别](#事务隔离级别)
- - [实际情况演示](#实际情况演示)
- - [脏读(读未提交)](#脏读读未提交)
- - [避免脏读(读已提交)](#避免脏读读已提交)
- - [不可重复读](#不可重复读)
- - [可重复读](#可重复读)
- - [防止幻读(可重复读)](#防止幻读可重复读)
- - [参考](#参考)
-
-
+---
+title: 事务隔离级别(图文详解)
+category: 数据库
+tag:
+ - MySQL
+---
+
+
+> 本文由 [SnailClimb](https://github.com/Snailclimb) 和 [guang19](https://github.com/guang19) 共同完成。
## 事务隔离级别(图文详解)
@@ -69,7 +61,7 @@
| REPEATABLE-READ | × | × | √ |
| SERIALIZABLE | × | × | × |
-MySQL InnoDB 存储引擎的默认支持的隔离级别是 **REPEATABLE-READ(可重读)**。我们可以通过`SELECT @@tx_isolation;`命令来查看,MySQL 8.0 该命令改为`SELECT @@transaction_isolation;`
+MySQL InnoDB 存储引擎的默认支持的隔离级别是 **REPEATABLE-READ(可重读)**。我们可以通过`SELECT @@tx_isolation;`命令来查看,MySQL 8.0 该命令改为`SELECT @@transaction_isolation;`
```sql
mysql> SELECT @@tx_isolation;
@@ -80,11 +72,17 @@ mysql> SELECT @@tx_isolation;
+-----------------+
```
-这里需要注意的是:与 SQL 标准不同的地方在于InnoDB 存储引擎在 **REPEATABLE-READ(可重读)**事务隔离级别下使用的是Next-Key Lock 锁算法,因此可以避免幻读的产生,这与其他数据库系统(如 SQL Server)是不同的。所以说InnoDB 存储引擎的默认支持的隔离级别是 **REPEATABLE-READ(可重读)** 已经可以完全保证事务的隔离性要求,即达到了 SQL标准的**SERIALIZABLE(可串行化)**隔离级别。
+~~这里需要注意的是:与 SQL 标准不同的地方在于 InnoDB 存储引擎在 **REPEATABLE-READ(可重读)** 事务隔离级别下使用的是 Next-Key Lock 锁算法,因此可以避免幻读的产生,这与其他数据库系统(如 SQL Server)是不同的。所以说 InnoDB 存储引擎的默认支持的隔离级别是 **REPEATABLE-READ(可重读)** 已经可以完全保证事务的隔离性要求,即达到了 SQL 标准的 **SERIALIZABLE(可串行化)** 隔离级别。~~
+
+🐛 问题更正:**MySQL InnoDB 的 REPEATABLE-READ(可重读)并不保证避免幻读,需要应用使用加锁读来保证。而这个加锁度使用到的机制就是 Next-Key Locks。**
+
+因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是 **READ-COMMITTED(读取提交内容)** ,但是你要知道的是 InnoDB 存储引擎默认使用 **REPEATABLE-READ(可重读)** 并不会有任何性能损失。
+
+InnoDB 存储引擎在 **分布式事务** 的情况下一般会用到 **SERIALIZABLE(可串行化)** 隔离级别。
-因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是**READ-COMMITTED(读取提交内容):**,但是你要知道的是InnoDB 存储引擎默认使用 **REPEATABLE-READ(可重读)**并不会有任何性能损失。
+🌈 拓展一下(以下内容摘自《MySQL 技术内幕:InnoDB 存储引擎(第 2 版)》7.7 章):
-InnoDB 存储引擎在 **分布式事务** 的情况下一般会用到**SERIALIZABLE(可串行化)**隔离级别。
+> InnoDB 存储引擎提供了对 XA 事务的支持,并通过 XA 事务来支持分布式事务的实现。分布式事务指的是允许多个独立的事务资源(transactional resources)参与到一个全局的事务中。事务资源通常是关系型数据库系统,但也可以是其他类型的资源。全局事务要求在其中的所有参与的事务要么都提交,要么都回滚,这对于事务原有的 ACID 要求又有了提高。另外,在使用分布式事务时,InnoDB 存储引擎的事务隔离级别必须设置为 SERIALIZABLE。
### 实际情况演示
diff --git a/docs/database/redis/3-commonly-used-cache-read-and-write-strategies.md b/docs/database/redis/3-commonly-used-cache-read-and-write-strategies.md
new file mode 100644
index 00000000000..841067861f2
--- /dev/null
+++ b/docs/database/redis/3-commonly-used-cache-read-and-write-strategies.md
@@ -0,0 +1,116 @@
+---
+title: 3种常用的缓存读写策略
+category: 数据库
+tag:
+ - Redis
+---
+
+
+看到很多小伙伴简历上写了“**熟练使用缓存**”,但是被我问到“**缓存常用的3种读写策略**”的时候却一脸懵逼。
+
+在我看来,造成这个问题的原因是我们在学习 Redis 的时候,可能只是简单了写一些 Demo,并没有去关注缓存的读写策略,或者说压根不知道这回事。
+
+但是,搞懂3种常见的缓存读写策略对于实际工作中使用缓存以及面试中被问到缓存都是非常有帮助的!
+
+下面我会简单介绍一下自己对于这 3 种缓存读写策略的理解。
+
+另外,**这3 种缓存读写策略各有优劣,不存在最佳,需要我们根据具体的业务场景选择更适合的。**
+
+*个人能力有限。如果文章有任何需要补充/完善/修改的地方,欢迎在评论区指出,共同进步!——爱你们的 Guide 哥*
+
+### Cache Aside Pattern(旁路缓存模式)
+
+**Cache Aside Pattern 是我们平时使用比较多的一个缓存读写模式,比较适合读请求比较多的场景。**
+
+Cache Aside Pattern 中服务端需要同时维系 DB 和 cache,并且是以 DB 的结果为准。
+
+下面我们来看一下这个策略模式下的缓存读写步骤。
+
+**写** :
+
+- 先更新 DB
+- 然后直接删除 cache 。
+
+简单画了一张图帮助大家理解写的步骤。
+
+
+
+**读** :
+
+- 从 cache 中读取数据,读取到就直接返回
+- cache中读取不到的话,就从 DB 中读取数据返回
+- 再把数据放到 cache 中。
+
+简单画了一张图帮助大家理解读的步骤。
+
+
+
+
+你仅仅了解了上面这些内容的话是远远不够的,我们还要搞懂其中的原理。
+
+比如说面试官很可能会追问:“**在写数据的过程中,可以先删除 cache ,后更新 DB 么?**”
+
+**答案:** 那肯定是不行的!因为这样可能会造成**数据库(DB)和缓存(Cache)数据不一致**的问题。为什么呢?比如说请求1 先写数据A,请求2随后读数据A的话就很有可能产生数据不一致性的问题。这个过程可以简单描述为:
+
+> 请求1先把cache中的A数据删除 -> 请求2从DB中读取数据->请求1再把DB中的A数据更新。
+
+当你这样回答之后,面试官可能会紧接着就追问:“**在写数据的过程中,先更新DB,后删除cache就没有问题了么?**”
+
+**答案:** 理论上来说还是可能会出现数据不一致性的问题,不过概率非常小,因为缓存的写入速度是比数据库的写入速度快很多!
+
+比如请求1先读数据 A,请求2随后写数据A,并且数据A不在缓存中的话也有可能产生数据不一致性的问题。这个过程可以简单描述为:
+
+> 请求1从DB读数据A->请求2写更新数据 A 到数据库并把删除cache中的A数据->请求1将数据A写入cache。
+
+现在我们再来分析一下 **Cache Aside Pattern 的缺陷**。
+
+**缺陷1:首次请求数据一定不在 cache 的问题**
+
+解决办法:可以将热点数据可以提前放入cache 中。
+
+**缺陷2:写操作比较频繁的话导致cache中的数据会被频繁被删除,这样会影响缓存命中率 。**
+
+解决办法:
+
+- 数据库和缓存数据强一致场景 :更新DB的时候同样更新cache,不过我们需要加一个锁/分布式锁来保证更新cache的时候不存在线程安全问题。
+- 可以短暂地允许数据库和缓存数据不一致的场景 :更新DB的时候同样更新cache,但是给缓存加一个比较短的过期时间,这样的话就可以保证即使数据不一致的话影响也比较小。
+
+### Read/Write Through Pattern(读写穿透)
+
+Read/Write Through Pattern 中服务端把 cache 视为主要数据存储,从中读取数据并将数据写入其中。cache 服务负责将此数据读取和写入 DB,从而减轻了应用程序的职责。
+
+这种缓存读写策略小伙伴们应该也发现了在平时在开发过程中非常少见。抛去性能方面的影响,大概率是因为我们经常使用的分布式缓存 Redis 并没有提供 cache 将数据写入DB的功能。
+
+**写(Write Through):**
+
+- 先查 cache,cache 中不存在,直接更新 DB。
+- cache 中存在,则先更新 cache,然后 cache 服务自己更新 DB(**同步更新 cache 和 DB**)。
+
+简单画了一张图帮助大家理解写的步骤。
+
+
+
+**读(Read Through):**
+
+- 从 cache 中读取数据,读取到就直接返回 。
+- 读取不到的话,先从 DB 加载,写入到 cache 后返回响应。
+
+简单画了一张图帮助大家理解读的步骤。
+
+
+
+Read-Through Pattern 实际只是在 Cache-Aside Pattern 之上进行了封装。在 Cache-Aside Pattern 下,发生读请求的时候,如果 cache 中不存在对应的数据,是由客户端自己负责把数据写入 cache,而 Read Through Pattern 则是 cache 服务自己来写入缓存的,这对客户端是透明的。
+
+和 Cache Aside Pattern 一样, Read-Through Pattern 也有首次请求数据一定不再 cache 的问题,对于热点数据可以提前放入缓存中。
+
+### Write Behind Pattern(异步缓存写入)
+
+Write Behind Pattern 和 Read/Write Through Pattern 很相似,两者都是由 cache 服务来负责 cache 和 DB 的读写。
+
+但是,两个又有很大的不同:**Read/Write Through 是同步更新 cache 和 DB,而 Write Behind Caching 则是只更新缓存,不直接更新 DB,而是改为异步批量的方式来更新 DB。**
+
+很明显,这种方式对数据一致性带来了更大的挑战,比如cache数据可能还没异步更新DB的话,cache服务可能就就挂掉了。
+
+这种策略在我们平时开发过程中也非常非常少见,但是不代表它的应用场景少,比如消息队列中消息的异步写入磁盘、MySQL 的 InnoDB Buffer Pool 机制都用到了这种策略。
+
+Write Behind Pattern 下 DB 的写性能非常高,非常适合一些数据经常变化又对数据一致性要求没那么高的场景,比如浏览量、点赞量。
diff --git a/docs/database/redis/images/redis-all/redis-list.drawio b/docs/database/redis/images/redis-all/redis-list.drawio
new file mode 100644
index 00000000000..afa767154b7
--- /dev/null
+++ b/docs/database/redis/images/redis-all/redis-list.drawio
@@ -0,0 +1 @@
+7VlNc5swFPw1PiaDENjmmNjpx6Gdjt1J2lNHAzKoEYgRcmzn11cYyRgJT1w3DnScU3grIaHd5b2HM4CTdP2Rozz5wiJMB64TrQdwOnBdEEAo/5TIpkICx6mAmJNITaqBOXnGCtTTliTCRWOiYIwKkjfBkGUZDkUDQ5yzVXPagtHmrjmKsQXMQ0Rt9IFEIqnQsTuq8U+YxIneGQyDaiRFerI6SZGgiK32IHg3gBPOmKiu0vUE05I8zUt134cDo7sH4zgTx9wwu/+V/H4g/myezsD35/vZ56/ulVrlCdGlOrB6WLHRDHC2zCJcLgIG8HaVEIHnOQrL0ZXUXGKJSKkaLh6xCBMVLFgmlKJgJGO1F+YCrw8eAuyokZ7CLMWCb+QUdcOVp9hUdnKhile1OL62WLInzEhhSPkh3i1dUyYvFGt/waBrMbi9BD3lUfPm2bwFLbT556INttPWc/sB035d0+i10wj7TaM77hmNw5fTIM6im7KeyCikqChI2ORMHp1vfsjA0cHPMrh2fR1P1/uj042KjiAbR1aRMqiWVRHxGIuXUr0tSSNxHqacY4oEeWo+RpsOaodvjMgHrPM2gKbk4+YaBVvyEKvb9suYuZLpHdcwRUWEtdDWF7tzn26V0f9ula4s4A0N4cbOtX+aB3zfWupNPaCb01cxAXjPF0cofGq68AJjIT8wXXdusxzRY/fbLJ0VDbPbOj1j2PXnrVOG/Z3A82WRWFaQXZUwujHB2SOeMMq4RDKW4VJLQqkBIUrirHSQVA9L/Lbs0Yj8lL1RAymJonKb1vavbhCP9M0/dYA+OFDF95zltTjLLPav1gECu5PmOcvf5aloD7qWx7fkoRf8+siiYggEuhbI/oSil/v+2Pp0/gLZ3y18u20/BTqDJp5O8VoSp2tJxvYrgxcXpIjV351PERnWv7RXPV39/wp49wc=
\ No newline at end of file
diff --git a/docs/database/redis/images/redis-all/redis-list.png b/docs/database/redis/images/redis-all/redis-list.png
new file mode 100644
index 00000000000..4fb4e36cb49
Binary files /dev/null and b/docs/database/redis/images/redis-all/redis-list.png differ
diff --git a/docs/database/redis/images/redis-all/redis-rollBack.png b/docs/database/redis/images/redis-all/redis-rollBack.png
new file mode 100644
index 00000000000..91f7f46d66d
Binary files /dev/null and b/docs/database/redis/images/redis-all/redis-rollBack.png differ
diff --git a/docs/database/redis/images/redis-all/redis-vs-memcached.png b/docs/database/redis/images/redis-all/redis-vs-memcached.png
new file mode 100644
index 00000000000..23844d67e6f
Binary files /dev/null and b/docs/database/redis/images/redis-all/redis-vs-memcached.png differ
diff --git a/docs/database/redis/images/redis-all/redis4.0-more-thread.png b/docs/database/redis/images/redis-all/redis4.0-more-thread.png
new file mode 100644
index 00000000000..e7e19e52e17
Binary files /dev/null and b/docs/database/redis/images/redis-all/redis4.0-more-thread.png differ
diff --git "a/docs/database/redis/images/redis-all/redis\344\272\213\344\273\266\345\244\204\347\220\206\345\231\250.png" "b/docs/database/redis/images/redis-all/redis\344\272\213\344\273\266\345\244\204\347\220\206\345\231\250.png"
new file mode 100644
index 00000000000..fc280fffaba
Binary files /dev/null and "b/docs/database/redis/images/redis-all/redis\344\272\213\344\273\266\345\244\204\347\220\206\345\231\250.png" differ
diff --git "a/docs/database/redis/images/redis-all/redis\344\272\213\345\212\241.png" "b/docs/database/redis/images/redis-all/redis\344\272\213\345\212\241.png"
new file mode 100644
index 00000000000..eb0c404cafd
Binary files /dev/null and "b/docs/database/redis/images/redis-all/redis\344\272\213\345\212\241.png" differ
diff --git "a/docs/database/redis/images/redis-all/redis\350\277\207\346\234\237\346\227\266\351\227\264.png" "b/docs/database/redis/images/redis-all/redis\350\277\207\346\234\237\346\227\266\351\227\264.png"
new file mode 100644
index 00000000000..27df6ead8e4
Binary files /dev/null and "b/docs/database/redis/images/redis-all/redis\350\277\207\346\234\237\346\227\266\351\227\264.png" differ
diff --git a/docs/database/redis/images/redis-all/try-redis.png b/docs/database/redis/images/redis-all/try-redis.png
new file mode 100644
index 00000000000..cd21a6518e4
Binary files /dev/null and b/docs/database/redis/images/redis-all/try-redis.png differ
diff --git a/docs/database/redis/images/redis-all/what-is-redis.png b/docs/database/redis/images/redis-all/what-is-redis.png
new file mode 100644
index 00000000000..913881ac6cf
Binary files /dev/null and b/docs/database/redis/images/redis-all/what-is-redis.png differ
diff --git "a/docs/database/redis/images/redis-all/\344\275\277\347\224\250\347\274\223\345\255\230\344\271\213\345\220\216.png" "b/docs/database/redis/images/redis-all/\344\275\277\347\224\250\347\274\223\345\255\230\344\271\213\345\220\216.png"
new file mode 100644
index 00000000000..2c73bd90276
Binary files /dev/null and "b/docs/database/redis/images/redis-all/\344\275\277\347\224\250\347\274\223\345\255\230\344\271\213\345\220\216.png" differ
diff --git "a/docs/database/redis/images/redis-all/\345\212\240\345\205\245\345\270\203\351\232\206\350\277\207\346\273\244\345\231\250\345\220\216\347\232\204\347\274\223\345\255\230\345\244\204\347\220\206\346\265\201\347\250\213.png" "b/docs/database/redis/images/redis-all/\345\212\240\345\205\245\345\270\203\351\232\206\350\277\207\346\273\244\345\231\250\345\220\216\347\232\204\347\274\223\345\255\230\345\244\204\347\220\206\346\265\201\347\250\213.png"
new file mode 100644
index 00000000000..a2c2ed6906f
Binary files /dev/null and "b/docs/database/redis/images/redis-all/\345\212\240\345\205\245\345\270\203\351\232\206\350\277\207\346\273\244\345\231\250\345\220\216\347\232\204\347\274\223\345\255\230\345\244\204\347\220\206\346\265\201\347\250\213.png" differ
diff --git "a/docs/database/redis/images/redis-all/\345\215\225\344\275\223\346\236\266\346\236\204.png" "b/docs/database/redis/images/redis-all/\345\215\225\344\275\223\346\236\266\346\236\204.png"
new file mode 100644
index 00000000000..648a404af8c
Binary files /dev/null and "b/docs/database/redis/images/redis-all/\345\215\225\344\275\223\346\236\266\346\236\204.png" differ
diff --git "a/docs/database/redis/images/redis-all/\347\274\223\345\255\230\347\232\204\345\244\204\347\220\206\346\265\201\347\250\213.png" "b/docs/database/redis/images/redis-all/\347\274\223\345\255\230\347\232\204\345\244\204\347\220\206\346\265\201\347\250\213.png"
new file mode 100644
index 00000000000..11860ae1f02
Binary files /dev/null and "b/docs/database/redis/images/redis-all/\347\274\223\345\255\230\347\232\204\345\244\204\347\220\206\346\265\201\347\250\213.png" differ
diff --git "a/docs/database/redis/images/redis-all/\347\274\223\345\255\230\347\251\277\351\200\217\346\203\205\345\206\265.png" "b/docs/database/redis/images/redis-all/\347\274\223\345\255\230\347\251\277\351\200\217\346\203\205\345\206\265.png"
new file mode 100644
index 00000000000..e7298c15ed6
Binary files /dev/null and "b/docs/database/redis/images/redis-all/\347\274\223\345\255\230\347\251\277\351\200\217\346\203\205\345\206\265.png" differ
diff --git "a/docs/database/redis/images/redis-all/\351\233\206\344\270\255\345\274\217\347\274\223\345\255\230\346\236\266\346\236\204.png" "b/docs/database/redis/images/redis-all/\351\233\206\344\270\255\345\274\217\347\274\223\345\255\230\346\236\266\346\236\204.png"
new file mode 100644
index 00000000000..5aff414baa4
Binary files /dev/null and "b/docs/database/redis/images/redis-all/\351\233\206\344\270\255\345\274\217\347\274\223\345\255\230\346\236\266\346\236\204.png" differ
diff --git "a/docs/database/redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-read.drawio" "b/docs/database/redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-read.drawio"
new file mode 100644
index 00000000000..bc4c6d0cca7
--- /dev/null
+++ "b/docs/database/redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-read.drawio"
@@ -0,0 +1 @@
+7Vpbc+o2EP41ekzHl/j2iAmk02nmdA6dpn3qCFsYTWSLY0Qg/fWVbMnYluE4B4jTQh4Se6WVVt+32pU2BvY43T3mcLV8ojEiwDLiHbAfgGWZluPzP0LyVkoCwygFSY5j2WkvmOF/kBSqbhsco3WjI6OUMLxqCiOaZShiDRnMc7ptdltQ0px1BROkCWYRJLr0GcdsWUp9y9vLf0Y4WaqZTTcoW1KoOsuVrJcwptuayJ4Ae5xTysqndDdGRICncCn1pgdaK8NylLE+Ck/Pf/xthHPraf7NePliLeLZ1+c7OcorJBu5YGkse1MI5HSTxUgMYgI73C4xQ7MVjETrlnPOZUuWEtm8wISMKaF5oWsvFgsrirh8zXL6gmotsTt3HVe0KFiEekRTzLs/GPw5IXC9ls/SSpQztDu4fLMClXsjoili+RvvIhVs5VHSEX3fKd+3e1pNxdWyRqkrZVB6UlINvQebP0i834G9db3YW0Njb2vYx5BBDX++ZNYEuQlmRjN0BHlIcJJxWcRxQrwxFChiHlxGsiHFcSzm6qR2T75RTaxiUME3zZgMmFbQIq1O5jkIvG8RqN5rBLod/NmX4u9e4y8TVN3468dflYWH4s+5mtjnGJ8t77jXi/3gece75Z13EWi3845OYFfcUjyfnT//lndO4W/wvBNcTexzg8+Wd9Ql4BrBHzzxmPpN/5Z5jjDoWZ/sxmPq5YJb6nkHgYOnHlOvOfxfw5/vf7rcoxcMrgb84XOPftu/5Z4jDAbtcunguUcvGdxyzzsIHD736HUHjTyUxSPxDzNBgIBGANKIeC38mgjbXIJ2mP0pe4vnvwSiPzny7WEnAS5e3hpoo1j7L1wLa24q3eQROrbIbk5qmDsdmCtZjghk+LVpRhcRcobfKOYGHiw1BSrjqCFK86XWns4eA7VqGQzmCWLaQIVfVMs+wVX0Esd/zFWGcgG/vevbzPV1gXYG/3AX6FElublAF3Nu+9Lxo1GgXUDQBrqwC6h1NFzAJUV2t/bK7reN+HwhBJN7EPrAD8DEAf4U+GPxwNcQmGASgMAG4QRMfDAywcivqdVdSgmF59ytC9cZ8Q62vdrVNdyknJDP8wACp3N4UnadClNlfymM8esJk8pB5vnpY3DrCluU9PSTlBSd9RBVv7IcOi/Vdrptn+f8ZPt+w/s9Vz8/OeZHHqAsvXz2PZcSFN5JNoQ/ELRgh/1hvYJZH68yne7twPcf3w5jsQt49uD37ckUhGMQjGYZxGRMcDrnnWEqSM7m61XTFcvZj3toKRXmnGDnBTCrBQQHjMYFFHzhngxFoVetu/z9iNlyM6/w+QW+wkfxkdkPwcGlJSK99/HF92e9vNDOxEKJ8hExEzsrEK8EzhEJYfSSFGO3AwxXrl3VjOKn0qJ5jPIOjSlMMREzPMIcpjSLlRkSGENS/aUyxTNKPiOcJb/TlTwiSMGvwgsakpAyRtOm7KsMA2eKQZ6qWakM7OhlFKsrBt1fLAbpFeDbsazXscw0vnep6nsuMw1v2LO5pVeRs41Q+9jAM2R1RefgcuUV/rr/QrfkcP+dsz35Fw==
\ No newline at end of file
diff --git "a/docs/database/redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-read.png" "b/docs/database/redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-read.png"
new file mode 100644
index 00000000000..f8b9589d6d7
Binary files /dev/null and "b/docs/database/redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-read.png" differ
diff --git "a/docs/database/redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-write.drawio" "b/docs/database/redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-write.drawio"
new file mode 100644
index 00000000000..6fddf10f064
--- /dev/null
+++ "b/docs/database/redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-write.drawio"
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git "a/docs/database/redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-write.png" "b/docs/database/redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-write.png"
new file mode 100644
index 00000000000..c976cc99b91
Binary files /dev/null and "b/docs/database/redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-write.png" differ
diff --git "a/docs/database/redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/read-through.drawio" "b/docs/database/redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/read-through.drawio"
new file mode 100644
index 00000000000..7f7bfd71641
--- /dev/null
+++ "b/docs/database/redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/read-through.drawio"
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git "a/docs/database/redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/read-through.png" "b/docs/database/redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/read-through.png"
new file mode 100644
index 00000000000..f8f457c7490
Binary files /dev/null and "b/docs/database/redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/read-through.png" differ
diff --git "a/docs/database/redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/write-through.drawio" "b/docs/database/redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/write-through.drawio"
new file mode 100644
index 00000000000..7626c8d1f50
--- /dev/null
+++ "b/docs/database/redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/write-through.drawio"
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git "a/docs/database/redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/write-through.png" "b/docs/database/redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/write-through.png"
new file mode 100644
index 00000000000..ecdbd6d2c2e
Binary files /dev/null and "b/docs/database/redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/write-through.png" differ
diff --git "a/docs/database/redis/redis\347\237\245\350\257\206\347\202\271&\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" "b/docs/database/redis/redis\347\237\245\350\257\206\347\202\271&\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md"
new file mode 100644
index 00000000000..f2ef0eb8756
--- /dev/null
+++ "b/docs/database/redis/redis\347\237\245\350\257\206\347\202\271&\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md"
@@ -0,0 +1,799 @@
+---
+title: Redis知识点&面试题总结
+category: 数据库
+tag:
+ - Redis
+---
+
+### 简单介绍一下 Redis 呗!
+
+简单来说 **Redis 就是一个使用 C 语言开发的数据库**,不过与传统数据库不同的是 **Redis 的数据是存在内存中的** ,也就是它是内存数据库,所以读写速度非常快,因此 Redis 被广泛应用于缓存方向。
+
+另外,**Redis 除了做缓存之外,也经常用来做分布式锁,甚至是消息队列。**
+
+**Redis 提供了多种数据类型来支持不同的业务场景。Redis 还支持事务 、持久化、Lua 脚本、多种集群方案。**
+
+### 分布式缓存常见的技术选型方案有哪些?
+
+分布式缓存的话,使用的比较多的主要是 **Memcached** 和 **Redis**。不过,现在基本没有看过还有项目使用 **Memcached** 来做缓存,都是直接用 **Redis**。
+
+Memcached 是分布式缓存最开始兴起的那会,比较常用的。后来,随着 Redis 的发展,大家慢慢都转而使用更加强大的 Redis 了。
+
+分布式缓存主要解决的是单机缓存的容量受服务器限制并且无法保存通用信息的问题。因为,本地缓存只在当前服务里有效,比如如果你部署了两个相同的服务,他们两者之间的缓存数据是无法共同的。
+
+### 说一下 Redis 和 Memcached 的区别和共同点
+
+现在公司一般都是用 Redis 来实现缓存,而且 Redis 自身也越来越强大了!不过,了解 Redis 和 Memcached 的区别和共同点,有助于我们在做相应的技术选型的时候,能够做到有理有据!
+
+**共同点** :
+
+1. 都是基于内存的数据库,一般都用来当做缓存使用。
+2. 都有过期策略。
+3. 两者的性能都非常高。
+
+**区别** :
+
+1. **Redis 支持更丰富的数据类型(支持更复杂的应用场景)**。Redis 不仅仅支持简单的 k/v 类型的数据,同时还提供 list,set,zset,hash 等数据结构的存储。Memcached 只支持最简单的 k/v 数据类型。
+2. **Redis 支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用,而 Memecache 把数据全部存在内存之中。**
+3. **Redis 有灾难恢复机制。** 因为可以把缓存中的数据持久化到磁盘上。
+4. **Redis 在服务器内存使用完之后,可以将不用的数据放到磁盘上。但是,Memcached 在服务器内存使用完之后,就会直接报异常。**
+5. **Memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据;但是 Redis 目前是原生支持 cluster 模式的。**
+6. **Memcached 是多线程,非阻塞 IO 复用的网络模型;Redis 使用单线程的多路 IO 复用模型。** (Redis 6.0 引入了多线程 IO )
+7. **Redis 支持发布订阅模型、Lua 脚本、事务等功能,而 Memcached 不支持。并且,Redis 支持更多的编程语言。**
+8. **Memcached 过期数据的删除策略只用了惰性删除,而 Redis 同时使用了惰性删除与定期删除。**
+
+相信看了上面的对比之后,我们已经没有什么理由可以选择使用 Memcached 来作为自己项目的分布式缓存了。
+
+### 缓存数据的处理流程是怎样的?
+
+作为暖男一号,我给大家画了一个草图。
+
+
+
+简单来说就是:
+
+1. 如果用户请求的数据在缓存中就直接返回。
+2. 缓存中不存在的话就看数据库中是否存在。
+3. 数据库中存在的话就更新缓存中的数据。
+4. 数据库中不存在的话就返回空数据。
+
+### 为什么要用 Redis/为什么要用缓存?
+
+_简单,来说使用缓存主要是为了提升用户体验以及应对更多的用户。_
+
+下面我们主要从“高性能”和“高并发”这两点来看待这个问题。
+
+
+
+**高性能** :
+
+对照上面 👆 我画的图。我们设想这样的场景:
+
+假如用户第一次访问数据库中的某些数据的话,这个过程是比较慢,毕竟是从硬盘中读取的。但是,如果说,用户访问的数据属于高频数据并且不会经常改变的话,那么我们就可以很放心地将该用户访问的数据存在缓存中。
+
+**这样有什么好处呢?** 那就是保证用户下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快。
+
+不过,要保持数据库和缓存中的数据的一致性。 如果数据库中的对应数据改变的之后,同步改变缓存中相应的数据即可!
+
+**高并发:**
+
+一般像 MySQL 这类的数据库的 QPS 大概都在 1w 左右(4 核 8g) ,但是使用 Redis 缓存之后很容易达到 10w+,甚至最高能达到 30w+(就单机 redis 的情况,redis 集群的话会更高)。
+
+> QPS(Query Per Second):服务器每秒可以执行的查询次数;
+
+由此可见,直接操作缓存能够承受的数据库请求数量是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。进而,我们也就提高了系统整体的并发。
+
+### Redis 除了做缓存,还能做什么?
+
+- **分布式锁** : 通过 Redis 来做分布式锁是一种比较常见的方式。通常情况下,我们都是基于 Redisson 来实现分布式锁。相关阅读:[《分布式锁中的王者方案 - Redisson》](https://mp.weixin.qq.com/s/CbnPRfvq4m1sqo2uKI6qQw)。
+- **限流** :一般是通过 Redis + Lua 脚本的方式来实现限流。相关阅读:[《我司用了 6 年的 Redis 分布式限流器,可以说是非常厉害了!》](https://mp.weixin.qq.com/s/kyFAWH3mVNJvurQDt4vchA)。
+- **消息队列** :Redis 自带的 list 数据结构可以作为一个简单的队列使用。Redis5.0 中增加的 Stream 类型的数据结构更加适合用来做消息队列。它比较类似于 Kafka,有主题和消费组的概念,支持消息持久化以及 ACK 机制。
+- **复杂业务场景** :通过 Redis 以及 Redis 扩展(比如 Redisson)提供的数据结构,我们可以很方便地完成很多复杂的业务场景比如通过 bitmap 统计活跃用户、通过 sorted set 维护排行榜。
+- ......
+
+### Redis 常见数据结构以及使用场景分析
+
+你可以自己本机安装 redis 或者通过 redis 官网提供的[在线 redis 环境](https://try.redis.io/)。
+
+
+
+#### string
+
+1. **介绍** :string 数据结构是简单的 key-value 类型。虽然 Redis 是用 C 语言写的,但是 Redis 并没有使用 C 的字符串表示,而是自己构建了一种 **简单动态字符串**(simple dynamic string,**SDS**)。相比于 C 的原生字符串,Redis 的 SDS 不光可以保存文本数据还可以保存二进制数据,并且获取字符串长度复杂度为 O(1)(C 字符串为 O(N)),除此之外,Redis 的 SDS API 是安全的,不会造成缓冲区溢出。
+2. **常用命令:** `set,get,strlen,exists,decr,incr,setex` 等等。
+3. **应用场景:** 一般常用在需要计数的场景,比如用户的访问次数、热点文章的点赞转发数量等等。
+
+下面我们简单看看它的使用!
+
+**普通字符串的基本操作:**
+
+```bash
+127.0.0.1:6379> set key value #设置 key-value 类型的值
+OK
+127.0.0.1:6379> get key # 根据 key 获得对应的 value
+"value"
+127.0.0.1:6379> exists key # 判断某个 key 是否存在
+(integer) 1
+127.0.0.1:6379> strlen key # 返回 key 所储存的字符串值的长度。
+(integer) 5
+127.0.0.1:6379> del key # 删除某个 key 对应的值
+(integer) 1
+127.0.0.1:6379> get key
+(nil)
+```
+
+**批量设置** :
+
+```bash
+127.0.0.1:6379> mset key1 value1 key2 value2 # 批量设置 key-value 类型的值
+OK
+127.0.0.1:6379> mget key1 key2 # 批量获取多个 key 对应的 value
+1) "value1"
+2) "value2"
+```
+
+**计数器(字符串的内容为整数的时候可以使用):**
+
+```bash
+127.0.0.1:6379> set number 1
+OK
+127.0.0.1:6379> incr number # 将 key 中储存的数字值增一
+(integer) 2
+127.0.0.1:6379> get number
+"2"
+127.0.0.1:6379> decr number # 将 key 中储存的数字值减一
+(integer) 1
+127.0.0.1:6379> get number
+"1"
+```
+
+**过期(默认为永不过期)**:
+
+```bash
+127.0.0.1:6379> expire key 60 # 数据在 60s 后过期
+(integer) 1
+127.0.0.1:6379> setex key 60 value # 数据在 60s 后过期 (setex:[set] + [ex]pire)
+OK
+127.0.0.1:6379> ttl key # 查看数据还有多久过期
+(integer) 56
+```
+
+#### list
+
+1. **介绍** :**list** 即是 **链表**。链表是一种非常常见的数据结构,特点是易于数据元素的插入和删除并且可以灵活调整链表长度,但是链表的随机访问困难。许多高级编程语言都内置了链表的实现比如 Java 中的 **LinkedList**,但是 C 语言并没有实现链表,所以 Redis 实现了自己的链表数据结构。Redis 的 list 的实现为一个 **双向链表**,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销。
+2. **常用命令:** `rpush,lpop,lpush,rpop,lrange,llen` 等。
+3. **应用场景:** 发布与订阅或者说消息队列、慢查询。
+
+下面我们简单看看它的使用!
+
+**通过 `rpush/lpop` 实现队列:**
+
+```bash
+127.0.0.1:6379> rpush myList value1 # 向 list 的头部(右边)添加元素
+(integer) 1
+127.0.0.1:6379> rpush myList value2 value3 # 向list的头部(最右边)添加多个元素
+(integer) 3
+127.0.0.1:6379> lpop myList # 将 list的尾部(最左边)元素取出
+"value1"
+127.0.0.1:6379> lrange myList 0 1 # 查看对应下标的list列表, 0 为 start,1为 end
+1) "value2"
+2) "value3"
+127.0.0.1:6379> lrange myList 0 -1 # 查看列表中的所有元素,-1表示倒数第一
+1) "value2"
+2) "value3"
+```
+
+**通过 `rpush/rpop` 实现栈:**
+
+```bash
+127.0.0.1:6379> rpush myList2 value1 value2 value3
+(integer) 3
+127.0.0.1:6379> rpop myList2 # 将 list的头部(最右边)元素取出
+"value3"
+```
+
+我专门画了一个图方便小伙伴们来理解:
+
+
+
+**通过 `lrange` 查看对应下标范围的列表元素:**
+
+```bash
+127.0.0.1:6379> rpush myList value1 value2 value3
+(integer) 3
+127.0.0.1:6379> lrange myList 0 1 # 查看对应下标的list列表, 0 为 start,1为 end
+1) "value1"
+2) "value2"
+127.0.0.1:6379> lrange myList 0 -1 # 查看列表中的所有元素,-1表示倒数第一
+1) "value1"
+2) "value2"
+3) "value3"
+```
+
+通过 `lrange` 命令,你可以基于 list 实现分页查询,性能非常高!
+
+**通过 `llen` 查看链表长度:**
+
+```bash
+127.0.0.1:6379> llen myList
+(integer) 3
+```
+
+#### hash
+
+1. **介绍** :hash 类似于 JDK1.8 前的 HashMap,内部实现也差不多(数组 + 链表)。不过,Redis 的 hash 做了更多优化。另外,hash 是一个 string 类型的 field 和 value 的映射表,**特别适合用于存储对象**,后续操作的时候,你可以直接仅仅修改这个对象中的某个字段的值。 比如我们可以 hash 数据结构来存储用户信息,商品信息等等。
+2. **常用命令:** `hset,hmset,hexists,hget,hgetall,hkeys,hvals` 等。
+3. **应用场景:** 系统中对象数据的存储。
+
+下面我们简单看看它的使用!
+
+```bash
+127.0.0.1:6379> hmset userInfoKey name "guide" description "dev" age "24"
+OK
+127.0.0.1:6379> hexists userInfoKey name # 查看 key 对应的 value中指定的字段是否存在。
+(integer) 1
+127.0.0.1:6379> hget userInfoKey name # 获取存储在哈希表中指定字段的值。
+"guide"
+127.0.0.1:6379> hget userInfoKey age
+"24"
+127.0.0.1:6379> hgetall userInfoKey # 获取在哈希表中指定 key 的所有字段和值
+1) "name"
+2) "guide"
+3) "description"
+4) "dev"
+5) "age"
+6) "24"
+127.0.0.1:6379> hkeys userInfoKey # 获取 key 列表
+1) "name"
+2) "description"
+3) "age"
+127.0.0.1:6379> hvals userInfoKey # 获取 value 列表
+1) "guide"
+2) "dev"
+3) "24"
+127.0.0.1:6379> hset userInfoKey name "GuideGeGe" # 修改某个字段对应的值
+127.0.0.1:6379> hget userInfoKey name
+"GuideGeGe"
+```
+
+#### set
+
+1. **介绍 :** set 类似于 Java 中的 `HashSet` 。Redis 中的 set 类型是一种无序集合,集合中的元素没有先后顺序。当你需要存储一个列表数据,又不希望出现重复数据时,set 是一个很好的选择,并且 set 提供了判断某个成员是否在一个 set 集合内的重要接口,这个也是 list 所不能提供的。可以基于 set 轻易实现交集、并集、差集的操作。比如:你可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis 可以非常方便的实现如共同关注、共同粉丝、共同喜好等功能。这个过程也就是求交集的过程。
+2. **常用命令:** `sadd,spop,smembers,sismember,scard,sinterstore,sunion` 等。
+3. **应用场景:** 需要存放的数据不能重复以及需要获取多个数据源交集和并集等场景
+
+下面我们简单看看它的使用!
+
+```bash
+127.0.0.1:6379> sadd mySet value1 value2 # 添加元素进去
+(integer) 2
+127.0.0.1:6379> sadd mySet value1 # 不允许有重复元素
+(integer) 0
+127.0.0.1:6379> smembers mySet # 查看 set 中所有的元素
+1) "value1"
+2) "value2"
+127.0.0.1:6379> scard mySet # 查看 set 的长度
+(integer) 2
+127.0.0.1:6379> sismember mySet value1 # 检查某个元素是否存在set 中,只能接收单个元素
+(integer) 1
+127.0.0.1:6379> sadd mySet2 value2 value3
+(integer) 2
+127.0.0.1:6379> sinterstore mySet3 mySet mySet2 # 获取 mySet 和 mySet2 的交集并存放在 mySet3 中
+(integer) 1
+127.0.0.1:6379> smembers mySet3
+1) "value2"
+```
+
+#### sorted set
+
+1. **介绍:** 和 set 相比,sorted set 增加了一个权重参数 score,使得集合中的元素能够按 score 进行有序排列,还可以通过 score 的范围来获取元素的列表。有点像是 Java 中 HashMap 和 TreeSet 的结合体。
+2. **常用命令:** `zadd,zcard,zscore,zrange,zrevrange,zrem` 等。
+3. **应用场景:** 需要对数据根据某个权重进行排序的场景。比如在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等信息。
+
+```bash
+127.0.0.1:6379> zadd myZset 3.0 value1 # 添加元素到 sorted set 中 3.0 为权重
+(integer) 1
+127.0.0.1:6379> zadd myZset 2.0 value2 1.0 value3 # 一次添加多个元素
+(integer) 2
+127.0.0.1:6379> zcard myZset # 查看 sorted set 中的元素数量
+(integer) 3
+127.0.0.1:6379> zscore myZset value1 # 查看某个 value 的权重
+"3"
+127.0.0.1:6379> zrange myZset 0 -1 # 顺序输出某个范围区间的元素,0 -1 表示输出所有元素
+1) "value3"
+2) "value2"
+3) "value1"
+127.0.0.1:6379> zrange myZset 0 1 # 顺序输出某个范围区间的元素,0 为 start 1 为 stop
+1) "value3"
+2) "value2"
+127.0.0.1:6379> zrevrange myZset 0 1 # 逆序输出某个范围区间的元素,0 为 start 1 为 stop
+1) "value1"
+2) "value2"
+```
+
+#### bitmap
+
+1. **介绍:** bitmap 存储的是连续的二进制数字(0 和 1),通过 bitmap, 只需要一个 bit 位来表示某个元素对应的值或者状态,key 就是对应元素本身 。我们知道 8 个 bit 可以组成一个 byte,所以 bitmap 本身会极大的节省储存空间。
+2. **常用命令:** `setbit` 、`getbit` 、`bitcount`、`bitop`
+3. **应用场景:** 适合需要保存状态信息(比如是否签到、是否登录...)并需要进一步对这些信息进行分析的场景。比如用户签到情况、活跃用户情况、用户行为统计(比如是否点赞过某个视频)。
+
+```bash
+# SETBIT 会返回之前位的值(默认是 0)这里会生成 7 个位
+127.0.0.1:6379> setbit mykey 7 1
+(integer) 0
+127.0.0.1:6379> setbit mykey 7 0
+(integer) 1
+127.0.0.1:6379> getbit mykey 7
+(integer) 0
+127.0.0.1:6379> setbit mykey 6 1
+(integer) 0
+127.0.0.1:6379> setbit mykey 8 1
+(integer) 0
+# 通过 bitcount 统计被被设置为 1 的位的数量。
+127.0.0.1:6379> bitcount mykey
+(integer) 2
+```
+
+针对上面提到的一些场景,这里进行进一步说明。
+
+**使用场景一:用户行为分析**
+很多网站为了分析你的喜好,需要研究你点赞过的内容。
+
+```bash
+# 记录你喜欢过 001 号小姐姐
+127.0.0.1:6379> setbit beauty_girl_001 uid 1
+```
+
+**使用场景二:统计活跃用户**
+
+使用时间作为 key,然后用户 ID 为 offset,如果当日活跃过就设置为 1
+
+那么我该如何计算某几天/月/年的活跃用户呢(暂且约定,统计时间内只要有一天在线就称为活跃),有请下一个 redis 的命令
+
+```bash
+# 对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上。
+# BITOP 命令支持 AND 、 OR 、 NOT 、 XOR 这四种操作中的任意一种参数
+BITOP operation destkey key [key ...]
+```
+
+初始化数据:
+
+```bash
+127.0.0.1:6379> setbit 20210308 1 1
+(integer) 0
+127.0.0.1:6379> setbit 20210308 2 1
+(integer) 0
+127.0.0.1:6379> setbit 20210309 1 1
+(integer) 0
+```
+
+统计 20210308~20210309 总活跃用户数: 1
+
+```bash
+127.0.0.1:6379> bitop and desk1 20210308 20210309
+(integer) 1
+127.0.0.1:6379> bitcount desk1
+(integer) 1
+```
+
+统计 20210308~20210309 在线活跃用户数: 2
+
+```bash
+127.0.0.1:6379> bitop or desk2 20210308 20210309
+(integer) 1
+127.0.0.1:6379> bitcount desk2
+(integer) 2
+```
+
+**使用场景三:用户在线状态**
+
+对于获取或者统计用户在线状态,使用 bitmap 是一个节约空间且效率又高的一种方法。
+
+只需要一个 key,然后用户 ID 为 offset,如果在线就设置为 1,不在线就设置为 0。
+
+### Redis 单线程模型详解
+
+**Redis 基于 Reactor 模式来设计开发了自己的一套高效的事件处理模型** (Netty 的线程模型也基于 Reactor 模式,Reactor 模式不愧是高性能 IO 的基石),这套事件处理模型对应的是 Redis 中的文件事件处理器(file event handler)。由于文件事件处理器(file event handler)是单线程方式运行的,所以我们一般都说 Redis 是单线程模型。
+
+**既然是单线程,那怎么监听大量的客户端连接呢?**
+
+Redis 通过**IO 多路复用程序** 来监听来自客户端的大量连接(或者说是监听多个 socket),它会将感兴趣的事件及类型(读、写)注册到内核中并监听每个事件是否发生。
+
+这样的好处非常明显: **I/O 多路复用技术的使用让 Redis 不需要额外创建多余的线程来监听客户端的大量连接,降低了资源的消耗**(和 NIO 中的 `Selector` 组件很像)。
+
+另外, Redis 服务器是一个事件驱动程序,服务器需要处理两类事件:1. 文件事件; 2. 时间事件。
+
+时间事件不需要多花时间了解,我们接触最多的还是 **文件事件**(客户端进行读取写入等操作,涉及一系列网络通信)。
+
+《Redis 设计与实现》有一段话是如是介绍文件事件的,我觉得写得挺不错。
+
+> Redis 基于 Reactor 模式开发了自己的网络事件处理器:这个处理器被称为文件事件处理器(file event handler)。文件事件处理器使用 I/O 多路复用(multiplexing)程序来同时监听多个套接字,并根据套接字目前执行的任务来为套接字关联不同的事件处理器。
+>
+> 当被监听的套接字准备好执行连接应答(accept)、读取(read)、写入(write)、关 闭(close)等操作时,与操作相对应的文件事件就会产生,这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件。
+>
+> **虽然文件事件处理器以单线程方式运行,但通过使用 I/O 多路复用程序来监听多个套接字**,文件事件处理器既实现了高性能的网络通信模型,又可以很好地与 Redis 服务器中其他同样以单线程方式运行的模块进行对接,这保持了 Redis 内部单线程设计的简单性。
+
+可以看出,文件事件处理器(file event handler)主要是包含 4 个部分:
+
+- 多个 socket(客户端连接)
+- IO 多路复用程序(支持多个客户端连接的关键)
+- 文件事件分派器(将 socket 关联到相应的事件处理器)
+- 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)
+
+
+
+《Redis设计与实现:12章》
+
+### Redis 没有使用多线程?为什么不使用多线程?
+
+虽然说 Redis 是单线程模型,但是,实际上,**Redis 在 4.0 之后的版本中就已经加入了对多线程的支持。**
+
+
+
+不过,Redis 4.0 增加的多线程主要是针对一些大键值对的删除操作的命令,使用这些命令就会使用主处理之外的其他线程来“异步处理”。
+
+大体上来说,**Redis 6.0 之前主要还是单线程处理。**
+
+**那,Redis6.0 之前 为什么不使用多线程?**
+
+我觉得主要原因有下面 3 个:
+
+1. 单线程编程容易并且更容易维护;
+2. Redis 的性能瓶颈不在 CPU ,主要在内存和网络;
+3. 多线程就会存在死锁、线程上下文切换等问题,甚至会影响性能。
+
+### Redis6.0 之后为何引入了多线程?
+
+**Redis6.0 引入多线程主要是为了提高网络 IO 读写性能**,因为这个算是 Redis 中的一个性能瓶颈(Redis 的瓶颈主要受限于内存和网络)。
+
+虽然,Redis6.0 引入了多线程,但是 Redis 的多线程只是在网络数据的读写这类耗时操作上使用了,执行命令仍然是单线程顺序执行。因此,你也不需要担心线程安全问题。
+
+Redis6.0 的多线程默认是禁用的,只使用主线程。如需开启需要修改 redis 配置文件 `redis.conf` :
+
+```bash
+io-threads-do-reads yes
+```
+
+开启多线程后,还需要设置线程数,否则是不生效的。同样需要修改 redis 配置文件 `redis.conf` :
+
+```bash
+io-threads 4 #官网建议4核的机器建议设置为2或3个线程,8核的建议设置为6个线程
+```
+
+推荐阅读:
+
+1. [Redis 6.0 新特性-多线程连环 13 问!](https://mp.weixin.qq.com/s/FZu3acwK6zrCBZQ_3HoUgw)
+2. [为什么 Redis 选择单线程模型](https://draveness.me/whys-the-design-redis-single-thread/)
+
+### Redis 给缓存数据设置过期时间有啥用?
+
+一般情况下,我们设置保存的缓存数据的时候都会设置一个过期时间。为什么呢?
+
+因为内存是有限的,如果缓存中的所有数据都是一直保存的话,分分钟直接 Out of memory。
+
+Redis 自带了给缓存数据设置过期时间的功能,比如:
+
+```bash
+127.0.0.1:6379> exp key 60 # 数据在 60s 后过期
+(integer) 1
+127.0.0.1:6379> setex key 60 value # 数据在 60s 后过期 (setex:[set] + [ex]pire)
+OK
+127.0.0.1:6379> ttl key # 查看数据还有多久过期
+(integer) 56
+```
+
+注意:**Redis 中除了字符串类型有自己独有设置过期时间的命令 `setex` 外,其他方法都需要依靠 `expire` 命令来设置过期时间 。另外, `persist` 命令可以移除一个键的过期时间。 **
+
+**过期时间除了有助于缓解内存的消耗,还有什么其他用么?**
+
+很多时候,我们的业务场景就是需要某个数据只在某一时间段内存在,比如我们的短信验证码可能只在 1 分钟内有效,用户登录的 token 可能只在 1 天内有效。
+
+如果使用传统的数据库来处理的话,一般都是自己判断过期,这样更麻烦并且性能要差很多。
+
+### Redis 是如何判断数据是否过期的呢?
+
+Redis 通过一个叫做过期字典(可以看作是 hash 表)来保存数据过期的时间。过期字典的键指向 Redis 数据库中的某个 key(键),过期字典的值是一个 long long 类型的整数,这个整数保存了 key 所指向的数据库键的过期时间(毫秒精度的 UNIX 时间戳)。
+
+
+
+过期字典是存储在 redisDb 这个结构里的:
+
+```c
+typedef struct redisDb {
+ ...
+
+ dict *dict; //数据库键空间,保存着数据库中所有键值对
+ dict *expires // 过期字典,保存着键的过期时间
+ ...
+} redisDb;
+```
+
+### 过期的数据的删除策略了解么?
+
+如果假设你设置了一批 key 只能存活 1 分钟,那么 1 分钟后,Redis 是怎么对这批 key 进行删除的呢?
+
+常用的过期数据的删除策略就两个(重要!自己造缓存轮子的时候需要格外考虑的东西):
+
+1. **惰性删除** :只会在取出 key 的时候才对数据进行过期检查。这样对 CPU 最友好,但是可能会造成太多过期 key 没有被删除。
+2. **定期删除** : 每隔一段时间抽取一批 key 执行删除过期 key 操作。并且,Redis 底层会通过限制删除操作执行的时长和频率来减少删除操作对 CPU 时间的影响。
+
+定期删除对内存更加友好,惰性删除对 CPU 更加友好。两者各有千秋,所以 Redis 采用的是 **定期删除+惰性/懒汉式删除** 。
+
+但是,仅仅通过给 key 设置过期时间还是有问题的。因为还是可能存在定期删除和惰性删除漏掉了很多过期 key 的情况。这样就导致大量过期 key 堆积在内存里,然后就 Out of memory 了。
+
+怎么解决这个问题呢?答案就是:**Redis 内存淘汰机制。**
+
+### Redis 内存淘汰机制了解么?
+
+> 相关问题:MySQL 里有 2000w 数据,Redis 中只存 20w 的数据,如何保证 Redis 中的数据都是热点数据?
+
+Redis 提供 6 种数据淘汰策略:
+
+1. **volatile-lru(least recently used)**:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
+2. **volatile-ttl**:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
+3. **volatile-random**:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
+4. **allkeys-lru(least recently used)**:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)
+5. **allkeys-random**:从数据集(server.db[i].dict)中任意选择数据淘汰
+6. **no-eviction**:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。这个应该没人使用吧!
+
+4.0 版本后增加以下两种:
+
+7. **volatile-lfu(least frequently used)**:从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰
+8. **allkeys-lfu(least frequently used)**:当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的 key
+
+### Redis 持久化机制(怎么保证 Redis 挂掉之后再重启数据可以进行恢复)
+
+很多时候我们需要持久化数据也就是将内存中的数据写入到硬盘里面,大部分原因是为了之后重用数据(比如重启机器、机器故障之后恢复数据),或者是为了防止系统故障而将数据备份到一个远程位置。
+
+Redis 不同于 Memcached 的很重要一点就是,Redis 支持持久化,而且支持两种不同的持久化操作。**Redis 的一种持久化方式叫快照(snapshotting,RDB),另一种方式是只追加文件(append-only file, AOF)**。这两种方法各有千秋,下面我会详细这两种持久化方法是什么,怎么用,如何选择适合自己的持久化方法。
+
+**快照(snapshotting)持久化(RDB)**
+
+Redis 可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。Redis 创建快照之后,可以对快照进行备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis 主从结构,主要用来提高 Redis 性能),还可以将快照留在原地以便重启服务器的时候使用。
+
+快照持久化是 Redis 默认采用的持久化方式,在 Redis.conf 配置文件中默认有此下配置:
+
+```conf
+save 900 1 #在900秒(15分钟)之后,如果至少有1个key发生变化,Redis就会自动触发BGSAVE命令创建快照。
+
+save 300 10 #在300秒(5分钟)之后,如果至少有10个key发生变化,Redis就会自动触发BGSAVE命令创建快照。
+
+save 60 10000 #在60秒(1分钟)之后,如果至少有10000个key发生变化,Redis就会自动触发BGSAVE命令创建快照。
+```
+
+**AOF(append-only file)持久化**
+
+与快照持久化相比,AOF 持久化的实时性更好,因此已成为主流的持久化方案。默认情况下 Redis 没有开启 AOF(append only file)方式的持久化,可以通过 appendonly 参数开启:
+
+```conf
+appendonly yes
+```
+
+开启 AOF 持久化后每执行一条会更改 Redis 中的数据的命令,Redis 就会将该命令写入到内存缓存 `server.aof_buf` 中,然后再根据 `appendfsync` 配置来决定何时将其同步到硬盘中的 AOF 文件。
+
+AOF 文件的保存位置和 RDB 文件的位置相同,都是通过 dir 参数设置的,默认的文件名是 `appendonly.aof`。
+
+在 Redis 的配置文件中存在三种不同的 AOF 持久化方式,它们分别是:
+
+```conf
+appendfsync always #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度
+appendfsync everysec #每秒钟同步一次,显示地将多个写命令同步到硬盘
+appendfsync no #让操作系统决定何时进行同步
+```
+
+为了兼顾数据和写入性能,用户可以考虑 `appendfsync everysec` 选项 ,让 Redis 每秒同步一次 AOF 文件,Redis 性能几乎没受到任何影响。而且这样即使出现系统崩溃,用户最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候,Redis 还会优雅的放慢自己的速度以便适应硬盘的最大写入速度。
+
+**相关 issue** :[783:Redis 的 AOF 方式](https://github.com/Snailclimb/JavaGuide/issues/783)
+
+**拓展:Redis 4.0 对于持久化机制的优化**
+
+Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 `aof-use-rdb-preamble` 开启)。
+
+如果把混合持久化打开,AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。这样做的好处是可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据。当然缺点也是有的, AOF 里面的 RDB 部分是压缩格式不再是 AOF 格式,可读性较差。
+
+官方文档地址:https://redis.io/topics/persistence
+
+
+
+**补充内容:AOF 重写**
+
+AOF 重写可以产生一个新的 AOF 文件,这个新的 AOF 文件和原有的 AOF 文件所保存的数据库状态一样,但体积更小。
+
+AOF 重写是一个有歧义的名字,该功能是通过读取数据库中的键值对来实现的,程序无须对现有 AOF 文件进行任何读入、分析或者写入操作。
+
+在执行 BGREWRITEAOF 命令时,Redis 服务器会维护一个 AOF 重写缓冲区,该缓冲区会在子进程创建新 AOF 文件期间,记录服务器执行的所有写命令。当子进程完成创建新 AOF 文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新 AOF 文件的末尾,使得新的 AOF 文件保存的数据库状态与现有的数据库状态一致。最后,服务器用新的 AOF 文件替换旧的 AOF 文件,以此来完成 AOF 文件重写操作。
+
+### Redis 事务
+
+Redis 可以通过 **`MULTI`,`EXEC`,`DISCARD` 和 `WATCH`** 等命令来实现事务(transaction)功能。
+
+```bash
+> MULTI
+OK
+> SET USER "Guide哥"
+QUEUED
+> GET USER
+QUEUED
+> EXEC
+1) OK
+2) "Guide哥"
+```
+
+使用 [`MULTI`](https://redis.io/commands/multi) 命令后可以输入多个命令。Redis 不会立即执行这些命令,而是将它们放到队列,当调用了 [`EXEC`](https://redis.io/commands/exec) 命令将执行所有命令。
+
+这个过程是这样的:
+
+1. 开始事务(`MULTI`)。
+2. 命令入队(批量操作 Redis 的命令,先进先出(FIFO)的顺序执行)。
+3. 执行事务(`EXEC`)。
+
+你也可以通过 [`DISCARD`](https://redis.io/commands/discard) 命令取消一个事务,它会清空事务队列中保存的所有命令。
+
+```bash
+> MULTI
+OK
+> SET USER "Guide哥"
+QUEUED
+> GET USER
+QUEUED
+> DISCARD
+OK
+```
+
+[`WATCH`](https://redis.io/commands/watch) 命令用于监听指定的键,当调用 `EXEC` 命令执行事务时,如果一个被 `WATCH` 命令监视的键被修改的话,整个事务都不会执行,直接返回失败。
+
+```bash
+> WATCH USER
+OK
+> MULTI
+> SET USER "Guide哥"
+OK
+> GET USER
+Guide哥
+> EXEC
+ERR EXEC without MULTI
+```
+
+Redis 官网相关介绍 [https://redis.io/topics/transactions](https://redis.io/topics/transactions) 如下:
+
+
+
+但是,Redis 的事务和我们平时理解的关系型数据库的事务不同。我们知道事务具有四大特性: **1. 原子性**,**2. 隔离性**,**3. 持久性**,**4. 一致性**。
+
+1. **原子性(Atomicity):** 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
+2. **隔离性(Isolation):** 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;
+3. **持久性(Durability):** 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
+4. **一致性(Consistency):** 执行事务前后,数据保持一致,多个事务对同一个数据读取的结果是相同的;
+
+**Redis 是不支持 roll back 的,因而不满足原子性的(而且不满足持久性)。**
+
+Redis 官网也解释了自己为啥不支持回滚。简单来说就是 Redis 开发者们觉得没必要支持回滚,这样更简单便捷并且性能更好。Redis 开发者觉得即使命令执行错误也应该在开发过程中就被发现而不是生产过程中。
+
+
+
+你可以将 Redis 中的事务就理解为 :**Redis 事务提供了一种将多个命令请求打包的功能。然后,再按顺序执行打包的所有命令,并且不会被中途打断。**
+
+**相关 issue** :
+
+- [issue452: 关于 Redis 事务不满足原子性的问题](https://github.com/Snailclimb/JavaGuide/issues/452) 。
+- [Issue491:关于 redis 没有事务回滚?](https://github.com/Snailclimb/JavaGuide/issues/491)
+
+### 缓存穿透
+
+#### 什么是缓存穿透?
+
+缓存穿透说简单点就是大量请求的 key 根本不存在于缓存中,导致请求直接到了数据库上,根本没有经过缓存这一层。举个例子:某个黑客故意制造我们缓存中不存在的 key 发起大量请求,导致大量请求落到数据库。
+
+#### 缓存穿透情况的处理流程是怎样的?
+
+如下图所示,用户的请求最终都要跑到数据库中查询一遍。
+
+
+
+#### 有哪些解决办法?
+
+最基本的就是首先做好参数校验,一些不合法的参数请求直接抛出异常信息返回给客户端。比如查询的数据库 id 不能小于 0、传入的邮箱格式不对的时候直接返回错误消息给客户端等等。
+
+**1)缓存无效 key**
+
+如果缓存和数据库都查不到某个 key 的数据就写一个到 Redis 中去并设置过期时间,具体命令如下: `SET key value EX 10086` 。这种方式可以解决请求的 key 变化不频繁的情况,如果黑客恶意攻击,每次构建不同的请求 key,会导致 Redis 中缓存大量无效的 key 。很明显,这种方案并不能从根本上解决此问题。如果非要用这种方式来解决穿透问题的话,尽量将无效的 key 的过期时间设置短一点比如 1 分钟。
+
+另外,这里多说一嘴,一般情况下我们是这样设计 key 的: `表名:列名:主键名:主键值` 。
+
+如果用 Java 代码展示的话,差不多是下面这样的:
+
+```java
+public Object getObjectInclNullById(Integer id) {
+ // 从缓存中获取数据
+ Object cacheValue = cache.get(id);
+ // 缓存为空
+ if (cacheValue == null) {
+ // 从数据库中获取
+ Object storageValue = storage.get(key);
+ // 缓存空对象
+ cache.set(key, storageValue);
+ // 如果存储数据为空,需要设置一个过期时间(300秒)
+ if (storageValue == null) {
+ // 必须设置过期时间,否则有被攻击的风险
+ cache.expire(key, 60 * 5);
+ }
+ return storageValue;
+ }
+ return cacheValue;
+}
+```
+
+**2)布隆过滤器**
+
+布隆过滤器是一个非常神奇的数据结构,通过它我们可以非常方便地判断一个给定数据是否存在于海量数据中。我们需要的就是判断 key 是否合法,有没有感觉布隆过滤器就是我们想要找的那个“人”。
+
+具体是这样做的:把所有可能存在的请求的值都存放在布隆过滤器中,当用户请求过来,先判断用户发来的请求的值是否存在于布隆过滤器中。不存在的话,直接返回请求参数错误信息给客户端,存在的话才会走下面的流程。
+
+加入布隆过滤器之后的缓存处理流程图如下。
+
+
+
+但是,需要注意的是布隆过滤器可能会存在误判的情况。总结来说就是: **布隆过滤器说某个元素存在,小概率会误判。布隆过滤器说某个元素不在,那么这个元素一定不在。**
+
+_为什么会出现误判的情况呢? 我们还要从布隆过滤器的原理来说!_
+
+我们先来看一下,**当一个元素加入布隆过滤器中的时候,会进行哪些操作:**
+
+1. 使用布隆过滤器中的哈希函数对元素值进行计算,得到哈希值(有几个哈希函数得到几个哈希值)。
+2. 根据得到的哈希值,在位数组中把对应下标的值置为 1。
+
+我们再来看一下,**当我们需要判断一个元素是否存在于布隆过滤器的时候,会进行哪些操作:**
+
+1. 对给定元素再次进行相同的哈希计算;
+2. 得到值之后判断位数组中的每个元素是否都为 1,如果值都为 1,那么说明这个值在布隆过滤器中,如果存在一个值不为 1,说明该元素不在布隆过滤器中。
+
+然后,一定会出现这样一种情况:**不同的字符串可能哈希出来的位置相同。** (可以适当增加位数组大小或者调整我们的哈希函数来降低概率)
+
+更多关于布隆过滤器的内容可以看我的这篇原创:[《不了解布隆过滤器?一文给你整的明明白白!》](https://github.com/Snailclimb/JavaGuide/blob/master/docs/cs-basics/data-structure/bloom-filter.md) ,强烈推荐,个人感觉网上应该找不到总结的这么明明白白的文章了。
+
+### 缓存雪崩
+
+#### 什么是缓存雪崩?
+
+我发现缓存雪崩这名字起的有点意思,哈哈。
+
+实际上,缓存雪崩描述的就是这样一个简单的场景:**缓存在同一时间大面积的失效,后面的请求都直接落到了数据库上,造成数据库短时间内承受大量请求。** 这就好比雪崩一样,摧枯拉朽之势,数据库的压力可想而知,可能直接就被这么多请求弄宕机了。
+
+举个例子:系统的缓存模块出了问题比如宕机导致不可用。造成系统的所有访问,都要走数据库。
+
+还有一种缓存雪崩的场景是:**有一些被大量访问数据(热点缓存)在某一时刻大面积失效,导致对应的请求直接落到了数据库上。** 这样的情况,有下面几种解决办法:
+
+举个例子 :秒杀开始 12 个小时之前,我们统一存放了一批商品到 Redis 中,设置的缓存过期时间也是 12 个小时,那么秒杀开始的时候,这些秒杀的商品的访问直接就失效了。导致的情况就是,相应的请求直接就落到了数据库上,就像雪崩一样可怕。
+
+#### 有哪些解决办法?
+
+**针对 Redis 服务不可用的情况:**
+
+1. 采用 Redis 集群,避免单机出现问题整个缓存服务都没办法使用。
+2. 限流,避免同时处理大量的请求。
+
+**针对热点缓存失效的情况:**
+
+1. 设置不同的失效时间比如随机设置缓存的失效时间。
+2. 缓存永不失效。
+
+### 如何保证缓存和数据库数据的一致性?
+
+细说的话可以扯很多,但是我觉得其实没太大必要(小声 BB:很多解决方案我也没太弄明白)。我个人觉得引入缓存之后,如果为了短时间的不一致性问题,选择让系统设计变得更加复杂的话,完全没必要。
+
+下面单独对 **Cache Aside Pattern(旁路缓存模式)** 来聊聊。
+
+Cache Aside Pattern 中遇到写请求是这样的:更新 DB,然后直接删除 cache 。
+
+如果更新数据库成功,而删除缓存这一步失败的情况的话,简单说两个解决方案:
+
+1. **缓存失效时间变短(不推荐,治标不治本)** :我们让缓存数据的过期时间变短,这样的话缓存就会从数据库中加载数据。另外,这种解决办法对于先操作缓存后操作数据库的场景不适用。
+2. **增加 cache 更新重试机制(常用)**: 如果 cache 服务当前不可用导致缓存删除失败的话,我们就隔一段时间进行重试,重试次数可以自己定。如果多次重试还是失败的话,我们可以把当前更新失败的 key 存入队列中,等缓存服务可用之后,再将缓存中对应的 key 删除即可。
+
+### 参考
+
+- 《Redis 开发与运维》
+- 《Redis 设计与实现》
+- Redis 命令总结:http://Redisdoc.com/string/set.html
+- 通俗易懂的 Redis 数据结构基础教程:[https://juejin.im/post/5b53ee7e5188251aaa2d2e16](https://juejin.im/post/5b53ee7e5188251aaa2d2e16)
+- WHY Redis choose single thread (vs multi threads): [https://medium.com/@jychen7/sharing-redis-single-thread-vs-multi-threads-5870bd44d153](https://medium.com/@jychen7/sharing-redis-single-thread-vs-multi-threads-5870bd44d153)
diff --git "a/docs/database/\345\255\227\347\254\246\351\233\206.md" "b/docs/database/\345\255\227\347\254\246\351\233\206.md"
new file mode 100644
index 00000000000..1cca2575e6e
--- /dev/null
+++ "b/docs/database/\345\255\227\347\254\246\351\233\206.md"
@@ -0,0 +1,160 @@
+---
+title: 字符集
+category: 数据库
+tag:
+ - 数据库基础
+---
+
+
+MySQL 字符编码集中有两套 UTF-8 编码实现:**`utf8`** 和 **`utf8mb4`**。
+
+如果使用 **`utf8`** 的话,存储emoji 符号和一些比较复杂的汉字、繁体字就会出错。
+
+为什么会这样呢?这篇文章可以从源头给你解答。
+
+## 何为字符集?
+
+字符是各种文字和符号的统称,包括各个国家文字、标点符号、表情、数字等等。 **字符集** 就是一系列字符的集合。字符集的种类较多,每个字符集可以表示的字符范围通常不同,就比如说有些字符集是无法表示汉字的。
+
+**计算机只能存储二进制的数据,那英文、汉字、表情等字符应该如何存储呢?**
+
+我们要将这些字符和二级制的数据一一对应起来,比如说字符“a”对应“01100001”,反之,“01100001”对应 “a”。我们将字符对应二进制数据的过程称为"**字符编码**",反之,二进制数据解析成字符的过程称为“**字符解码**”。
+
+## 有哪些常见的字符集?
+
+常见的字符集有 ASCII、GB2312、GBK、UTF-8......。
+
+不同的字符集的主要区别在于:
+
+- 可以表示的字符范围
+- 编码方式
+
+### ASCII
+
+**ASCII** (**A**merican **S**tandard **C**ode for **I**nformation **I**nterchange,美国信息交换标准代码) 是一套主要用于现代美国英语的字符集(这也是 ASCII 字符集的局限性所在)。
+
+**为什么 ASCII 字符集没有考虑到中文等其他字符呢?** 因为计算机是美国人发明的,当时,计算机的发展还处于比较雏形的时代,还未在其他国家大规模使用。因此,美国发布 ASCII 字符集的时候没有考虑兼容其他国家的语言。
+
+ASCII 字符集至今为止共定义了 128 个字符,其中有 33 个控制字符(比如回车、删除)无法显示。
+
+一个 ASCII 码长度是一个字节也就是 8 个 bit,比如“a”对应的 ASCII 码是“01100001”。不过,最高位是 0 仅仅作为校验位,其余 7 位使用 0 和 1 进行组合,所以,ASCII 字符集可以定义 128(2^7)个字符。
+
+由于,ASCII 码可以表示的字符实在是太少了。后来,人们对其进行了扩展得到了 **ASCII 扩展字符集** 。ASCII 扩展字符集使用 8 位(bits)表示一个字符,所以,ASCII 扩展字符集可以定义 256(2^8)个字符。
+
+
+
+### GB2312
+
+我们上面说了,ASCII 字符集是一种现代美国英语适用的字符集。因此,很多国家都捣鼓了一个适合自己国家语言的字符集。
+
+GB2312 字符集是一种对汉字比较友好的字符集,共收录 6700 多个汉字,基本涵盖了绝大部分常用汉字。不过,GB2312 字符集不支持绝大部分的生僻字和繁体字。
+
+对于英语字符,GB2312 编码和 ASCII 码是相同的,1 字节编码即可。对于非英字符,需要 2 字节编码。
+
+### GBK
+
+GBK 字符集可以看作是 GB2312 字符集的扩展,兼容 GB2312 字符集,共收录了 20000 多个汉字。
+
+GBK 中 K 是汉语拼音 Kuo Zhan(扩展)中的“Kuo”的首字母。
+
+### GB18030
+
+GB18030 完全兼容 GB2312 和 GBK 字符集,纳入中国国内少数民族的文字,且收录了日韩汉字,是目前为止最全面的汉字字符集,共收录汉字 70000 多个。
+
+### BIG5
+
+BIG5 主要针对的是繁体中文,收录了 13000 多个汉字。
+
+### Unicode & UTF-8编码
+
+为了更加适合本国语言,诞生了很多种字符集。
+
+我们上面也说了不同的字符集可以表示的字符范围以及编码规则存在差异。这就导致了一个非常严重的问题:**使用错误的编码方式查看一个包含字符的文件就会产生乱码现象。**
+
+就比如说你使用 UTF-8 编码方式打开 GB2312 编码格式的文件就会出现乱码。示例:“牛”这个汉字 GB2312 编码后的十六进制数值为 “C5A3”,而 “C5A3” 用 UTF-8 解码之后得到的却是 “ţ”。
+
+你可以通过这个网站在线进行编码和解码:https://www.haomeili.net/HanZi/ZiFuBianMaZhuanHuan
+
+
+
+这样我们就搞懂了乱码的本质: **编码和解码时用了不同或者不兼容的字符集** 。
+
+
+
+为了解决这个问题,人们就想:“如果我们能够有一种字符集将世界上所有的字符都纳入其中就好了!”。
+
+然后,**Unicode** 带着这个使命诞生了。
+
+Unicode 字符集中包含了世界上几乎所有已知的字符。不过,Unicode 字符集并没有规定如何存储这些字符(也就是如何使用二进制数据表示这些字符)。
+
+然后,就有了 **UTF-8**(**8**-bit **U**nicode **T**ransformation **F**ormat)。类似的还有 UTF-16、 UTF-32。
+
+UTF-8 使用 1 到 4 个字节为每个字符编码, UTF-16 使用 2 或 4 个字节为每个字符编码,UTF-32 固定位 4 个字节为每个字符编码。
+
+UTF-8 可以根据不同的符号自动选择编码的长短,像英文字符只需要 1 个字节就够了,这一点 ASCII 字符集一样 。因此,对于英语字符,UTF-8 编码和 ASCII 码是相同的。
+
+UTF-32 的规则最简单,不过缺陷也比较明显,对于英文字母这类字符消耗的空间是 UTF-8 的 4 倍之多。
+
+**UTF-8** 是目前使用最广的一种字符编码,。
+
+
+
+## MySQL 字符集
+
+MySQL 支持很多种字符编码的方式,比如 UTF-8、GB2312、GBK、BIG5。
+
+你可以通过 `SHOW CHARSET` 命令来查看。
+
+
+
+通常情况下,我们建议使用 UTF-8 作为默认的字符编码方式。
+
+不过,这里有一个小坑。
+
+MySQL 字符编码集中有两套 UTF-8 编码实现:
+
+- **`utf8`** : `utf8`编码只支持`1-3`个字节 。 在 `utf8` 编码中,中文是占 3 个字节,其他数字、英文、符号占一个字节。但 emoji 符号占 4 个字节,一些较复杂的文字、繁体字也是 4 个字节。
+- **`utf8mb4`** : UTF-8 的完整实现,正版!最多支持使用 4 个字节表示字符,因此,可以用来存储 emoji 符号。
+
+**为什么有两套 UTF-8 编码实现呢?** 原因如下:
+
+
+
+因此,如果你需要存储`emoji`类型的数据或者一些比较复杂的文字、繁体字到 MySQL 数据库的话,数据库的编码一定要指定为`utf8mb4` 而不是`utf8` ,要不然存储的时候就会报错了。
+
+演示一下吧!(环境:MySQL 5.7+)
+
+建表语句如下,我们指定数据库 CHARSET 为 `utf8` 。
+
+```sql
+CREATE TABLE `user` (
+ `id` varchar(66) CHARACTER SET utf8mb4 NOT NULL,
+ `name` varchar(33) CHARACTER SET utf8mb4 NOT NULL,
+ `phone` varchar(33) CHARACTER SET utf8mb4 DEFAULT NULL,
+ `password` varchar(100) CHARACTER SET utf8mb4 DEFAULT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+```
+
+当我们执行下面的 insert 语句插入数据到数据库时,果然报错!
+
+```sql
+INSERT INTO `user` (`id`, `name`, `phone`, `password`)
+VALUES
+ ('A00003', 'guide哥😘😘😘', '181631312312', '123456');
+
+```
+
+报错信息如下:
+
+```
+Incorrect string value: '\xF0\x9F\x98\x98\xF0\x9F...' for column 'name' at row 1
+```
+
+## 参考
+
+- 字符集和字符编码(Charset & Encoding): https://www.cnblogs.com/skynet/archive/2011/05/03/2035105.html
+- 十分钟搞清字符集和字符编码:http://cenalulu.github.io/linux/character-encoding/
+- Unicode-维基百科:https://zh.wikipedia.org/wiki/Unicode
+- GB2312-维基百科:https://zh.wikipedia.org/wiki/GB_2312
+- UTF-8-维基百科:https://zh.wikipedia.org/wiki/UTF-8
+- GB18030-维基百科: https://zh.wikipedia.org/wiki/GB_18030
\ No newline at end of file
diff --git "a/docs/database/\346\225\260\346\215\256\345\272\223\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/docs/database/\346\225\260\346\215\256\345\272\223\345\237\272\347\241\200\347\237\245\350\257\206.md"
new file mode 100644
index 00000000000..92d30998a71
--- /dev/null
+++ "b/docs/database/\346\225\260\346\215\256\345\272\223\345\237\272\347\241\200\347\237\245\350\257\206.md"
@@ -0,0 +1,150 @@
+---
+title: 数据库基础知识
+category: 数据库
+tag:
+ - 数据库基础
+---
+
+数据库知识基础,这部分内容一定要理解记忆。虽然这部分内容只是理论知识,但是非常重要,这是后面学习 MySQL 数据库的基础。PS: 这部分内容由于涉及太多概念性内容,所以参考了维基百科和百度百科相应的介绍。
+
+## 什么是数据库, 数据库管理系统, 数据库系统, 数据库管理员?
+
+* **数据库** : 数据库(DataBase 简称 DB)就是信息的集合或者说数据库是由数据库管理系统管理的数据的集合。
+* **数据库管理系统** : 数据库管理系统(Database Management System 简称 DBMS)是一种操纵和管理数据库的大型软件,通常用于建立、使用和维护数据库。
+* **数据库系统** : 数据库系统(Data Base System,简称 DBS)通常由软件、数据库和数据管理员(DBA)组成。
+* **数据库管理员** : 数据库管理员(Database Administrator, 简称 DBA)负责全面管理和控制数据库系统。
+
+数据库系统基本构成如下图所示:
+
+
+
+## 什么是元组, 码, 候选码, 主码, 外码, 主属性, 非主属性?
+
+* **元组** : 元组(tuple)是关系数据库中的基本概念,关系是一张表,表中的每行(即数据库中的每条记录)就是一个元组,每列就是一个属性。 在二维表里,元组也称为行。
+* **码** :码就是能唯一标识实体的属性,对应表中的列。
+* **候选码** : 若关系中的某一属性或属性组的值能唯一的标识一个元组,而其任何、子集都不能再标识,则称该属性组为候选码。例如:在学生实体中,“学号”是能唯一的区分学生实体的,同时又假设“姓名”、“班级”的属性组合足以区分学生实体,那么{学号}和{姓名,班级}都是候选码。
+* **主码** : 主码也叫主键。主码是从候选码中选出来的。 一个实体集中只能有一个主码,但可以有多个候选码。
+* **外码** : 外码也叫外键。如果一个关系中的一个属性是另外一个关系中的主码则这个属性为外码。
+* **主属性** : 候选码中出现过的属性称为主属性。比如关系 工人(工号,身份证号,姓名,性别,部门). 显然工号和身份证号都能够唯一标示这个关系,所以都是候选码。工号、身份证号这两个属性就是主属性。如果主码是一个属性组,那么属性组中的属性都是主属性。
+* **非主属性:** 不包含在任何一个候选码中的属性称为非主属性。比如在关系——学生(学号,姓名,年龄,性别,班级)中,主码是“学号”,那么其他的“姓名”、“年龄”、“性别”、“班级”就都可以称为非主属性。
+
+## 主键和外键有什么区别?
+
+* **主键(主码)** :主键用于唯一标识一个元组,不能有重复,不允许为空。一个表只能有一个主键。
+* **外键(外码)** :外键用来和其他表建立联系用,外键是另一表的主键,外键是可以有重复的,可以是空值。一个表可以有多个外键。
+
+## 为什么不推荐使用外键与级联?
+
+对于外键和级联,阿里巴巴开发手册这样说到:
+
+> 【强制】不得使用外键与级联,一切外键概念必须在应用层解决。
+>
+> 说明: 以学生和成绩的关系为例,学生表中的 student_id 是主键,那么成绩表中的 student_id 则为外键。如果更新学生表中的 student_id,同时触发成绩表中的 student_id 更新,即为级联更新。外键与级联更新适用于单机低并发,不适合分布式、高并发集群; 级联更新是强阻塞,存在数据库更新风暴的风 险; 外键影响数据库的插入速度
+
+为什么不要用外键呢?大部分人可能会这样回答:
+
+> 1. **增加了复杂性:** a. 每次做DELETE 或者UPDATE都必须考虑外键约束,会导致开发的时候很痛苦, 测试数据极为不方便; b. 外键的主从关系是定的,假如那天需求有变化,数据库中的这个字段根本不需要和其他表有关联的话就会增加很多麻烦。
+> 2. **增加了额外工作**: 数据库需要增加维护外键的工作,比如当我们做一些涉及外键字段的增,删,更新操作之后,需要触发相关操作去检查,保证数据的的一致性和正确性,这样会不得不消耗资源;(个人觉得这个不是不用外键的原因,因为即使你不使用外键,你在应用层面也还是要保证的。所以,我觉得这个影响可以忽略不计。)
+> 3. 外键还会因为需要请求对其他表内部加锁而容易出现死锁情况;
+> 4. **对分库分表不友好** :因为分库分表下外键是无法生效的。
+> 5. ......
+
+我个人觉得上面这种回答不是特别的全面,只是说了外键存在的一个常见的问题。实际上,我们知道外键也是有很多好处的,比如:
+
+1. 保证了数据库数据的一致性和完整性;
+2. 级联操作方便,减轻了程序代码量;
+3. ......
+
+所以说,不要一股脑的就抛弃了外键这个概念,既然它存在就有它存在的道理,如果系统不涉及分库分表,并发量不是很高的情况还是可以考虑使用外键的。
+
+
+## 什么是 ER 图?
+
+> 我们做一个项目的时候一定要试着画 ER 图来捋清数据库设计,这个也是面试官问你项目的时候经常会被问道的。
+
+**E-R 图** 也称实体-联系图(Entity Relationship Diagram),提供了表示实体类型、属性和联系的方法,用来描述现实世界的概念模型。 它是描述现实世界关系概念模型的有效方法。 是表示概念关系模型的一种方式。
+
+下图是一个学生选课的 ER 图,每个学生可以选若干门课程,同一门课程也可以被若干人选择,所以它们之间的关系是多对多(M: N)。另外,还有其他两种关系是:1 对 1(1:1)、1 对多(1: N)。
+
+
+
+我们试着将上面的 ER 图转换成数据库实际的关系模型(实际设计中,我们通常会将任课教师也作为一个实体来处理):
+
+
+
+## 数据库范式了解吗?
+
+**1NF(第一范式)**
+
+属性(对应于表中的字段)不能再被分割,也就是这个字段只能是一个值,不能再分为多个其他的字段了。**1NF 是所有关系型数据库的最基本要求** ,也就是说关系型数据库中创建的表一定满足第一范式。
+
+**2NF(第二范式)**
+
+2NF 在 1NF 的基础之上,消除了非主属性对于码的部分函数依赖。如下图所示,展示了第一范式到第二范式的过渡。第二范式在第一范式的基础上增加了一个列,这个列称为主键,非主属性都依赖于主键。
+
+
+
+一些重要的概念:
+
+* **函数依赖(functional dependency)** :若在一张表中,在属性(或属性组)X 的值确定的情况下,必定能确定属性 Y 的值,那么就可以说 Y 函数依赖于 X,写作 X → Y。
+* **部分函数依赖(partial functional dependency)** :如果 X→Y,并且存在 X 的一个真子集 X0,使得 X0→Y,则称 Y 对 X 部分函数依赖。比如学生基本信息表 R 中(学号,身份证号,姓名)当然学号属性取值是唯一的,在 R 关系中,(学号,身份证号)->(姓名),(学号)->(姓名),(身份证号)->(姓名);所以姓名部分函数依赖与(学号,身份证号);
+* **完全函数依赖(Full functional dependency)** :在一个关系中,若某个非主属性数据项依赖于全部关键字称之为完全函数依赖。比如学生基本信息表 R(学号,班级,姓名)假设不同的班级学号有相同的,班级内学号不能相同,在 R 关系中,(学号,班级)->(姓名),但是(学号)->(姓名)不成立,(班级)->(姓名)不成立,所以姓名完全函数依赖与(学号,班级);
+* **传递函数依赖** : 在关系模式 R(U)中,设 X,Y,Z 是 U 的不同的属性子集,如果 X 确定 Y、Y 确定 Z,且有 X 不包含 Y,Y 不确定 X,(X∪Y)∩Z=空集合,则称 Z 传递函数依赖(transitive functional dependency) 于 X。传递函数依赖会导致数据冗余和异常。传递函数依赖的 Y 和 Z 子集往往同属于某一个事物,因此可将其合并放到一个表中。比如在关系 R(学号 , 姓名, 系名,系主任)中,学号 → 系名,系名 → 系主任,所以存在非主属性系主任对于学号的传递函数依赖。。
+
+**3NF(第三范式)**
+
+3NF 在 2NF 的基础之上,消除了非主属性对于码的传递函数依赖 。符合 3NF 要求的数据库设计,**基本**上解决了数据冗余过大,插入异常,修改异常,删除异常的问题。比如在关系 R(学号 , 姓名, 系名,系主任)中,学号 → 系名,系名 → 系主任,所以存在非主属性系主任对于学号的传递函数依赖,所以该表的设计,不符合 3NF 的要求。
+
+**总结**
+
+* 1NF:属性不可再分。
+* 2NF:1NF 的基础之上,消除了非主属性对于码的部分函数依赖。
+* 3NF:3NF 在 2NF 的基础之上,消除了非主属性对于码的传递函数依赖 。
+
+## 什么是存储过程?
+
+我们可以把存储过程看成是一些 SQL 语句的集合,中间加了点逻辑控制语句。存储过程在业务比较复杂的时候是非常实用的,比如很多时候我们完成一个操作可能需要写一大串 SQL 语句,这时候我们就可以写有一个存储过程,这样也方便了我们下一次的调用。存储过程一旦调试完成通过后就能稳定运行,另外,使用存储过程比单纯 SQL 语句执行要快,因为存储过程是预编译过的。
+
+存储过程在互联网公司应用不多,因为存储过程难以调试和扩展,而且没有移植性,还会消耗数据库资源。
+
+阿里巴巴 Java 开发手册里要求禁止使用存储过程。
+
+
+
+## drop、delete 与 truncate 区别?
+
+### 用法不同
+
+* drop(丢弃数据): `drop table 表名` ,直接将表都删除掉,在删除表的时候使用。
+* truncate (清空数据) : `truncate table 表名` ,只删除表中的数据,再插入数据的时候自增长 id 又从 1 开始,在清空表中数据的时候使用。
+* delete(删除数据) : `delete from 表名 where 列名=值`,删除某一列的数据,如果不加 where 子句和`truncate table 表名`作用类似。
+
+truncate 和不带 where 子句的 delete、以及 drop 都会删除表内的数据,但是 **truncate 和 delete 只删除数据不删除表的结构(定义),执行 drop 语句,此表的结构也会删除,也就是执行 drop 之后对应的表不复存在。**
+
+### 属于不同的数据库语言
+
+truncate 和 drop 属于 DDL(数据定义语言)语句,操作立即生效,原数据不放到 rollback segment 中,不能回滚,操作不触发 trigger。而 delete 语句是 DML (数据库操作语言)语句,这个操作会放到 rollback segement 中,事务提交之后才生效。
+
+**DML 语句和 DDL 语句区别:**
+
+* DML 是数据库操作语言(Data Manipulation Language)的缩写,是指对数据库中表记录的操作,主要包括表记录的插入(insert)、更新(update)、删除(delete)和查询(select),是开发人员日常使用最频繁的操作。
+* DDL (Data Definition Language)是数据定义语言的缩写,简单来说,就是对数据库内部的对象进行创建、删除、修改的操作语言。它和 DML 语言的最大区别是 DML 只是对表内部数据的操作,而不涉及到表的定义、结构的修改,更不会涉及到其他对象。DDL 语句更多的被数据库管理员(DBA)所使用,一般的开发人员很少使用。
+
+### 执行速度不同
+
+一般来说:drop>truncate>delete(这个我没有设计测试过)。
+
+## 数据库设计通常分为哪几步?
+
+1. **需求分析** : 分析用户的需求,包括数据、功能和性能需求。
+2. **概念结构设计** : 主要采用 E-R 模型进行设计,包括画 E-R 图。
+3. **逻辑结构设计** : 通过将 E-R 图转换成表,实现从 E-R 模型到关系模型的转换。
+4. **物理结构设计** : 主要是为所设计的数据库选择合适的存储结构和存取路径。
+5. **数据库实施** : 包括编程、测试和试运行
+6. **数据库的运行和维护** : 系统的运行与数据库的日常维护。
+
+## 参考
+
+*
+*
+*
diff --git "a/docs/database/\346\225\260\346\215\256\345\272\223\350\277\236\346\216\245\346\261\240.md" "b/docs/database/\346\225\260\346\215\256\345\272\223\350\277\236\346\216\245\346\261\240.md"
deleted file mode 100644
index 3e84dfc8efd..00000000000
--- "a/docs/database/\346\225\260\346\215\256\345\272\223\350\277\236\346\216\245\346\261\240.md"
+++ /dev/null
@@ -1,21 +0,0 @@
-- 公众号和Github待发文章:[数据库:数据库连接池原理详解与自定义连接池实现](https://www.fangzhipeng.com/javainterview/2019/07/15/mysql-connector-pool.html)
-- [基于JDBC的数据库连接池技术研究与应用](http://blog.itpub.net/9403012/viewspace-111794/)
-- [数据库连接池技术详解](https://juejin.im/post/5b7944c6e51d4538c86cf195)
-
-数据库连接本质就是一个 socket 的连接。数据库服务端还要维护一些缓存和用户权限信息之类的 所以占用了一些内存
-
-连接池是维护的数据库连接的缓存,以便将来需要对数据库的请求时可以重用这些连接。为每个用户打开和维护数据库连接,尤其是对动态数据库驱动的网站应用程序的请求,既昂贵又浪费资源。**在连接池中,创建连接后,将其放置在池中,并再次使用它,因此不必建立新的连接。如果使用了所有连接,则会建立一个新连接并将其添加到池中。**连接池还减少了用户必须等待建立与数据库的连接的时间。
-
-操作过数据库的朋友应该都知道数据库连接池这个概念,它几乎每天都在和我们打交道,但是你真的了解 **数据库连接池** 吗?
-
-### 没有数据库连接池之前
-
-我相信你一定听过这样一句话:**Java语言中,JDBC(Java DataBase Connection)是应用程序与数据库沟通的桥梁**。
-
-
-
-
-
-
-
-
diff --git "a/docs/database/\351\230\277\351\207\214\345\267\264\345\267\264\345\274\200\345\217\221\346\211\213\345\206\214\346\225\260\346\215\256\345\272\223\351\203\250\345\210\206\347\232\204\344\270\200\344\272\233\346\234\200\344\275\263\345\256\236\350\267\265.md" "b/docs/database/\351\230\277\351\207\214\345\267\264\345\267\264\345\274\200\345\217\221\346\211\213\345\206\214\346\225\260\346\215\256\345\272\223\351\203\250\345\210\206\347\232\204\344\270\200\344\272\233\346\234\200\344\275\263\345\256\236\350\267\265.md"
deleted file mode 100644
index 7eb84001c00..00000000000
--- "a/docs/database/\351\230\277\351\207\214\345\267\264\345\267\264\345\274\200\345\217\221\346\211\213\345\206\214\346\225\260\346\215\256\345\272\223\351\203\250\345\210\206\347\232\204\344\270\200\344\272\233\346\234\200\344\275\263\345\256\236\350\267\265.md"
+++ /dev/null
@@ -1,41 +0,0 @@
-# 阿里巴巴Java开发手册数据库部分的一些最佳实践总结
-
-## 模糊查询
-
-对于模糊查询阿里巴巴开发手册这样说到:
-
-> 【强制】页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决。
->
-> 说明:索引文件具有 B-Tree 的最左前缀匹配特性,如果左边的值未确定,那么无法使用此索引。
-
-## 外键和级联
-
-对于外键和级联,阿里巴巴开发手册这样说到:
-
->【强制】不得使用外键与级联,一切外键概念必须在应用层解决。
->
->说明:以学生和成绩的关系为例,学生表中的 student_id 是主键,那么成绩表中的 student_id 则为外键。如果更新学生表中的 student_id,同时触发成绩表中的 student_id 更新,即为级联更新。外键与级联更新适用于单机低并发,不适合分布式、高并发集群;级联更新是强阻塞,存在数据库更新风暴的风 险;外键影响数据库的插入速度
-
-为什么不要用外键呢?大部分人可能会这样回答:
-
-> 1. **增加了复杂性:** a.每次做DELETE 或者UPDATE都必须考虑外键约束,会导致开发的时候很痛苦,测试数据极为不方便;b.外键的主从关系是定的,假如那天需求有变化,数据库中的这个字段根本不需要和其他表有关联的话就会增加很多麻烦。
-> 2. **增加了额外工作**: 数据库需要增加维护外键的工作,比如当我们做一些涉及外键字段的增,删,更新操作之后,需要触发相关操作去检查,保证数据的的一致性和正确性,这样会不得不消耗资源;(个人觉得这个不是不用外键的原因,因为即使你不使用外键,你在应用层面也还是要保证的。所以,我觉得这个影响可以忽略不计。)
-> 3. 外键还会因为需要请求对其他表内部加锁而容易出现死锁情况;
-> 4. **对分不分表不友好** :因为分库分表下外键是无法生效的。
-> 5. ......
-
-我个人觉得上面这种回答不是特别的全面,只是说了外键存在的一个常见的问题。实际上,我们知道外键也是有很多好处的,比如:
-
-1. 保证了数据库数据的一致性和完整性;
-2. 级联操作方便,减轻了程序代码量;
-3. ......
-
-所以说,不要一股脑的就抛弃了外键这个概念,既然它存在就有它存在的道理,如果系统不涉及分不分表,并发量不是很高的情况还是可以考虑使用外键的。
-
-我个人是不太喜欢外键约束,比较喜欢在应用层去进行相关操作。
-
-## 关于@Transactional注解
-
-对于`@Transactional`事务注解,阿里巴巴开发手册这样说到:
-
->【参考】@Transactional事务不要滥用。事务会影响数据库的QPS,另外使用事务的地方需要考虑各方面的回滚方案,包括缓存回滚、搜索引擎回滚、消息补偿、统计修正等。
diff --git a/docs/distributed-system/api-gateway.md b/docs/distributed-system/api-gateway.md
new file mode 100644
index 00000000000..11bab874ff1
--- /dev/null
+++ b/docs/distributed-system/api-gateway.md
@@ -0,0 +1,107 @@
+
+# 网关
+
+## 何为网关?为什么要网关?
+
+
+
+微服务背景下,一个系统被拆分为多个服务,但是像安全认证,流量控制,日志,监控等功能是每个服务都需要的,没有网关的话,我们就需要在每个服务中单独实现,这使得我们做了很多重复的事情并且没有一个全局的视图来统一管理这些功能。
+
+综上:**一般情况下,网关都会提供请求转发、安全认证(身份/权限认证)、流量控制、负载均衡、容灾、日志、监控这些功能。**
+
+上面介绍了这么多功能,实际上,网关主要做了一件事情:**请求过滤** 。
+
+## 有哪些常见的网关系统?
+
+### Netflix Zuul
+
+Zuul 是 Netflix 开发的一款提供动态路由、监控、弹性、安全的网关服务。
+
+Zuul 主要通过过滤器(类似于 AOP)来过滤请求,从而实现网关必备的各种功能。
+
+
+
+我们可以自定义过滤器来处理请求,并且,Zuul 生态本身就有很多现成的过滤器供我们使用。就比如限流可以直接用国外朋友写的 [spring-cloud-zuul-ratelimit](https://github.com/marcosbarbero/spring-cloud-zuul-ratelimit) (这里只是举例说明,一般是配合 hystrix 来做限流):
+
+```xml
+
+ org.springframework.cloud
+ spring-cloud-starter-netflix-zuul
+
+
+ com.marcosbarbero.cloud
+ spring-cloud-zuul-ratelimit
+ 2.2.0.RELEASE
+
+```
+
+Zuul 1.x 基于同步 IO,性能较差。Zuul 2.x 基于 Netty 实现了异步 IO,性能得到了大幅改进。
+
+- Github 地址 : https://github.com/Netflix/zuul
+- 官方 Wiki : https://github.com/Netflix/zuul/wiki
+
+### Spring Cloud Gateway
+
+SpringCloud Gateway 属于 Spring Cloud 生态系统中的网关,其诞生的目标是为了替代老牌网关 **Zuul **。准确点来说,应该是 Zuul 1.x。SpringCloud Gateway 起步要比 Zuul 2.x 更早。
+
+为了提升网关的性能,SpringCloud Gateway 基于 Spring WebFlux 。Spring WebFlux 使用 Reactor 库来实现响应式编程模型,底层基于 Netty 实现异步 IO。
+
+Spring Cloud Gateway 的目标,不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。
+
+Spring Cloud Gateway 和 Zuul 2.x 的差别不大,也是通过过滤器来处理请求。不过,目前更加推荐使用 Spring Cloud Gateway 而非 Zuul,Spring Cloud 生态对其支持更加友好。
+
+- Github 地址 : https://github.com/spring-cloud/spring-cloud-gateway
+- 官网 : https://spring.io/projects/spring-cloud-gateway
+
+### Kong
+
+Kong 是一款基于 [OpenResty](https://github.com/openresty/) 的高性能、云原生、可扩展的网关系统。
+
+> OpenResty 是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。
+
+Kong 提供了插件机制来扩展其功能。比如、在服务上启用 Zipkin 插件
+
+```shell
+$ curl -X POST http://kong:8001/services/{service}/plugins \
+ --data "name=zipkin" \
+ --data "config.http_endpoint=http://your.zipkin.collector:9411/api/v2/spans" \
+ --data "config.sample_ratio=0.001"
+```
+
+- Github 地址: https://github.com/Kong/kong
+- 官网地址 : https://konghq.com/kong
+
+### APISIX
+
+APISIX 是一款基于 Nginx 和 etcd 的高性能、云原生、可扩展的网关系统。
+
+> *etcd*是使用 Go 语言开发的一个开源的、高可用的分布式 key-value 存储系统,使用 Raft 协议做分布式共识。
+
+与传统 API 网关相比,APISIX 具有动态路由和插件热加载,特别适合微服务系统下的 API 管理。并且,APISIX 与 SkyWalking(分布式链路追踪系统)、Zipkin(分布式链路追踪系统)、Prometheus(监控系统) 等 DevOps 生态工具对接都十分方便。
+
+
+
+作为 NGINX 和 Kong 的替代项目,APISIX 目前已经是 Apache 顶级开源项目,并且是最快毕业的国产开源项目。国内目前已经有很多知名企业(比如金山、有赞、爱奇艺、腾讯、贝壳)使用 APISIX 处理核心的业务流量。
+
+根据官网介绍:“APISIX 已经生产可用,功能、性能、架构全面优于 Kong”。
+
+- Github 地址 :https://github.com/apache/apisix
+- 官网地址: https://apisix.apache.org/zh/
+
+相关阅读:
+
+- [有了 NGINX 和 Kong,为什么还需要 Apache APISIX](https://www.apiseven.com/zh/blog/why-we-need-Apache-APISIX)
+- [APISIX 技术博客](https://www.apiseven.com/zh/blog)
+- [APISIX 用户案例](https://www.apiseven.com/zh/usercases)
+
+### Shenyu
+
+Shenyu 是一款基于 WebFlux 的可扩展、高性能、响应式网关,Apache 顶级开源项目。
+
+
+
+Shenyu 通过插件扩展功能,插件是 ShenYu 的灵魂,并且插件也是可扩展和热插拔的。不同的插件实现不同的功能。Shenyu 自带了诸如限流、熔断、转发 、重写、重定向、和路由监控等插件。
+
+- Github 地址: https://github.com/apache/incubator-shenyu
+- 官网地址 : https://shenyu.apache.org/
+
diff --git a/docs/distributed-system/distributed-id.md b/docs/distributed-system/distributed-id.md
new file mode 100644
index 00000000000..0ec45819e55
--- /dev/null
+++ b/docs/distributed-system/distributed-id.md
@@ -0,0 +1,357 @@
+# 分布式 ID
+
+## 分布式 ID 介绍
+
+### 何为 ID?
+
+日常开发中,我们需要对系统中的各种数据使用 ID 唯一表示,比如用户 ID 对应且仅对应一个人,商品 ID 对应且仅对应一件商品,订单 ID 对应且仅对应一个订单。
+
+
+
+我们现实生活中也有各种 ID,比如身份证 ID 对应且仅对应一个人、地址 ID 对应且仅对应
+
+简单来说,**ID 就是数据的唯一标识**。
+
+### 何为分布式 ID?
+
+分布式 ID 是分布式系统下的 ID。分布式 ID 不存在与现实生活中,属于计算机系统中的一个概念。
+
+我简单举一个分库分表的例子。
+
+我司的一个项目,使用的是单机 MySQL 。但是,没想到的是,项目上线一个月之后,随着使用人数越来越多,整个系统的数据量将越来越大。
+
+单机 MySQL 已经没办法支撑了,需要进行分库分表(推荐 Sharding-JDBC)。
+
+在分库之后, 数据遍布在不同服务器上的数据库,数据库的自增主键已经没办法满足生成的主键唯一了。**我们如何为不同的数据节点生成全局唯一主键呢?**
+
+
+
+这个时候就需要生成**分布式 ID**了。
+
+### 分布式 ID 需要满足哪些要求?
+
+
+
+分布式 ID 作为分布式系统中必不可少的一环,很多地方都要用到分布式 ID。
+
+一个最基本的分布式 ID 需要满足下面这些要求:
+
+- **全局唯一** :ID 的全局唯一性肯定是首先要满足的!
+- **高性能** : 分布式 ID 的生成速度要快,对本地资源消耗要小。
+- **高可用** :生成分布式 ID 的服务要保证可用性无限接近于 100%。
+- **方便易用** :拿来即用,使用方便,快速接入!
+
+除了这些之外,一个比较好的分布式 ID 还应保证:
+
+- **安全** :ID 中不包含敏感信息。
+- **有序递增** :如果要把 ID 存放在数据库的话,ID 的有序性可以提升数据库写入速度。并且,很多时候 ,我们还很有可能会直接通过 ID 来进行排序。
+- **有具体的业务含义** :生成的 ID 如果能有具体的业务含义,可以让定位问题以及开发更透明化(通过 ID 就能确定是哪个业务)。
+- **独立部署** :也就是分布式系统单独有一个发号器服务,专门用来生成分布式 ID。这样就生成 ID 的服务可以和业务相关的服务解耦。不过,这样同样带来了网络调用消耗增加的问题。总的来说,如果需要用到分布式 ID 的场景比较多的话,独立部署的发号器服务还是很有必要的。
+
+## 分布式 ID 常见解决方案
+
+### 数据库
+
+#### 数据库主键自增
+
+这种方式就比较简单直白了,就是通过关系型数据库的自增主键产生来唯一的 ID。
+
+
+
+以 MySQL 举例,我们通过下面的方式即可。
+
+**1.创建一个数据库表。**
+
+```sql
+CREATE TABLE `sequence_id` (
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+ `stub` char(10) NOT NULL DEFAULT '',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `stub` (`stub`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+```
+
+`stub` 字段无意义,只是为了占位,便于我们插入或者修改数据。并且,给 `stub` 字段创建了唯一索引,保证其唯一性。
+
+**2.通过 `replace into` 来插入数据。**
+
+```java
+BEGIN;
+REPLACE INTO sequence_id (stub) VALUES ('stub');
+SELECT LAST_INSERT_ID();
+COMMIT;
+```
+
+插入数据这里,我们没有使用 `insert into` 而是使用 `replace into` 来插入数据,具体步骤是这样的:
+
+1)第一步: 尝试把数据插入到表中。
+
+2)第二步: 如果主键或唯一索引字段出现重复数据错误而插入失败时,先从表中删除含有重复关键字值的冲突行,然后再次尝试把数据插入到表中。
+
+这种方式的优缺点也比较明显:
+
+- **优点** :实现起来比较简单、ID 有序递增、存储消耗空间小
+- **缺点** : 支持的并发量不大、存在数据库单点问题(可以使用数据库集群解决,不过增加了复杂度)、ID 没有具体业务含义、安全问题(比如根据订单 ID 的递增规律就能推算出每天的订单量,商业机密啊! )、每次获取 ID 都要访问一次数据库(增加了对数据库的压力,获取速度也慢)
+
+#### 数据库号段模式
+
+数据库主键自增这种模式,每次获取 ID 都要访问一次数据库,ID 需求比较大的时候,肯定是不行的。
+
+如果我们可以批量获取,然后存在在内存里面,需要用到的时候,直接从内存里面拿就舒服了!这也就是我们说的 **基于数据库的号段模式来生成分布式 ID。**
+
+数据库的号段模式也是目前比较主流的一种分布式 ID 生成方式。像滴滴开源的[Tinyid](https://github.com/didi/tinyid/wiki/tinyid%E5%8E%9F%E7%90%86%E4%BB%8B%E7%BB%8D) 就是基于这种方式来做的。不过,TinyId 使用了双号段缓存、增加多 db 支持等方式来进一步优化。
+
+以 MySQL 举例,我们通过下面的方式即可。
+
+**1.创建一个数据库表。**
+
+```sql
+CREATE TABLE `sequence_id_generator` (
+ `id` int(10) NOT NULL,
+ `current_max_id` bigint(20) NOT NULL COMMENT '当前最大id',
+ `step` int(10) NOT NULL COMMENT '号段的长度',
+ `version` int(20) NOT NULL COMMENT '版本号',
+ `biz_type` int(20) NOT NULL COMMENT '业务类型',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+```
+
+`current_max_id` 字段和`step`字段主要用于获取批量 ID,获取的批量 id 为: `current_max_id ~ current_max_id+step`。
+
+
+
+`version` 字段主要用于解决并发问题(乐观锁),`biz_type` 主要用于表示业余类型。
+
+**2.先插入一行数据。**
+
+```sql
+INSERT INTO `sequence_id_generator` (`id`, `current_max_id`, `step`, `version`, `biz_type`)
+VALUES
+ (1, 0, 100, 0, 101);
+```
+
+**3.通过 SELECT 获取指定业务下的批量唯一 ID**
+
+```sql
+SELECT `current_max_id`, `step`,`version` FROM `sequence_id_generator` where `biz_type` = 101
+```
+
+结果:
+
+```
+id current_max_id step version biz_type
+1 0 100 1 101
+```
+
+**4.不够用的话,更新之后重新 SELECT 即可。**
+
+```sql
+UPDATE sequence_id_generator SET current_max_id = 0+100, version=version+1 WHERE version = 0 AND `biz_type` = 101
+SELECT `current_max_id`, `step`,`version` FROM `sequence_id_generator` where `biz_type` = 101
+```
+
+结果:
+
+```
+id current_max_id step version biz_type
+1 100 100 1 101
+```
+
+相比于数据库主键自增的方式,**数据库的号段模式对于数据库的访问次数更少,数据库压力更小。**
+
+另外,为了避免单点问题,你可以从使用主从模式来提高可用性。
+
+**数据库号段模式的优缺点:**
+
+- **优点** :ID 有序递增、存储消耗空间小
+- **缺点** :存在数据库单点问题(可以使用数据库集群解决,不过增加了复杂度)、ID 没有具体业务含义、安全问题(比如根据订单 ID 的递增规律就能推算出每天的订单量,商业机密啊! )
+
+#### NoSQL
+
+
+
+一般情况下,NoSQL 方案使用 Redis 多一些。我们通过 Redis 的 `incr` 命令即可实现对 id 原子顺序递增。
+
+```bash
+127.0.0.1:6379> set sequence_id_biz_type 1
+OK
+127.0.0.1:6379> incr sequence_id_biz_type
+(integer) 2
+127.0.0.1:6379> get sequence_id_biz_type
+"2"
+```
+
+为了提高可用性和并发,我们可以使用 Redis Cluser。Redis Cluser 是 Redis 官方提供的 Redis 集群解决方案(3.0+版本)。
+
+除了 Redis Cluser 之外,你也可以使用开源的 Redis 集群方案[Codis](https://github.com/CodisLabs/codis) (大规模集群比如上百个节点的时候比较推荐)。
+
+除了高可用和并发之外,我们知道 Redis 基于内存,我们需要持久化数据,避免重启机器或者机器故障后数据丢失。Redis 支持两种不同的持久化方式:**快照(snapshotting,RDB)**、**只追加文件(append-only file, AOF)**。 并且,Redis 4.0 开始支持 **RDB 和 AOF 的混合持久化**(默认关闭,可以通过配置项 `aof-use-rdb-preamble` 开启)。
+
+关于 Redis 持久化,我这里就不过多介绍。不了解这部分内容的小伙伴,可以看看 [JavaGuide 对于 Redis 知识点的总结](https://snailclimb.gitee.io/javaguide/#/docs/database/Redis/redis-all)。
+
+**Redis 方案的优缺点:**
+
+- **优点** : 性能不错并且生成的 ID 是有序递增的
+- **缺点** : 和数据库主键自增方案的缺点类似
+
+除了 Redis 之外,MongoDB ObjectId 经常也会被拿来当做分布式 ID 的解决方案。
+
+
+
+MongoDB ObjectId 一共需要 12 个字节存储:
+
+- 0~3:时间戳
+- 3~6: 代表机器 ID
+- 7~8:机器进程 ID
+- 9~11 :自增值
+
+**MongoDB 方案的优缺点:**
+
+- **优点** : 性能不错并且生成的 ID 是有序递增的
+- **缺点** : 需要解决重复 ID 问题(当机器时间不对的情况下,可能导致会产生重复 ID) 、有安全性问题(ID 生成有规律性)
+
+### 算法
+
+#### UUID
+
+UUID 是 Universally Unique Identifier(通用唯一标识符) 的缩写。UUID 包含 32 个 16 进制数字(8-4-4-4-12)。
+
+JDK 就提供了现成的生成 UUID 的方法,一行代码就行了。
+
+```java
+//输出示例:cb4a9ede-fa5e-4585-b9bb-d60bce986eaa
+UUID.randomUUID()
+```
+
+[RFC 4122](https://tools.ietf.org/html/rfc4122) 中关于 UUID 的示例是这样的:
+
+
+
+我们这里重点关注一下这个 Version(版本),不同的版本对应的 UUID 的生成规则是不同的。
+
+5 种不同的 Version(版本)值分别对应的含义(参考[维基百科对于 UUID 的介绍](https://zh.wikipedia.org/wiki/%E9%80%9A%E7%94%A8%E5%94%AF%E4%B8%80%E8%AF%86%E5%88%AB%E7%A0%81)):
+
+- **版本 1** : UUID 是根据时间和节点 ID(通常是 MAC 地址)生成;
+- **版本 2** : UUID 是根据标识符(通常是组或用户 ID)、时间和节点 ID 生成;
+- **版本 3、版本 5** : 版本 5 - 确定性 UUID 通过散列(hashing)名字空间(namespace)标识符和名称生成;
+- **版本 4** : UUID 使用[随机性](https://zh.wikipedia.org/wiki/随机性)或[伪随机性](https://zh.wikipedia.org/wiki/伪随机性)生成。
+
+下面是 Version 1 版本下生成的 UUID 的示例:
+
+
+
+JDK 中通过 `UUID` 的 `randomUUID()` 方法生成的 UUID 的版本默认为 4。
+
+```java
+UUID uuid = UUID.randomUUID();
+int version = uuid.version();// 4
+```
+
+另外,Variant(变体)也有 4 种不同的值,这种值分别对应不同的含义。这里就不介绍了,貌似平时也不怎么需要关注。
+
+需要用到的时候,去看看维基百科对于 UUID 的 Variant(变体) 相关的介绍即可。
+
+从上面的介绍中可以看出,UUID 可以保证唯一性,因为其生成规则包括 MAC 地址、时间戳、名字空间(Namespace)、随机或伪随机数、时序等元素,计算机基于这些规则生成的 UUID 是肯定不会重复的。
+
+虽然,UUID 可以做到全局唯一性,但是,我们一般很少会使用它。
+
+比如使用 UUID 作为 MySQL 数据库主键的时候就非常不合适:
+
+- 数据库主键要尽量越短越好,而 UUID 的消耗的存储空间比较大(32 个字符串,128 位)。
+- UUID 是无顺序的,InnoDB 引擎下,数据库主键的无序性会严重影响数据库性能。
+
+最后,我们再简单分析一下 **UUID 的优缺点** (面试的时候可能会被问到的哦!) :
+
+- **优点** :生成速度比较快、简单易用
+- **缺点** : 存储消耗空间大(32 个字符串,128 位) 、 不安全(基于 MAC 地址生成 UUID 的算法会造成 MAC 地址泄露)、无序(非自增)、没有具体业务含义、需要解决重复 ID 问题(当机器时间不对的情况下,可能导致会产生重复 ID)
+
+#### Snowflake(雪花算法)
+
+Snowflake 是 Twitter 开源的分布式 ID 生成算法。Snowflake 由 64 bit 的二进制数字组成,这 64bit 的二进制被分成了几部分,每一部分存储的数据都有特定的含义:
+
+- **第 0 位**: 符号位(标识正负),始终为 0,没有用,不用管。
+- **第 1~41 位** :一共 41 位,用来表示时间戳,单位是毫秒,可以支撑 2 ^41 毫秒(约 69 年)
+- **第 42~52 位** :一共 10 位,一般来说,前 5 位表示机房 ID,后 5 位表示机器 ID(实际项目中可以根据实际情况调整)。这样就可以区分不同集群/机房的节点。
+- **第 53~64 位** :一共 12 位,用来表示序列号。 序列号为自增值,代表单台机器每毫秒能够产生的最大 ID 数(2^12 = 4096),也就是说单台机器每毫秒最多可以生成 4096 个 唯一 ID。
+
+
+
+如果你想要使用 Snowflake 算法的话,一般不需要你自己再造轮子。有很多基于 Snowflake 算法的开源实现比如美团 的 Leaf、百度的 UidGenerator,并且这些开源实现对原有的 Snowflake 算法进行了优化。
+
+另外,在实际项目中,我们一般也会对 Snowflake 算法进行改造,最常见的就是在 Snowflake 算法生成的 ID 中加入业务类型信息。
+
+我们再来看看 Snowflake 算法的优缺点 :
+
+- **优点** :生成速度比较快、生成的 ID 有序递增、比较灵活(可以对 Snowflake 算法进行简单的改造比如加入业务 ID)
+- **缺点** : 需要解决重复 ID 问题(依赖时间,当机器时间不对的情况下,可能导致会产生重复 ID)。
+
+### 开源框架
+
+#### UidGenerator(百度)
+
+[UidGenerator](https://github.com/baidu/uid-generator) 是百度开源的一款基于 Snowflake(雪花算法)的唯一 ID 生成器。
+
+不过,UidGenerator 对 Snowflake(雪花算法)进行了改进,生成的唯一 ID 组成如下。
+
+
+
+可以看出,和原始 Snowflake(雪花算法)生成的唯一 ID 的组成不太一样。并且,上面这些参数我们都可以自定义。
+
+UidGenerator 官方文档中的介绍如下:
+
+
+
+自 18 年后,UidGenerator 就基本没有再维护了,我这里也不过多介绍。想要进一步了解的朋友,可以看看 [UidGenerator 的官方介绍](https://github.com/baidu/uid-generator/blob/master/README.zh_cn.md)。
+
+#### Leaf(美团)
+
+**[Leaf](https://github.com/Meituan-Dianping/Leaf)** 是美团开源的一个分布式 ID 解决方案 。这个项目的名字 Leaf(树叶) 起源于德国哲学家、数学家莱布尼茨的一句话: “There are no two identical leaves in the world”(世界上没有两片相同的树叶) 。这名字起得真心挺不错的,有点文艺青年那味了!
+
+
+
+Leaf 提供了 **号段模式** 和 **Snowflake(雪花算法)** 这两种模式来生成分布式 ID。并且,它支持双号段,还解决了雪花 ID 系统时钟回拨问题。不过,时钟问题的解决需要弱依赖于 Zookeeper 。
+
+Leaf 的诞生主要是为了解决美团各个业务线生成分布式 ID 的方法多种多样以及不可靠的问题。
+
+Leaf 对原有的号段模式进行改进,比如它这里增加了双号段避免获取 DB 在获取号段的时候阻塞请求获取 ID 的线程。简单来说,就是我一个号段还没用完之前,我自己就主动提前去获取下一个号段(图片来自于美团官方文章:[《Leaf——美团点评分布式 ID 生成系统》](https://tech.meituan.com/2017/04/21/mt-leaf.html))。
+
+
+
+根据项目 README 介绍,在 4C8G VM 基础上,通过公司 RPC 方式调用,QPS 压测结果近 5w/s,TP999 1ms。
+
+#### Tinyid(滴滴)
+
+[Tinyid](https://github.com/didi/tinyid) 是滴滴开源的一款基于数据库号段模式的唯一 ID 生成器。
+
+数据库号段模式的原理我们在上面已经介绍过了。**Tinyid 有哪些亮点呢?**
+
+为了搞清楚这个问题,我们先来看看基于数据库号段模式的简单架构方案。(图片来自于 Tinyid 的官方 wiki:[《Tinyid 原理介绍》](https://github.com/didi/tinyid/wiki/tinyid%E5%8E%9F%E7%90%86%E4%BB%8B%E7%BB%8D))
+
+
+
+在这种架构模式下,我们通过 HTTP 请求向发号器服务申请唯一 ID。负载均衡 router 会把我们的请求送往其中的一台 tinyid-server。
+
+这种方案有什么问题呢?在我看来(Tinyid 官方 wiki 也有介绍到),主要由下面这 2 个问题:
+
+- 获取新号段的情况下,程序获取唯一 ID 的速度比较慢。
+- 需要保证 DB 高可用,这个是比较麻烦且耗费资源的。
+
+除此之外,HTTP 调用也存在网络开销。
+
+Tinyid 的原理比较简单,其架构如下图所示:
+
+
+
+相比于基于数据库号段模式的简单架构方案,Tinyid 方案主要做了下面这些优化:
+
+- **双号段缓存** :为了避免在获取新号段的情况下,程序获取唯一 ID 的速度比较慢。 Tinyid 中的号段在用到一定程度的时候,就会去异步加载下一个号段,保证内存中始终有可用号段。
+- **增加多 db 支持** :支持多个 DB,并且,每个 DB 都能生成唯一 ID,提高了可用性。
+- **增加 tinyid-client** :纯本地操作,无 HTTP 请求消耗,性能和可用性都有很大提升。
+
+Tinyid 的优缺点这里就不分析了,结合数据库号段模式的优缺点和 Tinyid 的原理就能知道。
+
+## 分布式 ID 生成方案总结
+
+这篇文章中,我基本上已经把最常见的分布式 ID 生成方案都总结了一波。
+
+除了上面介绍的方式之外,像 ZooKeeper 这类中间件也可以帮助我们生成唯一 ID。**没有银弹,一定要结合实际项目来选择最适合自己的方案。**
\ No newline at end of file
diff --git a/docs/distributed-system/distributed-transaction.md b/docs/distributed-system/distributed-transaction.md
new file mode 100644
index 00000000000..985fefbee09
--- /dev/null
+++ b/docs/distributed-system/distributed-transaction.md
@@ -0,0 +1,7 @@
+# 分布式事务
+
+这部分内容为我的星球专属,已经整理到了[《Java面试进阶指北 打造个人的技术竞争力》](https://www.yuque.com/docs/share/f37fc804-bfe6-4b0d-b373-9c462188fec7?# )中。
+
+欢迎加入我的星球,[一个纯 Java 面试交流圈子 !Ready!](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=100015911&idx=1&sn=2e8a0f5acb749ecbcbb417aa8a4e18cc&chksm=4ea1b0ec79d639fae37df1b86f196e8ce397accfd1dd2004bcadb66b4df5f582d90ae0d62448#rd) (点击链接了解星球详细信息,还有专属优惠款可以领取)。
+
+
diff --git a/docs/distributed-system/rpc/dubbo.md b/docs/distributed-system/rpc/dubbo.md
new file mode 100644
index 00000000000..870cc45dc5f
--- /dev/null
+++ b/docs/distributed-system/rpc/dubbo.md
@@ -0,0 +1,501 @@
+# Dubbo知识点&面试题总结
+
+这篇文章是我根据官方文档以及自己平时的使用情况,对 Dubbo 所做的一个总结。欢迎补充!
+
+## RPC基础
+
+### 何为 RPC?
+
+**RPC(Remote Procedure Call)** 即远程过程调用,通过名字我们就能看出 RPC 关注的是远程调用而非本地调用。
+
+**为什么要 RPC ?** 因为,两个不同的服务器上的服务提供的方法不在一个内存空间,所以,需要通过网络编程才能传递方法调用所需要的参数。并且,方法调用的结果也需要通过网络编程来接收。但是,如果我们自己手动网络编程来实现这个调用过程的话工作量是非常大的,因为,我们需要考虑底层传输方式(TCP还是UDP)、序列化方式等等方面。
+
+
+**RPC 能帮助我们做什么呢?** 简单来说,通过 RPC 可以帮助我们调用远程计算机上某个服务的方法,这个过程就像调用本地方法一样简单。并且!我们不需要了解底层网络编程的具体细节。
+
+
+举个例子:两个不同的服务 A、B 部署在两台不同的机器上,服务 A 如果想要调用服务 B 中的某个方法的话就可以通过 RPC 来做。
+
+一言蔽之:**RPC 的出现就是为了让你调用远程方法像调用本地方法一样简单。**
+
+### RPC 的原理是什么?
+
+为了能够帮助小伙伴们理解 RPC 原理,我们可以将整个 RPC的 核心功能看作是下面👇 6 个部分实现的:
+
+
+1. **客户端(服务消费端)** :调用远程方法的一端。
+1. **客户端 Stub(桩)** : 这其实就是一代理类。代理类主要做的事情很简单,就是把你调用方法、类、方法参数等信息传递到服务端。
+1. **网络传输** : 网络传输就是你要把你调用的方法的信息比如说参数啊这些东西传输到服务端,然后服务端执行完之后再把返回结果通过网络传输给你传输回来。网络传输的实现方式有很多种比如最近基本的 Socket或者性能以及封装更加优秀的 Netty(推荐)。
+1. **服务端 Stub(桩)** :这个桩就不是代理类了。我觉得理解为桩实际不太好,大家注意一下就好。这里的服务端 Stub 实际指的就是接收到客户端执行方法的请求后,去指定对应的方法然后返回结果给客户端的类。
+1. **服务端(服务提供端)** :提供远程方法的一端。
+
+具体原理图如下,后面我会串起来将整个RPC的过程给大家说一下。
+
+
+
+
+1. 服务消费端(client)以本地调用的方式调用远程服务;
+1. 客户端 Stub(client stub) 接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体(序列化):`RpcRequest`;
+1. 客户端 Stub(client stub) 找到远程服务的地址,并将消息发送到服务提供端;
+1. 服务端 Stub(桩)收到消息将消息反序列化为Java对象: `RpcRequest`;
+1. 服务端 Stub(桩)根据`RpcRequest`中的类、方法、方法参数等信息调用本地的方法;
+1. 服务端 Stub(桩)得到方法执行结果并将组装成能够进行网络传输的消息体:`RpcResponse`(序列化)发送至消费方;
+1. 客户端 Stub(client stub)接收到消息并将消息反序列化为Java对象:`RpcResponse` ,这样也就得到了最终结果。over!
+
+相信小伙伴们看完上面的讲解之后,已经了解了 RPC 的原理。
+
+虽然篇幅不多,但是基本把 RPC 框架的核心原理讲清楚了!另外,对于上面的技术细节,我会在后面的章节介绍到。
+
+**最后,对于 RPC 的原理,希望小伙伴不单单要理解,还要能够自己画出来并且能够给别人讲出来。因为,在面试中这个问题在面试官问到 RPC 相关内容的时候基本都会碰到。**
+
+## Dubbo基础
+
+### 什么是 Dubbo?
+
+
+
+[Apache Dubbo](https://github.com/apache/dubbo) |ˈdʌbəʊ| 是一款高性能、轻量级的开源 Java RPC 框架。
+
+根据 [Dubbo 官方文档](https://dubbo.apache.org/zh/)的介绍,Dubbo 提供了六大核心能力
+
+1. 面向接口代理的高性能RPC调用。
+2. 智能容错和负载均衡。
+3. 服务自动注册和发现。
+4. 高度可扩展能力。
+5. 运行期流量调度。
+6. 可视化的服务治理与运维。
+
+
+
+简单来说就是: **Dubbo 不光可以帮助我们调用远程服务,还提供了一些其他开箱即用的功能比如智能负载均衡。**
+
+Dubbo 目前已经有接近 34.4 k 的 Star 。
+
+在 **2020 年度 OSC 中国开源项目** 评选活动中,Dubbo 位列开发框架和基础组件类项目的第7名。想比几年前来说,热度和排名有所下降。
+
+
+
+Dubbo 是由阿里开源,后来加入了 Apache 。正式由于 Dubbo 的出现,才使得越来越多的公司开始使用以及接受分布式架构。
+
+### 为什么要用 Dubbo?
+
+随着互联网的发展,网站的规模越来越大,用户数量越来越多。单一应用架构 、垂直应用架构无法满足我们的需求,这个时候分布式服务架构就诞生了。
+
+分布式服务架构下,系统被拆分成不同的服务比如短信服务、安全服务,每个服务独立提供系统的某个核心服务。
+
+我们可以使用 Java RMI(Java Remote Method Invocation)、Hessian这种支持远程调用的框架来简单地暴露和引用远程服务。但是!当服务越来越多之后,服务调用关系越来越复杂。当应用访问压力越来越大后,负载均衡以及服务监控的需求也迫在眉睫。我们可以用 F5 这类硬件来做负载均衡,但这样增加了成本,并且存在单点故障的风险。
+
+不过,Dubbo 的出现让上述问题得到了解决。**Dubbo 帮助我们解决了什么问题呢?**
+
+1. **负载均衡** : 同一个服务部署在不同的机器时该调用哪一台机器上的服务。
+2. **服务调用链路生成** : 随着系统的发展,服务越来越多,服务间依赖关系变得错踪复杂,甚至分不清哪个应用要在哪个应用之前启动,架构师都不能完整的描述应用的架构关系。Dubbo 可以为我们解决服务之间互相是如何调用的。
+3. **服务访问压力以及时长统计、资源调度和治理** :基于访问压力实时管理集群容量,提高集群利用率。
+4. ......
+
+
+
+另外,Dubbo 除了能够应用在分布式系统中,也可以应用在现在比较火的微服务系统中。不过,由于 Spring Cloud 在微服务中应用更加广泛,所以,我觉得一般我们提 Dubbo 的话,大部分是分布式系统的情况。
+
+**我们刚刚提到了分布式这个概念,下面再给大家介绍一下什么是分布式?为什么要分布式?**
+
+## 分布式基础
+
+### 什么是分布式?
+
+分布式或者说 SOA 分布式重要的就是面向服务,说简单的分布式就是我们把整个系统拆分成不同的服务然后将这些服务放在不同的服务器上减轻单体服务的压力提高并发量和性能。比如电商系统可以简单地拆分成订单系统、商品系统、登录系统等等,拆分之后的每个服务可以部署在不同的机器上,如果某一个服务的访问量比较大的话也可以将这个服务同时部署在多台机器上。
+
+
+
+### 为什么要分布式?
+
+从开发角度来讲单体应用的代码都集中在一起,而分布式系统的代码根据业务被拆分。所以,每个团队可以负责一个服务的开发,这样提升了开发效率。另外,代码根据业务拆分之后更加便于维护和扩展。
+
+另外,我觉得将系统拆分成分布式之后不光便于系统扩展和维护,更能提高整个系统的性能。你想一想嘛?把整个系统拆分成不同的服务/系统,然后每个服务/系统 单独部署在一台服务器上,是不是很大程度上提高了系统性能呢?
+
+## Dubbo 架构
+
+### Dubbo 架构中的核心角色有哪些?
+
+[官方文档中的框架设计章节](https://dubbo.apache.org/zh/docs/v2.7/dev/design/) 已经介绍的非常详细了,我这里把一些比较重要的点再提一下。
+
+
+
+上述节点简单介绍以及他们之间的关系:
+
+- **Container:** 服务运行容器,负责加载、运行服务提供者。必须。
+- **Provider:** 暴露服务的服务提供方,会向注册中心注册自己提供的服务。必须。
+- **Consumer:** 调用远程服务的服务消费方,会向注册中心订阅自己所需的服务。必须。
+- **Registry:** 服务注册与发现的注册中心。注册中心会返回服务提供者地址列表给消费者。非必须。
+- **Monitor:** 统计服务的调用次数和调用时间的监控中心。服务消费者和提供者会定时发送统计数据到监控中心。 非必须。
+
+### Dubbo 中的 Invoker 概念了解么?
+
+`Invoker` 是 Dubbo 领域模型中非常重要的一个概念,你如果阅读过 Dubbo 源码的话,你会无数次看到这玩意。就比如下面我要说的负载均衡这块的源码中就有大量 `Invoker` 的身影。
+
+简单来说,`Invoker` 就是 Dubbo 对远程调用的抽象。
+
+
+
+按照 Dubbo 官方的话来说,`Invoker` 分为
+
+- 服务提供 `Invoker`
+- 服务消费 `Invoker`
+
+假如我们需要调用一个远程方法,我们需要动态代理来屏蔽远程调用的细节吧!我们屏蔽掉的这些细节就依赖对应的 `Invoker` 实现, `Invoker` 实现了真正的远程服务调用。
+
+### Dubbo 的工作原理了解么?
+
+下图是 Dubbo 的整体设计,从下至上分为十层,各层均为单向依赖。
+
+> 左边淡蓝背景的为服务消费方使用的接口,右边淡绿色背景的为服务提供方使用的接口,位于中轴线上的为双方都用到的接口。
+
+
+
+- **config 配置层**:Dubbo相关的配置。支持代码配置,同时也支持基于 Spring 来做配置,以 `ServiceConfig`, `ReferenceConfig` 为中心
+- **proxy 服务代理层**:调用远程方法像调用本地的方法一样简单的一个关键,真实调用过程依赖代理类,以 `ServiceProxy` 为中心。
+- **registry 注册中心层**:封装服务地址的注册与发现。
+- **cluster 路由层**:封装多个提供者的路由及负载均衡,并桥接注册中心,以 `Invoker` 为中心。
+- **monitor 监控层**:RPC 调用次数和调用时间监控,以 `Statistics` 为中心。
+- **protocol 远程调用层**:封装 RPC 调用,以 `Invocation`, `Result` 为中心。
+- **exchange 信息交换层**:封装请求响应模式,同步转异步,以 `Request`, `Response` 为中心。
+- **transport 网络传输层**:抽象 mina 和 netty 为统一接口,以 `Message` 为中心。
+- **serialize 数据序列化层** :对需要在网络传输的数据进行序列化。
+
+### Dubbo 的 SPI 机制了解么? 如何扩展 Dubbo 中的默认实现?
+
+SPI(Service Provider Interface) 机制被大量用在开源项目中,它可以帮助我们动态寻找服务/功能(比如负载均衡策略)的实现。
+
+SPI 的具体原理是这样的:我们将接口的实现类放在配置文件中,我们在程序运行过程中读取配置文件,通过反射加载实现类。这样,我们可以在运行的时候,动态替换接口的实现类。和 IoC 的解耦思想是类似的。
+
+Java 本身就提供了 SPI 机制的实现。不过,Dubbo 没有直接用,而是对 Java原生的 SPI机制进行了增强,以便更好满足自己的需求。
+
+**那我们如何扩展 Dubbo 中的默认实现呢?**
+
+比如说我们想要实现自己的负载均衡策略,我们创建对应的实现类 `XxxLoadBalance` 实现 `LoadBalance` 接口或者 `AbstractLoadBalance` 类。
+
+```java
+package com.xxx;
+
+import org.apache.dubbo.rpc.cluster.LoadBalance;
+import org.apache.dubbo.rpc.Invoker;
+import org.apache.dubbo.rpc.Invocation;
+import org.apache.dubbo.rpc.RpcException;
+
+public class XxxLoadBalance implements LoadBalance {
+ public Invoker select(List> invokers, Invocation invocation) throws RpcException {
+ // ...
+ }
+}
+```
+
+我们将这个实现类的路径写入到`resources` 目录下的 `META-INF/dubbo/org.apache.dubbo.rpc.cluster.LoadBalance`文件中即可。
+
+```java
+src
+ |-main
+ |-java
+ |-com
+ |-xxx
+ |-XxxLoadBalance.java (实现LoadBalance接口)
+ |-resources
+ |-META-INF
+ |-dubbo
+ |-org.apache.dubbo.rpc.cluster.LoadBalance (纯文本文件,内容为:xxx=com.xxx.XxxLoadBalance)
+```
+
+`org.apache.dubbo.rpc.cluster.LoadBalance`
+
+```
+xxx=com.xxx.XxxLoadBalance
+```
+
+其他还有很多可供扩展的选择,你可以在[官方文档@SPI扩展实现](https://dubbo.apache.org/zh/docs/v2.7/dev/impls/)这里找到。
+
+
+
+### Dubbo 的微内核架构了解吗?
+
+Dubbo 采用 微内核(Microkernel) + 插件(Plugin) 模式,简单来说就是微内核架构。微内核只负责组装插件。
+
+**何为微内核架构呢?** 《软件架构模式》 这本书是这样介绍的:
+
+> 微内核架构模式(有时被称为插件架构模式)是实现基于产品应用程序的一种自然模式。基于产品的应用程序是已经打包好并且拥有不同版本,可作为第三方插件下载的。然后,很多公司也在开发、发布自己内部商业应用像有版本号、说明及可加载插件式的应用软件(这也是这种模式的特征)。微内核系统可让用户添加额外的应用如插件,到核心应用,继而提供了可扩展性和功能分离的用法。
+
+微内核架构包含两类组件:**核心系统(core system)** 和 **插件模块(plug-in modules)**。
+
+
+
+核心系统提供系统所需核心能力,插件模块可以扩展系统的功能。因此, 基于微内核架构的系统,非常易于扩展功能。
+
+我们常见的一些IDE,都可以看作是基于微内核架构设计的。绝大多数 IDE比如IDEA、VSCode都提供了插件来丰富自己的功能。
+
+正是因为Dubbo基于微内核架构,才使得我们可以随心所欲替换Dubbo的功能点。比如你觉得Dubbo 的序列化模块实现的不满足自己要求,没关系啊!你自己实现一个序列化模块就好了啊!
+
+通常情况下,微核心都会采用 Factory、IoC、OSGi 等方式管理插件生命周期。Dubbo 不想依赖 Spring 等 IoC 容器,也不想自己造一个小的 IoC 容器(过度设计),因此采用了一种最简单的 Factory 方式管理插件 :**JDK 标准的 SPI 扩展机制** (`java.util.ServiceLoader`)。
+
+### 关于Dubbo架构的一些自测小问题
+
+#### 注册中心的作用了解么?
+
+注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互。
+
+#### 服务提供者宕机后,注册中心会做什么?
+
+注册中心会立即推送事件通知消费者。
+
+#### 监控中心的作用呢?
+
+监控中心负责统计各服务调用次数,调用时间等。
+
+#### 注册中心和监控中心都宕机的话,服务都会挂掉吗?
+
+不会。两者都宕机也不影响已运行的提供者和消费者,消费者在本地缓存了提供者列表。注册中心和监控中心都是可选的,服务消费者可以直连服务提供者。
+
+
+## Dubbo 的负载均衡策略
+
+### 什么是负载均衡?
+
+先来看一下稍微官方点的解释。下面这段话摘自维基百科对负载均衡的定义:
+
+> 负载均衡改善了跨多个计算资源(例如计算机,计算机集群,网络链接,中央处理单元或磁盘驱动)的工作负载分布。负载平衡旨在优化资源使用,最大化吞吐量,最小化响应时间,并避免任何单个资源的过载。使用具有负载平衡而不是单个组件的多个组件可以通过冗余提高可靠性和可用性。负载平衡通常涉及专用软件或硬件。
+
+**上面讲的大家可能不太好理解,再用通俗的话给大家说一下。**
+
+我们的系统中的某个服务的访问量特别大,我们将这个服务部署在了多台服务器上,当客户端发起请求的时候,多台服务器都可以处理这个请求。那么,如何正确选择处理该请求的服务器就很关键。假如,你就要一台服务器来处理该服务的请求,那该服务部署在多台服务器的意义就不复存在了。负载均衡就是为了避免单个服务器响应同一请求,容易造成服务器宕机、崩溃等问题,我们从负载均衡的这四个字就能明显感受到它的意义。
+
+### Dubbo 提供的负载均衡策略有哪些?
+
+在集群负载均衡时,Dubbo 提供了多种均衡策略,默认为 `random` 随机调用。我们还可以自行扩展负载均衡策略(参考Dubbo SPI机制)。
+
+在 Dubbo 中,所有负载均衡实现类均继承自 `AbstractLoadBalance`,该类实现了 `LoadBalance` 接口,并封装了一些公共的逻辑。
+
+```java
+public abstract class AbstractLoadBalance implements LoadBalance {
+
+ static int calculateWarmupWeight(int uptime, int warmup, int weight) {
+ }
+
+ @Override
+ public Invoker select(List> invokers, URL url, Invocation invocation) {
+ }
+
+ protected abstract Invoker doSelect(List> invokers, URL url, Invocation invocation);
+
+
+ int getWeight(Invoker> invoker, Invocation invocation) {
+
+ }
+}
+```
+
+`AbstractLoadBalance` 的实现类有下面这些:
+
+
+
+官方文档对负载均衡这部分的介绍非常详细,推荐小伙伴们看看,地址:[https://dubbo.apache.org/zh/docs/v2.7/dev/source/loadbalance/#m-zhdocsv27devsourceloadbalance](https://dubbo.apache.org/zh/docs/v2.7/dev/source/loadbalance/#m-zhdocsv27devsourceloadbalance ) 。
+
+#### RandomLoadBalance
+
+根据权重随机选择(对加权随机算法的实现)。这是Dubbo默认采用的一种负载均衡策略。
+
+` RandomLoadBalance` 具体的实现原理非常简单,假如有两个提供相同服务的服务器 S1,S2,S1的权重为7,S2的权重为3。
+
+我们把这些权重值分布在坐标区间会得到:S1->[0, 7) ,S2->[7, 10)。我们生成[0, 10) 之间的随机数,随机数落到对应的区间,我们就选择对应的服务器来处理请求。
+
+
+
+`RandomLoadBalance` 的源码非常简单,简单花几分钟时间看一下。
+
+> 以下源码来自 Dubbo master 分支上的最新的版本 2.7.9。
+
+```java
+public class RandomLoadBalance extends AbstractLoadBalance {
+
+ public static final String NAME = "random";
+
+ @Override
+ protected Invoker doSelect(List> invokers, URL url, Invocation invocation) {
+
+ int length = invokers.size();
+ boolean sameWeight = true;
+ int[] weights = new int[length];
+ int totalWeight = 0;
+ // 下面这个for循环的主要作用就是计算所有该服务的提供者的权重之和 totalWeight(),
+ // 除此之外,还会检测每个服务提供者的权重是否相同
+ for (int i = 0; i < length; i++) {
+ int weight = getWeight(invokers.get(i), invocation);
+ totalWeight += weight;
+ weights[i] = totalWeight;
+ if (sameWeight && totalWeight != weight * (i + 1)) {
+ sameWeight = false;
+ }
+ }
+ if (totalWeight > 0 && !sameWeight) {
+ // 随机生成一个 [0, totalWeight) 区间内的数字
+ int offset = ThreadLocalRandom.current().nextInt(totalWeight);
+ // 判断会落在哪个服务提供者的区间
+ for (int i = 0; i < length; i++) {
+ if (offset < weights[i]) {
+ return invokers.get(i);
+ }
+ }
+
+ return invokers.get(ThreadLocalRandom.current().nextInt(length));
+ }
+
+}
+
+```
+
+#### LeastActiveLoadBalance
+
+`LeastActiveLoadBalance` 直译过来就是**最小活跃数负载均衡**。
+
+这个名字起得有点不直观,不仔细看官方对活跃数的定义,你压根不知道这玩意是干嘛的。
+
+我这么说吧!初始状态下所有服务提供者的活跃数均为 0(每个服务提供者的中特定方法都对应一个活跃数,我在后面的源码中会提到),每收到一个请求后,对应的服务提供者的活跃数 +1,当这个请求处理完之后,活跃数 -1。
+
+因此,**Dubbo 就认为谁的活跃数越少,谁的处理速度就越快,性能也越好,这样的话,我就优先把请求给活跃数少的服务提供者处理。**
+
+**如果有多个服务提供者的活跃数相等怎么办?**
+
+很简单,那就再走一遍 `RandomLoadBalance` 。
+
+```java
+public class LeastActiveLoadBalance extends AbstractLoadBalance {
+
+ public static final String NAME = "leastactive";
+
+ @Override
+ protected Invoker doSelect(List> invokers, URL url, Invocation invocation) {
+ int length = invokers.size();
+ int leastActive = -1;
+ int leastCount = 0;
+ int[] leastIndexes = new int[length];
+ int[] weights = new int[length];
+ int totalWeight = 0;
+ int firstWeight = 0;
+ boolean sameWeight = true;
+ // 这个 for 循环的主要作用是遍历 invokers 列表,找出活跃数最小的 Invoker
+ // 如果有多个 Invoker 具有相同的最小活跃数,还会记录下这些 Invoker 在 invokers 集合中的下标,并累加它们的权重,比较它们的权重值是否相等
+ for (int i = 0; i < length; i++) {
+ Invoker invoker = invokers.get(i);
+ // 获取 invoker 对应的活跃(active)数
+ int active = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName()).getActive();
+ int afterWarmup = getWeight(invoker, invocation);
+ weights[i] = afterWarmup;
+ if (leastActive == -1 || active < leastActive) {
+ leastActive = active;
+ leastCount = 1;
+ leastIndexes[0] = i;
+ totalWeight = afterWarmup;
+ firstWeight = afterWarmup;
+ sameWeight = true;
+ } else if (active == leastActive) {
+ leastIndexes[leastCount++] = i;
+ totalWeight += afterWarmup;
+ if (sameWeight && afterWarmup != firstWeight) {
+ sameWeight = false;
+ }
+ }
+ }
+ // 如果只有一个 Invoker 具有最小的活跃数,此时直接返回该 Invoker 即可
+ if (leastCount == 1) {
+ return invokers.get(leastIndexes[0]);
+ }
+ // 如果有多个 Invoker 具有相同的最小活跃数,但它们之间的权重不同
+ // 这里的处理方式就和 RandomLoadBalance 一致了
+ if (!sameWeight && totalWeight > 0) {
+ int offsetWeight = ThreadLocalRandom.current().nextInt(totalWeight);
+ for (int i = 0; i < leastCount; i++) {
+ int leastIndex = leastIndexes[i];
+ offsetWeight -= weights[leastIndex];
+ if (offsetWeight < 0) {
+ return invokers.get(leastIndex);
+ }
+ }
+ }
+ return invokers.get(leastIndexes[ThreadLocalRandom.current().nextInt(leastCount)]);
+ }
+}
+
+```
+
+活跃数是通过 `RpcStatus` 中的一个 `ConcurrentMap` 保存的,根据 URL 以及服务提供者被调用的方法的名称,我们便可以获取到对应的活跃数。也就是说服务提供者中的每一个方法的活跃数都是互相独立的。
+
+```java
+public class RpcStatus {
+
+ private static final ConcurrentMap> METHOD_STATISTICS =
+ new ConcurrentHashMap>();
+
+ public static RpcStatus getStatus(URL url, String methodName) {
+ String uri = url.toIdentityString();
+ ConcurrentMap map = METHOD_STATISTICS.computeIfAbsent(uri, k -> new ConcurrentHashMap<>());
+ return map.computeIfAbsent(methodName, k -> new RpcStatus());
+ }
+ public int getActive() {
+ return active.get();
+ }
+
+}
+```
+
+#### ConsistentHashLoadBalance
+
+`ConsistentHashLoadBalance` 小伙伴们应该也不会陌生,在分库分表、各种集群中就经常使用这个负载均衡策略。
+
+`ConsistentHashLoadBalance` 即**一致性Hash负载均衡策略**。 `ConsistentHashLoadBalance` 中没有权重的概念,具体是哪个服务提供者处理请求是由你的请求的参数决定的,也就是说相同参数的请求总是发到同一个服务提供者。
+
+
+
+另外,Dubbo 为了避免数据倾斜问题(节点不够分散,大量请求落到同一节点),还引入了虚拟节点的概念。通过虚拟节点可以让节点更加分散,有效均衡各个节点的请求量。
+
+
+
+
+
+官方有详细的源码分析:[https://dubbo.apache.org/zh/docs/v2.7/dev/source/loadbalance/#23-consistenthashloadbalance](https://dubbo.apache.org/zh/docs/v2.7/dev/source/loadbalance/#23-consistenthashloadbalance) 。这里还有一个相关的 [PR#5440](https://github.com/apache/dubbo/pull/5440) 来修复老版本中 ConsistentHashLoadBalance 存在的一些Bug。感兴趣的小伙伴,可以多花点时间研究一下。我这里不多分析了,这个作业留给你们!
+
+#### RoundRobinLoadBalance
+
+加权轮询负载均衡。
+
+轮询就是把请求依次分配给每个服务提供者。加权轮询就是在轮询的基础上,让更多的请求落到权重更大的服务提供者上。比如假如有两个提供相同服务的服务器 S1,S2,S1的权重为7,S2的权重为3。
+
+如果我们有 10 次请求,那么 7 次会被 S1处理,3次被 S2处理。
+
+但是,如果是 `RandomLoadBalance` 的话,很可能存在10次请求有9次都被 S1 处理的情况(概率性问题)。
+
+Dubbo 中的 `RoundRobinLoadBalance` 的代码实现被修改重建了好几次,Dubbo-2.6.5 版本的 `RoundRobinLoadBalance` 为平滑加权轮询算法。
+
+## Dubbo序列化协议
+
+### Dubbo 支持哪些序列化方式呢?
+
+
+
+Dubbo 支持多种序列化方式:JDK自带的序列化、hessian2、JSON、Kryo、FST、Protostuff,ProtoBuf等等。
+
+Dubbo 默认使用的序列化方式是 hession2。
+
+### 谈谈你对这些序列化协议了解?
+
+一般我们不会直接使用 JDK 自带的序列化方式。主要原因有两个:
+
+1. **不支持跨语言调用** : 如果调用的是其他语言开发的服务的时候就不支持了。
+2. **性能差** :相比于其他序列化框架性能更低,主要原因是序列化之后的字节数组体积较大,导致传输成本加大。
+
+JSON 序列化由于性能问题,我们一般也不会考虑使用。
+
+像 Protostuff,ProtoBuf、hessian2这些都是跨语言的序列化方式,如果有跨语言需求的话可以考虑使用。
+
+Kryo和FST这两种序列化方式是 Dubbo 后来才引入的,性能非常好。不过,这两者都是专门针对 Java 语言的。Dubbo 官网的一篇文章中提到说推荐使用 Kryo 作为生产环境的序列化方式。(文章地址:[https://dubbo.apache.org/zh/docs/v2.7/user/references/protocol/rest/](https://dubbo.apache.org/zh/docs/v2.7/user/references/protocol/rest/))
+
+
+
+Dubbo 官方文档中还有一个关于这些[序列化协议的性能对比图](https://dubbo.apache.org/zh/docs/v2.7/user/serialization/#m-zhdocsv27userserialization)可供参考。
+
+
+
diff --git a/docs/system-design/data-communication/why-use-rpc.md b/docs/distributed-system/rpc/why-use-rpc.md
similarity index 53%
rename from docs/system-design/data-communication/why-use-rpc.md
rename to docs/distributed-system/rpc/why-use-rpc.md
index f4443ddd103..a2fe5dbefa2 100644
--- a/docs/system-design/data-communication/why-use-rpc.md
+++ b/docs/distributed-system/rpc/why-use-rpc.md
@@ -1,3 +1,5 @@
+# 服务之间的调用为啥不直接用 HTTP 而用 RPC?
+
## 什么是 RPC?RPC原理是什么?
### **什么是 RPC?**
@@ -6,25 +8,19 @@ RPC(Remote Procedure Call)—远程过程调用,它是一种通过网络
### **RPC原理是什么?**
-我这里这是简单的提一下,详细内容可以查看下面这篇文章:
-
-http://www.importnew.com/22003.html
-
-
+
-1. 服务消费方(client)调用以本地调用方式调用服务;
-2. client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体;
-3. client stub找到服务地址,并将消息发送到服务端;
-4. server stub收到消息后进行解码;
-5. server stub根据解码结果调用本地的服务;
-6. 本地服务执行并将结果返回给server stub;
-7. server stub将返回结果打包成消息并发送至消费方;
-8. client stub接收到消息,并进行解码;
-9. 服务消费方得到最终结果。
+1. 服务消费端(client)以本地调用的方式调用远程服务;
+2. 客户端 Stub(client stub) 接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体(序列化):`RpcRequest`;
+3. 客户端 Stub(client stub) 找到远程服务的地址,并将消息发送到服务提供端;
+4. 服务端 Stub(桩)收到消息将消息反序列化为Java对象: `RpcRequest`;
+5. 服务端 Stub(桩)根据`RpcRequest`中的类、方法、方法参数等信息调用本地的方法;
+6. 服务端 Stub(桩)得到方法执行结果并将组装成能够进行网络传输的消息体:`RpcResponse`(序列化)发送至消费方;
+7. 客户端 Stub(client stub)接收到消息并将消息反序列化为Java对象:`RpcResponse` ,这样也就得到了最终结果。
-下面再贴一个网上的时序图:
+下面再贴一个网上的时序图,辅助理解:
-
+
### RPC 解决了什么问题?
@@ -35,17 +31,20 @@ http://www.importnew.com/22003.html
- **RMI(JDK自带):** JDK自带的RPC,有很多局限性,不推荐使用。
- **Dubbo:** Dubbo是 阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和 Spring框架无缝集成。目前 Dubbo 已经成为 Spring Cloud Alibaba 中的官方组件。
- **gRPC** :gRPC是可以在任何环境中运行的现代开源高性能RPC框架。它可以通过可插拔的支持来有效地连接数据中心内和跨数据中心的服务,以实现负载平衡,跟踪,运行状况检查和身份验证。它也适用于分布式计算的最后一英里,以将设备,移动应用程序和浏览器连接到后端服务。
-
-- **Hessian:** Hessian是一个轻量级的remotingonhttp工具,使用简单的方法提供了RMI的功能。 相比WebService,Hessian更简单、快捷。采用的是二进制RPC协议,因为采用的是二进制协议,所以它很适合于发送二进制数据。
+- **Hessian:** Hessian是一个轻量级的remoting on http工具,使用简单的方法提供了RMI的功能。 相比WebService,Hessian更简单、快捷。采用的是二进制RPC协议,因为采用的是二进制协议,所以它很适合于发送二进制数据。
- **Thrift:** Apache Thrift是Facebook开源的跨语言的RPC通信框架,目前已经捐献给Apache基金会管理,由于其跨语言特性和出色的性能,在很多互联网公司得到应用,有能力的公司甚至会基于thrift研发一套分布式服务框架,增加诸如服务注册、服务发现等功能。
+### RPC学习材料
+
+- [跟着 Guide 哥造轮子](https://github.com/Snailclimb/guide-rpc-framework)
+
## 既有 HTTP ,为啥用 RPC 进行服务调用?
-###RPC 只是一种设计而已
+### RPC 只是一种设计而已
RPC 只是一种概念、一种设计,就是为了解决 **不同服务之间的调用问题**, 它一般会包含有 **传输协议** 和 **序列化协议** 这两个。
-实现 RPC 的可以传输协议可以直接建立在 TCP 之上,也可以建立在 HTTP 协议之上。**大部分 RPC 框架都是使用的 TCP 连接(gRPC使用了HTTP2)。**
+但是,HTTP 是一种协议,RPC框架可以使用 HTTP协议作为传输协议或者直接使用TCP作为传输协议,使用不同的协议一般也是为了适应不同的场景。
### HTTP 和 TCP
@@ -53,31 +52,13 @@ RPC 只是一种概念、一种设计,就是为了解决 **不同服务之间
> 我们通常谈计算机网络的五层协议的体系结构是指:应用层、传输层、网络层、数据链路层、物理层。
>
-> **应用层(application-layer)的任务是通过应用进程间的交互来完成特定网络应用。**HTTP 属于应用层协议,它会基于TCP/IP通信协议来传递数据(HTML 文件, 图片文件, 查询结果等)。HTTP协议工作于客户端-服务端架构为上。浏览器作为HTTP客户端通过 URL 向HTTP服务端即WEB服务器发送所有请求。Web服务器根据接收到的请求后,向客户端发送响应信息。HTTP协议建立在 TCP 协议之上。
+> **应用层(application-layer)的任务是通过应用进程间的交互来完成特定网络应用。** HTTP 属于应用层协议,它会基于TCP/IP通信协议来传递数据(HTML 文件, 图片文件, 查询结果等)。HTTP协议工作于客户端-服务端架构上。浏览器作为HTTP客户端通过 URL 向HTTP服务端即WEB服务器发送所有请求。Web服务器根据接收到的请求后,向客户端发送响应信息。HTTP协议建立在 TCP 协议之上。
>
-> **运输层(transport layer)的主要任务就是负责向两台主机进程之间的通信提供通用的数据传输服务**。TCP是传输层协议,主要解决数据如何在网络中传输。相比于UDP,**TCP** 提供的是**面向连接**的,**可靠的**数据传输服务。
+> **传输层(transport layer)的主要任务就是负责向两台主机进程之间的通信提供通用的数据传输服务**。TCP是传输层协议,主要解决数据如何在网络中传输。相比于UDP,**TCP** 提供的是**面向连接**的,**可靠的**数据传输服务。
-**主要关键就在 HTTP 使用的 TCP 协议,和我们自定义的 TCP 协议在报文上的区别。**
+### RPC框架功能更齐全
-**http1.1协议的 TCP 报文包含太多在传输过程中可能无用的信息:**
-
-```
-HTTP/1.0 200 OK
-Content-Type: text/plain
-Content-Length: 137582
-Expires: Thu, 05 Dec 1997 16:00:00 GMT
-Last-Modified: Wed, 5 August 1996 15:55:28 GMT
-Server: Apache 0.84
-
-
- Hello World
-
-```
-
-**使用自定义 TCP 协议进行传输就会避免上面这个问题,极大地减轻了传输数据的开销。**
-这也就是为什么通常会采用自定义 TCP 协议的 RPC
-来进行进行服务调用的真正原因。除此之外,成熟的 RPC
-框架还提供好了“服务自动注册与发现”、"智能负载均衡"、“可视化的服务治理和运维”、“运行期流量调度”等等功能,这些也算是选择
+成熟的 RPC框架还提供好了“服务自动注册与发现”、"智能负载均衡"、“可视化的服务治理和运维”、“运行期流量调度”等等功能,这些也算是选择
RPC 进行服务注册和发现的一方面原因吧!
**相关阅读:**
@@ -86,15 +67,9 @@ RPC 进行服务注册和发现的一方面原因吧!
### 一个常见的错误观点
-很多文章中还会提到说 HTTP 协议相较于自定义 TCP 报文协议,增加的开销在于连接的建立与断开,但是这个观点已经被否认,下面截取自知乎中一个回答,原回答地址:https://www.zhihu.com/question/41609070/answer/191965937。
+很多文章中还会提到说 HTTP 协议相较于自定义 TCP 报文协议,增加的开销在于连接的建立与断开,但是这个观点已经被否认,下面截取自知乎中一个回答,原回答地址:https://www.zhihu.com/question/41609070/answer/191965937 。
>首先要否认一点 HTTP 协议相较于自定义 TCP 报文协议,增加的开销在于连接的建立与断开。HTTP 协议是支持连接池复用的,也就是建立一定数量的连接不断开,并不会频繁的创建和销毁连接。二一要说的是 HTTP 也可以使用 Protobuf 这种二进制编码协议对内容进行编码,因此二者最大的区别还是在传输协议上。
-### 题外话
-
-除此之外,还需要注意的一点是 Spring Cloud Netflix 并没有使用 RPC
-框架来进行不同服务之间的调用,而是使用 HTTP 协议进行调用的,速度虽然不比 RPC
-,但是使用 HTTP 协议也会带来其他很多好处(这一点,可以自行查阅相关资料了解)。
-
diff --git "a/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/images/curator.png" "b/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/images/curator.png"
new file mode 100644
index 00000000000..28da0247ec3
Binary files /dev/null and "b/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/images/curator.png" differ
diff --git "a/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/images/watche\346\234\272\345\210\266.png" "b/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/images/watche\346\234\272\345\210\266.png"
new file mode 100644
index 00000000000..68144db1e54
Binary files /dev/null and "b/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/images/watche\346\234\272\345\210\266.png" differ
diff --git "a/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/images/znode-structure.png" "b/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/images/znode-structure.png"
new file mode 100644
index 00000000000..746c3f6e3a4
Binary files /dev/null and "b/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/images/znode-structure.png" differ
diff --git "a/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/images/zookeeper\351\233\206\347\276\244.png" "b/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/images/zookeeper\351\233\206\347\276\244.png"
new file mode 100644
index 00000000000..a3067cda18e
Binary files /dev/null and "b/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/images/zookeeper\351\233\206\347\276\244.png" differ
diff --git "a/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/images/zookeeper\351\233\206\347\276\244\344\270\255\347\232\204\350\247\222\350\211\262.png" "b/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/images/zookeeper\351\233\206\347\276\244\344\270\255\347\232\204\350\247\222\350\211\262.png"
new file mode 100644
index 00000000000..6b118fe08e1
Binary files /dev/null and "b/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/images/zookeeper\351\233\206\347\276\244\344\270\255\347\232\204\350\247\222\350\211\262.png" differ
diff --git "a/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/images/\350\277\236\346\216\245ZooKeeper\346\234\215\345\212\241.png" "b/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/images/\350\277\236\346\216\245ZooKeeper\346\234\215\345\212\241.png"
new file mode 100644
index 00000000000..d391329949d
Binary files /dev/null and "b/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/images/\350\277\236\346\216\245ZooKeeper\346\234\215\345\212\241.png" differ
diff --git "a/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/zookeeper-in-action.md" "b/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/zookeeper-in-action.md"
new file mode 100644
index 00000000000..71dad09a4a2
--- /dev/null
+++ "b/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/zookeeper-in-action.md"
@@ -0,0 +1,296 @@
+# ZooKeeper 实战
+
+## 1. 前言
+
+这篇文章简单给演示一下 ZooKeeper 常见命令的使用以及 ZooKeeper Java客户端 Curator 的基本使用。介绍到的内容都是最基本的操作,能满足日常工作的基本需要。
+
+如果文章有任何需要改善和完善的地方,欢迎在评论区指出,共同进步!
+
+## 2. ZooKeeper 安装和使用
+
+### 2.1. 使用Docker 安装 zookeeper
+
+**a.使用 Docker 下载 ZooKeeper**
+
+```shell
+docker pull zookeeper:3.5.8
+```
+
+**b.运行 ZooKeeper**
+
+```shell
+docker run -d --name zookeeper -p 2181:2181 zookeeper:3.5.8
+```
+
+### 2.2. 连接 ZooKeeper 服务
+
+**a.进入ZooKeeper容器中**
+
+先使用 `docker ps` 查看 ZooKeeper 的 ContainerID,然后使用 `docker exec -it ContainerID /bin/bash` 命令进入容器中。
+
+**b.先进入 bin 目录,然后通过 `./zkCli.sh -server 127.0.0.1:2181`命令连接ZooKeeper 服务**
+
+```bash
+root@eaf70fc620cb:/apache-zookeeper-3.5.8-bin# cd bin
+```
+
+如果你看到控制台成功打印出如下信息的话,说明你已经成功连接 ZooKeeper 服务。
+
+
+
+### 2.3. 常用命令演示
+
+#### 2.3.1. 查看常用命令(help 命令)
+
+通过 `help` 命令查看 ZooKeeper 常用命令
+
+#### 2.3.2. 创建节点(create 命令)
+
+通过 `create` 命令在根目录创建了 node1 节点,与它关联的字符串是"node1"
+
+```shell
+[zk: 127.0.0.1:2181(CONNECTED) 34] create /node1 “node1”
+```
+
+通过 `create` 命令在根目录创建了 node1 节点,与它关联的内容是数字 123
+
+```shell
+[zk: 127.0.0.1:2181(CONNECTED) 1] create /node1/node1.1 123
+Created /node1/node1.1
+```
+
+#### 2.3.3. 更新节点数据内容(set 命令)
+
+```shell
+[zk: 127.0.0.1:2181(CONNECTED) 11] set /node1 "set node1"
+```
+
+#### 2.3.4. 获取节点的数据(get 命令)
+
+`get` 命令可以获取指定节点的数据内容和节点的状态,可以看出我们通过 `set` 命令已经将节点数据内容改为 "set node1"。
+
+```shell
+set node1
+cZxid = 0x47
+ctime = Sun Jan 20 10:22:59 CST 2019
+mZxid = 0x4b
+mtime = Sun Jan 20 10:41:10 CST 2019
+pZxid = 0x4a
+cversion = 1
+dataVersion = 1
+aclVersion = 0
+ephemeralOwner = 0x0
+dataLength = 9
+numChildren = 1
+
+```
+
+#### 2.3.5. 查看某个目录下的子节点(ls 命令)
+
+通过 `ls` 命令查看根目录下的节点
+
+```shell
+[zk: 127.0.0.1:2181(CONNECTED) 37] ls /
+[dubbo, ZooKeeper, node1]
+```
+
+通过 `ls` 命令查看 node1 目录下的节点
+
+```shell
+[zk: 127.0.0.1:2181(CONNECTED) 5] ls /node1
+[node1.1]
+```
+
+ZooKeeper 中的 ls 命令和 linux 命令中的 ls 类似, 这个命令将列出绝对路径 path 下的所有子节点信息(列出 1 级,并不递归)
+
+#### 2.3.6. 查看节点状态(stat 命令)
+
+通过 `stat` 命令查看节点状态
+
+```shell
+[zk: 127.0.0.1:2181(CONNECTED) 10] stat /node1
+cZxid = 0x47
+ctime = Sun Jan 20 10:22:59 CST 2019
+mZxid = 0x47
+mtime = Sun Jan 20 10:22:59 CST 2019
+pZxid = 0x4a
+cversion = 1
+dataVersion = 0
+aclVersion = 0
+ephemeralOwner = 0x0
+dataLength = 11
+numChildren = 1
+```
+
+上面显示的一些信息比如 cversion、aclVersion、numChildren 等等,我在上面 “znode(数据节点)的结构” 这部分已经介绍到。
+
+#### 2.3.7. 查看节点信息和状态(ls2 命令)
+
+`ls2` 命令更像是 `ls` 命令和 `stat` 命令的结合。 `ls2` 命令返回的信息包括 2 部分:
+
+1. 子节点列表
+2. 当前节点的 stat 信息。
+
+```shell
+[zk: 127.0.0.1:2181(CONNECTED) 7] ls2 /node1
+[node1.1]
+cZxid = 0x47
+ctime = Sun Jan 20 10:22:59 CST 2019
+mZxid = 0x47
+mtime = Sun Jan 20 10:22:59 CST 2019
+pZxid = 0x4a
+cversion = 1
+dataVersion = 0
+aclVersion = 0
+ephemeralOwner = 0x0
+dataLength = 11
+numChildren = 1
+
+```
+
+#### 2.3.8. 删除节点(delete 命令)
+
+这个命令很简单,但是需要注意的一点是如果你要删除某一个节点,那么这个节点必须无子节点才行。
+
+```shell
+[zk: 127.0.0.1:2181(CONNECTED) 3] delete /node1/node1.1
+```
+
+在后面我会介绍到 Java 客户端 API 的使用以及开源 ZooKeeper 客户端 ZkClient 和 Curator 的使用。
+
+## 3. ZooKeeper Java客户端 Curator简单使用
+
+Curator 是Netflix公司开源的一套 ZooKeeper Java客户端框架,相比于 Zookeeper 自带的客户端 zookeeper 来说,Curator 的封装更加完善,各种 API 都可以比较方便地使用。
+
+
+
+下面我们就来简单地演示一下 Curator 的使用吧!
+
+Curator4.0+版本对ZooKeeper 3.5.x支持比较好。开始之前,请先将下面的依赖添加进你的项目。
+
+```xml
+
+ org.apache.curator
+ curator-framework
+ 4.2.0
+
+
+ org.apache.curator
+ curator-recipes
+ 4.2.0
+
+```
+
+### 3.1. 连接 ZooKeeper 客户端
+
+通过 `CuratorFrameworkFactory` 创建 `CuratorFramework` 对象,然后再调用 `CuratorFramework` 对象的 `start()` 方法即可!
+
+```java
+private static final int BASE_SLEEP_TIME = 1000;
+private static final int MAX_RETRIES = 3;
+
+// Retry strategy. Retry 3 times, and will increase the sleep time between retries.
+RetryPolicy retryPolicy = new ExponentialBackoffRetry(BASE_SLEEP_TIME, MAX_RETRIES);
+CuratorFramework zkClient = CuratorFrameworkFactory.builder()
+ // the server to connect to (can be a server list)
+ .connectString("127.0.0.1:2181")
+ .retryPolicy(retryPolicy)
+ .build();
+zkClient.start();
+```
+
+对于一些基本参数的说明:
+
+- `baseSleepTimeMs`:重试之间等待的初始时间
+- `maxRetries` :最大重试次数
+- `connectString` :要连接的服务器列表
+- `retryPolicy` :重试策略
+
+### 3.2. 数据节点的增删改查
+
+#### 3.2.1. 创建节点
+
+我们在 [ZooKeeper常见概念解读](./zookeeper-intro.md) 中介绍到,我们通常是将 znode 分为 4 大类:
+
+- **持久(PERSISTENT)节点** :一旦创建就一直存在即使 ZooKeeper 集群宕机,直到将其删除。
+- **临时(EPHEMERAL)节点** :临时节点的生命周期是与 **客户端会话(session)** 绑定的,**会话消失则节点消失** 。并且,临时节点 **只能做叶子节点** ,不能创建子节点。
+- **持久顺序(PERSISTENT_SEQUENTIAL)节点** :除了具有持久(PERSISTENT)节点的特性之外, 子节点的名称还具有顺序性。比如 `/node1/app0000000001` 、`/node1/app0000000002` 。
+- **临时顺序(EPHEMERAL_SEQUENTIAL)节点** :除了具备临时(EPHEMERAL)节点的特性之外,子节点的名称还具有顺序性。
+
+你在使用的ZooKeeper 的时候,会发现 `CreateMode` 类中实际有 7种 znode 类型 ,但是用的最多的还是上面介绍的 4 种。
+
+**a.创建持久化节点**
+
+你可以通过下面两种方式创建持久化的节点。
+
+```java
+//注意:下面的代码会报错,下文说了具体原因
+zkClient.create().forPath("/node1/00001");
+zkClient.create().withMode(CreateMode.PERSISTENT).forPath("/node1/00002");
+```
+
+但是,你运行上面的代码会报错,这是因为的父节点`node1`还未创建。
+
+你可以先创建父节点 `node1` ,然后再执行上面的代码就不会报错了。
+
+```java
+zkClient.create().forPath("/node1");
+```
+
+更推荐的方式是通过下面这行代码, **`creatingParentsIfNeeded()` 可以保证父节点不存在的时候自动创建父节点,这是非常有用的。**
+
+```java
+zkClient.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath("/node1/00001");
+```
+
+**b.创建临时节点**
+
+```java
+zkClient.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath("/node1/00001");
+```
+
+**c.创建节点并指定数据内容**
+
+```java
+zkClient.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath("/node1/00001","java".getBytes());
+zkClient.getData().forPath("/node1/00001");//获取节点的数据内容,获取到的是 byte数组
+```
+
+**d.检测节点是否创建成功**
+
+```java
+zkClient.checkExists().forPath("/node1/00001");//不为null的话,说明节点创建成功
+```
+
+#### 3.2.2. 删除节点
+
+**a.删除一个子节点**
+
+```java
+zkClient.delete().forPath("/node1/00001");
+```
+
+**b.删除一个节点以及其下的所有子节点**
+
+```java
+zkClient.delete().deletingChildrenIfNeeded().forPath("/node1");
+```
+
+#### 3.2.3. 获取/更新节点数据内容
+
+```java
+zkClient.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath("/node1/00001","java".getBytes());
+zkClient.getData().forPath("/node1/00001");//获取节点的数据内容
+zkClient.setData().forPath("/node1/00001","c++".getBytes());//更新节点数据内容
+```
+
+#### 3.2.4. 获取某个节点的所有子节点路径
+
+```java
+List childrenPaths = zkClient.getChildren().forPath("/node1");
+```
+
+
+
+
+
diff --git "a/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/zookeeper-intro.md" "b/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/zookeeper-intro.md"
new file mode 100644
index 00000000000..3ead663c4b3
--- /dev/null
+++ "b/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/zookeeper-intro.md"
@@ -0,0 +1,280 @@
+# ZooKeeper 相关概念总结(入门)
+
+## 1. 前言
+
+相信大家对 ZooKeeper 应该不算陌生。但是你真的了解 ZooKeeper 到底有啥用不?如果别人/面试官让你给他讲讲对于 ZooKeeper 的认识,你能回答到什么地步呢?
+
+拿我自己来说吧!我本人曾经使用 Dubbo 来做分布式项目的时候,使用了 ZooKeeper 作为注册中心。为了保证分布式系统能够同步访问某个资源,我还使用 ZooKeeper 做过分布式锁。另外,我在学习 Kafka 的时候,知道 Kafka 很多功能的实现依赖了 ZooKeeper。
+
+前几天,总结项目经验的时候,我突然问自己 ZooKeeper 到底是个什么东西?想了半天,脑海中只是简单的能浮现出几句话:
+
+1. ZooKeeper 可以被用作注册中心、分布式锁;
+2. ZooKeeper 是 Hadoop 生态系统的一员;
+3. 构建 ZooKeeper 集群的时候,使用的服务器最好是奇数台。
+
+由此可见,我对于 ZooKeeper 的理解仅仅是停留在了表面。
+
+所以,通过本文,希望带大家稍微详细的了解一下 ZooKeeper 。如果没有学过 ZooKeeper ,那么本文将会是你进入 ZooKeeper 大门的垫脚砖。如果你已经接触过 ZooKeeper ,那么本文将带你回顾一下 ZooKeeper 的一些基础概念。
+
+另外,本文不光会涉及到 ZooKeeper 的一些概念,后面的文章会介绍到 ZooKeeper 常见命令的使用以及使用 Apache Curator 作为 ZooKeeper 的客户端。
+
+_如果文章有任何需要改善和完善的地方,欢迎在评论区指出,共同进步!_
+
+## 2. ZooKeeper 介绍
+
+### 2.1. ZooKeeper 由来
+
+正式介绍 ZooKeeper 之前,我们先来看看 ZooKeeper 的由来,还挺有意思的。
+
+下面这段内容摘自《从 Paxos 到 ZooKeeper 》第四章第一节,推荐大家阅读一下:
+
+> ZooKeeper 最早起源于雅虎研究院的一个研究小组。在当时,研究人员发现,在雅虎内部很多大型系统基本都需要依赖一个类似的系统来进行分布式协调,但是这些系统往往都存在分布式单点问题。所以,雅虎的开发人员就试图开发一个通用的无单点问题的分布式协调框架,以便让开发人员将精力集中在处理业务逻辑上。
+>
+> 关于“ZooKeeper”这个项目的名字,其实也有一段趣闻。在立项初期,考虑到之前内部很多项目都是使用动物的名字来命名的(例如著名的 Pig 项目),雅虎的工程师希望给这个项目也取一个动物的名字。时任研究院的首席科学家 RaghuRamakrishnan 开玩笑地说:“在这样下去,我们这儿就变成动物园了!”此话一出,大家纷纷表示就叫动物园管理员吧一一一因为各个以动物命名的分布式组件放在一起,雅虎的整个分布式系统看上去就像一个大型的动物园了,而 ZooKeeper 正好要用来进行分布式环境的协调一一于是,ZooKeeper 的名字也就由此诞生了。
+
+### 2.2. ZooKeeper 概览
+
+ZooKeeper 是一个开源的**分布式协调服务**,它的设计目标是将那些复杂且容易出错的分布式一致性服务封装起来,构成一个高效可靠的原语集,并以一系列简单易用的接口提供给用户使用。
+
+> **原语:** 操作系统或计算机网络用语范畴。是由若干条指令组成的,用于完成一定功能的一个过程。具有不可分割性·即原语的执行必须是连续的,在执行过程中不允许被中断。
+
+**ZooKeeper 为我们提供了高可用、高性能、稳定的分布式数据一致性解决方案,通常被用于实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。**
+
+另外,**ZooKeeper 将数据保存在内存中,性能是非常棒的。 在“读”多于“写”的应用程序中尤其地高性能,因为“写”会导致所有的服务器间同步状态。(“读”多于“写”是协调服务的典型场景)。**
+
+### 2.3. ZooKeeper 特点
+
+- **顺序一致性:** 从同一客户端发起的事务请求,最终将会严格地按照顺序被应用到 ZooKeeper 中去。
+- **原子性:** 所有事务请求的处理结果在整个集群中所有机器上的应用情况是一致的,也就是说,要么整个集群中所有的机器都成功应用了某一个事务,要么都没有应用。
+- **单一系统映像 :** 无论客户端连到哪一个 ZooKeeper 服务器上,其看到的服务端数据模型都是一致的。
+- **可靠性:** 一旦一次更改请求被应用,更改的结果就会被持久化,直到被下一次更改覆盖。
+
+### 2.4. ZooKeeper 典型应用场景
+
+ZooKeeper 概览中,我们介绍到使用其通常被用于实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。
+
+下面选 3 个典型的应用场景来专门说说:
+
+1. **分布式锁** : 通过创建唯一节点获得分布式锁,当获得锁的一方执行完相关代码或者是挂掉之后就释放锁。
+2. **命名服务** :可以通过 ZooKeeper 的顺序节点生成全局唯一 ID
+3. **数据发布/订阅** :通过 **Watcher 机制** 可以很方便地实现数据发布/订阅。当你将数据发布到 ZooKeeper 被监听的节点上,其他机器可通过监听 ZooKeeper 上节点的变化来实现配置的动态更新。
+
+实际上,这些功能的实现基本都得益于 ZooKeeper 可以保存数据的功能,但是 ZooKeeper 不适合保存大量数据,这一点需要注意。
+
+### 2.5. 有哪些著名的开源项目用到了 ZooKeeper?
+
+1. **Kafka** : ZooKeeper 主要为 Kafka 提供 Broker 和 Topic 的注册以及多个 Partition 的负载均衡等功能。
+2. **Hbase** : ZooKeeper 为 Hbase 提供确保整个集群只有一个 Master 以及保存和提供 regionserver 状态信息(是否在线)等功能。
+3. **Hadoop** : ZooKeeper 为 Namenode 提供高可用支持。
+
+## 3. ZooKeeper 重要概念解读
+
+_破音:拿出小本本,下面的内容非常重要哦!_
+
+### 3.1. Data model(数据模型)
+
+ZooKeeper 数据模型采用层次化的多叉树形结构,每个节点上都可以存储数据,这些数据可以是数字、字符串或者是二级制序列。并且。每个节点还可以拥有 N 个子节点,最上层是根节点以“/”来代表。每个数据节点在 ZooKeeper 中被称为 **znode**,它是 ZooKeeper 中数据的最小单元。并且,每个 znode 都一个唯一的路径标识。
+
+强调一句:**ZooKeeper 主要是用来协调服务的,而不是用来存储业务数据的,所以不要放比较大的数据在 znode 上,ZooKeeper 给出的上限是每个结点的数据大小最大是 1M。**
+
+从下图可以更直观地看出:ZooKeeper 节点路径标识方式和 Unix 文件系统路径非常相似,都是由一系列使用斜杠"/"进行分割的路径表示,开发人员可以向这个节点中写入数据,也可以在节点下面创建子节点。这些操作我们后面都会介绍到。
+
+
+
+### 3.2. znode(数据节点)
+
+介绍了 ZooKeeper 树形数据模型之后,我们知道每个数据节点在 ZooKeeper 中被称为 **znode**,它是 ZooKeeper 中数据的最小单元。你要存放的数据就放在上面,是你使用 ZooKeeper 过程中经常需要接触到的一个概念。
+
+#### 3.2.1. znode 4 种类型
+
+我们通常是将 znode 分为 4 大类:
+
+- **持久(PERSISTENT)节点** :一旦创建就一直存在即使 ZooKeeper 集群宕机,直到将其删除。
+- **临时(EPHEMERAL)节点** :临时节点的生命周期是与 **客户端会话(session)** 绑定的,**会话消失则节点消失** 。并且,**临时节点只能做叶子节点** ,不能创建子节点。
+- **持久顺序(PERSISTENT_SEQUENTIAL)节点** :除了具有持久(PERSISTENT)节点的特性之外, 子节点的名称还具有顺序性。比如 `/node1/app0000000001` 、`/node1/app0000000002` 。
+- **临时顺序(EPHEMERAL_SEQUENTIAL)节点** :除了具备临时(EPHEMERAL)节点的特性之外,子节点的名称还具有顺序性。
+
+#### 3.2.2. znode 数据结构
+
+每个 znode 由 2 部分组成:
+
+- **stat** :状态信息
+- **data** : 节点存放的数据的具体内容
+
+如下所示,我通过 get 命令来获取 根目录下的 dubbo 节点的内容。(get 命令在下面会介绍到)。
+
+```shell
+[zk: 127.0.0.1:2181(CONNECTED) 6] get /dubbo
+# 该数据节点关联的数据内容为空
+null
+# 下面是该数据节点的一些状态信息,其实就是 Stat 对象的格式化输出
+cZxid = 0x2
+ctime = Tue Nov 27 11:05:34 CST 2018
+mZxid = 0x2
+mtime = Tue Nov 27 11:05:34 CST 2018
+pZxid = 0x3
+cversion = 1
+dataVersion = 0
+aclVersion = 0
+ephemeralOwner = 0x0
+dataLength = 0
+numChildren = 1
+```
+
+Stat 类中包含了一个数据节点的所有状态信息的字段,包括事务 ID-cZxid、节点创建时间-ctime 和子节点个数-numChildren 等等。
+
+下面我们来看一下每个 znode 状态信息究竟代表的是什么吧!(下面的内容来源于《从 Paxos 到 ZooKeeper 分布式一致性原理与实践》,因为 Guide 确实也不是特别清楚,要学会参考资料的嘛! ) :
+
+| znode 状态信息 | 解释 |
+| -------------- | ------------------------------------------------------------ |
+| cZxid | create ZXID,即该数据节点被创建时的事务 id |
+| ctime | create time,即该节点的创建时间 |
+| mZxid | modified ZXID,即该节点最终一次更新时的事务 id |
+| mtime | modified time,即该节点最后一次的更新时间 |
+| pZxid | 该节点的子节点列表最后一次修改时的事务 id,只有子节点列表变更才会更新 pZxid,子节点内容变更不会更新 |
+| cversion | 子节点版本号,当前节点的子节点每次变化时值增加 1 |
+| dataVersion | 数据节点内容版本号,节点创建时为 0,每更新一次节点内容(不管内容有无变化)该版本号的值增加 1 |
+| aclVersion | 节点的 ACL 版本号,表示该节点 ACL 信息变更次数 |
+| ephemeralOwner | 创建该临时节点的会话的 sessionId;如果当前节点为持久节点,则 ephemeralOwner=0 |
+| dataLength | 数据节点内容长度 |
+| numChildren | 当前节点的子节点个数 |
+
+### 3.3. 版本(version)
+
+在前面我们已经提到,对应于每个 znode,ZooKeeper 都会为其维护一个叫作 **Stat** 的数据结构,Stat 中记录了这个 znode 的三个相关的版本:
+
+- **dataVersion** :当前 znode 节点的版本号
+- **cversion** : 当前 znode 子节点的版本
+- **aclVersion** : 当前 znode 的 ACL 的版本。
+
+### 3.4. ACL(权限控制)
+
+ZooKeeper 采用 ACL(AccessControlLists)策略来进行权限控制,类似于 UNIX 文件系统的权限控制。
+
+对于 znode 操作的权限,ZooKeeper 提供了以下 5 种:
+
+- **CREATE** : 能创建子节点
+- **READ** :能获取节点数据和列出其子节点
+- **WRITE** : 能设置/更新节点数据
+- **DELETE** : 能删除子节点
+- **ADMIN** : 能设置节点 ACL 的权限
+
+其中尤其需要注意的是,**CREATE** 和 **DELETE** 这两种权限都是针对 **子节点** 的权限控制。
+
+对于身份认证,提供了以下几种方式:
+
+- **world** : 默认方式,所有用户都可无条件访问。
+- **auth** :不使用任何 id,代表任何已认证的用户。
+- **digest** :用户名:密码认证方式: _username:password_ 。
+- **ip** : 对指定 ip 进行限制。
+
+### 3.5. Watcher(事件监听器)
+
+Watcher(事件监听器),是 ZooKeeper 中的一个很重要的特性。ZooKeeper 允许用户在指定节点上注册一些 Watcher,并且在一些特定事件触发的时候,ZooKeeper 服务端会将事件通知到感兴趣的客户端上去,该机制是 ZooKeeper 实现分布式协调服务的重要特性。
+
+
+
+_破音:非常有用的一个特性,都能出小本本记好了,后面用到 ZooKeeper 基本离不开 Watcher(事件监听器)机制。_
+
+### 3.6. 会话(Session)
+
+Session 可以看作是 ZooKeeper 服务器与客户端的之间的一个 TCP 长连接,通过这个连接,客户端能够通过心跳检测与服务器保持有效的会话,也能够向 ZooKeeper 服务器发送请求并接受响应,同时还能够通过该连接接收来自服务器的 Watcher 事件通知。
+
+Session 有一个属性叫做:`sessionTimeout` ,`sessionTimeout` 代表会话的超时时间。当由于服务器压力太大、网络故障或是客户端主动断开连接等各种原因导致客户端连接断开时,只要在`sessionTimeout`规定的时间内能够重新连接上集群中任意一台服务器,那么之前创建的会话仍然有效。
+
+另外,在为客户端创建会话之前,服务端首先会为每个客户端都分配一个 `sessionID`。由于 `sessionID`是 ZooKeeper 会话的一个重要标识,许多与会话相关的运行机制都是基于这个 `sessionID` 的,因此,无论是哪台服务器为客户端分配的 `sessionID`,都务必保证全局唯一。
+
+## 4. ZooKeeper 集群
+
+为了保证高可用,最好是以集群形态来部署 ZooKeeper,这样只要集群中大部分机器是可用的(能够容忍一定的机器故障),那么 ZooKeeper 本身仍然是可用的。通常 3 台服务器就可以构成一个 ZooKeeper 集群了。ZooKeeper 官方提供的架构图就是一个 ZooKeeper 集群整体对外提供服务。
+
+
+
+上图中每一个 Server 代表一个安装 ZooKeeper 服务的服务器。组成 ZooKeeper 服务的服务器都会在内存中维护当前的服务器状态,并且每台服务器之间都互相保持着通信。集群间通过 ZAB 协议(ZooKeeper Atomic Broadcast)来保持数据的一致性。
+
+**最典型集群模式: Master/Slave 模式(主备模式)**。在这种模式中,通常 Master 服务器作为主服务器提供写服务,其他的 Slave 服务器从服务器通过异步复制的方式获取 Master 服务器最新的数据提供读服务。
+
+### 4.1. ZooKeeper 集群角色
+
+但是,在 ZooKeeper 中没有选择传统的 Master/Slave 概念,而是引入了 Leader、Follower 和 Observer 三种角色。如下图所示
+
+
+
+ZooKeeper 集群中的所有机器通过一个 **Leader 选举过程** 来选定一台称为 “**Leader**” 的机器,Leader 既可以为客户端提供写服务又能提供读服务。除了 Leader 外,**Follower** 和 **Observer** 都只能提供读服务。Follower 和 Observer 唯一的区别在于 Observer 机器不参与 Leader 的选举过程,也不参与写操作的“过半写成功”策略,因此 Observer 机器可以在不影响写性能的情况下提升集群的读性能。
+
+| 角色 | 说明 |
+| -------- | ------------------------------------------------------------ |
+| Leader | 为客户端提供读和写的服务,负责投票的发起和决议,更新系统状态。 |
+| Follower | 为客户端提供读服务,如果是写服务则转发给 Leader。参与选举过程中的投票。 |
+| Observer | 为客户端提供读服务,如果是写服务则转发给 Leader。不参与选举过程中的投票,也不参与“过半写成功”策略。在不影响写性能的情况下提升集群的读性能。此角色于 ZooKeeper3.3 系列新增的角色。 |
+
+当 Leader 服务器出现网络中断、崩溃退出与重启等异常情况时,就会进入 Leader 选举过程,这个过程会选举产生新的 Leader 服务器。
+
+这个过程大致是这样的:
+
+1. **Leader election(选举阶段)**:节点在一开始都处于选举阶段,只要有一个节点得到超半数节点的票数,它就可以当选准 leader。
+2. **Discovery(发现阶段)** :在这个阶段,followers 跟准 leader 进行通信,同步 followers 最近接收的事务提议。
+3. **Synchronization(同步阶段)** :同步阶段主要是利用 leader 前一阶段获得的最新提议历史,同步集群中所有的副本。同步完成之后
+ 准 leader 才会成为真正的 leader。
+4. **Broadcast(广播阶段)** :到了这个阶段,ZooKeeper 集群才能正式对外提供事务服务,并且 leader 可以进行消息广播。同时如果有新的节点加入,还需要对新节点进行同步。
+
+### 4.2. ZooKeeper 集群中的服务器状态
+
+- **LOOKING** :寻找 Leader。
+- **LEADING** :Leader 状态,对应的节点为 Leader。
+- **FOLLOWING** :Follower 状态,对应的节点为 Follower。
+- **OBSERVING** :Observer 状态,对应节点为 Observer,该节点不参与 Leader 选举。
+
+### 4.3. ZooKeeper 集群为啥最好奇数台?
+
+ZooKeeper 集群在宕掉几个 ZooKeeper 服务器之后,如果剩下的 ZooKeeper 服务器个数大于宕掉的个数的话整个 ZooKeeper 才依然可用。假如我们的集群中有 n 台 ZooKeeper 服务器,那么也就是剩下的服务数必须大于 n/2。先说一下结论,2n 和 2n-1 的容忍度是一样的,都是 n-1,大家可以先自己仔细想一想,这应该是一个很简单的数学问题了。
+
+比如假如我们有 3 台,那么最大允许宕掉 1 台 ZooKeeper 服务器,如果我们有 4 台的的时候也同样只允许宕掉 1 台。
+假如我们有 5 台,那么最大允许宕掉 2 台 ZooKeeper 服务器,如果我们有 6 台的的时候也同样只允许宕掉 2 台。
+
+综上,何必增加那一个不必要的 ZooKeeper 呢?
+
+### 4.4. ZooKeeper 选举的过半机制防止脑裂
+
+**何为集群脑裂?**
+
+对于一个集群,通常多台机器会部署在不同机房,来提高这个集群的可用性。保证可用性的同时,会发生一种机房间网络线路故障,导致机房间网络不通,而集群被割裂成几个小集群。这时候子集群各自选主导致“脑裂”的情况。
+
+举例说明:比如现在有一个由 6 台服务器所组成的一个集群,部署在了 2 个机房,每个机房 3 台。正常情况下只有 1 个 leader,但是当两个机房中间网络断开的时候,每个机房的 3 台服务器都会认为另一个机房的 3 台服务器下线,而选出自己的 leader 并对外提供服务。若没有过半机制,当网络恢复的时候会发现有 2 个 leader。仿佛是 1 个大脑(leader)分散成了 2 个大脑,这就发生了脑裂现象。脑裂期间 2 个大脑都可能对外提供了服务,这将会带来数据一致性等问题。
+
+**过半机制是如何防止脑裂现象产生的?**
+
+ZooKeeper 的过半机制导致不可能产生 2 个 leader,因为少于等于一半是不可能产生 leader 的,这就使得不论机房的机器如何分配都不可能发生脑裂。
+
+## 5. ZAB 协议和 Paxos 算法
+
+Paxos 算法应该可以说是 ZooKeeper 的灵魂了。但是,ZooKeeper 并没有完全采用 Paxos 算法 ,而是使用 ZAB 协议作为其保证数据一致性的核心算法。另外,在 ZooKeeper 的官方文档中也指出,ZAB 协议并不像 Paxos 算法那样,是一种通用的分布式一致性算法,它是一种特别为 Zookeeper 设计的崩溃可恢复的原子消息广播算法。
+
+### 5.1. ZAB 协议介绍
+
+ZAB(ZooKeeper Atomic Broadcast 原子广播) 协议是为分布式协调服务 ZooKeeper 专门设计的一种支持崩溃恢复的原子广播协议。 在 ZooKeeper 中,主要依赖 ZAB 协议来实现分布式数据一致性,基于该协议,ZooKeeper 实现了一种主备模式的系统架构来保持集群中各个副本之间的数据一致性。
+
+### 5.2. ZAB 协议两种基本的模式:崩溃恢复和消息广播
+
+ZAB 协议包括两种基本的模式,分别是
+
+- **崩溃恢复** :当整个服务框架在启动过程中,或是当 Leader 服务器出现网络中断、崩溃退出与重启等异常情况时,ZAB 协议就会进入恢复模式并选举产生新的 Leader 服务器。当选举产生了新的 Leader 服务器,同时集群中已经有过半的机器与该 Leader 服务器完成了状态同步之后,ZAB 协议就会退出恢复模式。其中,**所谓的状态同步是指数据同步,用来保证集群中存在过半的机器能够和 Leader 服务器的数据状态保持一致**。
+- **消息广播** :**当集群中已经有过半的 Follower 服务器完成了和 Leader 服务器的状态同步,那么整个服务框架就可以进入消息广播模式了。** 当一台同样遵守 ZAB 协议的服务器启动后加入到集群中时,如果此时集群中已经存在一个 Leader 服务器在负责进行消息广播,那么新加入的服务器就会自觉地进入数据恢复模式:找到 Leader 所在的服务器,并与其进行数据同步,然后一起参与到消息广播流程中去。
+
+关于 **ZAB 协议&Paxos 算法** 需要讲和理解的东西太多了,具体可以看下面这两篇文章:
+
+- [图解 Paxos 一致性协议](http://codemacro.com/2014/10/15/explain-poxos/)
+- [Zookeeper ZAB 协议分析](https://dbaplus.cn/news-141-1875-1.html)
+
+## 6. 总结
+
+1. ZooKeeper 本身就是一个分布式程序(只要半数以上节点存活,ZooKeeper 就能正常服务)。
+2. 为了保证高可用,最好是以集群形态来部署 ZooKeeper,这样只要集群中大部分机器是可用的(能够容忍一定的机器故障),那么 ZooKeeper 本身仍然是可用的。
+3. ZooKeeper 将数据保存在内存中,这也就保证了 高吞吐量和低延迟(但是内存限制了能够存储的容量不太大,此限制也是保持 znode 中存储的数据量较小的进一步原因)。
+4. ZooKeeper 是高性能的。 在“读”多于“写”的应用程序中尤其地明显,因为“写”会导致所有的服务器间同步状态。(“读”多于“写”是协调服务的典型场景。)
+5. ZooKeeper 有临时节点的概念。 当创建临时节点的客户端会话一直保持活动,瞬时节点就一直存在。而当会话终结时,瞬时节点被删除。持久节点是指一旦这个 znode 被创建了,除非主动进行 znode 的移除操作,否则这个 znode 将一直保存在 ZooKeeper 上。
+6. ZooKeeper 底层其实只提供了两个功能:① 管理(存储、读取)用户程序提交的数据;② 为用户程序提供数据节点监听服务。
+
+## 7. 参考
+
+1. 《从 Paxos 到 ZooKeeper 分布式一致性原理与实践》
diff --git "a/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/zookeeper-plus.md" "b/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/zookeeper-plus.md"
new file mode 100644
index 00000000000..11f2ac5a1a8
--- /dev/null
+++ "b/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/zookeeper-plus.md"
@@ -0,0 +1,375 @@
+# ZooKeeper 相关概念总结(进阶)
+
+> [FrancisQ](https://juejin.im/user/5c33853851882525ea106810) 投稿。
+
+## 1. 好久不见
+
+离上一篇文章的发布也快一个月了,想想已经快一个月没写东西了,其中可能有期末考试、课程设计和驾照考试,但这都不是借口!
+
+一到冬天就懒的不行,望广大掘友督促我🙄🙄✍️✍️。
+
+> 文章很长,先赞后看,养成习惯。❤️ 🧡 💛 💚 💙 💜
+
+## 2. 什么是ZooKeeper
+
+`ZooKeeper` 由 `Yahoo` 开发,后来捐赠给了 `Apache` ,现已成为 `Apache` 顶级项目。`ZooKeeper` 是一个开源的分布式应用程序协调服务器,其为分布式系统提供一致性服务。其一致性是通过基于 `Paxos` 算法的 `ZAB` 协议完成的。其主要功能包括:配置维护、分布式同步、集群管理、分布式事务等。
+
+
+
+简单来说, `ZooKeeper` 是一个 **分布式协调服务框架** 。分布式?协调服务?这啥玩意?🤔🤔
+
+其实解释到分布式这个概念的时候,我发现有些同学并不是能把 **分布式和集群 **这两个概念很好的理解透。前段时间有同学和我探讨起分布式的东西,他说分布式不就是加机器吗?一台机器不够用再加一台抗压呗。当然加机器这种说法也无可厚非,你一个分布式系统必定涉及到多个机器,但是你别忘了,计算机学科中还有一个相似的概念—— `Cluster` ,集群不也是加机器吗?但是 集群 和 分布式 其实就是两个完全不同的概念。
+
+比如,我现在有一个秒杀服务,并发量太大单机系统承受不住,那我加几台服务器也 **一样** 提供秒杀服务,这个时候就是 **`Cluster` 集群** 。
+
+
+
+但是,我现在换一种方式,我将一个秒杀服务 **拆分成多个子服务** ,比如创建订单服务,增加积分服务,扣优惠券服务等等,**然后我将这些子服务都部署在不同的服务器上** ,这个时候就是 **`Distributed` 分布式** 。
+
+
+
+而我为什么反驳同学所说的分布式就是加机器呢?因为我认为加机器更加适用于构建集群,因为它真是只有加机器。而对于分布式来说,你首先需要将业务进行拆分,然后再加机器(不仅仅是加机器那么简单),同时你还要去解决分布式带来的一系列问题。
+
+
+
+比如各个分布式组件如何协调起来,如何减少各个系统之间的耦合度,分布式事务的处理,如何去配置整个分布式系统等等。`ZooKeeper` 主要就是解决这些问题的。
+
+## 3. 一致性问题
+
+设计一个分布式系统必定会遇到一个问题—— **因为分区容忍性(partition tolerance)的存在,就必定要求我们需要在系统可用性(availability)和数据一致性(consistency)中做出权衡** 。这就是著名的 `CAP` 定理。
+
+理解起来其实很简单,比如说把一个班级作为整个系统,而学生是系统中的一个个独立的子系统。这个时候班里的小红小明偷偷谈恋爱被班里的大嘴巴小花发现了,小花欣喜若狂告诉了周围的人,然后小红小明谈恋爱的消息在班级里传播起来了。当在消息的传播(散布)过程中,你抓到一个同学问他们的情况,如果回答你不知道,那么说明整个班级系统出现了数据不一致的问题(因为小花已经知道这个消息了)。而如果他直接不回答你,因为整个班级有消息在进行传播(为了保证一致性,需要所有人都知道才可提供服务),这个时候就出现了系统的可用性问题。
+
+
+
+而上述前者就是 `Eureka` 的处理方式,它保证了AP(可用性),后者就是我们今天所要讲的 `ZooKeeper` 的处理方式,它保证了CP(数据一致性)。
+
+## 4. 一致性协议和算法
+
+而为了解决数据一致性问题,在科学家和程序员的不断探索中,就出现了很多的一致性协议和算法。比如 2PC(两阶段提交),3PC(三阶段提交),Paxos算法等等。
+
+这时候请你思考一个问题,同学之间如果采用传纸条的方式去传播消息,那么就会出现一个问题——我咋知道我的小纸条有没有传到我想要传递的那个人手中呢?万一被哪个小家伙给劫持篡改了呢,对吧?
+
+
+
+这个时候就引申出一个概念—— **拜占庭将军问题** 。它意指 **在不可靠信道上试图通过消息传递的方式达到一致性是不可能的**, 所以所有的一致性算法的 **必要前提** 就是安全可靠的消息通道。
+
+而为什么要去解决数据一致性的问题?你想想,如果一个秒杀系统将服务拆分成了下订单和加积分服务,这两个服务部署在不同的机器上了,万一在消息的传播过程中积分系统宕机了,总不能你这边下了订单却没加积分吧?你总得保证两边的数据需要一致吧?
+
+### 4.1. 2PC(两阶段提交)
+
+两阶段提交是一种保证分布式系统数据一致性的协议,现在很多数据库都是采用的两阶段提交协议来完成 **分布式事务** 的处理。
+
+在介绍2PC之前,我们先来想想分布式事务到底有什么问题呢?
+
+还拿秒杀系统的下订单和加积分两个系统来举例吧(我想你们可能都吐了🤮🤮🤮),我们此时下完订单会发个消息给积分系统告诉它下面该增加积分了。如果我们仅仅是发送一个消息也不收回复,那么我们的订单系统怎么能知道积分系统的收到消息的情况呢?如果我们增加一个收回复的过程,那么当积分系统收到消息后返回给订单系统一个 `Response` ,但在中间出现了网络波动,那个回复消息没有发送成功,订单系统是不是以为积分系统消息接收失败了?它是不是会回滚事务?但此时积分系统是成功收到消息的,它就会去处理消息然后给用户增加积分,这个时候就会出现积分加了但是订单没下成功。
+
+所以我们所需要解决的是在分布式系统中,整个调用链中,我们所有服务的数据处理要么都成功要么都失败,即所有服务的 **原子性问题** 。
+
+在两阶段提交中,主要涉及到两个角色,分别是协调者和参与者。
+
+第一阶段:当要执行一个分布式事务的时候,事务发起者首先向协调者发起事务请求,然后协调者会给所有参与者发送 `prepare` 请求(其中包括事务内容)告诉参与者你们需要执行事务了,如果能执行我发的事务内容那么就先执行但不提交,执行后请给我回复。然后参与者收到 `prepare` 消息后,他们会开始执行事务(但不提交),并将 `Undo` 和 `Redo` 信息记入事务日志中,之后参与者就向协调者反馈是否准备好了。
+
+第二阶段:第二阶段主要是协调者根据参与者反馈的情况来决定接下来是否可以进行事务的提交操作,即提交事务或者回滚事务。
+
+比如这个时候 **所有的参与者** 都返回了准备好了的消息,这个时候就进行事务的提交,协调者此时会给所有的参与者发送 **`Commit` 请求** ,当参与者收到 `Commit` 请求的时候会执行前面执行的事务的 **提交操作** ,提交完毕之后将给协调者发送提交成功的响应。
+
+而如果在第一阶段并不是所有参与者都返回了准备好了的消息,那么此时协调者将会给所有参与者发送 **回滚事务的 `rollback` 请求**,参与者收到之后将会 **回滚它在第一阶段所做的事务处理** ,然后再将处理情况返回给协调者,最终协调者收到响应后便给事务发起者返回处理失败的结果。
+
+
+
+个人觉得 2PC 实现得还是比较鸡肋的,因为事实上它只解决了各个事务的原子性问题,随之也带来了很多的问题。
+
+
+
+* **单点故障问题**,如果协调者挂了那么整个系统都处于不可用的状态了。
+* **阻塞问题**,即当协调者发送 `prepare` 请求,参与者收到之后如果能处理那么它将会进行事务的处理但并不提交,这个时候会一直占用着资源不释放,如果此时协调者挂了,那么这些资源都不会再释放了,这会极大影响性能。
+* **数据不一致问题**,比如当第二阶段,协调者只发送了一部分的 `commit` 请求就挂了,那么也就意味着,收到消息的参与者会进行事务的提交,而后面没收到的则不会进行事务提交,那么这时候就会产生数据不一致性问题。
+
+### 4.2. 3PC(三阶段提交)
+
+因为2PC存在的一系列问题,比如单点,容错机制缺陷等等,从而产生了 **3PC(三阶段提交)** 。那么这三阶段又分别是什么呢?
+
+> 千万不要吧PC理解成个人电脑了,其实他们是 phase-commit 的缩写,即阶段提交。
+
+1. **CanCommit阶段**:协调者向所有参与者发送 `CanCommit` 请求,参与者收到请求后会根据自身情况查看是否能执行事务,如果可以则返回 YES 响应并进入预备状态,否则返回 NO 。
+2. **PreCommit阶段**:协调者根据参与者返回的响应来决定是否可以进行下面的 `PreCommit` 操作。如果上面参与者返回的都是 YES,那么协调者将向所有参与者发送 `PreCommit` 预提交请求,**参与者收到预提交请求后,会进行事务的执行操作,并将 `Undo` 和 `Redo` 信息写入事务日志中** ,最后如果参与者顺利执行了事务则给协调者返回成功的响应。如果在第一阶段协调者收到了 **任何一个 NO** 的信息,或者 **在一定时间内** 并没有收到全部的参与者的响应,那么就会中断事务,它会向所有参与者发送中断请求(abort),参与者收到中断请求之后会立即中断事务,或者在一定时间内没有收到协调者的请求,它也会中断事务。
+3. **DoCommit阶段**:这个阶段其实和 `2PC` 的第二阶段差不多,如果协调者收到了所有参与者在 `PreCommit` 阶段的 YES 响应,那么协调者将会给所有参与者发送 `DoCommit` 请求,**参与者收到 `DoCommit` 请求后则会进行事务的提交工作**,完成后则会给协调者返回响应,协调者收到所有参与者返回的事务提交成功的响应之后则完成事务。若协调者在 `PreCommit` 阶段 **收到了任何一个 NO 或者在一定时间内没有收到所有参与者的响应** ,那么就会进行中断请求的发送,参与者收到中断请求后则会 **通过上面记录的回滚日志** 来进行事务的回滚操作,并向协调者反馈回滚状况,协调者收到参与者返回的消息后,中断事务。
+
+
+
+> 这里是 `3PC` 在成功的环境下的流程图,你可以看到 `3PC` 在很多地方进行了超时中断的处理,比如协调者在指定时间内为收到全部的确认消息则进行事务中断的处理,这样能 **减少同步阻塞的时间** 。还有需要注意的是,**`3PC` 在 `DoCommit` 阶段参与者如未收到协调者发送的提交事务的请求,它会在一定时间内进行事务的提交**。为什么这么做呢?是因为这个时候我们肯定**保证了在第一阶段所有的协调者全部返回了可以执行事务的响应**,这个时候我们有理由**相信其他系统都能进行事务的执行和提交**,所以**不管**协调者有没有发消息给参与者,进入第三阶段参与者都会进行事务的提交操作。
+
+总之,`3PC` 通过一系列的超时机制很好的缓解了阻塞问题,但是最重要的一致性并没有得到根本的解决,比如在 `PreCommit` 阶段,当一个参与者收到了请求之后其他参与者和协调者挂了或者出现了网络分区,这个时候收到消息的参与者都会进行事务提交,这就会出现数据不一致性问题。
+
+所以,要解决一致性问题还需要靠 `Paxos` 算法⭐️ ⭐️ ⭐️ 。
+
+### 4.3. `Paxos` 算法
+
+`Paxos` 算法是基于**消息传递且具有高度容错特性的一致性算法**,是目前公认的解决分布式一致性问题最有效的算法之一,**其解决的问题就是在分布式系统中如何就某个值(决议)达成一致** 。
+
+在 `Paxos` 中主要有三个角色,分别为 `Proposer提案者`、`Acceptor表决者`、`Learner学习者`。`Paxos` 算法和 `2PC` 一样,也有两个阶段,分别为 `Prepare` 和 `accept` 阶段。
+
+#### 4.3.1. prepare 阶段
+
+* `Proposer提案者`:负责提出 `proposal`,每个提案者在提出提案时都会首先获取到一个 **具有全局唯一性的、递增的提案编号N**,即在整个集群中是唯一的编号 N,然后将该编号赋予其要提出的提案,在**第一阶段是只将提案编号发送给所有的表决者**。
+* `Acceptor表决者`:每个表决者在 `accept` 某提案后,会将该提案编号N记录在本地,这样每个表决者中保存的已经被 accept 的提案中会存在一个**编号最大的提案**,其编号假设为 `maxN`。每个表决者仅会 `accept` 编号大于自己本地 `maxN` 的提案,在批准提案时表决者会将以前接受过的最大编号的提案作为响应反馈给 `Proposer` 。
+
+> 下面是 `prepare` 阶段的流程图,你可以对照着参考一下。
+
+
+
+#### 4.3.2. accept 阶段
+
+当一个提案被 `Proposer` 提出后,如果 `Proposer` 收到了超过半数的 `Acceptor` 的批准(`Proposer` 本身同意),那么此时 `Proposer` 会给所有的 `Acceptor` 发送真正的提案(你可以理解为第一阶段为试探),这个时候 `Proposer` 就会发送提案的内容和提案编号。
+
+表决者收到提案请求后会再次比较本身已经批准过的最大提案编号和该提案编号,如果该提案编号 **大于等于** 已经批准过的最大提案编号,那么就 `accept` 该提案(此时执行提案内容但不提交),随后将情况返回给 `Proposer` 。如果不满足则不回应或者返回 NO 。
+
+
+
+当 `Proposer` 收到超过半数的 `accept` ,那么它这个时候会向所有的 `acceptor` 发送提案的提交请求。需要注意的是,因为上述仅仅是超过半数的 `acceptor` 批准执行了该提案内容,其他没有批准的并没有执行该提案内容,所以这个时候需要**向未批准的 `acceptor` 发送提案内容和提案编号并让它无条件执行和提交**,而对于前面已经批准过该提案的 `acceptor` 来说 **仅仅需要发送该提案的编号** ,让 `acceptor` 执行提交就行了。
+
+
+
+而如果 `Proposer` 如果没有收到超过半数的 `accept` 那么它将会将 **递增** 该 `Proposal` 的编号,然后 **重新进入 `Prepare` 阶段** 。
+
+> 对于 `Learner` 来说如何去学习 `Acceptor` 批准的提案内容,这有很多方式,读者可以自己去了解一下,这里不做过多解释。
+
+#### 4.3.3. `paxos` 算法的死循环问题
+
+其实就有点类似于两个人吵架,小明说我是对的,小红说我才是对的,两个人据理力争的谁也不让谁🤬🤬。
+
+比如说,此时提案者 P1 提出一个方案 M1,完成了 `Prepare` 阶段的工作,这个时候 `acceptor` 则批准了 M1,但是此时提案者 P2 同时也提出了一个方案 M2,它也完成了 `Prepare` 阶段的工作。然后 P1 的方案已经不能在第二阶段被批准了(因为 `acceptor` 已经批准了比 M1 更大的 M2),所以 P1 自增方案变为 M3 重新进入 `Prepare` 阶段,然后 `acceptor` ,又批准了新的 M3 方案,它又不能批准 M2 了,这个时候 M2 又自增进入 `Prepare` 阶段。。。
+
+就这样无休无止的永远提案下去,这就是 `paxos` 算法的死循环问题。
+
+
+
+那么如何解决呢?很简单,人多了容易吵架,我现在 **就允许一个能提案** 就行了。
+
+## 5. 引出 `ZAB`
+
+### 5.1. `Zookeeper` 架构
+
+作为一个优秀高效且可靠的分布式协调框架,`ZooKeeper` 在解决分布式数据一致性问题时并没有直接使用 `Paxos` ,而是专门定制了一致性协议叫做 `ZAB(ZooKeeper Atomic Broadcast)` 原子广播协议,该协议能够很好地支持 **崩溃恢复** 。
+
+
+
+### 5.2. `ZAB` 中的三个角色
+
+和介绍 `Paxos` 一样,在介绍 `ZAB` 协议之前,我们首先来了解一下在 `ZAB` 中三个主要的角色,`Leader 领导者`、`Follower跟随者`、`Observer观察者` 。
+
+* `Leader` :集群中 **唯一的写请求处理者** ,能够发起投票(投票也是为了进行写请求)。
+* `Follower`:能够接收客户端的请求,如果是读请求则可以自己处理,**如果是写请求则要转发给 `Leader`** 。在选举过程中会参与投票,**有选举权和被选举权** 。
+* `Observer` :就是没有选举权和被选举权的 `Follower` 。
+
+在 `ZAB` 协议中对 `zkServer`(即上面我们说的三个角色的总称) 还有两种模式的定义,分别是 **消息广播** 和 **崩溃恢复** 。
+
+### 5.3. 消息广播模式
+
+说白了就是 `ZAB` 协议是如何处理写请求的,上面我们不是说只有 `Leader` 能处理写请求嘛?那么我们的 `Follower` 和 `Observer` 是不是也需要 **同步更新数据** 呢?总不能数据只在 `Leader` 中更新了,其他角色都没有得到更新吧?
+
+不就是 **在整个集群中保持数据的一致性** 嘛?如果是你,你会怎么做呢?
+
+
+
+废话,第一步肯定需要 `Leader` 将写请求 **广播** 出去呀,让 `Leader` 问问 `Followers` 是否同意更新,如果超过半数以上的同意那么就进行 `Follower` 和 `Observer` 的更新(和 `Paxos` 一样)。当然这么说有点虚,画张图理解一下。
+
+
+
+嗯。。。看起来很简单,貌似懂了🤥🤥🤥。这两个 `Queue` 哪冒出来的?答案是 **`ZAB` 需要让 `Follower` 和 `Observer` 保证顺序性** 。何为顺序性,比如我现在有一个写请求A,此时 `Leader` 将请求A广播出去,因为只需要半数同意就行,所以可能这个时候有一个 `Follower` F1因为网络原因没有收到,而 `Leader` 又广播了一个请求B,因为网络原因,F1竟然先收到了请求B然后才收到了请求A,这个时候请求处理的顺序不同就会导致数据的不同,从而 **产生数据不一致问题** 。
+
+所以在 `Leader` 这端,它为每个其他的 `zkServer` 准备了一个 **队列** ,采用先进先出的方式发送消息。由于协议是 **通过 `TCP` **来进行网络通信的,保证了消息的发送顺序性,接受顺序性也得到了保证。
+
+除此之外,在 `ZAB` 中还定义了一个 **全局单调递增的事务ID `ZXID`** ,它是一个64位long型,其中高32位表示 `epoch` 年代,低32位表示事务id。`epoch` 是会根据 `Leader` 的变化而变化的,当一个 `Leader` 挂了,新的 `Leader` 上位的时候,年代(`epoch`)就变了。而低32位可以简单理解为递增的事务id。
+
+定义这个的原因也是为了顺序性,每个 `proposal` 在 `Leader` 中生成后需要 **通过其 `ZXID` 来进行排序** ,才能得到处理。
+
+### 5.4. 崩溃恢复模式
+
+说到崩溃恢复我们首先要提到 `ZAB` 中的 `Leader` 选举算法,当系统出现崩溃影响最大应该是 `Leader` 的崩溃,因为我们只有一个 `Leader` ,所以当 `Leader` 出现问题的时候我们势必需要重新选举 `Leader` 。
+
+`Leader` 选举可以分为两个不同的阶段,第一个是我们提到的 `Leader` 宕机需要重新选举,第二则是当 `Zookeeper` 启动时需要进行系统的 `Leader` 初始化选举。下面我先来介绍一下 `ZAB` 是如何进行初始化选举的。
+
+假设我们集群中有3台机器,那也就意味着我们需要两台以上同意(超过半数)。比如这个时候我们启动了 `server1` ,它会首先 **投票给自己** ,投票内容为服务器的 `myid` 和 `ZXID` ,因为初始化所以 `ZXID` 都为0,此时 `server1` 发出的投票为 (1,0)。但此时 `server1` 的投票仅为1,所以不能作为 `Leader` ,此时还在选举阶段所以整个集群处于 **`Looking` 状态**。
+
+接着 `server2` 启动了,它首先也会将投票选给自己(2,0),并将投票信息广播出去(`server1`也会,只是它那时没有其他的服务器了),`server1` 在收到 `server2` 的投票信息后会将投票信息与自己的作比较。**首先它会比较 `ZXID` ,`ZXID` 大的优先为 `Leader`,如果相同则比较 `myid`,`myid` 大的优先作为 `Leader`**。所以此时`server1` 发现 `server2` 更适合做 `Leader`,它就会将自己的投票信息更改为(2,0)然后再广播出去,之后`server2` 收到之后发现和自己的一样无需做更改,并且自己的 **投票已经超过半数** ,则 **确定 `server2` 为 `Leader`**,`server1` 也会将自己服务器设置为 `Following` 变为 `Follower`。整个服务器就从 `Looking` 变为了正常状态。
+
+当 `server3` 启动发现集群没有处于 `Looking` 状态时,它会直接以 `Follower` 的身份加入集群。
+
+还是前面三个 `server` 的例子,如果在整个集群运行的过程中 `server2` 挂了,那么整个集群会如何重新选举 `Leader` 呢?其实和初始化选举差不多。
+
+首先毫无疑问的是剩下的两个 `Follower` 会将自己的状态 **从 `Following` 变为 `Looking` 状态** ,然后每个 `server` 会向初始化投票一样首先给自己投票(这不过这里的 `zxid` 可能不是0了,这里为了方便随便取个数字)。
+
+假设 `server1` 给自己投票为(1,99),然后广播给其他 `server`,`server3` 首先也会给自己投票(3,95),然后也广播给其他 `server`。`server1` 和 `server3` 此时会收到彼此的投票信息,和一开始选举一样,他们也会比较自己的投票和收到的投票(`zxid` 大的优先,如果相同那么就 `myid` 大的优先)。这个时候 `server1` 收到了 `server3` 的投票发现没自己的合适故不变,`server3` 收到 `server1` 的投票结果后发现比自己的合适于是更改投票为(1,99)然后广播出去,最后 `server1` 收到了发现自己的投票已经超过半数就把自己设为 `Leader`,`server3` 也随之变为 `Follower`。
+
+> 请注意 `ZooKeeper` 为什么要设置奇数个结点?比如这里我们是三个,挂了一个我们还能正常工作,挂了两个我们就不能正常工作了(已经没有超过半数的节点数了,所以无法进行投票等操作了)。而假设我们现在有四个,挂了一个也能工作,**但是挂了两个也不能正常工作了**,这是和三个一样的,而三个比四个还少一个,带来的效益是一样的,所以 `Zookeeper` 推荐奇数个 `server` 。
+
+那么说完了 `ZAB` 中的 `Leader` 选举方式之后我们再来了解一下 **崩溃恢复** 是什么玩意?
+
+其实主要就是 **当集群中有机器挂了,我们整个集群如何保证数据一致性?**
+
+如果只是 `Follower` 挂了,而且挂的没超过半数的时候,因为我们一开始讲了在 `Leader` 中会维护队列,所以不用担心后面的数据没接收到导致数据不一致性。
+
+如果 `Leader` 挂了那就麻烦了,我们肯定需要先暂停服务变为 `Looking` 状态然后进行 `Leader` 的重新选举(上面我讲过了),但这个就要分为两种情况了,分别是 **确保已经被Leader提交的提案最终能够被所有的Follower提交** 和 **跳过那些已经被丢弃的提案** 。
+
+确保已经被Leader提交的提案最终能够被所有的Follower提交是什么意思呢?
+
+假设 `Leader (server2)` 发送 `commit` 请求(忘了请看上面的消息广播模式),他发送给了 `server3`,然后要发给 `server1` 的时候突然挂了。这个时候重新选举的时候我们如果把 `server1` 作为 `Leader` 的话,那么肯定会产生数据不一致性,因为 `server3` 肯定会提交刚刚 `server2` 发送的 `commit` 请求的提案,而 `server1` 根本没收到所以会丢弃。
+
+
+
+那怎么解决呢?
+
+聪明的同学肯定会质疑,**这个时候 `server1` 已经不可能成为 `Leader` 了,因为 `server1` 和 `server3` 进行投票选举的时候会比较 `ZXID` ,而此时 `server3` 的 `ZXID` 肯定比 `server1` 的大了**。(不理解可以看前面的选举算法)
+
+那么跳过那些已经被丢弃的提案又是什么意思呢?
+
+假设 `Leader (server2)` 此时同意了提案N1,自身提交了这个事务并且要发送给所有 `Follower` 要 `commit` 的请求,却在这个时候挂了,此时肯定要重新进行 `Leader` 的选举,比如说此时选 `server1` 为 `Leader` (这无所谓)。但是过了一会,这个 **挂掉的 `Leader` 又重新恢复了** ,此时它肯定会作为 `Follower` 的身份进入集群中,需要注意的是刚刚 `server2` 已经同意提交了提案N1,但其他 `server` 并没有收到它的 `commit` 信息,所以其他 `server` 不可能再提交这个提案N1了,这样就会出现数据不一致性问题了,所以 **该提案N1最终需要被抛弃掉** 。
+
+
+
+## 6. Zookeeper的几个理论知识
+
+了解了 `ZAB` 协议还不够,它仅仅是 `Zookeeper` 内部实现的一种方式,而我们如何通过 `Zookeeper` 去做一些典型的应用场景呢?比如说集群管理,分布式锁,`Master` 选举等等。
+
+这就涉及到如何使用 `Zookeeper` 了,但在使用之前我们还需要掌握几个概念。比如 `Zookeeper` 的 **数据模型** 、**会话机制**、**ACL**、**Watcher机制** 等等。
+
+### 6.1. 数据模型
+
+`zookeeper` 数据存储结构与标准的 `Unix` 文件系统非常相似,都是在根节点下挂很多子节点(树型)。但是 `zookeeper` 中没有文件系统中目录与文件的概念,而是 **使用了 `znode` 作为数据节点** 。`znode` 是 `zookeeper` 中的最小数据单元,每个 `znode` 上都可以保存数据,同时还可以挂载子节点,形成一个树形化命名空间。
+
+
+
+每个 `znode` 都有自己所属的 **节点类型** 和 **节点状态**。
+
+其中节点类型可以分为 **持久节点**、**持久顺序节点**、**临时节点** 和 **临时顺序节点**。
+
+* 持久节点:一旦创建就一直存在,直到将其删除。
+* 持久顺序节点:一个父节点可以为其子节点 **维护一个创建的先后顺序** ,这个顺序体现在 **节点名称** 上,是节点名称后自动添加一个由 10 位数字组成的数字串,从 0 开始计数。
+* 临时节点:临时节点的生命周期是与 **客户端会话** 绑定的,**会话消失则节点消失** 。临时节点 **只能做叶子节点** ,不能创建子节点。
+* 临时顺序节点:父节点可以创建一个维持了顺序的临时节点(和前面的持久顺序性节点一样)。
+
+节点状态中包含了很多节点的属性比如 `czxid` 、`mzxid` 等等,在 `zookeeper` 中是使用 `Stat` 这个类来维护的。下面我列举一些属性解释。
+
+* `czxid`:`Created ZXID`,该数据节点被 **创建** 时的事务ID。
+* `mzxid`:`Modified ZXID`,节点 **最后一次被更新时** 的事务ID。
+* `ctime`:`Created Time`,该节点被创建的时间。
+* `mtime`: `Modified Time`,该节点最后一次被修改的时间。
+* `version`:节点的版本号。
+* `cversion`:**子节点** 的版本号。
+* `aversion`:节点的 `ACL` 版本号。
+* `ephemeralOwner`:创建该节点的会话的 `sessionID` ,如果该节点为持久节点,该值为0。
+* `dataLength`:节点数据内容的长度。
+* `numChildre`:该节点的子节点个数,如果为临时节点为0。
+* `pzxid`:该节点子节点列表最后一次被修改时的事务ID,注意是子节点的 **列表** ,不是内容。
+
+### 6.2. 会话
+
+我想这个对于后端开发的朋友肯定不陌生,不就是 `session` 吗?只不过 `zk` 客户端和服务端是通过 **`TCP` 长连接** 维持的会话机制,其实对于会话来说你可以理解为 **保持连接状态** 。
+
+在 `zookeeper` 中,会话还有对应的事件,比如 `CONNECTION_LOSS 连接丢失事件` 、`SESSION_MOVED 会话转移事件` 、`SESSION_EXPIRED 会话超时失效事件` 。
+
+### 6.3. ACL
+
+`ACL` 为 `Access Control Lists` ,它是一种权限控制。在 `zookeeper` 中定义了5种权限,它们分别为:
+
+* `CREATE` :创建子节点的权限。
+* `READ`:获取节点数据和子节点列表的权限。
+* `WRITE`:更新节点数据的权限。
+* `DELETE`:删除子节点的权限。
+* `ADMIN`:设置节点 ACL 的权限。
+
+### 6.4. Watcher机制
+
+`Watcher` 为事件监听器,是 `zk` 非常重要的一个特性,很多功能都依赖于它,它有点类似于订阅的方式,即客户端向服务端 **注册** 指定的 `watcher` ,当服务端符合了 `watcher` 的某些事件或要求则会 **向客户端发送事件通知** ,客户端收到通知后找到自己定义的 `Watcher` 然后 **执行相应的回调方法** 。
+
+
+
+## 7. Zookeeper的几个典型应用场景
+
+前面说了这么多的理论知识,你可能听得一头雾水,这些玩意有啥用?能干啥事?别急,听我慢慢道来。
+
+
+
+### 7.1. 选主
+
+还记得上面我们的所说的临时节点吗?因为 `Zookeeper` 的强一致性,能够很好地在保证 **在高并发的情况下保证节点创建的全局唯一性** (即无法重复创建同样的节点)。
+
+利用这个特性,我们可以 **让多个客户端创建一个指定的节点** ,创建成功的就是 `master`。
+
+但是,如果这个 `master` 挂了怎么办???
+
+你想想为什么我们要创建临时节点?还记得临时节点的生命周期吗?`master` 挂了是不是代表会话断了?会话断了是不是意味着这个节点没了?还记得 `watcher` 吗?我们是不是可以 **让其他不是 `master` 的节点监听节点的状态** ,比如说我们监听这个临时节点的父节点,如果子节点个数变了就代表 `master` 挂了,这个时候我们 **触发回调函数进行重新选举** ,或者我们直接监听节点的状态,我们可以通过节点是否已经失去连接来判断 `master` 是否挂了等等。
+
+
+
+总的来说,我们可以完全 **利用 临时节点、节点状态 和 `watcher` 来实现选主的功能**,临时节点主要用来选举,节点状态和`watcher` 可以用来判断 `master` 的活性和进行重新选举。
+
+### 7.2. 分布式锁
+
+分布式锁的实现方式有很多种,比如 `Redis` 、数据库 、`zookeeper` 等。个人认为 `zookeeper` 在实现分布式锁这方面是非常非常简单的。
+
+上面我们已经提到过了 **zk在高并发的情况下保证节点创建的全局唯一性**,这玩意一看就知道能干啥了。实现互斥锁呗,又因为能在分布式的情况下,所以能实现分布式锁呗。
+
+如何实现呢?这玩意其实跟选主基本一样,我们也可以利用临时节点的创建来实现。
+
+首先肯定是如何获取锁,因为创建节点的唯一性,我们可以让多个客户端同时创建一个临时节点,**创建成功的就说明获取到了锁** 。然后没有获取到锁的客户端也像上面选主的非主节点创建一个 `watcher` 进行节点状态的监听,如果这个互斥锁被释放了(可能获取锁的客户端宕机了,或者那个客户端主动释放了锁)可以调用回调函数重新获得锁。
+
+> `zk` 中不需要向 `redis` 那样考虑锁得不到释放的问题了,因为当客户端挂了,节点也挂了,锁也释放了。是不是很简答?
+
+那能不能使用 `zookeeper` 同时实现 **共享锁和独占锁** 呢?答案是可以的,不过稍微有点复杂而已。
+
+还记得 **有序的节点** 吗?
+
+这个时候我规定所有创建节点必须有序,当你是读请求(要获取共享锁)的话,如果 **没有比自己更小的节点,或比自己小的节点都是读请求** ,则可以获取到读锁,然后就可以开始读了。**若比自己小的节点中有写请求** ,则当前客户端无法获取到读锁,只能等待前面的写请求完成。
+
+如果你是写请求(获取独占锁),若 **没有比自己更小的节点** ,则表示当前客户端可以直接获取到写锁,对数据进行修改。若发现 **有比自己更小的节点,无论是读操作还是写操作,当前客户端都无法获取到写锁** ,等待所有前面的操作完成。
+
+这就很好地同时实现了共享锁和独占锁,当然还有优化的地方,比如当一个锁得到释放它会通知所有等待的客户端从而造成 **羊群效应** 。此时你可以通过让等待的节点只监听他们前面的节点。
+
+具体怎么做呢?其实也很简单,你可以让 **读请求监听比自己小的最后一个写请求节点,写请求只监听比自己小的最后一个节点** ,感兴趣的小伙伴可以自己去研究一下。
+
+### 7.3. 命名服务
+
+如何给一个对象设置ID,大家可能都会想到 `UUID`,但是 `UUID` 最大的问题就在于它太长了。。。(太长不一定是好事,嘿嘿嘿)。那么在条件允许的情况下,我们能不能使用 `zookeeper` 来实现呢?
+
+我们之前提到过 `zookeeper` 是通过 **树形结构** 来存储数据节点的,那也就是说,对于每个节点的 **全路径**,它必定是唯一的,我们可以使用节点的全路径作为命名方式了。而且更重要的是,路径是我们可以自己定义的,这对于我们对有些有语意的对象的ID设置可以更加便于理解。
+
+### 7.4. 集群管理和注册中心
+
+看到这里是不是觉得 `zookeeper` 实在是太强大了,它怎么能这么能干!
+
+别急,它能干的事情还很多呢。可能我们会有这样的需求,我们需要了解整个集群中有多少机器在工作,我们想对集群中的每台机器的运行时状态进行数据采集,对集群中机器进行上下线操作等等。
+
+而 `zookeeper` 天然支持的 `watcher` 和 临时节点能很好的实现这些需求。我们可以为每条机器创建临时节点,并监控其父节点,如果子节点列表有变动(我们可能创建删除了临时节点),那么我们可以使用在其父节点绑定的 `watcher` 进行状态监控和回调。
+
+
+
+至于注册中心也很简单,我们同样也是让 **服务提供者** 在 `zookeeper` 中创建一个临时节点并且将自己的 `ip、port、调用方式` 写入节点,当 **服务消费者** 需要进行调用的时候会 **通过注册中心找到相应的服务的地址列表(IP端口什么的)** ,并缓存到本地(方便以后调用),当消费者调用服务时,不会再去请求注册中心,而是直接通过负载均衡算法从地址列表中取一个服务提供者的服务器调用服务。
+
+当服务提供者的某台服务器宕机或下线时,相应的地址会从服务提供者地址列表中移除。同时,注册中心会将新的服务地址列表发送给服务消费者的机器并缓存在消费者本机(当然你可以让消费者进行节点监听,我记得 `Eureka` 会先试错,然后再更新)。
+
+
+
+## 8. 总结
+
+看到这里的同学实在是太有耐心了👍👍👍,如果觉得我写得不错的话点个赞哈。
+
+不知道大家是否还记得我讲了什么😒。
+
+
+
+这篇文章中我带大家入门了 `zookeeper` 这个强大的分布式协调框架。现在我们来简单梳理一下整篇文章的内容。
+
+* 分布式与集群的区别
+
+* `2PC` 、`3PC` 以及 `paxos` 算法这些一致性框架的原理和实现。
+
+* `zookeeper` 专门的一致性算法 `ZAB` 原子广播协议的内容(`Leader` 选举、崩溃恢复、消息广播)。
+
+* `zookeeper` 中的一些基本概念,比如 `ACL`,数据节点,会话,`watcher`机制等等。
+
+* `zookeeper` 的典型应用场景,比如选主,注册中心等等。
+
+ 如果忘了可以回去看看再次理解一下,如果有疑问和建议欢迎提出🤝🤝🤝。
diff --git "a/docs/distributed-system/\347\220\206\350\256\272&\347\256\227\346\263\225/cap&base\347\220\206\350\256\272.md" "b/docs/distributed-system/\347\220\206\350\256\272&\347\256\227\346\263\225/cap&base\347\220\206\350\256\272.md"
new file mode 100644
index 00000000000..3340409a799
--- /dev/null
+++ "b/docs/distributed-system/\347\220\206\350\256\272&\347\256\227\346\263\225/cap&base\347\220\206\350\256\272.md"
@@ -0,0 +1,157 @@
+
+# CAP & BASE理论
+
+经历过技术面试的小伙伴想必对这个两个概念已经再熟悉不过了!
+
+Guide哥当年参加面试的时候,不夸张地说,只要问到分布式相关的内容,面试官几乎是必定会问这两个分布式相关的理论。
+
+并且,这两个理论也可以说是小伙伴们学习分布式相关内容的基础了!
+
+因此,小伙伴们非常非常有必要将这理论搞懂,并且能够用自己的理解给别人讲出来。
+
+这篇文章我会站在自己的角度对这两个概念进行解读!
+
+*个人能力有限。如果文章有任何需要改善和完善的地方,欢迎在评论区指出,共同进步!——爱你们的Guide哥*
+
+## CAP理论
+
+[CAP 理论/定理](https://zh.wikipedia.org/wiki/CAP%E5%AE%9A%E7%90%86)起源于 2000年,由加州大学伯克利分校的Eric Brewer教授在分布式计算原理研讨会(PODC)上提出,因此 CAP定理又被称作 **布鲁尔定理(Brewer’s theorem)**
+
+2年后,麻省理工学院的Seth Gilbert和Nancy Lynch 发表了布鲁尔猜想的证明,CAP理论正式成为分布式领域的定理。
+
+### 简介
+
+**CAP** 也就是 **Consistency(一致性)**、**Availability(可用性)**、**Partition Tolerance(分区容错性)** 这三个单词首字母组合。
+
+
+
+CAP 理论的提出者布鲁尔在提出 CAP 猜想的时候,并没有详细定义 **Consistency**、**Availability**、**Partition Tolerance** 三个单词的明确定义。
+
+因此,对于 CAP 的民间解读有很多,一般比较被大家推荐的是下面 👇 这种版本的解读。
+
+在理论计算机科学中,CAP 定理(CAP theorem)指出对于一个分布式系统来说,当设计读写操作时,只能同时满足以下三点中的两个:
+
+- **一致性(Consistency)** : 所有节点访问同一份最新的数据副本
+- **可用性(Availability)**: 非故障的节点在合理的时间内返回合理的响应(不是错误或者超时的响应)。
+- **分区容错性(Partition tolerance)** : 分布式系统出现网络分区的时候,仍然能够对外提供服务。
+
+**什么是网络分区?**
+
+> 分布式系统中,多个节点之前的网络本来是连通的,但是因为某些故障(比如部分节点网络出了问题)某些节点之间不连通了,整个网络就分成了几块区域,这就叫网络分区。
+
+
+
+### 不是所谓的“3 选 2”
+
+大部分人解释这一定律时,常常简单的表述为:“一致性、可用性、分区容忍性三者你只能同时达到其中两个,不可能同时达到”。实际上这是一个非常具有误导性质的说法,而且在 CAP 理论诞生 12 年之后,CAP 之父也在 2012 年重写了之前的论文。
+
+> **当发生网络分区的时候,如果我们要继续服务,那么强一致性和可用性只能 2 选 1。也就是说当网络分区之后 P 是前提,决定了 P 之后才有 C 和 A 的选择。也就是说分区容错性(Partition tolerance)我们是必须要实现的。**
+>
+> 简而言之就是:CAP 理论中分区容错性 P 是一定要满足的,在此基础上,只能满足可用性 A 或者一致性 C。
+
+因此,**分布式系统理论上不可能选择 CA 架构,只能选择 CP 或者 AP 架构。** 比如 ZooKeeper、HBase 就是 CP 架构,Cassandra、Eureka 就是 AP 架构,Nacos 不仅支持 CP 架构也支持 AP 架构。
+
+**为啥不可能选择 CA 架构呢?** 举个例子:若系统出现“分区”,系统中的某个节点在进行写操作。为了保证 C, 必须要禁止其他节点的读写操作,这就和 A 发生冲突了。如果为了保证 A,其他节点的读写操作正常的话,那就和 C 发生冲突了。
+
+**选择 CP 还是 AP 的关键在于当前的业务场景,没有定论,比如对于需要确保强一致性的场景如银行一般会选择保证 CP 。**
+
+另外,需要补充说明的一点是: **如果网络分区正常的话(系统在绝大部分时候所处的状态),也就说不需要保证 P 的时候,C 和 A 能够同时保证。**
+
+### CAP 实际应用案例
+
+我这里以注册中心来探讨一下 CAP 的实际应用。考虑到很多小伙伴不知道注册中心是干嘛的,这里简单以 Dubbo 为例说一说。
+
+下图是 Dubbo 的架构图。**注册中心 Registry 在其中扮演了什么角色呢?提供了什么服务呢?**
+
+注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互,注册中心不转发请求,压力较小。
+
+
+
+常见的可以作为注册中心的组件有:ZooKeeper、Eureka、Nacos...。
+
+1. **ZooKeeper 保证的是 CP。** 任何时刻对 ZooKeeper 的读请求都能得到一致性的结果,但是, ZooKeeper 不保证每次请求的可用性比如在 Leader 选举过程中或者半数以上的机器不可用的时候服务就是不可用的。
+2. **Eureka 保证的则是 AP。** Eureka 在设计的时候就是优先保证 A (可用性)。在 Eureka 中不存在什么 Leader 节点,每个节点都是一样的、平等的。因此 Eureka 不会像 ZooKeeper 那样出现选举过程中或者半数以上的机器不可用的时候服务就是不可用的情况。 Eureka 保证即使大部分节点挂掉也不会影响正常提供服务,只要有一个节点是可用的就行了。只不过这个节点上的数据可能并不是最新的。
+3. **Nacos 不仅支持 CP 也支持 AP。**
+
+### 总结
+
+在进行分布式系统设计和开发时,我们不应该仅仅局限在 CAP 问题上,还要关注系统的扩展性、可用性等等
+
+在系统发生“分区”的情况下,CAP 理论只能满足 CP 或者 AP。要注意的是,这里的前提是系统发生了“分区”
+
+如果系统没有发生“分区”的话,节点间的网络连接通信正常的话,也就不存在 P 了。这个时候,我们就可以同时保证 C 和 A 了。
+
+总结:**如果系统发生“分区”,我们要考虑选择 CP 还是 AP。如果系统没有发生“分区”的话,我们要思考如何保证 CA 。**
+
+### 推荐阅读
+
+1. [CAP 定理简化](https://medium.com/@ravindraprasad/cap-theorem-simplified-28499a67eab4) (英文,有趣的案例)
+2. [神一样的 CAP 理论被应用在何方](https://juejin.im/post/6844903936718012430) (中文,列举了很多实际的例子)
+3. [请停止呼叫数据库 CP 或 AP ](https://martin.kleppmann.com/2015/05/11/please-stop-calling-databases-cp-or-ap.html) (英文,带给你不一样的思考)
+
+## BASE 理论
+
+[BASE 理论](https://dl.acm.org/doi/10.1145/1394127.1394128)起源于 2008 年, 由eBay的架构师Dan Pritchett在ACM上发表。
+
+### 简介
+
+**BASE** 是 **Basically Available(基本可用)** 、**Soft-state(软状态)** 和 **Eventually Consistent(最终一致性)** 三个短语的缩写。BASE 理论是对 CAP 中一致性 C 和可用性 A 权衡的结果,其来源于对大规模互联网系统分布式实践的总结,是基于 CAP 定理逐步演化而来的,它大大降低了我们对系统的要求。
+
+### BASE 理论的核心思想
+
+即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。
+
+> 也就是牺牲数据的一致性来满足系统的高可用性,系统中一部分数据不可用或者不一致时,仍需要保持系统整体“主要可用”。
+
+**BASE 理论本质上是对 CAP 的延伸和补充,更具体地说,是对 CAP 中 AP 方案的一个补充。**
+
+**为什么这样说呢?**
+
+CAP 理论这节我们也说过了:
+
+> 如果系统没有发生“分区”的话,节点间的网络连接通信正常的话,也就不存在 P 了。这个时候,我们就可以同时保证 C 和 A 了。因此,**如果系统发生“分区”,我们要考虑选择 CP 还是 AP。如果系统没有发生“分区”的话,我们要思考如何保证 CA 。**
+
+因此,AP 方案只是在系统发生分区的时候放弃一致性,而不是永远放弃一致性。在分区故障恢复后,系统应该达到最终一致性。这一点其实就是 BASE 理论延伸的地方。
+
+### BASE 理论三要素
+
+
+
+#### 1. 基本可用
+
+基本可用是指分布式系统在出现不可预知故障的时候,允许损失部分可用性。但是,这绝不等价于系统不可用。
+
+**什么叫允许损失部分可用性呢?**
+
+- **响应时间上的损失**: 正常情况下,处理用户请求需要 0.5s 返回结果,但是由于系统出现故障,处理用户请求的时间变为 3 s。
+- **系统功能上的损失**:正常情况下,用户可以使用系统的全部功能,但是由于系统访问量突然剧增,系统的部分非核心功能无法使用。
+
+#### 2. 软状态
+
+软状态指允许系统中的数据存在中间状态(**CAP 理论中的数据不一致**),并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。
+
+#### 3. 最终一致性
+
+最终一致性强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。
+
+> 分布式一致性的 3 种级别:
+>
+> 1. **强一致性** :系统写入了什么,读出来的就是什么。
+>
+> 2. **弱一致性** :不一定可以读取到最新写入的值,也不保证多少时间之后读取到的数据是最新的,只是会尽量保证某个时刻达到数据一致的状态。
+>
+> 3. **最终一致性** :弱一致性的升级版,系统会保证在一定时间内达到数据一致的状态。
+>
+> **业界比较推崇是最终一致性级别,但是某些对数据一致要求十分严格的场景比如银行转账还是要保证强一致性。**
+
+那实现最终一致性的具体方式是什么呢? [《分布式协议与算法实战》](http://gk.link/a/10rZM) 中是这样介绍:
+
+> - **读时修复** : 在读取数据时,检测数据的不一致,进行修复。比如 Cassandra 的 Read Repair 实现,具体来说,在向 Cassandra 系统查询数据的时候,如果检测到不同节点 的副本数据不一致,系统就自动修复数据。
+> - **写时修复** : 在写入数据,检测数据的不一致时,进行修复。比如 Cassandra 的 Hinted Handoff 实现。具体来说,Cassandra 集群的节点之间远程写数据的时候,如果写失败 就将数据缓存下来,然后定时重传,修复数据的不一致性。
+> - **异步修复** : 这个是最常用的方式,通过定时对账检测副本数据的一致性,并修复。
+
+比较推荐 **写时修复**,这种方式对性能消耗比较低。
+
+### 总结
+
+**ACID 是数据库事务完整性的理论,CAP 是分布式系统设计理论,BASE 是 CAP 理论中 AP 方案的延伸。**
diff --git "a/docs/distributed-system/\347\220\206\350\256\272&\347\256\227\346\263\225/paxos&raft\347\256\227\346\263\225.md" "b/docs/distributed-system/\347\220\206\350\256\272&\347\256\227\346\263\225/paxos&raft\347\256\227\346\263\225.md"
new file mode 100644
index 00000000000..36bd77241dd
--- /dev/null
+++ "b/docs/distributed-system/\347\220\206\350\256\272&\347\256\227\346\263\225/paxos&raft\347\256\227\346\263\225.md"
@@ -0,0 +1,4 @@
+# Paxos 算法和 Raft 算法
+
+Paxos 算法诞生于 1990 年,这是一种解决分布式系统一致性的经典算法 。但是,由于 Paxos 算法非常难以理解和实现,不断有人尝试简化这一算法。到了2013 年才诞生了一个比 Paxos 算法更易理解和实现的分布式一致性算法—Raft 算法。
+
diff --git a/docs/essential-content-for-interview/BATJrealInterviewExperience/2019alipay-pinduoduo-toutiao.md b/docs/essential-content-for-interview/BATJrealInterviewExperience/2019alipay-pinduoduo-toutiao.md
deleted file mode 100644
index 183a1852a90..00000000000
--- a/docs/essential-content-for-interview/BATJrealInterviewExperience/2019alipay-pinduoduo-toutiao.md
+++ /dev/null
@@ -1,294 +0,0 @@
-作者: rhwayfun,原文地址:https://mp.weixin.qq.com/s/msYty4vjjC0PvrwasRH5Bw ,JavaGuide 已经获得作者授权并对原文进行了重新排版。
-
-
-- [写在2019年后的蚂蚁、头条、拼多多的面试总结](#写在2019年后的蚂蚁头条拼多多的面试总结)
- - [准备过程](#准备过程)
- - [蚂蚁金服](#蚂蚁金服)
- - [一面](#一面)
- - [二面](#二面)
- - [三面](#三面)
- - [四面](#四面)
- - [五面](#五面)
- - [小结](#小结)
- - [拼多多](#拼多多)
- - [面试前](#面试前)
- - [一面](#一面-1)
- - [二面](#二面-1)
- - [三面](#三面-1)
- - [小结](#小结-1)
- - [字节跳动](#字节跳动)
- - [面试前](#面试前-1)
- - [一面](#一面-2)
- - [二面](#二面-2)
- - [小结](#小结-2)
- - [总结](#总结)
-
-
-
-# 2019年蚂蚁金服、头条、拼多多的面试总结
-
-文章有点长,请耐心看完,绝对有收获!不想听我BB直接进入面试分享:
-
-- 准备过程
-- 蚂蚁金服面试分享
-- 拼多多面试分享
-- 字节跳动面试分享
-- 总结
-
-说起来开始进行面试是年前倒数第二周,上午9点,我还在去公司的公交上,突然收到蚂蚁的面试电话,其实算不上真正的面试。面试官只是和我聊了下他们在做的事情(主要是做双十一这里大促的稳定性保障,偏中间件吧),说的很详细,然后和我沟通了下是否有兴趣,我表示有兴趣,后面就收到正式面试的通知,最后没选择去蚂蚁表示抱歉。
-
-当时我自己也准备出去看看机会,顺便看看自己的实力。当时我其实挺纠结的,一方面现在部门也正需要我,还是可以有一番作为的,另一方面觉得近一年来进步缓慢,没有以前飞速进步的成就感了,而且业务和技术偏于稳定,加上自己也属于那种比较懒散的人,骨子里还是希望能够突破现状,持续在技术上有所精进。
-
-在开始正式的总结之前,还是希望各位同仁能否听我继续发泄一会,抱拳!
-
-我翻开自己2018年初立的flag,觉得甚是惭愧。其中就有一条是保持一周写一篇博客,奈何中间因为各种原因没能坚持下去。细细想来,主要是自己没能真正静下来心认真投入到技术的研究和学习,那么为什么会这样?说白了还是因为没有确定目标或者目标不明确,没有目标或者目标不明确都可能导致行动的失败。
-
-那么问题来了,目标是啥?就我而言,短期目标是深入研究某一项技术,比如最近在研究mysql,那么深入研究一定要动手实践并且有所产出,这就够了么?还需要我们能够举一反三,结合实际开发场景想一想日常开发要注意什么,这中间有没有什么坑?可以看出,要进步真的不是一件简单的事,这种反人类的行为需要我们克服自我的弱点,逐渐形成习惯。真正牛逼的人,从不觉得认真学习是一件多么难的事,因为这已经形成了他的习惯,就喝早上起床刷牙洗脸那么自然简单。
-
-扯了那么多,开始进入正题,先后进行了蚂蚁、拼多多和字节跳动的面试。
-
-## 准备过程
-
-先说说我自己的情况,我2016先在蚂蚁实习了将近三个月,然后去了我现在的老东家,2.5年工作经验,可以说毕业后就一直老老实实在老东家打怪升级,虽说有蚂蚁的实习经历,但是因为时间太短,还是有点虚的。所以面试官看到我简历第一个问题绝对是这样的。
-
-“哇,你在蚂蚁待过,不错啊”,面试官笑嘻嘻地问到。“是的,还好”,我说。“为啥才三个月?”,面试官脸色一沉问到。“哗啦啦解释一通。。。”,我解释道。“哦,原来如此,那我们开始面试吧”,面试官一本正经说到。
-
-尼玛,早知道不写蚂蚁的实习经历了,后面仔细一想,当初写上蚂蚁不就给简历加点料嘛。
-
-言归正传,准备过程其实很早开始了(当然这不是说我工作时老想着跳槽,因为我明白现在的老东家并不是终点,我还需要不断提升),具体可追溯到从蚂蚁离职的时候,当时出来也面了很多公司,没啥大公司,面了大概5家公司,都拿到offer了。
-
-工作之余常常会去额外研究自己感兴趣的技术以及工作用到的技术,力求把原理搞明白,并且会自己实践一把。此外,买了N多书,基本有时间就会去看,补补基础,什么操作系统、数据结构与算法、MySQL、JDK之类的源码,基本都好好温习了(文末会列一下自己看过的书和一些好的资料)。**我深知基础就像“木桶效应”的短板,决定了能装多少水。**
-
-此外,在正式决定看机会之前,我给自己列了一个提纲,主要包括Java要掌握的核心要点,有不懂的就查资料搞懂。我给自己定位还是Java工程师,所以Java体系是一定要做到心中有数的,很多东西没有常年的积累面试的时候很容易露馅,学习要对得起自己,不要骗人。
-
-剩下的就是找平台和内推了,除了蚂蚁,头条和拼多多都是找人内推的,感谢蚂蚁面试官对我的欣赏,以后说不定会去蚂蚁咯😄。
-
-平台:脉脉、GitHub、v2
-
-## 蚂蚁金服
-
-
-
-- 一面
-- 二面
-- 三面
-- 四面
-- 五面
-- 小结
-
-### 一面
-
-一面就做了一道算法题,要求两小时内完成,给了长度为N的有重复元素的数组,要求输出第10大的数。典型的TopK问题,快排算法搞定。
-
-算法题要注意的是合法性校验、边界条件以及异常的处理。另外,如果要写测试用例,一定要保证测试覆盖场景尽可能全。加上平时刷刷算法题,这种考核应该没问题的。
-
-### 二面
-
-- 自我介绍下呗
-- 开源项目贡献过代码么?(Dubbo提过一个打印accesslog的bug算么)
-- 目前在部门做什么,业务简单介绍下,内部有哪些系统,作用和交互过程说下
-- Dubbo踩过哪些坑,分别是怎么解决的?(说了异常处理时业务异常捕获的问题,自定义了一个异常拦截器)
-- 开始进入正题,说下你对线程安全的理解(多线程访问同一个对象,如果不需要考虑额外的同步,调用对象的行为就可以获得正确的结果就是线程安全)
-- 事务有哪些特性?(ACID)
-- 怎么理解原子性?(同一个事务下,多个操作要么成功要么失败,不存在部分成功或者部分失败的情况)
-- 乐观锁和悲观锁的区别?(悲观锁假定会发生冲突,访问的时候都要先获得锁,保证同一个时刻只有线程获得锁,读读也会阻塞;乐观锁假设不会发生冲突,只有在提交操作的时候检查是否有冲突)这两种锁在Java和MySQL分别是怎么实现的?(Java乐观锁通过CAS实现,悲观锁通过synchronize实现。mysql乐观锁通过MVCC,也就是版本实现,悲观锁可以通过select... for update加上排它锁)
-- HashMap为什么不是线程安全的?(多线程操作无并发控制,顺便说了在扩容的时候多线程访问时会造成死锁,会形成一个环,不过扩容时多线程操作形成环的问题再JDK1.8已经解决,但多线程下使用HashMap还会有一些其他问题比如数据丢失,所以多线程下不应该使用HashMap,而应该使用ConcurrentHashMap)怎么让HashMap变得线程安全?(Collections的synchronize方法包装一个线程安全的Map,或者直接用ConcurrentHashMap)两者的区别是什么?(前者直接在put和get方法加了synchronize同步,后者采用了分段锁以及CAS支持更高的并发)
-- jdk1.8对ConcurrentHashMap做了哪些优化?(插入的时候如果数组元素使用了红黑树,取消了分段锁设计,synchronize替代了Lock锁)为什么这样优化?(避免冲突严重时链表多长,提高查询效率,时间复杂度从O(N)提高到O(logN))
-- redis主从机制了解么?怎么实现的?
-- 有过GC调优的经历么?(有点虚,答得不是很好)
-- 有什么想问的么?
-
-### 三面
-
-- 简单自我介绍下
-- 监控系统怎么做的,分为哪些模块,模块之间怎么交互的?用的什么数据库?(MySQL)使用什么存储引擎,为什么使用InnnoDB?(支持事务、聚簇索引、MVCC)
-- 订单表有做拆分么,怎么拆的?(垂直拆分和水平拆分)
-- 水平拆分后查询过程描述下
-- 如果落到某个分片的数据很大怎么办?(按照某种规则,比如哈希取模、range,将单张表拆分为多张表)
-- 哈希取模会有什么问题么?(有的,数据分布不均,扩容缩容相对复杂 )
-- 分库分表后怎么解决读写压力?(一主多从、多主多从)
-- 拆分后主键怎么保证惟一?(UUID、Snowflake算法)
-- Snowflake生成的ID是全局递增唯一么?(不是,只是全局唯一,单机递增)
-- 怎么实现全局递增的唯一ID?(讲了TDDL的一次取一批ID,然后再本地慢慢分配的做法)
-- Mysql的索引结构说下(说了B+树,B+树可以对叶子结点顺序查找,因为叶子结点存放了数据结点且有序)
-- 主键索引和普通索引的区别(主键索引的叶子结点存放了整行记录,普通索引的叶子结点存放了主键ID,查询的时候需要做一次回表查询)一定要回表查询么?(不一定,当查询的字段刚好是索引的字段或者索引的一部分,就可以不用回表,这也是索引覆盖的原理)
-- 你们系统目前的瓶颈在哪里?
-- 你打算怎么优化?简要说下你的优化思路
-- 有什么想问我么?
-
-### 四面
-
-- 介绍下自己
-- 为什么要做逆向?
-- 怎么理解微服务?
-- 服务治理怎么实现的?(说了限流、压测、监控等模块的实现)
-- 这个不是中间件做的事么,为什么你们部门做?(当时没有单独的中间件团队,微服务刚搞不久,需要进行监控和性能优化)
-- 说说Spring的生命周期吧
-- 说说GC的过程(说了young gc和full gc的触发条件和回收过程以及对象创建的过程)
-- CMS GC有什么问题?(并发清除算法,浮动垃圾,短暂停顿)
-- 怎么避免产生浮动垃圾?(记得有个VM参数设置可以让扫描新生代之前进行一次young gc,但是因为gc是虚拟机自动调度的,所以不保证一定执行。但是还有参数可以让虚拟机强制执行一次young gc)
-- 强制young gc会有什么问题?(STW停顿时间变长)
-- 知道G1么?(了解一点 )
-- 回收过程是怎么样的?(young gc、并发阶段、混合阶段、full gc,说了Remember Set)
-- 你提到的Remember Set底层是怎么实现的?
-- 有什么想问的么?
-
-### 五面
-
-五面是HRBP面的,和我提前预约了时间,主要聊了之前在蚂蚁的实习经历、部门在做的事情、职业发展、福利待遇等。阿里面试官确实是具有一票否决权的,很看重你的价值观是否match,一般都比较喜欢皮实的候选人。HR面一定要诚实,不要说谎,只要你说谎HR都会去证实,直接cut了。
-
-- 之前蚂蚁实习三个月怎么不留下来?
-- 实习的时候主管是谁?
-- 实习做了哪些事情?(尼玛这种也问?)
-- 你对技术怎么看?平时使用什么技术栈?(阿里HR真的是既当爹又当妈,😂)
-- 最近有在研究什么东西么
-- 你对SRE怎么看
-- 对待遇有什么预期么
-
-最后HR还对我说目前稳定性保障部挺缺人的,希望我尽快回复。
-
-### 小结
-
-蚂蚁面试比较重视基础,所以Java那些基本功一定要扎实。蚂蚁的工作环境还是挺赞的,因为我面的是稳定性保障部门,还有许多单独的小组,什么三年1班,很有青春的感觉。面试官基本水平都比较高,基本都P7以上,除了基础还问了不少架构设计方面的问题,收获还是挺大的。
-
-## 拼多多
-
-
-
-- 面试前
-- 一面
-- 二面
-- 三面
-- 小结
-
-### 面试前
-
-面完蚂蚁后,早就听闻拼多多这个独角兽,决定也去面一把。首先我在脉脉找了一个拼多多的HR,加了微信聊了下,发了简历便开始我的拼多多面试之旅。这里要非常感谢拼多多HR小姐姐,从面试内推到offer确认一直都在帮我,人真的很nice。
-
-### 一面
-
-- 为啥蚂蚁只待了三个月?没转正?(转正了,解释了一通。。。)
-- Java中的HashMap、TreeMap解释下?(TreeMap红黑树,有序,HashMap无序,数组+链表)
-- TreeMap查询写入的时间复杂度多少?(O(logN))
-- HashMap多线程有什么问题?(线程安全,死锁)怎么解决?( jdk1.8用了synchronize + CAS,扩容的时候通过CAS检查是否有修改,是则重试)重试会有什么问题么?(CAS(Compare And Swap)是比较和交换,不会导致线程阻塞,但是因为重试是通过自旋实现的,所以仍然会占用CPU时间,还有ABA的问题)怎么解决?(超时,限定自旋的次数,ABA可以通过原理变量AtomicStampedReference解决,原理利用版本号进行比较)超过重试次数如果仍然失败怎么办?(synchronize互斥锁)
-- CAS和synchronize有什么区别?都用synchronize不行么?(CAS是乐观锁,不需要阻塞,硬件级别实现的原子性;synchronize会阻塞,JVM级别实现的原子性。使用场景不同,线程冲突严重时CAS会造成CPU压力过大,导致吞吐量下降,synchronize的原理是先自旋然后阻塞,线程冲突严重仍然有较高的吞吐量,因为线程都被阻塞了,不会占用CPU
-)
-- 如果要保证线程安全怎么办?(ConcurrentHashMap)
-- ConcurrentHashMap怎么实现线程安全的?(分段锁)
-- get需要加锁么,为什么?(不用,volatile关键字)
-- volatile的作用是什么?(保证内存可见性)
-- 底层怎么实现的?(说了主内存和工作内存,读写内存屏障,happen-before,并在纸上画了线程交互图)
-- 在多核CPU下,可见性怎么保证?(思考了一会,总线嗅探技术)
-- 聊项目,系统之间是怎么交互的?
-- 系统并发多少,怎么优化?
-- 给我一张纸,画了一个九方格,都填了数字,给一个M*N矩阵,从1开始逆时针打印这M*N个数,要求时间复杂度尽可能低(内心OS:之前貌似碰到过这题,最优解是怎么实现来着)思考中。。。
-- 可以先说下你的思路(想起来了,说了什么时候要变换方向的条件,向右、向下、向左、向上,依此循环)
-- 有什么想问我的?
-
-### 二面
-
-- 自我介绍下
-- 手上还有其他offer么?(拿了蚂蚁的offer)
-- 部门组织结构是怎样的?(这轮不是技术面么,不过还是老老实实说了)
-- 系统有哪些模块,每个模块用了哪些技术,数据怎么流转的?(面试官有点秃顶,一看级别就很高)给了我一张纸,我在上面简单画了下系统之间的流转情况
-- 链路追踪的信息是怎么传递的?(RpcContext的attachment,说了Span的结构:parentSpanId + curSpanId)
-- SpanId怎么保证唯一性?(UUID,说了下内部的定制改动)
-- RpcContext是在什么维度传递的?(线程)
-- Dubbo的远程调用怎么实现的?(讲了读取配置、拼装url、创建Invoker、服务导出、服务注册以及消费者通过动态代理、filter、获取Invoker列表、负载均衡等过程(哗啦啦讲了10多分钟),我可以喝口水么)
-- Spring的单例是怎么实现的?(单例注册表)
-- 为什么要单独实现一个服务治理框架?(说了下内部刚搞微服务不久,主要对服务进行一些监控和性能优化)
-- 谁主导的?内部还在使用么?
-- 逆向有想过怎么做成通用么?
-- 有什么想问的么?
-
-### 三面
-
-二面老大面完后就直接HR面了,主要问了些职业发展、是否有其他offer、以及入职意向等问题,顺便说了下公司的福利待遇等,都比较常规啦。不过要说的是手上有其他offer或者大厂经历会有一定加分。
-
-### 小结
-
-拼多多的面试流程就简单许多,毕竟是一个成立三年多的公司。面试难度中规中矩,只要基础扎实应该不是问题。但不得不说工作强度很大,开始面试前HR就提前和我确认能否接受这样强度的工作,想来的老铁还是要做好准备
-
-## 字节跳动
-
-
-
-- 面试前
-- 一面
-- 二面
-- 小结
-
-### 面试前
-
-头条的面试是三家里最专业的,每次面试前有专门的HR和你约时间,确定OK后再进行面试。每次都是通过视频面试,因为都是之前都是电话面或现场面,所以视频面试还是有点不自然。也有人觉得视频面试体验很赞,当然萝卜青菜各有所爱。最坑的二面的时候对方面试官的网络老是掉线,最后很冤枉的挂了(当然有一些点答得不好也是原因之一)。所以还是有点遗憾的。
-
-### 一面
-
-- 先自我介绍下
-- 聊项目,逆向系统是什么意思
-- 聊项目,逆向系统用了哪些技术
-- 线程池的线程数怎么确定?
-- 如果是IO操作为主怎么确定?
-- 如果计算型操作又怎么确定?
-- Redis熟悉么,了解哪些数据结构?(说了zset) zset底层怎么实现的?(跳表)
-- 跳表的查询过程是怎么样的,查询和插入的时间复杂度?(说了先从第一层查找,不满足就下沉到第二层找,因为每一层都是有序的,写入和插入的时间复杂度都是O(logN))
-- 红黑树了解么,时间复杂度?(说了是N叉平衡树,O(logN))
-- 既然两个数据结构时间复杂度都是O(logN),zset为什么不用红黑树(跳表实现简单,踩坑成本低,红黑树每次插入都要通过旋转以维持平衡,实现复杂)
-- 点了点头,说下Dubbo的原理?(说了服务注册与发布以及消费者调用的过程)踩过什么坑没有?(说了dubbo异常处理的和打印accesslog的问题)
-- CAS了解么?(说了CAS的实现)还了解其他同步机制么?(说了synchronize以及两者的区别,一个乐观锁,一个悲观锁)
-- 那我们做一道题吧,数组A,2*n个元素,n个奇数、n个偶数,设计一个算法,使得数组奇数下标位置放置的都是奇数,偶数下标位置放置的都是偶数
-- 先说下你的思路(从0下标开始遍历,如果是奇数下标判断该元素是否奇数,是则跳过,否则从该位置寻找下一个奇数)
-- 下一个奇数?怎么找?(有点懵逼,思考中。。)
-- 有思路么?(仍然是先遍历一次数组,并对下标进行判断,如果下标属性和该位置元素不匹配从当前下标的下一个遍历数组元素,然后替换)
-- 你这样时间复杂度有点高,如果要求O(N)要怎么做(思考一会,答道“定义两个指针,分别从下标0和1开始遍历,遇见奇数位是是偶数和偶数位是奇数就停下,交换内容”)
-- 时间差不多了,先到这吧。你有什么想问我的?
-
-### 二面
-
-- 面试官和蔼很多,你先介绍下自己吧
-- 你对服务治理怎么理解的?
-- 项目中的限流怎么实现的?(Guava ratelimiter,令牌桶算法)
-- 具体怎么实现的?(要点是固定速率且令牌数有限)
-- 如果突然很多线程同时请求令牌,有什么问题?(导致很多请求积压,线程阻塞)
-- 怎么解决呢?(可以把积压的请求放到消息队列,然后异步处理)
-- 如果不用消息队列怎么解决?(说了RateLimiter预消费的策略)
-- 分布式追踪的上下文是怎么存储和传递的?(ThreadLocal + spanId,当前节点的spanId作为下个节点的父spanId)
-- Dubbo的RpcContext是怎么传递的?(ThreadLocal)主线程的ThreadLocal怎么传递到线程池?(说了先在主线程通过ThreadLocal的get方法拿到上下文信息,在线程池创建新的ThreadLocal并把之前获取的上下文信息设置到ThreadLocal中。这里要注意的线程池创建的ThreadLocal要在finally中手动remove,不然会有内存泄漏的问题)
-- 你说的内存泄漏具体是怎么产生的?(说了ThreadLocal的结构,主要分两种场景:主线程仍然对ThreadLocal有引用和主线程不存在对ThreadLocal的引用。第一种场景因为主线程仍然在运行,所以还是有对ThreadLocal的引用,那么ThreadLocal变量的引用和value是不会被回收的。第二种场景虽然主线程不存在对ThreadLocal的引用,且该引用是弱引用,所以会在gc的时候被回收,但是对用的value不是弱引用,不会被内存回收,仍然会造成内存泄漏)
-- 线程池的线程是不是必须手动remove才可以回收value?(是的,因为线程池的核心线程是一直存在的,如果不清理,那么核心线程的threadLocals变量会一直持有ThreadLocal变量)
-- 那你说的内存泄漏是指主线程还是线程池?(主线程 )
-- 可是主线程不是都退出了,引用的对象不应该会主动回收么?(面试官和内存泄漏杠上了),沉默了一会。。。
-- 那你说下SpringMVC不同用户登录的信息怎么保证线程安全的?(刚才解释的有点懵逼,一下没反应过来,居然回答成锁了。大脑有点晕了,此时已经一个小时过去了,感觉情况不妙。。。)
-- 这个直接用ThreadLocal不就可以么,你见过SpringMVC有锁实现的代码么?(有点晕菜。。。)
-- 我们聊聊mysql吧,说下索引结构(说了B+树)
-- 为什么使用B+树?( 说了查询效率高,O(logN),可以充分利用磁盘预读的特性,多叉树,深度小,叶子结点有序且存储数据)
-- 什么是索引覆盖?(忘记了。。。 )
-- Java为什么要设计双亲委派模型?
-- 什么时候需要自定义类加载器?
-- 我们做一道题吧,手写一个对象池
-- 有什么想问我的么?(感觉我很多点都没答好,是不是挂了(结果真的是) )
-
-### 小结
-
-头条的面试确实很专业,每次面试官会提前给你发一个视频链接,然后准点开始面试,而且考察的点都比较全。
-
-面试官都有一个特点,会抓住一个值得深入的点或者你没说清楚的点深入下去直到你把这个点讲清楚,不然面试官会觉得你并没有真正理解。二面面试官给了我一点建议,研究技术的时候一定要去研究产生的背景,弄明白在什么场景解决什么特定的问题,其实很多技术内部都是相通的。很诚恳,还是很感谢这位面试官大大。
-
-## 总结
-
-从年前开始面试到头条面完大概一个多月的时间,真的有点身心俱疲的感觉。最后拿到了拼多多、蚂蚁的offer,还是蛮幸运的。头条的面试对我帮助很大,再次感谢面试官对我的诚恳建议,以及拼多多的HR对我的啰嗦的问题详细解答。
-
-这里要说的是面试前要做好两件事:简历和自我介绍,简历要好好回顾下自己做的一些项目,然后挑几个亮点项目。自我介绍基本每轮面试都有,所以最好提前自己练习下,想好要讲哪些东西,分别怎么讲。此外,简历提到的技术一定是自己深入研究过的,没有深入研究也最好找点资料预热下,不打无准备的仗。
-
-**这些年看过的书**:
-
-《Effective Java》、《现代操作系统》、《TCP/IP详解:卷一》、《代码整洁之道》、《重构》、《Java程序性能优化》、《Spring实战》、《Zookeeper》、《高性能MySQL》、《亿级网站架构核心技术》、《可伸缩服务架构》、《Java编程思想》
-
-说实话这些书很多只看了一部分,我通常会带着问题看书,不然看着看着就睡着了,简直是催眠良药😅。
-
-
-最后,附一张自己面试前准备的脑图:
-
-链接:https://pan.baidu.com/s/1o2l1tuRakBEP0InKEh4Hzw 密码:300d
-
-全文完。
diff --git "a/docs/essential-content-for-interview/BATJrealInterviewExperience/5\351\235\242\351\230\277\351\207\214,\347\273\210\350\216\267offer.md" "b/docs/essential-content-for-interview/BATJrealInterviewExperience/5\351\235\242\351\230\277\351\207\214,\347\273\210\350\216\267offer.md"
deleted file mode 100644
index 9efac14f6d4..00000000000
--- "a/docs/essential-content-for-interview/BATJrealInterviewExperience/5\351\235\242\351\230\277\351\207\214,\347\273\210\350\216\267offer.md"
+++ /dev/null
@@ -1,96 +0,0 @@
-> 作者:ppxyn。本文来自读者投稿,同时也欢迎各位投稿,**对于不错的原创文章我根据你的选择给予现金(50-200)、付费专栏或者任选书籍进行奖励!所以,快提 pr 或者邮件的方式(邮件地址在主页)给我投稿吧!** 当然,我觉得奖励是次要的,最重要的是你可以从自己整理知识点的过程中学习到很多知识。
-
-**目录**
-
-
-
-- [前言](#前言)
-- [一面\(技术面\)](#一面技术面)
-- [二面\(技术面\)](#二面技术面)
-- [三面\(技术面\)](#三面技术面)
-- [四面\(半个技术面\)](#四面半个技术面)
-- [五面\(HR面\)](#五面hr面)
-- [总结](#总结)
-
-
-
-### 前言
-
-在接触 Java 之前我接触的比较多的是硬件方面,用的比较多的语言就是C和C++。到了大三我才正式选择 Java 方向,到目前为止使用Java到现在大概有一年多的时间,所以Java算不上很好。刚开始投递的时候,实习刚辞职,也没准备笔试面试,很多东西都忘记了。所以,刚开始我并没有直接就投递阿里,毕竟心里还是有一点点小害怕的。于是,我就先投递了几个不算大的公司来练手,就是想着刷刷经验而已或者说是练练手(ps:还是挺对不起那些公司的)。面了一个月其他公司后,我找了我实验室的学长内推我,后面就有了这5次面试。
-
-下面简单的说一下我的这5次面试:4次技术面+1次HR面,希望我的经历能对你有所帮助。
-
-### 一面(技术面)
-
-1. 自我介绍(主要讲自己会的技术细节,项目经验,经历那些就一语带过,后面面试官会问你的)。
-2. 聊聊项目(就是一个很普通的分布式商城,自己做了一些改进),让我画了整个项目的架构图,然后针对项目抛了一系列的提高性能的问题,还问了我做项目的过程中遇到了那些问题,如何解决的,差不读就这些吧。
-3. 可能是我前面说了我会数据库优化,然后面试官就开始问索引、事务隔离级别、悲观锁和乐观锁、索引、ACID、MVVC这些问题。
-4. 浏览器输入URL发生了什么? TCP和UDP区别? TCP如何保证传输可靠性?
-5. 讲下跳表怎么实现的?哈夫曼编码是怎么回事?非递归且不用额外空间(不用栈),如何遍历二叉树
-6. 后面又问了很多JVM方面的问题,比如Java内存模型、常见的垃圾回收器、双亲委派模型这些
-7. 你有什么问题要问吗?
-
-### 二面(技术面)
-
-1. 自我介绍(主要讲自己会的技术细节,项目经验,经历那些就一语带过,后面面试官会问你的)。
-2. 操作系统的内存管理机制
-3. 进程和线程的区别
-4. 说下你对线程安全的理解
-5. volatile 有什么作用 ,sychronized和lock有什么区别
-6. ReentrantLock实现原理
-7. 用过CountDownLatch么?什么场景下用的?
-8. AQS底层原理。
-9. 造成死锁的原因有哪些,如何预防?
-10. 加锁会带来哪些性能问题。如何解决?
-11. HashMap、ConcurrentHashMap源码。HashMap是线程安全的吗?Hashtable呢?ConcurrentHashMap有了解吗?
-12. 是否可以实习?
-13. 你有什么问题要问吗?
-
-### 三面(技术面)
-
-1. 有没有参加过 ACM 或者他竞赛,有没有拿过什么奖?( 我说我没参加过ACM,本科参加过数学建模竞赛,名次并不好,没拿过什么奖。面试官好像有点失望,然后我又赶紧补充说我和老师一起做过一个项目,目前已经投入使用。面试官还比较感兴趣,后面又和他聊了一下这个项目。)
-2. 研究生期间,做过什么项目,发过论文吗?有什么成果吗?
-3. 你觉得你有什么优点和缺点?你觉得你相比于那些比你更优秀的人欠缺什么?
-4. 有读过什么源码吗?(我说我读过 Java 集合框架和 Netty 的,面试官说 Java 集合前几面一定问的差不多,就不问了,然后就问我 Netty的,我当时很慌啊!)
-5. 介绍一下自己对 Netty 的认识,为什么要用。说说业务中,Netty 的使用场景。什么是TCP 粘包/拆包,解决办法。Netty线程模型。Dubbo 在使用 Netty 作为网络通讯时候是如何避免粘包与半包问题?讲讲Netty的零拷贝?巴拉巴拉问了好多,我记得有好几个我都没回答上来,心里想着凉凉了啊。
-6. 用到了那些开源技术、在开源领域做过贡献吗?
-7. 常见的排序算法及其复杂度,现场写了快排。
-8. 红黑树,B树的一些问题。
-9. 讲讲算法及数据结构在实习项目中的用处。
-10. 自己的未来规划(就简单描述了一下自己未来的设想啊,说的还挺诚恳,面试官好像还挺满意的)
-11. 你有什么问题要问吗?
-
-### 四面(半个技术面)
-
-三面面完当天,晚上9点接到面试电话,感觉像是部门或者项目主管。 这个和之前的面试不大相同,感觉面试官主要考察的是你解决问题的能力、学习能力和团队协作能力。
-
-1. 让我讲一个自己觉得最不错的项目。然后就巴拉巴拉的聊,我记得主要是问了项目是如何进行协作的、遇到问题是如何解决的、与他人发生冲突是如何解决的这些。感觉聊了挺久。
-2. 出现 OOM 后你会怎么排查问题?
-3. 自己平时是如何学习新技术的?除了 Java 还回去了解其他技术吗?
-4. 上一段实习经历的收获。
-5. NginX如何做负载均衡、常见的负载均衡算法有哪些、一致性哈希的一致性是什么意思、一致性哈希是如何做哈希的
-6. 你有什么问题问我吗?
-7. 还有一些其他的,想不起来了,感觉这一面不是偏向技术来问。
-
-## 五面(HR面)
-
-1. 自我介绍(主要讲能突出自己的经历,会的编程技术一语带过)。
-2. 你觉得你有什么优点和缺点?如何克服这些缺点?
-3. 说一件大学里你自己比较有成就感的一件事情,为此付出了那些努力。
-4. 你前面跟其他面试官讲过一些你做的项目吧?可以给我讲讲吗?你要考虑到我不是一个做技术的人,怎么让我也听得懂。项目中有什么问题,你怎么解决的?你最大的收获是什么?
-5. 你目前有面试过其他公司吗?如果让你选,这些公司和阿里,你选哪个?(送分题,回答不好可能送命)
-6. 你期望的工作地点是哪里?
-7. 你有什么问题吗?
-
-### 总结
-
-1. 可以看出面试官问我的很多问题都是比较常见的问题,所以记得一定要提前准备,还要深入准备,不要回答的太皮毛。很多时候一个问题可能会牵扯出很多问题,遇到不会的问题不要慌,冷静分析,如果你真的回答不上来,也不要担心自己是不是就要挂了,很可能这个问题本身就比较难。
-2. 表达能力和沟通能力太重要了,一定要提前练一下,我自身就是一个不太会说话的人,所以,面试前我对于自我介绍、项目介绍和一些常见问题都在脑子里练了好久,确保面试的时候能够很清晰和简洁的说出来。
-3. 等待面试的过程和面试的过程真的好熬人,那段时间我压力也比较大,好在我私下找到学长聊了很多,心情也好了很多。
-4. 面试之后及时总结,面的好的话,不要得意,尽快准备下一场面试吧!
-
-我觉得我还算是比较幸运的,最后也祝大家都能获得心仪的Offer。
-
-
-
-
diff --git "a/docs/essential-content-for-interview/BATJrealInterviewExperience/\350\232\202\350\232\201\351\207\221\346\234\215\345\256\236\344\271\240\347\224\237\351\235\242\347\273\217\346\200\273\347\273\223(\345\267\262\346\213\277\345\217\243\345\244\264offer).md" "b/docs/essential-content-for-interview/BATJrealInterviewExperience/\350\232\202\350\232\201\351\207\221\346\234\215\345\256\236\344\271\240\347\224\237\351\235\242\347\273\217\346\200\273\347\273\223(\345\267\262\346\213\277\345\217\243\345\244\264offer).md"
deleted file mode 100644
index 2e2df23b941..00000000000
--- "a/docs/essential-content-for-interview/BATJrealInterviewExperience/\350\232\202\350\232\201\351\207\221\346\234\215\345\256\236\344\271\240\347\224\237\351\235\242\347\273\217\346\200\273\347\273\223(\345\267\262\346\213\277\345\217\243\345\244\264offer).md"
+++ /dev/null
@@ -1,251 +0,0 @@
-本文来自 Anonymous 的投稿 ,JavaGuide 对原文进行了重新排版和一点完善。
-
-
-
-- [一面 (37 分钟左右)](#一面-37-分钟左右)
-- [二面 (33 分钟左右)](#二面-33-分钟左右)
-- [三面 (46 分钟)](#三面-46-分钟)
-- [HR 面](#hr-面)
-
-
-
-### 一面 (37 分钟左右)
-
-一面是上海的小哥打来的,3.12 号中午确认的内推,下午就打来约时间了,也是唯一一个约时间的面试官。约的晚上八点。紧张的一比,人生第一次面试就献给了阿里。
-
-幸运的是一面的小哥特温柔。好像是个海归?口语中夹杂着英文。废话不多说,上干货:
-
-**面试官:** 先自我介绍下吧!
-
-**我:** 巴拉巴拉...。
-
-> 关于自我介绍:从 HR 面、技术面到高管面/部门主管面,面试官一般会让你先自我介绍一下,所以好好准备自己的自我介绍真的非常重要。网上一般建议的是准备好两份自我介绍:一份对 HR 说的,主要讲能突出自己的经历,会的编程技术一语带过;另一份对技术面试官说的,主要讲自己会的技术细节,项目经验,经历那些就一语带过。
-
-**面试官:** 我看你简历上写你做了个秒杀系统?我们就从这个项目开始吧,先介绍下你的项目。
-
-> 关于项目介绍:如果有项目的话,技术面试第一步,面试官一般都是让你自己介绍一下你的项目。你可以从下面几个方向来考虑:
->
-> 1. 对项目整体设计的一个感受(面试官可能会让你画系统的架构图)
-> 2. 在这个项目中你负责了什么、做了什么、担任了什么角色
-> 3. 从这个项目中你学会了那些东西,使用到了那些技术,学会了那些新技术的使用
-> 4. 另外项目描述中,最好可以体现自己的综合素质,比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的又或者说你在这个项目用了什么技术实现了什么功能比如:用 redis 做缓存提高访问速度和并发量、使用消息队列削峰和降流等等。
-
-**我:** 我说了我是如何考虑它的需求(秒杀地址隐藏,记录订单,减库存),一开始简单的用 synchronized 锁住方法,出现了问题,后来乐观锁改进,又有瓶颈,再上缓存,出现了缓存雪崩,于是缓存预热,错开缓存失效时间。最后,发现先记录订单再减库存会减少行级锁等待时间。
-
-> 一面面试官很耐心地听,并给了我一些指导,问了我乐观锁是怎么实现的,我说是基于 sql 语句,在减库存操作的 where 条件里加剩余库存数>0,他说这应该不算是一种乐观锁,应该先查库存,在减库存的时候判断当前库存是否与读到的库存一样(可这样不是多一次查询操作吗?不是很理解,不过我没有反驳,只是说理解您的意思。事实证明千万别怼面试官,即使你觉得他说的不对)
-
-**面试官:** 我缓存雪崩什么情况下会发生?如何避免?
-
-**我:** 当多个商品缓存同时失效时会雪崩,导致大量查询数据库。还有就是秒杀刚开始的时候缓存里没有数据。解决方案:缓存预热,错开缓存失效时间
-
-**面试官:** 问我更新数据库的同时为什么不马上更新缓存,而是删除缓存?
-
-**我:** 因为考虑到更新数据库后更新缓存可能会因为多线程下导致写入脏数据(比如线程 A 先更新数据库成功,接下来要取更新缓存,接着线程 B 更新数据库,但 B 又更新了缓存,接着 B 的时间片用完了,线程 A 更新了缓存)
-
-逼逼了将近 30 分钟,面试官居然用周杰伦的语气对我说:
-
-
-
-我突然受宠若惊,连忙说谢谢,也正是因为第一次面试得到了面试官的肯定,才让我信心大增,二三面稳定发挥。
-
-**面试官又曰:** 我看你还懂数据库是吧,答:略懂略懂。。。那我问个简单的吧!
-
-**我:** 因为这个问题太简单了,所以我忘记它是什么了。
-
-**面试官:** 你还会啥数据库知识?
-
-**我:** 我一听,问的这么随意的吗。。。都让我选题了,我就说我了解索引,慢查询优化,巴拉巴拉
-
-**面试官:** 等等,你说索引是吧,那你能说下索引的存储数据结构吗?
-
-**我:** 我心想这简单啊,我就说 B+树,还说了为什么用 B+树
-
-**面试官:** 你简历上写的这个 J.U.C 包是什么啊?(他居然不知道 JUC)
-
-**我:** 就是 java 多线程的那个包啊。。。
-
-**面试官:** 那你都了解里面的哪些东西呢?
-
-**我:** 哈哈哈!这可是我的强项,从 ConcurrentHashMap,ConcurrentLinkedQueue 说到 CountDownLatch,CyclicBarrier,又说到线程池,分别说了底层实现和项目中的应用。
-
-**面试官:** 我觉得差不多了,那我再问个与技术无关的问题哈,虽然这个问题可能不应该我问,就是你是如何考虑你的项目架构的呢?
-
-**我:** 先用最简单的方式实现它,再去发掘系统的问题和瓶颈,于是查资料改进架构。。。
-
-**面试官:** 好,那我给你介绍下我这边的情况吧
-
-
-
-**总结:** 一面可能是简历面吧,问的比较简单,我在讲项目中说出了我做项目时的学习历程和思考,赢得了面试官的好感,感觉他应该给我的评价很好。
-
-### 二面 (33 分钟左右)
-
-然而开心了没一会,内推人问我面的怎么样啊?看我流程已经到大大 boss 那了。我一听二面不是主管吗???怎么直接跳了一面。于是瞬间慌了,赶紧(下床)学习准备二面。
-
-隔了一天,3.14 的早上 10:56 分,杭州的大大 boss 给我打来了电话,卧槽我当时在上毛概课,万恶的毛概课每节课都点名,我还在最后一排不敢跑出去。于是接起电话来怂怂地说不好意思我在上课,晚上可以面试吗?大大 boss 看来很忙啊,跟我说晚上没时间啊,再说吧!
-
-于是又隔了一天,3.16 中午我收到了北京的电话,当时心里小失望,我的大大 boss 呢???接起电话来,就是一番狂轰乱炸。。。
-
-第一步还是先自我介绍,这个就不多说了,提前准备好要说的重点就没问题!
-
-**面试官:** 我们还是从你的项目开始吧,说说你的秒杀系统。
-
-**我:** 一面时的套路。。。我考虑到秒杀地址在开始前不应暴露给用户。。。
-
-**面试官:** 等下啊,为什么要这样呢?暴露给用户会怎么样?
-
-**我:** 用户提前知道秒杀地址就可以写脚本来抢购了,这样不公平
-
-**面试官:** 那比如说啊,我现在是个黑客,我在秒杀开始时写好了脚本,运行一万个线程获取秒杀地址,这样是不是也不公平呢?
-
-**我:** 我考虑到了这方面,于是我自己写了个 LRU 缓存(划重点,这么多好用的缓存我为啥不用偏要自己写?就是为了让面试官上钩问我是怎么写的,这样我就可以逼逼准备好的内容了!),用这个缓存存储请求的 ip 和用户名,一个 ip 和用户名只能同时透过 3 个请求。
-
-**面试官:** 那我可不可以创建一个 ip 代理池和很多用户来抢购呢?假设我有很多手机号的账户。
-
-**我:** 这就是在为难我胖虎啊,我说这种情况跟真实用户操作太像了。。。我没法区别,不过我觉得可以通过地理位置信息或者机器学习算法来做吧。。。
-
-**面试官:** 好的这个问题就到这吧,你接着说
-
-**我:** 我把生成订单和减库存两条 sql 语句放在一个事务里,都操作成功了则认为秒杀成功。
-
-**面试官:** 等等,你这个订单表和商品库存表是在一个数据库的吧,那如果在不同的数据库中呢?
-
-**我:** 这面试官好变态啊,我只是个本科生?!?!我觉得应该要用分布式锁来实现吧。。。
-
-**面试官:** 有没有更轻量级的做法?
-
-**我:** 不知道了。后来查资料发现可以用消息队列来实现。使用消息队列主要能带来两个好处:(1) 通过异步处理提高系统性能(削峰、减少响应所需时间);(2) 降低系统耦合性。关于消息队列的更多内容可以查看这篇文章:
-
-后来发现消息队列作用好大,于是现在在学手写一个消息队列。
-
-**面试官:** 好的你接着说项目吧。
-
-**我:** 我考虑到了缓存雪崩问题,于是。。。
-
-**面试官:** 等等,你有没有考虑到一种情况,假如说你的缓存刚刚失效,大量流量就来查缓存,你的数据库会不会炸?
-
-**我:** 我不知道数据库会不会炸,反正我快炸了。当时说没考虑这么高的并发量,后来发现也是可以用消息队列来解决,对流量削峰填谷。
-
-**面试官:** 好项目聊(怼)完了,我们来说说别的,操作系统了解吧,你能说说 NIO 吗?
-
-**我:** NIO 是。。。
-
-**面试官:** 那你知道 NIO 的系统调用有哪些吗,具体是怎么实现的?
-
-**我:** 当时复习 NIO 的时候就知道是咋回事,不知道咋实现。最近在补这方面的知识,可见 NIO 还是很重要的!
-
-**面试官:** 说说进程切换时操作系统都会发生什么?
-
-**我:** 不如杀了我,我最讨厌操作系统了。简单说了下,可能不对,需要答案自行百度。
-
-**面试官:** 说说线程池?
-
-**答:** 卧槽这我熟啊,把 Java 并发编程的艺术里讲的都说出来了,说了得有十分钟,自夸一波,毕竟这本书我看了五遍😂
-
-**面试官:** 好问问计网吧如果设计一个聊天系统,应该用 TCP 还是 UDP?为什么
-
-**我:** 当然是 TCP!原因如下:
-
-
-
-**面试官:** 好的,你有什么要问我的吗?
-
-**我:** 我还有下一次面试吗?
-
-**面试官:** 应该。应该有的,一周内吧。还告诉我居然转正前要实习三个月?wtf,一个大三满课的本科生让我如何在八月底前实习三个月?
-
-**我:** 面试官再见
-
-
-
-### 三面 (46 分钟)
-
-3.18 号,三面来了,这次又是那个大大 boss!
-
-第一步还是先自我介绍,这个就不多说了,提前准备好要说的重点就没问题!
-
-**面试官:** 聊聊你的项目?
-
-**我:** 经过二面的教训,我迅速学习了一下分布式的理论知识,并应用到了我的项目(吹牛逼)中。
-
-**面试官:** 看你用到了 Spring 的事务机制,你能说下 Spring 的事务传播吗?
-
-**我:** 完了这个问题好像没准备,虽然之前刷知乎看到过。。。我就只说出来一条,面试官说其实这个有很多机制的,比如事务嵌套,内事务回滚外事务回滚都会有不同情况,你可以回去看看。
-
-**面试官:** 说说你的分布式事务解决方案?
-
-**我:** 我叭叭的照着资料查到的解决方案说了一通,面试官怎么好像没大听懂???
-
-> 阿里巴巴之前开源了一个分布式 Fescar(一种易于使用,高性能,基于 Java 的开源分布式事务解决方案),后来,Ant Financial 加入 Fescar,使其成为一个更加中立和开放的分布式交易社区,Fescar 重命名为 Seata。Github 地址:
-
-**面试官:** 好,我们聊聊其他项目,说说你这个 MapReduce 项目?MapReduce 原理了解过吗?
-
-**我:** 我叭叭地说了一通,面试官好像觉得这个项目太简单了。要不是没项目,我会把我的实验写上吗???
-
-**面试官:** 你这个手写 BP 神经网络是干了啥?
-
-**我:** 这是我选修机器学习课程时的一个作业,我又对它进行了扩展。
-
-**面试官:** 你能说说为什么调整权值时要沿着梯度下降的方向?
-
-**我:** 老大,你太厉害了,怎么什么都懂。我压根没准备这个项目。。。没想到会问,做过去好几个月了,加上当时一紧张就忘了,后来想起来大概是....。
-
-**面试官:** 好我们问问基础知识吧,说说什么叫 xisuo?
-
-**我:**???xisuo,您说什么,不好意思我没听清。(这面试官有点口音。。。)就是 xisuo 啊!xisuo 你不知道吗?。。。尴尬了十几秒后我终于意识到,他在说死锁!!!
-
-**面试官:** 假如 A 账户给 B 账户转钱,会发生 xisuo 吗?能具体说说吗?
-
-**我:** 当时答的不好,后来发现面试官又是想问分布式,具体答案参考这个:
-
-**面试官:** 为什么不考研?
-
-**我:** 不喜欢学术氛围,巴拉巴拉。
-
-**面试官:** 你有什么问题吗?
-
-**我:** 我还有下一面吗。。。面试官说让我等,一周内答复。
-
-------
-
-等了十天,一度以为我凉了,内推人说我流程到 HR 了,让我等着吧可能 HR 太忙了,3.28 号 HR 打来了电话,当时在教室,我直接飞了出去。
-
-### HR 面
-
-**面试官:** 你好啊,先自我介绍下吧
-
-**我:** 巴拉巴拉....HR 面的技术面试和技术面的还是有所区别的!
-
-面试官人特别好,一听就是很会说话的小姐姐!说我这里给你悄悄透露下,你的评级是 A 哦!
-
-
-
-接下来就是几个经典 HR 面挂人的问题,什么难给我来什么,我看别人的 HR 面怎么都是聊聊天。。。
-
-**面试官:** 你为什么选择支付宝呢,你怎么看待支付宝?
-
-**我:** 我从个人情怀,公司理念,环境氛围,市场价值,趋势导向分析了一波(说白了就是疯狂夸支付宝,不过说实话我说的那些一点都没撒谎,阿里确实做到了。比如我举了个雷军和格力打赌 5 年 2000 亿销售额,大部分企业家关注的是利益,而马云更关注的是真的为人类为世界做一些事情,利益不是第一位的。)
-
-**面试官:** 明白了解,那你的优点我们都很明了了,你能说说你的缺点吗?
-
-> 缺点肯定不能是目标岗位需要的关键能力!!!
->
-> 总之,记住一点,面试官问你这个问题的话,你可以说一些不影响你这个职位工作需要的一些缺点。比如你面试后端工程师,面试官问你的缺点是什么的话,你可以这样说:自己比较内向,平时不太爱与人交流,但是考虑到以后可能要和客户沟通,自己正在努力改。
-
-**我:** 据说这是 HR 面最难的一个问题。。。我当时翻了好几天的知乎才找到一个合适的,也符合我的答案:我有时候会表现的不太自信,比如阿里的内推二月份就开始了,其实我当时已经复习了很久了,但是老是觉得自己还不行,不敢投简历,于是又把书看了一遍才投的,当时也是舍友怂恿一波才投的,面了之后发现其实自己也没有很差。(划重点,一定要把自己的缺点圆回来)。
-
-**面试官:** HR 好像不太满意我的答案,继续问我还有缺点吗?
-
-**我:** 我说比较容易紧张吧,举了自己大一面实验室因为紧张没进去的例子,后来不断调整心态,现在已经好很多了。
-
-接下来又是个好难的问题。
-
-**面试官:** BAT 都给你 offer 了,你怎么选?
-
-其实我当时好想说,BT 是什么?不好意思我只知道阿里。
-
-**我 :** 哈哈哈哈开玩笑,就说了阿里的文化,支付宝给我们带来很多便利,想加入支付宝为人类做贡献!
-
-最后 HR 问了我实习时间,现在大几之类的问题,说肯定会给我发 offer 的,让我等着就好了,希望过两天能收到好的结果。
-
-
diff --git "a/docs/essential-content-for-interview/MostCommonJavaInterviewQuestions/\347\254\254\344\270\200\345\221\250\357\274\2102018-8-7\357\274\211.md" "b/docs/essential-content-for-interview/MostCommonJavaInterviewQuestions/\347\254\254\344\270\200\345\221\250\357\274\2102018-8-7\357\274\211.md"
deleted file mode 100644
index 300f1fd6c11..00000000000
--- "a/docs/essential-content-for-interview/MostCommonJavaInterviewQuestions/\347\254\254\344\270\200\345\221\250\357\274\2102018-8-7\357\274\211.md"
+++ /dev/null
@@ -1,251 +0,0 @@
-
-
-## 一 为什么 Java 中只有值传递?
-
-
-首先回顾一下在程序设计语言中有关将参数传递给方法(或函数)的一些专业术语。**按值调用(call by value)表示方法接收的是调用者提供的值,而按引用调用(call by reference)表示方法接收的是调用者提供的变量地址。一个方法可以修改传递引用所对应的变量值,而不能修改传递值调用所对应的变量值。** 它用来描述各种程序设计语言(不只是Java)中方法参数传递方式。
-
-**Java程序设计语言总是采用按值调用。也就是说,方法得到的是所有参数值的一个拷贝,也就是说,方法不能修改传递给它的任何参数变量的内容。**
-
-**下面通过 3 个例子来给大家说明**
-
-### example 1
-
-
-```java
-public static void main(String[] args) {
- int num1 = 10;
- int num2 = 20;
-
- swap(num1, num2);
-
- System.out.println("num1 = " + num1);
- System.out.println("num2 = " + num2);
-}
-
-public static void swap(int a, int b) {
- int temp = a;
- a = b;
- b = temp;
-
- System.out.println("a = " + a);
- System.out.println("b = " + b);
-}
-```
-
-**结果:**
-
-```
-a = 20
-b = 10
-num1 = 10
-num2 = 20
-```
-
-**解析:**
-
-
-
-在swap方法中,a、b的值进行交换,并不会影响到 num1、num2。因为,a、b中的值,只是从 num1、num2 的复制过来的。也就是说,a、b相当于num1、num2 的副本,副本的内容无论怎么修改,都不会影响到原件本身。
-
-**通过上面例子,我们已经知道了一个方法不能修改一个基本数据类型的参数,而对象引用作为参数就不一样,请看 example2.**
-
-
-### example 2
-
-```java
- public static void main(String[] args) {
- int[] arr = { 1, 2, 3, 4, 5 };
- System.out.println(arr[0]);
- change(arr);
- System.out.println(arr[0]);
- }
-
- public static void change(int[] array) {
- // 将数组的第一个元素变为0
- array[0] = 0;
- }
-```
-
-**结果:**
-
-```
-1
-0
-```
-
-**解析:**
-
-
-
-array 被初始化 arr 的拷贝也就是一个对象的引用,也就是说 array 和 arr 指向的是同一个数组对象。 因此,外部对引用对象的改变会反映到所对应的对象上。
-
-
-**通过 example2 我们已经看到,实现一个改变对象参数状态的方法并不是一件难事。理由很简单,方法得到的是对象引用的拷贝,对象引用及其他的拷贝同时引用同一个对象。**
-
-**很多程序设计语言(特别是,C++和Pascal)提供了两种参数传递的方式:值调用和引用调用。有些程序员(甚至本书的作者)认为Java程序设计语言对对象采用的是引用调用,实际上,这种理解是不对的。由于这种误解具有一定的普遍性,所以下面给出一个反例来详细地阐述一下这个问题。**
-
-
-### example 3
-
-```java
-public class Test {
-
- public static void main(String[] args) {
- // TODO Auto-generated method stub
- Student s1 = new Student("小张");
- Student s2 = new Student("小李");
- Test.swap(s1, s2);
- System.out.println("s1:" + s1.getName());
- System.out.println("s2:" + s2.getName());
- }
-
- public static void swap(Student x, Student y) {
- Student temp = x;
- x = y;
- y = temp;
- System.out.println("x:" + x.getName());
- System.out.println("y:" + y.getName());
- }
-}
-```
-
-**结果:**
-
-```
-x:小李
-y:小张
-s1:小张
-s2:小李
-```
-
-**解析:**
-
-交换之前:
-
-
-
-交换之后:
-
-
-
-
-通过上面两张图可以很清晰的看出: **方法并没有改变存储在变量 s1 和 s2 中的对象引用。swap方法的参数x和y被初始化为两个对象引用的拷贝,这个方法交换的是这两个拷贝**
-
-### 总结
-
-Java程序设计语言对对象采用的不是引用调用,实际上,对象引用是按
-值传递的。
-
-下面再总结一下Java中方法参数的使用情况:
-
-- 一个方法不能修改一个基本数据类型的参数(即数值型或布尔型)。
-- 一个方法可以改变一个对象参数的状态。
-- 一个方法不能让对象参数引用一个新的对象。
-
-
-### 参考:
-
-《Java核心技术卷Ⅰ》基础知识第十版第四章4.5小节
-
-## 二 ==与equals(重要)
-
-**==** : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象。(基本数据类型==比较的是值,引用数据类型==比较的是内存地址)
-
-**equals()** : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况:
-
-- 情况1:类没有覆盖equals()方法。则通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象。
-- 情况2:类覆盖了equals()方法。一般,我们都覆盖equals()方法来两个对象的内容相等;若它们的内容相等,则返回true(即,认为这两个对象相等)。
-
-
-**举个例子:**
-
-```java
-public class test1 {
- public static void main(String[] args) {
- String a = new String("ab"); // a 为一个引用
- String b = new String("ab"); // b为另一个引用,对象的内容一样
- String aa = "ab"; // 放在常量池中
- String bb = "ab"; // 从常量池中查找
- if (aa == bb) // true
- System.out.println("aa==bb");
- if (a == b) // false,非同一对象
- System.out.println("a==b");
- if (a.equals(b)) // true
- System.out.println("aEQb");
- if (42 == 42.0) { // true
- System.out.println("true");
- }
- }
-}
-```
-
-**说明:**
-
-- String中的equals方法是被重写过的,因为object的equals方法是比较的对象的内存地址,而String的equals方法比较的是对象的值。
-- 当创建String类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个String对象。
-
-
-
-## 三 hashCode与equals(重要)
-
-面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写equals时必须重写hashCode方法?”
-
-### hashCode()介绍
-hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数。另外需要注意的是: Object 的 hashcode 方法是本地方法,也就是用 c 语言或 c++ 实现的,该方法通常用来将对象的 内存地址 转换为整数之后返回。
-
-```java
- /**
- * Returns a hash code value for the object. This method is
- * supported for the benefit of hash tables such as those provided by
- * {@link java.util.HashMap}.
- *
- * As much as is reasonably practical, the hashCode method defined by
- * class {@code Object} does return distinct integers for distinct
- * objects. (This is typically implemented by converting the internal
- * address of the object into an integer, but this implementation
- * technique is not required by the
- * Java™ programming language.)
- *
- * @return a hash code value for this object.
- * @see java.lang.Object#equals(java.lang.Object)
- * @see java.lang.System#identityHashCode
- */
- public native int hashCode();
-```
-
-散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)
-
-### 为什么要有hashCode
-
-
-**我们以“HashSet如何检查重复”为例子来说明为什么要有hashCode:**
-
-当你把对象加入HashSet时,HashSet会先计算对象的hashcode值来判断对象加入的位置,同时也会与其他已经加入的对象的hashcode值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同hashcode值的对象,这时会调用equals()方法来检查hashcode相等的对象是否真的相同。如果两者相同,HashSet就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的Java启蒙书《Head fist java》第二版)。这样我们就大大减少了equals的次数,相应就大大提高了执行速度。
-
-
-
-### hashCode()与equals()的相关规定
-
-1. 如果两个对象相等,则hashcode一定也是相同的
-2. 两个对象相等,对两个对象分别调用equals方法都返回true
-3. 两个对象有相同的hashcode值,它们也不一定是相等的
-4. **因此,equals方法被覆盖过,则hashCode方法也必须被覆盖**
-5. hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)
-
-### 为什么两个对象有相同的hashcode值,它们也不一定是相等的?
-
-在这里解释一位小伙伴的问题。以下内容摘自《Head Fisrt Java》。
-
-因为hashCode() 所使用的杂凑算法也许刚好会让多个对象传回相同的杂凑值。越糟糕的杂凑算法越容易碰撞,但这也与数据值域分布的特性有关(所谓碰撞也就是指的是不同的对象得到相同的 hashCode)。
-
-我们刚刚也提到了 HashSet,如果 HashSet 在对比的时候,同样的 hashcode 有多个对象,它会使用 equals() 来判断是否真的相同。也就是说 hashcode 只是用来缩小查找成本。
-
-参考:
-
-[https://blog.csdn.net/zhzhao999/article/details/53449504](https://blog.csdn.net/zhzhao999/article/details/53449504)
-
-[https://www.cnblogs.com/skywang12345/p/3324958.html](https://www.cnblogs.com/skywang12345/p/3324958.html)
-
-[https://www.cnblogs.com/Eason-S/p/5524837.html](https://www.cnblogs.com/Eason-S/p/5524837.html)
-
diff --git "a/docs/essential-content-for-interview/MostCommonJavaInterviewQuestions/\347\254\254\344\272\214\345\221\250(2018-8-13).md" "b/docs/essential-content-for-interview/MostCommonJavaInterviewQuestions/\347\254\254\344\272\214\345\221\250(2018-8-13).md"
deleted file mode 100644
index 2839aae916c..00000000000
--- "a/docs/essential-content-for-interview/MostCommonJavaInterviewQuestions/\347\254\254\344\272\214\345\221\250(2018-8-13).md"
+++ /dev/null
@@ -1,200 +0,0 @@
-
-### String和StringBuffer、StringBuilder的区别是什么?String为什么是不可变的?
-
-#### String和StringBuffer、StringBuilder的区别
-
-**可变性**
-
-
-简单的来说:String 类中使用 final 关键字字符数组保存字符串,`private final char value[]`,所以 String 对象是不可变的。而StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串`char[]value` 但是没有用 final 关键字修饰,所以这两种对象都是可变的。
-
-StringBuilder 与 StringBuffer 的构造方法都是调用父类构造方法也就是 AbstractStringBuilder 实现的,大家可以自行查阅源码。
-
-AbstractStringBuilder.java
-
-```java
-abstract class AbstractStringBuilder implements Appendable, CharSequence {
- char[] value;
- int count;
- AbstractStringBuilder() {
- }
- AbstractStringBuilder(int capacity) {
- value = new char[capacity];
- }
-```
-
-
-**线程安全性**
-
-String 中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
-
-
-**性能**
-
-每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StirngBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
-
-**对于三者使用的总结:**
-1. 操作少量的数据 = String
-2. 单线程操作字符串缓冲区下操作大量数据 = StringBuilder
-3. 多线程操作字符串缓冲区下操作大量数据 = StringBuffer
-
-#### String为什么是不可变的吗?
-简单来说就是String类利用了final修饰的char类型数组存储字符,源码如下图所以:
-
-```java
- /** The value is used for character storage. */
- private final char value[];
-```
-
-#### String真的是不可变的吗?
-我觉得如果别人问这个问题的话,回答不可变就可以了。
-下面只是给大家看两个有代表性的例子:
-
-**1) String不可变但不代表引用不可以变**
-```java
- String str = "Hello";
- str = str + " World";
- System.out.println("str=" + str);
-```
-结果:
-```
-str=Hello World
-```
-解析:
-
-实际上,原来String的内容是不变的,只是str由原来指向"Hello"的内存地址转为指向"Hello World"的内存地址而已,也就是说多开辟了一块内存区域给"Hello World"字符串。
-
-**2) 通过反射是可以修改所谓的“不可变”对象**
-
-```java
- // 创建字符串"Hello World", 并赋给引用s
- String s = "Hello World";
-
- System.out.println("s = " + s); // Hello World
-
- // 获取String类中的value字段
- Field valueFieldOfString = String.class.getDeclaredField("value");
-
- // 改变value属性的访问权限
- valueFieldOfString.setAccessible(true);
-
- // 获取s对象上的value属性的值
- char[] value = (char[]) valueFieldOfString.get(s);
-
- // 改变value所引用的数组中的第5个字符
- value[5] = '_';
-
- System.out.println("s = " + s); // Hello_World
-```
-
-结果:
-
-```
-s = Hello World
-s = Hello_World
-```
-
-解析:
-
-用反射可以访问私有成员, 然后反射出String对象中的value属性, 进而改变通过获得的value引用改变数组的结构。但是一般我们不会这么做,这里只是简单提一下有这个东西。
-
-### 什么是反射机制?反射机制的应用场景有哪些?
-
-#### 反射机制介绍
-
-JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
-
-#### 静态编译和动态编译
-
-- **静态编译:**在编译时确定类型,绑定对象
-- **动态编译:**运行时确定类型,绑定对象
-
-#### 反射机制优缺点
-
-- **优点:** 运行期类型的判断,动态加载类,提高代码灵活度。
-- **缺点:** 性能瓶颈:反射相当于一系列解释操作,通知 JVM 要做的事情,性能比直接的java代码要慢很多。
-
-#### 反射的应用场景
-
-反射是框架设计的灵魂。
-
-在我们平时的项目开发过程中,基本上很少会直接使用到反射机制,但这不能说明反射机制没有用,实际上有很多设计、开发都与反射机制有关,例如模块化的开发,通过反射去调用对应的字节码;动态代理设计模式也采用了反射机制,还有我们日常使用的 Spring/Hibernate 等框架也大量使用到了反射机制。
-
-举例:①我们在使用JDBC连接数据库时使用Class.forName()通过反射加载数据库的驱动程序;②Spring框架也用到很多反射机制,最经典的就是xml的配置模式。Spring 通过 XML 配置模式装载 Bean 的过程:1) 将程序内所有 XML 或 Properties 配置文件加载入内存中;
- 2)Java类里面解析xml或properties里面的内容,得到对应实体类的字节码字符串以及相关的属性信息; 3)使用反射机制,根据这个字符串获得某个类的Class实例; 4)动态配置实例的属性
-
-**推荐阅读:**
-
-- [Reflection:Java反射机制的应用场景](https://segmentfault.com/a/1190000010162647?utm_source=tuicool&utm_medium=referral)
-- [Java基础之—反射(非常重要)](https://blog.csdn.net/sinat_38259539/article/details/71799078)
-### 什么是JDK?什么是JRE?什么是JVM?三者之间的联系与区别
-
-这几个是Java中很基本很基本的东西,但是我相信一定还有很多人搞不清楚!为什么呢?因为我们大多数时候在使用现成的编译工具以及环境的时候,并没有去考虑这些东西。
-
-**JDK:** 顾名思义它是给开发者提供的开发工具箱,是给程序开发者用的。它除了包括完整的JRE(Java Runtime Environment),Java运行环境,还包含了其他供开发者使用的工具包。
-
-**JRE:** 普通用户而只需要安装JRE(Java Runtime Environment)来运行Java程序。而程序开发者必须安装JDK来编译、调试程序。
-
-**JVM:** 当我们运行一个程序时,JVM负责将字节码转换为特定机器代码,JVM提供了内存管理/垃圾回收和安全机制等。这种独立于硬件和操作系统,正是java程序可以一次编写多处执行的原因。
-
-**区别与联系:**
-
- 1. JDK用于开发,JRE用于运行java程序 ;
- 2. JDK和JRE中都包含JVM ;
- 3. JVM是java编程语言的核心并且具有平台独立性。
-
-### 什么是字节码?采用字节码的最大好处是什么?
-
-**先看下java中的编译器和解释器:**
-
-Java中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟的机器。这台虚拟的机器在任何平台上都提供给编译程序一个的共同的接口。编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转换为特定系统的机器码执行。在Java中,这种供虚拟机理解的代码叫做`字节码`(即扩展名为`.class`的文件),它不面向任何特定的处理器,只面向虚拟机。每一种平台的解释器是不同的,但是实现的虚拟机是相同的。Java源程序经过编译器编译后变成字节码,字节码由虚拟机解释执行,虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成特定机器上的机器码,然后在特定的机器上运行。这也就是解释了Java的编译与解释并存的特点。
-
- Java源代码---->编译器---->jvm可执行的Java字节码(即虚拟指令)---->jvm---->jvm中解释器----->机器可执行的二进制机器码---->程序运行。
-
-**采用字节码的好处:**
-
-Java语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以Java程序运行时比较高效,而且,由于字节码并不专对一种特定的机器,因此,Java程序无须重新编译便可在多种不同的计算机上运行。
-
-### Java和C++的区别
-
-我知道很多人没学过C++,但是面试官就是没事喜欢拿咱们Java和C++比呀!没办法!!!就算没学过C++,也要记下来!
-
-- 都是面向对象的语言,都支持封装、继承和多态
-- Java不提供指针来直接访问内存,程序内存更加安全
-- Java的类是单继承的,C++支持多重继承;虽然Java的类不可以多继承,但是接口可以多继承。
-- Java有自动内存管理机制,不需要程序员手动释放无用内存
-
-
-### 接口和抽象类的区别是什么?
-
-1. 接口的方法默认是public,所有方法在接口中不能有实现,抽象类可以有非抽象的方法
-2. 接口中的实例变量默认是final类型的,而抽象类中则不一定
-3. 一个类可以实现多个接口,但最多只能实现一个抽象类
-4. 一个类实现接口的话要实现接口的所有方法,而抽象类不一定
-5. 接口不能用new实例化,但可以声明,但是必须引用一个实现该接口的对象 从设计层面来说,抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。
-
-注意:Java8 后接口可以有默认实现( default )。
-
-### 成员变量与局部变量的区别有那些?
-
-1. 从语法形式上,看成员变量是属于类的,而局部变量是在方法中定义的变量或是方法的参数;成员变量可以被public,private,static等修饰符所修饰,而局部变量不能被访问控制修饰符及static所修饰;但是,成员变量和局部变量都能被final所修饰;
-2. 从变量在内存中的存储方式来看,成员变量是对象的一部分,而对象存在于堆内存,局部变量存在于栈内存
-3. 从变量在内存中的生存时间上看,成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动消失。
-4. 成员变量如果没有被赋初值,则会自动以类型的默认值而赋值(一种情况例外被final修饰但没有被static修饰的成员变量必须显示地赋值);而局部变量则不会自动赋值。
-
-### 重载和重写的区别
-
-**重载:** 发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。
-
-**重写:** 发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为private则子类就不能重写该方法。
-
-### 字符型常量和字符串常量的区别
-1) 形式上:
-字符常量是单引号引起的一个字符
-字符串常量是双引号引起的若干个字符
-2) 含义上:
-字符常量相当于一个整形值(ASCII值),可以参加表达式运算
-字符串常量代表一个地址值(该字符串在内存中存放位置)
-3) 占内存大小
-字符常量只占一个字节
-字符串常量占若干个字节(至少一个字符结束标志)
diff --git "a/docs/essential-content-for-interview/MostCommonJavaInterviewQuestions/\347\254\254\345\233\233\345\221\250(2018-8-30).md" "b/docs/essential-content-for-interview/MostCommonJavaInterviewQuestions/\347\254\254\345\233\233\345\221\250(2018-8-30).md"
deleted file mode 100644
index 82d0a02b0b1..00000000000
--- "a/docs/essential-content-for-interview/MostCommonJavaInterviewQuestions/\347\254\254\345\233\233\345\221\250(2018-8-30).md"
+++ /dev/null
@@ -1,196 +0,0 @@
-
-## 1. 简述线程,程序、进程的基本概念。以及他们之间关系是什么?
-
-**线程**与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
-
-**程序**是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码。
-
-**进程**是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。
-
-**线程** 是 **进程** 划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。从另一角度来说,进程属于操作系统的范畴,主要是同一段时间内,可以同时执行一个以上的程序,而线程则是在同一程序内几乎同时执行一个以上的程序段。
-
-**线程上下文的切换比进程上下文切换要快很多**
-
-- 进程切换时,涉及到当前进程的CPU环境的保存和新被调度运行进程的CPU环境的设置。
-- 线程切换仅需要保存和设置少量的寄存器内容,不涉及存储管理方面的操作。
-
-## 2. 线程有哪些基本状态?这些状态是如何定义的?
-
-Java 线程在运行的生命周期中的指定时刻只可能处于下面 6 种不同状态的其中一个状态(图源《Java 并发编程艺术》4.1.4 节)。
-
-
-
-线程在生命周期中并不是固定处于某一个状态而是随着代码的执行在不同状态之间切换。Java 线程状态变迁如下图所示(图源《Java 并发编程艺术》4.1.4 节):
-
-
-
-
-
-由上图可以看出:线程创建之后它将处于 **NEW(新建)** 状态,调用 `start()` 方法后开始运行,线程这时候处于 **READY(可运行)** 状态。可运行状态的线程获得了 CPU 时间片(timeslice)后就处于 **RUNNING(运行)** 状态。
-
-> 操作系统隐藏 Java 虚拟机(JVM)中的 RUNNABLE 和 RUNNING 状态,它只能看到 RUNNABLE 状态(图源:[HowToDoInJava](https://howtodoinjava.com/):[Java Thread Life Cycle and Thread States](https://howtodoinjava.com/java/multi-threading/java-thread-life-cycle-and-thread-states/)),所以 Java 系统一般将这两个状态统称为 **RUNNABLE(运行中)** 状态 。
-
-
-
-当线程执行 `wait()`方法之后,线程进入 **WAITING(等待)**状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态,而 **TIME_WAITING(超时等待)** 状态相当于在等待状态的基础上增加了超时限制,比如通过 `sleep(long millis)`方法或 `wait(long millis)`方法可以将 Java 线程置于 TIMED WAITING 状态。当超时时间到达后 Java 线程将会返回到 RUNNABLE 状态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到 **BLOCKED(阻塞)** 状态。线程在执行 Runnable 的` run() `方法之后将会进入到 **TERMINATED(终止)** 状态。
-
-## 3. 何为多线程?
-
-多线程就是多个线程同时运行或交替运行。单核CPU的话是顺序执行,也就是交替运行。多核CPU的话,因为每个CPU有自己的运算器,所以在多个CPU中可以同时运行。
-
-
-## 4. 为什么要使用多线程?
-
-先从总体上来说:
-
-- **从计算机底层来说:**线程可以比作是轻量级的进程,是程序执行的最小单位,线程间的切换和调度的成本远远小于进程。另外,多核 CPU 时代意味着多个线程可以同时运行,这减少了线程上下文切换的开销。
-- **从当代互联网发展趋势来说:**现在的系统动不动就要求百万级甚至千万级的并发量,而多线程并发编程正是开发高并发系统的基础,利用好多线程机制可以大大提高系统整体的并发能力以及性能。
-
-再深入到计算机底层来探讨:
-
-- **单核时代:** 在单核时代多线程主要是为了提高 CPU 和 IO 设备的综合利用率。举个例子:当只有一个线程的时候会导致 CPU 计算时,IO 设备空闲;进行 IO 操作时,CPU 空闲。我们可以简单地说这两者的利用率目前都是 50%左右。但是当有两个线程的时候就不一样了,当一个线程执行 CPU 计算时,另外一个线程可以进行 IO 操作,这样两个的利用率就可以在理想情况下达到 100%了。
-- **多核时代:** 多核时代多线程主要是为了提高 CPU 利用率。举个例子:假如我们要计算一个复杂的任务,我们只用一个线程的话,CPU 只会一个 CPU 核心被利用到,而创建多个线程就可以让多个 CPU 核心被利用到,这样就提高了 CPU 的利用率。
-## 5 使用多线程常见的三种方式
-
-### ①继承Thread类
-
-MyThread.java
-
-```java
-public class MyThread extends Thread {
- @Override
- public void run() {
- super.run();
- System.out.println("MyThread");
- }
-}
-```
-Run.java
-
-```java
-public class Run {
-
- public static void main(String[] args) {
- MyThread mythread = new MyThread();
- mythread.start();
- System.out.println("运行结束");
- }
-
-}
-
-```
-运行结果:
-
-从上面的运行结果可以看出:线程是一个子任务,CPU以不确定的方式,或者说是以随机的时间来调用线程中的run方法。
-
-### ②实现Runnable接口
-推荐实现Runnable接口方式开发多线程,因为Java单继承但是可以实现多个接口。
-
-MyRunnable.java
-
-```java
-public class MyRunnable implements Runnable {
- @Override
- public void run() {
- System.out.println("MyRunnable");
- }
-}
-```
-
-Run.java
-
-```java
-public class Run {
-
- public static void main(String[] args) {
- Runnable runnable=new MyRunnable();
- Thread thread=new Thread(runnable);
- thread.start();
- System.out.println("运行结束!");
- }
-
-}
-```
-运行结果:
-
-
-### ③使用线程池
-
-**在《阿里巴巴Java开发手册》“并发处理”这一章节,明确指出线程资源必须通过线程池提供,不允许在应用中自行显示创建线程。**
-
-**为什么呢?**
-
-> **使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源开销,解决资源不足的问题。如果不使用线程池,有可能会造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。**
-
-**另外《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险**
-
-> Executors 返回线程池对象的弊端如下:
->
-> - **FixedThreadPool 和 SingleThreadExecutor** : 允许请求的队列长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致OOM。
-> - **CachedThreadPool 和 ScheduledThreadPool** : 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致OOM。
-
-对于线程池感兴趣的可以查看我的这篇文章:[《Java多线程学习(八)线程池与Executor 框架》](http://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484042&idx=1&sn=541dbf2cb969a151d79f4a4f837ee1bd&chksm=fd9854ebcaefddfd1876bb96ab218be3ae7b12546695a403075d4ed22e5e17ff30ebdabc8bbf#rd) 点击阅读原文即可查看到该文章的最新版。
-
-
-## 6 线程的优先级
-
-每个线程都具有各自的优先级,**线程的优先级可以在程序中表明该线程的重要性,如果有很多线程处于就绪状态,系统会根据优先级来决定首先使哪个线程进入运行状态**。但这个并不意味着低
-优先级的线程得不到运行,而只是它运行的几率比较小,如垃圾回收机制线程的优先级就比较低。所以很多垃圾得不到及时的回收处理。
-
-**线程优先级具有继承特性。** 比如A线程启动B线程,则B线程的优先级和A是一样的。
-
-**线程优先级具有随机性。** 也就是说线程优先级高的不一定每一次都先执行完。
-
-Thread类中包含的成员变量代表了线程的某些优先级。如**Thread.MIN_PRIORITY(常数1)**,**Thread.NORM_PRIORITY(常数5)**,
-**Thread.MAX_PRIORITY(常数10)**。其中每个线程的优先级都在**Thread.MIN_PRIORITY(常数1)** 到**Thread.MAX_PRIORITY(常数10)** 之间,在默认情况下优先级都是**Thread.NORM_PRIORITY(常数5)**。
-
-学过操作系统这门课程的话,我们可以发现多线程优先级或多或少借鉴了操作系统对进程的管理。
-
-
-## 7 Java多线程分类
-
-### 用户线程
-
-运行在前台,执行具体的任务,如程序的主线程、连接网络的子线程等都是用户线程
-
-### 守护线程
-
-运行在后台,为其他前台线程服务.也可以说守护线程是JVM中非守护线程的 **“佣人”**。
-
-
-- **特点:** 一旦所有用户线程都结束运行,守护线程会随JVM一起结束工作
-- **应用:** 数据库连接池中的检测线程,JVM虚拟机启动后的检测线程
-- **最常见的守护线程:** 垃圾回收线程
-
-
-**如何设置守护线程?**
-
-可以通过调用 Thead 类的 `setDaemon(true)` 方法设置当前的线程为守护线程。
-
-注意事项:
-
- 1. setDaemon(true)必须在start()方法前执行,否则会抛出IllegalThreadStateException异常
- 2. 在守护线程中产生的新线程也是守护线程
- 3. 不是所有的任务都可以分配给守护线程来执行,比如读写操作或者计算逻辑
-
-
-## 8 sleep()方法和wait()方法简单对比
-
-- 两者最主要的区别在于:**sleep方法没有释放锁,而wait方法释放了锁** 。
-- 两者都可以暂停线程的执行。
-- Wait通常被用于线程间交互/通信,sleep通常被用于暂停执行。
-- wait()方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的notify()或者notifyAll()方法。sleep()方法执行完成后,线程会自动苏醒。
-
-
-## 9 为什么我们调用start()方法时会执行run()方法,为什么我们不能直接调用run()方法?
-
-这是另一个非常经典的java多线程面试问题,而且在面试中会经常被问到。很简单,但是很多人都会答不上来!
-
-new一个Thread,线程进入了新建状态;调用start()方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。
-start()会执行线程的相应准备工作,然后自动执行run()方法的内容,这是真正的多线程工作。 而直接执行run()方法,会把run方法当成一个main线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。
-
-**总结: 调用start方法方可启动线程并使线程进入就绪状态,而run方法只是thread的一个普通方法调用,还是在主线程里执行。**
-
-
-
-
diff --git a/docs/essential-content-for-interview/PreparingForInterview/JavaInterviewLibrary.md b/docs/essential-content-for-interview/PreparingForInterview/JavaInterviewLibrary.md
deleted file mode 100644
index 835b6a54fa0..00000000000
--- a/docs/essential-content-for-interview/PreparingForInterview/JavaInterviewLibrary.md
+++ /dev/null
@@ -1,89 +0,0 @@
-昨天我整理了公众号历史所有和面试相关的我觉得还不错的文章:[整理了一些有助于你拿Offer的文章]() 。今天分享一下最近逛Github看到了一些我觉得对于Java面试以及学习有帮助的仓库,这些仓库涉及Java核心知识点整理、Java常见面试题、算法、基础知识点比如网络和操作系统等等。
-
-## 知识点相关
-
-### 1.JavaGuide
-
-- Github地址: [https://github.com/Snailclimb/JavaGuide](https://github.com/Snailclimb/JavaGuide)
-- star: 64.0k
-- 介绍: 【Java学习+面试指南】 一份涵盖大部分Java程序员所需要掌握的核心知识。
-
-### 2.CS-Notes
-
-- Github 地址:
-- Star: 68.3k
-- 介绍: 技术面试必备基础知识、Leetcode 题解、后端面试、Java 面试、春招、秋招、操作系统、计算机网络、系统设计。
-
-### 3. advanced-java
-
-- Github地址:[https://github.com/doocs/advanced-java](https://github.com/doocs/advanced-java)
-- star: 23.4k
-- 介绍: 互联网 Java 工程师进阶知识完全扫盲:涵盖高并发、分布式、高可用、微服务等领域知识,后端同学必看,前端同学也可学习。
-
-### 4.JCSprout
-
-- Github地址:[https://github.com/crossoverJie/JCSprout](https://github.com/crossoverJie/JCSprout)
-- star: 21.2k
-- 介绍: Java Core Sprout:处于萌芽阶段的 Java 核心知识库。
-
-### 5.toBeTopJavaer
-
-- Github地址:[https://github.com/hollischuang/toBeTopJavaer](https://github.com/hollischuang/toBeTopJavaer)
-- star: 4.0 k
-- 介绍: Java工程师成神之路。
-
-### 6.architect-awesome
-
-- Github地址:[https://github.com/xingshaocheng/architect-awesome](https://github.com/xingshaocheng/architect-awesome)
-- star: 34.4 k
-- 介绍:后端架构师技术图谱。
-
-### 7.technology-talk
-
-- Github地址: [https://github.com/aalansehaiyang/technology-talk](https://github.com/aalansehaiyang/technology-talk)
-- star: 6.1k
-- 介绍: 汇总java生态圈常用技术框架、开源中间件,系统架构、项目管理、经典架构案例、数据库、常用三方库、线上运维等知识。
-
-### 8.fullstack-tutorial
-
-- Github地址: [https://github.com/frank-lam/fullstack-tutorial](https://github.com/frank-lam/fullstack-tutorial)
-- star: 4.0k
-- 介绍: fullstack tutorial 2019,后台技术栈/架构师之路/全栈开发社区,春招/秋招/校招/面试。
-
-### 9.3y
-
-- Github地址:[https://github.com/ZhongFuCheng3y/3y](https://github.com/ZhongFuCheng3y/3y)
-- star: 1.9 k
-- 介绍: Java 知识整合。
-
-### 10.java-bible
-
-- Github地址:[https://github.com/biezhi/java-bible](https://github.com/biezhi/java-bible)
-- star: 2.3k
-- 介绍: 这里记录了一些技术摘要,部分文章来自网络,本项目的目的力求分享精品技术干货,以Java为主。
-
-### 11.interviews
-
-- Github地址: [https://github.com/kdn251/interviews/blob/master/README-zh-cn.md](https://github.com/kdn251/interviews/blob/master/README-zh-cn.md)
-- star: 35.3k
-- 介绍: 软件工程技术面试个人指南(国外的一个项目,虽然有翻译版,但是不太推荐,因为很多内容并不适用于国内)。
-
-## 算法相关
-
-### 1.LeetCodeAnimation
-
-- Github 地址:
-- Star: 33.4k
-- 介绍: Demonstrate all the questions on LeetCode in the form of animation.(用动画的形式呈现解LeetCode题目的思路)。
-
-### 2.awesome-java-leetcode
-
-- Github地址:[https://github.com/Blankj/awesome-java-leetcode](https://github.com/Blankj/awesome-java-leetcode)
-- star: 6.1k
-- 介绍: LeetCode 上 Facebook 的面试题目。
-
-### 3.leetcode
-
-- Github地址:[https://github.com/azl397985856/leetcode](https://github.com/azl397985856/leetcode)
-- star: 12.0k
-- 介绍: LeetCode Solutions: A Record of My Problem Solving Journey.( leetcode题解,记录自己的leetcode解题之路。)
\ No newline at end of file
diff --git a/docs/essential-content-for-interview/PreparingForInterview/JavaProgrammerNeedKnow.md b/docs/essential-content-for-interview/PreparingForInterview/JavaProgrammerNeedKnow.md
deleted file mode 100644
index d515693722e..00000000000
--- a/docs/essential-content-for-interview/PreparingForInterview/JavaProgrammerNeedKnow.md
+++ /dev/null
@@ -1,81 +0,0 @@
- 身边的朋友或者公众号的粉丝很多人都向我询问过:“我是双非/三本/专科学校的,我有机会进入大厂吗?”、“非计算机专业的学生能学好吗?”、“如何学习Java?”、“Java学习该学哪些东西?”、“我该如何准备Java面试?”......这些方面的问题。我会根据自己的一点经验对大部分人关心的这些问题进行答疑解惑。现在又刚好赶上考研结束,这篇文章也算是给考研结束准备往Java后端方向发展的朋友们指明一条学习之路。道理懂了如果没有实际行动,那这篇文章对你或许没有任何意义。
-
-### Question1:我是双非/三本/专科学校的,我有机会进入大厂吗?
-
- 我自己也是非985非211学校的,结合自己的经历以及一些朋友的经历,我觉得让我回答这个问题再好不过。
-
- 首先,我觉得学校歧视很正常,真的太正常了,如果要抱怨的话,你只能抱怨自己没有进入名校。但是,千万不要动不动说自己学校差,动不动拿自己学校当做自己进不了大厂的借口,学历只是筛选简历的很多标准中的一个而已,如果你够优秀,简历够丰富,你也一样可以和名校同学一起同台竞争。
-
- 企业HR肯定是更喜欢高学历的人,毕竟985、211优秀人才比例肯定比普通学校高很多,HR团队肯定会优先在这些学校里选。这就好比相亲,你是愿意在很多优秀的人中选一个优秀的,还是愿意在很多普通的人中选一个优秀的呢?
-
- 双非本科甚至是二本、三本甚至是专科的同学也有很多进入大厂的,不过比率相比于名校的低很多而已。从大厂招聘的结果上看,高学历人才的数量占据大头,那些成功进入BAT、美团,京东,网易等大厂的双非本科甚至是二本、三本甚至是专科的同学往往是因为具备丰富的项目经历或者在某个含金量比较高的竞赛比如ACM中取得了不错的成绩。**一部分学历不突出但能力出众的面试者能够进入大厂并不是说明学历不重要,而是学历的软肋能够通过其他的优势来弥补。** 所以,如果你的学校不够好而你自己又想去大厂的话,建议你可以从这几点来做:**①尽量在面试前最好有一个可以拿的出手的项目;②有实习条件的话,尽早出去实习,实习经历也会是你的简历的一个亮点(有能力在大厂实习最佳!);③参加一些含金量比较高的比赛,拿不拿得到名次没关系,重在锻炼。**
-
-
-### Question2:非计算机专业的学生能学好Java后台吗?我能进大厂吗?
-
- 当然可以!现在非科班的程序员很多,很大一部分原因是互联网行业的工资比较高。我们学校外面的培训班里面90%都是非科班,我觉得他们很多人学的都还不错。另外,我的一个朋友本科是机械专业,大一开始自学安卓,技术贼溜,在我看来他比大部分本科是计算机的同学学的还要好。参考Question1的回答,即使你是非科班程序员,如果你想进入大厂的话,你也可以通过自己的其他优势来弥补。
-
- 我觉得我们不应该因为自己的专业给自己划界限或者贴标签,说实话,很多科班的同学可能并不如你,你以为科班的同学就会认真听讲吗?还不是几乎全靠自己课下自学!不过如果你是非科班的话,你想要学好,那么注定就要舍弃自己本专业的一些学习时间,这是无可厚非的。
-
- 建议非科班的同学,首先要打好计算机基础知识基础:①计算机网络、②操作系统、③数据机构与算法,我个人觉得这3个对你最重要。这些东西就像是内功,对你以后的长远发展非常有用。当然,如果你想要进大厂的话,这些知识也是一定会被问到的。另外,“一定学好数据结构与算法!一定学好数据结构与算法!一定学好数据结构与算法!”,重要的东西说3遍。
-
-
-
-### Question3: 我没有实习经历的话找工作是不是特别艰难?
-
- 没有实习经历没关系,只要你有拿得出手的项目或者大赛经历的话,你依然有可能拿到大厂的 offer 。笔主当时找工作的时候就没有实习经历以及大赛获奖经历,单纯就是凭借自己的项目经验撑起了整个面试。
-
- 如果你既没有实习经历,又没有拿得出手的项目或者大赛经历的话,我觉得在简历关,除非你有其他特别的亮点,不然,你应该就会被刷。
-
-### Question4: 我该如何准备面试呢?面试的注意事项有哪些呢?
-
-下面是我总结的一些准备面试的Tips以及面试必备的注意事项:
-
-1. **准备一份自己的自我介绍,面试的时候根据面试对象适当进行修改**(突出重点,突出自己的优势在哪里,切忌流水账);
-2. **注意随身带上自己的成绩单和简历复印件;** (有的公司在面试前都会让你交一份成绩单和简历当做面试中的参考。)
-3. **如果需要笔试就提前刷一些笔试题,大部分在线笔试的类型是选择题+编程题,有的还会有简答题。**(平时空闲时间多的可以刷一下笔试题目(牛客网上有很多),但是不要只刷面试题,不动手code,程序员不是为了考试而存在的。)另外,注意抓重点,因为题目太多了,但是有很多题目几乎次次遇到,像这样的题目一定要搞定。
-4. **提前准备技术面试。** 搞清楚自己面试中可能涉及哪些知识点、哪些知识点是重点。面试中哪些问题会被经常问到、自己该如何回答。(强烈不推荐背题,第一:通过背这种方式你能记住多少?能记住多久?第二:背题的方式的学习很难坚持下去!)
-5. **面试之前做好定向复习。** 也就是专门针对你要面试的公司来复习。比如你在面试之前可以在网上找找有没有你要面试的公司的面经。
-6. **准备好自己的项目介绍。** 如果有项目的话,技术面试第一步,面试官一般都是让你自己介绍一下你的项目。你可以从下面几个方向来考虑:①对项目整体设计的一个感受(面试官可能会让你画系统的架构图);②在这个项目中你负责了什么、做了什么、担任了什么角色;③ 从这个项目中你学会了那些东西,使用到了那些技术,学会了那些新技术的使用;④项目描述中,最好可以体现自己的综合素质,比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的又或者说你在这个项目用了什么技术实现了什么功能比如:用 redis 做缓存提高访问速度和并发量、使用消息队列削峰和降流等等。
-7. **面试之后记得复盘。** 面试遭遇失败是很正常的事情,所以善于总结自己的失败原因才是最重要的。如果失败,不要灰心;如果通过,切勿狂喜。
-
-
-**一些还算不错的 Java面试/学习相关的仓库,相信对大家准备面试一定有帮助:**[盘点一下Github上开源的Java面试/学习相关的仓库,看完弄懂薪资至少增加10k](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484817&idx=1&sn=12f0c254a240c40c2ccab8314653216b&chksm=fd9853f0caefdae6d191e6bf085d44ab9c73f165e3323aa0362d830e420ccbfad93aa5901021&token=766994974&lang=zh_CN#rd)
-
-### Question5: 我该自学还是报培训班呢?
-
- 我本人更加赞同自学(你要知道去了公司可没人手把手教你了,而且几乎所有的公司都对培训班出生的有偏见。为什么有偏见,你学个东西还要去培训班,说明什么,同等水平下,你的自学能力以及自律能力一定是比不上自学的人的)。但是如果,你连每天在寝室坚持学上8个小时以上都坚持不了,或者总是容易半途而废的话,我还是推荐你去培训班。观望身边同学去培训班的,大多是非计算机专业或者是没有自律能力以及自学能力非常差的人。
-
- 另外,如果自律能力不行,你也可以通过结伴学习、参加老师的项目等方式来督促自己学习。
-
- 总结:去不去培训班主要还是看自己,如果自己能坚持自学就自学,坚持不下来就去培训班。
-
-### Question6: 没有项目经历/博客/Github开源项目怎么办?
-
- 从现在开始做!
-
- 网上有很多非常不错的项目视频,你就跟着一步一步做,不光要做,还要改进,改善。另外,如果你的老师有相关 Java 后台项目的话,你也可以主动申请参与进来。
-
- 如果有自己的博客,也算是简历上的一个亮点。建议可以在掘金、Segmentfault、CSDN等技术交流社区写博客,当然,你也可以自己搭建一个博客(采用 Hexo+Githu Pages 搭建非常简单)。写一些什么?学习笔记、实战内容、读书笔记等等都可以。
-
- 多用 Github,用好 Github,上传自己不错的项目,写好 readme 文档,在其他技术社区做好宣传。相信你也会收获一个不错的开源项目!
-
-
-### Question7: 大厂到底青睐什么样的应届生?
-
- 从阿里、腾讯等大厂招聘官网对于Java后端方向/后端方向的应届实习生的要求,我们大概可以总结归纳出下面这 4 点能给简历增加很多分数:
-
-- 参加过竞赛(含金量超高的是ACM);
-- 对数据结构与算法非常熟练;
-- 参与过实际项目(比如学校网站);
-- 参与过某个知名的开源项目或者自己的某个开源项目很不错;
-
- 除了我上面说的这三点,在面试Java工程师的时候,下面几点也提升你的个人竞争力:
-
-- 熟悉Python、Shell、Perl等脚本语言;
-- 熟悉 Java 优化,JVM调优;
-- 熟悉 SOA 模式;
-- 熟悉自己所用框架的底层知识比如Spring;
-- 了解分布式一些常见的理论;
-- 具备高并发开发经验;大数据开发经验等等。
-
diff --git a/docs/essential-content-for-interview/PreparingForInterview/interviewPrepare.md b/docs/essential-content-for-interview/PreparingForInterview/interviewPrepare.md
deleted file mode 100644
index 1ae36a35734..00000000000
--- a/docs/essential-content-for-interview/PreparingForInterview/interviewPrepare.md
+++ /dev/null
@@ -1,88 +0,0 @@
-不论是校招还是社招都避免不了各种面试、笔试,如何去准备这些东西就显得格外重要。不论是笔试还是面试都是有章可循的,我这个“有章可循”说的意思只是说应对技术面试是可以提前准备。 我其实特别不喜欢那种临近考试就提前背啊记啊各种题的行为,非常反对!我觉得这种方法特别极端,而且在稍有一点经验的面试官面前是根本没有用的。建议大家还是一步一个脚印踏踏实实地走。
-
-
-
-- [1 如何获取大厂面试机会?](#1-如何获取大厂面试机会)
-- [2 面试前的准备](#2--面试前的准备)
- - [2.1 准备自己的自我介绍](#21-准备自己的自我介绍)
- - [2.2 关于着装](#22-关于着装)
- - [2.3 随身带上自己的成绩单和简历](#23-随身带上自己的成绩单和简历)
- - [2.4 如果需要笔试就提前刷一些笔试题](#24-如果需要笔试就提前刷一些笔试题)
- - [2.5 花时间一些逻辑题](#25-花时间一些逻辑题)
- - [2.6 准备好自己的项目介绍](#26-准备好自己的项目介绍)
- - [2.7 提前准备技术面试](#27-提前准备技术面试)
- - [2.7 面试之前做好定向复习](#27-面试之前做好定向复习)
-- [3 面试之后复盘](#3-面试之后复盘)
-
-
-
-## 1 如何获取大厂面试机会?
-
-**在讲如何获取大厂面试机会之前,先来给大家科普/对比一下两个校招非常常见的概念——春招和秋招。**
-
-1. **招聘人数** :秋招多于春招 ;
-2. **招聘时间** : 秋招一般7月左右开始,大概一直持续到10月底。但是大厂(如BAT)都会早开始早结束,所以一定要把握好时间。 春招最佳时间为3月,次佳时间为4月,进入5月基本就不会再有春招了(金三银四)。
-3. **应聘难度** :秋招略大于春招;
-4. **招聘公司:** 秋招数量多,而春招数量较少,一般为秋招的补充。
-
-**综上,一般来说,秋招的含金量明显是高于春招的。**
-
-**下面我就说一下我自己知道的一些方法,不过应该也涵盖了大部分获取面试机会的方法。**
-
-1. **关注大厂官网,随时投递简历(走流程的网申);**
-2. **线下参加宣讲会,直接投递简历;**
-3. **找到师兄师姐/认识的人,帮忙内推(能够让你避开网申简历筛选,笔试筛选,还是挺不错的,不过也还是需要你的简历够棒);**
-4. **博客发文被看中/Github优秀开源项目作者,大厂内部人员邀请你面试;**
-5. **求职类网站投递简历(不是太推荐,适合海投);**
-
-
-除了这些方法,我也遇到过这样的经历:有些大公司的一些部门可能暂时没招够人,然后如果你的亲戚或者朋友刚好在这个公司,而你正好又在寻求offer,那么面试机会基本上是有了,而且这种面试的难度好像一般还普遍比其他正规面试低很多。
-
-## 2 面试前的准备
-
-### 2.1 准备自己的自我介绍
-
-从HR面、技术面到高管面/部门主管面,面试官一般会让你先自我介绍一下,所以好好准备自己的自我介绍真的非常重要。网上一般建议的是准备好两份自我介绍:一份对hr说的,主要讲能突出自己的经历,会的编程技术一语带过;另一份对技术面试官说的,主要讲自己会的技术细节,项目经验,经历那些就一语带过。
-
-我这里简单分享一下我自己的自我介绍的一个简单的模板吧:
-
-> 面试官,您好!我叫某某。大学时间我主要利用课外时间学习某某。在校期间参与过一个某某系统的开发,另外,自己学习过程中也写过很多系统比如某某系统。在学习之余,我比较喜欢通过博客整理分享自己所学知识。我现在是某某社区的认证作者,写过某某很不错的文章。另外,我获得过某某奖,我的Github上开源的某个项目已经有多少Star了。
-
-### 2.2 关于着装
-
-穿西装、打领带、小皮鞋?NO!NO!NO!这是互联网公司面试又不是去走红毯,所以你只需要穿的简单大方就好,不需要太正式。
-
-### 2.3 随身带上自己的成绩单和简历
-
-有的公司在面试前都会让你交一份成绩单和简历当做面试中的参考。
-
-### 2.4 如果需要笔试就提前刷一些笔试题
-
-平时空闲时间多的可以刷一下笔试题目(牛客网上有很多)。但是不要只刷面试题,不动手code,程序员不是为了考试而存在的。
-
-### 2.5 花时间一些逻辑题
-
-面试中发现有些公司都有逻辑题测试环节,并且都把逻辑笔试成绩作为很重要的一个参考。
-
-### 2.6 准备好自己的项目介绍
-
-如果有项目的话,技术面试第一步,面试官一般都是让你自己介绍一下你的项目。你可以从下面几个方向来考虑:
-
-1. 对项目整体设计的一个感受(面试官可能会让你画系统的架构图)
-2. 在这个项目中你负责了什么、做了什么、担任了什么角色
-3. 从这个项目中你学会了那些东西,使用到了那些技术,学会了那些新技术的使用
-4. 另外项目描述中,最好可以体现自己的综合素质,比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的又或者说你在这个项目用了什么技术实现了什么功能比如:用redis做缓存提高访问速度和并发量、使用消息队列削峰和降流等等。
-
-### 2.7 提前准备技术面试
-
-搞清楚自己面试中可能涉及哪些知识点、哪些知识点是重点。面试中哪些问题会被经常问到、自己该如何回答。(强烈不推荐背题,第一:通过背这种方式你能记住多少?能记住多久?第二:背题的方式的学习很难坚持下去!)
-
-### 2.7 面试之前做好定向复习
-
-所谓定向复习就是专门针对你要面试的公司来复习。比如你在面试之前可以在网上找找有没有你要面试的公司的面经。
-
-举个栗子:在我面试 ThoughtWorks 的前几天我就在网上找了一些关于 ThoughtWorks 的技术面的一些文章。然后知道了 ThoughtWorks 的技术面会让我们在之前做的作业的基础上增加一个或两个功能,所以我提前一天就把我之前做的程序重新重构了一下。然后在技术面的时候,简单的改了几行代码之后写个测试就完事了。如果没有提前准备,我觉得 20 分钟我很大几率会完不成这项任务。
-
-## 3 面试之后复盘
-
-如果失败,不要灰心;如果通过,切勿狂喜。面试和工作实际上是两回事,可能很多面试未通过的人,工作能力比你强的多,反之亦然。我个人觉得面试也像是一场全新的征程,失败和胜利都是平常之事。所以,劝各位不要因为面试失败而灰心、丧失斗志。也不要因为面试通过而沾沾自喜,等待你的将是更美好的未来,继续加油!
diff --git "a/docs/essential-content-for-interview/PreparingForInterview/\347\250\213\345\272\217\345\221\230\347\232\204\347\256\200\345\216\206\344\271\213\351\201\223.md" "b/docs/essential-content-for-interview/PreparingForInterview/\347\250\213\345\272\217\345\221\230\347\232\204\347\256\200\345\216\206\344\271\213\351\201\223.md"
deleted file mode 100644
index 7feead7d8bd..00000000000
--- "a/docs/essential-content-for-interview/PreparingForInterview/\347\250\213\345\272\217\345\221\230\347\232\204\347\256\200\345\216\206\344\271\213\351\201\223.md"
+++ /dev/null
@@ -1,121 +0,0 @@
-
-
-- [程序员简历就该这样写](#程序员简历就该这样写)
- - [为什么说简历很重要?](#为什么说简历很重要)
- - [先从面试前来说](#先从面试前来说)
- - [再从面试中来说](#再从面试中来说)
- - [下面这几点你必须知道](#下面这几点你必须知道)
- - [必须了解的两大法则](#必须了解的两大法则)
- - [STAR法则(Situation Task Action Result)](#star法则situation-task-action-result)
- - [FAB 法则(Feature Advantage Benefit)](#fab-法则feature-advantage-benefit)
- - [项目经历怎么写?](#项目经历怎么写)
- - [专业技能该怎么写?](#专业技能该怎么写)
- - [排版注意事项](#排版注意事项)
- - [其他的一些小tips](#其他的一些小tips)
- - [推荐的工具/网站](#推荐的工具网站)
-
-
-
-# 程序员简历就该这样写
-
-本篇文章除了教大家用Markdown如何写一份程序员专属的简历,后面还会给大家推荐一些不错的用来写Markdown简历的软件或者网站,以及如何优雅的将Markdown格式转变为PDF格式或者其他格式。
-
-推荐大家使用Markdown语法写简历,然后再将Markdown格式转换为PDF格式后进行简历投递。
-
-如果你对Markdown语法不太了解的话,可以花半个小时简单看一下Markdown语法说明: http://www.markdown.cn 。
-
-## 为什么说简历很重要?
-
-一份好的简历可以在整个申请面试以及面试过程中起到非常好的作用。 在不夸大自己能力的情况下,写出一份好的简历也是一项很棒的能力。为什么说简历很重要呢?
-
-### 先从面试前来说
-
-- 假如你是网申,你的简历必然会经过HR的筛选,一张简历HR可能也就花费10秒钟看一下,然后HR就会决定你这一关是Fail还是Pass。
-- 假如你是内推,如果你的简历没有什么优势的话,就算是内推你的人再用心,也无能为力。
-
-另外,就算你通过了筛选,后面的面试中,面试官也会根据你的简历来判断你究竟是否值得他花费很多时间去面试。
-
-所以,简历就像是我们的一个门面一样,它在很大程度上决定了你能否进入到下一轮的面试中。
-
-### 再从面试中来说
-
-我发现大家比较喜欢看面经 ,这点无可厚非,但是大部分面经都没告诉你很多问题都是在特定条件下才问的。举个简单的例子:一般情况下你的简历上注明你会的东西才会被问到(Java、数据结构、网络、算法这些基础是每个人必问的),比如写了你会 redis,那面试官就很大概率会问你 redis 的一些问题。比如:redis的常见数据类型及应用场景、redis是单线程为什么还这么快、 redis 和 memcached 的区别、redis 内存淘汰机制等等。
-
-所以,首先,你要明确的一点是:**你不会的东西就不要写在简历上**。另外,**你要考虑你该如何才能让你的亮点在简历中凸显出来**,比如:你在某某项目做了什么事情解决了什么问题(只要有项目就一定有要解决的问题)、你的某一个项目里使用了什么技术后整体性能和并发量提升了很多等等。
-
-面试和工作是两回事,聪明的人会把面试官往自己擅长的领域领,其他人则被面试官牵着鼻子走。虽说面试和工作是两回事,但是你要想要获得自己满意的 offer ,你自身的实力必须要强。
-
-## 下面这几点你必须知道
-
-1. 大部分公司的HR都说我们不看重学历(骗你的!),但是如果你的学校不出众的话,很难在一堆简历中脱颖而出,除非你的简历上有特别的亮点,比如:某某大厂的实习经历、获得了某某大赛的奖等等。
-2. **大部分应届生找工作的硬伤是没有工作经验或实习经历,所以如果你是应届生就不要错过秋招和春招。一旦错过,你后面就极大可能会面临社招,这个时候没有工作经验的你可能就会面临各种碰壁,导致找不到一个好的工作**
-3. **写在简历上的东西一定要慎重,这是面试官大量提问的地方;**
-4. **将自己的项目经历完美的展示出来非常重要。**
-
-## 必须了解的两大法则
-
-### STAR法则(Situation Task Action Result)
-
-- **Situation:** 事情是在什么情况下发生;
-- **Task::** 你是如何明确你的任务的;
-- **Action:** 针对这样的情况分析,你采用了什么行动方式;
-- **Result:** 结果怎样,在这样的情况下你学习到了什么。
-
-简而言之,STAR法则,就是一种讲述自己故事的方式,或者说,是一个清晰、条理的作文模板。不管是什么,合理熟练运用此法则,可以轻松的对面试官描述事物的逻辑方式,表现出自己分析阐述问题的清晰性、条理性和逻辑性。
-
-### FAB 法则(Feature Advantage Benefit)
-
-- **Feature:** 是什么;
-- **Advantage:** 比别人好在哪些地方;
-- **Benefit:** 如果雇佣你,招聘方会得到什么好处。
-
-简单来说,这个法则主要是让你的面试官知道你的优势、招了你之后对公司有什么帮助。
-
-## 项目经历怎么写?
-
-简历上有一两个项目经历很正常,但是真正能把项目经历很好的展示给面试官的非常少。对于项目经历大家可以考虑从如下几点来写:
-
-1. 对项目整体设计的一个感受
-2. 在这个项目中你负责了什么、做了什么、担任了什么角色
-3. 从这个项目中你学会了那些东西,使用到了那些技术,学会了那些新技术的使用
-4. 另外项目描述中,最好可以体现自己的综合素质,比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的又或者说你在这个项目用了什么技术实现了什么功能比如:用redis做缓存提高访问速度和并发量、使用消息队列削峰和降流等等。
-
-## 专业技能该怎么写?
-
-先问一下你自己会什么,然后看看你意向的公司需要什么。一般HR可能并不太懂技术,所以他在筛选简历的时候可能就盯着你专业技能的关键词来看。对于公司有要求而你不会的技能,你可以花几天时间学习一下,然后在简历上可以写上自己了解这个技能。比如你可以这样写(下面这部分内容摘自我的简历,大家可以根据自己的情况做一些修改和完善):
-
-- 计算机网络、数据结构、算法、操作系统等课内基础知识:掌握
-- Java 基础知识:掌握
-- JVM 虚拟机(Java内存区域、虚拟机垃圾算法、虚拟垃圾收集器、JVM内存管理):掌握
-- 高并发、高可用、高性能系统开发:掌握
-- Struts2、Spring、Hibernate、Ajax、Mybatis、JQuery :掌握
-- SSH 整合、SSM 整合、 SOA 架构:掌握
-- Dubbo: 掌握
-- Zookeeper: 掌握
-- 常见消息队列: 掌握
-- Linux:掌握
-- MySQL常见优化手段:掌握
-- Spring Boot +Spring Cloud +Docker:了解
-- Hadoop 生态相关技术中的 HDFS、Storm、MapReduce、Hive、Hbase :了解
-- Python 基础、一些常见第三方库比如OpenCV、wxpy、wordcloud、matplotlib:熟悉
-
-## 排版注意事项
-
-1. 尽量简洁,不要太花里胡哨;
-2. 一些技术名词不要弄错了大小写比如MySQL不要写成mysql,Java不要写成java。这个在我看来还是比较忌讳的,所以一定要注意这个细节;
-3. 中文和数字英文之间加上空格的话看起来会舒服一点;
-
-## 其他的一些小tips
-
-1. 尽量避免主观表述,少一点语义模糊的形容词,尽量要简洁明了,逻辑结构清晰。
-2. 如果自己有博客或者个人技术栈点的话,写上去会为你加分很多。
-3. 如果自己的Github比较活跃的话,写上去也会为你加分很多。
-4. 注意简历真实性,一定不要写自己不会的东西,或者带有欺骗性的内容
-5. 项目经历建议以时间倒序排序,另外项目经历不在于多,而在于有亮点。
-6. 如果内容过多的话,不需要非把内容压缩到一页,保持排版干净整洁就可以了。
-7. 简历最后最好能加上:“感谢您花时间阅读我的简历,期待能有机会和您共事。”这句话,显的你会很有礼貌。
-
-## 推荐的工具/网站
-
-- 冷熊简历(MarkDown在线简历工具,可在线预览、编辑和生成PDF):
-- Typora+[Java程序员简历模板](https://github.com/geekcompany/ResumeSample/blob/master/java.md)
diff --git "a/docs/essential-content-for-interview/PreparingForInterview/\347\276\216\345\233\242\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230\346\200\273\347\273\223.md" "b/docs/essential-content-for-interview/PreparingForInterview/\347\276\216\345\233\242\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230\346\200\273\347\273\223.md"
deleted file mode 100644
index 10914cce8e8..00000000000
--- "a/docs/essential-content-for-interview/PreparingForInterview/\347\276\216\345\233\242\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230\346\200\273\347\273\223.md"
+++ /dev/null
@@ -1,947 +0,0 @@
-
-
-- [一 基础篇](#一-基础篇)
- - [1. `System.out.println(3|9)`输出什么?](#1-systemoutprintln39输出什么)
- - [2. 说一下转发\(Forward\)和重定向\(Redirect\)的区别](#2-说一下转发forward和重定向redirect的区别)
- - [3. 在浏览器中输入url地址到显示主页的过程,整个过程会使用哪些协议](#3-在浏览器中输入url地址到显示主页的过程整个过程会使用哪些协议)
- - [4. TCP 三次握手和四次挥手](#4-tcp-三次握手和四次挥手)
- - [为什么要三次握手](#为什么要三次握手)
- - [为什么要传回 SYN](#为什么要传回-syn)
- - [传了 SYN,为啥还要传 ACK](#传了-syn为啥还要传-ack)
- - [为什么要四次挥手](#为什么要四次挥手)
- - [5. IP地址与MAC地址的区别](#5-ip地址与mac地址的区别)
- - [6. HTTP请求,响应报文格式](#6-http请求响应报文格式)
- - [7. 为什么要使用索引?索引这么多优点,为什么不对表中的每一个列创建一个索引呢?索引是如何提高查询速度的?说一下使用索引的注意事项?Mysql索引主要使用的两种数据结构?什么是覆盖索引?](#7-为什么要使用索引索引这么多优点为什么不对表中的每一个列创建一个索引呢索引是如何提高查询速度的说一下使用索引的注意事项mysql索引主要使用的两种数据结构什么是覆盖索引)
- - [8. 进程与线程的区别是什么?进程间的几种通信方式说一下?线程间的几种通信方式知道不?](#8-进程与线程的区别是什么进程间的几种通信方式说一下线程间的几种通信方式知道不)
- - [9. 为什么要用单例模式?手写几种线程安全的单例模式?](#9-为什么要用单例模式手写几种线程安全的单例模式)
- - [10. 简单介绍一下bean;知道Spring的bean的作用域与生命周期吗?](#10-简单介绍一下bean知道spring的bean的作用域与生命周期吗)
- - [11. Spring 中的事务传播行为了解吗?TransactionDefinition 接口中哪五个表示隔离级别的常量?](#11-spring-中的事务传播行为了解吗transactiondefinition-接口中哪五个表示隔离级别的常量)
- - [事务传播行为](#事务传播行为)
- - [隔离级别](#隔离级别)
- - [12. SpringMVC 原理了解吗?](#12-springmvc-原理了解吗)
- - [13. Spring AOP IOC 实现原理](#13-spring-aop-ioc-实现原理)
-- [二 进阶篇](#二-进阶篇)
- - [1 消息队列MQ的套路](#1-消息队列mq的套路)
- - [1.1 介绍一下消息队列MQ的应用场景/使用消息队列的好处](#11-介绍一下消息队列mq的应用场景使用消息队列的好处)
- - [1)通过异步处理提高系统性能](#1通过异步处理提高系统性能)
- - [2)降低系统耦合性](#2降低系统耦合性)
- - [1.2 那么使用消息队列会带来什么问题?考虑过这些问题吗?](#12-那么使用消息队列会带来什么问题考虑过这些问题吗)
- - [1.3 介绍一下你知道哪几种消息队列,该如何选择呢?](#13-介绍一下你知道哪几种消息队列该如何选择呢)
- - [1.4 关于消息队列其他一些常见的问题展望](#14-关于消息队列其他一些常见的问题展望)
- - [2 谈谈 InnoDB 和 MyIsam 两者的区别](#2-谈谈-innodb-和-myisam-两者的区别)
- - [2.1 两者的对比](#21-两者的对比)
- - [2.2 关于两者的总结](#22-关于两者的总结)
- - [3 聊聊 Java 中的集合吧!](#3-聊聊-java-中的集合吧)
- - [3.1 ArrayList 与 LinkedList 有什么不同?\(注意加上从数据结构分析的内容\)](#31-arraylist-与-linkedlist-有什么不同注意加上从数据结构分析的内容)
- - [3.2 HashMap的底层实现](#32-hashmap的底层实现)
- - [1)JDK1.8之前](#1jdk18之前)
- - [2)JDK1.8之后](#2jdk18之后)
- - [3.3 既然谈到了红黑树,你给我手绘一个出来吧,然后简单讲一下自己对于红黑树的理解](#33-既然谈到了红黑树你给我手绘一个出来吧然后简单讲一下自己对于红黑树的理解)
- - [3.4 红黑树这么优秀,为何不直接使用红黑树得了?](#34-红黑树这么优秀为何不直接使用红黑树得了)
- - [3.5 HashMap 和 Hashtable 的区别/HashSet 和 HashMap 区别](#35-hashmap-和-hashtable-的区别hashset-和-hashmap-区别)
-- [三 终结篇](#三-终结篇)
- - [1. Object类有哪些方法?](#1-object类有哪些方法)
- - [1.1 Object类的常见方法总结](#11-object类的常见方法总结)
- - [1.2 hashCode与equals](#12-hashcode与equals)
- - [1.2.1 hashCode\(\)介绍](#121-hashcode介绍)
- - [1.2.2 为什么要有hashCode](#122-为什么要有hashcode)
- - [1.2.3 hashCode\(\)与equals\(\)的相关规定](#123-hashcode与equals的相关规定)
- - [1.2.4 为什么两个对象有相同的hashcode值,它们也不一定是相等的?](#124-为什么两个对象有相同的hashcode值它们也不一定是相等的)
- - [1.3 ==与equals](#13-与equals)
- - [2 ConcurrentHashMap 相关问题](#2-concurrenthashmap-相关问题)
- - [2.1 ConcurrentHashMap 和 Hashtable 的区别](#21-concurrenthashmap-和-hashtable-的区别)
- - [2.2 ConcurrentHashMap线程安全的具体实现方式/底层具体实现](#22-concurrenthashmap线程安全的具体实现方式底层具体实现)
- - [JDK1.7\(上面有示意图\)](#jdk17上面有示意图)
- - [JDK1.8\(上面有示意图\)](#jdk18上面有示意图)
- - [3 谈谈 synchronized 和 ReenTrantLock 的区别](#3-谈谈-synchronized-和-reentrantlock-的区别)
- - [4 线程池了解吗?](#4-线程池了解吗)
- - [4.1 为什么要用线程池?](#41-为什么要用线程池)
- - [4.2 Java 提供了哪几种线程池?他们各自的使用场景是什么?](#42-java-提供了哪几种线程池他们各自的使用场景是什么)
- - [Java 主要提供了下面4种线程池](#java-主要提供了下面4种线程池)
- - [各种线程池的适用场景介绍](#各种线程池的适用场景介绍)
- - [4.3 创建的线程池的方式](#43-创建的线程池的方式)
- - [5 Nginx](#5-nginx)
- - [5.1 简单介绍一下Nginx](#51-简单介绍一下nginx)
- - [反向代理](#反向代理)
- - [负载均衡](#负载均衡)
- - [动静分离](#动静分离)
- - [5.2 为什么要用 Nginx?](#52-为什么要用-nginx)
- - [5.3 Nginx 的四个主要组成部分了解吗?](#53-nginx-的四个主要组成部分了解吗)
-
-
-
-
-这些问题是2018年去美团面试的同学被问到的一些常见的问题,希望对你有帮助!
-
-# 一 基础篇
-
-
-## 1. `System.out.println(3|9)`输出什么?
-
-正确答案:11。
-
-**考察知识点:&和&&;|和||**
-
-**&和&&:**
-
-共同点:两者都可做逻辑运算符。它们都表示运算符的两边都是true时,结果为true;
-
-不同点: &也是位运算符。& 表示在运算时两边都会计算,然后再判断;&&表示先运算符号左边的东西,然后判断是否为true,是true就继续运算右边的然后判断并输出,是false就停下来直接输出不会再运行后面的东西。
-
-**|和||:**
-
-共同点:两者都可做逻辑运算符。它们都表示运算符的两边任意一边为true,结果为true,两边都不是true,结果就为false;
-
-不同点:|也是位运算符。| 表示两边都会运算,然后再判断结果;|| 表示先运算符号左边的东西,然后判断是否为true,是true就停下来直接输出不会再运行后面的东西,是false就继续运算右边的然后判断并输出。
-
-**回到本题:**
-
-3 | 9=0011(二进制) | 1001(二进制)=1011(二进制)=11(十进制)
-
-## 2. 说一下转发(Forward)和重定向(Redirect)的区别
-
-**转发是服务器行为,重定向是客户端行为。**
-
-**转发(Forword)** 通过RequestDispatcher对象的`forward(HttpServletRequest request,HttpServletResponse response)`方法实现的。`RequestDispatcher` 可以通过`HttpServletRequest` 的 `getRequestDispatcher()`方法获得。例如下面的代码就是跳转到 login_success.jsp 页面。
-
-```java
-request.getRequestDispatcher("login_success.jsp").forward(request, response);
-```
-
-**重定向(Redirect)** 是利用服务器返回的状态码来实现的。客户端浏览器请求服务器的时候,服务器会返回一个状态码。服务器通过HttpServletRequestResponse的setStatus(int status)方法设置状态码。如果服务器返回301或者302,则浏览器会到新的网址重新请求该资源。
-
-1. **从地址栏显示来说**:forward是服务器请求资源,服务器直接访问目标地址的URL,把那个URL的响应内容读取过来,然后把这些内容再发给浏览器。浏览器根本不知道服务器发送的内容从哪里来的,所以它的地址栏还是原来的地址。redirect是服务端根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址。所以地址栏显示的是新的URL。
-2. **从数据共享来说**:forward:转发页面和转发到的页面可以共享request里面的数据。redirect:不能共享数据。
-3. **从运用地方来说**:forward:一般用于用户登陆的时候,根据角色转发到相应的模块。redirect:一般用于用户注销登陆时返回主页面和跳转到其它的网站等。
-4. **从效率来说**:forward:高。redirect:低。
-
-
-## 3. 在浏览器中输入url地址到显示主页的过程,整个过程会使用哪些协议
-
-图片来源:《图解HTTP》:
-
-
-
-总体来说分为以下几个过程:
-
-1. DNS解析
-2. TCP连接
-3. 发送HTTP请求
-4. 服务器处理请求并返回HTTP报文
-5. 浏览器解析渲染页面
-6. 连接结束
-
-具体可以参考下面这篇文章:
-
-- [https://segmentfault.com/a/1190000006879700](https://segmentfault.com/a/1190000006879700)
-
-## 4. TCP 三次握手和四次挥手
-
-为了准确无误地把数据送达目标处,TCP协议采用了三次握手策略。
-
-**漫画图解:**
-
-图片来源:《图解HTTP》
-
-
-**简单示意图:**
-
-
-- 客户端–发送带有 SYN 标志的数据包–一次握手–服务端
-- 服务端–发送带有 SYN/ACK 标志的数据包–二次握手–客户端
-- 客户端–发送带有带有 ACK 标志的数据包–三次握手–服务端
-
-#### 为什么要三次握手
-
-**三次握手的目的是建立可靠的通信信道,说到通讯,简单来说就是数据的发送与接收,而三次握手最主要的目的就是双方确认自己与对方的发送与接收是正常的。**
-
-第一次握手:Client 什么都不能确认;Server 确认了对方发送正常,自己接收正常。
-
-第二次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己接收正常,对方发送正常
-
-第三次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己发送、接收正常,对方发送、接收正常
-
-所以三次握手就能确认双发收发功能都正常,缺一不可。
-
-#### 为什么要传回 SYN
-接收端传回发送端所发送的 SYN 是为了告诉发送端,我接收到的信息确实就是你所发送的信号了。
-
-> SYN 是 TCP/IP 建立连接时使用的握手信号。在客户机和服务器之间建立正常的 TCP 网络连接时,客户机首先发出一个 SYN 消息,服务器使用 SYN-ACK 应答表示接收到了这个消息,最后客户机再以 ACK(Acknowledgement[汉译:确认字符 ,在数据通信传输中,接收站发给发送站的一种传输控制字符。它表示确认发来的数据已经接受无误。 ])消息响应。这样在客户机和服务器之间才能建立起可靠的TCP连接,数据才可以在客户机和服务器之间传递。
-
-
-#### 传了 SYN,为啥还要传 ACK
-
-双方通信无误必须是两者互相发送信息都无误。传了 SYN,证明发送方(主动关闭方)到接收方(被动关闭方)的通道没有问题,但是接收方到发送方的通道还需要 ACK 信号来进行验证。
-
-
-
-断开一个 TCP 连接则需要“四次挥手”:
-
-- 客户端-发送一个 FIN,用来关闭客户端到服务器的数据传送
-- 服务器-收到这个 FIN,它发回一 个 ACK,确认序号为收到的序号加1 。和 SYN 一样,一个 FIN 将占用一个序号
-- 服务器-关闭与客户端的连接,发送一个FIN给客户端
-- 客户端-发回 ACK 报文确认,并将确认序号设置为收到序号加1
-
-
-#### 为什么要四次挥手
-
-任何一方都可以在数据传送结束后发出连接释放的通知,待对方确认后进入半关闭状态。当另一方也没有数据再发送的时候,则发出连接释放通知,对方确认后就完全关闭了TCP连接。
-
-举个例子:A 和 B 打电话,通话即将结束后,A 说“我没啥要说的了”,B回答“我知道了”,但是 B 可能还会有要说的话,A 不能要求 B 跟着自己的节奏结束通话,于是 B 可能又巴拉巴拉说了一通,最后 B 说“我说完了”,A 回答“知道了”,这样通话才算结束。
-
-上面讲的比较概括,推荐一篇讲的比较细致的文章:[https://blog.csdn.net/qzcsu/article/details/72861891](https://blog.csdn.net/qzcsu/article/details/72861891)
-
-
-
-## 5. IP地址与MAC地址的区别
-
-参考:[https://blog.csdn.net/guoweimelon/article/details/50858597](https://blog.csdn.net/guoweimelon/article/details/50858597)
-
-IP地址是指互联网协议地址(Internet Protocol Address)IP Address的缩写。IP地址是IP协议提供的一种统一的地址格式,它为互联网上的每一个网络和每一台主机分配一个逻辑地址,以此来屏蔽物理地址的差异。
-
-
-
-MAC 地址又称为物理地址、硬件地址,用来定义网络设备的位置。网卡的物理地址通常是由网卡生产厂家写入网卡的,具有全球唯一性。MAC地址用于在网络中唯一标示一个网卡,一台电脑会有一或多个网卡,每个网卡都需要有一个唯一的MAC地址。
-
-## 6. HTTP请求,响应报文格式
-
-
-
-HTTP请求报文主要由请求行、请求头部、请求正文3部分组成
-
-HTTP响应报文主要由状态行、响应头部、响应正文3部分组成
-
-详细内容可以参考:[https://blog.csdn.net/a19881029/article/details/14002273](https://blog.csdn.net/a19881029/article/details/14002273)
-
-## 7. 为什么要使用索引?索引这么多优点,为什么不对表中的每一个列创建一个索引呢?索引是如何提高查询速度的?说一下使用索引的注意事项?Mysql索引主要使用的两种数据结构?什么是覆盖索引?
-
-**为什么要使用索引?**
-
-1. 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。
-2. 可以大大加快 数据的检索速度(大大减少的检索的数据量), 这也是创建索引的最主要的原因。
-3. 帮助服务器避免排序和临时表
-4. 将随机IO变为顺序IO
-5. 可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。
-
-**索引这么多优点,为什么不对表中的每一个列创建一个索引呢?**
-
-1. 当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。
-2. 索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大。
-3. 创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。
-
-**索引是如何提高查询速度的?**
-
-将无序的数据变成相对有序的数据(就像查目录一样)
-
-**说一下使用索引的注意事项**
-
-1. 避免 where 子句中对字段施加函数,这会造成无法命中索引。
-2. 在使用InnoDB时使用与业务无关的自增主键作为主键,即使用逻辑主键,而不要使用业务主键。
-3. 将打算加索引的列设置为 NOT NULL ,否则将导致引擎放弃使用索引而进行全表扫描
-4. 删除长期未使用的索引,不用的索引的存在会造成不必要的性能损耗 MySQL 5.7 可以通过查询 sys 库的 schema_unused_indexes 视图来查询哪些索引从未被使用
-5. 在使用 limit offset 查询缓慢时,可以借助索引来提高性能
-
-**Mysql索引主要使用的哪两种数据结构?**
-
-- 哈希索引:对于哈希索引来说,底层的数据结构就是哈希表,因此在绝大多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快;其余大部分场景,建议选择BTree索引。
-- BTree索引:Mysql的BTree索引使用的是B树中的B+Tree。但对于主要的两种存储引擎(MyISAM和InnoDB)的实现方式是不同的。
-
-更多关于索引的内容可以查看我的这篇文章:[【思维导图-索引篇】搞定数据库索引就是这么简单](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484486&idx=1&sn=215450f11e042bca8a58eac9f4a97686&chksm=fd985227caefdb3117b8375f150676f5824aa20d1ebfdbcfb93ff06e23e26efbafae6cf6b48e&token=1990180468&lang=zh_CN#rd)
-
-**什么是覆盖索引?**
-
-如果一个索引包含(或者说覆盖)所有需要查询的字段的值,我们就称
-之为“覆盖索引”。我们知道在InnoDB存储引擎中,如果不是主键索引,叶子节点存储的是主键+列值。最终还是要“回表”,也就是要通过主键再查找一次,这样就会比较慢。覆盖索引就是把要查询出的列和索引是对应的,不做回表操作!
-
-
-## 8. 进程与线程的区别是什么?进程间的几种通信方式说一下?线程间的几种通信方式知道不?
- **进程与线程的区别是什么?**
-
-线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。另外,也正是因为共享资源,所以线程中执行时一般都要进行同步和互斥。总的来说,进程和线程的主要差别在于它们是不同的操作系统资源管理方式。
-
-**进程间的几种通信方式说一下?**
-
-
-1. **管道(pipe)**:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有血缘关系的进程间使用。进程的血缘关系通常指父子进程关系。管道分为pipe(无名管道)和fifo(命名管道)两种,有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间通信。
-2. **信号量(semophore)**:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它通常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
-3. **消息队列(message queue)**:消息队列是由消息组成的链表,存放在内核中 并由消息队列标识符标识。消息队列克服了信号传递信息少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。消息队列与管道通信相比,其优势是对每个消息指定特定的消息类型,接收的时候不需要按照队列次序,而是可以根据自定义条件接收特定类型的消息。
-4. **信号(signal)**:信号是一种比较复杂的通信方式,用于通知接收进程某一事件已经发生。
-5. **共享内存(shared memory)**:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问,共享内存是最快的IPC方式,它是针对其他进程间的通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量配合使用,来实现进程间的同步和通信。
-6. **套接字(socket)**:socket,即套接字是一种通信机制,凭借这种机制,客户/服务器(即要进行通信的进程)系统的开发工作既可以在本地单机上进行,也可以跨网络进行。也就是说它可以让不在同一台计算机但通过网络连接计算机上的进程进行通信。也因为这样,套接字明确地将客户端和服务器区分开来。
-
-**线程间的几种通信方式知道不?**
-
-1、锁机制
-
-- 互斥锁:提供了以排它方式阻止数据结构被并发修改的方法。
-- 读写锁:允许多个线程同时读共享数据,而对写操作互斥。
-- 条件变量:可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。
-
-2、信号量机制:包括无名线程信号量与有名线程信号量
-
-3、信号机制:类似于进程间的信号处理。
-
-线程间通信的主要目的是用于线程同步,所以线程没有象进程通信中用于数据交换的通信机制。
-
-## 9. 为什么要用单例模式?手写几种线程安全的单例模式?
-
-**简单来说使用单例模式可以带来下面几个好处:**
-
-- 对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销;
-- 由于 new 操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻 GC 压力,缩短 GC 停顿时间。
-
-**懒汉式(双重检查加锁版本)**
-
-```java
-public class Singleton {
-
- //volatile保证,当uniqueInstance变量被初始化成Singleton实例时,多个线程可以正确处理uniqueInstance变量
- private volatile static Singleton uniqueInstance;
- private Singleton() {
- }
- public static Singleton getInstance() {
- //检查实例,如果不存在,就进入同步代码块
- if (uniqueInstance == null) {
- //只有第一次才彻底执行这里的代码
- synchronized(Singleton.class) {
- //进入同步代码块后,再检查一次,如果仍是null,才创建实例
- if (uniqueInstance == null) {
- uniqueInstance = new Singleton();
- }
- }
- }
- return uniqueInstance;
- }
-}
-```
-
-**静态内部类方式**
-
-静态内部实现的单例是懒加载的且线程安全。
-
-只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance(只有第一次使用这个单例的实例的时候才加载,同时不会有线程安全问题)。
-
-```java
-public class Singleton {
- private static class SingletonHolder {
- private static final Singleton INSTANCE = new Singleton();
- }
- private Singleton (){}
- public static final Singleton getInstance() {
- return SingletonHolder.INSTANCE;
- }
-}
-```
-
-## 10. 简单介绍一下bean;知道Spring的bean的作用域与生命周期吗?
-
-在 Spring 中,那些组成应用程序的主体及由 Spring IOC 容器所管理的对象,被称之为 bean。简单地讲,bean 就是由 IOC 容器初始化、装配及管理的对象,除此之外,bean 就与应用程序中的其他对象没有什么区别了。而 bean 的定义以及 bean 相互间的依赖关系将通过配置元数据来描述。
-
-Spring中的bean默认都是单例的,这些单例Bean在多线程程序下如何保证线程安全呢? 例如对于Web应用来说,Web容器对于每个用户请求都创建一个单独的Sevlet线程来处理请求,引入Spring框架之后,每个Action都是单例的,那么对于Spring托管的单例Service Bean,如何保证其安全呢? Spring的单例是基于BeanFactory也就是Spring容器的,单例Bean在此容器内只有一个,Java的单例是基于 JVM,每个 JVM 内只有一个实例。
-
-
-
-Spring的bean的生命周期以及更多内容可以查看:[一文轻松搞懂Spring中bean的作用域与生命周期](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484400&idx=2&sn=7201eb365102fce017f89cb3527fb0bc&chksm=fd985591caefdc872a2fac897288119f94c345e4e12150774f960bf5f816b79e4b9b46be3d7f&token=1990180468&lang=zh_CN#rd)
-
-
-## 11. Spring 中的事务传播行为了解吗?TransactionDefinition 接口中哪五个表示隔离级别的常量?
-
-#### 事务传播行为
-
-事务传播行为(为了解决业务层方法之间互相调用的事务问题):
-当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。在TransactionDefinition定义中包括了如下几个表示传播行为的常量:
-
-**支持当前事务的情况:**
-
-- TransactionDefinition.PROPAGATION_REQUIRED: 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
-- TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
-- TransactionDefinition.PROPAGATION_MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)
-
-**不支持当前事务的情况:**
-
-- TransactionDefinition.PROPAGATION_REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则把当前事务挂起。
-- TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
-- TransactionDefinition.PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。
-
-**其他情况:**
-
-- TransactionDefinition.PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
-
-
-#### 隔离级别
-
-TransactionDefinition 接口中定义了五个表示隔离级别的常量:
-
-- **TransactionDefinition.ISOLATION_DEFAULT:** 使用后端数据库默认的隔离级别,Mysql 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别.
-- **TransactionDefinition.ISOLATION_READ_UNCOMMITTED:** 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
-- **TransactionDefinition.ISOLATION_READ_COMMITTED:** 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
-- **TransactionDefinition.ISOLATION_REPEATABLE_READ:** 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
-- **TransactionDefinition.ISOLATION_SERIALIZABLE:** 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
-
-## 12. SpringMVC 原理了解吗?
-
-
-
-客户端发送请求-> 前端控制器 DispatcherServlet 接受客户端请求 -> 找到处理器映射 HandlerMapping 解析请求对应的 Handler-> HandlerAdapter 会根据 Handler 来调用真正的处理器处理请求,并处理相应的业务逻辑 -> 处理器返回一个模型视图 ModelAndView -> 视图解析器进行解析 -> 返回一个视图对象->前端控制器 DispatcherServlet 渲染数据(Model)->将得到视图对象返回给用户
-
-关于 SpringMVC 原理更多内容可以查看我的这篇文章:[SpringMVC 工作原理详解](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484496&idx=1&sn=5472ffa687fe4a05f8900d8ee6726de4&chksm=fd985231caefdb27fc75b44ecf76b6f43e4617e0b01b3c040f8b8fab32e51dfa5118eed1d6ad&token=1990180468&lang=zh_CN#rd)
-
-## 13. Spring AOP IOC 实现原理
-
-过了秋招挺长一段时间了,说实话我自己也忘了如何简要概括 Spring AOP IOC 实现原理,就在网上找了一个较为简洁的答案,下面分享给各位。
-
-**IOC:** 控制反转也叫依赖注入。IOC利用java反射机制,AOP利用代理模式。IOC 概念看似很抽象,但是很容易理解。说简单点就是将对象交给容器管理,你只需要在spring配置文件中配置对应的bean以及设置相关的属性,让spring容器来生成类的实例对象以及管理对象。在spring容器启动的时候,spring会把你在配置文件中配置的bean都初始化好,然后在你需要调用的时候,就把它已经初始化好的那些bean分配给你需要调用这些bean的类。
-
-**AOP:** 面向切面编程。(Aspect-Oriented Programming) 。AOP可以说是对OOP的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码,属于静态代理。
-
-# 二 进阶篇
-
-## 1 消息队列MQ的套路
-
-消息队列/消息中间件应该是Java程序员必备的一个技能了,如果你之前没接触过消息队列的话,建议先去百度一下某某消息队列入门,然后花2个小时就差不多可以学会任何一种消息队列的使用了。如果说仅仅学会使用是万万不够的,在实际生产环境还要考虑消息丢失等等情况。关于消息队列面试相关的问题,推荐大家也可以看一下视频《Java工程师面试突击第1季-中华石杉老师》,如果大家没有资源的话,可以在我的公众号“Java面试通关手册”后台回复关键字“1”即可!
-
-### 1.1 介绍一下消息队列MQ的应用场景/使用消息队列的好处
-
-面试官一般会先问你这个问题,预热一下,看你知道消息队列不,一般在第一面的时候面试官可能只会问消息队列MQ的应用场景/使用消息队列的好处、使用消息队列会带来什么问题、消息队列的技术选型这几个问题,不会太深究下去,在后面的第二轮/第三轮技术面试中可能会深入问一下。
-
-**《大型网站技术架构》第四章和第七章均有提到消息队列对应用性能及扩展性的提升。**
-
-#### 1)通过异步处理提高系统性能
-
-如上图,**在不使用消息队列服务器的时候,用户的请求数据直接写入数据库,在高并发的情况下数据库压力剧增,使得响应速度变慢。但是在使用消息队列之后,用户的请求数据发送给消息队列之后立即 返回,再由消息队列的消费者进程从消息队列中获取数据,异步写入数据库。由于消息队列服务器处理速度快于数据库(消息队列也比数据库有更好的伸缩性),因此响应速度得到大幅改善。**
-
-通过以上分析我们可以得出**消息队列具有很好的削峰作用的功能**——即**通过异步处理,将短时间高并发产生的事务消息存储在消息队列中,从而削平高峰期的并发事务。** 举例:在电子商务一些秒杀、促销活动中,合理使用消息队列可以有效抵御促销活动刚开始大量订单涌入对系统的冲击。如下图所示:
-
-因为**用户请求数据写入消息队列之后就立即返回给用户了,但是请求数据在后续的业务校验、写数据库等操作中可能失败**。因此使用消息队列进行异步处理之后,需要**适当修改业务流程进行配合**,比如**用户在提交订单之后,订单数据写入消息队列,不能立即返回用户订单提交成功,需要在消息队列的订单消费者进程真正处理完该订单之后,甚至出库后,再通过电子邮件或短信通知用户订单成功**,以免交易纠纷。这就类似我们平时手机订火车票和电影票。
-
-#### 2)降低系统耦合性
-我们知道模块分布式部署以后聚合方式通常有两种:1.**分布式消息队列**和2.**分布式服务**。
-
-> **先来简单说一下分布式服务:**
-
-目前使用比较多的用来构建**SOA(Service Oriented Architecture面向服务体系结构)**的**分布式服务框架**是阿里巴巴开源的**Dubbo**。如果想深入了解Dubbo的可以看我写的关于Dubbo的这一篇文章:**《高性能优秀的服务框架-dubbo介绍》**:[https://juejin.im/post/5acadeb1f265da2375072f9c](https://juejin.im/post/5acadeb1f265da2375072f9c)
-
-> **再来谈我们的分布式消息队列:**
-
-我们知道如果模块之间不存在直接调用,那么新增模块或者修改模块就对其他模块影响较小,这样系统的可扩展性无疑更好一些。
-
-我们最常见的**事件驱动架构**类似生产者消费者模式,在大型网站中通常用利用消息队列实现事件驱动结构。如下图所示:
-
-
-**消息队列使利用发布-订阅模式工作,消息发送者(生产者)发布消息,一个或多个消息接受者(消费者)订阅消息。** 从上图可以看到**消息发送者(生产者)和消息接受者(消费者)之间没有直接耦合**,消息发送者将消息发送至分布式消息队列即结束对消息的处理,消息接受者从分布式消息队列获取该消息后进行后续处理,并不需要知道该消息从何而来。**对新增业务,只要对该类消息感兴趣,即可订阅该消息,对原有系统和业务没有任何影响,从而实现网站业务的可扩展性设计**。
-
-消息接受者对消息进行过滤、处理、包装后,构造成一个新的消息类型,将消息继续发送出去,等待其他消息接受者订阅该消息。因此基于事件(消息对象)驱动的业务架构可以是一系列流程。
-
-**另外为了避免消息队列服务器宕机造成消息丢失,会将成功发送到消息队列的消息存储在消息生产者服务器上,等消息真正被消费者服务器处理后才删除消息。在消息队列服务器宕机后,生产者服务器会选择分布式消息队列服务器集群中的其他服务器发布消息。**
-
-**备注:** 不要认为消息队列只能利用发布-订阅模式工作,只不过在解耦这个特定业务环境下是使用发布-订阅模式的,**比如在我们的ActiveMQ消息队列中还有点对点工作模式**,具体的会在后面的文章给大家详细介绍,这一篇文章主要还是让大家对消息队列有一个更透彻的了解。
-
-> 这个问题一般会在上一个问题问完之后,紧接着被问到。“使用消息队列会带来什么问题?”这个问题要引起重视,一般我们都会考虑使用消息队列会带来的好处而忽略它带来的问题!
-
-### 1.2 那么使用消息队列会带来什么问题?考虑过这些问题吗?
-
-- **系统可用性降低:** 系统可用性在某种程度上降低,为什么这样说呢?在加入MQ之前,你不用考虑消息丢失或者说MQ挂掉等等的情况,但是,引入MQ之后你就需要去考虑了!
-- **系统复杂性提高:** 加入MQ之后,你需要保证消息没有被重复消费、处理消息丢失的情况、保证消息传递的顺序性等等问题!
-- **一致性问题:** 我上面讲了消息队列可以实现异步,消息队列带来的异步确实可以提高系统响应速度。但是,万一消息的真正消费者并没有正确消费消息怎么办?这样就会导致数据不一致的情况了!
-
-> 了解下面这个问题是为了我们更好的进行技术选型!该部分摘自:《Java工程师面试突击第1季-中华石杉老师》,如果大家没有资源的话,可以在我的公众号“Java面试通关手册”后台回复关键字“1”即可!
-
-### 1.3 介绍一下你知道哪几种消息队列,该如何选择呢?
-
-
-| 特性 | ActiveMQ | RabbitMQ | RocketMQ | Kafka |
-| :---------------------- | -----------------------------------------------------------: | -----------------------------------------------------------: | -----------------------------------------------------------: | -----------------------------------------------------------: |
-| 单机吞吐量 | 万级,吞吐量比RocketMQ和Kafka要低了一个数量级 | 万级,吞吐量比RocketMQ和Kafka要低了一个数量级 | 10万级,RocketMQ也是可以支撑高吞吐的一种MQ | 10万级别,这是kafka最大的优点,就是吞吐量高。一般配合大数据类的系统来进行实时数据计算、日志采集等场景 |
-| topic数量对吞吐量的影响 | | | topic可以达到几百,几千个的级别,吞吐量会有较小幅度的下降这是RocketMQ的一大优势,在同等机器下,可以支撑大量的topic | topic从几十个到几百个的时候,吞吐量会大幅度下降。所以在同等机器下,kafka尽量保证topic数量不要过多。如果要支撑大规模topic,需要增加更多的机器资源 |
-| 可用性 | 高,基于主从架构实现高可用性 | 高,基于主从架构实现高可用性 | 非常高,分布式架构 | 非常高,kafka是分布式的,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用 |
-| 消息可靠性 | 有较低的概率丢失数据 | | 经过参数优化配置,可以做到0丢失 | 经过参数优化配置,消息可以做到0丢失 |
-| 时效性 | ms级 | 微秒级,这是rabbitmq的一大特点,延迟是最低的 | ms级 | 延迟在ms级以内 |
-| 功能支持 | MQ领域的功能极其完备 | 基于erlang开发,所以并发能力很强,性能极其好,延时很低 | MQ功能较为完善,还是分布式的,扩展性好 | 功能较为简单,主要支持简单的MQ功能,在大数据领域的实时计算以及日志采集被大规模使用,是事实上的标准 |
-| 优劣势总结 | 非常成熟,功能强大,在业内大量的公司以及项目中都有应用。偶尔会有较低概率丢失消息,而且现在社区以及国内应用都越来越少,官方社区现在对ActiveMQ 5.x维护越来越少,几个月才发布一个版本而且确实主要是基于解耦和异步来用的,较少在大规模吞吐的场景中使用 | erlang语言开发,性能极其好,延时很低;吞吐量到万级,MQ功能比较完备而且开源提供的管理界面非常棒,用起来很好用。社区相对比较活跃,几乎每个月都发布几个版本分在国内一些互联网公司近几年用rabbitmq也比较多一些但是问题也是显而易见的,RabbitMQ确实吞吐量会低一些,这是因为他做的实现机制比较重。而且erlang开发,国内有几个公司有实力做erlang源码级别的研究和定制?如果说你没这个实力的话,确实偶尔会有一些问题,你很难去看懂源码,你公司对这个东西的掌控很弱,基本职能依赖于开源社区的快速维护和修复bug。而且rabbitmq集群动态扩展会很麻烦,不过这个我觉得还好。其实主要是erlang语言本身带来的问题。很难读源码,很难定制和掌控。 | 接口简单易用,而且毕竟在阿里大规模应用过,有阿里品牌保障。日处理消息上百亿之多,可以做到大规模吞吐,性能也非常好,分布式扩展也很方便,社区维护还可以,可靠性和可用性都是ok的,还可以支撑大规模的topic数量,支持复杂MQ业务场景。而且一个很大的优势在于,阿里出品都是java系的,我们可以自己阅读源码,定制自己公司的MQ,可以掌控。社区活跃度相对较为一般,不过也还可以,文档相对来说简单一些,然后接口这块不是按照标准JMS规范走的有些系统要迁移需要修改大量代码。还有就是阿里出台的技术,你得做好这个技术万一被抛弃,社区黄掉的风险,那如果你们公司有技术实力我觉得用RocketMQ挺好的 | kafka的特点其实很明显,就是仅仅提供较少的核心功能,但是提供超高的吞吐量,ms级的延迟,极高的可用性以及可靠性,而且分布式可以任意扩展。同时kafka最好是支撑较少的topic数量即可,保证其超高吞吐量。而且kafka唯一的一点劣势是有可能消息重复消费,那么对数据准确性会造成极其轻微的影响,在大数据领域中以及日志采集中,这点轻微影响可以忽略这个特性天然适合大数据实时计算以及日志收集。 |
-
-> 这部分内容,我这里不给出答案,大家可以自行根据自己学习的消息队列查阅相关内容,我可能会在后面的文章中介绍到这部分内容。另外,下面这些问题在视频《Java工程师面试突击第1季-中华石杉老师》中都有提到,如果大家没有资源的话,可以在我的公众号“Java面试通关手册”后台回复关键字“1”即可!
-
-### 1.4 关于消息队列其他一些常见的问题展望
-
-1. 引入消息队列之后如何保证高可用性?
-2. 如何保证消息不被重复消费呢?
-3. 如何保证消息的可靠性传输(如何处理消息丢失的问题)?
-4. 我该怎么保证从消息队列里拿到的数据按顺序执行?
-5. 如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有几百万消息持续积压几小时,说说怎么解决?
-6. 如果让你来开发一个消息队列中间件,你会怎么设计架构?
-
-
-
-## 2 谈谈 InnoDB 和 MyIsam 两者的区别
-
-### 2.1 两者的对比
-
-1. **count运算上的区别:** 因为MyISAM缓存有表meta-data(行数等),因此在做COUNT(*)时对于一个结构很好的查询是不需要消耗多少资源的。而对于InnoDB来说,则没有这种缓存
-2. **是否支持事务和崩溃后的安全恢复:** MyISAM 强调的是性能,每次查询具有原子性,其执行速度比InnoDB类型更快,但是不提供事务支持。但是 InnoDB 提供事务支持,外部键等高级数据库功能。 具有事务(commit)、回滚(rollback)和崩溃修复能力(crash recovery capabilities)的事务安全(transaction-safe (ACID compliant))型表。
-3. **是否支持外键:** MyISAM不支持,而InnoDB支持。
-
-
-### 2.2 关于两者的总结
-
-MyISAM更适合读密集的表,而InnoDB更适合写密集的表。 在数据库做主从分离的情况下,经常选择MyISAM作为主库的存储引擎。
-
-一般来说,如果需要事务支持,并且有较高的并发读取频率(MyISAM的表锁的粒度太大,所以当该表写并发量较高时,要等待的查询就会很多了),InnoDB是不错的选择。如果你的数据量很大(MyISAM支持压缩特性可以减少磁盘的空间占用),而且不需要支持事务时,MyISAM是最好的选择。
-
-
-## 3 聊聊 Java 中的集合吧!
-
-### 3.1 Arraylist 与 LinkedList 有什么不同?(注意加上从数据结构分析的内容)
-
-- **1. 是否保证线程安全:** ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;
-- **2. 底层数据结构:** Arraylist 底层使用的是Object数组;LinkedList 底层使用的是双向链表数据结构(注意双向链表和双向循环链表的区别:);
-- **3. 插入和删除是否受元素位置的影响:** ① **ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。** 比如:执行`add(E e) `方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是O(1)。但是如果要在指定位置 i 插入和删除元素的话(`add(int index, E element) `)时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。 ② **LinkedList 采用链表存储,所以插入,删除元素时间复杂度不受元素位置的影响,都是近似 O(1) 而数组为近似 O(n) 。**
-- **4. 是否支持快速随机访问:** LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于`get(int index) `方法)。
-- **5. 内存空间占用:** ArrayList的空 间浪费主要体现在在list列表的结尾会预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间(因为要存放直接后继和直接前驱以及数据)。
-
-**补充内容:RandomAccess接口**
-
-```java
-public interface RandomAccess {
-}
-```
-
-查看源码我们发现实际上 RandomAccess 接口中什么都没有定义。所以,在我看来 RandomAccess 接口不过是一个标识罢了。标识什么? 标识实现这个接口的类具有随机访问功能。
-
-在 binarySearch() 方法中,它要判断传入的 list 是否RamdomAccess的实例,如果是,调用 indexedBinarySearch() 方法,如果不是,那么调用 iteratorBinarySearch() 方法
-
-```java
- public static
- int binarySearch(List extends Comparable super T>> list, T key) {
- if (list instanceof RandomAccess || list.size() Java 中的集合这类问题几乎是面试必问的,问到这类问题的时候,HashMap 又是几乎必问的问题,所以大家一定要引起重视!
-
-### 3.2 HashMap的底层实现
-
-#### 1)JDK1.8之前
-
-JDK1.8 之前 HashMap 底层是 **数组和链表** 结合在一起使用也就是 **链表散列**。**HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值,然后通过 `(n - 1) & hash` 判断当前元素存放的位置(这里的 n 指的时数组的长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。**
-
-**所谓扰动函数指的就是 HashMap 的 hash 方法。使用 hash 方法也就是扰动函数是为了防止一些实现比较差的 hashCode() 方法 换句话说使用扰动函数之后可以减少碰撞。**
-
-**JDK 1.8 HashMap 的 hash 方法源码:**
-
-JDK 1.8 的 hash方法 相比于 JDK 1.7 hash 方法更加简化,但是原理不变。
-
-```java
- static final int hash(Object key) {
- int h;
- // key.hashCode():返回散列值也就是hashcode
- // ^ :按位异或
- // >>>:无符号右移,忽略符号位,空位都以0补齐
- return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
- }
-```
-对比一下 JDK1.7的 HashMap 的 hash 方法源码.
-
-```java
-static int hash(int h) {
- // This function ensures that hashCodes that differ only by
- // constant multiples at each bit position have a bounded
- // number of collisions (approximately 8 at default load factor).
-
- h ^= (h >>> 20) ^ (h >>> 12);
- return h ^ (h >>> 7) ^ (h >>> 4);
-}
-```
-
-相比于 JDK1.8 的 hash 方法 ,JDK 1.7 的 hash 方法的性能会稍差一点点,因为毕竟扰动了 4 次。
-
-所谓 **“拉链法”** 就是:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。
-
-
-
-
-#### 2)JDK1.8之后
-
-相比于之前的版本, JDK1.8之后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。
-
-
-
-TreeMap、TreeSet以及JDK1.8之后的HashMap底层都用到了红黑树。红黑树就是为了解决二叉查找树的缺陷,因为二叉查找树在某些情况下会退化成一个线性结构。
-
-> 问完 HashMap 的底层原理之后,面试官可能就会紧接着问你 HashMap 底层数据结构相关的问题!
-
-### 3.3 既然谈到了红黑树,你给我手绘一个出来吧,然后简单讲一下自己对于红黑树的理解
-
-
-
-**红黑树特点:**
-
-1. 每个节点非红即黑;
-2. 根节点总是黑色的;
-3. 每个叶子节点都是黑色的空节点(NIL节点);
-4. 如果节点是红色的,则它的子节点必须是黑色的(反之不一定);
-5. 从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度)
-
-
-**红黑树的应用:**
-
-TreeMap、TreeSet以及JDK1.8之后的HashMap底层都用到了红黑树。
-
-**为什么要用红黑树**
-
-简单来说红黑树就是为了解决二叉查找树的缺陷,因为二叉查找树在某些情况下会退化成一个线性结构。
-
-
-### 3.4 红黑树这么优秀,为何不直接使用红黑树得了?
-
-说一下自己对于这个问题的看法:我们知道红黑树属于(自)平衡二叉树,但是为了保持“平衡”是需要付出代价的,红黑树在插入新数据后可能需要通过左旋,右旋、变色这些操作来保持平衡,这费事啊。你说说我们引入红黑树就是为了查找数据快,如果链表长度很短的话,根本不需要引入红黑树的,你引入之后还要付出代价维持它的平衡。但是链表过长就不一样了。至于为什么选 8 这个值呢?通过概率统计所得,这个值是综合查询成本和新增元素成本得出的最好的一个值。
-
-### 3.5 HashMap 和 Hashtable 的区别/HashSet 和 HashMap 区别
-
-**HashMap 和 Hashtable 的区别**
-
-1. **线程是否安全:** HashMap 是非线程安全的,Hashtable 是线程安全的;Hashtable 内部的方法基本都经过 `synchronized` 修饰。(如果你要保证线程安全的话就使用 ConcurrentHashMap 吧!);
-2. **效率:** 因为线程安全的问题,HashMap 要比 Hashtable 效率高一点。另外,Hashtable 基本被淘汰,不要在代码中使用它;
-3. **对Null key 和Null value的支持:** HashMap 中,null 可以作为键,这样的键只有一个,可以有一个或多个键所对应的值为 null。但是在 Hashtable 中 put 进的键值只要有一个 null,直接抛出 NullPointerException。
-4. **初始容量大小和每次扩充容量大小的不同 :** ①创建时如果不指定容量初始值,Hashtable 默认的初始大小为11,之后每次扩充,容量变为原来的2n+1。HashMap 默认的初始化大小为16。之后每次扩充,容量变为原来的2倍。②创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为2的幂次方大小(HashMap 中的`tableSizeFor()`方法保证,下面给出了源代码)。也就是说 HashMap 总是使用2的幂作为哈希表的大小,后面会介绍到为什么是2的幂次方。
-5. **底层数据结构:** JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。Hashtable 没有这样的机制。
-
-**HashSet 和 HashMap 区别**
-
-如果你看过 HashSet 源码的话就应该知道:HashSet 底层就是基于 HashMap 实现的。(HashSet 的源码非常非常少,因为除了 clone() 方法、writeObject()方法、readObject()方法是 HashSet 自己不得不实现之外,其他方法都是直接调用 HashMap 中的方法。)
-
-
-
-# 三 终结篇
-
-## 1. Object类有哪些方法?
-
-这个问题,面试中经常出现。我觉得不论是出于应付面试还是说更好地掌握Java这门编程语言,大家都要掌握!
-
-### 1.1 Object类的常见方法总结
-
-Object类是一个特殊的类,是所有类的父类。它主要提供了以下11个方法:
-
-```java
-
-public final native Class> getClass()//native方法,用于返回当前运行时对象的Class对象,使用了final关键字修饰,故不允许子类重写。
-
-public native int hashCode() //native方法,用于返回对象的哈希码,主要使用在哈希表中,比如JDK中的HashMap。
-public boolean equals(Object obj)//用于比较2个对象的内存地址是否相等,String类对该方法进行了重写用户比较字符串的值是否相等。
-
-protected native Object clone() throws CloneNotSupportedException//naitive方法,用于创建并返回当前对象的一份拷贝。一般情况下,对于任何对象 x,表达式 x.clone() != x 为true,x.clone().getClass() == x.getClass() 为true。Object本身没有实现Cloneable接口,所以不重写clone方法并且进行调用的话会发生CloneNotSupportedException异常。
-
-public String toString()//返回类的名字@实例的哈希码的16进制的字符串。建议Object所有的子类都重写这个方法。
-
-public final native void notify()//native方法,并且不能重写。唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。如果有多个线程在等待只会任意唤醒一个。
-
-public final native void notifyAll()//native方法,并且不能重写。跟notify一样,唯一的区别就是会唤醒在此对象监视器上等待的所有线程,而不是一个线程。
-
-public final native void wait(long timeout) throws InterruptedException//native方法,并且不能重写。暂停线程的执行。注意:sleep方法没有释放锁,而wait方法释放了锁 。timeout是等待时间。
-
-public final void wait(long timeout, int nanos) throws InterruptedException//多了nanos参数,这个参数表示额外时间(以毫微秒为单位,范围是 0-999999)。 所以超时的时间还需要加上nanos毫秒。
-
-public final void wait() throws InterruptedException//跟之前的2个wait方法一样,只不过该方法一直等待,没有超时时间这个概念
-
-protected void finalize() throws Throwable { }//实例被垃圾回收器回收的时候触发的操作
-
-```
-
-> 问完上面这个问题之后,面试官很可能紧接着就会问你“hashCode与equals”相关的问题。
-
-### 1.2 hashCode与equals
-
-面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写equals时必须重写hashCode方法?”
-
-#### 1.2.1 hashCode()介绍
-
-hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数。另外需要注意的是: Object 的 hashcode 方法是本地方法,也就是用 c 语言或 c++ 实现的,该方法通常用来将对象的 内存地址 转换为整数之后返回。
-
-```java
- public native int hashCode();
-```
-
-散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)
-
-#### 1.2.2 为什么要有hashCode
-
-
-**我们以“HashSet如何检查重复”为例子来说明为什么要有hashCode:**
-
-当你把对象加入HashSet时,HashSet会先计算对象的hashcode值来判断对象加入的位置,同时也会与其他已经加入的对象的hashcode值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同hashcode值的对象,这时会调用equals()方法来检查hashcode相等的对象是否真的相同。如果两者相同,HashSet就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的Java启蒙书《Head fist java》第二版)。这样我们就大大减少了equals的次数,相应就大大提高了执行速度。
-
-
-#### 1.2.3 hashCode()与equals()的相关规定
-
-1. 如果两个对象相等,则hashcode一定也是相同的
-2. 两个对象相等,对两个对象分别调用equals方法都返回true
-3. 两个对象有相同的hashcode值,它们也不一定是相等的
-4. **因此,equals方法被覆盖过,则hashCode方法也必须被覆盖**
-5. hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)
-
-#### 1.2.4 为什么两个对象有相同的hashcode值,它们也不一定是相等的?
-
-在这里解释一位小伙伴的问题。以下内容摘自《Head Fisrt Java》。
-
-因为hashCode() 所使用的杂凑算法也许刚好会让多个对象传回相同的杂凑值。越糟糕的杂凑算法越容易碰撞,但这也与数据值域分布的特性有关(所谓碰撞也就是指的是不同的对象得到相同的 hashCode)。
-
-我们刚刚也提到了 HashSet,如果 HashSet 在对比的时候,同样的 hashcode 有多个对象,它会使用 equals() 来判断是否真的相同。也就是说 hashcode 只是用来缩小查找成本。
-
-> ==与equals 的对比也是比较常问的基础问题之一!
-
-### 1.3 ==与equals
-
-**==** : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象。(基本数据类型==比较的是值,引用数据类型==比较的是内存地址)
-
-**equals()** : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况:
-
-- 情况1:类没有覆盖equals()方法。则通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象。
-- 情况2:类覆盖了equals()方法。一般,我们都覆盖equals()方法来两个对象的内容相等;若它们的内容相等,则返回true(即,认为这两个对象相等)。
-
-
-**举个例子:**
-
-```java
-public class test1 {
- public static void main(String[] args) {
- String a = new String("ab"); // a 为一个引用
- String b = new String("ab"); // b为另一个引用,对象的内容一样
- String aa = "ab"; // 放在常量池中
- String bb = "ab"; // 从常量池中查找
- if (aa == bb) // true
- System.out.println("aa==bb");
- if (a == b) // false,非同一对象
- System.out.println("a==b");
- if (a.equals(b)) // true
- System.out.println("aEQb");
- if (42 == 42.0) { // true
- System.out.println("true");
- }
- }
-}
-```
-
-**说明:**
-
-- String中的equals()方法是被重写过的,因为Object的equals()方法是比较的对象的内存地址,而String的equals()方法比较的是对象的值。
-- 当创建String类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个String对象。
-
-> 在[【备战春招/秋招系列5】美团面经总结进阶篇 (附详解答案)](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484625&idx=1&sn=9c4fa1f7d4291a5fbd7daa44bac2b012&chksm=fd9852b0caefdba6edcf9a827aa4a17ddc97bf6ad2e5ee6f7e1aa1b443b54444d05d2b76732b&token=723699735&lang=zh_CN#rd) 这篇文章中,我们已经提到了一下关于 HashMap 在面试中常见的问题:HashMap 的底层实现、简单讲一下自己对于红黑树的理解、红黑树这么优秀,为何不直接使用红黑树得了、HashMap 和 Hashtable 的区别/HashSet 和 HashMap 区别。HashMap 和 ConcurrentHashMap 这俩兄弟在一般只要面试中问到集合相关的问题就一定会被问到,所以各位务必引起重视!
-
-## 2 ConcurrentHashMap 相关问题
-
-### 2.1 ConcurrentHashMap 和 Hashtable 的区别
-
-ConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的方式上不同。
-
-- **底层数据结构:** JDK1.7的 ConcurrentHashMap 底层采用 **分段的数组+链表** 实现,JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树。Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似都是采用 **数组+链表** 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的;
-- **实现线程安全的方式(重要):** ① **在JDK1.7的时候,ConcurrentHashMap(分段锁)** 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。(默认分配16个Segment,比Hashtable效率提高16倍。) **到了 JDK1.8 的时候已经摒弃了Segment的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。(JDK1.6以后 对 synchronized锁做了很多优化)** 整个看起来就像是优化过且线程安全的 HashMap,虽然在JDK1.8中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;② **Hashtable(同一把锁)**:使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。
-
-**两者的对比图:**
-
-图片来源:http://www.cnblogs.com/chengxiao/p/6842045.html
-
-Hashtable:
-
-
-JDK1.7的ConcurrentHashMap:
-
-JDK1.8的ConcurrentHashMap(TreeBin: 红黑二叉树节点
-Node: 链表节点):
-
-
-### 2.2 ConcurrentHashMap线程安全的具体实现方式/底层具体实现
-
-#### JDK1.7(上面有示意图)
-
-首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。
-
-**ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成**。
-
-Segment 实现了 ReentrantLock,所以 Segment 是一种可重入锁,扮演锁的角色。HashEntry 用于存储键值对数据。
-
-```java
-static class Segment extends ReentrantLock implements Serializable {
-}
-```
-
-一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和HashMap类似,是一种数组和链表结构,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素,每个 Segment 守护着一个HashEntry数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment的锁。
-
-#### JDK1.8(上面有示意图)
-
-ConcurrentHashMap取消了Segment分段锁,采用CAS和synchronized来保证并发安全。数据结构跟HashMap1.8的结构类似,数组+链表/红黑二叉树。
-
-synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提升N倍。
-
-## 3 谈谈 synchronized 和 ReentrantLock 的区别
-
-**① 两者都是可重入锁**
-
-两者都是可重入锁。“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。
-
-**② synchronized 依赖于 JVM 而 ReentrantLock 依赖于 API**
-
-synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的,并没有直接暴露给我们。ReentrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock() 方法配合 try/finally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。
-
-**③ ReentrantLock 比 synchronized 增加了一些高级功能**
-
-相比synchronized,ReentrantLock增加了一些高级功能。主要来说主要有三点:**①等待可中断;②可实现公平锁;③可实现选择性通知(锁可以绑定多个条件)**
-
-- **ReentrantLock提供了一种能够中断等待锁的线程的机制**,通过 lock.lockInterruptibly() 来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。
-- **ReentrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。** ReentrantLock默认情况是非公平的,可以通过 ReentrantLock类的`ReentrantLock(boolean fair)`构造方法来制定是否是公平的。
-- synchronized关键字与wait()和notify/notifyAll()方法相结合可以实现等待/通知机制,ReentrantLock类当然也可以实现,但是需要借助于Condition接口与newCondition() 方法。Condition是JDK1.5之后才有的,它具有很好的灵活性,比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例(即对象监视器),**线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。 在使用notify/notifyAll()方法进行通知时,被通知的线程是由 JVM 选择的,用ReentrantLock类结合Condition实例可以实现“选择性通知”** ,这个功能非常重要,而且是Condition接口默认提供的。而synchronized关键字就相当于整个Lock对象中只有一个Condition实例,所有的线程都注册在它一个身上。如果执行notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题,而Condition实例的signalAll()方法 只会唤醒注册在该Condition实例中的所有等待线程。
-
-如果你想使用上述功能,那么选择ReentrantLock是一个不错的选择。
-
-**④ 两者的性能已经相差无几**
-
-在JDK1.6之前,synchronized 的性能是比 ReentrantLock 差很多。具体表示为:synchronized 关键字吞吐量岁线程数的增加,下降得非常严重。而ReentrantLock 基本保持一个比较稳定的水平。我觉得这也侧面反映了, synchronized 关键字还有非常大的优化余地。后续的技术发展也证明了这一点,我们上面也讲了在 JDK1.6 之后 JVM 团队对 synchronized 关键字做了很多优化。JDK1.6 之后,synchronized 和 ReentrantLock 的性能基本是持平了。所以网上那些说因为性能才选择 ReentrantLock 的文章都是错的!JDK1.6之后,性能已经不是选择synchronized和ReentrantLock的影响因素了!而且虚拟机在未来的性能改进中会更偏向于原生的synchronized,所以还是提倡在synchronized能满足你的需求的情况下,优先考虑使用synchronized关键字来进行同步!优化后的synchronized和ReentrantLock一样,在很多地方都是用到了CAS操作。
-
-
-## 4 线程池了解吗?
-
-
-### 4.1 为什么要用线程池?
-
-线程池提供了一种限制和管理资源(包括执行一个任务)。 每个线程池还维护一些基本统计信息,例如已完成任务的数量。
-
-这里借用《Java并发编程的艺术》提到的来说一下使用线程池的好处:
-
-- **降低资源消耗。** 通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
-- **提高响应速度。** 当任务到达时,任务可以不需要的等到线程创建就能立即执行。
-- **提高线程的可管理性。** 线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
-
-### 4.2 Java 提供了哪几种线程池?他们各自的使用场景是什么?
-
-#### Java 主要提供了下面4种线程池
-
-- **FixedThreadPool:** 该方法返回一个固定线程数量的线程池。该线程池中的线程数量始终不变。当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。若没有,则新的任务会被暂存在一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。
-- **SingleThreadExecutor:** 方法返回一个只有一个线程的线程池。若多余一个任务被提交到该线程池,任务会被保存在一个任务队列中,待线程空闲,按先入先出的顺序执行队列中的任务。
-- **CachedThreadPool:** 该方法返回一个可根据实际情况调整线程数量的线程池。线程池的线程数量不确定,但若有空闲线程可以复用,则会优先使用可复用的线程。若所有线程均在工作,又有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复用。
-- **ScheduledThreadPoolExecutor:** 主要用来在给定的延迟后运行任务,或者定期执行任务。ScheduledThreadPoolExecutor又分为:ScheduledThreadPoolExecutor(包含多个线程)和SingleThreadScheduledExecutor (只包含一个线程)两种。
-
-#### 各种线程池的适用场景介绍
-
-- **FixedThreadPool:** 适用于为了满足资源管理需求,而需要限制当前线程数量的应用场景。它适用于负载比较重的服务器;
-- **SingleThreadExecutor:** 适用于需要保证顺序地执行各个任务并且在任意时间点,不会有多个线程是活动的应用场景;
-- **CachedThreadPool:** 适用于执行很多的短期异步任务的小程序,或者是负载较轻的服务器;
-- **ScheduledThreadPoolExecutor:** 适用于需要多个后台执行周期任务,同时为了满足资源管理需求而需要限制后台线程的数量的应用场景;
-- **SingleThreadScheduledExecutor:** 适用于需要单个后台线程执行周期任务,同时保证顺序地执行各个任务的应用场景。
-
-### 4.3 创建的线程池的方式
-
-**(1) 使用 Executors 创建**
-
-我们上面刚刚提到了 Java 提供的几种线程池,通过 Executors 工具类我们可以很轻松的创建我们上面说的几种线程池。但是实际上我们一般都不是直接使用Java提供好的线程池,另外在《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 构造函数 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
-
-```java
-Executors 返回线程池对象的弊端如下:
-
-FixedThreadPool 和 SingleThreadExecutor : 允许请求的队列长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致OOM。
-CachedThreadPool 和 ScheduledThreadPool : 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致OOM。
-
-```
-**(2) ThreadPoolExecutor的构造函数创建**
-
-
-我们可以自己直接调用 ThreadPoolExecutor 的构造函数来自己创建线程池。在创建的同时,给 BlockQueue 指定容量就可以了。示例如下:
-
-```java
-private static ExecutorService executor = new ThreadPoolExecutor(13, 13,
- 60L, TimeUnit.SECONDS,
- new ArrayBlockingQueue(13));
-```
-
-这种情况下,一旦提交的线程数超过当前可用线程数时,就会抛出java.util.concurrent.RejectedExecutionException,这是因为当前线程池使用的队列是有边界队列,队列已经满了便无法继续处理新的请求。但是异常(Exception)总比发生错误(Error)要好。
-
-**(3) 使用开源类库**
-
-Hollis 大佬之前在他的文章中也提到了:“除了自己定义ThreadPoolExecutor外。还有其他方法。这个时候第一时间就应该想到开源类库,如apache和guava等。”他推荐使用guava提供的ThreadFactoryBuilder来创建线程池。下面是参考他的代码示例:
-
-```java
-public class ExecutorsDemo {
-
- private static ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
- .setNameFormat("demo-pool-%d").build();
-
- private static ExecutorService pool = new ThreadPoolExecutor(5, 200,
- 0L, TimeUnit.MILLISECONDS,
- new LinkedBlockingQueue(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
-
- public static void main(String[] args) {
-
- for (int i = 0; i < Integer.MAX_VALUE; i++) {
- pool.execute(new SubThread());
- }
- }
-}
-```
-
-通过上述方式创建线程时,不仅可以避免OOM的问题,还可以自定义线程名称,更加方便的出错的时候溯源。
-
-## 5 Nginx
-
-### 5.1 简单介绍一下Nginx
-
-Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器。 Nginx 主要提供反向代理、负载均衡、动静分离(静态资源服务)等服务。下面我简单地介绍一下这些名词。
-
-#### 反向代理
-
-谈到反向代理,就不得不提一下正向代理。无论是正向代理,还是反向代理,说到底,就是代理模式的衍生版本罢了
-
-- **正向代理:**某些情况下,代理我们用户去访问服务器,需要用户手动的设置代理服务器的ip和端口号。正向代理比较常见的一个例子就是 VPN 了。
-- **反向代理:** 是用来代理服务器的,代理我们要访问的目标服务器。代理服务器接受请求,然后将请求转发给内部网络的服务器,并将从服务器上得到的结果返回给客户端,此时代理服务器对外就表现为一个服务器。
-
-通过下面两幅图,大家应该更好理解(图源:http://blog.720ui.com/2016/nginx_action_05_proxy/):
-
-
-
-
-
-所以,简单的理解,就是正向代理是为客户端做代理,代替客户端去访问服务器,而反向代理是为服务器做代理,代替服务器接受客户端请求。
-
-#### 负载均衡
-
-在高并发情况下需要使用,其原理就是将并发请求分摊到多个服务器执行,减轻每台服务器的压力,多台服务器(集群)共同完成工作任务,从而提高了数据的吞吐量。
-
-Nginx支持的weight轮询(默认)、ip_hash、fair、url_hash这四种负载均衡调度算法,感兴趣的可以自行查阅。
-
-负载均衡相比于反向代理更侧重的是将请求分担到多台服务器上去,所以谈论负载均衡只有在提供某服务的服务器大于两台时才有意义。
-
-#### 动静分离
-
-动静分离是让动态网站里的动态网页根据一定规则把不变的资源和经常变的资源区分开来,动静资源做好了拆分以后,我们就可以根据静态资源的特点将其做缓存操作,这就是网站静态化处理的核心思路。
-
-### 5.2 为什么要用 Nginx?
-
-> 这部分内容参考极客时间—[Nginx核心知识100讲的内容](https://time.geekbang.org/course/intro/138?code=AycjiiQk6uQRxnVJzBupFkrGkvZlmYELPRsZbWzaAHE=)。
-
-如果面试官问你这个问题,就一定想看你知道 Nginx 服务器的一些优点吗。
-
-Nginx 有以下5个优点:
-
-1. 高并发、高性能(这是其他web服务器不具有的)
-2. 可扩展性好(模块化设计,第三方插件生态圈丰富)
-3. 高可靠性(可以在服务器行持续不间断的运行数年)
-4. 热部署(这个功能对于 Nginx 来说特别重要,热部署指可以在不停止 Nginx服务的情况下升级 Nginx)
-5. BSD许可证(意味着我们可以将源代码下载下来进行修改然后使用自己的版本)
-
-### 5.3 Nginx 的四个主要组成部分了解吗?
-
-> 这部分内容参考极客时间—[Nginx核心知识100讲的内容](https://time.geekbang.org/course/intro/138?code=AycjiiQk6uQRxnVJzBupFkrGkvZlmYELPRsZbWzaAHE=)。
-
-- Nginx 二进制可执行文件:由各模块源码编译出一个文件
-- nginx.conf 配置文件:控制Nginx 行为
-- acess.log 访问日志: 记录每一条HTTP请求信息
-- error.log 错误日志:定位问题
diff --git "a/docs/essential-content-for-interview/PreparingForInterview/\351\235\242\350\257\225\345\256\230-\344\275\240\346\234\211\344\273\200\344\271\210\351\227\256\351\242\230\350\246\201\351\227\256\346\210\221.md" "b/docs/essential-content-for-interview/PreparingForInterview/\351\235\242\350\257\225\345\256\230-\344\275\240\346\234\211\344\273\200\344\271\210\351\227\256\351\242\230\350\246\201\351\227\256\346\210\221.md"
deleted file mode 100644
index 7a55d539d11..00000000000
--- "a/docs/essential-content-for-interview/PreparingForInterview/\351\235\242\350\257\225\345\256\230-\344\275\240\346\234\211\344\273\200\344\271\210\351\227\256\351\242\230\350\246\201\351\227\256\346\210\221.md"
+++ /dev/null
@@ -1,64 +0,0 @@
-我还记得当时我去参加面试的时候,几乎每一场面试,特别是HR面和高管面的时候,面试官总是会在结尾问我:“问了你这么多问题了,你有什么问题问我吗?”。这个时候很多人内心就会陷入短暂的纠结中:我该问吗?不问的话面试官会不会对我影响不好?问什么问题?问这个问题会不会让面试官对我的影响不好啊?
-
-
-
-### 这个问题对最终面试结果的影响到底大不大?
-
-就技术面试而言,回答这个问题的时候,只要你不是触碰到你所面试的公司的雷区,那么我觉得这对你能不能拿到最终offer来说影响确实是不大的。我说这些并不代表你就可以直接对面试官说:“我没问题了。”,笔主当时面试的时候确实也说过挺多次“没问题要问了。”,最终也没有导致笔主被pass掉(可能是前面表现比较好,哈哈,自恋一下)。我现在回想起来,觉得自己当时做法其实挺不对的。面试本身就是一个双向选择的过程,你对这个问题的回答也会侧面反映出你对这次面试的上心程度,你的问题是否有价值,也影响了你最终的选择与公司是否选择你。
-
-面试官在技术面试中主要考察的还是你这样个人到底有没有胜任这个工作的能力以及你是否适合公司未来的发展需要,很多公司还需要你认同它的文化,我觉得你只要不是太笨,应该不会栽在这里。除非你和另外一个人在能力上相同,但是只能在你们两个人中选一个,那么这个问题才对你能不能拿到offer至关重要。有准备总比没准备好,给面试官留一个好的影响总归是没错的。
-
-但是,就非技术面试来说,我觉得好好回答这个问题对你最终的结果还是比较重要的。
-
-总的来说不管是技术面试还是非技术面试,如果你想赢得公司的青睐和尊重,我觉得我们都应该重视这个问题。
-
-### 真诚一点,不要问太 Low 的问题
-
-回答这个问题很重要的一点就是你没有必要放低自己的姿态问一些很虚或者故意讨好面试官的问题,也不要把自己从面经上学到的东西照搬下来使用。面试官也不是傻子,特别是那种特别有经验的面试官,你是真心诚意的问问题,还是从别处照搬问题来讨好面试官,人家可能一听就听出来了。总的来说,还是要真诚。除此之外,不要问太 Low 的问题,会显得你整个人格局比较小或者说你根本没有准备(侧面反映你对这家公司不上心,既然你不上心,为什么要要你呢)。举例几个比较 Low 的问题,大家看看自己有没有问过其中的问题:
-
-- 贵公司的主要业务是什么?(面试之前自己不知道提前网上查一下吗?)
-- 贵公司的男女比例如何?(考虑脱单?记住你是来工作的!)
-- 贵公司一年搞几次外出旅游?(你是来工作的,这些娱乐活动先别放在心上!)
-- ......
-
-### 有哪些有价值的问题值得问?
-
-针对这个问题。笔主专门找了几个专门做HR工作的小哥哥小姐姐们询问并且查阅了挺多前辈们的回答,然后结合自己的实际经历,我概括了下面几个比较适合问的问题。
-
-#### 面对HR或者其他Level比较低的面试官时
-
-1. **能不能谈谈你作为一个公司老员工对公司的感受?** (这个问题比较容易回答,不会让面试官陷入无话可说的尴尬境地。另外,从面试官的回答中你可以加深对这个公司的了解,让你更加清楚这个公司到底是不是你想的那样或者说你是否能适应这个公司的文化。除此之外,这样的问题在某种程度上还可以拉进你与面试官的距离。)
-2. **能不能问一下,你当时因为什么原因选择加入这家公司的呢或者说这家公司有哪些地方吸引你?有什么地方你觉得还不太好或者可以继续完善吗?** (类似第一个问题,都是问面试官个人对于公司的看法。)
-3. **我觉得我这次表现的不是太好,你有什么建议或者评价给我吗?**(这个是我常问的。我觉得说自己表现不好只是这个语境需要这样来说,这样可以显的你比较谦虚好学上进。)
-4. **接下来我会有一段空档期,有什么值得注意或者建议学习的吗?** (体现出你对工作比较上心,自助学习意识比较强。)
-5. **这个岗位为什么还在招人?** (岗位真实性和价值咨询)
-6. **大概什么时候能给我回复呢?** (终面的时候,如果面试官没有说的话,可以问一下)
-7. ......
-
-
-
-#### 面对部门领导
-
-1. **部门的主要人员分配以及对应的主要工作能简单介绍一下吗?**
-2. **未来如果我要加入这个团队,你对我的期望是什么?** (部门领导一般情况下是你的直属上级了,你以后和他打交道的机会应该是最多的。你问这个问题,会让他感觉你是一个对他的部门比较上心,比较有团体意识,并且愿意倾听的候选人。)
-3. **公司对新入职的员工的培养机制是什么样的呢?** (正规的公司一般都有培养机制,提前问一下是对你自己的负责也会显的你比较上心)
-4. **以您来看,这个岗位未来在公司内部的发展如何?** (在我看来,问这个问题也是对你自己的负责吧,谁不想发展前景更好的岗位呢?)
-5. **团队现在面临的最大挑战是什么?** (这样的问题不会暴露你对公司的不了解,并且也能让你对未来工作的挑战或困难有一个提前的预期。)
-
-
-
-#### 面对Level比较高的(比如总裁,老板)
-
-1. **贵公司的发展目标和方向是什么?** (看下公司的发展是否满足自己的期望)
-2. **与同行业的竞争者相比,贵公司的核心竞争优势在什么地方?** (充分了解自己的优势和劣势)
-3. **公司现在面临的最大挑战是什么?**
-
-### 来个补充,顺便送个祝福给大家
-
-薪酬待遇和相关福利问题一般在终面的时候(最好不要在前面几面的时候就问到这个问题),面试官会提出来或者在面试完之后以邮件的形式告知你。一般来说,如果面试官很愿意为你回答问题,对你的问题也比较上心的话,那他肯定是觉得你就是他们要招的人。
-
-大家在面试的时候,可以根据自己对于公司或者岗位的了解程度,对上面提到的问题进行适当修饰或者修改。上面提到的一些问题只是给没有经验的朋友一个参考,如果你还有其他比较好的问题的话,那当然也更好啦!
-
-金三银四。过了二月就到了面试高峰期或者说是黄金期。几份惊喜几份愁,愿各位能始终不忘初心!每个人都有每个人的难处。引用一句《阿甘正传》里面的台词:“生活就像一盒巧克力,你永远不知道下一块是什么味道“。
-
-
\ No newline at end of file
diff --git "a/docs/essential-content-for-interview/\346\211\213\346\212\212\346\211\213\346\225\231\344\275\240\347\224\250Markdown\345\206\231\344\270\200\344\273\275\351\253\230\350\264\250\351\207\217\347\232\204\347\256\200\345\216\206.md" "b/docs/essential-content-for-interview/\346\211\213\346\212\212\346\211\213\346\225\231\344\275\240\347\224\250Markdown\345\206\231\344\270\200\344\273\275\351\253\230\350\264\250\351\207\217\347\232\204\347\256\200\345\216\206.md"
deleted file mode 100644
index 9cb2811b75f..00000000000
--- "a/docs/essential-content-for-interview/\346\211\213\346\212\212\346\211\213\346\225\231\344\275\240\347\224\250Markdown\345\206\231\344\270\200\344\273\275\351\253\230\350\264\250\351\207\217\347\232\204\347\256\200\345\216\206.md"
+++ /dev/null
@@ -1,93 +0,0 @@
-## Markdown 简历模板样式一览
-
-**可以看到我把联系方式放在第一位,因为公司一般会与你联系,所以把联系方式放在第一位也是为了方便联系考虑。**
-
-## 为什么要用 Markdown 写简历?
-
-Markdown 语法简单,易于上手。使用正确的 Markdown 语言写出来的简历不论是在排版还是格式上都比较干净,易于阅读。另外,使用 Markdown 写简历也会给面试官一种你比较专业的感觉。
-
-除了这些,我觉得使用 Markdown 写简历可以很方便将其与PDF、HTML、PNG格式之间转换。后面我会介绍到转换方法,只需要一条命令你就可以实现 Markdown 到 PDF、HTML 与 PNG之间的无缝切换。
-
-> 下面的一些内容我在之前的一篇文章中已经提到过,这里再说一遍,最后会分享如何实现Markdown 到 PDF、HTML、PNG格式之间转换。
-
-## 为什么说简历很重要?
-
-假如你是网申,你的简历必然会经过HR的筛选,一张简历HR可能也就花费10秒钟看一下,然后HR就会决定你这一关是Fail还是Pass。
-
-假如你是内推,如果你的简历没有什么优势的话,就算是内推你的人再用心,也无能为力。
-
-另外,就算你通过了筛选,后面的面试中,面试官也会根据你的简历来判断你究竟是否值得他花费很多时间去面试。
-
-## 写简历的两大法则
-
-目前写简历的方式有两种普遍被认可,一种是 STAR, 一种是 FAB。
-
-**STAR法则(Situation Task Action Result):**
-
-- **Situation:** 事情是在什么情况下发生;
-- **Task::** 你是如何明确你的任务的;
-- **Action:** 针对这样的情况分析,你采用了什么行动方式;
-- **Result:** 结果怎样,在这样的情况下你学习到了什么。
-
-**FAB 法则(Feature Advantage Benefit):**
-
-- **Feature:** 是什么;
-- **Advantage:** 比别人好在哪些地方;
-- **Benefit:** 如果雇佣你,招聘方会得到什么好处。
-
-## 项目经历怎么写?
-简历上有一两个项目经历很正常,但是真正能把项目经历很好的展示给面试官的非常少。对于项目经历大家可以考虑从如下几点来写:
-
-1. 对项目整体设计的一个感受
-2. 在这个项目中你负责了什么、做了什么、担任了什么角色
-3. 从这个项目中你学会了那些东西,使用到了那些技术,学会了那些新技术的使用
-4. 另外项目描述中,最好可以体现自己的综合素质,比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的。
-
-## 专业技能该怎么写?
-先问一下你自己会什么,然后看看你意向的公司需要什么。一般HR可能并不太懂技术,所以他在筛选简历的时候可能就盯着你专业技能的关键词来看。对于公司有要求而你不会的技能,你可以花几天时间学习一下,然后在简历上可以写上自己了解这个技能。比如你可以这样写:
-
-- Dubbo:精通
-- Spring:精通
-- Docker:掌握
-- SOA分布式开发 :掌握
-- Spring Cloud:了解
-
-## 简历模板分享
-
-**开源程序员简历模板**: [https://github.com/geekcompany/ResumeSample](https://github.com/geekcompany/ResumeSample)(包括PHP程序员简历模板、iOS程序员简历模板、Android程序员简历模板、Web前端程序员简历模板、Java程序员简历模板、C/C++程序员简历模板、NodeJS程序员简历模板、架构师简历模板以及通用程序员简历模板)
-
-**上述简历模板的改进版本:** [https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/简历模板.md](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/简历模板.md)
-
-## 其他的一些小tips
-
-1. 尽量避免主观表述,少一点语义模糊的形容词,尽量要简洁明了,逻辑结构清晰。
-2. 注意排版(不需要花花绿绿的),尽量使用Markdown语法。
-3. 如果自己有博客或者个人技术栈点的话,写上去会为你加分很多。
-4. 如果自己的Github比较活跃的话,写上去也会为你加分很多。
-5. 注意简历真实性,一定不要写自己不会的东西,或者带有欺骗性的内容
-6. 项目经历建议以时间倒序排序,另外项目经历不在于多,而在于有亮点。
-7. 如果内容过多的话,不需要非把内容压缩到一页,保持排版干净整洁就可以了。
-8. 简历最后最好能加上:“感谢您花时间阅读我的简历,期待能有机会和您共事。”这句话,显的你会很有礼貌。
-
-
-> 我们刚刚讲了很多关于如何写简历的内容并且分享了一份 Markdown 格式的简历文档。下面我们来看看如何实现 Markdown 到 HTML格式、PNG格式之间转换。
-## Markdown 到 HTML格式、PNG格式之间转换
-
-网上很难找到一个比较方便并且效果好的转换方法,最后我是通过 Visual Studio Code 的 Markdown PDF 插件完美解决了这个问题!
-
-### 安装 Markdown PDF 插件
-
-**① 打开Visual Studio Code ,按快捷键 F1,选择安装扩展选项**
-
-
-
-**② 搜索 “Markdown PDF” 插件并安装 ,然后重启**
-
-
-
-### 使用方法
-
-随便打开一份 Markdown 文件 点击F1,然后输入export即可!
-
-
-
diff --git "a/docs/essential-content-for-interview/\347\256\200\345\216\206\346\250\241\346\235\277.md" "b/docs/essential-content-for-interview/\347\256\200\345\216\206\346\250\241\346\235\277.md"
deleted file mode 100644
index 2a7a0431f35..00000000000
--- "a/docs/essential-content-for-interview/\347\256\200\345\216\206\346\250\241\346\235\277.md"
+++ /dev/null
@@ -1,79 +0,0 @@
-# 联系方式
-
-- 手机:
-- Email:
-- 微信:
-
-# 个人信息
-
- - 姓名/性别/出生日期
- - 本科/xxx计算机系xxx专业/英语六级
- - 技术博客:[http://snailclimb.top/](http://snailclimb.top/)
- - 荣誉奖励:获得了什么奖(获奖时间)
- - Github:[https://github.com/Snailclimb ](https://github.com/Snailclimb)
- - Github Resume: [http://resume.github.io/?Snailclimb](http://resume.github.io/?Snailclimb)
- - 期望职位:Java 研发程序员/大数据工程师(Java后台开发为首选)
- - 期望城市:xxx城市
-
-
-# 项目经历
-
-## xxx项目
-
-### 项目描述
-
-介绍该项目是做什么的、使用到了什么技术以及你对项目整体设计的一个感受
-
-### 责任描述
-
-主要可以从下面三点来写:
-
-1. 在这个项目中你负责了什么、做了什么、担任了什么角色
-2. 从这个项目中你学会了那些东西,使用到了那些技术,学会了那些新技术的使用
-3. 另外项目描述中,最好可以体现自己的综合素质,比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的。
-
-# 开源项目和技术文章
-
-## 开源项目
-
-- [Java-Guide](https://github.com/Snailclimb/Java-Guide) :一份涵盖大部分Java程序员所需要掌握的核心知识。Star:3.9K; Fork:0.9k。
-
-
-## 技术文章推荐
-
-- [可能是把Java内存区域讲的最清楚的一篇文章](https://juejin.im/post/5b7d69e4e51d4538ca5730cb)
-- [搞定JVM垃圾回收就是这么简单](https://juejin.im/post/5b85ea54e51d4538dd08f601)
-- [前端&后端程序员必备的Linux基础知识](https://juejin.im/post/5b3b19856fb9a04fa42f8c71)
-- [可能是把Docker的概念讲的最清楚的一篇文章](https://juejin.im/post/5b260ec26fb9a00e8e4b031a)
-
-
-# 校园经历(可选)
-
-## 2016-2017
-
-担任学校社团-致深社副会长,主要负责团队每周活动的组建以及每周例会的主持。
-
-## 2017-2018
- 担任学校传媒组织:“长江大学在线信息传媒”的副站长以及安卓组成员。主要负责每周例会主持、活动策划以及学校校园通APP的研发工作。
-
-
-# 技能清单
-
-以下均为我熟练使用的技能
-
-- Web开发:PHP/Hack/Node
-- Web框架:ThinkPHP/Yaf/Yii/Lavarel/LazyPHP
-- 前端框架:Bootstrap/AngularJS/EmberJS/HTML5/Cocos2dJS/ionic
-- 前端工具:Bower/Gulp/SaSS/LeSS/PhoneGap
-- 数据库相关:MySQL/PgSQL/PDO/SQLite
-- 版本管理、文档和自动化部署工具:Svn/Git/PHPDoc/Phing/Composer
-- 单元测试:PHPUnit/SimpleTest/Qunit
-- 云和开放平台:SAE/BAE/AWS/微博开放平台/微信应用开发
-
-# 自我评价(可选)
-
-自我发挥。切记不要过度自夸!!!
-
-
-### 感谢您花时间阅读我的简历,期待能有机会和您共事。
-
diff --git "a/docs/essential-content-for-interview/\351\235\242\350\257\225\345\277\205\345\244\207\344\271\213\344\271\220\350\247\202\351\224\201\344\270\216\346\202\262\350\247\202\351\224\201.md" "b/docs/essential-content-for-interview/\351\235\242\350\257\225\345\277\205\345\244\207\344\271\213\344\271\220\350\247\202\351\224\201\344\270\216\346\202\262\350\247\202\351\224\201.md"
deleted file mode 100644
index 00aaecd8c5b..00000000000
--- "a/docs/essential-content-for-interview/\351\235\242\350\257\225\345\277\205\345\244\207\344\271\213\344\271\220\350\247\202\351\224\201\344\270\216\346\202\262\350\247\202\351\224\201.md"
+++ /dev/null
@@ -1,115 +0,0 @@
-点击关注[公众号](#公众号)及时获取笔主最新更新文章,并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。
-
-
-
-- [何谓悲观锁与乐观锁](#何谓悲观锁与乐观锁)
- - [悲观锁](#悲观锁)
- - [乐观锁](#乐观锁)
- - [两种锁的使用场景](#两种锁的使用场景)
-- [乐观锁常见的两种实现方式](#乐观锁常见的两种实现方式)
- - [1. 版本号机制](#1-版本号机制)
- - [2. CAS算法](#2-cas算法)
-- [乐观锁的缺点](#乐观锁的缺点)
- - [1 ABA 问题](#1-aba-问题)
- - [2 循环时间长开销大](#2-循环时间长开销大)
- - [3 只能保证一个共享变量的原子操作](#3-只能保证一个共享变量的原子操作)
-- [CAS与synchronized的使用情景](#cas与synchronized的使用情景)
-
-
-
-### 何谓悲观锁与乐观锁
-
-> 乐观锁对应于生活中乐观的人总是想着事情往好的方向发展,悲观锁对应于生活中悲观的人总是想着事情往坏的方向发展。这两种人各有优缺点,不能不以场景而定说一种人好于另外一种人。
-
-#### 悲观锁
-
-总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(**共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程**)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中`synchronized`和`ReentrantLock`等独占锁就是悲观锁思想的实现。
-
-
-#### 乐观锁
-
-总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。**乐观锁适用于多读的应用类型,这样可以提高吞吐量**,像数据库提供的类似于**write_condition机制**,其实都是提供的乐观锁。在Java中`java.util.concurrent.atomic`包下面的原子变量类就是使用了乐观锁的一种实现方式**CAS**实现的。
-
-#### 两种锁的使用场景
-
-从上面对两种锁的介绍,我们知道两种锁各有优缺点,不可认为一种好于另一种,像**乐观锁适用于写比较少的情况下(多读场景)**,即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以**一般多写的场景下用悲观锁就比较合适。**
-
-
-### 乐观锁常见的两种实现方式
-
-> **乐观锁一般会使用版本号机制或CAS算法实现。**
-
-#### 1. 版本号机制
-
-一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。
-
-**举一个简单的例子:**
-假设数据库中帐户信息表中有一个 version 字段,当前值为 1 ;而当前帐户余额字段( balance )为 $100 。
-
-1. 操作员 A 此时将其读出( version=1 ),并从其帐户余额中扣除 $50( $100-$50 )。
-2. 在操作员 A 操作的过程中,操作员B 也读入此用户信息( version=1 ),并从其帐户余额中扣除 $20 ( $100-$20 )。
-3. 操作员 A 完成了修改工作,将数据版本号加一( version=2 ),连同帐户扣除后余额( balance=$50 ),提交至数据库更新,此时由于提交数据版本大于数据库记录当前版本,数据被更新,数据库记录 version 更新为 2 。
-4. 操作员 B 完成了操作,也将版本号加一( version=2 )试图向数据库提交数据( balance=$80 ),但此时比对数据库记录版本时发现,操作员 B 提交的数据版本号为 2 ,数据库记录当前版本也为 2 ,不满足 “ 提交版本必须大于记录当前版本才能执行更新 “ 的乐观锁策略,因此,操作员 B 的提交被驳回。
-
-这样,就避免了操作员 B 用基于 version=1 的旧数据修改的结果覆盖操作员A 的操作结果的可能。
-
-#### 2. CAS算法
-
-即**compare and swap(比较与交换)**,是一种有名的**无锁算法**。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。**CAS算法**涉及到三个操作数
-
-- 需要读写的内存值 V
-- 进行比较的值 A
-- 拟写入的新值 B
-
-当且仅当 V 的值等于 A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个**自旋操作**,即**不断的重试**。
-
-关于自旋锁,大家可以看一下这篇文章,非常不错:[《
-面试必备之深入理解自旋锁》](https://blog.csdn.net/qq_34337272/article/details/81252853)
-
-### 乐观锁的缺点
-
-> ABA 问题是乐观锁一个常见的问题
-
-#### 1 ABA 问题
-
-如果一个变量V初次读取的时候是A值,并且在准备赋值的时候检查到它仍然是A值,那我们就能说明它的值没有被其他线程修改过了吗?很明显是不能的,因为在这段时间它的值可能被改为其他值,然后又改回A,那CAS操作就会误认为它从来没有被修改过。这个问题被称为CAS操作的 **"ABA"问题。**
-
-JDK 1.5 以后的 `AtomicStampedReference 类`就提供了此种能力,其中的 `compareAndSet 方法`就是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
-
-#### 2 循环时间长开销大
-**自旋CAS(也就是不成功就一直循环执行直到成功)如果长时间不成功,会给CPU带来非常大的执行开销。** 如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。
-
-#### 3 只能保证一个共享变量的原子操作
-
-CAS 只对单个共享变量有效,当操作涉及跨多个共享变量时 CAS 无效。但是从 JDK 1.5开始,提供了`AtomicReference类`来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行 CAS 操作.所以我们可以使用锁或者利用`AtomicReference类`把多个共享变量合并成一个共享变量来操作。
-
-### CAS与synchronized的使用情景
-
-> **简单的来说CAS适用于写比较少的情况下(多读场景,冲突一般较少),synchronized适用于写比较多的情况下(多写场景,冲突一般较多)**
-
-1. 对于资源竞争较少(线程冲突较轻)的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源;而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。
-2. 对于资源竞争严重(线程冲突严重)的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized。
-
-补充: Java并发编程这个领域中synchronized关键字一直都是元老级的角色,很久之前很多人都会称它为 **“重量级锁”** 。但是,在JavaSE 1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的 **偏向锁** 和 **轻量级锁** 以及其它**各种优化**之后变得在某些情况下并不是那么重了。synchronized的底层实现主要依靠 **Lock-Free** 的队列,基本思路是 **自旋后阻塞**,**竞争切换后继续竞争锁**,**稍微牺牲了公平性,但获得了高吞吐量**。在线程冲突较少的情况下,可以获得和CAS类似的性能;而线程冲突严重的情况下,性能远高于CAS。
-
-## 公众号
-
-如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
-
-**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"面试突击"** 即可免费领取!
-
-**Java工程师必备学习资源:** 一些Java工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/docs/github-trending/2018-12.md b/docs/github-trending/2018-12.md
deleted file mode 100644
index 3637e93ea0c..00000000000
--- a/docs/github-trending/2018-12.md
+++ /dev/null
@@ -1,78 +0,0 @@
-本文数据统计于 1.1 号凌晨,由 SnailClimb 整理。
-
-### 1. JavaGuide
-
-- **Github地址**: [https://github.com/Snailclimb/JavaGuide](https://github.com/Snailclimb/JavaGuide)
-- **star**: 18.2k
-- **介绍**: 【Java学习+面试指南】 一份涵盖大部分Java程序员所需要掌握的核心知识。
-
-### 2. mall
-
-- **Github地址**: [https://github.com/macrozheng/mall](https://github.com/macrozheng/mall)
-- **star**: 3.3k
-- **介绍**: mall项目是一套电商系统,包括前台商城系统及后台管理系统,基于SpringBoot+MyBatis实现。 前台商城系统包含首页门户、商品推荐、商品搜索、商品展示、购物车、订单流程、会员中心、客户服务、帮助中心等模块。 后台管理系统包含商品管理、订单管理、会员管理、促销管理、运营管理、内容管理、统计报表、财务管理、权限管理、设置等模块。
-
-### 3. advanced-java
-
-- **Github地址**:[https://github.com/doocs/advanced-java](https://github.com/doocs/advanced-java)
-- **star**: 3.3k
-- **介绍**: 互联网 Java 工程师进阶知识完全扫盲
-
-### 4. matrix
-
-- **Github地址**:[https://github.com/Tencent/matrix](https://github.com/Tencent/matrix)
-- **star**: 2.5k
-- **介绍**: Matrix 是一款微信研发并日常使用的 APM(Application Performance Manage),当前主要运行在 Android 平台上。 Matrix 的目标是建立统一的应用性能接入框架,通过各种性能监控方案,对性能监控项的异常数据进行采集和分析,输出相应的问题分析、定位与优化建议,从而帮助开发者开发出更高质量的应用。
-
-### 5. miaosha
-
-- **Github地址**:[https://github.com/qiurunze123/miaosha](https://github.com/qiurunze123/miaosha)
-- **star**: 2.4k
-- **介绍**: 高并发大流量如何进行秒杀架构,我对这部分知识做了一个系统的整理,写了一套系统。
-
-### 6. arthas
-
-- **Github地址**:[https://github.com/alibaba/arthas](https://github.com/alibaba/arthas)
-- **star**: 8.2k
-- **介绍**: Arthas 是Alibaba开源的Java诊断工具,深受开发者喜爱。
-
-### 7 spring-boot
-
-- **Github地址**: [https://github.com/spring-projects/spring-boot](https://github.com/spring-projects/spring-boot)
-- **star:** 32.6k
-- **介绍**: 虽然Spring的组件代码是轻量级的,但它的配置却是重量级的(需要大量XML配置),不过Spring Boot 让这一切成为了过去。 另外Spring Cloud也是基于Spring Boot构建的,我个人非常有必要学习一下。
-
- **关于Spring Boot官方的介绍:**
-
- > Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”…Most Spring Boot applications need very little Spring configuration.(Spring Boot可以轻松创建独立的生产级基于Spring的应用程序,只要通过 “just run”(可能是run ‘Application’或java -jar 或 tomcat 或 maven插件run 或 shell脚本)便可以运行项目。大部分Spring Boot项目只需要少量的配置即可)
-
-### 8. tutorials
-
-- **Github地址**:[https://github.com/eugenp/tutorials](https://github.com/eugenp/tutorials)
-- **star**: 10k
-- **介绍**: 该项目是一系列小而专注的教程 - 每个教程都涵盖Java生态系统中单一且定义明确的开发领域。 当然,它们的重点是Spring Framework - Spring,Spring Boot和Spring Securiyt。 除了Spring之外,还有以下技术:核心Java,Jackson,HttpClient,Guava。
-
-### 9. qmq
-
-- **Github地址**:[https://github.com/qunarcorp/qmq](https://github.com/qunarcorp/qmq)
-- **star**: 1.1k
-- **介绍**: QMQ是去哪儿网内部广泛使用的消息中间件,自2012年诞生以来在去哪儿网所有业务场景中广泛的应用,包括跟交易息息相关的订单场景; 也包括报价搜索等高吞吐量场景。
-
-
-### 10. symphony
-
-- **Github地址**:[https://github.com/b3log/symphony](https://github.com/b3log/symphony)
-- **star**: 9k
-- **介绍**: 一款用 Java 实现的现代化社区(论坛/BBS/社交网络/博客)平台。
-
-### 11. incubator-dubbo
-
-- **Github地址**:[https://github.com/apache/incubator-dubbo](https://github.com/apache/incubator-dubbo)
-- **star**: 23.6k
-- **介绍**: 阿里开源的一个基于Java的高性能开源RPC框架。
-
-### 12. apollo
-
-- **Github地址**:[https://github.com/ctripcorp/apollo](https://github.com/ctripcorp/apollo)
-- **star**: 10k
-- **介绍**: Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性,适用于微服务配置管理场景。
diff --git a/docs/github-trending/2019-1.md b/docs/github-trending/2019-1.md
deleted file mode 100644
index aa1de92f623..00000000000
--- a/docs/github-trending/2019-1.md
+++ /dev/null
@@ -1,76 +0,0 @@
-### 1. JavaGuide
-
-- **Github地址**: [https://github.com/Snailclimb/JavaGuide](https://github.com/Snailclimb/JavaGuide)
-- **star**: 22.8k
-- **介绍**: 【Java学习+面试指南】 一份涵盖大部分Java程序员所需要掌握的核心知识。
-
-### 2. advanced-java
-
-- **Github地址**:[https://github.com/doocs/advanced-java](https://github.com/doocs/advanced-java)
-- **star**: 7.9k
-- **介绍**: 互联网 Java 工程师进阶知识完全扫盲
-
-### 3. fescar
-
-- **Github地址**:[https://github.com/alibaba/fescar](https://github.com/alibaba/fescar)
-- **star**: 4.6k
-- **介绍**: 具有 **高性能** 和 **易用性** 的 **微服务架构** 的 **分布式事务** 的解决方案。(特点:高性能且易于使用,旨在实现简单并快速的事务提交与回滚。
-
-### 4. mall
-
-- **Github地址**: [https://github.com/macrozheng/mall](https://github.com/macrozheng/mall)
-- **star**: 5.6 k
-- **介绍**: mall项目是一套电商系统,包括前台商城系统及后台管理系统,基于SpringBoot+MyBatis实现。 前台商城系统包含首页门户、商品推荐、商品搜索、商品展示、购物车、订单流程、会员中心、客户服务、帮助中心等模块。 后台管理系统包含商品管理、订单管理、会员管理、促销管理、运营管理、内容管理、统计报表、财务管理、权限管理、设置等模块。
-
-### 5. miaosha
-
-- **Github地址**:[https://github.com/qiurunze123/miaosha](https://github.com/qiurunze123/miaosha)
-- **star**: 4.4k
-- **介绍**: 高并发大流量如何进行秒杀架构,我对这部分知识做了一个系统的整理,写了一套系统。
-
-### 6. flink
-
-- **Github地址**:[https://github.com/apache/flink](https://github.com/apache/flink)
-- **star**: 7.1 k
-- **介绍**: Apache Flink是一个开源流处理框架,具有强大的流和批处理功能。
-
-### 7. cim
-
-- **Github地址**:[https://github.com/crossoverJie/cim](https://github.com/crossoverJie/cim)
-- **star**: 1.8 k
-- **介绍**: cim(cross IM) 适用于开发者的即时通讯系统。
-
-### 8. symphony
-
-- **Github地址**:[https://github.com/b3log/symphony](https://github.com/b3log/symphony)
-- **star**: 10k
-- **介绍**: 一款用 Java 实现的现代化社区(论坛/BBS/社交网络/博客)平台。
-
-### 9. spring-boot
-
-- **Github地址**: [https://github.com/spring-projects/spring-boot](https://github.com/spring-projects/spring-boot)
-- **star:** 32.6k
-- **介绍**: 虽然Spring的组件代码是轻量级的,但它的配置却是重量级的(需要大量XML配置),不过Spring Boot 让这一切成为了过去。 另外Spring Cloud也是基于Spring Boot构建的,我个人非常有必要学习一下。
-
- **关于Spring Boot官方的介绍:**
-
- > Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”…Most Spring Boot applications need very little Spring configuration.(Spring Boot可以轻松创建独立的生产级基于Spring的应用程序,只要通过 “just run”(可能是run ‘Application’或java -jar 或 tomcat 或 maven插件run 或 shell脚本)便可以运行项目。大部分Spring Boot项目只需要少量的配置即可)
-
-### 10. arthas
-
-- **Github地址**:[https://github.com/alibaba/arthas](https://github.com/alibaba/arthas)
-- **star**: 9.5k
-- **介绍**: Arthas 是Alibaba开源的Java诊断工具。
-
-**概览:**
-
-当你遇到以下类似问题而束手无策时,`Arthas`可以帮助你解决:
-
-0. 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
-1. 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
-2. 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
-3. 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
-4. 是否有一个全局视角来查看系统的运行状况?
-5. 有什么办法可以监控到JVM的实时运行状态?
-
-`Arthas`支持JDK 6+,支持Linux/Mac/Winodws,采用命令行交互模式,同时提供丰富的 `Tab` 自动补全功能,进一步方便进行问题的定位和诊断。
diff --git a/docs/github-trending/2019-2.md b/docs/github-trending/2019-2.md
deleted file mode 100644
index 51d34b32f78..00000000000
--- a/docs/github-trending/2019-2.md
+++ /dev/null
@@ -1,64 +0,0 @@
-### 1. JavaGuide
-
-- **Github地址**: [https://github.com/Snailclimb/JavaGuide](https://github.com/Snailclimb/JavaGuide)
-- **Star**: 27.2k (4,437 stars this month)
-- **介绍**: 【Java学习+面试指南】 一份涵盖大部分Java程序员所需要掌握的核心知识。
-
-### 2.DoraemonKit
-
-- **Github地址**:
-- **Star**: 5.2k (3,786 stars this month)
-- **介绍**: 简称 "DoKit" 。一款功能齐全的客户端( iOS 、Android )研发助手,你值得拥有。
-
-### 3.advanced-java
-
-- **Github地址**:[https://github.com/doocs/advanced-java](https://github.com/doocs/advanced-java)
-- **Star**:11.2k (3,042 stars this month)
-- **介绍**: 互联网 Java 工程师进阶知识完全扫盲。
-
-### 4. spring-boot-examples
-
-- **Github地址**:
-- **star**: 9.6 k (1,764 stars this month)
-- **介绍**: Spring Boot 教程、技术栈示例代码,快速简单上手教程。
-
-### 5. mall
-
-- **Github地址**: [https://github.com/macrozheng/mall](https://github.com/macrozheng/mall)
-- **star**: 7.4 k (1,736 stars this month)
-- **介绍**: mall项目是一套电商系统,包括前台商城系统及后台管理系统,基于SpringBoot+MyBatis实现。 前台商城系统包含首页门户、商品推荐、商品搜索、商品展示、购物车、订单流程、会员中心、客户服务、帮助中心等模块。 后台管理系统包含商品管理、订单管理、会员管理、促销管理、运营管理、内容管理、统计报表、财务管理、权限管理、设置等模块。
-
-### 6. fescar
-
-- **Github地址**:[https://github.com/alibaba/fescar](https://github.com/alibaba/fescar)
-- **star**: 6.0 k (1,308 stars this month)
-- **介绍**: 具有 **高性能** 和 **易用性** 的 **微服务架构** 的 **分布式事务** 的解决方案。(特点:高性能且易于使用,旨在实现简单并快速的事务提交与回滚。)
-
-### 7. h4cker
-
-- **Github地址**:
-- **star**: 2.1 k (1,303 stars this month)
-- **介绍**: 该仓库主要由Omar Santos维护,包括与道德黑客/渗透测试,数字取证和事件响应(DFIR),漏洞研究,漏洞利用开发,逆向工程等相关的资源。
-
-### 8. spring-boot
-
-- **Github地址**: [https://github.com/spring-projects/spring-boot](https://github.com/spring-projects/spring-boot)
-- **star:** 34.8k (1,073 stars this month)
-- **介绍**: 虽然Spring的组件代码是轻量级的,但它的配置却是重量级的(需要大量XML配置),不过Spring Boot 让这一切成为了过去。 另外Spring Cloud也是基于Spring Boot构建的,我个人非常有必要学习一下。
-
- **关于Spring Boot官方的介绍:**
-
- > Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”…Most Spring Boot applications need very little Spring configuration.(Spring Boot可以轻松创建独立的生产级基于Spring的应用程序,只要通过 “just run”(可能是run ‘Application’或java -jar 或 tomcat 或 maven插件run 或 shell脚本)便可以运行项目。大部分Spring Boot项目只需要少量的配置即可)
-
-### 9. arthas
-
-- **Github地址**:[https://github.com/alibaba/arthas](https://github.com/alibaba/arthas)
-- **star**: 10.5 k (970 stars this month)
-- **介绍**: Arthas 是Alibaba开源的Java诊断工具。
-
-### 10. tutorials
-
-- **Github地址**:[https://github.com/eugenp/tutorials](https://github.com/eugenp/tutorials)
-- **star**: 12.1 k (789 stars this month)
-- **介绍**: 该项目是一系列小而专注的教程 - 每个教程都涵盖Java生态系统中单一且定义明确的开发领域。 当然,它们的重点是Spring Framework - Spring,Spring Boot和Spring Securiyt。 除了Spring之外,还有以下技术:核心Java,Jackson,HttpClient,Guava。
-
diff --git a/docs/github-trending/2019-3.md b/docs/github-trending/2019-3.md
deleted file mode 100644
index eaed4a5d483..00000000000
--- a/docs/github-trending/2019-3.md
+++ /dev/null
@@ -1,60 +0,0 @@
-### 1. JavaGuide
-
-- **Github 地址**: [https://github.com/Snailclimb/JavaGuide](https://github.com/Snailclimb/JavaGuide)
-- **Star**: 32.9k (6,196 stars this month)
-- **介绍**: 【Java 学习+面试指南】 一份涵盖大部分 Java 程序员所需要掌握的核心知识。
-
-### 2.advanced-java
-
-- **Github 地址**:[https://github.com/doocs/advanced-java](https://github.com/doocs/advanced-java)
-- **Star**: 15.1k (4,012 stars this month)
-- **介绍**: 互联网 Java 工程师进阶知识完全扫盲。
-
-### 3.spring-boot-examples
-
-- **Github 地址**:[https://github.com/ityouknow/spring-boot-examples](https://github.com/ityouknow/spring-boot-examples)
-- **Star**: 12.8k (3,462 stars this month)
-- **介绍**: Spring Boot 教程、技术栈示例代码,快速简单上手教程。
-
-### 4. mall
-
-- **Github 地址**: [https://github.com/macrozheng/mall](https://github.com/macrozheng/mall)
-- **star**: 9.7 k (2,418 stars this month)
-- **介绍**: mall 项目是一套电商系统,包括前台商城系统及后台管理系统,基于 SpringBoot+MyBatis 实现。 前台商城系统包含首页门户、商品推荐、商品搜索、商品展示、购物车、订单流程、会员中心、客户服务、帮助中心等模块。 后台管理系统包含商品管理、订单管理、会员管理、促销管理、运营管理、内容管理、统计报表、财务管理、权限管理、设置等模块。
-
-### 5. seata
-
-- **Github 地址** : [https://github.com/seata/seata](https://github.com/seata/seata)
-- **star**: 7.2 k (1359 stars this month)
-- **介绍**: Seata 是一种易于使用,高性能,基于 Java 的开源分布式事务解决方案。
-
-### 6. quarkus
-
-- **Github 地址**:[https://github.com/quarkusio/quarkus](https://github.com/quarkusio/quarkus)
-- **star**: 12 k (1,224 stars this month)
-- **介绍**: Quarkus 是为 GraalVM 和 HotSpot 量身定制的 Kubernetes Native Java 框架,由最佳的 Java 库和标准精心打造而成。Quarkus 的目标是使 Java 成为 Kubernetes 和无服务器环境中的领先平台,同时为开发人员提供统一的反应式和命令式编程模型,以优化地满足更广泛的分布式应用程序架构。
-
-### 7. arthas
-
-- **Github 地址**:[https://github.com/alibaba/arthas](https://github.com/alibaba/arthas)
-- **star**: 11.6 k (1,199 stars this month)
-- **介绍**: Arthas 是 Alibaba 开源的 Java 诊断工具。
-
-### 8.DoraemonKit
-
-- **Github 地址**:
-- **Star**: 6.2k (1,177 stars this month)
-- **介绍**: 简称 "DoKit" 。一款功能齐全的客户端( iOS 、Android )研发助手,你值得拥有。
-
-### 9.elasticsearch
-
-- **Github 地址** [https://github.com/elastic/elasticsearch](https://github.com/elastic/elasticsearch)
-- **Star**: 39.7k (1,069 stars this month)
-- **介绍**: 开源,分布式,RESTful 搜索引擎。
-
-### 10. tutorials
-
-- **Github 地址**:[https://github.com/eugenp/tutorials](https://github.com/eugenp/tutorials)
-- **star**: 13 k (998 stars this month)
-- **介绍**: 该项目是一系列小而专注的教程 - 每个教程都涵盖 Java 生态系统中单一且定义明确的开发领域。 当然,它们的重点是 Spring Framework - Spring,Spring Boot 和 Spring Securiyt。 除了 Spring 之外,还有以下技术:核心 Java,Jackson,HttpClient,Guava。
-
diff --git a/docs/github-trending/2019-4.md b/docs/github-trending/2019-4.md
deleted file mode 100644
index 713a76da642..00000000000
--- a/docs/github-trending/2019-4.md
+++ /dev/null
@@ -1,98 +0,0 @@
-以下涉及到的数据统计与 2019 年 5 月 1 日 12 点,数据来源: 。
-
-下面的内容从 Java 学习文档到最热门的框架再到热门的工具应有尽有,比如下面推荐到的开源项目 Hutool 就是近期比较热门的项目之一,它是 Java 工具包,能够帮助我们简化代码!我觉得下面这些项目对于学习 Java 的朋友还是很有帮助的!
-
-
-### 1. JavaGuide
-
-- **Github 地址**: [https://github.com/Snailclimb/JavaGuide](https://github.com/Snailclimb/JavaGuide)
-- **Star**: 37.9k (5,660 stars this month)
-- **介绍**: 【Java 学习+面试指南】 一份涵盖大部分 Java 程序员所需要掌握的核心知识。
-
-### 2. advanced-java
-
-- **Github 地址**:[https://github.com/doocs/advanced-java](https://github.com/doocs/advanced-java)
-- **Star**: 15.1k (4,654 stars this month)
-- **介绍**: 互联网 Java 工程师进阶知识完全扫盲。
-
-### 3. CS-Notes
-
-- **Github 地址**:
-- **Star**: 59.2k (4,012 stars this month)
-- **介绍**: 技术面试必备基础知识。
-
-### 4. ghidra
-
-- **Github 地址**:
-- **Star**: 15.0k (2,995 stars this month)
-- **介绍**: Ghidra是一个软件逆向工程(SRE)框架。
-
-### 5. mall
-
-- **Github 地址**: [https://github.com/macrozheng/mall](https://github.com/macrozheng/mall)
-- **star**: 11.6 k (2,100 stars this month)
-- **介绍**: mall 项目是一套电商系统,包括前台商城系统及后台管理系统,基于 SpringBoot+MyBatis 实现。 前台商城系统包含首页门户、商品推荐、商品搜索、商品展示、购物车、订单流程、会员中心、客户服务、帮助中心等模块。 后台管理系统包含商品管理、订单管理、会员管理、促销管理、运营管理、内容管理、统计报表、财务管理、权限管理、设置等模块。
-
-### 6. ZXBlog
-
-- **Github 地址**:
-- **star**: 2.1 k (2,086 stars this month)
-- **介绍**: 记录各种学习笔记(算法、Java、数据库、并发......)。
-
-### 7.DoraemonKit
-
-- **Github地址**:
-- **Star**: 7.6k (1,541 stars this month)
-- **介绍**: 简称 "DoKit" 。一款功能齐全的客户端( iOS 、Android )研发助手,你值得拥有。
-
-### 8. spring-boot
-
-- **Github地址**: [https://github.com/spring-projects/spring-boot](https://github.com/spring-projects/spring-boot)
-- **star:** 37.3k (1,489 stars this month)
-- **介绍**: 虽然Spring的组件代码是轻量级的,但它的配置却是重量级的(需要大量XML配置),不过Spring Boot 让这一切成为了过去。 另外Spring Cloud也是基于Spring Boot构建的,我个人非常有必要学习一下。
-
-**Spring Boot官方的介绍:**
-
-> Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”…Most Spring Boot applications need very little Spring configuration.(Spring Boot可以轻松创建独立的生产级基于Spring的应用程序,只要通过 “just run”(可能是run ‘Application’或java -jar 或 tomcat 或 maven插件run 或 shell脚本)便可以运行项目。大部分Spring Boot项目只需要少量的配置即可)
-
-### 9. spring-boot-examples
-
-- **Github 地址**:[https://github.com/ityouknow/spring-boot-examples](https://github.com/ityouknow/spring-boot-examples)
-- **Star**: 12.8k (1,453 stars this month)
-- **介绍**: Spring Boot 教程、技术栈示例代码,快速简单上手教程。
-
-### 10. seata
-
-- **Github 地址** : [https://github.com/seata/seata](https://github.com/seata/seata)
-- **star**: 8.4 k (1441 stars this month)
-- **介绍**: Seata 是一种易于使用,高性能,基于 Java 的开源分布式事务解决方案。
-
-### 11. litemall
-
-- **Github 地址**:[https://github.com/ityouknow/spring-boot-examples](https://github.com/ityouknow/spring-boot-examples)
-- **Star**: 6.0k (1,427 stars this month)
-- **介绍**: 又一个小商城。litemall = Spring Boot后端 + Vue管理员前端 + 微信小程序用户前端 + Vue用户移动端。
-
-### 12. skywalking
-
-- **Github 地址**:
-- **Star**: 8.0k (1,381 stars this month)
-- **介绍**: 针对分布式系统的应用性能监控,尤其是针对微服务、云原生和面向容器的分布式系统架构。
-
-### 13. elasticsearch
-
-- **Github 地址** [https://github.com/elastic/elasticsearch](https://github.com/elastic/elasticsearch)
-- **Star**: 4.0k (1,068stars this month)
-- **介绍**: 开源,分布式,RESTful 搜索引擎。
-
-### 14. arthas
-
-- **Github地址**:[https://github.com/alibaba/arthas](https://github.com/alibaba/arthas)
-- **star**: 12.6 k (1,080 stars this month)
-- **介绍**: Arthas 是Alibaba开源的Java诊断工具。
-
-### 15. hutool
-
-- **Github地址**:
-- **star**: 4.5 k (1,031 stars this month)
-- **介绍**: Hutool是一个Java工具包,也只是一个工具包,它帮助我们简化每一行代码,减少每一个方法,让Java语言也可以“甜甜的”。Hutool最初是我项目中“util”包的一个整理,后来慢慢积累并加入更多非业务相关功能,并广泛学习其它开源项目精髓,经过自己整理修改,最终形成丰富的开源工具集。官网: 。
\ No newline at end of file
diff --git a/docs/github-trending/2019-5.md b/docs/github-trending/2019-5.md
deleted file mode 100644
index 1ac28b22cd7..00000000000
--- a/docs/github-trending/2019-5.md
+++ /dev/null
@@ -1,125 +0,0 @@
-以下涉及到的数据统计与 2019 年 6 月 1 日 18 点,数据来源: 。下面推荐的内容从 Java 学习文档到最热门的框架再到热门的工具应有尽有,建议收藏+在看!
-
-### 1.LeetCodeAnimation
-
-- **Github 地址**:
-- **Star**: 29.0k (11,492 stars this month)
-- **介绍**: Demonstrate all the questions on LeetCode in the form of animation.(用动画的形式呈现解LeetCode题目的思路)。
-
-### 2.CS-Notes
-
-- **Github 地址**:
-- **Star**: 64.4k (5513 stars this month)
-- **介绍**: 技术面试必备基础知识、Leetcode 题解、后端面试、Java 面试、春招、秋招、操作系统、计算机网络、系统设计。
-
-### 3.JavaGuide
-
-- **Github 地址**:
-- **Star**: 42.0k (4,442 stars this month)
-- **介绍**: 【Java 学习+面试指南】 一份涵盖大部分 Java 程序员所需要掌握的核心知识。
-
-### 4.mall
-
-- **Github 地址**: [https://github.com/macrozheng/mall](https://github.com/macrozheng/mall)
-- **star**: 14.6 k (3,086 stars this month)
-- **介绍**: mall 项目是一套电商系统,包括前台商城系统及后台管理系统,基于 SpringBoot+MyBatis 实现。 前台商城系统包含首页门户、商品推荐、商品搜索、商品展示、购物车、订单流程、会员中心、客户服务、帮助中心等模块。 后台管理系统包含商品管理、订单管理、会员管理、促销管理、运营管理、内容管理、统计报表、财务管理、权限管理、设置等模块。
-
-### 5.advanced-java
-
-- **Github 地址**:[https://github.com/doocs/advanced-java](https://github.com/doocs/advanced-java)
-- **Star**: 20.8k (2,394 stars this month)
-- **介绍**: 互联网 Java 工程师进阶知识完全扫盲。
-
-### 6.spring-boot
-
-- **Github地址**: [https://github.com/spring-projects/spring-boot](https://github.com/spring-projects/spring-boot)
-- **star:** 38.5k (1,339 stars this month)
-- **介绍**: 虽然Spring的组件代码是轻量级的,但它的配置却是重量级的(需要大量XML配置),不过Spring Boot 让这一切成为了过去。 另外Spring Cloud也是基于Spring Boot构建的,我个人非常有必要学习一下。
-
-**Spring Boot官方的介绍:**
-
-> Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”…Most Spring Boot applications need very little Spring configuration.(Spring Boot可以轻松创建独立的生产级基于Spring的应用程序,只要通过 “just run”(可能是run ‘Application’或java -jar 或 tomcat 或 maven插件run 或 shell脚本)便可以运行项目。大部分Spring Boot项目只需要少量的配置即可)
-
-### 7. Java
-
-- **Github 地址**:
-- **Star**:14.3k (1,334 stars this month)
-- **介绍**: All Algorithms implemented in Java。
-
-### 8.server
-
-- **Github 地址**:
-- **star**: 2.2 k (1,275 stars this month)
-- **介绍**: 全开源即时通讯(IM)系统。
-
-### 9.litemall
-
-- **Github 地址**:
-- **Star**: 7.1k (1,114 stars this month)
-- **介绍**: 又一个小商城。litemall = Spring Boot后端 + Vue管理员前端 + 微信小程序用户前端 + Vue用户移动端。
-
-### 10.Linkage-RecyclerView
-
-- **Github 地址**:
-- **Star**: 10.0k (1,093 stars this month)
-- **介绍**: 即使不用饿了么订餐,也请务必收藏好该库!🔥 一行代码即可接入,二级联动订餐列表 - Even if you don't order food by PrubHub, be sure to collect this library, please! 🔥 This secondary linkage list widget can be accessed by only one line of code. Supporting by RecyclerView & AndroidX.
-
-### 11.toBeTopJavaer
-
-- **Github 地址** :
-- **Star**: 3.3k (1,007 stars this month)
-- **介绍**: To Be Top Javaer - Java工程师成神之路
-
-### 12.elasticsearch
-
-- **Github 地址** : [https://github.com/elastic/elasticsearch](https://github.com/elastic/elasticsearch)
-- **Star**: 48.0k (968 stars this month)
-- **介绍**: Design patterns implemented in Java。
-
-### 13.java-design-patterns
-
-- **Github 地址** :
-- **Star**: 41.5k (955 stars this month)
-- **介绍**: 开源,分布式,RESTful 搜索引擎。
-
-### 14.apollo
-
-- **Github 地址** :
-- **Star**: 14.5k (927 stars this month)
-- **介绍**: Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性,适用于微服务配置管理场景。
-
-### 15.arthas
-
-- **Github地址**:[https://github.com/alibaba/arthas](https://github.com/alibaba/arthas)
-- **star**: 13.5 k (933 stars this month)
-- **介绍**: Arthas 是Alibaba开源的Java诊断工具。
-
-### 16.dubbo
-
-- **Github地址**:
-- **star**: 26.9 k (769 stars this month)
-- **介绍**: Apache Dubbo是一个基于Java的高性能开源RPC框架。
-
-### 17.DoraemonKit
-
-- **Github地址**:
-- **Star**: 8.5k (909 stars this month)
-- **介绍**: 简称 "DoKit" 。一款功能齐全的客户端( iOS 、Android )研发助手,你值得拥有。
-
-### 18.halo
-
-- **Github地址**:
-- **Star**: 4.1k (829 stars this month)
-- **介绍**: Halo 可能是最好的 Java 博客系统。
-
-### 19.seata
-
-- **Github 地址** : [https://github.com/seata/seata](https://github.com/seata/seata)
-- **star**: 9.2 k (776 stars this month)
-- **介绍**: Seata 是一种易于使用,高性能,基于 Java 的开源分布式事务解决方案。
-
-### 20.hutool
-
-- **Github地址**:
-- **star**: 5,3 k (812 stars this month)
-- **介绍**: Hutool是一个Java工具包,也只是一个工具包,它帮助我们简化每一行代码,减少每一个方法,让Java语言也可以“甜甜的”。Hutool最初是我项目中“util”包的一个整理,后来慢慢积累并加入更多非业务相关功能,并广泛学习其它开源项目精髓,经过自己整理修改,最终形成丰富的开源工具集。官网: 。
\ No newline at end of file
diff --git a/docs/github-trending/2019-6.md b/docs/github-trending/2019-6.md
deleted file mode 100644
index 2a395e160d6..00000000000
--- a/docs/github-trending/2019-6.md
+++ /dev/null
@@ -1,119 +0,0 @@
-### 1.CS-Notes
-
-- **Github 地址**:https://github.com/CyC2018/CS-Notes
-- **Star**: 69.8k
-- **介绍**: 技术面试必备基础知识、Leetcode 题解、后端面试、Java 面试、春招、秋招、操作系统、计算机网络、系统设计。
-
-### 2.toBeTopJavaer
-
-- **Github 地址:**[https://github.com/hollischuang/toBeTopJavaer](https://github.com/hollischuang/toBeTopJavaer)
-- **Star**: 4.7k
-- **介绍**: To Be Top Javaer - Java工程师成神之路。
-
-### 3.p3c
-
-- **Github 地址:** [https://github.com/alibaba/p3c](https://github.com/alibaba/p3c)
-- **Star**: 16.6k
-- **介绍**: Alibaba Java Coding Guidelines pmd implements and IDE plugin。Eclipse 和 IDEA 上都有该插件,推荐使用!
-
-### 4.SpringCloudLearning
-
-- **Github 地址:** [https://github.com/forezp/SpringCloudLearning](https://github.com/forezp/SpringCloudLearning)
-- **Star**: 8.7k
-- **介绍**: 史上最简单的Spring Cloud教程源码。
-
-### 5.dubbo
-
-- **Github地址**:
-- **star**: 27.6 k
-- **介绍**: Apache Dubbo是一个基于Java的高性能开源RPC框架。
-
-### 6.jeecg-boot
-
-- **Github地址**: [https://github.com/zhangdaiscott/jeecg-boot](https://github.com/zhangdaiscott/jeecg-boot)
-- **star**: 3.3 k
-- **介绍**: 一款基于代码生成器的JAVA快速开发平台!全新架构前后端分离:SpringBoot 2.x,Ant Design&Vue,Mybatis,Shiro,JWT。强大的代码生成器让前后端代码一键生成,无需写任何代码,绝对是全栈开发福音!! JeecgBoot的宗旨是提高UI能力的同时,降低前后分离的开发成本,JeecgBoot还独创在线开发模式,No代码概念,一系列在线智能开发:在线配置表单、在线配置报表、在线设计流程等等。
-
-### 7.advanced-java
-
-- **Github 地址**:[https://github.com/doocs/advanced-java](https://github.com/doocs/advanced-java)
-- **Star**: 24.2k
-- **介绍**: 互联网 Java 工程师进阶知识完全扫盲:涵盖高并发、分布式、高可用、微服务等领域知识,后端同学必看,前端同学也可学习。
-
-### 8.FEBS-Shiro
-
-- **Github 地址**:[https://github.com/wuyouzhuguli/FEBS-Shiro](https://github.com/wuyouzhuguli/FEBS-Shiro)
-- **Star**: 2.6k
-- **介绍**: Spring Boot 2.1.3,Shiro1.4.0 & Layui 2.5.4 权限管理系统。预览地址:http://49.234.20.223:8080/login。
-
-### 9.SpringAll
-
-- **Github 地址**: [https://github.com/wuyouzhuguli/SpringAll](https://github.com/wuyouzhuguli/SpringAll)
-- **Star**: 5.4k
-- **介绍**: 循序渐进,学习Spring Boot、Spring Boot & Shiro、Spring Cloud、Spring Security & Spring Security OAuth2,博客Spring系列源码。
-
-### 10.JavaGuide
-
-- **Github 地址**:
-- **Star**: 47.2k
-- **介绍**: 【Java 学习+面试指南】 一份涵盖大部分 Java 程序员所需要掌握的核心知识。
-
-### 11.vhr
-
-- **Github 地址**:[https://github.com/lenve/vhr](https://github.com/lenve/vhr)
-- **Star**: 4.9k
-- **介绍**: 微人事是一个前后端分离的人力资源管理系统,项目采用SpringBoot+Vue开发。
-
-### 12. tutorials
-
-- **Github 地址**:[https://github.com/eugenp/tutorials](https://github.com/eugenp/tutorials)
-- **star**: 15.4 k
-- **介绍**: 该项目是一系列小而专注的教程 - 每个教程都涵盖 Java 生态系统中单一且定义明确的开发领域。 当然,它们的重点是 Spring Framework - Spring,Spring Boot 和 Spring Securiyt。 除了 Spring 之外,还有以下技术:核心 Java,Jackson,HttpClient,Guava。
-
-### 13.EasyScheduler
-
-- **Github 地址**:[https://github.com/analysys/EasyScheduler](https://github.com/analysys/EasyScheduler)
-- **star**: 1.1 k
-- **介绍**: Easy Scheduler是一个分布式工作流任务调度系统,主要解决“复杂任务依赖但无法直接监控任务健康状态”的问题。Easy Scheduler以DAG方式组装任务,可以实时监控任务的运行状态。同时,它支持重试,重新运行等操作... 。https://analysys.github.io/easyscheduler_docs_cn/
-
-### 14.thingsboard
-
-- **Github 地址**:[https://github.com/thingsboard/thingsboard](https://github.com/thingsboard/thingsboard)
-- **star**: 3.7 k
-- **介绍**: 开源物联网平台 - 设备管理,数据收集,处理和可视化。 [https://thingsboard.io](https://thingsboard.io/)
-
-### 15.mall-learning
-
-- **Github 地址**: [https://github.com/macrozheng/mall-learning](https://github.com/macrozheng/mall-learning)
-- **star**: 0.6 k
-- **介绍**: mall学习教程,架构、业务、技术要点全方位解析。mall项目(16k+star)是一套电商系统,使用现阶段主流技术实现。 涵盖了SpringBoot2.1.3、MyBatis3.4.6、Elasticsearch6.2.2、RabbitMQ3.7.15、Redis3.2、Mongodb3.2、Mysql5.7等技术,采用Docker容器化部署。 https://github.com/macrozheng/mall
-
-### 16. flink
-
-- **Github地址**:[https://github.com/apache/flink](https://github.com/apache/flink)
-- **star**: 9.3 k
-- **介绍**: Apache Flink是一个开源流处理框架,具有强大的流和批处理功能。
-
-### 17.spring-cloud-kubernetes
-
-- **Github地址**:[https://github.com/spring-cloud/spring-cloud-kubernetes](https://github.com/spring-cloud/spring-cloud-kubernetes)
-- **star**: 1.4 k
-- **介绍**: Kubernetes 集成 Spring Cloud Discovery Client, Configuration, etc...
-
-### 18.springboot-learning-example
-
-- **Github地址**:[https://github.com/JeffLi1993/springboot-learning-example](https://github.com/JeffLi1993/springboot-learning-example)
-- **star**: 10.0 k
-- **介绍**: spring boot 实践学习案例,是 spring boot 初学者及核心技术巩固的最佳实践。
-
-### 19.canal
-
-- **Github地址**:[https://github.com/alibaba/canal](https://github.com/alibaba/canal)
-- **star**: 9.3 k
-- **介绍**: 阿里巴巴 MySQL binlog 增量订阅&消费组件。
-
-### 20.react-native-device-info
-
-- **Github地址**:[https://github.com/react-native-community/react-native-device-info](https://github.com/react-native-community/react-native-device-info)
-- **star**: 4.0 k
-- **介绍**: React Native iOS和Android的设备信息。
\ No newline at end of file
diff --git a/docs/github-trending/JavaGithubTrending.md b/docs/github-trending/JavaGithubTrending.md
deleted file mode 100644
index 91d544ed37e..00000000000
--- a/docs/github-trending/JavaGithubTrending.md
+++ /dev/null
@@ -1,8 +0,0 @@
-- [2018 年 12 月](https://github.com/Snailclimb/JavaGuide/blob/master/docs/github-trending/2018-12.md)
-- [2019 年 1 月](https://github.com/Snailclimb/JavaGuide/blob/master/docs/github-trending/2019-1.md)
-- [2019 年 2 月](https://github.com/Snailclimb/JavaGuide/blob/master/docs/github-trending/2019-2.md)
-- [2019 年 3 月](https://github.com/Snailclimb/JavaGuide/blob/master/docs/github-trending/2019-3.md)
-- [2019 年 4 月](https://github.com/Snailclimb/JavaGuide/blob/master/docs/github-trending/2019-4.md)
-- [2019 年 5 月](https://github.com/Snailclimb/JavaGuide/blob/master/docs/github-trending/2019-5.md)
-- [2019 年 6 月](https://github.com/Snailclimb/JavaGuide/blob/master/docs/github-trending/2019-6.md)
-
diff --git a/docs/high-availability/limit-request.md b/docs/high-availability/limit-request.md
new file mode 100644
index 00000000000..1c611e55a41
--- /dev/null
+++ b/docs/high-availability/limit-request.md
@@ -0,0 +1,203 @@
+# 限流
+
+## 何为限流?为什么要限流?
+
+针对软件系统来说,限流就是对请求的速率进行限制,避免瞬时的大量请求击垮软件系统。毕竟,软件系统的处理能力是有限的。如果说超过了其处理能力的范围,软件系统可能直接就挂掉了。
+
+限流可能会导致用户的请求无法被正确处理,不过,这往往也是权衡了软件系统的稳定性之后得到的最优解。
+
+现实生活中,处处都有限流的实际应用,就比如排队买票是为了避免大量用户涌入购票而导致售票员无法处理。
+
+
+
+## 常见限流算法
+
+简单介绍 4 种非常好理解并且容易实现的限流算法!
+
+> 图片来源于 InfoQ 的一篇文章[《分布式服务限流实战,已经为你排好坑了》](https://www.infoq.cn/article/Qg2tX8fyw5Vt-f3HH673)。
+
+### 固定窗口计数器算法
+
+固定窗口其实就是时间窗口。**固定窗口计数器算法** 规定了我们单位时间处理的请求数量。
+
+假如我们规定系统中某个接口 1 分钟只能访问 33 次的话,使用固定窗口计数器算法的实现思路如下:
+
+- 给定一个变量 `counter` 来记录当前接口处理的请求数量,初始值为 0(代表接口当前 1 分钟内还未处理请求)。
+- 1 分钟之内每处理一个请求之后就将 `counter+1` ,当 `counter=33` 之后(也就是说在这 1 分钟内接口已经被访问 33 次的话),后续的请求就会被全部拒绝。
+- 等到 1 分钟结束后,将 `counter` 重置 0,重新开始计数。
+
+**这种限流算法无法保证限流速率,因而无法保证突然激增的流量。**
+
+就比如说我们限制某个接口 1 分钟只能访问 1000 次,该接口的 QPS 为 500,前 55s 这个接口 1 个请求没有接收,后 1s 突然接收了 1000 个请求。然后,在当前场景下,这 1000 个请求在 1s 内是没办法被处理的,系统直接就被瞬时的大量请求给击垮了。
+
+
+
+### 滑动窗口计数器算法
+
+**滑动窗口计数器算法** 算的上是固定窗口计数器算法的升级版。
+
+滑动窗口计数器算法相比于固定窗口计数器算法的优化在于:**它把时间以一定比例分片** 。
+
+例如我们的借口限流每分钟处理 60 个请求,我们可以把 1 分钟分为 60 个窗口。每隔 1 秒移动一次,每个窗口一秒只能处理 不大于 `60(请求数)/60(窗口数)` 的请求, 如果当前窗口的请求计数总和超过了限制的数量的话就不再处理其他请求。
+
+很显然, **当滑动窗口的格子划分的越多,滑动窗口的滚动就越平滑,限流的统计就会越精确。**
+
+
+
+### 漏桶算法
+
+我们可以把发请求的动作比作成注水到桶中,我们处理请求的过程可以比喻为漏桶漏水。我们往桶中以任意速率流入水,以一定速率流出水。当水超过桶流量则丢弃,因为桶容量是不变的,保证了整体的速率。
+
+如果想要实现这个算法的话也很简单,准备一个队列用来保存请求,然后我们定期从队列中拿请求来执行就好了(和消息队列削峰/限流的思想是一样的)。
+
+
+
+### 令牌桶算法
+
+令牌桶算法也比较简单。和漏桶算法算法一样,我们的主角还是桶(这限流算法和桶过不去啊)。不过现在桶里装的是令牌了,请求在被处理之前需要拿到一个令牌,请求处理完毕之后将这个令牌丢弃(删除)。我们根据限流大小,按照一定的速率往桶里添加令牌。如果桶装满了,就不能继续往里面继续添加令牌了。
+
+
+
+## 单机限流
+
+单机限流可以直接使用 Google Guava 自带的限流工具类 `RateLimiter` 。 `RateLimiter` 基于令牌桶算法,可以应对突发流量。
+
+> Guava 地址:https://github.com/google/guava
+
+除了最基本的令牌桶算法(平滑突发限流)实现之外,Guava 的`RateLimiter`还提供了 **平滑预热限流** 的算法实现。
+
+平滑突发限流就是按照指定的速率放令牌到桶里,而平滑预热限流会有一段预热时间,预热时间之内,速率会逐渐提升到配置的速率。
+
+我们下面通过两个简单的小例子来详细了解吧!
+
+我们直接在项目中引入 Guava 相关的依赖即可使用。
+
+```xml
+
+ com.google.guava
+ guava
+ 31.0.1-jre
+
+```
+
+下面是一个简单的 Guava 平滑突发限流的 Demo。
+
+```java
+import com.google.common.util.concurrent.RateLimiter;
+
+/**
+ * 微信搜 JavaGuide 回复"面试突击"即可免费领取个人原创的 Java 面试手册
+ *
+ * @author Guide哥
+ * @date 2021/10/08 19:12
+ **/
+public class RateLimiterDemo {
+
+ public static void main(String[] args) {
+ // 1s 放 5 个令牌到桶里也就是 0.2s 放 1个令牌到桶里
+ RateLimiter rateLimiter = RateLimiter.create(5);
+ for (int i = 0; i < 10; i++) {
+ double sleepingTime = rateLimiter.acquire(1);
+ System.out.printf("get 1 tokens: %ss%n", sleepingTime);
+ }
+ }
+}
+
+```
+
+输出:
+
+```bash
+get 1 tokens: 0.0s
+get 1 tokens: 0.188413s
+get 1 tokens: 0.197811s
+get 1 tokens: 0.198316s
+get 1 tokens: 0.19864s
+get 1 tokens: 0.199363s
+get 1 tokens: 0.193997s
+get 1 tokens: 0.199623s
+get 1 tokens: 0.199357s
+get 1 tokens: 0.195676s
+```
+
+下面是一个简单的 Guava 平滑预热限流的 Demo。
+
+```java
+import com.google.common.util.concurrent.RateLimiter;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 微信搜 JavaGuide 回复"面试突击"即可免费领取个人原创的 Java 面试手册
+ *
+ * @author Guide哥
+ * @date 2021/10/08 19:12
+ **/
+public class RateLimiterDemo {
+
+ public static void main(String[] args) {
+ // 1s 放 5 个令牌到桶里也就是 0.2s 放 1个令牌到桶里
+ // 预热时间为3s,也就说刚开始的 3s 内发牌速率会逐渐提升到 0.2s 放 1 个令牌到桶里
+ RateLimiter rateLimiter = RateLimiter.create(5, 3, TimeUnit.SECONDS);
+ for (int i = 0; i < 20; i++) {
+ double sleepingTime = rateLimiter.acquire(1);
+ System.out.printf("get 1 tokens: %sds%n", sleepingTime);
+ }
+ }
+}
+```
+
+输出:
+
+```bash
+get 1 tokens: 0.0s
+get 1 tokens: 0.561919s
+get 1 tokens: 0.516931s
+get 1 tokens: 0.463798s
+get 1 tokens: 0.41286s
+get 1 tokens: 0.356172s
+get 1 tokens: 0.300489s
+get 1 tokens: 0.252545s
+get 1 tokens: 0.203996s
+get 1 tokens: 0.198359s
+```
+
+另外,**Bucket4j** 是一个非常不错的基于令牌/漏桶算法的限流库。
+
+> Bucket4j 地址:https://github.com/vladimir-bukhtoyarov/bucket4j
+
+相对于,Guava 的限流工具类来说,Bucket4j 提供的限流功能更加全面。不仅支持单机限流和分布式限流,还可以集成监控,搭配 Prometheus 和 Grafana 使用。
+
+不过,毕竟 Guava 也只是一个功能全面的工具类库,其提供的开箱即用的限流功能在很多单机场景下还是比较实用的。
+
+Spring Cloud Gateway 中自带的单机限流的早期版本就是基于 Bucket4j 实现的。后来,替换成了 **Resilience4j**。
+
+Resilience4j 是一个轻量级的容错组件,其灵感来自于 Hystrix。自[Netflix 宣布不再积极开发 Hystrix](https://github.com/Netflix/Hystrix/commit/a7df971cbaddd8c5e976b3cc5f14013fe6ad00e6) 之后,Spring 官方和 Netflix 都更推荐使用 Resilience4j 来做限流熔断。
+
+> Resilience4j 地址: https://github.com/resilience4j/resilience4j
+
+一般情况下,为了保证系统的高可用,项目的限流和熔断都是要一起做的。
+
+Resilience4j 不仅提供限流,还提供了熔断、负载保护、自动重试等保障系统高可用开箱即用的功能。并且,Resilience4j 的生态也更好,很多网关都使用 Resilience4j 来做限流熔断的。
+
+因此,在绝大部分场景下 Resilience4j 或许会是更好的选择。如果是一些比较简单的限流场景的话,Guava 或者 Bucket4j 也是不错的选择。
+
+## 分布式限流
+
+分布式限流常见的方案:
+
+- **借助中间件架限流** :可以借助 Sentinel 或者使用 Redis 来自己实现对应的限流逻辑。
+- **网关层限流** :比较常用的一种方案,直接在网关层把限流给安排上了。不过,通常网关层限流通常也需要借助到中间件/框架。就比如 Spring Cloud Gateway 的分布式限流实现`RedisRateLimiter`就是基于 Redis+Lua 来实现的,再比如 Spring Cloud Gateway 还可以整合 Sentinel 来做限流。
+
+如果你要基于 Redis 来手动实现限流逻辑的话,建议配合 Lua 脚本来做。
+
+网上也有很多现成的脚本供你参考,就比如 Apache 网关项目 ShenYu 的 RateLimiter 限流插件就基于 Redis + Lua 实现了令牌桶算法/并发令牌桶算法、漏桶算法、滑动窗口算法。
+
+> ShenYu 地址: https://github.com/apache/incubator-shenyu
+
+
+
+## 相关阅读
+
+- 服务治理之轻量级熔断框架 Resilience4j :https://xie.infoq.cn/article/14786e571c1a4143ad1ef8f19
+- 超详细的 Guava RateLimiter 限流原理解析:https://cloud.tencent.com/developer/article/1408819
+- 实战 Spring Cloud Gateway 之限流篇 👍:https://www.aneasystone.com/archives/2020/08/spring-cloud-gateway-current-limiting.html
diff --git "a/docs/high-availability/\346\200\247\350\203\275\346\265\213\350\257\225.md" "b/docs/high-availability/\346\200\247\350\203\275\346\265\213\350\257\225.md"
new file mode 100644
index 00000000000..dc3ff9ba749
--- /dev/null
+++ "b/docs/high-availability/\346\200\247\350\203\275\346\265\213\350\257\225.md"
@@ -0,0 +1,150 @@
+# 性能测试入门
+
+性能测试一般情况下都是由测试这个职位去做的,那还需要我们开发学这个干嘛呢?了解性能测试的指标、分类以及工具等知识有助于我们更好地去写出性能更好的程序,另外作为开发这个角色,如果你会性能测试的话,相信也会为你的履历加分不少。
+
+这篇文章是我会结合自己的实际经历以及在测试这里取的经所得,除此之外,我还借鉴了一些优秀书籍,希望对你有帮助。
+
+本文思维导图:
+
+
+
+## 一 不同角色看网站性能
+
+### 1.1 用户
+
+当用户打开一个网站的时候,最关注的是什么?当然是网站响应速度的快慢。比如我们点击了淘宝的主页,淘宝需要多久将首页的内容呈现在我的面前,我点击了提交订单按钮需要多久返回结果等等。
+
+所以,用户在体验我们系统的时候往往根据你的响应速度的快慢来评判你的网站的性能。
+
+### 1.2 开发人员
+
+用户与开发人员都关注速度,这个速度实际上就是我们的系统**处理用户请求的速度**。
+
+开发人员一般情况下很难直观的去评判自己网站的性能,我们往往会根据网站当前的架构以及基础设施情况给一个大概的值,比如:
+
+1. 项目架构是分布式的吗?
+2. 用到了缓存和消息队列没有?
+3. 高并发的业务有没有特殊处理?
+4. 数据库设计是否合理?
+5. 系统用到的算法是否还需要优化?
+6. 系统是否存在内存泄露的问题?
+7. 项目使用的 Redis 缓存多大?服务器性能如何?用的是机械硬盘还是固态硬盘?
+8. ......
+
+### 1.3 测试人员
+
+测试人员一般会根据性能测试工具来测试,然后一般会做出一个表格。这个表格可能会涵盖下面这些重要的内容:
+
+1. 响应时间;
+2. 请求成功率;
+3. 吞吐量;
+4. ......
+
+### 1.4 运维人员
+
+运维人员会倾向于根据基础设施和资源的利用率来判断网站的性能,比如我们的服务器资源使用是否合理、数据库资源是否存在滥用的情况、当然,这是传统的运维人员,现在 Devpos 火起来后,单纯干运维的很少了。我们这里暂且还保留有这个角色。
+
+## 二 性能测试需要注意的点
+
+几乎没有文章在讲性能测试的时候提到这个问题,大家都会讲如何去性能测试,有哪些性能测试指标这些东西。
+
+### 2.1 了解系统的业务场景
+
+**性能测试之前更需要你了解当前的系统的业务场景。** 对系统业务了解的不够深刻,我们很容易犯测试方向偏执的错误,从而导致我们忽略了对系统某些更需要性能测试的地方进行测试。比如我们的系统可以为用户提供发送邮件的功能,用户配置成功邮箱后只需输入相应的邮箱之后就能发送,系统每天大概能处理上万次发邮件的请求。很多人看到这个可能就直接开始使用相关工具测试邮箱发送接口,但是,发送邮件这个场景可能不是当前系统的性能瓶颈,这么多人用我们的系统发邮件, 还可能有很多人一起发邮件,单单这个场景就这么人用,那用户管理可能才是性能瓶颈吧!
+
+### 2.2 历史数据非常有用
+
+当前系统所留下的历史数据非常重要,一般情况下,我们可以通过相应的些历史数据初步判定这个系统哪些接口调用的比较多、哪些 service 承受的压力最大,这样的话,我们就可以针对这些地方进行更细致的性能测试与分析。
+
+另外,这些地方也就像这个系统的一个短板一样,优化好了这些地方会为我们的系统带来质的提升。
+
+### 三 性能测试的指标
+
+### 3.1 响应时间
+
+**响应时间就是用户发出请求到用户收到系统处理结果所需要的时间。** 重要吗?实在太重要!
+
+比较出名的 2-5-8 原则是这样描述的:通常来说,2到5秒,页面体验会比较好,5到8秒还可以接受,8秒以上基本就很难接受了。另外,据统计当网站慢一秒就会流失十分之一的客户。
+
+但是,在某些场景下我们也并不需要太看重 2-5-8 原则 ,比如我觉得系统导出导入大数据量这种就不需要,系统生成系统报告这种也不需要。
+
+### 3.2 并发数
+
+**并发数是系统能同时处理请求的数目即同时提交请求的用户数目。**
+
+不得不说,高并发是现在后端架构中非常非常火热的一个词了,这个与当前的互联网环境以及中国整体的互联网用户量都有很大关系。一般情况下,你的系统并发量越大,说明你的产品做的就越大。但是,并不是每个系统都需要达到像淘宝、12306 这种亿级并发量的。
+
+### 3.3 吞吐量
+
+吞吐量指的是系统单位时间内系统处理的请求数量。衡量吞吐量有几个重要的参数:QPS(TPS)、并发数、响应时间。
+
+1. QPS(Query Per Second):服务器每秒可以执行的查询次数;
+2. TPS(Transaction Per Second):服务器每秒处理的事务数(这里的一个事务可以理解为客户发出请求到收到服务器的过程);
+3. 并发数;系统能同时处理请求的数目即同时提交请求的用户数目。
+4. 响应时间: 一般取多次请求的平均响应时间
+
+理清他们的概念,就很容易搞清楚他们之间的关系了。
+
+- **QPS(TPS)** = 并发数/平均响应时间
+- **并发数** = QPS\平均响应时间
+
+书中是这样描述 QPS 和 TPS 的区别的。
+
+> QPS vs TPS:QPS 基本类似于 TPS,但是不同的是,对于一个页面的一次访问,形成一个TPS;但一次页面请求,可能产生多次对服务器的请求,服务器对这些请求,就可计入“QPS”之中。如,访问一个页面会请求服务器2次,一次访问,产生一个“T”,产生2个“Q”。
+
+### 3.4 性能计数器
+
+**性能计数器是描述服务器或者操作系统的一些数据指标如内存使用、CPU使用、磁盘与网络I/O等情况。**
+
+### 四 几种常见的性能测试
+
+### 性能测试
+
+性能测试方法是通过测试工具模拟用户请求系统,目的主要是为了测试系统的性能是否满足要求。通俗地说,这种方法就是要在特定的运行条件下验证系统的能力状态。
+
+性能测试是你在对系统性能已经有了解的前提之后进行的,并且有明确的性能指标。
+
+### 负载测试
+
+对被测试的系统继续加大请求压力,直到服务器的某个资源已经达到饱和了,比如系统的缓存已经不够用了或者系统的响应时间已经不满足要求了。
+
+负载测试说白点就是测试系统的上线。
+
+### 压力测试
+
+不去管系统资源的使用情况,对系统继续加大请求压力,直到服务器崩溃无法再继续提供服务。
+
+### 稳定性测试
+
+模拟真实场景,给系统一定压力,看看业务是否能稳定运行。
+
+## 五 常用性能测试工具
+
+这里就不多扩展了,有时间的话会单独拎一个熟悉的说一下。
+
+### 5.1 后端常用
+
+没记错的话,除了 LoadRunner 其他几款性能测试工具都是开源免费的。
+
+1. Jmeter :Apache JMeter 是 JAVA 开发的性能测试工具。
+2. LoadRunner:一款商业的性能测试工具。
+3. Galtling :一款基于Scala 开发的高性能服务器性能测试工具。
+4. ab :全称为 Apache Bench 。Apache 旗下的一款测试工具,非常实用。
+
+### 5.2 前端常用
+
+1. Fiddler:抓包工具,它可以修改请求的数据,甚至可以修改服务器返回的数据,功能非常强大,是Web 调试的利器。
+2. HttpWatch: 可用于录制HTTP请求信息的工具。
+
+## 六 常见的性能优化策略
+
+性能优化之前我们需要对请求经历的各个环节进行分析,排查出可能出现性能瓶颈的地方,定位问题。
+
+下面是一些性能优化时,我经常拿来自问的一些问题:
+
+1. 系统是否需要缓存?
+2. 系统架构本身是不是就有问题?
+3. 系统是否存在死锁的地方?
+4. 系统是否存在内存泄漏?(Java 的自动回收内存虽然很方便,但是,有时候代码写的不好真的会造成内存泄漏)
+5. 数据库索引使用是否合理?
+6. ......
\ No newline at end of file
diff --git "a/docs/high-availability/\347\201\276\345\244\207\350\256\276\350\256\241\345\222\214\345\274\202\345\234\260\345\244\232\346\264\273.md" "b/docs/high-availability/\347\201\276\345\244\207\350\256\276\350\256\241\345\222\214\345\274\202\345\234\260\345\244\232\346\264\273.md"
new file mode 100644
index 00000000000..18756b69127
--- /dev/null
+++ "b/docs/high-availability/\347\201\276\345\244\207\350\256\276\350\256\241\345\222\214\345\274\202\345\234\260\345\244\232\346\264\273.md"
@@ -0,0 +1,14 @@
+# 灾备设计&异地多活
+
+**灾备** = 容灾+备份。
+
+- **备份** : 将系统所产生的所有重要数据多备份几份。
+- **容灾** : 在异地建立两个完全相同的系统。当某个地方的系统突然挂掉,整个应用系统可以切换到另一个,这样系统就可以正常提供服务了。
+
+**异地多活** 描述的是将服务部署在异地并且服务同时对外提供服务。和传统的灾备设计的最主要区别在于“多活”,即所有站点都是同时在对外提供服务的。异地多活是为了应对突发状况比如火灾、地震等自然或者人为灾害。
+
+相关阅读:
+
+- [搞懂异地多活,看这篇就够了](https://mp.weixin.qq.com/s/T6mMDdtTfBuIiEowCpqu6Q)
+- [四步构建异地多活](https://mp.weixin.qq.com/s/hMD-IS__4JE5_nQhYPYSTg)
+- [《从零开始学架构》— 28 | 业务高可用的保障:异地多活架构](http://gk.link/a/10pKZ)
\ No newline at end of file
diff --git "a/docs/high-availability/\350\266\205\346\227\266\345\222\214\351\207\215\350\257\225\346\234\272\345\210\266.md" "b/docs/high-availability/\350\266\205\346\227\266\345\222\214\351\207\215\350\257\225\346\234\272\345\210\266.md"
new file mode 100644
index 00000000000..ee4f90f2056
--- /dev/null
+++ "b/docs/high-availability/\350\266\205\346\227\266\345\222\214\351\207\215\350\257\225\346\234\272\345\210\266.md"
@@ -0,0 +1,5 @@
+# 超时&重试机制
+
+**一旦用户的请求超过某个时间得不到响应就结束此次请求并抛出异常。** 如果不进行超时设置可能会导致请求响应速度慢,甚至导致请求堆积进而让系统无法再处理请求。
+
+另外,重试的次数一般设为 3 次,再多次的重试没有好处,反而会加重服务器压力(部分场景使用失败重试机制会不太适合)。
\ No newline at end of file
diff --git "a/docs/high-availability/\351\231\215\347\272\247&\347\206\224\346\226\255.md" "b/docs/high-availability/\351\231\215\347\272\247&\347\206\224\346\226\255.md"
new file mode 100644
index 00000000000..2ff7b922893
--- /dev/null
+++ "b/docs/high-availability/\351\231\215\347\272\247&\347\206\224\346\226\255.md"
@@ -0,0 +1,9 @@
+# 降级&熔断
+
+降级是从系统功能优先级的角度考虑如何应对系统故障。
+
+服务降级指的是当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务和页面有策略的降级,以此释放服务器资源以保证核心任务的正常运行。
+
+熔断和降级是两个比较容易混淆的概念,两者的含义并不相同。
+
+降级的目的在于应对系统自身的故障,而熔断的目的在于应对当前系统依赖的外部系统或者第三方系统的故障。
\ No newline at end of file
diff --git "a/docs/high-availability/\351\233\206\347\276\244.md" "b/docs/high-availability/\351\233\206\347\276\244.md"
new file mode 100644
index 00000000000..5da34020f32
--- /dev/null
+++ "b/docs/high-availability/\351\233\206\347\276\244.md"
@@ -0,0 +1,3 @@
+# 集群
+
+相同的服务部署多份,避免单点故障。
\ No newline at end of file
diff --git "a/docs/high-availability/\351\253\230\345\217\257\347\224\250\347\263\273\347\273\237\350\256\276\350\256\241.md" "b/docs/high-availability/\351\253\230\345\217\257\347\224\250\347\263\273\347\273\237\350\256\276\350\256\241.md"
new file mode 100644
index 00000000000..e336f676251
--- /dev/null
+++ "b/docs/high-availability/\351\253\230\345\217\257\347\224\250\347\263\273\347\273\237\350\256\276\350\256\241.md"
@@ -0,0 +1,70 @@
+# 高可用系统设计
+
+一篇短小的文章,面试经常遇到的这个问题。本文主要包括下面这些内容:
+
+1. 高可用的定义
+2. 哪些情况可能会导致系统不可用?
+3. 有哪些提高系统可用性的方法?只是简单的提一嘴,更具体内容在后续的文章中介绍,就拿限流来说,你需要搞懂:何为限流?如何限流?为什么要限流?如何做呢?说一下原理?。
+
+## 什么是高可用?可用性的判断标准是啥?
+
+**高可用描述的是一个系统在大部分时间都是可用的,可以为我们提供服务的。高可用代表系统即使在发生硬件故障或者系统升级的时候,服务仍然是可用的。**
+
+**一般情况下,我们使用多少个 9 来评判一个系统的可用性,比如 99.9999% 就是代表该系统在所有的运行时间中只有 0.0001% 的时间是不可用的,这样的系统就是非常非常高可用的了!当然,也会有系统如果可用性不太好的话,可能连 9 都上不了。**
+
+除此之外,系统的可用性还可以用某功能的失败次数与总的请求次数之比来衡量,比如对网站请求 1000 次,其中有 10 次请求失败,那么可用性就是 99%。
+
+## 哪些情况会导致系统不可用?
+
+1. 黑客攻击;
+2. 硬件故障,比如服务器坏掉。
+3. 并发量/用户请求量激增导致整个服务宕掉或者部分服务不可用。
+4. 代码中的坏味道导致内存泄漏或者其他问题导致程序挂掉。
+5. 网站架构某个重要的角色比如 Nginx 或者数据库突然不可用。
+6. 自然灾害或者人为破坏。
+7. ......
+
+## 有哪些提高系统可用性的方法?
+
+### 1. 注重代码质量,测试严格把关
+
+我觉得这个是最最最重要的,代码质量有问题比如比较常见的内存泄漏、循环依赖都是对系统可用性极大的损害。大家都喜欢谈限流、降级、熔断,但是我觉得从代码质量这个源头把关是首先要做好的一件很重要的事情。如何提高代码质量?比较实际可用的就是 CodeReview,不要在乎每天多花的那 1 个小时左右的时间,作用可大着呢!
+
+另外,安利这个对提高代码质量有实际效果的宝贝:
+
+1. sonarqube :保证你写出更安全更干净的代码!(ps: 目前所在的项目基本都会用到这个插件)。
+2. Alibaba 开源的 Java 诊断工具 Arthas 也是很不错的选择。
+3. IDEA 自带的代码分析等工具进行代码扫描也是非常非常棒的。
+
+### 2.使用集群,减少单点故障
+
+先拿常用的 Redis 举个例子!我们如何保证我们的 Redis 缓存高可用呢?答案就是使用集群,避免单点故障。当我们使用一个 Redis 实例作为缓存的时候,这个 Redis 实例挂了之后,整个缓存服务可能就挂了。使用了集群之后,即使一台 Redis 实例挂了,不到一秒就会有另外一台 Redis 实例顶上。
+
+### 3.限流
+
+流量控制(flow control),其原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。——来自 alibaba-[Sentinel](https://github.com/alibaba/Sentinel "Sentinel") 的 wiki。
+
+### 4.超时和重试机制设置
+
+一旦用户请求超过某个时间的得不到响应,就抛出异常。这个是非常重要的,很多线上系统故障都是因为没有进行超时设置或者超时设置的方式不对导致的。我们在读取第三方服务的时候,尤其适合设置超时和重试机制。一般我们使用一些 RPC 框架的时候,这些框架都自带的超时重试的配置。如果不进行超时设置可能会导致请求响应速度慢,甚至导致请求堆积进而让系统无法再处理请求。重试的次数一般设为 3 次,再多次的重试没有好处,反而会加重服务器压力(部分场景使用失败重试机制会不太适合)。
+
+### 5.熔断机制
+
+超时和重试机制设置之外,熔断机制也是很重要的。 熔断机制说的是系统自动收集所依赖服务的资源使用情况和性能指标,当所依赖的服务恶化或者调用失败次数达到某个阈值的时候就迅速失败,让当前系统立即切换依赖其他备用服务。 比较常用的流量控制和熔断降级框架是 Netflix 的 Hystrix 和 alibaba 的 Sentinel。
+
+### 6.异步调用
+
+异步调用的话我们不需要关心最后的结果,这样我们就可以用户请求完成之后就立即返回结果,具体处理我们可以后续再做,秒杀场景用这个还是蛮多的。但是,使用异步之后我们可能需要 **适当修改业务流程进行配合**,比如**用户在提交订单之后,不能立即返回用户订单提交成功,需要在消息队列的订单消费者进程真正处理完该订单之后,甚至出库后,再通过电子邮件或短信通知用户订单成功**。除了可以在程序中实现异步之外,我们常常还使用消息队列,消息队列可以通过异步处理提高系统性能(削峰、减少响应所需时间)并且可以降低系统耦合性。
+
+### 7.使用缓存
+
+如果我们的系统属于并发量比较高的话,如果我们单纯使用数据库的话,当大量请求直接落到数据库可能数据库就会直接挂掉。使用缓存缓存热点数据,因为缓存存储在内存中,所以速度相当地快!
+
+### 8.其他
+
+1. **核心应用和服务优先使用更好的硬件**
+2. **监控系统资源使用情况增加报警设置。**
+3. **注意备份,必要时候回滚。**
+4. **灰度发布:** 将服务器集群分成若干部分,每天只发布一部分机器,观察运行稳定没有故障,第二天继续发布一部分机器,持续几天才把整个集群全部发布完毕,期间如果发现问题,只需要回滚已发布的一部分服务器即可
+5. **定期检查/更换硬件:** 如果不是购买的云服务的话,定期还是需要对硬件进行一波检查的,对于一些需要更换或者升级的硬件,要及时更换或者升级。
+6. .....(想起来再补充!也欢迎各位欢迎补充!)
diff --git "a/docs/high-performance/message-queue/kafka\347\237\245\350\257\206\347\202\271&\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" "b/docs/high-performance/message-queue/kafka\347\237\245\350\257\206\347\202\271&\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md"
new file mode 100644
index 00000000000..8cea44f2225
--- /dev/null
+++ "b/docs/high-performance/message-queue/kafka\347\237\245\350\257\206\347\202\271&\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md"
@@ -0,0 +1,222 @@
+
+# Kafka知识点&面试题总结
+
+### Kafka 是什么?主要应用场景有哪些?
+
+Kafka 是一个分布式流式处理平台。这到底是什么意思呢?
+
+流平台具有三个关键功能:
+
+1. **消息队列**:发布和订阅消息流,这个功能类似于消息队列,这也是 Kafka 也被归类为消息队列的原因。
+2. **容错的持久方式存储记录消息流**: Kafka 会把消息持久化到磁盘,有效避免了消息丢失的风险。
+3. **流式处理平台:** 在消息发布的时候进行处理,Kafka 提供了一个完整的流式处理类库。
+
+Kafka 主要有两大应用场景:
+
+1. **消息队列** :建立实时流数据管道,以可靠地在系统或应用程序之间获取数据。
+2. **数据处理:** 构建实时的流数据处理程序来转换或处理数据流。
+
+### 和其他消息队列相比,Kafka的优势在哪里?
+
+我们现在经常提到 Kafka 的时候就已经默认它是一个非常优秀的消息队列了,我们也会经常拿它跟 RocketMQ、RabbitMQ 对比。我觉得 Kafka 相比其他消息队列主要的优势如下:
+
+1. **极致的性能** :基于 Scala 和 Java 语言开发,设计中大量使用了批量处理和异步的思想,最高可以每秒处理千万级别的消息。
+2. **生态系统兼容性无可匹敌** :Kafka 与周边生态系统的兼容性是最好的没有之一,尤其在大数据和流计算领域。
+
+实际上在早期的时候 Kafka 并不是一个合格的消息队列,早期的 Kafka 在消息队列领域就像是一个衣衫褴褛的孩子一样,功能不完备并且有一些小问题比如丢失消息、不保证消息可靠性等等。当然,这也和 LinkedIn 最早开发 Kafka 用于处理海量的日志有很大关系,哈哈哈,人家本来最开始就不是为了作为消息队列滴,谁知道后面误打误撞在消息队列领域占据了一席之地。
+
+随着后续的发展,这些短板都被 Kafka 逐步修复完善。所以,**Kafka 作为消息队列不可靠这个说法已经过时!**
+
+### 队列模型了解吗?Kafka 的消息模型知道吗?
+
+> 题外话:早期的 JMS 和 AMQP 属于消息服务领域权威组织所做的相关的标准,我在 [JavaGuide](https://github.com/Snailclimb/JavaGuide)的 [《消息队列其实很简单》](https://github.com/Snailclimb/JavaGuide#%E6%95%B0%E6%8D%AE%E9%80%9A%E4%BF%A1%E4%B8%AD%E9%97%B4%E4%BB%B6)这篇文章中介绍过。但是,这些标准的进化跟不上消息队列的演进速度,这些标准实际上已经属于废弃状态。所以,可能存在的情况是:不同的消息队列都有自己的一套消息模型。
+
+#### 队列模型:早期的消息模型
+
+
+
+**使用队列(Queue)作为消息通信载体,满足生产者与消费者模式,一条消息只能被一个消费者使用,未被消费的消息在队列中保留直到被消费或超时。** 比如:我们生产者发送 100 条消息的话,两个消费者来消费一般情况下两个消费者会按照消息发送的顺序各自消费一半(也就是你一个我一个的消费。)
+
+**队列模型存在的问题:**
+
+假如我们存在这样一种情况:我们需要将生产者产生的消息分发给多个消费者,并且每个消费者都能接收到完整的消息内容。
+
+这种情况,队列模型就不好解决了。很多比较杠精的人就说:我们可以为每个消费者创建一个单独的队列,让生产者发送多份。这是一种非常愚蠢的做法,浪费资源不说,还违背了使用消息队列的目的。
+
+#### 发布-订阅模型:Kafka 消息模型
+
+发布-订阅模型主要是为了解决队列模型存在的问题。
+
+
+
+发布订阅模型(Pub-Sub) 使用**主题(Topic)** 作为消息通信载体,类似于**广播模式**;发布者发布一条消息,该消息通过主题传递给所有的订阅者,**在一条消息广播之后才订阅的用户则是收不到该条消息的**。
+
+**在发布 - 订阅模型中,如果只有一个订阅者,那它和队列模型就基本是一样的了。所以说,发布 - 订阅模型在功能层面上是可以兼容队列模型的。**
+
+**Kafka 采用的就是发布 - 订阅模型。**
+
+> **RocketMQ 的消息模型和 Kafka 基本是完全一样的。唯一的区别是 Kafka 中没有队列这个概念,与之对应的是 Partition(分区)。**
+
+### 什么是Producer、Consumer、Broker、Topic、Partition?
+
+Kafka 将生产者发布的消息发送到 **Topic(主题)** 中,需要这些消息的消费者可以订阅这些 **Topic(主题)**,如下图所示:
+
+
+
+上面这张图也为我们引出了,Kafka 比较重要的几个概念:
+
+1. **Producer(生产者)** : 产生消息的一方。
+2. **Consumer(消费者)** : 消费消息的一方。
+3. **Broker(代理)** : 可以看作是一个独立的 Kafka 实例。多个 Kafka Broker 组成一个 Kafka Cluster。
+
+同时,你一定也注意到每个 Broker 中又包含了 Topic 以及 Partition 这两个重要的概念:
+
+- **Topic(主题)** : Producer 将消息发送到特定的主题,Consumer 通过订阅特定的 Topic(主题) 来消费消息。
+- **Partition(分区)** : Partition 属于 Topic 的一部分。一个 Topic 可以有多个 Partition ,并且同一 Topic 下的 Partition 可以分布在不同的 Broker 上,这也就表明一个 Topic 可以横跨多个 Broker 。这正如我上面所画的图一样。
+
+> 划重点:**Kafka 中的 Partition(分区) 实际上可以对应成为消息队列中的队列。这样是不是更好理解一点?**
+
+### Kafka 的多副本机制了解吗?带来了什么好处?
+
+还有一点我觉得比较重要的是 Kafka 为分区(Partition)引入了多副本(Replica)机制。分区(Partition)中的多个副本之间会有一个叫做 leader 的家伙,其他副本称为 follower。我们发送的消息会被发送到 leader 副本,然后 follower 副本才能从 leader 副本中拉取消息进行同步。
+
+> 生产者和消费者只与 leader 副本交互。你可以理解为其他副本只是 leader 副本的拷贝,它们的存在只是为了保证消息存储的安全性。当 leader 副本发生故障时会从 follower 中选举出一个 leader,但是 follower 中如果有和 leader 同步程度达不到要求的参加不了 leader 的竞选。
+
+**Kafka 的多分区(Partition)以及多副本(Replica)机制有什么好处呢?**
+
+1. Kafka 通过给特定 Topic 指定多个 Partition, 而各个 Partition 可以分布在不同的 Broker 上, 这样便能提供比较好的并发能力(负载均衡)。
+2. Partition 可以指定对应的 Replica 数, 这也极大地提高了消息存储的安全性, 提高了容灾能力,不过也相应的增加了所需要的存储空间。
+
+### Zookeeper 在 Kafka 中的作用知道吗?
+
+> **要想搞懂 zookeeper 在 Kafka 中的作用 一定要自己搭建一个 Kafka 环境然后自己进 zookeeper 去看一下有哪些文件夹和 Kafka 有关,每个节点又保存了什么信息。** 一定不要光看不实践,这样学来的也终会忘记!这部分内容参考和借鉴了这篇文章:https://www.jianshu.com/p/a036405f989c 。
+
+
+
+下图就是我的本地 Zookeeper ,它成功和我本地的 Kafka 关联上(以下文件夹结构借助 idea 插件 Zookeeper tool 实现)。
+
+
+
+ZooKeeper 主要为 Kafka 提供元数据的管理的功能。
+
+从图中我们可以看出,Zookeeper 主要为 Kafka 做了下面这些事情:
+
+1. **Broker 注册** :在 Zookeeper 上会有一个专门**用来进行 Broker 服务器列表记录**的节点。每个 Broker 在启动时,都会到 Zookeeper 上进行注册,即到 `/brokers/ids` 下创建属于自己的节点。每个 Broker 就会将自己的 IP 地址和端口等信息记录到该节点中去
+2. **Topic 注册** : 在 Kafka 中,同一个**Topic 的消息会被分成多个分区**并将其分布在多个 Broker 上,**这些分区信息及与 Broker 的对应关系**也都是由 Zookeeper 在维护。比如我创建了一个名字为 my-topic 的主题并且它有两个分区,对应到 zookeeper 中会创建这些文件夹:`/brokers/topics/my-topic/Partitions/0`、`/brokers/topics/my-topic/Partitions/1`
+3. **负载均衡** :上面也说过了 Kafka 通过给特定 Topic 指定多个 Partition, 而各个 Partition 可以分布在不同的 Broker 上, 这样便能提供比较好的并发能力。 对于同一个 Topic 的不同 Partition,Kafka 会尽力将这些 Partition 分布到不同的 Broker 服务器上。当生产者产生消息后也会尽量投递到不同 Broker 的 Partition 里面。当 Consumer 消费的时候,Zookeeper 可以根据当前的 Partition 数量以及 Consumer 数量来实现动态负载均衡。
+4. ......
+
+### Kafka 如何保证消息的消费顺序?
+
+我们在使用消息队列的过程中经常有业务场景需要严格保证消息的消费顺序,比如我们同时发了 2 个消息,这 2 个消息对应的操作分别对应的数据库操作是:
+
+1. 更改用户会员等级。
+2. 根据会员等级计算订单价格。
+
+假如这两条消息的消费顺序不一样造成的最终结果就会截然不同。
+
+我们知道 Kafka 中 Partition(分区)是真正保存消息的地方,我们发送的消息都被放在了这里。而我们的 Partition(分区) 又存在于 Topic(主题) 这个概念中,并且我们可以给特定 Topic 指定多个 Partition。
+
+
+
+每次添加消息到 Partition(分区) 的时候都会采用尾加法,如上图所示。 **Kafka 只能为我们保证 Partition(分区) 中的消息有序。**
+
+> 消息在被追加到 Partition(分区)的时候都会分配一个特定的偏移量(offset)。Kafka 通过偏移量(offset)来保证消息在分区内的顺序性。
+
+所以,我们就有一种很简单的保证消息消费顺序的方法:**1 个 Topic 只对应一个 Partition**。这样当然可以解决问题,但是破坏了 Kafka 的设计初衷。
+
+Kafka 中发送 1 条消息的时候,可以指定 topic, partition, key,data(数据) 4 个参数。如果你发送消息的时候指定了 Partition 的话,所有消息都会被发送到指定的 Partition。并且,同一个 key 的消息可以保证只发送到同一个 partition,这个我们可以采用表/对象的 id 来作为 key 。
+
+总结一下,对于如何保证 Kafka 中消息消费的顺序,有了下面两种方法:
+
+1. 1 个 Topic 只对应一个 Partition。
+2. (推荐)发送消息的时候指定 key/Partition。
+
+当然不仅仅只有上面两种方法,上面两种方法是我觉得比较好理解的,
+
+### Kafka 如何保证消息不丢失
+
+#### 生产者丢失消息的情况
+
+生产者(Producer) 调用`send`方法发送消息之后,消息可能因为网络问题并没有发送过去。
+
+所以,我们不能默认在调用`send`方法发送消息之后消息发送成功了。为了确定消息是发送成功,我们要判断消息发送的结果。但是要注意的是 Kafka 生产者(Producer) 使用 `send` 方法发送消息实际上是异步的操作,我们可以通过 `get()`方法获取调用结果,但是这样也让它变为了同步操作,示例代码如下:
+
+> **详细代码见我的这篇文章:[Kafka系列第三篇!10 分钟学会如何在 Spring Boot 程序中使用 Kafka 作为消息队列?](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247486269&idx=2&sn=ec00417ad641dd8c3d145d74cafa09ce&chksm=cea244f6f9d5cde0c8eb233fcc4cf82e11acd06446719a7af55230649863a3ddd95f78d111de&token=1633957262&lang=zh_CN#rd)**
+
+```java
+SendResult sendResult = kafkaTemplate.send(topic, o).get();
+if (sendResult.getRecordMetadata() != null) {
+ logger.info("生产者成功发送消息到" + sendResult.getProducerRecord().topic() + "-> " + sendRe
+ sult.getProducerRecord().value().toString());
+}
+```
+
+但是一般不推荐这么做!可以采用为其添加回调函数的形式,示例代码如下:
+
+````java
+ ListenableFuture> future = kafkaTemplate.send(topic, o);
+ future.addCallback(result -> logger.info("生产者成功发送消息到topic:{} partition:{}的消息", result.getRecordMetadata().topic(), result.getRecordMetadata().partition()),
+ ex -> logger.error("生产者发送消失败,原因:{}", ex.getMessage()));
+````
+
+如果消息发送失败的话,我们检查失败的原因之后重新发送即可!
+
+**另外这里推荐为 Producer 的`retries `(重试次数)设置一个比较合理的值,一般是 3 ,但是为了保证消息不丢失的话一般会设置比较大一点。设置完成之后,当出现网络问题之后能够自动重试消息发送,避免消息丢失。另外,建议还要设置重试间隔,因为间隔太小的话重试的效果就不明显了,网络波动一次你3次一下子就重试完了**
+
+#### 消费者丢失消息的情况
+
+我们知道消息在被追加到 Partition(分区)的时候都会分配一个特定的偏移量(offset)。偏移量(offset)表示 Consumer 当前消费到的 Partition(分区)的所在的位置。Kafka 通过偏移量(offset)可以保证消息在分区内的顺序性。
+
+
+
+当消费者拉取到了分区的某个消息之后,消费者会自动提交了 offset。自动提交的话会有一个问题,试想一下,当消费者刚拿到这个消息准备进行真正消费的时候,突然挂掉了,消息实际上并没有被消费,但是 offset 却被自动提交了。
+
+**解决办法也比较粗暴,我们手动关闭自动提交 offset,每次在真正消费完消息之后再自己手动提交 offset 。** 但是,细心的朋友一定会发现,这样会带来消息被重新消费的问题。比如你刚刚消费完消息之后,还没提交 offset,结果自己挂掉了,那么这个消息理论上就会被消费两次。
+
+#### Kafka 弄丢了消息
+
+ 我们知道 Kafka 为分区(Partition)引入了多副本(Replica)机制。分区(Partition)中的多个副本之间会有一个叫做 leader 的家伙,其他副本称为 follower。我们发送的消息会被发送到 leader 副本,然后 follower 副本才能从 leader 副本中拉取消息进行同步。生产者和消费者只与 leader 副本交互。你可以理解为其他副本只是 leader 副本的拷贝,它们的存在只是为了保证消息存储的安全性。
+
+**试想一种情况:假如 leader 副本所在的 broker 突然挂掉,那么就要从 follower 副本重新选出一个 leader ,但是 leader 的数据还有一些没有被 follower 副本的同步的话,就会造成消息丢失。**
+
+**设置 acks = all**
+
+解决办法就是我们设置 **acks = all**。acks 是 Kafka 生产者(Producer) 很重要的一个参数。
+
+acks 的默认值即为1,代表我们的消息被leader副本接收之后就算被成功发送。当我们配置 **acks = all** 代表则所有副本都要接收到该消息之后该消息才算真正成功被发送。
+
+**设置 replication.factor >= 3**
+
+为了保证 leader 副本能有 follower 副本能同步消息,我们一般会为 topic 设置 **replication.factor >= 3**。这样就可以保证每个 分区(partition) 至少有 3 个副本。虽然造成了数据冗余,但是带来了数据的安全性。
+
+**设置 min.insync.replicas > 1**
+
+一般情况下我们还需要设置 **min.insync.replicas> 1** ,这样配置代表消息至少要被写入到 2 个副本才算是被成功发送。**min.insync.replicas** 的默认值为 1 ,在实际生产中应尽量避免默认值 1。
+
+但是,为了保证整个 Kafka 服务的高可用性,你需要确保 **replication.factor > min.insync.replicas** 。为什么呢?设想一下假如两者相等的话,只要是有一个副本挂掉,整个分区就无法正常工作了。这明显违反高可用性!一般推荐设置成 **replication.factor = min.insync.replicas + 1**。
+
+**设置 unclean.leader.election.enable = false**
+
+> **Kafka 0.11.0.0版本开始 unclean.leader.election.enable 参数的默认值由原来的true 改为false**
+
+我们最开始也说了我们发送的消息会被发送到 leader 副本,然后 follower 副本才能从 leader 副本中拉取消息进行同步。多个 follower 副本之间的消息同步情况不一样,当我们配置了 **unclean.leader.election.enable = false** 的话,当 leader 副本发生故障时就不会从 follower 副本中和 leader 同步程度达不到要求的副本中选择出 leader ,这样降低了消息丢失的可能性。
+
+### Kafka 如何保证消息不重复消费
+
+**kafka出现消息重复消费的原因:**
+
+- 服务端侧已经消费的数据没有成功提交 offset(根本原因)。
+- Kafka 侧 由于服务端处理业务时间长或者网络链接等等原因让 Kafka 认为服务假死,触发了分区 rebalance。
+
+**解决方案:**
+
+- 消费消息服务做幂等校验,比如 Redis 的set、MySQL 的主键等天然的幂等功能。这种方法最有效。
+- 将 **`enable.auto.commit`** 参数设置为 false,关闭自动提交,开发者在代码中手动提交 offset。那么这里会有个问题:**什么时候提交offset合适?**
+ * 处理完消息再提交:依旧有消息重复消费的风险,和自动提交一样
+ * 拉取到消息即提交:会有消息丢失的风险。允许消息延时的场景,一般会采用这种方式。然后,通过定时任务在业务不繁忙(比如凌晨)的时候做数据兜底。
+
+### Reference
+
+- Kafka 官方文档: https://kafka.apache.org/documentation/
+- 极客时间—《Kafka核心技术与实战》第11节:无消息丢失配置怎么实现?
diff --git a/docs/high-performance/message-queue/message-queue.md b/docs/high-performance/message-queue/message-queue.md
new file mode 100644
index 00000000000..161bd5a021d
--- /dev/null
+++ b/docs/high-performance/message-queue/message-queue.md
@@ -0,0 +1,138 @@
+# 消息队列知识点&面试题总结
+
+“RabbitMQ?”“Kafka?”“RocketMQ?”...在日常学习与开发过程中,我们常常听到消息队列这个关键词。我也在我的多篇文章中提到了这个概念。可能你是熟练使用消息队列的老手,又或者你是不懂消息队列的新手,不论你了不了解消息队列,本文都将带你搞懂消息队列的一些基本理论。如果你是老手,你可能从本文学到你之前不曾注意的一些关于消息队列的重要概念,如果你是新手,相信本文将是你打开消息队列大门的一板砖。
+
+## 一 什么是消息队列
+
+我们可以把消息队列看作是一个存放消息的容器,当我们需要使用消息的时候,直接从容器中取出消息供自己使用即可。
+
+
+
+消息队列是分布式系统中重要的组件之一。使用消息队列主要是为了通过异步处理提高系统性能和削峰、降低系统耦合性。
+
+我们知道队列 Queue 是一种先进先出的数据结构,所以消费消息时也是按照顺序来消费的。
+
+## 二 为什么要用消息队列
+
+通常来说,使用消息队列能为我们的系统带来下面三点好处:
+
+1. **通过异步处理提高系统性能(减少响应所需时间)。**
+2. **削峰/限流**
+3. **降低系统耦合性。**
+
+如果在面试的时候你被面试官问到这个问题的话,一般情况是你在你的简历上涉及到消息队列这方面的内容,这个时候推荐你结合你自己的项目来回答。
+
+《大型网站技术架构》第四章和第七章均有提到消息队列对应用性能及扩展性的提升。
+
+### 2.1 通过异步处理提高系统性能(减少响应所需时间)
+
+
+
+将用户的请求数据存储到消息队列之后就立即返回结果。随后,系统再对消息进行消费。
+
+因为用户请求数据写入消息队列之后就立即返回给用户了,但是请求数据在后续的业务校验、写数据库等操作中可能失败。因此,**使用消息队列进行异步处理之后,需要适当修改业务流程进行配合**,比如用户在提交订单之后,订单数据写入消息队列,不能立即返回用户订单提交成功,需要在消息队列的订单消费者进程真正处理完该订单之后,甚至出库后,再通过电子邮件或短信通知用户订单成功,以免交易纠纷。这就类似我们平时手机订火车票和电影票。
+
+### 2.2 削峰/限流
+
+**先将短时间高并发产生的事务消息存储在消息队列中,然后后端服务再慢慢根据自己的能力去消费这些消息,这样就避免直接把后端服务打垮掉。**
+
+举例:在电子商务一些秒杀、促销活动中,合理使用消息队列可以有效抵御促销活动刚开始大量订单涌入对系统的冲击。如下图所示:
+
+
+
+### 2.3 降低系统耦合性
+
+使用消息队列还可以降低系统耦合性。我们知道如果模块之间不存在直接调用,那么新增模块或者修改模块就对其他模块影响较小,这样系统的可扩展性无疑更好一些。还是直接上图吧:
+
+
+
+生产者(客户端)发送消息到消息队列中去,接受者(服务端)处理消息,需要消费的系统直接去消息队列取消息进行消费即可而不需要和其他系统有耦合,这显然也提高了系统的扩展性。
+
+**消息队列使利用发布-订阅模式工作,消息发送者(生产者)发布消息,一个或多个消息接受者(消费者)订阅消息。** 从上图可以看到**消息发送者(生产者)和消息接受者(消费者)之间没有直接耦合**,消息发送者将消息发送至分布式消息队列即结束对消息的处理,消息接受者从分布式消息队列获取该消息后进行后续处理,并不需要知道该消息从何而来。**对新增业务,只要对该类消息感兴趣,即可订阅该消息,对原有系统和业务没有任何影响,从而实现网站业务的可扩展性设计**。
+
+消息接受者对消息进行过滤、处理、包装后,构造成一个新的消息类型,将消息继续发送出去,等待其他消息接受者订阅该消息。因此基于事件(消息对象)驱动的业务架构可以是一系列流程。
+
+另外,为了避免消息队列服务器宕机造成消息丢失,会将成功发送到消息队列的消息存储在消息生产者服务器上,等消息真正被消费者服务器处理后才删除消息。在消息队列服务器宕机后,生产者服务器会选择分布式消息队列服务器集群中的其他服务器发布消息。
+
+**备注:** 不要认为消息队列只能利用发布-订阅模式工作,只不过在解耦这个特定业务环境下是使用发布-订阅模式的。除了发布-订阅模式,还有点对点订阅模式(一个消息只有一个消费者),我们比较常用的是发布-订阅模式。另外,这两种消息模型是 JMS 提供的,AMQP 协议还提供了 5 种消息模型。
+
+## 三 使用消息队列带来的一些问题
+
+- **系统可用性降低:** 系统可用性在某种程度上降低,为什么这样说呢?在加入 MQ 之前,你不用考虑消息丢失或者说 MQ 挂掉等等的情况,但是,引入 MQ 之后你就需要去考虑了!
+- **系统复杂性提高:** 加入 MQ 之后,你需要保证消息没有被重复消费、处理消息丢失的情况、保证消息传递的顺序性等等问题!
+- **一致性问题:** 我上面讲了消息队列可以实现异步,消息队列带来的异步确实可以提高系统响应速度。但是,万一消息的真正消费者并没有正确消费消息怎么办?这样就会导致数据不一致的情况了!
+
+## 四 JMS VS AMQP
+
+### 4.1 JMS
+
+#### 4.1.1 JMS 简介
+
+JMS(JAVA Message Service,java 消息服务)是 java 的消息服务,JMS 的客户端之间可以通过 JMS 服务进行异步的消息传输。**JMS(JAVA Message Service,Java 消息服务)API 是一个消息服务的标准或者说是规范**,允许应用程序组件基于 JavaEE 平台创建、发送、接收和读取消息。它使分布式通信耦合度更低,消息服务更加可靠以及异步性。
+
+**ActiveMQ 就是基于 JMS 规范实现的。**
+
+#### 4.1.2 JMS 两种消息模型
+
+**① 点到点(P2P)模型**
+
+
+
+使用**队列(Queue)**作为消息通信载体;满足**生产者与消费者模式**,一条消息只能被一个消费者使用,未被消费的消息在队列中保留直到被消费或超时。比如:我们生产者发送 100 条消息的话,两个消费者来消费一般情况下两个消费者会按照消息发送的顺序各自消费一半(也就是你一个我一个的消费。)
+
+**② 发布/订阅(Pub/Sub)模型**
+
+
+
+发布订阅模型(Pub/Sub) 使用**主题(Topic)**作为消息通信载体,类似于**广播模式**;发布者发布一条消息,该消息通过主题传递给所有的订阅者,**在一条消息广播之后才订阅的用户则是收不到该条消息的**。
+
+#### 4.1.3 JMS 五种不同的消息正文格式
+
+JMS 定义了五种不同的消息正文格式,以及调用的消息类型,允许你发送并接收以一些不同形式的数据,提供现有消息格式的一些级别的兼容性。
+
+- StreamMessage -- Java 原始值的数据流
+- MapMessage--一套名称-值对
+- TextMessage--一个字符串对象
+- ObjectMessage--一个序列化的 Java 对象
+- BytesMessage--一个字节的数据流
+
+### 4.2 AMQP
+
+AMQP,即 Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准 **高级消息队列协议**(二进制应用层协议),是应用层协议的一个开放标准,为面向消息的中间件设计,兼容 JMS。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件同产品,不同的开发语言等条件的限制。
+
+**RabbitMQ 就是基于 AMQP 协议实现的。**
+
+### 4.3 JMS vs AMQP
+
+| 对比方向 | JMS | AMQP |
+| :----------- | :-------------------------------------- | :----------------------------------------------------------- |
+| 定义 | Java API | 协议 |
+| 跨语言 | 否 | 是 |
+| 跨平台 | 否 | 是 |
+| 支持消息类型 | 提供两种消息模型:①Peer-2-Peer;②Pub/sub | 提供了五种消息模型:①direct exchange;②fanout exchange;③topic change;④headers exchange;⑤system exchange。本质来讲,后四种和 JMS 的 pub/sub 模型没有太大差别,仅是在路由机制上做了更详细的划分; |
+| 支持消息类型 | 支持多种消息类型 ,我们在上面提到过 | byte[](二进制) |
+
+**总结:**
+
+- AMQP 为消息定义了线路层(wire-level protocol)的协议,而 JMS 所定义的是 API 规范。在 Java 体系中,多个 client 均可以通过 JMS 进行交互,不需要应用修改代码,但是其对跨平台的支持较差。而 AMQP 天然具有跨平台、跨语言特性。
+- JMS 支持 TextMessage、MapMessage 等复杂的消息类型;而 AMQP 仅支持 byte[] 消息类型(复杂的类型可序列化后发送)。
+- 由于 Exchange 提供的路由算法,AMQP 可以提供多样化的路由方式来传递消息到消息队列,而 JMS 仅支持 队列 和 主题/订阅 方式两种。
+
+## 五 常见的消息队列对比
+
+| 对比方向 | 概要 |
+| -------- | ------------------------------------------------------------ |
+| 吞吐量 | 万级的 ActiveMQ 和 RabbitMQ 的吞吐量(ActiveMQ 的性能最差)要比 十万级甚至是百万级的 RocketMQ 和 Kafka 低一个数量级。 |
+| 可用性 | 都可以实现高可用。ActiveMQ 和 RabbitMQ 都是基于主从架构实现高可用性。RocketMQ 基于分布式架构。 kafka 也是分布式的,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用 |
+| 时效性 | RabbitMQ 基于 erlang 开发,所以并发能力很强,性能极其好,延时很低,达到微秒级。其他三个都是 ms 级。 |
+| 功能支持 | 除了 Kafka,其他三个功能都较为完备。 Kafka 功能较为简单,主要支持简单的 MQ 功能,在大数据领域的实时计算以及日志采集被大规模使用,是事实上的标准 |
+| 消息丢失 | ActiveMQ 和 RabbitMQ 丢失的可能性非常低, RocketMQ 和 Kafka 理论上不会丢失。 |
+
+**总结:**
+
+- ActiveMQ 的社区算是比较成熟,但是较目前来说,ActiveMQ 的性能比较差,而且版本迭代很慢,不推荐使用。
+- RabbitMQ 在吞吐量方面虽然稍逊于 Kafka 和 RocketMQ ,但是由于它基于 erlang 开发,所以并发能力很强,性能极其好,延时很低,达到微秒级。但是也因为 RabbitMQ 基于 erlang 开发,所以国内很少有公司有实力做 erlang 源码级别的研究和定制。如果业务场景对并发量要求不是太高(十万级、百万级),那这四种消息队列中,RabbitMQ 一定是你的首选。如果是大数据领域的实时计算、日志采集等场景,用 Kafka 是业内标准的,绝对没问题,社区活跃度很高,绝对不会黄,何况几乎是全世界这个领域的事实性规范。
+- RocketMQ 阿里出品,Java 系开源项目,源代码我们可以直接阅读,然后可以定制自己公司的 MQ,并且 RocketMQ 有阿里巴巴的实际业务场景的实战考验。RocketMQ 社区活跃度相对较为一般,不过也还可以,文档相对来说简单一些,然后接口这块不是按照标准 JMS 规范走的有些系统要迁移需要修改大量代码。还有就是阿里出台的技术,你得做好这个技术万一被抛弃,社区黄掉的风险,那如果你们公司有技术实力我觉得用 RocketMQ 挺好的
+- Kafka 的特点其实很明显,就是仅仅提供较少的核心功能,但是提供超高的吞吐量,ms 级的延迟,极高的可用性以及可靠性,而且分布式可以任意扩展。同时 kafka 最好是支撑较少的 topic 数量即可,保证其超高吞吐量。kafka 唯一的一点劣势是有可能消息重复消费,那么对数据准确性会造成极其轻微的影响,在大数据领域中以及日志采集中,这点轻微影响可以忽略这个特性天然适合大数据实时计算以及日志收集。
+
+参考:《Java 工程师面试突击第 1 季-中华石杉老师》
\ No newline at end of file
diff --git a/docs/system-design/data-communication/rabbitmq.md b/docs/high-performance/message-queue/rabbitmq-intro.md
similarity index 79%
rename from docs/system-design/data-communication/rabbitmq.md
rename to docs/high-performance/message-queue/rabbitmq-intro.md
index 28407cce610..d676114c8e6 100644
--- a/docs/system-design/data-communication/rabbitmq.md
+++ b/docs/high-performance/message-queue/rabbitmq-intro.md
@@ -1,25 +1,5 @@
-
-
-- [一文搞懂 RabbitMQ 的重要概念以及安装](#一文搞懂-rabbitmq-的重要概念以及安装)
- - [一 RabbitMQ 介绍](#一-rabbitmq-介绍)
- - [1.1 RabbitMQ 简介](#11-rabbitmq-简介)
- - [1.2 RabbitMQ 核心概念](#12-rabbitmq-核心概念)
- - [1.2.1 Producer(生产者) 和 Consumer(消费者)](#121-producer生产者-和-consumer消费者)
- - [1.2.2 Exchange(交换器)](#122-exchange交换器)
- - [1.2.3 Queue(消息队列)](#123-queue消息队列)
- - [1.2.4 Broker(消息中间件的服务节点)](#124-broker消息中间件的服务节点)
- - [1.2.5 Exchange Types(交换器类型)](#125-exchange-types交换器类型)
- - [① fanout](#①-fanout)
- - [② direct](#②-direct)
- - [③ topic](#③-topic)
- - [④ headers(不推荐)](#④-headers不推荐)
- - [二 安装 RabbitMq](#二-安装-rabbitmq)
- - [2.1 安装 erlang](#21-安装-erlang)
- - [2.2 安装 RabbitMQ](#22-安装-rabbitmq)
-
-
-
-# 一文搞懂 RabbitMQ 的重要概念以及安装
+
+# RabbitMQ 入门总结
## 一 RabbitMQ 介绍
@@ -32,7 +12,7 @@ RabbitMQ 是采用 Erlang 语言实现 AMQP(Advanced Message Queuing Protocol,
RabbitMQ 发展到今天,被越来越多的人认可,这和它在易用性、扩展性、可靠性和高可用性等方面的卓著表现是分不开的。RabbitMQ 的具体特点可以概括为以下几点:
- **可靠性:** RabbitMQ使用一些机制来保证消息的可靠性,如持久化、传输确认及发布确认等。
-- **灵活的路由:** 在消息进入队列之前,通过交换器来路由消息。对于典型的路由功能,RabbitMQ 己经提供了一些内置的交换器来实现。针对更复杂的路由功能,可以将多个交换器绑定在一起,也可以通过插件机制来实现自己的交换器。这个后面会在我们将 RabbitMQ 核心概念的时候详细介绍到。
+- **灵活的路由:** 在消息进入队列之前,通过交换器来路由消息。对于典型的路由功能,RabbitMQ 己经提供了一些内置的交换器来实现。针对更复杂的路由功能,可以将多个交换器绑定在一起,也可以通过插件机制来实现自己的交换器。这个后面会在我们讲 RabbitMQ 核心概念的时候详细介绍到。
- **扩展性:** 多个RabbitMQ节点可以组成一个集群,也可以根据实际业务情况动态地扩展集群中节点。
- **高可用性:** 队列可以在集群中的机器上设置镜像,使得在部分节点出现问题的情况下队列仍然可用。
- **支持多种协议:** RabbitMQ 除了原生支持 AMQP 协议,还支持 STOMP、MQTT 等多种消息中间件协议。
@@ -46,7 +26,7 @@ RabbitMQ 整体上是一个生产者与消费者模型,主要负责接收、
下面再来看看图1—— RabbitMQ 的整体模型架构。
-
+
下面我会一一介绍上图中的一些概念。
@@ -67,7 +47,7 @@ RabbitMQ 整体上是一个生产者与消费者模型,主要负责接收、
Exchange(交换器) 示意图如下:
-
+
生产者将消息发给交换器的时候,一般会指定一个 **RoutingKey(路由键)**,用来指定这个消息的路由规则,而这个 **RoutingKey 需要与交换器类型和绑定键(BindingKey)联合使用才能最终生效**。
@@ -75,7 +55,7 @@ RabbitMQ 中通过 **Binding(绑定)** 将 **Exchange(交换器)** 与 **Queue(
Binding(绑定) 示意图:
-
+
生产者将消息发送给交换器时,需要一个RoutingKey,当 BindingKey 和 RoutingKey 相匹配时,消息会被路由到对应的队列中。在绑定多个队列到同一个交换器的时候,这些绑定允许使用相同的 BindingKey。BindingKey 并不是在所有的情况下都生效,它依赖于交换器类型,比如fanout类型的交换器就会无视,而是将消息路由到所有绑定到该交换器的队列中。
@@ -85,7 +65,7 @@ Binding(绑定) 示意图:
**RabbitMQ** 中消息只能存储在 **队列** 中,这一点和 **Kafka** 这种消息中间件相反。Kafka 将消息存储在 **topic(主题)** 这个逻辑层面,而相对应的队列逻辑只是topic实际存储文件中的位移标识。 RabbitMQ 的生产者生产消息并最终投递到队列中,消费者可以从队列中获取消息并消费。
-**多个消费者可以订阅同一个队列**,这时队列中的消息会被平均分摊(Round-Robin,即轮询)给多个消费者进行处理,而不是每个消费者都收到所有的消息并处理,这样避免的消息被重复消费。
+**多个消费者可以订阅同一个队列**,这时队列中的消息会被平均分摊(Round-Robin,即轮询)给多个消费者进行处理,而不是每个消费者都收到所有的消息并处理,这样避免消息被重复消费。
**RabbitMQ** 不支持队列层面的广播消费,如果有广播消费的需求,需要在其上进行二次开发,这样会很麻烦,不建议这样做。
@@ -95,7 +75,7 @@ Binding(绑定) 示意图:
下图展示了生产者将消息存入 RabbitMQ Broker,以及消费者从Broker中消费数据的整个流程。
-
+
这样图1中的一些关于 RabbitMQ 的基本概念我们就介绍完毕了,下面再来介绍一下 **Exchange Types(交换器类型)** 。
@@ -111,7 +91,7 @@ fanout 类型的Exchange路由规则非常简单,它会把所有发送到该Ex
direct 类型的Exchange路由规则也很简单,它会把消息路由到那些 Bindingkey 与 RoutingKey 完全匹配的 Queue 中。
-
+
以上图为例,如果发送消息的时候设置路由键为“warning”,那么消息会路由到 Queue1 和 Queue2。如果在发送消息的时候设置路由键为"Info”或者"debug”,消息只会路由到Queue2。如果以其他的路由键发送消息,则消息不会路由到这两个队列中。
@@ -123,23 +103,23 @@ direct 类型常用在处理有优先级的任务,根据任务的优先级把
- RoutingKey 为一个点号“.”分隔的字符串(被点号“.”分隔开的每一段独立的字符串称为一个单词),如 “com.rabbitmq.client”、“java.util.concurrent”、“com.hidden.client”;
- BindingKey 和 RoutingKey 一样也是点号“.”分隔的字符串;
-- BindingKey 中可以存在两种特殊字符串“*”和“#”,用于做模糊匹配,其中“*”用于匹配一个单词,“#”用于匹配多个单词(可以是零个)。
+- BindingKey 中可以存在两种特殊字符串“\*”和“#”,用于做模糊匹配,其中“\*”用于匹配一个单词,“#”用于匹配多个单词(可以是零个)。
-
+
以上图为例:
-- 路由键为 “com.rabbitmq.client” 的消息会同时路由到 Queuel 和 Queue2;
+- 路由键为 “com.rabbitmq.client” 的消息会同时路由到 Queue1 和 Queue2;
- 路由键为 “com.hidden.client” 的消息只会路由到 Queue2 中;
- 路由键为 “com.hidden.demo” 的消息只会路由到 Queue2 中;
-- 路由键为 “java.rabbitmq.demo” 的消息只会路由到Queuel中;
+- 路由键为 “java.rabbitmq.demo” 的消息只会路由到 Queue1 中;
- 路由键为 “java.util.concurrent” 的消息将会被丢弃或者返回给生产者(需要设置 mandatory 参数),因为它没有匹配任何路由键。
##### ④ headers(不推荐)
-headers 类型的交换器不依赖于路由键的匹配规则来路由消息,而是根据发送的消息内容中的 headers 属性进行匹配。在绑定队列和交换器时制定一组键值对,当发送消息到交换器时,RabbitMQ会获取到该消息的 headers(也是一个键值对的形式)'对比其中的键值对是否完全匹配队列和交换器绑定时指定的键值对,如果完全匹配则消息会路由到该队列,否则不会路由到该队列。headers 类型的交换器性能会很差,而且也不实用,基本上不会看到它的存在。
+headers 类型的交换器不依赖于路由键的匹配规则来路由消息,而是根据发送的消息内容中的 headers 属性进行匹配。在绑定队列和交换器时指定一组键值对,当发送消息到交换器时,RabbitMQ会获取到该消息的 headers(也是一个键值对的形式),对比其中的键值对是否完全匹配队列和交换器绑定时指定的键值对,如果完全匹配则消息会路由到该队列,否则不会路由到该队列。headers 类型的交换器性能会很差,而且也不实用,基本上不会看到它的存在。
-## 二 安装 RabbitMq
+## 二 安装 RabbitMQ
通过 Docker 安装非常方便,只需要几条命令就好了,我这里是只说一下常规安装方法。
@@ -156,10 +136,10 @@ headers 类型的交换器不依赖于路由键的匹配规则来路由消息,
在官网下载然后上传到 Linux 上或者直接使用下面的命令下载对应的版本。
```shell
-[root@SnailClimb local]#wget http://erlang.org/download/otp_src_19.3.tar.gz
+[root@SnailClimb local]#wget https://erlang.org/download/otp_src_19.3.tar.gz
```
-erlang 官网下载:[http://www.erlang.org/downloads](http://www.erlang.org/downloads)
+erlang 官网下载:[https://www.erlang.org/downloads](https://www.erlang.org/downloads)
**2 解压 erlang 安装包**
@@ -211,7 +191,7 @@ make && make install
```erlang
io:format("hello world~n", []).
```
-
+
大功告成,我们的 erlang 已经安装完成。
@@ -242,7 +222,7 @@ export ERL_HOME PATH
[root@SnailClimb etc]# erl
```
-
+
### 2.2 安装 RabbitMQ
@@ -253,7 +233,7 @@ wget https://www.rabbitmq.com/releases/rabbitmq-server/v3.6.8/rabbitmq-server-3.
```
或者直接在官网下载
-https://www.rabbitmq.com/install-rpm.html[enter link description here](https://www.rabbitmq.com/install-rpm.html)
+[https://www.rabbitmq.com/install-rpm.html](https://www.rabbitmq.com/install-rpm.html)
**2. 安装rpm**
@@ -279,23 +259,23 @@ rabbitmq-plugins enable rabbitmq_management
chkconfig rabbitmq-server on
```
-**4. 启动服务**
+**5. 启动服务**
```shell
service rabbitmq-server start
```
-**5. 查看服务状态**
+**6. 查看服务状态**
```shell
service rabbitmq-server status
```
-**6. 访问 RabbitMQ 控制台**
+**7. 访问 RabbitMQ 控制台**
浏览器访问:http://你的ip地址:15672/
-默认用户名和密码: guest/guest;但是需要注意的是:guestuest用户只是被容许从localhost访问。官网文档描述如下:
+默认用户名和密码:guest/guest; 但是需要注意的是:guest用户只是被容许从localhost访问。官网文档描述如下:
```shell
“guest” user can only connect via localhost
@@ -319,6 +299,6 @@ Setting permissions for user "root" in vhost "/" ...
再次访问:http://你的ip地址:15672/ ,输入用户名和密码:root root
-
+
diff --git a/docs/high-performance/message-queue/rocketmq-intro.md b/docs/high-performance/message-queue/rocketmq-intro.md
new file mode 100644
index 00000000000..2fbacfabec4
--- /dev/null
+++ b/docs/high-performance/message-queue/rocketmq-intro.md
@@ -0,0 +1,456 @@
+# RocketMQ入门总结
+
+> 文章很长,点赞再看,养成好习惯😋😋😋
+>
+> [本文由 FrancisQ 老哥投稿!](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485969&idx=1&sn=6bd53abde30d42a778d5a35ec104428c&chksm=cea245daf9d5cccce631f93115f0c2c4a7634e55f5bef9009fd03f5a0ffa55b745b5ef4f0530&token=294077121&lang=zh_CN#rd)
+
+## 消息队列扫盲
+
+消息队列顾名思义就是存放消息的队列,队列我就不解释了,别告诉我你连队列都不知道是啥吧?
+
+所以问题并不是消息队列是什么,而是 **消息队列为什么会出现?消息队列能用来干什么?用它来干这些事会带来什么好处?消息队列会带来副作用吗?**
+
+### 消息队列为什么会出现?
+
+消息队列算是作为后端程序员的一个必备技能吧,因为**分布式应用必定涉及到各个系统之间的通信问题**,这个时候消息队列也应运而生了。可以说分布式的产生是消息队列的基础,而分布式怕是一个很古老的概念了吧,所以消息队列也是一个很古老的中间件了。
+
+### 消息队列能用来干什么?
+
+#### 异步
+
+你可能会反驳我,应用之间的通信又不是只能由消息队列解决,好好的通信为什么中间非要插一个消息队列呢?我不能直接进行通信吗?
+
+很好👍,你又提出了一个概念,**同步通信**。就比如现在业界使用比较多的 `Dubbo` 就是一个适用于各个系统之间同步通信的 `RPC` 框架。
+
+我来举个🌰吧,比如我们有一个购票系统,需求是用户在购买完之后能接收到购买完成的短信。
+
+
+
+我们省略中间的网络通信时间消耗,假如购票系统处理需要 150ms ,短信系统处理需要 200ms ,那么整个处理流程的时间消耗就是 150ms + 200ms = 350ms。
+
+当然,乍看没什么问题。可是仔细一想你就感觉有点问题,我用户购票在购票系统的时候其实就已经完成了购买,而我现在通过同步调用非要让整个请求拉长时间,而短信系统这玩意又不是很有必要,它仅仅是一个辅助功能增强用户体验感而已。我现在整个调用流程就有点 **头重脚轻** 的感觉了,购票是一个不太耗时的流程,而我现在因为同步调用,非要等待发送短信这个比较耗时的操作才返回结果。那我如果再加一个发送邮件呢?
+
+
+
+这样整个系统的调用链又变长了,整个时间就变成了550ms。
+
+当我们在学生时代需要在食堂排队的时候,我们和食堂大妈就是一个同步的模型。
+
+我们需要告诉食堂大妈:“姐姐,给我加个鸡腿,再加个酸辣土豆丝,帮我浇点汁上去,多打点饭哦😋😋😋” 咦~~~ 为了多吃点,真恶心。
+
+然后大妈帮我们打饭配菜,我们看着大妈那颤抖的手和掉落的土豆丝不禁咽了咽口水。
+
+最终我们从大妈手中接过饭菜然后去寻找座位了...
+
+回想一下,我们在给大妈发送需要的信息之后我们是 **同步等待大妈给我配好饭菜** 的,上面我们只是加了鸡腿和土豆丝,万一我再加一个番茄牛腩,韭菜鸡蛋,这样是不是大妈打饭配菜的流程就会变长,我们等待的时间也会相应的变长。
+
+
+
+那后来,我们工作赚钱了有钱去饭店吃饭了,我们告诉服务员来一碗牛肉面加个荷包蛋 **(传达一个消息)** ,然后我们就可以在饭桌上安心的玩手机了 **(干自己其他事情)** ,等到我们的牛肉面上了我们就可以吃了。这其中我们也就传达了一个消息,然后我们又转过头干其他事情了。这其中虽然做面的时间没有变短,但是我们只需要传达一个消息就可以干其他事情了,这是一个 **异步** 的概念。
+
+所以,为了解决这一个问题,聪明的程序员在中间也加了个类似于服务员的中间件——消息队列。这个时候我们就可以把模型给改造了。
+
+
+
+这样,我们在将消息存入消息队列之后我们就可以直接返回了(我们告诉服务员我们要吃什么然后玩手机),所以整个耗时只是 150ms + 10ms = 160ms。
+
+> 但是你需要注意的是,整个流程的时长是没变的,就像你仅仅告诉服务员要吃什么是不会影响到做面的速度的。
+
+#### 解耦
+
+回到最初同步调用的过程,我们写个伪代码简单概括一下。
+
+
+
+那么第二步,我们又添加了一个发送邮件,我们就得重新去修改代码,如果我们又加一个需求:用户购买完还需要给他加积分,这个时候我们是不是又得改代码?
+
+
+
+如果你觉得还行,那么我这个时候不要发邮件这个服务了呢,我是不是又得改代码,又得重启应用?
+
+
+
+这样改来改去是不是很麻烦,那么 **此时我们就用一个消息队列在中间进行解耦** 。你需要注意的是,我们后面的发送短信、发送邮件、添加积分等一些操作都依赖于上面的 `result` ,这东西抽象出来就是购票的处理结果呀,比如订单号,用户账号等等,也就是说我们后面的一系列服务都是需要同样的消息来进行处理。既然这样,我们是不是可以通过 **“广播消息”** 来实现。
+
+我上面所讲的“广播”并不是真正的广播,而是接下来的系统作为消费者去 **订阅** 特定的主题。比如我们这里的主题就可以叫做 `订票` ,我们购买系统作为一个生产者去生产这条消息放入消息队列,然后消费者订阅了这个主题,会从消息队列中拉取消息并消费。就比如我们刚刚画的那张图,你会发现,在生产者这边我们只需要关注 **生产消息到指定主题中** ,而 **消费者只需要关注从指定主题中拉取消息** 就行了。
+
+
+
+> 如果没有消息队列,每当一个新的业务接入,我们都要在主系统调用新接口、或者当我们取消某些业务,我们也得在主系统删除某些接口调用。有了消息队列,我们只需要关心消息是否送达了队列,至于谁希望订阅,接下来收到消息如何处理,是下游的事情,无疑极大地减少了开发和联调的工作量。
+
+#### 削峰
+
+我们再次回到一开始我们使用同步调用系统的情况,并且思考一下,如果此时有大量用户请求购票整个系统会变成什么样?
+
+
+
+如果,此时有一万的请求进入购票系统,我们知道运行我们主业务的服务器配置一般会比较好,所以这里我们假设购票系统能承受这一万的用户请求,那么也就意味着我们同时也会出现一万调用发短信服务的请求。而对于短信系统来说并不是我们的主要业务,所以我们配备的硬件资源并不会太高,那么你觉得现在这个短信系统能承受这一万的峰值么,且不说能不能承受,系统会不会 **直接崩溃** 了?
+
+短信业务又不是我们的主业务,我们能不能 **折中处理** 呢?如果我们把购买完成的信息发送到消息队列中,而短信系统 **尽自己所能地去消息队列中取消息和消费消息** ,即使处理速度慢一点也无所谓,只要我们的系统没有崩溃就行了。
+
+留得江山在,还怕没柴烧?你敢说每次发送验证码的时候是一发你就收到了的么?
+
+#### 消息队列能带来什么好处?
+
+其实上面我已经说了。**异步、解耦、削峰。** 哪怕你上面的都没看懂也千万要记住这六个字,因为他不仅是消息队列的精华,更是编程和架构的精华。
+
+#### 消息队列会带来副作用吗?
+
+没有哪一门技术是“银弹”,消息队列也有它的副作用。
+
+比如,本来好好的两个系统之间的调用,我中间加了个消息队列,如果消息队列挂了怎么办呢?是不是 **降低了系统的可用性** ?
+
+那这样是不是要保证HA(高可用)?是不是要搞集群?那么我 **整个系统的复杂度是不是上升了** ?
+
+抛开上面的问题不讲,万一我发送方发送失败了,然后执行重试,这样就可能产生重复的消息。
+
+或者我消费端处理失败了,请求重发,这样也会产生重复的消息。
+
+对于一些微服务来说,消费重复消息会带来更大的麻烦,比如增加积分,这个时候我加了多次是不是对其他用户不公平?
+
+那么,又 **如何解决重复消费消息的问题** 呢?
+
+如果我们此时的消息需要保证严格的顺序性怎么办呢?比如生产者生产了一系列的有序消息(对一个id为1的记录进行删除增加修改),但是我们知道在发布订阅模型中,对于主题是无顺序的,那么这个时候就会导致对于消费者消费消息的时候没有按照生产者的发送顺序消费,比如这个时候我们消费的顺序为修改删除增加,如果该记录涉及到金额的话是不是会出大事情?
+
+那么,又 **如何解决消息的顺序消费问题** 呢?
+
+就拿我们上面所讲的分布式系统来说,用户购票完成之后是不是需要增加账户积分?在同一个系统中我们一般会使用事务来进行解决,如果用 `Spring` 的话我们在上面伪代码中加入 `@Transactional` 注解就好了。但是在不同系统中如何保证事务呢?总不能这个系统我扣钱成功了你那积分系统积分没加吧?或者说我这扣钱明明失败了,你那积分系统给我加了积分。
+
+那么,又如何 **解决分布式事务问题** 呢?
+
+我们刚刚说了,消息队列可以进行削峰操作,那如果我的消费者如果消费很慢或者生产者生产消息很快,这样是不是会将消息堆积在消息队列中?
+
+那么,又如何 **解决消息堆积的问题** 呢?
+
+可用性降低,复杂度上升,又带来一系列的重复消费,顺序消费,分布式事务,消息堆积的问题,这消息队列还怎么用啊😵?
+
+
+
+别急,办法总是有的。
+
+## RocketMQ是什么?
+
+
+
+哇,你个混蛋!上面给我抛出那么多问题,你现在又讲 `RocketMQ` ,还让不让人活了?!🤬
+
+别急别急,话说你现在清楚 `MQ` 的构造吗,我还没讲呢,我们先搞明白 `MQ` 的内部构造,再来看看如何解决上面的一系列问题吧,不过你最好带着问题去阅读和了解喔。
+
+`RocketMQ` 是一个 **队列模型** 的消息中间件,具有**高性能、高可靠、高实时、分布式** 的特点。它是一个采用 `Java` 语言开发的分布式的消息系统,由阿里巴巴团队开发,在2016年底贡献给 `Apache`,成为了 `Apache` 的一个顶级项目。 在阿里内部,`RocketMQ` 很好地服务了集团大大小小上千个应用,在每年的双十一当天,更有不可思议的万亿级消息通过 `RocketMQ` 流转。
+
+废话不多说,想要了解 `RocketMQ` 历史的同学可以自己去搜寻资料。听完上面的介绍,你只要知道 `RocketMQ` 很快、很牛、而且经历过双十一的实践就行了!
+
+## 队列模型和主题模型
+
+在谈 `RocketMQ` 的技术架构之前,我们先来了解一下两个名词概念——**队列模型** 和 **主题模型** 。
+
+首先我问一个问题,消息队列为什么要叫消息队列?
+
+你可能觉得很弱智,这玩意不就是存放消息的队列嘛?不叫消息队列叫什么?
+
+的确,早期的消息中间件是通过 **队列** 这一模型来实现的,可能是历史原因,我们都习惯把消息中间件成为消息队列。
+
+但是,如今例如 `RocketMQ` 、`Kafka` 这些优秀的消息中间件不仅仅是通过一个 **队列** 来实现消息存储的。
+
+### 队列模型
+
+就像我们理解队列一样,消息中间件的队列模型就真的只是一个队列。。。我画一张图给大家理解。
+
+
+
+在一开始我跟你提到了一个 **“广播”** 的概念,也就是说如果我们此时我们需要将一个消息发送给多个消费者(比如此时我需要将信息发送给短信系统和邮件系统),这个时候单个队列即不能满足需求了。
+
+当然你可以让 `Producer` 生产消息放入多个队列中,然后每个队列去对应每一个消费者。问题是可以解决,创建多个队列并且复制多份消息是会很影响资源和性能的。而且,这样子就会导致生产者需要知道具体消费者个数然后去复制对应数量的消息队列,这就违背我们消息中间件的 **解耦** 这一原则。
+
+### 主题模型
+
+那么有没有好的方法去解决这一个问题呢?有,那就是 **主题模型** 或者可以称为 **发布订阅模型** 。
+
+> 感兴趣的同学可以去了解一下设计模式里面的观察者模式并且手动实现一下,我相信你会有所收获的。
+
+在主题模型中,消息的生产者称为 **发布者(Publisher)** ,消息的消费者称为 **订阅者(Subscriber)** ,存放消息的容器称为 **主题(Topic)** 。
+
+其中,发布者将消息发送到指定主题中,订阅者需要 **提前订阅主题** 才能接受特定主题的消息。
+
+
+
+### RocketMQ中的消息模型
+
+`RocketMQ` 中的消息模型就是按照 **主题模型** 所实现的。你可能会好奇这个 **主题** 到底是怎么实现的呢?你上面也没有讲到呀!
+
+其实对于主题模型的实现来说每个消息中间件的底层设计都是不一样的,就比如 `Kafka` 中的 **分区** ,`RocketMQ` 中的 **队列** ,`RabbitMQ` 中的 `Exchange` 。我们可以理解为 **主题模型/发布订阅模型** 就是一个标准,那些中间件只不过照着这个标准去实现而已。
+
+所以,`RocketMQ` 中的 **主题模型** 到底是如何实现的呢?首先我画一张图,大家尝试着去理解一下。
+
+
+
+我们可以看到在整个图中有 `Producer Group` 、`Topic` 、`Consumer Group` 三个角色,我来分别介绍一下他们。
+
+- `Producer Group` 生产者组: 代表某一类的生产者,比如我们有多个秒杀系统作为生产者,这多个合在一起就是一个 `Producer Group` 生产者组,它们一般生产相同的消息。
+- `Consumer Group` 消费者组: 代表某一类的消费者,比如我们有多个短信系统作为消费者,这多个合在一起就是一个 `Consumer Group` 消费者组,它们一般消费相同的消息。
+- `Topic` 主题: 代表一类消息,比如订单消息,物流消息等等。
+
+你可以看到图中生产者组中的生产者会向主题发送消息,而 **主题中存在多个队列**,生产者每次生产消息之后是指定主题中的某个队列发送消息的。
+
+每个主题中都有多个队列(分布在不同的 `Broker`中,如果是集群的话,`Broker`又分布在不同的服务器中),集群消费模式下,一个消费者集群多台机器共同消费一个 `topic` 的多个队列,**一个队列只会被一个消费者消费**。如果某个消费者挂掉,分组内其它消费者会接替挂掉的消费者继续消费。就像上图中 `Consumer1` 和 `Consumer2` 分别对应着两个队列,而 `Consumer3` 是没有队列对应的,所以一般来讲要控制 **消费者组中的消费者个数和主题中队列个数相同** 。
+
+当然也可以消费者个数小于队列个数,只不过不太建议。如下图。
+
+
+
+**每个消费组在每个队列上维护一个消费位置** ,为什么呢?
+
+因为我们刚刚画的仅仅是一个消费者组,我们知道在发布订阅模式中一般会涉及到多个消费者组,而每个消费者组在每个队列中的消费位置都是不同的。如果此时有多个消费者组,那么消息被一个消费者组消费完之后是不会删除的(因为其它消费者组也需要呀),它仅仅是为每个消费者组维护一个 **消费位移(offset)** ,每次消费者组消费完会返回一个成功的响应,然后队列再把维护的消费位移加一,这样就不会出现刚刚消费过的消息再一次被消费了。
+
+
+
+可能你还有一个问题,**为什么一个主题中需要维护多个队列** ?
+
+答案是 **提高并发能力** 。的确,每个主题中只存在一个队列也是可行的。你想一下,如果每个主题中只存在一个队列,这个队列中也维护着每个消费者组的消费位置,这样也可以做到 **发布订阅模式** 。如下图。
+
+
+
+但是,这样我生产者是不是只能向一个队列发送消息?又因为需要维护消费位置所以一个队列只能对应一个消费者组中的消费者,这样是不是其他的 `Consumer` 就没有用武之地了?从这两个角度来讲,并发度一下子就小了很多。
+
+所以总结来说,`RocketMQ` 通过**使用在一个 `Topic` 中配置多个队列并且每个队列维护每个消费者组的消费位置** 实现了 **主题模式/发布订阅模式** 。
+
+## RocketMQ的架构图
+
+讲完了消息模型,我们理解起 `RocketMQ` 的技术架构起来就容易多了。
+
+`RocketMQ` 技术架构中有四大角色 `NameServer` 、`Broker` 、`Producer` 、`Consumer` 。我来向大家分别解释一下这四个角色是干啥的。
+
+- `Broker`: 主要负责消息的存储、投递和查询以及服务高可用保证。说白了就是消息队列服务器嘛,生产者生产消息到 `Broker` ,消费者从 `Broker` 拉取消息并消费。
+
+ 这里,我还得普及一下关于 `Broker` 、`Topic` 和 队列的关系。上面我讲解了 `Topic` 和队列的关系——一个 `Topic` 中存在多个队列,那么这个 `Topic` 和队列存放在哪呢?
+
+ **一个 `Topic` 分布在多个 `Broker`上,一个 `Broker` 可以配置多个 `Topic` ,它们是多对多的关系**。
+
+ 如果某个 `Topic` 消息量很大,应该给它多配置几个队列(上文中提到了提高并发能力),并且 **尽量多分布在不同 `Broker` 上,以减轻某个 `Broker` 的压力** 。
+
+ `Topic` 消息量都比较均匀的情况下,如果某个 `broker` 上的队列越多,则该 `broker` 压力越大。
+
+ 
+
+ > 所以说我们需要配置多个Broker。
+
+- `NameServer`: 不知道你们有没有接触过 `ZooKeeper` 和 `Spring Cloud` 中的 `Eureka` ,它其实也是一个 **注册中心** ,主要提供两个功能:**Broker管理** 和 **路由信息管理** 。说白了就是 `Broker` 会将自己的信息注册到 `NameServer` 中,此时 `NameServer` 就存放了很多 `Broker` 的信息(Broker的路由表),消费者和生产者就从 `NameServer` 中获取路由表然后照着路由表的信息和对应的 `Broker` 进行通信(生产者和消费者定期会向 `NameServer` 去查询相关的 `Broker` 的信息)。
+
+- `Producer`: 消息发布的角色,支持分布式集群方式部署。说白了就是生产者。
+
+- `Consumer`: 消息消费的角色,支持分布式集群方式部署。支持以push推,pull拉两种模式对消息进行消费。同时也支持集群方式和广播方式的消费,它提供实时消息订阅机制。说白了就是消费者。
+
+听完了上面的解释你可能会觉得,这玩意好简单。不就是这样的么?
+
+
+
+嗯?你可能会发现一个问题,这老家伙 `NameServer` 干啥用的,这不多余吗?直接 `Producer` 、`Consumer` 和 `Broker` 直接进行生产消息,消费消息不就好了么?
+
+但是,我们上文提到过 `Broker` 是需要保证高可用的,如果整个系统仅仅靠着一个 `Broker` 来维持的话,那么这个 `Broker` 的压力会不会很大?所以我们需要使用多个 `Broker` 来保证 **负载均衡** 。
+
+如果说,我们的消费者和生产者直接和多个 `Broker` 相连,那么当 `Broker` 修改的时候必定会牵连着每个生产者和消费者,这样就会产生耦合问题,而 `NameServer` 注册中心就是用来解决这个问题的。
+
+> 如果还不是很理解的话,可以去看我介绍 `Spring Cloud` 的那篇文章,其中介绍了 `Eureka` 注册中心。
+
+当然,`RocketMQ` 中的技术架构肯定不止前面那么简单,因为上面图中的四个角色都是需要做集群的。我给出一张官网的架构图,大家尝试理解一下。
+
+
+
+其实和我们最开始画的那张乞丐版的架构图也没什么区别,主要是一些细节上的差别。听我细细道来🤨。
+
+第一、我们的 `Broker` **做了集群并且还进行了主从部署** ,由于消息分布在各个 `Broker` 上,一旦某个 `Broker` 宕机,则该`Broker` 上的消息读写都会受到影响。所以 `Rocketmq` 提供了 `master/slave` 的结构,` salve` 定时从 `master` 同步数据(同步刷盘或者异步刷盘),如果 `master` 宕机,**则 `slave` 提供消费服务,但是不能写入消息** (后面我还会提到哦)。
+
+第二、为了保证 `HA` ,我们的 `NameServer` 也做了集群部署,但是请注意它是 **去中心化** 的。也就意味着它没有主节点,你可以很明显地看出 `NameServer` 的所有节点是没有进行 `Info Replicate` 的,在 `RocketMQ` 中是通过 **单个Broker和所有NameServer保持长连接** ,并且在每隔30秒 `Broker` 会向所有 `Nameserver` 发送心跳,心跳包含了自身的 `Topic` 配置信息,这个步骤就对应这上面的 `Routing Info` 。
+
+第三、在生产者需要向 `Broker` 发送消息的时候,**需要先从 `NameServer` 获取关于 `Broker` 的路由信息**,然后通过 **轮询** 的方法去向每个队列中生产数据以达到 **负载均衡** 的效果。
+
+第四、消费者通过 `NameServer` 获取所有 `Broker` 的路由信息后,向 `Broker` 发送 `Pull` 请求来获取消息数据。`Consumer` 可以以两种模式启动—— **广播(Broadcast)和集群(Cluster)**。广播模式下,一条消息会发送给 **同一个消费组中的所有消费者** ,集群模式下消息只会发送给一个消费者。
+
+## 如何解决 顺序消费、重复消费
+
+其实,这些东西都是我在介绍消息队列带来的一些副作用的时候提到的,也就是说,这些问题不仅仅挂钩于 `RocketMQ` ,而是应该每个消息中间件都需要去解决的。
+
+在上面我介绍 `RocketMQ` 的技术架构的时候我已经向你展示了 **它是如何保证高可用的** ,这里不涉及运维方面的搭建,如果你感兴趣可以自己去官网上照着例子搭建属于你自己的 `RocketMQ` 集群。
+
+> 其实 `Kafka` 的架构基本和 `RocketMQ` 类似,只是它注册中心使用了 `Zookeeper` 、它的 **分区** 就相当于 `RocketMQ` 中的 **队列** 。还有一些小细节不同会在后面提到。
+
+### 顺序消费
+
+在上面的技术架构介绍中,我们已经知道了 **`RocketMQ` 在主题上是无序的、它只有在队列层面才是保证有序** 的。
+
+这又扯到两个概念——**普通顺序** 和 **严格顺序** 。
+
+所谓普通顺序是指 消费者通过 **同一个消费队列收到的消息是有顺序的** ,不同消息队列收到的消息则可能是无顺序的。普通顺序消息在 `Broker` **重启情况下不会保证消息顺序性** (短暂时间) 。
+
+所谓严格顺序是指 消费者收到的 **所有消息** 均是有顺序的。严格顺序消息 **即使在异常情况下也会保证消息的顺序性** 。
+
+但是,严格顺序看起来虽好,实现它可会付出巨大的代价。如果你使用严格顺序模式,`Broker` 集群中只要有一台机器不可用,则整个集群都不可用。你还用啥?现在主要场景也就在 `binlog` 同步。
+
+一般而言,我们的 `MQ` 都是能容忍短暂的乱序,所以推荐使用普通顺序模式。
+
+那么,我们现在使用了 **普通顺序模式** ,我们从上面学习知道了在 `Producer` 生产消息的时候会进行轮询(取决你的负载均衡策略)来向同一主题的不同消息队列发送消息。那么如果此时我有几个消息分别是同一个订单的创建、支付、发货,在轮询的策略下这 **三个消息会被发送到不同队列** ,因为在不同的队列此时就无法使用 `RocketMQ` 带来的队列有序特性来保证消息有序性了。
+
+
+
+那么,怎么解决呢?
+
+其实很简单,我们需要处理的仅仅是将同一语义下的消息放入同一个队列(比如这里是同一个订单),那我们就可以使用 **Hash取模法** 来保证同一个订单在同一个队列中就行了。
+
+### 重复消费
+
+emmm,就两个字—— **幂等** 。在编程中一个*幂等* 操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。比如说,这个时候我们有一个订单的处理积分的系统,每当来一个消息的时候它就负责为创建这个订单的用户的积分加上相应的数值。可是有一次,消息队列发送给订单系统 FrancisQ 的订单信息,其要求是给 FrancisQ 的积分加上 500。但是积分系统在收到 FrancisQ 的订单信息处理完成之后返回给消息队列处理成功的信息的时候出现了网络波动(当然还有很多种情况,比如Broker意外重启等等),这条回应没有发送成功。
+
+那么,消息队列没收到积分系统的回应会不会尝试重发这个消息?问题就来了,我再发这个消息,万一它又给 FrancisQ 的账户加上 500 积分怎么办呢?
+
+所以我们需要给我们的消费者实现 **幂等** ,也就是对同一个消息的处理结果,执行多少次都不变。
+
+那么如何给业务实现幂等呢?这个还是需要结合具体的业务的。你可以使用 **写入 `Redis`** 来保证,因为 `Redis` 的 `key` 和 `value` 就是天然支持幂等的。当然还有使用 **数据库插入法** ,基于数据库的唯一键来保证重复数据不会被插入多条。
+
+不过最主要的还是需要 **根据特定场景使用特定的解决方案** ,你要知道你的消息消费是否是完全不可重复消费还是可以忍受重复消费的,然后再选择强校验和弱校验的方式。毕竟在 CS 领域还是很少有技术银弹的说法。
+
+而在整个互联网领域,幂等不仅仅适用于消息队列的重复消费问题,这些实现幂等的方法,也同样适用于,**在其他场景中来解决重复请求或者重复调用的问题** 。比如将HTTP服务设计成幂等的,**解决前端或者APP重复提交表单数据的问题** ,也可以将一个微服务设计成幂等的,解决 `RPC` 框架自动重试导致的 **重复调用问题** 。
+
+## 分布式事务
+
+如何解释分布式事务呢?事务大家都知道吧?**要么都执行要么都不执行** 。在同一个系统中我们可以轻松地实现事务,但是在分布式架构中,我们有很多服务是部署在不同系统之间的,而不同服务之间又需要进行调用。比如此时我下订单然后增加积分,如果保证不了分布式事务的话,就会出现A系统下了订单,但是B系统增加积分失败或者A系统没有下订单,B系统却增加了积分。前者对用户不友好,后者对运营商不利,这是我们都不愿意见到的。
+
+那么,如何去解决这个问题呢?
+
+如今比较常见的分布式事务实现有 2PC、TCC 和事务消息(half 半消息机制)。每一种实现都有其特定的使用场景,但是也有各自的问题,**都不是完美的解决方案**。
+
+在 `RocketMQ` 中使用的是 **事务消息加上事务反查机制** 来解决分布式事务问题的。我画了张图,大家可以对照着图进行理解。
+
+
+
+在第一步发送的 half 消息 ,它的意思是 **在事务提交之前,对于消费者来说,这个消息是不可见的** 。
+
+> 那么,如何做到写入消息但是对用户不可见呢?RocketMQ事务消息的做法是:如果消息是half消息,将备份原消息的主题与消息消费队列,然后 **改变主题** 为RMQ_SYS_TRANS_HALF_TOPIC。由于消费组未订阅该主题,故消费端无法消费half类型的消息,**然后RocketMQ会开启一个定时任务,从Topic为RMQ_SYS_TRANS_HALF_TOPIC中拉取消息进行消费**,根据生产者组获取一个服务提供者发送回查事务状态请求,根据事务状态来决定是提交或回滚消息。
+
+你可以试想一下,如果没有从第5步开始的 **事务反查机制** ,如果出现网路波动第4步没有发送成功,这样就会产生 MQ 不知道是不是需要给消费者消费的问题,他就像一个无头苍蝇一样。在 `RocketMQ` 中就是使用的上述的事务反查来解决的,而在 `Kafka` 中通常是直接抛出一个异常让用户来自行解决。
+
+你还需要注意的是,在 `MQ Server` 指向系统B的操作已经和系统A不相关了,也就是说在消息队列中的分布式事务是——**本地事务和存储消息到消息队列才是同一个事务**。这样也就产生了事务的**最终一致性**,因为整个过程是异步的,**每个系统只要保证它自己那一部分的事务就行了**。
+
+## 消息堆积问题
+
+在上面我们提到了消息队列一个很重要的功能——**削峰** 。那么如果这个峰值太大了导致消息堆积在队列中怎么办呢?
+
+其实这个问题可以将它广义化,因为产生消息堆积的根源其实就只有两个——生产者生产太快或者消费者消费太慢。
+
+我们可以从多个角度去思考解决这个问题,当流量到峰值的时候是因为生产者生产太快,我们可以使用一些 **限流降级** 的方法,当然你也可以增加多个消费者实例去水平扩展增加消费能力来匹配生产的激增。如果消费者消费过慢的话,我们可以先检查 **是否是消费者出现了大量的消费错误** ,或者打印一下日志查看是否是哪一个线程卡死,出现了锁资源不释放等等的问题。
+
+> 当然,最快速解决消息堆积问题的方法还是增加消费者实例,不过 **同时你还需要增加每个主题的队列数量** 。
+>
+> 别忘了在 `RocketMQ` 中,**一个队列只会被一个消费者消费** ,如果你仅仅是增加消费者实例就会出现我一开始给你画架构图的那种情况。
+
+
+
+## 回溯消费
+
+回溯消费是指 `Consumer` 已经消费成功的消息,由于业务上需求需要重新消费,在`RocketMQ` 中, `Broker` 在向`Consumer` 投递成功消息后,**消息仍然需要保留** 。并且重新消费一般是按照时间维度,例如由于 `Consumer` 系统故障,恢复后需要重新消费1小时前的数据,那么 `Broker` 要提供一种机制,可以按照时间维度来回退消费进度。`RocketMQ` 支持按照时间回溯消费,时间维度精确到毫秒。
+
+这是官方文档的解释,我直接照搬过来就当科普了😁😁😁。
+
+## RocketMQ 的刷盘机制
+
+上面我讲了那么多的 `RocketMQ` 的架构和设计原理,你有没有好奇
+
+在 `Topic` 中的 **队列是以什么样的形式存在的?**
+
+**队列中的消息又是如何进行存储持久化的呢?**
+
+我在上文中提到的 **同步刷盘** 和 **异步刷盘** 又是什么呢?它们会给持久化带来什么样的影响呢?
+
+下面我将给你们一一解释。
+
+### 同步刷盘和异步刷盘
+
+
+
+如上图所示,在同步刷盘中需要等待一个刷盘成功的 `ACK` ,同步刷盘对 `MQ` 消息可靠性来说是一种不错的保障,但是 **性能上会有较大影响** ,一般地适用于金融等特定业务场景。
+
+而异步刷盘往往是开启一个线程去异步地执行刷盘操作。消息刷盘采用后台异步线程提交的方式进行, **降低了读写延迟** ,提高了 `MQ` 的性能和吞吐量,一般适用于如发验证码等对于消息保证要求不太高的业务场景。
+
+一般地,**异步刷盘只有在 `Broker` 意外宕机的时候会丢失部分数据**,你可以设置 `Broker` 的参数 `FlushDiskType` 来调整你的刷盘策略(ASYNC_FLUSH 或者 SYNC_FLUSH)。
+
+### 同步复制和异步复制
+
+上面的同步刷盘和异步刷盘是在单个结点层面的,而同步复制和异步复制主要是指的 `Borker` 主从模式下,主节点返回消息给客户端的时候是否需要同步从节点。
+
+- 同步复制: 也叫 “同步双写”,也就是说,**只有消息同步双写到主从结点上时才返回写入成功** 。
+- 异步复制: **消息写入主节点之后就直接返回写入成功** 。
+
+然而,很多事情是没有完美的方案的,就比如我们进行消息写入的节点越多就更能保证消息的可靠性,但是随之的性能也会下降,所以需要程序员根据特定业务场景去选择适应的主从复制方案。
+
+那么,**异步复制会不会也像异步刷盘那样影响消息的可靠性呢?**
+
+答案是不会的,因为两者就是不同的概念,对于消息可靠性是通过不同的刷盘策略保证的,而像异步同步复制策略仅仅是影响到了 **可用性** 。为什么呢?其主要原因**是 `RocketMQ` 是不支持自动主从切换的,当主节点挂掉之后,生产者就不能再给这个主节点生产消息了**。
+
+比如这个时候采用异步复制的方式,在主节点还未发送完需要同步的消息的时候主节点挂掉了,这个时候从节点就少了一部分消息。但是此时生产者无法再给主节点生产消息了,**消费者可以自动切换到从节点进行消费**(仅仅是消费),所以在主节点挂掉的时间只会产生主从结点短暂的消息不一致的情况,降低了可用性,而当主节点重启之后,从节点那部分未来得及复制的消息还会继续复制。
+
+在单主从架构中,如果一个主节点挂掉了,那么也就意味着整个系统不能再生产了。那么这个可用性的问题能否解决呢?**一个主从不行那就多个主从的呗**,别忘了在我们最初的架构图中,每个 `Topic` 是分布在不同 `Broker` 中的。
+
+
+
+但是这种复制方式同样也会带来一个问题,那就是无法保证 **严格顺序** 。在上文中我们提到了如何保证的消息顺序性是通过将一个语义的消息发送在同一个队列中,使用 `Topic` 下的队列来保证顺序性的。如果此时我们主节点A负责的是订单A的一系列语义消息,然后它挂了,这样其他节点是无法代替主节点A的,如果我们任意节点都可以存入任何消息,那就没有顺序性可言了。
+
+而在 `RocketMQ` 中采用了 `Dledger` 解决这个问题。他要求在写入消息的时候,要求**至少消息复制到半数以上的节点之后**,才给客⼾端返回写⼊成功,并且它是⽀持通过选举来动态切换主节点的。这里我就不展开说明了,读者可以自己去了解。
+
+> 也不是说 `Dledger` 是个完美的方案,至少在 `Dledger` 选举过程中是无法提供服务的,而且他必须要使用三个节点或以上,如果多数节点同时挂掉他也是无法保证可用性的,而且要求消息复制半数以上节点的效率和直接异步复制还是有一定的差距的。
+
+### 存储机制
+
+还记得上面我们一开始的三个问题吗?到这里第三个问题已经解决了。
+
+但是,在 `Topic` 中的 **队列是以什么样的形式存在的?队列中的消息又是如何进行存储持久化的呢?** 还未解决,其实这里涉及到了 `RocketMQ` 是如何设计它的存储结构了。我首先想大家介绍 `RocketMQ` 消息存储架构中的三大角色——`CommitLog` 、`ConsumeQueue` 和 `IndexFile` 。
+
+- `CommitLog`: **消息主体以及元数据的存储主体**,存储 `Producer` 端写入的消息主体内容,消息内容不是定长的。单个文件大小默认1G ,文件名长度为20位,左边补零,剩余为起始偏移量,比如00000000000000000000代表了第一个文件,起始偏移量为0,文件大小为1G=1073741824;当第一个文件写满了,第二个文件为00000000001073741824,起始偏移量为1073741824,以此类推。消息主要是**顺序写入日志文件**,当文件满了,写入下一个文件。
+- `ConsumeQueue`: 消息消费队列,**引入的目的主要是提高消息消费的性能**(我们再前面也讲了),由于`RocketMQ` 是基于主题 `Topic` 的订阅模式,消息消费是针对主题进行的,如果要遍历 `commitlog` 文件中根据 `Topic` 检索消息是非常低效的。`Consumer` 即可根据 `ConsumeQueue` 来查找待消费的消息。其中,`ConsumeQueue`(逻辑消费队列)**作为消费消息的索引**,保存了指定 `Topic` 下的队列消息在 `CommitLog` 中的**起始物理偏移量 `offset` **,消息大小 `size` 和消息 `Tag` 的 `HashCode` 值。**`consumequeue` 文件可以看成是基于 `topic` 的 `commitlog` 索引文件**,故 `consumequeue` 文件夹的组织方式如下:topic/queue/file三层组织结构,具体存储路径为:$HOME/store/consumequeue/{topic}/{queueId}/{fileName}。同样 `consumequeue` 文件采取定长设计,每一个条目共20个字节,分别为8字节的 `commitlog` 物理偏移量、4字节的消息长度、8字节tag `hashcode`,单个文件由30W个条目组成,可以像数组一样随机访问每一个条目,每个 `ConsumeQueue`文件大小约5.72M;
+- `IndexFile`: `IndexFile`(索引文件)提供了一种可以通过key或时间区间来查询消息的方法。这里只做科普不做详细介绍。
+
+总结来说,整个消息存储的结构,最主要的就是 `CommitLoq` 和 `ConsumeQueue` 。而 `ConsumeQueue` 你可以大概理解为 `Topic` 中的队列。
+
+
+
+`RocketMQ` 采用的是 **混合型的存储结构** ,即为 `Broker` 单个实例下所有的队列共用一个日志数据文件来存储消息。有意思的是在同样高并发的 `Kafka` 中会为每个 `Topic` 分配一个存储文件。这就有点类似于我们有一大堆书需要装上书架,`RockeMQ` 是不分书的种类直接成批的塞上去的,而 `Kafka` 是将书本放入指定的分类区域的。
+
+而 `RocketMQ` 为什么要这么做呢?原因是 **提高数据的写入效率** ,不分 `Topic` 意味着我们有更大的几率获取 **成批** 的消息进行数据写入,但也会带来一个麻烦就是读取消息的时候需要遍历整个大文件,这是非常耗时的。
+
+所以,在 `RocketMQ` 中又使用了 `ConsumeQueue` 作为每个队列的索引文件来 **提升读取消息的效率**。我们可以直接根据队列的消息序号,计算出索引的全局位置(索引序号*索引固定⻓度20),然后直接读取这条索引,再根据索引中记录的消息的全局位置,找到消息。
+
+讲到这里,你可能对 `RockeMQ` 的存储架构还有些模糊,没事,我们结合着图来理解一下。
+
+
+
+emmm,是不是有一点复杂🤣,看英文图片和英文文档的时候就不要怂,硬着头皮往下看就行。
+
+> 如果上面没看懂的读者一定要认真看下面的流程分析!
+
+首先,在最上面的那一块就是我刚刚讲的你现在可以直接 **把 `ConsumerQueue` 理解为 `Queue`**。
+
+在图中最左边说明了 红色方块 代表被写入的消息,虚线方块代表等待被写入的。左边的生产者发送消息会指定 `Topic` 、`QueueId` 和具体消息内容,而在 `Broker` 中管你是哪门子消息,他直接 **全部顺序存储到了 CommitLog**。而根据生产者指定的 `Topic` 和 `QueueId` 将这条消息本身在 `CommitLog` 的偏移(offset),消息本身大小,和tag的hash值存入对应的 `ConsumeQueue` 索引文件中。而在每个队列中都保存了 `ConsumeOffset` 即每个消费者组的消费位置(我在架构那里提到了,忘了的同学可以回去看一下),而消费者拉取消息进行消费的时候只需要根据 `ConsumeOffset` 获取下一个未被消费的消息就行了。
+
+上述就是我对于整个消息存储架构的大概理解(这里不涉及到一些细节讨论,比如稀疏索引等等问题),希望对你有帮助。
+
+因为有一个知识点因为写嗨了忘讲了,想想在哪里加也不好,所以我留给大家去思考🤔🤔一下吧。
+
+
+
+为什么 `CommitLog` 文件要设计成固定大小的长度呢?提醒:**内存映射机制**。
+
+## 总结
+
+总算把这篇博客写完了。我讲的你们还记得吗😅?
+
+这篇文章中我主要想大家介绍了
+
+1. 消息队列出现的原因
+2. 消息队列的作用(异步,解耦,削峰)
+3. 消息队列带来的一系列问题(消息堆积、重复消费、顺序消费、分布式事务等等)
+4. 消息队列的两种消息模型——队列和主题模式
+5. 分析了 `RocketMQ` 的技术架构(`NameServer` 、`Broker` 、`Producer` 、`Comsumer`)
+6. 结合 `RocketMQ` 回答了消息队列副作用的解决方案
+7. 介绍了 `RocketMQ` 的存储机制和刷盘策略。
+
+等等。。。
+
+> 如果喜欢可以点赞哟👍👍👍。
diff --git a/docs/system-design/data-communication/RocketMQ-Questions.md b/docs/high-performance/message-queue/rocketmq-questions.md
similarity index 76%
rename from docs/system-design/data-communication/RocketMQ-Questions.md
rename to docs/high-performance/message-queue/rocketmq-questions.md
index a41a4035b7b..68957689c11 100644
--- a/docs/system-design/data-communication/RocketMQ-Questions.md
+++ b/docs/high-performance/message-queue/rocketmq-questions.md
@@ -1,25 +1,8 @@
+# RocketMQ常见问题
+
本文来自读者 [PR](https://github.com/Snailclimb/JavaGuide/pull/291)。
-
-
-- [1 单机版消息中心](#1-%E5%8D%95%E6%9C%BA%E7%89%88%E6%B6%88%E6%81%AF%E4%B8%AD%E5%BF%83)
-- [2 分布式消息中心](#2-%E5%88%86%E5%B8%83%E5%BC%8F%E6%B6%88%E6%81%AF%E4%B8%AD%E5%BF%83)
- - [2.1 问题与解决](#21-%E9%97%AE%E9%A2%98%E4%B8%8E%E8%A7%A3%E5%86%B3)
- - [2.1.1 消息丢失的问题](#211-%E6%B6%88%E6%81%AF%E4%B8%A2%E5%A4%B1%E7%9A%84%E9%97%AE%E9%A2%98)
- - [2.1.2 同步落盘怎么才能快](#212-%E5%90%8C%E6%AD%A5%E8%90%BD%E7%9B%98%E6%80%8E%E4%B9%88%E6%89%8D%E8%83%BD%E5%BF%AB)
- - [2.1.3 消息堆积的问题](#213-%E6%B6%88%E6%81%AF%E5%A0%86%E7%A7%AF%E7%9A%84%E9%97%AE%E9%A2%98)
- - [2.1.4 定时消息的实现](#214-%E5%AE%9A%E6%97%B6%E6%B6%88%E6%81%AF%E7%9A%84%E5%AE%9E%E7%8E%B0)
- - [2.1.5 顺序消息的实现](#215-%E9%A1%BA%E5%BA%8F%E6%B6%88%E6%81%AF%E7%9A%84%E5%AE%9E%E7%8E%B0)
- - [2.1.6 分布式消息的实现](#216-%E5%88%86%E5%B8%83%E5%BC%8F%E6%B6%88%E6%81%AF%E7%9A%84%E5%AE%9E%E7%8E%B0)
- - [2.1.7 消息的 push 实现](#217-%E6%B6%88%E6%81%AF%E7%9A%84-push-%E5%AE%9E%E7%8E%B0)
- - [2.1.8 消息重复发送的避免](#218-%E6%B6%88%E6%81%AF%E9%87%8D%E5%A4%8D%E5%8F%91%E9%80%81%E7%9A%84%E9%81%BF%E5%85%8D)
- - [2.1.9 广播消费与集群消费](#219-%E5%B9%BF%E6%92%AD%E6%B6%88%E8%B4%B9%E4%B8%8E%E9%9B%86%E7%BE%A4%E6%B6%88%E8%B4%B9)
- - [2.1.10 RocketMQ 不使用 ZooKeeper 作为注册中心的原因,以及自制的 NameServer 优缺点?](#2110-rocketmq-%E4%B8%8D%E4%BD%BF%E7%94%A8-zookeeper-%E4%BD%9C%E4%B8%BA%E6%B3%A8%E5%86%8C%E4%B8%AD%E5%BF%83%E7%9A%84%E5%8E%9F%E5%9B%A0%E4%BB%A5%E5%8F%8A%E8%87%AA%E5%88%B6%E7%9A%84-nameserver-%E4%BC%98%E7%BC%BA%E7%82%B9)
- - [2.1.11 其它](#2111-%E5%85%B6%E5%AE%83)
-- [3 参考](#3-%E5%8F%82%E8%80%83)
-
-
-
-# 1 单机版消息中心
+
+## 1 单机版消息中心
一个消息中心,最基本的需要支持多生产者、多消费者,例如下:
@@ -127,40 +110,40 @@ class Broker {
4. 没有使用多个队列(即多个 LinkedBlockingQueue),RocketMQ 的顺序消息是通过生产者和消费者同时使用同一个 MessageQueue 来实现,但是如果我们只有一个 MessageQueue,那我们天然就支持顺序消息
5. 没有使用 MappedByteBuffer 来实现文件映射从而使消息数据落盘非常的快(实际 RocketMQ 使用的是 FileChannel+DirectBuffer)
-# 2 分布式消息中心
+## 2 分布式消息中心
-## 2.1 问题与解决
+### 2.1 问题与解决
-### 2.1.1 消息丢失的问题
+#### 2.1.1 消息丢失的问题
1. 当你系统需要保证百分百消息不丢失,你可以使用生产者每发送一个消息,Broker 同步返回一个消息发送成功的反馈消息
2. 即每发送一个消息,同步落盘后才返回生产者消息发送成功,这样只要生产者得到了消息发送生成的返回,事后除了硬盘损坏,都可以保证不会消息丢失
3. 但是这同时引入了一个问题,同步落盘怎么才能快?
-### 2.1.2 同步落盘怎么才能快
+#### 2.1.2 同步落盘怎么才能快
1. 使用 FileChannel + DirectBuffer 池,使用堆外内存,加快内存拷贝
2. 使用数据和索引分离,当消息需要写入时,使用 commitlog 文件顺序写,当需要定位某个消息时,查询index 文件来定位,从而减少文件IO随机读写的性能损耗
-### 2.1.3 消息堆积的问题
+#### 2.1.3 消息堆积的问题
1. 后台定时任务每隔72小时,删除旧的没有使用过的消息信息
2. 根据不同的业务实现不同的丢弃任务,具体参考线程池的 AbortPolicy,例如FIFO/LRU等(RocketMQ没有此策略)
3. 消息定时转移,或者对某些重要的 TAG 型(支付型)消息真正落库
-### 2.1.4 定时消息的实现
+#### 2.1.4 定时消息的实现
1. 实际 RocketMQ 没有实现任意精度的定时消息,它只支持某些特定的时间精度的定时消息
2. 实现定时消息的原理是:创建特定时间精度的 MessageQueue,例如生产者需要定时1s之后被消费者消费,你只需要将此消息发送到特定的 Topic,例如:MessageQueue-1 表示这个 MessageQueue 里面的消息都会延迟一秒被消费,然后 Broker 会在 1s 后发送到消费者消费此消息,使用 newSingleThreadScheduledExecutor 实现
-### 2.1.5 顺序消息的实现
+#### 2.1.5 顺序消息的实现
1. 与定时消息同原理,生产者生产消息时指定特定的 MessageQueue ,消费者消费消息时,消费特定的 MessageQueue,其实单机版的消息中心在一个 MessageQueue 就天然支持了顺序消息
2. 注意:同一个 MessageQueue 保证里面的消息是顺序消费的前提是:消费者是串行的消费该 MessageQueue,因为就算 MessageQueue 是顺序的,但是当并行消费时,还是会有顺序问题,但是串行消费也同时引入了两个问题:
>1. 引入锁来实现串行
>2. 前一个消费阻塞时后面都会被阻塞
-### 2.1.6 分布式消息的实现
+#### 2.1.6 分布式消息的实现
1. 需要前置知识:2PC
2. RocketMQ4.3 起支持,原理为2PC,即两阶段提交,prepared->commit/rollback
@@ -168,29 +151,31 @@ class Broker {
>注意,就算是事务消息最后回滚了也不会物理删除,只会逻辑删除该消息
-### 2.1.7 消息的 push 实现
+#### 2.1.7 消息的 push 实现
1. 注意,RocketMQ 已经说了自己会有低延迟问题,其中就包括这个消息的 push 延迟问题
2. 因为这并不是真正的将消息主动的推送到消费者,而是 Broker 定时任务每5s将消息推送到消费者
+3. pull模式需要我们手动调用consumer拉消息,而push模式则只需要我们提供一个listener即可实现对消息的监听,而实际上,RocketMQ的push模式是基于pull模式实现的,它没有实现真正的push。
+4. push方式里,consumer把轮询过程封装了,并注册MessageListener监听器,取到消息后,唤醒MessageListener的consumeMessage()来消费,对用户而言,感觉消息是被推送过来的。
-### 2.1.8 消息重复发送的避免
+#### 2.1.8 消息重复发送的避免
1. RocketMQ 会出现消息重复发送的问题,因为在网络延迟的情况下,这种问题不可避免的发生,如果非要实现消息不可重复发送,那基本太难,因为网络环境无法预知,还会使程序复杂度加大,因此默认允许消息重复发送
2. RocketMQ 让使用者在消费者端去解决该问题,即需要消费者端在消费消息时支持幂等性的去消费消息
-3. 最简单的解决方案是每条消费记录有个消费状态字段,根据这个消费状态字段来是否消费或者使用一个集中式的表,来存储所有消息的消费状态,从而避免重复消费
+3. 最简单的解决方案是每条消费记录有个消费状态字段,根据这个消费状态字段来判断是否消费或者使用一个集中式的表,来存储所有消息的消费状态,从而避免重复消费
4. 具体实现可以查询关于消息幂等消费的解决方案
-### 2.1.9 广播消费与集群消费
+#### 2.1.9 广播消费与集群消费
1. 消息消费区别:广播消费,订阅该 Topic 的消息者们都会消费**每个**消息。集群消费,订阅该 Topic 的消息者们只会有一个去消费**某个**消息
2. 消息落盘区别:具体表现在消息消费进度的保存上。广播消费,由于每个消费者都独立的去消费每个消息,因此每个消费者各自保存自己的消息消费进度。而集群消费下,订阅了某个 Topic,而旗下又有多个 MessageQueue,每个消费者都可能会去消费不同的 MessageQueue,因此总体的消费进度保存在 Broker 上集中的管理
-### 2.1.10 RocketMQ 不使用 ZooKeeper 作为注册中心的原因,以及自制的 NameServer 优缺点?
+#### 2.1.10 RocketMQ 不使用 ZooKeeper 作为注册中心的原因,以及自制的 NameServer 优缺点?
1. ZooKeeper 作为支持顺序一致性的中间件,在某些情况下,它为了满足一致性,会丢失一定时间内的可用性,RocketMQ 需要注册中心只是为了发现组件地址,在某些情况下,RocketMQ 的注册中心可以出现数据不一致性,这同时也是 NameServer 的缺点,因为 NameServer 集群间互不通信,它们之间的注册信息可能会不一致
-2. 另外,当有新的服务器加入时,NameServer 并不会立马通知到 Produer,而是由 Produer 定时去请求 NameServer 获取最新的 Broker/Consumer 信息(这种情况是通过 Producer 发送消息时,负载均衡解决)
+2. 另外,当有新的服务器加入时,NameServer 并不会立马通知到 Producer,而是由 Producer 定时去请求 NameServer 获取最新的 Broker/Consumer 信息(这种情况是通过 Producer 发送消息时,负载均衡解决)
-### 2.1.11 其它
+#### 2.1.11 其它
![][1]
@@ -199,13 +184,13 @@ class Broker {
2. 消息重试负载均衡策略(具体参考 Dubbo 负载均衡策略)
3. 消息过滤器(Producer 发送消息到 Broker,Broker 存储消息信息,Consumer 消费时请求 Broker 端从磁盘文件查询消息文件时,在 Broker 端就使用过滤服务器进行过滤)
4. Broker 同步双写和异步双写中 Master 和 Slave 的交互
-5. Broker 在 4.5.0 版本更新中引入了基于 Raft 协议的多副本选举,之前这是商业版才有的特性 [ISSUE-1046][2]
+5. Broker 在 4.5.0 版本更新中引入了基于 Raft 协议的多副本选举,之前这是商业版才有的特性 [ISSUE-1046][2]
-# 3 参考
+## 3 参考
1. 《RocketMQ技术内幕》:https://blog.csdn.net/prestigeding/article/details/85233529
2. 关于 RocketMQ 对 MappedByteBuffer 的一点优化:https://lishoubo.github.io/2017/09/27/MappedByteBuffer%E7%9A%84%E4%B8%80%E7%82%B9%E4%BC%98%E5%8C%96/
-3. 阿里中间件团队博客-十分钟入门RocketMQ:http://jm.taobao.org/2017/01/12/rocketmq-quick-start-in-10-minutes/
+3. 十分钟入门RocketMQ:https://developer.aliyun.com/article/66101
4. 分布式事务的种类以及 RocketMQ 支持的分布式消息:https://www.infoq.cn/article/2018/08/rocketmq-4.3-release
5. 滴滴出行基于RocketMQ构建企业级消息队列服务的实践:https://yq.aliyun.com/articles/664608
6. 基于《RocketMQ技术内幕》源码注释:https://github.com/LiWenGu/awesome-rocketmq
diff --git "a/docs/high-performance/\350\257\273\345\206\231\345\210\206\347\246\273&\345\210\206\345\272\223\345\210\206\350\241\250.md" "b/docs/high-performance/\350\257\273\345\206\231\345\210\206\347\246\273&\345\210\206\345\272\223\345\210\206\350\241\250.md"
new file mode 100644
index 00000000000..4937d9fd7f3
--- /dev/null
+++ "b/docs/high-performance/\350\257\273\345\206\231\345\210\206\347\246\273&\345\210\206\345\272\223\345\210\206\350\241\250.md"
@@ -0,0 +1,190 @@
+# 读写分离&分库分表
+
+大家好呀!今天和小伙伴们聊聊读写分离以及分库分表。
+
+相信很多小伙伴们对于这两个概念已经比较熟悉了,这篇文章全程都是大白话的形式,希望能够给你带来不一样的感受。
+
+如果你之前不太了解这两个概念,那我建议你搞懂之后,可以把自己对于读写分离以及分库分表的理解讲给你的同事/朋友听听。
+
+**原创不易,若有帮助,点赞/分享就是对我最大的鼓励!**
+
+_个人能力有限。如果文章有任何需要补充/完善/修改的地方,欢迎在评论区指出,共同进步!_
+
+## 读写分离
+
+### 何为读写分离?
+
+见名思意,根据读写分离的名字,我们就可以知道:**读写分离主要是为了将对数据库的读写操作分散到不同的数据库节点上。** 这样的话,就能够小幅提升写性能,大幅提升读性能。
+
+我简单画了一张图来帮助不太清楚读写分离的小伙伴理解。
+
+
+
+一般情况下,我们都会选择一主多从,也就是一台主数据库负责写,其他的从数据库负责读。主库和从库之间会进行数据同步,以保证从库中数据的准确性。这样的架构实现起来比较简单,并且也符合系统的写少读多的特点。
+
+### 读写分离会带来什么问题?如何解决?
+
+读写分离对于提升数据库的并发非常有效,但是,同时也会引来一个问题:主库和从库的数据存在延迟,比如你写完主库之后,主库的数据同步到从库是需要时间的,这个时间差就导致了主库和从库的数据不一致性问题。这也就是我们经常说的 **主从同步延迟** 。
+
+主从同步延迟问题的解决,没有特别好的一种方案(可能是我太菜了,欢迎评论区补充)。你可以根据自己的业务场景,参考一下下面几种解决办法。
+
+**1.强制将读请求路由到主库处理。**
+
+既然你从库的数据过期了,那我就直接从主库读取嘛!这种方案虽然会增加主库的压力,但是,实现起来比较简单,也是我了解到的使用最多的一种方式。
+
+比如 `Sharding-JDBC` 就是采用的这种方案。通过使用 Sharding-JDBC 的 `HintManager` 分片键值管理器,我们可以强制使用主库。
+
+```java
+HintManager hintManager = HintManager.getInstance();
+hintManager.setMasterRouteOnly();
+// 继续JDBC操作
+```
+
+对于这种方案,你可以将那些必须获取最新数据的读请求都交给主库处理。
+
+**2.延迟读取。**
+
+还有一些朋友肯定会想既然主从同步存在延迟,那我就在延迟之后读取啊,比如主从同步延迟 0.5s,那我就 1s 之后再读取数据。这样多方便啊!方便是方便,但是也很扯淡。
+
+不过,如果你是这样设计业务流程就会好很多:对于一些对数据比较敏感的场景,你可以在完成写请求之后,避免立即进行请求操作。比如你支付成功之后,跳转到一个支付成功的页面,当你点击返回之后才返回自己的账户。
+
+另外,[《MySQL 实战 45 讲》](https://time.geekbang.org/column/intro/100020801?code=ieY8HeRSlDsFbuRtggbBQGxdTh-1jMASqEIeqzHAKrI%3D)这个专栏中的[《读写分离有哪些坑?》](https://time.geekbang.org/column/article/77636)这篇文章还介绍了很多其他比较实际的解决办法,感兴趣的小伙伴可以自行研究一下。
+
+### 如何实现读写分离?
+
+不论是使用哪一种读写分离具体的实现方案,想要实现读写分离一般包含如下几步:
+
+1. 部署多台数据库,选择其中的一台作为主数据库,其他的一台或者多台作为从数据库。
+2. 保证主数据库和从数据库之间的数据是实时同步的,这个过程也就是我们常说的**主从复制**。
+3. 系统将写请求交给主数据库处理,读请求交给从数据库处理。
+
+落实到项目本身的话,常用的方式有两种:
+
+**1.代理方式**
+
+
+
+我们可以在应用和数据中间加了一个代理层。应用程序所有的数据请求都交给代理层处理,代理层负责分离读写请求,将它们路由到对应的数据库中。
+
+提供类似功能的中间件有 **MySQL Router**(官方)、**Atlas**(基于 MySQL Proxy)、**Maxscale**、**MyCat**。
+
+**2.组件方式**
+
+在这种方式中,我们可以通过引入第三方组件来帮助我们读写请求。
+
+这也是我比较推荐的一种方式。这种方式目前在各种互联网公司中用的最多的,相关的实际的案例也非常多。如果你要采用这种方式的话,推荐使用 `sharding-jdbc` ,直接引入 jar 包即可使用,非常方便。同时,也节省了很多运维的成本。
+
+你可以在 shardingsphere 官方找到[sharding-jdbc 关于读写分离的操作](https://shardingsphere.apache.org/document/legacy/3.x/document/cn/manual/sharding-jdbc/usage/read-write-splitting/)。
+
+### 主从复制原理了解么?
+
+MySQL binlog(binary log 即二进制日志文件) 主要记录了 MySQL 数据库中数据的所有变化(数据库执行的所有 DDL 和 DML 语句)。因此,我们根据主库的 MySQL binlog 日志就能够将主库的数据同步到从库中。
+
+更具体和详细的过程是这个样子的(图片来自于:[《MySQL Master-Slave Replication on the Same Machine》](https://www.toptal.com/mysql/mysql-master-slave-replication-tutorial)):
+
+
+
+1. 主库将数据库中数据的变化写入到 binlog
+2. 从库连接主库
+3. 从库会创建一个 I/O 线程向主库请求更新的 binlog
+4. 主库会创建一个 binlog dump 线程来发送 binlog ,从库中的 I/O 线程负责接收
+5. 从库的 I/O 线程将接收的 binlog 写入到 relay log 中。
+6. 从库的 SQL 线程读取 relay log 同步数据本地(也就是再执行一遍 SQL )。
+
+怎么样?看了我对主从复制这个过程的讲解,你应该搞明白了吧!
+
+你一般看到 binlog 就要想到主从复制。当然,除了主从复制之外,binlog 还能帮助我们实现数据恢复。
+
+🌈 拓展一下:
+
+不知道大家有没有使用过阿里开源的一个叫做 canal 的工具。这个工具可以帮助我们实现 MySQL 和其他数据源比如 Elasticsearch 或者另外一台 MySQL 数据库之间的数据同步。很显然,这个工具的底层原理肯定也是依赖 binlog。canal 的原理就是模拟 MySQL 主从复制的过程,解析 binlog 将数据同步到其他的数据源。
+
+另外,像咱们常用的分布式缓存组件 Redis 也是通过主从复制实现的读写分离。
+
+🌕 简单总结一下:
+
+**MySQL 主从复制是依赖于 binlog 。另外,常见的一些同步 MySQL 数据到其他数据源的工具(比如 canal)的底层一般也是依赖 binlog 。**
+
+## 分库分表
+
+读写分离主要应对的是数据库读并发,没有解决数据库存储问题。试想一下:**如果 MySQL 一张表的数据量过大怎么办?**
+
+换言之,**我们该如何解决 MySQL 的存储压力呢?**
+
+答案之一就是 **分库分表**。
+
+### 何为分库?
+
+**分库** 就是将数据库中的数据分散到不同的数据库上。
+
+下面这些操作都涉及到了分库:
+
+- 你将数据库中的用户表和用户订单表分别放在两个不同的数据库。
+- 由于用户表数据量太大,你对用户表进行了水平切分,然后将切分后的 2 张用户表分别放在两个不同的数据库。
+
+### 何为分表?
+
+**分表** 就是对单表的数据进行拆分,可以是垂直拆分,也可以是水平拆分。
+
+**何为垂直拆分?**
+
+简单来说,垂直拆分是对数据表列的拆分,把一张列比较多的表拆分为多张表。
+
+举个例子:我们可以将用户信息表中的一些列单独抽出来作为一个表。
+
+**何为水平拆分?**
+
+简单来说,水平拆分是对数据表行的拆分,把一张行比较多的表拆分为多张表。
+
+举个例子:我们可以将用户信息表拆分成多个用户信息表,这样就可以避免单一表数据量过大对性能造成影响。
+
+[《从零开始学架构》](https://time.geekbang.org/column/intro/100006601?code=i00Nq3pHUcUj04ZWy70NCRl%2FD2Lfj8GVzcGzZ3Wf5Ug%3D) 中的有一张图片对于垂直拆分和水平拆分的描述还挺直观的。
+
+
+
+### 什么情况下需要分库分表?
+
+遇到下面几种场景可以考虑分库分表:
+
+- 单表的数据达到千万级别以上,数据库读写速度比较缓慢(分表)。
+- 数据库中的数据占用的空间越来越大,备份时间越来越长(分库)。
+- 应用的并发量太大(分库)。
+
+### 分库分表会带来什么问题呢?
+
+记住,你在公司做的任何技术决策,不光是要考虑这个技术能不能满足我们的要求,是否适合当前业务场景,还要重点考虑其带来的成本。
+
+引入分库分表之后,会给系统带来什么挑战呢?
+
+- **join 操作** : 同一个数据库中的表分布在了不同的数据库中,导致无法使用 join 操作。这样就导致我们需要手动进行数据的封装,比如你在一个数据库中查询到一个数据之后,再根据这个数据去另外一个数据库中找对应的数据。
+- **事务问题** :同一个数据库中的表分布在了不同的数据库中,如果单个操作涉及到多个数据库,那么数据库自带的事务就无法满足我们的要求了。
+- **分布式 id** :分库之后, 数据遍布在不同服务器上的数据库,数据库的自增主键已经没办法满足生成的主键唯一了。我们如何为不同的数据节点生成全局唯一主键呢?这个时候,我们就需要为我们的系统引入分布式 id 了。
+- ......
+
+另外,引入分库分表之后,一般需要 DBA 的参与,同时还需要更多的数据库服务器,这些都属于成本。
+
+### 分库分表有没有什么比较推荐的方案?
+
+ShardingSphere 项目(包括 Sharding-JDBC、Sharding-Proxy 和 Sharding-Sidecar)是当当捐入 Apache 的,目前主要由京东数科的一些巨佬维护。
+
+
+
+ShardingSphere 绝对可以说是当前分库分表的首选!ShardingSphere 的功能完善,除了支持读写分离和分库分表,还提供分布式事务、数据库治理等功能。
+
+另外,ShardingSphere 的生态体系完善,社区活跃,文档完善,更新和发布比较频繁。
+
+艿艿之前写了一篇分库分表的实战文章,各位朋友可以看看:[《芋道 Spring Boot 分库分表入门》](https://mp.weixin.qq.com/s/A2MYOFT7SP-7kGOon8qJaw) 。
+
+### 分库分表后,数据怎么迁移呢?
+
+分库分表之后,我们如何将老库(单库单表)的数据迁移到新库(分库分表后的数据库系统)呢?
+
+比较简单同时也是非常常用的方案就是**停机迁移**,写个脚本老库的数据写到新库中。比如你在凌晨 2 点,系统使用的人数非常少的时候,挂一个公告说系统要维护升级预计 1 小时。然后,你写一个脚本将老库的数据都同步到新库中。
+
+如果你不想停机迁移数据的话,也可以考虑**双写方案**。双写方案是针对那种不能停机迁移的场景,实现起来要稍微麻烦一些。具体原理是这样的:
+
+- 我们对老库的更新操作(增删改),同时也要写入新库(双写)。如果操作的数据不存在于新库的话,需要插入到新库中。 这样就能保证,咱们新库里的数据是最新的。
+- 在迁移过程,双写只会让被更新操作过的老库中的数据同步到新库,我们还需要自己写脚本将老库中的数据和新库的数据做比对。如果新库中没有,那咱们就把数据插入到新库。如果新库有,旧库没有,就把新库对应的数据删除(冗余数据清理)。
+- 重复上一步的操作,直到老库和新库的数据一致为止。
+
+想要在项目中实施双写还是比较麻烦的,很容易会出现问题。我们可以借助上面提到的数据库同步工具 Canal 做增量数据迁移(还是依赖 binlog,开发和维护成本较低)。
diff --git "a/docs/high-performance/\350\264\237\350\275\275\345\235\207\350\241\241.md" "b/docs/high-performance/\350\264\237\350\275\275\345\235\207\350\241\241.md"
new file mode 100644
index 00000000000..a9d98b2cea5
--- /dev/null
+++ "b/docs/high-performance/\350\264\237\350\275\275\345\235\207\350\241\241.md"
@@ -0,0 +1,13 @@
+# 负载均衡
+
+负载均衡系统通常用于将任务比如用户请求处理分配到多个服务器处理以提高网站、应用或者数据库的性能和可靠性。
+
+常见的负载均衡系统包括 3 种:
+
+1. **DNS 负载均衡** :一般用来实现地理级别的均衡。
+2. **硬件负载均衡** : 通过单独的硬件设备比如 F5 来实现负载均衡功能(硬件的价格一般很贵)。
+3. **软件负载均衡** :通过负载均衡软件比如 Nginx 来实现负载均衡功能。
+
+## 推荐阅读
+
+- [《凤凰架构》-负载均衡](http://icyfenix.cn/architect-perspective/general-architecture/diversion-system/load-balancing.html)
diff --git a/docs/idea-tutorial/idea-plugins/camel-case.md b/docs/idea-tutorial/idea-plugins/camel-case.md
new file mode 100644
index 00000000000..5456378f4b2
--- /dev/null
+++ b/docs/idea-tutorial/idea-plugins/camel-case.md
@@ -0,0 +1,27 @@
+---
+title: Camel Case:命名之间快速切换
+category: IDEA指南
+tag:
+ - IDEA
+ - IDEA插件
+---
+
+非常有用!这个插件可以实现包含6种常见命名格式之间的切换。并且,你还可以对转换格式进行相关配置(转换格式),如下图所示:
+
+
+
+有了这个插件之后,你只需要使用快捷键 `shift+option+u(mac)` / `shift+alt+u` 对准你要修改的变量或者方法名字,就能实现在多种格式之间切换了,如下图所示:
+
+
+
+如果你突然忘记快捷键的话,可以直接在IDEA的菜单栏的 Edit 部分找到。
+
+
+
+使用这个插件对开发效率提升高吗?拿我之前项目组的情况举个例子:
+
+我之前有一个项目组的测试名字是驼峰这种形式: `ShouldReturnTicketWhenRobotSaveBagGiven1LockersWith2FreeSpace` 。但是,使用驼峰形式命名测试方法的名字不太明显,一般建议用下划线_的形式: `should_return_ticket_when_robot_save_bag_given_1_lockers_with_2_free_space`
+
+如果我们不用这个插件,而是手动去一个一个改的话,工作量想必会很大,而且正确率也会因为手工的原因降低。
+
+>
diff --git a/docs/idea-tutorial/idea-plugins/code-glance.md b/docs/idea-tutorial/idea-plugins/code-glance.md
new file mode 100644
index 00000000000..9345ab427bb
--- /dev/null
+++ b/docs/idea-tutorial/idea-plugins/code-glance.md
@@ -0,0 +1,11 @@
+---
+title: CodeGlance:代码微型地图
+category: IDEA指南
+tag:
+ - IDEA
+ - IDEA插件
+---
+
+CodeGlance提供一个代码的微型地图,当你的类比较多的时候可以帮忙你快速定位到要去的位置。这个插件在我们日常做普通开发的时候用处不大,不过,在你阅读源码的时候还是很有用的,如下图所示:
+
+
\ No newline at end of file
diff --git a/docs/idea-tutorial/idea-plugins/code-statistic.md b/docs/idea-tutorial/idea-plugins/code-statistic.md
new file mode 100644
index 00000000000..1d60c81bad6
--- /dev/null
+++ b/docs/idea-tutorial/idea-plugins/code-statistic.md
@@ -0,0 +1,39 @@
+---
+title: Statistic:项目代码统计
+category: IDEA指南
+tag:
+ - IDEA
+ - IDEA插件
+---
+
+编程是一个很奇妙的事情,大部分的我们把大部分时间实际都花在了复制粘贴,而后修改代码上面。
+
+很多时候,我们并不关注代码质量,只要功能能实现,我才不管一个类的代码有多长、一个方法的代码有多长。
+
+因此,我们经常会碰到让自己想要骂街的项目,不过,说真的,你自己写的代码也有极大可能被后者 DISS。
+
+为了快速分析项目情况,判断这个项目是不是一个“垃圾”项目,有一个方法挺简单的。
+
+那就是**对代码的总行数、单个文件的代码行数、注释行数等信息进行统计。**
+
+**怎么统计呢?**
+
+首先想到的是 Excel 。不过,显然太麻烦了。
+
+**有没有专门用于代码统计的工具呢?**
+
+基于Perl语言开发的cloc(count lines of code)或许可以满足你的要求。
+
+**有没有什么更简单的办法呢?**
+
+如果你使用的是 IDEA 进行开发的话,推荐你可以使用一下 **Statistic** 这个插件。
+
+有了这个插件之后你可以非常直观地看到你的项目中所有类型的文件的信息比如数量、大小等等,可以帮助你更好地了解你们的项目。
+
+
+
+你还可以使用它看所有类的总行数、有效代码行数、注释行数、以及有效代码比重等等这些东西。
+
+
+
+如果,你担心插件过多影响IDEA速度的话,可以只在有代码统计需求的时候开启这个插件,其他时间禁用它就完事了!
\ No newline at end of file
diff --git a/docs/idea-tutorial/idea-plugins/git-commit-template.md b/docs/idea-tutorial/idea-plugins/git-commit-template.md
new file mode 100644
index 00000000000..c75dae11c79
--- /dev/null
+++ b/docs/idea-tutorial/idea-plugins/git-commit-template.md
@@ -0,0 +1,19 @@
+---
+title: Git Commit Template:提交代码格式规范
+category: IDEA指南
+tag:
+ - IDEA
+ - IDEA插件
+---
+
+没有安装这个插件之前,我们使用IDEA提供的Commit功能提交代码是下面这样的:
+
+
+
+使用了这个插件之后是下面这样的,提供了一个commit信息模板的输入框:
+
+
+
+完成之后的效果是这样的:
+
+
\ No newline at end of file
diff --git a/docs/idea-tutorial/idea-plugins/gson-format.md b/docs/idea-tutorial/idea-plugins/gson-format.md
new file mode 100644
index 00000000000..56750e1cb05
--- /dev/null
+++ b/docs/idea-tutorial/idea-plugins/gson-format.md
@@ -0,0 +1,32 @@
+---
+title: GsonFormat:JSON转对象
+category: IDEA指南
+tag:
+ - IDEA
+ - IDEA插件
+---
+
+GsonFormat 这个插件可以根据Gson库使用的要求,将JSONObject格式的String 解析成实体类。
+
+> 说明:2021.x 版本以上的 IDEA 可以使用:GsonFormatPlus
+
+这个插件使用起来非常简单,我们新建一个类,然后在类中使用快捷键 `option + s`(Mac)或`alt + s` (win)调出操作窗口(**必须在类中使用快捷键才有效**),如下图所示。
+
+
+
+这个插件是一个国人几年前写的,不过已经很久没有更新了,可能会因为IDEA的版本问题有一些小Bug。而且,这个插件无法将JSON转换为Kotlin(这个其实无关痛痒,IDEA自带的就有Java转Kotlin的功能)。
+
+
+
+另外一个与之相似的插件是 **:RoboPOJOGenerator** ,这个插件的更新频率比较快。
+
+`File-> new -> Generate POJO from JSON`
+
+
+
+然后将JSON格式的数据粘贴进去之后,配置相关属性之后选择“*Generate*”
+
+
+
+
+
diff --git a/docs/idea-tutorial/idea-plugins/idea-features-trainer.md b/docs/idea-tutorial/idea-plugins/idea-features-trainer.md
new file mode 100644
index 00000000000..a5cb4960c4d
--- /dev/null
+++ b/docs/idea-tutorial/idea-plugins/idea-features-trainer.md
@@ -0,0 +1,17 @@
+---
+title: IDE Features Trainer:IDEA 交互式教程
+category: IDEA指南
+tag:
+ - IDEA
+ - IDEA插件
+---
+
+**有了这个插件之后,你可以在 IDE 中以交互方式学习IDEA最常用的快捷方式和最基本功能。** 非常非常非常方便!强烈建议大家安装一个,尤其是刚开始使用IDEA的朋友。
+
+当我们安装了这个插件之后,你会发现我们的IDEA 编辑器的右边多了一个“**Learn**”的选项,我们点击这个选项就可以看到如下界面。
+
+
+
+我们选择“Editor Basics”进行,然后就可以看到如下界面,这样你就可以按照指示来练习了!非常不错!
+
+
\ No newline at end of file
diff --git a/docs/idea-tutorial/idea-plugins/idea-themes.md b/docs/idea-tutorial/idea-plugins/idea-themes.md
new file mode 100644
index 00000000000..ca38f19f51f
--- /dev/null
+++ b/docs/idea-tutorial/idea-plugins/idea-themes.md
@@ -0,0 +1,99 @@
+---
+title: IDEA主题推荐
+category: IDEA指南
+tag:
+ - IDEA
+ - IDEA插件
+---
+
+经常有小伙伴问我:“Guide哥,你的IDEA 主题怎么这么好看,能推荐一下不?”。就实在有点不耐烦了,才索性写了这篇文章。
+
+在这篇文章中,我精选了几个比较是和 Java 编码的 IDEA 主题供小伙伴们选择。另外,我自己用的是 One Dark theme 这款。
+
+**注意:以下主题按照使用人数降序排序。**
+
+## [Material Theme UI](https://plugins.jetbrains.com/plugin/8006-material-theme-ui)
+
+**推荐指数** :⭐⭐⭐⭐
+
+这是 IDEA 中使用人数最多的一款主题。
+
+当你安装完这个插件之后,你会发现这个主题本身又提供了多种相关的主题供你选择。
+
+
+
+ **Material Deep Ocean** 这款的效果图如下。默认的字体是真的小,小伙伴们需要自行调整一下。
+
+
+
+## [One Dark theme](https://plugins.jetbrains.com/plugin/11938-one-dark-theme)
+
+**推荐指数** :⭐⭐⭐⭐⭐
+
+我比较喜欢的一款(*黄色比较多?*)。 没有花里花哨,简单大气,看起来比较舒服。颜色搭配也很棒,适合编码!
+
+这款主题的效果图如下。
+
+
+
+## [Gradianto](https://plugins.jetbrains.com/plugin/12334-gradianto)
+
+**推荐指数** :⭐⭐⭐⭐⭐
+
+Gradianto这个主题的目标是在保持页面色彩比较层次分明的情况下,让我们因为代码而疲惫的双眼更加轻松。
+
+Gradianto附带了自然界的渐变色,看着挺舒服的。另外,这个主题本身也提供了多种相关的主题供你选择。
+
+
+
+**Gradianto Nature Green** 的效果图如下。
+
+
+
+## [Dark Purple Theme](https://plugins.jetbrains.com/plugin/12100-dark-purple-theme)
+
+**推荐指数** :⭐⭐⭐⭐⭐
+
+这是一款紫色色调的深色主题,喜欢紫色的小伙伴不要错过。
+
+这个主题的效果图如下。个人觉得整体颜色搭配的是比较不错的,适合编码!
+
+
+
+## [Hiberbee Theme](https://plugins.jetbrains.com/plugin/12118-hiberbee-theme)
+
+**推荐指数** :⭐⭐⭐⭐⭐
+
+一款受到了 Monokai Pro 和 MacOS Mojave启发的主题,是一款色彩层次分明的深色主题。
+
+这个主题的效果图如下。看着也是非常赞!适合编码!
+
+
+
+上面推荐的都是偏暗色系的主题,这里我再推荐两款浅色系的主题。
+
+## [Gray Theme](https://plugins.jetbrains.com/plugin/12103-gray-theme)
+
+**推荐指数** :⭐⭐⭐
+
+这是一款对比度比较低的一款浅色主题,不太适合代码阅读,毕竟这款主题是专门为在IntelliJ IDE中使用Markdown而设计的。
+
+这个主题的效果图如下。
+
+
+
+## [Roboticket Light Theme](https://plugins.jetbrains.com/plugin/12191-roboticket-light-theme)
+
+**推荐指数** :⭐⭐⭐
+
+这是一款对比度比较低的浅色主题,不太适合代码阅读。
+
+这个主题的效果图如下。
+
+
+
+## 后记
+
+我个人还是比较偏爱深色系的主题。
+
+小伙伴们比较喜欢哪款主题呢?可以在评论区简单聊聊不?如果你还有其他比较喜欢的主题也可以在评论区说出来供大家参考哦!
\ No newline at end of file
diff --git a/docs/idea-tutorial/idea-plugins/improve-code.md b/docs/idea-tutorial/idea-plugins/improve-code.md
new file mode 100644
index 00000000000..91b31b4e232
--- /dev/null
+++ b/docs/idea-tutorial/idea-plugins/improve-code.md
@@ -0,0 +1,153 @@
+---
+title: IDEA 代码优化插件推荐
+category: IDEA指南
+tag:
+ - IDEA
+ - IDEA插件
+ - 代码优化
+---
+
+## Lombok:帮你简化代码
+
+之前没有推荐这个插件的原因是觉得已经是人手必备的了。如果你要使用 Lombok 的话,不光是要安装这个插件,你的项目也要引入相关的依赖。
+
+```xml
+
+ org.projectlombok
+ lombok
+ true
+
+```
+
+使用 Lombok 能够帮助我们少写很多代码比如 Getter/Setter、Constructor等等。
+
+关于Lombok的使用,可以查看这篇文章:[《十分钟搞懂Java效率工具Lombok使用与原理》](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485385&idx=2&sn=a7c3fb4485ffd8c019e5541e9b1580cd&chksm=cea24802f9d5c1144eee0da52cfc0cc5e8ee3590990de3bb642df4d4b2a8cd07f12dd54947b9&token=1667678311&lang=zh_CN#rd)。
+
+
+## Codota:代码智能提示
+
+我一直在用的一个插件,可以说非常好用了(*我身边的很多大佬平时写代码也会用这个插件*)。
+
+Codota 这个插件用于智能代码补全,它基于数百万Java程序,能够根据程序上下文提示补全代码。相比于IDEA自带的智能提示来说,Codota 的提示更加全面一些。
+
+如果你觉得 IDEA 插件安装的太多比较卡顿的话,不用担心!Codota 插件还有一个对应的在线网站([https://www.codota.com/code](https://www.codota.com/code)),在这个网站上你可以根据代码关键字搜索相关代码示例,非常不错!
+
+我在工作中经常会用到,说实话确实给我带来了很大便利,比如我们搜索 `Files.readAllLines`相关的代码,搜索出来的结果如下图所示:
+
+
+
+另外,Codota 插件的基础功能都是免费的。你的代码也不会被泄露,这点你不用担心。
+
+简单来看看 Codota 插件的骚操作吧!
+
+### 代码智能补全
+
+我们使用`HttpUrlConnection ` 建立一个网络连接是真的样的:
+
+
+
+我们创建线程池现在变成下面这样:
+
+
+
+上面只是为了演示这个插件的强大,实际上创建线程池不推荐使用这种方式, 推荐使用 `ThreadPoolExecutor` 构造函数创建线程池。我下面要介绍的一个阿里巴巴的插件-**Alibaba Java Code Guidelines** 就检测出来了这个问题,所以,`Executors`下面用波浪线标记了出来。
+
+### 代码智能搜索
+
+除了,在写代码的时候智能提示之外。你还可以直接选中代码然后搜索相关代码示例。
+
+
+
+## Alibaba Java Code Guidelines:阿里巴巴 Java 代码规范
+
+阿里巴巴 Java 代码规范,对应的Github地址为:[https://github.com/alibaba/p3c](https://github.com/alibaba/p3c ) 。非常推荐安装!
+
+安装完成之后建议将与语言替换成中文,提示更加友好一点。
+
+
+
+根据官方描述:
+
+> 目前这个插件实现了开发手册中的的53条规则,大部分基于PMD实现,其中有4条规则基于IDEA实现,并且基于IDEA [Inspection](https://www.jetbrains.com/help/idea/code-inspection.html)实现了实时检测功能。部分规则实现了Quick Fix功能,对于可以提供Quick Fix但没有提供的,我们会尽快实现,也欢迎有兴趣的同学加入进来一起努力。 目前插件检测有两种模式:实时检测、手动触发。
+
+上述提到的开发手册也就是在Java开发领域赫赫有名的《阿里巴巴Java开发手册》。
+
+### 手动配置检测规则
+
+你还可以手动配置相关 inspection规则:
+
+
+
+### 使用效果
+
+这个插件会实时检测出我们的代码不匹配它的规则的地方,并且会给出修改建议。比如我们按照下面的方式去创建线程池的话,这个插件就会帮我们检测出来,如下图所示。
+
+
+
+这个可以对应上 《阿里巴巴Java开发手册》 这本书关于创建线程池的方式说明。
+
+
+
+## CheckStyle: Java代码格式规范
+
+### 为何需要CheckStyle插件?
+
+**CheckStyle 几乎是 Java 项目开发必备的一个插件了,它会帮助我们检查 Java 代码的格式是否有问题比如变量命名格式是否有问题、某一行代码的长度是否过长等等。**
+
+在项目上,**通过项目开发人员自我约束来规范代码格式必然是不靠谱的!** 因此,我们非常需要这样一款工具来帮助我们规范代码格式。
+
+如果你看过我写的轮子的话,可以发现我为每一个项目都集成了 CheckStyle,并且设置了 **Git Commit 钩子**,保证在提交代码之前代码格式没有问题。
+
+> **Guide哥造的轮子**(*代码简洁,结构清晰,欢迎学习,欢迎一起完善*):
+>
+> 1. [guide-rpc-framework](https://github.com/Snailclimb/guide-rpc-framework) :A custom RPC framework implemented by Netty+Kyro+Zookeeper.(一款基于 Netty+Kyro+Zookeeper 实现的自定义 RPC 框架-附详细实现过程和相关教程)
+> 2. [jsoncat](https://github.com/Snailclimb/jsoncat) :仿 Spring Boot 但不同于 Spring Boot 的一个轻量级的 HTTP 框架
+>
+> **Git 钩子**: Git 能在特定的重要动作比如commit、push发生时触发自定义脚本。 钩子都被存储在 Git 目录下的 `hooks` 子目录中。 也即绝大部分项目中的 `.git/hooks` 。
+
+### 如何在Maven/Gradle项目中集成 Checksytle?
+
+一般情况下,我们都是将其集成在项目中,并设置相应的 Git 钩子。网上有相应的介绍文章,这里就不多提了。
+
+如果你觉得网上的文章不直观的话,可以参考我上面提到了两个轮子:
+
+1. [guide-rpc-framework](https://github.com/Snailclimb/guide-rpc-framework) :Maven项目集成 Checksytle。
+2. [jsoncat](https://github.com/Snailclimb/jsoncat) :Gradle项目集成 Checksytle。
+
+如果你在项目中集成了 Checksytle 的话,每次检测会生成一个 HTML格式的文件告诉你哪里的代码格式不对,这样看着非常不直观。通过 Checksytle插件的话可以非常直观的将项目中存在格式问题的地方显示出来。
+
+
+
+如果你只是自己在本地使用,并不想在项目中集成 Checksytle 的话也可以,只需要下载一个 Checksytle插件就足够了。
+
+### 如何安装?
+
+我们直接在 IDEA 的插件市场即可找到这个插件。我这里已经安装好了。
+
+
+
+安装插件之后重启 IDEA,你会发现就可以在底部菜单栏找到 CheckStyle 了。
+
+
+
+### 如何自定义检测规则?
+
+如果你需要自定义代码格式检测规则的话,可以按照如下方式进行配置(你可以导入用于自定义检测规则的`CheckStyle.xml`文件)。
+
+
+
+### 使用效果
+
+配置完成之后,按照如下方式使用这个插件!
+
+
+
+可以非常清晰的看到:CheckStyle 插件已经根据我们自定义的规则将项目中的代码存在格式问题的地方都检测了出来。
+
+## SonarLint:帮你优化代码
+
+SonarLint 帮助你发现代码的错误和漏洞,就像是代码拼写检查器一样,SonarLint 可以实时显示出代码的问题,并提供清晰的修复指导,以便你提交代码之前就可以解决它们。
+
+
+
+并且,很多项目都集成了 SonarQube,SonarLint 可以很方便地与 SonarQube 集成。
\ No newline at end of file
diff --git a/docs/idea-tutorial/idea-plugins/interface-beautification.md b/docs/idea-tutorial/idea-plugins/interface-beautification.md
new file mode 100644
index 00000000000..3f3f62fb679
--- /dev/null
+++ b/docs/idea-tutorial/idea-plugins/interface-beautification.md
@@ -0,0 +1,67 @@
+---
+title: IDEA 界面美化插件推荐
+category: IDEA指南
+tag:
+ - IDEA
+ - IDEA插件
+ - 代码优化
+---
+
+
+## Background Image Plus:背景图片
+
+我这里推荐使用国人 Jack Chu 基于 Background Image Plus 开发的最新版本,适用于 2021.x 版本的 IDEA。
+
+前面几个下载量比较高的,目前都还未支持 2021.x 版本的 IDEA。
+
+
+
+通过这个插件,你可以将 IDEA 背景设置为指定的图片,支持随机背景。
+
+效果图如下:
+
+
+
+如果你想要设置随机背景的话,可以通过 IDEA 设置页 **Settings** -> **Appearance & Behaviour** -> **Background Image Plus** 自定义设置项,随机显示目录下的图片为背景图。
+
+## Power Mode II : 代码特效
+
+使用了这个插件之后,写代码会自带特效,适用于 2021.x 版本的 IDEA。 2021.x 版本之前,可以使用 **activate-power-mode** 。
+
+
+
+你可以通过 IDEA 设置页 **Settings** -> **Appearance & Behaviour** -> **Power Mode II** 自定义设置项。
+
+
+
+## Nyan Progress Bar : 进度条美化
+
+可以让你拥有更加漂亮的进度条。
+
+
+
+## Grep Console:控制台输出处理
+
+可以说是必备的一个 IDEA 插件,非常实用!
+
+这个插件主要的功能有两个:
+
+**1. 自定义设置控制台输出颜色**
+
+我们可以在设置中进行相关的配置:
+
+
+
+配置完成之后的 log warn 的效果对比图如下:
+
+
+
+**2. 过滤控制台输出**
+
+
+
+## Rainbow Brackets : 彩虹括号
+
+使用各种鲜明的颜色来展示你的括号,效果图如下。可以看出代码层级变得更加清晰了,可以说非常实用友好了!
+
+
\ No newline at end of file
diff --git a/docs/idea-tutorial/idea-plugins/jclasslib.md b/docs/idea-tutorial/idea-plugins/jclasslib.md
new file mode 100644
index 00000000000..c5f29d2b657
--- /dev/null
+++ b/docs/idea-tutorial/idea-plugins/jclasslib.md
@@ -0,0 +1,93 @@
+---
+title: jclasslib :一款IDEA字节码查看神器
+category: IDEA指南
+tag:
+ - IDEA
+ - IDEA插件
+ - 字节码
+---
+
+由于后面要分享的一篇文章中用到了这篇文章要推荐的一个插件,所以这里分享一下。非常实用!你会爱上它的!
+
+
+
+**开始推荐 IDEA 字节码查看神器之前,先来回顾一下 Java 字节码是啥。**
+
+## 何为 Java 字节码?
+
+Java 虚拟机(JVM)是运行 Java 字节码的虚拟机。JVM 有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。
+
+**什么是字节码?采用字节码的好处是什么?**
+
+> 在 Java 中,JVM 可以理解的代码就叫做`字节码`(即扩展名为 `.class` 的文件),它不面向任何特定的处理器,只面向虚拟机。Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以 Java 程序运行时比较高效,而且,由于字节码并不针对一种特定的机器,因此,Java 程序无须重新编译便可在多种不同操作系统的计算机上运行。
+
+**Java 程序从源代码到运行一般有下面 3 步:**
+
+
+
+## 为什么要查看 Java 字节码?
+
+我们在平时学习的时候,经常需要查看某个 java 类的字节码文件。查看字节码文件更容易让我们搞懂 java 代码背后的原理比如搞懂 java 中的各种语法糖的本质。
+
+## 如何查看 Java 字节码?
+
+如果我们通过命令行来查看某个 class 的字节码文件的话,可以直接通过 `javap` 命令,不过这种方式太原始了,效率十分低,并且看起来不直观。
+
+下面介绍两种使用 IDEA 查看类对应字节码文件的方式(_`javap`这种方式就不提了_)。
+
+我们以这段代码作为案例:
+
+```java
+public class Main {
+ public static void main(String[] args) {
+ Integer i = null;
+ Boolean flag = false;
+ System.out.println(flag ? 0 : i);
+ }
+}
+```
+
+上面这段代码由于使用三目运算符不当导致诡异了 NPE 异常。为了搞清楚事情的原因,我们来看其对应的字节码。
+
+### 使用 IDEA 自带功能
+
+我们点击 `View -> Show Bytecode` 即可通过 IDEA 查看某个类对应的字节码文件。
+
+> 需要注意的是:**查看某个类对应的字节码文件之前确保它已经被编译过。**
+
+
+
+稍等几秒钟之后,你就可以直观到看到对应的类的字节码内容了。
+
+
+
+从字节码中可以看出,我圈出来的位置发生了 **拆箱操作** 。
+
+> - **装箱**:将基本类型用它们对应的引用类型包装起来;
+> - **拆箱**:将包装类型转换为基本数据类型;
+
+详细解释下就是:`flag ? 0 : i` 这行代码中,0 是基本数据类型 int,返回数据的时候 i 会被强制拆箱成 int 类型,由于 i 的值是 null,因此就抛出了 NPE 异常。
+
+```java
+Integer i = null;
+Boolean flag = false;
+System.out.println(flag ? 0 : i);
+```
+
+如果,我们把代码中 `flag` 变量的值修改为 true 的话,就不会存在 NPE 问题了,因为会直接返回 0,不会进行拆箱操作。
+
+### 使用 IDEA 插件 jclasslib(推荐)
+
+相比于 IDEA 自带的查看类字节的功能,我更推荐 `jclasslib` 这个插件,非常棒!
+
+**使用 `jclasslib` 不光可以直观地查看某个类对应的字节码文件,还可以查看类的基本信息、常量池、接口、属性、函数等信息。**
+
+
+
+我们直接在 IDEA 的插件市场即可找到这个插件。我这里已经安装好了。
+
+
+
+安装完成之后,重启 IDEA。点击`View -> Show Bytecode With jclasslib` 即可通过`jclasslib` 查看某个类对应的字节码文件。
+
+
\ No newline at end of file
diff --git a/docs/idea-tutorial/idea-plugins/maven-helper.md b/docs/idea-tutorial/idea-plugins/maven-helper.md
new file mode 100644
index 00000000000..d2b064a9934
--- /dev/null
+++ b/docs/idea-tutorial/idea-plugins/maven-helper.md
@@ -0,0 +1,19 @@
+---
+title: Maven Helper:解决 Maven 依赖冲突问题
+category: IDEA指南
+tag:
+ - IDEA
+ - IDEA插件
+ - Maven
+---
+
+
+**Maven Helper** 主要用来分析 Maven 项目的相关依赖,可以帮助我们解决 Maven 依赖冲突问题。
+
+
+
+**何为依赖冲突?**
+
+说白了就是你的项目使用的 2 个 jar 包引用了同一个依赖 h,并且 h 的版本还不一样,这个时候你的项目就存在两个不同版本的 h。这时 Maven 会依据依赖路径最短优先原则,来决定使用哪个版本的 Jar 包,而另一个无用的 Jar 包则未被使用,这就是所谓的依赖冲突。
+
+大部分情况下,依赖冲突可能并不会对系统造成什么异常,因为 Maven 始终选择了一个 Jar 包来使用。但是,不排除在某些特定条件下,会出现类似找不到类的异常,所以,只要存在依赖冲突,在我看来,最好还是解决掉,不要给系统留下隐患。
diff --git a/docs/idea-tutorial/idea-plugins/others.md b/docs/idea-tutorial/idea-plugins/others.md
new file mode 100644
index 00000000000..da505ff8dfe
--- /dev/null
+++ b/docs/idea-tutorial/idea-plugins/others.md
@@ -0,0 +1,21 @@
+---
+title: 其他
+category: IDEA指南
+tag:
+ - IDEA
+ - IDEA插件
+---
+
+
+1. **leetcode editor** :提供在线 Leetcode 刷题功能,比较方便我们刷题,不过我试用之后发现有一些小 bug,个人感觉还是直接在网站找题目刷来的痛快一些。
+2. **A Search with Github** :直接通过 Github搜索相关代码。
+3. **stackoverflow** : 选中相关内容后单击右键即可快速跳转到 stackoverflow 。
+4. **CodeStream** :让code review变得更加容易。
+5. **Code screenshots** :代码片段保存为图片。
+6. **GitToolBox** :Git工具箱
+7. **OK, Gradle!** :搜索Java库用于Gradle项目
+8. **Java Stream Debugger** : Java8 Stream调试器
+9. **EasyCode** : Easycode 可以直接对数据的表生成entity、controller、service、dao、mapper无需任何编码,简单而强大。更多内容可以查看这篇文章:[《懒人 IDEA 插件插件:EasyCode 一键帮你生成所需代码~》](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247486205&idx=1&sn=0ff2f87f0d82a1bd9c0c44328ef69435&chksm=cea24536f9d5cc20c6cc7669f0d4167d747fe8b8c05a64546c0162d694aa96044a2862e24b57&token=1862674725&lang=zh_CN#rd)
+10. **JFormDesigner** :Swing GUI 在线编辑器。
+11. **VisualVM Launcher** : Java性能分析神器。
+12. ......
diff --git a/docs/idea-tutorial/idea-plugins/pictures/Codota1.gif b/docs/idea-tutorial/idea-plugins/pictures/Codota1.gif
new file mode 100644
index 00000000000..7b2947fe3a0
Binary files /dev/null and b/docs/idea-tutorial/idea-plugins/pictures/Codota1.gif differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/Codota2.png b/docs/idea-tutorial/idea-plugins/pictures/Codota2.png
new file mode 100644
index 00000000000..0fe37e36047
Binary files /dev/null and b/docs/idea-tutorial/idea-plugins/pictures/Codota2.png differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/Codota3.png b/docs/idea-tutorial/idea-plugins/pictures/Codota3.png
new file mode 100644
index 00000000000..44d1093e492
Binary files /dev/null and b/docs/idea-tutorial/idea-plugins/pictures/Codota3.png differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/Codota4.gif b/docs/idea-tutorial/idea-plugins/pictures/Codota4.gif
new file mode 100644
index 00000000000..1322b60f5e0
Binary files /dev/null and b/docs/idea-tutorial/idea-plugins/pictures/Codota4.gif differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/GsonFormat1.png b/docs/idea-tutorial/idea-plugins/pictures/GsonFormat1.png
new file mode 100644
index 00000000000..c8e678acb68
Binary files /dev/null and b/docs/idea-tutorial/idea-plugins/pictures/GsonFormat1.png differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/GsonFormat2.gif b/docs/idea-tutorial/idea-plugins/pictures/GsonFormat2.gif
new file mode 100644
index 00000000000..7c371162d9e
Binary files /dev/null and b/docs/idea-tutorial/idea-plugins/pictures/GsonFormat2.gif differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/IDE-Features-Trainer1.png b/docs/idea-tutorial/idea-plugins/pictures/IDE-Features-Trainer1.png
new file mode 100644
index 00000000000..27f888a9499
Binary files /dev/null and b/docs/idea-tutorial/idea-plugins/pictures/IDE-Features-Trainer1.png differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/IDE-Features-Trainer2.png b/docs/idea-tutorial/idea-plugins/pictures/IDE-Features-Trainer2.png
new file mode 100644
index 00000000000..6d59082c281
Binary files /dev/null and b/docs/idea-tutorial/idea-plugins/pictures/IDE-Features-Trainer2.png differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/JavaStreamDebugger.gif b/docs/idea-tutorial/idea-plugins/pictures/JavaStreamDebugger.gif
new file mode 100644
index 00000000000..6e910e72ed5
Binary files /dev/null and b/docs/idea-tutorial/idea-plugins/pictures/JavaStreamDebugger.gif differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/Presentation-Assistant.gif b/docs/idea-tutorial/idea-plugins/pictures/Presentation-Assistant.gif
new file mode 100644
index 00000000000..335523ea5f9
Binary files /dev/null and b/docs/idea-tutorial/idea-plugins/pictures/Presentation-Assistant.gif differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/RestfulToolkit1.png b/docs/idea-tutorial/idea-plugins/pictures/RestfulToolkit1.png
new file mode 100644
index 00000000000..5a69bc0595a
Binary files /dev/null and b/docs/idea-tutorial/idea-plugins/pictures/RestfulToolkit1.png differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/RestfulToolkit2.png b/docs/idea-tutorial/idea-plugins/pictures/RestfulToolkit2.png
new file mode 100644
index 00000000000..6c8aefd7638
Binary files /dev/null and b/docs/idea-tutorial/idea-plugins/pictures/RestfulToolkit2.png differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/RestfulToolkit3.png b/docs/idea-tutorial/idea-plugins/pictures/RestfulToolkit3.png
new file mode 100644
index 00000000000..b6cf628e76a
Binary files /dev/null and b/docs/idea-tutorial/idea-plugins/pictures/RestfulToolkit3.png differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/RestfulToolkit4.png b/docs/idea-tutorial/idea-plugins/pictures/RestfulToolkit4.png
new file mode 100644
index 00000000000..be15f46bdd2
Binary files /dev/null and b/docs/idea-tutorial/idea-plugins/pictures/RestfulToolkit4.png differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/RoboPOJOGenerator1.png b/docs/idea-tutorial/idea-plugins/pictures/RoboPOJOGenerator1.png
new file mode 100644
index 00000000000..c2d7704766b
Binary files /dev/null and b/docs/idea-tutorial/idea-plugins/pictures/RoboPOJOGenerator1.png differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/RoboPOJOGenerator2.png b/docs/idea-tutorial/idea-plugins/pictures/RoboPOJOGenerator2.png
new file mode 100644
index 00000000000..4334b3db390
Binary files /dev/null and b/docs/idea-tutorial/idea-plugins/pictures/RoboPOJOGenerator2.png differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/Statistic1.png b/docs/idea-tutorial/idea-plugins/pictures/Statistic1.png
new file mode 100644
index 00000000000..47521ee25dd
Binary files /dev/null and b/docs/idea-tutorial/idea-plugins/pictures/Statistic1.png differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/Statistic2.png b/docs/idea-tutorial/idea-plugins/pictures/Statistic2.png
new file mode 100644
index 00000000000..f815aa1c722
Binary files /dev/null and b/docs/idea-tutorial/idea-plugins/pictures/Statistic2.png differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/camel-case/camel-case1.png b/docs/idea-tutorial/idea-plugins/pictures/camel-case/camel-case1.png
new file mode 100644
index 00000000000..7fbbbba97e5
Binary files /dev/null and b/docs/idea-tutorial/idea-plugins/pictures/camel-case/camel-case1.png differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/camel-case/camel-case2.gif b/docs/idea-tutorial/idea-plugins/pictures/camel-case/camel-case2.gif
new file mode 100644
index 00000000000..9565231e9d7
Binary files /dev/null and b/docs/idea-tutorial/idea-plugins/pictures/camel-case/camel-case2.gif differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/camel-case/camel-case3.png b/docs/idea-tutorial/idea-plugins/pictures/camel-case/camel-case3.png
new file mode 100644
index 00000000000..d4b2fd27ab3
Binary files /dev/null and b/docs/idea-tutorial/idea-plugins/pictures/camel-case/camel-case3.png differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/check-style.png b/docs/idea-tutorial/idea-plugins/pictures/check-style.png
new file mode 100644
index 00000000000..e0c17b64096
Binary files /dev/null and b/docs/idea-tutorial/idea-plugins/pictures/check-style.png differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/code-glance.png b/docs/idea-tutorial/idea-plugins/pictures/code-glance.png
new file mode 100644
index 00000000000..afdf1a1bca0
Binary files /dev/null and b/docs/idea-tutorial/idea-plugins/pictures/code-glance.png differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/git-commit-template/Git-Commit-Template1.png b/docs/idea-tutorial/idea-plugins/pictures/git-commit-template/Git-Commit-Template1.png
new file mode 100644
index 00000000000..26da6cd1b06
Binary files /dev/null and b/docs/idea-tutorial/idea-plugins/pictures/git-commit-template/Git-Commit-Template1.png differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/git-commit-template/Git-Commit-Template2.png b/docs/idea-tutorial/idea-plugins/pictures/git-commit-template/Git-Commit-Template2.png
new file mode 100644
index 00000000000..c0e436432a5
Binary files /dev/null and b/docs/idea-tutorial/idea-plugins/pictures/git-commit-template/Git-Commit-Template2.png differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/git-commit-template/Git-Commit-Template3.png b/docs/idea-tutorial/idea-plugins/pictures/git-commit-template/Git-Commit-Template3.png
new file mode 100644
index 00000000000..17f81a23469
Binary files /dev/null and b/docs/idea-tutorial/idea-plugins/pictures/git-commit-template/Git-Commit-Template3.png differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/grep-console/grep-console.gif b/docs/idea-tutorial/idea-plugins/pictures/grep-console/grep-console.gif
new file mode 100644
index 00000000000..293c134207f
Binary files /dev/null and b/docs/idea-tutorial/idea-plugins/pictures/grep-console/grep-console.gif differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/grep-console/grep-console2.png b/docs/idea-tutorial/idea-plugins/pictures/grep-console/grep-console2.png
new file mode 100644
index 00000000000..aa338d615ee
Binary files /dev/null and b/docs/idea-tutorial/idea-plugins/pictures/grep-console/grep-console2.png differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/grep-console/grep-console3.png b/docs/idea-tutorial/idea-plugins/pictures/grep-console/grep-console3.png
new file mode 100644
index 00000000000..411128ed121
Binary files /dev/null and b/docs/idea-tutorial/idea-plugins/pictures/grep-console/grep-console3.png differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/maver-helper.png b/docs/idea-tutorial/idea-plugins/pictures/maver-helper.png
new file mode 100644
index 00000000000..35a3f6e083f
Binary files /dev/null and b/docs/idea-tutorial/idea-plugins/pictures/maver-helper.png differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/p3c/Alibaba-Java-Code-Guidelines1.png b/docs/idea-tutorial/idea-plugins/pictures/p3c/Alibaba-Java-Code-Guidelines1.png
new file mode 100644
index 00000000000..67c3571d836
Binary files /dev/null and b/docs/idea-tutorial/idea-plugins/pictures/p3c/Alibaba-Java-Code-Guidelines1.png differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/p3c/Alibaba-Java-Code-Guidelines2.png b/docs/idea-tutorial/idea-plugins/pictures/p3c/Alibaba-Java-Code-Guidelines2.png
new file mode 100644
index 00000000000..e4b1dc8c9a8
Binary files /dev/null and b/docs/idea-tutorial/idea-plugins/pictures/p3c/Alibaba-Java-Code-Guidelines2.png differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/p3c/Alibaba-Java-Code-Guidelines3.png b/docs/idea-tutorial/idea-plugins/pictures/p3c/Alibaba-Java-Code-Guidelines3.png
new file mode 100644
index 00000000000..5213aff02ca
Binary files /dev/null and b/docs/idea-tutorial/idea-plugins/pictures/p3c/Alibaba-Java-Code-Guidelines3.png differ
diff --git "a/docs/idea-tutorial/idea-plugins/pictures/p3c/\351\230\277\351\207\214\345\267\264\345\267\264\345\274\200\345\217\221\346\211\213\345\206\214-\347\272\277\347\250\213\346\261\240\345\210\233\345\273\272.png" "b/docs/idea-tutorial/idea-plugins/pictures/p3c/\351\230\277\351\207\214\345\267\264\345\267\264\345\274\200\345\217\221\346\211\213\345\206\214-\347\272\277\347\250\213\346\261\240\345\210\233\345\273\272.png"
new file mode 100644
index 00000000000..4d18c60b055
Binary files /dev/null and "b/docs/idea-tutorial/idea-plugins/pictures/p3c/\351\230\277\351\207\214\345\267\264\345\267\264\345\274\200\345\217\221\346\211\213\345\206\214-\347\272\277\347\250\213\346\261\240\345\210\233\345\273\272.png" differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/power-mode/Power-Mode-II.gif b/docs/idea-tutorial/idea-plugins/pictures/power-mode/Power-Mode-II.gif
new file mode 100644
index 00000000000..026c32e947e
Binary files /dev/null and b/docs/idea-tutorial/idea-plugins/pictures/power-mode/Power-Mode-II.gif differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/rainbow-brackets.png b/docs/idea-tutorial/idea-plugins/pictures/rainbow-brackets.png
new file mode 100644
index 00000000000..6529899a3ed
Binary files /dev/null and b/docs/idea-tutorial/idea-plugins/pictures/rainbow-brackets.png differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/save-actions/save-actions.png b/docs/idea-tutorial/idea-plugins/pictures/save-actions/save-actions.png
new file mode 100644
index 00000000000..cf765cb6d0e
Binary files /dev/null and b/docs/idea-tutorial/idea-plugins/pictures/save-actions/save-actions.png differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/save-actions/save-actions2.gif b/docs/idea-tutorial/idea-plugins/pictures/save-actions/save-actions2.gif
new file mode 100644
index 00000000000..93ae62cf6c2
Binary files /dev/null and b/docs/idea-tutorial/idea-plugins/pictures/save-actions/save-actions2.gif differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/translation/translation1.jpg b/docs/idea-tutorial/idea-plugins/pictures/translation/translation1.jpg
new file mode 100644
index 00000000000..7b512c115b1
Binary files /dev/null and b/docs/idea-tutorial/idea-plugins/pictures/translation/translation1.jpg differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/translation/translation2.png b/docs/idea-tutorial/idea-plugins/pictures/translation/translation2.png
new file mode 100644
index 00000000000..c92664718dd
Binary files /dev/null and b/docs/idea-tutorial/idea-plugins/pictures/translation/translation2.png differ
diff --git a/docs/idea-tutorial/idea-plugins/rest-devlop.md b/docs/idea-tutorial/idea-plugins/rest-devlop.md
new file mode 100644
index 00000000000..329552b33b2
--- /dev/null
+++ b/docs/idea-tutorial/idea-plugins/rest-devlop.md
@@ -0,0 +1,96 @@
+---
+title: RestfulToolkit:RESTful Web 服务辅助开发工具
+category: IDEA指南
+tag:
+ - IDEA
+ - IDEA插件
+---
+
+
+开始推荐这个 IDEA 插件之前,我觉得有必要花一小会时间简单聊聊 **REST** 这个我们经常打交道的概念。
+
+## REST 相关概念解读
+
+### 何为 REST?
+
+REST 即 **REpresentational State Transfer** 的缩写。这个词组的翻译过来就是"**表现层状态转化**"。
+
+这样理解起来甚是晦涩,实际上 REST 的全称是 **Resource Representational State Transfer** ,直白地翻译过来就是 **“资源”在网络传输中以某种“表现形式”进行“状态转移”** 。
+
+**有没有感觉很难理解?**
+
+没关系,看了我对 REST 涉及到的一些概念的解读之后你没准就能理解了!
+
+- **资源(Resource)** :我们可以把真实的对象数据称为资源。一个资源既可以是一个集合,也可以是单个个体。比如我们的班级 classes 是代表一个集合形式的资源,而特定的 class 代表单个个体资源。每一种资源都有特定的 URI(统一资源定位符)与之对应,如果我们需要获取这个资源,访问这个 URI 就可以了,比如获取特定的班级:`/class/12`。另外,资源也可以包含子资源,比如 `/classes/classId/teachers`:列出某个指定班级的所有老师的信息
+- **表现形式(Representational)**:"资源"是一种信息实体,它可以有多种外在表现形式。我们把"资源"具体呈现出来的形式比如 json,xml,image,txt 等等叫做它的"表现层/表现形式"。
+- **状态转移(State Transfer)** :大家第一眼看到这个词语一定会很懵逼?内心 BB:这尼玛是啥啊? **大白话来说 REST 中的状态转移更多地描述的服务器端资源的状态,比如你通过增删改查(通过 HTTP 动词实现)引起资源状态的改变。** (HTTP 协议是一个无状态的,所有的资源状态都保存在服务器端)
+
+### 何为 RESTful 架构?
+
+满足 REST 风格的架构设计就可以称为 RESTful 架构:
+
+1. 每一个 URI 代表一种资源;
+2. 客户端和服务器之间,传递这种资源的某种表现形式比如 json,xml,image,txt 等等;
+3. 客户端通过特定的 HTTP 动词,对服务器端资源进行操作,实现"表现层状态转化"。
+
+### 何为 RESTful Web 服务?
+
+基于 REST 架构的 Web 服务就被称为 RESTful Web 服务。
+
+## RESTful Web 服务辅助开发工具
+
+### 安装
+
+这个插件的名字叫做 “**RestfulToolkit**” 。我们直接在 IDEA 的插件市场即可找到这个插件。如下图所示。
+
+> 如果你因为网络问题没办法使用 IDEA 自带的插件市场的话,也可以通过[IDEA 插件市场的官网](https://plugins.jetbrains.com/idea)手动下载安装。
+
+
+
+### 简单使用
+
+#### URL 跳转到对应方法
+
+根据 URL 直接跳转到对应的方法定义 (Windows: `ctrl+\` or `ctrl+alt+n` Mac:`command+\` or `command+alt+n` )并且提供了一个服务的树形可视化显示窗口。 如下图所示。
+
+
+
+#### 作为 HTTP 请求工具
+
+这个插件还可以作为一个简单的 http 请求工具来使用。如下图所示。
+
+
+
+#### 复制生成 URL、复制方法参数...
+
+这个插件还提供了生成 URL、查询参数、请求体(RequestBody)等功能。
+
+举个例子。我们选中 `Controller` 中的某个请求对应的方法右击,你会发现多了几个可选项。当你选择`Generate & Copy Full URL`的话,就可以把整个请求的路径直接复制下来。eg:`http://localhost:9333/api/users?pageNum=1&pageSize=1` 。
+
+
+
+#### 将 Java 类转换为对应的 JSON 格式
+
+这个插件还为 Java 类上添加了 **Convert to JSON** 功能 。
+
+我们选中的某个类对应的方法然后右击,你会发现多了几个可选项。
+
+
+
+当我们选择`Convert to JSON`的话,你会得到如下 json 类型的数据:
+
+```json
+{
+ "username": "demoData",
+ "password": "demoData",
+ "rememberMe": true
+}
+```
+
+## 后记
+
+RESTFulToolkit 原作者不更新了,IDEA.201 及以上版本不再适配。
+
+因此,国内就有一个大佬参考 RESTFulToolkit 开发了一款类似的插件——RestfulTool(功能较少一些,不过够用了)。
+
+
\ No newline at end of file
diff --git a/docs/idea-tutorial/idea-plugins/save-actions.md b/docs/idea-tutorial/idea-plugins/save-actions.md
new file mode 100644
index 00000000000..fc149ad301b
--- /dev/null
+++ b/docs/idea-tutorial/idea-plugins/save-actions.md
@@ -0,0 +1,23 @@
+---
+title: Save Actions:优化文件保存
+category: IDEA指南
+tag:
+ - IDEA
+ - IDEA插件
+---
+
+
+真必备插件!可以帮助我们在保存文件的时候:
+
+1. 优化导入;
+2. 格式化代码;
+3. 执行一些quick fix
+4. ......
+
+这个插件是支持可配置的,我的配置如下:
+
+
+
+实际使用效果如下:
+
+
\ No newline at end of file
diff --git a/docs/idea-tutorial/idea-plugins/sequence-diagram.md b/docs/idea-tutorial/idea-plugins/sequence-diagram.md
new file mode 100644
index 00000000000..050d6161163
--- /dev/null
+++ b/docs/idea-tutorial/idea-plugins/sequence-diagram.md
@@ -0,0 +1,91 @@
+---
+title: SequenceDiagram:一键可以生成时序图
+category: IDEA指南
+tag:
+ - IDEA
+ - IDEA插件
+---
+
+
+
+在平时的学习/工作中,我们会经常面临如下场景:
+
+1. 阅读别人的代码
+2. 阅读框架源码
+3. 阅读自己很久之前写的代码。
+
+千万不要觉得工作就是单纯写代码,实际工作中,你会发现你的大部分时间实际都花在了阅读和理解已有代码上。
+
+为了能够更快更清晰地搞清对象之间的调用关系,我经常需要用到序列图。手动画序列图还是很麻烦费时间的,不过 IDEA 提供了一个叫做**SequenceDiagram** 的插件帮助我们解决这个问题。通过 SequenceDiagram 这个插件,我们一键可以生成时序图。
+
+## 何为序列图?
+
+网上对于序列图的定义有很多,我觉得都不太好理解,太抽象了。最神奇的是,大部分文章对于序列图的定义竟然都是一模一样,看来大家是充分发挥了写代码的“精髓”啊!
+
+我还是简单说一说我的理解吧!不过,说实话,我自己对于 Sequence Diagram 也不是很明朗。下面的描述如有问题和需要完善的地方,还请指出。
+
+> **序列图**(Sequence Diagram),亦称为**循序图**,是一种[UML](https://zh.m.wikipedia.org/wiki/UML)行为图。表示系统执行某个方法/操作(如登录操作)时,对象之间的顺序调用关系。
+>
+> 这个顺序调用关系可以这样理解:你需要执行系统中某个对象 a 提供的方法/操作 login(登录),但是这个对象又依赖了对象 b 提供的方法 getUser(获取用户)。因此,这里就有了 a -> b 调用关系之说。
+
+再举两个例子来说一下!
+
+下图是微信支付的业务流程时序图。这个图描述了微信支付相关角色(顾客,商家...)在微信支付场景下,基础支付和支付的的顺序调用关系。
+
+
+
+下图是我写的一个 HTTP 框架中的执行某个方法的序列图。这个图描述了我们在调用 `InterceptorFactory`类的 `loadInterceptors()` 方法的时候,所涉及到的类之间的调用关系。
+
+
+
+另外,国内一般更喜欢称呼序列图为"时序图"。
+
+- 如果你按照纯翻译的角度来说, sequence 这个单词并无"时间"的意思,只有序列,顺序等意思,因此也有人说“时序图”的说法是不准确的。
+- 如果从定义角度来说,时序图这个描述是没问题的。因为 Sequence Diagram 中每条消息的触发时机确实是按照时间顺序执行的。
+
+我觉得称呼 Sequence Diagram 为时序图或者序列图都是没问题的,不用太纠结。
+
+## 哪些场景下需要查看类的时序图?
+
+我们在很多场景下都需要时序图,比如说:
+
+1. **阅读源码** :阅读源码的时候,你可能需要查看调用目标方法涉及的相关类的调用关系。特别是在代码的调用层级比较多的时候,对于我们理解源码非常有用。(_题外话:实际工作中,大部分时间实际我们都花在了阅读理解已有代码上。_)
+2. **技术文档编写** :我们在写项目介绍文档的时候,为了让别人更容易理解你的代码,你需要根据核心方法为相关的类生成时序图来展示他们之间的调用关系。
+3. **梳理业务流程** :当我们的系统业务流程比较复杂的时候,我们可以通过序列图将系统中涉及的重要的角色和对象的之间关系可视化出来。
+4. ......
+
+## 如何使用 IDEA 根据类中方法生成时序图?
+
+**通过 SequenceDiagram 这个插件,我们一键可以生成时序图。**
+
+并且,你还可以:
+
+1. 点击时序图中的类/方法即可跳转到对应的地方。
+2. 从时序图中删除对应的类或者方法。
+3. 将生成的时序图导出为 PNG 图片格式。
+
+### 安装
+
+我们直接在 IDEA 的插件市场即可找到这个插件。我这里已经安装好了。
+
+> 如果你因为网络问题没办法使用 IDEA 自带的插件市场的话,也可以通过[IDEA 插件市场的官网](https://plugins.jetbrains.com/idea)手动下载安装。
+
+
+
+### 简单使用
+
+1. 选中方法名(注意不要选类名),然后点击鼠标右键,选择 **Sequence Diagram** 选项即可!
+
+
+
+2. 配置生成的序列图的一些基本的参数比如调用深度之后,我们点击 ok 即可!
+
+
+
+你还可以通过生成的时序图来定位到相关的代码,这对于我们阅读源码的时候尤其有帮助!
+
+
+
+时序图生成完成之后,你还可以选择将其导出为图片。
+
+
\ No newline at end of file
diff --git a/docs/idea-tutorial/idea-plugins/shortcut-key.md b/docs/idea-tutorial/idea-plugins/shortcut-key.md
new file mode 100644
index 00000000000..c7e585290e7
--- /dev/null
+++ b/docs/idea-tutorial/idea-plugins/shortcut-key.md
@@ -0,0 +1,56 @@
+---
+title: IDEA 快捷键相关插件
+category: IDEA指南
+tag:
+ - IDEA
+ - IDEA插件
+---
+
+
+相信我!下面这两个一定是IDEA必备的插件。
+
+## Key Promoter X:快捷键提示
+
+这个插件的功能主要是**在你本可以使用快捷键操作的地方提醒你用快捷键操作。**
+
+举个例子。我直接点击tab栏下的菜单打开 Version Control(版本控制) 的话,这个插件就会提示你可以用快捷键 `command+9`或者`shift+command+9`打开。如下图所示。
+
+
+
+除了这个很棒的功能之外,这个插件还有一个功能我觉得非常棒。
+
+它可以展示出哪些快捷键你忘记使用的次数最多!这样的话,你可以给予你忘记次数最多的那些快捷键更多的关注。
+
+我忘记最多的快捷键是debug的时候经常使用的 F8(Step Over)。如下图所示。
+
+
+
+关于快捷键,很多人不愿意去记,觉得单纯靠鼠标就完全够了。
+
+让我来说的话!我觉得如果你偶尔使用一两次 IDEA 的话,你完全没有必要纠结快捷键。
+
+但是,如果 IDEA 是你开发的主力,你经常需要使用的话,相信我,掌握常用的一些快捷键真的很重要!
+
+不说多的,**熟练掌握IDEA的一些最常见的快捷键,你的工作效率至少提升 30 %。**
+
+**除了工作效率的提升之外,使用快捷键会让我们显得更加专业。**
+
+你在使用快捷键进行操作的时候,是很帅,很酷啊!但是,当你用 IDEA 给别人演示一些操作的时候,你使用了快捷键的话,别人可能根本不知道你进行了什么快捷键操作。
+
+**怎么解决这个问题呢?**
+
+很简单!这个时候就轮到 **Presentation Assistant** 这个插件上场了!
+
+## Presentation Assistant:快捷键展示
+
+安装这个插件之后,你使用的快捷键操作都会被可视化地展示出来,非常适合自己在录制视频或者给别人展示代码的时候使用。
+
+举个例子。我使用快捷键 `command+9`打开 Version Control ,使用了这个插件之后的效果如下图所示。
+
+
+
+从上图可以很清晰地看到,IDEA 的底部中间的位置将我刚刚所使用的快捷键给展示了出来。
+
+并且,**这个插件会展示出 Mac 和 Win/Linux 两种不同的版本的快捷键。**
+
+因此,不论你的操作系统是 Mac 还是 Win/Linux ,这款插件都能满足你的需求。
\ No newline at end of file
diff --git a/docs/idea-tutorial/idea-plugins/translation.md b/docs/idea-tutorial/idea-plugins/translation.md
new file mode 100644
index 00000000000..7de2619a3b5
--- /dev/null
+++ b/docs/idea-tutorial/idea-plugins/translation.md
@@ -0,0 +1,28 @@
+---
+title: Translation:翻译
+category: IDEA指南
+tag:
+ - IDEA
+ - IDEA插件
+---
+
+
+有了这个插件之后,你再也不用在编码的时候打开浏览器查找某个单词怎么拼写、某句英文注释什么意思了。
+
+并且,这个插件支持多种翻译源:
+
+1. Google 翻译
+2. Youdao 翻译
+3. Baidu 翻译
+
+除了翻译功能之外还提供了语音朗读、单词本等实用功能。这个插件的Github地址是:[https://github.com/YiiGuxing/TranslationPlugin](https://github.com/YiiGuxing/TranslationPlugin) (貌似是国人开发的,很赞)。
+
+**使用方法很简单!选中你要翻译的单词或者句子,使用快捷键 `command+ctrl+u(mac)` / `shift+ctrl+y(win/linux)`** (如果你忘记了快捷的话,鼠标右键操作即可!)
+
+
+
+**如果需要快速打开翻译框,使用快捷键`command+ctrl+i(mac)`/`ctrl + shift + o(win/linux)`**
+
+
+
+如果你需要将某个重要的单词添加到生词本的话,只需要点击单词旁边的收藏按钮即可!
\ No newline at end of file
diff --git a/docs/idea-tutorial/idea-tips/idea-plug-in-development-intro.md b/docs/idea-tutorial/idea-tips/idea-plug-in-development-intro.md
new file mode 100644
index 00000000000..8f8822c5946
--- /dev/null
+++ b/docs/idea-tutorial/idea-tips/idea-plug-in-development-intro.md
@@ -0,0 +1,213 @@
+# IDEA 插件开发入门
+
+我这个人没事就喜欢推荐一些好用的 [IDEA 插件](https://mp.weixin.qq.com/mp/appmsgalbum?action=getalbum&album_id=1319419426898329600&__biz=Mzg2OTA0Njk0OA==#wechat_redirect)给大家。这些插件极大程度上提高了我们的生产效率以及编码舒适度。
+
+**不知道大家有没有想过自己开发一款 IDEA 插件呢?**
+
+我自己想过,但是没去尝试过。刚好有一位读者想让我写一篇入门 IDEA 开发的文章,所以,我在周末就花了一会时间简单了解一下。
+
+
+
+不过,**这篇文章只是简单带各位小伙伴入门一下 IDEA 插件开发**,个人精力有限,暂时不会深入探讨太多。如果你已经有 IDEA 插件开发的相关经验的话,这篇文章就可以不用看了,因为会浪费你 3 分钟的时间。
+
+好的废话不多说!咱们直接开始!
+
+## 01 新建一个基于 Gradle 的插件项目
+
+这里我们基于 Gradle 进行插件开发,这也是 IntelliJ 官方的推荐的插件开发解决方案。
+
+**第一步,选择 Gradle 项目类型并勾选上相应的依赖。**
+
+
+
+**第二步,填写项目相关的属性比如 GroupId、ArtifactId。**
+
+
+
+**第三步,静静等待项目下载相关依赖。**
+
+第一次创建 IDEA 插件项目的话,这一步会比较慢。因为要下载 IDEA 插件开发所需的 SDK 。
+
+## 02 插件项目结构概览
+
+新建完成的项目结构如下图所示。
+
+
+
+这里需要额外注意的是下面这两个配置文件。
+
+**`plugin.xml` :插件的核心配置文件。通过它可以配置插件名称、插件介绍、插件作者信息、Action 等信息。**
+
+```xml
+
+ github.javaguide.my-first-idea-plugin
+
+ Beauty
+
+ JavaGuide
+
+
+ 这尼玛是什么垃圾插件!!!
+ ]]>
+
+
+ com.intellij.modules.platform
+
+
+
+
+
+
+
+
+
+```
+
+**`build.gradle` :项目依赖配置文件。通过它可以配置项目第三方依赖、插件版本、插件版本更新记录等信息。**
+
+```groovy
+plugins {
+ id 'java'
+ id 'org.jetbrains.intellij' version '0.6.3'
+}
+
+group 'github.javaguide'
+// 当前插件版本
+version '1.0-SNAPSHOT'
+
+repositories {
+ mavenCentral()
+}
+
+// 项目依赖
+dependencies {
+ testCompile group: 'junit', name: 'junit', version: '4.12'
+}
+
+// See https://github.com/JetBrains/gradle-intellij-plugin/
+// 当前开发该插件的 IDEA 版本
+intellij {
+ version '2020.1.2'
+}
+patchPluginXml {
+ // 版本更新记录
+ changeNotes """
+ Add change notes here.
+ most HTML tags may be used """
+}
+```
+
+没有开发过 IDEA 插件的小伙伴直接看这两个配置文件内容可能会有点蒙。所以,我专门找了一个 IDEA 插件市场提供的现成插件来说明一下。小伙伴们对照下面这张图来看下面的配置文件内容就非常非常清晰了。
+
+
+
+这就非常贴心了!如果这都不能让你点赞,我要这文章有何用!
+
+
+
+## 03 手动创建 Action
+
+我们可以把 Action 看作是 IDEA 提高的事件响应处理器,通过 Action 我们可以自定义一些事件处理逻辑/动作。比如说你点击某个菜单的时候,我们进行一个展示对话框的操作。
+
+**第一步,右键`java`目录并选择 new 一个 Action**
+
+![]()
+
+**第二步,配置 Action 相关信息比如展示名称。**
+
+![配置动作属性 (1)]()
+
+创建完成之后,我们的 `plugin.xml` 的 ``节点下会自动生成我们刚刚创建的 Action 信息:
+
+```xml
+
+
+
+
+
+
+```
+
+并且 `java` 目录下为生成一个叫做 `HelloAction` 的类。并且,这个类继承了 `AnAction` ,并覆盖了 `actionPerformed()` 方法。这个 `actionPerformed` 方法就好比 JS 中的 `onClick` 方法,会在你点击的时候被触发对应的动作。
+
+我简单对`actionPerformed` 方法进行了修改,添加了一行代码。这行代码很简单,就是显示 1 个对话框并展示一些信息。
+
+```java
+public class HelloAction extends AnAction {
+
+ @Override
+ public void actionPerformed(AnActionEvent e) {
+ //显示对话框并展示对应的信息
+ Messages.showInfoMessage("素材不够,插件来凑!", "Hello");
+ }
+}
+
+```
+
+另外,我们上面也说了,每个动作都会归属到一个 Group 中,这个 Group 可以简单看作 IDEA 中已经存在的菜单。
+
+举个例子。我上面创建的 Action 的所属 Group 是 **ToolsMenu(Tools)** 。这样的话,我们创建的 Action 所在的位置就在 Tools 这个菜单下。
+
+
+
+再举个例子。加入我上面创建的 Action 所属的 Group 是**MainMenu** (IDEA 最上方的主菜单栏)下的 **FileMenu(File)** 的话。
+
+```xml
+
+
+
+
+
+
+```
+
+我们创建的 Action 所在的位置就在 File 这个菜单下。
+
+
+
+## 04 验收成果
+
+点击 `Gradle -> runIde` 就会启动一个默认了这个插件的 IDEA。然后,你可以在这个 IDEA 上实际使用这个插件了。
+
+
+
+效果如下:
+
+
+
+我们点击自定义的 Hello Action 的话就会弹出一个对话框并展示出我们自定义的信息。
+
+
+
+## 05 完善一下
+
+想要弄点界面花里胡哨一下, 我们还可以通过 Swing 来写一个界面。
+
+这里我们简单实现一个聊天机器人。代码的话,我是直接参考的我大二刚学 Java 那会写的一个小项目(_当时写的代码实在太烂了!就很菜!_)。
+
+
+
+首先,你需要在[图灵机器人官网](http://www.tuling123.com/ "图灵机器人官网")申请一个机器人。(_其他机器人也一样,感觉这个图灵机器人没有原来好用了,并且免费调用次数也不多_)
+
+
+
+然后,简单写一个方法来请求调用机器人。由于代码比较简单,我这里就不放出来了,大家简单看一下效果就好。
+
+
+
+## 06 深入学习
+
+如果你想要深入学习的 IDEA 插件的话,可以看一下官网文档:[https://jetbrains.org/intellij/sdk/docs/basics/basics.html ](https://jetbrains.org/intellij/sdk/docs/basics/basics.html "https://jetbrains.org/intellij/sdk/docs/basics/basics.html") 。
+
+这方面的资料还是比较少的。除了官方文档的话,你还可以简单看看下面这几篇文章:
+
+- [8 条经验轻松上手 IDEA 插件开发](https://developer.aliyun.com/article/777850?spm=a2c6h.12873581.0.dArticle777850.118d6446r096V4&groupCode=alitech "8 条经验轻松上手 IDEA 插件开发")
+- [IDEA 插件开发入门教程](https://blog.xiaohansong.com/idea-plugin-development.html "IDEA 插件开发入门教程")
+
+## 07 后记
+
+我们开发 IDEA 插件主要是为了让 IDEA 更加好用,比如有些框架使用之后可以减少重复代码的编写、有些主题类型的插件可以让你的 IDEA 更好看。
+
+我这篇文章的这个案例说实话只是为了让大家简单入门一下 IDEA 开发,没有任何实际应用意义。**如果你想要开发一个不错的 IDEA 插件的话,还要充分发挥想象,利用 IDEA 插件平台的能力。**
diff --git a/docs/idea-tutorial/idea-tips/idea-refractor-intro.md b/docs/idea-tutorial/idea-tips/idea-refractor-intro.md
new file mode 100644
index 00000000000..7c3d4a66590
--- /dev/null
+++ b/docs/idea-tutorial/idea-tips/idea-refractor-intro.md
@@ -0,0 +1,75 @@
+# IDEA 重构入门
+
+我们在使用 IDEA 进行重构之前,先介绍一个方便我们进行重构的快捷键:`ctrl+t(mac)/ctrl+shift+alt+t`(如果忘记快捷键的话,鼠标右键也能找到重构选项),使用这个快捷键可以快速调出常用重构的选项,如下图所示:
+
+
+
+### 重命名(rename)
+
+快捷键:**Shift + F6(mac) / Shift + F6(windows/Linux):** 对类、变量或者方法名重命名。
+
+
+
+### 提取相关重构手段
+
+这部分的快捷键实际很好记忆,我是这样记忆的:
+
+前面两个键位是 `command + option(mac) / ctrl + alt (Windows/Linux)` 是固定的,只有后面一个键位会变比如Extract constant (提取变量)就是 c(constant)、Extract variable (提取变量)就是 v(variable)。
+
+#### 提取常量(extract constant)
+
+1. **使用场景** :提取未经过定义就直接出现的常量。提取常量使得你的编码更易读,避免硬编码。
+2. **快捷键:** `command + option+ c(mac)/ ctrl + alt + c(Windows/Linux)`
+
+**示例:**
+
+
+
+#### 提取参数(exact parameter)
+
+1. **使用场景** :提取参数到方法中。
+2. **快捷键:** `command + option+ p(mac)/ ctrl + alt + p(Windows/Linux)`
+
+
+
+#### 提取变量(exact variable)
+
+1. **使用场景** :提取多次出现的表达式。
+2. **快捷键:** `command + option+ v(mac) / ctrl + alt + v(Windows/Linux) `
+
+**示例:**
+
+
+
+#### 提取属性(exact field)
+
+1. **使用场景** :把当前表达式提取成为类的一个属性。
+2. **快捷键:** `command + option+ f(mac) / ctrl + alt + f(Windows/Linux) `
+
+**示例:**
+
+
+
+
+**示例:**
+
+
+
+#### 提取方法(exact method)
+
+1. **使用场景** :1个或者多个表达式可以提取为一个方法。 提取方法也能使得你的编码更易读,更加语义化。
+2. **快捷键:** `command + option+ m(mac)/ ctrl + alt + m(Windows/Linux)`
+
+**示例:**
+
+
+
+#### 提取接口(exact interface)
+
+1. **使用场景** :想要把一个类中的1个或多个方法提取到一个接口中的时候。
+2. **快捷键:** `command + option+ m(mac)/ ctrl + alt + m(Windows/Linux)`
+
+**示例:**
+
+
+
diff --git a/docs/idea-tutorial/idea-tips/idea-source-code-reading-skills.md b/docs/idea-tutorial/idea-tips/idea-source-code-reading-skills.md
new file mode 100644
index 00000000000..0064598c423
--- /dev/null
+++ b/docs/idea-tutorial/idea-tips/idea-source-code-reading-skills.md
@@ -0,0 +1,189 @@
+# IDEA源码阅读技巧
+
+项目有个新来了一个小伙伴,他看我查看项目源代码的时候,各种骚操作“花里胡哨”的。于是他向我请教,想让我分享一下我平时使用 IDEA 看源码的小技巧。
+
+## 基本操作
+
+这一部分的内容主要是一些我平时看源码的时候常用的快捷键/小技巧!非常好用!
+
+掌握这些快捷键/小技巧,看源码的效率提升一个等级!
+
+### 查看当前类的层次结构
+
+| 使用频率 | 相关快捷键 |
+| -------- | ---------- |
+| ⭐⭐⭐⭐⭐ | `Ctrl + H` |
+
+平时,我们阅读源码的时候,经常需要查看类的层次结构。就比如我们遇到抽象类或者接口的时候,经常需要查看其被哪些类实现。
+
+拿 Spring 源码为例,`BeanDefinition` 是一个关于 Bean 属性/定义的接口。
+
+```java
+public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
+ ......
+}
+```
+
+如果我们需要查看 `BeanDefinition` 被哪些类实现的话,只需要把鼠标移动到 `BeanDefinition` 类名上,然后使用快捷键 `Ctrl + H` 即可。
+
+
+
+同理,如果你想查看接口 `BeanDefinition` 继承的接口 `AttributeAccessor` 被哪些类实现的话,只需要把鼠标移动到 `AttributeAccessor` 类名上,然后使用快捷键 `Ctrl + H` 即可。
+
+### 查看类结构
+
+| 使用频率 | 相关快捷键 |
+| -------- | ------------------------------------- |
+| ⭐⭐⭐⭐ | `Alt + 7`(Win) / `Command +7` (Mac) |
+
+类结构可以让我们快速了解到当前类的方法、变量/常量,非常使用!
+
+我们在对应的类的任意位置使用快捷键 `Alt + 7`(Win) / `Command +7` (Mac)即可。
+
+
+
+### 快速检索类
+
+| 使用频率 | 相关快捷键 |
+| -------- | ---------------------------------------- |
+| ⭐⭐⭐⭐⭐ | `Ctrl + N` (Win) / `Command + O` (Mac) |
+
+使用快捷键 `Ctrl + N` (Win) / `Command + O` (Mac)可以快速检索类/文件。
+
+
+
+### 关键字检索
+
+| 使用频率 | 相关快捷键 |
+| -------- | ---------- |
+| ⭐⭐⭐⭐⭐ | 见下文 |
+
+- 当前文件下检索 : `Ctrl + F` (Win) / `Command + F` (Mac)
+- 全局的文本检索 : `Ctrl + Shift + F` (Win) / `Command + Shift + F` (Mac)
+
+### 查看方法/类的实现类
+
+| 使用频率 | 相关快捷键 |
+| -------- | -------------------------------------------------- |
+| ⭐⭐⭐⭐ | `Ctrl + Alt + B` (Win) / `Command + Alt + B` (Mac) |
+
+如果我们想直接跳转到某个方法/类的实现类,直接在方法名或者类名上使用快捷键 `Ctrl + Alt + B/鼠标左键` (Win) / `Command + Alt + B/鼠标左键` (Mac) 即可。
+
+如果对应的方法/类只有一个实现类的话,会直接跳转到对应的实现类。
+
+比如 `BeanDefinition` 接口的 `getBeanClassName()` 方法只被 `AbstractBeanDefinition` 抽象类实现,我们对这个方法使用快捷键就可以直接跳转到 `AbstractBeanDefinition` 抽象类中对应的实现方法。
+
+```java
+public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
+ @Nullable
+ String getBeanClassName();
+ ......
+}
+```
+
+如果对应的方法/类有多个实现类的话,IDEA 会弹出一个选择框让你选择。
+
+比如 `BeanDefinition` 接口的 `getParentName()` 方法就有多个不同的实现。
+
+
+
+### 查看方法被使用的情况
+
+| 使用频率 | 相关快捷键 |
+| -------- | ---------- |
+| ⭐⭐⭐⭐ | `Alt + F7` |
+
+我们可以通过直接在方法名上使用快捷键 `Alt + F7` 来查看这个方法在哪些地方被调用过。
+
+
+
+### 查看最近使用的文件
+
+| 使用频率 | 相关快捷键 |
+| -------- | -------------------------------------- |
+| ⭐⭐⭐⭐⭐ | `Ctrl + E`(Win) / `Command +E` (Mac) |
+
+你可以通过快捷键 `Ctrl + E`(Win) / `Command +E` (Mac)来显示 IDEA 最近使用的一些文件。
+
+
+
+### 查看图表形式的类继承链
+
+| 使用频率 | 相关快捷键 |
+| -------- | ------------------------ |
+| ⭐⭐⭐⭐ | 相关快捷键较多,不建议记 |
+
+点击类名 **右键** ,选择 **Shw Diagrams** 即可查看图表形式的类继承链。
+
+
+
+你还可以对图表进行一些操作。比如,你可以点击图表中具体的类 **右键**,然后选择显示它的实现类或者父类。
+
+
+
+再比如你还可以选择是否显示类中的属性、方法、内部类等等信息。
+
+
+
+如果你想跳转到对应类的源码的话,直接点击图表中具体的类 **右键** ,然后选择 **Jump to Source** 。
+
+
+
+## 插件推荐
+
+### 一键生成方法的序列图
+
+**序列图**(Sequence Diagram),亦称为**循序图**,是一种 UML 行为图。表示系统执行某个方法/操作(如登录操作)时,对象之间的顺序调用关系。
+
+这个顺序调用关系可以这样理解:你需要执行系统中某个对象 a 提供的方法/操作 login(登录),但是这个对象又依赖了对象 b 提供的方法 getUser(获取用户)。因此,这里就有了 a -> b 调用关系之说。
+
+我们可以通过 **SequenceDiagram** 这个插件一键生成方法的序列图。
+
+> 如果你因为网络问题没办法使用 IDEA 自带的插件市场的话,也可以通过 IDEA 插件市场的官网手动下载安装。
+
+
+
+**如何使用呢?**
+
+1、选中方法名(注意不要选类名),然后点击鼠标右键,选择 **Sequence Diagram** 选项即可!
+
+
+
+2、配置生成的序列图的一些基本的参数比如调用深度之后,我们点击 ok 即可!
+
+
+
+3、你还可以通过生成的时序图来定位到相关的代码,这对于我们阅读源码的时候尤其有帮助!
+
+
+
+4、时序图生成完成之后,你还可以选择将其导出为图片。
+
+
+
+相关阅读:[《安利一个 IDEA 骚操作:一键生成方法的序列图》](https://mp.weixin.qq.com/s/SG1twZczqdup_EQAOmNERg) 。
+
+### 项目代码统计
+
+为了快速分析项目情况,我们可以对项目的 **代码的总行数、单个文件的代码行数、注释行数等信息进行统计。**
+
+**Statistic** 这个插件来帮助我们实现这一需求。
+
+
+
+有了这个插件之后你可以非常直观地看到你的项目中所有类型的文件的信息比如数量、大小等等,可以帮助你更好地了解你们的项目。
+
+
+
+你还可以使用它看所有类的总行数、有效代码行数、注释行数、以及有效代码比重等等这些东西。
+
+
+
+如果,你担心插件过多影响 IDEA 速度的话,可以只在有代码统计需求的时候开启这个插件,其他时间禁用它就完事了!
+
+相关阅读:[快速识别烂项目!试试这款项目代码统计 IDEA 插件](https://mp.weixin.qq.com/s/fVEeMW6elhu79I-rTZB40A)
+
+
+
+
+
diff --git a/docs/idea-tutorial/idea-tips/pictures/exact/exact-field.gif b/docs/idea-tutorial/idea-tips/pictures/exact/exact-field.gif
new file mode 100644
index 00000000000..770df36522d
Binary files /dev/null and b/docs/idea-tutorial/idea-tips/pictures/exact/exact-field.gif differ
diff --git a/docs/idea-tutorial/idea-tips/pictures/exact/exact-interface.gif b/docs/idea-tutorial/idea-tips/pictures/exact/exact-interface.gif
new file mode 100644
index 00000000000..678b93de0a8
Binary files /dev/null and b/docs/idea-tutorial/idea-tips/pictures/exact/exact-interface.gif differ
diff --git a/docs/idea-tutorial/idea-tips/pictures/exact/exact-method.gif b/docs/idea-tutorial/idea-tips/pictures/exact/exact-method.gif
new file mode 100644
index 00000000000..3748903e21d
Binary files /dev/null and b/docs/idea-tutorial/idea-tips/pictures/exact/exact-method.gif differ
diff --git a/docs/idea-tutorial/idea-tips/pictures/exact/exact-parameter.gif b/docs/idea-tutorial/idea-tips/pictures/exact/exact-parameter.gif
new file mode 100644
index 00000000000..578b5ccca83
Binary files /dev/null and b/docs/idea-tutorial/idea-tips/pictures/exact/exact-parameter.gif differ
diff --git a/docs/idea-tutorial/idea-tips/pictures/exact/exact-variable.gif b/docs/idea-tutorial/idea-tips/pictures/exact/exact-variable.gif
new file mode 100644
index 00000000000..7326761ef55
Binary files /dev/null and b/docs/idea-tutorial/idea-tips/pictures/exact/exact-variable.gif differ
diff --git a/docs/idea-tutorial/idea-tips/pictures/exact/extract-constant.gif b/docs/idea-tutorial/idea-tips/pictures/exact/extract-constant.gif
new file mode 100644
index 00000000000..6752a385e74
Binary files /dev/null and b/docs/idea-tutorial/idea-tips/pictures/exact/extract-constant.gif differ
diff --git a/docs/idea-tutorial/idea-tips/pictures/refractor-help.png b/docs/idea-tutorial/idea-tips/pictures/refractor-help.png
new file mode 100644
index 00000000000..032319487ae
Binary files /dev/null and b/docs/idea-tutorial/idea-tips/pictures/refractor-help.png differ
diff --git a/docs/idea-tutorial/idea-tips/pictures/rename.gif b/docs/idea-tutorial/idea-tips/pictures/rename.gif
new file mode 100644
index 00000000000..c8a61b12863
Binary files /dev/null and b/docs/idea-tutorial/idea-tips/pictures/rename.gif differ
diff --git a/docs/idea-tutorial/readme.md b/docs/idea-tutorial/readme.md
new file mode 100644
index 00000000000..a5297b6bbed
--- /dev/null
+++ b/docs/idea-tutorial/readme.md
@@ -0,0 +1,11 @@
+---
+icon: creative
+category: IDEA指南
+---
+
+# IntelliJ IDEA 使用指南 | 必备插件推荐 | 插件开发入门 | 重构小技巧 | 源码阅读技巧
+
+分享一下自己使用 IDEA 的一些经验,希望对大家有帮助!
+
+- Github 地址:https://github.com/CodingDocs/awesome-idea-tutorial
+- 码云地址:https://gitee.com/SnailClimb/awesome-idea-tutorial (Github 无法访问或者访问速度比较慢的小伙伴可以看码云上的对应内容)
diff --git a/docs/java/BIO-NIO-AIO.md b/docs/java/BIO-NIO-AIO.md
deleted file mode 100644
index ee8e751cd6d..00000000000
--- a/docs/java/BIO-NIO-AIO.md
+++ /dev/null
@@ -1,346 +0,0 @@
-熟练掌握 BIO,NIO,AIO 的基本概念以及一些常见问题是你准备面试的过程中不可或缺的一部分,另外这些知识点也是你学习 Netty 的基础。
-
-
-
-- [BIO,NIO,AIO 总结](#bionioaio-总结)
- - [1. BIO \(Blocking I/O\)](#1-bio-blocking-io)
- - [1.1 传统 BIO](#11-传统-bio)
- - [1.2 伪异步 IO](#12-伪异步-io)
- - [1.3 代码示例](#13-代码示例)
- - [1.4 总结](#14-总结)
- - [2. NIO \(New I/O\)](#2-nio-new-io)
- - [2.1 NIO 简介](#21-nio-简介)
- - [2.2 NIO的特性/NIO与IO区别](#22-nio的特性nio与io区别)
- - [1)Non-blocking IO(非阻塞IO)](#1non-blocking-io(非阻塞io))
- - [2)Buffer\(缓冲区\)](#2buffer缓冲区)
- - [3)Channel \(通道\)](#3channel-通道)
- - [4)Selectors\(选择器\)](#4selectors选择器)
- - [2.3 NIO 读数据和写数据方式](#23-nio-读数据和写数据方式)
- - [2.4 NIO核心组件简单介绍](#24-nio核心组件简单介绍)
- - [2.5 代码示例](#25-代码示例)
- - [3. AIO \(Asynchronous I/O\)](#3-aio-asynchronous-io)
- - [参考](#参考)
-
-
-
-
-# BIO,NIO,AIO 总结
-
- Java 中的 BIO、NIO和 AIO 理解为是 Java 语言对操作系统的各种 IO 模型的封装。程序员在使用这些 API 的时候,不需要关心操作系统层面的知识,也不需要根据不同操作系统编写不同的代码。只需要使用Java的API就可以了。
-
-在讲 BIO,NIO,AIO 之前先来回顾一下这样几个概念:同步与异步,阻塞与非阻塞。
-
-**同步与异步**
-
-- **同步:** 同步就是发起一个调用后,被调用者未处理完请求之前,调用不返回。
-- **异步:** 异步就是发起一个调用后,立刻得到被调用者的回应表示已接收到请求,但是被调用者并没有返回结果,此时我们可以处理其他的请求,被调用者通常依靠事件,回调等机制来通知调用者其返回结果。
-
-同步和异步的区别最大在于异步的话调用者不需要等待处理结果,被调用者会通过回调等机制来通知调用者其返回结果。
-
-**阻塞和非阻塞**
-
-- **阻塞:** 阻塞就是发起一个请求,调用者一直等待请求结果返回,也就是当前线程会被挂起,无法从事其他任务,只有当条件就绪才能继续。
-- **非阻塞:** 非阻塞就是发起一个请求,调用者不用一直等着结果返回,可以先去干其他事情。
-
-举个生活中简单的例子,你妈妈让你烧水,小时候你比较笨啊,在那里傻等着水开(**同步阻塞**)。等你稍微再长大一点,你知道每次烧水的空隙可以去干点其他事,然后只需要时不时来看看水开了没有(**同步非阻塞**)。后来,你们家用上了水开了会发出声音的壶,这样你就只需要听到响声后就知道水开了,在这期间你可以随便干自己的事情,你需要去倒水了(**异步非阻塞**)。
-
-
-## 1. BIO (Blocking I/O)
-
-同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。
-
-### 1.1 传统 BIO
-
-BIO通信(一请求一应答)模型图如下(图源网络,原出处不明):
-
-
-
-采用 **BIO 通信模型** 的服务端,通常由一个独立的 Acceptor 线程负责监听客户端的连接。我们一般通过在`while(true)` 循环中服务端会调用 `accept()` 方法等待接收客户端的连接的方式监听请求,请求一旦接收到一个连接请求,就可以建立通信套接字在这个通信套接字上进行读写操作,此时不能再接收其他客户端连接请求,只能等待同当前连接的客户端的操作执行完成, 不过可以通过多线程来支持多个客户端的连接,如上图所示。
-
-如果要让 **BIO 通信模型** 能够同时处理多个客户端请求,就必须使用多线程(主要原因是`socket.accept()`、`socket.read()`、`socket.write()` 涉及的三个主要函数都是同步阻塞的),也就是说它在接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理,处理完成之后,通过输出流返回应答给客户端,线程销毁。这就是典型的 **一请求一应答通信模型** 。我们可以设想一下如果这个连接不做任何事情的话就会造成不必要的线程开销,不过可以通过 **线程池机制** 改善,线程池还可以让线程的创建和回收成本相对较低。使用`FixedThreadPool` 可以有效的控制了线程的最大数量,保证了系统有限的资源的控制,实现了N(客户端请求数量):M(处理客户端请求的线程数量)的伪异步I/O模型(N 可以远远大于 M),下面一节"伪异步 BIO"中会详细介绍到。
-
-**我们再设想一下当客户端并发访问量增加后这种模型会出现什么问题?**
-
-在 Java 虚拟机中,线程是宝贵的资源,线程的创建和销毁成本很高,除此之外,线程的切换成本也是很高的。尤其在 Linux 这样的操作系统中,线程本质上就是一个进程,创建和销毁线程都是重量级的系统函数。如果并发访问量增加会导致线程数急剧膨胀可能会导致线程堆栈溢出、创建新线程失败等问题,最终导致进程宕机或者僵死,不能对外提供服务。
-
-### 1.2 伪异步 IO
-
-为了解决同步阻塞I/O面临的一个链路需要一个线程处理的问题,后来有人对它的线程模型进行了优化一一一后端通过一个线程池来处理多个客户端的请求接入,形成客户端个数M:线程池最大线程数N的比例关系,其中M可以远远大于N.通过线程池可以灵活地调配线程资源,设置线程的最大值,防止由于海量并发接入导致线程耗尽。
-
-伪异步IO模型图(图源网络,原出处不明):
-
-
-
-采用线程池和任务队列可以实现一种叫做伪异步的 I/O 通信框架,它的模型图如上图所示。当有新的客户端接入时,将客户端的 Socket 封装成一个Task(该任务实现java.lang.Runnable接口)投递到后端的线程池中进行处理,JDK 的线程池维护一个消息队列和 N 个活跃线程,对消息队列中的任务进行处理。由于线程池可以设置消息队列的大小和最大线程数,因此,它的资源占用是可控的,无论多少个客户端并发访问,都不会导致资源的耗尽和宕机。
-
-伪异步I/O通信框架采用了线程池实现,因此避免了为每个请求都创建一个独立线程造成的线程资源耗尽问题。不过因为它的底层仍然是同步阻塞的BIO模型,因此无法从根本上解决问题。
-
-### 1.3 代码示例
-
-下面代码中演示了BIO通信(一请求一应答)模型。我们会在客户端创建多个线程依次连接服务端并向其发送"当前时间+:hello world",服务端会为每个客户端线程创建一个线程来处理。代码示例出自闪电侠的博客,原地址如下:
-
-[https://www.jianshu.com/p/a4e03835921a](https://www.jianshu.com/p/a4e03835921a)
-
-**客户端**
-
-```java
-/**
- *
- * @author 闪电侠
- * @date 2018年10月14日
- * @Description:客户端
- */
-public class IOClient {
-
- public static void main(String[] args) {
- // TODO 创建多个线程,模拟多个客户端连接服务端
- new Thread(() -> {
- try {
- Socket socket = new Socket("127.0.0.1", 3333);
- while (true) {
- try {
- socket.getOutputStream().write((new Date() + ": hello world").getBytes());
- Thread.sleep(2000);
- } catch (Exception e) {
- }
- }
- } catch (IOException e) {
- }
- }).start();
-
- }
-
-}
-
-```
-
-**服务端**
-
-```java
-/**
- * @author 闪电侠
- * @date 2018年10月14日
- * @Description: 服务端
- */
-public class IOServer {
-
- public static void main(String[] args) throws IOException {
- // TODO 服务端处理客户端连接请求
- ServerSocket serverSocket = new ServerSocket(3333);
-
- // 接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理
- new Thread(() -> {
- while (true) {
- try {
- // 阻塞方法获取新的连接
- Socket socket = serverSocket.accept();
-
- // 每一个新的连接都创建一个线程,负责读取数据
- new Thread(() -> {
- try {
- int len;
- byte[] data = new byte[1024];
- InputStream inputStream = socket.getInputStream();
- // 按字节流方式读取数据
- while ((len = inputStream.read(data)) != -1) {
- System.out.println(new String(data, 0, len));
- }
- } catch (IOException e) {
- }
- }).start();
-
- } catch (IOException e) {
- }
-
- }
- }).start();
-
- }
-
-}
-```
-
-### 1.4 总结
-
-在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。
-
-
-
-## 2. NIO (New I/O)
-
-### 2.1 NIO 简介
-
- NIO是一种同步非阻塞的I/O模型,在Java 1.4 中引入了NIO框架,对应 java.nio 包,提供了 Channel , Selector,Buffer等抽象。
-
-NIO中的N可以理解为Non-blocking,不单纯是New。它支持面向缓冲的,基于通道的I/O操作方法。 NIO提供了与传统BIO模型中的 `Socket` 和 `ServerSocket` 相对应的 `SocketChannel` 和 `ServerSocketChannel` 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发。
-
-### 2.2 NIO的特性/NIO与IO区别
-
-如果是在面试中回答这个问题,我觉得首先肯定要从 NIO 流是非阻塞 IO 而 IO 流是阻塞 IO 说起。然后,可以从 NIO 的3个核心组件/特性为 NIO 带来的一些改进来分析。如果,你把这些都回答上了我觉得你对于 NIO 就有了更为深入一点的认识,面试官问到你这个问题,你也能很轻松的回答上来了。
-
-#### 1)Non-blocking IO(非阻塞IO)
-
-**IO流是阻塞的,NIO流是不阻塞的。**
-
-Java NIO使我们可以进行非阻塞IO操作。比如说,单线程中从通道读取数据到buffer,同时可以继续做别的事情,当数据读取到buffer中后,线程再继续处理数据。写数据也是一样的。另外,非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。
-
-Java IO的各种流是阻塞的。这意味着,当一个线程调用 `read()` 或 `write()` 时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了
-
-#### 2)Buffer(缓冲区)
-
-**IO 面向流(Stream oriented),而 NIO 面向缓冲区(Buffer oriented)。**
-
-Buffer是一个对象,它包含一些要写入或者要读出的数据。在NIO类库中加入Buffer对象,体现了新库与原I/O的一个重要区别。在面向流的I/O中·可以将数据直接写入或者将数据直接读到 Stream 对象中。虽然 Stream 中也有 Buffer 开头的扩展类,但只是流的包装类,还是从流读到缓冲区,而 NIO 却是直接读到 Buffer 中进行操作。
-
-在NIO厍中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的; 在写入数据时,写入到缓冲区中。任何时候访问NIO中的数据,都是通过缓冲区进行操作。
-
-最常用的缓冲区是 ByteBuffer,一个 ByteBuffer 提供了一组功能用于操作 byte 数组。除了ByteBuffer,还有其他的一些缓冲区,事实上,每一种Java基本类型(除了Boolean类型)都对应有一种缓冲区。
-
-#### 3)Channel (通道)
-
-NIO 通过Channel(通道) 进行读写。
-
-通道是双向的,可读也可写,而流的读写是单向的。无论读写,通道只能和Buffer交互。因为 Buffer,通道可以异步地读写。
-
-#### 4)Selector (选择器)
-
-NIO有选择器,而IO没有。
-
-选择器用于使用单个线程处理多个通道。因此,它需要较少的线程来处理这些通道。线程之间的切换对于操作系统来说是昂贵的。 因此,为了提高系统效率选择器是有用的。
-
-
-
-### 2.3 NIO 读数据和写数据方式
-通常来说NIO中的所有IO都是从 Channel(通道) 开始的。
-
-- 从通道进行数据读取 :创建一个缓冲区,然后请求通道读取数据。
-- 从通道进行数据写入 :创建一个缓冲区,填充数据,并要求通道写入数据。
-
-数据读取和写入操作图示:
-
-
-
-
-### 2.4 NIO核心组件简单介绍
-
-NIO 包含下面几个核心的组件:
-
-- Channel(通道)
-- Buffer(缓冲区)
-- Selector(选择器)
-
-整个NIO体系包含的类远远不止这三个,只能说这三个是NIO体系的“核心API”。我们上面已经对这三个概念进行了基本的阐述,这里就不多做解释了。
-
-### 2.5 代码示例
-
-代码示例出自闪电侠的博客,原地址如下:
-
-[https://www.jianshu.com/p/a4e03835921a](https://www.jianshu.com/p/a4e03835921a)
-
-客户端 IOClient.java 的代码不变,我们对服务端使用 NIO 进行改造。以下代码较多而且逻辑比较复杂,大家看看就好。
-
-```java
-/**
- *
- * @author 闪电侠
- * @date 2019年2月21日
- * @Description: NIO 改造后的服务端
- */
-public class NIOServer {
- public static void main(String[] args) throws IOException {
- // 1. serverSelector负责轮询是否有新的连接,服务端监测到新的连接之后,不再创建一个新的线程,
- // 而是直接将新连接绑定到clientSelector上,这样就不用 IO 模型中 1w 个 while 循环在死等
- Selector serverSelector = Selector.open();
- // 2. clientSelector负责轮询连接是否有数据可读
- Selector clientSelector = Selector.open();
-
- new Thread(() -> {
- try {
- // 对应IO编程中服务端启动
- ServerSocketChannel listenerChannel = ServerSocketChannel.open();
- listenerChannel.socket().bind(new InetSocketAddress(3333));
- listenerChannel.configureBlocking(false);
- listenerChannel.register(serverSelector, SelectionKey.OP_ACCEPT);
-
- while (true) {
- // 监测是否有新的连接,这里的1指的是阻塞的时间为 1ms
- if (serverSelector.select(1) > 0) {
- Set set = serverSelector.selectedKeys();
- Iterator keyIterator = set.iterator();
-
- while (keyIterator.hasNext()) {
- SelectionKey key = keyIterator.next();
-
- if (key.isAcceptable()) {
- try {
- // (1) 每来一个新连接,不需要创建一个线程,而是直接注册到clientSelector
- SocketChannel clientChannel = ((ServerSocketChannel) key.channel()).accept();
- clientChannel.configureBlocking(false);
- clientChannel.register(clientSelector, SelectionKey.OP_READ);
- } finally {
- keyIterator.remove();
- }
- }
-
- }
- }
- }
- } catch (IOException ignored) {
- }
- }).start();
- new Thread(() -> {
- try {
- while (true) {
- // (2) 批量轮询是否有哪些连接有数据可读,这里的1指的是阻塞的时间为 1ms
- if (clientSelector.select(1) > 0) {
- Set set = clientSelector.selectedKeys();
- Iterator keyIterator = set.iterator();
-
- while (keyIterator.hasNext()) {
- SelectionKey key = keyIterator.next();
-
- if (key.isReadable()) {
- try {
- SocketChannel clientChannel = (SocketChannel) key.channel();
- ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
- // (3) 面向 Buffer
- clientChannel.read(byteBuffer);
- byteBuffer.flip();
- System.out.println(
- Charset.defaultCharset().newDecoder().decode(byteBuffer).toString());
- } finally {
- keyIterator.remove();
- key.interestOps(SelectionKey.OP_READ);
- }
- }
-
- }
- }
- }
- } catch (IOException ignored) {
- }
- }).start();
-
- }
-}
-```
-
-为什么大家都不愿意用 JDK 原生 NIO 进行开发呢?从上面的代码中大家都可以看出来,是真的难用!除了编程复杂、编程模型难之外,它还有以下让人诟病的问题:
-
-- JDK 的 NIO 底层由 epoll 实现,该实现饱受诟病的空轮询 bug 会导致 cpu 飙升 100%
-- 项目庞大之后,自行实现的 NIO 很容易出现各类 bug,维护成本较高,上面这一坨代码我都不能保证没有 bug
-
-Netty 的出现很大程度上改善了 JDK 原生 NIO 所存在的一些让人难以忍受的问题。
-
-### 3. AIO (Asynchronous I/O)
-
-AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的IO模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。
-
-AIO 是异步IO的缩写,虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作,IO操作本身是同步的。(除了 AIO 其他的 IO 类型都是同步的,这一点可以从底层IO线程模型解释,推荐一篇文章:[《漫话:如何给女朋友解释什么是Linux的五种IO模型?》](https://mp.weixin.qq.com/s?__biz=Mzg3MjA4MTExMw==&mid=2247484746&idx=1&sn=c0a7f9129d780786cabfcac0a8aa6bb7&source=41#wechat_redirect) )
-
-查阅网上相关资料,我发现就目前来说 AIO 的应用还不是很广泛,Netty 之前也尝试使用过 AIO,不过又放弃了。
-
-## 参考
-
-- 《Netty 权威指南》第二版
-- https://zhuanlan.zhihu.com/p/23488863 (美团技术团队)
diff --git a/docs/java/Basis/Arrays,CollectionsCommonMethods.md b/docs/java/Basis/Arrays,CollectionsCommonMethods.md
deleted file mode 100644
index 0710de44a95..00000000000
--- a/docs/java/Basis/Arrays,CollectionsCommonMethods.md
+++ /dev/null
@@ -1,383 +0,0 @@
-
-
-- [Collections 工具类和 Arrays 工具类常见方法](#collections-工具类和-arrays-工具类常见方法)
- - [Collections](#collections)
- - [排序操作](#排序操作)
- - [查找,替换操作](#查找替换操作)
- - [同步控制](#同步控制)
- - [Arrays类的常见操作](#arrays类的常见操作)
- - [排序 : `sort()`](#排序--sort)
- - [查找 : `binarySearch()`](#查找--binarysearch)
- - [比较: `equals()`](#比较-equals)
- - [填充 : `fill()`](#填充--fill)
- - [转列表 `asList()`](#转列表-aslist)
- - [转字符串 `toString()`](#转字符串-tostring)
- - [复制 `copyOf()`](#复制-copyof)
-
-
-# Collections 工具类和 Arrays 工具类常见方法
-
-## Collections
-
-Collections 工具类常用方法:
-
-1. 排序
-2. 查找,替换操作
-3. 同步控制(不推荐,需要线程安全的集合类型时请考虑使用 JUC 包下的并发集合)
-
-### 排序操作
-
-```java
-void reverse(List list)//反转
-void shuffle(List list)//随机排序
-void sort(List list)//按自然排序的升序排序
-void sort(List list, Comparator c)//定制排序,由Comparator控制排序逻辑
-void swap(List list, int i , int j)//交换两个索引位置的元素
-void rotate(List list, int distance)//旋转。当distance为正数时,将list后distance个元素整体移到前面。当distance为负数时,将 list的前distance个元素整体移到后面。
-```
-
-**示例代码:**
-
-```java
- ArrayList arrayList = new ArrayList();
- arrayList.add(-1);
- arrayList.add(3);
- arrayList.add(3);
- arrayList.add(-5);
- arrayList.add(7);
- arrayList.add(4);
- arrayList.add(-9);
- arrayList.add(-7);
- System.out.println("原始数组:");
- System.out.println(arrayList);
- // void reverse(List list):反转
- Collections.reverse(arrayList);
- System.out.println("Collections.reverse(arrayList):");
- System.out.println(arrayList);
-
-
- Collections.rotate(arrayList, 4);
- System.out.println("Collections.rotate(arrayList, 4):");
- System.out.println(arrayList);
-
- // void sort(List list),按自然排序的升序排序
- Collections.sort(arrayList);
- System.out.println("Collections.sort(arrayList):");
- System.out.println(arrayList);
-
- // void shuffle(List list),随机排序
- Collections.shuffle(arrayList);
- System.out.println("Collections.shuffle(arrayList):");
- System.out.println(arrayList);
-
- // void swap(List list, int i , int j),交换两个索引位置的元素
- Collections.swap(arrayList, 2, 5);
- System.out.println("Collections.swap(arrayList, 2, 5):");
- System.out.println(arrayList);
-
- // 定制排序的用法
- Collections.sort(arrayList, new Comparator() {
-
- @Override
- public int compare(Integer o1, Integer o2) {
- return o2.compareTo(o1);
- }
- });
- System.out.println("定制排序后:");
- System.out.println(arrayList);
-```
-
-### 查找,替换操作
-
-```java
-int binarySearch(List list, Object key)//对List进行二分查找,返回索引,注意List必须是有序的
-int max(Collection coll)//根据元素的自然顺序,返回最大的元素。 类比int min(Collection coll)
-int max(Collection coll, Comparator c)//根据定制排序,返回最大元素,排序规则由Comparatator类控制。类比int min(Collection coll, Comparator c)
-void fill(List list, Object obj)//用指定的元素代替指定list中的所有元素。
-int frequency(Collection c, Object o)//统计元素出现次数
-int indexOfSubList(List list, List target)//统计target在list中第一次出现的索引,找不到则返回-1,类比int lastIndexOfSubList(List source, list target).
-boolean replaceAll(List list, Object oldVal, Object newVal), 用新元素替换旧元素
-```
-
-**示例代码:**
-
-```java
- ArrayList arrayList = new ArrayList();
- arrayList.add(-1);
- arrayList.add(3);
- arrayList.add(3);
- arrayList.add(-5);
- arrayList.add(7);
- arrayList.add(4);
- arrayList.add(-9);
- arrayList.add(-7);
- ArrayList arrayList2 = new ArrayList();
- arrayList2.add(-3);
- arrayList2.add(-5);
- arrayList2.add(7);
- System.out.println("原始数组:");
- System.out.println(arrayList);
-
- System.out.println("Collections.max(arrayList):");
- System.out.println(Collections.max(arrayList));
-
- System.out.println("Collections.min(arrayList):");
- System.out.println(Collections.min(arrayList));
-
- System.out.println("Collections.replaceAll(arrayList, 3, -3):");
- Collections.replaceAll(arrayList, 3, -3);
- System.out.println(arrayList);
-
- System.out.println("Collections.frequency(arrayList, -3):");
- System.out.println(Collections.frequency(arrayList, -3));
-
- System.out.println("Collections.indexOfSubList(arrayList, arrayList2):");
- System.out.println(Collections.indexOfSubList(arrayList, arrayList2));
-
- System.out.println("Collections.binarySearch(arrayList, 7):");
- // 对List进行二分查找,返回索引,List必须是有序的
- Collections.sort(arrayList);
- System.out.println(Collections.binarySearch(arrayList, 7));
-```
-
-### 同步控制
-
-Collections提供了多个`synchronizedXxx()`方法·,该方法可以将指定集合包装成线程同步的集合,从而解决多线程并发访问集合时的线程安全问题。
-
-我们知道 HashSet,TreeSet,ArrayList,LinkedList,HashMap,TreeMap 都是线程不安全的。Collections提供了多个静态方法可以把他们包装成线程同步的集合。
-
-**最好不要用下面这些方法,效率非常低,需要线程安全的集合类型时请考虑使用 JUC 包下的并发集合。**
-
-方法如下:
-
-```java
-synchronizedCollection(Collection c) //返回指定 collection 支持的同步(线程安全的)collection。
-synchronizedList(List list)//返回指定列表支持的同步(线程安全的)List。
-synchronizedMap(Map m) //返回由指定映射支持的同步(线程安全的)Map。
-synchronizedSet(Set s) //返回指定 set 支持的同步(线程安全的)set。
-```
-
-### Collections还可以设置不可变集合,提供了如下三类方法:
-
-```java
-emptyXxx(): 返回一个空的、不可变的集合对象,此处的集合既可以是List,也可以是Set,还可以是Map。
-singletonXxx(): 返回一个只包含指定对象(只有一个或一个元素)的不可变的集合对象,此处的集合可以是:List,Set,Map。
-unmodifiableXxx(): 返回指定集合对象的不可变视图,此处的集合可以是:List,Set,Map。
-上面三类方法的参数是原有的集合对象,返回值是该集合的”只读“版本。
-```
-
-**示例代码:**
-
-```java
- ArrayList arrayList = new ArrayList();
- arrayList.add(-1);
- arrayList.add(3);
- arrayList.add(3);
- arrayList.add(-5);
- arrayList.add(7);
- arrayList.add(4);
- arrayList.add(-9);
- arrayList.add(-7);
- HashSet integers1 = new HashSet<>();
- integers1.add(1);
- integers1.add(3);
- integers1.add(2);
- Map scores = new HashMap();
- scores.put("语文" , 80);
- scores.put("Java" , 82);
-
- //Collections.emptyXXX();创建一个空的、不可改变的XXX对象
- List list = Collections.emptyList();
- System.out.println(list);//[]
- Set objects = Collections.emptySet();
- System.out.println(objects);//[]
- Map objectObjectMap = Collections.emptyMap();
- System.out.println(objectObjectMap);//{}
-
- //Collections.singletonXXX();
- List> arrayLists = Collections.singletonList(arrayList);
- System.out.println(arrayLists);//[[-1, 3, 3, -5, 7, 4, -9, -7]]
- //创建一个只有一个元素,且不可改变的Set对象
- Set> singleton = Collections.singleton(arrayList);
- System.out.println(singleton);//[[-1, 3, 3, -5, 7, 4, -9, -7]]
- Map nihao = Collections.singletonMap("1", "nihao");
- System.out.println(nihao);//{1=nihao}
-
- //unmodifiableXXX();创建普通XXX对象对应的不可变版本
- List integers = Collections.unmodifiableList(arrayList);
- System.out.println(integers);//[-1, 3, 3, -5, 7, 4, -9, -7]
- Set integers2 = Collections.unmodifiableSet(integers1);
- System.out.println(integers2);//[1, 2, 3]
- Map objectObjectMap2 = Collections.unmodifiableMap(scores);
- System.out.println(objectObjectMap2);//{Java=82, 语文=80}
-
- //添加出现异常:java.lang.UnsupportedOperationException
-// list.add(1);
-// arrayLists.add(arrayList);
-// integers.add(1);
-```
-
-## Arrays类的常见操作
-1. 排序 : `sort()`
-2. 查找 : `binarySearch()`
-3. 比较: `equals()`
-4. 填充 : `fill()`
-5. 转列表: `asList()`
-6. 转字符串 : `toString()`
-7. 复制: `copyOf()`
-
-
-### 排序 : `sort()`
-
-```java
- // *************排序 sort****************
- int a[] = { 1, 3, 2, 7, 6, 5, 4, 9 };
- // sort(int[] a)方法按照数字顺序排列指定的数组。
- Arrays.sort(a);
- System.out.println("Arrays.sort(a):");
- for (int i : a) {
- System.out.print(i);
- }
- // 换行
- System.out.println();
-
- // sort(int[] a,int fromIndex,int toIndex)按升序排列数组的指定范围
- int b[] = { 1, 3, 2, 7, 6, 5, 4, 9 };
- Arrays.sort(b, 2, 6);
- System.out.println("Arrays.sort(b, 2, 6):");
- for (int i : b) {
- System.out.print(i);
- }
- // 换行
- System.out.println();
-
- int c[] = { 1, 3, 2, 7, 6, 5, 4, 9 };
- // parallelSort(int[] a) 按照数字顺序排列指定的数组(并行的)。同sort方法一样也有按范围的排序
- Arrays.parallelSort(c);
- System.out.println("Arrays.parallelSort(c):");
- for (int i : c) {
- System.out.print(i);
- }
- // 换行
- System.out.println();
-
- // parallelSort给字符数组排序,sort也可以
- char d[] = { 'a', 'f', 'b', 'c', 'e', 'A', 'C', 'B' };
- Arrays.parallelSort(d);
- System.out.println("Arrays.parallelSort(d):");
- for (char d2 : d) {
- System.out.print(d2);
- }
- // 换行
- System.out.println();
-
-```
-
-在做算法面试题的时候,我们还可能会经常遇到对字符串排序的情况,`Arrays.sort()` 对每个字符串的特定位置进行比较,然后按照升序排序。
-
-```java
-String[] strs = { "abcdehg", "abcdefg", "abcdeag" };
-Arrays.sort(strs);
-System.out.println(Arrays.toString(strs));//[abcdeag, abcdefg, abcdehg]
-```
-
-### 查找 : `binarySearch()`
-
-```java
- // *************查找 binarySearch()****************
- char[] e = { 'a', 'f', 'b', 'c', 'e', 'A', 'C', 'B' };
- // 排序后再进行二分查找,否则找不到
- Arrays.sort(e);
- System.out.println("Arrays.sort(e)" + Arrays.toString(e));
- System.out.println("Arrays.binarySearch(e, 'c'):");
- int s = Arrays.binarySearch(e, 'c');
- System.out.println("字符c在数组的位置:" + s);
-```
-
-### 比较: `equals()`
-
-```java
- // *************比较 equals****************
- char[] e = { 'a', 'f', 'b', 'c', 'e', 'A', 'C', 'B' };
- char[] f = { 'a', 'f', 'b', 'c', 'e', 'A', 'C', 'B' };
- /*
- * 元素数量相同,并且相同位置的元素相同。 另外,如果两个数组引用都是null,则它们被认为是相等的 。
- */
- // 输出true
- System.out.println("Arrays.equals(e, f):" + Arrays.equals(e, f));
-```
-
-### 填充 : `fill()`
-
-```java
- // *************填充fill(批量初始化)****************
- int[] g = { 1, 2, 3, 3, 3, 3, 6, 6, 6 };
- // 数组中所有元素重新分配值
- Arrays.fill(g, 3);
- System.out.println("Arrays.fill(g, 3):");
- // 输出结果:333333333
- for (int i : g) {
- System.out.print(i);
- }
- // 换行
- System.out.println();
-
- int[] h = { 1, 2, 3, 3, 3, 3, 6, 6, 6, };
- // 数组中指定范围元素重新分配值
- Arrays.fill(h, 0, 2, 9);
- System.out.println("Arrays.fill(h, 0, 2, 9);:");
- // 输出结果:993333666
- for (int i : h) {
- System.out.print(i);
- }
-```
-
-### 转列表 `asList()`
-
-```java
- // *************转列表 asList()****************
- /*
- * 返回由指定数组支持的固定大小的列表。
- * (将返回的列表更改为“写入数组”。)该方法作为基于数组和基于集合的API之间的桥梁,与Collection.toArray()相结合 。
- * 返回的列表是可序列化的,并实现RandomAccess 。
- * 此方法还提供了一种方便的方式来创建一个初始化为包含几个元素的固定大小的列表如下:
- */
- List stooges = Arrays.asList("Larry", "Moe", "Curly");
- System.out.println(stooges);
-```
-
-### 转字符串 `toString()`
-
-```java
- // *************转字符串 toString()****************
- /*
- * 返回指定数组的内容的字符串表示形式。
- */
- char[] k = { 'a', 'f', 'b', 'c', 'e', 'A', 'C', 'B' };
- System.out.println(Arrays.toString(k));// [a, f, b, c, e, A, C, B]
-```
-
-### 复制 `copyOf()`
-
-```java
- // *************复制 copy****************
- // copyOf 方法实现数组复制,h为数组,6为复制的长度
- int[] h = { 1, 2, 3, 3, 3, 3, 6, 6, 6, };
- int i[] = Arrays.copyOf(h, 6);
- System.out.println("Arrays.copyOf(h, 6);:");
- // 输出结果:123333
- for (int j : i) {
- System.out.print(j);
- }
- // 换行
- System.out.println();
- // copyOfRange将指定数组的指定范围复制到新数组中
- int j[] = Arrays.copyOfRange(h, 6, 11);
- System.out.println("Arrays.copyOfRange(h, 6, 11):");
- // 输出结果66600(h数组只有9个元素这里是从索引6到索引11复制所以不足的就为0)
- for (int j2 : j) {
- System.out.print(j2);
- }
- // 换行
- System.out.println();
-```
diff --git "a/docs/java/Basis/final\343\200\201static\343\200\201this\343\200\201super.md" "b/docs/java/Basis/final\343\200\201static\343\200\201this\343\200\201super.md"
deleted file mode 100644
index e9008064af7..00000000000
--- "a/docs/java/Basis/final\343\200\201static\343\200\201this\343\200\201super.md"
+++ /dev/null
@@ -1,346 +0,0 @@
-
-
-- [final,static,this,super 关键字总结](#finalstaticthissuper-关键字总结)
- - [final 关键字](#final-关键字)
- - [static 关键字](#static-关键字)
- - [this 关键字](#this-关键字)
- - [super 关键字](#super-关键字)
- - [参考](#参考)
-- [static 关键字详解](#static-关键字详解)
- - [static 关键字主要有以下四种使用场景](#static-关键字主要有以下四种使用场景)
- - [修饰成员变量和成员方法\(常用\)](#修饰成员变量和成员方法常用)
- - [静态代码块](#静态代码块)
- - [静态内部类](#静态内部类)
- - [静态导包](#静态导包)
- - [补充内容](#补充内容)
- - [静态方法与非静态方法](#静态方法与非静态方法)
- - [static{}静态代码块与{}非静态代码块\(构造代码块\)](#static静态代码块与非静态代码块构造代码块)
- - [参考](#参考-1)
-
-
-
-# final,static,this,super 关键字总结
-
-## final 关键字
-
-**final关键字主要用在三个地方:变量、方法、类。**
-
-1. **对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。**
-
-2. **当用final修饰一个类时,表明这个类不能被继承。final类中的所有成员方法都会被隐式地指定为final方法。**
-
-3. 使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的Java版本已经不需要使用final方法进行这些优化了)。类中所有的private方法都隐式地指定为final。
-
-## static 关键字
-
-**static 关键字主要有以下四种使用场景:**
-
-1. **修饰成员变量和成员方法:** 被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享,可以并且建议通过类名调用。被static 声明的成员变量属于静态成员变量,静态变量 存放在 Java 内存区域的方法区。调用格式:`类名.静态变量名` `类名.静态方法名()`
-2. **静态代码块:** 静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行(静态代码块—>非静态代码块—>构造方法)。 该类不管创建多少对象,静态代码块只执行一次.
-3. **静态内部类(static修饰类的话只能修饰内部类):** 静态内部类与非静态内部类之间存在一个最大的区别: 非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有。没有这个引用就意味着:1. 它的创建是不需要依赖外围类的创建。2. 它不能使用任何外围类的非static成员变量和方法。
-4. **静态导包(用来导入类中的静态资源,1.5之后的新特性):** 格式为:`import static` 这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法。
-
-## this 关键字
-
-this关键字用于引用类的当前实例。 例如:
-
-```java
-class Manager {
- Employees[] employees;
-
- void manageEmployees() {
- int totalEmp = this.employees.length;
- System.out.println("Total employees: " + totalEmp);
- this.report();
- }
-
- void report() { }
-}
-```
-
-在上面的示例中,this关键字用于两个地方:
-
-- this.employees.length:访问类Manager的当前实例的变量。
-- this.report():调用类Manager的当前实例的方法。
-
-此关键字是可选的,这意味着如果上面的示例在不使用此关键字的情况下表现相同。 但是,使用此关键字可能会使代码更易读或易懂。
-
-
-
-## super 关键字
-
-super关键字用于从子类访问父类的变量和方法。 例如:
-
-```java
-public class Super {
- protected int number;
-
- protected showNumber() {
- System.out.println("number = " + number);
- }
-}
-
-public class Sub extends Super {
- void bar() {
- super.number = 10;
- super.showNumber();
- }
-}
-```
-
-在上面的例子中,Sub 类访问父类成员变量 number 并调用其其父类 Super 的 `showNumber()` 方法。
-
-**使用 this 和 super 要注意的问题:**
-
-- 在构造器中使用 `super()` 调用父类中的其他构造方法时,该语句必须处于构造器的首行,否则编译器会报错。另外,this 调用本类中的其他构造方法时,也要放在首行。
-- this、super不能用在static方法中。
-
-**简单解释一下:**
-
-被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享。而 this 代表对本类对象的引用,指向本类对象;而 super 代表对父类对象的引用,指向父类对象;所以, **this和super是属于对象范畴的东西,而静态方法是属于类范畴的东西**。
-
-## 参考
-
-- https://www.codejava.net/java-core/the-java-language/java-keywords
-- https://blog.csdn.net/u013393958/article/details/79881037
-
-# static 关键字详解
-
-## static 关键字主要有以下四种使用场景
-
-1. 修饰成员变量和成员方法
-2. 静态代码块
-3. 修饰类(只能修饰内部类)
-4. 静态导包(用来导入类中的静态资源,1.5之后的新特性)
-
-### 修饰成员变量和成员方法(常用)
-
-被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享,可以并且建议通过类名调用。被static 声明的成员变量属于静态成员变量,静态变量 存放在 Java 内存区域的方法区。
-
-方法区与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 Non-Heap(非堆),目的应该是与 Java 堆区分开来。
-
- HotSpot 虚拟机中方法区也常被称为 “永久代”,本质上两者并不等价。仅仅是因为 HotSpot 虚拟机设计团队用永久代来实现方法区而已,这样 HotSpot 虚拟机的垃圾收集器就可以像管理 Java 堆一样管理这部分内存了。但是这并不是一个好主意,因为这样更容易遇到内存溢出问题。
-
-
-
-调用格式:
-
-- 类名.静态变量名
-- 类名.静态方法名()
-
-如果变量或者方法被 private 则代表该属性或者该方法只能在类的内部被访问而不能在类的外部被访问。
-
-测试方法:
-
-```java
-public class StaticBean {
-
- String name;
- 静态变量
- static int age;
-
- public StaticBean(String name) {
- this.name = name;
- }
- 静态方法
- static void SayHello() {
- System.out.println(Hello i am java);
- }
- @Override
- public String toString() {
- return StaticBean{ +
- name=' + name + ''' + age + age +
- '}';
- }
-}
-```
-
-```java
-public class StaticDemo {
-
- public static void main(String[] args) {
- StaticBean staticBean = new StaticBean(1);
- StaticBean staticBean2 = new StaticBean(2);
- StaticBean staticBean3 = new StaticBean(3);
- StaticBean staticBean4 = new StaticBean(4);
- StaticBean.age = 33;
- StaticBean{name='1'age33} StaticBean{name='2'age33} StaticBean{name='3'age33} StaticBean{name='4'age33}
- System.out.println(staticBean+ +staticBean2+ +staticBean3+ +staticBean4);
- StaticBean.SayHello();Hello i am java
- }
-
-}
-```
-
-
-### 静态代码块
-
-静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行(静态代码块—非静态代码块—构造方法)。 该类不管创建多少对象,静态代码块只执行一次.
-
-静态代码块的格式是
-
-```
-static {
-语句体;
-}
-```
-
-
-一个类中的静态代码块可以有多个,位置可以随便放,它不在任何的方法体内,JVM加载类时会执行这些静态的代码块,如果静态代码块有多个,JVM将按照它们在类中出现的先后顺序依次执行它们,每个代码块只会被执行一次。
-
-
-
-静态代码块对于定义在它之后的静态变量,可以赋值,但是不能访问.
-
-
-### 静态内部类
-
-静态内部类与非静态内部类之间存在一个最大的区别,我们知道非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有。没有这个引用就意味着:
-
-1. 它的创建是不需要依赖外围类的创建。
-2. 它不能使用任何外围类的非static成员变量和方法。
-
-
-Example(静态内部类实现单例模式)
-
-```java
-public class Singleton {
-
- 声明为 private 避免调用默认构造方法创建对象
- private Singleton() {
- }
-
- 声明为 private 表明静态内部该类只能在该 Singleton 类中被访问
- private static class SingletonHolder {
- private static final Singleton INSTANCE = new Singleton();
- }
-
- public static Singleton getUniqueInstance() {
- return SingletonHolder.INSTANCE;
- }
-}
-```
-
-当 Singleton 类加载时,静态内部类 SingletonHolder 没有被加载进内存。只有当调用 `getUniqueInstance() `方法从而触发 `SingletonHolder.INSTANCE` 时 SingletonHolder 才会被加载,此时初始化 INSTANCE 实例,并且 JVM 能确保 INSTANCE 只被实例化一次。
-
-这种方式不仅具有延迟初始化的好处,而且由 JVM 提供了对线程安全的支持。
-
-### 静态导包
-
-格式为:import static
-
-这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法
-
-```java
-
-
- Math. --- 将Math中的所有静态资源导入,这时候可以直接使用里面的静态方法,而不用通过类名进行调用
- 如果只想导入单一某个静态方法,只需要将换成对应的方法名即可
-
-import static java.lang.Math.;
-
- 换成import static java.lang.Math.max;具有一样的效果
-
-public class Demo {
- public static void main(String[] args) {
-
- int max = max(1,2);
- System.out.println(max);
- }
-}
-
-```
-
-
-## 补充内容
-
-### 静态方法与非静态方法
-
-静态方法属于类本身,非静态方法属于从该类生成的每个对象。 如果您的方法执行的操作不依赖于其类的各个变量和方法,请将其设置为静态(这将使程序的占用空间更小)。 否则,它应该是非静态的。
-
-Example
-
-```java
-class Foo {
- int i;
- public Foo(int i) {
- this.i = i;
- }
-
- public static String method1() {
- return An example string that doesn't depend on i (an instance variable);
-
- }
-
- public int method2() {
- return this.i + 1; Depends on i
- }
-
-}
-```
-你可以像这样调用静态方法:`Foo.method1()`。 如果您尝试使用这种方法调用 method2 将失败。 但这样可行:`Foo bar = new Foo(1);bar.method2();`
-
-总结:
-
-- 在外部调用静态方法时,可以使用”类名.方法名”的方式,也可以使用”对象名.方法名”的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。
-- 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制
-
-### static{}静态代码块与{}非静态代码块(构造代码块)
-
-相同点: 都是在JVM加载类时且在构造方法执行之前执行,在类中都可以定义多个,定义多个时按定义的顺序执行,一般在代码块中对一些static变量进行赋值。
-
-不同点: 静态代码块在非静态代码块之前执行(静态代码块—非静态代码块—构造方法)。静态代码块只在第一次new执行一次,之后不再执行,而非静态代码块在每new一次就执行一次。 非静态代码块可在普通方法中定义(不过作用不大);而静态代码块不行。
-
-一般情况下,如果有些代码比如一些项目最常用的变量或对象必须在项目启动的时候就执行的时候,需要使用静态代码块,这种代码是主动执行的。如果我们想要设计不需要创建对象就可以调用类中的方法,例如:Arrays类,Character类,String类等,就需要使用静态方法, 两者的区别是 静态代码块是自动执行的而静态方法是被调用的时候才执行的.
-
-Example
-
-```java
-public class Test {
- public Test() {
- System.out.print(默认构造方法!--);
- }
-
- 非静态代码块
- {
- System.out.print(非静态代码块!--);
- }
- 静态代码块
- static {
- System.out.print(静态代码块!--);
- }
-
- public static void test() {
- System.out.print(静态方法中的内容! --);
- {
- System.out.print(静态方法中的代码块!--);
- }
-
- }
- public static void main(String[] args) {
-
- Test test = new Test();
- Test.test();静态代码块!--静态方法中的内容! --静态方法中的代码块!--
- }
-```
-
-当执行 `Test.test();` 时输出:
-
-```
-静态代码块!--静态方法中的内容! --静态方法中的代码块!--
-```
-
-当执行 `Test test = new Test();` 时输出:
-
-```
-静态代码块!--非静态代码块!--默认构造方法!--
-```
-
-
-非静态代码块与构造函数的区别是: 非静态代码块是给所有对象进行统一初始化,而构造函数是给对应的对象初始化,因为构造函数是可以多个的,运行哪个构造函数就会建立什么样的对象,但无论建立哪个对象,都会先执行相同的构造代码块。也就是说,构造代码块中定义的是不同对象共性的初始化内容。
-
-### 参考
-
-- httpsblog.csdn.netchen13579867831articledetails78995480
-- httpwww.cnblogs.comchenssyp3388487.html
-- httpwww.cnblogs.comQian123p5713440.html
diff --git "a/docs/java/Java IO\344\270\216NIO.md" "b/docs/java/Java IO\344\270\216NIO.md"
deleted file mode 100644
index 74bd850e696..00000000000
--- "a/docs/java/Java IO\344\270\216NIO.md"
+++ /dev/null
@@ -1,200 +0,0 @@
-
-
-- [IO流学习总结](#io流学习总结)
- - [一 Java IO,硬骨头也能变软](#一-java-io,硬骨头也能变软)
- - [二 java IO体系的学习总结](#二-java-io体系的学习总结)
- - [三 Java IO面试题](#三-java-io面试题)
-- [NIO与AIO学习总结](#nio与aio学习总结)
- - [一 Java NIO 概览](#一-java-nio-概览)
- - [二 Java NIO 之 Buffer\(缓冲区\)](#二-java-nio-之-buffer缓冲区)
- - [三 Java NIO 之 Channel(通道)](#三-java-nio-之-channel(通道))
- - [四 Java NIO之Selector(选择器)](#四-java-nio之selector(选择器))
- - [五 Java NIO之拥抱Path和Files](#五-java-nio之拥抱path和files)
- - [六 NIO学习总结以及NIO新特性介绍](#六-nio学习总结以及nio新特性介绍)
- - [七 Java NIO AsynchronousFileChannel异步文件通](#七-java-nio-asynchronousfilechannel异步文件通)
- - [八 高并发Java(8):NIO和AIO](#八-高并发java(8):nio和aio)
-- [推荐阅读](#推荐阅读)
- - [在 Java 7 中体会 NIO.2 异步执行的快乐](#在-java-7-中体会-nio2-异步执行的快乐)
- - [Java AIO总结与示例](#java-aio总结与示例)
-
-
-
-
-
-## IO流学习总结
-
-### [一 Java IO,硬骨头也能变软](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247483981&idx=1&sn=6e5c682d76972c8d2cf271a85dcf09e2&chksm=fd98542ccaefdd3a70428e9549bc33e8165836855edaa748928d16c1ebde9648579d3acaac10#rd)
-
-**(1) 按操作方式分类结构图:**
-
-
-
-
-**(2)按操作对象分类结构图**
-
-
-
-### [二 java IO体系的学习总结](https://blog.csdn.net/nightcurtis/article/details/51324105)
-1. **IO流的分类:**
- - 按照流的流向分,可以分为输入流和输出流;
- - 按照操作单元划分,可以划分为字节流和字符流;
- - 按照流的角色划分为节点流和处理流。
-2. **流的原理浅析:**
-
- java Io流共涉及40多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系, Java Io流的40多个类都是从如下4个抽象类基类中派生出来的。
-
- - **InputStream/Reader**: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
- - **OutputStream/Writer**: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
-3. **常用的io流的用法**
-
-### [三 Java IO面试题](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247483985&idx=1&sn=38531c2cee7b87f125df7aef41637014&chksm=fd985430caefdd26b0506aa84fc26251877eccba24fac73169a4d6bd1eb5e3fbdf3c3b940261#rd)
-
-## NIO与AIO学习总结
-
-
-### [一 Java NIO 概览](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247483956&idx=1&sn=57692bc5b7c2c6dfb812489baadc29c9&chksm=fd985455caefdd4331d828d8e89b22f19b304aa87d6da73c5d8c66fcef16e4c0b448b1a6f791#rd)
-
-1. **NIO简介**:
-
- Java NIO 是 java 1.4, 之后新出的一套IO接口NIO中的N可以理解为Non-blocking,不单纯是New。
-
-2. **NIO的特性/NIO与IO区别:**
- - 1)IO是面向流的,NIO是面向缓冲区的;
- - 2)IO流是阻塞的,NIO流是不阻塞的;
- - 3)NIO有选择器,而IO没有。
-3. **读数据和写数据方式:**
- - 从通道进行数据读取 :创建一个缓冲区,然后请求通道读取数据。
-
- - 从通道进行数据写入 :创建一个缓冲区,填充数据,并要求通道写入数据。
-
-4. **NIO核心组件简单介绍**
- - **Channels**
- - **Buffers**
- - **Selectors**
-
-
-### [二 Java NIO 之 Buffer(缓冲区)](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247483961&idx=1&sn=f67bef4c279e78043ff649b6b03fdcbc&chksm=fd985458caefdd4e3317ccbdb2d0a5a70a5024d3255eebf38183919ed9c25ade536017c0a6ba#rd)
-
-1. **Buffer(缓冲区)介绍:**
- - Java NIO Buffers用于和NIO Channel交互。 我们从Channel中读取数据到buffers里,从Buffer把数据写入到Channels;
- - Buffer本质上就是一块内存区;
- - 一个Buffer有三个属性是必须掌握的,分别是:capacity容量、position位置、limit限制。
-2. **Buffer的常见方法**
- - Buffer clear()
- - Buffer flip()
- - Buffer rewind()
- - Buffer position(int newPosition)
-3. **Buffer的使用方式/方法介绍:**
- - 分配缓冲区(Allocating a Buffer):
- ```java
- ByteBuffer buf = ByteBuffer.allocate(28);//以ByteBuffer为例子
- ```
- - 写入数据到缓冲区(Writing Data to a Buffer)
-
- **写数据到Buffer有两种方法:**
-
- 1.从Channel中写数据到Buffer
- ```java
- int bytesRead = inChannel.read(buf); //read into buffer.
- ```
- 2.通过put写数据:
- ```java
- buf.put(127);
- ```
-
-4. **Buffer常用方法测试**
-
- 说实话,NIO编程真的难,通过后面这个测试例子,你可能才能勉强理解前面说的Buffer方法的作用。
-
-
-### [三 Java NIO 之 Channel(通道)](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247483966&idx=1&sn=d5cf18c69f5f9ec2aff149270422731f&chksm=fd98545fcaefdd49296e2c78000ce5da277435b90ba3c03b92b7cf54c6ccc71d61d13efbce63#rd)
-
-
-1. **Channel(通道)介绍**
- - 通常来说NIO中的所有IO都是从 Channel(通道) 开始的。
- - NIO Channel通道和流的区别:
-2. **FileChannel的使用**
-3. **SocketChannel和ServerSocketChannel的使用**
-4. **️DatagramChannel的使用**
-5. **Scatter / Gather**
- - Scatter: 从一个Channel读取的信息分散到N个缓冲区中(Buufer).
- - Gather: 将N个Buffer里面内容按照顺序发送到一个Channel.
-6. **通道之间的数据传输**
- - 在Java NIO中如果一个channel是FileChannel类型的,那么他可以直接把数据传输到另一个channel。
- - transferFrom() :transferFrom方法把数据从通道源传输到FileChannel
- - transferTo() :transferTo方法把FileChannel数据传输到另一个channel
-
-
-### [四 Java NIO之Selector(选择器)](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247483970&idx=1&sn=d5e2b133313b1d0f32872d54fbdf0aa7&chksm=fd985423caefdd354b587e57ce6cf5f5a7bec48b9ab7554f39a8d13af47660cae793956e0f46#rd)
-
-
-1. **Selector(选择器)介绍**
- - Selector 一般称 为选择器 ,当然你也可以翻译为 多路复用器 。它是Java NIO核心组件中的一个,用于检查一个或多个NIO Channel(通道)的状态是否处于可读、可写。如此可以实现单线程管理多个channels,也就是可以管理多个网络链接。
- - 使用Selector的好处在于: 使用更少的线程来就可以来处理通道了, 相比使用多个线程,避免了线程上下文切换带来的开销。
-2. **Selector(选择器)的使用方法介绍**
- - Selector的创建
- ```java
- Selector selector = Selector.open();
- ```
- - 注册Channel到Selector(Channel必须是非阻塞的)
- ```java
- channel.configureBlocking(false);
- SelectionKey key = channel.register(selector, Selectionkey.OP_READ);
- ```
- - SelectionKey介绍
-
- 一个SelectionKey键表示了一个特定的通道对象和一个特定的选择器对象之间的注册关系。
- - 从Selector中选择channel(Selecting Channels via a Selector)
-
- 选择器维护注册过的通道的集合,并且这种注册关系都被封装在SelectionKey当中.
- - 停止选择的方法
-
- wakeup()方法 和close()方法。
-3. **模板代码**
-
- 有了模板代码我们在编写程序时,大多数时间都是在模板代码中添加相应的业务代码。
-4. **客户端与服务端简单交互实例**
-
-
-
-### [五 Java NIO之拥抱Path和Files](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247483976&idx=1&sn=2296c05fc1b840a64679e2ad7794c96d&chksm=fd985429caefdd3f48e2ee6fdd7b0f6fc419df90b3de46832b484d6d1ca4e74e7837689c8146&token=537240785&lang=zh_CN#rd)
-
-**一 文件I/O基石:Path:**
-- 创建一个Path
-- File和Path之间的转换,File和URI之间的转换
-- 获取Path的相关信息
-- 移除Path中的冗余项
-
-**二 拥抱Files类:**
-- Files.exists() 检测文件路径是否存在
-- Files.createFile() 创建文件
-- Files.createDirectories()和Files.createDirectory()创建文件夹
-- Files.delete()方法 可以删除一个文件或目录
-- Files.copy()方法可以吧一个文件从一个地址复制到另一个位置
-- 获取文件属性
-- 遍历一个文件夹
-- Files.walkFileTree()遍历整个目录
-
-### [六 NIO学习总结以及NIO新特性介绍](https://blog.csdn.net/a953713428/article/details/64907250)
-
-- **内存映射:**
-
-这个功能主要是为了提高大文件的读写速度而设计的。内存映射文件(memory-mappedfile)能让你创建和修改那些大到无法读入内存的文件。有了内存映射文件,你就可以认为文件已经全部读进了内存,然后把它当成一个非常大的数组来访问了。将文件的一段区域映射到内存中,比传统的文件处理速度要快很多。内存映射文件它虽然最终也是要从磁盘读取数据,但是它并不需要将数据读取到OS内核缓冲区,而是直接将进程的用户私有地址空间中的一部分区域与文件对象建立起映射关系,就好像直接从内存中读、写文件一样,速度当然快了。
-
-### [七 Java NIO AsynchronousFileChannel异步文件通](http://wiki.jikexueyuan.com/project/java-nio-zh/java-nio-asynchronousfilechannel.html)
-
-Java7中新增了AsynchronousFileChannel作为nio的一部分。AsynchronousFileChannel使得数据可以进行异步读写。
-
-### [八 高并发Java(8):NIO和AIO](http://www.importnew.com/21341.html)
-
-
-
-## 推荐阅读
-
-### [在 Java 7 中体会 NIO.2 异步执行的快乐](https://www.ibm.com/developerworks/cn/java/j-lo-nio2/index.html)
-
-### [Java AIO总结与示例](https://blog.csdn.net/x_i_y_u_e/article/details/52223406)
-AIO是异步IO的缩写,虽然NIO在网络操作中,提供了非阻塞的方法,但是NIO的IO行为还是同步的。对于NIO来说,我们的业务线程是在IO操作准备好时,得到通知,接着就由这个线程自行进行IO操作,IO操作本身是同步的。
-
-
-**欢迎关注我的微信公众号:"Java面试通关手册"(一个有温度的微信公众号,期待与你共同进步~~~坚持原创,分享美文,分享各种Java学习资源):**
diff --git "a/docs/java/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/docs/java/Java\345\237\272\347\241\200\347\237\245\350\257\206.md"
deleted file mode 100644
index de950a632bf..00000000000
--- "a/docs/java/Java\345\237\272\347\241\200\347\237\245\350\257\206.md"
+++ /dev/null
@@ -1,555 +0,0 @@
-点击关注[公众号](#公众号)及时获取笔主最新更新文章,并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。
-
-
-
-- [1. 面向对象和面向过程的区别](#1-面向对象和面向过程的区别)
-- [2. Java 语言有哪些特点?](#2-java-语言有哪些特点)
-- [3. 关于 JVM JDK 和 JRE 最详细通俗的解答](#3-关于-jvm-jdk-和-jre-最详细通俗的解答)
- - [JVM](#jvm)
- - [JDK 和 JRE](#jdk-和-jre)
-- [4. Oracle JDK 和 OpenJDK 的对比](#4-oracle-jdk-和-openjdk-的对比)
-- [5. Java和C++的区别?](#5-java和c的区别)
-- [6. 什么是 Java 程序的主类 应用程序和小程序的主类有何不同?](#6-什么是-java-程序的主类-应用程序和小程序的主类有何不同)
-- [7. Java 应用程序与小程序之间有哪些差别?](#7-java-应用程序与小程序之间有哪些差别)
-- [8. 字符型常量和字符串常量的区别?](#8-字符型常量和字符串常量的区别)
-- [9. 构造器 Constructor 是否可被 override?](#9-构造器-constructor-是否可被-override)
-- [10. 重载和重写的区别](#10-重载和重写的区别)
-- [11. Java 面向对象编程三大特性: 封装 继承 多态](#11-java-面向对象编程三大特性-封装-继承-多态)
- - [封装](#封装)
- - [继承](#继承)
- - [多态](#多态)
-- [12. String StringBuffer 和 StringBuilder 的区别是什么? String 为什么是不可变的?](#12-string-stringbuffer-和-stringbuilder-的区别是什么-string-为什么是不可变的)
-- [13. 自动装箱与拆箱](#13-自动装箱与拆箱)
-- [14. 在一个静态方法内调用一个非静态成员为什么是非法的?](#14-在一个静态方法内调用一个非静态成员为什么是非法的)
-- [15. 在 Java 中定义一个不做事且没有参数的构造方法的作用](#15-在-java-中定义一个不做事且没有参数的构造方法的作用)
-- [16. import java和javax有什么区别?](#16-import-java和javax有什么区别)
-- [17. 接口和抽象类的区别是什么?](#17-接口和抽象类的区别是什么)
-- [18. 成员变量与局部变量的区别有哪些?](#18-成员变量与局部变量的区别有哪些)
-- [19. 创建一个对象用什么运算符?对象实体与对象引用有何不同?](#19-创建一个对象用什么运算符对象实体与对象引用有何不同)
-- [20. 什么是方法的返回值?返回值在类的方法里的作用是什么?](#20-什么是方法的返回值返回值在类的方法里的作用是什么)
-- [21. 一个类的构造方法的作用是什么? 若一个类没有声明构造方法,该程序能正确执行吗? 为什么?](#21-一个类的构造方法的作用是什么-若一个类没有声明构造方法该程序能正确执行吗-为什么)
-- [22. 构造方法有哪些特性?](#22-构造方法有哪些特性)
-- [23. 静态方法和实例方法有何不同](#23-静态方法和实例方法有何不同)
-- [24. 对象的相等与指向他们的引用相等,两者有什么不同?](#24-对象的相等与指向他们的引用相等两者有什么不同)
-- [25. 在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是?](#25-在调用子类构造方法之前会先调用父类没有参数的构造方法其目的是)
-- [26. == 与 equals(重要)](#26--与-equals重要)
-- [27. hashCode 与 equals (重要)](#27-hashcode-与-equals-重要)
- - [hashCode()介绍](#hashcode介绍)
- - [为什么要有 hashCode](#为什么要有-hashcode)
- - [hashCode()与equals()的相关规定](#hashcode与equals的相关规定)
-- [28. 为什么Java中只有值传递?](#28-为什么java中只有值传递)
-- [29. 简述线程、程序、进程的基本概念。以及他们之间关系是什么?](#29-简述线程程序进程的基本概念以及他们之间关系是什么)
-- [30. 线程有哪些基本状态?](#30-线程有哪些基本状态)
-- [31 关于 final 关键字的一些总结](#31-关于-final-关键字的一些总结)
-- [32 Java 中的异常处理](#32-java-中的异常处理)
- - [Java异常类层次结构图](#java异常类层次结构图)
- - [Throwable类常用方法](#throwable类常用方法)
- - [异常处理总结](#异常处理总结)
-- [33 Java序列化中如果有些字段不想进行序列化,怎么办?](#33-java序列化中如果有些字段不想进行序列化怎么办)
-- [34 获取用键盘输入常用的两种方法](#34-获取用键盘输入常用的两种方法)
-- [35 Java 中 IO 流](#35-java-中-io-流)
- - [Java 中 IO 流分为几种?](#java-中-io-流分为几种)
- - [既然有了字节流,为什么还要有字符流?](#既然有了字节流为什么还要有字符流)
- - [BIO,NIO,AIO 有什么区别?](#bionioaio-有什么区别)
-- [36. 常见关键字总结:static,final,this,super](#36-常见关键字总结staticfinalthissuper)
-- [37. Collections 工具类和 Arrays 工具类常见方法总结](#37-collections-工具类和-arrays-工具类常见方法总结)
-- [参考](#参考)
-- [公众号](#公众号)
-
-
-
-## 1. 面向对象和面向过程的区别
-
-- **面向过程** :**面向过程性能比面向对象高。** 因为类调用时需要实例化,开销比较大,比较消耗资源,所以当性能是最重要的考量因素的时候,比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发。但是,**面向过程没有面向对象易维护、易复用、易扩展。**
-- **面向对象** :**面向对象易维护、易复用、易扩展。** 因为面向对象有封装、继承、多态性的特性,所以可以设计出低耦合的系统,使系统更加灵活、更加易于维护。但是,**面向对象性能比面向过程低**。
-
-参见 issue : [面向过程 :面向过程性能比面向对象高??](https://github.com/Snailclimb/JavaGuide/issues/431)
-
-> 这个并不是根本原因,面向过程也需要分配内存,计算内存偏移量,Java性能差的主要原因并不是因为它是面向对象语言,而是Java是半编译语言,最终的执行代码并不是可以直接被CPU执行的二进制机械码。
->
-> 而面向过程语言大多都是直接编译成机械码在电脑上执行,并且其它一些面向过程的脚本语言性能也并不一定比Java好。
-
-## 2. Java 语言有哪些特点?
-
-1. 简单易学;
-2. 面向对象(封装,继承,多态);
-3. 平台无关性( Java 虚拟机实现平台无关性);
-4. 可靠性;
-5. 安全性;
-6. 支持多线程( C++ 语言没有内置的多线程机制,因此必须调用操作系统的多线程功能来进行多线程程序设计,而 Java 语言却提供了多线程支持);
-7. 支持网络编程并且很方便( Java 语言诞生本身就是为简化网络编程设计的,因此 Java 语言不仅支持网络编程而且很方便);
-8. 编译与解释并存;
-
-> 修正(参见: [issue#544](https://github.com/Snailclimb/JavaGuide/issues/544)):C++11开始(2011年的时候),C++就引入了多线程库,在windows、linux、macos都可以使用`std::thread`和`std::async`来创建线程。参考链接:http://www.cplusplus.com/reference/thread/thread/?kw=thread
-
-## 3. 关于 JVM JDK 和 JRE 最详细通俗的解答
-
-### JVM
-
-Java虚拟机(JVM)是运行 Java 字节码的虚拟机。JVM有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。
-
-**什么是字节码?采用字节码的好处是什么?**
-
-> 在 Java 中,JVM可以理解的代码就叫做`字节码`(即扩展名为 `.class` 的文件),它不面向任何特定的处理器,只面向虚拟机。Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以 Java 程序运行时比较高效,而且,由于字节码并不针对一种特定的机器,因此,Java程序无须重新编译便可在多种不同操作系统的计算机上运行。
-
-**Java 程序从源代码到运行一般有下面3步:**
-
-
-
-我们需要格外注意的是 .class->机器码 这一步。在这一步 JVM 类加载器首先加载字节码文件,然后通过解释器逐行解释执行,这种方式的执行速度会相对比较慢。而且,有些方法和代码块是经常需要被调用的(也就是所谓的热点代码),所以后面引进了 JIT 编译器,而JIT 属于运行时编译。当 JIT 编译器完成第一次编译后,其会将字节码对应的机器码保存下来,下次可以直接使用。而我们知道,机器码的运行效率肯定是高于 Java 解释器的。这也解释了我们为什么经常会说 Java 是编译与解释共存的语言。
-
-> HotSpot采用了惰性评估(Lazy Evaluation)的做法,根据二八定律,消耗大部分系统资源的只有那一小部分的代码(热点代码),而这也就是JIT所需要编译的部分。JVM会根据代码每次被执行的情况收集信息并相应地做出一些优化,因此执行的次数越多,它的速度就越快。JDK 9引入了一种新的编译模式AOT(Ahead of Time Compilation),它是直接将字节码编译成机器码,这样就避免了JIT预热等各方面的开销。JDK支持分层编译和AOT协作使用。但是 ,AOT 编译器的编译质量是肯定比不上 JIT 编译器的。
-
-**总结:**
-
-Java虚拟机(JVM)是运行 Java 字节码的虚拟机。JVM有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。字节码和不同系统的 JVM 实现是 Java 语言“一次编译,随处可以运行”的关键所在。
-
-### JDK 和 JRE
-
-JDK是Java Development Kit,它是功能齐全的Java SDK。它拥有JRE所拥有的一切,还有编译器(javac)和工具(如javadoc和jdb)。它能够创建和编译程序。
-
-JRE 是 Java运行时环境。它是运行已编译 Java 程序所需的所有内容的集合,包括 Java虚拟机(JVM),Java类库,java命令和其他的一些基础构件。但是,它不能用于创建新程序。
-
-如果你只是为了运行一下 Java 程序的话,那么你只需要安装 JRE 就可以了。如果你需要进行一些 Java 编程方面的工作,那么你就需要安装JDK了。但是,这不是绝对的。有时,即使您不打算在计算机上进行任何Java开发,仍然需要安装JDK。例如,如果要使用JSP部署Web应用程序,那么从技术上讲,您只是在应用程序服务器中运行Java程序。那你为什么需要JDK呢?因为应用程序服务器会将 JSP 转换为 Java servlet,并且需要使用 JDK 来编译 servlet。
-
-## 4. Oracle JDK 和 OpenJDK 的对比
-
-可能在看这个问题之前很多人和我一样并没有接触和使用过 OpenJDK 。那么Oracle和OpenJDK之间是否存在重大差异?下面我通过收集到的一些资料,为你解答这个被很多人忽视的问题。
-
-对于Java 7,没什么关键的地方。OpenJDK项目主要基于Sun捐赠的HotSpot源代码。此外,OpenJDK被选为Java 7的参考实现,由Oracle工程师维护。关于JVM,JDK,JRE和OpenJDK之间的区别,Oracle博客帖子在2012年有一个更详细的答案:
-
-> 问:OpenJDK存储库中的源代码与用于构建Oracle JDK的代码之间有什么区别?
->
-> 答:非常接近 - 我们的Oracle JDK版本构建过程基于OpenJDK 7构建,只添加了几个部分,例如部署代码,其中包括Oracle的Java插件和Java WebStart的实现,以及一些封闭的源代码派对组件,如图形光栅化器,一些开源的第三方组件,如Rhino,以及一些零碎的东西,如附加文档或第三方字体。展望未来,我们的目的是开源Oracle JDK的所有部分,除了我们考虑商业功能的部分。
-
-**总结:**
-
-1. Oracle JDK大概每6个月发一次主要版本,而OpenJDK版本大概每三个月发布一次。但这不是固定的,我觉得了解这个没啥用处。详情参见:https://blogs.oracle.com/java-platform-group/update-and-faq-on-the-java-se-release-cadence。
-2. OpenJDK 是一个参考模型并且是完全开源的,而Oracle JDK是OpenJDK的一个实现,并不是完全开源的;
-3. Oracle JDK 比 OpenJDK 更稳定。OpenJDK和Oracle JDK的代码几乎相同,但Oracle JDK有更多的类和一些错误修复。因此,如果您想开发企业/商业软件,我建议您选择Oracle JDK,因为它经过了彻底的测试和稳定。某些情况下,有些人提到在使用OpenJDK 可能会遇到了许多应用程序崩溃的问题,但是,只需切换到Oracle JDK就可以解决问题;
-4. 在响应性和JVM性能方面,Oracle JDK与OpenJDK相比提供了更好的性能;
-5. Oracle JDK不会为即将发布的版本提供长期支持,用户每次都必须通过更新到最新版本获得支持来获取最新版本;
-6. Oracle JDK根据二进制代码许可协议获得许可,而OpenJDK根据GPL v2许可获得许可。
-
-## 5. Java和C++的区别?
-
-我知道很多人没学过 C++,但是面试官就是没事喜欢拿咱们 Java 和 C++ 比呀!没办法!!!就算没学过C++,也要记下来!
-
-- 都是面向对象的语言,都支持封装、继承和多态
-- Java 不提供指针来直接访问内存,程序内存更加安全
-- Java 的类是单继承的,C++ 支持多重继承;虽然 Java 的类不可以多继承,但是接口可以多继承。
-- Java 有自动内存管理机制,不需要程序员手动释放无用内存
-
-
-## 6. 什么是 Java 程序的主类 应用程序和小程序的主类有何不同?
-
-一个程序中可以有多个类,但只能有一个类是主类。在 Java 应用程序中,这个主类是指包含 main()方法的类。而在 Java 小程序中,这个主类是一个继承自系统类 JApplet 或 Applet 的子类。应用程序的主类不一定要求是 public 类,但小程序的主类要求必须是 public 类。主类是 Java 程序执行的入口点。
-
-## 7. Java 应用程序与小程序之间有哪些差别?
-
-简单说应用程序是从主线程启动(也就是 `main()` 方法)。applet 小程序没有 `main()` 方法,主要是嵌在浏览器页面上运行(调用`init()`或者`run()`来启动),嵌入浏览器这点跟 flash 的小游戏类似。
-
-## 8. 字符型常量和字符串常量的区别?
-
-1. 形式上: 字符常量是单引号引起的一个字符; 字符串常量是双引号引起的若干个字符
-2. 含义上: 字符常量相当于一个整型值( ASCII 值),可以参加表达式运算; 字符串常量代表一个地址值(该字符串在内存中存放位置)
-3. 占内存大小 字符常量只占2个字节; 字符串常量占若干个字节(至少一个字符结束标志) (**注意: char在Java中占两个字节**)
-
-> java编程思想第四版:2.2.2节
-
-
-## 9. 构造器 Constructor 是否可被 override?
-
-在讲继承的时候我们就知道父类的私有属性和构造方法并不能被继承,所以 Constructor 也就不能被 override(重写),但是可以 overload(重载),所以你可以看到一个类中有多个构造函数的情况。
-
-## 10. 重载和重写的区别
-
-#### 重载
-
-发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同。
-
-下面是《Java核心技术》对重载这个概念的介绍:
-
-
-
-#### 重写
-
- 重写是子类对父类的允许访问的方法的实现过程进行重新编写,发生在子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类。另外,如果父类方法访问修饰符为 private 则子类就不能重写该方法。**也就是说方法提供的行为改变,而方法的外貌并没有改变。**
-
-## 11. Java 面向对象编程三大特性: 封装 继承 多态
-
-### 封装
-
-封装把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法,如果属性不想被外界访问,我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了。
-
-
-### 继承
-继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码。
-
-**关于继承如下 3 点请记住:**
-
-1. 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,**只是拥有**。
-2. 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
-3. 子类可以用自己的方式实现父类的方法。(以后介绍)。
-
-### 多态
-
-所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。
-
-在Java中有两种形式可以实现多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法)。
-
-## 12. String StringBuffer 和 StringBuilder 的区别是什么? String 为什么是不可变的?
-
-**可变性**
-
-简单的来说:String 类中使用 final 关键字修饰字符数组来保存字符串,`private final char value[]`,所以 String 对象是不可变的。而StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串`char[]value` 但是没有用 final 关键字修饰,所以这两种对象都是可变的。
-
-StringBuilder 与 StringBuffer 的构造方法都是调用父类构造方法也就是 AbstractStringBuilder 实现的,大家可以自行查阅源码。
-
-AbstractStringBuilder.java
-
-```java
-abstract class AbstractStringBuilder implements Appendable, CharSequence {
- char[] value;
- int count;
- AbstractStringBuilder() {
- }
- AbstractStringBuilder(int capacity) {
- value = new char[capacity];
- }
-```
-
-
-**线程安全性**
-
-String 中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
-
-**性能**
-
-每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
-
-**对于三者使用的总结:**
-
-1. 操作少量的数据: 适用String
-2. 单线程操作字符串缓冲区下操作大量数据: 适用StringBuilder
-3. 多线程操作字符串缓冲区下操作大量数据: 适用StringBuffer
-
-## 13. 自动装箱与拆箱
-
-- **装箱**:将基本类型用它们对应的引用类型包装起来;
-- **拆箱**:将包装类型转换为基本数据类型;
-
-## 14. 在一个静态方法内调用一个非静态成员为什么是非法的?
-
-由于静态方法可以不通过对象进行调用,因此在静态方法里,不能调用其他非静态变量,也不可以访问非静态变量成员。
-
-## 15. 在 Java 中定义一个不做事且没有参数的构造方法的作用
-
-Java 程序在执行子类的构造方法之前,如果没有用 `super() `来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。因此,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用 `super() `来调用父类中特定的构造方法,则编译时将发生错误,因为 Java 程序在父类中找不到没有参数的构造方法可供执行。解决办法是在父类里加上一个不做事且没有参数的构造方法。
-
-## 16. import java和javax有什么区别?
-
-刚开始的时候 JavaAPI 所必需的包是 java 开头的包,javax 当时只是扩展 API 包来使用。然而随着时间的推移,javax 逐渐地扩展成为 Java API 的组成部分。但是,将扩展从 javax 包移动到 java 包确实太麻烦了,最终会破坏一堆现有的代码。因此,最终决定 javax 包将成为标准API的一部分。
-
-所以,实际上java和javax没有区别。这都是一个名字。
-
-## 17. 接口和抽象类的区别是什么?
-
-1. 接口的方法默认是 public,所有方法在接口中不能有实现(Java 8 开始接口方法可以有默认实现),而抽象类可以有非抽象的方法。
-2. 接口中除了static、final变量,不能有其他变量,而抽象类中则不一定。
-3. 一个类可以实现多个接口,但只能实现一个抽象类。接口自己本身可以通过extends关键字扩展多个接口。
-4. 接口方法默认修饰符是public,抽象方法可以有public、protected和default这些修饰符(抽象方法就是为了被重写所以不能使用private关键字修饰!)。
-5. 从设计层面来说,抽象是对类的抽象,是一种模板设计,而接口是对行为的抽象,是一种行为的规范。
-
-备注:在JDK8中,接口也可以定义静态方法,可以直接用接口名调用。实现类和实现是不可以调用的。如果同时实现两个接口,接口中定义了一样的默认方法,则必须重写,不然会报错。(详见issue:[https://github.com/Snailclimb/JavaGuide/issues/146](https://github.com/Snailclimb/JavaGuide/issues/146))
-
-## 18. 成员变量与局部变量的区别有哪些?
-
-1. 从语法形式上看:成员变量是属于类的,而局部变量是在方法中定义的变量或是方法的参数;成员变量可以被 public,private,static 等修饰符所修饰,而局部变量不能被访问控制修饰符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰。
-2. 从变量在内存中的存储方式来看:如果成员变量是使用`static`修饰的,那么这个成员变量是属于类的,如果没有使用`static`修饰,这个成员变量是属于实例的。而对象存在于堆内存,局部变量则存在于栈内存。
-3. 从变量在内存中的生存时间上看:成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动消失。
-4. 成员变量如果没有被赋初值:则会自动以类型的默认值而赋值(一种情况例外:被 final 修饰的成员变量也必须显式地赋值),而局部变量则不会自动赋值。
-
-## 19. 创建一个对象用什么运算符?对象实体与对象引用有何不同?
-
-new运算符,new创建对象实例(对象实例在堆内存中),对象引用指向对象实例(对象引用存放在栈内存中)。一个对象引用可以指向0个或1个对象(一根绳子可以不系气球,也可以系一个气球);一个对象可以有n个引用指向它(可以用n条绳子系住一个气球)。
-
-## 20. 什么是方法的返回值?返回值在类的方法里的作用是什么?
-
-方法的返回值是指我们获取到的某个方法体中的代码执行后产生的结果!(前提是该方法可能产生结果)。返回值的作用:接收出结果,使得它可以用于其他的操作!
-
-## 21. 一个类的构造方法的作用是什么? 若一个类没有声明构造方法,该程序能正确执行吗? 为什么?
-
-主要作用是完成对类对象的初始化工作。可以执行。因为一个类即使没有声明构造方法也会有默认的不带参数的构造方法。
-
-## 22. 构造方法有哪些特性?
-
-1. 名字与类名相同。
-2. 没有返回值,但不能用void声明构造函数。
-3. 生成类的对象时自动执行,无需调用。
-
-## 23. 静态方法和实例方法有何不同
-
-1. 在外部调用静态方法时,可以使用"类名.方法名"的方式,也可以使用"对象名.方法名"的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。
-
-2. 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制。
-
-## 24. 对象的相等与指向他们的引用相等,两者有什么不同?
-
-对象的相等,比的是内存中存放的内容是否相等。而引用相等,比较的是他们指向的内存地址是否相等。
-
-## 25. 在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是?
-
-帮助子类做初始化工作。
-
-## 26. == 与 equals(重要)
-
-**==** : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象(基本数据类型==比较的是值,引用数据类型==比较的是内存地址)。
-
-**equals()** : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况:
-- 情况1:类没有覆盖 equals() 方法。则通过 equals() 比较该类的两个对象时,等价于通过“==”比较这两个对象。
-- 情况2:类覆盖了 equals() 方法。一般,我们都覆盖 equals() 方法来比较两个对象的内容是否相等;若它们的内容相等,则返回 true (即,认为这两个对象相等)。
-
-
-**举个例子:**
-
-```java
-public class test1 {
- public static void main(String[] args) {
- String a = new String("ab"); // a 为一个引用
- String b = new String("ab"); // b为另一个引用,对象的内容一样
- String aa = "ab"; // 放在常量池中
- String bb = "ab"; // 从常量池中查找
- if (aa == bb) // true
- System.out.println("aa==bb");
- if (a == b) // false,非同一对象
- System.out.println("a==b");
- if (a.equals(b)) // true
- System.out.println("aEQb");
- if (42 == 42.0) { // true
- System.out.println("true");
- }
- }
-}
-```
-
-**说明:**
-
-- String 中的 equals 方法是被重写过的,因为 object 的 equals 方法是比较的对象的内存地址,而 String 的 equals 方法比较的是对象的值。
-- 当创建 String 类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个 String 对象。
-
-## 27. hashCode 与 equals (重要)
-
-面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写equals时必须重写hashCode方法?”
-
-### hashCode()介绍
-hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数。
-
-散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)
-
-### 为什么要有 hashCode
-
-**我们先以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode:** 当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 `equals()`方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的Java启蒙书《Head first java》第二版)。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。
-
-通过我们可以看出:`hashCode()` 的作用就是**获取哈希码**,也称为散列码;它实际上是返回一个int整数。这个**哈希码的作用**是确定该对象在哈希表中的索引位置。**`hashCode() `在散列表中才有用,在其它情况下没用**。在散列表中hashCode() 的作用是获取对象的散列码,进而确定该对象在散列表中的位置。
-
-### hashCode()与equals()的相关规定
-
-1. 如果两个对象相等,则hashcode一定也是相同的
-2. 两个对象相等,对两个对象分别调用equals方法都返回true
-3. 两个对象有相同的hashcode值,它们也不一定是相等的
-4. **因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖**
-5. hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)
-
-推荐阅读:[Java hashCode() 和 equals()的若干问题解答](https://www.cnblogs.com/skywang12345/p/3324958.html)
-
-
-## 28. 为什么Java中只有值传递?
-
- [为什么Java中只有值传递?](https://github.com/Snailclimb/JavaGuide/blob/master/docs/essential-content-for-interview/MostCommonJavaInterviewQuestions/%E7%AC%AC%E4%B8%80%E5%91%A8%EF%BC%882018-8-7%EF%BC%89.md)
-
-
-## 29. 简述线程、程序、进程的基本概念。以及他们之间关系是什么?
-
-**线程**与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
-
-**程序**是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码。
-
-**进程**是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。
-线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。从另一角度来说,进程属于操作系统的范畴,主要是同一段时间内,可以同时执行一个以上的程序,而线程则是在同一程序内几乎同时执行一个以上的程序段。
-
-## 30. 线程有哪些基本状态?
-
-Java 线程在运行的生命周期中的指定时刻只可能处于下面6种不同状态的其中一个状态(图源《Java 并发编程艺术》4.1.4节)。
-
-
-
-线程在生命周期中并不是固定处于某一个状态而是随着代码的执行在不同状态之间切换。Java 线程状态变迁如下图所示(图源《Java 并发编程艺术》4.1.4节):
-
-
-
-
-
-由上图可以看出:
-
-线程创建之后它将处于 **NEW(新建)** 状态,调用 `start()` 方法后开始运行,线程这时候处于 **READY(可运行)** 状态。可运行状态的线程获得了 cpu 时间片(timeslice)后就处于 **RUNNING(运行)** 状态。
-
-> 操作系统隐藏 Java虚拟机(JVM)中的 READY 和 RUNNING 状态,它只能看到 RUNNABLE 状态(图源:[HowToDoInJava](https://howtodoinjava.com/):[Java Thread Life Cycle and Thread States](https://howtodoinjava.com/java/multi-threading/java-thread-life-cycle-and-thread-states/)),所以 Java 系统一般将这两个状态统称为 **RUNNABLE(运行中)** 状态 。
-
-
-
-当线程执行 `wait()`方法之后,线程进入 **WAITING(等待)**状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态,而 **TIME_WAITING(超时等待)** 状态相当于在等待状态的基础上增加了超时限制,比如通过 `sleep(long millis)`方法或 `wait(long millis)`方法可以将 Java 线程置于 TIMED WAITING 状态。当超时时间到达后 Java 线程将会返回到 RUNNABLE 状态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到 **BLOCKED(阻塞)** 状态。线程在执行 Runnable 的` run() `方法之后将会进入到 **TERMINATED(终止)** 状态。
-
-## 31 关于 final 关键字的一些总结
-
-final关键字主要用在三个地方:变量、方法、类。
-
-1. 对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。
-2. 当用final修饰一个类时,表明这个类不能被继承。final类中的所有成员方法都会被隐式地指定为final方法。
-3. 使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的Java版本已经不需要使用final方法进行这些优化了)。类中所有的private方法都隐式地指定为final。
-
-## 32 Java 中的异常处理
-
-### Java异常类层次结构图
-
-
-
-
-
-在 Java 中,所有的异常都有一个共同的祖先java.lang包中的 **Throwable类**。Throwable: 有两个重要的子类:**Exception(异常)** 和 **Error(错误)** ,二者都是 Java 异常处理的重要子类,各自都包含大量子类。
-
-**Error(错误):是程序无法处理的错误**,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。
-
-这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时,如Java虚拟机运行错误(Virtual MachineError)、类定义错误(NoClassDefFoundError)等。这些错误是不可查的,因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在 Java中,错误通过Error的子类描述。
-
-**Exception(异常):是程序本身可以处理的异常**。Exception 类有一个重要的子类 **RuntimeException**。RuntimeException 异常由Java虚拟机抛出。**NullPointerException**(要访问的变量没有引用任何对象时,抛出该异常)、**ArithmeticException**(算术运算异常,一个整数除以0时,抛出该异常)和 **ArrayIndexOutOfBoundsException** (下标越界异常)。
-
-**注意:异常和错误的区别:异常能被程序本身处理,错误是无法处理。**
-
-### Throwable类常用方法
-
-- **public string getMessage()**:返回异常发生时的简要描述
-- **public string toString()**:返回异常发生时的详细信息
-- **public string getLocalizedMessage()**:返回异常对象的本地化信息。使用Throwable的子类覆盖这个方法,可以生成本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与getMessage()返回的结果相同
-- **public void printStackTrace()**:在控制台上打印Throwable对象封装的异常信息
-
-### 异常处理总结
-
-- **try 块:** 用于捕获异常。其后可接零个或多个catch块,如果没有catch块,则必须跟一个finally块。
-- **catch 块:** 用于处理try捕获到的异常。
-- **finally 块:** 无论是否捕获或处理异常,finally块里的语句都会被执行。当在try块或catch块中遇到return
-语句时,finally语句块将在方法返回之前被执行。
-
-**在以下4种特殊情况下,finally块不会被执行:**
-
-1. 在finally语句块第一行发生了异常。 因为在其他行,finally块还是会得到执行
-2. 在前面的代码中用了System.exit(int)已退出程序。 exit是带参函数 ;若该语句在异常语句之后,finally会执行
-3. 程序所在的线程死亡。
-4. 关闭CPU。
-
-下面这部分内容来自issue:。
-
-**注意:** 当try语句和finally语句中都有return语句时,在方法返回之前,finally语句的内容将被执行,并且finally语句的返回值将会覆盖原始的返回值。如下:
-
-```java
- public static int f(int value) {
- try {
- return value * value;
- } finally {
- if (value == 2) {
- return 0;
- }
- }
- }
-```
-
-如果调用 `f(2)`,返回值将是0,因为finally语句的返回值覆盖了try语句块的返回值。
-
-## 33 Java序列化中如果有些字段不想进行序列化,怎么办?
-
-对于不想进行序列化的变量,使用transient关键字修饰。
-
-transient关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被transient修饰的变量值不会被持久化和恢复。transient只能修饰变量,不能修饰类和方法。
-
-## 34 获取用键盘输入常用的两种方法
-
-方法1:通过 Scanner
-
-```java
-Scanner input = new Scanner(System.in);
-String s = input.nextLine();
-input.close();
-```
-
-方法2:通过 BufferedReader
-
-```java
-BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
-String s = input.readLine();
-```
-
-## 35 Java 中 IO 流
-
-### Java 中 IO 流分为几种?
-
- - 按照流的流向分,可以分为输入流和输出流;
- - 按照操作单元划分,可以划分为字节流和字符流;
- - 按照流的角色划分为节点流和处理流。
-
-Java Io流共涉及40多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系, Java I0流的40多个类都是从如下4个抽象类基类中派生出来的。
-
- - InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
- - OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
-
-按操作方式分类结构图:
-
-
-
-
-按操作对象分类结构图:
-
-
-
-### 既然有了字节流,为什么还要有字符流?
-
-问题本质想问:**不管是文件读写还是网络发送接收,信息的最小存储单元都是字节,那为什么 I/O 流操作要分为字节流操作和字符流操作呢?**
-
-回答:字符流是由 Java 虚拟机将字节转换得到的,问题就出在这个过程还算是非常耗时,并且,如果我们不知道编码类型就很容易出现乱码问题。所以, I/O 流就干脆提供了一个直接操作字符的接口,方便我们平时对字符进行流操作。如果音频文件、图片等媒体文件用字节流比较好,如果涉及到字符的话使用字符流比较好。
-
-### BIO,NIO,AIO 有什么区别?
-
-- **BIO (Blocking I/O):** 同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。
-- **NIO (New I/O):** NIO是一种同步非阻塞的I/O模型,在Java 1.4 中引入了NIO框架,对应 java.nio 包,提供了 Channel , Selector,Buffer等抽象。NIO中的N可以理解为Non-blocking,不单纯是New。它支持面向缓冲的,基于通道的I/O操作方法。 NIO提供了与传统BIO模型中的 `Socket` 和 `ServerSocket` 相对应的 `SocketChannel` 和 `ServerSocketChannel` 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发
-- **AIO (Asynchronous I/O):** AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的IO模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。AIO 是异步IO的缩写,虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作,IO操作本身是同步的。查阅网上相关资料,我发现就目前来说 AIO 的应用还不是很广泛,Netty 之前也尝试使用过 AIO,不过又放弃了。
-
-## 36. 常见关键字总结:static,final,this,super
-
-详见笔主的这篇文章:
-
-## 37. Collections 工具类和 Arrays 工具类常见方法总结
-
-详见笔主的这篇文章:
-
-### 38. 深拷贝 vs 浅拷贝
-
-1. **浅拷贝**:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝。
-2. **深拷贝**:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。
-
-
-
-## 参考
-
-- https://stackoverflow.com/questions/1906445/what-is-the-difference-between-jdk-and-jre
-- https://www.educba.com/oracle-vs-openjdk/
-- https://stackoverflow.com/questions/22358071/differences-between-oracle-jdk-and-openjdk?answertab=active#tab-top
-
-## 公众号
-
-如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
-
-**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"Java面试突击"** 即可免费领取!
-
-**Java工程师必备学习资源:** 一些Java工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。
-
-
-
diff --git "a/docs/java/Java\347\226\221\351\232\276\347\202\271.md" "b/docs/java/Java\347\226\221\351\232\276\347\202\271.md"
deleted file mode 100644
index 1a10e9586ef..00000000000
--- "a/docs/java/Java\347\226\221\351\232\276\347\202\271.md"
+++ /dev/null
@@ -1,373 +0,0 @@
-
-
-- [1. 基础](#1-基础)
- - [1.1. 正确使用 equals 方法](#11-正确使用-equals-方法)
- - [1.2. 整型包装类值的比较](#12-整型包装类值的比较)
- - [1.3. BigDecimal](#13-bigdecimal)
- - [1.3.1. BigDecimal 的用处](#131-bigdecimal-的用处)
- - [1.3.2. BigDecimal 的大小比较](#132-bigdecimal-的大小比较)
- - [1.3.3. BigDecimal 保留几位小数](#133-bigdecimal-保留几位小数)
- - [1.3.4. BigDecimal 的使用注意事项](#134-bigdecimal-的使用注意事项)
- - [1.3.5. 总结](#135-总结)
- - [1.4. 基本数据类型与包装数据类型的使用标准](#14-基本数据类型与包装数据类型的使用标准)
-- [2. 集合](#2-集合)
- - [2.1. Arrays.asList()使用指南](#21-arraysaslist使用指南)
- - [2.1.1. 简介](#211-简介)
- - [2.1.2. 《阿里巴巴Java 开发手册》对其的描述](#212-阿里巴巴java-开发手册对其的描述)
- - [2.1.3. 使用时的注意事项总结](#213-使用时的注意事项总结)
- - [2.1.4. 如何正确的将数组转换为ArrayList?](#214-如何正确的将数组转换为arraylist)
- - [2.2. Collection.toArray()方法使用的坑&如何反转数组](#22-collectiontoarray方法使用的坑如何反转数组)
- - [2.3. 不要在 foreach 循环里进行元素的 remove/add 操作](#23-不要在-foreach-循环里进行元素的-removeadd-操作)
-
-
-
-# 1. 基础
-
-## 1.1. 正确使用 equals 方法
-
-Object的equals方法容易抛空指针异常,应使用常量或确定有值的对象来调用 equals。
-
-举个例子:
-
-```java
-// 不能使用一个值为null的引用类型变量来调用非静态方法,否则会抛出异常
-String str = null;
-if (str.equals("SnailClimb")) {
- ...
-} else {
- ..
-}
-```
-
-运行上面的程序会抛出空指针异常,但是我们把第二行的条件判断语句改为下面这样的话,就不会抛出空指针异常,else 语句块得到执行。:
-
-```java
-"SnailClimb".equals(str);// false
-```
-不过更推荐使用 `java.util.Objects#equals`(JDK7 引入的工具类)。
-
-```java
-Objects.equals(null,"SnailClimb");// false
-```
-我们看一下`java.util.Objects#equals`的源码就知道原因了。
-```java
-public static boolean equals(Object a, Object b) {
- // 可以避免空指针异常。如果a==null的话此时a.equals(b)就不会得到执行,避免出现空指针异常。
- return (a == b) || (a != null && a.equals(b));
- }
-```
-
-**注意:**
-
-Reference:[Java中equals方法造成空指针异常的原因及解决方案](https://blog.csdn.net/tick_tock97/article/details/72824894)
-
-- 每种原始类型都有默认值一样,如int默认值为 0,boolean 的默认值为 false,null 是任何引用类型的默认值,不严格的说是所有 Object 类型的默认值。
-- 可以使用 == 或者 != 操作来比较null值,但是不能使用其他算法或者逻辑操作。在Java中`null == null`将返回true。
-- 不能使用一个值为null的引用类型变量来调用非静态方法,否则会抛出异常
-
-## 1.2. 整型包装类值的比较
-
-所有整型包装类对象值的比较必须使用equals方法。
-
-先看下面这个例子:
-
-```java
-Integer x = 3;
-Integer y = 3;
-System.out.println(x == y);// true
-Integer a = new Integer(3);
-Integer b = new Integer(3);
-System.out.println(a == b);//false
-System.out.println(a.equals(b));//true
-```
-
-当使用自动装箱方式创建一个Integer对象时,当数值在-128 ~127时,会将创建的 Integer 对象缓存起来,当下次再出现该数值时,直接从缓存中取出对应的Integer对象。所以上述代码中,x和y引用的是相同的Integer对象。
-
-**注意:**如果你的IDE(IDEA/Eclipse)上安装了阿里巴巴的p3c插件,这个插件如果检测到你用 ==的话会报错提示,推荐安装一个这个插件,很不错。
-
-## 1.3. BigDecimal
-
-### 1.3.1. BigDecimal 的用处
-
-《阿里巴巴Java开发手册》中提到:**浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用 equals 来判断。** 具体原理和浮点数的编码方式有关,这里就不多提了,我们下面直接上实例:
-
-```java
-float a = 1.0f - 0.9f;
-float b = 0.9f - 0.8f;
-System.out.println(a);// 0.100000024
-System.out.println(b);// 0.099999964
-System.out.println(a == b);// false
-```
-具有基本数学知识的我们很清楚的知道输出并不是我们想要的结果(**精度丢失**),我们如何解决这个问题呢?一种很常用的方法是:**使用使用 BigDecimal 来定义浮点数的值,再进行浮点数的运算操作。**
-
-```java
-BigDecimal a = new BigDecimal("1.0");
-BigDecimal b = new BigDecimal("0.9");
-BigDecimal c = new BigDecimal("0.8");
-BigDecimal x = a.subtract(b);// 0.1
-BigDecimal y = b.subtract(c);// 0.1
-System.out.println(x.equals(y));// true
-```
-
-### 1.3.2. BigDecimal 的大小比较
-
-`a.compareTo(b)` : 返回 -1 表示小于,0 表示 等于, 1表示 大于。
-
-```java
-BigDecimal a = new BigDecimal("1.0");
-BigDecimal b = new BigDecimal("0.9");
-System.out.println(a.compareTo(b));// 1
-```
-### 1.3.3. BigDecimal 保留几位小数
-
-通过 `setScale`方法设置保留几位小数以及保留规则。保留规则有挺多种,不需要记,IDEA会提示。
-
-```java
-BigDecimal m = new BigDecimal("1.255433");
-BigDecimal n = m.setScale(3,BigDecimal.ROUND_HALF_DOWN);
-System.out.println(n);// 1.255
-```
-
-### 1.3.4. BigDecimal 的使用注意事项
-
-注意:我们在使用BigDecimal时,为了防止精度丢失,推荐使用它的 **BigDecimal(String)** 构造方法来创建对象。《阿里巴巴Java开发手册》对这部分内容也有提到如下图所示。
-
-
-
-### 1.3.5. 总结
-
-BigDecimal 主要用来操作(大)浮点数,BigInteger 主要用来操作大整数(超过 long 类型)。
-
-BigDecimal 的实现利用到了 BigInteger, 所不同的是 BigDecimal 加入了小数位的概念
-
-## 1.4. 基本数据类型与包装数据类型的使用标准
-
-Reference:《阿里巴巴Java开发手册》
-
-- 【强制】所有的 POJO 类属性必须使用包装数据类型。
-- 【强制】RPC 方法的返回值和参数必须使用包装数据类型。
-- 【推荐】所有的局部变量使用基本数据类型。
-
-比如我们如果自定义了一个Student类,其中有一个属性是成绩score,如果用Integer而不用int定义,一次考试,学生可能没考,值是null,也可能考了,但考了0分,值是0,这两个表达的状态明显不一样.
-
-**说明** :POJO 类属性没有初值是提醒使用者在需要使用时,必须自己显式地进行赋值,任何 NPE 问题,或者入库检查,都由使用者来保证。
-
-**正例** : 数据库的查询结果可能是 null,因为自动拆箱,用基本数据类型接收有 NPE 风险。
-
-**反例** : 比如显示成交总额涨跌情况,即正负 x%,x 为基本数据类型,调用的 RPC 服务,调用不成功时,返回的是默认值,页面显示为 0%,这是不合理的,应该显示成中划线。所以包装数据类型的 null 值,能够表示额外的信息,如:远程调用失败,异常退出。
-
-# 2. 集合
-
-## 2.1. Arrays.asList()使用指南
-
-最近使用`Arrays.asList()`遇到了一些坑,然后在网上看到这篇文章:[Java Array to List Examples](http://javadevnotes.com/java-array-to-list-examples) 感觉挺不错的,但是还不是特别全面。所以,自己对于这块小知识点进行了简单的总结。
-
-### 2.1.1. 简介
-
-`Arrays.asList()`在平时开发中还是比较常见的,我们可以使用它将一个数组转换为一个List集合。
-
-```java
-String[] myArray = { "Apple", "Banana", "Orange" };
-List myList = Arrays.asList(myArray);
-//上面两个语句等价于下面一条语句
-List myList = Arrays.asList("Apple","Banana", "Orange");
-```
-
-JDK 源码对于这个方法的说明:
-
-```java
-/**
- *返回由指定数组支持的固定大小的列表。此方法作为基于数组和基于集合的API之间的桥梁,与 Collection.toArray()结合使用。返回的List是可序列化并实现RandomAccess接口。
- */
-public static List asList(T... a) {
- return new ArrayList<>(a);
-}
-```
-
-### 2.1.2. 《阿里巴巴Java 开发手册》对其的描述
-
-`Arrays.asList()`将数组转换为集合后,底层其实还是数组,《阿里巴巴Java 开发手册》对于这个方法有如下描述:
-
-方法.png)
-
-### 2.1.3. 使用时的注意事项总结
-
-**传递的数组必须是对象数组,而不是基本类型。**
-
-`Arrays.asList()`是泛型方法,传入的对象必须是对象数组。
-
-```java
-int[] myArray = { 1, 2, 3 };
-List myList = Arrays.asList(myArray);
-System.out.println(myList.size());//1
-System.out.println(myList.get(0));//数组地址值
-System.out.println(myList.get(1));//报错:ArrayIndexOutOfBoundsException
-int [] array=(int[]) myList.get(0);
-System.out.println(array[0]);//1
-```
-当传入一个原生数据类型数组时,`Arrays.asList()` 的真正得到的参数就不是数组中的元素,而是数组对象本身!此时List 的唯一元素就是这个数组,这也就解释了上面的代码。
-
-我们使用包装类型数组就可以解决这个问题。
-
-```java
-Integer[] myArray = { 1, 2, 3 };
-```
-
-**使用集合的修改方法:`add()`、`remove()`、`clear()`会抛出异常。**
-
-```java
-List myList = Arrays.asList(1, 2, 3);
-myList.add(4);//运行时报错:UnsupportedOperationException
-myList.remove(1);//运行时报错:UnsupportedOperationException
-myList.clear();//运行时报错:UnsupportedOperationException
-```
-
-`Arrays.asList()` 方法返回的并不是 `java.util.ArrayList` ,而是 `java.util.Arrays` 的一个内部类,这个内部类并没有实现集合的修改方法或者说并没有重写这些方法。
-
-```java
-List myList = Arrays.asList(1, 2, 3);
-System.out.println(myList.getClass());//class java.util.Arrays$ArrayList
-```
-
-下图是`java.util.Arrays$ArrayList`的简易源码,我们可以看到这个类重写的方法有哪些。
-
-```java
- private static class ArrayList extends AbstractList
- implements RandomAccess, java.io.Serializable
- {
- ...
-
- @Override
- public E get(int index) {
- ...
- }
-
- @Override
- public E set(int index, E element) {
- ...
- }
-
- @Override
- public int indexOf(Object o) {
- ...
- }
-
- @Override
- public boolean contains(Object o) {
- ...
- }
-
- @Override
- public void forEach(Consumer super E> action) {
- ...
- }
-
- @Override
- public void replaceAll(UnaryOperator operator) {
- ...
- }
-
- @Override
- public void sort(Comparator super E> c) {
- ...
- }
- }
-```
-
-我们再看一下`java.util.AbstractList`的`remove()`方法,这样我们就明白为啥会抛出`UnsupportedOperationException`。
-
-```java
-public E remove(int index) {
- throw new UnsupportedOperationException();
-}
-```
-
-### 2.1.4. 如何正确的将数组转换为ArrayList?
-
-stackoverflow:https://dwz.cn/vcBkTiTW
-
-**1. 自己动手实现(教育目的)**
-
-```java
-//JDK1.5+
-static List arrayToList(final T[] array) {
- final List l = new ArrayList(array.length);
-
- for (final T s : array) {
- l.add(s);
- }
- return (l);
-}
-```
-
-```java
-Integer [] myArray = { 1, 2, 3 };
-System.out.println(arrayToList(myArray).getClass());//class java.util.ArrayList
-```
-
-**2. 最简便的方法(推荐)**
-
-```java
-List list = new ArrayList<>(Arrays.asList("a", "b", "c"))
-```
-
-**3. 使用 Java8 的Stream(推荐)**
-
-```java
-Integer [] myArray = { 1, 2, 3 };
-List myList = Arrays.stream(myArray).collect(Collectors.toList());
-//基本类型也可以实现转换(依赖boxed的装箱操作)
-int [] myArray2 = { 1, 2, 3 };
-List myList = Arrays.stream(myArray2).boxed().collect(Collectors.toList());
-```
-
-**4. 使用 Guava(推荐)**
-
-对于不可变集合,你可以使用[`ImmutableList`](https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/ImmutableList.java)类及其[`of()`](https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/ImmutableList.java#L101)与[`copyOf()`](https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/ImmutableList.java#L225)工厂方法:(参数不能为空)
-
-```java
-List il = ImmutableList.of("string", "elements"); // from varargs
-List il = ImmutableList.copyOf(aStringArray); // from array
-```
-对于可变集合,你可以使用[`Lists`](https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/Lists.java)类及其[`newArrayList()`](https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/Lists.java#L87)工厂方法:
-
-```java
-List l1 = Lists.newArrayList(anotherListOrCollection); // from collection
-List l2 = Lists.newArrayList(aStringArray); // from array
-List l3 = Lists.newArrayList("or", "string", "elements"); // from varargs
-```
-
-**5. 使用 Apache Commons Collections**
-
-```java
-List list = new ArrayList();
-CollectionUtils.addAll(list, str);
-```
-
-## 2.2. Collection.toArray()方法使用的坑&如何反转数组
-
-该方法是一个泛型方法:` T[] toArray(T[] a);` 如果`toArray`方法中没有传递任何参数的话返回的是`Object`类型数组。
-
-```java
-String [] s= new String[]{
- "dog", "lazy", "a", "over", "jumps", "fox", "brown", "quick", "A"
-};
-List list = Arrays.asList(s);
-Collections.reverse(list);
-s=list.toArray(new String[0]);//没有指定类型的话会报错
-```
-
-由于JVM优化,`new String[0]`作为`Collection.toArray()`方法的参数现在使用更好,`new String[0]`就是起一个模板的作用,指定了返回数组的类型,0是为了节省空间,因为它只是为了说明返回的类型。详见:
-
-## 2.3. 不要在 foreach 循环里进行元素的 remove/add 操作
-
-如果要进行`remove`操作,可以调用迭代器的 `remove `方法而不是集合类的 remove 方法。因为如果列表在任何时间从结构上修改创建迭代器之后,以任何方式除非通过迭代器自身`remove/add`方法,迭代器都将抛出一个`ConcurrentModificationException`,这就是单线程状态下产生的 **fail-fast 机制**。
-
-> **fail-fast 机制** :多个线程对 fail-fast 集合进行修改的时,可能会抛出ConcurrentModificationException,单线程下也会出现这种情况,上面已经提到过。
-
-`java.util`包下面的所有的集合类都是fail-fast的,而`java.util.concurrent`包下面的所有的类都是fail-safe的。
-
-
-
-
-
diff --git "a/docs/java/Java\347\250\213\345\272\217\350\256\276\350\256\241\351\242\230.md" "b/docs/java/Java\347\250\213\345\272\217\350\256\276\350\256\241\351\242\230.md"
deleted file mode 100644
index 46c9c16994b..00000000000
--- "a/docs/java/Java\347\250\213\345\272\217\350\256\276\350\256\241\351\242\230.md"
+++ /dev/null
@@ -1,125 +0,0 @@
-## 泛型的实际应用
-
-### 实现最小值函数
-
-自己设计一个泛型的获取数组最小值的函数.并且这个方法只能接受Number的子类并且实现了Comparable接口。
-
-```java
-//注意:Number并没有实现Comparable
-private static > T min(T[] values) {
- if (values == null || values.length == 0) return null;
- T min = values[0];
- for (int i = 1; i < values.length; i++) {
- if (min.compareTo(values[i]) > 0) min = values[i];
- }
- return min;
-}
-```
-
-测试:
-
-```java
-int minInteger = min(new Integer[]{1, 2, 3});//result:1
-double minDouble = min(new Double[]{1.2, 2.2, -1d});//result:-1d
-String typeError = min(new String[]{"1","3"});//报错
-```
-
-## 数据结构
-
-### 使用数组实现栈
-
-**自己实现一个栈,要求这个栈具有`push()`、`pop()`(返回栈顶元素并出栈)、`peek()` (返回栈顶元素不出栈)、`isEmpty()`、`size()`这些基本的方法。**
-
-提示:每次入栈之前先判断栈的容量是否够用,如果不够用就用`Arrays.copyOf()`进行扩容;
-
-```java
-public class MyStack {
- private int[] storage;//存放栈中元素的数组
- private int capacity;//栈的容量
- private int count;//栈中元素数量
- private static final int GROW_FACTOR = 2;
-
- //TODO:不带初始容量的构造方法。默认容量为8
- public MyStack() {
- this.capacity = 8;
- this.storage=new int[8];
- this.count = 0;
- }
-
- //TODO:带初始容量的构造方法
- public MyStack(int initialCapacity) {
- if (initialCapacity < 1)
- throw new IllegalArgumentException("Capacity too small.");
-
- this.capacity = initialCapacity;
- this.storage = new int[initialCapacity];
- this.count = 0;
- }
-
- //TODO:入栈
- public void push(int value) {
- if (count == capacity) {
- ensureCapacity();
- }
- storage[count++] = value;
- }
-
- //TODO:确保容量大小
- private void ensureCapacity() {
- int newCapacity = capacity * GROW_FACTOR;
- storage = Arrays.copyOf(storage, newCapacity);
- capacity = newCapacity;
- }
-
- //TODO:返回栈顶元素并出栈
- private int pop() {
- count--;
- if (count == -1)
- throw new IllegalArgumentException("Stack is empty.");
-
- return storage[count];
- }
-
- //TODO:返回栈顶元素不出栈
- private int peek() {
- if (count == 0){
- throw new IllegalArgumentException("Stack is empty.");
- }else {
- return storage[count-1];
- }
- }
-
- //TODO:判断栈是否为空
- private boolean isEmpty() {
- return count == 0;
- }
-
- //TODO:返回栈中元素的个数
- private int size() {
- return count;
- }
-
-}
-
-```
-
-验证
-
-```java
-MyStack myStack = new MyStack(3);
-myStack.push(1);
-myStack.push(2);
-myStack.push(3);
-myStack.push(4);
-myStack.push(5);
-myStack.push(6);
-myStack.push(7);
-myStack.push(8);
-System.out.println(myStack.peek());//8
-System.out.println(myStack.size());//8
-for (int i = 0; i < 8; i++) {
- System.out.println(myStack.pop());
-}
-System.out.println(myStack.isEmpty());//true
-myStack.pop();//报错:java.lang.IllegalArgumentException: Stack is empty.
-```
\ No newline at end of file
diff --git "a/docs/java/Java\347\274\226\347\250\213\350\247\204\350\214\203.md" "b/docs/java/Java\347\274\226\347\250\213\350\247\204\350\214\203.md"
deleted file mode 100644
index 6b4731ef3c9..00000000000
--- "a/docs/java/Java\347\274\226\347\250\213\350\247\204\350\214\203.md"
+++ /dev/null
@@ -1,30 +0,0 @@
-讲真的,下面推荐的文章或者资源建议阅读 3 遍以上。
-
-### 团队
-
-- **阿里巴巴Java开发手册(详尽版)**
-- **Google Java编程风格指南:**
-
-### 个人
-
-- **程序员你为什么这么累:**
-
-### 如何写出优雅的 Java 代码
-
-1. 使用 IntelliJ IDEA 作为您的集成开发环境 (IDE)
-1. 使用 JDK 8 或更高版本
-1. 使用 Maven/Gradle
-1. 使用 Lombok
-1. 编写单元测试
-1. 重构:常见,但也很慢
-1. 注意代码规范
-1. 定期联络客户,以获取他们的反馈
-
-上述建议的详细内容:[八点建议助您写出优雅的Java代码](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485140&idx=1&sn=ecaeace613474f1859aaeed0282ae680&chksm=cea2491ff9d5c00982ffaece847ce1aead89fdb3fe190752d9837c075c79fc95db5940992c56&token=1328169465&lang=zh_CN&scene=21#wechat_redirect)。
-
-更多代码优化相关内容推荐:
-
-- [业务复杂=if else?刚来的大神竟然用策略+工厂彻底干掉了他们!](https://juejin.im/post/5dad23685188251d2c4ea2b6)
-- [一些不错的 Java 实践!推荐阅读3遍以上!](http://lrwinx.github.io/2017/03/04/%E7%BB%86%E6%80%9D%E6%9E%81%E6%81%90-%E4%BD%A0%E7%9C%9F%E7%9A%84%E4%BC%9A%E5%86%99java%E5%90%97/)
-- [[解锁新姿势] 兄dei,你代码需要优化了](https://juejin.im/post/5dafbc02e51d4524a0060bdd)
-- [消灭 Java 代码的“坏味道”](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485599&idx=1&sn=d83ff4e6b1ee951a0a33508a10980ea3&chksm=cea24754f9d5ce426d18b435a8c373ddc580c06c7d6a45cc51377361729c31c7301f1bbc3b78&token=1328169465&lang=zh_CN#rd)
\ No newline at end of file
diff --git a/docs/java/Multithread/AQS.md b/docs/java/Multithread/AQS.md
deleted file mode 100644
index 287116d7da2..00000000000
--- a/docs/java/Multithread/AQS.md
+++ /dev/null
@@ -1,478 +0,0 @@
-
-点击关注[公众号](#公众号)及时获取笔主最新更新文章,并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。
-
-
-- [1 AQS 简单介绍](#1-aqs-简单介绍)
-- [2 AQS 原理](#2-aqs-原理)
- - [2.1 AQS 原理概览](#21-aqs-原理概览)
- - [2.2 AQS 对资源的共享方式](#22-aqs-对资源的共享方式)
- - [2.3 AQS底层使用了模板方法模式](#23-aqs底层使用了模板方法模式)
-- [3 Semaphore\(信号量\)-允许多个线程同时访问](#3-semaphore信号量-允许多个线程同时访问)
-- [4 CountDownLatch (倒计时器)](#4-countdownlatch-倒计时器)
- - [4.1 CountDownLatch 的三种典型用法](#41-countdownlatch-的三种典型用法)
- - [4.2 CountDownLatch 的使用示例](#42-countdownlatch-的使用示例)
- - [4.3 CountDownLatch 的不足](#43-countdownlatch-的不足)
- - [4.4 CountDownLatch相常见面试题:](#44-countdownlatch相常见面试题)
-- [5 CyclicBarrier\(循环栅栏\)](#5-cyclicbarrier循环栅栏)
- - [5.1 CyclicBarrier 的应用场景](#51-cyclicbarrier-的应用场景)
- - [5.2 CyclicBarrier 的使用示例](#52-cyclicbarrier-的使用示例)
- - [5.3 CyclicBarrier和CountDownLatch的区别](#53-cyclicbarrier和countdownlatch的区别)
-- [6 ReentrantLock 和 ReentrantReadWriteLock](#6-reentrantlock-和-reentrantreadwritelock)
-
-
-
-> 常见问题:AQS原理?;CountDownLatch和CyclicBarrier了解吗,两者的区别是什么?用过Semaphore吗?
-
-
-### 1 AQS 简单介绍
-AQS的全称为(AbstractQueuedSynchronizer),这个类在java.util.concurrent.locks包下面。
-
-
-
-AQS是一个用来构建锁和同步器的框架,使用AQS能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的ReentrantLock,Semaphore,其他的诸如ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基于AQS的。当然,我们自己也能利用AQS非常轻松容易地构造出符合我们自己需求的同步器。
-
-### 2 AQS 原理
-
-> 在面试中被问到并发知识的时候,大多都会被问到“请你说一下自己对于AQS原理的理解”。下面给大家一个示例供大家参考,面试不是背题,大家一定要加入自己的思想,即使加入不了自己的思想也要保证自己能够通俗的讲出来而不是背出来。
-
-下面大部分内容其实在AQS类注释上已经给出了,不过是英语看着比较吃力一点,感兴趣的话可以看看源码。
-
-#### 2.1 AQS 原理概览
-
-**AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。**
-
-> CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。
-
-看个AQS(AbstractQueuedSynchronizer)原理图:
-
-
-
-
-AQS使用一个int成员变量来表示同步状态,通过内置的FIFO队列来完成获取资源线程的排队工作。AQS使用CAS对该同步状态进行原子操作实现对其值的修改。
-
-```java
-private volatile int state;//共享变量,使用volatile修饰保证线程可见性
-```
-
-状态信息通过protected类型的getState,setState,compareAndSetState进行操作
-
-```java
-
-//返回同步状态的当前值
-protected final int getState() {
- return state;
-}
- // 设置同步状态的值
-protected final void setState(int newState) {
- state = newState;
-}
-//原子地(CAS操作)将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值)
-protected final boolean compareAndSetState(int expect, int update) {
- return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
-}
-```
-
-#### 2.2 AQS 对资源的共享方式
-
-**AQS定义两种资源共享方式**
-
-- **Exclusive**(独占):只有一个线程能执行,如ReentrantLock。又可分为公平锁和非公平锁:
- - 公平锁:按照线程在队列中的排队顺序,先到者先拿到锁
- - 非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的
-- **Share**(共享):多个线程可同时执行,如Semaphore/CountDownLatch。Semaphore、CountDownLatCh、 CyclicBarrier、ReadWriteLock 我们都会在后面讲到。
-
-ReentrantReadWriteLock 可以看成是组合式,因为ReentrantReadWriteLock也就是读写锁允许多个线程同时对某一资源进行读。
-
-不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源 state 的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在上层已经帮我们实现好了。
-
-#### 2.3 AQS底层使用了模板方法模式
-
-同步器的设计是基于模板方法模式的,如果需要自定义同步器一般的方式是这样(模板方法模式很经典的一个应用):
-
-1. 使用者继承AbstractQueuedSynchronizer并重写指定的方法。(这些重写方法很简单,无非是对于共享资源state的获取和释放)
-2. 将AQS组合在自定义同步组件的实现中,并调用其模板方法,而这些模板方法会调用使用者重写的方法。
-
-这和我们以往通过实现接口的方式有很大区别,这是模板方法模式很经典的一个运用,下面简单的给大家介绍一下模板方法模式,模板方法模式是一个很容易理解的设计模式之一。
-
-> 模板方法模式是基于”继承“的,主要是为了在不改变模板结构的前提下在子类中重新定义模板中的内容以实现复用代码。举个很简单的例子假如我们要去一个地方的步骤是:购票`buyTicket()`->安检`securityCheck()`->乘坐某某工具回家`ride()`->到达目的地`arrive()`。我们可能乘坐不同的交通工具回家比如飞机或者火车,所以除了`ride()`方法,其他方法的实现几乎相同。我们可以定义一个包含了这些方法的抽象类,然后用户根据自己的需要继承该抽象类然后修改 `ride()`方法。
-
-**AQS使用了模板方法模式,自定义同步器时需要重写下面几个AQS提供的模板方法:**
-
-```java
-isHeldExclusively()//该线程是否正在独占资源。只有用到condition才需要去实现它。
-tryAcquire(int)//独占方式。尝试获取资源,成功则返回true,失败则返回false。
-tryRelease(int)//独占方式。尝试释放资源,成功则返回true,失败则返回false。
-tryAcquireShared(int)//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
-tryReleaseShared(int)//共享方式。尝试释放资源,成功则返回true,失败则返回false。
-
-```
-
-默认情况下,每个方法都抛出 `UnsupportedOperationException`。 这些方法的实现必须是内部线程安全的,并且通常应该简短而不是阻塞。AQS类中的其他方法都是final ,所以无法被其他类使用,只有这几个方法可以被其他类使用。
-
-以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。
-
-再以CountDownLatch以例,任务分为N个子线程去执行,state也初始化为N(注意N要与线程个数一致)。这N个子线程是并行执行的,每个子线程执行完后countDown()一次,state会CAS(Compare and Swap)减1。等到所有子线程都执行完后(即state=0),会unpark()主调用线程,然后主调用线程就会从await()函数返回,继续后余动作。
-
-一般来说,自定义同步器要么是独占方法,要么是共享方式,他们也只需实现`tryAcquire-tryRelease`、`tryAcquireShared-tryReleaseShared`中的一种即可。但AQS也支持自定义同步器同时实现独占和共享两种方式,如`ReentrantReadWriteLock`。
-
-推荐两篇 AQS 原理和相关源码分析的文章:
-
-- http://www.cnblogs.com/waterystone/p/4920797.html
-- https://www.cnblogs.com/chengxiao/archive/2017/07/24/7141160.html
-
-
-
-### 3 Semaphore(信号量)-允许多个线程同时访问
-
-**synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源,Semaphore(信号量)可以指定多个线程同时访问某个资源。** 示例代码如下:
-
-```java
-/**
- *
- * @author Snailclimb
- * @date 2018年9月30日
- * @Description: 需要一次性拿一个许可的情况
- */
-public class SemaphoreExample1 {
- // 请求的数量
- private static final int threadCount = 550;
-
- public static void main(String[] args) throws InterruptedException {
- // 创建一个具有固定线程数量的线程池对象(如果这里线程池的线程数量给太少的话你会发现执行的很慢)
- ExecutorService threadPool = Executors.newFixedThreadPool(300);
- // 一次只能允许执行的线程数量。
- final Semaphore semaphore = new Semaphore(20);
-
- for (int i = 0; i < threadCount; i++) {
- final int threadnum = i;
- threadPool.execute(() -> {// Lambda 表达式的运用
- try {
- semaphore.acquire();// 获取一个许可,所以可运行线程数量为20/1=20
- test(threadnum);
- semaphore.release();// 释放一个许可
- } catch (InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
-
- });
- }
- threadPool.shutdown();
- System.out.println("finish");
- }
-
- public static void test(int threadnum) throws InterruptedException {
- Thread.sleep(1000);// 模拟请求的耗时操作
- System.out.println("threadnum:" + threadnum);
- Thread.sleep(1000);// 模拟请求的耗时操作
- }
-}
-```
-
-执行 `acquire` 方法阻塞,直到有一个许可证可以获得然后拿走一个许可证;每个 `release` 方法增加一个许可证,这可能会释放一个阻塞的acquire方法。然而,其实并没有实际的许可证这个对象,Semaphore只是维持了一个可获得许可证的数量。 Semaphore经常用于限制获取某种资源的线程数量。
-
-当然一次也可以一次拿取和释放多个许可,不过一般没有必要这样做:
-
-```java
- semaphore.acquire(5);// 获取5个许可,所以可运行线程数量为20/5=4
- test(threadnum);
- semaphore.release(5);// 获取5个许可,所以可运行线程数量为20/5=4
-```
-
-除了 `acquire`方法之外,另一个比较常用的与之对应的方法是`tryAcquire`方法,该方法如果获取不到许可就立即返回false。
-
-
-Semaphore 有两种模式,公平模式和非公平模式。
-
-- **公平模式:** 调用acquire的顺序就是获取许可证的顺序,遵循FIFO;
-- **非公平模式:** 抢占式的。
-
-**Semaphore 对应的两个构造方法如下:**
-
-```java
- public Semaphore(int permits) {
- sync = new NonfairSync(permits);
- }
-
- public Semaphore(int permits, boolean fair) {
- sync = fair ? new FairSync(permits) : new NonfairSync(permits);
- }
-```
-**这两个构造方法,都必须提供许可的数量,第二个构造方法可以指定是公平模式还是非公平模式,默认非公平模式。**
-
-由于篇幅问题,如果对 Semaphore 源码感兴趣的朋友可以看下面这篇文章:
-
-- https://blog.csdn.net/qq_19431333/article/details/70212663
-
-### 4 CountDownLatch (倒计时器)
-
-CountDownLatch是一个同步工具类,它允许一个或多个线程一直等待,直到其他线程的操作执行完后再执行。在Java并发中,countdownlatch的概念是一个常见的面试题,所以一定要确保你很好的理解了它。
-
-#### 4.1 CountDownLatch 的三种典型用法
-
-①某一线程在开始运行前等待n个线程执行完毕。将 CountDownLatch 的计数器初始化为n :`new CountDownLatch(n) `,每当一个任务线程执行完毕,就将计数器减1 `countdownlatch.countDown()`,当计数器的值变为0时,在`CountDownLatch上 await()` 的线程就会被唤醒。一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行。
-
-②实现多个线程开始执行任务的最大并行性。注意是并行性,不是并发,强调的是多个线程在某一时刻同时开始执行。类似于赛跑,将多个线程放到起点,等待发令枪响,然后同时开跑。做法是初始化一个共享的 `CountDownLatch` 对象,将其计数器初始化为 1 :`new CountDownLatch(1) `,多个线程在开始执行任务前首先 `coundownlatch.await()`,当主线程调用 countDown() 时,计数器变为0,多个线程同时被唤醒。
-
-③死锁检测:一个非常方便的使用场景是,你可以使用n个线程访问共享资源,在每次测试阶段的线程数目是不同的,并尝试产生死锁。
-
-#### 4.2 CountDownLatch 的使用示例
-
-```java
-/**
- *
- * @author SnailClimb
- * @date 2018年10月1日
- * @Description: CountDownLatch 使用方法示例
- */
-public class CountDownLatchExample1 {
- // 请求的数量
- private static final int threadCount = 550;
-
- public static void main(String[] args) throws InterruptedException {
- // 创建一个具有固定线程数量的线程池对象(如果这里线程池的线程数量给太少的话你会发现执行的很慢)
- ExecutorService threadPool = Executors.newFixedThreadPool(300);
- final CountDownLatch countDownLatch = new CountDownLatch(threadCount);
- for (int i = 0; i < threadCount; i++) {
- final int threadnum = i;
- threadPool.execute(() -> {// Lambda 表达式的运用
- try {
- test(threadnum);
- } catch (InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- } finally {
- countDownLatch.countDown();// 表示一个请求已经被完成
- }
-
- });
- }
- countDownLatch.await();
- threadPool.shutdown();
- System.out.println("finish");
- }
-
- public static void test(int threadnum) throws InterruptedException {
- Thread.sleep(1000);// 模拟请求的耗时操作
- System.out.println("threadnum:" + threadnum);
- Thread.sleep(1000);// 模拟请求的耗时操作
- }
-}
-
-```
-上面的代码中,我们定义了请求的数量为550,当这550个请求被处理完成之后,才会执行`System.out.println("finish");`。
-
-与CountDownLatch的第一次交互是主线程等待其他线程。主线程必须在启动其他线程后立即调用CountDownLatch.await()方法。这样主线程的操作就会在这个方法上阻塞,直到其他线程完成各自的任务。
-
-其他N个线程必须引用闭锁对象,因为他们需要通知CountDownLatch对象,他们已经完成了各自的任务。这种通知机制是通过 CountDownLatch.countDown()方法来完成的;每调用一次这个方法,在构造函数中初始化的count值就减1。所以当N个线程都调 用了这个方法,count的值等于0,然后主线程就能通过await()方法,恢复执行自己的任务。
-
-#### 4.3 CountDownLatch 的不足
-
-CountDownLatch是一次性的,计数器的值只能在构造方法中初始化一次,之后没有任何机制再次对其设置值,当CountDownLatch使用完毕后,它不能再次被使用。
-
-#### 4.4 CountDownLatch相常见面试题:
-
-解释一下CountDownLatch概念?
-
-CountDownLatch 和CyclicBarrier的不同之处?
-
-给出一些CountDownLatch使用的例子?
-
-CountDownLatch 类中主要的方法?
-
-### 5 CyclicBarrier(循环栅栏)
-
-CyclicBarrier 和 CountDownLatch 非常类似,它也可以实现线程间的技术等待,但是它的功能比 CountDownLatch 更加复杂和强大。主要应用场景和 CountDownLatch 类似。
-
-CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。CyclicBarrier默认的构造方法是 `CyclicBarrier(int parties)`,其参数表示屏障拦截的线程数量,每个线程调用`await`方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。
-
-#### 5.1 CyclicBarrier 的应用场景
-
-CyclicBarrier 可以用于多线程计算数据,最后合并计算结果的应用场景。比如我们用一个Excel保存了用户所有银行流水,每个Sheet保存一个帐户近一年的每笔银行流水,现在需要统计用户的日均银行流水,先用多线程处理每个sheet里的银行流水,都执行完之后,得到每个sheet的日均银行流水,最后,再用barrierAction用这些线程的计算结果,计算出整个Excel的日均银行流水。
-
-#### 5.2 CyclicBarrier 的使用示例
-
-示例1:
-
-```java
-/**
- *
- * @author Snailclimb
- * @date 2018年10月1日
- * @Description: 测试 CyclicBarrier 类中带参数的 await() 方法
- */
-public class CyclicBarrierExample2 {
- // 请求的数量
- private static final int threadCount = 550;
- // 需要同步的线程数量
- private static final CyclicBarrier cyclicBarrier = new CyclicBarrier(5);
-
- public static void main(String[] args) throws InterruptedException {
- // 创建线程池
- ExecutorService threadPool = Executors.newFixedThreadPool(10);
-
- for (int i = 0; i < threadCount; i++) {
- final int threadNum = i;
- Thread.sleep(1000);
- threadPool.execute(() -> {
- try {
- test(threadNum);
- } catch (InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- } catch (BrokenBarrierException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- });
- }
- threadPool.shutdown();
- }
-
- public static void test(int threadnum) throws InterruptedException, BrokenBarrierException {
- System.out.println("threadnum:" + threadnum + "is ready");
- try {
- /**等待60秒,保证子线程完全执行结束*/
- cyclicBarrier.await(60, TimeUnit.SECONDS);
- } catch (Exception e) {
- System.out.println("-----CyclicBarrierException------");
- }
- System.out.println("threadnum:" + threadnum + "is finish");
- }
-
-}
-```
-
-运行结果,如下:
-
-```
-threadnum:0is ready
-threadnum:1is ready
-threadnum:2is ready
-threadnum:3is ready
-threadnum:4is ready
-threadnum:4is finish
-threadnum:0is finish
-threadnum:1is finish
-threadnum:2is finish
-threadnum:3is finish
-threadnum:5is ready
-threadnum:6is ready
-threadnum:7is ready
-threadnum:8is ready
-threadnum:9is ready
-threadnum:9is finish
-threadnum:5is finish
-threadnum:8is finish
-threadnum:7is finish
-threadnum:6is finish
-......
-```
-可以看到当线程数量也就是请求数量达到我们定义的 5 个的时候, `await`方法之后的方法才被执行。
-
-另外,CyclicBarrier还提供一个更高级的构造函数`CyclicBarrier(int parties, Runnable barrierAction)`,用于在线程到达屏障时,优先执行`barrierAction`,方便处理更复杂的业务场景。示例代码如下:
-
-```java
-/**
- *
- * @author SnailClimb
- * @date 2018年10月1日
- * @Description: 新建 CyclicBarrier 的时候指定一个 Runnable
- */
-public class CyclicBarrierExample3 {
- // 请求的数量
- private static final int threadCount = 550;
- // 需要同步的线程数量
- private static final CyclicBarrier cyclicBarrier = new CyclicBarrier(5, () -> {
- System.out.println("------当线程数达到之后,优先执行------");
- });
-
- public static void main(String[] args) throws InterruptedException {
- // 创建线程池
- ExecutorService threadPool = Executors.newFixedThreadPool(10);
-
- for (int i = 0; i < threadCount; i++) {
- final int threadNum = i;
- Thread.sleep(1000);
- threadPool.execute(() -> {
- try {
- test(threadNum);
- } catch (InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- } catch (BrokenBarrierException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- });
- }
- threadPool.shutdown();
- }
-
- public static void test(int threadnum) throws InterruptedException, BrokenBarrierException {
- System.out.println("threadnum:" + threadnum + "is ready");
- cyclicBarrier.await();
- System.out.println("threadnum:" + threadnum + "is finish");
- }
-
-}
-```
-
-运行结果,如下:
-
-```
-threadnum:0is ready
-threadnum:1is ready
-threadnum:2is ready
-threadnum:3is ready
-threadnum:4is ready
-------当线程数达到之后,优先执行------
-threadnum:4is finish
-threadnum:0is finish
-threadnum:2is finish
-threadnum:1is finish
-threadnum:3is finish
-threadnum:5is ready
-threadnum:6is ready
-threadnum:7is ready
-threadnum:8is ready
-threadnum:9is ready
-------当线程数达到之后,优先执行------
-threadnum:9is finish
-threadnum:5is finish
-threadnum:6is finish
-threadnum:8is finish
-threadnum:7is finish
-......
-```
-#### 5.3 CyclicBarrier和CountDownLatch的区别
-
-CountDownLatch是计数器,只能使用一次,而CyclicBarrier的计数器提供reset功能,可以多次使用。但是我不那么认为它们之间的区别仅仅就是这么简单的一点。我们来从jdk作者设计的目的来看,javadoc是这么描述它们的:
-
-> CountDownLatch: A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes.(CountDownLatch: 一个或者多个线程,等待其他多个线程完成某件事情之后才能执行;)
-> CyclicBarrier : A synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point.(CyclicBarrier : 多个线程互相等待,直到到达同一个同步点,再继续一起执行。)
-
-对于CountDownLatch来说,重点是“一个线程(多个线程)等待”,而其他的N个线程在完成“某件事情”之后,可以终止,也可以等待。而对于CyclicBarrier,重点是多个线程,在任意一个线程没有完成,所有的线程都必须等待。
-
-CountDownLatch是计数器,线程完成一个记录一个,只不过计数不是递增而是递减,而CyclicBarrier更像是一个阀门,需要所有线程都到达,阀门才能打开,然后继续执行。
-
-
-
-CyclicBarrier和CountDownLatch的区别这部分内容参考了如下两篇文章:
-
-- https://blog.csdn.net/u010185262/article/details/54692886
-- https://blog.csdn.net/tolcf/article/details/50925145?utm_source=blogxgwz0
-
-### 6 ReentrantLock 和 ReentrantReadWriteLock
-
-ReentrantLock 和 synchronized 的区别在上面已经讲过了这里就不多做讲解。另外,需要注意的是:读写锁 ReentrantReadWriteLock 可以保证多个线程可以同时读,所以在读操作远大于写操作的时候,读写锁就非常有用了。
-
-## 公众号
-
-如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
-
-**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"面试突击"** 即可免费领取!
-
-**Java工程师必备学习资源:** 一些Java工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。
-
-
\ No newline at end of file
diff --git a/docs/java/Multithread/JavaConcurrencyAdvancedCommonInterviewQuestions.md b/docs/java/Multithread/JavaConcurrencyAdvancedCommonInterviewQuestions.md
deleted file mode 100644
index c3143b25582..00000000000
--- a/docs/java/Multithread/JavaConcurrencyAdvancedCommonInterviewQuestions.md
+++ /dev/null
@@ -1,925 +0,0 @@
-点击关注[公众号](#公众号)及时获取笔主最新更新文章,并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。
-
-
-
-- [Java 并发进阶常见面试题总结](#java-并发进阶常见面试题总结)
- - [1. synchronized 关键字](#1-synchronized-关键字)
- - [1.1. 说一说自己对于 synchronized 关键字的了解](#11-说一说自己对于-synchronized-关键字的了解)
- - [1.2. 说说自己是怎么使用 synchronized 关键字,在项目中用到了吗](#12-说说自己是怎么使用-synchronized-关键字在项目中用到了吗)
- - [1.3. 讲一下 synchronized 关键字的底层原理](#13-讲一下-synchronized-关键字的底层原理)
- - [1.4. 说说 JDK1.6 之后的synchronized 关键字底层做了哪些优化,可以详细介绍一下这些优化吗](#14-说说-jdk16-之后的synchronized-关键字底层做了哪些优化可以详细介绍一下这些优化吗)
- - [1.5. 谈谈 synchronized和ReentrantLock 的区别](#15-谈谈-synchronized和reentrantlock-的区别)
- - [2. volatile关键字](#2-volatile关键字)
- - [2.1. 讲一下Java内存模型](#21-讲一下java内存模型)
- - [2.2. 说说 synchronized 关键字和 volatile 关键字的区别](#22-说说-synchronized-关键字和-volatile-关键字的区别)
- - [3. ThreadLocal](#3-threadlocal)
- - [3.1. ThreadLocal简介](#31-threadlocal简介)
- - [3.2. ThreadLocal示例](#32-threadlocal示例)
- - [3.3. ThreadLocal原理](#33-threadlocal原理)
- - [3.4. ThreadLocal 内存泄露问题](#34-threadlocal-内存泄露问题)
- - [4. 线程池](#4-线程池)
- - [4.1. 为什么要用线程池?](#41-为什么要用线程池)
- - [4.2. 实现Runnable接口和Callable接口的区别](#42-实现runnable接口和callable接口的区别)
- - [4.3. 执行execute()方法和submit()方法的区别是什么呢?](#43-执行execute方法和submit方法的区别是什么呢)
- - [4.4. 如何创建线程池](#44-如何创建线程池)
- - [5. Atomic 原子类](#5-atomic-原子类)
- - [5.1. 介绍一下Atomic 原子类](#51-介绍一下atomic-原子类)
- - [5.2. JUC 包中的原子类是哪4类?](#52-juc-包中的原子类是哪4类)
- - [5.3. 讲讲 AtomicInteger 的使用](#53-讲讲-atomicinteger-的使用)
- - [5.4. 能不能给我简单介绍一下 AtomicInteger 类的原理](#54-能不能给我简单介绍一下-atomicinteger-类的原理)
- - [6. AQS](#6-aqs)
- - [6.1. AQS 介绍](#61-aqs-介绍)
- - [6.2. AQS 原理分析](#62-aqs-原理分析)
- - [6.2.1. AQS 原理概览](#621-aqs-原理概览)
- - [6.2.2. AQS 对资源的共享方式](#622-aqs-对资源的共享方式)
- - [6.2.3. AQS底层使用了模板方法模式](#623-aqs底层使用了模板方法模式)
- - [6.3. AQS 组件总结](#63-aqs-组件总结)
- - [7 Reference](#7-reference)
-
-
-
-# Java 并发进阶常见面试题总结
-
-## 1. synchronized 关键字
-
-### 1.1. 说一说自己对于 synchronized 关键字的了解
-
-synchronized关键字解决的是多个线程之间访问资源的同步性,synchronized关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。
-
-另外,在 Java 早期版本中,synchronized属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的 Mutex Lock 来实现的,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的 synchronized 效率低的原因。庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对synchronized 较大优化,所以现在的 synchronized 锁效率也优化得很不错了。JDK1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。
-
-
-### 1.2. 说说自己是怎么使用 synchronized 关键字,在项目中用到了吗
-
-**synchronized关键字最主要的三种使用方式:**
-
-- **修饰实例方法:** 作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁
-- **修饰静态方法:** 也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管new了多少个对象,只有一份)。所以如果一个线程A调用一个实例对象的非静态 synchronized 方法,而线程B需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,**因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁**。
-- **修饰代码块:** 指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
-
-**总结:** synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。synchronized 关键字加到实例方法上是给对象实例上锁。尽量不要使用 synchronized(String a) 因为JVM中,字符串常量池具有缓存功能!
-
-下面我以一个常见的面试题为例讲解一下 synchronized 关键字的具体使用。
-
-面试中面试官经常会说:“单例模式了解吗?来给我手写一下!给我解释一下双重检验锁方式实现单例模式的原理呗!”
-
-**双重校验锁实现对象单例(线程安全)**
-
-```java
-public class Singleton {
-
- private volatile static Singleton uniqueInstance;
-
- private Singleton() {
- }
-
- public static Singleton getUniqueInstance() {
- //先判断对象是否已经实例过,没有实例化过才进入加锁代码
- if (uniqueInstance == null) {
- //类对象加锁
- synchronized (Singleton.class) {
- if (uniqueInstance == null) {
- uniqueInstance = new Singleton();
- }
- }
- }
- return uniqueInstance;
- }
-}
-```
-另外,需要注意 uniqueInstance 采用 volatile 关键字修饰也是很有必要。
-
-uniqueInstance 采用 volatile 关键字修饰也是很有必要的, uniqueInstance = new Singleton(); 这段代码其实是分为三步执行:
-
-1. 为 uniqueInstance 分配内存空间
-2. 初始化 uniqueInstance
-3. 将 uniqueInstance 指向分配的内存地址
-
-但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getUniqueInstance() 后发现 uniqueInstance 不为空,因此返回 uniqueInstance,但此时 uniqueInstance 还未被初始化。
-
-使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。
-
-### 1.3. 讲一下 synchronized 关键字的底层原理
-
-**synchronized 关键字底层原理属于 JVM 层面。**
-
-**① synchronized 同步语句块的情况**
-
-```java
-public class SynchronizedDemo {
- public void method() {
- synchronized (this) {
- System.out.println("synchronized 代码块");
- }
- }
-}
-
-```
-
-通过 JDK 自带的 javap 命令查看 SynchronizedDemo 类的相关字节码信息:首先切换到类的对应目录执行 `javac SynchronizedDemo.java` 命令生成编译后的 .class 文件,然后执行`javap -c -s -v -l SynchronizedDemo.class`。
-
-
-
-从上面我们可以看出:
-
-**synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。** 当执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor(monitor对象存在于每个Java对象的对象头中,synchronized 锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因) 的持有权。当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1。相应的在执行 monitorexit 指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。
-
-**② synchronized 修饰方法的的情况**
-
-```java
-public class SynchronizedDemo2 {
- public synchronized void method() {
- System.out.println("synchronized 方法");
- }
-}
-
-```
-
-
-
-synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法,JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。
-
-
-### 1.4. 说说 JDK1.6 之后的synchronized 关键字底层做了哪些优化,可以详细介绍一下这些优化吗
-
-JDK1.6 对锁的实现引入了大量的优化,如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销。
-
-锁主要存在四种状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。
-
-关于这几种优化的详细信息可以查看笔主的这篇文章:
-
-### 1.5. 谈谈 synchronized和ReentrantLock 的区别
-
-
-**① 两者都是可重入锁**
-
-两者都是可重入锁。“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。
-
-**② synchronized 依赖于 JVM 而 ReentrantLock 依赖于 API**
-
-synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的,并没有直接暴露给我们。ReentrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock() 方法配合 try/finally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。
-
-**③ ReentrantLock 比 synchronized 增加了一些高级功能**
-
-相比synchronized,ReentrantLock增加了一些高级功能。主要来说主要有三点:**①等待可中断;②可实现公平锁;③可实现选择性通知(锁可以绑定多个条件)**
-
-- **ReentrantLock提供了一种能够中断等待锁的线程的机制**,通过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。
-- **ReentrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。** ReentrantLock默认情况是非公平的,可以通过 ReentrantLock类的`ReentrantLock(boolean fair)`构造方法来制定是否是公平的。
-- synchronized关键字与wait()和notify()/notifyAll()方法相结合可以实现等待/通知机制,ReentrantLock类当然也可以实现,但是需要借助于Condition接口与newCondition() 方法。Condition是JDK1.5之后才有的,它具有很好的灵活性,比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例(即对象监视器),**线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。 在使用notify()/notifyAll()方法进行通知时,被通知的线程是由 JVM 选择的,用ReentrantLock类结合Condition实例可以实现“选择性通知”** ,这个功能非常重要,而且是Condition接口默认提供的。而synchronized关键字就相当于整个Lock对象中只有一个Condition实例,所有的线程都注册在它一个身上。如果执行notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题,而Condition实例的signalAll()方法 只会唤醒注册在该Condition实例中的所有等待线程。
-
-如果你想使用上述功能,那么选择ReentrantLock是一个不错的选择。
-
-**④ 性能已不是选择标准**
-
-## 2. volatile关键字
-
-### 2.1. 讲一下Java内存模型
-
-
-在 JDK1.2 之前,Java的内存模型实现总是从**主存**(即共享内存)读取变量,是不需要进行特别的注意的。而在当前的 Java 内存模型下,线程可以把变量保存**本地内存**(比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成**数据的不一致**。
-
-
-
-要解决这个问题,就需要把变量声明为**volatile**,这就指示 JVM,这个变量是不稳定的,每次使用它都到主存中进行读取。
-
-说白了, **volatile** 关键字的主要作用就是保证变量的可见性然后还有一个作用是防止指令重排序。
-
-
-
-
-### 2.2. 说说 synchronized 关键字和 volatile 关键字的区别
-
- synchronized关键字和volatile关键字比较
-
-- **volatile关键字**是线程同步的**轻量级实现**,所以**volatile性能肯定比synchronized关键字要好**。但是**volatile关键字只能用于变量而synchronized关键字可以修饰方法以及代码块**。synchronized关键字在JavaSE1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁以及其它各种优化之后执行效率有了显著提升,**实际开发中使用 synchronized 关键字的场景还是更多一些**。
-- **多线程访问volatile关键字不会发生阻塞,而synchronized关键字可能会发生阻塞**
-- **volatile关键字能保证数据的可见性,但不能保证数据的原子性。synchronized关键字两者都能保证。**
-- **volatile关键字主要用于解决变量在多个线程之间的可见性,而 synchronized关键字解决的是多个线程之间访问资源的同步性。**
-
-## 3. ThreadLocal
-
-### 3.1. ThreadLocal简介
-
-通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。**如果想实现每一个线程都有自己的专属本地变量该如何解决呢?** JDK中提供的`ThreadLocal`类正是为了解决这样的问题。 **`ThreadLocal`类主要解决的就是让每个线程绑定自己的值,可以将`ThreadLocal`类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。**
-
-**如果你创建了一个`ThreadLocal`变量,那么访问这个变量的每个线程都会有这个变量的本地副本,这也是`ThreadLocal`变量名的由来。他们可以使用 `get()` 和 `set()` 方法来获取默认值或将其值更改为当前线程所存的副本的值,从而避免了线程安全问题。**
-
-再举个简单的例子:
-
-比如有两个人去宝屋收集宝物,这两个共用一个袋子的话肯定会产生争执,但是给他们两个人每个人分配一个袋子的话就不会出现这样的问题。如果把这两个人比作线程的话,那么ThreadLocal就是用来避免这两个线程竞争的。
-
-### 3.2. ThreadLocal示例
-
-相信看了上面的解释,大家已经搞懂 ThreadLocal 类是个什么东西了。
-
-```java
-import java.text.SimpleDateFormat;
-import java.util.Random;
-
-public class ThreadLocalExample implements Runnable{
-
- // SimpleDateFormat 不是线程安全的,所以每个线程都要有自己独立的副本
- private static final ThreadLocal formatter = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMdd HHmm"));
-
- public static void main(String[] args) throws InterruptedException {
- ThreadLocalExample obj = new ThreadLocalExample();
- for(int i=0 ; i<10; i++){
- Thread t = new Thread(obj, ""+i);
- Thread.sleep(new Random().nextInt(1000));
- t.start();
- }
- }
-
- @Override
- public void run() {
- System.out.println("Thread Name= "+Thread.currentThread().getName()+" default Formatter = "+formatter.get().toPattern());
- try {
- Thread.sleep(new Random().nextInt(1000));
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- //formatter pattern is changed here by thread, but it won't reflect to other threads
- formatter.set(new SimpleDateFormat());
-
- System.out.println("Thread Name= "+Thread.currentThread().getName()+" formatter = "+formatter.get().toPattern());
- }
-
-}
-
-```
-
-Output:
-
-```
-Thread Name= 0 default Formatter = yyyyMMdd HHmm
-Thread Name= 0 formatter = yy-M-d ah:mm
-Thread Name= 1 default Formatter = yyyyMMdd HHmm
-Thread Name= 2 default Formatter = yyyyMMdd HHmm
-Thread Name= 1 formatter = yy-M-d ah:mm
-Thread Name= 3 default Formatter = yyyyMMdd HHmm
-Thread Name= 2 formatter = yy-M-d ah:mm
-Thread Name= 4 default Formatter = yyyyMMdd HHmm
-Thread Name= 3 formatter = yy-M-d ah:mm
-Thread Name= 4 formatter = yy-M-d ah:mm
-Thread Name= 5 default Formatter = yyyyMMdd HHmm
-Thread Name= 5 formatter = yy-M-d ah:mm
-Thread Name= 6 default Formatter = yyyyMMdd HHmm
-Thread Name= 6 formatter = yy-M-d ah:mm
-Thread Name= 7 default Formatter = yyyyMMdd HHmm
-Thread Name= 7 formatter = yy-M-d ah:mm
-Thread Name= 8 default Formatter = yyyyMMdd HHmm
-Thread Name= 9 default Formatter = yyyyMMdd HHmm
-Thread Name= 8 formatter = yy-M-d ah:mm
-Thread Name= 9 formatter = yy-M-d ah:mm
-```
-
-从输出中可以看出,Thread-0已经改变了formatter的值,但仍然是thread-2默认格式化程序与初始化值相同,其他线程也一样。
-
-上面有一段代码用到了创建 `ThreadLocal` 变量的那段代码用到了 Java8 的知识,它等于下面这段代码,如果你写了下面这段代码的话,IDEA会提示你转换为Java8的格式(IDEA真的不错!)。因为ThreadLocal类在Java 8中扩展,使用一个新的方法`withInitial()`,将Supplier功能接口作为参数。
-
-```java
- private static final ThreadLocal formatter = new ThreadLocal(){
- @Override
- protected SimpleDateFormat initialValue()
- {
- return new SimpleDateFormat("yyyyMMdd HHmm");
- }
- };
-```
-
-### 3.3. ThreadLocal原理
-
-从 `Thread`类源代码入手。
-
-```java
-public class Thread implements Runnable {
- ......
-//与此线程有关的ThreadLocal值。由ThreadLocal类维护
-ThreadLocal.ThreadLocalMap threadLocals = null;
-
-//与此线程有关的InheritableThreadLocal值。由InheritableThreadLocal类维护
-ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
- ......
-}
-```
-
-从上面`Thread`类 源代码可以看出`Thread` 类中有一个 `threadLocals` 和 一个 `inheritableThreadLocals` 变量,它们都是 `ThreadLocalMap` 类型的变量,我们可以把 `ThreadLocalMap` 理解为`ThreadLocal` 类实现的定制化的 `HashMap`。默认情况下这两个变量都是null,只有当前线程调用 `ThreadLocal` 类的 `set`或`get`方法时才创建它们,实际上调用这两个方法的时候,我们调用的是`ThreadLocalMap`类对应的 `get()`、`set() `方法。
-
-`ThreadLocal`类的`set()`方法
-
-```java
- public void set(T value) {
- Thread t = Thread.currentThread();
- ThreadLocalMap map = getMap(t);
- if (map != null)
- map.set(this, value);
- else
- createMap(t, value);
- }
- ThreadLocalMap getMap(Thread t) {
- return t.threadLocals;
- }
-```
-
-通过上面这些内容,我们足以通过猜测得出结论:**最终的变量是放在了当前线程的 `ThreadLocalMap` 中,并不是存在 `ThreadLocal` 上,`ThreadLocal` 可以理解为只是`ThreadLocalMap`的封装,传递了变量值。** `ThrealLocal` 类中可以通过`Thread.currentThread()`获取到当前线程对象后,直接通过`getMap(Thread t)`可以访问到该线程的`ThreadLocalMap`对象。
-
-**每个`Thread`中都具备一个`ThreadLocalMap`,而`ThreadLocalMap`可以存储以`ThreadLocal`为key的键值对。** 比如我们在同一个线程中声明了两个 `ThreadLocal` 对象的话,会使用 `Thread`内部都是使用仅有那个`ThreadLocalMap` 存放数据的,`ThreadLocalMap`的 key 就是 `ThreadLocal`对象,value 就是 `ThreadLocal` 对象调用`set`方法设置的值。`ThreadLocal` 是 map结构是为了让每个线程可以关联多个 `ThreadLocal`变量。这也就解释了 ThreadLocal 声明的变量为什么在每一个线程都有自己的专属本地变量。
-
-`ThreadLocalMap`是`ThreadLocal`的静态内部类。
-
-
-
-### 3.4. ThreadLocal 内存泄露问题
-
-`ThreadLocalMap` 中使用的 key 为 `ThreadLocal` 的弱引用,而 value 是强引用。所以,如果 `ThreadLocal` 没有被外部强引用的情况下,在垃圾回收的时候,key 会被清理掉,而 value 不会被清理掉。这样一来,`ThreadLocalMap` 中就会出现key为null的Entry。假如我们不做任何措施的话,value 永远无法被GC 回收,这个时候就可能会产生内存泄露。ThreadLocalMap实现中已经考虑了这种情况,在调用 `set()`、`get()`、`remove()` 方法的时候,会清理掉 key 为 null 的记录。使用完 `ThreadLocal`方法后 最好手动调用`remove()`方法
-
-```java
- static class Entry extends WeakReference> {
- /** The value associated with this ThreadLocal. */
- Object value;
-
- Entry(ThreadLocal> k, Object v) {
- super(k);
- value = v;
- }
- }
-```
-
-**弱引用介绍:**
-
-> 如果一个对象只具有弱引用,那就类似于**可有可无的生活用品**。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。
->
-> 弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
-
-## 4. 线程池
-
-### 4.1. 为什么要用线程池?
-
-> **池化技术相比大家已经屡见不鲜了,线程池、数据库连接池、Http 连接池等等都是对这个思想的应用。池化技术的思想主要是为了减少每次获取资源的消耗,提高对资源的利用率。**
-
-**线程池**提供了一种限制和管理资源(包括执行一个任务)。 每个**线程池**还维护一些基本统计信息,例如已完成任务的数量。
-
-这里借用《Java 并发编程的艺术》提到的来说一下**使用线程池的好处**:
-
-- **降低资源消耗**。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
-- **提高响应速度**。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
-- **提高线程的可管理性**。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
-
-### 4.2. 实现Runnable接口和Callable接口的区别
-
-`Runnable`自Java 1.0以来一直存在,但`Callable`仅在Java 1.5中引入,目的就是为了来处理`Runnable`不支持的用例。**`Runnable` 接口**不会返回结果或抛出检查异常,但是**`Callable` 接口**可以。所以,如果任务不需要返回结果或抛出异常推荐使用 **`Runnable` 接口**,这样代码看起来会更加简洁。
-
-工具类 `Executors` 可以实现 `Runnable` 对象和 `Callable` 对象之间的相互转换。(`Executors.callable(Runnable task`)或 `Executors.callable(Runnable task,Object resule)`)。
-
-`Runnable.java`
-
-```java
-@FunctionalInterface
-public interface Runnable {
- /**
- * 被线程执行,没有返回值也无法抛出异常
- */
- public abstract void run();
-}
-```
-
-`Callable.java`
-
-```java
-@FunctionalInterface
-public interface Callable {
- /**
- * 计算结果,或在无法这样做时抛出异常。
- * @return 计算得出的结果
- * @throws 如果无法计算结果,则抛出异常
- */
- V call() throws Exception;
-}
-```
-
-### 4.3. 执行execute()方法和submit()方法的区别是什么呢?
-
-1. **`execute()`方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;**
-2. **`submit()`方法用于提交需要返回值的任务。线程池会返回一个 `Future` 类型的对象,通过这个 `Future` 对象可以判断任务是否执行成功**,并且可以通过 `Future` 的 `get()`方法来获取返回值,`get()`方法会阻塞当前线程直到任务完成,而使用 `get(long timeout,TimeUnit unit)`方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。
-
-我们以**`AbstractExecutorService`**接口中的一个 `submit` 方法为例子来看看源代码:
-
-```java
- public Future> submit(Runnable task) {
- if (task == null) throw new NullPointerException();
- RunnableFuture ftask = newTaskFor(task, null);
- execute(ftask);
- return ftask;
- }
-```
-
-上面方法调用的 `newTaskFor` 方法返回了一个 `FutureTask` 对象。
-
-```java
- protected RunnableFuture newTaskFor(Runnable runnable, T value) {
- return new FutureTask(runnable, value);
- }
-```
-
-我们再来看看`execute()`方法:
-
-```java
- public void execute(Runnable command) {
- ...
- }
-```
-
-### 4.4. 如何创建线程池
-
-《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险
-
-> Executors 返回线程池对象的弊端如下:
->
-> - **FixedThreadPool 和 SingleThreadExecutor** : 允许请求的队列长度为 Integer.MAX_VALUE ,可能堆积大量的请求,从而导致OOM。
-> - **CachedThreadPool 和 ScheduledThreadPool** : 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致OOM。
-
-**方式一:通过构造方法实现**
-
-**方式二:通过Executor 框架的工具类Executors来实现**
-我们可以创建三种类型的ThreadPoolExecutor:
-
-- **FixedThreadPool** : 该方法返回一个固定线程数量的线程池。该线程池中的线程数量始终不变。当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。若没有,则新的任务会被暂存在一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。
-- **SingleThreadExecutor:** 方法返回一个只有一个线程的线程池。若多余一个任务被提交到该线程池,任务会被保存在一个任务队列中,待线程空闲,按先入先出的顺序执行队列中的任务。
-- **CachedThreadPool:** 该方法返回一个可根据实际情况调整线程数量的线程池。线程池的线程数量不确定,但若有空闲线程可以复用,则会优先使用可复用的线程。若所有线程均在工作,又有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复用。
-
-对应Executors工具类中的方法如图所示:
-
-
-### 4.5 ThreadPoolExecutor 类分析
-
-`ThreadPoolExecutor` 类中提供的四个构造方法。我们来看最长的那个,其余三个都是在这个构造方法的基础上产生(其他几个构造方法说白点都是给定某些默认参数的构造方法比如默认制定拒绝策略是什么),这里就不贴代码讲了,比较简单。
-
-```java
- /**
- * 用给定的初始参数创建一个新的ThreadPoolExecutor。
- */
- public ThreadPoolExecutor(int corePoolSize,
- int maximumPoolSize,
- long keepAliveTime,
- TimeUnit unit,
- BlockingQueue workQueue,
- ThreadFactory threadFactory,
- RejectedExecutionHandler handler) {
- if (corePoolSize < 0 ||
- maximumPoolSize <= 0 ||
- maximumPoolSize < corePoolSize ||
- keepAliveTime < 0)
- throw new IllegalArgumentException();
- if (workQueue == null || threadFactory == null || handler == null)
- throw new NullPointerException();
- this.corePoolSize = corePoolSize;
- this.maximumPoolSize = maximumPoolSize;
- this.workQueue = workQueue;
- this.keepAliveTime = unit.toNanos(keepAliveTime);
- this.threadFactory = threadFactory;
- this.handler = handler;
- }
-```
-
-**下面这些对创建 非常重要,在后面使用线程池的过程中你一定会用到!所以,务必拿着小本本记清楚。**
-
-#### 4.5.1 `ThreadPoolExecutor`构造函数重要参数分析
-
-**`ThreadPoolExecutor` 3 个最重要的参数:**
-
-- **`corePoolSize` :** 核心线程数线程数定义了最小可以同时运行的线程数量。
-- **`maximumPoolSize` :** 当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。
-- **`workQueue`:** 当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,信任就会被存放在队列中。
-
-`ThreadPoolExecutor`其他常见参数:
-
-1. **`keepAliveTime`**:当线程池中的线程数量大于 `corePoolSize` 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 `keepAliveTime`才会被回收销毁;
-2. **`unit`** : `keepAliveTime` 参数的时间单位。
-3. **`threadFactory`** :executor 创建新线程的时候会用到。
-4. **`handler`** :饱和策略。关于饱和策略下面单独介绍一下。
-
-#### 4.5.2 `ThreadPoolExecutor` 饱和策略
-
-**`ThreadPoolExecutor` 饱和策略定义:**
-
-如果当前同时运行的线程数量达到最大线程数量并且队列也已经被放满了任时,`ThreadPoolTaskExecutor` 定义一些策略:
-
-- **`ThreadPoolExecutor.AbortPolicy`**:抛出 `RejectedExecutionException`来拒绝新任务的处理。
-- **`ThreadPoolExecutor.CallerRunsPolicy`**:调用执行自己的线程运行任务。您不会任务请求。但是这种策略会降低对于新任务提交速度,影响程序的整体性能。另外,这个策略喜欢增加队列容量。如果您的应用程序可以承受此延迟并且你不能任务丢弃任何一个任务请求的话,你可以选择这个策略。
-- **`ThreadPoolExecutor.DiscardPolicy`:** 不处理新任务,直接丢弃掉。
-- **`ThreadPoolExecutor.DiscardOldestPolicy`:** 此策略将丢弃最早的未处理的任务请求。
-
-举个例子: Spring 通过 `ThreadPoolTaskExecutor` 或者我们直接通过 `ThreadPoolExecutor` 的构造函数创建线程池的时候,当我们不指定 `RejectedExecutionHandler` 饱和策略的话来配置线程池的时候默认使用的是 `ThreadPoolExecutor.AbortPolicy`。在默认情况下,`ThreadPoolExecutor` 将抛出 `RejectedExecutionException` 来拒绝新来的任务 ,这代表你将丢失对这个任务的处理。 对于可伸缩的应用程序,建议使用 `ThreadPoolExecutor.CallerRunsPolicy`。当最大池被填满时,此策略为我们提供可伸缩队列。(这个直接查看 `ThreadPoolExecutor` 的构造函数源码就可以看出,比较简单的原因,这里就不贴代码了)
-
-### 4.6 一个简单的线程池Demo:`Runnable`+`ThreadPoolExecutor`
-
-为了让大家更清楚上面的面试题中的一些概念,我写了一个简单的线程池 Demo。
-
-首先创建一个 `Runnable` 接口的实现类(当然也可以是 `Callable` 接口,我们上面也说了两者的区别。)
-
-`MyRunnable.java`
-
-```java
-import java.util.Date;
-
-/**
- * 这是一个简单的Runnable类,需要大约5秒钟来执行其任务。
- * @author shuang.kou
- */
-public class MyRunnable implements Runnable {
-
- private String command;
-
- public MyRunnable(String s) {
- this.command = s;
- }
-
- @Override
- public void run() {
- System.out.println(Thread.currentThread().getName() + " Start. Time = " + new Date());
- processCommand();
- System.out.println(Thread.currentThread().getName() + " End. Time = " + new Date());
- }
-
- private void processCommand() {
- try {
- Thread.sleep(5000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
-
- @Override
- public String toString() {
- return this.command;
- }
-}
-
-```
-
-编写测试程序,我们这里以阿里巴巴推荐的使用 `ThreadPoolExecutor` 构造函数自定义参数的方式来创建线程池。
-
-`ThreadPoolExecutorDemo.java`
-
-```java
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-
-public class ThreadPoolExecutorDemo {
-
- private static final int CORE_POOL_SIZE = 5;
- private static final int MAX_POOL_SIZE = 10;
- private static final int QUEUE_CAPACITY = 100;
- private static final Long KEEP_ALIVE_TIME = 1L;
- public static void main(String[] args) {
-
- //使用阿里巴巴推荐的创建线程池的方式
- //通过ThreadPoolExecutor构造函数自定义参数创建
- ThreadPoolExecutor executor = new ThreadPoolExecutor(
- CORE_POOL_SIZE,
- MAX_POOL_SIZE,
- KEEP_ALIVE_TIME,
- TimeUnit.SECONDS,
- new ArrayBlockingQueue<>(QUEUE_CAPACITY),
- new ThreadPoolExecutor.CallerRunsPolicy());
-
- for (int i = 0; i < 10; i++) {
- //创建WorkerThread对象(WorkerThread类实现了Runnable 接口)
- Runnable worker = new MyRunnable("" + i);
- //执行Runnable
- executor.execute(worker);
- }
- //终止线程池
- executor.shutdown();
- while (!executor.isTerminated()) {
- }
- System.out.println("Finished all threads");
- }
-}
-
-```
-
-可以看到我们上面的代码指定了:
-
-1. `corePoolSize`: 核心线程数为 5。
-2. `maximumPoolSize` :最大线程数 10
-3. `keepAliveTime` : 等待时间为 1L。
-4. `unit`: 等待时间的单位为 TimeUnit.SECONDS。
-5. `workQueue`:任务队列为 `ArrayBlockingQueue`,并且容量为 100;
-6. `handler`:饱和策略为 `CallerRunsPolicy`。
-
-**Output:**
-
-```
-pool-1-thread-2 Start. Time = Tue Nov 12 20:59:44 CST 2019
-pool-1-thread-5 Start. Time = Tue Nov 12 20:59:44 CST 2019
-pool-1-thread-4 Start. Time = Tue Nov 12 20:59:44 CST 2019
-pool-1-thread-1 Start. Time = Tue Nov 12 20:59:44 CST 2019
-pool-1-thread-3 Start. Time = Tue Nov 12 20:59:44 CST 2019
-pool-1-thread-5 End. Time = Tue Nov 12 20:59:49 CST 2019
-pool-1-thread-3 End. Time = Tue Nov 12 20:59:49 CST 2019
-pool-1-thread-2 End. Time = Tue Nov 12 20:59:49 CST 2019
-pool-1-thread-4 End. Time = Tue Nov 12 20:59:49 CST 2019
-pool-1-thread-1 End. Time = Tue Nov 12 20:59:49 CST 2019
-pool-1-thread-2 Start. Time = Tue Nov 12 20:59:49 CST 2019
-pool-1-thread-1 Start. Time = Tue Nov 12 20:59:49 CST 2019
-pool-1-thread-4 Start. Time = Tue Nov 12 20:59:49 CST 2019
-pool-1-thread-3 Start. Time = Tue Nov 12 20:59:49 CST 2019
-pool-1-thread-5 Start. Time = Tue Nov 12 20:59:49 CST 2019
-pool-1-thread-2 End. Time = Tue Nov 12 20:59:54 CST 2019
-pool-1-thread-3 End. Time = Tue Nov 12 20:59:54 CST 2019
-pool-1-thread-4 End. Time = Tue Nov 12 20:59:54 CST 2019
-pool-1-thread-5 End. Time = Tue Nov 12 20:59:54 CST 2019
-pool-1-thread-1 End. Time = Tue Nov 12 20:59:54 CST 2019
-
-```
-
-### 4.7 线程池原理分析
-
-承接 4.6 节,我们通过代码输出结果可以看出:**线程池每次会同时执行 5 个任务,这 5 个任务执行完之后,剩余的 5 个任务才会被执行。** 大家可以先通过上面讲解的内容,分析一下到底是咋回事?(自己独立思考一会)
-
-现在,我们就分析上面的输出内容来简单分析一下线程池原理。
-
-**为了搞懂线程池的原理,我们需要首先分析一下 `execute`方法。**在 4.6 节中的 Demo 中我们使用 `executor.execute(worker)`来提交一个任务到线程池中去,这个方法非常重要,下面我们来看看它的源码:
-
-```java
- // 存放线程池的运行状态 (runState) 和线程池内有效线程的数量 (workerCount)
- private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
-
- private static int workerCountOf(int c) {
- return c & CAPACITY;
- }
-
- private final BlockingQueue workQueue;
-
- public void execute(Runnable command) {
- // 如果任务为null,则抛出异常。
- if (command == null)
- throw new NullPointerException();
- // ctl 中保存的线程池当前的一些状态信息
- int c = ctl.get();
-
- // 下面会涉及到 3 步 操作
- // 1.首先判断当前线程池中之行的任务数量是否小于 corePoolSize
- // 如果小于的话,通过addWorker(command, true)新建一个线程,并将任务(command)添加到该线程中;然后,启动该线程从而执行任务。
- if (workerCountOf(c) < corePoolSize) {
- if (addWorker(command, true))
- return;
- c = ctl.get();
- }
- // 2.如果当前之行的任务数量大于等于 corePoolSize 的时候就会走到这里
- // 通过 isRunning 方法判断线程池状态,线程池处于 RUNNING 状态才会被并且队列可以加入任务,该任务才会被加入进去
- if (isRunning(c) && workQueue.offer(command)) {
- int recheck = ctl.get();
- // 再次获取线程池状态,如果线程池状态不是 RUNNING 状态就需要从任务队列中移除任务,并尝试判断线程是否全部执行完毕。同时执行拒绝策略。
- if (!isRunning(recheck) && remove(command))
- reject(command);
- // 如果当前线程池为空就新创建一个线程并执行。
- else if (workerCountOf(recheck) == 0)
- addWorker(null, false);
- }
- //3. 通过addWorker(command, false)新建一个线程,并将任务(command)添加到该线程中;然后,启动该线程从而执行任务。
- //如果addWorker(command, false)执行失败,则通过reject()执行相应的拒绝策略的内容。
- else if (!addWorker(command, false))
- reject(command);
- }
-```
-
-通过下图可以更好的对上面这 3 步做一个展示,下图是我为了省事直接从网上找到,原地址不明。
-
-
-
-现在,让我们在回到 4.6 节我们写的 Demo, 现在应该是不是很容易就可以搞懂它的原理了呢?
-
-没搞懂的话,也没关系,可以看看我的分析:
-
-> 我们在代码中模拟了 10 个任务,我们配置的核心线程数为 5 、等待队列容量为 100 ,所以每次只可能存在 5 个任务同时执行,剩下的 5 个任务会被放到等待队列中去。当前的 5 个任务之行完成后,才会之行剩下的 5 个任务。
-
-## 5. Atomic 原子类
-
-### 5.1. 介绍一下Atomic 原子类
-
-Atomic 翻译成中文是原子的意思。在化学上,我们知道原子是构成一般物质的最小单位,在化学反应中是不可分割的。在我们这里 Atomic 是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。
-
-所以,所谓原子类说简单点就是具有原子/原子操作特征的类。
-
-
-并发包 `java.util.concurrent` 的原子类都存放在`java.util.concurrent.atomic`下,如下图所示。
-
-
-
-### 5.2. JUC 包中的原子类是哪4类?
-
-**基本类型**
-
-使用原子的方式更新基本类型
-
-- AtomicInteger:整形原子类
-- AtomicLong:长整型原子类
-- AtomicBoolean:布尔型原子类
-
-**数组类型**
-
-使用原子的方式更新数组里的某个元素
-
-
-- AtomicIntegerArray:整形数组原子类
-- AtomicLongArray:长整形数组原子类
-- AtomicReferenceArray:引用类型数组原子类
-
-**引用类型**
-
-- AtomicReference:引用类型原子类
-- AtomicStampedReference:原子更新引用类型里的字段原子类
-- AtomicMarkableReference :原子更新带有标记位的引用类型
-
-**对象的属性修改类型**
-
-- AtomicIntegerFieldUpdater:原子更新整形字段的更新器
-- AtomicLongFieldUpdater:原子更新长整形字段的更新器
-- AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。
-
-
-### 5.3. 讲讲 AtomicInteger 的使用
-
- **AtomicInteger 类常用方法**
-
-```java
-public final int get() //获取当前的值
-public final int getAndSet(int newValue)//获取当前的值,并设置新的值
-public final int getAndIncrement()//获取当前的值,并自增
-public final int getAndDecrement() //获取当前的值,并自减
-public final int getAndAdd(int delta) //获取当前的值,并加上预期的值
-boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将该值设置为输入值(update)
-public final void lazySet(int newValue)//最终设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
-```
-
- **AtomicInteger 类的使用示例**
-
-使用 AtomicInteger 之后,不用对 increment() 方法加锁也可以保证线程安全。
-```java
-class AtomicIntegerTest {
- private AtomicInteger count = new AtomicInteger();
- //使用AtomicInteger之后,不需要对该方法加锁,也可以实现线程安全。
- public void increment() {
- count.incrementAndGet();
- }
-
- public int getCount() {
- return count.get();
- }
-}
-
-```
-
-### 5.4. 能不能给我简单介绍一下 AtomicInteger 类的原理
-
-AtomicInteger 线程安全原理简单分析
-
-AtomicInteger 类的部分源码:
-
-```java
- // setup to use Unsafe.compareAndSwapInt for updates(更新操作时提供“比较并替换”的作用)
- private static final Unsafe unsafe = Unsafe.getUnsafe();
- private static final long valueOffset;
-
- static {
- try {
- valueOffset = unsafe.objectFieldOffset
- (AtomicInteger.class.getDeclaredField("value"));
- } catch (Exception ex) { throw new Error(ex); }
- }
-
- private volatile int value;
-```
-
-AtomicInteger 类主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。
-
-CAS的原理是拿期望的值和原本的一个值作比较,如果相同则更新成新的值。UnSafe 类的 objectFieldOffset() 方法是一个本地方法,这个方法是用来拿到“原来的值”的内存地址,返回值是 valueOffset。另外 value 是一个volatile变量,在内存中可见,因此 JVM 可以保证任何时刻任何线程总能拿到该变量的最新值。
-
-关于 Atomic 原子类这部分更多内容可以查看我的这篇文章:并发编程面试必备:[JUC 中的 Atomic 原子类总结](https://mp.weixin.qq.com/s/joa-yOiTrYF67bElj8xqvg)
-
-## 6. AQS
-
-### 6.1. AQS 介绍
-
-AQS的全称为(AbstractQueuedSynchronizer),这个类在java.util.concurrent.locks包下面。
-
-
-
-AQS是一个用来构建锁和同步器的框架,使用AQS能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的ReentrantLock,Semaphore,其他的诸如ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基于AQS的。当然,我们自己也能利用AQS非常轻松容易地构造出符合我们自己需求的同步器。
-
-### 6.2. AQS 原理分析
-
-AQS 原理这部分参考了部分博客,在5.2节末尾放了链接。
-
-> 在面试中被问到并发知识的时候,大多都会被问到“请你说一下自己对于AQS原理的理解”。下面给大家一个示例供大家参加,面试不是背题,大家一定要加入自己的思想,即使加入不了自己的思想也要保证自己能够通俗的讲出来而不是背出来。
-
-下面大部分内容其实在AQS类注释上已经给出了,不过是英语看着比较吃力一点,感兴趣的话可以看看源码。
-
-#### 6.2.1. AQS 原理概览
-
-**AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。**
-
-> CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。
-
-看个AQS(AbstractQueuedSynchronizer)原理图:
-
-
-
-
-AQS使用一个int成员变量来表示同步状态,通过内置的FIFO队列来完成获取资源线程的排队工作。AQS使用CAS对该同步状态进行原子操作实现对其值的修改。
-
-```java
-private volatile int state;//共享变量,使用volatile修饰保证线程可见性
-```
-
-状态信息通过protected类型的getState,setState,compareAndSetState进行操作
-
-```java
-
-//返回同步状态的当前值
-protected final int getState() {
- return state;
-}
- // 设置同步状态的值
-protected final void setState(int newState) {
- state = newState;
-}
-//原子地(CAS操作)将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值)
-protected final boolean compareAndSetState(int expect, int update) {
- return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
-}
-```
-
-#### 6.2.2. AQS 对资源的共享方式
-
-**AQS定义两种资源共享方式**
-
-- **Exclusive**(独占):只有一个线程能执行,如ReentrantLock。又可分为公平锁和非公平锁:
- - 公平锁:按照线程在队列中的排队顺序,先到者先拿到锁
- - 非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的
-- **Share**(共享):多个线程可同时执行,如Semaphore/CountDownLatch。Semaphore、CountDownLatch、 CyclicBarrier、ReadWriteLock 我们都会在后面讲到。
-
-ReentrantReadWriteLock 可以看成是组合式,因为ReentrantReadWriteLock也就是读写锁允许多个线程同时对某一资源进行读。
-
-不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源 state 的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。
-
-#### 6.2.3. AQS底层使用了模板方法模式
-
-同步器的设计是基于模板方法模式的,如果需要自定义同步器一般的方式是这样(模板方法模式很经典的一个应用):
-
-1. 使用者继承AbstractQueuedSynchronizer并重写指定的方法。(这些重写方法很简单,无非是对于共享资源state的获取和释放)
-2. 将AQS组合在自定义同步组件的实现中,并调用其模板方法,而这些模板方法会调用使用者重写的方法。
-
-这和我们以往通过实现接口的方式有很大区别,这是模板方法模式很经典的一个运用。
-
-**AQS使用了模板方法模式,自定义同步器时需要重写下面几个AQS提供的模板方法:**
-
-```java
-isHeldExclusively()//该线程是否正在独占资源。只有用到condition才需要去实现它。
-tryAcquire(int)//独占方式。尝试获取资源,成功则返回true,失败则返回false。
-tryRelease(int)//独占方式。尝试释放资源,成功则返回true,失败则返回false。
-tryAcquireShared(int)//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
-tryReleaseShared(int)//共享方式。尝试释放资源,成功则返回true,失败则返回false。
-
-```
-
-默认情况下,每个方法都抛出 `UnsupportedOperationException`。 这些方法的实现必须是内部线程安全的,并且通常应该简短而不是阻塞。AQS类中的其他方法都是final ,所以无法被其他类使用,只有这几个方法可以被其他类使用。
-
-以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。
-
-再以CountDownLatch以例,任务分为N个子线程去执行,state也初始化为N(注意N要与线程个数一致)。这N个子线程是并行执行的,每个子线程执行完后countDown()一次,state会CAS(Compare and Swap)减1。等到所有子线程都执行完后(即state=0),会unpark()主调用线程,然后主调用线程就会从await()函数返回,继续后余动作。
-
-一般来说,自定义同步器要么是独占方法,要么是共享方式,他们也只需实现`tryAcquire-tryRelease`、`tryAcquireShared-tryReleaseShared`中的一种即可。但AQS也支持自定义同步器同时实现独占和共享两种方式,如`ReentrantReadWriteLock`。
-
-推荐两篇 AQS 原理和相关源码分析的文章:
-
-- http://www.cnblogs.com/waterystone/p/4920797.html
-- https://www.cnblogs.com/chengxiao/archive/2017/07/24/7141160.html
-
-### 6.3. AQS 组件总结
-
-- **Semaphore(信号量)-允许多个线程同时访问:** synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源,Semaphore(信号量)可以指定多个线程同时访问某个资源。
-- **CountDownLatch (倒计时器):** CountDownLatch是一个同步工具类,用来协调多个线程之间的同步。这个工具通常用来控制线程等待,它可以让某一个线程等待直到倒计时结束,再开始执行。
-- **CyclicBarrier(循环栅栏):** CyclicBarrier 和 CountDownLatch 非常类似,它也可以实现线程间的技术等待,但是它的功能比 CountDownLatch 更加复杂和强大。主要应用场景和 CountDownLatch 类似。CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。CyclicBarrier默认的构造方法是 CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await()方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。
-
-## 7 Reference
-
-- 《深入理解 Java 虚拟机》
-- 《实战 Java 高并发程序设计》
-- 《Java并发编程的艺术》
-- http://www.cnblogs.com/waterystone/p/4920797.html
-- https://www.cnblogs.com/chengxiao/archive/2017/07/24/7141160.html
--
-
-## 公众号
-
-如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
-
-**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"面试突击"** 即可免费领取!
-
-**Java工程师必备学习资源:** 一些Java工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。
-
-
diff --git a/docs/java/Multithread/ThredLocal.md b/docs/java/Multithread/ThredLocal.md
deleted file mode 100644
index 84d619df728..00000000000
--- a/docs/java/Multithread/ThredLocal.md
+++ /dev/null
@@ -1,170 +0,0 @@
-[ThreadLocal造成OOM内存溢出案例演示与原理分析](https://blog.csdn.net/xlgen157387/article/details/78298840)
-
-[深入理解 Java 之 ThreadLocal 工作原理]()
-
-## ThreadLocal
-
-### ThreadLocal简介
-
-通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。**如果想实现每一个线程都有自己的专属本地变量该如何解决呢?** JDK中提供的`ThreadLocal`类正是为了解决这样的问题。 **`ThreadLocal`类主要解决的就是让每个线程绑定自己的值,可以将`ThreadLocal`类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。**
-
-**如果你创建了一个`ThreadLocal`变量,那么访问这个变量的每个线程都会有这个变量的本地副本,这也是`ThreadLocal`变量名的由来。他们可以使用 `get()` 和 `set()` 方法来获取默认值或将其值更改为当前线程所存的副本的值,从而避免了线程安全问题。**
-
-再举个简单的例子:
-
-比如有两个人去宝屋收集宝物,这两个共用一个袋子的话肯定会产生争执,但是给他们两个人每个人分配一个袋子的话就不会出现这样的问题。如果把这两个人比作线程的话,那么ThreadLocal就是用来这两个线程竞争的。
-
-### ThreadLocal示例
-
-相信看了上面的解释,大家已经搞懂 ThreadLocal 类是个什么东西了。
-
-```java
-import java.text.SimpleDateFormat;
-import java.util.Random;
-
-public class ThreadLocalExample implements Runnable{
-
- // SimpleDateFormat 不是线程安全的,所以每个线程都要有自己独立的副本
- private static final ThreadLocal formatter = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMdd HHmm"));
-
- public static void main(String[] args) throws InterruptedException {
- ThreadLocalExample obj = new ThreadLocalExample();
- for(int i=0 ; i<10; i++){
- Thread t = new Thread(obj, ""+i);
- Thread.sleep(new Random().nextInt(1000));
- t.start();
- }
- }
-
- @Override
- public void run() {
- System.out.println("Thread Name= "+Thread.currentThread().getName()+" default Formatter = "+formatter.get().toPattern());
- try {
- Thread.sleep(new Random().nextInt(1000));
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- //formatter pattern is changed here by thread, but it won't reflect to other threads
- formatter.set(new SimpleDateFormat());
-
- System.out.println("Thread Name= "+Thread.currentThread().getName()+" formatter = "+formatter.get().toPattern());
- }
-
-}
-
-```
-
-Output:
-
-```
-Thread Name= 0 default Formatter = yyyyMMdd HHmm
-Thread Name= 0 formatter = yy-M-d ah:mm
-Thread Name= 1 default Formatter = yyyyMMdd HHmm
-Thread Name= 2 default Formatter = yyyyMMdd HHmm
-Thread Name= 1 formatter = yy-M-d ah:mm
-Thread Name= 3 default Formatter = yyyyMMdd HHmm
-Thread Name= 2 formatter = yy-M-d ah:mm
-Thread Name= 4 default Formatter = yyyyMMdd HHmm
-Thread Name= 3 formatter = yy-M-d ah:mm
-Thread Name= 4 formatter = yy-M-d ah:mm
-Thread Name= 5 default Formatter = yyyyMMdd HHmm
-Thread Name= 5 formatter = yy-M-d ah:mm
-Thread Name= 6 default Formatter = yyyyMMdd HHmm
-Thread Name= 6 formatter = yy-M-d ah:mm
-Thread Name= 7 default Formatter = yyyyMMdd HHmm
-Thread Name= 7 formatter = yy-M-d ah:mm
-Thread Name= 8 default Formatter = yyyyMMdd HHmm
-Thread Name= 9 default Formatter = yyyyMMdd HHmm
-Thread Name= 8 formatter = yy-M-d ah:mm
-Thread Name= 9 formatter = yy-M-d ah:mm
-```
-
-从输出中可以看出,Thread-0已经改变了formatter的值,但仍然是thread-2默认格式化程序与初始化值相同,其他线程也一样。
-
-上面有一段代码用到了创建 `ThreadLocal` 变量的那段代码用到了 Java8 的知识,它等于下面这段代码,如果你写了下面这段代码的话,IDEA会提示你转换为Java8的格式(IDEA真的不错!)。因为ThreadLocal类在Java 8中扩展,使用一个新的方法`withInitial()`,将Supplier功能接口作为参数。
-
-```java
- private static final ThreadLocal formatter = new ThreadLocal(){
- @Override
- protected SimpleDateFormat initialValue()
- {
- return new SimpleDateFormat("yyyyMMdd HHmm");
- }
- };
-```
-
-### ThreadLocal原理
-
-从 `Thread`类源代码入手。
-
-```java
-public class Thread implements Runnable {
- ......
-//与此线程有关的ThreadLocal值。由ThreadLocal类维护
-ThreadLocal.ThreadLocalMap threadLocals = null;
-
-//与此线程有关的InheritableThreadLocal值。由InheritableThreadLocal类维护
-ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
- ......
-}
-```
-
-从上面`Thread`类 源代码可以看出`Thread` 类中有一个 `threadLocals` 和 一个 `inheritableThreadLocals` 变量,它们都是 `ThreadLocalMap` 类型的变量,我们可以把 `ThreadLocalMap` 理解为`ThreadLocal` 类实现的定制化的 `HashMap`。默认情况下这两个变量都是null,只有当前线程调用 `ThreadLocal` 类的 `set`或`get`方法时才创建它们,实际上调用这两个方法的时候,我们调用的是`ThreadLocalMap`类对应的 `get()`、`set() `方法。
-
-`ThreadLocal`类的`set()`方法
-
-```java
- public void set(T value) {
- Thread t = Thread.currentThread();
- ThreadLocalMap map = getMap(t);
- if (map != null)
- map.set(this, value);
- else
- createMap(t, value);
- }
- ThreadLocalMap getMap(Thread t) {
- return t.threadLocals;
- }
-```
-
-通过上面这些内容,我们足以通过猜测得出结论:**最终的变量是放在了当前线程的 `ThreadLocalMap` 中,并不是存在 `ThreadLocal` 上,ThreadLocal 可以理解为只是ThreadLocalMap的封装,传递了变量值。**
-
-**每个Thread中都具备一个ThreadLocalMap,而ThreadLocalMap可以存储以ThreadLocal为key的键值对。** 比如我们在同一个线程中声明了两个 `ThreadLocal` 对象的话,会使用 `Thread`内部都是使用仅有那个`ThreadLocalMap` 存放数据的,`ThreadLocalMap`的 key 就是 `ThreadLocal`对象,value 就是 `ThreadLocal` 对象调用`set`方法设置的值。`ThreadLocal` 是 map结构是为了让每个线程可以关联多个 `ThreadLocal`变量。这也就解释了ThreadLocal声明的变量为什么在每一个线程都有自己的专属本地变量。
-
-```java
-public class Thread implements Runnable {
- ......
-//与此线程有关的ThreadLocal值。由ThreadLocal类维护
-ThreadLocal.ThreadLocalMap threadLocals = null;
-
-//与此线程有关的InheritableThreadLocal值。由InheritableThreadLocal类维护
-ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
- ......
-}
-```
-
-`ThreadLocalMap`是`ThreadLocal`的静态内部类。
-
-
-
-### ThreadLocal 内存泄露问题
-
-`ThreadLocalMap` 中使用的 key 为 `ThreadLocal` 的弱引用,而 value 是强引用。所以,如果 `ThreadLocal` 没有被外部强引用的情况下,在垃圾回收的时候会 key 会被清理掉,而 value 不会被清理掉。这样一来,`ThreadLocalMap` 中就会出现key为null的Entry。假如我们不做任何措施的话,value 永远无法被GC 回收,这个时候就可能会产生内存泄露。ThreadLocalMap实现中已经考虑了这种情况,在调用 `set()`、`get()`、`remove()` 方法的时候,会清理掉 key 为 null 的记录。使用完 `ThreadLocal`方法后 最好手动调用`remove()`方法
-
-```java
- static class Entry extends WeakReference> {
- /** The value associated with this ThreadLocal. */
- Object value;
-
- Entry(ThreadLocal> k, Object v) {
- super(k);
- value = v;
- }
- }
-```
-
-**弱引用介绍:**
-
-> 如果一个对象只具有弱引用,那就类似于**可有可无的生活用品**。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。
->
-> 弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
\ No newline at end of file
diff --git a/docs/java/Multithread/synchronized.md b/docs/java/Multithread/synchronized.md
deleted file mode 100644
index 0a1f4f2b073..00000000000
--- a/docs/java/Multithread/synchronized.md
+++ /dev/null
@@ -1,169 +0,0 @@
-
-
-
-
-### synchronized关键字最主要的三种使用方式的总结
-
-- **修饰实例方法,作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁**
-- **修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁** 。也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管new了多少个对象,只有一份,所以对该类的所有对象都加了锁)。所以如果一个线程A调用一个实例对象的非静态 synchronized 方法,而线程B需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,**因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁**。
-- **修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。** 和 synchronized 方法一样,synchronized(this)代码块也是锁定当前对象的。synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。这里再提一下:synchronized关键字加到非 static 静态方法上是给对象实例上锁。另外需要注意的是:尽量不要使用 synchronized(String a) 因为JVM中,字符串常量池具有缓冲功能!
-
-下面我已一个常见的面试题为例讲解一下 synchronized 关键字的具体使用。
-
-面试中面试官经常会说:“单例模式了解吗?来给我手写一下!给我解释一下双重检验锁方式实现单例模式的原理呗!”
-
-
-
-**双重校验锁实现对象单例(线程安全)**
-
-```java
-public class Singleton {
-
- private volatile static Singleton uniqueInstance;
-
- private Singleton() {
- }
-
- public static Singleton getUniqueInstance() {
- //先判断对象是否已经实例过,没有实例化过才进入加锁代码
- if (uniqueInstance == null) {
- //类对象加锁
- synchronized (Singleton.class) {
- if (uniqueInstance == null) {
- uniqueInstance = new Singleton();
- }
- }
- }
- return uniqueInstance;
- }
-}
-```
-另外,需要注意 uniqueInstance 采用 volatile 关键字修饰也是很有必要。
-
-uniqueInstance 采用 volatile 关键字修饰也是很有必要的, uniqueInstance = new Singleton(); 这段代码其实是分为三步执行:
-
-1. 为 uniqueInstance 分配内存空间
-2. 初始化 uniqueInstance
-3. 将 uniqueInstance 指向分配的内存地址
-
-但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getUniqueInstance() 后发现 uniqueInstance 不为空,因此返回 uniqueInstance,但此时 uniqueInstance 还未被初始化。
-
-使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。
-
-
-###synchronized 关键字底层原理总结
-
-
-
-**synchronized 关键字底层原理属于 JVM 层面。**
-
-**① synchronized 同步语句块的情况**
-
-```java
-public class SynchronizedDemo {
- public void method() {
- synchronized (this) {
- System.out.println("synchronized 代码块");
- }
- }
-}
-
-```
-
-通过 JDK 自带的 javap 命令查看 SynchronizedDemo 类的相关字节码信息:首先切换到类的对应目录执行 `javac SynchronizedDemo.java` 命令生成编译后的 .class 文件,然后执行`javap -c -s -v -l SynchronizedDemo.class`。
-
-
-
-从上面我们可以看出:
-
-**synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。** 当执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor(monitor对象存在于每个Java对象的对象头中,synchronized 锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因) 的持有权.当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1。相应的在执行 monitorexit 指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。
-
-**② synchronized 修饰方法的的情况**
-
-```java
-public class SynchronizedDemo2 {
- public synchronized void method() {
- System.out.println("synchronized 方法");
- }
-}
-
-```
-
-
-
-synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法,JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。
-
-
-在 Java 早期版本中,synchronized 属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的 Mutex Lock 来实现的,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的 synchronized 效率低的原因。庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对synchronized 较大优化,所以现在的 synchronized 锁效率也优化得很不错了。JDK1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。
-
-
-### JDK1.6 之后的底层优化
-
-JDK1.6 对锁的实现引入了大量的优化,如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销。
-
-锁主要存在四中状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。
-
-**①偏向锁**
-
-**引入偏向锁的目的和引入轻量级锁的目的很像,他们都是为了没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。但是不同是:轻量级锁在无竞争的情况下使用 CAS 操作去代替使用互斥量。而偏向锁在无竞争的情况下会把整个同步都消除掉**。
-
-偏向锁的“偏”就是偏心的偏,它的意思是会偏向于第一个获得它的线程,如果在接下来的执行中,该锁没有被其他线程获取,那么持有偏向锁的线程就不需要进行同步!关于偏向锁的原理可以查看《深入理解Java虚拟机:JVM高级特性与最佳实践》第二版的13章第三节锁优化。
-
-但是对于锁竞争比较激烈的场合,偏向锁就失效了,因为这样场合极有可能每次申请锁的线程都是不相同的,因此这种场合下不应该使用偏向锁,否则会得不偿失,需要注意的是,偏向锁失败后,并不会立即膨胀为重量级锁,而是先升级为轻量级锁。
-
-**② 轻量级锁**
-
-倘若偏向锁失败,虚拟机并不会立即升级为重量级锁,它还会尝试使用一种称为轻量级锁的优化手段(1.6之后加入的)。**轻量级锁不是为了代替重量级锁,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗,因为使用轻量级锁时,不需要申请互斥量。另外,轻量级锁的加锁和解锁都用到了CAS操作。** 关于轻量级锁的加锁和解锁的原理可以查看《深入理解Java虚拟机:JVM高级特性与最佳实践》第二版的13章第三节锁优化。
-
-**轻量级锁能够提升程序同步性能的依据是“对于绝大部分锁,在整个同步周期内都是不存在竞争的”,这是一个经验数据。如果没有竞争,轻量级锁使用 CAS 操作避免了使用互斥操作的开销。但如果存在锁竞争,除了互斥量开销外,还会额外发生CAS操作,因此在有锁竞争的情况下,轻量级锁比传统的重量级锁更慢!如果锁竞争激烈,那么轻量级将很快膨胀为重量级锁!**
-
-**③ 自旋锁和自适应自旋**
-
-轻量级锁失败后,虚拟机为了避免线程真实地在操作系统层面挂起,还会进行一项称为自旋锁的优化手段。
-
-互斥同步对性能最大的影响就是阻塞的实现,因为挂起线程/恢复线程的操作都需要转入内核态中完成(用户态转换到内核态会耗费时间)。
-
-**一般线程持有锁的时间都不是太长,所以仅仅为了这一点时间去挂起线程/恢复线程是得不偿失的。** 所以,虚拟机的开发团队就这样去考虑:“我们能不能让后面来的请求获取锁的线程等待一会而不被挂起呢?看看持有锁的线程是否很快就会释放锁”。**为了让一个线程等待,我们只需要让线程执行一个忙循环(自旋),这项技术就叫做自旋**。
-
-百度百科对自旋锁的解释:
-
-> 何谓自旋锁?它是为实现保护共享资源而提出一种锁机制。其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。
-
-自旋锁在 JDK1.6 之前其实就已经引入了,不过是默认关闭的,需要通过`--XX:+UseSpinning`参数来开启。JDK1.6及1.6之后,就改为默认开启的了。需要注意的是:自旋等待不能完全替代阻塞,因为它还是要占用处理器时间。如果锁被占用的时间短,那么效果当然就很好了!反之,相反!自旋等待的时间必须要有限度。如果自旋超过了限定次数任然没有获得锁,就应该挂起线程。**自旋次数的默认值是10次,用户可以修改`--XX:PreBlockSpin`来更改**。
-
-另外,**在 JDK1.6 中引入了自适应的自旋锁。自适应的自旋锁带来的改进就是:自旋的时间不在固定了,而是和前一次同一个锁上的自旋时间以及锁的拥有者的状态来决定,虚拟机变得越来越“聪明”了**。
-
-**④ 锁消除**
-
-锁消除理解起来很简单,它指的就是虚拟机即使编译器在运行时,如果检测到那些共享数据不可能存在竞争,那么就执行锁消除。锁消除可以节省毫无意义的请求锁的时间。
-
-**⑤ 锁粗化**
-
-原则上,我们在编写代码的时候,总是推荐将同步块的作用范围限制得尽量小,——直在共享数据的实际作用域才进行同步,这样是为了使得需要同步的操作数量尽可能变小,如果存在锁竞争,那等待线程也能尽快拿到锁。
-
-大部分情况下,上面的原则都是没有问题的,但是如果一系列的连续操作都对同一个对象反复加锁和解锁,那么会带来很多不必要的性能消耗。
-
-### Synchronized 和 ReenTrantLock 的对比
-
-
-**① 两者都是可重入锁**
-
-两者都是可重入锁。“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。
-
-**② synchronized 依赖于 JVM 而 ReenTrantLock 依赖于 API**
-
-synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的,并没有直接暴露给我们。ReenTrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock 方法配合 try/finally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。
-
-**③ ReenTrantLock 比 synchronized 增加了一些高级功能**
-
-相比synchronized,ReenTrantLock增加了一些高级功能。主要来说主要有三点:**①等待可中断;②可实现公平锁;③可实现选择性通知(锁可以绑定多个条件)**
-
-- **ReenTrantLock提供了一种能够中断等待锁的线程的机制**,通过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。
-- **ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。** ReenTrantLock默认情况是非公平的,可以通过 ReenTrantLock类的`ReentrantLock(boolean fair)`构造方法来制定是否是公平的。
-- synchronized关键字与wait()和notify/notifyAll()方法相结合可以实现等待/通知机制,ReentrantLock类当然也可以实现,但是需要借助于Condition接口与newCondition() 方法。Condition是JDK1.5之后才有的,它具有很好的灵活性,比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例(即对象监视器),**线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。 在使用notify/notifyAll()方法进行通知时,被通知的线程是由 JVM 选择的,用ReentrantLock类结合Condition实例可以实现“选择性通知”** ,这个功能非常重要,而且是Condition接口默认提供的。而synchronized关键字就相当于整个Lock对象中只有一个Condition实例,所有的线程都注册在它一个身上。如果执行notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题,而Condition实例的signalAll()方法 只会唤醒注册在该Condition实例中的所有等待线程。
-
-如果你想使用上述功能,那么选择ReenTrantLock是一个不错的选择。
-
-**④ 性能已不是选择标准**
-
-在JDK1.6之前,synchronized 的性能是比 ReenTrantLock 差很多。具体表示为:synchronized 关键字吞吐量随线程数的增加,下降得非常严重。而ReenTrantLock 基本保持一个比较稳定的水平。我觉得这也侧面反映了, synchronized 关键字还有非常大的优化余地。后续的技术发展也证明了这一点,我们上面也讲了在 JDK1.6 之后 JVM 团队对 synchronized 关键字做了很多优化。**JDK1.6 之后,synchronized 和 ReenTrantLock 的性能基本是持平了。所以网上那些说因为性能才选择 ReenTrantLock 的文章都是错的!JDK1.6之后,性能已经不是选择synchronized和ReenTrantLock的影响因素了!而且虚拟机在未来的性能改进中会更偏向于原生的synchronized,所以还是提倡在synchronized能满足你的需求的情况下,优先考虑使用synchronized关键字来进行同步!优化后的synchronized和ReenTrantLock一样,在很多地方都是用到了CAS操作**。
diff --git "a/docs/java/Multithread/\345\271\266\345\217\221\345\256\271\345\231\250\346\200\273\347\273\223.md" "b/docs/java/Multithread/\345\271\266\345\217\221\345\256\271\345\231\250\346\200\273\347\273\223.md"
deleted file mode 100644
index 243001efe59..00000000000
--- "a/docs/java/Multithread/\345\271\266\345\217\221\345\256\271\345\231\250\346\200\273\347\273\223.md"
+++ /dev/null
@@ -1,230 +0,0 @@
-点击关注[公众号](#公众号)及时获取笔主最新更新文章,并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。
-
-
-
-- [一 JDK 提供的并发容器总结](#一-jdk-提供的并发容器总结)
-- [二 ConcurrentHashMap](#二-concurrenthashmap)
-- [三 CopyOnWriteArrayList](#三-copyonwritearraylist)
- - [3.1 CopyOnWriteArrayList 简介](#31-copyonwritearraylist-简介)
- - [3.2 CopyOnWriteArrayList 是如何做到的?](#32-copyonwritearraylist-是如何做到的?)
- - [3.3 CopyOnWriteArrayList 读取和写入源码简单分析](#33-copyonwritearraylist-读取和写入源码简单分析)
- - [3.3.1 CopyOnWriteArrayList 读取操作的实现](#331-copyonwritearraylist-读取操作的实现)
- - [3.3.2 CopyOnWriteArrayList 写入操作的实现](#332-copyonwritearraylist-写入操作的实现)
-- [四 ConcurrentLinkedQueue](#四-concurrentlinkedqueue)
-- [五 BlockingQueue](#五-blockingqueue)
- - [5.1 BlockingQueue 简单介绍](#51-blockingqueue-简单介绍)
- - [5.2 ArrayBlockingQueue](#52-arrayblockingqueue)
- - [5.3 LinkedBlockingQueue](#53-linkedblockingqueue)
- - [5.4 PriorityBlockingQueue](#54-priorityblockingqueue)
-- [六 ConcurrentSkipListMap](#六-concurrentskiplistmap)
-- [七 参考](#七-参考)
-
-
-
-## 一 JDK 提供的并发容器总结
-
-JDK提供的这些容器大部分在 `java.util.concurrent` 包中。
-
-
-- **ConcurrentHashMap:** 线程安全的HashMap
-- **CopyOnWriteArrayList:** 线程安全的List,在读多写少的场合性能非常好,远远好于Vector.
-- **ConcurrentLinkedQueue:** 高效的并发队列,使用链表实现。可以看做一个线程安全的 LinkedList,这是一个非阻塞队列。
-- **BlockingQueue:** 这是一个接口,JDK内部通过链表、数组等方式实现了这个接口。表示阻塞队列,非常适合用于作为数据共享的通道。
-- **ConcurrentSkipListMap:** 跳表的实现。这是一个Map,使用跳表的数据结构进行快速查找。
-
-## 二 ConcurrentHashMap
-
-我们知道 HashMap 不是线程安全的,在并发场景下如果要保证一种可行的方式是使用 `Collections.synchronizedMap()` 方法来包装我们的 HashMap。但这是通过使用一个全局的锁来同步不同线程间的并发访问,因此会带来不可忽视的性能问题。
-
-所以就有了 HashMap 的线程安全版本—— ConcurrentHashMap 的诞生。在ConcurrentHashMap中,无论是读操作还是写操作都能保证很高的性能:在进行读操作时(几乎)不需要加锁,而在写操作时通过锁分段技术只对所操作的段加锁而不影响客户端对其它段的访问。
-
-关于 ConcurrentHashMap 相关问题,我在 [Java集合框架常见面试题](https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/collection/Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%A2%98.md) 这篇文章中已经提到过。下面梳理一下关于 ConcurrentHashMap 比较重要的问题:
-
-- [ConcurrentHashMap 和 Hashtable 的区别](https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/collection/Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%A2%98.md#concurrenthashmap-%E5%92%8C-hashtable-%E7%9A%84%E5%8C%BA%E5%88%AB)
-- [ConcurrentHashMap线程安全的具体实现方式/底层具体实现](https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/collection/Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%A2%98.md#concurrenthashmap%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E7%9A%84%E5%85%B7%E4%BD%93%E5%AE%9E%E7%8E%B0%E6%96%B9%E5%BC%8F%E5%BA%95%E5%B1%82%E5%85%B7%E4%BD%93%E5%AE%9E%E7%8E%B0)
-
-## 三 CopyOnWriteArrayList
-
-### 3.1 CopyOnWriteArrayList 简介
-
-```java
-public class CopyOnWriteArrayList
-extends Object
-implements List, RandomAccess, Cloneable, Serializable
-```
-
-在很多应用场景中,读操作可能会远远大于写操作。由于读操作根本不会修改原有的数据,因此对于每次读取都进行加锁其实是一种资源浪费。我们应该允许多个线程同时访问List的内部数据,毕竟读取操作是安全的。
-
-这和我们之前在多线程章节讲过 `ReentrantReadWriteLock` 读写锁的思想非常类似,也就是读读共享、写写互斥、读写互斥、写读互斥。JDK中提供了 `CopyOnWriteArrayList` 类比相比于在读写锁的思想又更进一步。为了将读取的性能发挥到极致,`CopyOnWriteArrayList` 读取是完全不用加锁的,并且更厉害的是:写入也不会阻塞读取操作。只有写入和写入之间需要进行同步等待。这样一来,读操作的性能就会大幅度提升。**那它是怎么做的呢?**
-
-### 3.2 CopyOnWriteArrayList 是如何做到的?
-
- `CopyOnWriteArrayList` 类的所有可变操作(add,set等等)都是通过创建底层数组的新副本来实现的。当 List 需要被修改的时候,我并不修改原有内容,而是对原有数据进行一次复制,将修改的内容写入副本。写完之后,再将修改完的副本替换原来的数据,这样就可以保证写操作不会影响读操作了。
-
-从 `CopyOnWriteArrayList` 的名字就能看出`CopyOnWriteArrayList` 是满足`CopyOnWrite` 的ArrayList,所谓`CopyOnWrite` 也就是说:在计算机,如果你想要对一块内存进行修改时,我们不在原有内存块中进行写操作,而是将内存拷贝一份,在新的内存中进行写操作,写完之后呢,就将指向原来内存指针指向新的内存,原来的内存就可以被回收掉了。
-
-### 3.3 CopyOnWriteArrayList 读取和写入源码简单分析
-
-#### 3.3.1 CopyOnWriteArrayList 读取操作的实现
-
-读取操作没有任何同步控制和锁操作,理由就是内部数组 array 不会发生修改,只会被另外一个 array 替换,因此可以保证数据安全。
-
-```java
- /** The array, accessed only via getArray/setArray. */
- private transient volatile Object[] array;
- public E get(int index) {
- return get(getArray(), index);
- }
- @SuppressWarnings("unchecked")
- private E get(Object[] a, int index) {
- return (E) a[index];
- }
- final Object[] getArray() {
- return array;
- }
-
-```
-
-#### 3.3.2 CopyOnWriteArrayList 写入操作的实现
-
-CopyOnWriteArrayList 写入操作 add() 方法在添加集合的时候加了锁,保证了同步,避免了多线程写的时候会 copy 出多个副本出来。
-
-```java
- /**
- * Appends the specified element to the end of this list.
- *
- * @param e element to be appended to this list
- * @return {@code true} (as specified by {@link Collection#add})
- */
- public boolean add(E e) {
- final ReentrantLock lock = this.lock;
- lock.lock();//加锁
- try {
- Object[] elements = getArray();
- int len = elements.length;
- Object[] newElements = Arrays.copyOf(elements, len + 1);//拷贝新数组
- newElements[len] = e;
- setArray(newElements);
- return true;
- } finally {
- lock.unlock();//释放锁
- }
- }
-```
-
-## 四 ConcurrentLinkedQueue
-
-Java提供的线程安全的 Queue 可以分为**阻塞队列**和**非阻塞队列**,其中阻塞队列的典型例子是 BlockingQueue,非阻塞队列的典型例子是ConcurrentLinkedQueue,在实际应用中要根据实际需要选用阻塞队列或者非阻塞队列。 **阻塞队列可以通过加锁来实现,非阻塞队列可以通过 CAS 操作实现。**
-
-从名字可以看出,`ConcurrentLinkedQueue`这个队列使用链表作为其数据结构.ConcurrentLinkedQueue 应该算是在高并发环境中性能最好的队列了。它之所有能有很好的性能,是因为其内部复杂的实现。
-
-ConcurrentLinkedQueue 内部代码我们就不分析了,大家知道ConcurrentLinkedQueue 主要使用 CAS 非阻塞算法来实现线程安全就好了。
-
-ConcurrentLinkedQueue 适合在对性能要求相对较高,同时对队列的读写存在多个线程同时进行的场景,即如果对队列加锁的成本较高则适合使用无锁的ConcurrentLinkedQueue来替代。
-
-## 五 BlockingQueue
-
-### 5.1 BlockingQueue 简单介绍
-
-上面我们己经提到了 ConcurrentLinkedQueue 作为高性能的非阻塞队列。下面我们要讲到的是阻塞队列——BlockingQueue。阻塞队列(BlockingQueue)被广泛使用在“生产者-消费者”问题中,其原因是BlockingQueue提供了可阻塞的插入和移除的方法。当队列容器已满,生产者线程会被阻塞,直到队列未满;当队列容器为空时,消费者线程会被阻塞,直至队列非空时为止。
-
-BlockingQueue 是一个接口,继承自 Queue,所以其实现类也可以作为 Queue 的实现来使用,而 Queue 又继承自 Collection 接口。下面是 BlockingQueue 的相关实现类:
-
-
-
-**下面主要介绍一下:ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue,这三个 BlockingQueue 的实现类。**
-
-### 5.2 ArrayBlockingQueue
-
-**ArrayBlockingQueue** 是 BlockingQueue 接口的有界队列实现类,底层采用**数组**来实现。ArrayBlockingQueue一旦创建,容量不能改变。其并发控制采用可重入锁来控制,不管是插入操作还是读取操作,都需要获取到锁才能进行操作。当队列容量满时,尝试将元素放入队列将导致操作阻塞;尝试从一个空队列中取一个元素也会同样阻塞。
-
-ArrayBlockingQueue 默认情况下不能保证线程访问队列的公平性,所谓公平性是指严格按照线程等待的绝对时间顺序,即最先等待的线程能够最先访问到 ArrayBlockingQueue。而非公平性则是指访问 ArrayBlockingQueue 的顺序不是遵守严格的时间顺序,有可能存在,当 ArrayBlockingQueue 可以被访问时,长时间阻塞的线程依然无法访问到 ArrayBlockingQueue。如果保证公平性,通常会降低吞吐量。如果需要获得公平性的 ArrayBlockingQueue,可采用如下代码:
-
-```java
-private static ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(10,true);
-```
-
-### 5.3 LinkedBlockingQueue
-
-**LinkedBlockingQueue** 底层基于**单向链表**实现的阻塞队列,可以当做无界队列也可以当做有界队列来使用,同样满足FIFO的特性,与ArrayBlockingQueue 相比起来具有更高的吞吐量,为了防止 LinkedBlockingQueue 容量迅速增,损耗大量内存。通常在创建LinkedBlockingQueue 对象时,会指定其大小,如果未指定,容量等于Integer.MAX_VALUE。
-
-**相关构造方法:**
-
-```java
- /**
- *某种意义上的无界队列
- * Creates a {@code LinkedBlockingQueue} with a capacity of
- * {@link Integer#MAX_VALUE}.
- */
- public LinkedBlockingQueue() {
- this(Integer.MAX_VALUE);
- }
-
- /**
- *有界队列
- * Creates a {@code LinkedBlockingQueue} with the given (fixed) capacity.
- *
- * @param capacity the capacity of this queue
- * @throws IllegalArgumentException if {@code capacity} is not greater
- * than zero
- */
- public LinkedBlockingQueue(int capacity) {
- if (capacity <= 0) throw new IllegalArgumentException();
- this.capacity = capacity;
- last = head = new Node(null);
- }
-```
-
-### 5.4 PriorityBlockingQueue
-
-**PriorityBlockingQueue** 是一个支持优先级的无界阻塞队列。默认情况下元素采用自然顺序进行排序,也可以通过自定义类实现 `compareTo()` 方法来指定元素排序规则,或者初始化时通过构造器参数 `Comparator` 来指定排序规则。
-
-PriorityBlockingQueue 并发控制采用的是 **ReentrantLock**,队列为无界队列(ArrayBlockingQueue 是有界队列,LinkedBlockingQueue 也可以通过在构造函数中传入 capacity 指定队列最大的容量,但是 PriorityBlockingQueue 只能指定初始的队列大小,后面插入元素的时候,**如果空间不够的话会自动扩容**)。
-
-简单地说,它就是 PriorityQueue 的线程安全版本。不可以插入 null 值,同时,插入队列的对象必须是可比较大小的(comparable),否则报 ClassCastException 异常。它的插入操作 put 方法不会 block,因为它是无界队列(take 方法在队列为空的时候会阻塞)。
-
-**推荐文章:**
-
-《解读 Java 并发队列 BlockingQueue》
-
-[https://javadoop.com/post/java-concurrent-queue](https://javadoop.com/post/java-concurrent-queue)
-
-## 六 ConcurrentSkipListMap
-
-下面这部分内容参考了极客时间专栏[《数据结构与算法之美》](https://time.geekbang.org/column/intro/126?code=zl3GYeAsRI4rEJIBNu5B/km7LSZsPDlGWQEpAYw5Vu0=&utm_term=SPoster)以及《实战Java高并发程序设计》。
-
-**为了引出ConcurrentSkipListMap,先带着大家简单理解一下跳表。**
-
-对于一个单链表,即使链表是有序的,如果我们想要在其中查找某个数据,也只能从头到尾遍历链表,这样效率自然就会很低,跳表就不一样了。跳表是一种可以用来快速查找的数据结构,有点类似于平衡树。它们都可以对元素进行快速的查找。但一个重要的区别是:对平衡树的插入和删除往往很可能导致平衡树进行一次全局的调整。而对跳表的插入和删除只需要对整个数据结构的局部进行操作即可。这样带来的好处是:在高并发的情况下,你会需要一个全局锁来保证整个平衡树的线程安全。而对于跳表,你只需要部分锁即可。这样,在高并发环境下,你就可以拥有更好的性能。而就查询的性能而言,跳表的时间复杂度也是 **O(logn)** 所以在并发数据结构中,JDK 使用跳表来实现一个 Map。
-
-跳表的本质是同时维护了多个链表,并且链表是分层的,
-
-
-
-最低层的链表维护了跳表内所有的元素,每上面一层链表都是下面一层的子集。
-
-跳表内的所有链表的元素都是排序的。查找时,可以从顶级链表开始找。一旦发现被查找的元素大于当前链表中的取值,就会转入下一层链表继续找。这也就是说在查找过程中,搜索是跳跃式的。如上图所示,在跳表中查找元素18。
-
-
-
-查找18 的时候原来需要遍历 18 次,现在只需要 7 次即可。针对链表长度比较大的时候,构建索引查找效率的提升就会非常明显。
-
-从上面很容易看出,**跳表是一种利用空间换时间的算法。**
-
-使用跳表实现Map 和使用哈希算法实现Map的另外一个不同之处是:哈希并不会保存元素的顺序,而跳表内所有的元素都是排序的。因此在对跳表进行遍历时,你会得到一个有序的结果。所以,如果你的应用需要有序性,那么跳表就是你不二的选择。JDK 中实现这一数据结构的类是ConcurrentSkipListMap。
-
-## 七 参考
-
-- 《实战Java高并发程序设计》
-- https://javadoop.com/post/java-concurrent-queue
-- https://juejin.im/post/5aeebd02518825672f19c546
-
-## 公众号
-
-如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
-
-**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"面试突击"** 即可免费领取!
-
-**Java工程师必备学习资源:** 一些Java工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。
-
-
diff --git "a/docs/java/Multithread/\345\271\266\345\217\221\347\274\226\347\250\213\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/docs/java/Multithread/\345\271\266\345\217\221\347\274\226\347\250\213\345\237\272\347\241\200\347\237\245\350\257\206.md"
deleted file mode 100644
index 68509cdc066..00000000000
--- "a/docs/java/Multithread/\345\271\266\345\217\221\347\274\226\347\250\213\345\237\272\347\241\200\347\237\245\350\257\206.md"
+++ /dev/null
@@ -1,407 +0,0 @@
-# Java 并发基础知识
-
-Java 并发的基础知识,可能会在笔试中遇到,技术面试中也可能以并发知识环节提问的第一个问题出现。比如面试官可能会问你:“谈谈自己对于进程和线程的理解,两者的区别是什么?”
-
-**本节思维导图:**
-
-## 一 进程和线程
-
-进程和线程的对比这一知识点由于过于基础,所以在面试中很少碰到,但是极有可能会在笔试题中碰到。
-
-常见的提问形式是这样的:**“什么是线程和进程?,请简要描述线程与进程的关系、区别及优缺点? ”**。
-
-### 1.1. 何为进程?
-
-进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。
-
-在 Java 中,当我们启动 main 函数时其实就是启动了一个 JVM 的进程,而 main 函数所在的线程就是这个进程中的一个线程,也称主线程。
-
-如下图所示,在 windows 中通过查看任务管理器的方式,我们就可以清楚看到 window 当前运行的进程(.exe 文件的运行)。
-
-
-
-### 1.2 何为线程?
-
-线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享进程的**堆**和**方法区**资源,但每个线程有自己的**程序计数器**、**虚拟机栈**和**本地方法栈**,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
-
-Java 程序天生就是多线程程序,我们可以通过 JMX 来看一下一个普通的 Java 程序有哪些线程,代码如下。
-
-```java
-public class MultiThread {
- public static void main(String[] args) {
- // 获取 Java 线程管理 MXBean
- ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
- // 不需要获取同步的 monitor 和 synchronizer 信息,仅获取线程和线程堆栈信息
- ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
- // 遍历线程信息,仅打印线程 ID 和线程名称信息
- for (ThreadInfo threadInfo : threadInfos) {
- System.out.println("[" + threadInfo.getThreadId() + "] " + threadInfo.getThreadName());
- }
- }
-}
-```
-
-上述程序输出如下(输出内容可能不同,不用太纠结下面每个线程的作用,只用知道 main 线程执行 main 方法即可):
-
-```
-[5] Attach Listener //添加事件
-[4] Signal Dispatcher // 分发处理给 JVM 信号的线程
-[3] Finalizer //调用对象 finalize 方法的线程
-[2] Reference Handler //清除 reference 线程
-[1] main //main 线程,程序入口
-```
-
-从上面的输出内容可以看出:**一个 Java 程序的运行是 main 线程和多个其他线程同时运行**。
-
-### 1.3 从 JVM 角度说进程和线程之间的关系(重要)
-
-#### 1.3.1 图解进程和线程的关系
-
-下图是 Java 内存区域,通过下图我们从 JVM 的角度来说一下线程和进程之间的关系。如果你对 Java 内存区域 (运行时数据区) 这部分知识不太了解的话可以阅读一下我的这篇文章:[《可能是把 Java 内存区域讲的最清楚的一篇文章》]()
-
-
-
-
-
-
-从上图可以看出:一个进程中可以有多个线程,多个线程共享进程的**堆**和**方法区 (JDK1.8 之后的元空间)**资源,但是每个线程有自己的**程序计数器**、**虚拟机栈** 和 **本地方法栈**。
-
-下面来思考这样一个问题:为什么**程序计数器**、**虚拟机栈**和**本地方法栈**是线程私有的呢?为什么堆和方法区是线程共享的呢?
-
-#### 1.3.2 程序计数器为什么是私有的?
-
-程序计数器主要有下面两个作用:
-
-1. 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
-2. 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。
-
-需要注意的是,如果执行的是 native 方法,那么程序计数器记录的是 undefined 地址,只有执行的是 Java 代码时程序计数器记录的才是下一条指令的地址。
-
-所以,程序计数器私有主要是为了**线程切换后能恢复到正确的执行位置**。
-
-#### 1.3.3 虚拟机栈和本地方法栈为什么是私有的?
-
-- **虚拟机栈:**每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息。从方法调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。
-- **本地方法栈:**和虚拟机栈所发挥的作用非常相似,区别是: **虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。** 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。
-
-所以,为了**保证线程中的局部变量不被别的线程访问到**,虚拟机栈和本地方法栈是线程私有的。
-
-#### 1.3.4 一句话简单了解堆和方法区
-
-堆和方法区是所有线程共享的资源,其中堆是进程中最大的一块内存,主要用于存放新创建的对象 (所有对象都在这里分配内存),方法区主要用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
-
-## 二 多线程并发编程
-
-### 2.1 并发与并行概念解读
-
-- **并发:** 同一时间段,多个任务都在执行 (单位时间内不一定同时执行);
-- **并行:**单位时间内,多个任务同时执行。
-
-### 2.2 为什么要使用多线程?
-
-先从总体上来说:
-
-- **从计算机底层来说:**线程可以比作是轻量级的进程,是程序执行的最小单位,线程间的切换和调度的成本远远小于进程。另外,多核 CPU 时代意味着多个线程可以同时运行,这减少了线程上下文切换的开销。
-- **从当代互联网发展趋势来说:**现在的系统动不动就要求百万级甚至千万级的并发量,而多线程并发编程正是开发高并发系统的基础,利用好多线程机制可以大大提高系统整体的并发能力以及性能。
-
-再深入到计算机底层来探讨:
-
-- **单核时代:** 在单核时代多线程主要是为了提高 CPU 和 IO 设备的综合利用率。举个例子:当只有一个线程的时候会导致 CPU 计算时,IO 设备空闲;进行 IO 操作时,CPU 空闲。我们可以简单地说这两者的利用率目前都是 50%左右。但是当有两个线程的时候就不一样了,当一个线程执行 CPU 计算时,另外一个线程可以进行 IO 操作,这样两个的利用率就可以在理想情况下达到 100%了。
-- **多核时代:** 多核时代多线程主要是为了提高 CPU 利用率。举个例子:假如我们要计算一个复杂的任务,我们只用一个线程的话,CPU 只会一个 CPU 核心被利用到,而创建多个线程就可以让多个 CPU 核心被利用到,这样就提高了 CPU 的利用率。
-
-### 2.3 使用多线程可能带来的问题
-
-并发编程的目的就是为了能提高程序的执行效率提高程序运行速度,但是并发编程并不总是能提高程序运行速度的,而且并发编程可能会遇到很多问题,比如:内存泄漏、上下文切换、死锁还有受限于硬件和软件的资源闲置问题。
-
-## 三 线程的创建与运行
-
-前两种实际上很少使用,一般都是用线程池的方式比较多一点。
-
-### 3.1 继承 Thread 类的方式
-
-
-```java
-public class MyThread extends Thread {
- @Override
- public void run() {
- super.run();
- System.out.println("MyThread");
- }
-}
-```
-Run.java
-
-```java
-public class Run {
-
- public static void main(String[] args) {
- MyThread mythread = new MyThread();
- mythread.start();
- System.out.println("运行结束");
- }
-
-}
-
-```
-运行结果:
-
-
-从上面的运行结果可以看出:线程是一个子任务,CPU 以不确定的方式,或者说是以随机的时间来调用线程中的 run 方法。
-
-### 3.2 实现 Runnable 接口的方式
-
-推荐实现 Runnable 接口方式开发多线程,因为 Java 单继承但是可以实现多个接口。
-
-MyRunnable.java
-
-```java
-public class MyRunnable implements Runnable {
- @Override
- public void run() {
- System.out.println("MyRunnable");
- }
-}
-```
-
-Run.java
-
-```java
-public class Run {
-
- public static void main(String[] args) {
- Runnable runnable=new MyRunnable();
- Thread thread=new Thread(runnable);
- thread.start();
- System.out.println("运行结束!");
- }
-
-}
-```
-运行结果:
-
-
-### 3.3 使用线程池的方式
-
-使用线程池的方式也是最推荐的一种方式,另外,《阿里巴巴 Java 开发手册》在第一章第六节并发处理这一部分也强调到“线程资源必须通过线程池提供,不允许在应用中自行显示创建线程”。这里就不给大家演示代码了,线程池这一节会详细介绍到这部分内容。
-
-## 四 线程的生命周期和状态
-
-Java 线程在运行的生命周期中的指定时刻只可能处于下面 6 种不同状态的其中一个状态(图源《Java 并发编程艺术》4.1.4 节)。
-
-
-
-线程在生命周期中并不是固定处于某一个状态而是随着代码的执行在不同状态之间切换。Java 线程状态变迁如下图所示(图源《Java 并发编程艺术》4.1.4 节):
-
-
-
-
-
-由上图可以看出:线程创建之后它将处于 **NEW(新建)** 状态,调用 `start()` 方法后开始运行,线程这时候处于 **READY(可运行)** 状态。可运行状态的线程获得了 CPU 时间片(timeslice)后就处于 **RUNNING(运行)** 状态。
-
-> 操作系统隐藏 Java 虚拟机(JVM)中的 RUNNABLE 和 RUNNING 状态,它只能看到 RUNNABLE 状态(图源:[HowToDoInJava](https://howtodoinjava.com/):[Java Thread Life Cycle and Thread States](https://howtodoinjava.com/java/multi-threading/java-thread-life-cycle-and-thread-states/)),所以 Java 系统一般将这两个状态统称为 **RUNNABLE(运行中)** 状态 。
-
-
-
-当线程执行 `wait()`方法之后,线程进入 **WAITING(等待)**状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态,而 **TIME_WAITING(超时等待)** 状态相当于在等待状态的基础上增加了超时限制,比如通过 `sleep(long millis)`方法或 `wait(long millis)`方法可以将 Java 线程置于 TIMED WAITING 状态。当超时时间到达后 Java 线程将会返回到 RUNNABLE 状态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到 **BLOCKED(阻塞)** 状态。线程在执行 Runnable 的` run() `方法之后将会进入到 **TERMINATED(终止)** 状态。
-
-## 五 线程优先级
-
-**理论上**来说系统会根据优先级来决定首先使哪个线程进入运行状态。当 CPU 比较闲的时候,设置线程优先级几乎不会有任何作用,而且很多操作系统压根不会不会理会你设置的线程优先级,所以不要让业务过度依赖于线程的优先级。
-
-另外,**线程优先级具有继承特性**比如 A 线程启动 B 线程,则 B 线程的优先级和 A 是一样的。**线程优先级还具有随机性** 也就是说线程优先级高的不一定每一次都先执行完。
-
-Thread 类中包含的成员变量代表了线程的某些优先级。如**Thread.MIN_PRIORITY(常数 1)**,**Thread.NORM_PRIORITY(常数 5)**,**Thread.MAX_PRIORITY(常数 10)**。其中每个线程的优先级都在**1** 到**10** 之间,在默认情况下优先级都是**Thread.NORM_PRIORITY(常数 5)**。
-
-**一般情况下,不会对线程设定优先级别,更不会让某些业务严重地依赖线程的优先级别,比如权重,借助优先级设定某个任务的权重,这种方式是不可取的,一般定义线程的时候使用默认的优先级就好了。**
-
-**相关方法:**
-
-```java
-public final void setPriority(int newPriority) //为线程设定优先级
-public final int getPriority() //获取线程的优先级
-```
-**设置线程优先级方法源码:**
-
-```java
- public final void setPriority(int newPriority) {
- ThreadGroup g;
- checkAccess();
- //线程游戏优先级不能小于 1 也不能大于 10,否则会抛出异常
- if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
- throw new IllegalArgumentException();
- }
- //如果指定的线程优先级大于该线程所在线程组的最大优先级,那么该线程的优先级将设为线程组的最大优先级
- if((g = getThreadGroup()) != null) {
- if (newPriority > g.getMaxPriority()) {
- newPriority = g.getMaxPriority();
- }
- setPriority0(priority = newPriority);
- }
- }
-
-```
-
-## 六 守护线程和用户线程
-
-**守护线程和用户线程简介:**
-
-- **用户 (User) 线程:**运行在前台,执行具体的任务,如程序的主线程、连接网络的子线程等都是用户线程
-- **守护 (Daemon) 线程:**运行在后台,为其他前台线程服务.也可以说守护线程是 JVM 中非守护线程的 **“佣人”**。一旦所有用户线程都结束运行,守护线程会随 JVM 一起结束工作.
-
-main 函数所在的线程就是一个用户线程啊,main 函数启动的同时在 JVM 内部同时还启动了好多守护线程,比如垃圾回收线程。
-
-**那么守护线程和用户线程有什么区别呢?**
-
-比较明显的区别之一是用户线程结束,JVM 退出,不管这个时候有没有守护线程运行。而守护线程不会影响 JVM 的退出。
-
-**注意事项:**
-
-1. `setDaemon(true)`必须在`start()`方法前执行,否则会抛出 `IllegalThreadStateException` 异常
-2. 在守护线程中产生的新线程也是守护线程
-3. 不是所有的任务都可以分配给守护线程来执行,比如读写操作或者计算逻辑
-4. 守护 (Daemon) 线程中不能依靠 finally 块的内容来确保执行关闭或清理资源的逻辑。因为我们上面也说过了一旦所有用户线程都结束运行,守护线程会随 JVM 一起结束工作,所以守护 (Daemon) 线程中的 finally 语句块可能无法被执行。
-
-## 七 上下文切换
-
-多线程编程中一般线程的个数都大于 CPU 核心的个数,而一个 CPU 核心在任意时刻只能被一个线程使用,为了让这些线程都能得到有效执行,CPU 采取的策略是为每个线程分配时间片并轮转的形式。当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程就属于一次上下文切换。
-
-概括来说就是:当前任务在执行完 CPU 时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换会这个任务时,可以再加载这个任务的状态。**任务从保存到再加载的过程就是一次上下文切换**。
-
-上下文切换通常是计算密集型的。也就是说,它需要相当可观的处理器时间,在每秒几十上百次的切换中,每次切换都需要纳秒量级的时间。所以,上下文切换对系统来说意味着消耗大量的 CPU 时间,事实上,可能是操作系统中时间消耗最大的操作。
-
-Linux 相比与其他操作系统(包括其他类 Unix 系统)有很多的优点,其中有一项就是,其上下文切换和模式切换的时间消耗非常少。
-
-## 八 线程死锁
-
-### 认识线程死锁
-
-多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。
-
-如下图所示,线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方的资源,所以这两个线程就会互相等待而进入死锁状态。
-
-
-
-下面通过一个例子来说明线程死锁,代码模拟了上图的死锁的情况 (代码来源于《并发编程之美》):
-
-```java
-public class DeadLockDemo {
- private static Object resource1 = new Object();//资源 1
- private static Object resource2 = new Object();//资源 2
-
- public static void main(String[] args) {
- new Thread(() -> {
- synchronized (resource1) {
- System.out.println(Thread.currentThread() + "get resource1");
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread() + "waiting get resource2");
- synchronized (resource2) {
- System.out.println(Thread.currentThread() + "get resource2");
- }
- }
- }, "线程 1").start();
-
- new Thread(() -> {
- synchronized (resource2) {
- System.out.println(Thread.currentThread() + "get resource2");
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread() + "waiting get resource1");
- synchronized (resource1) {
- System.out.println(Thread.currentThread() + "get resource1");
- }
- }
- }, "线程 2").start();
- }
-}
-```
-
-Output
-
-```
-Thread[线程 1,5,main]get resource1
-Thread[线程 2,5,main]get resource2
-Thread[线程 1,5,main]waiting get resource2
-Thread[线程 2,5,main]waiting get resource1
-```
-
-线程 A 通过 synchronized (resource1) 获得 resource1 的监视器锁,然后通过` Thread.sleep(1000);`让线程 A 休眠 1s 为的是让线程 B 得到执行然后获取到 resource2 的监视器锁。线程 A 和线程 B 休眠结束了都开始企图请求获取对方的资源,然后这两个线程就会陷入互相等待的状态,这也就产生了死锁。上面的例子符合产生死锁的四个必要条件。
-
-学过操作系统的朋友都知道产生死锁必须具备以下四个条件:
-
-1. 互斥条件:该资源任意一个时刻只由一个线程占用。
-1. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
-1. 不剥夺条件:线程已获得的资源在末使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
-1. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
-
-### 如何预防线程死锁?
-
-我们只要破坏产生死锁的四个条件中的其中一个就可以了。
-
-**破坏互斥条件**
-
-这个条件我们没有办法破坏,因为我们用锁本来就是想让他们互斥的(临界资源需要互斥访问)。
-
-**破坏请求与保持条件**
-
-一次性申请所有的资源。
-
-**破坏不剥夺条件**
-
-占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
-
-**破坏循环等待条件**
-
-靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。
-
-我们对线程 2 的代码修改成下面这样就不会产生死锁了。
-
-```java
- new Thread(() -> {
- synchronized (resource1) {
- System.out.println(Thread.currentThread() + "get resource1");
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread() + "waiting get resource2");
- synchronized (resource2) {
- System.out.println(Thread.currentThread() + "get resource2");
- }
- }
- }, "线程 2").start();
-```
-
-Output
-
-```
-Thread[线程 1,5,main]get resource1
-Thread[线程 1,5,main]waiting get resource2
-Thread[线程 1,5,main]get resource2
-Thread[线程 2,5,main]get resource1
-Thread[线程 2,5,main]waiting get resource2
-Thread[线程 2,5,main]get resource2
-
-Process finished with exit code 0
-```
-
-我们分析一下上面的代码为什么避免了死锁的发生?
-
-线程 1 首先获得到 resource1 的监视器锁,这时候线程 2 就获取不到了。然后线程 1 再去获取 resource2 的监视器锁,可以获取到。然后线程 1 释放了对 resource1、resource2 的监视器锁的占用,线程 2 获取到就可以执行了。这样就破坏了破坏循环等待条件,因此避免了死锁。
-
-## 参考
-
-- 《Java 并发编程之美》
-
-- 《Java 并发编程的艺术》
-
-- https://howtodoinjava.com/java/multi-threading/java-thread-life-cycle-and-thread-states/
-
-
\ No newline at end of file
diff --git "a/docs/java/What's New in JDK8/Java8foreach\346\214\207\345\215\227.md" "b/docs/java/What's New in JDK8/Java8foreach\346\214\207\345\215\227.md"
deleted file mode 100644
index 48cda3abfd9..00000000000
--- "a/docs/java/What's New in JDK8/Java8foreach\346\214\207\345\215\227.md"
+++ /dev/null
@@ -1,139 +0,0 @@
-> 本文由 JavaGuide 翻译,原文地址:https://www.baeldung.com/foreach-java
-
-## 1 概述
-
-在Java 8中引入的*forEach*循环为程序员提供了一种新的,简洁而有趣的迭代集合的方式。
-
-在本文中,我们将看到如何将*forEach*与集合*一起*使用,它采用何种参数以及此循环与增强的*for*循环的不同之处。
-
-## 2 基础知识
-
-```Java
-public interface Collection extends Iterable
-```
-
-Collection 接口实现了 Iterable 接口,而 Iterable 接口在 Java 8开始具有一个新的 API:
-
-```java
-void forEach(Consumer super T> action)//对 Iterable的每个元素执行给定的操作,直到所有元素都被处理或动作引发异常。
-```
-
-使用*forEach*,我们可以迭代一个集合并对每个元素执行给定的操作,就像任何其他*迭代器一样。*
-
-例如,迭代和打印字符串集合*的*for循环版本:
-
-```java
-for (String name : names) {
- System.out.println(name);
-}
-```
-
-我们可以使用*forEach*写这个 :
-
-```java
-names.forEach(name -> {
- System.out.println(name);
-});
-```
-
-## 3.使用forEach方法
-
-### 3.1 匿名类
-
-我们使用 *forEach*迭代集合并对每个元素执行特定操作。**要执行的操作包含在实现Consumer接口的类中,并作为参数传递给forEach 。**
-
-所述*消费者*接口是一个功能接口(具有单个抽象方法的接口)。它接受输入并且不返回任何结果。
-
-Consumer 接口定义如下:
-
-```java
-@FunctionalInterface
-public interface Consumer {
- void accept(T t);
-}
-```
-任何实现,例如,只是打印字符串的消费者:
-
-```java
-Consumer printConsumer = new Consumer() {
- public void accept(String name) {
- System.out.println(name);
- };
-};
-```
-
-可以作为参数传递给*forEach*:
-
-```java
-names.forEach(printConsumer);
-```
-
-但这不是通过消费者和使用*forEach* API 创建操作的唯一方法。让我们看看我们将使用*forEach*方法的另外2种最流行的方式:
-
-### 3.2 Lambda表达式
-
-Java 8功能接口的主要优点是我们可以使用Lambda表达式来实例化它们,并避免使用庞大的匿名类实现。
-
-由于 Consumer 接口属于函数式接口,我们可以通过以下形式在Lambda中表达它:
-
-```java
-(argument) -> { body }
-name -> System.out.println(name)
-names.forEach(name -> System.out.println(name));
-```
-
-### 3.3 方法参考
-
-我们可以使用方法引用语法而不是普通的Lambda语法,其中已存在一个方法来对类执行操作:
-
-```java
-names.forEach(System.out::println);
-```
-
-## 4.forEach在集合中的使用
-
-### 4.1.迭代集合
-
-**任何类型Collection的可迭代 - 列表,集合,队列 等都具有使用forEach的相同语法。**
-
-因此,正如我们已经看到的,迭代列表的元素:
-
-```java
-List names = Arrays.asList("Larry", "Steve", "James");
-
-names.forEach(System.out::println);
-```
-
-同样对于一组:
-
-```java
-Set uniqueNames = new HashSet<>(Arrays.asList("Larry", "Steve", "James"));
-
-uniqueNames.forEach(System.out::println);
-```
-
-或者让我们说一个*队列*也是一个*集合*:
-
-```java
-Queue namesQueue = new ArrayDeque<>(Arrays.asList("Larry", "Steve", "James"));
-
-namesQueue.forEach(System.out::println);
-```
-
-### 4.2.迭代Map - 使用Map的forEach
-
-Map没有实现Iterable接口,但它**提供了自己的forEach 变体,它接受BiConsumer**。*
-
-```java
-Map namesMap = new HashMap<>();
-namesMap.put(1, "Larry");
-namesMap.put(2, "Steve");
-namesMap.put(3, "James");
-namesMap.forEach((key, value) -> System.out.println(key + " " + value));
-```
-
-### 4.3.迭代一个Map - 通过迭代entrySet
-
-```java
-namesMap.entrySet().forEach(entry -> System.out.println(entry.getKey() + " " + entry.getValue()));
-```
\ No newline at end of file
diff --git "a/docs/java/What's New in JDK8/Java8\346\225\231\347\250\213\346\216\250\350\215\220.md" "b/docs/java/What's New in JDK8/Java8\346\225\231\347\250\213\346\216\250\350\215\220.md"
deleted file mode 100644
index 7de58352a5f..00000000000
--- "a/docs/java/What's New in JDK8/Java8\346\225\231\347\250\213\346\216\250\350\215\220.md"
+++ /dev/null
@@ -1,18 +0,0 @@
-### 书籍
-
-- **《Java8 In Action》**
-- **《写给大忙人看的Java SE 8》**
-
-上述书籍的PDF版本见 https://shimo.im/docs/CPB0PK05rP4CFmI2/ 中的 “Java 书籍推荐”。
-
-### 开源文档
-
-- **【译】Java 8 简明教程**:
-- **30 seconds of java8:**
-
-### 视频
-
-- **尚硅谷 Java 8 新特性**
-
-视频资源见: https://shimo.im/docs/CPB0PK05rP4CFmI2/ 。
-
diff --git "a/docs/java/basis/BigDecimal\350\247\243\345\206\263\346\265\256\347\202\271\346\225\260\350\277\220\347\256\227\347\262\276\345\272\246\344\270\242\345\244\261\351\227\256\351\242\230.md" "b/docs/java/basis/BigDecimal\350\247\243\345\206\263\346\265\256\347\202\271\346\225\260\350\277\220\347\256\227\347\262\276\345\272\246\344\270\242\345\244\261\351\227\256\351\242\230.md"
new file mode 100644
index 00000000000..5f788f087e8
--- /dev/null
+++ "b/docs/java/basis/BigDecimal\350\247\243\345\206\263\346\265\256\347\202\271\346\225\260\350\277\220\347\256\227\347\262\276\345\272\246\344\270\242\345\244\261\351\227\256\351\242\230.md"
@@ -0,0 +1,69 @@
+## BigDecimal 介绍
+
+`BigDecimal` 可以实现对浮点数的运算,不会造成精度丢失。
+
+那为什么浮点数 `float` 或 `double` 运算的时候会有精度丢失的风险呢?
+
+这是因为计算机是二进制的,浮点数没有办法用二进制精确表示。
+
+## BigDecimal 的用处
+
+《阿里巴巴Java开发手册》中提到:**浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用 equals 来判断。** 具体原理和浮点数的编码方式有关,这里就不多提了,我们下面直接上实例:
+
+```java
+float a = 1.0f - 0.9f;
+float b = 0.9f - 0.8f;
+System.out.println(a);// 0.100000024
+System.out.println(b);// 0.099999964
+System.out.println(a == b);// false
+```
+具有基本数学知识的我们很清楚的知道输出并不是我们想要的结果(**精度丢失**),我们如何解决这个问题呢?一种很常用的方法是:**使用 BigDecimal 来定义浮点数的值,再进行浮点数的运算操作。**
+
+```java
+BigDecimal a = new BigDecimal("1.0");
+BigDecimal b = new BigDecimal("0.9");
+BigDecimal c = new BigDecimal("0.8");
+
+BigDecimal x = a.subtract(b);
+BigDecimal y = b.subtract(c);
+
+System.out.println(x); /* 0.1 */
+System.out.println(y); /* 0.1 */
+System.out.println(Objects.equals(x, y)); /* true */
+```
+
+## BigDecimal 常见方法
+
+## 大小比较
+
+`a.compareTo(b)` : 返回 -1 表示 `a` 小于 `b`,0 表示 `a` 等于 `b` , 1表示 `a` 大于 `b`。
+
+```java
+BigDecimal a = new BigDecimal("1.0");
+BigDecimal b = new BigDecimal("0.9");
+System.out.println(a.compareTo(b));// 1
+```
+### 保留几位小数
+
+通过 `setScale`方法设置保留几位小数以及保留规则。保留规则有挺多种,不需要记,IDEA会提示。
+
+```java
+BigDecimal m = new BigDecimal("1.255433");
+BigDecimal n = m.setScale(3,BigDecimal.ROUND_HALF_DOWN);
+System.out.println(n);// 1.255
+```
+
+## BigDecimal 的使用注意事项
+
+注意:我们在使用BigDecimal时,为了防止精度丢失,推荐使用它的 **BigDecimal(String)** 构造方法来创建对象。《阿里巴巴Java开发手册》对这部分内容也有提到如下图所示。
+
+
+
+## 总结
+
+BigDecimal 主要用来操作(大)浮点数,BigInteger 主要用来操作大整数(超过 long 类型)。
+
+BigDecimal 的实现利用到了 BigInteger, 所不同的是 BigDecimal 加入了小数位的概念
+
+
+
diff --git "a/docs/java/basis/io\346\250\241\345\236\213\350\257\246\350\247\243.md" "b/docs/java/basis/io\346\250\241\345\236\213\350\257\246\350\247\243.md"
new file mode 100644
index 00000000000..231da542c35
--- /dev/null
+++ "b/docs/java/basis/io\346\250\241\345\236\213\350\257\246\350\247\243.md"
@@ -0,0 +1,131 @@
+---
+title: IO模型详解
+category: Java
+tag:
+ - Java基础
+---
+
+
+IO 模型这块确实挺难理解的,需要太多计算机底层知识。写这篇文章用了挺久,就非常希望能把我所知道的讲出来吧!希望朋友们能有收获!为了写这篇文章,还翻看了一下《UNIX 网络编程》这本书,太难了,我滴乖乖!心痛~
+
+_个人能力有限。如果文章有任何需要补充/完善/修改的地方,欢迎在评论区指出,共同进步!_
+
+## 前言
+
+I/O 一直是很多小伙伴难以理解的一个知识点,这篇文章我会将我所理解的 I/O 讲给你听,希望可以对你有所帮助。
+
+## I/O
+
+### 何为 I/O?
+
+I/O(**I**nput/**O**utpu) 即**输入/输出** 。
+
+**我们先从计算机结构的角度来解读一下 I/O。**
+
+根据冯.诺依曼结构,计算机结构分为 5 大部分:运算器、控制器、存储器、输入设备、输出设备。
+
+
+
+输入设备(比如键盘)和输出设备(比如显示器)都属于外部设备。网卡、硬盘这种既可以属于输入设备,也可以属于输出设备。
+
+输入设备向计算机输入数据,输出设备接收计算机输出的数据。
+
+**从计算机结构的视角来看的话, I/O 描述了计算机系统与外部设备之间通信的过程。**
+
+**我们再先从应用程序的角度来解读一下 I/O。**
+
+根据大学里学到的操作系统相关的知识:为了保证操作系统的稳定性和安全性,一个进程的地址空间划分为 **用户空间(User space)** 和 **内核空间(Kernel space )** 。
+
+像我们平常运行的应用程序都是运行在用户空间,只有内核空间才能进行系统态级别的资源有关的操作,比如文件管理、进程通信、内存管理等等。也就是说,我们想要进行 IO 操作,一定是要依赖内核空间的能力。
+
+并且,用户空间的程序不能直接访问内核空间。
+
+当想要执行 IO 操作时,由于没有执行这些操作的权限,只能发起系统调用请求操作系统帮忙完成。
+
+因此,用户进程想要执行 IO 操作的话,必须通过 **系统调用** 来间接访问内核空间
+
+我们在平常开发过程中接触最多的就是 **磁盘 IO(读写文件)** 和 **网络 IO(网络请求和响应)**。
+
+**从应用程序的视角来看的话,我们的应用程序对操作系统的内核发起 IO 调用(系统调用),操作系统负责的内核执行具体的 IO 操作。也就是说,我们的应用程序实际上只是发起了 IO 操作的调用而已,具体 IO 的执行是由操作系统的内核来完成的。**
+
+当应用程序发起 I/O 调用后,会经历两个步骤:
+
+1. 内核等待 I/O 设备准备好数据
+2. 内核将数据从内核空间拷贝到用户空间。
+
+### 有哪些常见的 IO 模型?
+
+UNIX 系统下, IO 模型一共有 5 种: **同步阻塞 I/O**、**同步非阻塞 I/O**、**I/O 多路复用**、**信号驱动 I/O** 和**异步 I/O**。
+
+这也是我们经常提到的 5 种 IO 模型。
+
+## Java 中 3 种常见 IO 模型
+
+### BIO (Blocking I/O)
+
+**BIO 属于同步阻塞 IO 模型** 。
+
+同步阻塞 IO 模型中,应用程序发起 read 调用后,会一直阻塞,直到内核把数据拷贝到用户空间。
+
+
+
+在客户端连接数量不高的情况下,是没问题的。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。
+
+### NIO (Non-blocking/New I/O)
+
+Java 中的 NIO 于 Java 1.4 中引入,对应 `java.nio` 包,提供了 `Channel` , `Selector`,`Buffer` 等抽象。NIO 中的 N 可以理解为 Non-blocking,不单纯是 New。它支持面向缓冲的,基于通道的 I/O 操作方法。 对于高负载、高并发的(网络)应用,应使用 NIO 。
+
+Java 中的 NIO 可以看作是 **I/O 多路复用模型**。也有很多人认为,Java 中的 NIO 属于同步非阻塞 IO 模型。
+
+跟着我的思路往下看看,相信你会得到答案!
+
+我们先来看看 **同步非阻塞 IO 模型**。
+
+
+
+同步非阻塞 IO 模型中,应用程序会一直发起 read 调用,等待数据从内核空间拷贝到用户空间的这段时间里,线程依然是阻塞的,直到在内核把数据拷贝到用户空间。
+
+相比于同步阻塞 IO 模型,同步非阻塞 IO 模型确实有了很大改进。通过轮询操作,避免了一直阻塞。
+
+但是,这种 IO 模型同样存在问题:**应用程序不断进行 I/O 系统调用轮询数据是否已经准备好的过程是十分消耗 CPU 资源的。**
+
+这个时候,**I/O 多路复用模型** 就上场了。
+
+
+
+IO 多路复用模型中,线程首先发起 select 调用,询问内核数据是否准备就绪,等内核把数据准备好了,用户线程再发起 read 调用。read 调用的过程(数据从内核空间->用户空间)还是阻塞的。
+
+> 目前支持 IO 多路复用的系统调用,有 select,epoll 等等。select 系统调用,是目前几乎在所有的操作系统上都有支持
+>
+> - **select 调用** :内核提供的系统调用,它支持一次查询多个系统调用的可用状态。几乎所有的操作系统都支持。
+> - **epoll 调用** :linux 2.6 内核,属于 select 调用的增强版本,优化了 IO 的执行效率。
+
+**IO 多路复用模型,通过减少无效的系统调用,减少了对 CPU 资源的消耗。**
+
+Java 中的 NIO ,有一个非常重要的**选择器 ( Selector )** 的概念,也可以被称为 **多路复用器**。通过它,只需要一个线程便可以管理多个客户端连接。当客户端数据到了之后,才会为其服务。
+
+
+
+### AIO (Asynchronous I/O)
+
+AIO 也就是 NIO 2。Java 7 中引入了 NIO 的改进版 NIO 2,它是异步 IO 模型。
+
+异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。
+
+
+
+目前来说 AIO 的应用还不是很广泛。Netty 之前也尝试使用过 AIO,不过又放弃了。这是因为,Netty 使用了 AIO 之后,在 Linux 系统上的性能并没有多少提升。
+
+最后,来一张图,简单总结一下 Java 中的 BIO、NIO、AIO。
+
+
+
+## 参考
+
+- 《深入拆解 Tomcat & Jetty》
+- 如何完成一次 IO:[https://llc687.top/post/如何完成一次-io/](https://llc687.top/post/如何完成一次-io/)
+- 程序员应该这样理解 IO:[https://www.jianshu.com/p/fa7bdc4f3de7](https://www.jianshu.com/p/fa7bdc4f3de7)
+- 10 分钟看懂, Java NIO 底层原理:https://www.cnblogs.com/crazymakercircle/p/10225159.html
+- IO 模型知多少 | 理论篇:https://www.cnblogs.com/sheng-jie/p/how-much-you-know-about-io-models.html
+- 《UNIX 网络编程 卷 1;套接字联网 API 》6.2 节 IO 模型
+
diff --git "a/docs/java/basis/java\345\237\272\347\241\200\347\237\245\350\257\206\346\200\273\347\273\223.md" "b/docs/java/basis/java\345\237\272\347\241\200\347\237\245\350\257\206\346\200\273\347\273\223.md"
new file mode 100644
index 00000000000..867bfb63867
--- /dev/null
+++ "b/docs/java/basis/java\345\237\272\347\241\200\347\237\245\350\257\206\346\200\273\347\273\223.md"
@@ -0,0 +1,1374 @@
+---
+title: Java基础知识&面试题总结
+category: Java
+tag:
+ - Java基础
+---
+
+## 基础概念与常识
+
+### Java 语言有哪些特点?
+
+1. 简单易学;
+2. 面向对象(封装,继承,多态);
+3. 平台无关性( Java 虚拟机实现平台无关性);
+4. 支持多线程( C++ 语言没有内置的多线程机制,因此必须调用操作系统的多线程功能来进行多线程程序设计,而 Java 语言却提供了多线程支持);
+5. 可靠性;
+6. 安全性;
+7. 支持网络编程并且很方便( Java 语言诞生本身就是为简化网络编程设计的,因此 Java 语言不仅支持网络编程而且很方便);
+8. 编译与解释并存;
+
+> **🐛 修正(参见: [issue#544](https://github.com/Snailclimb/JavaGuide/issues/544))** :C++11 开始(2011 年的时候),C++就引入了多线程库,在 windows、linux、macos 都可以使用`std::thread`和`std::async`来创建线程。参考链接:http://www.cplusplus.com/reference/thread/thread/?kw=thread
+
+### JVM vs JDK vs JRE
+
+#### JVM
+
+Java 虚拟机(JVM)是运行 Java 字节码的虚拟机。JVM 有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。
+
+**什么是字节码?采用字节码的好处是什么?**
+
+> 在 Java 中,JVM 可以理解的代码就叫做`字节码`(即扩展名为 `.class` 的文件),它不面向任何特定的处理器,只面向虚拟机。Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以 Java 程序运行时比较高效,而且,由于字节码并不针对一种特定的机器,因此,Java 程序无须重新编译便可在多种不同操作系统的计算机上运行。
+
+**Java 程序从源代码到运行一般有下面 3 步:**
+
+
+
+我们需要格外注意的是 .class->机器码 这一步。在这一步 JVM 类加载器首先加载字节码文件,然后通过解释器逐行解释执行,这种方式的执行速度会相对比较慢。而且,有些方法和代码块是经常需要被调用的(也就是所谓的热点代码),所以后面引进了 JIT 编译器,而 JIT 属于运行时编译。当 JIT 编译器完成第一次编译后,其会将字节码对应的机器码保存下来,下次可以直接使用。而我们知道,机器码的运行效率肯定是高于 Java 解释器的。这也解释了我们为什么经常会说 Java 是编译与解释共存的语言。
+
+> HotSpot 采用了惰性评估(Lazy Evaluation)的做法,根据二八定律,消耗大部分系统资源的只有那一小部分的代码(热点代码),而这也就是 JIT 所需要编译的部分。JVM 会根据代码每次被执行的情况收集信息并相应地做出一些优化,因此执行的次数越多,它的速度就越快。JDK 9 引入了一种新的编译模式 AOT(Ahead of Time Compilation),它是直接将字节码编译成机器码,这样就避免了 JIT 预热等各方面的开销。JDK 支持分层编译和 AOT 协作使用。但是 ,AOT 编译器的编译质量是肯定比不上 JIT 编译器的。
+
+**总结:**
+
+Java 虚拟机(JVM)是运行 Java 字节码的虚拟机。JVM 有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。字节码和不同系统的 JVM 实现是 Java 语言“一次编译,随处可以运行”的关键所在。
+
+#### JDK 和 JRE
+
+JDK 是 Java Development Kit 缩写,它是功能齐全的 Java SDK。它拥有 JRE 所拥有的一切,还有编译器(javac)和工具(如 javadoc 和 jdb)。它能够创建和编译程序。
+
+JRE 是 Java 运行时环境。它是运行已编译 Java 程序所需的所有内容的集合,包括 Java 虚拟机(JVM),Java 类库,java 命令和其他的一些基础构件。但是,它不能用于创建新程序。
+
+如果你只是为了运行一下 Java 程序的话,那么你只需要安装 JRE 就可以了。如果你需要进行一些 Java 编程方面的工作,那么你就需要安装 JDK 了。但是,这不是绝对的。有时,即使您不打算在计算机上进行任何 Java 开发,仍然需要安装 JDK。例如,如果要使用 JSP 部署 Web 应用程序,那么从技术上讲,您只是在应用程序服务器中运行 Java 程序。那你为什么需要 JDK 呢?因为应用程序服务器会将 JSP 转换为 Java servlet,并且需要使用 JDK 来编译 servlet。
+
+### 为什么说 Java 语言“编译与解释并存”?
+
+高级编程语言按照程序的执行方式分为编译型和解释型两种。简单来说,编译型语言是指编译器针对特定的操作系统将源代码一次性翻译成可被该平台执行的机器码;解释型语言是指解释器对源程序逐行解释成特定平台的机器码并立即执行。比如,你想阅读一本英文名著,你可以找一个英文翻译人员帮助你阅读,
+有两种选择方式,你可以先等翻译人员将全本的英文名著(也就是源码)都翻译成汉语,再去阅读,也可以让翻译人员翻译一段,你在旁边阅读一段,慢慢把书读完。
+
+Java 语言既具有编译型语言的特征,也具有解释型语言的特征,因为 Java 程序要经过先编译,后解释两个步骤,由 Java 编写的程序需要先经过编译步骤,生成字节码(`*.class` 文件),这种字节码必须由 Java 解释器来解释执行。因此,我们可以认为 Java 语言编译与解释并存。
+
+### Oracle JDK 和 OpenJDK 的对比
+
+可能在看这个问题之前很多人和我一样并没有接触和使用过 OpenJDK 。那么 Oracle JDK 和 OpenJDK 之间是否存在重大差异?下面我通过收集到的一些资料,为你解答这个被很多人忽视的问题。
+
+对于 Java 7,没什么关键的地方。OpenJDK 项目主要基于 Sun 捐赠的 HotSpot 源代码。此外,OpenJDK 被选为 Java 7 的参考实现,由 Oracle 工程师维护。关于 JVM,JDK,JRE 和 OpenJDK 之间的区别,Oracle 博客帖子在 2012 年有一个更详细的答案:
+
+> 问:OpenJDK 存储库中的源代码与用于构建 Oracle JDK 的代码之间有什么区别?
+>
+> 答:非常接近 - 我们的 Oracle JDK 版本构建过程基于 OpenJDK 7 构建,只添加了几个部分,例如部署代码,其中包括 Oracle 的 Java 插件和 Java WebStart 的实现,以及一些闭源的第三方组件,如图形光栅化器,一些开源的第三方组件,如 Rhino,以及一些零碎的东西,如附加文档或第三方字体。展望未来,我们的目的是开源 Oracle JDK 的所有部分,除了我们考虑商业功能的部分。
+
+**总结:**
+
+1. Oracle JDK 大概每 6 个月发一次主要版本,而 OpenJDK 版本大概每三个月发布一次。但这不是固定的,我觉得了解这个没啥用处。详情参见:[https://blogs.oracle.com/java-platform-group/update-and-faq-on-the-java-se-release-cadence](https://blogs.oracle.com/java-platform-group/update-and-faq-on-the-java-se-release-cadence) 。
+2. OpenJDK 是一个参考模型并且是完全开源的,而 Oracle JDK 是 OpenJDK 的一个实现,并不是完全开源的;
+3. Oracle JDK 比 OpenJDK 更稳定。OpenJDK 和 Oracle JDK 的代码几乎相同,但 Oracle JDK 有更多的类和一些错误修复。因此,如果您想开发企业/商业软件,我建议您选择 Oracle JDK,因为它经过了彻底的测试和稳定。某些情况下,有些人提到在使用 OpenJDK 可能会遇到了许多应用程序崩溃的问题,但是,只需切换到 Oracle JDK 就可以解决问题;
+4. 在响应性和 JVM 性能方面,Oracle JDK 与 OpenJDK 相比提供了更好的性能;
+5. Oracle JDK 不会为即将发布的版本提供长期支持,用户每次都必须通过更新到最新版本获得支持来获取最新版本;
+6. Oracle JDK 使用 BCL/OTN 协议获得许可,而 OpenJDK 根据 GPL v2 许可获得许可。
+
+🌈 拓展一下:
+
+- BCL 协议(Oracle Binary Code License Agreement): 可以使用JDK(支持商用),但是不能进行修改。
+- OTN 协议(Oracle Technology Network License Agreement): 11 及之后新发布的JDK用的都是这个协议,可以自己私下用,但是商用需要付费。
+
+
+
+相关阅读👍:[《Differences Between Oracle JDK and OpenJDK》](https://www.baeldung.com/oracle-jdk-vs-openjdk)
+
+### Java 和 C++的区别?
+
+我知道很多人没学过 C++,但是面试官就是没事喜欢拿咱们 Java 和 C++ 比呀!没办法!!!就算没学过 C++,也要记下来!
+
+- 都是面向对象的语言,都支持封装、继承和多态
+- Java 不提供指针来直接访问内存,程序内存更加安全
+- Java 的类是单继承的,C++ 支持多重继承;虽然 Java 的类不可以多继承,但是接口可以多继承。
+- Java 有自动内存管理垃圾回收机制(GC),不需要程序员手动释放无用内存。
+- C ++同时支持方法重载和操作符重载,但是 Java 只支持方法重载(操作符重载增加了复杂性,这与 Java 最初的设计思想不符)。
+- ......
+
+### import java 和 javax 有什么区别?
+
+刚开始的时候 JavaAPI 所必需的包是 java 开头的包,javax 当时只是扩展 API 包来使用。然而随着时间的推移,javax 逐渐地扩展成为 Java API 的组成部分。但是,将扩展从 javax 包移动到 java 包确实太麻烦了,最终会破坏一堆现有的代码。因此,最终决定 javax 包将成为标准 API 的一部分。
+
+所以,实际上 java 和 javax 没有区别。这都是一个名字。
+
+## 基本语法
+
+### 字符型常量和字符串常量的区别?
+
+1. **形式** : 字符常量是单引号引起的一个字符,字符串常量是双引号引起的 0 个或若干个字符
+2. **含义** : 字符常量相当于一个整型值( ASCII 值),可以参加表达式运算; 字符串常量代表一个地址值(该字符串在内存中存放位置)
+3. **占内存大小** : 字符常量只占 2 个字节; 字符串常量占若干个字节 (**注意: char 在 Java 中占两个字节**),
+
+ > 字符封装类 `Character` 有一个成员常量 `Character.SIZE` 值为 16,单位是`bits`,该值除以 8(`1byte=8bits`)后就可以得到 2 个字节
+
+> java 编程思想第四版:2.2.2 节
+> 
+
+### 注释
+
+Java 中的注释有三种:
+
+1. 单行注释
+
+2. 多行注释
+
+3. 文档注释。
+
+在我们编写代码的时候,如果代码量比较少,我们自己或者团队其他成员还可以很轻易地看懂代码,但是当项目结构一旦复杂起来,我们就需要用到注释了。注释并不会执行(编译器在编译代码之前会把代码中的所有注释抹掉,字节码中不保留注释),是我们程序员写给自己看的,注释是你的代码说明书,能够帮助看代码的人快速地理清代码之间的逻辑关系。因此,在写程序的时候随手加上注释是一个非常好的习惯。
+
+《Clean Code》这本书明确指出:
+
+> **代码的注释不是越详细越好。实际上好的代码本身就是注释,我们要尽量规范和美化自己的代码来减少不必要的注释。**
+>
+> **若编程语言足够有表达力,就不需要注释,尽量通过代码来阐述。**
+>
+> 举个例子:
+>
+> 去掉下面复杂的注释,只需要创建一个与注释所言同一事物的函数即可
+>
+> ```java
+> // check to see if the employee is eligible for full benefits
+> if ((employee.flags & HOURLY_FLAG) && (employee.age > 65))
+> ```
+>
+> 应替换为
+>
+> ```java
+> if (employee.isEligibleForFullBenefits())
+> ```
+
+### 标识符和关键字的区别是什么?
+
+在我们编写程序的时候,需要大量地为程序、类、变量、方法等取名字,于是就有了标识符,简单来说,标识符就是一个名字。但是有一些标识符,Java 语言已经赋予了其特殊的含义,只能用于特定的地方,这种特殊的标识符就是关键字。因此,关键字是被赋予特殊含义的标识符。比如,在我们的日常生活中 ,“警察局”这个名字已经被赋予了特殊的含义,所以如果你开一家店,店的名字不能叫“警察局”,“警察局”就是我们日常生活中的关键字。
+
+### Java 中有哪些常见的关键字?
+
+| 分类 | 关键字 | | | | | | |
+| :-------------------- | -------- | ---------- | -------- | ------------ | ---------- | --------- | ------ |
+| 访问控制 | private | protected | public | | | | |
+| 类,方法和变量修饰符 | abstract | class | extends | final | implements | interface | native |
+| | new | static | strictfp | synchronized | transient | volatile | |
+| 程序控制 | break | continue | return | do | while | if | else |
+| | for | instanceof | switch | case | default | | |
+| 错误处理 | try | catch | throw | throws | finally | | |
+| 包相关 | import | package | | | | | |
+| 基本类型 | boolean | byte | char | double | float | int | long |
+| | short | null | true | false | | | |
+| 变量引用 | super | this | void | | | | |
+| 保留字 | goto | const | | | | | |
+
+### 自增自减运算符
+
+在写代码的过程中,常见的一种情况是需要某个整数类型变量增加 1 或减少 1,Java 提供了一种特殊的运算符,用于这种表达式,叫做自增运算符(++)和自减运算符(--)。
+
+++和--运算符可以放在变量之前,也可以放在变量之后,当运算符放在变量之前时(前缀),先自增/减,再赋值;当运算符放在变量之后时(后缀),先赋值,再自增/减。例如,当 `b = ++a` 时,先自增(自己增加 1),再赋值(赋值给 b);当 `b = a++` 时,先赋值(赋值给 b),再自增(自己增加 1)。也就是,++a 输出的是 a+1 的值,a++输出的是 a 值。用一句口诀就是:“符号在前就先加/减,符号在后就后加/减”。
+
+### continue、break、和 return 的区别是什么?
+
+在循环结构中,当循环条件不满足或者循环次数达到要求时,循环会正常结束。但是,有时候可能需要在循环的过程中,当发生了某种条件之后 ,提前终止循环,这就需要用到下面几个关键词:
+
+1. continue :指跳出当前的这一次循环,继续下一次循环。
+2. break :指跳出整个循环体,继续执行循环下面的语句。
+
+return 用于跳出所在方法,结束该方法的运行。return 一般有两种用法:
+
+1. `return;` :直接使用 return 结束方法执行,用于没有返回值函数的方法
+2. `return value;` :return 一个特定值,用于有返回值函数的方法
+
+### Java 泛型了解么?什么是类型擦除?介绍一下常用的通配符?
+
+Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
+
+Java 的泛型是伪泛型,这是因为 Java 在运行期间,所有的泛型信息都会被擦掉,这也就是通常所说类型擦除 。
+
+```java
+List list = new ArrayList<>();
+
+list.add(12);
+//这里直接添加会报错
+list.add("a");
+Class extends List> clazz = list.getClass();
+Method add = clazz.getDeclaredMethod("add", Object.class);
+//但是通过反射添加,是可以的
+add.invoke(list, "kl");
+
+System.out.println(list);
+```
+
+泛型一般有三种使用方式:泛型类、泛型接口、泛型方法。
+
+**1.泛型类**:
+
+```java
+//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
+//在实例化泛型类时,必须指定T的具体类型
+public class Generic {
+
+ private T key;
+
+ public Generic(T key) {
+ this.key = key;
+ }
+
+ public T getKey() {
+ return key;
+ }
+}
+```
+
+如何实例化泛型类:
+
+```java
+Generic genericInteger = new Generic(123456);
+```
+
+**2.泛型接口** :
+
+```java
+public interface Generator {
+ public T method();
+}
+```
+
+实现泛型接口,不指定类型:
+
+```java
+class GeneratorImpl implements Generator{
+ @Override
+ public T method() {
+ return null;
+ }
+}
+```
+
+实现泛型接口,指定类型:
+
+```java
+class GeneratorImpl implements Generator{
+ @Override
+ public String method() {
+ return "hello";
+ }
+}
+```
+
+**3.泛型方法** :
+
+```java
+public static void printArray(E[] inputArray) {
+ for (E element : inputArray) {
+ System.out.printf("%s ", element);
+ }
+ System.out.println();
+}
+```
+
+使用:
+
+```java
+// 创建不同类型数组: Integer, Double 和 Character
+Integer[] intArray = { 1, 2, 3 };
+String[] stringArray = { "Hello", "World" };
+printArray(intArray);
+printArray(stringArray);
+```
+
+**常用的通配符为: T,E,K,V,?**
+
+- ? 表示不确定的 java 类型
+- T (type) 表示具体的一个 java 类型
+- K V (key value) 分别代表 java 键值中的 Key Value
+- E (element) 代表 Element
+
+### ==和 equals 的区别
+
+对于基本数据类型来说,==比较的是值。对于引用数据类型来说,==比较的是对象的内存地址。
+
+> 因为 Java 只有值传递,所以,对于 == 来说,不管是比较基本数据类型,还是引用数据类型的变量,其本质比较的都是值,只是引用类型变量存的值是对象的地址。
+
+**`equals()`** 作用不能用于判断基本数据类型的变量,只能用来判断两个对象是否相等。`equals()`方法存在于`Object`类中,而`Object`类是所有类的直接或间接父类。
+
+`Object` 类 `equals()` 方法:
+
+```java
+public boolean equals(Object obj) {
+ return (this == obj);
+}
+```
+
+`equals()` 方法存在两种使用情况:
+
+- **类没有覆盖 `equals()`方法** :通过`equals()`比较该类的两个对象时,等价于通过“==”比较这两个对象,使用的默认是 `Object`类`equals()`方法。
+- **类覆盖了 `equals()`方法** :一般我们都覆盖 `equals()`方法来比较两个对象中的属性是否相等;若它们的属性相等,则返回 true(即,认为这两个对象相等)。
+
+**举个例子:**
+
+```java
+public class test1 {
+ public static void main(String[] args) {
+ String a = new String("ab"); // a 为一个引用
+ String b = new String("ab"); // b为另一个引用,对象的内容一样
+ String aa = "ab"; // 放在常量池中
+ String bb = "ab"; // 从常量池中查找
+ if (aa == bb) // true
+ System.out.println("aa==bb");
+ if (a == b) // false,非同一对象
+ System.out.println("a==b");
+ if (a.equals(b)) // true
+ System.out.println("aEQb");
+ if (42 == 42.0) { // true
+ System.out.println("true");
+ }
+ }
+}
+```
+
+**说明:**
+
+- `String` 中的 `equals` 方法是被重写过的,因为 `Object` 的 `equals` 方法是比较的对象的内存地址,而 `String` 的 `equals` 方法比较的是对象的值。
+- 当创建 `String` 类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个 `String` 对象。
+
+`String`类`equals()`方法:
+
+```java
+public boolean equals(Object anObject) {
+ if (this == anObject) {
+ return true;
+ }
+ if (anObject instanceof String) {
+ String anotherString = (String)anObject;
+ int n = value.length;
+ if (n == anotherString.value.length) {
+ char v1[] = value;
+ char v2[] = anotherString.value;
+ int i = 0;
+ while (n-- != 0) {
+ if (v1[i] != v2[i])
+ return false;
+ i++;
+ }
+ return true;
+ }
+ }
+ return false;
+}
+```
+
+### hashCode()与 equals()
+
+面试官可能会问你:“你重写过 `hashcode` 和 `equals`么,为什么重写 `equals` 时必须重写 `hashCode` 方法?”
+
+**1)hashCode()介绍:**
+
+`hashCode()` 的作用是获取哈希码,也称为散列码;它实际上是返回一个 int 整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。`hashCode()`定义在 JDK 的 `Object` 类中,这就意味着 Java 中的任何类都包含有 `hashCode()` 函数。另外需要注意的是: `Object` 的 hashcode 方法是本地方法,也就是用 c 语言或 c++ 实现的,该方法通常用来将对象的 内存地址 转换为整数之后返回。
+
+```java
+public native int hashCode();
+```
+
+散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)
+
+**2)为什么要有 hashCode?**
+
+我们以“`HashSet` 如何检查重复”为例子来说明为什么要有 hashCode?
+
+当你把对象加入 `HashSet` 时,`HashSet` 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如果没有相符的 hashcode,`HashSet` 会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 `equals()` 方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,`HashSet` 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的 Java 启蒙书《Head First Java》第二版)。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。
+
+**3)为什么重写 `equals` 时必须重写 `hashCode` 方法?**
+
+如果两个对象相等,则 hashcode 一定也是相同的。两个对象相等,对两个对象分别调用 equals 方法都返回 true。但是,两个对象有相同的 hashcode 值,它们也不一定是相等的 。**因此,equals 方法被覆盖过,则 `hashCode` 方法也必须被覆盖。**
+
+> `hashCode()`的默认行为是对堆上的对象产生独特值。如果没有重写 `hashCode()`,则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)
+
+**4)为什么两个对象有相同的 hashcode 值,它们也不一定是相等的?**
+
+在这里解释一位小伙伴的问题。以下内容摘自《Head Fisrt Java》。
+
+因为 `hashCode()` 所使用的哈希算法也许刚好会让多个对象传回相同的哈希值。越糟糕的哈希算法越容易碰撞,但这也与数据值域分布的特性有关(所谓碰撞也就是指的是不同的对象得到相同的 `hashCode` )。
+
+我们刚刚也提到了 `HashSet`,如果 `HashSet` 在对比的时候,同样的 hashcode 有多个对象,它会使用 `equals()` 来判断是否真的相同。也就是说 `hashcode` 只是用来缩小查找成本。
+
+更多关于 `hashcode()` 和 `equals()` 的内容可以查看:[Java hashCode() 和 equals()的若干问题解答](https://www.cnblogs.com/skywang12345/p/3324958.html)
+
+## 基本数据类型
+
+### Java 中的几种基本数据类型是什么?对应的包装类型是什么?各自占用多少字节呢?
+
+Java 中有 8 种基本数据类型,分别为:
+
+1. 6 种数字类型 :`byte`、`short`、`int`、`long`、`float`、`double`
+2. 1 种字符类型:`char`
+3. 1 种布尔型:`boolean`。
+
+这 8 种基本数据类型的默认值以及所占空间的大小如下:
+
+| 基本类型 | 位数 | 字节 | 默认值 |
+| :-------- | :--- | :--- | :------ |
+| `int` | 32 | 4 | 0 |
+| `short` | 16 | 2 | 0 |
+| `long` | 64 | 8 | 0L |
+| `byte` | 8 | 1 | 0 |
+| `char` | 16 | 2 | 'u0000' |
+| `float` | 32 | 4 | 0f |
+| `double` | 64 | 8 | 0d |
+| `boolean` | 1 | | false |
+
+另外,对于 `boolean`,官方文档未明确定义,它依赖于 JVM 厂商的具体实现。逻辑上理解是占用 1 位,但是实际中会考虑计算机高效存储因素。
+
+**注意:**
+
+1. Java 里使用 `long` 类型的数据一定要在数值后面加上 **L**,否则将作为整型解析。
+2. `char a = 'h'`char :单引号,`String a = "hello"` :双引号。
+
+这八种基本类型都有对应的包装类分别为:`Byte`、`Short`、`Integer`、`Long`、`Float`、`Double`、`Character`、`Boolean` 。
+
+包装类型不赋值就是 `Null` ,而基本类型有默认值且不是 `Null`。
+
+另外,这个问题建议还可以先从 JVM 层面来分析。
+
+基本数据类型直接存放在 Java 虚拟机栈中的局部变量表中,而包装类型属于对象类型,我们知道对象实例都存在于堆中。相比于对象类型, 基本数据类型占用的空间非常小。
+
+> 《深入理解 Java 虚拟机》 :局部变量表主要存放了编译期可知的基本数据类型 **(boolean、byte、char、short、int、float、long、double)**、**对象引用**(reference 类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)。
+
+### 自动装箱与拆箱
+
+- **装箱**:将基本类型用它们对应的引用类型包装起来;
+- **拆箱**:将包装类型转换为基本数据类型;
+
+举例:
+
+```java
+Integer i = 10; //装箱
+int n = i; //拆箱
+```
+
+上面这两行代码对应的字节码为:
+
+```java
+ L1
+
+ LINENUMBER 8 L1
+
+ ALOAD 0
+
+ BIPUSH 10
+
+ INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
+
+ PUTFIELD AutoBoxTest.i : Ljava/lang/Integer;
+
+ L2
+
+ LINENUMBER 9 L2
+
+ ALOAD 0
+
+ ALOAD 0
+
+ GETFIELD AutoBoxTest.i : Ljava/lang/Integer;
+
+ INVOKEVIRTUAL java/lang/Integer.intValue ()I
+
+ PUTFIELD AutoBoxTest.n : I
+
+ RETURN
+```
+
+从字节码中,我们发现装箱其实就是调用了 包装类的`valueOf()`方法,拆箱其实就是调用了 `xxxValue()`方法。
+
+因此,
+
+- `Integer i = 10` 等价于 `Integer i = Integer.valueOf(10)`
+- `int n = i` 等价于 `int n = i.intValue()`;
+
+### 8 种基本类型的包装类和常量池
+
+Java 基本类型的包装类的大部分都实现了常量池技术。`Byte`,`Short`,`Integer`,`Long` 这 4 种包装类默认创建了数值 **[-128,127]** 的相应类型的缓存数据,`Character` 创建了数值在[0,127]范围的缓存数据,`Boolean` 直接返回 `True` Or `False`。
+
+**Integer 缓存源码:**
+
+```java
+/**
+
+*此方法将始终缓存-128 到 127(包括端点)范围内的值,并可以缓存此范围之外的其他值。
+
+*/
+
+public static Integer valueOf(int i) {
+
+ if (i >= IntegerCache.low && i <= IntegerCache.high)
+
+ return IntegerCache.cache[i + (-IntegerCache.low)];
+
+ return new Integer(i);
+
+}
+
+private static class IntegerCache {
+
+ static final int low = -128;
+
+ static final int high;
+
+ static final Integer cache[];
+
+}
+```
+
+**`Character` 缓存源码:**
+
+```java
+public static Character valueOf(char c) {
+
+ if (c <= 127) { // must cache
+
+ return CharacterCache.cache[(int)c];
+
+ }
+
+ return new Character(c);
+
+}
+
+
+
+private static class CharacterCache {
+
+ private CharacterCache(){}
+
+
+
+ static final Character cache[] = new Character[127 + 1];
+
+ static {
+
+ for (int i = 0; i < cache.length; i++)
+
+ cache[i] = new Character((char)i);
+
+ }
+
+}
+```
+
+**`Boolean` 缓存源码:**
+
+```java
+public static Boolean valueOf(boolean b) {
+
+ return (b ? TRUE : FALSE);
+
+}
+```
+
+如果超出对应范围仍然会去创建新的对象,缓存的范围区间的大小只是在性能和资源之间的权衡。
+
+两种浮点数类型的包装类 `Float`,`Double` 并没有实现常量池技术。
+
+```java
+Integer i1 = 33;
+
+Integer i2 = 33;
+
+System.out.println(i1 == i2);// 输出 true
+
+Float i11 = 333f;
+
+Float i22 = 333f;
+
+System.out.println(i11 == i22);// 输出 false
+
+Double i3 = 1.2;
+
+Double i4 = 1.2;
+
+System.out.println(i3 == i4);// 输出 false
+```
+
+下面我们来看一下问题。下面的代码的输出结果是 `true` 还是 `flase` 呢?
+
+```java
+Integer i1 = 40;
+
+Integer i2 = new Integer(40);
+
+System.out.println(i1==i2);
+```
+
+`Integer i1=40` 这一行代码会发生装箱,也就是说这行代码等价于 `Integer i1=Integer.valueOf(40)` 。因此,`i1` 直接使用的是常量池中的对象。而`Integer i1 = new Integer(40)` 会直接创建新的对象。
+
+因此,答案是 `false` 。你答对了吗?
+
+记住:**所有整型包装类对象之间值的比较,全部使用 equals 方法比较**。
+
+
+
+## 方法(函数)
+
+### 什么是方法的返回值?
+
+方法的返回值是指我们获取到的某个方法体中的代码执行后产生的结果!(前提是该方法可能产生结果)。返回值的作用是接收出结果,使得它可以用于其他的操作!
+
+### 方法有哪几种类型?
+
+**1.无参数无返回值的方法**
+
+```java
+// 无参数无返回值的方法(如果方法没有返回值,不能不写,必须写void,表示没有返回值)
+public void f1() {
+ System.out.println("无参数无返回值的方法");
+}
+```
+
+**2.有参数无返回值的方法**
+
+```java
+/**
+* 有参数无返回值的方法
+* 参数列表由零组到多组“参数类型+形参名”组合而成,多组参数之间以英文逗号(,)隔开,形参类型和形参名之间以英文空格隔开
+*/
+public void f2(int a, String b, int c) {
+ System.out.println(a + "-->" + b + "-->" + c);
+}
+```
+
+**3.有返回值无参数的方法**
+
+```java
+// 有返回值无参数的方法(返回值可以是任意的类型,在函数里面必须有return关键字返回对应的类型)
+public int f3() {
+ System.out.println("有返回值无参数的方法");
+ return 2;
+}
+```
+
+**4.有返回值有参数的方法**
+
+```java
+// 有返回值有参数的方法
+public int f4(int a, int b) {
+ return a * b;
+}
+```
+
+**5.return 在无返回值方法的特殊使用**
+
+```java
+// return在无返回值方法的特殊使用
+public void f5(int a) {
+ if (a > 10) {
+ return;//表示结束所在方法 (f5方法)的执行,下方的输出语句不会执行
+ }
+ System.out.println(a);
+}
+```
+
+### 在一个静态方法内调用一个非静态成员为什么是非法的?
+
+这个需要结合 JVM 的相关知识,静态方法是属于类的,在类加载的时候就会分配内存,可以通过类名直接访问。而非静态成员属于实例对象,只有在对象实例化之后才存在,然后通过类的实例对象去访问。在类的非静态成员不存在的时候静态成员就已经存在了,此时调用在内存中还不存在的非静态成员,属于非法操作。
+
+### 静态方法和实例方法有何不同?
+
+**1、调用方式**
+
+在外部调用静态方法时,可以使用 `类名.方法名` 的方式,也可以使用 `对象.方法名` 的方式,而实例方法只有后面这种方式。也就是说,**调用静态方法可以无需创建对象** 。
+
+不过,需要注意的是一般不建议使用 `对象.方法名` 的方式来调用静态方法。这种方式非常容易造成混淆,静态方法不属于类的某个对象而是属于这个类。
+
+因此,一般建议使用 `类名.方法名` 的方式来调用静态方法。
+
+```java
+
+public class Person {
+ public void method() {
+ //......
+ }
+
+ public static void staicMethod(){
+ //......
+ }
+ public static void main(String[] args) {
+ Person person = new Person();
+ // 调用实例方法
+ person.method();
+ // 调用静态方法
+ Person.staicMethod()
+ }
+}
+```
+
+**2、访问类成员是否存在限制**
+
+静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),不允许访问实例成员(即实例成员变量和实例方法),而实例方法不存在这个限制。
+
+### 为什么 Java 中只有值传递?
+
+首先,我们回顾一下在程序设计语言中有关将参数传递给方法(或函数)的一些专业术语。
+
+**按值调用(call by value)** 表示方法接收的是调用者提供的值,**按引用调用(call by reference)** 表示方法接收的是调用者提供的变量地址。一个方法可以修改传递引用所对应的变量值,而不能修改传递值调用所对应的变量值。它用来描述各种程序设计语言(不只是 Java)中方法参数传递方式。
+
+**Java 程序设计语言总是采用按值调用。也就是说,方法得到的是所有参数值的一个拷贝,也就是说,方法不能修改传递给它的任何参数变量的内容。**
+
+**下面通过 3 个例子来给大家说明**
+
+> **example 1**
+
+```java
+public static void main(String[] args) {
+ int num1 = 10;
+ int num2 = 20;
+
+ swap(num1, num2);
+
+ System.out.println("num1 = " + num1);
+ System.out.println("num2 = " + num2);
+}
+
+public static void swap(int a, int b) {
+ int temp = a;
+ a = b;
+ b = temp;
+
+ System.out.println("a = " + a);
+ System.out.println("b = " + b);
+}
+```
+
+**结果:**
+
+```
+a = 20
+b = 10
+num1 = 10
+num2 = 20
+```
+
+**解析:**
+
+
+
+在 swap 方法中,a、b 的值进行交换,并不会影响到 num1、num2。因为,a、b 中的值,只是从 num1、num2 的复制过来的。也就是说,a、b 相当于 num1、num2 的副本,副本的内容无论怎么修改,都不会影响到原件本身。
+
+**通过上面例子,我们已经知道了一个方法不能修改一个基本数据类型的参数,而对象引用作为参数就不一样,请看 example2.**
+
+> **example 2**
+
+```java
+ public static void main(String[] args) {
+ int[] arr = { 1, 2, 3, 4, 5 };
+ System.out.println(arr[0]);
+ change(arr);
+ System.out.println(arr[0]);
+ }
+
+ public static void change(int[] array) {
+ // 将数组的第一个元素变为0
+ array[0] = 0;
+ }
+```
+
+**结果:**
+
+```
+1
+0
+```
+
+**解析:**
+
+
+
+array 被初始化 arr 的拷贝也就是一个对象的引用,也就是说 array 和 arr 指向的是同一个数组对象。 因此,外部对引用对象的改变会反映到所对应的对象上。
+
+**通过 example2 我们已经看到,实现一个改变对象参数状态的方法并不是一件难事。理由很简单,方法得到的是对象引用的拷贝,对象引用及其他的拷贝同时引用同一个对象。**
+
+**很多程序设计语言(特别是,C++和 Pascal)提供了两种参数传递的方式:值调用和引用调用。有些程序员(甚至本书的作者)认为 Java 程序设计语言对对象采用的是引用调用,实际上,这种理解是不对的。由于这种误解具有一定的普遍性,所以下面给出一个反例来详细地阐述一下这个问题。**
+
+> **example 3**
+
+```java
+public class Test {
+
+ public static void main(String[] args) {
+ // TODO Auto-generated method stub
+ Student s1 = new Student("小张");
+ Student s2 = new Student("小李");
+ Test.swap(s1, s2);
+ System.out.println("s1:" + s1.getName());
+ System.out.println("s2:" + s2.getName());
+ }
+
+ public static void swap(Student x, Student y) {
+ Student temp = x;
+ x = y;
+ y = temp;
+ System.out.println("x:" + x.getName());
+ System.out.println("y:" + y.getName());
+ }
+}
+```
+
+**结果:**
+
+```
+x:小李
+y:小张
+s1:小张
+s2:小李
+```
+
+**解析:**
+
+交换之前:
+
+
+
+交换之后:
+
+
+
+通过上面两张图可以很清晰的看出: **方法并没有改变存储在变量 s1 和 s2 中的对象引用。swap 方法的参数 x 和 y 被初始化为两个对象引用的拷贝,这个方法交换的是这两个拷贝**
+
+> **总结**
+
+Java 程序设计语言对对象采用的不是引用调用,实际上,对象引用是按
+值传递的。
+
+下面再总结一下 Java 中方法参数的使用情况:
+
+- 一个方法不能修改一个基本数据类型的参数(即数值型或布尔型)。
+- 一个方法可以改变一个对象参数的状态。
+- 一个方法不能让对象参数引用一个新的对象。
+
+**参考:**
+
+《Java 核心技术卷 Ⅰ》基础知识第十版第四章 4.5 小节
+
+### 重载和重写的区别
+
+> 重载就是同样的一个方法能够根据输入数据的不同,做出不同的处理
+>
+> 重写就是当子类继承自父类的相同方法,输入数据一样,但要做出有别于父类的响应时,你就要覆盖父类方法
+
+#### 重载
+
+发生在同一个类中(或者父类和子类之间),方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同。
+
+下面是《Java 核心技术》对重载这个概念的介绍:
+
+
+
+综上:重载就是同一个类中多个同名方法根据不同的传参来执行不同的逻辑处理。
+
+#### 重写
+
+重写发生在运行期,是子类对父类的允许访问的方法的实现过程进行重新编写。
+
+1. 返回值类型、方法名、参数列表必须相同,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类。
+2. 如果父类方法访问修饰符为 `private/final/static` 则子类就不能重写该方法,但是被 static 修饰的方法能够被再次声明。
+3. 构造方法无法被重写
+
+综上:重写就是子类对父类方法的重新改造,外部样子不能改变,内部逻辑可以改变
+
+暖心的 Guide 哥最后再来个图表总结一下!
+
+| 区别点 | 重载方法 | 重写方法 |
+| :--------- | :------- | :----------------------------------------------------------- |
+| 发生范围 | 同一个类 | 子类 |
+| 参数列表 | 必须修改 | 一定不能修改 |
+| 返回类型 | 可修改 | 子类方法返回值类型应比父类方法返回值类型更小或相等 |
+| 异常 | 可修改 | 子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等; |
+| 访问修饰符 | 可修改 | 一定不能做更严格的限制(可以降低限制) |
+| 发生阶段 | 编译期 | 运行期 |
+
+**方法的重写要遵循“两同两小一大”**(以下内容摘录自《疯狂 Java 讲义》,[issue#892](https://github.com/Snailclimb/JavaGuide/issues/892) ):
+
+- “两同”即方法名相同、形参列表相同;
+- “两小”指的是子类方法返回值类型应比父类方法返回值类型更小或相等,子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等;
+- “一大”指的是子类方法的访问权限应比父类方法的访问权限更大或相等。
+
+⭐️ 关于 **重写的返回值类型** 这里需要额外多说明一下,上面的表述不太清晰准确:如果方法的返回类型是 void 和基本数据类型,则返回值重写时不可修改。但是如果方法的返回值是引用类型,重写时是可以返回该引用类型的子类的。
+
+```java
+public class Hero {
+ public String name() {
+ return "超级英雄";
+ }
+}
+public class SuperMan extends Hero{
+ @Override
+ public String name() {
+ return "超人";
+ }
+ public Hero hero() {
+ return new Hero();
+ }
+}
+
+public class SuperSuperMan extends SuperMan {
+ public String name() {
+ return "超级超级英雄";
+ }
+
+ @Override
+ public SuperMan hero() {
+ return new SuperMan();
+ }
+}
+```
+
+### 深拷贝 vs 浅拷贝
+
+1. **浅拷贝**:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝。
+2. **深拷贝**:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。
+
+
+
+## Java 面向对象
+
+### 面向对象和面向过程的区别
+
+- **面向过程** :**面向过程性能比面向对象高。** 因为类调用时需要实例化,开销比较大,比较消耗资源,所以当性能是最重要的考量因素的时候,比如单片机、嵌入式开发、Linux/Unix 等一般采用面向过程开发。但是,**面向过程没有面向对象易维护、易复用、易扩展。**
+- **面向对象** :**面向对象易维护、易复用、易扩展。** 因为面向对象有封装、继承、多态性的特性,所以可以设计出低耦合的系统,使系统更加灵活、更加易于维护。但是,**面向对象性能比面向过程低**。
+
+参见 issue : [面向过程 :面向过程性能比面向对象高??](https://github.com/Snailclimb/JavaGuide/issues/431)
+
+> 这个并不是根本原因,面向过程也需要分配内存,计算内存偏移量,Java 性能差的主要原因并不是因为它是面向对象语言,而是 Java 是半编译语言,最终的执行代码并不是可以直接被 CPU 执行的二进制机械码。
+>
+> 而面向过程语言大多都是直接编译成机械码在电脑上执行,并且其它一些面向过程的脚本语言性能也并不一定比 Java 好。
+
+### 成员变量与局部变量的区别有哪些?
+
+1. 从语法形式上看,成员变量是属于类的,而局部变量是在代码块或方法中定义的变量或是方法的参数;成员变量可以被 `public`,`private`,`static` 等修饰符所修饰,而局部变量不能被访问控制修饰符及 `static` 所修饰;但是,成员变量和局部变量都能被 `final` 所修饰。
+2. 从变量在内存中的存储方式来看,如果成员变量是使用 `static` 修饰的,那么这个成员变量是属于类的,如果没有使用 `static` 修饰,这个成员变量是属于实例的。而对象存在于堆内存,局部变量则存在于栈内存。
+3. 从变量在内存中的生存时间上看,成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动消失。
+4. 从变量是否有默认值来看,成员变量如果没有被赋初,则会自动以类型的默认值而赋值(一种情况例外:被 `final` 修饰的成员变量也必须显式地赋值),而局部变量则不会自动赋值。
+
+### 创建一个对象用什么运算符?对象实体与对象引用有何不同?
+
+new 运算符,new 创建对象实例(对象实例在堆内存中),对象引用指向对象实例(对象引用存放在栈内存中)。
+
+一个对象引用可以指向 0 个或 1 个对象(一根绳子可以不系气球,也可以系一个气球);一个对象可以有 n 个引用指向它(可以用 n 条绳子系住一个气球)。
+
+### 对象的相等与指向他们的引用相等,两者有什么不同?
+
+对象的相等,比的是内存中存放的内容是否相等。而引用相等,比较的是他们指向的内存地址是否相等。
+
+### 一个类的构造方法的作用是什么? 若一个类没有声明构造方法,该程序能正确执行吗? 为什么?
+
+构造方法主要作用是完成对类对象的初始化工作。
+
+如果一个类没有声明构造方法,也可以执行!因为一个类即使没有声明构造方法也会有默认的不带参数的构造方法。如果我们自己添加了类的构造方法(无论是否有参),Java 就不会再添加默认的无参数的构造方法了,这时候,就不能直接 new 一个对象而不传递参数了,所以我们一直在不知不觉地使用构造方法,这也是为什么我们在创建对象的时候后面要加一个括号(因为要调用无参的构造方法)。如果我们重载了有参的构造方法,记得都要把无参的构造方法也写出来(无论是否用到),因为这可以帮助我们在创建对象的时候少踩坑。
+
+### 构造方法有哪些特点?是否可被 override?
+
+特点:
+
+1. 名字与类名相同。
+2. 没有返回值,但不能用 void 声明构造函数。
+3. 生成类的对象时自动执行,无需调用。
+
+构造方法不能被 override(重写),但是可以 overload(重载),所以你可以看到一个类中有多个构造函数的情况。
+
+### 面向对象三大特征
+
+#### 封装
+
+封装是指把一个对象的状态信息(也就是属性)隐藏在对象内部,不允许外部对象直接访问对象的内部信息。但是可以提供一些可以被外界访问的方法来操作属性。就好像我们看不到挂在墙上的空调的内部的零件信息(也就是属性),但是可以通过遥控器(方法)来控制空调。如果属性不想被外界访问,我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了。就好像如果没有空调遥控器,那么我们就无法操控空凋制冷,空调本身就没有意义了(当然现在还有很多其他方法 ,这里只是为了举例子)。
+
+```java
+public class Student {
+ private int id;//id属性私有化
+ private String name;//name属性私有化
+
+ //获取id的方法
+ public int getId() {
+ return id;
+ }
+
+ //设置id的方法
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ //获取name的方法
+ public String getName() {
+ return name;
+ }
+
+ //设置name的方法
+ public void setName(String name) {
+ this.name = name;
+ }
+}
+```
+
+#### 继承
+
+不同类型的对象,相互之间经常有一定数量的共同点。例如,小明同学、小红同学、小李同学,都共享学生的特性(班级、学号等)。同时,每一个对象还定义了额外的特性使得他们与众不同。例如小明的数学比较好,小红的性格惹人喜爱;小李的力气比较大。继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承,可以快速地创建新的类,可以提高代码的重用,程序的可维护性,节省大量创建新类的时间 ,提高我们的开发效率。
+
+**关于继承如下 3 点请记住:**
+
+1. 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,**只是拥有**。
+2. 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
+3. 子类可以用自己的方式实现父类的方法。(以后介绍)。
+
+#### 多态
+
+多态,顾名思义,表示一个对象具有多种的状态。具体表现为父类的引用指向子类的实例。
+
+**多态的特点:**
+
+- 对象类型和引用类型之间具有继承(类)/实现(接口)的关系;
+- 引用类型变量发出的方法调用的到底是哪个类中的方法,必须在程序运行期间才能确定;
+- 多态不能调用“只在子类存在但在父类不存在”的方法;
+- 如果子类重写了父类的方法,真正执行的是子类覆盖的方法,如果子类没有覆盖父类的方法,执行的是父类的方法。
+
+### String StringBuffer 和 StringBuilder 的区别是什么? String 为什么是不可变的?
+
+**可变性**
+
+简单的来说:`String` 类中使用 final 关键字修饰字符数组来保存字符串,`private final char value[]`,所以`String` 对象是不可变的。
+
+> 补充(来自[issue 675](https://github.com/Snailclimb/JavaGuide/issues/675)):在 Java 9 之后,String 、`StringBuilder` 与 `StringBuffer` 的实现改用 byte 数组存储字符串 `private final byte[] value`
+
+而 `StringBuilder` 与 `StringBuffer` 都继承自 `AbstractStringBuilder` 类,在 `AbstractStringBuilder` 中也是使用字符数组保存字符串`char[]value` 但是没有用 `final` 关键字修饰,所以这两种对象都是可变的。
+
+`StringBuilder` 与 `StringBuffer` 的构造方法都是调用父类构造方法也就是`AbstractStringBuilder` 实现的,大家可以自行查阅源码。
+
+`AbstractStringBuilder.java`
+
+```java
+abstract class AbstractStringBuilder implements Appendable, CharSequence {
+ /**
+ * The value is used for character storage.
+ */
+ char[] value;
+
+ /**
+ * The count is the number of characters used.
+ */
+ int count;
+
+ AbstractStringBuilder(int capacity) {
+ value = new char[capacity];
+ }}
+```
+
+**线程安全性**
+
+`String` 中的对象是不可变的,也就可以理解为常量,线程安全。`AbstractStringBuilder` 是 `StringBuilder` 与 `StringBuffer` 的公共父类,定义了一些字符串的基本操作,如 `expandCapacity`、`append`、`insert`、`indexOf` 等公共方法。`StringBuffer` 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。`StringBuilder` 并没有对方法进行加同步锁,所以是非线程安全的。
+
+**性能**
+
+每次对 `String` 类型进行改变的时候,都会生成一个新的 `String` 对象,然后将指针指向新的 `String` 对象。`StringBuffer` 每次都会对 `StringBuffer` 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 `StringBuilder` 相比使用 `StringBuffer` 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
+
+**对于三者使用的总结:**
+
+1. 操作少量的数据: 适用 `String`
+2. 单线程操作字符串缓冲区下操作大量数据: 适用 `StringBuilder`
+3. 多线程操作字符串缓冲区下操作大量数据: 适用 `StringBuffer`
+
+### Object 类的常见方法总结
+
+Object 类是一个特殊的类,是所有类的父类。它主要提供了以下 11 个方法:
+
+```java
+public final native Class> getClass()//native方法,用于返回当前运行时对象的Class对象,使用了final关键字修饰,故不允许子类重写。
+
+public native int hashCode() //native方法,用于返回对象的哈希码,主要使用在哈希表中,比如JDK中的HashMap。
+public boolean equals(Object obj)//用于比较2个对象的内存地址是否相等,String类对该方法进行了重写用户比较字符串的值是否相等。
+
+protected native Object clone() throws CloneNotSupportedException//naitive方法,用于创建并返回当前对象的一份拷贝。一般情况下,对于任何对象 x,表达式 x.clone() != x 为true,x.clone().getClass() == x.getClass() 为true。Object本身没有实现Cloneable接口,所以不重写clone方法并且进行调用的话会发生CloneNotSupportedException异常。
+
+public String toString()//返回类的名字@实例的哈希码的16进制的字符串。建议Object所有的子类都重写这个方法。
+
+public final native void notify()//native方法,并且不能重写。唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。如果有多个线程在等待只会任意唤醒一个。
+
+public final native void notifyAll()//native方法,并且不能重写。跟notify一样,唯一的区别就是会唤醒在此对象监视器上等待的所有线程,而不是一个线程。
+
+public final native void wait(long timeout) throws InterruptedException//native方法,并且不能重写。暂停线程的执行。注意:sleep方法没有释放锁,而wait方法释放了锁 。timeout是等待时间。
+
+public final void wait(long timeout, int nanos) throws InterruptedException//多了nanos参数,这个参数表示额外时间(以毫微秒为单位,范围是 0-999999)。 所以超时的时间还需要加上nanos毫秒。
+
+public final void wait() throws InterruptedException//跟之前的2个wait方法一样,只不过该方法一直等待,没有超时时间这个概念
+
+protected void finalize() throws Throwable { }//实例被垃圾回收器回收的时候触发的操作
+```
+
+
+## 反射
+
+### 何为反射?
+
+如果说大家研究过框架的底层原理或者咱们自己写过框架的话,一定对反射这个概念不陌生。
+
+反射之所以被称为框架的灵魂,主要是因为它赋予了我们在运行时分析类以及执行类中方法的能力。
+
+通过反射你可以获取任意一个类的所有属性和方法,你还可以调用这些方法和属性。
+
+### 反射机制优缺点
+
+- **优点** : 可以让咱们的代码更加灵活、为各种框架提供开箱即用的功能提供了便利
+- **缺点** :让我们在运行时有了分析操作类的能力,这同样也增加了安全问题。比如可以无视泛型参数的安全检查(泛型参数的安全检查发生在编译时)。另外,反射的性能也要稍差点,不过,对于框架来说实际是影响不大的。[Java Reflection: Why is it so slow?](https://stackoverflow.com/questions/1392351/java-reflection-why-is-it-so-slow)
+
+### 反射的应用场景
+
+像咱们平时大部分时候都是在写业务代码,很少会接触到直接使用反射机制的场景。
+
+但是,这并不代表反射没有用。相反,正是因为反射,你才能这么轻松地使用各种框架。像 Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射机制。
+
+**这些框架中也大量使用了动态代理,而动态代理的实现也依赖反射。**
+
+比如下面是通过 JDK 实现动态代理的示例代码,其中就使用了反射类 `Method` 来调用指定的方法。
+
+```java
+public class DebugInvocationHandler implements InvocationHandler {
+ /**
+ * 代理类中的真实对象
+ */
+ private final Object target;
+
+ public DebugInvocationHandler(Object target) {
+ this.target = target;
+ }
+
+
+ public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
+ System.out.println("before method " + method.getName());
+ Object result = method.invoke(target, args);
+ System.out.println("after method " + method.getName());
+ return result;
+ }
+}
+
+```
+
+另外,像 Java 中的一大利器 **注解** 的实现也用到了反射。
+
+为什么你使用 Spring 的时候 ,一个`@Component`注解就声明了一个类为 Spring Bean 呢?为什么你通过一个 `@Value`注解就读取到配置文件中的值呢?究竟是怎么起作用的呢?
+
+这些都是因为你可以基于反射分析类,然后获取到类/属性/方法/方法的参数上的注解。你获取到注解之后,就可以做进一步的处理。
+
+## 异常
+
+### Java 异常类层次结构图
+
+
+
+图片来自:https://simplesnippets.tech/exception-handling-in-java-part-1/
+
+
+
+图片来自:https://chercher.tech/java-programming/exceptions-java
+
+在 Java 中,所有的异常都有一个共同的祖先 `java.lang` 包中的 `Throwable` 类。`Throwable` 类有两个重要的子类 `Exception`(异常)和 `Error`(错误)。`Exception` 能被程序本身处理(`try-catch`), `Error` 是无法处理的(只能尽量避免)。
+
+`Exception` 和 `Error` 二者都是 Java 异常处理的重要子类,各自都包含大量子类。
+
+- **`Exception`** :程序本身可以处理的异常,可以通过 `catch` 来进行捕获。`Exception` 又可以分为 受检查异常(必须处理) 和 不受检查异常(可以不处理)。
+- **`Error`** :`Error` 属于程序无法处理的错误 ,我们没办法通过 `catch` 来进行捕获 。例如,Java 虚拟机运行错误(`Virtual MachineError`)、虚拟机内存不够错误(`OutOfMemoryError`)、类定义错误(`NoClassDefFoundError`)等 。这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止。
+
+**受检查异常**
+
+Java 代码在编译过程中,如果受检查异常没有被 `catch`/`throw` 处理的话,就没办法通过编译 。比如下面这段 IO 操作的代码。
+
+
+
+除了`RuntimeException`及其子类以外,其他的`Exception`类及其子类都属于受检查异常 。常见的受检查异常有: IO 相关的异常、`ClassNotFoundException` 、`SQLException`...。
+
+**不受检查异常**
+
+Java 代码在编译过程中 ,我们即使不处理不受检查异常也可以正常通过编译。
+
+`RuntimeException` 及其子类都统称为非受检查异常,例如:`NullPointerException`、`NumberFormatException`(字符串转换为数字)、`ArrayIndexOutOfBoundsException`(数组越界)、`ClassCastException`(类型转换错误)、`ArithmeticException`(算术错误)等。
+
+### Throwable 类常用方法
+
+- **`public String getMessage()`**:返回异常发生时的简要描述
+- **`public String toString()`**:返回异常发生时的详细信息
+- **`public String getLocalizedMessage()`**:返回异常对象的本地化信息。使用 `Throwable` 的子类覆盖这个方法,可以生成本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与 `getMessage()`返回的结果相同
+- **`public void printStackTrace()`**:在控制台上打印 `Throwable` 对象封装的异常信息
+
+### try-catch-finally
+
+- **`try`块:** 用于捕获异常。其后可接零个或多个 `catch` 块,如果没有 `catch` 块,则必须跟一个 `finally` 块。
+- **`catch`块:** 用于处理 try 捕获到的异常。
+- **`finally` 块:** 无论是否捕获或处理异常,`finally` 块里的语句都会被执行。当在 `try` 块或 `catch` 块中遇到 `return` 语句时,`finally` 语句块将在方法返回之前被执行。
+
+**在以下 3 种特殊情况下,`finally` 块不会被执行:**
+
+1. 在 `try` 或 `finally`块中用了 `System.exit(int)`退出程序。但是,如果 `System.exit(int)` 在异常语句之后,`finally` 还是会被执行
+2. 程序所在的线程死亡。
+3. 关闭 CPU。
+
+下面这部分内容来自 issue:。
+
+**注意:** 当 try 语句和 finally 语句中都有 return 语句时,在方法返回之前,finally 语句的内容将被执行,并且 finally 语句的返回值将会覆盖原始的返回值。如下:
+
+```java
+public class Test {
+ public static int f(int value) {
+ try {
+ return value * value;
+ } finally {
+ if (value == 2) {
+ return 0;
+ }
+ }
+ }
+}
+```
+
+如果调用 `f(2)`,返回值将是 0,因为 finally 语句的返回值覆盖了 try 语句块的返回值。
+
+### 使用 `try-with-resources` 来代替`try-catch-finally`
+
+1. **适用范围(资源的定义):** 任何实现 `java.lang.AutoCloseable`或者 `java.io.Closeable` 的对象
+2. **关闭资源和 finally 块的执行顺序:** 在 `try-with-resources` 语句中,任何 catch 或 finally 块在声明的资源关闭后运行
+
+《Effecitve Java》中明确指出:
+
+> 面对必须要关闭的资源,我们总是应该优先使用 `try-with-resources` 而不是`try-finally`。随之产生的代码更简短,更清晰,产生的异常对我们也更有用。`try-with-resources`语句让我们更容易编写必须要关闭的资源的代码,若采用`try-finally`则几乎做不到这点。
+
+Java 中类似于`InputStream`、`OutputStream` 、`Scanner` 、`PrintWriter`等的资源都需要我们调用`close()`方法来手动关闭,一般情况下我们都是通过`try-catch-finally`语句来实现这个需求,如下:
+
+```java
+ //读取文本文件的内容
+ Scanner scanner = null;
+ try {
+ scanner = new Scanner(new File("D://read.txt"));
+ while (scanner.hasNext()) {
+ System.out.println(scanner.nextLine());
+ }
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ } finally {
+ if (scanner != null) {
+ scanner.close();
+ }
+ }
+```
+
+使用 Java 7 之后的 `try-with-resources` 语句改造上面的代码:
+
+```java
+try (Scanner scanner = new Scanner(new File("test.txt"))) {
+ while (scanner.hasNext()) {
+ System.out.println(scanner.nextLine());
+ }
+} catch (FileNotFoundException fnfe) {
+ fnfe.printStackTrace();
+}
+```
+
+当然多个资源需要关闭的时候,使用 `try-with-resources` 实现起来也非常简单,如果你还是用`try-catch-finally`可能会带来很多问题。
+
+通过使用分号分隔,可以在`try-with-resources`块中声明多个资源。
+
+```java
+try (BufferedInputStream bin = new BufferedInputStream(new FileInputStream(new File("test.txt")));
+ BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(new File("out.txt")))) {
+ int b;
+ while ((b = bin.read()) != -1) {
+ bout.write(b);
+ }
+ }
+ catch (IOException e) {
+ e.printStackTrace();
+ }
+```
+
+## I/O 流
+
+### 什么是序列化?什么是反序列化?
+
+如果我们需要持久化 Java 对象比如将 Java 对象保存在文件中,或者在网络传输 Java 对象,这些场景都需要用到序列化。
+
+简单来说:
+
+- **序列化**: 将数据结构或对象转换成二进制字节流的过程
+- **反序列化**:将在序列化过程中所生成的二进制字节流转换成数据结构或者对象的过程
+
+对于 Java 这种面向对象编程语言来说,我们序列化的都是对象(Object)也就是实例化后的类(Class),但是在 C++这种半面向对象的语言中,struct(结构体)定义的是数据结构类型,而 class 对应的是对象类型。
+
+维基百科是如是介绍序列化的:
+
+> **序列化**(serialization)在计算机科学的数据处理中,是指将数据结构或对象状态转换成可取用格式(例如存成文件,存于缓冲,或经由网络中发送),以留待后续在相同或另一台计算机环境中,能恢复原先状态的过程。依照序列化格式重新获取字节的结果时,可以利用它来产生与原始对象相同语义的副本。对于许多对象,像是使用大量引用的复杂对象,这种序列化重建的过程并不容易。面向对象中的对象序列化,并不概括之前原始对象所关系的函数。这种过程也称为对象编组(marshalling)。从一系列字节提取数据结构的反向操作,是反序列化(也称为解编组、deserialization、unmarshalling)。
+
+综上:**序列化的主要目的是通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中。**
+
+
+
+https://www.corejavaguru.com/java/serialization/interview-questions-1
+
+### Java 序列化中如果有些字段不想进行序列化,怎么办?
+
+对于不想进行序列化的变量,使用 `transient` 关键字修饰。
+
+`transient` 关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被 `transient` 修饰的变量值不会被持久化和恢复。
+
+关于 `transient` 还有几点注意:
+- `transient` 只能修饰变量,不能修饰类和方法。
+- `transient` 修饰的变量,在反序列化后变量值将会被置成类型的默认值。例如,如果是修饰 `int` 类型,那么反序列后结果就是 `0`。
+- `static` 变量因为不属于任何对象(Object),所以无论有没有 `transient` 关键字修饰,均不会被序列化。
+
+### 获取用键盘输入常用的两种方法
+
+方法 1:通过 `Scanner`
+
+```java
+Scanner input = new Scanner(System.in);
+String s = input.nextLine();
+input.close();
+```
+
+方法 2:通过 `BufferedReader`
+
+```java
+BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
+String s = input.readLine();
+```
+
+### Java 中 IO 流分为几种?
+
+- 按照流的流向分,可以分为输入流和输出流;
+- 按照操作单元划分,可以划分为字节流和字符流;
+- 按照流的角色划分为节点流和处理流。
+
+Java IO 流共涉及 40 多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系, Java IO 流的 40 多个类都是从如下 4 个抽象类基类中派生出来的。
+
+- InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
+- OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
+
+按操作方式分类结构图:
+
+
+
+按操作对象分类结构图:
+
+
+
+### 既然有了字节流,为什么还要有字符流?
+
+问题本质想问:**不管是文件读写还是网络发送接收,信息的最小存储单元都是字节,那为什么 I/O 流操作要分为字节流操作和字符流操作呢?**
+
+回答:字符流是由 Java 虚拟机将字节转换得到的,问题就出在这个过程还算是非常耗时,并且,如果我们不知道编码类型就很容易出现乱码问题。所以, I/O 流就干脆提供了一个直接操作字符的接口,方便我们平时对字符进行流操作。如果音频文件、图片等媒体文件用字节流比较好,如果涉及到字符的话使用字符流比较好。
+
+## 参考
+
+- https://stackoverflow.com/questions/1906445/what-is-the-difference-between-jdk-and-jre
+- https://www.educba.com/oracle-vs-openjdk/
+- https://stackoverflow.com/questions/22358071/differences-between-oracle-jdk-and-openjdk 基础概念与常识
+
diff --git "a/docs/java/basis/\344\273\243\347\220\206\346\250\241\345\274\217\350\257\246\350\247\243.md" "b/docs/java/basis/\344\273\243\347\220\206\346\250\241\345\274\217\350\257\246\350\247\243.md"
new file mode 100644
index 00000000000..0006caed0aa
--- /dev/null
+++ "b/docs/java/basis/\344\273\243\347\220\206\346\250\241\345\274\217\350\257\246\350\247\243.md"
@@ -0,0 +1,404 @@
+---
+title: 代理详解!静态代理+JDK/CGLIB 动态代理实战
+category: Java
+tag:
+ - Java基础
+---
+
+## 1. 代理模式
+
+代理模式是一种比较好理解的设计模式。简单来说就是 **我们使用代理对象来代替对真实对象(real object)的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。**
+
+**代理模式的主要作用是扩展目标对象的功能,比如说在目标对象的某个方法执行前后你可以增加一些自定义的操作。**
+
+举个例子:你找了小红来帮你问话,小红就可以看作是代理你的代理对象,代理的行为(方法)是问话。
+
+
+
+https://medium.com/@mithunsasidharan/understanding-the-proxy-design-pattern-5e63fe38052a
+
+代理模式有静态代理和动态代理两种实现方式,我们 先来看一下静态代理模式的实现。
+
+## 2. 静态代理
+
+**静态代理中,我们对目标对象的每个方法的增强都是手动完成的(_后面会具体演示代码_),非常不灵活(_比如接口一旦新增加方法,目标对象和代理对象都要进行修改_)且麻烦(_需要对每个目标类都单独写一个代理类_)。** 实际应用场景非常非常少,日常开发几乎看不到使用静态代理的场景。
+
+上面我们是从实现和应用角度来说的静态代理,从 JVM 层面来说, **静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。**
+
+静态代理实现步骤:
+
+1. 定义一个接口及其实现类;
+2. 创建一个代理类同样实现这个接口
+3. 将目标对象注入进代理类,然后在代理类的对应方法调用目标类中的对应方法。这样的话,我们就可以通过代理类屏蔽对目标对象的访问,并且可以在目标方法执行前后做一些自己想做的事情。
+
+下面通过代码展示!
+
+**1.定义发送短信的接口**
+
+```java
+public interface SmsService {
+ String send(String message);
+}
+```
+
+**2.实现发送短信的接口**
+
+```java
+public class SmsServiceImpl implements SmsService {
+ public String send(String message) {
+ System.out.println("send message:" + message);
+ return message;
+ }
+}
+```
+
+**3.创建代理类并同样实现发送短信的接口**
+
+```java
+public class SmsProxy implements SmsService {
+
+ private final SmsService smsService;
+
+ public SmsProxy(SmsService smsService) {
+ this.smsService = smsService;
+ }
+
+ @Override
+ public String send(String message) {
+ //调用方法之前,我们可以添加自己的操作
+ System.out.println("before method send()");
+ smsService.send(message);
+ //调用方法之后,我们同样可以添加自己的操作
+ System.out.println("after method send()");
+ return null;
+ }
+}
+```
+
+**4.实际使用**
+
+```java
+public class Main {
+ public static void main(String[] args) {
+ SmsService smsService = new SmsServiceImpl();
+ SmsProxy smsProxy = new SmsProxy(smsService);
+ smsProxy.send("java");
+ }
+}
+```
+
+运行上述代码之后,控制台打印出:
+
+```bash
+before method send()
+send message:java
+after method send()
+```
+
+可以输出结果看出,我们已经增加了 `SmsServiceImpl` 的`send()`方法。
+
+## 3. 动态代理
+
+相比于静态代理来说,动态代理更加灵活。我们不需要针对每个目标类都单独创建一个代理类,并且也不需要我们必须实现接口,我们可以直接代理实现类( _CGLIB 动态代理机制_)。
+
+**从 JVM 角度来说,动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。**
+
+说到动态代理,Spring AOP、RPC 框架应该是两个不得不提的,它们的实现都依赖了动态代理。
+
+**动态代理在我们日常开发中使用的相对较少,但是在框架中的几乎是必用的一门技术。学会了动态代理之后,对于我们理解和学习各种框架的原理也非常有帮助。**
+
+就 Java 来说,动态代理的实现方式有很多种,比如 **JDK 动态代理**、**CGLIB 动态代理**等等。
+
+[guide-rpc-framework](https://github.com/Snailclimb/guide-rpc-framework) 使用的是 JDK 动态代理,我们先来看看 JDK 动态代理的使用。
+
+另外,虽然 [guide-rpc-framework](https://github.com/Snailclimb/guide-rpc-framework) 没有用到 **CGLIB 动态代理** ,我们这里还是简单介绍一下其使用以及和**JDK 动态代理**的对比。
+
+### 3.1. JDK 动态代理机制
+
+#### 3.1.1. 介绍
+
+**在 Java 动态代理机制中 `InvocationHandler` 接口和 `Proxy` 类是核心。**
+
+`Proxy` 类中使用频率最高的方法是:`newProxyInstance()` ,这个方法主要用来生成一个代理对象。
+
+```java
+ public static Object newProxyInstance(ClassLoader loader,
+ Class>[] interfaces,
+ InvocationHandler h)
+ throws IllegalArgumentException
+ {
+ ......
+ }
+```
+
+这个方法一共有 3 个参数:
+
+1. **loader** :类加载器,用于加载代理对象。
+2. **interfaces** : 被代理类实现的一些接口;
+3. **h** : 实现了 `InvocationHandler` 接口的对象;
+
+要实现动态代理的话,还必须需要实现`InvocationHandler` 来自定义处理逻辑。 当我们的动态代理对象调用一个方法时,这个方法的调用就会被转发到实现`InvocationHandler` 接口类的 `invoke` 方法来调用。
+
+```java
+public interface InvocationHandler {
+
+ /**
+ * 当你使用代理对象调用方法的时候实际会调用到这个方法
+ */
+ public Object invoke(Object proxy, Method method, Object[] args)
+ throws Throwable;
+}
+```
+
+`invoke()` 方法有下面三个参数:
+
+1. **proxy** :动态生成的代理类
+2. **method** : 与代理类对象调用的方法相对应
+3. **args** : 当前 method 方法的参数
+
+也就是说:**你通过`Proxy` 类的 `newProxyInstance()` 创建的代理对象在调用方法的时候,实际会调用到实现`InvocationHandler` 接口的类的 `invoke()`方法。** 你可以在 `invoke()` 方法中自定义处理逻辑,比如在方法执行前后做什么事情。
+
+#### 3.1.2. JDK 动态代理类使用步骤
+
+1. 定义一个接口及其实现类;
+2. 自定义 `InvocationHandler` 并重写`invoke`方法,在 `invoke` 方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑;
+3. 通过 `Proxy.newProxyInstance(ClassLoader loader,Class>[] interfaces,InvocationHandler h)` 方法创建代理对象;
+
+#### 3.1.3. 代码示例
+
+这样说可能会有点空洞和难以理解,我上个例子,大家感受一下吧!
+
+**1.定义发送短信的接口**
+
+```java
+public interface SmsService {
+ String send(String message);
+}
+```
+
+**2.实现发送短信的接口**
+
+```java
+public class SmsServiceImpl implements SmsService {
+ public String send(String message) {
+ System.out.println("send message:" + message);
+ return message;
+ }
+}
+```
+
+**3.定义一个 JDK 动态代理类**
+
+```java
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * @author shuang.kou
+ * @createTime 2020年05月11日 11:23:00
+ */
+public class DebugInvocationHandler implements InvocationHandler {
+ /**
+ * 代理类中的真实对象
+ */
+ private final Object target;
+
+ public DebugInvocationHandler(Object target) {
+ this.target = target;
+ }
+
+
+ public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
+ //调用方法之前,我们可以添加自己的操作
+ System.out.println("before method " + method.getName());
+ Object result = method.invoke(target, args);
+ //调用方法之后,我们同样可以添加自己的操作
+ System.out.println("after method " + method.getName());
+ return result;
+ }
+}
+
+```
+
+`invoke()` 方法: 当我们的动态代理对象调用原生方法的时候,最终实际上调用到的是 `invoke()` 方法,然后 `invoke()` 方法代替我们去调用了被代理对象的原生方法。
+
+**4.获取代理对象的工厂类**
+
+```java
+public class JdkProxyFactory {
+ public static Object getProxy(Object target) {
+ return Proxy.newProxyInstance(
+ target.getClass().getClassLoader(), // 目标类的类加载
+ target.getClass().getInterfaces(), // 代理需要实现的接口,可指定多个
+ new DebugInvocationHandler(target) // 代理对象对应的自定义 InvocationHandler
+ );
+ }
+}
+```
+
+`getProxy()` :主要通过`Proxy.newProxyInstance()`方法获取某个类的代理对象
+
+**5.实际使用**
+
+```java
+SmsService smsService = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl());
+smsService.send("java");
+```
+
+运行上述代码之后,控制台打印出:
+
+```
+before method send
+send message:java
+after method send
+```
+
+### 3.2. CGLIB 动态代理机制
+
+#### 3.2.1. 介绍
+
+**JDK 动态代理有一个最致命的问题是其只能代理实现了接口的类。**
+
+**为了解决这个问题,我们可以用 CGLIB 动态代理机制来避免。**
+
+[CGLIB](https://github.com/cglib/cglib)(_Code Generation Library_)是一个基于[ASM](http://www.baeldung.com/java-asm)的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB 通过继承方式实现代理。很多知名的开源框架都使用到了[CGLIB](https://github.com/cglib/cglib), 例如 Spring 中的 AOP 模块中:如果目标对象实现了接口,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理。
+
+**在 CGLIB 动态代理机制中 `MethodInterceptor` 接口和 `Enhancer` 类是核心。**
+
+你需要自定义 `MethodInterceptor` 并重写 `intercept` 方法,`intercept` 用于拦截增强被代理类的方法。
+
+```java
+public interface MethodInterceptor
+extends Callback{
+ // 拦截被代理类中的方法
+ public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,
+ MethodProxy proxy) throws Throwable;
+}
+
+```
+
+1. **obj** :被代理的对象(需要增强的对象)
+2. **method** :被拦截的方法(需要增强的方法)
+3. **args** :方法入参
+4. **proxy** :用于调用原始方法
+
+你可以通过 `Enhancer`类来动态获取被代理类,当代理类调用方法的时候,实际调用的是 `MethodInterceptor` 中的 `intercept` 方法。
+
+#### 3.2.2. CGLIB 动态代理类使用步骤
+
+1. 定义一个类;
+2. 自定义 `MethodInterceptor` 并重写 `intercept` 方法,`intercept` 用于拦截增强被代理类的方法,和 JDK 动态代理中的 `invoke` 方法类似;
+3. 通过 `Enhancer` 类的 `create()`创建代理类;
+
+#### 3.2.3. 代码示例
+
+不同于 JDK 动态代理不需要额外的依赖。[CGLIB](https://github.com/cglib/cglib)(_Code Generation Library_) 实际是属于一个开源项目,如果你要使用它的话,需要手动添加相关依赖。
+
+```xml
+
+ cglib
+ cglib
+ 3.3.0
+
+```
+
+**1.实现一个使用阿里云发送短信的类**
+
+```java
+package github.javaguide.dynamicProxy.cglibDynamicProxy;
+
+public class AliSmsService {
+ public String send(String message) {
+ System.out.println("send message:" + message);
+ return message;
+ }
+}
+```
+
+**2.自定义 `MethodInterceptor`(方法拦截器)**
+
+```java
+import net.sf.cglib.proxy.MethodInterceptor;
+import net.sf.cglib.proxy.MethodProxy;
+
+import java.lang.reflect.Method;
+
+/**
+ * 自定义MethodInterceptor
+ */
+public class DebugMethodInterceptor implements MethodInterceptor {
+
+
+ /**
+ * @param o 代理对象(增强的对象)
+ * @param method 被拦截的方法(需要增强的方法)
+ * @param args 方法入参
+ * @param methodProxy 用于调用原始方法
+ */
+ @Override
+ public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
+ //调用方法之前,我们可以添加自己的操作
+ System.out.println("before method " + method.getName());
+ Object object = methodProxy.invokeSuper(o, args);
+ //调用方法之后,我们同样可以添加自己的操作
+ System.out.println("after method " + method.getName());
+ return object;
+ }
+
+}
+```
+
+**3.获取代理类**
+
+```java
+import net.sf.cglib.proxy.Enhancer;
+
+public class CglibProxyFactory {
+
+ public static Object getProxy(Class> clazz) {
+ // 创建动态代理增强类
+ Enhancer enhancer = new Enhancer();
+ // 设置类加载器
+ enhancer.setClassLoader(clazz.getClassLoader());
+ // 设置被代理类
+ enhancer.setSuperclass(clazz);
+ // 设置方法拦截器
+ enhancer.setCallback(new DebugMethodInterceptor());
+ // 创建代理类
+ return enhancer.create();
+ }
+}
+```
+
+**4.实际使用**
+
+```java
+AliSmsService aliSmsService = (AliSmsService) CglibProxyFactory.getProxy(AliSmsService.class);
+aliSmsService.send("java");
+```
+
+运行上述代码之后,控制台打印出:
+
+```bash
+before method send
+send message:java
+after method send
+```
+
+### 3.3. JDK 动态代理和 CGLIB 动态代理对比
+
+1. **JDK 动态代理只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代理未实现任何接口的类。** 另外, CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。
+2. 就二者的效率来说,大部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显。
+
+## 4. 静态代理和动态代理的对比
+
+1. **灵活性** :动态代理更加灵活,不需要必须实现接口,可以直接代理实现类,并且可以不需要针对每个目标类都创建一个代理类。另外,静态代理中,接口一旦新增加方法,目标对象和代理对象都要进行修改,这是非常麻烦的!
+2. **JVM 层面** :静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。而动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。
+
+## 5. 总结
+
+这篇文章中主要介绍了代理模式的两种实现:静态代理以及动态代理。涵盖了静态代理和动态代理实战、静态代理和动态代理的区别、JDK 动态代理和 Cglib 动态代理区别等内容。
+
+文中涉及到的所有源码,你可以在这里找到:[https://github.com/Snailclimb/guide-rpc-framework-learning/tree/master/src/main/java/github/javaguide/proxy](https://github.com/Snailclimb/guide-rpc-framework-learning/tree/master/src/main/java/github/javaguide/proxy) 。
+
diff --git "a/docs/java/basis/\345\217\215\345\260\204\346\234\272\345\210\266\350\257\246\350\247\243.md" "b/docs/java/basis/\345\217\215\345\260\204\346\234\272\345\210\266\350\257\246\350\247\243.md"
new file mode 100644
index 00000000000..cac029638e4
--- /dev/null
+++ "b/docs/java/basis/\345\217\215\345\260\204\346\234\272\345\210\266\350\257\246\350\247\243.md"
@@ -0,0 +1,182 @@
+---
+title: 反射机制详解!
+category: Java
+tag:
+ - Java基础
+---
+
+## 何为反射?
+
+如果说大家研究过框架的底层原理或者咱们自己写过框架的话,一定对反射这个概念不陌生。
+
+反射之所以被称为框架的灵魂,主要是因为它赋予了我们在运行时分析类以及执行类中方法的能力。
+
+通过反射你可以获取任意一个类的所有属性和方法,你还可以调用这些方法和属性。
+
+## 反射的应用场景了解么?
+
+像咱们平时大部分时候都是在写业务代码,很少会接触到直接使用反射机制的场景。
+
+但是,这并不代表反射没有用。相反,正是因为反射,你才能这么轻松地使用各种框架。像 Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射机制。
+
+**这些框架中也大量使用了动态代理,而动态代理的实现也依赖反射。**
+
+比如下面是通过 JDK 实现动态代理的示例代码,其中就使用了反射类 `Method` 来调用指定的方法。
+
+```java
+public class DebugInvocationHandler implements InvocationHandler {
+ /**
+ * 代理类中的真实对象
+ */
+ private final Object target;
+
+ public DebugInvocationHandler(Object target) {
+ this.target = target;
+ }
+
+
+ public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
+ System.out.println("before method " + method.getName());
+ Object result = method.invoke(target, args);
+ System.out.println("after method " + method.getName());
+ return result;
+ }
+}
+
+```
+
+另外,像 Java 中的一大利器 **注解** 的实现也用到了反射。
+
+为什么你使用 Spring 的时候 ,一个`@Component`注解就声明了一个类为 Spring Bean 呢?为什么你通过一个 `@Value`注解就读取到配置文件中的值呢?究竟是怎么起作用的呢?
+
+这些都是因为你可以基于反射分析类,然后获取到类/属性/方法/方法的参数上的注解。你获取到注解之后,就可以做进一步的处理。
+
+## 谈谈反射机制的优缺点
+
+**优点** : 可以让咱们的代码更加灵活、为各种框架提供开箱即用的功能提供了便利
+
+**缺点** :让我们在运行时有了分析操作类的能力,这同样也增加了安全问题。比如可以无视泛型参数的安全检查(泛型参数的安全检查发生在编译时)。另外,反射的性能也要稍差点,不过,对于框架来说实际是影响不大的。相关阅读:[Java Reflection: Why is it so slow?](https://stackoverflow.com/questions/1392351/java-reflection-why-is-it-so-slow)
+
+## 反射实战
+
+### 获取 Class 对象的四种方式
+
+如果我们动态获取到这些信息,我们需要依靠 Class 对象。Class 类对象将一个类的方法、变量等信息告诉运行的程序。Java 提供了四种方式获取 Class 对象:
+
+**1.知道具体类的情况下可以使用:**
+
+```java
+Class alunbarClass = TargetObject.class;
+```
+
+但是我们一般是不知道具体类的,基本都是通过遍历包下面的类来获取 Class 对象,通过此方式获取 Class 对象不会进行初始化
+
+**2.通过 `Class.forName()`传入类的路径获取:**
+
+```java
+Class alunbarClass1 = Class.forName("cn.javaguide.TargetObject");
+```
+
+**3.通过对象实例`instance.getClass()`获取:**
+
+```java
+TargetObject o = new TargetObject();
+Class alunbarClass2 = o.getClass();
+```
+
+**4.通过类加载器`xxxClassLoader.loadClass()`传入类路径获取:**
+
+```java
+Class clazz = ClassLoader.loadClass("cn.javaguide.TargetObject");
+```
+
+通过类加载器获取 Class 对象不会进行初始化,意味着不进行包括初始化等一些列步骤,静态块和静态对象不会得到执行
+
+### 反射的一些基本操作
+
+1.创建一个我们要使用反射操作的类 `TargetObject`。
+
+```java
+package cn.javaguide;
+
+public class TargetObject {
+ private String value;
+
+ public TargetObject() {
+ value = "JavaGuide";
+ }
+
+ public void publicMethod(String s) {
+ System.out.println("I love " + s);
+ }
+
+ private void privateMethod() {
+ System.out.println("value is " + value);
+ }
+}
+```
+
+2.使用反射操作这个类的方法以及参数
+
+```java
+package cn.javaguide;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+public class Main {
+ public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchFieldException {
+ /**
+ * 获取TargetObject类的Class对象并且创建TargetObject类实例
+ */
+ Class> tagetClass = Class.forName("cn.javaguide.TargetObject");
+ TargetObject targetObject = (TargetObject) tagetClass.newInstance();
+ /**
+ * 获取所有类中所有定义的方法
+ */
+ Method[] methods = tagetClass.getDeclaredMethods();
+ for (Method method : methods) {
+ System.out.println(method.getName());
+ }
+ /**
+ * 获取指定方法并调用
+ */
+ Method publicMethod = tagetClass.getDeclaredMethod("publicMethod",
+ String.class);
+
+ publicMethod.invoke(targetObject, "JavaGuide");
+ /**
+ * 获取指定参数并对参数进行修改
+ */
+ Field field = tagetClass.getDeclaredField("value");
+ //为了对类中的参数进行修改我们取消安全检查
+ field.setAccessible(true);
+ field.set(targetObject, "JavaGuide");
+ /**
+ * 调用 private 方法
+ */
+ Method privateMethod = tagetClass.getDeclaredMethod("privateMethod");
+ //为了调用private方法我们取消安全检查
+ privateMethod.setAccessible(true);
+ privateMethod.invoke(targetObject);
+ }
+}
+
+```
+
+输出内容:
+
+```
+publicMethod
+privateMethod
+I love JavaGuide
+value is JavaGuide
+```
+
+**注意** : 有读者提到上面代码运行会抛出 `ClassNotFoundException` 异常,具体原因是你没有下面把这段代码的包名替换成自己创建的 `TargetObject` 所在的包 。
+
+```java
+Class> tagetClass = Class.forName("cn.javaguide.TargetObject");
+```
+
diff --git a/docs/java/collection/ArrayList-Grow.md b/docs/java/collection/ArrayList-Grow.md
deleted file mode 100644
index 06fa5388d76..00000000000
--- a/docs/java/collection/ArrayList-Grow.md
+++ /dev/null
@@ -1,343 +0,0 @@
-
-## 一 先从 ArrayList 的构造函数说起
-
-**ArrayList有三种方式来初始化,构造方法源码如下:**
-
-```java
- /**
- * 默认初始容量大小
- */
- private static final int DEFAULT_CAPACITY = 10;
-
-
- private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
-
- /**
- *默认构造函数,使用初始容量10构造一个空列表(无参数构造)
- */
- public ArrayList() {
- this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
- }
-
- /**
- * 带初始容量参数的构造函数。(用户自己指定容量)
- */
- public ArrayList(int initialCapacity) {
- if (initialCapacity > 0) {//初始容量大于0
- //创建initialCapacity大小的数组
- this.elementData = new Object[initialCapacity];
- } else if (initialCapacity == 0) {//初始容量等于0
- //创建空数组
- this.elementData = EMPTY_ELEMENTDATA;
- } else {//初始容量小于0,抛出异常
- throw new IllegalArgumentException("Illegal Capacity: "+
- initialCapacity);
- }
- }
-
-
- /**
- *构造包含指定collection元素的列表,这些元素利用该集合的迭代器按顺序返回
- *如果指定的集合为null,throws NullPointerException。
- */
- public ArrayList(Collection extends E> c) {
- elementData = c.toArray();
- if ((size = elementData.length) != 0) {
- // c.toArray might (incorrectly) not return Object[] (see 6260652)
- if (elementData.getClass() != Object[].class)
- elementData = Arrays.copyOf(elementData, size, Object[].class);
- } else {
- // replace with empty array.
- this.elementData = EMPTY_ELEMENTDATA;
- }
- }
-
-```
-
-细心的同学一定会发现 :**以无参数构造方法创建 ArrayList 时,实际上初始化赋值的是一个空数组。当真正对数组进行添加元素操作时,才真正分配容量。即向数组中添加第一个元素时,数组容量扩为10。** 下面在我们分析 ArrayList 扩容时会讲到这一点内容!
-
-## 二 一步一步分析 ArrayList 扩容机制
-
-这里以无参构造函数创建的 ArrayList 为例分析
-
-### 1. 先来看 `add` 方法
-
-```java
- /**
- * 将指定的元素追加到此列表的末尾。
- */
- public boolean add(E e) {
- //添加元素之前,先调用ensureCapacityInternal方法
- ensureCapacityInternal(size + 1); // Increments modCount!!
- //这里看到ArrayList添加元素的实质就相当于为数组赋值
- elementData[size++] = e;
- return true;
- }
-```
-### 2. 再来看看 `ensureCapacityInternal()` 方法
-
-可以看到 `add` 方法 首先调用了`ensureCapacityInternal(size + 1)`
-
-```java
- //得到最小扩容量
- private void ensureCapacityInternal(int minCapacity) {
- if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
- // 获取默认的容量和传入参数的较大值
- minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
- }
-
- ensureExplicitCapacity(minCapacity);
- }
-```
-**当 要 add 进第1个元素时,minCapacity为1,在Math.max()方法比较后,minCapacity 为10。**
-
-### 3. `ensureExplicitCapacity()` 方法
-
-如果调用 `ensureCapacityInternal()` 方法就一定会进过(执行)这个方法,下面我们来研究一下这个方法的源码!
-
-```java
- //判断是否需要扩容
- private void ensureExplicitCapacity(int minCapacity) {
- modCount++;
-
- // overflow-conscious code
- if (minCapacity - elementData.length > 0)
- //调用grow方法进行扩容,调用此方法代表已经开始扩容了
- grow(minCapacity);
- }
-
-```
-
-我们来仔细分析一下:
-
-- 当我们要 add 进第1个元素到 ArrayList 时,elementData.length 为0 (因为还是一个空的 list),因为执行了 `ensureCapacityInternal()` 方法 ,所以 minCapacity 此时为10。此时,`minCapacity - elementData.length > 0 `成立,所以会进入 `grow(minCapacity)` 方法。
-- 当add第2个元素时,minCapacity 为2,此时e lementData.length(容量)在添加第一个元素后扩容成 10 了。此时,`minCapacity - elementData.length > 0 ` 不成立,所以不会进入 (执行)`grow(minCapacity)` 方法。
-- 添加第3、4···到第10个元素时,依然不会执行grow方法,数组容量都为10。
-
-直到添加第11个元素,minCapacity(为11)比elementData.length(为10)要大。进入grow方法进行扩容。
-
-### 4. `grow()` 方法
-
-```java
- /**
- * 要分配的最大数组大小
- */
- private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
-
- /**
- * ArrayList扩容的核心方法。
- */
- private void grow(int minCapacity) {
- // oldCapacity为旧容量,newCapacity为新容量
- int oldCapacity = elementData.length;
- //将oldCapacity 右移一位,其效果相当于oldCapacity /2,
- //我们知道位运算的速度远远快于整除运算,整句运算式的结果就是将新容量更新为旧容量的1.5倍,
- int newCapacity = oldCapacity + (oldCapacity >> 1);
- //然后检查新容量是否大于最小需要容量,若还是小于最小需要容量,那么就把最小需要容量当作数组的新容量,
- if (newCapacity - minCapacity < 0)
- newCapacity = minCapacity;
- // 如果新容量大于 MAX_ARRAY_SIZE,进入(执行) `hugeCapacity()` 方法来比较 minCapacity 和 MAX_ARRAY_SIZE,
- //如果minCapacity大于最大容量,则新容量则为`Integer.MAX_VALUE`,否则,新容量大小则为 MAX_ARRAY_SIZE 即为 `Integer.MAX_VALUE - 8`。
- if (newCapacity - MAX_ARRAY_SIZE > 0)
- newCapacity = hugeCapacity(minCapacity);
- // minCapacity is usually close to size, so this is a win:
- elementData = Arrays.copyOf(elementData, newCapacity);
- }
-```
-
-**int newCapacity = oldCapacity + (oldCapacity >> 1),所以 ArrayList 每次扩容之后容量都会变为原来的 1.5 倍!(JDK1.6版本以后)** JDk1.6版本时,扩容之后容量为 1.5 倍+1!详情请参考源码
-
-> ">>"(移位运算符):>>1 右移一位相当于除2,右移n位相当于除以 2 的 n 次方。这里 oldCapacity 明显右移了1位所以相当于oldCapacity /2。对于大数据的2进制运算,位移运算符比那些普通运算符的运算要快很多,因为程序仅仅移动一下而已,不去计算,这样提高了效率,节省了资源
-
-**我们再来通过例子探究一下`grow()` 方法 :**
-
-- 当add第1个元素时,oldCapacity 为0,经比较后第一个if判断成立,newCapacity = minCapacity(为10)。但是第二个if判断不会成立,即newCapacity 不比 MAX_ARRAY_SIZE大,则不会进入 `hugeCapacity` 方法。数组容量为10,add方法中 return true,size增为1。
-- 当add第11个元素进入grow方法时,newCapacity为15,比minCapacity(为11)大,第一个if判断不成立。新容量没有大于数组最大size,不会进入hugeCapacity方法。数组容量扩为15,add方法中return true,size增为11。
-- 以此类推······
-
-**这里补充一点比较重要,但是容易被忽视掉的知识点:**
-
-- java 中的 `length `属性是针对数组说的,比如说你声明了一个数组,想知道这个数组的长度则用到了 length 这个属性.
-- java 中的 `length()` 方法是针对字符串说的,如果想看这个字符串的长度则用到 `length()` 这个方法.
-- java 中的 `size()` 方法是针对泛型集合说的,如果想看这个泛型有多少个元素,就调用此方法来查看!
-
-### 5. `hugeCapacity()` 方法。
-
-从上面 `grow()` 方法源码我们知道: 如果新容量大于 MAX_ARRAY_SIZE,进入(执行) `hugeCapacity()` 方法来比较 minCapacity 和 MAX_ARRAY_SIZE,如果minCapacity大于最大容量,则新容量则为`Integer.MAX_VALUE`,否则,新容量大小则为 MAX_ARRAY_SIZE 即为 `Integer.MAX_VALUE - 8`。
-
-
-```java
- private static int hugeCapacity(int minCapacity) {
- if (minCapacity < 0) // overflow
- throw new OutOfMemoryError();
- //对minCapacity和MAX_ARRAY_SIZE进行比较
- //若minCapacity大,将Integer.MAX_VALUE作为新数组的大小
- //若MAX_ARRAY_SIZE大,将MAX_ARRAY_SIZE作为新数组的大小
- //MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
- return (minCapacity > MAX_ARRAY_SIZE) ?
- Integer.MAX_VALUE :
- MAX_ARRAY_SIZE;
- }
-```
-
-
-
-## 三 `System.arraycopy()` 和 `Arrays.copyOf()`方法
-
-
-阅读源码的话,我们就会发现 ArrayList 中大量调用了这两个方法。比如:我们上面讲的扩容操作以及`add(int index, E element)`、`toArray()` 等方法中都用到了该方法!
-
-
-### 3.1 `System.arraycopy()` 方法
-
-```java
- /**
- * 在此列表中的指定位置插入指定的元素。
- *先调用 rangeCheckForAdd 对index进行界限检查;然后调用 ensureCapacityInternal 方法保证capacity足够大;
- *再将从index开始之后的所有成员后移一个位置;将element插入index位置;最后size加1。
- */
- public void add(int index, E element) {
- rangeCheckForAdd(index);
-
- ensureCapacityInternal(size + 1); // Increments modCount!!
- //arraycopy()方法实现数组自己复制自己
- //elementData:源数组;index:源数组中的起始位置;elementData:目标数组;index + 1:目标数组中的起始位置; size - index:要复制的数组元素的数量;
- System.arraycopy(elementData, index, elementData, index + 1, size - index);
- elementData[index] = element;
- size++;
- }
-```
-
-我们写一个简单的方法测试以下:
-
-```java
-public class ArraycopyTest {
-
- public static void main(String[] args) {
- // TODO Auto-generated method stub
- int[] a = new int[10];
- a[0] = 0;
- a[1] = 1;
- a[2] = 2;
- a[3] = 3;
- System.arraycopy(a, 2, a, 3, 3);
- a[2]=99;
- for (int i = 0; i < a.length; i++) {
- System.out.println(a[i]);
- }
- }
-
-}
-```
-
-结果:
-
-```
-0 1 99 2 3 0 0 0 0 0
-```
-
-### 3.2 `Arrays.copyOf()`方法
-
-```java
- /**
- 以正确的顺序返回一个包含此列表中所有元素的数组(从第一个到最后一个元素); 返回的数组的运行时类型是指定数组的运行时类型。
- */
- public Object[] toArray() {
- //elementData:要复制的数组;size:要复制的长度
- return Arrays.copyOf(elementData, size);
- }
-```
-
-个人觉得使用 `Arrays.copyOf()`方法主要是为了给原有数组扩容,测试代码如下:
-
-```java
-public class ArrayscopyOfTest {
-
- public static void main(String[] args) {
- int[] a = new int[3];
- a[0] = 0;
- a[1] = 1;
- a[2] = 2;
- int[] b = Arrays.copyOf(a, 10);
- System.out.println("b.length"+b.length);
- }
-}
-```
-
-结果:
-
-```
-10
-```
-
-### 3.3 两者联系和区别
-
-**联系:**
-
-看两者源代码可以发现 copyOf() 内部实际调用了 `System.arraycopy()` 方法
-
-**区别:**
-
-`arraycopy()` 需要目标数组,将原数组拷贝到你自己定义的数组里或者原数组,而且可以选择拷贝的起点和长度以及放入新数组中的位置 `copyOf()` 是系统自动在内部新建一个数组,并返回该数组。
-
-## 四 `ensureCapacity`方法
-
-ArrayList 源码中有一个 `ensureCapacity` 方法不知道大家注意到没有,这个方法 ArrayList 内部没有被调用过,所以很显然是提供给用户调用的,那么这个方法有什么作用呢?
-
-```java
- /**
- 如有必要,增加此 ArrayList 实例的容量,以确保它至少可以容纳由minimum capacity参数指定的元素数。
- *
- * @param minCapacity 所需的最小容量
- */
- public void ensureCapacity(int minCapacity) {
- int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
- // any size if not default element table
- ? 0
- // larger than default for default empty table. It's already
- // supposed to be at default size.
- : DEFAULT_CAPACITY;
-
- if (minCapacity > minExpand) {
- ensureExplicitCapacity(minCapacity);
- }
- }
-
-```
-
-**最好在 add 大量元素之前用 `ensureCapacity` 方法,以减少增量重新分配的次数**
-
-我们通过下面的代码实际测试以下这个方法的效果:
-
-```java
-public class EnsureCapacityTest {
- public static void main(String[] args) {
- ArrayList list = new ArrayList();
- final int N = 10000000;
- long startTime = System.currentTimeMillis();
- for (int i = 0; i < N; i++) {
- list.add(i);
- }
- long endTime = System.currentTimeMillis();
- System.out.println("使用ensureCapacity方法前:"+(endTime - startTime));
-
- list = new ArrayList();
- long startTime1 = System.currentTimeMillis();
- list.ensureCapacity(N);
- for (int i = 0; i < N; i++) {
- list.add(i);
- }
- long endTime1 = System.currentTimeMillis();
- System.out.println("使用ensureCapacity方法后:"+(endTime1 - startTime1));
- }
-}
-```
-
-运行结果:
-
-```
-使用ensureCapacity方法前:4637
-使用ensureCapacity方法后:241
-```
-
-通过运行结果,我们可以很明显的看出向 ArrayList 添加大量元素之前最好先使用`ensureCapacity` 方法,以减少增量重新分配的次数
diff --git "a/docs/java/collection/Java\351\233\206\345\220\210\346\241\206\346\236\266\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230.md" "b/docs/java/collection/Java\351\233\206\345\220\210\346\241\206\346\236\266\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230.md"
deleted file mode 100644
index c5280d539e5..00000000000
--- "a/docs/java/collection/Java\351\233\206\345\220\210\346\241\206\346\236\266\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230.md"
+++ /dev/null
@@ -1,456 +0,0 @@
-点击关注[公众号](#公众号)及时获取笔主最新更新文章,并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。
-
-
-
-- [剖析面试最常见问题之Java集合框架](#剖析面试最常见问题之java集合框架)
- - [说说List,Set,Map三者的区别?](#说说listsetmap三者的区别)
- - [Arraylist 与 LinkedList 区别?](#arraylist-与-linkedlist-区别)
- - [补充内容:RandomAccess接口](#补充内容randomaccess接口)
- - [补充内容:双向链表和双向循环链表](#补充内容双向链表和双向循环链表)
- - [ArrayList 与 Vector 区别呢?为什么要用Arraylist取代Vector呢?](#arraylist-与-vector-区别呢为什么要用arraylist取代vector呢)
- - [说一说 ArrayList 的扩容机制吧](#说一说-arraylist-的扩容机制吧)
- - [HashMap 和 Hashtable 的区别](#hashmap-和-hashtable-的区别)
- - [HashMap 和 HashSet区别](#hashmap-和-hashset区别)
- - [HashSet如何检查重复](#hashset如何检查重复)
- - [HashMap的底层实现](#hashmap的底层实现)
- - [JDK1.8之前](#jdk18之前)
- - [JDK1.8之后](#jdk18之后)
- - [HashMap 的长度为什么是2的幂次方](#hashmap-的长度为什么是2的幂次方)
- - [HashMap 多线程操作导致死循环问题](#hashmap-多线程操作导致死循环问题)
- - [ConcurrentHashMap 和 Hashtable 的区别](#concurrenthashmap-和-hashtable-的区别)
- - [ConcurrentHashMap线程安全的具体实现方式/底层具体实现](#concurrenthashmap线程安全的具体实现方式底层具体实现)
- - [JDK1.7(上面有示意图)](#jdk17上面有示意图)
- - [JDK1.8 (上面有示意图)](#jdk18-上面有示意图)
- - [comparable 和 Comparator的区别](#comparable-和-comparator的区别)
- - [Comparator定制排序](#comparator定制排序)
- - [重写compareTo方法实现按年龄来排序](#重写compareto方法实现按年龄来排序)
- - [集合框架底层数据结构总结](#集合框架底层数据结构总结)
- - [Collection](#collection)
- - [1. List](#1-list)
- - [2. Set](#2-set)
- - [Map](#map)
- - [如何选用集合?](#如何选用集合)
-
-
-
-# 剖析面试最常见问题之Java集合框架
-
-## 说说List,Set,Map三者的区别?
-
-- **List(对付顺序的好帮手):** List接口存储一组不唯一(可以有多个元素引用相同的对象),有序的对象
-- **Set(注重独一无二的性质):** 不允许重复的集合。不会有多个元素引用相同的对象。
-- **Map(用Key来搜索的专家):** 使用键值对存储。Map会维护与Key有关联的值。两个Key可以引用相同的对象,但Key不能重复,典型的Key是String类型,但也可以是任何对象。
-
-## Arraylist 与 LinkedList 区别?
-
-- **1. 是否保证线程安全:** `ArrayList` 和 `LinkedList` 都是不同步的,也就是不保证线程安全;
-
-- **2. 底层数据结构:** `Arraylist` 底层使用的是 **`Object` 数组**;`LinkedList` 底层使用的是 **双向链表** 数据结构(JDK1.6之前为循环链表,JDK1.7取消了循环。注意双向链表和双向循环链表的区别,下面有介绍到!)
-
-- **3. 插入和删除是否受元素位置的影响:** ① **`ArrayList` 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。** 比如:执行`add(E e) `方法的时候, `ArrayList` 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是O(1)。但是如果要在指定位置 i 插入和删除元素的话(`add(int index, E element) `)时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。 ② **`LinkedList` 采用链表存储,所以对于`add(E e)`方法的插入,删除元素时间复杂度不受元素位置的影响,近似 O(1),如果是要在指定位置`i`插入和删除元素的话(`(add(int index, E element)`) 时间复杂度近似为`o(n))`因为需要先移动到指定位置再插入。**
-
-- **4. 是否支持快速随机访问:** `LinkedList` 不支持高效的随机元素访问,而 `ArrayList` 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于`get(int index) `方法)。
-
-- **5. 内存空间占用:** ArrayList的空 间浪费主要体现在在list列表的结尾会预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间(因为要存放直接后继和直接前驱以及数据)。
-
-### **补充内容:RandomAccess接口**
-
-```java
-public interface RandomAccess {
-}
-```
-
-查看源码我们发现实际上 `RandomAccess` 接口中什么都没有定义。所以,在我看来 `RandomAccess` 接口不过是一个标识罢了。标识什么? 标识实现这个接口的类具有随机访问功能。
-
-在 `binarySearch(`)方法中,它要判断传入的list 是否 `RamdomAccess` 的实例,如果是,调用`indexedBinarySearch()`方法,如果不是,那么调用`iteratorBinarySearch()`方法
-
-```java
- public static
- int binarySearch(List extends Comparable super T>> list, T key) {
- if (list instanceof RandomAccess || list.size() MAXIMUM_CAPACITY)
- initialCapacity = MAXIMUM_CAPACITY;
- if (loadFactor <= 0 || Float.isNaN(loadFactor))
- throw new IllegalArgumentException("Illegal load factor: " +
- loadFactor);
- this.loadFactor = loadFactor;
- this.threshold = tableSizeFor(initialCapacity);
- }
- public HashMap(int initialCapacity) {
- this(initialCapacity, DEFAULT_LOAD_FACTOR);
- }
-```
-
-下面这个方法保证了 HashMap 总是使用2的幂作为哈希表的大小。
-
-```java
- /**
- * Returns a power of two size for the given target capacity.
- */
- static final int tableSizeFor(int cap) {
- int n = cap - 1;
- n |= n >>> 1;
- n |= n >>> 2;
- n |= n >>> 4;
- n |= n >>> 8;
- n |= n >>> 16;
- return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
- }
-```
-
-## HashMap 和 HashSet区别
-
-如果你看过 `HashSet` 源码的话就应该知道:HashSet 底层就是基于 HashMap 实现的。(HashSet 的源码非常非常少,因为除了 `clone() `、`writeObject()`、`readObject()`是 HashSet 自己不得不实现之外,其他方法都是直接调用 HashMap 中的方法。
-
-| HashMap | HashSet |
-| :------------------------------: | :----------------------------------------------------------: |
-| 实现了Map接口 | 实现Set接口 |
-| 存储键值对 | 仅存储对象 |
-| 调用 `put()`向map中添加元素 | 调用 `add()`方法向Set中添加元素 |
-| HashMap使用键(Key)计算Hashcode | HashSet使用成员对象来计算hashcode值,对于两个对象来说hashcode可能相同,所以equals()方法用来判断对象的相等性, |
-
-## HashSet如何检查重复
-
-当你把对象加入`HashSet`时,HashSet会先计算对象的`hashcode`值来判断对象加入的位置,同时也会与其他加入的对象的hashcode值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同hashcode值的对象,这时会调用`equals()`方法来检查hashcode相等的对象是否真的相同。如果两者相同,HashSet就不会让加入操作成功。(摘自我的Java启蒙书《Head fist java》第二版)
-
-**hashCode()与equals()的相关规定:**
-
-1. 如果两个对象相等,则hashcode一定也是相同的
-2. 两个对象相等,对两个equals方法返回true
-3. 两个对象有相同的hashcode值,它们也不一定是相等的
-4. 综上,equals方法被覆盖过,则hashCode方法也必须被覆盖
-5. hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。
-
-**==与equals的区别**
-
-1. ==是判断两个变量或实例是不是指向同一个内存空间 equals是判断两个变量或实例所指向的内存空间的值是不是相同
-2. ==是指对内存地址进行比较 equals()是对字符串的内容进行比较
-3. ==指引用是否相同 equals()指的是值是否相同
-
-## HashMap的底层实现
-
-### JDK1.8之前
-
-JDK1.8 之前 `HashMap` 底层是 **数组和链表** 结合在一起使用也就是 **链表散列**。**HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值,然后通过 (n - 1) & hash 判断当前元素存放的位置(这里的 n 指的是数组的长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。**
-
-**所谓扰动函数指的就是 HashMap 的 hash 方法。使用 hash 方法也就是扰动函数是为了防止一些实现比较差的 hashCode() 方法 换句话说使用扰动函数之后可以减少碰撞。**
-
-**JDK 1.8 HashMap 的 hash 方法源码:**
-
-JDK 1.8 的 hash方法 相比于 JDK 1.7 hash 方法更加简化,但是原理不变。
-
-```java
- static final int hash(Object key) {
- int h;
- // key.hashCode():返回散列值也就是hashcode
- // ^ :按位异或
- // >>>:无符号右移,忽略符号位,空位都以0补齐
- return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
- }
-```
-
-对比一下 JDK1.7的 HashMap 的 hash 方法源码.
-
-```java
-static int hash(int h) {
- // This function ensures that hashCodes that differ only by
- // constant multiples at each bit position have a bounded
- // number of collisions (approximately 8 at default load factor).
-
- h ^= (h >>> 20) ^ (h >>> 12);
- return h ^ (h >>> 7) ^ (h >>> 4);
-}
-```
-
-相比于 JDK1.8 的 hash 方法 ,JDK 1.7 的 hash 方法的性能会稍差一点点,因为毕竟扰动了 4 次。
-
-所谓 **“拉链法”** 就是:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。
-
-
-
-### JDK1.8之后
-
-相比于之前的版本, JDK1.8之后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。
-
-
-
-> TreeMap、TreeSet以及JDK1.8之后的HashMap底层都用到了红黑树。红黑树就是为了解决二叉查找树的缺陷,因为二叉查找树在某些情况下会退化成一个线性结构。
-
-**推荐阅读:**
-
-- 《Java 8系列之重新认识HashMap》 :
-
-## HashMap 的长度为什么是2的幂次方
-
-为了能让 HashMap 存取高效,尽量较少碰撞,也就是要尽量把数据分配均匀。我们上面也讲到了过了,Hash 值的范围值-2147483648到2147483647,前后加起来大概40亿的映射空间,只要哈希函数映射得比较均匀松散,一般应用是很难出现碰撞的。但问题是一个40亿长度的数组,内存是放不下的。所以这个散列值是不能直接拿来用的。用之前还要先做对数组的长度取模运算,得到的余数才能用来要存放的位置也就是对应的数组下标。这个数组下标的计算方法是“ `(n - 1) & hash`”。(n代表数组长度)。这也就解释了 HashMap 的长度为什么是2的幂次方。
-
-**这个算法应该如何设计呢?**
-
-我们首先可能会想到采用%取余的操作来实现。但是,重点来了:**“取余(%)操作中如果除数是2的幂次则等价于与其除数减一的与(&)操作(也就是说 hash%length==hash&(length-1)的前提是 length 是2的 n 次方;)。”** 并且 **采用二进制位操作 &,相对于%能够提高运算效率,这就解释了 HashMap 的长度为什么是2的幂次方。**
-
-## HashMap 多线程操作导致死循环问题
-
-主要原因在于 并发下的Rehash 会造成元素之间会形成一个循环链表。不过,jdk 1.8 后解决了这个问题,但是还是不建议在多线程下使用 HashMap,因为多线程下使用 HashMap 还是会存在其他问题比如数据丢失。并发环境下推荐使用 ConcurrentHashMap 。
-
-详情请查看:
-
-## ConcurrentHashMap 和 Hashtable 的区别
-
-ConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的方式上不同。
-
-- **底层数据结构:** JDK1.7的 ConcurrentHashMap 底层采用 **分段的数组+链表** 实现,JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树。Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似都是采用 **数组+链表** 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的;
-- **实现线程安全的方式(重要):** ① **在JDK1.7的时候,ConcurrentHashMap(分段锁)** 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。 **到了 JDK1.8 的时候已经摒弃了Segment的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。(JDK1.6以后 对 synchronized锁做了很多优化)** 整个看起来就像是优化过且线程安全的 HashMap,虽然在JDK1.8中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;② **Hashtable(同一把锁)** :使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。
-
-**两者的对比图:**
-
-图片来源:
-
-**HashTable:**
-
-
-
-**JDK1.7的ConcurrentHashMap:**
-
-
-
-**JDK1.8的ConcurrentHashMap(TreeBin: 红黑二叉树节点 Node: 链表节点):**
-
-
-
-## ConcurrentHashMap线程安全的具体实现方式/底层具体实现
-
-### JDK1.7(上面有示意图)
-
-首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。
-
-**ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成**。
-
-Segment 实现了 ReentrantLock,所以 Segment 是一种可重入锁,扮演锁的角色。HashEntry 用于存储键值对数据。
-
-```java
-static class Segment extends ReentrantLock implements Serializable {
-}
-```
-
-一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和HashMap类似,是一种数组和链表结构,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素,每个 Segment 守护着一个HashEntry数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment的锁。
-
-### JDK1.8 (上面有示意图)
-
-ConcurrentHashMap取消了Segment分段锁,采用CAS和synchronized来保证并发安全。数据结构跟HashMap1.8的结构类似,数组+链表/红黑二叉树。Java 8在链表长度超过一定阈值(8)时将链表(寻址时间复杂度为O(N))转换为红黑树(寻址时间复杂度为O(log(N)))
-
-synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提升N倍。
-
-## comparable 和 Comparator的区别
-
-- comparable接口实际上是出自java.lang包 它有一个 `compareTo(Object obj)`方法用来排序
-- comparator接口实际上是出自 java.util 包它有一个`compare(Object obj1, Object obj2)`方法用来排序
-
-一般我们需要对一个集合使用自定义排序时,我们就要重写`compareTo()`方法或`compare()`方法,当我们需要对某一个集合实现两种排序方式,比如一个song对象中的歌名和歌手名分别采用一种排序方法的话,我们可以重写`compareTo()`方法和使用自制的Comparator方法或者以两个Comparator来实现歌名排序和歌星名排序,第二种代表我们只能使用两个参数版的 `Collections.sort()`.
-
-### Comparator定制排序
-
-```java
- ArrayList arrayList = new ArrayList();
- arrayList.add(-1);
- arrayList.add(3);
- arrayList.add(3);
- arrayList.add(-5);
- arrayList.add(7);
- arrayList.add(4);
- arrayList.add(-9);
- arrayList.add(-7);
- System.out.println("原始数组:");
- System.out.println(arrayList);
- // void reverse(List list):反转
- Collections.reverse(arrayList);
- System.out.println("Collections.reverse(arrayList):");
- System.out.println(arrayList);
-
- // void sort(List list),按自然排序的升序排序
- Collections.sort(arrayList);
- System.out.println("Collections.sort(arrayList):");
- System.out.println(arrayList);
- // 定制排序的用法
- Collections.sort(arrayList, new Comparator() {
-
- @Override
- public int compare(Integer o1, Integer o2) {
- return o2.compareTo(o1);
- }
- });
- System.out.println("定制排序后:");
- System.out.println(arrayList);
-```
-
-Output:
-
-```
-原始数组:
-[-1, 3, 3, -5, 7, 4, -9, -7]
-Collections.reverse(arrayList):
-[-7, -9, 4, 7, -5, 3, 3, -1]
-Collections.sort(arrayList):
-[-9, -7, -5, -1, 3, 3, 4, 7]
-定制排序后:
-[7, 4, 3, 3, -1, -5, -7, -9]
-```
-
-### 重写compareTo方法实现按年龄来排序
-
-```java
-// person对象没有实现Comparable接口,所以必须实现,这样才不会出错,才可以使treemap中的数据按顺序排列
-// 前面一个例子的String类已经默认实现了Comparable接口,详细可以查看String类的API文档,另外其他
-// 像Integer类等都已经实现了Comparable接口,所以不需要另外实现了
-
-public class Person implements Comparable {
- private String name;
- private int age;
-
- public Person(String name, int age) {
- super();
- this.name = name;
- this.age = age;
- }
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
- public int getAge() {
- return age;
- }
-
- public void setAge(int age) {
- this.age = age;
- }
-
- /**
- * TODO重写compareTo方法实现按年龄来排序
- */
- @Override
- public int compareTo(Person o) {
- // TODO Auto-generated method stub
- if (this.age > o.getAge()) {
- return 1;
- } else if (this.age < o.getAge()) {
- return -1;
- }
- return age;
- }
-}
-
-```
-
-```java
- public static void main(String[] args) {
- TreeMap pdata = new TreeMap();
- pdata.put(new Person("张三", 30), "zhangsan");
- pdata.put(new Person("李四", 20), "lisi");
- pdata.put(new Person("王五", 10), "wangwu");
- pdata.put(new Person("小红", 5), "xiaohong");
- // 得到key的值的同时得到key所对应的值
- Set keys = pdata.keySet();
- for (Person key : keys) {
- System.out.println(key.getAge() + "-" + key.getName());
-
- }
- }
-```
-
-Output:
-
-```
-5-小红
-10-王五
-20-李四
-30-张三
-```
-
-## 集合框架底层数据结构总结
-
-### Collection
-
-#### 1. List
-
-- **Arraylist:** Object数组
-- **Vector:** Object数组
-- **LinkedList:** 双向链表(JDK1.6之前为循环链表,JDK1.7取消了循环)
-
-#### 2. Set
-
-- **HashSet(无序,唯一):** 基于 HashMap 实现的,底层采用 HashMap 来保存元素
-- **LinkedHashSet:** LinkedHashSet 继承于 HashSet,并且其内部是通过 LinkedHashMap 来实现的。有点类似于我们之前说的LinkedHashMap 其内部是基于 HashMap 实现一样,不过还是有一点点区别的
-- **TreeSet(有序,唯一):** 红黑树(自平衡的排序二叉树)
-
-### Map
-
-- **HashMap:** JDK1.8之前HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突)。JDK1.8以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间
-- **LinkedHashMap:** LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。详细可以查看:[《LinkedHashMap 源码详细分析(JDK1.8)》](https://www.imooc.com/article/22931)
-- **Hashtable:** 数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的
-- **TreeMap:** 红黑树(自平衡的排序二叉树)
-
-## 如何选用集合?
-
-主要根据集合的特点来选用,比如我们需要根据键值获取到元素值时就选用Map接口下的集合,需要排序时选择TreeMap,不需要排序时就选择HashMap,需要保证线程安全就选用ConcurrentHashMap.当我们只需要存放元素值时,就选择实现Collection接口的集合,需要保证元素唯一时选择实现Set接口的集合比如TreeSet或HashSet,不需要就选择实现List接口的比如ArrayList或LinkedList,然后再根据实现这些接口的集合的特点来选用。
-
-## 公众号
-
-如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
-
-**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"Java面试突击"** 即可免费领取!
-
-**Java工程师必备学习资源:** 一些Java工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。
-
-
diff --git a/docs/java/collection/LinkedList.md b/docs/java/collection/LinkedList.md
deleted file mode 100644
index d26bc752267..00000000000
--- a/docs/java/collection/LinkedList.md
+++ /dev/null
@@ -1,515 +0,0 @@
-
-
-
-- [简介](#简介)
-- [内部结构分析](#内部结构分析)
-- [LinkedList源码分析](#linkedlist源码分析)
- - [构造方法](#构造方法)
- - [添加(add)方法](#add方法)
- - [根据位置取数据的方法](#根据位置取数据的方法)
- - [根据对象得到索引的方法](#根据对象得到索引的方法)
- - [检查链表是否包含某对象的方法:](#检查链表是否包含某对象的方法:)
- - [删除(remove/pop)方法](#删除方法)
-- [LinkedList类常用方法测试:](#linkedlist类常用方法测试)
-
-
-
-## 简介
-LinkedList 是一个实现了List接口 和Deque接口 的双端链表 。
-LinkedList底层的链表结构使它支持高效的插入和删除操作 ,另外它实现了Deque接口,使得LinkedList类也具有队列的特性;
-LinkedList不是线程安全的 ,如果想使LinkedList变成线程安全的,可以调用静态类Collections类 中的synchronizedList 方法:
-```java
-List list=Collections.synchronizedList(new LinkedList(...));
-```
-## 内部结构分析
-**如下图所示:**
-
-看完了图之后,我们再看LinkedList类中的一个**内部私有类Node** 就很好理解了:
-```java
-private static class Node {
- E item;//节点值
- Node next;//后继节点
- Node prev;//前驱节点
-
- Node(Node prev, E element, Node next) {
- this.item = element;
- this.next = next;
- this.prev = prev;
- }
- }
-```
-这个类就代表双端链表的节点Node。这个类有三个属性,分别是前驱节点,本节点的值,后继结点。
-
-## LinkedList源码分析
-### 构造方法
-**空构造方法:**
-```java
- public LinkedList() {
- }
-```
-**用已有的集合创建链表的构造方法:**
-```java
- public LinkedList(Collection extends E> c) {
- this();
- addAll(c);
- }
-```
-### add方法
-**add(E e)** 方法:将元素添加到链表尾部
-```java
-public boolean add(E e) {
- linkLast(e);//这里就只调用了这一个方法
- return true;
- }
-```
-
-```java
- /**
- * 链接使e作为最后一个元素。
- */
- void linkLast(E e) {
- final Node l = last;
- final Node newNode = new Node<>(l, e, null);
- last = newNode;//新建节点
- if (l == null)
- first = newNode;
- else
- l.next = newNode;//指向后继元素也就是指向下一个元素
- size++;
- modCount++;
- }
-```
-**add(int index,E e)**:在指定位置添加元素
-```java
-public void add(int index, E element) {
- checkPositionIndex(index); //检查索引是否处于[0-size]之间
-
- if (index == size)//添加在链表尾部
- linkLast(element);
- else//添加在链表中间
- linkBefore(element, node(index));
- }
-```
-linkBefore方法 需要给定两个参数,一个插入节点的值 ,一个指定的node ,所以我们又调用了Node(index)去找到index对应的node
-
-**addAll(Collection c ):将集合插入到链表尾部**
-
-```java
-public boolean addAll(Collection extends E> c) {
- return addAll(size, c);
- }
-```
-**addAll(int index, Collection c):** 将集合从指定位置开始插入
-```java
-public boolean addAll(int index, Collection extends E> c) {
- //1:检查index范围是否在size之内
- checkPositionIndex(index);
-
- //2:toArray()方法把集合的数据存到对象数组中
- Object[] a = c.toArray();
- int numNew = a.length;
- if (numNew == 0)
- return false;
-
- //3:得到插入位置的前驱节点和后继节点
- Node pred, succ;
- //如果插入位置为尾部,前驱节点为last,后继节点为null
- if (index == size) {
- succ = null;
- pred = last;
- }
- //否则,调用node()方法得到后继节点,再得到前驱节点
- else {
- succ = node(index);
- pred = succ.prev;
- }
-
- // 4:遍历数据将数据插入
- for (Object o : a) {
- @SuppressWarnings("unchecked") E e = (E) o;
- //创建新节点
- Node newNode = new Node<>(pred, e, null);
- //如果插入位置在链表头部
- if (pred == null)
- first = newNode;
- else
- pred.next = newNode;
- pred = newNode;
- }
-
- //如果插入位置在尾部,重置last节点
- if (succ == null) {
- last = pred;
- }
- //否则,将插入的链表与先前链表连接起来
- else {
- pred.next = succ;
- succ.prev = pred;
- }
-
- size += numNew;
- modCount++;
- return true;
- }
-```
-上面可以看出addAll方法通常包括下面四个步骤:
-1. 检查index范围是否在size之内
-2. toArray()方法把集合的数据存到对象数组中
-3. 得到插入位置的前驱和后继节点
-4. 遍历数据,将数据插入到指定位置
-
-**addFirst(E e):** 将元素添加到链表头部
-```java
- public void addFirst(E e) {
- linkFirst(e);
- }
-```
-```java
-private void linkFirst(E e) {
- final Node f = first;
- final Node newNode = new Node<>(null, e, f);//新建节点,以头节点为后继节点
- first = newNode;
- //如果链表为空,last节点也指向该节点
- if (f == null)
- last = newNode;
- //否则,将头节点的前驱指针指向新节点,也就是指向前一个元素
- else
- f.prev = newNode;
- size++;
- modCount++;
- }
-```
-**addLast(E e):** 将元素添加到链表尾部,与 **add(E e)** 方法一样
-```java
-public void addLast(E e) {
- linkLast(e);
- }
-```
-### 根据位置取数据的方法
-**get(int index):** 根据指定索引返回数据
-```java
-public E get(int index) {
- //检查index范围是否在size之内
- checkElementIndex(index);
- //调用Node(index)去找到index对应的node然后返回它的值
- return node(index).item;
- }
-```
-**获取头节点(index=0)数据方法:**
-```java
-public E getFirst() {
- final Node f = first;
- if (f == null)
- throw new NoSuchElementException();
- return f.item;
- }
-public E element() {
- return getFirst();
- }
-public E peek() {
- final Node f = first;
- return (f == null) ? null : f.item;
- }
-
-public E peekFirst() {
- final Node f = first;
- return (f == null) ? null : f.item;
- }
-```
-**区别:**
-getFirst(),element(),peek(),peekFirst()
-这四个获取头结点方法的区别在于对链表为空时的处理,是抛出异常还是返回null,其中**getFirst()** 和**element()** 方法将会在链表为空时,抛出异常
-
-element()方法的内部就是使用getFirst()实现的。它们会在链表为空时,抛出NoSuchElementException
-**获取尾节点(index=-1)数据方法:**
-```java
- public E getLast() {
- final Node l = last;
- if (l == null)
- throw new NoSuchElementException();
- return l.item;
- }
- public E peekLast() {
- final Node l = last;
- return (l == null) ? null : l.item;
- }
-```
-**两者区别:**
-**getLast()** 方法在链表为空时,会抛出**NoSuchElementException**,而**peekLast()** 则不会,只是会返回 **null**。
-### 根据对象得到索引的方法
-**int indexOf(Object o):** 从头遍历找
-```java
-public int indexOf(Object o) {
- int index = 0;
- if (o == null) {
- //从头遍历
- for (Node x = first; x != null; x = x.next) {
- if (x.item == null)
- return index;
- index++;
- }
- } else {
- //从头遍历
- for (Node x = first; x != null; x = x.next) {
- if (o.equals(x.item))
- return index;
- index++;
- }
- }
- return -1;
- }
-```
-**int lastIndexOf(Object o):** 从尾遍历找
-```java
-public int lastIndexOf(Object o) {
- int index = size;
- if (o == null) {
- //从尾遍历
- for (Node x = last; x != null; x = x.prev) {
- index--;
- if (x.item == null)
- return index;
- }
- } else {
- //从尾遍历
- for (Node x = last; x != null; x = x.prev) {
- index--;
- if (o.equals(x.item))
- return index;
- }
- }
- return -1;
- }
-```
-### 检查链表是否包含某对象的方法:
-**contains(Object o):** 检查对象o是否存在于链表中
-```java
- public boolean contains(Object o) {
- return indexOf(o) != -1;
- }
-```
-### 删除方法
-**remove()** ,**removeFirst(),pop():** 删除头节点
-```
-public E pop() {
- return removeFirst();
- }
-public E remove() {
- return removeFirst();
- }
-public E removeFirst() {
- final Node f = first;
- if (f == null)
- throw new NoSuchElementException();
- return unlinkFirst(f);
- }
-```
-**removeLast(),pollLast():** 删除尾节点
-```java
-public E removeLast() {
- final Node l = last;
- if (l == null)
- throw new NoSuchElementException();
- return unlinkLast(l);
- }
-public E pollLast() {
- final Node l = last;
- return (l == null) ? null : unlinkLast(l);
- }
-```
-**区别:** removeLast()在链表为空时将抛出NoSuchElementException,而pollLast()方法返回null。
-
-**remove(Object o):** 删除指定元素
-```java
-public boolean remove(Object o) {
- //如果删除对象为null
- if (o == null) {
- //从头开始遍历
- for (Node x = first; x != null; x = x.next) {
- //找到元素
- if (x.item == null) {
- //从链表中移除找到的元素
- unlink(x);
- return true;
- }
- }
- } else {
- //从头开始遍历
- for (Node x = first; x != null; x = x.next) {
- //找到元素
- if (o.equals(x.item)) {
- //从链表中移除找到的元素
- unlink(x);
- return true;
- }
- }
- }
- return false;
- }
-```
-当删除指定对象时,只需调用remove(Object o)即可,不过该方法一次只会删除一个匹配的对象,如果删除了匹配对象,返回true,否则false。
-
-unlink(Node x) 方法:
-```java
-E unlink(Node x) {
- // assert x != null;
- final E element = x.item;
- final Node next = x.next;//得到后继节点
- final Node prev = x.prev;//得到前驱节点
-
- //删除前驱指针
- if (prev == null) {
- first = next;//如果删除的节点是头节点,令头节点指向该节点的后继节点
- } else {
- prev.next = next;//将前驱节点的后继节点指向后继节点
- x.prev = null;
- }
-
- //删除后继指针
- if (next == null) {
- last = prev;//如果删除的节点是尾节点,令尾节点指向该节点的前驱节点
- } else {
- next.prev = prev;
- x.next = null;
- }
-
- x.item = null;
- size--;
- modCount++;
- return element;
- }
-```
-**remove(int index)**:删除指定位置的元素
-```java
-public E remove(int index) {
- //检查index范围
- checkElementIndex(index);
- //将节点删除
- return unlink(node(index));
- }
-```
-## LinkedList类常用方法测试
-
-```java
-package list;
-
-import java.util.Iterator;
-import java.util.LinkedList;
-
-public class LinkedListDemo {
- public static void main(String[] srgs) {
- //创建存放int类型的linkedList
- LinkedList linkedList = new LinkedList<>();
- /************************** linkedList的基本操作 ************************/
- linkedList.addFirst(0); // 添加元素到列表开头
- linkedList.add(1); // 在列表结尾添加元素
- linkedList.add(2, 2); // 在指定位置添加元素
- linkedList.addLast(3); // 添加元素到列表结尾
-
- System.out.println("LinkedList(直接输出的): " + linkedList);
-
- System.out.println("getFirst()获得第一个元素: " + linkedList.getFirst()); // 返回此列表的第一个元素
- System.out.println("getLast()获得第最后一个元素: " + linkedList.getLast()); // 返回此列表的最后一个元素
- System.out.println("removeFirst()删除第一个元素并返回: " + linkedList.removeFirst()); // 移除并返回此列表的第一个元素
- System.out.println("removeLast()删除最后一个元素并返回: " + linkedList.removeLast()); // 移除并返回此列表的最后一个元素
- System.out.println("After remove:" + linkedList);
- System.out.println("contains()方法判断列表是否包含1这个元素:" + linkedList.contains(1)); // 判断此列表包含指定元素,如果是,则返回true
- System.out.println("该linkedList的大小 : " + linkedList.size()); // 返回此列表的元素个数
-
- /************************** 位置访问操作 ************************/
- System.out.println("-----------------------------------------");
- linkedList.set(1, 3); // 将此列表中指定位置的元素替换为指定的元素
- System.out.println("After set(1, 3):" + linkedList);
- System.out.println("get(1)获得指定位置(这里为1)的元素: " + linkedList.get(1)); // 返回此列表中指定位置处的元素
-
- /************************** Search操作 ************************/
- System.out.println("-----------------------------------------");
- linkedList.add(3);
- System.out.println("indexOf(3): " + linkedList.indexOf(3)); // 返回此列表中首次出现的指定元素的索引
- System.out.println("lastIndexOf(3): " + linkedList.lastIndexOf(3));// 返回此列表中最后出现的指定元素的索引
-
- /************************** Queue操作 ************************/
- System.out.println("-----------------------------------------");
- System.out.println("peek(): " + linkedList.peek()); // 获取但不移除此列表的头
- System.out.println("element(): " + linkedList.element()); // 获取但不移除此列表的头
- linkedList.poll(); // 获取并移除此列表的头
- System.out.println("After poll():" + linkedList);
- linkedList.remove();
- System.out.println("After remove():" + linkedList); // 获取并移除此列表的头
- linkedList.offer(4);
- System.out.println("After offer(4):" + linkedList); // 将指定元素添加到此列表的末尾
-
- /************************** Deque操作 ************************/
- System.out.println("-----------------------------------------");
- linkedList.offerFirst(2); // 在此列表的开头插入指定的元素
- System.out.println("After offerFirst(2):" + linkedList);
- linkedList.offerLast(5); // 在此列表末尾插入指定的元素
- System.out.println("After offerLast(5):" + linkedList);
- System.out.println("peekFirst(): " + linkedList.peekFirst()); // 获取但不移除此列表的第一个元素
- System.out.println("peekLast(): " + linkedList.peekLast()); // 获取但不移除此列表的第一个元素
- linkedList.pollFirst(); // 获取并移除此列表的第一个元素
- System.out.println("After pollFirst():" + linkedList);
- linkedList.pollLast(); // 获取并移除此列表的最后一个元素
- System.out.println("After pollLast():" + linkedList);
- linkedList.push(2); // 将元素推入此列表所表示的堆栈(插入到列表的头)
- System.out.println("After push(2):" + linkedList);
- linkedList.pop(); // 从此列表所表示的堆栈处弹出一个元素(获取并移除列表第一个元素)
- System.out.println("After pop():" + linkedList);
- linkedList.add(3);
- linkedList.removeFirstOccurrence(3); // 从此列表中移除第一次出现的指定元素(从头部到尾部遍历列表)
- System.out.println("After removeFirstOccurrence(3):" + linkedList);
- linkedList.removeLastOccurrence(3); // 从此列表中移除最后一次出现的指定元素(从尾部到头部遍历列表)
- System.out.println("After removeFirstOccurrence(3):" + linkedList);
-
- /************************** 遍历操作 ************************/
- System.out.println("-----------------------------------------");
- linkedList.clear();
- for (int i = 0; i < 100000; i++) {
- linkedList.add(i);
- }
- // 迭代器遍历
- long start = System.currentTimeMillis();
- Iterator iterator = linkedList.iterator();
- while (iterator.hasNext()) {
- iterator.next();
- }
- long end = System.currentTimeMillis();
- System.out.println("Iterator:" + (end - start) + " ms");
-
- // 顺序遍历(随机遍历)
- start = System.currentTimeMillis();
- for (int i = 0; i < linkedList.size(); i++) {
- linkedList.get(i);
- }
- end = System.currentTimeMillis();
- System.out.println("for:" + (end - start) + " ms");
-
- // 另一种for循环遍历
- start = System.currentTimeMillis();
- for (Integer i : linkedList)
- ;
- end = System.currentTimeMillis();
- System.out.println("for2:" + (end - start) + " ms");
-
- // 通过pollFirst()或pollLast()来遍历LinkedList
- LinkedList temp1 = new LinkedList<>();
- temp1.addAll(linkedList);
- start = System.currentTimeMillis();
- while (temp1.size() != 0) {
- temp1.pollFirst();
- }
- end = System.currentTimeMillis();
- System.out.println("pollFirst()或pollLast():" + (end - start) + " ms");
-
- // 通过removeFirst()或removeLast()来遍历LinkedList
- LinkedList temp2 = new LinkedList<>();
- temp2.addAll(linkedList);
- start = System.currentTimeMillis();
- while (temp2.size() != 0) {
- temp2.removeFirst();
- }
- end = System.currentTimeMillis();
- System.out.println("removeFirst()或removeLast():" + (end - start) + " ms");
- }
-}
-```
diff --git a/docs/java/collection/ArrayList.md b/docs/java/collection/arraylist-source-code.md
similarity index 53%
rename from docs/java/collection/ArrayList.md
rename to docs/java/collection/arraylist-source-code.md
index f6578a7a784..36dfcbdbfc8 100644
--- a/docs/java/collection/ArrayList.md
+++ b/docs/java/collection/arraylist-source-code.md
@@ -1,34 +1,43 @@
-
+---
+title: ArrayList 源码+扩容机制分析
+category: Java
+tag:
+ - Java集合
+---
-- [ArrayList简介](#arraylist简介)
-- [ArrayList核心源码](#arraylist核心源码)
-- [ArrayList源码分析](#arraylist源码分析)
- - [System.arraycopy\(\)和Arrays.copyOf\(\)方法](#systemarraycopy和arrayscopyof方法)
- - [两者联系与区别](#两者联系与区别)
- - [ArrayList核心扩容技术](#arraylist核心扩容技术)
- - [内部类](#内部类)
-- [ArrayList经典Demo](#arraylist经典demo)
-
+## 1. ArrayList 简介
+`ArrayList` 的底层是数组队列,相当于动态数组。与 Java 中的数组相比,它的容量能动态增长。在添加大量元素前,应用程序可以使用`ensureCapacity`操作来增加 `ArrayList` 实例的容量。这可以减少递增式再分配的数量。
-### ArrayList简介
- ArrayList 的底层是数组队列,相当于动态数组。与 Java 中的数组相比,它的容量能动态增长。在添加大量元素前,应用程序可以使用`ensureCapacity`操作来增加 ArrayList 实例的容量。这可以减少递增式再分配的数量。
-
- 它继承于 **AbstractList**,实现了 **List**, **RandomAccess**, **Cloneable**, **java.io.Serializable** 这些接口。
-
- 在我们学数据结构的时候就知道了线性表的顺序存储,插入删除元素的时间复杂度为**O(n)**,求表长以及增加元素,取第 i 元素的时间复杂度为**O(1)**
+`ArrayList`继承于 **`AbstractList`** ,实现了 **`List`**, **`RandomAccess`**, **`Cloneable`**, **`java.io.Serializable`** 这些接口。
- ArrayList 继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。
+```java
+
+public class ArrayList extends AbstractList
+ implements List, RandomAccess, Cloneable, java.io.Serializable{
+
+ }
+```
+
+- `RandomAccess` 是一个标志接口,表明实现这个这个接口的 List 集合是支持**快速随机访问**的。在 `ArrayList` 中,我们即可以通过元素的序号快速获取元素对象,这就是快速随机访问。
+- `ArrayList` 实现了 **`Cloneable` 接口** ,即覆盖了函数`clone()`,能被克隆。
+- `ArrayList` 实现了 `java.io.Serializable`接口,这意味着`ArrayList`支持序列化,能通过序列化去传输。
- ArrayList 实现了**RandomAccess 接口**, RandomAccess 是一个标志接口,表明实现这个这个接口的 List 集合是支持**快速随机访问**的。在 ArrayList 中,我们即可以通过元素的序号快速获取元素对象,这就是快速随机访问。
+### 1.1. Arraylist 和 Vector 的区别?
- ArrayList 实现了**Cloneable 接口**,即覆盖了函数 clone(),**能被克隆**。
+1. `ArrayList` 是 `List` 的主要实现类,底层使用 `Object[ ]`存储,适用于频繁的查找工作,线程不安全 ;
+2. `Vector` 是 `List` 的古老实现类,底层使用 `Object[ ]`存储,线程安全的。
- ArrayList 实现**java.io.Serializable 接口**,这意味着ArrayList**支持序列化**,**能通过序列化去传输**。
+### 1.2. Arraylist 与 LinkedList 区别?
- 和 Vector 不同,**ArrayList 中的操作不是线程安全的**!所以,建议在单线程中才使用 ArrayList,而在多线程中可以选择 Vector 或者 CopyOnWriteArrayList。
-### ArrayList核心源码
+1. **是否保证线程安全:** `ArrayList` 和 `LinkedList` 都是不同步的,也就是不保证线程安全;
+2. **底层数据结构:** `Arraylist` 底层使用的是 **`Object` 数组**;`LinkedList` 底层使用的是 **双向链表** 数据结构(JDK1.6 之前为循环链表,JDK1.7 取消了循环。注意双向链表和双向循环链表的区别,下面有介绍到!)
+3. **插入和删除是否受元素位置的影响:** ① **`ArrayList` 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。** 比如:执行`add(E e)`方法的时候, `ArrayList` 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是 O(1)。但是如果要在指定位置 i 插入和删除元素的话(`add(int index, E element)`)时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。 ② **`LinkedList` 采用链表存储,所以对于`add(E e)`方法的插入,删除元素时间复杂度不受元素位置的影响,近似 O(1),如果是要在指定位置`i`插入和删除元素的话(`(add(int index, E element)`) 时间复杂度近似为`o(n))`因为需要先移动到指定位置再插入。**
+4. **是否支持快速随机访问:** `LinkedList` 不支持高效的随机元素访问,而 `ArrayList` 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于`get(int index)`方法)。
+5. **内存空间占用:** `ArrayList` 的空 间浪费主要体现在在 list 列表的结尾会预留一定的容量空间,而 `LinkedList` 的空间花费则体现在它的每一个元素都需要消耗比 `ArrayList` 更多的空间(因为要存放直接后继和直接前驱以及数据)。
+
+## 2. ArrayList 核心源码解读
```java
package java.util;
@@ -68,23 +77,25 @@ public class ArrayList extends AbstractList
private int size;
/**
- * 带初始容量参数的构造函数。(用户自己指定容量)
+ * 带初始容量参数的构造函数(用户可以在创建ArrayList对象时自己指定集合的初始大小)
*/
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
- //创建initialCapacity大小的数组
+ //如果传入的参数大于0,创建initialCapacity大小的数组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
- //创建空数组
+ //如果传入的参数等于0,创建空数组
this.elementData = EMPTY_ELEMENTDATA;
} else {
+ //其他情况,抛出异常
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
/**
- *默认构造函数,DEFAULTCAPACITY_EMPTY_ELEMENTDATA 为0.初始化为10,也就是说初始其实是空数组 当添加第一个元素的时候数组容量才变成10
+ *默认无参构造函数
+ *DEFAULTCAPACITY_EMPTY_ELEMENTDATA 为0.初始化为10,也就是说初始其实是空数组 当添加第一个元素的时候数组容量才变成10
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
@@ -94,22 +105,22 @@ public class ArrayList extends AbstractList
* 构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序。
*/
public ArrayList(Collection extends E> c) {
- //
+ //将指定集合转换为数组
elementData = c.toArray();
- //如果指定集合元素个数不为0
+ //如果elementData数组的长度不为0
if ((size = elementData.length) != 0) {
- // c.toArray 可能返回的不是Object类型的数组所以加上下面的语句用于判断,
- //这里用到了反射里面的getClass()方法
+ // 如果elementData不是Object类型数据(c.toArray可能返回的不是Object类型的数组所以加上下面的语句用于判断)
if (elementData.getClass() != Object[].class)
+ //将原来不是Object类型的elementData数组的内容,赋值给新的Object类型的elementData数组
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
- // 用空数组代替
+ // 其他情况,用空数组代替
this.elementData = EMPTY_ELEMENTDATA;
}
}
/**
- * 修改这个ArrayList实例的容量是列表的当前大小。 应用程序可以使用此操作来最小化ArrayList实例的存储。
+ * 修改这个ArrayList实例的容量是列表的当前大小。 应用程序可以使用此操作来最小化ArrayList实例的存储。
*/
public void trimToSize() {
modCount++;
@@ -127,13 +138,14 @@ public class ArrayList extends AbstractList
* @param minCapacity 所需的最小容量
*/
public void ensureCapacity(int minCapacity) {
+ //如果是true,minExpand的值为0,如果是false,minExpand的值为10
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// any size if not default element table
? 0
// larger than default for default empty table. It's already
// supposed to be at default size.
: DEFAULT_CAPACITY;
-
+ //如果最小容量大于已有的最大容量
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
@@ -141,7 +153,7 @@ public class ArrayList extends AbstractList