面试高频题

面试高频题

1.sleep()和wait()的区别

2.String、StringBuffer、StringBuilder的区别

3.==和equals()、hashCode()

4.new String字符串常量池

5.强引用 软引用 弱引用 虚引用

6.ThreaLocal原理及内存泄漏

7.进程、线程的生命周期及状态

在这里插入图片描述

8.Synchronized(偏向锁 自旋锁 重量级锁)+Volatile

用来解决对多个线程之间访问资源的同步性,可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。
早期版本中,synchronized 属于 重量级锁,效率低下。 因为监视器锁(monitor) 是依赖于底层的操作系统的 Mutex Lock 来实现的,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高。JDK1.6 对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。

偏向锁->轻量级锁->重量级锁偏向锁:

  • 偏向锁: 线程A进来判断对象头中是否有其他线程id,如果没有,通过一次CAS操作设置对象头的线程id为自己的线程id,以后A线程再进入和退出同步代码块石不需要进行CAS操作来加锁和解锁,只需要简单测试下对象投中Mark Word是否存储了指向当前线程的偏向锁;此时线程B进来,也进行同样的操作,发现对象头中已经有线程A的id,尝试撤销锁,撤销成功就把对象头的线程id设置为自己,只有达到全局安全点的时候才能撤销成功,撤销失败就升级为轻量级锁。

  • 轻量级锁: 假设线程A抢占到轻量级锁,就在自己的线程栈帧中维护一个Lock Record(锁记录空间),并将对象头中的Mark Word复制到锁记录中(Displaced Marked Word),然后将对象头中维护一个指针指向Lock Record,成功:线程获得锁,失败:其他线程竞争锁,当前线程使用自旋来获取锁;线程B进来也进行相同的操作,修改指针失败之后通过多次CAS(自旋)去尝试获取锁,如果自旋10次后仍未获得锁就升级为重量级锁。
    轻量级锁解锁:使用原子的CAS操作将Displaced Marked Word替换回对象头,成功:没有竞争,失败:当前锁存在竞争升级未重量级锁

  • 重量级锁: 假设线程A执行monitorenter指令,抢占到重量级锁,此时线程B进来,直接进入阻塞状态,进入同步队列(由CPU调度),如果是执行了wait方法就进入等待队列,等待抢占到锁的线程执行monitorexit指令然后自己被唤醒。

在这里插入图片描述
在这里插入图片描述

下图中第一个标绿 MarkWord 的起始状态是HashCode|age|0|01 是偏向锁未被启用时, 分配对象后的状态, 所以在图中并没有偏向锁这一流程的体现, 是直接从无锁状态进入了轻量级锁的状态
在这里插入图片描述

9.Sychronized和ReentrantLock的区别

10.线程池的底层⼯作原理

在这里插入图片描述

提交优先级
执行优先级
在这里插入图片描述

在这里插入图片描述

11.Atomic原子类及底层原理(CAS)

12.AQS+ReentrantLock

13.JVM内存区域(运行时数据区)

虚拟机栈、本地方法栈、程序计数器、堆、方法区

14.Java对象的创建过程(半初始化)

1.类加载检查
2.分配内存
3.初始化零值
4.设置对象头
5.执行init方法

15.DCL与volatile问题(指令重排)

在这里插入图片描述

16.对象在内存中的存储布局

对象头 类型指针 实例数据 对齐填充
在这里插入图片描述

17.对象头包括什么?

markword+类指针+数组长度
在这里插入图片描述

18.对象怎么定位?

在这里插入图片描述

19.对象怎么分配?(栈上-线程本地-Eden-Old)

在这里插入图片描述

20.一个Object占多少字节?

在这里插入图片描述

21.浅拷贝和深拷贝

在这里插入图片描述在这里插入图片描述
在这里插入图片描述

22.如何判断对象是否死亡?

引用计数法:无法解决对象间循环引用的问题
可达性分析:从gc roots为起点,向下搜索引用链,从gc root到这个对象不可达,则为不可用对象。

23.强引用、软引用、弱引用、虚引用

24.垃圾收集算法

  • 标记-清除算法(碎片化)
  • 复制算法(浪费空间)
  • 标记-整理算法(移动开销大)
  • 分代收集算法(新生代–复制,老年代–标记清楚或整理)
    在这里插入图片描述

25.垃圾回收器(SerialSerial Old、Parallel ScavengeParallel Old、ParNew==CMS、G1)

在这里插入图片描述
CMS 增量更新
在这里插入图片描述
并发标记,产生漏标,最后再进行重新标记
在这里插入图片描述

G1原始快照
在这里插入图片描述
CMS垃回收器
在这里插入图片描述
在这里插入图片描述
G1垃圾收集器

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

26.类文件结构Class

27.类加载过程(加载=》验证=》准备=》解析=》初始化)

在这里插入图片描述
加载=》验证=》准备=》解析=》初始化
在这里插入图片描述

28.双亲委派模型

在这里插入图片描述

在这里插入图片描述

为什么要使用双亲委派模型?

  • 双亲委派可以防止核心类被篡改,提升系统安全性!
  • 避免重复的类加载,加快速率!

28.B树和B+树的区别?

在这里插入图片描述

在这里插入图片描述

29.MylSAM和InnoDB索引实现(聚集索引和非聚集索引)

哈希索引

在这里插入图片描述

BTree索引

在这里插入图片描述
在这里插入图片描述

30.索引最左前缀原理、回表、索引覆盖、索引下推、倒排索引

在建立联合索引时,都遵循从左往右的优先级,最左优先,当出现范围查询(> < between 以%开头的like查询 等等)时停止匹配。
在这里插入图片描述

覆盖索引
覆盖索引即需要查询的字段正好是索引的字段,那么直接根据该索引,就可以查到数据了,而无需回表查询。

索引下推
在这里插入图片描述

倒排索引(Inverted Index):倒排索引是实现“单词-文档矩阵”的一种具体存储形式,通过倒排索引,可以根据单词快速获取包含这个单词的文档列表。倒排索引主要由两个部分组成:“单词词典”和“倒排文件”。

单词词典(Lexicon):搜索引擎的通常索引单位是单词,单词词典是由文档集合中出现过的所有单词构成的字符串集合,单词词典内每条索引项记载单词本身的一些信息以及指向“倒排列表”的指针。
倒排列表(PostingList):倒排列表记载了出现过某个单词的所有文档的文档列表及单词在该文档中出现的位置信息,每条记录称为一个倒排项(Posting)。根据倒排列表,即可获知哪些文档包含某个单词。
倒排文件(Inverted File):所有单词的倒排列表往往顺序地存储在磁盘的某个文件里,这个文件即被称之为倒排文件,倒排文件是存储倒排索引的物理文件。
在这里插入图片描述

30.MyISAM和InnoDB的区别?

在这里插入图片描述

30.truncate、delete和drop的区别?

1.执行速度,一般来说: drop> truncate > delete
2.delete是DML语句,不会自动提交。drop/truncate都是DDL语句,执行后会自动提交
3.TRUNCATE 和DELETE只删除数据, DROP则删除整个表(结构和数据)
4.truncate、drop是DDL,操作立即生效,原数据不放到 rollback segment中,不能回滚,delete不会自动提交,可以回滚
5.对于由 FOREIGN KEY 约束引用的表,不能使用 TRUNCATE TABLE,而应使用不带 WHERE 子句的 DELETE 语句。由于 TRUNCATE TABLE 不记录在日志中,所以它不能激活触发器。
6.TRUNCATE TABLE 不能用于参与了索引视图的表。
7.truncate会将计数器清零,delete再插入数据 id+1

31.sql中的慢查询怎么优化?

是查询条件没有命中索引?是load了不需要的数据列?还是数据量太大?
分析是否走了索引–>重写语句不查询不需要的列–>分库分表
在这里插入图片描述

32.ACID靠什么保证?

在这里插入图片描述

33.什么是MVCC?

在这里插入图片描述
在这里插入图片描述

33.mysql主从同步原理?

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

34.mysql索引类型?

在这里插入图片描述

35.MySQL分库分表

36.索引失效的情况

失效的第1种情况:
	select * from emp where ename like '%T';

	ename上即使添加了索引,也不会走索引,为什么?
		原因是因为模糊匹配当中以“%”开头了!
		尽量避免模糊查询的时候以“%”开始。
		这是一种优化的手段/策略。

	mysql> explain select * from emp where ename like '%T';
	+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
	| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra       |
	+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
	|  1 | SIMPLE      | emp   | ALL  | NULL          | NULL | NULL    | NULL |   14 | Using where |
	+----+-------------+-------+------+---------------+------+---------+------+------+-------------+

失效的第2种情况:
	使用or的时候会失效,如果使用or那么要求or两边的条件字段都要有
	索引,才会走索引,如果其中一边有一个字段没有索引,那么另一个
	字段上的索引也会失效。所以这就是为什么不建议使用or的原因。

	mysql> explain select * from emp where ename = 'KING' or job = 'MANAGER';
	+----+-------------+-------+------+-----------------+------+---------+------+------+-------------+
	| id | select_type | table | type | possible_keys   | key  | key_len | ref  | rows | Extra       |
	+----+-------------+-------+------+-----------------+------+---------+------+------+-------------+
	|  1 | SIMPLE      | emp   | ALL  | emp_ename_index | NULL | NULL    | NULL |   14 | Using where |
	+----+-------------+-------+------+-----------------+------+---------+------+------+-------------+

失效的第3种情况:
	使用复合索引的时候,没有使用左侧的列查找,索引失效
	什么是复合索引?
		两个字段,或者更多的字段联合起来添加一个索引,叫做复合索引。
	
	create index emp_job_sal_index on emp(job,sal);
	
	mysql> explain select * from emp where job = 'MANAGER';
	+----+-------------+-------+------+-------------------+-------------------+---------+-------+------+-------------+
	| id | select_type | table | type | possible_keys     | key               | key_len | ref   | rows | Extra       |
	+----+-------------+-------+------+-------------------+-------------------+---------+-------+------+-------------+
	|  1 | SIMPLE      | emp   | ref  | emp_job_sal_index | emp_job_sal_index | 30      | const |    3 | Using where |
	+----+-------------+-------+------+-------------------+-------------------+---------+-------+------+-------------+
	
	mysql> explain select * from emp where sal = 800;
	+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
	| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra       |
	+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
	|  1 | SIMPLE      | emp   | ALL  | NULL          | NULL | NULL    | NULL |   14 | Using where |
	+----+-------------+-------+------+---------------+------+---------+------+------+-------------+

失效的第4种情况:
	在where当中索引列参加了运算,索引失效。
	mysql> create index emp_sal_index on emp(sal);

	explain select * from emp where sal = 800;
	+----+-------------+-------+------+---------------+---------------+---------+-------+------+-------------+
	| id | select_type | table | type | possible_keys | key           | key_len | ref   | rows | Extra       |
	+----+-------------+-------+------+---------------+---------------+---------+-------+------+-------------+
	|  1 | SIMPLE      | emp   | ref  | emp_sal_index | emp_sal_index | 9       | const |    1 | Using where |
	+----+-------------+-------+------+---------------+---------------+---------+-------+------+-------------+

	mysql> explain select * from emp where sal+1 = 800;
	+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
	| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra       |
	+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
	|  1 | SIMPLE      | emp   | ALL  | NULL          | NULL | NULL    | NULL |   14 | Using where |
	+----+-------------+-------+------+---------------+------+---------+------+------+-------------+

失效的第5种情况:
	在where当中索引列使用了函数
	explain select * from emp where lower(ename) = 'smith';
	+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
	| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra       |
	+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
	|  1 | SIMPLE      | emp   | ALL  | NULL          | NULL | NULL    | NULL |   14 | Using where |
	+----+-------------+-------+------+---------------+------+---------+------+------+-------------+

37.binlog、redo log、undo log

MySQL InnoDB 引擎使用 redo log(重做日志) 保证事务的持久性,使用 undo log(回滚日志) 来保证事务的原子性。

MySQL数据库的数据备份、主备、主主、主从都离不开binlog,需要依靠binlog来同步数据,保证数据一致性。

38.redis 是单线程还是多线程?

redis内部使用文件事件处理器,是单线程的,采用IO多路复用机制同时监听多个socket,根据socket上的事件来选择对应的事件处理器来处理。
redis6.0后引入多线程主要是提高网络IO读写性能(redis性能瓶颈主要受限于内存和网络)。多线程部分只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程顺序执行的。

单线程快的原因:
(1)纯内存操作
(2)核心是基于非阻塞的IO多路复用机制
(3) 单线程反而避免了多线程的频繁上下文切换带来的性能问题

39.redis 持久化(RDB和AOF)

RDB在这里插入图片描述
AOF
在这里插入图片描述

RDB和AOF持久化的区别
在这里插入图片描述

40. 缓存和数据库双写时的数据一致性?

延时双删
(1)先淘汰缓存
(2)再写数据库
(3)休眠1秒,再次淘汰缓存
在这里插入图片描述

先更新数据库,后删除缓存
(1)缓存刚好失效
(2)请求A查询数据库,得一个旧值
(3)请求B将新值写入数据库
(4)请求B删除缓存
(5)请求A将查到的旧值写入缓存
在这里插入图片描述
然而,发生这种情况的概率又有多少呢?
发生上述情况有一个先天性条件,就是步骤(3)的写数据库比步骤(2)的读数据库操作耗更短才有可能使得步骤(4)先于步骤(5)。可是,大家想想,数据库的读操作速度远大于写操作的(不然做读写分离干嘛,做读写分离的意义就是因为读操作比较快,耗资源少),因此步骤(3)耗时比步骤(2)更短,这一情形很难出现。
一定要解决怎么办?如何解决上述并发问题?


首先,给缓存设有效时间是一种方案。其次,采用异步延时删除策略。
更新数据库成功,但是在删除缓存的阶段出错了没有删除成功怎么办?
(1)利用消息队列进行删除的补偿
在这里插入图片描述
但是这个方案会有一个缺点就是会对业务代码造成大量的侵入
深深的耦合在一起,所以这时会有一个优化的方案,我们知道对MysqL数据库更新操作后再binlog日志中我们都能够找到相应的操作,那么我们可以订阅Mysql数据库的 binlog日志对缓存进行操作。

(2)订阅Mysql数据库的binlog日志对缓存进行操作
在这里插入图片描述

41. 缓存雪崩、缓存穿透、缓存击穿

缓存雪崩是指缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。
解决方案:

  • 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。热点数据缓存永不失效。
  • 给每一个缓存数据增加相应的缓存标记,记录缓存是否失效,如果缓存标记失效,则更新数据缓存。
  • 缓存预热
  • 互斥锁
  • 采用redis集群
  • 限流

缓存穿透是指缓存和数据库中都没有的数据,大量请求的key根本不存在于缓存中,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。
在这里插入图片描述
解决方案
(1)首先做好参数校验,一些不合法的参数请求直接抛出异常信息返回客户端
(2)将无效的key缓存下来,缓存key-null,设置过期时间尽量短
(3)布隆过滤器
采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap 中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力
在这里插入图片描述
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。和缓存雪崩不同的是缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

42.redis的主从复制

1、从节点执行slaveof masterlp port,保存主节点信息
2、从节点中的定时任务发现主节点信息,建立和主节点的socket连接
3、从节点发送信号,主节点返回,两边能互相通信
4、连接建立后,主节点将所有数据发送给从节点(数据同步)(全量复制)
5、主节点把当前的数据同步给从节点后,便完成了复制过程。接下来,主节点就会持续的把写命令发送给从节点,保证主从数据一致性。(增量复制)

在这里插入图片描述
在这里插入图片描述
全量复制:
(1)主节点通过bgsave命令fork子进程进行RDB持久化,该过程是非常消耗CPU、内存(页表复制)、硬
盘IO的
(2)主节点通过网络将RDB文件发送给从节点,对主从节点的带宽都会带来很大的消耗
(3)从节点清空老数据、载入新RDB文件的过程是阻塞的,无法响应客户端的命令;如果从节点执行bgrewriteaof,重写AOF也会带来额外的消耗
增量复制:

  • 复制偏移量:执行复制的双方,主从节点,分别会维护一个复制偏移量offset
  • 复制积压缓冲区:主节点内部维护了一个固定长度的、先进先出(FIFO)队列 作为复制积压缓冲区,当主从节点offset的差距过大超过缓冲区长度时,将无法执行部分复制,只能执行全量复制。
  • 服务器运行ID(runid):每个Redis节点,都有其运行ID,运行ID由节点在启动时自动生成,主节点会将自己的运行ID发送给从节点,从节点会将主节点的运行ID存起来。 从节点Redis断开重连的时候,就是根据运行ID来判断同步的进度:
    1.如果从节点保存的runid与主节点现在的runid相同,说明主从节点之前同步过,主节点会继续尝试使用部分复制(到底能不能部分复制还要看offset和复制积压缓冲区的情况);
    2.如果从节点保存的runid与主节点现在的runid不同,说明从节点在断线前同步的Redis节点并不是当前的主节点,只能进行全量复制。

42.Redis主从复制的核心原理

Redis的主从复制是提⾼Redis的可靠性的有效措施,主从复制的流程如下:

  1. 集群启动时,主从库间会先建⽴连接,为全量复制做准备(从库设置slave of 127.0.0.1:6379)发送psync{runid}{offset}=》psync ?-1
  2. runid: redis实例唯一随机ID,redis每次启动的时候都会有一个随机的ID,作为一个标识
  3. offset偏移量: 数据写入量的字节
    比如主执行set hello world,就会有一个偏移量,然后从同步数据,也会记录一个偏移量,当两个偏移量达到一致时候,实际上数据就是完全同步的状态。
  4. 主库通过bgsave命令生成RDB快照文件,将所有数据同步给从库。从库收到数据后,在本地完成数据加载,这个过程依赖于内存快照RDB
  5. 在主库将数据同步给从库的过程中,主库不会阻塞,仍然可以正常接收请求。否则,redis的服务就被中断了。但是,这些请求中的写操作并没有记录到刚刚⽣成的RDB⽂件中。为了保证主从库的数据⼀致性,主库会在内存中⽤专⻔的replication buffer,记录RDB⽂件⽣成时收到的所有写操作。
  6. 最后,也就是第三个阶段,主库会把第⼆阶段执⾏过程中新收到的写命令,再发送给从库。具体的操作是,当主库完成RDB⽂件发送后,就会把此时replocation buffer中修改操作发送给从库,从库再执⾏这些操作。这样⼀来,主从库就实现同步了
  7. 后续主库和从库都可以处理客户端读操作,写操作只能交给主库处理,主库接收到写操作后,还会将写操作发送给从库,实现增量同步
    在这里插入图片描述

42.Redis哨兵机制

sentinel(哨兵)作用:
监控:同步信息
通知(提醒):保持联通
自动故障转移:发现问题 竞选负责人 选择新master
故障的自动检测,主节点的自动选举(主从切换),完成故障转移
在这里插入图片描述
sentinel 会以每秒一次的频率向所有节点(其他sentinel、主节点、以及从节点)发送 ping 消息,然后通过接收返回判断该节点是否下线;如果在配置指定 down-after-milliseconds 时间内,sentinel收到的都是无效回复, 则被判断为主观下线;
在这里插入图片描述
当一个 sentinel 节点将一个主节点判断为主观下线之后,为了确认这个主节点是否真的下线,它会向其他sentinel 节点进行询问,如果收到一定数量的已下线回复,sentinel 会将主节点判定为客观下线,并通过领头 sentinel 节点对主节点执行故障转移;

基于Raft算法选举领头sentinel:
(1)判断客观下线的sentinel节点向其他 sentinel 节点发送 SENTINEL is-master-down-by-addr ip port current_epoch runid

注意:这时的runid是自己的run id,每个sentinel节点都有一个自己运行时id

(2)目标sentinel回复是否同意master下线并选举领头sentinel,选择领头sentinel的过程符合先到先得的原则。举例:sentinel1判断了客观下线,向sentinel2发送了第一步中的命令,sentinel2回复了sentinel1,说选你为领头,这时候sentinel3也向sentinel2发送第一步的命令,sentinel2会直接拒绝回复

(3)当sentinel发现选自己的节点个数超过 majority 的个数的时候,自己就是领头节点

(4)如果没有一个sentinel达到了majority的数量,等一段时间,重新选举

哨兵模式当中涉及多个选举流程采用的是 Raft 算法的领头选举方法的实现;
在这里插入图片描述
剔除列表中已经下线的从服务
剔除有5s没有回复sentinel的info命令的slave
剔除与已经下线的主服务连接断开时间超过 down-after-milliseconds * 10 + master宕机时长 的slaver
在这里插入图片描述

新的master节点选择出来之后,还需要做一些事情配置的修改,如下:
(1)领头sentinel会对选出来的从节点执行slaveof no one 命令让其成为主节点
(2)领头sentinel 向别的slave发送slaveof命令,告诉他们新的master是谁谁谁,你们向这个master复制数据
(3)如果之前的master重新上线时,领头sentinel同样会给起发送slaveof命令,将其变成从节点

43.分布式缓存寻址算法

在这里插入图片描述

44.redis的过期键的删除策略

惰性删除: 只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。

定期删除: 每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。

45.redis内存淘汰机制

46.redis分布式锁底层是如何实现的?

  1. ⾸先利⽤setnx来保证:如果key不存在才能获取到锁,如果key存在,则获取不到锁
  2. 然后还要利⽤lua脚本来保证多个redis操作的原⼦性
  3. 同时还要考虑到锁过期,所以需要额外的⼀个看⻔狗定时任务来监听锁是否需要续约
  4. 同时还要考虑到redis节点挂掉后的情况,所以需要采⽤红锁的⽅式来同时向N/2+1个节点申请锁,
    都申请到了才证明获取锁成功,这样就算其中某个redis节点挂掉了,锁也不能被其他客户端获取到
    在这里插入图片描述

46.redis hash扩容机制(渐进式hash)

rehash原理

字典初始化,在redis中字典中的hash表也是采用延迟初始化策略,在创建字典的时候并没有为哈希表分配内存,只有当第一次插入数据时,才真正分配内存。看看字典创建函数dictCreate

随着操作的不断进行,哈希表保存的键值对会逐渐增多或减少,为了让哈希表负载因子维持在一个合理范围之内,当哈希表保存的键值对太多或太少时,程序要对哈希表的大小进行相应的扩展或收缩。

Redis对字典的哈希表执行rehash的步骤如下:

1、为字典的ht[1]哈希表分配空间,这个空间大小取决于要执行的操作:

如果执行的是扩展操作,则ht[1]的大小为第一个大于等于等于ht[0].used*2的2^n;

rehash后新生成的dictEntry节点数组大小等于超过当前key个数向上求整的2的n次方,比如当前key个数为100,则新生成的节点数组大小就是128

2、如果执行的收缩操作,则ht[1]的大小为第一个大于等于ht[0].used的2^n;

将保存在ht[0]中的所有键值对rehash到ht[1]上面:rehash指的是重新计算键的哈希值和索引值,然后将键值对放置到ht[1]的指定位置上。

3、当ht[0]包含的所有键值对都迁移到ht[1]之后,释放ht[0],将ht[1]设置为ht[0],并在ht[1]新创建一个空白哈希表,为下一次rehash做准备。

渐进式rehash

Redis中的rehash动作并不是一次性、集中式完成的,而是分多次、渐进式的完成的。

这样做的目的是,如果服务器中包含很多键值对,要一次性的将这些键值对全部rehash到ht[1]的话,庞大的计算量可能导致服务器在一段时间内停止服务于。

为了避免这种影响,Redis采用了渐进式Redis:

1、为ht[1]分配空间,让字典同时持有ht[0]和ht[1]两个哈希表。

2、在字典中维持一个索引计数器变量rehashidx,并将它置为0,表示rehash工作开始。

3、在rehash进行期间,每次对字典执行添加、删除、查找或者更新操作时,程序除了执行指定操作以外,还会顺带将ht[0]哈希表在rehashidx索引上的所有键值对rehash到ht[1]中,当rehash工作完成之后,程序将rehashidx属性的值+1。

4、随着字典操作的不断进行,最终在某个时间点上,ht[0]的所有键值对都被rehash到ht[1]上,这时将rehashidx属性设为-1,表示rehash完成。

渐进式rehash 的好处在于其采取分而治之的方式,将rehash键值对所需要的计算工作均摊到字典的每个添加、删除、查找和更新操作上,从而避免了集中式rehash而带来的庞大计算量。

46.转发和重定向的区别

1、请求转发是服务器行为、重定向是客户端浏览器行为
2、请求转发是request对象调用方法、重定向是response对象调用方法
3、请求转发只有一次请求所以可以实现request域对象中的数据共享,而重定向是多次请求、多次响应
4、请求转发的效率要高于重定向
5、请求转发url地址栏不变,而重定向会发生变化
6、既然请求转发是服务器内部的行为,所以只能访问服务器内部资源!而重定向既然是浏览器行为,地址栏会变,所以可以访问服务器外部资源!
7、不与视图解析器一同使用。重定向本质是两次访问,不能访问WEB-INF目录下的资源
如果既想要实现路径跳转,又需要实现数据共享,使用请求转发!
如果只是纯粹的想要实现路径跳转,我们可以使用重定向

47.拦截器和过滤器的区别

1.过滤器是servlet中的对象, 拦截器是框架中的对象
2.过滤器实现Filter接口的对象, 拦截器是实现HandlerInterceptor
3.过滤器是用来设置request,response的参数,属性的,侧重对数据过滤的。拦截器是用来验证请求的,能截断请求。
4.过滤器是在拦截器之前先执行的。
5.过滤器是tomcat服务器创建的对象
拦截器是springmvc容器中创建的对象
6.过滤器是一个执行时间点。
拦截器有三个执行时间点
7.过滤器可以处理jsp,js,html等等
拦截器是侧重拦截对Controller的对象。 如果你的请求不能被DispatcherServlet接收, 这个请求不会执行拦截器内容
8.拦截器拦截普通类方法执行,过滤器过滤servlet请求响应

48.cookie和session的区别

1.cookie数据保存在客户端,session数据保存在服务器端。
2.如果浏览器禁用了cookie. session机制会随之失效
3.session安全性高于cookie
4.有效期不同,Cookie 可设置为长时间保持,比如我们经常使用的默认登录功能,Session 一般失效时间较短,客户端关闭或者 Session 超时都会失效。
5.存储大小不同, 单个 Cookie 保存的数据不能超过 4K,Session 可存储数据远高于 Cookie。

48.get和post的区别

1.GET在浏览器回退时是无害的,而POST会再次提交请求。
2.GET产生的URL地址可以被Bookmark,而POST不可以。
3.GET请求会被浏览器主动cache,而POST不会,除非手动设置。
4.GET请求只能进行url编码,而POST支持多种编码方式。
5.GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。
6.GET请求在URL中传送的参数是有长度限制的,而POST没有。
7.对参数的数据类型,GET只接受ASCII字符,而POST没有限制,也允许二进制数据。
8.GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。
9.GET参数通过URL传递,POST参数放在Request body中。
10.GET产生一个TCP数据包,POST产生两个TCP数据包

49.正则表达式

正则表达式:
			     1,语言,语法:定义字符串的匹配模式,可以用来判断指定的具体字符串是否符合匹配模式。
			     2,语法通则:
			       1)//:在js中定义一个正则表达式.  var regExp=/...../;
			       2)^:匹配字符串的开头位置
			         $: 匹配字符串的结尾
			       3)[]:匹配指定字符集中的一位字符。 var regExp=/^[abc]$/;
			                                    var regExp=/^[a-z0-9]$/;
			       4){}:匹配次数.var regExp=/^[abc]{5}$/;
			            {m}:匹配m此
			            {m,n}:匹配m次到n次
			            {m,}:匹配m次或者更多次
			       5)特殊符号:
			         \d:匹配一位数字,相当于[0-9]
			         \D:匹配一位非数字
			         \w:匹配所有字符,包括字母、数字、下划线。
			         \W:匹配非字符,除了字母、数字、下划线之外的字符。

			         *:匹配0次或者多次,相当于{0,}
			         +:匹配1次或者多次,相当于{1,}
			         ?:匹配0次或者1次,相当于{0,1}

50. & 和&&、|和||的区别

在这里插入图片描述

51. 接口和抽象类的区别

52. 重写和重载的区别

53. BIO、NIO、AIO分别是什么

  1. BIO(Blocking I/O):同步阻塞IO,使⽤BIO读取数据时,线程会阻塞住,并且需要线程主动去查询是否有数据可
    读,并且需要处理完⼀个Socket之后才能处理下⼀个Socket
  2. NIO(Non-blocking/New I/O):同步⾮阻塞IO,使⽤NIO读取数据时,线程不会阻塞,但需要线程主动的去查询是否有IO事件
  3. AIO(Asynchronous I/O):也叫做NIO 2.0,异步⾮阻塞IO,使⽤AIO读取数据时,线程不会阻塞,并且当有数据可读时会通知给线程,不需要线程主动去查询
    在这里插入图片描述

54. select、poll、epoll

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值