9月面试总结
本文最后更新于:5 个月前
面试的问题都记录在下面,以及问题的一些解答。
Java 基础
谈谈你对 HashMap
的理解
HashMap
是以数组+链表的方式存储的;HashMap
存储的是键值对映射,即key-value
;HashMap
可以接受null
键和null
值;HashMap
的键如果出现冲突,会以链表的方式链接到后面;HashMap
的链表会一直增长,在JDK 1.8
中,当链表的长度大于8
时,将使用红黑树代替。
JVM
有哪些数据区
- 主要有五个区域: 堆、方法区、虚拟机栈、本地方法栈、程序计数器
说说JVM
运行过程
向操作系统申请空闲内存。系统查找内存分配表,然后把内存段的起始地址和终止地址给
JVM
,JVM
准备加载类文件。给堆、栈分配内存。
文件检查和分析
class
文件。加载类。(类加载机制,如双亲委派机制)
执行方法。
释放内存。
Java
中的锁机制有哪些
常见的锁有 synchronized
、volatile
等
synchronized
当使用
synchronized
修饰类普通方法时,那么当前加锁的级别就是实例对象,当多个线程并发访问该对象的同步方法、同步代码块时,会进行同步。当使用
synchronized
修饰类静态方法时,那么当前加锁的级别就是类,当多个线程并发访问该类(所有实例对象)的同步方法以及同步代码块时,会进行同步。当使用
synchronized
修饰代码块时,那么当前加锁的级别就是synchronized(X)
中配置的x
对象实例,当多个线程并发访问该对象的同步方法、同步代码块以及当前的代码块时,会进行同步。使用同步代码块时要注意的是不要使用
String
类型对象,因为String
常量池的存在,所以很容易导致出问题。
volatile
volatile
可以看做是一种synchronized
的轻量级锁,它能够保证并发时,被它修饰的共享变量的可见性,从jmm
的角度来看一下,每个线程拥有自己的工作内存,实际上线程所修改的共享变量是从主内存中拷贝的副本,当一个共享变量被volatile
修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。
说说你知道的设计模式
常用的设计模式有单例模式、工厂模式、代理模式、观察者模式、装饰模式、享元模式等
单例模式
确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
实现方式通常有两种,分别是 饿汉式单例模式 和 懒汉式单例模式
工厂模式
为创建一组相关或相互依赖的对象提供一个接口,而且无须指定它们的具体类。
一个调用者想创建一个对象,只要知道其名称就可以了。
扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。
屏蔽产品的具体实现,调用者只关心产品的接口。
代理模式
代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是生活中常见的中介。
代理模式主要有两个作用:中介隔离,开闭原则
主要有两种代理模式,分别是静态代理和动态代理
观察者模式
定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。
观察者模式是一个比较特殊的设计模式,它定义了触发机制,观察者只要订阅了被观察者,就可以第一时间得到被观察者传递的信息。
在工作中,使用观察者模式的场景也比较多,比如消息队列消费等等。
装饰模式
- 装饰模式在一些类与类之间有叠加效应(也就是给一个类增加附加功能)的场景中非常好用,它可以说是继承的替代品,有更好的扩展性,也比较灵活。在
Java JDK
源码中也大面积用到了装饰模式,比如:java.io.BufferedInputStream(InputStream)
。
享元模式
- 享元模式合理提高了对象的复用性,减少了程序的内存占用,还有一个提高性能的地方就是减少了对象创建的过程。
Spring 框架
Spring
的 IOC
理解
IOC
就是控制反转,是指创建对象的控制权的转移,以前创建对象的主动权和时机是由自己把控的,而现在这种权力转移到Spring
容器中,并由容器根据配置文件去创建实例和管理各个实例之间的依赖关系,对象与对象之间松散耦合,也利于功能的复用。DI
依赖注入,和控制反转是同一个概念的不同角度的描述,即应用程序在运行时依赖IoC
容器来动态注入对象需要的外部资源。最直观的表达就是,
IOC
让对象的创建不用去new
了,可以由spring
自动生产,使用java
的反射机制,根据配置文件在运行时动态的去创建对象以及管理对象,并调用对象的方法的。
Spring
的 AOP
理解
一般称为面向切面,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(
Aspect
),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。AOP
实现的关键在于代理模式,AOP
代理主要分为静态代理和动态代理。静态代理与动态代理区别在于生成AOP
代理对象的时机不同。AOP
中的动态代理主要有两种方式,JDK
动态代理和CGLIB
动态代理
Spring
框架中都用到了哪些设计模式?
工厂模式:
BeanFactory
就是简单工厂模式的体现,用来创建对象的实例;单例模式:
Bean
默认为单例模式;代理模式:
Spring
的AOP
功能用到了JDK
的动态代理和CGLIB
字节码生成技术;模板方法:用来解决代码重复的问题。比如
RestTemplate
,JmsTemplate
,JpaTemplate
。观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如Spring中listener的实现–ApplicationListener。
Spring
事务的实现方式和实现原理
Spring
事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring
是无法提供事务功能的。真正的数据库层的事务提交和回滚是通过bin log
或者redo log
实现的。Spring
支持编程式事务管理和声明式事务管理两种方式:编程式事务管理使用TransactionTemplate
;声明式事务管理建立在AOP
之上的。其本质是通过AOP
功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
Spring Boot 框架
为什么要用 Spring Boot
?
- Spring Boot 可以做到独立运行、简化配置、自动配置、无代码生成和
XML
配置、应用监控、上手容易等优点。
Mybatis 框架
#{}
和 ${}
的区别是什么?
#{}
是预编译处理,${}
是字符串替换。Mybatis
在处理#{}
时,会将sql
中的#{}
替换为?
号,调用PreparedStatement
的set
方法来赋值;Mybatis
在处理${}
时,就是把${}
替换成变量的值。使用
#{}
可以有效的防止SQL
注入,提高系统安全性。
Mybatis
的常用标签有哪些?
- 常用的有
<select>
、<insert>
、<update>
、<delete>
、<resultMap>
通常一个 Xml
映射文件,都会写一个 Dao
接口与之对应,请问,这个 Dao
接口的工作原理是什么?Dao
接口里的方法,参数不同时,方法能重载吗?
接口的全名,就是映射文件中的
namespace
的值;接口的方法名,就是映射文件中
Mapper
的Statement
的id
值;接口方法内的参数,就是传递给
sql
的参数。Mapper
接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为key
值,可唯一定位一个MapperStatement
。在Mybatis
中,每一个<select>
、<insert>
、<update>
、<delete>
标签,都会被解析为一个MapperStatement
对象。Mapper
接口里的方法,是不能重载的,因为是使用全限名+方法名的保存和寻找策略;Mapper
接口的工作原理是JDK
动态代理,Mybatis
运行时会使用JDK
动态代理为Mapper
接口生成代理对象proxy
,代理对象会拦截接口方法,转而执行MapperStatement
所代表的sql
,然后将sql
执行结果返回。
Mybatis
是如何将 sql
执行结果封装为目标对象并返回的?都有哪些映射形式?
第一种是使用
<resultMap>
标签,逐一定义数据库列名和对象属性名之间的映射关系。第二种是使用
sql
列的别名功能,将列的别名书写为对象属性名。有了列名与属性名的映射关系后,
Mybatis
通过反射创建对象,同时使用反射给对象的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。
如何获取自动生成的(主)键值?
- 可以在
<insert>
内加上usegeneratedkeys=”true” keyproperty=”id”(假设主键名称为id)
Mybatis
一对一、一对多的关联查询是用什么标签实现的?
- 主要是通过
<association>
,<collection>
来实现的
Redis
Redis
支持哪些数据类型?
- 支持的有
String
,Hash
,List
,Set
,zset
, 压缩列表等
什么是 Redis
持久化?Redis
有哪几种持久化方式?优缺点是什么?
持久化就是把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失。
Redis
提供了两种持久化方式:RDB
(默认)和AOF
RDB
持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork
一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。RDB
持久化会清除原有的存储结构,只将数据存储到磁盘中。当需要从磁盘还原数据到内存的时候,再重新将数据组织成原来的数据结构。AOF
持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。AOF
持久化会保留原来的存储格式,将数据按照原有的格式存储在磁盘中。拿散列表这样的数据结构来举例。可以将散列表的大小、每个数据被散列到的槽的编号等信息,都保存在磁盘中。有了这些信息,从磁盘中将数据还原到内存中的时候,就可以避免重新计算哈希值。AOF
文件比RDB
更新频率高,优先使用AOF
还原数据。AOF
比RDB
更安全也更大RDB
性能比AOF
好如果两个都配了优先加载
AOF
什么是缓存穿透?如何避免?什么是缓存雪崩?何如避免?
一般的缓存系统,都是按照
key
去缓存查询,如果不存在对应的value
,就应该去后端系统查找(比如DB
)。一些恶意的请求会故意查询不存在的key
,请求量很大,就会对后端系统造成很大的压力。这就叫做缓存穿透。避免的方法:对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该
key
对应的数据insert
了之后清理缓存。对一定不存在的key
进行过滤;可以把所有的可能存在的key
放到一个大的Bitmap
中,查询时通过该Bitmap
过滤。当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,会给后端系统带来很大压力。导致系统崩溃。这就叫做缓存雪崩。
避免的方法:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个
key
只允许一个线程查询数据和写缓存,其他线程等待;做二级缓存,A1
为原始缓存,A2
为拷贝缓存,A1
失效时,可以访问A2
,A1
缓存失效时间设置为短期,A2
设置为长期;不同的key
,设置不同的过期时间,让缓存失效的时间点尽量均匀。
MySQL
数据库事务的四个特性
ACID
原子性(
Atomicity
):指的是事务中所有操作,要么全做,要么全不做。事务在执行过程中如果出现差错会全部回滚到最初的状态一致性(
Correspondence
):在事务开始之前以及结束之后,数据库的完整性约束不会被破坏隔离性(
Isolation
):隔离状态下执行事务,使他们好像是系统在给定时间内执行的唯一操作持久性(
Durability
):在事务完成后,该事务对数据库所做的操作都会全部持久的保存在数据库中,不会被回滚
MySQL
中 MyISAM
与 InnoDB
的区别
InnoDB
支持事务,而MyISAM
不支持事务InnoDB
支持行级锁,而MyISAM
支持表级锁InnoDB
支持MVCC
, 而MyISAM
不支持InnoDB
支持外键,而MyISAM
不支持InnoDB
不支持全文索引,而MyISAM
支持。InnoDB
不能通过直接拷贝表文件的方法拷贝表到另外一台机器,MyISAM
支持InnoDB
表支持多种行格式,MyISAM
不支持InnoDB
是索引组织表,MyISAM
是堆表
InnoDB
引擎的4大特性
插入缓冲(
insert buffer
)二次写(
double write
)自适应哈希索引(
ahi
)预读(
read ahead
)
MyISAM
与 InnoDB
两者select count(*)
哪个更快,为什么
MyISAM
更快,因为MyISAM
内部维护了一个计数器,可以直接调取
InnoDB
的事务与日志的存放形式
redo
:在页修改的时候,先写到redo log buffer
里面,然后写到redo log
的文件系统缓存里面(fwrite
),然后再同步到磁盘文件(fsync
)undo
:在MySQL5.5
之前,undo
只能存放在ibdata*
文件里面,5.6
之后,可以通过设置innodb_undo_tablespaces
参数把undo log
存放在ibdata*
之外
InnoDB
的事务是如何通过日志来实现的
事务在修改页时,要先记
undo
,在记undo
之前要记undo
的redo
,然后修改数据页,再记数据页修改的redo
。redo
(里面包括undo
的修改) 一定要比数据页先持久化到磁盘。当事务需要回滚时,因为有
undo
,可以把数据页回滚到前镜像的状态,崩溃恢复时,如果redo log
中事务没有对应的commit
记录,那么需要用undo
把该事务的修改回滚到事务开始之前。如果有
commit
记录,就用redo
前滚到该事务完成时并提交掉。
explain
中的索引问题
explain
结果中,一般来说,要看到尽量用index
(type
为const
、ref
等,key
列有值),避免使用全表扫描(type
显式为ALL
)。比如说有
where
条件且选择性不错的列,需要建立索引。被驱动表的连接列,也需要建立索引。被驱动表的连接列也可能会跟
where
条件列一起建立联合索引。当有排序或者
group by
的需求时,也可以考虑建立索引来达到直接排序和汇总的需求。
MySQL
中InnoDB
引擎的行锁是通过加在什么上完成(或称实现)的?
InnoDB
是基于索引来完成行锁例如,
select * from tab_with_index where id = 1 for update;
for update
可以根据条件来完成行锁锁定,并且id
是有索引键的列, 如果id
不是索引键那么InnoDB
将加上表锁
描述下 MySQL
中 InnoDB
支持的四种事务隔离级别名称,以及逐级之间的区别?
读未提交(
read uncommitted
)
读已提交(read committed
)
可重复读(repeatable read
)
串行(serializable
)Read Uncommitted
:可以读取其他session
未提交的脏数据。Read Committed
:允许不可重复读取,但不允许脏读取。提交后,其他会话可以看到提交的数据。Repeatable Read
: 禁止不可重复读取和脏读取、以及幻读(innodb
独有)。Serializable
: 事务只能一个接着一个地执行,但不能并发执行。事务隔离级别最高。
不同的隔离级别有不同的现象,并有不同的锁定/并发机制,隔离级别越高,数据库的并发性就越差。
Kafka
为什么要使用 kafka
,为什么要使用消息队列
缓冲和削峰:上游数据时有突发流量,下游可能扛不住,或者下游没有足够多的机器来保证冗余,
kafka
在中间可以起到一个缓冲的作用,把消息暂存在kafka
中,下游服务就可以按照自己的节奏进行慢慢处理。解耦和扩展性:项目开始的时候,并不能确定具体需求。消息队列可以作为一个接口层,解耦重要的业务流程。只需要遵守约定,针对数据编程即可获取扩展能力。
冗余:可以采用一对多的方式,一个生产者发布消息,可以被多个订阅
topic
的服务消费到,供多个毫无关联的业务使用。健壮性:消息队列可以堆积请求,所以消费端业务即使短时间死掉,也不会影响主要业务的正常进行。
异步通信:很多时候,用户不想也不需要立即处理消息。消息队列提供了异步处理机制,允许用户把一个消息放入队列,但并不立即处理它。想向队列中放入多少消息就放多少,然后在需要的时候再去处理它们。
Kafka
中的 broker
是干什么的
broker
是消息的代理,Producers
往Brokers
里面的指定Topic
中写消息,Consumers
从Brokers
里面拉取指定Topic
的消息,然后进行业务处理,broker
在中间起到一个代理保存消息的中转站。
Kafka中是怎么体现消息顺序性的?
kafka
每个partition
中的消息在写入时都是有序的,消费时,每个partition
只能被每一个group
中的一个消费者消费,保证了消费时也是有序的。整个topic
不保证有序。如果为了保证topic
整个有序,那么将partition
调整为1
.