admin管理员组文章数量:1794759
Java常见面试题 非常实用
Java 面试题 一. 容器部分 二. 多线程部分 三. SpringMvc部分 四. Mybatis部分 五. MySQL部分 六. Redis部分 七. RabbitMQ部分 八. JVM虚拟机部分 九. 算法知识部分 十. 其他面试部分 2|1更新 时间:2022/4/9 内容: JVM虚拟机部分 预更: 算法部分
2|2容器部分面试题Java 容器都有哪些
- Collection 的子类 List、Set
- List 的子类 ArrayList、LinkedList等
- Set 的子类 HashSet、TreeSet等
- Map 的子类 HashMap、TreeMao等
Collecion 和 Collections 有什么区别
- java.util,Collection 是一个集合的顶级接口,它提供了对集合对象进行基本操作的通用接口方法,Collection 接口的意义是为各种具体的集合提供了最大化的统一操作方式,其直接继承接口的有 List 和 Set
- java.util.Collections 是一个包装类(工具类),它包含了各种有关集合操作的静态多态方法。此类不能被实例化,用于对集合中元素进行排序、搜索以及线程安全等各种操作,服务于 Java 的 Collection 框架
List、Set、Map 之间的区别
- List、Set 都继承 Collection 接口,而 Map 则不是
- List 是一个有序集合,元素可重复,可有多个NULL值。可以使用各种循环遍历集合,因为它是有序的
- Set 是一个无序集合,元素不可重复,重复元素会被覆盖掉(注意:元素虽然无放入顺序,但元素在 Set 中的位置是由该元素的 HashCode 决定的,其位置是固定的,加入 Set 的 Object 必须定义 equals()方法),Set 只能用迭代,因为它是无序的
- Map 是由一系列键值对组成的集合,提供了 key 和 value 的映射。在 Map 中保证 key 与 value 一一对应的关系。一个 key 对应一个 value ,不能存在相同的 key,但 value 可以相同
- Set 与 List 相比较
- Map 适合存储键值对的数据
HashMap 和 HashTable 的区别
- HashTable 继承自 Dictionary 类
- HashMap 继承自 AbstractMap 类
- HashMap 在缺省的情况下是非Synchronize的
- HashTable 的方法是Synchronize的
- 在多线程下直接使用 HashTable 不需要自己为它的方法实现同步。但使用 HashMap 时需要手动增加同步处理Map m = Collections.synchronizeMap(hashMap);
- HashMap 把 HashTable 的 contains() 方法去掉了,改成了 containsValue() 和 containsKey(),因为 contains 容易让人误解
- HashTable 则保留了 contains()、containsValue()、containsKey() 三个方法,其中 contains() 与 containsValue() 功能相同
- HashTable 中,key 和 value 都不能为 null 值
- HashMap 中,null 作为键,但这样的键只有一个。可以有多个 value 为 null 值的键。当 get() 方式返回 null 有可能是 HashMap 中没有该键,也有可能返回的 value 为 null。所以 HashMap 用containsKey()方法判断是否存在键
- HashTable 与 HashMap 都是使用 Iterator 迭代器遍历,而由于历史的原因,HashTable 还使用了 Enumeration 的方式
- 哈希值的使用不同,HashTable直接使用对象的HashCode,而 HashMap 重新计算哈希值
- hashCode是jdk根据对象的地址或者字符串或者数字算出来的int类型的数值
- HashTable 使用的取模运算
- HashMap 使用的与运算,先用 hash & 0x7FFFFFFF 后,再对 length 取模,&0x7FFFFFFF 的目的是为了将负的 hash 值转化为正值,因为 hash 值有可能为负数,而 &0x7FFFFFFF 后,只有符号外改变,而后面的位都不变
- HashTable 在不指定容量的情况下默认是11,而 HashMap 为16,HashTable 不要求底层数组的容量一定要是2的整数次幂,而 HashMap 底层数组则一定为2的整数次幂
- HashTable 扩容时,将容量变成原来的2倍+1 (old * 2 + 1),而 HashMap 则直接改为原来的2倍 (old * 2)
如何决定使用 HashMap 还是 TreeMap
- 如果需要得到一个有序的 Map 集合就应该使用 TreeMap (因为 HashMap 的排序顺序不是固定的)除此之外,由于 HashMap 有比 TreeMap 更好的性能,在不需要使用排序的情况下使用 HashMap 会更好
HashMap 的实现原理
- 利用key的hashCode重新hash计算出当前对象的元素在数组中的下标 存储时,如果出现hash值相同的key,此时有两种情况。(1)如果key相同,则覆盖原始值;(2)如果key不同(出现冲突),则将当前的key-value放入链表中 获取时,直接找到hash值对应的下标,在进一步判断key是否相同,从而找到对应值。 理解了以上过程就不难明白HashMap是如何解决hash冲突的问题,核心就是使用了数组的存储方式,然后将冲突的key的对象放入链表中,一旦发现冲突就在链表中做进一步的对比。
HashSet 的实现原理
- HashSet 实际上是一个 HashMap 实例,都是一个存放链表的数组。它不保证存储元素的迭代顺序;此类允许使用 null 元素。HashSet 中不允许有重复元素,这是因为 HashSet 是基于 HashMap 实现的,HashSet 中的元素都存放在 HashMap 的 key 上面,而 value 中的值都是统一的一个固定对象 private static final Object PRESENT = new Object();
- HashSet 中 add() 方法调用的是底层 HashMap 中的 put() 方法,而如果是在 HashMap 中调用 put() ,首先会判断 key 是否存在,如果 key 存在则修改 value 值,如果 key 不存在这插入这个 key-value。而在 set 中,因为 value 值没有用,也就不存在修改 value 值的说法,因此往 HashSet 中添加元素,首先判断元素(也就是key)是否存在,如果不存在这插入,如果存在着不插入,这样 HashSet 中就不存在重复值。所以判断 key 是否存在就要重写元素的类的 equals() 和 hashCode() 方法,当向 Set 中添加对象时,首先调用此对象所在类的 hashCode() 方法,计算次对象的哈希值,此哈希值决定了此对象在Set中存放的位置;若此位置没有被存储对象则直接存储,若已有对象则通过对象所在类的 equals() 比较两个对象是否相同,相同则不能被添加。
ArrayList 与 LinkList 的区别是什么
如何实现数组与 List 之间的转换
- List to Array : 可以使用 List 的 toArray() 方法,传入一个数组的类型例如 Stirng[] strs = strList.toArray(new String[strList.size()]);
- Array to List : 可以使用 java.util.Arrays 的 asList()方法 例如 List<String> strList = Arrays.asList(strs);
ArrayList 与 Vector 的区别是什么
- ArrayList 是非线程安全的,而 Vector 使用了 Synchronized 来实现线程同步的
- ArrayList 在性能方面要优于 Vector
- ArrayList 和 Vector 都会根据实际情况来动态扩容的,不同的是 ArrayList 扩容到原大小的1.5倍,而 Vector 扩容到原大小的2倍
Array 与 ArrayList 有什么区别
- Array 是数组,当定义数组时,必须指定数据类型及数组长度
- ArrayList 是动态数组,长度可以动态改变,会自动扩容,不使用泛型的时候,可以添加不同类型元素
在 Queue 中 poll() 与 remove() 有什么区别
- poll() 和 remove() 都是从队列头删除一个元素,如果队列元素为空,remove() 方法会抛出NoSuchElementException异常,而 poll() 方法只会返回 null
哪些集合类是线程安全的
迭代器 Iterator 是什么
- Iterator 是集合专用的遍历方式
- Iterator<E> iterator() : 返回此集合中元素的迭代器,通过集合的iterator()方法得到,所以Iterator是依赖于集合而存在的
Iterator 怎么使用 ? 有什么特点
Iterator 的使用方法
- java.lang.Iterable 接口被 java.util.Collection 接口继承,java.util.Collection 接口的 iterator() 方法返回一个 Iterator 对象
- next() 方法获取集合中下一个元素
- hasNext() 方法检查集合中是否还有元素
- remove() 方法将迭代器新返回的元素删除
Iterator 的特点
- Iterator 遍历集合过程中不允许线程对集合元素进行修改
- Iterator 遍历集合过程中可以用remove()方法来移除元素,移除的元素是上一次Iterator.next()返回的元素
- Iterator 的next()方法是通过游标指向的形式返回Iterator下一个元素
Iterator 与 LinkIterator 有什么区别
- 使用范围不同
怎么确保一个集合不能被修改
- 可以采用 java.util.Collections 工具类
- Collections.unmodifiableMap(map)
- Collections.unmodifiableList(list)
- Collections.unmodifiableSet(set)
- 如诺修改则会报错java.lang.UnsupportedOperationException
并发和并行有什么区别
- 并发:不同的代码块交替执行
- 并行:不同的代码块同时执行
- 个人理解
线程和进程的区别
- 根本区别 :进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位
- 在操作系统中能同时运行多个进程,进程中会执行多个线程
- 线程是操作系统能够进行运算调度的最小单位
守护线程是什么
- JVM内部的实现是如果运行的程序只剩下守护线程的话,程序将终止运行,直接结束。所以守护线程是作为辅助线程存在的
创建线程有哪几种方式
- 定义Thread类的子类,并重写该类的run()方法
- 创建Thread子类的实例,即创建了线程对象
- 调用线程对象的start()方法来启动该线程
- 创建runnable接口的实现类,并重写该接口的run()方法
- 创建Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该 Thread 对象才是真正的线程对象
- 调用线程对象的start()方法来启动该线程
- 创建Callable接口的实现类,并重写call()方法,该call()方法将作为线程执行体,并且有返回值
- 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值
- 使用FutureTask对象作为Thread对象的target创建并启动新线程
- 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
runnable 和 callable 有什么区别
- 相同点
- 不同点
注意 Callalble接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞
线程有哪些状态
- 新建 就绪 运行 阻塞 死亡
sleep() 和 wait() 的区别
- 最大区别是sleep() 休眠时不释放同步对象锁,其他线程无法访问 而wait()休眠时,释放同步对象锁其他线程可以访问
- sleep() 执行完后会自动恢复运行不需要其他线程唤起.而 wait() 执行后会放弃对象的使用权,其他线程可以访问到,需要其他线程调用 Monitor.Pulse() 或者 Monitor.PulseAll() 来唤醒或者通知等待队列
线程的 run() 和 start() 的区别
- start() 是来启动线程的
- run() 只是 Thread 类中一个普通方法,还是在主线程中执行
notify() 和 notifyAll() 有什么区别
- notify() 方法随机唤醒对象的等待池中的一个线程,进入锁池
- notifyAll() 唤醒对象的等待池中的所有线程,进入锁池。
创建线程池有哪几种方式
- 利用Executors创建线程池
线程池都有哪些状态
- SHUTDOWN状态下,任务数为 0, 其他所有任务已终止,线程池会变为 TIDYING 状态,会执行 terminated() 方法。线程池中的 terminated() 方法是空实现,可以重写该方法进行相应的处理
- 线程池在 SHUTDOWN 状态,任务队列为空且执行中任务为空,线程池就会由 SHUTDOWN 转变为 TIDYING 状态
- 线程池在 STOP 状态,线程池中执行中任务为空时,就会由 STOP 转变为 TIDYING 状态
线程池中的 submit() 和 execute() 有什么区别
- 两个方法都可以向线程池提交任务
- execute()方法的返回类型是 void,它定义在Executor接口中
- 而submit()方法可以返回持有计算结果的 Future 对象,它定义在ExecutorService接口中
在Java程序中怎么确保多线程运行安全
线程安全问题主要是:原子性,可见性,有序性
synchronized 和 volatile 的作用什么?有什么区别
- synchronized 表示只有一个线程可以获取作用对象的锁,执行代码,阻塞其他线程
- volatile 表示变量在 CPU 的寄存器中是不确定的,必须从主存中读取。保证多线程环境下变量的可见性,禁止指令重排序
- synchronized 可以作用于变量、方法、对象。volatile 只能作用于变量
- synchronized 可以保证线程间的有序性、原子性和可见性。volatile 只保证了可见性和有序性,无法保证原子性
- synchronized 线程阻塞,volatile 线程不阻塞
synchronized 和 Lock 有什么区别
- synchronized是一个Java关键字,在jvm层面上,而Lock是一个类
- synchronized以获取锁的线程执行完同步代码,释放锁,如果线程中发生异常,jvm会让线程释放锁。而Lock必须在finally中释放锁,否则容易造成线程死锁
- Lock可以查看锁的状态,而synchronized不能
- 在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择
- synchronized是非公平锁,而Lock是可公平锁
为什么要是使用 Spring
- 轻量 非侵入型框架
- 控制反转 IOC,促进了松耦合
- 面向切面编程 AOP
- 容器 采用Java Bean 代替 沉重的EJB容器
- 方便集成 可以和很多框架相互集成如Mybatis、Shiro
- 丰富的功能 Spring 已经写好了很多常的模板如JDBC抽象类、事务管理、JMS、JMX、Web Service...等
解释一下什么是 aop
- AOP是面向切面编程,是OOP编程的有效补充
- AOP包括Aspect切面,Join Point连接点,Advice通知,Ponitcut切点
其中Advice通知包括5中模式
解释一下什么是 ioc
- IOC是控制反转,是将代码本身的控制权移到外部Spring容器中进行集中管理,也是为了达到松耦合
Spring 主要有哪些模块
- 框架基础部分,提供了IOC容器,对Bean进行集中管理
- 基于Bean,提供上下文信
- 提供了JDBC的抽象层,它可消除冗长的JDBC编码,提供了声明性事务管理方法
- 提供了对象/关系映射常用的API集成层,如Mybatis、Hibernate等
- 提供了AOP面向切面的编程实现
- 提供了Web开发的信上下文,可与其他的Web集成开发
- 提供了Web应用的Model - View - Controller全功能的实现
Spring 常用的注入方式有哪些
- 构造方法注入
- Setter方法注入
不过值得一提的是:Setter注入时如果写了带参构造方法,那么无参构造方法必须也要写上,否则注入失败
- 基于注解得注入
基本上有五种常用注册Bean的注解
Spring 中的 bean 是线程安全的吗
- 线程不安全
- Spring容器中的Bean是否线程安全,容器本身并没有提供Bean的线程安全策略,因此可以说Spring容器中的Bean本身不具备线程安全的特性
Spring 支持几种 bean 的作用域
- 后面三种只有在Web应用中使用Spring才有效
Spring 自动装配 bean 有哪些方式
- default:默认方式和‘no’效果一样
- no :不自动配置,需要使用 节点或参数
- byName:根据名称进行装配
- byType:根据类型进行装配
- constructor :根据构造函数进行装配
Spring 事务实现方式有哪些
- 编程式事务管理对基于 POJO 的应用来说是唯一选择,我们需要在代码中调用beginTransaction()、commit()、rollback()等事务管理相关的方法,这就是编程式事务管理
- 基于 TransactionProxyFactoryBean的声明式事务管理
- 基于 @Transactional 的声明式事务管理
- 基于Aspectj AOP配置事务
Spring 的事务隔离是什么
Spring Mvc 的运行流程
Spring Mvc 有哪些组件
- DispatcherServlet:前端控制器
- HandlerMapping:处理器映射器
- HandlerAdapter:处理器适配器
- HandlerInterceptor:拦截器
- ViewResolver:视图解析器
- MultipartResolver:文件上传处理器
- HandlerExceptionResolver:异常处理器
- DataBinder:数据转换
- HttpMessageConverter:消转换器
- FlashMapManager:页面跳转参数管理器
- HandlerExecutionChain:处理程序执行链
- RequestToViewNameTranslator:请求转视图翻译器
- ThemeResolver:
- LocaleResolver:语言环境处理器
@RequestMapping 的作用是什么
- @RequestMapping是一个注解,用来标识 http 请求地址与 Controller 类的方法之间的映射
- 指定 http 请求的类型使用
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
@Autowired 与 @Resource 的区别
- @Autowired为Spring提供的注解,需要导入包org.springframework.beans.factory.annotation.Autowired,采取的策略为按照类型注入
- @Resource注解由J2EE提供,需要导入包javax.annotation.Resource,默认按照ByName自动注入
Mybatis 中 #{} 和 ${} 的区别
- #{} 表示一个占位符
可以有效的防止SQL注入
- ${} 表示拼接SQL串
可以用于动态判断字段order by ${field} desc可以动态修改fieId来达到动态根据字段降序查询
Mybatis 有几种分页方式
取出数据后,进行手动分割
修改执行SQL语句
将PageInfo信封装成RowBounds,调用DAO层方法
原理就是执行SQL语句前,在原SQL语句后加上limit关键字,不用自己去手动添加
RowBounds 是一次性查询全部结果吗?为什么
- RowBounds 表面是在“所有”数据中检索数据,其实并非是一次性查询出所有数据,因为 MyBatis 是对 jdbc 的封装,在 jdbc 驱动中有一个 Fetch Size 的配置,它规定了每次最多从数据库查询多少条数据,假如你要查询更多数据,它会在你执行 next()的时候,去查询更多的数据。就好比你去自动取款机取 10000 元,但取款机每次最多能取 2500 元,所以你要取 4 次才能把钱取完。只是对于 jdbc 来说,当你调用 next()的时候会自动帮你完成查询工作。这样做的好处可以有效的防止内存溢出。
Mybatis 逻辑分页和物理分页的区别是什么
- 逻辑分页是一次性查询很多数据,然后再在结果中检索分页的数据。这样做弊端是需要消耗大量的内存、有内存溢出的风险、对数据库压力较大。
- 物理分页是从数据库查询指定条数的数据,弥补了一次性全部查出的所有数据的种种缺点,比如需要大量的内存,对数据库查询压力较大等问题
Myabtis 是否支持延迟加载,延迟加载的原理是什么
- MyBatis 支持延迟加载,设置 lazyLoadingEnabled=true 即可
- 延迟加载的原理的是调用的时候触发加载,而不是在初始化的时候就加载信。比如调用 a.getB().getName(),这个时候发现 a.getB() 的值为 null,此时会单独触发事先保存好的关联 B 对象的 SQL,先查询出来 B,然后再调用 a.setB(b),而这时候再调用 a.getB(). getName() 就有值了,这就是延迟加载的基本原理
Mybatis 一级缓存和二级缓存
- 基于PerpetualCache的 HashMap本地缓存,它的声明周期是和 SQLSession一致的,有多个 SQLSession 或者分布式的环境中数据库操作,可能会出现脏数据
- 当Session flush或close 之后,该Sessio中的所有Cache 就将清空,默认一级缓存是开启的
- 也是基于PerpetualCache的 HashMap本地缓存,不同在于其存储作用域为Mapper 级别的,如果多个SQLSession之间需要共享缓存,则需要使用到二级缓存
- 二级缓存可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现 Serializable 序列化接口(可用来保存对象的状态)
- 开启二级缓存后的查询流程:二级缓存 -> 一级缓存 -> 数据库
- 缓存更新机制:当某一个作用域(一级缓存 Session/二级缓存 Mapper)进行了C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear
Mybatis 和 Hibernate 有哪些区别
- Mybatis 更加灵活,可以自己写SQL语句
- 也正是自己写了很多SQL语句,所以移植性比较差
- Mybatis 入门比较简单,使用门槛也低
- hibernate 可以自行把二级缓存更换为第三方的
Mybatis 有哪些执行器
Mybatis 分页插件的实现原理是什么
- 分页插件的基本原理是使用 MyBatis 提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的 SQL,然后重写 SQL,根据 dialect 方言,添加对应的物理分页语句和物理分页参数
Mybatis 如何编写一个自定义插件
MyBatis 自定义插件针对 MyBatis 四大对象(Executor、StatementHandler、ParameterHandler、ResultSetHandler)进行拦截
MyBatis 插件要实现 Interceptor 接口
public interface Interceptor { Object intercept(Invocation invocation) throws Throwable; Object plugin(Object target); void setProperties(Properties properties); }
官方插件实现:
@Intercepts({@Signature(type = Executor. class, method = "query", args = {MappedStatement. class, Object. class, RowBounds. class, ResultHandler. class})}) public class TestInterceptor implements Interceptor { public Object intercept(Invocation invocation) throws Throwable { Object target = invocation. getTarget(); //被代理对象 Method method = invocation. getMethod(); //代理方法 Object[] args = invocation. getArgs(); //方法参数 // do something . . . . . . 方法拦截前执行代码块 Object result = invocation. proceed(); // do something . . . . . . . 方法拦截后执行代码块 return result; } public Object plugin(Object target) { return Plugin. wrap(target, this); } }
具体案例请看 Mybatis 实现自定义插件通俗易懂
2|6MySQL部分面试题数据库的三范式是什么
- 第一范式:又称1NF,它指的bai是在一个应du用中的数据都可以组织zhi成由行和列的表格形式,且表格的任意一dao个行列交叉点即单元格,都不可再划分为行和列的形式,实际上任意一张表格都满足1NF
- 第二范式:又称2NF,它指的是在满足1NF的基础上,一张数据表中的任何非主键字段都全部依赖于主键字段,没有任何非主键字段只依赖于主键字段的一部分。即,可以由主键字段来唯一的确定一条记录。比如学号+课程号的联合主键,可以唯一的确定某个成绩是哪个学员的哪门课的成绩,缺少学号或者缺少课程号,都不能确定成绩的意义
- 第三范式:又称3NF,它是指在满足2NF的基础上,数据表的任何非主键字段之间都不产生函数依赖,即非主键字段之间没有依赖关系,全部只依赖于主键字段。例如将学员姓名和所属班级名称放在同一张表中是不科学的,因为学员依赖于班级,可将学员信和班级信单独存放,以满足3NF
如何获取当前数据库的版本
说一下 ACID 是什么
- ACID,是指在可靠数据库管理系统(DBMS)中,事务(transaction)所应该具有的四个特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)
char 和 varchar 的区别
- char 长度设置后不可变
- varchar 长度可动态改变
- char 类型每次修改的数据长度相同,效率更高
- varchar 类型每次修改的数据长度不同,效率更低
- char 类型存储的时候是初始预计字符串再加上一个记录字符串长度的字节,占用空间较大
- varchar 类型存储的时候是实际字符串再加上一个记录字符串长度的字节,占用空间较小
float 和 double 的区别
- 在数据库中的所有计算都是使用双精度完成的,使用float(单精度)会有误差,出现意想不到的结果
- MySQL浮点型和定点型可以用类型名称后加(M,D)来表示,M表示该值的总共长度,D表示小数点后面的长度,M和D又称为精度和标度,如float(7,4)的可显示为-999.9999,MySQL保存值时进行四舍五入,如果插入999.00009,则结果为999.0001。FLOAT和DOUBLE在不指定精度时,默认会按照实际的精度来显示,而DECIMAL在不指定精度时,默认整数为10,小数为0
MySQL 内连接、左连接、右连接有什么区别
- inner join ... on内连接:只显示两表中有关联的数据
- left join ... on左连接:显示左表所有数据,右表没有对应的数据用NULL补齐,多了的数据删除
- right join ... on右连接:显示右表所有数据,左表没有对应的数据用NULL对齐,多了的数据删除
MySQL 的索引是怎么实现的
- 由于B+Tree数据结构的优势,目前mysql基本都采用B+Tree方式实现索引
- MySQL索引实现的数据结构:两种存储引擎都使用B+Tree(B-Tree的变种)作为索引结构
- MyISAM索引叶子节点存放的是数据的地址,主键索引与辅助索引除了值得唯一性在结构上完全一样。InnoDB索引叶子节点存放的内容因索引类型不同而不同,主键索引叶子节点存放的是数据本身,辅助索引叶子节点上存放的是主键值
MySQL 索引设计原则
怎么验证 MySQL 的索引是否满足需求
- 使用explain函数验证索引是否有效
事务的隔离级别
MySQL 常用的引擎
InnoDB 和 Myisam 都是用 B+Tree 来存储数据的
MySQL 的行锁、表锁、页锁
- 行级锁
是Mysql中锁定粒度最细的一种锁,表示只针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突。其加锁粒度最小,但加锁的开销也最大。行级锁分为共享锁 和 排他锁。
- 行级锁的特点
开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
- 表级锁
表级锁是MySQL中锁定粒度最大的一种锁,表示对当前操作的整张表加锁,它实现简单,资源消耗较少,被大部分MySQL引擎支持。最常使用的MYISAM与INNODB都支持表级锁定。表级锁定分为表共享读锁(共享锁)与表独占写锁(排他锁)
- 表级锁的特点
开销小,加锁快;不会出现死锁;锁定粒度大,发出锁冲突的概率最高,并发度最低。
- 页级锁
页级锁是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。BDB支持页级锁
- 页级锁的特点
开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般
- 扩展
乐观锁和悲观锁
MySQL 问题排查都有哪些手段
- 使用 show processlist 命令查看当前所有连接信
- 使用 explain 命令查询 SQL 语句执行计划
- 开启慢查询日志,查看慢查询的 SQL
如何做 MySQL 的性能优化
Redis 是什么?有什么优点?都有哪些使用场景
- Reids 是完全开源免费的,用C语言编写的,遵守BSD协议, 是一个高性能的(key/value)分布式内存数据库,基于内存运行 并支持持久化的NoSQL数据库,是当前最热门的NoSql数据库之一, 也被人们称为数据结构服务器
- 优点
- 应用场景
Redis 为什么是单线程的
- Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈
Redis 的缓存预热
redis 缓存雪崩是什么,怎么解决 ?
缓存雪崩是指,缓存层出现了错误,不能正常工作了.于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况.
解决方案
缓存穿透是什么?如何解决
就是访问redis数据库,查不到数据,就是没有命中,会去持久化数据库查询,还是没有查到.假如高并发的情况下,持久化数据库一下增加了很大压力,就相当于出现了缓存穿透
解决方案
Redis 支持的数据类型有哪些
- String、List、Set、Hash、ZSet这5种
Redis 支持的 Java 客户端有哪些
- Redisson、Jedis、lettuce 等等,官方推荐使用 Redisson
Jedis 与 Redisson 有哪些区别
- Jedis 和 Redisson 都是Java中对Redis操作的封装。Jedis 只是简单的封装了 Redis 的API库,可以看作是Redis客户端,它的方法和Redis 的命令很类似。Redisson 不仅封装了 redis ,还封装了对更多数据结构的支持,以及锁等功能,相比于Jedis 更加大。但Jedis相比于Redisson 更原生一些,更灵活
怎么保证缓存与数据库数据的一致性
Redis 持久化有几种方式
- 快照方式(RDB, Redis DataBase)将某一个时刻的内存数据,以二进制的方式写入磁盘
- 文件追加方式(AOF, Append Only File),记录所有的操作命令,并以文本的形式追加到文件中
- 混合持久化方式,Redis 4.0 之后新增的方式,混合持久化是结合了 RDB 和 AOF 的优点,在写入的时候,先把当前的数据以 RDB 的形式写入文件的开头,再将后续的操作命令以 AOF 的格式存入文件,这样既能保证 Redis 重启时的速度,又能简单数据丢失的风险
Redis 怎么实现分布式锁
- SET key value [EX seconds] [PX milliseconds] [NX|XX]
- EX second :设置键的过期时间为second秒
- PX millisecond :设置键的过期时间为millisecond毫秒
- NX :只在键不存在时,才对键进行设置操作
- XX :只在键已经存在时,才对键进行设置操作
- SET操作成功完成时,返回OK ,否则返回nil
Redis 分布式锁有什么缺陷
- 设置锁的过期时间,且需要保证setNx和设置过期时间操作的原子性
- 加锁时记录当前线程ID,解锁时判断ID是否一致
- 解锁时,查询redis里记录锁的ID,以及删除redis中锁的记录,这两步操作可以使用lua脚本保持原子性
- 加锁成功后开启守护线程,当临近过期时间,业务还未完成时,开始续时,重复此步骤直到业务完成
Redis 如何做内存优化
- 缩减键值对象:满足业务要求下 key 越短越好;value 值进行适当压缩
- 共享对象池:即 Redis 内部维护[0-9999]的整数对象池,开发中在满足需求的前提下,尽量使用整数对象以节省内存
- 尽可能使用散列表(hashes)
- 编码优化,控制编码类型
- 控制 key 的数量
Redis 淘汰策略有哪些
- noeviction: 不删除策略, 达到最大内存限制时, 如果需要更多内存, 直接返回错误信。 大多数写命令都会导致占用更多的内存(有极少数会例外, 如 DEL )
- allkeys-lru: 所有key通用; 优先删除最近最少使用(less recently used ,LRU) 的 key
- volatile-lru: 只限于设置了 expire 的部分; 优先删除最近最少使用(less recently used ,LRU) 的 key
- allkeys-random: 所有key通用; 随机删除一部分 key
- volatile-random: 只限于设置了 expire 的部分; 随机删除一部分 key
- volatile-ttl: 只限于设置了 expire 的部分; 优先删除剩余时间(time to live,TTL) 短的key
Redis 常见的问题有哪些? 该如何解决
- 就是如果对数据有强一致性要求,不能放缓存。我们所做的一切,只能保证最终一致性
- 采取正确更新策略,先更新数据库,再删缓存。其次,因为可能存在删除缓存失败的问题,提供一个补偿措施即可,例如利用消队列
- 利用互斥锁,缓存失效的时候,先去获得锁,得到锁了,再去请求数据库。没得到锁,则休眠一段时间重试
- 采用异步更新策略,无论 Key 是否取到值,都直接返回。Value 值中维护一个缓存失效时间,缓存如果过期,异步起一个线程去读数据库,更新缓存。需要做缓存预热(项目启动前,先加载缓存)操作
- 提供一个能迅速判断请求是否有效的拦截机制,比如,利用布隆过滤器,内部维护一系列合法有效的 Key。迅速判断出,请求所携带的 Key 是否合法有效。如果不合法,则直接返回
- 给缓存的失效时间,加上一个随机值,避免集体失效
- 使用互斥锁,但是该方案吞吐量明显下降
- 双缓存。我们有两个缓存,缓存 A 和缓存 B。缓存 A 的失效时间为 20 分钟,缓存 B 不设失效时间。自己做缓存预热操作(从A中读不到,就去B读,返回数据时需要异步启动一个更新线程,更新线程同时更新缓存 A 和缓存 B)
- 这种情况下,准备一个分布式锁,大家去抢锁,抢到锁就做 set 操作即可,比较简单。
- 假设有一个 key1,系统 A 需要将 key1 设置为 valueA,系统 B 需要将 key1 设置为 valueB,系统 C 需要将 key1 设置为 valueC
- 期望按照 key1 的 value 值按照 valueA > valueB > valueC 的顺序变化。这种时候我们在数据写入数据库的时候,需要保存一个时间戳。
- 系统A key 1 {valueA 3:00}
- 系统B key 1 {valueB 3:05}
- 系统C key 1 {valueC 3:10}
- 那么,假设这会系统 B 先抢到锁,将 key1 设置为{valueB 3:05}。接下来系统 A 抢到锁,发现自己的 valueA 的时间戳早于缓存中的时间戳,那就不做 set 操作了,以此类推。
- 其他方法,比如利用队列,将 set 方法变成串行访问也可以。总之,灵活变通。
RabbitMq 的使用场景有哪些
- 比如:注册用户、发送激活邮箱、订单下单等
RabbitMq 有哪些重要的角色
- 生产者:消的创建者,负责创建和推送数据到消服务器
- 消费者:消的接收方,负责处理数据和确认消
- 代理:就是RabbiMQ本身,不生产不消费,只是快递消
RabbitMq 有哪些重要的组件
- ConnectionFactory(连接管理器):应用程序与Rabbit之间建立连接的管理器,程序代码中使用
- Channel(信道):消推送使用的通道
- Exchange(交换器):用于接受、分配消
- Queue(队列):用于存储生产者的消
- RoutingKey(路由键):用于把生成者的数据分配到交换器上
- BindingKey(绑定键):用于把交换器的消绑定到队列上
RabbitMQ的消存储方式
RabbitMQ 对于 queue 中的 message 的保存方式有两种方式:disc 和 ram.如果采用disc,则需要对 exchange/queue/delivery mode 都要设置成 durable 模式. Disc 方式的好处是当 RabbitMQ 失效了, message 仍然可以在重启之后恢复.而使用 ram 方式, RabbitMQ 处理 message 的效率要高很多, ram 和 disc 两种方式的效率比大概是 3:1.所以如果在有其它 HA 手段保障的情况下,选用 ram 方式是可以提高消队列的工作效率的.
RabbitMq 中 vhost 的作用是什么
- vhost本质上是一个mini版的RabbitMQ服务器,拥有自己的队列、绑定、交换器和权限控制
- 从 RabbitMQ 的全局角度 vhost可以作为不同权限隔离的手段(一个典型的例子,不同的应用可以跑在不同的vhost中)
RabbitMq 的消是怎么发送的
- 生产者把生产的小心通过channel发送到Exchange上,Exchange通过绑定的router key来选择Queue,消费者监听到Queue上有新的消,就消费调此消
RabbitMq 怎么保证消的稳定性
- 提供了事务的功能,通过将 channel 设置为 confirm(确认模式)
RabbitMq 怎么避免丢失消
要保证消持久化成功的条件有哪些
- 以上四个条件都满足才能保证消持久化成功
RabbitMq 持久化有什么缺点
- 持久化的缺点就是降低了服务器的吞吐量,因为使用的是磁盘而非内存存储,从而降低了吞吐量。可尽量使用 ssd 硬盘来缓解吞吐量的问题
RabbitMq 有几种广播方式
RabbitMq 怎么实现延迟消队列
- 通过消过期后进入死信交换器,再由交换器转发到延迟消费队列,实现延迟功能
- 使用 RabbitMQ-delayed-message-exchange 插件实现延迟功能。
RabbitMq 集群有什么用
- 高可用:某个服务器出现问题,整个 RabbitMQ 还可以继续使用
- 高容量:集群可以承载更多的消量。
RabbitMq 节点的类型有哪些
- 磁盘节点:消会存储到磁盘
- 内存节点:消都存储在内存中,重启服务器消丢失,性能高于磁盘类型
RabbitMq 集群搭建需要注意哪些问题
RabbitMq 每个节点是其他节点的完整拷贝吗
不是
- 如果每个节点都拥有所有队列的完全拷贝,这样新增节点,不但没有新增存储空间,反而增加了更多的冗余数据
- 如果每条消都需要完整拷贝到每一个集群节点,那新增节点并没有提升处理消的能力,最多是保持和单节点相同的性能甚至是更糟
RabbitMq 集群中唯一一个磁盘节点崩溃了会发生什么
- 唯一磁盘节点崩溃了,集群是可以保持运行的,但不能更改任何东西
RabbitMq 对集群停止顺序有要求吗
- RabbitMQ 对集群的停止的顺序是有要求的,应该先关闭内存节点,最后再关闭磁盘节点。如果顺序恰好相反的话,可能会造成消的丢失
JVM 主要的组成部分?及其作用
JVM 运行时数据区是什么
- 堆是java对象的存储区域,任何用new字段分配的java对象实例和数组,都被分配在堆上,java堆可用-Xms和-Xmx进行内存控制,
- 常量池:运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信外,还有一项信是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放,jdk1.7以后,运行时常量池从方法区移到了堆上
- 方法区:用于存储已被虚拟机加载的类信,常量,静态变量,即是编译器编译后的代码等数据
- 虚拟机栈:虚拟机栈中执行每个方法的时候,都会创建一个栈桢用于存储局部变量表,操作数栈,动态链接,方法出口等信
- 本地方法栈:与虚拟机发挥的作用相似,相比于虚拟机栈为Java方法服务,本地方法栈为虚拟机使用的Native方法服务,执行每个本地方法的时候,都会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信
- 程序计数器:指示Java虚拟机下一条需要执行的字节码指令
堆和栈的区别
队列和栈是什么?有什么区别
- 队列和栈是两种不同的数据结构
- 队列的插入称为入队,队列的删除称为出队。栈的插入称为进栈,栈的删除称为出栈
- 队列是在队尾入队,队头出队,即两边都可操作。而栈的进栈和出栈都是在栈顶进行的,无法对栈底直接进行操作。
- 队列先进先出 FIFO,栈是后进先出 LIFO
类加载器有哪些?什么是双亲委派模型
双亲委派的意思是如果一个类加载器需要加载类,那么首先它会把这个类请求委派给父类加载器去完成,每一层都是如此。一直递归到顶层,当父加载器无法完成这个请求时,子类才会尝试去加载。这里的双亲其实就指的是父类,没有mother。父类也不是我们平日所说的那种继承关系,只是调用逻辑是这样
类加载的执行过程
- 加载
- 链接
主要是为了保证加载进来的字节流符合虚拟机规范,不会造成安全错误,包括对于文件格式的验证
主要是为类变量(注意,不是实例变量)分配内存,并且赋予初值。特别需要注意,初值,不是代码中具体写的初始化的值,而是Java虚拟机根据不同变量类型的默认初始值:8种基本类型的初值,默认为0;引用类型的初值则为null;
将常量池内的符号引用替换为直接引用的过程。在解析阶段,虚拟机会把所有的类名,方法名,字段名这些符号引用替换为具体的内存地址或偏移量,也就是直接引用
- 初始化
这个阶段主要是对类变量初始化,是执行类构造器的过程
怎么判断对象是否可以收回
- 判断对象的引用数量
- 优缺点
- 通过判断对象的引用链是否可达来决定对象是否可以被回收
- 可以作为GC Root对象的对象有
Java 中有哪些引用类型
就是指在程序代码之中普遍存在的,类似“Object obj=new Object()” 这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象实例
是用来描述一些还有用但并非必需的对象。对于软引用关联着的对象, 在系统将要发生内存溢出异常之前,将会把这些对象实例列进回收范围之中进行 第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。在 JDK 1.2 之后,提供了SoftReference 类来实现软引用
也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱 引用关联的对象实例只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时, 无论当前内存是否足够,都会回收掉只被弱引用关联的对象实例。在 JDK 1.2 之 后,提供了WeakReference 类来实现弱引用
也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象实例是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用 来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象 实例被收集器回收时收到一个系统通知。在 JDK 1.2 之后,提供了 PhantomReference 类来实现虚引用
JVM 中垃圾回收算法
标记-清除算法采用从根集合(GC Roots)进行扫描,对存活的对象进行标记,标记完毕后,再扫描整个空间中未被标记的对象,进行回收,如下图所示。标记-清除算法不需要进行对象的移动,只需对不存活的对象进行处理,在存活对象比较多的情况下极为高效,但由于标记-清除算法直接回收不存活的对象,因此会造成内存碎片
复制算法的提出是为了克服句柄的开销和解决内存碎片的问题。它开始时把堆分成 一个对象 面和多个空闲面, 程序从对象面为对象分配空间,当对象满了,基于copying算法的垃圾 收集就从根集合(GC Roots)中扫描活动对象,并将每个 活动对象复制到空闲面(使得活动对象所占的内存之间没有空闲洞),这样空闲面变成了对象面,原来的对象面变成了空闲面,程序会在新的对象面中分配内存
标记-整理算法采用标记-清除算法一样的方式进行对象的标记,但在清除时不同,在回收不存活的对象占用的空间后,会将所有的存活对象往左端空闲空间移动,并更新对应的指针。标记-整理算法是在标记-清除算法的基础上,又进行了对象的移动,因此成本更高,但是却解决了内存碎片的问题
JVM 有哪些垃圾回收器
- 新生代收集器:Serial、ParNew、Parallel Scavenge
- 老年代收集器:CMS、Serial Old、Parallel Old
- 整堆收集器: G1
介绍一下 CMS 垃圾回收器
- CMS收集器 :一种以获取最短回收停顿时间为目标的收集器
- 特点:基于标记-清除算法实现。并发收集、低停顿
- 应用场景:适用于注重服务的响应速度,希望系统停顿时间最短,给用户带来更好的体验等场景下。如web程序、b/s服务
- CMS收集器的运行过程分为下列4步:
- CMS收集器的内存回收过程是与用户线程一起并发执行的
- CMS收集器的缺点:
- 对CPU资源非常敏感
- 无法处理浮动垃圾,可能出现Concurrent Model Failure失败而导致另一次Full GC的产生
- 因为采用标记-清除算法所以会存在空间碎片的问题,导致大对象无法分配空间,不得不提前触发一次Full GC
CMS收集器的工作过程图:
新生代垃圾回收器和老生代垃圾回收器有哪些?有什么区别
- 新生代收集器:Serial、ParNew、Parallel Scavenge
- 老年代收集器:CMS、Serial Old、Parallel Old
- 区别:
新生代垃圾回收器一般采用的是复制算法,复制算法的优点是效率高,缺点是内存利用率低;老年代回收器一般采用的是标记-整理的算法进行垃圾回收
简述分代垃圾回收器是怎么工作的
- 分代回收器有两个分区:老生代和新生代,新生代默认的空间占比总空间的 1/3,老生代的默认占比是 2/3。 新生代使用的是复制算法,新生代里有 3 个分区:Eden、To Survivor、From Survivor,它们的默认占比是 8:1:1
- 执行流程
JVM 调优的工具有哪些
- jconsole jdk自带的工具:是一个基于JMX(java management extensions)的GUI性能监测工具(jdk/bin目录下点击jconsole.exe即可启动)
- VisualVM:它提供了一个可视界面,用于查看 Java 虚拟机 (Java Virtual Machine, JVM) 上运行的基于 Java 技术的应用程序(Java 应用程序)的详细信(jdk/bin目录下面双击jvisualvm.exe既可使用)
- MAT 第三方调优工具:一个基于Eclipse的内存分析工具,是一个快速、功能丰富的Java heap分析工具,它可以帮助我们查找内存泄漏和减少内存消耗(MAT以eclipse 插件的形式来安装)
- GChisto:专业分析gc日志的工具,可以通过gc日志来分析:Minor GC、full gc的时间、频率等等,通过列表、报表、图表等不同的形式来反应gc的情况(配置好本地的jdk环境之后,双击GChisto.jar,在弹出的输入框中点击 add 选择gc.log日志)
- gcviewer:分析小工具,用于可视化查看由Sun / Oracle, IBM, HP 和 BEA Java 虚拟机产生的垃圾收集器的日志
JVM 调优的参数有哪些
2|10算法题 2|11其他部分面试题Api 接口如何实现 ?
在类里使用 implements 关键字实现 Api 接口
MySQL 链接数据库常用的几种方式 ?
SpringBoot 如何集成 Redis ?
在 pom.xml 文件引入 redis 依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
在 application 配置文件中 书写 redis 配置
spring.redis.host=127.0.0.1 #Redis服务器连接端口 spring.redis.port=6379 #Redis服务器连接密码(默认为空) #spring.redis.password=
SpringCloud 的优点 ?
SpringCloud 用了哪些组件 ?
List 和 Set 的区别
扩展
Map的实现类有HashMap、HashTable、TreeMap
Java 中 static 的作用
什么单例模式 ?
保证整个项目中一个类只有一个对象的实例,实现这种功能就叫做单例模式
把对象创建好,需要使用的时候直接拿到就行
等你需要的时候在创建对象,后边就不会再次创建
SpringBoot 常用的几个注解 ?
Java 八大数据类型
char 字符型 byte 字节型 boolean 布尔型
float 单浮点型 double 双浮点型
int 整数型 short 短整数型 long 长整数型
MySQL分页和升序降序如何实现 ?
select name,age,sex from t_student limit(0,5);
select name,age,sex from t_student order by age asc;
select name,age,sex from t_student order by age desc;
maven 是干什么的,它有什么好处 ?
MySQL 如何添加索引 ?
MySQL 索引的实现方式?
MySQL 索引底层的实现方式是 B+Tree也就是B+树 具体查看 B+Tree实现方式
Vue的数据双向绑定原理
使用v-mode属性, 它的原理是利用了Object.defineProperty()方法重新定义了对象获取属性值(get)和设置属性值(set)的操作来实现的
ActiveMQ的消存储方式
- 采取先进先出模式,同一时间,消只会发送给某一个消费者,只有当该消被消费并告知已收到时,它才能在代理的存储中被删除.
- 对于持久性订阅来说,每一个消费者都会获取消的拷贝.为了节约空间,代理的存储介质中只存储了一份消,存储介质的持久订阅对象为其以后的被存储的消维护了一个指针,消费者消费时,从存储介质中复制一个消.消被所有订阅者获取后才能删除.
KahaDB消存储
基于文件的消存储机制,为了提高消存储的可靠性和可恢复性,它整合了一个事务日志.KahaDB拥有高性能和可扩展性等特点.由于KahaDB使用的是基于文件的存储,所以不需要使用第三方数据库
版权声明:本文标题:Java常见面试题 非常实用 内容由林淑君副主任自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.xiehuijuan.com/baike/1686822695a107084.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论