自学内容网 自学内容网

面试题总结

一、mysql中的乐观锁、悲观锁、共享锁、排它锁、行锁、表锁
1、乐观锁
通过sql实现的,更新sql语句时加上where version = #{version}乐观锁不是数据库自带的锁,需要我们自己去实现。乐观锁是指操作数据库时(更新操作),想法很乐观,认为这次的操作不会导致冲突,在操作数据时,并不进行任何其他的特殊处理(也就是不加锁),而在进行更新后,再去判断是否有冲突。在表中的数据进行操作时(更新),先给数据表加一个版本(version)字段,每操作一次,将那条记录的版本号加1。也就是先查询出那条记录,获取到version字段值,如果要对那条记录进行操作(更新),则先判断此刻version的值是否与刚查出来的version的值相等,如果相等,则说明这段期间,没有其他程序对其进行操作,则可以执行更新,并将version字段的值加1;如果更新时发现此刻的version值与刚刚获取出来的version值不相等,则说明这段期间已经有其他程序对其进行操作了,则不进行更新操作。
2、悲观锁(可以是表锁,也可以是行锁)
悲观锁就是在操作数据时,认为此操作会出现数据冲突,所以在进行每次操作时都要通过获取锁才能进行对数据的更新操作,这点跟java中的synchronized很相似,所以悲观锁需要耗费较多的时间。另外与乐观锁相对应的,悲观锁是由数据库自己实现的,要用的时候,我们直接调用数据库的相关语句就可以了。
共享锁:指的是对于多个不同的事务,对同一个资源共享同一把锁(都得有这把锁)
lock in share mode
排他锁:指对于多个不同的事务,对同一个资源只能有一把锁
for update
3、行锁:给某一行加上锁,也就是给一条记录加上锁。
4、表锁:给整个表加上锁

二、mysql事务的四大特性
1、原子性(Atomicity):事务包含的所有操作要么全部成功,要么全部失败回滚。失败回滚的操作事务,将不能对事务有任何影响。
2、一致性(Consistency):一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
3、隔离性(Isolation):隔离性是指当多个用户并发访问数据库时,比如同时访问一张表,数据库每一个用户开启的事务,不能被其他事务所做的操作干扰,多个并发事务之间,应当相互隔离。
4、持久性(Durability):持久性是指事务的操作,一旦提交(commit),对于数据库中数据的改变是永久性的,即使数据库发生故障也不能丢失已提交事务所完成的改变。

三、mysql隔离级别说明
1、REPEATABLE READ Repeatable Read 可重复读
MySQL数据库默认的隔离级别。该级别解决了READ UNCOMMITTED隔离级别导致的问题。它保证同一事务的多个实例在并发读取事务时,会“看到同样的”数据行。不过,这会导致另外一个棘手问题“幻读”。InnoDB和Falcon存储引擎通过多版本并发控制机制解决了幻读问题。
幻读(Phantom Read)‌是指在数据库事务处理中,当一个事务在执行过程中,由于其他事务的插入或删除操作,导致该事务两次执行相同的查询时返回的结果集不一致的现象。幻读破坏了事务的独立性,使得事务在执行过程中无法得到一致的数据视图。‌
2、READ COMMITTED Read Committed 读取提交内容(读已提交)
大多数数据库系统的默认隔离级别(但不是MySQL的默认隔离级别),满足了隔离的早先简单定义:一个事务开始时,只能“看见”已经提交事务所做的改变,一个事务从开始到提交前,所做的任何数据改变都是不可见的,除非已经提交。这种隔离级别也支持所谓的“不可重复读”。这意味着用户运行同一个语句两次,看到的结果是不同的。
3、READ UNCOMMITTED Read UnCommitted 读取未提交内容(读未提交)
在这个隔离级别,所有事务都可以“看到”未提交事务的执行结果。在这种级别上,可能会产生很多问题,除非用户真的知道自己在做什么,并有很好的理由选择这样做。本隔离级别很少用于实际应用,因为它的性能也不比其他性能好多少,而别的级别还有其他更多的优点。读取未提交数据,也被称为“脏读”
4、SERIALIZABLE Serializable 可串行化
该级别是最高级别的隔离级别。它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简而言之,SERIALIZABLE是在每个读的数据行上加锁。在这个级别,可能导致大量的超时Timeout和锁竞争Lock Contention现象,实际应用中很少使用到这个级别,但如果用户的应用为了数据的稳定性,
需要强制减少并发的话,也可以选择这种隔离级

四、mysql的执行计划,可以使用explain关键字优化sql语句,通过key查看创建的索引是否生效。
1、select_type:查询类型
2、table:查询数据所在的表
3、type:访问类型,是判断sql执行性能比较关键的一个字段,性能从高到低依次是:
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
system:表示只有一条数据,类似于系统表,是const的一种特例
const:表示通过索引一次就查询到数据,比较块,用于primary key(主键)和unique唯一索引
4、possible_key:可能使用的索引
5、key:实际使用的索引,如果为null,表示没有用到索引
6、key_len:表示索引中使用的字节数,可以根据这个字段计算查询中索引使用的长度,长度越短越好。
7、ref:表示索引的哪一列被使用
8、rows:根据表统计信息和索引选用情况,大致计算出查询到所需数据需要查询的行数
9、Extra:using where:表示使用了where过滤,using index:表示使用了覆盖索引,避免使用表数据,using join buffer:表示使用了链接缓存

五、mysql索引失效
1、最佳左前缀法则:使用联合索引,最左边的一定要有索引,且中间不能中断
联合索引如果左边的索引没用,不会全部失效,而是部分失效‌。联合索引的最左匹配原则意味着查询时必须从联合索引的最左边开始匹配,如果最左边的索引没有被使用,则整个联合索引将不会生效。然而,如果最左边的索引被使用,但中间的某个索引没有被使用,那么该索引后面的字段将无法利用索引进行查询,但前面的索引部分仍然有效‌

2、不要在索引列上做任何操作(计算、函数、自动/手动类型转换),不然会导致索引失效而转向全表扫描
3、索引字段上使用(!= 或者 < >)判断时,会导致索引失效而转向全表扫描
4、索引字段上使用 is null / is not null 判断时,会导致索引失效而转向全表扫描
5、索引字段使用 or 时,会导致索引失效而转向全表扫描
6、索引字段是字符串时,但查询时不加单引号,会导致索引失效而转向全表扫描
7、索引字段使用like以通配符开头(‘%字符串’)时,会导致索引失效而转向全表扫描

六、redis常见的几种数据类型
1、String 字符串类型 String:String (value也是String)
2、Hash (哈希)String:key1:value1 key2:value2
3、List (链表) String:[value1 value2 value3] 可以重复(value是单个值的集合)
4、Set (集合) String:[value1 value2 value3] 不可以重复(value是单个值的集合)
5、zset (有序集合)String:[排序1 value1 排序2 value2](value是排好序的值的集合)

七、redis常见命令
keys *
del(key)
set
get
expire
ttl
flushdb
setnx(key, value):添加string,名称为key,值为value(没有时才设置)
分布式锁
hset
hget
lpush
lpop
rpop
sadd
smembers:获取所有元素
zadd

八、
mybatis中#和$的区别
#自带双引号,占位符,防止sql注入
$不带双引号,表名拼接

九、缓存穿透、缓存击穿、缓存雪崩
缓存穿透:缓存穿透是指缓存和数据库中都没有数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。解决方案:接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
缓存击穿:缓存击穿是指缓存中没有但数据库中有数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力(在缓存中设置热点数据永不过期)
缓存雪崩:缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。(不同的过期时间)

十、单例模式

public class SingletonInner {  
    private static volatile SingletonInner sInst = null;  // <<< 这里添加了 volatile  
    /** 
     * 私有的构造函数 
     */  
    private SingletonInner() {}  
  
    public static SingletonInner getInstance() {  
        if (inst == null) {
            synchronized (SingletonInner.class) {if (inst == null) {
                    sInst = new SingletonInner();
                }
            }
        }
        return sInst;  
    }
    protected void method() {  
        System.out.println("SingletonInner");  
    }  
}

十一、Java当中的线程池
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个可定期或者延时执行任务的定长线程池,支持定时及周期性任务执行。
newCachedThreadPool 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
四种线程池本质都是创建ThreadPoolExecutor类,ThreadPoolExecutor构造参数如下
int corePoolSize, 核心线程大小
int maximumPoolSize,最大线程大小
long keepAliveTime, 超过corePoolSize的线程多久不活动被销毁时间
TimeUnit unit,时间单位
BlockingQueue workQueue 任务队列
ThreadFactory threadFactory 线程池工厂
RejectedExecutionHandler handler 拒绝策略
‌AbortPolicy‌:这是默认的拒绝策略。当任务无法被线程池执行时,会抛RejectedExecutionException异常‌。
‌CallerRunsPolicy‌:当任务无法被线程池执行时,会直接在调用者线程中运行该任务。如果调用者线程正在执行一个任务,则会创建一个新线程来执行被拒绝的任务‌。
‌DiscardPolicy‌:当任务无法被线程池执行时,任务将被直接丢弃,不抛出异常也不执行任务‌。
‌DiscardOldestPolicy‌:当任务无法被线程池执行时,线程池会丢弃队列中最旧的任务,然后尝试再次提交当前任务‌。

十二、es的数据结构
倒排索引:根据关键字去找id,根据id去找对应的文档
Elasticsearch(ES)的数据结构是一个分布式文档存储,它使用JSON文档来存储数据。每个文档都被索引到一个索引中,这是一个极大的文档集合。类似于关系数据库中的表。
在Elasticsearch中,数据结构可以表示为:
索引(Index):一个索引就是一个文档的容器,类似于SQL中的数据库概念。
类型(Type):在索引内,可以定义一个或多个类型。类型是索引中的一个逻辑类分区,用于区分同一索引中的不同文档结构。
文档(Document):文档是可以被索引的基本信息单元,它是JSON对象,在Elasticsearch中,每个文档都有一个特定的ID。

十三、kafka
消息中间件,用于服务之间传递数据。
解耦、异步、削峰
zk做注册中心
Topic
Partition
Ack反馈机制
回调函数

十四、ThreadLocal
用于不同的线程存储数据类
Thread类中有一个成员变量,ThreadLocal.ThreadLocalMap
key是ThreadLocal对象,value值
使用:创建ThreadLocal对象,在不同的线程里面set和get值就可以。

十五、内存溢出和内存泄露
内存溢出:内存不够用。oom异常
内存泄露:指的是在内存中创建的对象已经没有引用了,但是GC没有回收,逐渐积累内存溢出。

十六、
面试基本就是背诵面试题

十七、SpringCloud的组件
eureka、feign、hystrix、ribbon、zuul、config

十八、存储引擎 MyISAM与InnoDB(默认) 的区别
1、InnoDB支持事务,MyISAM不支持
2、 InnoDB支持外键,MyISAM不支持。
3、InnoDB是聚集索引,使用B+Tree作为索引结构,数据文件是和(主键)索引绑在一起的(表数据文件本身就是按B+Tree组织的一个索引结构),必须要有主键,通过主键索引效率很高。但是辅助索引需要两次查询,先查询到主键,然后再通过主键查询到数据。因此,主键不应该过大,因为主键太大,其他索引也都会很大。
MyISAM是非聚集索引,也是使用B+Tree作为索引结构,索引和数据文件是分离的,索引保存的是数据文件的指针。主键索引和辅助索引是独立的。也就是说:InnoDB的B+树主键索引的叶子节点就是数据文件,辅助索引的叶子节点是主键的值;而MyISAM的B+树主键索引和辅助索引的叶子节点都是数据文件的地址指针。
4、InnoDB不保存表的具体行数,执行select count(*) from table时需要全表扫描。而MyISAM用一个变量保存了整个表的行数,执行上述语句时只需要读出该变量即可,速度很快(注意不能加有任何WHERE条件)。
5、InnoDB支持表、行(默认)级锁,而MyISAM支持表级锁。

十九、redis集群
1、主从模式,主节点提供写操作,从节点提供读操作,读写分离。
2、哨兵模式,哨兵模式的核心还是主从复制。只不过相对于主从模式在主节点宕机导致不可写的情况下,多了一个竞选机制——从所有的从节点竞选出新的主节点。竞选机制的实现,是依赖于在系统中启动一个sentinel进程。
3、Redis集群,Redis 集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽.集群的每个节点负责一部分hash槽,举个例子,比如当前集群有3个节点,那么:节点 A 包含 0 到 5500号哈希槽.节点 B 包含5501 到 11000 号哈希槽.节点 C 包含11001 到 16384号哈希槽.

二十、线程不安全原因
当多个线程同时修改同一个变量的时候,很容易产生线程的不安全。
导致线程不安全的所有原因中,最根本的原因是——抢占式执行。因为CPU字在进行线程调度的时候,是随机调度的,而且这是无法避免的一种原因。
栈是线程私有的,堆是线程共享的

二十一、springboot的自动配置
SpringBoot的自动配置就是当spring容器启动后,一些自动配置类(只是自动配置类,并不是当前组件配置到IOC容器当中,自动配置类通过@Conditional注解来按需配置)就自动装配到IOC容器中,不需要我们手动去注入,从而简化了开发,省去了繁琐的配置。一个SpringBoot工程想要成功运行,就必须拥有一个主程序类(被@SpringBootApplication 注解标识),而自动配置的相关工作就在@SpringBootApplication这个注解上。当我们进去到 @SpringBootApplication 注解的源码当中,可以发现它是一个复合注解,它是由 @SpringBootConfiguration + @EnableAutoConfiguration + @ComponentScan 这三个注解组成,在@SpringBootConfiguration 源码中可以发现有 @Configuration,代表是一个配置类,说明主程序类也是一个配置类,@ComponentScan指定扫描哪些组件,默认是扫描主程序所在的包以及其子包。
@EnableAutoConfiguration
@AutoConfigurationPackage将指定的一个包下的所有组件导入到容器当中。
自动配置包
在@AutoConfigurationPackage 注解中存在一个 @Import({Registrar.class}) 注解,自动配置包就是通过这个 Registrar 类的方法来完成的。由于@AutoConfigurationPackage 是在 @EnableAutoConfiguration 中,所以@AutoConfigurationPackage 是标识在主程序类上,所以 metadata为主程序类在Registrar中的 registerBeanDefinitions 方法,首先先获取到注解所标识的类,然后将这个类所在的包以及子包的名称放入到一个String数组当中,再将该String数组中的包的所有组件导入到容器当中,该注解的大概流程就是:将 spring-boot-autoconfigure-x.x.x.jar包中 META-INF/spring.factories文件中的文件进行加载。
不仅仅是spring-boot-autoconfigure-x.x.x.jar的文件,还有三方自动配置jar包中的文件

二十二、redis的删除策略
过期的数据真的删除了吗?其实并没有立即删除,而是懒删除。
定时删除。创建一个定时器,当key设置有过期时间,且过期时间到达时,由定时器任务立即执行对键的删除操作。
惰性删除。 数据到达过期时间,不做处理。等下次访问该数据时,如果未过期,返回数据。发现已过期,删除,返回不存在。
定期删除。周期性轮询redis库中的时效性数据,采用随机抽取的策略,利用过期数据占比的方式控制删除频度。

二十二、redis的持久化
RDB(Redis DataBase):RDB 是 Redis 的一种快照方式的持久化方法。它定期将 Redis 内存中的数据保存到磁盘上的一个二进制文件。当 Redis 重新启动时,可以加载这个 RDB 文件来恢复之前保存的数据状态。
AOF(Append Only File):AOF 是一种追加日志方式的持久化方法。它记录每次写操作(如 SET、INCR 等)到一个日志文件中。当 Redis 重新启动时,会重新执行这些写操作来恢复数据集的原始状态。当 Redis 重新启动时,可以加载这个 RDB 文件来恢复之前保存的数据状态。
‌AOF(Append Only File)比RDB(Redis DataBase)更安全‌。AOF通过记录所有写操作并追加到文件中,即使在持久化过程中出现意外,也可以通过重放AOF文件中的指令进行数据恢复,因此数据安全性更高‌。

二十三、jvm的组成
新生代与老年代的比例可以通过参数来配置,一般使用-XX:NewRatio参数来设置。该参数的值表示老年代与新生代的比例,例如-XX:NewRatio=2表示新生代和老年代的比例为1:2。 新生代主要用于存放短期生命周期的对象,因为大部分对象在创建之后很快就会被销毁。JVM的默认比例是新生代占整个堆内存的1/3,老年代占2/3。这个默认比例适用于大多数应用程序,但并不是适用于所有情况。
JVM初始分配的内存由-Xms指定,默认是物理内存的1/64;JVM最大分配的内存由-Xmx指 定,默认是物理内存的1/4。默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制。因此服务器一般设置-Xms、-Xmx相等以避免在每次GC 后调整堆的大小。
对象的堆内存被称为垃圾回收器的自动内存管理系统回收。堆中内存区域按分代收集算法分为老年代和年轻代,老年代和年轻代的比值默认为2,即老年代Old区占堆内存的2/3,年轻代占1/3。年轻代中又分为Eden区和两个Survivor区,分别占内存比为8:1:1。 -Xmn可以设置年轻代内存大小。伊甸园区(Eden区)占80%,两个幸存区(Survivor区)各占10%。这种划分的原因是为了解决内存碎片的问题,通过复制算法来减少碎片的产生‌。

二十四、jvm调优
JVM调优的主要目标是确保JVM在执行Java应用时能够高效地使用系统资源,提高应用的响应速度和吞吐量,减少系统暂停时间,避免内存溢出和内存泄漏等问题。
常见的调优方法包括调整堆内存大小、选择合适的‌垃圾回收器、优化‌线程堆栈大小等。
调整JVM的堆内存可以帮助避免内存溢出,提高垃圾回收的效率。可以通过-Xms和-Xmx参数设置JVM的初始堆大小和最大堆大小例如,设置初始堆大小为256MB,最大堆大小为1024MB的命令如下:
java -Xms256m -Xmx1024m -jar your-application.jar
选择合适的垃圾回收器能够提高应用的响应速度和吞吐量。例如,使用G1垃圾回收器的命令如下:
java -XX:+UseG1GC -jar your-application.jar
工具:jstat,jmap、‌jconsole

常用命令和参数
1、jps:查看java进程及其相关信息
2、jmap:用来生成dump文件和查看堆相关的各类信息的命令。
3、jstat:查看jvm运行时的状态信息,用于监视虚拟机运行时状态信息的命令,它可以显示出虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。
4、jstack:查看jvm线程快照的命令
5、jinfo:查看jvm参数和动态修改部分jvm参数
常用参数:
1、-Xms3550m:设置初始化堆内存大小为3550M
2、-Xmx3550m:设置最大堆内存大小为3550M
3、-Xmn2g:设置新生代的内存空间大小为2G
4、-XX:SurvivorRatio=4:设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6
5、-Xss128k:设置单个线程栈大小128k,一般默认512~1024kb。
6、-XX:PermSize=128m:设置永久代初始值为128m
7、- XX:MaxPermSize=512m:设置永久代最大为512m
8、-XX:NewRatio=4 :设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5

二十五、数据结构
数组
链表
哈希表

队列
二叉树
红黑树
B树
B+树

二十六、算法
冒泡排序
选择排序
归并排序

二十七、jvm什么时候触发fullgc(垃圾收集)
1、老年代空间不足。当老年代空间不足以为新对象分配空间时,JVM会触发Full GC,以便清理老年代空间。
2、元数据区满。当JVM的元数据区(Metaspace)空间不足时,会触发Full GC,清理无用的类信息。
3、显示调用System.gc()。程序员可以通过调用System.gc()来建议JVM进行一次Full GC,但是JVM是否执行这个建议取决于使用的垃圾收集器。垃圾收集器提供的特定条件。一些垃圾收集器,如G1,会根据预定的策略定期执行Full GC。
4、JVM启动时。在JVM启动时,会进行一次Full GC来初始化垃圾收集器。
5、使用JMX或者JVM TI接口强制触发。可以通过JMX或JVM Tool Interface(JVMTI)接口来监控JVM并在需要时触发Full GC。

二十八、红黑树结构
红黑树是一种自平衡的二叉查找树,是一种高效的查找树。它是由 Rudolf Bayer 于1978年发明的,在当时被称为平衡二叉 B 树(symmetric binary B-trees)。后来,在1978年被 Leo J. Guibas 和 Robert Sedgewick 修改为如今的红黑树。红黑树具有良好的效率,它可在 O(logN) 时间内完成查找、增加、删除等操作。
红黑树是一种接近平衡的二叉树。它是具备了某些特性的二叉搜索树,能解决非平衡树问题,红黑树是一种接近平衡的二叉树(说它是接近平衡是因为它并没有像AVL树的平衡因子的概念,它只是靠着满足红黑节点的5条性质来维持一种接近平衡的结构,进而提升整体的性能,并没有严格的卡定某个平衡因子来维持绝对平衡)。
红黑树是一个二叉搜索树,它在每个节点增加了一个存储位记录节点的颜色,可以是RED,也可以是BLACK;通过任意一条从根到叶子简单路径上颜色的约束,红黑树保证最长路径不超过最短路径的二倍,因而近似平衡(最短路径就是全黑节点,最长路径就是一个红节点一个黑节点,当从根节点到叶子节点的路径上黑色节点相同时,最长路径刚好是最短路径的两倍)。
节点是红色或黑色,根是黑色,叶子节点(外部节点,空节点)都是黑色,这里的叶子节点指的是最底层的空节点(外部节点),下图中的那些null节点才是叶子节点,null节点的父节点在红黑树里不将其看作叶子节点,红色节点的子节点都是黑色,红色节点的父节点都是黑色,从根节点到叶子节点的所有路径上不能有 2 个连续的红色节点,从任一节点到叶子节点的所有路径,都包含相同数目的黑色节点

二十九、B+树结构
‌‌B树(B-tree)是一种自平衡的树数据结构,能够保持数据有序,并且能够在对数时间内完成查找、插入、删除等操作。‌ B树是一种多路平衡查找树,每个节点可以拥有多于2个子节点,与自平衡二叉查找树不同,B树为系统大块数据的读写操作做了优化,减少定位记录时所经历的中间过程,从而加快存取速度。‌B树是一种自平衡的树,能够保持数据有序。B树的每个节点可以拥有多于2个子节点,这使得B树能够在读写大量数据时更加高效。
B树的定义和特性包括:
‌阶数‌:B树的阶数用m表示,表示一个节点最多可以有m个子节点。当m=2时,B树退化为二叉查找树。
节点内容‌:每个节点包含一个关键字集合,以及指向子树的指针。
‌平衡性‌:B树是一种自平衡的树,通过一系列操作保持树的平衡。
B+树是B树的一种变体,属于平衡多路查找树。
二叉树 --> 二叉查找树 --> 平衡二叉树 --> B树 --> B+树
B+树与B树最大的区别就是数据存放方式。B树的数据存放在每个结点上,B+树的数据只存放在叶子结点。
MySQL中的结点使用的数据结构为数据页
MySQL中每一层的结点使用双向指针关联组成一个双向链表

三十、平衡二叉树
平衡二叉树也叫AVL树,它或者是一颗空树,或者具有以下性质的二叉排序树:它的左子树和右子树的高度之差(平衡因子)的绝对值不超过1,且它的左子树和右子树都是一颗平衡二叉树。插入删除数据时,通过左旋右旋来保持平衡。平衡二叉树最大的作用就是查找,AVL树的查找、插入和删除在平均和最坏情况下都是O(logn)

三十一、zk的选举机制
当Zookeeper集群启动时,每个节点都会进入LOOKING状态并给自己投票。
节点通过比较自己的‌myid和接收到的投票结果来决定是否继续等待或成为Leader。
如果某个节点的票数超过总节点数的一半,则该节点成为Leader,否则继续等待。
当Leader节点失效时,剩余的节点会立即开始新的选举过程。
每个节点都会成为‌Candidate(候选者),并与其他节点交换选票。
选举基于节点的myid和zxid(事务ID),最终票数最多的节点成为新的Leader。
(1)服务器1启动,此时只有它一台服务器启动了,它发出去的报文没有任何响应,所以它的选举状态一直是LOOKING状态。
(2)服务器2启动,它与最开始启动的服务器1进行通信,互相交换自己的选举结果,由于两者都没有历史数据,所以id值较大的服务器2胜出,但是由于没有达到超过半数以上的服务器都同意选举它(这个例子中的半数以上是3),所以服务器1、2还是继续保持LOOKING状态。
(3)服务器3启动,根据前面的理论分析,服务器3成为服务器1、2、3中的老大,而与上面不同的是,此时有三台服务器选举了它,所以它成为了这次选举的Leader。
(4)服务器4启动,根据前面的分析,理论上服务器4应该是服务器1、2、3、4中最大的,但是由于前面已经有半数以上的服务器选举了服务器3,所以它只能接收当小弟的命了。
(5)服务器5启动,同4一样当小弟。

三十二、java当中的cas
CAS是一种无锁算法,它在硬件级别提供了原子性的条件更新操作,允许线程在不加锁的情况下实现对共享变量的修改。在Java中,CAS机制被广泛应用于java.util.concurrent.atomic包下的原子类以及高级并发工具类如AbstractQueuedSynchronizer(AQS)的实现中。
CAS是原子指令,一种基于锁的操作,而且是乐观锁,又称无锁机制。CAS操作包含三个基本操作数:内存位置、期望值和新值。
主内存中存放的共享变量的值:V(一般情况下这个V是内存的地址值,通过这个地址可以获得内存中的值)。
工作内存中共享变量的副本值,也叫预期值:A。
需要将共享变量更新到的最新值:B。
在执行CAS操作时,计算机会检查内存位置当前是否存放着期望值,如果是,则将内存位置的值更新为新值;若不是,则不做任何修改,保持原有值不变,并返回当前内存位置的实际值。
CAS操作通过一条CPU的原子指令,保证了比较和更新的原子性。在执行CAS操作时,CPU会判断当前系统是否为多核系统,如果是,则会给总线加锁,确保只有一个线程能够执行CAS操作。这种独占式的原子性实现方式,比起使用synchronized等重量级锁,具有更短的排他时间,因此在多线程情况下性能更佳。
①、读取内存位置P处的当前值。
②、将当前值与预期值E进行比较。
③、如果当前值等于预期值E,则将新值N写入内存位置P处,并返回 true 表示操作成功。
④、如果当前值不等于预期值E,则不做任何更改,并返回 false 表示操作失败。
①无锁操作:CAS 是一种无锁操作,这意味着它不需要线程互斥锁,可以避免因锁竞争而导致的性能瓶颈。
②、简单、高效:CAS 的实现相对简单易于理解。由于没有锁的开销,CAS 可以在多线程环境中实现高效的同步
java.util.concurrent.atomic原子类

三十三、kafka丢失消息
场景1:消息体太大
消息大小超过Broker的message.max.bytes的值。此时Broker会直接返回错误。
1、减少生产者发送消息体体积
可以通过压缩消息体、去除不必要的字段等方式减小消息大小。
2、调整参数max.request.size
max.request.size,表示生产者发送的单个消息的最大值,也可以指单个请求中所有消息的总和大小。默认值为1048576B,1MB。这个参数的值必须小于Broker的message.max.bytes。
场景2:异步发送机制
Kafka生产者默认采用异步发送消息,如果未正确处理发送结果,可能导致消息丢失。
1、使用带回调函数的发送方法
不要使用 producer.send(msg),而要使用 producer.send(msg, callback)。带有回调通知的 send 方法可以针对发送失败的消息进行重试处理。
场景3:网络问题和配置不当
​ 生产者在发送消息时可能遇到网络抖动或完全中断,导致消息未能到达Broker。如果生产者的配置没有考虑这种情况,例如未设置恰当的重试机制(retries参数)和确认机制(acks参数),消息就可能在网络不稳定时丢失。
1、设置acks参数设置为"all"
acks参数指定了必须要有多少个分区副本收到消息,生产者才认为该消息是写入成功的,这个参数对于消息是否丢失起着重要作用,该参数的配置具体如下:
all/-1 : 表示kafka isr列表中所有的副本同步数据成功,才返回消息给客户端
0 :表示客户端只管发送数据,不管服务端接收数据的任何情况
1 :表示客户端发送数据后,需要在服务端 leader 副本写入数据成功后,返回响应。
all:使用同步发送方式或确保acks参数设置为"all",以确保所有副本接收到消息。

三十四、Synchronized和Volatile的区别
synchronized可以修饰方法、代码块或整个类,用于保证线程安全。
volatile只能修饰变量,用于保证变量的内存可见性。‌
synchronized可以保证原子性、可见性和有序性。它通过锁定代码块或方法,防止多个线程同时执行,从而保证操作的原子性和有序性。在锁释放时,会将数据写入主内存,保证可见性。
volatile只能保证可见性和有序性,不能保证原子性。它通过禁止指令重排和使用内存屏障来保证变量的可见性,但无法保证复合操作(如自增)的原子性。‌
synchronized适用于需要同步多个线程对共享资源的读写操作,确保操作的原子性和一致性。
volatile适用于只需要保证变量的内存可见性,而不涉及复杂同步控制的场景,如状态标志位等。

三十五、Spring如何解决循环依赖
创建新的A时,发现要注入原型字段B,又创建新的B发现要注入原型字段A
Spring就先抛出了BeanCurrentlyInCreationException。
Spring的IOC机制只创建该类的一个实例,每次请求,都会用这同一个实例进行处理,因此若存在全局变量,本次请求的值肯定会影响下一次请求时该变量的值。
若不想影响下次请求,就需要用到原型模式,即@Scope(“prototype”)。

  1. 使用 @Lazy 注解,延迟加载
  2. 使用 @DependsOn 注解,指定加载先后关系
  3. 修改文件名称,改变循环依赖类的加载顺序

三十六、Spring bean的生命周期
Spring Bean的生命周期是指在Spring容器中创建、初始化、使用和销毁Bean实例的整个过程。Bean的生命周期包括以下几个主要阶段:
创建Bean实例、设置Bean属性、初始化Bean、使用Bean、销毁Bean。
创建Bean实例
‌通过构造器创建Bean实例‌:Spring使用无参数构造器来创建Bean实例。如果Bean类没有无参数构造器,Spring会抛出异常。通过静态工厂方法创建‌:如果Bean是通过静态方法来创建的,Spring会调用该静态方法返回类的实例。通过工厂实例方法创建‌:如果Bean是通过工厂的实例方法创建的,Spring会调用工厂的实例方法返回类的实例。
设置Bean属性
‌设置Bean属性‌:Spring通过反射机制调用setter方法(如setXXX)来设置Bean的属性值。
‌注入依赖的Bean‌:Spring会自动注入依赖的Bean,例如通过@Autowired注解。
初始化Bean
‌实现Aware接口‌:如果Bean实现了BeanFactoryAware或ApplicationContextAware等接口,Spring会调用相应的set方法,例如setBeanFactory或setApplicationContext。
‌调用初始化方法‌:如果定义了init-method属性,Spring会在初始化过程中调用该方法的实现。
‌使用BeanPostProcessor‌:Spring允许通过实现BeanPostProcessor接口来在Bean初始化前后进行自定义处理。
使用Bean
‌获取Bean‌:通过ApplicationContext对象或BeanFactory对象获取Bean。
‌使用Bean‌:获取到的Bean可以在应用程序中使用。
销毁Bean
‌定义销毁方法‌:可以通过@PreDestroy注解定义销毁方法。
‌手动关闭容器‌:需要手动关闭Spring容器(调用close方法),这样Spring容器才会销毁Bean并调用定义的销毁方法。

三十七、mybatis 二级缓存
二级缓存是 MyBatis 提供的一种缓存机制,用于减少对数据库的访问次数,提高应用程序的性能。它是基于 SQLSessionFactory 级别的缓存,与一级缓存(SQLSession 级别的缓存)不同,二级缓存在多个 SQLSession 之间共享。
二级缓存的主要作用是减少对数据库的直接查询,提高数据访问的效率,类似于学生共享公共书架上的教科书,从而减少每次都从书架上拿书的频率。
二级缓存的作用范围是整个 SQLSessionFactory,也就是说,同一个 SQLSessionFactory 下创建的多个 SQLSession 可以共享二级缓存的数据。
二级缓存会将查询结果存储在缓存中,当同样的查询再次执行时,会直接从缓存中获取结果,而不是重新查询数据库。
二级缓存是以 namespace 为单位的,也就是说,每个 mapper 的二级缓存是独立的,不同 mapper 之间的缓存数据不会共享。
一级缓存默认是开启的,二级缓存需要通过配置开启

三十八、Spring中BeanFactory与ApplicationContext的区别
BeanFactory与ApplicationContext是spring的两大核心接口,都可以当做spring的容器。
BeanFactory 是 Spring 框架的基础设施,面向 Spring 本身;
ApplicationContext 面向使用Spring 框架的开发者, ApplicationContext 使用的“场合”较多
BeanFactory是Spring中最底层的接口,是IOC的核心,其功能包含了各种Bean的定义、加载、实例化、依赖注入和生命周期的管理。是IOC最基本的功能。
而ApplicationContext接口是BeanFactory的子类,具有BeanFactory所有的功能,同时继承了MessageSource,所以提供了更完整的框架功能,支持国际化、资源文件访问、载入多个上下文配置文件,使得每一个上下文都专注于一个特定层次,提供在监听器中注册bean事件。
BeanFactory是延时加载,也就是说在容器启动时不会注入bean,而是在需要使用bean的时候,才会对该bean进行加载实例化。
ApplicationContext 是在容器启动的时候,一次性创建所有的bean,所以运行的时候速度相对BeanFactory比较快。
因为加载方式的不同,导致BeanFactory无法提前发现spring存在的配置问题。(如果bean的某个属性没有注入,BeanFactory加载不会抛出异常,直至第一次调用getBean()方法时才会抛出异常。)但是ApplicationContext 在容器启动时就可以发现spring存在的配置问题,因为他是一次性加载的,
有利于检测依赖属性是否注入(也因为其一次性加载的原因,导致占用内存空间,当Bean较多时,影响程序启动的速度)。
BeanFactory是以编程的方式创建的。
ApplicationContext 是以声明的方式创建的。
BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用。
BeanFactory是需要手动注册的。
ApplicationContext 是自动注册的。

三十九、linux中常用命令
cd、ls、ll、clear、pwd、cat、mkdir(-p)、vi、rm、rmdir、touch、mv、cp、ps、head、tail、diff、tar(tar xvf test.zip)、java -version、ifconfig、java -jar jar包等等

四十、Redis与MySQL数据同步
系统先查询 Redis 缓存,如果缓存中没有数据,再从 MySQL 中查询并将数据写入 Redis 缓存。
更新数据时,更新 MySQL 并删除 Redis 缓存,使缓存数据失效,保证下次读取时能拿到最新数据。
1、先更新数据库,在删除或更新Redis数据
优点:简单直接。保证数据库有最新数据
缺点:如果更新缓存失败,会导致数据不一致(缓存时旧数据,数据库是新数据,一直返回旧数据)。
2、先删除redis数据,在更新mysql数据。
优点:避免缓存更新失败而导致的不一致
缺点:在高并发下会导致数据不一致(请求A来了,删除缓存请求A更新数据库,发生卡顿,同时来了请求B,请求B发现没有缓存,就从数据库查询了数据,并生成了缓存此时请求A卡顿结束,更新了数据库)。
3、延迟双删策略通过以下步骤解决了这些问题:
‌①删除缓存‌:删除对应的缓存项,以确保后续的读请求会从数据库加载最新数据。
‌②更新数据库‌:首先将数据更新到数据库中。
‌③设置延迟‌:设定一段短暂的延迟时间,例如几百毫秒。
‌④再次删除缓存‌:在延迟时间结束后,再次尝试删除缓存,以确保在数据库更新传播到所有节点,并且在缓存中的旧数据彻底过期失效之前,第二次删除操作可以消除缓存中可能存在的旧数据。
‌优点‌:提高数据一致性‌:通过延迟双删策略,可以减少因网络延迟或并发控制不当导致的数据不一致问题。
‌适应高并发‌:在高并发场景下,能够确保数据的最新状态被读取和使用。
‌缺点‌:
‌性能影响‌:延时双删策略会增加一定的时间消耗,可能会影响系统的吞吐量。
‌实现复杂度‌:需要额外的逻辑来处理延迟和重试机制,增加了实现的复杂度。
4、使用消息队列。
1)先更新数据库,把需要删除的缓存数据放到消息队列中
2)消费端实现删除缓存的操作,如果成功,将该条数据从消息队列中移除,避免重复操作,如果失败,把重试请求写到消息队列中,由专门消费者重试,直到成功,如果达到重试次数还未成功,我们就需要向业务层发送报错信息了。

四十一、mysql数据库的三大范式
三大范式是 Mysql 数据库设计表结构所遵循的规范和指导方法,目的是为了减少冗余,建立结构合理的数据库,从而提高数据存储和使用的性能。
三大范式之间是具有依赖关系的,比如第二范式是在第一范式的基础上建设的、第三范式是在第二范式的基础上建设的。
当然 Mysql 数据库的范式不止三大范式,除了三大范式,还有巴斯-科德范式(BCNF)、第四范式(4NF)、第五范式(5NF,又称“完美范式")。
第一范式
遵循原子性。即,表中字段的数据,不可以再拆分。
第二范式
在满足第一范式的情况下,遵循唯一性,消除部分依赖。即,表中任意一个主键或任意一组联合主键,可以确定除该主键外的所有的非主键值。
再通俗点讲就是,一个表只能描述一件事情。
不满足

  1. 造成整表的数据冗余。
  2. 更新数据不方便。
  3. 插入数据不方便或产生异常。
    第三范式
    在满足第二范式的情况下,消除传递依赖。即,在任一主键都可以确定所有非主键字段值的情况下,不能存在某非主键字段 A 可以获取 某非主键字段 B。
    第一范式(1 NF):字段不可再拆分。
    第二范式(2 NF):表中任意一个主键或任意一组联合主键,可以确定除该主键外的所有的非主键值。
    第三范式(3 NF):在任一主键都可以确定所有非主键字段值的情况下,不能存在某非主键字段 A 可以获取 某非主键字段 B。

四十二、Spring中涉及到的设计模式
单例模式
Spring依赖注入Bean实例默认是单例的。
代理模式
AOP底层,就是动态代理模式的实现。
简单工厂模式
BeanFactory。Spring中的BeanFactory就是简单工厂模式的体现,根据传入一个唯一的标识来获得Bean对象,但是否是在传入参数后创建还是传入参数前创建这个要根据具体情况来定。
工厂模式
实现了FactoryBean接口的bean是一类叫做factory的bean。其特点是,spring会在使用getBean()调用获得该bean时,会自动调用该bean的getObject()方法,所以返回的不是factory这个bean,而是这个bean.getOjbect()方法的返回值。
适配器模式
SpringMVC中的适配器HandlerAdatper。
HandlerAdatper根据Handler规则执行不同的Handler。
装饰器模式
Spring中用到的包装器模式在类名上有两种表现:一种是类名中含有Wrapper,另一种是类名中含有Decorator。

四十三、spring中的aop
AOP(Aspect Oriented Programming)面向切面编程,一种编程范式,指导开发者如何组织程序结构
在不惊动原始设计的基础上为其进行功能增强。简单的说就是在不改变方法源代码的基础上对方法进行功能增强。
使用aop给代码加日志
@Aspect、@Pointcut、@Before、@After、@Around

四十四、spring事务如何管理
Spring 通过事务管理器(Transaction Manager)来管理事务的。不同的持久化技术(如 JDBC、JPA、Hibernate)对应不同的事务管理器,例如:
DataSourceTransactionManager:用于管理 JDBC 事务。
JpaTransactionManager:用于管理 JPA 事务。
HibernateTransactionManager:用于管理 Hibernate 事务。
事务管理器是 Spring 事务管理的核心,它负责处理事务的开始、提交和回滚。
编程式事务管理是指在代码中显式地管理事务,通常使用 TransactionTemplate 或者底层的 PlatformTransactionManager。(手动写代码)
声明式事务管理是通过 AOP(面向切面编程)和注解来管理事务,通常使用 @Transactional 注解。这种方式更加简洁和方便,推荐使用。
@Transactional 注解可以应用于类或方法上,它的主要属性包括:
propagation:事务的传播行为,定义事务如何传播。常用的传播行为包括:
REQUIRED(默认):如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新事务。
REQUIRES_NEW:总是创建一个新事务,如果当前存在事务,则挂起当前事务。
SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式执行。
NOT_SUPPORTED:总是以非事务方式执行,如果当前存在事务,则挂起当前事务。
MANDATORY:必须在事务中执行,如果当前没有事务,则抛出异常。
NEVER:必须在非事务中执行,如果当前存在事务,则抛出异常。
NESTED:如果当前存在事务,则创建一个嵌套事务;如果当前没有事务,则创建一个新事务。
isolation:事务的隔离级别,定义一个事务可以受到其他事务影响的程度。常用的隔离级别包括:
DEFAULT(默认):使用底层数据库的默认隔离级别。
READ_UNCOMMITTED:最低的隔离级别,允许脏读。
READ_COMMITTED:允许不可重复读,防止脏读。
REPEATABLE_READ:允许幻读,防止脏读和不可重复读。
SERIALIZABLE:最高的隔离级别,防止脏读、不可重复读和幻读。
timeout:事务超时时间,以秒为单位。默认值是 -1,表示没有超时限制。
readOnly:是否为只读事务。默认值是 false。只读事务通常用于查询操作,可以对某些数据库优化。
rollbackFor:指定哪些异常会导致事务回滚。例如:rollbackFor = RuntimeException.class。
noRollbackFor:指定哪些异常不会导致事务回滚。例如:noRollbackFor = IllegalArgumentException.class。
事务传播(Propagation):定义了事务方法被调用时事务的传播行为。例如,当前方法是否要在一个事务中运行,是否要加入现有的事务,或者是否要开启一个新的事务。
隔离级别(Isolation):定义了一个事务可以看到其他事务对数据的影响程度。常见的隔离级别包括:READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ、SERIALIZABLE。
@EnableTransactionManagement
@Transactional
事务管理在系统开发中是不可缺少的一部分,Spring提供了很好事务管理机制,主要分为编程式事务和声明式事务两种。
编程式事务:是指在代码中手动的管理事务的提交、回滚等操作,代码侵入性比较强
声明式事务:基于AOP面向切面的,它将具体业务与事务处理部分解耦,代码侵入性很低,所以在实际开发中声明式事务用的比较多。

四十五、拦截器和过滤器
过滤器(Filter)是 Java Servlet 技术中的一个重要部分,主要用于在 Servlet 处理请求之前或响应之后对数据进行某些处理。过滤器可以实现诸如日志记录、请求数据修改、响应数据修改、权限控制等功能。在 Java EE 或 Spring 应用中,过滤器的使用非常广泛。过滤器工作在 Servlet 容器中,它拦截客户端的请求和服务器的响应。过滤器链(Filter Chain)是多个过滤器按照一定的顺序执行的集合,一个请求可以依次通过多个过滤器,然后到达目标 Servlet,响应也会按相反的顺序经过这些过滤器返回给客户端。
拦截器(Interceptor)是 Spring MVC 框架中的一个核心组件,用于在处理 HTTP 请求的过程中进行拦截和处理。拦截器主要用于实现跨切面(cross-cutting)的逻辑,如日志记录、性能统计、安全控制、事务处理等。拦截器工作在 Spring 的 DispatcherServlet 和具体的 Controller 之间。当一个请求被发送到 Spring MVC 应用时,DispatcherServlet 首先接收到这个请求,然后根据配置的拦截器链对请求进行预处理,最后将请求转发到相应的 Controller 进行处理。
相同点
目的相似:都用于在请求到达目标资源(如 Servlet 或 Controller)之前或响应返回给客户端之后进行某些处理。
处理能力:都能够实现请求的预处理、后处理,比如日志记录、权限检查、请求数据修改等。
配置方式:可以通过注解或 XML 配置的方式进行配置。
不同点
过滤器是基于 Java Servlet 规范的,它是 Servlet 容器(如 Tomcat)的一部分。
拦截器则是 Spring MVC 框架的一部分,依赖于 Spring 的上下文环境。
过滤器更接近于底层,它在 Servlet 请求之前和响应之后进行处理。
拦截器则更接近于业务层,它在 DispatcherServlet 处理请求的过程中执行,即在请求到达 Controller 之前和处理完毕之后。
过滤器的粒度比较粗,通常对所有请求或特定 URL 模式的请求生效。
拦截器的粒度更细,可以控制到具体的控制器方法。

四十六、ArrayList 线程不安全的原因
两个线程,A将数据存放在0的位置,A暂停,B存放数据,由于A未将size++,所以B也将数据存放在0上,然后A和B都同时运行,size=2,但是只有位置0上有数据,所以说线程不安全

四十七、mysql三种删除表区别
drop会删除整个表,包括表结构和数据,释放空间,删除速度最快。无法找回数据。
truncate会删除表中的所有数据、释放空间,但是保留表结构。和delete区别,truncate操作通常更快,因为它不扫描每一行数据来删除,它直接删除数据页。类似删除之后重新建表。
delete:删除表中的数据,但不会删除表本身。如果你想删除表中的所有数据,但保留表结构,则可以使用它。一条条删除,可以回滚。记录日志

四十八、spring事务在什么情况下会失效
1、@Transactional注解应用在非 public 修饰的方法上
2、@Transactional 注解属性 propagation 设置错误
TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
3、@Transactional 注解属性 rollbackFor 设置错误
rollbackFor 可以指定能够触发事务回滚的异常类型。Spring默认抛出了未检查unchecked异常(继承自 RuntimeException 的异常)或者 Error才回滚事务;其他异常不会触发回滚事务。
如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要指定rollbackFor属性。
4、在类内部调用类内部@Transactional标注的方法。这种情况下也会导致事务不开启
当前类中方法A没有声明注解事务,而B方法有。外部调用方法A之后,方法A调用方法B的事务是不会起作用的由于使用Spring AOP代理造成的,因为只有当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理。
5、事务方法内部捕捉了异常,没有抛出新的异常,导致事务操作不会进行回滚。
6、数据库引擎不支持事务

四十九、springAop中的动态代理
Spring AOP ⽀持 JDK Proxy 和 CGLIB ⽅式实现动态代理。默认情况下,实现了接⼝的类,使用 AOP 会基于 JDK ⽣成代理类,没有实现接⼝的类,会基于 CGLIB ⽣成代理类。
JDK 实现,要求被代理类必须实现接⼝,之后是通过 InvocationHandler 及 Proxy,在运⾏时动态的在内存中⽣成了代理类对象,该代理对象是通过实现同样的接⼝实现(类似静态代理接⼝实现的⽅式),只是该代理类是在运⾏期时,动态的织⼊统⼀的业务逻辑字节码来完成。 也就是说没有实现接口的类就没有办法实现AOP了,没错,所以就需要CGLIB了。
CGLIB 实现,被代理类可以不实现接⼝,是通过继承被代理类,在运⾏时动态的⽣成代理类对象,所以他不能代理被final修饰的类。 (底层实现原理:字节码增强技术)
jdk的动态代理

//定义接口
public interface TargetInterFace {
    void targetMethod();
}
//接口实现
public class TargetObject implements TargetInterFace{
    @Override
    public void targetMethod() {
        System.out.println("/// "+this.getClass().getName()+" 执行");
    }
}
public class AopJdkInvocation implements InvocationHandler {

    private Object target;

    public AopJdkInvocation(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        System.out.println("/// "+this.getClass().getName()+" 执行前");

        method.invoke(this.target, args);

        System.out.println("/// "+this.getClass().getName()+" 执行后");

        return null;
    }
}
public static void main(String[] args) {
    //目标对象
    TargetObject targetObject = new TargetObject();
    //建立代理与被代理对象的关系
    AopJdkInvocation aopInvocation=new AopJdkInvocation(targetObject);
    //生成代理对象
    TargetInterFace instance = (TargetInterFace) Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), aopInvocation);
    instance.targetMethod();
}

五十、redis如何保持高可用
高可用(High Availability, HA)是指在信息技术中确保系统、服务或应用程序在绝大多数时间内都是可操作和可访问的能力。这通常涉及以下几个关键方面:
最小化停机时间:
高可用系统的目标是减少因硬件故障、系统升级、维护或其他原因导致的停机时间。
这通常通过冗余设计实现,例如使用多个服务器或组件,如果一个出现故障,另一个可以立即接管。
故障转移和冗余:
故障转移是指当一个系统组件失败时,自动切换到备用系统或组件以继续运行。
冗余指的是多余的备份组件,如额外的服务器或网络连接,以保证主组件故障时的连续性。
快速恢复:
当系统发生故障时,能够迅速恢复正常运行是高可用性的重要组成部分。
这可能涉及快速诊断和解决问题的能力,或者能够快速重启或切换到备用系统。
可扩展性和性能:
高可用系统通常需要具备良好的可扩展性,以应对负载增加。
系统性能也是重要考虑因素,以确保在高负载下仍能提供满意的服务。
数据完整性和备份:
高可用系统需要确保数据不会因系统故障而丢失或损坏。
定期备份和数据冗余是确保数据安全的关键措施。
高可用性不仅仅是技术上的实现,它还涉及到组织的政策、流程和人员培训。确保系统高可用性是许多组织特别是提供关键服务或拥有大量用户的组织的重要目标。
1、主从复制(Master-Slave Replication):
Redis允许设置一个主节点和多个从节点。
所有的写操作都在主节点上执行,然后数据被复制到从节点。
如果主节点出现故障,从节点可以提供读服务,保持数据的可访问性。
2、哨兵模式(Sentinel):
哨兵是一种监控系统,它监控Redis主从节点的健康状况。
如果主节点失败,哨兵会自动将从节点提升为新的主节点,实现故障转移。
它还可以通知应用程序关于主节点的变更,帮助应用程序快速适应新的主节点。
3、集群模式(Cluster):
Redis集群通过自动分片和数据复制在多个节点之间分布数据。
每个主节点都有至少一个复制节点,以确保数据的持续可用性。
在主节点失效时,其复制节点会自动被提升为新的主节点。
4、持久化:
Redis提供了RDB(快照)和AOF(只追加文件)两种数据持久化方式。
这些持久化方法可以保证在系统故障后数据不会丢失,并能够恢复到故障发生前的状态。
5、定期检查和维护:
定期对Redis系统进行检查和维护,可以预防潜在的问题,保持系统的稳定运行。
6、备份和恢复策略:
定期备份Redis数据,以便在发生灾难性事件时可以迅速恢复。

五十一、spring中支持bean的作用域有哪几种?
@Scope
Spring支持五个作用域:singleton、prototype、request、session、global session
1.singleton:默认作用域Spring IOC容器仅存在一个Bean实例,Bean以单例方式存在,在创建容器时就同时自动创建了一个Bean对象。作用域范围是ApplicationContext中。
2.prototype:每次从容器中调用Bean时,都会返回一个新的实例,即每次调用getBean时。作用域返回是getBean方法调用直至方法结束。相当于执行newXxxBean().Prototype是原型类型,再我们创建容器的时候并没有实例化,而是当我们获取Bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。
request:每次 http 请求都会创建一个 bean,该作用域仅在基于web 的Spring ApplicationContext 情形下有效。
session:在一个 HTTP Session 中,一个 bean 定义对应一个实例。该作用域仅在基于 web 的 Spring ApplicationContext 情形下有效。
global-session:在一个全局的 HTTP Session 中,一个 bean 定义对应一个实例。该作用域仅在基于 web 的 Spring ApplicationContext 情形下有效。

五十一、java当中的内存模型
Java中的内存模型主要指的是Java内存管理体系,它规定了如何在JVM中使用和分配内存。Java内存模型定义了线程和主内存之间的抽象关系,线程之间的工作内存交互。
Java内存模型规定了8种原子操作:
lock: 将一个变量标记为线程独占状态。
unlock: 将一个变量从线程独占状态中释放。
read: 读取变量的值。
load: 把read读取的值放入到工作内存。
use: 把工作内存中的变量值传递给执行引擎。
assign: 把执行引擎接收的值赋值给工作内存中的变量。
store: 把工作内存中的变量的值传递给主内存。
write: 把store传递的变量的值放入主内存的变量中。
其中,volatile关键字可以防止指令重排序优化,提供了可见性保证,确保线程之间的可见性。

五十二、linux修改权限的命令
Linux系统上对文件的权限有着严格的控制,如果想对某个文件执行某种操作,必须具有对应的权限方可执行成功。Linux下文件的权限类型一般包括读,写,执行。对应字母为 r、w、x。Linux下权限的粒度有 拥有者 、群组 、其它组 三种。每个文件都可以针对三个粒度,设置不同的rwx(读写执行)权限。通常情况下,一个文件只能归属于一个用户和组, 如果其它的用户想有这个文件的权限,则可以将该用户加入具备权限的群组,一个用户可以同时归属于多个组。
设置所有用户可读取文件 a.conf
chmod ugo+r a.conf 或 chmod a+r a.conf
设置 c.sh 只有 拥有者可以读写及执行
chmod u+rwx c.sh
设置所有人可以读写及执行
chmod 777 file (等价于 chmod u=rwx,g=rwx,o=rwx file 或 chmod a=rwx file)
设置拥有者可读写,其他人不可读写执行
chmod 600 file (等价于 chmod u=rw,g=—,o=— file 或 chmod u=rw,go-rwx file )
可以用三个8进制数字分别表示 拥有者 、群组 、其它组( u、 g 、o)的权限详情,并用chmod直接加三个8进制数字的方式直接改变文件权限。语法格式为 :
-rw------- (600) 只有拥有者有读写权限。
-rw-r–r-- (644) 只有拥有者有读写权限;而属组用户和其他用户只有读权限。
-rwx------ (700) 只有拥有者有读、写、执行权限。
-rwxr-xr-x (755) 拥有者有读、写、执行权限;而属组用户和其他用户只有读、执行权限。
-rwx–x–x (711) 拥有者有读、写、执行权限;而属组用户和其他用户只有执行权限。
-rw-rw-rw- (666) 所有用户都有文件读、写权限。
-rwxrwxrwx (777) 所有用户都有读、写、执行权限。
ll filename
4:读,2:写,1:执行

查看某个目录的下的权限

ll /path/path/path/

修改权限命令

sudo chmod 600 ××× (只有所有者有读和写的权限)
sudo chmod 644 ××× (所有者有读和写的权限,组用户只有读的权限)
sudo chmod 700 ××× (只有所有者有读和写以及执行的权限)
sudo chmod 666 ××× (每个人都有读和写的权限)
sudo chmod 777 ××× (每个人都有读和写以及执行的权限)

五十三、java自定义注解
注解可以看作是一种特殊的标记,可以用在方法、类、参数和包上,程序在编译或者运行时可以检测到这些标记而进行一些特殊的处理,例如标注在方法上可以实现接口权限的校验。
我们想定义一个自己的注解 需要使用 @interface 关键字来定义。
如定义一个叫 MyAnnotation 的注解:
public @interface MyAnnotation { }
光加上 @interface 关键字 还不够,我们还需要了解5大元注解
1、@Retention @Retention 指定注解的生命周期
RetentionPolicy.SOURCE : 注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃(.java文件)
RetentionPolicy.CLASS :注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期(.class文件)
RetentionPolicy.RUNTIME: 注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在(内存中的字节码)常用
2、@Target @Target指定注解可以修饰的元素类型
ElementType.ANNOTATION_TYPE - 标记的注解可以应用于注解类型。
ElementType.CONSTRUCTOR - 标记的注解可以应用于构造函数。
ElementType.FIELD - 标记的注解可以应用于字段或属性。
ElementType.LOCAL_VARIABLE - 标记的注解可以应用于局部变量。
ElementType.METHOD - 标记的注解可以应用于方法。
ElementType.PACKAGE - 标记的注解可以应用于包声明。
ElementType.PARAMETER - 标记的注解可以应用于方法的参数。
ElementType.TYPE - 标记的注解可以应用于类的任何元素。常用
3、@Documented 指定注解会被JavaDoc工具提取成文档。默认情况下,JavaDoc是不包括文档的
4、@Inherited(JDK8 引入) 表示该注解会被子类继承,注意,仅针对类,成员属性、方法并不受此注释的影响。
5、@Repeatable(JDK8 引入) 表示注解可以重复使用,为了解决同一个注解不能重复在同一类/方法/属性上使用的问题。
//注解用于字段上
@Target(ElementType.FIELD)
//运行时使用
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String message() default “aaa”;
}
通过反射机制解析注解并获取对应的值

五十四、@Autowired 和 @Resource注解
Autowired 和 Resource 注解来自不同的“父类”,其中Autowired注解是 Spring 定义的注解,而Resource 注解是 Java 定义的注解,它来自于 JSR-250(Java 250 规范提案)。
Autowired注解是spring的注解,此注解只根据type进行注入,不会去匹配name。但是如果只根据type无法辨别注入对象时,就需要配合使用@Qualifier注解或者@Primary注解使用。
Resource注解有两个重要的属性,分别是name和type,如果name属性有值,则使用byName的自动注入策略,将值作为需要注入bean的名字,如果type有值,则使用byType自动注入策略,将值作为需要注入bean的类型。如果既不指定name也不指定type属性,这时将通过反射机制使用byName自动注入策略。即@Resource注解默认按照名称进行匹配,名称可以通过name属性进行指定,如果没有指定name属性,当注解写在字段上时,默认取字段名,按照名称查找,当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
Autowired注解先根据类型(byType)查找,如果存在多个(Bean)再根据名称(byName)进行查找;
Resource注解先根据名称(byName)查找,如果(根据名称)查找不到,再根据类型(byType)进行查找。
Autowired注解支持属性注入、构造方法注入和 Setter 注入,而Resource注解只支持属性注入和 Setter 注入。

五十四、redis集群的竞选机制
‌Redis的竞选机制主要包括Redis Sentinel集群选举和Redis Cluster主节点选举两个部分。‌
Redis Sentinel集群选举机制
‌主观下线和客观下线‌:每个Sentinel节点会定时对Redis集群的所有节点发送心跳包检测节点是否正常。如果一个节点在down-after-milliseconds时间内没有回复Sentinel节点的心跳包,则该Redis节点被该Sentinel节点主观下线。当节点被一个Sentinel节点标记为主观下线后,还需要其他Sentinel节点共同判断为主观下线。如果超过quorum数量的Sentinel节点认为该Redis节点主观下线,则该节点客观下线‌。
‌选举Leader‌:当一个Sentinel节点确认Redis集群的主节点主观下线后,会请求其他Sentinel节点将自己选举为Leader。被请求的Sentinel节点如果没有同意过其他Sentinel节点的选举请求,则同意该请求(选举票数+1),否则不同意。如果一个Sentinel节点获得的选举票数达到Leader最低票数(quorum和Sentinel节点数/2+1的最大值),则该Sentinel节点被选举为Leader;否则重新进行选举‌。
决定新主节点‌:当Sentinel集群选举出Sentinel Leader后,由Sentinel Leader从Redis从节点中选择一个Redis节点作为主节点。选择的标准包括:过滤故障的节点、选择优先级最高的从节点、选择复制偏移量最大的从节点,最后选择run id比较小的从节点‌。
Redis Cluster主节点选举机制
‌故障检测‌:如果一个节点在cluster-node-timeout内没有返回pong,则被认为pfail,即主观下线。如果超过半数的节点都认为该节点主观下线,则变成fail,即客观下线‌。
‌从节点过滤‌:对宕机的master node,在其所有的slave node中,选择一个切换成master node。选择标准包括:检查每个slave node与master node断开连接的时间是否超过
cluster-node-timeout * cluster-slave-validity-factor,以及slave priority和replica offset等‌。
‌从节点选举‌:对slave进行排序,选择优先级最高的从节点作为主节点。如果优先级相同,则选择复制偏移量最大的从节点。如果仍然相同,则选择run id比较小的从节点‌。
总结
Redis的竞选机制通过Sentinel集群和Cluster集群两种方式实现高可用性和故障转移。Sentinel集群通过选举Leader来决定新主节点,而Cluster集群则通过故障检测和从节点选举
来确保服务的连续性。这两种机制共同保证了Redis的高可用性和数据的可靠性。

五十五、java当中的深拷贝和浅拷贝
深拷贝:深拷贝创建一个新对象,并且复制原对象中的所有字段,包括引用类型的字段。对于每个引用类型的字段,它都会创建一个新的实例,确保源对象和目标对象之间没有任何共享的引用。递归地复制所有子对象。即使原始对象或拷贝对象修改其子对象,也不会互相影响。
特点:
基本数据类型的值会被复制。
引用数据类型的对象会被完全独立地复制。
浅拷贝:浅拷贝创建一个新对象,该对象与原对象具有相同的值,但对引用类型字段只会复制引用地址,而不复制实际的对象。这意味着原对象和新对象的引用类型属性指向同一块内存。
不递归地复制对象所引用的子对象。它仅复制对象的基本数据类型和对引用类型的引用,因此原始对象和拷贝对象中对引用类型的修改会相互影响。
特点:
基本数据类型的值会被复制。
引用数据类型的对象会共享同一个实例。
深拷贝和浅拷贝的区别
对基本类型 复制其值 复制其值
对引用类型 复制引用(共同使用同一对象) 复制对象(各自独立的对象)
影响 修改一个对象的引用类型字段会影响另一个对象 修改一个对象不会影响另一个对象

五十六、如何解决跨域攻击
跨域就是当在页面上发送ajax请求时,由于浏览器同源策略的限制,要求当前页面和服务端必须同源,也就是协议、域名和端口号必须一致。
如果协议、域名和端口号中有其中一个不一致,则浏览器视为跨域,进行拦截。
1、JSONP方式解决跨域
jsonp的原理就是利用了script标签不受浏览器同源策略的限制,然后和后端一起配合来解决跨域问题的。
2、CORS方式解决跨域
cors是跨域资源共享,是一种基于 HTTP 头的机制,该机制通过允许服务器标示除了它自己以外的其它 origin(域,协议和端口),使得浏览器允许这些 origin 访问加载自己的资源。服务端设置了Access-Control-Allow-Origin就开启了CORS,所以这种方式只要后端实现了CORS,就解决跨域问题,前端不需要配置。
3、搭建Node代理服务器解决跨域:
4、Nginx反向代理解决跨域:
nginx通过反向代理解决跨域也是利用了服务器请求服务器不受浏览器同源策略的限制实现的

五十七、高并发时应采取什么措施保护系统
1、限流熔断降级
2、使用缓存
3、使用集群并做负载均衡
4、使用消息中间件进行削峰,异步,解耦

五十八、java当中的垃圾回收算法有哪些
垃圾回收算法有四种,分别是标记清除,复制回收,标记整理,分代回收
1、标记清除算法
标记:标记出所有需要回收的对象(引用计数法,可达性分析均可)
清除:标记完成后统一回收所有被标记的对象
优点:算法简单,效率高
缺点:标记清除后会产生大量不连续的内存碎片,为后续对象分配内存提出更高的需求
2、复制算法
将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。
这样就使每次的内存回收都是对内存区间的一半进行回收。
优点:空间规则,分配对象内存容易。
缺点:只使用了内存的一半,浪费内存空间
3、标记-整理算法
根据老年代的特点(存活率较高)特出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉另一端边界以外的内存。
4、分代收集算法
新生代中,每次收集都会有大量对象死去,所以选择复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。
老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。

五十九、tcp和udp的区别是什么?
TCP和UDP是计算机网络通信中常用的传输层协议,它们在连接性、可靠性、数据量、适用场景和头部开销等方面存在明显区别。TCP适用于需要可靠性和完整性保证的场景,如文件传输、网页访问等;而UDP适用于对数据传输速度要求较高、可以容忍部分数据丢失的场景,如实时通信、音视频传输等。选择时需根据应用需求和场景要求进行权衡。TCP(三次握手和四次挥手):TCP是一种面向连接的协议。在数据传输之前,TCP需要先建立连接,然后进行数据传输,最后再关闭连接。TCP通过三次握手来建立连接,确保数据的可靠性和完整性。一旦建立连接,数据传输过程中会进行流量控制和拥塞控制,以保证数据的顺序和及时性。
UDP:UDP是一种无连接的协议。UDP不需要事先建立连接,直接发送数据报文,不会进行三次握手和四次挥手的过程。因此,UDP的传输速度更快,但也更不可靠,因为它不提供数据的可靠性和完整性保证,数据报文可能会丢失或乱序。

六十、redis有哪些内存淘汰策略(8种)
1、noeviction:不删除任何键,当内存不足时,所有写操作都会报错。
2、allkeys-lru:在所有的键中,选择最近最少使用(Least Recently Used)的键进行删除。
3、volatile-lru:在设置了过期时间的键中,选择最近最少使用的键进行删除。
4、allkeys-random:在所有的键中,随机选择一个键进行删除。
5、volatile-random:在设置了过期时间的键中,随机选择一个键进行删除。
6、volatile-ttl:在设置了过期时间的键中,选择剩余时间最短的键进行删除。
7、volatile-lfu:在设置了过期时间的键中,选择最不经常使用(Least Frequently Used)的键进行删除。
8、allkeys-lfu:在所有的键中,选择最不经常使用的键进行删除。

六十一、object中的finalize()方法
用于在垃圾回收器清除对象之前进行必要的清理工作。
内存溢出:在内存中没有存储的地方
内存泄漏:内存被无效对象占用
一个对象分配内存之后,在使用的时候没有来得及释放,导致一直占用内存,使得实际内存变少
GC 垃圾回收机制,定时清理内存
1.在程序运行时自动启动,不定时回收内存中标记的垃圾(没有引用的对象,会被内存标记为垃圾)
2.GC是自动调用的,也可以手动调用GC -System.gc();
总结:原则上java中是不存在内存泄漏的

六十二、gc中的永久代
‌在JDK7及之前的版本中,所有类的元数据(包括类定义、方法定义、常量池等)都被存储在永久代中‌。这意味着,当JVM加载一个类时,相关的元数据会被存储在永久代中。此外,字符串常量池也在永久代中存储,直到JDK7版本,字符串常量池才从永久代中移出,存放在堆空间中‌。
永久代的特性
‌存储内容‌:永久代存储类的元数据、方法信息、常量池信息等静态数据。
‌内存管理‌:永久代是Java堆的一部分,其大小受到Java堆内存的限制。
‌垃圾回收‌:与Java堆的垃圾回收不同,永久代的垃圾回收较少,因为类信息通常不会被频繁卸载‌。
永久代与元空间的区别
在JDK8及以后的版本中,永久代被移除,取而代之的是元空间(Metaspace)。元空间使用本地内存,与JVM的堆内存分开,因此其大小不受JVM堆内存的限制,而是受本地内存的限制。
元空间可以动态调整大小,避免了OutOfMemoryError错误,并且避免了堆内存的碎片化问题‌。

六十三、HashMap的扩容机制
HashMap 数据结构为 数组+链表(JDk1.7),JDK1.8中增加了红黑树,其中:链表的节点存储的是一个 Entry 对象,每个Entry 对象存储四个属性(hash,key,value,next)
底层:采用数组+链表(JDK1.7),采用数组+链表+红黑树(JDK1.8)。线程不安全。
容器:HashMap默认容器长度为16,扩容因子为0.75,以2的n次方扩容,最高可扩容30次。如第一次是长度达到160.75=12的时候开始扩容,162^1=32。
1、JDK1.8相较与JDK1.7在数组+链表的基础上添加了红黑树,加红黑树的目的是提高HashMap插入和查询的整体效率;
2、JDK1.7中链表插入采用的是头插法,JDK1.8中插入使用的是尾插法,因为JDK1.8中插入key和value时需要判断链表元素个数,所以需要遍历链表统计链表元素个数,
所以正好直接使用尾插法;
3、JDK1.7哈希算法比较复杂,存在各种右移与异或运算,JDK1.8进行了简化,因为复杂的哈希算法目的就是提高散列性,来提供HashMap的整体效率,而1.8新增了红黑树,所以可以适当的简化哈希算法,节省CPU资源。

六十四、ConcurrentHashMap的数据结构
在JDK1.7版本中,ConcurrentHashMap的数据结构是由一个Segment数组和多个HashEntry组成,主要实现原理是实现了锁分离的思路解决了多线程的安全问题。Segment数组的意义就是将一个大的table分割成多个小的table来进行加锁,也就是上面的提到的锁分离技术,而每一个Segment元素存储的是HashEntry数组+链表,这个和HashMap的数据存储结构一样。
ConcurrentHashMap与HashMap和Hashtable 最大的不同在于:put和 get 两次Hash到达指定的HashEntry,第一次hash到达Segment,第二次到达Segment里面的Entry,然后在遍历entry链表。
JDK1.8的实现已经摒弃了Segment的概念,而是直接用Node数组+链表+红黑树的数据结构来实现,并发控制使用Synchronized和CAS来操作,整个看起来就像是优化过且线程安全的HashMap,
虽然在JDK1.8中还能看到Segment的数据结构,但是已经简化了属性,只是为了兼容旧版本.

六十五、创建索引的优缺点
‌1、提升查询速度‌:索引的主要目的是加快查询速度。通过创建索引,数据库可以迅速定位到数据所在的物理存储位置,减少全表扫描的必要,从而显著缩短响应时间‌
‌2、减少I/O操作‌:索引通过减少需要访问的物理数据页数量,显著减少I/O操作,降低系统的负担‌
‌3、提高系统性能‌:索引能够整体上提高数据库系统的性能,特别是在处理大量并发查询时,索引可以显著减少每个查询所需的资源,提高系统的并发处理能力‌
‌4、支持快速排序‌:在执行排序操作时,索引可以快速完成排序,减少全表扫描的时间‌
‌5、保证数据完整性‌:通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性,增强数据完整性‌
‌6、加速表与表之间的连接‌:索引可以加速表与表之间的连接,特别是在实现数据的参考完整性方面特别有意义‌
‌索引过多创建的缺点包括‌:
‌1、存储空间占用增加‌:每个索引都需要一定的存储空间,过多的索引会增加数据库的存储成本‌。
‌2、写操作性能下降‌:每次插入、更新或删除操作都需要更新索引,如果有太多的索引,这些操作会变得更加耗时‌。
‌3、维护成本增加‌:随着索引数量的增加,索引的维护成本也会增加,包括优化、重建索引等操作‌。
‌创建索引的准则包括‌:
1、‌在经常需要搜索的列上创建索引‌:可以加快搜索的速度‌。
2、‌在作为主键的列上创建索引‌:强制该列的唯一性和组织表中数据的排列结构‌。
‌3、在经常用在连接的列上创建索引‌:这些列主要是一些外键,可以加快连接的速度‌。
‌4、在经常需要根据范围进行搜索的列上创建索引‌:因为索引已经排序,其指定的范围是连续的‌。
‌5、在经常需要排序的列上创建索引‌:因为索引已经排序,这样查询可以利用索引的排序,加快排序查询时间‌。
‌6、在经常使用在WHERE子句中的列上创建索引‌:加快条件的判断速度‌。

六十六、Spring中的bean是线程安全的吗
‌Spring中的Bean是否线程安全取决于Bean的状态和作用域。‌ Spring框架本身并没有提供线程安全策略,Bean的线程安全性主要取决于其状态和作用域。
Bean的状态和作用域对线程安全性的影响
‌无状态Bean‌:如果Bean是无状态的,即不会在多线程环境中修改其内部状态,那么这样的Bean是线程安全的。例如,Spring MVC中的Controller、Service、Dao等通常是无状态的,因此它们是线程安全的。
‌有状态的单例Bean‌:单例Bean在Spring容器中只有一个全局共享的实例。如果这个单例Bean是有状态的,即它在多线程环境中会被多个线程共享并修改,那么它可能存在线程安全问题。解决这类问题的一种方法是使用ThreadLocal为每个线程创建独立的变量副本,从而避免线程间的干扰。
‌多例Bean‌:每次获取多例Bean时都会创建一个新的实例,因此不存在线程安全问题。多例Bean在每次调用时都是独立的,不会与其他线程共享状态。
具体实例和解决方案
‌无状态Bean‌:例如Spring MVC中的Controller,由于它只关注于方法本身,不会修改内部状态,因此是线程安全的。
‌有状态的单例Bean‌:如果需要在多线程环境中使用有状态的单例Bean,可以考虑使用ThreadLocal来为每个线程创建独立的变量副本,或者将Bean的作用域改为prototype,每次调用时都创建一个新的实例。
‌多例Bean‌:由于每次获取的都是一个新的实例,因此不需要担心线程安全问题。

六十六、集合当中的底层实现
1、ArrayList(非线程安全)底层是通过数组实现的,可以动态的扩容,默认初始空间是10,当ArrayList的size超过当前容量时,会触发扩容操作。ArrayList的扩容策略是按照1.5倍的方式进行扩容。新容量 = 旧容量 + (旧容量 >> 1),即新容量是旧容量的1.5倍。
2、Vector(线程安全)底层是通过数组实现的,可以动态的扩容和ArrayList一样
3、LinkedList(非线程安全)底层是基于双向链表结构实现的。
4、HashSet(非线程安全)底层是通过HashMap实现的,value值实际上是一个定义的常量,用key存储数据。
5、LinkedHashMap(非线程安全)底层是通过哈希表+双向链表实现的,存储数据是有序的(插入顺序)。
6、TreeMap(非线程安全)底层是通过红黑树实现的。
7、TreeSet(非线程安全)底层是通过TreeMap实现的,value值实际上是一个定义的常量
8、LinkedHashSet(非线程安全)基于LinkedHashMap实现的有序去重集合列表。有序指的是添加顺序它是HashSet和LinkedHashMap的结合体。它通过使用哈希表和双向链表来实现元素的存储和维护插入顺序。LinkedHashSet内部使用双向链表来维护元素的插入顺序。因此,通过迭LinkedHashSet,可以按照元素插入的顺序进行访问。与HashSet类似,LinkedHashSet不允许存储重复的元素。它通过哈希表来快速查找并判断元素的唯一性。LinkedHashSet和HashSet一样,允许存储一个null元素。

六十七、git常用命令
git reset head~
git reset head~2
git reset --soft 需要撤回到的commitId
git log
git add
git commit -m “本功能全部完成”
git status
git push origin HEAD:refs/for/xxxx
git rebase -i HEAD~3 (合并代码)
git diff
git pull
git stash
git unstash

六十八、ArrayList集合去重
//去除重复值(得到需要删除的)
oldList.removeAll (newList);
//得到二个list之间的重复值
List voList= oldList.stream()//将oldList转化成流
.filter(newList::contains)//oldlist和newList比较,重复值被留下
.toList();//将流转化为list

六十九、常用注解
1、@ResponseBody(将后端查询到的数据转为json格式返回给前端)SpringMVC注解
将controller中方法返回的对象通过适当的转换器转换为指定的格式之后,写入到response对象的body区,通常用来返回JSON数据或者是XML数据。用了此注解后,
控制器将不返回视图、只返回数据。
2、@RequestBody(将前端传来的参数放到方法的参数中)@RequestBody接收前端传过来的json数据(key:value)SpringMVC注解
将 HTTP 请求正文插入方法中,使用适合的 HttpMessageConverter 将请求体写入某个对象。用来接收前端传递给后端的json字符串中的数据(请求体中的数据);GET方式无请求体,
所以使用@RequestBody接收数据时,前端不能使用GET方式提交数据,而是使用POST方式进行提交。
3、@RestController(如果在控制层的类中使用它,则只能返回json或xml数据,不能返回视图)SpringMVC注解
@RestController = @Controller + @ResponseBody
4、@RequestMapping SpringMVC注解
用来处理请求地址映射的注解,可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。
5、@RequestParam SpringMVC注解
将请求参数绑定到你控制器的方法参数上(是springmvc中接收普通参数的注解)
6、@Primary Spring注解
在众多相同的Bean中,优先使用@Primary注解的Bean(主Bean)
7、@JsonFormat注解是一个时间格式化注解,比如我们存储在mysql中的数据是date类型时,当我们读取出来封装在实体类中的时候,就会变成英文时间格式,
而不是yyyy-MM-dd HH:mm:ss这样的中文时间,因此我们需要用到JsonFormat注解来格式化我们的时间。
8、@PathVariable Spring注解
是spring3.0的一个新功能:接收请求路径中占位符的值。
9、@Mapper(mybatis注解)
添加了@Mapper注解之后的接口会在编译时生成相应的实现类。
10、@MapperScan(mybatis注解)
扫描要生成实现类的接口所在的包,然后包下面的所有接口在编译之后都会生成相应的实现类。
11、@DependsOn Spring注解
@DependsOn注解可以定义在类和方法上,意思是我这个组件要依赖于另一个组件,也就是说被依赖的组件会比该组件先注册到IOC容器中。
12、@PostConstruct Spring注解
@PostConstruct该注解被用来修饰一个非静态的void()方法。被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。
PostConstruct在构造函数之后执行,init()方法之前执行。
13、@EnableScheduling和@Scheduled
定时任务在配置类上添加@EnableScheduling开启对定时任务的支持,在相应的方法上添加@Scheduled声明需要执行的定时任务。
一个是开启定时任务,一个是声明要执行的定时任务。
14、@Value
给属性注入值
@Value(“ ” ) 用来读取配置文件中的 k e y 对应的 v a l u e 值 1 、定义基本类型 @ V a l u e ( " {}”)用来读取配置文件中的key对应的value值 1、定义基本类型 @Value(" )用来读取配置文件中的key对应的value1、定义基本类型@Value("{user.test:3000}“)
private Integer num;
2、定义数组
@Value(” k u k u . t e s t : 1 , 2 , 3 , 4 , 5 " ) p r i v a t e i n t [ ] a r r a y ; 3 、定义集合 u s e r . t e s t = 10 , 11 , 12 , 13 @ V a l u e ( " {kuku.test:1,2,3,4,5}") private int[] array; 3、定义集合 user.test = 10,11,12,13 @Value(" kuku.test:1,2,3,4,5")privateint[]array;3、定义集合user.test=10,11,12,13@Value("{user.test}“)
private List test;
4、注入bean
@Value(”#{roleService}")
private RoleService roleService;
15、@Deprecated
若某类或某方法加上该注解,表示此方法或类不再建议使用,调用时也会出现删除线,但并不代表不能用,只是说,不推荐使用,因为还有更好的方法可以调用

七十、什么是雪花算法(Java实现)
‌雪花算法(Snowflake Algorithm)是Twitter开源的一种分布式ID生成算法‌。其核心思想是使用一个64位的long型数字作为全局唯一ID,通过时间戳、机器标识和序列号来确保每个节点在每个毫秒内可以生成多个唯一的ID。‌
雪花算法的核心思想包括以下几个部分:
‌时间戳‌:使用41位的时间序列,可以支持到2^41 - 1个毫秒数,即可以使用69年。
‌机器标识‌:使用10位的机器标识,最多支持部署1024个节点。
‌序列号‌:使用12位的计数序列号,支持每个节点每毫秒生成4096个ID序号。
雪花算法的优点包括:
‌全局唯一‌:通过时间戳、机器标识和序列号的组合,确保每个生成的ID是全局唯一的。
‌有序‌:由于时间戳的存在,生成的ID按时间顺序递增,便于排序。
‌高效‌:算法简单,在内存中进行,效率高。

七十一、Java中notify()和notifyAll()区别
1、notify():只随机唤醒一个正在等待该对象监视器的线程。如果有多个线程在等待,则具体唤醒哪一个线程是不确定的。
2、notifyAll():唤醒所有正在等待该对象监视器的线程。这意味着所有因调用 wait()、wait(long timeout) 或 wait(long timeout, int nanos) 而进入等待状态的线程都将被唤醒。

七十二、SpringMVC的运行流程
1、用户点击某个请求路径,发起一个 HTTP request 请求,该请求会被提交到 DispatcherServlet(前端控制器)
2、由 DispatcherServlet 请求一个或多个 HandlerMapping(处理器映射器),并返回一个执行链(HandlerExecutionChain)。
3、DispatcherServlet 将执行链返回的 Handler 信息发送给 HandlerAdapter(处理器适配器);
4、HandlerAdapter 根据 Handler 信息找到并执行相应的 Handler(常称为 Controller);
5、Handler 执行完毕后会返回给 HandlerAdapter 一个 ModelAndView 对象(Spring MVC的底层对象,包括 Model 数据模型和 View 视图信息)
6、HandlerAdapter 接收到 ModelAndView 对象后,将其返回给 DispatcherServlet ;
7、DispatcherServlet 接收到 ModelAndView 对象后,会请求 ViewResolver(视图解析器)对视图进行解析;
8、ViewResolver 根据 View 信息匹配到相应的视图结果,并返回给 DispatcherServlet;
9、DispatcherServlet 接收到具体的 View 视图后,进行视图渲染,将 Model 中的模型数据填充到 View 视图中的 request 域,生成最终的 View(视图)
10、视图负责将结果显示到浏览器(客户端)。

七十三、ThreadLocal为什么使用弱引用
‌ThreadLocal使用弱引用的主要原因是为了防止内存泄漏。‌
在Java中,ThreadLocal用于存储线程的局部变量,每个线程都有自己的ThreadLocalMap,Map中的key是ThreadLocal对象,value是用户存储的数据。
ThreadLocalMap中的key(即ThreadLocal对象)被设计为弱引用(WeakReference),而value则是强引用。这种设计的主要原因如下:
‌防止内存泄漏‌:如果ThreadLocal对象被设置为弱引用,当没有任何强引用指向ThreadLocal对象时,垃圾回收器可以回收这些对象,从而避免内存泄漏。
这是因为弱引用不会阻止垃圾回收器回收对象,而强引用则会阻止对象被回收‌。
‌线程结束时的自动清理‌:当线程结束时,由于ThreadLocal对象被设置为弱引用,垃圾回收器会自动回收这些对象,从而避免了内存泄漏。如果ThreadLocal对象是强引用,即使线程结束,这些对象也不会被回收,除非显式调用remove方法‌。
‌避免对垃圾回收的影响‌:使用弱引用可以减少对垃圾回收的影响,因为垃圾回收器可以更自由地回收那些不再被使用的对象。如果ThreadLocal对象是强引用,垃圾回收器需要更多的时间来处理这些对象,可能会影响性能‌。
弱引用的定义和特性
弱引用是一种非必需的引用类型,它不会阻止垃圾回收器回收对象。如果一个对象只被弱引用所引用,那么在垃圾回收时,这个对象可以被回收。弱引用通常用于实现缓存机制,因为它们不会阻止对象的回收‌。
实际应用场景
在实际应用中,ThreadLocal常用于存储线程的本地变量,如用户信息、数据库连接等。由于这些变量是线程独有的,使用弱引用可以确保在不需要时能够及时释放资源,避免内存泄漏。

七十四、Spring容器中的对象是否会被GC回收
Spring容器中的对象是否会被垃圾收集(GC)回收取决于这些对象的生命周期和它们在Spring容器中的作用域。
Spring容器中的对象可以分为以下几种作用域:
Singleton:在整个Spring容器中,每个bean定义只有一个对象实例。
Prototype:每次请求都会创建一个新的bean实例。
Request:每次HTTP请求都会创建一个新的bean实例,仅在当前HTTP request内有效。
Session:每次HTTP session都会创建一个新的bean实例,仅在当前HTTP session内有效。
Global Session:类似于HTTP session,但是在Portlet环境下的全局session中。
如果一个bean是单例的(singleton),并且没有其他对象保持对它的直接或间接引用,那么它可能会被垃圾收集器回收。
如果是原型的(prototype)bean,那么一旦创建了新的实例,Spring容器就不再持有对它的引用,它将成为垃圾收集器的一个候选对象。
对于作用域为request、session或global session的bean,它们通常在请求的生命周期内存在,不会被立即回收,但是在请求结束后,如果没有其他对象引用它们,它们将成为垃圾收集器的回收对象。
简单来说,Spring容器中的对象是否被GC回收取决于以下因素:
对象是否被Spring容器管理(即是否是Spring容器的一个bean)。
对象的作用域(单例还是原型)。
容器外部是否有对该对象的引用。
请注意,这是一个简化的解释,实际情况可能更复杂,因为Java的垃圾收集机制涉及到对象的可达性分析等复杂的算法和策略。

七十五、使用redis做分布式锁会出现哪些问题
锁失效:获取锁后,服务器宕机或者网络问题导致没有正确释放锁,其他服务器会错误地认为这个锁已经被占用。
死锁:一个服务器获取锁后,在处理完毕之前宕机,导致锁无法释放,其他服务器会一直等待,形成死锁。
锁抢占现象:在锁的持有时间较长时,其他服务器可能会因为等待锁而导致处理能力下降。
非原子操作:获取锁和设置过期时间可能不是原子操作,可能会出现竞争条件。
性能问题:过于频繁的锁获取和释放可能会导致Redis性能问题。
解决方案:
锁的原子性:使用SET命令的NX选项和PX选项一起设置锁,保证原子性。
锁的超时时间应合理设置,避免过期时间过短导致频繁更新锁。
锁的自动续期,即在锁到期之前自动延长锁的有效期。
监控和问题处理:通过Redis的监控和日志,及时发现并处理锁失效和死锁问题。
优化锁的使用频率和粒度,减少锁竞争。

七十六、kafka如何保证消费顺序
1. Kafka 消息的顺序保证原理
单分区内的消息顺序:Kafka 只能保证单个分区(Partition)内的消息是有序的。对于一个分区内的消息,生产者按顺序发送,消费者也会按顺序接收。
多分区的消息顺序:如果一个主题(Topic)有多个分区,Kafka 不会保证分区之间的消息顺序。需要特别设计和配置以确保全局的顺序性。
2. 确保单个分区内的顺序消费
确保单个分区内的顺序消费相对简单,只需要确保生产者和消费者的配置正确即可。
2.1 生产者配置
确保生产者按顺序发送消息到同一个分区,可以通过以下方式实现:
使用相同的分区键(Partition Key):生产者发送消息时,指定相同的分区键,使得所有消息都发送到同一个分区。
自定义分区器:如果需要更复杂的分区逻辑,可以实现自定义分区器。
2.2 消费者配置
确保消费者按顺序消费消息:
单线程消费:确保每个分区只有一个消费者线程在消费。
3. 确保多分区的顺序消费
如果需要在多个分区确保顺序消费,就需要对消息进行特殊设计和处理。
3.1 基于键的分区
通过为每个分区设置不同的键,可以在生产者端确保具有相同键的消息都发送到同一个分区,从而在消费者端按顺序消费这些消息。
3.2 全局顺序性
如果需要全局顺序性(所有消息按照严格的顺序消费),可以考虑以下方法:
使用单分区:将主题配置为只有一个分区,这样 Kafka 自然会保证所有消息的顺序。但这种做法会影响系统的吞吐量(单位时间内成功地传送数据的数量)和扩展性。
在应用层处理顺序:通过在应用层加入消息排序逻辑,确保消费者在处理消息时按顺序进行。比如,使用一个排序队列来保存消息,按顺序处理。
结合 Kafka Streams:使用 Kafka Streams 对流数据进行处理,Kafka Streams 可以管理消息顺序,并在流处理应用中提供有序的结果。
4. 确保消费逻辑的幂等性(幂等性(Idempotence)是一个数学与计算机科学概念,表示一次和多次请求某一个资源应该具有同样的作用)
即使确保了消息的顺序性,还需要确保消费逻辑具备幂等性,以防止重复消费造成的数据不一致。
使用唯一键:确保每条消息都有唯一标识,消费时检查是否已经处理过该消息。
事务支持:使用事务机制确保消息处理的一致性。
确保 Kafka 顺序消费需要结合生产者配置、消费者配置和应用设计来实现。对于单分区内的顺序保证相对简单,通过分区键或自定义分区器即可实现。对于全局顺序性,
需要在设计上进行更多考虑,如使用单分区、应用层排序或 Kafka Streams 等方法。此外,确保消费逻辑的幂等性也是顺序消费的一部分。根据具体的业务需求和系统设计,
选择合适的方法来确保消息的顺序消费。

七十七、什么是hash冲突?如何解决
哈希冲突是指在哈希表中,两个或更多个不同的键(key是不同的)被映射到了同一个哈希桶(数组下标)的情况。这种情况可能会导致数据丢失或者检索效率下降,因为不同的键被映射到了同一个位置,需要额外的操作来处理这种冲突。哈希冲突是不可避免的,因为哈希函数将可能非常大的输入空间(几乎无限的可能键)映射到一个有限的输出空间(固定大小的哈希表),这意味着不同的输入有可能映射到同一个输出(哈希值)。
1、开放寻址法:当发生冲突时,继续寻找下一个可用的位置,直到找到空闲的位置为止。这种方法可能会导致聚集(clustering)现象,即冲突位置附近的空间被更频繁地使用。
2、链地址法(Chaining):在哈希表的每个位置维护一个链表(或者其他数据结构),将具有相同哈希值的键值对存储在同一个链表中。当发生冲突时,新的键值对被添加到对应位置的链表中。这种方法需要额外的内存来存储链表,但可以避免聚集现象。
3、再哈希法:当发生冲突时,使用另一个哈希函数对键进行再次哈希,以确定下一个位置。这种方法需要选择一个合适的再哈希函数,以避免过多的冲突。
4、建立更复杂的数据结构:例如,使用平衡二叉树或者跳表等数据结构来解决冲突,这些数据结构能够保持较高的检索效率,并且能够处理冲突。当我们向 HashMap 中插入键值对时,首先通过哈希函数计算键的哈希值,然后将键值对存储在对应的哈希桶中。如果发生了哈希冲突,也就是两个不同的键具有相同的哈希值,则采用链地址法:在哈希桶中的位置上维护一个链表(Java 8 之后可能是红黑树),将具有相同哈希值的键值对按顺序存储在链表中。当发生冲突时,新的键值对会被添加到对应位置的链表的末尾。

七十八、Redis缓存和Jvm缓存有什么区别?
redis用的是服务器内存。JVM用的是服务器操作系统给分配的内存。
Redis是一个开源的内存数据库,具有高性能,支持多种数据结构和丰富的命令集,可用作数据库、缓存、消息队列等。
JVM缓存(Java Virtual Machine Cache)是指JVM在运行期所建立的内存缓存区,可以存储Java应用程序的对象,缓存对象可以避免重复计算和磁盘等外部数据源的读取,提高系统响应速度。
1、Redis侧重于内存数据库和缓存技术,而JVM内置的缓存可以通过启用某些容器(例如EHCache和MemcacheD)来实现缓存。
2、Redis的部署远比JVM复杂。Redis直接加载到服务器内存中,需要大量的配置(例如安全性、备份策略、可用性等)来防范数据丢失或受损。JVM的缓存只需要在容器中进行配置,并遵循所需技术的相关指南即可。
3、Redis对于各种数据类型的支持非常广泛,可以存储数据类型包括字符串、列表、哈希、集合和排序集。JVM通常用于对象缓存,不能像Redis一样存储多种数据类型。
4、Redis允许用户设置缓存项的过期时间(以秒为单位),这使得Redis更适合实时需要接收数据变化的应用,如web应用程序和移动应用程序。JVM的缓存是由容器或代码设置的,只要Java应用程序启动后就一直缓存,直到容器或代码调用该缓存的清除方法为止。
5、Redis将所有数据都存储在RAM(随机存取存储器)中,因此在比较大型的应用程序中,Redis在缓存方面可以同时存储大量的数据。JVM缓存通常由容器管理,缓存大小基于服务器分配给容器的可用内存大小。
6、Redis可以将缓存存储持久化在物理磁盘上,以免数据丢失。JVM缓存可以是永久缓存,但在容器(例如Tomcat)重新启动后,缓存将被清除。

七十九、Redis单线程为什么这么快?
1、基于内存操作。数据存于内存,内存读写速度比磁盘快得多,减少了数据读写的等待时间。就好比在书架上找书(内存)会比去仓库找书(磁盘)快很多。
2、高效的数据结构。Redis有多种简单高效的数据结构,像哈希表、跳表等。这些数据结构的底层实现经过优化,查找、插入、删除操作的时间复杂度比较低。
3、采用了I/O多路复用技术。它可以同时监听多个客户端连接的请求,在有请求到达时才进行处理,避免了频繁的线程上下文切换开销,就像一个前台可以同时处理多个顾客的咨询,效率很高。
缺点:
1、数据持久化问题:Redis默认将数据保存在内存中,虽然有RDB和AOF两种持久化选项,但相比专门的消息队列工具,其持久化机制略显弱势。
2、复杂功能支持不足:Redis可能无法直接支持一些高级功能,如消息确认、死信队列、优先级队列等,需要自己实现,增加了开发和维护的复杂度。
3、消息丢失风险:在高并发和大数据量的场景下,Redis可能会有消息丢失的风险,特别是在网络分区或者节点故障时。

八十、Java不能实现真正泛型的原因
泛型允许在类、接口和方法中定义类型参数,这样可以在不指定具体类型的情况下操作不同类型的数据。通过使用泛型,可以确保类型安全,避免类型转换异常。
泛型在编译和运行时的主要变化是类型擦除。‌ 在编译时,泛型类型信息会被擦除,这意味着在运行时,泛型类型的信息不再存在,不会产生额外的性能开销。
泛型的主要作用是在编译时进行类型检查,避免运行时类型转换异常或其他类型相关的问题。
优点
1、类型安全:通过泛型,Java编译器能够在编译时检查类型,避免类型转换异常。
2、代码复用:泛型使得代码更具通用性,可以在多种数据类型上重用相同的代码。
3、消除强制类型转换:使用泛型后,无需进行显式的类型转换,从而使代码更加简洁。
缺点
1、泛型的数据类型不能是基本数据类型。
2、方法重载中不能使用相同参数列表的泛型。
3、泛型的类型检查是编译期间的,通过反射机制可以绕过类型检查机制。
情况一:首先将 所有声明泛型的地方 都擦除,然后若 定义该泛型的地方 没有指定泛型上界,则 所有该泛型类型的变量的数据类型 在编译之后都替换为Object
情况二:首先将 所有声明泛型的地方 都擦除,然后若 定义该泛型的地方 指定了泛型上界,则 所有该泛型类型的变量的数据类型 在编译之后都替换为泛型上界

八十一、为什么重写equals也要重写hashcode(同一个对象hashcode一定相同,‌不同的对象有可能具有相同的hashcode值,这种情况被称为哈希冲突)
1、为了保证一个原则,equals相同的两个对象hashcode必须相同。如果重写了equals而没有重写hashcode,会出现equals相同但hashcode不相同这个现象。
2、在散列集合中,是使用hashcode来计算key应存储在hash表的索引,如果重写了equals而没有重写hashcode,会出现两个完全相同的两个对象,hashcode不同,计算出的索引不同,那么这些集合就乱套了。
3、提高效率,当我们比较两个对象是否相同的时候,先比较hashcode是否相同,如果hashcode不相同肯定不是一个对象,如果hashcode不同再调用equals来进行比较,减少比较次数提高效率

八十二、三方包下的类如何注入spring容器中
1、使用@ComponentScan指定扫描路径,让Spring自动扫描并注册这些类作为Bean。
2、使用@Bean注解在配置类中显式定义Bean。
3、使用XML配置或Java配置的方式来注册这些类。

八十三、在HashMap中用String作为key有什么好处?
1、唯一性
String在Java中是不可变的,这意味着一旦创建了一个String对象,它的内容就不能再被改变。不可变性对HashMap非常重要,因为HashMap依赖键的哈希码(hash code)来存储和查找对象。String的不可变性确保了哈希码一旦计算,键的内容不会改变,从而保证了哈希码的稳定性,避免了潜在的错误。
2、高效性
String类在Java中得到了高度优化,它的哈希码计算、高效的内存管理以及广泛的使用使得它非常适合作为HashMap的键。String类的hashCode()方法经过精心设计,能够快速计算出字符串的哈希码。hashCode()方法会遍历字符串中的每个字符,以31为乘法因子(选用31是因为它是一个小的质数,
具有良好的散列效果),生成哈希码。这样的实现既能保证哈希码的分布均匀,又能在性能上保持较高的效率。
3、易用性
String类提供了丰富的操作方法,如equals()、compareTo()、length()、substring()等,这些方法使得处理字符串非常方便。由于String类实现了equals()和hashCode()方法,开发者可以直接使用String对象作为键,而不需要额外的处理。

八十四、mysql自增主键和uuid的区别
1、自增ID的优点:
顺序插入:自增ID按顺序插入,数据页几乎没有碎片,写操作快,读操作也高效。
存储优化:顺序插入的数据行被紧密存放在一起,数据页利用率高,查询效率也有保证。
缺点:
业务信息泄露:ID的增长可以透露业务数据的增量,业务敏感性高的场景可能不适合。
高并发下的热点问题:自增ID在高并发场景下可能造成主键热点,导致锁竞争。
自增锁争夺:高并发下AUTO_INCREMENT锁竞争激烈,可能影响整体性能。
2、UUID的缺点
随机性高:UUID是无序的,插入新数据时数据库得花大力气去找位置,数据页也会经常分裂,影响效率。
增加I/O成本:页分裂导致的数据碎片增多,磁盘I/O上升,需要定期清理碎片(比如跑OPTIMIZE TABLE)。
查询不友好:UUID查起来效率也不高,因为它既不连续也不友好,数据量一多,查询性能劣势就显现出来了。

八十五、‌并发之CountDownLatch线程同步工具类
‌CountDownLatch‌是一个在Java中用于协调多个线程的同步工具类。它允许一个或多个线程等待其他线程完成某些操作后再继续执行。CountDownLatch通过一个整数计数器来控制,每当一个线程完成其任务后,它会调用countDown()方法来减少计数器的值。当计数器的值达到零时,所有等待的线程会被释放并继续执行。
‌1、初始化CountDownLatch‌:通过构造函数CountDownLatch(int count)来创建一个CountDownLatch实例,参数表示需要等待的线程数量。
‌2、等待操作完成‌:通过调用await()方法,当前线程会阻塞,直到计数器的值达到零。
3、‌完成任务‌:每个线程在完成任务后,调用countDown()方法来减少计数器的值。

八十六、‌in和exists区别
exists关键字后面的参数是一个任意的子查询,系统对子查询进行运算以判断它是否返回行,如果至少返回一行,那么exists的结果为true ,此时外层的查询语句将进行查询;
如果子查询没有返回任何行,那么exists的结果为false,此时外层语句将不进行查询。
in 关键字进行子查询时,内层查询语句仅仅返回一个数据列,这个数据列的值将提供给外层查询语句进行比较操作。
外层查询表小于子查询表,则用exists,外层查询表大于子查询表,则用in,如果外层和子查询表差不多,则爱用哪个用哪个。

八十七、dubbo服务注册与发现
Dubbo 是一种高性能的 RPC 框架,服务注册和发现对于分布式系统是核心的部分。
1、在 Provider 端,配置注册中心地址,并启动服务。‌
2、在 Consumer 端,配置注册中心地址,并消费服务注册中心(如 ZooKeeper)会存储服务提供者的地址信息,服务消费者可以从注册中心拉取服务提供者列表,实现负载均衡和远程调用。

八十八、Redis中的Hash与HashMap有什么不同?
1、在Redis中,Hash是一个键值对的集合,类似于Python中的字典。它的键值对是无序的,而HashMap是Java中的数据结构,也是一个键值对的集合,但是其键值对是有序的。
2、Hash和HashMap之间的主要区别在于有序性。Hash中的键值对是无序的,而HashMap中的键值对是有序的。
3、在实际应用中,如果需要存储无序的键值对,可以使用Hash;如果需要存储有序的键值对,可以使用HashMap。

八十九、线程池的工作原理?
1、任务提交‌:用户将任务提交给线程池,任务通常以函数指针、可调用对象或某种形式的任务对象表示。
‌2、任务队列‌:线程池维护一个任务队列,将提交的任务放入队列中等待执行。
3、‌工作线程‌:线程池中预先创建一组工作线程,这些线程不断从任务队列中取出任务执行。
‌4、任务执行‌:工作线程从任务队列中取出任务并执行,当任务执行完毕后,线程返回任务队列继续取下一个任务执行。
‌5、线程复用‌:线程池中的线程被复用,避免了频繁创建和销毁线程的开销。
‌6、线程监控‌:线程池会根据活动线程数自动新增或删除工作线程。
7、‌任务的执行和回收‌:工作线程不断从任务队列取出任务执行,直到队列为空。工作线程空闲超时后会被删除,线程池的线程数量不会无限增长‌。
‌线程池的优点‌:
‌减少线程创建和销毁的开销‌:通过复用线程,避免了频繁创建和销毁线程的开销。
‌提高系统响应速度‌:当有新的任务到来时,无需等待创建新线程就可以立即执行。
‌有效控制线程数量‌:方便进行线程管理,避免过多的线程带来的调度开销‌。

九十、‌SQL中的UNION和UNION ALL的主要区别?
‌1、结果集的处理方式不同‌:UNION会自动去除结果集中的重复行,而UNION ALL则会保留所有的行,包括重复的行。‌
2、‌排序的处理不同‌:UNION会对结果集进行排序,而UNION ALL则不会进行排序。
‌3、执行效率的差异‌:由于UNION ALL不需要去除重复行和排序,因此通常比UNION的执行效率更高。

九十一、‌索引的底层原理
索引的原理‌在于通过某种规则和数据结构,快速定位和访问数据库表中的特定信息。索引是对数据库表中一列或多列的值进行排序的一种结构,它提供指向表中物理标识这些值的数据页的逻辑指针清单。通过这些指针,数据库可以快速找到特定值,从而加快查询速度。
‌1、索引结构‌:MySQL使用B+树作为索引结构。B+树是一种多路平衡搜索树,能够保持数据有序,并且支持高效的查找、插入和删除操作。在B+树中,每个节点都存储了键值和指向子节点的指针,这使得查询可以快速定位到所需的数据。
‌2、最左前缀匹配原则‌:当创建联合索引时,例如(a, b, c),查询时会从最左边的列开始匹配。如果查询条件中的列顺序与索引列的顺序不一致,例如查询条件为c = 'x’或b = ‘y’ and c = ‘z’,则不会使用该联合索引,因为它们没有按照索引的定义顺序进行匹配。
‌3、优化技巧‌:为了优化查询性能,建议在创建联合索引时,将查询中使用频率最高且区分度最高的列放在最左边。这样可以确保在大多数情况下,查询都能有效利用索引,提高查询效率。
最左前缀原则是指在使用联合索引(复合索引)时,查询条件需要从索引的最左列开始,并且不跳过索引中的列。如果查询条件跳过了某一列,那么索引将部分失效,后面的字段索引也会失效‌。例如,如果有一个三列的索引(col1, col2, col3),查询条件需要按照col1, col2, col3的顺序来使用索引,如果跳过了col2,那么col3的索引将不会生效‌。
此外,最左前缀原则与查询条件的顺序无关,只要查询条件包含了最左列,索引就可以被使用。例如,查询条件可以是col1 = 1 AND col3 = 2,只要col1在查询条件中,索引就可以被使用‌。

九十二、‌JDK8中的新特性
1、Lambda表达式‌
2、Stream API
3、函数式接口
4、‌接口中的默认方法和静态方法
5、新的日期和时间API‌


原文地址:https://blog.csdn.net/weixin_42408447/article/details/144742027

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!