Java面试题

Redis篇章

Redis使用场景

缓存穿透

  1. 什么是缓存穿透
    1. 当查询的数据不存在时,每次查询都需要经过redis和db但是无法查询到数据,称做缓存穿透。
  2. 如何解决缓存穿透
    1. 将DB返回的空缓存到redis中,可以有效防止缓存穿透。
    2. 使用布隆过滤器
      1. 什么是布隆过滤器
        1. 在redis之前,当查询数据时,会先经过布隆过滤器查看数据是否存在,如果存在,则到redis中去查找,若数据不存在,则直接返回。
        2. 布隆过滤器底层是用bitmap(位图)来实现的,对数据进行三次hash运算,获得三个索引值,起初所有的bitmap的值都是0,将三个索引值变为1。
        3. 缺点:当数组长度小时,容易产生误判,即一个数据不存在时,但是经过三次hash运算获得的三个索引值,都存有数据1,则产生了误判,但是位图的误判率小于百分之5,可以忽略。

缓存击穿

  1. 什么是缓存击穿
    1. 当缓存的key过期了,在这个过期的节点,突然有大量访问请求进入,导致服务器处理不过来,称为缓存击穿。不同于缓存穿透,穿透是经过了redis和db都无法查询到数据。击穿是将缓存穿烂了。
  2. 如何解决缓存击穿
    1. 互斥锁:强一致、性能差
      1. 当一个请求过来,开启线程一,当线程一访问redis缓存时,没有命中,然后去获取互斥锁,接着去db查询数据,重建缓存,最后写入缓存,并释放锁。而在释放锁之前,有线程二出现,线程二查询缓存没有命中,获取互斥锁失败,休眠一会继续重试,直到查询到了缓存。返回的数据永远同db中相同,称为强一致,性能差则是当没有查询到缓存时,只能有一个线程获取锁,其他线程只能等待。
    2. 逻辑过期:高可用、性能优
      1. 不设置key的过期时间,设置一个逻辑过期字段。当线程一查询缓存发现逻辑过期,获取互斥锁,然后开启新线程二(该线程去查询db重建缓存,重置逻辑过期时间,最后释放锁),而线程一则会返回过期数据。其他线程类似,无论如何都会返回数据。保证了每次访问都能有返回数据,性能非常好。
    3. 两种解决方法的比较:都会过去互斥锁,但是唯一的不同在于,一个只会返回一致的数据,另外一个获取锁失败会返回过期数据。

缓存雪崩

  1. 什么是缓存雪崩
    1. 当大量缓存都设置了相同的过期时间,当缓存都过期了的时候,或者是redis服务器宕机,大量请求同时访问db,db压力过大雪崩。与击穿相比,击穿是某一个Key过期。
  2. 如何解决缓存雪崩
    1. 给缓存key设置不同的过期时间。
    2. 开启redis集群
    3. 采用限流熔断机制进行保底处理
    4. 开启多级缓存

如何保证数据库和Redis的数据一致性

  1. 双写一致性:当修改了数据库的数据的同时也要同时更新缓存的数据。
    1. 读操作:缓存命中,直接返回;缓存没有命中查询数据库,写入缓存,设定超时时间。
    2. 写操作:延迟双删,先删除缓存---->修改数据库---->延时一会----->删除缓存
      1. 先删除缓存还是先修改数据库
        1. 都可能出现脏数据
      2. 为什么要双删?
        1. 降低脏数据的出现
      3. 为什么要延时?
        1. 让主从数据库同步,但是延时的时间不好控制,所以还是有可能出现脏数据
  2. 添加分布式锁
    1. 效率太低
  3. redisson的读写锁
    1. 共享锁:其他线程只能读,进行读操作时添加。
    2. 排它锁:其他线程没有任何操作权限,进行写操作时添加。
    3. 强一致、性能低。
  4. 异步通知
    1. 使用MQ消息中间件
      1. 更新数据之后,通知缓存删除。
    2. 使用基于mysql的主从同步的Canal
      1. 不需要修改业务代码,伪装为MySQL的一个从节点,读取mysql的binlog数据更新缓存。

Redis作为缓存,数据的持久化是怎么做的

  1. RDB:Redis Database Backup file(Redis数据备份文件),redis数据快照。把内存中的所有数据都记录到磁盘中,当redis实例故障重启后,从磁盘读取快照文件,恢复数据。
  2. 如何开启RDB
    1. 手动开启:save 、bgsave
    2. 自动开启:在redis.conf文件中配置
  3. 执行原理
    1. 当bgsave时,主进程会fork一个子进程出来,主进程通过页表来操作真实内存数据,子进程fork的也只是主进程的页表,此时内存中的数据是read-only,子进程读取数据到一个新的RDB中,并且会替换原先的RDB。
    2. 如果需要写数据,主进程进行写数据时,会对原数据进行拷贝,然后在备份上进行读写操作,同时修改页表对应的内存数据为备份数据。
  4. AOF:Append Only File(追加文件)。Redis处理的每一个命令都会记录在AOF文件,可以看做是命令日志文件。默认关闭,需要修改redis.conf来开启AOF。AOF的命令记录的频率也通过配置文件来配置。AOF记录的命令操作,对同一个key的命令通常只有最后一个写操作才有意义,可以通过执行bgwriteaof命令,让AOF文件执行重写功能,用最少得命令达到相同效果。
  5. RDB与AOF的比较
    1. 持久化方式:RDB是对整个内存进行快照,AOF记录每一次执行的命令。
    2. 数据完整性:不完整,两次备份之间会丢失数据(例如,备份时间设置为60s,这时间内redis宕机了),AOF相对完整,取决于刷盘策略。
    3. 文件大小:AOF文件体积很大。
    4. 宕机恢复速度:RDF快
    5. 数据恢复优先级:RDF低,因为数据完整性不如AOF。
    6. 系统资源占用:RDF高,备份整个内存,要占用大量CPU和内存消耗。AOF低,主要是磁盘IO资源,但是AOF重写时,CPU和内存资源占用大。
    7. 使用场景:RDB可以容忍数分钟的数据丢失,追求更快的启动速度。AOF是对数据安全性要求高。

假如Redis的key过期之后,会立即删除吗?(数据过期策略)

  1. 惰性删除:当访问某个Key时,如果key过期了则删除,否则返回这个数据。
    1. 优点:对一些不常用的Key 不用去定期检查是否过期。
    2. 缺点:浪费内存,如果key已经过期了,但是一直没有使用,则会一直保存在内存中。
  2. 定期删除:每隔一段时间,选择一些key进行过期检查,如果过期则删除。每个key都会检查到。
    1. 定期删除的两种模式
      1. SLOW模式:定时任务,执行频率默认为10hz(每秒执行10次),每次不超过25ms,通过redis.conf文件修改。
      2. FAST模式:执行频率不固定,但每次间隔不低于2ms,每次耗时不超过1ms
    2. 优点:可以通过限制删除操作执行的时长和频率来减少删除操作对CPU的影响。也能有效释放过期key占用的内存。
    3. 缺点:难以确定删除操作执行的时长和频率。
  3. redis的过期策略是惰性删除和定期删除两种策略进行配合使用。

Redis的数据淘汰策略

  1. redis提供了8种不同的数据淘汰策略,默认是不删除任何数据,内存不足直接报错。
  2. 两种重要的淘汰策略:
    1. LRU:最近最久未使用算法
    2. LFU:最少频率使用算法。
  3. 最常用的淘汰策略:allkeys-lru:挑选最近最久未使用的数据淘汰策略,留下来的都是经常访问的热点数据。

Redis分布式锁

  1. 背景
    1. 当一个项目进行集群的时候,synchonize锁已经无法满足需要,引入分布式锁,让每个集群的线程都共享同一个锁。
  2. redisson
    1. 执行流程:线程一获取redission锁,同时会开辟一个新线程watchdog来对锁进行续期。线程二获取redission锁时,会有一个while循环来重复获取锁,这个循环次数可以控制。
    2. 原理:底层时setnx和lua脚本(保证原子性)
    3. 可以重入吗:可以,底层时hash结构,key是锁,value是线程信息和重入的次数。
    4. 能解决主从数据一致的问题吗:
      1. 什么是主从数据一致?
        1. redis集群中所有数据一致,当主节点还没有将数据同步到从节点时,主节点宕机了,redis提供的哨兵模式,会在从节点中找到一个作为主节点。新线程来访问redis时,会访问新的主节点。导致宕机前的线程和新线程持有同一把锁 ,可能导致脏数据。
        2. 如何解决?
          1. 不能解决,但是可以使用红锁,但是这样的话,性能太低了。建议采用zookeeper实现的分布式锁。

其他redis面试题

主从复制

  1. 是什么
    1. 单节点的redis的并发能力有限,要进一步提高redis的并发能力,就需要搭建主从集群,实现读写分离,主节点负责写数据,从节点负责读数据。
  2. 全量同步:
    1. 从节点请求主节点同步数据(replication id、offset)
    2. 主节点判断是否是第一次请求,是的话就与从节点同步版本信息。
    3. 主节点执行bgsave,生成rdb文件,发送给从节点去执行。
    4. 在rdb文件生成期间,主节点下执行的命令会记录到一个日志文件中。
    5. 把日志文件发送给从节点进行同步。
  3. 增量同步:
    1. 从节点请求主节点同步数据,主节点判断不是第一次请求,获取从节点的offset
    2. 主节点的日志文件获取offset值之后的数据,发送给从节点进行数据同步。

哨兵模式

  1. 哨兵的作用
    1. 监控:监控主从节点是否正常工作
    2. 自动故障恢复:
    3. 通知:通知客户端去redis服务器获取数据
  2. redis脑裂
    1. 什么是脑裂
      1. redis集群时,当主服务器和从服务器不在一个网络分区时,网络延迟时,哨兵没有感知到主服务器的存在,这时,哨兵会将一个从变主,当哨兵感知到老主的存在时,会将老主降为从节点,则会导致大量数据丢失。
    2. 如何预防脑裂
      1. 通过redis.conf进行配置,可以设置最少得从节点数量以及缩短主从数据同步的延迟时间,达不到要求就拒绝请求,就可以避免大量的数据丢失。

分片集群

  1. 分片集群有什么作用?
    1. 集群中有多个master,每个master保存不同的数据
    2. 每个master有多个slave节点
    3. master之间通过Ping检测彼此健康状态
    4. 客户端请求可以访问任意集群节点,最终都会被转发到正确的节点
  2. redis分片集群中数据时怎么存储和读取的?
    1. redis分片集群引入了哈希槽的概念,redis集群有16384个哈希槽
    2. 将16384个插槽分配到不同的实例
    3. 读写数据:根据Key的有效部分计算哈希值,对16384取余(有效部分,如果key前面有大括号,则有效部分是大括号的内容,否则是key本身),余数作为插槽,寻找插槽所有的实例。

redis是单线程的,为什么还那么快?

  1. 结论
    1. redis直接操作内存
    2. 单线程,避免了不必要的上下文切换可竞争条件,不存在线程安全问题
    3. 采用I/O多路复用模型
  2. 解释一下I/O多路复用模型
    1. Redis是纯内存操作,执行速度快,性能瓶颈是网络延迟,I/O多路复用模型主要就是实现了高效的网络请求。
    2. I/O多路复用:
      1. 单线程同时监听多个socket,只要有一个socket有回应时就会得到通知,避免无效等待,充分利用CPU资源。目前采用的是epoll模式,会在socket就绪的同时,将其写入用户空间,不需要redis挨个便利socket来判断是否就绪,提升了性能。
    3. Redis网络模型
      1. 使用I/O多路复用结合事件的处理器来应对多个socket。
        1. 连接应答处理器
        2. 命令回复处理器,在6.0之后,使用多线程
        3. 命令请求处理器,在6.0之后,将命令的转化使用了多线程,增加命令转换速度,在命令执行的时候,依然是单线程。

MySQL篇章

优化

在MySQL中,如何定位慢查询?

  1. 采用第三工具,例如运维工具Skywalking,可以监测处哪个接口执行慢。
  2. 在mysql中开启慢日志查询,例如设置的值为2s,则一旦sql执行 超过2s就会记录到日志中(调试阶段)

那这个SQL语句执行很慢,如何分析呢?

可以采用MySQL自带的分析工具EXOLAIN

  1. 通过key和key_len检查是否命中了索引(索引本身存在是否有失效的情况)
  2. 通过type字段查看sql是否有进一步的优化空间,是否存在全索引扫描或全盘扫描
  3. 通过extra建议判断,是否出现了回表的情况,如果出现了,可以尝试添加索引或修改字段来修复。

索引概念和底层数据结构

什么是索引?
  1. 索引是帮助MySQL高效获取数据的数据结果
  2. 提高数据检索的效率
  3. 通过索引列对数据进行排序,降低数据排序的成本,降低了CPU的消耗
有了解过底层数据结构吗?

MySQL的innoDB引擎采用的B+树的数据结构来存储索引

  1. 阶数更多,路径更短
  2. 叶子节点存储数据,非叶子节点只存储指针
  3. B+树便于扫库和区间耗材想你,叶子节点是一个双向链表

什么是聚簇索引?什么是非聚簇索引(二级索引)

聚集索引:数据与索引放到一块,B+树的叶子节点保存了整行数据,有且只有一个

二级索引:数据与索引分开,B+树的叶子节点保存对应的主键,可以有多个

什么是回表查询?

​ 通过二级索引找到对应的主键值,到聚集索引中查找整行数据,这个过程就是回表。

什么是覆盖索引?

查询使用了索引,并且返回的列在索引中全部能找到。

MySQL超大分页怎么处理?

  1. 问题:在数据量较大的时候,limit分页查询,需要对数据进行排序,效率低
  2. 解决方案:覆盖索引+子查询,先分页查询数据的id字段,确定了id之后,再用子查询来过滤,只查询这个id列表中的数据,因为查询id的时候采用的是覆盖索引。

索引创建的原则

  1. 数据量大,查询频繁的表
  2. 常作为where order by group by 操作的字段
  3. 字段内容区分度高
  4. 尽量使用联合索引
  5. 控制索引的数量

什么情况下,索引会失效?

  1. 违反最左前缀法则:当联合索引时,必须按照从左到右索引来查询,不能跳过索引。
  2. 范围查询右边的列,不能使用索引:如果依次查询的条件有一个条件是范围,则这个条件右边的查询列不能使用索引。
  3. 对索引列进行运算操作
  4. 字符串不加单引号
  5. 以%开头的模糊查询

谈谈你对sql的优化的经验?

  1. 表的优化设计(参考阿里开发手册)
    1. 设置合适的数值
    2. 设置合适的字符串类型
  2. 索引优化
    1. 为常作为where,order by ,group by 的字段创建索引
    2. 为字段区分度高的字段创建索引
    3. 使用联合索引
    4. 控制索引的数量
    5. 避免索引失效的操作:例如最左,范围查询右边,索引列进行运算操作等
  3. sql语句优化
    1. select语句指定字段名称
    2. 避免造成索引失效的写法
    3. 避免在where中对字段进行表达式操作
    4. Join优化,使用inner join,内连接会对两个表进行优化,优先把小表放到外边,大表放到里边。
  4. 主从负载、读写分离,不让数据的写入,影响读操作
  5. 分库分表

其他面试题

事务的特性是什么?详细说一下

  1. 原子性Atomicity
  2. 一致性Consistency
  3. 隔离性Isolation
  4. 持久性Durability
  5. 例子:A向B转账500元,转账成功,A扣除500,B增加500,原子性是要不全部成功,要不全部失败。在转账的过程中,数据要保存一致,A扣除500,B必须增加500。转账过程不受其他事务的影响。转账完成之后,要不数据持久化。

并发事务问题、隔离级别

  1. 并发事务问题
    1. 脏读:一个事务读到另一个事务还没有提交的数据
    2. 不可重复读:一个事务先后读取到同一条记录,但两次读取的数据不同
    3. 幻读:一个事务查询一条数据时,刚开始不存在,但是在插入数据时,发现这行数据已经存在。
  2. 怎么解决?
    1. 对事务进行隔离
      1. 读未提交(read uncommitted):不能解决
      2. 读已提交(read committed):解决了脏读
      3. 可重复读(默认级别 repeatable read):解决了脏读、不可重复读
      4. 串行化(serializable):全部解决

undo log 和 redo log 的区别?

  1. redo log:记录的是数据页的物理变化,服务宕机可用来同步数据
  2. undo log:记录的是逻辑日志,当事务回滚时,通过逆操作恢复原来的数据
  3. redo log保证了事务的持久性,undo log 保证了事务的原子性和一致性。

事务中的隔离性如何保证?

  1. 锁:排他锁(当一个事务获取了排它锁,其他事务就无法获取)
  2. mvcc:多版本并发控制

解释一下mvcc

MySQL中的多版本并发控制,指维护一个数据的多个版本,使得读写操作没有冲突。

  1. 隐藏字段
    1. trx_id(事务id):记录每一次操作的事务id,自增
    2. roll_pointer(回滚指针):指向上一个版本的事务版本记录地址
  2. undo log:
    1. 回滚日志,存储老版本数据
    2. 版本链:多个书屋并行操作某一行记录,记录不同事务修改数据的版本,通过roll_pointer指针形成一个链表
  3. readView解决的是一个事务查询选择版本的问题
    1. 根据readView的匹配规则和当前一些事务id判断该访问哪个版本的数据
    2. 不同的隔离界别快照读是不一样的,最终的访问的结果不一样
      1. RC(读已提交):每一次执行快照读时生成ReadView
      2. RR(可重复读):仅在事务中第一次执行快照读时生成ReadView,后续复用。

MySQL主从同步原理

核心就是二进制日志binlog,包含DDL(数据定义语言)和DML(数据操纵语言)

  1. 主库在事务提交时,会把数据变更记录在二进制日志文件Binlog中。
  2. 从库读取主库的二进制日志文件Binlog,写入到从库的中继日志Relay Log。
  3. 从库重做中继日志中的事件,将改变反映它自己的数据。

分库分表

  1. 水平分库:将一个库的数据拆分到多个库中,解决海量数据存储的和高并发的问题
  2. 水平分表:解决单表存储和性能的问题
  3. 垂直分库:根据业务进行拆分,例如用户服务和订单服务各放一个数据库中
  4. 垂直分表:冷热数据分离,例如一个数据表中的多个字段,有些字段很少用,这个字段则提取出来单独放置一个表中。

框架篇

Spring

单例bean是线程安全的吗?

不是线程安全的。Spring框架中有一个@Scope注解,默认的值就是singleton,单例的。因为一般在spring中的bean都是注入无状态的对象,没有线程安全问题。如果在bean中定义了可修改的成员变量,是要考虑线程安全问题的,可以使用多例或者加锁来解决。

AOP的相关面试题

什么是AOP?

面向切面编程,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取公共模块复用,降低耦合。

项目中使用到了吗?

记录操作日志、登录权限校验,通过环绕通知 + 切点表达式/自定义注解

Spring中的事务是如何实现的?

利用了AOP编程,对方法前后进行拦截,在执行方法之前开启事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

事务失效的场景

  1. 异常捕获处理,自己处理了异常(自己进行catch),没有抛出,解决:手动抛出
  2. 抛出检查异常,配置rollbackFor属性为Exception
  3. 非public方法导致的事务失效,改为public

Spring的bean的生命周期

  1. 通过BeanDefinition获取bean的定义信息
    1. spring在进行实例时,会将xml中的的信息封装成一个BeanDefinition对象。
  2. 调用构造函数实例化bean
  3. bean的依赖注入
  4. 处理Aware接口(实现三个aware接口,重写三个方法)
  5. Bean的后置处理器BeanPostProcessor-前置
  6. 初始化方法,实现initializingBean接口,自定义的初始化方法
  7. Bean的后置处理器BeanPostProcessor-后置
  8. 销毁bean

Spring的循环依赖

  1. 什么是循环依赖:两个或两个以上的bean相互持有对方,比如A依赖B,B依赖A。
  2. 一级缓存:单例池,缓存已经经历了完整的生命周期,已经初始化完成的bean对象
  3. 二级缓存:缓存早起的bean对象
  4. 三级缓存:缓存的是ObejctFactory,对象工厂,用来创建某个对象
  5. 构造方法出现了循环依赖如何解决?
    1. 使用@Lazy进行懒加载

SpringMVC的执行流程

  1. 用户发出请求到前端控制器DispatcherServlet
  2. DispatcherServlet收到请求调用handlerMapping(处理器映射器)
  3. HandlerMapping找到具体的处理器,生成处理器对象及处理器拦截器,再一起返回给DispatcherServlet
  4. DispatcherServlet调用HandlerAdapter(处理器适配器)
  5. HandlerAdapter经过适配调用具体的处理器(Handler/Controller)
  6. 方法上添加了@ResponseBody
  7. 通过HttpMessageConverter来返回结果转换为JSON并响应

SpringBoot自动配置原理

  1. 引导类上有一个注解@SpringBootApplication,这个注解是对三个注解进行了封装,分别是:
    1. @SpringBootConfiguration
    2. @EnableAutoConfiguration
    3. @ComponentScan
  2. @EnableAutoConfiguration是实现自动化配置的核心注解,该注解通过@import注解导入对应的配置选择器。
    1. 内部就是读取了该项目和该项目引用的jar包的classpath路径下META-INF/spring.factories文件中的所配置的类的全类名,在这些配置类中所定义的Bean会根据条件注解所制定的条件来决定是否需要将其导入到Spring容器中。
  3. 条件判断会有像@ConditionalOnClass这样的注解,判断是否有对应的class文件,如果有则加载该类,把这个配置类的所有的Bean放入spring容器中使用。

Spring框架常见注解

Spring的注解

  1. Component Controller Service Responsitory:实例化bean
  2. @Autowired
  3. @Qualifier:根据名称进行依赖注入
  4. @Scope:标注bean的作用范围
  5. @Configuration
  6. @ComponentScan:用于指定Spring在初始化容器时要扫描的包
  7. @Bean
  8. @Import
  9. @Aspect、Befor、After、Around、Pointcut

SpringMVC的注解

  1. @RequestMapping
  2. @RequestBody
  3. @RequestParam:指定请求参数的名称
  4. @PathVirable:从请求路径中获取请求参数(/user/id),传递给方法的形式参数
  5. @ResponseBody
  6. @RequestHeader:获取指定的请求头数据
  7. @RestController:@Controller + @ResponseBody

SpringBoot常见注解

  1. @SpringBootConfiguration:组合了@Configuration注解,实现配置文件的功能。
  2. @EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选择。
  3. @ComponentScan:Spring组件扫描

Mybatis执行流程

  1. 读取Mybatis配置文件:mybatis-config.xml加载运行环境和映射文件
  2. 构造会话工厂SqlSessionFactory
  3. 会话工厂创建SqlSession对象(包含了执行SQL语句的所有方法)
  4. 操作数据库的接口,Excutor执行器,同时负责查询缓存的维护。
  5. Executor接口的执行方法中有一个MapperStatement类型的参数,封装了映射信息
  6. 输入参数映射
  7. 输出结果映射

Mybaits是否支持延迟加载?

支持延迟加载,但默认没有开启。

  1. 什么叫做延迟加载?
    1. 例如,一个用户表有一个字段表示对应的订单表,一个用户对应多个订单,然后当查询用户的时候,订单的数据不会立即查询。订单字段加上fetch = lazy。
  2. 原理
    1. 使用CGLIB创建目标的代理对象
    2. 当调用目标方法时,进入拦截器Invoke方法,发现目标方法是null值,执行sql查询
    3. 获取数据以后,调用set方法设置属性值,再继续查询目标方法,就有值了。

Mybatis的一级、二级缓存用过吗?

  1. 一级缓存:基于PerpetualCache的HashMap本地缓存,其存储作用域为Session,当session进行flush或者close之后,该session中的所有Cache将被清空,默认打开一级缓存。
  2. 二级缓存:基于namespace和mapper的作用域,不依赖于sql session。需要单独开启,核心配置,mapper映射文件。

二级缓存什么时候会清理缓存中的数据?

当某一个作用域(一级缓存session或者二级缓存namespaces)进行增删改操作,默认该作用域下索引select的缓存被清空。

集合篇

ArrayList底层的实现原理是什么?

  1. 动态数组
  2. 初始容量为0,当第一次添加数据时,才会初始化容量为10
  3. 进行扩容的时候,为原来容量的1.5倍,每次扩容时都需要拷贝数组。
  4. 在添加数据时,
    1. 判断size+1是否大于当前的数组长度,则调用grow方法扩容。

如何实现数组和List之间的转换?

  1. 数组转list集合:调用java.utils.Arrays包的asList方法
  2. list集合转数组:调用list集合的toArray(参数为数组类型和长度)
  3. 如果数组或者list集合内容改变,转换后的集合或者数组会变吗?
    1. asList会变化,因为底层使用的是,Arrays类中的一个内部类ArraysList,最终指向的都是同一个内存地址。
    2. toArray不会变化,底层是对数组的拷贝。

ArrayList和LinkedList的区别是什么

  1. 底层数据结构
    1. 动态数组
    2. 双向链表
  2. 效率
    1. 前者可以根据索引查询
    2. 头尾增删都是O(1),其他需要遍历,为O(n)
  3. 空间
    1. 前者内存连续,节省内存
    2. 双向链表更占用内存
  4. 安全问题
    1. 都不是线程安全

说一下HashMap的实现原理?

  1. 底层数据结构是散列表(数组+链表\红黑树)
  2. 添加数据时,计算key的值确定元素在数组中的下标
    1. key相同则替换
    2. 不同则存入链表或红黑树中

HashMap的jdk1.7和1.8有什么区别?

  1. 1.8之前采用的拉链法,数组和链表
  2. 1.8之后采用数组+链表+红黑树,链表长度大于8且数组长度大于64则会从链表转化为红黑树。

HashMap的put方法的具体流程

  1. 判断table是否为空或者长度是否为0,则进行resize扩容
  2. 根据key计算hash值得到数组索引
  3. 判断该位置是否为空,直接添加
  4. 否则,
    1. 判断首个元素的key是否相等,相等则覆盖value
    2. 判断链表是否为红黑树,在树中插入键值对
    3. 否则在链表尾部插入数据,然后判断链表长度是否大于8,进入树化方法(在这个方法里面会判断如果数组长度小于64,则进行扩容,反之才真正树化)
  5. 插入成功后,判断实际存在的键值对数量size是否超过数组长度0.75。

HashMap的扩容机制

调用resize方法。

  1. 首先判断oldCap是否为0,如果为0,则将newCap设置为16,newThr为16*0.75=12
  2. 如果大于0,则将newCap和newThr扩容为原来的2倍。
  3. 同时,将数组进行复制,判断节点是否为空,
    1. 节点为空,跳过
    2. 节点不为空,判断下一个节点是否为空(判断是否产生哈希冲突)
      1. 为空,e.hash&newcap-1计算索引,添加到新数组。
      2. 不为空,判断是否是树节点,红黑树添加。反之,遍历链表,可能需要拆分链表,判断e.hash&oldCap是否为0,该元素的位置要么停留在原始位置,要么移动到原始位置+oldCap这个位置

HashMap的寻址算法

  1. 计算对象的hashCode()
  2. 调用hash方法进行二次哈希,hashcode值右移16位再进行异或运算。扰动算法,让哈希分布更加均匀。
  3. 最后cap-1&hash得到索引。

HashMap的数组长度一定是2的次幂?

  1. 计算索引时效率更高:如果是2的n次幂,可以使用位运算代替取模
  2. 扩容时重新计算索引效率更高:hash&oldCap==0的元素留在原来位置,否则新位置=旧位置+oldCap

HashMap在1.7的情况下的多线程死循环问题?

  1. jdk1.7中,hashMap的底层数据结构是数组和链表,进行扩容时,迁移链表是头插法。
  2. 链表AB,线程一将A移入新链表,线程二介入,将老链表的AB移入新链表,BA,B指向A,线程二执行完毕。线程一将B移入新链表,但是此时由于线程二的原因,B指向了A,导致了死循环。JDK8使用尾插法解决了这个死循环问题。

微服务篇

SpringCloud的五大组件?

  1. Eureka、
  2. Ribbon:负载均衡
  3. Feign:远程调用
  4. Hystrix:服务熔断
  5. Zuul、Gateway:网关

SpringCloudAlibba的五大组件?

  1. Nacos
  2. Ribbon
  3. Feign
  4. Sentinel
  5. Gateway

Nacos和Eureka的区别

  1. 共同点
    1. 都支持服务注册和发现
    2. 健康检查
  2. 不同点
    1. Nacos支持服务端主动监测提供者状态:临时实例采用心跳模式,非临时实例采用主动监测模式
    2. 非临时实例心跳不健康不会被剔除
    3. Nacos支持服务列表变更的消息推送模式,服务列表更新更及时。
    4. Nacos集群默认采用AP(高可用)方式,当集群中存在非临时实例时,采用CP(强一致)模式,Eureka采用AP方式
    5. Nacos支持配置中心

Ribbon负载均衡

你们项目负载均衡如何实现?

使用Ribbon,远程调用的Feign的底层的负载均衡使用的是ribbon

Ribbon负载均衡的策略有哪些?

  1. 简单轮询
  2. 按照权重轮询
  3. 随机选择一个可用的服务器
  4. 区域敏感策略:以区域可用的服务器为基础进行服务器的选择。

如果想自定义负载均衡策略如何实现

  1. 实现IRule接口,可以指定负载均衡策略(全局)
  2. 在客户端的配置文件中,可以配置某一个服务器调研的负载均衡策略(局部)

什么是服务雪崩,如何解决?

  1. 服务雪崩是微服务中,某一个服务失败,导致整个链路的服务都失败。
  2. 如何解决:
    1. 服务降级:一般与feign接口整合,编写降级逻辑,针对某个接口
    2. 服务熔断:接口调用超时比率达到一个阈值,会开启熔断,后续的请求直接执行设置的默认方法,达到服务降级的效果。

微服务如何监控

采用Skywalking。

  1. 可以看到哪些接口和服务比较慢,可以进行针对性的分析和优化。
  2. 可以设置告警规则,如果报错,分别设置了可以给相关负责人发短信和发邮件,第一时间知道项目的Bug,第一时间修复。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值