导图社区 Java知识总结
本图梳理了Java的基础知识,包括Java基础、WEB、JVM、ORM框架、容器、Spring、异常、设计模式、SpringMVC、网络、APO等方面,收藏下图学习吧!
编辑于2021-09-07 09:39:04Java
基础
JDK
面向开发人员使用的SDK,Java开发工具
JRE
面向Java程序的使用者,Java运行环境
数据类型
整数类型
byte:字节占用1字节 8位,用来表示最小的数据单位,取值范围为 -128 ~ 127
short:字节占用2字节 16位,取值范围为 -32768 ~ 32767
int:字节占用4字节 32位,最为常用的整数类型,取值范围为 -2^31-1 ~ 2^31-1
long:字节占用8字节 64位,取值范围为 -2^63 ~ 2^63-1
浮点数类型
float:单精度浮点型 32位
double:双精度浮点型 64位
字符类型
char:16位,Java字符使用Unicode编码
布尔类型
boolean:true/false
基本数据类型
引用数据类型
类class
接口interface
数组
类型转换
自动转换
条件:满足转换前的数据类型的位数要低于转换后的数据类型
大致转换规则为低级到高级依次如下: byte,short,char—> int —> long—> float —> double
规则
不能对boolean类型进行转换
不能把对象类型转换成不相关类的对象
在把容量大的类型转换为容量小的类型时,必须使用强制类型转换
转换过程中可能导致溢出或者损失精度(主要发生在强制转换过程中)
浮点数到整数的转换试通过舍弃小数得到,而不是四舍五入
强制转换
条件是转换的数据类型必须是兼容的
格式:(type)value,type是要强制类型转换后的数据类型
equals和hashCode
==与equals:==比较的是对象地址,equals比较的是对象值
equals相等与hashCode相等
两个对象equals相等,那么他们的hasdhCode一定也相等
两个对象hashCode相等,那么他们equals不一定相等
重写equlas的约束
自反性:对于任何非空引用值x,x.equals(x)都应返回true
对称性:对于任何非空引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)才应返回true
传递性:对于任何非空引用值x、y和z,如果x.equals(y)返回true,并且y.equals(z)也返回true,那么x.equals(z)应返回true
一致性:对于任何非空引用值z和y,多次调用x.equals(y)始终返回true或始终返回false,qi安提时对象上equals比较重所用的信息没有被修改
非空性:对于任何非空引用值x,x.equals(null)都应返回false
final
作用
修饰引用
引用为基本数据类型,则该引用为常量,该值无法被修改
引用为引用数据类型,比如对象、数组,则该对象、数组本身可以修改,但指向该对象或数组的地址的引用不能被修改
引用是类的成员变量,则必须当场赋值,否则编译会报错
修饰方法
该方法将成为最终方法,无法被子类重写;但可以被继承
修饰类
该类成为最终类,无法被继承(断子绝孙类)
finally
保证代码一定要被执行的一种机制,常用来关闭连接资源或者解锁等
finalize
是Object的一个方法,它的目的是保证对象在被垃圾收集前完成特定资源的回收。1.9后已经过时
字符串的操作
String

StringBuffer
StringBuilder
共同点/区别
共同点:都是以char[]的形式保存的字符串
区别
String类型的字符串是不可变的,对其做修改,都是重新创建对象,然后赋值新的引用
对StringBuffer和StringBuilder进行增删改操作都是对同一个对象做操作
StringBuffer中的方法大部分都使用synchronized关键字修饰,是线程安全的;多线程环境下使用StringBuffer
StringBuilder中的方法没有使用synchronized关键字,是线程不安全的;性能更高,在单线程环境中使用StringBuilder
字符串反转
StringBuilder的reverse()方法
public static String reverse4(String s) { return new StringBuffer(s).reverse().toString(); }
使用字符串数组,实现从尾部开始逐个逆序放入字符串
sublic static String reverse3(String s) { char[] array = s.toCharArray(); String reverse = ""; for(int i = array.length - 1; i >= 0; i--) reverse += array[i]; return reverse; }
使用String的CharAt方法。使用String的CharAt方法取出字符串中的各个字符,然后插入到字符串中
public static String reverse2(String s) { int length = s.length(); String reverse = ""; for(int i = 0; i < length; i++) reverse = s.charAt(i) + reverse; return reverse; }
使用递归方法
public static String reverse1(String s) { int length = s.length(); if(length <= 1){ return s; } String left = s.substring(0, length / 2); String right = s.substring(length / 2, length); return reverse1(right) + reverse1(left); }
抽象类
一个类使用了abstract关键字修饰,那么这个类就是一个抽象类
抽象类可以没有抽象方法
public abstract class TestAbstractClass { public static void notAbstractMethod() { System.out.println("I am not a abstract method."); } }
一个类如果包含抽象方法,那么这个类必须是抽象类
抽象类无法被实例化
抽象类不能被声明为静态
抽象方法不能用private修饰
抽象方法不能用final修饰
和接口的区别
接口的方法默认是public,所有方法在接口中不能有实现(Java8开始接口方法有默认实现);而抽线类可以有非抽象的方法
接口中除了static、final变量,不能有其他变量;而抽象类不一定
一个类可以实现多个接口,但只能继承一个抽象类;接口自己本身可以通过extends关键字扩展多个接口
接口方法默认修饰符是public;抽象方法可以有public、protected和default这些修饰符(抽象方法就是为了被重写所以不能使用private关键字修饰)
从设计层面来说,抽象是对类的抽象,是一种模板设计;而接口是对行为的抽象,是一种行为的规范
IO流
分类
按流向分类
输入流
输出流
按处理数据不同分类
字节流:二进制,可以处理一切文件,包括纯文本、doc、音频、视频等
字符流:文本文件,只能处理纯文本
按功能不同分类
节点流:包裹源头
处理流:增强功能,提高性能
具体分类
操作方式
Reader-字符读取
节点流
FileReader
PipedReader
CharArrayReader
处理流
BufferedReader
InputStreamReader
Writer-字符写出
节点流
FileWriter
PipedWriter
CharArrayWriter
处理流
BufferedWriter
OutputStreamWriter
PrintWriter
InputStream-字节读取
节点流
FileInputStream
PipedInputStream
ByteArrayInputStream
处理流
BufferedInputStream
DataInputStream
ObjectInputStream
SequenceInputStream
OutputStream-字节写出
节点流
FileOutputStream
PipedOutputStream
ByteArrayOutputStream
处理流
BufferedOutputStream
DataOutputStream
ObjectOutputStream
PrintStream
操作对象
处理流
缓冲操作
BufferedInputStream
BufferedOutputStream
BufferedReader
BufferedWriter
基本数据类型操作
DataInputStream
DataOutputStream
对象序列化操作
ObjectInputStream
ObjectOutputStream
转化控制
InputStreamReader
OutputStreamWriter
打印控制
PrintStream
PrintWriter
节点流
文件操作
FileInputStream
FileOutputStream
FileReader
FileWriter
管道操作
PipedInputStream
PipedOutputStream
PipedReader
PipedWriter
数组操作
ByteArrayInputStream
ByteArrayOutputStream
CharArrayReader
CharArrayWriter
BIO/NIO/AIO
BIO
同步并阻塞(传统阻塞型),服务器实现模式为一个连接一个线程,即客户端有连接请求时,服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,可以通过线程池机制改善
适用于连接数目比较小且固定的架构,对服务器资源要求比较高,并发局限于应用中,JDK1.4前唯一
NIO
同步非阻塞,服务器实现模式为一个线程处理多个请求(连接),即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求就进行处理
适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持
AIO
异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了,再通知服务器应用去启动线程进行处理
适用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持
Files的常用方法
String getName():返回File对象所表示的文件名或文件路径
String getPath():返回File对象所对应的相对路径
boolean exists():判断File对象的文件或者目录是否存在
boolean isDirectory():判断File对象是否是目录
动态代理
动态代理是将对象中不同方法的调用重新定向到一个统一处理函数,做自定义的逻辑处理,但是调用者察觉不到
Spring的AOP
事务
权限
日志
RPC框架
实现
利用JDK的反射机制实现
使用CGLIB代理
对象克隆
实现
实现Cloneable接口并重写Object的clone()方法
实现Serializable接口,通过对象的序列化和反序列化实现深度克隆
浅/深克隆
在浅克隆中,如果原型对象的成员变量是基本类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址赋值一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址

在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深度克隆将原型对象的所有引用对象也复制一份给克隆对象

JVM
主要组成部分

类加载器(ClassLoader)

启动类/根类加载器(Bootstrap ClassLoader):是虚拟机自身的一部分,用来加载Java_HOME/lib/目录中的,或者被-Xbootclasspath参数所指定的路径中并且被虚拟机识别的类库
扩展类加载器(Extension ClassLoader):负责加载/lib/ext目录或者Java.ext.dirs系统变量指定的路径中的所有类库
应用程序类加载器(Application ClassLoader):负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器。一般情况下,如果我们没有自定义类加载器默认使用此加载类
自定义类加载器
双亲委派机制
如果一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,而是把这个请求委派给父类加载器去完成,每一层的类加载器都是如此,这样所有的加载请求都会被传送到顶层的启动类加载器中,只有当父加载器无法完成加载请求(它的搜索范围中没找到所需的类)时,子加载器才会尝试去加载类
好处
主要是为了安全性,避免用户自己编写的类动态替换Java的一些核心类,比如String
避免了类的重复加载,因为JVM中区分不同类,不仅仅是根据类名,相同的class文件被不同的ClassLoader加载就是不同的两个类
破环方式
为了兼容JDK1.2以前的操作,因为类加载器是1.0出来的,而双亲委派机制是JDK1.2出来的(典型例子:JNDI应该由启动类加载器加载,但是启动类加载器并不认识这些代码,所以需要从上往下搜索)
热部署方式,会形成一个网状的结构,并不能保证让类加载器由下往上找
运行时数据区(Runtime Data Area)
程序计数器(Program Counter Register):当前线程所执行的字节码的行号指示器,字节码解析器的工作是通过改变这个计数器的值,来获取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能,都需要依赖这个计数器来完成
Java虚拟机栈(Java Virtual Machine Stacks):用于存储局部变量表、操作数栈、动态链接、方法出口等信息
本地方法栈(Native Method Stack):与虚拟机栈的作用是一样的,只不过虚拟机栈是服务Java方法的,而本地方法栈是为虚拟机调用Native方法服务的
Java堆(Java Heap):Java虚拟机中内存最大的一块,是被所有线程共享的,几乎所有的对象实例都在这里分配内存
方法区(Method Area):用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。1.8以后变成元空间
执行引擎(Execution Engine)
本地库接口(Native Interface)
首先通过类加载器(ClassLoader)会把Java代码转换成字节码,运行时数据区(Runtime Data Area)再把字节码加载到内存中,而字节码文件只是JVM的一套指令集规范,并不能直接交给底层操作系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine),将字节码翻译成底层系统指令,再交由CPU去执行,而这个过程中需要调用其他语言的本地库接口(Native Interface)来实现整个程序的功能
类加载执行过程
加载:根据查找路径找到相应的class文件然后导入
取得类的二进制流
转为方法区数据结构
在Java堆中生成对应的java.lang.Class对象
链接
验证:检查加载的class文件的正确性
目的:保证Class流的格式是正确的
文件格式的验证
是否是以0xCAFEBABE开头
元数据验证
是否有父类
是否继承了final类
非抽象类实现了所有的抽象方法
字节码验证
跳转指令指定到合理的位置
符号引用验证
常量池中描述类是否存在
访问的方法或字段是否存在且有足够的权限
准备:给类中的静态变量分配内存空间
分配内存,并为类的静态变量设置默认初始值(方法区中)

解析:虚拟机将常量池中的符号引用替换成直接引用的过程。符号引用就理解为一个标识,而在直接引用直接指向内存中的地址
符号引用:在Java中,一个Java类将会编译成一个class文件。在编译时,Java类并不知道所引用的类的实际地址,因此只能使用符号引用来代替
直接引用:实际内存地址,指向了实际的内存。如果有了直接引用,那引用的目标必定已经被加载入内存了
初始化:对静态变量和静态代码块执行初始化工作
对象在内存中的布局

对象头

第一部分用于存储对象自身运行时数据,如哈希码、GC分代年龄等,这部分数据的长度在32位和64位的虚拟机中分别为32位和64位(官方称为Mark Word)
第二部分用于存储指向对象类型数据的指针,如果是数组对象的话,还有一个额外存储数组长度
实例变量
存放类的属性数据信息,包括父类的属性信息,如果是数组的实例部分还包括数组的长度,这部分内存按4字节对齐
对齐填充
由于虚拟机要求对象起始地址必须是8字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐
栈(Stack)
栈也叫栈内存,主管Java程序的运行,是在线程创建时创建,它的生命期是跟随线程的生命期,线程结束栈内存也就释放,对于栈来说不存在垃圾回收问题,只要线程一结束该栈就Over,生命周期和线程一致,是线程私有的。基本类型的变量和对象的引用变量都是在函数的栈内存中分配
保存的数据
本地变量(Local Variables):输入参数和输出参数以及方法内的变量
栈操作(Operand Stack):(PC寄存器)记录出栈、入栈的操作
栈帧数据(Frame Data):栈帧是一个内存区块,是一个数据集,是一个有关方法(Method)和运行期数据的数据集
堆/栈
功能方面:堆是用来存放对象的;栈是用来执行程序的
共享性:堆是线程共享的;栈是线程私有的
空间大小:堆大小远远大于栈
队列/栈
队列和栈都是被用来预存储数据的
队列允许先进先出检索元素,但也有例外的情况,Deque接口允许从两端检索元素
栈和队列很相似,但它运行对元素进行后进先出检索
引用类型
强引用:发生GC时不会被回收
软引用:有用但不是必须的对象,在发生内存溢出之前会被回收
弱引用:有用但不是必须的对象,在下一次GC时会被回收
虚引用(幽灵引用/幻影引用):无法通过虚引用获得对象,用PhantomReference实现虚引用,虚引用的用途是在GC时返回一个通知
垃圾对象判断
引用计数器:为每个对象创建一个引用计数,有对象引用时计数器+1,引用被释放后-1,当计数器为0时就可以被回收
缺点:不能解决循环引用的问题

可达性分析:从GC Roots开始向下搜索,搜索所走过的路径成为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象时可以被回收的
可以作为GC Roots的对象
虚拟机栈(栈帧中的局部变量表)中引用的对象
方法区中的类静态属性引用的对象
方法区中的常量引用的对象
本地方法栈中JNI(native)方法引用的对象
垃圾回收算法
标记-清除算法:标记无用对象,然后进行清除回收。缺点:效率不高,无法清除垃圾碎片
标记-整理算法:标记无用对象,让所有存活的对象都向一端移动,然后直接清除掉端边界以外的内存
复制算法:按照容量划分两个大小相等的内存区域,当一块用完的时候,将存活的对象复制到另一块上,然后再把已使用的内存空间一次清理掉。缺点:内存使用率不高,只有原来的一半
分代算法:根据对象存活周期的不同将内存划分为几块。一般是新生代和老年代,新生代基本采用复制算法,老年代采用标记整理算法
垃圾回收器
Serial:最早的单线程串行垃圾回收器
Serial Old:Serial垃圾回收器的老年版本,同样也是单线程,可以作为CMS垃圾回收器的备选预案
ParNew:是Serial的多线程版本
Parallel:和ParNew收集器类似是多线程的,并行工作,但Parallel是吞吐量优先的回收器,可以牺牲等待时间换取系统的吞吐量
Parallel Old:是Parallel老生代版本,Parallel使用的是复制的内存回收算法,Parallel Old使用的是标记-整理的内存回收算法
CMS:一种以获得最短停顿时间为目标的回收器,非常适合B/S系统。用户线程和垃圾回收线程同时执行
CMS是英文Concurrent Mark-Sweep的简称,是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。对于服务器响应速度的引用上,这种垃圾回收器非常合适。在启动JVM的参数上加上”-XX:+UseConcMarkSweepGC“来指定使用CMS垃圾回收器
CMS使用的是标记-清除算法实现的,所以在GC的时候会产生大量的内存碎片,当剩余内存不能满足程序运行要求时,系统将会出现Concurrent Mode Failure,临时CMS会采用Serial Old回收器进行垃圾清除,此时的性能教会被降低
G1:一种兼顾吞吐量和停顿时间的GC实现,是JDK9以后的默认GC选项
调优
常用工具
jconsole:用于对JVM中的内存、线程和类等进行监控
jvisualvm:JDK自带的全能分析工具,可以分析:内存快照、线程快照、程序死锁、监控内存的变化、GC变化等
jps:虚拟机进程状况工具
jinfo:Java配置信息工具
jmap:内存印象工具
jstat:统计信息监视工具
jstack:堆栈异常跟踪工具
常用参数
-Xms2g:初始化堆大小为2G
-Xmx2g:堆最大内存为2G
-Xss:设置单个线程的栈大小。等价于-XX:ThreadStackSize
-XX:NewRatio=4:设置年轻代和老年代的内存比例为1:4
-XX:SurvivorRatio=8:设置新生代Eden和Survivor比例为8:2
-XX:UseParNewGC:指定使用ParNew+Serial Old垃圾回收器组合
-XX:UseParallelOldGC:指定使用ParNew+ParNew Old垃圾回收组合
-XX:UseConcMarkSweepGC:指定使用CMS+Serial Old垃圾回收器组合
-XX:+PrintGC:开启打印GC信息
-XX:+PrintGCDetails:打印GC详细信息
-XX:MetaspaceSize:设置元空间大小 可以在命令行窗口使用:jps -l 查看具体的java程序的进程ID,然后使用jinfo -flag 参数名 进程ID的方式查看默认参数值. 如: jinfo -flag MetaspaceSize 4567 用来查看4567java进程的元空间大小
java -XX:+PrintFlagsInitial:查看jvm初始默认值
java -XX:+PrintFlagsFinal:查看修改后的参数值
java -XX:+PrintCommandLineFlags:方便查看本次JVM使用的垃圾回收器是哪一种
-XX:+PrintGCDetails:输出详细GC收集日志信息
-XX:MaxTenuringThreshold:设置垃圾最大年龄, 最大为15
OOM
StackOverflowError:栈溢出错误,在调用递归方法时,如果无限调用就会发生
OutOfMemoryError:
Java heap space,在堆内存不足的时候会出现,比如在循环内创建对象
GC overhead limit exceeded,超过GC开销限制,GC耗时太久,且没什么效率,每次回收的空间都很小
Direct buffer memory,直接缓冲内存不足,在使用NIO的ByteBuffer.allocateDirect()方法时,是直接在元空间中分配的,这个区域不归GC管,所以对象越多,效率越低,最后不够用时程序崩溃
unable to create new native thread,无法创建新的本机线程
原因:应用创建了太多的线程,超过系统的承载极限
解决:降低应用创建线程的数量,也可以修改linux系统默认1024个线程的配置,扩大线程数
Metaspace,元空间内存溢出,不断往元空间加载类,超过其最大限制以后发生的错误
CPU占用过高,分析思路和定位
先用top命令找出CPU占比最高的
用jps -l 或者ps -ef找到有问题的进程编号
ps -mp 进程编号 -o THREAD,tid,time找到对应有问题的线程,参数:-m 显示所有的线程;-p pid 进程使用CPU的时间;-o 用户自定义格式
将定位到的线程ID转换为16进制格式的小写
使用命令:jstack 进程ID | grep tid(16进制线程id小写) -A60 打印出前60行
容器
Java容器
String
数组
java.util下的集合类
List:存放有序,列表存储,元素可重复
Set:无序,元素不可重复
Map:无序,元素可重复
HashMap
HashMap是基于Map接口实现,元素以键值对的方式存储,并且允许使用null键和null值,因为key不允许重复,因此只能有一个键为null;另外HashMap不能保证放入元素的顺序,它是无序的,和放入的顺序并不能相同;HashMap是线程不安全的
数据存储结构
由数组和链表来实现对数据的存储:采用Entry数组来存储key-value对,每一个键值对组成一个Entry实体,Entry类实际上是一个单项的链表结构,它具有Next指针,可以连接下一个Entry实体,以此来解决Hash冲突的问题
Entry类里面有一个next属性,作用是指向下一个Entry
JDK8的改变
数据结构的存储由数组+链表的方式,变化为数组+链表+红黑树的存储方式,当链表长度超过阈值(8)以及Entry[]数组长度大于等于64时,将链表转换成红黑树
重要方法
构造方法
提供了四个构造方法;构造方法中,依靠第三个方法来执行;但是前三个方法都没有进行数组的初始化操作,即使调用了构造方法,此时存放HashMap中数组元素的table表长度依旧为0;在第四个构造方法中调用了inflateTable()方法完成了table的初始化操作,并将m中的元素添加到HashMap中

添加方法
在该方法中,添加键值对时,首先进行table是否初始化的判断,如果没有进行初始化(分配空间,Entry[]数组的长度)。然后进行key是否为null的判断,如果key==null,放置在entry[]的0号位置。不是则计算在Entry[]数组的存储位置,判断该位置上是否已有元素,如果已经有元素存在,则遍历该Entry[]数组位置上的单链表。判断key是否存在,如果key已经存在,则用新的value值,替换旧的value值,并将旧的value值返回。如果key不存在,将key-value生成Entry实体,添加到HashMap中的Entry[]数组中

JDK8之后,数据存储结构发生变化:数组+链表+红黑树;如果存在key节点,返回旧值,不存在就返回NULL

addEntry()
在添加之前先进行容量的判断,如果当前容量达到了阈值,并且需要存储到Entry[]数组中,先进行扩容操作,扩充的容量为table长度的2倍。重新计算hash值,和数组存储的位置,扩容后的链表顺序与扩容前的链表顺序相反。在1.8之前,新插入的Entry实体都是放在了链表的头部位置,但是这种操作在高并发的环境下容易导致死锁,所以1.8之后,新插入的Entry实体都放在了链表的尾部

获取方法:get
首先计算hash值,然后调用indexFor()方法得到该key在table中的存储位置,得到该位置的单链表,遍历列表找到key和指定key内容相等的Entry,返回Entry.value值

删除方法
先计算指定key的hash值,然后计算出table中的存储位置,判断当前位置Entry实体是否存在,如果没有直接返回,若当前位置有Entry实体存在,则开始遍历列表。定义了三个Entry引用,分别为pre、e和next。在循环遍历的过程中,首先判断pre和e是否相等,若相等表明,table的当前位置只有一个元素,直接将table[i] = next = null。若形成了pre -> e -> next的连接关系,判断e的key是否和指定的key相等,若相等则让pre -> next,e失去引用

containKey
先计算指定key的hash值,然后使用hash和table.length取模得到index值,遍历table[index]元素查找是否包含key相同的值

containValue
直接遍历所有的元素直到找到value,由此可见HashMap的containsValue方法本质上和普通数组和List的ontains方法没什么区别。

和HashTable的区别
线程安全
HashTable是线程安全的,实现方法中都添加了synchronized关键字
HahMap非线程安全
针对null的不同
HashMap可以使用null作为key
HashMap以null作为key时,总是存储在table数组的第一个节点上
HashTable不允许null作为key
继承结构
HashMap是对Map接口的实现
HashTable实现了Map接口和Dictionary抽象类
初始容量与扩容
HashMap的初始容量为16,扩容时是当前容量翻倍,即capacity*2
HashTable的初始容量为11,扩容时是容量翻倍+1,即capacity*2+1
两者的填充因子默认都是0.75
两者计算hash的方法
HashMap:对key的hashcode进行二次hash,以获得更好的散列值,然后对table数组长度取模
HashTable:直接使用key的hashcode对table数组的长度直接进行取模
HashSet
实现原理总结
基于HashMap实现的,默认构造函数是构建一个初始容量为16,负载因子为0.75的HashMap。封装了一个HashMap对象来存储所有的集合元素,所有放入HashSet中的集合元素实际上由HashMap的key来保存,而HashMap的value则存储了一个PRESENT,他是一个静态的Object对象
当我们试图把某个类的对象当成HashMap的key,或试图将这个类的对象放入HashMap中保存时,重写该类的equals(Object object)方法和hashCode()方法很重要,而且这两个方法的返回值必须保证一致:当该类的两个hashCode()返回值相同时,它们通过equals()方法比较也应该返回true。通常来说,所有参与计算hashCode()返回值的关键属性,都应该用于作为equals()比较的标准
HashSet的其他操作都是基于HashMap的
实现原理详解
概述:HashSet实现Set接口,由哈希表(实际上是一个HashMap实例)支持。不保证set的迭代顺序,也不保证该顺序恒久不变。允许使用null元素
实现代码
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable { static final long serialVersionUID = -5024744406713321676L; // 底层使用HashMap来保存HashSet中所有元素。 private transient HashMap<E,Object> map; // 定义一个虚拟的Object对象作为HashMap的value,将此对象定义为static final。 private static final Object PRESENT = new Object(); /** * 默认的无参构造器,构造一个空的HashSet。 * * 实际底层会初始化一个空的HashMap,并使用默认初始容量为16和加载因子0.75。 */ public HashSet() { map = new HashMap<E,Object>(); } /** * 构造一个包含指定collection中的元素的新set。 * * 实际底层使用默认的加载因子0.75和足以包含指定 * collection中所有元素的初始容量来创建一个HashMap。 * @param c 其中的元素将存放在此set中的collection。 */ public HashSet(Collection<? extends E> c) { map = new HashMap<E,Object>(Math.max((int) (c.size()/.75f) + 1, 16)); addAll(c); } /** * 以指定的initialCapacity和loadFactor构造一个空的HashSet。 * * 实际底层以相应的参数构造一个空的HashMap。 * @param initialCapacity 初始容量。 * @param loadFactor 加载因子。 */ public HashSet(int initialCapacity, float loadFactor) { map = new HashMap<E,Object>(initialCapacity, loadFactor); } /** * 以指定的initialCapacity构造一个空的HashSet。 * * 实际底层以相应的参数及加载因子loadFactor为0.75构造一个空的HashMap。 * @param initialCapacity 初始容量。 */ public HashSet(int initialCapacity) { map = new HashMap<E,Object>(initialCapacity); } /** * 以指定的initialCapacity和loadFactor构造一个新的空链接哈希集合。 * 此构造函数为包访问权限,不对外公开,实际只是是对LinkedHashSet的支持。 * * 实际底层会以指定的参数构造一个空LinkedHashMap实例来实现。 * @param initialCapacity 初始容量。 * @param loadFactor 加载因子。 * @param dummy 标记。 */ HashSet(int initialCapacity, float loadFactor, boolean dummy) { map = new LinkedHashMap<E,Object>(initialCapacity, loadFactor); } /** * 返回对此set中元素进行迭代的迭代器。返回元素的顺序并不是特定的。 * * 底层实际调用底层HashMap的keySet来返回所有的key。 * 可见HashSet中的元素,只是存放在了底层HashMap的key上, * value使用一个static final的Object对象标识。 * @return 对此set中元素进行迭代的Iterator。 */ public Iterator<E> iterator() { return map.keySet().iterator(); } /** * 返回此set中的元素的数量(set的容量)。 * * 底层实际调用HashMap的size()方法返回Entry的数量,就得到该Set中元素的个数。 * @return 此set中的元素的数量(set的容量)。 */ public int size() { return map.size(); } /** * 如果此set不包含任何元素,则返回true。 * * 底层实际调用HashMap的isEmpty()判断该HashSet是否为空。 * @return 如果此set不包含任何元素,则返回true。 */ public boolean isEmpty() { return map.isEmpty(); } /** * 如果此set包含指定元素,则返回true。 * 更确切地讲,当且仅当此set包含一个满足(o==null ? e==null : o.equals(e)) * 的e元素时,返回true。 * * 底层实际调用HashMap的containsKey判断是否包含指定key。 * @param o 在此set中的存在已得到测试的元素。 * @return 如果此set包含指定元素,则返回true。 */ public boolean contains(Object o) { return map.containsKey(o); } /** * 如果此set中尚未包含指定元素,则添加指定元素。 * 更确切地讲,如果此 set 没有包含满足(e==null ? e2==null : e.equals(e2)) * 的元素e2,则向此set 添加指定的元素e。 * 如果此set已包含该元素,则该调用不更改set并返回false。 * * 底层实际将将该元素作为key放入HashMap。 * 由于HashMap的put()方法添加key-value对时,当新放入HashMap的Entry中key * 与集合中原有Entry的key相同(hashCode()返回值相等,通过equals比较也返回true), * 新添加的Entry的value会将覆盖原来Entry的value,但key不会有任何改变, * 因此如果向HashSet中添加一个已经存在的元素时,新添加的集合元素将不会被放入HashMap中, * 原来的元素也不会有任何改变,这也就满足了Set中元素不重复的特性。 * @param e 将添加到此set中的元素。 * @return 如果此set尚未包含指定元素,则返回true。 */ public boolean add(E e) { return map.put(e, PRESENT)==null; } /** * 如果指定元素存在于此set中,则将其移除。 * 更确切地讲,如果此set包含一个满足(o==null ? e==null : o.equals(e))的元素e, * 则将其移除。如果此set已包含该元素,则返回true * (或者:如果此set因调用而发生更改,则返回true)。(一旦调用返回,则此set不再包含该元素)。 * * 底层实际调用HashMap的remove方法删除指定Entry。 * @param o 如果存在于此set中则需要将其移除的对象。 * @return 如果set包含指定元素,则返回true。 */ public boolean remove(Object o) { return map.remove(o)==PRESENT; } /** * 从此set中移除所有元素。此调用返回后,该set将为空。 * * 底层实际调用HashMap的clear方法清空Entry中所有元素。 */ public void clear() { map.clear(); } /** * 返回此HashSet实例的浅表副本:并没有复制这些元素本身。 * * 底层实际调用HashMap的clone()方法,获取HashMap的浅表副本,并设置到HashSet中。 */ public Object clone() { try { HashSet<E> newSet = (HashSet<E>) super.clone(); newSet.map = (HashMap<E, Object>) map.clone(); return newSet; } catch (CloneNotSupportedException e) { throw new InternalError(); } } }
ArrayList
LinkedList
区别
ArrayList底层基于动态数组,LinkedList基于链表实现,底层时循环双向链表
对于随机访问get和set,ArrayList优于LinkedList
对于新增和删除,LinkedList比较快
Queue
添加
add方法
offer方法
都是添加元素,区别在于:add方法在队列满的情况下抛异常,而offer方法则返回false
删除
remove方法
poll方法
都是删除队列的头元素,区别在于:remove方法在队列为空时抛异常,poll方法将返回NULL
迭代器(Iterator)
是个接口,提供了很多对元素进行迭代的方法,迭代器可以在迭代过程中删除底层集合的元素,可以直接调用Iterator的remove()方法来删除。因为在Collection接口中定义了获取集合迭代器的方法。所以每一个集合都包含了可以返回迭代器实例的方法
使用
每个集合都可以用iterator()方法得到一个iterator实例
使用next()方法获取序列中的下一个元素,使用hasNext()方法检查序列中是否有元素
使用remove()方法将迭代器新返回的元素删除
特点:将集合的遍历和其底层的结构分离
ListIterator
Iterator的子接口,用于扩展Iterator。在Iterator中,我们只能向前移动,无法操作或者修改集合中的元素,ListIterator弥补了这种缺点
区别
范围不同,Iterator适用于所有集合,ListIterator只适用于List及其子类
ListIterator有add方法,可以添加元素,Iterator不可以
ListIterator可以实现双向遍历,Iterator不可以
ListIterator可以实现对象的修改,Iterator不行
ListIterator可以获取集合中的所有,Iterator不可以
异常
throw/throws
throws用来声明一个方法可能抛出的所有异常信息,不会处理异常,只是将异常向上传,交给调用者;throw抛出一个具体的异常类型
throws出现在方法头;throw出现在函数体
throws表示出现异常的可能,并不一定会发生;throw则是抛出一个存在的异常实例
try-catch-finally
cache可以省略,不管有没有捕获到异常,finally中的代码都会被执行
finally是在return之后执行的,程序在执行完return之后,会将值保存起来,当执行完finally中的代码之后,再将return值返回;如果finally中存在return,会导致最后返回的finally中的值
常见的异常类
空指针异常类型:NullPointerException
类型强制转换异常:ClassCastException
数组下标越界异常:ArrayIndexOutOfBoundsException
输入输出异常:IOException
并发写异常
设计模式
六大原则
开闭原则(Open Close Principle):对扩展开放,对修改关闭。在程序进行扩展的时候,不能修改原有的代码,实现一个热插拔的效果
里氏代换原则(Listov Substitution Principle):面向对象设计的基本原则之一。里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。里氏代换原则是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏替换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏替换原则是对实现抽象化的具体步骤的规范
依赖倒转原则(Dependence Inversion Principle):这个原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体
接口隔离原则(Interface Segregation Principle):使用多个隔离的接口,比使用单个接口好;降低类之间的耦合度
迪米特法则,又称最少知道原则(Demeter Principle):一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立
合成复用原则(Composite Reuse Principle):尽量使用合成/聚合的方式,而不是使用继承
类型
创建型模式(Creational Patterns):此模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用new运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活
工厂模式(Factory Pattern)
抽象工厂模式(Abstract Factory Pattern)
单例模式(Singleton Pattern)
建造者模式(Builder Pattern)
原型模式(Prototype Pattern)
结构型模式(Structural Patterns):此模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式
适配器模式(Adapter Pattern)
桥接模式(Bridge Pattern)
过滤器模式(Filter、Criteria Pattern)
组合模式(Composite Pattern)
装饰器模式(Decorator Pattern)
外观模式(Facade Pattern)
享元模式(Flyweight Pattern)
代理模式(Proxy Pattern)
行为型模式(Behavioral Patterns):此模式特别关注对象之间的通信
责任链模式(Chain Of Responsibility Pattern)
命令模式(Command Pattern)
解释器模式(Interpreter Pattern)
迭代器模式(Iterator Pattern)
中介者模式(Mediator Pattern)
备忘录模式(Memento Pattern)
观察者模式(Observer Pattern)
状态模式(State Pattern)
空对象模式(Null Object Pattern)
策略模式(Strategy Pattern)
模板模式(Template Pattern)
访问者模式(Visitor Pattern)
J2EE模式:此模式特别关注表示层。这些模式是由Sun Java Center鉴定的
MVC模式(MVC Pattern)
业务代表模式(Business Delegate Pattern)
组合实体模式(Composite Entity Pattern)
数据访问对象模式(Data Access Object Pattern)
前端控制器模式(Front Controller Pattern)
拦截过滤器模式(Intercepting Filter Pattern)
服务定位器模式(Service Locator Pattern)
传输对象模式(Transfer Object Pattern)
关系图
网络
HTTP响应码:用来表明HTTP请求是否已经成功完成
消息响应
100
Continue(继续)
客户端应当继续发送请求。这个临时响应是用来通知客户端它的部分请求已经被服务器接收,且仍未被拒绝。客户端应当继续发送请求打剩余部分,或者如果请求已经完成,忽略这个响应。服务器必须在请求完成后向客户端发送一个最终响应
101
Switching Protocol(切换协议)
服务器已经理解了客户端的请求,并将通过Upgrade消息头通知客户端采用不同的协议来完成这个请求。在发送完这个响应最后的空行后,服务器将会切换到在Upgrade消息头中定义的那些协议。只有在切换新的协议更有好处的时候才应该采取类似措施。例如,切换到新的HTTP协议版本比旧版本更有优势,或者切换到一个实时且同步的协议以传送利用此类特性的资源
HTTP/1.1可用
成功响应
200
OK(成功)
请求成功。成功的意义根据请求所使用的方法不同而不同
GET:资源已被提取,并作为响应体传回客户端
HEAD:实体头已作为响应头传回客户端
POST:经过服务器处理客户端传来的数据,适合的资源作为响应体传回客户端
TRACE:服务器收到请求消息作为响应体传回客户端
PUT、DELETE和OPTIONS方法永远不会返回200状态码
201
Created(已创建)
请求成功,而且有一个新的资源已经依据请求的需要而建立,通常这是PUT方法得到的响应码
202
Accepted(已创建)
服务器已接受请求,但尚未处理。正如它可能被拒绝一样,最终该请求可能会也可能不会被执行。在异步操作的场合下,没有比发送这个状态码更方便的做法了。返回202状态码的响应目的是允许服务器接受其他过程的请求(例如某个每天只执行一次的基于批处理的操作),而不必让客户端一直保持与服务器的连接直到批处理操作全部完成。在接受请求处理并返回202状态码的响应应当在返回的实体中包含一些指示处理当前状态的信息,以及指向处理状态监视器或状态预测的指针,以便用户能够估计操作是否已经成功
203
Non-Authoritative Information(未授权信息)
服务器已成功处理了请求,但返回的实体头部元信息不是在原始服务器上有效的确定集合,而是来自本地或者第三方的拷贝,如果不是上述情况,使用200状态码才是最合适的
HTTP/0.9可用
204
No Content(无内容)
该响应没有响应内容,只有响应头,响应头也可能是有用的。用户代理可以根据新的响应头来更新对应资源的缓存信息。
HTTP/0.9 and 1.1可用
205
Reset Content(重置内容)
告诉用户代理去重置发送该请求的窗口的文档视图
206
Partial Content(部分内容)
当客户端通过使用range头字段进行文件分段下载时使用该状态码
HTTP/1.1可用
重定向
300
Multiple Choice(多种选择)
该请求有多种可能的响应,用户代理或者用户必须选择它们其中的一个,服务器没有任何标准可以遵循去代替用户来进行选择
HTTP/1.0 and later
301
Moved Permanently(永久移动)
该状态码表示所请求的URI资源路径已经改变,新的URL会在响应的Location:头字段里找到
302
Found(临时移动)
该状态码表示所请求的URI资源路径临时改变,并且还可能继续改变。因此客户端在以后访问时还得继续使用该URI。新的URL会在响应的Location:头字段里找到
HTTP/0.9可用
303
See Other(查看其他位置)
服务器发送该响应用来引导客户端使用GET方法访问另一个URI
HTTP/0.9 and 1.1
304
Not Modified(未修改)
告诉客户端,所请求的内容距离上次访问并没有变化。客户端可以直接从浏览器缓存中获取该资源
HTTP/0.9可用
305
Use Proxy(使用代理)
所请求的资源必须通过代理才能访问到。由于安全原因,该状态码并未受到广泛支持
306
unused(未使用)
这个状态码已经不再被使用,当初它被用在HTTP 1.1规范的旧版本中
307
Temporary Redirect(临时重定向)
服务器发送该响应用来引导客户端使用相同的方法访问另外一个URI来获取想要获取的资源。新的URL会在响应的Location:头字段里找到。与302状态码有相同的语义,且前后两次访问必须使用相同的方法(GET POST)
HTTP/1.1可用
308
Permanent Redirect(永久重定向)
所请求的资源将永久的位于另外一个URI上。新的URL会在响应的Location:头字段中找到。与301状态码有相同的语义,且前后两次访问必须使用相同的方法(GET POST)
HTTPbis(试验草案)
客户端错误
400
Bad Request(错误请求)
因发送的请求错误,服务器无法正常读取
401
Unauthorized(未授权)
需要身份验证后才能获取所请求的内容,类似于403错误。不同点是,401错误后,只要正确输入账号密码,验证即可通过
HTTP/0.9 可用
402
Payment Required(需要付款)
该状态码被保留以供将来使用。创建此代码最初的目的是为数字支付系统而用,然而,到现在也没有投入使用
HTTP/0.9 and 1.1
403
Forbidden(禁止访问)
客户端没有权利访问所请求内容,服务器拒绝本次请求
404
Not Found(未找到)
服务器找不到所请求的资源。由于经常发生此种情况,所以该状态码在上网时是非常常见的
HTTP/0.9 可用
405
Method Not Allowed(不允许使用该方法)
该请求使用的方法被服务器端禁止使用,RFC2616中规定,GET和HEAD方法不能被禁止
406
Not Acceptable(无法接受)
在进行服务器驱动内容协商后,没有发现合适的内容传回客户端
407
Proxy Authentication Required(要求代理身份验证)
类似于状态码401,不过需要通过代理才能进行验证
408
Request Timeout(请求超时)
客户端没有在服务器预备等待的时间内完成一个请求的发送。这意味着服务器将会切断和客户端的连接。在其他浏览器中,这个响应更常见一些,例如Chrome和IE9,目的是为了使用HTTP预连机制加快浏览速度。同时注意,一些服务器不发送此种响应就直接切断连接
409
Conflict(冲突)
该请求与服务器的当前状态所冲突
410
Gone(已失效)
所请求的资源已经被删除
411
Length Required(需要内容长度头)
因服务器在本次请求中需要Content-Length头字段,而客户端没有发送。所以,服务器拒绝了该请求
412
Precondition Failed(预处理失败)
服务器没能满足客户端在获取资源时在请求头字段中设置的先决条件
413
Request Entity Too Large(请求实体过长)
请求实体大小超过服务器设置的最大限制,服务器可能会关闭HTTP链接并返回Retry-After头字段
414
Request-URI Too Long(请求网址过长)
客户端请求所包含的URI地址太长,以至于服务器无法处理
415
Unsupported Media Type(媒体类型不支持)
服务器不支持客户端所请求的媒体类型,因此拒绝该请求
416
Requested Range Not Satisfiable(请求范围不合要求)
请求中包含的Range头字段无法被满足,通常是因为Range中的数字范围超出所请求资源的大小
417
Expectation Failed(预期结果失败)
在请求头Expect中指定的预期内容无法被服务器满足
HTTP/1.1 可用
服务器端错误
500
Internal Server Error(内部服务器错误)
服务器遇到未知的无法解决的问题
501
Implemented(未实现)
服务器不支持该请求中使用的方法,比如POST和PUT,只有GET和HEAD是RFC2616规范中规定服务器必须实现的方法
502
Bad Gateway(网关错误)
服务器作为网关且从上游服务器获取到了一个无效的HTTP响应
503
Service Unavailable(服务不可用)
由于临时的服务器维护或者过载,服务器当前无法处理请求,这个状况是临时的,并且将在一段时间以后恢复。如果能够预计延迟时间,那么响应中可以包含一个Retry-After:头用以标明这个延迟时间,如果没有给出这个Retry-After:信息,那么客户端应当以处理500响应的方式处理它。同时,这种情况下,一个友好的用于解释服务器出现问题的页面应当被返回,并且,缓存相关的HTTP头信息也应该包含,因为通常这种错误提示网页不应当被客户端缓存
HTTP/0.9 可用
504
Gateway Timeout(网关超时)
服务器作为网关且不能从上游服务器及时的得到响应返回给客户端
505
HTTP Version Not Supported(HTTP版本不受支持)
服务器不支持客户端发送的HTTP请求中所使用的HTTP协议版本
HTTP/1.1 可用
forward(请求转发)
转发过程
客户浏览器发送HTTP请求 -> WEB服务器接受请求 -> 调用内部的一个方法在容器内部完成请求处理和转发动作 -> 将目标资源发送给客户;
工作原理

浏览器向Servlet1发出访问请求
Servlet1调用forward()方法,在服务器端将请求转发给Servlet2
最终由Servlet2做出响应
redirect(重定向)
重定向过程
客户浏览器发送HTTP请求 -> WEB服务器接受后发送302状态码响应及对应新的location给客户端浏览器 -> 客户端浏览器发现是302响应,则自动再发送一个新的HTTP请求,请求URL是新的location地址 -> 服务器根据此请求寻找资源并发送给客户
工作原理

浏览器向Servlet1发出访问请求
Servlet1调用sendRedirect()方法,将浏览器重定向到Servlet2
浏览器向Servlet2发出请求
最后由Servlet2做出响应
区别
forward:直接转发,客户端浏览器只发出一次请求,由第二个信息资源响应该请求,共享一个request对象
redirect:间接转发,服务器端响应第一次请求的时候,让浏览器去访问另外一个URL,从而达到转发的目的,本质上是两次HTTP请求
forward地址栏不变;redirect地址栏改变
TCP
一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC793定义
协议分层

应用层
向用户提供一组常用的应用程序,比如电子邮件、文件传输访问、远程登录等
传输层
提供应用程序间的通信
格式化信息流
提供可靠传输
网络层
负责相邻计算机之间的通信
一、处理来自传输层的分组发送请求,收到请求后,将分组转入IP数据报,填充报头,选择去往信宿机的路径,然后将数据报发往适当的网络接口
二、处理输入数据报:首先检查其合法性,然后进行寻径。假如该数据报已达到信宿机,则去掉报头,将剩下部分交给适当的传输协议;假如该数据报尚未到达信宿机,则转发该数据报
三、处理路径、流控、拥塞等问题
网络接口层
协议最底层,负责接受IP数据报并通过网络发送之,或者从网络上接收物理帧,抽出IP数据报,交给IP层
三次握手

TCP是因特网中的传输层协议,使用三次握手协议建立连接。当主动方发出SYN连接请求后,等待对方回到SYN+ACK,并最终对对方的SYN执行ACK确认。这种建立连接的方法可以防止产生错误的连接
过程
客户端发送SYN(SEQ=x)报文给服务器端,进入SYN_SEND状态
服务器端收到SYN报文,回应一个SYN(SEQ=y)ACK(ACK=x+1)报文,进入SYN_RECV状态
客户端收到服务器端的SYN报文,回应一个ACK(ACK=y+1)报文,进入Established(TCP连接成功)状态
四次挥手

建立一个连接需要三次握手,而终止一个连接需要经过四次握手,这是由TCP的半关闭(half-close)造成的
过程
某个应用进程首先调用close,称该端执行“主动关闭”(active close)。该端的TCP于是发送一个FIN分节,表示数据发送完毕
接收到这个FIN的对端执行“被动关闭”(passive close),这个FIN由TCP确认
注意:FIN的接收也作为一个文件结束符(end-of-file)传递给接受端应用进程,放在已排队等候该应用进程接收的任何其他数据之后,因为,FIN的接收意味着接收端应用进程在相应连接上再无额外数据可接收
一段时间后,接收到这个文件结束符的应用进程将调用close关闭它的套接字,这导致它的TCP也发送一个FIN
接收这个最终FIN的原发送端TCP(即执行主动关闭的那一端)确认这个FIN
TCP粘包
指发送方发送的若干包数据到接收方接收时沾成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾
产生原因
发送方原因:TCP默认会使用Nagle算法
Nagle算法主要做两件事: 1)只有上一个分组得到确认,才会发送下一个分组 2)收集多个小分组,在一个确认到来时一起发送
接收方原因:TCP接收到分组时,并不会立刻送至应用层处理,或者说,应用层并不一定会立即处理;实际上,TCP将收到的分组保存至接收缓存中,然后应用程序主动从缓存中读收到的分组。这样一下,如果TCP接收到分组的速度大于应用程序读分组的速度,多个包就会被存至缓存,应用程序读时,就会读到多个首尾相接粘到一起的包
UDP
Internet协议支持一个无连接的传输协议,该协议称为用户数据报协议(UDP,User Datagram Protocol)。UDP为应用程序提供了一种无需建立连接就可以发送封装的IP数据包的方法。RFC768描述了UDP
特点
无连接的,即发送数据之前不需要建立连接,因此减少了开销和发送数据之前的时延
尽最大努力交付,即不保证可靠交付,因此主机不需要维持复杂的连接状态表
面向报文的,发送方的UDP对应用程序交下来的报文,在添加首部后就向下交付IP层。UDP对应用程序交下来的报文,既不合并,也不拆分,而是保留这些报文的边界。因此,应用程序必须选择合适大小的报文
没有拥塞控制,因此网络出现的拥塞不会使源主机的发送速率降低
支持一对一、一对多、多对一和多对多的交互通信
首部开销小,只有8个字节,比TCP的20个字节的首部要短
首部格式

数据字段
首部字段
源端口:源端口号。在需要对方回信时选用,不需要时可用全0
目的端口:目的端口号。这在终点交付报文时必须要使用到
长度:UDP用户数据报的长度,其最小值是8(仅有首部),发送一个带0字节数据的UDP数据报是允许的
值得注意的是,UDP长度字段是冗余的; IPV4头部包含了数据报的总长度,同时IPV6头部包含了负载长度。 一个UDP/IPV4数据报的长度等于IPV4数据报的总长度减去IPV4头部的长度。 一个UDP/IPV6数据报的长度等于包含在IPV6头部中的负载长度(payload length)字段的值减去所有扩展头部(除非使用了超长数据报)的长度。 这两种情况下,UDP长度字段应该与从IP层提供的信息计算得到的长度是一致的。
校验和:检测UDP用户数据报在传输中是否有错。有错就丢弃
区别

TCP提供面向连接的传输,通信前要先建立连接(三次握手机制);UDP提供无连接的传输,通信前不需要建立连接
TCP提供可靠的传输(有序,无差错,不丢失,不重复);UDP提供不可靠的传输
TCP面向字节流的传输,因此它能将信息分割成组,并在接收端将其重组;UDP是面向数据报的传输,没有分组开销
TCP提供拥塞控制和流量控制机制;UDP不提供拥塞控制和流量控制
OSI

模型
物理层
数据链层
网络层
传输层
会话层
表示层
应用层
请求方式
GET
POST
区别
GET在浏览器回退时是无害的;POST会再次提交请求
GET产生的URL地址可以被Bookmark;POST不可以
GET请求会被浏览器主动cache;POST不会,除非手动设置
GET请求只能进行URL编码;POST支持多种编码方式
GET请求参数会被完整保留在浏览器历史记录中;POST中的参数不会被保留
GET请求在URL中传送的参数是有长度限制的,最大1024字节;POST没有
对参数的数据类型,GET只接受ASCII字符;POST没有限制
GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息
GET参数通过URL传递;POST放在Request body中
GET与POST都是HTTP协议请求,底层是基于TCP/IP的,所以,GET/POST都是TCP链接。但是,GET产生一个TCP数据包;POST产生两个TCP数据包
GET方式请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据)
POST方式请求,浏览器先发送http header,服务器响应100 continue,浏览器再发送data,服务器响应200(返回数据)
跨域
实现方式
JSONP技术
实现原理:动态添加一个<script>标签,在src属性中访问跨域地址,但是要携带一个回调函数名,服务器将返回的数据封装成js格式的文件返回给客户端后进行解析。如果是使用ajax的方式,需要添加jsonp属性,指定回调函数名;dataType属性值设置为‘jsonp’
CORS规范
通过服务端实现
websocket
AOP
即面向切面编程,在原有功能的基础上通过AOP添加新的功能,而原有的功能并不知道新添加的功能。简单来说,就是在某个类或者方法执行前后打个标记,声明在执行到这里之前要先执行什么,之后执行什么,插入新的执行方法
Spring Cloud
是什么?
一个微服务框架,提供全套的分布式系统解决方案,它为微服务架构开发提供配置管理、服务治理、熔断机制、智能路由、控制总线等一系列的管理操作
核心组件
服务发现(例如:Netflix Eureka)
客户端负载均衡(例如:Netflix Ribbon)
断路器(例如:Netflix Hystrix)
服务网关(例如:Netflix Zuul)
分布式配置(例如:Spring Cloud Config)
断路器
当某个微服务发生故障时,通过断路器的故障监控,向调用方返回一个错误响应,使之不至于长时间等待,避免了故障在分布式系统中的蔓延
Spring Boot
是什么?
可以认为是一个服务于框架的框架,简化了配置文件,整合了所有的框架
为什么使用?
开发速度快
测试简单
配置简单
部署简单
可以基于Spring Boot来构建Spring Cloud生态
配置文件
核心配置文件
application
主要用于Spring Boot项目的自动化配置
bootstrap
在使用Spring Cloud配置时使用
配置文件格式
.properties
.yml
热部署
Spring Loaded
Spring-boot-devtools
自动装配
@SpringBootApplication
@SpringBootConfiguration
@EnableAutoConfiguration:SpringBoot的自动装配就是通过自定义实现ImportSelector接口,从而导致项目启动时会自动将所有项目META-INF/spring.factories路径下的配置类注入到Spring容器中,实现自动装配
@ComponentScan
Spring MVC
组件
DispatcherServlet
前置控制器,配置在web.xml文件中
作用:拦截匹配的请求,并依据响应的规则分发到目标Controller处理
HandlerMapping
处理器映射
作用:请求派发,负责请求和控制器建立对应的关系
Controller
控制器
作用:负责处理由DispatcherServlet分发的请求,把用户请求的数据经过业务处理层处理之后封装成一个Model,然后再把该Model返回给对应的View进行展示
ModelAndView
封装数据信息和视图信息的模型
作用:使用ModelAndView类用来处理完成后的结果数据,以及显示该数据的视图
ViewResolver
视图解析器
作用:把一个逻辑上的视图名称解析为一个真正的视图
整体流程

用户发送请求到前端控制器,前端控制器根据请求信息(如URL)决定选择哪一个页面控制器进行处理并把请求委托给它
页面控制器接收到请求后,进行功能处理,首先需要收集和绑定请求参数到一个对象,这个对象在Spring Web MVC中叫命令对象,并进行验证,然后将命令对象委托给业务对象进行处理;处理完毕后返回一个ModelAndView(模型数据和逻辑视图名)
前端控制器收回控制器,然后根据返回的逻辑视图名,选择相应的视图进行渲染,并把模型数据传入以便视图渲染
前端控制器再次收回控制权,将响应返回给用户
核心流程

第一步:发起请求到前端控制器(DispatcherServlet)
第二步:前端控制器请求HandlerMapping查找Handler(可以根据XML配置、注解进行查找)
第三步:处理器映射器HandlerMapping向前端控制器返回Handler,HandlerMapping会把请求映射为HandlerExecutionChain对象(包含一个Handler处理器(页面控制器)对象,多个HandlerInterceptor拦截器对象),通过这种策略模式,很容易添加新的映射策略
第四步:前端控制器调用处理器适配器去执行Handler
第五步:处理器适配器HandlerAdapter将会根据适配的结果去执行Handler
第六步:Handler执行完成给适配器返回ModelAndView
第七步:处理器适配器向前端控制器返回ModelAndView(ModelAndView是Spring MVC框架的一个底层对象,包含Model和View)
第八步:前端控制器请求视图解析器去进行视图解析(根据逻辑视图名解析成真正的视图(JSP)),通过这种策略很容易更换其他视图技术,只需要更改视图解析器即可
第九步:视图解析器向前端控制器返回View
第十步:前端控制器进行视图渲染(视图渲染将模型数据(在ModelAndView对象中)填充到request域)
第十一步:前端控制器向用户响应结果
需要程序参与 开发
处理器Handler 注意:编写Handler时按照HandlerAdapter的要求去做,这样适配器才可以正确执行Handler
视图View 注意:View是一个接口,实现类支持不同的View类型(JSP、Freemarker、Pdf...)
DispatcherServlet.properties
Spring MVC的默认配置文件,系统会首先加载这里面的配置,如果我们没有配置,那么就默认使用这个文件的配置;如果我们配置了,那么就优先使用我们手动配置的
处理器适配器默认:org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter
处理器映射器默认:org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping
视图解析器默认:org.springframework.web.servlet.view.InternalResourceViewResolver
Spring
为什么使用
方便解耦,可以将对象间的依赖关系交给Spring
Spring支持AOP编程,可以很方便的对程序进行监控,拦截
方便测试,支持junit
集成其他框架比较方便
声明式事务
主要模块
Spring Core
Core是Spring的核心类库,它的所有功能都依赖于该类库,主要实现IOC功能
AOP
提供了常用的拦截器供用户配置
ORM
提供对常用的ORM框架的管理和辅助支持。Spring自己不实现ORM,只是对常见的ORM进行封装管理
DAO
提供对JDBC的支持,统一管理JDBC事务,并不对其进行实现
WEB
提供对常用框架(如Struts2)的支持,将Spring的资源注入给这些框架,也能在这些框架的前后插入拦截器
Context
提供框架式的Bean访问方式,其他程序可以通过Context访问Spring的Bean资源
MVC
提供一套轻量级的MVC实现,简单方便
IOC
控制反转,即把创建对象和维护对象之间关系的权利交给Spring容器去做,程序自己不再维护
传统与Spring区别
传统:自己使用new或者getInstance直接或者间接创建一个对象(高耦合,不易测试)
Spring:容器使用工厂模式为了创建所需要的对象,我们不用自己创建,直接调用即可
注入方式
构造器注入
可以在XML文件中通过constructor-arg标签来注入一个对象到构造器中
Setter方法注入
首先要配置被注入的Bean,在该Bean对应的类中,应该有要注入的对象属性或者基本数据类型的属性
方法注入
基于注解在XML文件中开启注解扫描以后,就可以在filed上使用注解@Autowired或者@Rsource来注入对象
字段注入
接口回调注入
事务
实现方式
编程式事务:允许用户在代码中精通定义事务的边界
声明式事务:基于AOP,将操作和事务管理分离
@Transactional
利用了Spring AOP(面向切面),提供了一个数据库事务的约定流程 
隔离级别
ISOLATION_DEFAULT:默认使用数据库默认的隔离级别
ISOLATION_READ_UNCOMMITTED:读未提交,最低的隔离级别,允许读未提交的数据变更,可能会导致脏读,幻读或不可重复读
ISOLATION_READ_COMMITTED:读已提交,允许读取已经提交的数据,可以阻止脏读,但是幻读和不可重复读有可能发生
ISOLATION_REPEATABLE_READ:可重复读,保证了一个事务不能读取另一个未提交的数据,可以避免脏读和不可重复读
ISOLATION_SERIALIZABLE:串行化,事务被处理为顺序执行,通过锁定事务相关的数据库来实现
传播机制
Propagation.REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择,也是Spring默认的传播机制
Propagation.SUPPORTS:持当前事务,如果当前有事务,就以事务方式执行;如果当前没有事务,就以非事务方式执行
Propagation.MANDATORY:使用当前的事务,且必须在一个已有的事务中执行,如果当前不存在事务,否则抛出异常
Propagation.REQUIRES_NEW:不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务
Propagation.NOT_SUPPORTED:以非事务方式执行,如果当前存在事务,就把当前事务挂起
Propagation.NEVER:以非事务方式执行,且必须在一个没有的事务中执行,如果当前存在事务,则抛出异常【与Propagation.MANDATORY相反】
Propagation.NESTED:如果当前存在事务,则在嵌套事务内执行;如果没有事务,则执行与【Propagation.REQUIRED】类似的操作
失效场景
@Transactional应用在非public修饰的方法上,不支持回滚
@Transactional注解属性propagation设置错误
@Transactional注解属性rollbackFor设置错误
在同一个类中方法调用,导致@Transactional失效
异常被catch处理了,导致@Transactional没办法回滚而失效
数据库配置了不支持事务的引擎,或者数据库本身就不支持事务
Spring隔离级别与数据库隔离级别不一致
以Spring事务为准,除非使用@Transactional(isolation=Isolation.DEFAULT)时,才会使用数据库设置的隔离级别
循环依赖
主要场景
单例的setter注入(能解决)
多例的setter注入(不能解决)
构造器注入(不能解决)
单例的代理代理对象setter注入(有可能解决)
DependsOn循环依赖(不能解决)
解决办法
三级缓存
singletonObjects:一级缓存,用于保存实例化、注入、初始化完成的bean实例
earlySingletonObjects:二级缓存,用于保存实例化完成的bean实例
singletonFactories:三级缓存,用于保存bean创建工厂,以便于后面扩展有机会创建代理对象
Bean
创建
构造器实例化(最常见)
静态工厂实例化
实例工厂实例化
作用域
Singleton
单例,默认作用域,在Spring容器中此种类型的Bean只有一个
Prototype
原型,每次调用getBean方法就会产生一个新的实例
Request
每次HTTP请求都会产生不同的Bean实例
Session
每次会话产生一个实例
Global-Session
所有会话共享一个实例
仅适用于WebApplicationContext环境
生命周期
四个阶段
实例化 Instantiation
属性赋值 Populate
初始化 Initialization
销毁 Destruction
自动装配
no:默认方式,手动装配方式,需要通过ref设定bean的依赖关系
byName:根据Bean的名字进行装配,当一个Bean的名称和其他Bean的属性一致,则自动装配
ByType:根据Bean的类型进行装配,当一个Bean的属性类型与其他Bean的属性的数据类型一致,则自动装配
constructor:根据构造器进行装配,与ByType类似,如果Bean的构造器有与其他Bean类型相同的属性,则进行自动装配
autodetect:如果有默认构造器,则以constructor方式进行装配,否则以byType方式进行装配
BeanFactory和FactoryBean
BeanFactory是个Factory,也就是一个IOC容器或对象工厂,FactoryBean是个Bean。在Spring中,所有的Bean都是由BeanFactory(也就是IOC容器)来进行管理的。但对FactoryBean而言,这个Bean不是简单的Bean,而是一个能生产或者修饰对象生成的工厂Bean,它的实现与设计模式中的工厂模式和修饰器模式类似
ORM框架
是什么?
ORM的意思是对象关系映射,它的作用是在关系型数据库和业务实体对象之间做映射,我们在操作具体业务对象的时候,不需要去和具体的SQL语句打交道,只需要操作对象的属性和方法
Hibernate
为什么使用?
对JDBC访问数据库的代码做了大量的封装,简化开发
性能好,支持各种关系数据库
查询方式
HQL查询
QBC查询(Criteria查询)
本地SQL查询
工作流程
通过Configuration config = new Configuration().configure();解析配置文件
由hibernate.xml中的<mapping resource="com/xx/User.xml"/>读取并解析映射信息
通过SessionFactory sf = config.buildSessionFactory();创建SessionFactory
Session session = sf.openSession();打开Session
Transaction tx = session.beginTransaction();创建并启动事务Transaction
persistent operate操作数据库,持久化操作
tx.commit();提交事务
关闭sessio和sessionFactory
get()查询和Load()查询
get方式会直接触发SQL语句,load方式会使用延迟加载的机制加载这个对象,此时是个代理对象,只保存实体对象的id值,只有用到其他属性的时候,才会调用SQL查询出来
如果对象不存在,get方式抛出空指针异常,load方式抛出ObjectNotFoundException
缓存机制
包括两大类:session一级缓存和sessionFactory二级缓存。一级缓存不可卸载
当根据ID查询数据的时候,首先从session缓存中查询。查询不到,如果设置了二级缓存,那么从二级缓存中查询。如果都查询不到,再查询数据库,将查询到的数据按照ID放入缓存中。在删除,更新,增加数据的时候更新缓存
对象状态

Transient:瞬时态,当new一个实体对象后,这个对象处于临时状态,即这个对象只是一个保存临时数据的内存区域,如果没有变量引用这个对象,则会被jre垃圾回收机制回收。这个对象所保存的数据与数据库没有任何关系,除非通过Session的save或者SaveOrUpdate把临时对象与数据库关联,并把数据插入或者更新到数据库,这个对象才转换为持久对象

Persistent:持久态,调用了save()方法或者游离态的对象调用了update方法后变成持久态。持久化对象的实例在数据库中有对应的数据,并拥有一个持久化标识(ID),并且对该对象的任何修改,都会在提交事务时才会与之进行比较。当对持久化对象进行delete操作后,数据库中对应的记录将被删除,那么持久化对象与数据库记录不再存在对应关系,持久化对象变成瞬时状态
当Session进行了Close、Clear或者evict后,持久化对象虽然拥有持久化标识符和与数据库对应记录一致的值,但是因为会话已经消失,对象不在持久化管理之内,所以处于游离状态(也叫:脱管状态)。
Mybatis
会话
Mybatis接到的一个或者多个执行SQL请求的这个过程叫做一次会话
开启事务的时候,同一个事务中的一个或者多个SQL执行是在同一个会话中的,一个会话是有同一个或者多个事务的;没有开启事务的时候,执行多次SQL,就会创建多次会话
缓存
一级缓存
Mybatis内置的一个强大的事务查询缓存机制
特点
默认开启
仅仅对一个会话中的数据做缓存
本地缓存
二级缓存
启用全局的二级缓存,需要在SQL映射文件中添加<cache/>
效果
映射语句文件中的所有select语句的结果将会被缓存
映射语句文件中的所有insert、update和delete语句会刷新缓存
缓存会使用最近最少使用算法(LRU,Least Recently Used)来清除不需要的缓存
缓存不会定时刷新,即没有刷新间隔
缓存会保存列表或对象的1024个引用
缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用或者修改,而不干扰其他调用者或线程所做的潜在修改
特点
默认关闭的,需要添加<cache/>配置后才能启动默认二级缓存,可以定制二级缓存
针对映射文件,即二级缓存作用范围是namespace
默认使用LRU算法清除不需要的缓存,也可以配置其他清除算法,比如FIFO、弱引用、软引用
清除的触发
MappedStatement上标记flushCache=true的SQL语句
执行增删改语句,即数据发生变化
自定义清除或者clearInterval设定了刷新周期,如自定义缓存可以设置redis过期时间
事务回滚
总结
一级缓存是默认开启的,默认作用范围是SqlSession,其实是可以调整为更新的STATEMENT,查询完成后就清空,等于关闭一级缓存
一级缓存作用范围是SqlSession会话内,所以一级缓存生命周期也是SqlSession
一级缓存结构是一个HashMap作为存储的,没有更新缓存和缓存过期的概念,当增删改、设置localCacheScope=STATEMENT,会清除缓存
二级缓存是默认关闭的,不同的namespace操作互不影响,所以出现连表操作的时候容易出现脏数据,谨慎使用
二级缓存的作用范围是namespace内,所以二级缓存生命周期也是namespace
二级缓存结构是一个HashMap作为存储的,可以自定义实现接口Cache来自定义
开启缓存后,数据查询执行的流程是:二级缓存 -> 一级缓存 -> 数据库
#{}和${}的区别
#{}会将传入的数据当成字符串,在之前加入双引号;${}是直接将数据库显示在SQL中
#{}会当作占位符,防SQL注入;${}不能
分页方式
内存分页
一次性查询出所有满足条件的数据,临时保存在集合中,通过List的subList的方式获取分页数据
一次性查询出很多数据,然后在结果中检索分页的数据,消耗内存
物理分页
借助SQL进行分页或者利用拦截器分页
从数据库查询指定条数的数据
分页插件
实现原理:使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的SQL,然后重写SQL,根据dialect方言,添加对应的物理分页语句和参数
延迟加载
在配置文件的<settings/>标签中设置<setting name="lazyLoadingEnabled" value="true"/>就可以激活
原理:在调用的时候触发加载,而不是在初始化的时候加载信息。如a.getB().getName().如果a.getB()的值为NULL,就会触发保存好的关联B对象的SQL语句查询出B,然后再调用getName()
执行器
SimpleExecutor:每执行一次update或select就开启一个Statement对象,用完立刻关闭Statement对象
ReuseExector:执行update或select以SQL作为key查找Statement对象,存在就使用,不存在就创建,用完后不关闭,可以重复使用
BatchExecutor:执行update(没有select,JDBC批处理不支持select)时,将所有SQL都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象
如何编写一个自定义插件
只需实现Interceptor接口,并指定要拦截的方法签名

WEB
Servlet
JSP
内置对象
request:用户端请求,此请求会包含来自GET/POST请求的参数
response:网页传回用户端的回应
pageContext:管理网页的属性
session:与请求有关的会话期
application:servlet正在执行的内容
out:用来传送回应的输出
config:servlet的架构部件
page:Jsp网页本身
exception:针对错误网页,未捕捉的例外
作用域
application:有效范围是整个应用。整个应用是指从应用启动,application里的变量可以被所有用户共用。如果用户A的操作修改了application中的变量,用户B访问时得到的时修改后的值
session:如果把变量放到session中,说明它的作用域是session,有效范围是当前会话。所谓当前会话,就是指从用户打开浏览器开始,到关闭浏览器这中间的过程。这个过程可能包含多个请求响应,也就是说,只要用户不关闭浏览器,服务器就有办法知道这些请求是一个人发起的,整个过程被称为一个会话(session)
request:有效范围是当前请求周期。所谓请求周期,就是指从http请求发起,到服务器处理结束,返回响应的整个过程。在这个过程中可能使用forward的方式跳转了多个jsp页面,在这些页面中都可以使用这个变量
page:代表变量只能在当前页面有效
区别
Jsp擅长表现页面显示,Servlet擅长逻辑控制
Jsp有内置对象,Servlet没有内置对象
Jsp是Java和Html组合成的一个.jsp文件,Servlet的应用逻辑在,java文件中
Jsp是在Html中嵌入Java代码,Servlet是在Java代码中嵌入Html代码
session
定义:一种记录客户状态的机制,保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上,这就是session。客户端浏览器再次访问时只需要从该session中查找该用户的状态就可以了。session相当于程序在服务器上建立的一份用户的档案,用户来访的时候只需要查询用户档案表就可以了
工作原理:当用户第一次访问一个服务器,服务器就会为该用户创建一个session,并生成一个和该session有关的sessionId,这个id是唯一的,不可重复,这个id将会在本次响应中返回,保存在客户端的cookie中,下次访问的时候,客户端浏览器的cookie中含有sessionId,服务器基于这个id就可以识别该用户
客户端禁止cookie: 当cookie被禁用,我们可以使用“URL重写”来使session生效,简单来说就是将sessionId的信息作为请求地址的一部分。这样服务器就可以解析URL,得到sessionId,进而识别用户
cookie
定义:是服务器传给客户端的体积很小的纯文本文件。客户端请求服务器,如果服务器需要记录该用户状态,就向客户端浏览器发一个cookie,客户端浏览器会把cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该cookie一起提交给服务器,服务器检查该cookie,以此来辨认用户状态
区别
数据存放位置不同;session在服务器,cookie在客户端浏览器
安全程度不同;存放在本地的cookie可以被分析,并进行cookie欺骗
存储大小限制;单个cookie保存的数据不能超过4K,session没有限制
存储内容限制;cookie只能存储String类型的数据,session可以存储对象
Spring MVC
Struts
区别
Spring MVC的入口是一个servlet;Struts的入口是一个filter
Spring MVC是单例的,Struts是多例的
Spring MVC面向方法开发,Struts面向类开发
Spring MVC通过参数解析器将request请求解析;Struts采用值栈存储请求和响应数据
攻击方式
sql注入
定义:指web应用程序对用户输入数据的合法性没有判断或过滤不严,攻击者可以在web应用程序中事先定义好的查询语句的结尾上添加额外的SQL语句,在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步得到相应的数据信息
如何避免?
采用预编译语句
使用正则过滤传入的参数
屏蔽不安全的字符
XSS攻击
定义:跨站脚本攻击,指攻击者在用户端注入恶意的可运行脚本,让其在用户浏览网页时运行,从而通过脚本来获得用户的信息
如何避免?
对用户输入和URL参数进行过滤,过滤脚本相关的内容
对输出进行编码
CSRF攻击
CSRF攻击也叫跨站请求伪造,攻击者通过伪造用户的浏览器请求,向用户自己曾经认证过的网站发送,使目标网站误以为使用户的真实操作而去执行命令
如何避免?
令牌机制
token验证
主题