diff --git a/.gitignore b/.gitignore index af0556b..3731d65 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ yarn-error.log* *.njsproj *.sln *.py +*.bat diff --git a/Java/JVM.md b/Java/JVM.md deleted file mode 100644 index b19e730..0000000 --- a/Java/JVM.md +++ /dev/null @@ -1,831 +0,0 @@ - - -> 本文已经收录到github仓库,此仓库用于分享Java相关知识总结,包括Java基础、MySQL、Springboot、mybatis、Redis、rabbitMQ等等,欢迎大家提pr和star! -> -> github地址:https://github.com/Tyson0314/Java-learning -> -> gitee地址:https://gitee.com/tysondai/Java-learning - - - - - -- [内存结构](#%E5%86%85%E5%AD%98%E7%BB%93%E6%9E%84) - - [程序计数器](#%E7%A8%8B%E5%BA%8F%E8%AE%A1%E6%95%B0%E5%99%A8) - - [虚拟机栈](#%E8%99%9A%E6%8B%9F%E6%9C%BA%E6%A0%88) - - [本地方法栈](#%E6%9C%AC%E5%9C%B0%E6%96%B9%E6%B3%95%E6%A0%88) - - [堆](#%E5%A0%86) - - [方法区](#%E6%96%B9%E6%B3%95%E5%8C%BA) - - [永久代](#%E6%B0%B8%E4%B9%85%E4%BB%A3) - - [元空间](#%E5%85%83%E7%A9%BA%E9%97%B4) - - [运行时常量池](#%E8%BF%90%E8%A1%8C%E6%97%B6%E5%B8%B8%E9%87%8F%E6%B1%A0) - - [直接内存](#%E7%9B%B4%E6%8E%A5%E5%86%85%E5%AD%98) - - [对象的访问定位](#%E5%AF%B9%E8%B1%A1%E7%9A%84%E8%AE%BF%E9%97%AE%E5%AE%9A%E4%BD%8D) -- [类文件结构](#%E7%B1%BB%E6%96%87%E4%BB%B6%E7%BB%93%E6%9E%84) -- [类的生命周期](#%E7%B1%BB%E7%9A%84%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F) -- [类加载的过程](#%E7%B1%BB%E5%8A%A0%E8%BD%BD%E7%9A%84%E8%BF%87%E7%A8%8B) - - [加载](#%E5%8A%A0%E8%BD%BD) - - [验证](#%E9%AA%8C%E8%AF%81) - - [准备](#%E5%87%86%E5%A4%87) - - [解析](#%E8%A7%A3%E6%9E%90) - - [初始化](#%E5%88%9D%E5%A7%8B%E5%8C%96) -- [双亲委派模型](#%E5%8F%8C%E4%BA%B2%E5%A7%94%E6%B4%BE%E6%A8%A1%E5%9E%8B) - - [实现](#%E5%AE%9E%E7%8E%B0) -- [对象死亡](#%E5%AF%B9%E8%B1%A1%E6%AD%BB%E4%BA%A1) - - [引用计数法](#%E5%BC%95%E7%94%A8%E8%AE%A1%E6%95%B0%E6%B3%95) - - [可达性分析](#%E5%8F%AF%E8%BE%BE%E6%80%A7%E5%88%86%E6%9E%90) - - [可作为GC Roots的对象](#%E5%8F%AF%E4%BD%9C%E4%B8%BAgc-roots%E7%9A%84%E5%AF%B9%E8%B1%A1) - - [引用](#%E5%BC%95%E7%94%A8) - - [强引用](#%E5%BC%BA%E5%BC%95%E7%94%A8) - - [软引用](#%E8%BD%AF%E5%BC%95%E7%94%A8) - - [弱引用](#%E5%BC%B1%E5%BC%95%E7%94%A8) - - [虚引用](#%E8%99%9A%E5%BC%95%E7%94%A8) - - [常量回收](#%E5%B8%B8%E9%87%8F%E5%9B%9E%E6%94%B6) - - [类的卸载](#%E7%B1%BB%E7%9A%84%E5%8D%B8%E8%BD%BD) -- [内存分配与回收策略](#%E5%86%85%E5%AD%98%E5%88%86%E9%85%8D%E4%B8%8E%E5%9B%9E%E6%94%B6%E7%AD%96%E7%95%A5) - - [Minor GC 和 Full GC](#minor-gc-%E5%92%8C-full-gc) - - [内存分配策略](#%E5%86%85%E5%AD%98%E5%88%86%E9%85%8D%E7%AD%96%E7%95%A5) - - [对象优先在 Eden 分配](#%E5%AF%B9%E8%B1%A1%E4%BC%98%E5%85%88%E5%9C%A8-eden-%E5%88%86%E9%85%8D) - - [大对象直接进入老年代](#%E5%A4%A7%E5%AF%B9%E8%B1%A1%E7%9B%B4%E6%8E%A5%E8%BF%9B%E5%85%A5%E8%80%81%E5%B9%B4%E4%BB%A3) - - [长期存活的对象进入老年代](#%E9%95%BF%E6%9C%9F%E5%AD%98%E6%B4%BB%E7%9A%84%E5%AF%B9%E8%B1%A1%E8%BF%9B%E5%85%A5%E8%80%81%E5%B9%B4%E4%BB%A3) - - [动态对象年龄判定](#%E5%8A%A8%E6%80%81%E5%AF%B9%E8%B1%A1%E5%B9%B4%E9%BE%84%E5%88%A4%E5%AE%9A) - - [空间分配担保](#%E7%A9%BA%E9%97%B4%E5%88%86%E9%85%8D%E6%8B%85%E4%BF%9D) - - [Full GC 的触发条件](#full-gc-%E7%9A%84%E8%A7%A6%E5%8F%91%E6%9D%A1%E4%BB%B6) - - [调用 System.gc()](#%E8%B0%83%E7%94%A8-systemgc) - - [老年代空间不足](#%E8%80%81%E5%B9%B4%E4%BB%A3%E7%A9%BA%E9%97%B4%E4%B8%8D%E8%B6%B3) - - [空间分配担保失败](#%E7%A9%BA%E9%97%B4%E5%88%86%E9%85%8D%E6%8B%85%E4%BF%9D%E5%A4%B1%E8%B4%A5) -- [垃圾回收算法](#%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E7%AE%97%E6%B3%95) - - [标记清除算法](#%E6%A0%87%E8%AE%B0%E6%B8%85%E9%99%A4%E7%AE%97%E6%B3%95) - - [复制清除算法](#%E5%A4%8D%E5%88%B6%E6%B8%85%E9%99%A4%E7%AE%97%E6%B3%95) - - [标记整理算法](#%E6%A0%87%E8%AE%B0%E6%95%B4%E7%90%86%E7%AE%97%E6%B3%95) - - [分类收集算法](#%E5%88%86%E7%B1%BB%E6%94%B6%E9%9B%86%E7%AE%97%E6%B3%95) - - [记忆集](#%E8%AE%B0%E5%BF%86%E9%9B%86) -- [垃圾收集器](#%E5%9E%83%E5%9C%BE%E6%94%B6%E9%9B%86%E5%99%A8) - - [Serial 收集器](#serial-%E6%94%B6%E9%9B%86%E5%99%A8) - - [ParNew 收集器](#parnew-%E6%94%B6%E9%9B%86%E5%99%A8) - - [Parallel Scavenge 收集器](#parallel-scavenge-%E6%94%B6%E9%9B%86%E5%99%A8) - - [Serial Old 收集器](#serial-old-%E6%94%B6%E9%9B%86%E5%99%A8) - - [Parallel Old 收集器](#parallel-old-%E6%94%B6%E9%9B%86%E5%99%A8) - - [CMS 收集器](#cms-%E6%94%B6%E9%9B%86%E5%99%A8) - - [回收过程](#%E5%9B%9E%E6%94%B6%E8%BF%87%E7%A8%8B) - - [CMS垃圾回收特点](#cms%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E7%89%B9%E7%82%B9) - - [G1收集器](#g1%E6%94%B6%E9%9B%86%E5%99%A8) - - [回收过程](#%E5%9B%9E%E6%94%B6%E8%BF%87%E7%A8%8B-1) -- [JVM调优工具](#jvm%E8%B0%83%E4%BC%98%E5%B7%A5%E5%85%B7) - - [jps](#jps) - - [jstack](#jstack) - - [jstat](#jstat) - - [jmap](#jmap) -- [补充](#%E8%A1%A5%E5%85%85) - - [对象头](#%E5%AF%B9%E8%B1%A1%E5%A4%B4) - - [main方法执行过程](#main%E6%96%B9%E6%B3%95%E6%89%A7%E8%A1%8C%E8%BF%87%E7%A8%8B) - - [对象创建过程](#%E5%AF%B9%E8%B1%A1%E5%88%9B%E5%BB%BA%E8%BF%87%E7%A8%8B) -- [参考资料](#%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99) - - - -## 内存结构 - -Java 内存模型(JMM)是基于共享内存的多线程通信机制。 - -JVM内存结构 = 类加载器 + 执行引擎 + 运行时数据区域 。 - -![image-20210905150636105](http://img.dabin-coder.cn/image/image-20210905150636105.png) - -> 图片来源:深入理解Java虚拟机-周志明 - -### 程序计数器 - -程序计数器主要有两个作用: - -1. 当前线程所执行的字节码的行号指示器,通过改变它实现代码的流程控制,如:顺序执行、选择、循环、异常处理。 -2. 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。 - -程序计数器是唯一一个不会出现 `OutOfMemoryError` 的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡。 - -### 虚拟机栈 - -Java 虚拟机栈是由一个个栈帧组成,而每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口信息。每一次函数调用都会有一个对应的栈帧被压入虚拟机栈,每一个函数调用结束后,都会有一个栈帧被弹出。 - -局部变量表是用于存放方法参数和方法内的局部变量。 - -每个栈帧都包含一个指向运行时常量池中该栈所属方法的符号引用,在方法调用过程中,会进行动态链接,将这个符号引用转化为直接引用。 - -- 部分符号引用在类加载阶段的时候就转化为直接引用,这种转化就是静态链接 -- 部分符号引用在运行期间转化为直接引用,这种转化就是动态链接 - -Java 虚拟机栈也是线程私有的,每个线程都有各自的 Java 虚拟机栈,而且随着线程的创建而创建,随着线程的死亡而死亡。Java 虚拟机栈会出现两种错误:`StackOverFlowError` 和 `OutOfMemoryError`。 - -可以通过 -Xss 参数来指定每个线程的 Java 虚拟机栈内存大小,在 JDK 1.4 中默认为 256K,而在 JDK 1.5+ 默认为 1M: - -```java -java -Xss2M -``` - -### 本地方法栈 - -虚拟机栈为虚拟机执行 Java 方法服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。Native 方法一般是用其它语言(C、C++ 或汇编语言等)编写的,并且被编译为基于本机硬件和操作系统的程序,对待这些方法需要特别处理。 - -本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。 - -### 堆 - -此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。Java 堆是垃圾收集器管理的主要区域,因此也被称作GC堆。 - -Java 堆可以细分为:新生代(Eden 空间、From Survivor、To Survivor 空间)和老年代。进一步划分的目的是更好地回收内存,或者更快地分配内存。 - -通过 -Xms设定程序启动时占用内存大小,通过 -Xmx 设定程序运行期间最大可占用的内存大小。如果程序运行需要占用更多的内存,超出了这个设置值,就会抛出OutOfMemory异常。 - -```java -java -Xms1M -Xmx2M -``` - -通过 -Xss 设定每个线程的堆栈大小。设置这个参数,需要评估一个线程大约需要占用多少内存,可能会有多少线程同时运行等。 - -> 在这里也给大家分享一个github仓库,上面放了**200多本经典的计算机书籍**,包括C语言、C++、Java、Python、前端、数据库、操作系统、计算机网络、数据结构和算法、机器学习、编程人生等,可以star一下,下次找书直接在上面搜索,仓库持续更新中~ -> -> github地址:https://github.com/Tyson0314/java-books -> -> 如果github访问不了,可以访问gitee仓库。 -> -> gitee地址:https://gitee.com/tysondai/java-books - -### 方法区 - -方法区与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法区逻辑上属于堆的一部分。 - -对方法区进行垃圾回收的主要目标是对常量池的回收和对类的卸载。 - -#### 永久代 - -方法区是 JVM 的规范,而永久代(PermGen)是方法区的一种实现方式,并且只有 HotSpot 有永久代。而对于其他类型的虚拟机,如 JRockit(Oracle)、J9(IBM) 并没有永久代。由于方法区主要存储类的相关信息,所以对于动态生成类的情况比较容易出现永久代的内存溢出。最典型的场景就是,在 jsp 页面比较多的情况,容易出现永久代内存溢出。 - -#### 元空间 - -JDK 1.8 的时候,HotSpot 的永久代被彻底移除了,使用元空间替代。元空间的本质和永久代类似,都是对JVM规范中方法区的实现。两者最大的区别在于:元空间并不在虚拟机中,而是使用直接内存。 - -为什么要将永久代替换为元空间呢? - -永久代内存受限于 JVM 可用内存,而元空间使用的是直接内存,受本机可用内存的限制,虽然元空间仍旧可能溢出,但是相比永久代内存溢出的概率更小。 - -### 运行时常量池 - -运行时常量池是方法区的一部分,在类加载之后,会将编译器生成的各种字面量和符号引号放到运行时常量池。在运行期间动态生成的常量,如 String 类的 intern()方法,也会被放入运行时常量池。 - -![](http://img.dabin-coder.cn/image/string-new.png) - -![](http://img.dabin-coder.cn/image/string-intern.png) - -![](http://img.dabin-coder.cn/image/string-equal.png) - -> 图片来源:https://blog.csdn.net/soonfly - -### 直接内存 - -直接内存并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用。而且也可能导致 OutOfMemoryError 错误出现。 - -NIO的Buffer提供了DirectBuffer,可以直接访问系统物理内存,避免堆内内存到堆外内存的数据拷贝操作,提高效率。DirectBuffer直接分配在物理内存中,并不占用堆空间,其可申请的最大内存受操作系统限制,不受最大堆内存的限制。 - -直接内存的读写操作比堆内存快,可以提升程序I/O操作的性能。通常在I/O通信过程中,会存在堆内内存到堆外内存的数据拷贝操作,对于需要频繁进行内存间数据拷贝且生命周期较短的暂存数据,都建议存储到直接内存。 - -### 对象的访问定位 - -Java 程序通过栈上的 reference 数据来操作堆上的具体对象。对象的访问方式由虚拟机实现而定,目前主流的访问方式有使用句柄和直接指针两种: - -- 如果使用句柄的话,那么 Java 堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息。使用句柄来访问的最大好处是 reference 中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而 reference 本身不需要修改。 - -![](http://img.dabin-coder.cn/image/object-handle.png) - -- 直接指针。reference 中存储的直接就是对象的地址。对象包含到对象类型数据的指针,通过这个指针可以访问对象类型数据。使用直接指针访问方式最大的好处就是访问对象速度快,它节省了一次指针定位的时间开销,虚拟机hotspot主要是使用直接指针来访问对象。 - -![](http://img.dabin-coder.cn/image/direct-pointer.png) - - - -## 类文件结构 - -Class 文件结构: - -```java -ClassFile { - u4 magic; //Class 文件的标志 - u2 minor_version;//Class 的小版本号 - u2 major_version;//Class 的大版本号 - u2 constant_pool_count;//常量池的数量 - cp_info constant_pool[constant_pool_count-1];//常量池 - u2 access_flags;//Class 的访问标记 - u2 this_class;//当前类 - u2 super_class;//父类 - u2 interfaces_count;//接口 - u2 interfaces[interfaces_count];//一个类可以实现多个接口 - u2 fields_count;//Class 文件的字段属性 - field_info fields[fields_count];//一个类会可以有个字段 - u2 methods_count;//Class 文件的方法数量 - method_info methods[methods_count];//一个类可以有个多个方法 - u2 attributes_count;//此类的属性表中的属性数 - attribute_info attributes[attributes_count];//属性表集合 -} -``` - -魔数:class 文件标志。 - -文件版本:高版本的 Java 虚拟机可以执行低版本编译器生成的 Class 文件,但是低版本的 Java 虚拟机不能执行高版本编译器生成的 Class 文件。 - -常量池:存放字面量和符号引用。字面量类似于Java的常量,如字符串,声明为final的常量值等。符号引用包含三类:类和接口的全限定名,方法的名称和描述符,字段的名称和描述符。 - -访问标志:识别一些类或者接口层次的访问信息,包括:这个 Class 是类还是接口,是否为 public 或者 abstract 类型,如果是类的话是否声明为 final 等等。 - -当前类索引this_class:类索引用于确定这个类的全限定名。 - -属性表集合:在 Class 文件,字段表,方法表中都可以携带自己的属性表集合,以用于描述某些场景专有的信息。与 Class 文件中其它的数据项目要求的顺序、长度和内容不同,属性表集合的限制稍微宽松一些,不再要求各个属性表具有严格的顺序,并且只要不与已有的属性名重复,任何人实现的编译器都可以向属性表中写 入自己定义的属性信息,Java 虚拟机运行时会忽略掉它不认识的属性。 - - - -## 类的生命周期 - -加载、验证、准备、解析、初始化、使用和卸载。 - -![](http://img.dabin-coder.cn/image/image-20210905002423703.png) - -## 类加载的过程 - -类的加载指的是将类的class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个对象,这个对象封装了类在方法区内的数据结构,并且提供了访问方法区内的类信息的接口。 - -### 加载 - -类加载过程的一个阶段:通过一个类的完全限定查找此类字节码文件,并利用字节码文件创建一个Class对象。 - -1. 通过全类名获取定义此类的二进制字节流 -2. 将字节流所代表的静态存储结构转换为方法区的运行时数据结构 -3. 在内存中生成一个代表该类的 Class 对象,作为方法区这些数据的访问入口 - -### 验证 - -确保Class文件的字节流中包含的信息符合虚拟机规范,保证这些信息被当作代码运行后不会危害虚拟机自身的安全。主要包括四种验证:文件格式验证,元数据验证,字节码验证,符号引用验证。 - -### 准备 - -为类变量分配内存并设置类变量初始值的阶段。此阶段进行内存分配的仅包括类变量,不包括实例变量和final修饰的static变量(因为final在编译的时候就会分配了),实例变量会在对象实例化时随着对象一块分配在 Java 堆中。 - -### 解析 - -虚拟机将常量池内的符号引用替换为直接引用的过程。符号引用用于描述目标,直接引用直接指向目标的地址。 - -### 初始化 - -初始化阶段就是执行类构造器\()方法的过程。\()并不是程序员在Java代码中直接编写的方法,它是Javac编译器的自动生成的,由编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并产生的。 - - - -## 双亲委派模型 - -一个类加载器收到一个类的加载请求时,它首先不会自己尝试去加载它,而是把这个请求委派给父类加载器去完成,这样层层委派,因此所有的加载请求最终都会传送到顶层的启动类加载器中,只有当父类加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载。 - -![](http://img.dabin-coder.cn/image/image-20210905002827546.png) - -双亲委派模型的好处:可以防止内存中出现多份同样的字节码。如果没有双亲委派模型而是由各个类加载器自行加载的话,如果用户编写了一个java.lang.Object的同名类并放在ClassPath中,多个类加载器都去加载这个类到内存中,系统中将会出现多个不同的Object类,那么类之间的比较结果及类的唯一性将无法保证。 - -### 实现 - -双亲委派模型的具体实现代码在抽象类 java.lang.ClassLoader 中,此类的 loadClass() 方法运行过程如下:先检查类是否已经加载过,如果没有则让父类加载器去加载。当父类加载器加载失败时抛出 ClassNotFoundException,此时尝试自己去加载。 - -```java -public abstract class ClassLoader { - // The parent class loader for delegation - private final ClassLoader parent; - - public Class loadClass(String name) throws ClassNotFoundException { - return loadClass(name, false); - } - - protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { - synchronized (getClassLoadingLock(name)) { - // First, check if the class has already been loaded - Class c = findLoadedClass(name); - if (c == null) { - try { - if (parent != null) { - c = parent.loadClass(name, false); - } else { - c = findBootstrapClassOrNull(name); - } - } catch (ClassNotFoundException e) { - // ClassNotFoundException thrown if class not found - // from the non-null parent class loader - } - - if (c == null) { - // If still not found, then invoke findClass in order - // to find the class. - c = findClass(name); - } - } - if (resolve) { - resolveClass(c); - } - return c; - } - } - - protected Class findClass(String name) throws ClassNotFoundException { - throw new ClassNotFoundException(name); - } -} -``` - - - -## 对象死亡 - -堆中几乎放着所有的对象实例,对堆垃圾回收前的第一步就是要判断那些对象已经死亡(即不能再被任何途径使用的对象)。 - -![](http://img.dabin-coder.cn/image/object-dead.png) - -### 引用计数法 - -给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加 1;当引用失效,计数器就减 1;任何时候计数器为 0 的对象就是不可能再被使用的。 - -这种方法很难解决对象之间相互循环引用的问题。 - -```java -public class ReferenceCountingGc { - Object instance = null; - public static void main(String[] args) { - ReferenceCountingGc objA = new ReferenceCountingGc(); - ReferenceCountingGc objB = new ReferenceCountingGc(); - objA.instance = objB; - objB.instance = objA; - objA = null; - objB = null; - } -} -``` - -### 可达性分析 - -通过GC Root对象为起点,从这些节点向下搜索,搜索所走过的路径叫引用链,当一个对象到GC Root没有任何的引用链相连时,说明这个对象是不可用的。 - -![](http://img.dabin-coder.cn/image/gc-root-refer.png) - -#### 可作为GC Roots的对象 - -1. 虚拟机栈(栈帧中的本地变量表)中引用的对象 -2. 本地方法栈中JNI(Native方法)引用的对象 -3. 方法区中类静态属性引用的对象 -4. 方法区中常量引用的对象 -5. 所有被同步锁(synchronized关键字)持有的对象。 - -### 引用 - -引用分为强引用、软引用、弱引用、虚引用四种。 - -在程序设计中一般很少使用弱引用与虚引用,使用软引用的情况较多,这是因为软引用可以加速 JVM 对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生。 - -#### 强引用 - -垃圾回收器绝不会回收它。当内存空间不足,Java 虚拟机宁愿抛出 OutOfMemoryError 错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。 - -#### 软引用 - -如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。 - -#### 弱引用 - -在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。 - -#### 虚引用 - -虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。**虚引用主要用来跟踪对象被垃圾回收的活动**。 - -### 常量回收 - -运行时常量池主要回收的是废弃的常量。假如在常量池中存在字符串 "abc",如果当前没有任何 String 对象引用该字符串常量的话,就说明常量 "abc" 就是废弃常量,如果这时发生内存回收的话而且有必要的话,"abc" 就会被系统清理出常量池。 - -### 类的卸载 - -需要同时满足下面 3 个条件才能算是 “无用的类” : - -- 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。 -- 加载该类的 ClassLoader 已经被回收。 -- 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。 - -虚拟机可以对满足上述 3 个条件的无用类进行回收,但不一定被回收。 - - - -## 内存分配与回收策略 - -### Minor GC 和 Full GC - -- Minor GC:回收新生代,因为新生代对象存活时间很短,因此 Minor GC 会频繁执行,执行的速度一般也会比较快。 - -- Full GC:回收老年代和新生代,老年代对象其存活时间长,因此 Full GC 很少执行,执行速度会比 Minor GC 慢很多。 - -### 内存分配策略 - -#### 对象优先在 Eden 分配 - -大多数情况下,对象在新生代 Eden 上分配,当 Eden 空间不够时,发起 Minor GC。 - -#### 大对象直接进入老年代 - -大对象是指需要连续内存空间的对象,最典型的大对象是那种很长的字符串以及数组。经常出现大对象会提前触发垃圾收集以获取足够的连续空间分配给大对象。 - -可以设置JVM参数 -XX:PretenureSizeThreshold,大于此值的对象直接在老年代分配,避免在 Eden 和 Survivor 之间的大量内存复制。 - -#### 长期存活的对象进入老年代 - -通过参数 `-XX:MaxTenuringThreshold` 可以设置对象进入老年代的年龄阈值。对象在 Survivor 中每经过一次 MinorGC,年龄就增加 1 岁,当它的年龄增加到一定程度,就会被晋升到老年代中。 - -#### 动态对象年龄判定 - -虚拟机并不是永远要求对象的年龄必须达到 MaxTenuringThreshold 才能晋升老年代,如果在 Survivor 中相同年龄所有对象大小的总和大于 Survivor 空间的一半,则年龄大于或等于该年龄的对象可以直接进入老年代,无需等到 MaxTenuringThreshold 中要求的年龄。 - -#### 空间分配担保 - -在发生 Minor GC 之前,虚拟机先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果条件成立的话,那么 Minor GC 可以确认是安全的。如果不成立的话虚拟机会查看 HandlePromotionFailure 的值是否允许担保失败。如果允许,那么就会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次 Minor GC;如果小于,或者 HandlePromotionFailure 的值不允许冒险,那么就要进行一次 Full GC。 - -### Full GC 的触发条件 - -对于 Minor GC,其触发条件比较简单,当 Eden 空间满时,就将触发一次 Minor GC。而 Full GC 触发条件相对复杂,有以下条件: - -#### 调用 System.gc() - -只是建议虚拟机执行 Full GC,但是虚拟机不一定真正去执行。不建议使用这种方式,而是让虚拟机管理内存。 - -#### 老年代空间不足 - -老年代空间不足的常见场景为前文所讲的大对象直接进入老年代、长期存活的对象进入老年代等。为了避免以上原因引起的 Full GC,应当尽量不要创建过大的对象以及数组。除此之外,可以通过 -Xmn 参数调大新生代的大小,让对象尽量在新生代被回收掉,不进入老年代。还可以通过 -XX:MaxTenuringThreshold 调大对象进入老年代的年龄,让对象在新生代多存活一段时间。 - -#### 空间分配担保失败 - -使用复制算法的 Minor GC 需要老年代的内存空间作担保,如果担保失败会执行一次 Full GC。 - - - -## 垃圾回收算法 - -### 标记清除算法 - -标记清除算法就是分为“标记”和“清除”两个阶段。标记出所有需要回收的对象,标记结束后统一回收所有被标记的对象。这种垃圾回收算法效率较低,并且会产生大量不连续的空间碎片。 - -![](http://img.dabin-coder.cn/image/image-20210905003458130.png) - -### 复制清除算法 - -半区复制,用于新生代垃圾回收。将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。 - -![](http://img.dabin-coder.cn/image/image-20210905003714551.png) - -特点:实现简单,运行高效,但可用内存缩小为了原来的一半,浪费空间。 - -### 标记整理算法 - -根据老年代的特点提出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉边界以外的内存。 - -### 分类收集算法 - -根据各个年代的特点采用最适当的收集算法。 - -一般将堆分为新生代和老年代。 - -- 新生代使用:复制算法 -- 老年代使用:标记清除算法或者标记整理算法 - -在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清理”或者“标记-整理”算法来进行回收。 - -由于对象之间会存在跨代引用,如果要进行一次新生代垃圾收集,除了需要遍历新生代对象,还要额外遍历整个老年代的所有对象,这会给内存回收带来很大的性能负担。 - -跨代引用相对于同代引用来说仅占极少数。存在互相引用关系的两个对象,是应该倾向于同时生存或者同时消亡的。举个例子,如果某个新生代对象存在跨代引用,由于老年代对象难以消亡,该引用会使得新生代对象在收集时同样得以存活,进而在年龄增长之后晋升到老年代中,这时跨代引用也随即被消除了。 - -所以没必要为了少量的跨代引用去扫描整个老年代,只需在新生代建立一个全局的数据结构 Remembered Set,这个结构把老年代划分成若干小块,标识出老年代的哪一块内存会存在跨代引用。此后当发生Minor GC时,只有包含了跨代引用的小块内存里的对象才会被加入到GC Roots进行扫描。 - -#### 记忆集 - -记忆集是一种用于记录从非收集区域指向收集区域的指针集合的抽象数据结构。Card Table 是最常用的一种记忆集实现形式。字节数组CARD_TABLE的每一个元素都对应着其标识的内存区域中一块特定大小的内存块,这个内存块被称作“卡页”(Card Page)。 - -一个卡页的内存中通常包含不止一个对象,只要卡页内有一个(或更多)对象的字段存在着跨代指针,那就将对应卡表的数组元素的值标识为1,称为这个元素变脏(Dirty),没有则标识为0。在垃圾收集发生时,只要筛选出卡表中变脏的元素,就能轻易得出哪些卡页内存块中包含跨代指针,把它们加入GC Roots中一并扫描。 - - - -## 垃圾收集器 - -经典的垃圾收集器主要有三种类型:串行收集器、并行收集器和并发标记清除收集器CMS,这三种收集器分别可以是满足Java应用三种不同的需求:内存占用及并发开销最小化、应用吞吐量最大化和应用GC暂停时间最小化。 - -JDK1.7和1.8中默认使用的是Parallel Scavenge和Parallel Old收集器组合。jdk1.9 默认垃圾收集器是G1。 - -```java -java -XX:+PrintCommandLineFlags -version -``` - -7个垃圾收集器的特点: - -| 收集器 | 串行、并行or并发 | 新生代/老年代 | 算法 | 目标 | 适用场景 | -| :-------------------: | :--------------: | :-----------: | :----------------: | :----------: | :---------------------------------------: | -| **Serial** | 串行 | 新生代 | 复制算法 | 响应速度优先 | 单CPU环境下的Client模式 | -| **ParNew** | 并行 | 新生代 | 复制算法 | 响应速度优先 | 多CPU环境时在Server模式下与CMS配合 | -| **Parallel Scavenge** | 并行 | 新生代 | 复制算法 | 吞吐量优先 | 在后台运算而不需要太多交互的任务 | -| **Serial Old** | 串行 | 老年代 | 标记-整理 | 响应速度优先 | 单CPU环境下的Client模式、CMS的后备预案 | -| **Parallel Old** | 并行 | 老年代 | 标记-整理 | 吞吐量优先 | 在后台运算而不需要太多交互的任务 | -| **CMS** | 并发 | 老年代 | 标记-清除 | 响应速度优先 | 集中在互联网站或B/S系统服务端上的Java应用 | -| **G1** | 并发 | both | 标记-整理+复制算法 | 响应速度优先 | 面向服务端应用,将来替换CMS | - -### Serial 收集器 - -单线程收集器,使用一条垃圾收集线程去完成垃圾收集工作,在进行垃圾收集工作的时候必须暂停其他所有的工作线程( "Stop The World" ),直到它收集结束。 - -![](http://img.dabin-coder.cn/image/serial-collector.png) - -特点:简单高效;内存消耗最小;没有线程交互的开销,单线程收集效率高;需暂停所有的工作线程,用户体验不好。 - -### ParNew 收集器 - -Serial 收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和 Serial 收集器完全一样。 - -![](http://img.dabin-coder.cn/image/parnew-collector.png) - -除了 Serial 收集器外,只有它能与 CMS 收集器配合工作。 - -### Parallel Scavenge 收集器 - -新生代收集器,基于复制清除算法实现的收集器。吞吐量优先收集器,也是能够并行收集的多线程收集器,允许多个垃圾回收线程同时运行,降低垃圾收集时间,提高吞吐量。所谓吞吐量就是 CPU 中用于运行用户代码的时间与 CPU 总消耗时间的比值(吞吐量 = 运行用户代码时间 /(运行用户代码时间 + 垃圾收集时间))。Parallel Scavenge 收集器关注点是吞吐量,高效率的利用 CPU 资源。CMS 垃圾收集器关注点更多的是用户线程的停顿时间。 - -Parallel Scavenge收集器提供了两个参数用于**精确控制吞吐量**,分别是控制最大垃圾收集停顿时间的-XX:MaxGCPauseMillis参数以及直接设置吞吐量大小的-XX:GCTimeRatio参数。 - --XX:MaxGCPauseMillis参数允许的值是一个大于0的毫秒数,收集器将尽力保证内存回收花费的时间不超过用户设定值。 - --XX:GCTimeRatio参数的值则应当是一个大于0小于100的整数,也就是垃圾收集时间占总时间的比率,相当于吞吐量的倒数。 - -相比ParNew收集器的优点: - -1. 精确控制吞吐量; -2. 垃圾收集的自适应的调节策略。通过参数-XX:+UseAdaptiveSizePolicy 打开自适应调节策略,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整参数以提供最合适的停顿时间或者最大的吞吐量。调整的参数包括新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRatio)、晋升老年代对象大小(-XX:PretenureSizeThreshold)等。 - -### Serial Old 收集器 - -Serial 收集器的老年代版本,它同样是一个单线程收集器,使用标记整理算法。它主要有两大用途:一种用途是在 JDK1.5 以及以前的版本中与 Parallel Scavenge 收集器搭配使用,另一种用途是作为 CMS 收集器的后备方案。 - -### Parallel Old 收集器 - -Parallel Scavenge 收集器的老年代版本。多线程垃圾收集,使用标记-整理算法。在注重吞吐量以及 CPU 资源的场合,都可以优先考虑 Parallel Scavenge 收集器和 Parallel Old 收集器。 - -### CMS 收集器 - -Concurrent Mark Sweep 并发标记清除,目的是获取最短应用停顿时间。第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程基本上同时工作。在并发标记和并发清除阶段,虽然用户线程没有被暂停,但是由于垃圾收集器线程占用了一部分系统资源,应用程序的吞吐量会降低。 - -#### 回收过程 - -基于标记清除算法实现,垃圾收集整个过程分为四个步骤: - -- 初始标记: stw暂停所有的其他线程,记录直接与 gc root 直接相连的对象,速度很快 。 -- 并发标记:从GC Roots开始对堆中对象进行可达性分析,找出存活对象,耗时较长,但是不需要停顿用户线程。 -- 重新标记: 在并发标记期间对象的引用关系可能会变化,需要重新进行标记。此阶段也会stw,停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短。 -- 并发清除:清除死亡对象,由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发的。 - -![](http://img.dabin-coder.cn/image/cms-collector.png) - -由于在整个过程中耗时最长的并发标记和并发清除阶段中,垃圾收集器线程都可以与用户线程一起工作,所以从总体上来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。 - -优点:并发收集,低停顿。 - -缺点: - -- 标记清除算法导致收集结束有大量空间碎片,往往出现老年代空间剩余,但无法找到足够大连续空间来分配当前对象,不得不提前触发一次 Full GC。 -- 会产生浮动垃圾,由于CMS并发清理阶段用户线程还在运行着,会不断有新的垃圾产生,这一部分垃圾出现在标记过程之后,CMS无法在当次收集中处理掉它们,只好等到下一次GC去处理; -- 对处理器资源非常敏感。在并发阶段,收集器占用了一部分线程资源,导致应用程序变慢,降低总吞吐量。 - -#### CMS垃圾回收特点 - -1. cms只会回收老年代和永久代(1.8开始为元数据区,需要设置CMSClassUnloadingEnabled),不会收集年轻代; -2. cms垃圾回收器开始执行回收操作,有一个触发阈值,默认是老年代或永久带达到92%,不能等到old内存用尽时回收,否则会导致并发回收失败。因为需要预留空间给用户线程运行。 - -### G1收集器 - -G1垃圾收集器的目标是用在多核、大内存的机器上,在不同应用场景中追求高吞吐量和低停顿之间的最佳平衡。 - -在G1收集器出现之前的所有其他收集器,包括CMS在内,垃圾收集的目标范围要么是整个新生代(Minor GC),要么就是整个老年代(Major GC),再要么就是整个Java堆(Full GC)。而G1可以面向堆内存任何部分来组成回收集(Collection Set,一般简称CSet)进行回收,衡量标准不再是它属于哪个分代,而是哪块内存中存放的垃圾数量最多,回收收益最大,这就是G1收集器的Mixed GC模式。 - -G1将整个堆分成相同大小的分区(Region),有四种不同类型的分区:Eden、Survivor、Old和Humongous(大对象)。分区的大小取值范围为1M到32M,都是2的幂次方。Region大小可以通过`-XX:G1HeapRegionSize`参数指定。Humongous区域用于存储大对象。G1认为只要大小超过了一个Region容量一半的对象即可判定为大对象。 - -![](http://img.dabin-coder.cn/image/g1-region.jpg) - -G1 收集器对各个Region回收所获得的空间大小和回收所需时间的经验值进行排序,得到一个优先级列表,每次根据用户设置的最大的回收停顿时间(使用参数-XX:MaxGCPauseMillis指定,默认值是200毫秒),优先处理回收价值最大的 Region。 - -Java堆分成多个独立Region,Region里面会存在跨Region引用对象,在垃圾回收寻找GC Roots需要扫描整个堆。G1采用了Rset(Remembered Set)来避免扫描整个堆。每个Region会有一个RSet,记录了哪些Region引用本Region中对象,即谁引用了我的对象,这样的话,在做可达性分析的时候就可以避免全堆扫描。 - -特点:可以由用户指定期望的垃圾收集停顿时间。 - -#### 回收过程 - -G1 收集器的运作大致分为以下几个步骤: - -- 初始标记:。stw暂停所有的其他线程,记录直接与 gc root 直接相连的对象,速度很快 。 -- 并发标记。从GC Root开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。 -- 最终标记。对用户线程做另一个短暂的暂停,用于处理并发阶段对象引用出现变动的区域。 -- 筛选回收。对各个Region的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧的Region的全部空间。这里的操作涉及存活对象的移动,是必须暂停用户线程,由多条收集器线程并行完成的。 - - - -## JVM调优工具 - -### jps - -列出本机所有java进程的pid。 - -选项 - -- -q 仅输出VM标识符,不包括class name,jar name,arguments in main method -- -m 输出main method的参数 -- -l 输出完全的包名,应用主类名,jar的完全路径名 -- -v 输出jvm参数 -- -V 输出通过flag文件传递到JVM中的参数(.hotspotrc文件或-XX:Flags=所指定的文件 -- -Joption 传递参数到vm,例如:-J-Xms48m - -```bash -jps -lvm -//output -//4124 com.zzx.Application -javaagent:E:\IDEA2019\lib\idea_rt.jar=10291:E:\IDEA2019\bin -Dfile.encoding=UTF-8 -``` - -### jstack - -查看某个Java进程内的线程堆栈信息。-l,long listings,打印额外的锁信息,发生死锁时可以使用`jstack -l pid`观察锁持有情况。 - -```java -jstack -l 4124 | more -``` - -output: - -```java -"http-nio-8001-exec-10" #40 daemon prio=5 os_prio=0 tid=0x000000002542f000 nid=0x4028 waiting on condition [0x000000002cc9e000] - java.lang.Thread.State: WAITING (parking) - at sun.misc.Unsafe.park(Native Method) - - parking to wait for <0x000000077420d7e8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject) - at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175) - at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039) - at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442) - at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:103) - at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:31) - at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074) - at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134) - at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) - at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) - at java.lang.Thread.run(Thread.java:748) - - Locked ownable synchronizers: - - None -``` - -### jstat - -虚拟机各种运行状态信息(类装载、内存、垃圾收集、jit编译等运行数据)。gcuitl 查看新生代、老年代及持久代GC的情况。 - -```java -jstat -gcutil 4124 - S0 S1 E O M CCS YGC YGCT FGC FGCT GCT - 0.00 0.00 67.21 19.20 96.36 94.96 10 0.084 3 0.191 0.275 -``` - -### jmap - -查看堆内存快照。查看进程中新生代、老年代、永久代的使用情况。 - -查询进程4124的堆内存快照: - -```java ->jmap -heap 4124 -Attaching to process ID 4124, please wait... -Debugger attached successfully. -Server compiler detected. -JVM version is 25.221-b11 - -using thread-local object allocation. -Parallel GC with 6 thread(s) - -Heap Configuration: - MinHeapFreeRatio = 0 - MaxHeapFreeRatio = 100 - MaxHeapSize = 4238344192 (4042.0MB) - NewSize = 88604672 (84.5MB) - MaxNewSize = 1412431872 (1347.0MB) - OldSize = 177733632 (169.5MB) - NewRatio = 2 - SurvivorRatio = 8 - MetaspaceSize = 21807104 (20.796875MB) - CompressedClassSpaceSize = 1073741824 (1024.0MB) - MaxMetaspaceSize = 17592186044415 MB - G1HeapRegionSize = 0 (0.0MB) - -Heap Usage: -PS Young Generation -Eden Space: - capacity = 327155712 (312.0MB) - used = 223702392 (213.33922576904297MB) - free = 103453320 (98.66077423095703MB) - 68.37795697725736% used -From Space: - capacity = 21495808 (20.5MB) - used = 0 (0.0MB) - free = 21495808 (20.5MB) - 0.0% used -To Space: - capacity = 23068672 (22.0MB) - used = 0 (0.0MB) - free = 23068672 (22.0MB) - 0.0% used -PS Old Generation - capacity = 217579520 (207.5MB) - used = 41781472 (39.845916748046875MB) - free = 175798048 (167.65408325195312MB) - 19.20285144484187% used - -27776 interned Strings occupying 3262336 bytes. -``` - -查询进程pid = 41843 存活的对象占用内存前100排序: `jmap -histo:live 41843 | head -n 100` - - - -## 补充 - -### 对象头 - -Java对象保存在内存中时,由以下三部分组成:对象头、实例数据和对齐填充字节。 - -java的对象头由以下三部分组成:mark word、指向类信息的指针和数组长度(数组对象才有)。 - -mark word包含:对象的hashcode、分代年龄和锁标志位。 - -对象的实例数据就是在java代码中对象的属性和值。 - -对齐填充字节:因为JVM要求java的对象占的内存大小应该是8bit的倍数,所以后面有几个字节用于把对象的大小补齐至8bit的倍数。 - -**内存对齐的主要作用是:** - -1. 平台原因:不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。 -2. 性能原因:经过内存对齐后,CPU的内存访问速度大大提升。 - -### main方法执行过程 - -以下是示例代码: - -```java -public class App { - public static void main(String[] args) { - Student s = new Student("dabin"); - s.getName(); - } -} - -class Student { - public String name; - - public Student(String name) { - this.name = name; - } - - public String getName() { - return this.name; - } -} -``` - -执行main方法的步骤如下: - -1. 编译好 App.java 后得到 App.class 后,执行 App.class,系统会启动一个 JVM 进程,从 classpath 路径中找到一个名为 App.class 的二进制文件,将 App 的类信息加载到运行时数据区的方法区内,这个过程叫做 App 类的加载 -2. JVM 找到 App 的主程序入口,执行main方法 -3. 这个main中的第一条语句为 `Student student = new Student("dabin") `,就是让 JVM 创建一个Student对象,但是这个时候方法区中是没有 Student 类的信息的,所以 JVM 马上加载 Student 类,把 Student 类的信息放到方法区中 -4. 加载完 Student 类后,JVM 在堆中为一个新的 Student 实例分配内存,然后调用构造函数初始化 Student 实例,这个 Student 实例持有 **指向方法区中的 Student 类的类型信息** 的引用 -5. 执行student.getName()时,JVM 根据 student 的引用找到 student 对象,然后根据 student 对象持有的引用定位到方法区中 student 类的类型信息的方法表,获得 getName() 的字节码地址。 -6. 执行getName() - - - -### 对象创建过程 - -1. 类加载检查 - - 虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。 - -2. 分配内存 - - 在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。 - -3. 初始化零值 - - 分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。 - -4. 设置对象头 - - Hotspot 虚拟机的对象头包括两部分信息,第一部分用于存储对象自身的运行时数据(哈希码、GC 分代年龄、锁状态标志等等),另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是那个类的实例。 - -5. 执行init方法 - - 按照Java代码进行初始化。 - - - -## 参考资料 - -- 周志明. 深入理解 Java 虚拟机 [M]. 机械工业出版社 diff --git a/Java/Java web.md b/Java/Java web.md deleted file mode 100644 index b92935b..0000000 --- a/Java/Java web.md +++ /dev/null @@ -1,83 +0,0 @@ - - - - -- [servlet](#servlet) -- [jsp](#jsp) -- [Tomcat](#tomcat) - - [tomcat和netty区别](#tomcat%E5%92%8Cnetty%E5%8C%BA%E5%88%AB) -- [跨域](#%E8%B7%A8%E5%9F%9F) - - [同源策略](#%E5%90%8C%E6%BA%90%E7%AD%96%E7%95%A5) - - [CSRF攻击](#csrf%E6%94%BB%E5%87%BB) -- [statement和prepareStatement](#statement%E5%92%8Cpreparestatement) - - - -## servlet - -servlet接口定义的是一套处理网络请求的规范。servlet运行在服务端,由servlet容器管理,用于生成动态的内容(早期的web技术主要用来浏览静态页面)。 - -Servlet是什么? - -- 运行在Servlet容器(如Tomcat)中的Java类 -- 没有main方法,不能独立运行,必须被部署到Servlet容器中,由容器来实例化和调用Servlet的方法 - -servlet生命周期指它从被web服务器加载到它被销毁的整个过程,分三个阶段: -1. 初始化阶段,调用init()方法 -2. 响应客户请求阶段,调用service()方法 -3. 终止阶段,调用destroy()方法 - -servlet容器:负责接收请求,生成servlet实例用于处理请求(调用service方法),然后将servlet生成的响应数据返回给客户端。 - -![](../img/web/servlet-container.jpg) - - - -## jsp - -Java server pages。当有人请求JSP时,服务器会自动帮我们把JSP中的HTML片段和java代码拼接成静态资源响应给浏览器。也就是说JSP运行在服务器端,但最终发给客户端的都已经是转换好的HTML静态页面(在响应体里)。 - -即:**JSP = HTML + Java片段**(各种标签本质上还是Java片段) - - - -## Tomcat - -Tomcat 是由 Apache 开发的一个 Servlet 容器,实现了对 Servlet 和 JSP 的支持。 - -### tomcat和netty区别 - -Netty和Tomcat最大的区别就在于通信协议,Tomcat是基于Http协议的,他的实质是一个基于http协议的web容器,但是Netty不一样,他能通过编程自定义各种协议,因为netty能够通过codec自己来编码/解码字节流,完成类似redis访问的功能,这就是netty和tomcat最大的不同。 - - - -## 跨域 - -当发送请求时,如果浏览器发现是跨源AJAX请求,就自动在头信息之中,添加一个Origin字段。 - -`Origin: http://api.bob.com` - -对于服务端,如果请求头Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP响应。浏览器收到响应后,发现响应头信息没有包含Access-Control-Allow-Origin字段,就知道出错了,从而抛出一个错误,被XMLHttpRequest的onerror回调函数捕获。注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。 - -如果请求头Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。 - -```java -Access-Control-Allow-Origin: http://api.bob.com -Access-Control-Allow-Credentials: true -Access-Control-Expose-Headers: FooBar -``` - -### 同源策略 - -同domain(或ip),同端口,同协议视为同一个域,一个域内的脚本仅仅具有本域内的权限,也就是本域脚本只能读写本域内的资源,而无法访问其它域的资源。这种安全限制称为同源策略。 - -### CSRF攻击 - -跨域请求有可能被黑客利用来发动 CSRF攻击。CSRF攻击(Cross-site request forgery),跨站请求伪造。攻击者盗用了你的身份,以你的名义发送请求,比如发送邮件,发消息,盗取你的账号,甚至购买商品。 - - - -## statement和prepareStatement -Statement对象每次执行sql,相关数据库都会执行sql语句的编译,prepareStatement是预编译的,支持批处理。 -PreparedStatement是预编译的,对于批量处理可以大大提高效率,也叫JDBC存储过程。 -prepareStatement对象的开销比statement对象开销大,对于一次性操作使用statement更佳。 \ No newline at end of file diff --git "a/Java/Java \347\274\226\347\250\213\346\200\235\346\203\263.md" "b/Java/Java \347\274\226\347\250\213\346\200\235\346\203\263.md" deleted file mode 100644 index 960b2ac..0000000 --- "a/Java/Java \347\274\226\347\250\213\346\200\235\346\203\263.md" +++ /dev/null @@ -1,3916 +0,0 @@ - - - -- [基础知识](#%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86) - - [数据类型](#%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B) - - [整型](#%E6%95%B4%E5%9E%8B) - - [浮点类型](#%E6%B5%AE%E7%82%B9%E7%B1%BB%E5%9E%8B) - - [char 类型](#char-%E7%B1%BB%E5%9E%8B) - - [boolean 类型](#boolean-%E7%B1%BB%E5%9E%8B) - - [大数值](#%E5%A4%A7%E6%95%B0%E5%80%BC) - - [操作符](#%E6%93%8D%E4%BD%9C%E7%AC%A6) - - [注释文档](#%E6%B3%A8%E9%87%8A%E6%96%87%E6%A1%A3) - - [代码规范](#%E4%BB%A3%E7%A0%81%E8%A7%84%E8%8C%83) -- [控制执行流程](#%E6%8E%A7%E5%88%B6%E6%89%A7%E8%A1%8C%E6%B5%81%E7%A8%8B) - - [switch](#switch) - - [break 和 continue 实现 goto](#break-%E5%92%8C-continue-%E5%AE%9E%E7%8E%B0-goto) -- [初始化和清理](#%E5%88%9D%E5%A7%8B%E5%8C%96%E5%92%8C%E6%B8%85%E7%90%86) - - [成员初始化](#%E6%88%90%E5%91%98%E5%88%9D%E5%A7%8B%E5%8C%96) - - [可变参数列表](#%E5%8F%AF%E5%8F%98%E5%8F%82%E6%95%B0%E5%88%97%E8%A1%A8) -- [访问权限控制](#%E8%AE%BF%E9%97%AE%E6%9D%83%E9%99%90%E6%8E%A7%E5%88%B6) - - [访问权限修饰词](#%E8%AE%BF%E9%97%AE%E6%9D%83%E9%99%90%E4%BF%AE%E9%A5%B0%E8%AF%8D) -- [复用类](#%E5%A4%8D%E7%94%A8%E7%B1%BB) - - [继承语法](#%E7%BB%A7%E6%89%BF%E8%AF%AD%E6%B3%95) - - [final 关键字](#final-%E5%85%B3%E9%94%AE%E5%AD%97) - - [初始化及类的加载](#%E5%88%9D%E5%A7%8B%E5%8C%96%E5%8F%8A%E7%B1%BB%E7%9A%84%E5%8A%A0%E8%BD%BD) - - [继承与初始化](#%E7%BB%A7%E6%89%BF%E4%B8%8E%E5%88%9D%E5%A7%8B%E5%8C%96) -- [多态](#%E5%A4%9A%E6%80%81) - - [缺陷:“覆盖”私有方法](#%E7%BC%BA%E9%99%B7%E8%A6%86%E7%9B%96%E7%A7%81%E6%9C%89%E6%96%B9%E6%B3%95) - - [域和静态方法](#%E5%9F%9F%E5%92%8C%E9%9D%99%E6%80%81%E6%96%B9%E6%B3%95) - - [构造器和多态](#%E6%9E%84%E9%80%A0%E5%99%A8%E5%92%8C%E5%A4%9A%E6%80%81) - - [构造器的调用顺序](#%E6%9E%84%E9%80%A0%E5%99%A8%E7%9A%84%E8%B0%83%E7%94%A8%E9%A1%BA%E5%BA%8F) -- [接口](#%E6%8E%A5%E5%8F%A3) - - [抽象类](#%E6%8A%BD%E8%B1%A1%E7%B1%BB) - - [接口的域](#%E6%8E%A5%E5%8F%A3%E7%9A%84%E5%9F%9F) -- [内部类](#%E5%86%85%E9%83%A8%E7%B1%BB) - - [.this 和 .new](#this-%E5%92%8C-new) - - [匿名内部类](#%E5%8C%BF%E5%90%8D%E5%86%85%E9%83%A8%E7%B1%BB) - - [工厂方法](#%E5%B7%A5%E5%8E%82%E6%96%B9%E6%B3%95) - - [嵌套类](#%E5%B5%8C%E5%A5%97%E7%B1%BB) - - [为什么需要内部类](#%E4%B8%BA%E4%BB%80%E4%B9%88%E9%9C%80%E8%A6%81%E5%86%85%E9%83%A8%E7%B1%BB) - - [局部内部类](#%E5%B1%80%E9%83%A8%E5%86%85%E9%83%A8%E7%B1%BB) - - [内部类标识符](#%E5%86%85%E9%83%A8%E7%B1%BB%E6%A0%87%E8%AF%86%E7%AC%A6) -- [容器](#%E5%AE%B9%E5%99%A8) - - [添加一组元素](#%E6%B7%BB%E5%8A%A0%E4%B8%80%E7%BB%84%E5%85%83%E7%B4%A0) - - [迭代器](#%E8%BF%AD%E4%BB%A3%E5%99%A8) - - [LinkedList](#linkedlist) - - [Set](#set) - - [Map](#map) - - [Queue](#queue) - - [PriorityQueue](#priorityqueue) - - [foreach 和 迭代器](#foreach-%E5%92%8C-%E8%BF%AD%E4%BB%A3%E5%99%A8) - - [适配器方法](#%E9%80%82%E9%85%8D%E5%99%A8%E6%96%B9%E6%B3%95) -- [异常、断言和日志](#%E5%BC%82%E5%B8%B8%E6%96%AD%E8%A8%80%E5%92%8C%E6%97%A5%E5%BF%97) - - [异常分类](#%E5%BC%82%E5%B8%B8%E5%88%86%E7%B1%BB) - - [声明异常](#%E5%A3%B0%E6%98%8E%E5%BC%82%E5%B8%B8) - - [捕获异常](#%E6%8D%95%E8%8E%B7%E5%BC%82%E5%B8%B8) - - [带资源的 try 语句](#%E5%B8%A6%E8%B5%84%E6%BA%90%E7%9A%84-try-%E8%AF%AD%E5%8F%A5) - - [断言](#%E6%96%AD%E8%A8%80) - - [启用和禁用断言](#%E5%90%AF%E7%94%A8%E5%92%8C%E7%A6%81%E7%94%A8%E6%96%AD%E8%A8%80) - - [日志](#%E6%97%A5%E5%BF%97) - - [logback](#logback) -- [字符串](#%E5%AD%97%E7%AC%A6%E4%B8%B2) - - [格式化输出](#%E6%A0%BC%E5%BC%8F%E5%8C%96%E8%BE%93%E5%87%BA) - - [printf() 和 format()](#printf-%E5%92%8C-format) - - [Formatter](#formatter) - - [正则表达式](#%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F) - - [基础](#%E5%9F%BA%E7%A1%80) - - [创建正则表达式](#%E5%88%9B%E5%BB%BA%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F) - - [Pattern 和 Matcher](#pattern-%E5%92%8C-matcher) - - [扫描输入](#%E6%89%AB%E6%8F%8F%E8%BE%93%E5%85%A5) - - [Scanner 定界符](#scanner-%E5%AE%9A%E7%95%8C%E7%AC%A6) - - [用正则表达式扫描](#%E7%94%A8%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%89%AB%E6%8F%8F) -- [类型信息](#%E7%B1%BB%E5%9E%8B%E4%BF%A1%E6%81%AF) - - [反射](#%E5%8F%8D%E5%B0%84) -- [泛型](#%E6%B3%9B%E5%9E%8B) - - [类型参数的好处](#%E7%B1%BB%E5%9E%8B%E5%8F%82%E6%95%B0%E7%9A%84%E5%A5%BD%E5%A4%84) - - [泛型类](#%E6%B3%9B%E5%9E%8B%E7%B1%BB) - - [泛型接口](#%E6%B3%9B%E5%9E%8B%E6%8E%A5%E5%8F%A3) - - [泛型方法](#%E6%B3%9B%E5%9E%8B%E6%96%B9%E6%B3%95) - - [可变参数和泛型方法](#%E5%8F%AF%E5%8F%98%E5%8F%82%E6%95%B0%E5%92%8C%E6%B3%9B%E5%9E%8B%E6%96%B9%E6%B3%95) - - [匿名内部类](#%E5%8C%BF%E5%90%8D%E5%86%85%E9%83%A8%E7%B1%BB-1) - - [泛型擦除](#%E6%B3%9B%E5%9E%8B%E6%93%A6%E9%99%A4) - - [类型变量的限定](#%E7%B1%BB%E5%9E%8B%E5%8F%98%E9%87%8F%E7%9A%84%E9%99%90%E5%AE%9A) - - [擦除的问题](#%E6%93%A6%E9%99%A4%E7%9A%84%E9%97%AE%E9%A2%98) - - [边界](#%E8%BE%B9%E7%95%8C) - - [通配符](#%E9%80%9A%E9%85%8D%E7%AC%A6) - - [上界通配符](#%E4%B8%8A%E7%95%8C%E9%80%9A%E9%85%8D%E7%AC%A6) - - [下界通配符](#%E4%B8%8B%E7%95%8C%E9%80%9A%E9%85%8D%E7%AC%A6) -- [数组](#%E6%95%B0%E7%BB%84) - - [复制数组](#%E5%A4%8D%E5%88%B6%E6%95%B0%E7%BB%84) - - [Arrays 工具](#arrays-%E5%B7%A5%E5%85%B7) - - [数组拷贝](#%E6%95%B0%E7%BB%84%E6%8B%B7%E8%B4%9D) - - [数组的比较](#%E6%95%B0%E7%BB%84%E7%9A%84%E6%AF%94%E8%BE%83) - - [数组元素的比较](#%E6%95%B0%E7%BB%84%E5%85%83%E7%B4%A0%E7%9A%84%E6%AF%94%E8%BE%83) - - [数组排序](#%E6%95%B0%E7%BB%84%E6%8E%92%E5%BA%8F) - - [排序数组查找](#%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84%E6%9F%A5%E6%89%BE) -- [容器深入研究](#%E5%AE%B9%E5%99%A8%E6%B7%B1%E5%85%A5%E7%A0%94%E7%A9%B6) - - [填充容器](#%E5%A1%AB%E5%85%85%E5%AE%B9%E5%99%A8) - - [SortedSet](#sortedset) - - [队列](#%E9%98%9F%E5%88%97) - - [优先级队列](#%E4%BC%98%E5%85%88%E7%BA%A7%E9%98%9F%E5%88%97) - - [双向队列](#%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97) - - [LinkedHashMap](#linkedhashmap) - - [Colletions 工具类](#colletions-%E5%B7%A5%E5%85%B7%E7%B1%BB) - - [排序和查询](#%E6%8E%92%E5%BA%8F%E5%92%8C%E6%9F%A5%E8%AF%A2) - - [只读容器](#%E5%8F%AA%E8%AF%BB%E5%AE%B9%E5%99%A8) - - [Collection 和 Map 的同步控制](#collection-%E5%92%8C-map-%E7%9A%84%E5%90%8C%E6%AD%A5%E6%8E%A7%E5%88%B6) - - [快速报错机制](#%E5%BF%AB%E9%80%9F%E6%8A%A5%E9%94%99%E6%9C%BA%E5%88%B6) - - [Java 1.0/1.1 的容器](#java-1011-%E7%9A%84%E5%AE%B9%E5%99%A8) - - [BitSet](#bitset) -- [Java I/O 系统](#java-io-%E7%B3%BB%E7%BB%9F) - - [输入和输出](#%E8%BE%93%E5%85%A5%E5%92%8C%E8%BE%93%E5%87%BA) - - [InputStream 和 OutputStream](#inputstream-%E5%92%8C-outputstream) - - [Reader 和 Writer](#reader-%E5%92%8C-writer) - - [组合输入输出流过滤器](#%E7%BB%84%E5%90%88%E8%BE%93%E5%85%A5%E8%BE%93%E5%87%BA%E6%B5%81%E8%BF%87%E6%BB%A4%E5%99%A8) - - [文本输入和输出](#%E6%96%87%E6%9C%AC%E8%BE%93%E5%85%A5%E5%92%8C%E8%BE%93%E5%87%BA) - - [以文本格式存储对象](#%E4%BB%A5%E6%96%87%E6%9C%AC%E6%A0%BC%E5%BC%8F%E5%AD%98%E5%82%A8%E5%AF%B9%E8%B1%A1) - - [字符编码方式](#%E5%AD%97%E7%AC%A6%E7%BC%96%E7%A0%81%E6%96%B9%E5%BC%8F) - - [读写二进制数据](#%E8%AF%BB%E5%86%99%E4%BA%8C%E8%BF%9B%E5%88%B6%E6%95%B0%E6%8D%AE) - - [DataInput 和 DataOutput](#datainput-%E5%92%8C-dataoutput) - - [随机访问文件](#%E9%9A%8F%E6%9C%BA%E8%AE%BF%E9%97%AE%E6%96%87%E4%BB%B6) - - [序列化](#%E5%BA%8F%E5%88%97%E5%8C%96) - - [操作文件](#%E6%93%8D%E4%BD%9C%E6%96%87%E4%BB%B6) - - [目录列表器](#%E7%9B%AE%E5%BD%95%E5%88%97%E8%A1%A8%E5%99%A8) - - [](#) -- [枚举类型](#%E6%9E%9A%E4%B8%BE%E7%B1%BB%E5%9E%8B) - - [基本 enum 特性](#%E5%9F%BA%E6%9C%AC-enum-%E7%89%B9%E6%80%A7) - - [向 enum 添加新方法](#%E5%90%91-enum-%E6%B7%BB%E5%8A%A0%E6%96%B0%E6%96%B9%E6%B3%95) - - [覆盖 enum 的方法](#%E8%A6%86%E7%9B%96-enum-%E7%9A%84%E6%96%B9%E6%B3%95) - - [Switch 语句中的 enum](#switch-%E8%AF%AD%E5%8F%A5%E4%B8%AD%E7%9A%84-enum) - - [EnumSet](#enumset) - - [源码解析](#%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90) - - [EnumMap](#enummap) -- [注解](#%E6%B3%A8%E8%A7%A3) - - [基本语法](#%E5%9F%BA%E6%9C%AC%E8%AF%AD%E6%B3%95) - - [自定义注解](#%E8%87%AA%E5%AE%9A%E4%B9%89%E6%B3%A8%E8%A7%A3) - - [元注解](#%E5%85%83%E6%B3%A8%E8%A7%A3) - - [编写注解处理器](#%E7%BC%96%E5%86%99%E6%B3%A8%E8%A7%A3%E5%A4%84%E7%90%86%E5%99%A8) - - [注解综合](#%E6%B3%A8%E8%A7%A3%E7%BB%BC%E5%90%88) - - - -## 基础知识 - -### 数据类型 - -#### 整型 - -| 类型 | 存储空间 | -| ----- | -------- | -| int | 4字节 | -| short | 2字节 | -| long | 8字节 | -| byte | 1字节 | - -byte 的取值范围是-128~127。整型的取值范围和运行 Java 代码的机器无关。Java 没有无符号的整型。 - -**数据类型表示:** - -长整型:400L - -十六进制:0xCA - -八进制:020(有前缀0) - -二进制:0b1001(Java 7及以上版本) - -从 Java 7开始,可以为数据字面量添加下划线,如1_000_000,方便易读。Java 编译器会去除这些下划线。 - -#### 浮点类型 - -| 类型 | 存储空间 | -| ------ | -------- | -| float | 4字节 | -| double | 8字节 | - -float 类型的数值会有后缀 F 或者 f (2.01F),没有后缀 F 的浮点数默认为 double 类型(也可以加后缀 D)。浮点数采用二进制系统表示,有些数值会有精度损失,如`System.out.println(2.0 - 1.1)`会输出0.8999999999999999。如果数值计算不允许有任何精度误差,应该使用 BigDecimal。 - -**特殊值** - -正无穷大:Double.POSITIVE_INFINITY - -负无穷大:Double.NEGATIVE_INFINITY - -NaN(不是一个数字):Double.NaN - -#### char 类型 - -Unicode 字符用一个或者两个 char 值描述。Unicode 字符可以表示为十六进制,其范围从/u0000到/uffff。 - -#### boolean 类型 - -布尔类型有两个值:true 和 false。布尔型和整型不能相互转化。Java 语言表达式所操作的 boolean 值,在编译之后都使用Java虚拟机中的int数据类型来代替,而 boolean 数组将会被编码成 Java 虚拟机的 byte 数组,每个元素 boolean 元素占8位”。这样我们可以得出 boolean 类型占了单独使用是4个字节,在数组中又是1个字节。 - -#### 大数值 - -BigInteger 实现了任意精度的整数运算,BigDecimal 实现了任意精度的浮点数运算。 - -```java -BigInteger a = BigInteger.valueOf(100); -BigInteger b = BigInteger.valueOf(100); -BigInteger c = a.add(b); -BigInteger d = c.multiply(b.add(BigInteger.valueOf(2))); -``` - -### 操作符 - -**截尾和舍入** - -```java -float num = 0.7f; -//类型转化对数字进行截尾 -System.out.println((int)num); //0 -//四舍五入操作 -System.out.println(Math.round(num)); //1 -``` - -### 注释文档 - -javadoc 用来提取注释的工具。javadoc 只能为 public 和 protected 成员进行文档注释。 - -### 代码规范 - -可以采用将 public 成员放在开头,后面跟着 protect、包访问权限和 private 成员的创建类的形式,方便类的使用者阅读最为重要的部分(public成员)。 - -```java -public class OrganizedByAccess { - public void method1() {} - public void method2() {} - protected void method3() {} - void method4() {} - private method5() {} - private int i; - //... -} -``` - - - -## 控制执行流程 - -### switch - -break 会跳出 switch 语句,没有 break 语句则会一直往下执行。 - -```java -int i = 1; -switch (i) { - case 0: - System.out.println("i = 0"); - break; - case 1: - System.out.println("i = 1"); - break; - case 2: - System.out.println("i = 2"); - break; - default: - System.out.println("i >= 3"); -} -//输出i = 1 - -switch (i) { - case 0: - System.out.println("i = 0"); - //break; - case 1: - System.out.println("i = 1"); - //break; - case 2: - System.out.println("i = 2"); - //break; - default: - System.out.println("i >= 3"); -} -//输出 -//i = 1 -//i = 2 -//i >= 3 -``` - -### break 和 continue 实现 goto - -```java -public class LabeledFor { - public static void main(String[] args) { - int i = 0; - outer: // Can't have statements here - for(; true ;) { // infinite loop - inner: // Can't have statements here - for(; i < 10; i++) { - print("i = " + i); - if(i == 2) { - print("continue"); - continue; - } - if(i == 3) { - print("break"); - i++; - break;//break跳出循环,不会执行递增操作 - } - if(i == 7) { - print("continue outer"); - i++; - continue outer;//continue跳到循环顶部,不会执行递增操作 - } - if(i == 8) { - print("break outer"); - break outer; - } - for(int k = 0; k < 5; k++) { - if(k == 3) { - print("continue inner"); - continue inner; - } - } - } - } - // Can't break or continue to labels here - } -} -``` - - - -## 初始化和清理 - -### 成员初始化 - -局部变量未初始化就使用,会产生编译时错误。类的数据成员是基本类型,它们会有初值(如 int 类型的数据成员初值为0)。 - -### 可变参数列表 - -Java SE5 开始支持可变参数列表。当指定参数时,编译器会将元素列表转换为数组。 - -```java -public class VarArgs { - public static void printArray(Object... args) { - for(Object obj : args) { - System.out.print(obj + " "); - } - System.out.println(); - } - - public static void main(String[] args) { - printArray(new Integer(1), new Integer(3)); - printArray("tyson", "sophia", "tom"); - printArray(new Integer[]{2, 4, 6}); - printArray();//空列表 - } -} -``` - -## 访问权限控制 - -package 语句必须是文件中第一行非注释代码。 - -### 访问权限修饰词 - -1. 包访问权限 - - 默认的访问权限没有任何关键字,通常是指包访问权限,即当前包的所有其他类对当前成员具有访问权限。 - -2. protected - - 继承访问权限,派生类可以访问基类的 protected 元素。同时,protected 也提供包访问权限,即相同包内的其他类也可以访问 protected 元素。 - -3. private - - 通过 private 隐藏了代码细节,类库设计者可以更改类内部工作方式,而不会影响客户端。 - -4. public - - - -示例代码: - -```java -package com.tyson.chapter6_access; -//Cookie.java -public class Cookie { - public Cookie() { - System.out.println("Cookie constructor"); - } - protected void bite() { - System.out.println("bite"); - } -} - -//ChocolateChip.java -public class ChocolateChip extends Cookie { - //私有构造器 - private ChocolateChip() { - System.out.println("chocolate chip constructor"); - } - //通过静态方法创建类 - public static ChocolateChip createChocolateChip() { - return new ChocolateChip(); - } - - public void chomp() { - //访问父类的protected成员 - bite(); - } -} - -class Application { - public static void main(String[] args) { - ChocolateChip chocolateChip = ChocolateChip.createChocolateChip(); - chocolateChip.chomp(); - } -} -``` - - - -## 复用类 - -### 继承语法 - -派生类如果没有指定某个基类的构造器,则会调用基类的默认构造器,若没有默认的构造器(不带参数的构造器),编译器会报错。 - -### final 关键字 - -1. final 数据 - - 基本类型变量用 final 修饰,则该变量是常量,数值不能改变;对象引用用 final 修饰,则一旦该引用被初始化指向一个对象,就不能再把它改为指向另一个对象,不过对象自身可以修改,只是引用不能修改。 - -2. final 参数 - - 将参数指明为 final,则无法在方法中更改参数。 - -```java -public class FinalArguments { - void add(final Num num) { - //num = new Num(1); //不能更改final参数 - num.i++; - } - void add(final int i) { - //i++; //不能更改final参数的值 - } -} - -class Num { - int i; - public Num(int i) { - this.i = i; - } -} -``` - -3. final 方法 - - final 方法不能被重写,使用 final 方法主要是为了防止继承类修改它的含义。过去使用 final 方法效率会高点,现在的 JVM 相比之前有了很大的优化,没必要通过设置 final 来提高效率。 - -4. final 类 - - final 类不能被继承。final 类的所有方法都被隐式指定为 final。JDK 的 String 类就是 final 类。 - -### 初始化及类的加载 - -#### 继承与初始化 - -```java -class Insect { - private int i = printInit("Insect.i initialized"); - protected int j; - Insect() { - System.out.println("i = " + i + ", j = " + j); - j = 39; - } - private static int x1 = - printInit("static Insect.x1 initialized"); - static int printInit(String s) { - System.out.println(s); - return 47; - } -} - -public class Beetle extends Insect { - private int k = printInit("Beetle.k initialized"); - public Beetle() { - System.out.println("k = " + k + ", j = " + j); - } - private static int x2 = - printInit("static Beetle.x2 initialized"); - public static void main(String[] args) { - System.out.println("Beetle constructor"); - Beetle b = new Beetle(); - } -} -/*output -static Insect.x1 initialized -static Beetle.x2 initialized -Beetle constructor -Insect.i initialized -i = 47, j = 0 -Beetle.k initialized -k = 47, j = 39 - */ -``` - - - -## 多态 - -封装把接口和实现分离开来,多态消除类型之间的耦合关系。 - -向上转型:将子类看作是它的基类的过程。子类转型为基类就是在继承图上向上移动。 - -![向上转型](https://img2018.cnblogs.com/blog/1252910/201904/1252910-20190402204948375-70203256.png) - -### 缺陷:“覆盖”私有方法 - -```java -public class PrivateOverride { - private void f() { - System.out.println("private f()"); - } - - public static void main(String[] args) { - PrivateOverride po = new Derived(); - po.f(); - } -} - -class Derived extends PrivateOverride { - public void f() { - System.out.println("public f()"); - } -} -/* -private f() - */ -``` - -Derived 类的 f() 实际上是一个全新的方法。基类的 f() 方法在子类 Derived 中不可见,因此不能被重载。 - -### 域和静态方法 - -只有普通的方法调用具备多态性,域和静态方法没有多态性。 - -### 构造器和多态 - -构造器实际上是 static 方法,只不过 static 声明是隐式的,不具备多态性。 - -#### 构造器的调用顺序 - -```java -class Meal { - private int weight = get(); - Meal() { - print("Meal()"); - } - public int get() { - System.out.println("Meal.get()"); - return 1; - } -} - -class Bread { - Bread() { - print("Bread()"); - } -} - -class Cheese { - Cheese() { - print("Cheese()"); - } -} - -class Lettuce { - Lettuce() { - print("Lettuce()"); - } -} - -class Lunch extends Meal { - private int price = food(); - Lunch() { - print("Lunch()"); - } - public int food() { - System.out.println("Lunch.food()"); - return 1; - } -} - -class PortableLunch extends Lunch { - PortableLunch() { - print("PortableLunch()"); - } -} - -public class Sandwich extends PortableLunch { - private Bread b = new Bread(); - private Cheese c = new Cheese(); - private Lettuce l = new Lettuce(); - - public Sandwich() { - print("Sandwich()"); - } - - public static void main(String[] args) { - new Sandwich(); - } -} /* Output: -Meal.get() -Meal() -Lunch.food() -Lunch() -PortableLunch() -Bread() -Cheese() -Lettuce() -Sandwich() -*/ - -``` - -调用顺序: - -1. 按照声明顺序调用基类成员的初始化方法 -2. 基类构造器。从最顶层的基类到最底层的派生类 -3. 按照声明顺序调用成员的初始化方法 -4. 派生类构造器 - - - -## 接口 - -接口和内部类为我们提供了一种将接口与实现分离的方法。 - -### 抽象类 - -从一个抽象类继承,需要为基类中的所有方法提供方法定义。如果没有提供,则派生类也是抽象类。 - -如果有一个类,让其包含任何 abstract 方法都没有实际意义,而我们又想阻止产生这个类的任何对象,这时候可以把它设置成一个没有任何抽象方法的抽象类。 - -### 接口的域 - -接口中的域都自动声明为`public static final`,故接口可以用来创建常量组。Java SE5之前没有提供 enum 实现,可以通过接口实现 enum 的功能。 - -## 内部类 - -内部类允许把一些逻辑相关的类组织在一起,并控制内部类的可视性。内部类可以访问外围类,有些通过编写内部类可以让代码结构更清晰。 - -### .this 和 .new - -使用外部类的名字后面紧跟 .this,可以生成对外部类对象的引用。 - -```java -public class DotThis { - void f() { - System.out.println("DotThis.f()"); - } - public class Inner { - public DotThis outer() { - return DotThis.this; - } - } - public Inner inner() { - return new Inner(); - } - - public static void main(String[] args) { - DotThis dt = new DotThis(); - DotThis.Inner dti = dt.inner(); - dti.outer().f(); - } -} -//output DotThis.f() -``` - -使用 .new 创建内部类的对象。 - -```java -public class DotNew { - public class Inner {} - - public static void main(String[] args) { - DotNew dn = new DotNew(); - DotNew.Inner dni = dn.new Inner(); - } -} -``` - -创建内部类的对象,必须通过外部类的对象来创建,在拥有外部类对象之前是不可能创建内部类对象的。这是因为内部类对象会隐式连接到创建它的外部类对象上。但是如果创建的是嵌套类(静态内部类),则不需要创建外部类对象。 - -### 匿名内部类 - -```java -//Wrapping.java -public class Wrapping { - private int i; - - public Wrapping(int x) { - i = x; - } - public int value() { - return i; - } -} - -//Parcel.java -public class Parcel { - public Wrapping wrapping(int x) { - return new Wrapping(x) {//传递构造器参数 - @Override - public int value() { - return super.value() * 47; - } - }; - } - - public static void main(String[] args) { - Parcel p = new Parcel(); - Wrapping w = p.wrapping(8); - System.out.println(w.value()); - } -} -//output 376 -``` - -在匿名类定义字段时,还能够对其执行初始化操作。 - -```java -//Destination.java -public interface Destination { - String readLabel(); -} - -//Parcel1.java -public class Parcel1 { - public Destination destination(final String dest) { - return new Destination() { - private String label = dest; - @Override - public String readLabel() { - return label; - } - }; - } - - public static void main(String[] args) { - Parcel1 p1 = new Parcel1(); - Destination d = p1.destination("Puning"); - System.out.println(d.readLabel()); - } -} -``` - -匿名内部类没有命名构造器,通过实例初始化给匿名内部类创建构造器的效果。 - -```java -abstract class Base { - public Base(int i) { - System.out.println("Base constructor, i = " + i); - } - - public abstract void f(); -} - -public class AnonymousConstructor { - public static Base getBase(int i) { - return new Base(i) { - //构造器的效果 - { - System.out.println("Inside instance initializer"); - } - @Override - public void f() { - System.out.println("In anonymous f()"); - } - }; - } - - public static void main(String[] args) { - Base base = getBase(47); - base.f(); - } -} /* Output: -Base constructor, i = 47 -Inside instance initializer -In anonymous f() -*/ -``` - -#### 工厂方法 - -使用匿名内部类改进工厂方法。 - -```java -interface Game { boolean move();} -interface GameFactory { Game getGame();} - -public class Games { - public static void playGame(GameFactory gameFactory) { - Game game = gameFactory.getGame(); - while(game.move()) { - ; - } - } - - public static void main(String[] args) { - playGame(Checkers.factory); - playGame(Chess.factory); - } -} - -class Chess implements Game { - - private Chess() {} - private int moves = 0; - private static final int MOVES = 4; - - @Override - public boolean move() { - System.out.println("chess move " + moves); - return ++moves != MOVES; - } - - public static GameFactory factory = new GameFactory() { - @Override - public Game getGame() { - return new Chess(); - } - }; -} - -class Checkers implements Game { - private Checkers() {} - private int moves = 0; - private static final int MOVES = 3; - @Override - public boolean move() { - System.out.println("checkers move" + moves); - return ++moves != MOVES; - } - - public static GameFactory factory = new GameFactory() { - @Override - public Game getGame() { - return new Checkers(); - } - }; -} -/* -checkers move0 -checkers move1 -checkers move2 -chess move 0 -chess move 1 -chess move 2 -chess move 3 - */ -``` - -### 嵌套类 - -将内部类声明为 static,即为嵌套类。普通的内部类隐式保存了一个指向外围类对象的引用,而嵌套类没有。所以创建嵌套类的对象,不需要先创建外围类对象。并且嵌套类不能访问非静态的外围类的成员。 - -普通的内部类不能包含 static 字段和 static 方法,也不能包含嵌套类,但嵌套类可以包含这些。 - -```java -public class NestingClass { - public static class StaticInner { - public void dynamicFuc() { - System.out.println("StaticInner动态方法"); - } - public static void staticFuc() { - System.out.println("StaticInner静态方法"); - } - } - - public static void main(String[] args) { - StaticInner si = new StaticInner(); - si.dynamicFuc(); - StaticInner.staticFuc();//直接通过类名调用静态方法 - } -} -/* -StaticInner动态方法 -StaticInner静态方法 - */ -``` - -嵌套类没有.this引用。 - -### 为什么需要内部类 - -使用内部类可以实现多重继承。 - -```java -interface Selector { - boolean end(); - Object current(); - void next(); -} - -public class Sequence { - private Object[] items; - private int next = 0; - - public Sequence(int size) { - items = new Object[size]; - } - - public void add(Object x) { - if (next < items.length) - items[next++] = x; - } - - private class SequenceSelector implements Selector { - private int i = 0; - - public boolean end() { - return i == items.length; - } - public Object current() { - return items[i]; - } - public void next() { - if (i < items.length) i++; - } - } - - public Selector selector() { - return new SequenceSelector(); - } - - public static void main(String[] args) { - Sequence sequence = new Sequence(10); - for (int i = 0; i < 10; i++) - sequence.add(Integer.toString(i)); - Selector selector = sequence.selector(); - while (!selector.end()) { - System.out.print(selector.current() + " "); - selector.next(); - } - } -} /* Output: -0 1 2 3 4 5 6 7 8 9 -*/ -``` - -如果 Sequence.java 不使用内部类,就必须声明 Sequence 是一个 Selector,对于某个特定的 Sequence 只能有一个 Selector。使用内部类的话很容易就可以拥有另一个方法 reverseSelector(),用来生成一个反向遍历序列的 Selector。 - -### 局部内部类 - -```java -interface Counter {int next();} - -public class LocalInnerClass { - private int count = 0; - - Counter getCounter(final String name) { - // A local inner class: - class LocalCounter implements Counter { - public LocalCounter() { - // Local inner class can have a constructor - print("LocalCounter()"); - } - - public int next() { - printnb(name); // Access local final - return count++; - } - } - return new LocalCounter(); - } - - // The same thing with an anonymous inner class: - Counter getCounter2(final String name) { - return new Counter() { - // Anonymous inner class cannot have a named - // constructor, only an instance initializer: - { - print("Counter()"); - } - - public int next() { - printnb(name); // Access local final - return count++; - } - }; - } - - public static void main(String[] args) { - LocalInnerClass lic = new LocalInnerClass(); - Counter c1 = lic.getCounter("Local inner "), - c2 = lic.getCounter2("Anonymous inner "); - print(c1.next()); - print(c2.next()); - } -} /* Output: -LocalCounter() -Counter() -Local inner 0 -Anonymous inner 1 -*/ -``` - -局部内部类可以拥有命名的构造器或者重载构造器,而匿名内部类只能使用实例初始化。如果需要不止一个该内部类对象,那么只能使用局部内部类。 - -### 内部类标识符 - -内部类文件的命名格式:外围类名字加上"$",再加上内部类的名字(如果是匿名类,则是一个数字)。 - -`LocalInnerClass$1LocalCounter.class` - -`LocalInnerClass$1.class` - - - -## 容器 - -![容器分类](https://img2018.cnblogs.com/blog/1252910/201904/1252910-20190407155626374-53010434.png) - -### 添加一组元素 - -```java -public class AddingGroups { - public static void main(String[] args) { - Collection nums = new ArrayList<>(Arrays.asList(1, 2, 3, 4)); - Integer[] ints = {5, 6, 7}; - nums.addAll(Arrays.asList(ints)); - Collections.addAll(nums, 8, 9); - Collections.addAll(nums, ints); - - List list = Arrays.asList(1, 2, 3); - list.set(1, 90); - System.out.println(list.getClass()); - //底层是数组java.util.Arrays$ArrayList,不能修改结构,java.lang.UnsupportedOperationException - //list.add(7); - } -} -``` - -`Arrays.asList()`返回类型是`java.util.Arrays$ArrayList`,底层是数组,试图删除或增加元素会抛异常 UnsupportedOperationException。 - -### 迭代器 - -```java -import typeinfo.pets.Pet; -import typeinfo.pets.Pets; -import java.util.Iterator; -import java.util.List; - -public class SimpleIteration { - public static void main(String[] args) { - List pets = Pets.arrayList(8); - Iterator it = pets.iterator(); - while(it.hasNext()) { - Pet p = it.next(); - System.out.print(p.id() + ":" + p + " "); - } - System.out.println(); - for(Pet p : pets) { - System.out.print(p.id() + ":" + p + " "); - } - System.out.println(); - it = pets.iterator(); - for(int i = 0; i < 4; i++) { - it.next(); - it.remove();//删除最近遍历的元素 - } - System.out.println(pets); - } -} -/*output -0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx -0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx -[Pug, Cymric, Pug, Manx] - */ -``` - -ListIterator 是一个更为强大的 Iterator 子类型,它只能用于各种 List 类的遍历。ListIterator 可以双向移动。它还可以产生迭代器当前位置前一个和后一个元素的索引,并且可以使用 set() 方法修改最近遍历的元素。通过 listIterator(index) 可以创建指向索引为index的元素的ListIterator。 - -```java -public class ListIteration { - public static void main(String[] args) { - List pets = Pets.arrayList(8); - ListIterator it = pets.listIterator(); - while(it.hasNext()) { - System.out.print(it.next() + ", " + it.nextIndex() + - ", " + it.previousIndex() + "; "); - } - System.out.println(); - while(it.hasPrevious()) { - System.out.print(it.previous().id() + " "); - } - System.out.println(); - System.out.println(pets); - //创建指向索引为index元素处的ListIterator - it = pets.listIterator(3); - while(it.hasNext()) { - it.next(); - it.set(Pets.randomPet()); - } - System.out.println(pets); - } -}/*output -Rat, 1, 0; Manx, 2, 1; Cymric, 3, 2; Mutt, 4, 3; Pug, 5, 4; Cymric, 6, 5; Pug, 7, 6; Manx, 8, 7; -7 6 5 4 3 2 1 0 -[Rat, Manx, Cymric, Mutt, Pug, Cymric, Pug, Manx] -[Rat, Manx, Cymric, Cymric, Rat, EgyptianMau, Hamster, EgyptianMau] - */ -``` - -### LinkedList - -```java -public class LinkedListFeatures { - public static void main(String[] args) { - LinkedList pets = - new LinkedList(Pets.arrayList(5)); - print(pets); - // Identical: - print("pets.getFirst(): " + pets.getFirst()); - print("pets.element(): " + pets.element()); - // Only differs in empty-list behavior: - print("pets.peek(): " + pets.peek()); - // Identical; remove and return the first element: - print("pets.remove(): " + pets.remove()); - print("pets.removeFirst(): " + pets.removeFirst()); - // Only differs in empty-list behavior: - print("pets.poll(): " + pets.poll()); - print(pets); - pets.addFirst(new Rat()); - print("After addFirst(): " + pets); - pets.offer(Pets.randomPet()); - print("After offer(): " + pets); - pets.add(Pets.randomPet()); - print("After add(): " + pets); - pets.addLast(new Hamster()); - print("After addLast(): " + pets); - print("pets.removeLast(): " + pets.removeLast()); - } -} /* Output: -[Rat, Manx, Cymric, Mutt, Pug] -pets.getFirst(): Rat -pets.element(): Rat -pets.peek(): Rat -pets.remove(): Rat -pets.removeFirst(): Manx -pets.poll(): Cymric -[Mutt, Pug] -After addFirst(): [Rat, Mutt, Pug] -After offer(): [Rat, Mutt, Pug, Cymric] -After add(): [Rat, Mutt, Pug, Cymric, Pug] -After addLast(): [Rat, Mutt, Pug, Cymric, Pug, Hamster] -pets.removeLast(): Hamster -*/ -``` - -LinkedList 具有实现栈所有功能的所有方法,可以将 LinkedList 作为栈使用: - -```java -public class MyStack { - private LinkedList storage = new LinkedList<>(); - public void push(T t) { - storage.addFirst(t); - } - public T pop() { - return storage.removeFirst(); - } - public T peek() { - return storage.getFirst(); - } - public boolean isEmpty() { - return storage.isEmpty(); - } - @Override - public String toString() { - return storage.toString(); - } -} -``` - -### Set - -Set 不保存重复的元素,插入相同的元素会被忽略。HashSet 存储元素没有顺序;TreeSet 按照升序的方式存储元素;LinkedHashList 使用链表维护元素的插入顺序,并通过散列提供了快速访问能力。 - -```java -public class SortedSetOfInteger { - public static void main(String[] args) { - Random rand = new Random(47); - SortedSet set = new TreeSet<>(); - for (int i = 0; i < 1000; i++) { - set.add(rand.nextInt(30)); - } - System.out.println(set); - } -} -/*output -[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, - 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29] - */ -``` - -TreeSet 对字母排序是按照字典序,大小写字母会被分到不同的组,如果想按照字母序排序,可以向 TreeSet 构造器传入 String.CASE_INSENTIVE_ORDER 比较器。 - -```java -public class SortedWords { - public static void main(String[] args) { - //往构造器传入String.CASE_INSENTIVE_ORDER 比较器 - Set words = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); - String[] strs = {"hello", "world", "tyson", "a"}; - words.addAll(Arrays.asList(strs)); - System.out.println(words); - } -} -/* -[a, hello, tyson, world] - */ -``` - -### Map - -HashMap 设计用来快速访问;TreeMap保持键始终处于升序状态;LinkedHashMap 使用链表维护元素的插入顺序,并通过散列提供了快速访问能力。 - -```java -public class PetMap { - public static void main(String[] args) { - Map petMap = new HashMap<>(); - petMap.put("Tyson", new Cat("cat")); - petMap.put("Sophia", new Dog("dog")); - petMap.put("Tom", new Hamster("hamster")); - - System.out.println(petMap.keySet()); - System.out.println(petMap.values()); - for(String person : petMap.keySet()) { - System.out.println(person + " : " + petMap.get(person)); - } - - Set> entries = petMap.entrySet(); - for(Map.Entry entry : entries) { - System.out.println(entry.getKey() + ": " + entry.getValue()); - entry.setValue(new Cat("cat")); - System.out.println(entry.getKey() + ": " + entry.getValue()); - } - - } -} -/*output -[Tom, Tyson, Sophia] -[Hamster hamster, Cat cat, Dog dog] -Tom : Hamster hamster -Tyson : Cat cat -Sophia : Dog dog -Tom: Hamster hamster -Tom: Cat cat -Tyson: Cat cat -Tyson: Cat cat -Sophia: Dog dog -Sophia: Cat cat - */ -``` - -### Queue - -LinkedList 实现了 Queue 接口,可以作为队列使用。 - -```java -public class QueueDemo { - public static void printQ(Queue queue) { - while(queue.peek() != null) { - System.out.print(queue.remove() + " "); - } - System.out.println(); - } - public static void main(String[] args) { - Queue queue = new LinkedList<>(); - Random rand = new Random(47); - for(int i = 0; i < 10; i++) { - queue.offer(rand.nextInt(i + 10)); - } - printQ(queue); - Queue qc = new LinkedList<>(); - for(char c : "tyson".toCharArray()) { - qc.offer(c); - } - printQ(qc); - } -} -``` - -peek()和 element()都将在不移除的情况下返回对头,peek()方法在队列为空时返回 null,而element()会跑 NoSuchElementExeception 异常。 - -poll()和remove()方法将移除并返回对头,poll()在队列为空时返回 null,而remove()会跑 NoSuchElementExeception 异常。 - -#### PriorityQueue - -Integer、String 和 Character 内建了自然排序,可以与 PriorityQueue 一起工作。要想在 PriorityQueue 中使用自己的类,就必须提供自己的 Comparator。 - -```java -public class PriorityQueueDemo { - public static void main(String[] args) { - PriorityQueue priorityQueue = - new PriorityQueue(); - Random rand = new Random(47); - for(int i = 0; i < 10; i++) - priorityQueue.offer(rand.nextInt(i + 10)); - QueueDemo.printQ(priorityQueue); - - List ints = Arrays.asList(25, 22, 20, - 18, 14, 9, 3, 1, 1, 2, 3, 9, 14, 18, 21, 23, 25); - priorityQueue = new PriorityQueue(ints); - QueueDemo.printQ(priorityQueue); - priorityQueue = new PriorityQueue( - ints.size(), Collections.reverseOrder()); - priorityQueue.addAll(ints); - QueueDemo.printQ(priorityQueue); - - String fact = "EDUCATION SHOULD ESCHEW OBFUSCATION"; - List strings = Arrays.asList(fact.split("")); - PriorityQueue stringPQ = - new PriorityQueue(strings); - QueueDemo.printQ(stringPQ); - stringPQ = new PriorityQueue( - strings.size(), Collections.reverseOrder()); - stringPQ.addAll(strings); - QueueDemo.printQ(stringPQ); - - Set charSet = new HashSet(); - for(char c : fact.toCharArray()) - charSet.add(c); // Autoboxing - PriorityQueue characterPQ = - new PriorityQueue(charSet); - QueueDemo.printQ(characterPQ); - } -} /* Output: -0 1 1 1 1 1 3 5 8 14 -1 1 2 3 3 9 9 14 14 18 18 20 21 22 23 25 25 -25 25 23 22 21 20 18 18 14 14 9 9 3 3 2 1 1 - A A B C C C D D E E E F H H I I L N N O O O O S S S T T U U U W -W U U U T T S S S O O O O N N L I I H H F E E E D D C C C B A A - A B C D E F H I L N O S T U W -*///:~ -``` - -### foreach 和 迭代器 - -Java SE5引入了 Iterable 接口,该接口包含了一个能够产生 Iterator 的 iterator()方法。任何实现了 Iterable 的类(如Collection)都可以应用于 foreach 语句中。 - -```java -public class IterableClass implements Iterable { - protected String[] words = {"hello", "world", "program"}; - @Override - public Iterator iterator() { - return new Iterator() { - private int index = 0; - @Override - public boolean hasNext() { - return index < words.length; - } - @Override - public String next() { - return words[index++]; - } - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - }; - } - - public static void main(String[] args) { - for(String s : new IterableClass()) { - System.out.print(s + " "); - } - } -} -``` - -### 适配器方法 - -添加反向迭代器功能。 - -```java -public class ReversibleArrayList extends ArrayList { - public ReversibleArrayList(Collection c) { super(c); } - public Iterable reversed() { - return new Iterable() { - @Override - public Iterator iterator() { - return new Iterator() { - int current = size() - 1; - @Override - public boolean hasNext() { return current > -1; } - @Override - public T next() { return get(current--); } - }; - } - }; - } - public static void main(String[] args) { - ReversibleArrayList ral = - new ReversibleArrayList<>(Arrays.asList(new String[]{"hello", "world", "yes"})); - for(String s : ral.reversed()) { - System.out.print(s + " "); - } - } -} -``` - - - -## 异常、断言和日志 - -Java 语言给出了三种处理系统错误的机制:抛出异常、日志和使用断言。 - -### 异常分类 - -所有异常都有 Throwable 继承而来。 - -![异常层次结构](https://img2018.cnblogs.com/blog/1252910/201904/1252910-20190420203248534-1858171357.png) - - - -Error 类描述 Java 运行时系统的内部错误和资源耗尽错误,应用程序不应该抛出这种类型的对象。Error是程序无法处理的错误。 - -RuntimeException由程序错误导致,如果发生了 Runtime Exception,那一定是你的问题,应该修正程序避免这类异常发生。常见的运行时异常有: - -```java -ClassCastException -IndexOutOfBoundsException -NullPointerException -ArrayStoreException -NumberFormatException -ArithmeticException -``` - -其他 Exception 由具体的环境,如读取的文件不存在、文件为空、sql异常、寻找不存在的 Class 对象等导致的异常。 - -Java 语言规范将派生于 Error 类或 Runtime Exception 类的异常称为非检查(unchecked)异常;其他所有异常称为检查(checked)异常,编译器将会核查是否为所有的检查异常提供异常处理器。 - -### 声明异常 - -一个方法必须声明所有可能抛出的检查异常。任何抛出异常的方法都可能是一个死亡陷阱,如果没有异常处理器捕获这个异常,当前线程就会结束。非检查异常要么不可控,要么应当避免发生。 - -如果在子类覆盖了父类的方法,在子类方法中可以抛出更特定的异常,或者不抛异常。如果父类方法没有抛出异常,则子类只能在方法内捕获所有检查异常,不允许在子类的 throws 说明符中出现超过超类方法所列出的异常类范围。 - -### 捕获异常 - -捕获多个异常可以合并 catch 子句(异常类型不存在子类关系)。 - -```java -try { - // -} catch (FileNotFindException | UnknowHostException e) { - e.printStackTrace(); -} -``` - -解耦合 try/catch 和 try/finally 语句块,这种设计方式不仅代码清晰,而且还会报告 finally 子句出现的错误。 - -```java -InputStream in = ...; -try { - try { - // - } finally { - in.close(); - } -} catch (IOException ex) { - ex.printStackTrace(); -} -``` - -### 带资源的 try 语句 - -```java -try (Scanner in = new Scanner(new FileInputStream("e:\data"), "UTF-8"); - PrintWriter out = new PrinterWriter("out.txt")) { - while (in.hasNext()) { - System.out.println(in.next().toUpperCase()); - } -} -``` - -Java SE7 引入了带资源的 try 语句,当语句正常退出或者有异常,都会调用`in.close()`方法。如果`in.close()`方法也抛出异常,则原来的异常会重新抛出,而close方法抛出的异常会被抑制,通过调用 getSuppressed 方法可以得到从close 方法抛出并被抑制的异常列表。 - -### 断言 - -```java -int x = -1; -if(x < 0) { - throw new IllegalArgumentException("x < 0"); -} -double y = Math.sqrt(x); -``` - -这段检查代码会一直保留在程序,如果程序中含有大量这样的检查,程序运行起来将会很慢。 - -断言允许在测试期间向代码插入一些检查语句。当代码发布时,这些检查语句将会被自动的移走。 - -Java 语言引入了关键字 assert,assert 有两种形式: - -```java -assert condition; -assert condition: expression;//expression用于产生消息字符串 -``` - -这两种形式都会对条件进行检测,如果结果是 false,则会抛出 AssertError 异常。第二种形式中,表达式将会被传入 AssertError 的构造器,并转化成一个消息字符串。 - -断言 x 是一个非负数值: - -```java -assert x >= 0; -assert x >= 0 : x;//将x的值传递给AssertError异常 -``` - -断言只应该用在测试阶段确定程序内部的错误位置。 - -#### 启用和禁用断言 - -默认情况下,断言被禁用,可以在运行程序时用`-eableassertions`或`-ea`选项启用。idea --> run configuration --> vm options 添加 `-ea` 开启断言。 - -`java -enableassertions MyApp` - -启用和禁用断言是类加载器的功能,在启用和禁用断言时不必重新编译程序。当断言被禁用时,类加载器将跳过断言代码,因此不会降低程序的运行速度。 - -在某个类和包使用断言:`java -ea:MyClass -ea:com.tyson.chapter12 MyApp` MyClass 类和 com.tyson.chapter12 包以及子包的所有类都会启用断言。 - -使用选项`-dableassertions`或`-da`禁用某个类和包的断言。 - -### 日志 - -#### logback - -``的三个属性 - -- scan:默认值为true,配置文件发生更改时会重新加载。 -- scanPeriod:设置监控配置文件的时间间隔,默认单位是毫秒。 -- debug:默认值是false,打印logback内部日志信息,实时查看logback运行状态。 - -``的子标签 -root是根``,只有level属性,默认为DEBUG。 -logger关联包或者具体的类到appender,可以定义日志类型、级别。 -Appender主要用于指定日志输出的目的地,如控制台、文件、数据库。 - -``` - - - - - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - - - - - - - - - - - -``` -日志级别:ERROR>WARN>INFO>DEBUG>TRACE - -Layout用于自定义日志输出格式。 -``` - - - -``` - - - -## 字符串 - -String 对象是不可变的。String 类中会修改 String 的方法都会创建一个全新的 String 对象。 - -### 格式化输出 - -#### printf() 和 format() - -```java -public class SimpleFormat { - public static void main(String[] args) { - int x = 5; - double y = 5.26; - System.out.format("Row 1: [%d %f]\n", x, y); - System.out.printf("Row 1: [%d %f]\n", x, y); - } -} -/*output -Row 1: [5 5.260000] -Row 1: [5 5.260000] - */ -``` - -#### Formatter - -```java -public class FormatterDemo { - public static void main(String[] args) { - Formatter f = new Formatter(System.out); - String item = "peas"; - int quatity = 3; - float price = 5.2f; - //-表示右对齐 - f.format("%-10s %5s %10s\n", "item", "quatity", "price"); - //-表示右对齐,%10.10s指宽度为10,最大输出字符为10 - f.format("%-10.10s %5d %10.2f\n", item, quatity, price); - //String.format() - System.out.println("------string.format()------"); - System.out.println(String.format("%-10.10s %5d %10.2f", item, quatity, price)); - } -} -/* -item quatity price -peas 3 5.20 -------string.format()------ -peas 3 5.20 - */ -``` - -String.format()接受与Formatter.format()一样的参数,它是通过创建一个 Formatter 对象实现的。 - -### 正则表达式 - -#### 基础 - -可能有一个负号在最前面:`-?` - -`\\`在 Java 表示插入一个正则表达式的反斜线 - -表示一个数字:`\\d` - -插入普通的反斜线:`\\\\` - -`+`表示一个或多个之前的表达式 - -可能有一个负号,后面跟着一个或多个数字:`-?\\d+` - -```java -public class IntegerMatch { - public static void main(String[] args) { - System.out.println("-89".matches("-?\\d+")); - System.out.println("+98".matches("-?\\d+")); - //竖直线|代表或操作;+是特殊符号,需转义;括号可以将表达式分组 - System.out.println("+789".matches("(-|\\+)?\\d+")); - } -} -/*output -true -false -true - */ -``` - -String 自带一个正则表达式工具 split()方法,它可以将字符串从正则表达式匹配的地方切开。特殊字符如`.`和`|`需要用反斜杠转义,即`\\.`和`\\|`。 - -```java -public class Splitting { - public static String motto = "All in all, we all have a dream."; - public static void split(String regex) { - System.out.println(Arrays.toString(motto.split(regex))); - } - public static void main(String[] args) { - //.需要转义 - split("\\."); - split(" "); - //\\w+表示一个或多个非单词字符 - split("\\W+"); - //表示字母n后面跟着一个或多个非单词字符 - split("n\\W+"); - } -} -/*output -[All in all, we all have a dream, Thanks] -[All, in, all,, we, all, have, a, dream., Thanks.] -[All, in, all, we, all, have, a, dream, Thanks] -[All i, all, we all have a dream. Thanks.] - */ -``` - -替换功能。 - -```java -public class Replacing { - public static void main(String[] args) { - //小写的w表示字母 - System.out.println(Splitting.motto.replaceFirst("w\\w+", "they")); - System.out.println(Splitting.motto.replaceAll("all|dream", "apple")); - } -} -/*output -All in all, they all have a dream. -All in apple, we apple have a apple. - */ -``` - -#### 创建正则表达式 - -字符类表达式,JDK 文档中 java.util.regex.Pattern 页面有完整的表达式。 - -[jdk8文档](https://docs.oracle.com/javase/8/docs/api/index.html) - -[菜鸟教程正则表达式语法](http://www.runoob.com/java/java-regular-expressions.html) - -**常用的字符类表达式** - -| 字符 | 描述 | -| -------- | ------------------------------------------------------------ | -| . | 匹配除"\r\n"之外的任何单个字符 | -| + | 表示一个或多个之前的表达式,如\\\d+表示一个或多个数字 | -| * | 零次或多次匹配前面的字符或子表达式。例如,zo* 匹配"z"和"zoo"。* 等效于 {0,} | -| ? | 零次或一次匹配前面的字符或子表达式。如"do(es)?"匹配"do"或"does"中的"do"。? 等效于 {0,1} | -| [abc] | 包含a/b/c的任何字符(和a\|b\|c作用相同) | -| [^abc] | 除了a/b/c外的任何字符 | -| [a-zA-z] | a-z/A-Z的任何字符 | -| \s | 空白符(空格、tab、换行、换页和回车) | -| \S | 非空白符(\[^\s]) | -| \d | 数字0-9 | -| \D | 非数字(\[^0-9]) | -| \w | 字符[a-zA-Z0-9] | -| \W | 非字符 | - -**边界匹配符** - -| 符号 | 描述 | -| ---- | ---------- | -| ^ | 一行的起始 | -| $ | 一行的结束 | -| \b | 词的边界 | -| \B | 非词的边界 | - -**量词** - -![正则表达式量词](https://img2018.cnblogs.com/blog/1252910/201904/1252910-20190409100645126-737003966.png) - -Rudolph.java - -```java -public class Rudolph { - public static void main(String[] args) { - for(String pattern : new String[]{ "Rudolph", - "[rR]udolph", "[rR][aeiou][a-z]ol.*", "R.*" }) - System.out.println("Rudolph".matches(pattern)); - } -} /* Output: -true -true -true -true -*///:~ -``` - -#### Pattern 和 Matcher - -Pattern 和 Matcher 功能较 String 更为强大。Pattern.compile(String regex) 用来编译正则表达式,生成一个。 - -**find()** - -`Matcher.find()`可用来在 CharSequence 中查找多个匹配。find()可以像迭代器一样前向遍历输入的字符串。重载的 find(int index)能够接收一个整数参数,作为搜索起点。 - -```java -public class Finding { - //划分为单词 - public static Pattern WORD_PATTERN = Pattern.compile("\\w+"); - public static void main(String[] args) { - Matcher m = WORD_PATTERN.matcher("Everyone is born equally"); - //find()可用来查找多个匹配 - while(m.find()) { - System.out.print(m.group() + " "); - } - System.out.println(); - int i = 0; - //指定搜索起点 - while(m.find(i++)) { - System.out.print(m.group() + " "); - } - } -} -/*output -Everyone is born equally -Everyone veryone eryone ryone yone one ne e is is s born born orn rn n equally equally qually ually ally lly ly y - */ -``` - -**组** - -组使用括号划分的正则表达式,如`A(B(C))D`,组0是ABCD,组1是BC,组2是C。Matcher 对象提供了一系列方法,用以获取与组相关的信息: - -`public int groupCount()`返回该匹配器的分组数目(**不包含第0组**),如上面例子分组数目为2组(不包含第0组); - -`public String group()`返回前一次匹配操作(如find())的第0组(整个匹配); - -`public String group(int i)`返回前一次匹配操作指定的组号,如果没有匹配到输入字符串的任何部分,将会返回null。 - -```java -public class Groups { - public static final String POEM = - "Twas brillig, and the slithy toves\n" + - "Did gyre and gimble in the wabe.\n" + - "All mimsy were the borogoves,\n" + - "And the mome raths outgrabe.\n\n" + - "Beware the Jabberwock, my son,\n" + - "The jaws that bite, the claws that catch.\n" + - "Beware the Jubjub bird, and shun\n" + - "The frumious Bandersnatch.\n" + - "end\n"; //匹配失败 - //捕获每行最后的三个词 - public static Pattern LAST_THREE_WORD_PATTERN = Pattern.compile("(?m)(\\S+)\\s+((\\S+)\\s+(\\S+))$"); - - public static void main(String[] args) { - Matcher m = LAST_THREE_WORD_PATTERN.matcher(POEM); - while(m.find()) { - //有4个分组,不包括分组0,分组0是整个匹配 - for(int i = 0; i <= m.groupCount(); i++) { - System.out.print("[" + m.group(i) + "]"); - } - System.out.println(); - } - } -} -/* Output: -[the slithy toves][the][slithy toves][slithy][toves] -[in the wabe.][in][the wabe.][the][wabe.] -[were the borogoves,][were][the borogoves,][the][borogoves,] -[mome raths outgrabe.][mome][raths outgrabe.][raths][outgrabe.] -[Jabberwock, my son,][Jabberwock,][my son,][my][son,] -[claws that catch.][claws][that catch.][that][catch.] -[bird, and shun][bird,][and shun][and][shun] -[The frumious Bandersnatch.][The][frumious Bandersnatch.][frumious][Bandersnatch.] -*/ -``` - -默认情况下,^和$仅匹配输入的完整字符串的开始和结束。?m表示多行模式下,^和$分别匹配一行的开始和结束。 - -**start() 与 end()** - -在匹配操作成功之后,start()返回匹配的起始位置的索引,end()返回所匹配字符的最后字符的索引加一的值。 - -**Pattern 标记** - -Pattern 类的 compile()方法还有一个版本,可以接收一个标记参数,以调整匹配的行为: - -`Pattern Pattern.compile(String regex, int flag)` - -flag 来自以下 Pattern 类中的常量: - -![Pattern 标记参数](https://img2018.cnblogs.com/blog/1252910/201904/1252910-20190409103507798-1192565494.png) - -可以通过'|'(或)操作符组合多个标记的功能: - -```java -public class ReFlags { - public static Pattern WORD_PATTERN = Pattern.compile("^java", - Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); - public static void main(String[] args) { - Matcher m = WORD_PATTERN.matcher("java has regex\nJava has regex\n" + - "JAVA has pretty good regular expressions\n" + - "Regular expressions are in Java"); - while(m.find()) { - System.out.println(m.group()); - } - } -} -/* Output: -java -Java -JAVA -*/ -``` - -**split()** - -```java -public class SplitDemo { - public static void main(String[] args) { - String input = "This!!unusual use!!of exclamation!!points"; - print(Arrays.toString(Pattern.compile("!!").split(input))); - // 限制分割字符串的数量 - print(Arrays.toString(Pattern.compile("!!").split(input, 3))); - } -} /* Output: -[This, unusual use, of exclamation, points] -[This, unusual use, of exclamation!!points] -*/ -``` - -**替换操作** - -```java -public class TheReplacements { - public static void main(String[] args) throws Exception { - String s = TextFile.read("src/com/tyson/chapter13/string/regex/TheReplacements.java"); - // 匹配上面的注释 - Matcher mInput = Pattern.compile("/\\*!(.*)!\\*/", Pattern.DOTALL).matcher(s); - if (mInput.find()) { - s = mInput.group(1); // 捕捉正则表达式括号,组是括号划分的正则表达式 - } - - // Replace two or more spaces with a single space: - s = s.replaceAll(" {2,}", " "); - // Replace one or more spaces at the beginning of each - // line with no spaces. Must enable MULTILINE mode: - s = s.replaceAll("(?m)^ +", ""); - print(s); - s = s.replaceFirst("[aeiou]", "(VOWEL1)"); - StringBuffer sbuf = new StringBuffer(); - Pattern p = Pattern.compile("[aeiou]"); - Matcher m = p.matcher(s); - // Process the find information as you - // perform the replacements: - while (m.find()) - //将找到的元音字母转化为大写字母 - m.appendReplacement(sbuf, m.group().toUpperCase()); - // Put in the remainder of the text: - m.appendTail(sbuf); - print(sbuf); - } -} /* Output: -Here's a block of text to use as input to -the regular expression matcher. Note that we'll -first extract the block of text by looking for -the special delimiters, then process the -extracted block. -H(VOWEL1)rE's A blOck Of tExt tO UsE As InpUt tO -thE rEgUlAr ExprEssIOn mAtchEr. NOtE thAt wE'll -fIrst ExtrAct thE blOck Of tExt by lOOkIng fOr -thE spEcIAl dElImItErs, thEn prOcEss thE -ExtrActEd blOck. -*/ -``` - -**reset()** - -通过 reset() 方法,可以将 Matcher 对象应用于一个新的字符序列。 - -```java -public class Resetting { - public static void main(String[] args) throws Exception { - Matcher m = Pattern.compile("[frb][aiu][gx]").matcher("fix the rug with bags"); - while (m.find()) - System.out.print(m.group() + " "); - System.out.println(); - m.reset("fix the rig with rags"); - while (m.find()) - System.out.print(m.group() + " "); - } -} /* Output: -fix rug bag -fix rig rag -*/ -``` - -### 扫描输入 - -Java SE5新增了 Scanner 类,Scanner 的构造器可以接受任何类型的输入对象,包括 File 对象、InputStream、String 或者 Readable 对象。Scanner 所有的输入、分词以及翻译都隐藏在不用类型的 next 方法中。普通的 next()方法返回下一个 String 。 - -#### Scanner 定界符 - -默认情况下,Scanner 根据空白字符对输入进行分词,我们可以用正则表达式指定自己所需的定界符。 - -```java -public class ScannerDelimiter { - public static void main(String[] args) { - Scanner sc = new Scanner("12, 42, 85, 23"); - //使用逗号包括逗号前后的空白字符作为定界符 - sc.useDelimiter("\\s*,\\s*"); - while(sc.hasNextInt()) { - System.out.println(sc.nextInt()); - } - } -} -``` - -#### 用正则表达式扫描 - -```java -public class ThreatAnalyzer { - static String threatData = - "58.27.82.161@02/10/2005\n" + - "204.45.234.40@02/11/2005\n" + - "58.27.82.161@02/11/2005\n" + - "58.27.82.161@02/12/2005\n" + - "58.27.82.161@02/12/2005\n" + - "[Next log section with different data format]"; - - public static void main(String[] args) { - Scanner sc = new Scanner(threatData); - String pattern = "(\\d+[.]\\d+[.]\\d+[.]\\d+)@(\\d{2}/\\d{2}/\\d{4})"; - while(sc.hasNext(pattern)) { - sc.next(pattern); - MatchResult match = sc.match(); - String ip = match.group(1); - String date = match.group(2); - System.out.format("Thread on %s from %s\n", ip, date); - } - } -} -/*output -Thread on 58.27.82.161 from 02/10/2005 -Thread on 204.45.234.40 from 02/11/2005 -Thread on 58.27.82.161 from 02/11/2005 -Thread on 58.27.82.161 from 02/12/2005 -Thread on 58.27.82.161 from 02/12/2005 - */ -``` - -注意:配合正则表达式进行扫描时,正则表达式不要含有定界符(Scanner 默认定界符是空白符,根据空白符对输入进行分词)。 - - - -## 类型信息 - -通过运行时类型信息可以在程序运行时发现和使用类型信息。 - -`Class.forName("Gum")`类 Gum 没有被加载就加载它。 - -### 反射 - -类方法提取: - -```java -public class typeinfo { - //匹配类似包名的字符串 com.tyson.chapter - public static Pattern p = Pattern.compile("\\w+\\."); - public static void main(String[] args) { - try { - Class c = Class.forName("com.tyson.chapter10.innerclass.LocalInnerClass"); - Method[] methods = c.getMethods(); - for(Method m : methods) { - //去掉包名 - System.out.println(p.matcher(m.toString()).replaceAll("")); - } - } catch (ClassNotFoundException e) { - e.printStackTrace(); - } - } -} -/*output -public static void main(String[]) -public final void wait() throws InterruptedException -public final void wait(long,int) throws InterruptedException -public final native void wait(long) throws InterruptedException -public boolean equals(Object) -public String toString() -public native int hashCode() -public final native Class getClass() -public final native void notify() -public final native void notifyAll() - */ -``` - - - -## 泛型 - -泛型实现了参数化类型的概念,使代码可以应用于多种类型。使用泛型编写的程序比使用 Object 变量再进行强制类型转化的程序具有更好的安全性和可读性。 - -### 类型参数的好处 - -Java SE5泛型出现之前,ArrayList 是通过维护一个 Object 数组实现的。 - -```java -public class MyArrayList { - private Object[] elementData; - private int index = 0; - public MyArrayList() { elementData = new Object[8]; } - public Object get(int i) { return elementData[i]; } - public void add(Object o) { elementData[index++] = o; } - - public static void main(String[] args) { - MyArrayList list = new MyArrayList(); - list.add("tyson"); - System.out.println((String)list.get(0)); - list.add(new Cat()); - System.out.println((String)list.get(1)); - } -} -/*output -tyson -Exception in thread "main" java.lang.ClassCastException: typeinfo.pets.Cat cannot be cast to java.lang.String - at com.tyson.chapter15.generics.MyArrayList.main(MyArrayList.java:29) - */ -``` - -当获取值的时候需要进行强制类型转化,并且 add 操作可以放进任何类型的对象,编译和运行都不报错,当获取值进行强制类型转换时,可能会抛异常。 - -### 泛型类 - -```java -public class Holder { - private T a; - public Holder(T a) { this.a = a; } - public void set(T a) { this.a = a; } - public T get() { return a; } - - public static void main(String[] args) { - Holder h = new Holder<>("Tyson"); - System.out.println(h.get()); - } -} -//output Tyson -``` - -### 泛型接口 - -泛型也可以用于接口。 - -```java -//public interface Generator { T next(); } - -public class Fibonacci implements Generator { - private int count = 0; - @Override - public Integer next() { - return fib(count++); - } - public int fib(int n) { - if(n < 2) { return 1; } - return fib(n - 2) + fib(n - 1); - } - - public static void main(String[] args) { - Fibonacci fib = new Fibonacci(); - for(int i = 0; i < 10; i++) { - System.out.print(fib.next() + " "); - } - } -} -//output:1 1 2 3 5 8 13 21 34 55 -``` - -### 泛型方法 - -如果使用泛型方法可以取代将整个类泛型化,那么就应该只使用泛型方法。对于一个 static 方法而言,无法访问泛型类的类型参数,所以如果 static 方法需要使用泛化能力,就必须使其成为泛型方法。 - -定义泛型方法,只需将泛型参数列表放在返回值之前。 - -```java -public class GenericMethod { - public void f(T t) { System.out.println(t.getClass().getName()); } - - public static void main(String[] args) { - GenericMethod gm = new GenericMethod(); - //可省略,编译器自己可推断出 - gm.f(""); - gm.f(1); - gm.f(1.0); - gm.f(1.0f); - gm.f(gm); - } -} -/*output -java.lang.String -java.lang.Integer -java.lang.Double -java.lang.Float -com.tyson.chapter15.generics.GenericMethod - */ -``` - -GenericMethod 并不是参数化的,只有 f() 拥有参数类型。 - -#### 可变参数和泛型方法 - -向参数个数可变的方法传递一个泛型类型的实例。 - -```java -public class GenericVarArgs { - public static List makeList(T... args) { - List result = new ArrayList<>(); - for(T item : args) { - result.add(item); - } - return result; - } - - public static void main(String[] args) { - List l = makeList("a", "b"); - System.out.println(l); - } -} -//output:[a, b] -``` - -### 匿名内部类 - -```java -public class Customer { - public Customer() {} - public static Generator generator() { - return new Generator() { - @Override - public Customer next() { - return new Customer(); - } - }; - } - - public static void main(String[] args) { - System.out.println(Customer.generator().next()); - } -} -``` - -### 泛型擦除 - -```java -public class ErasedTypeEquvalence { - public static void main(String[] args) { - Class c1 = new ArrayList().getClass(); - Class c2 = new ArrayList().getClass(); - System.out.println(c1 == c2); - } -} -//output:true -``` - -`List`的类型变量没有显式的限定,擦除类型后变成原始的 List,故`List`和`List`是相同的类型。 - -```java -class Manipulator { - private T obj; - public Manipulator(T t) { obj = t; } - public void manipulate() { obj.f(); } -} -``` - -Manipulator 类有显式限定 HasF,故擦除类型后变成: - -```java -class Manipulato { - private HasF obj; - public Manipulator(HasF t) { obj = HasF; } - public void manipulate() { obj.f(); } -} -``` - -### 类型变量的限定 - -给定泛型类的边界,以此告知编译器只能接受遵循这个边界的类型。 - -```java -//HasF.java -public class HasF { - public void f() { - System.out.println("Has.f()"); - } -} - -//Manipulation.java -class Manipulator { - private T obj; - public Manipulator(T t) { obj = t; } - public void manipulate() { obj.f(); } -} - -public class Manipulation { - public static void main(String[] args) { - HasF hf = new HasF(); - Manipulator manipulator = new Manipulator<>(hf); - manipulator.manipulate(); - } -} -//output: Has.f() -``` - -### 擦除的问题 - -```java -class GenericBase { - private T element; - public void set(T arg) { element = arg; } - public T get() { return element; } - - public static void main(String[] args) { - - } -} - -class Derived extends GenericBase {} - -public class ErasureAndInheritance { - public static void main(String[] args) { - Derived d = new Derived(); - d.set(new Object()); - System.out.println(d.get()); - } -} -//output: java.lang.Object@6d6f6e28 -``` - -Derived 继承自 GenericBase,但是没有任何泛型参数,而编译器不会发出任何警告。 - -### 边界 - -在泛型的参数类型上设置限制条件,可以强制规定泛型可以应用的类型,从而可以按照自己的边界类型去调用方法。如果将泛型参数限制为某个类型子集,那么就可以用这些类型子集来调用方法。 - -```java -interface HasColor { Color getColor(); } - -class Colored { - T item; - Colored(T item) { this.item = item; } - T getItem() { return item; } - Color color() { return item.getColor(); } -} - -class Dimension { public int x; } - -//编译错误,应该类在前,接口在后 -//class ColoredDimension {} - -class ColoredDimension { - T item; - ColoredDimension(T item) { this.item = item; } - T getItem() { return item; } - Color color() { return item.getColor(); } - int getX() { return item.x; } -} - -interface Weight { int weight(); } - -//边界继承只能有一个类,可以有多个接口 -class Solid { - T item; - Solid(T item) { this.item = item; } - T getItem() { return item; } - Color color() { return item.getColor(); } - int getX() { return item.x; } - int weight() { return item.weight(); } -} - -class Bounded extends Dimension implements HasColor, Weight { - - @Override - public Color getColor() { return null; } - - @Override - public int weight() { return 0; } -} - -public class BasicBounds { - public static void main(String[] args) { - Solid solid = new Solid<>(new Bounded()); - solid.color(); - solid.getX(); - solid.weight(); - } -} -``` - -通过在继承的每个层次上添加边界限制,消除 BasicBounds.java 代码冗余。 - -```java -class HoldItem { - T item; - HoldItem(T item) { this.item = item; } - T getItem() { return item; } -} - -class Colored2 extends HoldItem { - Colored2(T item) { super(item); } - Color color() { return item.getColor(); } -} - -class ColorDimension2 extends Colored2 { - ColorDimension2(T item) { super(item); } - int getX() { return item.x; } -} - -class Solid2 extends ColorDimension2 { - Solid2(T item) { super(item); } - int weight() { return item.weight(); } -} - -public class InheritBounds { - public static void main(String[] args) { - Solid2 solid2 = new Solid2<>(new Bounded()); - solid2.color(); - solid2.getX(); - solid2.weight(); - } -} -``` - -### 通配符 - -参考自:[泛型超详细解读](https://blog.csdn.net/jeffleo/article/details/52250948) | [通配符的上界通配符和下界通配符](https://blog.csdn.net/hello_worldee/article/details/77934244) - -#### 上界通配符 - -上界< ? extends Class> - -```java -class Fruit { } -class Apple extends Fruit { } -class Orange extends Fruit { } - -public class UpperBound { - public static void main(String[] args) { - List list = new ArrayList<>(); - //编译错误 - //list.add(new Apple()); - //list.add(new Orange()); - //list.add(new Fruit()); - list.add(null); - - list.contains(new Apple()); - list.indexOf(new Apple()); - - System.out.println(list.get(0)); - } -} -``` - -可见,指定了下边界,却不能add任何类型,甚至Object都不行,除了null,因为null代表任何类型。List< ? extends Fruit>可以解读为,“具有任何从Fruit继承的类型”,但实际上,它意味着,它没有指定具体类型。对于编译器来说,当你指定了一个List< ? extends Fruit>,add的参数也变成了“? extends Fruit”。因此编译器并不能了解这里到底需要哪种Fruit的子类型,因此他不会接受任何类型的Fruit。 - -然而,contain和indexof却能执行,这是因为,这两个方法的参数是Object,不涉及任何的通配符,所以编译器允许它调用。 - -list.get(0)能够执行是因为,当item在此list存在时,编译器能够确定他是Apple的子类,所以能够安全获得。 - -#### 下界通配符 - -< ? super Class>表示,指定类的基类。 - -```java -class Apple1 extends Apple {} - -public class LowerBound{ - - public static void main(String[] args) { - List list = new ArrayList<>(); - list.add(new Apple()); - list.add(new Apple1()); - //编译错误 - //list.add(new Fruit()); - - Object apple = list.get(0); - System.out.println(apple); - } -} -//output: com.tyson.chapter15.generics.Apple@6d6f6e28 -``` - -对于super,get返回的是Object,因为编译器不能确定列表中的是Apple的哪个子类,所以只能返回Object。 - -List < ? super Apple>, 代表容器内存放的是Apple的所有父类,所以有多态和上转型,这个容器时可以接受所有Apple父类的子类的(多态的定义:父类可以接受子类型对象)。Apple和Apple1都直接或间接继承了Fruit,所以Apple和Apple1是能够加入List < ? super Apple>这个容器的。 - -list.add(new Fruit())不能添加,是因为容器内存放的是Apple的**所有**父类,正是因为能存放所有,Apple的父类可能有Fruit1, Fruit2, Fruit3 , 所以编译器根本不能识别你要存放哪个Apple的父类,所以不能添加Fruit,因为这不能保证类型安全的原则。这从最后的Object apple = list.get(0)可以看出。 - - - -## 数组 - -数组的 length 是数组的大小,不是实际保存的元素个数。 - -如果不显式初始化,基本类型数组会被自动初始化成初值,对象数组则会被初始化成 null。 - -### 复制数组 - -Java 标准类库提供有 static 方法 System.arraycopy(),用来复制数组比用 for 循环复制要快的多。System.arraycopy() 针对所有类型做了重载。 - -```java -public class CopyArrays { - public static void main(String[] args) { - int[] src = new int[7]; - int[] dest = new int[10]; - Arrays.fill(src, 47); - Arrays.fill(dest, 99); - System.out.println("src = " + Arrays.toString(src)); - System.out.println("dest = " + Arrays.toString(dest)); - System.arraycopy(src, 0, dest, 0, src.length); - System.out.println("dest = " + Arrays.toString(dest)); - } -} -/*output -src = [47, 47, 47, 47, 47, 47, 47] -dest = [99, 99, 99, 99, 99, 99, 99, 99, 99, 99] -dest = [47, 47, 47, 47, 47, 47, 47, 99, 99, 99] - */ -``` - -基本类型数组和对象数组都可以复制。复制对象只是复制对象引用,即浅复制。System.arraycopy() 不会执行自动包装和自动拆包,两个数组需要具有相同的数据类型。 - -### Arrays 工具 - -#### 数组拷贝 - -```java -public class ArrayCopy { - //Arrays 类方法的使用 - public static void main(String[] args) { - int[] arr = {12, 45, 2, 56, 20}; - int[] arrCopy = Arrays.copyOf(arr, arr.length); - System.out.println("arr: " + Arrays.toString(arr)); - System.out.println("arrCopy: " + Arrays.toString(arrCopy)); - //增加数组大小 - arrCopy = Arrays.copyOf(arr, 2 * arr.length); - System.out.println("arr: " + Arrays.toString(arr)); - System.out.println("arrCopy: " + Arrays.toString(arrCopy)); - } -} -/*output -arr: [12, 45, 2, 56, 20] -arrCopy: [12, 45, 2, 56, 20] -arr: [12, 45, 2, 56, 20] -arrCopy: [12, 45, 2, 56, 20, 0, 0, 0, 0, 0] - */ -``` - -`Array.copyOf()`底层是通过`System.arraycopy()`使用的。 - -```java -public static T[] copyOf(U[] original, int newLength, Class newType) { - @SuppressWarnings("unchecked") - T[] copy = ((Object)newType == (Object)Object[].class) - ? (T[]) new Object[newLength] - : (T[]) Array.newInstance(newType.getComponentType(), newLength); - System.arraycopy(original, 0, copy, 0, - Math.min(original.length, newLength)); - return copy; -} -``` - -#### 数组的比较 - -Arrays 类提供了重载后的 equals() 方法,用来比较整个数组。 - -```java -public class ComparingArrays { - public static void main(String[] args) { - int[] a1 = new int[10]; - int[] a2 = new int[10]; - Arrays.fill(a1, 47); - Arrays.fill(a2, 47); - System.out.println(Arrays.equals(a1, a2)); - a2[1] = 0; - System.out.println(Arrays.equals(a1, a2)); - String[] s1 = new String[2]; - Arrays.fill(s1, "hello"); - String[] s2 = { new String("hello"), new String("hello")}; - System.out.println(Arrays.equals(s1, s2)); - } -} -/*output -true -false -true - */ -``` - -#### 数组元素的比较 - -1. 实现`java.lang.Comparable`接口,使得类本身具有比较比较能力。 - - ```java - public class CompType implements Comparable { - int i; - int j; - public CompType(int i, int j) { - this.i = i; - this.j = j; - } - - @Override - public String toString() { - return "CompType{" + - "i=" + i + - ", j=" + j + - '}'; - } - - @Override - public int compareTo(CompType ct) { - return i < ct.i ? -1 : (i == ct.i ? 0 : 1); - } - - private static Random r = new Random(47); - public static Generator generator() { - return new Generator() { - @Override - public CompType next() { - return new CompType(r.nextInt(100), r.nextInt(100)); - } - }; - } - - public static void main(String[] args) { - CompType[] arr = Generated.array(new CompType[4], generator()); - System.out.println("before sorting: " + Arrays.toString(arr)); - Arrays.sort(arr); - System.out.println("after sorting: " + Arrays.toString(arr)); - //反转自然排列顺序 - Arrays.sort(arr, Collections.reverseOrder()); - System.out.println("reverse order: " + Arrays.toString(arr)); - } - } - /*output - before sorting: [CompType{i=58, j=55}, CompType{i=93, j=61}, CompType{i=61, j=29}, CompType{i=68, j=0}] - after sorting: [CompType{i=58, j=55}, CompType{i=61, j=29}, CompType{i=68, j=0}, CompType{i=93, j=61}] - reverse order: [CompType{i=93, j=61}, CompType{i=68, j=0}, CompType{i=61, j=29}, CompType{i=58, j=55}] - */ - ``` - -2. 编写自己的比较器Comparator。 - - ```java - class CompTypeComparator implements Comparator { - - @Override - public int compare(CompType o1, CompType o2) { - return o1.j > o2.j ? 1 : (o1.j == o2.j ? 0 : -1); - } - } - - public class ComparatorTest { - public static void main(String[] args) { - CompType[] arr = Generated.array(new CompType[4], CompType.generator()); - System.out.println("before sorting: " + Arrays.toString(arr)); - Arrays.sort(arr, new CompTypeComparator()); - System.out.println("after sorting: " + Arrays.toString(arr)); - } - } - /*output - before sorting: [CompType{i=58, j=55}, CompType{i=93, j=61}, CompType{i=61, j=29}, CompType{i=68, j=0}] - after sorting: [CompType{i=68, j=0}, CompType{i=61, j=29}, CompType{i=58, j=55}, CompType{i=93, j=61}] - */ - ``` - -#### 数组排序 - -使用内置的排序方法,就可以对基本类型数组进行排序。也可以对对象数组进行排序,只要该对象实现了 Comparable 接口或者具有相关联的 Comparator。 - -```java -public class StringSorting { - public static void main(String[] args) { - String[] strs = Generated.array(new String[5], new RandomGenerator.String(5)); - System.out.println("before sorting: " + Arrays.toString(strs)); - Arrays.sort(strs); - System.out.println("after sorting: " + Arrays.toString(strs)); - Arrays.sort(strs, Collections.reverseOrder()); - System.out.println("reverse order: " + Arrays.toString(strs)); - Arrays.sort(strs, String.CASE_INSENSITIVE_ORDER); - System.out.println("case insensive: " + Arrays.toString(strs)); - } -} -/*output -before sorting: [YNzbr, nyGcF, OWZnT, cQrGs, eGZMm] -after sorting: [OWZnT, YNzbr, cQrGs, eGZMm, nyGcF] -reverse order: [nyGcF, eGZMm, cQrGs, YNzbr, OWZnT] -case insensive: [cQrGs, eGZMm, nyGcF, OWZnT, YNzbr] - */ -``` - -String 排序算法依据词典编排顺序排序。大写字母在前,小写字母在后。Java 标准类库中的排序算法针对正排序做了优化。针对基础类型设计了快速排序,针对对象设计了稳定归并排序。 - -#### 排序数组查找 - -若数组已经排序,既可以使用`Arrays.binarySearch()`执行快速查找。 - -```java -public class ArraySearching { - public static void main(String[] args) { - Generator gen = new RandomGenerator.Integer(1000); - int[] arr = ConvertTo.primitive(Generated.array(new Integer[5], gen)); - Arrays.sort(arr); - System.out.println("after sorting: " + Arrays.toString(arr)); - while(true) { - int r = gen.next(); - int location = Arrays.binarySearch(arr, r); - if(location >= 0) { - System.out.println("location: " + location + "-->" + arr[location]); - break; - } - } - } -} -/*output -after sorting: [258, 555, 693, 861, 961] -location: 3-->861 - */ -``` - -找到目标,`Arrays.binarySearch()`返回值大于或等于0。否则返回负值:`-(插入点)- 1`; - -对象数组使用`Arrays.binarySearch()`需要提供Comparator。 - -```java -public class StringSearch { - public static void main(String[] args) { - String[] strs = Generated.array(new String[6], new RandomGenerator.String(5)); - Arrays.sort(strs, String.CASE_INSENSITIVE_ORDER); - System.out.println(Arrays.toString(strs)); - int index = Arrays.binarySearch(strs, strs[3], String.CASE_INSENSITIVE_ORDER); - System.out.println("index: " + index + "-->" + strs[index]); - } -} -/*output -[cQrGs, eGZMm, JMRoE, nyGcF, OWZnT, YNzbr] -index: 3-->nyGcF - */ -``` - - - -## 容器深入研究 - -### 填充容器 - -```java -public class FillingList { - public static void main(String[] args) { - List list = new ArrayList<>(Collections.nCopies(3, "Tyson")); - System.out.println(list); - Collections.fill(list, "Sophia"); - System.out.println(list); - } -} -/*output -[Tyson, Tyson, Tyson] -[Sophia, Sophia, Sophia] - */ -``` - -### SortedSet - -按对象的比较函数对元素进行排序。用 TreeSet 迭代通常比用 HashSet 要快。 - -```java -public class SortedSetDemo { - public static void main(String[] args) { - SortedSet sortedSet = new TreeSet<>(); - Collections.addAll(sortedSet, "one two three four".split(" ")); - System.out.println(sortedSet); - System.out.println("first: " + sortedSet.first()); - System.out.println("last: " + sortedSet.last()); - Iterator itr = sortedSet.iterator(); - while (itr.hasNext()) { - System.out.print(itr.next() + " "); - } - System.out.println(); - //from包含-to不包含 - System.out.println("subSet: " + sortedSet.subSet("one", "three")); - //小于three的元素 - System.out.println(sortedSet.headSet("three")); - //大于等于one的元素 - System.out.println(sortedSet.tailSet("one")); - } -} -/*output -[four, one, three, two] -first: four -last: two -four one three two -subSet: [one] -[four, one] -[one, three, two] - */ -``` - -### 队列 - -除了并发应用,Queue Java SE5中仅有两个实现 LinkedList 和 PriorityQueue,它们的差异在于排序行为不在性能。 - -#### 优先级队列 - -```java -public class ToDoList extends PriorityQueue { - static class ToDoItem implements Comparable { - private char primary; - private int secondary; - private String item; - - public ToDoItem(String item, char pri, int sec) { - this.item = item; - primary = pri; - secondary = sec; - } - @Override - public int compareTo(ToDoItem o) { - if(primary > o.primary) { - return 1; - } - if(primary == o.primary) { - if(secondary > o.secondary) { - return 1; - } else if (secondary == o.secondary) { - return 0; - } else { - return -1; - } - } - return -1; - } - @Override - public String toString() { - return "ToDoItem{" + - "primary=" + primary + - ", secondary=" + secondary + - ", item='" + item + '\'' + - '}'; - } - } - public void add(String item, char pri, int sec) { - super.add(new ToDoItem(item, pri, sec)); - } - - public static void main(String[] args) { - ToDoList toDoList = new ToDoList(); - toDoList.add("homework", 'b', 3); - toDoList.add("feed dog", 'a', 4); - toDoList.add("lunch", 'a', 1); - while (!toDoList.isEmpty()) { - System.out.println(toDoList.remove()); - } - } -} -/*output -ToDoItem{primary=a, secondary=1, item='lunch'} -ToDoItem{primary=a, secondary=4, item='feed dog'} -ToDoItem{primary=b, secondary=3, item='homework'} - */ -``` - -#### 双向队列 - -可以在队列任何一端添加或移除元素。Java 标准库中没有任何显式的用于双向队列的接口。 - -```java -public class Deque { - private LinkedList deque = new LinkedList<>(); - public void addFirst(T t) { deque.add(t); } - public void addLast(T t) { deque.add(t); } - public T getFirst() { return deque.getFirst(); } - public T getLast() { return deque.getLast(); } - public T removeFirst() { return deque.removeFirst(); } - public T removeLast() { return deque.removeLast(); } - public int size() { return deque.size(); } - @Override - public String toString() { return deque.toString(); } - - public static void main(String[] args) { - Deque deque = new Deque<>(); - for(int i = 20; i < 27; i++) { - deque.addFirst(i); - } - for(int j = 50; j < 55; j++) { - deque.addLast(j); - } - System.out.println(deque); - while(deque.size() != 0) { - System.out.print(deque.removeFirst() + " "); - } - } -} -/*output -[20, 21, 22, 23, 24, 25, 26, 50, 51, 52, 53, 54] -20 21 22 23 24 25 26 50 51 52 53 54 - */ -``` - -### LinkedHashMap - -可以在构造器中设定 LinkedHashMap,使之采用基于访问的最近最少使用(LRU)算法,没有访问过的元素会出现在队列的前面。可用于定期清理元素以节省空间。 - -```java -public class LinkedHashMapDemo { - public static void main(String[] args) { - LinkedHashMap linkedHashMap = new LinkedHashMap<>(new CountingMapData(5)); - System.out.println(linkedHashMap); - linkedHashMap = new LinkedHashMap<>(16, 0.75f, true); - linkedHashMap.putAll(new CountingMapData(5)); - for(int i = 0; i < 3; i++) { - linkedHashMap.get(i); - } - System.out.println(linkedHashMap); - linkedHashMap.get(3); - System.out.println(linkedHashMap); - } -} -/*output -{0=A0, 1=B0, 2=C0, 3=D0, 4=E0} -{3=D0, 4=E0, 0=A0, 1=B0, 2=C0} -{4=E0, 0=A0, 1=B0, 2=C0, 3=D0} - */ -``` - -### Colletions 工具类 - -```java -public class Utilities { - static List list = Arrays.asList("one two three four".split(" ")); - - public static void main(String[] args) { - System.out.println(list); - System.out.println("list disjoint(four)?" + - Collections.disjoint(list, Collections.singletonList("four"))); //不相交 - System.out.println("max: " + Collections.max(list)); - System.out.println("min: " + Collections.min(list)); - System.out.println("max with comparator: " + Collections.max(list, String.CASE_INSENSITIVE_ORDER)); - List subList = Arrays.asList("one two".split(" ")); - System.out.println("indexOfSubList: " + Collections.indexOfSubList(list, subList)); - System.out.println("lastIndexOfSubList: " + Collections.lastIndexOfSubList(list, subList)); - Collections.replaceAll(list, "one", "emm"); - System.out.println("replace all: " + list); - Collections.reverse(list); - System.out.println("reverse: " + list); - Collections.rotate(list, 1); - System.out.println("rotate: " + list); - List source = Arrays.asList("int the matrix".split(" ")); - Collections.copy(list, source); - System.out.println("copy: " + list); - Collections.swap(list, 0, list.size() - 1); - System.out.println("swap: " + list); - Collections.shuffle(list, new Random((47))); - System.out.println("shuffle: " + list); //打乱 - Collections.fill(list, "frequency"); - System.out.println("fill: " + list); - System.out.println("frequency of 'frequency': " + Collections.frequency(list, "frequency")); - List nCopies = Collections.nCopies(3, "nCopies;"); - System.out.println("nCopies: " + nCopies); - System.out.println("list disjoint nCopies? " + Collections.disjoint(list, nCopies)); - Enumeration e = Collections.enumeration(nCopies); - Vector v = new Vector<>(); - while(e.hasMoreElements()) { - v.add(e.nextElement()); - } - ArrayList arrayList = Collections.list(v.elements()); - System.out.println("arrayList: " + arrayList); - } -} -/*output -[one, two, three, four] -list disjoint(four)?false -max: two -min: four -max with comparator: two -indexOfSubList: 0 -lastIndexOfSubList: 0 -replace all: [emm, two, three, four] -reverse: [four, three, two, emm] -rotate: [emm, four, three, two] -copy: [int, the, matrix, two] -swap: [two, the, matrix, int] -shuffle: [two, the, int, matrix] -fill: [frequency, frequency, frequency, frequency] -frequency of 'frequency': 4 -nCopies: [nCopies;, nCopies;, nCopies;] -list disjoint nCopies? true -arrayList: [nCopies;, nCopies;, nCopies;] - */ -``` - -#### 排序和查询 - -```java -public class ListSortSearch { - public static void main(String[] args) { - List list = - new ArrayList(Utilities.list); - list.addAll(Utilities.list); - print(list); - Collections.shuffle(list, new Random(47)); - print("Shuffled: " + list); - // Use a ListIterator to trim off the last elements: - ListIterator it = list.listIterator(4); - while (it.hasNext()) { - it.next(); - it.remove(); - } - print("Trimmed: " + list); - Collections.sort(list); - print("Sorted: " + list); - String key = list.get(3); - int index = Collections.binarySearch(list, key); - print("Location of " + key + " is " + index + - ", list.get(" + index + ") = " + list.get(index)); - Collections.sort(list, String.CASE_INSENSITIVE_ORDER); - print("Case-insensitive sorted: " + list); - key = list.get(3); - index = Collections.binarySearch(list, key, - String.CASE_INSENSITIVE_ORDER); - print("Location of " + key + " is " + index + - ", list.get(" + index + ") = " + list.get(index)); - } -} /* Output: -[one, Two, three, Four, five, six, one, one, Two, three, Four, five, six, one] -Shuffled: [Four, five, one, one, Two, six, six, three, three, five, Four, Two, one, one] -Trimmed: [Four, five, one, one, Two, six, six, three, three, five] -Sorted: [Four, Two, five, five, one, one, six, six, three, three] -Location of six is 7, list.get(7) = six -Case-insensitive sorted: [five, five, Four, one, one, six, six, three, three, Two] -Location of three is 7, list.get(7) = three -*/ -``` - -#### 只读容器 - -```java -public class ReadOnly { - static Collection data = - new ArrayList<>(Countries.names(6)); - - public static void main(String[] args) { - Collection c = Collections.unmodifiableCollection( - new ArrayList<>(data)); - print(c); // Reading is OK - //! c.add("one"); // Can't change it - - List a = Collections.unmodifiableList(new ArrayList<>(data)); - ListIterator lit = a.listIterator(); - print(lit.next()); // Reading is OK - //! lit.add("one"); // Can't change it - - Set s = Collections.unmodifiableSet(new HashSet<>(data)); - print(s); // Reading is OK - //! s.add("one"); // Can't change it - - // For a SortedSet: - Set ss = Collections.unmodifiableSortedSet(new TreeSet<>(data)); - - Map m = Collections.unmodifiableMap( - new HashMap<>(Countries.capitals(6))); - print(m); // Reading is OK - //! m.put("Ralph", "Howdy!"); - - // For a SortedMap: - Map sm = Collections.unmodifiableSortedMap( - new TreeMap<>(Countries.capitals(6))); - } -} -``` - -#### Collection 和 Map 的同步控制 - -```java -public class Synchronization { - public static void main(String[] args) { - Collection c = Collections.synchronizedCollection(new ArrayList<>()); - List list = Collections.synchronizedList(new ArrayList<>()); - Set set = Collections.synchronizedSet(new HashSet<>()); - Set ss = Collections.synchronizedSortedSet(new TreeSet<>()); - Map map = Collections.synchronizedMap(new HashMap<>()); - Map sm = Collections.synchronizedSortedMap(new TreeMap<>()); - } -} -``` - -#### 快速报错机制 - - fast-fail 是 Java 容器的一种保护机制。当多个线程对同一个集合进行操作时,就有可能会产生 fast-fail 事件。例如:当线程a正通过 iterator 遍历集合时,另一个线程b修改了集合的内容(modCount 不等于expectedModCount),那么线程a在遍历的时候会抛出 ConcurrentModificationException,产生 fast-fail 事件。 - -```java -public class FastFail { - public static void main(String[] args) { - Collection c = new ArrayList<>(); - Iterator itr = c.iterator(); - c.add("haha"); - try { - String s = itr.next(); - } catch (ConcurrentModificationException ex) { - ex.printStackTrace(); - } - } -} -``` - -在此例中,应该添加完所有元素后,再获取迭代器。 - -**多线程并发修改容器的方法:** - -- 使用`Colletions.synchronizedList()`方法或在修改集合内容的地方加上 synchronized。这样的话,增删集合内容的同步锁会阻塞遍历操作,影响性能。 -- 使用 CopyOnWriteArrayList 来替换 ArrayList。在对 CopyOnWriteArrayList 进行修改操作的时候,会拷贝一个新的集合,对新的集合进行操作,操作完成后再把引用指向新的集合。 - -### Java 1.0/1.1 的容器 - -#### BitSet - -参考自:[JAVA中BitSet使用](https://blog.csdn.net/xv1356027897/article/details/79518647) - -位图,数据的存在性可以使用bit位上的1或0来表示,一个long型数字占用64位空间,那么一个long型数字就可以保存64个数字的“存在性”状态(true or false)。BitSet内部是一个long[]数组,数组的大小由 BitSet 接收的最大数字决定,这个数组将数字分段表示[0,63],[64,127],[128,191]...。即long[0]用来存储[0,63]这个范围的数字的“存在性”,long[1]用来存储[64,127],依次递推。 - -```java -public class BitSetDemo { - public static void printBitSet(BitSet b) { - System.out.println("BitSet: " + b); - StringBuilder sb = new StringBuilder(); - for(int i = 0; i < b.size(); i++) { - sb.append(b.get(i) ? "1" : "0"); - } - System.out.println("bitset pattern: " + sb); - } - public static void main(String[] args) { - Random rand = new Random(47); - int num = rand.nextInt(20); - BitSet bs = new BitSet(); - bs.set(num); - System.out.println(bs); - System.out.println("num exist? " + bs.get(num)); - System.out.println("size of bitset: " + bs.size()); - System.out.println("the number of bits set: " + bs.cardinality()); - bs.set(num);//重复设置 - System.out.println("set again! num exist? " + bs.get(num)); - bs.clear(num);//清除 - System.out.println("after clearing bit: " + bs); - - bs.set(num + 100);//自动扩充容量 - System.out.println("num+100 exist? " + bs.get(num + 100)); - System.out.println("num-1 exist? " + bs.get(num - 1)); - System.out.println("size of bitset: " + bs.size()); - } -} -/*output -{18} -num exist? true -size of bitset: 64 -the number of bits set: 1 -set again! num exist? true -after clearing bit: {} -num+100 exist? true -num-1 exist? false -size of bitset: 128 - */ -``` - - - -## Java I/O 系统 - -### 输入和输出 - -继承自 InputStream 或 Reader 的类都具有 read() 方法,用于读取单个字节或者字节数组;继承自 OutputStream 或 Writer 的类都含有 write() 方法,用于写单个字节或字节数组。 - -#### InputStream 和 OutputStream - -InputStream 用来表示那些从不同数据源产生输入的类。这些数据源包括:1.字节数组;2.String 对象;3.文件;4.管道;5.一个由其他种类的流组成的序列。 - -InputStream 类有一个抽象方法:`abstract int read()`,这个方法将读入并返回一个字节,或者在遇到输入源结尾时返回-1。 - -OutputStream 决定了输出所要去的目标:字节数组、文件或管道。OutputStream 的 `abstract void write(int b)` 可以向某个输出位置写出一个字节。 - -read() 和 write() 方法在执行时都将阻塞,等待数据被读入或者写出。 - -#### Reader 和 Writer - -字符流是由通过字节流转换得到的,转化过程耗时,而且容易出现乱码问题。I/O 流提供了一个直接操作字符的接口,方便我们平时对字符进行流操作。如果音频文件、图片等媒体文件用字节流比较好,如果涉及到字符的话使用字符流比较好。 - -```java -abstract int read(); -abstract void write(char c); -``` - -### 组合输入输出流过滤器 - -FileInputStream 和 FileOutputStream 可以提供附在磁盘文件上的输入流和输出流。 - -`FileInputStream fin = new FileInputStream("E:/data.txt") //E:\\data.txt` - -通过 java.io.File.separator 可以获得与平台相关的 文件分隔符。 - -FileInputStream 只支持在文件的读写字节,而DataInputStream 只支持数值的读写。 - -实现从文件读取数字,需要将 FileInputStream 和 DataInputStream 组合。 - -```java -FileInputStream fin = new FileInputStream("e:/data.txt"); -DataInputStream din = new DataInputStream(fin); -double x = din.readDouble(); -``` - -使用缓冲机制: - -```java -DataInputStream din = new DataInputStream( - new BufferedInputStream( - new FileInputStream("E:/data.txt") - ) -); -``` - -可回推的输入流: - -```java -PushbackInputStream pin = new PushbackInputStream( - new BufferedInputStream( - new FileInputStream("E:/data.txt") - ) -); - -int b = pin.read(); -if(b != '<') { - pin.unread(b); -} -``` - -从 zip 文件读入数字: - -```java -DataInputStream din = new DataInputStream( - new ZipInputStream( - new FileInputStream("E:/data.zip") - ) -); -``` - -### 文本输入和输出 - -将字节流转化为 Unicode 字符的读入器: - -```java -//Reader in = new InputStreamReader(System.in); -Reader in = new InputStreamReader(new FileInputStream("E:/data.txt"), StandardCharsets.UTF_8); -``` - -将 Unicode 字符转化为字节流的写出器: - -```java -PrintWriter out = new PrintWriter("H:/data.txt", "UTF-8"); -out.print("haha"); -out.flush(); -``` - -默认情况下,自动冲刷机制是禁用的,可以使用`PrintWriter(Writer out, Boolean autoFlush)`启用或禁用自动冲刷机制: - -```java -PrintWriter out = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream("H:/data.txt"), "UTF-8"), - true); -out.print("tyson"); -out.flush(); -``` - -### 以文本格式存储对象 - -```java -public class TextFile { - public static void main(String[] args) throws IOException { - Employee[] staff = new Employee[2]; - DateTimeFormatter ymd = DateTimeFormatter.ofPattern("yyyy-MM-dd"); - //字符串转换成LocalDate类型 - LocalDate ld = LocalDate.parse("2015-11-23", ymd); - staff[0] = new Employee("tyson", 1200.00, ld); - staff[1] = new Employee("tom", 1200.00, ld); - - try (PrintWriter out = new PrintWriter("H:/data.txt", "UTF-8")) { - writeData(staff, out); - } - try (Scanner in = new Scanner( - new FileInputStream("H:/data.txt"), "UTF-8")) { - Employee[] newStaff = readData(in); - - for(Employee e : newStaff) { - System.out.println(e); - } - } - } - - private static void writeData(Employee[] employees, PrintWriter out) throws IOException { - out.println(employees.length); - for(Employee e : employees) { - writeEmployee(out, e); - } - } - - private static Employee[] readData(Scanner in) { - int n = in.nextInt(); - in.nextLine(); - - Employee[] employees = new Employee[n]; - for(int i = 0; i < n ; i++) { - employees[i] = readEmployee(in); - } - - return employees; - } - - public static void writeEmployee(PrintWriter out, Employee e) throws IOException { - out.println(e.getName() + "|" + e.getSalary() + "|" + e.getHireDate()); - } - - public static Employee readEmployee(Scanner in) { - String line = in.nextLine(); - String[] tokens = line.split("\\|"); - String name = tokens[0]; - double salary = Double.parseDouble(tokens[1]); - LocalDate hireDate = LocalDate.parse(tokens[2]); - return new Employee(name, salary, hireDate); - } -} -/*output -Employee{name='tyson', salary=1200.0, hireDate=2015-11-23} -Employee{name='tom', salary=1200.0, hireDate=2015-11-23} - */ -``` - -### 字符编码方式 - -输入和输出流都是用于字节序列,很多情况下,我们希望操作的是字符序列。 - -平台使用的编码方法可以由静态方法`Charset.defaultCharset`返回。 - -将字节数组转化成字符串: - -```java -String str = new String(bytes, StandardCharsets.UTF_8); -``` - -### 读写二进制数据 - -#### DataInput 和 DataOutput - -DataOutput 接口定义了以二进制格式写数组、字符、字符串等的方法,如 writeChars、writeByte、writeInt 和 writeDouble。 - -writeInt 总是将一个整数写出为4字节的二进制数量值,writeDouble 总将一个 double 值写出为8字节的二进制数量值。Java 会将所有的值都按照高位在前的模式写出,这使得 Java 数据文件可以独立于平台。 - -DataInput 接口用于二进制格式读数据,接口定义了如下方法:readInt、readShort、readChar等。 - -DataInputStream 实现了 DataInput 接口,DataOutputStream 实现了 DataOutput 接口: - -```java -DataInputStream in = new DataInputStream(new FileInputStream("e:/data.txt")); -DataOutputStream out = new DataOutputStream(new FileOutputStream("e:/data.txt")); -``` - -#### 随机访问文件 - -RandomAccessFile 类可以在文件中的任何位置查找或写入。RandomAccessFile 类同时实现了 DataInput 和 DataOutput 接口。 - -#### 序列化 - -**保存和加载序列化对象** - -保存对象: - -```java -ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("e:/data.txt")); -Employee tyson = new Employee(); -out.writeObject(tyson); -``` - -读回对象: - -```java -ObjectInputStream in = new ObjectInputStream(new FileInputStream("e:/data.txt")); -Employee e = (Employee)in.readObject(); -``` - -其中,Employee 需要实现 Serializable 接口,接口没有任何方法: - -```java -Employee implements Serializable {...} -``` - -ObjectStreamTest.java - -```java -/** - * Copyright (C), 2018-2019 - * FileName: ObjectStream - * Author: Tyson - * Date: 2019/4/25/0025 14:58 - * Description: 对象序列化 - */ -package com.tyson.chapter18.io; - -import java.io.*; - -/** - * @author Tyson - * @ClassName: ObjectStream - * @create 2019/4/25/0025 14:58 - */ -public class ObjectStream { - public static void main(String[] args) throws IOException, ClassNotFoundException { - Employee tyson = new Employee("tyson", 8888.0, "2015-02-15"); - Manager sophia = new Manager("sophia", 6666.0, "2012-03-15"); - sophia.setSecretary(tyson); - Manager tom = new Manager("tom", 5555.0, "2014-03-16"); - tom.setSecretary(tyson); - Employee[] staff = new Employee[3]; - staff[0] = tyson; - staff[1] = sophia; - staff[2] = tom; - - try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("e:/data.txt"))) { - out.writeObject(staff); - out.flush(); - } - - try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("e:/data.txt"))) { - Employee[] newStaff = (Employee[]) in.readObject(); - newStaff[1].setSalary(23333.0); - for(Employee e : newStaff) { - System.out.println(e); - } - //原对象sophia不变 - System.out.println(staff[1]); - } - } -} - -class Manager extends Employee { - private Employee secretary; - - public Manager(String name, Double salary, String hireDate) { - super(name, salary, hireDate); - } - - @Override - public String toString() { - return "Manager{" + - "name=" +super.getName() + - ", salary=" +super.getSalary() + - ", hireday=" +super.getHireDate() + - ", secretary=" + secretary + - '}'; - } - - public Employee getSecretary() { - return secretary; - } - - public void setSecretary(Employee secretary) { - this.secretary = secretary; - } -} -``` - -通过关键字 transient 可以将某些不可序列化的域设置成瞬时的域,这些域在对象被序列化时总是被跳过。 - -**使用序列化实现克隆** - -将对象序列化到输出流中,然后将其读回,产生的新对象是原对象的深拷贝。 - -### 操作文件 - -File 类既能代表特定文件的名称,又能代表一个目录下的一组文件的名称。 - -#### 目录列表器 - -获取某个目录下以".java"文件后缀结尾的文件名。 - -```java -class DirFilter implements FilenameFilter { - private Pattern pattern; - public DirFilter(String regex) { - pattern = Pattern.compile(regex); - } - @Override - public boolean accept(File dir, String name) { - return pattern.matcher(name).matches(); - } -} - -public class DirList { - public static void main(String[] args) { - File path = new File("H:" + File.separator + - "java-data" + File.separator + "TIJ4-code" + File.separator + "containers"); - //Java文件后缀".+\\.java$" - String[] list = path.list(new DirFilter("A.+\\.java$")); - Arrays.sort(list); - System.out.println(Arrays.toString(list)); - } -} -/*output -[AssociativeArray.java] - */ -``` - -用匿名内部类实现。 - -```java -public class DirList { - public static void main(String[] args) { - File f = new File("H:" + File.separator + - "java-data" + File.separator + "TIJ4-code" + File.separator + "containers"); - String[] fileNames = f.list(new FilenameFilter() { - @Override - public boolean accept(File dir, String name) { - return name.endsWith(".java"); - } - }); - System.out.println(Arrays.toString(fileNames)); - } -} -``` - -### - -## 枚举类型 - -### 基本 enum 特性 - -除了不能继承自一个 enum 之外,我们基本上可以将 enum 看做一个常规的类。所有的 enum 都继承自 java.lang.Enum 类,所以 enum 不能再继承其他类。在创建一个新的 enum 时,可以同时实现一个或多个接口。 - -```java -enum Color { RED, GREEN, BLUE } - -public class EnumClass { - public static void main(String[] args) { - for(Color c : Color.values()) { - System.out.println(c + " ordinal: " + c.ordinal()); - System.out.print(c.compareTo(Color.RED) + " "); - System.out.print(c.equals(Color.RED) + " "); - System.out.println(c == Color.RED); - System.out.println(c.getDeclaringClass()); - System.out.println(c.name()); - System.out.println("-------------------------"); - } - for(String s : "RED GREEN BLUE".split(" ")) { - Color c = Enum.valueOf(Color.class, s); - System.out.println(c); - } - } -} -/*output -RED ordinal: 0 -0 true true -class com.tyson.chapter19.enumerated.Color -RED -------------------------- -GREEN ordinal: 1 -1 false false -class com.tyson.chapter19.enumerated.Color -GREEN -------------------------- -BLUE ordinal: 2 -2 false false -class com.tyson.chapter19.enumerated.Color -BLUE -------------------------- -RED -GREEN -BLUE - */ -``` - -values() 是编译器添加的 static 方法,Enum 类中没有 values() 方法。ordinal() 方法返回一个 int 值,这是每个 enum 实例声明时的次序,从0开始。valueOf() 根据给定的名字返回相应的 enum实例,如果不存在给定名字的实例,将会抛出异常。 - -### 向 enum 添加新方法 - -```java -public enum Direction { - WEST("Xizang"), - NORTH("Beijing"), - EAST("Shanghai"), - SOUTH("Hainan");//定义自己的方法,enum实例序列的最后需添加分号 - //属性和方法需要在enum实例之后定义 - private String discription; - private Direction(String discription) { - this.discription = discription; - } - public String getDiscription() { - return discription; - } - - public static void main(String[] args) { - for(Direction dir : Direction.values()) { - System.out.println(dir + ": " + dir.getDiscription()); - } - } -} -/*output -WEST: Xizang -NORTH: Beijing -EAST: Shanghai -SOUTH: Hainan - */ -``` - -### 覆盖 enum 的方法 - -```java -public enum Animal { - CAT, DOG, BIRD, PIG; - @Override - public String toString() { - String id = name(); - String lower = id.substring(1).toLowerCase(); - return id.charAt(0) + lower; - } - - public static void main(String[] args) { - for(Animal a : values()) { - System.out.print(a + " "); - } - } -} -//output: Cat Dog Bird Pig -``` - -### Switch 语句中的 enum - -```java -enum Signal { - GREEN, YELLOW, RED, -} - -public class TrafficLight { - Signal color = Signal.RED; - - public void change() { - switch (color) { - case RED: - color = Signal.GREEN; - break; - case GREEN: - color = Signal.YELLOW; - break; - case YELLOW: - color = Signal.RED; - break; - } - } - @Override - public String toString() { - return "the traffic light is: " + color; - } - - public static void main(String[] args) { - TrafficLight tl = new TrafficLight(); - for (int i = 0; i < 4; i++) { - System.out.println(tl); - tl.change(); - } - } -} -/*output -the traffic light is: RED -the traffic light is: GREEN -the traffic light is: YELLOW -the traffic light is: RED - */ -``` - -### EnumSet - - 如果你想用一个数表示多种状态,那么位运算是一种很好的选择。EnumSet 是通过位运算实现的。它是一个与枚举类型一起使用的专用 Set 实现。枚举set中所有元素都必须来自单个枚举类型(即必须是同类型,且该类型是 Enum 的子类)。 - -```java -public enum Season { - SPRING, SUMMER, AUTUMN, WINTER; - - public static void main(String[] args) { - Set emptyEnumSet = EnumSet.noneOf(Season.class); - System.out.println("EnumSet.noneOf(): " + emptyEnumSet); - emptyEnumSet.add(SPRING); - System.out.println("emptyEnumSet.add(SPRING): " + emptyEnumSet); - Set enumSet = EnumSet.allOf(Season.class); - System.out.println("EnumSet.allOf(): " + enumSet); - emptyEnumSet.addAll(enumSet); - System.out.println("emptyEnumSet.addAll(): " + emptyEnumSet); - Season[] seasons = new Season[emptyEnumSet.size()]; - enumSet.toArray(seasons); - System.out.println("seasons: " + Arrays.toString(seasons)); - } -} -/*output -EnumSet.noneOf(): [] -emptyEnumSet.add(SPRING): [SPRING] -EnumSet.allOf(): [SPRING, SUMMER, AUTUMN, WINTER] -emptyEnumSet.addAll(): [SPRING, SUMMER, AUTUMN, WINTER] -seasons: [SPRING, SUMMER, AUTUMN, WINTER] - */ -``` - -#### 源码解析 - -参考自:[EnumSet源码解析](https://www.jianshu.com/p/f7035c5816b1) - -```java -//EnumSet的容量小于64,创建的是RegularEnumSet,大于64,创建的是JumboEnumSet -public boolean add(E e) { - // 校验枚举类型 - typeCheck(e); - - long oldElements = elements; - elements |= (1L << ((Enum)e).ordinal()); - return elements != oldElements; -} - -/** - * 用于校验枚举类型,位于EnumSet中 - */ -final void typeCheck(E e) { - Class eClass = e.getClass(); - if (eClass != elementType && eClass.getSuperclass() != elementType) - throw new ClassCastException(eClass + " != " + elementType); -} -``` - -每一个枚举元素都有一个属性ordinal,用来表示该元素在枚举类型中的次序或者说下标。 - -![EnumSet add方法](https://img2018.cnblogs.com/blog/1252910/201904/1252910-20190415211843720-460902205.png) - -addAll 方法就是将 elements 上,从低位到枚举长度上的下标值置为1。比如某一个枚举类型共5个元素,而addAll 就是将 elements 的二进制的低5位置为1。 - -```java -void addAll() { - if (universe.length != 0) - elements = -1L >>> -universe.length; -} -``` - -### EnumMap - -EnumMap 是一种特殊的 Map,它要求其中的键必须来自一个 enum。由于 enum 本身的限制,所以 EnumMap 在内部可由数组实现。因此 EnumMap 的速度很快。 - -```java -interface Command { void action(); } -public class EnumMaps { - public static void main(String[] args) { - EnumMap em = new EnumMap<>(AlarmPoints.class); - em.put(KITCHEN, new Command() { - @Override - public void action() { - System.out.println("Kitchen fire!"); - } - }); - em.put(BATHROOM, new Command() { - @Override - public void action() { - System.out.println("Bathroom alert"); - } - }); - for(Map.Entry e : em.entrySet()) { - System.out.print(e.getKey() + ": "); - e.getValue().action(); - } - //获取不存在的值 - try { - em.get(UTILITY).action(); - } catch (Exception ex) { - System.out.println(ex); - } - } -} -/*output -BATHROOM: Bathroom alert -KITCHEN: Kitchen fire! -java.lang.NullPointerException - */ -``` - -与 EnumSet 一样,enum 实例定义时的次序决定了其在 EnumMap 中的顺序。每个 enum 实例都会作为键存放到 EnumMap 中,如果没有为这个键调用 put() 方法来存入相应的值的话,其对应的值为 null。 - - - -## 注解 - -Java SE5内置了三种定义在 java.lang 中的注解:@Override,@Deprecated 和 @SuppressWarnings(关闭不当的编译器警告信息)。 - -### 基本语法 - -```java -@Target(ElementType.METHOD)//应用于什么地方,方法、域等 -@Retention(RetentionPolicy.SOURCE)//注解在哪一个级别可用,SOURCE/CLASS/RUNTIME -public @interface Override { -} -``` - -#### 自定义注解 - -```java -//UserCase.java -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.RUNTIME) -public @interface UserCase { - public int id(); - public String description() default "no description"; -} - -//PasswordUtils.java -public class PasswordUtils { - @UserCase(id = 47, description = "Passwords must contain at least one numeric") - public boolean validatePassword(String password) { - return password.matches("\\w*\\d\\w*"); - } -} -``` - -注解元素(本例的是 id和description)只能是基本类型、String、Class、enum、Annotation 和这些类型的数组。 - -@Retention注解三种取值:**RetentionPolicy.SOURCE**、**RetentionPolicy.CLASS**、**RetentionPolicy.RUNTIME**分别对应:Java源文件(.java文件)---->.class文件---->内存中的字节码。 - -@Target元注解决定了一个注解可以标识到哪些成分上,如标识在在类身上,或者属性身上,或者方法身上等成分,@Target默认值为任何元素(成分)。 - -**Retention注解说明** - -参考自:[注解](https://www.cnblogs.com/xdp-gacl/p/3622275.html) - -当在Java源程序上加了一个注解,这个Java源程序要由javac去编译,javac把java源文件编译成.class文件,在编译成class时可能会把Java源程序上的一些注解给去掉,java编译器在处理java源程序时,可能会认为这个注解没有用了,于是就把这个注解去掉了,那么此时在编译好的class中就找不到注解了, 这是编译器编译java源程序时对注解进行处理的第一种可能情况,假设java编译器在把java源程序编译成class时,没有把java源程序中的注解去掉,那么此时在编译好的class中就可以找到注解,当程序使用编译好的class文件时,需要用类加载器把class文件加载到内存中,class文件中的东西不是字节码,class文件里面的东西由类加载器加载到内存中去,类加载器在加载class文件时,会对class文件里面的东西进行处理,如安全检查,处理完以后得到的最终在内存中的二进制的东西才是字节码,类加载器在把class文件加载到内存中时也有转换,转换时是否把class文件中的注解保留下来,这也有说法,所以说**一个注解的生命周期有三个阶段:java源文件是一个阶段,class文件是一个阶段,内存中的字节码是一个阶段**,javac把java源文件编译成.class文件时,有可能去掉里面的注解,类加载器把.class文件加载到内存时也有可能去掉里面的注解,因此**在自定义注解时就可以使用Retention注解指明自定义注解的生命周期,自定义注解的生命周期是在RetentionPolicy.SOURCE阶段(java源文件阶段),还是在RetentionPolicy.CLASS阶段(class文件阶段),或者是在RetentionPolicy.RUNTIME阶段(内存中的字节码运行时阶段)**,根据**JDK提供的API可以知道默认是在RetentionPolicy.CLASS阶段 (JDK的API写到:the retention policy defaults to RetentionPolicy.CLASS)。** - -### 元注解 - -元、注解负责注解其他的注解。 - -| 注解 | 作用 | -| ----------- | -------------------------------- | -| @Target | 表示该注解可以用于什么地方 | -| @Retention | 表示需要在什么级别保存该注解信息 | -| @Documented | 将此注解包含在 Javadoc 中 | -| @Inherited | 允许子类继承父类的注解 | - -### 编写注解处理器 - -使用注解时,很重要的一部分就是创建和使用注解处理器。 - -```java -public class UseCaseTracker { - public static void trackUseCases(List useCases, Class c) { - for(Method m : c.getDeclaredMethods()) { - UseCase uc = m.getAnnotation(UseCase.class); - if(uc != null) { - System.out.println("found use case: " + uc.id() + " " + uc.description()); - useCases.remove(new Integer(uc.id())); - } - } - for(int i : useCases) { - System.out.println("warning: missing use case-" + i); - } - } - - public static void main(String[] args) { - List useCases = new ArrayList<>(); - Collections.addAll(useCases, 47, 48); - trackUseCases(useCases, PasswordUtils.class); - } -} -/*output -found use case: 47 Passwords must contain at least one numeric -warning: missing use case-48 - */ -``` - -### 注解综合 - -```java -//TrafficLight.java -public enum TrafficLight { - RED, GREEN, YELLOW; -} - -//MetaAnnotation.java -public @interface MetaAnnotation { - String value(); -} - -//MyAnnotation.java -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.METHOD, ElementType.TYPE}) -public @interface MyAnnotation { - String color() default "blue"; - //当只有value属性要设置时,可以省略"value=" - String value(); - int[] arrayAttr() default {1, 2, 3}; - TrafficLight lamp() default TrafficLight.GREEN; - MetaAnnotation annotationAttr() default @MetaAnnotation("tyson"); -} - -//MyAnnotationTracker.java -@MyAnnotation( - color = "red", - value = "MyAnnotation注解用于MyAnnotationTracker类", - arrayAttr = {6, 6, 6}, - lamp = TrafficLight.GREEN, - annotationAttr = @MetaAnnotation("sophia") -) -public class MyAnnotationTracker { - @MyAnnotation("MyAnnotation注解用于main方法") - public static void main(String[] args) { - //如果指定类型的注解存在于此元素上则返回true - if(MyAnnotationTracker.class.isAnnotationPresent(MyAnnotation.class)) { - //获取类的注解 - MyAnnotation myAnnotation = (MyAnnotation)MyAnnotationTracker.class.getAnnotation(MyAnnotation.class); - System.out.println(myAnnotation.color()); - System.out.println(myAnnotation.value()); - System.out.println(Arrays.toString(myAnnotation.arrayAttr())); - System.out.println(myAnnotation.lamp()); - MetaAnnotation ma = myAnnotation.annotationAttr(); - System.out.println(ma.value()); - } - //获取方法的注解 - for(Method m : MyAnnotationTracker.class.getDeclaredMethods()) { - MyAnnotation myAnnotation1 = m.getAnnotation(MyAnnotation.class); - if(myAnnotation1 != null) { - System.out.println(myAnnotation1.value()); - } - } - } -} -/*output -red -MyAnnotation注解用于MyAnnotationTracker类 -[6, 6, 6] -GREEN -sophia -MyAnnotation注解用于main方法 - */ -``` - - - - - - - diff --git "a/Java/Java\345\205\263\351\224\256\345\255\227.md" "b/Java/Java\345\205\263\351\224\256\345\255\227.md" deleted file mode 100644 index 3dc54e5..0000000 --- "a/Java/Java\345\205\263\351\224\256\345\255\227.md" +++ /dev/null @@ -1,205 +0,0 @@ - - - - -- [static](#static) - - [静态变量](#%E9%9D%99%E6%80%81%E5%8F%98%E9%87%8F) - - [静态方法](#%E9%9D%99%E6%80%81%E6%96%B9%E6%B3%95) - - [静态代码块](#%E9%9D%99%E6%80%81%E4%BB%A3%E7%A0%81%E5%9D%97) - - [静态内部类](#%E9%9D%99%E6%80%81%E5%86%85%E9%83%A8%E7%B1%BB) -- [final](#final) -- [this](#this) -- [super](#super) - - - -## static - -static可以用来修饰类的成员方法、类的成员变量。 - -### 静态变量 - -static变量也称作静态变量,静态变量和非静态变量的区别是:静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。 - -以下例子,age为非静态变量,则p1打印结果是:`Name:zhangsan, Age:10`;若age使用static修饰,则p1打印结果是:`Name:zhangsan, Age:12`,因为static变量在内存只有一个副本。 - -```java -public class Person { - String name; - int age; - - public String toString() { - return "Name:" + name + ", Age:" + age; - } - - public static void main(String[] args) { - Person p1 = new Person(); - p1.name = "zhangsan"; - p1.age = 10; - Person p2 = new Person(); - p2.name = "lisi"; - p2.age = 12; - System.out.println(p1); - System.out.println(p2); - } - /**Output - * Name:zhangsan, Age:10 - * Name:lisi, Age:12 - *///~ -} -``` - -### 静态方法 - -static方法一般称作静态方法。静态方法不依赖于任何对象就可以进行访问,通过类名即可调用静态方法。 - -```java -public class Utils { - public static void print(String s) { - System.out.println("hello world: " + s); - } - - public static void main(String[] args) { - Utils.print("程序员大彬"); - } -} -``` - -### 静态代码块 - -静态代码块只会在类加载的时候执行一次。以下例子,startDate和endDate在类加载的时候进行赋值。 - -```java -class Person { - private Date birthDate; - private static Date startDate, endDate; - static{ - startDate = Date.valueOf("2008"); - endDate = Date.valueOf("2021"); - } - - public Person(Date birthDate) { - this.birthDate = birthDate; - } -} -``` - -### 静态内部类 - -**在静态方法里**,使用⾮静态内部类依赖于外部类的实例,也就是说需要先创建外部类实例,才能用这个实例去创建非静态内部类。⽽静态内部类不需要。 - -```java -public class OuterClass { - class InnerClass { - } - static class StaticInnerClass { - } - public static void main(String[] args) { - // 在静态方法里,不能直接使用OuterClass.this去创建InnerClass的实例 - // 需要先创建OuterClass的实例o,然后通过o创建InnerClass的实例 - // InnerClass innerClass = new InnerClass(); - OuterClass outerClass = new OuterClass(); - InnerClass innerClass = outerClass.new InnerClass(); - StaticInnerClass staticInnerClass = new StaticInnerClass(); - - outerClass.test(); - } - - public void nonStaticMethod() { - InnerClass innerClass = new InnerClass(); - System.out.println("nonStaticMethod..."); - } -} -``` - - - -## final - -1. **基本数据**类型用final修饰,则不能修改,是常量;**对象引用**用final修饰,则引用只能指向该对象,不能指向别的对象,但是对象本身可以修改。 - -2. final修饰的方法不能被子类重写 - -3. final修饰的类不能被继承。 - - - -## this - - `this.属性名称`指访问类中的成员变量,可以用来区分成员变量和局部变量。如下代码所示,`this.name`访问类Person当前实例的变量。 - -```java -/** - * @description: - * @author: 程序员大彬 - * @time: 2021-08-17 00:29 - */ -public class Person { - String name; - int age; - - public Person(String name, int age) { - this.name = name; - this.age = age; - } -} -``` - -`this.方法名称`用来访问本类的方法。以下代码中,`this.born()`调用类 Person 的当前实例的方法。 - -```java -/** - * @description: - * @author: 程序员大彬 - * @time: 2021-08-17 00:29 - */ -public class Person { - String name; - int age; - - public Person(String name, int age) { - this.born(); - this.name = name; - this.age = age; - } - - void born() { - } -} -``` - - - -## super - -super 关键字用于在子类中访问父类的变量和方法。 - -```java -class A { - protected String name = "大彬"; - - public void getName() { - System.out.println("父类:" + name); - } -} - -public class B extends A { - @Override - public void getName() { - System.out.println(super.name); - super.getName(); - } - - public static void main(String[] args) { - B b = new B(); - b.getName(); - } - /** - * 大彬 - * 父类:大彬 - */ -} -``` - -在子类B中,我们重写了父类的getName()方法,如果在重写的getName()方法中我们要调用父类的相同方法,必须要通过super关键字显式指出。 - diff --git "a/Java/Java\345\237\272\347\241\200.md" "b/Java/Java\345\237\272\347\241\200.md" deleted file mode 100644 index 3a08dc1..0000000 --- "a/Java/Java\345\237\272\347\241\200.md" +++ /dev/null @@ -1,1582 +0,0 @@ - - - - -- [Java概述](#java%E6%A6%82%E8%BF%B0) - - [Java的特点](#java%E7%9A%84%E7%89%B9%E7%82%B9) - - [JKD和JRE](#jkd%E5%92%8Cjre) - - [JDK](#jdk) - - [JRE](#jre) -- [Java基础语法](#java%E5%9F%BA%E7%A1%80%E8%AF%AD%E6%B3%95) - - [基本数据类型](#%E5%9F%BA%E6%9C%AC%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B) - - [包装类型](#%E5%8C%85%E8%A3%85%E7%B1%BB%E5%9E%8B) - - [包装类缓存](#%E5%8C%85%E8%A3%85%E7%B1%BB%E7%BC%93%E5%AD%98) - - [String](#string) - - [String拼接](#string%E6%8B%BC%E6%8E%A5) - - [关键字](#%E5%85%B3%E9%94%AE%E5%AD%97) - - [static](#static) - - [静态变量](#%E9%9D%99%E6%80%81%E5%8F%98%E9%87%8F) - - [静态方法](#%E9%9D%99%E6%80%81%E6%96%B9%E6%B3%95) - - [静态代码块](#%E9%9D%99%E6%80%81%E4%BB%A3%E7%A0%81%E5%9D%97) - - [静态内部类](#%E9%9D%99%E6%80%81%E5%86%85%E9%83%A8%E7%B1%BB) - - [final](#final) - - [this](#this) - - [super](#super) - - [object常用方法](#object%E5%B8%B8%E7%94%A8%E6%96%B9%E6%B3%95) - - [toString](#tostring) - - [equals](#equals) - - [hashCode](#hashcode) - - [clone](#clone) - - [浅拷贝](#%E6%B5%85%E6%8B%B7%E8%B4%9D) - - [深拷贝](#%E6%B7%B1%E6%8B%B7%E8%B4%9D) - - [getClass](#getclass) - - [wait](#wait) - - [notity](#notity) - - [equals()和hashcode()的关系](#equals%E5%92%8Chashcode%E7%9A%84%E5%85%B3%E7%B3%BB) - - [==和equals区别](#%E5%92%8Cequals%E5%8C%BA%E5%88%AB) -- [面向对象](#%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1) - - [面向对象特性](#%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E7%89%B9%E6%80%A7) - - [多态怎么实现](#%E5%A4%9A%E6%80%81%E6%80%8E%E4%B9%88%E5%AE%9E%E7%8E%B0) - - [类与对象](#%E7%B1%BB%E4%B8%8E%E5%AF%B9%E8%B1%A1) - - [属性](#%E5%B1%9E%E6%80%A7) - - [方法](#%E6%96%B9%E6%B3%95) - - [普通方法](#%E6%99%AE%E9%80%9A%E6%96%B9%E6%B3%95) - - [构造方法](#%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95) - - [方法重载](#%E6%96%B9%E6%B3%95%E9%87%8D%E8%BD%BD) - - [方法重写](#%E6%96%B9%E6%B3%95%E9%87%8D%E5%86%99) - - [初始化顺序](#%E5%88%9D%E5%A7%8B%E5%8C%96%E9%A1%BA%E5%BA%8F) -- [接口和抽象类](#%E6%8E%A5%E5%8F%A3%E5%92%8C%E6%8A%BD%E8%B1%A1%E7%B1%BB) - - [抽象类](#%E6%8A%BD%E8%B1%A1%E7%B1%BB) - - [接口](#%E6%8E%A5%E5%8F%A3) - - [接口与抽象类区别](#%E6%8E%A5%E5%8F%A3%E4%B8%8E%E6%8A%BD%E8%B1%A1%E7%B1%BB%E5%8C%BA%E5%88%AB) -- [反射](#%E5%8F%8D%E5%B0%84) - - [Class类](#class%E7%B1%BB) - - [Field类](#field%E7%B1%BB) - - [Method类](#method%E7%B1%BB) -- [泛型](#%E6%B3%9B%E5%9E%8B) - - [泛型类](#%E6%B3%9B%E5%9E%8B%E7%B1%BB) - - [泛型接口](#%E6%B3%9B%E5%9E%8B%E6%8E%A5%E5%8F%A3) - - [泛型方法](#%E6%B3%9B%E5%9E%8B%E6%96%B9%E6%B3%95) -- [Exception](#exception) - - [Throwable](#throwable) - - [常见的Exception](#%E5%B8%B8%E8%A7%81%E7%9A%84exception) - - [关键字](#%E5%85%B3%E9%94%AE%E5%AD%97-1) -- [IO流](#io%E6%B5%81) - - [InputStream 和 OutputStream](#inputstream-%E5%92%8C-outputstream) - - [Reader 和 Writer](#reader-%E5%92%8C-writer) - - [字符流和字节流的转换](#%E5%AD%97%E7%AC%A6%E6%B5%81%E5%92%8C%E5%AD%97%E8%8A%82%E6%B5%81%E7%9A%84%E8%BD%AC%E6%8D%A2) - - [同步异步](#%E5%90%8C%E6%AD%A5%E5%BC%82%E6%AD%A5) - - [阻塞非阻塞](#%E9%98%BB%E5%A1%9E%E9%9D%9E%E9%98%BB%E5%A1%9E) - - [BIO](#bio) - - [NIO](#nio) - - [AIO](#aio) - - [BIO/NIO/AIO区别](#bionioaio%E5%8C%BA%E5%88%AB) -- [ThreadLocal](#threadlocal) - - [原理](#%E5%8E%9F%E7%90%86) - - [内存泄漏](#%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F) - - [使用场景](#%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF) -- [线程安全类](#%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E7%B1%BB) -- [常见操作](#%E5%B8%B8%E8%A7%81%E6%93%8D%E4%BD%9C) - - [排序](#%E6%8E%92%E5%BA%8F) - - [数组操作](#%E6%95%B0%E7%BB%84%E6%93%8D%E4%BD%9C) - - [拷贝](#%E6%8B%B7%E8%B4%9D) - - [数组拷贝](#%E6%95%B0%E7%BB%84%E6%8B%B7%E8%B4%9D) - - [对象拷贝](#%E5%AF%B9%E8%B1%A1%E6%8B%B7%E8%B4%9D) - - [序列化](#%E5%BA%8F%E5%88%97%E5%8C%96) - - [什么情况下需要序列化?](#%E4%BB%80%E4%B9%88%E6%83%85%E5%86%B5%E4%B8%8B%E9%9C%80%E8%A6%81%E5%BA%8F%E5%88%97%E5%8C%96) - - [如何实现序列化](#%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0%E5%BA%8F%E5%88%97%E5%8C%96) - - [serialVersionUID](#serialversionuid) - - [遍历](#%E9%81%8D%E5%8E%86) - - [fast-fail](#fast-fail) - - [fail-safe](#fail-safe) - - [移除集合元素](#%E7%A7%BB%E9%99%A4%E9%9B%86%E5%90%88%E5%85%83%E7%B4%A0) - - - -# Java概述 - -## Java的特点 - -**Java是一门面向对象的编程语言。**面向对象和面向过程是一种软件开发思想。 - -- 面向过程就是分析出解决问题所需要的步骤,然后用函数按这些步骤实现,使用的时候依次调用就可以了。面向对象是把构成问题事务分解成各个对象,分别设计这些对象,然后将他们组装成有完整功能的系统。面向过程只用函数实现,面向对象是用类实现各个功能模块。 - - 例如五子棋,面向过程的设计思路就是首先分析问题的步骤: - 1、开始游戏,2、黑子先走,3、绘制画面,4、判断输赢,5、轮到白子,6、绘制画面,7、判断输赢,8、返回步骤2,9、输出最后结果。 - 把上面每个步骤用分别的函数来实现,问题就解决了。 - -- 而面向对象的设计则是从另外的思路来解决问题。整个五子棋可以分为: - 1、黑白双方 - 2、棋盘系统,负责绘制画面 - 3、规则系统,负责判定诸如犯规、输赢等。 - 黑白双方负责接受用户的输入,并告知棋盘系统棋子布局发生变化,棋盘系统接收到了棋子的变化的信息就负责在屏幕上面显示出这种变化,同时利用规则系统来对棋局进行判定。 - -**Java具有平台独立性和移植性。** - -- Java有一句口号:Write once, run anywhere,一次编写、到处运行。这也是Java的魅力所在。而实现这种特性的正是Java虚拟机JVM。已编译的Java程序可以在任何带有JVM的平台上运行。你可以在windows平台编写代码,然后拿到linux上运行。只要你在编写完代码后,将代码编译成.class文件,再把class文件打成Java包,这个jar包就可以在不同的平台上运行了。 - -**Java具有稳健性。** - -- Java是一个强类型语言,它允许扩展编译时检查潜在类型不匹配问题的功能。Java要求显式的方法声明,它不支持C风格的隐式声明。这些严格的要求保证编译程序能捕捉调用错误,这就导致更可靠的程序。 -- 异常处理是Java中使得程序更稳健的另一个特征。异常是某种类似于错误的异常条件出现的信号。使用try/catch/finally语句,程序员可以找到出错的处理代码,这就简化了出错处理和恢复的任务。 - -## JKD和JRE - -JDK和JRE是Java开发和运行工具,其中JDK包含了JRE,而JRE是可以独立安装的。 - -### JDK - -Java Development Kit,JAVA语言的软件工具开发包,是整个JAVA开发的核心,它包含了JAVA的运行(JVM+JAVA类库)环境和JAVA工具。 - -### JRE - -JRE(Java Runtime Environment,Java运行环境):包含JVM标准实现及Java核心类库。JRE是Java运行环境,并不是一个开发环境,所以没有包含任何开发工具(如编译器和调试器)。 - -JRE是运行基于Java语言编写的程序所不可缺少的运行环境。也是通过它,Java的开发者才得以将自己开发的程序发布到用户手中,让用户使用。 - -# Java基础语法 - -## 基本数据类型 - -- byte,8bit -- char,16bit -- short,16bit -- int,32bit -- float,32bit -- long,64bit -- double,64bit -- boolean,只有两个值:true、false,可以使⽤用 1 bit 来存储,但是具体⼤小没有明确规定。 - -## 包装类型 - -基本类型都有对应的包装类型,基本类型与其对应的包装类型之间的赋值通过自动装箱与拆箱完成。 - -```java -Integer x = 1; // 装箱 调⽤ Integer.valueOf(1) -int y = x; // 拆箱 调⽤了 X.intValue() -``` - -### 包装类缓存 - -使用Integer.valueOf(i)生成Integer,如果 -128 <= i <= 127,则直接从cache取对象返回,不会创建新的对象,避免频繁创建包装类对象。 - -```java - public static Integer valueOf(int i) { - if (i >= IntegerCache.low && i <= IntegerCache.high) - return IntegerCache.cache[i + (-IntegerCache.low)]; - return new Integer(i); - } -``` - -## String - -String是final类,不可被继承。 - -### String拼接 - -字符串拼接可以使用String用+做拼接,也可以使用StringBuilder和StringBuffer实现,三种方式对比: - -- 底层都是char数组实现的 - -- 字符串拼接性能:StringBuilder > StringBuffer > String -- String 是字符串常量,一旦创建之后该对象是不可更改的,用+对String做拼接操作,实际上是先通过建立StringBuilder,然后调用append()做拼接操作,所以在大量字符串拼接的时候,会频繁创建StringBuilder,性能较差。 -- StringBuilder和StringBuffer的对象是字符串变量,对变量进行操作就是直接对该对象进行修改(修改char[]数组),所以速度要比String快很多。 -- 在线程安全上,StringBuilder是线程不安全的,而StringBuffer是线程安全的,StringBuffer中很多方法带有synchronized关键字,可以保证线程安全。 - -## 关键字 - -### static - -static可以用来修饰类的成员方法、类的成员变量。 - -#### 静态变量 - -static变量也称作静态变量,静态变量和非静态变量的区别是:静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。 - -以下例子,age为非静态变量,则p1打印结果是:`Name:zhangsan, Age:10`;若age使用static修饰,则p1打印结果是:`Name:zhangsan, Age:12`,因为static变量在内存只有一个副本。 - -```java -public class Person { - String name; - int age; - - public String toString() { - return "Name:" + name + ", Age:" + age; - } - - public static void main(String[] args) { - Person p1 = new Person(); - p1.name = "zhangsan"; - p1.age = 10; - Person p2 = new Person(); - p2.name = "lisi"; - p2.age = 12; - System.out.println(p1); - System.out.println(p2); - } - /**Output - * Name:zhangsan, Age:10 - * Name:lisi, Age:12 - *///~ -} -``` - -#### 静态方法 - -static方法一般称作静态方法。静态方法不依赖于任何对象就可以进行访问,通过类名即可调用静态方法。 - -```java -public class Utils { - public static void print(String s) { - System.out.println("hello world: " + s); - } - - public static void main(String[] args) { - Utils.print("程序员大彬"); - } -} -``` - -#### 静态代码块 - -静态代码块只会在类加载的时候执行一次。以下例子,startDate和endDate在类加载的时候进行赋值。 - -```java -class Person { - private Date birthDate; - private static Date startDate, endDate; - static{ - startDate = Date.valueOf("2008"); - endDate = Date.valueOf("2021"); - } - - public Person(Date birthDate) { - this.birthDate = birthDate; - } -} -``` - -#### 静态内部类 - -**在静态方法里**,使用⾮静态内部类依赖于外部类的实例,也就是说需要先创建外部类实例,才能用这个实例去创建非静态内部类。⽽静态内部类不需要。 - -```java -public class OuterClass { - class InnerClass { - } - static class StaticInnerClass { - } - public static void main(String[] args) { - // 在静态方法里,不能直接使用OuterClass.this去创建InnerClass的实例 - // 需要先创建OuterClass的实例o,然后通过o创建InnerClass的实例 - // InnerClass innerClass = new InnerClass(); - OuterClass outerClass = new OuterClass(); - InnerClass innerClass = outerClass.new InnerClass(); - StaticInnerClass staticInnerClass = new StaticInnerClass(); - - outerClass.test(); - } - - public void nonStaticMethod() { - InnerClass innerClass = new InnerClass(); - System.out.println("nonStaticMethod..."); - } -} -``` - - - -### final - -1. **基本数据**类型用final修饰,则不能修改,是常量;**对象引用**用final修饰,则引用只能指向该对象,不能指向别的对象,但是对象本身可以修改。 - -2. final修饰的方法不能被子类重写 - -3. final修饰的类不能被继承。 - - - -### this - - `this.属性名称`指访问类中的成员变量,可以用来区分成员变量和局部变量。如下代码所示,`this.name`访问类Person当前实例的变量。 - -```java -/** - * @description: - * @author: 程序员大彬 - * @time: 2021-08-17 00:29 - */ -public class Person { - String name; - int age; - - public Person(String name, int age) { - this.name = name; - this.age = age; - } -} -``` - -`this.方法名称`用来访问本类的方法。以下代码中,`this.born()`调用类 Person 的当前实例的方法。 - -```java -/** - * @description: - * @author: 程序员大彬 - * @time: 2021-08-17 00:29 - */ -public class Person { - String name; - int age; - - public Person(String name, int age) { - this.born(); - this.name = name; - this.age = age; - } - - void born() { - } -} -``` - - - -### super - -super 关键字用于在子类中访问父类的变量和方法。 - -```java -class A { - protected String name = "大彬"; - - public void getName() { - System.out.println("父类:" + name); - } -} - -public class B extends A { - @Override - public void getName() { - System.out.println(super.name); - super.getName(); - } - - public static void main(String[] args) { - B b = new B(); - b.getName(); - } - /** - * 大彬 - * 父类:大彬 - */ -} -``` - -在子类B中,我们重写了父类的getName()方法,如果在重写的getName()方法中我们要调用父类的相同方法,必须要通过super关键字显式指出。 - -## object常用方法 - -Java面试经常会出现的一道题目,Object的常用方法。下面给大家整理一下。 - -object常用方法有:toString()、equals()、hashCode()、clone()等。 - -### toString - -默认输出对象地址。 - -```java -public class Person { - private int age; - private String name; - - public Person(int age, String name) { - this.age = age; - this.name = name; - } - - public static void main(String[] args) { - System.out.println(new Person(18, "程序员大彬").toString()); - } - //output - //me.tyson.java.core.Person@4554617c -} -``` - -可以重写toString方法,按照重写逻辑输出对象值。 - -```java -public class Person { - private int age; - private String name; - - public Person(int age, String name) { - this.age = age; - this.name = name; - } - - @Override - public String toString() { - return name + ":" + age; - } - - public static void main(String[] args) { - System.out.println(new Person(18, "程序员大彬").toString()); - } - //output - //程序员大彬:18 -} -``` - -### equals - -默认比较两个引用变量是否指向同一个对象(内存地址)。 - -```java -public class Person { - private int age; - private String name; - - public Person(int age, String name) { - this.age = age; - this.name = name; - } - - public static void main(String[] args) { - String name = "程序员大彬"; - Person p1 = new Person(18, name); - Person p2 = new Person(18, name); - - System.out.println(p1.equals(p2)); - } - //output - //false -} -``` - -可以重写equals方法,按照age和name是否相等来判断: - -```java -public class Person { - private int age; - private String name; - - public Person(int age, String name) { - this.age = age; - this.name = name; - } - - @Override - public boolean equals(Object o) { - if (o instanceof Person) { - Person p = (Person) o; - return age == p.age && name.equals(p.name); - } - return false; - } - - public static void main(String[] args) { - String name = "程序员大彬"; - Person p1 = new Person(18, name); - Person p2 = new Person(18, name); - - System.out.println(p1.equals(p2)); - } - //output - //true -} -``` - -### hashCode - -将与对象相关的信息映射成一个哈希值,默认的实现hashCode值是根据内存地址换算出来。 - -```java -public class Cat { - public static void main(String[] args) { - System.out.println(new Cat().hashCode()); - } - //out - //1349277854 -} -``` - -### clone - -java赋值是复制对象引用,如果我们想要得到一个对象的副本,使用赋值操作是无法达到目的的。Object对象有个clone()方法,实现了对 - -象中各个属性的复制,但它的可见范围是protected的。 - -```java -protected native Object clone() throws CloneNotSupportedException; -``` - -所以实体类使用克隆的前提是: - -- 实现Cloneable接口,这是一个标记接口,自身没有方法,这应该是一种约定。调用clone方法时,会判断有没有实现Cloneable接口,没有实现Cloneable的话会抛异常CloneNotSupportedException。 -- 覆盖clone()方法,可见性提升为public。 - -```java -public class Cat implements Cloneable { - private String name; - - @Override - protected Object clone() throws CloneNotSupportedException { - return super.clone(); - } - - public static void main(String[] args) throws CloneNotSupportedException { - Cat c = new Cat(); - c.name = "程序员大彬"; - Cat cloneCat = (Cat) c.clone(); - c.name = "大彬"; - System.out.println(cloneCat.name); - } - //output - //程序员大彬 -} -``` - -#### 浅拷贝 - -拷⻉对象和原始对象的引⽤类型引用同⼀个对象。 - -以下例子,Cat对象里面有个Person对象,调用clone之后,克隆对象和原对象的Person引用的是同一个对象,这就是浅拷贝。 - -```java -public class Cat implements Cloneable { - private String name; - private Person owner; - - @Override - protected Object clone() throws CloneNotSupportedException { - return super.clone(); - } - - public static void main(String[] args) throws CloneNotSupportedException { - Cat c = new Cat(); - Person p = new Person(18, "程序员大彬"); - c.owner = p; - - Cat cloneCat = (Cat) c.clone(); - p.setName("大彬"); - System.out.println(cloneCat.owner.getName()); - } - //output - //大彬 -} -``` - -#### 深拷贝 - -拷贝对象和原始对象的引用类型引用不同的对象。 - -以下例子,在clone函数中不仅调用了super.clone,而且调用Person对象的clone方法(Person也要实现Cloneable接口并重写clone方法),从而实现了深拷贝。可以看到,拷贝对象的值不会受到原对象的影响。 - -```java -public class Cat implements Cloneable { - private String name; - private Person owner; - - @Override - protected Object clone() throws CloneNotSupportedException { - Cat c = null; - c = (Cat) super.clone(); - c.owner = (Person) owner.clone();//拷贝Person对象 - return c; - } - - public static void main(String[] args) throws CloneNotSupportedException { - Cat c = new Cat(); - Person p = new Person(18, "程序员大彬"); - c.owner = p; - - Cat cloneCat = (Cat) c.clone(); - p.setName("大彬"); - System.out.println(cloneCat.owner.getName()); - } - //output - //程序员大彬 -} -``` - -### getClass - -返回此 Object 的运行时类,常用于java反射机制。 - -```java -public class Person { - private String name; - - public Person(String name) { - this.name = name; - } - - public static void main(String[] args) { - Person p = new Person("程序员大彬"); - Class clz = p.getClass(); - System.out.println(clz); - //获取类名 - System.out.println(clz.getName()); - } - /** - * class com.tyson.basic.Person - * com.tyson.basic.Person - */ -} -``` - -### wait - -当前线程调用对象的wait()方法之后,当前线程会释放对象锁,进入等待状态。等待其他线程调用此对象的notify()/notifyAll()唤醒或者等待超时时间wait(long timeout)自动唤醒。线程需要获取obj对象锁之后才能调用 obj.wait()。 - -### notity - -obj.notify()唤醒在此对象上等待的单个线程,选择是任意性的。notifyAll()唤醒在此对象上等待的所有线程。 - -> LockSupport.park()/LockSupport.parkNanos(long nanos)/LockSupport.parkUntil(long deadlines),用于阻塞当前线程。对比obj.wait()方法,不需要获得锁就可以让线程进入等待状态,需要通过LockSupport.unpark(Thread thread)唤醒。 - -### equals()和hashcode()的关系 - -equals与hashcode的关系: -1、如果两个对象调用equals比较返回true,那么它们的hashCode值一定要相同; -2、如果两个对象的hashCode相同,它们并不一定相同。 - -hashcode方法主要是用来提升对象比较的效率,先进行hashcode()的比较,如果不相同,那就不必在进行equals的比较,这样就大大减少了equals比较的次数,当比较对象的数量很大的时候能提升效率。 - -之所以重写equals()要重写hashcode(),是为了保证equals()方法返回true的情况下hashcode值也要一致,如果重写了equals()没有重写hashcode(),就会出现两个对象相等但hashcode()不相等的情况。这样,当用其中的一个对象作为键保存到hashMap、hashTable或hashSet中,再以另一个对象作为键值去查找他们的时候,则会查找不到。 - -### ==和equals区别 - -- 对于基本数据类型,==比较的是他们的值。基本数据类型没有equal方法; - -- 对于复合数据类型,==比较的是它们的存放地址(是否是同一个对象)。equals()默认比较地址值,重写的话按照重写逻辑去比较。 - -# 面向对象 - -## 面向对象特性 - -面向对象四大特性:封装,继承,多态,抽象 - -- 封装就是将类的信息隐藏在类内部,不允许外部程序直接访问,而是通过该类的方法实现对隐藏信息的操作和访问。 良好的封装能够减少耦合。 -- 继承是从已有的类中派生出新的类,新的类继承父类的属性和行为,并能扩展新的能力,大大增加程序的重用性和易维护性。在Java中是单继承的,也就是说一个子类只有一个父类。 -- 多态是同一个行为具有多个不同表现形式的能力。在不修改程序代码的情况下改变程序运行时绑定的代码。 - 实现多态的三要素:继承、重写、父类引用指向子类对象。 - 静态多态性:通过重载实现,相同的方法有不同的參数列表,可以根据参数的不同,做出不同的处理。 - 动态多态性:在子类中重写父类的方法。运行期间判断所引用对象的实际类型,根据其实际类型调用相应的方法。 -- 抽象。把客观事物用代码抽象出来。 - -### 多态怎么实现 - -Java提供了编译时多态和运行时多态两种多态机制。编译时多态通过重载实现,根据传入参数不同调用不同的方法。运行时多态通过重写来实现,在子类中重写父类的方法,运行期间判断所引用对象的实际类型,根据其实际类型调用相应的方法。 - -## 类与对象 - -**类class**对一类事物的描述,是抽象的、概念上的定义。类的结构包括属性和方法。 - -```java -public class Person { - //属性 - private int age; - private String name; - - //构造方法 - public Person(int age, String name) { - this.age = age; - this.name = name; - } -} -``` - -对象是实际存在的该类事物的个体,也称为实例。 - -```java -public class Person { - private int age; - private String name; - - public Person(int age, String name) { - this.age = age; - this.name = name; - } - - public static void main(String[] args) { - //p为对象 - Person p = new Person(18, "程序员大彬"); - } -} -``` - -在Java中,万物皆对象,一切事物都可以看做对象。上述代码通过new关键字,创建了Person对象,p为对象的引用,p指向所创建的Person对象的内存地址。 - -## 属性 - -属性用来描述对象的状态信息,通常以变量的形式进行定义。变量分为成员变量和局部变量。 - -在类中,方法体之外定义的变量称为成员变量: - -- 成员变量定义在类中,在整个类中都可以被访问 -- 成员变量分为类变量和实例变量,实例变量存在于每个对象所在的堆内存中。实例变量要创建对象后才能访问。 -- 成员变量有默认初始化值 -- 成员变量的权限修饰符可以指定 - -定义在方法内,代码块内的变量称为局部变量: - -- 局部变量存在于栈内存中 -- 局部变量作用范围结束,变量的内存空间回自动释放 -- 局部变量没有默认值,每次必须显示初始化 -- 局部变量声明时不指定权限修饰符 - -## 方法 - -描述的是对象的动作信息,方法也称为函数。 - -### 普通方法 - -普通通方法的语法结构: - -```java -[修饰符列表] 返回值类型 方法名(形参列表){ - //方法体 -} -``` - -定义方法可以将功能代码进行封装。便于该功能进行复用。方法只有被调用才会被执行。 - -### 构造方法 - -构造方法是一种比较特殊的方法,通过构造方法可以创建对象以及初始化实例变量。实例变量没有手动赋值的时候,系统会赋默认值。 - -```java -public class Person { - //属性 - private int age; - private String name; - - //构造方法 - public Person(int age, String name) { - this.age = age; - this.name = name; - } -} -``` - -## 方法重载 - -同个类中的多个方法可以有相同的方法名称,但是有不同的参数列表,这就称为方法重载。参数列表又叫参数签名,包括参数的类型、参数的个数、参数的顺序,只要有一个不同就叫做参数列表不同。 - -重载是面向对象的一个基本特性。 - -```java -public class OverrideTest { - void setPerson() { } - - void setPerson(String name) { - //set name - } - - void setPerson(String name, int age) { - //set name and age - } -} -``` - -## 方法重写 - -方法的重写描述的是父类和子类之间的。当父类的功能无法满足子类的需求,可以在子类对方法进行重写。 - -方法重写时, 方法名与形参列表必须一致。 - -如下代码,Person为父类,Student为子类,在Student中重写了dailyTask方法。 - -```java -public class Person { - private String name; - - public void dailyTask() { - System.out.println("work eat sleep"); - } -} - - -public class Student extends Person { - @Override - public void dailyTask() { - System.out.println("study eat sleep"); - } -} -``` - -## 初始化顺序 - -Java中类初始化顺序: - -1. 静态属性,静态代码块。 -2. 普通属性,普通代码块。 -3. 构造方法。 - -```java -public class LifeCycle { - // 静态属性 - private static String staticField = getStaticField(); - - // 静态代码块 - static { - System.out.println(staticField); - System.out.println("静态代码块初始化"); - } - - // 普通属性 - private String field = getField(); - - // 普通代码块 - { - System.out.println(field); - System.out.println("普通代码块初始化"); - } - - // 构造方法 - public LifeCycle() { - System.out.println("构造方法初始化"); - } - - // 静态方法 - public static String getStaticField() { - String statiFiled = "静态属性初始化"; - return statiFiled; - } - - // 普通方法 - public String getField() { - String filed = "普通属性初始化"; - return filed; - } - - public static void main(String[] argc) { - new LifeCycle(); - } - - /** - * 静态属性初始化 - * 静态代码块初始化 - * 普通属性初始化 - * 普通代码块初始化 - * 构造方法初始化 - */ -} -``` - -# 接口和抽象类 - -Java中接口和抽象类的定义语法分别为interface与abstract关键字。 - -## 抽象类 - -首先了解一下抽象方法。抽象方法是一种特殊的方法:它只有声明,而没有具体的实现。抽象方法的格式为: - -```java -abstract void method(); -``` - -抽象方法必须用abstract关键字进行修饰。如果一个类含有抽象方法,则称这个类为抽象类,抽象类必须在类前用abstract关键字修饰。因为抽象类中含有未实现的方法,所以不能用抽象类创建对象。 - -抽象类的特点: - -- 抽象类不能被实例化只能被继承; -- 包含抽象方法的一定是抽象类,但是抽象类不一定含有抽象方法; -- 抽象类中的抽象方法的修饰符只能为public或者protected,默认为public。 - -## 接口 - -接口对行为的抽象。在Java中,定一个接口的形式如下: - -```java -public interface InterfaceName { -} -``` - -接口中可以含有变量和方法。 - -**接口的特点:** - -- 接口中的变量会被隐式指定为public static final类型,而方法会被隐式地指定为public abstract类型; -- 接口中的方法必须是抽象方法,所有的方法不能有具体的实现; -- 一个类可以实现多个接口。 - -**接口的用处** - -接口是对行为的抽象,通过接口可以实现不相关类的相同行为。通过接口可以知道一个类具有哪些行为特性。 - -## 接口与抽象类区别 - -- 语法层面上 - 1)抽象类可以有方法实现,而接口的方法中只能是抽象方法; - 2)抽象类中的成员变量可以是各种类型的,接口中的成员变量只能是public static final类型; - 3)接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法; - 4)一个类只能继承一个抽象类,而一个类却可以实现多个接口。 - -- 设计层面上的区别 - 1)抽象层次不同。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口只是对类行为进行抽象。继承抽象类是一种"是不是"的关系,而接口实现则是 "有没有"的关系。如果一个类继承了某个抽象类,则子类必定是抽象类的种类,而接口实现则是具备不具备的关系,比如鸟是否能飞。 - 2) 继承抽象类的是具有相似特点的类,而实现接口的却可以不同的类。 - - 门和警报的例子: - - ```java - class AlarmDoor extends Door implements Alarm { - //code - } - - class BMWCar extends Car implements Alarm { - //code - } - ``` - -# 反射 - -Java反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性,并且能改变它的属性。 - -作用:可以更灵活的编写代码,代码可以在运行时装配,无需在组件之间进行源代码链接,降低代码的耦合度;还有动态代理的实现等。需要注意的是反射使用不当会造成很高的资源消耗! - -## Class类 - -在Java中,每定义一个Java class实体都会产生一个Class对象。这个Class对象用于表示这个类的类型信息。 - -获取Class对象的三种方式: - -```java -//1、通过对象调用 getClass() 方法来获取,通常应用在:比如你传过来一个 Object -// 类型的对象,而我不知道你具体是什么类,用这种方法 -  Person p1 = new Person(); -  Class c1 = p1.getClass(); - -//2、直接通过 类名.class 的方式得到,该方法最为安全可靠,程序性能更高 -// 这说明任何一个类都有一个隐含的静态成员变量 class -  Class c2 = Person.class; - -//3、通过 Class 对象的 forName() 静态方法来获取,用的最多, -// 但可能抛出 ClassNotFoundException 异常 -  Class c3 = Class.forName("com.ys.reflex.Person"); -``` - -Class 类提供了一些方法,可以获取成员变量、成员方法、接口、超类、构造方法: - -```java -  getName():获得类的完整名字。 -  getFields():获得类的public类型的属性。 -  getDeclaredFields():获得类的所有属性。包括private 声明的和继承类 -  getMethods():获得类的public类型的方法。 -  getDeclaredMethods():获得类的所有方法。包括private 声明的和继承类 -  getMethod(String name, Class[] parameterTypes):获得类的特定方法,name参数指定方法的名字,parameterTypes 参数指定方法的参数类型。 -  getConstructors():获得类的public类型的构造方法。 -  getConstructor(Class[] parameterTypes):获得类的特定构造方法,parameterTypes 参数指定构造方法的参数类型。 -  newInstance():通过类的不带参数的构造方法创建这个类的一个对象。 -``` - -## Field类 - -Field提供了类和接口中字段的信息,通过Field类可以动态访问这些字段。下图是Field类提供的一些方法。 - -![field-method](http://img.dabin-coder.cn/image/field-method.png) - -## Method类 - -Method类位于 java.lang.reflect 包中,主要用于在程序运行状态中,动态地获取方法信息。 - -Method类常用的方法: - -1. getAnnotation(Class annotationClass):如果该方法对象存在指定类型的注解,则返回该注解,否则返回null。 -2. getName():返回方法对象名称。 -3. isAnnotationPresent(Class annotationClass):如果该方法对象上有指定类型的注解,则返回true,否则为false。 -4. getDeclaringClass ():返回该方法对象表示的方法所在类的Class对象。 -5. getParameters():返回一个参数对象数组,该数组表示该方法对象的所有参数。 -6. getReturnType():返回一个Class对象,该Class对象表示该方法对象的返回对象,会擦除泛型。 - -# 泛型 - -泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。编译时会进行类型擦除。在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。 - -## 泛型类 - -泛型类型用于类的定义中,被称为泛型类。通过泛型可以完成对一组类的操作对外开放相同的接口。最典型的就是各种容器类,如:List、Set、Map。 - -泛型类示例: - -```java -//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型 -//在实例化泛型类时,必须指定T的具体类型 -public class Generic{ - //key这个成员变量的类型为T,T的类型由外部指定 - private T key; - - public Generic(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定 - this.key = key; - } - - public T getKey(){ //泛型方法getKey的返回值类型为T,T的类型由外部指定 - return key; - } -} -``` - -泛型类的使用: - -```java -@Slf4j -public class GenericTest { - public static void main(String[] args) { - //泛型的类型参数只能是类类型(包括自定义类),不能是简单类型 - //传入的实参类型需与泛型的类型参数类型相同,即为Integer. - Generic genericInteger = new Generic(666); - - //传入的实参类型需与泛型的类型参数类型相同,即为String. - Generic genericString = new Generic("程序员大彬"); - log.info("泛型测试: key is {}", genericInteger.getKey()); - log.info("泛型测试: key is {}", genericString.getKey()); - } - - /** - * output - * 23:51:55.519 [main] INFO com.tyson.generic.GenericTest - 泛型测试: key is 666 - * 23:51:55.526 [main] INFO com.tyson.generic.GenericTest - 泛型测试: key is 程序员大彬 - */ -} -``` - -## 泛型接口 - -泛型接口与泛型类的定义及使用基本相同。 - -```java -//定义一个泛型接口 -public interface Generator { - public T next(); -} -``` - -## 泛型方法 - -泛型类,是在实例化类的时候指明泛型的具体类型;泛型方法,是在调用方法的时候指明泛型的具体类型 。 - -```java -@Slf4j -public class GenericMethod { - - /** - * 泛型方法的基本介绍 - * @param t 传入的泛型实参 - * @return T 返回值为T类型 - * 说明: - * 1)public 与 返回值中间非常重要,可以理解为声明此方法为泛型方法。 - * 2)只有声明了的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。 - * 3)表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。 - * 4)与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。 - */ - public void genericMethod(T t) { - log.info(t.toString()); - } - - public static void main(String[] args) { - GenericMethod genericMethod = new GenericMethod(); - genericMethod.genericMethod("程序员大彬"); - genericMethod.genericMethod(666); - } - - /** - * output - * 23:59:11.906 [main] INFO com.tyson.generic.GenericMethod - 程序员大彬 - * 23:59:11.912 [main] INFO com.tyson.generic.GenericMethod - 666 - */ -} -``` - - - -# Exception - -在 JAVA 语言中,将程序执行中发生的不正常情况称为异常。异常是程序经常会出现的情况,发现错误的最佳时机是在编译阶段,也就是运行程序之前。 - -所有异常都继承了 Throwable。 - -## Throwable - -Throwable类是Error和Exception的父类,只有继承于Throwable的类或者其子类才能被抛出。Throwable分为两类: - -![exception](http://img.dabin-coder.cn/image/exception.png) - -- Error:JVM 无法解决的严重问题,如栈溢出(StackOverflowError)、内存溢出(OOM)等。程序无法处理的错误。 - - 栈溢出:如下代码递归调用main,最终会抛出StackOverflowError。 - - ```java - public class Test { - public static void main(String[] args) { - main(args); - } - /** - * Exception in thread "main" java.lang.StackOverflowError - */ - } - ``` - - 内存溢出OOM:下面的代码中,新建了一个数组,数组长度是 1G,又因为是 Integer 包装类型,一个元素占 4 个字节,所以,这个数组占用内存 4GB。最后,堆内存空间不足,抛出了 OOM。 - - ```java - public class Test { - public static void main(String[] args) { - Integer[] arr = new Integer[1024 * 1024 * 1024]; - } - /** - * Exception in thread "main" java.lang.OutOfMemoryError: Java heap space - */ - } - ``` - -- Exception:其它因编程错误或偶然的外在因素导致的一般性问题。可以在代码中进行处理。如:空指针异常、数组下标越界等。 - -unchecked exception包括RuntimeException和Error类,其他所有异常称为检查(checked)异常。 - -运行时异常和非运行时异常(checked)的区别: - -1. RuntimeException由程序错误导致,应该修正程序避免这类异常发生。 -2. checked Exception由具体的环境(读取的文件不存在或文件为空或sql异常)导致的异常。必须进行处理,不然编译不通过,可以catch或者throws。 - -## 常见的Exception - -常见的RuntimeException: - -```java -ClassCastException //类型转换异常 -IndexOutOfBoundsException //数组越界异常 -NullPointerException //空指针 -ArrayStoreException //数组存储异常 -NumberFormatException //数字格式化异常 -ArithmeticException //数学运算异常 -``` - -unchecked Exception: - -``` -NoSuchFieldException //反射异常,没有对应的字段 -ClassNotFoundException //类没有找到异常 -IllegalAccessException //安全权限异常,可能是反射时调用了private方法 -``` - -## 关键字 - -- **throw**:用于抛出一个具体的异常对象。 - -- **throws**:用在方法签名中,用于声明该方法可能抛出的异常。子类方法抛出的异常范围更加小,或者根本不抛异常。 - -- **try**:用于监听。将可能抛出异常的代码放在try语句块之中,当try语句块内发生异常时,异常就被抛出。 -- **catch**:用于捕获异常。 -- **finally**:finally语句块总是会被执行。它主要用于回收在try块里打开的资源(如数据库连接、网络连接和磁盘文件)。 - -如下代码示例,除以0抛出异常,发生异常之后的代码不会执行,直接跳到catch语句块执行,最后执行finally语句块。 - -```java -public class ExceptionTest { - public static void main(String[] args) { - try { - int i = 1 / 0; - System.out.println(i + 1); - } catch (Exception e) { - System.out.println(e.getMessage()); - } finally { - System.out.println("run finally..."); - } - } - /** - * / by zero - * run finally... - */ -} -``` - - - -# IO流 - -Java IO流的核心就是对文件的操作,对于字节 、字符类型的输入和输出流。IO流主要分为两大类,字节流和字符流。字节流可以处理任何类型的数据,如图片,视频等,字符流只能处理字符类型的数据。 - -![io](http://img.dabin-coder.cn/image/io.jpg) - -> 图片参考:[Java io学习整理](https://zhuanlan.zhihu.com/p/25418336) - -## InputStream 和 OutputStream - -InputStream 用来表示那些从不同数据源产生输入的类。这些数据源包括:1.字节数组;2.String 对象;3.文件;4.管道;5.一个由其他种类的流组成的序列。 - -InputStream 类有一个抽象方法:`abstract int read()`,这个方法将读入并返回一个字节,或者在遇到输入源结尾时返回-1。 - -OutputStream 决定了输出所要去的目标:字节数组、文件或管道。OutputStream 的 `abstract void write(int b)` 可以向某个输出位置写出一个字节。 - -read() 和 write() 方法在执行时都将阻塞,等待数据被读入或者写出。 - -常用的字节流有FileInputStream、FileOutputStream、ObjectInputStream、ObjectOutputStream。 - -## Reader 和 Writer - -字符流是由通过字节流转换得到的,转化过程耗时,而且容易出现乱码问题。I/O 流提供了一个直接操作字符的接口,方便我们平时对字符进行流操作。如果音频文件、图片等媒体文件用字节流比较好,如果涉及到字符的话使用字符流比较好。 - -```java -abstract int read(); -abstract void write(char c); -``` - -## 字符流和字节流的转换 - -InputStreamReader:字节到字符的转换,可对读取到的字节数据经过指定编码转换成字符。 - -OutputStreamWriter:字符到字节的转换,可对读取到的字符数据经过指定编码转换成字节。 - -## 同步异步 - -同步:发出一个调用时,在没有得到结果之前,该调用就不返回。 - -异步:在调用发出后,被调用者返回结果之后会通知调用者,或通过回调函数处理这个调用。 - -## 阻塞非阻塞 - -阻塞和非阻塞关注的是线程的状态。 - -阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会恢复运行。 - -非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。 - -> 举个例子,理解下同步、阻塞、异步、非阻塞的区别: -> -> 同步就是烧开水,要自己来看开没开;异步就是水开了,然后水壶响了通知你水开了(回调通知)。阻塞是烧开水的过程中,你不能干其他事情,必须在旁边等着;非阻塞是烧开水的过程里可以干其他事情。 - -## BIO - -同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。 - -![bio](http://img.dabin-coder.cn/image/bio.png) - -## NIO - -NIO是一种同步非阻塞的I/O模型,在Java 1.4 中引入了 NIO 框架,对应 java.nio 包,提供了 Channel , Selector,Buffer等抽象。 - -![nio](http://img.dabin-coder.cn/image/nio.png) - -NIO与IO区别: - -- IO是面向流的,NIO是面向缓冲区的; -- IO流是阻塞的,NIO流是不阻塞的; -- NIO有选择器,而IO没有。 - -Buffer:Buffer用于和Channel交互。从Channel中读取数据到Buffer里,从Buffer把数据写入到Channel。 - -Channel:NIO 通过Channel(通道) 进行读写。通道是双向的,可读也可写,而流的读写是单向的。无论读写,通道只能和Buffer交互。 - -Selector:使用更少的线程来就可以来处理通道了,相比使用多个线程,避免了线程上下文切换带来的开销。 - -## AIO - -异步非阻塞 IO。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。 - -## BIO/NIO/AIO区别 - -同步阻塞IO : 用户进程发起一个IO操作以后,必须等待IO操作的真正完成后,才能继续运行。 - -同步非阻塞IO: 客户端与服务器通过Channel连接,采用多路复用器轮询注册的Channel。提高吞吐量和可靠性。用户进程发起一个IO操作以后,可做其它事情,但用户进程需要轮询IO操作是否完成,这样造成不必要的CPU资源浪费。 - -异步非阻塞IO: 非阻塞异步通信模式,NIO的升级版,采用异步通道实现异步通信,其read和write方法均是异步方法。用户进程发起一个IO操作,然后立即返回,等IO操作真正的完成以后,应用程序会得到IO操作完成的通知。类比Future模式。 - - - -# ThreadLocal -线程本地变量。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程。 - -## 原理 - -每个线程都有一个ThreadLocalMap(ThreadLocal内部类),Map中元素的键为ThreadLocal,而值对应线程的变量副本。 - -![image-20200715234257808](../img/threadlocal.png) - -调用threadLocal.set()-->调用getMap(Thread)-->返回当前线程的ThreadLocalMap-->map.set(this, value),this是ThreadLocal - -```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; -} - -void createMap(Thread t, T firstValue) { - t.threadLocals = new ThreadLocalMap(this, firstValue); -} -``` -调用get()-->调用getMap(Thread)-->返回当前线程的ThreadLocalMap-->map.getEntry(this),返回value - -```java - public T get() { - Thread t = Thread.currentThread(); - ThreadLocalMap map = getMap(t); - if (map != null) { - ThreadLocalMap.Entry e = map.getEntry(this); - if (e != null) { - @SuppressWarnings("unchecked") - T result = (T)e.value; - return result; - } - } - return setInitialValue(); - } -``` - -threadLocals的类型ThreadLocalMap的键为ThreadLocal对象,因为每个线程中可有多个threadLocal变量,如longLocal和stringLocal。 - -``` -public class ThreadLocalDemo { - ThreadLocal longLocal = new ThreadLocal<>(); - - public void set() { - longLocal.set(Thread.currentThread().getId()); - } - public Long get() { - return longLocal.get(); - } - - public static void main(String[] args) throws InterruptedException { - ThreadLocalDemo threadLocalDemo = new ThreadLocalDemo(); - threadLocalDemo.set(); - System.out.println(threadLocalDemo.get()); - - Thread thread = new Thread(() -> { - threadLocalDemo.set(); - System.out.println(threadLocalDemo.get()); - } - ); - - thread.start(); - thread.join(); - - System.out.println(threadLocalDemo.get()); - } -} -``` -ThreadLocal 并不是用来解决共享资源的多线程访问的问题,因为每个线程中的资源只是副本,并不共享。因此ThreadLocal适合作为线程上下文变量,简化线程内传参。 - -## 内存泄漏 - -每个Thread都有⼀个ThreadLocalMap的内部属性,map的key是ThreaLocal,定义为弱引用,value是强引用类型。GC的时候会⾃动回收key,而value的回收取决于Thread对象的生命周期。一般会通过线程池的方式复用Thread对象节省资源,这也就导致了Thread对象的生命周期比较长,这样便一直存在一条强引用链的关系:Thread --> ThreadLocalMap-->Entry-->Value,随着任务的执行,value就有可能越来越多且无法释放,最终导致内存泄漏。 - -![image-20200715235804982](../img/threadlocal-oom.png) - -解决⽅法:每次使⽤完ThreadLocal就调⽤它的remove()⽅法,手动将对应的键值对删除,从⽽避免内存泄漏。 - -```java -currentTime.set(System.currentTimeMillis()); -result = joinPoint.proceed(); -Log log = new Log("INFO",System.currentTimeMillis() - currentTime.get()); -currentTime.remove(); -``` - -## 使用场景 - -ThreadLocal 适用场景:每个线程需要有自己单独的实例,且需要在多个方法中共享实例,即同时满足实例在线程间的隔离与方法间的共享。比如Java web应用中,每个线程有自己单独的 Session 实例,就可以使用ThreadLocal来实现。 - - - -# 线程安全类 - -线程安全:代码段在多线程下执行和在单线程下执行能获得一样的结果。 -线程安全类:线程安全的类其方法是同步的,每次只能有一个线程访问,效率较低。 -- vector:比arraylist多了个同步化机制,效率较低 -- stack:堆栈类,继承自vector -- hashtable:hashtable不允许插入空值,hashmap允许 -- enumeration:枚举,相当于迭代器 -- StringBuffer - -Iterator和Enumeration的重要区别: -- Enumeration为vector/hashtable等类提供遍历接口,Iterator为ArrayList/HashMap提供遍历接口。 -- Enumeration只能读集合中的数据,不能删除。 -- Enumeration是先进后出,而Iterator是先进先出。 -- Enumeration不支持fast-fail机制,不会抛ConcurrentModificationException。 - - - -# 常见操作 - -## 排序 - -数组 - -```java -Arrays.sort(jdArray, (int[] jd1, int[] jd2) -> {return jd1[0] - jd2[0];}); -``` - - - -## 数组操作 - -数组遍历 - -```java -Arrays.asList(array).stream().forEach(System.out::println); -``` - -数组排序 - -```java -Arrays.sort(players,(String s1,String s2)->(s1.compareTo(s2))); -``` - -集合转数组: - -```java -//List --> Array -List list = new ArrayList<>(); -list.add(1); -Integer[] arr = list.toArray(new Integer[list.size()]); -``` - -数组转集合: - -```java -//Array --> List -String[] array = {"java", "c"}; -List list = Arrays.asList(array); -``` - -该方法存在一定的弊端,返回的list是Arrays里面的一个静态内部类(数组的视图),对list的操作会反映在原数组上,而且list是定长的,不支持add、remove操作。 - -该ArrayList并非java.util.ArrayList,而是 java.util.Arrays.ArrayList.ArrayList(T[]),该类并未实现add、remove方法,因此在使用时存在局限性。 - -代替方案: - -```java -List list = new ArrayList(Arrays.asList(array)); -``` - - - -## 拷贝 - -### 数组拷贝 - -```java -System.arraycopy(Object src, int srcPos, Object dest, int desPos, int length) -Arrays.copyOf(originalArr, length) //length为拷贝的长度 -Arrays.copyOfRange(originalArr, from, to); //from包含,to不包含 -``` - -二维数组拷贝: - -```java -int[][] arr = {{1, 2},{3, 4}}; -int[][] newArr = new int[2][2]; -for(int i = 0; i < arr.length; i++) { - newArr[i] = arr[i].clone(); -} -``` - -### 对象拷贝 - -实现对象克隆有两种方式: - -1. 实现Cloneable接口并重写Object类中的clone()方法; - -2. 实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆。 - -实现cloneable接口,重写clone方法。 - -```java -public class Dog implements Cloneable { - private String id; - private String name; - - public Dog(String id, String name) { - this.id = id; - this.name = name; - } - - // 省略 getter 、 setter 以及 toString 方法 - - @Override - public Dog clone() throws CloneNotSupportedException { - Dog dog = (Dog) super.clone(); - - return dog; - } -} -``` - -使用: - -```java -Dog dog1 = new Dog("1", "Dog1"); -Dog dog2 = dog1.clone(); - -dog2.setName("Dog1 changed"); - -System.out.println(dog1); // Dog{id='1', name='Dog1'} -System.out.println(dog2); // Dog{id='1', name='Dog1 changed'} -``` - -如果一个类引用了其他类,引用的类也需要实现cloneable接口,比较麻烦。可以将所有的类都实现Serializable接口,通过序列化反序列化实现对象的深度拷贝。 - -## 序列化 - -序列化:把内存中的对象转换为字节序列的过程称为对象的序列化。 - -### 什么情况下需要序列化? - -当你想把的内存中的对象状态保存到一个文件中或者数据库中时候; -当你想在网络上传送对象的时候; - -### 如何实现序列化 - -实现Serializable接口即可。序列化的时候(如objectOutputStream.writeObject(user)),会判断user是否实现了Serializable(obj instanceof Serializable),如果对象没有实现Serializable接口,在序列化的时候会抛出NotSerializableException异常。 - -### serialVersionUID - -serialVersionUID 是 Java 为每个序列化类产生的版本标识,可用来保证在反序列时,发送方发送的和接受方接收的是可兼容的对象。类的serialVersionUID的默认值完全依赖于Java编译器的实现。当完成序列化之后,此时对对象进行修改,由编译器生成的serialVersionUID会改变,这样反序列化的时候会报错。可以在序列化对象中添加 serialVersionUID,固定版本号,这样即便序列化对象做了修改,版本都是一致的,就能进行反序列化了。 - -## 遍历 - -### fast-fail - -fast-fail是Java集合的一种错误机制。当多个线程对同一个集合进行操作时,就有可能会产生fast-fail事件。 -例如:当线程a正通过iterator遍历集合时,另一个线程b修改了集合的内容,此时modCount(记录集合操作过程的修改次数)会加1,不等于expectedModCount,那么线程a访问集合的时候,就会抛出ConcurrentModificationException,产生fast-fail事件。边遍历边修改集合也会产生fast-fail事件。 - -解决方法: - -- 使用Colletions.synchronizedList方法或在修改集合内容的地方加上synchronized。这样的话,增删集合内容的同步锁会阻塞遍历操作,影响性能。 -- 使用CopyOnWriteArrayList来替换ArrayList。在对CopyOnWriteArrayList进行修改操作的时候,会拷贝一个新的数组,对新的数组进行操作,操作完成后再把引用移到新的数组。 - -### fail-safe - -fail-safe允许在遍历的过程中对容器中的数据进行修改。因为采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先copy原有集合内容,在拷贝的集合上进行遍历。 - -由于迭代时是对原集合的拷贝的值进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发`ConcurrentModificationException`。 - -java.util.concurrent包下的容器都是安全失败的,可以在多线程下并发使用。常见的的使用fail-safe方式遍历的容器有`ConcerrentHashMap`和`CopyOnWriteArrayList`等。 - -fail-safe机制有两个问题:(1)需要复制集合,产生大量的无效对象,内存开销大;(2)不能访问到修改后的内容 。 - -### 移除集合元素 - -遍历时安全的移除集合中的元素,要使用遍历器Iterator和iterator.remove()方法。next()必须在remove()之前调用。 - -``` -ArrayList list = new ArrayList(Arrays.asList("a","b","c","d")); -Iterator iter = list.iterator(); -while(iter.hasNext()){ - String s = iter.next(); - if(s.equals("a")){ - iter.remove(); - } -} -``` - - - diff --git "a/Java/Java\345\271\266\345\217\221\347\274\226\347\250\213.md" "b/Java/Java\345\271\266\345\217\221\347\274\226\347\250\213.md" deleted file mode 100644 index 5381c9e..0000000 --- "a/Java/Java\345\271\266\345\217\221\347\274\226\347\250\213.md" +++ /dev/null @@ -1,392 +0,0 @@ - - - - -- [共享对象](#%E5%85%B1%E4%BA%AB%E5%AF%B9%E8%B1%A1) - - [非原子的64位操作](#%E9%9D%9E%E5%8E%9F%E5%AD%90%E7%9A%8464%E4%BD%8D%E6%93%8D%E4%BD%9C) - - [this 引用逸出](#this-%E5%BC%95%E7%94%A8%E9%80%B8%E5%87%BA) - - [安全的对象构造过程](#%E5%AE%89%E5%85%A8%E7%9A%84%E5%AF%B9%E8%B1%A1%E6%9E%84%E9%80%A0%E8%BF%87%E7%A8%8B) - - [ThreadLocal](#threadlocal) -- [容器](#%E5%AE%B9%E5%99%A8) - - [间接的迭代操作](#%E9%97%B4%E6%8E%A5%E7%9A%84%E8%BF%AD%E4%BB%A3%E6%93%8D%E4%BD%9C) - - [ConcurrentHashMap](#concurrenthashmap) - - [同步工具类](#%E5%90%8C%E6%AD%A5%E5%B7%A5%E5%85%B7%E7%B1%BB) - - [信号量](#%E4%BF%A1%E5%8F%B7%E9%87%8F) - - [缓存系统](#%E7%BC%93%E5%AD%98%E7%B3%BB%E7%BB%9F) -- [任务执行](#%E4%BB%BB%E5%8A%A1%E6%89%A7%E8%A1%8C) - - [Executor 框架](#executor-%E6%A1%86%E6%9E%B6) - - [延迟任务](#%E5%BB%B6%E8%BF%9F%E4%BB%BB%E5%8A%A1) - - [携带结果的 Callable 和 Future](#%E6%90%BA%E5%B8%A6%E7%BB%93%E6%9E%9C%E7%9A%84-callable-%E5%92%8C-future) - - [为任务设置时限](#%E4%B8%BA%E4%BB%BB%E5%8A%A1%E8%AE%BE%E7%BD%AE%E6%97%B6%E9%99%90) - - [取消与关闭](#%E5%8F%96%E6%B6%88%E4%B8%8E%E5%85%B3%E9%97%AD) - - [任务取消](#%E4%BB%BB%E5%8A%A1%E5%8F%96%E6%B6%88) - - [阻塞和中断](#%E9%98%BB%E5%A1%9E%E5%92%8C%E4%B8%AD%E6%96%AD) - - - -## 共享对象 - -### 非原子的64位操作 - -在多线程程序使用共享且可变的64位数据类型的变量是不安全的。 - - - -### this 引用逸出 - -参考自:[this 引用逸出](https://www.cnblogs.com/whatisjava/archive/2013/05/29/3106336.html) - -实例化 ThisEscape 对象时,会调用 source 的 registerListener 方法,这时便启动了一个线程,而且这个线程持有了 ThisEscape 对象(调用了对象的 doSomething 方法),但此时 ThisEscape 对象却没有实例化完成(还没有返回一个引用),所以我们说,此时造成了一个 this 引用逸出,即还没有完成的实例化 ThisEscape 对象的动作,却已经暴露了对象的引用。其他线程访问还没有构造好的对象,可能会造成意料不到的问题。 - -```java -public class ThisEscape { -  public ThisEscape(EventSource source) { -    source.registerListener(new EventListener() { -      public void onEvent(Event e) { -        doSomething(e); -      } -    }); -  } - -  void doSomething(Event e) { -  } - -  interface EventSource { -    void registerListener(EventListener e); -  } - -  interface EventListener { -    void onEvent(Event e); -  } - -  interface Event { -  } -} -``` - -### 安全的对象构造过程 - -使用工厂方法来防止 this 引用在构造过程中逸出。 - -```java -public class SafeListener { -  private final EventListener listener; - -  private SafeListener() { -    listener = new EventListener() { -      public void onEvent(Event e) { -        doSomething(e); -      } -    }; -  } - -  public static SafeListener newInstance(EventSource source) { -    SafeListener safe = new SafeListener(); -    source.registerListener(safe.listener); -    return safe; -  } - -  void doSomething(Event e) { -  } - -  interface EventSource { -    void registerListener(EventListener e); -  } - -  interface EventListener { -    void onEvent(Event e); -  } - -  interface Event { -  } - } -``` - -构造好了 SafeListener 对象(通过构造器构造)之后,才启动了监听线程,也就确保了构造完成之后再使用SafeListener对象。 - -### ThreadLocal - -当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。 -每个线程都有一个ThreadLocalMap(ThreadLocal内部类),Map中元素的键为ThreadLocal,而值对应线程的变量副本。 -调用set()-->调用getMap(Thread)-->返回当前线程的ThreadLocalMap-->map.set(this, value),this是ThreadLocal。 -调用get()-->调用getMap(Thread)-->返回当前线程的ThreadLocalMap-->map.getEntry(this),返回value。 - -```java -public class ThreadLocalDemo { - ThreadLocal longLocal = new ThreadLocal<>(); - - public void set() { - longLocal.set(Thread.currentThread().getId()); - } - - public String get() { - return Thread.currentThread().getName() + ": " + longLocal.get(); - } - - public static void main(String[] args) throws InterruptedException { - ThreadLocalDemo threadLocalDemo = new ThreadLocalDemo(); - threadLocalDemo.set(); - System.out.println(threadLocalDemo.get()); - - Thread thread = new Thread(() -> { - threadLocalDemo.set(); - System.out.println(threadLocalDemo.get()); - } - ); - - thread.start(); - thread.join(); - - System.out.println(threadLocalDemo.get()); - } -} -/*output -main: 1 -Thread-0: 11 -main: 1 - */ -``` - -## 容器 - -### 间接的迭代操作 - -调用容器的 toString 方法会迭代容器。容器的 hashcode 和 equals 方法也会间接的进行迭代操作。 - -```java -public class HiddenIterator { - private final Set set = new HashSet<>(); - //... - public void print() { - System.out.println("set: " + set); - } -} -``` - -### ConcurrentHashMap -多线程环境下,使用Hashmap进行put操作会引起死循环。 -CocurrentHashMap利用锁分段技术增加了锁的数目,从而使争夺同一把锁的线程的数目得到控制。 -锁分段技术就是将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。 -ConcurrentHashMap调用get的时候不加锁,原因是node数组成员val和指针next是用volatile修饰的,更改后的值会立刻刷新到主存中,保证了可见性,node数组table也用volatile修饰,保证在运行过程对其他线程具有可见性。 - -JDK1.7中的ConcurrentHashmap主要使用Segment来实现减小锁粒度,把HashMap分割成若干个Segment,在put的时候需要锁住Segment,get时候不加锁,使用volatile来保证可见性,当要统计size时,比较统计前后modCount是否发生变化。如果没有变化,则直接返回size。否则,需要依次锁住所有的Segment来计算。jdk1.7中ConcurrentHashmap中,当长度过长碰撞会很频繁,链表的增改删查操作都会消耗很长的时间,影响性能。 - -jdk1.8不采用segment而采用Node,锁住Node来实现减小锁粒度。当链表长度过长时,Node会转换成TreeNode。 - -put 操作会对当前的table进行无条件自循环直到put成功,可以分成以下流程来概述: -1)如果没有初始化就先调用 initTable 方法来进行初始化过程 -2)如果没有hash冲突就直接CAS插入 -3)如果还在进行扩容操作就先进行扩容 -4)如果存在hash冲突,就加锁来保证线程安全,这里有两种情况,一种是链表形式就直接遍历到尾端插入,一种是红黑树就按照红黑树结构插入 - -5)如果该链表的数量大于阈值8,就要先转换成黑红树的结构 -6)如果添加成功就调用 addCount 方法统计size,并且检查是否需要扩容 - -### 同步工具类 - -#### 信号量 - -信号量 Semaphore 用来控制同时访问某个特定资源的操作数量,或者同时执行某个指定操作的数量。 - -通过信号量实现有界的HashSet: - -```java -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; -import java.util.concurrent.Semaphore; - -public class BoundedHashSet { - private final Set set; - private final Semaphore sem; - - public BoundedHashSet(int bound) { - set = Collections.synchronizedSet(new HashSet<>()); - sem = new Semaphore(bound); - } - - public boolean add(T t) throws InterruptedException { - sem.acquire(); - boolean wasAdded = false; - try { - wasAdded = set.add(t); - return wasAdded; - } finally { - if (!wasAdded) { - sem.release(); - } - } - } - - public boolean remove(Object o) { - boolean wasRemoved = set.remove(o); - if (wasRemoved) { - sem.release(); - } - return wasRemoved; - } -} -``` - -### 缓存系统 - -假设有一个高计算开销的 compute 函数,我们可以将计算结果保存在 Map 中,调用 compute 时先检查 Map 是否存在需要的结果。使用 ConturrentHashMap 可以提高系统的并发能力。假如两个线程同时调用 compute ,则相同的数据会被计算多次,使用 FutureTask 可以避免这个问题。 - -```java -interface Computable { - V compute(A arg) throws InterruptedException; -} - -public class Memorizer implements Computable { - private final ConcurrentMap> cache = new ConcurrentHashMap<>(); - private final Computable c; - public Memorizer(Computable c) { - this.c = c; - } - @Override - public V compute(A arg) throws InterruptedException { - while (true) { - Future f = cache.get(arg); - if (f == null) { - Callable eval = new Callable() { - @Override - public V call() throws InterruptedException { - return c.compute(arg); - } - }; - FutureTask ft = new FutureTask<>(eval); - //先放进缓存,再进行计算;若线程x正在计算某个值,而线程y刚好正在查找这个值,则线程y会等待x的计算结果 - f = cache.putIfAbsent(arg, ft); - //避免两个线程同一时间调用compute计算相同的值 - if (f == null) { - f = ft; - ft.run(); - } - } - try { - return f.get(); - } catch (CancellationException ex) { - cache.remove(arg, f); - } catch (ExecutionException ex) { - ex.printStackTrace(); - } - } - } -} -``` - -## 任务执行 - -### Executor 框架 - -1.5后引入的 Executor 框架的最大优点是把任务的提交和执行解耦。Executor 是任务执行的抽象,,使用 Runnable 或 Callable 来表示任务。 - -```java -public class TaskExecutionWebServer { - private static final int NTHREADS = 100; - private static final Executor exec = Executors.newFixedThreadPool(NTHREADS); - - public static void main(String[] args) throws IOException { - ServerSocket socket = new ServerSocket(80); - while (true) { - final Socket connection = socket.accept(); - Runnable task = new Runnable() { - @Override - public void run() { - //handleRequest(connection); - } - }; - //将任务提交到工作队列 - exec.execute(task); - } - } -} -``` - - - -#### 延迟任务 - -Timer 类负责延迟任务和周期任务,然后 Timer 存在一些缺陷。现在一般使用 ScheduledThreadPoolExecutor 来代替它,通过 ScheduledThreadPoolExecutor 的构造函数或者 Executors.newScheduledThreadPool 工厂方法来创建该类的对象(不推荐,见阿里编码规范)。 - -#### 携带结果的 Callable 和 Future - -当提交一个Callable对象给ExecutorService,将得到一个Future对象,调用Future对象的get方法等待执行结果就好了。而 get 方法的行为取决于任务的状态(尚未开始、正在运行、已完成)。任务已完成,那么 get 会立即返回或者抛出异常;任务没有完成,get 将一直阻塞直到任务完成;任务抛出异常,那么 get 会将异常封装成 ExecutionException 重新抛出;任务被取消,那么 get 将抛出 CancellationExeception,此时通过 getCause 可以获取被封装的初始异常。 - - -#### 为任务设置时限 - -Future.get() 支持时间限制,当结果可用时,它将直接返回,如果在指定时间内没有计算出结果,那么将抛出 TimeoutException。 - -```java -Page renderPageWithAd() throws InterruptedException { - long endNanos = System.nanoTime() + TIME_BUDGET; - Future f = exec.submit(new FetchAdTask()); - //等待广告同时显示页面 - Page page = renderPageBody(); - Ad ad; - try { - //只等待指定的时间 - long timeLeft = endNanos - System.nanoTime(); - ad = f.get(timeLeft, NANOSECONDES); - } catch (ExecutionException e) { - ad = DEFAULT_AD; - } catch (TimeoutException e) { - ad = DEFAULT_AD; - //超时则取消任务 - f.cancel(true); - } - page.setAd(ad); - return page; -} -``` - -### 取消与关闭 - -Java 没有提供任何机制来安全的终止线程,但它提供了中断机制,这是一种协作机制,能够使一个线程终止另一个线程的当前工作。 - - -#### 任务取消 - -给任务设置某个“请求取消”的标志,而任务将定期查看该标志,如果设置了该标志,那么任务将提前结束。 - -#### 阻塞和中断 - -如果任务中调用了一个阻塞方法,那么任务有可能永远不会检查取消标志,因此永远不会结束。通过中断机制可以避免这个问题。一些特殊的阻塞库的方法支持中断。 - -线程可能受到阻塞的原因:等待 IO 操作,等待获得锁,等待从 Thread.sleep 方法醒来,或是等待另一个线程的计算结果。阻塞方法可能会抛出 InterruptedException。当在代码中调用了一个会抛出 InterruptedException 的方法时,这个方法也就变成了阻塞方法,需要处理中断异常。阻塞方法必须等待某个不受它控制的事件发生后才能继续执行。 - -每个线程都有一个 boolean 类型的中断状态,当中断线程时,这个线程的中断状态会被设置为 true。在 Thread 中包含了中断线程以及查询线程中断状态的方法。 - -```java -public class Thread { - public void interrupt() {} //中断目标线程 - public boolean isInterrupted() {} //返回目标线程的中断状态 - public static boolean interrupted() {}//清除当前线程中断状态的唯一方法,并返回它之前的值 -} -``` - -阻塞方法如 Thread.sleep 和 Object.wait 等,都会检查线程何时中断,并且在发生中断时提前返回。它们在响应中断时执行的操作包含:清除中断状态,抛出 InterruptedException,表示阻塞操作由于中断而提前结束。**调用interrupt并不意味着立即停止目标线程正在进行的工作,而只是传递了请求中断的消息**。对中断操作的正确理解是:它并不会真正的中断一个正在运行的线程,而只是发出中断请求,然后由线程在下一个合适的时刻自己中断。 - -阻塞方法必须处理对中断的响应,有两种处理方法: - -1. 传递 InterruptedException 给方法的调用者。 -2. 恢复中断。有时不能抛出 InterruptedException,如代码是 Runnable 一部分时,必须捕获 InterruptedException,并通过调用当前线程上的 interrupt 方法恢复中断状态,这样在高层代码就看到引发了一个中断。 - -```java -public class TaskRunnable implements Runnable { - BlockingQueue queue; - ... - public void run() { - try { - processTask(queue.take()); - } catch (InterruptedException e) { - //恢复被中断的状态,不然会丢失线程被中断的证据 - Thread.currentThread().interrupt(); - } - } -} -``` - - - diff --git "a/Java/Object\347\261\273\345\270\270\347\224\250\346\226\271\346\263\225.md" "b/Java/Object\347\261\273\345\270\270\347\224\250\346\226\271\346\263\225.md" deleted file mode 100644 index e78bb55..0000000 --- "a/Java/Object\347\261\273\345\270\270\347\224\250\346\226\271\346\263\225.md" +++ /dev/null @@ -1,241 +0,0 @@ - - -**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* - -- [Object常用方法](#object%E5%B8%B8%E7%94%A8%E6%96%B9%E6%B3%95) - - [toString](#tostring) - - [equals](#equals) - - [hashCode](#hashcode) - - [clone](#clone) - - [浅拷贝](#%E6%B5%85%E6%8B%B7%E8%B4%9D) - - [深拷贝](#%E6%B7%B1%E6%8B%B7%E8%B4%9D) - - - -## Object常用方法 - -Java面试经常会出现的一道题目,Object的常用方法。下面给大家整理一下。 - -object常用方法有:toString()、equals()、hashCode()、clone()等。 - -### toString - -默认输出对象地址。 - -```java -public class Person { - private int age; - private String name; - - public Person(int age, String name) { - this.age = age; - this.name = name; - } - - public static void main(String[] args) { - System.out.println(new Person(18, "程序员大彬").toString()); - } - //output - //me.tyson.java.core.Person@4554617c -} -``` - -可以重写toString方法,按照重写逻辑输出对象值。 - -```java -public class Person { - private int age; - private String name; - - public Person(int age, String name) { - this.age = age; - this.name = name; - } - - @Override - public String toString() { - return name + ":" + age; - } - - public static void main(String[] args) { - System.out.println(new Person(18, "程序员大彬").toString()); - } - //output - //程序员大彬:18 -} -``` - -### equals - -默认比较两个引用变量是否指向同一个对象(内存地址)。 - -```java -public class Person { - private int age; - private String name; - - public Person(int age, String name) { - this.age = age; - this.name = name; - } - - public static void main(String[] args) { - String name = "程序员大彬"; - Person p1 = new Person(18, name); - Person p2 = new Person(18, name); - - System.out.println(p1.equals(p2)); - } - //output - //false -} -``` - -可以重写equals方法,按照age和name是否相等来判断: - -```java -public class Person { - private int age; - private String name; - - public Person(int age, String name) { - this.age = age; - this.name = name; - } - - @Override - public boolean equals(Object o) { - if (o instanceof Person) { - Person p = (Person) o; - return age == p.age && name.equals(p.name); - } - return false; - } - - public static void main(String[] args) { - String name = "程序员大彬"; - Person p1 = new Person(18, name); - Person p2 = new Person(18, name); - - System.out.println(p1.equals(p2)); - } - //output - //true -} -``` - -### hashCode - -将与对象相关的信息映射成一个哈希值,默认的实现hashCode值是根据内存地址换算出来。 - -```java -public class Cat { - public static void main(String[] args) { - System.out.println(new Cat().hashCode()); - } - //out - //1349277854 -} -``` - -### clone - -java赋值是复制对象引用,如果我们想要得到一个对象的副本,使用赋值操作是无法达到目的的。Object对象有个clone()方法,实现了对 - -象中各个属性的复制,但它的可见范围是protected的。 - -```java -protected native Object clone() throws CloneNotSupportedException; -``` - -所以实体类使用克隆的前提是: - -- 实现Cloneable接口,这是一个标记接口,自身没有方法,这应该是一种约定。调用clone方法时,会判断有没有实现Cloneable接口,没有实现Cloneable的话会抛异常CloneNotSupportedException。 -- 覆盖clone()方法,可见性提升为public。 - -```java -public class Cat implements Cloneable { - private String name; - - @Override - protected Object clone() throws CloneNotSupportedException { - return super.clone(); - } - - public static void main(String[] args) throws CloneNotSupportedException { - Cat c = new Cat(); - c.name = "程序员大彬"; - Cat cloneCat = (Cat) c.clone(); - c.name = "大彬"; - System.out.println(cloneCat.name); - } - //output - //程序员大彬 -} -``` - -下面介绍下浅拷贝和深拷贝。 - -#### 浅拷贝 - -拷⻉对象和原始对象的引⽤类型引用同⼀个对象。 - -以下例子,Cat对象里面有个Person对象,调用clone之后,克隆对象和原对象的Person引用的是同一个对象,这就是浅拷贝。 - -```java -public class Cat implements Cloneable { - private String name; - private Person owner; - - @Override - protected Object clone() throws CloneNotSupportedException { - return super.clone(); - } - - public static void main(String[] args) throws CloneNotSupportedException { - Cat c = new Cat(); - Person p = new Person(18, "程序员大彬"); - c.owner = p; - - Cat cloneCat = (Cat) c.clone(); - p.setName("大彬"); - System.out.println(cloneCat.owner.getName()); - } - //output - //大彬 -} -``` - -#### 深拷贝 - -拷贝对象和原始对象的引用类型引用不同的对象。 - -以下例子,在clone函数中不仅调用了super.clone,而且调用Person对象的clone方法(Person也要实现Cloneable接口并重写clone方法),从而实现了深拷贝。可以看到,拷贝对象的值不会受到原对象的影响。 - -```java -public class Cat implements Cloneable { - private String name; - private Person owner; - - @Override - protected Object clone() throws CloneNotSupportedException { - Cat c = null; - c = (Cat) super.clone(); - c.owner = (Person) owner.clone();//拷贝Person对象 - return c; - } - - public static void main(String[] args) throws CloneNotSupportedException { - Cat c = new Cat(); - Person p = new Person(18, "程序员大彬"); - c.owner = p; - - Cat cloneCat = (Cat) c.clone(); - p.setName("大彬"); - System.out.println(cloneCat.owner.getName()); - } - //output - //程序员大彬 -} -``` - diff --git "a/Java/\345\271\266\345\217\221.md" "b/Java/\345\271\266\345\217\221.md" deleted file mode 100644 index 65ea9c9..0000000 --- "a/Java/\345\271\266\345\217\221.md" +++ /dev/null @@ -1,1166 +0,0 @@ - - - - -- [线程池](#%E7%BA%BF%E7%A8%8B%E6%B1%A0) - - [线程池原理](#%E7%BA%BF%E7%A8%8B%E6%B1%A0%E5%8E%9F%E7%90%86) - - [线程池大小](#%E7%BA%BF%E7%A8%8B%E6%B1%A0%E5%A4%A7%E5%B0%8F) - - [关闭线程池](#%E5%85%B3%E9%97%AD%E7%BA%BF%E7%A8%8B%E6%B1%A0) -- [executor框架](#executor%E6%A1%86%E6%9E%B6) - - [简介](#%E7%AE%80%E4%BB%8B) - - [ThreadPoolExecutor实例](#threadpoolexecutor%E5%AE%9E%E4%BE%8B) - - [Runnable和Callable的区别](#runnable%E5%92%8Ccallable%E7%9A%84%E5%8C%BA%E5%88%AB) - - [Future和FutureTask](#future%E5%92%8Cfuturetask) - - [execute()和submit()](#execute%E5%92%8Csubmit) - - [常用的线程池](#%E5%B8%B8%E7%94%A8%E7%9A%84%E7%BA%BF%E7%A8%8B%E6%B1%A0) - - [FixedThreadPool](#fixedthreadpool) - - [SingleThreadExecutor](#singlethreadexecutor) - - [CachedThreadPool](#cachedthreadpool) - - [ScheduledThreadPoolExecutor](#scheduledthreadpoolexecutor) - - [编码规范](#%E7%BC%96%E7%A0%81%E8%A7%84%E8%8C%83) -- [JMM](#jmm) -- [进程线程](#%E8%BF%9B%E7%A8%8B%E7%BA%BF%E7%A8%8B) - - [线程状态](#%E7%BA%BF%E7%A8%8B%E7%8A%B6%E6%80%81) - - [中断](#%E4%B8%AD%E6%96%AD) - - [常见方法](#%E5%B8%B8%E8%A7%81%E6%96%B9%E6%B3%95) - - [join](#join) - - [yield](#yield) - - [sleep](#sleep) - - [wait()和sleep()的区别](#wait%E5%92%8Csleep%E7%9A%84%E5%8C%BA%E5%88%AB) - - [创建线程的方法](#%E5%88%9B%E5%BB%BA%E7%BA%BF%E7%A8%8B%E7%9A%84%E6%96%B9%E6%B3%95) -- [线程间通信](#%E7%BA%BF%E7%A8%8B%E9%97%B4%E9%80%9A%E4%BF%A1) - - [volatile](#volatile) - - [synchronized](#synchronized) - - [等待通知机制](#%E7%AD%89%E5%BE%85%E9%80%9A%E7%9F%A5%E6%9C%BA%E5%88%B6) -- [锁](#%E9%94%81) - - [synchronized](#synchronized-1) - - [释放锁](#%E9%87%8A%E6%94%BE%E9%94%81) - - [实现原理](#%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86) - - [锁的状态](#%E9%94%81%E7%9A%84%E7%8A%B6%E6%80%81) - - [ReentrantLock](#reentrantlock) - - [原理](#%E5%8E%9F%E7%90%86) - - [ReentrantLock和synchronized区别](#reentrantlock%E5%92%8Csynchronized%E5%8C%BA%E5%88%AB) - - [锁的分类](#%E9%94%81%E7%9A%84%E5%88%86%E7%B1%BB) - - [公平锁与非公平锁](#%E5%85%AC%E5%B9%B3%E9%94%81%E4%B8%8E%E9%9D%9E%E5%85%AC%E5%B9%B3%E9%94%81) - - [共享式与独占式锁](#%E5%85%B1%E4%BA%AB%E5%BC%8F%E4%B8%8E%E7%8B%AC%E5%8D%A0%E5%BC%8F%E9%94%81) - - [悲观锁与乐观锁](#%E6%82%B2%E8%A7%82%E9%94%81%E4%B8%8E%E4%B9%90%E8%A7%82%E9%94%81) - - [CAS](#cas) -- [并发工具](#%E5%B9%B6%E5%8F%91%E5%B7%A5%E5%85%B7) - - [CountDownLatch](#countdownlatch) - - [CyclicBarrier](#cyclicbarrier) - - [CyclicBarrier和CountDownLatch区别](#cyclicbarrier%E5%92%8Ccountdownlatch%E5%8C%BA%E5%88%AB) - - [Semaphore](#semaphore) -- [原子类](#%E5%8E%9F%E5%AD%90%E7%B1%BB) - - [基本类型原子类](#%E5%9F%BA%E6%9C%AC%E7%B1%BB%E5%9E%8B%E5%8E%9F%E5%AD%90%E7%B1%BB) - - [数组类型原子类](#%E6%95%B0%E7%BB%84%E7%B1%BB%E5%9E%8B%E5%8E%9F%E5%AD%90%E7%B1%BB) - - [引用类型原子类](#%E5%BC%95%E7%94%A8%E7%B1%BB%E5%9E%8B%E5%8E%9F%E5%AD%90%E7%B1%BB) -- [AQS](#aqs) - - [原理](#%E5%8E%9F%E7%90%86-1) -- [Condition](#condition) - - [实现原理](#%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86-1) -- [其他](#%E5%85%B6%E4%BB%96) - - [Daemon Thread](#daemon-thread) -- [参考资料](#%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99) - - - -> 首先给大家分享一个github仓库,上面放了**200多本经典的计算机书籍**,包括C语言、C++、Java、Python、前端、数据库、操作系统、计算机网络、数据结构和算法、机器学习、编程人生等,可以star一下,下次找书直接在上面搜索,仓库持续更新中~ -> -> github地址:https://github.com/Tyson0314/java-books -> -> 如果github访问不了,可以访问gitee仓库。 -> -> gitee地址:https://gitee.com/tysondai/java-books - -## 线程池 - -**使用线程池的好处**: - -- **降低资源消耗**。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。 -- **提高响应速度**。当任务到达时,任务可以不需要的等到线程创建就能立即执行。 -- **提高线程的可管理性**。统一管理线程,避免系统创建大量同类线程而导致消耗完内存。 - -``` -public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler); -``` -### 线程池原理 - -创建新的线程需要获取全局锁,通过这种设计可以尽量避免获取全局锁,当 ThreadPoolExecutor 完成预热之后(当前运行的线程数大于等于 corePoolSize),提交的大部分任务都会被放到 BlockingQueue。 - -![](http://img.dabin-coder.cn/image/thread-pool.png) - -ThreadPoolExecutor 的通用构造函数: - -``` -public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler); -``` - -- corePoolSize:当有新任务时,如果线程池中线程数没有达到线程池的基本大小,则会创建新的线程执行任务,否则将任务放入阻塞队列。当线程池中存活的线程数总是大于 corePoolSize 时,应该考虑调大 corePoolSize。 - -- maximumPoolSize:当阻塞队列填满时,如果线程池中线程数没有超过最大线程数,则会创建新的线程运行任务。否则根据拒绝策略处理新任务。非核心线程类似于临时借来的资源,这些线程在空闲时间超过 keepAliveTime 之后,就应该退出,避免资源浪费。 - -- BlockingQueue:存储等待运行的任务。 - -- keepAliveTime:**非核心线程**空闲后,保持存活的时间,此参数只对非核心线程有效。设置为0,表示多余的空闲线程会被立即终止。 - -- TimeUnit:时间单位 - - ```java - TimeUnit.DAYS - TimeUnit.HOURS - TimeUnit.MINUTES - TimeUnit.SECONDS - TimeUnit.MILLISECONDS - TimeUnit.MICROSECONDS - TimeUnit.NANOSECONDS - ``` -- ThreadFactory:每当线程池创建一个新的线程时,都是通过线程工厂方法来完成的。在 ThreadFactory 中只定义了一个方法 newThread,每当线程池需要创建新线程就会调用它。 - - ```java - public class MyThreadFactory implements ThreadFactory { - private final String poolName; - - public MyThreadFactory(String poolName) { - this.poolName = poolName; - } - - public Thread newThread(Runnable runnable) { - return new MyAppThread(runnable, poolName);//将线程池名字传递给构造函数,用于区分不同线程池的线程 - } - } - ``` - -- RejectedExecutionHandler:当队列和线程池都满了时,根据拒绝策略处理新任务。 - - ```java - AbortPolicy:默认的策略,直接抛出RejectedExecutionException - DiscardPolicy:不处理,直接丢弃 - DiscardOldestPolicy:将等待队列队首的任务丢弃,并执行当前任务 - CallerRunsPolicy:由调用线程处理该任务 - ``` - -### 线程池大小 - -如果线程池线程数量太小,当有大量请求需要处理,系统响应比较慢影响体验,甚至会出现任务队列大量堆积任务导致OOM。 - -如果线程池线程数量过大,大量线程可能会同时在争取 CPU 资源,这样会导致大量的上下文切换(cpu给线程分配时间片,当线程的cpu时间片用完后保存状态,以便下次继续运行),从而增加线程的执行时间,影响了整体执行效率。 - -**CPU 密集型任务(N+1)**: 这种任务消耗的主要是 CPU 资源,可以将线程数设置为 N(CPU 核心数)+1,比 CPU 核心数多出来的一个线程是为了防止某些原因导致的任务暂停(线程阻塞,如io操作,等待锁,线程sleep)而带来的影响。一旦某个线程被阻塞,释放了cpu资源,而在这种情况下多出来的一个线程就可以充分利用 CPU 的空闲时间。 - -**I/O 密集型任务(2N)**: 系统会用大部分的时间来处理 I/O 操作,而线程等待 I/O 操作会被阻塞,释放 cpu资源,这时就可以将 CPU 交出给其它线程使用。因此在 I/O 密集型任务的应用中,我们可以多配置一些线程,具体的计算方法:最佳线程数 = CPU核心数 * (1/CPU利用率) = CPU核心数 * (1 + (I/O耗时/CPU耗时)),一般可设置为2N。 - -### 关闭线程池 - -shutdown(): - -将线程池状态置为`SHUTDOWN`,并不会立即停止: - -- 停止接收外部提交的任务 -- 内部正在跑的任务和队列里等待的任务,会执行完 -- 等到第二步完成后,才真正停止 - -shutdownNow(): - -将线程池状态置为`STOP`。企图立即停止,事实上不一定: - -- 跟shutdown()一样,先停止接收外部提交的任务 -- 忽略队列里等待的任务 -- 尝试将正在跑的任务中断(不一定中断成功,取决于任务响应中断的逻辑) -- 返回未执行的任务列表 - - - -## executor框架 - -1.5后引入的Executor框架的最大优点是把任务的提交和执行解耦。当提交一个Callable对象给ExecutorService,将得到一个Future对象,调用Future对象的get方法等待执行结果。Executor框架的内部使用了线程池机制,它在java.util.cocurrent 包下,通过该框架来控制线程的启动、执行和关闭,可以简化并发编程的操作。 - -### 简介 - -executor框架由3部分组成:任务、任务的执行、异步计算的结果 - -- 任务。需要实现的接口:Runnable和Callable接口。 -- 任务的执行。ExecutorService 是一个接口,用于定义线程池,调用它的 execute(Runnable)或者 submit(Runnable/Callable)执行任务。ExecutorService接口继承于Executor,有两个实现类`ThreadPoolExecutor`和`ScheduledThreadPoolExecutor`。 -- 异步计算的结果。包括future接口和实现future接口的FutureTask,调用future.get()会阻塞当前线程直到任务完成,future.cancel()可以取消执行任务。 - -### ThreadPoolExecutor实例 - -使用 `ThreadPoolExecutor` 构造函数自定义参数的方式来创建线程池。 - -```java -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) throws ExecutionException, InterruptedException { - 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++) { - Callable worker = () -> { - System.out.println(Thread.currentThread().getName()); - return "ok"; - }; - Future f = executor.submit(worker); - f.get(); - } - executor.shutdown(); - while (!executor.isTerminated()) { - } - System.out.println("Finished all threads"); - } -} -``` - -### Runnable和Callable的区别 - -Runnable 任务执行后不能返回值或者抛出异常。Callable 任务执行后可以返回值或抛出异常。 - -``` -Executors.callable(Runnable task);//runnable转化为callable -ExecutorService.execute(Runnable); -ExecutorService.submit(Runnable/Callable);//submit callable任务有返回值 - -//返回值是泛型参数V -public interface Callable { - V call() throws Exception; -} -``` - -### Future和FutureTask - -Future 可以获取任务执行的结果、取消任务。调用 future.get()会阻塞当前线程直到任务返回结果。 - -```java -public interface Future { - boolean cancel(boolean mayInterruptIfRunning); - - boolean isCancelled(); - - boolean isDone(); - - V get() throws InterruptedException, ExecutionException; - - V get(long timeout, TimeUnit unit) - throws InterruptedException, ExecutionException, TimeoutException; -} -``` - -FutureTask 实现了 RunnableFuture 接口,而 RunnableFuture 实现了 Runnable 和 Future\ 接口。 - -### execute()和submit() - -`execute()`方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否。 - -`submit()`方法用于提交需要返回值的任务。线程池会返回一个 `Future` 类型的对象,通过这个 `Future` 对象可以判断任务是否执行成功,并且可以通过 `Future` 的 `get()`方法来获取返回值,`get()`方法会阻塞当前线程直到任务完成,而使用 `get(long timeout, TimeUnit unit)`方法则会阻塞当前线程一段时间后立即返回,无论任务是否执行完。 - -### 常用的线程池 - -常见的线程池有 FixedThreadPool、SingleThreadExecutor、CachedThreadPool 和 ScheduledThreadPool。这几个都是 ExecutorService (线程池)实例。 - -#### FixedThreadPool - -固定线程数的线程池。任何时间点,最多只有 nThreads 个线程处于活动状态执行任务。 - -``` -public static ExecutorService newFixedThreadPool(int nThreads) { - return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()); -} -``` - -使用无界队列 LinkedBlockingQueue(队列容量为 Integer.MAX_VALUE),运行中的线程池不会拒绝任务,即不会调用RejectedExecutionHandler.rejectedExecution()方法。 - -maxThreadPoolSize 是无效参数,故将它的值设置为与 coreThreadPoolSize 一致。 - -keepAliveTime 也是无效参数,设置为0L,因为此线程池里所有线程都是核心线程,核心线程不会被回收(除非设置了executor.allowCoreThreadTimeOut(true))。 - -适用场景:适用于处理CPU密集型的任务,确保CPU在长期被工作线程使用的情况下,尽可能的少的分配线程,即适用执行长期的任务。需要注意的是,FixedThreadPool 不会拒绝任务,**在任务比较多的时候会导致 OOM。** - -#### SingleThreadExecutor - -只有一个线程的线程池。 - -``` -public static ExecutionService newSingleThreadExecutor() { - return new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()); -} -``` - -使用无界队列 LinkedBlockingQueue。线程池只有一个运行的线程,新来的任务放入工作队列,线程处理完任务就循环从队列里获取任务执行。保证顺序的执行各个任务。 - -适用场景:适用于串行执行任务的场景,一个任务一个任务地执行。**在任务比较多的时候也是会导致 OOM。** - -#### CachedThreadPool - -根据需要创建新线程的线程池。 - -``` -public static ExecutorService newCachedThreadPool() { - return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue()); -} -``` - -如果主线程提交任务的速度高于线程处理任务的速度时,`CachedThreadPool` 会不断创建新的线程。极端情况下,这样会导致耗尽 cpu 和内存资源。 - -使用没有容量的SynchronousQueue作为线程池工作队列,当线程池有空闲线程时,`SynchronousQueue.offer(Runnable task)`提交的任务会被空闲线程处理,否则会创建新的线程处理任务。 - -适用场景:用于并发执行大量短期的小任务。`CachedThreadPool`允许创建的线程数量为 Integer.MAX_VALUE ,**可能会创建大量线程,从而导致 OOM。** - -#### ScheduledThreadPoolExecutor - -在给定的延迟后运行任务,或者定期执行任务。在实际项目中基本不会被用到,因为有其他方案选择比如`quartz`。 - -使用的任务队列 `DelayQueue` 封装了一个 `PriorityQueue`,`PriorityQueue` 会对队列中的任务进行排序,时间早的任务先被执行(即`ScheduledFutureTask` 的 `time` 变量小的先执行),如果time相同则先提交的任务会被先执行(`ScheduledFutureTask` 的 `squenceNumber` 变量小的先执行)。 - -执行周期任务步骤: - -1. 线程从 `DelayQueue` 中获取已到期的 `ScheduledFutureTask(DelayQueue.take())`。到期任务是指 `ScheduledFutureTask`的 time 大于等于当前系统的时间; -2. 执行这个 `ScheduledFutureTask`; -3. 修改 `ScheduledFutureTask` 的 time 变量为下次将要被执行的时间; -4. 把这个修改 time 之后的 `ScheduledFutureTask` 放回 `DelayQueue` 中(`DelayQueue.add()`)。 - -![](http://img.dabin-coder.cn/image/scheduled-task.jpg) - -适用场景:周期性执行任务的场景,需要限制线程数量的场景。 - -#### 编码规范 - -阿里巴巴编码规约不允许使用Executors去创建线程池,而是通过ThreadPoolExecutor的方式手动创建线程池,这样子使用者会更加明确线程池的运行机制,避免资源耗尽的风险。 - -Executors 创建线程池对象的弊端: - -FixedThreadPool和SingleThreadPool。允许请求队列长度为 Integer.MAX_VALUE,可能堆积大量请求,从而导致OOM。 - -CachedThreadPool。创建的线程池允许的最大线程数是Integer.MAX_VALUE,当添加任务的速度大于线程池处理任务的速度,可能会创建大量的线程,消耗资源,甚至导致OOM。 - -![](http://img.dabin-coder.cn/image/executors-ali.png) - -正确示例(阿里巴巴编码规范): - -```java -//正例1 -ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build(); -//Common Thread Pool -ExecutorService pool = new ThreadPoolExecutor(5, 200, -0L, TimeUnit.MILLISECONDS, //0L keepAliveTime -new LinkedBlockingQueue(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy()); - -pool.execute(()-> System.out.println(Thread.currentThread().getName())); -pool.shutdown();//gracefully shutdown - -//正例2 -ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1, //corePoolSize threadFactory -    new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build()); -``` - -## JMM - -Java内存模型:线程之间的共享变量存储在主内存里,每个线程都有自己私有的本地内存,本地内存保存了共享变量的副本,线程对变量的操作都在本地内存中进行,不能直接读写主内存中的变量。不同的线程之间也无法直接访问对方本地内存中的变量,线程之间值的传递都需要通过主内存来完成。 - -线程a和线程b要想进行数据交换一般要经过下面的步骤: - -1. 线程a把本地内存a中的更新过的共享变量刷新到主内存中去。 -2. 线程b到主内存中去读取线程a刷新过的共享变量,然后复制一份到本地内存b中去。 - -![](http://img.dabin-coder.cn/image/image-20210909233258929.png) - -本地内存是JMM的一个抽象概念,并不真实存在,它包括缓存、写缓冲区、寄存器以及其他硬件和编译器优化。 - -## 进程线程 - -进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。 -线程是比进程更小的执行单位,它是在一个进程中独立的控制流,一个进程可以启动多个线程,每条线程并行执行不同的任务。 - -### 线程状态 - -初始(NEW):线程被构建,还没有调用 start()。 - -运行(RUNNABLE):包括操作系统的就绪和运行两种状态。 - -阻塞(BLOCKED):一般是被动的,在抢占资源中得不到资源,被动的挂起在内存,等待资源释放将其唤醒。线程被阻塞会释放CPU,不释放内存。 - -等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。 - -超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。 - -终止(TERMINATED):表示该线程已经执行完毕。 - -![](http://img.dabin-coder.cn/image/image-20210909235618175.png) - -> 图片来源:Java并发编程的艺术 - -### 中断 - -线程中断即线程运行过程中被其他线程给打断了,它与 stop 最大的区别是:stop 是由系统强制终止线程,而线程中断则是给目标线程发送一个中断信号,如果目标线程没有接收线程中断的信号并结束线程,线程则不会终止,具体是否退出或者执行其他逻辑取决于目标线程。 - -线程中断三个重要的方法: - -**1、java.lang.Thread#interrupt** - -调用目标线程的interrupt()方法,给目标线程发一个中断信号,线程被打上中断标记。 - -**2、java.lang.Thread#isInterrupted()** - -判断目标线程是否被中断,不会清除中断标记。 - -**3、java.lang.Thread#interrupted** - -判断目标线程是否被中断,会清除中断标记。 - -```java -private static void test2() { - Thread thread = new Thread(() -> { - while (true) { - Thread.yield(); - - // 响应中断 - if (Thread.currentThread().isInterrupted()) { - System.out.println("Java技术栈线程被中断,程序退出。"); - return; - } - } - }); - thread.start(); - thread.interrupt(); -} -``` - -### 常见方法 - -#### join - -Thread.join(),在main中创建了thread线程,在main中调用了thread.join()/thread.join(long millis),main线程放弃cpu控制权,线程进入WAITING/TIMED_WAITING状态,等到thread线程执行完才继续执行main线程。 - -```java -public final void join() throws InterruptedException { - join(0); -} -``` - -#### yield - -Thread.yield(),一定是当前线程调用此方法,当前线程放弃获取的CPU时间片,但不释放锁资源,由运行状态变为就绪状态,让OS再次选择线程。作用:让相同优先级的线程轮流执行,但并不保证一定会轮流执行。实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield()不会导致阻塞。该方法与sleep()类似,只是不能由用户指定暂停多长时间。 - -```java -public static native void yield(); //static方法 -``` - -#### sleep - -Thread.sleep(long millis),一定是当前线程调用此方法,当前线程进入TIMED_WAITING状态,让出cpu资源,但不释放对象锁,指定时间到后又恢复运行。作用:给其它线程执行机会的最佳方式。 - -```java -public static native void sleep(long millis) throws InterruptedException;//static方法 -``` - -#### wait()和sleep()的区别 - -相同点: - -1. 使当前线程暂停运行,把机会交给其他线程 -2. 任何线程在等待期间被中断都会抛出InterruptedException - -不同点: - -1. wait() 是Object超类中的方法;而sleep()是线程Thread类中的方法 -2. 对锁的持有不同,wait()会释放锁,而sleep()并不释放锁 -3. 唤醒方法不完全相同,wait() 依靠notify或者notifyAll 、中断、达到指定时间来唤醒;而sleep()到达指定时间被唤醒 -4. 调用obj.wait()需要先获取对象的锁,而 Thread.sleep()不用 - -### 创建线程的方法 - -- 通过扩展Thread类来创建多线程 -- 通过实现Runnable接口来创建多线程,可实现线程间的资源共享 -- 实现Callable接口,通过FutureTask接口创建线程。 -- 使用Executor框架来创建线程池。 - -**继承 Thread 创建线程**代码如下。run()方法是由jvm创建完操作系统级线程后回调的方法,不可以手动调用,手动调用相当于调用普通方法。 - -```java -/** - * @author: 程序员大彬 - * @time: 2021-09-11 10:15 - */ -public class MyThread extends Thread { - public MyThread() { - } - - @Override - public void run() { - for (int i = 0; i < 10; i++) { - System.out.println(Thread.currentThread() + ":" + i); - } - } - - public static void main(String[] args) { - MyThread mThread1 = new MyThread(); - MyThread mThread2 = new MyThread(); - MyThread myThread3 = new MyThread(); - mThread1.start(); - mThread2.start(); - myThread3.start(); - } -} -``` - -**Runnable 创建线程代码**: - -```java -/** - * @author: 程序员大彬 - * @time: 2021-09-11 10:04 - */ -public class RunnableTest { - public static void main(String[] args){ - Runnable1 r = new Runnable1(); - Thread thread = new Thread(r); - thread.start(); - System.out.println("主线程:["+Thread.currentThread().getName()+"]"); - } -} - -class Runnable1 implements Runnable{ - @Override - public void run() { - System.out.println("当前线程:"+Thread.currentThread().getName()); - } -} -``` - -实现Runnable接口比继承Thread类所具有的优势: - -1. 资源共享,适合多个相同的程序代码的线程去处理同一个资源 -2. 可以避免java中的单继承的限制 -3. 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类 - -**Callable 创建线程代码**: - -```java -/** - * @author: 程序员大彬 - * @time: 2021-09-11 10:21 - */ -public class CallableTest { - public static void main(String[] args) { - Callable1 c = new Callable1(); - - //异步计算的结果 - FutureTask result = new FutureTask<>(c); - - new Thread(result).start(); - - try { - //等待任务完成,返回结果 - int sum = result.get(); - System.out.println(sum); - } catch (InterruptedException | ExecutionException e) { - e.printStackTrace(); - } - } - -} - -class Callable1 implements Callable { - - @Override - public Integer call() throws Exception { - int sum = 0; - - for (int i = 0; i <= 100; i++) { - sum += i; - } - return sum; - } -} -``` - -**使用 Executor 创建线程代码**: - -```java -/** - * @author: 程序员大彬 - * @time: 2021-09-11 10:44 - */ -public class ExecutorsTest { - public static void main(String[] args) { - //获取ExecutorService实例,生产禁用,需要手动创建线程池 - ExecutorService executorService = Executors.newCachedThreadPool(); - //提交任务 - executorService.submit(new RunnableDemo()); - } -} - -class RunnableDemo implements Runnable { - @Override - public void run() { - System.out.println("大彬"); - } -} -``` - - - -## 线程间通信 - -### volatile - -volatile是轻量级的同步机制,volatile保证变量对所有线程的可见性,不保证原子性。 - -1. 当对volatile变量进行写操作的时候,JVM会向处理器发送一条LOCK前缀的指令,将该变量所在缓存行的数据写回系统内存。 -2. 由于缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己的缓存是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行置为无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存中。 - -MESI(缓存一致性协议):当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,就会从内存重新读取。 - -volatile关键字的两个作用: - -1. 保证了不同线程对共享变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。 -2. 禁止进行指令重排序。 - -指令重排序是JVM为了优化指令,提高程序运行效率,在不影响单线程程序执行结果的前提下,尽可能地提高并行度。Java编译器会在生成指令系列时在适当的位置会插入`内存屏障`指令来禁止处理器重排序。插入一个内存屏障,相当于告诉CPU和编译器先于这个命令的必须先执行,后于这个命令的必须后执行。对一个volatile字段进行写操作,Java内存模型将在写操作后插入一个写屏障指令,这个指令会把之前的写入值都刷新到内存。 - -### synchronized - -保证线程对变量访问的可见性和排他性。synchronized 详细内容见下文锁部分。 - -### 等待通知机制 - -wait/notify为 Object 对象的方法,调用wait/notify需要先获得对象的锁。对象调用wait之后线程释放锁,将线程放到对象的等待队列,当通知线程调用此对象的notify()方法后,等待线程并不会立即从wait返回,需要等待通知线程释放锁(通知线程执行完同步代码块),等待队列里的线程获取锁,获取锁成功才能从wait()方法返回,即从wait方法返回前提是线程获得锁。 - -等待通知机制依托于同步机制,目的是确保等待线程从wait方法返回时能感知到通知线程对对象的变量值的修改。 - - - -## 锁 - -### synchronized - -较常用的用于保证线程安全的方式。当一个线程获取到锁时,其他线程都会被阻塞住,当持有锁的线程释放锁之后会唤醒这些线程,被唤醒的线程才有机会获取到锁。 - -- 修饰实例方法,作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁 -- 修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁(类的字节码文件) -- 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码块前要获得给定对象的锁 - -获取了类锁的线程和获取了对象锁的线程是不冲突的。 - -#### 释放锁 - -当方法或者代码块执行完毕后会自动释放锁,不需要做任何的操作。 -当一个线程执行的代码出现异常时,其所持有的锁会自动释放。 - -#### 实现原理 - -synchronized通过对象内部的监视器锁(monitor)实现。每个对象都有一个monitor,当对象的monitor被持有时,则它处于锁定的状态。 - -**代码块的同步**是使用monitorenter和monitorexit指令实现的,monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处或异常处。 - -```java -public class SynchronizedDemo { - public void method() { - synchronized (this) { - System.out.println("method start"); - } - } -} -``` - -线程访问同步块时,先执行monitorenter指令时尝试获取monitor,过程如下: - -1. 如果monitor的进入数entry count为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。 -2. 如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1。 -3. 如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor。 - -线程退出同步块时会执行monitorexit指令,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor。 - -Synchronized底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。 - -![](http://img.dabin-coder.cn/image/synchronized-block.png) - -**方法的同步**不是通过添加monitorenter和monitorexit指令来完成,而是在其常量池中添加了ACC_SYNCHRONIZED标识符。JVM就是根据该标识符来实现方法的同步的:当线程调用方法时,会先检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,说明此方法是同步方法,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他线程无法再获得同一个monitor对象。 - -```java -public class SynchronizedMethod { - public synchronized void method() { - System.out.println("Hello World!"); - } -} -``` - -![](http://img.dabin-coder.cn/image/synchronized-method.png) - -#### 锁的状态 - -Synchronized是通过对象内部的监视器来实现的。但是监视器锁本质又是依赖于底层的操作系统的Mutex Lock来实现的。而操作系统实现线程之间的切换这就需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间。这种依赖于操作系统Mutex Lock所实现的锁我们称之为重量级锁。 - -JDK1.6中为了减少获得锁和释放锁带来的性能消耗,引入了偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销。 - -synchronized锁主要存在四种状态,依次是:偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。 - -- 偏向锁:当线程访问同步块并获取锁时,会在对象头和锁记录中存储锁偏向的线程id,以后该线程进入和退出同步块时,只需简单测试一下对象头的mark word中是否存储着指向当前线程的偏向锁,如果测试成功,则线程获取锁成功,否则,需再测试一下mark word中偏向锁标识是否是1,是的话则使用CAS操作竞争锁。如果竞争成功,则将Mark Word中线程ID设置为当前线程ID,如果CAS获取偏向锁失败,则表示有竞争。当到达全局安全点时获得偏向锁的线程被挂起,偏向锁升级为轻量级锁,然后被阻塞在安全点的线程继续往下执行同步代码。 - **偏向锁偏向于第一个获得它的线程,如果程序运行过程,该锁没有被其他线程获取,那么持有偏向锁的线程就不需要进行同步。引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行的开销,因为轻量级锁的获取及释放使用了多次CAS原子指令,而偏向锁只在置换ThreadID的时候使用一次CAS原子指令。当存在锁竞争的时候,偏向锁会升级为轻量级锁。** - 适用场景:在锁无竞争的情况下使用,在线程没有执行完同步代码之前,没有其它线程去竞争锁,一旦有了竞争就升级为轻量级锁,升级为轻量级锁的时候需要撤销偏向锁,会做很多额外操作,导致性能下降。 - -- 轻量级锁 - 加锁过程:线程执行同步块之前,JVM会先在当前线程的栈帧中创建用于存储锁记录的空间,并将对象头的mark word复制到锁记录(displaced mark word)中,然后线程尝试使用cas将对象头的mark word替换为指向锁记录的指针。如果成功,则当前线程获得锁,否则表示有其他线程竞争锁,当前线程便尝试使用自旋来获得锁。当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁升级为重量级锁。 - 解锁过程:使用原子的cas操作将displaced mark word替换回到对象头,如果成功则解锁成功,否则表明有锁竞争,锁会膨胀成重量级锁。 - - **在没有多线程竞争的前提下,使用轻量级锁可以减少传统的重量级锁使用操作系统互斥量(申请互斥锁)产生的性能消耗,因为使用轻量级锁时,不需要申请互斥量。另外,轻量级锁的加锁和解锁都用到了CAS操作。如果没有竞争,轻量级锁使用 CAS 操作避免了使用互斥操作的开销。但如果存在锁竞争,除了互斥量开销外,还会额外发生CAS操作,因此在有锁竞争的情况下,轻量级锁比传统的重量级锁更慢!如果锁竞争激烈,那么轻量级锁将很快膨胀为重量级锁!** - -- 重量级锁:当一个线程获取到锁时,其他线程都会被阻塞住,当持有锁的线程释放锁之后会唤醒这些线程,被唤醒的线程才有机会获取到锁。 - - synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中,保证了可见性。 - -- 自旋锁:一般线程持有锁的时间都不是太长,所以仅仅为了这一点时间去挂起线程/恢复线程比较浪费资源。自旋锁就是让该线程等待一段时间,执行一段无意义的循环,不会被立即挂起,看持有锁的线程是否会很快释放锁。如果持有锁的线程很快就释放了锁,那么自旋的效率就非常好,反之,自旋的线程就会白白消耗掉处理的资源,这样反而会带来性能上的浪费。所以自旋的次数必须要有一个限度,如果自旋超过了限定次数仍然没有获取到锁,则应该被挂起。 - -- 自适应自旋锁:JDK 1.6引入了更加聪明的自旋锁,即自适应自旋锁。所谓自适应就意味着自旋的次数不再是固定的,它是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。 - -- 锁消除:虚拟机即使编译器在运行时,如果检测到那些共享数据不可能存在竞争,那么就执行锁消除。 - -- 锁粗化:如果一系列的连续操作都对同一个对象反复加锁和解锁,那么会带来很多不必要的性能消耗,使用锁粗化减少锁操作的开销。 - -### ReentrantLock - -重入锁,支持一个线程对资源的重复加锁。该锁的还支持设置获取锁时的公平和非公平性。 - -使用lock时需要在try finally块进行解锁: - -```java -public static final Object get(String key) { - r.lock(); - try { - return map.get(key); - } finally { - r.unlock(); - } -} -``` - -#### 原理 - -ReentrantLock是通过组合自定义同步器来实现锁的获取与释放。当线程尝试获取同步状态时,首先判断当前线程是否为获取锁的线程来决定获取操作是否成功,如果是获取锁的线程再次请求,则将同步状态值进行增加并返回true,表示获取同步状态成功。获取同步状态失败,则该线程会被构造成node节点放到AQS同步队列中。 - -如果锁被获取了n次,那么前n-1次 tryRelease(int releases)方法必须返回false,第n次调用tryRelease()之后,同步状态完全释放(值为0),才会返回true。 - -### ReentrantLock和synchronized区别 - -1. 使用synchronized关键字实现同步,线程执行完同步代码块会自动释放锁,而ReentrantLock需要手动释放锁。 -2. synchronized是非公平锁,ReentrantLock可以设置为公平锁。 -3. ReentrantLock上等待获取锁的线程是可中断的,线程可以放弃等待锁。而synchonized会无限期等待下去。 -4. ReentrantLock 可以设置超时获取锁。在指定的截止时间之前获取锁,如果截止时间到了还没有获取到锁,则返回。 -5. ReentrantLock 的 tryLock() 方法可以尝试非阻塞的获取锁,调用该方法后立刻返回,如果能够获取则返回true,否则返回false。 - -### 锁的分类 - -#### 公平锁与非公平锁 - -按照线程访问顺序获取对象锁。synchronized 是非公平锁, Lock 默认是非公平锁,可以设置为公平锁,公平锁会影响性能。 - -```java -public ReentrantLock() { - sync = new NonfairSync(); -} - -public ReentrantLock(boolean fair) { - sync = fair ? new FairSync() : new NonfairSync(); -} -``` - -#### 共享式与独占式锁 - -共享式与独占式的最主要区别在于:同一时刻独占式只能有一个线程获取同步状态,而共享式在同一时刻可以有多个线程获取同步状态。例如读操作可以有多个线程同时进行,而写操作同一时刻只能有一个线程进行写操作,其他操作都会被阻塞。 - -#### 悲观锁与乐观锁 - -悲观锁,每次访问资源都会加锁,执行完同步代码释放锁,synchronized 和 ReentrantLock 属于悲观锁。 - -乐观锁,不会锁定资源,所有的线程都能访问并修改同一个资源,如果没有冲突就修改成功并退出,否则就会继续循环尝试。乐观锁最常见的实现就是CAS。 - -乐观锁一般来说有以下2种方式: - -1. 使用数据版本记录机制实现,这是乐观锁最常用的一种实现方式。给数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的version字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据。 -2. 使用时间戳。数据库表增加一个字段,字段类型使用时间戳(timestamp),和上面的version类似,也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致则OK,否则就是版本冲突。 - -适用场景: - -- 悲观锁适合写操作多的场景。 -- 乐观锁适合读操作多的场景,不加锁可以提升读操作的性能。 - -##### CAS - -CAS全称 Compare And Swap,比较与交换,是乐观锁的主要实现方式。CAS 在不使用锁的情况下实现多线程之间的变量同步。ReentrantLock 内部的 AQS 和原子类内部都使用了 CAS。 - -CAS算法涉及到三个操作数: - -- 需要读写的内存值 V。 -- 进行比较的值 A。 -- 要写入的新值 B。 - -只有当 V 的值等于 A 时,才会使用原子方式用新值B来更新V的值,否则会继续重试直到成功更新值。 - -以 AtomicInteger 为例,AtomicInteger 的 getAndIncrement()方法底层就是CAS实现,关键代码是 `compareAndSwapInt(obj, offset, expect, update)`,其含义就是,如果`obj`内的`value`和`expect`相等,就证明没有其他线程改变过这个变量,那么就更新它为`update`,如果不相等,那就会继续重试直到成功更新值。 - -CAS 三大问题: - -1. **ABA问题**。CAS需要在操作值的时候检查内存值是否发生变化,没有发生变化才会更新内存值。但是如果内存值原来是A,后来变成了B,然后又变成了A,那么CAS进行检查时会发现值没有发生变化,但是实际上是有变化的。ABA问题的解决思路就是在变量前面添加版本号,每次变量更新的时候都把版本号加一,这样变化过程就从`A-B-A`变成了`1A-2B-3A`。 - - JDK从1.5开始提供了AtomicStampedReference类来解决ABA问题,原子更新带有版本号的引用类型。 - -2. **循环时间长开销大**。CAS操作如果长时间不成功,会导致其一直自旋,给CPU带来非常大的开销。 - -3. **只能保证一个共享变量的原子操作**。对一个共享变量执行操作时,CAS能够保证原子操作,但是对多个共享变量操作时,CAS是无法保证操作的原子性的。 - - Java从1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,可以把多个变量放在一个对象里来进行CAS操作。 - - - -## 并发工具 - -在JDK的并发包里提供了几个非常有用的并发工具类。CountDownLatch、CyclicBarrier和Semaphore工具类提供了一种并发流程控制的手段。 - -### CountDownLatch - -CountDownLatch用于某个线程等待其他线程**执行完任务**再执行,与thread.join()功能类似。常见的应用场景是开启多个线程同时执行某个任务,等到所有任务执行完再执行特定操作,如汇总统计结果。 - -``` -public class CountDownLatchDemo { - static final int N = 4; - static CountDownLatch latch = new CountDownLatch(N); - - public static void main(String[] args) throws InterruptedException { - - for(int i = 0; i < N; i++) { - new Thread(new Thread1()).start(); - } - - latch.await(1000, TimeUnit.MILLISECONDS); //调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行;等待timeout时间后count值还没变为0的话就会继续执行 - System.out.println("task finished"); - } - - static class Thread1 implements Runnable { - - @Override - public void run() { - try { - System.out.println(Thread.currentThread().getName() + "starts working"); - Thread.sleep(1000); - } catch (InterruptedException e) { - e.printStackTrace(); - } finally { - latch.countDown(); - } - } - } -} -``` - -运行结果: - -```java -Thread-0starts working -Thread-1starts working -Thread-2starts working -Thread-3starts working -task finished -``` - -### CyclicBarrier - -CyclicBarrier(同步屏障),用于一组线程互相等待到某个状态,然后这组线程再**同时**执行。 - -```java -public CyclicBarrier(int parties, Runnable barrierAction) { -} -public CyclicBarrier(int parties) { -} -``` - -参数parties指让多少个线程或者任务等待至某个状态;参数barrierAction为当这些线程都达到某个状态时会执行的内容。 - -```java -public class CyclicBarrierTest { - // 请求的数量 - private static final int threadCount = 10; - // 需要同步的线程数量 - 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"); - } - -} -``` - -运行结果如下,可以看出CyclicBarrier是可以重用的: - -```java -threadnum:0is ready -threadnum:1is ready -threadnum:2is ready -threadnum:3is ready -threadnum:4is ready -threadnum:4is finish -threadnum:3is finish -threadnum:2is finish -threadnum:1is finish -threadnum:0is finish -threadnum:5is ready -threadnum:6is ready -... -``` - -当四个线程都到达barrier状态后,会从四个线程中选择一个线程去执行Runnable。 - -### CyclicBarrier和CountDownLatch区别 - -CyclicBarrier 和 CountDownLatch 都能够实现线程之间的等待。 - -CountDownLatch用于某个线程等待其他线程**执行完任务**再执行。CyclicBarrier用于一组线程互相等待到某个状态,然后这组线程再**同时**执行。 -CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可以使用reset()方法重置,可用于处理更为复杂的业务场景。 - -### Semaphore - -Semaphore类似于锁,它用于控制同时访问特定资源的线程数量,控制并发线程数。 - -``` -public class SemaphoreDemo { - public static void main(String[] args) { - final int N = 7; - Semaphore s = new Semaphore(3); - for(int i = 0; i < N; i++) { - new Worker(s, i).start(); - } - } - - static class Worker extends Thread { - private Semaphore s; - private int num; - public Worker(Semaphore s, int num) { - this.s = s; - this.num = num; - } - - @Override - public void run() { - try { - s.acquire(); - System.out.println("worker" + num + " using the machine"); - Thread.sleep(1000); - System.out.println("worker" + num + " finished the task"); - s.release(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - } -} -``` - -运行结果如下,可以看出并非按照线程访问顺序获取资源的锁,即 - -```java -worker0 using the machine -worker1 using the machine -worker2 using the machine -worker2 finished the task -worker0 finished the task -worker3 using the machine -worker4 using the machine -worker1 finished the task -worker6 using the machine -worker4 finished the task -worker3 finished the task -worker6 finished the task -worker5 using the machine -worker5 finished the task -``` - - - -## 原子类 - -### 基本类型原子类 - -使用原子的方式更新基本类型 - -- AtomicInteger:整型原子类 -- AtomicLong:长整型原子类 -- AtomicBoolean :布尔型原子类 - -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 类主要利用 CAS (compare and swap) 保证原子操作,从而避免加锁的高开销。 - -### 数组类型原子类 - -使用原子的方式更新数组里的某个元素 - -- AtomicIntegerArray:整形数组原子类 -- AtomicLongArray:长整形数组原子类 -- AtomicReferenceArray :引用类型数组原子类 - -AtomicIntegerArray 类常用方法: - -```java -public final int get(int i) //获取 index=i 位置元素的值 -public final int getAndSet(int i, int newValue)//返回 index=i 位置的当前的值,并将其设置为新值:newValue -public final int getAndIncrement(int i)//获取 index=i 位置元素的值,并让该位置的元素自增 -public final int getAndDecrement(int i) //获取 index=i 位置元素的值,并让该位置的元素自减 -public final int getAndAdd(int i, int delta) //获取 index=i 位置元素的值,并加上预期的值 -boolean compareAndSet(int i, int expect, int update) //如果输入的数值等于预期值,则以原子方式将 index=i 位置的元素值设置为输入值(update) -public final void lazySet(int i, int newValue)//最终 将index=i 位置的元素设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。 -``` - -### 引用类型原子类 - -- AtomicReference:引用类型原子类 -- AtomicStampedReference:带有版本号的引用类型原子类。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。 -- AtomicMarkableReference :原子更新带有标记的引用类型。该类将 boolean 标记与引用关联起来 - - - -## AQS - -AQS定义了一套多线程访问共享资源的同步器框架,许多并发工具的实现都依赖于它,如常用的ReentrantLock/Semaphore/CountDownLatch。 - -### 原理 - -AQS使用一个volatile的int类型的成员变量state来表示同步状态,通过CAS修改同步状态的值。 - -```java -private volatile int state;//共享变量,使用volatile修饰保证线程可见性 -``` - -同步器依赖内部的同步队列(一个FIFO双向队列)来完成同步状态的管理,当前线程获取同步状态失败时,同步器会将当前线程以及等待状态(独占或共享 )构造成为一个节点(Node)并将其加入同步队列并进行自旋,当同步状态释放时,会把首节中的后继节点对应的线程唤醒,使其再次尝试获取同步状态。 - -![](http://img.dabin-coder.cn/image/image-20210910000456607.png) - - - -## Condition - -任意一个Java对象,都拥有一组监视器方法(定义在java.lang.Object上),主要包括wait()、wait(long timeout)、notify()以及notifyAll()方法,使用这些方法的前提是已经获取对象的锁,和 synchronized 配合使用。Condition接口也提供了类似Object的监视器方法,与Lock配合可以实现等待/通知模式。Condition是依赖Lock对象。 - -```java -Lock lock = new ReentrantLock(); -Condition condition = lock.newCondition(); -public void conditionWait() throws InterruptedException { - lock.lock(); - try { - condition.await(); - } finally { - lock.unlock(); - } -} -public void conditionSignal() throws InterruptedException { - lock.lock(); - try { - condition.signal(); - } finally { - lock.unlock(); - } -} -``` - -一般将Condition对象作为成员变量。当调用await()方法后,当前线程会释放锁进入等待队列。其他线程调用Condition对象的signal()方法,唤醒等待队列首节点的线程。 - -### 实现原理 - -每个Condition对象都包含着一个等待队列,如果一个线程成功获取了锁之后调用了Condition.await()方法,那么该线程将会释放同步状态、唤醒同步队列中的后继节点,然后构造成节点加入等待队列。只有当线程再次获取Condition相关联的锁之后,才能从await()方法返回。 - -![](http://img.dabin-coder.cn/image/condition-await.png) - -> 图片来源:Java并发编程的艺术 - -在Object的监视器模型上,一个对象拥有一个同步队列和等待队列。Lock通过AQS实现,AQS可以有多个Condition,所以Lock拥有一个同步队列和多个等待队列。 - -![](http://img.dabin-coder.cn/image/condition-wait-queue.png) - -> 图片来源:Java并发编程的艺术 - -线程获取了锁之后,调用Condition的signal()方法,会将等待队列的队首节点移到同步队列中,然后该节点的线程会尝试去获取同步状态。成功获取同步状态之后,线程将await()方法返回。 - -![](http://img.dabin-coder.cn/image/condition-signal.png) - -> 图片来源:Java并发编程的艺术 - - - -## 其他 - -### Daemon Thread - -在Java中有两类线程: - -- User Thread(用户线程) -- Daemon Thread(守护线程) - -只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。 - -Daemon的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是垃圾收集。 - -将线程转换为守护线程可以通过调用Thread对象的setDaemon(true)方法来实现。 - - - -## 参考资料 - -[线程中断](https://zhuanlan.zhihu.com/p/45667127) - -[synchronized实现原理](https://www.cnblogs.com/paddix/p/5367116.html) - -[指令重排导致单例模式失效](https://blog.csdn.net/jiyiqinlovexx/article/details/50989328) - - - -> 本文已经收录到github仓库,此仓库用于分享Java相关知识总结,包括Java基础、MySQL、Spring Boot、MyBatis、Redis、RabbitMQ、计算机网络、数据结构与算法等等,欢迎大家提pr和star! -> -> github地址:https://github.com/Tyson0314/Java-learning -> -> 如果github访问不了,可以访问gitee仓库。 -> -> gitee地址:https://gitee.com/tysondai/Java-learning diff --git "a/Java/\351\233\206\345\220\210.md" "b/Java/\351\233\206\345\220\210.md" deleted file mode 100644 index d626f12..0000000 --- "a/Java/\351\233\206\345\220\210.md" +++ /dev/null @@ -1,479 +0,0 @@ - - - - -- [ArrayList 简介](#arraylist-%E7%AE%80%E4%BB%8B) - - [Arraylist 和 Vector 的区别](#arraylist-%E5%92%8C-vector-%E7%9A%84%E5%8C%BA%E5%88%AB) - - [Arraylist 与 LinkedList 区别](#arraylist-%E4%B8%8E-linkedlist-%E5%8C%BA%E5%88%AB) -- [Map](#map) - - [HashMap](#hashmap) - - [hash算法](#hash%E7%AE%97%E6%B3%95) - - [resize](#resize) - - [put](#put) - - [红黑树](#%E7%BA%A2%E9%BB%91%E6%A0%91) - - [HashMap和HashTable](#hashmap%E5%92%8Chashtable) - - [LinkedHashMap](#linkedhashmap) - - [TreeMap](#treemap) -- [Set](#set) -- [Queue](#queue) -- [Iterator](#iterator) - - [ListIterator](#listiterator) -- [并发容器](#%E5%B9%B6%E5%8F%91%E5%AE%B9%E5%99%A8) - - [ConcurrentHashMap](#concurrenthashmap) - - [put](#put-1) - - [扩容](#%E6%89%A9%E5%AE%B9) - - [ConcurrentHashMap 和 Hashtable](#concurrenthashmap-%E5%92%8C-hashtable) - - [CopyOnWrite](#copyonwrite) - - [ConcurrentLinkedQueue](#concurrentlinkedqueue) - - [阻塞队列](#%E9%98%BB%E5%A1%9E%E9%98%9F%E5%88%97) - - [方法](#%E6%96%B9%E6%B3%95) - - [JDK提供的阻塞队列](#jdk%E6%8F%90%E4%BE%9B%E7%9A%84%E9%98%BB%E5%A1%9E%E9%98%9F%E5%88%97) - - [原理](#%E5%8E%9F%E7%90%86) - - - -## ArrayList 简介 - -`ArrayList` 的底层是动态数组,它的容量能动态增长。在添加大量元素前,应用可以使用`ensureCapacity`操作增加 `ArrayList` 实例的容量。ArrayList 继承了 AbstractList ,并实现了 List 接口。 - -### Arraylist 和 Vector 的区别 - -1. ArrayList在内存不够时默认是扩展50% + 1个,Vector是默认扩展1倍。 -2. Vector属于线程安全级别的,但是大多数情况下不使用Vector,因为操作Vector效率比较低。 - -### Arraylist 与 LinkedList 区别 - -1. ArrayList基于动态数组实现;LinkedList基于链表实现。 -2. 对于随机index访问的get和set方法,ArrayList的速度要优于LinkedList。因为ArrayList直接通过数组下标直接找到元素;LinkedList要移动指针遍历每个元素直到找到为止。 -3. 新增和删除元素,LinkedList的速度要优于ArrayList。因为ArrayList在新增和删除元素时,可能扩容和复制数组;LinkedList实例化对象需要时间外,只需要修改指针即可。 - -## Map - -以 Map 结尾的类都实现了 Map 接口,其他所有的类都实现了 Collection 接口。 - -![](http://img.dabin-coder.cn/image/Java-Collections.jpeg) - -### HashMap - -HashMap 使用数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的, 链表长度大于8(TREEIFY_THRESHOLD)时,会把链表转换为红黑树,红黑树节点个数小于6(UNTREEIFY_THRESHOLD)时才转化为链表,防止频繁的转化。 - -#### hash算法 - -Hash算法:取key的hashCode值、高位运算、取模运算。 - -``` -h=key.hashCode() //第一步 取hashCode值 -h^(h>>>16) //第二步 高位参与运算,减少冲突 -return h&(length-1); //第三步 取模运算 -``` - -在JDK1.8的实现中,优化了高位运算的算法,通过hashCode()的高16位异或低16位实现的:这么做可以在数组比较小的时候,也能保证考虑到高低位都参与到Hash的计算中,可以减少冲突,同时不会有太大的开销。 - -#### resize - -1.8扩容机制:当元素个数大于threshold时,会进行扩容,使用2倍容量的数组代替原有数组。采用尾插入的方式将原数组元素拷贝到新数组。1.8扩容之后链表元素相对位置没有变化,而1.7扩容之后链表元素会倒置。 - -1.7链表新节点采用的是头插法,这样在线程一扩容迁移元素时,会将元素顺序改变,导致两个线程中出现元素的相互指向而形成循环链表,1.8采用了尾插法,避免了这种情况的发生。 - -原数组的元素在重新计算hash之后,因为数组容量n变为2倍,那么n-1的mask范围在高位多1bit。在元素拷贝过程不需要重新计算元素在数组中的位置,只需要看看原来的hash值新增的那个bit是1还是0,是0的话索引没变,是1的话索引变成“原索引+oldCap”(根据`e.hash & (oldCap - 1) == 0`判断) 。这样可以省去重新计算hash值的时间,而且由于新增的1bit是0还是1可以认为是随机的,因此resize的过程会均匀的把之前的冲突的节点分散到新的bucket。 - -#### put - -put方法流程: - -1. 如果table没有初始化就先进行初始化过程 -2. 使用hash算法计算key的索引 -3. 判断索引处有没有存在元素,没有就直接插入 -4. 如果索引处存在元素,则遍历插入,有两种情况,一种是链表形式就直接遍历到尾端插入,一种是红黑树就按照红黑树结构插入 -5. 链表的数量大于阈值8,就要转换成红黑树的结构 -6. 添加成功后会检查是否需要扩容 - -![](http://img.dabin-coder.cn/image/hashmap-put.png) - -> 参考链接: -> -> http://www.importnew.com/20386.html -> -> https://www.cnblogs.com/yangming1996/p/7997468.html -> -> https://coolshell.cn/articles/9606.htm(HashMap 死循环) - - - -#### 红黑树 - -为什么使用红黑树而不使用AVL树? - -ConcurrentHashMap 在put的时候会加锁,使用红黑树插入速度更快,可以减少等待锁释放的时间。红黑树是对AVL树的优化,只要求部分平衡,用非严格的平衡来换取增删节点时候旋转次数的降低,提高了插入和删除的性能。 - -### HashMap和HashTable - -HashMap和Hashtable都实现了Map接口 - -1. HashMap可以接受为null的键值(key)和值(value),key为null的键值对放在下标为0的头结点的链表中,而Hashtable则不行。 -2. HashMap是非线程安全的,HashTable是线程安全的。Jdk1.5提供了ConcurrentHashMap,它是HashTable的替代。 -3. Hashtable很多方法是同步方法,在单线程环境下它比HashMap要慢。 -4. 哈希值的使用不同,HashTable直接使用对象的hashCode。而HashMap重新计算hash值。 - -### LinkedHashMap - -HashMap是无序的,迭代HashMap所得到元素的顺序并不是它们最初放到HashMap的顺序,即不能保持它们的插入顺序。 - -LinkedHashMap继承于HashMap,是HashMap和LinkedList的融合体,具备两者的特性。每次put操作都会将entry插入到双向链表的尾部。 - -![linkedhashmap](http://img.dabin-coder.cn/image/linkedhashmap.png) - -### TreeMap - -TreeMap是一个能比较元素大小的Map集合,会对传入的key进行了大小排序。可以使用元素的自然顺序,也可以使用集合中自定义的比较器来进行排序。 - -```java -public class TreeMap - extends AbstractMap - implements NavigableMap, Cloneable, java.io.Serializable { -} -``` - -TreeMap 的继承结构: - -![](http://img.dabin-coder.cn/image/image-20210905215046510.png) - -**TreeMap的特点:** - -1. TreeMap是有序的key-value集合,通过红黑树实现。根据键的自然顺序进行排序或根据提供的Comparator进行排序。 -2. TreeMap继承了AbstractMap,实现了NavigableMap接口,支持一系列的导航方法,给定具体搜索目标,可以返回最接近的匹配项。如floorEntry()、ceilingEntry()分别返回小于等于、大于等于给定键关联的Map.Entry()对象,不存在则返回null。lowerKey()、floorKey、ceilingKey、higherKey()只返回关联的key。 - -## Set - -HashSet 基于 HashMap 实现。放入HashSet中的元素实际上由HashMap的key来保存,而HashMap的value则存储了一个静态的Object对象。 - -```java -public class HashSet - extends AbstractSet - implements Set, Cloneable, java.io.Serializable { - static final long serialVersionUID = -5024744406713321676L; - - private transient HashMap map; //基于HashMap实现 - //... -} -``` - - - -## Queue - -ArrayDeque实现了双端队列,内部使用循环数组实现,默认大小为16。 - -特点: - -1. 在两端添加、删除元素的效率较高 - -2. 根据元素内容查找和删除的效率比较低。 - -3. 没有索引位置的概念,不能根据索引位置进行操作。 - -ArrayDeque和LinkedList都实现了Deque接口,如果只需要从两端进行操作,ArrayDeque效率更高一些。如果同时需要根据索引位置进行操作,或者经常需要在中间进行插入和删除(LinkedList有相应的 api,如add(int index, E e)),则应该选LinkedList。 - -ArrayDeque和LinkedList都是线程不安全的,可以使用Collections工具类中synchronizedXxx()转换成线程同步。 - -## Iterator - -Iterator模式用同一种逻辑来遍历集合。它可以把访问逻辑从不同类型的集合类中抽象出来,不需要了解集合内部实现便可以遍历集合元素,集合是数组还是链表实现的无所谓,统一使用 Iterator 提供的接口去遍历。 - -主要有三个方法:hasNext()、next()和remove()。 - -```java - for(Iterator it = c.iterater(); it.hasNext(); ) { ... } -``` - -### ListIterator - -Iterator的增强版,增加了以下功能: - -1. ListIterator有add()方法,可以向List中添加对象。 - -2. hasPrevious()和previous()方法,可以实现逆向(顺序向前)遍历。 -3. nextIndex()和previousIndex()定位索引位置。 -4. set() 实现对象的修改。 - -```java -public interface ListIterator extends Iterator { - boolean hasNext(); - - E next(); - - boolean hasPrevious(); - - E previous(); - - int nextIndex(); - - int previousIndex(); - - void remove(); - - void set(E var1); - - void add(E var1); -} -``` - - - -## 并发容器 - -JDK 提供的这些容器大部分在 `java.util.concurrent` 包中。 - -- **ConcurrentHashMap:** 线程安全的 HashMap -- **CopyOnWriteArrayList:** 线程安全的 List,在读多写少的场合性能非常好,远远好于 Vector. -- **ConcurrentLinkedQueue:** 高效的并发队列,使用链表实现。可以看做一个线程安全的 LinkedList,这是一个非阻塞队列。 -- **BlockingQueue:** 阻塞队列接口,JDK 内部通过链表、数组等方式实现了这个接口。非常适合用于作为数据共享的通道。 -- **ConcurrentSkipListMap:** 跳表的实现。使用跳表的数据结构进行快速查找。 - -### ConcurrentHashMap - -多线程环境下,使用Hashmap进行put操作会引起死循环,应该使用支持多线程的 ConcurrentHashMap。 - -```java -private static final int MAXIMUM_CAPACITY = 1 << 30; -private static final int DEFAULT_CAPACITY = 16; -static final int TREEIFY_THRESHOLD = 8; -static final int UNTREEIFY_THRESHOLD = 6; -static final int MIN_TREEIFY_CAPACITY = 64; -static final int MOVED = -1; // 表示正在转移 -static final int TREEBIN = -2; // 表示已经转换成树 -static final int RESERVED = -3; // hash for transient reservations -static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash -transient volatile Node[] table;//默认没初始化的数组,用来保存元素 -private transient volatile Node[] nextTable;//转移的时候用的数组 -/** - * 用来控制表初始化和扩容的,默认值为0,当在初始化的时候指定了大小,这会将这个大小保存在sizeCtl中,大小为数组的0.75 - * 当为负的时候,说明表正在初始化或扩张, - * -1表示初始化 - * -(1+n) n:表示活动的扩张线程 - */ -private transient volatile int sizeCtl; -​```JDK1.7中的ConcurrentHashmap主要使用Segment来实现减小锁粒度。Segment继承了ReentrantLock,所以它就是一种可重入锁。默认分配16个segment,允许16个线程并发执行。Segment维护了一个HashEntry**数组**,对于同一个Segment的操作才需考虑线程同步,不同的Segment则无需考虑。当要统计size时,比较统计前后modCount是否发生变化。如果没有变化,则直接返回size。否则,需要依次锁住所有的Segment来计算。当长度过长碰撞会很频繁,链表的增改删查操作都会消耗很长的时间,影响性能。 -``` - -JDK1.8 ConcurrentHashMap取消了segment分段锁,而采用CAS和synchronized来保证并发安全。数据结构采用数组+链表/红黑二叉树。synchronized只锁定当前链表或红黑二叉树的首节点,相比1.7锁定HashEntry数组,锁粒度更小,支持更高的并发量。当链表长度过长时,Node会转换成TreeNode,提高查找速度。 - -#### put - -在put的时候需要锁住Segment,保证并发安全。调用get的时候不加锁,因为node数组成员val和指针next是用volatile修饰的,更改后的值会立刻刷新到主存中,保证了可见性,node数组table也用volatile修饰,保证在运行过程对其他线程具有可见性。 - -```java -transient volatile Node[] table; - -static class Node implements Map.Entry { - volatile V val; - volatile Node next; -} -``` - -put 操作流程: - -1. 如果table没有初始化就先进行初始化过程 -2. 使用hash算法计算key的位置 -3. 如果这个位置为空则直接CAS插入,如果不为空的话,则取出这个节点来 -4. 如果取出来的节点的hash值是MOVED(-1)的话,则表示当前正在对这个数组进行扩容,复制到新的数组,则当前线程也去帮助复制 -5. 如果这个节点,不为空,也不在扩容,则通过synchronized来加锁,进行添加操作,这里有两种情况,一种是链表形式就直接遍历到尾端插入或者覆盖掉相同的key,一种是红黑树就按照红黑树结构插入 -6. 链表的数量大于阈值8,就会转换成红黑树的结构或者进行扩容(table长度小于64) -7. 添加成功后会检查是否需要扩容 - -#### 扩容 - -数组扩容transfer方法中会设置一个步长,表示一个线程处理的数组长度,最小值是16。在一个步长范围内只有一个线程会对其进行复制移动操作。 - -#### ConcurrentHashMap 和 Hashtable - -区别: - -1. Hashtable通过使用synchronized修饰方法的方式来实现多线程同步,因此,Hashtable的同步会锁住整个数组。在高并发的情况下,性能会非常差。ConcurrentHashMap采用了更细粒度的锁来提高在并发情况下的效率。注:Synchronized容器(同步容器)也是通过synchronized关键字来实现线程安全,在使用的时候会对所有的数据加锁。 -2. Hashtable默认的大小为11,当达到阈值后,每次按照下面的公式对容量进行扩充:newCapacity = oldCapacity * 2 + 1。ConcurrentHashMap默认大小是16,扩容时容量扩大为原来的2倍。 - -ConcurrentHashMap 和 Hashtable 的key和value不能为null。 - -HashMap.java 部分源码: - -```java - static final int hash(Object key) { - int h; - return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);//key为null时,hash值为0 - } -``` - -ConcurrentHashMap.java 部分源码: - -```java - /** Implementation for put and putIfAbsent */ - final V putVal(K key, V value, boolean onlyIfAbsent) { - if (key == null || value == null) throw new NullPointerException(); - int hash = spread(key.hashCode()); - int binCount = 0; - ...... - } -``` - -ConcurrentHashmap和Hashtable都支持并发,当你通过get(k)获取对应的value时,如果获取到的是null时,无法判断是key 对应的 value 为 null,还是这个 key 从来没有做过映射,在多线程里面是模糊不清的,所以不让put null。HashMap用于非并发场景,可以通过contains(key)来判断是否存在key。而支持并发的Map在调用m.get(key)之后,再调用m.contains(key),两个调用之间可能有其他线程删除了key,得到的结果不准确,产生多线程安全问题。因此ConcurrentHashMap 和 Hashtable 的key和value不能为null。 - -### CopyOnWrite - -写时复制。当我们往容器添加元素时,不直接往容器添加,而是先将当前容器进行复制,复制出一个新的容器,然后往新的容器添加元素,添加完元素之后,再将原容器的引用指向新容器。这样做的好处就是可以对CopyOnWrite容器进行并发的读而不需要加锁,因为当前容器不会被修改。 - -```java - public boolean add(E e) { - final ReentrantLock lock = this.lock; - lock.lock(); //add方法需要加锁 - 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(); - } - } -``` - -从JDK1.5开始Java并发包里提供了两个使用CopyOnWrite机制实现的并发容器,它们是CopyOnWriteArrayList和CopyOnWriteArraySet。 - -CopyOnWriteArrayList中add方法添加的时候是需要加锁的,保证同步,避免了多线程写的时候复制出多个副本。读的时候不需要加锁,如果读的时候有其他线程正在向CopyOnWriteArrayList添加数据,还是可以读到旧的数据。 - -**缺点:** - -- 内存占用问题。由于CopyOnWrite的写时复制机制,在进行写操作的时候,内存里会同时驻扎两个对象的内存。 -- CopyOnWrite容器不能保证数据的实时一致性,可能读取到旧数据。 - -### ConcurrentLinkedQueue - -非阻塞队列。高效的并发队列,使用链表实现。可以看做一个线程安全的 LinkedList,通过 CAS 操作实现。 - -如果对队列加锁的成本较高则适合使用无锁的 ConcurrentLinkedQueue 来替代。适合在对性能要求相对较高,同时有多个线程对队列进行读写的场景。 - -**非阻塞队列中的几种主要方法:** -add(E e) : 将元素e插入到队列末尾,如果插入成功,则返回true;如果插入失败(即队列已满),则会抛出异常; -remove() :移除队首元素,若移除成功,则返回true;如果移除失败(队列为空),则会抛出异常; -offer(E e) :将元素e插入到队列末尾,如果插入成功,则返回true;如果插入失败(即队列已满),则返回false; -poll() :移除并获取队首元素,若成功,则返回队首元素;否则返回null; -peek() :获取队首元素,若成功,则返回队首元素;否则返回null - -对于非阻塞队列,一般情况下建议使用offer、poll和peek三个方法,不建议使用add和remove方法。因为使用offer、poll和peek三个方法可以通过返回值判断操作成功与否,而使用add和remove方法却不能达到这样的效果。 - -### 阻塞队列 - -阻塞队列是java.util.concurrent包下重要的数据结构,BlockingQueue提供了线程安全的队列访问方式:当阻塞队列进行插入数据时,如果队列已满,线程将会阻塞等待直到队列非满;从阻塞队列取数据时,如果队列已空,线程将会阻塞等待直到队列非空。并发包下很多高级同步类的实现都是基于BlockingQueue实现的。BlockingQueue 适合用于作为数据共享的通道。 - -使用阻塞算法的队列可以用一个锁(入队和出队用同一把锁)或两个锁(入队和出队用不同的锁)等方式来实现。 - -阻塞队列和一般的队列的区别就在于: - -1. 多线程支持,多个线程可以安全的访问队列 -2. 阻塞操作,当队列为空的时候,消费线程会阻塞等待队列不为空;当队列满了的时候,生产线程就会阻塞直到队列不满。 - -#### 方法 - -| 方法\处理方式 | 抛出异常 | 返回特殊值 | 一直阻塞 | 超时退出 | -| ------------- | --------- | ---------- | -------- | ------------------ | -| 插入方法 | add(e) | offer(e) | put(e) | offer(e,time,unit) | -| 移除方法 | remove() | poll() | take() | poll(time,unit) | -| 检查方法 | element() | peek() | 不可用 | 不可用 | - -#### JDK提供的阻塞队列 - -JDK 7 提供了7个阻塞队列,如下 - -1、**ArrayBlockingQueue** - -有界阻塞队列,底层采用数组实现。ArrayBlockingQueue 一旦创建,容量不能改变。其并发控制采用可重入锁来控制,不管是插入操作还是读取操作,都需要获取到锁才能进行操作。此队列按照先进先出(FIFO)的原则对元素进行排序。默认情况下不能保证线程访问队列的公平性,参数`fair`可用于设置线程是否公平访问队列。为了保证公平性,通常会降低吞吐量。 - -```java -private static ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(10,true);//fair -``` - -2、**LinkedBlockingQueue** - -LinkedBlockingQueue是一个用单向链表实现的有界阻塞队列,可以当做无界队列也可以当做有界队列来使用。通常在创建 LinkedBlockingQueue 对象时,会指定队列最大的容量。此队列的默认和最大长度为`Integer.MAX_VALUE`。此队列按照先进先出的原则对元素进行排序。与 ArrayBlockingQueue 相比起来具有更高的吞吐量。 - -3、**PriorityBlockingQueue** - -支持优先级的**无界**阻塞队列。默认情况下元素采取自然顺序升序排列。也可以自定义类实现`compareTo()`方法来指定元素排序规则,或者初始化PriorityBlockingQueue时,指定构造参数`Comparator`来进行排序。 - -PriorityBlockingQueue 只能指定初始的队列大小,后面插入元素的时候,如果空间不够的话会**自动扩容**。 - -PriorityQueue 的线程安全版本。不可以插入 null 值,同时,插入队列的对象必须是可比较大小的(comparable),否则报 ClassCastException 异常。它的插入操作 put 方法不会 block,因为它是无界队列(take 方法在队列为空的时候会阻塞)。 - -4、**DelayQueue** - -支持延时获取元素的无界阻塞队列。队列使用PriorityBlockingQueue来实现。队列中的元素必须实现Delayed接口,在创建元素时可以指定多久才能从队列中获取当前元素。只有在延迟期满时才能从队列中提取元素。 - -5、**SynchronousQueue** - -不存储元素的阻塞队列,每一个put必须等待一个take操作,否则不能继续添加元素。支持公平访问队列。 - -SynchronousQueue可以看成是一个传球手,负责把生产者线程处理的数据直接传递给消费者线程。队列本身不存储任何元素,非常适合传递性场景。SynchronousQueue的吞吐量高于LinkedBlockingQueue和ArrayBlockingQueue。 - -6、**LinkedTransferQueue** - -由链表结构组成的无界阻塞TransferQueue队列。相对于其他阻塞队列,多了tryTransfer和transfer方法。 - -transfer方法:如果当前有消费者正在等待接收元素(take或者待时间限制的poll方法),transfer可以把生产者传入的元素立刻传给消费者。如果没有消费者等待接收元素,则将元素放在队列的tail节点,并等到该元素被消费者消费了才返回。 - -tryTransfer方法:用来试探生产者传入的元素能否直接传给消费者。如果没有消费者在等待,则返回false。和上述方法的区别是该方法无论消费者是否接收,方法立即返回。而transfer方法是必须等到消费者消费了才返回。 - -#### 原理 - -JDK使用通知模式实现阻塞队列。所谓通知模式,就是当生产者往满的队列里添加元素时会阻塞生产者,当消费者消费了一个队列中的元素后,会通知生产者当前队列可用。 - -ArrayBlockingQueue使用Condition来实现: - -```java -private final Condition notEmpty; -private final Condition notFull; - -public ArrayBlockingQueue(int capacity, boolean fair) { - if (capacity <= 0) - throw new IllegalArgumentException(); - this.items = new Object[capacity]; - lock = new ReentrantLock(fair); - notEmpty = lock.newCondition(); - notFull = lock.newCondition(); -} - -public E take() throws InterruptedException { - final ReentrantLock lock = this.lock; - lock.lockInterruptibly(); - try { - while (count == 0) // 队列为空时,阻塞当前消费者 - notEmpty.await(); - return dequeue(); - } finally { - lock.unlock(); - } -} - -public void put(E e) throws InterruptedException { - checkNotNull(e); - final ReentrantLock lock = this.lock; - lock.lockInterruptibly(); - try { - while (count == items.length) - notFull.await(); - enqueue(e); - } finally { - lock.unlock(); - } -} - -private void enqueue(E x) { - final Object[] items = this.items; - items[putIndex] = x; - if (++putIndex == items.length) - putIndex = 0; - count++; - notEmpty.signal(); // 队列不为空时,通知消费者获取元素 -} -``` - diff --git "a/Redis/\347\274\223\345\255\230\347\251\277\351\200\217\343\200\201\347\274\223\345\255\230\351\233\252\345\264\251\343\200\201\347\274\223\345\255\230\345\207\273\347\251\277.md" "b/Redis/\347\274\223\345\255\230\347\251\277\351\200\217\343\200\201\347\274\223\345\255\230\351\233\252\345\264\251\343\200\201\347\274\223\345\255\230\345\207\273\347\251\277.md" deleted file mode 100644 index ca08607..0000000 --- "a/Redis/\347\274\223\345\255\230\347\251\277\351\200\217\343\200\201\347\274\223\345\255\230\351\233\252\345\264\251\343\200\201\347\274\223\345\255\230\345\207\273\347\251\277.md" +++ /dev/null @@ -1,60 +0,0 @@ - - -**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* - -- [缓存穿透](#%E7%BC%93%E5%AD%98%E7%A9%BF%E9%80%8F) -- [缓存雪崩](#%E7%BC%93%E5%AD%98%E9%9B%AA%E5%B4%A9) -- [缓存击穿](#%E7%BC%93%E5%AD%98%E5%87%BB%E7%A9%BF) - - - -# 缓存穿透 - -缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时被动写的,如果从DB查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到DB去查询,失去了缓存的意义。在流量大时,可能DB就挂掉了。 - -1. 缓存空值,不会查数据库 -2. 采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,查询不存在的数据会被这个bitmap拦截掉,从而避免了对DB的查询压力。 - -布隆可以看成数据库的缩略版,用来判定是否存在值。启动的时候过滤器是要全表扫描的,数据库数据发生变化的时候会更新布隆过滤器。 - -布隆过滤器的原理:当一个元素被加入集合时,通过K个散列函数将这个元素映射成一个位数组中的K个点,把它们置为1。查询时,将元素通过散列函数映射之后会得到k个点,如果这些点有任何一个0,则被检元素一定不在,直接返回;如果都是1,则查询元素很可能存在,就会去查询redis和数据库。 - -![](E:/project/java/learn/Java-learning/img/bloom-filter.jpg) - -> 图片来源网络 - - - -# 缓存雪崩 - -缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重挂掉。 - -解决方法:在原有的失效时间基础上增加一个随机值,使得过期时间分散一些。 - -# 缓存击穿 - -缓存击穿:大量的请求同时查询一个 key 时,此时这个key正好失效了,就会导致大量的请求都落到数据库。缓存击穿是查询缓存中失效的 key,而缓存穿透是查询不存在的 key。 - -解决方法:加互斥锁(redis分布式锁或者使用ReentrantLock),第一个请求的线程可以拿到锁,拿到锁的线程查询到了数据之后设置缓存,其他的线程获取锁失败会等待50ms然后重新到缓存取数据,这样便可以避免大量的请求落到数据库。 - -```java -public String get(key) { - String value = redis.get(key); - if (value == null) { //代表缓存值过期 - String key_mutex = "mutext:key:" + key; - //设置30s的超时,防止del操作失败的时候,下次缓存过期一直不能load db - if (redis.set(key_mutex, 1, 'NX', 'PX', 30000) == 1) { //代表设置成功 - value = db.get(key); - redis.set(key, value, expire_secs); - redis.del(key_mutex); - } else { //这个时候代表同时候的其他线程已经load db并回设到缓存了,这时候重试获取缓存值即可 - sleep(50); - get(key); //重试 - } - } else { - return value; - } - } -``` - -SETNX:只有不存在的时候才设置,可以利用它来实现锁的效果。 \ No newline at end of file diff --git a/about/introduce.md b/about/introduce.md new file mode 100644 index 0000000..c339439 --- /dev/null +++ b/about/introduce.md @@ -0,0 +1,12 @@ +大家好,我是大彬~ + +大彬是非科班转码选手,本科专业是材料物理,大三开启转码之路,**自学Java**,校招斩获京东、携程等大厂offer。 + +作为一名转码选手,深感这一路的不易。坚持分享自学Java经历、后端技术和面试经验等,希望能帮助到更多的小伙伴,**我踩过的坑你们不要再踩**。想与大彬交流的话,可以添加大彬的私人微信~ + +![](http://img.dabin-coder.cn/image/公众号.jpg) + +著有『 [**大厂面试**](http://mp.weixin.qq.com/s?__biz=Mzg2OTY1NzY0MQ==&mid=2247488109&idx=1&sn=fd3ec79355505543a295df15845b936f&chksm=ce98ef2bf9ef663de7f5da18a5fae346672a758b4c43a9c8f34edbe20bca529adad4c6f73804&scene=21#wechat_redirect) 』专栏,已经帮助不少小伙伴上岸互联网大厂了,干货满满。 + +想要加群学习交流的话,点击[**技术交流群**](http://mp.weixin.qq.com/s?__biz=Mzg2OTY1NzY0MQ==&mid=2247486402&idx=1&sn=2165f1776dfcd9a08215f4f8af33a52a&chksm=ce98f684f9ef7f928f724a32dd2f2c342dbe896651562b3de77544fa021b6a23b34970a0bf07&scene=21#wechat_redirect) + diff --git a/advance/design-pattern.md b/advance/design-pattern.md new file mode 100644 index 0000000..ebbd8e1 --- /dev/null +++ b/advance/design-pattern.md @@ -0,0 +1,819 @@ +## 设计模式的六大原则 + +- 开闭原则:对扩展开放,对修改关闭,多使用抽象类和接口。 +- 里氏替换原则:基类可以被子类替换,使用抽象类继承,不使用具体类继承。 +- 依赖倒转原则:要依赖于抽象,不要依赖于具体,针对接口编程,不针对实现编程。 +- 接口隔离原则:使用多个隔离的接口,比使用单个接口好,建立最小的接口。 +- 迪米特法则:一个软件实体应当尽可能少地与其他实体发生相互作用,通过中间类建立联系。 +- 合成复用原则:尽量使用合成/聚合,而不是使用继承。 + +## 单例模式 + +需要对实例字段使用线程安全的延迟初始化,使用双重检查锁定的方案;需要对静态字段使用线程安全的延迟初始化,使用静态内部类的方案。 + +### 饿汉模式 + +JVM在类的初始化阶段,会执行类的静态方法。在执行类的初始化期间,JVM会去获取Class对象的锁。这个锁可以同步多个线程对同一个类的初始化。 + +饿汉模式只在类加载的时候创建一次实例,没有多线程同步的问题。单例没有用到也会被创建,而且在类加载之后就被创建,内存就被浪费了。 + +``` +public class Singleton { + private static Singleton instance = new Singleton(); + private Singleton() {} + public static Singleton newInstance() { + return instance; + } +} +``` +### 双重检查锁定 + +双重校验锁先判断 instance 是否已经被实例化,如果没有被实例化,那么才对实例化语句进行加锁。 + +instance使用static修饰的原因:getInstance为静态方法,因为静态方法的内部不能直接使用非静态变量,只有静态成员才能在没有创建对象时进行初始化,所以返回的这个实例必须是静态的。 + +``` +public class Singleton { + private static volatile Singleton instance = null; //volatile + private Singleton(){} + public static Singleton getInstance() { + if (instance == null) { + synchronized (Singleton.class) { + if (instance == null) { + instance = new Singleton(); + } + } + } + return instance; + } +} +``` +为什么两次判断`instance == null`: + +| Time | Thread A | Thread B | +| ---- | -------------------- | -------------------- | +| T1 | 检查到`instance`为空 | | +| T2 | | 检查到`instance`为空 | +| T3 | | 初始化对象`A` | +| T4 | | 返回对象`A` | +| T5 | 初始化对象`B` | | +| T6 | 返回对象`B` | | + +`new Singleton()`会执行三个动作:分配内存空间、初始化对象和对象引用指向内存地址。 + +```java +memory = allocate();  // 1:分配对象的内存空间 +ctorInstance(memory);  // 2:初始化对象 +instance = memory;   // 3:设置instance指向刚分配的内存地址 +``` + +由于指令重排优化的存在,导致初始化对象和将对象引用指向内存地址的顺序是不确定的。在某个线程创建单例对象时,会为该对象分配了内存空间并将对象的字段设置为默认值。此时就可以将分配的内存地址赋值给instance字段了,然而该对象可能还没有初始化。若紧接着另外一个线程来调用getInstance,取到的是未初始化的对象,程序就会出错。volatile 可以禁止指令重排序,保证了先初始化对象再赋值给instance变量。 + +| Time | Thread A | Thread B | +| :--- | :----------------------- | :--------------------------------------- | +| T1 | 检查到`instance`为空 | | +| T2 | 获取锁 | | +| T3 | 再次检查到`instance`为空 | | +| T4 | 为`instance`分配内存空间 | | +| T5 | 将`instance`指向内存空间 | | +| T6 | | 检查到`instance`不为空 | +| T7 | | 访问`instance`(此时对象还未完成初始化) | +| T8 | 初始化`instance` | | + +### 静态内部类 + +它与饿汉模式一样,也是利用了类初始化机制,因此不存在多线程并发的问题。不一样的是,它是在内部类里面去创建对象实例。这样的话,只要应用中不使用内部类,JVM就不会去加载这个单例类,也就不会创建单例对象,从而实现懒汉式的延迟加载。也就是说这种方式可以同时保证延迟加载和线程安全。 + +![](http://img.dabin-coder.cn/image/singleton-class-init.png) + +基于类初始化的方案的实现代码更简洁。 + +``` +public class Instance { + private static class InstanceHolder { + public static Instance instance = new Instance(); + } + private Instance() {} + public static Instance getInstance() { + return InstanceHolder.instance ;  // 这里将导致InstanceHolder类被初始化 + } +} +``` +但基于volatile的双重检查锁定的方案有一个额外的优势:除了可以对静态字段实现延迟初始化外,还可以对实例字段实现延迟初始化。字段延迟初始化降低了初始化类或创建实例的开销,但增加了访问被延迟初始化的字段的开销。在大多数时候,正常的初始化要优于延迟初始化。 + +## 工厂模式 + +工厂模式是用来封装对象的创建。 + +### 简单工厂模式 + +把实例化的操作单独放到一个类中,这个类就成为简单工厂类,让简单工厂类来决定应该用哪个具体子类来实例化,这样做能把客户类和具体子类的实现解耦,客户类不再需要知道有哪些子类以及应当实例化哪个子类。 + +```java + public class ShapeFactory { + public static final String TAG = "ShapeFactory"; + public static Shape getShape(String type) { + Shape shape = null; + if (type.equalsIgnoreCase("circle")) { + shape = new CircleShape(); + } else if (type.equalsIgnoreCase("rect")) { + shape = new RectShape(); + } else if (type.equalsIgnoreCase("triangle")) { + shape = new TriangleShape(); + } + return shape; + } + } +``` + +优点:只需要一个工厂创建对象,代码量少。 + +缺点:系统扩展困难,新增产品需要修改工厂逻辑,当产品较多时,会造成工厂逻辑过于复杂,不利于系统扩展和维护。 + +### 工厂方法模式 + +针对不同的对象提供不同的工厂。每个对象都有一个与之对应的工厂。 + +```java +public interface Reader { + void read(); +} + +public class JpgReader implements Reader { + @Override + public void read() { + System.out.print("read jpg"); + } +} + +public class PngReader implements Reader { + @Override + public void read() { + System.out.print("read png"); + } +} + +public interface ReaderFactory { + Reader getReader(); +} + +public class JpgReaderFactory implements ReaderFactory { + @Override + public Reader getReader() { + return new JpgReader(); + } +} + +public class PngReaderFactory implements ReaderFactory { + @Override + public Reader getReader() { + return new PngReader(); + } +} +``` + +客户端通过子类来指定创建对应的对象。 + +```java +ReaderFactory factory=new JpgReaderFactory(); +Reader reader=factory.getReader(); +reader.read(); +``` + +优点:增加新的产品类时无须修改现有系统,只需增加新产品和对应的工厂类即可。 + +### 抽象工厂模式 + +抽象工厂模式创建的是对象家族,也就是很多对象而不是一个对象,并且这些对象是相关的,也就是说必须一起创建出来。而工厂方法模式只是用于创建一个对象,这和抽象工厂模式有很大不同。 + +多了一层抽象,减少了工厂的数量(HpMouseFactory和HpKeyboFactory合并为HpFactory)。 + +![](http://img.dabin-coder.cn/image/抽象工厂.png) + + + +## 模板模式 + +模板模式:一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。 这种类型的设计模式属于行为型模式。定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。 + +**模板模式**主要由抽象模板(Abstract Template)角色和具体模板(Concrete Template)角色组成。 + +- 抽象模板(Abstract Template): 定义了一个或多个抽象操作,以便让子类实现。这些抽象操作叫做基本操作,它们是一个顶级逻辑的组成步骤;定义并实现了一个模板方法。这个模板方法一般是一个具体方法,它给出了一个顶级逻辑的骨架,而逻辑的组成步骤在相应的抽象操作中,推迟到子类实现。顶级逻辑也有可能调用一些具体方法。 +- 具体模板(Concrete Template): 实现父类所定义的一个或多个抽象方法,它们是一个顶级逻辑的组成步骤;每一个抽象模板角色都可以有任意多个具体模板角色与之对应,而每一个具体模板角色都可以给出这些抽象方法(也就是顶级逻辑的组成步骤)的不同实现,从而使得顶级逻辑的实现各不相同。 + +示例图如下: + +![](http://img.dabin-coder.cn/image/模板方法.jpg) + +以游戏为例。创建一个抽象类,它的模板方法被设置为 final,这样它就不会被重写。 + +```java +public abstract class Game { + abstract void initialize(); + abstract void startPlay(); + abstract void endPlay(); + + //模板 + public final void play(){ + //初始化游戏 + initialize(); + + //开始游戏 + startPlay(); + + //结束游戏 + endPlay(); + } +} +``` + +Football类: + +```java +public class Football extends Game { + + @Override + void endPlay() { + System.out.println("Football Game Finished!"); + } + + @Override + void initialize() { + System.out.println("Football Game Initialized! Start playing."); + } + + @Override + void startPlay() { + System.out.println("Football Game Started. Enjoy the game!"); + } +} +``` + +使用Game的模板方法 play() 来演示游戏的定义方式。 + +```java +public class TemplatePatternDemo { + public static void main(String[] args) { + + Game game = new Cricket(); + game.play(); + System.out.println(); + game = new Football(); + game.play(); + } +} +``` + +**模板模式优点** : + +1. 封装不变部分,扩展可变部分。 +2. 提取公共代码,便于维护。 +3. 行为由父类控制,子类实现。 + +**模板模式缺点**: + +- 每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。 + +## 策略模式 + +策略模式(Strategy Pattern)属于对象的行为模式。其用意是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。 + +其主要目的是通过定义相似的算法,替换if else 语句写法,并且可以随时相互替换。 + +**策略模式**主要由这三个角色组成,环境角色(Context)、抽象策略角色(Strategy)和具体策略角色(ConcreteStrategy)。 + +- 环境角色(Context):持有一个策略类的引用,提供给客户端使用。 +- 抽象策略角色(Strategy):这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。 +- 具体策略角色(ConcreteStrategy):包装了相关的算法或行为。 + +示例图如下: + +![](http://img.dabin-coder.cn/image/策略模式.png) + +以计算器为例,如果我们想得到两个数字相加的和,我们需要用到“+”符号,得到相减的差,需要用到“-”符号等等。虽然我们可以通过字符串比较使用if/else写成通用方法,但是计算的符号每次增加,我们就不得不加在原先的方法中进行增加相应的代码,如果后续计算方法增加、修改或删除,那么会使后续的维护变得困难。 + +但是在这些方法中,我们发现其基本方法是固定的,这时我们就可以通过策略模式来进行开发,可以有效避免通过if/else来进行判断,即使后续增加其他的计算规则也可灵活进行调整。 + +首先定义一个抽象策略角色,并拥有一个计算的方法。 + +```java +interface CalculateStrategy { + int doOperation(int num1, int num2); +} +``` + +然后再定义加减乘除这些具体策略角色并实现方法。代码如下: + +```java +class OperationAdd implements CalculateStrategy { + @Override + public int doOperation(int num1, int num2) { + return num1 + num2; + } +} + +class OperationSub implements CalculateStrategy { + @Override + public int doOperation(int num1, int num2) { + return num1 - num2; + } +} + +class OperationMul implements CalculateStrategy { + @Override + public int doOperation(int num1, int num2) { + return num1 * num2; + } +} + +class OperationDiv implements CalculateStrategy { + @Override + public int doOperation(int num1, int num2) { + return num1 / num2; + } +} +``` + +最后在定义一个环境角色,提供一个计算的接口供客户端使用。代码如下: + +```java +class CalculatorContext { + private CalculateStrategy strategy; + + public CalculatorContext(CalculateStrategy strategy) { + this.strategy = strategy; + } + + public int executeStrategy(int num1, int num2) { + return strategy.doOperation(num1, num2); + } +} +``` + +测试代码如下: + +```java +public static void main(String[] args) { + int a=4,b=2; + CalculatorContext context = new CalculatorContext(new OperationAdd()); + System.out.println("a + b = "+context.executeStrategy(a, b)); + + CalculatorContext context2 = new CalculatorContext(new OperationSub()); + System.out.println("a - b = "+context2.executeStrategy(a, b)); + + CalculatorContext context3 = new CalculatorContext(new OperationMul()); + System.out.println("a * b = "+context3.executeStrategy(a, b)); + + CalculatorContext context4 = new CalculatorContext(new OperationDiv()); + System.out.println("a / b = "+context4.executeStrategy(a, b)); +} +``` + +**策略模式优点:** + +- 扩展性好,可以在不修改对象结构的情况下,为新的算法进行添加新的类进行实现; +- 灵活性好,可以对算法进行自由切换; + +**策略模式缺点:** + +- 使用策略类变多,会增加系统的复杂度。; +- 客户端必须知道所有的策略类才能进行调用; + +**使用场景:** + +- 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为; + 一个系统需要动态地在几种算法中选择一种; +- 如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现; + +## 责任链模式 + +为了避免请求发送者与多个请求处理者耦合在一起,于是将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。 + +在责任链模式中,客户只需要将请求发送到责任链上即可,无须关心请求的处理细节和请求的传递过程,请求会自动进行传递。所以责任链将请求的发送者和请求的处理者解耦了。 + +**责任链模式是一种对象行为型模式,其主要优点如下。** + +1. 降低了对象之间的耦合度。该模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者也无须拥有对方的明确信息。 +2. 增强了系统的可扩展性。可以根据需要增加新的请求处理类,满足开闭原则。 +3. 增强了给对象指派职责的灵活性。当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任。 +4. 责任链简化了对象之间的连接。每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。 +5. 责任分担。每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。 + +代码实现如下: + +请假条类 + +```java +public class LeaveRequest { + //姓名 + private String name; + // 请假天数 + private int num; + // 请假内容 + private String content; + + public LeaveRequest(String name, int num, String content) { + this.name = name; + this.num = num; + this.content = content; + } + + public String getName() { + return name; + } + + public int getNum() { + return num; + } + + public String getContent() { + return content; + } + +} +``` + +处理类 + +```java +public abstract class Handler { + protected final static int NUM_ONE = 1; + protected final static int NUM_THREE = 3; + protected final static int NUM_SEVEN = 7; + + //该领导处理的请假天数区间 + private int numStart; + private int numEnd; + + + //领导上还有领导 + private Handler nextHandler; + + //设置请假天数范围 + public Handler(int numStart) { + this.numStart = numStart; + } + + //设置请假天数范围 + public Handler(int numStart, int numEnd) { + this.numStart = numStart; + this.numEnd = numEnd; + } + + //设置上级领导 + public void setNextHandler(Handler nextHandler) { + this.nextHandler = nextHandler; + } + + //提交请假条 + public final void submit(LeaveRequest leaveRequest) { + if (this.numStart == 0) { + return; + } + //请假天数达到领导处理要求 + if (leaveRequest.getNum() >= this.numStart) { + this.handleLeave(leaveRequest); + + //如果还有上级 并且请假天数超过当前领导的处理范围 + if (this.nextHandler != null && leaveRequest.getNum() > numEnd) { + //继续提交 + this.nextHandler.submit(leaveRequest); + } else { + System.out.println("流程结束!!!"); + } + } + } + + //各级领导处理请假条方法 + protected abstract void handleLeave(LeaveRequest leave); + +} +``` + +小组长类 + +```java +public class GroupLeader extends Handler { + //1-3天的假 + public GroupLeader() { + super(Handler.NUM_ONE, Handler.NUM_THREE); + } + + @Override + protected void handleLeave(LeaveRequest leave) { + System.out.println(leave.getName() + "请假" + leave.getNum() + "天," + leave.getContent() + "!"); + System.out.println("小组长审批通过:同意!"); + } +} +``` + +部门经理类 + +```java +public class Manager extends Handler { + //3-7天的假 + public Manager() { + super(Handler.NUM_THREE, Handler.NUM_SEVEN); + } + + @Override + protected void handleLeave(LeaveRequest leave) { + System.out.println(leave.getName() + "请假" + leave.getNum() + "天," + leave.getContent() + "!"); + System.out.println("部门经理审批通过:同意!"); + } +} +``` + +总经理类 + +```java +public class GeneralManager extends Handler{ + //7天以上的假 + public GeneralManager() { + super(Handler.NUM_THREE, Handler.NUM_SEVEN); + } + + @Override + protected void handleLeave(LeaveRequest leave) { + System.out.println(leave.getName() + "请假" + leave.getNum() + "天," + leave.getContent() + "!"); + System.out.println("总经理审批通过:同意!"); + } +} +``` + +测试类 + +```java +public class Client { + public static void main(String[] args) { + //请假条 + LeaveRequest leave = new LeaveRequest("小庄", 3, "出去旅游"); + + //各位领导 + Manager manager = new Manager(); + GroupLeader groupLeader = new GroupLeader(); + GeneralManager generalManager = new GeneralManager(); + + /* + * 小组长上司是经理 经理上司是总经理 + */ + groupLeader.setNextHandler(manager); + manager.setNextHandler(generalManager); + + //提交 + groupLeader.submit(leave); + + } +} +``` + +应用场景: + +1. 多个对象可以处理一个请求,但具体由哪个对象处理该请求在运行时自动确定。 +2. 可动态指定一组对象处理请求,或添加新的处理者。 +3. 需要在不明确指定请求处理者的情况下,向多个处理者中的一个提交请求。 + +[参考链接](https://segmentfault.com/a/1190000040450513) + +## 迭代器模式 + +提供一种方法顺序访问一个聚合对象中的各个元素, 而又不暴露其内部的表示。 + +把在元素之间游走的责任交给迭代器,而不是聚合对象。 + +**应用实例:**JAVA 中的 iterator。 + +**优点:** 1、它支持以不同的方式遍历一个聚合对象。 2、迭代器简化了聚合类。 3、在同一个聚合上可以有多个遍历。 4、在迭代器模式中,增加新的聚合类和迭代器类都很方便,无须修改原有代码。 + +**缺点:**由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。 + +**使用场景:** 1、访问一个聚合对象的内容而无须暴露它的内部表示。 2、需要为聚合对象提供多种遍历方式。 3、为遍历不同的聚合结构提供一个统一的接口。 + +**迭代器模式在JDK中的应用** + +ArrayList的遍历: + +```java +Iterator iter = null; + +System.out.println("ArrayList:"); +iter = arrayList.iterator(); +while (iter.hasNext()) { + System.out.print(iter.next() + "\t"); +} +``` + + + +## 装饰模式 + +装饰者模式(decorator pattern):动态地将责任附加到对象上, 若要扩展功能, 装饰者提供了比继承更有弹性的替代方案。 + +装饰模式以对客户端透明的方式拓展对象的功能,客户端并不会觉得对象在装饰前和装饰后有什么不同。装饰模式可以在不创造更多子类的情况下,将对象的功能加以扩展。 + +比如设置FileInputStream,先用BufferedInputStream装饰它,再用自己写的LowerCaseInputStream过滤器去装饰它。 + +``` +InputStream in = new LowerCaseInputStream( + new BufferedInputStream( + new FileInputStream("test.txt"))); +``` +在装饰模式中的角色有: + +- 抽象组件(Component)角色:给出一个抽象接口,以规范准备接收附加责任的对象。 +- 具体组件(ConcreteComponent)角色:定义一个将要接收附加责任的类。 +- 装饰(Decorator)角色:持有一个构件(Component)对象的实例,并定义一个与抽象构件接口一致的接口。 +- 具体装饰(ConcreteDecorator)角色:负责给构件对象“贴上”附加的责任。 + +## 适配器模式 +适配器模式将现成的对象通过适配变成我们需要的接口。 适配器让原本接口不兼容的类可以合作。 + +适配器模式有类的适配器模式和对象的适配器模式两种不同的形式。 + +对象适配器模式通过组合对象进行适配。 + +![](http://img.dabin-coder.cn/image/适配器对象适配,png) + +类适配器通过继承来完成适配。 + +![](http://img.dabin-coder.cn/image/适配器-类继承,png) + +适配器模式的**优点**: + +1. 更好的复用性。系统需要使用现有的类,而此类的接口不符合系统的需要。那么通过适配器模式就可以让这些功能得到更好的复用。 +2. 更好的扩展性。在实现适配器功能的时候,可以调用自己开发的功能,从而自然地扩展系统的功能。 + +## 观察者模式 + +定义对象之间的一对多依赖,当一个对象状态改变时,它的所有依赖都会收到通知并且自动更新状态。 + +主题(Subject)是被观察的对象,而其所有依赖者(Observer)称为观察者。 + +多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。其作用是让主题对象和观察者松耦合。 + +观察者模式所涉及的角色有: + +- 抽象主题(Subject)角色:抽象主题角色把所有对观察者对象的引用保存在一个聚集(比如ArrayList对象)里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象,抽象主题角色又叫做抽象被观察者(Observable)角色。 +- 具体主题(ConcreteSubject)角色:将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色又叫做具体被观察者(Concrete Observable)角色。 +- 抽象观察者(Observer)角色:为所有的具体观察者定义一个接口,在得到主题的通知时更新自己,这个接口叫做更新接口。 +- 具体观察者(ConcreteObserver)角色:存储与主题的状态自恰的状态。具体观察者角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。如果需要,具体观察者角色可以保持一个指向具体主题对象的引用。 + + + +## 代理模式 + +代理模式使用代理对象完成用户请求,屏蔽用户对真实对象的访问。 + +### 静态代理 + +静态代理:代理类在编译阶段生成,程序运行前就已经存在,在编译阶段将通知织入Java字节码中。 + +缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类。同时,一旦接口增加方法,目标对象与代理对象都要维护。 + +### 动态代理 + +动态代理:代理类在程序运行时创建,在内存中临时生成一个代理对象,在运行期间对业务方法进行增强。 + +**JDK动态代理** + +JDK实现代理只需要使用newProxyInstance方法: + +```java +static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h ) +``` + +三个入参: + +- ClassLoader loader:指定当前目标对象使用的类加载器 +- Class[] interfaces:目标对象实现的接口的类型 +- InvocationHandler:当代理对象调用目标对象的方法时,会触发事件处理器的invoke方法() + +示例代码: + +``` +public class DynamicProxyDemo { + + public static void main(String[] args) { + //被代理的对象 + MySubject realSubject = new RealSubject(); + + //调用处理器 + MyInvacationHandler handler = new MyInvacationHandler(realSubject); + + MySubject subject = (MySubject) Proxy.newProxyInstance(realSubject.getClass().getClassLoader(), + realSubject.getClass().getInterfaces(), handler); + + System.out.println(subject.getClass().getName()); + subject.rent(); + } +} + +interface MySubject { + public void rent(); +} +class RealSubject implements MySubject { + + @Override + public void rent() { + System.out.println("rent my house"); + } +} +class MyInvacationHandler implements InvocationHandler { + + private Object subject; + + public MyInvacationHandler(Object subject) { + this.subject = subject; + } + + @Override + public Object invoke(Object object, Method method, Object[] args) throws Throwable { + System.out.println("before renting house"); + //invoke方法会拦截代理对象的方法调用 + Object o = method.invoke(subject, args); + System.out.println("after rentint house"); + return o; + } +} +``` + +## 建造者模式 + +建造者模式:封装一个对象的构造过程,并允许按步骤构造。 + +有两种形式:传统建造者模式和传统建造者模式变种。 + +传统建造者模式: + +```java +public class Computer { + private final String cpu;//必须 + private final String ram;//必须 + private final int usbCount;//可选 + private final String keyboard;//可选 + private final String display;//可选 + + private Computer(Builder builder){ + this.cpu=builder.cpu; + this.ram=builder.ram; + this.usbCount=builder.usbCount; + this.keyboard=builder.keyboard; + this.display=builder.display; + } + public static class Builder{ + private String cpu;//必须 + private String ram;//必须 + private int usbCount;//可选 + private String keyboard;//可选 + private String display;//可选 + + public Builder(String cup,String ram){ + this.cpu=cup; + this.ram=ram; + } + public Builder setDisplay(String display) { + this.display = display; + return this; + } + //set... + public Computer build(){ + return new Computer(this); + } + } +} + +public class ComputerDirector { + public void makeComputer(ComputerBuilder builder){ + builder.setUsbCount(); + builder.setDisplay(); + builder.setKeyboard(); + } +} +``` + +传统建造者模式变种,链式调用: + +```java +public class LenovoComputerBuilder extends ComputerBuilder { + private Computer computer; + public LenovoComputerBuilder(String cpu, String ram) { + computer=new Computer(cpu,ram); + } + @Override + public void setUsbCount() { + computer.setUsbCount(4); + } + //... + @Override + public Computer getComputer() { + return computer; + } +} + +Computer computer=new Computer.Builder("因特尔","三星") + .setDisplay("三星24寸") + .setKeyboard("罗技") + .setUsbCount(2) + .build(); +``` + diff --git a/computer-basic/algorithm.md b/computer-basic/algorithm.md new file mode 100644 index 0000000..55fcc0c --- /dev/null +++ b/computer-basic/algorithm.md @@ -0,0 +1,1436 @@ +![](http://img.dabin-coder.cn/image/数据结构与算法.jpg) + +## 二叉树的遍历 + +二叉树是一种非常重要的数据结构,很多其它数据结构都是基于二叉树的基础演变而来的。 + +二叉树的先序、中序和后序属于深度优先遍历DFS,层次遍历属于广度优先遍历BFS。 + +![](http://img.dabin-coder.cn/image/前序中序后序.png) + +四种主要的遍历思想为: + +前序遍历:根结点 ---> 左子树 ---> 右子树 + +中序遍历:左子树---> 根结点 ---> 右子树 + +后序遍历:左子树 ---> 右子树 ---> 根结点 + +层次遍历:只需按层次遍历即可 + +### 前序遍历 + +遍历思路:根结点 ---> 左子树 ---> 右子树。 + +根据前序遍历的顺序,优先访问根结点,然后在访问左子树和右子树。所以,对于任意结点node,第一部分即直接访问之,之后在判断左子树是否为空,不为空时即重复上面的步骤,直到其为空。若为空,则需要访问右子树。注意,在访问过左孩子之后,需要反过来访问其右孩子,可以是栈这种数据结构来支持。对于任意一个结点node,具体步骤如下: + +1. 访问结点,并把结点node入栈,当前结点置为左孩子; +2. 判断结点node是否为空,若为空,则取出栈顶结点并出栈,将右孩子置为当前结点;否则重复a)步直到当前结点为空或者栈为空(可以发现栈中的结点就是为了访问右孩子才存储的) + +```java + +public void preOrderTraverse2(TreeNode root) { + LinkedList stack = new LinkedList<>(); + TreeNode pNode = root; + while (pNode != null || !stack.isEmpty()) { + if (pNode != null) { + System.out.print(pNode.val+" "); + stack.push(pNode); + pNode = pNode.left; + } else { //pNode == null && !stack.isEmpty() + TreeNode node = stack.pop(); + pNode = node.right; + } + } +} +``` + +### 中序遍历 + +遍历思路:左子树 ---> 根结点 ---> 右子树 + +```java + public List inorderTraversal(TreeNode root) { + List res = new ArrayList<>(); + Deque deque = new ArrayDeque<>(); + + while (!deque.isEmpty() || root != null) { + while (root != null) { + deque.push(root); + root = root.left; + } + root = deque.pop(); + res.add(root.val); + root = root.right; + } + + return res; + } +``` + +### 后序遍历 + +遍历思路:左子树 ---> 右子树 ---> 根结点。 + +使用 null 作为标志位,访问到 null 说明此次递归调用结束。 + +```java +class Solution { + public List postorderTraversal(TreeNode root) { + List res = new LinkedList<>(); + if (root == null) { + return res; + } + + Stack stack = new Stack<>(); + stack.push(root); + while (!stack.isEmpty()) { + root = stack.pop(); + if (root != null) { + stack.push(root);//最后访问 + stack.push(null); + if (root.right != null) { + stack.push(root.right); + } + if (root.left != null) { + stack.push(root.left); + } + } else { //值为null说明此次递归调用结束,将节点值存进结果 + res.add(stack.pop().val); + } + } + + return res; + } +} +``` + +### 层序遍历 + +只需要一个队列即可,先在队列中加入根结点。之后对于任意一个结点来说,在其出队列的时候,访问之。同时如果左孩子和右孩子有不为空的,入队列。 + +``` +public void levelTraverse(TreeNode root) { + if (root == null) { + return; + } + LinkedList queue = new LinkedList<>(); + queue.offer(root); + while (!queue.isEmpty()) { + TreeNode node = queue.poll(); + System.out.print(node.val+" "); + if (node.left != null) { + queue.offer(node.left); + } + if (node.right != null) { + queue.offer(node.right); + } + } +} +``` + +## 排序算法 + +常见的排序算法主要有:冒泡排序、插入排序、选择排序、快速排序、归并排序、堆排序、基数排序。各种排序算法的时间空间复杂度、稳定性见下图。 + +![](http://img.dabin-coder.cn/image/排序算法时间空间复杂度.png) + +### 冒泡排序 + +冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。 + +思路: + +- 比较相邻的元素。如果第一个比第二个大,就交换它们两个; +- 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数; +- 针对所有的元素重复以上的步骤,除了最后一个; +- 重复步骤1~3,直到排序完成。 + +代码实现: + +```java +public void bubbleSort(int[] arr) { + if (arr == null) { + return; + } + boolean flag; + for (int i = arr.length - 1; i > 0; i--) { + flag = false; + for (int j = 0; j < i; j++) { + if (arr[j] > arr[j + 1]) { + int tmp = arr[j]; + arr[j] = arr[j + 1]; + arr[j + 1] = tmp; + flag = true; + } + } + if (!flag) { + return; + } + } +} +``` + +### 插入排序 + +插入排序(Insertion-Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。 + +算法描述: + +一般来说,插入排序都采用in-place在数组上实现。具体算法描述如下: + +- 从第一个元素开始,该元素可以认为已经被排序; +- 取出下一个元素,在已经排序的元素序列中从后向前扫描; +- 如果该元素(已排序)大于新元素,将该元素移到下一位置; +- 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置; +- 将新元素插入到该位置后; +- 重复步骤2~5。 + +代码实现: + +```java +public void insertSort(int[] arr) { + if (arr == null) { + return; + } + for (int i = 1; i < arr.length; i++) { + int tmp = arr[i]; + int j = i; + for (; j > 0 && tmp < arr[j - 1]; j--) { + arr[j] = arr[j - 1]; + } + arr[j] = tmp; + } +} +``` + +### 选择排序 + +表现**最稳定的排序算法之一**,因为**无论什么数据进去都是O(n2)的时间复杂度**,所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间。 + +选择排序(Selection-sort)是一种简单直观的排序算法。它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。 + +思路:n个记录的直接选择排序可经过n-1趟直接选择排序得到有序结果。具体算法描述如下: + +- 初始状态:无序区为R[1..n],有序区为空; +- 第i趟排序(i=1,2,3…n-1)开始时,当前有序区和无序区分别为R[1..i-1]和R(i..n)。该趟排序从当前无序区中-选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1..i]和R[i+1..n)分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区; +- n-1趟结束,数组有序化了。 + +代码实现: + +```java + public void selectionSort(int[] arr) { + if (arr == null) { + return; + } + for (int i = 0; i < arr.length - 1; i++) { + for (int j = i + 1; j < arr.length; j++) { + if (arr[i] > arr[j]) { + int tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; + } + } + } + } +``` + +### 希尔排序 + +希尔排序是希尔(Donald Shell)于1959年提出的一种排序算法。希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序,同时该算法是冲破O(n2)的第一批算法之一。它与插入排序的不同之处在于,它会优先比较距离较远的元素。希尔排序又叫缩小增量排序。 + +**希尔排序是把记录按下表的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。** + +代码实现: + +```java +public static int[] ShellSort(int[] array) { + int len = array.length; + int temp, gap = len / 2; + while (gap > 0) { + for (int i = gap; i < len; i++) { + temp = array[i]; + int preIndex = i - gap; + while (preIndex >= 0 && array[preIndex] > temp) { + array[preIndex + gap] = array[preIndex]; + preIndex -= gap; + } + array[preIndex + gap] = temp; + } + gap /= 2; + } + return array; +} +``` + +### 基数排序 + +基数排序也是非比较的排序算法,对每一位进行排序,从最低位开始排序,复杂度为O(kn),为数组长度,k为数组中的数的最大的位数; + +基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。基数排序基于分别排序,分别收集,所以是稳定的。 + +算法描述: + +- 取得数组中的最大数,并取得位数; +- arr为原始数组,从最低位开始取每个位组成radix数组; +- 对radix进行计数排序(利用计数排序适用于小范围数的特点); + +代码实现: + +```java +public static int[] RadixSort(int[] array) { + if (array == null || array.length < 2) + return array; + // 1.先算出最大数的位数; + int max = array[0]; + for (int i = 1; i < array.length; i++) { + max = Math.max(max, array[i]); + } + int maxDigit = 0; + while (max != 0) { + max /= 10; + maxDigit++; + } + int mod = 10, div = 1; + ArrayList> bucketList = new ArrayList>(); + for (int i = 0; i < 10; i++) + bucketList.add(new ArrayList()); + for (int i = 0; i < maxDigit; i++, mod *= 10, div *= 10) { + for (int j = 0; j < array.length; j++) { + int num = (array[j] % mod) / div; + bucketList.get(num).add(array[j]); + } + int index = 0; + for (int j = 0; j < bucketList.size(); j++) { + for (int k = 0; k < bucketList.get(j).size(); k++) + array[index++] = bucketList.get(j).get(k); + bucketList.get(j).clear(); + } + } + return array; +} +``` + +### 计数排序 + +计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。 作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。 + +计数排序(Counting sort)是一种稳定的排序算法。计数排序使用一个额外的数组C,其中第i个元素是待排序数组A中值等于i的元素的个数。然后根据数组C来将A中的元素排到正确的位置。它只能对整数进行排序。 + +```java +public static int[] CountingSort(int[] array) { + if (array.length == 0) return array; + int bias, min = array[0], max = array[0]; + for (int i = 1; i < array.length; i++) { + if (array[i] > max) + max = array[i]; + if (array[i] < min) + min = array[i]; + } + bias = 0 - min; + int[] bucket = new int[max - min + 1]; + Arrays.fill(bucket, 0); + for (int i = 0; i < array.length; i++) { + bucket[array[i] + bias]++; + } + int index = 0, i = 0; + while (index < array.length) { + if (bucket[i] != 0) { + array[index] = i - bias; + bucket[i]--; + index++; + } else + i++; + } + return array; +} +``` + + + +### 快速排序 + +快速排序是由**冒泡排序**改进而得到的,是一种排序执行效率很高的排序算法,它利用**分治法**来对待排序序列进行分治排序,它的思想主要是通过一趟排序将待排记录分隔成独立的两部分,其中的一部分比关键字小,后面一部分比关键字大,然后再对这前后的两部分分别采用这种方式进行排序,通过递归的运算最终达到整个序列有序。 + +快速排序的过程如下: + +1. 在待排序的N个记录中任取一个元素(通常取第一个记录)作为基准,称为基准记录; +2. 定义两个索引 left 和 right 分别表示首索引和尾索引,key 表示基准值; +3. 首先,尾索引向前扫描,直到找到比基准值小的记录,并替换首索引对应的值; +4. 然后,首索引向后扫描,直到找到比基准值大于的记录,并替换尾索引对应的值; +5. 若在扫描过程中首索引等于尾索引(left = right),则一趟排序结束;将基准值(key)替换首索引所对应的值; +6. 再进行下一趟排序时,待排序列被分成两个区:[0,left-1]和[righ+1,end] +7. 对每一个分区重复以上步骤,直到所有分区中的记录都有序,排序完成 + +快排为什么比冒泡效率高? + +快速排序之所以比较快,是因为相比冒泡排序,每次的交换都是跳跃式的,每次设置一个基准值,将小于基准值的都交换到左边,大于基准值的都交换到右边,这样不会像冒泡一样每次都只交换相邻的两个数,因此比较和交换的此数都变少了,速度自然更高。 + +快速排序的平均时间复杂度是O(nlgn),最坏时间复杂度是O(n^2)。 + +```java + public void quickSort(int[] arr) { + if (arr == null) { + return; + } + quickSortHelper(arr, 0, arr.length - 1); + } + private void quickSortHelper(int[] arr, int left, int right) { + if (left > right) { + return; + } + int tmp = arr[left]; + int i = left; + int j = right; + while (i < j) { + //j先走,最终循环终止时,j停留的位置就是arr[left]的正确位置 + //改为i<=j,则会进入死循环,[1,5,5,5,5]->[1] 5 [5,5,5]->[5,5,5],会死循环 + while (i < j && arr[j] >= tmp) { + j--; + } + while (i < j && arr[i] <= tmp) { + i++; + } + if (i < j) { + int tmp1 = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp1; + } else { + break; + } + } + + //当循环终止的时候,i=j,因为是j先走的,j所在位置的值小于arr[left],交换arr[j]和arr[left] + arr[left] = arr[j]; + arr[j] = tmp; + + quickSortHelper(arr, left, j - 1); + quickSortHelper(arr, j + 1, right); + } +``` + +### 归并排序 + +归并排序 (merge sort) 是一类与插入排序、交换排序、选择排序不同的另一种排序方法。归并的含义是将两个或两个以上的有序表合并成一个新的有序表。归并排序有多路归并排序、两路归并排序 , 可用于内排序,也可以用于外排序。 + +两路归并排序算法思路是递归处理。每个递归过程涉及三个步骤 + +- 分解: 把待排序的 n 个元素的序列分解成两个子序列, 每个子序列包括 n/2 个元素 +- 治理: 对每个子序列分别调用归并排序MergeSort, 进行递归操作 +- 合并: 合并两个排好序的子序列,生成排序结果 + +![](http://img.dabin-coder.cn/image/20220327151830.png) + +时间复杂度:对长度为n的序列,需进行logn次二路归并,每次归并的时间为O(n),故时间复杂度是O(nlgn)。 + +空间复杂度:归并排序需要辅助空间来暂存两个有序子序列归并的结果,故其辅助空间复杂度为O(n) + +```java +public class MergeSort { + public void mergeSort(int[] arr) { + if (arr == null || arr.length == 0) { + return; + } + //辅助数组 + int[] tmpArr = new int[arr.length]; + mergeSort(arr, tmpArr, 0, arr.length - 1); + } + + private void mergeSort(int[] arr, int[] tmpArr, int left, int right) { + if (left < right) { + int mid = (left + right) >> 1; + mergeSort(arr, tmpArr, left, mid); + mergeSort(arr, tmpArr, mid + 1, right); + merge(arr, tmpArr, left, mid, right); + } + } + + private void merge(int[] arr, int[] tmpArr, int left, int mid, int right) { + int i = left; + int j = mid + 1; + int tmpIndex = left; + while (i <= mid && j <= right) { + if (arr[i] < arr[j]) { + tmpArr[tmpIndex++] = arr[i]; + i++; + } else { + tmpArr[tmpIndex++] = arr[j]; + j++; + } + } + + while (i <= mid) { + tmpArr[tmpIndex++] = arr[i++]; + } + + while (j <= right) { + tmpArr[tmpIndex++] = arr[j++]; + } + + for (int m = left; m <= right; m++) { + arr[m] = tmpArr[m]; + } + } +} +``` + +### 堆排序 + +堆是具有下列性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。 + +![](https://img-blog.csdn.net/20150312212515074) + +**Top大问题**解决思路:使用一个固定大小的**最小堆**,当堆满后,每次添加数据的时候与堆顶元素比较,若小于堆顶元素,则舍弃,若大于堆顶元素,则删除堆顶元素,添加新增元素,对堆进行重新排序。 + +对于n个数,取Top m个数,时间复杂度为O(nlogm),这样在n较大情况下,是优于nlogn(其他排序算法)的时间复杂度的。 + +PriorityQueue 是一种基于优先级堆的优先级队列。每次从队列中取出的是具有最高优先权的元素。如果不提供Comparator的话,优先队列中元素默认按自然顺序排列,也就是数字默认是小的在队列头。优先级队列用数组实现,但是数组大小可以动态增加,容量无限。 + +```java +//找出前k个最大数,采用小顶堆实现 +public static int[] findKMax(int[] nums, int k) { + PriorityQueue pq = new PriorityQueue<>(k);//队列默认自然顺序排列,小顶堆,不必重写compare + + for (int num : nums) { + if (pq.size() < k) { + pq.offer(num); + } else if (pq.peek() < num) {//如果堆顶元素 < 新数,则删除堆顶,加入新数入堆 + pq.poll(); + pq.offer(num); + } + } + + int[] result = new int[k]; + for (int i = 0; i < k&&!pq.isEmpty(); i++) { + result[i] = pq.poll(); + } + return result; +} +``` + + + +## 动态规划 + +动态规划常常适用于有重叠子问题的问题。动态规划的基本思想:若要解一个给定问题,我们需要解其不同部分(即子问题),再根据子问题的解以得出原问题的解。 + +动态规划法试图仅仅解决每个子问题一次,一旦某个给定子问题的解已经算出,则将其记忆化存储,以便下次遇到同一个子问题的时候直接查表得到解。 + +动态规划的解题思路:1、状态定义;2、状态转移方程;3、初始状态。 + +### 不同路径 + +[不同路径](/leetcode/unique-paths.md) + +### 最长回文子串 + +从给定的字符串 `s` 中找到最长的回文子串的长度。 + +例如 `s = "babbad"` 的最长回文子串是 `"abba"` ,长度是 `4` 。 + +解题思路: + +1. 定义状态。`dp[i][j]` 表示子串 `s[i..j]` 是否为回文子串 + +2. 状态转移方程:`dp[i][j] = (s[i] == s[j]) and dp[i + 1][j - 1]` +3. 初始化的时候,单个字符一定是回文串,因此把对角线先初始化为 `true`,即 `dp[i][i] = true` 。 +4. 只要一得到 `dp[i][j] = true`,就记录子串的长度和起始位置 + +注意事项:总是先得到小子串的回文判定,然后大子串才能参考小子串的判断结果,即填表顺序很重要。 + +![](http://img.dabin-coder.cn/image/image-20201115230411764.png) + +时间复杂度O(N2),空间复杂度O(N2),因为使用了二维数组。 + +```java +public class Solution { + + public String longestPalindrome(String s) { + // 特判 + int len = s.length(); + if (len < 2) { + return s; + } + + int maxLen = 1; + int begin = 0; + + // dp[i][j] 表示 s[i, j] 是否是回文串 + boolean[][] dp = new boolean[len][len]; + char[] charArray = s.toCharArray(); + + for (int i = 0; i < len; i++) { + dp[i][i] = true; + } + for (int j = 1; j < len; j++) { + for (int i = 0; i < j; i++) { + if (charArray[i] != charArray[j]) { + dp[i][j] = false; + } else { + if (j - i < 3) { + dp[i][j] = true; + } else { + dp[i][j] = dp[i + 1][j - 1]; + } + } + + // 只要 dp[i][j] == true 成立,就表示子串 s[i..j] 是回文,此时记录回文长度和起始位置 + if (dp[i][j] && j - i + 1 > maxLen) { + maxLen = j - i + 1; + begin = i; + } + } + } + return s.substring(begin, begin + maxLen); //substring(i, j)截取i到j(不包含j)的字符串 + } +} +``` + + + +### 最大子数组和 + +**题目描述**:给你一个整数数组 `nums` ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 + +**示例**: + +```java +输入: nums = [-2,1,-3,4,-1,2,1,-5,4] +输出: 6 +解释: 连续子数组 [4,-1,2,1] 的和最大,为 6 。 +``` + +1、首先确定dp数组(dp table)以及下标的含义。 + +dp[i]表示以nums[i]结尾的子数组的最大和。 + +2、确定递推公式。 + +`dp[i] = dp[i - 1] > 0 ? ( dp[i - 1] + nums[i]) : nums[i]` + +dp[i+1]取决于dp[i]的值,不需要使用数组保存状态,只需要一个变量sum来保存上一个状态即可。 + +3、dp数组如何初始化。 + +从递推公式可以看出来dp[i]是依赖于dp[i - 1]的状态,dp[0]就是递推公式的基础。 + +dp[0]应该是多少呢?根据dp[i]的定义,很明显dp[0]应为nums[0]即dp[0] = nums[0]。 + +示例代码如下: + +```java +class Solution { + public int maxSubArray(int[] nums) { + if (nums == null || nums.length == 0) { + return 0; + } + + int max = nums[0]; + int sum = 0; + + for (int num : nums) { + if (sum > 0) { + sum += num; + } else { + sum = num; + } + max = Math.max(max, sum); + } + + return max; + } +} +``` + +### 最长公共子序列 + +一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。 +例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。 + +动态规划。`dp[i][j]`表示text1以i-1结尾的子串和text2以j-1结尾的子串的最长公共子序列的长度。dp横坐标或纵坐标为0表示空字符串,`dp[0][j] = dp[i][0] = 0`,无需额外处理base case。 + +![](http://img.dabin-coder.cn/image/longestCommonSubsequence.png) + +```java +class Solution { + public int longestCommonSubsequence(String text1, String text2) { + char[] arr1 = text1.toCharArray(); + char[] arr2 = text2.toCharArray(); + //dp[0][x]和dp[x][0]表示有一个为空字符串 + //dp[1][1]为text1第一个字符和text2第一个字符的最长公共子序列的长度 + //dp[i][j]表示text1以i-1结尾的子串和text2以j-1结尾的子串的最长公共子序列的长度 + int len1 = arr1.length; + int len2 = arr2.length; + int[][] dp = new int[len1 + 1][len2 + 1]; + + for (int i = 1; i < len1 + 1; i++) { + for (int j = 1; j < len2 + 1; j++) { + if (arr1[i - 1] == arr2[j - 1]) { + dp[i][j] = dp[i - 1][j - 1] + 1; + } else { + dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); + } + } + } + + return dp[len1][len2]; + } +} +``` + +`dp[i][j]`表示text1以i结尾的子串和text2以j结尾的子串的最长公共子序列的长度。需要处理base case。 + +```java +class Solution { + public int longestCommonSubsequence(String text1, String text2) { + char[] arr1 = text1.toCharArray(); + char[] arr2 = text2.toCharArray(); + + int len1 = arr1.length; + int len2 = arr2.length; + //`dp[i][j]`表示text1以i结尾的子串和text2以j结尾的子串的最长公共子序列的长度。 + int[][] dp = new int[len1][len2]; + + if (arr1[0] == arr2[0]) { + dp[0][0] = 1; + } + for (int i = 1; i < len1; i++) { + if (arr1[i] == arr2[0]) { + dp[i][0] = 1; + } else { + dp[i][0] = dp[i - 1][0]; + } + } + for (int i = 1; i < len2; i++) { + if (arr1[0] == arr2[i]) { + dp[0][i] = 1; + } else { + dp[0][i] = dp[0][i - 1]; + } + } + + for (int i = 1; i < len1; i++) { + for (int j = 1; j < len2; j++) { + if (arr1[i] == arr2[j]) { + dp[i][j] = dp[i - 1][j - 1] + 1; + } else { + dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); + } + } + } + + return dp[len1 - 1][len2 - 1]; + } +} +``` + + + +### 接雨水 + +给定 `n` 个非负整数表示每个宽度为 `1` 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。 + +![](http://img.dabin-coder.cn/image/接雨水.png) + +示例: + +```java +输入:height = [0,1,0,2,1,0,1,3,2,1,2,1] +输出:6 +解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。 +``` + +动态规划,使用两个数组空间。leftMax[i] 代表第 `i` 列左边(不包含自身)最高的墙的高度,rightMax[i] 代表第 `i` 列右边最高的墙的高度。 + +```java +class Solution { + public int trap(int[] height) { + int len = height.length; + int res = 0; + int[] leftMax = new int[len]; + int[] rightMax = new int[len]; + + for (int i = 1; i < len; i++) { + leftMax[i] = Math.max(leftMax[i - 1], height[i - 1]); + } + + for (int j = len - 2; j > 0; j--) { + rightMax[j] = Math.max(rightMax[j + 1], height[j + 1]); + } + + for (int i = 1; i < len - 1; i++) { + int min = Math.min(leftMax[i], rightMax[i]); + if (min > height[i]) { + res += min - height[i]; + } + } + + return res; + } +} +``` + + + +### 单词拆分 + +![](http://img.dabin-coder.cn/image/word-break.png) + +```java +class Solution { + public boolean wordBreak(String s, List wordDict) { + int len = s.length(), maxw = 0; + //dp[i]表示前i个字母组成的字符串是否可以被拆分 + boolean[] dp = new boolean[len + 1]; + //状态转移方程初始化条件 + dp[0] = true; + Set set = new HashSet(); + for(String str : wordDict){ + set.add(str); + maxw = Math.max(maxw, str.length()); + } + for(int i = 1; i < len + 1; i++){ + for(int j = i; j >= 0 && j >= i - maxw; j--){ + if(dp[j] && set.contains(s.substring(j, i))){ + dp[i] = true; + break; + } + } + } + return dp[len]; + } +} +``` + +## 回溯算法 + +回溯算法的基本思想是:从一条路往前走,能进则进,不能进则退回来,换一条路再试。 + +### 组合总和 + +题目描述:给定一个**无重复元素**的数组 `candidates` 和一个目标数 `target` ,找出 `candidates` 中所有可以使数字和为 `target` 的组合。 + +示例: + +``` +输入:candidates = [2,3,6,7], target = 7, +输出: +[ + [7], + [2,2,3] +] +``` + +使用回溯算法。 + +```java +class Solution { + private List> ans = new ArrayList<>(); + public List> combinationSum2(int[] candidates, int target) { + if (candidates == null || candidates.length == 0) { + return ans; + } + Arrays.sort(candidates);//排序方便回溯剪枝 + Deque path = new ArrayDeque<>();//作为栈来使用,效率高于Stack;也可以作为队列来使用,效率高于LinkedList;线程不安全 + combinationSum2Helper(candidates, target, 0, path); + return ans; + } + + public void combinationSum2Helper(int[] arr, int target, int start, Deque path) { + if (target == 0) { + ans.add(new ArrayList(path)); + } + + for (int i = start; i < arr.length; i++) { + if (target < arr[i]) {//剪枝 + return; + } + if (i > start && arr[i] == arr[i - 1]) {//在一个层级,会产生重复 + continue; + } + path.addLast(arr[i]); + combinationSum2Helper(arr, target - arr[i], i + 1, path); + path.removeLast(); + } + } +} +``` + + + +### 全排列 + +给定一个 **没有重复** 数字的序列,返回其所有可能的全排列。 + +示例: + +``` +输入: [1,2,3] +输出: +[ + [1,2,3], + [1,3,2], + [2,1,3], + [2,3,1], + [3,1,2], + [3,2,1] +] +``` + +使用回溯。注意与组合总和的区别(数字有无顺序)。 + +```java +class Solution { + private List> ans = new ArrayList<>(); + public List> permute(int[] nums) { + boolean[] flag = new boolean[nums.length]; + ArrayDeque path = new ArrayDeque<>(); + permuteHelper(nums, flag, path); + + return ans; + } + + private void permuteHelper(int[] nums, boolean[] flag, ArrayDeque path) { + if (path.size() == nums.length) { + ans.add(new ArrayList<>(path)); + return; + } + for (int i = 0; i < nums.length; i++) { + if (flag[i]) { + continue;//继续循环 + } + path.addLast(nums[i]); + flag[i] = true; + permuteHelper(nums, flag, path); + path.removeLast(); + flag[i] = false; + } + } +} +``` + + + +### 全排列II + +给定一个可包含重复数字的序列,返回所有不重复的全排列。注意与组合总和的区别。 + +1、排序;2、同一层级相同元素剪枝。 + +![](http://img.dabin-coder.cn/image/permutations-ii.png) + +```java +class Solution { + private List> ans = new ArrayList<>(); + public List> permuteUnique(int[] nums) { + if (nums == null || nums.length == 0) { + return ans; + } + ArrayDeque path = new ArrayDeque<>(); + boolean[] used = new boolean[nums.length]; + Arrays.sort(nums);//切记 + dps(nums, used, path); + + return ans; + } + + private void dps(int[] nums, boolean[] used, ArrayDeque path) { + if (path.size() == nums.length) { + ans.add(new ArrayList<>(path)); + return; + } + for (int i = 0; i < nums.length; i++) { + if (used[i]) { + continue; + } + if ((i > 0 && nums[i] == nums[i - 1]) && !used[i - 1]) {//同一层相同的元素,剪枝 + continue;//继续循环,不是return退出循环 + } + path.addLast(nums[i]); + used[i] = true; + dps(nums, used, path); + path.removeLast(); + used[i] = false; + } + } +} +``` + +## 贪心算法 + +贪心算法,是寻找**最优解问题**的常用方法,这种方法模式一般将求解过程分成**若干个步骤**,但每个步骤都应用贪心原则,选取当前状态下**最好/最优的选择**(局部最有利的选择),并以此希望最后堆叠出的结果也是最好/最优的解。 + +**贪婪法的基本步骤:** + +1. 从某个初始解出发; +2. 采用迭代的过程,当可以向目标前进一步时,就根据局部最优策略,得到一部分解,缩小问题规模; +3. 将所有解综合起来。 + +### 买卖股票的最佳时机 II + +**题目描述**: + +给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。 + +在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。 + +返回 你能获得的 最大 利润 。 + +**示例**: + +```java +输入:prices = [1,2,3,4,5] +输出:4 +解释:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。 +  总利润为 4 。 +``` + +思路:可以尽可能地完成更多的交易,但不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 + +```java +//输入: [7,1,5,3,6,4] +//输出: 7 +class Solution { + public int maxProfit(int[] prices) { + int profit = 0; + for (int i = 1; i < prices.length; i++) { + int tmp = prices[i] - prices[i - 1]; + if (tmp > 0) { + profit += tmp; + } + } + + return profit; + } +} +``` + +### 跳跃游戏 + +**题目描述** + +给定一个非负整数数组 `nums` ,你最初位于数组的 **第一个下标** 。 + +数组中的每个元素代表你在该位置可以跳跃的最大长度。 + +判断你是否能够到达最后一个下标。 + +**示例**: + +```java +输入:nums = [2,3,1,1,4] +输出:true +解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。 +``` + +解题思路: + +1. 如果某一个作为 起跳点 的格子可以跳跃的距离是 3,那么表示后面 3 个格子都可以作为 起跳点 +2. 可以对每一个能作为 起跳点 的格子都尝试跳一次,把 能跳到最远的距离 不断更新 +3. 如果可以一直跳到最后,就成功了 + +```java +class Solution { + public boolean canJump(int[] nums) { + if (nums == null || nums.length == 0) { + return true; + } + + int maxIndex = nums[0]; + for (int i = 1; i < nums.length; i++) { + if (maxIndex >= i) { + maxIndex = Math.max(maxIndex, i + nums[i]); + } else { + return false; + } + } + + return true; + } +} +``` + +### 加油站 + +**题目描述** + +在一条环路上有 n 个加油站,其中第 i 个加油站有汽油 gas[i] 升。 + +你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。 + +给定两个整数数组 gas 和 cost ,如果你可以绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1 。如果存在解,则 保证 它是 唯一 的。 + +**示例** + +```java +输入: gas = [1,2,3,4,5], cost = [3,4,5,1,2] +输出: 3 +解释: +从 3 号加油站(索引为 3 处)出发,可获得 4 升汽油。此时油箱有 = 0 + 4 = 4 升汽油 +开往 4 号加油站,此时油箱有 4 - 1 + 5 = 8 升汽油 +开往 0 号加油站,此时油箱有 8 - 2 + 1 = 7 升汽油 +开往 1 号加油站,此时油箱有 7 - 3 + 2 = 6 升汽油 +开往 2 号加油站,此时油箱有 6 - 4 + 3 = 5 升汽油 +开往 3 号加油站,你需要消耗 5 升汽油,正好足够你返回到 3 号加油站。 +因此,3 可为起始索引。 +``` + +**思路**: + +1. 遍历一周,总获得的油量少于要花掉的油量必然没有结果; +2. 先苦后甜,记录遍历时所存的油量最少的站点,由于题目有解只有唯一解,所以从当前站点的下一个站点开始是唯一可能成功开完全程的。 + +```java +class Solution { + public int canCompleteCircuit(int[] gas, int[] cost) { + int minIdx=0; + int sum=Integer.MAX_VALUE; + int num=0; + for (int i = 0; i < gas.length; i++) { + num+=gas[i]-cost[i]; + if(num 0) { + fast = fast.next; + } + + while (fast.next != null) { + fast = fast.next; + slow = slow.next; + } + slow.next = slow.next.next; + + return tmp.next; + } +} +``` + +### 三数之和 + +[题目链接](https://leetcode.cn/problems/3sum/) + +**题目描述** + +给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。 + +注意:答案中不可以包含重复的三元组。 + +**示例** + +```java +输入:nums = [-1,0,1,2,-1,-4] +输出:[[-1,-1,2],[-1,0,1]] +``` + +**思路**: + +- 首先对数组进行排序,排序后固定一个数 nums[i]nums[i],再使用左右指针指向 nums[i]nums[i]后面的两端,数字分别为 nums[L]nums[L] 和 +- nums[R]nums[R],计算三个数的和 sumsum 判断是否满足为 00,满足则添加进结果集 +- 如果 nums[i]nums[i]大于 00,则三数之和必然无法等于 00,结束循环 +- 如果 nums[i]nums[i] == nums[i-1]nums[i−1],则说明该数字重复,会导致结果重复,所以应该跳过 +- 当 sumsum == 00 时,nums[L]nums[L] == nums[L+1]nums[L+1] 则会导致结果重复,应该跳过,L++L++ +- 当 sumsum == 00 时,nums[R]nums[R] == nums[R-1]nums[R−1] 则会导致结果重复,应该跳过,R--R−− + +**参考代码**: + +```java +class Solution { + public List> threeSum(int[] nums) { + List> res = new ArrayList<>(); + Arrays.sort(nums); + + for (int i = 0; i < nums.length; i++) { + if (nums[i] > 0) { //最左边的数字大于0,则sum不会等于0,退出 + break; + } + if (i > 0 && nums[i] == nums[i - 1]) { //去重复 + continue; + } + + int left = i + 1; + int right = nums.length - 1; + + while (left < right) { + int sum = nums[i] + nums[left] + nums[right]; + if (sum == 0) { + res.add(Arrays.asList(nums[i], nums[left], nums[right])); ///array to list + while (left < right && nums[left] == nums[left + 1]) { + left++; + } + while (left < right && nums[right] == nums[right - 1]) { + right--; + } + left++; + right--; + } else if (sum > 0) { + right--; + } else { + left++; + } + } + } + + return res; + } +} +``` + +### 环形链表 + +[题目链接](https://leetcode.cn/problems/linked-list-cycle/) + +**题目描述** + +给你一个链表的头节点 head ,判断链表中是否有环。 + +如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。 + +如果链表中存在环 ,则返回 true 。 否则,返回 false 。 + +**示例** + +```java +输入:head = [3,2,0,-4], pos = 1 +输出:true +解释:链表中有一个环,其尾部连接到第二个节点。 +``` + +**思路** + +快慢指针。快指针每次走两步,慢指针走一步,相当于慢指针不动,快指针每次走一步,如果是环形链表,则一定会相遇。 + +```java +public class Solution { + public boolean hasCycle(ListNode head) { + if (head == null) { + return false; + } + + ListNode quick = head; + ListNode slow = head; + + while (quick != null && quick.next != null) { + slow = slow.next; + quick = quick.next.next; + + if (slow == quick) { + return true; + } + } + + return false; + } +} +``` + +### 环形链表II + +[题目链接](https://leetcode.cn/problems/linked-list-cycle-ii/) + +**题目描述** + +给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。 + +如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。 + +不允许修改 链表。 + +**示例** + +```java +输入:head = [3,2,0,-4], pos = 1 +输出:返回索引为 1 的链表节点 +解释:链表中有一个环,其尾部连接到第二个节点 +``` + +**解题思路** + +方法一:头结点到入环结点的距离为a,入环结点到相遇结点的距离为b,相遇结点到入环结点的距离为c。然后,当fast以slow的两倍速度前进并和slow相遇时,fast走过的距离是s的两倍,即有等式:a+b+c+b = 2(a+b) ,可以得出 a = c ,所以说,让fast和slow分别从相遇结点和头结点同时同步长出发,他们的相遇结点就是入环结点。 + +```java +public class Solution { + public ListNode detectCycle(ListNode head) { + ListNode fast = head; + ListNode slow = head; + + while (true) { + if (fast == null || fast.next == null) { + return null; + } + fast = fast.next.next; + slow = slow.next; + if (fast == slow) { + break; + } + } + + fast = head; + + while (slow != fast) { + slow = slow.next; + fast = fast.next; + } + + return fast; + } +} +``` + +方法二:先算出环的大小n,快指针先走n步,然后快慢指针一起走,相遇的地方即是环的入口。 + +```java +public class Solution { + public ListNode detectCycle(ListNode head) { + ListNode slow = head; + ListNode fast = head; + //快慢指针找出环的大小 + while (fast != null && fast.next != null) { + slow = slow.next; + fast = fast.next.next; + if (fast == slow) { + break; + } + } + + if (fast == null || fast.next == null) { + return null; + } + + int cycleSize = 1; + while (fast.next != slow) { + cycleSize++; + fast = fast.next; + } + + //快慢指针重新从链表首部出发,快指针先走sizeOfCycle步 + //然后两个指针同时一起走,步长为1,相遇节点即是环的入口 + fast = head; + slow = head; + while (cycleSize-- > 0) { + fast = fast.next; + } + while (fast != slow) { + fast = fast.next; + slow = slow.next; + } + + return fast; + } +} +``` + diff --git "a/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\225\260\346\215\256\347\273\223\346\236\204.md" b/computer-basic/data-structure.md similarity index 62% rename from "\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\225\260\346\215\256\347\273\223\346\236\204.md" rename to computer-basic/data-structure.md index 9bb6945..9c50818 100644 --- "a/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\225\260\346\215\256\347\273\223\346\236\204.md" +++ b/computer-basic/data-structure.md @@ -1,3 +1,5 @@ +![](http://img.dabin-coder.cn/image/数据结构与算法.jpg) + ## 各种数据结构应用场景 - 栈:逆序输出;语法检查,符号成对判断;方法调用 @@ -77,21 +79,50 @@ ### AVL树 -平衡二叉搜索树,它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1。 +**平衡二叉搜索树**,它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1。 + +![](http://img.dabin-coder.cn/image/image-20220722233208322.png) + +简单了解一下**左旋与右旋**的概念。 - - 左左单旋转 - ![在这里插入图片描述](https://img-blog.csdn.net/20181005222022420?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1R5c29uMDMxNA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) +左旋与右旋就是为了解决不平衡问题而产生的,我们构建一颗AVL树的过程会出现结点平衡因子(平衡因子就是二叉排序树中每个结点的左子树和右子树的高度差。)绝对值大于1的情况,这时就可以通过左旋或者右旋操作来达到平衡的目的。 -- 左右双旋转 - ![在这里插入图片描述](https://img-blog.csdn.net/20181005222244445?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1R5c29uMDMxNA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) +四种旋转情况 + +![](http://img.dabin-coder.cn/image/四种旋转情况.png) + +### 红黑树 + +红黑树是对AVL树的优化,只要求部分平衡,用非严格的平衡来换取增删节点时候旋转次数的降低,提高了插入和删除的性能。查找性能并没有提高,查找的时间复杂度是O(logn)。红黑树通过左旋、右旋和变色维持平衡。 + +![](http://img.dabin-coder.cn/image/20220619165116.png) + +对于插入节点,AVL和红黑树都是最多两次旋转来实现平衡。对于删除节点,avl需要维护从被删除节点到根节点root这条路径上所有节点的平衡,旋转的量级为O(logN),而红黑树最多只需旋转3次。 -- 四种旋转情况 +红黑树的特性: - ![在这里插入图片描述](https://img-blog.csdnimg.cn/20181206115511699.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1R5c29uMDMxNA==,size_16,color_FFFFFF,t_70) +- 每个节点或者是黑色,或者是红色。 +- 根节点和叶子节点是黑色,叶子节点为空。 +- 红色节点的子节点必须是黑色的。 +- 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点,保证没有一条路径会比其他路径长一倍。 + +优点:相比avl树,红黑树插入删除的效率更高。红黑树维持红黑性质所做的红黑变换和旋转的开销,相较于avl树维持平衡的开销要小得多。 + +**应用场景** + +- Java ConcurrentHashMap & TreeMap +- C++ STL: map & set +- linux进程调度Completely Fair Scheduler,用红黑树管理进程控制块 +- epoll在内核中的实现,用红黑树管理事件块 +- nginx中,用红黑树管理timer等 ### B树 -也称B-树,属于多叉树又名平衡多路查找树。规则: +也称B-树,属于多叉树又名平衡多路查找树。 + +![](http://img.dabin-coder.cn/image/B-树.png) + +规则: - 1<子节点数<=m,m代表一个树节点最多有多少个查找路径 - 每个节点最多有m-1个关键字,非根节点至少有m/2个关键字,根节点最少可以只有1个关键字 @@ -105,46 +136,81 @@ B-树的特性: B+树是B-树的变体,也是一种多路搜索树。B+的搜索与B-树基本相同,区别是B+树只有达到叶子结点才命中,B-树可以在非叶子结点命中。B+树更适合文件索引系统。 -B-和B+树的区别 +![](http://img.dabin-coder.cn/image/B+树.jpg) + +B-和B+树的区别: - B+树的非叶子结点不包含data,叶子结点使用链表连接,便于区间查找和遍历。B-树需要遍历整棵树,范围查询性能没有B+树好。 - B-树的非树节点存放数据和索引,搜索可能在非叶子结点结束,访问更快。 -### 红黑树 +## 图 -红黑树是对AVL树的优化,只要求部分平衡,用非严格的平衡来换取增删节点时候旋转次数的降低,提高了插入和删除的性能。查找性能并没有提高,查找的时间复杂度是O(logn)。红黑树通过左旋、右旋和变色维持平衡。 +图(Graph)是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为: G(V,E),其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合。 -对于插入节点,AVL和红黑树都是最多两次旋转来实现平衡。对于删除节点,avl需要维护从被删除节点到根节点root这条路径上所有节点的平衡,旋转的量级为O(logN),而红黑树最多只需旋转3次。 +和线性表,树的差异: -特性: -(1) 每个节点或者是黑色,或者是红色。 -(2) 根节点和叶子节点是黑色,叶子节点为空。 -(4)红色节点的子节点必须是黑色的。 -(5) 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点,保证没有一条路径会比其他路径长一倍。 +- 线性表中我们把数据元素叫元素,树中将数据元素叫结点,在图中数据元素,我们则称之为顶点(Vertex)。 +- 线性表可以没有元素,称为空表;树中可以没有节点,称为空树;但是,在图中不允许没有顶点(有穷非空性)。 +- 线性表中的各元素是线性关系,树中的各元素是层次关系,而图中各顶点的关系是用边来表示(边集可以为空)。 -![](https://raw.githubusercontent.com/Tyson0314/img/master/20220619165116.png) +![](http://img.dabin-coder.cn/image/20220619165442.png) -优点:相比avl树,红黑树插入删除的效率更高。红黑树维持红黑性质所做的红黑变换和旋转的开销,相较于avl树维持平衡的开销要小得多。 +### 相关术语 -应用:主要用来存储有序的数据,Java中的TreeSet和TreeMap都是通过红黑树实现的。 +- 顶点的度 +顶点Vi的度(Degree)是指在图中与Vi相关联的边的条数。对于有向图来说,有入度(In-degree)和出度(Out-degree)之分,有向图顶点的度等于该顶点的入度和出度之和。 +- 邻接 -## 图 +若无向图中的两个顶点V1和V2存在一条边(V1,V2),则称顶点V1和V2邻接(Adjacent); + +若有向图中存在一条边,则称顶点V3与顶点V2邻接,且是V3邻接到V2或V2邻接直V3; + +- 路径 + +在无向图中,若从顶点Vi出发有一组边可到达顶点Vj,则称顶点Vi到顶点Vj的顶点序列为从顶点Vi到顶点Vj的路径(Path)。 + +- 连通 + +若从Vi到Vj有路径可通,则称顶点Vi和顶点Vj是连通(Connected)的。 + +- 权(Weight) + +有些图的边或弧具有与它相关的数字,这种与图的边或弧相关的数叫做权(Weight)。 -图由顶点集(vertex set)和边集(edge set)所组成。 +### 类型 -![](https://raw.githubusercontent.com/Tyson0314/img/master/20220619165442.png) +无向图 -图的存储结构有邻接矩阵、邻接表和边集数组三种。 +如果图中任意两个顶点之间的边都是无向边(简而言之就是没有方向的边),则称该图为无向图(Undirected graphs)。 -1、邻接矩阵 +无向图中的边使用小括号“()”表示; 比如 (V1,V2); -![](https://raw.githubusercontent.com/Tyson0314/img/master/20220619165526.png) +有向图 -2、邻接表 +如果图中任意两个顶点之间的边都是有向边(简而言之就是有方向的边),则称该图为有向图(Directed graphs)。 -![](https://raw.githubusercontent.com/Tyson0314/img/master/20220619165617.png) +有向图中的边使用尖括号“<>”表示; 比如/ + +完全图 + +- `无向完全图`: 在无向图中,如果任意两个顶点之间都存在边,则称该图为无向完全图。(含有n个顶点的无向完全图有(n×(n-1))/2条边) +- `有向完全图: 在有向图中,如果任意两个顶点之间都存在方向互为相反的两条弧,则称该图为有向完全图。(含有n个顶点的有向完全图有n×(n-1)条边 + +### 图的存储结构 + +1、**邻接矩阵** + +图的邻接矩阵(Adjacency Matrix)存储方式是用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(称为邻接矩阵)存储图中的边或弧的信息。 + +![](http://img.dabin-coder.cn/image/20220619165526.png) + +2、**邻接表** + +邻接表由表头节点和表节点两部分组成,图中每个顶点均对应一个存储在数组中的表头节点。如果这个表头节点所对应的顶点存在邻接节点,则把邻接节点依次存放于表头节点所指向的单向链表中。 + +![](http://img.dabin-coder.cn/image/20220619165617.png) 下面给出建立图的邻接表中所使用的边结点类的定义 @@ -186,6 +252,8 @@ public interface Graph } ``` +### 图的遍历 + 深度优先遍历 ```java diff --git "a/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\347\275\221\347\273\234/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\351\253\230\351\242\221\351\235\242\350\257\225\351\242\230.md" b/computer-basic/network.md similarity index 56% rename from "\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\347\275\221\347\273\234/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\351\253\230\351\242\221\351\235\242\350\257\225\351\242\230.md" rename to computer-basic/network.md index 3d82083..38282f2 100644 --- "a/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\347\275\221\347\273\234/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\351\253\230\351\242\221\351\235\242\350\257\225\351\242\230.md" +++ b/computer-basic/network.md @@ -1,10 +1,12 @@ +![](http://img.dabin-coder.cn/image/计算机网络知识.jpg) + ## 网络分层结构 计算机网络体系大致分为三种,OSI七层模型、TCP/IP四层模型和五层模型。一般面试的时候考察比较多的是五层模型。 -![](https://raw.githubusercontent.com/Tyson0314/img/master/tcp5layer2.png) +![](http://img.dabin-coder.cn/image/tcp5layer2.png) -五层模型:应用层、传输层、网络层、数据链路层、物理层。 +**五层模型**:应用层、传输层、网络层、数据链路层、物理层。 - **应用层**:为应用程序提供交互服务。在互联网中的应用层协议很多,如域名系统DNS、HTTP协议、SMTP协议等。 - **传输层**:负责向两台主机进程之间的通信提供数据传输服务。传输层的协议主要有传输控制协议TCP和用户数据协议UDP。 @@ -12,11 +14,28 @@ - **数据链路层**:在两个相邻节点之间传送数据时,数据链路层将网络层交下来的 IP 数据报组装成帧,在两个相邻节点间的链路上传送帧。 - **物理层**:实现相邻节点间比特流的透明传输,尽可能屏蔽传输介质和物理设备的差异。 +**ISO七层模型**是国际标准化组织(International Organization for Standardization)制定的一个用于计算机或通信系统间互联的标准体系。 + +- 应用层:网络服务与最终用户的一个接口,常见的协议有:**HTTP FTP SMTP SNMP DNS**. +- 表示层:数据的表示、安全、压缩。,确保一个系统的应用层所发送的信息可以被另一个系统的应用层读取。 +- 会话层:建立、管理、终止会话,对应主机进程,指本地主机与远程主机正在进行的会话. +- 传输层:定义传输数据的协议端口号,以及流控和差错校验,协议有**TCP UDP**. +- 网络层:进行逻辑地址寻址,实现不同网络之间的路径选择,协议有**ICMP IGMP IP等**. +- 数据链路层:在物理层提供比特流服务的基础上,建立相邻结点之间的数据链路。 +- 物理层:建立、维护、断开物理连接。 + +**TCP/IP 四层模型** + +- 应用层:对应于OSI参考模型的(应用层、表示层、会话层)。 +- 传输层: 对应OSI的传输层,为应用层实体提供端到端的通信功能,保证了数据包的顺序传送及数据的完整性。 +- 网际层:对应于OSI参考模型的网络层,主要解决主机到主机的通信问题。 +- 网络接口层:与OSI参考模型的数据链路层、物理层对应。 + ## 三次握手 假设发送端为客户端,接收端为服务端。开始时客户端和服务端的状态都是`CLOSED`。 -![](https://raw.githubusercontent.com/Tyson0314/img/master/三次握手图解.png) +![](http://img.dabin-coder.cn/image/三次握手图解.png) 1. 第一次握手:客户端向服务端发起建立连接请求,客户端会随机生成一个起始序列号x,客户端向服务端发送的字段中包含标志位`SYN=1`,序列号`seq=x`。第一次握手前客户端的状态为`CLOSE`,第一次握手后客户端的状态为`SYN-SENT`。此时服务端的状态为`LISTEN`。 2. 第二次握手:服务端在收到客户端发来的报文后,会随机生成一个服务端的起始序列号y,然后给客户端回复一段报文,其中包括标志位`SYN=1`,`ACK=1`,序列号`seq=y`,确认号`ack=x+1`。第二次握手前服务端的状态为`LISTEN`,第二次握手后服务端的状态为`SYN-RCVD`,此时客户端的状态为`SYN-SENT`。(其中`SYN=1`表示要和客户端建立一个连接,`ACK=1`表示确认序号有效) @@ -33,7 +52,7 @@ ## 四次挥手 -![](https://raw.githubusercontent.com/Tyson0314/img/master/四次挥手0.png) +![](http://img.dabin-coder.cn/image/四次挥手0.png) 1. A的应用进程先向其TCP发出连接释放报文段(`FIN=1,seq=u`),并停止再发送数据,主动关闭TCP连接,进入`FIN-WAIT-1`(终止等待1)状态,等待B的确认。 2. B收到连接释放报文段后即发出确认报文段(`ACK=1,ack=u+1,seq=v`),B进入`CLOSE-WAIT`(关闭等待)状态,此时的TCP处于半关闭状态,A到B的连接释放。 @@ -58,6 +77,19 @@ - TCP提供**全双工通信**。 - **面向字节流**。 +## 说说TCP报文首部有哪些字段,其作用又分别是什么? + +![](http://img.dabin-coder.cn/image/tcp报文.png) + +- **16位端口号**:源端口号,主机该报文段是来自哪里;目标端口号,要传给哪个上层协议或应用程序 +- **32位序号**:一次TCP通信(从TCP连接建立到断开)过程中某一个传输方向上的字节流的每个字节的编号。 +- **32位确认号**:用作对另一方发送的tcp报文段的响应。其值是收到的TCP报文段的序号值加1。 +- **4位头部长度**:表示tcp头部有多少个32bit字(4字节)。因为4位最大能标识15,所以TCP头部最长是60字节。 +- **6位标志位**:URG(紧急指针是否有效),ACk(表示确认号是否有效),PSH(缓冲区尚未填满),RST(表示要求对方重新建立连接),SYN(建立连接消息标志接),FIN(表示告知对方本端要关闭连接了) +- **16位窗口大小**:是TCP流量控制的一个手段。这里说的窗口,指的是接收通告窗口。它告诉对方本端的TCP接收缓冲区还能容纳多少字节的数据,这样对方就可以控制发送数据的速度。 +- **16位校验和**:由发送端填充,接收端对TCP报文段执行CRC算法以检验TCP报文段在传输过程中是否损坏。注意,这个校验不仅包括TCP头部,也包括数据部分。这也是TCP可靠传输的一个重要保障。 +- **16位紧急指针**:一个正的偏移量。它和序号字段的值相加表示最后一个紧急数据的下一字节的序号。因此,确切地说,这个字段是紧急指针相对当前序号的偏移,不妨称之为紧急偏移。TCP的紧急指针是发送端向接收端发送紧急数据的方法。 + ## TCP和UDP的区别? 1. TCP**面向连接**;UDP是无连接的,即发送数据之前不需要建立连接。 @@ -67,6 +99,90 @@ 5. 每一条TCP连接只能是**点到点**的;UDP支持一对一、一对多、多对一和多对多的通信方式。 6. TCP首部开销20字节;UDP的首部开销小,只有8个字节。 +## TCP 和 UDP 分别对应的常见应用层协议有哪些? + +**基于TCP的应用层协议有:HTTP、FTP、SMTP、TELNET、SSH** + +- **HTTP**:HyperText Transfer Protocol(超文本传输协议),默认端口80 +- **FTP**: File Transfer Protocol (文件传输协议), 默认端口(20用于传输数据,21用于传输控制信息) +- **SMTP**: Simple Mail Transfer Protocol (简单邮件传输协议) ,默认端口25 +- **TELNET**: Teletype over the Network (网络电传), 默认端口23 +- **SSH**:Secure Shell(安全外壳协议),默认端口 22 + +**基于UDP的应用层协议:DNS、TFTP、SNMP** + +- **DNS** : Domain Name Service (域名服务),默认端口 53 +- **TFTP**: Trivial File Transfer Protocol (简单文件传输协议),默认端口69 +- **SNMP**:Simple Network Management Protocol(简单网络管理协议),通过UDP端口161接收,只有Trap信息采用UDP端口162。 + +## TCP的粘包和拆包 + +TCP是面向流,没有界限的一串数据。TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行包的划分,所以在业务上认为,一**个完整的包可能会被TCP拆分成多个包进行发送**,**也有可能把多个小的包封装成一个大的数据包发送**,这就是所谓的TCP粘包和拆包问题。 + +**为什么会产生粘包和拆包呢?** + +- 要发送的数据小于TCP发送缓冲区的大小,TCP将多次写入缓冲区的数据一次发送出去,将会发生粘包; +- 接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包; +- 要发送的数据大于TCP发送缓冲区剩余空间大小,将会发生拆包; +- 待发送数据大于MSS(最大报文长度),TCP在传输前将进行拆包。即TCP报文长度-TCP头部长度>MSS。 + +**解决方案:** + +- 发送端将每个数据包封装为固定长度 +- 在数据尾部增加特殊字符进行分割 +- 将数据分为两部分,一部分是头部,一部分是内容体;其中头部结构大小固定,且有一个字段声明内容体的大小。 + +## 说说TCP是如何确保可靠性的呢? + +- 首先,TCP的连接是基于**三次握手**,而断开则是基于**四次挥手**。确保连接和断开的可靠性。 +- 其次,TCP的可靠性,还体现在**有状态**;TCP会记录哪些数据发送了,哪些数据被接收了,哪些没有被接受,并且保证数据包按序到达,保证数据传输不出差错。 +- 再次,TCP的可靠性,还体现在**可控制**。它有数据包校验、ACK应答、**超时重传(发送方)**、失序数据重传(接收方)、丢弃重复数据、流量控制(滑动窗口)和拥塞控制等机制。 + +## 说下TCP的滑动窗口机制 + +TCP 利用滑动窗口实现流量控制。流量控制是为了控制发送方发送速率,保证接收方来得及接收。 TCP会话的双方都各自维护一个发送窗口和一个接收窗口。接收窗口大小取决于应用、系统、硬件的限制。发送窗口则取决于对端通告的接收窗口。接收方发送的确认报文中的window字段可以用来控制发送方窗口大小,从而影响发送方的发送速率。将接收方的确认报文window字段设置为 0,则发送方不能发送数据。 + +![](http://img.dabin-coder.cn/image/image-20210921112213523.png) + + +TCP头包含window字段,16bit位,它代表的是窗口的字节容量,最大为65535。这个字段是接收端告诉发送端自己还有多少缓冲区可以接收数据。于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来。接收窗口的大小是约等于发送窗口的大小。 + +## 详细讲一下拥塞控制? + +防止过多的数据注入到网络中。 几种拥塞控制方法:慢开始( slow-start )、拥塞避免( congestion avoidance )、快重传( fast retransmit )和快恢复( fast recovery )。 + +![](http://img.dabin-coder.cn/image/拥塞控制.jpg) + +**慢开始** + +把拥塞窗口 cwnd 设置为一个最大报文段MSS的数值。而在每收到一个对新的报文段的确认后,把拥塞窗口增加至多一个MSS的数值。每经过一个传输轮次,拥塞窗口 cwnd 就加倍。 为了防止拥塞窗口cwnd增长过大引起网络拥塞,还需要设置一个慢开始门限ssthresh状态变量。 + + 当 cwnd < ssthresh 时,使用慢开始算法。 + + 当 cwnd > ssthresh 时,停止使用慢开始算法而改用拥塞避免算法。 + + 当 cwnd = ssthresh 时,既可使用慢开始算法,也可使用拥塞控制避免算法。 + +**拥塞避免** + +让拥塞窗口cwnd缓慢地增大,每经过一个往返时间RTT就把发送方的拥塞窗口cwnd加1,而不是加倍。这样拥塞窗口cwnd按线性规律缓慢增长。 + +无论在慢开始阶段还是在拥塞避免阶段,只要发送方判断网络出现拥塞(其根据就是没有收到确认),就要把慢开始门限ssthresh设置为出现拥塞时的发送 方窗口值的一半(但不能小于2)。然后把拥塞窗口cwnd重新设置为1,执行慢开始算法。这样做的目的就是要迅速减少主机发送到网络中的分组数,使得发生 拥塞的路由器有足够时间把队列中积压的分组处理完毕。 + +**快重传** + +有时个别报文段会在网络中丢失,但实际上网络并未发生拥塞。如果发送方迟迟收不到确认,就会产生超时,就会误认为网络发生了拥塞。这就导致发送方错误地启动慢开始,把拥塞窗口cwnd又设置为1,因而降低了传输效率。 + +快重传算法可以避免这个问题。快重传算法首先要求接收方每收到一个失序的报文段后就立即发出重复确认,使发送方及早知道有报文段没有到达对方。 + +发送方只要一连收到三个重复确认就应当立即重传对方尚未收到的报文段,而不必继续等待重传计时器到期。由于发送方尽早重传未被确认的报文段,因此采用快重传后可以使整个网络吞吐量提高约20%。 + +**快恢复** + +当发送方连续收到三个重复确认,就会把慢开始门限ssthresh减半,接着把cwnd值设置为慢开始门限ssthresh减半后的数值,然后开始执行拥塞避免算法,使拥塞窗口缓慢地线性增大。 + +在采用快恢复算法时,慢开始算法只是在TCP连接建立时和网络出现超时时才使用。 采用这样的拥塞控制方法使得TCP的性能有明显的改进。 + ## HTTP协议的特点? 1. HTTP允许传输**任意类型**的数据。传输的类型由Content-Type加以标记。 @@ -116,7 +232,32 @@ Content-Length:112 ## HTTP状态码有哪些? -![](https://raw.githubusercontent.com/Tyson0314/img/master/http-status-code.png) +![](http://img.dabin-coder.cn/image/http-status-code.png) + +## HTTP 协议包括哪些请求? + +HTTP协议中共定义了八种方法来表示对Request-URI指定的资源的不同操作方式,具体如下: + +- GET:向特定的资源发出请求。 +- POST:向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的创建和/或已有资源的修改。 +- OPTIONS:返回服务器针对特定资源所支持的HTTP请求方法。也可以利用向Web服务器发送'*'的请求来测试服务器的功能性。 +- HEAD:向服务器索要与GET请求相一致的响应,只不过响应体将不会被返回。这一方法可以在不必传输整个响应内容的情况下,就可以获取包含在响应消息头中的元信息。 +- PUT:向指定资源位置上传其最新内容。 +- DELETE:请求服务器删除Request-URI所标识的资源。 +- TRACE:回显服务器收到的请求,主要用于测试或诊断。 +- CONNECT:HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。 + +## HTTP状态码301和302的区别? + +- 301:(永久性转移)请求的网页已被永久移动到新位置。服务器返回此响应时,会自动将请求者转到新位置。 +- 302:(暂时性转移)服务器目前正从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。此代码与响应GET和HEAD请求的301代码类似,会自动将请求者转到不同的位置。 + +**举个形象的例子**:当一个网站或者网页24—48小时内临时移动到一个新的位置,这时候就要进行302跳转,打个比方说,我有一套房子,但是最近走亲戚去亲戚家住了,过两天我还回来的。而使用301跳转的场景就是之前的网站因为某种原因需要移除掉,然后要到新的地址访问,是永久性的,就比如你的那套房子其实是租的,现在租期到了,你又在另一个地方找到了房子,之前租的房子不住了。 + +## URI和URL的区别 + +- URI,全称是Uniform Resource Identifier),中文翻译是统一资源标志符,主要作用是唯一标识一个资源。 +- URL,全称是Uniform Resource Location),中文翻译是统一资源定位符,主要作用是提供资源的路径。打个经典比喻吧,URI像是身份证,可以唯一标识一个人,而URL更像一个住址,可以通过URL找到这个人。 ## POST和GET的区别? @@ -125,6 +266,10 @@ Content-Length:112 - GET请求会被浏览器主动缓存,而POST不会,除非手动设置。 - GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。 +## 如何理解HTTP协议是无状态的 + +当浏览器第一次发送请求给服务器时,服务器响应了;如果同个浏览器发起第二次请求给服务器时,它还是会响应,但是呢,服务器不知道你就是刚才的那个浏览器。简言之,服务器不会去记住你是谁,所以是无状态协议。 + ## HTTP长连接和短连接? HTTP短连接:浏览器和服务器每进行一次HTTP操作,就建立一次连接,任务结束就中断连接。**HTTP1.0默认使用的是短连接**。 @@ -133,7 +278,21 @@ HTTP长连接:指的是**复用TCP连接**。多个HTTP请求可以复用同 **HTTP/1.1起,默认使用长连接**。要使用长连接,客户端和服务器的HTTP首部的Connection都要设置为keep-alive,才能支持长连接。 +## HTTP 如何实现长连接? +HTTP分为长连接和短连接,**本质上说的是TCP的长短连接**。TCP连接是一个双向的通道,它是可以保持一段时间不关闭的,因此TCP连接才具有真正的长连接和短连接这一说法哈。 + +TCP长连接可以复用一个TCP连接,来发起多次的HTTP请求,这样就可以减少资源消耗,比如一次请求HTML,如果是短连接的话,可能还需要请求后续的JS/CSS。 + +**如何设置长连接?** + +通过在头部(请求和响应头)设置**Connection**字段指定为`keep-alive`,HTTP/1.0协议支持,但是是默认关闭的,从HTTP/1.1以后,连接默认都是长连接。 + +## HTTP长连接在什么时候会超时? + +HTTP一般会有httpd守护进程,里面可以设置**keep-alive timeout**,当tcp连接闲置超过这个时间就会关闭,也可以在HTTP的header里面设置超时时间。 + +TCP 的**keep-alive**包含三个参数,支持在系统内核的net.ipv4里面设置;当 TCP 连接之后,闲置了**tcp_keepalive_time**,则会发生侦测包,如果没有收到对方的ACK,那么会每隔 tcp_keepalive_intvl再发一次,直到发送了**tcp_keepalive_probes**,就会丢弃该连接。 ## HTTP1.1和 HTTP2.0的区别? @@ -158,7 +317,7 @@ HTTP2.0相比HTTP1.1支持的特性: 服务端可以向证书颁发机构CA申请证书,以避免中间人攻击(防止证书被篡改)。证书包含三部分内容:**证书内容、证书签名算法和签名**,签名是为了验证身份。 -![](https://raw.githubusercontent.com/Tyson0314/img/master/image-20211004111441594.png) +![](http://img.dabin-coder.cn/image/image-20211004111441594.png) 服务端把证书传输给浏览器,浏览器从证书里取公钥。证书可以证明该公钥对应本网站。 @@ -180,29 +339,29 @@ HTTP2.0相比HTTP1.1支持的特性: 1. **协商加密算法** 。在`Client Hello`里面客户端会告知服务端自己当前的一些信息,包括客户端要使用的TLS版本,支持的加密算法,要访问的域名,给服务端生成的一个随机数(Nonce)等。需要提前告知服务器想要访问的域名以便服务器发送相应的域名的证书过来。 - ![](https://raw.githubusercontent.com/Tyson0314/img/master/image-20210921104210833.png) + ![](http://img.dabin-coder.cn/image/image-20210921104210833.png) 2. 服务端响应`Server Hello`,告诉客户端服务端**选中的加密算法**。 - ![](https://raw.githubusercontent.com/Tyson0314/img/master/image-20210921105450791.png) + ![](http://img.dabin-coder.cn/image/image-20210921105450791.png) 3. 接着服务端给客户端发来了2个证书。第二个证书是第一个证书的签发机构(CA)的证书。 - ![](https://raw.githubusercontent.com/Tyson0314/img/master/image-20211004172007102.png) + ![](http://img.dabin-coder.cn/image/image-20211004172007102.png) 4. 客户端使用证书的认证机构CA公开发布的RSA公钥**对该证书进行验证**,下图表明证书认证成功。 - ![](https://raw.githubusercontent.com/Tyson0314/img/master/image-20210921105929268.png) + ![](http://img.dabin-coder.cn/image/image-20210921105929268.png) 5. 验证通过之后,浏览器和服务器通过**密钥交换算法**产生共享的**对称密钥**。 - ![](https://raw.githubusercontent.com/Tyson0314/img/master/image-20210921110025197.png) + ![](http://img.dabin-coder.cn/image/image-20210921110025197.png) - ![](https://raw.githubusercontent.com/Tyson0314/img/master/image-20210921110155075.png) + ![](http://img.dabin-coder.cn/image/image-20210921110155075.png) 6. 开始传输数据,使用同一个对称密钥来加解密。 - ![](https://raw.githubusercontent.com/Tyson0314/img/master/image-20210921110315068.png) + ![](http://img.dabin-coder.cn/image/image-20210921110315068.png) ## DNS 的解析过程? @@ -221,7 +380,7 @@ HTTP2.0相比HTTP1.1支持的特性: 4. 服务器**响应请求**,返回响应数据。 5. 浏览器**解析响应内容,进行渲染**,呈现给用户。 -![](https://raw.githubusercontent.com/Tyson0314/img/master/输入url返回页面过程1.png) +![](http://img.dabin-coder.cn/image/输入url返回页面过程1.png) ## 什么是cookie和session? @@ -230,7 +389,7 @@ HTTP2.0相比HTTP1.1支持的特性: **cookie**就是由服务器发给客户端的特殊信息,而这些信息以文本文件的方式存放在客户端,然后客户端每次向服务器发送请求的时候都会带上这些特殊的信息。说得更具体一些:当用户使用浏览器访问一个支持cookie的网站的时候,用户会提供包括用户名在内的个人信息并且提交至服务器;接着,服务器在向客户端回传相应的超文本的同时也会发回这些个人信息,当然这些信息并不是存放在HTTP响应体中的,而是存放于HTTP响应头;当客户端浏览器接收到来自服务器的响应之后,浏览器会将这些信息存放在一个统一的位置。 自此,客户端再向服务器发送请求的时候,都会把相应的cookie存放在HTTP请求头再次发回至服务器。服务器在接收到来自客户端浏览器的请求之后,就能够通过分析存放于请求头的cookie得到客户端特有的信息,从而动态生成与该客户端相对应的内容。网站的登录界面中“请记住我”这样的选项,就是通过cookie实现的。 -![](https://raw.githubusercontent.com/Tyson0314/img/master/cookie.png) +![](http://img.dabin-coder.cn/image/cookie.png) **cookie工作流程**: @@ -240,9 +399,9 @@ HTTP2.0相比HTTP1.1支持的特性: **session原理**:首先浏览器请求服务器访问web站点时,服务器首先会检查这个客户端请求是否已经包含了一个session标识、称为SESSIONID,如果已经包含了一个sessionid则说明以前已经为此客户端创建过session,服务器就按照sessionid把这个session检索出来使用,如果客户端请求不包含session id,则服务器为此客户端创建一个session,并且生成一个与此session相关联的独一无二的sessionid存放到cookie中,这个sessionid将在本次响应中返回到客户端保存,这样在交互的过程中,浏览器端每次请求时,都会带着这个sessionid,服务器根据这个sessionid就可以找得到对应的session。以此来达到共享数据的目的。 这里需要注意的是,session不会随着浏览器的关闭而死亡,而是等待超时时间。 -![](https://raw.githubusercontent.com/Tyson0314/img/master/session.png) +![](http://img.dabin-coder.cn/image/session.png) -## Cookie和Session的区别? +## cookie和session的区别? - **作用范围不同**,Cookie 保存在客户端,Session 保存在服务器端。 - **有效期不同**,Cookie 可设置为长时间保持,比如我们经常使用的默认登录功能,Session 一般失效时间较短,客户端关闭或者 Session 超时都会失效。 @@ -255,61 +414,95 @@ HTTP2.0相比HTTP1.1支持的特性: **非对称加密**:它需要生成两个密钥,**公钥和私钥**。公钥是公开的,任何人都可以获得,而私钥是私人保管的。公钥负责加密,私钥负责解密;或者私钥负责加密,公钥负责解密。这种加密算法**安全性更高**,但是**计算量相比对称加密大很多**,加密和解密都很慢。常见的非对称算法有`RSA`和`DSA`。 -## 滑动窗口机制 +## 说说 WebSocket与socket的区别 -TCP 利用滑动窗口实现流量控制。流量控制是为了控制发送方发送速率,保证接收方来得及接收。 TCP会话的双方都各自维护一个发送窗口和一个接收窗口。接收窗口大小取决于应用、系统、硬件的限制。发送窗口则取决于对端通告的接收窗口。接收方发送的确认报文中的window字段可以用来控制发送方窗口大小,从而影响发送方的发送速率。将接收方的确认报文window字段设置为 0,则发送方不能发送数据。 +Socket是一套标准,它完成了对TCP/IP的高度封装,屏蔽网络细节,以方便开发者更好地进行网络编程。Socket其实就是等于**IP地址 + 端口 + 协议**。 -![](https://raw.githubusercontent.com/Tyson0314/img/master/image-20210921112213523.png) +WebSocket是一个持久化的协议,它是伴随H5而出的协议,用来解决**http不支持持久化连接**的问题。 +Socket一个是**网编编程的标准接口**,而WebSocket则是应用层通信协议。 -TCP头包含window字段,16bit位,它代表的是窗口的字节容量,最大为65535。这个字段是接收端告诉发送端自己还有多少缓冲区可以接收数据。于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来。接收窗口的大小是约等于发送窗口的大小。 +## ARP协议的工作过程? -## 详细讲一下拥塞控制? +ARP解决了同一个局域网上的主机和路由器IP和MAC地址的解析。 -防止过多的数据注入到网络中。 几种拥塞控制方法:慢开始( slow-start )、拥塞避免( congestion avoidance )、快重传( fast retransmit )和快恢复( fast recovery )。 +- 每台主机都会在自己的ARP缓冲区中建立一个ARP列表,以表示IP地址和MAC地址的对应关系。 +- 当源主机需要将一个数据包要发送到目的主机时,会首先检查自己 ARP列表中是否存在该 IP地址对应的MAC地址,如果有,就直接将数据包发送到这个MAC地址;如果没有,就向本地网段发起一个ARP请求的广播包,查询此目的主机对应的MAC地址。此ARP请求数据包里包括源主机的IP地址、硬件地址、以及目的主机的IP地址。 +- 网络中所有的主机收到这个ARP请求后,会检查数据包中的目的IP是否和自己的IP地址一致。如果不相同就忽略此数据包;如果相同,该主机首先将发送端的MAC地址和IP地址添加到自己的ARP列表中,如果ARP表中已经存在该IP的信息,则将其覆盖,然后给源主机发送一个 ARP响应数据包,告诉对方自己是它需要查找的MAC地址。 +- 源主机收到这个ARP响应数据包后,将得到的目的主机的IP地址和MAC地址添加到自己的ARP列表中,并利用此信息开始数据的传输。 +- 如果源主机一直没有收到ARP响应数据包,表示ARP查询失败。 -![](https://raw.githubusercontent.com/Tyson0314/img/master/拥塞控制.jpg) +## ICMP协议的功能 -### 慢开始 +ICMP,Internet Control Message Protocol ,Internet控制消息协议。 -把拥塞窗口 cwnd 设置为一个最大报文段MSS的数值。而在每收到一个对新的报文段的确认后,把拥塞窗口增加至多一个MSS的数值。每经过一个传输轮次,拥塞窗口 cwnd 就加倍。 为了防止拥塞窗口cwnd增长过大引起网络拥塞,还需要设置一个慢开始门限ssthresh状态变量。 +- ICMP协议是一种面向无连接的协议,用于传输出错报告控制信息。 +- 它是一个非常重要的协议,它对于网络安全具有极其重要的意义。它属于网络层协议,主要用于在主机与路由器之间传递控制信息,包括**报告错误、交换受限控制和状态信息**等。 +- 当遇到IP数据无法访问目标、IP路由器无法按当前的传输速率转发数据包等情况时,会自动发送ICMP消息。 - 当 cwnd < ssthresh 时,使用慢开始算法。 +比如我们日常使用得比较多的**ping**,就是基于ICMP的。 - 当 cwnd > ssthresh 时,停止使用慢开始算法而改用拥塞避免算法。 +## 什么是DoS、DDoS、DRDoS攻击? - 当 cwnd = ssthresh 时,既可使用慢开始算法,也可使用拥塞控制避免算法。 +- **DOS**: (Denial of Service),翻译过来就是拒绝服务,一切能引起DOS行为的攻击都被称为DOS攻击。最常见的DoS攻击就有**计算机网络宽带攻击**、**连通性攻击**。 +- **DDoS**: (Distributed Denial of Service),翻译过来是分布式拒绝服务。是指处于不同位置的多个攻击者同时向一个或几个目标发动攻击,或者一个攻击者控制了位于不同位置的多台机器并利用这些机器对受害者同时实施攻击。常见的DDos有**SYN Flood、Ping of Death、ACK Flood、UDP Flood**等。 +- **DRDoS**: (Distributed Reflection Denial of Service),中文是分布式反射拒绝服务,该方式靠的是发送大量带有被害者IP地址的数据包给攻击主机,然后攻击主机对IP地址源做出大量回应,从而形成拒绝服务攻击。 -### 拥塞避免 +## 什么是CSRF攻击,如何避免 -让拥塞窗口cwnd缓慢地增大,每经过一个往返时间RTT就把发送方的拥塞窗口cwnd加1,而不是加倍。这样拥塞窗口cwnd按线性规律缓慢增长。 +CSRF,跨站请求伪造(英文全称是Cross-site request forgery),是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。 -无论在慢开始阶段还是在拥塞避免阶段,只要发送方判断网络出现拥塞(其根据就是没有收到确认),就要把慢开始门限ssthresh设置为出现拥塞时的发送 方窗口值的一半(但不能小于2)。然后把拥塞窗口cwnd重新设置为1,执行慢开始算法。这样做的目的就是要迅速减少主机发送到网络中的分组数,使得发生 拥塞的路由器有足够时间把队列中积压的分组处理完毕。 +**怎么解决CSRF攻击呢?** -### 快重传 +- 检查Referer字段。 +- 添加校验token。 -有时个别报文段会在网络中丢失,但实际上网络并未发生拥塞。如果发送方迟迟收不到确认,就会产生超时,就会误认为网络发生了拥塞。这就导致发送方错误地启动慢开始,把拥塞窗口cwnd又设置为1,因而降低了传输效率。 +## 什么是XSS攻击? -快重传算法可以避免这个问题。快重传算法首先要求接收方每收到一个失序的报文段后就立即发出重复确认,使发送方及早知道有报文段没有到达对方。 +XSS,跨站脚本攻击(Cross-Site Scripting)。它指的是恶意攻击者往Web页面里插入恶意html代码,当用户浏览该页之时,嵌入其中Web里面的html代码会被执行,从而达到恶意攻击用户的特殊目的。XSS攻击一般分三种类型:**存储型 、反射型 、DOM型XSS** -发送方只要一连收到三个重复确认就应当立即重传对方尚未收到的报文段,而不必继续等待重传计时器到期。由于发送方尽早重传未被确认的报文段,因此采用快重传后可以使整个网络吞吐量提高约20%。 +## 如何解决XSS攻击问题? -### 快恢复 +- 对输入进行过滤,过滤标签等,只允许合法值。 +- HTML转义 +- 对于链接跳转,如` 1003; ``` -#### 范围查询: +### 范围查询: ```mysql SELECT prod_name, prod_price @@ -179,7 +177,7 @@ FROM products WHERE prod_price BETWEEN 5 AND 10; ``` -#### 空值检查 +### 空值检查 ```mysql SELECT prod_name @@ -187,7 +185,7 @@ FROM products WHERE prod_price IS NULL; ``` -#### 计算次序 +### 计算次序 ```mysql SELECT prod_name, prod_price @@ -195,7 +193,7 @@ FROM products WHERE vend_id = 1002 OR vend_id = 1003 AND prod_price >= 10; # AND优先级大于OR ``` -#### IN 操作符 +### IN 操作符 ```mysql SELECT prod_name, product_price @@ -206,7 +204,7 @@ ORDER BY prod_name; IN操作符一般比OR操作符清单执行更快。IN的最大优点是可以包含其他SELECT语句,使得能够更动态地建立WHERE子句。 -#### NOT操作符 +### NOT操作符 MySQL支持使用NOT 对IN 、BETWEEN 和EXISTS子句取反。 @@ -216,7 +214,7 @@ FROM products WHERE vend_id NOT IN (1002, 1003) ``` -#### LIKE操作符 +### LIKE操作符 % 匹配0到多个任意字符。 @@ -236,11 +234,11 @@ WHERE prod_name LIKE '_jet_'; 通配符搜索比其他简单搜索耗时,不能过度使用通配符。 -#### LIMIT +### LIMIT limit 0,4 :从第0条记录开始,取4条 -#### 正则表达式 +### 正则表达式 OR 匹配: @@ -316,9 +314,9 @@ ORDER BY prod_name; SELECT 'hello' REGEXP '[0-9]';#REGEXP检查返回0或1;此处返回0 ``` -## 计算字段 +# 计算字段 -### 拼接字段 +## 拼接字段 MySQL使用Concat()函数实现拼接。 @@ -331,7 +329,7 @@ ORDER BY vend_name; 返回值:`ACME (USA)` 使用别名:`SELECT dept AS department FROM t_dept;` -### 计算字段 +## 计算字段 ```mysql SELECT prod_id, quantity, item_price, quantity*item_price AS expanded_price @@ -339,9 +337,9 @@ FROM orderitems WHERE order_num = 2005; ``` -## 函数 +# 函数 -### 文本处理 +## 文本处理 ```mysql SELECT vend_name, Upper(vend_name) AS vend_name_upcase @@ -359,7 +357,7 @@ WHERE Soundex(cust_contact) = Soundex('Y Lie'); 返回数据:`Tyson Y lee` -### 日期处理函数 +## 日期处理函数 ![](http://img.dabin-coder.cn/image/20220530235607.png) 查找2005年9月的所有订单: @@ -378,13 +376,13 @@ FROM orders WHERE Year(order_date) = 2005 AND Month(order_date) = 9; ``` -### 数值处理函数 +## 数值处理函数 ![](http://img.dabin-coder.cn/image/20220530233617.png) -## 汇总数据 +# 汇总数据 -### 聚集函数 +## 聚集函数 Sum:求和 Avg:求平均数 @@ -406,7 +404,7 @@ FROM products WHERE vend_id = 1003; ``` -## 分组 +# 分组 单独地使用group by没意义,它只能显示出每组记录的第一条记录。 @@ -427,7 +425,7 @@ GROUP BY vend_id; GROUP BY子句必须出现在WHERE子句之后,ORDER BY子句之前。 -### 过滤分组 +## 过滤分组 having 用来分组查询后指定一些条件来输出查询结果,having作用和where类似,但是having只能用在group by场合,并且必须位于group by之后order by之前。 @@ -438,7 +436,7 @@ GROUP BY cust_id HAVING COUNT(*) >= 2; ``` -### having和where区别 +## having和where区别 ```mysql SELECT cust_id FROM orders GROUP BY cust_id HAVING COUNT(cust_id) >= 2; @@ -463,7 +461,7 @@ HAVING COUNT(*) >= 2; WHERE子句过滤所有prod_price至少为10的行。然后按vend_id分组数据,HAVING子句过滤计数为2或2以上的分组。 -### SELECT 子句顺序 +## SELECT 子句顺序 ```mysql SELECT @@ -477,7 +475,7 @@ LIMIT -## 子查询 +# 子查询 由于性能的限制,不能嵌套太多的子查询。 @@ -491,9 +489,9 @@ WHERE order_num IN (SELECT order_num -## 连接 +# 连接 -### 内连接 +## 内连接 找出供应商生产的产品。 @@ -513,7 +511,7 @@ WHERE vendors.vend_id = products.vend_id; 没有给出连接条件的话,会得到两张表的笛卡尔积。 -### 自连接 +## 自连接 找出生产nike的供应商生产的所有物品。 @@ -524,7 +522,7 @@ WHERE p1.vend_id = p2.vend_id AND p2.prod_id = 'nike'; ``` -### 自然连接 +## 自然连接 natural join是对两张表中字段名和数据类型都相同的字段进行**等值连接**,并返回符合条件的结果 。 @@ -536,7 +534,7 @@ SELECT * FROM role NATURAL JOIN user_role; ![](http://img.dabin-coder.cn/image/20220530235619.png) -### 内连接 +## 内连接 显示符合连接条件的记录。没有设置连接条件则返回笛卡尔积的结果。join 默认是 inner join。 @@ -559,7 +557,7 @@ SELECT * FROM role INNER JOIN user_role ON role.role_id = user_role.role_id ![](http://img.dabin-coder.cn/image/20220530235654.png) -### 外连接 +## 外连接 左外联接(Left Outer Join):除了匹配2张表中相关联的记录外,还会匹配左表中剩余的记录,右表中未匹配到的字段用NULL表示。 右外联接(Right Outer Join):除了匹配2张表中相关联的记录外,还会匹配右表中剩余的记录,左表中未匹配到的字段用NULL表示。 @@ -573,7 +571,7 @@ FROM customers LEFT OUTER JOIN orders ON customers.cust_id = order.cust_id; ``` -### 多表连接 +## 多表连接 ```mysql SELECT goal.player, eteam.teamname, game.stadium, game.mdate @@ -586,9 +584,9 @@ WHERE eteam.id = 'GRE' -## 组合查询 +# 组合查询 -### UNION +## UNION UNION中的每个查询必须包含相同的列、表达式或聚集函数。列数据类型必须兼容。 @@ -611,7 +609,7 @@ UNION 默认会去掉重复的行,使用 UNION ALL可以返回所有匹配行 -## 全文搜索 +# 全文搜索 为了进行全文本搜索,必须索引被搜索的列,而且要随着数据的改变不断地重新索引。在对表列进行适当设计后,MySQL会自动进行所有的索引和重新索引。 @@ -653,9 +651,9 @@ FROM productnotes; -## 表操作 +# 表操作 -### 创建表 +## 创建表 ```mysql CREATE TABLE productnotes @@ -670,7 +668,7 @@ CREATE TABLE productnotes 主键中只能使用NOT NULL值的列。 -### 更新表 +## 更新表 数据库表的更改不能撤销,应先做好备份。 @@ -708,7 +706,7 @@ MODIFY vend_phone CHAR(16); - 重新创建触发器、存储过程、索引和外键。 -### 约束 +## 约束 添加主键约束: @@ -737,19 +735,33 @@ ADD FOREIGN KEY(vendor_id) REFERENCES vendors(vendor_id); ALTER TABLE products DROP FOREIGN KEY vendor_id; ``` -### 删除表 +## 删除表 `DROP TABLE cumstomers` -### 重命名表 +## truncate、delete与drop区别 + +**相同点:** + +1. truncate和不带where子句的delete、以及drop都会删除表内的数据。 + +2. drop、truncate都是DDL语句(数据定义语言),执行后会自动提交。 + +**不同点:** + +1. truncate 和 delete 只删除数据不删除表的结构;drop 语句将删除表的结构被依赖的约束、触发器、索引; + +2. 速度,一般来说: drop> truncate > delete。 + +## 重命名表 `RENAME TABLE cusmtomers TO cust` -## 列操作 +# 列操作 -### 插入数据 +## 插入数据 MySQL用单条INSERT语句处理多个插入比使用多条INSERT语句快。 @@ -761,7 +773,7 @@ INSERT INTO customers(cust_name, cust_city) INSERT操作可能很耗时(特别是有很多索引需要更新时),而且它可能降低等待处理的SELECT语句的性能。降低INSERT语句的优先级:`INSERT LOW_PRIORITY INTO` -### 更新数据 +## 更新数据 如果用UPDATE语句更新多行,并且在更新这些行中的一行或多行时出一个现错误,则整个UPDATE操作被取消。为了在发生错误时也继续进行更新,可使用IGNORE关键字:`UPDATE IGNORE customers...` @@ -773,7 +785,7 @@ WHERE cust_id = 1005; 返回值是受影响的记录数。 -### 删除数据 +## 删除数据 如果想从表中删除所有行,不要使用DELETE。可使用TRUNCATE TABLE语句,它完成相同的工作,但速度更快(TRUNCATE实际是删除原来的表并重新创建一个表,而不是逐行删除表中的数据)。 @@ -795,23 +807,9 @@ AND EXISTS WHERE b.id = a.id); ``` -### truncate、delete与drop区别 - -**相同点:** - -1. truncate和不带where子句的delete、以及drop都会删除表内的数据。 - -2. drop、truncate都是DDL语句(数据定义语言),执行后会自动提交。 - -**不同点:** - -1. truncate 和 delete 只删除数据不删除表的结构;drop 语句将删除表的结构被依赖的约束、触发器、索引; - -2. 速度,一般来说: drop> truncate > delete。 - -## 引擎 +# 引擎 InnoDB是一个可靠的事务处理引擎,它不支持全文本搜索; @@ -823,23 +821,23 @@ MyISAM是一个性能极高的引擎,它支持全文本搜索,但不支持 -## 视图 +# 视图 视图为虚拟的表。视图提供了一种MySQL的SELECT语句层次的封装,可用来简化数据处理以及重新格式化基础数据或保护基础数据。 -### 应用 +## 应用 - 重用SQL语句。 - 保护数据。可以给用户授予表的特定部分的访问权限而不是整个表的访问权限。 - 更改数据格式和表示。视图可返回与底层表的表示和格式不同的数据。 -### 限制 +## 限制 - 与表一样,视图必须唯一命名 - 视图不能索引,也不能有关联的触发器或默认值。 - 视图可以和表一起使用。例如,编写一条联结表和视图的SELECT语句。 - ORDER BY可以用在视图中,但如果从该视图检索数据SELECT中也含有ORDER BY,那么该视图中的ORDER BY将被覆盖。 -### 语法 +## 语法 `CREATE VIEW`:创建视图 @@ -848,7 +846,7 @@ MyISAM是一个性能极高的引擎,它支持全文本搜索,但不支持 `DROP VIEW viewname`:删除视图 `CREATE ORREPLACE VIEW`:更新视图,相当于先用`DROP`再用`CREATE` -### 简化复杂连接 +## 简化复杂连接 创建一个视图,返回订购了任意产品的客户列表。 ```mysql CREATE VIEW productcustomers AS @@ -863,20 +861,20 @@ SELECT cust_name, cust_contact FROM productcustomers WHERE prod_id = 'nike'; ``` -### 更新视图 +## 更新视图 对视图增加或删除行,实际上是对其基表增加或删除行。视图主要用于数据检索。 -## 存储过程 +# 存储过程 为以后的使用而保存的一条或多条MySQL语句的集合。可将其视为批文件。 为什么使用存储过程: - 把复杂处理进行封装,简化复杂的操作; - 提高性能,存储过程比单独SQL语句更快; -### 创建 +## 创建 返回产品平均价格的存储过程: ```mysql CREATE PROCEDURE productpricing() # 可以接受参数 @@ -886,12 +884,12 @@ BEGIN END; ``` BEGIN/END 用来限定存储过程体。此段代码仅创建了存储过程,未执行。 -### 调用 +## 调用 `CALL productpricing()` -### 删除 +## 删除 存储过程在创建之后,被保存在服务器上以供使用,直至被删除。 `DROP PROCEDURE productpricing IF EXISTS` -### 参数 +## 参数 MySQL支持IN(传递给存储过程)、OUT(从存储过程传出)和INOUT(对存储过程传入和传出)类型的参数。 接受订单号并返回该订单的金额: ```mysql @@ -908,7 +906,7 @@ END; ``` 调用存储过程:`CALL ordertotal(20, @total);` 显示订单金额:`SELECT @total;` -### 实例 +## 实例 获取订单税后金额(订单金额+税收)。 ```mysql CREATE PROCEDURE ordertotal( @@ -936,7 +934,7 @@ END; CALL ordertotal(20005, 1, @total); SELECT @total; ``` -### 查看 +## 查看 创建存储过程的 CREATE 语句。 ```mysql SHOW CREATE PROCEDURE ordertotal; @@ -949,9 +947,9 @@ SHOW CREATE PROCEDURE ordertotal; SHOW PROCEDURE status; ``` -## 游标 +# 游标 存储了游标之后,应用程序可以根据需要滚动或浏览其中的数据。MySQL游标只能用于存储过程(和函数)。 -### 创建游标 +## 创建游标 DECLARE 命名游标。存储过程处理完成后,游标便消失(游标只存在于存储过程)。定义游标之后,便可以打开它。 ```mysql CREATE PROCEDURE processorders() @@ -961,7 +959,7 @@ BEGIN SELECT order_num FROM orders; END; ``` -### 使用游标 +## 使用游标 `OPEN ordernumbers` 打开游标。 `CLOSE ordernumbers` CLOSE释放游标使用的所有内部内存和资源。 ```mysql @@ -1002,20 +1000,20 @@ END; -## 触发器 +# 触发器 -提供SQL语句自动执行的功能。DELETE/INSERT/UPDATE支持触发器,其他SQL语句不支持。 -### 创建 +触发器提供SQL语句自动执行的功能。DELETE/INSERT/UPDATE支持触发器,其他SQL语句不支持。 +## 创建 创建触发器四要素:1.唯一的触发器名(MySQL5规定触发器名在表中唯一,数据库没要求);2.触发器关联的表;3.相应的SQL语句;4.何时执行(处理之前或者之后)。 ```mysql CREATE TRIGGER newproduct AFTER INSERT ON products #插入之后执行 FOR EACH ROW SELECT 'product added'; #对每个插入行执行 ``` 只有表支持触发器,视图不支持。单一触发器不能与多个事件或多个表关联,如果需要对INSERT和UPDATE操作执行触发器,则应该定义两个触发器。 -### 删除 +## 删除 `DROP TRIGGER newproduct` -### 使用触发器 +## 使用触发器 INSERT 触发器可饮用名为 NEW 的虚拟表,访问被插入的行。NEW中的值也可以被更新(允许更改被插入的值)。 @@ -1048,7 +1046,7 @@ FOR EACH ROW SET NEW.vend_state = Upper(NEW.vend_state); -## 事务处理 +# 事务处理 事务处理可以用来维护数据库的完整性。它保证成批的MySQL操作要么完全执行,要么完全不执行。 @@ -1056,6 +1054,8 @@ CREATE/DROP 操作不能回退,即便可以执行回退操作,回退不会 执行事务过程,一旦某个SQL失败,则之前执行成功的SQL会被自动撤销。 +## 语法 + ```mysql START TRANSACTION; DELETE FROM orderitems WHERE order_num = 20010; @@ -1065,7 +1065,7 @@ COMMIT; 当COMMIT或ROLLBACK语句执行后,事务会自动关闭。 -### 保留点 +## 保留点 为了支持回退部分事务处理,必须能在事务处理块中合适的位置放置占位符。这样,如果需要回退,可以回退到某个占位符。 @@ -1080,11 +1080,11 @@ ROLLBACK TO delete1; -## 权限 +# 权限 -### 管理用户 +MySQL用户账号和信息存储在名为mysql的MySQL数据库中。 -MySQL用户账号和信息存储在名为mysql的MySQL数据库中。获取用户账号列表。 +获取用户账号列表: ```mysql USE mysql; @@ -1108,15 +1108,16 @@ SELECT user FROM user; 撤销权限:`REVOKE SELECT, INSERT ON mall.* FROM tyson`,被撤销的访问权限必须存在,否则会出错。 GRANT和REVOKE可在几个层次上控制访问权限: - 整个服务器,使用GRANT ALL和REVOKE ALL; - 整个数据库,使用ON database.*; - 特定的表,使用ON database.table; - 特定的列; - 特定的存储过程。 +- 整个服务器,使用GRANT ALL和REVOKE ALL; +- 整个数据库,使用ON database.*; +- 特定的表,使用ON database.table; +- 特定的列; +- 特定的存储过程。 -## 优化性能 + +# 性能优化 使用EXPLAIN语句让MySQL解释它将如何执行一条SELECT语句。 @@ -1127,6 +1128,7 @@ GRANT和REVOKE可在几个层次上控制访问权限: LIKE很慢,最好是使用FULLTEXT而不是LIKE。 很多高性能的应用都会对关联查询进行分解,有如下的优势: + 1 、让缓存效率更高。如果某张表很少变化,那么基于该表的查询就可以重复利用查询缓存结果。 2 、将查询分解后,执行单个查询可以减少锁的竞争。 3 、在应用层做关联,可以更容易对数据库进行拆分,更容易做到高性能和可扩展。 @@ -1136,9 +1138,9 @@ LIKE很慢,最好是使用FULLTEXT而不是LIKE。 -## 索引 +# 索引 -### 创建索引 +## 创建索引 ALTER TABLE用来创建普通索引、UNIQUE索引或PRIMARY KEY索引。 @@ -1157,7 +1159,7 @@ CREATE UNIQUE INDEX index_name ON table_name (column_list) 在创建索引时,可以规定索引能否包含重复值。如果不包含,则索引应该创建为PRIMARY KEY或UNIQUE索引。 -### 删除索引 +## 删除索引 ```mysql DROP INDEX index_name ON talbe_name @@ -1165,7 +1167,7 @@ ALTER TABLE table_name DROP INDEX index_name ALTER TABLE table_name DROP PRIMARY KEY #只有一个主键,不需要指定索引名 ``` -### 查看索引 +## 查看索引 ```mysql show index from tblname; diff --git a/database/mysql-basic/1-data-type.md b/database/mysql-basic/1-data-type.md new file mode 100644 index 0000000..fede8a7 --- /dev/null +++ b/database/mysql-basic/1-data-type.md @@ -0,0 +1,90 @@ +--- +date: 2019-11-15 +category: Linux +tag: + - 介绍 +--- + + +# 数据类型 + +主要包括以下五大类: + +整数类型:BIT、BOOL、TINY INT、SMALL INT、MEDIUM INT、 INT、 BIG INT + +浮点数类型:FLOAT、DOUBLE、DECIMAL + +字符串类型:CHAR、VARCHAR、TINY TEXT、TEXT、MEDIUM TEXT、LONGTEXT、TINY BLOB、BLOB、MEDIUM BLOB、LONG BLOB + +日期类型:Date、DateTime、TimeStamp、Time、Year + +其他数据类型:BINARY、VARBINARY、ENUM、SET、Geometry、Point、MultiPoint、LineString、MultiLineString、Polygon、GeometryCollection等 + +## 整型 + +| MySQL数据类型 | 含义(有符号) | +| ------------- | ------------------------------------ | +| tinyint(m) | 1个字节 范围(-128~127) | +| smallint(m) | 2个字节 范围(-32768~32767) | +| mediumint(m) | 3个字节 范围(-8388608~8388607) | +| int(m) | 4个字节 范围(-2147483648~2147483647) | +| bigint(m) | 8个字节 范围(+-9.22*10的18次方) | + +int(m) m 用于指定显示宽度,int(5)表示5位数的宽度。注意显示宽度属性不能控制列可以存储的值范围,显示宽度属性通常由应用程序用于格式化整数值。 + +## 浮点型 + +| MySQL数据类型 | 含义 | +| ------------- | ------------------------------------- | +| float(m,d) | 单精度浮点型 4字节 m总个数,d小数位 | +| double(m,d) | 双精度浮点型 8字节 m总个数,d小数位 | + +浮点型数据类型会有精度丢失的问题,比如小数位设置6位,存入0.45,0.45转换成二进制是个无限循环小数0.01110011100...,无法准确表示,存储的时候会发生精度丢失。 + +不论是定点还是浮点类型,如果用户指定的精度超出精度范围,则会四舍五入进行处理。 + +## 定点数 + +浮点型在数据库中存放的是近似值,而定点类型在数据库中存放的是精确值,不会丢失精度。定点数以字符串形式存储。 + +decimal(m,d) 参数m是总个数,d是小数位。 + +## 字符串 + +| MySQL数据类型 | 含义 | +| ------------- | ------------------------------- | +| char(n) | 固定长度,最多255个字节 | +| varchar(n) | 可变长度,最多65535个字节 | +| tinytext | 可变长度,最多255个字节 | +| text | 可变长度,最多65535个字节 | +| mediumtext | 可变长度,最多2的24次方-1个字节 | +| longtext | 可变长度,最多2的32次方-1个字节 | + +查询速度:char > varchar > text + +char:定长,效率高,一般用于固定长度的表单提交数据存储 ;例如:身份证号,手机号,电话,密码等。char长度不足时,在右边使用空格填充,而varchar值保存时只保存需要的字符数。 + +varchar:不定长,效率偏低,内容开头用1到2个字节表示实际长度(长度超过255时需要2个字节),因此最大长度不能超过65535。 + +nvarchar(存储的是Unicode数据类型的字符)不管是一个字符还是一个汉字,都存为2个字节 ,一般用作中文或者其他语言输入,这样不容易乱码 ;varchar存储汉字是2个字节,其他字符存为1个字节 ,varchar适合输入英文和数字。 + +text:不需要指定存储长度,能用varchar就不用text。 + +## 二进制数据(BLOB) + +二进制数据类型可存储任何数据,如图像、多媒体、文档等。 + +BLOB和TEXT存储方式不同,TEXT以文本方式存储,英文存储区分大小写;而Blob是以二进制方式存储,不区分大小写。 + +## 日期时间类型 + +| MySQL数据类型 | 含义 | +| ------------- | ----------------------------- | +| date | 日期 '2008-12-2' | +| time | 时间 '12:25:36' | +| datetime | 日期时间 '2008-12-2 22:06:44' | +| timestamp | 自动存储记录修改时间 | + +若定义一个字段为timestamp,这个字段里的时间数据会随其他字段修改的时候自动刷新,所以这个数据类型的字段可以存放这条记录最后被修改的时间。 + + diff --git a/database/mysql-basic/10-view.md b/database/mysql-basic/10-view.md new file mode 100644 index 0000000..f770fb5 --- /dev/null +++ b/database/mysql-basic/10-view.md @@ -0,0 +1,50 @@ +# 视图 + +视图为虚拟的表。视图提供了一种MySQL的SELECT语句层次的封装,可用来简化数据处理以及重新格式化基础数据或保护基础数据。 + +## 应用 + +- 重用SQL语句。 +- 保护数据。可以给用户授予表的特定部分的访问权限而不是整个表的访问权限。 +- 更改数据格式和表示。视图可返回与底层表的表示和格式不同的数据。 + +## 限制 + +- 与表一样,视图必须唯一命名 +- 视图不能索引,也不能有关联的触发器或默认值。 +- 视图可以和表一起使用。例如,编写一条联结表和视图的SELECT语句。 +- ORDER BY可以用在视图中,但如果从该视图检索数据SELECT中也含有ORDER BY,那么该视图中的ORDER BY将被覆盖。 + +## 语法 + +`CREATE VIEW`:创建视图 + +`SHOW CREATE VIEW viewname`:查看创建视图的语句 + +`DROP VIEW viewname`:删除视图 + +`CREATE ORREPLACE VIEW`:更新视图,相当于先用`DROP`再用`CREATE` + +## 简化复杂连接 + +创建一个视图,返回订购了任意产品的客户列表。 + +```mysql +CREATE VIEW productcustomers AS +SELECT cust_name, orders, orderitems +FROM customers, orders, orderitems +WHERE orderitems.order_num = orders.order_num + AND customers.cust_id = orders.cust_id; +``` + +使用视图: + +```mysql +SELECT cust_name, cust_contact +FROM productcustomers +WHERE prod_id = 'nike'; +``` + +## 更新视图 + +对视图增加或删除行,实际上是对其基表增加或删除行。视图主要用于数据检索。 \ No newline at end of file diff --git a/database/mysql-basic/11-procedure.md b/database/mysql-basic/11-procedure.md new file mode 100644 index 0000000..f03c531 --- /dev/null +++ b/database/mysql-basic/11-procedure.md @@ -0,0 +1,101 @@ +# 存储过程 + +为以后的使用而保存的一条或多条MySQL语句的集合。可将其视为批文件。 +为什么使用存储过程: + +- 把复杂处理进行封装,简化复杂的操作; +- 提高性能,存储过程比单独SQL语句更快; + +## 创建 + +返回产品平均价格的存储过程: + +```mysql +CREATE PROCEDURE productpricing() # 可以接受参数 +BEGIN + SELECT Avg(prod_price) AS priceaverage + FROM products; +END; +``` + +BEGIN/END 用来限定存储过程体。此段代码仅创建了存储过程,未执行。 + +## 调用 + +`CALL productpricing()` + +## 删除 + +存储过程在创建之后,被保存在服务器上以供使用,直至被删除。 +`DROP PROCEDURE productpricing IF EXISTS` + +## 参数 + +MySQL支持IN(传递给存储过程)、OUT(从存储过程传出)和INOUT(对存储过程传入和传出)类型的参数。 +接受订单号并返回该订单的金额: + +```mysql +CREATE PROCEDURE ordertotal( + IN ordernum INT, + OUT ordersum DECIMAL(8, 2) +) +BEGIN + SELECT Sum(item_price * quantity) + FROM orderitems + WHERE order_num = ordernum + INTO ordersum; +END; +``` + +调用存储过程:`CALL ordertotal(20, @total);` +显示订单金额:`SELECT @total;` + +## 实例 + +获取订单税后金额(订单金额+税收)。 + +```mysql +CREATE PROCEDURE ordertotal( + IN onum INT, + IN taxable BOOLEAN, # 是否计税 + OUT ototal DECIMAL(8, 2) +) COMMENT 'order total, adding tax' +BEGIN + DECLARE total DECIMAL(8, 2); + DECLARE taxrate INT DEFAULT 6; + + SELECT Sum(item_price * quanlity) + FROM orderitems + WHERE order_num = onum + INTO total; + + IF taxable THEN + SELECT total + (total / 100 * taxrate) INTO total; + END IF; + -- SELECT total INTO ototal; +END; +``` + +调用存储过程: + +```mysql +CALL ordertotal(20005, 1, @total); +SELECT @total; +``` + +## 查看 + +创建存储过程的 CREATE 语句。 + +```mysql +SHOW CREATE PROCEDURE ordertotal; +``` + +获得包括何时、由谁创建等详细信息的存储过程列表,使用`SHOW PROCEDURE STATUS LIKE 'ordertotal';` + +查看存储过程状态: + +```mysql +SHOW PROCEDURE status; +``` + diff --git a/database/mysql-basic/12-cursor.md b/database/mysql-basic/12-cursor.md new file mode 100644 index 0000000..dad75b4 --- /dev/null +++ b/database/mysql-basic/12-cursor.md @@ -0,0 +1,58 @@ +# 游标 + +存储了游标之后,应用程序可以根据需要滚动或浏览其中的数据。MySQL游标只能用于存储过程(和函数)。 + +## 创建游标 + +DECLARE 命名游标。存储过程处理完成后,游标便消失(游标只存在于存储过程)。定义游标之后,便可以打开它。 + +```mysql +CREATE PROCEDURE processorders() +BEGIN + DECLARE ordernumbers CURSOR + FOR + SELECT order_num FROM orders; +END; +``` + +## 使用游标 + +`OPEN ordernumbers` 打开游标。 +`CLOSE ordernumbers` CLOSE释放游标使用的所有内部内存和资源。 + +```mysql +CREATE PROCEDURE processorders() +BEGIN + + DECLARE done BOOLEAN DEFAULT 0; + DECLARE o INT; + DECLARE t DECIMAL(8, 2); + + DECLARE ordernumbers CURSOR + FOR + SELECT order_num FROM orders; + + DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done=1; #游标移到最后 + + CREATE TABLE IF NOT EXISTS ordertotals + (order_num INT, total DECIMAL(8,2)); + -- 打开游标 + OPEN ordernumbers; + + -- 循环 + REPEAT + FETCH ordernumbers INTO o; + CALL ordertotal(o, 1, t); + + -- 插入订单号和订单金额 + INSERT INTO ordertotals(order_num, total) + VALUES(o, t); + + -- done为1结束循环 + UNTIL done END REPEAT; + + CLOSE ordernumbers; +END; +``` + +存储过程还在运行中创建了一个新表,。这个表将保存存储过程生成的结果。FETCH取每个order_num,然后用CALL执行另一个存储过程,计算每个订单税后金额。最后,用INSERT保存每个订单的订单号和金额。 \ No newline at end of file diff --git a/database/mysql-basic/13-trigger.md b/database/mysql-basic/13-trigger.md new file mode 100644 index 0000000..f05b92f --- /dev/null +++ b/database/mysql-basic/13-trigger.md @@ -0,0 +1,51 @@ +# 触发器 + +触发器提供SQL语句自动执行的功能。DELETE/INSERT/UPDATE支持触发器,其他SQL语句不支持。 + +## 创建 + +创建触发器四要素:1.唯一的触发器名(MySQL5规定触发器名在表中唯一,数据库没要求);2.触发器关联的表;3.相应的SQL语句;4.何时执行(处理之前或者之后)。 + +```mysql +CREATE TRIGGER newproduct AFTER INSERT ON products #插入之后执行 +FOR EACH ROW SELECT 'product added'; #对每个插入行执行 +``` + +只有表支持触发器,视图不支持。单一触发器不能与多个事件或多个表关联,如果需要对INSERT和UPDATE操作执行触发器,则应该定义两个触发器。 + +## 删除 + +`DROP TRIGGER newproduct` + +## 使用 + +INSERT 触发器可饮用名为 NEW 的虚拟表,访问被插入的行。NEW中的值也可以被更新(允许更改被插入的值)。 + +```mysql +CREATE TRIGGER neworder AFTER INSERT ON order +FOR EACH ROW SELECT NEW.order_num; #返回新的订单号 +``` + +DELETE 触发器可以引用名为 OLD 的虚拟表,访问被删除的行。OLD中的值全都是只读的,不能更新。 + +```mysql +CREATE TRIGGER deleteorder BEFORE DELETE ON orders +FOR EACH ROW +BEGIN + INSERT INTO archive_orders(order_num, cust_id) + VALUES(OLD.order_num, OLD.cust_id); +END; +``` + +订单删除之前保存订单信息到存档表。 + +UPDATE 触发器可以引用名为 OLD 的虚拟表访问以前的值,引用一个名为NEW的虚拟表访问新更新的值。NEW 值可被更新,OLD 值是只读的。 + +下面的例子保证州名缩写总是大写。 + +```mysql +CREATE TRIGGER updatevendor BEFORE UPDATE ON vendor +FOR EACH ROW SET NEW.vend_state = Upper(NEW.vend_state); +``` + + diff --git a/database/mysql-basic/14-transaction.md b/database/mysql-basic/14-transaction.md new file mode 100644 index 0000000..1526284 --- /dev/null +++ b/database/mysql-basic/14-transaction.md @@ -0,0 +1,32 @@ +# 事务处理 + +事务处理可以用来维护数据库的完整性。它保证成批的MySQL操作要么完全执行,要么完全不执行。 + +CREATE/DROP 操作不能回退,即便可以执行回退操作,回退不会有效果。 + +执行事务过程,一旦某个SQL失败,则之前执行成功的SQL会被自动撤销。 + +## 语法 + +```mysql +START TRANSACTION; +DELETE FROM orderitems WHERE order_num = 20010; +DELETE FROM orders WHERE order_num = 20010; +COMMIT; +``` + +当COMMIT或ROLLBACK语句执行后,事务会自动关闭。 + +## 保留点 + +为了支持回退部分事务处理,必须能在事务处理块中合适的位置放置占位符。这样,如果需要回退,可以回退到某个占位符。 + +保留点在事务处理完成后自动释放。 + +```mysql +... +SAVEPOINT delete1; +... +ROLLBACK TO delete1; +``` + diff --git a/database/mysql-basic/15-permission.md b/database/mysql-basic/15-permission.md new file mode 100644 index 0000000..e65754c --- /dev/null +++ b/database/mysql-basic/15-permission.md @@ -0,0 +1,35 @@ +# 权限 + +MySQL用户账号和信息存储在名为mysql的MySQL数据库中。 + +获取用户账号列表: + +```mysql +USE mysql; +SELECT user FROM user; +``` + +创建用户账号:`CREATE USER tyson IDENTIFIED BY 'abc123'` + +修改密码:`SET PASSWORD FOR tyson = Password('xxx');`,新密码需传递到Password()函数进行加密。 + +设置当前用户密码:`SET PASSWORD = Password('xxx');` + +重命名账号:`RENAME USER tyson TO tom` + +删除用户账号:`DROP USER tyson` + +查看访问权限:`SHOW GRANTS FOR tyson`,返回`USAGE ON *.*`则表示没有权限。 + +授予访问权限:`GRANT SELECT ON mall.# TO tyson`,允许用户在mall数据库所有表使用SELECT。 + +撤销权限:`REVOKE SELECT, INSERT ON mall.* FROM tyson`,被撤销的访问权限必须存在,否则会出错。 + +GRANT和REVOKE可在几个层次上控制访问权限: + +- 整个服务器,使用GRANT ALL和REVOKE ALL; +- 整个数据库,使用ON database.*; +- 特定的表,使用ON database.table; +- 特定的列; +- 特定的存储过程。 + diff --git a/database/mysql-basic/16-performace-optimization.md b/database/mysql-basic/16-performace-optimization.md new file mode 100644 index 0000000..8fe6e47 --- /dev/null +++ b/database/mysql-basic/16-performace-optimization.md @@ -0,0 +1,18 @@ +# 性能优化 + +使用EXPLAIN语句让MySQL解释它将如何执行一条SELECT语句。 + +如果一个简单的WHERE子句返回结果所花的时间太长,则可以断定其中使用的某些列就是需要索引的对象。 + +避免使用OR。通过使用多条SELECT语句和连接它们的UNION语句,会有极大的性能改进。 + +LIKE很慢,最好是使用FULLTEXT而不是LIKE。 + +很多高性能的应用都会对关联查询进行分解,有如下的优势: + +1 、让缓存效率更高。如果某张表很少变化,那么基于该表的查询就可以重复利用查询缓存结果。 +2 、将查询分解后,执行单个查询可以减少锁的竞争。 +3 、在应用层做关联,可以更容易对数据库进行拆分,更容易做到高性能和可扩展。 +4 、查询本身效率也可能会有所提升。例如 IN()代替关联查询,可能比随机的关联更高效。 +5 、减少冗余记录得查询。 +6 、更进一步,这样做相当于在应用中实现了哈希关联,而不是使用 MySQL 得嵌套循环关联。某些场景哈希关联得效率要高很多。 \ No newline at end of file diff --git a/database/mysql-basic/17-index.md b/database/mysql-basic/17-index.md new file mode 100644 index 0000000..a28dc43 --- /dev/null +++ b/database/mysql-basic/17-index.md @@ -0,0 +1,36 @@ +# 索引 + +## 创建索引 + +ALTER TABLE用来创建普通索引、UNIQUE索引或PRIMARY KEY索引。 + +```mysql +ALTER TABLE table_name ADD INDEX index_name (column_list) +ALTER TABLE table_name ADD UNIQUE (column_list) +ALTER TABLE table_name ADD PRIMARY KEY (column_list) +``` + +CREATE INDEX可对表增加普通索引或UNIQUE索引。 + +```mysql +CREATE INDEX index_name ON table_name (column_list) +CREATE UNIQUE INDEX index_name ON table_name (column_list) +``` + +在创建索引时,可以规定索引能否包含重复值。如果不包含,则索引应该创建为PRIMARY KEY或UNIQUE索引。 + +## 删除索引 + +```mysql +DROP INDEX index_name ON talbe_name +ALTER TABLE table_name DROP INDEX index_name +ALTER TABLE table_name DROP PRIMARY KEY #只有一个主键,不需要指定索引名 +``` + +## 查看索引 + +```mysql +show index from tblname; +show keys from tblname; +``` + diff --git a/database/mysql-basic/2-basic-command.md b/database/mysql-basic/2-basic-command.md new file mode 100644 index 0000000..621b0b4 --- /dev/null +++ b/database/mysql-basic/2-basic-command.md @@ -0,0 +1,230 @@ +# 基本命令 +## 启动 + +启动服务:`service mysqld start` + +关闭服务:`service mysqld stop` + +启动客户端:`mysql -uroot -p` -u 后不要有空格(Ubuntu有空格) + +## 数据库操作 + +```mysql +SHOW DATABASES; +CREATE DATABASE db_name; +USE db_name; +DROP DATABASE db_name; +``` + +## 表 + +创建表:`create table user (id int, name varchar(10))` + +清空表数据:`truncate table user;` + +查看表结构:`desc table_name/select columns from table_name` + +`SHOW CREATE db` | `SHOW CREATE table`:显示创建特定数据库或表的MySQL语句 + +## 检索 + +检索不同的行: + +```mysql +SELECT DISTINCT vend_id +FROM products; +``` + +限制结果: + +```mysql +SELECT prod_name +FROM products +LIMIT 0, 5; #开始位置,行数|返回从第0行开始的5行数据 +``` + +## 排序 + +```mysql +SELECT prod_name +FROM products +ORDER BY prod_name, prod_price DESC; #先按名称排序,再按价格排序 | DESC降序排列,默认ASC升序 +``` + +找出最贵的物品: + +```mysql +SELECT prod_price +FROM products +ORDER BY prod_price DESC +LIMIT 1; # 仅返回一行 +``` + +子句顺序:FORM -- ORDER BY -- LIMIT,顺序不对会报错。 + +## 过滤 + +子句操作符: + +| 操作符 | 说明 | +| ------- | -------- | +| = | 等于 | +| <> | 不等于 | +| != | 不等于 | +| < | 小于 | +| <= | 小于等于 | +| BETWEEN | 两值之间 | + +### 不匹配检查: + +```mysql +SELECT vend_id, prod_name +FROM products +WHERE vend_id <> 1003; +``` + +### 范围查询: + +```mysql +SELECT prod_name, prod_price +FROM products +WHERE prod_price BETWEEN 5 AND 10; +``` + +### 空值检查 + +```mysql +SELECT prod_name +FROM products +WHERE prod_price IS NULL; +``` + +### 计算次序 + +```mysql +SELECT prod_name, prod_price +FROM products +WHERE vend_id = 1002 OR vend_id = 1003 AND prod_price >= 10; # AND优先级大于OR +``` + +### IN 操作符 + +```mysql +SELECT prod_name, product_price +FROM products +WHERE vend_id IN (1002, 1003) +ORDER BY prod_name; +``` + +IN操作符一般比OR操作符清单执行更快。IN的最大优点是可以包含其他SELECT语句,使得能够更动态地建立WHERE子句。 + +### NOT操作符 + +MySQL支持使用NOT 对IN 、BETWEEN 和EXISTS子句取反。 + +```mysql +SELECT prod_name, product_price +FROM products +WHERE vend_id NOT IN (1002, 1003) +``` + +### LIKE操作符 + +% 匹配0到多个任意字符。 + +```mysql +SELECT prod_id, prod_name +FROM products +WHERE prod_name LIKE '%jet%'; +``` + +_ 匹配单个字符。 + +```mysql +SELECT prod_id, prod_name +FROM products +WHERE prod_name LIKE '_jet_'; +``` + +通配符搜索比其他简单搜索耗时,不能过度使用通配符。 + +### LIMIT + +limit 0,4 :从第0条记录开始,取4条 + +### 正则表达式 + +OR 匹配: + +```mysql +SELECT prod_name +FROM products +WHERE prod_name REGEXP '1000|2000' +ORDER BY prod_name; +``` + +匹配特定字符: + +```mysql +SELECT prod_name +FROM products +WHERE prod_name REGEXP '[123] Rely' #匹配1或2或3 [^123]取反 +ORDER BY prod_name; +``` + +匹配范围: + +```mysql +SELECT prod_name +FROM products +WHERE prod_anem REGEXP '[1-5] Ton';#匹配1-5任意一个数字,[a-z]同理 +``` + +匹配特殊字符: + +```mysql +SELECT prod_name +FROM products +WHERE prod_anem REGEXP '\\.';#转义 +``` + +匹配多个实例: + +```mysql +SELECT prod_name +FROM products +WHERE prod_anem REGEXP '\\([0-9] sticks?\\)'; #?匹配它前面的任何字符出现0次或1次 +``` + +匹配连着的四个数: + +```mysql +SELECT prod_name +FROM products +WHERE prod_anem REGEXP '[[:digit:]]{4}'; #[:digit:]匹配任意数字 +``` + +定位符: + +| 元字符 | 说明 | +| ------- | -------- | +| ^ | 文本开始 | +| $ | 文本结束 | +| [[:<:]] | 词开始 | +| [[:>:]] | 词结束 | + +查找一个数(包括小数点开始的数)开始的所有产品: + +```mysql +SELECT prod_name +FROM products +WHERE prod_name REGEXP '^[0-9\\.]' +ORDER BY prod_name; +``` + +简单的正则表达式测试: + +```mysql +SELECT 'hello' REGEXP '[0-9]';#REGEXP检查返回0或1;此处返回0 +``` + diff --git a/database/mysql-basic/3-function.md b/database/mysql-basic/3-function.md new file mode 100644 index 0000000..80f3df4 --- /dev/null +++ b/database/mysql-basic/3-function.md @@ -0,0 +1,42 @@ +# 函数 +## 文本处理 + +```mysql +SELECT vend_name, Upper(vend_name) AS vend_name_upcase +FROM vendors +ORDER BY vend_name; +``` + +Soundex()函数,匹配所有同音字符串。 + +```mysql +SELECT cust_name, cust_contact +FROM customers +WHERE Soundex(cust_contact) = Soundex('Y Lie'); +``` + +返回数据:`Tyson Y lee` + +## 日期处理函数 + +![](http://img.dabin-coder.cn/image/20220530235607.png) +查找2005年9月的所有订单: + +```mysql +SELECT cust_id, order_num +FROM orders +WHERE Date(order_date) BETWEEN '2005-09-01' AND '2005-09-30'; +``` + +或者 + +```mysql +SELECT cust_id, order_num +FROM orders +WHERE Year(order_date) = 2005 AND Month(order_date) = 9; +``` + +## 数值处理函数 + +![](http://img.dabin-coder.cn/image/20220530233617.png) + diff --git a/database/mysql-basic/4-sum.md b/database/mysql-basic/4-sum.md new file mode 100644 index 0000000..c2f38ec --- /dev/null +++ b/database/mysql-basic/4-sum.md @@ -0,0 +1,22 @@ +# 聚集函数 + +Sum:求和 +Avg:求平均数 +Max:求最大值 + Min:求最小值 + Count:求记录 + +```mysql +SELECT SUM(item_price*quanlity) AS total_price +FROM orderitems +WHERE order_num = 2005; +``` + +聚集不同值: + +```mysql +SELECT AVG(DISTINCT prod_price) AS avg_price #只考虑不同价格 +FROM products +WHERE vend_id = 1003; +``` + diff --git a/database/mysql-basic/5-group.md b/database/mysql-basic/5-group.md new file mode 100644 index 0000000..f839dea --- /dev/null +++ b/database/mysql-basic/5-group.md @@ -0,0 +1,56 @@ +# 分组 + +单独地使用group by没意义,它只能显示出每组记录的第一条记录。 + +```mysql +SELECT * FROM orders +GROUP BY cust_id; +``` + +![](http://img.dabin-coder.cn/image/20220530233523.png) + +除聚集计算语句外,SELECT语句中的每个列都必须在GROUP BY子句中给出。 + +```mysql +SELECT vend_id, COUNT(*) AS num_prods #vend_id在GROUP BY子句给出 +FROM products +GROUP BY vend_id; +``` + +GROUP BY子句必须出现在WHERE子句之后,ORDER BY子句之前。 + +## 过滤分组 + +having 用来分组查询后指定一些条件来输出查询结果,having作用和where类似,但是having只能用在group by场合,并且必须位于group by之后order by之前。 + +```mysql +SELECT cust_id, COUNT(*) AS orders +FROM orders +GROUP BY cust_id +HAVING COUNT(*) >= 2; +``` + +## having和where区别 + +```mysql +SELECT cust_id FROM orders GROUP BY cust_id HAVING COUNT(cust_id) >= 2; +SELECT cust_id FROM orders GROUP BY cust_id WHERE COUNT(cust_id) >= 2; #Error Code : 1064 +``` + +第一个sql语句可以执行,但是第二个会报错。 + +- WHERE子句不起作用,因为过滤是基于分组聚集值而不是特定行值的。 + +- 二者作用的对象不同,where子句作用于表和视图,having作用于组。 + +- WHERE在数据分组前进行过滤,HAVING在数据分组后进行过滤。 + +```mysql +SELECT vend_id, COUNT(*) AS num_prods +FROM products +WHERE prod_price >= 10 +GROUP BY vend_id +HAVING COUNT(*) >= 2; +``` + +WHERE子句过滤所有prod_price至少为10的行。然后按vend_id分组数据,HAVING子句过滤计数为2或2以上的分组。 diff --git a/database/mysql-basic/6-join.md b/database/mysql-basic/6-join.md new file mode 100644 index 0000000..359ab9f --- /dev/null +++ b/database/mysql-basic/6-join.md @@ -0,0 +1,92 @@ +# 连接 +## 内连接 + +找出供应商生产的产品。 + +```mysql +SELECT vend_name, prod_name +FROM vendors INNER JOIN products +ON vendors.vend_id = products.vend_id; #连接条件使用on子句 +``` + +等价于: + +```mysql +SELECT vend_name, prod_name +FROM vendors, products +WHERE vendors.vend_id = products.vend_id; +``` + +没有给出连接条件的话,会得到两张表的笛卡尔积。 + +## 自连接 + +找出生产nike的供应商生产的所有物品。 + +```mysql +SELECT prod_id, prod_name +FROM products AS p1, products AS p2 +WHERE p1.vend_id = p2.vend_id + AND p2.prod_id = 'nike'; +``` + +## 自然连接 + +natural join是对两张表中字段名和数据类型都相同的字段进行**等值连接**,并返回符合条件的结果 。 + +```mysql +SELECT * FROM role NATURAL JOIN user_role; +``` + +返回结果: + +![](http://img.dabin-coder.cn/image/20220530235619.png) + +## 内连接 + +显示符合连接条件的记录。没有设置连接条件则返回笛卡尔积的结果。join 默认是 inner join。 + +```mysql +SELECT * FROM role INNNER JOIN user_role +``` + +返回结果: + +![](http://img.dabin-coder.cn/image/20220530235640.png) + +join…using(column)按指定的属性做等值连接。 +join…on tableA.column1 = tableB.column2 指定条件。 + +```mysql +SELECT * FROM role INNER JOIN user_role ON role.role_id = user_role.role_id +``` + +返回结果: + +![](http://img.dabin-coder.cn/image/20220530235654.png) + +## 外连接 + +左外联接(Left Outer Join):除了匹配2张表中相关联的记录外,还会匹配左表中剩余的记录,右表中未匹配到的字段用NULL表示。 +右外联接(Right Outer Join):除了匹配2张表中相关联的记录外,还会匹配右表中剩余的记录,左表中未匹配到的字段用NULL表示。 +在判定左表和右表时,要根据表名出现在Outer Join的左右位置关系。 + +查找所有客户及其订单,包括没有下过订单的客户。使用左外连接,保留左边表的所有记录。 + +```mysql +SELECT customer.cust_id, order.order_num +FROM customers LEFT OUTER JOIN orders +ON customers.cust_id = order.cust_id; +``` + +## 多表连接 + +```mysql +SELECT goal.player, eteam.teamname, game.stadium, game.mdate +FROM game JOIN goal +ON game.id = goal.matchid +JOIN eteam +ON eteam.id = goal.teamid +WHERE eteam.id = 'GRE' +``` + diff --git a/database/mysql-basic/7-full-text-query.md b/database/mysql-basic/7-full-text-query.md new file mode 100644 index 0000000..f122e0e --- /dev/null +++ b/database/mysql-basic/7-full-text-query.md @@ -0,0 +1,39 @@ +# 全文搜索 +为了进行全文本搜索,必须索引被搜索的列,而且要随着数据的改变不断地重新索引。在对表列进行适当设计后,MySQL会自动进行所有的索引和重新索引。 + +启动全文搜索(仅在MyISAM数据库引擎中支持全文本搜索): + +```mysql +CREATE TABLE productnotes +( + note_id int NOT NULL AUTO_INCREMENT, + note_text text NULL, + PRIMARY KEY(note_id), + FULLTEXT(note_text) +) ENGINE=MyISAM; +``` + +在定义之后,MySQL自动维护该索引。在增加、更新或删除行时,索引随之自动更新。 + +不要在导入数据时使用FULLTEXT,。应该首先导入所有数据,然后再修改表,定义FULLTEXT,这样可以更快导入数据。 + +使用全文搜索: + +```mysql +SELECT note_text +FROM productnotes +WHERE Match(note_text) Against('shoe'); #Match指定搜索列,Against指定搜索词 +``` + +返回结果:`nike shoes is good`,搜索不区分大小写。 + +全文搜索会对返回结果进行排序,具有高等级的行先返回: + +```mysql +SELECT note_text, + Match(note_text) Against('shoes') AS rank #等级由MySQL根据行中词的数目、唯一词的数目、整个索引中词的总数以及包含该词的行的数目计算出来。 +FROM productnotes; +``` + +全文搜索数据是有索引的,速度快。 + diff --git a/database/mysql-basic/8-table-operate.md b/database/mysql-basic/8-table-operate.md new file mode 100644 index 0000000..e3f6fd9 --- /dev/null +++ b/database/mysql-basic/8-table-operate.md @@ -0,0 +1,105 @@ +# 表操作 + +## 创建表 + +```mysql +CREATE TABLE productnotes +( + note_id int NOT NULL AUTO_INCREMENT, + note_text text NULL, + quanlity int NOT NULL DEFAULT 1, # 默认值,只支持常量 + PRIMARY KEY(note_id), + FULLTEXT(note_text) +) ENGINE=MyISAM; +``` + +主键中只能使用NOT NULL值的列。 + +## 更新表 + +数据库表的更改不能撤销,应先做好备份。 + +添加列: + +```mysql +ALTER TABLE vendors +ADD vend_phone CHAR(20); +``` + +删除列: + +```mysql +ALTER TABLE vendors +DROP COLUMN vend_phone; +``` + +更改列属性: + +```mysql +ALTER TABLE vendors +MODIFY vend_phone CHAR(16); +``` + +复杂的表结构更改一般需要手动删除过程: + +- 用新的列布局创建一个新表; + +- 使用INSERT SELECT语句,从旧表复制数据到新表; + +- 检验包含所需数据的新表; +- 重命名旧表(如果确定,可以删除它); + +- 用旧表原来的名字重命名新表; + +- 重新创建触发器、存储过程、索引和外键。 + +## 约束 + +添加主键约束: + +```mysql +ALTER TABLE vendors +ADD CONSTRAINT pk_vendors PRIMARY KEY(vend_id); +``` + +删除主键约束: + +```mysql +ALTER TABLE vendors +DROP PRIMARY KEY; +``` + +添加外键约束: + +```mysql +ALTER TABLE products +ADD FOREIGN KEY(vendor_id) REFERENCES vendors(vendor_id); +``` + +删除外键约束: + +```mysql +ALTER TABLE products DROP FOREIGN KEY vendor_id; +``` + +## 删除表 + +`DROP TABLE cumstomers` + +## truncate、delete与drop区别 + +**相同点:** + +1. truncate和不带where子句的delete、以及drop都会删除表内的数据。 + +2. drop、truncate都是DDL语句(数据定义语言),执行后会自动提交。 + +**不同点:** + +1. truncate 和 delete 只删除数据不删除表的结构;drop 语句将删除表的结构被依赖的约束、触发器、索引; + +2. 速度,一般来说: drop> truncate > delete。 + +## 重命名表 + +`RENAME TABLE cusmtomers TO cust` diff --git a/database/mysql-basic/9-column-operate.md b/database/mysql-basic/9-column-operate.md new file mode 100644 index 0000000..536c1ee --- /dev/null +++ b/database/mysql-basic/9-column-operate.md @@ -0,0 +1,48 @@ +# 列操作 + +## 插入数据 + +MySQL用单条INSERT语句处理多个插入比使用多条INSERT语句快。 + +```mysql +INSERT INTO customers(cust_name, cust_city) + VALUES('Tyson', 'GD'), + ('sophia','GZ'); +``` + +INSERT操作可能很耗时(特别是有很多索引需要更新时),而且它可能降低等待处理的SELECT语句的性能。降低INSERT语句的优先级:`INSERT LOW_PRIORITY INTO` + +## 更新数据 + +如果用UPDATE语句更新多行,并且在更新这些行中的一行或多行时出一个现错误,则整个UPDATE操作被取消。为了在发生错误时也继续进行更新,可使用IGNORE关键字:`UPDATE IGNORE customers...` + +```mysql +UPDATE customers +SET cust_city = NULL +WHERE cust_id = 1005; +``` + +返回值是受影响的记录数。 + +## 删除数据 + +如果想从表中删除所有行,不要使用DELETE。可使用TRUNCATE TABLE语句,它完成相同的工作,但速度更快(TRUNCATE实际是删除原来的表并重新创建一个表,而不是逐行删除表中的数据)。 + +```mysql +DELETE FROM customers +WHERE cust_id = 1006; +``` + +如果执行DELETE语句而不带WHERE子句,表的所有数据都将被删除。MySQL没有撤销操作,应该非常小心地使用UPDATE和DELETE。 + +delete使用别名的时候,要在delete和from间加上删除表的别名。 + +```mysql +DELETE a #加上删除表的别名 +FROM table1 a +WHERE a.status = 0 +AND EXISTS + (SELECT b.id FROM table2 b + WHERE b.id = a.id); +``` + diff --git a/database/mysql-basic/README.md b/database/mysql-basic/README.md new file mode 100644 index 0000000..52e7c6f --- /dev/null +++ b/database/mysql-basic/README.md @@ -0,0 +1,11 @@ +--- +title: MySQL基础 +icon: linux +date: 2022-08-06 +category: MySQL +star: true +--- + +**本专栏是大彬学习MySQL基础知识的学习笔记,如有错误,可以在评论区指出**~ + +![](http://img.dabin-coder.cn/image/MySQL知识点总结.jpg) diff --git a/database/mysql-basic/data-type.md b/database/mysql-basic/data-type.md new file mode 100644 index 0000000..18394e2 --- /dev/null +++ b/database/mysql-basic/data-type.md @@ -0,0 +1,82 @@ +### 数据类型 + +主要包括以下五大类: + +整数类型:BIT、BOOL、TINY INT、SMALL INT、MEDIUM INT、 INT、 BIG INT + +浮点数类型:FLOAT、DOUBLE、DECIMAL + +字符串类型:CHAR、VARCHAR、TINY TEXT、TEXT、MEDIUM TEXT、LONGTEXT、TINY BLOB、BLOB、MEDIUM BLOB、LONG BLOB + +日期类型:Date、DateTime、TimeStamp、Time、Year + +其他数据类型:BINARY、VARBINARY、ENUM、SET、Geometry、Point、MultiPoint、LineString、MultiLineString、Polygon、GeometryCollection等 + +#### 整型 + +| MySQL数据类型 | 含义(有符号) | +| ------------- | ------------------------------------ | +| tinyint(m) | 1个字节 范围(-128~127) | +| smallint(m) | 2个字节 范围(-32768~32767) | +| mediumint(m) | 3个字节 范围(-8388608~8388607) | +| int(m) | 4个字节 范围(-2147483648~2147483647) | +| bigint(m) | 8个字节 范围(+-9.22*10的18次方) | + +int(m) m 用于指定显示宽度,int(5)表示5位数的宽度。注意显示宽度属性不能控制列可以存储的值范围,显示宽度属性通常由应用程序用于格式化整数值。 + +#### 浮点型 + +| MySQL数据类型 | 含义 | +| ------------- | ------------------------------------- | +| float(m,d) | 单精度浮点型 4字节 m总个数,d小数位 | +| double(m,d) | 双精度浮点型 8字节 m总个数,d小数位 | + +浮点型数据类型会有精度丢失的问题,比如小数位设置6位,存入0.45,0.45转换成二进制是个无限循环小数0.01110011100...,无法准确表示,存储的时候会发生精度丢失。 + +不论是定点还是浮点类型,如果用户指定的精度超出精度范围,则会四舍五入进行处理。 + +#### 定点数 + +浮点型在数据库中存放的是近似值,而定点类型在数据库中存放的是精确值,不会丢失精度。定点数以字符串形式存储。 + +decimal(m,d) 参数m是总个数,d是小数位。 + +#### 字符串 + +| MySQL数据类型 | 含义 | +| ------------- | ------------------------------- | +| char(n) | 固定长度,最多255个字节 | +| varchar(n) | 可变长度,最多65535个字节 | +| tinytext | 可变长度,最多255个字节 | +| text | 可变长度,最多65535个字节 | +| mediumtext | 可变长度,最多2的24次方-1个字节 | +| longtext | 可变长度,最多2的32次方-1个字节 | + +查询速度:char > varchar > text + +char:定长,效率高,一般用于固定长度的表单提交数据存储 ;例如:身份证号,手机号,电话,密码等。char长度不足时,在右边使用空格填充,而varchar值保存时只保存需要的字符数。 + +varchar:不定长,效率偏低,内容开头用1到2个字节表示实际长度(长度超过255时需要2个字节),因此最大长度不能超过65535。 + +nvarchar(存储的是Unicode数据类型的字符)不管是一个字符还是一个汉字,都存为2个字节 ,一般用作中文或者其他语言输入,这样不容易乱码 ;varchar存储汉字是2个字节,其他字符存为1个字节 ,varchar适合输入英文和数字。 + +text:不需要指定存储长度,能用varchar就不用text。 + +#### 二进制数据(BLOB) + +二进制数据类型可存储任何数据,如图像、多媒体、文档等。 + +BLOB和TEXT存储方式不同,TEXT以文本方式存储,英文存储区分大小写;而Blob是以二进制方式存储,不区分大小写。 + +#### 日期时间类型 + +| MySQL数据类型 | 含义 | +| ------------- | ----------------------------- | +| date | 日期 '2008-12-2' | +| time | 时间 '12:25:36' | +| datetime | 日期时间 '2008-12-2 22:06:44' | +| timestamp | 自动存储记录修改时间 | + +若定义一个字段为timestamp,这个字段里的时间数据会随其他字段修改的时候自动刷新,所以这个数据类型的字段可以存放这条记录最后被修改的时间。 + + diff --git "a/\346\225\260\346\215\256\345\272\223/MySQL\346\211\247\350\241\214\350\256\241\345\210\222.md" b/database/mysql-execution-plan.md similarity index 92% rename from "\346\225\260\346\215\256\345\272\223/MySQL\346\211\247\350\241\214\350\256\241\345\210\222.md" rename to database/mysql-execution-plan.md index eaffa77..3b48511 100644 --- "a/\346\225\260\346\215\256\345\272\223/MySQL\346\211\247\350\241\214\350\256\241\345\210\222.md" +++ b/database/mysql-execution-plan.md @@ -1,39 +1,3 @@ - - - - -- [id](#id) -- [select_type](#select_type) -- [table](#table) -- [partitions](#partitions) -- [type](#type) - - [system](#system) - - [const](#const) - - [eq_ref](#eq_ref) - - [ref](#ref) - - [ref_or_null](#ref_or_null) - - [index_merge](#index_merge) - - [range](#range) - - [index](#index) - - [all](#all) -- [possible_keys](#possible_keys) -- [key](#key) -- [ref](#ref-1) -- [rows](#rows) -- [filtered](#filtered) -- [extra](#extra) - - [using where](#using-where) - - [using index](#using-index) - - [Using where&Using index](#using-whereusing-index) - - [null](#null) - - [using index condition](#using-index-condition) - - [using temporary](#using-temporary) - - [filesort](#filesort) - - [using join buffer](#using-join-buffer) -- [参考资料](#%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99) - - - 使用 explain 输出 SELECT 语句执行的详细信息,包括以下信息: - 表的加载顺序 diff --git "a/\346\225\260\346\215\256\345\272\223/MySQL\351\253\230\351\242\221\351\235\242\350\257\225\351\242\230.md" b/database/mysql.md similarity index 99% rename from "\346\225\260\346\215\256\345\272\223/MySQL\351\253\230\351\242\221\351\235\242\350\257\225\351\242\230.md" rename to database/mysql.md index 123d7cc..5442a99 100644 --- "a/\346\225\260\346\215\256\345\272\223/MySQL\351\253\230\351\242\221\351\235\242\350\257\225\351\242\230.md" +++ b/database/mysql.md @@ -1,3 +1,5 @@ +![](http://img.dabin-coder.cn/image/MySQL知识点总结.jpg) + ## 事务的四大特性? **事务特性ACID**:**原子性**(`Atomicity`)、**一致性**(`Consistency`)、**隔离性**(`Isolation`)、**持久性**(`Durability`)。 @@ -140,11 +142,11 @@ utf8 就像是阉割版的utf8mb4,只支持部分字符。比如`emoji`表情 如果改成`collation=utf8mb4_bin`,就是指**挨个比较二进制位大小**。 -于是**"debug"和"Debug"就不是同一个单词。** +于是**"debug"和"Debug"就不是同一个单词**。 如果改成`collation=utf8mb4_bin`,就是指**挨个比较二进制位大小**。 -于是**"debug"和"Debug"就不是同一个单词。** +于是**"debug"和"Debug"就不是同一个单词**。 **那utf8mb4对比utf8有什么劣势吗?** @@ -412,8 +414,8 @@ MVCC(`Multiversion concurrency control`) 就是同一份数据保留多版本的 MVCC 的实现依赖于版本链,版本链是通过表的三个隐藏字段实现。 - `DB_TRX_ID`:当前事务id,通过事务id的大小判断事务的时间顺序。 -- `DB_ROLL_PRT`:回滚指针,指向当前行记录的上一个版本,通过这个指针将数据的多个版本连接在一起构成`undo log`版本链。 -- `DB_ROLL_ID`:主键,如果数据表没有主键,InnoDB会自动生成主键。 +- `DB_ROLL_PTR`:回滚指针,指向当前行记录的上一个版本,通过这个指针将数据的多个版本连接在一起构成`undo log`版本链。 +- `DB_ROW_ID`:主键,如果数据表没有主键,InnoDB会自动生成主键。 每条表记录大概是这样的: diff --git "a/\345\210\206\345\270\203\345\274\217/RocketMQ\345\256\236\347\216\260RPC\347\232\204\345\216\237\347\220\206.md" "b/distributed/RocketMQ\345\256\236\347\216\260RPC\347\232\204\345\216\237\347\220\206.md" similarity index 100% rename from "\345\210\206\345\270\203\345\274\217/RocketMQ\345\256\236\347\216\260RPC\347\232\204\345\216\237\347\220\206.md" rename to "distributed/RocketMQ\345\256\236\347\216\260RPC\347\232\204\345\216\237\347\220\206.md" diff --git "a/\345\210\206\345\270\203\345\274\217/\345\210\206\345\270\203\345\274\217\351\224\201.md" b/distributed/distributed-lock.md similarity index 100% rename from "\345\210\206\345\270\203\345\274\217/\345\210\206\345\270\203\345\274\217\351\224\201.md" rename to distributed/distributed-lock.md diff --git a/distributed/distributed-transaction.md b/distributed/distributed-transaction.md new file mode 100644 index 0000000..ed33589 --- /dev/null +++ b/distributed/distributed-transaction.md @@ -0,0 +1,195 @@ +## 简介 + +### 事务 + +事务是应用程序中一系列严密的操作,所有操作必须成功完成,否则在每个操作中所作的所有更改都会被撤消。也就是事务具有原子性,一个事务中的一系列的操作要么全部成功,要么一个都不做。事务应该具有 4 个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为 ACID 特性。 + +### 分布式事务 + +分布式事务是指事务的参与者,支持事务的服务器,资源服务器以及事务管理器分别位于分布式系统的不同节点之上。通常一个分布式事务中会涉及对多个数据源或业务系统的操作。分布式事务也可以被定义为一种嵌套型的事务,同时也就具有了ACID事务的特性。 + +### 强一致性、弱一致性、最终一致性 + +**强一致性** + +任何一次读都能读到某个数据的最近一次写的数据。系统中的所有进程,看到的操作顺序,都和全局时钟下的顺序一致。简言之,在任意时刻,所有节点中的数据是一样的。 + +**弱一致性** + +数据更新后,如果能容忍后续的访问只能访问到部分或者全部访问不到,则是弱一致性。 + +**最终一致性** + +不保证在任意时刻任意节点上的同一份数据都是相同的,但是随着时间的迁移,不同节点上的同一份数据总是在向趋同的方向变化。简单说,就是在一段时间后,节点间的数据会最终达到一致状态。 + +由于分布式事务方案,无法做到完全的ACID的保证,没有一种完美的方案,能够解决掉所有业务问题。因此在实际应用中,会根据业务的不同特性,选择最适合的分布式事务方案。 + +## 分布式事务的基础 + +### CAP理论 + +**Consistency**(一致性):数据一致更新,所有数据变动都是同步的(强一致性)。 + +**Availability**(可用性):好的响应性能。 + +**Partition tolerance**(分区容错性) :可靠性。 + +定理:任何分布式系统**只可同时满足二点**,没法三者兼顾。 + +CA系统(放弃P):指将所有数据(或者仅仅是那些与事务相关的数据)都放在一个分布式节点上,就不会存在网络分区。所以强一致性以及可用性得到满足。 + +CP系统(放弃A):如果要求数据在各个服务器上是强一致的,然而网络分区会导致同步时间无限延长,那么如此一来可用性就得不到保障了。坚持事务ACID(原子性、一致性、隔离性和持久性)的传统数据库以及对结果一致性非常敏感的应用通常会做出这样的选择。 + +AP系统(放弃C):这里所说的放弃一致性,并不是完全放弃数据一致性,而是放弃数据的强一致性,而保留数据的最终一致性。如果即要求系统高可用又要求分区容错,那么就要放弃一致性了。因为一旦发生网络分区,节点之间将无法通信,为了满足高可用,每个节点只能用本地数据提供服务,这样就会导致数据不一致。一些遵守BASE原则数据库,(如:Cassandra、CouchDB等)往往会放宽对一致性的要求(满足最终一致性即可),一次来获取基本的可用性。 + +### BASE理论 + +BASE 是 Basically Available(基本可用)、Soft state(软状态)和 Eventually consistent (最终一致性)三个短语的缩写。是对CAP中AP的一个扩展。 + +1. 基本可用:分布式系统在出现故障时,允许损失部分可用功能,保证核心功能可用。 +2. 软状态:允许系统中存在中间状态,这个状态不影响系统可用性,这里指的是CAP中的不一致。 +3. 最终一致:最终一致是指经过一段时间后,所有节点数据都将会达到一致。 + +BASE解决了CAP中理论没有网络延迟,在BASE中用软状态和最终一致,保证了延迟后的一致性。BASE和 ACID 是相反的,它完全不同于ACID的强一致性模型,而是通过牺牲强一致性来获得可用性,并允许数据在一段时间内是不一致的,但最终达到一致状态。 + +## 分布式事务解决方案 + +分布式事务的实现主要有以下 6 种方案: + +- 2PC 方案 +- TCC 方案 +- 本地消息表 +- MQ事务 +- Saga事务 +- 最大努力通知方案 + +### 2PC方案 + +2PC方案分为两阶段: + +第一阶段:事务管理器要求每个涉及到事务的数据库预提交(precommit)此操作,并反映是否可以提交. + +第二阶段:事务协调器要求每个数据库提交数据,或者回滚数据。 + +优点: 尽量保证了数据的强一致,实现成本较低,在各大主流数据库都有自己实现,对于MySQL是从5.5开始支持。 + +缺点: + +- 单点问题:事务管理器在整个流程中扮演的角色很关键,如果其宕机,比如在第一阶段已经完成,在第二阶段正准备提交的时候事务管理器宕机,资源管理器就会一直阻塞,导致数据库无法使用。 +- 同步阻塞:在准备就绪之后,资源管理器中的资源一直处于阻塞,直到提交完成,释放资源。 +- 数据不一致:两阶段提交协议虽然为分布式数据强一致性所设计,但仍然存在数据不一致性的可能,比如在第二阶段中,假设协调者发出了事务commit的通知,但是因为网络问题该通知仅被一部分参与者所收到并执行了commit操作,其余的参与者则因为没有收到通知一直处于阻塞状态,这时候就产生了数据的不一致性。 + +总的来说,2PC方案比较简单,成本较低,但是其单点问题,以及不能支持高并发(由于同步阻塞)依然是其最大的弱点。 + +### TCC + +TCC 的全称是:`Try`、`Confirm`、`Cancel`。 + +- **Try 阶段**:这个阶段说的是对各个服务的资源做检测以及对资源进行 **锁定或者预留**。 +- **Confirm 阶段**:这个阶段说的是在各个服务中执行实际的操作。 +- **Cancel 阶段**:如果任何一个服务的业务方法执行出错,那么这里就需要 **进行补偿**,就是执行已经执行成功的业务逻辑的回滚操作。(把那些执行成功的回滚) +- + +举个简单的例子如果你用100元买了一瓶水, Try阶段:你需要向你的钱包检查是否够100元并锁住这100元,水也是一样的。 + +如果有一个失败,则进行cancel(释放这100元和这一瓶水),如果cancel失败不论什么失败都进行重试cancel,所以需要保持幂等。 + +如果都成功,则进行confirm,确认这100元扣,和这一瓶水被卖,如果confirm失败无论什么失败则重试(会依靠活动日志进行重试)。 + +这种方案说实话几乎很少人使用,但是也有使用的场景。因为这个**事务回滚实际上是严重依赖于你自己写代码来回滚和补偿**了,会造成补偿代码巨大。 + +### 本地消息表 + +本地消息表的核心是将需要分布式处理的任务通过消息日志的方式来异步执行。消息日志可以存储到本地文本、数据库或消息队列,再通过业务规则自动或人工发起重试。人工重试更多的是应用于支付场景,通过对账系统对事后问题的处理。 + +![](http://img.dabin-coder.cn/image/本地消息表.png) + +对于本地消息队列来说核心是把大事务转变为小事务。还是举上面用100元去买一瓶水的例子。 + +1.当你扣钱的时候,你需要在你扣钱的服务器上新增加一个本地消息表,你需要把你扣钱和写入减去水的库存到本地消息表放入同一个事务(依靠数据库本地事务保证一致性。 + +2.这个时候有个定时任务去轮询这个本地事务表,把没有发送的消息,扔给商品库存服务器,叫他减去水的库存,到达商品服务器之后这个时候得先写入这个服务器的事务表,然后进行扣减,扣减成功后,更新事务表中的状态。 + +3.商品服务器通过定时任务扫描消息表或者直接通知扣钱服务器,扣钱服务器本地消息表进行状态更新。 + +4.针对一些异常情况,定时扫描未成功处理的消息,进行重新发送,在商品服务器接到消息之后,首先判断是否是重复的,如果已经接收,在判断是否执行,如果执行在马上又进行通知事务,如果未执行,需要重新执行需要由业务保证幂等,也就是不会多扣一瓶水。 + +本地消息队列是BASE理论,是最终一致模型,适用于对一致性要求不高的。实现这个模型时需要注意重试的幂等。 + +### MQ事务 + +基于 MQ 的分布式事务方案其实是对本地消息表的封装,将本地消息表基于 MQ 内部,其他方面的协议基本与本地消息表一致。 + +MQ事务方案整体流程和本地消息表的流程很相似,如下图: + +![](http://img.dabin-coder.cn/image/MQ事务方案.png) + +从上图可以看出和本地消息表方案唯一不同就是将本地消息表存在了MQ内部,而不是业务数据库中。 + +那么MQ内部的处理尤为重要,下面主要基于 RocketMQ 4.3 之后的版本介绍 MQ 的分布式事务方案。 + +在本地消息表方案中,保证事务主动方发写业务表数据和写消息表数据的一致性是基于数据库事务,RocketMQ 的事务消息相对于普通 MQ提供了 2PC 的提交接口,方案如下: + +**正常情况:事务主动方发消息** + +![](http://img.dabin-coder.cn/image/事务主动方发消息.png) + +这种情况下,事务主动方服务正常,没有发生故障,发消息流程如下: + +- 发送方向 MQ 服务端(MQ Server)发送 half 消息。 +- MQ Server 将消息持久化成功之后,向发送方 ack 确认消息已经发送成功。 +- 发送方开始执行本地事务逻辑。 +- 发送方根据本地事务执行结果向 MQ Server 提交二次确认(commit 或是 rollback)。 +- MQ Server 收到 commit 状态则将半消息标记为可投递,订阅方最终将收到该消息;MQ Server 收到 rollback 状态则删除半消息,订阅方将不会接受该消息。 + +**异常情况:事务主动方消息恢复** + +![](http://img.dabin-coder.cn/image/事务主动方消息恢复.png) + +在断网或者应用重启等异常情况下,图中 4 提交的二次确认超时未到达 MQ Server,此时处理逻辑如下: + +- MQ Server 对该消息发起消息回查。 +- 发送方收到消息回查后,需要检查对应消息的本地事务执行的最终结果。 +- 发送方根据检查得到的本地事务的最终状态再次提交二次确认。 +- MQ Server基于 commit/rollback 对消息进行投递或者删除。 + +**优点** + +相比本地消息表方案,MQ 事务方案优点是: + +- 消息数据独立存储 ,降低业务系统与消息系统之间的耦合。 +- 吞吐量大于使用本地消息表方案。 + +**缺点** + +- 一次消息发送需要两次网络请求(half 消息 + commit/rollback 消息) 。 +- 业务处理服务需要实现消息状态回查接口。 + +### Saga事务 + +Saga是由一系列的本地事务构成。每一个本地事务在更新完数据库之后,会发布一条消息或者一个事件来触发Saga中的下一个本地事务的执行。如果一个本地事务因为某些业务规则无法满足而失败,Saga会执行在这个失败的事务之前成功提交的所有事务的补偿操作。 + +Saga的实现有很多种方式,其中最流行的两种方式是: + +- **基于事件的方式**。这种方式没有协调中心,整个模式的工作方式就像舞蹈一样,各个舞蹈演员按照预先编排的动作和走位各自表演,最终形成一只舞蹈。处于当前Saga下的各个服务,会产生某类事件,或者监听其它服务产生的事件并决定是否需要针对监听到的事件做出响应。 +- **基于命令的方式**。这种方式的工作形式就像一只乐队,由一个指挥家(协调中心)来协调大家的工作。协调中心来告诉Saga的参与方应该执行哪一个本地事务。 + +### 最大努力通知方案 + +最大努力通知也称为定期校对,是对MQ事务方案的进一步优化。它在事务主动方增加了消息校对的接口,如果事务被动方没有接收到消息,此时可以调用事务主动方提供的消息校对的接口主动获取。 + +最大努力通知的整体流程如下图: + +![](http://img.dabin-coder.cn/image/最大努力通知方案.png) + +在可靠消息事务中,事务主动方需要将消息发送出去,并且消息接收方成功接收,这种可靠性发送是由事务主动方保证的; + +但是最大努力通知,事务主动方尽最大努力(重试,轮询....)将事务发送给事务接收方,但是仍然存在消息接收不到,此时需要事务被动方主动调用事务主动方的消息校对接口查询业务消息并消费,这种通知的可靠性是由事务被动方保证的。 + +最大努力通知适用于业务通知类型,例如微信交易的结果,就是通过最大努力通知方式通知各个商户,既有回调通知,也有交易查询接口。 + +## 参考文章 + +https://www.pdai.tech/md/arch/arch-z-transection.html + +https://juejin.cn/post/6844903647197806605#heading-15 \ No newline at end of file diff --git "a/\345\210\206\345\270\203\345\274\217/\345\205\250\345\261\200\345\224\257\344\270\200ID.md" b/distributed/global-unique-id.md similarity index 97% rename from "\345\210\206\345\270\203\345\274\217/\345\205\250\345\261\200\345\224\257\344\270\200ID.md" rename to distributed/global-unique-id.md index 0871efa..883ccc6 100644 --- "a/\345\210\206\345\270\203\345\274\217/\345\205\250\345\261\200\345\224\257\344\270\200ID.md" +++ b/distributed/global-unique-id.md @@ -4,7 +4,7 @@ 如上图,如果第一个订单存储在 DB1 上则订单 ID 为1,当一个新订单又入库了存储在 DB2 上订单 ID 也为1。我们系统的架构虽然是分布式的,但是在用户层应是无感知的,重复的订单主键显而易见是不被允许的。那么针对分布式系统如何做到主键唯一性呢? -# UUID +## UUID UUID (Universally Unique Identifier),通用唯一识别码的缩写。UUID是由一组32位数的16进制数字所构成,所以UUID理论上的总数为 1632=2128,约等于 3.4 x 10^38。也就是说若每纳秒产生1兆个UUID,要花100亿年才会将所有UUID用完。 @@ -52,7 +52,7 @@ public static void main(String[] args) { - 信息不安全:基于MAC地址生成UUID的算法可能会造成MAC地址泄露,暴露使用者的位置。 - 对MySQL索引不利:如果作为数据库主键,在InnoDB引擎下,UUID的无序性可能会引起数据位置频繁变动,严重影响性能,可以查阅 Mysql 索引原理 B+树的知识。 -# 数据库生成 +## 数据库生成 是不是一定要基于外界的条件才能满足分布式唯一ID的需求呢,我们能不能在我们分布式数据库的基础上获取我们需要的ID? @@ -71,7 +71,7 @@ public static void main(String[] args) { 但是缺点也很明显,首先它强依赖DB,当DB异常时整个系统不可用。虽然配置主从复制可以尽可能的增加可用性,但是数据一致性在特殊情况下难以保证。主从切换时的不一致可能会导致重复发号。还有就是ID发号性能瓶颈限制在单台MySQL的读写性能。 -# 使用redis实现 +## 使用redis实现 Redis实现分布式唯一ID主要是通过提供像 *INCR* 和 *INCRBY* 这样的自增原子命令,由于Redis自身的单线程的特点所以能保证生成的 ID 肯定是唯一有序的。 @@ -83,7 +83,7 @@ Redis 实现分布式全局唯一ID,它的性能比较高,生成的数据是 当然现在Redis的使用性很普遍,所以如果其他业务已经引进了Redis集群,则可以资源利用考虑使用Redis来实现。 -# 雪花算法-Snowflake +## 雪花算法-Snowflake Snowflake,雪花算法是由Twitter开源的分布式ID生成算法,以划分命名空间的方式将 64-bit位分割成多个部分,每个部分代表不同的含义。而 Java中64bit的整数是Long类型,所以在 Java 中 SnowFlake 算法生成的 ID 就是 long 来存储的。 @@ -290,7 +290,7 @@ public static void main(String[] args) { 很多其他类雪花算法也是在此思想上的设计然后改进规避它的缺陷,后面介绍的百度 UidGenerator 和 美团分布式ID生成系统 Leaf 中snowflake模式都是在 snowflake 的基础上演进出来的。 -# 百度-UidGenerator +## 百度-UidGenerator [百度的 UidGenerator](https://github.com/baidu/uid-generator) 是百度开源基于Java语言实现的唯一ID生成器,是在雪花算法 snowflake 的基础上做了一些改进。UidGenerator以组件形式工作在应用项目中, 支持自定义workerId位数和初始化策略,适用于docker等虚拟化环境下实例自动重启、漂移等场景。 @@ -323,7 +323,7 @@ PRIMARY KEY(ID) COMMENT='DB WorkerID Assigner for UID Generator',ENGINE = INNODB; ``` -## DefaultUidGenerator 实现 +### DefaultUidGenerator 实现 DefaultUidGenerator 就是正常的根据时间戳和机器位还有序列号的生成方式,和雪花算法很相似,对于时钟回拨也只是抛异常处理。仅有一些不同,如以秒为为单位而不再是毫秒和支持Docker等虚拟化环境。 @@ -371,7 +371,7 @@ protected synchronized long nextId() { ``` -## CachedUidGenerator 实现 +### CachedUidGenerator 实现 而官方建议的性能较高的 CachedUidGenerator 生成方式,是使用 RingBuffer 缓存生成的id。数组每个元素成为一个slot。RingBuffer容量,默认为Snowflake算法中sequence最大值(2^13 = 8192)。可通过 boostPower 配置进行扩容,以提高 RingBuffer 读写吞吐量。 @@ -382,13 +382,13 @@ Tail指针、Cursor指针用于环形数组上读写slot: - Cursor指针 表示Consumer消费到的最小序号(序号序列与Producer序列相同)。Cursor不能超过Tail,即不能消费未生产的slot。当Cursor已赶上tail,此时可通过rejectedTakeBufferHandler指定TakeRejectPolicy -[![img](https://img2018.cnblogs.com/blog/1162587/201907/1162587-20190707142503899-1530677221.png)](https://img2018.cnblogs.com/blog/1162587/201907/1162587-20190707142503899-1530677221.png) +![](http://img.dabin-coder.cn/image/20220706225625.png) CachedUidGenerator采用了双RingBuffer,Uid-RingBuffer用于存储Uid、Flag-RingBuffer用于存储Uid状态(是否可填充、是否可消费)。 由于数组元素在内存中是连续分配的,可最大程度利用CPU cache以提升性能。但同时会带来「伪共享」FalseSharing问题,为此在Tail、Cursor指针、Flag-RingBuffer中采用了CacheLine 补齐方式。 -[![img](https://img2018.cnblogs.com/blog/1162587/201907/1162587-20190707142450492-1894906450.png)](https://img2018.cnblogs.com/blog/1162587/201907/1162587-20190707142450492-1894906450.png) +![](http://img.dabin-coder.cn/image/20220706225530.png) RingBuffer填充时机 @@ -399,13 +399,13 @@ RingBuffer填充时机 - 周期填充 通过Schedule线程,定时补全空闲slots。可通过scheduleInterval配置,以应用定时填充功能,并指定Schedule时间间隔。 -# 美团Leaf +## 美团Leaf Leaf是美团基础研发平台推出的一个分布式ID生成服务,名字取自德国哲学家、数学家莱布尼茨的著名的一句话:“There are no two identical leaves in the world”,世间不可能存在两片相同的叶子。 Leaf 也提供了两种ID生成的方式,分别是 Leaf-segment 数据库方案和 Leaf-snowflake 方案。 -## Leaf-segment 数据库方案 +### Leaf-segment 数据库方案 Leaf-segment 数据库方案,是在上文描述的在使用数据库的方案上,做了如下改变: @@ -444,7 +444,7 @@ CREATE TABLE `leaf_alloc` ( 对于这种方案依然存在一些问题,它仍然依赖 DB的稳定性,需要采用主从备份的方式提高 DB的可用性,还有 Leaf-segment方案生成的ID是趋势递增的,这样ID号是可被计算的,例如订单ID生成场景,通过订单id号相减就能大致计算出公司一天的订单量,这个是不能忍受的。 -## Leaf-snowflake方案 +### Leaf-snowflake方案 Leaf-snowflake方案完全沿用 snowflake 方案的bit位设计,对于workerID的分配引入了Zookeeper持久顺序节点的特性自动对snowflake节点配置 wokerID。避免了服务规模较大时,动手配置成本太高的问题。 @@ -466,7 +466,7 @@ Leaf-snowflake是按照下面几个步骤启动的: 在性能上官方提供的数据目前 Leaf 的性能在4C8G 的机器上QPS能压测到近5w/s,TP999 1ms。 -# 总结 +## 总结 以上基本列出了所有常用的分布式ID生成方式,其实大致分类的话可以分为两类: diff --git "a/\345\210\206\345\270\203\345\274\217/\345\276\256\346\234\215\345\212\241.md" b/distributed/micro-service.md similarity index 100% rename from "\345\210\206\345\270\203\345\274\217/\345\276\256\346\234\215\345\212\241.md" rename to distributed/micro-service.md diff --git "a/\345\210\206\345\270\203\345\274\217/\350\277\234\347\250\213\350\260\203\347\224\250.md" b/distributed/rpc.md similarity index 100% rename from "\345\210\206\345\270\203\345\274\217/\350\277\234\347\250\213\350\260\203\347\224\250.md" rename to distributed/rpc.md diff --git "a/\346\241\206\346\236\266/Apollo\351\205\215\347\275\256\344\270\255\345\277\203.md" "b/framework/Apollo\351\205\215\347\275\256\344\270\255\345\277\203.md" similarity index 100% rename from "\346\241\206\346\236\266/Apollo\351\205\215\347\275\256\344\270\255\345\277\203.md" rename to "framework/Apollo\351\205\215\347\275\256\344\270\255\345\277\203.md" diff --git "a/\346\241\206\346\236\266/Mybatis\351\235\242\350\257\225\351\242\230.md" b/framework/mybatis.md similarity index 100% rename from "\346\241\206\346\236\266/Mybatis\351\235\242\350\257\225\351\242\230.md" rename to framework/mybatis.md diff --git "a/\346\241\206\346\236\266/netty\345\256\236\346\210\230.md" b/framework/netty-overview.md similarity index 88% rename from "\346\241\206\346\236\266/netty\345\256\236\346\210\230.md" rename to framework/netty-overview.md index 112aef3..e40b7dd 100644 --- "a/\346\241\206\346\236\266/netty\345\256\236\346\210\230.md" +++ b/framework/netty-overview.md @@ -1,82 +1,3 @@ - - - - -- [简介](#%E7%AE%80%E4%BB%8B) - - [netty 核心组件](#netty-%E6%A0%B8%E5%BF%83%E7%BB%84%E4%BB%B6) - - [NIO](#nio) -- [简单的 netty 应用程序](#%E7%AE%80%E5%8D%95%E7%9A%84-netty-%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F) - - [Echo 服务器](#echo-%E6%9C%8D%E5%8A%A1%E5%99%A8) - - [ChannelHandler 和业务逻辑](#channelhandler-%E5%92%8C%E4%B8%9A%E5%8A%A1%E9%80%BB%E8%BE%91) - - [引导服务器](#%E5%BC%95%E5%AF%BC%E6%9C%8D%E5%8A%A1%E5%99%A8) - - [Echo 客户端](#echo-%E5%AE%A2%E6%88%B7%E7%AB%AF) - - [ChannelHandler](#channelhandler) - - [引导客户端](#%E5%BC%95%E5%AF%BC%E5%AE%A2%E6%88%B7%E7%AB%AF) - - [构建和运行 Echo 服务器和客户端](#%E6%9E%84%E5%BB%BA%E5%92%8C%E8%BF%90%E8%A1%8C-echo-%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%92%8C%E5%AE%A2%E6%88%B7%E7%AB%AF) -- [Netty 的组件和设计](#netty-%E7%9A%84%E7%BB%84%E4%BB%B6%E5%92%8C%E8%AE%BE%E8%AE%A1) - - [Channel 接口](#channel-%E6%8E%A5%E5%8F%A3) - - [EventLoop 接口](#eventloop-%E6%8E%A5%E5%8F%A3) - - [ChannelFuture 接口](#channelfuture-%E6%8E%A5%E5%8F%A3) - - [ChannelHandler](#channelhandler-1) - - [ChannelPipeline](#channelpipeline) - - [ChannelInitializer](#channelinitializer) - - [引导](#%E5%BC%95%E5%AF%BC) -- [传输](#%E4%BC%A0%E8%BE%93) - - [传输迁移](#%E4%BC%A0%E8%BE%93%E8%BF%81%E7%A7%BB) - - [传输 API](#%E4%BC%A0%E8%BE%93-api) - - [内置的传输](#%E5%86%85%E7%BD%AE%E7%9A%84%E4%BC%A0%E8%BE%93) - - [Epoll](#epoll) -- [ByteBuf](#bytebuf) - - [Upooled 缓冲区](#upooled-%E7%BC%93%E5%86%B2%E5%8C%BA) -- [ChannelHandler](#channelhandler-2) - - [Channel 的生命周期](#channel-%E7%9A%84%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F) - - [ChannelHandler 的生命周期](#channelhandler-%E7%9A%84%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F) - - [ChannelInboundHandler 接口](#channelinboundhandler-%E6%8E%A5%E5%8F%A3) - - [ChannelOutboundHandler 接口](#channeloutboundhandler-%E6%8E%A5%E5%8F%A3) - - [ChannelHandlerAdapter](#channelhandleradapter) - - [资源管理](#%E8%B5%84%E6%BA%90%E7%AE%A1%E7%90%86) -- [ChannelPipeline 接口](#channelpipeline-%E6%8E%A5%E5%8F%A3) - - [修改ChannelPipeline](#%E4%BF%AE%E6%94%B9channelpipeline) - - [ChannelHandlerContext 接口](#channelhandlercontext-%E6%8E%A5%E5%8F%A3) - - [异常处理](#%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86) -- [EventLoop 和线程模型](#eventloop-%E5%92%8C%E7%BA%BF%E7%A8%8B%E6%A8%A1%E5%9E%8B) - - [EventLoop 接口](#eventloop-%E6%8E%A5%E5%8F%A3-1) - - [任务调度](#%E4%BB%BB%E5%8A%A1%E8%B0%83%E5%BA%A6) - - [实现细节](#%E5%AE%9E%E7%8E%B0%E7%BB%86%E8%8A%82) -- [引导](#%E5%BC%95%E5%AF%BC-1) - - [引导客户端](#%E5%BC%95%E5%AF%BC%E5%AE%A2%E6%88%B7%E7%AB%AF-1) - - [引导服务器](#%E5%BC%95%E5%AF%BC%E6%9C%8D%E5%8A%A1%E5%99%A8-1) - - [在引导过程添加多个 ChannelHandler](#%E5%9C%A8%E5%BC%95%E5%AF%BC%E8%BF%87%E7%A8%8B%E6%B7%BB%E5%8A%A0%E5%A4%9A%E4%B8%AA-channelhandler) - - [关闭](#%E5%85%B3%E9%97%AD) -- [编解码器](#%E7%BC%96%E8%A7%A3%E7%A0%81%E5%99%A8) - - [解码器](#%E8%A7%A3%E7%A0%81%E5%99%A8) - - [抽象类 ByteToMessageDecoder](#%E6%8A%BD%E8%B1%A1%E7%B1%BB-bytetomessagedecoder) - - [抽象类 ReplayingDecoder](#%E6%8A%BD%E8%B1%A1%E7%B1%BB-replayingdecoder) - - [抽象类 MessageToMessageDecoder](#%E6%8A%BD%E8%B1%A1%E7%B1%BB-messagetomessagedecoder) - - [TooLongFrameException 类](#toolongframeexception-%E7%B1%BB) - - [编码器](#%E7%BC%96%E7%A0%81%E5%99%A8) - - [抽象类 MessageToByteEncoder](#%E6%8A%BD%E8%B1%A1%E7%B1%BB-messagetobyteencoder) - - [抽象类 MessageToMessageEncoder](#%E6%8A%BD%E8%B1%A1%E7%B1%BB-messagetomessageencoder) - - [编解码器类](#%E7%BC%96%E8%A7%A3%E7%A0%81%E5%99%A8%E7%B1%BB) - - [抽象类 ByteToMessageCodec](#%E6%8A%BD%E8%B1%A1%E7%B1%BB-bytetomessagecodec) - - [抽象类 MessageToMessageCodec](#%E6%8A%BD%E8%B1%A1%E7%B1%BB-messagetomessagecodec) - - [CombinedChannelDuplexHandler 类](#combinedchannelduplexhandler-%E7%B1%BB) -- [预置的 ChannelHandler 和编解码器](#%E9%A2%84%E7%BD%AE%E7%9A%84-channelhandler-%E5%92%8C%E7%BC%96%E8%A7%A3%E7%A0%81%E5%99%A8) - - [SSL/TLS](#ssltls) - - [HTTP/HTTPS 应用程序](#httphttps-%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F) - - [添加 HTTP 支持](#%E6%B7%BB%E5%8A%A0-http-%E6%94%AF%E6%8C%81) - - [聚合 HTTP 消息](#%E8%81%9A%E5%90%88-http-%E6%B6%88%E6%81%AF) - - [HTTP 压缩](#http-%E5%8E%8B%E7%BC%A9) - - [HTTPS](#https) - - [WebSocket](#websocket) - - [空闲的连接和超时](#%E7%A9%BA%E9%97%B2%E7%9A%84%E8%BF%9E%E6%8E%A5%E5%92%8C%E8%B6%85%E6%97%B6) - - [基于分隔符的协议](#%E5%9F%BA%E4%BA%8E%E5%88%86%E9%9A%94%E7%AC%A6%E7%9A%84%E5%8D%8F%E8%AE%AE) - - [基于长度的协议](#%E5%9F%BA%E4%BA%8E%E9%95%BF%E5%BA%A6%E7%9A%84%E5%8D%8F%E8%AE%AE) - - [写大型数据](#%E5%86%99%E5%A4%A7%E5%9E%8B%E6%95%B0%E6%8D%AE) -- [ctx.write() 和 channel().write() 的区别](#ctxwrite-%E5%92%8C-channelwrite-%E7%9A%84%E5%8C%BA%E5%88%AB) - - - ## 简介 ### netty 核心组件 @@ -545,7 +466,7 @@ ChannelPipeline 的用于访问ChannelHandler 的操作: ChannelHandlerContext 代表了ChannelHandler 和ChannelPipeline 之间的关联,每当有ChannelHandler 添加到ChannelPipeline 中时,都会创建ChannelHandlerContext。 -![ChannelHandler/ChannelPipeline/ChannelHandlerContext/Channel的关系](https://img2018.cnblogs.com/blog/1252910/201909/1252910-20190912194315417-1954624274.png) +![](http://img.dabin-coder.cn/image/netty1.png) | 方法 | 描述 | | --------------- | ---------------------------------------------------------- | @@ -636,7 +557,7 @@ ScheduledFuture future = ch.eventLoop().scheduleAtFixedRate( ### 实现细节 -![EventLoop执行逻辑](https://img2018.cnblogs.com/blog/1252910/201909/1252910-20190912194501807-1452675286.png) +![](http://img.dabin-coder.cn/image/netty-eventloop执行逻辑.png) @@ -654,7 +575,7 @@ BootStrap 类被用于客户端或者使用了无连接协议的应用程序中 ### 引导服务器 -![ServerBoostrap和ServerChannel](https://img2018.cnblogs.com/blog/1252910/201909/1252910-20190912194603147-253748270.png) +![](http://img.dabin-coder.cn/image/ServerBoostrap和ServerChannel.png) 在基类AbstractBootstrap有handler方法,目的是添加一个handler,监听Bootstrap的动作。 @@ -851,7 +772,7 @@ public class CombinedByteCharCodec extends CombinedChannelDuplexHandler { WebSocket 在客户端和服务器之间提供了真正的双向数据交换。 -![WebSocket握手](https://img2018.cnblogs.com/blog/1252910/201909/1252910-20190912194009527-799081544.png) +![](http://img.dabin-coder.cn/image/netty-websocket协议.png) WebSocketFrame 类型: @@ -1071,7 +992,7 @@ public class WebSocketServerInitializer extends ChannelInitializer { 用于空闲连接以及超时的 ChannelHandler。 -![用于空闲连接以及超时的ChannelHandler](https://img2018.cnblogs.com/blog/1252910/201909/1252910-20190912194117618-854988800.png) +![](http://img.dabin-coder.cn/image/用于空闲连接以及超时的ChannelHandler.png) 发送心跳: diff --git "a/\346\241\206\346\236\266/Spring\351\235\242\350\257\225\351\242\230.md" b/framework/spring.md similarity index 93% rename from "\346\241\206\346\236\266/Spring\351\235\242\350\257\225\351\242\230.md" rename to framework/spring.md index 82c11b5..5c6ca20 100644 --- "a/\346\241\206\346\236\266/Spring\351\235\242\350\257\225\351\242\230.md" +++ b/framework/spring.md @@ -1,3 +1,5 @@ +![](http://img.dabin-coder.cn/image/Spring总结.jpg) + ## Spring的优点 - 通过控制反转和依赖注入实现**松耦合**。 @@ -184,7 +186,7 @@ finishBeanFactoryInitialization(beanFactory); ## Bean的生命周期 -![](http://img.dabin-coder.cn/image/bean生命周期.png) +![](http://img.dabin-coder.cn/image/20220709213529.png) 1.调用bean的构造方法创建Bean @@ -194,17 +196,17 @@ finishBeanFactoryInitialization(beanFactory); 4.如果Bean实现了`BeanFactoryAware`接口,Spring将调用`setBeanFactory()`把bean factory设置给Bean -5.如果Bean实现了`ApplicationContextAware`接口,Spring容器将调用`setApplicationContext()`给Bean设置ApplictionContext +5.如果存在`BeanPostProcessor`,Spring将调用它们的`postProcessBeforeInitialization`(预初始化)方法,在Bean初始化前对其进行处理 -6.如果存在`BeanPostProcessor`,Spring将调用它们的`postProcessBeforeInitialization`(预初始化)方法,在Bean初始化前对其进行处理 +6.如果Bean实现了`InitializingBean`接口,Spring将调用它的`afterPropertiesSet`方法,然后调用xml定义的 init-method 方法,两个方法作用类似,都是在初始化 bean 的时候执行 -7.如果Bean实现了`InitializingBean`接口,Spring将调用它的`afterPropertiesSet`方法,然后调用xml定义的 init-method 方法,两个方法作用类似,都是在初始化 bean 的时候执行 +7.如果存在`BeanPostProcessor`,Spring将调用它们的`postProcessAfterInitialization`(后初始化)方法,在Bean初始化后对其进行处理 -8.如果存在`BeanPostProcessor`,Spring将调用它们的`postProcessAfterInitialization`(后初始化)方法,在Bean初始化后对其进行处理 +8.Bean初始化完成,供应用使用,这里分两种情况: -9.Bean初始化完成,供应用使用,直到应用被销毁 +8.1 如果Bean为单例的话,那么容器会返回Bean给用户,并存入缓存池。如果Bean实现了`DisposableBean`接口,Spring将调用它的`destory`方法,然后调用在xml中定义的 `destory-method`方法,这两个方法作用类似,都是在Bean实例销毁前执行。 -10.如果Bean实现了`DisposableBean`接口,Spring将调用它的`destory`方法,然后调用在xml中定义的 `destory-method`方法,这两个方法作用类似,都是在Bean实例销毁前执行 +8.2 如果Bean是多例的话,容器将Bean返回给用户,剩下的生命周期由用户控制。 ```java public interface BeanPostProcessor { @@ -270,9 +272,29 @@ public class SqlSessionFactoryBean implements FactoryBean, In Spring 将会在应用启动时创建 `SqlSessionFactory`,并使用 `sqlSessionFactory` 这个名字存储起来。 +## BeanFactory和ApplicationContext有什么区别? + +BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。其中ApplicationContext是BeanFactory的子接口。 + +两者区别如下: + +1、BeanFactory是Spring里面最底层的接口,包含了各种Bean的定义,读取bean配置文档,管理bean的加载、实例化,控制bean的生命周期,维护bean之间的依赖关系。 + +ApplicationContext接口作为BeanFactory的派生,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能,如继承MessageSource、支持国际化、统一的资源文件访问方式、同时加载多个配置文件等功能。 + +2、BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,我们就不能发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。 + +而ApplicationContext是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。 ApplicationContext启动后预载入所有的单例Bean,那么在需要的时候,不需要等待创建bean,因为它们已经创建好了。 + +相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。 + +3、BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用ContextLoader。 + +4、BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册。 + ## Bean注入容器有哪些方式? -1、@Configuration + @Bean +1、**@Configuration + @Bean** @Configuration用来声明一个配置类,然后使用 @Bean 注解,用于声明一个bean,将其加入到Spring容器中。 @@ -288,7 +310,7 @@ public class MyConfiguration { } ``` -2、通过包扫描特定注解的方式 +2、**通过包扫描特定注解的方式** @ComponentScan放置在我们的配置类上,然后可以指定一个路径,进行扫描带有特定注解的bean,然后加至容器中。 @@ -310,7 +332,7 @@ public class Demo1 { } ``` -3、@Import注解导入 +3、**@Import注解导入** @Import注解平时开发用的不多,但是也是非常重要的,在进行Spring扩展时经常会用到,它经常搭配自定义注解进行使用,然后往容器中导入一个配置文件。 @@ -327,7 +349,7 @@ public class App { } ``` -4、实现BeanDefinitionRegistryPostProcessor进行后置处理。 +4、**实现BeanDefinitionRegistryPostProcessor进行后置处理。** 在Spring容器启动的时候会执行 BeanDefinitionRegistryPostProcessor 的 postProcessBeanDefinitionRegistry 方法,就是等beanDefinition加载完毕之后,对beanDefinition进行后置处理,可以在此进行调整IOC容器中的beanDefinition,从而干扰到后面进行初始化bean。 @@ -358,7 +380,7 @@ class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPos } ``` -5、使用FactoryBean接口 +5、**使用FactoryBean接口** 如下图代码,使用@Configuration + @Bean的方式将 PersonFactoryBean 加入到容器中,这里没有向容器中直接注入 Person,而是注入 PersonFactoryBean,然后从容器中拿Person这个类型的bean。 @@ -913,4 +935,6 @@ public class AsyncExecutionInterceptor extends AsyncExecutionAspectSupport imple + + ![](http://img.dabin-coder.cn/image/20220612101342.png) diff --git "a/\346\241\206\346\236\266/SpringBoot\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" b/framework/springboot.md similarity index 74% rename from "\346\241\206\346\236\266/SpringBoot\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" rename to framework/springboot.md index 7e70a4c..b5f6de1 100644 --- "a/\346\241\206\346\236\266/SpringBoot\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" +++ b/framework/springboot.md @@ -22,6 +22,16 @@ starter提供了一个自动化配置类,一般命名为 XXXAutoConfiguration 3. spring-boot-starter-data-Redis :提供 Redis 。 4. mybatis-spring-boot-starter :提供 MyBatis 。 +## Spring Boot 的核心注解是哪个? + +启动类上面的注解是@SpringBootApplication,它也是 Spring Boot 的核心注解,主要组合包含了以下 3 个注解: + +- @SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能。 + +- @EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项,如关闭数据源自动配置功能: @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。 + +- @ComponentScan:Spring组件扫描。 + ## 自动配置原理 SpringBoot实现自动配置原理图解: @@ -291,10 +301,59 @@ public class SpringbootDemoApplication { hello.msg=大彬 ``` -## @Value原理 +## @Value注解的原理 @Value的解析就是在bean初始化阶段。BeanPostProcessor定义了bean初始化前后用户可以对bean进行操作的接口方法,它的一个重要实现类`AutowiredAnnotationBeanPostProcessor`为bean中的@Autowired和@Value注解的注入功能提供支持。 +## Spring Boot 需要独立的容器运行吗? + +不需要,内置了 Tomcat/ Jetty 等容器。 + +## Spring Boot 支持哪些日志框架? + +Spring Boot 支持 Java Util Logging, Log4j2, Lockback 作为日志框架,如果你使用 Starters 启动器,Spring Boot 将使用 Logback 作为默认日志框架,但是不管是那种日志框架他都支持将配置文件输出到控制台或者文件中。 + +## YAML 配置的优势在哪里 ? + +YAML 配置和传统的 properties 配置相比之下,有这些优势: + +- 配置有序 +- 简洁明了,支持数组,数组中的元素可以是基本数据类型也可以是对象 + +缺点就是不支持 @PropertySource 注解导入自定义的 YAML 配置。 + +## 什么是 Spring Profiles? + +在项目的开发中,有些配置文件在开发、测试或者生产等不同环境中可能是不同的,例如数据库连接、redis的配置等等。那我们如何在不同环境中自动实现配置的切换呢?Spring给我们提供了profiles机制给我们提供的就是来回切换配置文件的功能 + +Spring Profiles 允许用户根据配置文件(dev,test,prod 等)来注册 bean。因此,当应用程序在开发中运行时,只有某些 bean 可以加载,而在 PRODUCTION中,某些其他 bean 可以加载。假设我们的要求是 Swagger 文档仅适用于 QA 环境,并且禁用所有其他文档。这可以使用配置文件来完成。Spring Boot 使得使用配置文件非常简单。 + +## SpringBoot多数据源事务如何管理 + +第一种方式是在service层的@TransactionManager中使用transactionManager指定DataSourceConfig中配置的事务。 + +第二种是使用jta-atomikos实现分布式事务管理。 + +## spring-boot-starter-parent 有什么用 ? + +新创建一个 Spring Boot 项目,默认都是有 parent 的,这个 parent 就是 spring-boot-starter-parent ,spring-boot-starter-parent 主要有如下作用: + +1. 定义了 Java 编译版本。 +2. 使用 UTF-8 格式编码。 +3. 执行打包操作的配置。 +4. 自动化的资源过滤。 +5. 自动化的插件配置。 +6. 针对 application.properties 和 application.yml 的资源过滤,包括通过 profile 定义的不同环境的配置文件,例如 application-dev.properties 和 application-dev.yml。 + +## Spring Boot 打成的 jar 和普通的 jar 有什么区别 ? + +- Spring Boot 项目最终打包成的 jar 是可执行 jar ,这种 jar 可以直接通过 `java -jar xxx.jar` 命令来运行,这种 jar 不可以作为普通的 jar 被其他项目依赖,即使依赖了也无法使用其中的类。 +- Spring Boot 的 jar 无法被其他项目依赖,主要还是他和普通 jar 的结构不同。普通的 jar 包,解压后直接就是包名,包里就是我们的代码,而 Spring Boot 打包成的可执行 jar 解压后,在 `\BOOT-INF\classes` 目录下才是我们的代码,因此无法被直接引用。如果非要引用,可以在 pom.xml 文件中增加配置,将 Spring Boot 项目打包成两个 jar ,一个可执行,一个可引用。 + +## SpringBoot多数据源拆分的思路 + +先在properties配置文件中配置两个数据源,创建分包mapper,使用@ConfigurationProperties读取properties中的配置,使用@MapperScan注册到对应的mapper包中 。 + ![](http://img.dabin-coder.cn/image/20220612101342.png) diff --git "a/\346\241\206\346\236\266/SpringCloud\345\276\256\346\234\215\345\212\241\345\256\236\346\210\230.md" b/framework/springcloud-overview.md similarity index 100% rename from "\346\241\206\346\236\266/SpringCloud\345\276\256\346\234\215\345\212\241\345\256\236\346\210\230.md" rename to framework/springcloud-overview.md diff --git a/framework/springmvc.md b/framework/springmvc.md new file mode 100644 index 0000000..cf8050e --- /dev/null +++ b/framework/springmvc.md @@ -0,0 +1,184 @@ +## 说说你对 SpringMVC 的理解 + +SpringMVC是一种基于 Java 的实现MVC设计模型的请求驱动类型的轻量级Web框架,属于Spring框架的一个模块。 + +它通过一套注解,让一个简单的Java类成为处理请求的控制器,而无须实现任何接口。同时它还支持RESTful编程风格的请求。 + +## 什么是MVC模式? + +MVC的全名是`Model View Controller`,是模型(model)-视图(view)-控制器(controller)的缩写,是一种软件设计典范。它是用一种业务逻辑、数据与界面显示分离的方法来组织代码,将众多的业务逻辑聚集到一个部件里面,在需要改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑,达到减少编码的时间。 + +View,视图是指用户看到并与之交互的界面。比如由html元素组成的网页界面,或者软件的客户端界面。MVC的好处之一在于它能为应用程序处理很多不同的视图。在视图中其实没有真正的处理发生,它只是作为一种输出数据并允许用户操纵的方式。 + +model,模型是指模型表示业务规则。在MVC的三个部件中,模型拥有最多的处理任务。被模型返回的数据是中立的,模型与数据格式无关,这样一个模型能为多个视图提供数据,由于应用于模型的代码只需写一次就可以被多个视图重用,所以减少了代码的重复性。 + +controller,控制器是指控制器接受用户的输入并调用模型和视图去完成用户的需求,控制器本身不输出任何东西和做任何处理。它只是接收请求并决定调用哪个模型构件去处理请求,然后再确定用哪个视图来显示返回的数据。 + +## SpringMVC 有哪些优点? + +1. 与 Spring 集成使用非常方便,生态好。 +2. 配置简单,快速上手。 +3. 支持 RESTful 风格。 +4. 支持各种视图技术,支持各种请求资源映射策略。 + +## Spring MVC和Struts的区别 + +1. Spring MVC是基于方法开发,Struts2是基于类开发的。 + - Spring MVC会将用户请求的URL路径信息与Controller的某个方法进行映射,所有请求参数会注入到对应方法的形参上,生成Handler对象,对象中只有一个方法; + - Struts每处理一次请求都会实例一个Action,Action类的所有方法使用的请求参数都是Action类中的成员变量,随着方法增多,整个Action也会变得混乱。 +2. Spring MVC支持单例开发模式,Struts只能使用多例 + + - Struts由于只能通过类的成员变量接收参数,故只能使用多例。 +3. Struts2 的核心是基于一个Filter即StrutsPreparedAndExcuteFilter,Spring MVC的核心是基于一个Servlet即DispatcherServlet(前端控制器)。 +4. Struts处理速度稍微比Spring MVC慢,Struts使用了Struts标签,加载数据较慢。 + +## Spring MVC的工作原理 + +Spring MVC的工作原理如下: + +1. DispatcherServlet 接收用户的请求 +2. 找到用于处理request的 handler 和 Interceptors,构造成 HandlerExecutionChain 执行链 +3. 找到 handler 相对应的 HandlerAdapter +4. 执行所有注册拦截器的preHandler方法 +5. 调用 HandlerAdapter 的 handle() 方法处理请求,返回 ModelAndView +6. 倒序执行所有注册拦截器的postHandler方法 +7. 请求视图解析和视图渲染 + +![](http://img.dabin-coder.cn/image/spring_mvc原理.png) + +## Spring MVC的主要组件? + +- 前端控制器(DispatcherServlet):接收用户请求,给用户返回结果。 +- 处理器映射器(HandlerMapping):根据请求的url路径,通过注解或者xml配置,寻找匹配的Handler。 +- 处理器适配器(HandlerAdapter):Handler 的适配器,调用 handler 的方法处理请求。 +- 处理器(Handler):执行相关的请求处理逻辑,并返回相应的数据和视图信息,将其封装到ModelAndView对象中。 +- 视图解析器(ViewResolver):将逻辑视图名解析成真正的视图View。 +- 视图(View):接口类,实现类可支持不同的View类型(JSP、FreeMarker、Excel等)。 + +## Spring MVC的常用注解由有哪些? +- @Controller:用于标识此类的实例是一个控制器。 +- @RequestMapping:映射Web请求(访问路径和参数)。 +- @ResponseBody:注解返回数据而不是返回页面 +- @RequestBody:注解实现接收 http 请求的 json 数据,将 json 数据转换为 java 对象。 +- @PathVariable:获得URL中路径变量中的值 +- @RestController:@Controller+@ResponseBody +- @ExceptionHandler标识一个方法为全局异常处理的方法。 + +## @Controller 注解有什么用? + +`@Controller` 注解标记一个类为 Spring Web MVC 控制器。Spring MVC 会将扫描到该注解的类,然后扫描这个类下面带有 `@RequestMapping` 注解的方法,根据注解信息,为这个方法生成一个对应的处理器对象,在上面的 HandlerMapping 和 HandlerAdapter组件中讲到过。 + +当然,除了添加 `@Controller` 注解这种方式以外,你还可以实现 Spring MVC 提供的 `Controller` 或者 `HttpRequestHandler` 接口,对应的实现类也会被作为一个处理器对象 + +## @RequestMapping 注解有什么用? + +`@RequestMapping` 注解,用于配置处理器的 HTTP 请求方法,URI等信息,这样才能将请求和方法进行映射。这个注解可以作用于类上面,也可以作用于方法上面,在类上面一般是配置这个控制器的 URI 前缀。 + +## @RestController 和 @Controller 有什么区别? + +`@RestController` 注解,在 `@Controller` 基础上,增加了 `@ResponseBody` 注解,更加适合目前前后端分离的架构下,提供 Restful API ,返回 JSON 数据格式。 + +## @RequestMapping 和 @GetMapping 注解有什么不同? + +1. `@RequestMapping`:可注解在类和方法上;`@GetMapping` 仅可注册在方法上 +2. `@RequestMapping`:可进行 GET、POST、PUT、DELETE 等请求方法;`@GetMapping` 是 `@RequestMapping` 的 GET 请求方法的特例。 + +## @RequestParam 和 @PathVariable 两个注解的区别 + +两个注解都用于方法参数,获取参数值的方式不同,`@RequestParam` 注解的参数从请求携带的参数中获取,而 `@PathVariable` 注解从请求的 URI 中获取 + +## @RequestBody和@RequestParam的区别 + +@RequestBody一般处理的是在ajax请求中声明contentType: "application/json; charset=utf-8"时候。也就是json数据或者xml数据。 + +@RequestParam一般就是在ajax里面没有声明contentType的时候,为默认的`x-www-form-urlencoded`格式时。 + +## Spring MVC的异常处理 + +可以将异常抛给Spring框架,由Spring框架来处理;我们只需要配置简单的异常处理器,在异常处理器中添视图页面即可。 + +- 使用系统定义好的异常处理器 SimpleMappingExceptionResolver +- 使用自定义异常处理器 +- 使用异常处理注解 + +## SpringMVC 用什么对象从后台向前台传递数据的? + +1. 将数据绑定到 request; +2. 返回 ModelAndView; +3. 通过ModelMap对象,可以在这个对象里面调用put方法,把对象加到里面,前端就可以通过el表达式拿到; +4. 绑定数据到 Session中。 + +## SpringMvc的Controller是不是单例模式? + +单例模式。在多线程访问的时候有线程安全问题,解决方案是在控制器里面不要写可变状态量,如果需要使用这些可变状态,可以使用ThreadLocal,为每个线程单独生成一份变量副本,独立操作,互不影响。 + +## 介绍下 Spring MVC 拦截器? + +Spring MVC 拦截器对应HandlerInterceor接口,该接口位于org.springframework.web.servlet的包中,定义了三个方法,若要实现该接口,就要实现其三个方法: + +1. **前置处理(preHandle()方法)**:该方法在执行控制器方法之前执行。返回值为Boolean类型,如果返回false,表示拦截请求,不再向下执行,如果返回true,表示放行,程序继续向下执行(如果后面没有其他Interceptor,就会执行controller方法)。所以此方法可对请求进行判断,决定程序是否继续执行,或者进行一些初始化操作及对请求进行预处理。 +2. **后置处理(postHandle()方法)**:该方法在执行控制器方法调用之后,且在返回ModelAndView之前执行。由于该方法会在DispatcherServlet进行返回视图渲染之前被调用,所以此方法多被用于处理返回的视图,可通过此方法对请求域中的模型和视图做进一步的修改。 +3. **已完成处理(afterCompletion()方法)**:该方法在执行完控制器之后执行,由于是在Controller方法执行完毕后执行该方法,所以该方法适合进行一些资源清理,记录日志信息等处理操作。 + +可以通过拦截器进行权限检验,参数校验,记录日志等操作 + +## SpringMvc怎么配置拦截器? + +有两种写法,一种是实现HandlerInterceptor接口,另外一种是继承适配器类,接着在接口方法当中,实现处理逻辑;然后在SpringMvc的配置文件中配置拦截器即可: + +```java + + + + + + + + + + +``` + +## Spring MVC 的拦截器和 Filter 过滤器有什么差别? + +有以下几点: + +- **功能相同**:拦截器和 Filter 都能实现相应的功能 +- **容器不同**:拦截器构建在 Spring MVC 体系中;Filter 构建在 Servlet 容器之上 +- **使用便利性不同**:拦截器提供了三个方法,分别在不同的时机执行;过滤器仅提供一个方法 + +## 什么是REST? + +REST,英文全称,Resource Representational State Transfer,对资源的访问状态的变化通过url的变化表述出来。 + +Resource:**资源**。资源是REST架构或者说整个网络处理的核心。 + +Representational:**某种表现形式**,比如用JSON,XML,JPEG等。 + +State Transfer:**状态变化**。通过HTTP method实现。 + +REST描述的是在网络中client和server的一种交互形式。用大白话来说,就是**通过URL就知道要什么资源,通过HTTP method就知道要干什么,通过HTTP status code就知道结果如何**。 + +举个例子: + +```java +GET /tasks 获取所有任务 +POST /tasks 创建新任务 +GET /tasks/{id} 通过任务id获取任务 +PUT /tasks/{id} 更新任务 +DELETE /tasks/{id} 删除任务 +``` + +GET代表获取一个资源,POST代表添加一个资源,PUT代表修改一个资源,DELETE代表删除一个资源。 + +server提供的RESTful API中,URL中只使用名词来指定资源,原则上不使用动词。用`HTTP Status Code`传递server的状态信息。比如最常用的 200 表示成功,500 表示Server内部错误等。 + +## 使用REST有什么优势呢? + +第一,**风格统一**了,不会出现`delUser/deleteUser/removeUser`各种命名的代码了。 + +第二,**面向资源**,一目了然,具有自解释性。 + +第三,**充分利用 HTTP 协议本身语义**。 + +![](http://img.dabin-coder.cn/image/20220612101342.png) diff --git a/img/1588431199776.png b/img/1588431199776.png deleted file mode 100644 index c28de4d..0000000 Binary files a/img/1588431199776.png and /dev/null differ diff --git a/img/ConcurrentHashMap-segment.jpg b/img/ConcurrentHashMap-segment.jpg deleted file mode 100644 index 4b709a3..0000000 Binary files a/img/ConcurrentHashMap-segment.jpg and /dev/null differ diff --git a/img/JDK1.8-ConcurrentHashMap-Structure.jpg b/img/JDK1.8-ConcurrentHashMap-Structure.jpg deleted file mode 100644 index a443bb3..0000000 Binary files a/img/JDK1.8-ConcurrentHashMap-Structure.jpg and /dev/null differ diff --git a/img/Java-Collections.jpeg b/img/Java-Collections.jpeg deleted file mode 100644 index cf9071f..0000000 Binary files a/img/Java-Collections.jpeg and /dev/null differ diff --git a/img/all-table-search.jpg b/img/all-table-search.jpg deleted file mode 100644 index 8c0605a..0000000 Binary files a/img/all-table-search.jpg and /dev/null differ diff --git a/img/aqs.png b/img/aqs.png deleted file mode 100644 index 08ceae5..0000000 Binary files a/img/aqs.png and /dev/null differ diff --git a/img/bean-life-cycle.jpg b/img/bean-life-cycle.jpg deleted file mode 100644 index 628017f..0000000 Binary files a/img/bean-life-cycle.jpg and /dev/null differ diff --git a/img/bloom-filter.jpg b/img/bloom-filter.jpg deleted file mode 100644 index 96e3f88..0000000 Binary files a/img/bloom-filter.jpg and /dev/null differ diff --git a/img/cms-collector.png b/img/cms-collector.png deleted file mode 100644 index 3ed3bd8..0000000 Binary files a/img/cms-collector.png and /dev/null differ diff --git a/img/concurrent/executors-ali.png b/img/concurrent/executors-ali.png deleted file mode 100644 index a1a3858..0000000 Binary files a/img/concurrent/executors-ali.png and /dev/null differ diff --git a/img/concurrent/synchronized-block.png b/img/concurrent/synchronized-block.png deleted file mode 100644 index 9c905bb..0000000 Binary files a/img/concurrent/synchronized-block.png and /dev/null differ diff --git a/img/concurrent/synchronized-method.png b/img/concurrent/synchronized-method.png deleted file mode 100644 index 2b471ef..0000000 Binary files a/img/concurrent/synchronized-method.png and /dev/null differ diff --git a/img/condition-await.png b/img/condition-await.png deleted file mode 100644 index 014b97b..0000000 Binary files a/img/condition-await.png and /dev/null differ diff --git a/img/condition-signal.png b/img/condition-signal.png deleted file mode 100644 index ea388d9..0000000 Binary files a/img/condition-signal.png and /dev/null differ diff --git a/img/condition-wait-queue.png b/img/condition-wait-queue.png deleted file mode 100644 index 9d2f803..0000000 Binary files a/img/condition-wait-queue.png and /dev/null differ diff --git a/img/cover-index.png b/img/cover-index.png deleted file mode 100644 index f36d390..0000000 Binary files a/img/cover-index.png and /dev/null differ diff --git a/img/direct-pointer.png b/img/direct-pointer.png deleted file mode 100644 index 2449ab7..0000000 Binary files a/img/direct-pointer.png and /dev/null differ diff --git a/img/docker/docker.jpg b/img/docker/docker.jpg deleted file mode 100644 index a64ca63..0000000 Binary files a/img/docker/docker.jpg and /dev/null differ diff --git a/img/explain-all.png b/img/explain-all.png deleted file mode 100644 index 7b49bf4..0000000 Binary files a/img/explain-all.png and /dev/null differ diff --git a/img/explain-const.png b/img/explain-const.png deleted file mode 100644 index 80a4fb4..0000000 Binary files a/img/explain-const.png and /dev/null differ diff --git a/img/explain-id.png b/img/explain-id.png deleted file mode 100644 index 86fe346..0000000 Binary files a/img/explain-id.png and /dev/null differ diff --git a/img/explain-range.png b/img/explain-range.png deleted file mode 100644 index b2a4d3e..0000000 Binary files a/img/explain-range.png and /dev/null differ diff --git a/img/explain-ref.png b/img/explain-ref.png deleted file mode 100644 index 5e52fc8..0000000 Binary files a/img/explain-ref.png and /dev/null differ diff --git a/img/g1-region.jpg b/img/g1-region.jpg deleted file mode 100644 index 99f595a..0000000 Binary files a/img/g1-region.jpg and /dev/null differ diff --git a/img/gc-root-refer.png b/img/gc-root-refer.png deleted file mode 100644 index f326103..0000000 Binary files a/img/gc-root-refer.png and /dev/null differ diff --git a/img/git-status.png b/img/git-status.png deleted file mode 100644 index f93b1c3..0000000 Binary files a/img/git-status.png and /dev/null differ diff --git a/img/heap-structure.png b/img/heap-structure.png deleted file mode 100644 index 7934357..0000000 Binary files a/img/heap-structure.png and /dev/null differ diff --git a/img/image-20200520234137916.png b/img/image-20200520234137916.png deleted file mode 100644 index 8ea464e..0000000 Binary files a/img/image-20200520234137916.png and /dev/null differ diff --git a/img/image-20200520234200868.png b/img/image-20200520234200868.png deleted file mode 100644 index 54d8b42..0000000 Binary files a/img/image-20200520234200868.png and /dev/null differ diff --git a/img/image-20200520234231001.png b/img/image-20200520234231001.png deleted file mode 100644 index 5867656..0000000 Binary files a/img/image-20200520234231001.png and /dev/null differ diff --git a/img/image-20200608232749393.png b/img/image-20200608232749393.png deleted file mode 100644 index 55e28ff..0000000 Binary files a/img/image-20200608232749393.png and /dev/null differ diff --git a/img/image-20200608232951873.png b/img/image-20200608232951873.png deleted file mode 100644 index 9ae4e7e..0000000 Binary files a/img/image-20200608232951873.png and /dev/null differ diff --git a/img/image-20200608233400458.png b/img/image-20200608233400458.png deleted file mode 100644 index 980e322..0000000 Binary files a/img/image-20200608233400458.png and /dev/null differ diff --git a/img/image-20200614165333479.png b/img/image-20200614165333479.png deleted file mode 100644 index aba7127..0000000 Binary files a/img/image-20200614165333479.png and /dev/null differ diff --git a/img/image-20200614165432775.png b/img/image-20200614165432775.png deleted file mode 100644 index 69dea69..0000000 Binary files a/img/image-20200614165432775.png and /dev/null differ diff --git a/img/index-search-range.jpg b/img/index-search-range.jpg deleted file mode 100644 index f4ab778..0000000 Binary files a/img/index-search-range.jpg and /dev/null differ diff --git a/img/index-search.jpg b/img/index-search.jpg deleted file mode 100644 index 3273df7..0000000 Binary files a/img/index-search.jpg and /dev/null differ diff --git a/img/java-basic/exception.png b/img/java-basic/exception.png deleted file mode 100644 index ab9b49e..0000000 Binary files a/img/java-basic/exception.png and /dev/null differ diff --git a/img/java-basic/field-method.png b/img/java-basic/field-method.png deleted file mode 100644 index cb566fc..0000000 Binary files a/img/java-basic/field-method.png and /dev/null differ diff --git a/img/java-basic/io.jpg b/img/java-basic/io.jpg deleted file mode 100644 index 9bd463e..0000000 Binary files a/img/java-basic/io.jpg and /dev/null differ diff --git a/img/java-ram-region.png b/img/java-ram-region.png deleted file mode 100644 index c5088be..0000000 Binary files a/img/java-ram-region.png and /dev/null differ diff --git a/img/jmeter/http-request-param.png b/img/jmeter/http-request-param.png deleted file mode 100644 index 098ea41..0000000 Binary files a/img/jmeter/http-request-param.png and /dev/null differ diff --git a/img/jmeter/jmeter-aggregate-report.png b/img/jmeter/jmeter-aggregate-report.png deleted file mode 100644 index 5a2668e..0000000 Binary files a/img/jmeter/jmeter-aggregate-report.png and /dev/null differ diff --git a/img/jmeter/jmeter-default-request.png b/img/jmeter/jmeter-default-request.png deleted file mode 100644 index ed4ad06..0000000 Binary files a/img/jmeter/jmeter-default-request.png and /dev/null differ diff --git a/img/jmeter/jmeter-request-header.png b/img/jmeter/jmeter-request-header.png deleted file mode 100644 index 3191795..0000000 Binary files a/img/jmeter/jmeter-request-header.png and /dev/null differ diff --git a/img/jmeter/jmeter-thread-group.png b/img/jmeter/jmeter-thread-group.png deleted file mode 100644 index a2869a2..0000000 Binary files a/img/jmeter/jmeter-thread-group.png and /dev/null differ diff --git a/img/jvm/string-equal.png b/img/jvm/string-equal.png deleted file mode 100644 index 1dc11f3..0000000 Binary files a/img/jvm/string-equal.png and /dev/null differ diff --git a/img/jvm/string-intern.png b/img/jvm/string-intern.png deleted file mode 100644 index 935adc7..0000000 Binary files a/img/jvm/string-intern.png and /dev/null differ diff --git a/img/jvm/string-new.png b/img/jvm/string-new.png deleted file mode 100644 index 2c0a320..0000000 Binary files a/img/jvm/string-new.png and /dev/null differ diff --git a/img/left-match.jpg b/img/left-match.jpg deleted file mode 100644 index 66ca509..0000000 Binary files a/img/left-match.jpg and /dev/null differ diff --git a/img/middleware/redis-transaction.png b/img/middleware/redis-transaction.png deleted file mode 100644 index 5234b99..0000000 Binary files a/img/middleware/redis-transaction.png and /dev/null differ diff --git a/img/middleware/redis-usage.png b/img/middleware/redis-usage.png deleted file mode 100644 index cea1fa6..0000000 Binary files a/img/middleware/redis-usage.png and /dev/null differ diff --git a/img/mysql-architecture.png b/img/mysql-architecture.png deleted file mode 100644 index 16597c9..0000000 Binary files a/img/mysql-architecture.png and /dev/null differ diff --git a/img/mysql-clustered-index.png b/img/mysql-clustered-index.png deleted file mode 100644 index 6b83fd0..0000000 Binary files a/img/mysql-clustered-index.png and /dev/null differ diff --git a/img/mysql/arch.png b/img/mysql/arch.png deleted file mode 100644 index f2c94c6..0000000 Binary files a/img/mysql/arch.png and /dev/null differ diff --git a/img/mysql/current-read.png b/img/mysql/current-read.png deleted file mode 100644 index 56d2db2..0000000 Binary files a/img/mysql/current-read.png and /dev/null differ diff --git a/img/mysql/mvcc-impl.png b/img/mysql/mvcc-impl.png deleted file mode 100644 index cc3f58a..0000000 Binary files a/img/mysql/mvcc-impl.png and /dev/null differ diff --git a/img/mysql/myisam-innodb-index.png b/img/mysql/myisam-innodb-index.png deleted file mode 100644 index 308f01a..0000000 Binary files a/img/mysql/myisam-innodb-index.png and /dev/null differ diff --git a/img/mysql/mysql-archpng.png b/img/mysql/mysql-archpng.png deleted file mode 100644 index e7e8d2a..0000000 Binary files a/img/mysql/mysql-archpng.png and /dev/null differ diff --git a/img/mysql/nested-loop.png b/img/mysql/nested-loop.png deleted file mode 100644 index 1ba69ca..0000000 Binary files a/img/mysql/nested-loop.png and /dev/null differ diff --git a/img/mysql/orderby.png b/img/mysql/orderby.png deleted file mode 100644 index 1b1283e..0000000 Binary files a/img/mysql/orderby.png and /dev/null differ diff --git a/img/net/bio.png b/img/net/bio.png deleted file mode 100644 index a2740f1..0000000 Binary files a/img/net/bio.png and /dev/null differ diff --git a/img/net/cdn.png b/img/net/cdn.png deleted file mode 100644 index 1694d81..0000000 Binary files a/img/net/cdn.png and /dev/null differ diff --git a/img/net/https-certificate-chain.jpg b/img/net/https-certificate-chain.jpg deleted file mode 100644 index 79b5120..0000000 Binary files a/img/net/https-certificate-chain.jpg and /dev/null differ diff --git a/img/net/https-certificate.png b/img/net/https-certificate.png deleted file mode 100644 index ba526b5..0000000 Binary files a/img/net/https-certificate.png and /dev/null differ diff --git a/img/net/https-client-hello.jpg b/img/net/https-client-hello.jpg deleted file mode 100644 index e994177..0000000 Binary files a/img/net/https-client-hello.jpg and /dev/null differ diff --git a/img/net/https-server-hello.jpg b/img/net/https-server-hello.jpg deleted file mode 100644 index 72ef9e2..0000000 Binary files a/img/net/https-server-hello.jpg and /dev/null differ diff --git a/img/net/nio.png b/img/net/nio.png deleted file mode 100644 index 6aa8959..0000000 Binary files a/img/net/nio.png and /dev/null differ diff --git a/img/oauth2.png b/img/oauth2.png deleted file mode 100644 index c7ccdc2..0000000 Binary files a/img/oauth2.png and /dev/null differ diff --git a/img/object-dead.png b/img/object-dead.png deleted file mode 100644 index 092dc12..0000000 Binary files a/img/object-dead.png and /dev/null differ diff --git a/img/object-handle.png b/img/object-handle.png deleted file mode 100644 index e794881..0000000 Binary files a/img/object-handle.png and /dev/null differ diff --git a/img/optimistic-lock.jpg b/img/optimistic-lock.jpg deleted file mode 100644 index 7a3c464..0000000 Binary files a/img/optimistic-lock.jpg and /dev/null differ diff --git a/img/others/json-web-token.png b/img/others/json-web-token.png deleted file mode 100644 index cfae5cd..0000000 Binary files a/img/others/json-web-token.png and /dev/null differ diff --git a/img/others/seckill.jpg b/img/others/seckill.jpg deleted file mode 100644 index f8e03ed..0000000 Binary files a/img/others/seckill.jpg and /dev/null differ diff --git a/img/parnew-collector.png b/img/parnew-collector.png deleted file mode 100644 index c79c76f..0000000 Binary files a/img/parnew-collector.png and /dev/null differ diff --git a/img/rabbitmq-direct.png b/img/rabbitmq-direct.png deleted file mode 100644 index b93f4a8..0000000 Binary files a/img/rabbitmq-direct.png and /dev/null differ diff --git a/img/rabbitmq-fanout.png b/img/rabbitmq-fanout.png deleted file mode 100644 index 485a4a0..0000000 Binary files a/img/rabbitmq-fanout.png and /dev/null differ diff --git a/img/rabbitmq-return-listener.png b/img/rabbitmq-return-listener.png deleted file mode 100644 index c4b6874..0000000 Binary files a/img/rabbitmq-return-listener.png and /dev/null differ diff --git a/img/rabbitmq-topic.png b/img/rabbitmq-topic.png deleted file mode 100644 index 1cc325b..0000000 Binary files a/img/rabbitmq-topic.png and /dev/null differ diff --git a/img/rabbitmq.png b/img/rabbitmq.png deleted file mode 100644 index 18e05e2..0000000 Binary files a/img/rabbitmq.png and /dev/null differ diff --git a/img/redis/cache-consist.png b/img/redis/cache-consist.png deleted file mode 100644 index b74c9e2..0000000 Binary files a/img/redis/cache-consist.png and /dev/null differ diff --git a/img/redis/evalsha.png b/img/redis/evalsha.png deleted file mode 100644 index ce00e50..0000000 Binary files a/img/redis/evalsha.png and /dev/null differ diff --git a/img/redis/list-api.png b/img/redis/list-api.png deleted file mode 100644 index ca79514..0000000 Binary files a/img/redis/list-api.png and /dev/null differ diff --git a/img/redis/redis-replication.png b/img/redis/redis-replication.png deleted file mode 100644 index 057306a..0000000 Binary files a/img/redis/redis-replication.png and /dev/null differ diff --git a/img/redis/redis-skiplist.png b/img/redis/redis-skiplist.png deleted file mode 100644 index eb9fd26..0000000 Binary files a/img/redis/redis-skiplist.png and /dev/null differ diff --git a/img/redis/time-complexity.png b/img/redis/time-complexity.png deleted file mode 100644 index 88a7713..0000000 Binary files a/img/redis/time-complexity.png and /dev/null differ diff --git "a/img/redis/\347\274\223\345\255\230\345\207\273\347\251\277\345\212\240\344\272\222\346\226\245\351\224\201.png" "b/img/redis/\347\274\223\345\255\230\345\207\273\347\251\277\345\212\240\344\272\222\346\226\245\351\224\201.png" deleted file mode 100644 index 16bd977..0000000 Binary files "a/img/redis/\347\274\223\345\255\230\345\207\273\347\251\277\345\212\240\344\272\222\346\226\245\351\224\201.png" and /dev/null differ diff --git a/img/scheduled-task.jpg b/img/scheduled-task.jpg deleted file mode 100644 index c56521d..0000000 Binary files a/img/scheduled-task.jpg and /dev/null differ diff --git a/img/serial-collector.png b/img/serial-collector.png deleted file mode 100644 index 2145dce..0000000 Binary files a/img/serial-collector.png and /dev/null differ diff --git a/img/singleton-class-init.png b/img/singleton-class-init.png deleted file mode 100644 index 4887284..0000000 Binary files a/img/singleton-class-init.png and /dev/null differ diff --git "a/img/spring/\346\226\271\346\263\225\347\272\247\345\210\253\346\263\250\350\247\243.png" "b/img/spring/\346\226\271\346\263\225\347\272\247\345\210\253\346\263\250\350\247\243.png" deleted file mode 100644 index a1ad6f0..0000000 Binary files "a/img/spring/\346\226\271\346\263\225\347\272\247\345\210\253\346\263\250\350\247\243.png" and /dev/null differ diff --git "a/img/springboot/SpringBoot\347\232\204\350\207\252\345\212\250\351\205\215\347\275\256\345\216\237\347\220\206.jpg" "b/img/springboot/SpringBoot\347\232\204\350\207\252\345\212\250\351\205\215\347\275\256\345\216\237\347\220\206.jpg" deleted file mode 100644 index f4b8c47..0000000 Binary files "a/img/springboot/SpringBoot\347\232\204\350\207\252\345\212\250\351\205\215\347\275\256\345\216\237\347\220\206.jpg" and /dev/null differ diff --git a/img/thread-pool.png b/img/thread-pool.png deleted file mode 100644 index bc66194..0000000 Binary files a/img/thread-pool.png and /dev/null differ diff --git a/img/thread-status.jpeg b/img/thread-status.jpeg deleted file mode 100644 index e44c970..0000000 Binary files a/img/thread-status.jpeg and /dev/null differ diff --git a/img/threadlocal-oom.png b/img/threadlocal-oom.png deleted file mode 100644 index f146346..0000000 Binary files a/img/threadlocal-oom.png and /dev/null differ diff --git a/img/threadlocal.png b/img/threadlocal.png deleted file mode 100644 index 4c235ba..0000000 Binary files a/img/threadlocal.png and /dev/null differ diff --git a/img/tree-visit.png b/img/tree-visit.png deleted file mode 100644 index 44d075c..0000000 Binary files a/img/tree-visit.png and /dev/null differ diff --git a/img/two-index.jpg b/img/two-index.jpg deleted file mode 100644 index e5b9371..0000000 Binary files a/img/two-index.jpg and /dev/null differ diff --git a/img/web/servlet-container.jpg b/img/web/servlet-container.jpg deleted file mode 100644 index f7e6310..0000000 Binary files a/img/web/servlet-container.jpg and /dev/null differ diff --git "a/img/\345\271\273\350\257\2731.png" "b/img/\345\271\273\350\257\2731.png" deleted file mode 100644 index 50fc31c..0000000 Binary files "a/img/\345\271\273\350\257\2731.png" and /dev/null differ diff --git "a/img/\346\213\245\345\241\236\346\216\247\345\210\266.jpg" "b/img/\346\213\245\345\241\236\346\216\247\345\210\266.jpg" deleted file mode 100644 index 941f50f..0000000 Binary files "a/img/\346\213\245\345\241\236\346\216\247\345\210\266.jpg" and /dev/null differ diff --git "a/img/\350\257\267\346\261\202\344\277\235\346\212\244\351\205\215\347\275\256\346\226\271\346\263\225.png" "b/img/\350\257\267\346\261\202\344\277\235\346\212\244\351\205\215\347\275\256\346\226\271\346\263\225.png" deleted file mode 100644 index cfe3855..0000000 Binary files "a/img/\350\257\267\346\261\202\344\277\235\346\212\244\351\205\215\347\275\256\346\226\271\346\263\225.png" and /dev/null differ diff --git "a/Java/Java\345\237\272\347\241\200\351\235\242\350\257\225\351\242\230.md" b/java/java-basic.md similarity index 80% rename from "Java/Java\345\237\272\347\241\200\351\235\242\350\257\225\351\242\230.md" rename to java/java-basic.md index 04f0e86..b47004e 100644 --- "a/Java/Java\345\237\272\347\241\200\351\235\242\350\257\225\351\242\230.md" +++ b/java/java-basic.md @@ -1,12 +1,20 @@ +--- +sidebar: heading +copy: + disableCopy: true +--- + +![](http://img.dabin-coder.cn/image/Java基础.jpg) + ## Java的特点 -**Java是一门面向对象的编程语言。**面向对象和面向过程的区别参考下一个问题。 +**Java是一门面向对象的编程语言**。面向对象和面向过程的区别参考下一个问题。 -**Java具有平台独立性和移植性。** +**Java具有平台独立性和移植性**。 - Java有一句口号:`Write once, run anywhere`,一次编写、到处运行。这也是Java的魅力所在。而实现这种特性的正是Java虚拟机JVM。已编译的Java程序可以在任何带有JVM的平台上运行。你可以在windows平台编写代码,然后拿到linux上运行。只要你在编写完代码后,将代码编译成.class文件,再把class文件打成Java包,这个jar包就可以在不同的平台上运行了。 -**Java具有稳健性。** +**Java具有稳健性**。 - Java是一个强类型语言,它允许扩展编译时检查潜在类型不匹配问题的功能。Java要求显式的方法声明,它不支持C风格的隐式声明。这些严格的要求保证编译程序能捕捉调用错误,这就导致更可靠的程序。 - 异常处理是Java中使得程序更稳健的另一个特征。异常是某种类似于错误的异常条件出现的信号。使用`try/catch/finally`语句,程序员可以找到出错的处理代码,这就简化了出错处理和恢复的任务。 @@ -40,11 +48,11 @@ 黑白双方负责接受用户的输入,并告知棋盘系统棋子布局发生变化,棋盘系统接收到了棋子的变化的信息就负责在屏幕上面显示出这种变化,同时利用规则系统来对棋局进行判定。 -## JKD/JRE/JVM三者的关系 +## JDK/JRE/JVM三者的关系 -### JVM +**JVM** -**JVM** :英文名称(Java Virtual Machine),就是我们耳熟能详的 Java 虚拟机。Java 能够跨平台运行的核心在于 JVM 。 +英文名称(Java Virtual Machine),就是我们耳熟能详的 Java 虚拟机。Java 能够跨平台运行的核心在于 JVM 。 ![](http://img.dabin-coder.cn/image/20220402230447.png) @@ -52,7 +60,7 @@ 针对不同的系统有不同的 jvm 实现,有 Linux 版本的 jvm 实现,也有Windows 版本的 jvm 实现,但是同一段代码在编译后的字节码是一样的。这就是Java能够跨平台,实现一次编写,多处运行的原因所在。 -### JRE +**JRE** 英文名称(Java Runtime Environment),就是Java 运行时环境。我们编写的Java程序必须要在JRE才能运行。它主要包含两个部分,JVM 和 Java 核心类库。 @@ -62,7 +70,7 @@ JRE是Java的运行环境,并不是一个开发环境,所以没有包含任 如果你只是想运行Java程序,而不是开发Java程序的话,那么你只需要安装JRE即可。 -### JDK +**JDK** 英文名称(Java Development Kit),就是 Java 开发工具包 @@ -76,15 +84,11 @@ JRE是Java的运行环境,并不是一个开发环境,所以没有包含任 ![](https://cdn.jsdelivr.net/gh/Tyson0314/img/20220404120507.png) - - -### 总结 - 最后,总结一下JDK/JRE/JVM,他们三者的关系 -JRE = JVM + Java 核心类库 +**JRE = JVM + Java 核心类库** -JDK = JRE + Java工具 + 编译器 + 调试器 +**JDK = JRE + Java工具 + 编译器 + 调试器** ![](http://img.dabin-coder.cn/image/20220402230613.png) @@ -119,6 +123,8 @@ JDK = JRE + Java工具 + 编译器 + 调试器 | 二进制位数 | 1 | 8 | 16 | 16 | 32 | 64 | 32 | 64 | | 包装类 | Boolean | Byte | Character | Short | Integer | Long | Float | Double | +在Java规范中,没有明确指出boolean的大小。在《Java虚拟机规范》给出了单个boolean占4个字节,和boolean数组1个字节的定义,具体 **还要看虚拟机实现是否按照规范来**,因此boolean占用1个字节或者4个字节都是有可能的。 + ## 为什么不能用浮点型表示金额? 由于计算机中保存的小数其实是十进制的小数的近似值,并不是准确值,所以,千万不要在代码中使用浮点数来表示金额等重要的指标。 @@ -127,8 +133,10 @@ JDK = JRE + Java工具 + 编译器 + 调试器 ## 什么是值传递和引用传递? -- 值传递是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量。 -- 引用传递一般是对于对象型变量而言的,传递的是该对象地址的一个副本, 并不是原对象本身 。所以对引用对象进行操作会同时改变原对象。 +- 值传递是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量。 +- 引用传递一般是对于对象型变量而言的,传递的是该对象地址的一个副本,并不是原对象本身,两者指向同一片内存空间。所以对引用对象进行操作会同时改变原对象。 + +**java中不存在引用传递,只有值传递**。即不存在变量a指向变量b,变量b指向对象的这种情况。 ## 了解Java的包装类型吗?为什么需要包装类? @@ -283,6 +291,62 @@ String类内部所有的字段都是私有的,也就是被private修饰。而 - StringBuilder 不是线程安全的 - StringBuffer 是线程安全的,内部使用 synchronized 进行同步 +## 什么是StringJoiner? + +StringJoiner是 Java 8 新增的一个 API,它基于 StringBuilder 实现,用于实现对字符串之间通过分隔符拼接的场景。 + +StringJoiner 有两个构造方法,第一个构造要求依次传入分隔符、前缀和后缀。第二个构造则只要求传入分隔符即可(前缀和后缀默认为空字符串)。 + +```java +StringJoiner(CharSequence delimiter, CharSequence prefix, CharSequence suffix) +StringJoiner(CharSequence delimiter) +``` + +有些字符串拼接场景,使用 StringBuffer 或 StringBuilder 则显得比较繁琐。 + +比如下面的例子: + +```java +List values = Arrays.asList(1, 3, 5); +StringBuilder sb = new StringBuilder("("); + +for (int i = 0; i < values.size(); i++) { + sb.append(values.get(i)); + if (i != values.size() -1) { + sb.append(","); + } +} + +sb.append(")"); +``` + +而通过StringJoiner来实现拼接List的各个元素,代码看起来更加简洁。 + +```java +List values = Arrays.asList(1, 3, 5); +StringJoiner sj = new StringJoiner(",", "(", ")"); + +for (Integer value : values) { + sj.add(value.toString()); +} +``` + +另外,像平时经常使用的Collectors.joining(","),底层就是通过StringJoiner实现的。 + +源码如下: + +```java +public static Collector joining( + CharSequence delimiter,CharSequence prefix,CharSequence suffix) { + return new CollectorImpl<>( + () -> new StringJoiner(delimiter, prefix, suffix), + StringJoiner::add, StringJoiner::merge, + StringJoiner::toString, CH_NOID); +} +``` + + + ## String 类的常用方法有哪些? - indexOf():返回指定字符的索引。 @@ -1130,6 +1194,129 @@ Java泛型是JDK 5中引⼊的⼀个新特性, 允许在定义类和接口的 ## String为什么不可变? +先看看什么是不可变的对象。 + +如果一个对象,在它创建完成之后,不能再改变它的状态,那么这个对象就是不可变的。 + +不能改变状态的意思是,不能改变对象内的成员变量,包括基本数据类型的值不能改变,引用类型的变量不能指向其他的对象,引用类型指向的对象的状态也不能改变。 + +接着来看Java8 String类的源码: + +```java +public final class String + implements java.io.Serializable, Comparable, CharSequence { + / The value is used for character storage. */ + private final char value[]; + + / Cache the hash code for the string */ + private int hash; // Default to 0 +} +``` + +从源码可以看出,String对象其实在内部就是一个个字符,存储在这个value数组里面的。 + +value数组用final修饰,final 修饰的变量,值不能被修改。因此value不可以指向其他对象。 + +String类内部所有的字段都是私有的,也就是被private修饰。而且String没有对外提供修改内部状态的方法,因此value数组不能改变。 + +所以,String是不可变的。 + +那为什么String要设计成不可变的? + +主要有以下几点原因: + +1. 线程安全。同一个字符串实例可以被多个线程共享,因为字符串不可变,本身就是线程安全的。 +2. 支持hash映射和缓存。因为String的hash值经常会使用到,比如作为 Map 的键,不可变的特性使得 hash 值也不会变,不需要重新计算。 +3. 出于安全考虑。网络地址URL、文件路径path、密码通常情况下都是以String类型保存,假若String不是固定不变的,将会引起各种安全隐患。比如将密码用String的类型保存,那么它将一直留在内存中,直到垃圾收集器把它清除。假如String类不是固定不变的,那么这个密码可能会被改变,导致出现安全隐患。 +4. 字符串常量池优化。String对象创建之后,会缓存到字符串常量池中,下次需要创建同样的对象时,可以直接返回缓存的引用。 + +既然我们的String是不可变的,那它内部还有很多substring, replace, replaceAll这些操作的方法。 + +这些方法好像会改变String对象?怎么解释呢? + +其实不是的,我们每次调用replace等方法,其实会在堆内存中创建了一个新的对象。然后其value数组引用指向不同的对象。 + +## 如何停止一个正在运行的线程? + +有几种方式。 + +1、**使用线程的stop方法**。 + +使用stop()方法可以强制终止线程。不过stop是一个被废弃掉的方法,不推荐使用。 + +使用Stop方法,会一直向上传播ThreadDeath异常,从而使得目标线程解锁所有锁住的监视器,即释放掉所有的对象锁。使得之前被锁住的对象得不到同步的处理,因此可能会造成数据不一致的问题。 + +2、**使用interrupt方法中断线程**,该方法只是告诉线程要终止,但最终何时终止取决于计算机。调用interrupt方法仅仅是在当前线程中打了一个停止的标记,并不是真的停止线程。 + +接着调用 Thread.currentThread().isInterrupted()方法,可以用来判断当前线程是否被终止,通过这个判断我们可以做一些业务逻辑处理,通常如果isInterrupted返回true的话,会抛一个中断异常,然后通过try-catch捕获。 + +3、**设置标志位** + +设置标志位,当标识位为某个值时,使线程正常退出。设置标志位是用到了共享变量的方式,为了保证共享变量在内存中的可见性,可以使用volatile修饰它,这样的话,变量取值始终会从主存中获取最新值。 + +但是这种volatile标记共享变量的方式,在线程发生阻塞时是无法完成响应的。比如调用Thread.sleep() 方法之后,线程处于不可运行状态,即便是主线程修改了共享变量的值,该线程此时根本无法检查循环标志,所以也就无法实现线程中断。 + +因此,interrupt() 加上手动抛异常的方式是目前中断一个正在运行的线程**最为正确**的方式了。 + +## 什么是跨域? + +简单来讲,跨域是指从一个域名的网页去请求另一个域名的资源。由于有**同源策略**的关系,一般是不允许这么直接访问的。但是,很多场景经常会有跨域访问的需求,比如,在前后端分离的模式下,前后端的域名是不一致的,此时就会发生跨域问题。 + +**那什么是同源策略呢**? + +所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。 + +同源策略限制以下几种行为: + +```mipsasm +1. Cookie、LocalStorage 和 IndexDB 无法读取 +2. DOM 和 Js对象无法获得 +3. AJAX 请求不能发送 +``` + +**为什么要有同源策略**? + +举个例子,假如你刚刚在网银输入账号密码,查看了自己的余额,然后再去访问其他带颜色的网站,这个网站可以访问刚刚的网银站点,并且获取账号密码,那后果可想而知。因此,从安全的角度来讲,同源策略是有利于保护网站信息的。 + +## 跨域问题怎么解决呢? + +嗯,有以下几种方法: + +**CORS**,跨域资源共享 + +CORS(Cross-origin resource sharing),跨域资源共享。CORS 其实是浏览器制定的一个规范,浏览器会自动进行 CORS 通信,它的实现主要在服务端,通过一些 HTTP Header 来限制可以访问的域,例如页面 A 需要访问 B 服务器上的数据,如果 B 服务器 上声明了允许 A 的域名访问,那么从 A 到 B 的跨域请求就可以完成。 + +**@CrossOrigin注解** + +如果项目使用的是Springboot,可以在Controller类上添加一个 @CrossOrigin(origins ="*") 注解就可以实现对当前controller 的跨域访问了,当然这个标签也可以加到方法上,或者直接加到入口类上对所有接口进行跨域处理。注意SpringMVC的版本要在4.2或以上版本才支持@CrossOrigin。 + +**nginx反向代理接口跨域** + +nginx反向代理跨域原理如下: 首先同源策略是浏览器的安全策略,不是HTTP协议的一部分。服务器端调用HTTP接口只是使用HTTP协议,不会执行JS脚本,不需要同源策略,也就不存在跨越问题。 + +nginx反向代理接口跨域实现思路如下:通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录。 + +```js +// proxy服务器 +server { + listen 81; + server_name www.domain1.com; + location / { + proxy_pass http://www.domain2.com:8080; #反向代理 + proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名 + index index.html index.htm; + + add_header Access-Control-Allow-Origin http://www.domain1.com; + } +} +``` + +这样我们的前端代理只要访问 http:www.domain1.com:81/*就可以了。 + +**通过jsonp跨域** + +通常为了减轻web服务器的负载,我们把js、css,img等静态资源分离到另一台独立域名的服务器上,在html页面中再通过相应的标签从不同域名下加载静态资源,这是浏览器允许的操作,基于此原理,我们可以通过动态创建script,再请求一个带参网址实现跨域通信。 + ![](http://img.dabin-coder.cn/image/20220612101342.png) diff --git "a/Java/Java\351\233\206\345\220\210\351\235\242\350\257\225\351\242\230.md" b/java/java-collection.md similarity index 95% rename from "Java/Java\351\233\206\345\220\210\351\235\242\350\257\225\351\242\230.md" rename to java/java-collection.md index 5af404d..c634c0a 100644 --- "a/Java/Java\351\233\206\345\220\210\351\235\242\350\257\225\351\242\230.md" +++ b/java/java-collection.md @@ -1,3 +1,5 @@ +![](http://img.dabin-coder.cn/image/Java集合.jpg) + ## 常见的集合有哪些? Java集合类主要由两个接口**Collection**和**Map**派生出来的,Collection有三个子接口:List、Set、Queue。 @@ -124,7 +126,7 @@ return h&(length-1); //第三步 取模运算 在JDK1.8的实现中,优化了高位运算的算法,通过`hashCode()`的高16位异或低16位实现的:这么做可以在数组比较小的时候,也能保证考虑到高低位都参与到Hash的计算中,可以减少冲突,同时不会有太大的开销。 -## 为什么建议设置HashMap的容量? +### 为什么建议设置HashMap的容量? HashMap有扩容机制,就是当达到扩容条件时会进行扩容。扩容条件就是当HashMap中的元素个数超过临界值时就会自动扩容(threshold = loadFactor * capacity)。 @@ -147,7 +149,7 @@ HashMap有扩容机制,就是当达到扩容条件时会进行扩容。扩容 5. 链表的数量大于阈值8,就要转换成红黑树的结构 6. 添加成功后会检查是否需要扩容 -![图片来源网络](http://img.dabin-coder.cn/image/hashmap-put.png) +![](http://img.dabin-coder.cn/image/map_put.png) ### 红黑树的特点? @@ -400,12 +402,22 @@ Copy-On-Write,写时复制。当我们往容器添加元素时,不直接往 ### CopyOnWriteArrayList -CopyOnWriteArrayList相当于线程安全的ArrayList,CopyOnWriteArrayList使用了一种叫写时复制的方法,当有新元素add到CopyOnWriteArrayList时,先从原有的数组中拷贝一份出来,然后在新的数组做写操作,写完之后,再将原来的数组引用指向到新数组。 +**CopyOnWriteArrayList**是Java并发包中提供的一个并发容器。CopyOnWriteArrayList相当于线程安全的ArrayList,CopyOnWriteArrayList使用了一种叫写时复制的方法,当有新元素add到CopyOnWriteArrayList时,先从原有的数组中拷贝一份出来,然后在新的数组做写操作,写完之后,再将原来的数组引用指向到新数组。 `CopyOnWriteArrayList`中add方法添加的时候是需要加锁的,保证同步,避免了多线程写的时候复制出多个副本。读的时候不需要加锁,如果读的时候有其他线程正在向`CopyOnWriteArrayList`添加数据,还是可以读到旧的数据。 CopyOnWrite并发容器用于读多写少的并发场景。 +**优点**: + +读操作性能很高,因为无需任何同步措施,比较适用于**读多写少**的并发场景。Java的list在遍历时,若中途有别的线程对list容器进行修改,则会抛出**ConcurrentModificationException**异常。而CopyOnWriteArrayList由于其"读写分离"的思想,遍历和修改操作分别作用在不同的list容器,所以在使用迭代器进行遍历时候,也就不会抛出ConcurrentModificationException异常了。 + +**缺点**: + +**一是内存占用问题**,毕竟每次执行写操作都要将原容器拷贝一份,数据量大时,对内存压力较大,可能会引起频繁GC; + +**二是无法保证实时性**,Vector对于读写操作均加锁同步,可以保证读和写的强一致性。而CopyOnWriteArrayList由于其实现策略的原因,写和读分别作用在新老不同容器上,在写操作执行过程中,读不会阻塞但读取到的却是老容器的数据。 + ### ConcurrentLinkedQueue 非阻塞队列。高效的并发队列,使用链表实现。可以看做一个线程安全的 `LinkedList`,通过 CAS 操作实现。 diff --git "a/Java/Java\345\271\266\345\217\221\351\235\242\350\257\225\351\242\230.md" b/java/java-concurrent.md similarity index 91% rename from "Java/Java\345\271\266\345\217\221\351\235\242\350\257\225\351\242\230.md" rename to java/java-concurrent.md index 022a2fc..b739636 100644 --- "a/Java/Java\345\271\266\345\217\221\351\235\242\350\257\225\351\242\230.md" +++ b/java/java-concurrent.md @@ -1,7 +1,41 @@ +![](http://img.dabin-coder.cn/image/并发与多线程知识点总结.jpg) + ## 线程池 线程池:一个管理线程的池子。 +### 为什么平时都是使用线程池创建线程,直接new一个线程不好吗? + +嗯,手动创建线程有两个缺点 + +1. 不受控风险 +2. 频繁创建开销大 + +**为什么不受控**? + +系统资源有限,每个人针对不同业务都可以手动创建线程,并且创建线程没有统一标准,比如创建的线程有没有名字等。当系统运行起来,所有线程都在抢占资源,毫无规则,混乱场面可想而知,不好管控。 + +**频繁手动创建线程为什么开销会大?跟new Object() 有什么差别?** + +虽然Java中万物皆对象,但是new Thread() 创建一个线程和 new Object()还是有区别的。 + +new Object()过程如下: + +1. JVM分配一块内存 M +2. 在内存 M 上初始化该对象 +3. 将内存 M 的地址赋值给引用变量 obj + +创建线程的过程如下: + +1. JVM为一个线程栈分配内存,该栈为每个线程方法调用保存一个栈帧 +2. 每一栈帧由一个局部变量数组、返回值、操作数堆栈和常量池组成 +3. 每个线程获得一个程序计数器,用于记录当前虚拟机正在执行的线程指令地址 +4. 系统创建一个与Java线程对应的本机线程 +5. 将与线程相关的描述符添加到JVM内部数据结构中 +6. 线程共享堆和方法区域 + +创建一个线程大概需要1M左右的空间(Java8,机器规格2c8G)。可见,频繁手动创建/销毁线程的代价是非常大的。 + ### 为什么使用线程池? - **降低资源消耗**。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。 @@ -427,31 +461,41 @@ Thread[线程 2,5,main]waiting get resource1 ### 线程都有哪些方法? -**join** +**start** -Thread.join(),在main中创建了thread线程,在main中调用了thread.join()/thread.join(long millis),main线程放弃cpu控制权,线程进入WAITING/TIMED_WAITING状态,等到thread线程执行完才继续执行main线程。 +用于启动线程。 -```java -public final void join() throws InterruptedException { - join(0); -} -``` +**getPriority** -**yield** +获取线程优先级,默认是5,线程默认优先级为5,如果不手动指定,那么线程优先级具有继承性,比如线程A启动线程B,那么线程B的优先级和线程A的优先级相同 -Thread.yield(),一定是当前线程调用此方法,当前线程放弃获取的CPU时间片,但不释放锁资源,由运行状态变为就绪状态,让OS再次选择线程。作用:让相同优先级的线程轮流执行,但并不保证一定会轮流执行。实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield()不会导致阻塞。该方法与sleep()类似,只是不能由用户指定暂停多长时间。 +**setPriority** -```java -public static native void yield(); //static方法 -``` +设置线程优先级。CPU会尽量将执行资源让给优先级比较高的线程。 -**sleep** +**interrupt** -Thread.sleep(long millis),一定是当前线程调用此方法,当前线程进入TIMED_WAITING状态,让出cpu资源,但不释放对象锁,指定时间到后又恢复运行。作用:给其它线程执行机会的最佳方式。 +告诉线程,你应该中断了,具体到底中断还是继续运行,由被通知的线程自己处理。 -```java -public static native void sleep(long millis) throws InterruptedException;//static方法 -``` +当对一个线程调用 interrupt() 时,有两种情况: + +1. 如果线程处于被阻塞状态(例如处于sleep, wait, join 等状态),那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常。 + +2. 如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true。不过,被设置中断标志的线程可以继续正常运行,不受影响。 + +interrupt() 并不能真正的中断线程,需要被调用的线程自己进行配合才行。 + +**join** + +等待其他线程终止。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。 + +**yield** + +暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。 + +**sleep** + +使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,线程自动转为Runnable状态。 @@ -612,15 +656,15 @@ class SeasonThreadTask implements Runnable{ ## 线程间通信方式 -### volatile +**volatile** **volatile** 使用共享内存实现线程间相互通信。多个线程同时监听一个变量,当这个变量被某一个线程修改的时候,其他线程可以感知到这个变化。 -### wait 和 notify +**wait 和 notify** `wait/notify`为Object对象的方法,调用`wait/notify`需要先获得对象的锁。对象调用`wait()`之后线程释放锁,将线程放到对象的等待队列,当通知线程调用此对象的`notify()`方法后,等待线程并不会立即从`wait()`返回,需要等待通知线程释放锁(通知线程执行完同步代码块),等待队列里的线程获取锁,获取锁成功才能从`wait()`方法返回,即从`wait()`方法返回前提是线程获得锁。 -### join +**join** 当在一个线程调用另一个线程的`join()`方法时,当前线程阻塞等待被调用join方法的线程执行完毕才能继续执行。`join()`是基于等待通知机制实现的。 @@ -1093,6 +1137,28 @@ SynchronizedMap一次锁住整张表来保证线程安全,所以每次只能 JDK1.8 ConcurrentHashMap采用CAS和synchronized来保证并发安全。数据结构采用数组+链表/红黑二叉树。synchronized只锁定当前链表或红黑二叉树的首节点,支持并发访问、修改。 另外ConcurrentHashMap使用了一种不同的迭代方式。当iterator被创建后集合再发生改变就不再是抛出ConcurrentModificationException,取而代之的是在改变时new新的数据从而不影响原有的数据 ,iterator完成后再将头指针替换为新的数据 ,这样iterator线程可以使用原来老的数据,而写线程也可以并发的完成改变。 +## 怎么判断线程池的任务是不是执行完了? + +有几种方法: + +1、使用线程池的原生函数**isTerminated()**; + +executor提供一个原生函数isTerminated()来判断线程池中的任务是否全部完成。如果全部完成返回true,否则返回false。 + +2、**使用重入锁,维持一个公共计数**。 + +所有的普通任务维持一个计数器,当任务完成时计数器加一(这里要加锁),当计数器的值等于任务数时,这时所有的任务已经执行完毕了。 + +3、**使用CountDownLatch**。 + +它的原理跟第二种方法类似,给CountDownLatch一个计数值,任务执行完毕后,调用countDown()执行计数值减一。最后执行的任务在调用方法的开始调用await()方法,这样整个任务会阻塞,直到这个计数值为零,才会继续执行。 + +这种方式的**缺点**就是需要提前知道任务的数量。 + +4、**submit向线程池提交任务,使用Future判断任务执行状态**。 + +使用submit向线程池提交任务与execute提交不同,submit会有Future类型的返回值。通过future.isDone()方法可以知道任务是否执行完成。 + ![](http://img.dabin-coder.cn/image/20220612101342.png) diff --git "a/Java/Java8\346\226\260\347\211\271\346\200\247.md" b/java/java8.md similarity index 88% rename from "Java/Java8\346\226\260\347\211\271\346\200\247.md" rename to java/java8.md index efd2c16..359350d 100644 --- "a/Java/Java8\346\226\260\347\211\271\346\200\247.md" +++ b/java/java8.md @@ -1,36 +1,3 @@ - - - - -- [函数式编程](#%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B) -- [Lambda 表达式](#lambda-%E8%A1%A8%E8%BE%BE%E5%BC%8F) -- [函数式接口](#%E5%87%BD%E6%95%B0%E5%BC%8F%E6%8E%A5%E5%8F%A3) -- [内置的函数式接口](#%E5%86%85%E7%BD%AE%E7%9A%84%E5%87%BD%E6%95%B0%E5%BC%8F%E6%8E%A5%E5%8F%A3) - - [Predicate 断言](#predicate-%E6%96%AD%E8%A8%80) - - [Comparator](#comparator) - - [Consumer](#consumer) -- [Stream](#stream) - - [Filter 过滤](#filter-%E8%BF%87%E6%BB%A4) - - [Sorted 排序](#sorted-%E6%8E%92%E5%BA%8F) - - [Map 转换](#map-%E8%BD%AC%E6%8D%A2) - - [Match 匹配](#match-%E5%8C%B9%E9%85%8D) - - [Count 计数](#count-%E8%AE%A1%E6%95%B0) - - [Reduce](#reduce) - - [flatMap](#flatmap) -- [Parallel-Streams](#parallel-streams) -- [Map 集合](#map-%E9%9B%86%E5%90%88) -- [参考资料](#%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99) - - - -> 本文已经收录到github仓库,此仓库用于分享Java相关知识总结,包括Java基础、MySQL、Spring Boot、MyBatis、Redis、RabbitMQ等等,欢迎大家提pr和star! -> -> github地址:https://github.com/Tyson0314/Java-learning -> -> 如果github访问不了,可以访问gitee仓库。 -> -> gitee地址:https://gitee.com/tysondai/Java-learning - ## 函数式编程 面向对象编程:面向对象的语言,一切皆对象,如果想要调用一个函数,函数必须属于一个类或对象,然后在使用类或对象进行调用。面向对象编程可能需要多写很多重复的代码行。 @@ -47,8 +14,6 @@ 函数式编程:在某些编程语言中,如js、c++,我们可以直接写一个函数,然后在需要的时候进行调用,即函数式编程。 - - ## Lambda 表达式 在Java8以前,使用`Collections`的sort方法对字符串排序的写法: @@ -675,11 +640,3 @@ public class MapTest3 { `https://juejin.im/post/5c3d7c8a51882525dd591ac7#heading-16` - -最后给大家分享一个github仓库,上面放了**200多本经典的计算机书籍**,包括C语言、C++、Java、Python、前端、数据库、操作系统、计算机网络、数据结构和算法、机器学习、编程人生等,可以star一下,下次找书直接在上面搜索,仓库持续更新中~ - -github地址:https://github.com/Tyson0314/java-books - -如果github访问不了,可以访问gitee仓库。 - -gitee地址:https://gitee.com/tysondai/java-books \ No newline at end of file diff --git "a/Java/JVM\351\253\230\351\242\221\351\235\242\350\257\225\351\242\230.md" b/java/jvm.md similarity index 94% rename from "Java/JVM\351\253\230\351\242\221\351\235\242\350\257\225\351\242\230.md" rename to java/jvm.md index 3f2af40..caea8cd 100644 --- "a/Java/JVM\351\253\230\351\242\221\351\235\242\350\257\225\351\242\230.md" +++ b/java/jvm.md @@ -1,10 +1,12 @@ +![](http://img.dabin-coder.cn/image/JVM知识点总结.jpg) + ## 讲一下JVM内存结构? JVM内存结构分为5大区域,**程序计数器**、**虚拟机栈**、**本地方法栈**、**堆**、**方法区**。 ![](http://img.dabin-coder.cn/image/jvm内存结构0.png) -### 程序计数器 +**程序计数器** 线程私有的,作为当前线程的行号指示器,用于记录当前虚拟机正在执行的线程指令地址。程序计数器主要有两个作用: @@ -13,7 +15,7 @@ JVM内存结构分为5大区域,**程序计数器**、**虚拟机栈**、**本 程序计数器是唯一一个不会出现 `OutOfMemoryError` 的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡。 -### 虚拟机栈 +**虚拟机栈** Java 虚拟机栈是由一个个栈帧组成,而每个栈帧中都拥有:**局部变量表**、**操作数栈**、**动态链接**、**方法出口信息**。每一次函数调用都会有一个对应的栈帧被压入虚拟机栈,每一个函数调用结束后,都会有一个栈帧被弹出。 @@ -32,13 +34,13 @@ Java 虚拟机栈也是线程私有的,每个线程都有各自的 Java 虚拟 java -Xss2M ``` -### 本地方法栈 +**本地方法栈** 虚拟机栈为虚拟机执行 `Java` 方法服务,而本地方法栈则为虚拟机使用到的 `Native` 方法服务。`Native` 方法一般是用其它语言(C、C++等)编写的。 本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。 -### 堆 +**堆** 堆用于存放对象实例,是垃圾收集器管理的主要区域,因此也被称作`GC`堆。堆可以细分为:新生代(`Eden`空间、`From Survivor`、`To Survivor`空间)和老年代。 @@ -48,17 +50,17 @@ java -Xss2M java -Xms1M -Xmx2M ``` -### 方法区 +1.方法区 方法区与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。 对方法区进行垃圾回收的主要目标是**对常量池的回收和对类的卸载**。 -**永久代** +2.永久代 方法区是 JVM 的规范,而永久代`PermGen`是方法区的一种实现方式,并且只有 `HotSpot` 有永久代。对于其他类型的虚拟机,如`JRockit`没有永久代。由于方法区主要存储类的相关信息,所以对于动态生成类的场景比较容易出现永久代的内存溢出。 -**元空间** +3.元空间 JDK 1.8 的时候,`HotSpot`的永久代被彻底移除了,使用元空间替代。元空间的本质和永久代类似,都是对JVM规范中方法区的实现。两者最大的区别在于:元空间并不在虚拟机中,而是使用直接内存。 @@ -66,11 +68,11 @@ JDK 1.8 的时候,`HotSpot`的永久代被彻底移除了,使用元空间替 永久代内存受限于 JVM 可用内存,而元空间使用的是直接内存,受本机可用内存的限制,虽然元空间仍旧可能溢出,但是相比永久代内存溢出的概率更小。 -### 运行时常量池 +**运行时常量池** 运行时常量池是方法区的一部分,在类加载之后,会将编译器生成的各种字面量和符号引号放到运行时常量池。在运行期间动态生成的常量,如 String 类的 intern()方法,也会被放入运行时常量池。 -### 直接内存 +**直接内存** 直接内存并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用。而且也可能导致 `OutOfMemoryError` 错误出现。 @@ -695,6 +697,34 @@ class Person { - jstat 查看监控JVM的内存和GC情况,评估问题大概出在什么区域 - 使用MAT工具载入dump文件,分析大对象的占用情况 +## 什么是内存溢出和内存泄露? + +内存溢出指的是程序申请内存时,**没有足够的内存**供申请者使用,比如给了你一块存储int类型数据的存储空间,但是你却存储long类型的数据,那么结果就是内存不够用,此时就会报错OOM,即内存溢出。 + +内存泄露是指程序中间动态分配了内存,但在程序结束时没有释放这部分内存,从而造成那部分**内存不可用**的情况。这种情况重启计算机可以解决,但也有可能再次发生内存泄露。内存泄露和硬件没有关系,它是由软件设计缺陷引起的。 + +像IO操作或者网络连接等,在使用完成之后没有调用close()方法将其连接关闭,那么它们占用的内存是不会自动被GC回收的,此时就会产生内存泄露。 + +比如操作数据库时,通过SessionFactory获取一个session: + +```java +Session session=sessionFactory.openSession(); +``` + +完成后我们必须调用session.close()方法关闭,否则就会产生内存泄露,因为sessionFactory这个长生命周期对象一直持有session这个短生命周期对象的引用。 + +那两者有什么不同呢? + +内存泄露可以通过完善代码来避免,内存溢出可以通过调整配置来减少发生频率,但无法彻底避免。 + +如何避免内存泄露和溢出呢? + +1. **尽早释放无用对象的引用**。比如使用临时变量的时候,让引用变量在退出活动域后自动设置为null,暗示垃圾收集器来收集该对象,防止发生内存泄露。 +2. **尽量少用静态变量**。因为静态变量是全局的,GC不会回收。 +3. **避免集中创建对象尤其是大对象**,如果可以的话尽量使用流操作。 +4. 尽量运用**池化技术**(数据库连接池等)以提高系统性能。 +5. **避免在循环中创建过多对象**。 + **参考资料** diff --git a/learning-resources/cs-learn-guide.md b/learning-resources/cs-learn-guide.md new file mode 100644 index 0000000..540416e --- /dev/null +++ b/learning-resources/cs-learn-guide.md @@ -0,0 +1,269 @@ +大家好,我是大彬~今天给大家分享CS自学路线。 + +首先看看计算机专业的培养体系,基本上每个学校都差不多: + +![](http://img.dabin-coder.cn/image/20220703110718.png) + +像计算机组成原理、操作系统等课程让我们了解计算机体系结构的基本知识和概念,计算机是怎么运行的,学习计算机的基本组成原理和内部运行机制,并探索硬、软件之间相互作用的关系,以及如何有效利用硬件提高系统性能。 + +计算机网络则是让你明白计算机网络的概念、原理和体系结构,知道计算机分层结构,物理层、数据链路层、介质访问子层、网络层、传输层和应用层的基本原理和协议,掌握以 TCP/IP 协议族为主的网络协议结构,并且了解网络新技术的最新发展。 + +编译原理则是介绍编译程序构造的原理与实践,让你明白高级语言都是如何被转换为另外一种语言的。学完编译原理,可以尝试自己去实现一个完整的小型面向对象语言的编译程序。 + +汇编语言则是高级语言在机器层面的表示,掌握这两种语言的对应可以将程序的执行与计算机的工作过程紧密联系起来,直接体现汇编语言本身固有的特点,即它是最易于将“程序”和“机器” 统一起来的一个结合点。 + +计算机专业课程里边,**计算机核心课程无非以下几个:** + +1. **计算机组成原理** +2. **操作系统** +3. **编译原理** +4. **计算机网络** +5. **数据结构与算法** +6. **数据库基础** + +![](http://img.dabin-coder.cn/image/20220703162734.png) + +## 操作系统 + +无论学习什么编程语言,和需要和操作系统打交道。如果对操作系统不熟悉,那么你在未来的学习路上将会遇到很多障碍,比如线程进程调度、内存分配、Java的虚拟机等知识,都会一头雾水。因此,只有把操作系统搞明白了,才能够更好地学习计算机的其他知识。 + +**书籍推荐** + +入门级别书籍:**《现代操作系统》、《操作系统导论》**,进阶:**《深入理解计算机系统》** + +强推《深入理解计算机系统》 这本书。 + +![](http://img.dabin-coder.cn/image/20220703132845.jpg) + +CSAPP是一本很好的书,糅合了计算机组成原理、操作系统、网络编程、并行程序设计原理等课程的基础知识。对于刚接触编程,或者像大彬这种非科班出身的人来说,这是一本指导性的书,它会告诉你,要想成为一个优秀的程序员,应当重点理解哪些计算机底层原理,告诉你应该在以后的自学过程中,应该重点学习哪些课程,比如操作系统和体系结构等。 + +**视频教程推荐** + +Udacity的Advanced OS公开课:https://www.classcentral.com/course/udacity-advanced-operating-systems-1016 + +还有国内不错的操作系统的课程,清华大学的公开课:https://www.xuetangx.com/course/THU08091000267/5883104?channel=search_result + +![](http://img.dabin-coder.cn/image/20220703132955.png) + +由清华大学两位老师向勇、陈渝讲授,同时配有一套完整的实验,实验内容是从无到有地建立起一个小却五脏俱全的操作系统,以主流操作系统为实例,以教学操作系统ucore为实验环境,讲授操作系统的概念、基本原理和实现技术,为学生从事操作系统软件研究和开发,以及充分利用操作系统功能进行应用软件研究和开发打下扎实的基础。 + +另外,推荐另一门MIT操作系统课程:**MIT6.268** + +课程地址:https://pdos.csail.mit.edu/6.828/2018/schedule.html + +![](http://img.dabin-coder.cn/image/20220626115851.png) + +MIT6.828 是一门非常值得学习的课程,广受好评,是**理论与实践相结合的经典**。 + +只要你跟着项目一步一步走,做完 6 个实验,就能实现一个简单的操作系统内核。 + +每个实验都有对应的知识点,学完理论知识后会有相应的练习,学习体验非常棒! + +**建议**在开始学习这门课之前先熟悉C和汇编,对计算机组成有一定了解。 + +**操作系统主要知识点**: + +- 操作系统的基础特征 +- 进程与线程的本质区别、以及各自的使用场景 +- 进程的几种状态 +- 进程通信方法的特点以及使用场景 +- 进程任务调度算法的特点以及使用场景 +- 死锁的原因、必要条件、死锁处理。手写死锁代码、Java是如何解决死锁的。 +- 线程实现的方式 +- 协程的作用 +- 内存管理的方式 +- 虚拟内存的作用,分页系统实现虚拟内存原理 +- 页面置换算法的原理 +- 静态链接和动态链接 + +## 计算机组成原理 + +计算机组成原理,主要学习计算机的基本组成原理和内部运行机制,并探索硬、软件之间相互作用的关系,以及如何有效利用硬件提高系统性能。 + +**书籍推荐** + +《计算机是怎样跑起来的》这本书相对比较基础,描述计算机各个方面。从单片机电路开始,汇编,结构化程序,数据结构于算法,面向对象,数据库,TCP/IP原理,加密解密,XML,软件工程统统有清晰描述,易于理解。在知识的整体理解基础上再阅读文档,学习编程会事半功倍。所以而推荐本书。 + +![](http://img.dabin-coder.cn/image/20220703123249.jpg) + +另外还有两本书也不错,推荐给大家《计算机组成与设计:硬件 / 软件接口》 、《深入理解计算机系统》 + +**视频推荐** + +计算机组成原理(哈工大刘宏伟): https://www.bilibili.com/video/BV1WW411Q7PF + +刘宏伟老师主讲,他的课不仅适合考研人,也非常适合初学者,初学者也听得懂。 + +![](http://img.dabin-coder.cn/image/20220703131541.png) + +【麻省理工学院-中文字幕版】计算机组成原理:https://www.bilibili.com/video/BV1kU4y177x9 + +课程为 MIT 6.004 Computation Structures, Spring 2017,如果英文不错,可以跟着学学,课程质量很高。 + +![](http://img.dabin-coder.cn/image/20220703131900.png) + + + +## 编译原理 + +编译原理介绍了编译程序构造的原理与实践,让你明白高级语言都是如何被转换为另外一种语言的。学完编译原理,可以尝试自己去实现一个完整的小型面向对象语言的编译程序。 + +**书籍推荐** + +**《编译原理》**和**《编译器设计 第二版》**。 + +特别是《编译器设计 第二版》这本书,对于对编译有兴趣的同学,这本书绝对是值得推荐的。 + +![](http://img.dabin-coder.cn/image/20220703133449.jpg) + +这本书覆盖了编译器从前端到后端的全部主题,很多算法都可以在 JVM C2 compiler 中找到对应实现。整体翻译很流畅,不愧是经典! + +哈工大的编译原理视频:https://www.bilibili.com/video/BV1zW411t7YE?p=1&vd_source=2b77c4a826e636ae19a4f75a4b2ca146 + +![](http://img.dabin-coder.cn/image/20220703132211.png) + +比起很多砖头书和博客,强太多!陈鄞老师的 PPT 做的很好,讲得也很通俗易懂,课程评价也很高。推荐! + +编译原理-国防科技大学:https://www.bilibili.com/video/BV12741147J3 + +课程前置知识:具备计算机程序设计语言和程序设计知识,对数据结构与算法、计算机原理、离散数学等相关知识有一定了解更好。视频简洁明了,适合多刷几遍。 + +![](http://img.dabin-coder.cn/image/20220703132547.png) + +## 数据结构和算法 + +为什么学习数据结构与算法?对于计算机专业的同学来说,这门课程是必修的,考研基本也是必考科目。对于程序员来说,数据结构与算法也是面试、笔试必备的非常重要的考察点。 + +数据结构与算法是程序员内功体现的重要标准之一,且数据结构也应用在各个方面。数据结构也蕴含一些面向对象的思想,故学好掌握数据结构对逻辑思维处理抽象能力有很大提升。 + +**书籍推荐** + +**《大话数据结构》和《算法图解》** + +《大话数据结构》 这本书最大的特点是,通篇以一种趣味方式来叙述,大量引用了各种各样的生活知识来类比,并充分运用图形语言来体现抽象内容,对数据结构所涉及到的一些经典算法做到逐行分析、多算法比较。这本书特别适合初学者。 + +![](http://img.dabin-coder.cn/image/20220703150022.jpg) + +《算法图解》是非常好的入门算法书,示例丰富,图文并茂,以让人容易理解的方式阐释了算法,旨在帮助程序员在日常项目中更好地发挥算法的能量。 + +很多学Java的同学,可能会问有没有Java版本的数据结构和算法书籍? + +当然有的,可以看看**《数据结构与算法分析 java语言描述》**这本书,用Java语言描述各种数据结构和算法,对于Java开发者来说,更容易理解。 + +**视频推荐** + +**UCSanDiego的数据结构与算法专项课程**:https://www.coursera.org/specializations/algorithms + +**浙大陈越姥姥的数据结构课程**: + +https://www.bilibili.com/video/BV1H4411N7oD + +![](http://img.dabin-coder.cn/image/20220703150635.png) + +浙江大学陈越姥姥和何钦铭教授联合授课,非常经典的课程。姥姥我的偶像! + +**小甲鱼的数据结构和算法课程**:https://www.bilibili.com/video/BV1jW411K7yg + +数据结构与算法主要学习以下内容: + +- 基本数据结构(数组、链表、栈、队列等) +- 树(二叉树、avl树、b树、红黑树等) +- 堆结构 +- 排序算法(冒泡排序、选择排序、插入排序、快速排序、归并排序、堆排序等及时间空间复杂度) +- 动态规划、回溯、贪心算法(多刷刷leetcode) +- 递归 +- 位运算 + +学完感觉还很吃力?可以借助一些刷题网站巩固下。下面推荐几个刷题网站。 + +### [牛客网](https://www.nowcoder.com/) + +![](http://img.dabin-coder.cn/image/20220619223253.png) + +作为牛客红名大佬,来给牛客宣传一波!(牛客打钱!) + +牛客网拥有超级丰富的 IT 题库,题库+面试+学习+求职+讨论,基本涵盖所有面试笔试题型,堪称"互联网求职神器"。在这里不仅可以刷题,还可以跟其他牛友讨论交流,一起成长。牛客上还会各种的内推机会,对于求职的同学也是极其不错的。 + +### [LeetCode](https://leetcode.cn/) + +![](http://img.dabin-coder.cn/image/20220619231232.png) + +**力扣,强推**!力扣虐我千百遍,我待力扣如初恋! + +从现在开始,每天一道力扣算法题,坚持几个月的时间,你会感谢我的(傲娇脸) + +我刚开始刷算法题的时候,就选择在力扣上刷。最初刷easy级别题目的时候,都感觉有点吃力,坚持半年之后,遇到中等题目甚至hard级别的题目都不慌了。 + +不过是熟能生巧罢了。 + +### [LintCode](https://www.lintcode.com/) + +![](http://img.dabin-coder.cn/image/20220619231320.png) + +与Leetcode类似的刷题网站。 + +LeetCode/LintCode的题目量差不多。LeetCode的**test case比较完备**,并且LeetCode有**讨论区**,看别人的代码还是比较有意义的。 + +LintCode的UI、tagging、filter更加灵活,更有优点,大家选择其中一个进行刷题即可。 + +## 计算机网络 + +计算机网络这门课需要学习计算机网络的概念、原理和体系结构,知道计算机分层结构,物理层、数据链路层、介质访问子层、网络层、传输层和应用层的基本原理和协议,掌握以 TCP/IP 协议族为主的网络协议结构,并且了解网络新技术的最新发展。 + +**书籍推荐** + +《计算机网络自顶向下方法》 + +![](http://img.dabin-coder.cn/image/20220703144400.jpg) + +这本书是经典的计算机网络教材,采用作者独创的自顶向下方法来讲授计算机网络的原理及其协议,自第1版出版以来已经被数百所大学和学院选作教材。书中从应用层讲起,然后展开,摆脱了从物理层开始的枯燥,直接接触应用实例,更能吸引读者的兴趣。而且,书上很多例子举的很好,生动形象。 + +**视频推荐** + +视频推荐中科大郑烇、杨坚全套《计算机网络(自顶向下方法 第7版,James F.Kurose,Keith W.Ross)》课程。这门课是2020年秋科大自动化系本科课程录制版,可与中科大学生一起完成专业知识的学习。 + +https://www.bilibili.com/video/BV1JV411t7ow?p=7&vd_source=2b77c4a826e636ae19a4f75a4b2ca146 + +![](http://img.dabin-coder.cn/image/20220703143955.png) + +另外还可以看看哈尔滨工业大学李全龙老师的计算机网络课程:https://www.bilibili.com/video/BV1Up411Z7hC + +![](http://img.dabin-coder.cn/image/20220703144500.png) + +**计算机网络核心知识点**: + +- 网络分层结构 +- TCP/IP +- 三次握手四次挥手 +- 滑动窗口、拥塞控制 +- HTTP/HTTPS +- 网络安全问题(CSRF、XSS、SQL注入等) + +## 数据库 + +互联网应用大多属于数据密集型应用,对于真实世界的数据密集型应用而言,除非你准备从基础组件的轮子造起,不然根本没那么多机会去摆弄花哨的数据结构和算法。 + +实际生产中,数据表就是数据结构,索引与查询就是算法。而应用代码往往扮演的是胶水的角色,处理IO与业务逻辑,其他大部分工作都是在数据系统之间搬运数据。在最宽泛的意义上,有状态的地方就有数据库。它无所不在,网站的背后、应用的内部,单机软件,区块链里,甚至在离数据库最远的Web浏览器中。 + +**书籍推荐** + +- 《MySQL必知必会》 +- 《高性能mysql》 + +《MySQL必知必会》主要是Mysql的基础语法,很好理解。后面有了基础再看《高性能mysql》,这本书主要讲解索引、SQL优化、高级特性等,很多Mysql相关面试题出自《高性能MySQL》这本书,值得一看。 + +**视频推荐** + +伯克利的 CS168 课程:https://archive.org/details/UCBerkeley_Course_Computer_Science_186 + +![](http://img.dabin-coder.cn/image/20220703152850.png) + +国内中国人民大学王珊老师的《数据库系统概论》:https://www.bilibili.com/video/BV1pW411W7Do + +![](http://img.dabin-coder.cn/image/20220703153133.png) + + + +码字不易,如果觉得对你有帮助,可以**点个赞**鼓励一下! + +我是 [@程序员大彬](https://zhuanlan.zhihu.com/people/dai-shu-bin-13) ,专注Java后端硬核知识分享,欢迎大家关注~ \ No newline at end of file diff --git a/learning-resources/java-learn-guide.md b/learning-resources/java-learn-guide.md new file mode 100644 index 0000000..e392730 --- /dev/null +++ b/learning-resources/java-learn-guide.md @@ -0,0 +1,806 @@ +大家好,我是大彬~ + +我本科学的不是计算机,大四开始自学Java,并且找到了中大厂的offer。自学路上遇到不少问题,每天晚上都是坚持到一两点才睡觉,**最终也拿到了30w的offer**。 + +![](http://img.dabin-coder.cn/image/image-20211206000941636.png) + +在这里也提醒学弟学妹们,要尽早确定以后的方向,读研还是工作,找工作的话,也要尽快确定工作岗位,想转行的,需要花更多的时间准备。很多同学到了大四快毕业的时候,才思考自己未来要做什么,这个时候已经有点晚了。如果错过了校招,走社招渠道去找工作,难度将会提升一个等级,到时后悔也来不及! + +下面来说说自己的经历吧(附自学路线)。 + +## 接触编程 + +大学以前基本没碰过电脑,家里没电脑,也没去过网吧。高中的计算机课程,期末作业要完成一个自我介绍的PPT,也不会做,最后直接抄同桌的作业(复制粘贴都不会。。还得同桌教,捂脸)。 + +高考完一个月后,买了电脑,真正开始使用上了电脑。 + +大一上学期的时候,系里开了一门C语言的课程,这也是我第一次接触编程。教材是英文的,刚开始学还是挺头大的。每次课程作业,周围的同学都是一顿复制粘贴,我也一样嘿嘿。 + +记得在讲指针那一章的时候,听的一头雾水。稍微走神,回过头来,已经不知道讲的是啥了。 + +后面系里开设了兴趣小组,因为平时比较闲,也想着去捣鼓点东西,就去参加了。刚开始的时候,什么都不懂,老师推荐我学一下51单片机,拿了一本厚厚的51单片机的书籍,跟着书里的demo敲了一遍,发现了新天地!原来编程这么有意思! + +![](https://pic2.zhimg.com/80/v2-2ac0759cc48ed0d17b9ce46a13bb0f1e_720w.jpg) + +记得第一次跑出流水灯的时候,那叫一个激动啊,满满的都是成就感!后面也写了一些电机、红外遥控等demo。从那以后,激发了我学习编程的兴趣。 + +到了大二,辅导员在群里发布全国电子设计大赛的信息,参赛题跟四轴飞行器相关,那段时间对四轴飞行器比较感兴趣,于是约了两个小伙伴一块参加。距离比赛时间只有一个月,在那一个月的时间里,每天都是早出晚归,吃饭的时候还在想着哪一块代码出了bug。虽然最后没能获奖,但是在这个过程中,学到很多知识,编程能力也有了很大的提升。 + +## 决定转码 + +转眼间,大三开学,开始纠结考研还是工作,思考了一周时间,也进了系里的实验室体验了一把研究生生活,最后还是听从内心的想法,决定直接找工作。 + +我咨询了本专业的师兄师姐们往年的就业情况,他们大部分人还是找了互联网方向的工作。有一个在传统行业的师兄,也劝我投互联网公司的岗位,因为在传统行业加班也不少,但是工资贼低。。最后决定转行程序员,找后端相关的工作。 + +那么学习哪一种语言呢?当时有三个选择:c++,Java,python。 + +那段时间python比较火,但是经过一番深思熟虑之后,还是选择了Java。为什么选择Java呢? + +很简单,市场需求大,学习难度适中。相比科班同学来说,我缺乏系统的计算机基础知识,而距离秋招也只有不到一年时间,所以还是选择学习难度低一点的Java。 + +## 闭关自学 + +确定方向后,便开始制定学习路线。不得不说,Java要学的东西是真的多。。 + +自学期间遇到挺多问题,比如一些环境配置问题,有时候搞上好几天,很打击积极性。中途也有很多次怀疑自己的水平,是不是不适合干编程,差点就放弃了。幸好最后还是坚持了下来。 + +半年多的时间,除了平时上课,其他时间就是在图书馆。周末或者节假日,每天都是7点起床,八点到图书馆开始学习,到了晚上十点,图书馆闭馆,才回宿舍,每天都是图书馆最后走的一批。回到宿舍,洗完澡,继续肝到十二点多(卷王!)。 + +![img](https://pic3.zhimg.com/80/v2-25f6523ffc0ea6982c576b75458695dc_720w.jpg) + +> 很多人在问,大三才开始自学Java,来的及吗? 我觉得,还是看个人的投入程度和学习能力。有些人自学能力强一点,每天可以投入10小时及以上的时间去学习,那完全没问题。 + +自学过程还是挺辛苦的,要耐得住寂寞,最最重要的还是得坚持! + +我根据自己的自学经历,整理了一些学习过程中踩坑总结的经验,希望自学的小伙伴可以少走弯路: + +- **注重实践**,不要只是埋头看书,一定要多动手写代码。 +- 刚开始自学的时候,可以不用太深究细节,不然可能会怀疑自己的学习能力。等到后面有了一定的基础,回过头来重新回顾,可能会恍然大悟,没有当初想的那么难。 +- 可以适当加一些交流群,遇到不懂的知识点,多与其他人交流。 + +好了,下面给大家分享一下我的自学经验。 + +## 自学路线 + +首先看一下Java学习路线图: + +![](http://img.dabin-coder.cn/image/Java学习路线-gitmind-清晰.png) + +在这里也给大家分享一份精心整理的**大厂高频面试题PDF**,小伙伴靠着这份手册拿过阿里offer,需要的小伙伴可以自行下载: + +http://mp.weixin.qq.com/s?__biz=Mzg2OTY1NzY0MQ==&mid=2247485445&idx=1&sn=1c6e224b9bb3da457f5ee03894493dbc&chksm=ce98f543f9ef7c55325e3bf336607a370935a6c78dbb68cf86e59f5d68f4c51d175365a189f8#rd + +## Java + +![](http://img.dabin-coder.cn/image/Java基础.jpg) + +推荐书籍: + +- 《head first java》 +- 《JAVA核心技术卷》 + +head first系列的书籍讲解比较有趣,比较好理解。《JAVA核心技术卷》难度相对适中,内容也比较全面,部分章节(如Swing)可以跳过。 + +视频推荐动力节点老杜的视频教程,1000w的播放量!视频总体上质量很不错,讲解挺详细,适合新手。跟着老杜的视频学下来,可以学到很多知识! + +https://www.bilibili.com/video/BV1Rx411876f + +再次强调:**多敲代码!多敲代码!多敲代码!** + +学习编程就是看书加实践,要多动手,不然看过的知识点很快就会忘,而且多实践也会遇到很多坑,丰富经验。 可以到github上找一些项目练练手,通过做项目巩固知识,而且每实现一个功能之后,会有满满的成就感,也会激励你不断去学习。 + +**Java基础知识主要有:** + +- 面向对象特性 +- Java语言基础、循环、数组 ; 了解类和对象 + + - 掌握强制数据类型转换和自动类型提升规则; + - 常量如何声明及赋值; + - 循环的语法及作用; + - 数组的声明及定义; + - 掌握类的概念以及什么是对象。 +- 抽象类和接口 +- 数据类型、重写重载、封装继承多态 +- 容器类Map/List/Set等 +- 异常处理 +- 反射机制 +- 泛型 +- 常用类:String、时间类 +- 函数式编程 +- Stream API +- Lambda 表达式 +- IO流操作,多线程及Socket + - 掌握IO读写流相关的类,了解字节流,字符流和字符流缓冲区; + - 掌握线程的概念,多线程的创建、启动方式,锁和同步的概念及运用; + - 掌握Socket通信的概念,如何声明客户端服务端,如何完成双端数据通信。 + +## Java Web + +Java Web是一系列技术的综合,也是大多数Java开发者的技术方向。有必要学习一下。这部分可以看看视频教程。 + +视频推荐尚硅谷的JavaWeb全套教程,HTML/CSS/JavaScript等跟前端相关的可以倍速观看。 + +https://www.bilibili.com/video/BV1Y7411K7zz + +黑马程序员的Java web教程总体也不错 + +https://www.bilibili.com/video/BV1qv4y1o79t + +下面列举Java web需要掌握的知识点。 + +HTML: + +- 掌握网页的基本构成; +- 掌握HTML的基本语法; +- 表格的作用以及合并行、合并列; +- 表单标签的使用,提交方式get/post的区别; +- 框架布局的使用 + +CSS: + +- 掌握CSS的语法及作用,在html中的声明方式; +- 掌握CSS布局的函数使用; +- 掌握CSS外部样式的引入。 + +JavaScript: + +- 掌握JS的语法及作用,在HTML中的声明方式; +- 掌握JS的运行方式; +- 掌握JS中的变量声明、函数声明、参数传递等; +- 掌握HTML中的标签事件使用; +- 掌握JS中的DOM原型 + +jQuery: + +- 了解如何使用jQuery,下载最新版或者老版本的jQuery.js +- 掌握选择器、文档处理、属性、事件等语法及使用; +- 能够灵活使用选择器查找到想要查找的元素并操作他们的属性; +- 动态声明事件; +- 动态创建元素。 + +Servlet + +- 掌握Java中的Web项目目录结构; +- 掌握Java Web项目的重要中间件Tomcat; +- 掌握Servlet中的Request和Response; +- 掌握Servlet的基本运行过程。 +- 掌握Servlet的声明周期 + +Ajax + +- 掌握Ajax的基本概念; +- 掌握jQuery中的Ajax请求; +- 掌握JSON + +Filter、Listener: + +- 掌握Filter和Listener +- 掌握Session过滤器和编码过滤器 + +JSP数据交互 + +- JSP中如何编写Java代码,如何使用Java中的类; +- JSP中的参数传递。 + +状态管理Session和Cookie + +- 掌握Session、Cookie的作用及作用域; +- 掌握Session及Cookie的区别,存储位置,声明周期等; +- 掌握Session及Cookie分别在JSP和Cookie中的使用 + +## 框架 + +主流框架主要有: + +- spring:面向切面、依赖注入。 +- springboot:习惯优于配置、自动配置。目前很多公司内部都是使用Spring Boot。 +- springmvc:基于MVC架构模式的轻量级Web框架 +- Mybatis:orm框架。 +- springcloud + +### Spring + +![](http://img.dabin-coder.cn/image/Spring总结.jpg) + +大部分公司都会用到 Spring框架,必学!。主要理解 Spring 面向切面、依赖注入的特性,学会使用 Spring 构建应用程序。推荐书籍《Spring实战》,通过demo的方式带你一步步搭建Spring应用 + +视频推荐尚硅谷王泽老师的Spring5框架最新版教程,视频刚出不久,内容也是与时俱进,值得学习! + +https://www.bilibili.com/video/BV1Vf4y127N5 + +### SpringMVC + +SpringMVC是基于**MVC架构模式**的轻量级Web框架,对于初学者,需要掌握Web请求从发出到相应的这个过程,SpringMVC做了什么,还有MVC模式的思想。 + +视频推荐狂神说Java的SpringMVC最新教程。 + +【狂神说Java】SpringMVC最新教程IDEA版通俗易懂:https://www.bilibili.com/video/BV1aE41167Tu + +### Mybatis + +MyBatis 是一款优秀的持久层框架,MyBatis 帮助我们做了很多事情:建立连接、操作 Statment、ResultSet、处理 JDBC 相关异常等,简化了开发流程。推荐书籍《深入浅出Mybatis》。 + +视频推荐狂神说的Mybatis最新完整教程,b站播放量最高,获得了很多小伙伴的一致好评。 + +https://www.bilibili.com/video/BV1NE411Q7Nx + +### SpringBoot + +学完 SSM,就要进一步学习 SpringBoot 了,相信很多人在学了 Spring 之后,面对各种各样的配置,想必都会头疼。而 SpringBoot 的出现解决了这个问题,SpringBoot 去除了大量的 XML 配置文件,简化了复杂的依赖管理。书籍推荐《Spring Boot实战》。 + +视频推荐尚硅谷雷神的2021版最新SpringBoot2权威教程。 + +https://www.bilibili.com/video/BV1Et411Y7tQ + +### SpringCloud + +现在面试基本都会问到微服务相关的内容,最好了解下微服务相关的知识。服务注册与发现、负载均衡、服务降级、API网关等。推荐书籍《spring cloud微服务实战》 + +视频教程可以看看尚硅谷周阳老师的: + +https://www.bilibili.com/video/BV18E411x7eT + +## 并发 + +![](http://img.dabin-coder.cn/image/并发与多线程知识点总结.jpg) + +什么是并发编程,简单来说就是为了充分利用cpu,多个任务同时执行,快速完成任务。 + +并发编程的相关内容可以看看《JAVA并发编程实战》这本书。 + +视频推荐狂神说Java,很不错的视频: + +https://www.bilibili.com/video/BV1B7411L7tE + +主要知识点有: + +- 线程的概念以及案例 +- 线程池原理 +- 线程间通信方式 +- 锁(synchronized、ReentrantLock) +- 并发工具类(CountDownLatch/CyclicBarrier/Semaphore) +- 原子类 +- AQS +- Thread生命周期状态 +- Java内存模型 + +## Redis + +![](http://img.dabin-coder.cn/image/Redis知识点.jpg) + +用来缓存热点数据,加快读写速度,从而提高性能。现在Java后端的面试基本都会问到Redis。 + +书籍推荐《redis实战》和《redis设计与实现》。 + +视频推荐狂神说Java的Redis最新超详细版教程,不仅教你学Redis,还会教你学习的方式。 + +https://www.bilibili.com/video/BV1S54y1R7SB + +## 消息队列 + +消息队列是基础数据结构中`FIFO`的一种数据结构,用来解决应用解耦、异步消息、流量削锋等问题,可以实现高性能、高可用、可伸缩和最终一致性。 + +视频推荐黑马的RocketMQ教程和百知教育的RabbitMQ教程,两者挑一个学习就可以! + +【编程不良人】MQ消息中间件之RabbitMQ: + +https://www.bilibili.com/video/BV1dE411K7MG + +黑马程序员Java教程RocketMQ系统精讲: + +https://www.bilibili.com/video/BV1L4411y7mn + +## JVM + +![](http://img.dabin-coder.cn/image/JVM知识点总结.jpg) + +JVM也是面试经常会问的内容。Java开发者不用自己进行内存管理、垃圾回收,JVM帮我们做了,但是还是有必要了解下JVM的工作原理,这样在出现oom等问题的时候,才有思路去排查和解决问题。书籍推荐周老师的《深入理解Java虚拟机》。 + +视频推荐尚硅谷宋红康的全套课程,全套课程分为三个篇章:《内存与垃圾回收篇》、《字节码与类的加载篇》和《性能监控与调优篇》。 + +尚硅谷JVM全套教程: + +https://www.bilibili.com/video/BV1PJ411n7xZ + +JVM的基础知识: + +- jvm内存结构(程序计数器、虚拟机栈、本地方法栈、堆、方法区、运行时常量池、直接内存) +- 类加载过程 +- 双亲委派 +- 垃圾回收算法 +- 垃圾回收器 +- 调优工具(jsp/jstack/jstat/jmap,了解即可) + +## 计算机基础知识 + +学编程一定要打好计算机基础! + +对于非科班同学来说,与科班同学最大的差距在于**基本理论知识**。如果你是非科班自学编程的,想要进入大厂,那么计算机基础知识一定不能落下。 + +每一个合格的程序员,应该要知道计算机体系的结构,内在的逻辑是什么,要有自己的思考。 + +**总之,基本功非常重要!** + +### 操作系统 + +无论学习什么编程语言,和需要和操作系统打交道。如果对操作系统不熟悉,那么你在未来的学习路上将会遇到很多障碍,比如线程进程调度、内存分配、Java的虚拟机等知识,都会一头雾水。因此,只有把操作系统搞明白了,才能够更好地学习计算机的其他知识。 + +**书籍推荐** + +入门级别书籍:**《现代操作系统》、《操作系统导论》**,进阶:**《深入理解计算机系统》** + +强推《深入理解计算机系统》 这本书。 + +![](http://img.dabin-coder.cn/image/20220703132845.jpg) + +CSAPP是一本很好的书,糅合了计算机组成原理、操作系统、网络编程、并行程序设计原理等课程的基础知识。对于刚接触编程,或者像大彬这种非科班出身的人来说,这是一本指导性的书,它会告诉你,要想成为一个优秀的程序员,应当重点理解哪些计算机底层原理,告诉你应该在以后的自学过程中,应该重点学习哪些课程,比如操作系统和体系结构等。 + +**视频教程推荐** + +Udacity的Advanced OS公开课:https://www.classcentral.com/course/udacity-advanced-operating-systems-1016 + +还有国内不错的操作系统的课程,清华大学的公开课:https://www.xuetangx.com/course/THU08091000267/5883104?channel=search_result + +![](http://img.dabin-coder.cn/image/20220703132955.png) + +由清华大学两位老师向勇、陈渝讲授,同时配有一套完整的实验,实验内容是从无到有地建立起一个小却五脏俱全的操作系统,以主流操作系统为实例,以教学操作系统ucore为实验环境,讲授操作系统的概念、基本原理和实现技术,为学生从事操作系统软件研究和开发,以及充分利用操作系统功能进行应用软件研究和开发打下扎实的基础。 + +另外,推荐另一门MIT操作系统课程:**MIT6.268** + +课程地址:https://pdos.csail.mit.edu/6.828/2018/schedule.html + +![](http://img.dabin-coder.cn/image/20220626115851.png) + +MIT6.828 是一门非常值得学习的课程,广受好评,是**理论与实践相结合的经典**。 + +只要你跟着项目一步一步走,做完 6 个实验,就能实现一个简单的操作系统内核。 + +每个实验都有对应的知识点,学完理论知识后会有相应的练习,学习体验非常棒! + +**建议**在开始学习这门课之前先熟悉C和汇编,对计算机组成有一定了解。 + +**操作系统主要知识点**: + +- 操作系统的基础特征 +- 进程与线程的本质区别、以及各自的使用场景 +- 进程的几种状态 +- 进程通信方法的特点以及使用场景 +- 进程任务调度算法的特点以及使用场景 +- 死锁的原因、必要条件、死锁处理。手写死锁代码、Java是如何解决死锁的。 +- 线程实现的方式 +- 协程的作用 +- 内存管理的方式 +- 虚拟内存的作用,分页系统实现虚拟内存原理 +- 页面置换算法的原理 +- 静态链接和动态链接 + +### 数据结构和算法 + +![](http://img.dabin-coder.cn/image/数据结构与算法.jpg) + +为什么学习数据结构与算法?对于计算机专业的同学来说,这门课程是必修的,考研基本也是必考科目。对于程序员来说,数据结构与算法也是面试、笔试必备的非常重要的考察点。 + +数据结构与算法是程序员内功体现的重要标准之一,且数据结构也应用在各个方面。数据结构也蕴含一些面向对象的思想,故学好掌握数据结构对逻辑思维处理抽象能力有很大提升。 + +**书籍推荐** + +**《大话数据结构》和《算法图解》** + +《大话数据结构》 这本书最大的特点是,通篇以一种趣味方式来叙述,大量引用了各种各样的生活知识来类比,并充分运用图形语言来体现抽象内容,对数据结构所涉及到的一些经典算法做到逐行分析、多算法比较。这本书特别适合初学者。 + +![](http://img.dabin-coder.cn/image/20220703150022.jpg) + +《算法图解》是非常好的入门算法书,示例丰富,图文并茂,以让人容易理解的方式阐释了算法,旨在帮助程序员在日常项目中更好地发挥算法的能量。 + +很多学Java的同学,可能会问有没有Java版本的数据结构和算法书籍? + +当然有的,可以看看**《数据结构与算法分析 java语言描述》**这本书,用Java语言描述各种数据结构和算法,对于Java开发者来说,更容易理解。 + +**视频推荐** + +**UCSanDiego的数据结构与算法专项课程**:https://www.coursera.org/specializations/algorithms + +**浙大陈越姥姥的数据结构课程**: + +https://www.bilibili.com/video/BV1H4411N7oD + +![](http://img.dabin-coder.cn/image/20220703150635.png) + +浙江大学陈越姥姥和何钦铭教授联合授课,非常经典的课程。姥姥我的偶像! + +**小甲鱼的数据结构和算法课程**:https://www.bilibili.com/video/BV1jW411K7yg + +数据结构与算法主要学习以下内容: + +- 基本数据结构(数组、链表、栈、队列等) +- 树(二叉树、avl树、b树、红黑树等) +- 堆结构 +- 排序算法(冒泡排序、选择排序、插入排序、快速排序、归并排序、堆排序等及时间空间复杂度) +- 动态规划、回溯、贪心算法(多刷刷leetcode) +- 递归 +- 位运算 + +学完感觉还很吃力?可以借助一些刷题网站巩固下。下面推荐几个刷题网站。 + +### [牛客网](https://www.nowcoder.com/) + +![](http://img.dabin-coder.cn/image/20220619223253.png) + +作为牛客红名大佬,来给牛客宣传一波!(牛客打钱!) + +牛客网拥有超级丰富的 IT 题库,题库+面试+学习+求职+讨论,基本涵盖所有面试笔试题型,堪称"互联网求职神器"。在这里不仅可以刷题,还可以跟其他牛友讨论交流,一起成长。牛客上还会各种的内推机会,对于求职的同学也是极其不错的。 + +### [LeetCode](https://leetcode.cn/) + +![](http://img.dabin-coder.cn/image/20220619231232.png) + +**力扣,强推**!力扣虐我千百遍,我待力扣如初恋! + +从现在开始,每天一道力扣算法题,坚持几个月的时间,你会感谢我的(傲娇脸) + +我刚开始刷算法题的时候,就选择在力扣上刷。最初刷easy级别题目的时候,都感觉有点吃力,坚持半年之后,遇到中等题目甚至hard级别的题目都不慌了。 + +不过是熟能生巧罢了。 + +### [LintCode](https://www.lintcode.com/) + +![](http://img.dabin-coder.cn/image/20220619231320.png) + +与Leetcode类似的刷题网站。 + +LeetCode/LintCode的题目量差不多。LeetCode的**test case比较完备**,并且LeetCode有**讨论区**,看别人的代码还是比较有意义的。 + +LintCode的UI、tagging、filter更加灵活,更有优点,大家选择其中一个进行刷题即可。 + +### 数据库 + +![](http://img.dabin-coder.cn/image/MySQL知识点总结.jpg) + +互联网应用大多属于数据密集型应用,对于真实世界的数据密集型应用而言,除非你准备从基础组件的轮子造起,不然根本没那么多机会去摆弄花哨的数据结构和算法。 + +实际生产中,数据表就是数据结构,索引与查询就是算法。而应用代码往往扮演的是胶水的角色,处理IO与业务逻辑,其他大部分工作都是在数据系统之间搬运数据。在最宽泛的意义上,有状态的地方就有数据库。它无所不在,网站的背后、应用的内部,单机软件,区块链里,甚至在离数据库最远的Web浏览器中。 + +**书籍推荐** + +- 《MySQL必知必会》 +- 《高性能mysql》 + +《MySQL必知必会》主要是Mysql的基础语法,很好理解。后面有了基础再看《高性能mysql》,这本书主要讲解索引、SQL优化、高级特性等,很多Mysql相关面试题出自《高性能MySQL》这本书,值得一看。 + +**视频推荐** + +伯克利的 CS168 课程:https://archive.org/details/UCBerkeley_Course_Computer_Science_186 + +![](http://img.dabin-coder.cn/image/20220703152850.png) + +国内中国人民大学王珊老师的《数据库系统概论》:https://www.bilibili.com/video/BV1pW411W7Do + +![](http://img.dabin-coder.cn/image/20220703153133.png) + +MySQL基础知识: + +- 增删改查 +- 事务特性、隔离级别 +- 索引原理、优化 +- b+树 +- 最左匹配原则 +- 存储引擎 +- MVCC +- 执行计划 +- 分库分表 +- 日志,bin log/undo log/redo log +- ... + +### 计算机网络 + +![](http://img.dabin-coder.cn/image/计算机网络知识.jpg) + +计算机网络这门课需要学习计算机网络的概念、原理和体系结构,知道计算机分层结构,物理层、数据链路层、介质访问子层、网络层、传输层和应用层的基本原理和协议,掌握以 TCP/IP 协议族为主的网络协议结构,并且了解网络新技术的最新发展。 + +**书籍推荐** + +《计算机网络自顶向下方法》 + +![](http://img.dabin-coder.cn/image/20220703144400.jpg) + +这本书是经典的计算机网络教材,采用作者独创的自顶向下方法来讲授计算机网络的原理及其协议,自第1版出版以来已经被数百所大学和学院选作教材。书中从应用层讲起,然后展开,摆脱了从物理层开始的枯燥,直接接触应用实例,更能吸引读者的兴趣。而且,书上很多例子举的很好,生动形象。 + +**视频推荐** + +视频推荐中科大郑烇、杨坚全套《计算机网络(自顶向下方法 第7版,James F.Kurose,Keith W.Ross)》课程。这门课是2020年秋科大自动化系本科课程录制版,可与中科大学生一起完成专业知识的学习。 + +https://www.bilibili.com/video/BV1JV411t7ow?p=7&vd_source=2b77c4a826e636ae19a4f75a4b2ca146 + +![](http://img.dabin-coder.cn/image/20220703143955.png) + +另外还可以看看哈尔滨工业大学李全龙老师的计算机网络课程:https://www.bilibili.com/video/BV1Up411Z7hC + +![](http://img.dabin-coder.cn/image/20220703144500.png) + +**计算机网络核心知识点**: + +- 网络分层结构 +- TCP/IP +- 三次握手四次挥手 +- 滑动窗口、拥塞控制 +- HTTP/HTTPS +- 网络安全问题(CSRF、XSS、SQL注入等) + +### linux + +Linux 系统已经渗透到 IT 领域的各个角落,作为一名 IT 从业人员,不管你是专注于编程,还是专注于运维,都应该对 Linux 有所了解,甚至还要深入学习,掌握核心原理。 + +至少要熟悉常用的Linux命令。书籍推荐**《鸟哥的linux私房菜》**。 + +视频推荐: + +https://www.bilibili.com/video/BV1dW411M7xL + +## 设计模式 + +![](http://img.dabin-coder.cn/image/设计模式.jpg) + +设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。对于具有丰富的开发经验的开发人员,学习设计模式有助于了解在软件开发过程中所面临的问题的最佳解决方案;对于那些经验不足的开发人员,学习设计模式有助于通过一种简单快捷的方式来学习软件设计。 + +**为什么要学习设计模式**: + +- 设计模式是从许多优秀的软件系统中总结出能够实现可维护性、复用的设计方案,使用这些方案可以避免做一些重复性的工作 +- 合理使用设计模式并对设计模式的使用情况进行文档化,将有助于别人更快地理解系统 +- 学习设计模式将有助于初学者更加深入地理解面向对象思想 + +**设计模式分类**: + +**1.1 创建型模式** + +创建型模式(Creational Pattern)对类的实例化过程进行了抽象,能够将模块中对象的创建和对象的使用分离。 + +创建型模式包括工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式 + +**1.2 结构型模式** + +结构型模式(Structural Pattern)描述如何将类或者对 象结合在一起形成更大的结构,就像搭积木,可以通过 简单积木的组合形成复杂的、功能更为强大的结构。 + +结构型模式包括适配器模式、装饰模式、代理模式、外观模式、桥接模式、组合模式、享元模式 + +**1.3 行为型模式** + +行为型模式(Behavioral Pattern)是对在不同的对象之间划分责任和算法的抽象化。行为型模式不仅仅关注类和对象的结构,而且重点关注它们之间的相互作用。 + + 行为型模式包括策略模式、模板模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式等。 + +推荐秦疆老师**基于Java讲解的23种设计模式**视频教程。 + +https://www.bilibili.com/video/BV1mc411h719 + +## 工具 + +### Git + +Git 是一个开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目。Git 是 Linus Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。 + +视频推荐:https://www.bilibili.com/video/BV1BE411g7SV + +### Maven + +Maven 是一个软件项目管理工具,可以对 Java 项目进行全自动构建,管理项目所需要的依赖。Maven 也可被用于构建和管理各种项目,例如 C#,Ruby,Scala 和其他语言编写的项目。 + +视频推荐: + +https://www.bilibili.com/video/BV1Ah411S7ZE + +### docker + +Docker 是一个开源的应用容器引擎。Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。 + +Docker 是一个用于开发,交付和运行应用程序的开放平台。Docker 使您能够将应用程序与基础架构分开,从而可以快速交付软件。 + +**Docker的应用场景** + +- Web 应用的自动化打包和发布。 +- 自动化测试和持续集成、发布。 +- 在服务型环境中部署和调整数据库或其他的后台应用。 + +视频推荐广州云科的docker入门教程,非常详细。 + +https://www.bilibili.com/video/BV11L411g7U1 + +## 项目 + +很多同学初学Java都会遇到一个问题,不知道去哪里找Java的项目练手。以前我也遇到这个问题,现在在这里分享下一些比较值得学习的项目。 + +首先给大家推荐几个Java项目的**视频教程**,都是B站上的视频,风评很好,讲解也非常详细,有兴趣的可以看一下~ + +尚硅谷尚筹网Java项目实战开发教程: + +https://www.bilibili.com/video/BV1bE411T7oZ + +尚硅谷Java微服务+分布式+全栈项目【尚医通】 + +https://www.bilibili.com/video/BV1V5411K7rT + +Java Web项目实战-畅购商城: + +https://www.bilibili.com/video/BV13J411k7aQ + +下面也推荐几个Github上比较优质的开源项目。 + +### newbee-mall + +star:7.8k + +https://github.com/newbee-ltd/newbee-mall + +newbee-mall 项目是一套电商系统,包括 newbee-mall 商城系统及 newbee-mall-admin 商城后台管理系统,基于 Spring Boot 2.X 及相关技术栈开发。 前台商城系统包含首页门户、商品分类、新品上线、首页轮播、商品推荐、商品搜索、商品展示、购物车、订单结算、订单流程、个人订单管理、会员中心、帮助中心等模块。 后台管理系统包含数据面板、轮播图管理、商品管理、订单管理、会员管理、分类管理、设置等模块。 + +![](http://img.dabin-coder.cn/image/image-20210827001950033.png) + +### litemall + +star:16.2k + +https://github.com/linlinjava/litemall + +又一个小商城。litemall = Spring Boot后端 + Vue管理员前端 + 微信小程序用户前端 + Vue用户移动端。 + +小商城功能: + +- 首页 +- 专题列表、专题详情 +- 分类列表、分类详情 +- 品牌列表、品牌详情 +- 新品首发、人气推荐 +- 优惠券列表、优惠券选择 +- ... + +![](http://img.dabin-coder.cn/image/litemall.png) + +![](http://img.dabin-coder.cn/image/litemall01.png) + +在这里也分享一份非常棒的Java学习笔记,**Github标星137k+**!这份笔记主要Java基础、容器、Java IO、并发和虚拟机等内容,排版精良,内容更是无可挑剔。 + +![](https://pic3.zhimg.com/80/v2-cdfb49d3d6562191415ae9771055807a_720w.jpg) + +需要的小伙伴可自行下载: + +http://mp.weixin.qq.com/s?__biz=Mzg2OTY1NzY0MQ==&mid=100000392&idx=1&sn=f6c8e84651ce48f6ef5b0d496f0f6adf&chksm=4e98ffce79ef76d8dcebdc4787ae8b37760ec193574da9036e46954ae8954ebd56c78792726f#rd + +### eladmin + +star:16.2k + +https://github.com/elunez/eladmin + +一个基于 Spring Boot 2.1.0 、 Spring Boot Jpa、 JWT、Spring Security、Redis、Vue的前后端分离的后台管理系统。项目采用分模块开发方式, 权限控制采用 RBAC,支持数据字典与数据权限管理,支持一键生成前后端代码,支持动态路由。 + +项目提供了非常详细的文档,地址是[https://el-admin.vip](https://el-admin.vip/) + +项目体验地址:[https://el-admin.xin](https://el-admin.xin/) + +使用的技术栈也比较新,给作者点赞! + +![](http://img.dabin-coder.cn/image/image-20210826235913747.png) + +![](http://img.dabin-coder.cn/image/image-20210826235952292.png) + +### vhr + +star:22.2k + +https://github.com/lenve/vhr + +微人事是一个前后端分离的人力资源管理系统,项目采用SpringBoot+Vue开发。项目加入常见的企业级应用所涉及到的技术点,例如 Redis、RabbitMQ 等。 + +![](http://img.dabin-coder.cn/image/vhr01.png) + + + +### Blog + +star1.2k + +https://github.com/zhisheng17/blog + +`My-Blog` 使用的是 Docker + SpringBoot + Mybatis + thymeleaf 打造的一个个人博客模板。此项目在 [Tale](https://github.com/otale/tale) 博客系统基础上进行修改的。 + +![](http://img.dabin-coder.cn/image/blog.png) + +### community + +star:1.8k + +https://github.com/codedrinker/community + +码问社区。开源论坛、问答系统,现有功能提问、回复、通知、最新、最热、消除零回复功能。技术栈 Spring、Spring Boot、MyBatis、MySQL/H2、Bootstrap。 + +![](http://img.dabin-coder.cn/image/image-20210826234936711.png) + + + +### vblog + +star:6.5k + +https://github.com/lenve/VBlog + +V部落,Vue+SpringBoot实现的多用户博客管理平台! + +后端主要采用了: + +1.SpringBoot +2.SpringSecurity +3.MyBatis +4.部分接口遵循Restful风格 +5.MySQL + +前端主要采用了: + +1.Vue +2.axios +3.ElementUI +4.vue-echarts +5.mavon-editor +6.vue-router + +![](http://img.dabin-coder.cn/image/20220505162337.png) + +### gpmall + +star:4.3k + +https://github.com/2227324689/gpmall + +【咕泡学院实战项目】基于SpringBoot+Dubbo构建的电商平台。业务模块划分,尽量贴合互联网公司的架构体系。所以,除了业务本身的复杂度不是很高之外,整体的架构基本和实际架构相差无几。 + +后端的主要架构是基于springboot+dubbo+mybatis。 + +![](http://img.dabin-coder.cn/image/20220505162427.png) + +![](http://img.dabin-coder.cn/image/20220505162154.png) + +### guns + +star:3.4k + +https://github.com/stylefeng/Guns + +Guns是一个现代化的Java应用开发框架,基于主流技术Spring Boot2,Guns的核心理念是提高开发人员开发效率,降低企业信息化系统的开发成本,提高企业整体开发水平。 + +Guns基于**插件化架构**,在建设系统时,可以自由组合细粒度模块依赖,实现不同功能的组合和剔除,让项目体积灵活控制,从而更方便地搭建不同的业务系统。 + +使用Guns可以快速开发出各类信息化管理系统,例如OA办公系统、项目管理系统、商城系统、供应链系统、客户关系管理系统等。 + +![](http://img.dabin-coder.cn/image/20220505162031.png) + +### music-website + +star:2.3k + +https://github.com/Yin-Hongwei/music-website + +音乐网站。客户端和管理端使用 **Vue** 框架来实现,服务端使用 **Spring Boot + MyBatis** 来实现,数据库使用了 **MySQL**。 + +前端技术栈:Vue3.0 + TypeScript + Vue-Router + Vuex + Axios + ElementPlus + Echarts。 + +![](http://img.dabin-coder.cn/image/20220505161944.jpg) + + + +以上就是Java自学的学习路线,内容不少,转行的小伙伴们加油! + + + +另外,上面提到的书籍,我已经整理了电子版,放到github上了,总共**200多本经典的计算机书籍**,包括C语言、C++、Java、Python、前端、数据库、操作系统、计算机网络、数据结构和算法、机器学习、编程人生等,可以star一下,下次找书直接在上面搜索,仓库持续更新中~(花了一个多月的时间整理的,希望对大家有帮助,欢迎star~) + +仓库持续更新中~ + +![](http://img.dabin-coder.cn/image/书单new.png) + +有需要的自取: + +github仓库:https://github.com/Tyson0314/java-books + + + +码字不易,小伙伴们觉得有帮助的话,点个赞呗 你的赞就是我创作的动力! + +我是 [@程序员大彬](https://zhuanlan.zhihu.com/people/a19ae109d127ec8dacde6bdaa3e83c7a) ,定期会分享Java后台硬核知识,欢迎大家关注~ \ No newline at end of file diff --git a/learning-resources/leetcode-note.md b/learning-resources/leetcode-note.md new file mode 100644 index 0000000..3a42301 --- /dev/null +++ b/learning-resources/leetcode-note.md @@ -0,0 +1,32 @@ +很多同学刚开始刷LeetCode的时候,都会感到很吃力,刷题效率很低。我前段时间刷题的时候也遇到这个问题,直到后来看到这个谷歌师兄总结的刷题笔记,发现LeetCode刷题都是套路呀,掌握这些套路之后,就变得非常简单了! + +这份笔记是作者在找工作的时候,刷了几百道的LeetCode题目,然后按照数据结构和算法进行分类总结成的,非常适合面试前阅读! + +笔记讲解很详细,排版很用心,强烈推荐。**看完这本笔记并且理解透彻之后,面试遇到中等难度的题目基本都能做出来了。** + +![](http://img.dabin-coder.cn/image/image-20210828120937311.png) + +![](http://img.dabin-coder.cn/image/image-20210828121035926.png) + +笔记分为算法和数据结构两大部分,又细分了十五个章节,详细讲解了刷LeetCode 时常用的技巧。手册包含了 LeetCode 大部分高频题目的答案。 + +这本笔记共148页,内容分别有**字符串、栈队列、树、排序、查找、BFS、DFS、贪心、动态规划**等等。每一章节都会先进行详细的算法解释,然后精心挑选几道典型的题目进行实现操练。 + +![](http://img.dabin-coder.cn/image/image-20210828121622711.png) + +![](http://img.dabin-coder.cn/image/image-20210828121736489.png) + +有些不好理解的地方,还有配图讲解,实在是太赞啦! + +![](http://img.dabin-coder.cn/image/image-20210828124053892.png) + +在学习数据结构和算法,或者准备面试的小伙伴们,千万不要错过这份宝藏笔记! + +**手册获取方式**:微信搜索「**程序员大彬**」或者扫描下面的二维码,关注后发送关键字「**谷歌**」就可以找到下载链接了(**无套路,无解压密码**)。 + +![](http://img.dabin-coder.cn/image/公众号.jpg) + +祝大家学习愉快! + + + diff --git a/leetcode/unique-paths.md b/leetcode/unique-paths.md new file mode 100644 index 0000000..db7cdc1 --- /dev/null +++ b/leetcode/unique-paths.md @@ -0,0 +1,118 @@ +分享一道腾讯面试算法题目(LeetCode62题) + +# 题目描述 + +一个机器人位于一个 m x n 网格的左上角 。机器人每次只能**向下或者向右移动一步**。机器人试图达到网格的右下角。 + +问总共有多少条不同的路径? + +![](http://img.dabin-coder.cn/image/uniquePaths1.png) + +**示例**: + +**输入**:m = 3, n = 2 + +**输出**:3 + +从左上角开始,总共有 3 条路径可以到达右下角。 + +1. 向右 -> 向下 -> 向下 +2. 向下 -> 向下 -> 向右 +3. 向下 -> 向右 -> 向下 + +# 解题思路 + +首先了解下**动态规划**的思想。 + +动态规划用于处理有重叠子问题的问题。其基本思想:假如要解一个问题,需要先将问题分解成子问题,求出子问题的解,**再根据子问题的解得出原问题的解**。 + +动态规划算法会将计算出来的**子问题的解存储起来**,以便下次遇到同一个子问题的时候直接可以得到该子问题的解,减少重复计算。 + +**动态规划的解题思路:1、状态定义;2、状态转移方程;3、初始状态;4、确定遍历顺序。** + +接下来分步骤讲解本题目的思路。 + +1、首先是**状态定义**。假设 `dp[i][j]` 是到达 `(i, j)` 的路径数量,`dp[2][2]`就是到达`(2, 2)`的路径数量。 + +2、然后是**状态转移方程**。根据题意,只能向右和向下运动,当前位置`(i, j)`只能从`(i-1, j)`和`(i, j-1)`两个方向走过来,由此可以确定状态方程为`dp[i][j] = dp[i-1][j] + dp[i][j-1]`。 + +![](http://img.dabin-coder.cn/image/uniquePaths2.png) + +3、**初始状态**。对于第一行 `dp[0][j]`和第一列 `dp[i][0]`,由于都在边界,只有一个方向可以走,所以只能为 1。 + +4、**确定遍历顺序**。`dp[i][j]`是从其上边和左边推导而来,所以按照从左到右,从上到下的顺序来遍历。 + +# 代码实现 + +使用二维数组`dp[][]`保存中间状态,时间复杂度和空间复杂度都是`O(m*n)`。 + +```java +public int uniquePaths(int m, int n) { + int[][] dp = new int[m][n]; + //初始化 + for (int i = 0; i < n; i++) { + dp[0][i] = 1; + } + //初始化 + for (int i = 0; i < m; i++) { + dp[i][0] = 1; + } + for (int i = 1; i < m; i++) { + for (int j = 1; j < n; j++) { + //状态方程 + dp[i][j] = dp[i - 1][j] + dp[i][j - 1]; + } + } + return dp[m - 1][n - 1]; +} +``` + +**优化1**:根据状态转移方程`dp[i][j] = dp[i-1][j] + dp[i][j-1]`可知,只需要保存当前行与上一行的数据即可,空间复杂度可以优化为`O(2n)`,具体代码如下: + +```java +public int uniquePaths(int m, int n) { + int[] preRow = new int[n]; + int[] curRow = new int[n]; + //初始化 + for (int i = 0; i < n; i++) { + preRow[i] = 1; + curRow[i] = 1; + } + for (int i = 1; i < m; i++){ + for (int j = 1; j < n; j++){ + curRow[j] = curRow[j-1] + preRow[j]; + } + preRow = curRow.clone(); + } + return curRow[n-1]; +} +``` + +**优化2**:上述代码还可以继续优化,对于`curRow[j] = curRow[j-1] + preRow[j]`,在未赋值之前`curRow[j]`就是**当前行第`i`行的上一行第`j`列的值**(这里可能不太好理解,小伙伴们好好思考一下),也就是说未赋值之前`curRow[j]`与`preRow[j]`相等,因此`curRow[j] = curRow[j-1] + preRow[j]`可以写成`curRow[j] += curRow[j-1]`,优化1代码中的`preRow`数组可以不用,只需要`curRow`数组即可,代码如下: + +```java +public int uniquePaths(int m, int n) { + int[] curRow = new int[n]; + //初始化 + for (int i = 0; i < n; i++) { + curRow[i] = 1; + } + for (int i = 1; i < m; i++){ + for (int j = 1; j < n; j++){ + curRow[j] += curRow[j-1]; + } + } + return curRow[n-1]; +} +``` + + + +这道腾讯面试题,算是动态规划里面比较简单的题目,虽然不难,但是在面试时氛围比较紧张的情况下,想要一次性bug free做出来,并且做到最优解,还是有点难度的。 + +希望大家阅读后有所收获,如果小伙伴们有更好的解法,**欢迎扫描下方二维码加我微信或者加我微信号『 i_am_dabin 』**,一起探讨~ + + + + + diff --git "a/\346\265\267\351\207\217\346\225\260\346\215\256\345\234\272\346\231\257\351\242\230/\347\273\237\350\256\241\344\270\215\345\220\214\347\224\265\350\257\235\345\217\267\347\240\201\347\232\204\344\270\252\346\225\260.md" b/mass-data/count-phone-num.md similarity index 95% rename from "\346\265\267\351\207\217\346\225\260\346\215\256\345\234\272\346\231\257\351\242\230/\347\273\237\350\256\241\344\270\215\345\220\214\347\224\265\350\257\235\345\217\267\347\240\201\347\232\204\344\270\252\346\225\260.md" rename to mass-data/count-phone-num.md index c2ac733..7fef9ce 100644 --- "a/\346\265\267\351\207\217\346\225\260\346\215\256\345\234\272\346\231\257\351\242\230/\347\273\237\350\256\241\344\270\215\345\220\214\347\224\265\350\257\235\345\217\267\347\240\201\347\232\204\344\270\252\346\225\260.md" +++ b/mass-data/count-phone-num.md @@ -1,6 +1,4 @@ -大家好,我是大彬~ - -海量数据题目是面试经常会考的题型,今天给大家分享一道百度二面的题目。 +题目来自百度二面。 ## 题目描述 diff --git "a/\346\265\267\351\207\217\346\225\260\346\215\256\345\234\272\346\231\257\351\242\230/\345\246\202\344\275\225\344\273\216\346\265\267\351\207\217\346\225\260\346\215\256\346\211\276\345\207\272\351\253\230\351\242\221\350\257\215.md" b/mass-data/find-hign-frequency-word.md similarity index 98% rename from "\346\265\267\351\207\217\346\225\260\346\215\256\345\234\272\346\231\257\351\242\230/\345\246\202\344\275\225\344\273\216\346\265\267\351\207\217\346\225\260\346\215\256\346\211\276\345\207\272\351\253\230\351\242\221\350\257\215.md" rename to mass-data/find-hign-frequency-word.md index 70a08a9..6ced48e 100644 --- "a/\346\265\267\351\207\217\346\225\260\346\215\256\345\234\272\346\231\257\351\242\230/\345\246\202\344\275\225\344\273\216\346\265\267\351\207\217\346\225\260\346\215\256\346\211\276\345\207\272\351\253\230\351\242\221\350\257\215.md" +++ b/mass-data/find-hign-frequency-word.md @@ -1,7 +1,3 @@ -大家好,我是大彬~ - -今天分享一道面试常考的海量数据场景题。 - ## 题目描述 假如有一个**1G**大小的文件,文件里每一行是一个词,每个词的大小不超过**16byte**,要求返回出现频率最高的100个词。内存大小限制是**10M** diff --git "a/message-queue/Kafka\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" "b/message-queue/Kafka\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" new file mode 100644 index 0000000..9d81ae5 --- /dev/null +++ "b/message-queue/Kafka\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" @@ -0,0 +1,206 @@ +### **1、如何获取 topic 主题的列表** + +bin/kafka-topics.sh --list --zookeeper localhost:2181 + +### **2、生产者和消费者的命令行是什么?** + +**生产者在主题上发布消息:** + +bin/kafka-console-producer.sh --broker-list 192.168.43.49:9092 --topicHello-Kafka + +注意这里的 IP 是 server.properties 中的 listeners 的配置。接下来每个新行就是输入一条新消息。 + +**消费者接受消息:** + +bin/kafka-console-consumer.sh --zookeeper localhost:2181 --topicHello-Kafka --from-beginning + +### **3、consumer 是推还是拉?** + +customer 应该从 brokes 拉取消息还是 brokers 将消息推送到 consumer,也就是 pull 还 push。在这方面,Kafka 遵循了一种大部分消息系统共同的传统的设计:producer 将消息推送到 broker,consumer 从broker 拉取消息。 + +push 模式,将消息推送到下游的 consumer。这样做有好处也有坏处:由 broker 决定消息推送的速率,对于不同消费速率的 consumer 就不太好处理了。消息系统都致力于让 consumer 以最大的速率最快速的消费消息,但不幸的是,push 模式下,当 broker 推送的速率远大于 consumer 消费的速率时,consumer 恐怕就要崩溃了。最终 Kafka 还是选取了传统的 pull 模式。 + +### **4、 kafka 维护消费状态跟踪的方法有什么?** + +大部分消息系统在 broker 端的维护消息被消费的记录:一个消息被分发到consumer 后 broker 就马上进行标记或者等待 customer 的通知后进行标记。这样也可以在消息在消费后立马就删除以减少空间占用。 + +那这样会不会有什么问题呢? + +如果一条消息发送出去之后就立即被标记为消费过的,旦 consumer 处理消息时失败了(比如程序崩溃)消息就丢失了。为了解决这个问题,很多消息系统提供了另外一个个功能:当消息被发送出去之后仅仅被标记为已发送状态,当接到 consumer 已经消费成功的通知后才标记为已被消费的状态。这虽然解决了消息丢失的问题,但产生了新问题, + +首先,如果 consumer处理消息成功了但是向 broker 发送响应时失败了,这条消息将被消费两次。第二个问题时,broker 必须维护每条消息的状态,并且每次都要先锁住消息然后更改状态然后释放锁。这样麻烦又来了,且不说要维护大量的状态数据,比如如果消息发送出去但没有收到消费成功的通知,这条消息将一直处于被锁定的状态。 + +Kafka 采用了不同的策略。Topic 被分成了若干分区,每个分区在同一时间只被一个 consumer 消费。这意味着每个分区被消费的消息在日志中的位置仅仅是一个简单的整数:offset。这样就很容易标记每个分区消费状态就很容易了,仅仅需要一个整数而已。这样消费状态的跟踪就很简单了。 + +这带来了另外一个好处:consumer 可以把 offset 调成一个较老的值,去重新消费老的消息。这对传统的消息系统来说看起来有些不可思议,但确实是非常有用的,谁规定了一条消息只能被消费一次呢? + +### **5、讲一下主从同步** + +Kafka允许topic的分区拥有若干副本,这个数量是可以配置的,你可以为每个topic配置副本的数量。Kafka会自动在每个个副本上备份数据,所以当一个节点down掉时数据依然是可用的。 + +Kafka的副本功能不是必须的,你可以配置只有一个副本,这样其实就相当于只有一份数据。 + +### **6、为什么需要消息系统,mysql 不能满足需求吗?** + +**(1)解耦:** + +允许你独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束。 + +**(2)冗余:** + +消息队列把数据进行持久化直到它们已经被完全处理,通过这一方式规避了数据丢失风险。许多消息队列所采用的”插入-获取-删除”范式中,在把一个消息从队列中删除之前,需要你的处理系统明确的指出该消息已经被处理完毕,从而确保你的数据被安全的保存直到你使用完毕。 + +**(3)扩展性:** + +因为消息队列解耦了你的处理过程,所以增大消息入队和处理的频率是很容易的,只要另外增加处理过程即可。 + +**(4)灵活性 & 峰值处理能力:** + +在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量并不常见。如果为以能处理这类峰值访问为标准来投入资源随时待命无疑是巨大的浪费。使用消息队列能够使关键组件顶住突发的访问压力,而不会因为突发的超负荷的请求而完全崩溃。 + +**(5)可恢复性:** + +系统的一部分组件失效时,不会影响到整个系统。消息队列降低了进程间的耦合度,所以即使一个处理消息的进程挂掉,加入队列中的消息仍然可以在系统恢复后被处理。 + +**(6)顺序保证:** + +在大多使用场景下,数据处理的顺序都很重要。大部分消息队列本来就是排序的,并且能保证数据会按照特定的顺序来处理。(Kafka 保证一个 Partition 内的消息的有序性) + +**(7)缓冲:** + +有助于控制和优化数据流经过系统的速度,解决生产消息和消费消息的处理速度不一致的情况。 + +**(8)异步通信:** + +很多时候,用户不想也不需要立即处理消息。消息队列提供了异步处理机制,允许用户把一个消息放入队列,但并不立即处理它。想向队列中放入多少消息就放多少,然后在需要的时候再去处理它们。 + +### **7、Zookeeper 对于 Kafka 的作用是什么?** + +Zookeeper 是一个开放源码的、高性能的协调服务,它用于 Kafka 的分布式应用。 + +Zookeeper 主要用于在集群中不同节点之间进行通信 + +在 Kafka 中,它被用于提交偏移量,因此如果节点在任何情况下都失败了,它都可以从之前提交的偏移量中获取除此之外,它还执行其他活动,如: leader 检测、分布式同步、配置管理、识别新节点何时离开或连接、集群、节点实时状态等等。 + +**8、数据传输的事务定义有哪三种?** + +和 MQTT 的事务定义一样都是 3 种。 + +(1)最多一次: 消息不会被重复发送,最多被传输一次,但也有可能一次不传输 + +(2)最少一次: 消息不会被漏发送,最少被传输一次,但也有可能被重复传输. + +(3)精确的一次(Exactly once): 不会漏传输也不会重复传输,每个消息都传输被一次而且仅仅被传输一次,这是大家所期望的 + +### **9、Kafka 判断一个节点是否还活着有那两个条件?** + +(1)节点必须可以维护和 ZooKeeper 的连接,Zookeeper 通过心跳机制检查每个节点的连接 + +(2)如果节点是个 follower,他必须能及时的同步 leader 的写操作,延时不能太久 + +### **10、Kafka 与传统 MQ 消息系统之间有什么区别** + +(1).Kafka 持久化日志,这些日志可以被重复读取和无限期保留 + +(2).Kafka 是一个分布式系统:它以集群的方式运行,可以灵活伸缩,在内部通过复制数据提升容错能力和高可用性 + +(3).Kafka 支持实时的流式处理 + +### **12、消费者故障,出现活锁问题如何解决?** + +出现“活锁”的情况,是它持续的发送心跳,但是没有处理。为了预防消费者在这种情况下一直持有分区,我们使用 max.poll.interval.ms 活跃检测机制。 在此基础上,如果你调用的 poll 的频率大于最大间隔,则客户端将主动地离开组,以便其他消费者接管该分区。 发生这种情况时,你会看到 offset 提交失败。这是一种安全机制,保障只有活动成员能够提交 offset。所以要留在组中,你必须持续调用 poll。 + +### **13、如何控制消费的位置** + +kafka 使用 seek(TopicPartition, long)指定新的消费位置。用于查找服务器保留的最早和最新的 offset 的特殊的方法也可用(seekToBeginning(Collection) 和seekToEnd(Collection)) + +### **14、kafka 的高可用机制是什么?** + +多副本冗余的高可用机制 + +producer、broker 和 consumer 都会拥有多个 + +分区选举机制 、 消息确认机制 + +### **15、kafka 如何减少数据丢失** + +Kafka到底会不会丢数据(data loss)? 通常不会,但有些情况下的确有可能会发生。下面的参数配置及Best practice列表可以较好地保证数据的持久性(当然是trade-off,牺牲了吞吐量)。 + +• block.on.buffer.full = true + +• acks = all + +• retries = MAX_VALUE + +• max.in.flight.requests.per.connection = 1 + +• 使用KafkaProducer.send(record, callback) + +• callback逻辑中显式关闭producer:close(0) + +• unclean.leader.election.enable=false + +• replication.factor = 3 + +• min.insync.replicas = 2 + +• replication.factor > min.insync.replicas + +• enable.auto.commit=false + +• 消息处理完成之后再提交位移 + +### **16、kafka 如何不消费重复数据?比如扣款,我们不能重复的扣。** + +你拿个数据要写库,你先根据主键查一下,如果这数据都有了,你就别插入了,update 一下。 + +比如你是写 Redis,那没问题了,反正每次都是 set,天然幂等性。 + +比如你不是上面两个场景,那做的稍微复杂一点,你需要让生产者发送每条数据的时候,里面加一个全局唯一的 id,类似订单 id 之类的东西,然后你这里消费到了之后,先根据这个 id 去比如 Redis 里查一下,之前消费过吗?如果没有消费过,你就处理,然后这个 id 写 Redis。如果消费过了,那你就别处理了,保证别重复处理相同的消息即可。 + +比如基于数据库的唯一键来保证重复数据不会重复插入多条。因为有唯一键约束了,重复数据插入只会报错,不会导致数据库中出现脏数据。 + +解决: + +1.幂等操作,重复消费不会产生问题 +2.事务 + +对每个partitionID,产生一个uniqueID,.只有这个partition的数据被完全消费,才算成功,否则失败回滚。下次若重复执行,就skip + +### **17、谈谈 Kafka 吞吐量为何如此高?** + +多分区、batch send、kafka Reator 网络模型、pagecache、sendfile 零拷贝、数据压缩 + +1>顺序读写 + +![img](https://pic2.zhimg.com/80/v2-12ae2ae2e8c7606b7cb028088ae87f2d_720w.jpg) + +上图就展示了Kafka是如何写入数据的, 每一个Partition其实都是一个文件 ,收到消息后Kafka会把数据插入到文件末尾(虚框部分)。 + +这种方法有一个缺陷—— 没有办法删除数据 ,所以Kafka是不会删除数据的,它会把所有的数据都保留下来,每个消费者(Consumer)对每个Topic都有一个offset用来表示 读取到了第几条数据 。 + +2>Page Cache + +为了优化读写性能,Kafka利用了操作系统本身的Page Cache,就是利用操作系统自身的内存而不是JVM空间内存。 + +3>零拷贝 + +零拷贝就是一种避免 CPU 将数据从一块存储拷贝到另外一块存储的技术。 + +linux操作系统 “零拷贝” 机制使用了sendfile方法, 允许操作系统将数据从Page Cache 直接发送到网络,只需要最后一步的copy操作将数据复制到 NIC 缓冲区, 这样避免重新复制数据 。示意图如下: + +![img](https://pic2.zhimg.com/80/v2-4802ab4f3e588e5e36413a700bee9dd1_720w.jpg) + +通过这种 “零拷贝” 的机制,Page Cache 结合 sendfile 方法,Kafka消费端的性能也大幅提升。这也是为什么有时候消费端在不断消费数据时,我们并没有看到磁盘io比较高,此刻正是操作系统缓存在提供数据。 + +4>分区分段+索引 + +Kafka的message是按topic分类存储的,topic中的数据又是按照一个一个的partition即分区存储到不同broker节点。每个partition对应了操作系统上的一个文件夹,partition实际上又是按照segment分段存储的。这也非常符合分布式系统分区分桶的设计思想。 + +5>批量读写 + +Kafka数据读写也是批量的而不是单条的。 + +6>批量压缩 + +如果每个消息都压缩,但是压缩率相对很低,所以Kafka使用了批量压缩,即将多个消息一起压缩而不是单个消息压缩 \ No newline at end of file diff --git a/message-queue/kafka.md b/message-queue/kafka.md new file mode 100644 index 0000000..9d81ae5 --- /dev/null +++ b/message-queue/kafka.md @@ -0,0 +1,206 @@ +### **1、如何获取 topic 主题的列表** + +bin/kafka-topics.sh --list --zookeeper localhost:2181 + +### **2、生产者和消费者的命令行是什么?** + +**生产者在主题上发布消息:** + +bin/kafka-console-producer.sh --broker-list 192.168.43.49:9092 --topicHello-Kafka + +注意这里的 IP 是 server.properties 中的 listeners 的配置。接下来每个新行就是输入一条新消息。 + +**消费者接受消息:** + +bin/kafka-console-consumer.sh --zookeeper localhost:2181 --topicHello-Kafka --from-beginning + +### **3、consumer 是推还是拉?** + +customer 应该从 brokes 拉取消息还是 brokers 将消息推送到 consumer,也就是 pull 还 push。在这方面,Kafka 遵循了一种大部分消息系统共同的传统的设计:producer 将消息推送到 broker,consumer 从broker 拉取消息。 + +push 模式,将消息推送到下游的 consumer。这样做有好处也有坏处:由 broker 决定消息推送的速率,对于不同消费速率的 consumer 就不太好处理了。消息系统都致力于让 consumer 以最大的速率最快速的消费消息,但不幸的是,push 模式下,当 broker 推送的速率远大于 consumer 消费的速率时,consumer 恐怕就要崩溃了。最终 Kafka 还是选取了传统的 pull 模式。 + +### **4、 kafka 维护消费状态跟踪的方法有什么?** + +大部分消息系统在 broker 端的维护消息被消费的记录:一个消息被分发到consumer 后 broker 就马上进行标记或者等待 customer 的通知后进行标记。这样也可以在消息在消费后立马就删除以减少空间占用。 + +那这样会不会有什么问题呢? + +如果一条消息发送出去之后就立即被标记为消费过的,旦 consumer 处理消息时失败了(比如程序崩溃)消息就丢失了。为了解决这个问题,很多消息系统提供了另外一个个功能:当消息被发送出去之后仅仅被标记为已发送状态,当接到 consumer 已经消费成功的通知后才标记为已被消费的状态。这虽然解决了消息丢失的问题,但产生了新问题, + +首先,如果 consumer处理消息成功了但是向 broker 发送响应时失败了,这条消息将被消费两次。第二个问题时,broker 必须维护每条消息的状态,并且每次都要先锁住消息然后更改状态然后释放锁。这样麻烦又来了,且不说要维护大量的状态数据,比如如果消息发送出去但没有收到消费成功的通知,这条消息将一直处于被锁定的状态。 + +Kafka 采用了不同的策略。Topic 被分成了若干分区,每个分区在同一时间只被一个 consumer 消费。这意味着每个分区被消费的消息在日志中的位置仅仅是一个简单的整数:offset。这样就很容易标记每个分区消费状态就很容易了,仅仅需要一个整数而已。这样消费状态的跟踪就很简单了。 + +这带来了另外一个好处:consumer 可以把 offset 调成一个较老的值,去重新消费老的消息。这对传统的消息系统来说看起来有些不可思议,但确实是非常有用的,谁规定了一条消息只能被消费一次呢? + +### **5、讲一下主从同步** + +Kafka允许topic的分区拥有若干副本,这个数量是可以配置的,你可以为每个topic配置副本的数量。Kafka会自动在每个个副本上备份数据,所以当一个节点down掉时数据依然是可用的。 + +Kafka的副本功能不是必须的,你可以配置只有一个副本,这样其实就相当于只有一份数据。 + +### **6、为什么需要消息系统,mysql 不能满足需求吗?** + +**(1)解耦:** + +允许你独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束。 + +**(2)冗余:** + +消息队列把数据进行持久化直到它们已经被完全处理,通过这一方式规避了数据丢失风险。许多消息队列所采用的”插入-获取-删除”范式中,在把一个消息从队列中删除之前,需要你的处理系统明确的指出该消息已经被处理完毕,从而确保你的数据被安全的保存直到你使用完毕。 + +**(3)扩展性:** + +因为消息队列解耦了你的处理过程,所以增大消息入队和处理的频率是很容易的,只要另外增加处理过程即可。 + +**(4)灵活性 & 峰值处理能力:** + +在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量并不常见。如果为以能处理这类峰值访问为标准来投入资源随时待命无疑是巨大的浪费。使用消息队列能够使关键组件顶住突发的访问压力,而不会因为突发的超负荷的请求而完全崩溃。 + +**(5)可恢复性:** + +系统的一部分组件失效时,不会影响到整个系统。消息队列降低了进程间的耦合度,所以即使一个处理消息的进程挂掉,加入队列中的消息仍然可以在系统恢复后被处理。 + +**(6)顺序保证:** + +在大多使用场景下,数据处理的顺序都很重要。大部分消息队列本来就是排序的,并且能保证数据会按照特定的顺序来处理。(Kafka 保证一个 Partition 内的消息的有序性) + +**(7)缓冲:** + +有助于控制和优化数据流经过系统的速度,解决生产消息和消费消息的处理速度不一致的情况。 + +**(8)异步通信:** + +很多时候,用户不想也不需要立即处理消息。消息队列提供了异步处理机制,允许用户把一个消息放入队列,但并不立即处理它。想向队列中放入多少消息就放多少,然后在需要的时候再去处理它们。 + +### **7、Zookeeper 对于 Kafka 的作用是什么?** + +Zookeeper 是一个开放源码的、高性能的协调服务,它用于 Kafka 的分布式应用。 + +Zookeeper 主要用于在集群中不同节点之间进行通信 + +在 Kafka 中,它被用于提交偏移量,因此如果节点在任何情况下都失败了,它都可以从之前提交的偏移量中获取除此之外,它还执行其他活动,如: leader 检测、分布式同步、配置管理、识别新节点何时离开或连接、集群、节点实时状态等等。 + +**8、数据传输的事务定义有哪三种?** + +和 MQTT 的事务定义一样都是 3 种。 + +(1)最多一次: 消息不会被重复发送,最多被传输一次,但也有可能一次不传输 + +(2)最少一次: 消息不会被漏发送,最少被传输一次,但也有可能被重复传输. + +(3)精确的一次(Exactly once): 不会漏传输也不会重复传输,每个消息都传输被一次而且仅仅被传输一次,这是大家所期望的 + +### **9、Kafka 判断一个节点是否还活着有那两个条件?** + +(1)节点必须可以维护和 ZooKeeper 的连接,Zookeeper 通过心跳机制检查每个节点的连接 + +(2)如果节点是个 follower,他必须能及时的同步 leader 的写操作,延时不能太久 + +### **10、Kafka 与传统 MQ 消息系统之间有什么区别** + +(1).Kafka 持久化日志,这些日志可以被重复读取和无限期保留 + +(2).Kafka 是一个分布式系统:它以集群的方式运行,可以灵活伸缩,在内部通过复制数据提升容错能力和高可用性 + +(3).Kafka 支持实时的流式处理 + +### **12、消费者故障,出现活锁问题如何解决?** + +出现“活锁”的情况,是它持续的发送心跳,但是没有处理。为了预防消费者在这种情况下一直持有分区,我们使用 max.poll.interval.ms 活跃检测机制。 在此基础上,如果你调用的 poll 的频率大于最大间隔,则客户端将主动地离开组,以便其他消费者接管该分区。 发生这种情况时,你会看到 offset 提交失败。这是一种安全机制,保障只有活动成员能够提交 offset。所以要留在组中,你必须持续调用 poll。 + +### **13、如何控制消费的位置** + +kafka 使用 seek(TopicPartition, long)指定新的消费位置。用于查找服务器保留的最早和最新的 offset 的特殊的方法也可用(seekToBeginning(Collection) 和seekToEnd(Collection)) + +### **14、kafka 的高可用机制是什么?** + +多副本冗余的高可用机制 + +producer、broker 和 consumer 都会拥有多个 + +分区选举机制 、 消息确认机制 + +### **15、kafka 如何减少数据丢失** + +Kafka到底会不会丢数据(data loss)? 通常不会,但有些情况下的确有可能会发生。下面的参数配置及Best practice列表可以较好地保证数据的持久性(当然是trade-off,牺牲了吞吐量)。 + +• block.on.buffer.full = true + +• acks = all + +• retries = MAX_VALUE + +• max.in.flight.requests.per.connection = 1 + +• 使用KafkaProducer.send(record, callback) + +• callback逻辑中显式关闭producer:close(0) + +• unclean.leader.election.enable=false + +• replication.factor = 3 + +• min.insync.replicas = 2 + +• replication.factor > min.insync.replicas + +• enable.auto.commit=false + +• 消息处理完成之后再提交位移 + +### **16、kafka 如何不消费重复数据?比如扣款,我们不能重复的扣。** + +你拿个数据要写库,你先根据主键查一下,如果这数据都有了,你就别插入了,update 一下。 + +比如你是写 Redis,那没问题了,反正每次都是 set,天然幂等性。 + +比如你不是上面两个场景,那做的稍微复杂一点,你需要让生产者发送每条数据的时候,里面加一个全局唯一的 id,类似订单 id 之类的东西,然后你这里消费到了之后,先根据这个 id 去比如 Redis 里查一下,之前消费过吗?如果没有消费过,你就处理,然后这个 id 写 Redis。如果消费过了,那你就别处理了,保证别重复处理相同的消息即可。 + +比如基于数据库的唯一键来保证重复数据不会重复插入多条。因为有唯一键约束了,重复数据插入只会报错,不会导致数据库中出现脏数据。 + +解决: + +1.幂等操作,重复消费不会产生问题 +2.事务 + +对每个partitionID,产生一个uniqueID,.只有这个partition的数据被完全消费,才算成功,否则失败回滚。下次若重复执行,就skip + +### **17、谈谈 Kafka 吞吐量为何如此高?** + +多分区、batch send、kafka Reator 网络模型、pagecache、sendfile 零拷贝、数据压缩 + +1>顺序读写 + +![img](https://pic2.zhimg.com/80/v2-12ae2ae2e8c7606b7cb028088ae87f2d_720w.jpg) + +上图就展示了Kafka是如何写入数据的, 每一个Partition其实都是一个文件 ,收到消息后Kafka会把数据插入到文件末尾(虚框部分)。 + +这种方法有一个缺陷—— 没有办法删除数据 ,所以Kafka是不会删除数据的,它会把所有的数据都保留下来,每个消费者(Consumer)对每个Topic都有一个offset用来表示 读取到了第几条数据 。 + +2>Page Cache + +为了优化读写性能,Kafka利用了操作系统本身的Page Cache,就是利用操作系统自身的内存而不是JVM空间内存。 + +3>零拷贝 + +零拷贝就是一种避免 CPU 将数据从一块存储拷贝到另外一块存储的技术。 + +linux操作系统 “零拷贝” 机制使用了sendfile方法, 允许操作系统将数据从Page Cache 直接发送到网络,只需要最后一步的copy操作将数据复制到 NIC 缓冲区, 这样避免重新复制数据 。示意图如下: + +![img](https://pic2.zhimg.com/80/v2-4802ab4f3e588e5e36413a700bee9dd1_720w.jpg) + +通过这种 “零拷贝” 的机制,Page Cache 结合 sendfile 方法,Kafka消费端的性能也大幅提升。这也是为什么有时候消费端在不断消费数据时,我们并没有看到磁盘io比较高,此刻正是操作系统缓存在提供数据。 + +4>分区分段+索引 + +Kafka的message是按topic分类存储的,topic中的数据又是按照一个一个的partition即分区存储到不同broker节点。每个partition对应了操作系统上的一个文件夹,partition实际上又是按照segment分段存储的。这也非常符合分布式系统分区分桶的设计思想。 + +5>批量读写 + +Kafka数据读写也是批量的而不是单条的。 + +6>批量压缩 + +如果每个消息都压缩,但是压缩率相对很低,所以Kafka使用了批量压缩,即将多个消息一起压缩而不是单个消息压缩 \ No newline at end of file diff --git "a/\346\266\210\346\201\257\351\230\237\345\210\227/\346\266\210\346\201\257\351\230\237\345\210\227\351\235\242\350\257\225\351\242\230.md" b/message-queue/mq.md similarity index 50% rename from "\346\266\210\346\201\257\351\230\237\345\210\227/\346\266\210\346\201\257\351\230\237\345\210\227\351\235\242\350\257\225\351\242\230.md" rename to message-queue/mq.md index 90a7b0b..34a8aee 100644 --- "a/\346\266\210\346\201\257\351\230\237\345\210\227/\346\266\210\346\201\257\351\230\237\345\210\227\351\235\242\350\257\225\351\242\230.md" +++ b/message-queue/mq.md @@ -30,6 +30,22 @@ - RocketMQ 阿里出品,Java 系开源项目,源代码我们可以直接阅读,然后可以定制自己公司的 MQ,并且 RocketMQ 有阿里巴巴的实际业务场景的实战考验。RocketMQ 社区活跃度相对较为一般,不过也还可以,文档相对来说简单一些,然后接口这块不是按照标准 JMS 规范走的有些系统要迁移需要修改大量代码。还有就是阿里出台的技术,你得做好这个技术万一被抛弃,社区黄掉的风险,那如果你们公司有技术实力我觉得用 RocketMQ 挺好的 - Kafka 的特点其实很明显,就是仅仅提供较少的核心功能,但是提供超高的吞吐量,ms 级的延迟,极高的可用性以及可靠性,而且分布式可以任意扩展。同时 kafka 最好是支撑较少的 topic 数量即可,保证其超高吞吐量。kafka 唯一的一点劣势是有可能消息重复消费,那么对数据准确性会造成极其轻微的影响,在大数据领域中以及日志采集中,这点轻微影响可以忽略这个特性天然适合大数据实时计算以及日志收集。 +## 如何保证消息队列的高可用? + +RabbitMQ:**镜像集群模式** + +RabbitMQ 是基于主从做高可用性的,Rabbitmq有三种模式:单机模式、普通集群模式、镜像集群模式。单机模式一般在生产环境中很少用,普通集群模式只是提高了系统的吞吐量,让集群中多个节点来服务某个 Queue 的读写操作。那么真正实现 RabbitMQ 高可用的是镜像集群模式。 + +镜像集群模式跟普通集群模式不一样的是,创建的 Queue,无论元数据还是Queue 里的消息都会存在于多个实例上,然后每次你写消息到 Queue 的时候,都会自动和多个实例的 Queue 进行消息同步。这样设计,好处在于:任何一个机器宕机不影响其他机器的使用。坏处在于:1. 性能开销太大:消息同步所有机器,导致网络带宽压力和消耗很重;2. 扩展性差:如果某个 Queue 负载很重,即便加机器,新增的机器也包含了这个 Queue 的所有数据,并没有办法线性扩展你的 Queue。 + +Kafka:**partition 和 replica 机制** + +Kafka 基本架构是多个 broker 组成,每个 broker 是一个节点。创建一个 topic 可以划分为多个 partition,每个 partition 可以存在于不同的 broker 上,每个 partition 就放一部分数据,这就是天然的分布式消息队列。就是说一个 topic 的数据,是分散放在多个机器上的,每个机器就放一部分数据。 + +Kafka 0.8 以前,是没有 HA 机制的,任何一个 broker 宕机了,它的 partition 就没法写也没法读了,没有什么高可用性可言。 + +Kafka 0.8 以后,提供了 HA 机制,就是 replica 副本机制。每个 partition 的数据都会同步到其他机器上,形成自己的多个 replica 副本。然后所有 replica 会选举一个 leader 出来,生产和消费都跟这个 leader 打交道,然后其他 replica 就是 follower。写的时候,leader 会负责把数据同步到所有 follower 上去,读的时候就直接读 leader 上数据即可。Kafka 会均匀的将一个 partition 的所有 replica 分布在不同的机器上,这样才可以提高容错性。 + ## MQ常用协议 - **AMQP协议** AMQP即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同开发语言等条件的限制。 @@ -59,14 +75,94 @@ ## 如何保证消息的顺序性? -单线程消费保证消息的顺序性;对消息进行编号,消费者根据编号处理消息。 +**RabbitMQ** + +拆分多个 Queue,每个 Queue一个 Consumer;或者就一个 Queue 但是对应一个 Consumer,然后这个 Consumer 内部用内存队列做排队,然后分发给底层不同的 Worker 来处理。 + +**Kafka** +1. 一个 Topic,一个 Partition,一个 Consumer,内部单线程消费,单线程吞吐量太低,一般不会用这个。 + +2. 写 N 个内存 Queue,具有相同 key 的数据都到同一个内存 Queue;然后对于 N 个线程,每个线程分别消费一个内存 Queue 即可,这样就能保证顺序性。 ## 如何避免消息重复消费? 在消息生产时,MQ内部针对每条生产者发送的消息生成一个唯一id,作为去重和幂等的依据(消息投递失败并重传),避免重复的消息进入队列。 在消息消费时,要求消息体中也要有一全局唯一id作为去重和幂等的依据,避免同一条消息被重复消费。 +## 大量消息在 MQ 里长时间积压,该如何解决? + +一般这个时候,只能临时紧急扩容了,具体操作步骤和思路如下: + +1. 先修复 consumer 的问题,确保其恢复消费速度,然后将现有 consumer 都停掉; +2. 新建一个 topic,partition 是原来的 10 倍,临时建立好原先 10 倍的 queue 数量; +3. 然后写一个临时的分发数据的 consumer 程序,这个程序部署上去消费积压的数据,消费之后不做耗时的处理,直接均匀轮询写入临时建立好的 10 倍数量的 queue; +4. 接着临时用 10 倍的机器来部署 consumer,每一批 consumer 消费一个临时 queue 的数据。这种做法相当于是临时将 queue 资源和 consumer 资源扩大 10 倍,以正常的 10 倍速度来消费数据; +5. 等快速消费完积压数据之后,得恢复原先部署的架构,重新用原先的 consumer 机器来消费消息。 + +## MQ 中的消息过期失效了怎么办? + +如果使用的是RabbitMQ的话,RabbtiMQ 是可以设置过期时间的(TTL)。如果消息在 Queue 中积压超过一定的时间就会被 RabbitMQ 给清理掉,这个数据就没了。这时的问题就不是数据会大量积压在 MQ 里,而是大量的数据会直接搞丢。这个情况下,就不是说要增加 Consumer 消费积压的消息,因为实际上没啥积压,而是丢了大量的消息。 + +我们可以采取一个方案,就是批量重导。就是大量积压的时候,直接将数据写到数据库,然后等过了高峰期以后将这批数据一点一点的查出来,然后重新灌入 MQ 里面去,把丢的数据给补回来。 + +## 消息中间件如何做到高可用? + +以Kafka为例。 + + Kafka 的基础集群架构,由多个`broker`组成,每个`broker`都是一个节点。当你创建一个`topic`时,它可以划分为多个`partition`,而每个`partition`放一部分数据,分别存在于不同的 broker 上。也就是说,一个 topic 的数据,是分散放在多个机器上的,每个机器就放一部分数据。 + +每个`partition`放一部分数据,如果对应的broker挂了,那这部分数据是不是就丢失了?那不是保证不了高可用吗? + +Kafka 0.8 之后,提供了复制多副本机制来保证高可用,即每个 partition 的数据都会同步到其它机器上,形成多个副本。然后所有的副本会选举一个 leader 出来,让leader去跟生产和消费者打交道,其他副本都是follower。写数据时,leader 负责把数据同步给所有的follower,读消息时,直接读 leader 上的数据即可。如何保证高可用的?就是假设某个 broker 宕机,这个broker上的partition 在其他机器上都有副本的。如果挂的是leader的broker呢?其他follower会重新选一个leader出来。 + +## 如何保证数据一致性,事务消息如何实现? + +一条普通的MQ消息,从产生到被消费,大概流程如下: + +![](http://img.dabin-coder.cn/image/消息一致性1.png) + +1. 生产者产生消息,发送带MQ服务器 +2. MQ收到消息后,将消息持久化到存储系统。 +3. MQ服务器返回ACk到生产者。 +4. MQ服务器把消息push给消费者 +5. 消费者消费完消息,响应ACK +6. MQ服务器收到ACK,认为消息消费成功,即在存储中删除消息。 + +举个下订单的例子吧。订单系统创建完订单后,再发送消息给下游系统。如果订单创建成功,然后消息没有成功发送出去,下游系统就无法感知这个事情,出导致数据不一致。 +如何保证数据一致性呢?可以使用**事务消息**。一起来看下事务消息是如何实现的吧。 + +![](http://img.dabin-coder.cn/image/消息一致性2.png) + +1. 生产者产生消息,发送一条半事务消息到MQ服务器 +2. MQ收到消息后,将消息持久化到存储系统,这条消息的状态是待发送状态。 +3. MQ服务器返回ACK确认到生产者,此时MQ不会触发消息推送事件 +4. 生产者执行本地事务 +5. 如果本地事务执行成功,即commit执行结果到MQ服务器;如果执行失败,发送rollback。 +6. 如果是正常的commit,MQ服务器更新消息状态为可发送;如果是rollback,即删除消息。 +7. 如果消息状态更新为可发送,则MQ服务器会push消息给消费者。消费者消费完就回ACK。 +8. 如果MQ服务器长时间没有收到生产者的commit或者rollback,它会反查生产者,然后根据查询到的结果执行最终状态。 + +## 如何设计一个消息队列? + +首先是消息队列的整体流程,producer发送消息给broker,broker存储好,broker再发送给consumer消费,consumer回复消费确认等。 + +producer发送消息给broker,broker发消息给consumer消费,那就需要两次RPC了,RPC如何设计呢?可以参考开源框架Dubbo,你可以说说服务发现、序列化协议等等 + +broker考虑如何持久化呢,是放文件系统还是数据库呢,会不会消息堆积呢,消息堆积如何处理呢。 + +消费关系如何保存呢? 点对点还是广播方式呢?广播关系又是如何维护呢?zk还是config server + +消息可靠性如何保证呢?如果消息重复了,如何幂等处理呢? + +消息队列的高可用如何设计呢? 可以参考Kafka的高可用保障机制。多副本 -> leader & follower -> broker 挂了重新选举 leader 即可对外服务。 + +消息事务特性,与本地业务同个事务,本地消息落库;消息投递到服务端,本地才删除;定时任务扫描本地消息库,补偿发送。 + +MQ得伸缩性和可扩展性,如果消息积压或者资源不够时,如何支持快速扩容,提高吞吐?可以参照一下 Kafka 的设计理念,broker -> topic -> partition,每个 partition 放一个机器,就存一部分数据。如果现在资源不够了,简单啊,给 topic 增加 partition,然后做数据迁移,增加机器,不就可以存放更多数据,提供更高的吞吐量了吗。 + +[参考链接](https://juejin.cn/post/7088909199475736612#heading-18) + ![](http://img.dabin-coder.cn/image/20220612101342.png) \ No newline at end of file diff --git "a/\346\266\210\346\201\257\351\230\237\345\210\227/RabbitMQ.md" b/message-queue/rabbitmq.md similarity index 87% rename from "\346\266\210\346\201\257\351\230\237\345\210\227/RabbitMQ.md" rename to message-queue/rabbitmq.md index c89865f..0076d62 100644 --- "a/\346\266\210\346\201\257\351\230\237\345\210\227/RabbitMQ.md" +++ b/message-queue/rabbitmq.md @@ -1,41 +1,10 @@ - - - - -- [简介](#%E7%AE%80%E4%BB%8B) - - [基本概念](#%E5%9F%BA%E6%9C%AC%E6%A6%82%E5%BF%B5) - - [什么时候使用MQ](#%E4%BB%80%E4%B9%88%E6%97%B6%E5%80%99%E4%BD%BF%E7%94%A8mq) - - [优缺点](#%E4%BC%98%E7%BC%BA%E7%82%B9) -- [Exchange 类型](#exchange-%E7%B1%BB%E5%9E%8B) - - [direct](#direct) - - [fanout](#fanout) - - [topic](#topic) - - [headers](#headers) -- [消息丢失](#%E6%B6%88%E6%81%AF%E4%B8%A2%E5%A4%B1) - - [生产者确认机制](#%E7%94%9F%E4%BA%A7%E8%80%85%E7%A1%AE%E8%AE%A4%E6%9C%BA%E5%88%B6) - - [路由不可达消息](#%E8%B7%AF%E7%94%B1%E4%B8%8D%E5%8F%AF%E8%BE%BE%E6%B6%88%E6%81%AF) - - [Return消息机制](#return%E6%B6%88%E6%81%AF%E6%9C%BA%E5%88%B6) - - [备份交换机](#%E5%A4%87%E4%BB%BD%E4%BA%A4%E6%8D%A2%E6%9C%BA) - - [消费者手动消息确认](#%E6%B6%88%E8%B4%B9%E8%80%85%E6%89%8B%E5%8A%A8%E6%B6%88%E6%81%AF%E7%A1%AE%E8%AE%A4) - - [持久化](#%E6%8C%81%E4%B9%85%E5%8C%96) - - [镜像队列](#%E9%95%9C%E5%83%8F%E9%98%9F%E5%88%97) -- [重复消费](#%E9%87%8D%E5%A4%8D%E6%B6%88%E8%B4%B9) -- [消费端限流](#%E6%B6%88%E8%B4%B9%E7%AB%AF%E9%99%90%E6%B5%81) -- [死信队列](#%E6%AD%BB%E4%BF%A1%E9%98%9F%E5%88%97) -- [其他](#%E5%85%B6%E4%BB%96) - - [pull模式](#pull%E6%A8%A1%E5%BC%8F) - - [消息过期时间](#%E6%B6%88%E6%81%AF%E8%BF%87%E6%9C%9F%E6%97%B6%E9%97%B4) -- [参考链接](#%E5%8F%82%E8%80%83%E9%93%BE%E6%8E%A5) - - - -# 简介 +## 什么是RabbitMQ? RabbitMQ是一个由erlang开发的消息队列。消息队列用于应用间的异步协作。 ![](http://img.dabin-coder.cn/image/rabbitmq.png) -## 基本概念 +## RabbitMQ的组件 Message:由消息头和消息体组成。消息体是不透明的,而消息头则由一系列的可选属性组成,这些属性包括routing-key、priority、delivery-mode(是否持久性存储)等。 @@ -57,15 +26,23 @@ Broker:消息队列服务器实体。 以常见的订单系统为例,用户点击下单按钮之后的业务逻辑可能包括:扣减库存、生成相应单据、发短信通知。这种场景下就可以用 MQ 。将短信通知放到 MQ 异步执行,在下单的主流程(比如扣减库存、生成相应单据)完成之后发送一条消息到 MQ, 让主流程快速完结,而由另外的线程消费MQ的消息。 -## 优缺点 +## RabbitMQ的优缺点 缺点:使用erlang实现,不利于二次开发和维护;性能较kafka差,持久化消息和ACK确认的情况下生产和消费消息单机吞吐量大约在1-2万左右,kafka单机吞吐量在十万级别。 优点:有管理界面,方便使用;可靠性高;功能丰富,支持消息持久化、消息确认机制、多种消息分发机制。 +## RabbitMQ 有哪些重要的角色? +RabbitMQ 中重要的角色有:生产者、消费者和代理。 -# Exchange 类型 +1. 生产者:消息的创建者,负责创建和推送数据到消息服务器; + +2. 消费者:消息的接收方,用于处理数据和确认消息; + +3. 代理:就是 RabbitMQ 本身,用于扮演“快递”的角色,本身不生产消息,只是扮演“快递”的角色。 + +## Exchange 类型 Exchange分发消息时根据类型的不同分发策略不同,目前共四种类型:direct、fanout、topic、headers 。headers 模式根据消息的headers进行路由,此外 headers 交换器和 direct 交换器完全一致,但性能差很多。 @@ -78,35 +55,35 @@ Exchange规则。 | topic | 模糊匹配 | | headers | Exchange不依赖于routing key与binding key的匹配规则来路由消息,而是根据发送的消息内容中的header属性进行匹配。 | -## direct +**direct** direct交换机会将消息路由到binding key 和 routing key完全匹配的队列中。它是完全匹配、单播的模式。 ![](http://img.dabin-coder.cn/image/rabbitmq-direct.png) -## fanout +**fanout** 所有发到 fanout 类型交换机的消息都会路由到所有与该交换机绑定的队列上去。fanout 类型转发消息是最快的。 ![](http://img.dabin-coder.cn/image/rabbitmq-fanout.png) -## topic +**topic** -topic交换机使用routing key和binding key进行模糊匹配,匹配成功则将消息发送到相应的队列。routing key和binding key都是句点号“. ”分隔的字符串,binding key中可以存在两种特殊字符“*”与“#”,用于做模糊匹配,其中“\*”用于匹配一个单词,“#”用于匹配多个单词。 +topic交换机使用routing key和binding key进行模糊匹配,匹配成功则将消息发送到相应的队列。routing key和binding key都是句点号“. ”分隔的字符串,binding key中可以存在两种特殊字符“*”与“##”,用于做模糊匹配,其中“\*”用于匹配一个单词,“##”用于匹配多个单词。 ![](http://img.dabin-coder.cn/image/rabbitmq-topic.png) -## headers +**headers** headers交换机是根据发送的消息内容中的headers属性进行路由的。在绑定Queue与Exchange时指定一组键值对;当消息发送到Exchange时,RabbitMQ会取到该消息的headers(也是一个键值对的形式),对比其中的键值对是否完全匹配Queue与Exchange绑定时指定的键值对;如果完全匹配则消息会路由到该Queue,否则不会路由到该Queue。 -# 消息丢失 +## 消息丢失 消息丢失场景:生产者生产消息到RabbitMQ Server消息丢失、RabbitMQ Server存储的消息丢失和RabbitMQ Server到消费者消息丢失。 消息丢失从三个方面来解决:生产者确认机制、消费者手动确认消息和持久化。 -## 生产者确认机制 +### 生产者确认机制 生产者发送消息到队列,无法确保发送的消息成功的到达server。 @@ -120,7 +97,7 @@ headers交换机是根据发送的消息内容中的headers属性进行路由的 ```yaml spring: rabbitmq: - #开启 confirm 确认机制 + ##开启 confirm 确认机制 publisher-confirms: true ``` @@ -139,20 +116,20 @@ final RabbitTemplate.ConfirmCallback confirmCallback = (CorrelationData correlat rabbitTemplate.setConfirmCallback(confirmCallback); ``` -## 路由不可达消息 +### 路由不可达消息 生产者确认机制只确保消息正确到达交换机,对于从交换机路由到Queue失败的消息,会被丢弃掉,导致消息丢失。 对于不可路由的消息,有两种处理方式:Return消息机制和备份交换机。 -### Return消息机制 +**Return消息机制** Return消息机制提供了回调函数 ReturnCallback,当消息从交换机路由到Queue失败才会回调这个方法。需要将`mandatory` 设置为 `true` ,才能监听到路由不可达的消息。 ```yaml spring: rabbitmq: - #触发ReturnCallback必须设置mandatory=true, 否则Exchange没有找到Queue就会丢弃掉消息, 而不会触发ReturnCallback + ##触发ReturnCallback必须设置mandatory=true, 否则Exchange没有找到Queue就会丢弃掉消息, 而不会触发ReturnCallback template.mandatory: true ``` @@ -167,11 +144,11 @@ rabbitTemplate.setReturnCallback(returnCallback); 当消息从交换机路由到Queue失败时,会返回 `return exchange: , routingKey: MAIL, replyCode: 312, replyText: NO_ROUTE`。 -### 备份交换机 +**备份交换机** 备份交换机alternate-exchange 是一个普通的exchange,当你发送消息到对应的exchange时,没有匹配到queue,就会自动转移到备份交换机对应的queue,这样消息就不会丢失。 -## 消费者手动消息确认 +### 消费者手动消息确认 有可能消费者收到消息还没来得及处理MQ服务就宕机了,导致消息丢失。因为消息者默认采用自动ack,一旦消费者收到消息后会通知MQ Server这条消息已经处理好了,MQ 就会移除这条消息。 @@ -180,7 +157,7 @@ rabbitTemplate.setReturnCallback(returnCallback); 消费者设置手动ack: ```java -#设置消费端手动 ack +##设置消费端手动 ack spring.rabbitmq.listener.simple.acknowledge-mode=manual ``` @@ -204,7 +181,7 @@ spring.rabbitmq.listener.simple.acknowledge-mode=manual 当消息消费失败时,消费端给broker回复nack,如果consumer设置了requeue为false,则nack后broker会删除消息或者进入死信队列,否则消息会重新入队。 -## 持久化 +### 持久化 如果RabbitMQ服务异常导致重启,将会导致消息丢失。RabbitMQ提供了持久化的机制,将内存中的消息持久化到硬盘上,即使重启RabbitMQ,消息也不会丢失。 @@ -216,15 +193,13 @@ spring.rabbitmq.listener.simple.acknowledge-mode=manual 当发布一条消息到交换机上时,Rabbit会先把消息写入持久化日志,然后才向生产者发送响应。一旦从队列中消费了一条消息的话并且做了确认,RabbitMQ会在持久化日志中移除这条消息。在消费消息前,如果RabbitMQ重启的话,服务器会自动重建交换机和队列,加载持久化日志中的消息到相应的队列或者交换机上,保证消息不会丢失。 -## 镜像队列 +### 镜像队列 当MQ发生故障时,会导致服务不可用。引入RabbitMQ的镜像队列机制,将queue镜像到集群中其他的节点之上。如果集群中的一个节点失效了,能自动地切换到镜像中的另一个节点以保证服务的可用性。 通常每一个镜像队列都包含一个master和多个slave,分别对应于不同的节点。发送到镜像队列的所有消息总是被直接发送到master和所有的slave之上。除了publish外所有动作都只会向master发送,然后由master将命令执行的结果广播给slave,从镜像队列中的消费操作实际上是在master上执行的。 - - -# 重复消费 +## 消息重复消费怎么处理? 消息重复的原因有两个:1.生产时消息重复,2.消费时消息重复。 @@ -238,9 +213,7 @@ spring.rabbitmq.listener.simple.acknowledge-mode=manual 2. 如果不存在,则正常消费,消费完毕后写入redis/db 3. 如果存在,则证明消息被消费过,直接丢弃 - - -# 消费端限流 +## 消费端怎么进行限流? 当 RabbitMQ 服务器积压大量消息时,队列里的消息会大量涌入消费端,可能导致消费端服务器奔溃。这种情况下需要对消费端限流。 @@ -249,7 +222,7 @@ Spring RabbitMQ 提供参数 prefetch 可以设置单个请求处理的消息个 开启消费端限流: ```properties -#在单个请求中处理的消息个数,unack的最大数量 +##在单个请求中处理的消息个数,unack的最大数量 spring.rabbitmq.listener.simple.prefetch=2 ``` @@ -261,7 +234,7 @@ spring.rabbitmq.listener.simple.prefetch=2 void basicQos(int prefetchSize, int prefetchCount, boolean global) throws IOException; ``` -# 死信队列 +## 什么是死信队列? 消费失败的消息存放的队列。 @@ -383,9 +356,7 @@ public class DeadListener { 当普通队列中有死信时,RabbitMQ 就会自动的将这个消息重新发布到设置的死信交换机去,然后被路由到死信队列。可以监听死信队列中的消息做相应的处理。 -# 其他 - -## pull模式 +## 说说pull模式 pull模式主要是通过channel.basicGet方法来获取消息,示例代码如下: @@ -395,7 +366,7 @@ System.out.println(new String(response.getBody())); channel.basicAck(response.getEnvelope().getDeliveryTag(),false); ``` -## 消息过期时间 +## 怎么设置消息的过期时间? 在生产端发送消息的时候可以给消息设置过期时间,单位为毫秒(ms) @@ -406,7 +377,7 @@ msg.getMessageProperties().setExpiration("3000"); 也可以在创建队列的时候指定队列的ttl,从消息入队列开始计算,超过该时间的消息将会被移除。 -# 参考链接 +## 参考链接 [RabbitMQ基础](https://www.jianshu.com/p/79ca08116d57) diff --git a/other/site-diary.md b/other/site-diary.md new file mode 100644 index 0000000..4acc87b --- /dev/null +++ b/other/site-diary.md @@ -0,0 +1,37 @@ +大彬的小破站是22年7月份开始搭建的,收录了我平时梳理的一些文章,网站内容还在不断更新中~ + +在此记录下小破站的迭代更新记录。 + +- 2022.07.31,应粉丝要求,增加暗黑模式~很贴心有木有! + + ![](http://img.dabin-coder.cn/image/image-20220801004603404.png) + + ![](http://img.dabin-coder.cn/image/image-20220801004446607.png) + +- 2022.07.30,SEO优化,小破站被谷歌、百度等搜索引擎收录啦! + + ![](http://img.dabin-coder.cn/image/image-20220801003633682.png) + +- 2022.07.29,增加工具-Nginx面试题 + +- 2022.07.26,完善计算机网络面试题部分 + +- 2022.07.24,增加编程内容-设计模式,工具-Git/Maven详解 + +- 2022.07.23,整理力扣算法题目 + +- 2022.07.20,修复小伙伴通过微信、评论留言等渠道提出的bug + +- 2022.07.18,增加ElasticSearch面试题 + +- 2022.07.16,整理数据库相关面试题 + +- 2022.07.13,增加**分布式**相关内容,包括分布式事务、分布式锁、微服务等 + +- 2022.07.10,增加学习资源,包括面试手册、算法手册 + +- 2022.07.08,整理**计算机基础**内容,包括操作系统、计算机网络、数据结构与算法等 + +- 2022.07.06,7月6号正式上线,**首日上线PV过万**! + +- 2022.07.01-2022.07.06,网站搭建,整理大彬在公众号、知乎等平台发表的文章 \ No newline at end of file diff --git "a/Redis/Redis\345\205\245\351\227\250\346\214\207\345\215\227\346\200\273\347\273\223.md" b/redis/redis-basic-all.md similarity index 100% rename from "Redis/Redis\345\205\245\351\227\250\346\214\207\345\215\227\346\200\273\347\273\223.md" rename to redis/redis-basic-all.md diff --git a/redis/redis-basic/1-introduce.md b/redis/redis-basic/1-introduce.md new file mode 100644 index 0000000..a1aa20f --- /dev/null +++ b/redis/redis-basic/1-introduce.md @@ -0,0 +1,36 @@ +# 简介 + +Redis是一个高性能的key-value数据库。Redis对数据的操作都是原子性的。 + +## 优缺点 + +优点: + +1. 基于内存操作,内存读写速度快。 +2. Redis是单线程的,避免线程切换开销及多线程的竞争问题。单线程是指在处理网络请求(一个或多个redis客户端连接)的时候只有一个线程来处理,redis运行时不止有一个线程,数据持久化或者向slave同步aof时会另起线程。 +3. 支持多种数据类型,包括String、Hash、List、Set、ZSet等 +4. 支持持久化。Redis支持RDB和AOF两种持久化机制,持久化功能有效地避免数据丢失问题。 +5. redis 采用IO多路复用技术。多路指的是多个socket连接,复用指的是复用一个线程。redis使用单线程来轮询描述符,将数据库的开、关、读、写都转换成了事件。多路复用主要有三种技术:select,poll,epoll。epoll是最新的也是目前最好的多路复用技术。 + +缺点:对join或其他结构化查询的支持就比较差。 + +## io多路复用 + +将用户socket对应的文件描述符(file description)注册进epoll,然后epoll帮你监听哪些socket上有消息到达。当某个socket可读或者可写的时候,它可以给你一个通知。只有当系统通知哪个描述符可读了,才去执行read操作,可以保证每次read都能读到有效数据。这样,多个描述符的I/O操作都能在一个线程内并发交替地顺序完成,这就叫I/O多路复用,这里的复用指的是复用同一个线程。 + +## 应用场景 + +1. 缓存热点数据,缓解数据库的压力。 +2. 利用Redis中原子性的自增操作,可以用使用实现计算器的功能,比如统计用户点赞数、用户访问数等,这类操作如果用MySQL,频繁的读写会带来相当大的压力。 +3. 简单消息队列,不要求高可靠的情况下,可以使用Redis自身的发布/订阅模式或者List来实现一个队列,实现异步操作。 +4. 好友关系,利用集合的一些命令,比如求交集、并集、差集等。可以方便搞定一些共同好友、共同爱好之类的功能。 +5. 限速器,比较典型的使用场景是限制某个用户访问某个API的频率,常用的有抢购时,防止用户疯狂点击带来不必要的压力。 + +## Memcached和Redis的区别 + +1. Redis只使用单核,而Memcached可以使用多核。 +2. MemCached数据结构单一,仅用来缓存数据,而Redis支持更加丰富的数据类型,也可以在服务器端直接对数据进行丰富的操作,这样可以减少网络IO次数和数据体积。 +3. MemCached不支持数据持久化,断电或重启后数据消失。Redis支持数据持久化和数据恢复,允许单点故障。 + + + diff --git a/redis/redis-basic/10-lua.md b/redis/redis-basic/10-lua.md new file mode 100644 index 0000000..f88539f --- /dev/null +++ b/redis/redis-basic/10-lua.md @@ -0,0 +1,70 @@ +# LUA脚本 + +Redis 通过 LUA 脚本创建具有原子性的命令: 当lua脚本命令正在运行的时候,不会有其他脚本或 Redis 命令被执行,实现组合命令的原子操作。 + +在Redis中执行Lua脚本有两种方法:eval和evalsha。 + +eval 命令使用内置的 Lua 解释器,对 Lua 脚本进行求值。 + +``` +//第一个参数是lua脚本,第二个参数是键名参数个数,剩下的是键名参数和附加参数 +> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second +1) "key1" +2) "key2" +3) "first" +4) "second" +``` + +## evalsha + +Redis还提供了evalsha命令来执行Lua脚本。首先要将Lua脚本加载到Redis服务端,得到该脚本的SHA1校验和。Evalsha 命令根据给定的 sha1 校验和,执行缓存在服务器中的脚本。 + +script load命令可以将脚本内容加载到Redis内存中。 + +``` +redis 127.0.0.1:6379> SCRIPT LOAD "return 'hello moto'" +"232fd51614574cf0867b83d384a5e898cfd24e5a" + +redis 127.0.0.1:6379> EVALSHA "232fd51614574cf0867b83d384a5e898cfd24e5a" 0 +"hello moto" +``` + +使用evalsha执行Lua脚本过程如下: + +![](http://img.dabin-coder.cn/image/evalsha.png) + +## lua脚本作用 + +1、Lua脚本在Redis中是原子执行的,执行过程中间不会插入其他命令。 + +2、Lua脚本可以将多条命令一次性打包,有效地减少网络开销。 + +## 应用场景 + +限制接口访问频率。 + +在Redis维护一个接口访问次数的键值对,key是接口名称,value是访问次数。每次访问接口时,会执行以下操作: + +- 通过aop拦截接口的请求,对接口请求进行计数,每次进来一个请求,相应的接口count加1,存入redis。 +- 如果是第一次请求,则会设置count=1,并设置过期时间。因为这里set()和expire()组合操作不是原子操作,所以引入lua脚本,实现原子操作,避免并发访问问题。 +- 如果给定时间范围内超过最大访问次数,则会抛出异常。 + +```java +private String buildLuaScript() { + return "local c" + + "\nc = redis.call('get',KEYS[1])" + + "\nif c and tonumber(c) > tonumber(ARGV[1]) then" + + "\nreturn c;" + + "\nend" + + "\nc = redis.call('incr',KEYS[1])" + + "\nif tonumber(c) == 1 then" + + "\nredis.call('expire',KEYS[1],ARGV[2])" + + "\nend" + + "\nreturn c;"; +} + +String luaScript = buildLuaScript(); +RedisScript redisScript = new DefaultRedisScript<>(luaScript, Number.class); +Number count = redisTemplate.execute(redisScript, keys, limit.count(), limit.period()); +``` + diff --git a/redis/redis-basic/11-deletion-policy.md b/redis/redis-basic/11-deletion-policy.md new file mode 100644 index 0000000..d479872 --- /dev/null +++ b/redis/redis-basic/11-deletion-policy.md @@ -0,0 +1,26 @@ +# 删除策略 + +1. 被动删除。在访问key时,如果发现key已经过期,那么会将key删除。 +2. 主动删除。定时清理key,每次清理会依次遍历所有DB,从db随机取出20个key,如果过期就删除,如果其中有5个key过期,那么就继续对这个db进行清理,否则开始清理下一个db。 + +3. 内存不够时清理。Redis有最大内存的限制,通过maxmemory参数可以设置最大内存,当使用的内存超过了设置的最大内存,就要进行内存释放, 在进行内存释放的时候,会按照配置的淘汰策略清理内存,淘汰策略一般有6种,Redis4.0版本后又增加了2种,主要由分为三类: + + - 第一类 不处理 noeviction。发现内存不够时,不删除key,执行写入命令时直接返回错误信息。(默认的配置) + + - 第二类 从所有结果集中的key中挑选,进行淘汰 + + - allkeys-random 就是从所有的key中随机挑选key,进行淘汰 + - allkeys-lru 就是从所有的key中挑选最近最少使用的数据淘汰 + - allkeys-lfu 就是从所有的key中挑选使用频率最低的key,进行淘汰。(这是Redis 4.0版本后新增的策略) + + - 第三类 从设置了过期时间的key中挑选,进行淘汰 + + 这种就是从设置了expires过期时间的结果集中选出一部分key淘汰,挑选的算法有: + + - volatile-random 从设置了过期时间的结果集中随机挑选key删除。 + - volatile-lru 从设置了过期时间的结果集中挑选最近最少使用的数据淘汰 + - volatile-ttl 从设置了过期时间的结果集中挑选可存活时间最短的key开始删除(也就是从哪些快要过期的key中先删除) + - volatile-lfu 从过期时间的结果集中选择使用频率最低的key开始删除(这是Redis 4.0版本后新增的策略) + + + diff --git a/redis/redis-basic/12-others.md b/redis/redis-basic/12-others.md new file mode 100644 index 0000000..c0c8069 --- /dev/null +++ b/redis/redis-basic/12-others.md @@ -0,0 +1,81 @@ +# 其他 + +## 客户端 + +Redis 客户端与服务端之间的通信协议是在TCP协议之上构建的。 + +Redis Monitor 命令用于实时打印出 Redis 服务器接收到的命令,调试用。 + +``` +redis 127.0.0.1:6379> MONITOR +OK +1410855382.370791 [0 127.0.0.1:60581] "info" +1410855404.062722 [0 127.0.0.1:60581] "get" "a" +``` + +## 慢查询 + +Redis原生提供慢查询统计功能,执行`slowlog get{n}`命令可以获取最近的n条慢查询命令,默认对于执行超过10毫秒的命令都会记录到一个定长队列中,线上实例建议设置为1毫秒便于及时发现毫秒级以上的命令。慢查询队列长度默认128,可适当调大。 + +Redis客户端执行一条命令分为4个部分:发送命令;命令排队;命令执行;返回结果。慢查询只统计命令执行这一步的时间,所以没有慢查询并不代表客户端没有超时问题。 + +Redis提供了`slowlog-log-slower-than`(设置慢查询阈值,单位为微秒)和`slowlog-max-len`(慢查询队列大小)配置慢查询参数。 + +相关命令: + +``` +showlog get n //获取慢查询日志 +slowlog len //慢查询日志队列当前长度 +slowlog reset //重置,清理列表 +``` + +慢查询解决方案: + +1. 修改为低时间复杂度的命令,如hgetall改为hmget等,禁用keys、sort等命令。 +2. 调整大对象:缩减大对象数据或把大对象拆分为多个小对象,防止一次命令操作过多的数据。 + +## pipeline + +redis客户端执行一条命令分4个过程: 发送命令-〉命令排队-〉命令执行-〉返回结果。使用Pipeline可以批量请求,批量返回结果,执行速度比逐条执行要快。 + +使用pipeline组装的命令个数不能太多,不然数据量过大,增加客户端的等待时间,还可能造成网络阻塞,可以将大量命令的拆分多个小的pipeline命令完成。 + +原生批命令(mset, mget)与Pipeline对比: + +1. 原生批命令是原子性,pipeline是非原子性。pipeline命令中途异常退出,之前执行成功的命令不会回滚。 + +2. 原生批命令只有一个命令, 但pipeline支持多命令。 + +## 数据一致性 + +缓存和DB之间怎么保证数据一致性: +读操作:先读缓存,缓存没有的话读DB,然后取出数据放入缓存,最后响应数据 +写操作:先删除缓存,再更新DB +为什么是删除缓存而不是更新缓存呢? + +1. 线程安全问题。同时有请求A和请求B进行更新操作,那么会出现(1)线程A更新了缓存(2)线程B更新了缓存(3)线程B更新了数据库(4)线程A更新了数据库,由于网络等原因,请求B先更新数据库,这就导致缓存和数据库不一致的问题。 +2. 如果业务需求写数据库场景比较多,而读数据场景比较少,采用这种方案就会导致,数据压根还没读到,缓存就被频繁的更新,浪费性能。 +3. 如果你写入数据库的值,并不是直接写入缓存的,而是要经过一系列复杂的计算再写入缓存。那么,每次写入数据库后,都再次计算写入缓存的值,无疑是浪费性能的。 + +先删除缓存,再更新DB,同样也有问题。假如A先删除了缓存,但还没更新DB,这时B过来请求数据,发现缓存没有,去请求DB拿到旧数据,然后再写到缓存,等A更新完了DB之后就会出现缓存和DB数据不一致的情况了。 + +解决方法:采用延时双删策略。更新完数据库之后,延时一段时间,再次删除缓存,确保可以删除读请求造成的缓存脏数据。评估项目的读数据业务逻辑的耗时。然后写数据的休眠时间则在读数据业务逻辑的耗时基础上,加几百ms即可。 + +```java +public void write(String key,Object data){ + redis.delKey(key); + db.updateData(data); + Thread.sleep(1000);//确保读请求结束,写请求可以删除读请求造成的缓存脏数据 + redis.delKey(key); +} +``` + +可以将第二次删除作为异步的。自己起一个线程,异步删除。这样,写的请求就不用沉睡一段时间后了,加大吞吐量。 + +当删缓存失败时,也会就出现数据不一致的情况。 + +解决方法: + +![](http://img.dabin-coder.cn/image/image-20210913235221410.png) + +> 图片来源:https://tech.it168.com diff --git a/redis/redis-basic/2-data-type.md b/redis/redis-basic/2-data-type.md new file mode 100644 index 0000000..1f2c31a --- /dev/null +++ b/redis/redis-basic/2-data-type.md @@ -0,0 +1,318 @@ +# 数据类型 + +Redis支持五种数据类型: + +- string(字符串) +- hash(哈希) +- list(列表) +- set(集合) +- zset(sorted set) + +## 字符串类型 + +字符串类型的值可以是字符串、数字或者二进制,但值最大不能超过512MB。 + +常用命令:set, get, incr, incrby, desr, keys, append, strlen + +- 赋值和取值 + +``` +SET name tyson +GET name +``` + +- 递增数字 + +``` +INCR num //若键值不是整数时,则会提示错误。 +INCRBY num 2 //增加指定整数 +DESR num //递减数字 +INCRBY num 2.7 //增加指定浮点数 +``` + +- 其他 + +`keys list*` 列出匹配的key + +`APPEND name " dai"` 追加值 + +`STRLEN name` 获取字符串长度 + +`MSET name tyson gender male` 同时设置多个值 + +`MGET name gender` 同时获取多个值 + +`GETBIT name 0` 获取0索引处二进制位的值 + +`FLUSHDB` 删除当前数据库所有的key + +`FLUSHALL` 删除所有数据库中的key + +**SETNX和SETEX** + +`SETNX key value`:当key不存在时,将key的值设为value。若给定的key已经存在,则SETNX不做任何操作。 + +`SETEX key seconds value`:比SET多了seconds参数,相当于`SET KEY value` + `EXPIRE KEY seconds`,而且SETEX是原子性操作。 + +**keys和scan** + +redis的单线程的。keys指令会导致线程阻塞一段时间,直到执行完毕,服务才能恢复。scan采用渐进式遍历的方式来解决keys命令可能带来的阻塞问题,每次scan命令的时间复杂度是O(1),但是要真正实现keys的功能,需要执行多次scan。 + +scan的缺点:在scan的过程中如果有键的变化(增加、删除、修改),遍历过程可能会有以下问题:新增的键可能没有遍历到,遍历出了重复的键等情况,也就是说scan并不能保证完整的遍历出来所有的键。 + +scan命令用于迭代当前数据库中的数据库键:`SCAN cursor [MATCH pattern] [COUNT count]` + +``` +scan 0 match * count 10 //返回10个元素 +``` + +SCAN相关命令包括SSCAN 命令、HSCAN 命令和 ZSCAN 命令,分别用于集合、哈希键及有序集合。 + +**expire** + +``` +SET password 666 +EXPIRE password 5 +TTL password //查看键的剩余生存时间,-1为永不过期 +SETEX password 60 123abc //SETEX可以在设置键的同时设置它的生存时间 +``` + +EXPIRE时间单位是秒,PEXPIRE时间单位是毫秒。在键未过期前可以重新设置过期时间,过期之后则键被销毁。 + +在Redis 2.6和之前版本,如果key不存在或者已过期时返回`-1`。 + +从Redis2.8开始,错误返回值的结果有如下改变: + +- 如果key不存在或者已过期,返回 `-2` +- 如果key存在并且没有设置过期时间(永久有效),返回 `-1` 。 + +**type** + +TYPE 命令用于返回 key 所储存的值的类型。 + +``` +127.0.0.1:6379> type NEWBLOG +list +``` + +## 散列类型 + +常用命令:hset, hget, hmset, hmget, hgetall, hdel, hkeys, hvals + +- 赋值和取值 + +``` +HSET car price 500 //HSET key field value +HGET car price +``` + +同时设置获取多个字段的值 + +``` +HMSET car price 500 name BMW +HMGET car price name +HGETALL car +``` + +使用 HGETALL 命令时,如果哈希元素个数比较多,会存在阻塞Redis的可能。如果只需要获取部分field,可以使用hmget,如果一定要获取全部field-value,可以使用hscan命令,该命令会渐进式遍历哈希类型。 + +`HSETNX car price 400 //当字段不存在时赋值,HSETNX是原子操作,不存在竞态条件` + +- 增加数字 + `HINCRBY person score 60` +- 删除字段 + `HDEL car price` +- 其他 + +``` +HKEYS car //获取key +HVALS car //获取value +HLEN car //长度 +``` + +## 列表类型 + +常用命令:lpush, rpush, lpop, rpop, lrange, lrem + +**添加和删除元素** + +``` +LPUSH numbers 1 +RPUSH numbers 2 3 +LPOP numbers +RPOP numbers +``` + +**获取列表片段** + +``` +LRANGE numbers 0 2 +LRANGE numbers -2 -1 //支持负索引 -1是最右边第一个元素 +LRANGE numbers 0 -1 +``` + +**向列表插入值** + +首先从左到右寻找值为pivot的值,向列表插入value + +``` +LINSERT numbers AFTER 5 8 //往5后面插入8 +LINSERT numbers BEFORE 6 9 //往6前面插入9 +``` + +**删除元素** + +`LTRIM numbers 1 2` 删除索引1到2以外的所有元素 + +LPUSH常和LTRIM一起使用来限制列表的元素个数,如保留最近的100条日志 + +``` +LPUSH logs $newLog +LTRIM logs 0 99 +``` + +**删除列表指定的值** + +`LREM key count value` + + 1. count < 0, 则从右边开始删除前count个值为value的元素 + 2. count > 0, 则从左边开始删除前count个值为value的元素 + 3. count = 0, 则删除所有值为value的元素 `LREM numbers 0 2` + +**其他** + +``` +LLEN numbers //获取列表元素个数 +LINDEX numbers -1 //返回指定索引的元素,index是负数则从右边开始计算 +LSET numbers 1 7 //把索引为1的元素的值赋值成7 +``` + +## 集合类型 + +常用命令:sadd, srem, smembers, scard, sismember, sdiff + +集合中不能有相同的元素。 + +**增加/删除元素** + +``` +SADD letters a b c +SREM letters c d +``` + +**获取元素** + +``` +SMEMBERS letters +SCARD letters //获取集合元素个数 +``` + +**判断元素是否在集合中** +`SISMEMBER letters a` + +**集合间的运算** + +``` +SDIFF setA setB //差集运算 +SINTER setA setB //交集运算 +SUNION setA setB //并集运算 +``` + +三个命令都可以传进多个键 `SDIFF setA setB setC` + +**其他** + +`SDIFFSTORE result setA setB` 进行集合运算并将结果存储 + +`SRANDMEMBER key count` + +>随机获取集合里的一个元素,count大于0,则从集合随机获取count个不重复的元素,count小于0,则随机获取的count个元素有些可能相同。 + +## 有序集合类型 + +常用命令:zadd, zrem, zscore, zrange + +```java +zadd zsetkey 50 e1 60 e2 30 e3 +``` + +Zset(sorted set)是string类型的有序集合。zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。不同的是Zset每个元素都会关联一个double(超过17位使用科学计算法表示,可能丢失精度)类型的分数,通过分数来为集合中的成员进行排序。zset的成员是唯一的,但分数(score)可以重复。 + +**有序集合和列表相同点:** + +1. 都是有序的; +2. 都可以获得某个范围内的元素。 + +**有序集合和列表不同点:** + +1. 列表基于链表实现,获取两端元素速度快,访问中间元素速度慢; +2. 有序集合基于散列表和跳跃表实现,访问中间元素时间复杂度是OlogN; +3. 列表不能简单的调整某个元素的位置,有序列表可以(更改元素的分数); +4. 有序集合更耗内存。 + +**增加/删除元素** + +时间复杂度OlogN。 + +``` +ZADD scoreboard 89 Tom 78 Sophia +ZADD scoreboard 85.5 Tyson //支持双精度浮点数 +ZREM scoreboard Tyson +ZREMRANGEBYRANK scoreboard 0 2 //按照排名范围删除元素 +ZREMRANGEBYSCORE scoreboard (80 100 //按照分数范围删除元素,"("代表不包含 +``` + +**获取元素分数** + +时间复杂度O1。 + +`ZSCORE scoreboard Tyson` + +**获取排名在某个范围的元素列表** + +ZRANGE命令时间复杂度是O(log(n)+m), n是有序集合元素个数,m是返回元素个数。 + +``` +ZRANGE scoreboard 0 2 +ZRANGE scoreboard 1 -1 //-1表示最后一个元素 +ZRANGE scoreboard 0 -1 WITHSCORES //同时获得分数 +``` + +**获取指定分数范围的元素** + +ZRANGEBYSCORE命令时间复杂度是O(log(n)+m), n是有序集合元素个数,m是返回元素个数。 + +``` +ZRANGEBYSCORE scoreboard 80 100 +ZRANGEBYSCORE scoreboard 80 (100 //不包含100 +ZRANGEBYSCORE scoreboard (60 +inf LIMIT 1 3 //获取分数高于60的从第二个人开始的3个人 +``` + +**增加某个元素的分数** + +时间复杂度OlogN。 + +`ZINCRBY scoreboard 10 Tyson` + +**其他** + +``` +ZCARD scoreboard //获取集合元素个数,时间复杂度O1 +ZCOUNT scoreboard 80 100 //指定分数范围的元素个数 +ZRANK scoreboard Tyson //按从小到大的顺序获取元素排名 +ZREVRANK scoreboard Tyson //按从大到小的顺序获取元素排名 +``` + +## Bitmaps + +Bitmaps本身不是一种数据结构,实际上它就是字符串,但是它可以对字符串的位进行操作,可以把Bitmaps想象成一个以位为单位的数组,数组的每个单元只能存储0和1。 + +bitmap的长度与集合中元素个数无关,而是与基数的上限有关。假如要计算上限为1亿的基数,则需要12.5M字节的bitmap。就算集合中只有10个元素也需要12.5M。 + +## HyperLogLog + +HyperLogLog 是用来做基数统计的算法,其优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。 + +基数:比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8},基数即不重复元素为5。 + +应用场景:独立访客(unique visitor,uv)统计。 diff --git a/redis/redis-basic/3-data-structure.md b/redis/redis-basic/3-data-structure.md new file mode 100644 index 0000000..3cc6f88 --- /dev/null +++ b/redis/redis-basic/3-data-structure.md @@ -0,0 +1,100 @@ +# 数据结构 + +## 动态字符串 + +SDS(simple dynamic string,SDS),简单动态字符串,其定义如下: + +```c +struct sdshdr { + + // 记录 buf 数组中已使用字节的数量 + // 等于 SDS 所保存字符串的长度 + int len; + + // 记录 buf 数组中未使用字节的数量 + int free; + + // 字节数组,用于保存字符串 + char buf[]; + +}; +``` + +在64位系统下,属性len和属性free各占4个字节,紧接着存放字节数组。 + +下面展示一个SDS示例: + +```c +set name "Redis" +``` + +![](http://img.dabin-coder.cn/image/sds.png) + +free属性的值为0,表示这个SDS没有分配任何未使用空间。 + +len属性的值为5,表示这个SDS保存了一个物字节长的字符串。 + +buf属性是一个char类型的数组,数组的前五个字节分别保存了'R'、'e'、'd'、'i'、's'五个字符,而最后一个字节则保存了空字符'\0'。 + +SDS遵循C字符串以空字符结尾的惯例,保存空字符的1字节空间不计算在SDS的len属性里面,并且为空字符分配额外的1字节空间,以及添加空字符到字符串末尾等操作,都是由SDS函数自动完成的,所以这个空字符对于SDS的使用者来说是完全透明的。遵循空字符串结尾这一惯例的好处是,SDS可以直接重用一部分C字符串函数库里面的函数。 + +下面是SDS与C字符串的区别。 + +| C 字符串 | SDS | +| :--------------------------------------------------- | :--------------------------------------------------- | +| 获取字符串长度的复杂度为 O(N) 。 | 获取字符串长度的复杂度为 O(1) 。 | +| API 是不安全的,可能会造成缓冲区溢出。 | API 是安全的,不会造成缓冲区溢出。 | +| 修改字符串长度 `N` 次必然需要执行 `N` 次内存重分配。 | 修改字符串长度 `N` 次最多需要执行 `N` 次内存重分配。 | +| 只能保存文本数据。 | 可以保存文本或者二进制数据。 | +| 可以使用所有 `` 库中的函数。 | 可以使用一部分 `` 库中的函数。 | + +## 字典 + +字典使用hashtable作为底层实现。键值对的值可以是一个指针, 或者是一个 uint64_t 整数, 又或者是一个 int64_t 整数。 + +```c +typedef struct dictEntry { + + // 键 + void *key; + + // 值 + union { + void *val; + uint64_t u64; + int64_t s64; + } v; + + // 指向下个哈希表节点,形成链表 + struct dictEntry *next; + +} dictEntry; +``` + +## 整数集合 + +整数集合(intset)是 Redis 用于保存整数值的集合抽象数据结构, 它可以保存类型为 int16_t 、 int32_t 或者 int64_t 的整数值, 并且保证集合中不会出现重复元素。 + +## 压缩列表 + +ziplist是 Redis 为了节约内存而开发的, 由一系列特殊编码的连续内存块组成的顺序型(sequential)数据结构。每个压缩列表节点都由 previous_entry_length 、 encoding 、 content 三个部分组成。 + +节点的 previous_entry_length 属性以字节为单位, 记录了压缩列表中前一个节点的长度。 +节点的 encoding 属性记录了节点的 content 属性所保存数据的类型以及长度。有两种编码方式,字节数组编码和整数编码。 + +压缩列表的从表尾向表头遍历操作就是使用这一原理实现的: 只要我们拥有了一个指向某个节点起始地址的指针, 那么通过这个指针以及这个节点的 previous_entry_length 属性, 程序就可以一直向前一个节点回溯, 最终到达压缩列表的表头节点。 + +## 跳表 + +跳表可以看成多层链表,它有如下的性质: + +- 多层的结构组成,每层是一个有序的链表 +- 最底层的链表包含所有的元素 +- 跳跃表的查找次数近似于层数,时间复杂度为O(logn),插入、删除也为 O(logn) + +![](http://img.dabin-coder.cn/image/redis-skiplist.png) + +## 对象 + +Redis 的对象系统还实现了基于引用计数技术的内存回收机制: 当程序不再使用某个对象的时候, 这个对象所占用的内存就会被自动释放; 另外, Redis 还通过引用计数技术实现了对象共享机制, 这一机制可以在适当的条件下, 通过让多个数据库键共享同一个对象来节约内存。 + diff --git a/redis/redis-basic/4-implement.md b/redis/redis-basic/4-implement.md new file mode 100644 index 0000000..fb53417 --- /dev/null +++ b/redis/redis-basic/4-implement.md @@ -0,0 +1,68 @@ +# 底层实现 + +## string + +字符串对象的编码可以是 int 、 raw 或者 embstr 。 + +1. 如果一个字符串对象保存的是整数值, 并且这个整数值可以用 long 类型来表示, 那么会将编码设置为 int 。 +2. 如果字符串对象保存的是一个字符串值, 并且这个字符串值的长度大于 39 字节, 那么字符串对象将使用一个简单动态字符串(SDS)来保存这个字符串值, 并将对象的编码设置为 raw 。 +3. 如果字符串对象保存的是一个字符串值, 并且这个字符串值的长度小于等于 39 字节, 那么字符串对象将使用 embstr 编码的方式来保存这个字符串值。 + +| 值 | 编码 | +| :----------------------------------------------------------- | :------------------ | +| 可以用 `long` 类型保存的整数。 | `int` | +| 可以用 `long double` 类型保存的浮点数。 | `embstr` 或者 `raw` | +| 字符串值, 或者因为长度太大而没办法用 `long` 类型表示的整数, 又或者因为长度太大而没办法用 `long double` 类型表示的浮点数。 | `embstr` 或者 `raw` | + +## hash + +hash类型内部编码有两种: + +1. ziplist,压缩列表。当哈希类型元素个数小于512个,并且所有值都小于64字节时,Redis会使用ziplist作为哈希的内部实现。ziplist使用更加紧凑的结构实现多个元素的连续存储,更加节省内存。 +2. hashtable。当哈希类型无法满足ziplist的条件时,Redis会使用hashtable作为哈希的内部实现,因为此时ziplist的读写效率会下降,而hashtable的读写时间复杂度为O(1)。 + +使用 ziplist 作为 hash 的底层实现时,添加元素的时候,同一键值对的两个节点总是紧挨在一起, 保存键的节点在前, 保存值的节点在后。 + +使用场景:记录博客点赞数量。`hset MAP_BLOG_LIKE_COUNT blogId likeCount`,key为MAP_BLOG_LIKE_COUNT,field为博客id,value为点赞数量。 + +## list + +列表list类型内部编码有两种: + +1. ziplist,压缩列表。当列表中的元素个数小于512个,同时列表中每个元素的值都小于64字节时,Redis会选用ziplist来作为列表的内部实现来减少内存的使用。 +2. 当列表类型无法满足ziplist的条件时,Redis会使用linkedlist作为列表的内部实现。 + +Redis3.2版本提供了quicklist内部编码,简单地说它是以一个ziplist为节点的linkedlist,它结合了ziplist和linkedlist两者的优势,为列表类型提供了一种更为优秀的内部编码实现。 + +![](http://img.dabin-coder.cn/image/list-api.png) + +使用场景: + +1. 消息队列。Redis的lpush+brpop命令组合即可实现阻塞队列。 + +## set + +集合对象的编码可以是 intset 或者 hashtable 。 + +1. intset 编码的集合对象使用整数集合作为底层实现, 集合对象包含的所有元素都被保存在整数集合(数组)里面。 +2. hashtable 编码的集合对象使用字典作为底层实现, 字典的每个键都是一个字符串对象, 而字典的值则全部被设置为 NULL 。 + +## zset + +有序集合的编码可以是 ziplist 或者 skiplist 。当有序集合的元素个数小于128,同时每个元素的值都小于64字节时,Redis会用ziplist来作为有序集合的内部实现,ziplist可以有效减少内存的使用。否则,使用skiplist作为有序集合的内部实现。 + +1. ziplist 编码的有序集合对象使用压缩列表作为底层实现, 每个集合元素使用两个紧挨在一起的压缩列表节点来保存, 第一个节点保存元素的成员(member), 而第二个元素则保存元素的分值(score)。压缩列表内的集合元素按分值从小到大进行排序。 +2. skiplist 编码的有序集合对象使用字典和跳跃表实现。使用字典查找给定成员的分值,时间复杂度为O(1) (跳跃表查找时间复杂度为O(logN))。使用跳跃表可以对有序集合进行范围型操作。 + +## 使用场景 + +string:1、常规key-value缓存应用。常规计数如微博数、粉丝数。2、分布式锁。 + +hash:存放结构化数据,如用户信息(昵称、年龄、性别、积分等)。 + +list:热门博客列表、消息队列系统。使用list可以构建队列系统,比如:将Redis用作日志收集器,多个端点将日志信息写入Redis,然后一个worker统一将所有日志写到磁盘。 + +set:1、好友关系,微博粉丝的共同关注、共同喜好、共同好友等;2、利用唯一性,统计访问网站的所有独立ip 。 + +zset:1、排行榜;2、优先级队列。 + diff --git a/redis/redis-basic/5-sort.md b/redis/redis-basic/5-sort.md new file mode 100644 index 0000000..75969b2 --- /dev/null +++ b/redis/redis-basic/5-sort.md @@ -0,0 +1,36 @@ +# 排序 + +``` +LPUSH myList 4 8 2 3 6 +SORT myList DESC +``` + +``` +LPUSH letters f l d n c +SORT letters ALPHA +``` + +**BY参数** + +``` +LPUSH list1 1 2 3 +SET score:1 50 +SET score:2 100 +SET score:3 10 +SORT list1 BY score:* DESC +``` + +**GET参数** + +GET参数命令作用是使SORT命令的返回结果是GET参数指定的键值。 + +`SORT tag:Java:posts BY post:*->time DESC GET post:*->title GET post:*->time GET #` + +GET #返回文章ID。 + +**STORE参数** + +`SORT tag:Java:posts BY post:*->time DESC GET post:*->title STORE resultCache` + +`EXPIRE resultCache 10 //STORE结合EXPIRE可以缓存排序结果` + diff --git a/redis/redis-basic/6-transaction.md b/redis/redis-basic/6-transaction.md new file mode 100644 index 0000000..6f099d4 --- /dev/null +++ b/redis/redis-basic/6-transaction.md @@ -0,0 +1,61 @@ +# 事务 + +事务的原理是将一个事务范围内的若干命令发送给Redis,然后再让Redis依次执行这些命令。 + +事务的生命周期: + +1. 使用MULTI开启一个事务 +2. 在开启事务的时候,每次操作的命令将会被插入到一个队列中,同时这个命令并不会被真的执行 + +3. EXEC命令进行提交事务 + +![](http://img.dabin-coder.cn/image/redis-multi.jpg) + +DISCARD:放弃事务,即该事务内的所有命令都将取消 + +一个事务范围内某个命令出错不会影响其他命令的执行,不保证原子性: + +``` +127.0.0.1:6379> multi +OK +127.0.0.1:6379> set a 1 +QUEUED +127.0.0.1:6379> set b 1 2 +QUEUED +127.0.0.1:6379> set c 3 +QUEUED +127.0.0.1:6379> exec +1) OK +2) (error) ERR syntax error +3) OK +``` + +事务里的命令执行时会读取最新的值: + +![](http://img.dabin-coder.cn/image/redis-transaction.png) + +## WATCH命令 + +WATCH命令可以监控一个或多个键,一旦其中有一个键被修改,之后的事务就不会执行(类似于乐观锁)。执行EXEC命令之后,就会自动取消监控。 + +``` +127.0.0.1:6379> watch name +OK +127.0.0.1:6379> set name 1 +OK +127.0.0.1:6379> multi +OK +127.0.0.1:6379> set name 2 +QUEUED +127.0.0.1:6379> set gender 1 +QUEUED +127.0.0.1:6379> exec +(nil) +127.0.0.1:6379> get gender +(nil) +``` + +UNWATCH:取消WATCH命令对多有key的监控,所有监控锁将会被取消。 + + + diff --git a/redis/redis-basic/7-message-queue.md b/redis/redis-basic/7-message-queue.md new file mode 100644 index 0000000..aa10a6b --- /dev/null +++ b/redis/redis-basic/7-message-queue.md @@ -0,0 +1,31 @@ +# 消息队列 + +使用一个列表,让生产者将任务使用LPUSH命令放进列表,消费者不断用RPOP从列表取出任务。 + +BRPOP和RPOP命令相似,唯一的区别就是当列表没有元素时BRPOP命令会一直阻塞连接,直到有新元素加入。 +`BRPOP queue 0 //0表示不限制等待时间` + +## 优先级队列 + +`BLPOP queue:1 queue:2 queue:3 0` +如果多个键都有元素,则按照从左到右的顺序取元素 + +## 发布/订阅模式 + +``` +PUBLISH channel1 hi +SUBSCRIBE channel1 +UNSUBSCRIBE channel1 //退订通过SUBSCRIBE命令订阅的频道。 +``` + +`PSUBSCRIBE channel?*` 按照规则订阅 +`PUNSUBSCRIBE channel?*` 退订通过PSUBSCRIBE命令按照某种规则订阅的频道。其中订阅规则要进行严格的字符串匹配,`PUNSUBSCRIBE *`无法退订`channel?*`规则。 + +缺点:在消费者下线的情况下,生产的消息会丢失。 + +## 延时队列 + +使用sortedset,拿时间戳作为score,消息内容作为key,调用zadd来生产消息,消费者用`zrangebyscore`指令获取N秒之前的数据轮询进行处理。 + + + diff --git a/redis/redis-basic/8-persistence.md b/redis/redis-basic/8-persistence.md new file mode 100644 index 0000000..f109548 --- /dev/null +++ b/redis/redis-basic/8-persistence.md @@ -0,0 +1,67 @@ +# 持久化 + +Redis支持两种方式的持久化,一种是RDB的方式,一种是AOF的方式。前者会根据指定的规则定时将内存中的数据存储在硬盘上,而后者在每次执行完命令后将命令记录下来。一般将两者结合使用。 + +## RDB方式 + +RDB 是 Redis 默认的持久化方案。RDB持久化时会将内存中的数据写入到磁盘中,在指定目录下生成一个dump.rdb文件。Redis 重启会加载dump.rdb文件恢复数据。 + +RDB持久化的过程(执行SAVE命令除外): + +- 创建一个子进程; +- 父进程继续接收并处理客户端的请求,而子进程开始将内存中的数据写进硬盘的临时文件; +- 当子进程写完所有数据后会用该临时文件替换旧的RDB文件。 + +Redis启动时会读取RDB快照文件,将数据从硬盘载入内存。通过RDB方式的持久化,一旦Redis异常退出,就会丢失最近一次持久化以后更改的数据。 + +触发RDB快照: + +1. 手动触发: + - 用户执行SAVE或BGSAVE命令。SAVE命令执行快照的过程会阻塞所有来自客户端的请求,应避免在生产环境使用这个命令。BGSAVE命令可以在后台异步进行快照操作,快照的同时服务器还可以继续响应客户端的请求,因此需要手动执行快照时推荐使用BGSAVE命令; + +2. 被动触发: + - 根据配置规则进行自动快照,如`SAVE 300 10`,300秒内至少有10个键被修改则进行快照。 + - 如果从节点执行全量复制操作,主节点自动执行bgsave生成RDB文件并发送给从节点。 + - 默认情况下执行shutdown命令时,如果没有开启AOF持久化功能则自动执行bgsave。 + - 执行debug reload命令重新加载Redis时,也会自动触发save操作。 + +优点:Redis加载RDB恢复数据远远快于AOF的方式。 + +缺点: + +1. RDB方式数据没办法做到实时持久化/秒级持久化。因为bgsave每次运行都要执行fork操作创建子进程,属于重量级操作,频繁执行成本过高。 +2. 存在老版本Redis服务和新版本RDB格式兼容性问题。RDB文件使用特定二进制格式保存,Redis版本演进过程中有多个格式的RDB版本,存在老版本Redis服务无法兼容新版RDB格式的问题。 + +## AOF方式 + +AOF(append only file)持久化:以独立日志的方式记录每次写命令,Redis重启时会重新执行AOF文件中的命令达到恢复数据的目的。AOF的主要作用是**解决了数据持久化的实时性**,目前已经是Redis持久化的主流方式。 + +默认情况下Redis没有开启AOF方式的持久化,可以通过appendonly参数启用`appendonly yes`。开启AOF方式持久化后每执行一条写命令,Redis就会将该命令写进aof_buf缓冲区,AOF缓冲区根据对应的策略向硬盘做同步操作。 + +默认情况下系统每30秒会执行一次同步操作。为了防止缓冲区数据丢失,可以在Redis写入AOF文件后主动要求系统将缓冲区数据同步到硬盘上。可以通过`appendfsync`参数设置同步的时机。 + +``` +appendfsync always //每次写入aof文件都会执行同步,最安全最慢,只能支持几百TPS写入,不建议配置 +appendfsync everysec //保证了性能也保证了安全,建议配置 +appendfsync no //由操作系统决定何时进行同步操作 +``` + +重写机制: + +随着命令不断写入AOF,文件会越来越大,为了解决这个问题,Redis引入AOF重写机制压缩文件体积。AOF文件重写是把Redis进程内的数据转化为写命令同步到新AOF文件的过程。 + +优点: +(1)AOF可以更好的保护数据不丢失,一般AOF会每秒去执行一次fsync操作,如果redis进程挂掉,最多丢失1秒的数据。 +(2)AOF以appen-only的模式写入,所以没有任何磁盘寻址的开销,写入性能非常高。 +缺点 +(1)对于同一份文件AOF文件比RDB数据快照要大。 +(2)不适合写多读少场景。 +(3)数据恢复比较慢。 + +RDB和AOF如何选择 +(1)仅使用RDB这样会丢失很多数据。 +(2)仅使用AOF,因为这一会有两个问题,第一通过AOF恢复速度慢;第二RDB每次简单粗暴生成数据快照,更加安全健壮。 +(3)综合AOF和RDB两种持久化方式,用AOF来保证数据不丢失,作为恢复数据的第一选择;用RDB来做不同程度的冷备,在AOF文件都丢失或损坏不可用的时候,可以使用RDB进行快速的数据恢复。 + + + diff --git a/redis/redis-basic/9-cluster.md b/redis/redis-basic/9-cluster.md new file mode 100644 index 0000000..1bbf7c7 --- /dev/null +++ b/redis/redis-basic/9-cluster.md @@ -0,0 +1,121 @@ +# 集群 + +## 主从复制 + +redis的复制功能是支持多个数据库之间的数据同步。主数据库可以进行读写操作,当主数据库的数据发生变化时会自动将数据同步到从数据库。从数据库一般是只读的,它会接收主数据库同步过来的数据。一个主数据库可以有多个从数据库,而一个从数据库只能有一个主数据库。 + +``` +redis-server //启动Redis实例作为主数据库 +redis-server --port 6380 --slaveof 127.0.0.1 6379 //启动另一个实例作为从数据库 +slaveof 127.0.0.1 6379 +SLAVEOF NO ONE //停止接收其他数据库的同步并转化为主数据库。 +``` + +### 同步机制 + +1. 保存主节点信息。 +2. 主从建立socket连接。 +3. 从节点发送ping命令进行首次通信,主要用于检测网络状态。 +4. 权限认证。如果主节点设置了requirepass参数,则需要密码认证。从节点必须配置masterauth参数保证与主节点相同的密码才能通过验证。 +5. 同步数据集。第一次同步的时候,从数据库启动后会向主数据库发送SYNC命令。主数据库接收到命令后开始在后台保存快照(RDB持久化过程),并将保存快照过程接收到的命令缓存起来。当快照完成后,Redis会将快照文件和缓存的命令发送到从数据库。从数据库接收到后,会载入快照文件并执行缓存的命令。以上过程称为复制初始化。 +6. 复制初始化完成后,主数据库每次收到写命令就会将命令同步给从数据库,从而实现主从数据库数据的一致性。 + +![](http://img.dabin-coder.cn/image/redis-replication.png) + +Redis在2.8及以上版本使用psync命令完成主从数据同步,同步过程分为:全量复制和部分复制。 + +全量复制:一般用于初次复制场景,Redis早期支持的复制功能只有全量复制,它会把主节点全部数据一次性发送给从节点,当数据量较大时,会对主从节点和网络造成很大的开销。 + +部分复制:用于处理在主从复制中因网络闪断等原因造成的数据丢失场景,当从节点再次连上主节点后,如果条件允许,主节点会补发丢失数据给从节点。因为补发的数据远远小于全量数据,可以有效避免全量复制的过高开销。 + +### 读写分离 + +通过redis的复制功能可以实现数据库的读写分离,提高服务器的负载能力。主数据库主要进行写操作,而从数据库负责读操作。很多场景下对数据库的读频率大于写,当单机的Redis无法应付大量的读请求时,可以通过复制功能建立多个从数据库节点,主数据库负责写操作,从数据库负责读操作。这种一主多从的结构很适合读多写少的场景。 + +### 从数据库持久化 + +持久化的操作比较耗时,为了提高性能,可以建立一个从数据库,并在从数据库进行持久化,同时在主数据库禁用持久化。 + +## 哨兵Sentinel + +当master节点奔溃时,可以手动将slave提升为master,继续提供服务。 + +- 首先,从数据库使用`SLAVE NO ONE`将从数据库提升为主数据库继续服务; +- 启动奔溃的主数据库,通过`SLAVEOF`命令将其设置为新的主数据库的从数据库,即可将数据同步过来。 + +通过哨兵机制可以自动切换主从节点。哨兵是一个独立的进程,用于监控redis实例的是否正常运行。 + +### 作用 + +1. 监测redis实例的状态 +2. 如果master实例异常,会自动进行主从节点切换 + +客户端连接redis的时候,先连接哨兵,哨兵会告诉客户端redis主节点的地址,然后客户端连接上redis并进行后续的操作。当主节点宕机的时候,哨兵监测到主节点宕机,会重新推选出某个表现良好的从节点成为新的主节点,然后通过发布订阅模式通知其他的从服务器,让它们切换主机。 + +### 定时任务 + +1. 每隔10s,每个Sentinel节点会向主节点和从节点发送info命令获取最新的拓扑结构。 +2. 每隔2s,每个Sentinel节点会去获取其他Sentinel节点对于主节点的判断以及当前Sentinel节点的信息,用于判断主节点是否客观下线和是否有新的Sentinel节点加入。 +3. 每隔1s,每个Sentinel节点会向主节点、从节点、其余Sentinel节点发送一条ping命令做一次心跳检测,来确认这些节点是否可达。 + +### 工作原理 + +- 每个Sentinel以每秒钟一次的频率向它所知的Master,Slave以及其他 Sentinel 实例发送一个 PING 命令。 +- 如果一个实例距离最后一次有效回复 PING 命令的时间超过指定的值, 则这个实例会被 Sentinel 标记为主观下线。 +- 如果一个Master被标记为主观下线,则正在监视这个Master的所有 Sentinel 要以每秒一次的频率确认Master是否真正进入主观下线状态。 +- 当有足够数量的 Sentinel(大于等于配置文件指定的值)在指定的时间范围内确认Master的确进入了主观下线状态, 则Master会被标记为客观下线 。若没有足够数量的 Sentinel 同意 Master 已经下线, Master 的客观下线状态就会被移除。 若 Master 重新向 Sentinel 的 PING 命令返回有效回复, Master 的主观下线状态就会被移除。 +- 哨兵节点会选举出哨兵领导者,负责故障转移的工作。 +- 哨兵领导者会推选出某个表现良好的从节点成为新的主节点,然后通知其他从节点更新主节点。 + +```java +/** + * 测试Redis哨兵模式 + * @author liu + */ +public class TestSentinels { + @SuppressWarnings("resource") + @Test + public void testSentinel() { + JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); + jedisPoolConfig.setMaxTotal(10); + jedisPoolConfig.setMaxIdle(5); + jedisPoolConfig.setMinIdle(5); + // 哨兵信息 + Set sentinels = new HashSet<>(Arrays.asList("192.168.11.128:26379", + "192.168.11.129:26379","192.168.11.130:26379")); + // 创建连接池 + JedisSentinelPool pool = new JedisSentinelPool("mymaster", sentinels,jedisPoolConfig,"123456"); + // 获取客户端 + Jedis jedis = pool.getResource(); + // 执行两个命令 + jedis.set("mykey", "myvalue"); + String value = jedis.get("mykey"); + System.out.println(value); + } +} +``` + +## cluster + +集群用于分担写入压力,主从用于灾难备份和高可用以及分担读压力。 + +主从复制存在不能自动故障转移、达不到高可用的问题。 +哨兵模式解决了主从复制不能自动故障转移、达不到高可用的问题,但还是存在主节点的写能力、容量受限于单机配置的问题。 +cluster模式实现了Redis的分布式存储,每个节点存储不同的内容,解决主节点的写能力、容量受限于单机配置的问题。 + +### 哈希分区算法 + +节点取余分区。使用特定的数据,如Redis的键或用户ID,对节点数量N取余:hash(key)%N计算出哈希值,用来决定数据映射到哪一个节点上。 +优点是简单性。扩容时通常采用翻倍扩容,避免数据映射全部被打乱导致全量迁移的情况。 + +一致性哈希分区:为系统中每个节点分配一个token,范围一般在0~232,这些token构成一个哈希环。数据读写执行节点查找操作时,先根据key计算hash值,然后顺时针找到第一个大于等于该哈希值的token节点。 +这种方式相比节点取余最大的好处在于加入和删除节点只影响哈希环中相邻的节点,对其他节点无影响。 + +Redis Cluser采用虚拟槽分区,所有的键根据哈希函数映射到0~16383整数槽内,计算公式:slot=CRC16(key)&16383。每一个节点负责维护一部分槽以及槽所映射的键值数据。 + +### 故障转移 + +Redis集群内节点通过ping/pong消息实现节点通信,消息不但可以传播节点槽信息,还可以传播其他状态如:主从状态、节点故障等。因此故障发现也是通过消息传播机制实现的,主要环节包括:主观下线(pfail)和客观下线(fail)。 + + + diff --git a/redis/redis-basic/README.md b/redis/redis-basic/README.md new file mode 100644 index 0000000..52e7c6f --- /dev/null +++ b/redis/redis-basic/README.md @@ -0,0 +1,11 @@ +--- +title: MySQL基础 +icon: linux +date: 2022-08-06 +category: MySQL +star: true +--- + +**本专栏是大彬学习MySQL基础知识的学习笔记,如有错误,可以在评论区指出**~ + +![](http://img.dabin-coder.cn/image/MySQL知识点总结.jpg) diff --git "a/Redis/Redis\351\235\242\350\257\225\351\242\230.md" b/redis/redis.md similarity index 96% rename from "Redis/Redis\351\235\242\350\257\225\351\242\230.md" rename to redis/redis.md index b9a05f4..a9e9e50 100644 --- "a/Redis/Redis\351\235\242\350\257\225\351\242\230.md" +++ b/redis/redis.md @@ -1,3 +1,5 @@ +![](http://img.dabin-coder.cn/image/Redis知识点.jpg) + ## Redis是什么? Redis(`Remote Dictionary Server`)是一个使用 C 语言编写的,高性能非关系型的键值对数据库。与传统数据库不同的是,Redis 的数据是存在内存中的,所以读写速度非常快,被广泛应用于缓存方向。Redis可以将数据写入磁盘中,保证了数据的安全不丢失,而且Redis的操作是原子性的。 @@ -185,7 +187,7 @@ Redis单条命令是原子性执行的,但事务不保证原子性,且没有 Redis支持两种方式的持久化,一种是`RDB`的方式,一种是`AOF`的方式。**前者会根据指定的规则定时将内存中的数据存储在硬盘上**,而**后者在每次执行完命令后将命令记录下来**。一般将两者结合使用。 -### RDB方式 +**RDB方式** `RDB`是 Redis 默认的持久化方案。RDB持久化时会将内存中的数据写入到磁盘中,在指定目录下生成一个`dump.rdb`文件。Redis 重启会加载`dump.rdb`文件恢复数据。 @@ -220,7 +222,7 @@ Redis启动时会读取RDB快照文件,将数据从硬盘载入内存。通过 1. **RDB方式数据无法做到实时持久化**。因为`BGSAVE`每次运行都要执行`fork`操作创建子进程,属于重量级操作,频繁执行成本比较高。 2. RDB 文件使用特定二进制格式保存,Redis 版本升级过程中有多个格式的 RDB 版本,**存在老版本 Redis 无法兼容新版 RDB 格式的问题**。 -### AOF方式 +**AOF方式** AOF(append only file)持久化:以独立日志的方式记录每次写命令,Redis重启时会重新执行AOF文件中的命令达到恢复数据的目的。AOF的主要作用是**解决了数据持久化的实时性**,AOF 是Redis持久化的主流方式。 @@ -253,7 +255,7 @@ appendfsync no //由操作系统决定何时进行同步操作 1. 对于同一份文件AOF文件比RDB数据快照要大。 2. 数据恢复比较慢。 -### RDB和AOF如何选择? +## RDB和AOF如何选择? 通常来说,应该同时使用两种持久化方案,以保证数据安全。 @@ -605,6 +607,22 @@ Redis 官方站提出了一种权威的基于 Redis 实现分布式锁的方式 5. Master调用BGREWRITEAOF重写AOF文件,AOF在重写的时候会占大量的CPU和内存资源,导致服务load过高,出现短暂服务暂停现象。 6. 为了Master的稳定性,主从复制不要用图状结构,用单向链表结构更稳定,即主从关系为:Master<–Slave1<–Slave2<–Slave3…,这样的结构也方便解决单点故障问题,实现Slave对Master的替换,也即,如果Master挂了,可以立马启用Slave1做Master,其他不变。 +## 说说为什么Redis过期了为什么内存没释放? + +第一种情况,可能是覆盖之前的key,导致key过期时间发生了改变。 + +当一个key在Redis中已经存在了,但是由于一些误操作使得key过期时间发生了改变,从而导致这个key在应该过期的时间内并没有过期,从而造成内存的占用。 + +第二种情况是,Redis过期key的处理策略导致内存没释放。 + +一般Redis对过期key的处理策略有两种:惰性删除和定时删除。 + +先说惰性删除的情况 + +当一个key已经确定设置了xx秒过期同时中间也没有修改它,xx秒之后它确实已经过期了,但是惰性删除的策略它并不会马上删除这个key,而是当再次读写这个key时它才会去检查是否过期,如果过期了就会删除这个key。也就是说,惰性删除策略下,就算key过期了,也不会立刻释放内容,要等到下一次读写这个key才会删除key。 + +而定时删除会在一定时间内主动淘汰一部分已经过期的数据,默认的时间是每100ms过期一次。因为定时删除策略每次只会淘汰一部分过期key,而不是所有的过期key,如果redis中数据比较多的话要是一次性全量删除对服务器的压力比较大,每一次只挑一批进行删除,所以很可能出现部分已经过期的key并没有及时的被清理掉,从而导致内存没有即时被释放。 + ![](http://img.dabin-coder.cn/image/20220612101342.png) diff --git a/system-design/scan-code-login.md b/system-design/scan-code-login.md new file mode 100644 index 0000000..3da9371 --- /dev/null +++ b/system-design/scan-code-login.md @@ -0,0 +1,83 @@ +> 回复【**手册**】获取大彬精心整理的**大厂面试手册**。 + +## 面试开始 + +旁白:通过微信聊天方式模拟现场面试 + +**面试官**:**看你简历上写了做过扫码登录的功能** + +**面试官**:**详细介绍下怎么设计的?** + +大彬:嗯,扫码登录功能主要分为三个阶段:**待扫描、已扫描待确认、已确认**。 + +大彬:整体流程图如下图。 + +![](E:\b站\视频素材\图片\二维码扫描\整个流程.png) + +大彬:下面分阶段来看看设计原理。 + +**1、待扫描阶段** + +首先是待扫描阶段,这个阶段是 PC 端跟服务端的交互过程。 + +每次用户打开PC端登陆请求,系统返回一个**唯一的二维码ID**,并将二维码ID的信息绘制成二维码返回给用户。 + +这里的二维码ID一定是唯一的,后续流程会将二维码ID跟身份信息绑定,不唯一的话就会造成你登陆了其他用户的账号或者其他用户登陆你的账号。 + +此时在 PC 端会启动一个定时器,**轮询查询二维码是否被扫描**。 + +如果移动端未扫描的话,那么一段时间后二维码将会失效。 + +这个阶段的交互过程如下图所示。 + +![](E:\b站\视频素材\图片\二维码扫描\第一阶段.png) + +**2、已扫描待确认阶段** + +第二个阶段是已扫描待确认阶段,主要是移动端跟服务端交互的过程。 + +首先移动端扫描二维码,获取二维码 ID,然后**将手机端登录的凭证(token)和 二维码 ID 作为参数发送给服务端** + +此时的手机在之前已经是登录的,不存在没登录的情况。 + +服务端接受请求后,会将 token 与二维码 ID 关联,然后会生成一个临时token,这个 token 会返回给移动端,临时 token 用作确认登录的凭证。 + +PC 端的定时器,会轮询到二维码的状态已经发生变化,会将 PC 端的二维码更新为已扫描,请在手机端确认。 + +**面试官**:**打断一下,这里为什么要有手机端确认的操作?** + +大彬:假设没有确认这个环节,很容易就会被坏人拦截token去冒充登录。所以二维码扫描一定要有这个确认的页面,让用户去确认是否进行登录。 + +大彬:另外,二维码扫描确认之后,再往用户app或手机等发送登录提醒的通知,告知如果不是本人登录的,则建议用户立即修改密码。 + +大彬:这个阶段是交互过程如下图所示。 + +![](http://img.dabin-coder.cn/image/20220411002823.png) + +**3、已确认** + +扫码登录的最后阶段,用户点击确认登录,移动端携带上一步骤中获取的临时 token访问服务端。 + +服务端校对完成后,会更新二维码状态,并且给 PC 端生成一个正式的 token。 + +后续 PC 端就是持有这个 token 访问服务端。 + +这个阶段是交互过程如下图所示。 + +![](http://img.dabin-coder.cn/image/20220411002832.png) + +大彬:以上就是整个扫码登录功能的详细设计! + +**面试官**:**点赞!** + + + +## 点关注,不迷路 + +大彬,**非科班出身,自学Java**,校招斩获京东、携程、华为等offer。作为一名转码选手,深感这一路的不易。 + +希望我的分享能帮助到更多的小伙伴,**我踩过的坑你们不要再踩**。想与大彬交流的话,可以到公众号后台获取大彬的微信~ + +后台回复『 **笔记**』即可领取大彬斩获大厂offer的**面试笔记**。 + +![](http://img.dabin-coder.cn/image/公众号.jpg) diff --git "a/\347\263\273\347\273\237\350\256\276\350\256\241/\346\211\253\347\240\201\347\231\273\345\275\225\345\216\237\347\220\206.md" "b/system-design/\346\211\253\347\240\201\347\231\273\345\275\225\345\216\237\347\220\206.md" similarity index 100% rename from "\347\263\273\347\273\237\350\256\276\350\256\241/\346\211\253\347\240\201\347\231\273\345\275\225\345\216\237\347\220\206.md" rename to "system-design/\346\211\253\347\240\201\347\231\273\345\275\225\345\216\237\347\220\206.md" diff --git "a/\347\263\273\347\273\237\350\256\276\350\256\241/\347\263\273\347\273\237\350\256\276\350\256\241.md" "b/system-design/\347\263\273\347\273\237\350\256\276\350\256\241.md" similarity index 100% rename from "\347\263\273\347\273\237\350\256\276\350\256\241/\347\263\273\347\273\237\350\256\276\350\256\241.md" rename to "system-design/\347\263\273\347\273\237\350\256\276\350\256\241.md" diff --git "a/\347\263\273\347\273\237\350\256\276\350\256\241/\347\263\273\347\273\237\350\256\276\350\256\241old.md" "b/system-design/\347\263\273\347\273\237\350\256\276\350\256\241old.md" similarity index 100% rename from "\347\263\273\347\273\237\350\256\276\350\256\241/\347\263\273\347\273\237\350\256\276\350\256\241old.md" rename to "system-design/\347\263\273\347\273\237\350\256\276\350\256\241old.md" diff --git "a/\345\267\245\345\205\267/docker.md" b/tools/docker-overview.md similarity index 76% rename from "\345\267\245\345\205\267/docker.md" rename to tools/docker-overview.md index 0359ec2..3899f46 100644 --- "a/\345\267\245\345\205\267/docker.md" +++ b/tools/docker-overview.md @@ -1,65 +1,9 @@ - - -**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* - -- [简介](#%E7%AE%80%E4%BB%8B) - - [基本概念](#%E5%9F%BA%E6%9C%AC%E6%A6%82%E5%BF%B5) -- [docker镜像常用命令](#docker%E9%95%9C%E5%83%8F%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4) - - [添加docker仓库位置](#%E6%B7%BB%E5%8A%A0docker%E4%BB%93%E5%BA%93%E4%BD%8D%E7%BD%AE) - - [安装docker服务](#%E5%AE%89%E8%A3%85docker%E6%9C%8D%E5%8A%A1) - - [启动 docker 服务](#%E5%90%AF%E5%8A%A8-docker-%E6%9C%8D%E5%8A%A1) - - [重启docker服务](#%E9%87%8D%E5%90%AFdocker%E6%9C%8D%E5%8A%A1) - - [搜索镜像](#%E6%90%9C%E7%B4%A2%E9%95%9C%E5%83%8F) - - [下载镜像:](#%E4%B8%8B%E8%BD%BD%E9%95%9C%E5%83%8F) - - [列出镜像](#%E5%88%97%E5%87%BA%E9%95%9C%E5%83%8F) - - [删除镜像](#%E5%88%A0%E9%99%A4%E9%95%9C%E5%83%8F) - - [打包镜像](#%E6%89%93%E5%8C%85%E9%95%9C%E5%83%8F) - - [创建镜像](#%E5%88%9B%E5%BB%BA%E9%95%9C%E5%83%8F) - - [推送镜像:](#%E6%8E%A8%E9%80%81%E9%95%9C%E5%83%8F) - - [docker hub](#docker-hub) - - [修改镜像存放位置](#%E4%BF%AE%E6%94%B9%E9%95%9C%E5%83%8F%E5%AD%98%E6%94%BE%E4%BD%8D%E7%BD%AE) -- [docker容器常用命令](#docker%E5%AE%B9%E5%99%A8%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4) - - [新建容器](#%E6%96%B0%E5%BB%BA%E5%AE%B9%E5%99%A8) - - [查看容器](#%E6%9F%A5%E7%9C%8B%E5%AE%B9%E5%99%A8) - - [停止容器](#%E5%81%9C%E6%AD%A2%E5%AE%B9%E5%99%A8) - - [启动容器](#%E5%90%AF%E5%8A%A8%E5%AE%B9%E5%99%A8) - - [重启容器](#%E9%87%8D%E5%90%AF%E5%AE%B9%E5%99%A8) - - [进入容器](#%E8%BF%9B%E5%85%A5%E5%AE%B9%E5%99%A8) - - [删除容器](#%E5%88%A0%E9%99%A4%E5%AE%B9%E5%99%A8) - - [容器日志](#%E5%AE%B9%E5%99%A8%E6%97%A5%E5%BF%97) - - [容器ip](#%E5%AE%B9%E5%99%A8ip) - - [容器启动方式](#%E5%AE%B9%E5%99%A8%E5%90%AF%E5%8A%A8%E6%96%B9%E5%BC%8F) - - [资源占用](#%E8%B5%84%E6%BA%90%E5%8D%A0%E7%94%A8) - - [磁盘使用情况](#%E7%A3%81%E7%9B%98%E4%BD%BF%E7%94%A8%E6%83%85%E5%86%B5) - - [执行容器内部命令](#%E6%89%A7%E8%A1%8C%E5%AE%B9%E5%99%A8%E5%86%85%E9%83%A8%E5%91%BD%E4%BB%A4) - - [网络](#%E7%BD%91%E7%BB%9C) - - [复制文件](#%E5%A4%8D%E5%88%B6%E6%96%87%E4%BB%B6) -- [docker-compose](#docker-compose) - - [安装](#%E5%AE%89%E8%A3%85) - - [常用命令](#%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4) - - [部署多个服务](#%E9%83%A8%E7%BD%B2%E5%A4%9A%E4%B8%AA%E6%9C%8D%E5%8A%A1) - - [定义配置文件](#%E5%AE%9A%E4%B9%89%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6) - - [启动服务](#%E5%90%AF%E5%8A%A8%E6%9C%8D%E5%8A%A1) -- [maven插件构建docker镜像](#maven%E6%8F%92%E4%BB%B6%E6%9E%84%E5%BB%BAdocker%E9%95%9C%E5%83%8F) - - [docker镜像仓库](#docker%E9%95%9C%E5%83%8F%E4%BB%93%E5%BA%93) - - [docker开启远程访问](#docker%E5%BC%80%E5%90%AF%E8%BF%9C%E7%A8%8B%E8%AE%BF%E9%97%AE) - - [docker支持http上传镜像](#docker%E6%94%AF%E6%8C%81http%E4%B8%8A%E4%BC%A0%E9%95%9C%E5%83%8F) - - [重启docker](#%E9%87%8D%E5%90%AFdocker) - - [开放端口](#%E5%BC%80%E6%94%BE%E7%AB%AF%E5%8F%A3) - - [构建docker镜像](#%E6%9E%84%E5%BB%BAdocker%E9%95%9C%E5%83%8F) -- [其他](#%E5%85%B6%E4%BB%96) - - [给nginx增加端口映射](#%E7%BB%99nginx%E5%A2%9E%E5%8A%A0%E7%AB%AF%E5%8F%A3%E6%98%A0%E5%B0%84) - - - ## 简介 Docker是一个开源的应用容器引擎,通过容器可以隔离应用程序的运行时环境(程序运行时依赖的各种库和配置),比虚拟机更轻量(虚拟机在操作系统层面进行隔离)。docker的另一个优点就是build once, run everywhere,只编译一次,就可以在各个平台(windows、linux等)运行。 ### 基本概念 -[docker](https://zhuanlan.zhihu.com/p/23599229) - docker的基本概念: 1. 镜像(image),类似于虚拟机中的镜像,可以理解为可执行程序。 @@ -71,12 +15,13 @@ docker的基本概念: docker的基本命令: -- docker build:我们只需要在dockerfile中指定需要哪些程序、依赖什么样的配置,之后把dockerfile交给“编译器”docker进行“编译”,生成的可执行程序就是image。 +1、docker build:我们只需要在dockerfile中指定需要哪些程序、依赖什么样的配置,之后把dockerfile交给“编译器”docker进行“编译”,生成的可执行程序就是image。 + +2、docker run:运行image,运行起来后就是docker container。 -- docker run:运行image,运行起来后就是docker container。 -- docker pull:到Docker Hub(docker registry)下载别人写好的image。 +3、docker pull:到Docker Hub(docker registry)下载别人写好的image。 -![](../img/docker/docker.jpg) +![](http://img.dabin-coder.cn/image/docker.jpg) > 图片来源:知乎小灰 @@ -265,36 +210,32 @@ Docker官方维护了一个DockerHub的公共仓库,里边包含有很多平 ### 修改镜像存放位置 -1. 查看镜像存放位置: - - ``` - docker info | grep "Docker Root Dir" - Docker Root Dir: /var/lib/docker - ``` - -2. 关闭docker服务: - - ``` - systemctl stop docker - ``` +1、查看镜像存放位置: -3. 原镜像目录移动到目标目录: - - ``` - mv /var/lib/docker /home/data/docker - ``` +``` +docker info | grep "Docker Root Dir" + Docker Root Dir: /var/lib/docker +``` -4. 建立软连接: +2、关闭docker服务: - ``` - ln -s /home/data/docker /var/lib/docker - ``` +``` +systemctl stop docker +``` -5. 再次查看镜像存放位置,发现已经修改。 +3、原镜像目录移动到目标目录: +``` +mv /var/lib/docker /home/data/docker +``` +4、建立软连接: +``` +ln -s /home/data/docker /var/lib/docker +``` +5、再次查看镜像存放位置,发现已经修改。 ## docker容器常用命令 @@ -312,13 +253,13 @@ docker run -p 33055:33055 --name nginx \ # -d nginx:1.18.0 -d表示容器以后台方式运行 ``` -- -p:将宿主机和容器端口进行映射,格式为:宿主机端口:容器端口; -- --name:指定容器名称,之后可以通过容器名称来操作容器; -- -e:设置容器的环境变量,这里设置的是时区; -- -v:将宿主机上的文件挂载到容器上,格式为:宿主机文件目录:容器文件目录; -- -d:表示容器以后台方式运行。终端不会输出任何运行信息; -- -i: 以交互模式运行容器,通常与 -t 同时使用; -- -t: 为容器分配一个终端,通常与 -i 同时使用。 +> - -p:将宿主机和容器端口进行映射,格式为:宿主机端口:容器端口; +> - --name:指定容器名称,之后可以通过容器名称来操作容器; +> - -e:设置容器的环境变量,这里设置的是时区; +> - -v:将宿主机上的文件挂载到容器上,格式为:宿主机文件目录:容器文件目录; +> - -d:表示容器以后台方式运行。终端不会输出任何运行信息; +> - -i: 以交互模式运行容器,通常与 -t 同时使用; +> - -t: 为容器分配一个终端,通常与 -i 同时使用。 使用-it,此时如果使用exit退出,则容器的状态处于Exit,而不是后台运行。如果想让容器一直运行,而不是停止,可以使用快捷键 ctrl+p ctrl+q 退出,此时容器的状态为Up。 @@ -328,11 +269,11 @@ docker run -p 33055:33055 --name nginx \ docker run --name mysql4blog -e MYSQL_ROOT_PASSWORD=123456 -p 3307:3307 -d mysql:8.0.20 ``` -- `-name`: 给新创建的容器命名,此处命名为`mysql4blog` -- `-e`: 配置信息,此处配置MySQL的 root 用户的登录密码 -- `-p`: 端口映射,此处映射主机的3307端口到容器的3307端口 -- -d: 成功启动同期后输出容器的完整ID -- 最后一个`mysql:8.0.20`指的是`mysql`镜像 +> - `-name`: 给新创建的容器命名,此处命名为`mysql4blog` +> - `-e`: 配置信息,此处配置MySQL的 root 用户的登录密码 +> - `-p`: 端口映射,此处映射主机的3307端口到容器的3307端口 +> - -d: 成功启动同期后输出容器的完整ID +> - 最后一个`mysql:8.0.20`指的是`mysql`镜像 ### 查看容器 @@ -386,17 +327,17 @@ docker attach container_name/container_id 使用exit退出命令行之后,重新进入容器: -1. 先查询容器id:`docker inspect --format "{{.State.Pid}}" nginx` +1、先查询容器id:`docker inspect --format "{{.State.Pid}}" nginx` -2. 根据查到的容器id进入容器:`nsenter --target 28487 --mount --uts --ipc --net --pid` +2、根据查到的容器id进入容器:`nsenter --target 28487 --mount --uts --ipc --net --pid` - ``` - [root@VM_0_7_centos ~]# docker inspect --format "{{.State.Pid}}" nginx - 28487 - [root@VM_0_7_centos ~]# nsenter --target 28487 --mount --uts --ipc --net --pid - mesg: ttyname failed: No such device - root@b217a35fc808:/# ls -l - ``` +``` +[root@VM_0_7_centos ~]# docker inspect --format "{{.State.Pid}}" nginx +28487 +[root@VM_0_7_centos ~]# nsenter --target 28487 --mount --uts --ipc --net --pid +mesg: ttyname failed: No such device +root@b217a35fc808:/# ls -l +``` ### 删除容器 @@ -645,8 +586,6 @@ docker-compose up -d ## maven插件构建docker镜像 - - ### docker镜像仓库 服务器创建docker镜像仓库docker registry: @@ -838,3 +777,6 @@ docker start nginx +## 参考内容 + +[只要一小时,零基础入门Docker](https://zhuanlan.zhihu.com/p/23599229) \ No newline at end of file diff --git "a/\345\267\245\345\205\267/progit2.md" b/tools/git-overview.md similarity index 85% rename from "\345\267\245\345\205\267/progit2.md" rename to tools/git-overview.md index a79a027..9bf8be0 100644 --- "a/\345\267\245\345\205\267/progit2.md" +++ b/tools/git-overview.md @@ -1,74 +1,3 @@ - - - - -- [Git 简介](#git-%E7%AE%80%E4%BB%8B) - - [Git工作流程](#git%E5%B7%A5%E4%BD%9C%E6%B5%81%E7%A8%8B) - - [存储原理](#%E5%AD%98%E5%82%A8%E5%8E%9F%E7%90%86) - - [Git 快照](#git-%E5%BF%AB%E7%85%A7) - - [三种状态](#%E4%B8%89%E7%A7%8D%E7%8A%B6%E6%80%81) - - [配置](#%E9%85%8D%E7%BD%AE) - - [获取帮助](#%E8%8E%B7%E5%8F%96%E5%B8%AE%E5%8A%A9) -- [Git 基础](#git-%E5%9F%BA%E7%A1%80) - - [获取 Git 仓库](#%E8%8E%B7%E5%8F%96-git-%E4%BB%93%E5%BA%93) - - [文件状态](#%E6%96%87%E4%BB%B6%E7%8A%B6%E6%80%81) - - [配置别名](#%E9%85%8D%E7%BD%AE%E5%88%AB%E5%90%8D) - - [工作区](#%E5%B7%A5%E4%BD%9C%E5%8C%BA) - - [暂存区](#%E6%9A%82%E5%AD%98%E5%8C%BA) - - [提交](#%E6%8F%90%E4%BA%A4) - - [修改commit信息](#%E4%BF%AE%E6%94%B9commit%E4%BF%A1%E6%81%AF) - - [查看提交历史](#%E6%9F%A5%E7%9C%8B%E6%8F%90%E4%BA%A4%E5%8E%86%E5%8F%B2) - - [版本回退](#%E7%89%88%E6%9C%AC%E5%9B%9E%E9%80%80) - - [stash](#stash) - - [rm和mv](#rm%E5%92%8Cmv) - - [忽略文件](#%E5%BF%BD%E7%95%A5%E6%96%87%E4%BB%B6) - - [skip-worktree和assume-unchanged](#skip-worktree%E5%92%8Cassume-unchanged) - - [远程仓库](#%E8%BF%9C%E7%A8%8B%E4%BB%93%E5%BA%93) - - [查看远程仓库](#%E6%9F%A5%E7%9C%8B%E8%BF%9C%E7%A8%8B%E4%BB%93%E5%BA%93) - - [添加远程仓库](#%E6%B7%BB%E5%8A%A0%E8%BF%9C%E7%A8%8B%E4%BB%93%E5%BA%93) - - [修改远程仓库](#%E4%BF%AE%E6%94%B9%E8%BF%9C%E7%A8%8B%E4%BB%93%E5%BA%93) - - [pull 和 fetch](#pull-%E5%92%8C-fetch) - - [本地仓库上传git服务器](#%E6%9C%AC%E5%9C%B0%E4%BB%93%E5%BA%93%E4%B8%8A%E4%BC%A0git%E6%9C%8D%E5%8A%A1%E5%99%A8) - - [推送到远程仓库](#%E6%8E%A8%E9%80%81%E5%88%B0%E8%BF%9C%E7%A8%8B%E4%BB%93%E5%BA%93) - - [查看远程仓库](#%E6%9F%A5%E7%9C%8B%E8%BF%9C%E7%A8%8B%E4%BB%93%E5%BA%93-1) - - [远程仓库移除和命名](#%E8%BF%9C%E7%A8%8B%E4%BB%93%E5%BA%93%E7%A7%BB%E9%99%A4%E5%92%8C%E5%91%BD%E5%90%8D) - - [标签](#%E6%A0%87%E7%AD%BE) - - [创建标签](#%E5%88%9B%E5%BB%BA%E6%A0%87%E7%AD%BE) - - [附注标签](#%E9%99%84%E6%B3%A8%E6%A0%87%E7%AD%BE) - - [轻量标签](#%E8%BD%BB%E9%87%8F%E6%A0%87%E7%AD%BE) - - [推送标签](#%E6%8E%A8%E9%80%81%E6%A0%87%E7%AD%BE) - - [后期打标签](#%E5%90%8E%E6%9C%9F%E6%89%93%E6%A0%87%E7%AD%BE) - - [共享标签](#%E5%85%B1%E4%BA%AB%E6%A0%87%E7%AD%BE) - - [检出标签](#%E6%A3%80%E5%87%BA%E6%A0%87%E7%AD%BE) - - [git 别名](#git-%E5%88%AB%E5%90%8D) -- [git 分支](#git-%E5%88%86%E6%94%AF) - - [分支创建](#%E5%88%86%E6%94%AF%E5%88%9B%E5%BB%BA) - - [分支切换](#%E5%88%86%E6%94%AF%E5%88%87%E6%8D%A2) - - [分支合并](#%E5%88%86%E6%94%AF%E5%90%88%E5%B9%B6) - - [合并冲突](#%E5%90%88%E5%B9%B6%E5%86%B2%E7%AA%81) - - [rebase](#rebase) - - [删除分支](#%E5%88%A0%E9%99%A4%E5%88%86%E6%94%AF) - - [分支管理](#%E5%88%86%E6%94%AF%E7%AE%A1%E7%90%86) - - [远程分支](#%E8%BF%9C%E7%A8%8B%E5%88%86%E6%94%AF) - - [推送](#%E6%8E%A8%E9%80%81) - - [跟踪分支](#%E8%B7%9F%E8%B8%AA%E5%88%86%E6%94%AF) - - [fetch和pull](#fetch%E5%92%8Cpull) - - [删除远程分支](#%E5%88%A0%E9%99%A4%E8%BF%9C%E7%A8%8B%E5%88%86%E6%94%AF) - - [创建远程分支](#%E5%88%9B%E5%BB%BA%E8%BF%9C%E7%A8%8B%E5%88%86%E6%94%AF) - - [cherry-pick](#cherry-pick) - - [cherry-pick与rebase的区别](#cherry-pick%E4%B8%8Erebase%E7%9A%84%E5%8C%BA%E5%88%AB) - - [补丁](#%E8%A1%A5%E4%B8%81) - - - -> 首先给大家分享一个github仓库,上面放了**200多本经典的计算机书籍**,包括C语言、C++、Java、Python、前端、数据库、操作系统、计算机网络、数据结构和算法、机器学习等,可以star一下,下次找书直接在上面搜索,仓库持续更新中~ -> -> github地址:https://github.com/Tyson0314/java-books -> -> 如果github访问不了,可以访问gitee仓库。 -> -> gitee地址:https://gitee.com/tysondai/java-books - # Git 简介 Git 是一个开源的分布式版本控制系统,可以有效、快速的进行项目版本管理。Git 是 Linus Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。 @@ -156,7 +85,7 @@ git clone https://github.com/... 查看文件状态:`git status` -![git生命周期](http://img.dabin-coder.cn/image/git生命周期.png) +![](http://img.dabin-coder.cn/image/git生命周期.png) > 图片来源:`https://img2018.cnblogs.com/blog/1252910/201907/1252910-20190726163854195-886320537.png` diff --git a/tools/linux-overview.md b/tools/linux-overview.md new file mode 100644 index 0000000..f3cd728 --- /dev/null +++ b/tools/linux-overview.md @@ -0,0 +1,647 @@ +# 基本操作 + +## Linux关机,重启 + +``` +#关机 +shutdown -h now + +#重启 +shutdown -r now +``` + +## 查看系统,CPU信息 + +``` +#查看系统内核信息 +uname -a + +#查看系统内核版本 +cat /proc/version + +#查看当前用户环境变量 +env + +cat /proc/cpuinfo + +#查看有几个逻辑cpu, 包括cpu型号 +cat /proc/cpuinfo | grep name | cut -f2 -d: | uniq -c + +#查看有几颗cpu,每颗分别是几核 +cat /proc/cpuinfo | grep physical | uniq -c + +#查看当前CPU运行在32bit还是64bit模式下, 如果是运行在32bit下也不代表CPU不支持64bit +getconf LONG_BIT + +#结果大于0, 说明支持64bit计算. lm指long mode, 支持lm则是64bit +cat /proc/cpuinfo | grep flags | grep ' lm ' | wc -l +``` + +## 建立软连接 + +``` +ln -s /usr/local/jdk1.8/ jdk +``` + +## rpm相关 + +``` +#查看是否通过rpm安装了该软件 +rpm -qa | grep 软件名 +``` + +## sshkey + +``` +#创建sshkey +ssh-keygen -t rsa -C your_email@example.com + +#id_rsa.pub 的内容拷贝到要控制的服务器的 home/username/.ssh/authorized_keys 中,如果没有则新建(.ssh权限为700, authorized_keys权限为600) +``` + +## 命令重命名 + +``` +#在各个用户的.bash_profile中添加重命名配置 +alias ll='ls -alF' +``` + +## 同步服务器时间 + +``` +sudo ntpdate -u ntp.api.bz +``` + +## 后台运行命令 + +``` +#后台运行,并且有nohup.out输出 +nohup xxx & + +#后台运行, 不输出任何日志 +nohup xxx > /dev/null & + +#后台运行, 并将错误信息做标准输出到日志中 +nohup xxx >out.log 2>&1 & +``` + +## 强制活动用户退出 + +``` +#命令来完成强制活动用户退出.其中TTY表示终端名称 +pkill -kill -t [TTY] +``` + +## 查看命令路径 + +``` +which <命令> +``` + +## 查看进程所有打开最大fd数 + +``` +ulimit -n +``` + +## 配置dns + +``` +vim /etc/resolv.conf +``` + +## nslookup,查看域名路由表 + +``` +nslookup google.com +``` + +## last, 最近登录信息列表 + +``` +#最近登录的5个账号 +last -n 5 +``` + +## 设置固定ip + +``` +ifconfig em1 192.168.5.177 netmask 255.255.255.0 +``` + +## 查看进程内加载的环境变量 + +``` +#也可以去 cd /proc 目录下, 查看进程内存中加载的东西 +ps eww -p XXXXX(进程号) +``` + +## 查看进程树找到服务器进程 + +``` +ps auwxf +``` + +## 查看进程启动路径 + +``` +cd /proc/xxx(进程号) +ls -all +#cwd对应的是启动路径 +``` + +## 添加用户, 配置sudo权限 + +``` +#新增用户 +useradd 用户名 +passwd 用户名 + +#增加sudo权限 +vim /etc/sudoers +#修改文件里面的 +#root ALL=(ALL) ALL +#用户名 ALL=(ALL) ALL +``` + +## 强制关闭进程名包含xxx的所有进程 + +``` +ps aux|grep xxx | grep -v grep | awk '{print $2}' | xargs kill -9 +``` + +# 磁盘,文件,目录相关操作 + +## vim操作 + +``` +#normal模式下 g表示全局, x表示查找的内容, y表示替换后的内容 +:%s/x/y/g + +#normal模式下 +0 #光标移到行首(数字0) +$ #光标移至行尾 +shift + g #跳到文件最后 +gg #跳到文件头 + +#显示行号 +:set nu + +#去除行号 +:set nonu + +#检索 +/xxx(检索内容) #从头检索, 按n查找下一个 +?xxx(检索内容) #从尾部检索 +``` + +## 打开只读文件,修改后需要保存时(不用切换用户即可保存的方式) + +``` +#在normal模式下 +:w !sudo tee % +``` + +## 查看磁盘, 文件目录基本信息 + +``` +#查看磁盘挂载情况 +mount + +#查看磁盘分区信息 +df + +#查看目录及子目录大小 +du -H -h + +#查看当前目录下各个文件, 文件夹占了多少空间, 不会递归 +du -sh * +``` + +## wc命令 + +``` +#查看文件里有多少行 +wc -l filename + +#看文件里有多少个word +wc -w filename + +#文件里最长的那一行是多少个字 +wc -L filename + +#统计字节数 +wc -c +``` + +## 常用压缩, 解压缩命令 + +### 压缩命令 + +``` +tar czvf xxx.tar 压缩目录 + +zip -r xxx.zip 压缩目录 +``` + +### 解压缩命令 + +``` +tar zxvf xxx.tar + +#解压到指定文件夹 +tar zxvf xxx.tar -C /xxx/yyy/ + +unzip xxx.zip +``` + +## 变更文件所属用户, 用户组 + +``` +chown eagleye.eagleye xxx.log +``` + +## cp, scp, mkdir + +``` +#复制 +cp xxx.log + +#复制并强制覆盖同名文件 +cp -f xxx.log + +#复制文件夹 +cp -r xxx(源文件夹) yyy(目标文件夹) + +#远程复制 +scp -P ssh端口 username@10.10.10.101:/home/username/xxx /home/xxx + +#级联创建目录 +mkdir -p /xxx/yyy/zzz + +#批量创建文件夹, 会在test,main下都创建java, resources文件夹 +mkdir -p src/{test,main}/{java,resources} +``` + +## 比较两个文件 + +``` +diff -u 1.txt 2.txt +``` + +## 日志输出的字节数,可以用作性能测试 + +``` +#如果做性能测试, 可以每执行一次, 往日志里面输出 “.” , 这样日志中的字节数就是实际的性能测试运行的次数, 还可以看见实时速率. +tail -f xxx.log | pv -bt +``` + +## 查看, 去除特殊字符 + +``` +#查看特殊字符 +cat -v xxx.sh + +#去除特殊字符 +sed -i 's/^M//g’ env.sh 去除文件的特殊字符, 比如^M: 需要这样输入: ctrl+v+enter +``` + +## 处理因系统原因引起的文件中特殊字符的问题 + +``` +#可以转换为该系统下的文件格式 +cat file.sh > file.sh_bak + +#先将file.sh中文件内容复制下来然后运行, 然后粘贴内容, 最后ctrl + d 保存退出 +cat > file1.sh + +#在vim中通过如下设置文件编码和文件格式 +:set fileencodings=utf-8 ,然后 w (存盘)一下即可转化为 utf8 格式, +:set fileformat=unix + +#在mac下使用dos2unix进行文件格式化 +find . -name "*.sh" | xargs dos2unix +``` + +## tee, 重定向的同时输出到屏幕 + +``` +awk ‘{print $0}’ xxx.log | tee test.log +``` + +# 检索相关 + +## grep + +``` +#反向匹配, 查找不包含xxx的内容 +grep -v xxx + +#排除所有空行 +grep -v '^/pre> + +#返回结果 2,则说明第二行是空行 +grep -n “^$” 111.txt + +#查询以abc开头的行 +grep -n “^abc” 111.txt + +#同时列出该词语出现在文章的第几行 +grep 'xxx' -n xxx.log + +#计算一下该字串出现的次数 +grep 'xxx' -c xxx.log + +#比对的时候,不计较大小写的不同 +grep 'xxx' -i xxx.log +``` + +## awk + +``` +#以':' 为分隔符,如果第五域有user则输出该行 +awk -F ':' '{if ($5 ~ /user/) print $0}' /etc/passwd + +#统计单个文件中某个字符(串)(中文无效)出现的次数 +awk -v RS='character' 'END {print --NR}' xxx.txt +``` + +## find检索命令 + +``` +#在目录下找后缀是.mysql的文件 +find /home/eagleye -name '*.mysql' -print + +#会从 /usr 目录开始往下找,找最近3天之内存取过的文件。 +find /usr -atime 3 –print + +#会从 /usr 目录开始往下找,找最近5天之内修改过的文件。 +find /usr -ctime 5 –print + +#会从 /doc 目录开始往下找,找jacky 的、文件名开头是 j的文件。 +find /doc -user jacky -name 'j*' –print + +#会从 /doc 目录开始往下找,找寻文件名是 ja 开头或者 ma开头的文件。 +find /doc \( -name 'ja*' -o- -name 'ma*' \) –print + +#会从 /doc 目录开始往下找,找到凡是文件名结尾为 bak的文件,把它删除掉。-exec 选项是执行的意思,rm 是删除命令,{ } 表示文件名,“\;”是规定的命令结尾。 +find /doc -name '*bak' -exec rm {} \; +``` + +# 网络相关 + +## 查看什么进程使用了该端口 + +``` +lsof -i:port +``` + +## 获取本机ip地址 + +``` +/sbin/ifconfig -a|grep inet|grep -v 127.0.0.1|grep -v inet6|awk '{print $2}'|tr -d "addr:" +``` + +## iptables + +``` +#查看iptables状态 +service iptables status + +#要封停一个ip +iptables -I INPUT -s ***.***.***.*** -j DROP + +#要解封一个IP,使用下面这条命令: +iptables -D INPUT -s ***.***.***.*** -j DROP + +备注: 参数-I是表示Insert(添加),-D表示Delete(删除)。后面跟的是规则,INPUT表示入站,***.***.***.***表示要封停的IP,DROP表示放弃连接。 + +#开启9090端口的访问 +/sbin/iptables -I INPUT -p tcp --dport 9090 -j ACCEPT + +#防火墙开启、关闭、重启 +/etc/init.d/iptables status +/etc/init.d/iptables start +/etc/init.d/iptables stop +/etc/init.d/iptables restart +``` + +## nc命令, tcp调试利器 + +``` +#给某一个endpoint发送TCP请求,就将data的内容发送到对端 +nc 192.168.0.11 8000 < data.txt + +#nc可以当做服务器,监听某个端口号,把某一次请求的内容存储到received_data里 +nc -l 8000 > received_data + +#上边只监听一次,如果多次可以加上-k参数 +nc -lk 8000 +``` + +## tcpdump + +``` +#dump出本机12301端口的tcp包 +tcpdump -i em1 tcp port 12301 -s 1500 -w abc.pcap +``` + +## 跟踪网络路由路径 + +``` +#traceroute默认使用udp方式, 如果是-I则改成icmp方式 +traceroute -I www.163.com + +#从ttl第3跳跟踪 +traceroute -M 3 www.163.com + +#加上端口跟踪 +traceroute -p 8080 192.168.10.11 +``` + +## ss + +``` +#显示本地打开的所有端口 +ss -l + +#显示每个进程具体打开的socket +ss -pl + +#显示所有tcp socket +ss -t -a + +#显示所有的UDP Socekt +ss -u -a + +#显示所有已建立的SMTP连接 +ss -o state established '( dport = :smtp or sport = :smtp )' + +#显示所有已建立的HTTP连接 +ss -o state established '( dport = :http or sport = :http )' + +找出所有连接X服务器的进程 +ss -x src /tmp/.X11-unix/* + +列出当前socket统计信息 +ss -s + +解释:netstat是遍历/proc下面每个PID目录,ss直接读/proc/net下面的统计信息。所以ss执行的时候消耗资源以及消耗的时间都比netstat少很多 +``` + +## netstat + +``` +#输出每个ip的连接数,以及总的各个状态的连接数 +netstat -n | awk '/^tcp/ {n=split($(NF-1),array,":");if(n<=2)++S[array[(1)]];else++S[array[(4)]];++s[$NF];++N} END {for(a in S){printf("%-20s %s\n", a, S[a]);++I}printf("%-20s %s\n","TOTAL_IP",I);for(a in s) printf("%-20s %s\n",a, s[a]);printf("%-20s %s\n","TOTAL_LINK",N);}' + +#统计所有连接状态, +#CLOSED:无连接是活动的或正在进行 +#LISTEN:服务器在等待进入呼叫 +#SYN_RECV:一个连接请求已经到达,等待确认 +#SYN_SENT:应用已经开始,打开一个连接 +#ESTABLISHED:正常数据传输状态 +#FIN_WAIT1:应用说它已经完成 +#FIN_WAIT2:另一边已同意释放 +#ITMED_WAIT:等待所有分组死掉 +#CLOSING:两边同时尝试关闭 +#TIME_WAIT:主动关闭连接一端还没有等到另一端反馈期间的状态 +#LAST_ACK:等待所有分组死掉 +netstat -n | awk '/^tcp/ {++state[$NF]} END {for(key in state) print key,"\t",state[key]}' + +#查找较多time_wait连接 +netstat -n|grep TIME_WAIT|awk '{print $5}'|sort|uniq -c|sort -rn|head -n20 +``` + +# 监控linux性能命令 + +## top + +``` +按大写的 F 或 O 键,然后按 a-z 可以将进程按照相应的列进行排序, 然后回车。而大写的 R 键可以将当前的排序倒转 +``` + +| 列名 | 含义 | +| :------ | :----------------------------------------------------------- | +| PID | 进程id | +| PPID | 父进程id | +| RUSER | Real user name | +| UID | 进程所有者的用户id | +| USER | 进程所有者的用户名 | +| GROUP | 进程所有者的组名 | +| TTY | 启动进程的终端名。不是从终端启动的进程则显示为 ? | +| PR | 优先级 | +| NI | nice值。负值表示高优先级,正值表示低优先级 | +| P | 最后使用的CPU,仅在多CPU环境下有意义 | +| %CPU | 上次更新到现在的CPU时间占用百分比 | +| TIME | 进程使用的CPU时间总计,单位秒 | +| TIME+ | 进程使用的CPU时间总计,单位1/100秒 | +| %MEM | 进程使用的物理内存百分比 | +| VIRT | 进程使用的虚拟内存总量,单位kb。VIRT=SWAP+RES | +| SWAP | 进程使用的虚拟内存中,被换出的大小,单位kb。 | +| RES | 进程使用的、未被换出的物理内存大小,单位kb。RES=CODE+DATA | +| CODE | 可执行代码占用的物理内存大小,单位kb | +| DATA | 可执行代码以外的部分(数据段+栈)占用的物理内存大小,单位kb | +| SHR | 共享内存大小,单位kb | +| nFLT | 页面错误次数 | +| nDRT | 最后一次写入到现在,被修改过的页面数。 | +| S | 进程状态。D=不可中断的睡眠状态,R=运行,S=睡眠,T=跟踪/停止,Z=僵尸进程 | +| COMMAND | 命令名/命令行 | +| WCHAN | 若该进程在睡眠,则显示睡眠中的系统函数名 | +| Flags | 任务标志,参考 sched.h | + +## dmesg,查看系统日志 + +``` +dmesg +``` + +## iostat,磁盘IO情况监控 + +``` +iostat -xz 1 + +#r/s, w/s, rkB/s, wkB/s:分别表示每秒读写次数和每秒读写数据量(千字节)。读写量过大,可能会引起性能问题。 +#await:IO操作的平均等待时间,单位是毫秒。这是应用程序在和磁盘交互时,需要消耗的时间,包括IO等待和实际操作的耗时。如果这个数值过大,可能是硬件设备遇到了瓶颈或者出现故障。 +#avgqu-sz:向设备发出的请求平均数量。如果这个数值大于1,可能是硬件设备已经饱和(部分前端硬件设备支持并行写入)。 +#%util:设备利用率。这个数值表示设备的繁忙程度,经验值是如果超过60,可能会影响IO性能(可以参照IO操作平均等待时间)。如果到达100%,说明硬件设备已经饱和。 +#如果显示的是逻辑设备的数据,那么设备利用率不代表后端实际的硬件设备已经饱和。值得注意的是,即使IO性能不理想,也不一定意味这应用程序性能会不好,可以利用诸如预读取、写缓存等策略提升应用性能。 +``` + +## free,内存使用情况 + +``` +free -m + +eg: + + total used free shared buffers cached +Mem: 1002 769 232 0 62 421 +-/+ buffers/cache: 286 715 +Swap: 1153 0 1153 + +第一部分Mem行: +total 内存总数: 1002M +used 已经使用的内存数: 769M +free 空闲的内存数: 232M +shared 当前已经废弃不用,总是0 +buffers Buffer 缓存内存数: 62M +cached Page 缓存内存数:421M + +关系:total(1002M) = used(769M) + free(232M) + +第二部分(-/+ buffers/cache): +(-buffers/cache) used内存数:286M (指的第一部分Mem行中的used – buffers – cached) +(+buffers/cache) free内存数: 715M (指的第一部分Mem行中的free + buffers + cached) + +可见-buffers/cache反映的是被程序实实在在吃掉的内存,而+buffers/cache反映的是可以挪用的内存总数. + +第三部分是指交换分区 +``` + +## sar,查看网络吞吐状态 + +``` +#sar命令在这里可以查看网络设备的吞吐率。在排查性能问题时,可以通过网络设备的吞吐量,判断网络设备是否已经饱和 +sar -n DEV 1 + +# +#sar命令在这里用于查看TCP连接状态,其中包括: +#active/s:每秒本地发起的TCP连接数,既通过connect调用创建的TCP连接; +#passive/s:每秒远程发起的TCP连接数,即通过accept调用创建的TCP连接; +#retrans/s:每秒TCP重传数量; +#TCP连接数可以用来判断性能问题是否由于建立了过多的连接,进一步可以判断是主动发起的连接,还是被动接受的连接。TCP重传可能是因为网络环境恶劣,或者服务器压力过大导致丢包 +sar -n TCP,ETCP 1 +``` + +## vmstat, 给定时间监控CPU使用率, 内存使用, 虚拟内存交互, IO读写 + +``` +#2表示每2秒采集一次状态信息, 1表示只采集一次(忽略既是一直采集) +vmstat 2 1 + +eg: +r b swpd free buff cache si so bi bo in cs us sy id wa +1 0 0 3499840 315836 3819660 0 0 0 1 2 0 0 0 100 0 +0 0 0 3499584 315836 3819660 0 0 0 0 88 158 0 0 100 0 +0 0 0 3499708 315836 3819660 0 0 0 2 86 162 0 0 100 0 +0 0 0 3499708 315836 3819660 0 0 0 10 81 151 0 0 100 0 +1 0 0 3499732 315836 3819660 0 0 0 2 83 154 0 0 100 0 +``` + +- r 表示运行队列(就是说多少个进程真的分配到CPU),我测试的服务器目前CPU比较空闲,没什么程序在跑,当这个值超过了CPU数目,就会出现CPU瓶颈了。这个也和top的负载有关系,一般负载超过了3就比较高,超过了5就高,超过了10就不正常了,服务器的状态很危险。top的负载类似每秒的运行队列。如果运行队列过大,表示你的CPU很繁忙,一般会造成CPU使用率很高。 +- b 表示阻塞的进程,这个不多说,进程阻塞,大家懂的。 +- swpd 虚拟内存已使用的大小,如果大于0,表示你的机器物理内存不足了,如果不是程序内存泄露的原因,那么你该升级内存了或者把耗内存的任务迁移到其他机器。 +- free 空闲的物理内存的大小,我的机器内存总共8G,剩余3415M。 +- buff Linux/Unix系统是用来存储,目录里面有什么内容,权限等的缓存,我本机大概占用300多M +- cache cache直接用来记忆我们打开的文件,给文件做缓冲,我本机大概占用300多M(这里是Linux/Unix的聪明之处,把空闲的物理内存的一部分拿来做文件和目录的缓存,是为了提高 程序执行的性能,当程序使用内存时,buffer/cached会很快地被使用。) +- si 每秒从磁盘读入虚拟内存的大小,如果这个值大于0,表示物理内存不够用或者内存泄露了,要查找耗内存进程解决掉。我的机器内存充裕,一切正常。 +- so 每秒虚拟内存写入磁盘的大小,如果这个值大于0,同上。 +- bi 块设备每秒接收的块数量,这里的块设备是指系统上所有的磁盘和其他块设备,默认块大小是1024byte,我本机上没什么IO操作,所以一直是0,但是我曾在处理拷贝大量数据(2-3T)的机器上看过可以达到140000/s,磁盘写入速度差不多140M每秒 +- bo 块设备每秒发送的块数量,例如我们读取文件,bo就要大于0。bi和bo一般都要接近0,不然就是IO过于频繁,需要调整。 +- in 每秒CPU的中断次数,包括时间中断 +- cs 每秒上下文切换次数,例如我们调用系统函数,就要进行上下文切换,线程的切换,也要进程上下文切换,这个值要越小越好,太大了,要考虑调低线程或者进程的数目,例如在apache和nginx这种web服务器中,我们一般做性能测试时会进行几千并发甚至几万并发的测试,选择web服务器的进程可以由进程或者线程的峰值一直下调,压测,直到cs到一个比较小的值,这个进程和线程数就是比较合适的值了。系统调用也是,每次调用系统函数,我们的代码就会进入内核空间,导致上下文切换,这个是很耗资源,也要尽量避免频繁调用系统函数。上下文切换次数过多表示你的CPU大部分浪费在上下文切换,导致CPU干正经事的时间少了,CPU没有充分利用,是不可取的。 +- us 用户CPU时间,我曾经在一个做加密解密很频繁的服务器上,可以看到us接近100,r运行队列达到80(机器在做压力测试,性能表现不佳)。 +- sy 系统CPU时间,如果太高,表示系统调用时间长,例如是IO操作频繁。 +- id 空闲 CPU时间,一般来说,id + us + sy = 100,一般我认为id是空闲CPU使用率,us是用户CPU使用率,sy是系统CPU使用率。 +- wt 等待IO CPU时间。 \ No newline at end of file diff --git "a/\345\267\245\345\205\267/Maven\345\256\236\346\210\230.md" b/tools/maven-overview.md similarity index 87% rename from "\345\267\245\345\205\267/Maven\345\256\236\346\210\230.md" rename to tools/maven-overview.md index a8255db..3709059 100644 --- "a/\345\267\245\345\205\267/Maven\345\256\236\346\210\230.md" +++ b/tools/maven-overview.md @@ -1,44 +1,3 @@ - - - -- [简介](#%E7%AE%80%E4%BB%8B) - - [配置](#%E9%85%8D%E7%BD%AE) -- [入门](#%E5%85%A5%E9%97%A8) - - [编写测试代码](#%E7%BC%96%E5%86%99%E6%B5%8B%E8%AF%95%E4%BB%A3%E7%A0%81) - - [添加 junit 依赖](#%E6%B7%BB%E5%8A%A0-junit-%E4%BE%9D%E8%B5%96) - - [编译](#%E7%BC%96%E8%AF%91) - - [测试代码](#%E6%B5%8B%E8%AF%95%E4%BB%A3%E7%A0%81) - - [执行测试](#%E6%89%A7%E8%A1%8C%E6%B5%8B%E8%AF%95) - - [`mvn clean test`](#mvn-clean-test) - - [打包和安装](#%E6%89%93%E5%8C%85%E5%92%8C%E5%AE%89%E8%A3%85) -- [依赖](#%E4%BE%9D%E8%B5%96) - - [依赖范围 scope](#%E4%BE%9D%E8%B5%96%E8%8C%83%E5%9B%B4-scope) - - [传递性依赖](#%E4%BC%A0%E9%80%92%E6%80%A7%E4%BE%9D%E8%B5%96) - - [排除依赖](#%E6%8E%92%E9%99%A4%E4%BE%9D%E8%B5%96) - - [优化依赖](#%E4%BC%98%E5%8C%96%E4%BE%9D%E8%B5%96) -- [仓库](#%E4%BB%93%E5%BA%93) - - [本地仓库](#%E6%9C%AC%E5%9C%B0%E4%BB%93%E5%BA%93) - - [远程仓库](#%E8%BF%9C%E7%A8%8B%E4%BB%93%E5%BA%93) - - [认证和部署](#%E8%AE%A4%E8%AF%81%E5%92%8C%E9%83%A8%E7%BD%B2) - - [镜像](#%E9%95%9C%E5%83%8F) -- [生命周期](#%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F) - - [三套生命周期](#%E4%B8%89%E5%A5%97%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F) - - [命令行与生命周期](#%E5%91%BD%E4%BB%A4%E8%A1%8C%E4%B8%8E%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F) -- [插件](#%E6%8F%92%E4%BB%B6) - - [内置绑定](#%E5%86%85%E7%BD%AE%E7%BB%91%E5%AE%9A) - - [自定义绑定](#%E8%87%AA%E5%AE%9A%E4%B9%89%E7%BB%91%E5%AE%9A) - - [命令行插件配置](#%E5%91%BD%E4%BB%A4%E8%A1%8C%E6%8F%92%E4%BB%B6%E9%85%8D%E7%BD%AE) - - [插件全局配置](#%E6%8F%92%E4%BB%B6%E5%85%A8%E5%B1%80%E9%85%8D%E7%BD%AE) -- [聚合](#%E8%81%9A%E5%90%88) -- [继承](#%E7%BB%A7%E6%89%BF) - - [依赖管理](#%E4%BE%9D%E8%B5%96%E7%AE%A1%E7%90%86) - - [import 导入依赖管理](#import-%E5%AF%BC%E5%85%A5%E4%BE%9D%E8%B5%96%E7%AE%A1%E7%90%86) - - [插件管理](#%E6%8F%92%E4%BB%B6%E7%AE%A1%E7%90%86) -- [测试](#%E6%B5%8B%E8%AF%95) - - [跳过测试](#%E8%B7%B3%E8%BF%87%E6%B5%8B%E8%AF%95) - - - ## 简介 Maven 是强大的构建工具,能够帮我们自动化构建过程--清理、编译、测试、打包和部署。比如测试,我们无需告诉 maven 如何去测试,只需遵循 maven 的约定编写好测试用例,当我们运行构建的时候,这些测试就会自动运行。 @@ -190,13 +149,13 @@ maven 在编译、测试和运行项目时会使用不同的 classpath(编译c spring-core 是 account 的第一直接依赖,common-logging 是 spring-core 的第二直接依赖,common-logging 是 account 的传递性依赖。第一直接依赖的范围和第二直接依赖的范围共同决定了传递性依赖的范围。下表左边是第一直接依赖的范围,上面一行是第二直接依赖的范围,中间部分是传递依赖的范围 -![依赖范围和传递性依赖](https://img2018.cnblogs.com/blog/1252910/201907/1252910-20190705222236595-1506009657.png) +![](http://img.dabin-coder.cn/image/传递性依赖.png) ### 排除依赖 传递性依赖可能会带来一些问题,像引入一些类库的 SNAPSHOT 版本,会影响到当前项目的稳定性。此时可以通过 exclusions 元素声明排除传递性依赖,exclusions 元素可以包含一个或多个 exclusion 元素,因此可以排除多个传递性依赖。声明 exclusion 时只需要 groupId 和 artifactId,而不需要 version 元素。 -![排除依赖](https://img2018.cnblogs.com/blog/1252910/201907/1252910-20190706165424929-1192438533.png) +![](http://img.dabin-coder.cn/image/排查依赖.png) ### 优化依赖 @@ -224,7 +183,7 @@ spring-core 是 account 的第一直接依赖,common-logging 是 spring-core 当默认的中央仓库无法满足项目需要,可以通过 repositories 元素在 POM 中配置远程仓库。maven 中央仓库 id 为 central,若其他仓库 id 命名为 central,则会覆盖中央仓库的配置。 -![使用Jboss maven仓库](https://img2018.cnblogs.com/blog/1252910/201907/1252910-20190706072227846-1642299607.png) +![](http://img.dabin-coder.cn/image/使用Jboss maven仓库.png) maven中的仓库分为两种,snapshot 快照仓库和 release 发布仓库。元素 releases 的 enabled 为 true 表示开启 Jboss 仓库 release 版本下载支持,maven 会从 Jboss 仓库下载 release 版本的构件。 @@ -326,9 +285,9 @@ maven 的生命周期和插件相互绑定,用以完成具体的构建任务 ### 内置绑定 -![内置绑定1](https://img2018.cnblogs.com/blog/1252910/201907/1252910-20190706171830733-661580052.png) +![](http://img.dabin-coder.cn/image/maven内置绑定1.png) -![内置绑定2](https://img2018.cnblogs.com/blog/1252910/201907/1252910-20190706171854778-608245986.png) +![](http://img.dabin-coder.cn/image/maven内置绑定2.png) ### 自定义绑定 @@ -387,7 +346,7 @@ maven-surefire-plugin 提供了一个 maven.test.skip 参数,当其值为 true 项目有多个模块时,使用一个聚合体将这些模块聚合起来,通过聚合体就可以一次构建全部模块。 -![maven聚合](https://img2018.cnblogs.com/blog/1252910/201907/1252910-20190706142605104-1246458390.png) +![](http://img.dabin-coder.cn/image/maven聚合.png) accout-aggregator 的版本号要跟各个模块版本号相同,packaging 的值必须为 pom。module 标签的值是模块根目录名字(为了方便,模块根目录名字常与 artifactId 同名)。这样聚合模块和其他模块的目录结构是父子关系。如果使用平行目录结构,聚合模块的 pom 文件需要做相应的修改。 @@ -531,7 +490,7 @@ springframework 依赖的 version 继承自父模块,可以省略,可避免 使用 import 依赖范围可以导入依赖管理配置,将目标 pom 的 dependencyManagement 配置导入合并到当前 pom 的 dependencyManagement 元素中。 -![使用import依赖范围导入依赖管理配置](https://img2018.cnblogs.com/blog/1252910/201907/1252910-20190706155054098-1669414992.png) +![](http://img.dabin-coder.cn/image/使用import依赖范围导入依赖管理配置.png) ### 插件管理 diff --git a/tools/typora-overview.md b/tools/typora-overview.md new file mode 100644 index 0000000..efd4093 --- /dev/null +++ b/tools/typora-overview.md @@ -0,0 +1,180 @@ +11月23日,Typora 正式发布 1.0 版本,正式版开始收费了,定价14.99美元。不过,Beta版本还是可以继续免费使用的。 + +作为 Typora 的重度用户,今天给大家介绍一下这款 Markdown 神器。 + +## 简介 + +Typora 是一款**支持实时预览的 Markdown 文本编辑器**。 + +## 特点 + +1. **所见即所得**。输入`Markdown`标记后,会即时渲染成相应格式。大部分的`Markdown`编辑器都是一半是编辑窗口,一半是预览窗口,而Typora合二为一,更为简洁。 +2. **支持 LaTeX 语法**。 +3. **支持图床功能**。 +4. **定制化主题**。 + + + +## Markdown + +Markdown是一种**轻量级标记语言**,排版语法简洁,让使用者更多地关注内容本身而非排版。 + +**基础语法**: + +![](http://img.dabin-coder.cn/image/image-20211205134819994.png) + +**代码高亮**:输入 ``` 后并输入语言名,换行,开始写代码,Typora 会自动实现代码高亮的效果(如下图)。 + +![](http://img.dabin-coder.cn/image/image-20211205133939439.png) + +## 图床 + +Typora 里的图片是链接到本地图片的,如果将文档同步到其他平台,图片链接会失效。可以使用图床来保证文档在分享后图片仍能正常显示。 + +我使用的是PicGo图床工具,具体配置方法如下: + +1、下载 PicGo:`https://github.com/Molunerfinn/PicGo/releases` + +2、选择图床,设置相关参数。PicGo 支持多个图床平台,如七牛、阿里云OSS等。 + +![](http://img.dabin-coder.cn/image/图床1.png) + +3、设置 PicGo server。 + +![](http://img.dabin-coder.cn/image/图床2.png) + +4、打开 Typora 中的「文件-偏好设置-图像」选项,配置上传服务为 PicGo 和 PicGo 的路径。 + +![](http://img.dabin-coder.cn/image/图床3.png) + +配置完成之后,当你在 Typora 中插入本地图片时,PicGo 会自动将图片上传图床并使用 Markdown 语法替换图片地址。 + +## LaTeX + +Typora 支持 LaTeX 语法,可以往文档插入数学公式。 + +数学公式有两种形式: inline 和 display。 + +- **inline(行间公式)**:在正文插入数学公式,用`$...$` 将公式括起来 +- **display(块间公式)** :独立排列的公式,用 `$$...$$`将公式括起来,默认显示在行中间 + +**常用语法**: + +![](http://img.dabin-coder.cn/image/latex语法.png) + +下面举几个例子: + +**分段函数**: +``` +$$ +f(n)= + \begin{cases} + n/2, & \text{if $n$ is even}\\ + 3n+1,& \text{if $n$ is odd} + \end{cases} +$$ +``` +![](http://img.dabin-coder.cn/image/image-20211204235551407.png) + +**矩阵**: +``` +$$ +X=\left| + \begin{matrix} + x_{11} & x_{12} & \cdots & x_{1d}\\ + x_{21} & x_{22} & \cdots & x_{2d}\\ + \vdots & \vdots & \ddots & \vdots \\ + x_{11} & x_{12} & \cdots & x_{1d}\\ + \end{matrix} +\right| +$$ +``` +![](http://img.dabin-coder.cn/image/image-20211204235601934.png) + +**偏导数和微分:** +``` +$$ +\frac{\partial z}{\partial x_1} + \frac{\partial z}{\partial x_2} \\ +\frac{\mathrm{d}z}{\mathrm{d}x_1}+\frac{\mathrm{d}z}{\mathrm{d}x_2} +$$ +``` + +![](http://img.dabin-coder.cn/image/image-20211204235614665.png) + +## 目录 + +markdown文档生成目录,我使用过的两种方法: + +1、在文章开始使用`[TOC]` 将自动在文章生成目录。 + +- 某些平台(如掘金)不支持 + +2、使用插件 doctoc 生成目录(页内超链接)。 + +- 需要执行命令`doctoc xxx.md`生成目录。如果修改了标题,需要再次执行命令更新目录 + +使用 doctoc 生成目录的步骤: + +1. 安装 doctoc,`npm install doctoc` +2. 在文档当前目录执行`doctoc xxx.md`命令,即可生成标题 + +## 定制化主题 + +在 Typora 中 CSS 被称为「主题」,但其本质仍是 CSS 文件。可以在 `文件 - 偏好设置 - 主题 - 打开主题文件夹` 看到这些 CSS 文件。 + +可以自定义修改 CSS 文件,生成新的主题。 + + + +## Mermaid + +`Mermaid`是一个用于画流程图、状态图、时序图、甘特图的库,使用 JavaScript 进行本地渲染,广泛集成于许多 Markdown 编辑器中。Typora也支持`Mermaid`语法。 + +下面举几个例子。 + +**流程图**: + +```mermaid +graph TD; +A-->B; +A-->C; +B-->D; +C-->D; +``` + +![](http://img.dabin-coder.cn/image/image-20211204235313626.png) + +**时序图**: + +```mermaid +sequenceDiagram + Alice->>+John: Hello John, how are you? + Alice->>+John: John, can you hear me? + John-->>-Alice: Hi Alice, I can hear you! + John-->>-Alice: I feel great! +``` + +![](http://img.dabin-coder.cn/image/image-20211204235348115.png) + +## 导入导出 + +Typora 支持导入和导出的文件格式:`html`、`pdf`、`docx`、`epub`和`latex`等。其中导出`docx`、`epub`和`latex`需要安装 `Pandoc` 插件。 + +## 其他功能 + +**打字机模式**:使得你所编辑的那一行永远处于屏幕正中。 + +**专注模式**:使你正在编辑的那一行保留颜色,而其他行的字体呈灰色。 + +![](http://img.dabin-coder.cn/image/image-20211204235513676.png) + + + + + +码字不易,如果觉得对你有帮助,可以**点个赞**鼓励一下! + +我是 程序员大彬,专注Java后端硬核知识分享,欢迎大家关注~ + + + diff --git "a/\344\270\255\351\227\264\344\273\266/Elasticsearch\345\205\245\351\227\250.md" "b/\344\270\255\351\227\264\344\273\266/Elasticsearch\345\205\245\351\227\250.md" deleted file mode 100644 index 7e2b094..0000000 --- "a/\344\270\255\351\227\264\344\273\266/Elasticsearch\345\205\245\351\227\250.md" +++ /dev/null @@ -1,2438 +0,0 @@ - - - - -- [基础](#%E5%9F%BA%E7%A1%80) - - [概念](#%E6%A6%82%E5%BF%B5) - - [启动和关闭](#%E5%90%AF%E5%8A%A8%E5%92%8C%E5%85%B3%E9%97%AD) - - [配置文件](#%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6) - - [PUT](#put) - - [GET](#get) - - [全文搜索](#%E5%85%A8%E6%96%87%E6%90%9C%E7%B4%A2) - - [高亮搜索](#%E9%AB%98%E4%BA%AE%E6%90%9C%E7%B4%A2) - - [分析](#%E5%88%86%E6%9E%90) - - [分布式特性](#%E5%88%86%E5%B8%83%E5%BC%8F%E7%89%B9%E6%80%A7) -- [集群原理](#%E9%9B%86%E7%BE%A4%E5%8E%9F%E7%90%86) - - [术语](#%E6%9C%AF%E8%AF%AD) - - [节点](#%E8%8A%82%E7%82%B9) - - [分片](#%E5%88%86%E7%89%87) - - [集群健康](#%E9%9B%86%E7%BE%A4%E5%81%A5%E5%BA%B7) - - [索引](#%E7%B4%A2%E5%BC%95) - - [故障转移](#%E6%95%85%E9%9A%9C%E8%BD%AC%E7%A7%BB) - - [单机多节点](#%E5%8D%95%E6%9C%BA%E5%A4%9A%E8%8A%82%E7%82%B9) -- [数据输入与输出](#%E6%95%B0%E6%8D%AE%E8%BE%93%E5%85%A5%E4%B8%8E%E8%BE%93%E5%87%BA) - - [文档元数据](#%E6%96%87%E6%A1%A3%E5%85%83%E6%95%B0%E6%8D%AE) - - [创建新文档](#%E5%88%9B%E5%BB%BA%E6%96%B0%E6%96%87%E6%A1%A3) - - [取回文档](#%E5%8F%96%E5%9B%9E%E6%96%87%E6%A1%A3) - - [取回多个文档](#%E5%8F%96%E5%9B%9E%E5%A4%9A%E4%B8%AA%E6%96%87%E6%A1%A3) - - [删除文档](#%E5%88%A0%E9%99%A4%E6%96%87%E6%A1%A3) - - [检查文档是否存在](#%E6%A3%80%E6%9F%A5%E6%96%87%E6%A1%A3%E6%98%AF%E5%90%A6%E5%AD%98%E5%9C%A8) - - [更新文档](#%E6%9B%B4%E6%96%B0%E6%96%87%E6%A1%A3) - - [更新和冲突](#%E6%9B%B4%E6%96%B0%E5%92%8C%E5%86%B2%E7%AA%81) - - [批量操作](#%E6%89%B9%E9%87%8F%E6%93%8D%E4%BD%9C) -- [分布式文档存储](#%E5%88%86%E5%B8%83%E5%BC%8F%E6%96%87%E6%A1%A3%E5%AD%98%E5%82%A8) - - [路由文档到分片](#%E8%B7%AF%E7%94%B1%E6%96%87%E6%A1%A3%E5%88%B0%E5%88%86%E7%89%87) -- [映射和分析](#%E6%98%A0%E5%B0%84%E5%92%8C%E5%88%86%E6%9E%90) - - [核心简单域类型](#%E6%A0%B8%E5%BF%83%E7%AE%80%E5%8D%95%E5%9F%9F%E7%B1%BB%E5%9E%8B) - - [查看映射](#%E6%9F%A5%E7%9C%8B%E6%98%A0%E5%B0%84) - - [自定义映射器](#%E8%87%AA%E5%AE%9A%E4%B9%89%E6%98%A0%E5%B0%84%E5%99%A8) - - [index](#index) - - [analyzer](#analyzer) - - [更新映射](#%E6%9B%B4%E6%96%B0%E6%98%A0%E5%B0%84) - - [测试映射](#%E6%B5%8B%E8%AF%95%E6%98%A0%E5%B0%84) - - [分析器](#%E5%88%86%E6%9E%90%E5%99%A8) - - [测试分析器](#%E6%B5%8B%E8%AF%95%E5%88%86%E6%9E%90%E5%99%A8) - - [复杂核心域类型](#%E5%A4%8D%E6%9D%82%E6%A0%B8%E5%BF%83%E5%9F%9F%E7%B1%BB%E5%9E%8B) - - [多值域](#%E5%A4%9A%E5%80%BC%E5%9F%9F) - - [内部对象](#%E5%86%85%E9%83%A8%E5%AF%B9%E8%B1%A1) -- [搜索](#%E6%90%9C%E7%B4%A2) - - [空搜索](#%E7%A9%BA%E6%90%9C%E7%B4%A2) - - [多索引多类型](#%E5%A4%9A%E7%B4%A2%E5%BC%95%E5%A4%9A%E7%B1%BB%E5%9E%8B) - - [轻量搜索](#%E8%BD%BB%E9%87%8F%E6%90%9C%E7%B4%A2) -- [请求体查询](#%E8%AF%B7%E6%B1%82%E4%BD%93%E6%9F%A5%E8%AF%A2) - - [空查询](#%E7%A9%BA%E6%9F%A5%E8%AF%A2) - - [提升权重](#%E6%8F%90%E5%8D%87%E6%9D%83%E9%87%8D) - - [explain](#explain) - - [查询和过滤器的区别](#%E6%9F%A5%E8%AF%A2%E5%92%8C%E8%BF%87%E6%BB%A4%E5%99%A8%E7%9A%84%E5%8C%BA%E5%88%AB) -- [排序与相关性](#%E6%8E%92%E5%BA%8F%E4%B8%8E%E7%9B%B8%E5%85%B3%E6%80%A7) - - [按照字段的值排序](#%E6%8C%89%E7%85%A7%E5%AD%97%E6%AE%B5%E7%9A%84%E5%80%BC%E6%8E%92%E5%BA%8F) - - [多级排序](#%E5%A4%9A%E7%BA%A7%E6%8E%92%E5%BA%8F) - - [多值字段的排序](#%E5%A4%9A%E5%80%BC%E5%AD%97%E6%AE%B5%E7%9A%84%E6%8E%92%E5%BA%8F) - - [字符串排序](#%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%8E%92%E5%BA%8F) -- [索引管理](#%E7%B4%A2%E5%BC%95%E7%AE%A1%E7%90%86) - - [创建索引](#%E5%88%9B%E5%BB%BA%E7%B4%A2%E5%BC%95) - - [删除索引](#%E5%88%A0%E9%99%A4%E7%B4%A2%E5%BC%95) - - [索引设置](#%E7%B4%A2%E5%BC%95%E8%AE%BE%E7%BD%AE) - - [配置分析器](#%E9%85%8D%E7%BD%AE%E5%88%86%E6%9E%90%E5%99%A8) - - [自定义分析器](#%E8%87%AA%E5%AE%9A%E4%B9%89%E5%88%86%E6%9E%90%E5%99%A8) - - [类型和映射](#%E7%B1%BB%E5%9E%8B%E5%92%8C%E6%98%A0%E5%B0%84) - - [根对象](#%E6%A0%B9%E5%AF%B9%E8%B1%A1) -- [分片内部原理](#%E5%88%86%E7%89%87%E5%86%85%E9%83%A8%E5%8E%9F%E7%90%86) - - [倒排索引](#%E5%80%92%E6%8E%92%E7%B4%A2%E5%BC%95) -- [深入搜索](#%E6%B7%B1%E5%85%A5%E6%90%9C%E7%B4%A2) - - [精确值查找](#%E7%B2%BE%E7%A1%AE%E5%80%BC%E6%9F%A5%E6%89%BE) - - [term 查询文本](#term-%E6%9F%A5%E8%AF%A2%E6%96%87%E6%9C%AC) - - [terms 查询](#terms-%E6%9F%A5%E8%AF%A2) - - [全文搜索](#%E5%85%A8%E6%96%87%E6%90%9C%E7%B4%A2-1) - - [match 查询](#match-%E6%9F%A5%E8%AF%A2) - - [range 查询](#range-%E6%9F%A5%E8%AF%A2) - - [数字范围](#%E6%95%B0%E5%AD%97%E8%8C%83%E5%9B%B4) - - [日期范围](#%E6%97%A5%E6%9C%9F%E8%8C%83%E5%9B%B4) - - [分页](#%E5%88%86%E9%A1%B5) - - [exists 查询](#exists-%E6%9F%A5%E8%AF%A2) - - [bool 组合查询](#bool-%E7%BB%84%E5%90%88%E6%9F%A5%E8%AF%A2) - - [如何使用 bool 查询](#%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8-bool-%E6%9F%A5%E8%AF%A2) - - [constant_score 查询](#constant_score-%E6%9F%A5%E8%AF%A2) - - [多字段搜索](#%E5%A4%9A%E5%AD%97%E6%AE%B5%E6%90%9C%E7%B4%A2) - - [多字符串查询](#%E5%A4%9A%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%9F%A5%E8%AF%A2) - - [multi_match 查询](#multi_match-%E6%9F%A5%E8%AF%A2) - - [多字段映射](#%E5%A4%9A%E5%AD%97%E6%AE%B5%E6%98%A0%E5%B0%84) - - [copy_to 定制组合 field](#copy_to-%E5%AE%9A%E5%88%B6%E7%BB%84%E5%90%88-field) -- [springboot 集成 es](#springboot-%E9%9B%86%E6%88%90-es) -- [mall](#mall) - - - -[Elasticsearch 权威指南](https://www.elastic.co/guide/cn/elasticsearch/guide/current/_talking_to_elasticsearch.html) - -## 基础 - -Elasticsearch 是一个开源的搜索引擎,建立在全文搜索引擎库 lucene 基础之上。Elasticsearch 也是使用 Java 编写的,它的内部使用 Lucene 做索引与搜索,但是它的目的是使全文检索变得简单, 通过隐藏 Lucene 的复杂性,取而代之的提供一套简单一致的 RESTful API。 - -ES与mysql的对应关系: - -- index –> DB -- type –> Table -- Document –> row - -### 概念 - -[参考--阮一峰es教程](http://www.ruanyifeng.com/blog/2017/08/elasticsearch.html) - -1. node 和 cluster - - Elastic 本质上是一个分布式数据库,允许多台服务器协同工作,每台服务器可以运行多个 Elastic 实例。单个 Elastic 实例称为一个节点(node)。一组节点构成一个集群(cluster)。 - -2. Index - - 数据库的同义词。每个 Index (即数据库)的名字必须是小写。查看当前节点的所有index:`GET _cat/indices` - -3. Type - - 类似于表结构。不同的 Type 应该有相似的结构(schema),举例来说,`id`字段不能在这个组是字符串,在另一个组是数值。这是与关系型数据库的表的区别。性质完全不同的数据(比如`products`和`logs`)应该存成两个 Index,而不是一个 Index 里面的两个 Type。 - -4. Document - - 单条的记录称为 Document。Document 可以分组,比如`weather`这个 Index 里面,可以按城市分组(北京和上海),这种分组就是 Type。同一个 Index 里面的 Document,不要求有相同的结构(scheme),但是最好保持相同,这样有利于提高搜索效率。 - - -### 启动和关闭 - -不用安装,解压即可。 - -启动:`bin\elasticsearch.bat` - -关闭:ctrl+c/`curl -XPOST http://localhost:9200/_cluster/nodes/_shutdown `关掉整个集群 - -启动 head 插件:到 head 安装目录下运行`grunt server` - -### 配置文件 - -安装目录config下的 elasticsearch.yml 可以配置集群的信息,如cluster.name 和 node.name。 - -```yaml -# ---------------------------------- MyConfig ---------------------------------- -#cluster.name: xxx -node.name: node-2 -#指明该节点可以是主节点,也可以是数据节点 -node.master: true -node.data: true -network.host: 127.0.0.1 -http.port: 9200 -transport.tcp.port: 9300 -``` - -http.port 是elasticsearch对外提供服务的http端口配置。 - -transport.tcp.port 指定了elasticsearch集群内数据通讯使用的端口,默认情况下为9300。 - - -### PUT - -```json -PUT /company/employee/1 -{ - "first_name" : "Tyson", - "last_name" : "dai", - "age" : 23, - "interests" : ["sport", "music"], - "hire_date" : "2014-01-01" -} -``` - -company:索引名称,employee:类型名称,1是 ID。 - -### GET - -```json -GET /company/employee/1 -``` - -搜索所有雇员: - -```json -GET /company/employee/_search -``` - -搜索结果如下,雇员数据放在 hits 数组中: - -```json -{ - "took": 413, - "timed_out": false, - "_shards": { - "total": 5, - "successful": 5, - "skipped": 0, - "failed": 0 - }, - "hits": { - "total": 2, - "max_score": 1, - "hits": [ - { - "_index": "company", - "_type": "employee", - "_id": "2", - "_score": 1, - "_source": { - "first_name": "Lily", - "last_name": "dai", - "age": 23, - "interests": [ - "sport", - "music" - ] - } - }, - { - "_index": "company", - "_type": "employee", - "_id": "1", - "_score": 1, - "_source": { - "first_name": "Tyson", - "last_name": "dai", - "age": 23, - "interests": [ - "sport", - "music" - ] - } - } - ] - } -} -``` - -按特定条件搜索: - -```json -GET /company/employee/_search?q=first_name:Tyson -``` - -使用查询表达式搜索: - -```json -GET /company/employee/_search -{ - "query": { - "match": { - "first_name": "Tyson" - } - } -} -``` - -复杂查询: - -姓氏为 Dai的雇员,但这次我们只需要年龄大于 20 的。 - -```json -GET /company/employee/_search -{ - "query": { - "bool" : { - "must" : { - "match" : { - "last_name" : "dai" - } - }, - "filter": { - "range": { - "age": {"gt" : 20} - } - } - } - } -} -``` - -### 全文搜索 - -传统数据库确实很难搞定的任务。 - -```json -GET /company/employee/_search -{ - "query": { - "match": { - "about": "rock climbing" - } - } -} -``` - -返回`"about": "rock climbing"`和`"about": "rock albums"`两条记录,默认按照每个文档跟查询的匹配程度排序。 - -### 高亮搜索 - -```json -GET /company/employee/_search -{ - "query": { - "match_phrase": { - "about": "climbing" - } - }, - "highlight": { - "fields": { - "about": {} - } - } -} -``` - -返回结果多 `highlight` 的部分。这个部分包含了 `about` 属性匹配的文本片段,并以 HTML 标签 `` 封装。 - -```json -{ - "took": 43, - "timed_out": false, - "_shards": { - "total": 5, - "successful": 5, - "skipped": 0, - "failed": 0 - }, - "hits": { - "total": 1, - "max_score": 0.2876821, - "hits": [ - { - "_index": "company", - "_type": "employee", - "_id": "3", - "_score": 0.2876821, - "_source": { - "first_name": "Tyson", - "last_name": "dai", - "age": 23, - "interests": [ - "sport", - "music" - ], - "about": "rock climbing" - }, - "highlight": { - "about": [ - "rock climbing" - ] - } - } - ] - } -} -``` - -### 分析 - -Elasticsearch 有一个功能叫聚合(aggregations),允许我们基于数据生成一些精细的分析结果。聚合与 SQL 中的 `GROUP BY` 类似但更强大。 - -```json -GET /company/employee/_search -{ - "aggs": { - "all_interests": { - "terms": { "field": "interests" } - } - } -} -``` - -直接执行上面的代码会报错,原因是5.x后对排序,聚合这些操作用单独的数据结构(fielddata)缓存到内存里了,需要单独开启。 - -```json -PUT company/_mapping/employee/ -{ - "properties": { - "interests": { - "type": "text", - "fielddata": true - } - } -} -``` - -搜索结果: - -```json -{ - ... - "hits": { ... }, - "aggregations": { - "all_interests": { - "doc_count_error_upper_bound": 0, - "sum_other_doc_count": 0, - "buckets": [ - { - "key": "music", - "doc_count": 4 - }, - { - "key": "sport", - "doc_count": 4 - } - ] - } - } -} -``` - -如果想知道叫 Tyson的雇员中最受欢迎的兴趣爱好,可以直接添加适当的查询来组合查询: - -```json -GET company/employee/_search -{ - "query": { - "match": { - "first_name": "Tyson" - } - }, - "aggs": { - "all_interests": { - "terms": { - "field": "interests" - } - } - } -} -``` - -搜索结果: - -```json -{ - "took": 33, - "timed_out": false, - "_shards": { - "total": 5, - "successful": 5, - "skipped": 0, - "failed": 0 - }, - "hits": { - "total": 1, - "max_score": 0.2876821, - "hits": [ - { - "_index": "company", - "_type": "employee", - "_id": "3", - "_score": 0.2876821, - "_source": { - "first_name": "Tyson", - "last_name": "dai", - "age": 23, - "interests": [ - "sport", - "music" - ], - "about": "rock climbing" - } - } - ] - }, - "aggregations": { - "all_interests": { - "doc_count_error_upper_bound": 0, - "sum_other_doc_count": 0, - "buckets": [ - { - "key": "music", - "doc_count": 1 - }, - { - "key": "sport", - "doc_count": 1 - } - ] - } - } -} -``` - -聚合还支持分级汇总 。比如,查询特定兴趣爱好员工的平均年龄: - -```java -GET company/employee/_search -{ - "aggs": { - "all_interests": { - "terms": { - "field": "interests" - }, - "aggs": { - "avg_age": { - "avg": { - "field": "age" - } - } - } - } - } -} -``` - -搜索结果: - -```json -"aggregations": { - "all_interests": { - "doc_count_error_upper_bound": 0, - "sum_other_doc_count": 0, - "buckets": [ - { - "key": "music", - "doc_count": 4, - "avg_age": { - "value": 18.5 - } - }, - { - "key": "sport", - "doc_count": 4, - "avg_age": { - "value": 18.5 - } - } - ] - } - } -``` - -### 分布式特性 - -Elasticsearch 可以横向扩展至数百(甚至数千)的服务器节点,同时可以处理PB级数据。 - -## 集群原理 - -ElasticSearch 的主旨是随时可用和按需扩容。扩容可以通过购买性能更强大( *垂直扩容* ) 或者数量更多的服务器( *水平扩容* )来实现。 - -### 术语 - -#### 节点 - -一个运行中的 Elasticsearch 实例称为节点,而集群是由一个或者多个拥有相同 `cluster.name` 配置的节点组成, 它们共同承担数据和负载的压力。当有节点加入集群中或者从集群中移除节点时,集群将会重新平均分布所有的数据。 - -当一个节点被选举成为主节点时, 它将负责管理集群范围内的所有变更,例如增加、删除索引,或者增加、删除节点等。 而主节点并不需要涉及到文档级别的变更和搜索等操作,所以当集群只拥有一个主节点的情况下,即使流量的增加它也不会成为瓶颈。 任何节点都可以成为主节点。 - -我们可以将请求发送到集群中的任何节点,包括主节点。 每个节点都知道任意文档所处的位置,并且能够将我们的请求直接转发到存储我们所需文档的节点。 无论我们将请求发送到哪个节点,它都能负责从各个包含我们所需文档的节点收集回数据,并将最终结果返回給客户端。 Elasticsearch 对这一切的管理都是透明的。 - -获取节点信息:`http://localhost:9200/_cluster/state/nodes?pretty`,pretty 用于换行。 - -#### 分片 - -每个节点可以分配一个或多个分片。分片是数据的容器,文档保存在分片内,分片又被分配到集群内的各个节点里。 当你的集群规模扩大或者缩小时, Elasticsearch 会自动的在各节点中迁移分片,使得数据仍然均匀分布在集群里。 - -一个分片可以是 *主* 分片或者 *副本* 分片。 索引内任意一个文档都归属于一个主分片,所以主分片的数目决定着索引能够保存的最大数据量。 - -一个副本分片只是一个主分片的拷贝。 副本分片作为硬件故障时保护数据不丢失的冗余备份,并为搜索和返回文档等读操作提供服务。 - -### 集群健康 - -`GET /_cluster/health` - -返回内容: - -```json -{ - "cluster_name": "elasticsearch", - "status": "yellow", - "timed_out": false, - "number_of_nodes": 1, - "number_of_data_nodes": 1, - "active_primary_shards": 6, - "active_shards": 6, - "relocating_shards": 0, - "initializing_shards": 0, - "unassigned_shards": 5, - "delayed_unassigned_shards": 0, - "number_of_pending_tasks": 0, - "number_of_in_flight_fetch": 0, - "task_max_waiting_in_queue_millis": 0, - "active_shards_percent_as_number": 54.54545454545454 -} -``` - -status 字段表示当前集群总体是否正常,它有三个值: - -1. green,所有的主分片和副本分片都正常运行。 - -2. yellow,所有的主分片都正常运行,但不是所有的副本分片都正常运行。 -3. red,有主分片不能正常运行。 - -### 索引 - -往 elasticsearch 添加数据时需要用到索引,索引是指向一个或多个分片的逻辑命名空间。一个分片是一个 Lucene 的实例,以及它本身就是一个完整的搜索引擎。 - -当我们只开启一个节点时,索引在默认情况下会被分配5个主分片,每个主分片拥有一个副本分片。主分片会被分配到这个节点上,而副本分片不会被分配到任何节点。 - -```json -{ - "cluster_name": "elasticsearch", - "status": "yellow", - ... - "unassigned_shards": 5,//5个副本分片都是 unassigned,都没有被分配到任何节点 - ... -} -``` - -当前我们的集群是正常运行的,但是在硬件故障时有丢失数据的风险。 - -### 故障转移 - -可以在同一个目录下开启另一个节点,副本分片会被分配到这个节点上,此时计算有硬件故障也不会丢失数据。 - -### 单机多节点 - -node1 的 elasticsearch.yml 做如下配置: - -```yaml -# ---------------------------------- MyConfig ---------------------------------- -#cluster.name: xxx -node.name: node-1 -#指明该节点可以是主节点,也可以是数据节点 -#node.master: true -#node.data: true -network.host: 127.0.0.1 -http.port: 9200 -transport.tcp.port: 9300 -discovery.zen.ping.unicast.hosts: ["127.0.0.1:9300", "127.0.0.1:9301", "127.0.0.1:9302"] - -# 解决elasticsearch-head 集群健康值: 未连接问题 -http.cors.enabled: true -http.cors.allow-origin: "*" -``` - -node2 的 elasticsearch.yml 做如下配置: - -```yaml -# ---------------------------------- MyConfig ---------------------------------- -#cluster.name: xxx -node.name: node-2 -#指明该节点可以是主节点,也可以是数据节点 -#node.master: true -#node.data: true -network.host: 127.0.0.1 -http.port: 9201 -transport.tcp.port: 9301 -discovery.zen.ping.unicast.hosts: ["127.0.0.1:9300", "127.0.0.1:9301", "127.0.0.1:9302"] - -# 解决elasticsearch-head 集群健康值: 未连接问题 -http.cors.enabled: true -http.cors.allow-origin: "*" -``` - -同理 node3 也做好配置。然后重启 elasticsearch 节点,就可以实现单机多节点集群搭建。 - - - -## 数据输入与输出 - -### 文档元数据 - -一个文档不仅包含数据,也包含元数据。三个必须的元数据元素如下:\_index、\_type和_id。 - -索引名字必须小写,不能以下划线开头,不能包含逗号。 - -\_type在索引中对数据进行逻辑分区,如产品下面还可以分为很多子类。一个 `_type` 命名可以是大写或者小写,但是不能以下划线或者句号开头,不应该包含逗号, 并且长度限制为256个字符。 - -### 创建新文档 - -使用 PUT 请求,需要定义 _id: - -```json -PUT /{index}/{type}/{id} -{ - "field": "value", - ... -} -``` - -id 为2的文档存在时会报错: document already exists - -```json -PUT company/employee/2/_create -{ - "first_name" : "Tyson", - "last_name" : "dai", - "age" : 23, - "interests" : ["sport", "music"] -} -``` - -不存在时才创建: - -```json -PUT company/employee/2?op_type=create -{ - "first_name" : "Tyson", - "last_name" : "dai", - "age" : 23, - "interests" : ["sport", "music"] -} -``` - -使用 `POST` 可以让 Elasticsearch 自动生成唯一 的_id。 - -```json -POST company/employee -{ - "first_name" : "Tyson", - "last_name" : "dai", - "age" : 23, - "interests" : ["sport", "music"] -} -``` - -### 取回文档 - -返回文档的一部分: - -```json -GET /company/employee/1?_source=first_name,interests -``` - -只得到_source 字段(即id为1的整个文档): - -```json -GET /company/employee/1/_source -``` - - -### 取回多个文档 - -mget api 要求传入一个 docs 数组作为参数,可以通过 _source 指定返回字段。 - -```json -GET /_mget -{ - "docs": [ - { - "_index": "company", - "_type": "employee", - "_id": 2 - }, - { - "_index": "class", - "_type": "student", - "_id": 1, - "_source": "about" - } - ] -} -``` - -如果获取的文档 _index 和 _type 相同,可以在 url 指定默认的 _index 和 _type,传入一个 ids 数组。 - -```json -GET /company/employee/_mget -{ - "ids": ["1", "2"] -} -``` - -通过单独请求可以覆盖默认的 _index 和 _type。 - -```json -GET /company/employee/_mget -{ - "docs" : [ - { "_id" : 2 }, - { "_index" : "class", "_type" : "student", "_id" : 1 } - ] -} -``` - -### 删除文档 - -`DELETE company/employee/1`,删除文档,版本号会增加。 - -```json -{ - "_index": "company", - "_type": "employee", - "_id": "1", - "_version": 2, - "result": "deleted", - "_shards": { - "total": 2, - "successful": 1, - "failed": 0 - }, - "_seq_no": 1, - "_primary_term": 4 -} -``` - -### 检查文档是否存在 - -`HEAD /company/employee/1` - -返回结果:`200 - OK` - -### 更新文档 - -文档是不可变的,不能被修改,只能被替换。 `update` API 必须遵循同样的规则。 从外部来看,我们在一个文档的某个位置进行部分更新。然而在内部, `update` API 简单使用 *检索-修改-重建索引* 的处理过程。 - -```json -POST company/employee/1/_update -{ - "doc": { - "first_name": "sophia", - "interests": ["sport", "chess"] - } -} -``` - -#### 更新和冲突 - -```json -POST company/employee/1/_update?retry_on_conflict=5 -{ - "doc": { - "first_name": "sophia", - "interests": ["sport", "chess"] - } -} -``` - -### 批量操作 - -语法: - -```json -{ action: { metadata }}\n -{ request body }\n -{ action: { metadata }}\n -{ request body }\n -... -``` - -action 有 create、delete、index 和 update。metadata 定义了\_index,\_type,_id 等信息。 - -create:如果文档不存在,那么就创建它。index:创建一个新文档或者替换一个现有的文档。create 和 index 不指定 `_id`的话 ,将会自动生成一个 ID 。 - -```json -POST /_bulk -{ "delete": { "_index": "website", "_type": "blog", "_id": "123" }} -{ "create": { "_index": "website", "_type": "blog", "_id": "123" }} -{ "title": "My first blog post" } -{ "index": { "_index": "website", "_type": "blog" }} -{ "title": "My second blog post" } -{ "update": { "_index": "website", "_type": "blog", "_id": "123", "_retry_on_conflict" : 3} } -{ "doc" : {"title" : "My updated blog post"} } -``` - -bulk 请求不是原子的,不能用它来实现事务控制。每个请求是单独处理的,因此一个请求的成功或失败不会影响其他的请求。 - -**默认的 index 和 type** - -```json -POST /website/_bulk -{ "index": { "_type": "log" }} -{ "event": "User logged in" } -``` - - - -## 分布式文档存储 - -### 路由文档到分片 - -文档所在分片的位置通过这个公式计算:`shard = hash(routing) % number_of_primary_shards`,rounting 默认是文档的 _id,可以设置成自定义的值。创建索引的时候就确定好主分片的数量,并且永远不会改变这个数量:因为如果数量变化了,那么所有之前路由的值都会无效,文档也再也找不到了。 - - - - -## 映射和分析 - -映射:为了能够将时间域视为时间,数字域视为数字,字符串域视为全文或精确值字符串, Elasticsearch 需要知道每个域中数据的类型。这个信息包含在映射中。 - -分析:将一块文本分成适合于倒排索引的独立的词条,然后将这些词条统一化为标准格式以提高它们的可搜索性。 - -### 核心简单域类型 - -Elasticsearch 支持 如下简单域类型: - -- 字符串: `string` -- 整数 : `byte`, `short`, `integer`, `long` -- 浮点数: `float`, `double` -- 布尔型: `boolean` -- 日期: `date` - -当你索引一个新的文档,elasticsearch 会使用动态映射,通过 json 中的基本数据类型,尝试猜测域的类型。 - -| **JSON type** | **域 type** | -| ------------------------------ | ----------- | -| 布尔型: `true` 或者 `false` | `boolean` | -| 整数: `123` | `long` | -| 浮点数: `123.45` | `double` | -| 字符串,有效日期: `2014-09-15` | `date` | -| 字符串: `foo bar` | `string` | - -如果你通过引号( `"123"` )索引一个数字,它会被映射为 `string` 类型,而不是 `long` 。但是,如果这个域已经映射为 `long` ,那么 Elasticsearch 会尝试将这个字符串转化为 long ,如果无法转化,则抛出一个异常。 - -### 查看映射 - -`GET /company/_mapping/employee` - -返回结果: - -```json -{ - "company": { - "mappings": { - "employee": { - "properties": { - "age": { - "type": "long" - }, - "first_name": { - "type": "text", - "fields": { - "keyword": { - "type": "keyword", - "ignore_above": 256 - } - } - }, - "hire_date": { - "type": "date" - }, - "interests": { - "type": "text", - "fields": { - "keyword": { - "type": "keyword", - "ignore_above": 256 - } - } - }, - "last_name": { - "type": "text", - "fields": { - "keyword": { - "type": "keyword", - "ignore_above": 256 - } - } - } - } - } - } - } -} -``` - -### 自定义映射器 - -`string` 类型域会被认为包含全文。它们的值在索引前,会通过 一个分析器,针对于这个域的查询在搜索前也会经过一个分析器。string 域映射的两个最重要的属性是 index 和 analyzer。 - -#### index - -`index` 属性控制怎样索引字符串。它有三个值: - -- analyzed:默认,首先分析这个域,然后索引它; -- not_analyzed:索引这个域,索引的是精确值,不会对它进行分析; -- no:不要索引这个域,这个域不会被搜索到。 - -设置 tag 域的 index 为 not_analyzed: - -```json -{ - "tag": { - "type": "string", - "index": "not_analyzed" - } -} -``` - -其他简单类型(例如 `long` , `double` , `date` 等)也接受 `index` 参数,但有意义的值只有 `no` 和 `not_analyzed` , 因为它们永远不会被分析。 - -#### analyzer - -对于 `analyzed` 字符串域,用 `analyzer` 属性指定在搜索和索引时使用的分析器。默认, Elasticsearch 使用 `standard` 分析器, 但你可以指定一个内置的分析器替代它,例如 `whitespace` 、 `simple` 和 `english`: - -```json -{ - "tweet": { - "type": "string", - "analyzer": "english" - } -} -``` - - -### 更新映射 - -我们可以更新一个映射来添加一个新域,但不能将一个存在的域从 `analyzed` 改为 `not_analyzed` ,否则索引的数据可能会出错,数据不能被正常的搜索。 - -增加名为 tag 的 not_analyzed 的文本域: - -```json -PUT /gb/_mapping/tweet -{ - "properties" : { - "tag" : { - "type" : "text", - "index": "not_analyzed" - } - } -} -``` - -指定中文分词器: - -```json -PUT /gb/_mapping/tweet -{ - "properties" : { - "user": { - "type": "text", - "analyzer": "ik_max_word", - "search_analyzer": "ik_max_word" - } - } -} -``` - -`analyzer`是字段文本的分词器,`search_analyzer`是搜索词的分词器。`ik_max_word`分词器是插件`ik`提供的,可以对文本进行最大数量的分词。 - - -### 测试映射 - -```json -GET /gb/_analyze -{ - "field": "tag", - "text": "Black-cats" -} -``` - - `tag` 域产生单独的词条 `Black-cats` 。 - -### 分析器 - -分析器的三个功能: - -- 字符过滤。字符串按顺序通过每个字符过滤器。它们在分词前整理字符串。一个字符过滤器可以用来去掉HTML或者将 & 转化为 and。 -- 字符串被分词器分成单个的词条 -- token 过滤器。改变词条(Quick 小写),删除词条(a/the/and),增加词条(leap/jump这种同义词) - -对特定域和全文域_all 查询字符串时可能会返回不同的结果。 - -当你查询一个 *全文* 域时, 会对查询字符串应用相同的分析器,以产生正确的搜索词条列表。 - -当你查询一个 *精确值* 域时,不会分析查询字符串, 而是搜索你指定的精确值。 - -```json -GET /_search?q=2014 # 12 results -GET /_search?q=2014-09-15 # 12 results -GET /_search?q=date:2014-09-15 # 1 result -GET /_search?q=date:2014 # 0 results 没有"2014",只有"2014-09-15" -``` - -date 域包含一个精确值:单独的词条 `2014-09-15`。 - -_all 域是一个全文域,所以分词进程将日期转化为三个词条: `2014`, `09`, 和 `15`。 - -#### 测试分析器 - -```json -GET /_analyze -{ - "analyzer": "standard", - "text": "Text to analyze" -} -``` - -结果: - -```json -{ - "tokens": [ - { - "token": "text", - "start_offset": 0, - "end_offset": 4, - "type": "", - "position": 0 - }, - { - "token": "to", - "start_offset": 5, - "end_offset": 7, - "type": "", - "position": 1 - }, - { - "token": "analyze", - "start_offset": 8, - "end_offset": 15, - "type": "", - "position": 2 - } - ] -} -``` - -### 复杂核心域类型 - -#### 多值域 - -`{ "tag": [ "search", "nosql" ]}` - -### 内部对象 - -```json -{ - "tweet": "Elasticsearch is very flexible", - "user": { - "id": "@johnsmith", - "gender": "male", - "age": 26, - "name": { - "full": "John Smith", - "first": "John", - "last": "Smith" - } - } -} -``` - -Lucene 不理解内部对象。 Lucene 文档是由一组键值对列表组成的。为了能让 Elasticsearch 有效地索引内部类,它把我们的文档转化成这样: - -```json -{ - "tweet": [elasticsearch, flexible, very], - "user.id": [@johnsmith], - "user.gender": [male], - "user.age": [26], - "user.name.full": [john, smith], - "user.name.first": [john], - "user.name.last": [smith] -} -``` - - - - -## 搜索 - -### 空搜索 - -`GET /_search`,返回的 hits 数组包含所查询结果的前十个文档。 - -`GET /_search?timeout=10ms`,在请求超时之前,Elasticsearch 将会返回已经成功从每个分片获取的结果。 - - -### 多索引多类型 - -`GET /c*,g*/_search`,在以 c 开头和 g 开头的索引中搜索所有的类型。 - -`GET /_all/employee,student/_search?size=1&from=1`,在所有索引中搜索 employee 和 student 类型。elasticsearch 默认一次返回10条结果,size 可以指定返回返回结果数量,from 指定位移。 - - -### 轻量搜索 - -查询 employee 类型的 last_name 字段为 dai 的所有文档:`GET company/employee/_search?q=last_name:dai` - -查询包含 dai 的所有文档:`GET /_search?q=dai` - - - -## 请求体查询 - -### 空查询 - -```json -GET _search -{ - "query" : { - "match_all": {} - } -} -``` - - - - -### 提升权重 - -我们可以通过指定 `boost` 来控制任何查询语句的相对的权重, `boost` 的默认值为 `1` ,大于 `1` 会提升一个语句的相对权重。 - -```json -GET /company/employee/_search -{ - "query": { - "bool": { - "must": { - "match": { - "interests": { - "query": "sport music", - "operator": "and" - } - } - }, - "should": [ - { "match": { - "first_name": { - "query": "tyson", - "boost": 3 - } - }}, - { "match": { - "first_name": { - "query": "sophia", - "boost": 2 - } - }} - ] - } - } -} -``` - -### explain - -查询结果说明。 - -```json -GET /_validate/query?explain -{ - "query": { - "match" : { - "interests" : "sport shoes" - } - } -} -``` - -结果: - -```json -{ - "valid": true, - "_shards": { - "total": 3, - "successful": 3, - "failed": 0 - }, - "explanations": [ - { - "index": "class", - "valid": true, - "explanation": "interests:sport interests:shoes" - }, - { - "index": "company", - "valid": true, - "explanation": "interests:sport interests:shoes" - } - ] -} -``` - -### 查询和过滤器的区别 - -查询会计算得分,而过滤不计算得分,过滤器所需处理更少,所以过滤器可以比普通查询更快。而且过滤器可以被缓存。 - - - -## 排序与相关性 - -有时,_score 相关性评分对你来说并没有意义。例如,下面的查询返回所有 `user_id` 字段包含 `1` 的结果: - -```json -GET /_search -{ - "query" : { - "constant_score" : { - "filter" : { - "term" : { - "id" : 1 - } - } - } - } -} -``` - -### 按照字段的值排序 - -按照 hire_date 排序。 - -```json -GET _search -{ - "query" : { - "bool" : { - "filter" : { - "match" : { - "first_name" : "tyson" - } - } - } - }, - "sort" : { - "hire_date" : { "order" : "desc" } - } -} -``` - -### 多级排序 - -首先按第一个条件排序,仅当结果集的第一个 `sort` 值完全相同时才会按照第二个条件进行排序,以此类推。 - -```json -GET _search -{ - "query" : { - "bool" : { - "filter" : { - "match" : { - "first_name" : "tyson" - } - } - } - }, - "sort" : { - "hire_date" : { "order" : "desc" }, - "_score" : { "order" : "desc" } - } -} -``` - -Query-string 搜索 也支持自定义排序,可以在查询字符串中使用 `sort` 参数: - -```js -GET /_search?sort=hire_date:desc&sort=_score -``` - -### 多值字段的排序 - -对于数字或日期,你可以将多值字段减为单值,这可以通过使用 `min` 、 `max` 、 `avg` 或是 `sum` 排序模式。 例如你可以按照每个 hire_date 字段中的最早日期进行排序,通过以下方法: - -```json -"sort": { - "hire_date": { - "order": "asc", - "mode": "min" - } -} -``` - -### 字符串排序 - -为了对字符串字段进行排序,需要用两种方式对同一个字符串进行索引: `analyzed` 用于搜索, `not_analyzed` 用于排序。 - -用两种方式索引一个单字段: - -```json -"interests": { - "type": "string", - "analyzer": "english", - "fields": { - "raw": { - "type": "string", - "index": "not_analyzed" - } - } -} -``` - -interests.raw 子字段是 not_analyzed。interests 字段只在 _source 中出现一次。 - -使用 interests 字段用于搜索,interests.raw 字段用于排序: - -```json -GET /_search -{ - "query": { - "match": { - "interests": "elasticsearch" - } - }, - "sort": "interests.raw" -} -``` - -备注:以全文 `analyzed` 字段排序会消耗大量的内存。 - - - -## 索引管理 - -### 创建索引 - -```json -PUT /my_index -{ - "settings": { ... any settings ... }, - "mappings": { - "type_one": { ... any mappings ... }, - "type_two": { ... any mappings ... }, - ... - } -} -``` - -禁止自动创建索引,你 可以通过在 `config/elasticsearch.yml` 的每个节点下添加下面的配置: - -`action.auto_create_index: false` - -### 删除索引 - -`DELETE /company,student` - -`DELETE /index_*` - -`DELETE /_all` 或 `DELETE /*` - -### 索引设置 - -number_of_shards:每个索引的主分片数,默认值是 `5` 。这个配置在索引创建后不能修改。 - -number_of_replicas:每个主分片的副本数,默认值是 `1` 。对于活动的索引库,这个配置可以随时修改。 - -创建只有 一个主分片,没有副本的小索引: - -```json -PUT /my_temp_index -{ - "settings": { - "number_of_shards" : 1, - "number_of_replicas" : 0 - } -} -``` - - 动态修改副本数: - -```json -PUT /my_temp_index/_settings -{ - "number_of_replicas": 1 -} -``` - -### 配置分析器 - -#### 自定义分析器 - -,一个分析器组合了三种函数:字符过滤器、分词器和词单元过滤器, 三种函数按照顺序被执行。 - -```json -PUT /my_index -{ - "settings": { - "analysis": { - "char_filter": { - "&_to_and" : { - "type" : "mapping", - "mappings" : ["&=>and"] - } - }, - "filter": { - "my_stopwords" : { - "type" : "stop", - "stopwords" : ["the", "a"] - } - }, - "analyzer": { - "my_analyzer" : { - "type" : "custom", - "char_filter" : ["html_strip", "&_to_and"], - "tokenizer" : "standard", - "filter" : ["lowercase", "my_stopwords"] - } - } - } - } -} -``` - -测试: - -```json -GET /my_index/_analyze -{ - "analyzer" : "my_analyzer", - "text" : "The quick & brown fox" -} -``` - -结果: - -```json -{ - "tokens": [ - { - "token": "quick", - "start_offset": 4, - "end_offset": 9, - "type": "", - "position": 1 - }, - { - "token": "and", - "start_offset": 10, - "end_offset": 11, - "type": "", - "position": 2 - }, - { - "token": "brown", - "start_offset": 12, - "end_offset": 17, - "type": "", - "position": 3 - }, - { - "token": "fox", - "start_offset": 18, - "end_offset": 21, - "type": "", - "position": 4 - } - ] -} -``` - -把分析器应用在 string 字段上: - -```json -PUT /my_index/_mapping/my_type -{ - "properties": { - "title": { - "type": "string", - "analyzer": "my_analyzer" - } - } -} -``` - -### 类型和映射 - -同一个索引下,不同的类型type应该有相同的结构,映射也应该相同。因为 Lucene 会将同一个索引下的所有字段的映射扁平化,相同字段不同映射会导致冲突。 - -```json -{ - "data": { - "mappings": { - "people": { - "properties": { - "name": { - "type": "string", - }, - "address": { - "type": "string" - } - } - }, - "transactions": { - "properties": { - "timestamp": { - "type": "date", - "format": "strict_date_optional_time" - }, - "message": { - "type": "string" - } - } - } - } - } -} -``` - -后台 Lucene 将创建一个映射: - -```json -{ - "data": { - "mappings": { - "_type": { - "type": "string", - "index": "not_analyzed" - }, - "name": { - "type": "string" - } - "address": { - "type": "string" - } - "timestamp": { - "type": "long" - } - "message": { - "type": "string" - } - } - } -} -``` - -因此,类型不适合 *完全不同类型的数据* 。如果两个类型的字段集是互不相同的,这就意味着索引中将有一半的数据是空的(字段将是 *稀疏的* ),最终将导致性能问题。在这种情况下,最好是使用两个单独的索引。 - - -### 根对象 - -映射最高的一层被称为根对象。它可能包含以下几项: - -- properties 节点,列出文档每个字段的映射 -- 下划线开头的元数据字段,如 \_type,_id 和 \_source -- 设置项,控制如何动态处理新的字段,例如 `analyzer` 、 `dynamic_date_formats` 和`dynamic_templates` -- 其他设置,可以同时应用在根对象和其他 `object` 类型的字段上,例如 `enabled` 、 `dynamic` 和 `include_in_all` - - - -## 分片内部原理 - -### 倒排索引 - -倒排索引包含一个有序列表,列表包含所有文档出现过的不重复个体,或称为 *词项* ,对于每一个词项,包含了它所有曾出现过文档的列表。 - -| Term | doc1 | doc2 | doc3 | -| ----- | ---- | ---- | ---- | -| sport | x | | x | -| music | | x | | -| chess | x | x | x | - - - -## 深入搜索 - -### 精确值查找 - -term 查询被用于精确值匹配,这些精确值可能是数字、时间、布尔或者那些 `not_analyzed` 的字符串。 - -```json -GET /company/employee/_search -{ - "query" : { - "term" : { - "interests" : "listen" - } - } -} -``` - -hire_date 在过去四年内的文档: - -```json -GET company/employee/_search -{ - "query": { - "constant_score": { - "filter": { - "range": { - "hire_date": { - "gt": "now-4y" - } - } - } - } - } -} -``` - -#### term 查询文本 - -删除旧索引(因为旧索引字段都是 analyzed 的,它的映射不正确)然后创建一个能正确映射的新索引: - -```json -PUT /my_store -{ - "mappings" : { - "products" : { - "properties": { - "productID" : { - "type" : "keyword",//es6废除了string,index是boolean值,keyword不分词,text分词 - } - } - } - } -} -``` - -放进数据: - -```json -POST /my_store/products/_bulk -{ "index": { "_id": 1 }} -{ "price" : 10, "productID" : "XHDK-A-1293-#fJ3" } -{ "index": { "_id": 2 }} -{ "price" : 20, "productID" : "KDKE-B-9947-#kL5" } -{ "index": { "_id": 3 }} -{ "price" : 30, "productID" : "JODL-X-1937-#pV7" } -{ "index": { "_id": 4 }} -{ "price" : 30, "productID" : "QQPX-R-3956-#aD8" } -``` - -查询文本: - -```json -GET /my_store/products/_search -{ - "query" : { - "constant_score" : { - "filter" : { - "term" : { - "productID" : "XHDK-A-1293-#fJ3" - } - } - } - } -} -``` - -#### terms 查询 - -允许指定多值进行匹配。如果这个字段包含了指定值中的任何一个值,那么这个文档满足条件。 - -```json -GET /company/employee/_search -{ - "query" : { - "terms" : { - "interests" : ["listen", "music", "sport"] - } - } -} -``` - -置入 `filter` 语句的常量评分查询: - -```json -GET /my_store/products/_search -{ - "query": { - "constant_score": { - "filter": { - "terms" : { - "interests" : ["listen", "music", "sport"] - } - }, - "boost": 1.2 - } - } -} -``` - -interests 为 listen、music 和 sport 的文档会被匹配。 - -### 全文搜索 - -#### match 查询 - -match 查询是对全文进行查询。 - -如果有多个搜索关键字, Elastic 认为它们是`or`关系。下面例子返回 interests 为 sport 或者 music 的文档。 - -```json -GET _search -{ - "query" : { - "match": { - "interests": "sport music" - } - } -} -``` - -指定词都必须匹配: - -```json -GET _search -{ - "query" : { - "match": { - "interests": { - "query": "sport music", - "operator": "and" - } - } - } -} -``` - -控制匹配的字段数目: - -```json -GET _search -{ - "query" : { - "match": { - "interests": { - "query": "sport music", - "minimum_should_match": 2 - } - } - } -} -``` - -### range 查询 - -查询找出那些落在指定区间内的数字或者时间。 - -#### 数字范围 - -```json -GET company/employee/_search -{ - "query": { - "constant_score": { - "filter": { - "range": { - "age": { - "gt": 20, - "lt": 100 - } - } - } - } - } -} -``` - -`gt`:大于;`gte`:大于或等于;`lt`:小于;`lte`:小于或等于。 - -#### 日期范围 - -```json -GET company/employee/_search -{ - "query": { - "constant_score": { - "filter": { - "range": { - "hire_date": { - "gt": "2015-01-01", - "lt": "2019-01-01" - } - } - } - } - } -} -``` - -### 分页 - -请求得到 1 到 3 页的结果: - -```json -GET /_search?size=5 -GET /_search?size=5&from=5 -GET /_search?size=5&from=10 -``` - -### exists 查询 - -查询字段 interests 中不为空的文档: - -```json -GET /company/employee/_search -{ - "query": { - "constant_score": { - "filter": { - "exists": { - "field": "interests" - } - } - } - } -} -``` - -字段 interests 为空的文档。es6 没有 missing api,直接用 bool 和 exists 实现。 - -```json -GET /company/employee/_search -{ - "query": { - "bool": { - "must_not": { - "exists": { - "field": "interests" - } - } - } - } -} -``` - -### bool 组合查询 - -bool 查询允许在单独的查询中组合任意数量的查询,适用于将不同查询字符串映射到不同字段的情况。bool 查询接收如下参数: - -- must:文档必须匹配这些条件才能被包含进来 -- must_not:文档必须不匹配这些条件才能被包含进来 -- should:如果满足这些语句中的任意语句,将增加 _score,否则没有影响。主要用来修正每个文档的相关性得分 -- filter:必须匹配,但是不影响评分 - -如果没有 `must` 语句,那么至少需要能够匹配其中的一条 `should` 语句。但,如果存在至少一条 `must` 语句,则对 `should` 语句的匹配没有要求。 - -```json -GET /company/employee/_search -{ - "query" : { - "bool" : { - "must" : { "match" : {"first_name" : "tyson"}}, - "must_not" : { "match" : {"last_name" : "tang"}}, - "should" : [ - {"match" : {"interest" : "music"}}, - {"range" : {"age" : { "gte" : 30 }}} - ] - } - } -} -``` - -如果不想因为文档的时间而影响得分,可以添加 filter 过滤掉某个时间段。 - -```json -GET _search -{ - "query" : { - "bool" : { - "must" : { - "match" : { "first_name" : "tyson"} - }, - "must_not" : { - "match" : {"last_name" : "tang"} - }, - "should" : [ - {"match" : { "interests": "sport"}}, - {"range" : { "age" : { "gt" : 20 }}} - ], - "filter" : { - "range" : { "hire_date" : { "gt" : "2015-01-01" }} - } - } - } -} -``` - -可以将查询移到 `bool` 查询的 `filter` 语句中,这样它就自动的转成一个不评分的查询了。 - -```json -{ - "bool": { - "must": { "match": { "title": "how to make millions" }}, - "must_not": { "match": { "tag": "spam" }}, - "should": [ - { "match": { "tag": "starred" }} - ], - "filter": { - "bool": { - "must": [ - { "range": { "date": { "gte": "2014-01-01" }}}, - { "range": { "price": { "lte": 29.99 }}} - ], - "must_not": [ - { "term": { "category": "ebooks" }} - ] - } - } - } -} -``` - -bool 查询使用`minimum_should_match`控制需要匹配的 should 语句的数量: - -```json -GET /my_index/my_type/_search -{ - "query": { - "bool": { - "should": [ - { "match": { "title": "brown" }}, - { "match": { "title": "fox" }}, - { "match": { "title": "dog" }} - ], - "minimum_should_match": 2 - } - } -} -``` - -### 如何使用 bool 查询 - -多次 match 查询: - -```json -GET /company/employee/_search -{ - "query": { - "match": { - "interests": "sport music" - } - } -} -``` - -等价的 bool 查询(存在`must`,则对 `should` 语句的匹配没有要求,否则至少需要能够匹配其中的一条 `should` 语句): - -```json -GET /company/employee/_search -{ - "query": { - "bool": { - "should": [ - { "term" : { "interests": "sport" } }, - { "term" : { "interests" : "music" } } - ] - } - } -} -``` - -使用 and 操作符,返回所有字段都匹配到的文档: - -```json -GET /company/employee/_search -{ - "query": { - "match": { - "interests": { - "query": "sport music", - "operator": "and" - } - } - } -} -``` - -等价的 bool 查询: - -```json -GET /company/employee/_search -{ - "query": { - "bool": { - "must": [ - { "term": { "interests": "sport" }}, - { "term": { "interests": "music" }} - ] - } - } -} -``` - -指定参数 minimum_should_match: - -```json -GET /company/employee/_search -{ - "query": { - "match": { - "interests": { - "query": "sport music chess", - "minimum_should_match" : 2 - } - } - } -} -``` - -等价的 bool 查询: - -```json -GET /company/employee/_search -{ - "query": { - "bool": { - "should": [ - {"term": { "interests": "sport" }}, - {"term": { "interests": "music" }}, - {"term": { "interests": "chess" }} - ], - "minimum_should_match": 2 - } - } -} -``` - -### constant_score 查询 - -constant_score 查询返回的文档`score`都是1。它经常用于只需要执行一个 filter 而没有其它查询的情况下。`score`会受到`boost`影响,出现 tyson 的文档`score`为1.2。 - -```json -GET _search -{ - "query" : { - "constant_score": { - "filter": { - "match" : { "first_name" : "tyson"} - }, - "boost": 1.2 - } - } -} -``` - - - - -### 多字段搜索 - -参考自:[best_fields most_fields cross_fields从内在实现看区别——本质就是前两者是以field为中心,后者是词条为中心](https://www.cnblogs.com/bonelee/p/6827068.html) - -最佳字段 best_fields:搜索结果中应该返回某一个字段匹配到了最多的关键词的文档。 - -多数字段 most_fields:返回匹配了更多的字段的文档,尽可能多地匹配文档。ES会为每个字段生成一个match查询,然后将它们包含在一个bool查询中。 - -跨字段 cross_fields:每个查询的单词都出现在不同的字段中。*cross_fields类型采用了一种以词条为中心(Term-centric)的方法,这种方法和best_fields及most_fields采用的以字段为中心(Field-centric)的方法有很大的区别。它将所有的字段视为一个大的字段,然后在任一字段中搜索每个词条。 - -#### 多字符串查询 - -```json -GET /_search -{ - "query": { - "bool": { - "should": [ - { "match": { // 权重:1/3 - "title": { - "query": "War and Peace", - "boost": 2 - }}}, - { "match": { // 权重:1/3 - "author": { - "query": "Leo Tolstoy", - "boost": 2 - }}}, - { "bool": { //译者信息用布尔查询,降低评分权重 权重:1/3 - "should": [ - { "match": { "translator": "Constance Garnett" }}, - { "match": { "translator": "Louise Maude" }} - ] - }} - ] - } - } -} -``` - -#### multi_match 查询 - -multi_match 查询可以在多个字段上执行相同的 match 查询。`multi_match` 多匹配查询的类型有三种:`best_fields` 、 `most_fields` 和 `cross_fields` (最佳字段、多数字段、跨字段)。 - -```json -GET company/employee/_search -{ - "query" : { - "multi_match": { - "query": "sport shoes", - "type": "best_fields", - "fields": [ "interests", "first_name" ], - "tie_breaker": 0.3, - "minimum_should_match": 1 - } - } -} -``` - -模糊匹配: - -```json -GET company/employee/_search -{ - "query" : { - "multi_match": { - "query": "tyson dai", - "type": "best_fields", - "fields": "*_name", - "tie_breaker": 0.3, - "minimum_should_match": "30%" - } - } -} -``` - -提升字段的权重,first_name 字段的 `boost` 值为 `2` : - -```json -GET company/employee/_search -{ - "query" : { - "multi_match": { - "query": "tyson dai", - "type": "best_fields", - "fields": ["*_name", "first_name^3"], - "tie_breaker": 0.3, - "minimum_should_match": "30%" - } - } -} -``` - -#### 多字段映射 - -对字段索引两次: 一次使用词干模式以及一次非词干模式。 - -```json -DELETE /my_index - -PUT /my_index -{ - "settings": { "number_of_shards": 1 }, - "mappings": { - "my_type": { - "properties": { - "title": { - "type": "string", - "analyzer": "english",//提取词干 - "fields": { - "std": { - "type": "string", - "analyzer": "standard" - } - } - } - } - } - } -} -``` - -`title` 字段使用 `english` 分析器来提取词干;`title.std` 字段使用 `standard` 标准分析器,所以没有词干提取。 - -索引文档: - -```json -PUT /my_index/my_type/1 -{ "title": "My rabbit jumps" } - -PUT /my_index/my_type/2 -{ "title": "Jumping jack rabbits" } -``` - -multi_match 查询: - -```json -GET /my_index/_search -{ - "query": { - "multi_match": { - "query": "jumping rabbits", - "type": "most_fields", - "fields": [ "title", "title.std" ] - } - } -} -``` - -文档2匹配度更高,因为 title.std 不会提取词干,只有文档2是匹配的。 - -设置`title` 字段的 `boost` 的值为 `10`,提升 title 字段的权重: - -```json -GET /my_index/_search -{ - "query": { - "multi_match": { - "query": "jumping rabbits", - "type": "most_fields", - "fields": [ "title^10", "title.std" ] - } - } -} -``` - -#### copy_to 定制组合 field - -定制组合 field 与 cross_field 跨字段查询类似,根据两者的实际性能选择具体方案。 - -创建映射: - -```json -PUT my_index1 -{ - "mappings": { - "my_type": { - "properties": { - "first_name": { - "type": "keyword", - "copy_to": "full_name" - }, - "last_name": { - "type": "keyword", - "copy_to": "full_name" - }, - "full_name": { - "type": "text", - "fielddata": true - } - } - } - } -} -``` - -插入数据: - -```json -PUT my_index1/my_type/1 -{ - "first_name": "John", - "last_name": "Smith" -} -``` - -校验查询: - -```json -GET my_index1/_search -{ - "query": { - "match": { - "full_name": { - "query": "John Smith", - "operator": "and" - } - } - } -} - -``` - -`copy_to` 设置对 multi-field 无效。如果尝试这样配置映射,Elasticsearch 会抛异常。多字段只是以不同方式简单索引主字段;它们没有自己的数据源。 - -```json -PUT /my_index -{ - "mappings": { - "person": { - "properties": { - "first_name": { - "type": "string", - "copy_to": "full_name", - "fields": { - "raw": { - "type": "string", - "index": "not_analyzed" - } - } - }, - "full_name": { - "type": "string" - } - } - } - } -} -``` - -first_name 是主字段,first_name.raw 是多字段。 - - - -## springboot 集成 es - -[springboot 集成 es](https://blog.csdn.net/cwenao/article/details/54943505) - -[springboot整合elasticsearch5.x以及IK分词器做全文检索](https://blog.csdn.net/chenxihua1/article/details/94546282) - - - -## mall - -创建文档: - -```json -POST /pms/product -{ - "productSn": "HNTBJ2E080A", - "brandId": 50, - "brandName": "海澜之家", - "productCategoryId": 8, - "productCategoryName": "T恤", - "pic": "http://macro-oss.oss-cn-shenzhen.aliyuncs.com/mall/images/20180615/5ac98b64N70acd82f.jpg!cc_350x449.jpg", - "name": "HLA海澜之家蓝灰花纹圆领针织布短袖T恤", - "subTitle": "2018夏季新品短袖T恤男HNTBJ2E080A 蓝灰花纹80 175/92A/L80A 蓝灰花纹80 175/92A/L", - "keywords": "", - "price": 98, - "sale": 0, - "newStatus": 0, - "recommandStatus": 0, - "stock": 100, - "promotionType": 0, - "sort": 0, - "attrValueList": [ - { - "id": 183, - "productAttributeId": 24, - "value": null, - "type": 1, - "name": "商品编号" - }, - { - "id": 184, - "productAttributeId": 25, - "value": "夏季", - "type": 1, - "name": "适用季节" - } - ] -} -``` - -获取文档映射:`GET /pms/_mapping/product` - -按字段查询:`GET /pms/product/_search?q=subTitle:2018` - -match查询: - -```json -GET /pms/product/_search -{ - "query": { - "match": { - "brandName": "小米" - } - } -} -``` - - - - - diff --git "a/\345\205\266\344\273\226/code.md" "b/\345\205\266\344\273\226/code.md" deleted file mode 100644 index 074879c..0000000 --- "a/\345\205\266\344\273\226/code.md" +++ /dev/null @@ -1,77 +0,0 @@ - - - - -- [单例模式](#%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F) -- [Java8](#java8) - - - -## 单例模式 - -双重检查锁定: - -```java -public class Singleton { - private static volatile Singleton instance = null; //volatile - private Singleton(){} - public static Singleton getInstance() { - if (instance == null) { //两次检查,降低同步开销 - synchronized (Singleton.class) { - if (instance == null) { - instance = new Singleton(); - } - } - } - return instance; - } -} -``` - -静态内部类: - -```java -public class Singleton { - private static class SingletonHolder { - public static Singleton instance = new Singleton(); - } - private Singleton() {} - public static Singleton getSingleton() { - return SingletonHolder.instance;// 这里将导致SingletonHolder类被初始化 - } -} -``` - - - -## Java8 - -排序: - -```java -stringCollection - .stream() - .sorted() - .filter((s) -> s.startsWith("a")) - .forEach(System.out::println); - -// "aaa1", "aaa2" - -list.stream().sorted(Comparator.comparing(Student::getAge).reversed()); -list.stream().sorted(Comparator.comparing(Student::getAge)); -``` - -过滤: - -```java -stringCollection - .stream() - .sorted() - .filter((s) -> s.startsWith("a")) - .forEach(System.out::println); - -// "aaa1", "aaa2" -``` - - - diff --git "a/\345\205\266\344\273\226/linux.md" "b/\345\205\266\344\273\226/linux.md" deleted file mode 100644 index d2e1663..0000000 --- "a/\345\205\266\344\273\226/linux.md" +++ /dev/null @@ -1,417 +0,0 @@ - - - - -- [基本命令](#%E5%9F%BA%E6%9C%AC%E5%91%BD%E4%BB%A4) - - [与或](#%E4%B8%8E%E6%88%96) - - [help](#help) -- [文件](#%E6%96%87%E4%BB%B6) - - [当前路径](#%E5%BD%93%E5%89%8D%E8%B7%AF%E5%BE%84) - - [查看目录](#%E6%9F%A5%E7%9C%8B%E7%9B%AE%E5%BD%95) - - [查看文件](#%E6%9F%A5%E7%9C%8B%E6%96%87%E4%BB%B6) - - [文件操作](#%E6%96%87%E4%BB%B6%E6%93%8D%E4%BD%9C) - - [vim](#vim) - - [查找文件](#%E6%9F%A5%E6%89%BE%E6%96%87%E4%BB%B6) - - [whereis](#whereis) - - [locate](#locate) - - [which](#which) - - [find](#find) - - [权限](#%E6%9D%83%E9%99%90) -- [服务](#%E6%9C%8D%E5%8A%A1) -- [进程端口](#%E8%BF%9B%E7%A8%8B%E7%AB%AF%E5%8F%A3) -- [grep](#grep) - - [高亮查找](#%E9%AB%98%E4%BA%AE%E6%9F%A5%E6%89%BE) - - [环顾四周](#%E7%8E%AF%E9%A1%BE%E5%9B%9B%E5%91%A8) - - [打印行号](#%E6%89%93%E5%8D%B0%E8%A1%8C%E5%8F%B7) - - [统计行数](#%E7%BB%9F%E8%AE%A1%E8%A1%8C%E6%95%B0) - - [不区分大小写](#%E4%B8%8D%E5%8C%BA%E5%88%86%E5%A4%A7%E5%B0%8F%E5%86%99) - - [反查](#%E5%8F%8D%E6%9F%A5) - - [多文件查找](#%E5%A4%9A%E6%96%87%E4%BB%B6%E6%9F%A5%E6%89%BE) -- [sed](#sed) -- [磁盘](#%E7%A3%81%E7%9B%98) - - [mount](#mount) -- [内存](#%E5%86%85%E5%AD%98) - - - -## 基本命令 - -### 与或 - -方式:command1 && command2 -如果command1执行成功,则执行command2 -方式:command1 || command2 -如果command1执行失败,则执行command2 - -### help - -help 命令是用于显示 shell 内建命令的简要帮助信息 `help cd` -man得到的内容比用 help 更多更详细,而且man没有内建与外部命令的区分 - - - -## 文件 - -### 当前路径 - -查看当前路径:pwd - -### 查看目录 -目录下各个文件容量。 - -```bash -ls -lht -``` - --l:使用长格式列出文件及目录信息 - --t:根据最后的修改时间排序 - -### 查看文件 - -列出文件:`ls -l` 可以简写为 `ll`(别名,ubantu下不支持) - -cat:输出文件所有内容 - -tail -10 demo.txt:显示最后10行 - -tail -f -n 100 demo.txt:实时查看文件,显示最后100行 - -head -10 demo.txt - -head -20 nohup.out | tail -10:第11行到20行 - -less:分页显示工具,可以前后翻页查看文件。pagedown下一页,pageup上一页。 - -```bash -less demo.log -``` - -查看占用磁盘容量最多的文件: - -```bash - du -a /var | sort -n -r | head -n 10 -``` - -### 文件操作 - -新建文件夹: mkdir - -新建文件:touch - -编辑文件:vi - -删除文件:rm -rf (-f强制删除 -r 递归删除) - -删除空目录:`rmdir dir`。rmdir命令只能删除空目录。当要删除非空目录时,就要使用带有“-R”选项的rm命令。 - -复制文件:cp -r dir1 dir2 表示将dir1及其dir1下所包含的文件复制到dir2下。-r 复制目录 - -文件重命名:mv before.txt after.txt - -解压:tar -zxvf jdk-8u131-linux-x64.tar.gz -C /usr/lib/jvm (-C解压到指定的目录) - -解压带有中文字符的zip文件:unzip -O gbk filename.zip - -### vim - -退出编辑:输入i进入编辑模式,按下esc退出编辑,输入:w,保存不退出;:z/:wq,保存后退出;:q!,退出不保存(w:write,q:quit) - -搜索:输入/+搜索的词,回车,输入n跳转下一个匹配词 - -删除行:按esc进入命令模式,dd删除当前行,Ndd删除当前行以下的n行 - -撤销上一个操作:命令模式下,按 u - -恢复上一步被撤销的操作:ctrl+r - -当vim非正常关闭时,会产生swp文件,在当前目录删除:rm .xxx.swp - -### 查找文件 - -#### whereis - -简单快速,直接从数据库查找,whereis 只能搜索二进制文件(-b),man 帮助文件(-m)和源代码文件(-s) - -查找tomcat:`whereis tomcat` - -#### locate - -快而全,通过“ /var/lib/mlocate/mlocate.db ”数据库查找,数据库不是实时更新,递归查找指定目录下的不同文件类型,刚添加的文件需手 - -动执行updatedb。 - -查找图片:locate background.jpg - -查找某个目录下所有图片:locate -ic /usr/share/\*.jpg(-c统计数目,-i忽略大小写) - -#### which - -小而精,which 本身是shell內建的一个命令,它只从PATH环境变量指定的路径去搜索,通常使用which来确定是否安装了某个命令。 - -查看是否安装man:`which man` - -#### find - -最强大,速度慢,第一个参数是搜索的地方。 - -```bash -find / -name background.jpg -sudo find / -name .ssh //隐藏文件 -``` - -### 权限 - -```bash -chmod +x xxx -chmod u+x shutdown -sudo chmod 744 hello.c -sudo chown tyson hello.c -chown mysql slow.log -``` - - - -## 服务 - -启动服务: - -```bash -service mysqld start -/sbin/service rabbitmq-server start -redis-server /etc/redis.conf -redis-cli #启动redis客户端 -``` - -停止服务: - -```bash -service mysqld stop -/sbin/service rabbitmq-server stop #service是个文件,路径/usr/sbin/service,根目录下有/sbin链接到/usr/sbin,可以在任何文件夹使用此命令 -redis-cli -a password shutdown #关闭redis服务,没有设置密码则直接redis-cli shutdown -``` - -[redis安装](https://cloud.tencent.com/developer/article/1119337) - - - -## 进程端口 - -查看所有进程:`ps -ef` -e显示所有进程,-f显示进程对应的命令行 - -查看特定进程:`ps -ef | grep nginx` - -根据进程号查看进程信息:`ps aux | grep pid` - -根据进程号查找线程:`ps -T -p xxx` - -正常结束进程:`kill -15 pid` 默认-15(SIGTERM)可以省略 - -杀死进程:`kill -9 pid` -9(SIGKILL)强制中断 - -放通端口:iptables -I INPUT -p TCP --dport 8013 -j ACCEPT - -查看监听的端口:`netstat -tunlp` - -查看端口信息:`netstat -tunlp | grep 8000` - -- -t - 显示 TCP 端口。 -- -u - 显示 UDP 端口。 -- -n - 显示数字地址而不是主机名。 -- -l - 仅显示侦听端口。 -- -p - 显示进程的 PID 和名称。仅当以 root 或 sudo 用户身份运行命令时,才会显示此信息。 - - - -## grep - -global search regular expression and print out the line。全面搜索正则表达式,并将其打印出来。 - -### 高亮查找 - -```bash -grep --color "leo" /etc/passwd -``` - -### 环顾四周 - -`-A`表示除了展示匹配行之外,还要展示出匹配行下面的若干行。`-B`表示除了展示匹配行之外,还要展示出匹配行上面的若干行。`-C`表示除了展示匹配行之外,还要展示出匹配行上面和下面各若干行。 - -```bash -grep -C 1 leo passwd -``` - -### 打印行号 - -查找字符串mail,打印行号: - -```bash -[root@VM-0-7-centos etc]# grep -n mail /etc/passwd -9:mail:x:8:12:mail:/var/spool/mail:/sbin/nologin -``` - -### 统计行数 - -```bash -[root@VM-0-7-centos etc]# grep -c mail /etc/passwd -1 -``` - -### 不区分大小写 - -```bash -[root@VM-0-7-centos etc]# grep -i mail /etc/passwd -mail:x:8:12:mail:/var/spool/mail:/sbin/nologin -``` - -### 反查 - -搜索不包含root的行,-v实现反查效果: - -```bash -[root@VM-0-7-centos etc]# grep -v "root" /etc/passwd -bin:x:1:1:bin:/bin:/sbin/nologin -daemon:x:2:2:daemon:/sbin:/sbin/nologin -adm:x:3:4:adm:/var/adm:/sbin/nologin -``` - -### 多文件查找 - -找出内容中含有first单词的文件: - -```bash -[root@VM-0-7-centos test]# grep -l "first" *.txt -1.txt -``` - -找出不含 first 单词的文件: - -```bash -[root@VM-0-7-centos test]# grep -L "first" *.txt -2.txt -3.txt -``` - -搜索/etc/passwd文件中开头是mail的行: - -```bash -[root@VM-0-7-centos test]# grep '^mail' /etc/passwd -mail:x:8:12:mail:/var/spool/mail:/sbin/nologin -``` - -搜索 /etc/passwd 文件中行尾是nologin的行: - -```bash -[root@VM-0-7-centos test]# grep 'nologin$' /etc/passwd -bin:x:1:1:bin:/bin:/sbin/nologin -daemon:x:2:2:daemon:/sbin:/sbin/nologin -``` - -精确匹配某个词,单纯使用bin会把sbin等也搜索出来: - -```bash -[root@VM-0-7-centos test]# grep -w 'bin' /etc/passwd -root:x:0:0:root:/root:/bin/bash -bin:x:1:1:bin:/bin:/sbin/nologin -``` - -正则表达式: - -```mysql -# 将匹配以'z'开头以'o'结尾的所有字符串 -$ echo 'zero\nzo\nzoo' | grep 'z.*o' - -# 将匹配以'z'开头以'o'结尾,中间包含一个任意字符的字符串 -$ echo 'zero\nzo\nzoo' | grep 'z.o' - -# 将匹配以'z'开头,以任意多个'o'结尾的字符串 -$ echo 'zero\nzo\nzoo' | grep 'zo*' - - -# grep命令用于打印输出文本中匹配的模式串 -# grep默认是区分大小写的,这里将匹配所有的小写字母 -$ echo '1234\nabcd' | grep '[a-z]' - -# 将匹配所有的数字 -$ echo '1234\nabcd' | grep '[0-9]' - -# 将匹配所有的数字 -$ echo '1234\nabcd' | grep '[[:digit:]]' - -# 将匹配所有的小写字母 -$ echo '1234\nabcd' | grep '[[:lower:]]' - -# 将匹配所有的大写字母 -$ echo '1234\nabcd' | grep '[[:upper:]]' - -# 将匹配所有的字母和数字,包括0-9,a-z,A-Z -$ echo '1234\nabcd' | grep '[[:alnum:]]' - -# 将匹配所有的字母 -$ echo '1234\nabcd' | grep '[[:alpha:]]' -``` - - - -## sed - -处理、编辑文本文件。 - -```bash -# 打印2-5行 nl输出文件内容带上行号 -$ nl passwd | sed -n '2,5p' - -# 删除2-5行 -nl /etc/passwd | sed '2,5d' - -# 删除第三到最后一行 -nl /etc/passwd | sed '3,$d' -``` - - - -## 磁盘 - -以容易阅读的方式,显示磁盘使用情况: - -```bash -df -h -``` - -指定文件磁盘使用情况: - -```bash -[root@VM-0-7-centos etc]# df /etc/dhcp -Filesystem 1K-blocks Used Available Use% Mounted on -/dev/vda1 51539404 23774760 25567400 49% / -``` - -### mount - -挂载Linux系统外的文件。 - -挂载hda2到/mnt/hda2目录下。确定目录/mnt/hda2 已经存在。 - -```bash -mount /dev/hda2 /mnt/hda2 -``` - -卸载hda2硬盘。先从挂载点/mnt/hda2退出。 - -```bash -umount /dev/hda2 -``` - - - -## 内存 - -`free -h` -h以可读的方式展示。 - -``` - total used free shared buff/cache available -Mem: 1.8G 1.1G 107M 1.1M 562M 515M -Swap: 0B 0B 0B - -``` - diff --git "a/\345\205\266\344\273\226/note.md" "b/\345\205\266\344\273\226/note.md" deleted file mode 100644 index dad41bf..0000000 --- "a/\345\205\266\344\273\226/note.md" +++ /dev/null @@ -1,286 +0,0 @@ - - - - -- [RESTful](#restful) -- [JWT](#jwt) - - [原理](#%E5%8E%9F%E7%90%86) - - [数据结构](#%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84) -- [金额使用decimal存储类型的缺点](#%E9%87%91%E9%A2%9D%E4%BD%BF%E7%94%A8decimal%E5%AD%98%E5%82%A8%E7%B1%BB%E5%9E%8B%E7%9A%84%E7%BC%BA%E7%82%B9) -- [Maven的作用](#maven%E7%9A%84%E4%BD%9C%E7%94%A8) -- [为什么使用消息队列](#%E4%B8%BA%E4%BB%80%E4%B9%88%E4%BD%BF%E7%94%A8%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97) -- [秒杀系统](#%E7%A7%92%E6%9D%80%E7%B3%BB%E7%BB%9F) -- [shiro](#shiro) -- [分布式锁](#%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81) -- [dubbo](#dubbo) -- [分布式ID](#%E5%88%86%E5%B8%83%E5%BC%8Fid) - - [UUID](#uuid) - - [数据库自增ID](#%E6%95%B0%E6%8D%AE%E5%BA%93%E8%87%AA%E5%A2%9Eid) - - [基于Redis模式](#%E5%9F%BA%E4%BA%8Eredis%E6%A8%A1%E5%BC%8F) - - [雪花算法](#%E9%9B%AA%E8%8A%B1%E7%AE%97%E6%B3%95) -- [foeach和for的效率](#foeach%E5%92%8Cfor%E7%9A%84%E6%95%88%E7%8E%87) -- [epoll](#epoll) -- [防止重复请求](#%E9%98%B2%E6%AD%A2%E9%87%8D%E5%A4%8D%E8%AF%B7%E6%B1%82) -- [接口幂等性](#%E6%8E%A5%E5%8F%A3%E5%B9%82%E7%AD%89%E6%80%A7) -- [令牌桶算法](#%E4%BB%A4%E7%89%8C%E6%A1%B6%E7%AE%97%E6%B3%95) -- [超卖问题](#%E8%B6%85%E5%8D%96%E9%97%AE%E9%A2%98) -- [TPS和QPS](#tps%E5%92%8Cqps) -- [微服务](#%E5%BE%AE%E6%9C%8D%E5%8A%A1) -- [进程通信](#%E8%BF%9B%E7%A8%8B%E9%80%9A%E4%BF%A1) -- [分布式事务](#%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1) - - - -## RESTful - -[RESTful API](https://www.zhihu.com/question/28557115) - -REST -- REpresentational State Transfer 直接翻译:表现层状态转移。URL定位资源,用HTTP动词(GET,POST,DELETE,DETC)描述操作。 - -REST描述的是在网络中client和server的一种交互形式。REST本身不实用,实用的是如何设计 RESTful API(REST风格的网络接口)。 - - Server提供的RESTful API中,URL中只使用名词来指定资源,原则上不使用动词。通过HTTP方法来实现资源的状态扭转: - -```java -DELETE http://api.qc.com/v1/friends/{id} -GET http://api.qc.com/v1/friends -``` - - - - - -## JWT - -[JWT教程](http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html) | [数字签名](https://blog.csdn.net/qq_21514303/article/details/82898984) - -JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案。 - -session认证流程: - -1、用户向服务器发送用户名和密码。 - -2、服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登录时间等等。 - -3、服务器向用户返回一个 session_id,写入用户的 Cookie。 - -4、用户随后的每一次请求,都会通过 Cookie,将 session_id 传回服务器。 - -5、服务器收到 session_id,找到前期保存的数据,由此得知用户的身份。 - -如果需要跨域认证,即用户登录了a网站,再访问b网站需要自动登录,则要求session数据共享。可以将session数据持久化,存到数据库来实现session共享。 - -JWT不在服务器端保存session数据,所有数据都保存在客户端,每次请求都发回服务器。它是通过算法实现对Token合法性的验证,不依赖数据库,只要密钥和算法相同,**不同服务器程序生成的Token可以互相验证**。 - -### 原理 - -用户使用用户名和密码登录服务器。服务器认证完成后,使用私钥生成一个 JSON 对象(包括用户名、权限等),发回给用户。 - -```json -{ - "username": "张三", - "roles": "管理员", - "expire": "2018年7月1日0点0分" -} -``` - -以后,用户与服务端通信的时候,都要发回这个 JSON 对象。服务器依据JSON对象认证用户身份。为了防止用户篡改数据,服务器在生成JSON对象的时候会使用私钥进行加密生成签名。 - -![](../img/others/json-web-token.png) - -### 数据结构 - -JWT数据分为三个部分: - -- Header(头部) -- Payload(负载) -- Signature(签名) - -Header和Payload会使用 Base64URL 算法转成字符串,然后三部分用`.`进行分隔: - -```javascript -Header.Payload.Signature -``` - -Header 部分是一个 JSON 对象,描述 JWT 的元数据。 - -```javascript -{ - "alg": "HS256", - "typ": "JWT" -} -``` - -Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据,如用户名、权限等。 - -Signature 部分是对前两部分的签名,防止数据篡改。首先需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后使用 Header 里面指定的签名算法(默认是 HMAC SHA256),根据下面的公式产生签名。 - -```javascript -HMACSHA256( - base64UrlEncode(header) + "." + - base64UrlEncode(payload), - secret) -``` - - - -## 金额使用decimal存储类型的缺点 - -- 占用存储空间。浮点类型在存储同样范围的值时,通常比decimal使用更少的空间 -- 使用decimal计算效率不高 - -因为使用decimal时间和空间开销较大,选用int作为数据库存储格式比较合适,可以同时避免浮点存储计算的不精确和decimal的缺点。对于存储数值较大或者保留小数较多的数字,数据库存储结构可以选择bigint,可以同时避免浮点存储计算不精准和DECIMAL精度计算代价高的问题 - - - -## Maven的作用 - -管理jar包依赖;根据配置自动下载相应jar包; 热部署,编译* package命令完成了项目编译、单元测试、打包功能,但没有把打好的可执行jar包(war包或其它形式的包)布署到本地maven仓库和远程maven私服仓库* install命令完成了项目编译、单元测试、打包功能,同时把打好的可执行jar包(war包或其它形式的包)布署到本地maven仓库,但没有布署到远程maven私服仓库* deploy命令完成了项目编译、单元测试、打包功能,同时把打好的可执行jar包(war包或其它形式的包)布署到本地maven仓库和远程maven私服仓库  - - - -## 为什么使用消息队列 - -在高并发环境下,由于来不及同步处理,请求往往会发生堵塞,比如说,大量的insert,update之类的请求同时到达MySQL,直接导致无数的行锁表锁,甚至最后请求会堆积过多,从而触发too many connections错误。通过使用消息队列,我们可以异步处理请求,从而缓解系统的压力。 -异步通信就是你发了一个请求,没收到回答的时候,你发了另一个请求。 - - - - - -## shiro - -作用: - -1. 验证用户身份 -2. 用户访问权限控制 -3. 支持提供`Remember Me`服务 - - - -## 分布式锁 - -在多线程的环境下,为了保证一个代码块在同一时间只能由一个线程访问,Java中我们一般可以使用synchronized语法和ReetrantLock去保证,这实际上是本地锁的方式。 - -在一个分布式系统中,多台机器上部署了多个服务,当客户端一个用户发起一个数据插入请求时,如果没有分布式锁机制保证,那么那多台机器上的多个服务可能进行并发插入操作,导致数据重复插入,对于某些不允许有多余数据的业务来说,这就会造成问题。而分布式锁机制就是为了解决类似这类问题,保证多个服务之间互斥的访问共享资源,如果一个服务抢占了分布式锁,其他服务没获取到锁,就不进行后续操作。 - -![](https://user-gold-cdn.xitu.io/2019/4/25/16a53749547937bb?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) - -实现分布式锁有以下几种方式: - -- 基于数据库 - -- 基于Redis - - 使用lua优点,可以保证多个命令是一次行传输到Redis服务器并且是串行执行的,保证串行执行的命令中不行插入其他命令,防止并发问题。 - -- 基于zookeeper - - - -## dubbo - -分布式协调服务:在分布式系统中共享配置,协调锁资源,提供命名服务。 - - - -## 分布式ID - -`全局唯一ID`就叫`分布式ID`。 - -### UUID - -```java -String uuid = UUID.randomUUID().toString().replaceAll("-",""); -``` - -优点: - -- 生成足够简单,本地生成无网络消耗,具有唯一性 - -缺点: - -- 无序的字符串,不具备趋势自增特性 -- 没有具体的业务含义 - -### 数据库自增ID - -优点: - -- 简单方便,有序递增,方便排序和分页 - -缺点: - -- 分库分表会带来问题,需要进行改造。 - -### 基于Redis模式 - -利用`redis`的 `incr`命令实现ID的原子性自增。 - -### 雪花算法 - -![](https://user-gold-cdn.xitu.io/2020/2/16/1704bd6d27b09766?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) - - - -## foeach和for的效率 - -1、foreach适用于数组或实现了iterator的集合类。foreach就是使用Iterator接口来实现对集合的遍历的。 - -2、在用foreach循环遍历一个集合时,不能改变集合中的元素,如增加元素、修改元素。否则会抛出ConcurrentModificationException异常。普通 for 循环遍历过程可以修改元素。 - - - -## epoll - -select、poll和epoll都是IO多路复用的机制。 - - - -## 防止重复请求 - -1. 前端提交按钮置灰几秒钟。 -2. 后端使用Redis保存请求的唯一id(业务参数或者前端自己生成,保证唯一),当第一次请求过来,从redis中取id,如果value为null,说明是第一次请求,将这个id存入redis;如果不为null,说明是重复请求,直接抛异常。 - - - -## 接口幂等性 - -幂等性:接口一次调用和多次调用的结果一致。 - -对于业务中需要考虑幂等性的地方一般都是接口的重复请求,重复请求是指同一个请求因为某些原因被多次提交。导致这个情况会有几种场景: - -- 前端重复提交:提交订单,用户快速重复点击多次,造成后端生成多个内容重复的订单。 -- 接口超时重试:对于给第三方调用的接口,为了防止网络抖动或其他原因造成请求丢失,这样的接口一般都会设计成超时重试多次。 -- 消息重复消费:MQ消息中间件,消息重复消费。 - - - -## 令牌桶算法 - -令牌桶算法的原理是系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。 - - - - - -## TPS和QPS - -**QPS**:Queries Per Second意思是“每秒查询率”,是一台服务器每秒处理的查询次数。 - -**TPS:**是TransactionsPerSecond的缩写,也就是事务数/秒。它是软件测试结果的测量单位。一个事务是指一个客户机向服务器发送请求然后服务器做出反应的过程。客户机在发送请求时开始计时,收到服务器响应后结束计时,以此来计算使用的时间和完成的事务个数。 - -Tps即每秒处理事务数,包括了三个过程: - -1)用户请求服务器 - -2)服务器自己的内部处理 - -3)服务器返回给用户 - -如果每秒能够完成N个这三个过程,Tps也就是N; - -对于一个页面的一次访问,形成一个Tps;但一次页面请求,可能产生多次对服务器的请求,这些请求可以计入Qps之中。例如访问一个页面会请求服务器3次,产生一个TPS,产生3个QPS。 - - - diff --git "a/\345\205\266\344\273\226/\345\256\236\346\210\230\347\257\207.md" "b/\345\205\266\344\273\226/\345\256\236\346\210\230\347\257\207.md" deleted file mode 100644 index 7d5164d..0000000 --- "a/\345\205\266\344\273\226/\345\256\236\346\210\230\347\257\207.md" +++ /dev/null @@ -1,93 +0,0 @@ -## OOM问题排查 - -[记一次OOM问题排查!](https://mp.weixin.qq.com/s?__biz=Mzg2OTY1NzY0MQ==&mid=2247486106&idx=1&sn=0d528efad06f0a1440e02983bf336bca&chksm=ce98f7dcf9ef7ecafee10115f1d29bfba56dd8bf694122dd2cb69a53f333b2242522f88aa92e&token=1360123733&lang=zh_CN#rd) - - - -## RT过长,排查思路 - -[Java诊断工具Arthas](https://mp.weixin.qq.com/s/TnLl2OW9XJLSZihcpgP7VQ) - - - -## 限流算法 - -限流是指在系统面临高并发、大流量请求的情况下,限制新的流量对系统的访问,从而保证系统服务的安全性。常用的限流算法有计数器固定窗口算法、滑动窗口算法、漏斗算法和令牌桶算法。 - -### 计数器固定窗口算法 - -计数器固定窗口算法是最基础也是最简单的一种限流算法。原理就是对一段固定时间窗口内的请求进行计数,如果请求数超过了阈值,则舍弃该请求;如果没有达到设定的阈值,则接受该请求,且计数加1。当时间窗口结束时,重置计数器为0。 - -**优点**:实现简单,容易理解。 - -**缺点**:流量曲线可能不够平滑,有“突刺现象”,这样会有两个问题: - -1. **一段时间内(不超过时间窗口)系统服务不可用**。比如窗口大小为1s,限流大小为100,然后恰好在某个窗口的第1ms来了100个请求,然后第2ms-999ms的请求就都会被拒绝,这段时间用户会感觉系统服务不可用。 -2. **窗口切换时可能会产生两倍于阈值流量的请求**。比如窗口大小为1s,限流大小为100,然后恰好在某个窗口的第999ms来了100个请求,窗口前期没有请求,所以这100个请求都会通过。再恰好,下一个窗口的第1ms有来了100个请求,也全部通过了,那也就是在2ms之内通过了200个请求,而我们设定的阈值是100,通过的请求达到了阈值的两倍。 - -### 计数器滑动窗口算法 - -计数器滑动窗口算法是计数器固定窗口算法的改进,解决了固定窗口切换时可能会产生两倍于阈值流量请求的缺点。 - -滑动窗口算法在固定窗口的基础上,将一个计时窗口分成了若干个小窗口,然后每个小窗口维护一个独立的计数器。当请求的时间大于当前窗口的最大时间时,则将计时窗口向前平移一个小窗口。平移时,将第一个小窗口的数据丢弃,然后将第二个小窗口设置为第一个小窗口,同时在最后面新增一个小窗口,将新的请求放在新增的小窗口中。同时要保证整个窗口中所有小窗口的请求数目之后不能超过设定的阈值。 - -将计时窗口划分成一个小窗口,滑动窗口算法就退化成了固定窗口算法。而滑动窗口算法其实就是对请求数进行了更细粒度的限流,窗口划分的越多,则限流越精准。 - -**特点分析** - -1. 避免了计数器固定窗口算法固定窗口切换时可能会产生两倍于阈值流量请求的问题; -2. 和漏斗算法相比,新来的请求也能够被处理到,避免了漏斗算法的饥饿问题。 - -### 漏斗算法 - -漏斗算法的原理也很容易理解。请求来了之后会首先进到漏斗里,然后漏斗以恒定的速率将请求流出进行处理,从而起到平滑流量的作用。当请求的流量过大时,漏斗达到最大容量时会溢出,此时请求被丢弃。从系统的角度来看,我们不知道什么时候会有请求来,也不知道请求会以多大的速率来,这就给系统的安全性埋下了隐患。但是如果加了一层漏斗算法限流之后,就能够保证请求以恒定的速率流出。在系统看来,请求永远是以平滑的传输速率过来,从而起到了保护系统的作用。 - -**特点分析** - -1. **漏桶的漏出速率是固定的,可以起到整流的作用**。即虽然请求的流量可能具有随机性,忽大忽小,但是经过漏斗算法之后,变成了有固定速率的稳定流量,从而对下游的系统起到保护作用。 -2. **不能解决流量突发的问题**。假如设定的漏斗速率是2个/秒,漏斗限流算法的容量是5。然后突然来了10个请求,受限于漏斗的容量,只有5个请求被接受,另外5个被拒绝。你可能会说,漏斗速率是2个/秒,然后瞬间接受了5个请求,这不就解决了流量突发的问题吗?不,这5个请求只是被接受了,但是没有马上被处理,处理的速度仍然是我们设定的2个/秒,所以没有解决流量突发的问题。而接下来我们要谈的令牌桶算法能够在一定程度上解决流量突发的问题。 - -### 令牌桶算法 - -令牌桶算法是对漏斗算法的一种改进,除了能够起到限流的作用外,还允许一定程度的流量突发。在令牌桶算法中,存在一个令牌桶,算法中存在一种机制以恒定的速率向令牌桶中放入令牌。令牌桶也有一定的容量,如果满了令牌就无法放进去了。当请求来时,会首先到令牌桶中去拿令牌,如果拿到了令牌,则该请求会被处理,并消耗掉拿到的令牌;如果令牌桶为空,则该请求会被丢弃。 - -比如过来10个请求,令牌桶算法和漏斗算法一样,都是接受了5个请求,拒绝了5个请求。与漏斗算法不同的是,令牌桶算法马上处理了这5个请求,处理速度可以认为是5个/秒,超过了我们设定的2个/秒的速率,即**允许一定程度的流量突发**。这一点也是和漏斗算法的主要区别。 - -**特点分析** - -令牌桶算法是对漏桶算法的一种改进,除了能够在限制调用的平均速率的同时还允许一定程度的流量突发。 - -### 小结 - -**计数器固定窗口算法**实现简单,容易理解。和漏斗算法相比,新来的请求也能够被马上处理到。但是流量曲线可能不够平滑,有“突刺现象”,在窗口切换时可能会产生两倍于阈值流量的请求。而**计数器滑动窗口算法**作为计数器固定窗口算法的一种改进,有效解决了窗口切换时可能会产生两倍于阈值流量请求的问题。 - -**漏斗算法**能够对流量起到整流的作用,让随机不稳定的流量以固定的速率流出,但是不能解决**流量突发**的问题。**令牌桶算法**作为漏斗算法的一种改进,除了能够起到平滑流量的作用,还允许一定程度的流量突发。 - -令牌桶算法一般用于保护自身的系统,对调用者进行限流,保护自身的系统不被突发的流量打垮。如果自身的系统实际的处理能力强于配置的流量限制时,可以允许一定程度的流量突发,使得实际的处理速率高于配置的速率,充分利用系统资源。而漏斗算法一般用于保护第三方的系统,比如自身的系统需要调用第三方的接口,为了保护第三方的系统不被自身的调用打垮,便可以通过漏斗算法进行限流,保证自身的流量平稳的打到第三方的接口上。 - -参考:https://zhuanlan.zhihu.com/p/228412634 - - - -## 亿级数据分页查询难怎么解决? - -无深翻页需求的实时在线应用:禁止深翻页,数据库 offset < N。 - -确有深翻页需求的离线分析应用:es搜scroll,jdbc自己维护这个读取游标的长链接,持续流式读取。 - - - -## 秒杀场景 - -并发修改库存,会加行锁。rt较高,tps上不去。两种方案: - -1. 分库分表。减小锁粒度 -2. 降低锁的持有时间 - -可以在内存起一个队列,将用户请求放进队列。起一个异步线程,每200ms处理队列数据,批量扣库存。这样就不用每个请求都去扣减库存,减少行锁持有时间。 - -每个请求封装一个锁对象(锁里面包含购买数量),丢进队列,调用wait(200)。当扣库存完成后拿到锁对象执行notify,通知用户请求线程继续往下执行。即每个用户线程都会有一个锁对象,用来阻塞和通知,阻塞是最多200ms - - - -## MySQL表数据量大 diff --git "a/\345\205\266\344\273\226/\350\256\276\350\256\241\346\250\241\345\274\217.md" "b/\345\205\266\344\273\226/\350\256\276\350\256\241\346\250\241\345\274\217.md" deleted file mode 100644 index 3808e3a..0000000 --- "a/\345\205\266\344\273\226/\350\256\276\350\256\241\346\250\241\345\274\217.md" +++ /dev/null @@ -1,437 +0,0 @@ - - - - -- [单例模式](#%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F) - - [饿汉模式](#%E9%A5%BF%E6%B1%89%E6%A8%A1%E5%BC%8F) - - [双重检查锁定](#%E5%8F%8C%E9%87%8D%E6%A3%80%E6%9F%A5%E9%94%81%E5%AE%9A) - - [静态内部类](#%E9%9D%99%E6%80%81%E5%86%85%E9%83%A8%E7%B1%BB) -- [装饰模式](#%E8%A3%85%E9%A5%B0%E6%A8%A1%E5%BC%8F) -- [适配器模式](#%E9%80%82%E9%85%8D%E5%99%A8%E6%A8%A1%E5%BC%8F) -- [观察者模式](#%E8%A7%82%E5%AF%9F%E8%80%85%E6%A8%A1%E5%BC%8F) -- [代理模式](#%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8F) - - [静态代理](#%E9%9D%99%E6%80%81%E4%BB%A3%E7%90%86) - - [动态代理](#%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86) - - [JDK动态代理](#jdk%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86) -- [工厂模式](#%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F) - - [简单工厂模式](#%E7%AE%80%E5%8D%95%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F) - - [工厂方法模式](#%E5%B7%A5%E5%8E%82%E6%96%B9%E6%B3%95%E6%A8%A1%E5%BC%8F) - - [抽象工厂模式](#%E6%8A%BD%E8%B1%A1%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F) -- [模板模式](#%E6%A8%A1%E6%9D%BF%E6%A8%A1%E5%BC%8F) -- [建造者模式](#%E5%BB%BA%E9%80%A0%E8%80%85%E6%A8%A1%E5%BC%8F) - - - -## 设计模式的六大原则 - -- 开闭原则:对扩展开放,对修改关闭,多使用抽象类和接口。 -- 里氏替换原则:基类可以被子类替换,使用抽象类继承,不使用具体类继承。 -- 依赖倒转原则:要依赖于抽象,不要依赖于具体,针对接口编程,不针对实现编程。 -- 接口隔离原则:使用多个隔离的接口,比使用单个接口好,建立最小的接口。 -- 迪米特法则:一个软件实体应当尽可能少地与其他实体发生相互作用,通过中间类建立联系。 -- 合成复用原则:尽量使用合成/聚合,而不是使用继承。 - -## 单例模式 - -需要对实例字段使用线程安全的延迟初始化,使用双重检查锁定的方案;需要对静态字段使用线程安全的延迟初始化,使用静态内部类的方案。 - -### 饿汉模式 - -``` -public class Singleton { - private static Singleton instance = new Singleton(); - private Singleton() {} - public static Singleton newInstance() { - return instance; - } -} -``` -JVM在类的初始化阶段,会执行类的静态方法。在执行类的初始化期间,JVM会去获取Class对象的锁。这个锁可以同步多个线程对同一个类的初始化。 - -饿汉模式只在类加载的时候创建一次实例,没有多线程同步的问题。单例没有用到也会被创建,而且在类加载之后就被创建,内存就被浪费了。 - -### 双重检查锁定 - -instance使用static修饰的原因:getInstance为静态方法,因为静态方法的内部不能直接使用非静态变量,只有静态成员才能在没有创建对象时进行初始化,所以返回的这个实例必须是静态的。 - -``` -public class Singleton { - private static volatile Singleton instance = null; //volatile - private Singleton(){} - public static Singleton getInstance() { - if (instance == null) { - synchronized (Singleton.class) { - if (instance == null) { - instance = new Singleton(); - } - } - } - return instance; - } -} -``` -为什么两次判断`instance == null`: - -| Time | Thread A | Thread B | -| ---- | -------------------- | -------------------- | -| T1 | 检查到`instance`为空 | | -| T2 | | 检查到`instance`为空 | -| T3 | | 初始化对象`A` | -| T4 | | 返回对象`A` | -| T5 | 初始化对象`B` | | -| T6 | 返回对象`B` | | - -`new Singleton()`会执行三个动作:分配内存空间、初始化对象和对象引用指向内存地址。 - -```java -memory = allocate();  // 1:分配对象的内存空间 -ctorInstance(memory);  // 2:初始化对象 -instance = memory;   // 3:设置instance指向刚分配的内存地址 -``` - -由于指令重排优化的存在,导致初始化对象和将对象引用指向内存地址的顺序是不确定的。在某个线程创建单例对象时,会为该对象分配了内存空间并将对象的字段设置为默认值。此时就可以将分配的内存地址赋值给instance字段了,然而该对象可能还没有初始化。若紧接着另外一个线程来调用getInstance,取到的是未初始化的对象,程序就会出错。volatile 可以禁止指令重排序,保证了先初始化对象再赋值给instance变量。 - -| Time | Thread A | Thread B | -| :--- | :----------------------- | :--------------------------------------- | -| T1 | 检查到`instance`为空 | | -| T2 | 获取锁 | | -| T3 | 再次检查到`instance`为空 | | -| T4 | 为`instance`分配内存空间 | | -| T5 | 将`instance`指向内存空间 | | -| T6 | | 检查到`instance`不为空 | -| T7 | | 访问`instance`(此时对象还未完成初始化) | -| T8 | 初始化`instance` | | - -### 静态内部类 - -``` -public class Instance { - private static class InstanceHolder { - public static Instance instance = new Instance(); - } - private Instance() {} - public static Instance getInstance() { - return InstanceHolder.instance ;  // 这里将导致InstanceHolder类被初始化 - } -} -``` -它与饿汉模式一样,也是利用了类初始化机制,因此不存在多线程并发的问题。不一样的是,它是在内部类里面去创建对象实例。这样的话,只要应用中不使用内部类,JVM就不会去加载这个单例类,也就不会创建单例对象,从而实现懒汉式的延迟加载。也就是说这种方式可以同时保证延迟加载和线程安全。 - -![image-20200630092741672](../img/singleton-class-init.png) - -基于类初始化的方案的实现代码更简洁。但基于volatile的双重检查锁定的方案有一个额外的优势:除了可以对静态字段实现延迟初始化外,还可以对实例字段实现延迟初始化。字段延迟初始化降低了初始化类或创建实例的开销,但增加了访问被延迟初始化的字段的开销。在大多数时候,正常的初始化要优于延迟初始化。 - -[参考:单例模式](https://blog.csdn.net/goodlixueyong/article/details/51935526) - - - -## 装饰模式 -装饰模式以对客户端透明的方式拓展对象的功能,客户端并不会觉得对象在装饰前和装饰后有什么不同。装饰模式可以在不创造更多子类的情况下,将对象的功能加以扩展。 - -``` -InputStream in = new LowerCaseInputStream( - new BufferedInputStream( - new FileInputStream("test.txt"))); -``` -设置FileInputStream,先用BufferedInputStream装饰它,再用自己写的LowerCaseInputStream过滤器去装饰它。 - -在装饰模式中的角色有: -抽象组件(Component)角色:给出一个抽象接口,以规范准备接收附加责任的对象。 -具体组件(ConcreteComponent)角色:定义一个将要接收附加责任的类。 -装饰(Decorator)角色:持有一个构件(Component)对象的实例,并定义一个与抽象构件接口一致的接口。 -具体装饰(ConcreteDecorator)角色:负责给构件对象“贴上”附加的责任。 -![在这里插入图片描述](https://img-blog.csdn.net/20180925095631732?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1R5c29uMDMxNA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) - -## 适配器模式 -将现成的对象通过适配变成我们需要的接口。 -适配器模式有类的适配器模式和对象的适配器模式两种不同的形式。 - -对象适配器模式通过组合对象进行适配。 - -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAxOC8xMC8xOS8xNjY4YWM5YTA2NzUzMjQ0?x-oss-process=image/format,png) - -类适配器通过继承来完成适配。 - -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAxOC8xMC8xOS8xNjY4YWM5YTA2NTEyYjBj?x-oss-process=image/format,png) - -适配器模式的优点 - -1. 更好的复用性。系统需要使用现有的类,而此类的接口不符合系统的需要。那么通过适配器模式就可以让这些功能得到更好的复用。 -2. 更好的扩展性。在实现适配器功能的时候,可以调用自己开发的功能,从而自然地扩展系统的功能。 - -## 观察者模式 -多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。其作用是让主题对象和观察者松耦合。 - -观察者模式所涉及的角色有: -抽象主题(Subject)角色:抽象主题角色把所有对观察者对象的引用保存在一个聚集(比如ArrayList对象)里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象,抽象主题角色又叫做抽象被观察者(Observable)角色。 -具体主题(ConcreteSubject)角色:将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色又叫做具体被观察者(Concrete Observable)角色。 -抽象观察者(Observer)角色:为所有的具体观察者定义一个接口,在得到主题的通知时更新自己,这个接口叫做更新接口。 -具体观察者(ConcreteObserver)角色:存储与主题的状态自恰的状态。具体观察者角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。如果需要,具体观察者角色可以保持一个指向具体主题对象的引用。 -Observer接口 Observable类 -被观察者类都是java.util.Observable类的子类。 -![在这里插入图片描述](https://img-blog.csdn.net/20180925095713437?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1R5c29uMDMxNA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) -[观察者模式](https://www.cnblogs.com/luohanguo/p/7825656.html) - - - -## 代理模式 - -代理模式使用代理对象完成用户请求,屏蔽用户对真实对象的访问。 - -### 静态代理 - -静态代理:代理类在编译阶段生成,程序运行前就已经存在,在编译阶段将通知织入Java字节码中。 - -缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类。同时,一旦接口增加方法,目标对象与代理对象都要维护。 - -### 动态代理 - -动态代理:代理类在程序运行时创建,在内存中临时生成一个代理对象,在运行期间对业务方法进行增强。 - -#### JDK动态代理 - -JDK实现代理只需要使用newProxyInstance方法: - -```java -static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h ) -``` - -ClassLoader loader:指定当前目标对象使用的类加载器 -Class[] interfaces:目标对象实现的接口的类型 -InvocationHandler h:当代理对象调用目标对象的方法时,会触发事件处理器的invoke方法() - -``` -public class DynamicProxyDemo { - - public static void main(String[] args) { - //被代理的对象 - MySubject realSubject = new RealSubject(); - - //调用处理器 - MyInvacationHandler handler = new MyInvacationHandler(realSubject); - - MySubject subject = (MySubject) Proxy.newProxyInstance(realSubject.getClass().getClassLoader(), - realSubject.getClass().getInterfaces(), handler); - - System.out.println(subject.getClass().getName()); - subject.rent(); - } -} - -interface MySubject { - public void rent(); -} -class RealSubject implements MySubject { - - @Override - public void rent() { - System.out.println("rent my house"); - } -} -class MyInvacationHandler implements InvocationHandler { - - private Object subject; - - public MyInvacationHandler(Object subject) { - this.subject = subject; - } - - @Override - public Object invoke(Object object, Method method, Object[] args) throws Throwable { - System.out.println("before renting house"); - //invoke方法会拦截代理对象的方法调用 - Object o = method.invoke(subject, args); - System.out.println("after rentint house"); - return o; - } -} -``` - - - -## 工厂模式 -工厂模式是用来封装对象的创建。 - -### 简单工厂模式 - -只有一个工厂类。适用于需要创建的对象较少的场景。 - -```java - public class ShapeFactory { - public static final String TAG = "ShapeFactory"; - public static Shape getShape(String type) { - Shape shape = null; - if (type.equalsIgnoreCase("circle")) { - shape = new CircleShape(); - } else if (type.equalsIgnoreCase("rect")) { - shape = new RectShape(); - } else if (type.equalsIgnoreCase("triangle")) { - shape = new TriangleShape(); - } - return shape; - } - } -``` - -优点:只需要一个工厂创建对象,代码量少。 - -缺点:系统扩展困难,新增产品需要修改工厂逻辑,当产品较多时,会造成工厂逻辑过于复杂,不利于系统扩展和维护。 - -### 工厂方法模式 - -针对不同的对象提供不同的工厂。每个对象都有一个与之对应的工厂。 - -```java -public interface Reader { - void read(); -} - -public class JpgReader implements Reader { - @Override - public void read() { - System.out.print("read jpg"); - } -} - -public class PngReader implements Reader { - @Override - public void read() { - System.out.print("read png"); - } -} - -public interface ReaderFactory { - Reader getReader(); -} - -public class JpgReaderFactory implements ReaderFactory { - @Override - public Reader getReader() { - return new JpgReader(); - } -} - -public class PngReaderFactory implements ReaderFactory { - @Override - public Reader getReader() { - return new PngReader(); - } -} -``` - -客户端通过子类来指定创建对应的对象。 - -```java -ReaderFactory factory=new JpgReaderFactory(); -Reader reader=factory.getReader(); -reader.read(); -``` - -优点:增加新的产品类时无须修改现有系统,只需增加新产品和对应的工厂类即可。 - -### 抽象工厂模式 - -多了一层抽象,减少了工厂的数量(HpMouseFactory和HpKeyboFactory合并为HpFactory)。 -![在这里插入图片描述](https://img-blog.csdn.net/2018092509574846?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1R5c29uMDMxNA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) - - - -## 模板模式 - -在抽象类中定义一个操作中算法的骨架,子类按照需要重写方法实现。 - -```java -public abstract class DodishTemplate { - //模板 - protected void dodish(){ - this.preparation(); - this.doing(); - this.carriedDishes(); - } - - public abstract void preparation(); - - public abstract void doing(); - - public abstract void carriedDishes (); -} -``` - - - -## 建造者模式 - -传统建造者模式: - -```java -public class Computer { - private final String cpu;//必须 - private final String ram;//必须 - private final int usbCount;//可选 - private final String keyboard;//可选 - private final String display;//可选 - - private Computer(Builder builder){ - this.cpu=builder.cpu; - this.ram=builder.ram; - this.usbCount=builder.usbCount; - this.keyboard=builder.keyboard; - this.display=builder.display; - } - public static class Builder{ - private String cpu;//必须 - private String ram;//必须 - private int usbCount;//可选 - private String keyboard;//可选 - private String display;//可选 - - public Builder(String cup,String ram){ - this.cpu=cup; - this.ram=ram; - } - public Builder setDisplay(String display) { - this.display = display; - return this; - } - //set... - public Computer build(){ - return new Computer(this); - } - } -} - -public class ComputerDirector { - public void makeComputer(ComputerBuilder builder){ - builder.setUsbCount(); - builder.setDisplay(); - builder.setKeyboard(); - } -} -``` - -传统建造者模式变种,链式调用: - -```java -public class LenovoComputerBuilder extends ComputerBuilder { - private Computer computer; - public LenovoComputerBuilder(String cpu, String ram) { - computer=new Computer(cpu,ram); - } - @Override - public void setUsbCount() { - computer.setUsbCount(4); - } - //... - @Override - public Computer getComputer() { - return computer; - } -} - -Computer computer=new Computer.Builder("因特尔","三星") - .setDisplay("三星24寸") - .setKeyboard("罗技") - .setUsbCount(2) - .build(); -``` - diff --git "a/\345\210\206\345\270\203\345\274\217/\345\210\206\345\270\203\345\274\217\344\272\213\345\212\241.md" "b/\345\210\206\345\270\203\345\274\217/\345\210\206\345\270\203\345\274\217\344\272\213\345\212\241.md" deleted file mode 100644 index 05ce1f0..0000000 --- "a/\345\210\206\345\270\203\345\274\217/\345\210\206\345\270\203\345\274\217\344\272\213\345\212\241.md" +++ /dev/null @@ -1,77 +0,0 @@ -分布式事务是指事务的参与者,支持事务的服务器,资源服务器以及事务管理器分别位于分布式系统的不同节点之上。通常一个分布式事务中会涉及对多个数据源或业务系统的操作。分布式事务也可以被定义为一种嵌套型的事务,同时也就具有了ACID事务的特性。 - -## CAP理论 - -Consistency(一致性):数据一致更新,所有数据变动都是同步的(强一致性)。 - -Availability(可用性):好的响应性能 - -Partition tolerance(分区容错性) :可靠性 - -定理:任何分布式系统只可同时满足二点,没法三者兼顾。 - -CA系统(放弃P):指将所有数据(或者仅仅是那些与事务相关的数据)都放在一个分布式节点上,就不会存在网络分区。所以强一致性以及可用性得到满足。 - -CP系统(放弃A):如果要求数据在各个服务器上是强一致的,然而网络分区会导致同步时间无限延长,那么如此一来可用性就得不到保障了。坚持事务ACID(原子性、一致性、隔离性和持久性)的传统数据库以及对结果一致性非常敏感的应用通常会做出这样的选择。 - -AP系统(放弃C):这里所说的放弃一致性,并不是完全放弃数据一致性,而是放弃数据的强一致性,而保留数据的最终一致性。如果即要求系统高可用又要求分区容错,那么就要放弃一致性了。因为一旦发生网络分区,节点之间将无法通信,为了满足高可用,每个节点只能用本地数据提供服务,这样就会导致数据不一致。一些遵守BASE原则数据库,(如:Cassandra、CouchDB等)往往会放宽对一致性的要求(满足最终一致性即可),一次来获取基本的可用性。 - -## BASE理论 - -Basically Available基本可用:指分布式系统在出现不可预知的故障的时候,允许损失部分可用性——但不是系统不可用。 - -响应时间上的损失:假如正常一个在线搜索0.5秒之内返回,但由于故障(机房断电或网络不通),查询结果的响应时间增加到1—2秒。功能上的损失:如果流量激增或者一个请求需要多个服务间配合,而此时有的服务发生了故障,这时需要进行服务降级,进而保护系统稳定性。 - -Soft state软状态:允许系统在不同节点的数据副本之间进行数据同步的过程存在延迟。Eventually consistent最终一致:最终数据是一致的就可以了,而不是时时高一致。 - -BASE思想主要强调基本的可用性,如果你需要High 可用性,也就是纯粹的高性能,那么就要以一致性或容错性为牺牲。 - -## 实现方案 - -分布式事务的实现主要有以下 5 种方案: - -- XA 方案 -- TCC 方案 -- 可靠消息最终一致性方案 -- 最大努力通知方案 - -## 2PC/XA方案 - -所谓的 XA 方案,即:两阶段提交,有一个事务管理器的概念,负责协调多个数据库(资源管理器)的事务,事务管理器先问问各个数据库你准备好了吗?如果每个数据库都回复 ok,那么就正式提交事务,在各个数据库上执行操作;如果任何其中一个数据库回答不 ok,那么就回滚事务。 - -这种分布式事务方案,比较适合单块应用里,跨多个库的分布式事务,而且因为严重依赖于数据库层面来搞定复杂的事务,效率很低,绝对不适合高并发的场景。 - -一般来说某个系统内部如果出现跨多个库的这么一个操作,是不合规的。如果你要操作别人的服务的库,你必须是通过调用别的服务的接口来实现,绝对不允许交叉访问别人的数据库。 - -## TCC强一致性方案 - -TCC 的全称是:`Try`、`Confirm`、`Cancel`。 - -- **Try 阶段**:这个阶段说的是对各个服务的资源做检测以及对资源进行 **锁定或者预留**。 -- **Confirm 阶段**:这个阶段说的是在各个服务中执行实际的操作。 -- **Cancel 阶段**:如果任何一个服务的业务方法执行出错,那么这里就需要 **进行补偿**,就是执行已经执行成功的业务逻辑的回滚操作。(把那些执行成功的回滚) -- - -这种方案说实话几乎很少人使用,但是也有使用的场景。因为这个 **事务回滚实际上是严重依赖于你自己写代码来回滚和补偿** 了,会造成补偿代码巨大,非常之恶心。 - - - -## 可靠消息最终一致性方案 - -基于 MQ 来实现事务。比如阿里的 RocketMQ 就支持消息事务。大概的意思就是: - -1. A 系统先发送一个 prepared 消息到 MQ,如果这个 prepared 消息发送失败那么就直接取消操作别执行了; -2. 如果这个消息发送成功过了,那么接着执行本地事务,如果成功就告诉 MQ 发送确认消息,如果失败就告诉 MQ 回滚消息; -3. 如果发送了确认消息,那么此时 B 系统会接收到确认消息,然后执行本地的事务; -4. mq 会自动定时轮询所有 prepared 消息回调你的接口,问你,这个消息是不是本地事务处理失败了,所有没发送确认的消息,是继续重试还是回滚?一般来说这里你就可以查下数据库看之前本地事务是否执行,如果回滚了,那么这里也回滚吧。这个就是避免可能本地事务执行成功了,而确认消息却发送失败了。 -5. 这个方案里,要是系统 B 的事务失败了咋办?重试咯,自动不断重试直到成功,如果实在是不行,要么就是针对重要的资金类业务进行回滚,比如 B 系统本地回滚后,想办法通知系统 A 也回滚;或者是发送报警由人工来手工回滚和补偿。 - -这个还是比较合适的,目前国内互联网公司大都是这么玩儿的,要不你举用 RocketMQ 支持的,要不你就自己基于类似 ActiveMQ?RabbitMQ?自己封装一套类似的逻辑出来,总之思路就是这样子的。 - -## 最大努力通知方案 - -这个方案的大致意思就是: - -1. 系统 A 本地事务执行完之后,发送个消息到 MQ; -2. 这里会有个专门消费 MQ 的最大努力通知服务,这个服务会消费 MQ 然后写入数据库中记录下来,或者是放入个内存队列也可以,接着调用系统 B 的接口; -3. 要是系统 B 执行成功就 ok 了;要是系统 B 执行失败了,那么最大努力通知服务就定时尝试重新调用系统 B,反复 N 次,最后还是不行就放弃。 \ No newline at end of file diff --git "a/\345\210\206\345\270\203\345\274\217/\345\210\206\345\270\203\345\274\217\347\274\223\345\255\230.md" "b/\345\210\206\345\270\203\345\274\217/\345\210\206\345\270\203\345\274\217\347\274\223\345\255\230.md" deleted file mode 100644 index d19654d..0000000 --- "a/\345\210\206\345\270\203\345\274\217/\345\210\206\345\270\203\345\274\217\347\274\223\345\255\230.md" +++ /dev/null @@ -1,98 +0,0 @@ -高并发环境下,例如典型的淘宝双11秒杀,几分钟内上亿的用户涌入淘宝,这个时候如果访问不加拦截,让大量的读写请求涌向数据库,由于磁盘的处理速度与内存显然不在一个量级,服务器马上就要宕机。**从减轻数据库的压力和提高系统响应速度两个角度来考虑,都会在数据库之前加一层缓存**,访问压力越大的,在缓存之前就开始 CDN 拦截图片等访问请求。 - -并且由于最早的单台机器的内存资源以及承载能力有限,如果大量使用本地缓存,也会使相同的数据被不同的节点存储多份,对内存资源造成较大的浪费,因此,才催生出了分布式缓存。 - -## 应用场景 - -1. **页面缓存**:用来缓存Web 页面的内容片段,包括HTML、CSS 和图片等; -2. **应用对象缓存**:缓存系统作为ORM 框架的二级缓存对外提供服务,目的是减轻数据库的负载压力,加速应用访问;解决分布式Web部署的 session 同步问题,状态缓存.缓存包括Session 会话状态及应用横向扩展时的状态数据等,这类数据一般是难以恢复的,对可用性要求较高,多应用于高可用集群。 -3. **并行处理**:通常涉及大量中间计算结果需要共享; -4. **云计算领域提供分布式缓存服务** - -## 缓存常见的问题 - -### 缓存穿透 - -缓存穿透是指查询一个**不存在的数据**,由于缓存是不命中时被动写的,如果从DB查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到DB去查询,失去了缓存的意义。在流量大时,可能DB就挂掉了。 - -1. **缓存空值**,不会查数据库。 -2. 采用**布隆过滤器**,将所有可能存在的数据哈希到一个足够大的`bitmap`中,查询不存在的数据会被这个`bitmap`拦截掉,从而避免了对`DB`的查询压力。 - -布隆过滤器的原理:当一个元素被加入集合时,通过K个散列函数将这个元素映射成一个位数组中的K个点,把它们置为1。查询时,将元素通过散列函数映射之后会得到k个点,如果这些点有任何一个0,则被检元素一定不在,直接返回;如果都是1,则查询元素很可能存在,就会去查询Redis和数据库。 - -### 缓存雪崩 - -缓存雪崩是指在我们设置缓存时采用了相同的过期时间,**导致缓存在某一时刻同时失效**,请求全部转发到DB,DB瞬时压力过重挂掉。 - -解决方法:在原有的失效时间基础上**增加一个随机值**,使得过期时间分散一些。 - -### 缓存击穿 - -缓存击穿:大量的请求同时查询一个 key 时,此时这个 key 正好失效了,就会导致大量的请求都落到数据库。**缓存击穿是查询缓存中失效的 key,而缓存穿透是查询不存在的 key。** - -解决方法:加分布式锁,第一个请求的线程可以拿到锁,拿到锁的线程查询到了数据之后设置缓存,其他的线程获取锁失败会等待50ms然后重新到缓存取数据,这样便可以避免大量的请求落到数据库。 - -```java -public String get(String key) { - String value = redis.get(key); - if (value == null) { //缓存值过期 - String unique_key = systemId + ":" + key; - //设置30s的超时 - if (redis.set(unique_key, 1, 'NX', 'PX', 30000) == 1) { //设置成功 - value = db.get(key); - redis.set(key, value, expire_secs); - redis.del(unique_key); - } else { //其他线程已经到数据库取值并回写到缓存了,可以重试获取缓存值 - sleep(50); - get(key); //重试 - } - } else { - return value; - } -} -``` - -### 缓存预热 - -缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据! - -解决方案: - -1. 直接写个缓存刷新页面,上线时手工操作一下; -2. 数据量不大,可以在项目启动的时候自动进行加载; -3. 定时刷新缓存; - -### 缓存降级 - -当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。 - -缓存降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)。 - -在进行降级之前要对系统进行梳理,看看系统是不是可以丢卒保帅;从而梳理出哪些必须誓死保护,哪些可降级;比如可以参考日志级别设置预案: - -1. 一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级; -2. 警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警; -3. 错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级; -4. 严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。 - -服务降级的目的,是为了防止Redis服务故障,导致数据库跟着一起发生雪崩问题。因此,对于不重要的缓存数据,可以采取服务降级策略,例如一个比较常见的做法就是,Redis出现问题,不去数据库查询,而是直接返回默认值给用户。 - -### 如何保证缓存与数据库双写时的数据一致性? - -**1、先删除缓存再更新数据库** - -进行更新操作时,先删除缓存,然后更新数据库,后续的请求再次读取时,会从数据库读取后再将新数据更新到缓存。 - -存在的问题:删除缓存数据之后,更新数据库完成之前,这个时间段内如果有新的读请求过来,就会从数据库读取旧数据重新写到缓存中,再次造成不一致,并且后续读的都是旧数据。 - -**2、先更新数据库再删除缓存** - -进行更新操作时,先更新MySQL,成功之后,删除缓存,后续读取请求时再将新数据回写缓存。 - -存在的问题:更新MySQL和删除缓存这段时间内,请求读取的还是缓存的旧数据,不过等数据库更新完成,就会恢复一致,影响相对比较小。 - -**3、异步更新缓存** - -数据库的更新操作完成后不直接操作缓存,而是把这个操作命令封装成消息扔到消息队列中,然后由Redis自己去消费更新数据,消息队列可以保证数据操作顺序一致性,确保缓存系统的数据正常。 - -以上几个方案都不完美,需要根据业务需求,评估哪种方案影响较小,然后选择相应的方案。 \ No newline at end of file diff --git "a/\345\211\215\347\253\257/vue.md" "b/\345\211\215\347\253\257/vue.md" deleted file mode 100644 index 74605b7..0000000 --- "a/\345\211\215\347\253\257/vue.md" +++ /dev/null @@ -1,401 +0,0 @@ - - - -- [基础](#%E5%9F%BA%E7%A1%80) - - [v-modal](#v-modal) - - [computed和method](#computed%E5%92%8Cmethod) -- [组件](#%E7%BB%84%E4%BB%B6) -- [深入组件](#%E6%B7%B1%E5%85%A5%E7%BB%84%E4%BB%B6) - - [prop](#prop) -- [vue-router](#vue-router) - - [动态匹配](#%E5%8A%A8%E6%80%81%E5%8C%B9%E9%85%8D) - - [导航](#%E5%AF%BC%E8%88%AA) - - [命名路由](#%E5%91%BD%E5%90%8D%E8%B7%AF%E7%94%B1) - - [命名视图](#%E5%91%BD%E5%90%8D%E8%A7%86%E5%9B%BE) - - [嵌套命名视图](#%E5%B5%8C%E5%A5%97%E5%91%BD%E5%90%8D%E8%A7%86%E5%9B%BE) - - [重定向](#%E9%87%8D%E5%AE%9A%E5%90%91) - - - -## 基础 - -### v-modal - -```vue - -

Message is: {{ message }}

-``` - -等价于: - -```vue - -``` - -在组件上使用: - -```vue -Vue.component('custom-input', { - props: ['value'], - template: ` - - ` -}) - - -``` - - - -### computed和method - -```vue -
-

message:${ message }

-

now (computed):${ now }

-

getNow (method):${ getNow() }

-
- -var vm = new Vue({ - el: '#app', - delimiters: ['${', '}'], - data: { - message: 'Hello World!', - }, - computed: { - now: function() { - return Date.now(); - }, - }, - methods: { - getNow: function() { - return Date.now(); - }, - }, -}); - -//改变了message,computed才会更新,即computed依赖于属性 -//getNow()方法每次调用都会更新 -vm.message = '999'; -``` - - - - - -## 组件 - -```vue -// 定义一个名为 button-counter 的新组件 -Vue.component('button-counter', { - data: function () { - return { - count: 0 - } - }, - template: '' -}) - -
- -
- -new Vue({ el: '#components-demo' }) -``` - -data 必须声明为返回一个初始数据对象的函数,因为组件可能被用来创建多个实例。通过提供 data 函数,每次创建一个新实例后,我们能够调用 data 函数,从而返回初始数据的一个全新副本数据对象。 - -通过 Prop 向子组件传递数据,一个组件默认可以拥有任意数量的 prop。 - -```vue -Vue.component('blog-post', { - props: ['title'], - template: '

{{ title }}

' -}) - - - - -``` - -使用有约束条件的元素,table 内部只能有 tr: - -```vue - - -
-``` - -这个自定义组件 `` 会被作为无效的内容提升到外部,并导致最终渲染结果出错。幸好这个特殊的 `is` attribute 给了我们一个变通的办法: - -```vue - - -
-``` - - - -## 深入组件 - -全局注册,注册之后可以用在任何新创建的 Vue 根实例 (`new Vue`) 的模板中: - -```vue -Vue.component('component-a', { /* ... */ }) -Vue.component('component-b', { /* ... */ }) -Vue.component('component-c', { /* ... */ }) - -new Vue({ el: '#app' }) ---------------------- -
- - - -
-``` - -局部注册: - -```vue -var ComponentA = { /* ... */ } -var ComponentB = { /* ... */ } -var ComponentC = { /* ... */ } - -new Vue({ - el: '#app', - components: { - 'component-a': ComponentA, - 'component-b': ComponentB - } -}) -``` - -局部注册的组件在其子组件中不可用,如果要在 ComponentB 使用ComponentA,则应该这样写: - -```vue -var ComponentA = { /* ... */ } - -var ComponentB = { - components: { - 'component-a': ComponentA - }, - // ... -} - -//过 Babel 和 webpack 使用 ES2015 模块 -import ComponentA from './ComponentA.vue' - -export default { - components: { - ComponentA - }, - // ... -} -``` - -### prop - -使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名: - -```vue -Vue.component('blog-post', { - // 在 JavaScript 中是 camelCase 的 - props: ['postTitle'], - template: '

{{ postTitle }}

' -}) - - - -``` - -给 props 传入静态值: - -```vue -props: { - title: String, - likes: Number, - isPublished: Boolean, - commentIds: Array, - author: Object, - callback: Function, - contactsPromise: Promise // or any other constructor -} - - -``` - -可以通过 `v-bind` 动态赋值: - -```vue - - - - - -``` - - - - - -## vue-router - -### 动态匹配 - -```vue -const User = { - template: '
User
' -} - -const router = new VueRouter({ - routes: [ - // dynamic segments start with a colon - { path: '/user/:id', component: User } - ] -}) -``` - -/user/tyson 和 /user/tom 都会映射到同一个组件。路由匹配之后,每次组件都可以获取到 `this.$route.params`。 - -```vue -const User = { - template: '
User {{ $route.params.id }}
' -} -``` - -在一个路由中设置多段“路径参数”,对应的值都会设置到 `$route.params` 中。 - -| pattern | matched path | $route.params | -| ----------------------------- | ------------------- | -------------------------------------- | -| /user/:username | /user/evan | `{ username: 'evan' }` | -| /user/:username/post/:post_id | /user/evan/post/123 | `{ username: 'evan', post_id: '123' }` | - -### 导航 - -```js -// 字符串 -router.push('home') - -// 对象 -router.push({ path: 'home' }) - -// 命名的路由 -router.push({ name: 'user', params: { userId: '123' }}) - -// 带查询参数,变成 /register?plan=private -router.push({ path: 'register', query: { plan: 'private' }}) -``` - - history 记录中向前或者后退多少步: - -```vue -// 在浏览器记录中前进一步,等同于 history.forward() -router.go(1) - -// 后退一步记录,等同于 history.back() -router.go(-1) - -// 前进 3 步记录 -router.go(3) - -// 如果 history 记录不够用,那就默默地失败呗 -router.go(-100) -router.go(100) -``` - -### 命名路由 - -有时候,通过一个名称来标识一个路由显得更方便一些,特别是在链接一个路由,或者是执行一些跳转的时候。 - -```vue -const router = new VueRouter({ - routes: [ - { - path: '/user/:userId', - name: 'user', - component: User - } - ] -}) -``` - -要链接到一个命名路由,可以给 `router-link` 的 `to` 属性传一个对象: - -```vue -User -``` - -等同于: - -```vue -router.push({ name: 'user', params: { userId: 123 }}) -``` - -### 命名视图 - -有时候想同时 (同级) 展示多个视图,可以使用命名视图。 - -```vue - - - -``` - -如果 `router-view` 没有设置名字,那么默认为 `default`。 - -多个视图需要使用多个组件渲染,使用 components 配置: - -```vue -const router = new VueRouter({ - routes: [ - { - path: '/', - components: { - default: Foo, - a: Bar, - b: Baz - } - } - ] -}) -``` - -### 嵌套命名视图 - -https://jsfiddle.net/posva/22wgksa3/ - -### 重定向 - -重定向也是通过 `routes` 配置来完成。 - -```vue -const router = new VueRouter({ - routes: [ - { path: '/a', redirect: '/b' } - ] -}) - -const router = new VueRouter({ - routes: [ - { path: '/a', redirect: { name: 'foo' }} //重定向目标可以是命名路由 - ] -}) -``` - -别名: - -```vue -const router = new VueRouter({ - routes: [ - { path: '/a', component: A, alias: '/b' } //访问路径是/b,路由匹配为/a - ] -}) -``` \ No newline at end of file diff --git "a/\345\267\245\345\205\267/GitHub\346\214\207\345\215\227.md" "b/\345\267\245\345\205\267/GitHub\346\214\207\345\215\227.md" deleted file mode 100644 index d78291f..0000000 --- "a/\345\267\245\345\205\267/GitHub\346\214\207\345\215\227.md" +++ /dev/null @@ -1,91 +0,0 @@ - - -**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* - -- [GitHub 的使用](#github-%E7%9A%84%E4%BD%BF%E7%94%A8) -- [GitHub 搜索技巧](#github-%E6%90%9C%E7%B4%A2%E6%8A%80%E5%B7%A7) -- [使用GitHub可以做什么](#%E4%BD%BF%E7%94%A8github%E5%8F%AF%E4%BB%A5%E5%81%9A%E4%BB%80%E4%B9%88) - - - -作为全球最大的~~代码托管~~同性交友平台,GitHub上面有着丰富的学习资源。 - -要使用GitHub,就要先学会使用Git。 - -Git又是什么呢?简单的说,**Git 是一个管理你的代码历史记录的工具。** - -# GitHub 的使用 - -首先看下怎么使用GitHub。 - -- 注册一个GitHub账号,这样比较简单,不详细展开讲了。 - -- 创建一个新的仓库,用来存放项目。 - - ![](http://img.dabin-coder.cn/image/image-20210822154700616.png) - -- 或者你在GitHub上看到别人有一个超级无敌有趣的项目,可以直接fork过来,可以理解成复制过来,变成你自己的。之后你想怎么改就怎么改! - - ![image-20210822155107839](http://img.dabin-coder.cn/image/image-20210822155107839.png) - -- 然后你可以通过Git命令行`git clone xxx`把项目clone到本地,在本地进行创作。 - - ![](http://img.dabin-coder.cn/image/image-20210822155359118.png) - -- 最后,在本地创作完成,可以使用`git commit -m xxx`提交到本地库,然后使用`git push`把修改推送到GitHub仓库。之后就可以在GitHub上面看到你修改的内容啦~ - -以上就是GitHub的基本使用方法,有一些细节没有写出来,但是关键的步骤都列举出来了~ - -# GitHub 搜索技巧 - -接下来说一下GitHub的搜索技巧,非常重要! - -- 评价GitHub项目的两个重要的参数:star和fork。 - -![](http://img.dabin-coder.cn/image/image-20210822161003170.png) - -比较优秀和热门的项目,star数目和fork数目都会比较多。我们可以根据这两个参数筛选出比较好的项目。使用`关键字 stars:>=xxx forks:>=xxx` 可以筛选出star和fork数目大于xxx的相关项目。 - -![](http://img.dabin-coder.cn/image/image-20210822161122586.png) - -- 使用 `awesome 关键字`,可以筛选出比较高质量的学习、书籍、工具类或者插件类的集合。 - -![](http://img.dabin-coder.cn/image/image-20210822161608599.png) - -- 在特定位置搜索关键词。有些关键词出现在项目的不同位置,比如项目名称、项目描述和README等。使用`关键词 in name/description/Readme`,可以搜索到相关的内容。比如使用`spring in name`,可以搜索到在项目名中包含spring的项目。 - -![image-20210822162144086](http://img.dabin-coder.cn/image/image-20210822162144086.png) - -- 指定条件搜索关键词。如`tool language:java`搜索到的是包含关键字tool的Java项目,`tool followers:>1000`可以搜索到包含关键字tool,且follower数量大于1000的项目。 - -![](http://img.dabin-coder.cn/image/image-20210822163111390.png) - - - -# 使用GitHub可以做什么 - -- 托管代码。GitHub 上记录这代码的修改历史,必要时可以进行回退; - -- 搜索牛逼的开源项目,参与开源项目开发。这里分享下自己的一个开源仓库,用于分享Java核心知识,包括Java基础、MySQL、SpringBoot、Mybatis、Redis、RabbitMQ等等,面试必备。 - - https://github.com/Tyson0314/Java-learning - -- 文档神器。可以为自己的项目建立wiki,可以用markdown语法写wiki; - -![](http://img.dabin-coder.cn/image/image-20210822172419760.png) - -- 使用GitHub pages建立个人静态网站,搞一个有自己域名的独立博客,想想都觉得开心。使用GitHub pages的好处是搭建简单而且免费,支持静态脚本,并且可以绑定自己的域名。具体可以参考:[GitHub Pages 建立个人网站详细教程 - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/58229299) - -- 写书写文章,团队协作分工。 - - - -最后给大家分享一个GitHub仓库,上面放了200多本经典的计算机书籍,包括数据库、操作系统、计算机网络、数据结构和算法等,可以star一下,下次找书直接在上面搜索,仓库持续更新中~ - -GitHub地址:https://github.com/Tyson0314/java-books - -如果github访问不了,可以访问gitee仓库。 - -gitee地址:https://gitee.com/tysondai/java-books - - diff --git "a/\345\267\245\345\205\267/NPM.md" "b/\345\267\245\345\205\267/NPM.md" deleted file mode 100644 index f3f6108..0000000 --- "a/\345\267\245\345\205\267/NPM.md" +++ /dev/null @@ -1,48 +0,0 @@ - - - -- [安装](#%E5%AE%89%E8%A3%85) -- [卸载](#%E5%8D%B8%E8%BD%BD) -- [更新](#%E6%9B%B4%E6%96%B0) -- [搜索](#%E6%90%9C%E7%B4%A2) -- [创建模块](#%E5%88%9B%E5%BB%BA%E6%A8%A1%E5%9D%97) - - - -NPM 是随同NodeJS一起安装的包管理工具。 - -测试是否安装 npm:`npm -v` - -升级 npm 版本:`npm install npm -g` - -## 安装 - -本地安装:`npm install express` 安装包放在工程目录下的 ./node_modules,在代码中通过 require() 来引入本地安装的包。 - -全局安装:`npm install exprexss -g` 安装包放在 /usr/local 下或者你 node 的安装目录,可以直接在命令行里使用。 - -使用淘宝镜像的命令:`npm install -g cnpm --registry.npm.taobao.org`,接下来就可以使用 cnpm 命令来安装模块了:`cnpm install express` - -查看安装信息:`npm list -g` - -查看某个模块的版本号:`npm list grunt` - -## 卸载 - -`npm uninstall express` - -## 更新 - -`npm update express` - -## 搜索 - -`npm search express` - -## 创建模块 - -创建模块,package.json 文件是必不可少的:`npm init` - -注册用户:`npm adduser` - -发布模块:`npm publish` diff --git "a/\345\267\245\345\205\267/jenkins.md" "b/\345\267\245\345\205\267/jenkins.md" deleted file mode 100644 index 4f88a52..0000000 --- "a/\345\267\245\345\205\267/jenkins.md" +++ /dev/null @@ -1,120 +0,0 @@ - - -**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* - -- [简介](#%E7%AE%80%E4%BB%8B) -- [自动化部署](#%E8%87%AA%E5%8A%A8%E5%8C%96%E9%83%A8%E7%BD%B2) - - [安装jenkis镜像](#%E5%AE%89%E8%A3%85jenkis%E9%95%9C%E5%83%8F) - - [Jenkins配置](#jenkins%E9%85%8D%E7%BD%AE) - - [运行任务](#%E8%BF%90%E8%A1%8C%E4%BB%BB%E5%8A%A1) -- [问题记录](#%E9%97%AE%E9%A2%98%E8%AE%B0%E5%BD%95) - - [无法进入登录页面](#%E6%97%A0%E6%B3%95%E8%BF%9B%E5%85%A5%E7%99%BB%E5%BD%95%E9%A1%B5%E9%9D%A2) - - [Cannot link to /mysql](#cannot-link-to-mysql) - - - -## 简介 - -Jenkins 提供超过1000个插件来支持构建、部署、自动化,满足任何项目的需要。我们可以用Jenkins来构建和部署我们的项目,比如说从我们的代码仓库获取代码,然后将代码打包成可执行的文件,通过远程的ssh工具执行脚本来运行我们的项目。 - - - -## 自动化部署 - -将生成docker镜像和运行docker容器的操作进行整合,实现[一键部署](https://mp.weixin.qq.com/s/tQqvgSc9cHBtnqRQSbI4aw)。 - -1. 基础环境搭建,安装mysql镜像等; -2. 服务器安装jenkins并启动; -3. 配置jenkins,添加代码仓路径、maven配置、git配置、ssh配置和启动docker容器的脚本(镜像打包完成后运行); -4. 在任务列表点击运行,一键部署。 - -### 安装jenkis镜像 - -下载jenkins镜像: - -``` -docker pull jenkins/jenkins:lts -``` - -在Docker容器中运行Jenkins: - -``` -docker run -p 80:8080 -p 50000:5000 --name jenkins \ --u root \ --v /mydata/jenkins_home:/var/jenkins_home \ --d jenkins/jenkins:lts -``` - -### Jenkins配置 - -访问http://192.168.6.132:8080/,登录Jenkins。 - -准备用于启动docker容器的脚本,上传到服务器。注意使用`--network network_name`指定与依赖服务(如mysql)在同一个网络下,不然会导致Cannot link to /mysql的异常。 - -``` -#!/usr/bin/env bash -app_name='mall-tiny-jenkins' -docker stop ${app_name} -echo'----stop container----' -docker rm ${app_name} -echo'----rm container----' -docker run -p 8088:8088 --name ${app_name} \ ---link mysql:db \ # 不同容器之间可以通过--link指定的服务别名互相访问,containName:alias ---network yml_default \ # 注意要与其他依赖的服务在同一个网络下,不然会导致Cannot link to /mysql的异常 --v /etc/localtime:/etc/localtime \ --v /mydata/app/${app_name}/logs:/var/logs \ --d mall-tiny/${app_name}:1.0-SNAPSHOT -echo'----start container----' -``` - -在jentins界面新建任务,配置maven、git、代码仓库、脚本路径等。 - -### 运行任务 - -可以在控制台界面查看任务执行的信息。 - - - -## 问题记录 - -### 无法进入登录页面 - -可能是机器网络配置改变,需要重启docker(systemctl restart docker),才能正常访问jenkins登录页面。 - -### Cannot link to /mysql - -mall-tiny-jenkins容器与mysql容器不在同一个网络下。应该在运行mall-tiny-jenkins的脚本中添加--network参数: - -``` -docker run -d --name mall-tiny-jenkins -p 8088:8088 --net yml_default mall-tiny-jenkins -``` - -查看mysql容器的networks: - -``` -docker inspect mysql -``` - -返回的networks信息: - -```yaml -"Networks": { - "yml_default": { - "IPAMConfig": null, - "Links": null, - "Aliases": [ - "mysql", - "3333d057ad33" - ], - "NetworkID": "0621fa711575b12f81015e7763733ac1db29c65d7abaf11d0b5da0484f5f70ea", - ... - } -} -``` - -查看所有容器的networks信息: - -``` -docker network ls -``` - diff --git "a/\345\267\245\345\205\267/jmeter.md" "b/\345\267\245\345\205\267/jmeter.md" deleted file mode 100644 index fe0f915..0000000 --- "a/\345\267\245\345\205\267/jmeter.md" +++ /dev/null @@ -1,39 +0,0 @@ - - -**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* - -- [线程组配置](#%E7%BA%BF%E7%A8%8B%E7%BB%84%E9%85%8D%E7%BD%AE) -- [Request 默认配置](#request-%E9%BB%98%E8%AE%A4%E9%85%8D%E7%BD%AE) -- [HTTP头配置](#http%E5%A4%B4%E9%85%8D%E7%BD%AE) -- [聚合报告](#%E8%81%9A%E5%90%88%E6%8A%A5%E5%91%8A) -- [HTTP请求参数](#http%E8%AF%B7%E6%B1%82%E5%8F%82%E6%95%B0) - - - -## 线程组配置 - -![image-20201011235317624](../img/jmeter/jmeter-thread-group.png) - - - -## Request 默认配置 - -![image-20201011234642519](../img/jmeter/jmeter-default-request.png) - - - -## HTTP头配置 - -![image-20201011234827310](../img/jmeter/jmeter-request-header.png) - - - -## 聚合报告 - -![image-20201012000129480](../img/jmeter/jmeter-aggregate-report.png) - - - -## HTTP请求参数 - -![image-20201014232942952](../img/jmeter/http-request-param.png) \ No newline at end of file diff --git "a/\345\267\245\345\205\267/nginx\351\203\250\347\275\262\351\241\271\347\233\256.md" "b/\345\267\245\345\205\267/nginx\351\203\250\347\275\262\351\241\271\347\233\256.md" deleted file mode 100644 index 2b9f46f..0000000 --- "a/\345\267\245\345\205\267/nginx\351\203\250\347\275\262\351\241\271\347\233\256.md" +++ /dev/null @@ -1,325 +0,0 @@ - - - - -- [简介](#%E7%AE%80%E4%BB%8B) - - [基本命令](#%E5%9F%BA%E6%9C%AC%E5%91%BD%E4%BB%A4) - - [配置文件](#%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6) -- [应用](#%E5%BA%94%E7%94%A8) - - [动静分离](#%E5%8A%A8%E9%9D%99%E5%88%86%E7%A6%BB) -- [后端部署](#%E5%90%8E%E7%AB%AF%E9%83%A8%E7%BD%B2) -- [前端部署](#%E5%89%8D%E7%AB%AF%E9%83%A8%E7%BD%B2) -- [多站点配置](#%E5%A4%9A%E7%AB%99%E7%82%B9%E9%85%8D%E7%BD%AE) -- [HTTPS配置](#https%E9%85%8D%E7%BD%AE) - - - -## 简介 - -[Nginx入门指南](https://juejin.im/post/5e982d4b51882573b0474c07#heading-1) - -轻量级的 HTTP 服务器。 - -### 基本命令 - -nginx安装目录:使用whereis nginx查看安装路径 - -启动nginx:安装目录/sbin/nginx - -停止nginx:安装目录/sbin/nginx -s stop - -重启nginx:安装目录/sbin/nginx -s reload - -查看nginx是否正在运行:`netstat -anput | grep nginx` - -### 配置文件 - -配置文件路径:`/usr/local/nginx/conf/nginx.conf` - -日志路径:`/usr/local/nginx/logs/` - -配置文件介绍: - -``` -server { - # 当nginx接到请求后,会匹配其配置中的service模块 - # 匹配方法就是将请求携带的host和port去跟配置中的server_name和listen相匹配 - listen 8080; - server_name localhost; # 定义当前虚拟主机(站点)匹配请求的主机名 - - location / { - root html; # Nginx默认值 - # 设定Nginx服务器返回的文档名 - index index.html index.htm; # 先找根目录下的index.html,如果没有再找index.htm - } -} -``` - -server{ } 其实是包含在 http{ } 内部的。每一个 server{ } 是一个虚拟主机(站点)。 - -## 应用 - -### 动静分离 - -![1588431199776](..\img\1588431199776.png) - -静态请求直接从 nginx 服务器所设定的根目录路径去取对应的资源,动态请求转发给后端去处理。不仅能给应用服务器减轻压力,将后台api接口服务化,还能将前后端代码分开并行开发和部署。 - -## 后端部署 - -1. 修改配置文件,如端口、主机等; - -2. 执行 package 打包项目,在 target 目录下生成 jar 包,上传到服务器; - -3. jar 包目录下运行 `./start.sh` 启动服务; - - 启动脚本 start.sh:`nohup java -jar xxx.jar --spring.profiles.active=prod &` - - 停止脚本 stop.sh: - - ```shell - PID=$(ps -ef | grep eladmin-system-2.4.jar | grep -v grep | awk '{ print $2 }') - if [ -z "$PID" ] - then - echo Application is already stopped - else - echo kill $PID - kill $PID - fi - ``` - - 新建 log 文件 nohup.out:`touch nohup.out` - - 查看日志脚本 log.sh:`tail -f nohup.out` - - 所有脚本放在同一目录下。 - -4. 运行 `./stop.sh` 停止服务 - -5. 运行 `./log.sh`查看日志 - -6. 配置 nginx。使用 nginx 代理 Java 服务。 - - ```json - #auth|api|avatar开头的请求发送到后端服务上 - location ~* ^/(auth|api|avatar|file/) { - proxy_pass http://localhost:8000; - proxy_set_header Host $http_host; - proxy_connect_timeout 150s; - proxy_send_timeout 150s; - proxy_read_timeout 150s; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - } - ``` - - - -## 前端部署 - -1. 修改后端接口地址 `http://xxx:80`,请求会被代理到`http://127.0.0.1:8000` - -2. `npm run rebuild:prod` 打包项目生成 dist 文件夹,上传到服务器项目目录下; - -3. 配置 nginx。 - - ``` - server { - listen 80; #localhost:80请求nginx服务器时,该请求就会被匹配进该代码块的 server{ } 中执行。 - server_name localhost; - root /home/eladmin/file/dist; - index index.html; - #charset koi8-r; - - #access_log logs/host.access.log main; - - location / { - try_files $uri $uri/ @router; - index index.html; - } - - location @router { - rewrite ^.*$ /index.html last; - } - #auth|api|avatar开头的请求发送到后端服务上 - location ~* ^/(auth|api|avatar|file/) { - proxy_pass http://localhost:8000; - proxy_set_header Host $http_host; - proxy_connect_timeout 150s; - proxy_send_timeout 150s; - proxy_read_timeout 150s; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - } - } - ``` - - - -## 多站点配置 - -nginx 配置: - -1. /etc/nginx/nginx.conf 中 http 节点增加`include /etc/nginx/conf.d/*.conf` 不同站点使用不同的配置文件。 - - ``` - http { - include mime.types; - default_type application/octet-stream; - keepalive_timeout 65; - - include /etc/nginx/conf.d/*.conf; #配置多个站点 - - server { - xxx - } - xxx - } - ``` - -2. 新建/etc/nginx/conf.d/blog.conf,配置nginx: - - ``` - #blog - server { - listen 80; - server_name localhost; - #访问vue项目 - location / { - root /home/blog/dist; - index index.html; - } - #将api转发到后端 - location /api/ { - proxy_pass http://129.204.179.3:8001/; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header REMOTE-HOST $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - } - #转发图片请求到后端 - location /img/ { - proxy_pass http://129.204.179.3:8001/img/; - } - } - ``` - -后端部署: - -1. idea--maven--project name--lifecycle--package,打包项目,在target目录生成 jar 包,拷贝到服务器。这里存放到 /home/blog 目录下。 - -2. 导入项目 sql 文件 `source blog.sql`。 - -3. 运行 jar 包。 - - ``` - #不挂断地运行命令;&表示后台运行;输出都将附加到当前目录的 nohup.out 文件 - nohup java -jar blog-springboot-1.0.jar & - - #结束运行 - PID=$(ps -ef | grep blog-springboot-1.0.jar | grep -v grep | awk '{ print $2 }') - if [ -z "$PID" ] - then - echo Application is already stopped - else - echo kill -9 $PID - kill -9 $PID - fi - ``` - -前端部署:运行 `npm run build` 生成 dist 文件夹,将 dist 文件夹下面的文件拷贝到 /home/blog/dist 目录。 - -环境准备:启动 mysql、redis、rabbitmq 等。 - -```bash -service mysqld start -/sbin/service rabbitmq-server start -``` - -启动 nginx:`安装目录/sbin/nginx` - - - -## HTTPS配置 - -[nginx配置HTTPS](https://www.cnblogs.com/ambition26/p/14077773.html) - -1. 安装 nginx ssl模块; - -2. 申请 ssl 证书,下载证书,复制到服务器某个目录下; - -3. 修改 /usr/local/nginx/conf/nginx.conf 文件 - - ```java - server { - listen 80; - server_name localhost; - rewrite ^(.*)$ https://$host$1 permanent; #http请求会被转发到https - #访问vue项目 - location / { - root /home/blog/dist; - index index.html; - } - #将api转发到后端 - location /api/ { - proxy_pass http://129.204.179.3:8001/; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header REMOTE-HOST $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - } - #转发图片请求到后端 - location /img/ { - proxy_pass http://129.204.179.3:8001/img/; - } - } - server { - listen 443 ssl; # 1.1版本后这样写 - server_name tysonbin.com; #填写绑定证书的域名 - ssl_certificate /usr/local/nginx/cert/1_tysonbin.com_bundle.crt; # 指定证书的位置,绝对路径 - ssl_certificate_key /usr/local/nginx/cert/2_tysonbin.com.key; # 绝对路径 - ssl_session_timeout 5m; - ssl_protocols TLSv1 TLSv1.1 TLSv1.2; #按照这个协议配置 - ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;#按照这个套件配置 - ssl_prefer_server_ciphers on; - location / { - root /home/blog/dist; #站点目录,绝对路径 - index index.html; - } - - #将api转发到后端 - location /api/ { - proxy_pass https://tysonbin.com:8001/; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header REMOTE-HOST $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - } - #转发图片请求到后端 - location /img/ { - proxy_pass https://tysonbin.com:8001/img/; - } - } - ``` - - 4. 执行`/usr/local/nginx/sbin/nginx -s reload`,重启nginx。 - -springboot配置https: - -1. 复制 tomcat 目录下的 tysonbin.com.jks 到 application.yml 同级目录。 - -2. 修改 application.yml: - - ```yaml - server: - port: 8001 - ssl: - #SSL证书路径 一定要加上classpath: - key-store: classpath:tysonbin.com.jks - #SSL证书密码,在证书tomcat目录下的keystorePass.txt或者是设置的私钥密码 - key-store-password: tx.123456 - #证书类型 - key-store-type: JKS - ``` - -3. 重新生成jar包,将jar包和tysonbin.com.jks文件一起放到服务器同一个目录下。 \ No newline at end of file diff --git "a/\346\225\260\346\215\256\345\272\223/MySQL\350\277\233\351\230\266.md" "b/\346\225\260\346\215\256\345\272\223/MySQL\350\277\233\351\230\266.md" deleted file mode 100644 index d66e18c..0000000 --- "a/\346\225\260\346\215\256\345\272\223/MySQL\350\277\233\351\230\266.md" +++ /dev/null @@ -1,811 +0,0 @@ - - - - -- [事务特性](#%E4%BA%8B%E5%8A%A1%E7%89%B9%E6%80%A7) -- [事务隔离级别](#%E4%BA%8B%E5%8A%A1%E9%9A%94%E7%A6%BB%E7%BA%A7%E5%88%AB) -- [索引](#%E7%B4%A2%E5%BC%95) - - [什么是索引?](#%E4%BB%80%E4%B9%88%E6%98%AF%E7%B4%A2%E5%BC%95) - - [索引的优缺点?](#%E7%B4%A2%E5%BC%95%E7%9A%84%E4%BC%98%E7%BC%BA%E7%82%B9) - - [索引的作用](#%E7%B4%A2%E5%BC%95%E7%9A%84%E4%BD%9C%E7%94%A8) - - [B+ 树](#b-%E6%A0%91) - - [索引实例](#%E7%B4%A2%E5%BC%95%E5%AE%9E%E4%BE%8B) - - [索引分类](#%E7%B4%A2%E5%BC%95%E5%88%86%E7%B1%BB) - - [最左匹配](#%E6%9C%80%E5%B7%A6%E5%8C%B9%E9%85%8D) - - [聚集索引](#%E8%81%9A%E9%9B%86%E7%B4%A2%E5%BC%95) - - [覆盖索引](#%E8%A6%86%E7%9B%96%E7%B4%A2%E5%BC%95) - - [索引失效](#%E7%B4%A2%E5%BC%95%E5%A4%B1%E6%95%88) -- [存储引擎](#%E5%AD%98%E5%82%A8%E5%BC%95%E6%93%8E) - - [InnoDB](#innodb) - - [MyISAM](#myisam) - - [MEMORY](#memory) - - [MyISAM和InnoDB区别](#myisam%E5%92%8Cinnodb%E5%8C%BA%E5%88%AB) -- [MVCC](#mvcc) - - [实现原理](#%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86) - - [read view](#read-view) - - [数据访问流程](#%E6%95%B0%E6%8D%AE%E8%AE%BF%E9%97%AE%E6%B5%81%E7%A8%8B) - - [快照读和当前读](#%E5%BF%AB%E7%85%A7%E8%AF%BB%E5%92%8C%E5%BD%93%E5%89%8D%E8%AF%BB) - - [select 读取锁定](#select-%E8%AF%BB%E5%8F%96%E9%94%81%E5%AE%9A) -- [分库分表](#%E5%88%86%E5%BA%93%E5%88%86%E8%A1%A8) - - [垂直划分](#%E5%9E%82%E7%9B%B4%E5%88%92%E5%88%86) - - [水平划分](#%E6%B0%B4%E5%B9%B3%E5%88%92%E5%88%86) -- [日志](#%E6%97%A5%E5%BF%97) - - [bin log](#bin-log) - - [redo log](#redo-log) - - [undo Log](#undo-log) - - [查询日志](#%E6%9F%A5%E8%AF%A2%E6%97%A5%E5%BF%97) -- [MySQL架构](#mysql%E6%9E%B6%E6%9E%84) - - [Server 层基本组件](#server-%E5%B1%82%E5%9F%BA%E6%9C%AC%E7%BB%84%E4%BB%B6) - - [语法解析器和预处理](#%E8%AF%AD%E6%B3%95%E8%A7%A3%E6%9E%90%E5%99%A8%E5%92%8C%E9%A2%84%E5%A4%84%E7%90%86) - - [查询优化器](#%E6%9F%A5%E8%AF%A2%E4%BC%98%E5%8C%96%E5%99%A8) - - [查询执行引擎](#%E6%9F%A5%E8%AF%A2%E6%89%A7%E8%A1%8C%E5%BC%95%E6%93%8E) - - [查询语句执行流程](#%E6%9F%A5%E8%AF%A2%E8%AF%AD%E5%8F%A5%E6%89%A7%E8%A1%8C%E6%B5%81%E7%A8%8B) - - [更新语句执行过程](#%E6%9B%B4%E6%96%B0%E8%AF%AD%E5%8F%A5%E6%89%A7%E8%A1%8C%E8%BF%87%E7%A8%8B) -- [慢查询](#%E6%85%A2%E6%9F%A5%E8%AF%A2) - - [mysqldumpslow](#mysqldumpslow) -- [分区表](#%E5%88%86%E5%8C%BA%E8%A1%A8) - - [分区表类型](#%E5%88%86%E5%8C%BA%E8%A1%A8%E7%B1%BB%E5%9E%8B) - - [分区的问题](#%E5%88%86%E5%8C%BA%E7%9A%84%E9%97%AE%E9%A2%98) - - [查询优化](#%E6%9F%A5%E8%AF%A2%E4%BC%98%E5%8C%96) -- [其他](#%E5%85%B6%E4%BB%96) - - [processlist](#processlist) - - [exist和in](#exist%E5%92%8Cin) - - - -> PS:本文已经收录到github仓库,此仓库用于分享Java核心知识,包括Java基础、MySQL、SpringBoot、Mybatis、Redis、RabbitMQ等等,面试必备。 -> -> github地址:https://github.com/Tyson0314/Java-learning -> 如果github访问不了,可以访问gitee仓库。 -> gitee地址:https://gitee.com/tysondai/Java-learning - -## 事务特性 - -事务特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。 - -- 原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚。 -- 一致性是指一个事务执行之前和执行之后都必须处于一致性状态。比如a与b账户共有1000块,两人之间转账之后无论成功还是失败,它们的账户总和还是1000。 -- 隔离性。跟隔离级别相关,如read committed,一个事务只能读到已经提交的修改。 -- 持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。 - - - -## 事务隔离级别 - -先了解下几个概念:脏读、不可重复读、幻读。 - -- 脏读是指在一个事务处理过程里读取了另一个未提交的事务中的数据。 -- 不可重复读是指在对于数据库中的某行记录,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,另一个事务修改了数据并提交了。 -- 幻读是当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围的记录时,会产生幻行,就像产生幻觉一样,这就是发生了幻读。 - -不可重复读和脏读的区别是,脏读是某一事务读取了另一个事务未提交的脏数据,而不可重复读则是读取了前一事务提交的数据。 -幻读和不可重复读都是读取了另一条已经提交的事务,不同的是不可重复读的重点是修改,幻读的重点在于新增或者删除。 - -事务隔离就是为了解决上面提到的脏读、不可重复读、幻读这几个问题。 - -MySQL数据库为我们提供的四种隔离级别: - -- Serializable (串行化):通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。 -- Repeatable read (可重复读):MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行,解决了不可重复读的问题。 -- Read committed (读已提交):一个事务只能看见已经提交事务所做的改变。可避免脏读的发生。 -- Read uncommitted (读未提交):所有事务都可以看到其他未提交事务的执行结果。 - -查看隔离级别: - -```mysql -select @@transaction_isolation; -``` - -设置隔离级别: - -```mysql -set session transaction isolation level read uncommitted; -``` - - - -## 索引 - -### 什么是索引? - -索引是存储引擎用于提高数据库表的访问速度的一种数据结构。 - -### 索引的优缺点? - -特点:1、避免进行数据库全表的扫描,大多数情况,只需要扫描较少的索引页和数据页;提升查询语句的执行效率,但降低了新增、删除操作的速度,同时也会占用额外的存储空间。 - -### 索引的作用 - -数据是存储在磁盘上的,查询数据时,如果没有索引,会加载所有的数据到内存,依次进行检索,读取磁盘次数较多。有了索引,就不需要加载所有数据,因为B+树的高度一般在2-4层,最多只需要读取2-4次磁盘,查询速度大大提升。 - -什么情况下需要建索引: - -1. 经常用于查询的字段 -2. 经常用于连接的字段(如外键)建立索引,可以加快连接的速度 -3. 经常需要排序的字段建立索引,因为索引已经排好序,可以加快排序查询速度 - -什么情况下不建索引? - -1. where条件中用不到的字段不适合建立索引 -2. 表记录较少 -3. 需要经常增删改 -4. 参与列计算的列不适合建索引 -5. 区分度不高的字段不适合建立索引,性别等 - -### B+ 树 - -B+ 树是基于B 树和叶子节点顺序访问指针进行实现,它具有B树的平衡性,并且通过顺序访问指针来提高区间查询的性能。 - -在 B+ 树中,节点中的 key 从左到右递增排列,如果某个指针的左右相邻 key 分别是 keyi 和 keyi+1,则该指针指向节点的所有 key 大于等于 keyi 且小于等于 keyi+1。 - -![](http://img.dabin-coder.cn/image/image-20210821165019147.png) - -进行查找操作时,首先在根节点进行二分查找,找到key所在的指针,然后递归地在指针所指向的节点进行查找。直到查找到叶子节点,然后在叶子节点上进行二分查找,找出 key 所对应的数据项。 - -MySQL 数据库使用最多的索引类型是BTREE索引,底层基于B+树数据结构来实现。 - -```mysql -mysql> show index from blog\G; -*************************** 1. row *************************** - Table: blog - Non_unique: 0 - Key_name: PRIMARY - Seq_in_index: 1 - Column_name: blog_id - Collation: A - Cardinality: 4 - Sub_part: NULL - Packed: NULL - Null: - Index_type: BTREE - Comment: -Index_comment: - Visible: YES - Expression: NULL -``` - -### 索引实例 - -下面来看看一个索引的例子: - -如下图,col1 是主键,col2和col3是普通字段。 - -![](http://img.dabin-coder.cn/image/image-20200520234137916.png) - -下图是主键索引对应的 B+树结构,每个节点对应磁盘的一页。 - -![](http://img.dabin-coder.cn/image/image-20200520234200868.png) - -对col3 建立一个单列索引,对应的B+树结构: - -![](http://img.dabin-coder.cn/image/image-20200520234231001.png) - -### 索引分类 -1. 主键索引:名为primary的唯一非空索引,不允许有空值。 - -2. 唯一索引:索引列中的值必须是唯一的,但是允许为空值。 - - 唯一索引和主键索引的区别是:UNIQUE 约束的列可以为null且可以存在多个null值。UNIQUE KEY的用途:唯一标识数据库表中的每条记录,主要是用来防止数据重复插入。 - - 创建唯一索引: - - ```mysql - ALTER TABLE table_name - ADD CONSTRAINT constraint_name UNIQUE KEY(column_1,column_2,...); - ``` - -3. 组合索引:在表中的多个字段组合上创建的索引,只有在查询条件中使用了这些字段的左边字段时,索引才会被使用,使用组合索引时遵循最左前缀原则。 - -4. 全文索引:全文索引,只有在MyISAM引擎上才能使用,只能在CHAR,VARCHAR,TEXT类型字段上使用全文索引。 - -### 最左匹配 - -如果 SQL 语句中用到了组合索引中的最左边的索引,那么这条 SQL 语句就可以利用这个组合索引去进行匹配。当遇到范围查询(>、<、between、like)就会停止匹配,后面的字段不会用到索引。 - -对(a,b,c)建立索引,查询条件使用 a/ab/abc 会走索引,使用 bc 不会走索引。 - -对(a,b,c,d)建立索引,查询条件为`a = 1 and b = 2 and c > 3 and d = 4`,那么,a,b,c三个字段能用到索引,而d就匹配不到。因为遇到了范围查询! - -对(a, b) 建立索引,a 在索引树中是全局有序的,而 b 是全局无序,局部有序(当a相等时,会对b进行比较排序)。直接执行`b = 2`这种查询条件没有办法利用索引。 - -![最左匹配](http://img.dabin-coder.cn/image/image-20210821103313578.png) - -从局部来看,当a的值确定的时候,b是有序的。例如a = 1时,b值为1,2是有序的状态。当a=2时候,b的值为1,4也是有序状态。 因此,你执行`a = 1 and b = 2`是a,b字段能用到索引的。而你执行`a > 1 and b = 2`时,a字段能用到索引,b字段用不到索引。因为a的值此时是一个范围,不是固定的,在这个范围内b值不是有序的,因此b字段用不上索引。 - -### 聚集索引 - -InnoDB使用表的主键构造主键索引树,同时叶子节点中存放的即为整张表的记录数据。聚集索引叶子节点的存储是逻辑上连续的,使用双向链表连接,叶子节点按照主键的顺序排序,因此对于主键的排序查找和范围查找速度比较快。 - -聚集索引的叶子节点就是整张表的行记录。InnoDB 主键使用的是聚簇索引。聚集索引要比非聚集索引查询效率高很多。 - -![](http://img.dabin-coder.cn/image/mysql-clustered-index.png) - -对于InnoDB来说,聚集索引一般是表中的主键索引,如果表中没有显示指定主键,则会选择表中的第一个不允许为NULL的唯一索引。如果没有主键也没有合适的唯一索引,那么innodb内部会生成一个隐藏的主键作为聚集索引,这个隐藏的主键长度为6个字节,它的值会随着数据的插入自增。 - -### 覆盖索引 - -select的数据列只用从索引中就能够取得,不需要到数据表进行二次查询,换句话说查询列要被所使用的索引覆盖。对于innodb表的二级索引,如果索引能覆盖到查询的列,那么就可以避免对主键索引的二次查询。 - -不是所有类型的索引都可以成为覆盖索引。覆盖索引要存储索引列的值,而哈希索引、全文索引不存储索引列的值,所以MySQL只能使用b+树索引做覆盖索引。 - - 对于使用了覆盖索引的查询,在查询前面使用explain,输出的extra列会显示为using index。 - -比如user_like 用户点赞表,组合索引为(user_id, blog_id),user_id和blog_id都不为null。 - -```mysql -explain select blog_id from user_like where user_id = 13; -``` - -Extra中为`Using index`,查询的列被索引覆盖,并且where筛选条件符合最左前缀原则,通过**索引查找**就能直接找到符合条件的数据,不需要回表查询数据。 - -```mysql -explain select user_id from user_like where blog_id = 1; -``` - -Extra中为`Using where; Using index`, 查询的列被索引覆盖,where筛选条件不符合最左前缀原则,无法通过索引查找找到符合条件的数据,但可以通过**索引扫描**找到符合条件的数据,也不需要回表查询数据。 - -![](http://img.dabin-coder.cn/image/cover-index.png) - -### 索引失效 - -导致索引失效的情况: - -- 对于组合索引,不是使用组合索引最左边的字段,则不会使用索引 -- 以%开头的like查询如`%abc`,无法使用索引;非%开头的like查询如`abc%`,相当于范围查询,会使用索引 -- 查询条件中列类型是字符串,没有使用引号,可能会因为类型不同发生隐式转换,使索引失效 -- 判断索引列是否不等于某个值时 -- 对索引列进行运算 -- 使用or连接的条件,如果左边的字段有索引,右边的字段没有索引,那么左边的索引会失效 - -对于Java选手来说,基础知识非常重要,这里给大家分享一个超全面的Java知识总结,GitHub标星137k+,非常有用! - -![](https://pic3.zhimg.com/v2-cdfb49d3d6562191415ae9771055807a.jpg) - -有需要的小伙伴可以自行下载: - -http://mp.weixin.qq.com/s?__biz=Mzg2OTY1NzY0MQ==&mid=100000392&idx=1&sn=f6c8e84651ce48f6ef5b0d496f0f6adf&chksm=4e98ffce79ef76d8dcebdc4787ae8b37760ec193574da9036e46954ae8954ebd56c78792726f#rd - -## 存储引擎 - -MySQL 5.5版本后默认的存储引擎为InnoDB。 - -### InnoDB -InnoDB是MySQL默认的事务型存储引擎,使用最广泛,基于聚簇索引建立的。InnoDB内部做了很多优化,如能够自动在内存中创建自适应hash索引,以加速读操作。 - -**优点**:支持事务和崩溃修复能力。InnoDB引入了行级锁和外键约束。 - -**缺点**:占用的数据空间相对较大。 - -**适用场景**:需要事务支持,并且有较高的并发读写频率。 - -### MyISAM - -数据以紧密格式存储。对于只读数据,或者表比较小、可以容忍修复操作,可以使用MyISAM引擎。MyISAM会将表存储在两个文件中,数据文件.MYD和索引文件.MYI。 - -**优点**:访问速度快。 - -**缺点**:MyISAM不支持事务和行级锁,不支持崩溃后的安全恢复,也不支持外键。 - -**适用场景**:对事务完整性没有要求;只读的数据,或者表比较小,可以忍受修复repair操作。 - -MyISAM特性: - -1. MyISAM对整张表加锁,而不是针对行。读取数据时会对需要读到的所有表加共享锁,写入时则对表加排它锁。但在读取表记录的同时,可以往表中插入新的记录(并法插入)。 -2. 对于MyISAM表,MySQL可以手动或者自动执行检查和修复操作。执行表的修复可能会导致数据丢失,而且修复操作非常慢。可以通过`CHECK TABLE tablename`检查表的错误,如果有错误执行`REPAIR TABLE tablename`进行修复。 - -### MEMORY -MEMORY引擎将数据全部放在内存中,访问速度较快,但是一旦系统奔溃的话,数据都会丢失。 - -MEMORY引擎默认使用哈希索引,将键的哈希值和指向数据行的指针保存在哈希索引中。哈希索引使用拉链法来处理哈希冲突。 - -**优点**:访问速度较快。 - -**缺点**: - -1. 哈希索引数据不是按照索引值顺序存储,无法用于排序。 -2. 不支持部分索引匹配查找,因为哈希索引是使用索引列的全部内容来计算哈希值的。 -3. 只支持等值比较,不支持范围查询。 -4. 当出现哈希冲突时,存储引擎需要遍历链表中所有的行指针,逐行进行比较,直到找到符合条件的行。 - -### MyISAM和InnoDB区别 - -1. **是否支持行级锁** : MyISAM 只有表级锁,而InnoDB 支持行级锁和表级锁,默认为行级锁。 - -2. **是否支持事务和崩溃后的安全恢复**: MyISAM 强调的是性能,每次查询具有原子性,其执行速度比InnoDB类型更快,但是不提供事务支持。但是InnoDB 提供事务支持,具有事务、回滚和崩溃修复能力。 - -3. **是否支持外键:** MyISAM不支持,而InnoDB支持。 - -4. **是否支持MVCC** :仅 InnoDB 支持。应对高并发事务,MVCC比单纯的加锁更高效;MVCC只在 `READ COMMITTED` 和 `REPEATABLE READ` 两个隔离级别下工作;MVCC可以使用乐观锁和悲观锁来实现;各数据库中MVCC实现并不统一。 - -5. MyISAM不支持聚集索引,InnoDB支持聚集索引。 - - myisam引擎主键索引和其他索引区别不大,叶子节点都包含索引值和行指针。 - innodb引擎二级索引叶子存储的是索引值和主键值(不是行指针),这样可以减少行移动和数据页分裂时二级索引的维护工作。 - - ![myisam-innodb-index](http://img.dabin-coder.cn/image/myisam-innodb-index.png) - - - -## MVCC - -MVCC(`Multiversion concurrency control`) 就是同一份数据保留多版本的一种方式,进而实现并发控制。可以认为MVCC是行级锁的变种。在查询的时候,通过read view和版本链找到对应版本的数据。 - -MVCC只适用于read committed和repeatable read。使用事务更新行记录时,会生成一个新的版本的行记录。 - -作用:提升并发性能。对于高并发场景,MVCC比行级锁更有效、开销更小。 - -### 实现原理 - -mvcc实现依赖于版本链,版本链是通过表的三个隐藏字段实现。 - -- 事务id:data_trx_id,当前事务id - -- 回滚指针:data_roll_ptr,指向当前行记录的上一个版本,通过这个指针将数据的多个版本连接在一起构成undo log版本链 - -- 主键:db_row_id,如果数据表没有主键,InnoDB会自动生成主键 - -使用事务更新行记录的时候,就会生成版本链: - -1. 用排他锁锁住该行; -2. 将该行原本的值拷贝到 undo log,作为旧版本用于回滚; -3. 修改当前行的值,生成一个新版本,更新事务id,使回滚指针指向旧版本的记录,这样就形成一条版本链; -4. 记录redo log; - -![](http://img.dabin-coder.cn/image/mvcc-impl.png) - -### read view - -read view就是在某一时刻给事务打snapshot快照。在read_view内部维护一个活跃事务链表,这个链表包含在创建read view之前还未提交的事务,不包含创建read view之后提交的事务。 - -不同隔离级别创建read view的时机不同。 - -read committed:每次执行select都会创建新的read_view,保证能读取到其他事务已经提交的修改。 - -repeatable read:在一个事务范围内,第一次select时更新这个read_view,以后不会再更新,后续所有的select都是复用之前的read_view。这样可以保证事务范围内每次读取的内容都一样,即可重复读。 - -### 数据访问流程 - -当访问数据行时,会先判断当前版本数据项是否可见,如果是不可见的,会通过版本链找到一个可见的版本。 - -- 如果数据行的当前版本 < read view最早的活跃事务id:说明在创建read_view时,修改该数据行的事务已提交,该版本的数据行可被当前事务读取到。 -- 如果数据行的当前版本 >= read view最晚的活跃事务id:说明当前版本的数据行的事务是在创建read_view之后生成的,该版本的数据行不可以被当前事务访问。此时需要通过版本链找到上一个版本,然后重新判断该版本数据对当前事务的可见性。 -- 如果数据行的当前版本在最早的活跃事务id和最晚的活跃事务id之间: - 1. 需要在活跃事务链表中查找是否包含该数据行的最新事务id,即生成当前版本数据行的事务是否已经提交。 - 2. 如果存在,说明生成当前版本数据行的事务未提交,所以该版本的数据行不能被当前事务访问。此时需要通过版本链找到上一个版本,然后重新判断该版本的可见性。 - 3. 如果不存在,说明事务已经提交,可以直接读取该数据行。 - -**总结**:通过比较read view和数据行的当前版本,找到当前事务可见的版本,进而实现read commit和repeatable read的事务隔离级别。 - -### 快照读和当前读 - -记录的两种读取方式。 - -快照读:读取的是快照版本,也就是历史版本。普通的SELECT就是快照读。通过MVCC来进行控制的,不用加锁。 - -当前读:读取的是最新版本。UPDATE、DELETE、INSERT、SELECT … LOCK IN SHARE MODE、SELECT … FOR UPDATE是当前读。 - -快照读情况下,InnoDB通过mvcc机制避免了幻读现象。而mvcc机制无法避免当前读情况下出现的幻读现象。 - -事务a和事务b同时开启事务,事务a插入数据然后提交,事务b执行全表的update,然后执行查询,查到了事务A中添加的数据。 - -![](http://img.dabin-coder.cn/image/幻读1.png) - -MySQL如何实现避免幻读: - -- 在快照读情况下,MySQL通过mvcc来避免幻读。 -- 在当前读情况下,MySQL通过next-key来避免幻读(加行锁和间隙锁来实现的)。 - -![](http://img.dabin-coder.cn/image/current-read.png) - -next-key包括两部分:行锁和间隙锁。行锁是加在索引上的锁,间隙锁是加在索引之间的。 - -```mysql -select * from table where id<6 lock in share mode;--共享锁 锁定的是小于6的行和等于6的行 -select * from table where id<6 for update;--排他锁 -``` - -实际上很多的项目中是不会使用到上面的两种方法的,串行化读的性能太差,而且其实幻读很多时候是我们完全可以接受的。 - -Serializable隔离级别也可以避免幻读,会锁住整张表,并发性极低,一般很少使用。 - -### select 读取锁定 - -在SELECT 的读取锁定主要分为两种方式:共享锁和排他锁。 - -```mysql -SELECT ... LOCK IN SHARE MODE  -SELECT ... FOR UPDATE -``` - -这两种方式主要的不同在于LOCK IN SHARE MODE 多个事务同时更新同一个表单时很容易造成死锁。这种情况最好使用SELECT ...FOR UPDATE。 - -`select * from goods where id = 1 for update`:申请排他锁的前提是,没有线程对该结果集的任何行数据使用排它锁或者共享锁,否则申请会受到阻塞。在进行事务操作时,MySQL会对查询结果集的每行数据添加排它锁,其他线程对这些数据的更改或删除操作会被阻塞(只能读操作),直到该语句的事务被commit语句或rollback语句结束为止。 - -select... for update 使用注意事项 - -1. for update 仅适用于Innodb,且必须在事务范围内才能生效。 -2. 根据主键进行查询,查询条件为 like或者不等于,主键字段产生表锁。 -3. 根据非索引字段进行查询,name字段产生表锁。 - - - -## 分库分表 - -当单表的数据量达到1000W或100G以后,优化索引、添加从库等可能对数据库性能提升效果不明显,此时就要考虑对其进行切分了。切分的目的就在于减少数据库的负担,缩短查询的时间。 - -数据切分可以分为两种方式:垂直(纵向)划分和水平(横向)划分。 - -### 垂直划分 - -垂直划分数据库是根据业务进行划分,例如将shop库中涉及商品、订单、用户的表分别划分出成一个库,通过降低单库的大小来提高性能,但这种方式并没有解决高数据量带来的性能损耗。同样的,分表的情况就是将一个大表根据业务功能拆分成一个个子表,例如商品基本信息和商品描述,商品基本信息一般会展示在商品列表,商品描述在商品详情页,可以将商品基本信息和商品描述拆分成两张表。 - -![垂直划分](https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/e130e5b8-b19a-4f1e-b860-223040525cf6.jpg) - -优点:行记录变小,数据页可以存放更多记录,在查询时减少I/O次数。 - -缺点: - -- 主键出现冗余,需要管理冗余列; -- 会引起表连接JOIN操作,可以通过在业务服务器上进行join来减少数据库压力; -- 依然存在单表数据量过大的问题。 - -### 水平划分 - -水平划分是根据一定规则,例如时间或id序列值等进行数据的拆分。比如根据年份来拆分不同的数据库。每个数据库结构一致,但是数据得以拆分,从而提升性能。 - -![水平划分](https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/63c2909f-0c5f-496f-9fe5-ee9176b31aba.jpg) - -优点:单库(表)的数据量得以减少,提高性能;切分出的表结构相同,程序改动较少。 - -缺点: - -- 分片事务一致性难以解决 -- 跨节点Join性能差,逻辑复杂 -- 数据分片在扩容时需要迁移 - -## 日志 - -MySQL日志 主要包括查询日志、慢查询日志、事务日志、错误日志、二进制日志等。其中比较重要的是二进制日志binlog和事务日志 redo log(重做日志)和 undo log(回滚日志)。 - -### bin log - -二进制日志(bin log)是MySQL数据库级别的文件,记录对MySQL数据库执行修改的所有操作,不会记录select和show语句,主要用于恢复数据库和同步数据库。 - -查看bin log是否开启,以及保存位置: - -```mysql -MySQL> show variables like '%log_bin%'; -+---------------------------------+----------------------------------------------------+ -| Variable_name | Value | -+---------------------------------+----------------------------------------------------+ -| log_bin | ON | -| log_bin_basename | F:\java\MySQL8\data\Data\DESKTOP-8F30VS1-bin | -| log_bin_index | F:\java\MySQL8\data\Data\DESKTOP-8F30VS1-bin.index | -| log_bin_trust_function_creators | OFF | -| log_bin_use_v1_row_events | OFF | -| sql_log_bin | ON | -+---------------------------------+----------------------------------------------------+ -``` - -关闭bin log,找到/etc/my.cnf文件,注释以下代码: - -```mysql -log-bin=MySQL-bin -binlog_format=mixed -``` - -### redo log - -重做日志(redo log)是Innodb引擎级别,用来记录Innodb存储引擎的事务日志,不管事务是否提交都会记录下来,用于数据恢复。当数据库发生故障,InnoDB存储引擎会使用redo log恢复到发生故障前的时刻,以此来保证数据的完整性。将参数innodb_flush_log_at_tx_commit设置为1,那么在执行commit时将redo log同步写到磁盘。 - -bin log和redo log区别: - -1. bin log会记录所有日志记录,包括innoDB、MyISAM等存储引擎的日志;redo log只记录innoDB自身的事务日志 -2. bin log只在事务提交前写入到磁盘,一个事务只写一次,无论事务多大;而在事务进行过程,会有redo log不断写入磁盘 -3. binlog 是逻辑日志,记录的是SQL语句的原始逻辑;redo log 是物理日志,记录的是在某个数据页上做了什么修改。 - -### undo Log - -除了记录redo log外,当进行数据修改时还会记录undo log,undo log用于数据的撤回操作,它保留了记录修改前的内容。通过undo log可以实现事务回滚,并且可以根据undo log回溯到某个特定的版本的数据,实现MVCC。 - -### 查询日志 - -记录所有对MySQL请求的信息,无论请求是否正确执行。 - -```mysql -MySQL> show variables like '%general_log%'; -+------------------+----------------------------------+ -| Variable_name | Value | -+------------------+----------------------------------+ -| general_log | OFF | -| general_log_file | /var/lib/MySQL/VM_0_7_centos.log | -+------------------+----------------------------------+ -``` - - - -## MySQL架构 - -MySQL主要分为 Server 层和存储引擎层: - -- **Server 层**:主要包括连接器、查询缓存、分析器、优化器、执行器等,所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图,函数等,还有一个通用的日志模块 binglog 日志模块。 -- **存储引擎**: 主要负责数据的存储和读取。server 层通过api与存储引擎进行通信。 - -![MySQL-archpng](http://img.dabin-coder.cn/image/mysql-archpng.png) - -### Server 层基本组件 - -- **连接器:** 当客户端连接 MySQL 时,server层会对其进行身份认证和权限校验。 -- **查询缓存:** 执行查询语句的时候,会先查询缓存(MySQL 8.0 版本后移除),先校验这个 sql 是否执行过,如果缓存 key (sql语句)被命中,就会直接返回给客户端,如果没有命中,就会执行后续的操作。MySQL 查询不建议使用缓存,因为查询缓存失效在实际业务场景中可能会非常频繁,不推荐使用。 -- **分析器:** 没有命中缓存的话,SQL 语句就会经过分析器,主要分为两步,词法分析和语法分析,先看 SQL 语句要做什么,再检查 SQL 语句语法是否正确。 -- **优化器:** 优化器对查询进行优化,包括重写查询、决定表的读写顺序以及选择合适的索引等,生成执行计划。 -- **执行器:** 首先执行前会校验该用户有没有权限,如果没有权限,就会返回错误信息,如果有权限,就会根据执行计划去调用引擎的接口,返回结果。 - -#### 语法解析器和预处理 - -MySQL通过关键字将SQL语句进行解析,生成解析树。 - -MySQL解析器使用MySQL语法规则验证和解析查询,比如验证是否使用正确的关键字、关键字的次序是否正确和验证引号是否前后正确匹配。 - -预处理器会进一步检查解析树是否合法,如检查数据表和数据列是否存在,然后验证权限。 - -#### 查询优化器 - -优化器会找出一个它认为最优的执行计划。 - -MySQL 能够处理的优化类型: - -1. 重新定义表的关联顺序。数据表的关联并不是总按照查询中指定的顺序进行的。 -3. 使用等价变换,简化表达式。比如将 `5=5 AND a > 5` 转化为 `a > 5`。 -4. 优化COUNT/MIN/MAX。MIN查询最小值,对应的是b+树索引的第一行记录,优化器会将这个表达式作为一个常数对待。 -4. 列表IN()的比较。很多数据库系统,IN完成等价于多个OR子句。MySQL不一样,MySQL将IN列表的数据先进行排序,然后通过二分查找的方式确定列表的值是否符合要求,时间复杂度为O(logN),而OR查询的时间复杂度为O(N)。当IN列表有大量取值时,处理速度相比OR查询会更快。 -5. 覆盖索引扫描。 -6. 将外连接转化成内连接。某些情况下,外连接可能等价于一个内连接。 - -#### 查询执行引擎 - -在解析和优化阶段,MySQL将生成查询对应的执行计划,MySQL的查询执行引擎则根据这个执行计划,调用存储引擎接口来完成整个查询。 - -### 查询语句执行流程 - -查询语句的执行流程如下:权限校验、查询缓存、分析器、优化器、权限校验、执行器、引擎。 - -查询语句: - -```mysql -select * from user where id > 1 and name = '大彬'; -``` - -1. 检查权限,没有权限则返回错误; -2. MySQL以前会查询缓存,缓存命中则直接返回,没有则执行下一步; -3. 词法分析和语法分析。提取表名、查询条件,检查语法是否有错误; -4. 两种执行方案,先查 `id > 1` 还是 `name = '大彬'`,优化器根据自己的优化算法选择执行效率最好的方案; -5. 校验权限,有权限就调用数据库引擎接口,返回引擎的执行结果。 - -### 更新语句执行过程 - -更新语句执行流程如下:分析器、权限校验、执行器、引擎、redo log(prepare 状态)、binlog、redo log(commit状态) - -更新语句: - -```mysql -update user set name = '大彬' where id = 1; -``` - -1. 先查询到 id 为1的记录,有缓存会使用缓存 -2. 拿到查询结果,将 name 更新为 大彬,然后调用引擎接口,写入更新数据,innodb 引擎将数据保存在内存中,同时记录 redo log,此时 redo log 进入 prepare 状态,然后告诉执行器,执行完成了,随时可以提交。 -3. 执行器收到通知后记录 binlog,然后调用引擎接口,提交 redo log 为提交状态。 -4. 更新完成。 - -为什么记录完 redo log,不直接提交,先进入prepare状态? - -假设先写 redo log 直接提交,然后写 binlog,写完 redo log 后,机器挂了,binlog 日志没有被写入,那么机器重启后,这台机器会通过 redo log 恢复数据,但是这个时候 binlog 并没有记录该数据,后续进行机器备份的时候,就会丢失这一条数据,同时主从同步也会丢失这一条数据。 - -假设写完了 binlog,机器异常重启了,由于没有 redo log,本机是无法恢复这一条记录的,但是 binlog 又有记录,那么和上面同样的道理,就会产生数据不一致的情况。 - - - -## 慢查询 - -sql 语句查询时间超过(不包括等于) long_query_time,称为慢查询。 - -查看慢查询配置: - -```mysql -show variables like '%slow_query_log%'; #查看慢查询配置 -set global slow_query_log=1; #开启慢查询 -``` - -使用`set global slow_query_log=1`开启了慢查询日志只对当前数据库生效,如果MySQL重启后则会失效。如果要永久生效,就必须修改配置文件my.cnf。 - -```properties -slow_query_log =1 -slow_query_log_file=/tmp/MySQL_slow.log #系统默认会给一个缺省的文件host_name-slow.log -``` - -默认情况下long_query_time的值为10秒,可以使用命令修改,也可以在my.cnf参数里面修改。 - -```mysql -show variables like 'long_query_time%'; -set global long_query_time=4; #需要重新连接或新开一个会话才能看到修改值或者使用show global variables like 'long_query_time' -``` - -MySQL数据库支持同时两种日志存储方式,配置的时候以逗号隔开即可,如:log_output='FILE,TABLE'。 - -日志记录到系统的专用日志表中,要比记录到文件耗费更多的系统资源,因此对于需要启用慢查询日志,又需要能够获得更高的系统性能,那么建议优先记录到文件。 - -### mysqldumpslow - -如果自己手动查找、分析SQL,显然是个体力活,MySQL提供了日志分析工具mysqldumpslow。 - -获取执行时间最长的10条sql语句: - -```mysql -mysqldumpslow -s al -n 10 /usr/local/MySQL/data/slow.log -``` - - - -## 分区表 - -分区表是一个独立的逻辑表,但是底层由多个物理子表组成。 - -当查询条件的数据分布在某一个分区的时候,查询引擎只会去某一个分区查询,而不是遍历整个表。在管理层面,如果需要删除某一个分区的数据,只需要删除对应的分区即可。 - -### 分区表类型 - -1. 按照范围分区。 - - ```mysql - CREATE TABLE test_range_partition( - id INT auto_increment, - createdate DATETIME, - primary key (id,createdate) - ) - PARTITION BY RANGE (TO_DAYS(createdate) ) ( - PARTITION p201801 VALUES LESS THAN ( TO_DAYS('20210201') ), - PARTITION p201802 VALUES LESS THAN ( TO_DAYS('20210301') ), - PARTITION p201803 VALUES LESS THAN ( TO_DAYS('20210401') ), - PARTITION p201804 VALUES LESS THAN ( TO_DAYS('20210501') ), - PARTITION p201805 VALUES LESS THAN ( TO_DAYS('20210601') ), - PARTITION p201806 VALUES LESS THAN ( TO_DAYS('20210701') ), - PARTITION p201807 VALUES LESS THAN ( TO_DAYS('20210801') ), - PARTITION p201808 VALUES LESS THAN ( TO_DAYS('20210901') ), - PARTITION p201809 VALUES LESS THAN ( TO_DAYS('20211001') ), - PARTITION p201810 VALUES LESS THAN ( TO_DAYS('20211101') ), - PARTITION p201811 VALUES LESS THAN ( TO_DAYS('20211201') ) - ); - - insert into test_range_partition (createdate) values ('20210105'); - insert into test_range_partition (createdate) values ('20210205'); - ``` - - 在`/var/lib/mysql/data/`可以找到对应的数据文件,每个分区表都有一个使用#分隔命名的表文件: - - ``` - -rw-rw---- 1 mysql mysql 65 Aug 21 09:24 db.opt - -rw-rw---- 1 mysql mysql 98304 Aug 21 09:27 test_range_partition#P#p201801.ibd - -rw-rw---- 1 mysql mysql 98304 Aug 21 09:27 test_range_partition#P#p201802.ibd - -rw-rw---- 1 mysql mysql 98304 Aug 21 09:27 test_range_partition#P#p201803.ibd - -rw-rw---- 1 mysql mysql 98304 Aug 21 09:27 test_range_partition#P#p201804.ibd - -rw-rw---- 1 mysql mysql 98304 Aug 21 09:27 test_range_partition#P#p201805.ibd - -rw-rw---- 1 mysql mysql 98304 Aug 21 09:27 test_range_partition#P#p201806.ibd - -rw-rw---- 1 mysql mysql 98304 Aug 21 09:27 test_range_partition#P#p201807.ibd - -rw-rw---- 1 mysql mysql 98304 Aug 21 09:27 test_range_partition#P#p201808.ibd - -rw-rw---- 1 mysql mysql 98304 Aug 21 09:27 test_range_partition#P#p201809.ibd - -rw-rw---- 1 mysql mysql 98304 Aug 21 09:27 test_range_partition#P#p201810.ibd - -rw-rw---- 1 mysql mysql 98304 Aug 21 09:27 test_range_partition#P#p201811.ibd - -rw-rw---- 1 mysql mysql 8598 Aug 21 09:27 test_range_partition.frm - -rw-rw---- 1 mysql mysql 116 Aug 21 09:27 test_range_partition.par - ``` - -2. list分区。对于List分区,分区字段必须是已知的,如果插入的字段不在分区时枚举值中,将无法插入。 - - ```mysql - create table test_list_partiotion - ( - id int auto_increment, - data_type tinyint, - primary key(id,data_type) - )partition by list(data_type) - ( - partition p0 values in (0,1,2,3,4,5,6), - partition p1 values in (7,8,9,10,11,12), - partition p2 values in (13,14,15,16,17) - ); - ``` - -3. hash分区,可以将数据均匀地分布到预先定义的分区中。 - - ```mysql - drop table test_hash_partiotion; - create table test_hash_partiotion - ( - id int auto_increment, - create_date datetime, - primary key(id,create_date) - )partition by hash(year(create_date)) partitions 10; - ``` - -### 分区的问题 - -1. 打开和锁住所有底层表的成本可能很高。当查询访问分区表时,MySQL需要打开并锁住所有的底层表,这个操作在分区过滤之前发生,所以无法通过分区过滤来降低此开销,会影响到查询速度。可以通过批量操作来降低此类开销,比如批量插入、LOAD DATA INFILE和一次删除多行数据。 -2. 维护分区的成本可能很高。例如重组分区,会先创建一个临时分区,然后将数据复制到其中,最后再删除原分区。 -3. 所有分区必须使用相同的存储引擎。 - -### 查询优化 - -分区最大的优点就是优化器可以根据分区函数过滤掉一些分区,可以让查询扫描更少的数据。在查询条件中加入分区列,就可以让优化器过滤掉无需访问的分区。如果查询条件没有分区列,MySQL会让存储引擎访问这个表的所有分区。需要注意的是,查询条件中的分区列不能使用表达式。 - - - -## 其他 - -### processlist - -`select * `会查询出不需要的、额外的数据,那么这些额外的数据在网络上进行传输,带来了额外的网络开销。 - -`show processlist` 或 `show full processlist` 可以查看当前 MySQL 是否有压力,正在运行的sql,有没有慢 SQL 正在执行。 - -- **id** - 线程ID,可以用:`kill id;` 杀死一个线程,很有用 - -- **db** - 数据库 - -- **user** - 用户 - -- **host** - 连库的主机IP - -- **command** - 当前执行的命令,比如最常见的:Sleep,Query,Connect 等 - -- **time** - 消耗时间,单位秒,很有用 - -- **state** - 执行状态 - - sleep,线程正在等待客户端发送新的请求 - - query,线程正在查询或者正在将结果发送到客户端 - - Sorting result,线程正在对结果集进行排序 - - Locked,线程正在等待锁 - -- **info** - 执行的SQL语句,很有用 - - - -### exist和in - -exists 用于对外表记录做筛选。 - -exists 会遍历外表,将外查询表的每一行,代入内查询进行判断。当 exists 里的条件语句能够返回记录行时,条件就为真,返回外表当前记录。反之如果exists里的条件语句不能返回记录行,条件为假,则外表当前记录被丢弃。 - -```mysql -select a.* from A a -where exists(select 1 from B b where a.id=b.id) -``` - -in 是先把后边的语句查出来放到临时表中,然后遍历临时表,将临时表的每一行,代入外查询去查找。 - -```mysql -select * from A -where id in(select id from B) -``` - -子查询的表大的时候,使用EXISTS可以有效减少总的循环次数来提升速度;当外查询的表大的时候,使用IN可以有效减少对外查询表循环遍历来提升速度。 - - - -> 参考资料: -> -> 高性能MySQL书籍 -> -> MVCC实现原理:https://zhuanlan.zhihu.com/p/64576887 -> -> 多版本并发控制机制:https://www.cnblogs.com/axing-articles/p/11415763.html -> -> 排他锁分析:https://blog.csdn.net/claram/article/details/54023216 -> -> 分区表:https://www.cnblogs.com/wy123/p/9778590.html -> -> 一条SQL语句在MySQL中如何执行的:https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485097&idx=1&sn=84c89da477b1338bdf3e9fcd65514ac1&chksm=cea24962f9d5c074d8d3ff1ab04ee8f0d6486e3d015cfd783503685986485c11738ccb542ba7&token=79317275&lang=zh_CN#rd - diff --git "a/\346\241\206\346\236\266/SpringBoot\345\256\236\346\210\230.md" "b/\346\241\206\346\236\266/SpringBoot\345\256\236\346\210\230.md" deleted file mode 100644 index 2c1a423..0000000 --- "a/\346\241\206\346\236\266/SpringBoot\345\256\236\346\210\230.md" +++ /dev/null @@ -1,1028 +0,0 @@ - - - - -- [Spring Boot基础](#spring-boot%E5%9F%BA%E7%A1%80) - - [特点](#%E7%89%B9%E7%82%B9) -- [Spring Boot核心](#spring-boot%E6%A0%B8%E5%BF%83) - - [基本配置](#%E5%9F%BA%E6%9C%AC%E9%85%8D%E7%BD%AE) - - [外部配置](#%E5%A4%96%E9%83%A8%E9%85%8D%E7%BD%AE) - - [日志配置](#%E6%97%A5%E5%BF%97%E9%85%8D%E7%BD%AE) - - [Profile配置](#profile%E9%85%8D%E7%BD%AE) -- [Spring Boot的Web开发](#spring-boot%E7%9A%84web%E5%BC%80%E5%8F%91) - - [Thymeleaf 模板引擎](#thymeleaf-%E6%A8%A1%E6%9D%BF%E5%BC%95%E6%93%8E) - - [Thymeleaf 基础知识](#thymeleaf-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86) - - [与 Spring MVC 集成](#%E4%B8%8E-spring-mvc-%E9%9B%86%E6%88%90) - - [Spring Boot 的 Thymeleaf 支持](#spring-boot-%E7%9A%84-thymeleaf-%E6%94%AF%E6%8C%81) - - [实战](#%E5%AE%9E%E6%88%98) - - [Web 相关配置](#web-%E7%9B%B8%E5%85%B3%E9%85%8D%E7%BD%AE) - - [Spring Boot 提供的自动配置](#spring-boot-%E6%8F%90%E4%BE%9B%E7%9A%84%E8%87%AA%E5%8A%A8%E9%85%8D%E7%BD%AE) - - [实现自己的 MVC 配置](#%E5%AE%9E%E7%8E%B0%E8%87%AA%E5%B7%B1%E7%9A%84-mvc-%E9%85%8D%E7%BD%AE) - - [注册 Servlet、Filter、Listener](#%E6%B3%A8%E5%86%8C-servletfilterlistener) - - [Tomcat配置](#tomcat%E9%85%8D%E7%BD%AE) - - [配置 Tomcat](#%E9%85%8D%E7%BD%AE-tomcat) - - [替换 Tomcat](#%E6%9B%BF%E6%8D%A2-tomcat) -- [Spring Boot 的数据访问](#spring-boot-%E7%9A%84%E6%95%B0%E6%8D%AE%E8%AE%BF%E9%97%AE) - - [Docker 常用命令及参数](#docker-%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4%E5%8F%8A%E5%8F%82%E6%95%B0) - - [Docker 镜像命令](#docker-%E9%95%9C%E5%83%8F%E5%91%BD%E4%BB%A4) - - [Docker 容器命令](#docker-%E5%AE%B9%E5%99%A8%E5%91%BD%E4%BB%A4) - - [Spring Boot 对 Spring Data JPA 的支持](#spring-boot-%E5%AF%B9-spring-data-jpa-%E7%9A%84%E6%94%AF%E6%8C%81) - - [JDBC 的自动配置](#jdbc-%E7%9A%84%E8%87%AA%E5%8A%A8%E9%85%8D%E7%BD%AE) - - [对 JPA 的自动配置](#%E5%AF%B9-jpa-%E7%9A%84%E8%87%AA%E5%8A%A8%E9%85%8D%E7%BD%AE) - - [对 Spring Data JPA 的自动配置](#%E5%AF%B9-spring-data-jpa-%E7%9A%84%E8%87%AA%E5%8A%A8%E9%85%8D%E7%BD%AE) -- [@Value原理](#value%E5%8E%9F%E7%90%86) -- [启动过程](#%E5%90%AF%E5%8A%A8%E8%BF%87%E7%A8%8B) - - - -## Spring Boot基础 -理念:习惯优于配置,内置习惯性配置,无需手动进行配置。使用Spring boot可以很快创建一个独立运行、准生产级别的基于Spring框架的项目,不需要或者只需很少的Spring配置。 -### 特点 -- 内置servlet容器,不需要在服务器部署 tomcat。只需要将项目打成 jar 包,使用 java -jar xxx.jar一键式启动项目 -- SpringBoot提供了starter,把常用库聚合在一起,简化复杂的环境配置,快速搭建spring应用环境 -- 可以快速创建独立运行的spring项目,集成主流框架 -- 准生产环境的运行应用监控 - -## Spring Boot核心 - -### 基本配置 - -Spring Boot通常有个Application入口类: - -```java -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@SpringBootApplication -public class SpringbootDemoApplication { - public static void main(String[] args) { - SpringApplication.run(SpringbootDemoApplication.class, args); - } - -} -``` - -@SpringBootApplication是Spring Boot的核心注解,它是组合注解,源码如下: - -```java -@Target({ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Inherited -@SpringBootConfiguration -@EnableAutoConfiguration -@ComponentScan( - excludeFilters = {@Filter( - type = FilterType.CUSTOM, - classes = {TypeExcludeFilter.class} -), @Filter( - type = FilterType.CUSTOM, - classes = {AutoConfigurationExcludeFilter.class} -)} -) -public @interface SpringBootApplication { -} -``` - -@SpringBootApplication注解组合了@Configuration、@EnableAutoConfiguration和@ComponentScan注解,若不使用@SpringBootApplication注解,则可以在入口类上直接使用@Configuration、@EnableAutoConfiguration和@ComponentScan。 - -@EnableAutoConfiguration作用是让Spring Boot根据类路径中的jar包依赖为当前项目进行自动配置。例如,添加了spring-boot-starter-web依赖,会自动添加Tomcat和Spring MVC依赖,那么Spring Boot会对Tomcat和Spring MVC进行自动配置。 - -@Configuration标注在类上,相当于把该类作为spring的xml配置文件中的``,作用是配置spring容器。 - -**关闭特定的自动配置** - -使用@SpringBootApplication的exclude参数关闭特定的自动配置。 - -```java -@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) -``` - -**Spring Boot的配置文件** - -Spring Boot使用一个全局的配置文件application.properties或application.yml。这个全局配置文件可以对一些默认配置的配置值进行修改。如修改Tomcat默认的端口号,并将默认的访问路径"/"修改为"/hello": - -```properties -server.port=9090 -server.context-path=/hello -``` - -**starter pom** - -Spring Boot为我们提供了简化企业级开发绝大多数场景的starter pom,只要使用了应用场景所需要的starter pom,相应的配置就可以消除,就可以得到Spring Boot为我们提供的自动配置的bean。 - -**xml配置** - -Spring Boot提倡零配置,但实际项目中可能需要使用xml配置,此时可以通过Spring提供的@ImportResource来加载xml配置。 - -```java -@ImportResource({"classpath:xxx-context.xml", "classpath:yyy-context.xml"}) -``` - -### 外部配置 - -**命令行参数配置** - -Spring Boot是基于jar包运行的,打成jar包的程序可以直接通过下面的命令运行: - -``` -java -jar xx.jar -``` - -可以通过以下命令修改端口号: - -``` -java -jar xx.jar --server.port=9090 -``` - -**常规属性配置** - -在Spring Boot里,我们只需在application.properties定义属性,直接使用@Value注入即可。 - -application.properties增加属性: - -```properties -book.author=tyson -book.name=life -``` - -修改入口类: - -```java -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@SpringBootApplication -public class SpringbootDemoApplication { - - @Value("${book.author}") - private String bookAuthor; - @Value("${book.name}") - private String bookName; - - @RequestMapping("/") - public String index() { - return "book name: " + bookName + ", written by: " + bookAuthor; - } - - public static void main(String[] args) { - SpringApplication.run(SpringbootDemoApplication.class, args); - } - -} -``` - -@RestController=@Controller + @ResponseBody,注解的类里面的方法以json格式输出。 - -**类型安全的配置** - -Spring Boot提供了基于类型安全的配置方式,通过@ConfigurationProperties将配置文件application.properties中配置的属性值映射到当前类的属性中,从而实现类型安全的配置。 - -类型安全的bean: - -```java -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.stereotype.Component; - -@ConfigurationProperties(prefix = "book") -public class BookConfig { - private String name; - private String author; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getAuthor() { - return author; - } - - public void setAuthor(String author) { - this.author = author; - } -} -``` - -通过@ConfigurationProperties加载properties文件内的配置,通过prefix属性指定前缀。 - -检验代码: - -```java -import com.tyson.springbootdemo.config.BookConfig; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@SpringBootApplication -@EnableConfigurationProperties({BookConfig.class}) -public class SpringbootDemoApplication { - - @Autowired - public BookConfig bookConfig; - - @RequestMapping("/") - public String index() { - return "book name: " + bookConfig.getName() + ", written by: " + bookConfig.getAuthor(); - } - - public static void main(String[] args) { - SpringApplication.run(SpringbootDemoApplication.class, args); - } - -} -``` - -@EnableConfigurationProperties注解将带有@ConfigurationProperties注解的类注入为Spring容器的Bean。 - -### 日志配置 - -Spring Boot 支持 Log4J、Logback、Java Util Logging、Log4J2 作为日志框架,无论使用哪种日志框架,Spring Boot 已为当前使用日志框架的控制台输出及文件输出做好了配置。默认情况下,Spring Boot 使用 Logback 作为日志框架,日志级别为 INFO。 - -配置日志文件: - -```properties -logging.path=H:/log/ -logging.file=springbootdemo.log -``` - -配置日志级别,格式为 logging.level.包名=级别: - -```properties -logging.level.root=INFO #root级别,项目所有日志 -logging.level.org.springframework.web=DEBUG #package级别 -``` - -配置日志样式: - -```properties -logging.pattern.console=%d{yyyy/MM/dd-HH:mm:ss} [%thread] %-5level %logger- %msg%n -logging.pattern.file=%d{yyyy/MM/dd-HH:mm} [%thread] %-5level %logger- %msg%n -``` - -### Profile配置 - -Profile 是 Spring 用来针对不同环境对不同配置提供支持的,全局Profile配置使用 application-{profile}.properties(如application-prod.properties)。通过在 application.properties 中设置spring.profiles.active=prod 来制定活动的Profile。 - -假如有生产和开发环境,生产环境下端口号为80,开发环境下端口号为8888。配置文件如下: - -application-prod.properties: - -```properties -server.port=80 -``` - -application-dev.properties - -```properties -server.port=8888 -``` - -application.properties 增加: - -```properties -spring.profiles.active=prod -``` - -启动程序结果为: - -```java -2019-03-03 09:17:08.003 INFO 17812 --- [ main] c.t.s.SpringbootDemoApplication : The following profiles are active: prod -2019-03-03 09:17:11.007 INFO 17812 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8888 (http) -``` - -## Spring Boot的Web开发 - -spring-boot-starter-web 为我们提供了嵌入的 tomcat 和 Spring MVC 的依赖。 - -### Thymeleaf 模板引擎 - -JSP 在内嵌的 Servlet 容器上运行会存在一些问题(内嵌 Tomcat、Jetty 不支持以 jar 形式运行 JSP,Undertow 不支持 JSP)。Spring Boot 提供了大量的模板引擎,包含 FreeMarker、Groovy、Thymeleaf、Velocity 和 Mustache,Spring Boot 中推荐使用 Thymeleaf 作为模板引擎,因为 Thymeleaf 提供了完美的 Spring MVC 的支持。 - -#### Thymeleaf 基础知识 - -Thymeleaf 是 Java 类库,它是一个 xml/xhtml/html5 的模板引擎,可以作为 MVC 的 Web 应用的 view 层。Thymeleaf 还提供了额外的模块与 Spring MVC 集成,使用 Thymeleaf 完全可以替代 JSP。 - -1. 引入 Thymeleaf - -基本的 Thymeleaf 模板页面,引入了 Bootstrap(作为样式控制)和 jQuery(DOM 操作)。 - -```html - - - - - - - Demo - - - - - - -``` - -通过 xmlns:th="http://www.thymeleaf.org" 命名空间,将静态页面转换为动态视图。需要进行动态处理的元素需使用 “th:” 为前缀。通过 “@{}” 引用 Web 静态资源,这在 JSP 下是极易出错的。 - -2. 访问 model 中的数据 - -通过 “${}” 访问 model 中的属性,这和 JSP 很相似。需要处理的动态内容需要加上 “th:” 前缀。 - -```html -
-
-

访问model

-
-
- -
-
-``` - -3. model 中的数据迭代 - -```html -
-
-

列表

-
-
-
    -
  • - - -
  • -
-
-
-``` - -4. 数据判断 - -通过${not #lists.isEmpty(people)}表达式判断 people 是否为空。Thymeleaf 还支持 >、<、==、!= 等作为比较条件,同时也支持将 SpringEL 表达式语言用于条件中。 - -```html -
-
-
-

列表

-
-
-
    -
  • - - -
  • -
-
-
-
-``` - -5. 在 JavaScript 中访问 model - -```html - -``` - -通过 th:inline="javascript"添加到 script 标签,这样 JavaScript 代码即可访问 model 中的属性; - -通过“[[${person}]]”格式可以获得实际的值。 - -如果需要在 html 代码里访问 model 中的属性,比如我们需要在列表后面单击每一行后面的按钮获得 model 中的值,可做如下处理: - -```html -
  • - - - -
  • -``` - -#### 与 Spring MVC 集成 - -在 Spring MVC 中,若我们需要集成一个模板引擎的话,需要定义 ViewResolver,而 ViewResolver 需要定义一个 View。在 Spring MVC 中集成 Thymeleaf 非常简单,Thymeleaf 为我们定义好了 org.thymeleaf.spring4.view .ThymeleafView 和 org.thymeleaf.spring4.view.ThymeleafViewResolver(默认使用 ThymeleafView 作为 View)。Thymeleaf 给我们提供了一个 SpringTemplateEngine 类,用来驱动 Spring MVC 下使用 Thymeleaf 模板引擎,另外提供了一个 TemplateResolver 用来设置通用的模板引擎(包含前缀、后缀等)。 - -引入依赖: - -```xml - - - org.thymeleaf - thymeleaf-spring4 - ${thymeleaf.version} - - - org.thymeleaf - thymeleaf - ${thymeleaf.version} - -``` - -xml 配置: - -```java - - - - - - - - - - - - - - - - - -``` - -或者使用 JavaConfig 配置: - -```java -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.thymeleaf.spring4.SpringTemplateEngine; -import org.thymeleaf.spring4.view.ThymeleafViewResolver; -import org.thymeleaf.templateresolver.ServletContextTemplateResolver; -import org.thymeleaf.templateresolver.TemplateResolver; - -@Configuration -public class ThymeleafConfig { - @Bean - public TemplateResolver templateResolver() { - TemplateResolver templateResolver = new ServletContextTemplateResolver(); - templateResolver.setPrefix("/WEB-INF/template"); - templateResolver.setSuffix(".html"); - templateResolver.setTemplateMode("HTML5"); - return templateResolver; - } - - @Bean - public SpringTemplateEngine templateEngine(TemplateResolver templateResolver) { - SpringTemplateEngine templateEngine = new SpringTemplateEngine(); - templateEngine.setTemplateResolver(templateResolver); - return templateEngine; - } - - @Bean - public ThymeleafViewResolver thymeleafViewResolver(SpringTemplateEngine templateEngine) { - ThymeleafViewResolver thymeleafViewResolver = new ThymeleafViewResolver(); - thymeleafViewResolver.setTemplateEngine(templateEngine); - //thymeleafViewResolver.setViewClass(ThymeleafView.class); - return thymeleafViewResolver; - } -} -``` - -#### Spring Boot 的 Thymeleaf 支持 - -Spring Boot 通过 org.springframework.boot.autoconfigure.thymeleaf 包对 Thymeleaf 进行了自动配置,通过ThymeleafAutoConfiguration 类对集成所需的 Bean 进行自动配置,包括 templateResolver、templateEngine 和 thymeleafViewResolvers 的配置。通过 ThymeleafProperties 来设置属性以及默认配置。 - -```java -@ConfigurationProperties( - prefix = "spring.thymeleaf" -) -public class ThymeleafProperties { - private static final Charset DEFAULT_ENCODING; - public static final String DEFAULT_PREFIX = "classpath:/templates/"; - public static final String DEFAULT_SUFFIX = ".html"; - private boolean checkTemplate = true; - private boolean checkTemplateLocation = true; - private String prefix = "classpath:/templates/"; - private String suffix = ".html"; - private String mode = "HTML"; - private Charset encoding; - private boolean cache; - private Integer templateResolverOrder; - private String[] viewNames; - private String[] excludedViewNames; - private boolean enableSpringElCompiler; - private boolean renderHiddenMarkersBeforeCheckboxes; - private boolean enabled; - private final ThymeleafProperties.Servlet servlet; - private final ThymeleafProperties.Reactive reactive; - ... -} -``` - -#### 实战 - -1. 引入依赖 - -```xml - - org.springframework.boot - spring-boot-starter-thymeleaf - -``` - -引入 Thymeleaf 之后,Controller才能根据逻辑视图名转发到相应的视图。 - -2. JavaBean - -```java -public class Person { - private String name; - private Integer age; - private String address; - - public Person() {} - - //setter和getter -} -``` - -3. 页面 - -```html - - - - - - - - index - - -
    -
    -

    welcome

    -
    -
    - -
    -
    - - -``` - -页面引入了Bootstrap和jQuery,这些静态文件放置在src/main/resources/static下面。 - -4. Controller - -```java -import com.tyson.springbootdemo.pojo.Person; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.RequestMapping; - -@Controller -@RequestMapping("/person") -public class PersonController { - @RequestMapping("/hi") - public String index(Model model) { - Person p = new Person(); - p.setName("tyson"); - model.addAttribute("person", p); - return "index"; - } - - @RequestMapping("/") - public String home() { - return "home"; - } -} -``` - -### Web 相关配置 - -#### Spring Boot 提供的自动配置 - -WebMvcAutoConfiguration 及 WebMvcProperties 定义了 Web 相关的自动配置。 - -1. 自动配置的ViewResolver - -```java -@Bean -@ConditionalOnMissingBean -public InternalResourceViewResolver defaultViewResolver() { - InternalResourceViewResolver resolver = new InternalResourceViewResolver(); - resolver.setPrefix(this.mvcProperties.getView().getPrefix()); - resolver.setSuffix(this.mvcProperties.getView().getSuffix()); - return resolver; -} - -@Bean -@ConditionalOnBean({View.class}) -@ConditionalOnMissingBean -public BeanNameViewResolver beanNameViewResolver() { - BeanNameViewResolver resolver = new BeanNameViewResolver(); - resolver.setOrder(2147483637); - return resolver; -} -``` - -2. 自动配置的静态资源 - -将类路径下的/static、/resources、/public 和 /META-INF/resources 文件夹下的静态文件直接映射为/**,可以通过 http://localhost:8080/\*\*访问。 - -把 webjar 的 /META-INF/resources/webjars/ 下的静态文件映射为/webjar/**,可以通过 http://localhost:8080/webjar/\*\*访问。 - -3. 自动配置的 Formatter 和 Converter - -```java -public void addFormatters(FormatterRegistry registry) { - Iterator var2 = this.getBeansOfType(Converter.class).iterator(); - - while(var2.hasNext()) { - Converter converter = (Converter)var2.next(); - registry.addConverter(converter); - } - - var2 = this.getBeansOfType(GenericConverter.class).iterator(); - - while(var2.hasNext()) { - GenericConverter converter = (GenericConverter)var2.next(); - registry.addConverter(converter); - } - - var2 = this.getBeansOfType(Formatter.class).iterator(); - - while(var2.hasNext()) { - Formatter formatter = (Formatter)var2.next(); - registry.addFormatter(formatter); - } - -} -``` - -只要我们定义了 Converter、GenericConverter 和 Formatter 接口的实现类的 Bean,这些 Bean 就会自动注册到 Spring MVC 中。 - -3. 静态首页的支持 - -把静态首页放到如下目录: - -- classpath:/META-INF/resources/index.html -- classpath:/resources/index.html -- classpath:/static/index.html -- classpath:/public/index.html - -当我们访问应用根目录 http://localhost:8080/ 时,会直接映射。 - -#### 实现自己的 MVC 配置 - -当 Spring Boot 提供的 Spring MVC 不符合要求时,可以通过一个配置类(注解有@Configuration 的类)加上@EnableWebMvc 注解来实现完全自己控制的 MVC 配置。 - -要想保留 Spring Boot 提供的 MVC 配置,同时增加额外的配置,可以通过定义一个配置类并继承 WebMvcConfigurerAdapter,无需使用@EnableWebMvc注解。 - -```java -import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -public class WebMvcConfig implements WebMvcConfigurer { - @Override - public void addViewControllers(ViewControllerRegistry registry) { - registry.addViewController("/xx").setViewName("xx"); - } -} -``` - -重写的 addViewControllers 方法并不会覆盖 WebMvcAutocConfiguration 中的 addViewControllers(此方法中,Spring Boot 将“/”映射到 index.html),即我们自己的配置和 Spring Boot 的自动配置同时生效。 - -#### 注册 Servlet、Filter、Listener - -当使用嵌入式的 Servlet 容器时,通过将 Servlet、Filter 和 Listener 声明为 Spring Bean 达到注册的效果;或者注册 ServletRegistrationBean、FilterRegistrationBean 和 ServletRegistrationBean 的 Bean。 - -直接注册 bean: - -```java -@Bean -public xxServlet xxServlet() { - return new XxServlet(); -} -``` - -通过 RegistrationBean 示例: - -```java -@Bean -public ServletRegistrationBean servletRegistrationBean() { - return new ServletRegistrationBean(new XxServlet(), "/xx/*"); -} - -@Bean -public FilterRegistrationBean filterRegistrationBean() { - FilterRegistrationBean registrationBean = new FilterRegistrationBean(); - registrationBean.setFilter(new YyFilter()); - registrationBean.setOrder(2); - return registrtionBean; -} - -@Bean -public ServletListenerRegistrationBean zzListenerServletRegistrationBean() { - return new ServletListenerRegistrationBean(new ZzListener()); -} -``` - -### Tomcat配置 - -#### 配置 Tomcat - -关于 Tomcat 的所有属性都在 org.springframework.boot.autoconfigure.web.ServerPr=8080operties 配置类中做了定义,只需在 application.properties 配置即可。通用的 Servlet 容器配置都以 "server" 作为前缀,而 Tomcat 特有的配置都以 "server.tomcat" 作为前缀。 - -```properties -#配置Servlet容器 -server.port=8080 -server.session-timeout=3600 #以秒为单位 -server.context-path=/ -#配置Tomcat -server.tomcat.uri-encoding=UTF-8 -server.tomcat.compression=off #是否开启压缩,默认是关闭 -``` - -#### 替换 Tomcat - -Spring Boot 默认使用 Tomcat 作为内嵌的 Servlet 容器,如果要使用 Jetty 或者 Undertow 为容器,只需修改 spring-boot-start-web 的依赖即可。 - -1. 替换为 Jetty - -在 pom.xml中,将 spring-boot-starter-web 的依赖由 spring-boot-starter-tomcat 替换为 spring-boot-starter-jetty: - -```xml - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-tomcat - - - - - - - org.springframework.boot - spring-boot-starter-jetty - -``` - -启动 Spring Boot,控制台输出效果如下: - -```java -INFO 21724 --- [ main] o.s.b.web.embedded.jetty.JettyWebServer : Jetty started on port(s) 8080 (http/1.1) with context path '/' -``` - -2. 替换为 Undertow - -将 spring-boot-starter-web 的依赖由 spring-boot-starter-tomcat 替换为 spring-boot-starter-undertow: - -```xml - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-tomcat - - - - - - org.springframework.boo - spring-boot-starter-undertow - -``` - -## Spring Boot 的数据访问 - -### Docker 常用命令及参数 - -#### Docker 镜像命令 - -(1)Docker 镜像检索 - -```powershell -docker search redis -``` - -(2)镜像下载 - -```powershell -docker pull redis -``` - -(3)镜像列表 - -```powershell -docker images -``` - -(4)删除镜像 - -删除指定镜像: - -```powershell -docker rmi image-id -``` - -删除所有镜像: - -```powershell -docker rmi ${docker images -q} -``` - -#### Docker 容器命令 - -(1)容器基本操作 - -运行容器,Docker 会为我们生成唯一的标识。 - -```powershell -docker run --name container-name -d image-name -``` - -(2)容器列表 - -查看运行中的容器列表: - -```powershell -docker ps -``` - -查看运行和停止状态的容器: - -```powershell -docker ps -a -``` - -(3)停止和启动容器 - -停止容器: - -```powershell -docker stop container-name/container-id -``` - -启动容器: - -```powershell -docker start container-name/container-id -``` - -端口映射:Docker 容器中运行的软件所使用的端口,在本机和本机的局域网是不能访问的,需要将 Docker 容器的端口映射到当前主机的端口上,这样我们在本机和本机的局域网才能访问该软件。 - -映射容器的6379端口到虚拟机的6378端口上: - -```powershell -docker run -d -p 6378:6379 --name port-redis resdis -``` - -删除单个容器: - -```powershell -docker rm container-id -``` - -删除所有容器: - -```powershell -docker rm ${docker ps -a -q} -``` - -查看容器日志: - -```powershell -docker logs container-name/container-id -``` - -登录容器: - -```powershell -docker exec -it container-id/container-name bash -``` - -登录后可以在容器中进行常规的 Linux 系统操作命令。 - -### Spring Boot 对 Spring Data JPA 的支持 - -JPA 是一个基于 O/R 映射的标准规范(不提供实现)。Spring Data JPA 是 Spring Data 的一个子项目,它通过提供基于 JPA 的Repository 极大地减少了 JPA 作为数据访问方案的代码量。 - -#### JDBC 的自动配置 - -spring-boot-starter-data-jpa 依赖于 spring-boot-starter-jdbc,Spring Boot 对 JDBC 做了一些自动配置,源码在 org.springframework.boot.autoconfigure.jdbc 下。 - -在 DataSourceAutoConfiguration 类中配置了对 DataSource 的自动配置,通过"spring.datasource"为前缀的属性自动配置 dataSource;Spring Boot 开启了注解事务的支持(@EnableTransactionManagement);还配置了一个jdbcTemplate。以下是 JdbcProperties 类的源码: - -```java -@ConfigurationProperties( - prefix = "spring.jdbc" -) -public class JdbcProperties { - private final JdbcProperties.Template template = new JdbcProperties.Template(); - - public JdbcProperties() { - } - - public JdbcProperties.Template getTemplate() { - return this.template; - } - - public static class Template { - private int fetchSize = -1; - private int maxRows = -1; - @DurationUnit(ChronoUnit.SECONDS) - private Duration queryTimeout; - ... - } -} -``` - - - -#### 对 JPA 的自动配置 - -Spring Boot 对 JPA 的自动配置放置在 org.springframework.boot.autoconfiguration.orm.jpa 下,从HibernateJpaAutoConfiguration 可以看出,Spring Boot 默认的 JPA 实现是Hibernate。 - -配置 JPA 可以在 application.properties 中使用 spring.jpa 为前缀的属性来配置。 - -以下是 JpaProperties 类的源码: - -```java -@ConfigurationProperties( - prefix = "spring.jpa" -) -public class JpaProperties { - private Map properties = new HashMap(); - private final List mappingResources = new ArrayList(); - private String databasePlatform; - private Database database; - private boolean generateDdl = false; - private boolean showSql = false; - private Boolean openInView; - ... -} -``` - -在 JpaBaseConfiguration 类中,Spring Boot 为我们创建了 transactionManager、jpaVendorAdapter、entityManagerFactory 等 bean。JpaBaseConfiguration 还有 getPackagesToScan 方法,可以自动扫描有@Entity 注解的实体类。 - -#### 对 Spring Data JPA 的自动配置 - -Spring Boot 对 Spring Data JPA 的自动配置放置在 org.springframework.boot.autoconfigure.data.jpa 中。JpaRepositoriesAutoConfiguration 是依赖于 HibernateJpaAutoConfiguration 配置的,且 Spring Boot 自动开启了对 Spring Data JPA 的支持,无需在配置类显式声明@EnableJpaRepositories。 - -在 Spring Boot 下使用 Spring Data JPA,首先在项目的 maven 依赖里添加 spring-boot-starter-data-jpa,然后只需定义DataSource、实体类和数据访问层,在需要使用数据访问的地方注入数据访问层的 Bean 即可。 - - - -## @Value原理 - -@Value的解析就是在bean初始化阶段。BeanPostProcessor定义了bean初始化前后用户可以对bean进行操作的接口方法,它的一个重要实现类AutowiredAnnotationBeanPostProcessor为bean中的@Autowired和@Value注解的注入功能提供支持。 - - - -## 启动过程 - -准备Environment——发布事件——创建上下文、bean——刷新上下文——结束。 - -构造SpringApplication的时候会进行初始化的工作,初始化的时候会做以下几件事: -判断运行环境类型,有三种运行环境:NONE 非 web 的运行环境、SERVLET 普通 web 的运行环境、REACTIVE 响应式 web 的运行环境 -加载 spring.factories 配置文件, 并设置 ApplicationContextInitializer -加载配置文件, 设置 ApplicationListener - - -SpringApplication构造完成之后调用run方法,启动SpringApplication,run方法执行的时候会做以下几件事: -构造一个StopWatch,观察SpringApplication的执行 -找出SpringApplicationRunListener,用于监听SpringApplication run方法的执行。监听的过程中会封装SpringApplicationEvent事件,然后使用ApplicationEventMulticaster广播出去,应用程序监听器ApplicationListener会监听到这些事件 -发布starting事件 -加载配置资源到environment,包括命令行参数、application.yml等 -发布environmentPrepared事件 -创建并初始化ApplicationContext,设置environment,加载配置 -refresh ApplicationContext -- 设置beanFactory -- 调用BeanFactoryPostProcessors -- 初始化消息源 -- 初始化事件广播器(initApplicationEventMulticaster) -- 调用onRefresh()方法,默认是空实现 -- 注册监听器 -- 实例化non-lazy-init单例 -- 完成refresh -- 发布ContextRefreshedEvent事件 - -发布started事件,启动结束 \ No newline at end of file diff --git "a/\346\241\206\346\236\266/SpringMVC.md" "b/\346\241\206\346\236\266/SpringMVC.md" deleted file mode 100644 index 79c40cc..0000000 --- "a/\346\241\206\346\236\266/SpringMVC.md" +++ /dev/null @@ -1,1602 +0,0 @@ - - - - -- [简介](#%E7%AE%80%E4%BB%8B) -- [Spring MVC处理流程](#spring-mvc%E5%A4%84%E7%90%86%E6%B5%81%E7%A8%8B) -- [Spring MVC和Struts的区别](#spring-mvc%E5%92%8Cstruts%E7%9A%84%E5%8C%BA%E5%88%AB) -- [Spring MVC环境搭建](#spring-mvc%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA) -- [处理器映射器和适配器](#%E5%A4%84%E7%90%86%E5%99%A8%E6%98%A0%E5%B0%84%E5%99%A8%E5%92%8C%E9%80%82%E9%85%8D%E5%99%A8) - - [非注解的处理器映射器和适配器](#%E9%9D%9E%E6%B3%A8%E8%A7%A3%E7%9A%84%E5%A4%84%E7%90%86%E5%99%A8%E6%98%A0%E5%B0%84%E5%99%A8%E5%92%8C%E9%80%82%E9%85%8D%E5%99%A8) - - [注解的处理器映射器和适配器](#%E6%B3%A8%E8%A7%A3%E7%9A%84%E5%A4%84%E7%90%86%E5%99%A8%E6%98%A0%E5%B0%84%E5%99%A8%E5%92%8C%E9%80%82%E9%85%8D%E5%99%A8) -- [前端控制器](#%E5%89%8D%E7%AB%AF%E6%8E%A7%E5%88%B6%E5%99%A8) - - [对静态资源的处理](#%E5%AF%B9%E9%9D%99%E6%80%81%E8%B5%84%E6%BA%90%E7%9A%84%E5%A4%84%E7%90%86) -- [视图解析器](#%E8%A7%86%E5%9B%BE%E8%A7%A3%E6%9E%90%E5%99%A8) - - [AbstractCachingViewResolver](#abstractcachingviewresolver) - - [UrlBasedViewResolver](#urlbasedviewresolver) - - [InternalResourceViewResolver](#internalresourceviewresolver) - - [XmlViewResolver](#xmlviewresolver) - - [BeanNameViewResolver](#beannameviewresolver) - - [ResourceBundleViewResolver](#resourcebundleviewresolver) - - [FreeMarkerViewResolver](#freemarkerviewresolver) -- [请求映射](#%E8%AF%B7%E6%B1%82%E6%98%A0%E5%B0%84) -- [参数绑定](#%E5%8F%82%E6%95%B0%E7%BB%91%E5%AE%9A) - - [简单类型参数绑定](#%E7%AE%80%E5%8D%95%E7%B1%BB%E5%9E%8B%E5%8F%82%E6%95%B0%E7%BB%91%E5%AE%9A) - - [包装类型参数绑定](#%E5%8C%85%E8%A3%85%E7%B1%BB%E5%9E%8B%E5%8F%82%E6%95%B0%E7%BB%91%E5%AE%9A) - - [集合类型参数绑定](#%E9%9B%86%E5%90%88%E7%B1%BB%E5%9E%8B%E5%8F%82%E6%95%B0%E7%BB%91%E5%AE%9A) -- [Converter和Formatter](#converter%E5%92%8Cformatter) - - [Converter](#converter) - - [Formatter](#formatter) -- [验证器](#%E9%AA%8C%E8%AF%81%E5%99%A8) - - [使用Validator接口进行验证](#%E4%BD%BF%E7%94%A8validator%E6%8E%A5%E5%8F%A3%E8%BF%9B%E8%A1%8C%E9%AA%8C%E8%AF%81) - - [使用JSR-303 Validation进行验证](#%E4%BD%BF%E7%94%A8jsr-303-validation%E8%BF%9B%E8%A1%8C%E9%AA%8C%E8%AF%81) - - [自定义限制类型的注解](#%E8%87%AA%E5%AE%9A%E4%B9%89%E9%99%90%E5%88%B6%E7%B1%BB%E5%9E%8B%E7%9A%84%E6%B3%A8%E8%A7%A3) - - [分组校验](#%E5%88%86%E7%BB%84%E6%A0%A1%E9%AA%8C) -- [RequestBody和RequestParam](#requestbody%E5%92%8Crequestparam) - - - -## 简介 - -Spring MVC是一种基于MVC架构模式的轻量级Web框架。 - -## Spring MVC处理流程 - -Spring MVC的处理过程: - -1. DispatcherServlet 接收用户的请求 -2. 找到用于处理request的 handler 和 Interceptors,构造成 HandlerExecutionChain 执行链 -3. 找到 handler 相对应的 HandlerAdapter -4. 执行所有注册拦截器的preHandler方法 -5. 调用 HandlerAdapter 的 handle() 方法处理请求,返回 ModelAndView -6. 倒序执行所有注册拦截器的postHandler方法 -7. 请求视图解析和视图渲染 - -![Spring MVC处理流程](https://img-blog.csdnimg.cn/20190125180502787.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1R5c29uMDMxNA==,size_16,color_FFFFFF,t_70) - -处理流程中各个组件的功能: - -- 前端控制器(DispatcherServlet):接收用户请求,给用户返回结果。 -- 处理器映射器(HandlerMapping):根据请求的url路径,通过注解或者xml配置,寻找匹配的Handler。 -- 处理器适配器(HandlerAdapter):Handler 的适配器,调用 handler 的方法处理请求。 -- 处理器(Handler):执行相关的请求处理逻辑,并返回相应的数据和视图信息,将其封装到ModelAndView对象中。 -- 视图解析器(ViewResolver):将逻辑视图名解析成真正的视图View。 -- 视图(View):接口类,实现类可支持不同的View类型(JSP、FreeMarker、Excel等)。 - - - -## Spring MVC和Struts的区别 - -1. Spring MVC是基于方法开发,Struts2是基于类开发的。 - - Spring MVC会将用户请求的URL路径信息与Controller的某个方法进行映射,所有请求参数会注入到对应方法的形参上,生成Handler对象,对象中只有一个方法; - - Struts每处理一次请求都会实例一个Action,Action类的所有方法使用的请求参数都是Action类中的成员变量,随着方法增多,整个Action也会变得混乱。 -2. Spring MVC支持单例开发模式,Struts只能使用多例 - - - Struts由于只能通过类的成员变量接收参数,故只能使用多例。 -3. Struts2 的核心是基于一个Filter即StrutsPreparedAndExcuteFilter,Spring MVC的核心是基于一个Servlet即DispatcherServlet(前端控制器)。 -4. Struts处理速度稍微比Spring MVC慢,Struts使用了Struts标签,加载数据较慢。 - - -## Spring MVC环境搭建 - -导入jar包: - -```xml - - - 4.0.0 - - com.tyson - springmvc-demo - 1.0-SNAPSHOT - - - UTF-8 - yyyyMMdd - 5.0.9.RELEASE - 1.2.3 - 1.7.12 - 2.0 - 2.9.1 - - - - - - org.slf4j - slf4j-api - ${slf4j.version} - - - org.slf4j - jcl-over-slf4j - ${slf4j.version} - - - ch.qos.logback - logback-classic - ${logback.version} - - - ch.qos.logback - logback-core - ${logback.version} - - - ch.qos.logback - logback-access - ${logback.version} - - - - org.codehaus.janino - janino - 2.6.1 - - - - org.projectlombok - lombok - 1.12.4 - - - - - org.springframework - spring-core - ${spring.version} - - - - org.springframework - spring-beans - ${spring.version} - - - - org.springframework - spring-context - ${spring.version} - - - - org.springframework - spring-tx - ${spring.version} - - - - org.springframework - spring-test - ${spring.version} - - - - - - org.springframework - spring-web - ${spring.version} - - - - org.springframework - spring-webmvc - ${spring.version} - - - - - - jstl - jstl - 1.2 - - - - javax.servlet - javax.servlet-api - 3.1.0 - - - - javax.servlet - jsp-api - ${jsp.version} - - - - - com.fasterxml.jackson.core - jackson-databind - ${json.version} - - - - - -``` - -新建logback.xml用来配置日志: - -```xml - - - - - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - - - - - - - - -``` - -web.xml文件中添加Spring MVC的前端控制器,用于拦截符合配置的url请求。 - -``` - - - - springmvc - - org.springframework.web.servlet.DispatcherServlet - - - - contextConfigLocation - classpath:config/springmvc.xml - - - - - - 1 - - - - springmvc - *.action - - - - - default - /resources/* - - - - - CharacherEncodingFilter - org.springframework.web.filter.CharacterEncodingFilter - - encoding - utf-8 - - - - CharacherEncodingFilter - /* - - -``` - -编写核心配置文件springmvc.xml。 - -``` - - - - - - - - - - - - - - -``` -Handler类 -``` -import org.springframework.web.servlet.ModelAndView; -import org.springframework.web.servlet.mvc.Controller; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -public class UserController implements Controller { - - public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception { - ModelAndView mv = new ModelAndView(); - mv.setViewName("/WEB-INF/hello.html"); - return mv; - } -} -``` - - - -## 处理器映射器和适配器 - -在Spring MVC核心jar包中有一个默认的配置文件**DispatcherServlet.properties**(org.springframework.wen.servlet包下),当核心配置文件springmvc.xml没有配置处理器映射器和适配器时,会使用默认配置。 - - - -### 非注解的处理器映射器和适配器 -常用的处理器映射器有BeanNameUrlHandlerMapping,SimpleUrlHandlerMapping,ControllerClassNameHandlerMapping。 -``` - - -``` -``` - - - - - userController - - - - -``` -常用的处理器适配器有SimpleControllerHandlerAdapter,HttpRequestHandlerAdapter,AnnotationMethodHandlerAdapter。 -``` - - -``` - -### 注解的处理器映射器和适配器 -方式一(常用的配置方式):annotation-driven标签会自动注册RequestMappingHandlerMapping与RequestMappingHandlerAdapter两个Bean,这是Spring MVC为@Controller分发请求所必需的,并且提供了数据绑定支持,@NumberFormat支持,@DateTimeFormat支持,@Valid支持读写XML的支持(JAXB)和读写JSON的支持(默认Jackson)等功能。 -``` - -``` -方式二:必须保证基于注解的处理器映射器和适配器成对配置,否则没有效果。 -``` - - - - -``` - -使用了注解的处理器映射器和适配器,则Handler无需实现任何接口,也不用在xml中配置任何信息,只需在Handler处理器类上添加相应的注解即可(@Controller,@RequestMapping)。 - -为了让注解的处理器映射器和适配器找到注解的Handler,需要在springmvc.xml中声明相关的bean信息。有两种配置方式: -``` - -``` -扫描配置,对包下所有的类进行扫描,找出所有使用了@Controller注解的Handler控制器类。 -``` - - -``` -Handler类 -``` -import com.tyson.po.User; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.ResponseBody; - -@Controller -@RequestMapping("/user") -public class UserController { - @RequestMapping("/findUser") - @ResponseBody - public User findUser() { - return new User("666", "tyson"); - } -} -``` - - -## 前端控制器 -前端控制器DispatcherServlet类最核心的方法是doDispatch()。 - -在web.xml中配置了名为"springmvc"的Servlet,拦截以".action"结尾的url请求。当Web应用接收到这种请求时,会调用前端控制器。 -``` - protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { - HttpServletRequest processedRequest = request; - HandlerExecutionChain mappedHandler = null; - boolean multipartRequestParsed = false; - WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); - - try { - try { - ModelAndView mv = null; - Exception dispatchException = null; - - try { - //检测request是否包含多媒体类型(File文件),并将request转化为processedRequest - processedRequest = this.checkMultipart(request); - //判断processedRequest是否为原始的request - multipartRequestParsed = processedRequest != request; - mappedHandler = this.getHandler(processedRequest); - if (mappedHandler == null || mappedHandler.getHandler() == null) { - this.noHandlerFound(processedRequest, response); - return; - } - - HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler()); - String method = request.getMethod(); - boolean isGet = "GET".equals(method); - if (isGet || "HEAD".equals(method)) { - long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); - if (this.logger.isDebugEnabled()) { - this.logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified); - } - - if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) { - return; - } - } - - if (!mappedHandler.applyPreHandle(processedRequest, response)) { - return; - } - - mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); - if (asyncManager.isConcurrentHandlingStarted()) { - return; - } - - this.applyDefaultViewName(processedRequest, mv); - mappedHandler.applyPostHandle(processedRequest, response, mv); - } catch (Exception var19) { - dispatchException = var19; - } - //处理ModelAndView,包含render()方法 - this.processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); - } catch (Exception var20) { - this.triggerAfterCompletion(processedRequest, response, mappedHandler, var20); - } catch (Error var21) { - this.triggerAfterCompletionWithError(processedRequest, response, mappedHandler, var21); - } - - } finally { - if (asyncManager.isConcurrentHandlingStarted()) { - if (mappedHandler != null) { - mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); - } - } else if (multipartRequestParsed) { - this.cleanupMultipart(processedRequest); - } - - } - } -``` - -### 对静态资源的处理 - -web.xml中DispatcherServlet的配置如下: - -```xml - - springmvc - - org.springframework.web.servlet.DispatcherServlet - - - - contextConfigLocation - classpath:config/springmvc.xml - - - - - - 1 - - - - springmvc - / - -``` - -经测试,`/`会拦截\*.html请求,不会拦截\*.jsp请求。"/"优先级最低,故\*.jsp请求会先被默认的jsp Servlet处理,不会被dispatcherServlet拦截。而\*.html没有默认的Servlet可以处理,会被dispatcherServlet拦截。 - -而`/*`则会拦截所有请求,包括\*.html和\*.jsp。 - -`*.action`则只会拦截后缀为action的请求,不会拦截静态资源的请求。 - -"/"和"/\*"的区别在于"/\*"的优先级高于"/路径"和"\*.后缀"的路径,而"/"在所有的匹配路径中,优先级最低,即当别的路径都无法匹配时,"/"所匹配的servlet才会进行相应的请求资源处理。 - -```xml - - default - org.apache.catalina.servlets.DefaultServlet - - debug - 0 - - - listings - false - - 1 - -``` - - - -```xml - - jsp - org.apache.jasper.servlet.JspServlet - - fork - false - - - xpoweredBy - false - - 3 - -``` - - - -```xml - - - default - / - - - - - jsp - *.jsp - *.jspx - -``` - - - - - -静态资源的处理: - -方法一:激活Tomcat的default Servlet来处理静态资源 - -```xml - - - default - *.jsp - *.html - *.js - - - - springmvc - / - -``` - -default Servlet无法解析jsp页面,直接输出html源码。 - -![default servlet处理静态资源](https://img-blog.csdnimg.cn/20190127162223870.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1R5c29uMDMxNA==,size_16,color_FFFFFF,t_70) - -方式二:使用`` - -请求的url若是静态资源请求,则转由org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler 处理并返回,否则才由DispatcherServlet处理。DefaultServletHttpRequestHandler使用的是各个Servlet容器自己默认的Servlet(如jsp servlet)。 - - - -方式三:Spring3.0.4以后版本`` - -```xml - - - -``` - -`` 将静态资源的处理经由 Spring MVC 框架交回 Web 应用服务器处理。而 ` `更进一步,由 Spring MVC 框架自行处理静态资源。 - -` `允许静态资源放在任何地方,如 WEB-INF 目录下、类路径,甚至 JAR 包中。可通过 cache-period 设置客户端数据缓存时间。 - -使用` `元素,把 mapping 的 URI 注册到 SimpleUrlHandlerMapping的urlMap 中, -key 为 mapping 的 URI pattern值,而 value为 ResourceHttpRequestHandler, -这样就巧妙的把对静态资源的访问由 HandlerMapping 转到 ResourceHttpRequestHandler 处理并返回,所以就支持classpath目录和jar包内静态资源的访问。 - - - - -## 视图解析器 -视图解析器ViewResolver的作用是把逻辑视图名称解析成具体的View对象,让View对象去解析视图,并将带有数据的视图反馈给客户端。 - -常用的视图解析器类有AbstractCachingViewResolver,UrlBasedViewResolver,InternalResourceViewResolver,XmlViewResolver,BeanNameViewResolver,ResorceBundleViewResolver,FreeMarkerViewResolver和VelocityViewResolver。 - - -### AbstractCachingViewResolver -抽象类,实现了该抽象类的视图解析器会将其曾经解析过的视图进行缓存。 - - -### UrlBasedViewResolver -继承了AbstractCachingViewResolver,通过拼接资源的uri路径来展示视图。 -``` - - - - - - - -``` -UrlBasedViewResolver支持返回的视图名称中含有"redirect:"和"forward:"前缀,支持视图的重定向和内部跳转设置。 - - -### InternalResourceViewResolver -内部资源视图解析器,最常用的视图解析器类型。它是UrlBasedViewResolver的子类。 - -特点:它会把返回的视图名称自动解析为InternalResourceView类型的对象,而InternalResourceView会把Controller处理器方法返回的模型属性都存放到对应的request属性中,然后通过RequestDispatcher在服务端把请求以forward的方式跳转到目标url。 -``` - - - - - -``` -当Controller处理器方法返回名为"login"的视图时,InternalResourceViewResolver会将"login"解析成一个InternalResourceView对象,然后将返回的模型数据存放到对应的HttpServletRequest属性中,最后利用RequestDispatcher把请求forward到"/WEB-INF/jsp/login.jsp"上。 - - -### XmlViewResolver -继承了AbstractCachingViewResolver,使用XmlViewResolver需要添加一个xml配置文件,用来定义视图的bean对象。当获得Controller方法返回的视图名称后,XmlViewResolver会在指定的配置文件中寻找对应名称的bean配置,解析并处理该视图。 -``` - - - - - - - -``` -/WEB-INF/config/view.xml,其遵循的DTD规则和Spring的bean工厂配置文件相同。 -``` - - - - - - -``` - - -### BeanNameViewResolver -通过把返回的逻辑视图名称去匹配定义好的视图bean对象。BeanNameViewResolver要求视图bean对象都定义在Spring的application context中。 -``` - - - - - - -``` - -MyView 定义: - -```java -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.util.Map; - -public class MyView implements View { - - public String getContentType() { - return "text/html"; - } - - public void render(Map map, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception { - httpServletResponse.getWriter().print("my view"); - } -} -``` - -MyViewController - -```java -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestMapping; - -@Controller -public class MyViewController { - @RequestMapping("/myView") - public String myView() { - return "myView"; - } -} -``` - -Spring MVC 根据返回的逻辑视图名去寻找视图 bean 对象。 - - -### ResourceBundleViewResolver -继承了AbstractCachingViewResolver,需要一个properties文件定义逻辑视图名和View对象的对应关系,配置文件需放在classpath根目录下。 -``` - - - - viewResource - - - -``` -classpath:viewResource.properties -``` -login.(class)=org.springframework.web.servlet.view.InternalResourceView -login.url=/hello.html -``` - - -### FreeMarkerViewResolver -FreeMarkViewResolver会将Controller返回的逻辑视图信息解析成FreeMarkerView类型。它是UrlBasedViewResolver的子类。 - -FreeMarkerViewResolver解析逻辑视图后,返回FreeMarker模板,该模板负责将模型数据合并到模板中,从而生成标准输出(html、xml等)。 - - -springmvc.xml配置 -``` - - - - - - - - - -``` -模板文件:web\WEB-INF\freemarker\template\fm_freemarker.ftl -``` - - - FreeMarker - - -

    hello, FreeMarker

    - - - -``` -建议在ViewResolver中,将InternalResourceViewResolver解析器优先级设置为最低,因为该解析器能解析所有类型的视图,并返回一个不为空的View对象。 - - -## 请求映射 -在使用annotation-driven标签时,处理器Handler的类型要符合annotation-driven标签指定的处理器映射器和适配器的类型。annotation-driven标签指定的默认处理器映射器和适配器在Spring3.1之前为DefaultAnnotationHandlerMapping和AnnotationMethodHandlerAdapter,在Spring3.1之后为RequestMappingHandlerMapping和RequestMappingHandlerAdapter。 - -每个处理器适配器都实现了HandlerAdapter接口。 -``` -public interface HandlerAdapter { - //检测Handler是否是支持的类型 - boolean supports(Object var1); - - ModelAndView handle(HttpServletRequest var1, HttpServletResponse var2, Object var3) throws Exception; - - long getLastModified(HttpServletRequest var1, Object var2); -} -``` -RequestMappingHandlerAdapter本身没有重写supports方法,它的supports方法定义在父类AbstractHandlerMethodAdapter中。 -``` - public final boolean supports(Object handler) { - //支持HandlerMethod类型的Handler - return handler instanceof HandlerMethod && this.supportsInternal((HandlerMethod)handler); - } -``` -RequestMappingHandlerAdapter支持HandlerMethod类型的Handler,HandlerMethod可以访问方法参数、方法返回值和方法的注解,所以它支持带有注解信息的Handler类。 - -@RequestMapping作用是为控制器指定可以处理那些url请求,该注解可以放在类上或者方法上。method用于指定处理哪些HTTP方法。 -``` -@RequestMapping(value = "/findUser", method={RequestMethod.GET, RequestMethod.POST}) -``` -@RequestMapping的param属性可以指定某一种参数名类型,当请求数据中包含该名称的请求参数时,才能进行相应,否则拒绝此次请求。 -``` -@RequestMapping(value = "/findUser", param = "username") -``` -@RequestMapping的headers属性可以指定某一种请求头类型。 -``` -@RequestMapping(value = "/findUser", headers = "Content-Type:text/html;charset=UTF-8") -``` -consumes属性表示处理请求的提交内容类型(Content-Type),例如"application/json,text/html"。而produces表示返回的内容类型,仅当request请求头中的Accept包含该指定类型时才会返回。 -``` -//仅处理reqeust的Content-Type为"application/json"类型的请求 -@RequestMapping(value = "/findUser", consumes = "application/json") - -@RequestMapping(value = "/findUser", produces = "application/json") -``` - - -## 参数绑定 -当用户发送请求时,前端控制器会请求处理器映射器返回一个处理器链,然后请求处理器适配器执行相应的Handler。此时,HandlerAdapter会调用Spring MVC提供的参数绑定组件将请求的key/value数据绑定到Controller处理器方法对应的形参上。 - - -### 简单类型参数绑定 -通过RequestParam将某个请求参数绑定到方法的形参上。value属性不指定时,则请求参数名称要与形参名称相同。required参数表示是否必须传入。defaultValue参数可以指定参数的默认值。 -``` - @RequestMapping("/findUser") - @ResponseBody - public User findUser(@RequestParam(value = "user_id", required = true, defaultValue = "1") long id) { - LOGGER.info("user_id: " + id); - return new User(id , "tyson"); - } -``` - -路径变量。 - -``` - @RequestMapping(value = "/insertUser/{id}") - public String insertUser(@PathVariable long id) { - return "success"; - } -``` - - - -### 包装类型参数绑定 - -``` -
    - id: - name: - -
    -``` -name属性名称与User类属性对应,Spring MVC的HandlerAdapter会解析请求参数生成具体的实体类,将相关的属性值通过set方法绑定到实体类中。 -``` - @RequestMapping("/findUserByCondition") - @ResponseBody - public User findUserByCondition(User user) { - return user; - } -``` - - -### 集合类型参数绑定 -``` - @RequestMapping("/findUsers") - @ResponseBody - public List findUsers(UserList userList) { - List users = userList.getUsers(); - for(User user : users) { - LOGGER.info("user_id: " + user.getId() + " " + "user_name: " + user.getName()); - } - return users; - } -``` - -``` -
    - - - - - -
    -``` -包装类中定义的List属性的名称要与前端页面的集合名一致。 -``` -public class UserList { - private List users; - - public List getUsers() { - return users; - } - - public void setUsers(List users) { - this.users = users; - } -} -``` - - - - -## Converter和Formatter - -### Converter - -将字符串转化成日期格式,可通过编写Converter接口的实现类来实现。 - -```java -import org.springframework.core.convert.converter.Converter; - -import java.text.SimpleDateFormat; -import java.util.Date; - -public class StringToDateConverter implements Converter { - private String dataPattern; - //利用传给构造器的日期样式,将String转化成Date - public StringToDateConverter(String dataPattern) { - this.dataPattern = dataPattern; - } - - public Date convert(String s) { - try { - SimpleDateFormat dateFormat = new SimpleDateFormat(dataPattern); - dateFormat.setLenient(false); - return dateFormat.parse(s); - } catch(Exception e) { - throw new IllegalArgumentException("invalid date format"); - } - } -} -``` - -在Spring MVC配置文件编写一个ConversionService bean。这个bean需包含一个converters属性。 - -```xml - - - - - - - - - -``` - -然后给annotation-driven标签的converter-service属性赋bean名称。 - -```xml - -``` - -### Formatter - -Formatter和Converter一样,也是将一种类型转化为另一种类型。但Formatter的源类型必须是String,而Converter适用于各种类型的源类型。Formatter更适合于web层。 - -```java -import org.springframework.format.Formatter; - -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; - -public class DateFormatter implements Formatter { - private static final Logger LOGGER = LoggerFactory.getLogger(Formatter.class); - private String datePattern; - private SimpleDateFormat dateFormat; - - public DateFormatter(String datePattern) { - this.datePattern = datePattern; - this.dateFormat = new SimpleDateFormat(datePattern); - dateFormat.setLenient(false); - } - - public Date parse(String s, Locale locale) throws ParseException { - try { - System.out.println(s); - return dateFormat.parse(s); - } catch(IllegalArgumentException ex) { - throw new IllegalArgumentException("Illegal date format. Please use \"" + datePattern + "\""); - } - } - - public String print(Date date, Locale locale) { - return dateFormat.format(date); - } -} -``` - -spring配置文件。 - -```xml - - - - - - - - - - - -``` - -## 验证器 - - -本节内容参考:[SpringMVC介绍之Validation](https://elim.iteye.com/blog/1812584) - -### 使用Validator接口进行验证 - -需要进行验证的实体类 - -```java -public class User { - private Long id; - - private String name; - - public User(Long id, String name) { - this.id = id; - this.name = name; - } - - public long getId() { - return id; - } - - public void setId(long id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } -} -``` - - - -Spring MVC提供了Validator接口,我们可以通过实现该接口来定义自己对实体对象的验证。 - -```java -import com.tyson.po.User; -import org.springframework.validation.Errors; -import org.springframework.validation.ValidationUtils; -import org.springframework.validation.Validator; - -public class UserValidator implements Validator { - - /** - * 判断当前Validator实现类是否支持校验当前需要校验的实体类 - * UserValidator只支持对User对象的校验 - */ - public boolean supports(Class aClass) { - return User.class.equals(aClass); - } - - /** - * @param errors 存放错误信息 - */ - public void validate(Object o, Errors errors) { - ValidationUtils.rejectIfEmpty(errors, "id", null, "id is empty"); - User user = (User)o; - if(user.getName().length() <= 4) { - errors.rejectValue("name", null, "name's length must be longer than 4"); - } - } -} -``` - -使用UserValidator校验User对象,使用DataBinder设定当前Controller由哪个Validator校验。 - -```java -import com.tyson.po.User; -import com.tyson.validator.UserValidator; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Controller; -import org.springframework.validation.BindingResult; -import org.springframework.validation.DataBinder; -import org.springframework.validation.ObjectError; -import org.springframework.web.bind.annotation.InitBinder; -import org.springframework.web.bind.annotation.RequestMapping; - -import javax.validation.Valid; -import java.util.List; - -@Slf4j -@Controller -public class UserController { - @InitBinder - public void initBinder(DataBinder binder) { - binder.setValidator(new UserValidator()); - } - - /** - *用@Valid标识需要校验的参数user,否则Spring不会对它进行校验 - * BindingResult参数告诉Spring数据校验单的错误由我们自己处理,否则Spring会直接抛出异常 - * BindingResult参数必须紧挨着@Valid参数,有多少个@Valid参数就有多少个BindingResult参数 - */ - @RequestMapping("/login") - public String login(@Valid User user, BindingResult bindingResult) { - if(bindingResult.hasErrors()) { - List errors = bindingResult.getAllErrors(); - for(ObjectError error : errors) { - log.info(error.toString()); - } - return "error"; - } - - return "success"; - } -} -``` - -在Controller类中通过@InitBinder标记的方法只有在请求当前Controller的时候才会被执行,所以其中定义的Validator也只能在当前Controller中使用,如果我们希望一个Validator对所有的Controller都起作用的话,我们可以通过WebBindingInitializer的initBinder方法来设定了。另外,在SpringMVC的配置文件中通过mvc:annotation-driven的validator属性也可以指定全局的Validator。 - -```xml - - - - - - - -``` - -非注解方式编写的适配器。 - -```xml - - - - - - - - - - -``` - - - -### 使用JSR-303 Validation进行验证 - -JSR-303是一个数据验证的规范,Spring没有对这一规范进行实现,当我们在Spring MVC使用JSR-303的时候需要提供一个对JSR-303规范的实现,Hibernate Validator实现了这一规范。 - -JSR303的校验是基于注解的,它的内部定义了一系列限制注解,我们只需要把这些注解标注在需要进行校验的实体类的属性或者对应的getter方法上面。 - -首先引入依赖。 - -```xml - - - org.hibernate - hibernate-validator - 5.0.2.Final - - - javax.validation - validation-api - 1.1.0.Final - -``` - -在SpringMVC的配置文件中引入MVC Namespace,并加上``,此时便可以使用JSR-303来进行实体对象的验证。 - -```xml - - - - - -``` - -实体类,其中@NotBlank是Hibernate Validator的扩展。 - -```java -import org.hibernate.validator.constraints.NotBlank; - -import javax.validation.constraints.Min; -import javax.validation.constraints.NotNull; - -public class User { - private String name; - private String password; - private int age; - - @NotBlank(message = "用户名不能为空") - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - @NotNull(message = "密码不能为null") - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } - - @Min(value = 10, message = "年龄最小为10") - public int getAge() { - return age; - } - - public void setAge(int age) { - this.age = age; - } - - @Override - public String toString() { - return name + ":" + age; - } -} -``` - -Controller类 - -```java -import com.tyson.po.User; -import org.springframework.stereotype.Controller; -import org.springframework.validation.BindingResult; -import org.springframework.web.bind.annotation.RequestMapping; - -import javax.validation.Valid; - -@Controller -public class UserController { - @RequestMapping("/login") - public String login(@Valid User user, BindingResult bindingResult) { - if(bindingResult.hasErrors()) { - return "error"; - } - return "success"; - } -} -``` - -**JSR-303原生支持的限制有如下几种:** - -| **限制** | 说明 | -| ----------------------------- | ------------------------------------------------------------ | -| **@Null** | 限制只能为null | -| **@NotNull** | 限制必须不为null | -| **@AssertFalse** | 限制必须为false | -| **@AssertTrue** | 限制必须为true | -| **@DecimalMax(value)** | 限制必须为一个不大于指定值的数字 | -| **@DecimalMin(value)** | 限制必须为一个不小于指定值的数字 | -| **@Digits(integer,fraction)** | 限制必须为一个小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction | -| **@Future** | 限制必须是一个将来的日期 | -| **@Max(value)** | 限制必须为一个不大于指定值的数字 | -| **@Min(value)** | 限制必须为一个不小于指定值的数字 | -| **@Past** | 限制必须是一个过去的日期 | -| **@Pattern(value)** | 限制必须符合指定的正则表达式 | -| **@Size(max,min)** | 限制字符长度必须在min到max之间 | - -#### 自定义限制类型的注解 - -除了JSR-303原生支持的限制类型之外我们还可以定义自己的限制类型。定义自己的限制类型首先我们得定义一个该种限制类型的注解,而且该注解需要使用@Constraint标注。下面定义一个表示金额的限制类型。 - -```java -package com.tyson.validator; - -import javax.validation.Constraint; -import javax.validation.Payload; -import java.lang.annotation.*; - -@Documented -@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER}) -@Retention(RetentionPolicy.RUNTIME) -@Constraint(validatedBy = MoneyValidator.class) -public @interface Money { - String message() default "不是金额形式"; - - Class[] groups() default {}; - - Class[] payload() default {}; -} -``` - -@Constraint注解的validatedBy属性用于指定我们定义的当前限制类型需要被哪个ConstraintValidator进行校验。在定义自己的限制类型的注解时有三个属性是必须定义的,message、groups和payload属性。 - -接下来定义限制类型校验类MoneyValidator,限制类型校验类必须实现接口javax.validation.ConstraintValidator,并实现它的initialize和isValid方法。 - -```java -import javax.validation.ConstraintValidator; -import javax.validation.ConstraintValidatorContext; -import java.util.regex.Pattern; - -public class MoneyValidator implements ConstraintValidator { - private String moneyReg = "^\\d+(\\.\\d{1,2})?$"; //表示金额的正则表达式 - private Pattern moneyPattern = Pattern.compile(moneyReg); - - /** - * 通过initialize可以获取限制类型 - */ - public void initialize(Money money) { - } - - public boolean isValid(Double value, ConstraintValidatorContext constraintValidatorContext) { - if(value == null) { - return false; - } - return moneyPattern.matcher(value.toString()).matches(); - } -} -``` - -同样的方法定义自己的@Min限制类型和对应的MinValidator校验器。 - -```java -import javax.validation.Constraint; -import javax.validation.Payload; -import java.lang.annotation.*; - -@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Constraint( - validatedBy = {MinValidator.class} -) -public @interface Min { - int value() default 0; - - String message(); - - Class[] groups() default {}; - - Class[] payload() default {}; -} - -``` - -isValid方法的第一个参数正是对应的当前需要校验的数据的值,而它的类型也**正是对应的我们需要校验的数据的数据类型。**这两者的数据类型必须保持一致,否则Spring会提示找不到对应数据类型的ConstraintValidator。 - -```java -import javax.validation.ConstraintValidator; -import javax.validation.ConstraintValidatorContext; - -public class MinValidator implements ConstraintValidator { - private int minValue; - - public void initialize(Min min) { - minValue = min.value(); - } - - public boolean isValid(Integer val, ConstraintValidatorContext constraintValidatorContext) { - return val >= minValue; - } -} -``` - -下面是使用了@Min和@Money限制的一个实体类 - -```java -import com.tyson.validator.Min; -import com.tyson.validator.Money; - -public class Worker { - private int age; - private Double salary; - - @Min(value = 10, message = "最小年龄是10") - public int getAge() { - return age; - } - - public void setAge(int age) { - this.age = age; - } - - @Money(message = "标准的金额格式是xxx.xx") - public Double getSalary() { - return salary; - } - - public void setSalary(Double salary) { - this.salary = salary; - } -} -``` - -WorkerController类 - -```java -import javax.validation.Valid; -import java.util.List; - -@Slf4j -@Controller -public class WorkerController { - @RequestMapping("/addWorker") - public String addWorker(@Valid Worker worker, BindingResult bindingResult) { - if(bindingResult.hasErrors()) { - List errors = bindingResult.getAllErrors(); - for(ObjectError error : errors) { - log.info(error.toString()); - } - return "error"; - } - return "success"; - } -} -``` - -另外Spring对自定义JSR-303限制类型支持的新特性,那就是Spring支持往ConstraintValidator里面注入bean对象。 - -```java -public class MoneyValidator implements ConstraintValidator { - - private String moneyReg = "^\\d+(\\.\\d{1,2})?$";//表示金额的正则表达式 - private Pattern moneyPattern = Pattern.compile(moneyReg); - private UserController controller; - - public void initialize(Money money) {} - - public boolean isValid(Double value, ConstraintValidatorContext arg1) { - if (value == null) - return true; - return moneyPattern.matcher(value.toString()).matches(); - } - - public UserController getController() { - return controller; - } - - @Resource - public void setController(UserController controller) { - this.controller = controller; - } - -} -``` - -#### 分组校验 - -POJO有多个属性,分组校验可以使得Controller的方法只校验POJO的某个属性,而不是校验所有的属性。 - -```java -import com.tyson.validator.Min; -import com.tyson.validator.Money; -import com.tyson.validator.ValidationGroup1; -import com.tyson.validator.ValidationGroup2; - -public class Worker { - private int age; - private Double salary; - - @Min(value = 10, message = "最小年龄是10", groups = {ValidationGroup1.class}) - public int getAge() { - return age; - } - - public void setAge(int age) { - this.age = age; - } - - @Money(message = "标准的金额格式是xxx.xx", groups = {ValidationGroup2.class}) - public Double getSalary() { - return salary; - } - - public void setSalary(Double salary) { - this.salary = salary; - } -} -``` - -分组接口ValidationGroup1、ValidationGroup2,不需要写实现。 - -```java -public interface ValidationGroup1 {} -``` - -WorkerController类,@Validated(value = {ValidationGroup1.class})使得addWorker方法只校验ValidationGroup1这个分组的校验注解,即只校验年龄,不校验工资格式。 - -```java -import com.tyson.po.Worker; -import com.tyson.validator.ValidationGroup1; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Controller; -import org.springframework.validation.BindingResult; -import org.springframework.validation.ObjectError; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.RequestMapping; -import java.util.List; - -@Slf4j -@Controller -public class WorkerController { - @RequestMapping("/addWorker") - public String addWorker(@Validated(value = {ValidationGroup1.class}) Worker worker, BindingResult bindingResult) { - if(bindingResult.hasErrors()) { - List errors = bindingResult.getAllErrors(); - for(ObjectError error : errors) { - log.info(error.toString()); - } - return "error"; - } - return "success"; - } -} -``` - - - -补充:@Valid和@Validated的区别:@Valid可以用于对象属性上,可嵌套验证,@Validated不可以嵌套验证;@Validated提供分组功能,而@Valid不支持分组功能。 - -```java -public class Item { - - @NotNull(message = "id不能为空") - @Min(value = 1, message = "id必须为正整数") - private Long id; - - @Valid // 嵌套验证必须用@Valid - @NotNull(message = "props不能为空") - @Size(min = 1, message = "props至少要有一个自定义属性") - private List props; -} -``` - -```java -public class Prop { - - @NotNull(message = "pid不能为空") - @Min(value = 1, message = "pid必须为正整数") - private Long pid; - - @NotNull(message = "vid不能为空") - @Min(value = 1, message = "vid必须为正整数") - private Long vid; - - @NotBlank(message = "pidName不能为空") - private String pidName; - - @NotBlank(message = "vidName不能为空") - private String vidName; -} -``` - - - -## RequestBody和RequestParam - -@RequestBody一般处理的是在ajax请求中声明contentType: "application/json; charset=utf-8"时候。也就是json数据或者xml数据。 - -@RequestParam一般就是在ajax里面没有声明contentType的时候,为默认的`x-www-form-urlencoded`格式时。 - - - diff --git "a/\346\241\206\346\236\266/SpringMVC\351\235\242\350\257\225\351\242\230.md" "b/\346\241\206\346\236\266/SpringMVC\351\235\242\350\257\225\351\242\230.md" deleted file mode 100644 index 0892077..0000000 --- "a/\346\241\206\346\236\266/SpringMVC\351\235\242\350\257\225\351\242\230.md" +++ /dev/null @@ -1,89 +0,0 @@ -## 说说你对 SpringMVC 的理解 - -SpringMVC是一种基于 Java 的实现MVC设计模型的请求驱动类型的轻量级Web框架,属于Spring框架的一个模块。 - -它通过一套注解,让一个简单的Java类成为处理请求的控制器,而无须实现任何接口。同时它还支持RESTful编程风格的请求。 - -## 什么是MVC模式? - -MVC的全名是`Model View Controller`,是模型(model)-视图(view)-控制器(controller)的缩写,是一种软件设计典范。它是用一种业务逻辑、数据与界面显示分离的方法来组织代码,将众多的业务逻辑聚集到一个部件里面,在需要改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑,达到减少编码的时间。 - -View,视图是指用户看到并与之交互的界面。比如由html元素组成的网页界面,或者软件的客户端界面。MVC的好处之一在于它能为应用程序处理很多不同的视图。在视图中其实没有真正的处理发生,它只是作为一种输出数据并允许用户操纵的方式。 - -model,模型是指模型表示业务规则。在MVC的三个部件中,模型拥有最多的处理任务。被模型返回的数据是中立的,模型与数据格式无关,这样一个模型能为多个视图提供数据,由于应用于模型的代码只需写一次就可以被多个视图重用,所以减少了代码的重复性。 - -controller,控制器是指控制器接受用户的输入并调用模型和视图去完成用户的需求,控制器本身不输出任何东西和做任何处理。它只是接收请求并决定调用哪个模型构件去处理请求,然后再确定用哪个视图来显示返回的数据。 - -## SpringMVC 有哪些优点? - -1. 与 Spring 集成使用非常方便,生态好。 -2. 配置简单,快速上手。 -3. 支持 RESTful 风格。 -4. 支持各种视图技术,支持各种请求资源映射策略。 - -## Spring MVC和Struts的区别 - -1. Spring MVC是基于方法开发,Struts2是基于类开发的。 - - Spring MVC会将用户请求的URL路径信息与Controller的某个方法进行映射,所有请求参数会注入到对应方法的形参上,生成Handler对象,对象中只有一个方法; - - Struts每处理一次请求都会实例一个Action,Action类的所有方法使用的请求参数都是Action类中的成员变量,随着方法增多,整个Action也会变得混乱。 -2. Spring MVC支持单例开发模式,Struts只能使用多例 - - - Struts由于只能通过类的成员变量接收参数,故只能使用多例。 -3. Struts2 的核心是基于一个Filter即StrutsPreparedAndExcuteFilter,Spring MVC的核心是基于一个Servlet即DispatcherServlet(前端控制器)。 -4. Struts处理速度稍微比Spring MVC慢,Struts使用了Struts标签,加载数据较慢。 - -## Spring MVC的工作原理 - -Spring MVC的工作原理如下: - -1. DispatcherServlet 接收用户的请求 -2. 找到用于处理request的 handler 和 Interceptors,构造成 HandlerExecutionChain 执行链 -3. 找到 handler 相对应的 HandlerAdapter -4. 执行所有注册拦截器的preHandler方法 -5. 调用 HandlerAdapter 的 handle() 方法处理请求,返回 ModelAndView -6. 倒序执行所有注册拦截器的postHandler方法 -7. 请求视图解析和视图渲染 - -![Spring MVC处理流程](https://img-blog.csdnimg.cn/20190125180502787.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1R5c29uMDMxNA==,size_16,color_FFFFFF,t_70) - -## Spring MVC的主要组件? - -- 前端控制器(DispatcherServlet):接收用户请求,给用户返回结果。 -- 处理器映射器(HandlerMapping):根据请求的url路径,通过注解或者xml配置,寻找匹配的Handler。 -- 处理器适配器(HandlerAdapter):Handler 的适配器,调用 handler 的方法处理请求。 -- 处理器(Handler):执行相关的请求处理逻辑,并返回相应的数据和视图信息,将其封装到ModelAndView对象中。 -- 视图解析器(ViewResolver):将逻辑视图名解析成真正的视图View。 -- 视图(View):接口类,实现类可支持不同的View类型(JSP、FreeMarker、Excel等)。 - -## Spring MVC的常用注解由有哪些? -- @Controller:用于标识此类的实例是一个控制器。 -- @RequestMapping:映射Web请求(访问路径和参数)。 -- @ResponseBody:注解返回数据而不是返回页面 -- @RequestBody:注解实现接收 http 请求的 json 数据,将 json 数据转换为 java 对象。 -- @PathVariable:获得URL中路径变量中的值 -- @RestController:@Controller+@ResponseBody - -## Spring MVC的异常处理 - -可以将异常抛给Spring框架,由Spring框架来处理;我们只需要配置简单的异常处理器,在异常处理器中添视图页面即可。 - -- 使用系统定义好的异常处理器 SimpleMappingExceptionResolver -- 使用自定义异常处理器 -- 使用异常处理注解 - -## SpringMVC 用什么对象从后台向前台传递数据的? - -1. 将数据绑定到 request; -2. 返回 ModelAndView; -3. 通过ModelMap对象,可以在这个对象里面调用put方法,把对象加到里面,前端就可以通过el表达式拿到; -4. 绑定数据到 Session中。 - -## RequestBody和RequestParam的区别 - -@RequestBody一般处理的是在ajax请求中声明contentType: "application/json; charset=utf-8"时候。也就是json数据或者xml数据。 - -@RequestParam一般就是在ajax里面没有声明contentType的时候,为默认的`x-www-form-urlencoded`格式时。 - - - -![](http://img.dabin-coder.cn/image/20220612101342.png) diff --git "a/\346\241\206\346\236\266/Spring\345\256\236\346\210\230.md" "b/\346\241\206\346\236\266/Spring\345\256\236\346\210\230.md" deleted file mode 100644 index f56fe59..0000000 --- "a/\346\241\206\346\236\266/Spring\345\256\236\346\210\230.md" +++ /dev/null @@ -1,3228 +0,0 @@ -## Spring的核心 - -相对于EJB(Enterprise JaveBean),Spring提供了更加轻量级和简单的编程模型。 - -Spring可以简化Java开发:1.基于pojo的轻量级和最小侵入式编程;2.通过依赖注入和面向接口实现松耦合;3.基于切面和惯例进行声明式编程;4.通过切面和模板减少样板式代码。 - -1. 非侵入编程 - -很多框架通过强迫应用继承它们的类或实现它们的接口从而导致应用与框架绑死。Spring竭力避免自身API与应用代码的耦合。Spring不会强迫你实现Spring规范的接口或继承Spring规范的类。 - -2. 依赖注入 - -假如两个类相互协作完成特定的业务,按照传统的做法,每个对象负责管理与自己相互协作的对象的引用,这会导致高度耦合。 - -```java -public class Knight { - private RescueQuest quest; - - public Knight() { - this.quest = new RescueQuest(); //勇士只能进行救援工作 - } - - public void embarkOnQuest() { - quest.embark(); - } -} -``` - -通过DI,对象无需自行创建或管理它们的依赖关系,依赖关系将被自动注入到对象当中。对象只通过接口来表明依赖关系,可以传入不同的具体实现。 - -```java -public class Knight { - private Quest quest; - - //通过构造器注入依赖,可以传入任何Quest接口的实现类,如RescueQuest,SlayDragonQuest等 - public Knight(Quest quest) { - this.quest = quest; - } - - public void embarkOnQuest() { - quest.embark(); - } -} -``` - -3. 面向切面编程 - -aop允许你将遍布应用各处的功能(日志,事务管理)分离出来形成可重用的组件。 - -![在这里插入图片描述](https://img-blog.csdnimg.cn/20190216210355438.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1R5c29uMDMxNA==,size_16,color_FFFFFF,t_70) - -4. 模板技术 - -编程中会遇到许多样板式代码,如使用JDBC访问数据库查询数据时,首先需要创建数据库连接(Connection),然后再创建一个语句对象(PrepareStatement),然后进行查询,可能需要处理SQL异常,最后关闭数据库连接、语句和结果集。Spring通过模板封装来消除样板式代码。Spring的JDBCTemplate可以避免传统的JDBC样板代码。 - - - -## 装配bean - -Spring bean装配机制:1.自动装配;2.使用spring基于Java的配置(JavaConfig);3.xml配置。 - -我们应当尽可能使用自动装配的机制,显式配置越少越好。当自动装配行不通时,如使用第三方库的组件装配到应用,则需采用显式装配的方式,此时推荐使用类型安全并且比xml更为强大的JavaConfig。只有当需要使用便利的xml命名空间,并且在JavaConfig中没有同样的实现时,才应该使用xml。 - -### 自动装配bean - -实现自动装配bean 需要两点:1.开启组件扫描;2.给bean加自动装配的注解。 - -```java -public interface CD { - public void play(); -} - -@Component("cd") -public class EasonCD implements CD { - private String song = "Ten years"; - private String singer = "Eason"; - - @Override - public void play() { - System.out.println("singer--" + singer + " sings " + song); - } -} -``` - -@Component表明该类会作为组件类,并告知Spring为这个类创建bean。@Component("cd")可以为bean命名,没有配置默认是类名首字母小写。@Named注解与@Component作用类似。 - -CDPlayer类中,在构造器上加了注解@Autowired,表明当Spring创建CDPlayer bean的时候,会通过这个构造器进行实例化并传入一个类型为CD的bean。@Inject注解和@Autowired作用类似。 - -```java -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -/** - * @Autowired,通过byType的形式自动装配,可以用在属性,构造器和setter方法上 - * required属性设为false,没有符合条件的bean也不会抛异常 - */ -@Component -public class CDPlayer implements MediaPlayer { - private CD cd; - - @Autowired - public CDPlayer(CD cd) { - this.cd = cd; - } - - @Override - public void play() { - cd.play(); - } -} -``` - -组件扫描默认不启动,需要显式配置一下Spring,开启组件扫描。通过@ComponentScan注解启动了组件扫描,扫描包下是否有@Component标注的类,若有,则会在Spring中自动为其创建bean。 - -```java -import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.Configuration; - -/** - * @Configuration用于定义配置类。该类应该包含Spring应用上下文如何创建bean的细节。 - * 通过@ComponentScan注解启动了组件扫描 - * 配置basePackages则扫描指定的包,没配置则默认扫描当前包 - * 配置basePackageClasses则通过扫描配置的类所在的包 - */ -@Configuration -//@ComponentScan(basePackages = {"com.tyson.pojo", "com.tyson.service"}) -@ComponentScan(basePackageClasses = {EasonCD.class}) -public class CDPlayerConfig { -} -``` - -#### 验证自动装配 - -```java -import com.tyson.config.CDPlayerConfig; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; - -/** - * @RunWith(SpringJUnit4ClassRunner.class)在测试开始时自动创建Spring应用上下文 - */ -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes = {CDPlayerConfig.class}) //开启组件扫描 -public class CDPlayerTest { - @Autowired - private CD cd; - @Autowired - private MediaPlayer player; - - @Test - public void cdShouldNotBeNull() { - Assert.assertNotNull(cd); - } - - @Test - public void play() { - player.play(); - } -} -``` - -也可以在applicationContext.xml使用\来启动组件扫描。 - -```xml - - - - - -``` - -测试代码。 - -```java -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; - - -/** - * @RunWith(SpringJUnit4ClassRunner.class)在测试开始时自动创建Spring应用上下文 - */ -@RunWith(SpringJUnit4ClassRunner.class) -//@ContextConfiguration(classes = {CDPlayerConfig.class}) //开启组件扫描 -@ContextConfiguration(locations = {"classpath:applicationContext.xml"}) //开启组件扫描 -public class CDPlayerTest { - @Autowired - private CD cd; - @Autowired - private MediaPlayer player; - - @Test - public void cdShouldNotBeNull() { - Assert.assertNotNull(cd); - } - - @Test - public void play() { - player.play(); - } -} -``` - -### 通过Java代码装配bean - -有些情况下无法使用自动装配,如要将第三方类库的组件装配到我们的应用,便无法使用自动装配。下面使用JavaConfig显式配置Spring。(JavaConfig是配置代码,应该放在单独的包中,与其他应用程序逻辑分离开。) - -CDPlayerConfig配置类 - -```java -import com.tyson.pojo.CD; -import com.tyson.pojo.CDPlayer; -import com.tyson.pojo.EasonCD; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -/** - * @Configuration用于定义配置类,可替换xml配置文件。该类应该包含Spring应用上下文如何创建bean的细节。 - */ -@Configuration -public class CDPlayerConfig { - @Bean(name = "easonCD") //@Bean告诉spring将对象注册到spring容器 - public CD easonCD() { - return new EasonCD(); - } - - /** - * 推荐这种方式,不要求将CD声明到同一个配置文件 - * CD可以采用各种方式创建(自动扫描、配置类、JavaConfig),Spring都能将其传入到配置方法中 - */ - @Bean(name = "cdPlayer") - public CDPlayer cdPlayer(CD cd) { - return new CDPlayer(cd); - } - -/* @Bean(name = "cdPlayer") - public CDPlayer cdPlayer() { - return new CDPlayer(easonCD()); - }*/ -} -``` - -通过AnnotationConfigApplicationContext从Java配置类加载Spring应用上下文。 - -```java -//AnnotationConfigApplicationContext:从一个或多个基于Java的配置类加载Spring应用上下文 -@Test -public void play() { - ApplicationContext cxt = new AnnotationConfigApplicationContext(CDPlayerConfig.class); - CDPlayer cdPlayer = (CDPlayer)cxt.getBean("cdPlayer"); - cdPlayer.play(); -} -``` - -### 通过xml装配bean - -#### 构造器注入 - -借助构造器注入初始化bean有两种方式:constructor-arg元素或者Spring3.0引入的c-命名空间。 - -1. 将引用注入构造器。 - -EasonCD.java - -```java -public class EasonCD implements CD { - @Override - public void play() { - System.out.println("haha"); - } -} -``` - -CDPlayer.java - -```java -import org.springframework.beans.factory.annotation.Autowired; - -public class CDPlayer implements MediaPlayer { - private CD cd; - - /** - * 构造器注入 - */ - public CDPlayer(CD cd) { - this.cd = cd; - } - - @Override - public void play() { - cd.play(); - } -} -``` - -applicationContext.xml使用构造器注入创建bean。 - -```xml - - - - - - - - - - - - - -``` - -测试类 - -```java -/** - * AnnotationConfigApplicationContext:从一个或多个基于Java的配置类加载Spring应用上下文 - * ClassPathXmlApplicationContext:从类路径上的一个或多个xml配置文件中加载Spring应用上下文 - */ -@Test -public void play() { - ApplicationContext cxt = new ClassPathXmlApplicationContext("applicationContext.xml"); - CDPlayer cdPlayer = (CDPlayer)cxt.getBean("cdPlayer"); - cdPlayer.play(); -} -``` - -c-命名空间元素组成如下图,可以看到使用了构造器参数。 - -![在这里插入图片描述](https://img-blog.csdnimg.cn/20190219211708943.png) - -也可以使用索引识别构造器参数。 - -```xml - -``` - -2. 将字面量注入构造器 - -```java -public class PuthCD implements CD { - private String song; - private String note; - - public PuthCD(String song, String note) { - this.song = song; - this.note = note; - } - - @Override - public void play() { - System.out.println("Puth sings \"" + song + "\""); - } -} -``` - -利用constructor-arg元素的value进字面量注入构造器。 - -```xml - - - - - -``` - -也可以使用c-空间。 - -```xml - - - - - -``` - -测试类 - -```java -@Test -public void puthPlay() { - ApplicationContext cxt = new ClassPathXmlApplicationContext("applicationContext.xml"); - PuthCD puthCD = (PuthCD)cxt.getBean("puthCD"); - puthCD.play(); -} -``` - -3. 装配集合 - -```java -public class EuropeCD implements CD { - private String name; - private List singers; - - public EuropeCD(String name, List singers) { - this.name = name; - this.singers = singers; - } - - @Override - public void play() { - System.out.println(name); - singers.forEach(singer -> { - System.out.print(singer + ", "); - }); - } -} -``` - -使用list元素装配集合。这里c-命名空间没有响应的实现,只能用constructor-arg元素。 - -```xml - - - - - - 断眉 - 梦龙 - 西域男孩 - 一体共和 - - - -``` - -#### 属性注入 - -除了可以通过构造器注入初始化bean以外,也可以通过属性注入初始化bean。通过属性注入,则类中不能有带参数的构造器,或者要显式声明无参的构造器。 - -1. 将引用注入属性中 - -```java -import org.springframework.beans.factory.annotation.Autowired; - -public class CDPlayer implements MediaPlayer { - private CD cd; - - /** - *属性注入 - */ - public void setCd(CD cd) { - this.cd = cd; - } - - @Override - public void play() { - cd.play(); - } -} -``` - -applicationContext.xml中进行属性注入创建bean。有两种方式,使用property元素或者p-命名空间。 - -```xml - - - - - - - -``` - -p命名空间元素组成如下: - -![在这里插入图片描述](https://img-blog.csdnimg.cn/20190220133234267.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1R5c29uMDMxNA==,size_16,color_FFFFFF,t_70) - -2. 将字面量和集合注入属性中 - -EuropeCD.java - -```java -import java.util.List; - -public class EuropeCD implements CD { - private String name; - private List singers; - - //属性注入 - public void setName(String name) { - this.name = name; - } - - //属性注入 - public void setSingers(List singers) { - this.singers = singers; - } - - @Override - public void play() { - System.out.println(name); - singers.forEach(singer -> { - System.out.print(singer + ", "); - }); - } -} -``` - -applicationContext.xml - -```xml - - - - - - - - - 断眉 - 梦龙 - 西域男孩 - 一体共和 - - -``` - -引入util命名空间。 - -![在这里插入图片描述](https://img-blog.csdnimg.cn/20190220152747529.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1R5c29uMDMxNA==,size_16,color_FFFFFF,t_70) - -### 混合配置 - -#### 在JavaConfig中引用xml配置 - -假如CD使用xml配置,CDPlayer使用JavaConfig配置。 - -```java -import com.tyson.pojo.CD; -import com.tyson.pojo.CDPlayer; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class CDPlayerConfig { - @Bean(name = "cdPlayer") - public CDPlayer cdPlayer(CD cd) { - return new CDPlayer(cd); - } -} -``` - -装配CD定义在cd-config.xml中。 - -```xml - - - - - - - 断眉 - 梦龙 - 西域男孩 - 一体共和 - - -``` - -新建一个SoundSystemConfig.java类,通过@Import和@ImportResource引入JavaConfig配置类和xml配置文件。 - -```java -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.context.annotation.ImportResource; - -//JavaConfig引用JavaConfig--@Import(CDPlayerConfig.class, CDConfig.class) -@Configuration -@Import(CDPlayerConfig.class) -@ImportResource("classpath:cd-config.xml") -public class SoundSystemConfig { -} -``` - -测试代码 - -```java -/** - * AnnotationConfigApplicationContext:从一个或多个基于Java的配置类加载Spring应用上下文 - * ClassPathXmlApplicationContext:从类路径上的一个或多个xml配置文件中加载Spring应用上下文 - */ -@Test -public void javaConfigImportXmlTest() { - ApplicationContext ctx = new AnnotationConfigApplicationContext(SoundSystemConfig.class); - CDPlayer player = (CDPlayer) ctx.getBean("cdPlayer"); - player.play(); -} -``` - -#### 在xml配置中引用JavaConfig - -假如CDPlayer使用player-config.xml配置,CD使用cd-config.xml配置,则在player-config.xml中需要用import元素引用cd-config文件,从而将CD注入到CDPlayer。 - -player-config.xml - -```xml - - - - - - - -``` - -如果CDPlayer使用player-config.xml配置,而CD使用JavaConfig来配置,则需要这样声明bean: - -```xml - - - - - - - - - - - -``` - -CDConfig.java - -```java -import com.tyson.pojo.CD; -import com.tyson.pojo.EasonCD; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class CDConfig { - @Bean(name = "easonCD") - public CD getCD() { - return new EasonCD(); - } -} -``` - -测试代码: - -```java -@Test -public void xmlImportJavaConfigTest() { - ApplicationContext cxt = new ClassPathXmlApplicationContext("player-config.xml"); - CDPlayer player = (CDPlayer) cxt.getBean("cdPlayer"); - player.play(); -} -``` - -也可以使用第三个配置文件,将JavaConfig和xml配置组合起来。 - -soundSystem-config.xml - -```xml - - - - - - - - - -``` - -故无论是JavaConfig还是xml配置,都可以先创建一个根配置,在根配置里将多个装配类或者xml文件组合起来,同时可以在根配置打开组件扫描(通过\或者@ComponentScan)。 - -## 高级装配 - -### 自动装配的歧义性 - -编写Dessert接口,有三个实现Dessert的类:Cake、IceCream和Cookie。假如三个类都使用@Component注解,Spring在进行组件扫描时会为它们创建bean,这时如果Spring试图自动装配setDessert里面的Dessert参数时,这时Spring便不知道该使用哪个bean。 - -```java -@Autowired -public void setDessert(Dessert dessert) { - this.dessert = dessert; -} -``` - -#### 标识首选的bean - -通过设置其中某个bean为首选的bean可避免自动装配的歧义性。 - -定义Bean时使用@Primary注解。 - -```java -import org.springframework.context.annotation.Primary; -import org.springframework.stereotype.Component; - -@Component -@Primary -public class Cookie implements Dessert { - @Override - public void make() { - System.out.println("making cookie..."); - } -} -``` - -或者在JavaConfig类中使用@Primary注解。 - -```java -import com.tyson.pojo.Cookie; -import com.tyson.pojo.Dessert; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Primary; - -@Configuration -public class DessertConfig { - @Bean - @Primary - public Dessert dessert() { - return new Cookie(); - } -} -``` - -xml配置使用primary属性。 - -```xml - -``` - -#### 限定自动装配的bean - -@Primary只能标识一个首选的bean,当首选的bean存在有多个时,这种方法便失效。Spring的限定符可以在可选的bean进行缩小范围,最终使得只有一个bean符合限定条件。 - -@Qualifier注解是使用限定符的主要方式。它与@Autowired和@Inject协同使用,在注入的时候指定注入哪个bean。 - -```java -@Autowired -@Qualifier("cookie") -public void setDessert(Dessert dessert) { - this.dessert = dessert; -} -``` - -这种方式setDessert方法上所指定的限定符和要注入的bean名称是紧耦合的。如果类名称修改则会导致限定符失效。 - -#### 创建自定义的限定符 - -为bean设置自己的限定符。 - -```java -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.stereotype.Component; - -@Component -@Qualifier("cold") -public class IceCream implements Dessert { - - @Override - public void make() { - System.out.println("making ice cream......"); - } -} -``` - -在自动装配的地方引入自定义限定符。@Autowired和@Qualifier组合,按byName方式自动装配。 - -```java -@Autowired -@Qualifier("cold") -public void setDessert(Dessert dessert) { - this.dessert = dessert; -} -``` - -当使用JavaConfig装配bean时,@Qualifier可以和@Bean注解一起使用。 - -```java -import com.tyson.pojo.Cookie; -import com.tyson.pojo.Dessert; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -//@ComponentScan(basePackages = {"com.tyson.pojo"}) -public class DessertConfig { - @Bean - @Qualifier("cold") - public Dessert dessert() { - return new IceCream(); - } -} -``` - -使用自定义的限定符注解 - -当有多个Dessert都具有cold特征时,此时需要添加更多的限定符来避免歧义性的问题。 - -```java -import com.tyson.pojo.Dessert; -import com.tyson.pojo.IceCream; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class DessertConfig { - @Bean - @Qualifier("cold") - @Qualifier("creamy") - public Dessert dessert() { - return new IceCream(); - } -} -``` - -然而在一个类上出现多个相同类型的注解是不被允许的,会报编译错误。可以通过创建自定义的限定符注解解决这个问题。下面是自定义@Cold注解的例子: - -```java -import org.springframework.beans.factory.annotation.Qualifier; - -import java.lang.annotation.*; - -@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.CONSTRUCTOR}) -@Retention(RetentionPolicy.RUNTIME) -@Qualifier //具有@Qualifier的特性 -public @interface Cold { -} -``` - -自定义@Creamy注解。 - -```java -import org.springframework.beans.factory.annotation.Qualifier; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.CONSTRUCTOR}) -@Retention(RetentionPolicy.RUNTIME) -@Qualifier //具有@Qualifier的特性 -public @interface Creamy { -} -``` - -使用@Cold和@Creamy。 - -```java -import com.tyson.annotation.Cold; -import com.tyson.annotation.Creamy; -import org.springframework.stereotype.Component; - -@Component -@Cold -@Creamy -public class IceCream implements Dessert { - - @Override - public void make() { - System.out.println("making ice cream......"); - } -} -``` - -使用自定义注解更为安全。 - -### bean的作用域 - -默认情况下,Spring应用上下文所有的bean都是以单例的形式创建。不管给定的bean注入到其他bean多少次,每次注入的都是同一个实例。 - -Spring定义了多种定义域: - -单例(Singleton):在整个应用,只创建bean一个实例。 - -原型(prototype):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例。 - -会话(session):在web应用中,为每个会话创建一个bean实例。 - -请求(request):在web应用中,为每个请求创建一个bean实例。 - -单例是默认的作用域,使用@Scope选择其他的作用域,它可以跟@Component和@Bean一起使用。 - -使用@Scope声明CDPlayer为原型bean。 - -```java -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.config.ConfigurableBeanFactory; -import org.springframework.context.annotation.Scope; -import org.springframework.stereotype.Component; - -@Component -@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) -public class CDPlayer implements MediaPlayer { - private CD cd; - - /** - * 构造器注入 - */ - @Autowired - public CDPlayer(CD cd) { - this.cd = cd; - } - - @Override - public void play() { - cd.play(); - } -} -``` - -在JavaConfig中声明CDPlayer为原型bean。 - -```java -@Bean(name = "cdPlayer") -@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) -public CDPlayer cdPlayer(CD cd) { - return new CDPlayer(cd); -} - -``` - -若是通过xml配置bean,可以通过元素的scope属性设置作用域。 - -```java - -``` - -### 运行时值注入 - -依赖注入,是将一个bean引用注入到另一个bean的属性或者构造器参数, 是一个对象和另一个对象进行关联。bean装配是将一个值注入到bean的属性或者构造器参数中。 - -#### 注入外部的值 - -使用JavaConfig的方式装配EasonCD bean。通过Environment类获得app.properties定义的属性值。 - -```java -import com.tyson.pojo.CD; -import com.tyson.pojo.EasonCD; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.PropertySource; -import org.springframework.core.env.Environment; - -@Configuration -@PropertySource("classpath:app.properties") -public class PropertySourcesPlaceholderConfig { - @Autowired - private Environment env; - - @Bean - public CD easonCD() { - return new EasonCD(env.getProperty("song"), - env.getProperty("singer") - ); - } -} -``` - -通过@PropertySource引用了类路径中一个名为app.properties的文件。 - -```properties -song = ten years -singer = Eason -``` - -测试代码 - -```java -@Test -public void test1() { - ApplicationContext ctx = new AnnotationConfigApplicationContext(PropertySourcesPlaceholderConfig.class); - CD cd = ctx.getBean("easonCD", CD.class); - cd.play(); -} -``` - -如果使用组件扫描和自动装配创建bean,可以通过@Value注解引用外部的值。 - -```java -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.PropertySource; -import org.springframework.stereotype.Component; - -@Component("easonCD") -@PropertySource("classpath:app.properties") -public class EasonCD implements CD { - private String song; - private String singer; - -/* @Bean - public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() { - PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer(); - ClassPathResource classPathResource = new ClassPathResource("app.properties"); - propertySourcesPlaceholderConfigurer.setLocation(classPathResource); - propertySourcesPlaceholderConfigurer.setLocalOverride(true); - return propertySourcesPlaceholderConfigurer; - }*/ - - public EasonCD(@Value("${song}") String song, - @Value("${singer}") String singer) { - this.song = song; - this.singer = singer; - } - - @Override - public void play() { - System.out.println("singer--" + singer + " sings " + song); - } -} -``` - -测试代码 - -```java -@Test -public void test1() { - ApplicationContext ctx = new AnnotationConfigApplicationContext(CDConfig.class); - CD cd = ctx.getBean("easonCD", CD.class); - cd.play(); -} -``` - -使用xml配置的话,Spring的\元素可以为我们生成PropertySourcesPlaceholderConfigurer bean,进而可以使用占位符。 - -```xml - - - - - - - - - - - - -``` - -解析外部属性可以将值的处理推迟到运行时,但是它的关注点在于根据名称解析来自于Spring Environment和属性源的属性。 - -> \ 只能导入一个 properties 文件,若需要导入多个 properties 文件,则使用下面的方法: - -```xml - - - - classpath:app.properties - - - -``` - - -#### 使用Spring表达式语言进行装配 - -Spring3引入了Spring表达式语言SpEL,它在将值装配到bean属性和构造器参数过程中所使用的表达式会在运行时计算得到值。 - -Spring表达式要放到"#{...}"之中。 - -```java -public EasonCD(@Value("#{systemProperties['song']}") String song, - @Value("#{systemProperties['singer']}") String singer) { - this.song = song; - this.singer = singer; -} -``` - -使用xml配置。 - -```xml - - - - -``` - -## 面向切面 - -依赖注入实现了对象之间的解耦。AOP可以实现横切关注点和它们所影响的对象之间的解耦。 - -切面可以帮我们模块化横切关注点。安全就是一个横切关注点,应用中的许多方法都会涉及到安全规则。 - -![在这里插入图片描述](https://img-blog.csdnimg.cn/20190223082943466.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1R5c29uMDMxNA==,size_16,color_FFFFFF,t_70) - -横切关注点可以被模块化为特殊的类,这些类称之为切面。这样做有两个好处:每个关注点都集中到一个地方,不会分散到多处代码;服务模块更简洁,因为它们只包含核心功能的代码,而非核心功能的代码被转移到切面了。 - -### AOP术语 - -![在这里插入图片描述](https://img-blog.csdnimg.cn/20190223083512446.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1R5c29uMDMxNA==,size_16,color_FFFFFF,t_70) - -通知(advice):切面的工作被称为通知。通知定义了切面是什么以及何时使用。除了描述切面要完成的工作,通知还解决了何时执行这个工作的问题,它应该在某个方法被调用之前还是之后调用,或者在方法抛异常的时候才调用。 - -Spring切面定义了五种类型的通知:前置通知,后置通知,返回通知,异常通知,环绕通知。 - -连接点(join point):连接点是应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至是修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。 - -切点(point cut):匹配通知所要织入的一个或多个连接点,缩小切面所通知的连接点的范围。通常使用明确的类和方法名称,或者利用正则表达式定义所匹配的类和方法名称来指定这些切点。有些AOP框架允许我们创建动态的切点,可以在运行时(通过方法的参数值等)决定是否应用通知。 - -切面(aspect):切面是通知和切点的结合,通知和切点共同定义了切面的全部内容。 - -引入(introduction):在无需修改原有类的情况下,引入允许我们向类添加新的方法或者属性(新的行为或状态)。 - -织入(weaving):织入是把切面应用到目标对象并创建代理对象的过程。切面在指定的连接点被织入到目标对象中。在目标对象的生命周期有多个点可以进行织入: - -- 编译期:切面在目标对象编译时被织入。这种方式需要特殊的编译器。Aspect的织入编译器就是以这种方式织入切面的。 -- 类加载期:切面在目标对象被加载到JVM时被织入。这种方式需要特殊的类加载器,它可以在目标类被引入应用之前增强目标类的字节码。 -- 运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态创建一个代理对象。Spring AOP就是以这种方式织入切面的。 - -### Spring对aop的支持 - -**Spring提供了4种类型的aop支持:** - -1.基于代理的经典Spring aop;2.纯pojo切面(借助Spring aop命名空间,xml配置);3.@AspectJ注解驱动的切面;4.注入式AspectJ切面(适用于Spring各版本) - -Spring aop构建在动态代理基础之上,因此Spring对aop的支持局限在方法拦截。如果应用的aop需求超过了简单的方法调用(构造器或属性拦截),就需要考虑使用AspectJ来实现切面。这种情况下,第四种类型能够帮助我们将值注入到AspectJ驱动的切面中。AspectJ通过特有的aop语言,我们可以获得更为强大和细粒度的控制,以及更丰富的aop工具集。 - -**Spring在运行时通知对象** - -通过在代理类中包裹切面,Spring在运行时将切面织入到Spring管理的bean中。代理类封装了目标类,并拦截被通知方法的调用,执行额外的切面逻辑,并调用目标方法。 - -![在这里插入图片描述](https://img-blog.csdnimg.cn/20190227162637751.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1R5c29uMDMxNA==,size_16,color_FFFFFF,t_70) - -### 通过切面选择连接点 - -在Spring aop中,要使用AspectJ的切点表达式语言来定义切点。Spring仅支持AspectJ切点指示器的一个子集。下表列出Spring AOP支持的AspectJ切点指示器。 - -| AspectJ指示器 | 描述 | -| ------------- | ------------------------------------------------------------ | -| arg() | 限制连接点匹配参数为指定类型的执行方法 | -| @args() | 限制连接点匹配参数由指定注解标注的执行方法 | -| execution() | 用于匹配是连接点的执行方法 | -| this() | 限制连接点匹配AOP代理的bean引用为指定类型的类 | -| target | 限制连接点匹配目标对象为指定类型的类 | -| @target() | 限制连接点匹配特定的执行对象,这些对象对应的类要具有指定类型的注解 | -| within() | 限制连接点匹配指定的类型 | -| @within() | 限制连接点匹配指定注解所标注的类型(当使用Spring AOP时,方法定义在由指定的注解所标注的类里) | -| @annotation | 限定匹配带有指定注解的连接点 | - -在Spring中尝试使用AspectJ其他指示器时,将会抛出IllegalArgument-Exception异常。 - -**编写切点** - -定义一个Performance接口: - -```java -public interface Performance { - public void perform(); -} -``` - -使用AspectJ切点表达式来选择Performance的perform()方法: - -```java -execution(* com.tyson.aop.Performance.perform(..)) -``` - -使用execution()指示器选择Performance的perform()方法。 - -![在这里插入图片描述](https://img-blog.csdnimg.cn/20190226163248197.png) - -使用within()指示器限制切点范围: - -![在这里插入图片描述](https://img-blog.csdnimg.cn/20190226164058113.png) - -因为“&”在XML中有特殊含义,所以在Spring的XML配置里面描述切点时,我们可以使用and来代替“&&”。同样,or和not可以分别用来代替“||”和“!”。 - -**在切点中选择bean** - -Spring引入了一个新的bean()指示器,它允许我们在切点表达式使用bean的ID作为参数来限定切点只匹配特定的bean。 - -```java -execution(* com.tyson.aop.Performance.perform(..)) - and bean('drum') -``` - -在执行Performance的perform()方法时应用通知,当限定bean的ID为drum。 - -```java -execution(* com.tyson.aop.Performance.perform(..)) - and !bean('drum') -``` - -### 使用注解创建切面 - -使用注解创建切面是AspectJ 5引入的关键特性,通过少量的注解便可以把Java类转变为切面。 - -#### 定义切面 - -使用aop相关注解需先导入依赖。 - -```xml - - - - 4.0.0 - - com.tyson - SpringDemo - 1.0-SNAPSHOT - war - - SpringDemo Maven Webapp - - http://www.example.com - - - UTF-8 - 3.5.1 - 1.8 - 1.8 - UTF-8 - 5.0.5.RELEASE - 4.3.9.RELEASE - 1.6.11 - 2.1 - 4.12 - - - - - - - org.springframework - spring-core - ${spring.version} - - - - org.springframework - spring-test - ${spring.version} - - - - - org.springframework - spring-context - ${spring.version} - - - org.springframework - spring-aop - ${spring-aop.version} - - - org.aspectj - aspectjrt - ${aspectj-version} - - - - org.aspectj - aspectjweaver - ${aspectj-version} - - - - cglib - cglib - ${cglib.version} - - - org.springframework - spring-tx - ${spring.version} - - - - - junit - junit - ${junit.version} - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - ${maven.compiler.version} - - ${maven.compiler.source} - ${maven.compiler.target} - - - - - -``` - -Audience类定义了一个切面。 - -```java -import org.aspectj.lang.annotation.*; - -@Aspect -public class Audience { - @Pointcut("execution(* com.tyson.aop.Performance.perform(..))") - public void performance() {} - - @Before("performance()") - public void silenceCellPhones() { - System.out.println("silencing cell phones"); - } - - @Before("performance()") - public void takeSeats() { - System.out.println("taking seats"); - } - - @AfterReturning("performance()") - public void applause() { - System.out.println("clap clap clap!"); - } - - @AfterThrowing("performance()") - public void demandRefund() { - System.out.println("demanding a refund"); - } -} -``` - -启动自动代理功能。若使用JavaConfig,可以在配置类的类级别使用@EnableAspectJAutoProxy注解启动自动代理。 - -```java -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.EnableAspectJAutoProxy; - -@Configuration -@EnableAspectJAutoProxy -public class ConcertConfig { - @Bean - public Audience audience() { - return new Audience(); - } - - @Bean - public Performance magicPerformance() { - return new MagicPerformance(); - } -} -``` - -若使用xml装配bean,则可以用\元素启动自动代理。concert-config.xml如下: - -```xml - - - - - - - -``` - -测试代码: - -```java -import org.junit.Test; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.support.ClassPathXmlApplicationContext; - -public class ConcertTest { - @Test - public void consertTest() { - ApplicationContext ctx = new AnnotationConfigApplicationContext(ConcertConfig.class); - Performance magicPerformance = (Performance) ctx.getBean("magicPerformance"); - magicPerformance.perform(); - } - - @Test - public void consertTest1() { - ApplicationContext ctx = new ClassPathXmlApplicationContext("concert-config.xml"); - Performance magicPerformance = (Performance) ctx.getBean("magicPerformance"); - magicPerformance.perform(); - } -} -``` - -测试结果: - -```java -silencing cell phones -taking seats -performing magic... -clap clap clap! -``` - - -#### 创建环绕通知 - -使用环绕通知重新实现Audience切面。 - -```java -import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.annotation.*; - -@Aspect -public class Audience { - @Pointcut("execution(* com.tyson.aop.Performance.perform(..))") - public void performance() {} - - @Around("performance()") - public void watchPerformance(ProceedingJoinPoint joinPoint) { - try { - System.out.println("silencing cell phones"); - System.out.println("taking seats"); - //不调用proceed会阻塞对被通知方法performance的调用,也可以多次调用proceed方法 - joinPoint.proceed(); - System.out.println("clap clap clap!"); - } catch (Throwable e) { - System.out.println("demanding a refund"); - } - } -} -``` - -#### 处理通知中的参数 - -被通知方法含有参数,切面可以访问和使用传给被通知方法的参数。下面使用TrackCounter类记录磁道播放的次数。其中被通知方法playTrack方法有形参trackNum。 - -```java -import org.aspectj.lang.annotation.Aspect; -import org.aspectj.lang.annotation.Before; -import org.aspectj.lang.annotation.Pointcut; - -import java.util.HashMap; -import java.util.Map; - -@Aspect -public class TrackCounter { - private Map trackCounts = new HashMap<>(); - - @Pointcut("execution(* com.tyson.pojo.BlankDisc.playTrack(int)) && args(trackNum)") - public void playTrack(int trackNum) {} - - @Before("playTrack(trackNum)") - public void countTrack(int trackNum) { - int currentCount = getTrackCount(trackNum); - trackCounts.put(trackNum, currentCount + 1); - } - - public int getTrackCount(int trackNum) { - return trackCounts.containsKey(trackNum) ? trackCounts.get(trackNum) : 0; - } -} -``` - -BlankDisc类: - -```java -public class BlankDisc { - private String title; - - public void setTitle(String title) { - this.title = title; - } - - public void playTrack(int trackNum) { - System.out.println("playing track: " + trackNum); - } -} -``` - -TrackCounterConfig开启自动代理,装配BlankDisc和TrackCounter bean。 - -```java -import com.tyson.pojo.BlankDisc; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.EnableAspectJAutoProxy; - -@Configuration -@EnableAspectJAutoProxy -public class TrackCounterConfig { - @Bean - public BlankDisc blankDisc() { - BlankDisc cd = new BlankDisc(); - cd.setTitle("apple band"); - - return cd; - } - - @Bean - public TrackCounter trackCounter() { - return new TrackCounter(); - } -} -``` - -测试代码: - -```java -import com.tyson.pojo.BlankDisc; -import org.junit.Test; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; - -public class TrackCounterTest { - @Test - public void test() { - ApplicationContext ctx = new AnnotationConfigApplicationContext(TrackCounterConfig.class); - BlankDisc blankDisc = (BlankDisc) ctx.getBean("blankDisc"); - TrackCounter counter = (TrackCounter) ctx.getBean("trackCounter"); - blankDisc.playTrack(1); - blankDisc.playTrack(2); - blankDisc.playTrack(2); - System.out.println(counter.getTrackCount(1)); - System.out.println(counter.getTrackCount(2)); - } -} -``` - -#### 通过注解引入新功能 - -利用引入的功能,切面可以为Spring bean添加新方法。 - -![在这里插入图片描述](https://img-blog.csdnimg.cn/2019022717044484.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1R5c29uMDMxNA==,size_16,color_FFFFFF,t_70) - -假如为Performance实现类引入Encoreable(再次表演)接口。 - -```java -public interface Encoreable { - public void performEncore(); -} -``` - -为了实现该功能,我们需要创建一个切面: - -```java -import org.aspectj.lang.annotation.Aspect; -import org.aspectj.lang.annotation.DeclareParents; - -@Aspect -public class EncoreableIntroducer { - @DeclareParents(value="com.tyson.aop.Performance+", - defaultImpl = DefaultEncoreable.class) - public static Encoreable encoreable; -} -``` - -ConcertConfig类如下,当Spring发现一个bean使用了@Aspect注解,Spring就会创建一个代理,在运行时会将调用委托给被代理的bean或者被引入的实现。 - -```java -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.EnableAspectJAutoProxy; - -@Configuration -@EnableAspectJAutoProxy -public class ConcertConfig { - @Bean - public Performance magicPerformance() { - return new MagicPerformance(); - } - - @Bean - public EncoreableIntroducer encoreableIntroducer() { - return new EncoreableIntroducer(); - } -} -``` - -测试代码: - -```java - @Test - public void consertTest2() { - ApplicationContext ctx = new AnnotationConfigApplicationContext(ConcertConfig.class); - Encoreable magicPerformance = ctx.getBean("magicPerformance", Encoreable.class); - magicPerformance.performEncore(); - } -``` - -### 在xml中声明切面 - -在不能为通知类添加注解的时候,就只能使用xml配置。Spring的AOP配置元素如下: - -| aop配置元素 | 用途 | -| ---------------------- | ------------------------------------------------------------ | -| \ | 定义aop通知器 | -| \ | 定义aop切面 | -| \ | 定义切点 | -| \ | 以透明的方式为被通知对象引入新的接口 | -| \ | 顶层的aop配置元素,大多数\配置元素必须包含在\内 | -| \ | 前置通知 | -| \ | 后置通知 | -| \ | 返回通知 | -| \ | 异常通知 | -| \ | 环绕通知 | - -重新定义Audience类。 - -```java -public class Audience { - - public void silenceCellPhones() { - System.out.println("silencing cell phones"); - } - - public void takeSeats() { - System.out.println("taking seats"); - } - - public void applause() { - System.out.println("clap clap clap!"); - } - - public void demandRefund() { - System.out.println("demanding a refund"); - } -} -``` - -#### 声明通知 - -通过xml将无注解的Audience声明为切面。 - -```xml - - - - - - - - - - - - - - - - - - - -``` - -#### 创建环绕通知 - -```java -import org.aspectj.lang.ProceedingJoinPoint; - -public class Audience { - public void watchPerformance(ProceedingJoinPoint joinPoint) { - try { - System.out.println("silencing cell phones"); - System.out.println("taking seating"); - joinPoint.proceed(); - System.out.println("clap clap clap!"); - } catch (Throwable e) { - System.out.println("demanding a refund"); - } - } -} -``` - -声明Audience切面 - -```xml - - - - - - - - - - - - - - - - -``` - -#### 为通知传递参数 - -同样使用TrackCounter记录磁道播放的次数。无注解的TrackCounter如下: - -```java -import java.util.HashMap; -import java.util.Map; - -public class TrackCounter { - private Map trackCounts = new HashMap<>(); - public void countTrack(int trackNum) { - int currentCount = getTrackCount(trackNum); - trackCounts.put(trackNum, currentCount + 1); - } - - public int getTrackCount(int trackNum) { - return trackCounts.containsKey(trackNum) ? trackCounts.get(trackNum) : 0; - } -} -``` - -在xml中将TrackCounter配置为参数化的切面。xml中&符号会被解析成实体的开始,故用and代替。 - -```xml - - - - - - - - - - - - - - - - - -``` - -#### 通过切面引入新的功能 - -concert-config.xml - -```xml - - - - - - - - - - - - - - - - -``` - -测试代码: - -```java - @Test - public void consertTest4() { - ApplicationContext ctx = new ClassPathXmlApplicationContext("concert-config.xml"); - Encoreable magicPerformance = ctx.getBean("magicPerformance", Encoreable.class); - magicPerformance.performEncore(); - } -``` - -### 注入AspectJ切面 - -Spring AOP是功能比较弱的AOP解决方案,AspectJ提供了Spring AOP所不支持的许多类型的切点。如当我们需要在创建对象时应用通知,使用构造器切点很容易实现,而Spring AOP不支持构造器切点,所以基于代理的Spring AOP不能把通知应用于对象的创建过程。此时可以使用AspectJ实现。 - -使用AspectJ实现的表演评论员: - -``` -package com.tyson.aop; - -public aspect CriticAspect { - public CriticAspect() {} - - pointcut performance() : execution(* com.tyson.aop.Performance.perform(..)); - - pointcut construct() : execution(com.tyson.aop.CriticismEngineImpl.new()); - - before() : performance() { - System.out.println("performance构造器调用之前"); - } - - after() : performance() { - System.out.println("performance构造器调用之后"); - } - - after() returning : performance() { - System.out.println("criticism: " + criticismEngine.getCriticism()); - } - - private CriticismEngine criticismEngine; - - //注入CriticismEngine - public void setCriticismEngine(CriticismEngine criticismEngine) { - this.criticismEngine = criticismEngine; - } -} -``` - -CriticismEngine类以及实现类 - -```java -public class CriticismEngine { - private String criticism; - - public String getCriticism() { - return criticism; - } - - public void setCriticism(String criticism) { - this.criticism = criticism; - } -} - -public class CriticismEngineImpl extends CriticismEngine { - private String[] criticismPool; - - @Override - public String getCriticism() { - int i = (int) (Math.random() * criticismPool.length); - return criticismPool[i]; - } - - //Injected - public void setCriticismPool(String[] criticismPool) { - this.criticismPool = criticismPool; - } -} -``` - -在concert-config.xml中将CriticismEngine bean注入到CritisicAspect。CritisicAspect bean的声明使用了factory-method属性。通常情况下,Spring bean由Spring容器初始化,而Aspect切面是由AspectJ在运行期创建的。在Spring为CriticAspect注入CriticismEngine之前,CriticAspect已经被实例化了。故我们需要一种方式为Spring获得已经由AspectJ创建的CriticAspect实例的句柄,从而可以注入CriticismEngine。AspectJ切面提供了一个静态的aspectOf()方法,该方法返回切面的一个单例。Spring需要通过aspectOf()工厂方法获得切面的引用,然后进行依赖注入。 - -```xml - - - - - - - - - - - good - waste time - enjoy it - deserve - - - - - - - -``` - -测试代码: - -```java - @Test - public void consertTest5() { - ApplicationContext ctx = new ClassPathXmlApplicationContext("concert-config.xml"); - MagicPerformance magicPerformance = ctx.getBean("magicPerformance", MagicPerformance.class); - magicPerformance.perform(); - } -``` - -测试结果: - -```java -performance构造器调用之前 -performing magic... -performance构造器调用之后 -criticism: deserve -``` - -## 后端中的 Spring - -### 数据访问模板化 - -Spring 将数据访问过程分为两个部分:模板(template)和回调(callback),Spring 的模板类处理数据访问的固定部分,如事务控制、资源管理和处理异常;应用程序相关的数据访问,如语句、参数绑定以及处理结果集,在回调的实现中处理。 - -针对不同的持久化平台,Spring 提供了不同的持久化模板。如果直接使用 JDBC,则可以使用 JdbcTemplate。如果想要使用对象关系映射框架,可以使用 JpaTemplate 和 HibernateTemplate。 - -### 配置数据源 - -Spring 提供了配置数据源 bean 的多种方式: - -- 通过 JDBC 驱动程序定义的数据源 -- 通过 JNDI (Java Naming and Directory Interface)查找的数据源 -- 连接池的数据源 - -### 在 Spring 中集成 Hibernate - -本节参考自:[spring+springmvc+hibernate 整合](https://www.cnblogs.com/xuezhajun/p/7687230.html) - -Hibernate 是开源的持久化框架,不仅提供了基本的对象关系映射,还提供了 ORM 工具的复杂功能,如缓存、延迟加载、预先抓取(eager fetching)等。 - -引入依赖: - -```xml - - org.springframework - spring-tx - ${spring.version} - - - - org.springframework - spring-orm - 4.1.7.RELEASE - - - - - org.hibernate - hibernate-core - - - - - c3p0 - c3p0 - 0.9.1.2 - - - - - mysql - mysql-connector-java - 5.1.37 - -``` - -**声明 Hibernate 的 Session 工厂** - -Session 接口提供了基本的数据访问功能,获取 Session 对象的标准方式是借助于 Hibernate SessionFactory 接口的实现类。SessionFactory 主要负责 Hibernate Session 的打开、关闭以及管理。 - -从Spring 3.1版开始,Spring 提供了三个 SessionFactorybean : - -```Java -org.springframework.orm.hibernate3.LocalSessionFactoryBean -org.springframework.orm.hibernate4.LocalSessionFactoryBean -org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean -``` - -如果使用高于 Hibernate 3.1版本,低于4.0版本,并且使用 xml 定义映射的话,则需要定义org.springframework.orm.hibernate3.LocalSessionFactoryBean。 - -如果倾向于使用注解来定义映射的话,并且没有使用 Hibernate 4的话,那么需要使用 AnnotationSessionFactoryBean。 - -当使用 Hibernate 4时,就应该使用org.springframework.orm.hibernate4.LocalSessionFactoryBean,Spring 3.1引入的这个 SessionFactoryBean 类似于 Hibernate 3中的 LocalSessionFactoryBean 和 AnnotationSessionFactoryBean 的结合体。 - -datasource.xml 文件如下: - -```xml - - - - - - - - - - - - - - - - - - - - - - ${hibernate.hbm2ddl.auto} - ${hibernate.dialect} - ${hibernate.show_sql} - ${hibernate.format_sql} - - - - - - - - - - - -``` - -datasource.properties 如下: - -```properties -#database connection config -jdbc.driver = com.mysql.jdbc.Driver -jdbc.url = jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf-8 -jdbc.username = root -jdbc.password = 123456 - -#hibernate config -hibernate.dialect = org.hibernate.dialect.MySQLDialect -hibernate.show_sql = true -hibernate.format_sql = true -hibernate.hbm2ddl.auto = update -``` - - beans.xml 如下: - -```xml - - - - - - - - - - - - - - - - classpath:datasource.properties - - - - - - - -``` - -实体类(使用注解来定义映射): - -```java -import java.util.Set; - -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Transient; - -@Entity -@Table(name="people") -public class People { - - @Id - @Column(name="ID") - private int id; - - @Column(name="name") - private String name; - - @Column(name="sex") - private String sex; - - public void setName(String name) { - this.name = name; - } - public String getSex() { - return sex; - } - public void setSex(String sex) { - this.sex = sex; - } - -} -``` - -### Spring 与 Java 持久化 API - -JPA 基于 POJO 的持久化机制。JPA 是一种规范,而 Hibernate 是它的一种实现。除了 Hibernate,还有 EclipseLink,OpenJPA 等可供选择,所以使用 JPA 的一个好处是,可以更换实现而不必改动太多代码。 - -**导入依赖包** - -```xml - - - org.springframework - spring-test - ${spring.version} - - - - org.springframework - spring-context - ${spring.version} - - - - org.springframework - spring-orm - ${spring.version} - - - - - org.hibernate - hibernate-entitymanager - ${hibernate.version} - -``` - -在 Spring 中使用 JPA 的第一步是要在 Spring 应用的上下文将实体管理器工厂(entity manager factory)按照 Bean 的形式进行配置。 - -#### 配置实体管理器工厂 - -基于 JPA 的应用程序需要通过 EntityManegerFactory 获取 EntityManager 实例。JPA 定义了两种类型的EntityManagerFactory: - -- 应用程序管理类型(Application-managed):当应用程序向实体管理器工厂直接请求实体管理器时,工厂会创建一个实体管理器。在这种模式下,程序要负责打开或关闭实体管理器并在事务中对其进行控制。这种方式的实体管理器适合于不运行在 Java EE 容器中的独立应用程序。 -- 容器管理类型(container-managed):实体管理器由 Java EE 创建和管理。应用程序根本不与实体管理器工厂打交道。相反,实体管理器直接通过注入或 JNDI 来获取。容器负责配置实体管理器工厂。这种类型的实体管理器最适用于 Java EE 容器。 - -两种实体管理器实现了同一个 EntityManager 接口。这两种实体管理器工厂分别由对应的Spring工厂Bean创建: - -- LocalEntityManagerFactoryBean 生成应用程序管理类型的 EntityManager-Factory -- LocalContainerEntityManagerFactoryBean 生成容器管理类型的 Entity-ManagerFactory - -**使用容器管理的 EntityManagerFactory** - -配置容器管理类型的 EntityManagerFactory: - -```java -@Bean -public LocalContainerEntityManagerFactoryBean entityManagerFactory( - DataSource dataSource, JpaVendorAdapter jpaVendorAdapter) { - LocalContainerEntityManagerFactoryBean entityManagerFactory = new LocalContainerEntityManagerFactoryBean(); - entityManagerFactory.setDataSource(dataSource); - entityManagerFactory.setJpaVendorAdapter(jpaVendorAdapter); - entityManagerFactory.setPackagesToScan("com.tyson.domain"); - - return entityManagerFactory; -} -``` - -LocalContainerEntityManager 的 DataSource 属性 dataSource 可以是 java.sql.DataSource 的任何实现,dataSource 还可以在 persistence.xml 中进行配置,但是这个属性指定的数据源具有更高的优先级。 - -jpaVendorAdapter 属性用于指定所使用的哪一个厂商的 JPA 实现。Spring提供了多个 JPA 厂商适配器。 - - - -- EclipseLinkJpaVendorAdapter -- HibernateJpaVendorAdapter -- OpenJpaVendorAdapter -- TopLinkJpaVendorAdapter - -配置容器管理类型的 JPA 完整代码: - -```java -import com.mchange.v2.c3p0.ComboPooledDataSource; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.PropertySource; -import org.springframework.core.env.Environment; -import org.springframework.orm.jpa.JpaTransactionManager; -import org.springframework.orm.jpa.JpaVendorAdapter; -import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; -import org.springframework.orm.jpa.vendor.Database; -import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; -import org.springframework.transaction.annotation.EnableTransactionManagement; - -import javax.sql.DataSource; -import java.beans.PropertyVetoException; - -@Configuration -@ComponentScan(basePackages = {"com.tyson.db"}) -@EnableTransactionManagement -@PropertySource(value = "db.properties") -public class JpaConfig { - @Autowired - Environment env; - - /** - * dataSource 注入到entityManagerFactory中,不用在persistence.xml配置数据库信息 - * jpaVendorAdapter 指定所使用的哪一个厂商的 JPA 实现 - * setPackagesToScan 可以在包下查找带有@Entity 注解的类 - * 可以不用使用 persistence.xml(主要作用是识别持久化单元的实体类) - */ - @Bean - public LocalContainerEntityManagerFactoryBean entityManagerFactory( - DataSource dataSource, JpaVendorAdapter jpaVendorAdapter) { - LocalContainerEntityManagerFactoryBean entityManagerFactory = new LocalContainerEntityManagerFactoryBean(); - entityManagerFactory.setDataSource(dataSource); - entityManagerFactory.setJpaVendorAdapter(jpaVendorAdapter); - /** - * - */ - entityManagerFactory.setPackagesToScan("com.tyson.domain"); - - return entityManagerFactory; - } - - @Bean - public DataSource dataSource() { - ComboPooledDataSource dataSource = new ComboPooledDataSource(); - try { - dataSource.setUser(env.getProperty("c3p0.user")); - dataSource.setPassword(env.getProperty("c3p0.password")); - dataSource.setJdbcUrl(env.getProperty("c3p0.jdbcUrl")); - dataSource.setDriverClass(env.getProperty("c3p0.driverClass")); - } catch (PropertyVetoException e) { - e.printStackTrace(); - } - return dataSource; - } - - @Bean - public JpaVendorAdapter jpaVendorAdapter() { - HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter(); - jpaVendorAdapter.setDatabasePlatform("org.hibernate.dialect.MySQL5InnoDBDialect"); - jpaVendorAdapter.setDatabase(Database.MYSQL); - jpaVendorAdapter.setShowSql(true); - jpaVendorAdapter.setGenerateDdl(false); - - return jpaVendorAdapter; - } - - @Bean - public JpaTransactionManager transactionManager() { - JpaTransactionManager transactionManager = new JpaTransactionManager(); - transactionManager.setEntityManagerFactory( - entityManagerFactory(dataSource(), jpaVendorAdapter()).getObject()); - - return transactionManager; - } - - /* @Bean - public PersistenceAnnotationBeanPostProcessor postProcessor() { - return new PersistenceAnnotationBeanPostProcessor(); - }*/ - - /** - * 给 Repository 添加异常转换功能 - * 捕获平台相关的异常,然后使用 Spring 统一的非检查型异常的形式重新抛出 - */ - @Bean - public BeanPostProcessor persistenceTranslation() { - return new PersistenceExceptionTranslationPostProcessor(); - } -} -``` - -src/main/resources 目录下的 db.properties: - -```properties -c3p0.driverClass=com.mysql.jdbc.Driver -c3p0.jdbcUrl=jdbc:mysql://localhost:3306/springdemo?useUnicode=true&characterEncoding=utf-8 -c3p0.user= -c3p0.password= - -#连接池初始化时创建的连接数 -c3p0.initialPoolSize=3 -#连接池保持的最小连接数 -c3p0.minPoolSize=3 -#连接池在无空闲连接可用时一次性创建的新数据库连接数,default:3 -c3p0.acquireIncrement=3 -#连接池中拥有的最大连接数,如果获得新连接时会使连接总数超过这个值则不会再获取新连接,而是等待其他连接释放,所以这个值有可能会设计地很大,default : 15 -c3p0.maxPoolSize=15 -#连接的最大空闲时间,如果超过这个时间,某个数据库连接还没有被使用,则会断开掉这个连接,单位秒 -c3p0.maxIdleTime=100 -#连接池在获得新连接失败时重试的次数,如果小于等于0则无限重试直至连接获得成功 -c3p0.acquireRetryAttempts=30 -#连接池在获得新连接时的间隔时间 -c3p0.acquireRetryDelay=1000 -``` - -#### 编写 JPA Repository - -UserRepository 接口代码: - -```java -package com.tyson.db.jpa; - -import com.tyson.domain.User; - -import java.util.List; - -public interface UserRepository { - void saveUser(User user); - User findById(long id); - User findByUsername(String username); - List findAll(); -} -``` - -不使用 Spring 模板的纯 JPA Repository: - -```java -package com.tyson.db.jpa; - -import com.tyson.db.UserRepository; -import com.tyson.domain.User; -import org.springframework.stereotype.Repository; -import org.springframework.transaction.annotation.Transactional; - -import javax.persistence.EntityManager; -import javax.persistence.PersistenceContext; -import java.util.List; - -@Repository("jpaUserRepository") -@Transactional -public class JpaUserRepository implements UserRepository { - - @PersistenceContext - private EntityManager entityManager; - - @Override - public void saveUser(User user) { - entityManager.persist(user); - } - - @Override - public User findById(long id) { - return entityManager.find(User.class, id); - } - - @Override - public User findByUsername(String username) { - return (User) entityManager.createQuery("select u from User u where u.username = ? ").setParameter(1, username).getSingleResult(); - } - - @Override - public List findAll() { - return entityManager.createQuery("select u from User u").getResultList(); - } -} -``` - -1. @Repository 可以减少显式配置,通过组件扫描自动创建。JpaTemplate 会捕获平台相关的异常,然后使用 Spring 统一的非检查型异常的形式重新抛出。为了给不使用模板的 Jpa Repository 添加异常转化功能,我们只需在 Spring 应用上下文添加一个 PersistenceExceptionTranslationPostProcessor bean,这是一个 bean 后置处理器,它会在所有拥有@Repository 注解的类上添加一个通知器(advisor),这样就会捕获平台相关的异常并以 Spring 非检查型数据访问异常的形式重新抛出。 - -2. @Transactional 表明这个 Repository 中的持久化方法是在事务上下文中执行的。不添加@Transactional 注解 会抛异常:javax.persistence.TransactionRequiredException: No transactional EntityManager available。 - -3. 使用@PersistenceContext 并不会真正注入 EntityManager,它没有把真正的 EntityManager 设置给 Repository,而是给了它一个 EntityManager 的代理。真正的 EntityManager 是与当前事务相关联的那个,如果不存在这样的 EntityManager 的话,就会创建一个新的。这样便能以线程安全的方式使用 EntityManager。 - -4. @PersistenceContext 并不是 Spring 的注解,它是由 JPA 规范提供的,为了让 Spring 理解这些注解,并注入EntityManager,我们需要配置 Spring 的 PersistenceAnnotationBeanPostProcessor。如果已经使用 \或者\,会自动注册 PersistenceAnnotationBeanPostProcessor bean。否则需要显式注册这个bean。 - -```java -@Bean -public PersistenceAnnotationBeanPostProcessor postProcessor() { - return new PersistenceAnnotationBeanPostProcessor(); -} -``` - -实体类 User: - -```java -package com.tyson.domain; - -import javax.persistence.*; - -@Entity -public class User { - public User() {} - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Column(name="username") - private String username; - - //setter/getter/toString -} -``` - -测试代码: - -```java -import com.tyson.config.JpaConfig; -import com.tyson.db.UserRepository; -import com.tyson.domain.User; -import org.junit.Test; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; - -public class JpaTest { - @Test - public void test() { - ApplicationContext ctx = new AnnotationConfigApplicationContext(JpaConfig.class); - UserRepository userRepository = (UserRepository) ctx.getBean("jpaUserRepository"); - - User user = new User(); - user.setUsername("tyson"); - userRepository.saveUser(user); - } -} -``` - -### 借助 Spring Data 实现自动化的 JPA Repository - -Spring Data JPA 是Spring基于ORM框架、JPA规范封装的一套 JPA 应用框架,可使开发者用极简的代码即可实现对数据的访问和操作。 - -#### Spring Data JPA 的核心接口 - -- Repository:最顶层的接口,是一个空的接口,目的是为了统一所有Repository的类型,且能让组件扫描的时候自动识别。 -- CrudRepository :是Repository的子接口,提供CRUD的功能 -- PagingAndSortingRepository:是CrudRepository的子接口,添加分页和排序的功能 -- JpaRepository:是PagingAndSortingRepository的子接口,增加了一些实用的功能,如批量操作等 -- JpaSpecificationExecutor:用来做负责查询的接口 -- Specification:是 Spring Data JPA 提供的一个查询规范,Criteria 查询 - -#### 定义数据访问层 - -引入依赖: - -```xml - - - org.springframework - spring-test - ${spring.version} - - - - org.springframework - spring-context - ${spring.version} - - - - org.springframework - spring-orm - ${spring.version} - - - - - org.springframework.data - spring-data-jpa - ${springdatajpa.version} - - - - - org.hibernate - hibernate-entitymanager - ${hibernate.vers\ion} - -``` - -只需定义一个继承 JpaRepository 的接口即可: - -```java -package com.tyson.db.springdatajpa; - -import com.tyson.domain.User; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface UserRepository extends JpaRepository { -} -``` - -继承了 JpaRepository 接口默认已经有了下面的数据访问操作方法: - -```java -@NoRepositoryBean -public interface JpaRepository extends PagingAndSortingRepository, QueryByExampleExecutor { - List findAll(); - - List findAll(Sort var1); - - List findAll(Iterable var1); - - List save(Iterable var1); - - void flush(); - - S saveAndFlush(S var1); - - void deleteInBatch(Iterable var1); - - void deleteAllInBatch(); - - T getOne(ID var1); - - List findAll(Example var1); - - List findAll(Example var1, Sort var2); -} -``` - -Spring Data 自动为我们生成 UserRepository 的实现类。我们需要在 Spring 配置中添加一个元素启用 Spring Data JPA。下面使用 xml 配置方式启用Spring Data JPA: - -```xml - - - - - - ... - -``` - -\元素会扫描它的基础包来查找扩展自 Spring Data JPA Repository 接口的所有接口。如果发现有扩展自 Repository 的接口,它会在应用启动时自动生成该接口的实现类。 - -也可以使用 Java 配置方式启用 Spring Data JPA: - -```java -@Configuration -@EnableJpaRepositories(basePackages = {"com.tyson.db.springdatajpa"}) -public class SpringDataJpaConfig { -} -``` - -UserReposity 扩展了 Repository 接口,当Spring Data 扫描到它后,会为它创建 UserRepository 的实现类,其中包含了继承自 JpaRepository、PagingAndSortingRepository 和 CrudRepository 的18个方法。Repository 的 实现类是在应用启动时生成的,不是在构建时通过代码生成技术生成,也不是在接口调用时才创建的。 - -#### 定义查询方法 - -**根据属性名查询** - -将 UserRepository 接口修改如下: - -```java -import com.tyson.domain.User; -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.List; - -public interface UserRepository extends JpaRepository { - List findByUsername(String username); - - //限制结果数量 - public List findFirst10ByUsername(String username); -} -``` - -通过方法签名已经告诉 Spring Data JPA 怎样实现这个方法了。Repository 方法是由一个动词、一个可选的主题、关键字 By 和一个断言所组成的。 - -![](https://img2018.cnblogs.com/blog/1252910/201903/1252910-20190309201549758-1658225268.png) - - - -1. Spring Data 允许在方法名中使用四种名词:get/read/find/count。get/read/find是同义的。 - -2. 如果主题的名称以 Distinct 开头的话,那么返回的结果集不包含重复记录。 - -3. 断言指定了限制结果集的属性。断言中会有一个或者多个限制条件,并且每个条件可以指定一种比较操作,如果省略比较操作,则默认是相等比较操作。条件中可能包含 IgnoringCase 或者 IgnoresCase. - -```java -List findByFirstnameIgnoresCaseOrLastnameIgnoresCase(String first, String last); - -List findByFirstnameOrLastnameAllIgnoresCase(String first, String last); -``` - -4. 在方法名称结尾处添加 OrderBy,实现结果集排序。如果要根据多个属性排序的话,只需将其依序添加到 OrderBy 中即可。下面的代码会根据 Lastname 升序排列,然后根据 Firstname 降序排列。 - -```java -List readByFirstnameOrLastnameOrderByLastnameAscFirstnameDesc(String first, String last); -``` - -5. 限制结果数量,如 findFirst10ByUsername。 - -**排序和分页查询** - -```java -//分页 -public Page findByUsername(String username, Pageable pageable); - -//排序 -public List findByUsername(String username, Sort sort); -``` - -测试代码: - -```java -@Test -public void testPage() { - Page userPage = userRepository.findByUsername( - "tyson", new PageRequest(0, 10)); - //第0页的十条记录 - List users = userPage.getContent(); - users.forEach(user -> { - System.out.println(user); - }); -} - -@Test -public void testSort() { - List users = userRepository.findByUsername( - "tyson", new Sort(Sort.Direction.ASC, "id")); - users.forEach(user -> { - System.out.println(user); - }); -} -``` - -**使用@Query 查询** - -当所需的数据无法通过方法名描述时,如查找邮箱地址是不是 QQ 邮箱时,使用findQQMailUser方法不符合 Spring Data 方法命名约定,这时我们可以使用@Query 注解,为 Spring Data 提供要执行的查询。 - -```java -@Query("select u from User u where u.username like '%teacher%'") -public List findTeacher(); - -//使用参数索引 -@Query("select u from User u where u.username = ?1") -List findByUsername1(String username); - -//使用命名参数 -@Query("select u from User u where u.username = :username") -List findByUsername2(@Param("username") String username); - -//更新查询,Spring Data JPA支持@Modifying、@Query和@Transactional注解组合做更新查询 -@Modifying -@Transactional -@Query("update User u set u.username=?2 where u.id=?1") -public int setUsername(Long id, String username); -``` - -**Specification** - -有时我们在查询某个实体的时候,给定的条件是不固定的,这时我们就需要动态构建相应的查询语句,在JPA2.0中我们可以通过Criteria接口查询。在Spring data JPA中相应的接口是JpaSpecificationExecutor,这个接口基本是围绕着Specification 复杂查询接口来定义的。 - -(1)定义。接口类需实现 JpaSpecificationExecutor 接口 - -```java -public interface UserRepository extends JpaRepository , JpaSpecificationExecutor { -} -``` - -(2)定义 Criteria 查询(Criteria 查询采用面向对象的方式封装查询条件) - -```java -import com.tyson.domain.User; -import org.springframework.data.jpa.domain.Specification; - -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Predicate; -import javax.persistence.criteria.Root; - -public class UserSpecification { - public static Specification userNamedTyson() { - return new Specification() { - @Override - public Predicate toPredicate(Root root, CriteriaQuery criteriaQuery, CriteriaBuilder criteriaBuilder) { - //假设这是个很复杂的条件 - return criteriaBuilder.equal(root.get("username"), "tyson"); - } - }; - } -} -``` - -使用 Root 俩获得需要查询的属性,通过 CriteriaBuilder 构造条件。CriteriaBuilder 包含的条件有:exists、and、or、isTrue、isNull、greaterThan、greaterThanOrEqualTo、between等。 - -(3)使用。注入 userRepository 的 bean 后: - -```java -@Autowired -public UserRepository userRepository; - -@Test -public void testSpecification() { - List users = userRepository.findAll(userNamedTyson()); - users.forEach(user -> { - System.out.println(user); - }); -} -``` - -#### 混合自定义的功能 - -当所需的功能无法用 Spring Data 的方法命名约定来描述,并且无法用@Query 注解来实现,这时我们只能使用较低层级的 JPA 的方法。 - -当 Spring Data JPA 为 Repository 接口生成实现时,它会查找名字与接口相同,并且添加后缀 Impl 的一个类(本例是 UserRepositoryImpl),如果这个类存在,那么 Spring Data JPA 会将它的方法和 Spring Data JPA 生成的方法合并在一起。 - -UserRepositoryImpl 实现了 MixUserRepository 接口。 - -```java -package com.tyson.db.springdatajpa; - -import org.springframework.data.jpa.repository.Modifying; -import org.springframework.stereotype.Repository; -import org.springframework.transaction.annotation.Transactional; - -import javax.persistence.EntityManager; -import javax.persistence.PersistenceContext; - -public class UserRepositoryImpl implements MixUserRepository { - - @PersistenceContext - EntityManager entityManager; - - /** - * 假设这是个很复杂的更新操作 - * 更新操作需要使用@Modifying 和 @Transactional 注解 - */ - @Override - @Modifying - @Transactional(rollbackFor = Exception.class) - public int updateUser() { - String update = "update User user " + - "set user.username='Bob' " + - "where user.id=1"; - - return entityManager.createQuery(update).executeUpdate(); - } -} -``` - -我们还需要确保 updateUser 方法被声明在 UserRepository 接口中,让 UserRepository 扩展 MixUserRepository。 - -```java -package com.tyson.db.springdatajpa; - -import com.tyson.domain.User; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; - -import java.util.List; - -public interface UserRepository extends JpaRepository , MixUserRepository{ - List findByUsername(String username); - - @Query("select u from User u where u.username like '%teacher%'") - List findTeacher(); -} -``` - -Spring Data 将实现类与接口关联起来是基于接口的名称。Impl 后缀只是默认的做法,如果想到使用其他后缀,只需在配置@EnableJpaReposities 的时候,设置 repositoryImplementationPostfix 属性即可: - -```java -@EnableJpaRepositories(basePackages = {"com.tyson.db.springdatajpa"}, repositoryImplementationPostfix = "Helper") -``` - -如果在 xml 使用 \元素来配置 Spring Data JPA 的话,可以借助 repository-impl-postfix 属性指定后缀: - -``` - - -``` - -设置成 Helper 后缀的话,则 Spring Data JPA 会查找名为 UserRepositoryHelper 的类,用它来匹配 UserRepository 接口。 - - - -## Spring Security - -参考:[Spring Security](https://www.jianshu.com/p/e6655328b211) - -基于 Spring AOP 和 Servlet 规范中的 Filter 实现的安全框架。它能够在 web 请求级别和方法调用级别处理身份认证和授权。Spring Security 使用了依赖注入和面向切面的技术。 - -添加依赖: - -```xml - 3.2.3.RELEASE - - - - org.springframework.security - spring-security-web - ${spring.security.version} - - - - org.springframework.security - spring-security-config - ${spring.security.version} - -``` - -### 基于内存的用户存储 - -```java -package com.tyson.config; - -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; -import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity; - -@Configuration -@EnableWebMvcSecurity -public class SecurityConfig extends WebSecurityConfigurerAdapter { - @Override - protected void configure(AuthenticationManagerBuilder auth) throws Exception { - //添加两个用户,密码都是password,一个角色是user,一个有两个角色user和admin - //roles方法是authorities方法简写形式role("USER")等价于authorities("ROLE_USER") - auth.inMemoryAuthentication(). - withUser("user").password("password").roles("USER").and(). - withUser("admin").password("password").roles("USER", "ADMIN"); - } -} -``` - -### 基于数据库表进行认证 - -```java -@Configuration -@EnableWebMvcSecurity -public class SecurityConfig extends WebSecurityConfigurerAdapter { - @Autowired - DataSource dataSource; - @Override - protected void configure(AuthenticationManagerBuilder auth) throws Exception { - //passwordEncoder方法指定一个密码转换器 - auth.jdbcAuthentication() - .dataSource(dataSource) - .usersByUsernameQuery( - "select username, password from User where username=?" - ).authoritiesByUsernameQuery( - "select username, 'ROLE_USER' from User where username=?" - ).passwordEncoder((new StandardPasswordEncoder("53cr3t"))); - } -} -``` - -### 拦截请求 - -对每个请求进行细粒度安全性控制的关键在于重载configure(HttpSecurity)方法。 - -```java - @Override - protected void configure(HttpSecurity http) throws Exception { - //具体的访问路径放前面,最不具体的访问路径放最后(anyRequest) - http - .authorizeRequests() - .antMatchers("/").authenticated() - .antMatchers("/spitter/**", "/spitter/me").authenticated()//指定多个路径 - .antMatchers(HttpMethod.POST, "/spittles").authenticated().hasRole("SPLITTER")//不仅需要认证,还要有SPLITTER角色 - .anyRequest().permitAll();//其他请求无条件允许访问 - } -``` - -保护请求的配置方法: - -![img](../img/请求保护配置方法.png) - -### 使用Spring表达式进行安全保护 - -上表提供的方法都是一维的,如果使用了hasRole() 方法限制特定角色,就无法使用 hasIpAddress() 限制特定的 ip 地址。借助 access() 和 SpEL 表达式可以实现多维的访问限制: - -```java -.antMatchers("/splitter/me").access("hasRole('ROLE_SPLITTER') and hasIpAddress('192.168.2.1')") -``` - - -### 安全通道 - -requiresChannel()方法会为选定的 URL 强制使用 HTTPS。 - -```java - @Override - protected void configure(HttpSecurity http) throws Exception { - //具体的访问路径放前面,最不具体的访问路径放最后(anyRequest) - http - .authorizeRequests() - .antMatchers("/").authenticated() - .antMatchers("/spitter/**", "/spitter/me").authenticated()//指定多个路径 - .antMatchers(HttpMethod.POST, "/spittles").authenticated().hasRole("SPLITTER")//不仅需要认证,还要有SPLITTER角色 - .anyRequest().permitAll()//其他请求无条件允许访问 - .and() - .requiresChannel() - .antMatchers("/spitter/from").requiresSecure();//需要走HTTPS - } -``` - -有些页面不需要通过 HTTPS 传送,如首页(不包含敏感信息),可以声明始终通过 HTTP 传送。此时尽管通过 https 访问首页,也会重定向到 http 通道。 - -```java -.antMatchers("/").requiresInsecure(); -``` - -### 认证用户 - -1、启动默认的登录页。 - -2、自定义登录页 - -3、拦截/logout请求 - -4、logout成功后重定向到首页 - -5、rememberMe 功能,通过在 cookie 存储 一个 token 实现。 - -```java - http - .formLogin()//1 - .loginPage("/login")//2 - .and() - .logout()//3 - .logoutSuccessUrl("/")//4 - .and() - .rememberMe()//5 - .tokenRepository(new InMemoryTokenRepositoryImpl()) - .tokenValiditySeconds(2419200) - .key("spittrKey") - .and() - .httpBasic()//6 - .realmName("Spittr") - .and() - .authorizeRequests() - .antMatchers("/").authenticated() - .antMatchers("/spitter/me").authenticated() - .antMatchers(HttpMethod.POST, "/spittles").hasRole("SPLITTER") - .anyRequest().permitAll() - .and() - .requiresChannel() - .antMatchers("/spitter/from").requiresSecure()//需要走HTTPS - .antMatchers("/").requiresInsecure(); -``` - -### 方法级别安全 - -Spring Security 使用Spring AOP保护方法调用——借助于对象代理和使用通知,能够确保只有具备适当权限的用户才能访问安全保护的方法。 - -Spring Security提供了三种不同的安全注解: - -- Spring Security自带的@Secured注解; -- JSR-250的@RolesAllowed注解; -- 表达式驱动的注解,包括@PreAuthorize、@PostAuthorize、@PreFilter和@PostFilter。 - -@Secured和@RolesAllowed方案类似,基于用户所授予的权限限制对方法的访问。@PreFilter/@和ostFilter能够过滤方法返回和传入方法的集合。 - -#### @Secured - -要启用基于注解的方法安全性,关键之处在于要在配置类上使用@EnableGlobalMethodSecurity。 - -```java -@Configuration -@EnableGlobalMethodSecurity(securedEnabled=true) -public class SecuredConfig extends GlobalMethodSecurityConfiguration { - - @Override - protected void configure(AuthenticationManagerBuilder auth) throws Exception { - auth - .inMemoryAuthentication() - .withUser("user").password("password").roles("USER"); - } -} -``` - -GlobalMethodSecurityConfiguration 能够为方法级别的安全性提供更精细的配置。securedEnabled 属性的值为true,将会创建一个切点,Spring Security切面就会包装带有@Secured注解的方法。 - -用户必须具备ROLE_SPITTER或ROLE_ADMIN权限才能触发addSpittle: - -```java - @Secured({"ROLE_SPITTER", "ROLE_ADMIN"}) - public void addSpittle(Spittle spittle) { - System.out.println("Method was called successfully"); - } -``` - -如果方法被没有认证的用户或没有所需权限的用户调用,保护这个方法的切面将抛出一个Spring Security异常(非检查型异常)。 - -#### @RolesAllowed - -开启 @RolesAllowed 注解功能: - -```java -@Configuration -@EnableGlobalMethodSecurity(jsr250Enabled=true) -public class JSR250Config extends GlobalMethodSecurityConfiguration { - - @Override - protected void configure(AuthenticationManagerBuilder auth) throws Exception { - auth - .inMemoryAuthentication() - .withUser("user").password("password").roles("USER"); - } - - @Bean - public SpittleService spitterService() { - return new JSR250SpittleService(); - } - -} -``` - -@Secured注解的不足之处在于它是Spring特定的注解。如果更倾向于使用Java标准定义的注解,应该考虑使用@RolesAllowed注解。 - -```java - @RolesAllowed("ROLE_SPITTER") - public void addSpittle(Spittle spittle) { - System.out.println("Method was called successfully"); - } -``` - -#### 使用表达式实现方法级别的安全性 - -开启方法调用前后注解: - -```java -@Configuration -@EnableGlobalMethodSecurity(prePostEnabled=true) -public class ExpressionSecurityConfig extends GlobalMethodSecurityConfiguration { - - @Override - protected void configure(AuthenticationManagerBuilder auth) throws Exception { - auth - .inMemoryAuthentication() - .withUser("user").password("password").roles("USER"); - } - - @Bean - public SpittleService spitterService() { - return new ExpressionSecuredSpittleService(); - } - -} -``` - - - -![方法级别注解](..\img\spring\方法级别注解.png) - -方法调用前检查,`#spittle.text.length()`检查传入方法的参数: - -```java - @PreAuthorize("(hasRole('ROLE_SPITTER') and #spittle.text.length() le 140) or hasRole('ROLE_PREMIUM')") - public void addSpittle(Spittle spittle) { - System.out.println("Method was called successfully"); - } -``` - -进入方法以前过滤输入值: - -```java - @PreAuthorize("(hasRole('ROLE_SPITTER', 'ROLE_ADMIN')") - @PreFilter("hasRole('ROLE_ADMIN')" || "targetObject.spitter.username == principal.name") - public void addSpittle(Spittle spittle) { - } -``` - -@PreFilter注解能够保证传递给deleteSpittles()方法的列表中,只包含当前用户有权限删除的Spittle。targetObject是Spring Security提供的另外一个值,它代表了要进行计算的当前列表元素。 \ No newline at end of file diff --git "a/\346\241\206\346\236\266/Spring\346\200\273\347\273\223.md" "b/\346\241\206\346\236\266/Spring\346\200\273\347\273\223.md" deleted file mode 100644 index cd822bf..0000000 --- "a/\346\241\206\346\236\266/Spring\346\200\273\347\273\223.md" +++ /dev/null @@ -1,471 +0,0 @@ - - - - -- [AOP](#aop) - - [静态代理](#%E9%9D%99%E6%80%81%E4%BB%A3%E7%90%86) - - [动态代理](#%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86) - - [Spring AOP动态代理](#spring-aop%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86) - - [实现原理](#%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86) -- [IOC](#ioc) - - [ioc容器](#ioc%E5%AE%B9%E5%99%A8) - - [BeanDefinition](#beandefinition) - - [容器初始化](#%E5%AE%B9%E5%99%A8%E5%88%9D%E5%A7%8B%E5%8C%96) - - [Bean生命周期](#bean%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F) - - [Aware接口](#aware%E6%8E%A5%E5%8F%A3) - - [BeanFactory和FactoryBean](#beanfactory%E5%92%8Cfactorybean) - - [FactoryBean使用](#factorybean%E4%BD%BF%E7%94%A8) - - [bean注入容器的方法](#bean%E6%B3%A8%E5%85%A5%E5%AE%B9%E5%99%A8%E7%9A%84%E6%96%B9%E6%B3%95) -- [bean的作用域](#bean%E7%9A%84%E4%BD%9C%E7%94%A8%E5%9F%9F) -- [事务](#%E4%BA%8B%E5%8A%A1) - - [@Transactional](#transactional) - - [事务传播行为](#%E4%BA%8B%E5%8A%A1%E4%BC%A0%E6%92%AD%E8%A1%8C%E4%B8%BA) -- [循环依赖](#%E5%BE%AA%E7%8E%AF%E4%BE%9D%E8%B5%96) - - [初始化](#%E5%88%9D%E5%A7%8B%E5%8C%96) - - [三级缓存](#%E4%B8%89%E7%BA%A7%E7%BC%93%E5%AD%98) -- [Spring启动过程](#spring%E5%90%AF%E5%8A%A8%E8%BF%87%E7%A8%8B) -- [Spring Bean线程安全](#spring-bean%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8) - - - -## AOP - -面向切面编程,作为面向对象的一种补充,将公共逻辑(事务管理、日志、缓存等)封装成切面,跟业务代码进行分离,可以减少系统的重复代码和降低模块之间的耦合度。切面就是那些与业务无关,但所有业务模块都会调用的公共逻辑。 - -AOP有两种实现方式:静态代理和动态代理。 - -### 静态代理 - -静态代理:代理类在编译阶段生成,在编译阶段将通知织入Java字节码中,也称编译时增强。AspectJ使用的是静态代理。 - -缺点:代理对象需要与目标对象实现一样的接口,并且实现接口的方法,会有冗余代码。同时,一旦接口增加方法,目标对象与代理对象都要维护。 - -### 动态代理 - -动态代理:代理类在程序运行时创建,AOP框架不会去修改字节码,而是在内存中临时生成一个代理对象,在运行期间对业务方法进行增强,不会生成新类。 - -### Spring AOP动态代理 - -Spring AOP中的动态代理主要有两种方式:JDK动态代理和CGLIB动态代理。 - -1. JDK动态代理(生成的代理类实现了接口)。如果目标类实现了接口,Spring AOP会选择使用JDK动态代理目标类。代理类根据目标类实现的接口动态生成,不需要自己编写,生成的动态代理类和目标类都实现相同的接口。JDK动态代理的核心是`InvocationHandler`接口和`Proxy`类。 - - 缺点:目标类必须有实现的接口。如果某个类没有实现接口,那么这个类就不能用JDK动态代理。 - -2. CGLIB来动态代理(通过继承)。如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library)可以在运行时动态生成类的字节码,动态创建目标类的子类对象,在子类对象中增强目标类。 - - CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为`final`,那么它是无法使用CGLIB做动态代理的。 - - 优点:目标类不需要实现特定的接口,更加灵活。 - -什么时候采用哪种动态代理? - -1. 如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP -2. 如果目标对象实现了接口,可以强制使用CGLIB实现AOP -3. 如果目标对象没有实现了接口,必须采用CGLIB库 - -区别: - -1. jdk动态代理使用jdk中的类Proxy来创建代理对象,它使用反射技术来实现,不需要导入其他依赖。cglib需要引入相关依赖:asm.jar,它使用字节码增强技术来实现。 -2. 当目标类实现了接口的时候Spring Aop默认使用jdk动态代理方式来增强方法,没有实现接口的时候使用cglib动态代理方式增强方法。 - -#### 实现原理 - -[Spring aop实现原理](https://www.zhihu.com/question/23641679) - -Spring会为目标对象生成代理对象。当调用代理对象方法的时候,会触发CglibAopProxy.intercept(),然后将目标对象的增强包装成拦截器,形成拦截器链,最后执行全部拦截器和目标方法。 - - - -## IOC - -[IOC基础](https://juejin.im/entry/588083111b69e60059034e3d#comment) | [IOC demo](https://juejin.im/post/5b399eb1e51d4553156c0525#heading-3) - -IOC:控制反转,由Spring容器管理bean的整个生命周期。通过反射实现对其他对象的控制,包括初始化、创建、销毁等,解放手动创建对象的过程,同时降低类之间的耦合度。 - -IOC的好处:降低了类之间的耦合,对象创建和初始化交给Spring容器管理,在需要的时候只需向容器进行申请。 - -DI(依赖注入):在Spring创建对象的过程中,把对象依赖的属性注入到对象中。有两种方式:构造器注入和属性注入。 - -### ioc容器 - -Spring主要有两种ioc容器,实现了BeanFactory接口的简单容器和ApplicationContext高级容器。 - -- `BeanFactory` :延迟注入(使用到某个 bean 的时候才会注入),相比于`BeanFactory` 来说会占用更少的内存,程序启动速度更快。BeanFactory提供了最基本的ioc容器的功能(最基本的依赖注入支持)。 - -- `ApplicationContext` :容器启动的时候,一次性创建所有 bean 。`ApplicationContext` 扩展了 `BeanFactory` ,除了有`BeanFactory`的功能还有额外更多功能,所以一般开发人员使用`ApplicationContext`会更多。 - -ApplicationContext 提供了 BeanFactory 没有的新特性: - -1. 支持多语言版本; -2. 支持多种途径获取 Bean 定义信息; -3. 支持应用事件,方便管理 Bean; - -DefaultListableBeanFactory 实现了 ioc 容器的基本功能,其他 ioc 容器如 XmlBeanFactory 和 ApplicationContext 都是通过持有或扩展 DefaultListableBeanFactory 获得基本的 ioc 容器的功能。 - -### BeanDefinition - -BeanDefinition 用于管理Spring应用的对象和对象之间的依赖关系,是对象依赖关系的数据抽象。 - -### 容器初始化 - -ioc 容器初始化过程:BeanDefinition 的资源定位、解析和注册。 - -1. 从XML中读取配置文件。 -2. 将bean标签解析成 BeanDefinition,如解析 property 元素, 并注入到 BeanDefinition 实例中。 -3. 将 BeanDefinition 注册到容器 BeanDefinitionMap 中。 -4. BeanFactory 根据 BeanDefinition 的定义信息创建实例化和初始化 bean。 - -单例bean的初始化以及依赖注入一般都在容器初始化阶段进行,只有懒加载(lazy-init为true)的单例bean是在应用第一次调用getBean()时进行初始化和依赖注入。 - -```java -// AbstractApplicationContext -// Instantiate all remaining (non-lazy-init) singletons. -finishBeanFactoryInitialization(beanFactory); -``` - -多例bean 在容器启动时不实例化,即使设置 lazy-init 为 false 也没用,只有调用了getBean()才进行实例化。 - -loadBeanDefinitions 采用了模板模式,具体加载 BeanDefinition 的逻辑由各个子类完成。 - -### Bean生命周期 - -![](../img/bean-life-cycle.jpg) - -1.对Bean进行实例化 - -2.依赖注入 - -3.如果Bean实现了BeanNameAware接口,Spring将调用setBeanName(),设置 Bean id(xml文件中bean标签的id) - -4.如果Bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory() - -5.如果Bean实现了ApplicationContextAware接口,Spring容器将调用setApplicationContext() - -6.如果存在BeanPostProcessor,Spring将调用它们的postProcessBeforeInitialization(预初始化)方法,在Bean初始化前对其进行处理 - -7.如果Bean实现了InitializingBean接口,Spring将调用它的afterPropertiesSet方法,然后调用xml定义的 init-method 方法,两个方法作用类似,都是在初始化 bean 的时候执行 - -8.如果存在BeanPostProcessor,Spring将调用它们的postProcessAfterInitialization(后初始化)方法,在Bean初始化后对其进行处理 - -9.Bean初始化完成,供应用使用,直到应用被销毁 - -10.如果Bean实现了DisposableBean接口,Spring将调用它的destory方法,然后调用在xml中定义的 destory-method 方法,这两个方法作用类似,都是在Bean实例销毁前执行。 - -```java -public interface BeanPostProcessor { - @Nullable - default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { - return bean; - } - - @Nullable - default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { - return bean; - } - -} - -public interface InitializingBean { - void afterPropertiesSet() throws Exception; -} -``` - -### Aware接口 - -对于应用程序来说,应该尽量减少对Sping Api的耦合程度,然而有些时候为了运用Spring所提供的一些功能,有必要让Bean了解Spring容器对其进行管理的细节信息,如让Bean知道在容器中是以那个名称被管理的,或者让Bean知道BeanFactory或者ApplicationContext的存在,也就是让该Bean可以取得BeanFactory或者ApplicationContext的实例,如果Bean可以意识到这些对象,那么就可以在Bean的某些动作发生时,做一些如事件发布等操作。 - -BeanNameAware:通过调用setBeanName()可以让bean获取自身的id属性。 - -ApplicationContextAware:通过调用 setApplicationContext() 设置应用上下文实例,从而可以直接在 Bean 中使用应用上下文的服务。 - -ApplicationEventPublisherAware:通过调用 setApplicationEventPublisher() 给 Bean 设置事件发布器,从而可以在 Bean 中发布应用上下文的事件。 - -### BeanFactory和FactoryBean - -BeanFactory:管理Bean的容器,Spring中生成的Bean都是由这个接口的实现来管理的。 - -FactoryBean:通常是用来创建比较复杂的bean,一般的bean 直接用xml配置即可,但如果一个bean的创建过程中涉及到很多其他的bean 和复杂的逻辑,直接用xml配置比较麻烦,这时可以考虑用FactoryBean,可以隐藏实例化复杂Bean的细节。 - -当配置文件中bean标签的class属性配置的实现类是FactoryBean时,通过 getBean()方法返回的不是FactoryBean本身,而是调用FactoryBean#getObject()方法所返回的对象,相当于FactoryBean#getObject()代理了getBean()方法。如果想得到FactoryBean必须使用 '&' + beanName 的方式获取。 - -Mybatis 提供了 SqlSessionFactoryBean,可以简化 SqlSessionFactory 的配置: - -```java -public class SqlSessionFactoryBean implements FactoryBean, InitializingBean, ApplicationListener { - - @Override - public void afterPropertiesSet() throws Exception { - notNull(dataSource, "Property 'dataSource' is required"); - notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required"); - state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null), - "Property 'configuration' and 'configLocation' can not specified with together"); - - this.sqlSessionFactory = buildSqlSessionFactory(); - } - - protected SqlSessionFactory buildSqlSessionFactory() throws IOException { - //复杂逻辑 - } - - @Override - public SqlSessionFactory getObject() throws Exception { - if (this.sqlSessionFactory == null) { - afterPropertiesSet(); - } - - return this.sqlSessionFactory; - } -} -``` - -在 xml 配置 SqlSessionFactoryBean: - -```xml - - - - - - -``` - -Spring 将会在应用启动时创建 `SqlSessionFactory`,并使用 `sqlSessionFactory` 这个名字存储起来。 - -#### FactoryBean使用 - -[FactoryBean使用](https://www.cnblogs.com/davidwang456/p/3688250.html) - -如果使用传统方式配置下面Car的\时,Car的每个属性分别对应一个\元素标签。 - -```java -public class Car { - private int maxSpeed ; - private String brand ; - private double price ; -} -``` - -如果用FactoryBean的方式实现就会灵活一些,下例通过逗号分割符的方式一次性地为Car的所有属性指定配置值: - -```java -public class CarFactoryBean implements FactoryBean { - private String carInfo ; - public Car getObject () throws Exception { - Car car = new Car () ; - String [] infos = carInfo .split ( "," ) ; - car.setBrand ( infos [ 0 ]) ; - car.setMaxSpeed ( Integer. valueOf ( infos [ 1 ])) ; - car.setPrice ( Double. valueOf ( infos [ 2 ])) ; - return car; - } - public Class getObjectType () { - return Car. class ; - } - public boolean isSingleton () { - return false ; - } - public String getCarInfo () { - return this . carInfo ; - } - - // 接受逗号分割符设置属性信息 - public void setCarInfo ( String carInfo ) { - this . carInfocarInfo = carInfo; - } -} -``` - -xml 配置 CarFactoryBean: - -```xml - -``` - -当调用getBean("car") 时,Spring通过反射机制发现CarFactoryBean实现了FactoryBean的接口,这时Spring容器就调用接口方法CarFactoryBean#getObject()方法返回。如果希望获取CarFactoryBean的实例,则需要在使getBean(beanName) 方法时在beanName前显示的加上 "&" 前缀,例如getBean("&car")。 - -### bean注入容器的方法 - -将普通类交给Spring容器管理,通常有以下方法: - -1、使用 @Configuration与@Bean 注解 - -2、使用@Controller @Service @Repository @Component 注解标注该类,然后启用@ComponentScan自动扫描 - -3、使用@Import 方法 - -@Import注解把bean导入到当前容器中。 - -```java -//@SpringBootApplication -@ComponentScan -/*把用到的资源导入到当前容器中*/ -@Import({Dog.class, Cat.class}) -public class App { - - public static void main(String[] args) throws Exception { - - ConfigurableApplicationContext context = SpringApplication.run(App.class, args); - System.out.println(context.getBean(Dog.class)); - System.out.println(context.getBean(Cat.class)); - context.close(); - } -} - -``` - - - -## bean的作用域 - -Spring创建bean默认是单例,每一个Bean的实例只会被创建一次,通过getBean()获取的是同一个Bean的实例。可使用<bean>标签的scope属性来指定一个Bean的作用域。 - -```xml - - -``` - -通过注解来声明作用域: - -```java -@Scope("prototype") -public class AccountDaoImpl { - //...... -} -``` - -容器在创建完一个prototype实例后,就不会去管理这个bean了,会把它交给应用自己去管理。 - -一般情况下,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。所谓有状态就是该bean保存有自己的信息,不能共享,否则会造成线程安全问题。而无状态则不保存信息,可以共享,spring中大部分bean都是单例的,整个生命周期过程只会存在一个。 - -request作用域:对于每次HTTP请求到达应用程序,Spring容器会创建一个全新的Request作用域的bean实例,且该bean实例仅在当前HTTP request内有效,整个请求过程也只会使用相同的bean实例,而其他请求HTTP请求则创建新bean的实例,当处理请求结束,request作用域的bean实例将被销毁。 - -session 作用域:每当创建一个新的HTTP Session时就会创建一个Session作用域的Bean,并该实例bean伴随着会话的结束(session过期)而销毁。 - - - -## 事务 - -事务就是一系列的操作原子执行。 - -Spring事务机制主要包括声明式事务和编程式事务。 - -声明式事务将事务管理代码从业务方法中分离出来,通过aop进行封装。用 @Transactional 注解开启声明式事务。 - -Spring声明式事务使得我们无需要去处理获得连接、关闭连接、事务提交和回滚等这些操作。 - -### @Transactional - -| 属性 | 类型 | 描述 | -| :--------------------- | ---------------------------------- | -------------------------------------- | -| value | String | 可选的限定描述符,指定使用的事务管理器 | -| propagation | enum: Propagation | 可选的事务传播行为设置 | -| isolation | enum: Isolation | 可选的事务隔离级别设置 | -| readOnly | boolean | 读写或只读事务,默认读写 | -| timeout | int (in seconds granularity) | 事务超时时间设置 | -| rollbackFor | Class对象数组,必须继承自Throwable | 导致事务回滚的异常类数组 | -| rollbackForClassName | 类名数组,必须继承自Throwable | 导致事务回滚的异常类名字数组 | -| noRollbackFor | Class对象数组,必须继承自Throwable | 不会导致事务回滚的异常类数组 | -| noRollbackForClassName | 类名数组,必须继承自Throwable | 不会导致事务回滚的异常类名字数组 | - -### 事务传播行为 - -在TransactionDefinition接口中定义了七个事务传播行为: - -1. PROPAGATION_REQUIRED 如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。如果嵌套调用的两个方法都加了事务注解,并且运行在相同线程中,则这两个方法使用相同的事务中。如果运行在不同线程中,则会开启新的事务。 -2. PROPAGATION_SUPPORTS 如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。 -3. PROPAGATION_MANDATORY 如果已经存在一个事务,支持当前事务。如果不存在事务,则抛出异常`IllegalTransactionStateException`。 -4. PROPAGATION_REQUIRES_NEW 总是开启一个新的事务。需要使用JtaTransactionManager作为事务管理器。 -5. PROPAGATION_NOT_SUPPORTED 总是非事务地执行,并挂起任何存在的事务。需要使用JtaTransactionManager作为事务管理器。 -6. PROPAGATION_NEVER 总是非事务地执行,如果存在一个活动事务,则抛出异常。 -7. PROPAGATION_NESTED 如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务, 则按PROPAGATION_REQUIRED 属性执行。 - -**PROPAGATION_NESTED 与PROPAGATION_REQUIRES_NEW的区别:** - -使用PROPAGATION_REQUIRES_NEW时,内层事务与外层事务是两个独立的事务。一旦内层事务进行了提交后,外层事务不能对其进行回滚。两个事务互不影响。 - -使用PROPAGATION_NESTED时,外层事务的回滚可以引起内层事务的回滚。而内层事务的异常并不会导致外层事务的回滚,它是一个真正的嵌套事务。 - - - -## 循环依赖 - -[图解spring循环依赖](https://juejin.im/post/5e927e27f265da47c8012ed9#heading-5) | [循环依赖](https://blog.csdn.net/u010853261/article/details/77940767) - -构造器注入的循环依赖:spring处理不了,直接抛出BeanCurrentlylnCreationException异常。 - -单例模式下属性注入的循环依赖:通过三级缓存处理循环依赖。 - -非单例循环依赖:无法处理。 - -### 初始化 - -spring单例对象的初始化大略分为三步: - -1. createBeanInstance:实例化bean,使用构造方法创建对象,为对象分配内存。 -2. populateBean:进行依赖注入。 -3. initializeBean:初始化bean。 - -this.singletonsCurrentlyInCreation.add(String beanName)将当前正在创建的bean id记录在缓存中,如果在记录的过程中发现自己已经在缓存中,则说明存在循环依赖,将抛出BeanCurrentlylnCreationException 异常表示循环依赖。创建完成的bean将会从缓存中清除。 - -### 三级缓存 - -Spring为了解决单例的循环依赖问题,使用了三级缓存。 - -singletonObjects:完成了初始化的单例对象map,bean name --> bean instance - -earlySingletonObjects :完成实例化未初始化的单例对象map,bean name --> bean instance - -singletonFactories : 单例对象工厂map,bean name --> ObjectFactory,单例对象实例化完成之后会加入singletonFactories。 - -在调用createBeanInstance进行实例化之后,会调用addSingletonFactory,将单例对象放到singletonFactories中。 - -```java -protected void addSingletonFactory(String beanName, ObjectFactory singletonFactory) { - Assert.notNull(singletonFactory, "Singleton factory must not be null"); - synchronized (this.singletonObjects) { - if (!this.singletonObjects.containsKey(beanName)) { - this.singletonFactories.put(beanName, singletonFactory); - this.earlySingletonObjects.remove(beanName); - this.registeredSingletons.add(beanName); - } - } -} -``` - -假如A依赖了B的实例对象,同时B也依赖A的实例对象。 - -1. A首先完成了实例化,并且将自己添加到singletonFactories中 -2. 接着进行依赖注入,发现自己依赖对象B,此时就尝试去get(B) -3. 发现B还没有被实例化,对B进行实例化 -4. 然后B在初始化的时候发现自己依赖了对象A,于是尝试get(A),尝试一级缓存singletonObjects和二级缓存earlySingletonObjects没找到,尝试三级缓存singletonFactories,由于A初始化时将自己添加到了singletonFactories,所以B可以拿到A对象,然后将A从三级缓存中移到二级缓存中 -5. B拿到A对象后顺利完成了初始化,然后将自己放入到一级缓存singletonObjects中 -6. 此时返回A中,A此时能拿到B的对象顺利完成自己的初始化 - -由此看出,属性注入的循环依赖主要是通过将实例化完成的bean添加到singletonFactories来实现的。而使用构造器依赖注入的bean在实例化的时候会进行依赖注入,不会被添加到singletonFactories中。比如A和B都是通过构造器依赖注入,A在调用构造器进行实例化的时候,发现自己依赖B,B没有被实例化,就会对B进行实例化,此时A未实例化完成,不会被添加到singtonFactories。而B依赖于A,B会去三级缓存寻找A对象,发现不存在,于是又会实例化A,A实例化了两次,从而导致抛异常。 - -总结:1、利用缓存识别已经遍历过的节点; 2、利用Java引用,先提前设置对象地址,后完善对象。 - - - -## Spring启动过程 - -1. 读取web.xml文件。 - -2. 创建 ServletContext,为 ioc 容器提供宿主环境。 - -3. 触发容器初始化事件,调用 contextLoaderListener.contextInitialized()方法,在这个方法会初始化一个应用上下文WebApplicationContext,即 Spring 的 ioc 容器。ioc 容器初始化完成之后,会被存储到 ServletContext 中。 - - ```java - public void contextInitialized(ServletContextEvent event) { - initWebApplicationContext(event.getServletContext()); - } - ``` - -4. 初始化web.xml中配置的Servlet。如DispatcherServlet,用于匹配、处理每个servlet请求。 - - - -## Spring Bean线程安全 - -Spring Bean默认是单例的,大部分的Spring bean没有可变的状态(比如Service类和DAO类),是线程安全的。如果Bean带有状态,可以将bean设置为prototype或者使用ThreadLocal确保线程安全。 \ No newline at end of file diff --git "a/\346\241\206\346\236\266/Spring\347\224\250\345\210\260\345\223\252\344\272\233\350\256\276\350\256\241\346\250\241\345\274\217.md" "b/\346\241\206\346\236\266/Spring\347\224\250\345\210\260\345\223\252\344\272\233\350\256\276\350\256\241\346\250\241\345\274\217.md" deleted file mode 100644 index dc0aacd..0000000 --- "a/\346\241\206\346\236\266/Spring\347\224\250\345\210\260\345\223\252\344\272\233\350\256\276\350\256\241\346\250\241\345\274\217.md" +++ /dev/null @@ -1,67 +0,0 @@ - - -- 简单工厂:BeanFactory 就是简单工厂模式的体现,根据传入一个唯一标识来获得 Bean 对象。 - - ```java - @Override - public Object getBean(String name) throws BeansException { - assertBeanFactoryActive(); - return getBeanFactory().getBean(name); - } - ``` - -- 工厂方法:FactoryBean 就是典型的工厂方法模式。spring在使用getBean()调用获得该bean时,会自动调用该bean的getObject()方法。每个 Bean 都会对应一个 FactoryBean,如 SqlSessionFactory 对应 SqlSessionFactoryBean。 - -- 单例:一个类仅有一个实例,提供一个访问它的全局访问点。Spring 创建 Bean 实例默认是单例的。 - -- 适配器:SpringMVC中的适配器HandlerAdatper。由于应用会有多个Controller实现,如果需要直接调用Controller方法,那么需要先判断是由哪一个Controller处理请求,然后调用相应的方法。当增加新的 Controller,需要修改原来的逻辑,违反了开闭原则(对修改关闭,对扩展开放)。 - - ```java - if(mappedHandler.getHandler() instanceof MultiActionController){ - ((MultiActionController)mappedHandler.getHandler()).xxx - }else if(mappedHandler.getHandler() instanceof XXX){ - ... - }else if(...){ - ... - } - ``` - - 为此,Spring提供了一个适配器接口,每一种 Controller 对应一种 HandlerAdapter 实现类,当请求过来,SpringMVC会调用getHandler()获取相应的Controller,然后获取该Controller对应的 HandlerAdapter,最后调用HandlerAdapter的handle()方法处理请求,实际上调用的是Controller的handleRequest()。每次添加新的 Controller 时,只需要增加一个适配器类就可以,无需修改原有的逻辑。 - - 常用的处理器适配器:SimpleControllerHandlerAdapter,HttpRequestHandlerAdapter,AnnotationMethodHandlerAdapter。 - - ```java - // Determine handler for the current request. - mappedHandler = getHandler(processedRequest); - - HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); - - // Actually invoke the handler. - mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); - - public class HttpRequestHandlerAdapter implements HandlerAdapter { - - @Override - public boolean supports(Object handler) {//handler是被适配的对象,这里使用的是对象的适配器模式 - return (handler instanceof HttpRequestHandler); - } - - @Override - @Nullable - public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) - throws Exception { - - ((HttpRequestHandler) handler).handleRequest(request, response); - return null; - } - ``` - -- 代理:spring 的 aop 使用了动态代理,有两种方式JdkDynamicAopProxy 和Cglib2AopProxy。 - -- 观察者(observer):spring 中 observer 模式常用的地方是 listener 的实现,如ApplicationListener。 - -- 模板方法(template method):spring 中的 jdbctemplate。 - - - -> 参考链接:[spring设计模式](https://blog.csdn.net/caoxiaohong1005/article/details/80039656) | [SpringMVC适配器模式](https://blog.csdn.net/u010288264/article/details/53835185) \ No newline at end of file diff --git "a/\346\241\206\346\236\266/Spring\350\207\252\345\212\250\350\243\205\351\205\215.md" "b/\346\241\206\346\236\266/Spring\350\207\252\345\212\250\350\243\205\351\205\215.md" deleted file mode 100644 index 968c78c..0000000 --- "a/\346\241\206\346\236\266/Spring\350\207\252\345\212\250\350\243\205\351\205\215.md" +++ /dev/null @@ -1,187 +0,0 @@ - - -**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* - -- [byType](#bytype) -- [byName](#byname) -- [constructor](#constructor) -- [@Autowired和@Resource](#autowired%E5%92%8Cresource) -- [声明Bean注解](#%E5%A3%B0%E6%98%8Ebean%E6%B3%A8%E8%A7%A3) -- [@Bean和@Component](#bean%E5%92%8Ccomponent) - - - -Spring的自动装配有三种模式:byType(根据类型),byName(根据名称)、constructor(根据构造函数)。 - -## byType - -找到与依赖类型相同的bean注入到另外的bean中,这个过程需要借助setter注入来完成,因此必须存在set方法,否则注入失败。 - -当xml文件中存在多个相同类型名称不同的实例Bean时,Spring容器依赖注入仍然会失败,因为存在多种适合的选项,Spring容器无法知道该注入那种,此时我们需要为Spring容器提供帮助,指定注入那个Bean实例。可以通过<bean>标签的autowire-candidate设置为false来过滤那些不需要注入的实例Bean - -```xml - - - - - - - -``` - -## byName - -将属性名与bean名称进行匹配,如果找到则注入依赖bean。 - -```xml - - - - - -``` - -## constructor - -存在单个实例则优先按类型进行参数匹配(无论名称是否匹配),当存在多个类型相同实例时,按名称优先匹配,如果没有找到对应名称,则注入失败,此时可以使用autowire-candidate=”false” 过滤来解决。 - -```xml - - - - - - -``` - -@Autowired 可以传递了一个required=false的属性,false指明当userDao实例存在就注入不存就忽略,如果为true,就必须注入,若userDao实例不存在,就抛出异常。由于默认情况下@Autowired是按类型匹配的(byType),如果需要按名称(byName)匹配的话,可以使用@Qualifier注解与@Autowired结合。 - -```java -public class UserServiceImpl implements UserService { - //标注成员变量 - @Autowired - @Qualifier("userDao1") - private UserDao userDao; - } -``` - -byName模式 xml 配置: - -```xml - - - - - -``` - -@Resource,默认按 byName模式自动注入。@Resource有两个中重要的属性:name和type。Spring容器对于@Resource注解的name属性解析为bean的名字,type属性则解析为bean的类型。因此使用name属性,则按byName模式的自动注入策略,如果使用type属性则按 byType模式自动注入策略。倘若既不指定name也不指定type属性,Spring容器将通过反射技术默认按byName模式注入。 - -```java -@Resource(name=“userDao”) -private UserDao userDao;//用于成员变量 - -//也可以用于set方法标注 -@Resource(name=“userDao”) -public void setUserDao(UserDao userDao) { - this.userDao= userDao; -} -``` - -上述两种自动装配的依赖注入并不适合简单值类型,如int、boolean、long、String以及Enum等,对于这些类型,Spring容器也提供了@Value注入的方式。@Value接收一个String的值,该值指定了将要被注入到内置的java类型属性值,Spring 容器会做好类型转换。一般情况下@Value会与properties文件结合使用。 - -jdbc.properties文件如下: - -```properties -jdbc.driver=com.mysql.jdbc.Driver -jdbc.url=jdbc:mysql://127.0.0.1:3306/test?characterEncoding=UTF-8&allowMultiQueries=true -jdbc.username=root -jdbc.password=root -``` - -利用注解@Value获取jdbc.url和jdbc.username的值,实现如下: - -```java -public class UserServiceImpl implements UserService { - //占位符方式 - @Value("${jdbc.url}") - private String url; - //SpEL表达方式,其中代表xml配置文件中的id值configProperties - @Value("#{configProperties['jdbc.username']}") - private String userName; - -} -``` - -xml配置文件: - -```xml - - - - - - - - - - - - classpath:/conf/jdbc.properties - - - -``` - -## @Autowired和@Resource - -@Autowired注解是按照类型(byType)装配依赖对象的,但是存在多个类型⼀致的bean,⽆法通过byType注⼊时,就会再使⽤byName来注⼊,如果还是⽆法判断注⼊哪个bean则会UnsatisfiedDependencyException。 -@Resource会⾸先按照byName来装配,如果找不到bean,会⾃动byType再找⼀次。 - -## 声明Bean注解 - -Spring 容器通过xml的bean标签配置和java注解两种方式声明的Bean对象。Spring的框架中提供了与@Component注解等效的用于声明bean的三个注解,@Repository 用于对DAO实现类进行标注,@Service 用于对Service实现类进行标注,@Controller 用于对Controller实现类进行标注。同时还可以给定一个bean名称,如果没有提供名称,那么默认情况下就是一个简单的类名(第一个字符小写)变成Bean名称。 - -```java -//@Component 相同效果 -@Service -public class AccountServiceImpl implements AccountService { - @Autowired - private AccountDao accountDao; -} -``` - -xml配置 bean: - -```xml - -``` - -## @Bean和@Component - -都是使用注解定义 Bean。@Bean 是使用 Java 代码装配 Bean,@Component 是自动装配 Bean。 - -@Component 注解用在类上,表明一个类会作为组件类,并告知Spring要为这个类创建bean,每个类对应一个 Bean。 - -@Bean 注解用在方法上,表示这个方法会返回一个 Bean。@Bean 需要在配置类中使用,即类上需要加上@Configuration注解。 - -```java -@Component -public class Student { - private String name = "lkm"; - - public String getName() { - return name; - } -} - -@Configuration -public class WebSocketConfig { - @Bean - public Student student(){ - return new Student(); - } -} -``` - -@Bean 注解更加灵活。当需要将第三方类装配到 Spring 容器中,因为没办法源代码上添加@Component注解,只能使用@Bean 注解的方式,当然也可以使用 xml 的方式。 \ No newline at end of file diff --git "a/\346\241\206\346\236\266/\346\267\261\345\205\245\346\265\205\345\207\272Mybatis\346\212\200\346\234\257\345\216\237\347\220\206\344\270\216\345\256\236\346\210\230.md" "b/\346\241\206\346\236\266/\346\267\261\345\205\245\346\265\205\345\207\272Mybatis\346\212\200\346\234\257\345\216\237\347\220\206\344\270\216\345\256\236\346\210\230.md" deleted file mode 100644 index bc1d6ca..0000000 --- "a/\346\241\206\346\236\266/\346\267\261\345\205\245\346\265\205\345\207\272Mybatis\346\212\200\346\234\257\345\216\237\347\220\206\344\270\216\345\256\236\346\210\230.md" +++ /dev/null @@ -1,2435 +0,0 @@ -## Mybatis简介 - -JDBC定义了连接数据库的接口规范,每个数据库厂商都会提供具体的实现,JDBC是一种典型的桥接模式。 - -### 传统的JDBC编程 - -- 获取数据库连接; -- 操作Connection,打开Statement对象; -- 通过Statement对象执行SQL,返回结果到ResultSet对象; -- 关闭数据库资源 - -```java -public class javaTest { - public static void main(String[] args) throws ClassNotFoundException, SQLException { - String URL="jdbc:mysql://127.0.0.1:3306/imooc?useUnicode=true&characterEncoding=utf-8"; - String USER="root"; - String PASSWORD="tiger"; - //1.加载驱动程序 - Class.forName("com.mysql.jdbc.Driver"); - //2.获得数据库链接 - Connection conn=DriverManager.getConnection(URL, USER, PASSWORD); - //3.通过数据库的连接操作数据库,实现增删改查(使用Statement类) - Statement st=conn.createStatement(); - ResultSet rs=st.executeQuery("select * from user"); - //4.处理数据库的返回结果(使用ResultSet类) - while(rs.next()){ - System.out.println(rs.getString("user_name")+" " - +rs.getString("user_password")); - } - - //关闭资源 - rs.close(); - st.close(); - conn.close(); - } -} -``` - -### Hibernate与Mybatis - -Hibernate建立在POJO和数据库表模型的直接映射关系上。通过POJO我们可以直接操作数据库的数据。相对而言,Hibernate对JDBC的封装程度比较高,我们不需要编写SQL,直接通过HQL去操作POJO进而操作数据库的数据。 - -Mybatis是半自动映射的orm框架,它需要我们提供POJO,SQL和映射关系,而全表映射的Hibernate只需要提供POJO和映射关系。 - -Hibernate编程简单,需要我们提供映射的规则,完全可以通过IDE实现,同时无需编写SQL,开发效率优于Mybatis。此外,它提供缓存、级联、日志等强大的功能, - -Hibernate与Mybatis区别: - -1. Hibernate是全自动,而Mybatis是半自动。 - Hibernate是全表映射,可以通过对象关系模型实现对数据库的操作,拥有完整的JavaBean对象与数据库的映射结构来自动生成sql。而Mybatis仅有基本的字段映射,对象数据以及对象实际关系仍然需要通过手写sql来实现和管理。 - -2. Hibernate数据库移植性较好。 - Hibernate通过它强大的映射结构和hql语言,大大降低了对象与数据库的耦合性,而Mybatis由于需要手写sql,因此与数据库的耦合性直接取决于程序员写sql的方法,如果sql不具通用性而用了很多某数据库特性的sql语句的话,移植性也会随之降低很多,成本很高。 -3. Hibernate拥有完整的日志系统,Mybatis则欠缺一些。 - Hibernate日志系统非常健全,涉及广泛,包括:sql记录、关系异常、优化警告、缓存提示、脏数据警告等;而Mybatis则除了基本记录功能外,功能薄弱很多。 -4. sql直接优化上,Mybatis要比Hibernate方便很多。 - 由于Mybatis的sql都是写在xml里,因此优化sql比Hibernate方便很多,解除了sql与代码的耦合。而Hibernate的sql很多都是自动生成的,无法直接维护sql;写sql的灵活度上Hibernate不及Mybatis。 -5. Mybatis提供xml标签,支持编写动态sql。 - -## Mybatis入门 - -### SqlSessionFactory - -使用xml构建SqlSessionFactory,配置信息在mybatis-config.xml - -```xml - - - - - - - - - - - - - - - - - - - - - - - - - - -``` - -```java -import org.apache.ibatis.io.Resources; -import org.apache.ibatis.session.SqlSession; -import org.apache.ibatis.session.SqlSessionFactory; -import org.apache.ibatis.session.SqlSessionFactoryBuilder; - -import java.io.IOException; -import java.io.InputStream; - -public class SqlSessionFactoryUtil { - private static SqlSessionFactory sqlSessionFactory = null; - //类线程锁 - private static final Class CLASS_LOCK = SqlSessionFactoryUtil.class; - - /** - * 私有化构造器 - */ - private SqlSessionFactoryUtil() {} - - public static SqlSessionFactory initSqlSessionFactory() { - String resource = "mybatis-config.xml"; - InputStream inputStream = null; - try { - inputStream = Resources.getResourceAsStream(resource); - } catch (IOException ex) { - ex.printStackTrace(); - } - synchronized (CLASS_LOCK) { - if(sqlSessionFactory == null) { - sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); - } - } - return sqlSessionFactory; - } - - public static SqlSession openSqlSession() { - if(sqlSessionFactory == null) { - initSqlSessionFactory(); - } - - return sqlSessionFactory.openSession(); - } -} -``` - -### SqlSession - -SqlSession是接口类,扮演门面的作用,真正处理数据的是Executor接口。在Mybatis中SqlSession的实现类有DefaultSqlSession和SqlSessionManager。 - - - -### 映射器Mapper - -映射器的实现方式有两种:通过xml方式实现,在mybatis-config.xml中定义Mapper;通过代码方式实现,在Configuration里面注册Mapper接口。建议使用xml配置方式,这种方式比较灵活,尤其当SQL语句很复杂时。 - -xml文件配置方式实现Mapper - -```java -public interface RoleMapper { - public Role getRole(@Param("id") Long id); -} -``` - -映射xml文件RoleMapper.xml - -```xml - - - - - - - -``` - -测试类 - -```java -import com.tyson.mapper.RoleMapper; -import com.tyson.pojo.Role; -import com.tyson.util.SqlSessionFactoryUtil; -import lombok.extern.slf4j.Slf4j; -import org.apache.ibatis.session.SqlSession; -import org.junit.Test; - -@Slf4j -public class RoleTest { - @Test - public void findRoleTest() { - SqlSession sqlSession = null; - try { - sqlSession = SqlSessionFactoryUtil.openSqlSession(); - RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class); - Role role = roleMapper.getRole(1L); - if(role != null) { - log.info(role.toString()); - } - } catch (Exception e) { - e.printStackTrace(); - } finally { - if (sqlSession != null) { - sqlSession.close(); - } - } - } -} -``` - -Java方式实现Mapper - -```java -import com.tyson.pojo.Role; -import org.apache.ibatis.annotations.Select; - -public interface RoleMapper1 { - @Select(value="SELECT id, role_name as roleName, note from role where id = #{id}") - public Role getRole(@Param("id") Long id); -} -``` - -在Mybatis全局配置文件注册Mapper,有三种方式。 - -```xml - - - - - - - - - - - - - - - -``` - -### Mybatis组件的生命周期 - -1. SqlSessionFactoryBuilder - -作用是生成SqlSessionFactory,构建完毕则作用完结,生命周期只存在于方法的局部。 - -2. SqlSessionFactory - -创建SqlSession,每次访问数据库都需要通过SqlSessionFactory创建SqlSession。故SqlSessionFactory应存在于Mybatis应用的整个生命周期。 - -3. SqlSession - -会话,相当于JDBC的Connection对象,生命周期为请求数据库处理事务的过程。 - -4. Mapper - -作用是发送SQL,返回结果或执行SLQ修改数据库数据,它的生命周期在一个SqlSession事务方法之内。其最大的作用范围和SqlSession相同。 - -![Mybatis组件的生命周期](https://img-blog.csdnimg.cn/20190125180820866.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1R5c29uMDMxNA==,size_16,color_FFFFFF,t_70) - -## 配置 - -Mybatis全局配置文件mybatis-config.xml的层次结构顺序不能颠倒,否则在解析xml文件会产生异常。 - -```java - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -``` - -### properties - -```xml - -``` - -```properties -driver=com.mysql.jdbc.Driver -url=jdbc:mysql://localhost:3306/mybatisdemo?serverTimezone=UTC -username=root -password=ad1234 -``` - -### typeAliases - -```xml - - - - - - -``` - -### typeHandler - -参考自:[关于mybatis中typeHandler的两个案例](https://blog.csdn.net/u012702547/article/details/54572679) - -类型处理器,作用是将参数从javaType转化为jdbcType,或者从数据库取出结果时把jdbcType转化成javaType。typeHandler和别名一样,分为系统定义和用户自定义。Mybatis系统定义的typeHandler就可以实现大部分的功能。 - -```java -public TypeHandlerRegistry() { - this.register((Class)Boolean.class, (TypeHandler)(new BooleanTypeHandler())); - this.register((Class)Boolean.TYPE, (TypeHandler)(new BooleanTypeHandler())); - this.register((JdbcType)JdbcType.BOOLEAN, (TypeHandler)(new BooleanTypeHandler())); - this.register((JdbcType)JdbcType.BIT, (TypeHandler)(new BooleanTypeHandler())); - this.register((Class)Byte.class, (TypeHandler)(new ByteTypeHandler())); - this.register((Class)Byte.TYPE, (TypeHandler)(new ByteTypeHandler())); - ...... -} -``` - -#### 自定义typeHandler - -假如需要将日期以字符串格式(转化成毫秒数)写进数据库,此时可以通过自定义typeHandler来实现此功能。 - -role表 - -```my -CREATE TABLE `role` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `role_name` varchar(20) DEFAULT NULL, - `note` varchar(20) DEFAULT NULL, - `reg_time` varchar(64) DEFAULT NULL, - `users` varchar(64) DEFAULT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=latin1 -``` - -Role实体类 - -```java -import java.util.Date; -import java.util.List; - -public class Role { - private Long id; - private String roleName; - private String note; - private Date regTime; - - //setter和getter - - @Override - public String toString() { - return "id: " + id + ", roleName: " + roleName + ", note: " + note + ", regTime: " + regTime; //+ ", users: " + users.get(0); - } -} -``` - -1. 首先定义TypeHandler实现类(或继承BaseTypeHandler,BaseTypeHandler是TypeHandler的实现类)。在setNonNullParameter方法中,我们重新定义要写往数据库的数据。 在另外三个方法中我们将从数据库读出的数据进行类型转换。 - -```java -import lombok.extern.slf4j.Slf4j; -import org.apache.ibatis.type.BaseTypeHandler; -import org.apache.ibatis.type.JdbcType; -import org.apache.ibatis.type.MappedJdbcTypes; -import org.apache.ibatis.type.MappedTypes; - -import java.sql.CallableStatement; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.Date; - -@Slf4j -@MappedJdbcTypes({JdbcType.VARCHAR}) -@MappedTypes({Date.class}) -public class MyDateTypeHandler extends BaseTypeHandler { - - @Override - public void setNonNullParameter(PreparedStatement preparedStatement, int i, Date date, JdbcType jdbcType) throws SQLException { - log.info("预编译语句设置参数: " + date.toString()); - preparedStatement.setString(i, String.valueOf(date.getTime())); - } - - @Override - public Date getNullableResult(ResultSet resultSet, String s) throws SQLException { - log.info("由列名 " + s + " 获取字符串:" + resultSet.getLong(s)); - return new Date(resultSet.getLong(s)); - } - - @Override - public Date getNullableResult(ResultSet resultSet, int i) throws SQLException { - log.info("由下标 " + i + " 获取字符串:" + resultSet.getLong(i)); - return new Date(resultSet.getLong(i)); - } - - @Override - public Date getNullableResult(CallableStatement callableStatement, int i) throws SQLException { - log.info("通过callbleStatement下标获取字符串"); - return callableStatement.getDate(i); - } -} -``` - -2. 在mybatis-config.xml中国注册自定义的typeHandler。单独配置或者扫描包的形式。注册之后数据的读写会被这个类过滤。 - -```java - - - - - - -``` - -3. 在RoleMapper.xml编写SQL语句,resultMap中的转换字段需要指定相应的jdbcType和javaType,启动我们自定义的typeHandler,本例中应设置reg_time字段jdbcType为VARCHAR,对应的regTime属性javaType设置为java.util.Date,与MyDateTypeHandler处理类型匹配,所以对reg_time字段的读取会先经过MyDateTypeHandler的处理。插入的时候不会启用我们自定义的typeHandler,需要在insert标签配置typeHandler(或指定jdbcType和javaType)。 - -```xml - - - - - - - - - - - - - - - - INSERT into role(id, role_name, note, reg_time) - VALUES(#{id}, #{roleName}, #{note}, #{regTime,javaType=Date, jdbcType=VARCHAR}) - - -``` - -#### 枚举类型typeHandler - -Mybatis内部提供了两个转化枚举类型的typeHandler:org.apache.ibatis.type.EnumTypeHandler和org.apache.apache.ibatis.type.EnumOrdinalTypeHandler。EnumTypeHandler使用枚举字符串名称作为参数传递,EnumOrdinalTypeHandler使用整数下标作为参数传递。 - -下面通过EnumOrdinalTypeHandler实现性别枚举。 - -```java -public enum Sex { - MALE(1, "男"), FEMALE(2, "女"); - - private int id; - private String name; - - private Sex(int id, String name) { - this.id = id; - this.name = name; - } - - public static Sex getSex(int id) { - if(id == 1) { - return MALE; - } else if(id == 2) { - return FEMALE; - } else { - return null; - } - } - - //setter和getter -} -``` - -StudentMapper.java - -```java -public interface StudentMapper { - public Student findStudent(int id); - public void insertStudent(Student student); -} -``` - -StudentMapper.xml - -```xml - - - - - - - - - - - - select last_insert_id() - - insert into student(name, sex) values(#{name}, - #{sex, typeHandler=org.apache.ibatis.type.EnumOrdinalTypeHandler}) - - - -``` - -测试类 - -```java -import com.tyson.entity.Sex; -import com.tyson.entity.Student; -import com.tyson.mapper.StudentMapper; -import com.tyson.util.SqlSessionFactoryUtil; -import lombok.extern.slf4j.Slf4j; -import org.apache.ibatis.session.SqlSession; -import org.junit.Test; - -@Slf4j -public class StudentTest { - @Test - public void findStudentTest() { - SqlSession sqlSession = null; - try { - sqlSession = SqlSessionFactoryUtil.openSqlSession(); - StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class); - Student s = studentMapper.findStudent(3); - if(s != null) { - log.info(s.toString()); - } - } catch (Exception ex) { - ex.printStackTrace(); - } finally { - if(sqlSession != null) { - sqlSession.close(); - } - } - } - @Test - public void insertStudentTest() { - SqlSession sqlSession = null; - try { - sqlSession = SqlSessionFactoryUtil.openSqlSession(); - StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class); - Student s = new Student(); - s.setName("tyson"); - s.setSex(Sex.MALE); - studentMapper.insertStudent(s); - sqlSession.commit(); - } catch (Exception ex) { - ex.printStackTrace(); - sqlSession.rollback(); - } finally { - if(sqlSession != null) { - sqlSession.close(); - } - } - } -} -``` - -插入的sex字段为INTEGER,测试结果如下: - -![枚举插入结果](https://img-blog.csdnimg.cn/20190202180633368.png) - -通过EnumTypeHandler实现性别枚举只需修改StudentMapper.xml相应的typeHandler,修改如下: - -```xml - - - - - - - - - - - - - select last_insert_id() - - insert into student(name, sex) values(#{name}, - #{sex, typeHandler=org.apache.ibatis.type.EnumTypeHandler}) - - - -``` - -插入的sex字段为VARCHAR类型,测试结果如下: - -![枚举插入结果](https://img-blog.csdnimg.cn/20190202180340893.png) - -##### 自定义枚举类typeHandler - -SexEnumTypeHandler类的定义。 - -```java -import com.tyson.entity.Sex; -import lombok.extern.slf4j.Slf4j; -import org.apache.ibatis.type.JdbcType; -import org.apache.ibatis.type.TypeHandler; - -import java.sql.CallableStatement; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; - -@Slf4j -public class SexEnumTypeHandler implements TypeHandler { - - @Override - public void setParameter(PreparedStatement preparedStatement, int i, Sex sex, JdbcType jdbcType) throws SQLException { - log.info("预编译语句设置参数: " + i); - preparedStatement.setInt(i, sex.getId()); - } - - @Override - public Sex getResult(ResultSet resultSet, String s) throws SQLException { - log.info("由列名 " + s + " 获取字符串:" + resultSet.getString(s)); - int id = resultSet.getInt(s); - return Sex.getSex(id); - } - - @Override - public Sex getResult(ResultSet resultSet, int i) throws SQLException { - log.info("由下标 " + i + " 获取字符串:" + resultSet.getInt(i)); - int id = resultSet.getInt(i); - return Sex.getSex(id); - } - - @Override - public Sex getResult(CallableStatement callableStatement, int i) throws SQLException { - @Override - public Sex getResult(CallableStatement callableStatement, int i) throws SQLException { - int id = callableStatement.getInt(i); - return Sex.getSex(id); - } - } -} -``` - -mybatis-config.xml增加SexEnumTypeHandler的定义 - -```xml - - - - -``` - -StudentMapper.xml - -```xml - - - - - - - - - - - - - - select last_insert_id() - - insert into student(name, sex) values(#{name}, - #{sex, typeHandler=com.tyson.typeHandler.SexEnumTypeHandler}) - - - -``` - -测试结果如下: - -![测试结果](https://img-blog.csdnimg.cn/20190202182217878.png) - - - -### objectFactory - -当Mybatis在构建一个结果返回时,都会使用ObjectFactory去构建POJO,可以定制自己的对象工厂。一般使用默认的ObjectFactory即可,默认的ObjectFactory为org.apache.ibatis.reflection.factory.DefaultObjectFactory提。 - -### environments - -配置环境可以注册多个数据源,每一个数据源分为两部分的配置:数据库源的配置和数据库事务的配置。 - -```xml - - - - - - - - - - - - - - - - -``` - -- environments的default属性,表明在缺省的情况下,将使用哪个数据源配置。 -- transactionManager配置的是数据库事务,其type属性有三种配置方式。 - -(1)JDBC,使用JDBC管理事务,独立编码时常常使用; - -(2)MANAGED,使用容器方式管理事务,在JNDI数据源中常用; - -(3)自定义,由使用者自定义数据库管理方式,适用于特殊应用。 - -- dataSource标签,配置数据源连接的信息,type属性提供数据库连接方式的配置: - -(1)UNPOOLED,非连接池数据库 - -(2)POOLED,连接池数据库 - -(3)JNDI数据源 - -(4)自定义数据源 - -### 数据库事务 - -Mybatis数据库事务由SqlSession控制,我们可以通过SqlSession提交或回滚。 - -```java - @Test - public void insertRoleTest() { - SqlSession sqlSession = null; - try { - sqlSession = SqlSessionFactoryUtil.openSqlSession(); - RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class); - - Role role = new Role(); - role.setId(2L); - role.setNote("hi"); - role.setRoleName("teacher"); - - roleMapper.insertRole(role); - sqlSession.commit(); - } catch (Exception e) { - e.printStackTrace(); - sqlSession.rollback(); - } finally { - if(sqlSession != null) { - sqlSession.close(); - } - } - } -``` - -## 映射器 - -### select元素 - -| 元素 | 说明 | 备注 | -| ------------- | -------------------------------------------------------- | ------------------------------------------------------------ | -| id | 和Mapper命名空间的组合是唯一的 | 命名空间和id组合不唯一,则抛异常 | -| parameterType | 类的全路径或者别名 | 基本数据类型,JavaBean,Map等 | -| resultType | 基本数据类型或者类的全路径,可使用别名(需符合别名规范) | 允许自动匹配的请况下,结果集将通过JavaBean的规范映射,不能和resultMap同时使用 | -| resultMap | 自定义映射规则 | Mybatis最复杂的元素,可以配置映射规则、级联、typeHandler等 | - -#### 自动映射 - -autoMappingBehavior不为NONE时,Mybatis会提供自动映射的功能,只要返回的列名和JavaBean的属性一致,Mybatis就会帮助我们回填这些字段。实际上大部分数据库规范使用下划线分割单词,而Java则是用驼峰命名法,于是需要使用列的别名使得Mybatis能够自动映射,或者在配置文件中开启驼峰命名方式。 - -```xml - - -``` - -自动映射可以在setting元素中配置autoMappingBehavior属性值设定其策略。包含三个值: - -- NONE,取消自动映射 -- PARTIAL,只会自动映射,没有定义嵌套结果集映射的结果集 -- FULL,会自动映射任意复杂的结果集(无论是否嵌套) - -默认值是PARTIAL,默认情况下可以做到当前对象的映射,使用FULL是嵌套映射,性能会下降。 - -如果数据库是规范命名的,即每个单词用下划线分隔,而POJO是驼峰式命名的方式,此时可设置mapUnderscoreToCamelCase为true,这样就可以实现从数据库到POJO的自动映射了。 - -#### 传递多个参数 - -1. 使用注解方式传递参数 - -```java -public List findRoleByCondition(@Param("roleName") String roleName, @Param("note")String note); -``` - -RoleMapper.xml - -```xml - -``` - -2. 使用JavaBean传递参数 - -将参数组织成JavaBean,通过getter和setter方法设置参数。 - -```java -public class RoleParam { - private String roleName; - private String note; - - public String getRoleName() { - return roleName; - } - - public void setRoleName(String roleName) { - this.roleName = roleName; - } - - public String getNote() { - return note; - } - - public void setNote(String note) { - this.note = note; - } -} -``` - -接口RoleMapper - -```java -public List findRoleByParams2(RoleParam roleParam); -``` - -RoleMapper.xml - -```xml - -``` - -参数个数多于5,建议使用JavaBean方式。 - -#### 使用resultMap映射结果集 - -```xml - - - - - - - - -``` - -### insert元素 - -执行插入之后会返回一个整数,表示插入的记录数。parameterType 为 role(mybatis-config.xml 定义的别名)。 - -```xml - - INSERT into role(id, role_name, note) VALUES(#{id}, #{roleName}, #{note}) - -``` - -#### 主键回填 - -部分内容参考自:[insert主键返回 selectKey使用](https://blog.csdn.net/qq_29663071/article/details/79486048) - -设计表的时候有两种主键,一种自增主键,一般为int类型,一种为非自增的主键,例如用uuid等。 - -##### 自增主键 - -role表指定id字段为自增字段,对应的Role实体类提供getter和setter方法,便可以使用Mybatis的主键回填功能。通过keyProperty指定主键字段,并使用useGeneratedKeys告诉Mybatis这个主键是否使用数据库内置策略生成。 - -```xml - - - INSERT into role(role_name, note) VALUES(#{roleName}, #{note}) - -``` - -传入的role无需设置id,Mybatis在插入记录时会自动回填主键。 - -```java - @Test - public void insertRoleUseGeneratedKeysTest() { - SqlSession sqlSession = null; - try { - sqlSession = SqlSessionFactoryUtil.openSqlSession(); - RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class); - - Role role = new Role(); - role.setNote("hello"); - role.setRoleName("worker"); - - roleMapper.insertRoleUseGeneratedKeys(role); - log.info(role.toString()); - sqlSession.commit(); - } catch (Exception e) { - e.printStackTrace(); - sqlSession.rollback(); - } finally { - if (sqlSession != null) { - sqlSession.close(); - } - } - } -``` - -也可以通过selectKey设置主键回填。 - -```xml - - - - select LAST_INSERT_ID() - - INSERT into role(role_name, note) VALUES(#{roleName}, #{note}) - -``` - -##### 非自增主键 - -假设增加如下需求,当表role没有记录时,则插入第一条记录时id设为1,否则取最大的id加2,设置为新的主键,这个时候可以使用selectKey来处理。 - -```xml - - - - select if(max(id) is null, 1, max(id) + 2) as newId from role - - INSERT into role(id, role_name, note) VALUES(#{id}, #{roleName}, #{note}) - -``` - -selectKey标签的语句会被先执行,然后把查询到的id放到role对象。 - -```java - @Test - public void myInsertRoleTest() { - SqlSession sqlSession = null; - try { - sqlSession = SqlSessionFactoryUtil.openSqlSession(); - RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class); - - Role role = new Role(); - role.setNote("hello"); - role.setRoleName("worker"); - - roleMapper.myInsertRole(role); - log.info(role.toString()); - sqlSession.commit(); - } catch (Exception e) { - e.printStackTrace(); - sqlSession.rollback(); - } finally { - if (sqlSession != null) { - sqlSession.close(); - } - } - } -``` - -假设主键是VARCHAR类型,以uuid()方式生成主键。 - -```xml - - - - - - - select uuid() - - insert customer(id, name) values(#{id}, #{name}) - - -``` - -测试类。 - -```java -import com.tyson.mapper.CustomerMapper; -import com.tyson.pojo.Customer; -import com.tyson.util.SqlSessionFactoryUtil; -import lombok.extern.slf4j.Slf4j; -import org.apache.ibatis.session.SqlSession; -import org.junit.Test; - -@Slf4j -public class InsertCustomerTest { - @Test - public void insertCustomer() { - SqlSession session = null; - try { - session = SqlSessionFactoryUtil.openSqlSession(); - CustomerMapper customerMapper = session.getMapper(CustomerMapper.class); - Customer customer = new Customer(); - customer.setName("tyson"); - customerMapper.insertCustomer(customer); - session.commit(); - } catch (Exception ex) { - ex.printStackTrace(); - session.rollback(); - } finally { - if(session != null) { - session.close(); - } - } - } -} -``` - -### update和delete元素 - -update和delete元素用于更新记录和删除记录。插入和删除记录执行完成会返回一个整数,表示插入或删除几条记录。 - -```xml - - update role set - role_name = #{roleName}, - note = #{note} - where id = #{id} - - - - delete from role where id = #{id} - -``` - -测试类 - -```java - @Test - public void updateRole() { - SqlSession sqlSession = null; - try { - sqlSession = SqlSessionFactoryUtil.openSqlSession(); - RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class); - Role role = new Role(); - role.setId(2L); - role.setRoleName("actor"); - role.setNote("fired"); - roleMapper.updateRole(role); - sqlSession.commit(); - } catch (Exception ex) { - ex.printStackTrace(); - sqlSession.rollback(); - } finally { - if(sqlSession != null) { - sqlSession.close(); - } - } - } - - @Test - public void deleteRole() { - SqlSession sqlSession = null; - try { - sqlSession = SqlSessionFactoryUtil.openSqlSession(); - RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class); - roleMapper.deleteRole(8L); - sqlSession.commit(); - } catch (Exception ex) { - ex.printStackTrace(); - sqlSession.rollback(); - } finally { - if(sqlSession != null) { - sqlSession.close(); - } - } - } -``` - -### sql元素 - -### resultMap元素 - -resultMap元素主要包括以下元素: - -```xml - - - - - - - - - - - - - -``` - -constructor元素用于配置构造方法。对于没有无参构造方法的POJO,可用constructor元素进行配置。 - -```xml - - - - -``` - -#### 级联 - -Mybatis中级联分为三种:association、collection和discriminator。 - -- association,代表一对一关系 -- collection,代表一对多关系 -- discriminator,鉴别器,它可以根据实际选择选用哪个类作为实例,允许根据特定的条件去关联不同的结果集。 - -##### association一对一级联 - -以学生和学生证为例,学生和学生证是一对一的关系,在Student建立一个类型为StudentCart的属性sc,这样便形成了级联。 - -```java -public class Student { - int id; - String name; - Sex sex; - StudentCard sc; - - //setter和getter -} -``` - -```java -public class StudentCard { - int id; - int sid; - String note; - - //setter和getter -} -``` - - - -在StudentCartMapper.xml中提供findStudentCardByStudentId方法。 - -```xml - - - - - - - - - - - -``` - -在StudentMapper里使用StudentCardMapper进行级联。 - -```xml - - - - - - - - - - - - - - - - select last_insert_id() - - insert into student(name, sex) values(#{name}, - #{sex, typeHandler=com.tyson.typeHandler.SexEnumTypeHandler}) - - -``` - -测试association级联。 - -```java - @Test - public void findStudentTest() { - SqlSession sqlSession = null; - try{ - sqlSession = SqlSessionFactoryUtil.openSqlSession(); - StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class); - Student s = studentMapper.findStudent(1); - log.info(s.toString()); - } catch (Exception ex) { - ex.printStackTrace(); - } finally { - if(sqlSession != null) { - sqlSession.close(); - } - } - } -``` - -测试结果: - -```java -22:37:54.931 [main] INFO com.tyson.StudentTest - id: 1, name: tyson, sex: MALE, cardId: 10086 -``` - -##### collection一对多级联 - -一个学生有多门课程,这是一对多的级联。建立Lecture的POJO记录课程和StudentLecture表示学生课程表,StudentLecture里有一个类型为Lecture的属性lecture,用来记录学生成绩。 - -StudentLecture和Lecture类: - -```java -public class StudentLecture { - int id; - int studentId; - Lecture lecture; - int grade; - - //setter和getter -} -``` - -```java -public class Lecture { - int id; - String lectureName; - - //setter和getter -} -``` - -Student类添加一个List类型的属性。 - -```java -public class Student { - int id; - String name; - Sex sex; - StudentCard sc; - List studentLectureList; - - //setter和getter -} -``` - -StudentMapper.xml使用collection对Student和StudentLecture做一对多的级联。 - -```xml - - - - - - - - - - - -``` - -StudentLectureMapper.xml需要使用association对StudentLecture和Lecture做一对一的级联。 - -```xml - - - - - - - - - - - -``` - -LectureMapper.xml - -```xml - - - - - - -``` - -测试类 - -```java - @Test - public void findStudentTest() { - SqlSession sqlSession = null; - try{ - sqlSession = SqlSessionFactoryUtil.openSqlSession(); - StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class); - Student s = studentMapper.findStudent(1); - log.info(s.toString()); - } catch (Exception ex) { - ex.printStackTrace(); - } finally { - if(sqlSession != null) { - sqlSession.close(); - } - } - } -``` - -测试结果如下: - -```java -10:46:33.916 [main] INFO com.tyson.StudentTest - id: 1, name: tyson, sex: MALE, cardId: 10086, studentLectureList: StudentLecture{studentId=1, lecture=id: 1, lectureName: math, grade=90}, StudentLecture{studentId=1, lecture=id: 2, lectureName: physics, grade=78}, -``` - -##### discriminator鉴别器级联 - -鉴别器级联是在特定的条件下去使用不用的POJO。比如可以通过学生信息表的sex属性进行判断关联男生健康指标或者女生的健康指标。新建MaleStudentHealth.java和FemaleStudentHealth.java,存储男生女生的健康信息。新建MaleStudent.java和FemaleStudent.java,继承自Student.java类。 - -```java -public class MaleStudentHealth { - int height; - //setter和getter -} - -public class MaleStudent extends Student { - List maleStudentHealthList; - //setter和getter -} -``` - -StudentMapper.xml如下,在discriminator元素通过sex字段的值判断是男生还是女生。当sex=1时,引入maleStudentMap的resultMap,这个resultMap继承自studentMap,使用collection对MaleStudent和MaleStudentHealth做一对多的级联。 - -```xml - - - - - - - - - - - - - - - - - - - - - - - - - -``` - -测试结果如下: - -```java -16:39:14.123 [main] INFO com.tyson.StudentTest - MaleStudent{maleStudentHealthList=MaleStudentHealth{height=170}, MaleStudentHealth{height=172}, id=1, name='tyson', sex=MALE, sc=studentCard: 10086, studentLectureList=[StudentLecture{studentId=1, lecture=id: 1, lectureName: math, grade=90}, StudentLecture{studentId=1, lecture=id: 2, lectureName: physics, grade=78}]} -``` - -##### 延迟加载 - -级联的优势在于能够方便快捷地获取数据,但是每次获取数据时,所有级联数据都会取出,每一个关联都会多执行一次SQL,这样会造成SQL执行过多性能下降。 - -假如我们通过传入id查找学生信息,然后打印出学生证信息,代码如下: - -```java -@Test -public void findStudentTest() { - SqlSession sqlSession = null; - try{ - sqlSession = SqlSessionFactoryUtil.openSqlSession(); - StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class); - Student s = studentMapper.findStudent(1); - log.info("***********获取学生证信息***********"); - log.info("学生的学生证信息:" + s.getSc().toString()); - } catch (Exception ex) { - ex.printStackTrace(); - } finally { - if(sqlSession != null) { - sqlSession.close(); - } - } -} -``` - -测试结果如下,所有级联数据都会被加载出来。 - -![在这里插入图片描述](https://img-blog.csdnimg.cn/20190216152606573.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1R5c29uMDMxNA==,size_16,color_FFFFFF,t_70) - -为了解决这个问题可以采用延迟加载的功能。首先打开延迟加载的开关。 - -```xml - - - - -``` - -重新运行测试代码,结果如下: - -![在这里插入图片描述](https://img-blog.csdnimg.cn/20190216151926516.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1R5c29uMDMxNA==,size_16,color_FFFFFF,t_70) - -由图可知,当我们查找学生信息时,会同时查出健康信息,当访问学生课程时,会同时把学生证信息查出。原因是Mybatis默认是按层级延迟加载的,如下图所示: - -![在这里插入图片描述](https://img-blog.csdnimg.cn/20190216115304804.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1R5c29uMDMxNA==,size_16,color_FFFFFF,t_70) - -当加载学生信息时,会根据鉴别器找到健康的信息。而当我们访问学生课程时,由于学生证和学生课程是一个层级,也会访问到学生证信息。通过设置全局参数aggressiveLazyLoading可以避免这种情况。aggressiveLazyLoading默认值是true,使用层级加载的策略,设置为false则会按照我们的需要去延迟加载数据。 - -```xml - - - - - - -``` - -测试结果如下,此时会根据我们的需要加载数据,不需要的数据不会被加载。 - -![在这里插入图片描述](https://img-blog.csdnimg.cn/20190216153512266.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1R5c29uMDMxNA==,size_16,color_FFFFFF,t_70) - -aggressiveLazyLoading是全局的设置,不能指定哪个属性可以立即加载,哪个属性可以延迟加载。假如我们在查找学生信息时,很多情况下需要同时把学生课程成绩查出,此时采用即时加载比较好,多条SQL同时发出,性能高。我们可以在association和collection元素加入属性值fetchType(取值为lazy和eager),便可以实现局部延迟加载的功能。(需先设置aggressiveLazyLoading为false) - -StudentMapper.xml设置学生证和健康信息延时加载,学生课程即时加载。 - -```xml - - - - - - - -``` - -StudentLectureMapper.xml设置课程信息即时加载。 - -```xml - - - - - -``` - -测试代码: - -```java -public void findStudentTest() { - SqlSession sqlSession = null; - try{ - sqlSession = SqlSessionFactoryUtil.openSqlSession(); - StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class); - studentMapper.findStudent(1); - } catch (Exception ex) { - ex.printStackTrace(); - } finally { - if(sqlSession != null) { - sqlSession.close(); - } - } -} -``` - -测试结果如下,可以看到有三条SQL被执行,查询学生信息,学生课程和课程信息。当我们访问延迟加载对象时,它才会发送SQL到数据库把数据加载回来。 - -![在这里插入图片描述](https://img-blog.csdnimg.cn/20190216160411511.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1R5c29uMDMxNA==,size_16,color_FFFFFF,t_70) - - - -## 动态SQL - - Mybatis的动态SQL主要包括以下几种元素。 - -| 元素 | 作用 | -| ----------------------- | ------------------------ | -| if | 单条件分支判断 | -| choose(when、otherwise) | 相当于Java的switch、case | -| foreach | 在in语句等列举条件常用 | -| trim(where、set) | 用于处理SQL拼装问题 | - -### if元素 - -if元素和test属性联合使用。 - -```xml - -``` - -### choose元素 - -choose、when和otherwise类似于Java的switch、case和default。 - -```xml - -``` - -- 当roleName不为空,则只用roleName作为条件查询; -- 当roleName为空,note不为空,则用note作为条件进行查询; -- 当roleName和note都为空时,则以 id != 1 作为查询条件 - -### where元素 - -where元素解析时会自动将第一个字段的and去掉。 - -```xml - -``` - -测试结果: - -```java -11:42:16.301 [main] DEBUG c.tyson.mapper.RoleMapper.findRoles - ==> Preparing: select id, role_name, note, reg_time from role where note like concat('%', ?, '%') -11:42:16.404 [main] DEBUG c.tyson.mapper.RoleMapper.findRoles - ==> Parameters: hi(String) -``` - -使用trim也可以达到同样的效果。prefix代表语句前缀,prefixOverrides代表需要去掉的字符串。 - -```xml - -``` - -### set元素 - -当在 update 语句中使用if标签时,如果前面的if没有执行,则或导致逗号多余错误。使用set标签可以将动态的配置 SET 关键字,并剔除追加到条件末尾的任何不相关的逗号。使用 if+set 标签修改后,如果某项为 null 则不进行更新,而是保持数据库原值。 - -```xml - - update role - - - role_name = #{roleName}, - - - note = #{note}, - - - reg_time = #{regTime} - - - where id = #{id} - -``` - -测试结果如下,最后一个逗号被去掉了。 - -```java -11:39:37.975 [main] DEBUG c.tyson.mapper.RoleMapper.updateRole - ==> Preparing: update role SET role_name = ?, note = ? where id = ? -11:39:38.111 [main] DEBUG c.tyson.mapper.RoleMapper.updateRole - ==> Parameters: actor(String), fired(String), 2(Long) -``` - -### foreach元素 - -foreach用于遍历元素,支持数组、List和Set接口的集合。 - -```xml - - - select LAST_INSERT_ID() - - insert into role(role_name, note, reg_time) values - - - (#{role.roleName}, #{role.note}, #{role.regTime,javaType=Date, jdbcType=VARCHAR}) - - - - -``` - -RoleMapper.java - -```java -//List没有使用@Param指定参数名称,则对应Mapper.xml中的collection名称为list -public void batchInsertRole(@Param("roleList") List roleList); -public List findRolesInIds(@Param("ids") int[] ids); -``` - -| 参数 | 说明 | -| ---------- | ---------------------------- | -| collection | 数组、List或Set接口 | -| item | 当前元素 | -| index | 当前元素在集合的下标 | -| open/close | 用什么符号将集合元素包装起来 | -| separator | 间隔符 | - -### bind元素 - -当进行模糊查询时,对于MySQL数据库,我们经常会用concat函数用%和参数连接。对于Oracle则是用||符号连接。这样不同的数据库便需要不同的实现。有了bind元素,就不用考虑使用何种数据库语言,只要使用Mybatis的语言即可与所需参数相连,提高其移植性。 - -```xml - -``` - -测试结果: - -```java -15:05:21.798 [main] DEBUG c.tyson.mapper.RoleMapper.findRoles - ==> Preparing: select id, role_name, note, reg_time from role where role_name like ? and note like ? -15:05:21.946 [main] DEBUG c.tyson.mapper.RoleMapper.findRoles - ==> Parameters: %teacher%(String), %hi%(String) -``` - - - -## Mybatis-Spring应用 - -整合Mybatis-Spring可以通过xml的方式配置,也可以通过注解配置。配置Mybatis-Spring分为几个部分:配置数据源、配置SqlSessionFactory、配置SqlSessionTemplate、配置Mapper和事务处理。SqlSessionTemplate是对SqlSession操作的封装。 - -pom.xml导入依赖。 - -```xml - - - 4.0.0 - - com.tyson - mybatis-spring - 1.0-SNAPSHOT - - - UTF-8 - 4.3.2.RELEASE - 1.3.0 - 5.1.38 - 3.4.1 - 4.12 - 0.9.1.2 - - - - - - org.springframework - spring-context - ${spring.version} - - - - org.springframework - spring-test - ${spring.version} - - - - org.mybatis - mybatis-spring - ${mybatis-spring.version} - - - - org.springframework - spring-jdbc - ${spring.version} - - - - mysql - mysql-connector-java - ${mysql.version} - - - - org.mybatis - mybatis - 3.4.1 - - - - junit - junit - ${junit.version} - - - - c3p0 - c3p0 - ${c3p0.version} - - - - - - - - src/main/java - - **/*.xml - - - - src/main/resources - - - - -``` - -applicationContext.xml - -```xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -``` - -sqlMapConfig.xml - -```xml - - - - - - - - - - - - - - - - -``` - -实体类Role.java - -```java -import java.util.Date; - -public class Role { - private Long id; - private String roleName; - private String note; - private Date regTime; - - //getter和setter -} -``` - -RoleMapper.java - -```java -import com.tyson.pojo.Role; -import org.springframework.stereotype.Repository; - -@Repository -public interface RoleMapper { - public void insertRole(Role role); -} -``` - -RoleMapper.xml - -```xml - - - - - insert into role(id, role_name, note) values(#{id}, #{roleName}, #{note}) - - -``` - -RoleService.java - -```java -import com.tyson.pojo.Role; - -public interface RoleService { - public void insertRole(Role role); -} -``` - -RoleServiceImpl.java - -```java -import com.tyson.mapper.RoleMapper; -import com.tyson.pojo.Role; -import com.tyson.service.RoleService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.annotation.Transactional; - -@Service("roleService") -public class RoleServiceImpl implements RoleService { - @Autowired - RoleMapper roleMapper; - - @Transactional(propagation = Propagation.REQUIRED) - public void insertRole(Role role) { - roleMapper.insertRole(role); - } -} -``` - -测试 - -```java -import com.tyson.pojo.Role; -import com.tyson.service.RoleService; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; - -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(locations = {"classpath:applicationContext.xml"}) -public class RoleTest { - @Autowired - RoleService roleService; - - @Test - public void insertRolesTest() { - Role role = new Role(); - role.setId(1L); - role.setRoleName("stu"); - role.setNote("emm"); - - roleService.insertRole(role); - } -} -``` - - - -## 实用场景 - -### 批量更新 - -Mybatis内置的ExecutorType有3种,默认的是simple,该模式下它为每个语句的执行创建一个新的预处理语句,单条提交sql;而batch模式重复使用已经预处理的语句,并且批量执行所有更新语句。 - -在数据库中使用批量更新有利于提高性能。在Mybatis中通过修改mybatis-config.xml配置文件中的settings的defaultExecutorType来制定其执行器为批量执行器。 - -```xml - - - -``` - -也可以通过Java代码实现批量执行器的使用。 - -```java -sqlSessionFactory.openSession(ExecutorType.BATCH); -``` - -在Spring中使用批量执行器。 - -```xml - - - - - -``` - -使用批量执行器,在默认情况下,它在sqlSession进行commit操作之后才会执行SQL语句。 - -测试代码如下: - -```java - @Test - public void roleTest() { - SqlSession sqlSession = null; - try { - sqlSession = SqlSessionFactoryUtil.openSqlSession(); - RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class); - - Role role = new Role(); - role.setNote("emm"); - role.setRoleName("man"); - - roleMapper.insertRole(role); - roleMapper.insertRole(role); - roleMapper.insertRole(role); - - sqlSession.commit(); - } catch (Exception e) { - e.printStackTrace(); - sqlSession.rollback(); - } finally { - if (sqlSession != null) { - sqlSession.close(); - } - } - } -``` - -测试结果: - -未开启批量执行: - -![在这里插入图片描述](https://img-blog.csdnimg.cn/20190214232057457.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1R5c29uMDMxNA==,size_16,color_FFFFFF,t_70) - -开启批量执行: - -![在这里插入图片描述](https://img-blog.csdnimg.cn/20190214232308469.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1R5c29uMDMxNA==,size_16,color_FFFFFF,t_70) - -设置ExecutorType.BATCH原理:把SQL语句发给数据库,数据库预编译好,数据库等待需要运行的参数,接收到参数后一次运行,ExecutorType.BATCH只打印一次SQL语句,多次设置参数。 - - - -### 存储过程 - -存储过程就是具有名字的一段代码,用来完成一个特定的功能。创建的存储过程保存在数据库的数据字典中。 - -优点(为什么要用存储过程?): - -  ①将重复性很高的一些操作,封装到一个存储过程中,简化了对这些SQL的调用 - -  ②批量处理:SQL+循环,减少流量 - -#### in和out参数 - -1.新建存储过程,按照传入的参数查询男女学生人数。 - -```my -#声明分隔符,默认为“;",编译器将两个$之间的内容当做存储过程的代码,不会执行这些代码 -DELIMITER $ -CREATE PROCEDURE gesture_count(IN sex INT, OUT ges_count INT) -BEGIN -IF sex=1 THEN -SELECT COUNT(*) FROM student AS s WHERE s.sex='male' INTO ges_count; -ELSE -SELECT COUNT(*) FROM student AS s WHERE s.sex='female' INTO ges_count; -END IF; -END -$ -#还原分隔符 -DELIMITER ; -``` - -调用存储过程。 - -```mysql -SET @ges_count=1; -CALL gesture_count(2, @ges_count); -SELECT @ges_count -``` - -2.定义一个Pojo反映存储过程的参数。 - -```java -public class ProcedureParam { - private int sex; - private int gesCount; - - //setter和getter -} -``` - -3.在xml映射器做配置,调用存储过程。 - -```xml - - -``` - -4.存储过程接口。 - -```java -public void gesCount(ProcedureParam procedureParam); -``` - -5.测试代码。 - -```java -@Test -public void getCountTest() { - SqlSession sqlSession = null; - try{ - sqlSession = SqlSessionFactoryUtil.openSqlSession(); - StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class); - ProcedureParam procedureParam = new ProcedureParam(); - procedureParam.setSex(2); - studentMapper.gesCount(procedureParam); - String sex = procedureParam.getSex() == 1 ? "male" : "female"; - log.info("sex: " + sex + " count: " + procedureParam.getGesCount()); - } catch (Exception ex) { - ex.printStackTrace(); - } finally { - if(sqlSession != null) { - sqlSession.close(); - } - } -} -``` - -#### 游标 - -游标可以遍历返回的多行结果。Mysql中游标只适合存储过程和函数。 - -语法: - -1.定义游标:declare cur_name cursor for select 语句; - -2.打开游标:open cur_name; - -3.获取结果:fetch cur_name into param1, param2, ...; - -4.关闭游标:close cur_name; - -```mysql -DROP PROCEDURE IF EXISTS find_customer; -#声明分隔符,默认为“;",编译器将两个$之间的内容当做存储过程的代码,不会执行这些代码 -DELIMITER $ -CREATE PROCEDURE find_customer() -BEGIN - DECLARE no_more_record INT DEFAULT 0; - DECLARE id VARCHAR(64); - DECLARE cus_name VARCHAR(16); - #声明游标 - DECLARE cur CURSOR FOR SELECT * FROM customer; - DECLARE CONTINUE HANDLER FOR NOT FOUND SET no_more_record = 1; - OPEN cur; - FETCH cur INTO id, cus_name; - - #不断循环到达表的末尾,继续fetch会报错 - WHILE no_more_record != 1 DO - INSERT INTO customer_tmp(id, `name`) - VALUES(id, cus_name); - FETCH cur INTO id, cus_name; - - END WHILE; - CLOSE cur; -END -$ -#还原分隔符 -DELIMITER ; -``` - -调用存储过程。 - -```mysql -TRUNCATE TABLE customer_tmp; -CALL find_customer(); -``` - -### 分页 - -#### RowBounds分页 - -RowBounds分页是Mybatis内置的基本功能,在任何的select语句中都可以使用,它是在SQL语句查询出所有结果之后,对结果进行截断,当SQL语句返回大量结果时,容易造成内存溢出。其适用于返回数据量小的查询。 - -RowBounds有两个重要的参数limit和offeset,offeset表示从哪一条记录开始读取,limit表示限制返回的记录数。 - -下面通过角色名称模糊查询角色信息。 - -```xml - - -``` - -RoleMapper接口定义。 - -```java -import com.tyson.pojo.Role; -import org.apache.ibatis.annotations.Param; -import org.apache.ibatis.session.RowBounds; - -public interface RoleMapper { - public List getRoleByRoleName(@Param("roleName") String roleName, RowBounds rowBounds); -} -``` - -测试代码。 - -```java -@Test -public void getRoleByRoleNameTest() { - SqlSession sqlSession = null; - try { - sqlSession = SqlSessionFactoryUtil.openSqlSession(); - RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class); - List roles = roleMapper.getRoleByRoleName("man", new RowBounds(0, 5)); - roles.forEach(role -> { - log.info(role.toString()); - }); - } catch (Exception e) { - e.printStackTrace(); - } finally { - if (sqlSession != null) { - sqlSession.close(); - } - } -} -``` - -测试结果返回五条记录。 - - - -## 预编译 - -#{ } 被解析成预编译语句,预编译之后可以直接执行,不需要重新编译sql。 - -```mysql -//sqlMap 中如下的 sql 语句 -select * from user where name = #{name}; -//解析成为预编译语句;编译好SQL语句再取值 -select * from user where name = ?; -``` - -${ } 仅仅为一个字符串替换,每次执行sql之前需要进行编译,存在 sql 注入问题。 - -```mysql -select * from user where name = '${name}' -//传递的参数为 "ruhua" 时,解析为如下,然后发送数据库服务器进行编译。取值以后再去编译SQL语句。 -select * from user where name = "ruhua"; -``` - - - -数据库接受到sql语句之后,需要词法和语义解析,优化sql语句,制定执行计划。这需要花费一些时间。如果一条sql语句需要反复执行,每次都进行语法检查和优化,会浪费很多时间。预编译语句就是将sql语句中的`值用占位符替代`,即将`sql语句模板化`。一次编译、多次运行,省去了解析优化等过程。 - -mybatis是通过PreparedStatement和占位符来实现预编译的。 - -mybatis底层使用PreparedStatement,默认情况下,将对所有的 sql 进行预编译,将#{}替换为?,然后将带有占位符?的sql模板发送至mysql服务器,由服务器对此无参数的sql进行编译后,将编译结果缓存,然后直接执行带有真实参数的sql。 - -预编译的作用: - -1. 预编译阶段可以优化 sql 的执行。预编译之后的 sql 多数情况下可以直接执行,数据库服务器不需要再次编译,可以提升性能。 - -2. 预编译语句对象可以重复利用。把一个 sql 预编译后产生的 PreparedStatement 对象缓存下来,下次对于同一个sql,可以直接使用这个缓存的 PreparedState 对象。 - -3. 防止SQL注入。使用预编译,而其后注入的参数将`不会再进行SQL编译`。也就是说其后注入进来的参数系统将不会认为它会是一条SQL语句,而默认其是一个参数。 - - - - ## 缓存 - -目前流行的缓存服务器有Redis、Ehcache、MangoDB等。缓存是计算机内存保存的数据,在读取数据的时候不用从磁盘读入,具备快速读取的特点,如果缓存命中率高,可以极大提升系统的性能。若缓存命中率低,则使用缓存意义不大,故使用缓存的关键在于存储内容访问的命中率。 - -### 一级缓存和二级缓存 - -Mybatis对缓存提供支持,默认情况下只开启一级缓存,一级缓存作用范围为同一个SqlSession。在SQL和参数相同的情况下,我们使用同一个SqlSession对象调用同一个Mapper方法,往往只会执行一次SQL。因为在使用SqlSession第一次查询后,Mybatis会将结果放到缓存中,以后再次查询时,如果没有声明需要刷新,并且缓存没超时的情况下,SqlSession只会取出当前缓存的数据,不会再次发送SQL到数据库。若使用不同的SqlSession,因为不同的SqlSession是相互隔离的,不会使用一级缓存。 - -二级缓存作用范围是Mapper(Namespace),可以使缓存在各个SqlSession之间共享。二级缓存默认不开启,需要在mybatis-config.xml开启二级缓存: - -```xml - - - - -``` - -并在相应的Mapper.xml文件添加cache标签,表示对哪个mapper 开启缓存: - -```xml - -``` - -二级缓存要求返回的POJO必须是可序列化的,即要求实现Serializable接口。 - -当开启二级缓存后,数据的查询执行的流程就是 二级缓存 -> 一级缓存 -> 数据库。 - - - -## 原理 - -[Mybatis原理](https://blog.csdn.net/weixin_43184769/article/details/91126687) - -当调用Mapper接口方法的时候,Mybatis会使用JDK动态代理返回一个Mapper代理对象,代理对象会拦截接口方法,根据接口的全路径和方法名,定位到sql,使用executor执行sql语句,然后将sql执行结果返回。 - -因为mybatis动态代理寻找策略是 全限定名+方法名,不涉及参数,所以不支持重载。 - - - -## 优缺点 - -优点: - -1. SQL写在XML里,解除sql与程序代码的耦合,便于统一管理;提供XML标签,支持编写动态SQL语句,并可重用。 -2. 开发时只需要关注SQL语句本身,不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。 -3. 与各种数据库兼容。 - -缺点: - -1. SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。 - diff --git "a/\346\266\210\346\201\257\351\230\237\345\210\227/\346\255\273\344\277\241\351\230\237\345\210\227.md" "b/\346\266\210\346\201\257\351\230\237\345\210\227/\346\255\273\344\277\241\351\230\237\345\210\227.md" deleted file mode 100644 index e09527c..0000000 --- "a/\346\266\210\346\201\257\351\230\237\345\210\227/\346\255\273\344\277\241\351\230\237\345\210\227.md" +++ /dev/null @@ -1,117 +0,0 @@ -死信队列:消费失败的消息存放的队列。消息消费失败的原因: - -- 消息被拒绝并且消息没有重新入队(requeue=false) -- 消息超时未消费 -- 达到最大队列长度 - -设置死信队列的 exchange 和 queue,然后进行绑定: - -```java - @Bean - public DirectExchange dlxExchange() { - return new DirectExchange(RabbitMqConfig.DLX_EXCHANGE); - } - - @Bean - public Queue dlxQueue() { - return new Queue(RabbitMqConfig.DLX_QUEUE, true); - } - - @Bean - public Binding bindingDeadExchange(Queue dlxQueue, DirectExchange deadExchange) { - return BindingBuilder.bind(dlxQueue).to(deadExchange).with(RabbitMqConfig.DLX_QUEUE); - } -``` - -在普通队列加上两个参数,绑定普通队列到死信队列。当消息消费失败时,消息会被路由到死信队列。 - -```java - @Bean - public Queue sendSmsQueue() { - Map arguments = new HashMap<>(2); - // 绑定该队列到私信交换机 - arguments.put("x-dead-letter-exchange", RabbitMqConfig.DLX_EXCHANGE); - arguments.put("x-dead-letter-routing-key", RabbitMqConfig.DLX_QUEUE); - return new Queue(RabbitMqConfig.MAIL_QUEUE, true, false, false, arguments); - } -``` - -生产者完整代码: - -```java -@Component -@Slf4j -public class MQProducer { - - @Autowired - RabbitTemplate rabbitTemplate; - - @Autowired - RandomUtil randomUtil; - - @Autowired - UserService userService; - - final RabbitTemplate.ConfirmCallback confirmCallback = (CorrelationData correlationData, boolean ack, String cause) -> { - log.info("correlationData: " + correlationData); - log.info("ack: " + ack); - if(!ack) { - log.info("异常处理...."); - } - }; - - - final RabbitTemplate.ReturnCallback returnCallback = (Message message, int replyCode, String replyText, String exchange, String routingKey) -> - log.info("return exchange: " + exchange + ", routingKey: " - + routingKey + ", replyCode: " + replyCode + ", replyText: " + replyText); - - public void sendMail(String mail) { - //貌似线程不安全 范围100000 - 999999 - Integer random = randomUtil.nextInt(100000, 999999); - Map map = new HashMap<>(2); - String code = random.toString(); - map.put("mail", mail); - map.put("code", code); - - MessageProperties mp = new MessageProperties(); - //在生产环境中这里不用Message,而是使用 fastJson 等工具将对象转换为 json 格式发送 - Message msg = new Message("tyson".getBytes(), mp); - msg.getMessageProperties().setExpiration("3000"); - //如果消费端要设置为手工 ACK ,那么生产端发送消息的时候一定发送 correlationData ,并且全局唯一,用以唯一标识消息。 - CorrelationData correlationData = new CorrelationData("1234567890"+new Date()); - - rabbitTemplate.setMandatory(true); - rabbitTemplate.setConfirmCallback(confirmCallback); - rabbitTemplate.setReturnCallback(returnCallback); - rabbitTemplate.convertAndSend(RabbitMqConfig.MAIL_QUEUE, msg, correlationData); - - //存入redis - userService.updateMailSendState(mail, code, MailConfig.MAIL_STATE_WAIT); - } -} -``` - -消费者完整代码: - -```java -@Slf4j -@Component -public class DeadListener { - - @RabbitListener(queues = RabbitMqConfig.DLX_QUEUE) - public void onMessage(Message message, Channel channel) throws IOException { - - try { - Thread.sleep(5000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - long deliveryTag = message.getMessageProperties().getDeliveryTag(); - //手工ack - channel.basicAck(deliveryTag,false); - System.out.println("receive--1: " + new String(message.getBody())); - } -} -``` - -当普通队列中有死信时,RabbitMQ 就会自动的将这个消息重新发布到设置的死信交换机去,然后被路由到死信队列。可以监听死信队列中的消息做相应的处理。 diff --git "a/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\347\256\227\346\263\225.md" "b/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\347\256\227\346\263\225.md" deleted file mode 100644 index 7100651..0000000 --- "a/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\347\256\227\346\263\225.md" +++ /dev/null @@ -1,374 +0,0 @@ -## 二叉树的遍历 - -二叉树的先序、中序和后序属于深度优先遍历DFS,层次遍历属于广度优先遍历BFS。 - -![](https://raw.githubusercontent.com/Tyson0314/img/master/20220619165820.png) - -### 前序遍历 - -```java -class Solution { - //方法1 - public List preorderTraversal1(TreeNode root) { - List result = new ArrayList<>(); - LinkedList ll = new LinkedList<>(); //类型声明List改为LinkedList,List没有addFirst()/removeFirst()方法 - - while (root != null || !ll.isEmpty()) { - while (root != null) { - result.add(root.val); - ll.addFirst(root); - root = root.left; - } - root = ll.removeFirst(); - root = root.right; - } - - return result; - } - //方法2 - public List preorderTraversal2(TreeNode root) { - List result = new ArrayList<>(); - if(root == null) { - return result; - } - - Stack s = new Stack<>(); - s.push(root); - while(!s.isEmpty()) { - TreeNode node = s.pop(); - result.add(node.val); - if(node.right != null) { - s.push(node.right);//先压右,再压左 - } - if(node.left != null) { - s.push(node.left); - } - } - - return result; - } -} -``` - -### 中序遍历 - -```java - public List inorderTraversal(TreeNode root) { - List res = new ArrayList<>(); - Deque deque = new ArrayDeque<>(); - - while (!deque.isEmpty() || root != null) { - while (root != null) { - deque.push(root); - root = root.left; - } - root = deque.pop(); - res.add(root.val); - root = root.right; - } - - return res; - } -``` - -### 后序遍历 - -使用 null 作为标志位,访问到 null 说明此次递归调用结束。 - -```java -class Solution { - public List postorderTraversal(TreeNode root) { - List res = new LinkedList<>(); - if (root == null) { - return res; - } - - Stack stack = new Stack<>(); - stack.push(root); - while (!stack.isEmpty()) { - root = stack.pop(); - if (root != null) { - stack.push(root);//最后访问 - stack.push(null); - if (root.right != null) { - stack.push(root.right); - } - if (root.left != null) { - stack.push(root.left); - } - } else { //值为null说明此次递归调用结束,将节点值存进结果 - res.add(stack.pop().val); - } - } - - return res; - } -} -``` - -### 层序遍历 - -``` -class Solution { - public List> levelOrder(TreeNode root) { - List> res = new ArrayList<>(); - LinkedList queue = new LinkedList<>(); - if (root == null) { - return res; - } - queue.addLast(root); - while (!queue.isEmpty()) { - List levelList = new LinkedList<>(); - int size = queue.size(); - while (size-- > 0) { - root = queue.removeFirst(); - levelList.add(root.val); - if (root.left != null) { - queue.addLast(root.left); - } - if (root.right != null) { - queue.addLast(root.right); - } - } - res.add(levelList); - } - return res; - } -} -``` - -## 排序算法 - -常见的排序算法主要有:冒泡排序、插入排序、选择排序、快速排序、归并排序、堆排序、基数排序。各种排序算法的时间空间复杂度、稳定性见下图。 - -![](https://raw.githubusercontent.com/Tyson0314/img/master/排序算法.png) - -### 冒泡排序 - -```java - public void bubbleSort(int[] arr) { - if (arr == null) { - return; - } - boolean flag; - for (int i = arr.length - 1; i > 0; i--) { - flag = false; - for (int j = 0; j < i; j++) { - if (arr[j] > arr[j + 1]) { - int tmp = arr[j]; - arr[j] = arr[j + 1]; - arr[j + 1] = tmp; - flag = true; - } - } - if (!flag) { - return; - } - } - } -``` - -### 插入排序 - -```java - public void insertSort(int[] arr) { - if (arr == null) { - return; - } - for (int i = 1; i < arr.length; i++) { - int tmp = arr[i]; - int j = i; - for (; j > 0 && tmp < arr[j - 1]; j--) { - arr[j] = arr[j - 1]; - } - arr[j] = tmp; - } - } -``` - -### 选择排序 - -```java - public void selectionSort(int[] arr) { - if (arr == null) { - return; - } - for (int i = 0; i < arr.length - 1; i++) { - for (int j = i + 1; j < arr.length; j++) { - if (arr[i] > arr[j]) { - int tmp = arr[i]; - arr[i] = arr[j]; - arr[j] = tmp; - } - } - } - } -``` - -### 基数排序 - -在基数排序中,因为没有比较操作,所以在时间复杂上,最好的情况与最坏的情况在时间上是一致的,均为 O(d * (n + r))。d 为位数,r 为基数,n 为原数组个数。 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9pbWFnZXMyMDE4LmNuYmxvZ3MuY29tL2Jsb2cvMTI1MjkxMC8yMDE4MDkvMTI1MjkxMC0yMDE4MDkxMzExMDYyOTY4Ni00MDU0Mjk5NjkucG5n?x-oss-process=image/format,png) - -### 快速排序 - -快速排序是由**冒泡排序**改进而得到的,是一种排序执行效率很高的排序算法,它利用**分治法**来对待排序序列进行分治排序,它的思想主要是通过一趟排序将待排记录分隔成独立的两部分,其中的一部分比关键字小,后面一部分比关键字大,然后再对这前后的两部分分别采用这种方式进行排序,通过递归的运算最终达到整个序列有序。 - -快速排序的过程如下: - -1. 在待排序的N个记录中任取一个元素(通常取第一个记录)作为基准,称为基准记录; -2. 定义两个索引 left 和 right 分别表示首索引和尾索引,key 表示基准值; -3. 首先,尾索引向前扫描,直到找到比基准值小的记录,并替换首索引对应的值; -4. 然后,首索引向后扫描,直到找到比基准值大于的记录,并替换尾索引对应的值; -5. 若在扫描过程中首索引等于尾索引(left = right),则一趟排序结束;将基准值(key)替换首索引所对应的值; -6. 再进行下一趟排序时,待排序列被分成两个区:[0,left-1]和[righ+1,end] -7. 对每一个分区重复以上步骤,直到所有分区中的记录都有序,排序完成 - -快排为什么比冒泡效率高? - -快速排序之所以比较快,是因为相比冒泡排序,每次的交换都是跳跃式的,每次设置一个基准值,将小于基准值的都交换到左边,大于基准值的都交换到右边,这样不会像冒泡一样每次都只交换相邻的两个数,因此比较和交换的此数都变少了,速度自然更高。 - -快速排序的平均时间复杂度是O(nlgn),最坏时间复杂度是O(n^2)。 - -```java - public void quickSort(int[] arr) { - if (arr == null) { - return; - } - quickSortHelper(arr, 0, arr.length - 1); - } - private void quickSortHelper(int[] arr, int left, int right) { - if (left > right) { - return; - } - int tmp = arr[left]; - int i = left; - int j = right; - while (i < j) { - //j先走,最终循环终止时,j停留的位置就是arr[left]的正确位置 - //改为i<=j,则会进入死循环,[1,5,5,5,5]->[1] 5 [5,5,5]->[5,5,5],会死循环 - while (i < j && arr[j] >= tmp) { - j--; - } - while (i < j && arr[i] <= tmp) { - i++; - } - if (i < j) { - int tmp1 = arr[i]; - arr[i] = arr[j]; - arr[j] = tmp1; - } else { - break; - } - } - - //当循环终止的时候,i=j,因为是j先走的,j所在位置的值小于arr[left],交换arr[j]和arr[left] - arr[left] = arr[j]; - arr[j] = tmp; - - quickSortHelper(arr, left, j - 1); - quickSortHelper(arr, j + 1, right); - } -``` - -### 归并排序 - -归并排序 (merge sort) 是一类与插入排序、交换排序、选择排序不同的另一种排序方法。归并的含义是将两个或两个以上的有序表合并成一个新的有序表。归并排序有多路归并排序、两路归并排序 , 可用于内排序,也可以用于外排序。 - -两路归并排序算法思路是递归处理。每个递归过程涉及三个步骤 - -- 分解: 把待排序的 n 个元素的序列分解成两个子序列, 每个子序列包括 n/2 个元素 -- 治理: 对每个子序列分别调用归并排序MergeSort, 进行递归操作 -- 合并: 合并两个排好序的子序列,生成排序结果 - -![](https://raw.githubusercontent.com/Tyson0314/img/master/20220327151830.png) - -时间复杂度:对长度为n的序列,需进行logn次二路归并,每次归并的时间为O(n),故时间复杂度是O(nlgn)。 - -空间复杂度:归并排序需要辅助空间来暂存两个有序子序列归并的结果,故其辅助空间复杂度为O(n) - -```java -public class MergeSort { - public void mergeSort(int[] arr) { - if (arr == null || arr.length == 0) { - return; - } - //辅助数组 - int[] tmpArr = new int[arr.length]; - mergeSort(arr, tmpArr, 0, arr.length - 1); - } - - private void mergeSort(int[] arr, int[] tmpArr, int left, int right) { - if (left < right) { - int mid = (left + right) >> 1; - mergeSort(arr, tmpArr, left, mid); - mergeSort(arr, tmpArr, mid + 1, right); - merge(arr, tmpArr, left, mid, right); - } - } - - private void merge(int[] arr, int[] tmpArr, int left, int mid, int right) { - int i = left; - int j = mid + 1; - int tmpIndex = left; - while (i <= mid && j <= right) { - if (arr[i] < arr[j]) { - tmpArr[tmpIndex++] = arr[i]; - i++; - } else { - tmpArr[tmpIndex++] = arr[j]; - j++; - } - } - - while (i <= mid) { - tmpArr[tmpIndex++] = arr[i++]; - } - - while (j <= right) { - tmpArr[tmpIndex++] = arr[j++]; - } - - for (int m = left; m <= right; m++) { - arr[m] = tmpArr[m]; - } - } -} -``` - -### 堆排序 - -堆是具有下列性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。 - -![](https://img-blog.csdn.net/20150312212515074) - -**Top大问题**解决思路:使用一个固定大小的**最小堆**,当堆满后,每次添加数据的时候与堆顶元素比较,若小于堆顶元素,则舍弃,若大于堆顶元素,则删除堆顶元素,添加新增元素,对堆进行重新排序。 - -对于n个数,取Top m个数,时间复杂度为O(nlogm),这样在n较大情况下,是优于nlogn(其他排序算法)的时间复杂度的。 - -PriorityQueue 是一种基于优先级堆的优先级队列。每次从队列中取出的是具有最高优先权的元素。如果不提供Comparator的话,优先队列中元素默认按自然顺序排列,也就是数字默认是小的在队列头。优先级队列用数组实现,但是数组大小可以动态增加,容量无限。 - -```java -//找出前k个最大数,采用小顶堆实现 -public static int[] findKMax(int[] nums, int k) { - PriorityQueue pq = new PriorityQueue<>(k);//队列默认自然顺序排列,小顶堆,不必重写compare - - for (int num : nums) { - if (pq.size() < k) { - pq.offer(num); - } else if (pq.peek() < num) {//如果堆顶元素 < 新数,则删除堆顶,加入新数入堆 - pq.poll(); - pq.offer(num); - } - } - - int[] result = new int[k]; - for (int i = 0; i < k&&!pq.isEmpty(); i++) { - result[i] = pq.poll(); - } - return result; -} -``` - - - diff --git "a/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\347\275\221\347\273\234/TCP IP\345\215\217\350\256\256.md" "b/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\347\275\221\347\273\234/TCP IP\345\215\217\350\256\256.md" deleted file mode 100644 index 0415e38..0000000 --- "a/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\347\275\221\347\273\234/TCP IP\345\215\217\350\256\256.md" +++ /dev/null @@ -1,349 +0,0 @@ - - - -- [概述](#%E6%A6%82%E8%BF%B0) - - [分层](#%E5%88%86%E5%B1%82) - - [封装](#%E5%B0%81%E8%A3%85) - - [分用](#%E5%88%86%E7%94%A8) - - [端口号](#%E7%AB%AF%E5%8F%A3%E5%8F%B7) -- [链路层](#%E9%93%BE%E8%B7%AF%E5%B1%82) - - [SLIP](#slip) - - [PPP 点对点协议](#ppp-%E7%82%B9%E5%AF%B9%E7%82%B9%E5%8D%8F%E8%AE%AE) - - [以太网](#%E4%BB%A5%E5%A4%AA%E7%BD%91) -- [网际协议](#%E7%BD%91%E9%99%85%E5%8D%8F%E8%AE%AE) - - [IP 首部](#ip-%E9%A6%96%E9%83%A8) - - [IP 路由选择](#ip-%E8%B7%AF%E7%94%B1%E9%80%89%E6%8B%A9) - - [子网寻址](#%E5%AD%90%E7%BD%91%E5%AF%BB%E5%9D%80) -- [ARP](#arp) - - [工作原理](#%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86) - - [分组格式](#%E5%88%86%E7%BB%84%E6%A0%BC%E5%BC%8F) - - [ARP 代理](#arp-%E4%BB%A3%E7%90%86) -- [RARP](#rarp) - - [分组格式](#%E5%88%86%E7%BB%84%E6%A0%BC%E5%BC%8F-1) -- [ICMP](#icmp) - - [ICMP 报文类型](#icmp-%E6%8A%A5%E6%96%87%E7%B1%BB%E5%9E%8B) - - [ICMP 地址掩码请求与应答](#icmp-%E5%9C%B0%E5%9D%80%E6%8E%A9%E7%A0%81%E8%AF%B7%E6%B1%82%E4%B8%8E%E5%BA%94%E7%AD%94) - - [ICMP 时间戳请求与应答](#icmp-%E6%97%B6%E9%97%B4%E6%88%B3%E8%AF%B7%E6%B1%82%E4%B8%8E%E5%BA%94%E7%AD%94) - - [ICMP 端口不可达差错](#icmp-%E7%AB%AF%E5%8F%A3%E4%B8%8D%E5%8F%AF%E8%BE%BE%E5%B7%AE%E9%94%99) -- [Traceroute](#traceroute) -- [IP 选路](#ip-%E9%80%89%E8%B7%AF) -- [广播和多播](#%E5%B9%BF%E6%92%AD%E5%92%8C%E5%A4%9A%E6%92%AD) -- [IGMP](#igmp) -- [UDP](#udp) - - [特点](#%E7%89%B9%E7%82%B9) - - [首部](#%E9%A6%96%E9%83%A8) -- [TCP](#tcp) - - [特点](#%E7%89%B9%E7%82%B9-1) - - [停止等待协议](#%E5%81%9C%E6%AD%A2%E7%AD%89%E5%BE%85%E5%8D%8F%E8%AE%AE) - - [首部](#%E9%A6%96%E9%83%A8-1) -- [DNS](#dns) -- [TFTP](#tftp) -- [BOOTP](#bootp) - - - -## 概述 - -### 分层 - -1. 数据链路层:有时称作网络接口层,通常包括操作系统的设备驱动程序和计算机中对应的网络接口卡。 -2. 网络层:处理分组在网络中的活动,如分组的选路。 -3. 运输层:主要为两台主机的应用程序进程之间的通信提供通用的数据传输服务。通用是指多种应用可以使用同一个运输层服务。 -4. 应用层:应用进程间通信和交互的规则。 - -### 封装 - -TCP 传给 IP 的数据单元称作 TCP报文段。IP 传给网络接口层的数据单元称作 IP 数据报。通过以太网传输的比特流称作帧。 - -![数据进入协议栈时的封装过程](https://img2018.cnblogs.com/blog/1252910/201908/1252910-20190817161255444-332455631.png) - -应用程序使用 TCP/UDP 传输数据,运输层协议在生成报文首部时要存入一个应用程序的标识。TCP、UDP 使用16 bit 的端口号来表示不同的应用程序,把源端口号和目的端口号分别存入报文首部。 - -TCP 报文进入 IP 层时,IP 层会在首部存入一个长度为8 bit 的数值,称作协议域。1表示为 ICMP 协议, 2表示为 IGMP 协议, 6表示为 TCP 协议, 17表示为 UDP 协议。 - -数据从网络层进入数据链路层时,需要在帧首部加入一个16 bit的帧类型域,以指明生成数据的网络层协议。 - -### 分用 - -当目的主机收到一个以太网数据帧时,数据就开始从协议栈由下而上进行分用,同时去掉各层协议的首部。每个协议层都会检查报文首部的协议标识,从而确定接收数据的上层协议。 - -![以太网数据帧分用过程](https://img2018.cnblogs.com/blog/1252910/201908/1252910-20190817163045743-889709219.png) - -### 端口号 - -服务器一般使用知名的端口号,FTP 服务器的 TCP 端口号是21,Telnet 服务器的 TCP 端口号是23。客户端端口号也称为临时端口号,只有用户运行了这个应用程序时端口号才存在。 - - - -## 链路层 - -TCP/IP 协议族中,链路层主要有三个目的:1.为 IP 模块发送和接收 IP 数据报;2.为 ARP 模块发送ARP 请求和接收 ARP 应答;3.为 RARP 发送 RARP 请求和接收 RARP 应答。 -数据链路层使用的信道主要有以下两种类型: -- 点对点信道 -- 广播信道 - -### SLIP - -SLIP 的全程是 Serial Line IP。它是一种在串行线路上对 ip 数据报进行封装的简单格式。 - -SLIP 是一种简单的帧封装方法,有以下缺陷: - -1. 每一端必须知道对方的I P地址; -2. 数据帧中没有类型字段(类似于以太网中的类型字段)。如果一条串行线路用于 SLIP,那么它不能同时使用其他协议; -3. 没有在数据帧中加上检验和。 - -### PPP 点对点协议 - -PPP 修改了 SLIP 协议中的所有缺陷,有三个组成部分: - -1. 在串行链路上封装I IP 数据报的方法; -2. 一个用来建立、配置和测试数据链路连接的链路控制协议LCP; -3. 一套网络控制协议NCP。 - -MTU:最大传输单元。如果 IP 层传输数据比链路层的 MTU 大,那么需要在 IP 层对数据进行分片。 - - - -环回接口(Loopback Interface)允许运行在同一台主机上的客户端和服务器通过 tcp/ip 进行通信。大多数系统把 ip 地址127.0.0.1 分配给这个接口,命名为 localhost。一个传给环回接口的数据报不能出现在任何网络上。 - -### 以太网 -在局域网中,硬件地址又称为物理地址或MAC地址。 -![MAC帧格式](https://img-blog.csdnimg.cn/20190824171916714.png) - - - -## 网际协议 - -IP 协议特点有: - -1. 不可靠。不能保证 IP 数据报能成功地到达目的地。IP 仅提供最好的传输服务。如果发生某种错误时,如某个路由器暂时用完了缓冲区, IP有一个简单的错误错误处理算法:丢弃该数据报,然后发送 ICMP 消息报给信源端。任何要求的可靠性必须由上层来提供。 -2. 无连接。IP 并不维护任何关于后续数据报的状态信息。 - -### IP 首部 - -IP 数据报的格式: - -![ip首部](https://img2018.cnblogs.com/blog/1252910/201909/1252910-20190912193522827-1130088556.png) - -TTL 设置了数据报可以经过的最多路由器数目。TTL 初始值由主机设置,每经过一个路由器它的值会减一。当该字段的值为0时,数据报就被丢弃,并发送 ICMP 报文通知源主机。 - -### IP 路由选择 - -IP 层可以配置成路由器的功能,也可以配置成主机的功能。 - -路由表包含的信息:1.目的 ip 地址;2.下一跳路由器的 ip 地址;3.标志。其中一个标志指明目的 ip 地址是网络地址还是主机地址,另一个标志指明下一跳路由器是否是真正的路由器,还是一个直接连接的接口。 - -ip 路由选择主要完成以下功能: - -1. 搜索路由表,寻找能与目的 ip 地址完全匹配的表目(网络号和主机号都要匹配); -2. 搜索路由表,寻找能与目的网络号相匹配的表目; -3. 搜索路由表,寻找默认路由。 - -如果上述步骤都没有成功,那么该数据报不能被传送。 - -为一个网络指定一个路由器,而不是为每个主机指定路由器,可以极大的缩小路由表的规模。 - -### 子网寻址 - -B 类地址:16位网络号;8位子网号;8位主机号 - -子网对外部路由器来说隐藏了内部网络组织(一个校园或公司内部)的细节。 - -通过 ip 地址可以分辨属于 A/B/C/D 类地址。通过子网掩码可以知道子网号和主机号之间的分界线。 - -![ip地址分类](https://img2018.cnblogs.com/blog/1252910/201908/1252910-20190822111405226-1329649757.png) - - - -## ARP - -地址解析协议。ARP 的功能是在同一个局域网内为32 bit的IP地址和采用不同网络技术的硬件地址之间提供动态映射。 - -### 工作原理 - -首先,每台主机都会在自己的ARP缓冲区中建立一个ARP列表,以表示IP地址和MAC地址的对应关系。当源主机需要将一个数据包要发送到目的主机时,会首先检查自己 ARP列表中是否存在该 IP地址对应的MAC地址,如果有,就直接将数据包发送到这个MAC地址;如果没有,就向本地网段发起一个ARP请求的广播包,查询此目的主机对应的MAC地址。此ARP请求数据包里包括源主机的IP地址、硬件地址、以及目的主机的IP地址。网络中所有的主机收到这个ARP请求后,会检查数据包中的目的IP是否和自己的IP地址一致。如果不相同就忽略此数据包;如果相同,该主机首先将发送端的MAC地址和IP地址添加到自己的ARP列表中,如果ARP表中已经存在该IP的信息,则将其覆盖,然后给源主机发送一个 ARP响应数据包,告诉对方自己是它需要查找的MAC地址;源主机收到这个ARP响应数据包后,将得到的目的主机的IP地址和MAC地址添加到自己的ARP列表中,并利用此信息开始数据的传输。如果源主机一直没有收到ARP响应数据包,表示ARP查询失败。 - -### 分组格式 - -![ARP分组格式](https://img2018.cnblogs.com/blog/1252910/201908/1252910-20190819170719796-1815161798.png) - -目的地址为全1的特殊地址是广播地址。电缆上的所有以太网接口都要接收广播的数据帧。 - -两个字节长的以太网帧类型表示后面数据的类型。对于A R P请求或应答来说,该字段的值为0x0806。 - -硬件类型字段表示硬件地址的类型。它的值为1即表示以太网地址。协议类型字段表示要映射的协议地址类型。它的值为0x0800即表示 IP 地址。 - -操作字段 op 有四种类型,ARP 请求(值为1)、ARP 应答(2)、RARP 请求(3)和 RARP 应答(4)。这个字段是必须的,因为 ARP 请求和 ARP 应答的帧类型值是一样的。 - -对于一个 ARP 请求,除了目的端硬件地址(以太网地址)外的所有字段都需要填满。当系统收到一个目的主机为本机的ARP 请求后,它会把自己的硬件地址填写进去,然后将两个目的端硬件地址改成发送 ARP 请求的主机的硬件地址。 - -### ARP 代理 - -如果 APR 请求是从一个网络的主机发往另一个网络上的主机,那么连接这两个网络的路由器就可以回答该请求,这个过程称作 ARP 代理。这样可以欺骗发起 ARP 请求的发送端,使它误以为路由器就是目的主机,而事实上目的主机是在路由器的“另一边”。路由器的功能相当于目的主机的代理,把分组从其他主机转发给它。 - - - -## RARP - -逆地址解析协议。具有本地磁盘的系统引导时,一般从磁盘的配置文件读取 IP 地址。而无盘系统通过 RARP 协议来读取 ip:从接口卡上读取唯一的硬件地址,在分组中表明发送端的硬件地址,然后发送 RARP 请求,请求某个主机响应该无盘系统的 IP 地址(RARP 应答中)。 - -### 分组格式 - -RARP 的分组格式和 ARP 的分组格式类似,主要区别是 RARP 请求或应答的帧类型是0x8053,而且 RARP 请求的 op 操作代码是3,RARP 应答的 op 操作代码是4。 - - - -## ICMP - -Internet 控制报文协议。ICMP 通常被认为是 IP 层的组成部分。它被用来传递报文以及其他需要注意的信息。ICMP 报文通常被 IP 层或者更高层的协议使用。 - -### ICMP 报文类型 - -查询报文和差错报文。 - -### ICMP 地址掩码请求与应答 - -ICMP 地址掩码请求用于无盘系统在引导过程获取自己的子网掩码。 - -### ICMP 时间戳请求与应答 - -ICMP 时间戳请求系统向另一个系统查询当前时间。 - -### ICMP 端口不可达差错 - -如果收到一份 UDP 数据报而目的端口与某个正在使用的进程不相符,那么 UDP 就会返回一个端口不可达报文。 - - - -## Traceroute - -Traceroute 程序的操作过程。开始时发送一个 TTL 字段为1的 UDP 数据报,然后将 TTL 字段每次加1,以确定路径中的每个路由器。每个路由器在丢弃 UDP 数据报时都返回一个 ICMP 超时报文,而最终目的主机则产生一个ICMP 端口不可达的报文(Traceroute 程序发送数据报给目的主机时指定了一个不可能的值作为 UDP 端口,目的主机任何应用程序都不可能使用此端口)。 - -对于每个 TTL 值会发送三份数据报,每接收到一份 ICMP 报文,就计算并打印出往返时间。如果在5秒种内仍未收到3份数据报的任意一份的响应,则打印一个星号,并发送下一份数据报。 - -`tracert baid.com` - -![traceroute命令](https://img2018.cnblogs.com/blog/1252910/201908/1252910-20190820112635665-2041876448.png) - - - -## IP 选路 - -系统产生的或转发的每份 IP 数据报都要搜索路由表,它可以被路由守护程序或 ICMP 重定 -向报文修改。 - - - -## 广播和多播 - -三种 IP 地址:单播地址、广播地址和多播地址。广播和多播仅适用于 UDP。多播的数据帧仅发送给属于多播组的多个主机。 - -网卡查看由信道传送过来的帧,确定是否接收该帧,若接收后就将它传往设备驱动程序。通常网卡仅接收那些目的地址为网卡物理地址或广播地址的帧。 - -如果网卡收到一个帧,这个帧将被传送给设备驱动程序(如果帧检验和错,网卡将丢弃该帧)。设备驱动程序将进行另外的帧过滤。首先,帧类型中必须指定要使用的协议( IP、ARP等等)。其次,进行多播过滤来检测该主机是否属于多播地址说明的多播组。 - -广播是将数据报发送到网络中的所有主机(通常是本地相连的网络),而多播是将数据报发送到网络的一个主机组。。在许多应用中,多播比广播更好,因为多播降低了不参与通信的主机的负担。 - - - -## IGMP - -Internet 组管理协议用于支持主机和路由器进行多播。它让一个物理网络上的所有系统知道当前主机所在的多播组。。多播路由器需要这些信息以便知道多播数据报应该向哪些接口转发。跟 ICMP 一样,IGMP 也被当做 IP 层的一部分,IGMP 报文通过 IP 数据报进行传输。IGMP 有固定的报文长度,没有可选的数据。 - - - -## UDP - -UDP 是一个简单的面向数据报的运输层协议。 - -### 特点 - -无连接:发送数据之前不需要建立连接。 - -不可靠的服务:尽最大努力的交互,主机不需要维持复杂的连接状态表。 - -面向报文:发送方的 UDP 对应用程序传进来的报文,在添加首部后就向下交给 IP 层。 - -没有拥塞控制:网络出现拥塞不会使源主机的发送速率变慢。 - -支持一对一、一对多、多对多的交互通信。 - -UDP 的首部开销小。 - -### 首部 - -![UDP 首部](https://img2018.cnblogs.com/blog/1252910/201908/1252910-20190823160458588-1745399117.png) - -伪首部既不向上传送也不向下提交,而仅仅为了计算检验和。伪首部第三字段是全0,第四个字段是 IP 首部中协议字段的值,对于 UDP,此协议字段值为17。 - - - -## TCP - -TCP 提供一种面向连接的、可靠的字节流服务。 - -![使用TCP/UDP的各种应用](https://img2018.cnblogs.com/blog/1252910/201908/1252910-20190823155102643-1792038068.png) - -### 特点 - -1. 面向连接。应用程序传输数据之前需要先建立连接。 -2. 提供可靠交互的服务。 -3. 全双工通信。TCP 连接的两端都设有发送缓存和接收缓存,用来临时存放双向通信的数据。 -4. 面向字节流。TCP 把应用程序交下来的数据仅仅看成一串无结构的字节流。 - -### 停止等待协议 - -每发送一个分组就会设置一个超时计数器,如果在计数器没有过期之前收到了对方的确认,就撤销超时计数器。 - -![停止等待协议](https://img2018.cnblogs.com/blog/1252910/201908/1252910-20190823162130085-100928361.png) - -a 图 B 对 M1 的确认丢失了,A 会重传 M1,然后 B 会收到重复的 M1,将其丢弃,重传确认。 - -b 图 B 对 M1 的确认在网络滞留,A重传M1,最后会收到两次对M1确认,A会手下迟到的确认但是什么都不做。 - -![确认丢失和确认迟到](https://img2018.cnblogs.com/blog/1252910/201908/1252910-20190823163614916-2026615611.png) - -### 首部 - -![TCP首部](https://img2018.cnblogs.com/blog/1252910/201908/1252910-20190823164725621-1852408493.png) - -序号:TCP 连接中传送的字节流中的每一个字节都按顺序编号。 - -数据偏移:TCP 报文段的数据起始处距离 TCP 报文段的起始处有多远。 - -紧急 URG:当 URG = 1时,表明紧急指针字段有效,它告诉系统此报文段有紧急数据,应该尽快传送,不要按照之前的排队顺序来传送。 - -推送 PSH:接收方收到一个 PSH = 1 的报文段,就会尽快把数据交互到应用程序,而不再等到整个缓存填充满了再向上交互。 - -复位 RST:当 RST = 1 时,表明 TCP 连接中出现了严重差错,必须释放连接,重新建立连接。 - -同步 SYN:建立连接同步序号。 - -窗口:窗口值表明接收方能接受多少数据。窗口值作为接收方让发送方设置其发送窗口的依据。 - -检验和:检验的范围包括首部和数据。 - -紧急指针:URG = 1时有效,指明紧急字段的末尾在报文段的位置。在窗口为0时也可以发送紧急数据。 - -最大报文段长度 MSS:默认值是536字节。 - - - -## DNS - -域名系统是一种用于 TCP/IP 应用程序的分布式数据库,它提供主机名字和 IP 地址之间的转换及有关电子邮件的选路信息。 - - - -## TFTP - -简单文件传输协议是一个简单的协议,适合于只读存储器,仅用于无盘系统进行系统引导。TFTP 使用不可靠的 UDP,需要处理分组丢失和分组重复。 - -![tftp命令](https://img2018.cnblogs.com/blog/1252910/201908/1252910-20190822211859937-1502767473.png) - - - -## BOOTP - -BOOTP 使用 UDP,它为引导无盘系统获得它的 IP 地址提供了除 RARP 外的另外一种选择。 - diff --git "a/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\347\275\221\347\273\234/session\345\222\214cookie.md" "b/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\347\275\221\347\273\234/session\345\222\214cookie.md" deleted file mode 100644 index 6cc4584..0000000 --- "a/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\347\275\221\347\273\234/session\345\222\214cookie.md" +++ /dev/null @@ -1,23 +0,0 @@ -由于HTTP协议是无状态的协议,需要用某种机制来识具体的用户身份,用来跟踪用户的整个会话。常用的会话跟踪技术是cookie与session。 - -## cookie - -cookie就是由服务器发给客户端的特殊信息,而这些信息以文本文件的方式存放在客户端,然后客户端每次向服务器发送请求的时候都会带上这些特殊的信息。说得更具体一些:当用户使用浏览器访问一个支持cookie的网站的时候,用户会提供包括用户名在内的个人信息并且提交至服务器;接着,服务器在向客户端回传相应的超文本的同时也会发回这些个人信息,当然这些信息并不是存放在HTTP响应体中的,而是存放于HTTP响应头;当客户端浏览器接收到来自服务器的响应之后,浏览器会将这些信息存放在一个统一的位置。 自此,客户端再向服务器发送请求的时候,都会把相应的cookie存放在HTTP请求头再次发回至服务器。服务器在接收到来自客户端浏览器的请求之后,就能够通过分析存放于请求头的cookie得到客户端特有的信息,从而动态生成与该客户端相对应的内容。网站的登录界面中“请记住我”这样的选项,就是通过cookie实现的。 -![](https://img-blog.csdn.net/20180924195237286?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1R5c29uMDMxNA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) - -cookie工作流程: - -1. servlet创建cookie,保存少量数据,发送给浏览器。 -2. 浏览器获得服务器发送的cookie数据,将自动的保存到浏览器端。 -3. 下次访问时,浏览器将自动携带cookie数据发送给服务器。 - -## session - -session原理:首先浏览器请求服务器访问web站点时,服务器首先会检查这个客户端请求是否已经包含了一个session标识、称为SESSIONID,如果已经包含了一个sessionid则说明以前已经为此客户端创建过session,服务器就按照sessionid把这个session检索出来使用,如果客户端请求不包含session id,则服务器为此客户端创建一个session,并且生成一个与此session相关联的独一无二的sessionid存放到cookie中,这个sessionid将在本次响应中返回到客户端保存,这样在交互的过程中,浏览器端每次请求时,都会带着这个sessionid,服务器根据这个sessionid就可以找得到对应的session。以此来达到共享数据的目的。 这里需要注意的是,session不会随着浏览器的关闭而死亡,而是等待超时时间。 -session的工作原理就是依靠cookie来做支撑,第一次使用request.getsession()时session被创建 -![](https://img-blog.csdn.net/20180924195247106?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1R5c29uMDMxNA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) - -## 区别 - -session是**服务器端**记录用户信息的一种机制,用来跟踪用户的状态,这个数据可以保存在集群、数据库、文件中;cookie是**客户端**保存用户信息的一种机制,用来记录用户的一些信息,也是实现session的一种方式。 - diff --git "a/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\347\275\221\347\273\234/\347\275\221\347\273\234.md" "b/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\347\275\221\347\273\234/\347\275\221\347\273\234.md" deleted file mode 100644 index 60a94c1..0000000 --- "a/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\347\275\221\347\273\234/\347\275\221\347\273\234.md" +++ /dev/null @@ -1,362 +0,0 @@ - - - - -- [三次握手](#%E4%B8%89%E6%AC%A1%E6%8F%A1%E6%89%8B) -- [四次挥手](#%E5%9B%9B%E6%AC%A1%E6%8C%A5%E6%89%8B) -- [滑动窗口](#%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3) -- [拥塞控制](#%E6%8B%A5%E5%A1%9E%E6%8E%A7%E5%88%B6) - - [慢开始](#%E6%85%A2%E5%BC%80%E5%A7%8B) - - [拥塞避免](#%E6%8B%A5%E5%A1%9E%E9%81%BF%E5%85%8D) - - [快重传](#%E5%BF%AB%E9%87%8D%E4%BC%A0) - - [快恢复](#%E5%BF%AB%E6%81%A2%E5%A4%8D) -- [TCP和UDP](#tcp%E5%92%8Cudp) - - [TCP的特点](#tcp%E7%9A%84%E7%89%B9%E7%82%B9) - - [TCP和UDP区别](#tcp%E5%92%8Cudp%E5%8C%BA%E5%88%AB) - - [协议](#%E5%8D%8F%E8%AE%AE) - - [TCP首部](#tcp%E9%A6%96%E9%83%A8) - - [UDP首部](#udp%E9%A6%96%E9%83%A8) -- [http](#http) - - [主要特点](#%E4%B8%BB%E8%A6%81%E7%89%B9%E7%82%B9) - - [http请求](#http%E8%AF%B7%E6%B1%82) - - [http响应](#http%E5%93%8D%E5%BA%94) - - [http状态码](#http%E7%8A%B6%E6%80%81%E7%A0%81) - - [post和get](#post%E5%92%8Cget) - - [HTTPS](#https) - - [数字证书](#%E6%95%B0%E5%AD%97%E8%AF%81%E4%B9%A6) - - [原理](#%E5%8E%9F%E7%90%86) - - [HTTPS和HTTP的区别](#https%E5%92%8Chttp%E7%9A%84%E5%8C%BA%E5%88%AB) - - [浏览器中输入URL返回页面过程](#%E6%B5%8F%E8%A7%88%E5%99%A8%E4%B8%AD%E8%BE%93%E5%85%A5url%E8%BF%94%E5%9B%9E%E9%A1%B5%E9%9D%A2%E8%BF%87%E7%A8%8B) - - [长连接](#%E9%95%BF%E8%BF%9E%E6%8E%A5) -- [网络分层结构](#%E7%BD%91%E7%BB%9C%E5%88%86%E5%B1%82%E7%BB%93%E6%9E%84) -- [DNS解析](#dns%E8%A7%A3%E6%9E%90) -- [加密算法](#%E5%8A%A0%E5%AF%86%E7%AE%97%E6%B3%95) -- [CDN](#cdn) -- [参考资料](#%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99) - - - -# 三次握手 - -1. 第一次握手:客户端A向B发出连接请求报文段(首部的同步位SYN=1,初始序号seq=x),此时客户端A进入SYN-SENT(同步已发送)状态。 -2. 第二次握手:B收到连接请求报文段后,如同意建立连接,则向A发送确认(确认报文段中SYN=1,ACK=1,确认号ack=x+1,初始序号seq=y),B进入SYN-RCVD(同步收到)状态; -3. 第三次握手:A收到B的确认后,向B发送确认报文段(ACK=1,确认号ack=y+1,序号seq=x+1)。然后A进入ESTABLISHED。 -4. B收到确认报文段,就会进入ESTABLISHED状态,TCP连接成功。 - -![三次握手](https://img2018.cnblogs.com/blog/1252910/201909/1252910-20190912193729969-2125931519.png) - -为什么A还要发送一次确认呢?可以二次握手吗? - -主要为了防止已失效的连接请求报文段突然又传送到了B,因而产生错误。如A发出连接请求,可能因为网络阻塞原因,A没有收到确认报文,于是A再重传一次连接请求。连接成功,等待数据传输完毕后,就释放了连接。而A发出的第一个连接请求等到连接释放以后的某个时间才到达B,此时B误认为A又发出一次新的连接请求,于是就向A发出确认报文段,同意建立连接,不采用三次握手,只要B发出确认,就建立新的连接了,此时A不理睬B的确认且不发送数据,则B一直等待A发送数据,浪费资源。 - - -# 四次挥手 - -1. A的应用进程先向其TCP发出连接释放报文段(FIN=1,序号seq=u),并停止再发送数据,主动关闭TCP连接,进入FIN-WAIT-1(终止等待1)状态,等待B的确认。 -2. B收到连接释放报文段后即发出确认报文段(ACK=1,确认号ack=u+1,序号seq=v),B进入CLOSE-WAIT(关闭等待)状态,此时的TCP处于半关闭状态,A到B的连接释放。 -3. A收到B的确认后,进入FIN-WAIT-2(终止等待2)状态,等待B发出的连接释放报文段。 -4. B发送完数据,就会发出连接释放报文段(FIN=1,ACK=1,序号seq=w,确认号ack=u+1),B进入LAST-ACK(最后确认)状态,等待A的确认。 -5. A收到B的连接释放报文段后,对此发出确认报文段(ACK=1,seq=u+1,ack=w+1),A进入TIME-WAIT(时间等待)状态。此时TCP未释放掉,需要经过时间等待计时器设置的时间2MSL(最大报文段生存时间)后,A才进入CLOSED状态。B收到A发出的确认报文段后关闭连接,若没收到A发出的确认报文段,B就会重传连接释放报文段。 - -![四次挥手](https://img2018.cnblogs.com/blog/1252910/201908/1252910-20190823193334395-1767472912.png) - -为什么A在TIME-WAIT状态必须等待2MSL(最大报文段生存时间)的时间? - -- 保证A发送的最后一个ACK报文段能够到达B。这个ACK报文段有可能丢失,B收不到这个确认报文,就会超时重传连接释放报文段,然后A可以在2MSL时间内收到这个重传的连接释放报文段,接着A重传一次确认,重新启动2MSL计时器,最后A和B都进入到CLOSED状态,若A在TIME-WAIT状态不等待一段时间,而是发送完ACK报文段后立即释放连接,则无法收到B重传的连接释放报文段,所以不会再发送一次确认报文段,B就无法正常进入到CLOSED状态。 -- 防止已失效的连接请求报文段出现在本连接中。A在发送完最后一个ACK报文段后,再经过2MSL,就可以使这个连接所产生的所有报文段都从网络中消失,使下一个新的连接中不会出现旧的连接请求报文段。 - -为什么连接的时候是三次握手,关闭的时候却是四次握手? - -- 因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到连接释放报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,"你发的连接释放报文我收到了"。只有等到Server端所有的报文都发送完了,才能发送连接释放报文,因此不能一起发送。故需要四步握手。 - -# 滑动窗口 - -TCP 利用滑动窗口实现流量控制。流量控制是为了控制发送方发送速率,保证接收方来得及接收。 TCP会话的双方都各自维护一个发送窗口和一个接收窗口。接收窗口大小取决于应用、系统、硬件的限制。发送窗口则取决于对端通告的接收窗口。接收方发送的确认报文中的window字段可以用来控制发送方窗口大小,从而影响发送方的发送速率。将接收方的确认报文window字段设置为 0,则发送方不能发送数据。 - -![滑动窗口](https://img2018.cnblogs.com/blog/1252910/201908/1252910-20190823173235953-969655958.png) - - -TCP头包含window字段,16bit位,它代表的是窗口的字节容量,最大为65535。这个字段是接收端告诉发送端自己还有多少缓冲区可以接收数据。于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来。接收窗口的大小是约等于发送窗口的大小。 - -# 拥塞控制 - -防止过多的数据注入到网络中。 几种拥塞控制方法:慢开始( slow-start )、拥塞避免( congestion avoidance )、快重传( fast retransmit )和快恢复( fast recovery )。 - -![](../img/拥塞控制.jpg) - -## 慢开始 - -把拥塞窗口 cwnd 设置为一个最大报文段MSS的数值。而在每收到一个对新的报文段的确认后,把拥塞窗口增加至多一个MSS的数值。每经过一个传输轮次,拥塞窗口 cwnd 就加倍。 为了防止拥塞窗口cwnd增长过大引起网络拥塞,还需要设置一个慢开始门限ssthresh状态变量。 - - 当 cwnd < ssthresh 时,使用慢开始算法。 - - 当 cwnd > ssthresh 时,停止使用慢开始算法而改用拥塞避免算法。 - - 当 cwnd = ssthresh 时,既可使用慢开始算法,也可使用拥塞控制避免算法。 - -## 拥塞避免 - -让拥塞窗口cwnd缓慢地增大,每经过一个往返时间RTT就把发送方的拥塞窗口cwnd加1,而不是加倍。这样拥塞窗口cwnd按线性规律缓慢增长。 - -无论在慢开始阶段还是在拥塞避免阶段,只要发送方判断网络出现拥塞(其根据就是没有收到确认),就要把慢开始门限ssthresh设置为出现拥塞时的发送 方窗口值的一半(但不能小于2)。然后把拥塞窗口cwnd重新设置为1,执行慢开始算法。这样做的目的就是要迅速减少主机发送到网络中的分组数,使得发生 拥塞的路由器有足够时间把队列中积压的分组处理完毕。 - -## 快重传 - -有时个别报文段会在网络中丢失,但实际上网络并未发生拥塞。如果发送方迟迟收不到确认,就会产生超时,就会误认为网络发生了拥塞。这就导致发送方错误地启动慢开始,把拥塞窗口cwnd又设置为1,因而降低了传输效率。 - -快重传算法可以避免这个问题。快重传算法首先要求接收方每收到一个失序的报文段后就立即发出重复确认,使发送方及早知道有报文段没有到达对方。 - -发送方只要一连收到三个重复确认就应当立即重传对方尚未收到的报文段,而不必继续等待重传计时器到期。由于发送方尽早重传未被确认的报文段,因此采用快重传后可以使整个网络吞吐量提高约20%。 - -## 快恢复 - -当发送方连续收到三个重复确认,就会把慢开始门限ssthresh减半,接着把cwnd值设置为慢开始门限ssthresh减半后的数值,然后开始执行拥塞避免算法,使拥塞窗口缓慢地线性增大。 - -在采用快恢复算法时,慢开始算法只是在TCP连接建立时和网络出现超时时才使用。 采用这样的拥塞控制方法使得TCP的性能有明显的改进。 - - - -# TCP和UDP - -TCP:传输控制协议。 - -UDP:用户数据报协议。 - -## TCP的特点 - -- TCP是面向连接的运输层协议 -- 每一条TCP连接只能有两个端点(一对一) -- TCP提供可靠交付的服务 -- TCP提供全双工通信 -- 面向字节流 - -- TCP可靠传输、流量控制和拥塞控制的实现 -TCP的可靠性如何保证:对于收到的请求,给出确认响应。 -流量控制:让发送方的发送速率不要太快,要让接收方来得及接收。利用滑动窗口实现流量控制。 -拥塞控制:防止过多的数据注入到网络中,这样可以使网络中的路由器或链路不致过载。 -TCP可靠传输是因为有: 数据报校验, 失序数据重排序, 丢弃重复数据,应答机制,超时重发,流量控制等原因 - -## TCP和UDP区别 -1. TCP面向连接,UDP是无连接的,即发送数据之前不需要建立连接 -2. TCP提供可靠的服务;UDP不保证可靠交付 -3. TCP面向字节流,把数据看成一连串无结构的字节流;UDP是面向报文的 -4. TCP有拥塞控制;UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等) -5. 每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信 -6. TCP首部开销20字节;UDP的首部开销小,只有8个字节 - -## 协议 - -TCP对应的协议: -(1)FTP:定义了文件传输协议,使用21端口。 -(2)Telnet:用于远程登陆的端口。 -(3)SMTP:定义了简单邮件传送协议,25端口。 -(4)POP3:它是和SMTP对应,POP3用于接收邮件,110端口。 -(5)HTTP协议:是从万维网服务器传输超文本到本地浏览器的传送协议,使用80端口。 - -UDP对应的协议: -(1)DNS:用于域名解析服务,将域名地址转换为IP地址。DNS用的是53号端口。 -(2)SNMP:简单网络管理协议,使用161号端口。 -(3)TFTP,Trival File Transfer Protocal,简单文件传输协议,使用端口69。 -(4)RIP,路由信息协议。 -(5)DHCP,动态主机配置协议。 - - -## TCP首部 - -![](https://img-blog.csdn.net/20180924195341168) - -## UDP首部 - -![](https://img-blog.csdn.net/20180924195345900?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1R5c29uMDMxNA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) - -# http - -超文本传输协议。 - -## 主要特点 - -1. 灵活:HTTP允许传输任意类型的数据。传输的类型由Content-Type加以标记。 -2. HTTP 0.9和1.0使用非持续连接:限制每次连接只处理一个请求,服务器处理完客户的请求,并收到客户的应答后,即断开连接。HTTP 1.1支持使用持续连接:一个连接可以处理多个请求,不必为每个请求创建一个新的连接,采用这种方式可以节省传输时间。 -3. 无状态:是指服务端对于客户端每次发送的请求都认为它是一个新的请求,上一次会话和下一次会话没有联系;HTTP 协议这种特性有优点也有缺点,优点在于解放了服务器,不会造成不必要连接占用,缺点在于如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。 - -## http请求 - -http请求由请求行、请求头部、空行和请求体四个部分组成。 - -![](https://img-blog.csdn.net/20180924195305583?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1R5c29uMDMxNA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) - -- 请求行:请求方法,访问的资源URL,使用的HTTP版本;GET和POST是最常见的HTTP方法,除此以外还包括DELETE、HEAD、OPTIONS、PUT、TRACE。 -- 请求头包含一些属性,格式为“属性名:属性值”,服务端据此获取客户端的信息,主要有cookie、host、connection、accept-language、accept-encoding、user-agent。 -- 请求体:用户的请求数据如用户名,密码等。 - -## http响应 - -HTTP响应也由四个部分组成,分别是:状态行、响应头、空行和响应体。 - -![](https://img-blog.csdn.net/20180924195318593?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1R5c29uMDMxNA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) - -- 状态行:协议版本,状态码及状态描述。 -- 响应头:connection、content-type、content-encoding、content-length、set-cookie、Last-Modified,、Cache-Control、Expires。 -- 响应体:服务器返回给客户端的内容。 - -## http状态码 - -![](https://img-blog.csdn.net/20180924195323664?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1R5c29uMDMxNA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) - -## post和get - -GET和POST是HTTP协议中的两种发送请求的方法。GET/POST请求都是TCP连接。 - -Get方法并没有限制提交的数据长度,HTTP协议规范没有对URL长度进行限制。但是浏览器及服务器可能会对URL长度有限制。 - -POST方法没有对请求体长度进行限制,但是服务端可以对POST数据大小进行限制,比如tomcat默认限制2M。可以修改conf/server.xml:`maxPostSize=0`,取消POST的大小限制。 - -post和get请求的区别: - -- GET请求参数通过URL传递,POST的参数放在请求体中。 -- GET产生一个TCP数据包;POST产生两个TCP数据包。对于GET方式的请求,浏览器会把请求头和请求体一并发送出去;而对于POST,浏览器先发送请求头,服务器响应100 continue,浏览器再发送请求体。 -- GET请求会被浏览器主动缓存,而POST不会,除非手动设置。 -- GET请求只能进行url编码,而POST支持多种编码方式。 -- GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。 - -## HTTPS - -HTTP协议以明文方式发送内容,不提供任何方式的数据加密,因此,HTTP协议不适合传输一些敏感信息,比如:信用卡号、密码等支付信息。而HTTPS协议是由SSL+HTTP协议构建的可进行身份认证(非对称加密)、加密传输(对称加密)的网络协议。SSL作用在应用层和运输层之间,在TCP之上建立起一个安全通道,确保数据传输安全。在 SSL 层完成身份认证、加解密操作。 - -### 数字证书 - -服务端可以向证书颁发机构CA申请证书,以避免中间人攻击(防止证书被篡改)。证书包含三部分内容:tbsCertificate(to be signed certificate)待签名证书内容、证书签名算法和CA给的签名(使用证书签名算法对tbsCertificate进行哈希运算得到哈希值,CA会用它的私钥对此哈希值进行签名,并放在签名部分)。签名是为了验证身份。 - -![](../img/net/https-certificate.png) - -服务端把证书传输给浏览器,浏览器从证书里取公钥。证书可以证明该公钥对应该网站。 - -数字签名的制作过程: - -1. CA使用证书签名算法对证书内容(待签名证书内容)进行hash。 -2. 对hash后的值用CA自己的私钥加密,得到数字签名。 - -浏览器验证过程: - -1. 拿到证书,得到证书内容、证书签名算法和数字签名。 -2. 用CA机构的公钥对数字签名解密(由于是浏览器信任的机构,所以浏览器会保存它的公钥)。 -3. 用证书里的签名算法对证书内容进行hash。 -4. 比较解密后的数字签名和对证书内容hash后的哈希值,相等则表明证书可信。 - -### 原理 - -首先是TCP三次握手,然后客户端(浏览器)发起一个HTTPS连接建立请求,客户端先发一个Client Hello的包,然后服务端响应一个Server Hello,接着再给客户端发送它的证书,然后双方经过密钥交换,最后使用交换的密钥加解密数据。 - -1. 协商加密算法 。在Client Hello里面客户端会告知服务端自己当前的一些信息,包括客户端要使用的TLS版本,支持的加密套装,要访问的域名,给服务端生成的一个随机数(Nonce)等。需要提前告知服务器想要访问的域名以便服务器发送相应的域名的证书过来。 - - ![](../img/net/https-client-hello.jpg) - -2. 服务端在Server Hello里面会做一些响应,告诉客户端服务端选中的加密套装。 - - ![](../img/net/https-server-hello.jpg) - -3. 接着服务给客户端发来了4个证书。第二个证书是第一个证书的签发机构(CA)的证书,它是Amazon,也就是说Amazon会用它的私钥给[http://developer.mozilla.org](https://link.zhihu.com/?target=http%3A//developer.mozilla.org)进行签名。依此类推,第三个证书会给第二个证书签名,第四个证书会给第三个证书签名,并且我们可以看到第四个证书是一个根(Root)证书。 - - ![](../img/net/https-certificate-chain.jpg) - -4. 客户端使用证书的认证机构CA公开发布的RSA公钥对该证书进行验证。 - -5. 验证通过之后,浏览器和服务器通过密钥交换算法产生共享的对称密钥。 - -6. 开始传输数据,使用同一个对称密钥来加解密。 - - ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9KZExrRUk5c1pmZXIxV1NQWWljN0trWWJpYm1wTkYwVnVPWTdsbU1qb0U4SVZPejhpYzNBQUZFWkFFOGliUmVhSjc4dWxWMXU4TTdQU1pGMzJwWllNS0tZV0EvNjQw?x-oss-process=image/format,png) - -### HTTPS和HTTP的区别 - -1. http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。 -2. http和https用的端口不一样,前者是80,后者是443。 -3. https协议需要到ca机构申请证书,一般免费证书较少,因而需要一定费用。 - -## 浏览器中输入URL返回页面过程 - -1. 解析域名,找到主机IP。 -2. 浏览器利用IP直接与网站主机通信,三次握手。 -3. 浏览器向主机发起一个HTTP-GET报文请求。 -4. 服务器响应请求,发回网页内容。 -5. 浏览器解析网页内容。 - -## 长连接 - -HTTP长连接,指的是复用TCP连接。多个HTTP请求可以复用同一个TCP连接,这就节省了TCP连接建立和断开的消耗。长连接在HTTP1.0时是实验性扩展,HTTP1.1默认设置connection:keep-alive,使用长连接。设置为connection:close可以关闭长连接。客户端和服务器的HTTP首部的Connection都要设置为keep-alive,才能支持长连接。 - -长连接的过期时间可以通过Keep-Alive: timeout=20或者max=xxx(max表示这个长连接最多接收xxx次请求就断开)设置。 - -长连接数据传输完成的标志: - -- 判断传输数据是否达到了`Content-Length`指示的大小;4 -- 分块传输(chunked)没有`Content-Length`,这时候就要根据chunked编码来判断,chunked编码的数据在最后有一个空chunked块,表明本次传输数据结束。 - -# 网络分层结构 - -计算机网络七层模型:应用层、表示层、会话层、传输层、网络层、数据链路层、物理层。 - -- 应用层的任务是通过应用进程之间的交互来完成特定的网络作用,常见的应用层协议有域名系统DNS,HTTP协议等。 - -- 表示层的主要作用是数据的表示、安全、压缩。可确保一个系统的应用层所发送的信息可以被另一个系统的应用层读取。 -- 会话层的主要作用是建立通信链接,保持会话过程通信链接的畅通,同步两个节点之间的对话,决定通信是否被中断以及通信中断时决定从何处重新发送。。 - -- 传输层的主要作用是负责向两台主机进程之间的通信提供数据传输服务。传输层的协议主要有传输控制协议TCP和用户数据协议UDP。 - -- 网络层的主要作用是选择合适的网间路由和交换结点,确保数据及时送达。常见的协议有IP协议。 - -- 数据链路层的作用是在物理层提供比特流服务的基础上,建立相邻结点之间的数据链路,通过差错控制提供数据帧(Frame)在信道上无差错的传输,并进行各电路上的动作系列。 常见的协议有SDLC、HDLC、PPP等。 - -- 物理层的主要作用是实现相邻计算机结点之间比特流的透明传输,并尽量屏蔽掉具体传输介质和物理设备的差异。 - -# DNS解析 - -1. 浏览器搜索自己的DNS缓存(维护一张域名与IP地址的对应表) -2. 若没有,则搜索操作系统中的DNS缓存和hosts文件 -3. 若没有,则操作系统将域名发送至本地域名服务器,本地域名服务器查询自己的DNS缓存,查找成功则返回结果,否则通过递归或者迭代的查询方式依次向根域名服务器、顶级域名服务器、权限域名服务器发起查询请求,最终返回IP地址给本地域名服务器 -4. 本地域名服务器将得到的IP地址返回给操作系统,同时自己也将IP地址缓存起来 -5. 操作系统将 IP 地址返回给浏览器,同时自己也将IP地址缓存起来 -6. 浏览器得到域名对应的IP地址 - -# 加密算法 - -对称加密:通信双方使用相同的密钥进行加密。特点是加密速度快,但是缺点是需要保护好密钥,如果密钥泄露的话,那么加密就会被别人破解。常见的对称加密有AES,DES算法。 - -非对称加密:它需要生成两个密钥:公钥和私钥。公钥是公开的,任何人都可以获得,而私钥是私人保管的。我们提交代码到github的时候,就可以使用SSH key:在本地生成私钥和公钥,私钥放在本地`.ssh`目录中,公钥放在github网站上,这样每次提交代码,就不用输入用户名和密码了,github会根据网站上存储的公钥来识别我们的身份。公钥负责加密,私钥负责解密;或者,私钥负责加密,公钥负责解密。这种加密算法安全性更高,但是计算量相比对称加密大很多,加密和解密都很慢。常见的非对称算法有RSA。 - -对称加密和非对称加密需要搭配使用更主要的原因是: - -1. 对称加密:两边需要使用相同的密钥,需要使用一种安全的方式交换密钥,单纯使用对称加密,无法实现密钥交换。 -2. 非对称加密:只使用非对称加密是可以满足安全性要求的,但是由于非对称加密的计算耗时高于对称加密的2-3个数量级(相同安全加密级别),所以才先使用非对称交换密钥,之后再使用对称加密通信。 - -# CDN - -CDN用户访问流程: - -![cdn](https://raw.githubusercontent.com/Tyson0314/img/master/cdn.png) - -1.用户向浏览器输入www.web.com这个域名,浏览器第一次发现本地没有dns缓存,则向网站的DNS服务器请求; - -2.网站的DNS域名解析器设置了CNAME,指向了www.web.51cdn.com,请求指向了CDN网络中的智能DNS负载均衡系统; - -3.智能DNS负载均衡系统解析域名,把对用户响应速度最快的IP节点返回给用户; - -4.用户向该IP节点(CDN服务器)发出请求; - -5.由于是第一次访问,CDN服务器会向原web站点请求,并缓存内容; - -6.请求结果发给用户。 - -最简单的CDN网络有一个负责全局负载均衡的DNS和各节点一台Cache,即可运行。DNS支持根据用户源IP地址解析不同的IP,实现就近访问CDN服务器。为了保证高可用性等,需要监视各节点的流量、健康状况等。一个节点的单台Cache承载数量不够时,才需要多台Cache,多台Cache同时 工作,才需要负载均衡器,使Cache群协同工作。 - -# 参考资料 - -计算机网络,谢希仁 - -[拥塞控制](https://www.cnblogs.com/losbyday/p/5847041.html) - -[常见http状态码](https://www.cnblogs.com/zt123123/p/8327706.html) - -[浏览器中输入URL到返回页面的全过程](https://www.cnblogs.com/jiaosq/p/5841366.html) - -[HTTPS原理](https://zhuanlan.zhihu.com/p/75461564) \ No newline at end of file