一、JavaSE
1、jdk1.8和其他版本有哪些区别?
1.jdk1.8 新增了 Lambda 表达式
2.jdk1.8 新增了很多函数式接口:主要分为四大类,消费型、供给型、判断型、功能型
3.jdk1.8 新增了接口的静态方法和默认方法
4.jdk1.8 更新了日期时间API:LocalDate、LocalTime、LocalDateTime、DateTimeFormatter(实现日期时间和字符串之间的相互转换)
5.jdk1.8 新增了 StreamAPI:创建 Stream、中间操作、终止操作
6.jdk1.8 将方法区的实现永久代改为了元空间
2、解释下java中的Stream()流
当项目中需要把 map 集合转换成 list 集合时,本来需要写很多代码,现在用 Stream()流可以很简单的就实现了
3、hashMap的底层原理 * 6
JDK1.7
1.映射关系被封装为HashMap.Entry类型
2.底层是数组 + 链表
3.形成链表时,采用头插法
4.饿加载创建数组
JDK1.8
1.映射关系被封装为HashMap.Node类型
2.底层是数组 + 链表 + 红黑树
3.形成链表时,采用尾插法
4.懒加载创建数组
5.当数组长度大于64,链表长度8时,转为红黑树存储
名词解释:
树化阈值:8
树化阈值:6
最小树化容量:64
初始化容量:16
默认负载因子:0.75,太大hash冲突会比较严重,太小数组扩容的频率太频繁
扩容的临界值 = 数组的容量 * 负载因子
数组的容量:2 的 n 次幂,因为扩容 2 倍
为什么链表结构要转换为红黑树结构?
1.当链表长度过长时,查询效率低
2.链表的时间复杂度为 O(n)、红黑树的时间复杂度为 O(lgn)
4、HashMap的扩容机制
以JDK1.8为例:
1.计算 key 的哈希值,如果 key 为 null,会放在index[0] 的位置
2.判断是否为第一次 put ,如果是第一次则初始容量为16的数组,临界值为12
3.然后计算得到 index,判断 index 位置上的数据是否为空
4.如果为空,则判断数组是否需要扩容
5.如果小于临界值,则添加成功,否则扩容2倍,再添加成功
6.如果 index 位置不为空,则判断链表的长度
7.如果链表的长度小于8,调用 equals 方法判断元素是否相等,相等则覆盖
8.不相等则添加到链表中
9.如果链表的长度大于8,且数组的长度大于64,则转换为红黑树存储
5、set,list,map的区别 * 4
set |
list |
map |
无序,不可重复 |
有序,可重复 |
key:无序,不可重复;value:无序,可重复 |
HashSet、LinkedHashSet、TreeSet |
ArrayList、LinkedList、Vector |
HashMap、TreeMap、HashTable |
ArrayList |
Vector |
LinkedList |
线程不安全 |
线程安全 |
线程不安全 |
动态数组 |
动态数组 |
双向链表 |
JDK1.7之前底层是默认长度为10的数组,默认扩容1.5倍,JDK1.7之后底层默认是空数组 |
底层默认是长度为10的数组,默认扩容2.0倍 |
当频繁在集合中插入、删除元素时,效率较高,但是查找遍历的效率较低 |
6、hashmap 和 hashtable 和 hashset 区别?* 2
hashmap |
hashtable |
hashset |
底层是哈希表,线程不安全,速度快 |
底层是哈希表,线程安全,速度慢 |
底层是hashmap |
key ,value可以存储 null 值 |
key,value不可以存储 null 值 |
可以存储null值 |
7、反射是什么
通过反射机制,可以获得运行期间对象的类型信息,在很多框架中都有使用,比如spring、mybatis,利用反射可以实现工厂模式和代理模式等
8、new String(“123”)创建了几个对象
第一次:创建了两个,堆空间一个,常量池一个
第二次:创建了一个,堆空间一个,常量池中已存在不会在创建
9、你认为的编码规范的风格是什么样子的
10、stringbuffer和stringbuilder的区别说一下/为什么是线程安全的
String |
不可变字符序列,字符串常量存储在常量池中,属于引用数据类型,底层使用 char 型数组 |
StringBuilder |
可变的字符序列,底层是长度为16的 char 型数组,默认扩容2倍+2,线程不安全 |
StringBuffer |
和 StringBuilder类似,但是在所有的方法上都加了 synchronized 同步锁,线程安全 |
执行效率 |
StringBuilder > StringBuffer > String |
11、IO说一下?
1.Java中 I/O 操作主要是指使用 java.io 包下的内容,进行输入、输出操作
2.输入流:把数据从其他设备上读取到内存中的流
3.输出流:把数据从内存中写入到其他设备上的流
4.字节流:以字节为单位,读写数据的流
5.字符流:以字符为单位,读写数据的流
6.ObjectOutputStream(序列化):内存中的对象--->存储中的文件、通过网络传输出去
7.ObjectInputStream(反序列化):存储中的文件、通过网络接收过来 --->内存中的对象
12、java里面"=="和"equals"的区别
== |
equals |
适用于基本数据类型:比较变量的值是否相等 |
只适用于引用数据类型:未重写的情况下等 == 作用一样 |
适用于引用数据类型:比较两个引用的地址是否相等 |
开发中一般重写 equal 方法,用于比较两个对象的实体内容是否相等 |
13、深拷贝与浅拷贝的理解
深拷贝和浅拷贝就是指对象的拷贝,一个对象中存在两种类型的属性,一种是基本数据类型,一种是实例对象的引用。
1.浅拷贝是指,只会拷贝基本数据类型的值,以及实例对象的引用地址,并不会复制一份引用地址所指向的对象,也就是浅拷贝出来的对象,内部的类属性指向的是同一个对象
2.深拷贝是指,既会拷贝基本数据类型的值,也会针对实例对象的引用地址所指向的对象进行复制,深拷贝出来的对象,内部的类执行指向的不是同一个对象
二、JavaWEB
三、SpringMVC
1、你们Controller层的接口是否直接暴露给前端
2、前端调用接口,后端寻找对应微服务的的具体链路
3、springMVC执行流程
(1)用户发送请求至前端控制器 DispatcherServlet
(2) DispatcherServlet 收到请求后,调用 HandlerMapping 处理器映射器,请求获取Handle方法
(3)处理器映射器根据请求 url 找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给 DispatcherServlet
(4)DispatcherServlet 调用 HandlerAdapter 处理器适配器
(5)HandlerAdapter 经过适配调用具体处理器(Handler,也叫后端控制器)
(6)Handler执行完成返回 ModelAndView
(7)HandlerAdapter 将 ModelAndView 返回给 DispatcherServlet
(8)DispatcherServlet 将 ModelAndView 传给ViewResolver 视图解析器进行解析
(9)ViewResolver 解析后返回具体的视图 View
(10)DispatcherServlet 对视图 View 进行渲染视图(将模型数据填充至视图中)
(11)最后 DispatcherServlet 响应给用户
四、Spring
1、Spring是怎么解决Bean之间的循环依赖的
2、spring事务传播机制
REQUIRED |
支持当前事务,如果不存在,就新建一个 |
SUPPORTS |
支持当前事务,如果不存在,就不使用事务 |
MANDATORY |
支持当前事务,如果不存在,抛出异常 |
REQUIRES_NEW |
如果有事务存在,挂起当前事务,创建一个新的事务 |
NOT_SUPPORTED |
以非事务方式运行,如果有事务存在,挂起当前事务 |
NEVER |
以非事务方式运行,如果有事务存在,抛出异常 |
NESTED |
如果当前事务不存在,就新建一个事务运行,如果存在,则嵌套事务执行 |
3、Spring的IOC、AOP讲一下
概念 |
IOC 即控制反转,将创建对象和对象间的依赖关系放入 IOC 容器中交给 Spring 管理 |
作用 |
管理对象的创建(生命周期)、维护对象间的依赖关系、解耦 |
原理 |
IOC 的实现原理就是工厂模式加反射机制 |
4、Aop是什么?项目用到了吗? * 2
概念 |
AOP 也就是面向切面编程,是面向对象的一种补充,在不修改源代码的基础上对业务功能进行增强 |
作用 |
减少系统的重复代码、降低系统的耦合度、提高了系统的可维护性、常用于事务、日志处理等 |
原理 |
基于动态代理( JDK 的动态代理、Cglib动态代理) |
AOP实现缓存:
1.自定义缓存注解@GmallCache和注解的属性(类似于事务@Transactional)
2.编写切面类,使用环绕通知实现缓存的逻辑封装
3.在业务方法上添加自定义注解,即可完成缓存功能
五、Mybatis
1、mybatis单表查询和多表查询有什么区别嘛? ×
2、mybatis里resultmap可以继承吗?怎么实现 ×
3、resultmap和resulttype的区别作用是什么(假如我的JavaBean里有List字段我该使用什么) ×
4、mybatis用什么接收参数的 #{}和${}的区别
#{} |
${} |
预编译处理 |
字符串替换 |
处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值 |
处理 ${}时,就是把{}内的值替换成变量的值 |
可以有效的防止SQL注入,安全 |
有 SQL 注入的风险,不安全 |
5、mybatis使用公共的sql是怎么使用的 ×
1.使用sql标签抽取重复出现的SQL片段
select emp_id,emp_name,emp_age,emp_salary,emp_gender from t_emp
2.使用include标签引用声明的SQL片段
六、Redis
1、Redis中的一个key的value是多少
一个Redis中字符串value最多可以是512M
2、redis数据结构有哪些
1.Redis字符串(String): String的数据结构为简单动态字符串
2.Redis列表(List) : 底层是个双向链表,对两端的操作性能很高,通过索引下标的操作中间的节点性能会较差
3.Redis集合(Set) : 功能与 list 类似是一个列表的功能,特殊之处在于 set 是可以自动排重的
4.Redis哈希(Hash): Redis hash是一个string 类型的 field 和 value 的映射表,hash特别适合用于存储对象
5.Redis有序集合Zset(sorted set): zset与普通集合set非常相似,不同之处是有序集合的每个成员都关联了一个评分,可以实现排序
6.Bitmaps:合理地使用操作位能够有效地提高内存使用率和开发效率
7.HyperLogLog:是一种用来做基数统计的算法
8.Geospatial:该类型,就是元素的2维坐标,在地图上就是经纬度
3、redis的set和zset有什么区别
zset与普通集合set非常相似,是一个没有重复元素的字符串集合。不同之处是有序集合的每个成员都关联了一个评分,可以实现排序
4、redis在项目中哪些地方用到
1.商品刚加入购物车时缓存实时价格
2.将当前用户的购物车信息存入Redis
3.首页内容信息的展示,把分类列表的查询结果放入缓存
4.把库存的锁定信息保存到 redis 中
5.将防重的唯一标识、订单号存入 Redis 缓存
6.购物车异步写入数据库异常时,会将异常的userId缓存到Redis,将来定时任务定时进行 MySQL 数据同步
7.一个商品对应多个用户评论,并且按照时间顺序显示评论,为了提高查询效率,因此我们选择了redis的list类型将商品评论放在缓存中
8.在统计模块中,我们有个功能是做商品销售的排行榜,因此选择redis的zset结构来实现
5、redis缓存写的一致性问题 * 2
5.1 双写模式:会产生写的一致性问题
先写 Redis |
写 Redis 成功 (新值) |
写 MySQL 失败,回滚 (旧值) |
数据不一致 |
|
先写 MySQL |
写 MySQL 成功 (新值) |
写 Redis 成功 (新值) |
代码异常服务器宕机,MySQL 回滚 (旧值) |
数据不一致 |
5.2 失效模式:高并发会产生写的一致性问题
a用户(写) |
先删 Redis (空) |
再写 MySQL |
提交 MySQL (新) |
b用户(读) |
|
查询数据,先查 Redis (空),再查 MySQL (旧),放入 Redis (旧) |
|
a用户(写) |
写 MySQL 成功,删 Redis (空) |
|
提交 MySQL (新) |
b用户(读) |
|
查询数据,先查 Redis (空),再查 MySQL (旧),放入 Redis (旧) |
|
5.3 双删模式:完美解决写的一致性问题
1. 当要写 MySQL 的时候先删除Redis
2. 写 MySQL
3. 提交事务,MySQL 数据更新为新数据
4. 异步删除 Redis
5. 再读的话会查询数据库,然后写入Redis,实现数据一致
6、redis缓存并发读问题
并发读问题 |
概念 |
解决方案 |
缓存穿透 |
大量请求访问数据库不存在的数据,此时缓存中没有大量请求直达数据库 |
数据为空也进行缓存、使用布隆过滤器 |
缓存雪崩 |
缓存时间相同,导致大量 key 同时过期,导致大量请求会直达数据库 |
给缓存时间添加随机值 |
缓存击穿 |
一个热点 key 过期,导致大量请求会直达数据库 |
加分布式锁 |
7、分布式锁的设计细节 * 2
实现方式 |
可靠性 |
实现复杂度 |
性能 |
推荐使用 |
基于 Redis 实现 |
较高 |
简单 |
最高 |
√ |
基于关系型数据库实现 |
最高 |
中等 |
较差 |
|
基于 zookeeper 实现 |
较高 |
困难 |
中等 |
|
第一步 |
多个客户端同时尝试获取锁(setnx,独占排他) |
第二步 |
获取成功,执行业务逻辑,执行完成释放锁(del) |
第三步 |
其他客户端等待重试(自旋锁) |
问题一 |
setnx 刚好获取到锁,业务逻辑出现异常,导致锁无法释放 ,可能导致死锁问题 |
解决 |
设置过期时间,自动释放锁,set 时直接指定过期时间(保证加锁原子性) |
问题二 |
当业务逻辑的执行时间大于设置的锁的过期时间,可能会导致释放其他服务器的锁,出现误删的情况 |
解决 |
setnx 获取锁时,设置一个唯一的 UUID,释放前获取这个值,判断是否自己的锁 |
问题三 |
删除操作仍然缺乏原子性,判断 UUID正确 后,执 |