查漏补缺第十七期(华为三面)
前言
目前正在出一个查漏补缺专题
系列教程, 篇幅会较多, 喜欢的话,给个关注❤️ ~
本专题主要以Java
语言为主, 好了, 废话不多说直接开整吧~
Spring Boot常用的注解有哪些
-
@SpringBootApplication
: 这是一个组合注解,用于标记主应用程序类。它包含了@Configuration
、@EnableAutoConfiguration
和@ComponentScan
三个注解,用于配置Spring Boot应用程序。 -
@RestController
: 标记一个类为RESTful控制器,处理HTTP请求并返回响应。 -
@RequestMapping
: 用于映射HTTP请求的路径和方法。 -
@GetMapping
、@PostMapping
、@PutMapping
、@DeleteMapping
: 这些注解分别用于处理HTTP的GET、POST、PUT和DELETE请求。 -
@PathVariable
: 用于获取URL路径中的参数值。 -
@RequestParam
: 用于获取请求参数的值。 -
@RequestBody
: 用于将请求的JSON或XML数据绑定到方法的参数上。 -
@ResponseBody
: 将方法的返回值直接作为HTTP响应的内容返回。 -
@Autowired
: 自动装配Bean,通过类型进行依赖注入。 -
@Value
: 注入配置文件中的属性值。 -
@Component
: 通用的组件注解,用于标记一个类为Spring容器的组件。 -
@Service
: 标记一个类为业务逻辑组件。 -
@Repository
: 标记一个类为数据访问组件。 -
@Configuration
: 标记一个类为配置类。 -
@EnableAutoConfiguration
: 开启自动配置。 -
@Conditional
: 根据条件进行Bean的条件化注册。
redis集群的几种方式详细说一下
Redis
集群是将多个Redis
节点组成一个分布式集群,以实现数据的高可用性和横向扩展。在Redis
中,有几种方式可以实现Redis
集群:
-
Redis Sentinel(哨兵模式)
:Redis Sentinel
是Redis
官方提供的高可用解决方案之一,它通过监控Redis主节点和从节点的状态,以及执行自动故障转移来实现高可用。哨兵模式中包含一个或多个哨兵节点,这些节点负责监控Redis节点的状态,并在主节点故障时自动将其中的一个从节点升级为主节点。 -
Redis Cluster(集群模式)
:Redis Cluster
是Redis
官方提供的分布式解决方案,它通过将数据分片存储在多个Redis节点上来实现数据的分布式存储和负载均衡。集群模式中包含多个节点,每个节点都是相互连接的,并共同组成一个集群。数据会根据哈希算法分布到不同的节点上,每个节点负责一部分数据。 -
第三方解决方案
: 除了Redis
官方提供的哨兵模式和集群模式,还有一些第三方解决方案可以实现Redis
的集群。这些解决方案通常是基于代理或代理-路由的架构,例如Twemproxy、Codis
等。它们在Redis
节点之上添加一个代理层,负责将客户端的请求路由到正确的节点上。
需要注意的是,无论采用哪种方式,Redis
集群都需要在部署和配置时考虑以下几个方面:
- 节点数量:确定集群中的
Redis
节点数量,以及主节点和从节点的比例。 - 数据分片:决定如何将数据分片存储在不同的节点上,以实现负载均衡和高性能。
- 故障处理:考虑节点故障时的自动故障转移和恢复机制,确保数据的高可用性。
- 客户端路由:确保客户端能够正确地路由请求到集群中的节点上。
redis缓存雪崩,缓存击穿,缓存穿透是什么,怎么解决
缓存雪崩(Cache Avalanche):
缓存雪崩指的是缓存中大量的键同时过期或失效,导致大量的请求直接落到后端数据库上,造成数据库负载骤增,甚至引发系统崩溃。这通常是由于缓存中的键具有相同的过期时间,一旦过期,大量请求同时访问数据库。
解决方案:
- 设置合理的缓存过期时间,尽量避免大量键同时失效。
- 使用分布式锁或互斥机制,保证在缓存失效时只有一个请求能够重新生成缓存。
- 在高峰期通过预热缓存或异步更新缓存来分散请求压力。
public class CacheService {
private final Cache cache;
private final Object lock = new Object();
public CacheService() {
cache = new Cache();
}
public Object getData(String key) {
Object data = cache.get(key);
if (data == null) {
synchronized (lock) {
// 在获取锁之前再次检查缓存,避免重复加载数据
data = cache.get(key);
if (data == null) {
data = fetchDataFromDatabase(key);
cache.put(key, data, expirationTime);
}
}
}
return data;
}
private Object fetchDataFromDatabase(String key) {
// 从数据库中获取数据的逻辑
}
}
缓存击穿(Cache Penetration):
缓存击穿指的是某个特定的键非常热门,但它在缓存中不存在,导致每次对该键的请求都会穿透缓存,直接查询数据库。这种情况可能是由于并发请求中有大量请求同时访问一个不存在的缓存键。
解决方案:
- 在缓存中设置空值(null)或者特殊值(例如占位符)来表示该键对应的数据不存在,避免重复查询数据库。
- 使用互斥锁或分布式锁,在查询数据库的过程中只允许一个请求进行数据库查询,其他请求等待查询结果。
public class CacheService {
private final Cache cache;
private final Object lock = new Object();
public CacheService() {
cache = new Cache();
}
public Object getData(String key) {
Object data = cache.get(key);
if (data == null) {
synchronized (lock) {
// 在获取锁之前再次检查缓存,避免重复加载数据
data = cache.get(key);
if (data == null) {
data = fetchDataFromDatabase(key);
if (data != null) {
cache.put(key, data, expirationTime);
}
}
}
}
return data;
}
private Object fetchDataFromDatabase(String key) {
// 从数据库中获取数据的逻辑
}
}
缓存穿透(Cache Miss):
缓存穿透指的是查询一个不存在于缓存和数据库中的键,导致每次对该键的请求都会直接查询数据库,浪费资源。这种情况可能是由于恶意攻击或非法请求引起的。
解决方案:
- 输入合法性校验:对请求的参数进行校验,过滤掉非法请求。
- 布隆过滤器(Bloom Filter):使用布隆过滤器来快速判断请求的键是否存在于缓存或数据库中,避免对不存在的键进行查询操作。
public class CacheService {
private final Cache cache;
private final BloomFilter<String> bloomFilter;
public CacheService() {
cache = new Cache();
bloomFilter = new BloomFilter<>();
}
public Object getData(String key) {
if (!bloomFilter.contains(key)) {
return null; // 返回空值表示数据不存在
}
Object data = cache.get(key);
if (data == null) {
synchronized (this) {
// 在获取锁之前再次检查缓存,避免重复加载数据
data = cache.get(key);
if (data == null) {
data = fetchDataFromDatabase(key);
if (data != null) {
cache.put(key, data, expirationTime);
} else {
// 数据不存在,将键添加到布隆过滤器
bloomFilter.add(key);
}
}
}
}
return data;
}
private Object fetchDataFromDatabase(String key) {
// 从数据库中获取数据的逻辑
}
}
Mysql索引为啥用B+树
MySQL使用B+树作为索引结构的主要原因有以下几点:
-
有序性:
B+树
是一种自平衡的二叉搜索树,保证了索引的有序性。在B+树中,数据按照顺序存储在叶子节点上,可以通过范围查询获取连续的数据块,提高查询效率。 -
高度平衡:
B+树
是一种平衡的树结构,所有叶子节点都在同一层,使得查询操作的时间复杂度保持在O(log N)
级别。同时,B+树通过分裂和合并操作来保持树的平衡,动态适应数据的插入和删除。 -
磁盘IO优化:
B+树
的节点采用了更大的块大小,可以减少磁盘IO的次数。B+
树的叶子节点形成了链表结构,可以进行顺序访问,减少随机IO的开销。 -
支持范围查询:
B+树
在叶子节点上形成有序的链表结构,使得范围查询更加高效。通过遍历链表即可获取满足范围条件的数据。 -
支持高效的插入和删除:
B+树
通过分裂和合并操作来维护平衡,对于数据的插入和删除操作,只需要调整少数节点,而不是整个树。 -
支持最左前缀匹配:
B+树
的特性使得它支持最左前缀匹配,即可以利用索引的最左侧列进行查询优化。
综上所述,B+树结构在MySQL中作为索引的选择,主要是为了保持有序性、支持高效的范围查询、高效的插入和删除操作,以及磁盘IO
的优化,使得数据库的查询性能得到提升。
HashSet数据结构,跟HashMap有什么区别
HashSet
和HashMap
是Java集合
框架中两种不同的数据结构,它们的区别主要体现在以下几个方面:
-
存储方式:
HashSet:HashSet
是基于哈希表实现的集合,使用哈希算法来存储元素。它通过计算元素的哈希码来确定存储位置,具有较快的查找速度。- HashMap:HashMap是基于哈希表实现的键值对存储结构,使用哈希算法来存储键值对。它通过计算键的哈希码来确定存储位置,并使用
链表
或红黑树
来解决哈希冲突,提供高效的键值对查找和存储。
-
存储内容:
HashSet:HashSet
存储的是唯一的元素集合,不允许重复元素。它使用元素的哈希码来检查和确保唯一性。HashMap:HashMap
存储的是键值对,其中键是唯一的,但值可以重复。它使用键的哈希码来进行查找和存储。
-
访问方式:
HashSet:HashSet
只能通过元素来进行访问,没有提供通过索引或键的方式访问元素。HashMap:HashMap
可以通过键来访问值,提供了键值对的操作方法。
-
内存消耗:
HashSet:HashSet
只需要存储元素,不需要存储键值对,因此通常比HashMap
占用更少的内存。HashMap:HashMap
需要同时存储键和值,因此占用的内存通常比HashSet
更多。
需要注意的是,HashSet
实际上是通过HashMap
实现的,HashSet
的元素存储在HashMap
的键的位置上,而值则统一使用一个静态的Object
对象作为占位符。
char和varchar的区别
在Mysql
中,char
和varchar
是两种常见的字符数据类型,它们在以下几个方面有所不同:
-
存储方式:
char:char
是固定长度的字符数据类型,它会占用固定的存储空间,不管实际存储的字符数是多少。例如,如果定义char(10)
,则无论实际存储的字符数是1
个还是10
个,它都会占用10个字符的存储空间。varchar:varchar
是可变长度的字符数据类型,它只会占用实际存储的字符数所需的存储空间。例如,如果定义varchar(10)
,如果实际存储的字符数是5
个,它只会占用5个字符的存储空间。
-
存储效率:
char
:由于char
是固定长度的,无论实际存储的字符数是多少,都会占用固定的存储空间。这可能导致一些浪费,尤其是当存储的字符串长度较短时。varchar
:由于varchar
是可变长度的,只占用实际存储的字符数所需的存储空间,可以节省存储空间。
-
存储限制:
char
:由于char
是固定长度的,因此它有一个最大长度限制。如果尝试存储超过指定长度的字符,则会被截断或引发错误。varchar
:varchar
的长度限制通常较大,可以存储更长的字符序列。不过,具体的长度限制取决于数据库的实现和配置。
-
查询性能:
char
:由于char
是固定长度的,查询时可以更快地定位到所需的字符位置,因为每个字符占用相同的存储空间。varchar
:由于varchar
是可变长度的,查询时需要先确定实际存储的字符数,然后再定位到所需的字符位置,可能稍微影响一些查询性能。
综上所述,char
和varchar
在存储方式、存储效率、存储限制和查询性能等方面有所不同。选择使用哪种类型取决于具体的需求,如果需要存储长度固定的字符串或对存储空间要求较高,可以选择char
;如果需要存储长度可变的字符串或更灵活地使用存储空间,可以选择varchar
。
Mysql建索引的原则,索引是不是越多越好,为什么
在MySQL
中建立索引时,有几个原则需要考虑,而索引并不是越多越好,主要考虑以下几个方面:
-
选择合适的索引列:选择那些在查询条件、连接条件和排序操作中频繁使用的列作为索引列。通过分析查询语句和业务需求,选择对查询性能有重要影响的列作为索引列,可以有效提高查询速度。
-
考虑列的选择性:选择性是指索引列中不同取值的数量与表中总记录数的比例。选择性越高,表示不同取值的数量相对较少,索引的效果通常会更好。例如,一个性别列只有两个取值(男、女),选择性较高,适合建立索引;而一个日期列的选择性可能就较低,取值范围广泛,建立索引的效果可能有限。
-
避免过多的索引:虽然索引可以提高查询速度,但是过多的索引也会增加数据库的维护成本和存储空间占用。每个索引都需要占用一定的磁盘空间,并且在数据修改时需要更新索引,影响写操作的性能。因此,只建立那些真正有必要的索引,避免过度索引。
-
组合索引的选择:对于经常同时使用多个列作为查询条件的情况,可以考虑建立组合索引。组合索引可以减少索引的个数,并且在多个列组合起来进行查询时,可以更好地利用索引。
-
定期维护和优化索引:随着数据的增删改,索引的维护也很重要。定期检查索引的使用情况和性能表现,对需要优化的索引进行调整或删除,可以提高查询效率。
总的来说,索引的目的是提高查询性能,但是建立过多的索引可能会带来额外的开销。正确选择索引列、合理设置索引数量,并定期维护和优化索引,是建立高效索引的关键。根据具体的业务需求和查询模式进行评估和调整,才能达到最佳的查询性能。
算法题: 链表求和 (力扣02.05)
给定两个用链表表示的整数,每个节点包含一个数位。这些数位是反向
存放的,也就是个位排在链表首部。
输入:(7 -> 1 -> 6) + (5 -> 9 -> 2),即617 + 295
输出:2 -> 1 -> 9,即912
输入:(6 -> 1 -> 7) + (2 -> 9 -> 5),即617 + 295
输出:9 -> 1 -> 2,即912
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode head = null, tail = null;
int carry = 0;
while (l1 != null || l2 != null) {
int n1 = l1 != null ? l1.val : 0;
int n2 = l2 != null ? l2.val : 0;
int sum = n1 + n2 + carry;
if (head == null) {
head = tail = new ListNode(sum % 10);
} else {
tail.next = new ListNode(sum % 10);
tail = tail.next;
}
carry = sum / 10;
if (l1 != null) {
l1 = l1.next;
}
if (l2 != null) {
l2 = l2.next;
}
}
if (carry > 0) {
tail.next = new ListNode(carry);
}
return head;
}
}
时间复杂度为O(max(m,n)),其中m,n是链表的节点数 空间复杂度O(1)
进阶问题: 假如是正向
的呢?
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
Deque<Integer> stack1 = new ArrayDeque<Integer>();
Deque<Integer> stack2 = new ArrayDeque<Integer>();
while (l1 != null) {
stack1.push(l1.val);
l1 = l1.next;
}
while (l2 != null) {
stack2.push(l2.val);
l2 = l2.next;
}
int carry = 0;
ListNode ans = null;
while (!stack1.isEmpty() || !stack2.isEmpty() || carry != 0) {
int a = stack1.isEmpty() ? 0 : stack1.pop();
int b = stack2.isEmpty() ? 0 : stack2.pop();
int cur = a + b + carry;
carry = cur / 10;
cur %= 10;
ListNode curnode = new ListNode(cur);
curnode.next = ans;
ans = curnode;
}
return ans;
}
}
时间复杂度为O(max(m,n)),其中m,n是链表的节点数 空间复杂度O(m+n)
结束语
大家可以针对自己薄弱的地方进行复习, 然后多总结,形成自己的理解,不要去背~
本着把自己知道的都告诉大家,如果本文对您有所帮助,点赞+关注
鼓励一下呗~
相关文章
- 查漏补缺第一期(Redis相关)
- 查漏补缺第二期(synchronized & 锁升级)
- 查漏补缺第三期(分布式事务相关)
- 查漏补缺第四期(Mysql相关)
- 查漏补缺第五期(HashMap & ConcurrentHashMap)
- 查漏补缺第六期(京东一面)
- 查漏补缺第七期(美团到店一面)
- 查漏补缺第八期(阿里一面)
- 查漏补缺第九期(阿里二面)
- 查漏补缺第十期(网易实习一面)
- 查漏补缺第十一期(网易实习二面)
- 查漏补缺第十二期(网易实习三面)
- 查漏补缺第十三期(滴滴实习一面)
- 查漏补缺第十四期(滴滴实习二面)
- 查漏补缺第十五期(华为一面)
- 查漏补缺第十六期(华为二面)
项目源码(源码已更新 欢迎star⭐️)
往期设计模式相关文章
- 一起来学设计模式之认识设计模式
- 一起来学设计模式之单例模式
- 一起来学设计模式之工厂模式
- 一起来学设计模式之建造者模式
- 一起来学设计模式之原型模式
- 一起来学设计模式之适配器模式
- 一起来学设计模式之桥接模式
- 一起来学设计模式之组合模式
- 一起来学设计模式之装饰器模式
- 一起来学设计模式之外观模式
- 一起来学设计模式之享元模式
- 一起来学设计模式之代理模式
- 一起来学设计模式之责任链模式
- 一起来学设计模式之命令模式
- 一起来学设计模式之解释器模式
- 一起来学设计模式之迭代器模式
- 一起来学设计模式之中介者模式
- 一起来学设计模式之备忘录模式
- 一起来学设计模式之观察者模式
- 一起来学设计模式之状态模式
- 一起来学设计模式之策略模式
- 一起来学设计模式之模板方法模式
- 一起来学设计模式之访问者模式
- 一起来学设计模式之依赖注入模式
设计模式项目源码(源码已更新 欢迎star⭐️)
Kafka 专题学习
- 一起来学kafka之Kafka集群搭建
- 一起来学kafka之整合SpringBoot基本使用
- 一起来学kafka之整合SpringBoot深入使用(一)
- 一起来学kafka之整合SpringBoot深入使用(二)
- 一起来学kafka之整合SpringBoot深入使用(三)
项目源码(源码已更新 欢迎star⭐️)
ElasticSearch 专题学习
项目源码(源码已更新 欢迎star⭐️)
往期并发编程内容推荐
- Java多线程专题之线程与进程概述
- Java多线程专题之线程类和接口入门
- Java多线程专题之进阶学习Thread(含源码分析)
- Java多线程专题之Callable、Future与FutureTask(含源码分析)
- 面试官: 有了解过线程组和线程优先级吗
- 面试官: 说一下线程的生命周期过程
- 面试官: 说一下线程间的通信
- 面试官: 说一下Java的共享内存模型
- 面试官: 有了解过指令重排吗,什么是happens-before
- 面试官: 有了解过volatile关键字吗 说说看
- 面试官: 有了解过Synchronized吗 说说看
- Java多线程专题之Lock锁的使用
- 面试官: 有了解过ReentrantLock的底层实现吗?说说看
- 面试官: 有了解过CAS和原子操作吗?说说看
- Java多线程专题之线程池的基本使用
- 面试官: 有了解过线程池的工作原理吗?说说看
- 面试官: 线程池是如何做到线程复用的?有了解过吗,说说看
- 面试官: 阻塞队列有了解过吗?说说看
- 面试官: 阻塞队列的底层实现有了解过吗? 说说看
- 面试官: 同步容器和并发容器有用过吗? 说说看
- 面试官: CopyOnWrite容器有了解过吗? 说说看
- 面试官: Semaphore在项目中有使用过吗?说说看(源码剖析)
- 面试官: Exchanger在项目中有使用过吗?说说看(源码剖析)
- 面试官: CountDownLatch有了解过吗?说说看(源码剖析)
- 面试官: CyclicBarrier有了解过吗?说说看(源码剖析)
- 面试官: Phaser有了解过吗?说说看
- 面试官: Fork/Join 有了解过吗?说说看(含源码分析)
- 面试官: Stream并行流有了解过吗?说说看
推荐 SpringBoot & SpringCloud (源码已更新 欢迎star⭐️)
博客(阅读体验较佳)
转载自:https://juejin.cn/post/7250177390278049848