Java全栈工程师面试宝典
2022-03-11 09:01:18 2 举报
AI智能生成
本文整理了java全栈工程师常见面试题,并持续更新,如果不对之处,忘大家提出来一起完善,一起学习,一起进步
作者其他创作
大纲/内容
一、Java知识面试点全集
第一题:请说说HashMap和HashTable的区别?
问题:
请说说HashMap和HashTable的区别?
参考答案:
1、HashMap 不是线程安全的、HashTable 是线程安全 Collection。
2、HashMap 是 map 接口的实现类,是将键映射到值的对象,
其中键和值都是对象,并且不能包含重复键,但可以包含重复值。
其中键和值都是对象,并且不能包含重复键,但可以包含重复值。
3、HashMap 允许 null key 和 null value,而 HashTable 不允许。
4、HashMap 是 HashTable 的轻量级实现
5、HashMap 把 Hashtable 的 contains 方法去掉了,改成 containsValue
和 containsKey。因为 contains 方法容易让人引起误解。
和 containsKey。因为 contains 方法容易让人引起误解。
6、HashTable 继承自 Dictionary 类,而 HashMap 是 Java1.2 引进的 Map
interface 的一个实现。
interface 的一个实现。
7、HashTable 的方法是 Synchronize 的,而 HashMap 不是,在多个线程访
问 Hashtable 时,不需要自己为它的方法实现同步,而 HashMap 就必须为之
提供同步方法。
问 Hashtable 时,不需要自己为它的方法实现同步,而 HashMap 就必须为之
提供同步方法。
第二题:请说说ArrayList 和 LinkedList 的区别?
问题:
请说说ArrayList 和 LinkedList 的区别是什么?
参考答案:
1、ArrayList是基于动态数组的数据结构实现,LinkedList是基于链表的数据结构实
现。
现。
2、随机访问数据get和set,ArrayList优于LinkedList,因为LinkedList要移动指针。
3、新增(add)和删除(remove)操作,LinedList比较占优势,因为ArrayList要
移动数据。
移动数据。
第三题:请说说Array 和 ArrayList 有何区别?
问题:
请说说Array 和 ArrayList 有何区别?
参考答案:
1、Array 可以包含基本数据类型和引用类型,ArrayList只能包含引用类型。
ArrayList是一个可变的数组。
ArrayList是一个可变的数组。
2、ArrayList是基于数组实现的,Array不可以调整大小,但ArrayList
可以通过内部方法自动调整容量。
可以通过内部方法自动调整容量。
3、ArrayList是List接口的实现类,相比Array支持更多的方法和特性。
第四题:请说出JDK7和JDK8中HashMap的大致变化有那些?
问题:
请说出JDK7和JDK8中HashMap的大致变化有那些?
参考答案:
1、1.7中采用数组+链表,1.8采用的是数组+链表/红黑树,即在1.8中链表长
度超过一定长度后就改成红黑树存储。
度超过一定长度后就改成红黑树存储。
2、1.7扩容时需要重新计算哈希值和索引位置,1.8并不重新计算哈希值,巧妙
地采用和扩容后容量进行&操作来计算新的索引位置。
地采用和扩容后容量进行&操作来计算新的索引位置。
3、1.7是采用表头插入法插入链表,1.8采用的是尾部插入法。
4、在1.7中采用表头插入法,在扩容时会改变链表中元素原本的顺序,以至于
在并发场景下导致链表成环的问题;在1.8中采用尾部插入法,在扩容时会保
持链表元素原本的顺序,就不会出现链表成环的问题了。
在并发场景下导致链表成环的问题;在1.8中采用尾部插入法,在扩容时会保
持链表元素原本的顺序,就不会出现链表成环的问题了。
第五题:什么是哈希表?
问题:
什么是哈希表?
参考答案:
1、了解一下其他数据结构
1、数组:采用一段连续的存储单元来存储数据。对于指定下标的查找,
时间复杂度为O(1);通过给定值进行查找,需要遍历数组,逐一比对
给定关键字和数组元素,时间复杂度为O(n),当然,对于有序数组,
则可采用二分查找,插值查找,斐波那契查找等方式,可将查找复杂
度提高为O(logn);对于一般的插入删除操作,涉及到数组元素的移
动,其平均复杂度也为O(n)
时间复杂度为O(1);通过给定值进行查找,需要遍历数组,逐一比对
给定关键字和数组元素,时间复杂度为O(n),当然,对于有序数组,
则可采用二分查找,插值查找,斐波那契查找等方式,可将查找复杂
度提高为O(logn);对于一般的插入删除操作,涉及到数组元素的移
动,其平均复杂度也为O(n)
2、线性链表:对于链表的新增,删除等操作(在找到指定操作位置后),
仅需处理结点间的引用即可,时间复杂度为O(1),而查找操作需要遍历
链表逐一进行比对,复杂度为O(n)
仅需处理结点间的引用即可,时间复杂度为O(1),而查找操作需要遍历
链表逐一进行比对,复杂度为O(n)
3、二叉树:对一棵相对平衡的有序二叉树,对其进行插入,查找,删除
等操作,平均复杂度均为O(logn)。
等操作,平均复杂度均为O(logn)。
4、哈希表:相比上述几种数据结构,在哈希表中进行添加,删除,查找等操作,性能
十分之高,不考虑哈希冲突的情况下,仅需一次定位即可完成,时间复杂度为O(1),
接下来我们就来看看哈希表是如何实现达到惊艳的常数阶O(1)的。
十分之高,不考虑哈希冲突的情况下,仅需一次定位即可完成,时间复杂度为O(1),
接下来我们就来看看哈希表是如何实现达到惊艳的常数阶O(1)的。
2、hash表的理解
1、我们知道,数据结构的物理存储结构只有两种:顺序存储结构和链式存储结构(像栈,
队列,树,图等是从逻辑结构去抽象的,映射到内存中,也这两种物理组织形式),
而在上面我们提到过,在数组中根据下标查找某个元素,一次定位就可以达到,哈希
表利用了这种特性,哈希表的主干就是数组。
队列,树,图等是从逻辑结构去抽象的,映射到内存中,也这两种物理组织形式),
而在上面我们提到过,在数组中根据下标查找某个元素,一次定位就可以达到,哈希
表利用了这种特性,哈希表的主干就是数组。
2、比如我们要新增或查找某个元素,我们通过把当前元素的关键字 通过某个函数映射到数
组中的某个位置,通过数组下标一次定位就可完成操作
组中的某个位置,通过数组下标一次定位就可完成操作
3、存储位置 = f(关键字)
4、其中,这个函数f一般称为哈希函数,这个函数的设计好坏会直接影响到哈希表的优劣。
举个例子,比如我们要在哈希表中执行插入操作:
查找操作同理,先通过哈希函数计算出实际存储地址,然后从数组中对应地址取出即可。
举个例子,比如我们要在哈希表中执行插入操作:
查找操作同理,先通过哈希函数计算出实际存储地址,然后从数组中对应地址取出即可。
5、哈希冲突:然而万事无完美,如果两个不同的元素,通过哈希函数得出的实际存储地址
相同怎么办?也就是说,当我们对某个元素进行哈希运算,得到一个存储地址,然后要进
行插入的时候,发现已经被其他元素占用了,其实这就是所谓的哈希冲突,也叫哈希碰撞。
前面我们提到过,哈希函数的设计至关重要,好的哈希函数会尽可能地保证 计算简单和散
列地址分布均匀,但是,我们需要清楚的是,数组是一块连续的固定长度的内存空间,再好
的哈希函数也不能保证得到的存储地址绝对不发生冲突。
那么哈希冲突如何解决呢?哈希冲突的解决方案有多种:开放定址法(发生冲突,继续寻找
下一块未被占用的存储地址),再散列函数法,链地址法,而HashMap即是采用了链地址
法,也就是数组+链表的方式
相同怎么办?也就是说,当我们对某个元素进行哈希运算,得到一个存储地址,然后要进
行插入的时候,发现已经被其他元素占用了,其实这就是所谓的哈希冲突,也叫哈希碰撞。
前面我们提到过,哈希函数的设计至关重要,好的哈希函数会尽可能地保证 计算简单和散
列地址分布均匀,但是,我们需要清楚的是,数组是一块连续的固定长度的内存空间,再好
的哈希函数也不能保证得到的存储地址绝对不发生冲突。
那么哈希冲突如何解决呢?哈希冲突的解决方案有多种:开放定址法(发生冲突,继续寻找
下一块未被占用的存储地址),再散列函数法,链地址法,而HashMap即是采用了链地址
法,也就是数组+链表的方式
3、
第六题:说说Object类下面有几种方法呢?
问题:
说说Object类下面有几种方法呢?
参考答案:
1、总体结构图
2、Object():这个没什么可说的,Object类的构造方法。
3、registerNatives():通过使用registerNatives(或者更确切地说,
JNI函数RegisterNatives),可以命名任何你想要你的C函数
JNI函数RegisterNatives),可以命名任何你想要你的C函数
4、clone():clone()函数的用途是用来另存一个当前存在的对象。只有实现了
Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常
Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常
5、getClass():final方法,用于获得运行时的类型。该方法返回的是此Object对象
的类对象/运行时类对象Class。效果与Object.class相同。
的类对象/运行时类对象Class。效果与Object.class相同。
6、equals():equals用来比较两个对象的内容是否相等。默认情况下(继承自Object
类),equals和==是一样的,除非被覆写(override)了。
类),equals和==是一样的,除非被覆写(override)了。
7、hashCode():该方法用来返回其所在对象的物理地址(哈希码值),常会和
equals方法同时重写,确保相等的两个对象拥有相等的hashCode。
equals方法同时重写,确保相等的两个对象拥有相等的hashCode。
8、toString():toString()方法返回该对象的字符串表示,这个方法没什么可说的。
9、wait():导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或
notifyAll() 方法。
notifyAll() 方法。
10、wait(long timeout):导致当前的线程等待,直到其他线程调用此对象的
notify() 方法或 notifyAll() 方法,或者超过指定的时间量
notify() 方法或 notifyAll() 方法,或者超过指定的时间量
11、wait(long timeout, int nanos):导致当前的线程等待,直到其他线程调用
此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或
者已超过某个实际时间量。
此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或
者已超过某个实际时间量。
12、notify():唤醒在此对象监视器上等待的单个线程。
13、notifyAll():唤醒在此对象监视器上等待的所有线程。
14、finalize():当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾
回收器调用此方法。
回收器调用此方法。
第七题:请说说Java引用类型原理?
问题:
请说说Java引用类型原理?
参考答案:
1、Java中一共有4种引用类型(其实还有一些其他的引用类型比如FinalReference):
强引用、软引用、弱引用、虚引用。
强引用、软引用、弱引用、虚引用。
2、强引用就是我们经常使用的Object a = new Object(); 这样的形式,在Java中并
没有对应的Reference类。
没有对应的Reference类。
第八题:HashMap的原理与理解?
问题:
HashMap的原理与理解?
参考答案:
1、数据put过程原理
逻辑图
子主题
①判断键值对数组table[i]是否为空或为null,否则执行resize()进行扩容;
②根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直
接新建节点添加,转向⑥,如果table[i]不为空,转向③;
接新建节点添加,转向⑥,如果table[i]不为空,转向③;
③判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,否则
转向④,这里的相同指的是hashCode以及equals;
转向④,这里的相同指的是hashCode以及equals;
④判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑
树,则直接在树中插入键值对,否则转向⑤;
树,则直接在树中插入键值对,否则转向⑤;
⑤遍历table[i],判断链表长度是否大于8,大于8的话把链表转换为红黑树,
在红黑树中执行插入操作,否则进行链表的插入操作;遍历过程中若发现
key已经存在直接覆盖value即可;
在红黑树中执行插入操作,否则进行链表的插入操作;遍历过程中若发现
key已经存在直接覆盖value即可;
⑥插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold,
如果超过,进行扩容
如果超过,进行扩容
2、数据get过程原理
1、指定key 通过hash函数得到key的hash值 int hash=key.hashCode();
2、调用内部方法 getNode(),得到桶号(一般都为hash值对桶数求模)
int index =hash%Entry[].length;
int index =hash%Entry[].length;
3、比较桶的内部元素是否与key相等,若都不相等,则没有找到。相等,
则取出相等记录的value。
则取出相等记录的value。
4、如果得到 key 所在的桶的头结点恰好是红黑树节点,就调用红黑树节点
的 getTreeNode() 方法,否则就遍历链表节点。getTreeNode 方法使通过
调用树形节点的 find()方法进行查找。由于之前添加时已经保证这个树是有
序的,因此查找时基本就是折半查找,效率很高。
的 getTreeNode() 方法,否则就遍历链表节点。getTreeNode 方法使通过
调用树形节点的 find()方法进行查找。由于之前添加时已经保证这个树是有
序的,因此查找时基本就是折半查找,效率很高。
5、如果对比节点的哈希值和要查找的哈希值相等,就会判断 key 是否相等,相
等就直接返回;不相等就从子树中递归查找。
等就直接返回;不相等就从子树中递归查找。
6、如果没有找到就返回null
3、hashMap的hash算法
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
4、hashMap我们知道默认初始容量是16,
也就是有16个桶,那hashmap是通过什么
来计算出put对象的时候该放到哪个桶呢
也就是有16个桶,那hashmap是通过什么
来计算出put对象的时候该放到哪个桶呢
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
5、数组长度-1、^运算、>>>16,这三个操作都是为了让key在hashmap的桶中尽可能分散
用&而不用%是为了提高计算性能
用&而不用%是为了提高计算性能
参考理解地址:
https://blog.csdn.net/qq_33709582/article/details/113337405
第九题:hashCode和equals的关系?
问题
hashCode和equals的关系?
参考答案:
1、重写equals()方法时候一定要重写hashCode()方法
2、如果两个对象的hashCode()相等,那么他们的equals()不一定相等。
如果两个对象的equals()相等,那么他们的hashCode()必定相等。
如果两个对象的equals()相等,那么他们的hashCode()必定相等。
第十题:String类能不能被继承?为什么?
问题:
String类能不能被继承?为什么?
参考答案:
不能
因为string类是被final修饰的类,final修饰过的类不能被继承、final修饰过的变量不能被修改
因为string类是被final修饰的类,final修饰过的类不能被继承、final修饰过的变量不能被修改
第十一题;简述Java的反射机制和使用场景?
问题:
简述Java的反射机制和使用场景?
参考答案:
反射是Java的一种机制,可以让我们在运行时获取类的信息
通过反射我们可以获取到类的所有信息,比如它的属性、构造器、方法、注解等
适用于需要动态创建对象的场景
通过反射我们可以获取到类的所有信息,比如它的属性、构造器、方法、注解等
适用于需要动态创建对象的场景
第十二题:String str = new String(“abc“)到底new了几个对象?
问题:
String str = new String(“abc“)到底new了几个对象?
参考答案:
1、两个:如果常量池里面没有“abc”这个字符串,那虚拟机就会在堆内存
中new出一个String对象,还会在常量池中new一个abc字符串对象;
中new出一个String对象,还会在常量池中new一个abc字符串对象;
2、一个:如果常量池中已经有"abc"这个字符串,也就是说你在前面已经new过
一个值为“abc”的字符串,那虚拟机就只会在堆内存中new一个String对象,并
将常量池中“abc”的地址指向你刚刚new的String对象
一个值为“abc”的字符串,那虚拟机就只会在堆内存中new一个String对象,并
将常量池中“abc”的地址指向你刚刚new的String对象
第十三题:Java中接口和抽象类的异同?
问题:
Java中接口和抽象类的异同?
参考答案:
1、都能包含抽象的方法,这些抽象的方法用于描述类具备的功能,不提供具体的实现
(jdk1.8允许接口有一个default的实现方法)
(jdk1.8允许接口有一个default的实现方法)
2、接口是对事物行为的抽象,而抽象类是对事务本质的抽象
3、接口中的变量必须给出初始值,抽象类可以不给;
4、一个类只能继承一个抽象类,但可以实现多个接口
5、抽象类中可以写非抽象的方法,从而避免在子类中重复书写它们,这样可以提高代码
的复用性,这是抽象类的优势;接口中只能有抽象的方法
的复用性,这是抽象类的优势;接口中只能有抽象的方法
ps:接口也能被继承,只不过是被接口继承
第十四题:Java中sleep和wait的区别?
问题:
Java中sleep和wait的区别?
参考答案:
1、sleep是Thread的方法,wait是Object的方法
2、sleep方法没有释放锁,而wait方法释放了锁
3、wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,
而sleep可以在任何地方使用
而sleep可以在任何地方使用
4、sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常
第十五题:Java如何进行高效的数组拷贝?
问题:
Java如何进行高效的数组拷贝?
参考答案:
使用java提供方法Arrays.copyOf或 System.arraycopy,是自己new数组, 然后for循环
复制效率的两倍左右。为什么快,因为它们是native方法;
复制效率的两倍左右。为什么快,因为它们是native方法;
第十六题:Java编译后的.class文件包含了哪些内容?
问题:
Java编译后的.class文件包含了哪些内容?
参考答案:
编译后的.class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑
地排列在.class文件之中,中间没有添加任何分隔符;
地排列在.class文件之中,中间没有添加任何分隔符;
根据Java虚拟机规范的规定,.class文件格式采用一种类似于C语言的伪结构来存储数据,包含无
符号数和表:
符号数和表:
无符号数:以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个字节的无符号数,
无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成的字符串值;
无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成的字符串值;
表:由多个无符号数或者其他表作为数据项构成的复合数据类型,所有表都习惯性的以”_info“结尾。
Class文件的结构没有分隔符,无论你是数量还是顺序,都是严格规定的,哪个字节代表什么含义、
长度多少、先后顺序如何,都不允许改变
长度多少、先后顺序如何,都不允许改变
第十七题:解决hash冲突的方式?
问题:
解决hash冲突的方式?
参考答案:
1、拉链法
代表作:hashMap
原理:把所有hash值相同的元素放到链表中
代表作:hashMap
原理:把所有hash值相同的元素放到链表中
2、再哈希法
原理:产生冲突时,对结果再进行一次hash,直到没有hash冲突,一般没人用,
太鲁莽了,而且治标不治本,理论上永远不能彻底解决hash冲突
原理:产生冲突时,对结果再进行一次hash,直到没有hash冲突,一般没人用,
太鲁莽了,而且治标不治本,理论上永远不能彻底解决hash冲突
2、开放地址法
a、线性探测法
b、线性补偿探测法
c、伪随机探测
a、线性探测法
b、线性补偿探测法
c、伪随机探测
第十八题:什么是重写和重载?
问题:
什么是重写和重载?
参考答案:
1、重写是在子类存在方法与父类的方法的名字相同,而且参数的个数
与类型一样,返回值也一样的方法,就称为重写(Override)
与类型一样,返回值也一样的方法,就称为重写(Override)
2、重载是一个类中定义了多个方法名相同,而他们的参数的数量不同
或数量相同而类型和次序不同,则称为方法的重载(Overload)
或数量相同而类型和次序不同,则称为方法的重载(Overload)
3、重载是一个类的多态性表现,而重写是子类与父类的一种多态性表现
第十九题:volatile关键字解决了什么问题?实现原理是什么?
问题:
volatile关键字解决了什么问题?实现原理是什么?
参考答案:
1、解决了什么问题:
1、保证了变量的可见性
2、禁止指令重排
例如:
比如i=i+1,单线程操作没问题,如果使用多线程,比如两个线程,执行这段
代码后(i初始值为0),i应该等于2,但是如果不用volatile修饰变量i,结果
会等于1,初始时,两个线程分别读取i的值存入各自所在的CPU的高速缓存当
中,然后线程1进行加1操作,然后把i的最新值1写入到内存。此时线程2的高速
缓存当中i的值还是0,进行加1操作之后,i的值为1,然后线程2把i的值写入内存。
代码后(i初始值为0),i应该等于2,但是如果不用volatile修饰变量i,结果
会等于1,初始时,两个线程分别读取i的值存入各自所在的CPU的高速缓存当
中,然后线程1进行加1操作,然后把i的最新值1写入到内存。此时线程2的高速
缓存当中i的值还是0,进行加1操作之后,i的值为1,然后线程2把i的值写入内存。
2、实现原理
基于内存屏障,关于内存屏障,搞java开发的同学在开发中不可能接触到,
所以不用关心太多,知道内存屏障有什么作用,面试官问到你能唬住他就
行了,因为面试官自己也不懂
所以不用关心太多,知道内存屏障有什么作用,面试官问到你能唬住他就
行了,因为面试官自己也不懂
(1)它确保指令重排序时不会把其后面的指令排到内存屏障前面,也不会把前面
的指令排到内存屏障后面,总之一句话,他能保证指令按照我们希望的顺序执行;
的指令排到内存屏障后面,总之一句话,他能保证指令按照我们希望的顺序执行;
(2)它会强制将对缓存的修改操作立即写入主存,使得其它线程能立马发现;
第二十题:什么是内存泄漏,怎么确定内存泄漏?
问题:
什么是内存泄漏,怎么确定内存泄漏?
参考答案:
概念:内存泄漏就是指jvm内存没有及时释放,用人话说就是使用完的对象没有被及时回收
怎么确认:linux有个工具叫valgrind。
怎么确认:linux有个工具叫valgrind。
第二十一题:int和Integer有什么区别?
问题:
int和Integer有什么区别?
参考答案
int是基本数据类型,默认值为0,integer是其包装类型,默认值为null
原始类型: boolean,char,byte,short,int,long,float,double
包装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double
包装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double
第二十二题:String和StringBuilder、StringBuffer的区别?
问题:
String和StringBuilder、StringBuffer的区别?
参考答案:
Java平台提供了两种类型的字符串:String和StringBuffer/StringBuilder,
它们可以储存和操作字符串。其中String是只读字符串,也就意味着String
引用的字符串内容是不能被改变的。而StringBuffer/StringBuilder类表示
的字符串对象可以直接进行修改。StringBuilder是Java 5中引入的,它和
StringBuffer的方法完全相同,区别在于它是在单线程环境下使用的,因为
它的所有方面都没有被synchronized修饰,因此它的效率也比StringBuffer
要高。
它们可以储存和操作字符串。其中String是只读字符串,也就意味着String
引用的字符串内容是不能被改变的。而StringBuffer/StringBuilder类表示
的字符串对象可以直接进行修改。StringBuilder是Java 5中引入的,它和
StringBuffer的方法完全相同,区别在于它是在单线程环境下使用的,因为
它的所有方面都没有被synchronized修饰,因此它的效率也比StringBuffer
要高。
第二十三题:常见的运行时异常?
问题:
常见的运行时异常?
参考答案:
1、ArithmeticException(算术异常)
2、ClassCastException (类转换异常)
3、IllegalArgumentException (非法参数异常)
4、IndexOutOfBoundsException (下标越界异常)
5、NullPointerException (空指针异常)
6、SecurityException (安全异常)
第二十四题:悲观锁和乐观锁的原理以及应用场景?
问题:
悲观锁和乐观锁的原理以及应用场景?
参考答案:
悲观锁:
顾名思义,比较悲观,每次去拿数据都认为别人会修改,所以每次在操作前都会加锁,
如:读写锁、行锁、表锁等,synchronized的原理也是悲观锁;适用于多写操作
如:读写锁、行锁、表锁等,synchronized的原理也是悲观锁;适用于多写操作
乐观锁:
每次拿数据都认为别人不会修改,所以不会加锁,但是在更新的时候,会先判断在此期
间有没有人更新该数据,如果有,返回冲突报错信息,让用户决定怎么操作;适用于多
读操作
间有没有人更新该数据,如果有,返回冲突报错信息,让用户决定怎么操作;适用于多
读操作
第二十五题:什么是泛型擦除?
问题:
什么是泛型擦除?
参考答案:
泛型只是为了在编码过程中,我的理解是泛型存在的意义有两个:一是为了让我们更
快地发现错误,比如你把User放进了ArrayList< Dog >中,编译器立马会报错;二是
避免类型检查,从而避免在运行时抛出 classCastException;泛型擦除就是指泛型会
在编译时被消除
快地发现错误,比如你把User放进了ArrayList< Dog >中,编译器立马会报错;二是
避免类型检查,从而避免在运行时抛出 classCastException;泛型擦除就是指泛型会
在编译时被消除
代码示例:
ArrayList<User> users = new ArrayList<>();
ArrayList<Dog> dogs = new ArrayList<>();
System.out.println(users.getClass() == dogs.getClass());
//true
ArrayList<Dog> dogs = new ArrayList<>();
System.out.println(users.getClass() == dogs.getClass());
//true
如上,运行结果表面两个list是相等的,因为经过编译后,泛型被擦除,两个list当然也就相等;
第二十六题:什么是可重入锁?实现原理?
问题:
什么是可重入锁?实现原理?
参考答案:
1、可重入锁:不是一种锁,它是锁的一种性质,代表该锁是否可重入,可重入意思
就是**任意线程在获取到锁后能够再次获取该锁,不会被锁阻塞**,这个锁知道它属
于谁,其它线程来了就会阻塞等待;
就是**任意线程在获取到锁后能够再次获取该锁,不会被锁阻塞**,这个锁知道它属
于谁,其它线程来了就会阻塞等待;
2、原理:锁的内部维护了线程计数器state和当前持有锁的线程对象,当线程A获取
锁资源后,锁会记录下A线程,并且state+1,此时如果有其它线程来获取锁,会被
封装成node节点插到队列尾部并且阻塞;而线程A再来获取锁资源时,会成功拿到锁,
并且state+1;当线程退出同步代码块时,state-1,如果计数器为0,则释放锁;
锁资源后,锁会记录下A线程,并且state+1,此时如果有其它线程来获取锁,会被
封装成node节点插到队列尾部并且阻塞;而线程A再来获取锁资源时,会成功拿到锁,
并且state+1;当线程退出同步代码块时,state-1,如果计数器为0,则释放锁;
第二十七题:synchronized可重入吗?ReentrantLock呢?
问题:
synchronized可重入吗?ReentrantLock呢?
参考答案:
都是可重入锁,java的锁都可重入
第二十八题:公平锁和非公平锁的区别?
问题:
公平锁和非公平锁的区别?
参考答案:
拿ReentrantLock来说,众所周知,没抢到锁的线程会被放入同步队列,当持有锁的线程释放锁后,此时
公平锁:唤醒第一个没被取消且不为null的节点去拿锁;
非公平锁:所有等待线程都会一起争夺锁,包括队列内和队列外的所有线程;
注意:AQS源码注释中有一句话,大概意思是下个头节点不一定能拿到锁,但那是节点已取消或为null时才会
发生,aqs解锁逻辑是依次唤醒后继节点,直到找到第一个没被取消且不为null的节点
发生,aqs解锁逻辑是依次唤醒后继节点,直到找到第一个没被取消且不为null的节点
一句话总结原理:
AQS维护了一个CLH队列,队列由node节点来实现,试图加锁失败的线程都会进入队列尾部并自旋,当前持有锁
的线程释放锁后,AQS会根据公平锁与否唤醒等待线程去抢夺锁
AQS维护了一个CLH队列,队列由node节点来实现,试图加锁失败的线程都会进入队列尾部并自旋,当前持有锁
的线程释放锁后,AQS会根据公平锁与否唤醒等待线程去抢夺锁
第二十九题:String有没有长度限制?
问题:
String有没有长度限制?
参考答案:
既然都这么问了那肯定是有的,分编译器和运行期,长度限制不一样
编译器2 ^ 16 - 1(65535)
运行期2 ^ 32 - 1(2147483647)
编译器2 ^ 16 - 1(65535)
运行期2 ^ 32 - 1(2147483647)
不管是编译器还是运行期,如果超出长度都会抛异常:常量字符串过长
第三十题:为什么finally里的代码一定会执行?
问题:
为什么finally里的代码一定会执行?
参考 答案:
编译器在编译的时候,会把finally里面的代码复制多份,分别放在try和catch内所有能够正常执行
以及异常执行逻辑的出口处,最直观的就是我们可以在字节码文件里看到很多份finally内部代码;
以及异常执行逻辑的出口处,最直观的就是我们可以在字节码文件里看到很多份finally内部代码;
第三十一题:分别说下ConcurrentHashMap1.7和1.8的实现?
问题:
分别说下ConcurrentHashMap1.7和1.8的实现?
参考答案:
1.7:
基于Segment数组和HashEntry,Segment继承自ReentrantLock,懂了吧,它自然就
有了锁的基本功能;每个Segment数组中都有多个HashEntry,我们的数据都存在HashEntry
里面,每次需要修改数据时,先对HashEntry所在的Segment加锁,其它Segment不受影响,
分段锁就是这么来的;
基于Segment数组和HashEntry,Segment继承自ReentrantLock,懂了吧,它自然就
有了锁的基本功能;每个Segment数组中都有多个HashEntry,我们的数据都存在HashEntry
里面,每次需要修改数据时,先对HashEntry所在的Segment加锁,其它Segment不受影响,
分段锁就是这么来的;
1.8
整体实现很像HashMap,在它基础上引入了synchronized,和大量的CAS操作,以及大量的
volatile关键字,所以1.8的ConcurrentHashMap中锁的粒度更小;
整体实现很像HashMap,在它基础上引入了synchronized,和大量的CAS操作,以及大量的
volatile关键字,所以1.8的ConcurrentHashMap中锁的粒度更小;
第三十二题:java有哪些类加载器?
问题:
java有哪些类加载器?
参考答案:
1、启动类(Bootstrap)加载器 BootClassPathHolder: 加载<JAVA_HOME>/lib下的jar包
2、扩展类(Extension)加载器ExtClassLoader:加载<JAVA_HOME>/lib/ext下的jar包
3、系统类(System)加载器AppClassLoader:加载我们自己项目中写的java文件编译而成的
class文件,位于target/classes下
class文件,位于target/classes下
第三十三题:判断一块内存空间是否会被垃圾回收器回收的标准有哪些?
问题:
判断一块内存空间是否会被垃圾回收器回收的标准有哪些?
参考答案:
1、对象的引用被赋值为null,并且后面不再调用
2、对象的引用被重新分配了内存空间
3、对象的引用被赋予了新值
第三十四题:
二、Java多线程面试全集
第一题:ReentrantLock公平锁在持有锁线程释放锁后,哪些线程会参与锁竞争?
问题:
ReentrantLock公平锁在持有锁线程释放锁后,哪些线程会参与锁竞争?
参考答案:
五个线程ABCDE一起抢夺锁资源,假设A抢到了锁,BC没抢到但是先后进
入了clh队列,DE没抢到也没进入clh队列,当A释放锁后,哪些线程会去
抢夺锁资源?
入了clh队列,DE没抢到也没进入clh队列,当A释放锁后,哪些线程会去
抢夺锁资源?
第二题:ThreadLocal实现原理?
问题:
ThreadLocal实现原理?
参考答案:
ThreadLocal中文名叫线程变量,它底层维护了一个map,key就是当前的ThreadLocal对象
(可以理解为当前执行该段代码的线程),value就是你set的值,这个map保证了各个线程
的数据互不干扰;
(可以理解为当前执行该段代码的线程),value就是你set的值,这个map保证了各个线程
的数据互不干扰;
第三题:java是如何实现线程安全的?哪些数据结构是线程安全的?
问题:
java是如何实现线程安全的?哪些数据结构是线程安全的?
参考答案:
1、锁机制:用synchronize、lock给共享资源加锁;
2、使用java提供的安全类:java.util.concurrent包下的类自身就是线程安全的,
在保证安全的同时还能保证性能;
在保证安全的同时还能保证性能;
第四题:什么是CAS操作?什么ABA问题?如何解决?
问题:
什么是CAS操作?什么ABA问题?如何解决?
参考答案:
1、CAS全称compare and swap(比较并交换),作用是保证原子性
CAS操作包含三个操作数 —— 内存位置、预期原值、新值。 如果内存位置的
值和预期原值相等,就把该值更新为新值,如果不相等,则什么都不做;
值和预期原值相等,就把该值更新为新值,如果不相等,则什么都不做;
2、ABA问题
CAS操作存在的一个并发问题,打个比方,有两个线程A、B同时操作变量x,
A读取到的预期原值是1,此时线程B先将x设置为2,再设置为1,等线程A
再来操作的时候,x变量的预期原值和当前值相等,但是x在整个过程中的值
是发生过变化的,这在某些业务场景下是不允许的;
A读取到的预期原值是1,此时线程B先将x设置为2,再设置为1,等线程A
再来操作的时候,x变量的预期原值和当前值相等,但是x在整个过程中的值
是发生过变化的,这在某些业务场景下是不允许的;
3、如何解决
利用版本号,给变量x增加版本号,每次操作增加对本版好的判断和修改;
第五题:说出Java创建线程的三种方式及对比?
问题
说出Java创建线程的三种方式及对比?
参考答案:
1、创建方式
1、继承Thread类创建线程类
(1)定义Thread类的子类,并重写该类的run方法,该run方法的方法
体就代表了线程要完成的任务。因此把run()方法称为执行体。
体就代表了线程要完成的任务。因此把run()方法称为执行体。
(2)创建Thread子类的实例,即创建了线程对象。
(3)调用线程对象的start()方法来启动该线程。
示例
package com.thread;
public class FirstThreadTest extends Thread{
int i = 0;
//重写run方法,run方法的方法体就是现场执行体
public void run()
{
for(;i<100;i++){
System.out.println(getName()+" "+i);
}
}
public static void main(String[] args)
{
for(int i = 0;i< 100;i++)
{
System.out.println(Thread.currentThread().getName()+" : "+i);
if(i==20)
{
new FirstThreadTest().start();
new FirstThreadTest().start();
}
}
}
}
public class FirstThreadTest extends Thread{
int i = 0;
//重写run方法,run方法的方法体就是现场执行体
public void run()
{
for(;i<100;i++){
System.out.println(getName()+" "+i);
}
}
public static void main(String[] args)
{
for(int i = 0;i< 100;i++)
{
System.out.println(Thread.currentThread().getName()+" : "+i);
if(i==20)
{
new FirstThreadTest().start();
new FirstThreadTest().start();
}
}
}
}
2、通过Runnable接口创建线程类
(1)定义runnable接口的实现类,并重写该接口的run()方法,该run()方法
的方法体同样是该线程的线程执行体。
的方法体同样是该线程的线程执行体。
(2)创建 Runnable实现类的实例,并以此实例作为Thread的target来创建
Thread对象,该Thread对象才是真正的线程对象
Thread对象,该Thread对象才是真正的线程对象
(3)调用线程对象的start()方法来启动该线程
示例
package com.thread;
public class RunnableThreadTest implements Runnable
{
private int i;
public void run()
{
for(i = 0;i <100;i++)
{
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
public static void main(String[] args)
{
for(int i = 0;i < 100;i++)
{
System.out.println(Thread.currentThread().getName()+" "+i);
if(i==20)
{
RunnableThreadTest rtt = new RunnableThreadTest();
new Thread(rtt,"新线程1").start();
new Thread(rtt,"新线程2").start();
}
}
}
}
public class RunnableThreadTest implements Runnable
{
private int i;
public void run()
{
for(i = 0;i <100;i++)
{
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
public static void main(String[] args)
{
for(int i = 0;i < 100;i++)
{
System.out.println(Thread.currentThread().getName()+" "+i);
if(i==20)
{
RunnableThreadTest rtt = new RunnableThreadTest();
new Thread(rtt,"新线程1").start();
new Thread(rtt,"新线程2").start();
}
}
}
}
3、通过Callable和Future创建线程
(1)创建Callable接口的实现类,并实现call()方法,该call()方法将作
为线程执行体,并且有返回值。
public interface Callable
{
V call() throws Exception;
}
为线程执行体,并且有返回值。
public interface Callable
{
V call() throws Exception;
}
(2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,
该FutureTask对象封装了该Callable对象的call()方法的返回值。
(FutureTask是一个包装器,它通过接受Callable来创建,它同时实现了
Future和Runnable接口。)
该FutureTask对象封装了该Callable对象的call()方法的返回值。
(FutureTask是一个包装器,它通过接受Callable来创建,它同时实现了
Future和Runnable接口。)
(3)使用FutureTask对象作为Thread对象的target创建并启动新线程。
(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
示例:
package com.thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CallableThreadTest implements Callable<Integer>
{
public static void main(String[] args)
{
CallableThreadTest ctt = new CallableThreadTest();
FutureTask<Integer> ft = new FutureTask<>(ctt);
for(int i = 0;i < 100;i++)
{
System.out.println(Thread.currentThread().getName()+" 的循环变量i的值"+i);
if(i==20)
{
new Thread(ft,"有返回值的线程").start();
}
}
try
{
System.out.println("子线程的返回值:"+ft.get());
} catch (InterruptedException e)
{
e.printStackTrace();
} catch (ExecutionException e)
{
e.printStackTrace();
}
}
@Override
public Integer call() throws Exception
{
int i = 0;
for(;i<100;i++)
{
System.out.println(Thread.currentThread().getName()+" "+i);
}
return i;
}
}
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CallableThreadTest implements Callable<Integer>
{
public static void main(String[] args)
{
CallableThreadTest ctt = new CallableThreadTest();
FutureTask<Integer> ft = new FutureTask<>(ctt);
for(int i = 0;i < 100;i++)
{
System.out.println(Thread.currentThread().getName()+" 的循环变量i的值"+i);
if(i==20)
{
new Thread(ft,"有返回值的线程").start();
}
}
try
{
System.out.println("子线程的返回值:"+ft.get());
} catch (InterruptedException e)
{
e.printStackTrace();
} catch (ExecutionException e)
{
e.printStackTrace();
}
}
@Override
public Integer call() throws Exception
{
int i = 0;
for(;i<100;i++)
{
System.out.println(Thread.currentThread().getName()+" "+i);
}
return i;
}
}
2、对比
1、采用实现Runnable、Callable接口的方式创建多线程时,
优势是:
线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。
在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同
一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向
对象的思想。
劣势是:
编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。
线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。
在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同
一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向
对象的思想。
劣势是:
编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。
2、使用继承Thread类的方式创建多线程时
优势是:
编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,
直接使用this即可获得当前线程。
劣势是:
线程类已经继承了Thread类,所以不能再继承其他父类。
编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,
直接使用this即可获得当前线程。
劣势是:
线程类已经继承了Thread类,所以不能再继承其他父类。
3、Runnable和Callable的区别
(1) Callable规定(重写)的方法是call(),Runnable规定(重写)的方法是run()。
(2) Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。
(3) call方法可以抛出异常,run方法不可以。
(4) 运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是
否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行
情况,可取消任务的执行,还可获取执行结果。
(2) Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。
(3) call方法可以抛出异常,run方法不可以。
(4) 运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是
否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行
情况,可取消任务的执行,还可获取执行结果。
三、Spring相关面试全集
第一题:说说你对spring IOC和AOP的理解?
问题:
说说你对spring IOC和AOP的理解?
参考答案:
1、IOC(控制反转)
也叫DI(依赖注入),是一种思想,不是一种技术,IOC主张把对象的控制权
交由spring,底层实现是反射+工厂方法模式,IOC容器实际上就是个Map,
用于存放各种对象;
交由spring,底层实现是反射+工厂方法模式,IOC容器实际上就是个Map,
用于存放各种对象;
2、AOP(面向切面)
面向切面编程,把一些能共用、冗余、繁琐的功能提取出来,AOP能在不改
变原有业务逻辑的情况下,增强横切逻辑代码,根本上解耦合,避免横切逻
辑代码重复;常见使用场景有事务管理、日志、全局异常处理、用户鉴权;
变原有业务逻辑的情况下,增强横切逻辑代码,根本上解耦合,避免横切逻
辑代码重复;常见使用场景有事务管理、日志、全局异常处理、用户鉴权;
第二题:@Autowire和@Resource区别?
问题:
@Autowire和@Resource区别?
参考答案:
1、都是用来装配java bean
@Autowired:按类型注入,这是spring的注解,可以搭配@Qualifie实现按名称注入;
@Resource:默认情况下是按照名称进行匹配,如果没有找到相同名称的Bean,则会按照
类型进行匹配,这是java自己的注解;
类型进行匹配,这是java自己的注解;
2、注意事项:
@Autowired有个弊端,打个比方,有个userService,然后它有两个实现类userServiceA
和userServiceB,这时候用@Autowired就行不通了,因为它不知道找谁,但是你也不能因
为这个一上来就直接用@Resource,这玩意儿性能没@Autowired好,因为@Resource要
匹配两次
和userServiceB,这时候用@Autowired就行不通了,因为它不知道找谁,但是你也不能因
为这个一上来就直接用@Resource,这玩意儿性能没@Autowired好,因为@Resource要
匹配两次
第三题:springboot自动装配原理?
问题:
springboot自动装配原理?
参考答案:
在springboot的启动类上有个SpringBootApplication注解,这是个组合注解,这个注解里
面有个注解叫EnableAutoConfiguration注解,@EnableAutoConfigration 注解会导入一
个自动配置选择器去扫描每个jar包的META-INF/xxxx.factories 这个文件,这个文件是一个
key-value形式的配置文件,里面存放了这个jar包依赖的具体依赖的自动配置类。这些自动配
置类又通过@EnableConfigurationProperties 注解支持通过xxxxProperties 读取application.
properties/application.yml属性文件中我们配置的值。如果我们没有配置值,就使用默认值,
这就是所谓约定大于配置的具体落地点。
面有个注解叫EnableAutoConfiguration注解,@EnableAutoConfigration 注解会导入一
个自动配置选择器去扫描每个jar包的META-INF/xxxx.factories 这个文件,这个文件是一个
key-value形式的配置文件,里面存放了这个jar包依赖的具体依赖的自动配置类。这些自动配
置类又通过@EnableConfigurationProperties 注解支持通过xxxxProperties 读取application.
properties/application.yml属性文件中我们配置的值。如果我们没有配置值,就使用默认值,
这就是所谓约定大于配置的具体落地点。
四、Mybaties相关面试全集
第一题:mybatis插入一条数据,怎么获得这条数据的id?
问题:
mybatis插入一条数据,怎么获得这条数据的id?
参考答案:
比如你插入一个user对象
userMapper.insert(User user);
在insert标签上加个配置
<insert id="insert" parameterType="User" keyProperty="id" useGeneratedKeys="true">
然后 新插入user的id会赋到你传入的user对象,通过user.getId()就能拿到了
当然 你先insert进去再select出来拿id也不是不
第二题:#{}和${}的区别是什么?
问题:
#{}和${}的区别是什么?
参考答案:
1、${}是Properties文件中的变量占位符,用于标签属性值
和sql内部,属于静态文本替换,比如${driver}会被静态替换为
com.mysql.jdbc.Driver。
和sql内部,属于静态文本替换,比如${driver}会被静态替换为
com.mysql.jdbc.Driver。
2、#{}是sql的参数占位符,Mybatis会将sql中的#{}替换为?号,在
sql执行前会使用PreparedStatement的参数设置方法,按序给sql
的?号占位符设置参数值,比如ps.setInt(0, parameterValue),
#{item.name}的取值方式为使用反射从参数对象中获取item对象的
name属性值,相当于param.getItem().getName()。
sql执行前会使用PreparedStatement的参数设置方法,按序给sql
的?号占位符设置参数值,比如ps.setInt(0, parameterValue),
#{item.name}的取值方式为使用反射从参数对象中获取item对象的
name属性值,相当于param.getItem().getName()。
第三题:Xml映射文件中,除了常见的select|insert|updae|delete标签之外,还有哪些标签?
问题:
Xml映射文件中,除了常见的select|insert|updae|delete标签之外,还有哪些标签?
参考答案:
还有很多其他的标签,<resultMap>、<parameterMap>、<sql>、<include>、
<selectKey>。
动态sql的标签:trim、where、set、foreach、if、choose、when、
otherwise、bind等,其中<sql>为sql片段标签,通过<include>标签引入sql片段,
<selectKey>为不支持自增的主键生成策略标签。
<selectKey>。
动态sql的标签:trim、where、set、foreach、if、choose、when、
otherwise、bind等,其中<sql>为sql片段标签,通过<include>标签引入sql片段,
<selectKey>为不支持自增的主键生成策略标签。
第四题:Mybatis是如何进行分页的?分页插件的原理是什么?
问题:
Mybatis是如何进行分页的?分页插件的原理是什么?
参考答案:
1、Mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的
内存分页,而非物理分页,可以在sql内直接书写带有物理分页的参数来完成物
理分页功能,也可以使用分页插件来完成物理分页。
内存分页,而非物理分页,可以在sql内直接书写带有物理分页的参数来完成物
理分页功能,也可以使用分页插件来完成物理分页。
2、分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插
件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的
物理分页语句和物理分页参数。
件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的
物理分页语句和物理分页参数。
3、举例:select * from student,拦截sql后重写为:
select t.* from (select * from student)t limit 0,10
select t.* from (select * from student)t limit 0,10
第五题:简述Mybatis的插件运行原理,以及如何编写一个插件。
问题
简述Mybatis的插件运行原理,以及如何编写一个插件。
参考答案:
1、Mybatis仅可以编写针对ParameterHandler、ResultSetHandler、StatementHandler、
Executor这4种接口的插件,Mybatis使用JDK的动态代理,为需要拦截的接口生成代理
对象以实现接口方法拦截功能,每当执行这4种接口对象的方法时,就会进入拦截方法,
具体就是InvocationHandler的invoke()方法,当然,只会拦截那些你指定需要拦截的方法。
Executor这4种接口的插件,Mybatis使用JDK的动态代理,为需要拦截的接口生成代理
对象以实现接口方法拦截功能,每当执行这4种接口对象的方法时,就会进入拦截方法,
具体就是InvocationHandler的invoke()方法,当然,只会拦截那些你指定需要拦截的方法。
2、实现Mybatis的Interceptor接口并复写intercept()方法,然后在给插件编写注解,指定
要拦截哪一个接口的哪些方法即可,记住,别忘了在配置文件中配置你编写的插件。
要拦截哪一个接口的哪些方法即可,记住,别忘了在配置文件中配置你编写的插件。
第六题:Mybatis执行批量插入,能返回数据库主键列表吗?
问题:
Mybatis执行批量插入,能返回数据库主键列表吗?
参考答案:
能,JDBC都能,Mybatis当然也能。
第八题:Mybatis动态sql是做什么的?都有哪些动态sql?能简述一下动态sql的执行原理不?
问题:
Mybatis动态sql是做什么的?都有哪些动态sql?能简述一下动态sql的执行原理不?
参考答案:
1、Mybatis动态sql可以让我们在Xml映射文件内,以标签的形式编写动态sql,完成
逻辑判断和动态拼接sql的功能,Mybatis提供了9种动态sql标签trim|where|set|
foreach|if|choose|when|otherwise|bind。
逻辑判断和动态拼接sql的功能,Mybatis提供了9种动态sql标签trim|where|set|
foreach|if|choose|when|otherwise|bind。
2、其执行原理为,使用OGNL从sql参数对象中计算表达式的值,根据表达式的值动态
拼接sql,以此来完成动态sql的功能。
拼接sql,以此来完成动态sql的功能。
第九题:Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?
问题:
Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?
参考答案:
1、第一种是使用<resultMap>标签,逐一定义列名和对象属性名之间的映射关系。
第二种是使用sql列的别名功能,将列别名书写为对象属性名,比如T_NAME AS
NAME,对象属性名一般是name,小写,但是列名不区分大小写,Mybatis会忽
略列名大小写,智能找到与之对应对象属性名,你甚至可以写成T_NAME AS NaMe,
Mybatis一样可以正常工作。
第二种是使用sql列的别名功能,将列别名书写为对象属性名,比如T_NAME AS
NAME,对象属性名一般是name,小写,但是列名不区分大小写,Mybatis会忽
略列名大小写,智能找到与之对应对象属性名,你甚至可以写成T_NAME AS NaMe,
Mybatis一样可以正常工作。
2、有了列名与属性名的映射关系后,Mybatis通过反射创建对象,同时使用反射给对象
的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。
的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。
第十题:Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?
问题:
Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?
参考答案:
1、Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,association指
的就是一对一,collection指的就是一对多查询。在Mybatis配置文件中,可以配置是否启用
延迟加载lazyLoadingEnabled=true|false。
的就是一对一,collection指的就是一对多查询。在Mybatis配置文件中,可以配置是否启用
延迟加载lazyLoadingEnabled=true|false。
2、它的原理是,使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方
法,比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,那么就会
单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的
对象b属性就有值了,接着完成a.getB().getName()方法的调用。这就是延迟加载的基本原理。
法,比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,那么就会
单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的
对象b属性就有值了,接着完成a.getB().getName()方法的调用。这就是延迟加载的基本原理。
3、当然了,不光是Mybatis,几乎所有的包括Hibernate,支持延迟加载的原理都是一样的
第十一题:Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重复?
问题;
Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重复?
参考答案:
1、不同的Xml映射文件,如果配置了namespace,那么id可以重复;如果没有配置
namespace,那么id不能重复;毕竟namespace不是必须的,只是最佳实践而已。
namespace,那么id不能重复;毕竟namespace不是必须的,只是最佳实践而已。
2、原因就是namespace+id是作为Map<String, MappedStatement>的key使用的,
如果没有namespace,就剩下id,那么,id重复会导致数据互相覆盖。有了namespace,
自然id就可以重复,namespace不同,namespace+id自然也就不同。
如果没有namespace,就剩下id,那么,id重复会导致数据互相覆盖。有了namespace,
自然id就可以重复,namespace不同,namespace+id自然也就不同。
第十二题:Mybatis中如何执行批处理?
问题:
Mybatis中如何执行批处理?
参考答案:
使用BatchExecutor完成批处理。
第十三题:Mybatis都有哪些Executor执行器?它们之间的区别是什么?
问题:
Mybatis都有哪些Executor执行器?它们之间的区别是什么?
参考答案:
1、Mybatis有三种基本的Executor执行器,SimpleExecutor、ReuseExecutor、
BatchExecutor。
BatchExecutor。
2、SimpleExecutor:每执行一次update或select,就开启一个Statement对象,
用完立刻关闭Statement对象。
用完立刻关闭Statement对象。
3、ReuseExecutor:执行update或select,以sql作为key查找Statement对象,
存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于
Map<String, Statement>内,供下一次使用。简言之,就是重复使用Statement对象。
存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于
Map<String, Statement>内,供下一次使用。简言之,就是重复使用Statement对象。
4、BatchExecutor:执行update(没有select,JDBC批处理不支持select),将所
有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓
存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执
行executeBatch()批处理。与JDBC批处理相同。
有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓
存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执
行executeBatch()批处理。与JDBC批处理相同。
作用范围:Executor的这些特点,都严格限制在SqlSession生命周期范围内。
第十四题:Mybatis中如何指定使用哪一种Executor执行器?
问题;
Mybatis中如何指定使用哪一种Executor执行器?
参考答案:
在Mybatis配置文件中,可以指定默认的ExecutorType执行器类型,也可以手动给
DefaultSqlSessionFactory的创建SqlSession的方法传递ExecutorType类型参数。
DefaultSqlSessionFactory的创建SqlSession的方法传递ExecutorType类型参数。
第十五题:Mybatis是否可以映射Enum枚举类?
问题:
Mybatis是否可以映射Enum枚举类?
参考答案:
1、Mybatis可以映射枚举类,不单可以映射枚举类,Mybatis可以映射任何对象
到表的一列上。映射方式为自定义一个TypeHandler,实现TypeHandler的
setParameter()和getResult()接口方法。
到表的一列上。映射方式为自定义一个TypeHandler,实现TypeHandler的
setParameter()和getResult()接口方法。
2、TypeHandler有两个作用,一是完成从javaType至jdbcType的转换,二是完成
jdbcType至javaType的转换,体现为setParameter()和getResult()两个方法,分
别代表设置sql问号占位符参数和获取列查询结果。
jdbcType至javaType的转换,体现为setParameter()和getResult()两个方法,分
别代表设置sql问号占位符参数和获取列查询结果。
第十六题:Mybatis映射文件中,如果A标签通过include引用了B标签的内容,
请问,B标签能否定义在A标签的后面,还是说必须定义在A标签的前面?
请问,B标签能否定义在A标签的后面,还是说必须定义在A标签的前面?
问题:
Mybatis映射文件中,如果A标签通过include引用了B标签的内容,
请问,B标签能否定义在A标签的后面,还是说必须定义在A标签的前面?
请问,B标签能否定义在A标签的后面,还是说必须定义在A标签的前面?
参考答案:
1、虽然Mybatis解析Xml映射文件是按照顺序解析的,但是,被引用的
B标签依然可以定义在任何地方,Mybatis都可以正确识别。
B标签依然可以定义在任何地方,Mybatis都可以正确识别。
2、原理是,Mybatis解析A标签,发现A标签引用了B标签,但是B标签
尚未解析到,尚不存在,此时,Mybatis会将A标签标记为未解析状态,
然后继续解析余下的标签,包含B标签,待所有标签解析完毕,Mybatis
会重新解析那些被标记为未解析的标签,此时再解析A标签时,B标签已
经存在,A标签也就可以正常解析完成了。
尚未解析到,尚不存在,此时,Mybatis会将A标签标记为未解析状态,
然后继续解析余下的标签,包含B标签,待所有标签解析完毕,Mybatis
会重新解析那些被标记为未解析的标签,此时再解析A标签时,B标签已
经存在,A标签也就可以正常解析完成了。
第十七题:简述Mybatis的Xml映射文件和Mybatis内部数据结构之间的映射关系?
问题:
简述Mybatis的Xml映射文件和Mybatis内部数据结构之间的映射关系?
参考答案:
1、Mybatis将所有Xml配置信息都封装到All-In-One重量级对象Configuration
内部。在Xml映射文件中,<parameterMap>标签会被解析为ParameterMap
对象,其每个子元素会被解析为ParameterMapping对象。
内部。在Xml映射文件中,<parameterMap>标签会被解析为ParameterMap
对象,其每个子元素会被解析为ParameterMapping对象。
2、<resultMap>标签会被解析为ResultMap对象,其每个子元素会被解析为
ResultMapping对象。每一个<select>、<insert>、<update>、<delete>
标签均会被解析为MappedStatement对象,标签内的sql会被解析为BoundSql对象。
ResultMapping对象。每一个<select>、<insert>、<update>、<delete>
标签均会被解析为MappedStatement对象,标签内的sql会被解析为BoundSql对象。
第十八题:为什么说Mybatis是半自动ORM映射工具?它与全自动的区别在哪里?
问题:
为什么说Mybatis是半自动ORM映射工具?它与全自动的区别在哪里?
参考答案:
Hibernate属于全自动ORM映射工具,使用Hibernate查询关联对象或者关联
集合对象时,可以根据对象关系模型直接获取,所以它是全自动的。而Mybatis
在查询关联对象或关联集合对象时,需要手动编写sql来完成,所以,称之为半
自动ORM映射工具。
集合对象时,可以根据对象关系模型直接获取,所以它是全自动的。而Mybatis
在查询关联对象或关联集合对象时,需要手动编写sql来完成,所以,称之为半
自动ORM映射工具。
四、Redis数据库相关面试全集
第一题:你在项目中如何保证缓存和数据库的一致性?
问题:
你在项目中如何保证缓存和数据库的一致性?
参考答案:
记住一句话,只要有引入缓存的地方,都不可能保证强一致性,所以这里的一致性是指最终一致性
方法很多,最常用的就是延时双删,先删除缓存,再操作数据库,完事儿再删除一次缓存
第二次删除缓存是为了避免 在第一次删除缓存之后,到操作数据库完成之前,这期间有新的查询过来,导致再次把旧数据生成缓存
方法很多,最常用的就是延时双删,先删除缓存,再操作数据库,完事儿再删除一次缓存
第二次删除缓存是为了避免 在第一次删除缓存之后,到操作数据库完成之前,这期间有新的查询过来,导致再次把旧数据生成缓存
第二题:redis的持久化机制?
问题:
redis的持久化机制?
参考答案:
1、所谓持久化机制就是保证 redis 挂掉再重启后,可以恢复数据
2、快照RDB(默认)
默认开启,无需设置,有个参数 save m n,这表示m秒内进行了n次写操作就进行备份,
而且可以设置多组,满足不同场景;这里备份有两种,一个是save(阻塞),一个是
bgsave(异步),还有一种是自动化,redis的快照是采用bgsave
而且可以设置多组,满足不同场景;这里备份有两种,一个是save(阻塞),一个是
bgsave(异步),还有一种是自动化,redis的快照是采用bgsave
3、AOF(AppendOnlyFile:只追加文件)
a、需手动开启,在redis.conf中开启appendonly,默认是no,改为yes,生成的日志文件名
默认为appendonly.aof,可以修改,然后配置appendfsync,有三个选项,always、
everysec和no:
b、默认是everysec,表示每秒同步一次,性能和数据可靠性都能兼顾,最坏情况会丢失不到2秒的数据;
c、no表示平时不进行同步,只会在redis关闭或者aof被关闭时同步,性能最佳,但是丢数据风险高;
d、always表示每次写操作都会同步,性能差,但是丢数据风险低;
默认为appendonly.aof,可以修改,然后配置appendfsync,有三个选项,always、
everysec和no:
b、默认是everysec,表示每秒同步一次,性能和数据可靠性都能兼顾,最坏情况会丢失不到2秒的数据;
c、no表示平时不进行同步,只会在redis关闭或者aof被关闭时同步,性能最佳,但是丢数据风险高;
d、always表示每次写操作都会同步,性能差,但是丢数据风险低;
第三题:redis如何管理过期的key?
问题:
redis如何管理过期的key?
参考答案:
redis采用 定期 + 惰性 删除的方式来管理key
redis会周期性地扫描当前所有key,发现过期的立即清除,这招叫 定期删除;
但是这样有个问题,扫描间隔太长的话,可能导致某些key多存活一个周期;
太短的话,又会很影响性能,所以除了定期扫描,redis在使用某个key时会
先校验key是否过期,如果过期直接删除,这招叫 惰性删除
但是这样有个问题,扫描间隔太长的话,可能导致某些key多存活一个周期;
太短的话,又会很影响性能,所以除了定期扫描,redis在使用某个key时会
先校验key是否过期,如果过期直接删除,这招叫 惰性删除
第四题:使用Redis有哪些好处?
问题:
使用Redis有哪些好处?
参考答案:
(1) 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找
和操作的时间复杂度都是O(1)
和操作的时间复杂度都是O(1)
(2) 支持丰富数据类型,支持string,list,set,Sorted Set(有序集合),hash
(3) 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要
么全部不执行
么全部不执行
(4) 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除
第五题:redis相比memcached有哪些优势?
问题:
redis相比memcached有哪些优势?
参考答案:
(1) memcached所有的值均是简单的字符串,redis作为其替代者,支持更为丰富的数据类型
(2) redis的速度比memcached快很多
(3) redis可以持久化其数据
第六题:Memcache与Redis的区别都有哪些?
问题:
Memcache与Redis的区别都有哪些?
参考答案:
1)、存储方式
Memecache把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。
Redis有部份存在硬盘上,这样能保证数据的持久性。
Redis有部份存在硬盘上,这样能保证数据的持久性。
2)、数据支持类型
Memcache对数据类型支持相对简单。
Redis有复杂的数据类型。
Redis有复杂的数据类型。
3)、使用底层模型不同
它们之间底层实现方式 以及与客户端之间通信的应用协议不一样。
Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,
会浪费一定的时间去移动和请求。
Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,
会浪费一定的时间去移动和请求。
4)、value大小
redis最大可以达到512M,而memcache只有1MB
第七题:缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级等问题?
问题:
缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级等问题?
参考答案:
1、缓存雪崩
缓存雪崩:由于原有缓存失效,新缓存未到期,访问直达db
(造成原因:由于设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期),
所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压
力,严重的会造成数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃。
(造成原因:由于设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期),
所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压
力,严重的会造成数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃。
解决办法:
1、访问加锁
2、使缓存分散
最终的目的就是防止大量请求落到db上
1、访问加锁
2、使缓存分散
最终的目的就是防止大量请求落到db上
2、缓存穿透
缓存穿透是指用户查询数据时数据库没有,自然在缓存中也不会有。这样就导致用户查
询的时候,在缓存中找不到,每次都要去数据库再查询一遍,然后返回空(相当于进行了
两次无用的查询)。这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题。
询的时候,在缓存中找不到,每次都要去数据库再查询一遍,然后返回空(相当于进行了
两次无用的查询)。这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题。
解决办法:
最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个
一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
另外也有一个更为简单粗暴的方法,如果一个查询返回的数据为空(不管是数据不存在,还是
系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。
通过这个直接设置的默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问
数据库,这种办法最简单粗暴。
最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个
一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
另外也有一个更为简单粗暴的方法,如果一个查询返回的数据为空(不管是数据不存在,还是
系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。
通过这个直接设置的默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问
数据库,这种办法最简单粗暴。
3、缓存穿透与缓存击穿的区别
缓存击穿:是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行
访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据。
访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据。
解决方案:在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁
住当前key的访问,访问结束再删除该短期key。
住当前key的访问,访问结束再删除该短期key。
4、缓存预热
缓存预热这个应该是一个比较常见的概念,相信很多小伙伴都应该可以很容易的理解,
缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免
在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被
预热的缓存数据!
缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免
在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被
预热的缓存数据!
解决思路:
1、直接写个缓存刷新页面,上线时手工操作下;
2、数据量不大,可以在项目启动的时候自动进行加载;
3、定时刷新缓存;
1、直接写个缓存刷新页面,上线时手工操作下;
2、数据量不大,可以在项目启动的时候自动进行加载;
3、定时刷新缓存;
5、缓存更新
第八题:Redis 常见性能问题和解决方案?
第九题:说说Redis的过期键删除策略?
问题:
说说Redis的过期键删除策略?
参考答案:
1. 常见的删除策略
定时删除:
在设置键的过期时间的同时,创建一个定时器,让定时器在键的过期时间来临时,
立即执行对键的删除操作。
优点:对内存非常友好
缺点:对CPU时间非常不友好
在设置键的过期时间的同时,创建一个定时器,让定时器在键的过期时间来临时,
立即执行对键的删除操作。
优点:对内存非常友好
缺点:对CPU时间非常不友好
惰性删除:
放任过期键不管,每次从键空间中获取键时,检查该键是否过期,如果过期,就删
除该键,如果没有过期,就返回该键。
优点:对CPU时间非常友好
缺点:对内存非常不友好
放任过期键不管,每次从键空间中获取键时,检查该键是否过期,如果过期,就删
除该键,如果没有过期,就返回该键。
优点:对CPU时间非常友好
缺点:对内存非常不友好
定期删除:
每隔一段时间,程序对数据库进行一次检查,删除里面的过期键,至于要删除哪些
数据库的哪些过期键,则由算法决定。
定期删除策略是定时删除策略和惰性删除策略的一种整合折中方案。
每隔一段时间,程序对数据库进行一次检查,删除里面的过期键,至于要删除哪些
数据库的哪些过期键,则由算法决定。
定期删除策略是定时删除策略和惰性删除策略的一种整合折中方案。
2、Redis使用的过期键删除策略
Redis服务器使用的是惰性删除策略和定期删除策略。
惰性删除策略的实现
过期键的惰性删除策略由expireIfNeeded函数实现,所有读写数据库的
Redis命令在执行之前都会调用expireIfNeeded函数对输入键进行检查:
如果输入键已经过期,那么将输入键从数据库中删除
如果输入键未过期,那么不做任何处理
Redis命令在执行之前都会调用expireIfNeeded函数对输入键进行检查:
如果输入键已经过期,那么将输入键从数据库中删除
如果输入键未过期,那么不做任何处理
子主题
定期删除策略的实现
过期键的定期删除策略由activeExpireCycle函数实现,每当Redis服务器的周期性操作serverCron
函数执行时,activeExpireCycle函数就会被调用,它在规定的时间内,分多次遍历服务器中的各个
数据库,从数据库的expires字典中随机检查一部分键的过期时间,并删除其中的过期键。
activeExpireCycle函数的大体流程为:
函数每次运行时,都从一定数量的数据库中随机取出一定数量的键进行检查,并删除其中的过期键,
比如先从0号数据库开始检查,下次函数运行时,可能就是从1号数据库开始检查,直到15号数据库
检查完毕,又重新从0号数据库开始检查,这样可以保证每个数据库都被检查到。
划重点:
关于定期删除的大体流程,最近面试时有被问到,我就是按上述描述回答的。
可能有的面试官还会问,每次随机删除哪些key呢?可以提下LRU算法(Least Recently Used 最近最
少使用),一般不会再细问,不过有兴趣的同学可以深入研究下。更多面试题,欢迎关注公众号 Java
面试题精选
函数执行时,activeExpireCycle函数就会被调用,它在规定的时间内,分多次遍历服务器中的各个
数据库,从数据库的expires字典中随机检查一部分键的过期时间,并删除其中的过期键。
activeExpireCycle函数的大体流程为:
函数每次运行时,都从一定数量的数据库中随机取出一定数量的键进行检查,并删除其中的过期键,
比如先从0号数据库开始检查,下次函数运行时,可能就是从1号数据库开始检查,直到15号数据库
检查完毕,又重新从0号数据库开始检查,这样可以保证每个数据库都被检查到。
划重点:
关于定期删除的大体流程,最近面试时有被问到,我就是按上述描述回答的。
可能有的面试官还会问,每次随机删除哪些key呢?可以提下LRU算法(Least Recently Used 最近最
少使用),一般不会再细问,不过有兴趣的同学可以深入研究下。更多面试题,欢迎关注公众号 Java
面试题精选
3. RDB对过期键的处理
生成RDB文件
在执行SAVE命令或者BGSAVE命令创建一个新的RDB文件时,程序会对
数据库中的键进行检查,已过期的键不会被保存到新创建的RDB文件中。
数据库中的键进行检查,已过期的键不会被保存到新创建的RDB文件中。
举个例子,如果数据库中包含3个键k1、k2、k3,并且k2已经过期,那么
创建新的RDB文件时,程序只会将k1和k3保存到RDB文件中,k2则会被忽略
创建新的RDB文件时,程序只会将k1和k3保存到RDB文件中,k2则会被忽略
载入RDB文件
在启动Redis服务器时,如果服务器只开启了RDB持久化,那么服务器将会载入RDB文件:
如果服务器以主服务器模式运行,在载入RDB文件时,程序会对文件中保存的键进行检查,
未过期的键会被载入到数据库中,过期键会被忽略。
如果服务器以从服务器模式运行,在载入RDB文件时,文件中保存的所有键,不论是否过
期,都会被载入到数据库中。
因为主从服务器在进行数据同步(完整重同步)的时候,从服务器的数据库会被清空,所
以一般情况下,过期键对载入RDB文件的从服务器不会造成影响。
如果服务器以主服务器模式运行,在载入RDB文件时,程序会对文件中保存的键进行检查,
未过期的键会被载入到数据库中,过期键会被忽略。
如果服务器以从服务器模式运行,在载入RDB文件时,文件中保存的所有键,不论是否过
期,都会被载入到数据库中。
因为主从服务器在进行数据同步(完整重同步)的时候,从服务器的数据库会被清空,所
以一般情况下,过期键对载入RDB文件的从服务器不会造成影响。
4. AOF对过期键的处理
AOF文件写入
如果数据库中的某个键已经过期,并且服务器开启了AOF持久化功能,当过期键被惰性删除
或者定期删除后,程序会向AOF文件追加一条DEL命令,显式记录该键已被删除。
或者定期删除后,程序会向AOF文件追加一条DEL命令,显式记录该键已被删除。
举个例子,如果客户端执行命令GET message访问已经过期的message键,那么服务器将执行
以下3个动作:
从数据库中删除message键
追加一条DEL message命令到AOF文件
-向执行GET message命令的客户端返回空回复
以下3个动作:
从数据库中删除message键
追加一条DEL message命令到AOF文件
-向执行GET message命令的客户端返回空回复
AOF文件重写
在执行AOF文件重写时,程序会对数据库中的键进行检查,已过期的键不会被保存到重写后的
AOF文件中。
AOF文件中。
复制功能对过期键的处理
在主从复制模式下,从服务器的过期键删除动作由主服务器控制:
主服务器在删除一个过期键后,会显式地向所有从服务器发送一个DEL命令,告知从服务器删除这个过期键。
从服务器在执行客户端发送的读命令时,即使发现该键已过期也不会删除该键,照常返回该键的值。
从服务器只有接收到主服务器发送的DEL命令后,才会删除过期键。
主服务器在删除一个过期键后,会显式地向所有从服务器发送一个DEL命令,告知从服务器删除这个过期键。
从服务器在执行客户端发送的读命令时,即使发现该键已过期也不会删除该键,照常返回该键的值。
从服务器只有接收到主服务器发送的DEL命令后,才会删除过期键。
五、Mysql相关面试全集
第一题:MySQL中myisam与innodb的区别?
问题:
MySQL中myisam与innodb的区别?
参考答案:
1、MyISAM:
不支持事务,但是每次查询都是原子的;
支持表级锁,即每次操作对整个表加锁;
存储表的总行数;
一个MYISAM表有三个文件:索引文件、表结构文件、数据文件;
采用非聚集索引,索引文件的数据域存储指向数据文件的指针。辅索引与主索引基本一致,但是辅
索引不用保证唯一性
不支持事务,但是每次查询都是原子的;
支持表级锁,即每次操作对整个表加锁;
存储表的总行数;
一个MYISAM表有三个文件:索引文件、表结构文件、数据文件;
采用非聚集索引,索引文件的数据域存储指向数据文件的指针。辅索引与主索引基本一致,但是辅
索引不用保证唯一性
2、InnoDb:
支持ACID的事务,支持事务的四种隔离级别;
支持行级锁及外键约束:因此可以支持写并发;
不存储总行数;
一个InnoDb引擎存储在一个文件空间(共享表空间,表大小不受操作系统控制,一个表可能
分布在多个文件里),也有可能为多个(设置为独立表空,表大小受操作系统文件大小限制,
一般为2G),受操作系统文件大小的限制;
主键索引采用聚集索引(索引的数据域存储数据文件本身),辅索引的数据域存储主键的值;
因此从辅索引查找数据,需要先通过辅索引找到主键值,再访问辅索引;最好使用自增主键,
防止插入数据时,为维持B+树结构,文件的大调整。
支持ACID的事务,支持事务的四种隔离级别;
支持行级锁及外键约束:因此可以支持写并发;
不存储总行数;
一个InnoDb引擎存储在一个文件空间(共享表空间,表大小不受操作系统控制,一个表可能
分布在多个文件里),也有可能为多个(设置为独立表空,表大小受操作系统文件大小限制,
一般为2G),受操作系统文件大小的限制;
主键索引采用聚集索引(索引的数据域存储数据文件本身),辅索引的数据域存储主键的值;
因此从辅索引查找数据,需要先通过辅索引找到主键值,再访问辅索引;最好使用自增主键,
防止插入数据时,为维持B+树结构,文件的大调整。
3、两者的适用场景:
因为MyISAM相对简单所以在效率上要优于InnoDB.如果系统读多,写少。对原子性要求低。
那么MyISAM最好的选择。且MyISAM恢复速度快。可直接用备份覆盖恢复。
如果系统读少,写多的时候,尤其是并发写入高的时候。InnoDB就是首选了。
因为MyISAM相对简单所以在效率上要优于InnoDB.如果系统读多,写少。对原子性要求低。
那么MyISAM最好的选择。且MyISAM恢复速度快。可直接用备份覆盖恢复。
如果系统读少,写多的时候,尤其是并发写入高的时候。InnoDB就是首选了。
第二题:说说你知道的MySQL的索引类型,并分别简述一下各自的场景?
问题:
说说你知道的MySQL的索引类型,并分别简述一下各自的场景。
参考答案:
1、普通索引:没有任何限制条件的索引,该索引可以在任何数据类型中创建。
2、唯一索引:使用UNIQUE参数可以设置唯一索引。创建该索引时,索引列的值
必须唯一,但允许有空值。通过唯一索引,用户可以快速地定位某条记录,主键索
引是一种特殊的唯一索引。
必须唯一,但允许有空值。通过唯一索引,用户可以快速地定位某条记录,主键索
引是一种特殊的唯一索引。
3、全文索引:仅可用于 MyISAM 表,针对较大的数据,生成全文索引耗时耗空间。
4、空间索引:只能建立在空间数据类型上。这样可以提高系统获取空间数据类型的效
率。仅可用于 MyISAM 表,索引的字段不能为空值。使用SPATIAL参数可以设置索引
为空间索引。
率。仅可用于 MyISAM 表,索引的字段不能为空值。使用SPATIAL参数可以设置索引
为空间索引。
5、单列索引:只对应一个字段的索引。
6、多列索引:在表的多个字段上创建一个索引。该索引指向创建时对应的多个字段,用
户可以通过这几个字段进行查询,想使用该索引,用户必须使用这些字段中的一个字段。
户可以通过这几个字段进行查询,想使用该索引,用户必须使用这些字段中的一个字段。
第三题:MySQL建索引需要遵循哪些原则呢?
问题:
MySQL建索引需要遵循哪些原则呢?
参考答案:
1.选择唯一性索引
唯一性索引的值是唯一的,可以更快速的通过该索引来确定某条记录。
例如,学生表中学号是具有唯一性的字段。为该字段建立唯一性索引
可以很快的确定某个学生的信息。如果使用姓名的话,可能存在同名
现象,从而降低查询速度
例如,学生表中学号是具有唯一性的字段。为该字段建立唯一性索引
可以很快的确定某个学生的信息。如果使用姓名的话,可能存在同名
现象,从而降低查询速度
2.为经常需要排序、分组和联合操作的字段建立索引
经常需要ORDER BY、GROUP BY、DISTINCT和UNION等操作的字段,
排序操作会浪费很多时间。如果为其建立索引,可以有效地避免排序操作。
排序操作会浪费很多时间。如果为其建立索引,可以有效地避免排序操作。
3.为常作为查询条件的字段建立索引
如果某个字段经常用来做查询条件,那么该字段的查询速度会影响整个表的
查询速度。因此,为这样的字段建立索引,可以提高整个表的查询速度。
查询速度。因此,为这样的字段建立索引,可以提高整个表的查询速度。
4.限制索引的数目
索引的数目不是越多越好。每个索引都需要占用磁盘空间,索引越多,需要的磁盘
空间就越大。修改表时,对索引的重构和更新很麻烦。越多的索引,会使更新表变
得很浪费时间。
空间就越大。修改表时,对索引的重构和更新很麻烦。越多的索引,会使更新表变
得很浪费时间。
5.尽量使用数据量少的索引
如果索引的值很长,那么查询的速度会受到影响。例如,对一个CHAR(100)类型的字段
进行全文检索需要的时间肯定要比对CHAR(10)类型的字段需要的时间要多。
进行全文检索需要的时间肯定要比对CHAR(10)类型的字段需要的时间要多。
6.尽量使用前缀来索引
如果索引字段的值很长,最好使用值的前缀来索引。例如,TEXT和BLOG类型的字段,进行全文
检索会很浪费时间。如果只检索字段的前面的若干个字符,这样可以提高检索速度
检索会很浪费时间。如果只检索字段的前面的若干个字符,这样可以提高检索速度
7.删除不再使用或者很少使用的索引
表中的数据被大量更新,或者数据的使用方式被改变后,原有的一些索引可能不再需要。
数据库管理员应当定期找出这些索引,将它们删除,从而减少索引对更新操作的影响。
数据库管理员应当定期找出这些索引,将它们删除,从而减少索引对更新操作的影响。
8.最左前缀匹配原则,非常重要的原则
mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,比如
a 1=”” and=”” b=”2” c=”“> 3 and d = 4 如果建立(a,b,c,d)顺序的索引,
d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。
a 1=”” and=”” b=”2” c=”“> 3 and d = 4 如果建立(a,b,c,d)顺序的索引,
d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。
9.=和in可以乱序
比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序,
mysql的查询优化器会帮你优化成索引可以识别的形式
mysql的查询优化器会帮你优化成索引可以识别的形式
10.尽量选择区分度高的列作为索引。
区分度的公式是count(distinct col)/count(*),表示字段不重复的比例,比例越大我们
扫描的记录数越少,唯一键的区分度是1,而一些状态、性别字段可能在大数据面前区
分度就 是0,那可能有人会问,这个比例有什么经验值吗?使用场景不同,这个值也很
难确定,一般需要join的字段我们都要求是0.1以上,即平均1条扫描10条 记录
扫描的记录数越少,唯一键的区分度是1,而一些状态、性别字段可能在大数据面前区
分度就 是0,那可能有人会问,这个比例有什么经验值吗?使用场景不同,这个值也很
难确定,一般需要join的字段我们都要求是0.1以上,即平均1条扫描10条 记录
11.索引列不能参与计算,保持列“干净”
比如from_unixtime(create_time) = ’2014-05-29’就不能使用到索引,原因很
简单,b+树中存的都是数据表中的字段值,但进行检索时,需要把所有元素都应用
函数才能比较,显然成本 太大。所以语句应该写成
create_time = unix_timestamp(’2014-05-29’);
简单,b+树中存的都是数据表中的字段值,但进行检索时,需要把所有元素都应用
函数才能比较,显然成本 太大。所以语句应该写成
create_time = unix_timestamp(’2014-05-29’);
12.尽量的扩展索引,不要新建索引
比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可
注意:选择索引的最终目的是为了使查询的速度变快。上面给出的原则是最基本的准则,但不能拘泥于上面的准则。读者要在以后的学习和工作中进行不断的实践。根据应用的实际情况进行分析和判断,选择最合适的索引方式。
六、nginx相关面试全集
第一题:什么是Nginx?
问题;
什么是Nginx?
参考答案:
Nginx是一个 轻量级/高性能的反向代理Web服务器,他实现非常高效的
反向代理、负载平衡,他可以处理2-3万并发连接数,官方监测能支持5万
并发,现在中国使用nginx网站用户有很多,例如:新浪、网易、 腾讯等
反向代理、负载平衡,他可以处理2-3万并发连接数,官方监测能支持5万
并发,现在中国使用nginx网站用户有很多,例如:新浪、网易、 腾讯等
第二题:为什么要用Nginx?
问题:
为什么要用Nginx?
参考答案:
1、跨平台、配置简单、方向代理、高并发连接:处理2-3万并发连接数,官
方监测能支持5万并发,内存消耗小:开启10个nginx才占150M内存 ,nginx
处理静态文件好,耗费内存少。
方监测能支持5万并发,内存消耗小:开启10个nginx才占150M内存 ,nginx
处理静态文件好,耗费内存少。
2、而且Nginx内置的健康检查功能:如果有一个服务器宕机,会做一个健康检
查,再发送的请求就不会发送到宕机的服务器了。重新将请求提交到其他的节点上。
查,再发送的请求就不会发送到宕机的服务器了。重新将请求提交到其他的节点上。
3、使用Nginx的话还能:
节省宽带:支持GZIP压缩,可以添加浏览器本地缓存
稳定性高:宕机的概率非常小
接收用户请求是异步的
节省宽带:支持GZIP压缩,可以添加浏览器本地缓存
稳定性高:宕机的概率非常小
接收用户请求是异步的
第三题:为什么Nginx性能这么高?
问题:
为什么Nginx性能这么高?
参考答案:
因为他的事件处理机制:异步非阻塞事件处理机制:运用了epoll模型,提供了一
个队列,排队解决
个队列,排队解决
第四题:Nginx怎么处理请求的?
问题:
Nginx怎么处理请求的?
参考答案:
nginx接收一个请求后,首先由listen和server_name指令匹配server模块,
再匹配server模块里的location,location就是实际地址
再匹配server模块里的location,location就是实际地址
server { # 第一个Server区块开始,表示一个独立的虚拟主机站点
listen 80; # 提供服务的端口,默认80
server_name localhost; # 提供服务的域名主机名
location / { # 第一个location区块开始
root html; # 站点的根目录,相当于Nginx的安装目录
index index.html index.htm; # 默认的首页文件,多个用空格分开
} # 第一个location区块结果
}
listen 80; # 提供服务的端口,默认80
server_name localhost; # 提供服务的域名主机名
location / { # 第一个location区块开始
root html; # 站点的根目录,相当于Nginx的安装目录
index index.html index.htm; # 默认的首页文件,多个用空格分开
} # 第一个location区块结果
}
第五题:什么是正向代理和反向代理?
问题:
什么是正向代理和反向代理?
参考答案:
1、正向代理就是一个人发送一个请求直接就到达了目标的服务器。
2、反向代理就是请求统一被Nginx接收,nginx反向代理服务器接收
到之后,按照一定的规则分发给了后端的业务处理服务器进行处理了。
到之后,按照一定的规则分发给了后端的业务处理服务器进行处理了。
第六题:使用“反向代理服务器的优点是什么?
问题:
使用“反向代理服务器的优点是什么?
参考答案:
反向代理服务器可以隐藏源服务器的存在和特征。它充当互联网云和web
服务器之间的中间层。这对于安全方面来说是很好的,特别是当您使用
web托管服务时。
服务器之间的中间层。这对于安全方面来说是很好的,特别是当您使用
web托管服务时。
第七题:Nginx的优缺点?
问题:
Nginx的优缺点?
参考答案:
优点:
占内存小,可实现高并发连接,处理响应快
可实现http服务器、虚拟主机、方向代理、负载均衡
Nginx配置简单
可以不暴露正式的服务器IP地址
占内存小,可实现高并发连接,处理响应快
可实现http服务器、虚拟主机、方向代理、负载均衡
Nginx配置简单
可以不暴露正式的服务器IP地址
缺点:
动态处理差:nginx处理静态文件好,耗费内存少,但是处
理动态页面则很鸡肋,现在一般前端用nginx作为反向代
理抗住压力,
动态处理差:nginx处理静态文件好,耗费内存少,但是处
理动态页面则很鸡肋,现在一般前端用nginx作为反向代
理抗住压力,
第八题:Nginx应用场景?
问题:
Nginx应用场景?
参考答案:
1、http服务器。Nginx是一个http服务可以独立提供http服务。可以做网页静态服务器。
2、虚拟主机。可以实现在一台服务器虚拟出多个网站,例如个人网站使用的虚拟机。
3、反向代理,负载均衡。当网站的访问量达到一定程度后,单台服务器不能满足用户的请
求时,需要用多台服务器集群可以使用nginx做反向代理。并且多台服务器可以平均分担负
载,不会应为某台服务器负载高宕机而某台服务器闲置的情况。
求时,需要用多台服务器集群可以使用nginx做反向代理。并且多台服务器可以平均分担负
载,不会应为某台服务器负载高宕机而某台服务器闲置的情况。
4、nginz 中也可以配置安全管理、比如可以使用Nginx搭建API接口网关,对每个接口服务
进行拦截。
进行拦截。
第九题:Nginx目录结构有哪些?
问题:
Nginx目录结构有哪些?
参考答案:
[root@localhost ~]# tree /usr/local/nginx
/usr/local/nginx
├── client_body_temp
├── conf # Nginx所有配置文件的目录
│ ├── fastcgi.conf # fastcgi相关参数的配置文件
│ ├── fastcgi.conf.default # fastcgi.conf的原始备份文件
│ ├── fastcgi_params # fastcgi的参数文件
│ ├── fastcgi_params.default
│ ├── koi-utf
│ ├── koi-win
│ ├── mime.types # 媒体类型
│ ├── mime.types.default
│ ├── nginx.conf # Nginx主配置文件
│ ├── nginx.conf.default
│ ├── scgi_params # scgi相关参数文件
│ ├── scgi_params.default
│ ├── uwsgi_params # uwsgi相关参数文件
│ ├── uwsgi_params.default
│ └── win-utf
├── fastcgi_temp # fastcgi临时数据目录
├── html # Nginx默认站点目录
│ ├── 50x.html # 错误页面优雅替代显示文件,例如当出现502错误时会调用此页面
│ └── index.html # 默认的首页文件
├── logs # Nginx日志目录
│ ├── access.log # 访问日志文件
│ ├── error.log # 错误日志文件
│ └── nginx.pid # pid文件,Nginx进程启动后,会把所有进程的ID号写到此文件
├── proxy_temp # 临时目录
├── sbin # Nginx命令目录
│ └── nginx # Nginx的启动命令
├── scgi_temp # 临时目录
└── uwsgi_temp # 临时目录
/usr/local/nginx
├── client_body_temp
├── conf # Nginx所有配置文件的目录
│ ├── fastcgi.conf # fastcgi相关参数的配置文件
│ ├── fastcgi.conf.default # fastcgi.conf的原始备份文件
│ ├── fastcgi_params # fastcgi的参数文件
│ ├── fastcgi_params.default
│ ├── koi-utf
│ ├── koi-win
│ ├── mime.types # 媒体类型
│ ├── mime.types.default
│ ├── nginx.conf # Nginx主配置文件
│ ├── nginx.conf.default
│ ├── scgi_params # scgi相关参数文件
│ ├── scgi_params.default
│ ├── uwsgi_params # uwsgi相关参数文件
│ ├── uwsgi_params.default
│ └── win-utf
├── fastcgi_temp # fastcgi临时数据目录
├── html # Nginx默认站点目录
│ ├── 50x.html # 错误页面优雅替代显示文件,例如当出现502错误时会调用此页面
│ └── index.html # 默认的首页文件
├── logs # Nginx日志目录
│ ├── access.log # 访问日志文件
│ ├── error.log # 错误日志文件
│ └── nginx.pid # pid文件,Nginx进程启动后,会把所有进程的ID号写到此文件
├── proxy_temp # 临时目录
├── sbin # Nginx命令目录
│ └── nginx # Nginx的启动命令
├── scgi_temp # 临时目录
└── uwsgi_temp # 临时目录
第十题:Nginx配置文件nginx.conf有哪些属性模块?
问题:
Nginx配置文件nginx.conf有哪些属性模块?
参考答案:
worker_processes 1; # worker进程的数量
events { # 事件区块开始
worker_connections 1024; # 每个worker进程支持的最大连接数
} # 事件区块结束
http { # HTTP区块开始
include mime.types; # Nginx支持的媒体类型库文件
default_type application/octet-stream; # 默认的媒体类型
sendfile on; # 开启高效传输模式
keepalive_timeout 65; # 连接超时
server { # 第一个Server区块开始,表示一个独立的虚拟主机站点
listen 80; # 提供服务的端口,默认80
server_name localhost; # 提供服务的域名主机名
location / { # 第一个location区块开始
root html; # 站点的根目录,相当于Nginx的安装目录
index index.html index.htm; # 默认的首页文件,多个用空格分开
} # 第一个location区块结果
error_page 500502503504 /50x.html; # 出现对应的http状态码时,使用50x.html回应客户
location = /50x.html { # location区块开始,访问50x.html
root html; # 指定对应的站点目录为html
}
}
......
events { # 事件区块开始
worker_connections 1024; # 每个worker进程支持的最大连接数
} # 事件区块结束
http { # HTTP区块开始
include mime.types; # Nginx支持的媒体类型库文件
default_type application/octet-stream; # 默认的媒体类型
sendfile on; # 开启高效传输模式
keepalive_timeout 65; # 连接超时
server { # 第一个Server区块开始,表示一个独立的虚拟主机站点
listen 80; # 提供服务的端口,默认80
server_name localhost; # 提供服务的域名主机名
location / { # 第一个location区块开始
root html; # 站点的根目录,相当于Nginx的安装目录
index index.html index.htm; # 默认的首页文件,多个用空格分开
} # 第一个location区块结果
error_page 500502503504 /50x.html; # 出现对应的http状态码时,使用50x.html回应客户
location = /50x.html { # location区块开始,访问50x.html
root html; # 指定对应的站点目录为html
}
}
......
第十一题:Nginx静态资源?
问题:
Nginx静态资源?
参考答案:
静态资源访问,就是存放在nginx的html页面,我们可以自己编写
第十二题:如何用Nginx解决前端跨域问题?
问题:
如何用Nginx解决前端跨域问题?
参考答案:
使用Nginx转发请求。把跨域的接口写成调本域的接口,然后将这些接口转发到真正的请求地址。
第十三题:Nginx虚拟主机怎么配置?
问题:
Nginx虚拟主机怎么配置?
参考答案:
1、基于域名的虚拟主机,通过域名来区分虚拟主机——应用:外部网站
2、基于端口的虚拟主机,通过端口来区分虚拟主机——应用:公司内部网站,外部网站的管理后台
3、基于ip的虚拟主机。
示例
基于虚拟主机配置域名
需要建立/data/www /data/bbs目录,windows本地hosts添加虚拟机ip地址对应的域名解析;对应域名网站目录下新增index.html文件;
#当客户端访问www.lijie.com,监听端口号为80,直接跳转到data/www目录下文件
server {
listen 80;
server_name www.lijie.com;
location / {
root data/www;
index index.html index.htm;
}
}
#当客户端访问www.lijie.com,监听端口号为80,直接跳转到data/bbs目录下文件
server {
listen 80;
server_name bbs.lijie.com;
location / {
root data/bbs;
index index.html index.htm;
}
}
server {
listen 80;
server_name www.lijie.com;
location / {
root data/www;
index index.html index.htm;
}
}
#当客户端访问www.lijie.com,监听端口号为80,直接跳转到data/bbs目录下文件
server {
listen 80;
server_name bbs.lijie.com;
location / {
root data/bbs;
index index.html index.htm;
}
}
基于端口的虚拟主机
使用端口来区分,浏览器使用域名或ip地址:端口号 访问
#当客户端访问www.lijie.com,监听端口号为8080,直接跳转到data/www目录下文件
server {
listen 8080;
server_name 8080.lijie.com;
location / {
root data/www;
index index.html index.htm;
}
}
#当客户端访问www.lijie.com,监听端口号为80直接跳转到真实ip服务器地址 127.0.0.1:8080
server {
listen 80;
server_name www.lijie.com;
location / {
proxy_pass http://127.0.0.1:8080;
index index.html index.htm;
}
}
server {
listen 8080;
server_name 8080.lijie.com;
location / {
root data/www;
index index.html index.htm;
}
}
#当客户端访问www.lijie.com,监听端口号为80直接跳转到真实ip服务器地址 127.0.0.1:8080
server {
listen 80;
server_name www.lijie.com;
location / {
proxy_pass http://127.0.0.1:8080;
index index.html index.htm;
}
}
第十四题:location的作用是什么?
问题:
location的作用是什么?
参考答案:
location指令的作用是根据用户请求的URI来执行不同的应用,
也就是根据用户请求的网站URL进行匹配,匹配成功即进行相
关的操作。更多面试题,欢迎关注公众号 Java面试题精选
也就是根据用户请求的网站URL进行匹配,匹配成功即进行相
关的操作。更多面试题,欢迎关注公众号 Java面试题精选
location的语法能说出来吗?
注意:~ 代表自己输入的英文字母
Location正则案例
#优先级1,精确匹配,根路径
location =/ {
return 400;
}
#优先级2,以某个字符串开头,以av开头的,优先匹配这里,区分大小写
location ^~ /av {
root /data/av/;
}
#优先级3,区分大小写的正则匹配,匹配/media*****路径
location ~ /media {
alias /data/static/;
}
#优先级4 ,不区分大小写的正则匹配,所有的****.jpg|gif|png 都走这里
location ~* .*\.(jpg|gif|png|js|css)$ {
root /data/av/;
}
#优先7,通用匹配
location / {
return 403;
}
location =/ {
return 400;
}
#优先级2,以某个字符串开头,以av开头的,优先匹配这里,区分大小写
location ^~ /av {
root /data/av/;
}
#优先级3,区分大小写的正则匹配,匹配/media*****路径
location ~ /media {
alias /data/static/;
}
#优先级4 ,不区分大小写的正则匹配,所有的****.jpg|gif|png 都走这里
location ~* .*\.(jpg|gif|png|js|css)$ {
root /data/av/;
}
#优先7,通用匹配
location / {
return 403;
}
第十五题:限流怎么做的?
问题:
限流怎么做的?
参考答案:
Nginx限流就是限制用户请求速度,防止服务器受不了
限流有3种
1、正常限制访问频率(正常流量)
2、突发限制访问频率(突发流量)
3、限制并发连接数
1、正常限制访问频率(正常流量)
2、突发限制访问频率(突发流量)
3、限制并发连接数
Nginx的限流都是基于漏桶流算法
实现三种限流算法
1、正常限制访问频率(正常流量):
限制一个用户发送的请求,我Nginx多久接收一个请求。
Nginx中使用ngx_http_limit_req_module模块来限制的访问频率,
限制的原理实质是基于漏桶算法原理来实现的。在nginx.conf配置
文件中可以使用limit_req_zone命令及limit_req命令限制单个IP的
请求处理频率。
限制的原理实质是基于漏桶算法原理来实现的。在nginx.conf配置
文件中可以使用limit_req_zone命令及limit_req命令限制单个IP的
请求处理频率。
#定义限流维度,一个用户一分钟一个请求进来,多余的全部漏掉
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/m;
#绑定限流维度
server{
location/seckill.html{
limit_req zone=zone;
proxy_pass http://lj_seckill;
}
}
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/m;
#绑定限流维度
server{
location/seckill.html{
limit_req zone=zone;
proxy_pass http://lj_seckill;
}
}
1r/s代表1秒一个请求,1r/m一分钟接收一个请求, 如果Nginx这时还有别人的
请求没有处理完,Nginx就会拒绝处理该用户请求。
请求没有处理完,Nginx就会拒绝处理该用户请求。
2、突发限制访问频率(突发流量):
限制一个用户发送的请求,我Nginx多久接收一个。
上面的配置一定程度可以限制访问频率,但是也存在着一个问题:如果突发流量超出
请求被拒绝处理,无法处理活动时候的突发流量,这时候应该如何进一步处理呢?
请求被拒绝处理,无法处理活动时候的突发流量,这时候应该如何进一步处理呢?
Nginx提供burst参数结合nodelay参数可以解决流量突发的问题,可以设置能处理的超
过设置的请求数外能额外处理的请求数。我们可以将之前的例子添加burst参数以及nodelay参数:
过设置的请求数外能额外处理的请求数。我们可以将之前的例子添加burst参数以及nodelay参数:
#定义限流维度,一个用户一分钟一个请求进来,多余的全部漏掉
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/m;
#绑定限流维度
server{
location/seckill.html{
limit_req zone=zone burst=5 nodelay;
proxy_pass http://lj_seckill;
}
}
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/m;
#绑定限流维度
server{
location/seckill.html{
limit_req zone=zone burst=5 nodelay;
proxy_pass http://lj_seckill;
}
}
为什么就多了一个 burst=5 nodelay; 呢,多了这个可以代表Nginx对于一个用户的请求会立即处理
前五个,多余的就慢慢来落,没有其他用户的请求我就处理你的,有其他的请求的话我Nginx就漏掉
不接受你的请求
前五个,多余的就慢慢来落,没有其他用户的请求我就处理你的,有其他的请求的话我Nginx就漏掉
不接受你的请求
3、 限制并发连接数
Nginx中的ngx_http_limit_conn_module模块提供了限制并发连接数的功能,可以
使用limit_conn_zone指令以及limit_conn执行进行配置。接下来我们可以通过一个
简单的例子来看下:
使用limit_conn_zone指令以及limit_conn执行进行配置。接下来我们可以通过一个
简单的例子来看下:
http {
limit_conn_zone $binary_remote_addr zone=myip:10m;
limit_conn_zone $server_name zone=myServerName:10m;
}
server {
location / {
limit_conn myip 10;
limit_conn myServerName 100;
rewrite / http://www.lijie.net permanent;
}
}
limit_conn_zone $binary_remote_addr zone=myip:10m;
limit_conn_zone $server_name zone=myServerName:10m;
}
server {
location / {
limit_conn myip 10;
limit_conn myServerName 100;
rewrite / http://www.lijie.net permanent;
}
}
上面配置了单个IP同时并发连接数最多只能10个连接,并且设置了整个虚拟服务器同时
最大并发数最多只能100个链接。当然,只有当请求的header被服务器处理后,虚拟服
务器的连接数才会计数。
最大并发数最多只能100个链接。当然,只有当请求的header被服务器处理后,虚拟服
务器的连接数才会计数。
刚才有提到过Nginx是基于漏桶算法原理实现的,实际上限流一般都是基于漏桶算法和令牌桶算法实现的。接下来我们来看看两个算法的介绍:
第十七题:漏桶流算法和令牌桶算法知道?
问题:
漏桶流算法和令牌桶算法知道?
参考答案:
1、漏桶算法
漏桶算法是网络世界中流量整形或速率限制时经常使用的一种算法,它的主要
目的是控制数据注入到网络的速率,平滑网络上的突发流量。漏桶算法提供了
一种机制,通过它,突发流量可以被整形以便为网络提供一个稳定的流量。
目的是控制数据注入到网络的速率,平滑网络上的突发流量。漏桶算法提供了
一种机制,通过它,突发流量可以被整形以便为网络提供一个稳定的流量。
也就是我们刚才所讲的情况。漏桶算法提供的机制实际上就是刚才的案例:突发
流量会进入到一个漏桶,漏桶会按照我们定义的速率依次处理请求,如果水流过
大也就是突发流量过大就会直接溢出,则多余的请求会被拒绝。所以漏桶算法能
控制数据的传输速率。
流量会进入到一个漏桶,漏桶会按照我们定义的速率依次处理请求,如果水流过
大也就是突发流量过大就会直接溢出,则多余的请求会被拒绝。所以漏桶算法能
控制数据的传输速率。
图
子主题
2、令牌桶算法
令牌桶算法是网络流量整形和速率限制中最常使用的一种算法。典型情况下,
令牌桶算法用来控制发送到网络上的数据的数目,并允许突发数据的发送。
Google开源项目Guava中的RateLimiter使用的就是令牌桶控制算法。
令牌桶算法用来控制发送到网络上的数据的数目,并允许突发数据的发送。
Google开源项目Guava中的RateLimiter使用的就是令牌桶控制算法。
令牌桶算法的机制如下:存在一个大小固定的令牌桶,会以恒定的速率源源不
断产生令牌。如果令牌消耗速率小于生产令牌的速度,令牌就会一直产生直至
装满整个令牌桶。更多面试题,欢迎关注公众号 Java面试题精选
断产生令牌。如果令牌消耗速率小于生产令牌的速度,令牌就会一直产生直至
装满整个令牌桶。更多面试题,欢迎关注公众号 Java面试题精选
图:
第十八题:为什么要做动静分离?
问题:
为什么要做动静分离?
参考答案:
1、Nginx是当下最热的Web容器,网站优化的重要点在于静态化网站,
网站静态化的关键点则是是动静分离,动静分离是让动态网站里的
动态网页根据一定规则把不变的资源和经常变的资源区分开来,动
静资源做好了拆分以后,我们则根据静态资源的特点将其做缓存操作。
网站静态化的关键点则是是动静分离,动静分离是让动态网站里的
动态网页根据一定规则把不变的资源和经常变的资源区分开来,动
静资源做好了拆分以后,我们则根据静态资源的特点将其做缓存操作。
2、让静态的资源只走静态资源服务器,动态的走动态的服务器
3、Nginx的静态处理能力很强,但是动态处理能力不足,因此,在企业中常
用动静分离技术。
用动静分离技术。
4、对于静态资源比如图片,js,css等文件,我们则在反向代理服务器nginx中
进行缓存。这样浏览器在请求一个静态资源时,代理服务器nginx就可以直
接处理,无需将请求转发给后端服务器tomcat。
进行缓存。这样浏览器在请求一个静态资源时,代理服务器nginx就可以直
接处理,无需将请求转发给后端服务器tomcat。
5、若用户请求的动态文件,比如servlet,jsp则转发给Tomcat服务器处理,
从而实现动静分离。这也是反向代理服务器的一个重要的作用。
从而实现动静分离。这也是反向代理服务器的一个重要的作用。
第十九题:Nginx怎么做的动静分离?
问题:
Nginx怎么做的动静分离?
参考答案:
只需要指定路径对应的目录。location/可以使用正则表达式匹配。并指定对
应的硬盘中的目录。如下:(操作都是在Linux上)
应的硬盘中的目录。如下:(操作都是在Linux上)
location /image/ {
root /usr/local/static/;
autoindex on;
}
root /usr/local/static/;
autoindex on;
}
1.创建目录
mkdir /usr/local/static/image
2.进入目录
cd /usr/local/static/image
3.放一张照片上去
1.jpg
4.重启 nginx
sudo nginx -s reload
打开浏览器 输入 server_name/image/1.jpg 就可以访问该静态图片了
第二十题:Nginx负载均衡的算法怎么实现的?策略有哪些?
问题:
Nginx负载均衡的算法怎么实现的?策略有哪些?
参考答案:
为了避免服务器崩溃,大家会通过负载均衡的方式来分担服务器压力。
将对台服务器组成一个集群,当用户访问时,先访问到一个转发服务
器,再由转发服务器将访问分发到压力更小的服务器。
将对台服务器组成一个集群,当用户访问时,先访问到一个转发服务
器,再由转发服务器将访问分发到压力更小的服务器。
Nginx负载均衡实现的策略有以下五种:
1 轮询(默认)
每个请求按时间顺序逐一分配到不同的后端服务器,如果后端某个服务器宕机,能自动剔除故障系统
upstream backserver {
server 192.168.0.12;
server 192.168.0.13;
}
server 192.168.0.12;
server 192.168.0.13;
}
2 权重 weight
weight的值越大分配
到的访问概率越高,主要用于后端每台服务器性能不均衡的情况下。其次是为在主从的情况下
设置不同的权值,达到合理有效的地利用主机资源。
设置不同的权值,达到合理有效的地利用主机资源。
upstream backserver {
server 192.168.0.12 weight=2;
server 192.168.0.13 weight=8;
}
server 192.168.0.12 weight=2;
server 192.168.0.13 weight=8;
}
权重越高,在被访问的概率越大,如上例,分别是20%,80%。
五、网络相关面试全集
第一题:RPC协议与HTTP协议的区别?
问题:
RPC协议与HTTP协议的区别?
参考答案:
1、RPC:远程过程调用,一般用于一台计算机调用另一个计算机上的服务,
rpc能让我们像调用本地方法一样调用远程方法;
rpc能让我们像调用本地方法一样调用远程方法;
2、HTTP:超文本传输协议,一般用于浏览器和服务器之间的通讯;
3、RPC效率更高,但是实现起来较复杂;
4、HTTP一般使用json传输数据,RPC一般采用二进制;
第二题:什么websocket?它有什么特点?
问题:
什么websocket?它有什么特点?
参考答案:
作用:
用来和服务端保持长连接,使服务端能主动推送消息给客户端
用来和服务端保持长连接,使服务端能主动推送消息给客户端
对比http:
websocket是一种类似http的协议,可以理解成http协议的加强版;
http每次请求都需要建立tcp连接,而且一个request只能对应一个
response,每次请求必须由客户端发起,由服务端响应;而websocket
能使客户端在和服务端建立好tcp连接后,与服务端保持会话连接,
服务端可以自由向客户端推送消息
websocket是一种类似http的协议,可以理解成http协议的加强版;
http每次请求都需要建立tcp连接,而且一个request只能对应一个
response,每次请求必须由客户端发起,由服务端响应;而websocket
能使客户端在和服务端建立好tcp连接后,与服务端保持会话连接,
服务端可以自由向客户端推送消息
注意:如果服务端同时维护了很多websocket连接,会对服务端造成很大
压力,需要我们对websocket做一些优化(有个互联网独角兽公司面试官
这么问我的)
我目前能想到的优化就是:
1、合并推送:将一些能合并的消息整合到一次推送,以此减少websocket连接
2、横向扩展:增加服务器 0.0
压力,需要我们对websocket做一些优化(有个互联网独角兽公司面试官
这么问我的)
我目前能想到的优化就是:
1、合并推送:将一些能合并的消息整合到一次推送,以此减少websocket连接
2、横向扩展:增加服务器 0.0
六、问题解决思路面试全集
第一题:如果你发现某个接口响应很慢,该怎么排查?
问题:
如果你发现某个接口响应很慢,该怎么排查?
参考答案:
导致接口响应慢的原因太多了:网络、应用层、数据库事务、服务器自身、慢sql等
网络:对于单个请求来讲,网络因素影响其实很小,除非网络挂了导致请求超时才能意识到;
而对于大批量请求,每个请求慢10ms,请求多了,时间也就长了,这种情况可以检查下你的
应用部署机和数据库机地理位置是不是隔得很远,比如一个在华东一个在西南,地理距离也
会对请求响应时间产生影响,请求量越大越明显;
而对于大批量请求,每个请求慢10ms,请求多了,时间也就长了,这种情况可以检查下你的
应用部署机和数据库机地理位置是不是隔得很远,比如一个在华东一个在西南,地理距离也
会对请求响应时间产生影响,请求量越大越明显;
应用层:就是我们敲的controller、service那些代码,这一层出问题很好解决,因为代码毕竟都
是我们敲的嘛,一看日志就大概知道什么原因,最多的就是出现死循环(当然一旦出现死循环也
不只是响应慢那么简单了);代码逻辑写的差点其实不会太影响性能,现在的cpu执行效率你尽
管放心,再怎么优化也顶不了少一次io;
是我们敲的嘛,一看日志就大概知道什么原因,最多的就是出现死循环(当然一旦出现死循环也
不只是响应慢那么简单了);代码逻辑写的差点其实不会太影响性能,现在的cpu执行效率你尽
管放心,再怎么优化也顶不了少一次io;
数据库事务:检查下你的数据库是不是卡事务了,导致锁了很多表;
服务器自身:服务器是不是卡了,cpu是不是炸了,内存是不是满了;
慢sql:这一层出问题的几率很大,同一组查询结果,由于sql不同,耗时能相差几百上千倍,可以
通过查看sql执行计划来排查问题,
通过查看sql执行计划来排查问题,
第二题:导致线程阻塞的原因有哪些?
问题:
导致线程阻塞的原因有哪些?
参考答案:
1、主动调用Thread.sleep(1000)方法:暂时放弃对cpu的使用,不会释放锁,睡眠
时间到了后直接进入就绪态,拿到cpu时间片立即执行;
时间到了后直接进入就绪态,拿到cpu时间片立即执行;
2、主动调用Thread.yield()方法:向调度系统表明当前线程愿意放弃其对处理器的使用;
3、遇到Object类的wait()方法:放弃当前持有的锁,进入等待状态,直到有其他
线程将其唤醒;
线程将其唤醒;
4、遇到Thread类的join()方法:当线程t调用在当前线程内部调用join时,当前
线程会陷入阻塞,直到线程t执行完;
线程会陷入阻塞,直到线程t执行完;
5、cpu时间片用完:线程调度是由操作系统控制,同一个系统里面线程那么多,
cpu不可能只执行你这一个线程,所以每个线程在执行前都需要先拿到cpu时间
片,用完后进入就绪态,再次拿到时间片即可开始执行;
cpu不可能只执行你这一个线程,所以每个线程在执行前都需要先拿到cpu时间
片,用完后进入就绪态,再次拿到时间片即可开始执行;
分支主题
子主题
子主题
https://huangjie.blog.csdn.net/article/details/113549520?spm=1001.2014.3001.5502
https://mp.weixin.qq.com/s/uE2G1t6XNuCxkrOYPn4HPw
CENTOS替代品
https://rockylinux.org/download
0 条评论
下一页