面试补充知识点
HashMap 的底层原理以及如何扩容?
- HashMap 由 数组 + 链表 + 红黑树 构成
- 初始大小为 16,通过哈希策略将数据分布到不同的桶中
- 扩容机制:
- 当数组长度小于 64 时,扩容并且会 Rehash
- 当数组长度大于等于 64 且链表长度超过 8 时,链表转为 红黑树,查询复杂度从 O(n) 变为 O(logN)
- 扩容时数组大小翻倍(*2),装载因子默认为 0.75,可自定义
Redis 的穿透、击穿、雪崩
缓存穿透
- 现象:客户端访问不存在的数据,缓存和数据库均未命中,大量请求直达数据库,导致负载过大甚至宕机
- 原因:
- 业务层误删缓存和数据库的数据
- 恶意请求不存在的数据
- 解决方案:
- 未命中时将空值写入缓存,下次请求直接返回空值
- 使用 布隆过滤器拦截不存在的请求
缓存击穿
- 现象:某个热点数据在缓存失效的瞬间,大量并发请求直达数据库,导致服务崩溃
- 解决方案:
- 对热点数据设置 永不过期
- 使用 互斥锁,一个线程重建缓存,其他线程等待
缓存雪崩
- 现象:大量缓存同时过期,或 Redis 节点故障,导致所有请求直达数据库,造成数据库宕机
- 解决方案:
- 避免同时过期,设置 随机过期时间
- 启用 降级和熔断措施
- 热点数据设置 永不过期
- 使用 Redis 集群,单点宕机时仍有节点可用
创建线程的方法?
- 继承Thread类,重写run()方法;
- 实现Runnable接口,实现run()方法;
- 实现Callable接口,实现call方法;
- 线程池创建。
Spring/SpringBoot怎么理解
- 其实Spring就是一个管理类的容器,我们通过注解将我们定义好的类添加到Spring容器里,在我们需要的时候就是用注解从Spring容器里拿出之前定义好的类,可以理解为Spring就是一个Map结构,K是类名,V是类,取出来类后就可以调用类的方法了。
- 存进银行的注解:@Component,@Component可以细化为这三个:@Service@Controller@Repository
- 从银行取出来的注解:@Resource@Autowired
单点登录和鉴权
- 可以使用SpringSecurity进行鉴权的判断,当用户第一次登陆的时候,就去数据库查询该用户的权限并封装成token,这样之后每一个请求都会去token判断用户的类型进而判断是否放行。
消息队列
- 其实消息队列就是一个Map,然后用List存取很多个Map,当生产数据的时候,就把他存进List,消费数据的时候,就是用get(),然后remove掉;
Docker
- docker是一个容器,我们之前部署上线的时候是把项目生成一个jar包,然后上Linux服务器启动这个jar包,但是我们可能还需要redis,mysql等一系列的东西,如果有多台服务器,安装就很麻烦;
- 于是我们使用docker把jar包、mysql、redis、nginx、jdk17全部打包成一个镜像,然后把这个镜像上传到linux上,这样linux只需要安装一个docker,然后docker run就可以运行了。
- 但是镜像过多,所以出现了k8s去管理镜像。
索引失效的场景有哪些?
- 使用左或者左右模糊匹配的时候,
like %超 /like %超% - 对索引使用函数:where length(name)=6,这样就没有走原来定义好的name了
- 对索引进行表达式计算:where id+1=10
- 联合索引非最左
- where的or条件前语句是索引列后语句不是where id = 1 or age = 2;id是索引但是age不是就不会走。
线程池参数该如何设置?
- 如果是IO密集型,线程的大部分时间都花在了IO上,就会导致CPU利用率低,所以我们需要多开线程让cpu运作起来,一般是2*核心数
- 如果是计算密集型,大部分线程都在处理计算任务,不会频繁进行上下文切换,所以设置为核心数+1即可
hashCode和equals方法有什么关系?
- 如果两个对象相等,那他们equals和hashCode的返回值都相等
- 但如果两个对象的hashCode返回值相同,两个对象不一定相同
- 所以重写equals方法的时候一定要重写hashCode方法,以保证在使用hash等数据结构的时候能正常工作
如何使用Spring实现事务?
- 事务四大原则ACID:原子、隔离、持久、一致
- 使用注解@Transactional
- @Transactional失效的场景有哪些?
- 方法不是public:Spring默认基于代理,只有public方法才会被代理拦截
- 方法内部调用,不会经过代理对象
- final/static方法
- 异常没有抛出
- 多线程调用,因为新线程不会继承老线程的上下文
Cookie和Session有什么区别?
- cookie和session都是用来跟踪用户状态的,不同的是cookie存在客户端,session存在用户端
- cookie容易被篡改,session相对安全
- cookie一般有大小限制,限制为4kb,session没有,但是会占用服务器内存;
在分布式系统下,很多网站会使用jwt,token+redis进行登录。
过滤器和拦截器有什么区别?
- 过滤器(Filter)只能拿到
HttpServletResponse以及HttpServletRequest,不知道调用的是哪一个Controller - 拦截器能精准的拦截到某一个Controller
过滤器通常用于统一编码处理,日志打印;而拦截器用来做登录校验,权限校验。
HashMap和ConcurrentHashMap有什么区别?后者如何保证线程安全?
- HashMap是非线程安全的,多个线程同时写有可能会导致数据不一致;ConcurrentHashMap是线程安全的,通过
CAS+synchronized来保证线程安全,在链表/红黑树的节点级别加锁,实现更细粒度的并发控制,提高性能。 - 不用Hashtable的原因是Hashtable粗暴的给整个表加锁;常见的原子方法有computeIfAbsent
什么是数据库事务?其四大特性(ACID)是什么?
- 数据库事务就是把一组数据库操作当作一个整体,要么全部成功,要么全部失败
- 原子性、隔离性、持久性、一致性
- 隔离级别是读未提交、读提交、可重复读、串行化
String\StringBuilder\StringBuffer有什么区别
- 他们三个都是处理字符串的类
- String不可变,StringBuilder和StringBuffer可变
- 但StringBuilder不是线程安全的,StringBuffer带synchronized,线程安全
如果需要线程安全的可变字符串,才会考虑StringBuffer
HashMap的数据结构,put流程,以及发生hash冲突的解决方法,为什么是红黑树而不是二叉树
- HashMap的底层是
数组+链表+红黑树,当链表长度过长(超过8)转为红黑树 - put流程
- 先根据key的hashCode进行hash运算确定下标
- 如果该位置为空直接放进去
- 如果不为空,比较key,如果相同就覆盖掉,不同就挂在链表/红黑树上
- 插入后若超出阈值(0.75)会触发扩容
- 如果使用二叉树有可能会退化成链表,最坏的查询复杂度是O(n),而红黑树是自平衡二叉搜索树,删改查的复杂度为O(n*logn)
MySQL锁
- 全局锁:对整个表加锁,变为只读状态/可以在开始备份的时候创建一个Read View
- 表级锁:
- 表锁
- 表读锁:多个线程可以同时读,但不能写
- 表写锁:只有持锁线程可以读写,其他线程的读写都会被阻塞
- 意向锁
- 表锁
Synchronized和ReentrantLock的区别
- Synchronized是关键字,ReentrantLock是类,需要手动lock(),unlock();
- 都是可重入锁,同一个线程可以反复去获得锁
- synchronized自动获取、自动释放、不可中断,非公平
- ReentrantLock可中断、可以尝试去获得锁、提供Condition对象实现多条件等待/通知
- ReentrantLock默认构造的是非公平锁,获取锁的时候会直接尝试CAS抢占;如果使用构造
ReentrantLock relock = new ReentrantLock(true);就是公平锁,会按照锁的请求顺序(FIFO),底层是AQS(AbstractQueuedSynchronizer)维护了一个队列,获取锁的时候会先去看队列是否有线程,如果有的话就排队,没有就获取锁。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 Explainfuture's Blog!
