HashMap 的底层原理以及如何扩容?

  • HashMap 由 数组 + 链表 + 红黑树 构成
  • 初始大小为 16,通过哈希策略将数据分布到不同的桶中
  • 扩容机制:
    • 当数组长度小于 64 时,扩容并且会 Rehash
    • 当数组长度大于等于 64 且链表长度超过 8 时,链表转为 红黑树,查询复杂度从 O(n) 变为 O(logN)
    • 扩容时数组大小翻倍(*2),装载因子默认为 0.75,可自定义

Redis 的穿透、击穿、雪崩

缓存穿透

  • 现象:客户端访问不存在的数据,缓存和数据库均未命中,大量请求直达数据库,导致负载过大甚至宕机
  • 原因
    • 业务层误删缓存和数据库的数据
    • 恶意请求不存在的数据
  • 解决方案
    1. 未命中时将空值写入缓存,下次请求直接返回空值
    2. 使用 布隆过滤器拦截不存在的请求

缓存击穿

  • 现象:某个热点数据在缓存失效的瞬间,大量并发请求直达数据库,导致服务崩溃
  • 解决方案
    1. 对热点数据设置 永不过期
    2. 使用 互斥锁,一个线程重建缓存,其他线程等待

缓存雪崩

  • 现象:大量缓存同时过期,或 Redis 节点故障,导致所有请求直达数据库,造成数据库宕机
  • 解决方案
    1. 避免同时过期,设置 随机过期时间
    2. 启用 降级和熔断措施
    3. 热点数据设置 永不过期
    4. 使用 Redis 集群,单点宕机时仍有节点可用

创建线程的方法?

  1. 继承Thread类,重写run()方法;
  2. 实现Runnable接口,实现run()方法;
  3. 实现Callable接口,实现call方法;
  4. 线程池创建。

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)维护了一个队列,获取锁的时候会先去看队列是否有线程,如果有的话就排队,没有就获取锁。