目录
11. 访问修饰符 public,private,protected,以及不写(默认)时的区别?
12. 抽象类(abstract class)和接口(interface)有什么异同?
13. 可以在 static 环境中访问非 static 变量吗?
14. int 和 Integer 有什么区别,还有 Integer 缓存实现
15. String 和 StringBuilder、StringBuffer 的区别?
19. 静态变量和实例变量的区别。(被static修饰的变量和普通变量的区别)
26. String s = new String("jay");创了几个字符串对象?
32. 说下Vector和Array List、LinkedList联系和区别?分别的使用场景
33. 如果需要保证线程安全,ArrayList应该怎么做,用有几种方式
34. CopyOnWriteArrayList的设计思想是怎样的,有什么缺点?
35. 了解CopyOnWriteArrayList吗? 和Collections.synchronizedList实现线程安全有什么区 别,使用场景是怎样的?。
18. 列举文档对象模型 DOM 里 document 的常用的查找访问节点的方法
1. websocket是什么?为什么使用websocket?
13. 什么是深拷贝,什么是浅拷贝?深拷贝和浅拷贝对数据类型有要求吗?
4. MySQL的常见的存储引擎有哪几种?特点是什么?使用什么场景?
基础篇
1. 八种基本数据类型的大小,以及他们的封装类
基本类型 | 大小(字节) | 默认值 | 封装类 |
byte | 1 | (byte)0 | Byte |
short | 2 | (short)0 | Short |
int | 4 | 0 | Integer |
long | 8 | 0L | Long |
float | 4 | 0.0f | Float |
double | 8 | 0.0d | Doouble |
boolean | - | false | Boolean |
char | 2 | /u0000(null) | Character |
2. String 是最基本的数据类型吗?
不是。Java 中的基本数据类型只有 8 个:byte、short、int、long、float、double、char、 boolean;除了基本类型(primitive type),剩下的都是引用类型(referencetype)。
3. 面向对象的特征有哪些方面?
继承:继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父类(超 类、基类);得到继承信息的类被称为子类(派生类)。继承让变化中的软件系统有了一定 的延续性。
封装:通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的 接口。可以说,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口
多态性:多态性是指允许不同子类型的对象对同一消息作出不同的响应。简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情。多态存在的条件有三个:继承或者实现关系,方法重写,子类对象指向父类引用。
4. 标识符的命名规则。
标识符的含义:
是指在程序中,我们自己定义的内容,譬如,类的名字,方法名称以及变量名称等等,都是标识符
命名规则:(硬性要求)
标识符可以包含英文字母,0-9的数字,$以及标识符不能以数字开头标识符不是关键字
命名规范:(非硬性要求)
类名规范:首字符大写,后面每个单词首字母大写(大驼峰式)变量名规范:首字母小写,后面每个单词首字母大写(小驼峰式)方法名规范:同变量名。
5. Java 创建对象有几种方式
用 new 语句创对象。
使用反射,使用 Class.newInstance()创对象
调用类对象构造方法Constructor 调用对象 clone()方法。 运用反序列化手段,调用 java.io.ObjectInputStream 对象 readObject()方法
6. Java自动装箱与拆箱
装箱就是自动将基本数据类型转换为包装器类型(int-->Integer);调用方法:Integer的
valueOf(int) 方法
拆箱就是自动将包装器类型转换为基本数据类型(Integer-->int)。调用方法:Integer的intValue方法
7. instanceof 关键字的作用
instanceof 严格来说是Java中的一个双目运算符,用来测试一个对象是否为一个类的实例,用法为:
boolean result = obj instanceof Class
其中 obj 为一个对象,Class 表示一个类或者一个接口,当 obj 为 Class 的对象,或者是其直接或
间接子类,或者是其接口的实现类,结果result 都返回 true,否则返回false。
注意:编译器会检查 obj 是否能转换成右边的class类型,如果不能转换则直接报错,如果不能确定
类型,则通过编译,具体看运行时定。
int i = 0;
System.out.println(i instanceof Integer);//编译不通过 i必须是引用类型,不能是基本类型
System.out.println(i instanceof Object);//编译不通过
Integer integer = new Integer(1);
System.out.println(integer instanceof Integer);//true
//false ,在 JavaSE规范 中对 instanceof 运算符的规定就是:如果 obj 为 null,那么将返回
false。
System.out.println(null instanceof Object);
8. ArrayList和linkedList的区别
Array(数组)是基于索引(index)的数据结构,它使用索引在数组中搜索和读取数据是很快的。
Array获取数据的时间复杂度是O(1),但是要删除数据却是开销很大,因为这需要重排数组中的所有数据,
(因为删除数据以后, 需要把后面所有的数据前移)
缺点: 数组初始化必须指定初始化的长度, 否则报错
List—是一个有序的集合,可以包含重复的元素,提供了按索引访问的方式,它继承Collection。
List有两个重要的实现类:ArrayList和LinkedList
ArrayList: 可以看作是能够自动增长容量的数组
ArrayList的toArray方法返回一个数组
ArrayList的asList方法返回一个列表
ArrayList底层的实现是Array, 数组扩容实现
LinkList是一个双链表,在添加和删除元素时具有比ArrayList更好的性能.但在get与set方面弱于
ArrayList.当然,这些对比都是指数据量很大或者操作很频繁。
9. 重载和重写的区别
- 重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回值类型可以相同也可以不同
- 重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。
子类的权限必须大于等于父类
10. throw和throws的作用
在 Java 中,当程序发生异常时,可以使用 throw 关键字来抛出异常对象。throw 语句的格 式为:throw new 异常类名称(构造函数参数);
Java中的 throws 关键字用于声明可能会抛出哪些异常,它只是在方法签名中声明了方法可能抛出的异常,而并未实际抛出异常。
修饰符 返回类型 方法名(参数列表) throws 异常列表 { // 方法体 }
11. 访问修饰符 public,private,protected,以及不写(默认)时的区别?
private修饰符:可用来修饰内部类、属性、方法,即被private修饰的属性、方法、类只能 被该类的对象访问,其子类不能访问,private可以修饰内部类,不可以修饰外部类。
default修饰符:当定义变量、方法以及类时,没有写访问修饰符,则代表默认的修饰符 default修饰的属性、方法、类,只能被本类或者同一个包中的其他类访问到,针对本包访 问而设计,任何处于本包下的属性、方法、类、接口等,都可以相互访问。在接口中,方法 默认的访问修饰符是public
protected修饰符:表示受保护的,它主要的作用是保护子类,子类可以用它修饰的成员, 其他的不可以
public修饰符:表示公开的,公共的。不同类、不同包下都可以访问
12. 抽象类(abstract class)和接口(interface)有什么异同?
抽象类和接口都不能够实例化,但可以定义抽象类和接口类型的引用。一个类如果继承了某个抽象类或者实现了某个接口都需要对其中的抽象方法全部进行实现,否则该类仍然需要被声明为抽象类。接口比抽象类更加抽象,因为抽象类中可以定义构造器,可以有抽象方法和具体方法,而接口中不能定义构造器而且其中的方法全部都是抽象方法。抽象类中的成员可以是 private、默认、protected、public 的,而接口中的成员全都是 public 的。抽象类中可以定义成员变量,而接口中定义的成员变量实际上都是常量。有抽象方法的类必须被声明为抽象类,而抽象类未必要有抽象方法。
单继承、多实现
实现或继承接口时,必须重写里面的所有抽象方法,除非,子类时抽象类或接口
13. 可以在 static 环境中访问非 static 变量吗?
static 变量在 Java中属于类,它在所有实例中值一样。当类被Java虚拟机载入时候,会对static 变量进行初始化。因为静态成员属于类,随着类加载而加载到静态方法区内存,当类加载时,此时不一定有实例创建,没有实例,就不可以访问非静态成员。类加载先于实例创建,因此静态环境中,不可以访问非静态!
14. int 和 Integer 有什么区别,还有 Integer 缓存实现
int 基本数据类型,interger 是 int的 包装类
int 默认值为 0 ,而 interger 默认值为 null, Interger 使用需要判空处理
Integer 缓存机制:为了节省内存和提高性能,Integer 类在内部通过使用相同对象引用实现缓存和重用,Integer 类默认在-128 ~ 127 之间,可以通过 - XX:AutoBoxCacheMax 进行修改,且这种机制仅在自动装箱时候有用,在使用构造器创建Integer 对象时无用。
15. String 和 StringBuilder、StringBuffer 的区别?
- String:
-
- 不可变性: 一旦创建,内容不能修改,只能改变其引用指向新的对象。
- 实例化方式:可以直接通过赋值(如 String str = "hello";)来创建。
- 内存效率:每次修改都会生成新对象,占用更多内存。
- StringBuffer:
-
- 可变性:内容可以修改,不会生成新对象。
- 实例化方式:需要通过构造方法(如 StringBuffer sb = new StringBuffer("hello");)来创建。
- 线程安全: 支持多线程操作,但速度较慢。
- 内存效率: 修改字符串时不会生成新对象(在后面做拼接),更节省内存。
- StringBuilder:
-
- 可变性: 内容可以修改,不会生成新对象。
- 实例化方式: 需要通过构造方法(如 StringBuilder sb = new StringBuilder("hello");)来创建。
- 线程不安全: 不支持多线程操作,但执行速度更快。
- 内存效率: 类似于 StringBuffer,在修改字符串时不会生成新对象。
16. final、finally、finalize的区别
final的用法:
- 被final修饰的类不可以被继承
- 被final修饰的方法不可以被重写
- 被final修饰的变量不可以被改变,如果修饰引用,那么表示引用不可变,引用指向的内容可变.
finally的用法:
finally是在异常处理中的使用的
- 不管有没有出现异常,finally块中的代码都会执行;
- 当try和catch中有return时,finally仍然会执行;
finalize:
是Object类中的一个方法,在垃圾回收器确定对象不再被使用时被调用,目的是为了进行一些清理操作。
不推荐使用
17. 转发和重定向的区别
转发是一次请求 重定向是两次请求
转发时地址栏不发生改变,重定向时地址栏发生改变。
转发只能在本项目内转发 所以转发是不用写项目名称。 重定向可以重定向到项目以外的资源,路径必须包含项目名称。
18. 同步和异步有什么区别?
同步的思想是:所有的操作都做完,才返回给用户。这样用户在线等待的时间太长,给用户一种卡死了的感觉(就是系统迁移中,点击了迁移,界面就不动了,但是程序还在执行,卡 死了的感觉)。这种情况下,用户不能关闭界面,如果关闭了,即迁移程序就中断了。
异步:将用户请求放入消息队列,并反馈给用户,系统迁移程序已经启动,你可以关闭浏览器了。然后程序再慢慢地去写入数据库去。这就是异步。但是用户没有卡死的感觉,会告诉你,你的请求系统已经响应了。你可以关闭界面了。
19. 静态变量和实例变量的区别。
(被static修饰的变量和普通变量的区别)
静态变量是被 static 修饰符修饰的变量,也称为类变量,它属于类,不属于类的任何一个对象,一个类不管创建多少个对象,静态变量在内存中有且仅有一个拷贝;
实例变量必须依存于某一实例,需要先创建对象然后通过对象才能访问到它。静态变量可以实现让多个对象共享内存。
20. equals 与==区别
==
- 如果基本类型,==表示判断它们值否相等;
- 如果引用对象,==表示判断两个对象指向内存地址否相同。
Equals
- 如果不重写,和==的作用一样
- 如果自己类重写 equals 方法,可以自定义两个对象否相等。
如果字符串,表示判断字符串内容否相同;
如果object 对象方法,比较也引用内存地址值;
21. 编译异常和运行异常
运行时异常:都是RuntimeException类及其子类异常,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。
常见的运行异常:
- NullPointerException(空指针异常)
- IndexOutOfBoundsException(下标越界异常)
- ClassNotFoundException (指定的类不存在)
- NoSuchMethodError (方法不存在错误)
- NumberFormatException (数字格式异常)
- ArrithmeticException(算数异常)
编译异常:是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。
常见的编译异常:
IOException(包括FileNotFoundException)
SQLException
22. 如何将字符串反转呢?
使用 StringBuilder 或 StringBuffer reverse 方法
23. java8 新特性。
- Lambda 表达式:Lambda 允许函数作为一个方法参数
- Stream API :新添加 Stream API(java.util.stream)真正函数式编程风格引入到 Java 中
- 方法引用:方法引用提供了非常有用语法,可以直接引用已有 Java 类或对象(实例)方法 或构造器。
- Optional 类:Optional 类已经成为 Java 8 类库一部分,用来解决空指针异常。
- Date Time API:加强对日期与时间处理
24. break 和 continue 有什么区别?
break 可以使流程跳出 switch 语句体,也可以在循环结构终止本层循环体,从而提前结束 本层循环。
continue 作用跳过本次循环体中余下尚未执行语句,立即进行下一次循环条件判定,可以 理解为仅结束本次循环
25. get和post的区别:
Get:
- get主要用来获取资源
- get把参数包含在url中,以?分隔路径与参数,并用&连接多个参数。
- get直接暴露在url中,相对不安全
- get支持缓存和添加到书签,因为所有的信息都在url里面。
- get是幂等的,意味着无论执行多少次相同的get请求,结果都是形同的。
Post:
- post用来提交数据到服务器
- post通过http请求体发送数据
- post数据不在url中显示,相对比较安全
- post不建议缓存和添加到书签中,因为其数据包含在请求体中,重新访问需要再次提交数据。
- post不是幂等的,同样的post请求可能导致服务器上不同的状态变化
26. String s = new String("jay");创了几个字符串对象?
第一次调用 new String("jay"); 时,会在堆内存中创一个字符串对象,同时在字符串常量池 中创一个对象 "jay"
第二次调用 new String("jay"); 时,只会在堆内存中创一个字符串对象,指向之前在字符串 常量池中创 jay
27. String 类常用方法都有那些呢?
indexOf():返回指定字符索引。
charAt():返回指定索引处字符。
replace():字符串替换。
trim():去除字符串两端空白。
split():分割字符串,返回一个分割后字符串数组。
getBytes():返回字符串 byte 类型数组。
length():返回字符串长度。
toLowerCase()(偷漏k死):将字符串转成小写字母。
toUpperCase()(套普k死):将字符串转成大写字符。
substring():截取字符串。
equals():字符串比较。
28. Object 中定义了哪些方法?
getClass(); 获取类结构信息
hashCode() 获取哈希码
equals(Object) 默认比较对象地址值否相等,子类可以重写比较规则
clone() 用于对象克隆
toString() 对象转变成字符串
notify() 多线程中唤醒功能
notifyAll() 多线程中唤醒所有等待线程功能
wait() 让持有对象锁线程进入等待
wait(long timeout) 让持有对象锁线程进入等待,设置超时毫秒数时间
wait(long timeout, int nanos) 让持有对象锁线程进入等待,设置超时纳秒数时间 finalize() 垃圾回收前执行方法
hashCode 作用什么
- hashCode 存在主要用于查找快捷性,如 Hashtable,HashMap 等,hashCode 用来在散列存储结构中确定对象存储地址;
- 如果两个对象相同,就适用于 equals(java.lang.Object) 方法,那么这两个对象 hashCode 一定要相同;
- 如果对象 equals 方法被重写,那么对象 hashCode 也尽量重写
- 两个对象 hashCode 相同,并不一定表示两个对象就相同,也就不一定适用于 equals(java.lang.Object) 方法,只能够说明这两个对象在散列存储结构中
29. this 和super 关键字作用
相同点:
this和super不能出现在同一个构造函数里
不同点:
this:在同一类中调用其他方法
super:从子类中调用父类的构造方法
this是指向本对象的指针
super是一个java关键字
30. 列举出 JAVA 中 6 个比较常用包
java.lang;
java.util;
java.io;
java.sql;
java.awt;
java.net;
31. 数组和ArrayList转换
数组转集合: Arrays.asList()
集合转数组: 集合名.toArray()
ArrayList<String> list = **new** ArrayList<>();
Collections.*addAll*(list, **"abc"**, **"def"**);
String[] array = list.toArray(**new** String[0]);
System.**out**.println(Arrays.*toString*(array));
List<String> list1 = Arrays.*asList*(array);
System.**out**.println(list1);
32. 说下Vector和Array List、LinkedList联系和区别?
分别的使用场景
线程安全
- ArrayList:底层是数组实现,线程不安全,查询和修改非常快,但是增加和删除慢
- LinkedList:底层是双向链表,线程不安全,查询和修改速度慢,但是增加和删除速度快
- Vector:底层是数组实现,线程安全的,操作的时候使用synchronized进行加锁使用场景
使用场景:
- Vector已经很少用了
- 增加和删除场景多则用LinkedList
- 查询和修改多则用ArrayList
33. 如果需要保证线程安全,ArrayList应该怎么做,用有几种方式
- 自己写个包装类,根据业务一般是add/update/remove加锁
- Collections.synchronizedList(new ArrayList<>());使用synchronized加锁
- 并发包下的CopyOnWriteArrayList<>(),使用ReentrantLock加锁
34. CopyOnWriteArrayList的设计思想是怎样的,有什么缺点?
设计思想:读写分离+最终一致
缺点:内存占用问题,由于写时复制,内存里面同时存在两个对象占用的内存,如果对象大 则容易发生YongGC和FullGC
35. 了解CopyOnWriteArrayList吗? 和Collections.synchronizedList实现线程安全有什么区 别,使用场景是怎样的?。
CopyOnWriteArrayList:
- 执行修改操作,会拷贝一份新的数据(add/set/remove),代价昂贵,修改好后会将原来的集合指向新的集合来完成操作,使用ReentrantLock来保证不会让多个线程 同时修改
- 场景: 适合读操作远大于写操作的场景 (读操作是不需要加锁的,直接获取,但是 删除和增加需要加锁,读多写少)vbh
Collections.synchronizedList:
- 线程安全的原因就是几乎每个方法都是使用synchronized加同步锁
- 场景:写操作性能比CopyOnWriteArrayList好,但是读操作性能并不如 CopyOnWriteArrayList
高级篇
1. @Resource和@Autowired的区别
@Resource是JDK自带的,@Autowired是Spring框架下面的。
@Resource是默认按照名称注入的。找不到名称可以指定类型注入
@Autowired是按照类型注入的,如果项目中存在多个相同类型的Bean时,可以按照名称注入,通过@Qualifier 注解指定bean的名称。
2. 是否可以继承 String 类?
String 类是 final 类,不可以被继承
3. Java 获取反射三种方法
第一种,使用 Class.forName() 静态方法。
第二种,使用类.class 方法
第三种,使用实例对象 getClass() 方法。
4. 数据库连接池有什么用?它有哪些关键参数?
在数据库访问量较大的情况下,频繁的创建连接会带来较大的性能开销。
而连接池的核心思想,就是应用程序在启动的时候提前初始化一部分连接保存
到连接池里面,当应用需要使用连接的时候,直接从连接池获取一个已经建立好的链接。
连接池的设计,避免了每次连接的建立和释放带来的开销。
连接池的参数有很多,不过关键参数就几个:
首先是,连接池初始化的时候会有几个关键参数:
1. 初始化连接数,表示启动的时候初始多少个连接保存到连接池里面。
2. 最大连接数,表示同时最多能支持多少连接,如果连接数不够,后续要获取连接的线程会阻塞。
3. 最大空闲连接数,表示没有请求的时候,连接池中要保留的最大空闲连接。
4. 最小空闲连接,当连接数小于这个值的时候,连接池需要再创建连接来补充到这个值。
然后,就是在使用连接的时候的关键参数:
5. 最大等待时间,就是连接池里面的连接用完了以后,新的请求要等待的时间,超过这个时间就会提示超时异常。
6. 无效连接清除, 清理连接池里面的无效连接,避免使用这个连接操作的时候出现错误。
不同的连接池框架,除了核心的参数以外,还有很多业务型的参数,比如是否要检测连接 sql 的有效性、连接初始化 SQL 等等,这些配置参数可以在使用的时候去查询 api文档就可以知道。
5. MyBatis的一二级缓存
一级缓存:是 SqlSession 级别的缓存,也叫本地缓存
用户请求数据库的时候,把数据库的缓存方到MyBatis的本地缓存中,后续在请求这个数据的时候,如果命中这个缓存,就直接返回;如果没命中,在去数据库查,和Redis作用比较类似。
可能会有多个SqlSession,造成数据的脏读,相当于多个Redis造成的数据不同步。
二级缓存:开启二级缓存以后,会被多个 SqlSession 共享,所以它是一个全局缓存
解决了一级缓存的数据不同步,因为它的SqlSession是共享的,只要由一个用户去数据库查数据,查到后,先放到二级缓存中,之后的请求先去二级缓存中找,如果有的会话直接返回,相当于共用一个Redis
顺序:先去二级 > 一级 > 去数据库写入数据
6. 聊聊你知道的设计模式
按照模式的应用目标分类,设计模式可以分为创建型模式、结构型模式和行为型模式。
创建型模式:是对对象创建过程的各种问题和解决方案的总结,包括各种工厂模式、单
例模式、构建器模式、原型模式。
结构型模式:是针对软件设计结构的总结,关注于类、对象继承、组合方式的实践经验。
常见的结构型模式,包括桥接模式、适配器模式、装饰者模式、代理模式、组合模式、
外观模式、享元模式等。
行为型模式:是从类或对象之间交互、职责划分等角度总结的模式。
比较常见的行为型模式有策略模式、解释器模式、命令模式、观察者模式、迭代器模式、
模板方法模式、访问者模式。
以上就是我对这个问题的理解。
7. 事务的传播机制:
事务的传播机制决定了当一个方法被调用时,事务应该如何传播。
Spring框架提供了多种传播行为,常用的有以下几种:
1、Required(默认)
- 特点:如果当前存在事务,则加入该事务;否则,创建一个新的事务
- 使用场景:最常见的传播行为,适用于大多数情况。
2、Requires_new
- 特点:总是创建一个新的事务,如果当前存在事务,则暂停当前事务。
- 使用场景:适用于需要独立事务的操作,如日志记录、事务补偿等。
3、Supports
- 特点:如果当前存在事务,则加入该事务;否则,以非事务方式执行。
- 使用场景:适用于读操作,对事务没有严格要求。
4、Not_supportwed
- 特点:以非事务方式执行,如果当前存在事务,则暂停当前事务。
- 使用场景:适用于不需要事务的操作,如查询缓存、发送邮件等。
5、Mandatory
- 特点:如果当前存在事务,则加入该事务;否则,抛出异常。
- 使用场景:适用于必须在事务上下文中执行的操作。
6、Never
- 特点:以非事务方式执行,如果当前存在事务,则抛出异常。
- 使用场景:适用于绝对不能在事务上下文中执行的操作。
7、Nested
- 特点:如果当前存在事务,则在嵌套事务内执行;否则,创建一个新的事务。
- 使用场景:适用于需要嵌套事务的场景,如复杂的业务流程。
8. 事务的四大特性:
原子性:不开启事务的时候,单独的SQL语句就是一个单独的事务。
一致性:缓存和数据库之间没有一致性。
最终一致性:不管中间的,只要最终的数据统一
隔离新:单独事务被隔离的时候
持久性:
事务的隔离级别:
事务的隔离级别是为了防止多个事务同时访问统一数据时,出现的并发问题。
SQL标准定义的四种隔离级别:
1、Read Uncommitted(读未提交)
一个事务在执行的过程中读取到了其他事务还没有提交的数据
特点:允许一个事务读取另一个事务尚未提交的数据。
问题:肯恶搞会导致脏读、不可重复读和幻读。
使用场景:非常少,因为这种隔离级别几乎没有任何保护。
2、Read Committed(读已提交)
两次读取的数据不一样,自己事务没有提交的时候可以读取别的已经提交的事务。
特点:一个事务只能读取另一个事务已经提交的数据。
问题:可能会导致不可重复读和幻读。
使用场景:广泛使用,使用于大多数应用场景。
3、Repeatable Read(可重复读)
特点:在一个事务内多次读取同一数据,结果是一致的,即使其他事务对数据进行了修改并提交。
问题:可能会导致幻读。
使用场景:MySQL InnDB默认的隔离级别,适用于需要高度一致性的场景。
4、Serializable(可序列化)
可避免 脏读、不可重复读、幻读 的发生。
- 特点:最高的隔离级别,通过强制食物排序,完全杜绝了并发问题。
- 问题:性能开销大,可能会导致大量锁竞争。
- 使用场景:适用于对数据一致性要求极高的场景,如金融交易系统。
9. Stream流的中间操作
Stream流的中间操作,所谓中间操作的意思是,stream经过中间操作以后,变成的还是stream。
- filter---从stream流中过滤某些元素
- limit--- 截断流,使其元素不超过指定数量
- skip--- 跳过元素
- distinct---筛选,去除重复元素
- sort--- 流中的数据排序
- map--- 映射
10. Stream流的终端操作
查找与匹配
- allMatch--检查是否匹配所有元素
- anyMatch--检查是否至少匹配一个元素
- noneMatch--检查是否没有匹配所有元素
- findFirst--返回第一个元素
- findAny--返回当前流中的任意元素
- count--返回流中元素的总个数
- max--返回流中最大值
- min--返回流中最小值
11. 匿名内部类使用场景
实现接口:当需要实现一个接口,并且实现类只需要用一次时,可以使用匿名内部类来简化代码
继承类:当需要创建一个类的子类,并且子类只需要用一次时,可以使用匿名内部类来实现
线程创建:在创建线程时,如果Runnable接口的实现只需要用一次,可以使用匿名内部类
12. Spring下面有哪些API?
- Web开发:包括Spring MVC用于构建Web应用程序,提供RESTful服务等。
- 安全性:Spring Security提供了全面的安全服务,包括认证和授权。
- 数据访问:Spring Data提供了对各种数据库的访问支持,如Spring Data JPA、Spring Data MongoDB等。
- 集成:Spring Integration支持企业集成模式,包括消息传递、事件驱动等。
- 测试:Spring Test提供了对应用程序的测试支持,包括单元测试和集成测试。
- 配置管理:Spring Cloud Config提供了分布式配置管理服务。
- 服务发现:Spring Cloud Eureka实现了服务发现机制,帮助微服务架构中的服务注册与发现。
- 路由和负载均衡:Spring Cloud Gateway提供了路由和负载均衡的功能。
- 消息队列:Spring AMQP与RabbitMQ等消息队列服务集成,支持消息驱动的架构。
- API文档:Spring REST Docs用于生成API文档,结合Swagger可以提供更丰富的API描述和测试功能。这些API共同构成了Spring生态系统的基础,为Java开发人员提供了全面的开发支持。
13. 跨域是什么
- 当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域。
14. 跨域怎么解决
前端
跨域请求JSONP
$.ajax({
url:http://localhost:8080/cors/jsonp/1’
dataType: "jsonp"
//jsonp: "a" 不指定默认callback
//jsonpCallback:"cc" 不指定自动生成
type:'GET
success: function(result){
alert(result.data)
}
}):
- JSONP的优点就是因为他够老,能兼容各种浏览器,无兼容问题
- JSONP缺点:只支持get,而且存在安全问题。
- 技术发展至今,jsonp这种前后端耦合的方式基本已被代替
跨域请求CORS
$.ajax(
url:"http://localhost:8080/cors/1",
type:"CET",
success: function(result) {
alert(result. data)
}
});
- CORS从具体的代码实现上来说比较方便,前端几乎不需要写任何代码就可以支持。主要是靠服务端进行配置
- CORS需要浏览器和服务器同时支持。目前所有浏览器都支持该功能,IE浏览器不能低于IE10。
- 浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
跨域请求NGINX
$. ajax({
url: "http://localhost:808l/cors/1",
type: "CET",
success: function(result) {
alert(result.data)
}
});
同源策略只限制于浏览器端的服务器,当后端服务器访问后端服务器时即使是非同源也是可以正常访问的
后端
使用注解或全局配置:
- @CrossOrigin
- @CorsMapping
全局过滤器
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class MyCorsFilter {
@Bean
public CorsFilter corsFilter() {
// 1.创建 CORS 配置对象
CorsConfiguration config = new CorsConfiguration();
// 支持域,格式是:域名+端口(http://localhost:8081),
config.addAllowedOriginPattern("*");
// 是否发送 Cookie
config.setAllowCredentials(true);
// 支持请求方式:POST,GET,HEAD等,*代表全部支持
config.addAllowedMethod("POST");
config.addAllowedMethod("GET");
// 2.添加地址映射
UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
// /** 表示当前项目的所有请求地址,都支持跨域访问。
corsConfigurationSource.registerCorsConfiguration("/**", config);
// 3.返回 CorsFilter 对象
return new CorsFilter(corsConfigurationSource);
}
}
全局MVC配置
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MyWebMvcConfigurer implements WebMvcConfigurer {
/**
* 全局CORS配置
*
* @param registry CORS注册器
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
// 映射服务器中那些http接口进行跨域访问
registry.addMapping("/cors/*")
// 配置哪些来源有权限跨域
.allowedOrigins("http://localhost:8081")
// 配置运行跨域访问的请求方法
.allowedMethods("GET", "POST", "DELETE", "PUT");
}
}
15. Http协议及Http,Https的区别
http(超文本传输协议)是用于分布式、协作性、超媒体信息系统的应用层协议。
http:
- 安全性:以明文形式发送数据,不提供任何形式的数据加密
- 性能:HTTP:由于没有加密过程,HTTP请求处理速度理论上会比HTTPS快一些。
- 信任度:HTTP:网站使用HTTP协议时,用户访问时可能看到“不安全”的警告提示,尤其是在涉及个人信息输入的情况下。
https:
- 安全性:在http基础上加入了SSL/TLS加密协议,为浏览器和服务器之间的通信提供加密保护。
- 性能:虽然HTTPS需要额外的时间来建立安全连接(SSL/TLS握手),但是一旦连接建立起来,对于大多数应用场景来说,这个影响是可以接受的。而且,HTTPS可以通过HTTP/2协议进一步优化性能,提高加载速度。
- 信任度:HTTPS:采用HTTPS的网站可以获得更高的信任度,因为这意味着网站采取了措施来保护用户的隐私和数据安全。
16. 什么是ajax?
ajax是一种创建交互式网页应用的开发技术。
特点:
- 异步性:这是Ajax的核心特性之一。传统的Web请求需要刷新整个页面来显示新数据,而Ajax可以在不干扰当前页面显示的情况下发送请求并接收响应。
- 非阻塞操作:用户可以继续与页面交互,即使请求还在处理中,这提高了用户的互动体验。
- 局部更新:仅更新页面的某一部分,而不是刷新整个页面,节省了带宽,并提高了性能。
17. 四大域对象:
ServletContext:
- 作用范围:整个Web应用程序。
- 用途:用于在整个应用的所有Servlet和JSP页面之间共享数据。例如,可以用来存储一些配置参数或统计信息等。它也是获取Web应用资源路径的一个入口。
HttpSession:
- 作用范围:一次会话期间(从用户到达网站开始,直到离开网站为止,除非超时或者手动失效)。
- 用途:用于保存某个用户的状态信息。通过会话跟踪技术(如Cookie或URL重写),可以在多个请求之间保持用户的数据,比如登录状态、购物车内容等。
ServletRequest:
- 作用范围:一次请求/响应周期内。
- 用途:主要用于在处理同一个请求的不同Servlet或过滤器(Filter)之间传递数据。例如,在一个Servlet处理完请求后将结果存入请求属性中,然后转发到JSP页面展示这些数据。
PageContext:
- 作用范围:当前页面内。
- 用途:主要用于JSP页面内部,简化对其他三个域对象的操作以及管理页面内的各种资源。它提供了统一的方法来访问request、session和application范围的数据,同时也支持对输出流、异常信息等进行管理。
18. 列举文档对象模型 DOM 里 document 的常用的查找访问节点的方法
getElementById(id):
- 返回具有指定ID的第一个元素。
- 示例:var element = document.getElementById('myElement');
getElementsByClassName(classNames):
- 返回文档中所有指定类名的元素集合(实时更新的HTMLCollection)。
- 示例:var elements = 01ocument.getElementsByClassName('myClass');
getElementsByTagName(tagName):
- 返回文档中指定标签名的所有元素集合(实时更新的HTMLCollection)。
- 示例:var elements = document.getElementsByTagName('div');
getElementsByName(name):
- 返回文档中具有指定名称的所有元素集合(注意:此方法返回的是NodeList,在某些情况下可能只适用于特定类型的元素,如表单元素)。
- 示例:var elements = document.getElementsByName('myRadioGroup');
querySelector(selector):
- 返回文档中与指定的选择器或选择器组匹配的第一个元素。如果找不到匹配项,则返回null。
- 示例:var element = document.querySelector('.myClass #myId');
querySelectorAll(selectors):
- 返回文档中与指定的选择器或选择器组匹配的所有元素的一个静态列表(NodeList)。如果没有找到匹配项,则返回一个空的NodeList。
- 示例:var elements = document.querySelectorAll('div.myClass');
elementFromPoint(x, y):
- 返回位于页面视口中指定坐标处最上层的元素。
- 示例:var element = document.elementFromPoint(200, 150);
elementsFromPoint(x, y):
- 返回位于页面视口中指定坐标处从上到下的所有元素的数组。
- 示例:var elements = document.elementsFromPoint(200, 150);
SSM
1. Spring
Spring框加是Java应用最广的框架,它主要是一种理念,包括IOC(控制反转)和AOP“(面向切面编程)。
Spring是一个轻量级、DI/IOC和AOP容器的开源框架
轻量级是用户适应框架。属于半自动,简单、灵活、启动快、侵入性小()、依赖少(用什么吗加什么)
重量级是框架适应用户。属于全自动,功能丰富、复杂、启动慢、侵入性强、依赖多
Spring 是一个容器,存放了Java里类的对象。也叫JavaBean,就是写的类。
1.1. Spring:
Spring是一个轻量级的开源框架,以一个容器的形式存放Java类的对象称JavaBean类。
特点: 依赖注入DI:Spring提供了一种方式,通过容器管理对象的生命周期和依赖关系,从而实现解耦合和高内聚的代码结构。
面向切面编程AOP:指在周期中加入操作,将整个程序允许逻辑看成时间轴,每一块都是一个操作节点。允许开发者定义横切关注点。添加内容也称代码增加(前置增强、后置增强、返回增强、异常时增强、最终增强)。
组件化:创建Java类实例并放入spring容器的过程
事务管理:Spring提供了声明式和编程式事务管理,支持多种事务管理器
数据访问:提供了对JDBC、ORM和XML绑定的支持,简化了数据访问层的开发
IOC:控制反转--指类里的对象进入spring容器
DI:依赖注入--从容器里拿出使用
重要程度:约定大于配置--意味着开发者遵循框架的约定和最佳实践,而不是通过大量的配置来定制应用程序的行为。
2. MVC
Spring MVC拥有控制器,作用跟Struts类似,接受外部请求,解析参数传给服务层。
MVC是指:C控制层、M模块层、V显示层这样的设计理念。
再SSM里,Spring MVC本身就是MVC框架,作用是帮助我们(也可以叫约束)我们按照MVC的设计来开发web项目。
MyBatis是用来操作数据库的,也在MVC里。
V是指展示部分,一般指JSP
2.1. SpringMVC
M --指Model 模型层(Java底层)
V --指view 视图层(页面)
C --Controller 控制器(java底层与视图的桥梁)作用:让前后端联系起来
SpringMVC --接收外部请求,解析参数传给服务器
SpringMVC架构原理:
- 发起请求到前端控制器(DispatcherServlet)
- 前端控制器请求HandlerMapping查找Handler,可以根据xml配置,注解进行查找
- 处理器映射器HandlerMapping向前端控制器返回Handler
- 前端控制器调用处理器适配器执行Handler
- 处理器适配器去执行Handler
- Handler执行完成给适配器返回ModelAndView
- 处理器适配器向前端控制器返回ModelAndView,ModelAndView是springMVC框架的底层对象包括Model和View
- 前端控制器请求视图解析器去进行视图解析根据逻辑视图名解析成真正的视图jsp
- 视图解析器向前端控制器返回view
- 前端控制器进行视图渲染,视图渲染将模型数据(在ModelAndView对象中)填充到request域
- 前端控制器向用户响应结果
DispatcherServlet 中央控制器
HandlerMapping 处理器映射器
HandlerAdapter 处理器适配器
ViewResolver 视图解析器
3.2.2. 原理
SpringMVC架构原理解析
第一步:发起请求到前端控制器(DispatcherServlet)
第二步:前端控制器请求HandlerMapping,查找 Handler可以根据 xm1配置、注解进行查找
第三步:处理器映射器 HandlerMapping,向前端控制器返回 Handler
第四步:前端控制器调用处理器适配器去执行 Handler
第五步:处理器适配器去执行 Handler
第六步:Handler 执行完成给适配器返回 ModelAndView
第七步:处理器适配器向前端控制器返回 ModelAndviewModelAndView是 springmve,框架的一个底层对象,包括 Model 和 view
第八步:前端控制器请求视图解析器去进行视图解析根据逻辑视图名解析成真正的视图(jsp)
第九步:视图解析器向前端控制器返回 View
第十步:前端控制器进行视图渲染视图渲染将模型数据(在 ModelAndView对象中)填充到 request 域
第十一步:前端控制器向用户响应结果
3. MyBatis
MyBatis是一款持久层框架,支持定制化SQL、存储过程以及高级映射。
持久化一般值得是dao层
对象关系映射(object Relational Mapping,简称ORM)
ORM(object Relational Mapping)是持久层框架,是面向对象语言。
Relational 对象关系映射。
对象是实体类,关系是关系型数据库,映射是数据库和实体类的一一对应的关系。
技术栈
一、泛型、HashMap、迭代器
1. 什么是泛型?泛型的好处是什么?
泛型是lava SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数好处
泛型提供了编译期的类型安全,确保你只能把正确类型的对象放入 集合中,避免了在运行时出现ClassCastException。
2. 泛型标识:
泛型标识:当我们定义一个数据的时候,不知道到底是什么数据类型,那我们就用一个特殊的标识代替,本质就是一个占位符,当我们使用的时候,再用确定的数据类型替换我们的标识。
T:通用泛型类型,通常作为第一个泛型类型
E:集合元素 泛型类型,主要用于定义集合泛型类型 List<E>
K:映射·键 泛型类型,主要用于定义映射泛型类型
V:映射-值 泛型类型,主要用于定义映射泛型类型
泛型类在创建对象的时候,如果没有指定类型,将按照Object类型来操作
3. 泛型有几种?
泛型类
泛型方法
泛型接口
4. 泛型的子类和父类的类型要一致嘛?
如果子类也是泛型类,子类和父类的泛型类型要一致,如果子类泛型标识和父类不一样,就无法得到具体的数据类型,会报红。
5. 泛型的本质是什么?
泛型的本质是参数化类型
泛型提供了编译期的类型安全
6. 说说泛型擦除(用来兼容JDK5之前的代码)
泛型信息只存在于代码编译阶段,在进入IM 之前,与泛型相关的信息会被擦除掉,专业术语叫做类型擦除。
- 不指定泛型,擦除的话,就直接擦成Object
- 指定上限,擦到指定的上限
- 指定下限,擦成Object
7. HashMap的底层原理是什么?
- HashMap是由数组跟链表组成的,当数组长度大于等于64,链表长度大于8,链表结构就会转成红黑树;当链表长度小于6的时候红黑树就会变回链表;
- 在存值的时候,会通过哈希取余法获取数组下标,如果该数组下标下有东西<key,value,node对象>,就会通过equals来判断这个key是否一样,如果一样就覆盖当前的值,如果不同就追加到链表;
- 1.8之前是头部追加,1.8之后是尾部追加
8. 为什么重写equals必须重写hashcode?
如果两个对象相等,则 hashcode ⼀定也是相同的。两个对象相等,对两个对象分别调⽤ equals⽅法都返回 true。反之,两个对象有相同的 hashcode 值,它们也不⼀定是相等的 。
因此,equals ⽅法被覆盖过,则 hashCode ⽅法也必须被覆盖。hashCode() 的默认⾏为是对堆上的对象产⽣独特值。如果没有重写 hashCode() ,则该class 的两个对象⽆论如何都不会相等(即使这两个对象指向相同的数据)
9. 迭代
即Collection集合元素的通用获取方式。在取元素之前先要判断集合中有没有元素,如果有,就把这个 元素取出来,继续在判断,如果还有就再取出出来。一直把集合中的所有元素全部取出。这种取出方式专业 术语称为迭代。
二、反射、内部类
1. 反射的作用
JAVA反射提供了在运行时获取类各个信息的能力,(名称、父类、接口、变量、方法)
虚方法
2. -java-mirror的常见方法
getMetheds():获取所有的公共方法,包括父类的实现接口的方法
getName():获取类名
getSuperName():获取父类名
getInterfaces():获取接口名
getDeclaredConstructors():获取所有的构造方法
getDeclaredFieies():获取所有的成员变量
getDeclaredMetheds():获取所有的方法
3. 获取反射对象的4种方式
1、类名.class
2、Integer num = 180;
Num.getClass();
3、Class.forName("java.lang.Integer");
4、Integet.TYPE
4. 获取反射对象的4种方式
1、类名.class
2、Integer num = 180;
Num.getClass();
3、Class.forName("java.lang.Integer");
4、Integet.TYPE
5. 反射的优缺点
优点:
- 动态性:反射允许在运行时动态地加载、查找和使用类,可以在编译时不知道类名的情况下实例化对象、调用方法和访问字段 ,
- 灵活性:反射提供了一种灵活的方式来操作类和对象,使得代码可以更加通用、动态,适用于各种场景”。
- 框架开发:反射在框架开发中非常有用,例如依赖注入、配置文件解析、ORM(对象关系映射)等,使得框架可以适应不同的类和配置 ”
- 插件系统:反射使得可以在运行时加载和使用插件,从而实现模块化和可扩展的应用程序。
缺点:
- 性能损失:使用反射会导致运行时的性能损失,因为反射操作需要动态查找和调用方法,相比静态绑定会更慢 ”
- 安全隐患:反射可以访问和修改类的私有成员,这可能会破坏封装性和安全性。如果不适当地使用反射,可能会导致意外的行为和安全漏洞”
- 代码可读性隆低:反射使得代码更加动态和复杂,可能会降低代码的可读性和维护性”。
- 编译时检查确实:由于反射是在运行时进行的,编译器无法金叉愈多反射型骨干的错误,例如类名乒协错误、方法名错误等,只能在运行时发现。
6. 反射API
获取类对应的字节码的对象(三种)
- 调用某个类的对象的getClass()方法,即:对象.getClass();
- 调用类的class属性类获取该类对应的Class对象,即:类名.class
- 使用Class类中的forName()静态方法(最安全,性能最好)即:Class.forName(“类的全路径”)
注意:在运行期间,一个类,只有一个Class对象产生。
三种方式常用第三种,第一种对象都有了还要反射干什么。第二种需要导入类的包,依赖太强,不导包就抛编译错误。
7. 类的作用范围
局部<成员<静态<包<程序
平时使用局部内部类 -(匿名内部类)比较多
8. 匿名内部类使用场景:
实现接口:当需要实现一个接口,并且实现类只需要用一次时,可以使用匿名内部类来简化代码
继承类:当需要创建一个类的子类,并且子类只需要用一次时,可以使用匿名内部类来实现
线程创建:在创建线程时,如果Runnable接口的实现只需要用一次,可以使用匿名内部类
三、Servlet
1. 什么是Servlet
Servlet是一个运行在服务器端的Java小程序,通过HTTP协议用于接收来自客户端请求,并发出响应。
是JavaWeb的三大组件(servlet,filter(过滤器),listener(监听器))之一,它属于动态资源。
2. 实现Servlet的方式
实现javax.servlet.Servlet接口;
继承javax.servlet.GenericServlet类;和Http不相关
继承javax.servlet.http.HttpServlet类;和Http相关
3. Servlet的作用
接受请求 处理请求 完成响应
4. Servlet的生命周期
Servlet的生命周期 | ||
调用时机 | 方法名称 | 调用次数 |
创建Servlet对象时调用 | init()诞生方法 | 一次 |
浏览器访问servlet时调用 | service()服务方法 | 访问一次就调用一次 |
服务器关闭前调用 | destory()销毁方法 | 一次 |
第一次访问时调用 | 构造方法 | 一次 |
四、JDBC
1. JDBC核心类接口介绍
JDBC中的核心类有:
DriverManager 驱动管理器
Connection 连接对象
Statement 发送sql语句
ResultSet 查询返回的结果集
2. 连接数据库的四大参数
DriverClass 连接数据库的驱动类
Url 连接数据库的地址
Username 用户名
Password 密码
3. 什么是JDBC?
JDBC是Java语言中用于执行SQL语句的API。
4. 数据库连接池的概念
用池来管理Connection,这可以重复使用Connection。有了池,所以我们就不用自己来创建Connection,而是通过池来获取Connection对象。当使用完Connection后,调用Connection的close()方法也不会真的关闭Connection,而是把Connection“归还”给池。池就可以再利用这个Connection对象了
5. https协议及http、https区别
https协议需要到ca申请证书,一般免费证书很少,需要交费。
http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。
http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络,比http协议安全
五、JWT
1. JWT概述
JWT全称Json Web Token是web领域 基于json数据格式的令牌,是访问资源接口所需的凭证。
2. JWT由什么组成?
JWT由三部分组成 Header-头部、Payload-载荷 Signature-签名
3. JWT的优缺点
优点:无状态、跨域支持、轻量级
缺点:不适合存储敏感信息,一旦签发,无法撤销、过期管理复杂
4. token放在哪里?
浏览器的cookie、localstroage、sessionstroage(对于web应用),或者设备上安全的本机数据库(对于移动应用)
5. token安全吗?
是安全的,因为有数字签名,用HS256算法实现,后端校验数字签名,暴力破解会留下痕迹,可以丢弃旧的token重新生成新的token
6. token过期了怎么办?
- 自动刷新:如果支持,服务端可以在检测到token即将过期时自动发送一个新的token给客户端 ,替换旧的
- 用户提示:前端可以设置一个定时任务检查token的有效性,并在token过期时提示用户登录或重新授权
- 直接重定向:如果后端返回401未授权响应 ,前端清除当前token并重定向到登录页面
7. token的失效情况
- 如果改了头部和负载部分的数据就会验证失败
- 密钥改了
- token过期
token通过cookie发送到浏览器,用户在访问其他服务器接口时会自动在cookie中带上jwt令牌发送到服务器
注:JWT校验时使用的签名密钥必须和生成时使用的是相同的
8. JWT的使用场景
身份验证(Authentication):JWT 可以被用作用户登录的身份验证凭证。当用户成功登录后,服务端可以生成一个包含用户信息的 JWT,并将其返回给客户端。以后,客户端在每次请求时都会携带这个 JWT,服务端通过验证 JWT 的签名来确认用户的身份。
授权(Authorization):在用户登录后,服务端可以生成包含用户角色、权限等信息的 JWT,并在用户每次请求时进行验证。通过解析 JWT 中的声明信息,服务端可以判断用户是否有权限执行特定的操作或访问特定的资源。
信息交换(Information Exchange):由于 JWT 的声明信息可以被加密,因此可以安全地在用户和服务器之间传递信息。这在分布式系统中非常有用,因为可以确保信息在各个环节中的安全传递。
单点登录(Single Sign-On):JWT 可以被用于支持单点登录,使得用户在多个应用之间只需要登录一次即可使用多个应用,从而提高用户体验。
9. Cookie
使用场景
Cookie最初被设计用来存储一些简单的用户信息,以便在用户和服务器之间保持状态。以下是Cookie最初存储的一些基本信息:
- 会话状态管理:用于跟踪用户的登录状态、购物车内容、游戏分数等需要记录的信息。
- 个性化设置:存储用户的自定义设置,如主题、字体大小等。
- 浏览器行为跟踪:用于分析用户行为,比如跟踪用户的浏览习惯。
随着技术的发展,现代网站中的Cookie存储的信息变得更加复杂和多样化,主要包括:
- 身份验证和会话管理:存储用户的登录凭证,如session ID或认证令牌,以保持用户的登录状态。
- 个性化内容:根据用户的历史行为和偏好,存储信息以提供个性化的内容和推荐。
- 分析和广告:用于跟踪用户在网站或跨网站上的行为,以进行分析和定向广告。
- 安全措施:如HttpOnly和Secure属性,增加安全性,防止跨站脚本攻击(XSS)和确保数据通过HTTPS传输。
- 跨站请求伪造(CSRF)防护:使用SameSite属性来控制Cookie在跨站请求中的发送,以防止CSRF攻击。
总的来说,Cookie的使用已经从最初的简单状态管理扩展到了更复杂的用户跟踪、个性化体验和安全保护等多个方面。
10. Secure属性
Secure 属性是一个指示,告诉浏览器这个Cookie应该只能通过安全的HTTPS连接发送给服务器。这意味着,如果浏览器和服务器之间的连接不是HTTPS,那么带有Secure属性的Cookie不会被发送。这个属性有助于防止在不安全的HTTP连接中传输敏感信息,因为这些信息可能会被中间人攻击者截获。
SameSite属性
SameSite 是一个设置在Cookie中的属性,用于提供一些保护,防止跨站请求伪造(CSRF)攻击。
跨站请求伪造(CSRF) 是一种攻击方式,攻击者诱使已经登录的用户在不知情的情况下,对服务器发起非预期的请求。例如,攻击者可以创建一个恶意网站,当用户访问这个网站时,它会自动向用户已登录的网站发送请求,执行一些操作,如转账或更改设置。
SameSite属性可以设置为以下几个值:
- Strict:最严格的模式,只有当请求来自同一站点时,Cookie才会被发送。
- Lax:较为宽松的模式,允许在导航到目标网址的GET请求中发送Cookie,但不允许在从其他站点发起的链接中发送。
- None:不限制Cookie的发送,即使请求来自不同的站点,也会发送Cookie,但要求Cookie同时设置Secure属性,即只能在HTTPS连接中发送。
通过合理设置SameSite属性,可以减少CSRF攻击的风险,保护用户免受恶意网站的侵害。
11. HttpOnly属性
HttpOnly 是一个设置在Cookie中的属性,用于增加Web应用程序的安全性。当一个Cookie被标记为HttpOnly时,它不能被客户端的脚本(如JavaScript)访问。这样做的主要目的是防止跨站脚本攻击(XSS)。
跨站脚本攻击(XSS) 是一种攻击方式,攻击者通过在网页中注入恶意脚本,当其他用户浏览该网页时,恶意脚本就会在用户的浏览器上执行。如果Cookie没有设置HttpOnly属性,攻击者可以通过XSS漏洞读取用户的Cookie信息,进而进行会话劫持。
六、websocket
1. websocket是什么?为什么使用websocket?
websocket是基于TCP协议并且是全双工的通讯协议
使用它是因为http不满足服务端主动向客户端发送消息
2. tcp的3次握手和4次挥手
3次握手主要作用是为了确认双方的接收能力和发送能力是否正常
4次挥手确认数据的传出正确
三次握手
三次握手(Three-way Handshake)是指建立一个TCP连接时,需要客户端和服务器总共发送3个包。主要作用是为了确认双方的接收能力和发送能力是否正常、指定自己的初始化序列号为后面的可靠性传送做准备。实质上其实就是连接服务器指定端口,建立TCP连接,并同步连接双方的序列号和确认号,交换TCP窗口大小信息。
第一次握手:客户端向服务端发送一个SYN报文,表示建立连接 得出结论:客户端的发送能力、服务端的接收能力是正常的。第二次握手:服务端收到后向客户端发送,SYN,ACK报文,为这次连接分配资源 得出结论:服务端的发送能力正常第三次握手:客户端收到ACK报文后,向Server端发送ACK报文,并分配资源,到这里就可以认为客户端与服务端已经建立了连接 得出结论:客户端的接受能力正常。至此服务端和客户端的发送和接受能力正常,
四次挥手
SYN:表示建立连接
FIN:表示释放连接
ACK:确认序号有效。表示响应
CLOSE_WAIT:被动关闭
LAST_ACK:等待中断请求的确认状态
连接终止协议(四次挥手)
由于TCP连接是全双工的,因此每个方向都必须单独进行关闭,它是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个FIN意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN仍能发送数据。首先进行关闭的一个方法将执行主动关闭,而另一方执行被动关闭
第一次挥手:客户端向服务端发送一个FIN报文,用来关闭客户到服务器的数据传送。
第二次挥手:服务器收到这个FIN,会给客户端发送一个ACK,Server进入CLOSE_WAIT状态
第三次挥手:服务器向发送一个FIN报文给客户端,用来关闭服务端到客户端的数据传送,Server进入LAST_ACK状态
第四次挥手:客户端收到FIN后,给服务端发送一个ACK报文确认,并将确认序号设置为收到序号+1,Server进入CLOSED状态,完成四次挥手
TCP报文头是用于在网络层和传输层之间传递数据时封装的元数据。TCP 报文头包含了确保可靠传输所需的各种信息,如源端口、目的端口、序列号等。
3. tcp为什么是可靠的连接?
- 通过 TCP 连接传输的数据无差错,不丢失,不重复,且按顺序到达。
- TCP 报文头里面的序号能使 TCP 的数据按序到达
- 报文头里面的确认序号能保证不丢包,累计确认及超时重传机制
- TCP 拥有流量控制及拥塞控制的机制
4. 全双工,半双工,单工的区别
全双工:就是在同一时间内,双方可以同时发送信息(如:微信、QQ)
半双工:在同一时间内,只能由一个用户发送信息,发送完后另一个用户才可以发送(如:BB机)
单工:同一事件内只有一方能接收或发送信息,不能实现双向通信。(一个人只能发送信息或者接收信息)(如:电视)
5. websocket和socket的区别
socket是应用层与tcp/ip协议通信的中间软件抽象层,是一组接口而websocket是一个完整的应用层协议包含一套标准的API
6. http和websocket协议的区别?
http是一次请求一次连接,是短连接
websocket是长连接,建议一次连接后就一直存在
7. UDP和TCP的概念?
TCP在发送消息前会先建立连接,安全几乎不丢包、发送流的
UDP不建立连接,经常丢包不安全
UDP(用户数据报协议)
- 无连接:UDP是一个无连接的协议,它在发送数据之前不需要建立连接。
- 不保证交付:UDP不保证数据包的可靠交付,数据包可能会丢失、重复或乱序到达。
- 快速:由于UDP不需要建立连接和确认交付,它通常比TCP更快。
- 头部开销小:UDP头部开销小,只有8字节,这使得UDP在传输小数据包时非常高效。
- 应用场景:UDP适用于对速度要求高但可以容忍一定丢包率的应用,如视频流、在线游戏、DNS查询等。
TCP(传输控制协议)
- 面向连接:TCP是一个面向连接的协议,它在发送数据之前需要建立一个连接。
- 保证交付:TCP提供可靠的数据传输,确保数据包按顺序到达且不丢失、不重复。
- 拥塞控制:TCP具有拥塞控制机制,可以根据网络状况调整发送速率。
- 流量控制:TCP能够根据接收方的处理能力控制发送数据的速率。
- 头部开销大:TCP头部开销较大,包含20字节或更多的控制信息。
- 应用场景:TCP适用于需要可靠传输的应用,如Web浏览、文件传输、电子邮件等。
七、进程、线程、多线程、锁
(一) 进程
1. 概述
进程(Process):是系统进行资源分配的基本单位,进程是程序的动态执行过程,当程序被加载到内存并开始执行时就形成了一个进程。正在运行的程序(软件)就是一个独立的进程。
简单来说就是程序的运行过程。
2. 特点
1.独立性:每个进程都有自己的命名内存空间,在没有经过进程本身允许的情况下,一个进程不可以直接访问其他的进程空间
2.动态性:进程是动态产生,动态消亡的
3.并发性:任何进程都可以同其他进程一起并发执行
3. 并行、并发、串行
并行:同一时刻,多个指令在同时执行
并发:同一时刻,多个指令在单个cpu上交替执行
串行:单线程依次执行
(二) 线程
1. 概述
- 进程是资源分配最小单位,线程是程序执行的最小单位。
- 线程是程序执行的最小单位。进程可以同时执行每个任务,每个任务就是线程,就是程序内部的一条执行流程,一个任务就是一条线程。
- 多个任务同时执行就是多线程同时执行。
2. 线程的创建方式:
- 继承Thread类创建线程
- 实现Runnable接口创建线程
- 使用Callable和Future创建线程
- 使用线程池例如用Executor框架
- 使用匿名内部类的形式创建线程
- 使用lambda表达式创建线程
3. 线程的基本方法:
1、线程等待:wait方法
2、线程睡眠:sleep方法
3、线程让步:yield方法
4、线程中断:interrupt方法
5、线程加入join方法
6、线程唤醒:notify方法
4. 线程状态:
1、新建
当用new关键字创建一个线程时,还没调用start 就是新建状态。
2、就绪
调用了 start 方法之后,线程就进入了就绪阶段。此时,线程不会立即执行run方法,需要等待获取CPU资源。
3、运行
当线程获得CPU时间片后,就会进入运行状态,开始执行run方法
4、阻塞
5、死亡
5. java默认存在线程
java程序默认是多线程的,程序启动后默认存在两条线程。
1--主线程
2--垃圾回收线程
主线程负责程序的主要逻辑执行,而垃圾回收线程则负责自动管理内存,回收不再使用的对象。
6. 多线程优点
同时处理多个任务提高执行效率。
7. Thread提供的常用API方法
方法 | 含义 |
String getName() | 返回此线程的名称 |
void setName(String name) | 设置线程名字 |
static Thread currentThread() | 获取当前线程的对象 |
static void sleep(Long time) | 让线程休眠,指定的时间,单位为毫秒 |
setPriority(int newPriority) | 设置线程优先级(1~10) 默认为5 |
final int getPriority | 获取线程优先级 |
final void setDaemon | 设置为守护线程 |
void run() | 线程的任务方法 |
void start() | 启动线程 |
8. 线程的调度方式
抢占式调度(随机) ---java中使用的
非抢占式调度(轮流使用)
9. 线程安全问题
指的是多个线程,同时操作同一个共享资源的时候引发的业务安全问题
出现原因:存在多个线程同时执行,同时访问一个共享资源,且存在修改操作
解决方案
线程同步
思想--让多个线程实现先后依次访问共享资源
通过线程同步控制多个线程先后执行的方案
加锁--每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后解锁,然后其他线程才能再加锁进来
加锁实现方案:1、同步代码块 2、同步方法 3、Lock锁
- 同步代码块
作用:把访问共享资源的核心代码上锁
synchronized(锁对象){
访问共享资源的核心代码
}
原理:每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可以进来执行
同步锁的注意事项:
对于当前同时执行的线程来说,同步锁必须是同一把(同一个对象)否则会出bug
锁对象的使用规范
建议使用共享资源作为锁对象,对于实例方法建议使用this作为锁对象(this代表当前资源),静态方法使用类名.class作为锁对象
- 同步方法
把访问共享资源的核心方法给上锁
访问修饰符 synchronized 返回值类型 方法名(){
操作共享资源的代码
}
底层原理:同步方法底层也是隐式锁对象,实例方法为this,静态方法为类名.class
锁的范围是整个方法代码
--同步代码块与同步方法
相比之下锁的范围越小,性能越好
在可读性上同步方法更好
同步代码块--锁的范围为访问共享资源的核心代码
同步方法--锁的范围为访问共享资源的核心方法
- Lock锁
概述:
lock锁是jdk5开始提供的一个新的锁定操作,通过它开发者可以创建锁对象,进行加锁和解锁
优点
程序员自己创建锁对象,手动进行加锁和解锁,更灵活,更方便,更强大
使用
首先Lock是个接口,不能直接实例化,可以采用它的实现类 ReentrantLock 来构建lock锁对象
private final Lock lock = new ReentrantLock();
设置为封装、静态是为了不让外人访问且修改
lock.lock();//加锁
// 访问共享资源代码
lock.unlock(); //解锁
如果线程任务代码出现BUG异常程序不能正常执行就无法解锁了容易引起一些问题,所以手动加锁解锁应放入try...catch...finally中这样就保证即使出错也能解锁
10. 线程通信
线程通信的前提是线程得是安全的
当多个线程共同操作共享的资源时,线程之间通过某种方式互相告知自己的状态,以相互协调避免无效的资源争夺
object类的等待和唤醒方法
方法 | 含义 |
void wait | 让当前线程等待并释放所占锁, 直到另一个线程调用notify方法或notifyAll方法 |
void notify | 随机唤醒正在等待的单个线程 |
void notifyAll | 唤醒正在等待的所有线程 |
上述方法需要使用锁对象调用,且效率较低
注意:wait()方法在等待的时候会释放锁对象,释放了锁对象cpu才会切换其他线程
sleep方法与wait方法的区别
sleep方法时线程休眠,时间到了自动醒来,sleep方法在休眠的时候,不会释放锁
wait方法是线程等待,需要由其他线程进行notify唤醒,wait方法在等待期间会释放锁
提升性能:
使用ReentrantLock实现同步,并获取Condition对象
方法 | 含义 |
void await() | 指定线程等待 |
void singnal() | 指定唤醒单个等待的线程 |
创建ReentrantLock对象
ReentrantLock lock = new ReentrantLock()
获取Condition对象(根据锁对象获取线程的状态对象)
Condition c1 = lock.Condition()
Condition c2 = lock.Condition()
定义一个状态,判断这个状态决定什么线程等待,什么唤醒
//例如:
int status =1;
public void Thread1() {
lock.lock() //加锁
if(status != 1) {
c.await;
}
System.out.println("线程1任务执行");
status = 2;
c2.signal();
lock.unlock(); //解锁
}
public void Thread2(){
lock.lock() //加锁
if(status ! = 2) {
c2.await;
}
System.out.println("线程2任务执行");
status = 1;
c1.signal;
lock.unlock(); //解锁
}
11. 线程池
线程池就是一个复用线程的技术,创建出的线程可以继续复用去处理其他任务,线程会长久存在被重复利用
不使用线程池的问题
用户每发起一次请求就会创建一个新线程来处理,新任务来了继续创建新线程,开销(内存资源、cpu资源)很大,请求过多时,产生大量线程,严重影响性能
12. 线程池的工作原理
线程池内部有一块放固定数量的线程的区域,还有一块区域放用户请求的任务,固定线程数量重复用这些线程处理任务
好处:不会因为任务过多创建很多线程导致系统崩溃
- 存放任务的任务区还可以固定任务的数量,控制不会因为任务过多而导致系统崩溃避免资源耗尽的风险
- 重复用的线程称为工作线程WorkThread 或核心线程。
- 存放任务的区域叫 任务队列WorkQueue
- 这些任务都是对象,必须实现Runnable接口、Callable接口才能成为任务对象
13. 如何创建线程池?
一、方法一、实现ExecutorService
java从jdk5起提供了代表线程池的接口 ExecutorService --规范了线程池基本功能
常用实现类 ThreadPoolExecutor -->创建这个实现类对象代表一个线程池
public class ThreadPool_test1 implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程任务资源");
try {
Thread.sleep(Integer.MAX_VALUE); //线程休眠
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
ExecutorService es = new ThreadPoolExecutor(3,4,7,
TimeUnit.MINUTES,
new ArrayBlockingQueue<>(4),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
Runnable runnable = new ThreadPool_test1();
es.execute(runnable); //线程池自动创建一个新线程,自动处理这个任务,自动执行的!
es.execute(runnable); //线程池自动创建一个新线程,自动处理这个任务,自动执行的!
es.execute(runnable); //线程池自动创建一个新线程,自动处理这个任务,自动执行的!
es.execute(runnable); //复用前面的核心线程
es.execute(runnable); //复用前面的核心线程
es.execute(runnable);
es.execute(runnable);
//临时线程创建时机
es.execute(runnable);
es.execute(runnable);
//新任务的拒绝时机
es.execute(runnable);
//线程池存在后不会中断死亡,里面的线程要长久存活处理线程任务
//es.shutdown(); //等线程池中的任务执行完成后关闭线程池
//es.shutdownNow(); //立即关闭线程池
共七个参数
- 第一个参数--指此线程池当中的核心线程数量
- 第二个参数--指此线程池中最大线程数量
- 第三个参数--指临时线程存活时间
- 第四个参数--指临时线程存活时间的单位
- 第五个参数--指任务队列
-
- 两种任务对列(常用)
- new ArrayBlockingQueue<>()
- 基于数组实现,控制任务数量
- new LinkedBlockingQueue<>()
- 基于链表实现,不限制任务数量
- 第六个参数--指定线程池的线程工厂对象(负责为线程池创建线程)
-
- Executors.defaultThreadFactory() --默认线程工厂对象
- 第七个参数--指定线程池的任务拒绝策略(工作线程都在忙,任务队列也满了,新任务来了怎么处理)
ExecutorService的常用方法
方法 | 含义 |
void execute(Runnable command) | 执行runnable任务 |
Future<T> submit(Callable<T> task) | 执行Callable对象,返回未来任务对象用于获取结果 |
void shutdown() | 等全部任务执行完毕后,再关闭线程池 |
List<Runnable> shutdownNow() | 立刻关闭线程池,停止正在执行的任务,并返回队列中未执行的任务 |
二、方法二、通过Executors工具类实现线程池创建
Executors:是一个线程池的工具类,提供了很多静态方法用于返回不同特点的线程池对象。
public static ExecutorService newFixedThreadPool(int nThreads)
创建固定数量线程的线程池,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程来替代它
public static ExecutorService newSingleThreadExecutor()
创建只有一个线程的线程池任务对象如果该线程出现异常而结束,那么线程池会补充一个新线程
public static ExecutorService newCachedThreadPool(
线程数量随着任务的增加而增加,如果线程任务执行完毕且空闲了60s则会被回收掉
public static ExecutorService newScheduiedThreadPool(int corePoolSize)
创建一个线程可以实现在给定的延迟后允许任务,或者定期执行任务
---这些方法的底层都是通过线程池的实现类ThreadPoolExecutoe创建的线程池对象
三、注意:
在并发系统环境中使用Executors创建线程池如果不注意可能会出现系统风险,所以线程池不允许使用Executors创建而是通过ThreadPoolExecutor的方式
Executors返回的线程池对象的弊端:
- FixedThreadPool 和 SingleThreadPool
允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM(Out of Memory 内存溢出) - CachedThreadPool
允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量线程,从而导致OOM
四、拓展:
计算密集型的任务:核心线程数量 = cpu核数 +1
IO密集型(做文件数据通信)的任务:核心线程数量 = cpu核数 * 2
(三) 多线程
1. 线程池的核心参数有哪些?
- corePoolsize:线程池中核心线程的数量
- maximumPoolsize:线程池中最大线程的数量
- keepAliveTime:当前线程数量超过corePoolsize时,空闲线程的存活时间
- unit:keepAliveTime的时间单位
- workQueue:任务队列。被提交但尚未被执行的任务存放的地方
2. 线程池参数设置
- 计算密集型(CPU密集型)的任务比较消耗cpu,所以一般核心线程数设置的大小等于或者略微大于cpu的核数;一般为N+1(N为CPU
- 的核数)
- 磁盘密集型(IO密集型)的任务主要时间消耗在 10等待上,cpu压力并不大,所以线程数一般设置较大。10密集型一般为2*N(N为CPU的核数)
3. 线程池的拒绝策略:
- AbortPolicy:直接抛出异常,阻止线程正常运行
- CallerRunsPolicy:如果被拒绝的任务线程未关闭,则执行这个任务
- DiscardOldestPolicy:移除线程队列中最早的线程任务
- DiscardPolicy:丢弃当前的线程任务不做任何处理。
4. 什么是死锁?
死锁是指两个或多个事务在同一资源上相互占用,并请求锁定对方的资源,从而导致恶性循环的现象。
5. 什么情况下会造成死锁?
- 互斥条件:资源必须以互斥的方式使用,即一次只能有一个线程占用该资源。
- 占有并等待条件:一个线程已经持有了某个资源,同时又在等待其他线程持有的资源。
- 不可剥夺条件:资源不能被强制剥夺,只有持有资源的线程可以主动释放资源。
- 循环等待条件:存在一个线程等待环,每个线程都在等待下一个线程持有的资源。
6. 死锁怎么解决?
- 如果不同程序会并发存取多个表,尽量约定以相同的顺序访问表,可以大大降低死锁机会。
- 在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁产生概率。
- 对于非常容易产生死锁的业务部分,可以尝试使用升级锁定颗粒度,通过表级锁定来减少死锁产生的概率。
- 如果业务处理不好,可以用分布式事务锁或者使用乐观锁。
7. CAS是什么?
CAS(Compare And Swap ⽐较并且替换)是乐观锁的⼀种实现⽅式,是⼀种轻量级锁,JUC 中很多 ⼯具类的实现就是基于 CAS 的。
8. CAS 是怎么实现线程安全的?
线程在读取数据时不进⾏加锁,在准备写回数据时,先去查询原值,操作的时候⽐较原值是否修改,若未被其他线程修改则写回,若已被修改,则重新执⾏读取流程。
举个例⼦:比如我们要修改数据库里的name(字段),修改前先在数据库里查一下name是什么,然后再修改数据,修改前在判断一下数据库里是否和之前拿的一样,一样在修改,不一样旧不进行操作
Tip:⽐较+更新 整体是⼀个原⼦操作,当然这个流程还是有问题的,我下⾯会提到。 他是乐观锁的⼀种实现,就是说认为数据总是不会被更改,我是乐观的仔,每次我都觉得你不会渣我, 差不多是这个意思。
你这个栗⼦不错,他存在什么问题呢? 有,当然是有问题的,我也刚好想提到。要是结果⼀直就⼀直循环了,CUP开销是个问题,还有ABA问题和只能保证⼀个共享 变量原⼦操作的问题。
9. 锁升级的具体过程
- 初始获取锁:当一个线程首次尝试获取某个对象上的锁时,如果此时锁处于无锁状态,JVM 会尝试将锁设置为偏向该线程。如果成功,则进入偏向锁状态。
- 第二个线程竞争锁:如果有另一个线程也想获取同一个锁,JVM 会检查当前是否为偏向锁。如果是,它会尝试撤销偏向锁,并将锁升级为轻量级锁。
- CAS 获取锁:在轻量级锁状态下,所有竞争线程都会尝试通过 CAS 操作来获取锁。如果成功,则线程获得锁并继续执行;如果不成功,线程可能会选择自旋一段时间后再试。
- 自旋失败后的升级:如果经过多次自旋后仍然无法获取锁,或者锁争用非常激烈,JVM 会决定将锁升级为重量级锁。此时,所有未能获取锁的线程都将被阻塞,直到锁变为可用。
10. ABA问题
ABA问题是指在CAS操作时,其他线程将变量值A改为了B,但是又被改回了A,等到本线程使用期望值A与当前变量进行比较时,发现变量A没有变,于是CAS就将A值进行了交换操作,但是实际上该值已经被其他线程改变过。
解决办法:在变量前面加上版本号,每次变量更新的时候变量的版本号都+1,即A->B->A就变成了1A->2B->3A。只要变量被某一线程修改过,变量对应的版本号就会发生递增变化,从而解决了ABA问题。
(四) 锁
1. 锁的种类
- 乐观锁、悲观锁
- 公平锁、非公平锁
- 可重入锁、非可重入锁
- 共享锁、排它锁。
- 重量级锁和轻量级锁
- 偏向锁
- 分段锁
2. 乐观锁、悲观锁
乐观锁
乐观锁采用乐观的思想处理数据,在每次读取数据时都认为别人不会修改该数据,所以不会上锁,但在更新时会判断在此期间别人有没有更新该数据、通常采用在写时先读出当前版本号然后加锁的方法。具体过程为:比较当前版本号与上一次的版本号,如果版本号一致.则更新,如果版本号不一致,则重复进行读、比较、写操作。
悲观锁
悲观锁采用悲观思想处理数据,在每次读取数据时都认为别人会修改数据、所以每次在读写数据时都会上锁,这样别人想读写这个数据时就会阻塞、等待直到拿到锁。
Java 中的悲观锁大部分基于AQS(Abstract Qucued Synchronized,抽象的队列同步器)架构实现。AQS定义了一套多线程访问共享资源的同步框架,许多同步类的实现都依赖于它,例如常用的Synchronized、ReentrantLock、Semaphore、CountDownLatch等。该框架下的锁会先尝试以 CAS 乐观锁去获取锁、如果获取不到,则会转为悲观锁(如RetreenLock )。
比较适合写入操作比较频繁的场景,如果出现大量的读取操作,每次读取的时候都会进行加锁,这样会增加大量的锁的开销,降低了系统的吞吐量。
3. 公平锁、非公平锁
公平锁
- 概念: 是指多个线程按照申请锁的顺序来获取锁,线程直接进入队列中排队,队列中的第一个线程才能获得锁。
- 好处:公平锁的优点是等待锁的线程不会饿死(专业术语)。
- 缺点:是整体吞吐效率相对非公平锁要低,等待队列中除第一个线程以外的所有线程都会阻塞,CPU唤醒阻塞线程的开销比非公平锁大。
非公平锁
- 概念:非公平锁是多个线程加锁时直接尝试获取锁,获取不到才会到等待队列的队尾等待。但如果此时锁刚好可用,那么这个线程可以无需阻塞直接获取到锁,所以有可能出现后申请锁的线程先获取锁的场景
- 好处:非公平锁的优点是可以减少唤起线程的开销,整体的吞吐效率高,因为线程有几率不阻塞直接获得锁,CPU不必唤醒所有线程。
- 缺点:处于等待队列中的线程可能会饿死,或者等很久才会获得锁。
4. 可重入锁、非可重入锁
可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象或者class),不会因为之前已经获取过还没释放而阻塞。Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。
5. 共享锁、排它锁
共享锁和排它锁多用于数据库中的事物操作,主要针对读和写的操作。而在 Java 中,对这组概念通过 ReentrantReadWriteLock 进行了实现,它的理念和数据库中共享锁与排它锁的理念几乎一致,即一条线程进行读的时候,允许其他线程进入上锁的区域中进行读操作;当一条线程进行写操作的时候,不允许其他线程进入进行任何操作。即读 + 读可以存在,读 + 写、写 + 写均不允许存在。
- 共享锁:也称读锁或 S 锁。如果事务 T 对数据 A 加上共享锁后,则其他事务只能对 A 再加共享锁,不能加排它锁。获准共享锁的事务只能读数据,不能修改数据。
- 排它锁:也称独占锁、写锁或 X 锁。如果事务 T 对数据 A 加上排它锁后,则其他事务不能再对 A 加任何类型的锁。获得排它锁的事务即能读数据又能修改数据。
-
- 排他锁是一种锁的行为模式,描述的是锁的互斥性质。
- 可以根据使用场景设置为轻量级锁或重量级锁
6. 重量级锁和轻量级锁
- 重量级锁是基于操作系统的互斥量(Mutex Lock)而实现的锁,会导致进程在用户态与内核态之间切换,相对开销较大。
- synchronized(重量级锁)在内部基于监视器锁(Monitor)实现,监视器锁基于底层的操作系统的 Mutex Lock实现,因此 synchronized属于重量级锁。<font color='red'>重量级锁需要在用户态和内核态之间做转换</font>,所以synchronized的运行效率不高。
- JDK在1.6版本以后,为了减少获取锁和释放锁所带来的性能消耗及提高性能,引人了轻量级锁和偏向锁。
- 轻量级锁是相对于重量级锁而言的。轻量级锁的核心设计是在没有多线程竞争的前提下,减少重量级锁的使用以提高系统性能。轻量级锁适用于线程交替执行同步代码块的情况(即互斥操作),如果同一时刻有多个线程访问同一个锁,则将会导致轻量级锁膨胀为重量级锁。
- 轻量级锁也叫自旋锁。
7. 偏向锁
给第一个使用这个锁分配一些权重。
- 除了在多线程之间存在竞争获取锁的情况,还会经常出现同一个锁被同一个线程多次获取的情况。偏向锁用于在某个线程获取某个锁之后,消除这个线程锁重人的开销,看起来似乎是这个线程得到了该锁的偏向(偏袒)。
- 偏向锁的主要目的是在同一个线程多次获取某个锁的情况下尽量减少轻量级锁的执行路径、因为轻量级锁的获取及释放需要多次CAS ( Compare and Swap )原于操作,而偏向锁只需要在切换 ThreadID 时执行一次 CAS 原子操作,因此可以提高锁的运行效率。
- 在出现多线程竞争锁的情况时,JVM 会自动撤销偏向锁,因此偏向锁的撤销操作的耗时必须少于节省下来的 CAS原子操作的耗时。
- 综上所述,轻量级锁用于提高线程交替执行同步块时的性能,偏向锁则在某个线程交替执行同步块时进一步提高性能。
- 锁的状态总共有4种:无锁、偏向锁、轻量级锁和重量级锁。随着锁竞争越来越激烈,锁可能从偏向锁升级到轻量级锁,再升级到重量级锁,但在Java 中锁只单向升级,不会降级。
- hashtable
8. 分段锁
- 分段锁并非一种实际的锁,而是一种思想,用于将数据分段并在每个分段上都单独加锁,把锁进一步细粒度化,以提高并发效率。ConcurrentHashMap(线程安全的HashMap)在内部就是使用分段锁实现的。
9. 无锁
没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功,其他修改失败的线程会不断重试直到修改成功
10. 锁的使用场景:
乐观锁:适合读多写少的场景。
悲观锁:适合读少写多的场景。适合金融操作的场景。
公平锁:适合任务调度系统、流程系统。
非公平锁:适合数据一致性不高
可重入锁:适合金融、存取钱等场景。
非重入锁:
共享锁:日志系统。读多写少的场景。
(轻)排他锁:一致性要求强的场景
(轻)自旋锁:低延迟系统,
重量级锁:
轻量级锁(操作系统级别的锁):
(轻)偏向锁:
分段锁:
11. 锁升级
没有优化以前,synchronized是重量级锁(悲观锁),使用 wait 和 notify、notifyAll 来切换线程状态非常消耗系统资源;线程的挂起和唤醒间隔很短暂,这样很浪费资源,影响性能。所以 JVM 对 synchronized 关键字进行了优化,把锁分为 无锁、偏向锁、轻量级锁、重量级锁 状态。
- 锁的级别从低到高依次为:无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级。
总结:锁的升级过程,如果只有一个线程获取到锁,这个锁就是偏向锁,因为对象头和栈帧中的锁记录里存储偏向的线程 ID,如果没有别的线程来竞争锁,那么直接执行代码;如果有别的线程在竞争锁,那么会释放偏向锁,线程会通过自旋的方式尝试获取锁,这个阶段的锁叫自旋锁(轻量级锁),如果经过多次自旋还是没有获取到锁,那么就会变成重量级锁。
12. 锁升级的具体过程
- 初始获取锁:当一个线程首次尝试获取某个对象上的锁时,如果此时锁处于无锁状态,JVM 会尝试将锁设置为偏向该线程。如果成功,则进入偏向锁状态。
- 第二个线程竞争锁:如果有另一个线程也想获取同一个锁,JVM 会检查当前是否为偏向锁。如果是,它会尝试撤销偏向锁,并将锁升级为轻量级锁。
- CAS 获取锁:在轻量级锁状态下,所有竞争线程都会尝试通过 CAS 操作来获取锁。如果成功,则线程获得锁并继续执行;如果不成功,线程可能会选择自旋一段时间后再试。
- 自旋失败后的升级:如果经过多次自旋后仍然无法获取锁,或者锁争用非常激烈,JVM 会决定将锁升级为重量级锁。此时,所有未能获取锁的线程都将被阻塞,直到锁变为可用。
八、Security
1. 概念:
Spring Security 是强大的,且容易定制的,基于Spring开发的实现认证登录与资源授权的应用安全框架。
2. Springsecurity 的核心功能:
。Authentication:身份认证,用户登陆的验证(解决你是谁的问题)
。Authorization:访问授权,授权系统资源的访问权限(解决你能干什么的问题)安全防护,防止跨站请求,session 攻击等
3. 盐值
盐值(随机盐):乱码器造的字符串。盐值越高,加密的层级越深。
4. Security的认证流程:
步骤:
- 客户端发送请求:用户通过客户端(如浏览器或移动应用)向服务器发送请求,请求中包含认证信息,通常是用户名和密码。
- 过滤器链的拦截:请求到达Spring Security的过滤器链,特别是UsernamePasswordAuthenticationFilter,它负责处理表单登录。
- 认证令牌的创建:UsernamePasswordAuthenticationFilter检查请求,如果发现包含认证信息,则创建一个Authentication对象,通常是UsernamePasswordAuthenticationToken。
- 认证管理器的调用:过滤器链中的ProviderManager(或AuthenticationManager)接收到Authentication对象,并将其传递给配置的认证提供者(AuthenticationProvider)。
- 用户详细信息的加载:认证提供者加载用户的详细信息,这通常涉及到查询数据库或其他存储系统,以验证用户名和密码。
- 密码的校验:如果配置了密码编码器(如BCryptPasswordEncoder),则使用它来校验用户输入的密码与存储的密码是否匹配。
- 认证成功或失败:如果用户名和密码验证成功,AuthenticationProvider会创建一个包含用户详细信息和权限信息的Authentication对象,并返回给ProviderManager。如果认证失败,抛出AuthenticationException异常,通常由AuthenticationEntryPoint处理,如重定向到登录页面或返回401错误。
- 授权决策:一旦用户认证成功,Spring Security会根据配置的访问控制列表(ACL)和权限注解等进行授权决策,确定用户是否有权访问请求的资源。
- 成功处理请求:如果用户被授权访问资源,请求会继续通过过滤器链,并最终被相应的控制器处理。
- 安全上下文的存储:认证成功后,SecurityContextHolder会存储Authentication对象,它在整个会话期间都可用,用于访问当前用户的安全信息。
- 退出认证:用户可以通过注销(logout)来结束会话,Spring Security会清理SecurityContextHolder中的Authentication对象,并执行任何配置的注销处理程序。
九、Redis
1. Redis的底层协议是什么?
Redis 使用的底层通信协议是 RESP (REdis Serialization Protocol),这是一种专门为 Redis 设计的简单、高效的序列化协议。RESP 是一种文本协议,但它也支持二进制安全的数据传输,因此可以处理任意类型的字符串数据,包括包含换行符或二进制内容的字符串。
2. 主从复制的原理
Redis主从复制是一种常见的数据备份方式,可以实现数据的冗余备份、读写分离等功能。Redis主从复制的原理是将一个Redis实例的数据复制到多个Redis实例上。其中,一个Redis实例作为主节点(Master),其他实例作为从节点(Slave)。主节点负责接收所有的写操作,并将写操作同步给从节点。而从节点只能接收读请求,不处理任何写请求从而保证了数据的一致性。
3. 从机的数量有要求吗?
从机(即从服务器)的数量并没有严格的硬性要求,但为了确保系统的高可用性和容错能力
- 至少要有2个从机
原因:这是最基础的配置,能够提供基本的数据冗余和故障转移能力。
防止脑裂问题
提供更多选择
提高容错能力
- 推荐3个或更多从机:更多的从机可以提高系统的容错能力和灵活性,尤其是在大规模应用中,能够更好地分担读负载并确保高可用性。
4. 哨兵机制的原理
哨兵机制是Redis高可用性的重要保障。当Redis主节点出现故障时,哨兵机制可以自动选举新的主节点,确保整个系统的可用性。哨兵机制主要由哨兵节点组成,哨兵节点会定期检查Redis实例的健康状态,并在主节点出现故障时,自动选举新的主节点。同时,哨兵节点还可以执行故障转移操作,将原本的从节点提升为新的主节点,并重新建立主从复制关系.
5. 如何保证Redis和MySQL数据库的一致性?
- 不是强一致性:
-
- 先删Redis在更新数据库,在还没有更新数据的时候,用户请求打进来,发现Redis是空的,去数据库拿的还是旧数据,会产生脏数据。
- 先更新数据库在删Redis,在数据库更新完,Redis还没有更新时访问,用户拿到的还是旧数据和脏数据。
- 先更新数据库在删Redis,过一会在删一便Redis,也会产生脏数据,只有第2次删除Redis才能保证数据一致。
- 强一致性要加锁,操作数据库时加锁,操作Redis的时候也加锁。保证只有一个用户在操作。
- 借助MQ,先更新Redis,其他用户访问时,拿到的都是Redis里的新数据,这时数据库还是旧数据,把要更新的数据给MQ,让消费者监听路由键,根据MQ里的信息把数据同步给数据库。
- 如果先删 Redis 中的数据,再更新数据库,用户可能会在 Redis 数据为空时访问数据库,从而获取旧数据,导致脏数据。
- 如果先更新数据库再删 Redis 中的数据,数据库更新完成后 Redis 仍未更新,用户仍然会获取旧数据,导致脏数据。
- 如果先删Redis(数据库)再更新数据库(Redis)中的数据,并且在一段时间后再次删除 Redis,只有第二次删除 Redis 才能保证数据一致性。
- 在操作数据库时加锁,并在操作 Redis 时也加锁,确保只有一个用户在操作,从而保证数据的一致性。
- 先更新 Redis,其他用户访问时,拿到的都是 Redis 中的新数据。此时数据库还是旧数据,将要更新的数据发送给 MQ。消费者监听路由键,根据 MQ 中的信息将数据同步给数据库。
Redis持久化
我之前公司是俩种都用了(开启了),RDB的持久化机制是以快照形式持久化的。AOF是以命令的文件进行存储的。
RDB是回复的快,AOF是回复的全。所以对于这两种机制我们是都开启的。
如何保证Redis和数据库一致?
运维成本和代码维护成本高。
1、现删Redis在保存数据库
2、先删数据库在更新Redis
3、先删Redis,过一会再删数据库
4、给Redis和数据库枷锁,如果没有修改成果,用户页面会一致加载
5、使用RebbitMQ解决
6. AOF的重写原理
重写是因为随着Redis服务器运行时间的增长,AOF文件会变得越来越大,因为每个写命令都会被追加到AOF文件中,为了减少AOF文件的大小并提高性能
当AOF文件的大小超过配置的阈值时Redis会自动启动AOF重写(配置文件里加相关配置)
当触发AOF文件重写时,Redis主进程会调用fork()系统调用创建子进程,这个子进程负责执行实际的AOF重写操作,而主进程继续处理客户端请求确保服务不中断
子进程在创建时会获得父进程的一个内存副本,会根据内存中的数据集生成新的AOF文件,而不是简单的将旧的AOF文件中的所有命令重新写入并且新的AOF文件只包含重建当前数据库集所需的最少命令集合,在子进程构建新的AOF文件期间,主进程仍然接收新的写命令,并同时追加到旧的AOF文件和临时缓冲区中,一旦子进程完成新AOF文件的构建,主进程就会将临时缓冲区中的所有命令追加到新的AOF文件中,再拿这个新的AOF文件替换掉旧的AOF文件,替换完成后就继续正常服务,新的写命令直接追加到新的AOF文件中
重写原理
注意:重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,替换原有的文件这点和快照有点类似。
重写流程
- 1. redis调用fork ,现在有父子两个进程 子进程根据内存中的数据库快照,往临时文件中写入重建数据库状态的命令
- 2. 父进程继续处理client请求,除了把写命令写入到原来的aof文件中。同时把收到的写命令缓存起来。这样就能保证如果子进程重写失败的话并不会出问题。
- 3. 当子进程把快照内容写入已命令方式写到临时文件中后,子进程发信号通知父进程。然后父进程把缓存的写命令也写入到临时文件。
- 4. 现在父进程可以使用临时文件替换老的aof文件,并重命名,后面收到的写命令也开始往新的aof文件中追加。
7. 什么是客观下限?什么是主观下限?
单台哨兵自己的想法
主观下线是单个哨兵节点的独立判断,适用于所有Redis实例
- 客观下线是多个哨兵节点达成一致的结果,仅适用于主服务器,并触发故障转移流程。
8. 哨兵的数量有要求吗?
哨兵机制最少要有3个节点,一个哨兵节点失效,剩下的两个哨兵节点仍然可以达成多数派。
- 1台哨兵主观太强,2台哨兵会打架。
- 推荐使用奇数个哨兵节点:奇数个哨兵节点可以避免投票平局,确保多数派的一致性。
- 根据集群规模调整哨兵数量:对于更大规模的集群或对高可用性有更高要求的场景,可以考虑增加哨兵节点的数量,通常是5个或7个。
- 合理分布哨兵节点:确保哨兵节点分布在不同的物理机器或数据中心,以提高系统的容错能力。
9. 触发同步
- 初次同步:当一个新的从节点(slave)加入并开始与主节点(master)同步时,会进行一次全量同步。这包括将主节点的数据快照(RDB文件)发送给从节点。
- 增量同步:一旦初始同步完成后,从节点会持续接收来自主节点的命令流,以保持数据的一致性。这是通过复制主节点接收到的写入命令到从节点上来实现的,这个过程是异步的,并且几乎实时发生,只要网络状况良好,延迟通常很低。
- 部分重同步(PSYNC):如果一个从节点暂时断开连接后重新连接上,它可能会尝试使用部分重同步机制。这意味着如果从节点和主节点之间的复制积压缓冲区(replication backlog)中仍然存在该从节点所需要的更新数据,那么就可以仅传输这部分数据而不是整个数据库。
- 全量同步:如果从节点长时间断开导致其所需的数据不在主节点的复制积压缓冲区内,则需要再次执行全量同步。
- 配置更改:在某些情况下,例如修改了主节点上的配置后,也可能触发新的同步。
触发同步流程:
当一个新的节点加入集群的时候就会向主节点发送请求,主节点会把当前数据拍摄成快照RDB文件,把这个文件传给新加入的节点,这个从节点就会读取这个文件实现数据同步,当主节点向从节点发送快照完成后,也会将发送期间用户产生的写操作记录到复制积压缓冲区中(AOF文件)并将这个文件也发送给从节点,当从节点使用镜像文件同步完后也会使用这个aof文件,这样就实现了。
10. 缓存击穿、缓存雪崩、缓存穿透的产生原因和解决方案
缓存击穿(Cache Breakdown)
产生原因
- 热点数据过期:当某个非常热门的缓存数据(即访问频率极高的数据)在缓存中过期时,大量请求几乎同时到达缓存层,导致这些请求直接打到数据库,造成数据库压力骤增。
- 缓存未命中:如果缓存中的数据被频繁删除或更新,可能会导致大量的缓存未命中,进而引发对后端数据库的高并发访问。
解决方案
- 加锁机制:可以在缓存失效时,使用互斥锁(如Redis的SETNX命令)来确保只有一个请求去加载数据并更新缓存,其他请求等待该操作完成后再从缓存中获取数据。这种方式可以避免多个请求同时查询数据库。
缓存穿透(Cache Penetration)
产生原因
- 恶意攻击或异常请求:某些请求可能故意或无意地查询不存在的数据(如ID为负数或不存在的用户),并且这些请求会绕过缓存直接打到数据库,导致数据库负载增加。
- 缓存未命中:如果缓存中确实没有存储某些数据(如从未存在过的用户信息),每次请求都会导致缓存未命中,并且这些请求会不断查询数据库,形成“穿透”现象。
解决方案
- 缓存空结果:对于查询不存在的数据,可以在缓存中存储一个空结果(如null或None),并设置较短的过期时间(如5秒)。这样,后续的相同请求可以直接从缓存中获取空结果,而不会再次查询数据库。
缓存雪崩(Cache Avalanche)
产生原因
- 大规模缓存失效:当缓存中的大量数据在同一时间点过期时,会导致短时间内大量请求直接打到数据库,造成数据库压力剧增,甚至导致系统崩溃。这种情况通常发生在缓存服务器重启、缓存策略不当或网络故障后缓存全部失效的情况下。
- 缓存依赖同一数据源:如果多个缓存项依赖于同一个数据源(如同一个SQL查询),并且该数据源发生变更,可能会导致多个缓存项同时失效,进而引发雪崩效应。
总结
- 缓存击穿:由于热点数据过期或缓存未命中导致大量请求直接打到数据库。
-
- 解决方案包括加锁机制、设置随机过期时间和预热缓存。
- 缓存穿透:由于恶意请求或查询不存在的数据导致缓存未命中,进而打到数据库。
-
- 解决方案包括缓存空结果、使用布隆过滤器和参数校验。
- 缓存雪崩:由于大量缓存同时失效导致数据库压力剧增。
-
- 解决方案包括设置随机过期时间、分级缓存、限流和熔断机制以及异步加载缓存。
11. Redis的过期策略和淘汰策略
一、过期策略:
定时删除:
设置了过期时间的 key 创建一个计时器,过期后将立即清除。
优点:
可以保证过期 key 会被尽快删除,也就是内存可以被尽快地释放,定时删除对内存是最友好的。
缺点:
在过期 key 比较多的情况下,删除过期 key 可能会占用相当一部分 CPU 时间,在内存不紧张但 CPU 时间紧张的情况下,将 CPU 时间用于删除和当前任务无关的过期键上,无疑会对服务器的响应时间和吞吐量造成影响。所以,定时删除策略对 CPU 不友好。
定期删除:
定期扫描特定数量数据库的过期字典中特定数据的Key(随机检查、集中检查),并清除过期 Key。
优点:
通过限制删除操作的持续时间和频率,可以减少删除操作对CPU的影响。同时,可以删除一些过期数据,以减少过期 Key 的无效空间占用。
缺点:
内存清理不如常规删除有效,并且在没有延迟删除的情况下使用更少的系统资源。很难确定执行删除操作的时间和频率。如果执行过于频繁,则常规删除策略与常规删除策略相同,这对CPU不友好;如果执行太少,则与延迟删除相同。过期 Key 占用的内存将无法及时释放。
惰性删除:
当访问 Key 时,将判断该 Key 是否已过期。如果过期,它将被清除。
优点:
由于每次访问 Key 时都会检查其是否过期,因此此策略仅使用少数系统资源。因此,延迟删除策略对CPU时间最为友好。
缺点:
如果某个 Key 已过期且该 Key 仍在数据库中,则只要该过期 Key 未被访问,该过期 Key 所占用的内存就不会被释放,从而导致一定的内存空间浪费。因此,延迟删除策略对内存不友好。
二、8种淘汰策略
Redis支持8种不同策略来选择要删除的key:
1、noeviction(no v k 深):不淘汰任何key,但是内存满时不允许写入新数据,默认就是这种策略。
2、volatile-tt(veng能太不):对设置了TTL的key,比较key的剩余TTL值,TTL越小越先被淘汰
3、alkeys-random(奥 k 死-软的):对全体key ,随机进行汰。
4.、volatile-random:对设置了TTL的key ,随机进行淘汰。
5、allkeys-ru(OK s):对全体key,基于LRU算法进行淘汰
6、volatile-lru:对设置了TTL的key,基于LRU算法进行淘汰
7、 allkeys-lfu:对全体key,基于LFU算法进行淘汰
8、volatile-lfu:对设置了TTL的key,基于LFU算法进行淘汰
12. 布隆过滤器
- 布隆过滤器(Bloom Filter):布隆过滤器是一种概率型数据结构,可以用来快速判断某个元素是否存在于集合中。
- 原理:对存入(插入)元素进行哈希,计算存入(插入)的数组位置,如果当前没有元素就存入并将位设置为1,在查存不存在时会同样会先计算hash并检查对应的位置是否为1,如果是1则认为该元素可能存在集合中,因为会有哈希碰撞的原因,存的位置一样但值不一样,如果有一个位置为0,则确定该元素不在集合中
LRU算法--基于最少使用,用当前时间减去最后一次访问时间,值越大则淘汰优先级越高
LFU算法--基于频率,会统计每个key的访问频率,越低则淘汰优先级高
13. 什么是深拷贝,什么是浅拷贝?深拷贝和浅拷贝对数据类型有要求吗?
浅拷贝:
对于基本数据类型,创建独立副本。
对于引用数据类型,只复制顶层对象,内部的可变对象仍然是共享的。
深拷贝:
对于基本数据类型,创建独立副本。
对于引用数据类型,递归地复制所有嵌套的对象,确保每个元素都是独立的。
十、RabbitMQ
1. MQ概述(用来转发消息)
MQ全称 Message Queue(消息队列),是在消息的传输过程中保存消息的容器。多用于分布式
系统之间进行通信。消息队列就是所谓的存放消息的队列。消息队列解决的不是存放消息的队列的目的,解决的是通信问题。
2. RabbitMQ消息投递路径
生产者-->交换机-->队列-->消费者
通过两个点的控制,保证消息的可靠性投递
- 生产者到交换机confirmGallback
- 交换机到队列
- returnCallback
- ACK队列到交换机
3. MQ的工作模式有几种?
简单模式:一个生产者一个消费者,有默认的交换机
工作队列:多个消费者可以消费同一个队列中的消息
发布订阅:一条消息被多个消费者消费,生产者发送的消息会被发送到所有订阅了该主题的消费者。
路由模式:使用路由交换机,根据消息属性将消息发送到特定的队列。
主题模式(通配符模式):通过一定的规则来选择性地接收消息,使用通配符来匹配路由键。
远程调用:发布者发布消息,并且通过RPC方式等待结果。
Header模式:通过消息的header属性进行匹配,而不是路由键。
4. MQ的应用场景
- 第一个是流量消峰
-
- 主要是针对大流量入口过大,而业务需求又要时间响但是服务器性能无法满足, 导致大量请求积压,从而使得客户蚓大量等待超时的场景·为了保证高可用,可以把大量的并行任务发送 给MQ,MQ冉将请求分发给其他服务器,从而平稳地处理后续的业务·起到一个大流量缓冲的作用· 举个例子,假设订单系统最大QPS是1万,这个处理能力应付正常时段在1秒内就能返回结果。但是在 流量高还期。比如促诮秒杀,如果QPS达到2万,订单系统就处理不过来了,只能在超过负载后不允许用 户下单·如果使用消息队列做缓冲。我们可以取消这个限制,把超出负载的订单分散成一段时间来处理, 这时有些户可能在下单十丿秒后才能&到下单成功的操作,但是比不能下单的体验要好
- 第二个是应用解耦
-
- 可以理解为,把一些相关的、但耦合度不高的系统关联起来,以电商应用为例,比如订单系统、库存系统、物流系统、支付系统有关联,但是有没有那么紧密。每个系统将一些约定好的消息发送到,另外的系统直接去消费这些消息就可以了,它可以解决不同系统之间使用不同的框架或者不同编程语言的兼容性问题,从而提高整个系统的灵活性,
5. MQ的3个消息确认机制
confirmCallback--生产者确认
ack --消息确认
returnsCallBack --保证消息成功的投到队列里
6. 如何保证MQ消息不丢失
队列持久化
消息持久化
开启生产者确认
开启消费者失败重试机制
7. 如何保证MQ幂等性的配置
1、唯一消息标识:
- 为每条消息生成一个全局唯一的ID。
- 在消费者处理消息之前,检查该消息ID是否已经被处理过。
2、持久化已处理消息的状态:
- 使用数据库或者其他存储系统记录已经成功处理的消息ID。
- 消费者接收到消息后,首先查询这个状态表,如果发现消息已经被处理,则直接忽略或进行相应的补偿操作。
3、去重机制:
- 在业务逻辑层面实现去重逻辑,确保相同的业务操作不会产生不同的结果。
- 对于一些支持原生去重特性的MQ产品,可以直接利用这些特性来保证幂等性。
4、事务性消息:
- 一些高级的消息队列提供事务性消息的功能,可以在消息发送和接收之间建立一个事务,保证消息传递的一致性和幂等性。
5、幂等接口设计:
- 设计业务接口时考虑幂等性,例如通过使用GET方法代替POST来进行查询操作,或者在命令式接口中加入版本号、时间戳等参数以识别重复请求。
6、补偿机制:
- 如果无法完全避免重复消息,那么需要设计补偿机制,在检测到重复消息时采取适当的措施来纠正可能产生的不一致情况。
7、消费确认机制:
- 确保只有当消息被成功处理之后才向MQ发送确认,避免因为网络抖动等原因导致的消息重复消费。
8、选择合适的MQ组件:
- 不同的MQ组件对幂等性的支持程度不同,选择那些内置了较好幂等性保障机制的产品可以帮助简化开发工作。
8. 死信队列
死信队列用于存储那些无法被正常消费的消息,比如因为消费者代码错误、外部系统不可用或其他暂时性问题而无法被处理的消息。这些消息会被发送到死信队列,以便后续进行重试或人工干预
9. 消息成为死信的条件
消息被拒签,并且没有重回队列,消息将成为死信,regueue=false
消息过期了,消息将成为死信。
队列长度有限,存不下消息了,存不下的消息将会成为死信
10. 死信消息如何被再次消费?
1、放在死信队列
2、发送给原队列
3、发送给新队列
11. 如何用MQ实现定时任务?
1. 延迟队列
一些MQ提供了内置的延迟功能,允许消息在特定时间后才被消费。例如,RabbitMQ 可以通过插件 `rabbitmq_delayed_message_exchange` 实现这一功能;Kafka 可以利用其自身的机制或者第三方库来模拟延迟消息。
优点:简单易用,直接利用MQ特性。
缺点:并非所有MQ都原生支持延迟消息,可能需要额外配置或依赖社区支持。
2. 定时器与调度服务
可以创建一个独立的服务,该服务负责调度和发送消息到MQ。这个服务可以根据预定义的时间表或者事件触发条件将消息推送到MQ中。这种方案通常结合像 Quartz、Apache Airflow 或者 Kubernetes CronJob 这样的调度工具来实现。
优点:灵活性高,易于扩展复杂逻辑。
缺点:增加了系统的复杂度,引入了额外的组件。
3. TTL + 死信队列(DLQ)
某些MQ(如 RabbitMQ 和 Kafka)支持为消息设置生存时间(TTL),当消息达到其TTL时会被自动移动到死信交换机(Dead Letter Exchange, DLX),然后从那里转发到指定的队列进行处理。这种方式可以用来实现定时任务。
步骤:
创建一个正常的消息队列,并为其关联一个DLX。
当需要执行定时任务时,向此队列为消息设置适当的TTL值。
消息过期后会进入DLX,并最终到达另一个队列,在那里由消费者处理。
优点:利用MQ本身的特性,不需要额外的服务。
缺点:对于非常精确的定时任务可能会有一定的误差,因为消息过期后的处理时间取决于MQ的工作负载。
4. 使用 Redis 的 Sorted Set 或者其他键值存储
虽然这不是传统意义上的MQ,但Redis的Sorted Set数据结构非常适合用来实现简单的定时任务。你可以将任务的时间戳作为score,任务ID作为member插入到Sorted Set中。然后有一个后台进程定期检查Sorted Set中的最小score是否小于当前时间,如果是,则取出该任务并将其放入MQ中等待处理。
优点:实现简单,适合小型项目。
缺点:不适用于大规模分布式系统,且需要额外维护Redis实例。
12. 一条消息到底是怎么从生产者到了消费者的?
- 首先生产者通过连接的方式连接到MQ的一个虚拟机,需要知道MQ的ip,端口,虚拟机路径,用户名和密码,准备好了以后就可以建立连接了TCP 连接Connection连接,
- 但是建立和关闭TCP连接是有代价的,频繁的建立关闭TCP连接对于系统的性能有很大的影响,而目TCP的连接数也有限制,这也限制了系统处理高并发的能力。但是,在TCP连接中建立Channel是没有上述代价的,所以我们使用信道changel的方式发送和接受消息。
- 消息进入MQ的第一站是Exchange交换机,交换机的作用:① 接收生产者发送的消息 ②和队列绑定。交换机是不保存信息的。自生产者发消息的时候可以指定一个路由键,路由键可以理解为就是给消息贴了一个标签(做标记作用,消费者接收消息的时候有用)
- 消息进入第二站queue,消费者要接收消息,需要一直监听着queue,那么消费者在监听queue的时候需要先指定队列要和那个交换机绑定,绑定的时候也需要指定路由键,如果发消息时的路由键和接收消息时候路由键一样,那么这个消息就会进入到这个队列。
- 最后消费者就拿到消息了。需要说明的一点,所有的交换机和队列创建的时候都是需要起名字的。
13. 为什么是B+树,而不是B树或者红红黑树?
- 磁盘读写代价更低:B+树的内部节点并不直接存储数据,只存储索引键和子节点指针,因此相对于B树,B+树的内部节点可以更小,能够将更多的关键字放入同一个磁盘块中,减少了磁盘I/O操作的次数。
- 查询效率更稳定:B+树的所有查询都会走到叶子节点,因此查询路径长度相同,查询效率稳定。而B树的查询可能在非叶子节点结束,导致查询效率不稳定。
- 适合范围查询:B+树的叶子节点通过指针相互连接,形成了一个有序链表,非常适合进行范围查询和顺序访问操作。而B树的叶子节点没有相互连接,进行范围查询时效率不如B+树。
- 树的高度更低:B+树由于非叶子节点存储的索引键更多,因此树的高度更小,查询时所需的磁盘I/O操作次数更少,查询效率更高。
- 全节点遍历更快:B+树只需要遍历叶子节点就可以实现整棵树的遍历,而B树需要对每一层进行遍历,这在数据库中进行全表扫描时更为高效。
- 红黑树的局限性:红黑树作为一种自平衡的二叉查找树,虽然在内存中的数据结构实现中表现良好,但在处理大量数据时,由于树的高度较高,导致磁盘I/O操作频繁,效率不如B树和B+树。
十一、MySQL、SQL优化
1. 什么是SQL注入?
SQL注入就是指web应用程序对用户输入数据的合理性没有进行判断,前端传入后端的参数是攻击者可控制的,并且根据参数带入数据库查询,攻击者可以通过构造不同的SQL语句来对数据库进行任意查询。
2. 数据库内连接和外连接:
返回俩个表中满足条件的数据。
select 列名 from 表1 inner join 表2 on 表1.列名 = 表2.列名
以左表为主,返回左表所有数据和右表符合条件的数据
select 列名 from 表1 left join 表2 on 表1.列名 = 表2.列名
以右表为主,返回右表所有数据和左表符合条件的数据
select 列名 from 表1 rightjoin 表2 on 表1.列名 = 表2.列名
3. MySQL索引的底层数据结构是什么?
B+树
4. MySQL的常见的存储引擎有哪几种?特点是什么?使用什么场景?
- InnoDB
主要是用来
特性:
支持事务(ACID兼容)、行级锁定、外键约束(不推荐使用)。
提供崩溃恢复功能。
使用 B+ 树索引结构。
支持多版本并发控制(MVCC),有助于提高读写并发性能。
包含重做日志(Redo Log)和撤销日志(Undo Log)以确保数据完整性和一致性。
适用3:适合于需要高可靠性的应用,尤其是那些涉及大量插入、更新操作以及复杂查询的应用程序。它是 MySQL 的默认存储引擎,广泛用于 OLTP(在线事务处理)系统。
- MyISAM
特性:
不支持事务。
表锁而非行锁,因此在高并发写入时性能可能较差。
更快的读取速度,在只读或读多写少的情况下表现良好。
支持全文索引,这对搜索引擎等应用很有用。
适用场景:适用于以读为主的应用程序,如内容管理系统(CMS)、博客平台等,尤其是在对数据一致性和事务支持要求不高的情况下。
特点:
- 应用范围比较小。表级锁限制了读/写的性能
- 它通常用于只读或以读为主的工作。
- 支持表级别的锁(插入和更新会锁表)。不支持事务。
- 拥有较高的插入(insert)和查询(select)速度。
- 存储了表的行数(count 速度更快)。
- 当我们指定了主键的时候,插入顺序和查询的顺序是一致的。
如果有 100W 的数据要插入到数据库该怎么办?
我们可以将表的储存引擎先改成 MyISAM,将数据插入到表中,然后再讲储存引擎修改回来。
5. InnoDB
mysql 5.7 中的默认存储引擎。!nnoD8 是一个事务安全(与 ACID 兼容)的,使得 MySQL存储引擎,它具有提交、回滚和崩溃恢复功能来保护用户数据。!nnoD8 行级锁(不升级为更粗粒度的锁)和 Oracle 风格的一致非锁读提高了多用户并发性和性能。
特点:
- 支持事务,支持外键,因此数据的完整性、一致性更高。
- 支持行级别的锁和表级别的锁。
- 支持读写并发,写不阻塞读(MVCC)。
- 特殊的索引存放方式,可以减少10,提升查询效率:
适合:经常更新的表,存在并发读写或者有事务处理的业务系统。
create table t1 (
a int PRIMARY key,
b int,
c int,
d int,
e VARCHAR(20)
)ENGINE=INNODB;
INSERT into t1 VALUES(4,3,1,1,'123');
INSERT into t1 VALUES(5,3,1,1,'222');
INSERT into t1 VALUES(3,3,1,1,'134');
INSERT into t1 VALUES(1,3,1,1,'654');
INSERT into t1 VALUES(2,3,1,1,'121');
6. MyISAM
在MySQL 5.5 版本之前, 默认的存储引擎是 MyISAM,它是 MySQL 自带的。我们创建表的时候不指定存储引擎, 它就会使用 MyISAM 作为存储引擎
特点:
- 应用范围比较小。表级锁限制了读/写的性能
- 它通常用于只读或以读为主的工作。
- 支持表级别的锁(插入和更新会锁表)。不支持事务。
- 拥有较高的插入(insert)和查询(select)速度。
- 存储了表的行数(count 速度更快)。
- 当我们指定了主键的时候,插入顺序和查询的顺序是一致的。
如果有100W的数据要插入到数据库该怎么办?
我们可以将表的储存引擎先改成MyISAM,将数据插入到表中,然后再讲储存引擎修改回来。
create table t2 (
a int PRIMARY key,
b int,
c int,
d int,
e VARCHAR(20)
)ENGINE=MyISAM;
INSERT into t2 VALUES(4,3,1,1,'123');
INSERT into t2 VALUES(5,3,1,1,'222');
INSERT into t2 VALUES(3,3,1,1,'134');
INSERT into t2 VALUES(1,3,1,1,'654');
INSERT into t2 VALUES(2,3,1,1,'121');
表级锁:操作数据库的时候,将整个表都锁住,不能操作表中的任何数据。
行级锁:操作数据库的时候,将表中的一行数据锁住,不能操作被锁定的这行数据。
7. innodb 和 myisam 的区別
- 事务支持
- ImnoDB:支持事务处理,提供了ACID属性(原子性、一致性、隔离性、持久性),能够保证数据的一致性和完整性。
- MYISAM:不支持事务处理。
- 行级锁
- InnoDB:支持行级锁定,这意味着在并发操作中可以锁定特定的行而不影响其他行的操作,从而提高了并发处理能力。
- MVISAM:使用表级锁定,在执行写操作时会锁定整个表,这可能会影响其他操作的执行速度。
- 外键支持
- InnoDB:支持外键约束,可以维护表之间的关系,有助于保证数据的完整性和一致性。
- MyISAM:不支持外键约束。
- 性能
- ImnoDB:由于支持行级锁定,因此在高并发环境中性能更优。
- MyISAM:在读取密集的应用场景中性能较好,因为它不支持行级锁定,所以对读操作更加友好。
- 使用场景
- ImnoDB:适合需要事务处理的应用程序,如电子商务网站、金融交易系统。
- MyISAM:适合简单的 Web 应用程序,尤其是那些以读取为主的应用程序。
8. 什么是SQL攻击
SQL注入即是指web应用程序对用户输入数据的合法性没有判断或过滤不严,攻击者可以在web应用程序中事先定义好的查询语句的结尾上添加额外的SQL语句,在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询
例如:
在需要用户输入的地方,用户输入的是SQL语句的片段,最终用户输入的SQL片段与我们DAO中写的SQL语句合成一个完整的SQL语句!例如用户在登录时输入的用户名和密码都是为SQL语句的片段!
9. 完整性约束条件表
完整性约束条件表 | |
PRIMARY KEY | 标识该属性为该表的主键,可以唯一的标识对应的元组 非空 唯一 |
FOREIGN KEY | 标识该属性为该表的外键,是与之联系的某表的主键 |
NOT NULL | 标识该属性不能为空 |
UNIQUE | 标识该属性的值是唯一的 |
AUTO INCREMENT | 标识该属性的值自动增加,这是MySQL的SQL语句的特色 |
DEFAULT | 为该属性设置默认值 |
10. 索引有哪些类型?
- 主键索引:索引列的值必须是唯一的,不允许有空值。
- 普通(Normal):也叫非唯一索引,是最普通的索引,没有任何的限制。
- 唯一(Unique):索引列中的值必须是唯一的,但是允许为空值。
- 全文(Fulltext):针对比较大的数据,比如我们存放的是消息内容,有几KB的数据的这种情况,如果要解决 like 查询效率低的问题,可以创建全文索引。只有文本类型的字段才可以创建全文索引,比如 char、varchar、text。
- 空间索引:MySQL在5.7之后的版本支持了空间索引,而且支持OpenGIS几何数据模型。MySQL在空间索引这方面遵循OpenGIS几何数据模型规则。(存坐标)
- 前缀索引:在文本类型如char、varchar、text类列上创建索引时,可以指定索引列的长度,但是数值类型不能指定。
- 其他(按照索引列数量分类)
-
- 单列索引
- 组合索引
组合索引的使用,需要遵循最左前缀匹配原则(最左匹配原则)。一般情况下在条件允许的情况下使用组合索引替代多个单列索引使用。
11. 你经常用哪些索引?
- B树索引:最常见的索引类型,适用于大多数数据库系统
- 哈希索引:主要用于等值匹配的快速查找
- 聚簇索引:决定了数据在磁盘上的物理存储顺序
- 辅助索引:除主键外的其它字段建立的索引称为二级索引。
- 联合索引:联合索引是由多个字段组成的索引。
- 覆盖索引:在辅助索引里面,不管是单列索引还是联合索引,如果 select 的数据列只用从索引中就能够取得,不必从数据区中读取,这时候使用的索引就叫做覆盖索引,这样就避免了回表。
12. 索引应该建在哪些字段上?
- 唯一字段:是指每个值都是唯一的字段,通常是数据库表的主键或者唯一约束字段。由于唯一字段的值不会重复,建立索引可以加速通过唯一值进行查找的操作。
- 不为空字段:是指在数据库表中不能为null的字段。这些字段通常会经常被查询,并且由于其不为空的特性,索引可以帮助快速定位到具有特定数值的记录,提高查询效率。
- 经常被查询的字段:是指在查询操作中频繁使用的字段,例如经常用于过滤、排序或者连接操作的字段。通过为这些字段建立索引,可以加速相关的查询操作,提高数据库的性能。
13. 建索引的原则是什么?
- 选择唯一性索引
唯一性索引的值是唯一的,可以更快速的通过该索引来确定某条记录。例如,学生表中学号是具有唯一性的字段。为该字段建立唯一性索引可以很快的确定某个学生的信息。如果使用姓名的话,可能存在同名现象,从而降低查询速度。
- 为经常需要排序、分组和联合操作的字段建立索引
经常需要ORDER BY、GROUP BY、DISTINCT和UNION等操作的字段,排序操作会浪费很多时间。如果为其建立索引,可以有效地避免排序操作。
- 为常作为查询条件的字段建立索引
如果某个字段经常用来做查询条件,那么该字段的查询速度会影响整个表的查询速度。因此,为这样的字段建立索引,可以提高整个表的查询速度。
- 限制索引的数目
索引的数目不是越多越好。每个索引都需要占用磁盘空间,索引越多,需要的磁盘空间就越大。修改表时,对索引的重构和更新很麻烦。越多的索引,会使更新表变得很浪费时间。
- 尽量使用数据量少的索引
如果索引的值很长,那么查询的速度会受到影响。例如,对一个CHAR(100)类型的字段进行全文检索需要的时间肯定要比对CHAR(10)类型的字段需要的时间要多。
- 尽量使用前缀来索引
如果索引字段的值很长,最好使用值的前缀来索引。例如,TEXT和BLOG类型的字段,进行全文检索会很浪费时间。如果只检索字段的前面的若干个字符,这样可以提高检索速度。
- 删除不再使用或者很少使用的索引
表中的数据被大量更新,或者数据的使用方式被改变后,原有的一些索引可能不再需要。数据库管理员应当定期找出这些索引,将它们删除,从而减少索引对更新操作的影响。
- 最左前缀匹配原则,非常重要的原则。
MySQL会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,比如a 1=”” and=”” b=”2” c=”“> 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。
- =和in可以乱序。
比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序,mysql的查询优化器会帮你优化成索引可以识别的形式
- 尽量选择区分度高的列作为索引。
区分度的公式是count(distinct col)/count(*),表示字段不重复的比例,比例越大我们扫描的记录数越少,唯一键的区分度是1,而一些状态、性别字段可能在大数据面前区分度就 是0,那可能有人会问,这个比例有什么经验值吗?使用场景不同,这个值也很难确定,一般需要join的字段我们都要求是0.1以上,即平均1条扫描10条 记录
- 索引列不能参与计算,保持列“干净”。
比如from_unixtime(create_time) = ’2014-05-29’就不能使用到索引,原因很简单,b+树中存的都是数据表中的字段值,但进行检索时,需要把所有元素都应用函数才能比较,显然成本 太大。所以语句应该写成create_time = unix_timestamp(’2014-05-29’);
- 尽量的扩展索引,不要新建索引。
比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可
- 当单个索引字段查询数据很多,区分度都不是很大时,则需要考虑建立联合索引来提高查询效率
注意:选择索引的最终目的是为了使查询的速度变快。上面给出的原则是最基本的准则,但不能拘泥于上面的准则。读者要在以后的学习和工作中进行不断的实践。根据应用的实际情况进行分析和判断,选择最合适的索引方式。
14. 什么情况下会造成索引失效?
- 尽量避免在字段开头模糊查询,会导致数据库引擎放弃索引进行全表扫描
select * from user where name like '%用%'
-- 优化方式:尽量再字段后面使用模糊查询
select * from user where name like '用%'
如果需求是要在前面使用模糊查询,使用MySQL内置函数INSTR(str,substr) 来匹配,作用类似于java中的indexOf()
- 尽量避免使用in 和not in,会导致引擎走全表扫描
SELECT * FROM user WHERE id IN (2,3)
-- 优化方式:如果是连续数值,可以用between代替
SELECT * FROM user WHERE id BETWEEN 2 AND 3
- 尽量避免使用 or,会导致数据库引擎放弃索引进行全表扫描
select * from user WHERE id = 1 or id = 3
-- 优化方式:可以用union(且)代替or
select * from user WHERE id = 1
UNION
select * from user WHERE id = 3
- 尽量避免进行null值的判断,会导致数据库引擎放弃索引进行全表扫描
select * from user WHERE age is null
-- 优化方式:可以给字段添加默认值0,对0值进行判断
select * from user WHERE age = 0
- 尽量避免在where条件中等号的左侧进行表达式、函数操作,会导致数据库引擎放弃索引进行全表扫描
可以将表达式、函数操作移动到等号右侧 ( 不建议这样写会造成运行效率降低 )
-- 全表扫描
SELECT * FROM user WHERE age/10 = 9
-- 走索引
SELECT * FROM user WHERE age = 10*9
当数据量大时,避免使用where 1=1的条件。通常为了方便拼装查询条件,我们会默认使用该条件,数据库引擎会放弃索引进行全表扫描
SELECT name, age, pwd FROM user and 1=1
-- 优化方式:用代码拼装sql时进行判断,没 where 条件就去掉 where,有where条件就加 and
SELECT name, age, pwd FROM user WHERE 1 and 1
查询条件不能用 < > 或者 !=
使用索引列作为条件进行查询时,需要避免使用<>或者!=等判断条件。要去java进行判断,不能对数据库进行操作
- where条件仅包含复合索引非前置列
如下:复合(联合)索引包含a1,b2,c3三列,但SQL语句没有包含索引前置列"a1",按照MySQL联合索引的最左匹配原则,不会走联合索引
-- 创建联合索引
create index user_index on user (name,age,pwd)
-- 这种方式是不会走索引的
select name,age,pwd from user where age=123 and pwd='312'
- 隐式类型转换造成不使用索引
如下SQL语句由于索引对列类型为varchar,但给定的值为数值,涉及隐式类型转换,造成不能正确走索引。
select name from table where name = 123;
15. 如果一张表没有主键怎么办
每张表里面肯定会有一个聚簇索引(主键),如果自己没定义,会从表里面找到符合的字段如果表中没有符合的字段,那么数据库会自己(不包含nul,而且还是唯一的)做主键,给我们生成一个隐式的主键
16. 回表
当数据库引擎通过索引查询数据时,如果索引不包含查询所需的所有列,那么即使找到匹配的索引条,数据库仍需根据主键索引回到实际数据行去读取不在索引内的额外信息。这种从索引到数据库表的实际记录之间的额外访问就叫回表。
产生原因:
覆盖索引不足:如果一个索引不能完全覆盖查询所需的字段,就仍会访问实际数据读取缺失的字段信息
索引使用不当:数据库优化器可能会选择一个不适合当前查询的索引,特别是当存在多个可用索引时
查询语句编写不合理:如果sql语句没有正确利用现有的索引或者包含了不必要的字段(select *),也会导致回表
频繁更新的数据:对于经常更新的数据,维护索引的成本较高,因此数据库可能不会为这些数据创建高效的索引,从而导致回表
解决方式:
可以通过创建覆盖索引的方式,设计索引以包含查询所需的所有字段,这样数据库可以直接从索引中获取所有数据,而不需要回表。在数据库层面,可以通过调整配置参数来优化查询性能,减少回表。
17. 什么是最左匹配原则?
指的是使用联合索引时,必须从联合索引的最左边开始匹配,这样才能有效地利用索引来加速查询。
18. MySQL 服务允许的最大连接数是多少呢?
- show variables like 'max connections'.
- 默认 151,最大2的 14 次方 16384
- 临时修改set global max connections=1000;
- 永久生效需要修改/etchmy.cnf文件
19. 优化器
一条 SOL 语句是可以有很多种执行方式的,最终返回相同的结果,他们是等价的。但是如果有这么多种执行方式,这些执行方式怎么得到的?最终选择哪一种去执行?根据什么判标准去选择?查询优化器的目的就是根据解析树生成不同的执行计划,然后选择一种最优的执行计划,MySQL 里面使用的是基于开销(cost)的优化器,那种执行计划开销最小,就用哪种。
- 当我们对多张表进行关联查询的时候,以哪个表的数据作为基准表。
- 有多个索引可以使用的时候,选择哪个索引。
- 但是优化器也不是万能的,并不是再垃圾的 SQL 语句都能自动优化?也不是每次都能选择到最优的执行计划。
- 优化器最后会得到一个执行结果(执行计划)。执行计划,也是一种数据结构。
- 条条大道通罗马,优化器就是指路人。
20. 存储引擎
索引:帮助 mysgl高效获取数据的排好序的数据结构。
分为:B+树和HASH
关系型数据库的数据是存在表里面的,表在存储数据的同时还会组织数据的存储结构,这个结构就是我们储存引擎决定的,所以我们也可以把存储引擎叫表类型。在 MySQL 里面,支持多种存储引擎,他们是可以替换的,所以叫做插件式的存储引擎。showengimes;##查看支持的引擎
不同的存储引擎会决定一张表在磁盘中的类型,任何一个存储引擎都有一个frm 文件,这个是表结构定义文件。
十二、JVM
1. JVM参数
- Xms:Java堆内存的大小
- Xmx:java堆内存的最大大小
- Xmn:lava堆内存中的新生代大小,扣除新生代剩下的就是老年代的内存大小了
- XX:Permsize:永久代大小
- XX:MaxPermsize:永久代最大大小
- Xss:每个线程的栈内存大小
2. JVM中的元空间和永久代有什么区别?
- 存储位置不同:永久代是物理上堆的一部分,与新生代、老年代的地址是连续的,而元空间属于本地内存。
- 存储内容不同:在原来的永久代中,存放类的元数据信息、静态变量以及常量池等。现在,类的元信息存储在元空间中,静态变量和常量池等并入堆中,相当于原来的永久代中的数据,被元空间和堆内存给瓜分了
- 内存限制:永久代有一个固定的大小上限,而元空间使用的是本地内存,理论上其大小只受限于操作系统的实际可用内存,这大大减少了内存溢出的可能性
3. JVM原空间替代了永久代
- 存储位置不同:永久代是物理上堆的一部分,与新生代、老年代的地址是连续的,而元空间属于本地内存。
- 存储内容不同:在原来的永久代中,存放类的元数据信息、静态变量以及常量池等。现在,类的元信息存储在元空间中,静态变量和常量池等并入堆中,相当于原来的永久代中的数据,被元空间和堆内存给瓜分了
- 内存限制:永久代有一个固定的大小上限,而元空间使用的是本地内存,理论上其大小只受限于操作系统的实际可用内存,这大大减少了内存溢出的可能性
4. 什么是双亲委派(类加载器)?
当一个类收到了类加载请求,会先判断自己有没有加载过,没有则委派给父类然后执行同样判断,因此所有加载请求都传送到了启动类加载器中,启动类加载器判断为没加载过的话就会尝试加载,能加载就返回,不能加载委派到子类判断能不能加载。
优点:
确保类的一致性
避免重复加载
加载层次清晰、分工明确。
类加载器
- 启动类加载器 Bootstrap classLoader:加载]ava安装文件jre\1ib包下的文件 (老大)(官方的)
- 扩展类加载器 Extension classLoader:加载]ava安装文件jre\lib\ext包下的文件 (老二)
- 应用类加载器 Application classLoader:加载classpath下的文件,也就是自己写的文件(老三)
- 自定义类加载器 user classLoader : 自己编写的加载器,指定了加载文件的路径和获取数据的方式(老幺)
5. JVM内存划分,并且知道每个内存区域的作用。
元空间:存放类的元数据信息
堆:存放对象实例,静态变量、常量池。也叫GC堆
java虚拟机栈:方法执行所在的地方,单位是栈帧
栈帧包含局部变量表、操作数栈、动态链接、返回出口等信息
局部变量表:存着方法参数和内部定义的变量
操作数栈:用于计算时临时存数据的区域
程序计数器:记录当前线程执行的地址值,方便下次执行
6. java堆的划分
新生代:用来存放新创建的对象的区域
对象的生命周期比较短,垃圾回收比较频繁
老年代:用来存放长期存活的对象的区域
生命周期比较长,垃圾回收率比较低
年轻代能变成老年代,老年代变不成年轻代
年轻代能变成老年代条件:躲过15次GC就能变成老年代
7. 为什么要分成年轻代(新生代)和老年代?
因为这跟垃圾回收有关,对于年轻代里的对象,他们的特点是创建之后很快就会被回收,需要用一种垃圾回收算法,对于老年代里的对象,他们的特点是需要长期存在,需要另外一种垃圾回收算法,所以需要分成两个区域来放不同的对象。
ivm分代是因为不同垃圾回收算法的需要
8. 垃圾,怎么判断垃圾(可达性分析算法)
不再被引用的对象实例就是jvm中的垃圾
判断垃圾的方法:
- 引用计数法:判断对象的引用决定是否回收,创建+1,销毁-1.为0就是垃圾就清除
- 可达性分析算法:每个对象都分析有谁在引用它,然后一层一层向上判断,看是否有一个GCROOT(对象可达根)
-
- 标记清除
- 复制算法
- 标记整理算法
- 分代收集算法:根据对象生命周期的不同划分不同的区域采用不同的回收算法
新生代--minor GC
minor GC --分为一个Eden区和两个survivor区
触发条件:当Eden区满时就会将里面存的对象转移到一个survivor区域中,Eden清空再次分配新对象到Eden区,下一次满时就会将里面的对象和放着上一次GC存活对象的survivor区里的存活对象转移到另一块survivor区
老年代--full GC (清空内存)
9. 新生代垃圾回收
触发新生代垃圾回收的条件时,新生代内存满时就会触发,将没人指向的对象实例清除掉--minorGC
新生代分为:
- Eden区:新创建的对象首先会被分到Eden区。当 Eden 区满时,JVM 会触发一次 Minor GC(也称为 Young GC),回收 Eden 区中的不再使用的对象。
- Survivor 区:Survivor 区有两个,通常称为 S0 和 S1。每次 Minor GC 后,仍然存活的对象会被移动到 Survivor 区。Survivor 区的作用是减少对象从新生代直接晋升到老年代的数量,给对象更多的存活机会。两个 Survivor 区交替使用,一个用于存储存活对象,另一个为空闲状态。
-
- S0(From):当前用于存储存活对象的 Survivor 区。
- S1(To):空闲的 Survivor 区,用于下一次 Minor GC 时存储存活对象。
10. 垃圾回收的算法
- 标记清除:可达性分析,将属于垃圾的对象标记然后删除,缺点是碎片化
- 复制算法:分为对象面和空闲面两块区域,将存活的对象复制到空闲面,将对象面内容清除。解决了碎片化的问题,浪费内存空间。
- 标记整理算法:可达性分析,将属于垃圾的对象标记,将存活对象根据内存地址排序,把最后一个对象之后的内容全部回收
11. 对象进入老年代要求
- 新生代躲过15次GC就会变为老年代
- 动态对象年龄判断:一批对象的内存总大小超过survivor内存区域的百分之五十,那么此时大于等于这批对象年龄(这批对象的最小年龄)的对象就可直接进入
- 特别大的对象会直接不经过新生代就进入老年代
12. 全局变量、局部变量、方法、类的生命周期
全局变量:程序启动时初始化,持续存到程序结束
局部变量:在被声明的那一行代码被执行时,并且持续到包含它的代码块执行完毕为止。
方法:方法被调用时初始化,返回结果后销毁
类:通过new关键字或其他方式创建对象的时候,持续到没有引用指向该对象并且它被垃圾回收器回收为止。
13. 如何解决循环依赖?什么情况下的循环依赖不能解决?
循环依赖是指两个或多个模块相互之间直接或间接地依赖对方。这种依赖关系可能导致初始化失败、内存泄漏或其他难以调试的问题
解决循环依赖的方法:
- 重构代码以消除依赖:重新设计类的结构来移除不必要的依赖
- 引入中间层:创建一个中介者对象来处理原本由两个相互依赖的对象负责的任务
- 延迟加载:使用懒加载技术推迟对象实例化的时间点,直到真正需要时才创建对象
- 依赖注入:
- 使用代理:使用动态代理来代替直接引用另一个对象
- 拆分大类成小类:
- 调整包结构和导入路径:
- 工厂方法模式:
不能解决循环依赖的情况:
- 业务逻辑固有的循环依赖
- 框架限制:特定框架或库的设计可能会强制产生循环依赖
- 性能考量:有时候为了优化性能,就会选择接受一定程度的循环依赖,因为重构会带来额外的开销
- 遗留系统:对于已经存在很长时间的老系统,由于历史原因和技术债务累积,重构以消除循环依赖可能是非常困难甚至是不可行的。
十三、Activiti7
1. Activiti7介绍
Activiti是目前使用最为广泛的开源工作流引擎,2010年5月就正是启动了。在了解Activiti之前,我们首先要了解下什么是工作流。
2. Activiti数据表
Activiti 使用到的数据表都是 ACT_ 开头的,表名的第二部分表示用途。
ACT_GE_ (GE) 表示 general 全局通用数据及设置,各种情况都使用的数据。
ACT_HI_ (HI) 表示 history 历史数据表,包含着程执行的历史相关数据。
ACT_RE_ (RE) 表示 repository 存储,包含的是静态信息。
ACT_RU_ (RU) 表示 runtime 运行时,运行时的流程变量,用户任务,变量,职责(job)等运行时数据。Activiti 只存储实例执行期间的运行时数据,当流程实例结束时,将删除这些记录
3. Activiti使用步骤
通常使用Activiti时包含以下几个步骤:
部署activiti:Activiti包含一堆Jar包,因此需要把业务系统和Activiti的环境集成在一起进行部署。
定义流程:使用Activiti的建模工具定义业务流程.bpmn文件。
部署流程定义:使用Activiti提供的API把流程定义内容存储起来,在Acitivti执行过程汇总可以査询定义的内容。Activiti是通过数据库来存储业务流程的。
启动流程实例:流程实例也叫Processinstance。启动一个流程实例表示开始一次业务流程的运作。例如员工提交请假中请后,就可以开启一个流程实例,从而推动后续的审批等操作。
用户查询待办任务(task):因为现在系统的业务流程都交给了activiti管理,通过activiti就可以查询当前流程执行到哪个步骤了。当前用户需要办理哪些任务也就同样可以由activiti帮我们管理,开发人员不需要自己编写sql语句进行査询了。
用户办理任务:用户查询到自己的待办任务后,就可以办理某个业务,如果这个业务办理完成还需要其他用户办理,就可以由activiti帮我们把工作流程往后面的步骤推动。
流程结束:当任务办理完成没有下一个任务节点后,这个流程实例就执行完成了
了解这些后,我们来开始进入实战内容。
4. 核心四个Service:
- getRuntimeService():表示运行时的管理类
- getTaskService():管理任务的
- getHistoryService():
- getRepositoryService():
用的不多,主要是运维使用:
- getManagementService():对Activiti的流程引擎进行管理和维护
5. 工作流WorkFlow
关于什么是工作流,有一个官方的定义: 工作流是指一类能够完全自动执行的经营过程,根据一系列现成规则,将文档、信息或任务在不同的执行者之间进行传递和执行。其实说直白一点,就是业务上一个完整的审批流程。例如员工的入职、请假、出差、采购等等、还有一些关键业务如订单申请、合同审核等等,这些过程,都是一个工作流。
对于工作流,传统的处理方式往往需要有人拿着各类的文件,在多个执行部门之间不断的审批。而当我们开始用软件来协助处理这一类审批流程时,就开始出现了工作流系统。工作流系统可以减少大量的线下沟通成本,提高工作效率
有了工作流系统之后,才开始出现工作流引擎。在没有专门的工作流引擎之前,我们为了实现这样的流程控制,通常的做法都是采用状态字段的方式来跟踪流程的变化情况。例如对一个员工请假请求,我们会定义已申请、组长已审核、部门经理已审核等等这样一些状态,然后通过这些状态来控制不同的业务行为,比如部门经理角色只能看到组长已审核通过的请假天当超过3天的订单年
6. 建模语言BPMN
谈到BPMN,首先就要谈BPM。 BPM即Business Process Managemenet,业务流程管理。是一种规范化的构造端到端的业务流程,以持续的提高组织业务效率。在常见的商业管理教育如EMBA、MBA中都包含了BPM的课程。
有了BPM的需求,就出现了BPM软件。他是根据企业中业务环境的变化,推进人与人之间,人与系统之间以及系统与系统之间的整合及调整的经营方法域解决方案的IT工具。通过对企业业务流程的整个生命周期进行建模、自动化、管理监控和优化,使企业成本降低,利润得到提升。BPM软件在企业中应用非常广泛,凡是有业务流程的地方都可以使用BPM进行管理比如企业人事办公管理、采购流程管理、公文审批流程管理、财务管理等。
而BPMN是Business Process Model And Notation 业务流程模型和符号,就是用来描述业务流程的一种建模标准。BPMN最早由BPMI(BusinessProcess Management |nitiative)方案提出。由一整套标准的业务流程建模符号组成。使用BPMN可以快速定义业务流程。
BPMN最早在2004年5月发布。2005年9月开始并入OMG(The Object Managemenet Group)组织。OMG于2011年1月发布BPMN2.0的最终版本。BPMN是目前被各大BPM厂商广泛接受的BPM标准。Activiti就是使用BPMN2.0进行流程建模流程执行管理。
整个BPMN是用一组符号来描述业务流程中发生的各种事件的。BPMN通过在这些符号事件之间连线来描述一个完整的业务流程。
十四、Oauth2.0
1. 概念
OAuth 2.0 到底是什么呢?我们先从字面上来分析下。0Auth 2.0 一词中的“Auth"表示“授权",字母“0"是 Open 的简称,表示“开放”连在一起就表示“开放授权"。这也是为什么我们使用 OAuth 的场景,通常发生在开放平台的环境下。 OAuth2.0是OAuth协议的延续版本,但不向后兼容OAuth1.0,即完全废止了OAuth1.0。很多大公司如Google,Yahoo,Microsoft等都提供了OAUTH认证服务,这些都足以说明OAUTH标准逐渐成为开放资源授权的标准。
2. 认证流程
用户借助微信认证登录看云,用户就不用单独在看云注册用户,怎么样算认证成功吗?看云网站需要成功从微信获取用户的身份信息则认为用户认证成功,那如何从微信获取用户的身份信息?用户信息的拥有者是用户本人,微信需要经过用户的同意方可为看云网站生成令牌,看云网站拿此令牌方可从微信获取用户的信息。
1)客户端请求第三方授权
用户进入看云登录页面,点击微信的图标以微信账号登录系统,用户是自己在微信里信息的资源拥有者
3. Oauth内部路径
4. 密码模式
5. 授权码模式
6. 简化模式
简化模式是授权码模式的“简化”,所以只需要在以上配置的基础上,为authorizedGrantTypes加上implicit配置即可。
7. 客户端模式
客户端模式实际上是密码模式的简化,无需配置或使用资源拥有者账号。因为它没有用户的概念,直接与授权服务器交互,通过Client 的编号(client_id)和密码(client_secret)来保证安全性。
配置方式为authorizedGrantTypes加上c1ient_credentia1s配置即可和
十五、Git
1. Git的基本推拉流程
//初始化
git init
//配置用户信息
例:git config --global user.name '用户名'
git config--global user.email '邮箱'
//关联远程仓库
git remout add origin 'git地址'
//拉取远程分支内容
git pull origin master
//添加文件到暂存区
git add .
//提交更改
git conmmit -m '提交说明'
//推送更改到远程仓库
git push origin master
附加操作:
//查看当前状态
git status
//查看提交历史
git log
//创建新分支
git branch 分支名
//切换分支
git checkout 分支名
//合并分支
git merge 分支名
//克隆远程仓库
git clone 远程仓库地址
2. git reset 几种模式
- git reset --soft 可以轻松地撤销提交,同时保留所有更改在暂存区中。
- git reset --mixed(默认模式)可以撤销提交并将更改移回工作区,允许你重新决定哪些更改应该被暂存。
- git reset --hard 需要非常谨慎,因为它会永久性地丢弃所有未提交的更改,适用于你确定不再需要那些更改的情况。