面试总结20250421-2

Posted by WK on 2025-04-21
Estimated Reading Time 20 Minutes
Words 5.6k In Total
Viewed Times

jdk8 开始,hashmap 底层原理?

  1. 数据+链表+红黑树:每个 HashMap 实例都有一个默认大小的数组(桶),初始容量为 16,默认加载因子(load factor)为 0.75。当向 HashMap 中添加键值对时,首先根据键的哈希值计算出该键值对应该存放的桶的位置。如果发生哈希冲突(即多个键值对映射到同一个桶),则这些冲突的元素会以链表的形式链接起来。一旦某个桶中的链表长度超过特定阈值(默认为 8),并且 HashMap 的容量至少为 64,则这个链表会被转换成一棵红黑树,以此来提高查找效率。
  2. 引入红黑树解决哈希冲突:
    1. 在 JDK 8 之前,处理哈希冲突的方式仅通过链表实现。当出现大量的哈希冲突时,查找时间复杂度可能退化至 O(n)。
    2. 引入红黑树后,在最坏的情况下(即存在大量哈希冲突)查找时间复杂度降低到了 O(log n),大大提高了性能。
  3. 扩容机制:当 HashMap 中的元素数量超过了负载因子与当前容量的乘积时,会发生扩容操作,即将数组的容量扩大一倍,并重新计算每个元素的新位置(rehashing)。此过程有助于保持较低的哈希冲突率和良好的性能。
  4. 哈希操作:
    1. 判断是否为 null 键:如果键为 null,则返回哈希值 0。HashMap 允许 null 作为键,并将其固定存储在数组的第一个位置(索引为 0 的桶中)。
    2. 获取原始哈希码:调用键对象的 hashCode() 方法,得到原始哈希码 h。
    3. 高位与低位异或运算:将原始哈希码 h 的高 16 位与低 16 位进行异或运算:(h >>> 16) 表示将哈希码无符号右移 16 位,然后与原哈希码进行异或操作。
    4. 这种扰动操作的目的是让高位的信息参与到最终的哈希值计算中,从而提高哈希分布的均匀性。

mysql 最佳左匹配原则是什么?

  1. 从最左边的列开始使用:当使用复合索引时,查询条件必须从索引的最左边的列开始使用才能利用到该索引。这意味着如果有一个索引是(col1, col2, col3),那么查询条件中至少要包含 col1 才能用到这个索引。仅仅使用 col2 或 col3 作为查询条件无法直接利用这个复合索引。
  2. 连续性:一旦跳过了某个索引列,后面的列也无法利用此索引。例如,在上面提到的索引结构下,WHERE col1 = ? AND col2 = ?可以有效利用索引,但是 WHERE col1 = ? AND col3 = ?则不能完全利用该索引,因为跳过了 col2。
  3. 部分使用:如果查询条件包含了复合索引的前几列,即使没有涵盖所有列,也可以部分地利用该索引。例如,对于索引(col1, col2, col3),查询 WHERE col1 = ?可以直接使用该索引来过滤数据;而 WHERE col1 = ? AND col2 = ?能更精确地缩小搜索范围。
  4. 范围查询的影响:一旦在查询条件中出现了范围查询(如 BETWEEN, <, >等),后续的列将不能再使用索引进行精确查找。比如,在索引(col1, col2, col3)中,WHERE col1 = ? AND col2 BETWEEN ? AND ?只能有效利用到 col1 和 col2 上的索引,col3 将无法再利用该索引进行优化。

select(年龄,姓名,id), from 表 where 年龄=xx,走全表扫描了吗?

  1. 全表扫描:如果查询条件没有任何索引,则会进行全表扫描,扫描整个表,然后再应用查询条件进行过滤。
  2. 索引扫描:如果查询条件有索引,则会使用索引进行快速定位,然后根据索引返回的结果进行过滤。
  3. 索引失效:如果查询条件有索引,但是索引失效了,则会进行全表扫描。

慢查询分析,怎么优化 sql?

  1. 使用 EXPLAIN 分析查询计划
    • type:访问类型(如 ALL 表示全表扫描,ref 表示索引查找)。
    • key:实际使用的索引。
    • rows:扫描的行数(越少越好)。
    • Extra:额外信息(如 Using filesort 或 Using temporary 表示可能有性能问题)。
  2. 优化索引:添加合适的索引、避免冗余索引、覆盖索引
  3. 优化查询语句:避免全表扫描、减少返回的数据量、避免不必要的排序和分组
  4. 优化表结构:选择合适的数据类型、拆分大表、归档历史数据
  5. 优化 JOIN 操作:确保 JOIN 列上有索引、避免笛卡尔积、减少 JOIN 表的数量
  6. 使用缓存:应用层缓存、MySQL 查询缓存

SELECT *怎么优化?

为什么需要优化 SELECT *?

  1. 返回不必要的数据:SELECT * 会返回表中的所有列,即使你只需要其中的一部分字段。这会增加网络传输的开销,并占用更多的内存。
  2. 无法使用覆盖索引:如果查询的所有字段都包含在索引中(即覆盖索引),MySQL 可以直接从索引中获取数据,而无需回表查询。使用 SELECT * 通常会导致 MySQL 需要访问数据表本身,从而降低性能。
  3. 影响 JOIN 性能:在多表连接时,SELECT * 会返回所有表的所有字段,可能导致大量的冗余数据传输和更高的计算开销。
  4. 维护困难:使用 SELECT * 会使代码可读性变差,难以清楚地知道具体需要哪些字段。如果表结构发生变化(如新增或删除字段),可能引发意外问题。

如何优化 SELECT *?

  1. 明确指定需要的字段

  2. 利用覆盖索引

  3. 避免 SELECT COUNT(*):

    1
    2
    SELECT COUNT(*) FROM table_name;
    SELECT EXISTS (SELECT 1 FROM users WHERE age > 30);
  4. 分页查询优化:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    -- 优化前
    SELECT * FROM users ORDER BY id LIMIT 10000, 10;

    -- 优化后
    SELECT id, name, email
    FROM users
    WHERE id > last_seen_id
    ORDER BY id
    LIMIT 10;
    ```
  5. 减少大字段的影响:

    1
    2
    3
    4
    5
    6
     -- 优化前
    SELECT * FROM articles;

    -- 优化后
    SELECT id, title, author FROM articles;
    ```
  6. 联合查询优化:在多表连接时,避免使用 SELECT *,只选择需要的字段。

  7. 使用视图或存储过程

  8. 动态生成查询语句

分页怎么分,什么语句?

  1. 使用 LIMIT 和 OFFSET
  2. 使用主键或唯一索引优化:
    1
    2
    3
    4
    5
    SELECT id, name, email
    FROM users
    WHERE id > last_seen_id
    ORDER BY id
    LIMIT 10;
  3. 使用覆盖索引

LIMIT 深分页怎么处理?

在处理大数据量的分页查询时,使用 LIMIT 和 OFFSET 的方式(即深分页)会导致性能问题。这是因为即使你只需要少量的数据,数据库仍然需要扫描并跳过前面的所有行。例如:

1
SELECT * FROM users ORDER BY id LIMIT 10 OFFSET 100000;

在这种情况下,数据库需要扫描前 100,000 行数据才能返回第 100,001 到 100,010 行的数据,这显然是低效的。

  1. 使用主键或唯一索引进行分页:

    1
    2
    3
    4
    5
    SELECT id, name, email
    FROM users
    WHERE id > last_seen_id
    ORDER BY id
    LIMIT 10, 100000;

    这种方式可以利用索引进行快速定位,并只返回需要的数据。

  2. 使用覆盖索引: CREATE INDEX idx_id_name_email ON users(id, name, email);

  3. 使用延迟关联(Deferred Join)/子查询:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    -- 先通过索引快速定位需要的主键
    SELECT id
    FROM users
    ORDER BY id
    LIMIT 10 OFFSET 100000;

    -- 再根据主键回表查询完整数据
    SELECT *
    FROM users
    WHERE id IN (/* 上一步的结果 */);

mysql 隔离级别哪些?

隔离级别 脏读 不可重复读 幻读
READ-UNCOMMITTED
READ-COMMITTED ×
REPEATABLE-READ × ×
SERIALIZABLE × × ×

事务 acid 特性?

  1. A: Atomicity(原子性)

事务的原子性是指事务是一个不可分割的工作单位,事务中包括的诸操作要么全部完成,要么全部不完成,事务的操作不受其他操作的影响。

  1. C: Consistency(一致性)

事务的一致性是指事务必须是数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。

  1. I: Isolation(隔离性)

事务的隔离性是指一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

  1. D: Durability(持久性)

事务的持久性是指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。

反向代理 / 正向代理?

  • 正向代理:从客户端的角度出发,帮助客户端访问外部资源,隐藏客户端的身份。
  • 反向代理:从服务器的角度出发,保护后端服务器,隐藏后端服务器的身份,并提供负载均衡、缓存等功能。

mysql 存储引擎?

  1. InnoDB
  • 支持事务(ACID 特性)。
  • 支持行级锁(Row-Level Locking),适合高并发环境。
  • 支持外键约束(Foreign Key)。
  • 提供崩溃恢复能力(Crash Recovery)。
  • 使用聚簇索引(Clustered Index),主键查询性能优异。
  • 数据存储在表空间中,支持大表和高并发操作。
  • 适用场景:
  • 需要事务支持的应用(如银行系统、电商系统)。
  • 高并发读写场景。
  • 数据完整性和一致性要求较高的场景。
  1. MyISAM
  • 不支持事务。
  • 支持表级锁(Table-Level Locking),并发性能较差。
  • 查询速度快,尤其适合以读为主的场景。
  • 支持全文索引(Full-Text Index),但在 MySQL 5.6 之后,InnoDB 也支持全文索引。
  • 数据存储在 .MYD(数据文件)和 .MYI(索引文件)中。
  • 不支持外键。
  • 适用场景:
  • 以读为主的应用(如日志系统、数据仓库)。
  • 不需要事务支持的场景。

InnoDB 行锁实现?

数据库主从复制原理?

  1. 主服务器(Master):处理所有的写操作(如 INSERT、UPDATE、DELETE),并记录这些操作到二进制日志(Binary Log)中。
  2. 从服务器(Slave):负责读取主服务器的二进制日志,并将其应用到自己的数据库中,从而保持与主服务器的数据同步。

redis 的持久化方式?

  1. RDB 持久化:RDB 持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,它恢复时是将快照文件直接读到内存里。RDB 持久化可以配置,默认配置是每隔 10 分钟执行一次,可以根据需要修改。RDB 持久化是 Redis 默认的持久化方式,但它不支持 AOF 持久化。
  2. AOF 持久化:AOF 持久化是指将写操作命令记录到文件中,并在 Redis 启动时,从文件中读取并重新执行这些命令,AOF 持久化可以配置,默认配置是每秒钟执行一次,可以根据需要修改。AOF 持久化支持多种同步策略,如 always、everysec、noappendfsync。AOF 持久化比 RDB 持久化更加安全,因为 AOF 持久化可以记录更多的命令,即使 Redis 发生故障也只会丢失一秒钟的数据。

简单说,缓存穿透,击穿,雪崩是什么,怎么解决?

  1. 缓存穿透:指查询一个一定不存在的数据,由于缓存是不命中时被动写到数据库,并且出于容错考虑,如果从数据库查询不到对应的数据,则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,失去了缓存的意义。
  2. 缓存击穿:指缓存中有某个热点数据,当大量的请求访问这个热点数据时,缓存失效,大量请求直接访问数据库,数据库压力过大,甚至导致雪崩。
  3. 缓存雪崩:指缓存服务器重启或者大量缓存集中失效,所有请求都直接访问数据库,数据库瘫痪,甚至宕机。
  4. 解决方案:
    1. 缓存空值:当缓存和数据库中都没有数据时,直接返回空值,避免缓存穿透。
    2. 缓存永不过期:当缓存和数据库中都有数据时,设置一个较短的过期时间,避免缓存击穿。
    3. 缓存更新策略:当缓存和数据库中都有数据时,更新缓存,避免缓存雪崩。
    4. 缓存预热:将热点数据提前加载到缓存中,避免缓存穿透和缓存雪崩。

分布式锁除了 redis 还有什么方法?

  1. 数据库锁:可以通过数据库的唯一约束或事务来实现分布式锁。例如,在一个表中插入一条记录,这条记录的存在就代表了锁的状态。这种方法简单易懂,但性能通常不如专门的分布式锁服务。
  2. Zookeeper:Apache Zookeeper 是一个分布式的协调服务,可以用来实现分布式锁。通过创建临时顺序节点,可以确保同一时间只有一个客户端能够获取到锁。Zookeeper 提供了强大的一致性和可靠性保障,但是它的性能可能比不上基于内存的解决方案(如 Redis)。

假如有一组数据,有序的,增删多,用什么集合?

  1. TreeSet
    • 特点: 基于红黑树实现,元素天然有序(默认升序),支持快速插入、删除和查找。
    • 时间复杂度: 插入、删除和查找的时间复杂度为 O(log n)。
    • 适用场景: 如果需要维护一个动态有序集合,并且频繁进行增删操作,TreeSet 是一个很好的选择。
  2. LinkedList
    • 特点: 基于双向链表实现,支持快速的头尾插入和删除(O(1)),但中间插入和删除需要先找到位置(O(n))。
    • 适用场景: 如果数据量较小或者主要对头部或尾部进行操作,LinkedList 是可选方案。

jvm 内存结构

  1. 堆: 存放对象实例,是垃圾收集器管理的主要区域,也是 Java 内存管理的核心区域。
  2. 方法区: 存放类信息、常量、静态变量、即时编译器编译后的代码等数据。
  3. 虚拟机栈: 存放局部变量表、操作栈、动态链接、返回地址等信息。
  4. 本地方法栈: 存放 native 方法信息。
  5. 程序计数器: 存放当前线程执行的字节码的地址。
  6. 直接内存: 直接内存并不是虚拟机运行时数据区的一部分,而是通过 Native 函数库直接分配堆外内存。

垃圾回收算法哪几种

  1. 标记-清除算法:首先标记出所有需要回收的对象,标记完成后统一回收。
  2. 复制算法:将内存分为两块,每次只使用其中一块,当这一块内存用完时,就将还存活的对象复制到另一块内存上,然后再把使用过的内存空间一次清理掉。
  3. 标记-整理算法:标记-清除算法的基础上,再进行一次碎片整理。
  4. 分代收集算法:根据对象存活周期不同,将内存分为几块,不同块采用不同的算法进行垃圾回收。

threallocal 原理

ThreadLocal 是 Java 提供的一种用于实现线程本地存储的机制,它允许每个线程拥有某个变量的独立副本,从而避免线程间的共享和竞争。简单来说,ThreadLocal 的核心作用是为每个线程提供一个独立的变量副本,确保线程之间的数据隔离。

  • 线程隔离:每个线程都有自己独立的变量副本,线程之间互不干扰。
  • 无需同步:由于每个线程操作的是自己的副本,因此不需要使用锁来保证线程安全。
  • 生命周期绑定到线程:ThreadLocal 中存储的数据与线程的生命周期绑定,当线程结束时,存储在 ThreadLocal 中的数据也会被回收(前提是正确清理)。

工作原理:

  • 每个线程(Thread)内部都有一个名为 threadLocals 的 ThreadLocalMap 对象。
  • 当调用 ThreadLocal 的 set() 方法时,会将当前线程作为键,要存储的值作为值,存入该线程的 threadLocals 映射中。
  • 当调用 get() 方法时,会从当前线程的 threadLocals 中查找对应的值。
  • 如果没有调用过 set() 方法,则会返回 initialValue() 方法提供的默认值(默认为 null)。

ThreadLocalMap 防止内存泄漏

ThreadLocalMap 是 ThreadLocal 的核心实现,它负责存储每个线程的变量副本。然而,由于其特殊的存储机制(键是弱引用,值是强引用),如果使用不当,可能会导致内存泄漏问题。

为什么会有内存泄漏的风险?

  • 键是弱引用:在 ThreadLocalMap 中,键(ThreadLocal 实例)是通过弱引用(WeakReference)存储的。这意味着当没有其他强引用指向 ThreadLocal 实例时,垃圾回收器会回收该实例。
  • 值是强引用:值(存储的数据)是强引用,即使 ThreadLocal 实例被回收了,值仍然会被 ThreadLocalMap 强引用着,不会被垃圾回收。
  • 线程长期存活:如果线程长时间运行(例如线程池中的线程),而 ThreadLocal 实例被回收后没有及时清理对应的值,那么这些值会一直占用内存,无法释放。

如何防止内存泄漏?

  1. 显式调用 remove() 方法
  2. 垃圾回收时自动清理

classloader 方法重写了会怎么样

在 Java 中,ClassLoader 是负责加载类的核心组件。它定义了几个关键方法,用于实现类的加载逻辑。如果重写这些方法,可能会影响类加载的行为。

  1. findClass(String name): 该方法用于加载指定类。如果重写该方法,则会导致自定义类的加载方式。从文件系统、网络或其他非标准位置加载类。
  2. loadClass(String name):默认双亲委派机制,重写此方法并跳过父类加载器,可能会破坏双亲委派机制,导致核心类库被重复加载或出现类冲突。
  3. defineClass(String name, byte[] b, int off, int len):默认行为:将字节数组转换为 Class 对象,是类加载的核心步骤。通常不建议重写此方法,因为它是 JVM 内部实现的一部分。
  4. findResource(String name) 和 findResources(String name):默认用于查找类路径中的资源文件,可以通过重写这些方法来自定义资源加载逻辑,例如从网络或数据库中加载资源。

springboot 用到了哪些设计模式?

  • 工厂设计模式 : Spring 使用工厂模式通过 BeanFactory、ApplicationContext 创建 bean 对象。
  • 代理设计模式 : Spring AOP 功能的实现。
  • 单例设计模式 : Spring 中的 Bean 默认都是单例的。
  • 模板方法模式 : Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。
  • 包装器设计模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
  • 观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。
  • 适配器模式 : Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配 Controller。

工厂方法模式一般用在哪?

  1. 当一个类不知道它所必须创建的对象的类的时候

  2. 当一个类希望它的子类来指定它所创建的对象的时候

  3. 类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候

  4. 应用:框架开发、图形用户界面(GUI)工具包、支持多样的文件格式处理

简单说,mysql 慢查询分析举例子,索引失效情况

  1. 慢查询通常指的是那些执行时间超过预设阈值的 SQL 查询
  2. 索引失效情况分析:
    • 隐式类型转换:如果 department_id 列定义为整数类型,而查询条件使用了字符串(例如 WHERE department_id = ‘10’),MySQL 可能需要进行隐式类型转换,这会导致索引失效。
    • 函数使用:如果在索引列上使用了函数,如 WHERE YEAR(hire_date) > 2010,MySQL 将无法有效利用 hire_date 上的索引,因为索引不能用于函数计算的结果。
    • 范围查询后的索引使用:在这个查询中,虽然 department_id 上有索引,但是由于 hire_date 也参与了查询条件,且是一个范围查询,根据 MySQL 索引最左前缀原则,可能会导致后续的索引部分(如果有)失效或效率降低。
    • 不等于和 NULL 检查:!=, NOT IN, IS NULL, IS NOT NULL 等操作符可能导致索引失效或仅部分使用索引,因为这些操作通常需要扫描整个索引树。

redis 怎么保证原子操作

  • 单线程模型:天然保证了简单命令的原子性。
  • 原子性命令:许多内置命令本身就是原子操作。
  • 事务(MULTI/EXEC):将多个命令打包成一个事务,保证按顺序执行。
  • Lua 脚本:在服务器端以原子方式执行复杂逻辑。
  • 分布式锁:通过锁机制协调多个客户端对共享资源的访问。
  • 乐观锁(WATCH/MULTI/EXEC):通过监控键的变化来避免并发冲突。

redis 大 key 怎么处理,简单说

  1. 设置过期时间
  2. 逐步删除
  3. 设计数据结构时考虑将大数据分散存储到多个较小的 key 中
  4. 使用 redis-cli --bigkeys 命令,它可以快速扫描并报告数据库中最大的 key。

简单说,饭店老板餐馆 20 个桌子,设计用户点餐,怎么设计系统

购物车怎么实现

如果 2 个人一起点,怎么让两个人相互看到对方点的,

redis 怎么设计数据结构


If you like this blog or find it useful for you, you are welcome to comment on it. You are also welcome to share this blog, so that more people can participate in it. If the images used in the blog infringe your copyright, please contact the author to delete them. Thank you !