------------------------------面经1------------------------------------
1. list和set的区别
- list可以存储重复元素,set不能存储重复元素
- list可以存储多个null值,set只能存储一个null值
- list的存储顺序和插入顺序一致,set的存储顺序和插入顺序不一致
2. 写过切面类吗
- 横切关注点与业务逻辑无关,比如日志、声明式事务、安全等,都与业务逻辑无关,因此将这些东西抽象为单独的模块,就是切面类。
- Spring中的切面类需要两个注解,一个是@Component,将切面类加入IOC容器中,另一个是@Aspect,使其成为切面类
- 切面类中的功能被称为通知,分为五种:
(1)前置通知:在目标方法调用之前调用通知的内容
(2)后置通知:在目标方法完成之后调用通知的内容,不关心方法的输出是什么
(3)返回通知:在目标方法成功执行之后
(4)异常通知:在目标方法抛出异常后调用通知
(5)环绕通知:通知包裹了被通知的方法,在被通知方法调用之前和之后执行自定义的行为
@Component
@Aspect
public class LoggingAspect {
//现在想在实现类中的每个方法执行前、后、以及是否发生异常等信息打印出来,需要把日志信息抽取出来,写到对应的切面的类中 LoggingAspect.java 中
//要想把一个类变成切面类,需要两步,
//① 在类上使用 @Component 注解 把切面类加入到IOC容器中
//② 在类上使用 @Aspect 注解 使之成为切面类
/**
* 前置通知:目标方法执行之前执行以下方法体的内容
* @param jp
*/
@Before("execution(* com.svse.aop.*.*(..))")
public void beforeMethod(JoinPoint jp){
String methodName =jp.getSignature().getName();
System.out.println("【前置通知】the method 【" + methodName + "】 begins with " + Arrays.asList(jp.getArgs()));
}
/**
* 返回通知:目标方法正常执行完毕时执行以下代码
* @param jp
* @param result
*/
@AfterReturning(value="execution(* com.svse.aop.*.*(..))",returning="result")
public void afterReturningMethod(JoinPoint jp, Object result){
String methodName =jp.getSignature().getName();
System.out.println("【返回通知】the method 【" + methodName + "】 ends with 【" + result + "】");
}
/**
* 后置通知:目标方法执行之后执行以下方法体的内容,不管是否发生异常。
* @param jp
*/
@After("execution(* com.svse.aop.*.*(..))")
public void afterMethod(JoinPoint jp){
System.out.println("【后置通知】this is a afterMethod advice...");
}
/**
* 异常通知:目标方法发生异常的时候执行以下代码
*/
@AfterThrowing(value="execution(* com.qcc.beans.aop.*.*(..))",throwing="e")
public void afterThorwingMethod(JoinPoint jp, NullPointerException e){
String methodName = jp.getSignature().getName();
System.out.println("【异常通知】the method 【" + methodName + "】 occurs exception: " + e);
}
/**
* 环绕通知:目标方法执行前后分别执行一些代码,发生异常的时候执行另外一些代码
* @return
*/
/*@Around(value="execution(* com.svse.aop.*.*(..))")
public Object aroundMethod(ProceedingJoinPoint jp){
String methodName = jp.getSignature().getName();
Object result = null;
try {
System.out.println("【环绕通知中的--->前置通知】:the method 【" + methodName + "】 begins with " + Arrays.asList(jp.getArgs()));
//执行目标方法
result = jp.proceed();
System.out.println("【环绕通知中的--->返回通知】:the method 【" + methodName + "】 ends with " + result);
} catch (Throwable e) {
System.out.println("【环绕通知中的--->异常通知】:the method 【" + methodName + "】 occurs exception " + e);
}
System.out.println("【环绕通知中的--->后置通知】:-----------------end.----------------------");
return result;
}*/
}
3. TreeMap中自定义类作为key如何比较
(1)自定义类实现Comparable接口
import java.util.Comparator;
import java.util.HashMap;
import java.util.TreeMap;
class Solution {
public static void main(String[] args) {
TreeMap<Student, Integer> treeMap = new TreeMap<>();
treeMap.put(new Student(1,"xixi"), 1);
treeMap.put(new Student(1,"haha"), 1);
}
}
class Student implements Comparable<Student>{
private int age;
private String name;
public Student(int age, String name) {
this.age = age;
this.name = name;
}
@Override
public int compareTo(Student o) {
return this.age - o.age;
}
}
(2)创建TreeMap对象时,指定比较器 Comparator
import java.util.Comparator;
import java.util.HashMap;
import java.util.TreeMap;
class Solution {
public static void main(String[] args) {
TreeMap<Student, Integer> treeMap = new TreeMap<>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.getAge() - o2.getAge();
}
});
treeMap.put(new Student(1,"xixi"), 1);
treeMap.put(new Student(1,"haha"), 1);
}
}
class Student{
private int age;
private String name;
public Student(int age, String name) {
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public String getName() {
return name;
}
}
4. 索引,如何知道查询过程有没有使用索引,索引结构
- (1)什么是索引?
索引是将数据库中的一个或者多个字段,进行排序的一种结构,可以提高数据库的查询效率。 - (2)有哪些索引结构?
Hash表、B树、B+树等。 - (3)各个索引结构的特点
Hash表:查询时间复杂度O(1),但是不合适范围查找、模糊查找、排序等;
B树:多路搜索树,关键字和记录放在一起,非叶子节点也存储数据
B+树:叶子结点只存储关键字,非叶子节点存储所有的关键字和记录,叶子结点之间存在双向指针(便于范围查找) - (4)如何知道查询过程有没有使用索引?在查询语句前加上explain就可以了
- (5)explain列解释?
- type:这是重要的列,显示连接使用了何种类型。从最好到最差的连接类型为const、eq_reg、ref、range、index和ALL
- possible_keys:显示可能应用在这张表中的索引。如果为空,没有可能的索引
- key: 实际使用的索引。如果为NULL,则没有使用索引。很少的情况下,MYSQL会选择优化不足的索引。这种情况下,可以在SELECT语句中使用USE INDEX(indexname)来强制使用一个索引或者用IGNORE INDEX(indexname)来强制MYSQL忽略索引
5. 项目部署在哪里的
服务器:
- 操作系统:CentOs
- 内存:2GB
---------------------------------面经2---------------------------------
1. HaspMap扩容是怎样扩容的,为什么都是2的N次幂的大小。
- (1)初始容量
默认构造函数:初始容量16
指定初始容量:Hash会选择大于该数字的第一个2的幂作为容量。(3->4、7->8、9->16) - (2)什么时候扩容
第一种情况:put元素之前,会判断HashMap是否为空,或者长度为0
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
第二种情况:put元素之后,看当前元素的总数是否大于阈值
//jdk 1.8源码,可以看出,HashMap在put元素之后,会计算当前元素的总数是否大于阈值
//(阈值 = 容量 * 负载因子)
if (++size > threshold)
resize();
- (3)扩容之后的容量
对于第一种情况:初始化的扩容,扩容之后为16;
对于第二种情况:扩容为原来的两倍。
- (4)扩容之后,旧的hash表中的元素怎么移到新的hash表中
------判断如果是红黑树节点,则调用TreeNode的split方法,来判断是否需要将原来的红黑树节点拆分为两个节点
------else:
jdk1.8之前:
***先新建一个数组,数组长度为原数组的2倍
***循环遍历原数组的每一个键值对,得到键的hash然后与新数组的长度进行&运算得到新数组的位置。然后把键值对放到对应的位置。
***(需要遍历原hash表中的每个元素)
jdk1.8之后
我们在扩充HashMap的时候,不需要像JDK1.7的实现那样重新计算hash,只需要看看原来的hash值新增的那个bit是1还是0就好了,是0的话索引没变,是1的话索引变成“原索引+oldCap”。
这个设计确实非常的巧妙,既省去了重新计算hash值的时间,而且同时,由于新增的1bit是0还是1可以认为是随机的,因此resize的过程,均匀的把之前的冲突的节点分散到新的bucket了。这一块就是JDK1.8新增的优化点。有一点注意区别,JDK1.7中rehash的时候,旧链表迁移新链表的时候,如果在新表的数组索引位置相同,则链表元素会倒置,但是JDK1.8不会倒置。
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
//这一步是否为0只需要看元素的二进制数对应数组长度的二进制数1那个位置是否为0.
if ((e.hash & oldCap) == 0) { //e.hash & oldCap 就是新增的bit位
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead; //这里很重要,新的位置为原老所处的位置+原数组的长度
}
- (5)为什么都是2的N次幂的大小
在源码中,向集合中添加元素时,会使用(n - 1) & hash的计算方法来得出该元素在集合中的位置(n就是tab.length)。当hash表的长度为2的幂次方的时候,n-1的二进制形式就是…1111 1111111(低位就是连续的1),这样与添加元素的hash值进行位运算时,能够充分的散列,使得添加的元素均匀分布在HashMap的每个位置上,减少hash碰撞。
2. 内存模型 new一个类的具体过程 调用一个成员的具体在内存中的过程
new一个对象的具体过程:
(1)类加载检查
当虚拟机遇到一条new指令之后,它会根据指令参数到常量池中查看是否可以定位到类的符号引用,如果没有,则进行类加载过程
(2)在堆空间上分配内存
一个对象所需的空间大小,在类加载阶段就已经知道,分类堆内存的过程就是划分出一块对象大小的内存出来。根据堆内存空间是否规整(所用的垃圾收集器是否有整理功能),为对象分配内存有两种方式:如果堆内存规整,则使用碰撞指针法(指针指向使用和未使用空间的临界位置);如果不是规整的,则使用空闲列表法(列表记录哪些空间没有被使用)
(3)初始化零值
将分配到的内存空间都初始化零值,这步可以保证对象的实例变量不用初始化就可以使用
(4)设置对象头
对象头包含了对象的一些必要设置,比如属于哪个类、对象的hash值、GC分代年龄等等
(5)执行init方法
这步是将对象按照程序员的意愿进行初始化。
简化的回答:
Instence instence = new Instence();
发生了啥?
(1)在堆空间分配内存
(2)在堆空间上初始化对象
(3)将堆空间的地址赋值给引用变量
多线程环境下,分配内存可能出现并发问题,怎么解决?
(1)CAS+失败重试 :保证指针操作的原子性
(2)本地线程分配缓冲(Thread Local Allocation Buffer, TLAB) :每个线程在分配内存时,JVM会为每个内存预留一部分堆内存空间,这部分的空间叫做本地线程分配缓冲,只有在申请TLAB才需要同步锁定。(一个线程可能进行多次对象内存空间分配)
3. 静态代理 动态代理
(占坑,我自己也搞不明白)
4. 索引 联合列索引
- 联合索引是将数据库中多个字段组合在一起作为索引列;
- 联合索引关键字匹配的时候满足最左前缀匹配法则(以最左边的为起点任何连续的索引都能匹配上)
联合索引本质:
当创建 (a,b,c)联合索引时,相当于创建了(a)单列索引,(a,b)联合索引以及 (a,b,c)联合索引,想要索引生效的话,只能使用 a和a,b和a,b,c三种组合;
5. 线程池
(1) 什么是线程池?使用线程池的好处?
(2)线程池类、构造函数的核心参数
(3)线程池的原理
(4)Excutors提供的四个线程池
(5)如何设置线程池初始容量
线程的创建
了解spring吗
了解servlet吗
8. java I/O相关的简单介绍一下
9. 关于maven说一下
10. Hashmap了解吗
11. 那你说说关于红黑树
12. Java 反射机制
13. 网络层次 HTTP TCP
14. 对称加密 非对称加密
15. 乐观锁 悲观锁
16. 设计模式 说几种