第六节 面试问题-初级版

亮子 2025-09-05 10:22:24 104 0 0 0

1、Java的基本数据类型有哪些?

8种:byte、short、int、long、float、double、char、boolean。

记忆方法:按“整数型-浮点型-字符型-布尔型”分类记。
- 整数型4种(byte、short、int、long,范围从小到大);
- 浮点型2种(float、double,精度从低到高);
- 字符型1种(char);
- 布尔型1种(boolean)。

2、Java的引用数据类型有哪些?

Java的引用数据类型包括:类(class)、接口(interface)、数组(array)、枚举(enum)、注解(annotation)。

记忆方法:可记为“类接数枚注”,对应“类、接口、数组、枚举、注解”,通过谐音和顺序帮助记忆,这些类型都指向对象,存储的是对象的引用地址。

3、java的包装类有什么作用?

(1) 使基本数据类型**具备对象特性**,可参与泛型、集合操作
(2)提供**类型转换**、常量等实用方法(如Integer.parseInt())
(3)方便在**需要对象的场景**中使用基本类型(如反射、序列化)

记忆方法:“包装基本成对象,泛型集合能进入,转换常量有帮助”——核心是将基本类型“包装”成对象,解决基本类型无法直接用于泛型、集合等需要对象的场景,并提供实用工具方法。

4、什么叫装箱拆箱?

装箱:将基本数据类型转换为对应包装类对象(如int→Integer)。
拆箱:将包装类对象转换为对应基本数据类型(如Integer→int)。

记忆方法:“基本进箱变对象(装箱),对象出箱回基本(拆箱)”——把“箱”理解为包装类,形象记忆转换方向和结果。

5、面向对象编程有哪些特点?

三大特点:封装、继承、多态。

记忆方法:“封继多”——谐音联想,封装是隐藏细节,继承是复用扩展,多态是同一行为不同实现,三者是面向对象的核心支柱。

6、== 和 equals 的区别是什么?

==:
- 基本类型:比较值是否相等
- 引用类型:比较对象地址是否相同(是否为同一对象)

equals:
- 默认(Object类):同==,比较地址
- 重写后(如String、包装类):比较对象内容是否相等

记忆方法:“==比地址,重写equals比内容”——基本类型特殊,==直接比 value;引用类型==比地址,equals需看是否重写,重写后通常比内容。

7、说说你对HashCode的理解

HashCode是对象的哈希值,由Object类的hashCode()方法生成,是一个int类型数值。

主要作用:
(1) 用于哈希表(如HashMap)中快速定位对象,提高查询效率
(2) 与equals配合使用,遵循“相等对象必同哈希码,同哈希码对象不一定相等”原则

记忆方法: “哈希码是对象指纹,表中定位快如神,相等对象码必等,同码对象未必同”——核心记住哈希码的定位作用及与equals的关系准则。

8、java的容器(集合)有哪些?

Java容器主要分为两类:
(1) Collection接口(存储单个元素):
- List(有序可重复):ArrayList、LinkedList、Vector
- Set(无序不可重复):HashSet、TreeSet、LinkedHashSet
- Queue(队列):LinkedList、PriorityQueue

(2) Map接口(存储键值对):HashMap、TreeMap、LinkedHashMap、Hashtable、ConcurrentHashMap

记忆方法: “Collect存单元素,List有序Set唯一,Map键值成对居;Array快链增删易,Hash无序Tree有序”——按接口分类,结合典型实现类的特点辅助记忆。

9、请说一下ArrayList的扩容机制

ArrayList扩容机制:
1. 初始容量默认为10(无参构造)
2. 当添加元素导致容量不足时,触发扩容
3. 扩容公式:新容量 = 旧容量 + 旧容量/2(即1.5倍)
4. 若1.5倍后仍不足,则直接扩容至所需容量
5. 通过Arrays.copyOf()复制原数组到新数组

记忆方法: “初始10满就扩,1.5倍是基础,不够直接按需扩,复制数组换新家”——核心记住触发条件、扩容倍数及底层复制操作。

10、请说一下HashMap的扩容机制

HashMap的扩容机制(以JDK1.8为例):
1. 默认初始容量16,负载因子0.75,当元素数超过容量×负载因子(12)时触发扩容
2. 扩容后容量变为原来的2倍(左移1位)
3. 扩容时重新哈希:原数组元素重新计算在新数组中的位置(因容量是2的幂,可通过高位运算简化计算)
4. 链表长度≥8且数组容量<64时,先扩容而非转红黑树

记忆方法: “16初始0.75载,超12就扩两倍来,重算位置新位栽,链长8时先扩再树开”——核心记容量、负载因子、扩容倍数及与红黑树转换的关联。

image.png

11、单例模式有哪些实现方式?

单例模式主要实现方式:
1. 饿汉式:类加载时初始化实例(线程安全,可能浪费资源)
2. 懒汉式(线程不安全):首次使用时创建,多线程有风险
3. 懒汉式(线程安全):加synchronized同步(效率低)
4. 双重检查锁:两次判空+volatile,兼顾安全与效率
5. 静态内部类:利用类加载机制实现延迟加载和线程安全
6. 枚举:天然防止反射和序列化破坏单例,简洁安全

记忆方法: “饿汉加载急,懒汉用再砌;双重锁防弃,静态内部避;枚举最彻底”——按初始化时机和安全性特点分类记忆,枚举是最优方案之一。

12、JDK1.8有哪些新特?

JDK1.8的主要新特性:
1. Lambda表达式:简化匿名内部类写法,函数式编程支持
2. Stream API:简化集合数据处理,支持链式操作
3. 接口默认方法:接口可定义带实现的default方法
4. 新时间API:LocalDateTime等,线程安全且易用
5. 方法引用:通过::符号复用已有方法
6. Optional类:避免空指针异常的容器类

记忆方法:“拉姆达简化代码,Stream流处理集合,接口默认方法好,时间API更可靠,方法引用少冗余,Optional空指针跑”——按功能领域串联核心特性,突出各自解决的问题。

13、字节流和字符流区别?

字节流和字符流的区别:
1. 处理单位:字节流以字节(8位)为单位,字符流以字符(16位,对应Unicode)为单位
2. 适用场景:字节流适合处理二进制数据(如图片、音频),字符流适合处理文本数据(需考虑编码)
3. 底层实现:字符流底层依赖字节流,会使用缓冲区并进行编码转换

记忆方法: “字节8位通吃二进制,字符16位专司文本义;字节直接读写快,字符编码转换来”——核心区分处理单位和适用场景,字符流本质是带编码处理的字节流封装。

14、JDK、JRE、JVM他们之间有什么区别?

  • JVM(Java虚拟机):负责执行字节码文件,是跨平台的核心
  • JRE(Java运行时环境):包含JVM+运行Java程序所需的核心类库
  • JDK(Java开发工具包):包含JRE+开发工具(编译器、调试器等)

记忆方法: “JVM是引擎,JRE加类库能运行,JDK含工具可开发”——从功能范围理解:JDK ⊇ JRE ⊇ JVM,开发用JDK,运行用JRE,跨平台靠JVM。

15、String、StringBuffer与StringBuilder之间有什么区别?

String、StringBuffer、StringBuilder的区别:
(1) 可变性:String不可变(每次修改生成新对象);StringBuffer和StringBuilder可变(直接修改自身)
(2) 线程安全:StringBuffer线程安全(方法加synchronized);StringBuilder线程不安全
(3) 性能:String修改性能差;StringBuilder性能优于StringBuffer(无同步开销)

记忆方法: “String不可变,缓冲(Buffer)安全慢,构建(Builder)可变快,单线程用后者,多线程选前者”——核心记可变性、线程安全与性能的对应关系,按使用场景选择。


16、线程的创建有哪几种方式?

线程创建有4种方式:
1. 继承Thread类,重写run()方法
2. 实现Runnable接口,重写run()方法
3. 实现Callable接口,重写call()方法(有返回值,可抛异常)
4. 使用线程池(如ExecutorService)创建

记忆方法: “继承Thread,实现Runnable,还有Callable带返回,线程池里批量给”——前两种无返回值,Callable有返回值,线程池适合管理多个线程。

17、线程池的参数有哪些?

线程池(以ThreadPoolExecutor为例)核心参数有7个:
1. 核心线程数(corePoolSize):常驻核心线程数量
2. 最大线程数(maximumPoolSize):线程池允许的最大线程数
3. 空闲时间(keepAliveTime):非核心线程闲置后的存活时间
4. 时间单位(unit):keepAliveTime的时间单位
5. 工作队列(workQueue):用于存放等待执行的任务
6. 线程工厂(threadFactory):创建线程的工厂
7. 拒绝策略(handler):任务满时的处理策略

记忆方法: “核心最大定范围,空闲时间加单位,工作队列存任务,工厂策略不可无”——按线程数量控制、任务管理、辅助配置三类划分记忆。

18、线程池都有哪些类型?

Java线程池主要类型(基于Executors工具类创建):
1. FixedThreadPool:**固定线程数的线程池**,核心线程=最大线程
2. SingleThreadExecutor:**单线程线程池**,仅1个线程执行任务
3. CachedThreadPool:**可缓存线程池**,线程数可动态增减(60秒空闲销毁)
4. ScheduledThreadPool:**定时/周期性执行任务的线程池**
5. SingleThreadScheduledExecutor:**单线程定时任务线程池**

记忆方法: “固定数、单线程、可缓存、定时行”——按线程数量特性和任务类型区分,Fixed固定、Single单一、Cached动态、Scheduled定时。

19、请说说线程池的工作原理?

线程池工作原理:
1. 提交任务时,先判断核心线程是否已满:未满则创建核心线程执行任务
2. 核心线程满时,任务放入工作队列等待
3. 队列满时,判断是否达到最大线程数:未达到则创建非核心线程执行任务
4. 达到最大线程数时,触发拒绝策略处理任务
5. 非核心线程空闲超过keepAliveTime时销毁,核心线程默认不销毁(可通过allowCoreThreadTimeOut调整)

记忆方法: “核心线程先干活,满了队列来暂存,队列满了扩线程,扩到最大就拒绝,空闲线程会销毁”——按任务提交后的流程顺序记忆,从核心线程到队列再到最大线程数的递进逻辑。

image.png

20、线程的状态都有哪些?

线程的状态(JDK定义)有6种:
1. 新建(New):刚创建,未调用start()
2. 运行(Runnable):包含就绪(等待CPU调度)和正在运行
3. 阻塞(Blocked):等待锁释放(如synchronized竞争)
4. 等待(Waiting):无时间限制等待(如wait()、join())
5. 超时等待(Timed Waiting):有时间限制等待(如sleep(ms)、wait(ms))
6. 终止(Terminated):线程执行完毕

记忆方法: “新启运行,阻等超时,最终终止”——按线程生命周期顺序:从新建到启动运行,中间可能进入阻塞、等待或超时等待,最后执行完毕终止。

image.png

21、synchronized与Lock有啥区别?

synchronized与Lock的区别主要体现在以下几个方面:

  1. 实现方式
  • synchronized是Java内置关键字,由JVM层面实现
  • Lock是接口,通过ReentrantLock等实现类提供功能,是API层面的实现
  1. 锁的释放
  • synchronized自动释放锁(代码块执行完毕或异常时)
  • Lock需手动释放(必须在finally中调用unlock(),否则可能导致死锁)
  1. 锁的获取
  • synchronized无法获取锁的状态,只能被动等待
  • Lock可尝试获取锁(tryLock()),能判断是否获取成功,可设置超时
  1. 功能扩展性
  • synchronized功能简单,无法中断等待锁的线程
  • Lock支持中断(lockInterruptibly())、公平锁等高级特性
  1. 性能
  • 低竞争场景下,synchronized性能接近Lock
  • 高竞争场景下,Lock性能更优(JDK1.6后synchronized做了优化,差距缩小)

记忆方法: “sync内置自动放,Lock手动需释放,尝试中断加公平,Lock灵活性能强”——核心区分实现层面、释放方式和功能灵活性。

22、线程的start()和run()有啥区别?

start()和run()的核心区别在于是否启动新线程:

  1. start()
  • 启动新线程,使线程进入就绪状态(等待CPU调度)
  • 由JVM调用线程的run()方法,不能重复调用(否则抛IllegalThreadStateException)
  1. run()
  • 仅作为普通方法执行,不会创建新线程
  • 直接调用时在当前线程中执行,可重复调用
  • 是线程的实际执行逻辑入口

记忆方法: “start()启新线,run()只是方法现;start()调一次,run()可多遍”——关键记住start()的核心作用是启动新线程,而run()只是线程要执行的任务逻辑。

23、java中的wait()和sleep()有啥区别?

wait()和sleep()的核心区别在于是否释放锁及使用场景:

  1. 所属类不同
  • wait()是Object类的方法
  • sleep()是Thread类的静态方法
  1. 锁的处理
  • wait()会释放当前对象的锁,让其他线程可获取该锁
  • sleep()不会释放任何锁,持有锁时其他线程仍无法获取
  1. 唤醒方式
  • wait()需被其他线程调用notify()/notifyAll()唤醒(或等待超时)
  • sleep()到指定时间后自动唤醒
  1. 使用场景
  • wait()用于线程间通信(如生产者消费者模型)
  • sleep()用于暂停当前线程执行一段时间

记忆方法: “wait放锁等通知,sleep持锁到点醒;wait属于Object,sleep是Thread静态”——核心区分锁的释放行为和使用目的,这是两者最关键的差异。

24、悲观锁和乐观锁有啥区别?

悲观锁和乐观锁是并发控制的两种思想,核心区别在于对待数据冲突的假设和处理方式:

  1. 核心假设
  • 悲观锁:假设数据总会发生冲突,因此操作前先锁定资源,阻止其他线程修改(“先上锁,再操作”)
  • 乐观锁:假设数据很少发生冲突,操作时不锁定资源,仅在提交时检查是否有冲突(“先操作,再校验”)
  1. 实现方式
  • 悲观锁:依赖数据库锁机制(如MySQL的行锁、表锁)或Java的synchronized/Lock
  • 乐观锁:通常通过版本号(version)或CAS(Compare And Swap)机制实现
  1. 适用场景
  • 悲观锁:适合写操作频繁、冲突概率高的场景(如库存扣减)
  • 乐观锁:适合读操作频繁、冲突概率低的场景(如商品详情查看)
  1. 性能影响
  • 悲观锁:因频繁加锁释放,可能导致线程阻塞,性能开销较大
  • 乐观锁:无锁竞争,性能更好,但冲突时需重试,可能带来额外开销

记忆方法: “悲观总防冲突来,先锁资源再操作;乐观默认冲突少,提交时候再校验”——核心记住两者对冲突的假设和处理时机的差异。

25、什么是锁?

在并发编程中,**锁(Lock)是控制多个个线程对共享资源访问的同步机制**,用于解决多线程同时操作共享数据时可能出现的线程安全问题(如数据不一致、脏读、竞态条件等)。

简单说,锁的核心作用是**保证同一时间只有一个(或指定数量的)线程能执行特定代码块**,从而实现对共享资源的有序访问。

锁的关键特性:
1. 排他性:多数锁(如synchronized)具有排他性,即同一时间只有一个线程能持有锁
2. 可见性:释放锁时,线程对共享资源的修改会同步到主内存,确保其他线程可见
3. 有序性:通过控制线程执行顺序,避免指令重排序导致的并发问题

常见锁的实现:
- Java中的synchronized关键字(隐式锁)
- JUC包中的Lock接口及其实现类(如ReentrantLock,显式锁)
- 数据库中的行锁、表锁等

记忆方法:“锁是共享资源的门卫,同一时间只放一个(批)线程进,保证数据安全不出错”——核心理解锁是并发环境下保护共享资源的“控制机制”。

26、什么是死锁?

死锁是多线程并发时出现的一种状态:**两个或多个线程相互持有对方需要的资源(锁),且都不主动释放,导致所有线程永远阻塞,无法继续执行**。

死锁产生的4个必要条件:
1. 互斥条件:资源(锁)只能被一个线程持有
2. 持有并等待:线程持有已获得的资源,同时等待其他资源
3. 不可剥夺:资源不能被强制从持有线程中剥夺
4. 循环等待:线程间形成相互等待资源的循环链

举例:
- 线程A持有锁1,等待锁2
- 线程B持有锁2,等待锁1
- 两者相互等待,无法继续,形成死锁

解决思路:
- 破坏其中任一必要条件(如按固定顺序获取锁,避免循环等待)
- 使用tryLock()设置超时,超时后释放已持有的锁
- 定期检测并中断死锁线程

记忆方法: “线程互相等资源,你不放手我也攥,循环等待陷僵局,四个条件全占全”——核心记住死锁是线程间相互等待资源的僵持状态及必要条件。

27、java都有哪些锁?

Java中的锁种类丰富,可按不同维度分类,常见的有:

1. 按锁的获取机制

  • 偏向锁:针对无竞争场景,线程获取锁后会“偏向”这个线程,避免频繁加锁解锁的开销
  • 轻量级锁:当偏向锁竞争时升级,通过CAS操作尝试获取锁,避免重量级锁的阻塞
  • 重量级锁:竞争激烈时升级,依赖操作系统互斥量实现,会导致线程阻塞

2. 按锁的特性

  • 可重入锁:线程可重复获取已持有的锁(如synchronized、ReentrantLock)
  • 公平锁:按线程请求顺序分配锁(ReentrantLock可通过构造参数设置)
  • 非公平锁:允许“插队”获取锁,可能导致饥饿,但性能更好(默认大多是非公平锁)
  • 排他锁(独占锁):同一时间只有一个线程能持有(如synchronized)
  • 共享锁:多个线程可同时持有(如ReadWriteLock的读锁)

3. 按实现方式

  • 隐式锁:JVM自动管理,无需手动释放(synchronized)
  • 显式锁:需手动获取和释放(Lock接口及其实现类)

4. 其他特殊锁

  • 自旋锁:线程获取锁失败时不阻塞,而是循环尝试(减少上下文切换)
  • 读写锁:区分读操作和写操作,读-读不互斥,读-写、写-写互斥(如ReentrantReadWriteLock)

记忆方法: “偏向轻量重量级,可重公平与读写,隐式显式自旋锁,按需选择锁不同”——按锁的升级路径、功能特性和使用方式分类记忆,重点理解各类锁的适用场景。


28、数据库设计三大范式是什么?

  1. 第一范式(1NF):确保每列不可再分,是原子值。
    记忆:列不可分,像“地址”拆成“省/市/街道”才符合。

  2. 第二范式(2NF):满足1NF,且非主键列完全依赖主键(消除部分依赖)。
    记忆:非主属性“完全”靠主键,不能只依赖主键的一部分(如复合主键场景)。

  3. 第三范式(3NF):满足2NF,且非主键列不依赖其他非主键列(消除传递依赖)。
    记忆:非主属性只“直接”依赖主键,不通过其他列间接依赖(如“学号→系名→系主任”需拆分)。

整体记忆: 一拆原子,二去部分,三消传递。

29、数据库的事务有哪些特性?

数据库事务有四大特性,简称ACID:

  1. 原子性(Atomicity):事务是不可分割的最小单位,要么全执行,要么全不执行。
    记忆:“原子”不可再分,对应事务要么完成要么回滚。

  2. 一致性(Consistency):事务执行前后,数据库从一个一致状态变到另一个一致状态。
    记忆:“一致”即数据符合预设规则(如约束、逻辑),不会因事务出问题。

  3. 隔离性(Isolation):多个事务并发执行时,彼此互不干扰,像单独执行一样。
    记忆:“隔离”即事务间有边界,避免互相影响。

  4. 持久性(Durability):事务提交后,修改永久保存,不受系统故障影响。
    记忆:“持久”即数据写入后稳定存在,不会丢失。

整体记忆:ACID四个字母对应,联想“酸(ACID)性事务”更易记。

30、数据库MySQL的事务隔离级别有哪些?

MySQL有四种事务隔离级别,从低到高依次为:

  1. 读未提交(Read Uncommitted):允许读取未提交的数据,可能出现脏读、不可重复读、幻读。
    记忆:“未提交就读”,隔离性最低,问题最多。

  2. 读已提交(Read Committed):只能读取已提交的数据,解决脏读,仍可能有不可重复读、幻读。
    记忆:“提交后才读”,避免脏读,但同一事务内两次读结果可能不同。

  3. 可重复读(Repeatable Read):事务内多次读取结果一致,解决不可重复读,仍可能有幻读(MySQL通过MVCC机制实际避免了幻读)。
    记忆:“重复读都一样”,**MySQL默认级别**。

  4. 串行化(Serializable):事务串行执行,完全隔离,无并发问题,但性能最差。
    记忆:“串行排队”,隔离性最高,像单线程一样执行。

记忆口诀: 未提、已提、可重复、串行化(按隔离级别递增顺序)。

31、每种隔离级别可能会出现什么问题?

  1. 读未提交:可能出现脏读、不可重复读、幻读(三种问题都有)。
    记忆:隔离最低,“啥问题都可能碰上”。

  2. 读已提交:解决脏读,仍可能不可重复读、幻读。
    记忆:“提交后才读”,避免了“读脏数据”,但同一事务内两次读可能不一样(不可重复读),或多了新数据(幻读)。

  3. 可重复读:解决脏读、不可重复读,理论上可能幻读(MySQL实际通过MVCC避免)。
    记忆:“重复读结果不变”,但可能看到新插入的行(幻读,逻辑上存在)。

  4. 串行化:无任何问题(脏读、不可重复读、幻读都解决)。
    记忆:“串行执行”,相当于单线程,没有并发冲突。

规律记忆: 隔离级别从低到高,解决的问题依次增加——读未提交(全有)→读已提交(去脏读)→可重复读(再去不可重复读)→串行化(全解决)。

image.png

32、什么是脏读、不可重复度、幻读?

  1. 脏读:一个事务读取到另一个未提交事务的修改数据,若后者回滚,前者读的数据就是“脏”的。
    记忆:“读了没提交的,还可能被撤回的数据”。

  2. 不可重复读:同一事务内,两次读取同一数据,结果因其他事务提交的修改而不同。
    记忆:“重复读同一条,结果不一样(被修改了)”。

  3. 幻读:同一事务内,两次执行相同查询,结果因其他事务插入新数据而多了“不存在过”的行。
    记忆:“像幻觉一样多了新数据,针对新增/删除”。

区分:不可重复读侧重“修改”,幻读侧重“新增/删除”。

33、数据库事务的底层是如何实现?

数据库事务的底层实现依赖三大核心机制:

  1. 日志机制(Undo Log + Redo Log)
  • Redo Log:记录事务对数据的修改,确保提交后即使崩溃也能恢复(持久性)。
  • Undo Log:记录数据修改前的状态,用于事务回滚(原子性)和MVCC(隔离性)。
  1. 锁机制
    通过行锁、表锁等控制并发访问,避免事务间干扰(隔离性)。

  2. MVCC(多版本并发控制)
    为数据保留多个版本,实现读写不阻塞,是隔离级别的核心支撑(如可重复读)。

记忆:日志保安全(原子、持久),锁控并发(隔离),MVCC提性能,共同保障事务特性。

34、数据库锁有哪些?

数据库锁主要分为:

  1. 按粒度分:行锁(锁定单行数据)、表锁(锁定整张表)、页锁(锁定一页数据,介于行锁和表锁之间)
  2. 按功能分:共享锁(S锁,可读不可写,多事务可同时持有)、排他锁(X锁,可写可读,仅单事务持有)
  3. 特殊锁:意向锁(表级锁,表明事务未来可能加的锁类型)、间隙锁(防止幻读)、自增锁(针对自增列的特殊锁)

记忆法:“粒功特”三类别,粒度“行表页”由小到大;功能“共排”(共享、排他)分读写;特殊锁记“意、间、自”(意向、间隙、自增)。

35、MySQL的存储引擎有哪些?

  1. 回答:
  • InnoDB:支持事务、行级锁、外键,MySQL 5.5+默认引擎
  • MyISAM:不支持事务和外键,支持表级锁,查询性能好
  • Memory:数据存内存,速度快但重启丢失
  • Archive:高压缩,适合日志等归档场景
  • CSV:以CSV格式存储,适合数据交换
  1. 记忆方法:
    “因(InnoDB)为事务用得多,我(MyISAM)查得快但不稳;内存(Memory)快易丢,归档(Archive)压缩好,CSV来交换”
    (通过场景特点关联引擎名称,突出核心差异)

36、SQL查询中哪些情况会导致索引失效?

SQL查询中导致索引失效的常见情况:

  1. 使用函数或表达式操作索引列(如WHERE SUBSTR(name,1,3)='abc'
  2. 索引列参与运算(如WHERE age+1=30
  3. 使用NOT!=<>NOT IN等否定操作符
  4. LIKE以通配符开头(如WHERE name LIKE '%abc'
  5. 字符串不加引号(如WHERE name=123,实际应为字符串)
  6. 组合索引不满足最左匹配原则
  7. 使用OR连接包含非索引列的条件
  8. MySQL优化器判断全表扫描更快时

记忆法:“函数运算否,like左通配,字符无引号,组合不左配,or含非索引,优化器选错”。

37、数据库索引的结构有哪些?

数据库索引常见结构有:

  1. B+树索引:最常用,叶子节点链表连接,适合范围查询,MySQL的InnoDB和MyISAM均支持
  2. 哈希索引:基于哈希表,适用于精确匹配,不支持范围查询,Memory引擎默认使用
  3. 全文索引:针对文本内容的关键词索引,用于模糊查询(如MATCH AGAINST
  4. R树索引:适用于空间数据类型,如地理坐标等
  5. 聚簇索引:数据与索引存储在一起(如InnoDB的主键索引)
  6. 非聚簇索引:索引与数据分开存储(如MyISAM的索引)

记忆法:“B+哈希全文R,聚簇非聚分存否”——前四个按功能分类,后两个按存储方式区分,B+树为核心结构。

38、B树和B+树有什么区别?

B树和B+树的核心区别:

  1. 存储结构:B树节点存储数据+索引;B+树仅叶子节点存数据,非叶子节点只存索引
  2. 叶子节点:B树叶子节点独立;B+树叶子节点通过链表连接,便于范围查询
  3. 查询效率:B树查询可能停在中间节点;B+树必须遍历到叶子节点,查询效率更稳定
  4. 空间占用:B树节点存数据,空间占用更大;B+树非叶子节点仅存索引,更节省空间

记忆法:“B树存数在节点,B+数据叶链接;B查中途可停下,B+叶终稳如一;B树占空比较大,B+索引更省地”。

39、哪些字段适合建索引?

适合建索引的字段特征:

  1. 频繁出现在WHERE、JOIN、ORDER BY、GROUP BY中的字段
  2. 区分度高(基数大)的字段(如身份证号,而非性别)
  3. 长度较短的字段(如int类型比长字符串更适合)
  4. 主键和外键(数据库通常自动建索引)

记忆法:“频用(查询条件)、高异(区分度)、短字(长度)、主外键”——高频使用且特征显著的字段优先建索引。

40、哪些情况不适合建立索引?

不适合建立索引的情况:

  1. 表数据量极少(如仅几百行,全表扫描更快)
  2. 频繁更新的字段(索引会随数据更新而维护,增加开销)
  3. 区分度极低的字段(如性别、状态等只有少数取值的字段)
  4. 很少出现在查询条件中的字段
  5. 大文本字段(如TEXT、BLOB类型,索引维护成本高)

记忆法:“量少、常改、区分低,少用、文本不建引”——数据量小、变动频繁、特征不明显的字段无需索引。

41、说说你对复合索引的最左匹配原则的理解?

复合索引的最左匹配原则是指:当使用复合索引(多字段组合的索引)时,查询条件需从索引的最左侧字段开始匹配,且不能跳过中间字段,否则索引无法完全生效。

例如,对(a, b, c)建立复合索引:
- 能命中索引的情况:WHERE a=?、WHERE a=? AND b=?、WHERE a=? AND b=? AND c=?
- 不能命中索引的情况:WHERE b=?、WHERE c=?、WHERE a=? AND c=?(跳过了b)

记忆法:“复合索引像排队,必须从左依次对,中间跳过全白费”——类比排队需从第一个人开始依次识别,跳过中间则无法完整匹配。

42、说说你对索引回表的理解?

索引回表是指在使用非聚簇索引(二级索引)查询时,索引叶子节点只存储索引列和主键值,当查询需要获取索引列之外的其他字段时,需通过主键值再次查询聚簇索引(主键索引)以获取完整数据的过程。

例如:InnoDB中,若对name列建索引(非聚簇索引),查询SELECT age FROM user WHERE name='张三'时,会先通过name索引找到主键值,再用主键查聚簇索引获取age,这就是回表。

记忆法:“非聚索引存主键,要取其他需回表”——非聚簇索引只含部分信息,需返回聚簇索引拿完整数据,类似“先查目录找页码,再翻页码看内容”。

43、如何避免索引回表?

避免索引回表的核心是让查询所需字段都包含在索引中,无需额外查询聚簇索引。主要方法:

  1. 覆盖索引:将查询需要的所有字段都加入索引(包括查询条件和返回字段),形成“索引覆盖查询”。
    例:对(a,b)建复合索引,查询SELECT a,b FROM t WHERE a=?,索引已包含所需字段,无需回表。

  2. 主键索引查询:直接通过主键索引查询,因聚簇索引本身包含完整数据,天然无回表。

记忆法:“字段全在索引中,覆盖查询免回表;主键查询最直接,数据自带不绕道”。


44、Redis常用的数据类型有哪些?

Redis常用的数据类型有:String(字符串)、List(列表)、Hash(哈希)、Set(集合)、Sorted Set(有序集合)。

记忆方法:可记为“字列哈希集,有序也在内”。“字”对应String,“列”对应List,“哈希”对应Hash,“集”对应Set,“有序”对应Sorted Set,通过谐音和归类帮助记忆。

45、redis的持久化方式有几种,项目中怎么选择?

Redis的持久化方式有两种:RDB和AOF。

  • RDB:定期生成内存快照,适合数据备份、恢复速度快的场景。
  • AOF:记录所有写操作日志,数据安全性更高,适合对数据完整性要求高的场景。

选择方式:
- 追求性能和备份效率选RDB;
- 追求数据安全性选AOF;
- 关键场景可两者结合,兼顾安全与性能。

记忆方法:“R快A全,结合更保险”。R(RDB)的特点是快,A(AOF)的特点是全,结合使用更可靠。

46、如果想在redis里面存储一个Java对象,能存吗?

能存。

需要先将Java对象序列化为字节数组或字符串(如JSON、XML格式),再存入Redis;读取时反序列化还原为对象。

记忆方法:“对象要进门,先穿序列化的衣”。把对象比作访客,Redis比作房间,序列化是必须的“准入手续”。

47、如何保证redis中存放的都是热点数据?

可以通过以下方式保证Redis中存放的都是热点数据:

  1. 合理设置内存淘汰策略,如allkeys-lru(移除最近最少使用的键)或volatile-lru(只对设置过期时间的键使用LRU)
  2. 主动缓存预热,将已知热点数据提前加载到Redis
  3. 结合业务场景,对非热点数据设置较短过期时间
  4. 监控并分析访问频率,动态调整缓存内容

记忆方法:“策略控淘汰,预热加时效,监控调内容”——通过淘汰策略控制非热点数据,提前预热热点数据,设置合理时效,并动态调整来保证热点特性。

48、什么是缓存击穿?如何解决?

缓存击穿:一个热点key突然失效(过期),大量瞬间量请求同时刻直达数据库,导致数据库压力骤增。

解决方法:
1. 热点key永不过期(避免失效触发)
2. 互斥锁(同一时间只让一个请求查库并更新缓存)
3. 预热热点数据并延长过期时间

记忆方法:“热点失效击穿库,永不过期锁保护,预热延长更稳固”——点出问题核心,对应三种解决思路。

49、什么是Redis的缓存雪崩?如何解决?

缓存雪崩:大量缓存key在同一时间过期失效,或Redis服务宕机,导致所有请求瞬间涌向数据库,造成数据库崩溃。

解决方法:
1. 过期时间加随机值(避免集中过期)
2. 服务熔断与限流(保护数据库)
3. 缓存集群(避免单点故障)
4. 热点数据永不过期

记忆方法:“缓存集体失效崩,随机过期防集中,熔断限流加集群,热点永活保稳定”——概括问题及核心解决方案。

50、什么是缓存穿透?如何解决?

缓存穿透:查询一个不存在的数据,因缓存和数据库都无此数据,导致每次请求都穿透到数据库,造成资源浪费。

解决方法:
1. 缓存空值(对不存在的key缓存空结果,设置短期过期)
2. 布隆过滤器(预先过滤不存在的key,阻止请求到数据库)
3. 接口层校验(非法或无效请求直接拦截)

记忆方法:“查无数据穿到底,空值缓存暂隔离,布隆过滤提前挡,接口校验校验先拦截”——形象描述问题及多层防护方案。

51、Redis是单线程的为什么速度还那么快?

Redis单线程却速度快的原因:

  1. 基于内存操作,避免磁盘IO瓶颈
  2. 单线程避免多线程切换的开销和竞态问题
  3. 使用IO多路复用模型,高效处理并发连接
  4. 数据结构简单且优化到位(如跳表、压缩列表)

记忆方法:“内存操作无瓶颈,单线程免切换,多路复用高并发,结构优化效率佳”——从存储介质、线程模型、IO处理、数据结构四个维度记忆核心原因。

52、Redis 是单进程单线程的吗?

Redis 处理客户端键值对读写命令的核心逻辑(解析、执行、返回)始终是由一个单线程来串行执行的,这是其简单性和高性能的基石。但从 Redis 4.0 开始,它使用多线程来处理一些异步后台任务(如删除),从 Redis 6.0 开始,更支持使用多线程来并行处理网络 I/O(读请求和写回复),以进一步提升在多核CPU上的性能。

理解记忆:

版本 核心命令处理 网络 I/O 后台任务(如删除)
Redis < 4.0 单线程 单线程 单线程
Redis 4.0+ 单线程 单线程 多线程(可选)
Redis 6.0+ 单线程 多线程(可选) 多线程(可选)

53、Redis 内存淘汰策略有哪些?

Redis的内存淘汰策略分为以下8种:

  1. noeviction:默认策略,内存满时拒绝写操作,返回错误
  2. allkeys-lru:从所有key中淘汰最近最少使用的
  3. volatile-lru:仅从设置过期时间的key中淘汰最近最少使用的
  4. allkeys-random:从所有key中随机淘汰
  5. volatile-random:仅从设置过期时间的key中随机淘汰
  6. volatile-ttl:仅从设置过期时间的key中,淘汰剩余生存时间最短的
  7. allkeys-lfu:从所有key中淘汰最不经常使用的
  8. volatile-lfu:仅从设置过期时间的key中淘汰最不经常使用的

记忆方法:“两默认两随机,两LRU两LFU,volatile只找带过期的”
- 默认(noeviction)+ 随机(random)+ LRU(最近最少)+ LFU(最不经常)各分两类
- 带volatile前缀的仅作用于设置了过期时间的key,不带的作用于所有key

54、Redis过期的键是如何删除的?

Redis采用三种策略结合删除过期键:

  1. 惰性删除:访问键时才检查是否过期,过期则删除(节省CPU,可能浪费内存)
  2. 定期删除:每隔一段时间随机抽查部分过期键并删除(平衡CPU和内存)
  3. 内存淘汰:内存达到maxmemory时,触发淘汰策略删除键(避免内存溢出)

记忆方法:“惰性查时删,定期随机清,内存满时按策略”——三种机制分别在访问时、定时、内存满时工作,互补解决过期键问题。

55、如何保证mysql和redis的数据的一致性?

  1. 回答:
  • 方案1:先更新MySQL,成功后再更新Redis(适合非核心场景)
  • 方案2:使用事务+消息队列,确保双写成功(适合高一致性场景)
  • 方案3:采用Canal监听MySQL binlog,异步更新Redis(适合读多写少场景)
  1. 记忆方法:
    “先更库,再更缓;强一致,用事务;读为主,靠binlog”
    (库指MySQL,缓指Redis,binlog是MySQL的二进制日志)

56、请说说延时双删的方案?

  1. 回答:
  • 步骤1:先删除Redis缓存
  • 步骤2:更新MySQL数据库
  • 步骤3:延迟一段时间(如几百毫秒)后,再次删除Redis缓存
  1. 记忆方法:
    “删缓存,更数据库,等一会,再删缓存”
    (核心是通过第二次删除解决更新MySQL后、缓存未过期前的脏读问题)

57、JVM 内存区域都有哪些?

  • 程序计数器:线程私有,记录当前执行位置
  • 虚拟机栈:线程私有,存储方法栈帧(局部变量、操作数栈等)
  • 本地方法栈:类似虚拟机栈,为Native方法服务
  • 堆:线程共享,存储对象实例,GC主要区域
  • 方法区:线程共享,存储类信息、常量、静态变量等(JDK8后为元空间)

记忆方法:
“两栈两器一堆区”
(两栈:虚拟机栈、本地方法栈;两器:程序计数器、方法区;一堆:堆。按线程私有/共享分类记忆更清晰)

58、java的类加载器有哪些?

  • 启动类加载器(Bootstrap ClassLoader):加载JVM核心类(如rt.jar)
  • 扩展类加载器(Extension ClassLoader):加载扩展目录(ext)下的类
  • 应用程序类加载器(Application ClassLoader):加载应用classpath下的类
  • 自定义类加载器:继承ClassLoader,按需需求自定义加载逻辑

记忆方法:
“ Bootstrap 启动核心,Extension 扩展功能,Application 加载应用,自定义按需变通”
(按加载范围从小到大,结合名称含义记忆)

59、什么是双亲委派模型?它的好处是什么?

  1. 回答:
  • 定义:类加载时,先委托父加载器加载,父加载器无法加载时才由子加载器自己加载(向上委托,向下尝试)。
  • 好处:① 防止类重复加载;② 保证核心类安全(如java.lang.String不会被自定义类篡改)。
  1. 记忆方法:
    “先找爹,爹不行,自己来”
    (“爹”指父加载器,形象体现委托流程;好处可记“去重+保安全”)

60、常见的垃圾收集算法有哪些?

  1. 回答:
  • 标记-清除算法:先标记可回收对象,再统一清除(有内存碎片问题)
  • 标记-复制算法:将内存分为两块,只一块存对象,满后复制存活对象到另一块(无碎片,内存利用率低)
  • 标记-整理算法:标记后将存活对象移向一端,再清除边界外内存(无碎片,适用于老年代)
  • 分代收集算法:结合以上算法,新生代用复制算法,老年代用标记-清除/整理算法
  1. 记忆方法:
    “清除有碎片,复制无碎片但费空间,整理无碎片适合老年代,分代按代选”
    (按算法特点和适用场景关联记忆,突出核心差异)