---
title: Java BAT高频面题
category: 
  - Java
  - 面经
  - 八股文
tag:
  - Java面试题
head:
  - - meta
    - name: keywords
      content: 项目,简历,JAVA,大厂面试,项目包装
    - name: description
      content: 全网质量最高的Java基础常见知识点和面试题总结，希望对你有帮助！
---
[TOC]

## 1、hashmap底层原理（数据结构、为什么用红黑树等）

在JDK1.7 和JDK1.8 中有所差别：

在JDK1.7 中，由 `数组+链表` 组成，数组是 HashMap 的主体，链表则是主要为了解决哈希冲突而存在的。

在JDK1.8 中，由 `数组+链表+红黑树`组成。当链表过长，则会严重影响 HashMap 的性能，红黑树搜索时间复杂度是 O(logn)，而链表是糟糕的 O(n)。因此，JDK1.8 对数据结构做了进一步的优化，引入了红黑树，链表和红黑树在达到一定条件会进行转换：

- 当链表超过 8 且数据总量超过 64 才会转红黑树。
- 将链表转换成红黑树前会判断，如果当前数组的长度小于 64，那么会选择先进行数组扩容，而不是转换为红黑树，以减少搜索时间。

## 2、HashMap 扩容机制

一般情况下，当元素数量超过阈值时便会触发扩容。每次扩容的容量都是之前容量的 2 倍。 HashMap 的**容量**是有上限的，必须小于1<<30，即 1073741824。如果容量超出了这个 数，则不再增长，且阈值会被设置为 Integer.MAX_VALUE。 

**JDK7 中的扩容机制**

- 空参数的构造函数：以默认容量、默认负载因子、默认阈值初始化数组。内部数组是空数 组。
- 有参构造函数：根据参数确定容量、负载因子、阈值等。
- 第一次 put 时会初始化数组，其容量变为不小于指定容量的 2 的幂数，然后根据负载因 子确定阈值。
- 如果不是第一次扩容，则 新容量=旧容量 x 2 ，新阈值=新容量 x 负载因子 。 

**JDK8 的扩容机制**

- 空参数的构造函数：实例化的 HashMap 默认内部数组是 null，即没有实例化。第一次 调用 put 方法时，则会开始第一次初始化扩容，长度为 16。
- 有参构造函数：用于指定容量。会根据指定的正整数找到不小于指定容量的 2 的幂数， 将这个数设置赋值给阈值（threshold）。第一次调用 put 方法时，会将阈值赋值给容量， 然后让 **阈值 = 容量 x 负载因子**。
- 如果不是第一次扩容，则容量变为原来的 2 倍，阈值也变为原来的 2 倍。（容量和阈值 都变为原来的 2 倍时，负载因子还是不变）。 此外还有几个细节需要注意： 
- 首次 put 时，先会触发扩容（算是初始化），然后存入数据，然后判断是否需要扩容； 
- 不是首次 put，则不再初始化，直接存入数据，然后判断是否需要扩容；

## 2、Jvm内存模型

## 3、索引的数据结构对比（hash、B树与B+树），为什么不用红黑树

**何为索引**：以图书馆为例，需借助检索目录，以加快书本查询定位；同理，MySQL索引也即为排好序的一种数据结构，用于提升数据库的查找速度。

**哈希(hash)比树(tree)更快，索引结构为什么要设计成树型？**

知其然，知其所以然。分析两种数据结构时间复杂度：

（1）**哈希（Hash）结构**，例如 `HashMap`，`查询、删除、插入、修改`的平均时间复杂度都是 O(1)；

（2）**树（tree）结构**，例如平衡二叉搜索树，`查询、删除、插入、修改` 的平均时间复杂度都是 O(logN) ；

从中分析可见，若用 `Hash` 类型的索引结构，无论是在**读请求，还是写请求**，都比 `树（tree）`类型的索引结构快；、

**那，为何 MySQL 索引结构还设计成树型结构呢？**

其一：若是简单的读写请求，Hash结构效果当然更佳；Hash 底层采用哈希表实现，等值查询，可以快速定位，一般情况效率很高。但也不稳定，当出现大量键重复哈希冲突，效率下降。

其二：MySQL 需要满足更多场景的 SQL 查询需求。Hash不支持范围查询，无法用于排序分组（grou by），无法模糊查询（like %），多列索引的最左前缀匹配原则。

而在众多树之中，MySQL最后选择了改进后的B树—>B+树

**那，MySQL 索引为什么使用 B+ 树呢？**而不用 B 树、红黑树？

你事先需要知道的几种树：

**二叉搜索树**

（1）当数据量大的时候，树的高度会比较高，数据量大的时候，**查询会比较慢**；

（2）当数据线性增大时，二叉搜索树会呈现单边倒的情况，时间复杂度退化 O(n)，**效率更低**；

（3）每个节点只存储一个记录，可能导致**一次查询有很多次磁盘IO**；

![2tree](https://xiaolongcoder.oss-cn-beijing.aliyuncs.com/imgs/Java2Top/concurrent202303251021618.png)

**AVL 树（平衡二叉搜索树）**

平衡树虽然可以弥补 "当数据线性增大时，二叉搜索树会呈现单边倒的情况，时间复杂度退化 O(n) 的情况”，但是他还是具有二叉搜索树一样的缺点。

![2stree](https://xiaolongcoder.oss-cn-beijing.aliyuncs.com/imgs/Java2Top/concurrent202303251034147.png)

**B树**

B树，如下图，它的特点是：

（1）不再是二叉搜索，而是 **m 叉搜索**（可以根据不同度数分裂），而正由于是 m 分叉的，高度能够大大降低；

（2）并且 B树 **叶子节点、非叶子节点，都可以存储数据**；如果将每个节点大小设置为页大小，那么利用`磁盘预读`的特性，可以极大减少磁盘IO；并且，**通过中序遍历，可以很快找到节点对应的数据**。

**思考：那到了 B 树结构，直接用来做索引是否就完美了呢？**

虽然 B 树相对其他种树优势很明显，但是在**范围查询时，还是很吃力**；想要减少磁盘IO，节点最好设置为页大小，节点（页）大小固定后，**若每个节点都存数据，每个节点也存不下多少，数据大时树的高度也不低**。

于是，B+ 树应运而生，在B树上做小小改动，便是目前完美的索引结构啦。

![Btree](https://xiaolongcoder.oss-cn-beijing.aliyuncs.com/imgs/Java2Top/concurrent202303250903511.png)

**B+树**

B+树，如下图，仍是 m 叉搜索树。在 B树 的基础上，**进行了一些改进**：

（1）非叶子结点不存data，只存key，查询更稳定，增大了广度；数据只存在叶子节点；

（2）叶子之间，增加了链表。可以很好的支持范围查询，并且获取所有节点，不再需要中序遍历；

**相比 B树 具有更优的特性：**

（1）范围查找，定位min与max之后，中间叶子节点，就是结果集啦；

（2）叶子节点存储实际记录行，记录行相对比较紧密的存储，适合大数据量磁盘存储；非叶子节点存储记录的PK（KEY数据小，相同内存情况下，节点可以多存KEY，增大了节点广度(B+树出度更大，进而树高更矮，磁盘IO次数更少)）用于查询加速，适合内存存储；

![B+tree](https://xiaolongcoder.oss-cn-beijing.aliyuncs.com/imgs/Java2Top/concurrent202303251015700.png)

**总结：**

1. 索引为排好序的一种数据结构，用于提升数据库的查找速度。

2. Hash索引时间复杂度为O(1)，树索引是O(log(n))。Hash 底层是哈希表实现，等值查询，可以快速定位数据。但不支持范围查询，无法用于排序分组，无法模糊查询等操作。

3. **B+树作为索引优势：**

   - 叶子节点存储实际记录行，记录行相对比较紧密的存储，适合大数据量磁盘存储；
   - 非叶子节点存储记录的PK（KEY数据小，相同内存情况下，节点可以多存KEY，增大了节点广度(B+树出度更大，进而树高更矮，磁盘IO次数更少)）用于查询加速，适合内存存储；
   - 叶子之间，增加了链表。可以很好的支持范围查询，并且获取所有节点，不再需要中序遍历；
   - 更少查询次数：B+树出度更大，树高更低，查询次数更少；
   - 很适合磁盘存储，能够充分利用局部性原理，磁盘预读（为了减少IO操作，往往不严格按需读取，而是预读。B+树叶子**结点存储相临**，**读取会快一些**）

   > 数据结构可视化网站：https://www.cs.usfca.edu/~galles/visualization/BTree.html

## 4、Mysql的默认隔离级别、不同等级隔离级别解决的问题与实现原理

隔离级别原理及解决问题分析：

1. **READ-UNCOMMITTED（读未提交）**：原理：直接读取数据，不能解决任何并发问题
2. **READ-COMMITTED（读已提交）**：读操作不加锁，写操作加排他锁，解决了脏读。原理：利用MVCC实现，每一句语句执行前都会生成Read View（一致性视图）
3. **REPEATABLE-READ（可重复读）**：MVCC实现，只有事务开始时会创建Read View，之后事务里的其他查询都用这个Read View。解决了脏读、不可重复读，快照读（普通查询，读取历史数据）使用MVCC解决了幻读，当前读（读取最新提交数据）通过间隙锁解决幻读（lock in share mode、for update、update、detete、insert），间隙锁在可重复读下才生效。（**默认隔离级别**）
4. **SERIALIZABLE（可串行化）**：原理：使用锁，读加共享锁，写加排他锁，串行执行

**总结**：读已提交和可重复读实现原理就是MVCC Read View不同的生成时机。可重复读只在事务开始时生成一个Read View，之后都用的这个；读已提交每次执行前都会生成Read View。

## 5、Java线程池核心参数与工作流程，拒绝策略

**线程池好处**：降低资源消耗，提高响应速度，方便统一管理。

**七大核心参数**：核心线程数、最大线程数、keepAlive Time(工作线程空闲后，存活时间)、TimeUnit、workQueue、ThreadFactory、拒绝策略

**线程池工作原理**：向线程池添加一个任务，首先看线程池中线程数是否小于核心线程数，若小于则创建一个线程执行任务，反之再判断任务队列满没，若没有满则将任务放到任务队列并等待执行，若满了再判断现在线程池中线程数是否大于最大线程数，若没有大于则创建一个线程执行任务，若大于则使用饱和策略进行处理任务。

**任务队列**：ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue、SynchronousQueue（存一个取一个，阻塞等待）、DelayedWorkQueue

**拒绝策略：**

1.CallerRunsPolicy 由**调用线程**处理该任务。

2.AbortPolicy **默认**拒绝策略 直接丢弃，抛出异常**RejectedExecutionException**

3.DiscardPolicy 直接丢弃，不抛出异常

4.DiscardOldestPolicy 丢弃队列最早的任务，然后重新尝试执行任务。

**Executors的4种功能线程池**：定长、定时、可缓存、单线程化

**线程池5大状态**：Running(可接收，可处理)、Shutdown(不接收，可处理)、Stop(不接收，不处理，中断正在执行)、Tidying（所有任务终止）、Terminated（线程池彻底终止）。

**创建线程池：**

《阿⾥巴巴 Java 开发⼿册》中强制线程池不允许使⽤ Executors 去创建，⽽是通过**ThreadPoolExecutor** 的⽅式，避免使用Executors创建线程池，主要是避免使用其中的默认实现（比如定长缓存池使用链表任务队列，默认长度为Integer.MAX_VALUE，可能堆积大量请求，导致OOM）那么我们可以自己直接调用ThreadPoolExecutor的构造函数自己创建线程池。在创建的同时，给BlockQueue指定容量就可以了。规避资源耗尽的⻛险

- **Executors 返回线程池对象的弊端如下：**

- - **newFixedThreadPool** 和 **newSingleThreadExecutor** ： 允许请求的队列⻓度为Integer.MAX_VALUE ，**可能堆积⼤量的请求**，从⽽导致 OOM。
  - **newCachedThreadPool**和 **newScheduledThreadPool** ： 允许创建的线程数量为Integer.MAX_VALUE ，**可能会创建⼤量线程**，从⽽导致 OOM。注意二者产生OOM的原因不一样

- 通过 **Executor** 框架的⼯具类 **Executors** 来实现

- - 定长线程池（newFixedThreadPool）

- - - **特点**：只有核心线程（corePoolSize=maximumPoolSize），线程数量固定，执行完立即回收，任务队列为链表结构的无界队列（Integer.MAX_VALUE）。
    - **应用场景**：控制线程最大并发数。

- - 单线程化线程池（newSingleThreadExecutor）

- - - **特点**：只有1个核心线程，最大线程数为1，无非核心线程，(corePoolSize=maximumPoolSize=1)执行完立即回收，任务队列为链表结构的无界队列（Integer.MAX_VALUE）。
    - **应用场景**：不适合并发但可能引起IO阻塞性及影响UI线程响应的操作，如数据库操作、文件操作等。

- - 定时线程池（newScheduledThreadPool ）

- - - **特点**：核心线程数量固定，非核心线程数量无限，执行完闲置10ms后回收，任务队列为延时阻塞队列。(corePoolSize为传进来参数，最大线程数=Integer.MAX_VALUE，使用DelayedWorkQueue())
    - **应用场景**：执行定时或周期性的任务。

- - 可缓存线程池（newCachedThreadPool）

- - - **特点**：无核心线程，非核心线程数量无限，执行完闲置60s后回收，任务队列为不存储元素的阻塞队列（SynchronousQueue）。（corePoolSize=0，maximumPoolSize=Integer.MAX_VALUE，keepAlive Time=60L）
    - **应用场景**：执行大量、耗时少的任务。

- 通过ThreadPoolExecutor 构造方法

## 6、Tcp与Udp区别

tcp**可靠传输**，面向**字节流**，拥塞控制，流量控制，一对一

udp不可靠传输，面向报文，没有拥塞控制，流量控制，支持一对一，一对多

tcp首部长度若没有选项字段大小20字节，udp首部长度固定8字节（这也是为何udp没有首部长度字段）

使用场景：

tcp效率要求低，准确性要求高的（文件传输）；udp效率要求高，准备要求相对可以低点（QQ聊天，视频通话）

为何udp快：

不需要建立连接、没有超时重传、对收到数据不需给确认、没有流量控制拥塞控制

tcp首部格式：

20字节源端口、目的端口、序列号、确认号、数据偏移、保留字、URG、ACK、FIN、PSH、FIN

udp首部格式：

首部8字节，源端口、目的端口、包长度、检验和，再计算检验和时还会添加12字节的伪首部

## 7、键入网址到页面显示，期间发生什么？

## 8、Redis基本数据类型，平时怎么使用？

## 9、MySQL事务及特性

事务：一系列操作组成，要么全部成功，要么全部失败

**事务ACID特性**

1. 原子性：一些列操作要么全部成功，要么全部失败
2. 隔离性：事务的结果只有提交了其他事务才可见
3. 一致性：数据库总时从一个一致状态变到另一个一致状态（事务修改前后的**数据总体保证一致 转账**）
4. 持久性：事务提交后，对数据修改永久的

**事务的并发问题：**

1. 脏读：读到未提交的数据
2. 不可重复读：一个事务下，两次读取数据不一致（侧重内容数据的修改）
3. 幻读：事务A 按照一定条件进行数据读取， 期间事务B 插入了相同搜索条件的新数据，事务A再次按照原先条件进行读取时，发现了事务B 新插入的数据 称为幻读（侧重新增或删除，插入数据读到多了一行）

## 10、Tcp三次握手四次挥手及对应的状态

开始客户端服务端都处于CLOSE状态，然后服务端主动监听某个端口，处于listen状态

**客户端发送SYN报文**：客户端随机初始化seq=x，并将序列号置于TCP首部序列号字段中，并将SYN标志位置为1，发送SYN报文到服务端，客户端变为SYN_SEND

**服务端发送SYN+ACK报文**：服务端收到客户端的SYN报文，也会随机初始化序列号seq=y，并将序列号置于TCP首部序列号字段，还会将x+1置于确认应答号字段中，然后置SYN=1,ACK=1将报文发给客户端，不包含应用层数据，之后服务端处于SYN_REVD状态

**客户端发送ACK报文：**客户端收到服务端报文，向服务端发送确认的确认，置ACK标志位为1，确认应答号为y+1，然后发给服务端，这次可以携带数据，此后，客户端服务端都处于ESTABLISHED状态。

客户端发起连接请求报文段（客：SYBN_SEND）

服务端为该TCP连接分配缓存与变量，给予确认（服：SYN_REVD）

客户端为该TCP连接分配缓存与变量（客：ESTABLISHED）,给予确认的确认（服：ESTABLISHED）

## 11、Http、Https、两者区别

## 12、浏览器上输入地址后的整个请求过程

## 13、进程与线程的区别

## 14、Java集合类（线程安全/不安全）

## 15、Jvm垃圾回收算法

## 16、OSI七层、五层模型，每一层的作用

## 17、volatile关键字的原理与作用

## 18、Tcp流量控制与拥塞控制

## 19、Synchronized和Lock的实现原理与区别

## 20、Redis持久化方式，RDB和AOF的区别与优劣势

## 21、进程、线程、协程的区别

## 22、ArrayList与LinkedList区别

## 23、Mvcc实现机制(RC和RR隔离级别下的区别)

## 24、SpringAOP的底层原理

## 25、进程之间通信方式

## 26、类加载机制

## 27、ConcurrentHashMap原理，如何保证线程安全

## 28、synchronized原理

## 29、Redis如何实现分布式锁？

## 30、死锁的产生条件与解决方案

## 31、Mysql的聚簇索引和非聚簇索引作用与区别

## 32、Tcp如何保证可靠传输

## 33、索引失效的几种场景

## 34、常见的设计模式与优缺点

## 35、Mysql sql优化，慢Sql如何排查

## 36、Mysql的几种存储引擎

## 37、CAS操作原理与实现