尼尧的面试日记:面试记录(三)

最近开始找工作了,坐标杭州。渣渣感觉面试应该挺困难的,也不知道具体会问哪些类型,会在面试完把每一次面试问到的题目列出来, 一个作用是参考记录,另一个作用是可以把自己不会或者弱项的问题及时补齐

暂时是先把问题列出来,有时间的时候会把答案补上。

希望能找到一个好工作,加油


我大概总结一下,小型公司2年以下开发主要业务就是CRUD,招人肯定是希望能立马上手,所以面试范围我押题是常用的框架(SpringBoot、Spring基本概念,Mybatis),事务、数据库相关(锁、事务级别、sql优化,数据库底层概念等)、异常。有些会问一些设计模式(主要掌握工厂模式、单例模式、代理模式。)。

中大型公司的话,加上 JVM,集合,并发编程,一些网络知识。有些可能还会有一两道算法题。甚至开始问分布式了,具体看他们业务。

大厂请直接参考我画的java路线图,大厂知识路线没有3年以下之分,只有深/广度之分:Java知识体系脑图(2020年)


本次面试过程


  • 背景

小型公司,常驻项目现场(非外包),政府、银行类业务。电话面试,问的比较简单。

  • 过程

本次就一轮面试,一开始也是自我介绍,然后就问了几个问题。

面试题


一、你先简单介绍一下自己吧。

阿巴阿巴… (几年工作经验,上一家项目主要负责哪一块,自己的强项是什么。)

二、了解mysql吗,那先说一下数据库的主键索引和普通索引的区别。

索引类型说明
主键索引使用主键字段作为索引(聚集索引),唯一且不能为空。主键索引的叶子节点存放的是整行数据 ;主键索引也被称为聚簇索引
普通索引(非主键索引)普通索引使用除主键外某一个字段作为索引,是辅助索引中的一种。辅助索引的叶子节点存放的是主键的值;辅助索引也被称为二级索引
  • 什么是索引?

索引在MySQL中也叫做“键”或者"key"(primary key,unique key,还有一个index key),是存储引擎用于快速找到记录的一种数据结构。索引对于良好的性能非常关键,尤其是当表中的数据量越来越大时,索引对于性能的影响愈发重要,减少io次数,加速查询。

其中primary key、unique key,还有约束的效果,primary key 不为空且唯一,unique key 唯一,而index key只有加速查询的效果,没有约束效果

  • 主键索引和非主键索引

聚集索引和辅助索引示意图如下:
在这里插入图片描述
其中 R 代表一整行的值。

在mysql数据库innodb引擎里面,主键的确就是聚集索引。但是myisam引擎里面主键也不是聚集索引。

对于Innodb,主键毫无疑问是一个聚集索引。但是当一个表没有主键,或者没有一个索引,Innodb会如何处理呢。请看如下规则:

  1. 如果一个主键被定义了,那么这个主键就是作为聚集索引
  2. 如果没有主键被定义,MySQL取第一个唯一索引(unique)而且只含非空列(NOT NULL)作为主键,InnoDB使用它作为聚簇索引。
  3. 如果没有主键也没有合适的唯一索引,那么innodb内部会生成一个隐藏的主键作为聚集索引,这个隐藏的主键是一个6个字节的列,改列的值会随着数据的插入自增。
  4. 自增主键会把数据自动向后插入,避免了插入过程中的聚集索引排序问题。聚集索引的排序,必然会带来大范围的数据的物理移动,这里面带来的磁盘IO性能损耗是非常大的。 而如果聚集索引上的值可以改动的话,那么也会触发物理磁盘上的移动,于是就可能出现page分裂,表碎片横生。所以不应该修改聚集索引。

除了聚集索引外其他索引都是辅助索引(Secondary Index,也称为非聚集索引,例如 unique key啊、index key啊)。

从图上看出,聚集索引(主键索引)和辅助索引(非主键索引)的区别是:聚集索引的叶子节点存放的是 整行数据,而辅助索引的叶子节点存放的是 主键的值,辅助索引也被称为二级索引。

聚集索引的叶子节点除了主键值还包含一个书签(bookmark),其实这个书签你可以理解为是一个{‘name字段’,name的值,主键id值}的这么一个数据。该书签用来告诉InnoDB存储引擎去哪里可以找到与索引相对应的行数据。

根据这两种结构我们来进行下查询,看看他们在查询上有什么区别。

1、如果查询语句是 select * from table where ID = 1,即主键查询的方式,则只需要搜索 ID 这棵 B+树。

2、如果查询语句是 select * from table where key = 10,即非主键的查询方式,则先搜索key字段建立的索引树,得到ID=1,再到ID索引树搜索一次,这个过程也被称为回表。

  • MySql索引类型

MYSQL的索引类型:PRIMARY, INDEX,UNIQUE,FULLTEXT,SPAIAL。

举个例子来说,比如你在为某商场做一个会员卡的系统。

这个系统有一个会员表
有下列字段:
会员编号 INT
会员姓名 VARCHAR(10)
会员身份证号码 VARCHAR(18)
会员电话 VARCHAR(10)
会员住址 VARCHAR(50)
会员备注信息 TEXT

那么这个 会员编号,作为主键,使用 PRIMARY
会员姓名 如果要建索引的话,那么就是普通的 INDEX
会员身份证号码 如果要建索引的话,那么可以选择 UNIQUE (唯一的,不允许重复)

#除此之外还有全文索引,即FULLTEXT
会员备注信息 , 如果需要建索引的话,可以选择全文搜索。
用于搜索很长一篇文章的时候,效果最好。
用在比较短的文本,如果就一两行字的,普通的 INDEX 也可以。
但其实对于全文搜索,我们并不会使用MySQL自带的该索引,而是会选择第三方软件如Sphinx,专门来做全文搜索。

#其他的如空间索引SPATIAL,了解即可,几乎不用


三、刚才听你说到聚集索引,那你说说(后提醒这是联合索引,你记错了)。

联合索引,即多个字段共同组成的索引。

-PRIMARY KEY(id,name):联合主键索引
-UNIQUE(id,name):联合唯一索引
-INDEX(id,name):联合普通索引

联合索引有两个特点(好处):

  • 最左匹配原则。比如字段(a,b,c)作为联合索引,那么他的查询条件只能是 (a)、(a,b)、(a,b,c),字段间关联词为 and,若用or失效。

若你自行测试发现(a,c)也生效,但仔细观察实际上只用了(a)索引。
创建复合索引时,应该仔细考虑列的顺序,将区分度高的放在最左边,依次排下来,范围查询的条件尽可能的往后边放。

  • 在第一个键相同的情况下,已经对第二个键进行了排序处理。因为B+树特性,索引本身在叶子节点已经排序了。


四、说一说乐观锁和悲观锁


  • 乐观锁

乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。

乐观锁不是一种实际的锁,它是一种理论,一般通过CAS(比较并交换)来实现。一般数据库中都会存在隐藏字段版本号,可通过比较版本号实现。

乐观锁的特点是先进行业务操作,不到万不得已不会去拿锁。乐观地认为拿锁多半会是成功的,因此在完成业务操作需要实际更新数据的最后一步再去拿一下锁。


  • 悲观锁

悲观锁(Pessimistic Locking),是指在数据处理过程,使数据处于锁定状态,一般使用数据库的锁机制实现。思路即一开始处理就先锁上,处理完毕后再释放锁。

现在互联网高并发的架构中,受到fail-fast思路的影响,悲观锁使用的频率较低了。

在MySQL中使用悲观锁,必须关闭MySQL的自动提交,set autocommit=0,MySQL默认开启自动提交autocommit模式,即你执行一个更新操作,MySQL会自动将结果提交。

在MySQL中用悲观锁务必须确定走了索引,而不是全表扫描,否则将会将整个数据表锁住。


五、线程池知道吗,说说其中的参数,或者知道几个说几个。

线程池在博主之前的博客中写过:Java基础进阶——多线程与JUC(下)

线程池,本质上是一种对象池,用于管理线程资源。在任务执行前,需要从线程池中拿出线程来执行。在任务执行完成之后,需要把线程放回线程池。

通过线程的这种反复利用机制,可以有效地避免直接创建线程所带来的坏处。

  • 线程池的好处
  1. 降低资源的消耗。线程本身是一种资源,创建和销毁线程会有CPU开销;创建的线程也会占用一定的内存。
  2. 提高任务执行的响应速度。任务执行时,可以不必等到线程创建完之后再执行。
  3. 提高线程的可管理性。线程不能无限制地创建,需要进行统一的分配、调优和监控。

一句话总结:线程复用,可以控制最大并发量,提高响应速度,方便管理。

  • 线程池的缺点
  1. 频繁的线程创建和销毁会占用更多的CPU和内存。
  2. 频繁的线程创建和销毁会对GC产生比较大的压力。
  3. 线程太多,线程切换带来的开销将不可忽视。
  4. 线程太少,多核CPU得不到充分利用,是一种浪费。

  • ThreadPoolExecutor

注:在阿里Java开发规范手册里要求,创建线程池不能使用Executors,必须使用ThreadPoolExecutor。
原因是Executors源码中创建了无长度限制的队列new LinkedBlockingQueue<Runnable>(),则队列最大容量可达Integer.MAX_VALUE,可能因为任务堆积过多导致OOM(OutOfMemoryError),这是非常严重的bug。Executors底层实际也是ThreadPoolExecutor

ThreadPoolExecutor中有7个参数,就是平常说的线程池调优时需要调整的参数。下面我们用构造函数来讲解下各个参数的含义:

public class ThreadPoolExecutor{
	public ThreadPoolExecutor(
		int corePoolSize, //线程池中的核心线程数
		int maximumPoolSize, //线程池中的最大线程数
		long keepAliveTime, //存活时间,当线程数超过核心线程数时,空闲线程等待的时长,若超过则释放
		TimeUnit unit, //存活时间的时间单位
		BlockingQueue<Runnable> workQueue, //等待队列,用于存放超过核心线程数的空闲线程,类似于银行的候客厅
		ThreadFactory threadFactory, //线程工厂,我们可以使用它来创建一个线程,一般不会改动它
		RejectedExecutionHandler handler) //拒绝策略,超出最大线程数和队列容量导致被阻塞时所使用的处理程序
}
  • 拒绝策略

所谓拒绝策略,就是当线程达到了最大承载的时候,我们对多出来的线程采取的措施。jdk自带以下4种拒绝策略:

  1. ThreadPoolExecutor.AbortPolicy() //最大承载直接抛出异常。
  2. ThreadPoolExecutor.CallerRunsPolicy() // 在调用者线程执行。谁创建谁处理。
  3. ThreadPoolExecutor.DiscardPolicy // 任务直接丢弃,不做任何处理,不抛出异常。
  4. ThreadPoolExecutor.DiscardOldestPolicy() // 尝试去和最早的那个线程竞争,失败则不处理,不抛异常。

其中比较常用的是DiscardPolicy,但是他不记录信息。当不满足我们需求的时候,可以通过实现RejectedExecutionHandler接口的方式自定义拒绝策略。

更多具体内容请查看博主的原博内容。

六、说一说bean什么情况下注入失败。

我一开始没太懂问的方向,然后回答是:

现在Spring中bean一般都是用注解注入的,如果失败的话是实体类没有set/get、构造函数吗?

面试官说:一般就是实体类没有受spring管理,或者你要注入的类没有受spring管理,所以注入失败了。

我:嗯嗯,谢谢您的解答。

七、AOP知道吗,那你知道在同一个类中,某个方法调用了切面的方法,切面生效吗?

不生效的。AOP增加注解时,实际是在注入的时候增加了一个代理增强类,在同一个类的方法中调用,并不会注入增强类。

原因是,由于 Spring AOP (包括动态代理和 CGLIB 的 AOP) 的限制导致的。 Spring AOP 并不是扩展了一个类(目标对象),而是使用了一个代理对象来包装目标对象,并拦截目标对象的方法调用。这样的实现带来的影响是,在目标对象中调用自己类内部实现的方法时,这些调用并不会转发到代理对象中,甚至代理对象都不知道有此调用的存在。

这个情况与this类似,在bean中不要使用this来调用被@Async、@Transactional、@Cacheable等注解标注的方法,this下注解是不生效的,这些注解都是通过动态代理实现的。

如何解决可参考本文(解决方法含较多源码不在此写出来了):透过现象看原理:详解Spring中Bean的this调用导致AOP失效的原因


八、暂时问这么多吧,你有什么要问我的?


本文答案参考:
[1] @626 :MySQL索引
[2] @Andy_li :主键索引就是聚集索引吗?
[3] @光闪 :透过现象看原理:详解Spring中Bean的this调用导致AOP失效的原因

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值