赞
踩
String类型的底层实现
基于SDS这个类型。优势在于:
C语言字符存储本身的字符长度,O(n); 存储字符长度,只需要读取,O(1)
C语言字符串拼接,如果没有分配到足够长的内存空间就会内存溢出,【SDS】能根据len提前判断是否满足,如果空间不够就会扩展,避免缓冲区溢出的问题
SDS还提供了空间预分配和惰性空间释放两种策略。
惰性空间释放:free记录需要释放的空间,等到后面使用的时候再释放。
具体的空间预分配策略原则是:当修改字符串后的长度len小于1MB,就会预分配和len一样长度的空间,即len = free; 若是len大于1MB,free分配的空间大小就为1MB
SDS是二进制安全的,可以存储二进制文件(如图片、音频等);C语言字符串必须以**\0空字符作为结束符**,而某些二进制文件中间可能含有空字符。
Hash类型的底层实现
ziplist和 hashtable,hashtable原理:
在新增时会通过Key值计算出数组下标(和 Hash Map类似),不同之处在于,hashtable在计算出hash值之后,还要通过sizemask属性和哈希值再次得到数组下标。
rehash 和 渐进式rehash
ht[0] 和 ht[1] 两个hash表交替扩展。
扩展操作:ht[1]扩展的大小是比当前 ht[0].used 值的二倍大的第一个 2 的整数幂;收缩操作:ht[0].used 的第一个大于等于的 2 的整数幂。
当ht[0]上的所有的键值对都rehash到ht[1]中,会重新计算所有的数组下标值,当数据迁移完后ht[0]就会被释放,然后将ht[1]改为ht[0],并新创建ht[1],为下一次的扩展和收缩做准备。
当rehash操作开始时会将该值改成0,在渐进式rehash的过程**「更新、删除、查询会在ht[0]和ht[1]中都进行」**,比如更新一个值先更新ht[0],然后再更新ht[1]。
而新增操作直接就新增到ht[1]表中,ht[0]不会新增任何的数据,这样保证**「ht[0]只减不增,直到最后的某一个时刻变成空表」**,这样rehash操作完成。
ziplist 压缩列表
压缩列表是一块连续的内存,「压缩列表并不是以某种压缩算法进行压缩存储数据,而是它表示一组连续的内存空间的使用,节省空间」
hash键只包含较少的key-value对,且key-value对都是小整数型或者较短的字符串时,会使用ziplist作为存储结构
附:可以用作高并发下唯一ID的生成。
List类型的底层实现
ziplist 和 linkedList 后又引入了 3.2之后quicklist
linkedlist 和 quicklist都是双向链表,链表都定义了自己的长度信息,获取长度的时间复杂度为o(1);
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P8RW1go3-1692088161164)(…/…/杂七杂八/Typora图片存储/image-20230724134949743.png)]
Set类型的底层实现
ht和intzet
intzet ?
Zset类型的底层实现\
ziplist和skiplist,
- skiplist 跳跃表 即兼顾查询效率,也保证了顺序查询
包括:SDS,链表,字典(hash),跳跃表(skiplist),整数集合,压缩列表,对象。
链表(list)
redis对于链表的处理,在原listNode的基础上,还引入了**list(包含head, tail, length, dup, free, match)**类型,用于简化对链表的操作。其中,dup:复制链表节点所保存的值;free:释放链表节点所保存的值;match:对比链表节点所保存的值和另一个输入值是否相等。
字典(hash) hash
typedef struct dict { // 类型特定函数 dictType *type; // 私有数据 void *privdata; // 哈希表 dictht ht[2]; // rehash索引 // 当 rehash 不在进行时,值为 -1 int rehashidx; } dict; // 哈希表数组 typedef struct dictht { dictEntry **table; // 哈希表大小 unsigned long size; // 哈希表大小掩码,用于计算索引值 // 总是等于 size - 1 unsigned long sizemask; // 该哈希表已有节点的数量 unsigned long used; }dictht; //哈希表节点 typedef struct dictEntry { // 键 void *key; // 值 union { void *val; uint64_t u64; int64_t s64; } v; // 指向下个哈希表节点,形成链表 struct dictEntry *next; } dictEntry;
跳跃表(skiplist)
整数集合
压缩列表(ziplist)
AOF (append only file)
特点:只会在文件末尾添加写入和修改操作命令。默认未开启。
aof文件写入过程:
命令传播 -》 缓存追加 -》文件写入和保存
AOF重写
rdb(redis database)
特点:快照读,RDB保持redis在某一时刻的数据的快照
触发条件:
- 符合自定义配置的快照规则
- 执行
save
或者bgsave
命令- 执行flushall命令
- 执行主从复制操作(第一次)
原理:
- Redis父进程首先判断:当前是否在执行save,或者bgsave/ bgwriteaof(aof重写命令)的子进程,如果在执行则bgsave命令返回;
- 父进程执行fork操作创建子进程,这个过程中父进程是阻塞的,Redis不能执行来自客户端的任何命令;
- 父进程fork后,bgsave命令返回”Background save started" 信息并不再阻塞父进程,并可以响应其他命令;
- 子进程创建RDB文件,根据父进程的内存快照生成临时快照文件,完成后对原有文件进行原子替换(RDB始终完整);
- 子进程发送信号给父进程表示完成,父进程更新统计信息。
- 父进程fork子进程后,继续工作。
优缺点:
- RDB是二进制压缩文件,占用空间小,便于传输(传给slaver);
- 主进程fork子进程,最大化Redis性能
- 使用RDB文件来恢复数据比较快
缺点:
- 不保证数据的完整性,会丢失最后一次快照后所有数据
- 父进程在fork子进程的时候,如果主进程比较大会阻塞很久。
三种形式:主从模式、哨兵模式、**集群模式shuuredis集群采用Hash Slot 数据切片的方式,而不是采用一致性哈希。原理:redis集群包含16438个插槽,在存入KV时,首先根据Key%16438计算该键属于哪个插槽。集群里的节点,各自保持一段的插槽,这样就将不同的KV对平均分配到各个节点里。
例如:有A、B、C三个节点,其中节点和插槽的对应:A(0 - 5000)、B(5001 - 10000)、C(10001 - 16438)。当有KV插入时,根据CRC16(key)%16438 计算插槽值,并存入到对应的服务器节点,比如A;当B、C或者其他节点读取该KV时,会根据对应的算法计算出该KV处于A节点,就从A节点读取到KV,并返回客户端;当新增或者删除节点时,只需要将对应的插槽进行移动即可。从用户角度来看,整个集群就像是一体的。
redis集群策略在CAP的体现:
A:采用插槽的方式,节点增删改,只需要移动对应的插槽即可,不会影响对应的数据查询和删除,整体代价非常小。可扩展性极高。
P:为了保证一定的分区容错性。redis设计了主从复制模式 和 哨兵模式 。slave of.当主机挂掉,没有节点处理主机对应的插槽时,从机会自动升级为主机替代主机进行工作。
C:redis并不能保证数据的强一致性。 原因如下:
redis集群采用异步复制作为主从复制的方式。写操作过程:
客户端向主节点B写入一条命令.
主节点B向客户端回复命令状态.
主节点将写操作复制给他得从节点 B1, B2 和 B3
cluster节点通信原理
采用 Gossip通信协议。
Gossip协议基本思想就是:一个节点想要分享一些信息给网络中的其他的一些节点。于是,它周期性的随机选择一些节点,并把信息传递给这些节点。这些收到信息的节点接下来会做同样的事情,即把这些信息传递给其他一些随机选择的节点。一般而言,信息会周期性的传递给N个目标节点,而不只是一个。这个N被称为fanout(这个单词的本意是扇出)
哨兵模式
等等……
参考文章:(170条消息) MySQL数据库面试题总结(2022最新版)_mysql面试题_程序猿周周的博客-CSDN博客
SQL语句
SQL 编写相关,select, update, insert, delete. join on left join on inner join, right join
连接(join) 和 联合(union)。前者是左右拼接,后者是上下拼接。
连接: 内连接,只返回匹配的行,外连接,返回全表,不匹配为NULL。
SQL语句的执行顺序,explain语句的原理
SQL执行顺序:先处理表(from on jion),再削行(where group by),(处理列值avg, sum),然后再削列(select),最好唯一和排序。
- from
- on
- jion
- where
- group by
- avg, sum……
- having
- select
- distinct
- order by
SQL调优技巧:
- 慢查询日志
- explain
- 分库分表(垂直分表【切字段】,水平分表【切行数】)
事务的ACID性质,原子性,一致性,持续性,隔离性。
原子性:MySQL通过undolog实现原子性,即回滚操作。
持久性:持久性是指事务一旦提交后,对数据库的改变就是永久的。基于redolog实现。背景,MySQL写数据时,是先将数据放入缓冲池,再定期将数据刷入磁盘,以达到充分利用CPU和内存资源,减少线程阻塞。但是也引发了新的问题,就是当事务提交后,数据存放在内存还没来得及刷入磁盘时,数据库宕机,就会导致事务丢失。于是引入redolog。事务在提交后,是先在redolog里写入操作命令,再更新到Buffer Pool。这样即使数据库宕机,重启之后仍然可以通过重读redolog恢复。
隔离性:指并发事务,执行结果应该跟串行化执行相等。
脏读,不可重复读,幻影读 - 》 读未提交,读已提交,可重复读,串行化。
一致性:一致性是指事务执行结束后,**数据库的完整性约束没有被破坏,事务执行的前后都是合法的数据状态。**数据库的完整性约束包括但不限于:实体完整性(如行的主键存在且唯一)、列完整性(如字段的类型、大小、长度要符合要求)、外键约束、用户自定义完整性(如转账前后,两个账户余额的和应该不变)。
MySQL隔离级别(),实现原理?
隔离级别表
加锁、InnoDB的MVCC多版本并发控制:
笔记:
当前读:读取的是记录的最新的版本。(InnoDB引擎默认是可重复读)
快照读: 简单的select(不加锁)就是快照读,读取的是数据的可见版本。
MVCC实现原理:
数据库记录的隐藏字段
DB_TRX_ID:最近修改事务ID,记录插入这条记录或者最后一次修改改记录的事务ID。
DB_ROLL_PIR:回滚指正,指向这条记录的上一个版本,用于配合undo log,指向上一个版本。
DB_ROW_ID:隐藏主键,如果表结构没有指定主键,就会生产该隐藏字段。
undolog
undo log版本链:
readview
ReadView,记录并维护系统当前活跃的事务(未提交的)ID
m_ids:当前活跃的事务ID集合
min_trx_id:最小活跃事务ID
max_trx_id: 预分配事务ID,当前最大事务ID+1
creator_trx_id: ReadView创建者的事务ID
版本链数据访问原则:(trx_id:代表是当前事务ID)
- trx_id == creator_trx_id ? 可以访问该版本
- trx_id < min_trx_id ? 可以访问该版本
- trx_id > max_trx_id ? 不可以访问该版本,说明该事务是在ReadView生成之后才开启的
- min_trx_id <= trx_id <= max_trx_id ? 如果trx_id 不在m_ids中是可以访问该版本的 成立,说明事务已经提交。
不同的隔离级别,生成ReadView的时机不同:
**Read Commited:**在事务中每一次执行快照读时生成ReadView。
REPEATABLE READ: 仅在事务中第一次执行快照读时生成ReadView, 后续复用该ReadView.
索引的底层实现
B+树,
索引失效的原因(13种)
**select ***
不满足最左匹配法则(联合索引)
索引列上有计算
索引列使用了函数
类型隐式转换
查询字符串时,对字符串条件未加引号,例如:select name from student where height = 13; (注意,height在MySQL里为varchar类型),则此时会走全表扫描
LIKE 左边包含 %
列对比
使用了OR关键字
NOT IN和NOT EXISTS关键字
order by 在某些情况下也不会走索引
- 没加where或者limit条件
- 对不同的索引进行ORDER BY
- 不满足最左匹配原则
不等于比较
IS NOT NULL
优化器发现走索引效率不如全表扫描
最左前缀法则
常用集合类及其实现原理
ArrayList LinkedList Stack (Queue) HashMap TreeMap ProtieQueue等
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7Y85kPCl-1692088161165)(…/…/杂七杂八/Typora图片存储/v2-1fb29db41cbe03a0ad3b6a7947f2cd0a_r.jpg)]
ArrayList扩容机制
LinkedList底层原理?
HashMap自动扩容机制和哈希冲突的解决办法(红黑树)
红黑树有五条原则。
HashMap(非并发安全)VS ConcurrentHashMap(并发安全)
面试题:HashMap是并发安全的吗,为什么?如何解决HashMap的非并发安全(使用ConcurrentHashMap)?ConcurrentHashMap的底层原理是什么?
作答:HashMap不是并发安全的,举例,如果同时有两个线程对Map的同一个Key值设置value值,那么因为,并发线程的执行顺序是未知的,所以会导致HashMap对应的Key值也未知,或者说某个线程的更新丢失。
ConcurrentHashMap,使用CAS锁和synchronized同步锁来保证并发安全,同时为了保证查询效率,使用了分段锁的形式。只synchronized某个节点,来提高插入效率。
HashTable VS HashMap (HashTable已经被Org官方宣布弃用)
语言级实现机制
在Java中,主要使用synchronized、ReentrantLock来实现悲观锁,使用CAS算法(AtomicInteger类型)来实现乐观锁(并行自增一致)
volite使用及其实现原理
synchronized同步锁的实现原理
原理:synchronized在JVM的实现原理,JVM基于moniter进入和退出来实现对代码块和方法区的同步实现,但二者的实现细节有区别。代码块同步是使用moniterenter和moniterexit来实现的,方法同步是另一种方式,细节在JVM规范里并没有详细说明,但方法的同步同样可以使用这两个指令来实现。monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处,JVM要保证每个monitorenter必须有对应的monitorexit与之配对。任何对象都有一个monitor与之关联,当且一个monitor被持有后,它将处于锁定状态。线程执行到monitorenter
指令时,将会尝试获取对象所对应的monitor的所有权,即尝试获得对象的锁。
CAS(Compare And Sweep)乐观锁原理及其Atomic类的底层原理
AtomicInteger类型如何保证原子自增?
Thread线程技术:
Java开启多线程的三个方式(new Thread(), 实现Runable类,实现Cal……类
ThreadLocal详解:
ThreadLocal实现原理:
ThreadLocal的
get()
和set(T value)
方法都是基于ThreadLocalMap
(每个线程内部的Map),Map中的key为ThreadLocal实例的弱引用,value为对应设置的value
(线程变量的副本),这些对象之间的引用关系如下:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3sB55SkM-1692088161165)(…/…/杂七杂八/Typora图片存储/v2-e2d4b8eac152596232d3e32313927d59_r.jpg)]
HashMap<Object, String> map = new HashMap<>(); map.put(null, "hello"); System.out.println(map.get(null)); //输出为:hello,证明null也能作为Key
- 1
- 2
- 3
- 4
ThreadLocal内存泄漏:
从上图可以看出,当ThreadLocal不存在外部强引用后,势必会被JVM回收,这时ThreadLocalMap存储的Key值就变成了null,但因为value是强引用,所以不会被JVM回收。但如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:
Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value
永远无法回收,造成内存泄漏。
线程池技术:
怎么使用线程池:
ThreadPoolExetuter
类执行,初始化方法的参数有 corePoolSize, maximumPoolSize, workQueue, keepAliveTime, unit, threadFactory(线程生成策略), handler(饱和策略,或者任务拒绝策略);线程池需要解决的问题是,减少线程频繁创建销毁带来的性能开销。
ThreadPoolExecuter类的几个比较重要的参数理解:
- corePoolSize
- maximumPoolSize
- workQueue
- keepAliveTime
- unit
- threadFactory
- handler
线程池设计一共需要处理三个问题:
线程池如何维持自身的状态?如何管理任务?如何管理线程?
- 线程池如何维持自身的状态?
1.1 线程池内部用一个ctl的变量存储了
runstate
线程池状态 和workerCount
线程池线程数量 两个变量,其中前三位bit 存储线程池状态,后29位bit存储线程池数量。线程池状态分为:RUNNING、SHOTDOWN、STOP、TIDYING?、TERMINATED?;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dMR8rcFu-1692088161165)(…/…/…/…/杂七杂八/Typora图片存储/image-20230725152045210.png)]
- 线程池如何管理任务?
任务调度:
任务缓冲:(阻塞队列)
- 数组有界
- 链表有界
- 优先级
- 延迟队列
- 同步队列(只有一个位置)
- 链表无界队列
- 双向链表阻塞队列
任务申请:(线程申请任务执行)
由
getTask()
方法实现[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XaVWWbIu-1692088161166)(…/…/…/…/杂七杂八/Typora图片存储/image-20230725153842951.png)]
任务拒绝:
任务拒绝策略,即ThreadPoolExcuter,初始化参数里的handler参数填入的部分。
拒绝策略是一个接口,设计如下:
public interface RejectedExecutionHandler {
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}用户即可以自行实现该接口,也可以选择JDK提供的四种拒绝策略
- 拒绝其报错,默认 ThreadPoolExecuter.AbortPolicy
- 拒绝但不报错 ThreadPoolExecuter.DiscardPolicy
- 抛弃阻塞队列里最老的任务,并对当前任务重新申请线程 ThreadPoolExecuter.DiscardOldestPolicy
- 由创建任务的线程去执行任务 ThreadPoolExecuter.CallerRunsPolicy
- 线程池如何管理线程?
线程池设计了worker线程,来作为线程池内部的执行任务线程。其设计如下:
private final class Worker extends AbstractQueuedSynchronizer implements Runnable{
final Thread thread;//Worker持有的线程
Runnable firstTask;//初始化的任务,可以为null
}
woker线程的声明周期
为了便于判断worker线程的状态(是否处于执行状态),编写者让worker线程继承了AQS, 使用AQS来实现独占锁的功能。线程回收时,会去询问worker线程的AQS锁,如果无法获得独占锁,则
woker线程的新增
woker线程回收
线程池不会销毁线程,而是交给JVM来回收。线程池只会维护对线程的引用,当需要回收线程时,就放弃对线程的引用。 核心线程可以无限等待获取任务,非核心线程则只能限时等待。
woker线程执行任务
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7LcDg5hm-1692088161166)(…/…/…/…/杂七杂八/Typora图片存储/image-20230725163955630.png)]
Java内存模型
运行时数据区域
注:除了程序计数器,其他区域包括直接内存区域都会有OutOfMemoryError,故对应省略,不代表没有。
程序计数器(线程私有,线程同周期)
当前线程所执行的字节码的行号指示器。唯一一个没有OutOfMemoryError的区域。Java虚拟机栈(线程私有,线程同周期,线程方法)StackOverFlowError
Java虚拟机栈描述的是Java方法执行的线程内存模型:每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
局部变量表存放了编译期间可知的各种Java虚拟机基本数据类型、对象引用类型和returnAddress类型(指向了一条字节码的地址)本地方法栈(为本地方法服务)StackOverFlowError
本地方法栈和Java虚拟机栈类似,区别在于:虚拟机栈为虚拟机执行Java方法服务,而本地方法栈为虚拟机使用到的本地(Native)方法服务。
附:java方法: 是由java语言编写,编译成字节码,存储在class文件中的。 java方法是与平台无关的。 本地方法: 本地方法是由其他语言(如C、C++ 或其他汇编语言)编写,编译成和处理器相关的代码。 本地方法保存在动态连接库中,格式是各个平台专用的,运行中的java程序调用本地方法时,虚拟机装载包含这个本地方法的动态库,并调用这个方法。Java堆(所有线程共享,存放对象的实例)‘
主要存储Java实例化的对象和数组,是虚拟机进行GC的主要区域。方法区(线程共享,存储常量、静态变量等)
用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。运行时常量池(方法区的一部分)
直接内存
HotSpot虚拟机在Java堆中对象分配、布局和访问的全过程
new 先检查使用有类的符号引用以及类是否被加载,如果没有则进行相应的类加载
类加载检查通过之后,虚拟机将为新生对象分配内存。
- 根据GC收集器是否有压缩整理内存的能力,来决定是否进行指正碰撞式的内存分配
- 同时,内存分配时需要考虑到并发问题(解决方案是为每个线程分配一个线程缓冲池,先在缓冲池内实例化对象)
- 分配完内存后,需要对该块内存进行初始化。
- 然后设置一些必要的信息(比如说,这个对象是哪个类的实例,如何才能找到类的元数据信息,对象的hashCode,对象的GC分代年龄信息等信息),存放在对象头里(Object Header)
垃圾回收以及内存分配策略
垃圾识别算法
- 引用计数法:对象头存储对它的引用计数,当计数为0,则可回收。实现简单,额外开销较小,但是存在循环引用的问题。现已弃用
- 可达性分析:
垃圾回收算法:
- 分代收集理论:
- 标记-清除算法:
- 标记-复制算法:
- 标记-整理算法:
几大垃圾收集器:
Serial 最原始的垃圾回收策略,新生代采用复制算法,老年代采用整理算法,流程采用单线程,用户线程全停止的策略。
ParNew + CMS 最经典的商业垃圾回收策略 JDK1.7及以前
ParNew 是新生代垃圾收集器,主要使用标记复制算法,采用多线程并发标记,并发清理的策略。
CMS是一款以低时延为设计目的老年代垃圾收集器,主要使用标记整理算法,有初始标记- 并发标记- 重新标记(增量标记的策略) 和并发清除四个阶段。
G1 (Garbage First)JDK1.8默认的垃圾收集器。以停顿模型(指定一个M毫秒的时间段,其垃圾收集时间大概率不超过N毫秒),的更加可控稳定的GC。
创造性的使用了基于Region的堆内存布局(不在使用分代的形式,而是将堆内存划分为若干个大小相同的Region),仍然采用了CMS并发标记并发整理的算法形式,变动点在于:
- 对每个Region维护一个卡表(意味着G1需要使用Java堆内存的10%-20%用来维护算法)以及
- 在后台维护一个优先级队列,每次整理就不再是Full Gc,而是仅针对部分Region的GC。
- 对于并发标记,CMS使用的是增量标记的策略,而G1则是采用原始快照(SATB)算法来解决的。
有初试标记,并发标记,最终标记和筛选回收四个阶段。
附:G1除了并发标记,其余阶段都需要暂停用户线程。其中最终标记和筛选回收是多线程进行。Shenanadoah收集器 (仍然基于Region的堆内存分区,但是不再使用卡表而是维护一个全局的指向矩阵,减少额外内存开销)
ZGC 是一款基于Region的内存分区,不设分代的,使用了读屏障、染色指针和内存多重映射等技术来实现可并发的标记-整理算法的,以低延时为首要目标的垃圾收集器。
特点:
- ZGC的region分为小、中、大三个大小,用于存储某些大对象。
JVM调优技巧:
注:基于Java堆是虚拟机控制的最大的一块内存区域,而Java堆上的主要问题为GC回收,故JVM调优技巧主要为如何根据当前服务器的硬件条件和运行要求,选择合适的垃圾收集器以及使用合适的参数来确保与GC充分配合,软件运行不会崩溃。但是不代表JVM调优只有GC方面一块,肯定还包括查看程序运行时线程占用是否合理等等。
- JDK常用的命令:jps(虚拟机进程状况工具),jstat(虚拟机统计信息监视工具),jinfo(Java配置信息工具), jmap(java内存映射工具),jhat(虚拟机堆转储快照分析工具),jstack(Java堆栈跟踪工具)
- **-Xmx:指定应用程序可用的最大堆大小; -
Xms
指定应用程序可用的最小堆大小,-Xmn 堆内新生代的大小。通过这个值也可以得到老生代的大小:-Xmx减去-Xmn,-Xss 设置每个线程可使用的内存大小,即栈的大小。
JVM类加载机制和双亲委派模型
class文件(二进制字节流)加载过程:加载 -> 验证 -> 准备 -> 解析 -> 初始化 -> 使用 -> 卸载
类加载机制:
在类加载过程中,《虚拟机规范》只规定了要通过class文件的全限定名加载二进制流文件,并没有规定加载的方式和加载文件的实际类型。
这就让类加载有了很大的可扩展性:
- 从压缩文件中读取,是WAR,EAR,JAR包的基础支撑
- 从网络空间读取,最典型的应用是Applet
- 运行时计算生成,这就是动态代理的基础
- 由其他文件生成,最典型的应用就是JSP应用,由JSP文件生成对应的类。
类加载器和类的唯一性:在虚拟机中,类的唯一性是由类的加载器和这个类本身共同决定的。这就意味着,如果某个类是由两个类加载器加载出来的两个类,则这两个类在虚拟机中会被认为是不同的。
双亲委派模型:
考的比较少就简单聊聊吧,IO技术主要涉及文件读取
JavaIO按读取的数据流分为
字节流
InputStream(), OutputStream();
字符流
Reader(), Writer();
按读取方式又分为
注:计算机网络的知识相对来说只有大厂考察较多,但相对靠底层(相比考研计网的要求要低许多),故有必要进行一次结构性的梳理。
从上往下依次为:
RabbitMQ是基于AMQP(高级消息队列协议)编写的一款开源的消息队列中间件。
docker pull rabbitmq:management
docker run -id --name=rabbitmq -v rabbitmq-home:/var/lib/rabbitmq -p 15672:15672 -p 5672:5672 -e RABBITMQ_DEFAULT_USER=swsk33 -e RABBITMQ_DEFAULT_PASS=123456 rabbitmq:management
什么是死信队列:
消费失败的消息存放的队列。
消息消费失败的原因:
- 消息被拒绝并且消息没有重新入队(requeue=false)
- 消息超时未消费
- 达到最大队列长度
生产者生产的消息到RabbitMQ Server消息丢失,RabbitMQ Server存储的消息丢失和Rabbit MQ到消费者的信息丢失
对应的解决方案:生产者确认机制、消费者确认机制和持久化
生产者确认机制
事务机制。 在一条消息发送之后会是发送端堵塞,等待RabbitMQ的回应,之后才能继续发送下一条信息,性能差。
开启生产者确认机制。 只要消息到达路由端,就给生产者发送一条ack确认,如果没收到,就会发送nack提示发送失败
消费者手动确认:
消费者设置为手动确认消息。消费者处理完逻辑之后再给broker回复ack,表示消息已经成功消费,可以从broker中删除。当消息者消费失败的时候,给broker回复nack,根据配置决定重新入队还是从broker移除,或者进入死信队列。只要没收到消费者的 acknowledgment,broker 就会一直保存着这条消息,但不会 requeue,也不会分配给其他 消费者。
消费者设置手动ack:
##设置消费端手动 ack
spring.rabbitmq.listener.simple.acknowledge-mode=manual
持久化:
消息持久化需要满足以下条件:
消息重复消费怎么处理?
消费端怎么进行限流
docker run -id --name=rabbitmq -v rabbitmq-home:/var/lib/rabbitmq -p 15672:15672 -p 5672:5672 -e RABBITMQ_DEFAULT_USER=swsk33 -e RABBITMQ_DEFAULT_PASS=123456 rabbitmq:management
一共有23种设计模式,可分为三大类:创建型,结构型,行为型;
单例模式:
保证在程序中只有一个实例存在,并且能全局访问到。
工厂模式:
包括 简单工厂,工厂方法,抽象工厂这3种细分模式。
每个对象的创建逻辑都比较复杂的时候,会考虑使用工厂模式,将实例的创建和使用分割开来。
建造者模式:
用来创建复杂对象,(主要是单个对象)。
原型模式(不常用):
如果对象的创建成本比较大,同一个类,不同对象之间差别不大(大部分字段相同),这种情况就可以利用已有对象(原型)进行拷贝复制的方式,来创建新对象。
代理模式:
在不改变原始类接口的条件下,为原始类定义一个代理类,主要目的是控制访问,而非加强功能,这是它跟装饰器模式最大的不同
桥接模式:(不常用)
将抽象和实现解耦,让它们能独立开发。
装饰器模式:
主要解决继承关系过于复杂的问题,通过组合来代替继承,给原始类添加增强功能。
适配器模式:
代理模式、装饰器模式提供的都是跟原始类相同的接口,而适配器提供跟原始类不同的接口。适配器模式是用来做适配的,它将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能一起工作的类可以一起工作。适配器模式是一种事后的补救策略,用来补救设计上的缺陷。
门面模式:
组合模式:
享元模式:
主要解决的就是“类或对象之间的交互问题”。
开闭原则(对扩展开放,对修改关闭)
里氏代换原则(面向对象的基本原则)
任何基类出现的地方,子类一定可以出现。是实现抽象化的具体步骤的规范。
依赖倒转原则(针对接口编程,依赖于抽象而不依赖于具体)开闭原则的基础
接口隔离原则:
使用多个隔离的接口,比使用单个接口要好。
迪米特法则(最少知道原则)
一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。
合成复用原则
原则是尽量使用合成/聚合的方式,而不是使用继承。
CAP理论:
C,一致性:所有节点访问同一份最新的数据副本
A,可用性:非故障的节点在合理的时间内做出合理的响应
P,分区容错性:指分布式系统出现网络分区时,仍然能够对外提供服务。(网络分区是指,多个节点之间因为某些故障,不连通了,整个网络出现了几个区域。)
AP和CP的矛盾:
这里其实是有一个误解,当不存在网络分区时,也就是不需要保证P时,C和A是可以同时保证的;当出现网络分区时,如果对某个分区A进行写数据操作,假设分区B与A不连通,且存在与A相同的数据副本,此时,如果要保证数据的一致性,则需要关闭B节点的服务,即放弃了保证A;如果要保证两个分区的可用性,则就不能保证在两个分区的数据一致性,即无法保证C。
BASE理论:
是Basically Available(基本可用),Soft-Status(软状态)和Eventually Consistant(最终一致性)的短语缩写。该理论是对AP策略的扩展。
基本思想是,即使无法做到强一致性,也可以采取适当的方法来使系统达到最终一致性。
共识算法:Paxos算法,Raft算法
Gossip协议。
定义:是一款实时分布式搜索和分析引擎。 是面向文档的非关系型数据库。
elasticsearch(集群)可以包含多个索引(数据库),每个索引中可以包含多个类型(表),每个类型下可以包含多个文档(行),每个文档中又包含多个字段(列)。
重要概念:
索引-》类型-》文档:
IK分词器: 用于拆分搜索关键词。
选择排序
插入排序
冒泡排序
希尔排序(通过增量,将数组分成多个子表,对每个子表采用插入排序,)
归并排序
快速排序(归并排序的思想,子表采用交换类排序)
堆排序
基于堆的排序算法,(不稳定),基本思路为,首先基于数组生成大顶堆(或者小顶堆),每次将堆顶元素拿出作为排序好的元素,再将叶子节点最左边的数放到堆顶,并调整堆,直到堆只剩一个元素,排序完成。
补充:
概念,镜像,容器,DockerFile
流程,先拉取镜像,在运行容器。
docker pull [name]:[version] #默认最新
docker build -t [image_name] . #通过Dockerfile文件构建容器
docker run -d --name=communnity -p 8080:8080 -v /temp/data:/temp/config [image]
docker exec -it [container] /bin/bash #以控制台的方式进入Docker容器
docker ps (-a) [container] #查看容器
笔者编:本身Spring框架应该收入到Java那一节中,但是考虑到Spring系列在面试八股中已经占了相当重要的一部分,单独作为一节来进行总结。
Spring框架,是基于约定大于配置原则,拥有着IoC(inverse of Controll, 控制反转) 和AOP两大特性的,基于Bean的企业级开发框架。
控制反转(IoC):
控制反转是通过DI,即依赖注入来实现的,实现的思想为,将对象的实例通过各种方式注册为Bean,由IoC容器统一进行管理,当需要某个Bean时,只需要通过依赖注入就可以获取对应的对象实例,而不需要考虑对象实例的生成,配置和销毁。以达到代码解耦等目的。
如何声明Application容器,以及如何声明Bean?
ioc 容器初始化过程:BeanDefinition 的资源定位、解析和注册。
Bean的创建方式有三种:
基于xml文件的方式:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<bean id="mobian3" class="pers.mobian.springseventh.MoBian3XML">
</beans>
public class MainTestXML1 {
public static void main(String[] args) {
//启动容器
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-test.xml");
System.out.println(context.getBean("mobian3"));
}
}
基于注解:
@Component
@Service
@Controller
@Dao
@Mapper
……等
//在容器类上添加注解:
@ComponentScan("pers.mobian.firstTest")
基于注解 + xml的方式
主要是在xml里声明包的扫描路径。在包类中使用注解声明Bean
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="pers.mobian.firstTest"/>
</beans>
Bean的生命周期?
对Bean进行实例化
依赖注入
配置Bean的各种属性:
4.如果Bean实现了
BeanFactoryAware
接口,Spring将调用setBeanFactory()
5.如果Bean实现了
ApplicationContextAware
接口,Spring容器将调用setApplicationContext()
6.如果存在
BeanPostProcessor
,Spring将调用它们的postProcessBeforeInitialization
(预初始化)方法,在Bean初始化前对其进行处理7.如果Bean实现了
InitializingBean
接口,Spring将调用它的afterPropertiesSet
方法,然后调用xml定义的 init-method 方法,两个方法作用类似,都是在初始化 bean 的时候执行8.如果存在
BeanPostProcessor
,Spring将调用它们的postProcessAfterInitialization
(后初始化)方法,在Bean初始化后对其进行处理
Bean初始化完成,交给应用使用,直到应用被销毁
10.如果Bean实现了
DisposableBean
接口,Spring将调用它的destory
方法,然后调用在xml中定义的destory-method
方法,这两个方法作用类似,都是在Bean实例销毁前执行。
单例Bean的线程安全问题?循环依赖的解决方案?
面向切面编程(AOP):
AOP 编程思想是指在不改变源代码的前提下,对代码进行增强。
实现:静态代理和 动态代理
动态代理是指在程序运行时生成一个代理对象,在运行期间对业务方法进行增强,不会生成新类。
JDK动态代理:
通过implment接口实现,目标类必须实现接口,如果某个类没有实现接口,就不能用JDK动态代理。
CGLIB动态代理:
通过继承实现,CGLIB可以在运行时动态生成类的字节码,动态创建目标类的子类对象,在子类中增强目标类。
AOP术语:
MVC为web编程的一种工作模式,即Model(模型, 通常为数据模型),View(视图,通常为静态资源,如HTML文件),Controller(控制器,通常为请求的映射器,用来决定对某个请求返回什么样的Model And View)
SpringMVC 其实就是一种基于Servlet的MVC模型:
SpringMVC原理 OR web请求的执行流程:
客户端请求分发到 -> DispatchServlet
(前端控制器);
DispatchaServle
t控制器通过HandlerMapping
(底层是一个url -> Controller方法的map),找到处理请求的controller
3、处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
4、 DispatcherServlet调用HandlerAdapter处理器适配器。
5、 HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。
DispatchaServlet
将请求交给Controller
处理
controller
处理完逻辑后,将ModelAndView
返回给DispatchaServlet
;
DispatchServlet
将ModelAndView
交给 ViewResolver
(视图解析器),处理成View。
DispatchServlet
将结果(View)封装成HTTP,返回响应数据
浏览器绘制页面。
SpringBoot这个框架,以Spring框架为底层,完全基于约定大于配置的设计思路,通过自动配置,注解等方式,自动处理了Application容器,和大多数Bean的创建声明,大大简化了搭建一个Spring框架的流程以及开发Java应用过程中用于配置Bean的消耗。
改变:在原有Spring框架的基础上,推崇以Java注释的方式声明Bean,支持application.properties / .yml 配置文件的方式,快速配置一些Bean的属性,支持集成多种Spring框架,包括SpringMVC,SpringData, SpringSecurity;
SpringBoot容器的启动流程,@SpringBootApplication注解的原理
指示一个配置类,该类声明一个或多个@Bean方法,还触发自动配置和组件扫描。这是一个方便的注释,相当于声明@Configuration、@EnableAutoConfiguration和@ComponentScan。
@SpringBootApplication注解主要由三个部分组成
@ComponentScan:Spring注解,用于声明应该在哪些包路径下扫描基于Java注解的Bean,用于创建注入到Application容器里。
@SpringBootConfiguration
主要是对Spring的
@Configuration
的封装:@EnableAutoConfiguration(主要部分)
官方说明:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EBhOyP2q-1692088161167)(…/…/杂七杂八/Typora图片存储/image-20230815161726840.png)]
翻译:
启用Spring应用程序上下文的自动配置,尝试猜测和配置您可能需要的bean。自动配置类通常基于类路径和您定义的bean来应用。例如,如果您的类路径上有tomcat-embedded.jar,那么您可能想要一个TomcatServlet WebServerFactory(除非您定义了自己的Servlet WebServerFactoryBean)。当使用@SpringBootApplication时,上下文的自动配置会自动启用,因此添加此注释不会产生额外效果。自动配置试图尽可能地智能化,并将随着您定义更多自己的配置而退出。您总是可以手动排除()任何您永远不想应用的配置(如果您没有访问权限,请使用excludeName())。您也可以通过spring.autoconfig.exclude属性排除它们。自动配置总是在用户定义的bean注册后应用。
用@EnableAutoConfiguration注释的类的包(通常通过@SpringBootApplication)具有特定的意义,通常用作“默认”。例如,它将在扫描@Entity类时使用。通常建议您将@EnableAutoConfiguration(如果您没有使用@SpringBootApplication)放在根包中,以便可以搜索所有子包和类。自动配置类是常规的Spring@Configurationbean。它们使用SpringFactoriesLoader机制进行定位(针对此类进行键控)。通常,自动配置bean是@Conditionalbean(最常用的是@ConditionalOnClass和@ConditionalOnMissingBean注释)。
乐观锁,悲观锁,自旋锁?,偏向锁。
**复习:**乐观锁常用的算法为CAS(Compare And Sweep),该算法存在的问题ABA 问题,解决方案,获得锁时只能原子操作 + 1
确保pms_product 的 stock字段在扣值之前,保持读取判断时的值。
考虑实现方案:CAS乐观锁
具体过程:
MySQL行锁
具体过程:
token原理:
所谓的Token,其实就是服务端生成的一串加密字符串、以作客户端进行请求的一个“令牌”
用户登录时服务器生成一个token发送给用户,用户再次登录时就只需要使用这个token进行登录,对于服务器来说也不需要维护session。
而JWT(JSON Web Tokens)是一种Token的实现方案:
一个JWT包含三个部分:头部,负载,签名
header(头部):是一个JSON对象,包含算法和token类型。
Payload(负载):用来存放实际需要传递的数据。
前面五个字段都是JWT的标准所定义的,也支持自定义字段。
- iss: 该JWT的签发者
- sub:该JWT面向的用户
- aud:接收该JWT的用户
- exp:什么时候过期,这里是一个unix时间戳
- iat:在什么时候签发的
- [自定义字段] name : hello
Signature(签名):把前面两端拼接起来,并用算法和仅服务器指定的私钥加密,从而确保token不会被篡改。
例如:HMACSHA256(base64UrlEncode(header) + “.” +base64UrlEncode(payload),secret)
生成的形式如下:
eyJhbGciOiJIUzUxMiJ9.eyJleHAiOjE2MTQ2Puk.v6cyfk0B1j7vbIqw_Q
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9DNrTFDn-1692088161167)(…/…/杂七杂八/Typora图片存储/image-20230806123456712.png)]
客户端向应用程序发送请求时,会通过一个Filter Chain
, 其中的每个Filter
可以完成如下工作
Filter
被调用Filter
的HttpServerletRequest
和HttpServerletResponse
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
// do something before the rest of the application
chain.doFilter(request, response); // invoke the rest of the application
// do something after the rest of the application
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rMUKtU3G-1692088161167)(…/…/杂七杂八/Typora图片存储/image-20230806124804115.png)]
Spring提供了一个叫DelegatingFilterProxy
的Filter
实现,然后DelegatingFilterProxy
会从ApplicationContext
查找Bean Filter,对应的伪代码实现如下:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
Filter delegate = getFilterBean(someBeanName);
delegate.doFilter(request, response);
}
1 | 延迟地获取被注册为Spring Bean的 Filter。 对于 DelegatingFilterProxy 中的例子,delegate 是 Bean Filter0 的一个实例。 |
---|---|
2 | 将工作委托给 Spring Bean。 |
DelegatingFilterProxy
的另一个好处是,它允许延迟查找 Filter
Bean实例。这一点很重要,因为在容器启动之前,容器需要注册 Filter
实例。然而, Spring 通常使用 ContextLoaderListener
来加载 Spring Bean,这在需要注册 Filter
实例之后才会完成。
Spring Security 的 Servlet 支持包含在 FilterChainProxy
中。FilterChainProxy
是 Spring Security 提供的一个特殊的 Filter
,允许通过 SecurityFilterChain
委托给许多 Filter
实例。由于 FilterChainProxy
是一个Bean,它通常被包裹在 DelegatingFilterProxy 中。
SecurityFilterChain
被 FilterChainProxy 用来确定当前请求应该调用哪些 Spring Security Filter
实例。
总结:
- Spring Security的思路是基于拦截器链(
Filter Chain
)实现的,通过在客户端和服务器之间搭建起拦截器链实现了两个效果:防止一直不需要的请求进入到Serverlet 以及 修改对应的HttpServerletRequest
和HttpServerletResponse
来实现一些特定的功能- 由于
Filter Chain
是在Serverlet注册的同时就生成了,比ApplicationContext内容早,故Spring Sercurity通过在拦截器链中加入了一个DelegatingFilterProxy(先生成),再通过DelegatingFilterPorxy去延迟加载bean的思路- DelegatingFilterProxy延迟加载的往往是
FilterChainProxy
这个bean。FilterChainProxy
作为Spring Security组件的核心和起点。,用来插入Security自有的FilterChain-----SecurityFilterChain。- SecurityFilerChain可以自定义,内部的 SecurityFiler是bean,基于SpringSecurity的自定义注册,登录,权限控制等功能都可以在其中实现。
- SecurityFiler是通过SecurityFilterChainAPI插入到FilterChainProxy中的。可以用于认证、登录、授权、漏洞保护等等。Filter是按照特定的顺序执行的,可以在FilterOrderRegistration的代码中查看Filter的执行顺序。
认证功能:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-omRXhWk4-1692088161168)(…/…/杂七杂八/Typora图片存储/image-20230806131700199.png)]
tip: | 你可以从 OncePerRequestFilter 中继承,而不是实现 Filter ,这是一个基类,用于每个请求只调用一次的 filter,并提供一个带有 HttpServletRequest 和 HttpServletResponse 参数的 doFilterInternal 方法。 |
---|
SqlSessionFactoryBuilder -> SqlSesstionFactory -> SqlSession
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。