1.SE基础高频面试题
1、JDK和JRE的区别是什么
JDK是Java开发工具包,JRE是Java运行时环境,二者的区别在于
JRE是Java程序运行所必须的,它包含jvm和一些Java的基础类库,jvm是Java虚拟机
JDK是Java程序开发所必须的,它包含JRE和一些开发工具
总结一下就是:JDK包含JRE,如果仅仅是运行Java程序,只要有JRE即可;如果是要开发Java程序,则必须要有JDK
2、&和&&的区别是什么
&和&&都是逻辑运算符,都可以实现取并的效果,即符号两边的表达式都为true,结果才是true
不一样的是&&有短路的功能,即当符号前面的表达式为false时,后面的表达式将不再执行,而&没有这个功能
另外,&还可以用作位运算符,当&操作符两边的表达式不是boolean类型时,&表示按位与操作
3、final finally finalize区别是什么
-
final可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、 修饰变量表示该变量是一个常量不能被重新赋值。
-
finally一般作用在try-catch代码块中,在处理异常的时候,无论程序是否出现异常,写在finally中的代码都会被执行,一般用来释放一些资源
-
finalize是Object类的一个方法,它会在一个对象被垃圾回收的时候自动被垃圾回收器来调用
4、String、StringBuffer、 StringBuilder 、StringJoiner的区别
区别有以下几点:
-
可变性:String是不可变对象,其它的都是可变对象
-
线程安全:String和StringBuffer是线程安全的,StringBuilder和StringJoiner是线程不安全的
-
效率:StringBuilder和StringJoiner效率最高,StringBuffer居中,String效率最低
-
使用场景:少量字符串的操作使用String,大量字符串的频繁操作在多线程下使用StringBuffer,单线程下可以使用StringBuilder、StringJoiner
5、使用=和new创建字符串的区别是什么
两种方式都可以创建出字符串,但是在内存分配上却是不一样的
等号方式:JVM会在常量池中创建一个字符串对象
new方式:JVM会先判断常量池中是否有此字符串,如果没有,它就会在常量池中创建一个,而且无论常量池中是否有,它都会在堆内存中重新创建一个
6、float f=3.4 是否正确
不正确
因为直接写出的字面量3.4是double类型的,double转float属于向下转型
这种情况Java是不允许的,要赋值的话,要强制类型转换 float f =(float)3.4
或者是在声明字面量3.4的时候,直接声明成float类型,即写成 float f =3.4F
7、重写和重载的区别是什么
重载和重写都是用于描述方法间的关系的,区别在于:
重写是存在于子父类之间的,一般用在父类的方法无法满足子类需求时,子类重写方法来自定义方法功能。它要求子类定义的方法与父类中的方法具有相同的方法名字,相同的参数表和相同的返回类型
重载是存在于同一个类中的,一般用在功能相似的方法需要接收不同的参数时,它要求多个方法具有相同的名字,但方法具有不同的参数列表
8、this和super的应用场景是什么
他们都是Java提供的关键字
this代表的是当前对象,一般用于在一个方法中调用本对象的成员变量或其它方法
supper代表是父类对象,一般在本对象和父对象出现成员名称冲突时,强行调用父对象的成员,也经常用于调用父类的构造方法
类:人 ,对象: 我 类可以new出来多个对象(实实在在是对象--实例化,虚构的就是类)
9、throw和throws的区别是什么
throws:用在方法的声明上,声明当前方法可能抛出的异常
throw:写在方法里,真正的抛出一个异常,抛出自定义异常。创建对象自定义抛出异常
10、应该使用什么数据类型来计算价格
如果不是特别关心内存和性能的话,使用 BigDecimal
否则使用预定义精度的 double 类型
项目中一般用integer类型,在页面上显示的时候再显示double类型的
11、== 与 equals 的区别
是一个运算符,equals 是 Object 类的方法 用于基本类型的变量比较时: 比较的是值是否相等,equals不能直接用于基本数据类型的比较,需要转换为其对应的包装类型。 用于引用类型的比较时。和 equals 都是比较栈内存中的地址是否相等。但是通常会重写 equals 方法去实现对象内容的比较。
-
数值型的引用数据类型: 缓存区间内结果为true ,缓存区间之外为false
Integer -127--128(缓存区间)
12、接口和抽象类的区别
它们的共同点是:都不能实例化对象
它们的不同点是:
-
抽象类一般用于抽取子类中的共同方法和属性,接口一般用于指定实现类的规范
-
抽象类可以有构造方法和静态代码块,接口中都不能有
-
一个类只能继承一个抽象类,而一个类却可以实现多个接口。
当然我这里对于接口的描述是基于JDK1.8之后的
13、说出几个常见的异常
Java中的异常分为运行时异常和编译时异常两大类:
1.运行时异常都是 RuntimeException 类及其子类异常,这类异常的特点是不强行要求程序员进行处理,常见的有:
-
NullPointerException 空指针异常,调用了未经初始化的对象或者是不存在的对象
-
IndexOutOfBoundsException 数组角标越界异常,常见于操作数组对象时发生
-
ClassCastException 数据类型转换异常
-
NoSuchMethodException 方法不存在异常
2.非运行时异常,也叫编译异常,是 Exception 的子类但不是RuntimeException的子类,类型上都属于Exception类及其子类
它要求程序员在编写代码的过程中提供异常处理方案,否则编译不通过,常见的有:IOException和SQLException等
14、Java 反射有了解吗
反射是指在运行状态中,对于任意一个类都能够知道这个类所有的属性和方法,并且可以调用它的任意一个方法
它主要应用于大量的框架底层,比如 Spring/Spring Boot、MyBatis 等等
15、浅拷贝和深拷贝区别
浅拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。即对象的浅拷贝会对“主”对象进行拷贝,但不会复制主对象里面的对象。”里面的对象“会在原来的对象和它的副本之间共享。
深拷贝:深拷贝是一个整个独立的对象拷贝,深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。简而言之,深拷贝把要复制的对象所引用的对象都复制了一遍。
2.Java集合高频面试题
1、说一说集合类的体系结构
常见的集合主要有两大类,分别是单列集合和双列集合
-
单列集合的顶级接口是Collection,它下面有两个主要的子接口分别是List和Set
List的特点是元素有序的,可以重复的;Set的特点是元素无序的,不可重复的
List下我们常用的类有ArrayList、LinkedList等,Set下我们常用的类有HashSet、LinkedHashSet、TreeSet等
-
双列集合的顶级接口是Map,它的特点是每个元素都有键和值两部分组成,且键不能重复
Map接口下我们常用的类有:HashMap、LinkedHashMap、TreeMap等
2、聊聊集合类的底层数据结构
集合主要分为单列集合和双列集合
-
双列集合都是Map的实现类,主要有HashMap、LinkedHashMap和TreeMap
-
HashMap: JDK1.8之前是由数组+链表组成的,JDK1.8之后,为了提升效率,在当链表的长度>8,并且数组长度>=64的时候,链表就会转换为红黑树
-
LinkedHashMap:继承自HashMap,在HashMap的基础上增加了一条双向链表,来保持键值对的插入顺序。
-
TreeMap:底层是红黑树
-
单列集合主要是List和Set
-
List有ArrayList和LinkedList,ArrayList底层是数组,查询快,增删慢;LinkedList底层是双向链表,查询慢,增删快
-
Set有HashSet、LinkedHashSet和TreeSet,它的实现原理和对应的Map是一样的,底层都是用的对应Map的key实现
3、ArrayList和LinkedList的区别
ArrayList和LinkedList都是单列集合,都是有序,可重复的
不同点有:
-
ArrayList 底层是动态数组,而LinkedList底层是双向链表
-
ArrayList查询快,增删慢,适合查询场景;LinkedList查询慢,增删快,适合频繁修改的场景
-
LinkedList比ArrayList更占内存,这是因为它的每个节点除了存储数据,还存储了前后节点的引用
4、HashMap和HashTable区别
调用put方法时 HashTable有锁保证线程安全 但效率较低 HashMap没有 但效率高
我们公司一般都是采用的HashMap,HashTable其实不太常用
如果碰到需要线程安全的场景,我们一般会使用ConcurrentHashMap(采用了乐观锁和悲观锁)
我理解的差不多就是这样
-
乐观锁往下是native(C或者是C++)
-
HashTable--悲观锁--synchronized--存数据时加锁
原子类也可以解决线程安全问题(Atomic开头)--乐观锁 CAS机制 原理指向c++
5、HashMap的底层原理
底层数据结构是哈希表,哈希表在JDK1.8之前是数组+链表实现,在JDK1.8之后是数组+链表+红黑树实现的
-
下面我以map中存储对象的流程来说一下它的实现原理:
-
当创建一个HashMap时,JDK就会在内存中创建一个长度为16的数组
-
当调用put方法向HashMap中保存一个元素时,它会先调用key的hashCode方法计算出key的hash值
-
然后用得到的hash值对数组长度取余,找出当前对象的元素在数组中的位置
-
接着它会判断该位置上是否有元素,如果没有,就会将此元素直接存储到当前位置上
-
如果算出的位置上有元素或者链表,它会再调用key的equals方法跟存在元素的key做比较
-
如果有一个比较得到的结果为true,则会进行值的覆盖,如果都为false,则会将元素追加在链表的末尾
-
为了降低Hash冲突和链表长度,HashMap还做了一些优化:
-
当元素的数量超过数组大小与加载因子的乘积时,就会执行扩容,扩容为原来的2倍,并将原来数组中的键重新进行hash运算,然后分配到新数组中
-
当链表的长度>8,并且数组长度>=64时,链表会转换为红黑树,当红黑树结点数小于6时将再次转回为链表
6、HashMap是怎么解决哈希冲突的
解决hash冲突最常用的方式有链表法和开放地址法
HashMap采用了链表法。
当哈希冲突出现后,HashMap会在发生冲突的位置上创建一个链表来保存元素。在JDK1.8之后,又对此做出了改进,那就是当链表的长度>8,并且数组长度>=64的时候,链表就会转换为红黑树,使得效率更高。
7、HashMap的扩容机制是怎样的
当HashMap中的元素个数超过数组长度乘以负载因子时,会重新分配一个更大的数组,将原来的元素重新计算哈希值并插入到新的数组中。
在JDK1.8中,底层是调用resize方法实现扩容的。
它的默认做法是:当元素个数超过数组长度的0.75倍时触发扩容,每次扩容的时候,都是扩容为原来的2倍, 扩展后Node对象的位置要么在原位置,要么移动到原偏移量两倍的位置。
8、为何HashMap的数组长度一定是2的次幂?
为了能让 HashMap 存取数据的效率更高,尽可能地减少 hash 值的碰撞,尽量把数据均匀分配,每个链表或者红黑树长度尽量相等。
-
计算索引时效率更高
-
扩容时重新计算索引效率更高
9、说一下HashSet的实现原理?
是基于HashMap实现的,HashSet的值存放于HashMap的key上
相关 HashSet 的操作,基本上都是直接调用底层 HashMap 的相关方法来完成
由于HashMap的键是不能重复的,所有HashSet 不允许重复的值。
10、HashSet如何检查重复?
通过哈希表来实现,具体做法是:
-
在向HashSet中保存元素的时候,会先计算该元素的哈希值,确定他在数组中的位置
-
如果该位置上没有元素,则保存成功,如果有元素,则调用equals方法去跟存在的每个值进行比较
-
比较结果有一个相等,则直接丢弃;不相等,就存到HashSet中(HashSet底层不会产生链表)
3.多线程高频面试题
1、创建线程有几种方式
继承Theard类,重写里面的run方法,这种方式写起来简单,但是局限于只能单继承,不利于扩展
实现runnable接口重写润,避免了单继承的局限性,编码更灵活,实现解耦
实现callable接口重写call方法,这种方式可以获得线程的返回值,并且可以抛出异常
创建线程池
2、runnable和callable的区别
都是线程任务类的接口
Runnable接口run方法无返回值;Callable接口call方法有返回值,需要获取线程类的执行结果,必须要使用Callable,如果不需要返回结果,用Runnable会简单一些 Runnable接口run方法只能抛出运行时异常,无法捕获处理;Callable接口call方法允许抛出异常,也可以获取异常信息
3、start和run的区别
run(): 本质上就是一个普通方法,可以被调用多次,start(): 用来启动线程,start方法只能被调用一次,底层会自动去执行run方法中的代码,也就是启动线程的时候,只能调用start方法,如果调用的run方法,不会启动线程,只会当成普通方法调用执行
4、notify和 notifyAll的区别
唤醒被wait方法休眠的线程,notifyAll:唤醒所有wait的线程,notify:随机唤醒一个 wait 线程
5、sleep 和 wait 的区别
sleep和wait都是Java中用来让线程暂时放弃CPU使用权,进入阻塞状态的方法。他们的主要区别点有下面几个:
wait是Object的成员方法,wait会释放锁,wait需要其他线程的唤醒,必须用在synchronized代码块中,而sleep无此限制sleep是Thread 的静态方法,不会释放锁,会在指定的时间后自动苏醒
6、说一下线程的状态及转换
在我的理解中,线程共分为7种状态,分别是:新建、就绪、运行、终止以及阻塞、等待、计时等待
它们之间的转换关系是这样的:
-
当线程new出来之后,没有start之前就会处于新建状态
-
当线程执行start方法之后,就进入就绪状态
-
当就绪的线程一旦获取到了cpu的执行权,就可以进入运行状态
-
当线程执行完了run方法之后,就进入了死亡状态
这是一条正常的流程,但是代码在运行状态下可以因为一些原因进入到其它状态,比如说:
-
当进行抢锁操作时,抢锁失败就会进入到阻塞状态
-
当代码调用了wait方法时,就会进入等待状态
-
当代码调用了sleep方法时,就会进入计时等待状态
这是我对线程状态及其转换的理解
7、现在有T1,T2,T3三个线程,如何保证它们按顺序执行?
有很多种方法,最简单的方式就是使用线程类的join方法实现
join方法是Thread类中的一个方法,它的作用是将当前线程挂起,等待其他线程结束后再执行当前线程
具体来说就是:可以在t2之前调用t1.join(),在t3之前调用t2.join()
8、synchronized的实现原理是怎样的
基于 Monitor 机制实现,每一个对象都有一个与之关联的监视器 (Monitor)
-
当一个线程想要访问某个对象的 synchronized 代码块,首先需要获取该对象的 Monitor。
-
如果该 Monitor 已经被其他线程持有,则当前线程将会被阻塞,直至 Monitor 变为可用状态。
-
当线程完成 synchronized 块的代码执行后,它会释放 Monitor,并把 Monitor 返还给对象池,这样其他线程才能获取 Monitor 并进入 synchronized 代码块
synchronized--锁方法 == synchronized(this)锁对象
synchronized(userId.intern()--同一个字符串用同一个对象接收)--两个线程可以同时进入(张三和李四)但两个张三不能同时进,防止恶意攻击
9.项目中多线程的使用(线程池)
文章内容审核时(IO密集型)
同一个微服务使用多线程,使用Spring提供的线程池
-
在方法上加异步注解@Async
-
在类上加@EnableAsync注解开启支持
用了ThreadPoolTaskExcutor线程池,提供的默认参数不合适 因为他的核心线程和最大线程数很离谱 所以自己在config中定义一个线程池
核心线程数(通过IO/CPU密集型设置)
4.线程池高频面试题
1、线程池的执行流程
1.当我们的任务进到线程池,首先会判断核心线程是否都在执行任务,如果不是就会创建新的核心线程,
2.如果核心线程满了会先进入等待队列,队列未满进入队列,队列已满,判断线程数是否小于最大线程数
3.如果小于就会创建临时线程临时线程执行任务
4.当最大线程已满就会执行拒绝策略
2、线程池的核心参数
核心线程数
最大线程数
临时线程存活的空闲时间
临时线程存活的时间单位
工作队列
创建线程的工厂
拒绝策略
3、线程池的拒绝策略有哪些
默认策略:直接抛出异常AbortPolicy()(额不跑了谁)
直接丢弃任务 DiscardPolicy (迪斯卡跑了谁)
丢弃队列中最靠前的任务,执行当前任务 DiscardOldestPolicy
用调用者所在的线程执行任务 CallerRunsPolicy (卡了润跑了谁)
4、线程池的阻塞队列有哪些
当核心线程都在执行任务就会让新任务进入队列
-
ArrayBlockingQueue(额瑞不老king扣):基于数组结构的有界阻塞队列
-
LinkedBlockingQueue(林克的不老king扣):基于链表结构的无界阻塞队列
-
PriorityBlockingQueue:具有优先级别的阻塞队列
-
SynchronousQueue:不存储元素的阻塞队列,每个插入操作都必须等待一个移出操作
5、submit和execute方法的区别
都是线程池中用于提交线程任务的方法,区别点在于:
-
两个方法的参数都可以是Runnable对象;submit方法的参数还可以是Callable对象
-
submit可以返回一个Future对象,从这个对象中可以拿到线程运行结果,execute没有返回值
-
submit能够控制异常,在主线程中通过 Future 的 get 方法捕获线程中的异常;execute不可以
6、了解Executors创建线程池吗
Excutors是JDK提供的一个可以创建线程池的工具类,可以直接创建出四种
-
创建单线程化的线程池
-
创建一个定长线程池
-
创建一个可缓存的线程池
-
创建固定大小的线程,延迟或定时执行任务
但是用它创建的线程池有的没有限制最大线程数,有的没有限制阻塞队列的长度,这样的话,极大可能导致OOM(内存用完了)
因此我们公司不允许我们使用它,而是使用自己传递参数的方式创建线程池
7、如何确定线程池的核心线程数
看我们的任务是哪种类型
对于IO密集型的,任务需要大量的IO,线程太少的话容易造成阻塞,所以需要配置较多的线程推荐CPU核数的2倍
要是算数密集型的,就不需要那么多的线程 推荐是CPU核数+1,就足够让每个任务都有线程去执行
5.Mybatis高频面试题
1、Mybatis中#{}和${}的区别
在Mybatis中#{}和${}都可以用于在sql语句中拼接参数
Mybatis在处理${}时,会直接将${}替换成变量的值,${}表示的是字符串拼接,${}处理的sql到数据库每次都要重新编译,${}存在SQL注入问题
而#{}是预编译处理,Mybatis在处理它时,会将sql中的#{}替换为?号,通过底层完成预编译赋值,#{}处理的sql只需要编译一次,#{}可以有效的防止SQL注入
尽量使用#{},而避免使用${},但是在sql中需要动态传递表名或者字段名,那就只能使用${}
2、当实体类中的属性名和表中的字段名不一样 ,怎么办
是这样的,当实体类中的属性名和表中的字段名一样的时候,Mybatis会自动完成查询结果的映射
但是如果不一样,Mybatis默认无法完成结果映射,此时我们可以使用下面这几种方案:
1、开启驼峰映射:这种方式可以处理掉字段和属性满足驼峰转换规则的那部分
2、字段起别名:可以在查询的SQL语句中定义字段名的别名,让字段名的别名和实体类的属性名一致
3、手动映射:mybatis提供了resultMap标签,它可以通过来自定义映射关系来保证字段和属性的映射
3、MyBatis动态SQL了解吗
在我们的mapper层通常以注解的方式,去对数据库完成操作,但是他只能完成简单的sq它无法完成复杂的sql语句,动态sql就能解决复杂的sql语句,他可根据不同的需求,以标签的形式完成sql的拼接
条件判断标签:if、choose、when、otherwise 当条件成立时才执行其中的 SQL 语句 格式整理标签:trim、where、set 它可以在生成的SQL语句中调整格式,去除多余的关键字和符号 循环遍历标签:foreach 它用于遍历一个集合并将集合中的元素添加到 SQL 语句中
动态 SQL 的执行原理是,当 MyBatis 执行动态 SQL 语句时,会将 SQL 语句和参数传递给 SQL 解析器进行解析SQL 解析器会根据 SQL 语句中的动态标签和参数的值,生成一个完整的 SQL 语句然后,MyBatis将生成的SQL语句和参数传递给 JDBC 驱动程序进行执行
4、Mybatis一级缓存和二级缓存
一级缓存(默认开启):同一个session下执行相同方法,sql语句相同 --SQL session级别缓存
二级缓存(默认关闭):同一个名称空间下<namespace>--同一个mapper下 不管有多少个session 执行的SQL语句相同的话 走二级缓存
6.Spring高频面试题
1、谈谈你对SpringIOC的理解
IOC的思想是控制反转,将对象的创建和他们之间的依赖关系交给spring管理
2、Spring中有哪些依赖注入方式
构造器注入
setter方法注入
3、你用过哪些Spring注解
@controller @component @repository @service @Autowired
@configuration @Bean @ComponentScan
用于切面编程的@Around(额入昂的)
4、SpringBean的作用域有几种
大致有五种,常用的的有两种
单例作用域,他是随着spring的创建而创建,也会跟着一起销毁,每次获取到的都是同一个,也是默认作用域
多例作用域,每次获取的时候都回创建一个新的,每次获取到的都不是同一个
还有三种不常用的request、session和application
虽然单例比多例节省空间,但是在多线程的情况下单例有些地方是不安全的
5、Spring中的bean线程安全吗
在多例的情况下线程安全的,因为每次获取到的都不是同一个对象,线程之间不存在Bean共享问题,他们之间不会相互干扰
在单例的情况下才会考虑线程安全的问题,在单例又分为有状态和无状态,有状态是对数据的更新或者删除会改变
原有的数据,就会有线程安全的问题,无状态是对数据只进行查询的操作不会修改原有的数据就不会有线程安全的
问题
解决办法就是将单例改为多例,
将需要的可变成员变量保存在ThreadLocal中, ThreadLocal本身就具备线程隔离的特性,这就相当于为每个线程提供了一个独立的变量副本,每个线程只需要操作自己的线程副本变量,从而解决线程安全问题
6、谈谈你对SpringAOP的理解
他是面向切面编程,将我们的业务代码和增强代码(日志功能)分离写,使用的时候再结合到一块,可以提高代码的复
用性,降低代码之间的耦合度,并有利于扩展和维护,Spring AOP是基于动态代理的,它底层同时支持JDK和
CGLIB的代理方式,并且会根据被代理类是否有接口自动选择最合适的代理方式
7、AOP的代理有几种方式
jdk代理方式:对实现接口的类进行代理
cglib代理方式:可以对任意类进行代理
jdk的代理方式比cglib的代理方式效率高,如果被代理类有接口,就用JDK;如果没有接口,就用CGLIB
8、Spring的通知类型有哪些
通知是个在方法执行前或执行后要做的动作,(程序执行时要通过SpringAOP框架触发的代码段)
前置通知:方法执行前的通知
后置通知:方法执行后的通知
异常后通知:方法执行异常的通知
返回后通知:方法退出后的通知(无论是否有异常)
环绕通知:对一个方法的所有通知
9、了解Spring的事务管理吗
编程式事物:在代码中用trycatch捕获异常 进行对事务的管理,这种方法写的代码复用性低,编写起来也比较麻
烦,但能够精确控制增强的内容
声明式事物:是用的AOP的思想,代码分开写,通过动态代理的方式对方法增强进行事务管理,代码的复用性高,耦合性更低
10、Spring事务传播行为有几种
事务传播行为是为了解决业务层方法之间互相调用的事务问题。
当事务方法被另一事务方法调用时,必须指定事务应该如何传播。
-
必须事务:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务
-
必须新事务:创建一个新的事务,如果当前存在事务,则把当前事务挂起
-
强制事务:有就加入,没有就抛出异常
-
强制无事务:有就抛出异常
-
支持事务:有就加入该事务,没有就继续运行
-
不支持事务:有就挂起,没有就继续运行
-
嵌套事务:外层失败内层回滚,内层失败外层正常运行
11、Spring中的事务是如何实现的
基于数据库事务和AOP机制,spring使用了@Transactional(穿啥可惜nal)的Bean创建代理对象,当调用代理对象的方法时,会先判断该方法上是否加了@Transactional注解。如果加了,那么则利用事务管理器创建一个数据库连接,并且禁止此连接的自动提交事务然后执行当前方法,方法中会执行sql ,执行完当前方法后,如果没有出现异常就直接提交事务;如果出现了异常,并且这个异常是需要回滚的就会回滚事务,否则仍然提交事务。
12、@Transactional控制事务的原理
-
@Transactional 可以加属性:
-
事务传播行为 :必须 require
-
隔离级别 :默认-和数据库相对相应(mysql-可重复读)
mysql事务是自动提交的
-
原理:基于AOP来实现的
-
先从连接池中获取connection再把事务改成手动提交(获取到的connection会放入到ThreadLocal中)
-
整个方法执行完 有问题rollback 没有问题再commit提交(保证这个方法中共用的是同一个connection)
-
connection改为自动提交后再还回连接池中,并把ThreadLocal中的connection移除掉
-
分布式事务:涉及多个数据源
不能再用ThreadLocal了而是要用一个事务调节器:Seata
让Seata管理从多个数据源来的connection
12、Spring中的设计模式有哪些
-
工厂模式:创建 bean 对象
-
单例模式: 默认都是单例的
-
代理模式:AOP 功能用到了 JDK 和 CGLIB 的动态代理方式
-
模板方法:用来解决代码重复的问题。比如 RestTemplate
-
观察者模式
13、Spring是怎么解决循环依赖的(待整理)
简单来说就是a依赖b b依赖a ,a和b之间相互注入,导致它们之间形成了一个闭环,无法继续执行下去的情况
-
解决的话利用了三级缓存:
一级缓存:放存储初始化完成的实例
二级缓存:放未初始化完成的实例
三级缓存:放用来创建对象的工厂(ObjectFactory)
如果加了@Async会报错 因为它也会产生一个对象 所以a或者b上加@Lazy注解
14、SpringBean的生命周期
需要实现一些接口
主要由五个部分构成:
-
构造Bean对象:相当于new对象
-
设置Bean属性:给属性赋值
-
初始化回调:加上@PostConstruct的方法称为初始化方法,有就执行,没有就不执行
-
Bean调用:进行调用,要用对象的话从ConcurrentHashMap中取值,要是需要被代理,用AOP生成代理对象 最终生成的Bean存到单例池中
-
销毁Bean:在容器关闭之后销毁Bean加上@PreDestroy
15、SpringMVC执行流程
MVC 是 Model — View — Controler 的简称,它是一种架构模式,它分离了表现与交互。它被分为三个核心部件:模型、视图、控制器。
具体流程如下所示:
1、用户发送出请求到前端控制器DispatcherServlet。
2、DispatcherServlet收到请求调用HandlerMapping(处理器映射器)。
3、HandlerMapping找到具体的处理器(可查找xml配置或注解配置),生成处理器对象及处理器拦截器(如果有),再一起返回给DispatcherServlet。
4、DispatcherServlet调用HandlerAdapter(处理器适配器)。
5、HandlerAdapter经过适配调用具体的处理器(Handler/Controller)。
6、Controller执行完成返回ModelAndView对象。
7、HandlerAdapter将Controller执行结果ModelAndView返回给DispatcherServlet。
8、DispatcherServlet将ModelAndView传给ViewReslover(视图解析器)。
9、ViewReslover解析后返回具体View(视图)。
10、DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
11、DispatcherServlet响应用户。
16、SpringMVC的常用注解有哪些
我们常用的Springmvc注解主要分类下面几大类:
-
用于声明Bean到Springmvc容器:
@RestController=@Controller+@ResponseBody
将返回的集合或对象转换为JSON
-
设置请求路径:通用的@RequestMapping、
@GetMapping、@PostMapping 、@PutMapping、@DeleteMapping
-
接收请求参数:
-
@RequestBody: 接收请求体中的json数据
-
@PathViriable:接收请求路径中的参数
-
@RequestHeader:接收请求头中的参数
-
@RequestParam:一般用于给参数设置默认值或者完成请求参数和controller方法参数的映射
17、SpringMVC如何处理统一异常
我们会自己定义一个类在类上标注@RestControllerAdvice 声明被标注的类是一个用于专门处理异常的类(全局
异常处理器),在业务代码中不需要在处理异常,有异常直接就上抛到框架中,框架就会将异常交给全局异常处理器
中统一处理,用@ExceptionHandler 标注在异常处理类中的方法上,就可以明确处理哪些异常
预期内异常:精准处理
非预期异常:统一处理
不能捕获拦截器 只能捕获controller
7.SpringBoot面试题
1、项目中为什么选择SpringBoot
SpringBoot简化了Spring,开发效率更高,它的主要优点如下:
-
版本锁定:在父工程中进行了大量常见依赖的版本锁定,我们就不用去找了
-
起步依赖:将依赖进行组装,并且允许程序员以starter的方式进行引入
-
默认配置:实现了大量依赖框架的默认配置项,不需要自己配置
-
内置Tomcat
缺点:SpringBoot可能会装载大量用不到的对象,会浪费资源空间
2、SpringBoot的自动装配原理
Springboot自动装配主要是基于注解编程和约定优于配置的思想来进行设计的
自动装配就是自动地把其他组件中的Bean装载到IOC容器中,不需要配置文件中添加大量的配置
-
开启自动装配:启动类上加@SpringBootApplication
-
让启动类具有配置类功能:@SpringBootConfigration
-
将自己写的类对象进行包扫描创建交给IOC容器:@ComponentScan
-
它底层最重要的是通过@EnableAutoConfiguration来实现的,作用是:读取所有jar包中两个指定配置文件中的所有自动配置类(xxxxAutoConfiguration)
-
条件装配:@Conditional(康特系no)声明配置成立的必要条件
3、SpringBoot的核心注解是哪个
在启动类上:@SpringBootApplication
主要包含了以下3个注解:
-
@SpringBootConfiguration:实现配置文件的功能
-
@EnableAutoConfiguration:打开或关闭某个自动配置
-
@ComponentScan:默认扫描启动类所在的包
4、SpringBoot中的starter是干什么的
每个starter都可以为我们提供某个服务场景所需要的一系列依赖
在导入starter之后,SpringBoot主要帮我们完成了两件事情:
-
相关组件的自动导入
-
相关组件的自动配置
5、SpringBoot可以有哪些方式加载配置
常见的有三种:
-
配置文件:比如properties、yaml 、yml(常用)
-
系统环境变量:但不推荐
-
命令行参数:一般用于临时修改配置
6、bootstrap.yml和application.yml有何区别
都是SpringBoot支持的核心配置文件,区别在于:
-
boostrap比applicaton优先加载,在应用程序上下文的引导阶段生效,且里面的属性不能被覆盖 一般来说我们在SpringCloud Config或者Nacos中会用到它
一般用于多环境配置
-
application用于SpringBoot项目的自动化配置,一般来说我们会将自己项目的业务配置项写在这里面
7、SpringBoot读取配置的方式有几种
常见的有两种:
-
使用@Value配合EL表达式(@Value(“${name}”)) 直接注入对应的值
-
使用@ConfigurationProperties(prefix=" ") 把对应的值绑定到一个配置对象,然后将配置对象注入到需要的地方
推荐使用使用第二种方式,在配置比较多的情况下,操作简单,可读性好
8、SpringBoot项目如何热部署
Spring Boot有一个开发工具(DevTools)模块,通过它可以实现SpringBoot项目的热部署
也就是开发人员将文件更改后,它会自动部署到服务器并自动重启服务器。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency>
9、SpringBoot项目如何实现方法的异步调用
异步调用指的是两个方法a与b,a调用b的时候,不用等b执行完毕就可以继续向下执行,一般用在a方法不需要使用b方法返回结果的场景,可以提高一定的运行效率
在SpringBoot只需要做两个操作就可以实现异步调用:
-
在启动类上添加@EnableAsync注解,开启异步调用支持
-
在被调用的方法上添加@Async注解
controller-a(){ 代码1 service-a() 代码3 } @Async service-a(){}
10、SpringBoot中如何实现定时任务
主要有两种方式:
-
使用第三方框架Quartz
-
使用SpringTask:主要是通过@Scheduled注解来实现定时任务触发
主要属性如下:
-
fixedRate:按一定的频率执行任务,参数类型为long,单位 ms
-
fixedDelay:上一次任务执行完后多久再执行,参数类型为long,单位 ms
-
initialDelay:延迟多久再第一次执行任务,参数类型为 long,单位 ms
-
cron:使用cron表达式指定任务在特定时间执行
集群部署用xxl-job来实现定时任务--分布式锁借助Mysql来实现:两个微服务同时向表里插入数据,谁插入成功谁来执行定时任务
11、SpringBoot中如何解决跨域问题
基于前后端分离 出现域名、端口、协议任意一个不同,都属于跨域
解决方法:添加一个配置类实现WebMvcConfigurer接口然后重写addCorsMappings方法即可
最后都是可以采用跨域资源共享CORS解决跨域问题
12、如何理解拦截器
拦截器是Spring提供的一种拦截机制,实现对指定请求路径进行拦截
-
单体项目中拦截器校验token,拦截用户信息存入ThreadLocal
-
微服务在网关校验,获取用户ID放到请求头中传入后面微服务,微服务中拦截器拦截请求获取用户信息,将其放入ThreadLocal中
自定义一个拦截器需要实现HandlerInterceptor接口并重写3个方法:
-
preHandle: 这个方法在Controller处理请求之前被调用,通过方法的返回值可以确定是否放行请求
-
postHandle:这个方法在Controller处理请求之后被调用
-
afterCompletion:这个方法将在整个请求结束之后被调用,此方法主要用于进行资源清理
13、拦截器和过滤器的区别是什么
都可以实现请求的拦截处理,不同点有4个:
-
技术栈所属不同:过滤器属于JavaWeb技术,依赖Servlet容器;而拦截器是属于Spring的技术
-
实现原理不同:拦截器是基于Java的反射机制,而过滤器是基于函数回调
-
拦截范围不同:过滤器可以拦截所有请求,而拦截器主要是针对发往controller请求
-
拦截位置不同:过滤器在前端控制器前拦截行,而拦截器在前端控制器后拦截
8.SpringCloud高频面试题
1、SpringCloud组件有哪些
-
Eureka : 注册中心
-
Ribbon : 负载均衡
-
Feign : 远程调用( 封装了RestTemplate)
-
Hystrix : 服务熔断
-
Gateway : 网关(解决跨域问题采用跨域资源共享CORS、路由和鉴权--过滤器)
-
注册中心/配置中心: Nacos
2、Feign工作原理
他就是来做远程调用的,工作步骤如下:
-
首先需要在SpringBoot的启动类上添加@EnableFeignClients 注解开启对Feign的支持
-
创建接口并加上@FeignClient注解,其中的方法必须要和即将调用的controller方法保持一致
-
运行时,根据接口动态生成代理类,在其中实现接口中的方法
-
最后封装后再结合Ribbon负载均衡发起服务之间的调用
3、什么是Hystrix
熔断器,是防止服务器发生雪崩的,它具有服务降级,服务熔断,服务隔离等一些防止雪崩的技术。
-
服务隔离:隔离服务之间相互影响
-
服务熔断:接口调用失败就会进入调用接口提前定义好的一个熔断的方法,返回错误信息
-
服务降级:接口调用失败就调用本地的方法返回一个空
4、Hystrix断路器状态有哪些
包括三个状态:
-
closed:关闭状态,断路器关闭,所有请求正常访问
-
open:打开状态,断路器打开,所有请求都被降级。在一定时间内,请求超过20次,失败的百分比达到阈值(默认50%),则触发熔断,断路器完全关闭
-
half-open:半开状态,open开启后会进入休眠(默认5s),之后断路器自动进入半开状态,判断请求的执行结果来切换close(请求成功)或者open(请求失败)状态
5、Ribbon的工作原理
他是做负载均衡的,大体使用流程如下:
-
当请求发出的时候,会被Ribbon的负载均衡拦截器所有拦截
-
Ribbon会提取请求路径中微服务的名称,然后查找他对应的服务地址
-
Ribbon会使用配置的负载均衡策略从众多地址中选择一个,进行调用,默认是轮询
6、Ribbon的负载均衡策略有哪些
我所熟悉的有:
-
轮询策略:按照一定的顺序依次调用服务实例(默认)
-
随机策略:从服务提供者的列表中随机选择一个服务实例
-
权重策略:根据每个服务提供者的响应时间分配一个权重,响应时间越长,权重越小,被选中的可能性也就越低。
7、Nacos的工作原理
在项目中主要用作服务注册中心和服务配置中心
-
Nacos做注册中心原理:
-
服务提供者会将自己的地址信息注册到Nacos中
-
服务消费者会从Nacos中查询服务提供者的信息,缓存到本地,并且每隔30s更新一次。当服务提供者的地址发生变化之后,Nacos也会主动推送最新的地址信息给消费者
-
服务提供者会间隔一定时间就给Nacos发一个信息,表明自己在线
-
当nacos一段时间内接收不到微服务的续约请求时或者收到微服务的下线请求时,就会将服务地址删除
-
Nacos做配置中心的原理:
-
可以将微服务经常改动的一些配置保存到Nacos中,然后在本地的bootstrap.yml中指定远程配置的位置信息
-
当Nacos的配置发生变化时,他会主动推送给微服务,微服务进行热更新
13.Redis高频面试题
1、项目中为什么用Redis
-
操作速度快:Redis的数据都保存在内存中
-
数据类型丰富:Redis支持 string,list,set,Zset,hash 等数据类型
-
使用场景丰富:Redis可用于缓存,消息队列,按 key 设置过期时间,过期后会自动删除
2、Redis的数据类型有哪些
Redis最常见的数据类型有5种,分别是String、List、Hash、Set、ZSet
-
String:字符串类型,场景:计数、缓存文章标题、微博内容等
-
List:底层是链表,特点是:增删容易,随机访问困难。场景:发布与订阅或消息队列
-
Hash:类似于Java中的HashMap,适合存储对象。场景:系统中对象数据的存储
-
Set:无序集合,场景:共同关注、共同粉丝、共同喜好等功能
-
ZSet:有序集合,且集合中每个元素关联一个分数,可以根据分数进行排序。场景:各种排行榜,弹幕消息
3、Redis为什么这么快
-
纯内存操作:Redis的绝大部分请求是纯粹的内存操作,非常快速
-
单线程:Redis的核心部分是单线程运行的,避免了不必要的上下文切换,也不存在线程切换导致的 CPU消耗
-
使用 I/O 多路复用模型:利用单个线程来同时监听多个Socket ,并在某个Socket可读、可写时得到通知,从而避免无效的等待,充分利用CPU资源
4、Redis的过期删除策略有哪些
指的是当Redis中的key过期之后在什么时候进行删除的处理方案
常用的删除策略就两个:
-
惰性删除:只会在取出 key 的时候才对数据进行过期检查,过期了就删除
-
定期删除:每隔一段时间抽取一批 key执行删除过期 key 操作
两者相比,定期删除对内存更加友好,惰性删除对 CPU 更加友好。 Redis 采用的是定期删除+惰性/懒汉式删除。
5、Redis的内存淘汰策略有哪些
指的是当Redis的内存已经存满,又有新的数据需要保存时的处理方案
默认的策略是报错:当内存不足以容纳新写入数据时,新写入操作会报错。
我所了解的有随机淘汰策略和淘汰最近最少使用或者最不经常使用的策略
6、Redis的RDB和AOF区别
-
RDB采用的是定期更新的方式,磁盘上保存的就是Redis的内存快照
文件较小,数据恢复速度较快,但存在丢失数据的风险
-
AOF磁盘保存的是所有执行的指令,在下次Redis重启时,只需要将指令重写一遍即可
相对完整但文件较大,数据恢复的速度较慢
在我们公司是同时开启RDB和AOF 持久化机制的,这样做的好处是:
Redis重启时先使用AOF日志进行恢复,然后再使用RDB快照进行备份 定期进行RDB快照的备份,以便在需要时进行全量数据的恢复,提高数据的可靠性和恢复能力,提高数据的持久化能力
7、RDB期间可以同时处理写请求吗
可以,bgsave快照备份时不耽误写,但save不行
具体来说,就是Redis在持久化时会产生一个子进程,快照持久化完全交给子进程来处理,父进程继续处理客户端请求
当主线程执行写指令修改数据的时候,这个数据就会复制一份副本, 然后修改副本中的数据,RDB结束后,主进程读取这个副本数据写到 RDB 文件
这既保证了快照的完整性,也允许主线程同时对数据进行修改,避免了对正常业务的影响
8、Redis集群有哪些方案
主要有三种,分别是主从、哨兵和分片集群,我也是会搭建的
-
主从集群主要用来解决Redis的并发问题,实现读写分离
一般是一个主节点负责写,多个从节点负责读,主节点的数据会实时同步给从节点
-
哨兵集群主要用来解决Redis的高可用问题
哨兵会监控集群中节点的状态,并在主节点出现问题时进行重新选主,但他不保存数据
-
分片集群主要用来解决Redis的海量数据存储和高并发写的问题--哈希槽
要求有多个主节点,然后写入的数据会经过计算落到其中一个上
现阶段我们Redis的哨兵模式即可,项目中用Redis存:
-
登录验证码
-
全局变量 :干线支线接驳路线--每公里成本数 分配任务延迟多长时间
-
refreshToken
-
运费模版
9、如何保存Redis数据与MySQL一致
常见的有三种:
-
同步双写:即在程序更新完MySQL之后后立即同步更新redis
-
异步监听:即通过Canal监听MySQL的binlog日志变化,然后再通过程序将变化的数据更新数据到 Redis
-
MQ异步:即程序在更新完MySQL后,发送一条消息到MQ中,然后在通过一个程序监听MQ,获取到消息,然后更新Redis
10、什么是缓存预热
提前将数据放进Redis中--先查Redis再查MySQL,避免高并发访问时,对数据库造成的压力。
缓存预热解决方案主要有下面几个:
-
数据量不大的时候,工程启动的时候进行加载缓存动作
-
数据量大的时候,设置一个定时任务脚本,进行缓存的刷新
-
数据量太大的时候,优先保证热点数据进行提前加载到缓存
11、什么是缓存穿透, 怎么解决
当查询redis中未存在的数据时就会去MySQL中查询,然后大量请求进入MySQL就会导致MySQL承受不了压力而宕机
常用的解决方案有:
-
对传入参数进行判断是否合法
-
对数据库未查询到的id信息也存入redis,这样下次查询就会从redis中获取
-
使用布隆过滤器:以较小内存为代价的,用于检测大量元素是否存在的算法
核心原理是二进制数组 + 哈希运算,可以通过Redisson、Guava、Hutool来实现
12、什么是缓存击穿,怎么解决
当redis中一个热点数据的过期时间到了,然后还有大量请求来访问这个热点数据,但redis已经对该数据做了过期处理,这个时候就会访问MySQL当大量请求进入MySQL,就会导致MySQL承受不了压力而宕机
常用的解决方案有:
-
不对热点数据设置过期时间
-
布隆过滤器
13、什么是缓存雪崩,怎么解决
当redis中的中的大量数据都是同一过期时间,当这些数据同时过期,这时候然后大量请求过来在redis中没有拿到数据,这些请求就会去MySQL中获取数据,导致MySQL承受不了压力而宕机
解决方案:给这些数据设置随机过期时间或者不进行过期处理
14、用过Redis的事务吗
没有用过,他本身没有事务概念 但一些命令组合起来可以起到事务的效果
14.mysql高频面试题
1.内连接和外连接的区别
这两种都是多表联查使用的的连接方式,区别是两种获取的数据是不同的,
内连接(笛卡尔积):指的是用左表的每一条数据去连接右表的每一条数据,显示匹配成功的部分
左外链接:先显示左表的全部,然后使用连接条件去匹配右表,成功就显示,不成功null;
右外链接:先显示右表的全部,然后使用连接条件去匹配左表,成功就显示,不成功null;
2、drop、delete与truncate区别
三种都是mysql中删除用到的关键字,在效率方面drop 最高、truncate 其次、delete最低,但是drop和truncate 都不记录日志,无法回滚
delete是删除表中的一行,并且会将删除的操作记录到事务的日志中保存以便回滚的操作
drop是删除数据表,表中的列,索引等结构
truncate(船K特)是直接把表删掉,重建表结构
3、union与union all的区别
union all会将所有的结果返回,union(有你嗯)只将不重复的结果返回
都是用于合并select语句查询结果的关键字,他会将前后两条select语句的结果组合到一个结果集合中
4、char和varchar的区别
在储存不定长度的字符串的时候建议使用varchar,使用varchar的时候尽量声明长度贴近实际长度,因为他是不定长的,在储存上会更加节省空间,但是他的查询效率会比较低.
在存储固定长度的字符串,例如身份证号、手机号建议使用char,因为他是定长的,不足的部分会用隐藏空格填充,所以他比较浪费空间,但是查询的效率比较高
注意:varchar(50)中50的涵义是最多存放50个字符,varchar(50)和varchar(200)存储hello所占空间一样
5、事务的四大特性
事务的四大特性指的是原子性、一致性、隔离性、持久性
原子性:事务是最小的执行单位,不允许分割,同一个事务中的所有命令要么全部执行,要么全部不执行 一致性:事务执行前后,数据的状态要保持一致,例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的 隔离性:并发访问数据库时,一个事务不被其他事务所干扰,各并发事务是独立执行的 持久性:一个事务一旦提交,对数据库的改变应该是永久的,即使系统发生故障也不能丢失
6、并发事务带来的问题
并发事务下,可能会产生如下的问题:
-
脏读:一个事务读取到了另外一个事务没有提交的数据
-
不可重复读:一个事务读取到了另外一个事务修改的数据
-
幻读(虚读):一个事务读取到了另外一个事务新增的数据
7、事务隔离级别
事务隔离级别是用来解决并发事务问题的方案,不同的隔离级别可以解决的事务问题不一样
读未提交: 允许读取尚未提交的数据,可能会导致脏读、幻读或不可重复读 读已提交: 允许读取并发事务已提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生 可重复读: 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生 可串行化: 所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,该级别可以防止脏读、不可重复读以及幻读。 上面的这些事务隔离级别效率依次降低,安全性依次升高,如果不单独设置,MySQL默认的隔离级别是可重复读
8、数据库三大范式
三大范式是指导设计数据库的原则
第一范式:表中的每一列不能再进行拆分,也就是每一列都应该是原子的 第二范式:一张表只做一件事,不要将多个层次的数据列保存到一张表中 第三范式:数据不能存在传递关系,也就是说可以通过其它字段推出来的字段没必要再存储 在现有的程序设计中认为第三范式是可以不遵守的,也就是通过添加冗余字段,来减少多表联查或计算,我们称为反三范式
9、索引的分类
-
主键索引:默认是聚簇索引,只有一个(Primary)(普瑞美瑞)
-
唯一索引:不允许重复(Unique)(优尼科)
-
普通索引:给经常用来查询的某个字段设置索引
-
组合索引:给经常一起用来查询的某几个字段设置一个索引(同时经常用到)
10、索引的创建原则
索引可以提高查询的效率,但是会影响增删改的效率,所以不是所有的字段都要加,也不是加的越多越好,因为索引会占据磁盘空间,
建立索引应该遵循一些原则
-
主键字段、外键字段应该添加索引
-
经常作为查询条件、排序条件或分组条件的字段需要建立索引
-
经常使用聚合函数进行统计的列可以建立索引
-
经常使用多个条件查询时建议使用组合索引代替多个单列索引
不应该建立索引的情况有数据量小的表不建议添加索引还有,不要在区分度低的字段建立索引,比如性别字段、年龄字段等
11、索引失效的情况
使用like关键字时,模糊匹配 %开头将导致索引失效 使用连接条件时,如果条件中存在没有索引的列会导致索引失效 计算、聚合函数、自动/手动类型转换会导致索引失效 使用 !=、not in、is null、is not null时会导致索引失效 使用联合索引时,没有遵循最左匹配原则会导致索引失效
12、如何知道索引是否失效 explain(伊克斯普林)
通过执行计划:explain,主要观察type的结果
如果出现all代表没走索引
我们在优化的时候尽量优化到range(晕机)级别以上
13、MyISAM和InnoDB的区别
不同的两种存储引擎
MyISAM(卖谁母)不支持事务,每次操作都是原子的,不支持外键,仅仅支持表级锁,即每次操作是对整个表加锁,属于非聚集性索引,它的数据和索引不在同一个文件中
InnoDB(一no布)支持事务,支持事务的四种隔离级别,支持外键,InnoDB支持行级锁,因此可以支持写并发,InnoDB属于聚集性索引,它的数据和索引在同一个文件中
14、查询语句执行流程
一条查询语句到达MySQL数据库之后:
-
连接器会负责建立连接、检查权限等操作
-
连接成功之后,会查询缓存,如果缓存中有结果会直接返回;如果缓存中没有结果,会将sql交给分析器处理
-
分析器检查sql的词法、语法,如果没有问题,再将sql交给优化器处理
-
优化器会决定用哪个索引,决定表的连接顺序等,然后将优化之后的sql交给执行器
-
执行器根据存储引擎类型,调用存储引擎接口
-
存储引擎负责最后数据的读写
15、索引的数据结构是什么
是B+Tree,B+树是基于B树的变种,它具有B树的平衡性,而且树的高度更低
特点是矮胖:层级较小,每层数据较多
数据存储在叶子节点上,非叶子节点只存储索引和指针
16、数据库中的锁有哪些
-
粒度上可以分为表锁和行锁
表锁指的是会锁定修改数据所在的整个表,开销小,加锁快,锁定粒度大,发生锁冲突概率高
行锁指的是会锁定修改数据所在的行记录,开销大,加锁慢,锁定粒度小,发生锁冲突概率低
-
概念上有悲观锁和乐观锁
悲观锁:认为并发事务之间可能会经常发生冲突。为了防止其他事务对其进行修改,会强制使用锁来保证数据安全,它会阻塞其他事务的访问
代码块、synchronized锁对象(不会自动释放)、lock(锁代码块或对象、unlock之后才能释放锁)
乐观锁:认为多个事务在并发执行时不会经常发生冲突。所以一般就是先执行了再说,最后再看是提交还是回滚
17、MySQL日志类型
常见的日志有:
-
Binlog归档日志:用于做数据同步,记录主数据库的增删改查操作,一般用在主从复制过程中记录日志
-
Redolog重做日志:存储未提交的数据,用于确保事务的持久性
-
Undo log :放可以回滚的日志
18、MySQL主从复制的流程(主:写/从:读)
其实就是主节点向从节点同步数据,主要是依靠MySQL的binLog实现的,大体流程分为三步:
-
主库事务提交时,会把数据变更记录在BinLog日志中
-
读取主库的 Binlog日志 ,写入到从库的RelayLog日志中
-
重做RelayLog日志中的事件,将改变反映它自己的数据
19、谈谈你对sql的优化的经验
-
大体分为三步:
-
查找问题sql,开启mysql的慢查询日志,它会将执行时间较长的sql记录记录下来
-
找到sql之后,分析出现问题的原因,主要有字段类型选择错误、sql语句效率低、索引失效等等
-
根据问题不同,再去定具体的解决方案
-
几个常见问题:
1)确定选择的引擎是否合适:
myisam:查询为主,不支持事务和外键
Innodb:支持事务和外键,或者包括很多的更新和删除的数据表
2)表设计是否合理:
单表不要有太多字段,建议在20以内,超过20个分到另一张表中
合理的加入冗余字段可以提高查询速度
3)确定字段的数据类型是否合适:
-
数值型字段的比较比字符串的比较效率高得多,字段类型尽量使用最小、最简单的数据类型
-
设置合适的字符串类型(char和varchar)char定长效率高,varchar可变长度,效率稍低,varchar的长度只分配真正需要的空间
-
尽量使用TIMESTAMP而非DATETIME,尽量设计所有字段都得有默认值,尽量避免null
4)确定sql的书写是否有的题:
-
SELECT语句务必指明字段名称,避免直接使用select *
-
SQL语句中IN包含的值不应过多
-
可以用内连接,就尽量不要使用外连接
-
使用连接连接查询来代替子查询
-
适用联合(UNION)来代替手动创建的临时表
-
表数据比较多的时候是否添加了合适的索引
-
表的主键、外键必须有索引
-
经常出现在where子句中的字段,特别是大表的字段,应该建立索引
-
经常用于排序、分组的字段,应当建立索引
-
加上索引之后,还应该使用执行计划Explain来确认索引是否生效
如果上面的几项都没有问题,那可能就是因为服务器性能,或者数据量过大导致的查询慢,此时可以考虑读写分离
也就是我们搭建一个MySQL的主从集群,让1个主节点负责写入数据,多个从节点负责查询数据,已分摊查询压力
15.优化相关问题
一、并发编程相关
1.程序 进程 线程
进程:一个程序运行起来就是一个进程
线程:一个进程中可以执行多个任务,每个任务就是线程
2.join、yield方法有什么区别
join方法:thread类中的方法,当前线程挂起,执行完其他的在执行当前的
yield方法:线程等待,相同或优先级别更高的线程线程来执行。相当于让出线程的占有权,但让出的时间不能设定
3. 什么是代码重排
指编译器和处理器为了优化程序性能而对指令序列进行重新排序的手段
4.用户线程和守护线程有什么区别
用户线程:就是普通线程
守护线程:在start方法前执行一个thread方法就变为守护线程,守护线程最典型的应用就是 GC (垃圾回收器),如果JVM中仅仅剩下了守护线程,那么JVM会自动退出
5.同步方法和同步块,哪个是更好的选择?
同步块是更好的选择,因为它不会锁住整个对象(也可以让它锁住整个对象)。同步方法会锁住整个对象,可能会导致他们停止执行并需要等待获得这个对象上的锁。
-
同步方法是指在方法上添加 synchronized 关键字,可以保证同一时间只有一个线程访问该对象的同步方法。同步方法的好处是使用简单,代码量少,但是锁的范围较大,当多个线程需要同时访问一个对象的不同方法时,会导致性能下降。
-
同步块是指使用 synchronized 关键字来锁定一个代码块,可以灵活控制锁的范围。同步块的好处是锁的范围更小,可以提高并发性能,但使用复杂,需要手动控制锁的范围
6.Lock和synchronized的区别
synchronized:关键字,修饰方法和代码块,有异常会自动释放锁
Lock:接口,只能修饰代码块,需要在finally中释放锁,性能高,不会锁住整个对象
7. volatile关键作用
-
变量可见:多个线程发生改变,其他线程可以马上发现
-
防止指令重排
在设置变量时加在变量前:public volatile static 变量
二、JVM相关优化
8. Minor GC(新生代)与Major GC(老年代)分别在什么时候发生?
Minor GC:年轻代空间不足时,会触发老年代
Major GC:老年代空间不足时,会先尝试触发年轻代,若之后空间还不足,会触发老年代
9. 哪些情况会触发类加载机制?
-
在遇到 new、putstatic、getstatic、invokestatic 字节码指令时,如果类尚未初始化,则需要先触发初始化。
-
对类进行反射调用时,如果类还没有初始化,则需要先触发初始化。
-
初始化一个类时,如果其父类还没有初始化,则需要先初始化父类。
-
虚拟机启动时,用于需要指定一个包含 main() 方法的主类,虚拟机会先初始化这个主类。
10. 哪些变量可以作为GC Roots
-
虚拟机栈中 (栈帧中的本地变量表) 引用的对象
-
方法区中类静态属性引用的对象
-
方法区中常量的引用对象
-
本地方法栈中引用的对象
11. Java中都有哪些引用类型?
-
强引用:发生 gc 的时候不会被回收。
-
软引用:有用但不是必须的对象,在发生内存溢出之前会被回收。
-
弱引用:有用但不是必须的对象,在下一次GC时会被回收。
-
虚引用(幽灵引用/幻影引用):无法通过虚引用获得对象,用途是在 gc 时返回一个通知。
16.额外题(待添加)
1、面向对象的特征
其实就是封装、继承和多态
封装:把数据和方法封装在一个类中,控制访问权限,只保留对外的接口
继承:创建子类继承父类的属性和方法,在此基础上扩展修改,也可以添加自己的
多态:方法的重写和重载
2、@Autowired 与@Resource区别
@Autowired :按类型装配(用名称装配结合@Qualifier注解)
@Resource:按名称装配
3、多态怎样实现
继承:子类对方法重写
接口:实现接口并覆盖接口中方法
实现的三个必要条件:
继承关系、子类重写父类方法、父类引用指向子类对象
4.视图是一个“虚表”,视图的构造基于什么?
基于基本表或视图,作用:
-
隐藏表的一些列
-
封装复杂的SQL
5.删除正在循环遍历的集合方法
不能删除正在循环遍历的集合
-
先将要删除的存到大集合中,循环遍历后删除大集合
-
迭代器(iterator)
-
stream流(removeIf)--底层是迭代器
6.minio或者阿里云在上传文件的过程中有可能出现大量无效的文件,怎么处理?
-
用文件URL地址与数据库中地址作比较,若找到说明有用不能删除,没找到说明可以删除(不考虑资源或者实现的问题)
-
上传图片后返回URL地址,将URL地址存入表中后,将Redis中的URL删除。可以做定时任务,在指定时间删除Redis中的地址
7.请分别列举出String中的方法(至少5个)和Object中的方法(至少3个)?
String:切割、截取、长度、替换replace 替换全部replaceall
Object: equal方法 wait方法 hashcode方法 notify方法
17.中州养老面试题
1.聊一下最近做的这个养老项目?
好的,面试官!
我最近做这个项目是一个单体项目,主要是给养老院使用的一个后台管理系统。它有两端:
-
第一个是给养老院员工使用的后台管理系统
-
第二个是给老人的家属使用的小程序端
这个养老院的功能是比较全的,包含了老人的预约参观、入住、退住、计费、老人健康数据监测等很多的模块
在这个项目的开发过程中,我主要负责的是项目中核心模块的设计和开发
介绍下苍穹外卖中的套餐模块
套餐模块使用的角色分为管理者和用户 管理者通过电脑对套餐的信息进行修改,用户则会在小程序中同步到修改的信息 在套餐模块中管理者可以新增套餐,对套餐中的菜品进行添加,同时还可以备注套餐的精美评语, 让用户对套餐有跟进一步的了解,同时支持多种不同的套餐样式,让用户有更多的选择,套餐模块中的搜索功能包括对套餐名称的模糊搜索, 和售卖状态的搜索以及套餐的分类,让管理者在操作套餐更加的方便 而用户在小程序使用时,套餐模块做了滑动效果,用户在使用过程中更加的灵活,但是因为用户在选择套餐时,会进行套餐的比对, 所以会发送大量的请求,且每次请求都会向数据库发起查询,所以我们在套餐模块中添加了Redis的缓存技术,这样会降低查询数据库的压力,并且效率会更好。
2.你主要负责了哪些模块?
在这个项目中的模块是很多的,我主要负责的是项目中的基础数据维护,比如在养老院中有房型管理、床位管理、护理等级管理、入住管理、在住管理这些模块。
我主要的开发工作就是设计这些模块的表结构、有的时候需要与前端开发配合,也去设计这些功能接口
面试官:那你详细说一下护理等级模块这个模块
护理等级这个模块在养老院系统的主要作用是,让将要入住的老人进行选择配置,比如有一个老人入住养老院,在前期呢,会先给老人做一个健康评估,然后销售员会根据老人的健康评估报告以及老人的年龄这些综合因素,去推荐老人选择不同的护理等级,不同的护理等级包含的护理项目是不同的,价格也是不一样的
面试官:那你详细说一下床位管理模块这个模块
床位管理这个模块在养老院系统的主要作用是,基础数据的维护,比如根据养老院实际的情况来创建对应的楼层,楼层中的房间对应编号,以及每个房间对应的床位编号,并且每个楼层的房间和床位对应的配置也是不同的。老人也可以根据自己的实际情况来选择需要入住的房间和床位。这样的话,老人就能与房间和床位进行绑定,后期方便更好的服务于老人
3.项目组成员有哪些?
当时,我们项目组大概有8-9个人左右吧 - 后端开发3人 - 前端开发1人 - 测试2人(多项目组公共的) - 项目经理1人 - 产品经理1人 - UI设计1人(多项目组公共的)
4.如何创建接口,你设计过接口吗?
首先需求确定之后,我们会根据产品原型和需求文档来设计接口,我们项目中是遵循了Restful风格的规范。主要包含了几个部分 请求路径:一般都是以资源名称或者小模块名称进行命名 请求方式 查询是get请求 新增是post请求 修改是put请求 删除是delete请求 接口的入参,一般也有两种形式 其中get请求会使用问号传参或者是path路径传参,后端使用形参接收参数 如果是put或者post请求会使用json对象传参,后端需要使用dto去接收参数 接口的出参,我们项目中定义了统一的接口规范 如果返回数据较多,一般会封装一个vo进行返回,vo可以过滤敏感数据,或者是整合数据
5.项目是如何进行异常处理的
在我们的项目中,异常处理都是通过spring的全局异常处理器来实现的,核心是两个注解: 一个是@RestControllerAdvice,标注在类上,可以定义全局异常处理类对异常进 行拦截 一个是@ExceptionHandler,标注在异常处理类中的方法上,可以声明每个方法 能够处理的异常类型 在我们的项目中,将异常分为了三大类: 第一类是指定异常: 指定异常指的是用户操作产生的与程序设计相关的异常,比如说字段重复异常 第二类是业务异常处理: 业务异常是由于用户不正当操作产生的与业务相关的的异常,这种异常往往需要我们自定义,然后在程序的相关位置手动抛出, 在抛出的时候还会指定异常提示信息。然后异常处理器捕获之后,直接将异常提示消息返回给前端 第三类是第三种异常时兜底异常: 此处主要捕获的是不属于上面两种异常的异常,一般是一些程序员代码不够严谨引发的运行时异常,对于这些异常,我们处理方案是首先要把错误记录到日志系统中,然后给前端一个类似于服务器开小差了之类的统一提示
6.git在工作中的使用流程
1. 首先,每天上班之后,我会从公司远程仓库中进行拉取(pull),以保证本地项 目和远程仓库项目进度一致 2. 然后,在本地的开发分支上新建一个当天的分支,进行代码开发 开发过程中一般在完成某功能或某一模块时,进行本地提交(commit) 3.当我们完成个完整模块的开发后,会将本地新分支的代码合并到开发分支 4. 最后,提交(push)到远程仓库前,先进行拉取(pull),如果有冲突,就先进 行冲突解决,解决完毕之后,再push
7.聊一下小程序端的有哪些业务模块?
好的~ 小程序是家属端,是让老人的家属使用的,在小程序上家属可以通过微信登录,完成登录过后,可以进行操作,比如,可以预约参观,预约探访,购买服务,管理合同等,查看房型等操作。 我在小程序端主要负责的是微信登录和预约这个模块。
8.说一下微信登录的流程?
好! 微信登录需要前后端共同写作完成开发。 首先前端需要调用微信端来获取一个临时登录的凭证,然后就可以向后端发起登录请求了 后端接收到登录请求后,获取到临时登录凭证,然后携带着这个凭证到微信开发平台发起远程接口调用,获取用户在微信方的唯一用户标识,openId 如果能正常获取到openId,说明这个用户是存在的,在我们的后端就可以生成jwt的token,然后返回给用户,就说明这个用户登录成功了 当然,在我们本地也会存在一份用户的数据,保存用户的openId,方便对客户进行跟踪和验证 如果需要获取用户的手机号,也需要前后端协作,也会有一个手机号的临时凭证,跟获取openId的流程差不多,只不过这次获取的是用户的手机号
8.前端小程序的微信登录流程
1. 首先微信小程序会向微信官方申请一个临时登录code 2. 然后,小程序带着code向后台服务发送请求 3. 后台接收到code后,会调用微信官方接口验证code是否合法,如果合法,官方会 返回一个openid;这个openid就是此用户在我们系统中的唯一标识,同时也代表 用户身份合法 4. 后台服务接收到来着微信的openid之后,会去数据库查询一下是否存在此账户; 如果存在,代表这是一个老用户,如果不存在,则代表这是一个新用户首次使用 我们的系统,我们需要将其信息保存到用户表中 5. 登录成功之后,需要生成一个标识用户身份的token,返回给前端,前端会将 token保存起来 6. 用户后面访问系统的时候,需要携带着这个token,而我们后端需要编写一个拦 截器,用于拦截请求,校验token 7. 校验通过,则放行请求,正常访问;校验失败,则禁止通行,返回提示
9.如何校验token?流程是什么?
嗯!这个一般在登录验证的过程中,都会使用到 - 首先,我们自定义一个拦截器,来拦截请求 - 从请求头中获取token - 然后解析token,完成校验token是否有效 - 从token解析出的用户数据存储到Threadlocal中 - 最后完成全部校验过后,拦截器放行,继续请求 - 如果中间的非空判断或者解析错误,则会直接抛出401错误,提示前端重新登录
10.哪里用到了Threadlocal,它有什么特点?
- 在拦截器中用到了Threadlocal,将获取到的用户ID赋值到线程属性中。 - ThreadLocal是Java中的一个线程局部变量工具类,它提供了一种在多线程环境下,每个线程都可以独立访问自己的变量副本的机制。ThreadLocal中存储的数据对于每个线程来说都是独立的,互不干扰。
10.ThreadLocal在项目中的应用
1.ThreadLocal称为线程局部变量,可以为每一个线程单独提供一份存储空间,他的特点是:线程之内,数据贡献 线程之间数据隔离 2.在我们的项目中经常使用ThreadLocal来存储用户的登录信息,具体做法是:每次用户访问后台都需要拦截器 鉴权,如果鉴权通过,我们就将登录用户的信息保存到ThreadLocal中,接下来,在项目中就可以从ThreadLocal中 获取用户信息,最后,当请求访问完服务离开的时候,还会再次经过拦截器,这个时候就可以清理掉ThreadLocal中 的内容了
11.项目中对接过三方接口吗?如何对接?什么业务(场景)?
是的,我开发的多个项目中都对接过第三方接口, 比如 - 项目中需要对接微信登录,获取微信用户的openId,需要发起远程请求,请求微信开发者平台提供的接口 - 项目中需要对接阿里云的OSS或者IOT平台,也需要发起远程请求到阿里的开发者平台提供的接口 一般对接这些第三方接口, - 首先要明确的是该接口的详细描述,是否符合当前业务的需要 - 可以从三方提供的接口文档中明确接口的请求方式、入参、出参、路径等等这些必要信息 每个接口的厂商不一样的,使用java对接的方式也不相同,比如 - 对接微信,需要根据接口文档的描述,使用Http客户端来封装参数发起请求 - 对接阿里云相关接口,阿里提供了对应的sdk,只要导入pom依赖,就可以很方便的发起远程调用 - 总之,别管哪种方式,都是http请求,都要仔细按照接口文档说明进行调用
12.你们项目中哪里用到了定时任务?使用了什么技术?
嗯!这个是很常见的,我们项目中很多地方都用到了定时任务 - 预约模块的过期状态自动修改 - 智能监测模块中规则每分钟进行扫描 - 智能监测模块中,设备数据自动清理30天之前的数据 因为我们是单体项目,我们使用的就是Spring框架自带的定时任务调度Spring Task,集成它很方便,只需要两个注解就可以搞定,第一是引导类中开启任务调度,第二在任务类中使用@Scheduled注解来标明该方法的执行频率 关于执行频率,可以使用cron表达式来进行表达
12.SpringTask在项目中的应用
SpringTask是Spring框架提供的一种任务调度工具,用来按照定义的时间格式执行某段代码。 它的一个关键注解是@Scheduled,此注解标注在方法上,用于设置方法的调用时机,它支持下面一些属性: 1. fixedDelay:上一次任务完成后多久执行下一次任务 2. fixedRate:上一次任务开始后多久执行下一次任务(注意:任务不能有重叠) 3. initialDelay:第一次任务延迟多久执行 4. cron:通过cron表达式控制任务执行时间 在我们的项目中,超时订单的状态改变用到了SpringTask,比如: - 每隔1分钟检查是否有超过15分钟未支付的订单,如果有就将订单取消 - 每天凌晨1点检查前一天是否有派送中的订单,如果有将订单状态改成已完成
12-1.cron表达式
cron表达式其实就是一个字符串,通过cron表达式可以定义任务的触发时间 SpringTask支持的cron表达式分为6个域,由空格分隔开,每个域代表一个含义:秒 分 时 日 月 周 每个域都支持精准数值的写法,也支持一些具有特殊意义的字符,主要的有下面这些: - *:表示任意 - ?:表示忽略,只能用在日和周两个域 - -:表示区间, - /:表示起始时间开始触发,然后每隔固定时间触发一次 - ,:表示列出枚举值,例如在分域使用5,20则意味着在5和20分触发一次 - #: 用于确定每个月第几个星期几
13.SpringCache在项目中的应用
SpringCache是Spring提供的一个缓存框架,它可以通过简单的注解实现缓存的操作 本项目中菜品和套餐列表的缓存用到了SpringCache,常用的注解有下面几个: @EnableCaching: 开启基于注解的缓存, @CachePut: 一般用在查询方法上,表示将方法的返回值放到缓存中 @Cacheable: 一般用在查询方法上,表示在方法执行前先查看缓存中是否有数据,如果有直接返回;如果没有,再调用方法体查询数据并将返回结果放到缓存中;他有两个关键属性: - value: 缓存的名称,每个缓存名称下面可以有多个key - key: 缓存的key,支持Spring的表达式语言SPEL语法 @CacheEvict: 一般用在增删改方法上 ,用于清理指定缓存,可以根据key清理,也可以清理整个value下的缓存 - SpringCache还有一个优点:就是可以随意切换底层的缓存软件,比如:Redis、内存等等
13.你们项目中哪里用到了什么缓存,使用的什么缓存技术?
嗯!就像刚才说的,权限管理模块中有大量的接口都使用到了缓存,我们使用的缓存是非关系型数据库Redis。在 使用Redis的过程中,我们发现直接使用Spring 封装的模块RedisTemplate需要在业务层中耦合大量的代码,所以我 们最好采用了Spring Cache这个缓存框架,来为业务层的代码添加缓存的逻辑,它只需要添加少量的注解就可以很 方便的达到添加缓存的效果。
13.你在项目中做过优化吗?是怎么优化的?
嗯!我想一下 我们当时开发这个项目的时候,做过很多的优化,主要的任务就是优化用户的体验,主要是尽可能的缩短接口的响应时间。 - 首先呢,我们开发完成之后,会把项目发布到测试环境中,让测试人员去这些功能,他们有的时候会采用压力进行测试,在测试的过程中,如果发现接口的响应时间超过500ms,则会提出来,让后端进行优化 - 像这些比较慢的接口,一般都是接口的返回值数据量较大才会导致响应时间过长。如果这些对应接口的数据变化写操作较少,查询又比较多的话,我们一般会采用缓存来优化性能 - 比如,权限管理这些功能,有大量的树形接口需要返回,比如资源树形、部门树形,这些响应的数据很多,并且写操作的情况又较少,所以比较适合使用缓存来解决
13.Redis在项目中的应用
我们项目中有两处地方用到了Redis,分别是:店铺营业状态标识和小程序端的套餐、菜品列表数据 - 店铺营业状态标识:仅仅需要在redis中保存一个0|1值即可。这里之所以选择redis,有两个原因: 1. 这个字段太简单了,没有必要在数据库中新建一张表 2. 这个状态访问比较频繁,放在redis中,提高了查询速度的同时,可以减轻数据库的访问压力 - 小程序端的套餐、菜品列表数据:由于小程序端以后的访问量比较大,所以采用Redis提高访问速度 具体的操作步骤就是: 1. 在查询列表的时候,先判断Redis缓存中是否有数据,如果有,直接返回给前端 2. 如果没有,再去查询数据库,并将查询结果保存到redis中的同时,再返回给前端 为了保证Redis和数据库中数据的实时一致性,在对数据库相关数据进行增删改操作时,需要同时清理Redis中数据
13-1.Redis如何与MySQL进行数据同步
好!是这样的,我们这个项目添加缓存的地方主要是权限管理这些接口,它是一个单体项目,时效上要求没那么严格,所以我们的一个同步思路是: - 当查询数据的时候,先判断Redis中是否有缓存 - 如果有直接返回 - 如果缓存中没有,则会查询数据库返回数据,同时也会把从库中查询的数据同步到redis中 - 当有写操作(增删改)的时候,会直接删除Redis的数据 这样的话,就能保证MySQL中的数据一直与Redis是同步的
13-2.Redis的数据类型有哪些?有什么特点?
好的~ Redis比较常见的数据类型有5种 第一是**字符串类型(String)**:它Java中的String类似 第二是**哈希(Hash)**:又叫散列,类似Java中的HashMap结构,存在两个key 第三是**列表(list)**:数据按照插入顺序排列,可以重复,类似Java中的LinkedList(双向链表结构) 第四是**集合(set)**:无序集合,数据排列无序,并且没有重复,类似Java中的HashSet 第五个是**有序集合(Sorted set/zset)**:集合中的每个数据会关联一个分数(score),按照分数的升序排列,并且没有重复
14.你们项目中哪里用到了物联网?(详细介绍业务-->位置绑定设备,老人绑定设备)
由于目前的这个项目是一个养老院中的管理系统,主要会对养老院中的房间床位或者是老人进行绑定设备。 - 比如在老人的床位中绑定睡眠监测带,方便检查老人的睡眠状况 - 比如在房间绑定智能烟雾报警器,方便检查房间中的温度和湿度 - 比如给老人提供一些穿戴设备,智能定位手表,方便监测老人的健康数据等等 通过这些设备的实时上报,我们使用了阿里云的物联网平台,来监测这些设备的上报数据,一旦发现异常情况,方便养老院及时进行处理
15.项目中用过多线程吗?(重点讲解线程池,什么业务,线程池的核心参数和原理)
物联网中设备的数据交互用到了多线程,保证数据交互的高效,多设备能够传递信息 线程池配置七大核心参数: 1. 核心池参数:线程池中始终保持活跃的线程数 2. 最大池参数:线程池允许的最大线程数 3. 工作队列:用于保存等待执行的任务 4. 线程空闲时间:除了核心池的线程其他线程能存活的最大时间 5. 空闲时间的单位:用于指定空闲时间的单位 6. 线程工厂:用于创建新线程的工厂 7. 拒绝策略:当线程都在工作、队列都满了之后,线程池将执行拒绝策略
16.MySQL的索引是如何创建的?是什么场景?(介绍你项目中的业务)
好的!养老系统中有一个家属端,在方便老人的家属查看老人的信息,比如就可以实时的查看老人的健康数据,也可以查看老人的健康历史数据。 由于目前设备上报的数据都是存储到了MySQL的表中,设备上报的数据是巨大的,一天大概能上报70万的数据,当家属查看老人的历史数据的时候,由于表存储的数据较多,查询的效率就会很低。所以我们的解决方案就是给表中的查询字段添加了索引,这样查询效率就得到了很大的提升
17.MySQL索引底层的实现原理是什么?
MySQL的默认的存储引擎InnoDB采用的B+树的数据结构来存储索引,选择B+树的主要的原因是: - 第一阶数更多,路径更短 - 第二个磁盘读写代价B+树更低,非叶子节点只存储指针,叶子阶段存储数据 - 第三是B+树便于扫库和区间查询,叶子节点是一个双向链表
18.如何检查MySQL索引是否失效?
我们通常会使用mysql自动的执行计划explain来去查看这条sql的执行情况,比如在这里面可以通过key和key_len检查是否命中了索引,如果本身已经添加了索引,也可以判断索引是否有失效的情况
19.关于索引失效,你遇到过哪些?
嗯,这个情况比较多,我说一些自己的经验,以前遇到过的 比如,索引在使用的时候没有遵循最左匹配法则,第二个是,模糊查询,如果%号在前面也会导致索引失效。如果在添加索引的字段上进行了运算操作或者类型转换也都会导致索引失效。 我们之前还遇到过一个就是,如果使用了复合索引,中间使用了范围查询,右边的条件索引也会失效 所以,通常情况下,想要判断出这条sql是否有索引失效的情况,可以使用explain执行计划来分析
20.什么是聚集索引和二级索引,什么是回表查询?
好的~,聚集索引主要是指数据与索引放到一块,B+树的叶子节点保存了整行数据,有且只有一个,一般情况下主键在作为聚集索引的 非聚集索引值的是数据与索引分开存储,B+树的叶子节点保存对应的主键,可以有多个,一般我们自己定义的索引都是非聚集索引 什么是回表查询? 嗯,其实跟刚才介绍的聚簇索引和非聚簇索引是有关系的,回表的意思就是通过二级索引找到对应的主键值,然后再通过主键值找到聚集索引中所对应的整行数据,这个过程就是回表
21.你们项目中的bug是如何管理的?
嗯!上家公司自己搭建了禅道服务器,所有的bug都提在了禅道中。 - 当某一阶段代码完成之后,会把代码提供交到测试环境,由测试人员来进行测试 - 如果测试过程中,发现了bug,他们会禅道中详细说明bug产生的原因,并且分配给对应的开发人员 - 我们开发在禅道中看到bug之后,首先会复现这个问题,然后解决掉,在禅道中标记为**已解决** - 测试人员会对已解决的bug的再次进行回归测试,如果没有问题,则会结束这个bug
WebSocket对比HTTP
- HTTP的通信是单向的,要先请求后响应,类似于对讲机 - WebSocket的通信双向的、实时的,客户端和服务端可以同时发消息,类似于手机通话 我们在项目中大部分场景下都是使用HTTP协议,只有在高实时场景下,建议使用WebSocket 项目在向商家提醒接单时,用户催单发送提醒时使用了webSocket
Excel有哪些技术方案
Excel技术方案有:ApachePOI和EasyExcel等,EasyExcel是在POI的基础上进行二次开发的 - POI的封装度较低,使用时需要写大量的代码,并且性能也比较低,同时它是对文档一次性导入,容易导致内存溢出 - EasyExcel的封装度比较高,使用起来比较方便,而且它是一条条导入数据,不会导致内存溢出 在实际开发中,更倾向于使用稳定性更好的方案,所以一般选择EasyExcel作为Excel的导入导出技术方案
微信支付流程
整个微信支付流程涉及到三个角色:微信小程序、服务端、微信平台 1. 首先,由小程序发起下单请求到服务端,服务端生成订单保存到数据库后,将订 单号返给前端 2. 然后,小程序会向服务端发起支付请求,这个请求中会携带着订单号 3. 服务端根据订单号查询到订单信息后,开始调用微信下单接口从微信平台获取预 支付交易标识 4. 服务端需要将预支付交易标识进行签名之后组装成支付参数,回传给小程序,小 程序就会弹出支付窗口 5. 用户通过小程序向微信平台付款,并可以获取到支付结果,进行显示 6. 微信平台还会将订单支付结果推送给我们的后台程序,后台程序需要修改订单状态
Hashtable和ConcurrentHashMap的区别
Hashtable和ConcurrentHashMap是两种常用的哈希表数据结构,它们在实现上有一些区别。 1. 线程安全性:Hashtable是线程安全的,而ConcurrentHashMap是并发安全的。 在多线程环境中,多个线程可以同时访问和修改ConcurrentHashMap,而不会发生数据不一致的问题。 而Hashtable则使用了synchronized关键字来保证线程安全,这会导致在高并发场景下性能下降。 2. 锁的粒度:ConcurrentHashMap使用了分段锁(Segment),将整个数据结构分成多个段,每个段拥有自己的锁。 这样多个线程可以同时访问不同的段,提高了并发性能。而Hashtable则使用了全局锁,所有的线程需要竞争同一个锁,限制了并发性能。 3. 迭代器:ConcurrentHashMap的迭代器是弱一致性的,它不会抛出ConcurrentModificationException异常,即使在迭代过程中有其他线程对集合进行了修改。 而Hashtable的迭代器是强一致性的,如果在迭代过程中有其他线程对集合进行了修改,会抛出ConcurrentModificationException异常。 4. null值:Hashtable不允许存储null值,而ConcurrentHashMap允许存储null值。 总的来说,如果在多线程环境下需要高并发性能,可以选择使用ConcurrentHashMap。而如果只在单线程环境下使用,或者对线程安全性要求不高,可以选择Hashtable。