秋招面经记录

MySQL

  1. SQL优化

     1、explain输出执行计划:能够简单分析sql的执行情况,是否走索引等。type列,连接类型。一个好的SQL语句至少要达到range级别。
     2、in 和 not in 要慎用:MySQL对于IN做了相应的优化,即将IN中的常量全部存储在一个数组里面,而且这个数组是排好序的。
     但是如果数值较多,产生的消耗也是比较大的。对于连续的数值,能用 between 就不要用 in 了。
     3、少用select *
     4、善用limit 1:这是为了使explain中type列达到const类型。
     5、order by字段建索引:考虑在 where 及 order by涉及的列上建立索引,如果排序字段没有用到索引,就尽量少排序可以在程序中排序。
     6、count(*)推荐使用:按照效率排序的话,count(字段)<count(主键 id)<count(1)≈count(*),推荐使用count(*)
     7、设计表字段时尽量避免null值出现,null值很难查询优化且占用额外的索引空间,推荐默认数字0代替null。
     8、应尽量避免在 where !=或<>:子句中使用!=或<>操作符,会放弃使用索引而进行全表扫描。
     9、应尽量避免在 where 子句中使用 or:可能会索引失效,很多时候使用union all的方式来代替“or”会得到更好的效果。
     10、尽量用union all代替union:差异主要是前者需要将结果集合并后再进行唯一性过滤操作,这就会涉及到排序,增加CPU运算。
     11、应尽量避免在where子句中对字段进行函数操作
     13、避免%xxx式查询
     14、选择重复值较低的字段建索引
     15、高效的分页:limit的优化
     16、关于索引本身:使用多列索引时主意顺序和查询条件保持一致,同时删除不必要的单列索引。
    
  2. Mysql中有1000万条数据,每次查询10条,该如何优化(答:Limit子查询优化)
    在这里插入图片描述在这里插入图片描述
    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 可能会利用索引的有序性来直接定位和返回指定范围内的数据,而无需扫描和跳过大量的记录。

  3. 有了解过mysql索引吗

  4. 项目中使用到索引的情况(答:覆盖索引,避免回表)

  5. B+树和b树区别

  6. 内连接和外连接区别
    在这里插入图片描述

  7. 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适用于对事务支持要求不高、读操作较多、并发性能要求不高的应用程序。根据实际需求选择合适的存储引擎是很重要的。
    
  8. 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:查询可以被优化为常量查询。
    
  9. B+树结点区别

  10. 数据库中有某个二级索引name,有若干行数据为null,现在使用name is null统计null的行数,是否会走索引。

     例如:select * from table where name is null
     非聚簇索引是通过B+树的方式进行存储的,null值作为最小数看待,全部放在树的最左边,形成链表,
     如果获取is null的数据,可以从最左开始 直到找到记录不是null结束。 
     决定is null或者is not null走不走索引取决于执行成本,是否走索引取决于优化器。
     通过非聚簇索引查询需要回表才能获得记录数据(覆盖索引除外),那么在这过程中优化器发现回表次数太多,
     执行成本已经超过全表扫描.例如:几乎所有数据都命中,都需要回表.这个时候,优化器会放弃索引,走效率更高全表扫描 
    
  11. 索引删除和添加是否会改变整个索引结构
    在这里插入图片描述

  12. 索引优化
    在这里插入图片描述

  13. 索引失效场景
    链接描述

  14. 死锁情况及如何解决
    死锁及解决方案

  15. 什么场景不适合使用innodb存储引擎
    变相在问什么时候适合使用MyISAM
    使用MySQL的MyISAM引擎相较于InnoDB引擎更好的情况主要包括以下几个方面:

    非事务性应用:如果你的应用程序不需要ACID(原子性、一致性、隔离性、持久性)特性或者具备较低的并发需求,可以考虑使用MyISAM引擎。
    读密集型应用:MyISAM引擎在处理大量读取操作时速度较快。相比之下,InnoDB可能会涉及各种锁的操作。
    需要全文搜索功能:如果需要进行全文搜索,则可以考虑使用MySQL自带的MyISAM存储引擎。MyISAM在全文搜索方面比InnoDB更为高效。
    

Redis

  1. Redis中bitmap的原理,如何做签到统计

     Bitmap,即位图,是一串连续的二进制数组(0和1),可以通过偏移量(offset)定位元素。BitMap通过最小的单位bit来进行0|1的设置,
     表示某个元素的值或者状态,时间复杂度为O(1)。Bitmap 本身是用 String 类型作为底层数据结构实现的一种统计二值状态的数据类型。
     String 类型是会保存为二进制的字节数组,所以,Redis 就把字节数组的每个 bit 位利用起来,用来表示一个元素的二值状态,
     你可以把 Bitmap 看作是一个 bit 数组。由于 bit 是计算机中最小的单位,使用它进行储存将非常节省空间,
     特别适合一些数据量大且使用二值统计的场景。
    
  2. redis持久化方式,区别

  3. redis的使用场景
    在这里插入图片描述

  4. 为什么使用redis作为缓存
    在这里插入图片描述

  5. 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使用自己的高效二进制协议,该协议具有较低的开销,可以在网络上传输更少的数据,从而提高了网络性能。
    
  6. IO多路复用
    在这里插入图片描述
    在这里插入图片描述
    IO多路复用-select方式:
    在这里插入图片描述
    内核数据准备好或者超时后会将数据拷贝到用户空间,然后用户空间遍历fd_set,找到就绪的fd,读取其中的数据
    在这里插入图片描述
    IO多路复用-poll方式:

    在这里插入图片描述
    IO多路复用-epoll方式:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    信号趋动IO
    在这里插入图片描述

  7. Redis中的IO(网络模型IO多路复用)
    前提:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    以一个基于epoll的web服务为例理解redis中网络模型对应的api

    在这里插入图片描述

    在这里插入图片描述

  8. redis mysql区别(从处理业务的角度回答)
    在这里插入图片描述

  9. Redis setnx分布式锁

     (1)获取锁:
     	key:自定义前缀+参数name,name为模块名+用户ID,可以实现对同一用户加锁
     	value: UUID+当前线程ID
     	设置过期时间
     (2)释放锁:
     	lua脚本:根据value判断是否是自己的锁,然后del key删除锁
     (3)自动续期:
     	在获取到锁的同时,启动一个定时任务或者后台线程,定时使用 Lua 脚本更新锁的过期时间。
     	Lua 脚本可以通过比较当前锁的值是否与之前设置的唯一标识符相同来确保只有持有锁的客户端可以进行续期操作。
     	续期时间应该小于锁的过期时间,以确保在客户端因故障或其他原因导致无法续期时,锁能够最终过期释放。
     (4)注意
     	锁的过期时间需要根据业务操作的预估时间和系统负载来设置,避免锁的过期时间过短或过长。
     	续期操作需要保证原子性,可以使用 Lua 脚本将获取锁和续期操作原子化执行。
     	锁的持有时间应该有一个上限,避免因为某个客户端发生故障而导致锁无法被释放。
     	客户端在获取锁前,可以设定一个尝试获取锁的超时时间,避免长时间的等待。
    
  10. 守护线程可以续期(如何实现),当过期时间为10,守护线程在9时进行续期,但是如果此时发生gc,出现stw,此时续期会失败。该如何解决。
    在这里插入图片描述

  11. Redission原理
    链接
    redission中锁的结构为一个hash,name为锁名,hash中的第一个key为uuid+线程id,对应value为加锁的次数(可重入锁)。

    ​以上使用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 的生存时间。
    
  12. 缓存雪崩、击穿、穿透
    链接描述

  13. Redis有那些内存淘汰策略,项目中是设置的那种
    内存淘汰

  14. HyperLogLog 如何实现网站的 UV 统计
    在这里插入图片描述
    PFADD: 添加元素(可同时添加多个)
    PFCOUNT: 统计元素个数(有误差)
    PFMERGE:合并多个key
    HyperLogLog不会存储重复的数据,所以可以进行UV统计
    总结:hyperloglog占用内存不会高于16kb,误差率也可以实现,可以满足对uv统计的需求。
    HyperLogLog 是用来做基数统计的算法。
    因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。
    什么是基数?
    比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5。 基数估计就是在误差可接受的范围内,快速计算基数。

  15. Redis的数据结构
    redis数据结构

  16. Redis中的事务命令
    Redis的事务机制是通过MULTI和EXEC命令实现的。在Redis的事务中,如果其中的某个命令执行错误,不会导致整个事务回滚,而是会继续执行后续的命令。当执行出错的命令时,Redis会将错误信息记录下来,但并不会中断事务。

    当事务执行完毕后,通过EXEC命令触发执行,Redis会按照事务中命令的顺序逐个执行它们。如果某个命令执行失败,Redis会将错误信息返回给客户端,并继续执行后续的命令。所以redis中多个命令中的原子性是假原子性

  17. lua脚本底层原理,如何保证原子性
    在Redis中,Lua脚本能够保证原子性的主要原因还是Redis采用了单线程执行模型。也就是说,当Redis执行Lua脚本时,Redis会把Lua脚本作为一个整体并把它当作一个任务加入到一个队列中,然后单线程按照队列的顺序依次执行这些任务,在执行过程中Lua脚本是不会被其他命令或请求打断,因此可以保证每个任务的执行都是原子性的。
    但是,redis只能实现假原子性,应为如果lua脚本中某个命令执行错误并不会回滚。redis认为如果命令执行错误是用户在编写命令是出错导致的。

项目

  1. 实习遇到的问题
  2. 两个项目上线了吗,单独完成还是合作
  3. 项目redis各种数据结构使用场景
  4. 项目中三方库替换后,如果后续又超期再次替换是否还会很麻烦:
    不会:本次替换已经将所有涉及api封装到工具类,下次直接修改该脚本
  5. 项目中用到了es,了解的底层原理吗
    只了解倒排索引
  6. 介绍一下实习项目,以及负责的excel上传下载接口的逻辑
  7. 你们项目中如果程序出错了怎么处理的
    用户导致的:抛出错误提示。
    系统内部:回滚
  8. 项目中的工作量(安全连线、三方库替换)
  9. 三方库替换将设计函数封装为utils,java中那些场景需要封装工具类
  10. 例如:string相关的操作需要封装工具类吗
  11. 封装工具类仅仅是为了维护和修改方便吗,有其他原因吗
  12. 项目遇到的困难
  13. Python、Java的掌握程度、学习时间及方式

分布式

  1. 异步业务:类似于购物下单,异步处理如果失败了怎么办
    重试+死信队列(你这个思路业务太复杂,不好实现)
    (1)业务逻辑有问题,无法执行成功
    (2)依赖的第三方服务有问题:例如mysql宕机了,暂时无法执行
    在这里插入图片描述

  2. 访问量过高,系统如何设计(高可用)
    高可用设计
    集群、限流(令牌桶)、熔断、降级、超时重试、异步调用
    在这里插入图片描述
    在这里插入图片描述

  3. 分布式事务理论
    CAP

    CAP 也就是 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.最终一致性:弱一致性的升级版。,系统会保证在一定时间内达到数据一致的状态,
    业界比较推崇是最终一致性级别,但是某些对数据一致要求十分严格的场景比如银行转账还是要保证强一致性。
    
  4. 分布式事务实现方式
    链接

  5. 分布式事务场景:向mysql和mq写入数据
    本地消息表实现:
    (1)使用消息表记录需要写入mq的消息记录,信息写入mysql。(同一个事务执行)
    (2)使用后台程序源源不断的扫描消息表中的记录并写入mq,若写入成功则删除对应消息

  6. 1000万条数据,找出出现次数最多的前100条数据(内存有限)

    (1)使用hash算法将所有数据映射到若干个小文件,这样相同的数据会在同一个文件
    (2)针对每个文件使用hashmap统计数量,然后使用堆排序找出前100的数据
    (3)最后统计所有文件中出现次数前100的数据

  7. 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^1
     F(1)= 2 * 10^0
     最终将f(0) + ... + f(n)求和即可。
     以0-100为例,求包含2或7数字的个数
     F(3) = 0 * 10 ^2
     F(2) = 2 * 10 ^ 1
     F(3) = 0 * 10 ^ 0
     最终结果为20个数字即(2,12,...92和20,21,...29)
    
  8. 20个服务节点,结点有输入输出均为list,某个节点的输出list中的某个值可能是其他节点的输入,如何能快速执行完20个结点。
    拓扑图。如何构建拓扑图是一个问题
    面试官:可以用空间换时间,一直在问空时复杂度

  9. 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消息已经被成功处理。
     	用例:用于确保消息不会被重复处理。
     	实现:通常有手动确认和自动确认两种方式,消费者发送确认消息以确认消息处理完成。
    
  10. Mq的使用场景
    异步任务,eg:下单、分布式事务

  11. 下单的完整流程(包含付款,判断库存、异步等操作的前后顺序

     (1)下单请求:
     	客户端发起下单请求,将订单信息发送到服务器。
     (2)库存检查:
     	服务器接收到下单请求后,首先进行库存检查,确保所需商品的库存足够。
     	并把用户欲购买的商品锁定,设定一个锁定时间,锁定时间内没有付款则释放库存。
     	如果库存不足,服务器发送一条库存不足的通知给客户端,客户端可以选择修改订单或者取消订单。
     (3)付款处理:
     	如果库存检查通过,服务器将订单信息发送给付款系统进行付款处理。这可以是同步或异步的过程,具体取决于付款系统的设计和性能要求。
     	付款系统处理付款请求,返回付款结果。
     (4)订单创建:
     	服务器接收到付款结果后,将订单信息保存到数据库中,创建订单。
     	订单创建成功后,服务器生成订单号等相关信息。
     (5)通知客户下单成功
     (6)异步处理其他操作:
     	除了上述基本流程,可能还有其他后续操作,如发货、通知物流、发送发票等。这些操作通常是异步的,可以通过消息队列来处理。
     	服务器将这些操作封装成消息,发送到相应的消息队列,等待后续处理。
     (7)后续处理:
     	后续处理可以由独立的服务或者工作者来完成。这些工作者从消息队列中接收消息,执行相应的操作。
     	例如,发货工作者会从消息队列中接收到需要发货的订单信息,然后进行发货操作。
     	这些后续操作可以并行执行,提高了系统的性能和可伸缩性。
     (8)通知客户端:
     	在完成后续操作后,服务器可以发送通知给客户端,告知订单的状态更新,如发货通知、物流跟踪信息等。
    
  12. 大数据了解吗?Eg:分库分表
    链接描述

  13. redis mysql数据一致性
    先删缓存再更新数据库数据不一致问题解决方案:延迟双删(小林coding)
    先更新数据库再删缓存数据不一致问题解决方案:消息队列 or Canal
    Canal保证一致性详解

框架

  1. Spring 和springboot区别,springboot特点
    在这里插入图片描述

  2. ioc aop(***)
    guide链接

  3. 循环依赖问题

    循环依赖就是A依赖B,而B又依赖A,Spring是怎么做的?

     其实也是在Spring的生命周期里面,从Spring的生命周期可以知道,对象属性的注入在对象实例化之后的。它的大致过程是这样的:
     (1)首先A对象实例化,然后对属性进行注入,发现依赖B对象;
     (2)此时B对象还没有创建出来,所有转头去实例化B对象
     (3)B对象实例化之后,发现需要依赖A对象,那A对象已经实例化了,所以B对象最终能完成创建
     (4)B对象返回到A对象的属性注入的方法上,A对象最终完成创建
     原理:三级的缓存,三级缓存其实就是三个Map
     singletonObjects(一级,日常实际获取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的动态代理对象每次都是由工厂创建的新对象,地址值不一样。
      所以需要一个二级缓存来存,保证代理对象的一致性,如果二级里面有就不用查三级了。
    
  1. 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 返回给请求者(浏览器)

  2. 自动装配
    链接描述

  3. springboot 相较于spring的优点

    在这里插入图片描述

  4. spring中@Transactional注解面试问到的问题

    • Spring @transaction事务实现原理(相对于AOP更底层的原理)

    • 事务失效的情况

    • spring事务修饰代码块

        Spring事务注解只能加在类或方法上,
        如果想要修饰代码块,避免长事务,需要使用编程式事务,例如TransactionTemplate,搭配try finaly使用 
      
    • @Transactional回滚的是怎么实现的?是在数据库还是在程序里进行回滚的?

        这两个应该说是一个问题,在系统调用声明@Transactional的目标方法的时候,
        spring会开启一个拦截器TransactionInterceptor 来进行拦截,
        会在目标方法开始执行之前创建并加入事务,然后再执行目标方法的逻辑,
        当逻辑里面抛异常的时候统一进行回滚事务,如果一切正常的话就提交事务。
      
    • 嵌套方法调用时出现的事务失效情况及如何保证事务生效

        1.问题描述
        使用了@Transactional的方法,对同一个类里面的方法调用, @Transactional无效。
        比如有一个类Test,它的一个方法A,A调用Test本类的方法B(不管B是否public还是private),
        但A没有声明注解事务,而B有。则外部调用A之后,B的事务是不会起作用的。
        2.原因
        在Spring中,@Transactional注解是通过AOP(面向切面编程)实现的。
        默认情况下,Spring事务只在通过代理对象调用被@Transactional注解修饰的公共方法时生效。
        当你在同一个类的方法A中调用了该类的方法B时,无论B方法是否被@Transactional注解修饰,事务都不会生效。
        这是因为Spring的事务切面是基于代理对象的,当你调用同一个类的另一个方法时,实际上是通过this指针直接调用的,
        而不是通过代理对象,因此事务切面也就不会被触发。
        3.解决方案
        (1)将方法B抽取到另外一个Bean中:将方法B抽取到另外一个Bean中,并通过依赖注入的方式在方法A中使用该Bean,
        并在方法B上添加@Transactional注解。这样在方法A中调用方法B时,会通过代理对象调用,事务也会生效。
        (2)强制方法B通过代理对象调用:可以使用AopContext.currentProxy()方法获取当前方法的代理对象,
        在方法A中显式地通过代理对象调用方法B。这样事务切面会被触发,事务也会生效。
      
  5. 为什么不推荐使用Autowire注解

     首先,让我们看一下@Autowired注解。它的作用是自动注入依赖关系,避免手动编写冗长的构造函数或setter方法。
     这种方式很方便,但是也有一些缺点。
     首先,它是一个魔法注解。在代码中,你可能看不到明确的依赖关系,而这种魔法注解的使用会增加代码的复杂性。
     其次,它是一个运行时注解,会影响应用程序的性能。
    

    在Spring的文档中,我们可以看到以下建议:
    尽管@Autowired注解是方便的,我们建议使用构造函数注入来明确表达依赖关系,并避免使用自动注入
    为什么呢?

     因为构造函数注入可以提供明确的依赖关系,这样代码更容易理解和维护。
     并且,这种方式还能保证依赖关系的正确性,因为只有在所有的依赖项都可用的情况下才能创建对象。
     而对于@Autowired注解,如果依赖项不可用,则会在运行时抛出异常。
    

    另一个问题

     IDEA不推荐使用@Autowired注解的原因是它可能会导致代码的不可读性和不稳定性。
     在IDEA中,当你使用@Autowired注解时,它不会检查依赖关系是否存在,这可能会导致错误的行为。
     而使用构造函数注入,则可以在编译时检查依赖关系的正确性,并提供更好的代码提示和自动补全功能。
    

    替换方案
    (1)使用@Resource注解

     @Resource是JSR-250规范中定义的注解,它可以用来注入一个依赖的bean,类似于@Autowired注解。
     但是,@Resource注解具有更严格的匹配规则,它可以根据bean的名称、类型、甚至是注入位置进行匹配。
     这使得@Resource注解更加灵活、精确。
    

    (2)使用构造函数注入

     除了使用注解,还可以使用构造函数注入来注入依赖的bean。这种方式通常被认为是更加安全和可读性更高的注入方式。
     通过构造函数注入,我们可以保证所有必须的依赖都被正确初始化,避免了空指针异常等问题。
     此外,通过构造函数注入,我们还可以显式地定义bean之间的依赖关系,使得代码更加清晰易懂。
    

    使用构造函数注入的方式如下所示:

    @Service
    public class MyService{
    	private MyDao myDao;
    	public MyService(MyDao myDao){
    		this.myDao = myDao;
    	}
    }
    

    在这个例子中,MyService类中的myDao属性将会被注入一个类型为MyDao的bean。需要注意的是,这里我们没有使用任任的注解来进行注入,而是通过构造函数的参数来实现注入。这样做可以保证MyService类中的myDao属性在对象创建时被正确初始化。

  6. Spring事务隔离级别
    读未提交、读已提交、可重复读、串行化

java

  1. Java对象和spring bean的区别

  2. Java创建对象的流程

  3. 有遇到过内存溢出的情况吗:栈溢出、堆溢出。

  4. 堆溢出的原因,什么时候进行gc

  5. Arraylist get put

  6. Java和python的区别

  7. Java代码一次编译,可以随时运行的原理

  8. Int和Integer的区别

    (1)数据类型:
    int 是一种基本数据类型(primitive data type)
    Integer 是一个类,通常被称为包装类(wrapper class)。
    (2)可空性:
    int 是一个原始数据类型,不能表示为 null,即它不具备可空性。如果不初始化,它将有一个默认值,如 0。
    Integer 是一个对象,可以具备可空性。它可以被设置为 null,表示没有值。
    (3)自动装箱和拆箱:
    在Java等支持自动装箱和拆箱的语言中,可以将 int 自动转换为 Integer 和反之,
    这种过程称为自动装箱(autoboxing)和自动拆箱(unboxing)。
    (4)可空性、泛型、集合等都需要Integer

  9. Arraylist和linkedlist区别

  10. 线程池设计
    在这里插入图片描述
    根据题目要求:1s内可以执行两个任务,所以核心线程数设置为100,最大线程数可以设置为200

  11. Hashmap扩容
    链接

  12. 抽象类和接口
    (1)抽象类

    抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。
    由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。也是因为这个原因,通常在设计阶段决定要不要设计抽象类。
    

    抽象方法

    如果你想设计这样一个类,该类包含一个特别的成员方法,该方法的具体实现由它的子类确定,那么你可以在父类中声明该方法为抽象方法。
    Abstract 关键字同样可以用来声明抽象方法,抽象方法只包含一个方法名,而没有方法体。
    抽象方法没有定义,方法名后面直接跟一个分号,而不是花括号。
    声明抽象方法会造成以下两个结果:
    如果一个类包含抽象方法,那么该类必须是抽象类。
    任何子类必须重写父类的抽象方法,或者声明自身为抽象类。
    
    public abstract class Employee
    {
       private String name;
       private String address;
       private int number;
       
       public abstract double computePay();
       
       //其余代码
    }
    

    总结

    1. 抽象类不能被实例化(初学者很容易犯的错),如果被实例化,就会报错,编译无法通过。只有抽象类的非抽象子类可以创建对象。
    
    2. 抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
    
    3. 抽象类中的抽象方法只是声明,不包含方法体,就是不给出方法的具体实现也就是方法的具体功能。
    
    4. 构造方法,类方法(用 static 修饰的方法)不能声明为抽象方法。
    
    5. 抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类。
    

    (2)接口

    接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错)。
    接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量(并且只能是 public,用 private 修饰会报编译错误)。
    接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法。
    接口是隐式抽象的,当声明一个接口的时候,不必使用abstract关键字。
    接口中每一个方法也是隐式抽象的,声明时同样不需要abstract关键字。
    接口中的方法都是公有的。
    

    接口的继承

    一个接口能继承另一个接口,和类之间的继承方式比较相似。接口的继承使用extends关键字,子接口继承父接口的方法。
    下面的Sports接口被Hockey和Football接口继承:

    接口的多继承

    在Java中,类的多继承是不合法,但接口允许多继承。在接口的多继承中extends关键字只需要使用一次,在其后跟着继承接口。 如下所示:public interface Hockey extends Sports, Event

    标记接口
    最常用的继承接口是没有包含任何方法的接口。标记接口是没有任何方法和属性的接口.它仅仅表明它的类属于一个特定的类型,供其他代码来测试允许做一些事情。eg: ArrayList相对于LinkedList实现了RandomAccess 接口,表示其可以随机访问。

    (3)抽象类和接口的区别

    1. 抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行。
    2. 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的。
    3. 接口中不能含有静态代码块以及静态方法(用 static 修饰的方法),而抽象类是可以有静态代码块和静态方法。
    4. 一个类只能继承一个抽象类,而一个类却可以实现多个接口。
    
  13. 高并发
    现有三个线程t1,t2,t3,t3需要等待t1,t2执行完后执行,可以使用completableFuture实现。
    completableFuture使用

  14. stream流map使用
    map的功能
    中间操作 map 会将元素根据指定的 Function 接口来依次将元素转成另外的对象。

    区分问题:集合转为map,使用stream流的收集器
    使用 java.util.stream.Collectors 类的 toMap() 方法转为 Map 集合
    注意:当 value 为 null 时会抛 NPE 异常

    class Person {
        private String name;
        private String phoneNumber;
         // getters and setters
    }
    
    List<Person> bookList = new ArrayList<>();
    bookList.add(new Person("jack","18163138123"));
    bookList.add(new Person("martin",null));
    // 空指针异常
    bookList.stream().collect(Collectors.toMap(Person::getName, Person::getPhoneNumber));
    

    原因:toMap() 方法 ,其内部调用了 Map 接口的 merge() 方法,该方法方法会先调用 Objects.requireNonNull() 方法判断 value 是否为空。

  15. 线程池参数的设置,cpu和io密集型
    链接

  16. IO涉及的0拷贝问题
    做服务端开发的小伙伴,文件下载功能应该实现过不少了吧。如果你实现的是一个 Web 程序,前端请求过来,服务端的任务就是: 将服务端主机磁盘中的文件从已连接的 socket 发出去。关键实现代码如下:

    while((n = read(diskfd, buf, BUF_SIZE)) > 0)
    	write(sockfd, buf, n);
    
    传统的 IO 流程,包括 read 和 write 的过程
    read :把数据从磁盘读取到内核缓冲区,再拷贝到用户缓冲区
    write : 先把数据写入到 socket 缓冲区,最后写入网卡设备
    

    在这里插入图片描述

    (1)用户应用进程调用 read 函数,向操作系统发起10 用,上下文从用户态转为内核态 (切换 1)。
    (2)DMA 控制器把数据从磁盘中,读取到内核缓冲区。
    (3)CPU 把内核缓冲区数据,拷贝到用户应用缓冲区,上下文从内核态转为用户态 切换 2),read 函数返回
    (4)用户应用进程通过 write 函数,发起 0 调用,上下文从用户态转为内核态 (切换 3)。
    (4)CPU 将应用缓冲区中的数据,拷贝到 socket 缓冲区
    (5)DMA 控制器把数据从 socket 缓冲区,拷贝到网卡设备,上下文从内核态切换回用户态(切换 4)write 函数返回
    

    从流程图可以看出,传统 0 的读写流程,包括了 4 次上下文切换(4 次用户态和内核态的切换),4 次数据拷贝 (两次 CPU 拷贝以及两次的 DMA 拷贝),什么是 DMA 拷贝呢? 我们一起来回顾下,零拷贝涉及的操作系统知识点哈。

    零拷贝实现的几种方式
    零拷贝并不是没有拷贝数据,而是减少用户态/内核态的切换次数以及 CPU 拷贝的次数。分别是
    (1)mmap+write
    (2)sendfile

    在这里插入图片描述
    在这里插入图片描述
    可以发现, mmap+write 实现的零拷贝,I/0 发生了4次用户空间与内核空间的上下文切换,以及 3 次数据拷贝。其中 3 次数据拷贝中,包括了2 次 DMA 拷贝和 1次 CPU 拷贝
    mmap 是将读缓冲区的地址和用户缓冲区的地址进行映射,内核缓冲区和应用缓冲区共享,所以节省了已次 CPU 拷贝并且用户进程内存是虚拟的,只是映射到内核的读缓冲区,可以节省一半的内存空间。mmap 用了虚拟内存,它将内核中的读缓冲区与用户空间的缓冲区进行映射,所有的 IO 都在内核中完成

    在这里插入图片描述
    sendfile 表示在两个文件描述符之间传输数据,它是在操作系统内核中操作的,避免了数据从内核缓冲区和用户缓冲区之间的拷贝操作,因此可以使用它来实现零拷贝。该方法减少的是用户态内核态切换的次数

    在这里插入图片描述
    可以发现,sendfile 实现的零拷贝,I/0 发生了2次用户空间与内核空间的上下文切换,以及 3 次数据拷贝。其中 3 次数据拷贝中,包括了2 次 DMA 拷贝和 1次 CPU 拷贝

    (1)java实现mmap:
    Java NIO 有一个 MappedByteBuffer 的类,可以用来实现内存映射。它的底层是调用了 Linux 内核的mmap的 APl。
    (2)Java NIO 对 sendfile 的支持:
    FileChannel的 transferTo()/transferFrom(),底层就是 sendfilel 系统调用函数。
    Netty 这个开源项目就用到它,平时面试的时候,回答面试官为什么这么快,就可以提到零拷贝 sendfile 这个点。
    

网络

  1. Tcp和udp区别

  2. Udp场景

  3. 聊天框使用那种协议,具体到应用层

    (1)WebSocket:WebSocket 是一种全双工通信协议,通常用于实时聊天应用程序。它建立在 TCP 协议之上,允许客户端和服务器之间双向通信,
    从而实现实时消息传输。WebSocket 在现代Web应用中广泛使用,因为它提供了低延迟和高效的实时通信。
    (2)HTTP(Hypertext Transfer Protocol):虽然 HTTP 主要用于 Web 页面的请求和响应,但也可以用于长轮询(long polling)
    和 Server-Sent Events(SSE)等技术来实现实时消息传输。然而,相对于WebSocket,HTTP在实时聊天中的性能和效率通常较低。

  4. 相关linux命令

     netstat -napt 查看TCP的连接状态
     route -n 查询当前系统路由表
     arp -a 查询ARP缓存内容
    
  5. Http
    状态码
    强制缓存和协商缓存

     强制缓存:Cache_Control;Expires
     协商缓存:If-Modified-Since与Last-Modified;If-None-Match与Etag
     协商缓存这两个字段都需要配合强制缓存中 Cache-Control 字段来使用,
     只有在未能命中强制缓存的时候,才能发起带有协商缓存字段的请求。
    

    http与https区别

  6. 学习技术的途径

数据结构

  1. 栈和队列应用场景
    在这里插入图片描述

  2. 字典树
    在这里插入图片描述
    在这里插入图片描述
    对于字典树(Trie树)的节点,一般情况下是不直接存放字符码的。通常,在字典树的节点中,会存储一个指向子节点的指针数组或哈希表,每个指针指向一个子节点。这样可以根据字符串的字符在字母表中的位置来索引对应的子节点。

设计模式

  1. 单例模式
    反序列化破坏单例的原因及解决方案

HR

  1. 职业规划
    以应聘金融公司为例
    • 在入职的半年或者一年内:完成学生到职场人身份的转变,学习公司内部的业务,尽快投入到公司的生产中,积极向前辈们交流学习使自己能够尽快的融入团队。
    • 在未来的两到三年内,学习一些金融相关的知识,尤其是信贷相关,因为应聘的公司以金融业务为主,并且部门是信贷方向。
    • 在后续的时间,关注一些新技术的出现,并利用空余的时间学习,因为将来在公司的开发中可能会使用到该技术,做到未雨绸缪。
    • 总之,我认为人是要不断学习的,不断的向周围的人学习,不多的学习新知识,这样不仅可以为公司做出更多的贡献,也可以提高自己的核心竞争力。

手撕

  1. 合并两个有序数组

  2. 算法题:java实现输出字符串中第一个出现不重复的字符详解
    没有通过全部测试用例(答案:队列+map)

  3. k个一组反转链表

Linux

  1. linux查询日志文档中“abc”所在位置数据信息,该使用什么命令
    在这里插入图片描述
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值