之前分享很多大厂的面经,这次分享一家上海某小厂的 Java 岗位面试,面试的时间也挺长的,接近 1 个小时,无算法,全程抓着项目+mysql+redis+java这 4 个方向问。
介绍你的项目
这个项目是企业里面做的还是学校的项目?
说说你在这个项目负责了那些,应用了那些技术,你学到了什么?
你说你用到了 redis 分布式锁,为何采用 redis 分布式锁啊,redis 分布锁有啥好处?
你用过线程池吗? 谈谈线程池的好处?
在这个项目中线程池参数怎么配置的?
你这个项目是根据 io密集型 还是 CPU密集型 计算?
按隔离水平高低排序如下:
我们需要了解两个知识:
Read View 有四个重要的字段:
对于使用 InnoDB 存储引擎的数据库表,它的聚簇索引记录中都包含下面两个隐藏列:
在创建 Read View 后,我们可以将记录中的 trx_id 划分这三种情况:
一个事务去访问记录的时候,除了自己的更新记录总是可见之外,还有这几种情况:
min_trx_id
值,表示这个版本的记录是在创建 Read View 前已经提交的事务生成的,所以该版本的记录对当前事务可见。max_trx_id
值,表示这个版本的记录是在创建 Read View 后才启动的事务生成的,所以该版本的记录对当前事务不可见。m_ids
列表中,表示生成该版本记录的活跃事务依然活跃着(还没提交事务),所以该版本的记录对当前事务不可见。m_ids
列表中,表示生成该版本记录的活跃事务已经被提交,所以该版本的记录对当前事务可见。这种通过「版本链」来控制并发事务访问同一个记录时的行为就叫 MVCC(多版本并发控制)。
不会,快照读,
MySQL InnoDB 引擎的默认隔离级别虽然是「可重复读」,但是它很大程度上避免幻读现象(并不是完全解决了),解决的方案有两种:
这两个解决方案是很大程度上解决了幻读现象,但是还是有个别的情况造成的幻读现象是无法解决的。
比如这个场景:
在可重复读隔离级别下,事务 A 第一次执行普通的 select 语句时生成了一个 ReadView,之后事务 B 向表中新插入了一条 id = 5 的记录并提交。接着,事务 A 对 id = 5 这条记录进行了更新操作,在这个时刻,这条新记录的 trx_id 隐藏列的值就变成了事务 A 的事务 id,之后事务 A 再使用普通 select 语句去查询这条记录时就可以看到这条记录了,于是就发生了幻读。
因为这种特殊现象的存在,所以我们认为 MySQL Innodb 中的 MVCC 并不能完全避免幻读现象。
可重复读隔离级别是启动事务时生成一个 Read View,然后整个事务期间都在用这个 Read View。
读提交隔离级别是在每次读取数据时,都会生成一个新的 Read View。也意味着,事务期间的多次读取同一条数据,前后两次读的数据可能会出现不一致,因为可能这期间另外一个事务修改了该记录,并提交了事务。
索引是数据库中一种用于提高查询性能的数据结构。它类似于书籍的目录,可以帮助数据库系统快速地定位和访问数据。通过在数据库表的一个或多个列上创建索引,可以加快查询速度,减少数据扫描的时间和成本。
索引的作用是通过建立一个索引文件,将索引列的值与对应的数据记录进行映射关联。当进行查询时,数据库系统可以根据索引文件快速定位到符合查询条件的数据记录,而无需逐条扫描整个表。这样可以大大减少查询的时间复杂度,提高查询效率。
索引的创建需要占用额外的存储空间,并且在数据更新时需要维护索引结构,可能会增加写操作的开销。因此,索引的使用需要权衡查询频率和数据更新频率之间的平衡。对于经常进行查询的字段,可以考虑创建索引;而对于更新频率较高的字段,或者区分度较小的字段,创建索引可能效果有限,甚至会影响性能。
通过将多个字段组合成一个索引,该索引就被称为联合索引。
比如,将商品表中的 product_no 和 name 字段组合成联合索引(product_no, name)
,创建联合索引的方式如下:
CREATE INDEX index_product_no_name ON product(product_no, name);
联合索引(product_no, name)
的 B+Tree 示意图如下(图中叶子节点之间我画了单向链表,但是实际上是双向链表,原图我找不到了,修改不了,偷个懒我不重画了,大家脑补成双向链表就行)。
可以看到,联合索引的非叶子节点用两个字段的值作为 B+Tree 的 key 值。当在联合索引查询数据时,先按 product_no 字段比较,在 product_no 相同的情况下再按 name 字段比较。
也就是说,联合索引查询的 B+Tree 是先按 product_no 进行排序,然后再 product_no 相同的情况再按 name 字段排序。
因此,使用联合索引时,存在最左匹配原则,也就是按照最左优先的方式进行索引的匹配。在使用联合索引进行查询的时候,如果不遵循「最左匹配原则」,联合索引会失效,这样就无法利用到索引快速查询的特性了。
二叉树会比较数据,从上至下以此分配和排序数据
开启慢查询日志,定位到出现问题的sql的语句
如果查询出来的结果集,存在连续且递增的字段,可以基于有序字段来进行查询,例如 select xxx from book where 有序字段 >= 1 limit 100
舍弃limit关键字,如果查询出来的结果集存在连续且递增的字段,使用between and来进行范围结果集查询,例如 select xxx from book where 有序字段 between 10000000 and 1000100
采用MongoDB、ES搜索引擎优化深分页
like %xx
或者 like %xx%
这两种方式都会造成索引失效;不一定,如果 or 左右两个字段都是索引,就能走索引。
如果 a 和b 是联合索引,会发生索引失效,对于联合索引(比如 bc),如果使用了 b =xxx or c=xxx,会走不了索引。
左模糊和全模糊查询
因为索引 B+ 树是按照「索引值」有序排列存储的,只能根据前缀进行比较。
举个例子,下面这张二级索引图(图中叶子节点之间我画了单向链表,但是实际上是双向链表,原图我找不到了,修改不了,偷个懒我不重画了,大家脑补成双向链表就行),是以 name 字段有序排列存储的。
假设我们要查询 name 字段前缀为「林」的数据,也就是 name like '林%'
,扫描索引的过程:
如果使用 name like '%林'
方式来查询,因为查询的结果可能是「陈林、张林、周林」等之类的,所以不知道从哪个索引值开始比较,于是就只能通过全表扫描的方式来查询。
Redis 提供了丰富的数据类型,常见的有五种数据类型:String(字符串),Hash(哈希),List(列表),Set(集合)、Zset(有序集合)。
随着 Redis 版本的更新,后面又支持了四种数据类型:BitMap(2.2 版新增)、HyperLogLog(2.8 版新增)、GEO(3.2 版新增)、Stream(5.0 版新增)。Redis 五种数据类型的应用场景:
Redis 后续版本又支持四种数据类型,它们的应用场景如下:
Redis 的读写操作都是在内存中,所以 Redis 性能才会高,但是当 Redis 重启后,内存中的数据就会丢失,那为了保证内存中的数据不会丢失,Redis 实现了数据持久化的机制,这个机制会把数据存储到磁盘,这样在 Redis 重启就能够从磁盘中恢复原有的数据。
Redis 共有三种数据持久化的方式:
我们的业务通常会有几个数据会被频繁地访问,比如秒杀活动,这类被频地访问的数据被称为热点数据。
如果缓存中的某个热点数据过期了,此时大量的请求访问了该热点数据,就无法从缓存中读取,直接访问数据库,数据库很容易就被高并发的请求冲垮,这就是缓存击穿的问题。
可以发现缓存击穿跟缓存雪崩很相似,你可以认为缓存击穿是缓存雪崩的一个子集。
应对缓存击穿可以采取前面说到两种方案:
ArrayList是Java中的一个动态数组类,它实现了List接口。它的特点是可以根据需要自动扩展和收缩数组的大小,以便容纳不同数量的元素。
ArrayList可以存储任意类型的对象,包括基本类型的包装类。它提供了一系列方法来操作和访问数组中的元素,比如添加元素、删除元素、获取元素、遍历数组等。
ArrayList的内部实现是基于数组,当添加元素时,如果数组已满,它会自动创建一个更大的数组,并将原数组中的元素复制到新数组中。同样,当删除元素时,如果数组中的元素数量变得较少,它会自动缩小数组的大小,以节省内存空间。
由于ArrayList是基于数组实现的,所以它具有随机访问的特性,可以通过索引直接访问数组中的元素,时间复杂度为O(1)。但在插入和删除元素时,需要移动其他元素以保持数组的连续性,时间复杂度为O(n)。
哈希函数是一种将输入数据映射到固定大小值(哈希值)的函数。它将任意长度的输入数据转换为固定长度的输出,通常是一个较短的数字或字符串。
哈希函数具有以下特性:
哈希函数常用于密码学、数据完整性校验、数据索引等领域。在密码学中,哈希函数常用于将用户密码进行加密存储,以保护用户的隐私。在数据完整性校验中,哈希函数可以用于验证数据是否被篡改。在数据索引中,哈希函数可以用于快速查找和比较数据。
常见的哈希函数包括MD5、SHA-1、SHA-256等。这些哈希函数具有较低的碰撞概率,保证了数据的唯一性和安全性。然而,随着计算能力的提升,一些传统的哈希函数可能存在安全性问题,因此在实际应用中,需要根据具体需求选择适当的哈希函数。
HashMap 基于 Hash 算法实现的,我们通过 put(key,value)存储,get(key)来获取。当传入 key 时,HashMap 会根据 key. hashCode() 计算出 hash 值,根据 hash 值将 value 保存在 bucket 里。当计算出的 hash 值相同时,我们称之为 hash 冲突,HashMap 的做法是用链表和红黑树存储相同 hash 值的 value。当 hash 冲突的个数比较少时,使用链表否则使用红黑树。
HashMap在多线程环境下会存在线程安全问题。HashMap是一种非线程安全的数据结构,当多个线程同时对HashMap进行修改时,可能会导致数据不一致或产生其他异常。
如果需要在多线程环境下使用HashMap,可以考虑使用ConcurrentHashMap代替。ConcurrentHashMap是Java提供的线程安全的哈希表实现,它采用了分段锁的机制,不同的线程可以同时访问不同的分段,从而提高了并发读写的能力。
ConcurrentHashMap是Java中的一个线程安全的哈希表实现,它可以在多线程环境下并发地进行读写操作,而不需要像传统的HashTable那样在读写时加锁。
ConcurrentHashMap的实现原理主要基于分段锁和CAS操作。它将整个哈希表分成了多个Segment(段),每个Segment都类似于一个小的HashMap,它拥有自己的数组和一个独立的锁。在ConcurrentHashMap中,读操作不需要锁,可以直接对Segment进行读取,而写操作则只需要锁定对应的Segment,而不是整个哈希表,这样可以大大提高并发性能。
Spring MVC和Spring Boot是两个在Java开发中常用的框架,它们有以下几个主要区别:
目标和定位:Spring MVC是一个在Java企业级应用中构建Web应用程序的框架,它提供了一套完整的MVC(模型-视图-控制器)架构,用于处理HTTP请求和响应。而Spring Boot是一个快速构建独立、可运行的、生产级的Spring应用程序的框架,它简化了Spring应用程序的配置和部署过程。
配置方式:Spring MVC需要通过XML文件或Java配置类进行显式的配置,包括配置控制器、视图解析器、拦截器等。而Spring Boot采用约定大于配置的原则,提供了自动配置功能,可以根据应用程序的依赖和配置文件中的设置,自动完成大部分配置工作,简化了开发者的配置过程。
依赖管理:Spring MVC需要开发者手动管理依赖的版本和冲突。而Spring Boot使用了一个叫做"Starter"的概念,它提供了一组预定义的依赖,可以通过简单的引入Starter来管理依赖,减少了开发者的工作量。
Spring Boot的自动装配原理是基于条件注解和Spring的依赖注入机制实现的。
首先,Spring Boot会根据classpath下的依赖以及配置文件中的设置,自动扫描并加载相应的自动配置类。
其次,自动配置类中使用了条件注解,根据条件判断是否要进行自动装配。条件注解可以根据一些特定的条件,如某个类是否在classpath中、某个配置是否存在等,来决定是否要进行自动装配。
最后,当条件满足时,自动配置类会自动注册和配置相应的Bean,将它们添加到Spring容器中。这样,在应用程序启动时,Spring Boot就会自动完成大部分配置工作,开发者无需手动配置。
通过自动装配,Spring Boot可以根据应用程序的依赖和配置文件的设置,智能地选择合适的配置,并将其应用到应用程序中,从而简化了开发者的配置过程,提高了开发效率。
MVC(Model-View-Controller)是一种软件设计模式,用于将应用程序的逻辑分离为三个不同的组件:模型(Model)、视图(View)和控制器(Controller)。
模型(Model)表示应用程序的数据和业务逻辑。它负责处理数据的读取、存储、验证以及与数据库的交互等操作。
视图(View)是用户界面的呈现部分,负责展示模型中的数据给用户。它可以是一个网页、一个图形界面或者其他任何形式。
控制器(Controller)充当模型和视图之间的中介,负责处理用户的请求、更新模型的状态,以及决定要显示哪个视图。它接收用户的输入,调用相应的模型方法来处理请求,并最终将结果返回给视图进行显示。
通过MVC的分层结构,实现了应用程序的解耦和可维护性。模型、视图和控制器各司其职,彼此之间的依赖关系降低,使得应用程序的开发、测试和维护更加灵活和高效。
MVC模式被广泛应用于Web开发、桌面应用程序以及移动应用程序等领域,是一种重要的软件设计模式。
用过gateway做网关,nacos做注册中心和配置中心
面试官问的很多,但是人很好,问的问题基本答出来了,说我理解的可以,就是说让我要多看看底层原理和源码, 聊了50多分钟。
文章引用微信公众号"猿大侠",如有侵权,请联系管理员删除!