图解学习网站:https://xiaolincoding.com
大家好,我是小林。
腾讯的面试普遍都是 1 小时的左右面经,累计下来可能有 30 多个面试题,也有同学跟我反馈,他面腾讯的时候,也经历过 2 小时+ 的面试,写了 3 个算法之后,就来 30 多个面试题,面完都满头大汗了。
不过,有时候也有可能遇到比较简单腾讯面试,比如今天这位同学的腾讯的Java后端面经,相比其他同学的腾讯面经,这次的面经问题都还算比较简单和基础的,没有太难的题目。
可惜同学没有把握住机会,一面之后就凉了。挂了没事,重在补齐自己不会的知识点,持续修正自己,迭代自己的知识体系,会慢慢越来越顺利的。
这次来跟大家拆解一下,大家看看自己能回答出来吗?
考察的知识点如下:
- Java:Spring、接口和抽象类、SpringBoot计网:HTTP、HTTPSMySQL:索引、一条SQL语句的执行过程、隔离级别、多表查询算法:反转字符串中的单词 III
MySQL
MySQL聚簇索引和非聚簇索引的区别是什么?
数据存储:在聚簇索引中,数据行按照索引键值的顺序存储,也就是说,索引的叶子节点包含了实际的数据行。这意味着索引结构本身就是数据的物理存储结构。非聚簇索引的叶子节点不包含完整的数据行,而是包含指向数据行的指针或主键值。数据行本身存储在聚簇索引中。
索引与数据关系:由于数据与索引紧密相连,当通过聚簇索引查找数据时,可以直接从索引中获得数据行,而不需要额外的步骤去查找数据所在的位置。当通过非聚簇索引查找数据时,首先在非聚簇索引中找到对应的主键值,然后通过这个主键值回溯到聚簇索引中查找实际的数据行,这个过程称为“回表”。
唯一性:聚簇索引通常是基于主键构建的,因此每个表只能有一个聚簇索引,因为数据只能有一种物理排序方式。一个表可以有多个非聚簇索引,因为它们不直接影响数据的物理存储位置。
效率:对于范围查询和排序查询,聚簇索引通常更有效率,因为它避免了额外的寻址开销。非聚簇索引在使用覆盖索引进行查询时效率更高,因为它不需要读取完整的数据行。但是需要进行回表的操作,使用非聚簇索引效率比较低,因为需要进行额外的回表操作。
MySQL主键是聚簇索引吗?
在MySQL的InnoDB存储引擎中,主键确实是以聚簇索引的形式存储的。
InnoDB将数据存储在B+树的结构中,其中主键索引的B+树就是所谓的聚簇索引。这意味着表中的数据行在物理上是按照主键的顺序排列的,聚簇索引的叶节点包含了实际的数据行。
InnoDB 在创建聚簇索引时,会根据不同的场景选择不同的列作为索引:
- 如果有主键,默认会使用主键作为聚簇索引的索引键;如果没有主键,就选择第一个不包含 NULL 值的唯一列作为聚簇索引的索引键;在上面两个都没有的情况下,InnoDB 将自动生成一个隐式自增 id 列作为聚簇索引的索引键;
一张表只能有一个聚簇索引,那为了实现非主键字段的快速搜索,就引出了二级索引(非聚簇索引/辅助索引),它也是利用了 B+ 树的数据结构,但是二级索引的叶子节点存放的是主键值,不是实际数据。
执行一条SQL请求的过程
先来一个上帝视角图,下面就是 MySQL 执行一条 SQL 查询语句的流程,也从图中可以看到 MySQL 内部架构里的各个功能模块。
-
- 连接器:建立连接,管理连接、校验用户身份;查询缓存:查询语句如果命中查询缓存则直接返回,否则继续往下执行。MySQL 8.0 已删除该模块;解析 SQL,通过解析器对 SQL 查询语句进行词法分析、语法分析,然后构建语法树,方便后续模块读取表名、字段、语句类型;执行 SQL:执行 SQL 共有三个阶段:
-
-
- 预处理阶段:检查表或字段是否存在;将select *中的*符号扩展为表上的所有列。优化阶段:基于查询成本的考虑, 选择查询成本最小的执行计划;执行阶段:根据执行计划执行 SQL 查询语句,从存储引擎读取记录,返回给客户端;
-
-
MySQL如何避免重复插入数据?
使用UNIQUE约束
- :在表的相关列上添加UNIQUE约束,确保每个值在该列中唯一。例如:
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
email VARCHAR(255) UNIQUE,
name VARCHAR(255)
);
如果尝试插入重复的email,MySQL会返回错误。
使用INSERT ... ON DUPLICATE KEY UPDATE:这种语句允许在插入记录时处理重复键的情况。
如果插入的记录与现有记录冲突,可以选择更新现有记录:
INSERT INTO users (email, name)
VALUES ('example@example.com', 'John Doe')
ON DUPLICATE KEY UPDATE name = VALUES(name);
使用INSERT IGNORE:该语句会在插入记录时忽略那些因重复键而导致的插入错误。
例如:
INSERT IGNORE INTO users (email, name)
VALUES ('example@example.com', 'John Doe');
如果email已经存在,这条插入语句将被忽略而不会返回错误。
选择哪种方法取决于具体的需求:
-
-
- 如果需要保证全局唯一性,使用UNIQUE约束是最佳做法。如果需要插入和更新结合可以使用ON DUPLICATE KEY UPDATE。对于快速忽略重复插入,INSERT IGNORE是合适的选择。
-
MySQL的隔离级别
读未提交(read uncommitted),指一个事务还没提交时,它做的变更就能被其他事务看到;
读提交(read committed),指一个事务提交之后,它做的变更才能被其他事务看到;
可重复读(repeatable read),指一个事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,
MySQL InnoDB 引擎的默认隔离级别;
串行化(serializable);会对记录加上读写锁,在多个事务对这条记录进行读写操作时,如果发生了读写冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行;
按隔离水平高低排序如下:
针对不同的隔离级别,并发事务时可能发生的现象也会不同。
也就是说:
- 在「读未提交」隔离级别下,可能发生脏读、不可重复读和幻读现象;在「读提交」隔离级别下,可能发生不可重复读和幻读现象,但是不可能发生脏读现象;在「可重复读」隔离级别下,可能发生幻读现象,但是不可能脏读和不可重复读现象;在「串行化」隔离级别下,脏读、不可重复读和幻读现象都不可能会发生。
MySQL多表查询
数据库有以下几种联表查询类型:
内连接 (INNER JOIN)左外连接 (LEFT JOIN)右外连接 (RIGHT JOIN)全外连接 (FULL JOIN)
1. 内连接 (INNER JOIN)内连接返回两个表中有匹配关系的行。
示例:
SELECT employees.name, departments.name
FROM employees
INNER JOIN departments
ON employees.department_id = departments.id;
这个查询返回每个员工及其所在的部门名称。
2. 左外连接 (LEFT JOIN)左外连接返回左表中的所有行,即使在右表中没有匹配的行。未匹配的右表列会包含NULL。
示例:
SELECT employees.name, departments.name
FROM employees
LEFT JOIN departments
ON employees.department_id = departments.id;
这个查询返回所有员工及其部门名称,包括那些没有分配部门的员工。
3. 右外连接 (RIGHT JOIN)右外连接返回右表中的所有行,即使左表中没有匹配的行。未匹配的左表列会包含NULL。
示例:
SELECT employees.name, departments.name
FROM employees
RIGHT JOIN departments
ON employees.department_id = departments.id;
这个查询返回所有部门及其员工,包括那些没有分配员工的部门。
4. 全外连接 (FULL JOIN)全外连接返回两个表中所有行,包括非匹配行,在MySQL中,FULL JOIN 需要使用 UNION 来实现,因为 MySQL 不直接支持 FULL JOIN。
示例:
SELECT employees.name, departments.name
FROM employees
LEFT JOIN departments
ON employees.department_id = departments.id
UNION
SELECT employees.name, departments.name
FROM employees
RIGHT JOIN departments
ON employees.department_id = departments.id;
这个查询返回所有员工和所有部门,包括没有匹配行的记录。
计网
Http请求包含哪些部分?
请求行(Request Line):包含请求方法(如 GET, POST, PUT, DELETE 等),请求的URL(统一资源定位符)或资源路径,使用的HTTP协议版本(如 HTTP/1.1 或 HTTP/2)。
请求头(Request Headers):包含一系列键值对,提供客户端和请求的附加信息,如:
Host
:指定请求的目标服务器,
User-Agent
:描述发起请求的用户代理(浏览器或其他客户端),
Accept
:列出客户端可以接受的媒体类型,
Content-Type
:指定请求体中的数据类型,如application/json
,
Content-Length
:如果请求体存在,此头指定其长度。
空行(Blank Line):这是一个必要的换行符,用于分隔请求头和请求体。它标记了请求头的结束以及请求体的开始。
请求体(Request Body):可选部分,通常在POST、PUT等请求中出现,用于传输数据到服务器。如果请求方法不需要或不支持数据传输,则可能不存在请求体。
Https是如何交换协议的?
SSL/TLS 协议基本流程:
- 客户端向服务器索要并验证服务器的公钥。双方协商生产「会话秘钥」。双方采用「会话秘钥」进行加密通信。
前两步也就是 SSL/TLS 的建立过程,也就是 TLS 握手阶段。TLS 的「握手阶段」涉及四次通信,使用不同的密钥交换算法,TLS 握手流程也会不一样的,现在常用的密钥交换算法有两种:RSA 算法(opens new window)和 ECDHE 算法(opens new window)。基于 RSA 算法的 TLS 握手过程比较容易理解,所以这里先用这个给大家展示 TLS 握手过程,如下图:
TLS 协议建立的详细流程:
1. ClientHello
首先,由客户端向服务器发起加密通信请求,也就是 ClientHello 请求。在这一步,客户端主要向服务器发送以下信息:(1)客户端支持的 TLS 协议版本,如 TLS 1.2 版本。(2)客户端生产的随机数(Client Random),后面用于生成「会话秘钥」条件之一。(3)客户端支持的密码套件列表,如 RSA 加密算法。
2. SeverHello
服务器收到客户端请求后,向客户端发出响应,也就是 SeverHello。服务器回应的内容有如下内容:(1)确认 TLS 协议版本,如果浏览器不支持,则关闭加密通信。(2)服务器生产的随机数(Server Random),也是后面用于生产「会话秘钥」条件之一。(3)确认的密码套件列表,如 RSA 加密算法。(4)服务器的数字证书。
3.客户端回应
客户端收到服务器的回应之后,首先通过浏览器或者操作系统中的 CA 公钥,确认服务器的数字证书的真实性。
如果证书没有问题,客户端会从数字证书中取出服务器的公钥,然后使用它加密报文,向服务器发送如下信息:(1)一个随机数(pre-master key)。该随机数会被服务器公钥加密。(2)加密通信算法改变通知,表示随后的信息都将用「会话秘钥」加密通信。(3)客户端握手结束通知,表示客户端的握手阶段已经结束。这一项同时把之前所有内容的发生的数据做个摘要,用来供服务端校验。
上面第一项的随机数是整个握手阶段的第三个随机数,会发给服务端,所以这个随机数客户端和服务端都是一样的。
服务器和客户端有了这三个随机数(Client Random、Server Random、pre-master key),接着就用双方协商的加密算法,各自生成本次通信的「会话秘钥」。
4. 服务器的最后回应服务器收到客户端的第三个随机数(pre-master key)之后,通过协商的加密算法,计算出本次通信的「会话秘钥」。
然后,向客户端发送最后的信息:(1)加密通信算法改变通知,表示随后的信息都将用「会话秘钥」加密通信。(2)服务器握手结束通知,表示服务器的握手阶段已经结束。这一项同时把之前所有内容的发生的数据做个摘要,用来供客户端校验。
至此,整个 TLS 的握手阶段全部结束。接下来,客户端与服务器进入加密通信,就完全是使用普通的 HTTP 协议,只不过用「会话秘钥」加密内容。
Http返回状态301 302分别是什么?
3xx 类状态码表示客户端请求的资源发生了变动,需要客户端用新的 URL 重新发送请求获取资源,也就是重定向。
「301 Moved Permanently」表示永久重定向,说明请求的资源已经不存在了,需改用新的 URL 再次访问。
「302 Found」表示临时重定向,说明请求的资源还在,但暂时需要用另一个 URL 来访问。
301 和 302 都会在响应头里使用字段 Location,指明后续要跳转的 URL,浏览器会自动重定向新的 URL。
Java
简述spring IoC和AOP
IoC(控制反转) 是一种设计思想,而不是一个具体的技术实现。IoC 的思想就是将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。
AOP(面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑封装起来,以减少系统的重复代码,降低模块间的耦合度。Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理。
在 Spring 框架中,IOC 和 AOP 结合使用,可以更好地实现代码的模块化和分层管理。例如,通过 IOC 容器管理对象的依赖关系,然后通过 AOP 将横切关注点统一切入到需要的业务逻辑中。
例如:使用 IOC 容器管理 Service 层和 DAO 层的依赖关系,然后通过 AOP 在 Service 层实现事务管理、日志记录等横切功能,使得业务逻辑更加清晰和可维护。
Java抽象类和接口的区别是什么?
- 一个子类只能继承一个抽象类, 但能实现多个接口抽象类可以有构造方法, 接口没有构造方法抽象类可以有普通成员变量, 接口没有普通成员变量抽象类和接口都可有静态成员变量, 抽象类中静态成员变量访问类型任意,接口只能public static final(默认)抽象类可以没有抽象方法, 抽象类可以有普通方法;接口在JDK8之前都是抽象方法,在JDK8可以有default方法,在JDK9中允许有私有普通方法抽象类可以有静态方法;接口在JDK8之前不能有静态方法,在JDK8中可以有静态方法,且只能被接口类直接调用(不能被实现类的对象调用)抽象类中的方法可以是public、protected; 接口方法在JDK8之前只有public abstract,在JDK8可以有default方法,在JDK9中允许有private方法
抽象类可以被实例化吗
在Java中,抽象类本身不能被实例化。
这意味着不能使用new
关键字直接创建一个抽象类的对象。抽象类的存在主要是为了被继承,它通常包含一个或多个抽象方法(由abstract
关键字修饰且无方法体的方法),这些方法需要在子类中被实现。
抽象类可以有构造器,这些构造器在子类实例化时会被调用,以便进行必要的初始化工作。然而,这个过程并不是直接实例化抽象类,而是创建了子类的实例,间接地使用了抽象类的构造器。
例如:
public abstract class AbstractClass {
public AbstractClass() {
// 构造器代码
}
public abstract void abstractMethod();
}
public class ConcreteClass extends AbstractClass {
public ConcreteClass() {
super(); // 调用抽象类的构造器
}
@Override
public void abstractMethod() {
// 实现抽象方法
}
}
// 下面的代码可以运行
ConcreteClass obj = new ConcreteClass();
在这个例子中,ConcreteClass
继承了AbstractClass
并实现了抽象方法abstractMethod()
。当我们创建ConcreteClass
的实例时,AbstractClass
的构造器被调用,但这并不意味着AbstractClass
被实例化;实际上,我们创建的是ConcreteClass
的一个对象。
简而言之,抽象类不能直接实例化,但通过继承抽象类并实现所有抽象方法的子类是可以被实例化的。
接口可以包含构造函数吗?
在接口中,不可以有构造方法,在接口里写入构造方法时,编译器提示:Interfaces cannot have constructors,因为接口不会有自己的实例的,所以不需要有构造函数。
为什么呢?构造函数就是初始化class的属性或者方法,在new的一瞬间自动调用,那么问题来了Java的接口,都不能new 那么要构造函数干嘛呢?根本就没法调用
SpringBoot的项目结构是怎么样的?
一个正常的企业项目里一种通用的项目结构和代码层级划分的指导意见。按这《阿里巴巴Java开发手册》时本书上说的,一般分为如下几层:
开放接口层:可直接封装 Service 接口暴露成 RPC 接口;通过 Web 封装成 http 接口;网关控制层等。终端显示层:各个端的模板渲染并执行显示的层。当前主要是 velocity 渲染,JS 渲染,JSP 渲染,移动端展示等。Web 层:主要是对访问控制进行转发,各类基本参数校验,或者不复用的业务简单处理等。Service 层:相对具体的业务逻辑服务层。Manager 层:通用业务处理层,它有如下特征
1)对第三方平台封装的层,预处理返回结果及转化异常信息,适配上层接口。
2)对 Service 层通用能力的下沉,如缓存方案、中间件通用处理。
3)与 DAO 层交互,对多个 DAO 的组合复用。
DAO 层:数据访问层,与底层 MySQL、Oracle、Hbase、OceanBase 等进行数据交互。第三方服务:包括其它部门 RPC 服务接口,基础平台,其它公司的 HTTP 接口,如淘宝开放平台、支付宝付款服务、高德地图服务等。外部接口:外部(应用)数据存储服务提供的接口,多见于数据迁移场景中。
如果从一个用户访问一个网站的情况来看,对应着上面的项目代码结构来分析,可以贯穿整个代码分层:
对应代码目录的流转逻辑就是:
所以,以后每当我们拿到一个新的项目到手时,只要按照这个思路去看别人项目的代码,应该基本都是能理得顺的。
算法
反转字符串中的单词 III
开辟一个新字符串。然后从头到尾遍历原字符串,直到找到空格为止,此时找到了一个单词,并能得到单词的起止位置。随后,根据单词的起止位置,可以将该单词逆序放到新字符串当中。如此循环多次,直到遍历完原字符串,就能得到翻转后的结果。
class Solution {
public String reverseWords(String s) {
StringBuffer ret = new StringBuffer();
int length = s.length();
int i = 0;
while (i < length) {
int start = i;
while (i < length && s.charAt(i) != ' ') {
i++;
}
for (int p = start; p < i; p++) {
ret.append(s.charAt(start + i - 1 - p));
}
while (i < length && s.charAt(i) == ' ') {
i++;
ret.append(' ');
}
}
return ret.toString();
}
}
- 时间复杂度:O(N),其中 N 为字符串的长度。原字符串中的每个字符都会在 O(1) 的时间内放入新字符串中。空间复杂度:O(N)。我们开辟了与原字符串等大的空间。