Java 面试题
一. 容器部分
二. 多线程部分
三. SpringMvc部分
四. Mybatis部分
五. MySQL部分
六. Redis部分
七. RabbitMQ部分
八. JVM虚拟机部分
九. 算法知识部分
十. 其他面试部分
2|1更新
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 相比较
-
Set 检索元素效率较低,删除和插入效率高,因为删除和插入不会引起元素的位置变化
-
List 可动态增长,查找元素效率高,但是删除和插入效率低,因为删除或插入一条元素,会引起其他元素位置变化
-
Map 适合存储键值对的数据
HashMap 和 HashTable 的区别
-
继承的父类不同,但二者都实现了 Map接口
-
HashTable 继承自 Dictionary 类
-
HashMap 继承自 AbstractMap 类
-
线程安全不同
-
HashMap 在缺省的情况下是非Synchronize的
-
HashTable 的方法是Synchronize的
-
在多线程下直接使用 HashTable 不需要自己为它的方法实现同步。但使用 HashMap 时需要手动增加同步处理Map m = Collections.synchronizeMap(hashMap);
-
是否提供 contains() 方法
-
HashMap 把 HashTable 的 contains() 方法去掉了,改成了 containsValue() 和 containsKey(),因为 contains 容易让人误解
-
HashTable 则保留了 contains()、containsValue()、containsKey() 三个方法,其中 contains() 与 containsValue() 功能相同
-
key 和 value 是否可以为 null 值
-
HashTable 中,key 和 value 都不能为 null 值
-
HashMap 中,null 作为键,但这样的键只有一个。可以有多个 value 为 null 值的键。当 get() 方式返回 null 有可能是 HashMap 中没有该键,也有可能返回的 value 为 null。所以 HashMap 用containsKey()方法判断是否存在键
-
遍历方式不同
-
HashTable 与 HashMap 都是使用 Iterator 迭代器遍历,而由于历史的原因,HashTable 还使用了 Enumeration 的方式
-
hash 值不同
-
哈希值的使用不同,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 的区别是什么
-
AarrayList 是动态数组构成 LinkList 是链表组成
-
AarrayList 常用于经常查询的集合,因为 LinkList 是线性存储方式,需要移动指针从前往后查找
-
LinkList 常用于新增和删除的集合,因为 ArrayList 是数组构成,删除某个值会对下标影响,需要进行数据的移动
-
AarrayList 自由度较低,需要手动设置固定的大小,但是它的操作比较方便的,①直接创建②添加对象③根据下标进行使用
-
LinkList 自由度较高,能够动态的随数组的数据量而变化
-
ArrayList 主要开销在List需要预留一定空间
-
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
哪些集合类是线程安全的
-
Vector :比 ArrayList 多了同步化机制(线程安全)
-
HashTable :比 HashMap 多了线程安全
-
ConcurrentHashMap :是一种高效但是线程安全的集合
-
Stack :栈,继承于 Vector 也是线程安全
迭代器 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 有什么区别
-
使用范围不同
-
Iterator 适用于所有集合, Set、List、Map以及这些集合的子类型,而 ListIterator 只适用于 List 及其子类型
-
ListIterator 有 add() 方法,可以向 List 中添加元素,而 Iterator 不能
-
ListIterator 和 Iterator 都有 hasNext() 和 next() 方法,来实现顺序向后遍历。而 ListIterator 有 hasPrevious() 和 previous() 方法,可以实现逆向遍历,但是 Iterator 不能
-
ListIterator 可以使用 nextIdnex() 和 previousIndex() 方法定位到当前索引位置,而 Iterator 不能
-
它们都可以实现 remove() 删除操作,但是 ListIterator 可以使用 set() 方法实现对象修改,而 Iterator 不能
怎么确保一个集合不能被修改
-
可以采用 java.util.Collections 工具类
-
Collections.unmodifiableMap(map)
-
Collections.unmodifiableList(list)
-
Collections.unmodifiableSet(set)
-
如诺修改则会报错java.lang.UnsupportedOperationException
2|3多线程部分面试题
并发和并行有什么区别
-
并发:不同的代码块交替执行
-
并行:不同的代码块同时执行
-
个人理解
-
并发就是放下手头的任务A去执行另外一个任务B,执行完任务B后,再回来执行任务A,就比如说吃饭时来电话了,去接电话,打完电话后又回来吃饭
-
并行就是执行A的同时,接受到任务B,然后我一起执行,就比如说吃饭时来电话了,一边吃饭一边打电话
线程和进程的区别
-
根本区别 :进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位
-
在操作系统中能同时运行多个进程,进程中会执行多个线程
-
线程是操作系统能够进行运算调度的最小单位
守护线程是什么
-
JVM内部的实现是如果运行的程序只剩下守护线程的话,程序将终止运行,直接结束。所以守护线程是作为辅助线程存在的
创建线程有哪几种方式
-
继承Thread类创建线程类
-
定义Thread类的子类,并重写该类的run()方法
-
创建Thread子类的实例,即创建了线程对象
-
调用线程对象的start()方法来启动该线程
-
实现Runnable接口创建线程类
-
创建runnable接口的实现类,并重写该接口的run()方法
-
创建Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该 Thread对象才是真正的线程对象
-
调用线程对象的start()方法来启动该线程
-
通过 Callable 和 Future 创建线程
-
创建Callable接口的实现类,并重写call()方法,该call()方法将作为线程执行体,并且有返回值
-
创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值
-
使用FutureTask对象作为Thread对象的target创建并启动新线程
-
调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
runnable 和 callable 有什么区别
-
相同点
-
都是接口
-
都可以编写多线程程序
-
都是采用Thread.start()启动线程
-
不同点
-
Runnable没有返回值,Callable可以返回执行结果,是个泛型和Future、FutureTask配合可以用来获取异步执行的结果
-
Callable接口的call()方法允许抛出异常,Runnable的run()方法异常只能在内部消化,不能往上继续抛
注意 Callalble接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞
线程有哪些状态
-
新建 就绪 运行 阻塞 死亡
sleep() 和 wait() 的区别
-
最大区别是sleep() 休眠时不释放同步对象锁,其他线程无法访问 而wait()休眠时,释放同步对象锁其他线程可以访问
-
sleep() 执行完后会自动恢复运行不需要其他线程唤起.而 wait() 执行后会放弃对象的使用权,其他线程可以访问到,需要其他线程调用 Monitor.Pulse() 或者 Monitor.PulseAll() 来唤醒或者通知等待队列
线程的 run() 和 start() 的区别
-
start() 是来启动线程的
-
run() 只是 Thread 类中一个普通方法,还是在主线程中执行
notify() 和 notifyAll() 有什么区别
-
notify() 方法随机唤醒对象的等待池中的一个线程,进入锁池
-
notifyAll() 唤醒对象的等待池中的所有线程,进入锁池。
创建线程池有哪几种方式
-
利用Executors创建线程池
-
newCachedThreadPool(),它是用来处理大量短时间工作任务的线程池
-
newFixedThreadPool(int nThreads),重用指定数目nThreads的线程
-
newSingleThreadExecutor(),它的特点在于工作线程数目限制为1
-
newSingleThreadScheduledExecutor()和newScheduledThreadPool(int corePoolSize),创建的是个ScheduledExecutorService,可以进行定时或周期性的工作调度,区别在于单一工作线程还是多个工作线程。
-
newWorkStealingPool(int parallelism),这是一个经常被人忽略的线程池,Java 8 才加入这个创建方法,其内部会构建ForkJoinPool,利用Work-Stealing算法,并行地处理任务,不保证处理顺序
线程池都有哪些状态
-
运行 RUNNING:线程池一旦被创建,就处于 RUNNING 状态,任务数为 0,能够接收新任务,对已排队的任务进行处理
-
关闭 SHUTDOWN:不接收新任务,但能处理已排队的任务。调用线程池的 shutdown() 方法,线程池由 RUNNING 转变为 SHUTDOWN 状态
-
停止 STOP:不接收新任务,不处理已排队的任务,并且会中断正在处理的任务。调用线程池的 shutdownNow() 方法,线程池由(RUNNING 或 SHUTDOWN ) 转变为 STOP 状态
-
整理 TIDYING:
-
SHUTDOWN状态下,任务数为 0, 其他所有任务已终止,线程池会变为 TIDYING 状态,会执行 terminated() 方法。线程池中的 terminated() 方法是空实现,可以重写该方法进行相应的处理
-
线程池在 SHUTDOWN 状态,任务队列为空且执行中任务为空,线程池就会由 SHUTDOWN 转变为 TIDYING 状态
-
线程池在 STOP 状态,线程池中执行中任务为空时,就会由 STOP 转变为 TIDYING 状态
-
终止 TERMINATED:线程池彻底终止。线程池在 TIDYING 状态执行完 terminated() 方法就会由 TIDYING 转变为 TERMINATED 状态
线程池中的 submit() 和 execute() 有什么区别
-
两个方法都可以向线程池提交任务
-
execute()方法的返回类型是 void,它定义在Executor接口中
-
而submit()方法可以返回持有计算结果的 Future 对象,它定义在ExecutorService接口中
在Java程序中怎么确保多线程运行安全
-
使用synchronied关键字
-
使用volatile 关键字,防止指令重排,所有对该变量读写都是直接操作共享内存
-
lock锁机制 lock()与unlock()
-
使用线程安全的类 比如StringBuffer、HashTable、Vector等
线程安全问题主要是:原子性,可见性,有序性
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是可公平锁
2|4Spring Mvc面试题部分
为什么要是使用 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中模式
-
Before advice:方法执行前‘增强’
-
After returning advice:方法执行后‘增强’
-
After throwing advice:方法执行中抛出异常时‘增强’
-
After finally advice:方法执行完后‘增强’
-
Around advice:环绕增强‘以上四种的整合’
解释一下什么是 ioc
-
IOC是控制反转,是将代码本身的控制权移到外部Spring容器中进行集中管理,也是为了达到松耦合
Mybatis 如何编写一个自定义插件
MyBatis 自定义插件针对 MyBatis 四大对象(Executor、StatementHandler、ParameterHandler、ResultSetHandler)进行拦截
-
Executor:拦截内部执行器,它负责调用 StatementHandler 操作数据库,并把结果集通过 ResultSetHandler 进行自动映射,另外它还处理了二级缓存的操作 . StatementHandler:拦截 SQL 语法构建的处理,它是 MyBatis 直接和数据库执行 SQL 脚本的对象,另外它也实现了 MyBatis 的一级缓存
-
ParameterHandler:拦截参数的处理
-
ResultSetHandler:拦截结果集的处理
MyBatis 插件要实现 Interceptor 接口
-
setProperties 方法是在 MyBatis 进行配置插件的时候可以配置自定义相关属性,即:接口实现对象的参数配置
-
plugin 方法是插件用于封装目标对象的,通过该方法我们可以返回目标对象本身,也可以返回一个它的代理,可以决定是否要进行拦截进而决定要返回一个什么样的目标对象,官方提供了示例:return Plugin. wrap(target, this)
-
intercept 方法就是要进行拦截的时候要执行的方法
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
如何获取当前数据库的版本
-
进入MySQL输入select version();
-
进入cmd输入mysql -V
说一下 ACID 是什么
-
ACID,是指在可靠数据库管理系统(DBMS)中,事务(transaction)所应该具有的四个特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)
-
原子性:意味着数据库中的事务执行是作为原子。即不可再分,整个语句要么执行,要么不执行
-
一致性:即在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏
-
隔离性:事务的执行是互不干扰的,一个事务不可能看到其他事务运行时,中间某一时刻的数据
-
持久性,意味着在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚
2|7Redis部分面试题
Redis 是什么?有什么优点?都有哪些使用场景
-
Reids 是完全开源免费的,用C语言编写的,遵守BSD协议, 是一个高性能的(key/value)分布式内存数据库,基于内存运行 并支持持久化的NoSQL数据库,是当前最热门的NoSql数据库之一, 也被人们称为数据结构服务器
-
优点
-
Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用
-
Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储
-
Redis支持数据的备份,即master-slave模式的数据备份
-
应用场景
-
内存存储和持久化:redis支持异步将内存中的数据写到硬盘上,同时不影响继续服务
-
取最新N个数据的操作,如:可以将最新的10条评论的ID放在Redis的List集合里面
-
模拟类似于HttpSession这种需要设定过期时间的功能
-
发布、订阅消息系统
-
定时器、计数器
Redis 为什么是单线程的
-
Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈
Redis 的缓存预热
-
在项目配置文件中生命自定义的key ,在项目启动时会判断redis是否存在key ,如果没有就会创建一个key传入null值
-
数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中
redis 缓存雪崩是什么,怎么解决 ?
缓存雪崩是指,缓存层出现了错误,不能正常工作了.于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况.
解决方案
-
redis 高可用 就是搭建 redis 集群,其中一台redis挂掉后 可以使用其他的 redis
-
限流降级 就是每一个 key 只能一个线程来查询数据和缓存,其他线程等待
-
数据预热 数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中.在即将发生大并发访问前手动触发加载缓存不同的 key ,设置不同的过期时间,让缓存失效的时间点尽量均匀.*
缓存穿透是什么?如何解决
就是访问redis数据库,查不到数据,就是没有命中,会去持久化数据库查询,还是没有查到.假如高并发的情况下,持久化数据库一下增加了很大压力,就相当于出现了缓存穿透
解决方案
-
缓存空对象 在存储层命中失败后,即使返回空对象也将其缓存,并设置一个过期时间,之后访问的这个数据将会从缓存中取出,很好的保护了后端数据源,这样也会有出现问题 例如空值被缓存也就会增加大量的缓存空间,设置了过期时间还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响
-
布隆过滤器 对所有可能查询的参数以 hash 形式存储,查询时发现值不存在就直接丢弃,不会去持久层查询
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 更原生一些,更灵活
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 怎么避免丢失消息
-
消息持久化
-
消费端的ack签收机制
-
设置集群镜像模式
-
消息补偿机制
要保证消息持久化成功的条件有哪些
-
声明队列必须设置持久化 durable 设置为 true
-
消息推送投递模式必须设置持久化,deliveryMode 设置为 2(持久)
-
消息已经到达持久化交换器
-
消息已经到达持久化队列
-
以上四个条件都满足才能保证消息持久化成功
RabbitMq 持久化有什么缺点
-
持久化的缺点就是降低了服务器的吞吐量,因为使用的是磁盘而非内存存储,从而降低了吞吐量。可尽量使用 ssd 硬盘来缓解吞吐量的问题
RabbitMq 有几种广播方式
-
fanout广播模式:所有bind到此exchange的queue都可以接收消息
-
direct直接交换:通过routingKey和exchange决定的那个唯一的queue可以接收消息
-
topic通配符模式:所有符合routingKey(此时可以是一个表达式)的routingKey所bind的queue可以接收消息
RabbitMq 怎么实现延迟消息队列
-
通过消息过期后进入死信交换器,再由交换器转发到延迟消费队列,实现延迟功能
-
使用 RabbitMQ-delayed-message-exchange 插件实现延迟功能。
队列和栈是什么?有什么区别
Java 中有哪些引用类型
-
强引用(strongreference)
就是指在程序代码之中普遍存在的,类似“Object obj=new Object()” 这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象实例
-
软引用(softreference)
是用来描述一些还有用但并非必需的对象。对于软引用关联着的对象, 在系统将要发生内存溢出异常之前,将会把这些对象实例列进回收范围之中进行 第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。在 JDK 1.2 之后,提供了SoftReference 类来实现软引用
-
弱引用(weakreference)
也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱 引用关联的对象实例只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时, 无论当前内存是否足够,都会回收掉只被弱引用关联的对象实例。在 JDK 1.2 之 后,提供了WeakReference 类来实现弱引用
-
虚引用(phantomreference)
也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象实例是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用 来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象 实例被收集器回收时收到一个系统通知。在 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步:
-
初始标记:标记GC Roots能直接到的对象,速度很快但是仍存在Stop The World问题
-
并发标记:进行GC Roots Tracing 的过程,找出存活对象且用户线程可并发执行
-
重新标记:为了修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录。仍然存在Stop The World问题
-
并发清除:对标记的对象进行清除回收
-
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
-
执行流程
-
把 Eden + From Survivor 存活的对象放入 To Survivor 区
-
清空 Eden 和 From Survivor 分区; From Survivor 和 To Survivor 分区交换,From Survivor 变 To Survivor,To Survivor 变 From Survivor
-
每次在 From Survivor 到 To Survivor 移动时都存活的对象,年龄就 +1,当年龄到达 15(默认配置是 15)时,升级为老生代。大对象也会直接进入老生代
-
老生代当空间占用到达某个值之后就会触发全局垃圾收回,一般使用标记整理的执行算法
-
以上这些循环往复就构成了整个分代垃圾回收的整体执行流程
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 链接数据库常用的几种方式 ?
-
Mybatis 框架
-
Hibernate 框架
-
JDBC 技术
-
c3p0 连接池
-
dbcp 连接池
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 的优点 ?
-
服务之间采用Restful等轻量级通讯
-
精准的制定优化服务方案,提高系统的可维护性
-
服务之间拆分细致,资源可重复利用,提高开发效率
SpringCloud 用了哪些组件 ?
-
netflix Eureka 注册中心
-
netflix Ribbon 负载均衡
-
netflix Zuul 网关
-
netflix Hystrix 熔断器
-
feign 服务调用
List 和 Set 的区别
-
List 允许有多个重复对象,而 Set 不允许有重复对象
-
List 允许有多个NULL值,而 Set 只允许有一个NULL值
-
List 是一个有序的容器,输出顺序即是输入顺序
-
Set 是一个无序的容器无法保证每个元素的顺序,但是可以用 TreeSet 通过 Comparator 或者 Comparable 维护排序顺序
-
List的实现类有 ArrayList、LinkList、Vector 其中 ArrayList 最常用于查询较多,随意访问.LinkList 同于新增和删除较多的情况,Vector 表示底层数组,线程安全象
-
Set的实现类有 HashSet、TreeSet、LinkedHashSet 其中基于 HashMap 实现的 HashSet 最为流行,TreeSet 是一个有序的Set容器象
扩展 Map的实现类有HashMap、HashTable、TreeMap
Java 中 static 的作用
-
表示全局或静态的意思、用来修饰成员变量和成员方法,也可以形成静态代码块
-
达到了不用实例化就可以使用被 public static 修饰的变量或方法
什么单例模式 ?
保证整个项目中一个类只有一个对象的实例,实现这种功能就叫做单例模式
-
单例模式的好处:
-
单例模式节省公共资源
-
单例模式方便控制
-
如何保证是单利模式 ?
-
构造私有化
-
以静态方法返回实例
-
确保对象实例只有一个
-
单例模式有哪几个 ?
-
饿汉模式
把对象创建好,需要使用的时候直接拿到就行
-
懒汉模式
等你需要的时候在创建对象,后边就不会再次创建
SpringBoot 常用的几个注解 ?
-
@SpringBootApplication SpringBoot的核心注解 启动类
-
@EnableAutoConfiguration 开启SpringBoot自动配置
-
@RestController 在控制层 是@ResponseBody注解与@Controller注解的合集
-
@RequestMapper 处理请求地址映射的注解
-
@RequestParam 获取url上传过来的参数
-
@Configuration 声明配置类
-
@Component 通用注解
-
@Service 业务逻辑层
Java 八大数据类型
char 字符型 byte 字节型 boolean 布尔型
float 单浮点型 double 双浮点型
int 整数型 short 短整数型 long 长整数型
MySQL分页和升序降序如何实现 ?
-
可以用 limit
select name,age,sex from t_student limit(0,5);
-
升序 order by xx asc
select name,age,sex from t_student order by age asc;
-
降序 order by xx desc
select name,age,sex from t_student order by age desc;
maven 是干什么的,它有什么好处 ?
-
maven 专门构建和管理Java项目的工具
-
maven的好处在于可以将项目过程规范化、自动化、高效化以及强大的可扩展性
MySQL 如何添加索引 ?
-
PRIMARY KEY (主键索引)
-
添加INDEX(普通索引) ALTER TABLE table_name ADD INDEX index_name ( column )
-
添加UNIQUE(唯一索引) ALTER TABLE table_name ADD UNIQUE index_name ( column )
-
添加FULLTEXT(全文索引) ALTER TABLE table_name ADD FULLTEXT ( column)
-
添加多列索引 ALTER TABLE table_name ADD INDEX index_name ( column1, column2, column3 )
MySQL 索引的实现方式?
MySQL 索引底层的实现方式是 B+Tree也就是B+树 具体查看 B+Tree实现方式
Vue的数据双向绑定原理
使用v-mode属性, 它的原理是利用了Object.defineProperty()方法重新定义了对象获取属性值(get)和设置属性值(set)的操作来实现的
ActiveMQ的消息存储方式
-
采取先进先出模式,同一时间,消息只会发送给某一个消费者,只有当该消息被消费并告知已收到时,它才能在代理的存储中被删除.
-
对于持久性订阅来说,每一个消费者都会获取消息的拷贝.为了节约空间,代理的存储介质中只存储了一份消息,存储介质的持久订阅对象为其以后的被存储的消息维护了一个指针,消费者消费时,从存储介质中复制一个消息.消息被所有订阅者获取后才能删除.
KahaDB消息存储
基于文件的消息存储机制,为了提高消息存储的可靠性和可恢复性,它整合了一个事务日志.KahaDB拥有高性能和可扩展性等特点.由于KahaDB使用的是基于文件的存储,所以不需要使用第三方数据库
学习没资料,面试找不到方向怎么办?
下面是针对一到五年开发的Java程序员整理的Java实战视频+电子版本书籍+面试试题资料文档分享给大家学习!
面试试题资料及答案:
面试试题资料
电子版本书籍:
电子版本书籍
电子书
领取步骤:
1、转发+点赞文章
2、关注我,私信“电子书”,即可免费领取