• Java八股文总结(二)


    Java八股文总结(续)

    接上篇笔记:https://blog.csdn.net/weixin_44780078/article/details/130192373

    文章目录

    六、MySql 相关

    1. InnoDB 与 MyISAM 的区别?

    InnoDB 和 MyISAM 都是 MySql 的存储引擎

    InnoDBMyISAM
    事务支持不支持
    主键一定要有不一定要有
    外键支持不支持
    聚簇索引是(数据文件和索引文件是绑定在一起的)不是(相反数据文件和索引文件是分开的)
    全文索引不支持(v5.7之后也支持)支持
    行数不存储存储
    行锁支持不支持

    InnoDB: InnoDB 在 mysql 3 版本就出现了,但是是在 5.5版本之后才成为 mysql 默认的存储引擎,InnoDB 最大的好处就是支持事务、支持外键和支持行级锁,它也是聚簇索引,但它不支持全文索引(5.7版本之后也支持)。
    MyISAM:是 mysql 5.5版本之前默认的存储引擎,不支持事务、不支持外键、不支持行级锁,也不是聚簇索引,因此也导致了在 5.5版本之后 mysql 把存储引擎改为了 InnoDB,它支持全文索引。


    2. 为什么 InnoDB 存储引擎表必须有主键,并且推荐使用整型的自增方式?

    对于主键:

    • 主键可以确保表中每一行数据的唯一性,确保数据完整性。没有主键的表可能会出现重复数据或数据无法明确定位的情况。
    • InnoDB 存储引擎采用 B+ 树作为索引结构,在数据访问和查询过程中,可以通过主键高效的定位数据。如果没有主键,InnoDB 会选择一个唯一非空索引作为隐藏主键,但这样可能导致索引性能下降。
    • 方便与其他表进行外键关联。

    对于整型的自增方式:

    • 整型数据类型在存储和比较上相对于其他数据类型占用空间小,更加高效,索引速度快。自增方式可以保证每次插入新记录时,主键值都是递增的,这样可以减少索引维护的开销。
    • 自增的主键具有连续性和可读性,方便人们查看和理解;

    3. explain 查看 sql 执行计划

    explain 中 type:all 表示全表通查。

    explain select * from fangyi_user.user
    
    • 1

    在这里插入图片描述


    explain 中 type:index 表示全表扫描索引文件(但是只遍历索引树)。

    explain select user_id from fangyi_user.user  // user_id是主键
    
    • 1

    在这里插入图片描述


    explain 中 type:range 表示检索给定范围的行,使用一个索引来选择行。(不需要优化了)

    explain select * from fangyi_user.user WHERE user_id > 1
    
    • 1

    在这里插入图片描述


    explain 中 type:const 表示最多有一个匹配行,读取1次,速度非常快。(不需要优化了)

    explain select * from fangyi_user.user WHERE user_id = 5661
    
    • 1

    在这里插入图片描述


    explain type 级别:system > const > eq_ref > ref > range > index > all.
    SQL 优化目标:至少要达到 range 级别,要求是 ref 级别,如果是 const 最好。


    4. 聚簇索引(也叫聚集)和非聚簇索引的区别

    简单的说聚簇索引就是索引和数据文件是放在一个位置的,非聚簇索引是把索引和数据文件分开存储。

    我们都知道 mysql 的底层采用的是 B+ 树作为索引结构,对于 mysql 的存储引擎 又分为 MyISAM 和 InnoDB,假如对同一张表添加索引:

    • 对于 MyISAM,新建一张表就会创建 .frm(表示表结构),.myd(表数据),.myi(表索引) 为后缀的三个文件,并且在 B+ 树的叶子节点中,Data 存储的是当前索引所对应的行数据在磁盘中的地址,拿到这个地址再通过内存去磁盘中进行 IO 读取,CPU 再去内存中加载获取数据。

    在这里插入图片描述

    • 对于 InnoDB,新建一张表就会创建 .frm(表示表结构),.idb(数据、索引数据)为后缀的两个文件,并且在 B+ 树的叶子结点中,Data 存储的是当前索引所对应的行数据的所有字段的数据值。
      在这里插入图片描述

    在这里插入图片描述

    因此不难得出 InnoDB 是采用聚簇索引,MyISAM 采用的是非聚簇索引。并且聚簇索引的效率更高,因为不需要再通过内存地址去进行二次查找。

    5. 对于数据表的普通字段建索引,是如何存储的?

    对于普通字段建立索引(非主键索引),mysql 也是采用 B+ 树进行存储,但是 B+ 树的叶子结点存储的是该表的主键,查出主键过后再通过主键索引进行二次查看,然后得出最终的数据(也就是二级索引)。


    6. 联合索引

    一般对于一张数据库表,不推荐给单个字段建立太多的索引,而是建立联合索引(给多个字段联合起来建立一个索引)

    在这里插入图片描述
    比如对于这个表,对 dept_name 和 dept_address 、num 三列建立联合索引:

    create index index_name_address_num on dept(dept_name, dept_address, num);
    
    • 1

    联合索引遵循一种最左前缀原则,顾名思义就是最左优先,以最左边的列为起点任何连续的索引都能匹配上。如果没有第一列作为查询条件的话,直接访问第二列,那第二列肯定是无序的,直接访问后面的列索引也不会生效。当创建 (a,b,c) 联合索引时,想要索引生效的话,只能使用 a、ab、ac 和 abc 四种组合!

    假如创建(a,b,c)联合索引,底层的 B+ 树是先按列 a 进行排序,排完过后再按照列 b,依次类推,直到排序所有列。如果列 a 存在相同的值,则再排序列 b。这样查询起来就很方便,因此必须要遵顼最左前缀原则。


    7. 索引种类

    在 mysql 中索引是在存储引擎层实现的,而不是在服务器层实现,所以不太存储引擎具有不同的索引类型和实现,常见的索引分类如下:

    • 按数据结构分类:有 B+ 树索引、哈希索引等。
    • 按物料存储分类:有聚簇索引和非聚簇索引。
    • 按字段特性分类:有主键索引(primary key)、唯一索引(unique)、普通索引(index)、全文索引(fulltext)。
    • 按字段个数分类:单列索引、联合索引。

    8. 二级索引

    在 mysql 中,如果是 InnoDB 存储引擎,主键索引的 B+ 树叶子结点上存储的是整行数据的值,而二级索引(比如单个列建立的索引、多个列建立的联合索引,所有非主键索引都可以称之为二级索引)的 B+ 树叶子结点上,存储的是该行数据的主键,也就是通过二级索引找到主键后,再通过主键去查询具体的值,这就是二级索引。这种进行二次操作的查询也称为回表。


    9. 覆盖索引

    在上面提到的二级索引会进行回表操作,那是因为要通过主键查其他的列数据,假如执行这条 sql:

    select id from user where age = 18;
    
    • 1

    只查询主键 id,那还会进行回表查询吗?答案是不会,因为通过 age 列的普通索引,已经查询出了主键,不必再通过主键去进行回表查询。这种就称为覆盖查询。


    10. 联合索引相对单列索引的优势是什么?

    • 减少开销:建立一个联合索引(a,b,c)实际只建立了一颗 B+ 树,但是查询(a)、(a,b)、(a,c)、(a,b,c) 四种情况都会走索引,而分别对a,b,c三个列单独建索引,会生成三颗 B+ 树。
    • 覆盖索引效率高:假如联合索引(a,b,c),查询的时候 select a,b,c from x where a = ? and b = ?;这样就可以不去回表查询,如果是单列索引的话会进行回表查询。

    11. 如果一个表没有主键索引,那还会创建 B+ 树吗?

    答案是会,InnoDB 是 mysql 中的一种存储引擎,它会为每个表创建一个主键索引。如果表没有明确的主键索引,InnoDB 会使用一个隐藏的、自动生成的主键(row_id)来创建索引,这个 row_id 使用的就是 B+ 树,因此,在 InnoDB 中,即使没有明确的主键索引,也会创建 B+ 树索引。


    12. 数据库的三范式是什么?

    • 第一范式:表的每一列都是原子的不可再分。
    • 第二范式:表中非主键列完全函数依赖于主键列,使得行可以唯一区分。
    • 第三范式:表中非主键列不能依赖于其他的非主键列,不能出现传递依赖。

    13. 数据库的事务?

    什么是事务?:多条sql语句,要么全部执行成功,要么全部执行失败。

    事务的四大特性:

    • 原子性:事务是一个单一的、不可分割的工作单位,事务中的操作要么全部成功,要么全部失败,如果事务过程中出现错误,所有已执行的操作都会回滚,回到事务开始前的状态。
    • 一致性:事务的执行使数据库从一个一致性状态转变为另一个一致性状态,比如张三向李四转账,不管成功与否,最终他们俩的钱总额应该是不变的,不能因为转账的成功或失败,两个人钱的总额就发生变化,转账前后,金额总和都应该保持一致。
    • 隔离性:多个事务同时执行时,各个事务之间应该相互隔离,互不干扰,哪怕是并发环境下,并发的事务之间也互不干扰。
    • 持久性:一旦事务提交成功,事务中的所有操作都必须持久化到数据库中。

    14. 事务的隔离级别?

    事务的隔离级别就是在并发环境下,事务之间的隔离程度,事务有四种隔离级别。

    • 1、读未提交:该隔离级别是最低的隔离级别,比如事务 A 和事务 B 同时进行更新数据,事务 A 在整个执行过程中,会将某个数值从 1 开始一直累加 10,然后才进行事务提交,此时,事务 B 能够看到这个数值在事务 A 操作过程中的所有中间值(如1变成2,2变成3等),而对这一系列的中间值的读取都是脏读,都没有意义,因为都是未提交事务的读取。
    • 2、读已提交:还是上述那个例子,事务 B 在读取的过程中只能读取事务 A 最终提交的数据 10,中间的所有过程都不能读取,假如这个时候还有另一个事务将数值改成了 20,那么事务 B 最终读取的也是 20。解决了脏读
    • 3、可重复读:上述的例子事务 B 在多次重复读取数值时,都应该是最终已提交的 20,不能出现不一致的情况,并且也只能在其他事务提交后才能进行读取。解决了脏读和不可重复读
    • 4、串行化:串行化是最高、最严格的隔离级别,它要求所有并发事务都依次串行执行,也就是事务一个个排队执行,这样也导致了性能的下降。解决了脏读、不可重复读、幻读

    mysql 默认的隔离级别是可重复读。


    15. 事务的隔离级别实际要解决的是哪些问题?

    事务的四种隔离级别主要解决三种实际问题:脏读、不可重复读、幻读。都是在并发事务下才会发生。

    • 1、脏读:脏读指事务读取到了其他事务未提交的数据,未提交的数据很有可能会进行回滚,也就是最终不会存到数据库中,读取也没有意义。
    • 2、不可重复读:在同一个事务内,事务对某数据进行读取,发现两次读取的结果不同,也就是在第一次读取后,又被其他的事务修改了数据,导致前后两次读取的数据不一致。但是如果加了行锁,没有了其他事务的干扰,最终的读取结果应该是一致的。
    • 3、幻读:幻读也发生在同一个事务内,和不可重复读有点类似,假如事务 A 读取了某个范围内的数据,得到 n 条数据,此时事务 B 修改并提交了这个范围内的数据 m 条,这时事务A再次来读取时,发现符合范围内的数据已经变成 n-m 了,和之前读取的内容出现了差别,这就是幻读。

    16. MySQL中varchar与char的区别以及varchar(50)、char(50)中的50代表的涵义?

    • varchar:表示可变长度的字符串,它可以存储任意长度的字符串,但是需要根据实际长度分配相应的存储空间。例如,如果存储一个长度为 10 的字符串,则只会分配10个字符的存储空间。因此,varchar 在存储空间上比较灵活。
    • char:表示定长字符串,不论实际存储的字符串的长度是多少,它只分配固定长度的存储空间。例如,如果定义了一个char(10) 的字段,无论实际存储的字符串长度是1 还是 10,在存储空间上都会分配 10 个字符的存储空间。
    • varchar(50) 中的 50:这个50表示一个 varchar 能够存储的最大字符数,如果存储的字符串长度超过 50,则会进行截断或报错。
    • char(50) 中的 50:这个 50 表示分配 50 的固定长度,如果实际存储的字符串不超过 50,则会以右对齐的方式用空格填补,但是不管存储多长的字符串(前提是要小于或等于50),都会分配 50 的长度。

    17. delete、truncate、drop三者的区别?

    • delete 属于 DML 语言,需要事务管理,commit 之后才能生效,它只能删除表数据。
    • drop 和 truncate 都属于 DDL语言,不需要事务,操作立刻生效,不可回滚,并且 drop 会删除表结构,私放内存空间。
    • 删除数据的速度:drop> truncate > delete,
    • 使用场合: 当不再需要该表时,用 drop; 当需要保留该表,但要清空所有记录时,用 truncate;当只是要删除部分数据时,用 delete。

    18. SQL优化手段有哪些?

    • 查询语句中不要使用select *;

      • 性能原因:当使用 select * 查询表中的所有列时,数据库需要查询和返回表中的每一列,包括不需要的列,这会导致性能的下降。对于 sql 语句而言,使用 select * sql 语句会先去查询表中的列,再根据列去查询数据,而直接查询具体的列可以避免这一操作。
      • 数据安全:使用 select * 可能会暴露数据库中的敏感数据,例如密码。
      • 冗余数据:使用 select * 可能会查询例如 text 这类不必要的数据,增加网络的开销。
    • 尽量减少子查询,使用关联查询(left join,right join,inner join)替代;

      • 性能优化:子查询往往需要执行多次,每次执行都会涉及到查询和计算,增加了数据可的开销。而关联查询可以将多个查询合并为一次查询,减少了数据库的访问次数和计算量,提高了查询的性能。
      • 代码简洁性:子查询的语法较复杂,嵌套层次多,可读性查,维护起来比较困难,而关联查询的语法相对简单,易于理解和维护,代码可读性更高。
      • 可扩展性:使用关联查询可以方便的添加更多的表和连接条件,灵活性更高。
      • 可优化性:关联查询可以索引等优化手段来提高查询性能,而子查询的性能优化相对较难。

    七、MySql 优化

    1. 优化方法

    在这里插入图片描述
    对于 mysql 的优化,从上图可以看出成本越低的反而效果越明显,因此作为程序员我们要重点掌握在 SQL 及索引层去优化。关于 SQL 优化方法,包括五点:

    • 创建索引减少扫描量;
    • 调整索引减少计算量;
    • 索引覆盖(减少不必要访问的列,避免回表查询);
    • 干预执行计划;
    • SQL 改写;

    2. explain 介绍

    在 mysql 5.7版本之前,explain 的使用有差别,此处只从 5.7 版本之后进行讲解。

    explain 语句返回列介绍:

    explain select * from dept where num > 1
    
    • 1

    在这里插入图片描述

    列名含义
    id每个 select 都有一个对应的 id 号,并且是从 1 开始自增的
    select_type查询语句执行的查询操作类型
    table表名
    partions表分区情况
    * type 查询所用的访问类型
    possible_keys可能用到的索引
    key实际查询到的索引
    key_len所用到的索引长度
    ref使用到索引时,与索引进行等值匹配的列或常量
    rows预计扫描的行数(索引行树或者表记录行数)
    filtered表示符合查询条件的数据百分比
    extrasql 执行的额外信息
    • 对于 id,举例关联查询和子查询。

      • id相同的,从上往下依次执行。
      • id不同的,从id大的向小的执行。
      • 如果两种都存在,先执行序号大的,在同级从上往下执行。
      • 如果显示 NULL,最后执行,表示结果集,并且不需要使用它来进行查询。

    • select_type:表示查询语句执行的查询操作类型。

      • simple :简单 select,不包括 union 与子查询。
      • primary :复杂查询中最外层查询,比如使用 union 或 union all 时,id 为 1的记录 select_type 通常是 primary。
      • subquery :指在 select 语句中出现的子查询语句,结果不依赖于外部查询(不在from语句中)。
      • dependent subquery :指在 select 语句中出现的查询语句,结果依赖于外部查询。
      • derived : 派生表,在from子句的查询语句,表示从外部数据源中推导出来的,而不是从 select 语句中的其他列选择出来的。
      • union:分union与union all两种,若第二个select出现在union之后,则被标记为union;如果union被from子句的子查询包含,那么第一个select会被标记为derived;union会针对相同的结果集进行去重,union all不会进行去重处理。
      • dependent union:当union作为子查询时,其中第一个union为dependent subquery,第二个union为dependent union。
      • union result:如果两个查询中有相同的列,则会对这些列进行重复删除,只保留一个表中的列。

    • table :查询所涉及的表名。如果有多个表,将显示多行记录。

    • partitions :表分区情况。

    • type :效率从高到低分别为:system > const > eq_ref > ref > fulltext > ref_or_null > range > index > ALL,一般来说保证range级别,最好能达到ref级别。

      • system:const类型的一种特殊场景,查询的表只有一行记录的情况,并且该表使用的存储引擎的统计数据是精确的.
      • const:基于主键或唯一索引查看一行,当 MySQL 对查询某部分进行优化,并转换为一个常量时,使用这些类型访问转换成常量查询,效率高。
      • eq_ref:基于主键或唯一索引连接两个表,对于每个索引键值,只有一条匹配记录,被驱动表的类型为 ‘eq_ref’。
      • ref:基于非唯一索引连接两个表或通过二级索引列与常量进行等值匹配,可能会存在多条匹配记录。
      • range:使用非唯一索引扫描部分索引,比如使用索引获取某些范围区间的记录。
      • index:扫描整个索引就能拿到结果,一般是二级索引,这种查询一般为使用覆盖索引(需优化,缩小数据范围)。
      • all:扫描整个表进行匹配,即扫描聚簇索引树(需优化,添加索引优化)。
      • NULL:MySQL 在优化过程中分解语句就已经可以获取到结果,执行时甚至不用访问表或索引。

    • possible_keys :表示在查询中可能使用到某个索引或多个索引;如果没有选择索引,显示 NULL。

    • key :表示在查询中实际使用的索引,如果没有使用索引,显示 NULL。

    • key_len :表示当优化器决定使用某个索引执行查询时,该索引记录的最大长度(主要使用在联合索引)

    • ref :表示将哪个字段或常量和 key 列所使用的字段进行比较。

    • rows :全表扫描时表示需要扫描表的行数估计值;索引扫描时表示扫描索引的行数估计值;值越小越好(不是结果集中的行数)。

    • filtered :表示符合查询条件的数据百分比。可以使用rows * filtered/100计算出与explain前一个表进行连接的行数。

    • Extra :SQL执行查询的一些额外信息。


    3. 索引失效

    1、在索引列上做计算、使用函数、类型转换,会导致索引失效。

    // 例:
    select * from dept where left(dept_name, 2) = '信息'; // 索引失效
    select * from dept where dept_name like '信息%'; // sql改造,不失效
    
    • 1
    • 2
    • 3

    2、索引列如果使用了范围查询,范围查询之后的列索引会失效。

    // 索引正常
    explain select * from dept where dept_name = '人力资源部' and num = 3 and dept_address = 'xx大厦7楼'; 
    // dept_address 列索引失效
    explain select * from dept where dept_name = '人力资源部' and num > 3 and dept_address = 'xx大厦7楼';
    // num 和 dept_address 列索引失效
    explain select * from dept where dept_name > '人力资源部' and num = 3 and dept_address = 'xx大厦7楼';
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    3、查询语句中使用了 !=、is null、or 可能不会走索引。

    4、对于 type 为 all,possible_keys 存在索引的,可以使用 force index (索引名) 强制使用索引。

    // 例:
    select * from person force index(index_name_age_sex)  where name != '李四' and age = 33 and sex = '男';
    
    • 1
    • 2

    5、Like 模糊查询尽量写右模糊。
    • 左模糊:比较了9000多行,type 为 index。
    explain select * from person  where name like '%四';
    
    • 1

    在这里插入图片描述

    • 右模糊:只比较了 1 行,type 为 range。效率的提升很明显。
    explain select * from person  where name like '李%';
    
    • 1

    在这里插入图片描述
    假如还是需要使用左模糊,可以使用覆盖索引提升效率。

    explain select name, age, sex(建立了索引的列) from person where name like ‘%四’;


    6、字符串不加单引号索引失效,可能还会导致全表扫描,因此一定要加引号。

    4. trace 跟踪工具

    // 开启trace
    set optimizer_trace = "enabled=on", end_markers_in_JSON = on;
    
    • 1
    • 2

    开启后再次查看 sql 的 trace 跟踪。同时运行两条 sql。

    在这里插入图片描述

    在这里插入图片描述
    可以看到使用了 select * 后mysql底层会先把 * 转换成具体的列,然后对全部通查,使用索引等进行评分,哪个更优就使用哪个。


    5. 避免使用 select *

    • 增加查询分析器的解析成本,上面已分析,会先把 * 转换成具体的列。
    • select * 不会走索引覆盖,会产生大量的回表查询。
    • 无用字段增加内存、网络的消耗,尤其是text类型的字段。

    6. 用连接查询代替子查询,子查询可能存在不走索引的情况。


    7. group by 的效率。

    group by 后面的列如果没有索引,那么group by 的效率会很低,


    8. 用 union all 代替 union。

    • union all:获取所有数据但是数据不去重,包含重复数据。
    • union:获取所有数据且数据去重,不包含重复数据。



    八、Spring

    1. 什么是 Spring ?

    Spring 是一个轻量的、开源的 java 框架,用于搭建企业级应用程序,Spring 框架的主要目标是提供一种简化的 java 开发方式,它提供了许多功能模块,比如依赖注入、面向切面编程、数据访问、web 开发等等。Spring 框架还有很多扩展的模块,比如 SpringBoot、SpringCloud等,用以支持更丰富的应用开发。


    2. 使用Spring框架的好处是什么 ?

    • 1、轻量、无侵入:

      Spring 是轻量的,基本的版本只有 2MB 左右。无侵入指的是可以在 Spring 中自由选择和组装 Spring 框架的各个功能模块,不会强制要求开发人员使用特定的类或接口。

    • 2、依赖注入(DI):

      Spring 框架提供了依赖注入的功能,可以通过配置文件或注解将对象之间的依赖关系交由 Spring 容器来管理。这就简化了对象之间的耦合度,方便开发人员更好的开发和维护。

    • 3、控制反转(IOC):

      在传统的程序设计当中,是通过 new 关键字来创建对象,也就是由程序去主动创建并控制这个依赖对象,而在 Spring 中有一个专门用来创建和管理对象的容器,这个容器就是 IoC 容器。对于某一个具体的对象而言,原本它要控制其他的依赖对象,但是现在所有的对象统一被 Spring 控制,所以这叫控制反转。

    • 4、面向切面的编程(AOP):

      面向切面编程是一种编程思想,主要在程序运行期间会使用到。光这样子理解可能有点抽象,举个不恰当的例子:洗澡。

    • 对于男生:脱衣服、洗头、洗脸、洗身体、唱歌、擦干身体、穿衣服。

    • 对于女生:脱衣服、洗头、洗脸、洗身体、擦干身体、护肤、整理头发、穿衣服。

      我们发现,不管是男生还是女生,最开始的脱衣服和最终的穿衣服都是必不可少的过程,但是这两个步骤在洗澡这个业务里面又不是最核心的,它只是一个关注点(最核心的应该是洗头、洗身体)。如果把洗澡的每个步骤都当成是一个对象的话,站在面向对象的角度,整个洗澡的过程就是一个纵向的过程,但是如果我们站在横向的角度,把脱衣服、穿衣服这两个耦合的步骤提取出来封装成一个可复用的模块,进行统一的管理,因此这种横向的思考方式就是面向切面编程。

    面向切面编程最典型的应用就是日志记录、全局异常处理等等。


    3. Spring MVC的执行流程?

    自我理解总结的 Spring MVC 执行步骤共有10个,具体如下:

    • 1、客户端发起 Http 请求,到达 DispatcherServlet 前端控制器。
    • 2、DispatcherServlet 接收到请求后调用 HandlerMapping(处理器映射器)。
    • 3、HandlerMapping 解析请求对应的 Handler,这个 Handler 也就是Controller控制器。
    • 4、然后 HandlerMapping 将请求映射到对应的 Controller 处理器上。
    • 5、Controller 层调用 Service 层处理业务。
    • 6、Service 层调用 Dao 层处理数据。
    • 7、Dao 层与数据库进行交互,查询对应的数据后最终返回到 Controller 层。
    • 8、Controller 接收到 Dao 层返回的数据过后再返回一个 ModelAndView 对象给处理器适配器,处理器适配器再将对象返回给前端控制器。
    • 9、前端控制器 DispatcherServlet 将接收到的 ModelAndView 对象传给试图解析器进行试图解析,解析完成过后再返回一个具体的试图给前端控制器。
    • 10、前端控制器 DispatcherServlet 再将具体的试图进行渲染,渲染完毕后响应给用户,展示在浏览器上。

    4. Spring 框架中用到了哪些设计模式?

    • 简单工厂模式:Spring 通过 BeanFactory 和 ApplicationContext 创建bean对象,这里用到了简单工厂设计模式。
    • 单例设计模式:Spring 中的创建的 bean 对象默认都是单例的。
    • 模板方法模式:Spring 中的 jdbcTemplate、redisTemplate 等以 Template 结尾的类,用到了模板模式。
    • 适配器模式:Spring MVC中用到了适配器模式适配具体的 Controller。
    • 代理模式:Spring AOP 功能的实现。

    等等还有其他,这里只举例部分。

    5. @Component和@Bean的区别是什么?

    • 1、作用对象不同,@Component注解作用于类,而@Bean注解作用于方法。
    • 2、@Bean注解比@Component注解的自定义性更强,而且很多地方只能通过@Bean注解来注册bean。比如当引用第三方库的类需要装配到Spring容器的时候,就只能通过@Bean注解来实现。

    6. 将一个类声明为 Spring 的 bean 的注解有哪些?

    我们一般使用 @Autowired 注解去自动装配 bean。但是也有其他的注解进行声明 bean:

    • 1、@Component 注解。通用的注解,可标注任意类为Spring组件,如果一个Bean不知道属于哪一个层,可以使用@Component 注解标注。
    • 2、@Repository / @Mapper 注解。对应持久层,即 Dao 层,主要用于数据库相关操作。
    • 3、@Service 注解。对应服务层,即 Service 层,主要涉及一些复杂的逻辑,需要用到Dao层(注入)。
    • 4、@Controller 注解。对应Spring MVC的控制层,即 Controller 层,主要用于接受用户请求并调用 Service 层的方法返回数据给前端页面。



    九、SpringBoot

    1. 什么是Spring Boot?

    • Spring Boot是一个开源的 Java 框架,用于创建独立的、基于 Spring 的、但是比 Spring 更加简化了的应用程序。它简化了 Spring 应用的配置和部署过程,使开发起来更加高效、方便。

    • Spring Boot提供了一种约定优于配置的方式,可以通过自动配置的方式开发,因此减少了繁琐的配置文件。它也集成了许多常用的第三方框架,如Spring Security等,使得开发者能够更容易地使用这些框架。

    • Spring Boot还提供了一个内嵌的Web服务器,如Tomcat,使得应用程序可以快速启动和运行。

    总之,Spring Boot是一个简化了的Spring框架开发工具,使开发者能够更加快速、方便地创建和部署Java应用程序。



    十、Redis

    1. Redis有哪些数据结构?

    Redis 有 5 种数据结构,分别是 String:字符串、Hash:hash表、list:集合、Set:set集合(无序不重复)、zset集合(有序不重复)。

    2. Redis主要消耗什么物理资源?

    mysql、oracle 等他们是存储在磁盘中的,所以读取效率不是很高,redis 的 IO 读取效率非常高,是因此它是存储在内存中的,因此消耗的是内存资源。

    3. Redis是单线程还是多线程?

    Redis 对外提供的键值存储,如set、get等,是单线程的,也就是网络 IO 和数据读写都是单线程。但是为啥单线程的 Redis 效率还是如此的高?

    • 原因一:Redis 是基于内存实现的, 相对于基于磁盘实现的 MySql、Oracle等,效率自然高得多。
    • 原因二:Redis 由于是基于单线程实现的,因此也避免了很多由线程上下文切换、死锁等带来的效率影响,因此对提升效率也有帮助。



    十一、多线程

    1. ReentrantLock与synchronized的区别

    • 相同点:

      • 都是独占锁(也就是每次只能有一个线程能够获取锁)。
      • 都是公平锁:公平锁就是按照线程获取锁的顺序进行排队,依次使线程获得锁、释放锁。
      • 都具有可重入特性:
        • 对于 synchronized 和 ReentrantLock,它们都有一个计数器,当线程第一次进入加锁代码块时,锁的计数器会进行 +1,并且将锁的线程设置为当前线程,当线程再次进入同一个加锁代码块时,会判断当前线程是否是锁的持有线程,如果是,计数器再次 +1,如果不是,则会被阻塞等待。而在退出加锁代码块时,锁的计数器会 -1,直到计数器为 0 时,锁会被释放。
    • 不同点:

      • ReentrantLock:需要手动开启锁和释放锁,比较灵活,但是比较麻烦一点;而synchronized是自动加锁并释放锁的,相对来说比较简单,但是不灵活。
      • Synchronized 加锁后不可以响应中断,只能等它释放锁,而 ReentrantLock 可以响应中断,这时等待的线程也可以获取锁。



    十二、MQ 相关

    1. 为什么使用消息队列(MQ)?

    使用 MQ 最核心的场景有三种:

    • 异步

      异步主要是减少服务端的响应时间。比如美团平台的借钱入口,后台的处理步骤大概是这样的:首先用户提交信息,后台会根据用户信息调用一些第三方的接口,比如调用蚂蚁金服查询芝麻分、调用法院官网查询此人是不是老赖、调用银行接口查询此人的征信是否良好、调用相关政府部门的接口查询此人是否有房产车产等,然后再进行综合评估得出一个可借款金额。

      美团后台的这些步骤,如果设计成同步的,则用户体验就十分不好,因为要依次去调用每个接口,这样势必会浪费大量的时间,如果使用 MQ 设计成异步的话,异步去调用每一个接口,这样用户的响应时间就是后台处理 + 调用最费时的那个接口的时间和,这样就大大降低了这个借钱接口的响应时间,从而提升用户的体验。

      tips:MQ 还有一个好处就是,假如在调用其他服务的过程中,其他服务宕机了,等到其他服务恢复过后 MQ 还会继续调用。如果是同步的话一个服务宕机后续步骤就无法进行了。

    • 解耦

      还是刚才那个例子,假如响应用户可借款金额后,还要发送短信提醒用户,这时也要调用短信服务,把用户加入到会员表后,后期也需要对用户进行数据分析,采用 MQ 就能有效的对代码进行解耦,假如后期还需要加入其他服务,也比较方便。


    • 削峰(抗极端的并发请求)

      比如像淘宝京东那些电商平台,一到双十一或者6-18,用户量会大大增加,全国14亿的人口可想而知后端服务的压力有多大,因此使用 MQ 可以对服务进行削峰,请求会通过 MQ 平缓的到达后端,对后端服务进行缓冲保护。


    2. 如何使用合适的消息队列?

    • 1、RabbitMQ:RabbitMQ 支持 AMQP 协议,AMQP 就是进程间传递异步消息的网络协议。RabbitMQ 最大的特点就是轻量,它的交换机和队列都可以通过 Java 代码去创建,非常利于使用和部署。RabbitMQ 支持的语言也比较多,比如 Java、python、go等等。

      • 但是 RabbitMQ 也有它的缺点:
        • 比如当消息堆积的时候,会导致性能下降。

        • RabbitMQ 的性能相对 Kafka、RocketMQ 来说,性能不及它们,如果对消息队列的性能要求非常高,那么就不要选择 RabbitMQ。因此 RabbitMQ 适用于中小型项目,不适用于大项目。

        • RabbitMQ 底层是采用 Erlang 开发的,扩展和二次开发成本比较高。


    • 2、Kafka:Kafka 依赖于 ZooKeeper,他们两者需配合使用,Kafka 是一个分布式的发布订阅系统,它比较稳定、可靠、性能高效。
      • Kafka 它最大的优势其实是兼容性比较好,特别是在大数据领域,基本开源的软件系统都会优先支持 kafka。
      • Kafka 是使用 Scala 和 Java 开发的,设计上使用了大量异步批量的思想,因此使得它拥有超高的性能。
      • 但是 kafka 也有一个缺点,由于它是异步批量的设计,因此当客户端发送一条消息的时候,kafka 并不会立即发送出去,而是要先攒到一定程度过后批量发送出去,因此对于一些在线的实时的业务,就不推荐使用 kafka。

    • 3、RoketMQ:RoketMQ 是阿里巴巴的开源产品,用 Java 语言实现的,在设计时参考了 kafka,但也做了一些改进,RoketMQ 它的性能和可靠性都非常好,也很稳定。
      • 由于 RoketMQ 是阿里巴巴开发的,因此使用它遇到问题后也能在网上找到很多答案,它底层采用 Java 语言进行开发,因此也容易进行扩展和二次开发。
      • RoketMQ 对在线业务的响应延迟做了很多优化,大多数情况下都可以做到毫秒级的响应,如果我们的业务场景很在意响应延迟,那么可以有限选择 RoketMQ。
      • RoketMQ 也有它的缺点,由于它诞生比较晚,所以它的生态系统没有 kafka 那么完善,它的兼容语言也没有 RabbitMQ 那么多。

    3. RabbitMQ 如何保证消息不丢失?

    首先了解一下消息从生产者到消费者的步骤:
    生产者生产消息 --> 到达 MQ 服务器的交换机 --> 交换机对消息进行路由到具体的队列 --> 队列再把队列中的消息投递至消费者。

    要保证消息不丢失:

    • 站在生产者的角度:就要把消息正确投递到 MQ 服务器,如果生产者投递消息的过程中 MQ 服务器宕机了,则生产者可以把消息记录在第三方的数据库中,如mysql、redis,后期再由定时任务去定时把数据库中的消息投递到 MQ 服务器。
    • 站在 MQ 服务器的角度:要确保交换机把消息路由到正确的队列,队列也要对信息进行正确的存储,因此队列可以把消息持久化到硬盘当中来避免消息丢失。
    • 站在消费者的角度:必须要进行消息确认,只有消费者消费成功才会把消息删除。

    4. 什么是 MQ 中的消息重复?

    导致 MQ 中的消息重复有几类原因:

    • 站在生产者的角度:

      • 假如生产者发送消息给 MQ服务器后,MQ 服务器在进行确认返回时服务宕机了,或者返回超时,都会导致生产者以为 MQ 没有收到消息,因此进行重复投递。
    • 站在 MQ 服务器的角度:

      • 如果 MQ 服务器中队列里的消息已经传送给了消费者,但是消费者消费完消息过后宕机了,没有给 MQ 服务器发送确认反馈;或者出现网络延迟,导致确认反馈发送超时;又或者消费者消费消息后发送确认反馈,但是 MQ 服务器宕机了,这都会导致 MQ 服务器进行再次投递。

    5. 如何解决 MQ 服务器中的重复消息?

    消息重复的情况不可避免,因此要解决消息重复问题,我们可以进行幂等操作。


    消息幂等性处理就是对于多条重复处理的消息,最终都得到同样的结果,举个简单的例子就是,比如使用 MQ 异步去扣减库存,就算扣减库存的操作重复了,最终也只扣减一次,不会重复扣减。


    6. MQ 如何确保消息发送、消息接收?

    MQ 是可以确保消息的可靠发送和接收的,一条消息的完整处理步骤大概是这样的:

    • 1、首先由生产者生产消息,然后向 MQ 服务器进行投递。
    • 2、MQ 服务器收到消息过后,会进行返回确认,只有进行了返回确认,生产者才知道这条消息发送成功,不然的话会进行重新投递。
    • 3、MQ 服务器根据交换机路由到不同的队列,然后在队列中进行存储,依次发送给消费者进行消费。为了避免 MQ 服务器出现故障或宕机,因此 MQ 服务器也会将消息缓存在第三方的数据库中,比如 mysql 或 redis,这样 MQ 服务器恢复过后也能备份消息。
    • 4、MQ 服务器向消费者发送消息后,也会等待返回确认,只有收到了返回确认才能确保消息准确发送,然后在 MQ 服务器中进行删除,如果没有收到回馈,则会进行重新投递。

    7. RabbitMQ 死信队列与延时队列

    RabbitMQ 是支持死信队列和延时队列的。

    • 死信队列:死信队列是一种特殊的队列,用于存储无法被消费者正确处理的消息,当消息满足预设的前置条件时,就会投递到死信队列中,比如超过了重复投递的阈值,或者消息过期了。对于我们程序员来说,可以对死信队列进行分析和后续处理。

      • 死信队列创建步骤:

        • 创建一个正常的队列,专门用于存放这类“死信”消息。
        • 创建一个特定的交换机,设置特定的路由条件,绑定到这个队列上。

    • 延时队列:延时队列是一种特殊的队列,用于延迟处理投递的消息。当消息进入延时队列后,消息将在设定的延迟时间后才会被投递给消费者处理。

      • 使用延时队列的步骤:

        • 创建一个正常的队列和交换机并进行绑定。
        • 再创建一个延时队列和延时交换机,将二者进行绑定,再设置指定的延迟时间(消息是先到达延迟队列,延迟时间到后再到达正常队列)。
        • 然后设置一条延迟交换机到正常交换机路由路线,当延迟消息的时间到后,就会经过正常的交换机到达正常的队列,然后投递给消费者进行消费。








    每天都在更新中。。。

  • 相关阅读:
    OpenCV项目实战(1)— 如何去截取视频中的帧
    福伦王梅花代工爱马仕新款自行车售价16.5万售罄,交不了货?
    后端进阶知识 MySQL为什么那么快 图文详解 之 flush 链表 与 LRU链表
    服务器遭遇挖矿病毒syst3md及其伪装者rcu-sched:原因、症状与解决方案
    家政小程序开发制作,家政保洁上门维修小程序搭建
    5G LDPC polar 3GPP 定案过程
    kubernetes安全检测工具-kube-bench
    vue组件间的通讯方式
    AndroidAuto 解决连接手机启动AA屏闪一下问题
    【C语言】从零开始理解初级指针
  • 原文地址:https://blog.csdn.net/weixin_44780078/article/details/131796843