[TOC]

数据库

MySQL

MySQL索引

Hash索引

  • 特点:哈希索引采用哈希算法,将键值换算成新的哈希值,并映射到对应的槽位上。哈希索引的查询效率通常很高,因为在没有产生哈希冲突的情况下,通常只需要一次检索就可以找到目标数据。
  • 限制:哈希索引只能用于对等比较(如=、in),不支持范围查询(如between、>、<等)。此外,哈希索引也无法利用索引完成排序操作,因为哈希索引是无序排列的。
  • 应用:在MySQL中,Memory存储引擎支持哈希索引。InnoDB存储引擎虽然本身不直接支持哈希索引,但具有自适应哈希功能,可以在特定条件下将B+树索引转化为哈希索引

B+Tree索引:

  • 特点:B+树是一种多路平衡查找树,其所有叶子节点在同一层,且叶子节点通过指针相连,构成了一个有序的链表。在B+树中,所有非叶子节点仅起到索引作用,包含子树所有节点的最大值,而叶子节点则包含了所有的关键字(值)。
  • 优势:由于B+树的非叶子节点只存储索引信息,因此可以容纳更多的节点元素,使得树的高度相对较低,从而减少了查找过程中磁盘I/O的存取次数。此外,叶子节点通过链表相连,便于区间查找和遍历。
  • 应用:MySQL的InnoDB存储引擎默认采用B+树作为索引的数据结构。
为什么说B+树比B树更适合数据库索引?

1、 B+树的磁盘读写代价更低:B+树的内部节点并没有指向关键字具体信息的指针,因此其内部节点相对B树更小,如果把所有同一内部节点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多,一次性读入内存的需要查找的关键字也就越多,相对IO读写次数就降低了。

2、B+树的查询效率更加稳定:由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。

3、由于B+树的数据都存储在叶子结点中,分支结点均为索引,方便扫库,只需要扫一遍叶子结点即可,但是B树因为其分支结点同样存储着数据,我们要找到具体的数据,需要进行一次中序遍历按序来扫,所以B+树更加适合在区间查询的情况,所以通常B+树用于数据库索引。

索引失效

  • 创建了组合索引,但是查询条件没有遵守最左原则
  • 在索引列上进行了计算、函数、类型转换等操作
  • 以 % 开头的LIKE查询
  • 查询条件中使用 OR,且 OR 的前后条件中有一个列没有索引,涉及的索引都不会被使用到;
  • IN 的取值范围较大时会导致索引失效,走全表扫描(NOT IN 和 IN 的失效场景相同)

Redis

常见五种基本类型

  • String:适用于简单字符缓存、分布式锁、需要简单计数的场景、需要存储常规数据的场景
  • Hash:适用于对象数据存储场景
  • List:可以用于实现栈、队列
  • Set:用来处理无序、不重复元素
  • Zset:用来处理需要随机获取数据源中元素按某个权重排序的场景(例如排行榜)

生产问题

  • 缓存穿透:大量请求的 key 是不合理的,根本不存在于缓存中,也不存在于数据库中。
    • 解决方案:布隆过滤器
  • 缓存击穿:缓存击穿中,请求的 key 对应的是热点数据 ,该数据存在于数据库中,但不存在于缓存中
    • 解决方案:针对热点数据提前预热,将其存入缓存中并设置合理的过期时间比如秒杀场景下的数据在秒杀结束之前不过期。
  • 缓存雪崩:缓存在同一时间大面积的失效,导致大量的请求都直接落到了数据库上,对数据库造成了巨大的压力。
    • 解决方案:针对热点数据提前预热,将其存入缓存中并设置合理的过期时间比如秒杀场景下的数据在秒杀结束之前不过期。

Redis为什么这么快

  1. Redis基于内存
  2. 内置了许多优化后的数据结构
  3. 通信协议实现简单且解析高效
  4. Redis 基于 Reactor 模式设计开发了一套高效的事件处理模型,主要是单线程事件循环和 IO 多路复用
    1. Redis 通过 IO 多路复用程序 来监听来自客户端的大量连接(或者说是监听多个 socket),它会将感兴趣的事件及类型(读、写)注册到内核中并监听每个事件是否发生。

缓存一致性

  1. 先更新数据库,再删除缓存。
  2. 延迟双删:先删除缓存,再更新数据库,再删除一次缓存
  3. cache-aside:更新数据库,基于 binlog 监听进行缓存删除

计算机网络

Https

HTTPS:简单讲是HTTP的安全版,在HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。

HTTPS特点:

  • 内容加密:采用混合加密技术,中间者无法直接查看明文内容
  • 验证身份:通过证书认证客户端访问的是自己的服务器
  • 保护数据完整性:防止传输的内容被中间人冒充或者篡改
  • 加密算法 —- RSA(混合加密机制)

Q:HTTPS传输过程?

A:客户端发起 HTTPS 请求,服务端返回证书,客户端对证书验证,验证通过后本地生成用于改造对称加密算法的随机数,通过证书中的公钥对随机数进行加密传输到服务端,服务端接收后通过私钥解密得到随机数,之后的数据交互通过对称加密算法进行加解密。

Q:为什么需要证书?

A:防止中间人攻击,验证服务器身份  

Q:怎么防止的篡改?

A:证书是公开的,虽然中间人可以拿到证书,但私钥无法获取,公钥无法推断出私钥,所以篡改后不能用私钥加密,强行加密客户也无法解密,强行修改内容,会导致证书内容与签名中的指纹不匹配

对称加密(Symmetric Key Encryption)是采用单钥密码系统的加密方法,同一个密钥可以同时用作信息的加密和解密,这种加密方法称为对称加密,也称为单密钥加密。

非对称加密:加密和解密使用不同的秘钥,一把公开的公钥,一把私有的私钥。公钥加密的信息只有私钥才能解密,私钥加密的信息只有公钥才能解密。

操作系统

进程

进程间的通信方式

  1. 管道/匿名管道(Pipes) :用于具有亲缘关系的父子进程间或者兄弟进程之间的通信。

  2. 有名管道(Named Pipes) : 匿名管道由于没有名字,只能用于亲缘关系的进程间通信。为了克服这个缺点,提出了有名管道。有名管道严格遵循 先进先出(First In First Out) 。有名管道以磁盘文件的方式存在,可以实现本机任意两个进程通信

  3. 信号(Signal) :信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生;

  4. 消息队列(Message Queuing) :消息队列是消息的链表,具有特定的格式,存放在内存中并由消息队列标识符标识。管道和消息队列的通信数据都是先进先出的原则。与管道(无名管道:只存在于内存中的文件;命名管道:存在于实际的磁盘介质或者文件系统)不同的是消息队列存放在内核中,只有在内核重启(即,操作系统重启)或者显式地删除一个消息队列时,该消息队列才会被真正的删除。消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取.比 FIFO 更有优势。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。

  5. 信号量(Semaphores) :信号量是一个计数器,用于多进程对共享数据的访问,信号量的意图在于进程间同步。这种通信方式主要用于解决与同步相关的问题并避免竞争条件。

  6. 共享内存(Shared memory) :使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。可以说这是最有用的进程间通信方式。

  7. 套接字(Sockets) : 此方法主要用于在客户端和服务器之间通过网络进行通信。套接字是支持 TCP/IP 的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。

springboot

注解

自定义注解

1
2
3
4
5
6
7
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {

OperationType value();

}

自定义切面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class AutoFillAspect {

@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
public void autoFillPointCut() {

}

@Before("autoFillPointCut()")
public void autoFill(JoinPoint joinPoint) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
log.info("公共字段Auto fill start");

// 方法签名对象
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
//获得方法注解上的对象
AutoFill autoFill = methodSignature.getMethod().getAnnotation(AutoFill.class);
OperationType operationType = autoFill.value();

Object[] args = joinPoint.getArgs();
if(args == null && args.length <= 0) {
return;
}

Object entity = args[0];

LocalDateTime now = LocalDateTime.now();
Long currentId= BaseContext.getCurrentId();

if(operationType == OperationType.INSERT) {
Method setCreateTime=entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME,LocalDateTime.class);
Method setUpdateTime=entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME,LocalDateTime.class);
Method setCreateUser=entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER,Long.class);
Method setUpdateUser=entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER,Long.class);

setCreateTime.invoke(entity,now);
setUpdateTime.invoke(entity,now);
setCreateUser.invoke(entity,currentId);
setUpdateUser.invoke(entity,currentId);

}else if(operationType == OperationType.UPDATE) {
Method setUpdateTime=entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME,LocalDateTime.class);
Method setUpdateUser=entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER,Long.class);

setUpdateTime.invoke(entity,now);
setUpdateUser.invoke(entity,currentId);
}

}
}

spring-cache

  • @CachePut: 将方法的返回值放到缓存里面
  • @Cacheable: 在方法执行前先查询,如果有数据直接返回;如果没有就调用方法然后把返回值放到缓存里面
  • @CacheEvict:将一条或者多条数据从缓存中删除
  • EnableCaching:开启缓存注解功能,通常加到启动类上

导入依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>

CachePut

这里key里面需要和形参一致

spring cache缓存数据,key生成:userCache::1

1
2
3
4
5
6
7
8
9
10
11
12
@CachePut(cacheNames="userCache",key="#user.id")
// 这里还要把数据库插入之后,把主键值赋给user.id
//@CachePut(cacheNames="userCache",key="#result") 返回值
//@CachePut(cacheNames="userCache",key="#p0") 第一个参数
public User save(@RequestBody User user){
userMapper.insert(user);
return user;
}

@Insert("insert into user(name,age) values(#{name},#{age})")
@Options(useGeneratedKeys=true,keyProperty="id")
void insert(User user);

Cacheable

请求这个方法之前先进入代理,查看是否存在这个缓存,如果有缓存就不会调用这个方法。

1
2
3
4
5
6
@GetMapping
@Cacheable(cacheNames="userCache",key="#id")
public User getById(Long id){
User user=userMapper.getById(id);
return user;
}

CacheEvict

方法执行完了之后清除缓存

1
2
3
4
5
@DeleteMapping
@CacheEvict(cacheNames="userCache",key="#id")
public void deleteById(Long id){
//
}

清理所有数据,这里会删除所有userCache下的键值对

1
2
3
4
5
@DeleteMapping
@CacheEvict(cacheNames="userCache",allEntries=true)
public void deleteAll(){
//
}

Java

集合

HashMap

JDK1.8 之前 HashMap 由 数组+链表 组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突)。 JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于等于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间。

关键字

synchronized 是什么?

synchronized 是 Java 中的一个关键字,翻译成中文是同步的意思,主要解决的是多个线程之间访问资源的同步性,可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。

懒汉式,线程安全

  • 是否 Lazy 初始化:是
  • 是否多线程安全:是
  • 实现难度:易
1
2
3
4
5
6
7
8
9
10
public class Singleton {  
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

自我介绍

尊敬的面试官,您好!

我叫xxx,非常荣幸能够参加本次面试,我目前就读于xxxx大学,专业是xxxxx。在校期间,我对计算机科学与软件工程产生了浓厚的兴趣,特别是java后端开发领域,通过自学和实践掌握了java的基础知识。此外我还熟悉springboot、mybatis等主流框架的使用。

在校期间实现过一个外卖平台,这个系统主要用到了springboot和mybatis框架。我在实现了登录、菜品添加、用户添加等功能。

在团队合作方面,我认为沟通和协作是非常重要的。我擅长与不同背景的人合作,能够快速适应新环境,并为团队贡献我的技术和创意。

感谢您给我这次面试的机会,我期待与您进一步交流。

参考

  1. https://javaguide.cn/cs-basics/network/other-network-questions.html
  2. HTTPS详解及常见面试题 - Duikerdd - 博客园
  3. https://javaguide.cn/java/collection/hashmap-source-code.html