秋招面经记录
- MySQL
- Redis
- 项目
- 分布式
- 框架
- java
- 网络
- 数据结构
- 设计模式
- HR
- 手撕
MySQL
-
Mysql中有1000万条数据,每次查询10条,该如何优化(答:Limit子查询优化)
select t.* from t_topic t LIMIT 90000,10;
对上面的mysql语句说明:limit 90000,10的意思扫描满足条件的90010行,扔掉前面的90000行,返回最后的10行,问题就在这里,如果是limit 100000,100,需要扫描100100行,并且此处查询是普通查询。优化:建立id索引
select * from t_topic where id>=(select id from t_topic limit 90000,1) limit 10;
先使用索引查询到第90000行数据的主键值,然后在根据主键值查询后续的数据gpt: 如果查询的数据是索引列,并且该索引是有序的(例如,主键索引或普通索引),
那么LIMIT 90000, 1 的执行方式会稍有不同。在这种情况下,
DBMS 可能会利用索引的有序性来直接定位和返回指定范围内的数据,而无需扫描和跳过大量的记录。 -
有了解过mysql索引吗
-
项目中使用到索引的情况(答:覆盖索引,避免回表)
-
B+树和b树区别
-
内连接和外连接区别
-
Innodb 和myisam区别
(1)事务支持:InnoDB是支持事务处理的存储引擎,它遵循ACID(原子性、一致性、隔离性和持久性)特性。它支持提交(COMMIT)和回滚(ROLLBACK)操作,适用于需要处理并发操作和保证数据完整性的应用程序。MyISAM不支持事务处理,不能进行回滚操作。因此,在并发访问下可能会导致数据不一致的问题。(2)锁机制:InnoDB使用行级锁(row-level locking),这意味着在同时处理多个事务时,只锁定受影响的行,而不是整个表。这样可以提高并发性能,允许多个事务同时读取和修改不同的行。MyISAM使用表级锁(table-level locking),这意味着在执行写操作时会锁定整个表,这会导致并发性能受限,因为其他事务无法同时读取或写入相同的表。(3)外键支持:InnoDB支持外键约束(foreign key),可以通过定义外键关系来保证数据的引用完整性。MyISAM不支持外键约束,无法自动检查和强制执行关联表之间的引用完整性。(4)崩溃恢复:InnoDB具有崩溃恢复能力,可以在数据库异常崩溃时进行自动恢复,并保持数据的一致性。MyISAM在崩溃时无法进行自动恢复,可能会导致数据损坏或丢失。(5)全文索引:InnoDB从MySQL 5.6版本开始支持全文索引(Full-Text Index),可以用于高效地进行全文搜索。MyISAM一直以来都支持全文索引,并提供了一些额外的全文搜索功能。综上所述,InnoDB适用于需要事务支持、并发性能较高、数据完整性要求较高的应用程序;而MyISAM适用于对事务支持要求不高、读操作较多、并发性能要求不高的应用程序。根据实际需求选择合适的存储引擎是很重要的。
-
explain命令查询出的字段
(1)id:表示查询的标识符,每个子查询都有一个唯一的标识符。(2)select_type:表示查询的类型。SIMPLE:简单查询,不包含子查询或UNION查询。PRIMARY:最外层查询。SUBQUERY:子查询(非UNION子查询)。DERIVED:派生表(FROM子句中的子查询)。UNION:UNION连接的第二个或后续查询。UNION RESULT:UNION的结果。table:表示访问的表名。(3)type:表示连接类型或访问类型。const:通过索引返回的常量值查询。eq_ref:使用唯一索引或主键进行连接。ref:使用非唯一索引进行连接。range:使用索引范围查询。index:全表扫描,遍历整个索引树。all:全表扫描,没有使用索引。(4)possible_keys:表示可能选择的索引。(5)key:表示实际选择的索引(可能为NULL)。(6)key_len:表示索引字段的长度。(7)ref:表示连接时使用的字段。(8)rows:表示预计需要扫描的行数。(9)filtered:表示通过条件过滤后的比例。(10)Extra:表示额外的信息。Using index:只使用了索引树,不需要访问表数据。Using where:在获取数据之前进行了WHERE过滤。Using temporary:需要创建临时表来处理查询。Using filesort:需要额外的排序操作。Using join buffer:需要使用连接缓冲区。Impossible where:WHERE子句总是返回false,不会有匹配的行。Select tables optimized away:查询可以被优化为常量查询。
-
B+树结点区别
-
数据库中有某个二级索引name,有若干行数据为null,现在使用name is null统计null的行数,是否会走索引。
例如:select * from table where name is null非聚簇索引是通过B+树的方式进行存储的,null值作为最小数看待,全部放在树的最左边,形成链表,如果获取is null的数据,可以从最左开始 直到找到记录不是null结束。 决定is null或者is not null走不走索引取决于执行成本,是否走索引取决于优化器。通过非聚簇索引查询需要回表才能获得记录数据(覆盖索引除外),那么在这过程中优化器发现回表次数太多,执行成本已经超过全表扫描.例如:几乎所有数据都命中,都需要回表.这个时候,优化器会放弃索引,走效率更高全表扫描
-
索引删除和添加是否会改变整个索引结构
Redis
-
Redis中bitmap的原理,如何做签到统计
Bitmap,即位图,是一串连续的二进制数组(0和1),可以通过偏移量(offset)定位元素。BitMap通过最小的单位bit来进行0|1的设置,表示某个元素的值或者状态,时间复杂度为O(1)。Bitmap 本身是用 String 类型作为底层数据结构实现的一种统计二值状态的数据类型。String 类型是会保存为二进制的字节数组,所以,Redis 就把字节数组的每个 bit 位利用起来,用来表示一个元素的二值状态,你可以把 Bitmap 看作是一个 bit 数组。由于 bit 是计算机中最小的单位,使用它进行储存将非常节省空间,特别适合一些数据量大且使用二值统计的场景。
-
redis持久化方式,区别
-
redis的使用场景
-
为什么使用redis作为缓存
-
Redis高性能高并发的原因
(1)基于内存的存储:Redis将数据存储在内存中,相对于磁盘存储来说,内存访问速度更快。这使得Redis能够快速读取和写入数据,从而实现高性能。(2)单线程模型:Redis采用了单线程模型,即每个Redis实例都由一个主线程来处理所有请求。虽然看起来似乎不会支持高并发,但实际上,这种单线程模型通过异步非阻塞的方式处理多个客户端请求,从而避免了线程切换的开销。这使得Redis能够有效地处理大量并发请求。(3)事件驱动:Redis使用事件驱动的方式来处理客户端请求,它依赖于事件循环(event loop)来监听和处理事件。这种事件驱动的机制允许Redis在一个线程中高效地处理多个客户端连接,而无需创建大量线程,从而减少了线程管理的开销。(4)非阻塞IO操作:Redis使用非阻塞IO操作,这意味着它可以在等待IO完成的同时继续处理其他请求,而不会被阻塞。这对于高并发场景非常重要,因为它允许Redis在处理多个客户端请求时保持高响应性。(5)高效的数据结构:Redis内置了许多高效的数据结构,如哈希表、跳跃表和位图,这些数据结构在不同场景下提供了高性能的数据操作。(6)持久性选项:尽管Redis主要是一个内存数据库,但它提供了多种持久性选项,可以将数据定期保存到磁盘上。这可以确保即使在服务器重启时,数据也不会丢失,同时不会牺牲太多性能。(7)集群和复制:Redis支持主从复制和集群模式,这可以通过多个Redis实例来提高性能和可用性。主从复制可以将负载分散到多个节点上,而集群模式可以水平扩展以应对更多的并发请求。(8)优化的网络协议:Redis使用自己的高效二进制协议,该协议具有较低的开销,可以在网络上传输更少的数据,从而提高了网络性能。
-
IO多路复用
IO多路复用-select方式:
内核数据准备好或者超时后会将数据拷贝到用户空间,然后用户空间遍历fd_set,找到就绪的fd,读取其中的数据
IO多路复用-poll方式:
IO多路复用-epoll方式:
信号趋动IO
-
Redis中的IO(网络模型IO多路复用)
前提:
以一个基于epoll的web服务为例理解redis中网络模型对应的api -
redis mysql区别(从处理业务的角度回答)
-
Redis setnx分布式锁
(1)获取锁:key:自定义前缀+参数name,name为模块名+用户ID,可以实现对同一用户加锁value: UUID+当前线程ID设置过期时间(2)释放锁:lua脚本:根据value判断是否是自己的锁,然后del key删除锁(3)自动续期:在获取到锁的同时,启动一个定时任务或者后台线程,定时使用 Lua 脚本更新锁的过期时间。Lua 脚本可以通过比较当前锁的值是否与之前设置的唯一标识符相同来确保只有持有锁的客户端可以进行续期操作。续期时间应该小于锁的过期时间,以确保在客户端因故障或其他原因导致无法续期时,锁能够最终过期释放。(4)注意锁的过期时间需要根据业务操作的预估时间和系统负载来设置,避免锁的过期时间过短或过长。续期操作需要保证原子性,可以使用 Lua 脚本将获取锁和续期操作原子化执行。锁的持有时间应该有一个上限,避免因为某个客户端发生故障而导致锁无法被释放。客户端在获取锁前,可以设定一个尝试获取锁的超时时间,避免长时间的等待。
-
守护线程可以续期(如何实现),当过期时间为10,守护线程在9时进行续期,但是如果此时发生gc,出现stw,此时续期会失败。该如何解决。
-
Redission原理
链接以上使用setnx实现分布式锁步骤考虑到了使用分布式锁需要考虑的互斥性、防死锁、加锁和解锁必须为同一个进程等问题, 但是锁的续期无法实现。所以通常情况都是采用 Redisson 实现 Redis 的分布式锁, 借助Redisson的 WatchDog机制能够很好的解决锁续期的问题。 Watch Dog 机制其实就是一个后台定时任务线程,获取锁成功之后,会将持有锁的线程放入到一个 RedissonLock.EXPIRATION_RENEWAL_MAP里面, EXPIRATION_RENEWAL_MAP中的key为锁名,value为uudId+线程id; 然后每隔 10 秒 (internalLockLeaseTime / 3)定时执行一次定时任务 -->检查一下,如果客户端 1 还持有锁 key. 判断客户端是否还持有 key,其实就是遍历 EXPIRATION_RENEWAL_MAP 里面线程 id 然后根据线程 id 去 Redis 中查,如果存在就会延长 key 的时间, 那么就会不断的延长锁 key 的生存时间。
-
缓存雪崩
链接描述
项目
- 实习遇到的问题
- 两个项目上线了吗,单独完成还是合作
- 项目redis各种数据结构使用场景
- 项目中三方库替换后,如果后续又超期再次替换是否还会很麻烦:
不会:本次替换已经将所有涉及api封装到工具类,下次直接修改该脚本 - 项目中用到了es,了解的底层原理吗
只了解倒排索引 - 介绍一下实习项目,以及负责的excel上传下载接口的逻辑
- 你们项目中如果程序出错了怎么处理的
用户导致的:抛出错误提示。
系统内部:回滚 - 项目中的工作量(安全连线、三方库替换)
- 三方库替换将设计函数封装为utils,java中那些场景需要封装工具类
- 例如:string相关的操作需要封装工具类吗
- 封装工具类仅仅是为了维护和修改方便吗,有其他原因吗
- 项目遇到的困难
- Python、Java的掌握程度、学习时间及方式
分布式
-
异步业务:类似于购物下单,异步处理如果失败了怎么办
重试+死信队列(你这个思路业务太复杂,不好实现)
(1)业务逻辑有问题,无法执行成功
(2)依赖的第三方服务有问题:例如mysql宕机了,暂时无法执行
-
访问量过高,系统如何设计(高可用)
高可用设计
集群、限流(令牌桶)、熔断、降级、超时重试、异步调用
-
分布式事务理论
CAPCAP 也就是 Consistency (一致性) 、Availability (可用性) 、Partition Tolerance(分区容错性) 这三个单词首字母组合。 一致性 (Consistence) :所有节点访问同一份最新的数据副本 可用性 (Availability):非故障的节点在合理的时间内返回合理的响应(不是错误或者超时的响应)。 分区容错性 (Partition tolerance) : 分布式系统出现网络分区的时候,仍然能够对外提供服务。
网络分区:分布式系统中,多个节点之前的网络本来是连通的,但是因为某些故障(比如部分节点网络出了问题) 某些节点之间不连通了,整个网络就分成了几块区域,这就叫网络分区。
当发生网络分区的时候,如果我们要继续服务,那么强一致性和可用性只能 2 选1。 也就是说当网络分区之后 P 是前提,决定了 P 之后才有 C 和 A 的选择。 也就是说分区容错性 (Partition tolerance) 我们是必须要实现的。 简而言之就是: CAP 理论中分区容错性 P 是一定要满足的,在此基础上,只能满足可用性 A 或者一致性 C。 为啥无同时保证 CA 呢? 举个例子: 若系统出现“分区”,系统中的某个节点在进行写操作。为了保证 C, 必须要禁止其他节点的读写操作,这就和 A 发生冲突了。 如果为了保证 A,其他节点的读写操作正常的话,那就和 C 发生冲突了。 选择的关键在于当前的业务场景,没有定论,比如对于需要确保强一致性的场景如银行般会选择保证 CP 。
BASE
BASE 是 Basically Available (基本可用) 、Soft-state (软状态) 和 EventuallyConsistent (最终一致性) 三个短语的缩写。 BASE 理论是对 CAP 中一致性 C 和可用性A 权衡的结果,其来源于对大规模互联网系统分布式实践的总结, 是基于 CAP 定理逐步演化而来的,它大大降低了我们对系统的要求。 核心思想:也就是牺牲数据的强一致性来满足系统的高可用性,系统中一部分数据不可用或者不一致时,仍需要保持系统整体“主要可用”
分布式一致性的 3 种级别:1.强一致性 : 系统写入了什么,读出来的就是什么 2.弱一致性 : 不一定可以读取到最新写入的值,也不保证多少时间之后读取到的数据是最新的,只是会尽量保证某个时刻达到数据一致的状态。 3.最终一致性:弱一致性的升级版。,系统会保证在一定时间内达到数据一致的状态, 业界比较推崇是最终一致性级别,但是某些对数据一致要求十分严格的场景比如银行转账还是要保证强一致性。
-
分布式事务实现方式
链接 -
分布式事务场景:向mysql和mq写入数据
本地消息表实现:
(1)使用消息表记录需要写入mq的消息记录,信息写入mysql。(同一个事务执行)
(2)使用后台程序源源不断的扫描消息表中的记录并写入mq,若写入成功则删除对应消息 -
1000万条数据,找出出现次数最多的前100条数据(内存有限)
外部排序内次取10万条放入内存,找出前100条数据,然后归并
存在问题:某个数据并不是在前100,但是由于其分散不均匀,导致其在某块中位于前100,这种情况该如何解决。
-
0-68包含2,或7数字的个数
有规律,可以写出递推公式吗?(听她讲了好久还是没明白)假设范围内的最大数字有n位,则从最高位开始计算,f(n)表示第n位为2,7的数字个数。则f(n) = x * 10 ^(n-1),其中x是判断第n位的数可以取2,7的数字个数。所以解答的核心是如何确定x:(1)针对于最高位,要防止越上界,最高位为6,只能包含2,所以x=1(2)针对于最低为,要防止越上界,最低为为8,可以包含2,7,所以x=2(3)针对于中间位,x值为题目要求包含的数字个数。Eg:2,7都可以,所以为2以6为例,只包含2所以x=1。最高位和最低位的x要分开处理,中间位置的x取值均为2(要求包含数字的个数)以0-68为例, n = 2;F(2) = 1 * 10^1F(1)= 2 * 10^0最终将f(0) + ... + f(n)求和即可。以0-100为例,求包含2或7数字的个数F(3) = 0 * 10 ^2F(2) = 2 * 10 ^ 1F(3) = 0 * 10 ^ 0最终结果为20个数字即(2,12,...92和20,21,...29)
-
20个服务节点,结点有输入输出均为list,某个节点的输出list中的某个值可能是其他节点的输入,如何能快速执行完20个结点。
拓扑图。如何构建拓扑图是一个问题
面试官:可以用空间换时间,一直在问空时复杂度 -
MQ的设计模式
(1)发布-订阅模式(Publish-Subscribe Pattern):描述:发布-订阅模式允许一个消息发布者将消息发送到多个订阅者,而不需要订阅者知道消息来自哪个发布者。用例:用于实现广播消息,例如新闻订阅、实时通知等。实现:通常通过主题(Topic)或交换机(Exchange)来实现,发布者将消息发布到特定主题,订阅者订阅感兴趣的主题。(2)点对点模式(Point-to-Point Pattern):描述:点对点模式中,消息从一个发送者传递到一个接收者,每个消息只能被一个接收者消费。用例:用于实现任务队列,例如工作排队、负载均衡等。实现:通常通过队列(Queue)来实现,消息被发送到队列,接收者从队列中获取消息。(3)请求-响应模式(Request-Reply Pattern):描述:请求-响应模式允许客户端发送请求消息并等待服务器响应消息,通常用于同步通信。用例:用于RPC(远程过程调用)或需要精确响应时间的场景。实现:客户端发送请求消息到队列或主题,服务器接收请求并发送响应消息。(4)消息过滤模式(Message Filter Pattern):描述:消息过滤模式允许订阅者根据特定条件过滤消息,只接收满足条件的消息。用例:用于筛选感兴趣的消息,减少不必要的消息传递。实现:订阅者可以定义消息过滤条件,MQ将消息传递给满足条件的订阅者。(5)消息重试模式(Message Retry Pattern):描述:消息重试模式允许在消息传递失败时进行重试,以确保消息被成功处理。用例:用于处理失败恢复或消息可靠性要求高的场景。实现:通常通过设定重试策略、延迟重试或死信队列来实现。(6)消息确认模式(Message Acknowledgment Pattern):描述:消息确认模式允许消费者发送确认消息,告知MQ消息已经被成功处理。用例:用于确保消息不会被重复处理。实现:通常有手动确认和自动确认两种方式,消费者发送确认消息以确认消息处理完成。
-
Mq的使用场景
异步任务,eg:下单、分布式事务 -
下单的完整流程(包含付款,判断库存、异步等操作的前后顺序
(1)下单请求:客户端发起下单请求,将订单信息发送到服务器。(2)库存检查:服务器接收到下单请求后,首先进行库存检查,确保所需商品的库存足够。并把用户欲购买的商品锁定,设定一个锁定时间,锁定时间内没有付款则释放库存。如果库存不足,服务器发送一条库存不足的通知给客户端,客户端可以选择修改订单或者取消订单。(3)付款处理:如果库存检查通过,服务器将订单信息发送给付款系统进行付款处理。这可以是同步或异步的过程,具体取决于付款系统的设计和性能要求。付款系统处理付款请求,返回付款结果。(4)订单创建:服务器接收到付款结果后,将订单信息保存到数据库中,创建订单。订单创建成功后,服务器生成订单号等相关信息。(5)通知客户下单成功(6)异步处理其他操作:除了上述基本流程,可能还有其他后续操作,如发货、通知物流、发送发票等。这些操作通常是异步的,可以通过消息队列来处理。服务器将这些操作封装成消息,发送到相应的消息队列,等待后续处理。(7)后续处理:后续处理可以由独立的服务或者工作者来完成。这些工作者从消息队列中接收消息,执行相应的操作。例如,发货工作者会从消息队列中接收到需要发货的订单信息,然后进行发货操作。这些后续操作可以并行执行,提高了系统的性能和可伸缩性。(8)通知客户端:在完成后续操作后,服务器可以发送通知给客户端,告知订单的状态更新,如发货通知、物流跟踪信息等。
-
大数据了解吗?Eg:分库分表
链接描述 -
redis mysql数据一致性
延迟双删(小林coding)
canal保证一致性
框架
-
Spring 和springboot区别,springboot特点
-
ioc aop
-
循环依赖问题
循环依赖就是A依赖B,而B又依赖A,Spring是怎么做的?
其实也是在Spring的生命周期里面,从Spring的生命周期可以知道,对象属性的注入在对象实例化之后的。它的大致过程是这样的:(1)首先A对象实例化,然后对属性进行注入,发现依赖B对象;(2)此时B对象还没有创建出来,所有转头去实例化B对象(3)B对象实例化之后,发现需要依赖A对象,那A对象已经实例化了,所以B对象最终能完成创建(4)B对象返回到A对象的属性注入的方法上,A对象最终完成创建原理:三级的缓存,三级缓存其实就是三个MapsingletonObjects(一级,日常实际获取Bean的地方)earlySingletonObjects(二级,还没进行属性注入,由三级缓存放进来)singletonFactories(用于保存bean创建工厂,以便于后面扩展有机会创建代理对象)
-
A对象实例化之后,属性注入之前,其实会把A对象通过addSingletonFactory()放入三级缓存中,key是beanName,value是singletonFactories(ObjectFactory)。
-
等到A对象属性注入(setter 方法属性注入)时,发现依赖B,那么他就会从三级缓存中的第一级缓存开始依次查找有没有B对应的Bean,肯定都没有啊,因为B还没创建呢,又去实例化B。
-
B就开始创建了,先实例化一个B对象,然后缓存对应的一个singletonFactories到第三级缓存中,然后就到了需要处理属性注入,即B属性注入需要去获取A对象,这里就是从三级缓存中的第一级缓存开始依次查找有没有A对应的Bean,结果从三级缓存里拿出singletonFactories,调用其getObject得到对应的实例化但未初始化的Bean(不完整对象A的引用)。
-
然后将早期的A对象放到二级缓存,删除三级缓存中的A。为什么需要放到二级缓存,主要是怕还有其他的循环依赖,如果还有的话,直接从二级缓存中就能拿到早期的A对象(保证对象的一致性)。(等到bean完全初始化之后,就会把二级缓存给remove掉,塞到一级缓存中)。
-
于是接下来就把早期的A对象注入给B,此时B的属性注入A对象就完成了,之后再经过其他阶段的处理之后,B对象就完完全全的创建完了。
-
B对象创建完之后,就会将B放入第一级缓存,然后清空B对应的第三级缓存,当然也会去清空第二级缓存,只是没有而已,至于为什么清空,很简单,因为B已经完全创建好了,如果需要B那就在第一级缓存中就能查找到,不需要在从第二级或者第三级缓存中找到早期的B对象。
-
B对象完完全全的创建完之后。然后A就会跟B一样,继续处理其它阶段的,完全创建好之后,也会清空二三级缓存,放入第一级缓存。
-
B在构建的时候,已经注入了A的引用,虽然是早期的A,但的确是A对象,现在又把B注入给了A,那么是不是已经解决了循环依赖的问题了,A和B都各自注入了对方,如图。上面的A是A单例对象的引用,所以还是符合单例模式
主要是第二级和第三级用来存早期的对象,这样在有循环依赖的对象,就可以注入另一个对象的早期状态(引用),从而达到解决循环依赖的问题,由于是同一个引用,所以,早期状态的对象在构建完成之后,也就会成为完完全全可用的对象。总结:上面解决的循环依赖其实是属性注入时的循环依赖,即setter 方法的注入。Spring中的属性注入的方式:(1)setter注入(2)构造方法注入(3)基于注解的注入如果是构造器的注入,spring则不能解决,因为对于构造器的循环依赖来说,在 bean 调用构造器实例化之前(bean生命周期的实例化),bean还没有实例化,所以一二三级缓存并没有 bean 的任何相关信息,在实例化之后才放入三级缓存中,因此当 getBean 的时候缓存并没有命中,这样就抛出了循环依赖的异常了。所以spring不能解决构造器的循环依赖问题:
问题1: 如果只有一级缓存可以解决循环依赖吗(一级缓存放的是完整bean)
实例化A对象。填充A的属性阶段时需要去填充B对象,而此时B对象还没有创建,所以这里为了完成A的填充就必须要先去创建B对象;实例化B对象。执行到B对象的填充属性阶段,又会需要去获取A对象,而此时Map中没有A,因为A还没有创建完成,导致又需要去创建A对象。这样,就会循环往复,一直创建下去,只到堆栈溢出。为什么不能在实例化A之后就放入Map?因为此时A尚未创建完整,所有属性都是默认值,并不是一个完整的对象,在执行业务时可能会抛出未知的异常。所以必须要在A创建完成之后才能放入Map。
问题2:如果只有一二级缓存
此时我们引入二级缓存用另外一个Map2 {k:name; v:earlybean} 来存储已经开始创建但是尚未完整创建的对象。1.实例化A对象之后,将A对象放入Map2中。2.在填充A的属性阶段需要去填充B对象,而此时B对象还没有创建,所以这里为了完成A的填充就必须要先去创建B对象。3.创建B对象的过程中,实例化B对象之后,将B对象放入Map2中。4.执行到B对象填充属性阶段,又会需要去获取A对象,而此时Map中没有A,因为A还没有创建完成,但是我们继续从Map2中拿到尚未创建完毕的A的引用赋值给a字段。这样B对象其实就已经创建完整了,尽管B.a对象是一个还未创建完成的对象。5.此时将B放入Map并且从Map2中删除。6.这时候B创建完成,A继续执行b的属性填充可以拿到B对象,这样A也完成了创建。7.此时将A对象放入Map并从Map2中删除。8.B中提前注入了一个没有经过初始化的A类型对象不会有问题吗?虽然在创建B时会提前给B注入了一个还未初始化的A对象,但是在创建A的流程中一直使用的是注入到B中的A对象的引用,之后会根据这个引用对A进行初始化,所以这是没有问题的。
问题3:二级缓存已然解决了循环依赖问题,为什么还需要三级缓存?
只要两个缓存确实可以做到解决循环依赖的问题,但是有一个前提这个bean没被AOP进行切面代理,如果这个bean被AOP进行了切面代理,那么只使用两个缓存是无法解决问题,因为一二级缓存中存放的都是原始对象,而我们需要注入的其实是A的代理对象。
问题4:为什么不能只使用一级+三级缓存
如上一个问题所述,如果出现循环依赖+aop时,属性注入的就是代理对象,但是,三级缓存有一个硬性要求:多个地方注入这个动态代理对象需要保证都是同一个对象,如果多个对象依赖了A,则它们在三级缓存中的取出来的A的动态代理对象每次都是由工厂创建的新对象,地址值不一样。所以需要一个二级缓存来存,保证代理对象的一致性,如果二级里面有就不用查三级了。
-
Mvc原理
流程说明:
(1) 客户端(浏览器)发送请求, DispatcherServlet拦截请求。
(2) DispatcherServlet 根据请求信息调用 HandlerMapping 。HandlerMapping 根据 URL 去匹配查找能处理的 Handler(也就是我们平常说的 Controller 控制器) ,并会将请求涉及到的拦截器和 Handler 一起封装。
(3) DispatcherServlet 调用 HandlerAdapter适配器执行 Handler 。
(4) Handler 完成对用户请求的处理后,会返回一个 ModelAndView 对象给DispatcherServlet,ModelAndView 顾名思义,包含了数据模型以及相应的视图的信息。Model 是返回的数据对象,View 是个逻辑上
的 View。
(5) ViewResolver 会根据逻辑 View
查找实际的 View
。
(6) DispaterServlet 把返回的
Model 传给 View(视图渲染)。
(7) 把 View 返回给请求者(浏览器) -
自动装配
链接描述 -
springboot 相较于spring的优点
-
Spring @transaction事务实现原理(相对于AOP更底层的原理)
链接描述
-
事务失效的情况
链接描述
java
15.Java对象和spring bean的区别
16.Java创建对象的流程
17.有遇到过内存溢出的情况吗:栈溢出、堆溢出。
18.堆溢出的原因,什么时候进行gc
19.Arraylist get put
1. Java和python的区别
2. Java代码一次编译,可以随时运行的原理
3. Int和Integer的区别
(1)数据类型:int 是一种基本数据类型(primitive data type)Integer 是一个类,通常被称为包装类(wrapper class)。
(2)可空性:int 是一个原始数据类型,不能表示为 null,即它不具备可空性。如果不初始化,它将有一个默认值,如 0。Integer 是一个对象,可以具备可空性。它可以被设置为 null,表示没有值。
(3)自动装箱和拆箱:在Java等支持自动装箱和拆箱的语言中,可以将 int 自动转换为 Integer 和反之,这种过程称为自动装箱(autoboxing)和自动拆箱(unboxing)。
(4)可空性、泛型、集合等都需要Integer
4. Arraylist和linkedlist区别
5. 线程池设计
根据题目要求:1s内可以执行两个任务,所以核心线程数设置为100,最大线程数可以设置为200
4. Hashmap扩容
网络
8.Tcp和udp区别
9.Udp场景
10.聊天框使用那种协议,具体到应用层
(1)WebSocket:WebSocket 是一种全双工通信协议,通常用于实时聊天应用程序。它建立在 TCP 协议之上,允许客户端和服务器之间双向通信,
从而实现实时消息传输。WebSocket 在现代Web应用中广泛使用,因为它提供了低延迟和高效的实时通信。
(2)HTTP(Hypertext Transfer Protocol):虽然 HTTP 主要用于 Web 页面的请求和响应,但也可以用于长轮询(long polling)
和 Server-Sent Events(SSE)等技术来实现实时消息传输。然而,相对于WebSocket,HTTP在实时聊天中的性能和效率通常较低。
13.学习技术的途径
数据结构
-
栈和队列应用场景
-
字典树
对于字典树(Trie树)的节点,一般情况下是不直接存放字符码的。通常,在字典树的节点中,会存储一个指向子节点的指针数组或哈希表,每个指针指向一个子节点。这样可以根据字符串的字符在字母表中的位置来索引对应的子节点。
设计模式
- 单例模式
反序列化破坏单例的原因及解决方案
HR
7.职业规划
手撕
20.合并两个有序数组
4.算法题:java实现输出字符串中第一个出现不重复的字符详解
没有通过全部测试用例(答案:队列+map)
18.k个一组反转链表