7月4日面经

------------------------------面经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. 索引 联合列索引

  1. 联合索引是将数据库中多个字段组合在一起作为索引列;
  2. 联合索引关键字匹配的时候满足最左前缀匹配法则(以最左边的为起点任何连续的索引都能匹配上)

联合索引本质:
当创建 (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. 设计模式 说几种

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值