diff --git a/Algorithm.md b/Algorithm.md index a25fbd6..3f3ae0e 100644 --- a/Algorithm.md +++ b/Algorithm.md @@ -2463,7 +2463,23 @@ Tips:希尔排序的核心在于间隔序列的设定。既可以提前设定 ### 归并排序(Merging Sort) -归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。 +**简介** + +**基本思想**:归并(Merge)排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列**合并**为整体有序序列。 + + + +**场景使用** + +**应用场景**:内存少的时候使用,可以进行**并行计算**的时候使用。 + + + +**步骤**: + +- 选择**相邻**两个数组成一个有序序列 +- 选择相邻的两个有序序列组成一个有序序列 +- 重复第二步,直到全部组成一个**有序**序列 ![归并排序](images/Algorithm/归并排序.jpg) @@ -3819,103 +3835,104 @@ public class Solution { # Design Pattern -在Java编程语言中,常用的设计模式可分为三种类型: +## 创建型模式 -- **建造类设计模式**:主要用于定义和约束如何创建一个新的对象 -- **结构类设计模式**:主要用于定义如何使用多个对象组合出一个或多个复合对象 -- **行为类设计模式**:主要用于定义和描述对象之间的交互规则和限定对象的职责边界线 +建造类设计模式提供了对创建对象的基本定义和约束条件,以寻求最佳的实例化Java对象解决方案。 +### 工厂模式(Factory) +**概念** -设计模式功能: +定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的**实例化延迟**到其子类。 -- **建造类** - - **单例(Singleton)模式**:某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多例模式 - - **工厂方法(Factory Method)模式**:定义一个用于创建产品的接口,由子类决定生产什么产品 - - **抽象工厂(AbstractFactory)模式**:提供一个创建产品族的接口,其每个子类可以生产一系列相关的产品 - - **建造者(Builder)模式**:将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象 - - **原型(Prototype)模式**:将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例 +**使用场景** -- **结构类** - - **适配器(Adapter)模式**:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作 - - **组合(Composite)模式**:将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性 - - **代理(Proxy)模式**:为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性 - - **享元(Flyweight)模式**:运用共享技术来有效地支持大量细粒度对象的复用 - - **外观(Facade)模式**:为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问 - - **桥接(Bridge)模式**:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度 - - **装饰(Decorator)模式**:动态的给对象增加一些职责,即增加其额外的功能 +jdbc 连接数据库,硬件访问,降低对象的产生和销毁。 -- **行为类** - - **模板方法(TemplateMethod)模式**:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤 - - **策略(Strategy)模式**:定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户 - - **命令(Command)模式**:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开 - - **职责链(Chain of Responsibility)模式**:把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合 - - **状态(State)模式**:允许一个对象在其内部状态发生改变时改变其行为能力 - - **观察者(Observer)模式**:多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为 - - **中介者(Mediator)模式**:定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解 - - **迭代器(Iterator)模式**:提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示 - - **访问者(Visitor)模式**:在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象访问 - - **备忘录(Memento)模式**:在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它 - - **解释器(Interpreter)模式**:提供如何定义语言的文法,以及对语言句子的解释方法,即解释器 +实现工厂模式需要满足三个条件: -## 创建型模式 +- 超类(super class):超类是一个抽象类 +- 子类(sub class):子类需继承超类 +- 工厂类(factory class):工厂类根据输入参数实例化子类 -建造类设计模式提供了对创建对象的基本定义和约束条件,以寻求最佳的实例化Java对象解决方案。 +![工厂模式](images/Algorithm/工厂模式.png) -### 简单工厂模式 -![简单工厂模式](images/Algorithm/简单工厂模式.png) +### 抽象工厂模式(Abstract Factory) +**概念** -### 工厂模式-Factory +为创建一组相关或相互依赖的对象提供一个接口,而且**无须指定它们的具体类。** -在Java程序设计过程中,当一个超类(super class)具有多个子类(sub class),且需要频繁的创建子类对象时,我们可以采用工厂模式。工厂模式的作用是将子类的实例化工作统一交由工厂类来完成,通过对输入参数的判断,工厂类自动实例化具体的子类。实现工厂模式需要满足三个条件: -- 超类(super class):超类是一个抽象类 -- 子类(sub class):子类需继承超类 -- 工厂类(factory class):工厂类根据输入参数实例化子类 -![工厂模式](images/Algorithm/工厂模式.png) +**使用场景** +一个对象族(或是一组没有任何关系的对象)都有相同的约束。 +涉及不同操作系统的时候,都可以考虑使用抽象工厂模式。 -### 抽象工厂模式-Abstract Factory -抽象工厂模式与工厂模式很类似,抽象工厂模式可以简单的理解为“工厂的工厂”。在工厂模式中,根据提供的输入参数返回产品类的实例化对象,这个过程需要通过if-else或者switch这样的逻辑判断语句来完成具体子类的判定。而在抽象工厂模式中,每种产品都有具体的工厂类与之对应,从而避免在编码过程中使用大量的逻辑判断代码。抽象工厂模式会根据输入的工厂类型以返回具体的工厂子类。抽象工厂类只负责实例化工厂子类,不参与商品子类的实例化工作。 +### 单例模式(Singleton) +**概念** +确保某一个类只有一个实例,而且**自行实例化**并向整个系统提供这个实例。 -### 单例模式-Singleton -单例模式限制类的实例化过程,以确保在Java虚拟机(JVM)中有且只有一个类的实例化对象。单例模式是Java中最常用,也是最简单的设计模式之一。单例模式通常需具备如下的几个特征: +**使用场景** + +- 要求生成唯一序列号的环境 +- 在整个项目中需要一个共享访问点或共享数据,例如一个Web页面上的计数器,可以不用把每次刷新都记录到数据库中,使用单例模式保持计数器的值,并确保是线程安全的 +- 创建一个对象需要消耗的资源过多,如要访问IO和数据库等资源 +- 需要定义大量的静态常量和静态方法(如工具类)的环境,可以采用单例模式(当然,也可以直接声明为static的方式) + -- 单例模式限制类的实例化,且Java虚拟机中只能存在一个该类的示例化对象 -- 单例模式必须提供一个全局可用的访问入口来获取该类的实例化对象 -- 单例模式常被用于日志记录,驱动程序对象设计,缓存以及线程池 -- 单例模式也会被用于其他的设计模式当中,如抽象工厂模式,建造者模式,原型模式等 ![单例模式](images/Algorithm/单例模式.png) -### 建造者模式-Builder +### 建造者模式(Builder) + +**概念** + +将一个复杂对象的构建与它的表示分离,使得**同样的构建过程可以创建不同的表示。** + + + +**使用场景** + +- 相同的方法,不同的执行顺序,产生不同的事件结果时,可以采用建造者模式 +- 多个部件或零件,都可以装配到一个对象中,但是产生的运行结果又不相同时,则可以使用该模式 +- 产品类非常复杂,或者产品类中的调用顺序不同产生了不同的效能,这个时候使用建造者模式非常合适 + -建造者模式通常被用于需要多个步骤创建对象的场景中。建造者模式的主要意图是将类的构建逻辑转移到类的实例化之外,当一个类有许多的属性,当在实例化该类的对象时,并不一定拥有该实例化对象的全部属性信息,便可使用建造者模式通过逐步获取实例化对象的属性信息,来完成该类的实例化过程。而工厂模式和抽象工厂模式需要在实例化时获取该类实例化对象的全部属性信息。 ![建造者模式](images/Algorithm/建造者模式.png) -### 原型模式-Prototype +### 原型模式(Prototype) + +**概念** + +用原型实例指定创建对象的种类,并且**通过拷贝这些原型**创建新的对象。 + -原型模式的主要作用是可以利用现有的类通过复制(克隆)的方式创建一个新的对象。当示例化一个类的对象需要耗费大量的时间和系统资源时,可是采用原型模式,将原始已存在的对象通过复制(克隆)机制创建新的对象,然后根据需要,对新对象进行修改。原型模式要求被复制的对象自身具备拷贝功能,此功能不能由外界完成。 +**使用场景** +- **资源优化场景:**类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等 + +- **性能和安全要求的场景:**通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式 + +- **一个对象多个修改者的场景:**一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以、考虑使用原型模式拷贝多个对象供调用者使用 @@ -3923,9 +3940,19 @@ public class Solution { 结构类设计模式主要解决如何通过多个小对象组合出一个大对象的问题,如使用继承和接口实现将多个类组合在一起。 -### 适配器模式-Adapter +### 适配器模式(Adapter) + +**概念** + +将一个类的接口**变换成客户端所期待的另一种接口,**从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。 + + + +**使用场景** + +你有动机修改一个已经投产中的接口时,适配器模式可能是最适合你的模式。比如系统扩展了,需要使用一个已有或新建立的类,但这个类又不符合系统的接口,怎么办?详细设计阶段不要考虑使用适配器模式,使用主要场景为扩展应用中。 + -适配器模式的主要作用是使现有的多个可用接口能够在一起为客服端提供新的接口服务。在适配器模式中,负责连接不同接口的对象成为适配器。在现实生活中,我们也能够找到很多实际的案例来理解适配器的工作原理,例如常用的手机充电头,在手机和电源插座之间,手机充电头就扮演一个适配器的角色,它能够同时适配220V,200V,120V等不同的电压,最终将电转换成手机可用的5V电压为手机进行充电。 ![适配器模式-默认适配器](images/Algorithm/适配器模式-默认适配器.png) @@ -3935,9 +3962,21 @@ public class Solution { -### 组合模式-Composite +### 组合模式(Composite) + +**概念** + +将对象组合成树形结构以表示**“部分-整体”**的层次结构,使得用户对单个对象和组合对象的使用具有一致性。 + + + +**使用场景** + +- 维护和展示部分-整体关系的场景,如树形菜单、文件和文件夹管理 +- 从一个整体中能够独立出部分模块或功能的场景 +- 只要是树形结构,就考虑使用组合模式 + -组合模式的主要作用是让整体与局部之前具有相同的行为。例如我们需要绘制一个图形(正方形,三角形,圆形或其他多边形),首先需要准备一张空白的纸,然后是选择一种绘制图案的颜色,再次是确定绘制图案的大小,最后是绘制图案。不管是绘制正方形还是三角形,都需要按照这个步骤进行。在软件设计过程中,组合模式的最大意义在于保证了客户端在调用单个对象与组合对象时,在其操作流程上是保持一致的。 **案例**:每个员工都有姓名、部门、薪水这些属性,同时还有下属员工集合(虽然可能集合为空),而下属员工和自己的结构是一样的,也有姓名、部门这些属性,同时也有他们的下属员工集合。 @@ -3945,37 +3984,75 @@ public class Solution { -### 代理模式-Proxy +### 代理模式(Proxy) + +**概念** + +为其他对象**提供一种代理以控制**对这个对象的访问。 + -代理模式的主要作用是通过提供一个代理对象或者一个占位符来控制对实际对象的访问行为。代理模式通常用于需要频繁操作一些复杂对象的地方,通过使用代理模式,可以借由代理类来操作目标对象,简化操作流程。 ![代理模式](images/Algorithm/代理模式.png) -### 享元模式-Flywight +### 享元模式(Flywight) + +**概念** + +使用共享对象可有效地**支持大量的细粒度的对象。** + +对象的信息分为两个部分:内部状态(intrinsic)与外部状态(extrinsic)。 + +- **内部状态:**内部状态是对象可共享出来的信息,存储在享元对象内部并且不会随环境改变而改变 + +- **外部状态:**外部状态是对象得以依赖的一个标记,是随环境改变而改变的、不可以共享的状态 -享元模式的主要作用是通过共享来有效地支持大量细粒度的对象。例如当需要创建一个类的很多对象时,可以使用享元模式,通过共享对象信息来减轻内存负载。如果在软件设计过程中采用享元模式,需要考虑以下三个问题: -- 应用程序需要创建的对象数量是否很大? -- 对象的创建对内存消耗和时间消耗是否有严格的要求? -- 对象的属性是否可以分为内在属性和外在属性?对象的外在属性是否支持有客户端定义? +**使用场景** +- 系统中存在大量的相似对象 +- 细粒度的对象都具备较接近的外部状态,而且内部状态与环境无关,也就是说对象没有特定身份 +- 需要缓冲池的场景 -### 门面模式-Facade +### 门面模式(Facade) + +**概念** + +要求一个子系统的外部与其内部的通信必须**通过一个统一的对象进行。**门面模式提供一个高层次的接口,使得子系统更易于使用。 + + + +**使用场景** + +- 为一个复杂的模块或子系统提供一个供外界访问的接口 +- 子系统相对独立——外界对子系统的访问只要黑箱操作即可 +- 预防低水平人员带来的风险扩散 + -门面模式(也叫外观模式)的主要作用是为子系统中的一组接口提供一个统一的接口,以便客户端更容易去使用子系统中的接口。简单的理解是外观模式为众多复杂接口定义了一个更高级别的接口。外观模式的目的是让接口更容易被使用。 ![门面模式](images/Algorithm/门面模式.png) -### 桥梁模式-Bridge +### 桥接模式(Bridge) + +**概念** + +将**抽象和实现解耦,**使得两者可以独立地变化。 + + + +**使用场景** + +- 不希望或不适用使用继承的场景 +- 接口或抽象类不稳定的场景 +- 重用性要求较高的场景 + -桥梁模式的主要用途是将抽象类与抽象类的具体实现相分离,以实现结构上的解耦,使抽象和实现可以独立的进行变化。桥梁模式的实现优先遵循组合而不是继承,当使用桥梁模式时,在一定程度上可以在客户端中因此接口的内部实现。 ![桥梁模式-1](images/Algorithm/桥梁模式-1.png) @@ -3983,17 +4060,19 @@ public class Solution { -### 修饰模式-Decorator - -修饰模式的主要作用是在运行时动态的组合类的行为。通常,你会添加一些新的类或者新的方法来扩展已有的代码库,然而,在某些情况下你需要在程序运行时为某个对象组合新的行为,此时你可以采用修饰模式。 +### 装饰器模式(Decorator) +**概念** +动态地给一个对象**添加一些额外的职责。**就增加功能来说,装饰模式相比生成子类更为灵活。 -### 过滤器模式-Filter -过滤器模式是使用不同的标准来过滤一组对象,通过逻辑运算以解耦的方式将对象组合起来。 +**使用场景** +- 需要扩展一个类的功能,或给一个类增加附加功能 +- 需要动态地给一个对象增加功能,这些功能可以再动态地撤销 +- 需要为一批的兄弟类进行改装或加装功能,当然是首选装饰模式 @@ -4001,85 +4080,182 @@ public class Solution { 行为类设计模式主要用于定义和描述对象之间的交互规则和职责边界,为对象之间更好的交互提供解决方案。 -### 模板方法模式-Template Method +### 模板方法模式(Template Method) + +**概念** + +定义一个操作中的算法的框架,而将一些步骤延迟到子类中。使得子类可以**不改变一个算法的结构**即可重定义该算法的某些特定步骤。 + + + +**使用场景** + +- 多个子类有公有的方法,并且逻辑基本相同时 +- 重要、复杂的算法,可以把核心算法设计为模板方法,周边的相关细节功能则由各个子类实现 +- 重构时,模板方法模式是一个经常使用的模式,把相同的代码抽取到父类中,然后通过钩子函数(见“模板方法模式的扩展”)约束其行为 + + + -模板方法模式的主要作用是在一个方法里实现一个算法,可以将算法中的的一些步骤抽象为方法,并将这些方法的实现推迟到子类中去实现。例如建造一栋房子,我们需要设计图纸,打地基,构筑墙体,安装门窗和内部装修。我们可以设计不同的房屋样式(别墅,高楼,板房等),不同的门窗和不同的装修材料和风格,但是其顺序不能颠倒。在这种情况下,我们可以定义一个模板方法,规定方法的执行顺序,而将方法的实现推迟到子类中完成。 ![模板方法模式](images/Algorithm/模板方法模式.png) -### 解释器模式-Mediator +### 中介者模式(Mediator) + +**概念** -解释器(中介)模式的主要设计意图是定义一个中间对象,封装一组对象的交互,从而降低对象的耦合度,避免了对象间的显示引用,并可以独立地改变对象的行为。解释器(中介)模式可以在系统中的不同对象之间提供集中式的交互介质,降低系统中各组件的耦合度。 +用一个中介对象封装一系列的对象交互,中介者使各对象不需要显示地相互作用,从而使其**耦合松散,**而且可以独立地改变它们之间的交互。 +**使用场景** +中介者模式适用于多个对象之间紧密耦合的情况,紧密耦合的标准是:在类图中出现了蜘蛛网状结构,即每个类都与其他的类有直接的联系。 + + + +### 责任链模式(Chain) + +**概念** + +使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。将这些对象**连成一条链,**并沿着这条链传递该请求,直到有对象处理它为止。 -### 责任链模式-Chain of Responsibility -责任链模式主要作用是让多个对象具有对同一任务(请求)的处理机会,以解除请求发送者与接收者之间的耦合度。try-catch就是一个典型的责任链模式的应用案例。在try-catch语句中,可以同时存在多个catch语句块,每个catch语句块都是处理该特定异常的处理器。当try语句块中发生异常是,异常将被发送到第一个catch语句块进行处理,如果第一个语句块无法处理它,它将会被请求转发到链中的下一个catch语句块。如果最后一个catch语句块仍然不能处理该异常,则该异常将会被向上抛出。 ![责任链模式](images/Algorithm/责任链模式.png) -### 观察者模式-Observer +### 观察者模式(Observer) + +**概念** + +定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会**得到通知并被自动更新。** + + + +**使用场景** + +- 关联行为场景。需要注意的是,关联行为是可拆分的,而不是“组合”关系 +- 事件多级触发场景 +- 跨系统的消息交换场景,如消息队列的处理机制 + -观察者模式的目的是在多个对象之间定义一对多的依赖关系,当一个对象的状态发生改变时,观察者会通知依赖它的对象,并根据新状态做出相应的反应。简单来说,如果你需要在对象状态发生改变时及时收到通知,你可以定义一个监听器,对该对象的状态进行监听,此时的监听器即为观察者(Observer),被监听对象称为主题(Subject)。Java消息服务(JMS)即采用了观察者设计模式(同时还使用了中介模式),允许应用程序订阅数据并将数据发布到其他应用程序中。 ![观察者模式](images/Algorithm/观察者模式.png) -### 策略模式-Strategy +### 策略模式(Strategy) + +**概念** + +定义一组算法,将**每个算法都封装起来,**并且使它们之间可以互换。 + + + +**使用场景** + +- 多个类只有在算法或行为上稍有不同的场景 +- 算法需要自由切换的场景 +- 需要屏蔽算法规则的场景 +- 具体策略数量超过 4 个,则需要考虑使用混合模式 + -策略模式的主要目的是将可互换的方法封装在各自独立的类中,并且让每个方法都实现一个公共的操作。策略模式定义了策略的输入与输出,实现则由各个独立的类完成。策略模式可以让一组策略共存,代码互不干扰,它不仅将选择策略的逻辑从策略本身中分离出来,还帮助我们组织和简化了代码。一个典型的例子是Collections.sort()方法,采用Comparator作为方法参数,根据Comparator接口实现类的不同,对象将以不同的方式进行排序。 ![策略模式](images/Algorithm/策略模式.png) -### 命令模式-Command +### 命令模式(Command) + +**概念** + +将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对**请求排队或者记录请求日志,**可以提供命令的撤销和恢复功能。 + + + +**使用场景** + +认为是命令的地方就可以采用命令模式,例如,在 GUI 开发中,一个按钮的点击是一个命令,可以采用命令模式;模拟 DOS 命令的时候,当然也要采用命令模式;触发-反馈机制的处理等。 + -命令模式的设计意图是将请求封装在对象的内部。直接调用是执行方法的通常做法,然而,在有些时候我们无法控制方法被执行的时机和上下文信息。在这种情况下,可以将方法封装到对象的内部,通过在对象内部存储调用方所需要的信息,就可以让客户端或者服务自由决定何时调用方法。 ![命令模式](images/Algorithm/命令模式.png) -### 状态模式-State +### 状态模式(State) + +**概念** + +当一个对象**内在状态改变时允许其改变行为,**这个对象看起来像改变了其类。 + + + +**使用场景** + +- 行为随状态改变而改变的场景,这也是状态模式的根本出发点,例如权限设计,人员的状态不同即使执行相同的行为结果也会不同,在这种情况下需要考虑使用状态模式 +- 条件、分支判断语句的替代者 + -状态模式的设计意图是更具对象的状态改变其行为。如果我们必须根据对象的状态改变对象的行为,可以在对象中定义一个状态变量,并使用逻辑判断语句块(如if-else)根据状态执行不同的操作。 ![状态模式](images/Algorithm/状态模式.png) -### 访客模式-Visitor +### 访问者模式(Visitor) + +**概念** + +封装一些**作用于某种数据结构中的各元素的操作,**它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。 + + + +**使用场景** + +- 一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作,也就说是用迭代器模式已经不能胜任的情景 +- 需要对一个对象结构中的对象进行很多不同并且不相关的操作,而你想避免让这些操作“污染”这些对象的类 + + + +### 解释器模式(Interpreter) + +**概念** + +给定一门语言,定义它的文法的一种表示,并定义一个解释器,该解释器**使用该表示来解释语言中的句子。** + + + +**使用场景** + +- 重复发生的问题可以使用解释器模式 +- 一个简单语法需要解释的场景 -访客模式的设计意图是在不改变现有类层次结构的前提下,对该层次结构进行扩展。例如在购物网站中,我们将不同的商品添加进购物车,然后支付按钮时,它会计算出需要支付的总金额数。我们可以在购物车类中完成金额的计算,也可以使用访客模式,将购物应付金额逻辑转移到新的类中。 +### 迭代器模式(Iterator) -### 转义模式-Interpreter +**概念** -转义(翻译)模式的设计意图是让你根据事先定义好的一系列组合规则,组合可执行的对象。实现转义(翻译)模式的一个基本步骤如下: +它提供一种方法**访问一个容器对象中各个元素,**而又不需暴露该对象的内部细节。 -- 创建执行解释工作的上下文引擎 -- 根据不同的表达式实现类,实现上下文中的解释工作 -- 创建一个客户端,客户端从用户那里获取输入,并决定使用哪一种表达式来输出转义后的内容 +### 备忘录模式(Memento) -### 迭代器模式-Iterator +**概念** -迭代器模式为迭代一组对象提供了一个标准的方法。迭代器模式被广泛的应用于Java Collection框架中,Iterator接口提供了遍历集合元素的方法。迭代器模式不仅仅是遍历集合,我们还可以根据不同的要求提供不同类型的迭代器。迭代器模式通过集合隐藏内部的遍历细节,客户端只需要使用对应的迭代方法即可完成元素的遍历操作。 +在不破坏封装性的前提下,**捕获一个对象的内部状态,**并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。 -### 备忘录模式-Memento +**使用场景** -备忘录模式的设计意图是为对象的状态提供存储和恢复功能。备忘录模式由两个对象来实现-Originator和Caretaker。Originator需要具有保存和恢复对象状态的能力,它使用内部类来保存对象的状态。内部内则称为备忘录,因为它是对象私有的,因此外部类不能直接访问它。 +- 需要保存和恢复数据的相关状态场景 +- 提供一个可回滚(rollback)的操作 +- 需要监控的副本场景中 +- 数据库连接的事务管理就是用的备忘录模式 diff --git a/Middleware.md b/Middleware.md index ff26c5c..5aae031 100644 --- a/Middleware.md +++ b/Middleware.md @@ -571,49 +571,77 @@ contents 数组是整数集合的底层实现:整数集合的每个元素都 ## 持久化机制 -### RDB机制(Redis DataBase) +通常来说,应该同时使用两种持久化方案,以保证数据安全: -RDB(Redis DataBase)持久化方式:是指用数据集快照的方式半持久化模式记录 Redis 数据库的所有键值对,在某个时间点将数据写入一个临时文件,持久化结束后,用这个临时文件替换上次持久化的文件,达到数据恢复。 +- 如果数据不敏感,且可以从其他地方重新生成,可以关闭持久化 +- 如果数据比较重要,且能够承受几分钟的数据丢失,比如缓存等,只需要使用RDB即可 +- 如果是用做内存数据,要使用Redis的持久化,建议是RDB和AOF都开启 +- 如果只用AOF,优先使用everysec的配置选择,因为它在可靠性和性能之间取了一个平衡 + +当RDB与AOF两种方式都开启时,Redis会优先使用AOF恢复数据,因为AOF保存的文件比RDB文件更完整 + + + +### RDB模式(内存快照) + +`RDB`(Redis Database Backup File,**Redis数据备份文件**)持久化方式:是指用数据集快照的方式半持久化模式记录 Redis 数据库的所有键值对,在某个时间点将数据写入一个临时文件,持久化结束后,用这个临时文件替换上次持久化的文件,达到数据恢复。 **优点** -- **适合大规模的数据恢复** -- **如果业务对数据完整性和一致性要求不高,RDB是很好的选择** +- RDB快照是一个压缩过的非常紧凑的文件。保存着某个时间点的数据集,适合做数据的备份,灾难恢复 +- 可最大化Redis的的性能。在保存RDB文件,服务器进程只需要fork一个子进程来完成RDB文件创建,父进程不需要做IO操作 +- 与AOF相比,恢复大数据集的时候会更快 **缺点** -- **数据的完整性和一致性不高,因为RDB可能在最后一次备份时宕机** -- **备份时占用内存**。因为Redis 在备份时会独立创建一个子进程,将数据写入到一个临时文件(此时内存中的数据是原来的两倍哦),最后再将临时文件替换之前的备份文件。所以要考虑到大概两倍的数据膨胀性 +- RDB的数据安全性是不如AOF的,保存整个数据集的过程是比繁重的,根据配置可能要几分钟才快照一次,如果服务器宕机,那么就可能丢失几分钟的数据 +- Redis数据集较大时,fork的子进程要完成快照会比较耗CPU、耗时 -#### save触发方式 +**① 创建** -`save` 命令(同步数据到磁盘上)是执行一个同步操作,以RDB文件的方式保存所有数据的快照。该命令会阻塞当前Redis服务器,执行save命令期间,Redis不能处理其他命令,直到RDB过程完成为止。具体流程如下: +当 Redis 持久化时,程序会将当前内存中的**数据库状态**保存到磁盘中。创建 RDB 文件主要有两个 Redis 命令:`SAVE` 和 `BGSAVE`。 +![Redis-RDB-创建](images/Middleware/Redis-RDB-创建.png) -![RDB-save命令](images/Middleware/RDB-save.jpg) -由于 `save` 命令是同步命令,会占用Redis的主进程。若Redis数据非常多时,`save`命令执行速度会非常慢,阻塞所有客户端的请求。因此很少在生产环境直接使用SAVE 命令,可以使用BGSAVE 命令代替。如果在BGSAVE命令的保存数据的子进程发生错误的时,用 SAVE命令保存最新的数据是最后的手段。 +**② 载入** + +服务器在载入 RDB 文件期间,会一直处于**阻塞**状态,直到载入工作完成为止。 +![Redis-RDB-载入](images/Middleware/Redis-RDB-载入.png) -#### bgsave触发方式 -`bgsave` 命令(异步保存数据到磁盘上)是执行一个异步操作,以RDB文件的方式保存所有数据的快照。Redis会在后台异步进行快照操作,快照同时还可以响应客户端请求。具体流程如下: +#### save同步保存 +`save` 命令是同步操作,执行命令时,会 **阻塞** Redis 服务器进程,拒绝客户端发送的命令请求。 +具体流程如下: -![RDB-gbsave](images/Middleware/RDB-gbsave.jpg) +![Redis-RDB-Save命令](images/Middleware/Redis-RDB-Save命令.png) + +由于 `save` 命令是同步命令,会占用Redis的主进程。若Redis数据非常多时,`save`命令执行速度会非常慢,阻塞所有客户端的请求。因此很少在生产环境直接使用SAVE 命令,可以使用BGSAVE 命令代替。如果在BGSAVE命令的保存数据的子进程发生错误的时,用 SAVE命令保存最新的数据是最后的手段。 + + + +#### bgsave异步保存 + +`bgsave` 命令是异步操作,执行命令时,子进程执行保存工作,服务器还可以继续让主线程**处理**客户端发送的命令请求。 + +具体流程如下: + +![Redis-RDB-BgSave命令](images/Middleware/Redis-RDB-BgSave命令.png) Redis使用Linux系统的`fock()`生成一个子进程来将DB数据保存到磁盘,主进程继续提供服务以供客户端调用。如果操作成功,可以通过客户端命令LASTSAVE来检查操作结果。 -#### 自动触发方式 +#### 自动保存 -可以通过配置文件对 Redis 进行设置, 让它在“ N 秒内数据集至少有 M 个改动”这一条件被满足时, 自动进行数据集保存操作: +可通过配置文件对 Redis 进行设置, 让它在“ N 秒内数据集至少有 M 个改动”这一条件被满足时, 自动进行数据集保存操作: ```shell # RDB自动持久化规则 @@ -642,53 +670,147 @@ rdbchecksum yes -### AOF机制(Append Only File) +#### 默认配置 -AOF(Append-only file)持久化方式:是指所有的命令行记录以 Redis 命令请求协议的格式完全持久化存储保存为 aof 文件。 +RDB 文件默认的配置如下: + +```properties +################################ SNAPSHOTTING ################################ +# +# Save the DB on disk: +# 在给定的秒数和给定的对数据库的写操作数下,自动持久化操作。 +# save +# +save 900 1 +save 300 10 +save 60 10000 +#bgsave发生错误时是否停止写入,一般为yes +stop-writes-on-bgsave-error yes +#持久化时是否使用LZF压缩字符串对象? +rdbcompression yes +#是否对rdb文件进行校验和检验,通常为yes +rdbchecksum yes +# RDB持久化文件名 +dbfilename dump.rdb +#持久化文件存储目录 +dir ./ +``` -AOF 机制对每条写入命令作为日志,以 append-only 的模式写入一个日志文件中,因为这个模式是**只追加**的方式,所以没有任何磁盘寻址的开销,所以很快,有点像 Mysql 中的binlog,AOF更适合做热备。 -优点: -- AOF是一秒一次去通过一个后台的线程fsync操作,数据丢失不用怕 +### AOF模式(日志追加) -缺点: +AOF(Append Only File,**追加日志文件**)持久化方式:是指所有的命令行记录以 Redis 命令请求协议的格式完全持久化存储保存为 aof 文件。Redis 是先执行命令,把数据写入内存,然后才记录日志。因为该模式是**只追加**的方式,所以没有任何磁盘寻址的开销,所以很快,有点像 Mysql 中的binlog,AOF更适合做热备。 -- 对于相同数量的数据集而言,AOF文件通常要大于RDB文件。RDB 在**恢复**大数据集时的速度比 AOF 的恢复速度要快 -- 根据同步策略的不同,AOF在运行效率上往往会慢于RDB。总之每秒同步策略的效率是比较高的 +![Redis-AOF](images/Middleware/Redis-AOF.png) -**AOF整个流程分两步**: +**优点** -- 第一步是命令的实时写入,不同级别可能有1秒数据损失。命令先追加到`aof_buf`然后再同步到AO磁盘,**如果实时写入磁盘会带来非常高的磁盘IO,影响整体性能** -- 第二步是对aof文件的**重写**,目的是为了减少AOF文件的大小,可以自动触发或者手动触发(**BGREWRITEAOF**),是Fork出子进程操作,期间Redis服务仍可用 +- 数据更完整,安全性更高,秒级数据丢失(取决fsync策略,如果是everysec,最多丢失1秒的数据) +- AOF文件是一个只进行追加的日志文件,且写入操作是以Redis协议的格式保存的,内容是可读的,适合误删紧急恢复 -![AOF](images/Middleware/AOF.jpg) +**缺点** -- 在重写期间,由于主进程依然在响应命令,为了保证最终备份的完整性;它`依然会写入旧`的AOF中,如果重写失败,能够保证数据不丢失 -- 为了把重写期间响应的写入信息也写入到新的文件中,因此也会`为子进程保留一个buf`,防止新写的file丢失数据 -- 重写是直接把`当前内存的数据生成对应命令`,并不需要读取老的AOF文件进行分析、命令合并 -- **无论是 RDB 还是 AOF 都是先写入一个临时文件,然后通过`rename`完成文件的替换工作**。 +- 对于相同的数据集,AOF文件的体积要大于RDB文件,数据恢复也会比较慢 +- 根据所使用的fsync策略,AOF的速度可能会慢于RDB。 不过在一般情况下,每秒fsync的性能依然非常高 -关于Fork的建议: -- 降低fork的频率,比如可以手动来触发RDB生成快照、与AOF重写 -- 控制Redis最大使用内存,防止fork耗时过长 -- 配置牛逼点,合理配置Linux的内存分配策略,避免因为物理内存不足导致fork失败 -- Redis在执行`BGSAVE`和`BGREWRITEAOF`命令时,哈希表的负载因子>=5,而未执行这两个命令时>=1。目的是**尽量减少写操作**,避免不必要的内存写入操作 -- **哈希表的扩展因子**:哈希表已保存节点数量 / 哈希表大小。因子决定了是否扩展哈希表 +#### 持久化流程 +![Redis-AOF持久化流程](images/Middleware/Redis-AOF持久化流程.png) -AOF(Append-only file)持久化方式:是指所有的命令行记录以 Redis 命令请求协议的格式完全持久化存储保存为 aof 文件。 +**① 命令追加** -- 优点 - - 数据安全,aof 持久化可以配置 appendfsync 属性,有 always,每进行一次命令操作就记录到 aof 文件中一次 - - 通过 append 模式写文件,即使中途服务器宕机,可以通过 redis-check-aof 工具解决数据一致性问题 - - AOF 机制的 rewrite 模式。AOF 文件没被 rewrite 之前(文件过大时会对命令进行合并重写),可以删除其中的某些命令(比如误操作的 flushall) +若 AOF 持久化功能处于打开状态,服务器在执行完一个命令后,会以协议格式将被执行的写命令**追加**到服务器状态的 `aof_buf` 缓冲区的末尾。 -- 缺点 - - AOF 文件比 RDB 文件大,且恢复速度慢 - - 数据集大的时候,比 RDB 启动效率低 + + +**② 文件同步** + +服务器每次结束一个事件循环之前,都会调用 `flushAppendOnlyFile` 函数,这个函数会考虑是否需要将 `aof_buf` 缓冲区中的内容**写入和保存**到 AOF 文件里。`flushAppendOnlyFile` 函数执行以下流程: + +- WRITE:根据条件,将 aof_buf 中的**缓存**写入到 AOF 文件; +- SAVE:根据条件,调用 fsync 或 fdatasync 函数,将 AOF 文件保存到**磁盘**中。 + +这个函数是由服务器配置的 `appendfsync` 的三个值:`always、everysec、no` 来影响的,也被称为三种策略: + +- **always**:每条**命令**都会 fsync 到硬盘中,这样 redis 的写入数据就不会丢失。 + + ![Redis-AOF-Always](images/Middleware/Redis-AOF-Always.png) + +- **everysec**:**每秒**都会刷新缓冲区到硬盘中(默认值)。 + + ![Redis-AOF-Everysec](images/Middleware/Redis-AOF-Everysec.png) + +- **no**:根据当前**操作系统**的规则决定什么时候刷新到硬盘中,不需要我们来考虑。 + + ![Redis-AOF-No](images/Middleware/Redis-AOF-No.png) + + + +**数据加载** + +- 创建一个不带网络连接的伪客户端 +- 从 AOF 文件中分析并读取出一条写命令 +- 使用伪客户端执行被读出的写命令 +- 一直执行步骤 2 和 3,直到 AOF 文件中的所有写命令都被**处理完毕**为止 + + + +#### 文件重写 + +**为何需要文件重写** + +- 为了解决 AOF 文件**体积膨胀**的问题 +- 通过重写创建一个新的 AOF 文件来替代现有的 AOF 文件,新的 AOF 文件不会包含任何浪费空间的冗余命令 + + + +**文件重写的实现原理** + +- 不需要对现有的 AOF 文件进行任何操作 +- 从数据库中直接读取键现在的值 +- 用一条命令记录键值对,从而**代替**之前记录这个键值对的多条命令 + + + +**后台重写** + +为不阻塞父进程,Redis将AOF重写程序放到**子进程**里执行。在子进程执行AOF重写期间,服务器进程需要执行三个流程: + +- 执行客户端发来的命令 +- 将执行后的写命令追加到 AOF 缓冲区 +- 将执行后的写命令追加到 AOF 重写缓冲区 + +![Redis-AOF-后台重写](images/Middleware/Redis-AOF-后台重写.png) + + + +#### 默认配置 + +AOF 文件默认的配置如下: + +```properties +############################## APPEND ONLY MODE ############################### +#开启AOF持久化方式 +appendonly no +#AOF持久化文件名 +appendfilename "appendonly.aof" +#每秒把缓冲区的数据fsync到磁盘 +appendfsync everysec +# appendfsync no +#是否在执行重写时不同步数据到AOF文件 +no-appendfsync-on-rewrite no +# 触发AOF文件执行重写的增长率 +auto-aof-rewrite-percentage 100 +#触发AOF文件执行重写的最小size +auto-aof-rewrite-min-size 64mb +#redis在恢复时,会忽略最后一条可能存在问题的指令 +aof-load-truncated yes +#是否打开混合开关 +aof-use-rdb-preamble yes +``` diff --git a/images/Middleware/Redis-AOF-Always.png b/images/Middleware/Redis-AOF-Always.png new file mode 100644 index 0000000..2018959 Binary files /dev/null and b/images/Middleware/Redis-AOF-Always.png differ diff --git a/images/Middleware/Redis-AOF-Everysec.png b/images/Middleware/Redis-AOF-Everysec.png new file mode 100644 index 0000000..c814bda Binary files /dev/null and b/images/Middleware/Redis-AOF-Everysec.png differ diff --git a/images/Middleware/Redis-AOF-No.png b/images/Middleware/Redis-AOF-No.png new file mode 100644 index 0000000..63fef42 Binary files /dev/null and b/images/Middleware/Redis-AOF-No.png differ diff --git a/images/Middleware/Redis-AOF-后台重写.png b/images/Middleware/Redis-AOF-后台重写.png new file mode 100644 index 0000000..74e7a83 Binary files /dev/null and b/images/Middleware/Redis-AOF-后台重写.png differ diff --git a/images/Middleware/Redis-AOF.png b/images/Middleware/Redis-AOF.png new file mode 100644 index 0000000..17d2796 Binary files /dev/null and b/images/Middleware/Redis-AOF.png differ diff --git a/images/Middleware/Redis-AOF持久化流程.png b/images/Middleware/Redis-AOF持久化流程.png new file mode 100644 index 0000000..0fb3351 Binary files /dev/null and b/images/Middleware/Redis-AOF持久化流程.png differ diff --git a/images/Middleware/Redis-RDB-BgSave命令.png b/images/Middleware/Redis-RDB-BgSave命令.png new file mode 100644 index 0000000..591f152 Binary files /dev/null and b/images/Middleware/Redis-RDB-BgSave命令.png differ diff --git a/images/Middleware/Redis-RDB-Save命令.png b/images/Middleware/Redis-RDB-Save命令.png new file mode 100644 index 0000000..60f6c00 Binary files /dev/null and b/images/Middleware/Redis-RDB-Save命令.png differ diff --git a/images/Middleware/Redis-RDB-创建.png b/images/Middleware/Redis-RDB-创建.png new file mode 100644 index 0000000..b492f8d Binary files /dev/null and b/images/Middleware/Redis-RDB-创建.png differ diff --git a/images/Middleware/Redis-RDB-载入.png b/images/Middleware/Redis-RDB-载入.png new file mode 100644 index 0000000..7deeda9 Binary files /dev/null and b/images/Middleware/Redis-RDB-载入.png differ