java 常见面试题(精简篇)

目录

Java基础篇

1 抽象类与接口区别

2 == 和 equals 的区别是什么?

3 Stream常用方法

4 final 在 Java 中有什么作用

5 String, StringBuilder,StringBuffer三者的区别

6 反射常见API

7 Java中finally和return执行顺序

8 基本排序

9 JDK 1.8新特性

JVM篇

1 JVM内存结构

2 JVM如何确定对象要被回收

3 垃圾回收算法

4 JVM参数

5 类加载过程

6 双亲委派

7 对象创建过程

8 Java内存溢出(泄露)

多线程篇

1 创建线程方式

2 线程的状态

3 sleep()和wait()区别

4 线程池种类

5 线程池核心参数

6 volatile关键字的理解

7 Lock锁和synchronized区别

8 synchronized优化 JDK1.6

9 死锁

10 ThreadLocal内存泄露

IO模型篇

IO对比总结

集合篇

1 ArrayList

2 ArrayList和LinkedList区别

3 数组和链表的区别

4 HashMap工作原理

5 为什么要引入红黑树

6 为什么不一上来就用红黑树

7 红黑树何时退化为链表

8 HashMap的长度为什么是2的幂次方

9 HashMap和HashTable区别

10 CurrentHashMap底层实现

WEB篇

1 cookie和session的区别与联系

2 Get方法和Post方法区别

3 servlet的生命周期及常用方法

4 转发和重定向的区别

5 TCP和UDP的区别,HTTP协议

6 三次握手和四次挥手

数据库篇

1 事务的特性和隔离级别

2 谈谈你对MySQL索引的理解

3 MySQL为什么采用B+树而不是B树?

4 聚簇索引和非聚簇索引

5 MySQL日志

6 sql优化

7 索引失效

8 三范式

9 乐观锁和悲观锁

10 主键使用自增ID还是UUID?

11 Mysql的体系结构

12 两表关联

13 mysql常用函数

Spring篇

1 spring模块

2 对spring的理解

3 bean的生命周期

4 谈谈对IOC的理解

3 IOC底层实现

5 循环依赖--三级缓存解决

6 BeanFactory和FactoryBean区别

7 spring中用到的设计模式

8 AOP的底层实现原理

9 AOP应用场景

10Bean的作用域

11 singleton并发线程安全问题

12 bean实例化和依赖注入

13 spring常用注解

14 spring事务传播行为

SpringMVC篇

1 SpringMVC执行流程

2 SpringMVC常用注解

3 SpringMVC异常处理

4 SpringMVC文件上传

5 SpringMVC拦截器

MyBatis篇

1 MyBatis和JDBC区别,优化

2 Mybatis 和 Mybatis Plus 的区别和优化

SpringBoot篇

1 SpringBoot自动装配原理(@AutoWired)

2 SpringBoot自动配置原理....(启动过程)

3 SpringBoot常用注解

3 SpringBoot读取文件配置方式

4 SpringBoot事务

5 SpringBoot Async异步调用

6 Spring Boot 全局异常处理

7 SpringBoot 实现 session 共享

SpringCloud篇

1 常用注册中心,基本原理

2 对SpringCloud理解

3 Eureka工作流程

4 网关GateWay的作用

Redis篇

1 redis存储类型

2 Redis持久化

3 Redis主从集群 数据同步

4 Redis哨兵

5 Redis分片集群

6 缓存穿透、击穿、雪崩

7 redis 过期键的删除策略

8 Redis 的回收(淘汰)策略

9 系统的高并发问题怎么解决

10 MySQL和Redis数据一致性

11 Redis性能优化

RabbitMQ篇

1 RabbitMQ组件

2 中间件,交换机,优点

3 谈谈对MQ的理解

4 保证幂等性(不被重复消费)

5 消息堆积问题

6 消息不丢失

7 消息的顺序性

8 MQ如何保证分布式事务的最终一致性

Elasticsearch篇

1 elasticsearch 了解多少,说说你们公司 es 的集群架构,索引数据大小,分片有多少,以及一些调优手段

2 elasticsearch 倒排索引

Linxu篇

1 常用命令

2 linux查看日志命令


面试前言:

        《葵花宝典》奉上!-!,最近浏览了以下网上的面试题,很多都是长篇大论,甚至上百上千到题,看到这些面试题,让人直打退堂鼓,我结合了市面上常见的面试题,总结了一下内容,可以说再面试过程中百分之70的问题,都在这了,剩下30问道就是不会,谁也不能问啥会啥吧,大家一起卷起来吧!!

个人介绍:省略 ...

项目介绍:省略 ...

Java基础篇

1 抽象类与接口区别

抽象类可以有构造方法,接口中不能有构造方法、
抽象类中可以有普通成员变量,接口中没有普通成员变量、
一个类可以实现多个接口,但只能继承一个抽象类。
抽象类中的抽象方法的访问类型可以是public,protected,但接口中的抽象方法只能是public类型的,并且默认即为public abstract类型
​
普通类和抽象类的区别总结
1、抽象类的存在时为了被继承,不能实例化,而普通类存在是为了实例化一个对象
2、抽象类的子类必须重写抽象类中的抽象方法,而普通类可以选择重写父类的方法,也可以直接调用父类的方法
3、抽象类必须用abstract来修饰,普通类则不用
4、普通类和抽象类都可以含有普通成员属性和普通方法
5、普通类和抽象类都可以继承别的类或者被别的类继承
6、普通类和抽象类的属性和方法都可以通过子类对象来调用

2 == 和 equals 的区别是什么?

== 对于基本类型来说是值比较,对于引用类型来说是比较的是引用;
equals 默认情况下是引用比较,只是很多类重新了 equals 方法,比如 String、Integer 等把它变成了值比较,所以一般情况下 equals 比较的是值是否相等。
​
当两个对象equals相等,hashcode值一定相等,反之equals不相等,hashcode值有可能会相等,所以重写equals.

3 Stream常用方法

map: 用作类型转换 如把集合里面的字符串转为大写,或者一个对象的集合取几个字段转为新的对象集合
filter: 过滤 符合条件的集合元素保存下来,不符合条件的去掉
flatMap:合并集合,比如List<Album> Album里面有一LIst<Track> 对象,这个时候就能不通过循环的方式把 List<Album> 里的每一个元素的 trasks 对象组装成一个新的集合
reduce: reduce可以做累加运算, .reduce(0, (a,b)-> a+b);
count: count和size一样返回的是元素的个数
max,min: 求最大值和最小值,这两个方法需要传入一个comparator比较器,Comparator比较器有一个comparing() 方法 
anyMatch表示,判断的条件里,任意一个元素成功,返回true
allMatch表示,判断条件里的元素,所有的都是,返回true

4 final 在 Java 中有什么作用

final 修饰的类叫最终类,该类不能被继承。
final 修饰的方法不能被重写。
final 修饰的变量叫常量,常量必须初始化,初始化之后值就不能被修改。

5 String, StringBuilder,StringBuffer三者的区别

String是一个不可改变的字符序列.
StringBuilder是一个可以改变的字符序列.
常见的字符拼接,该选择谁   推荐使用StringBuilder,因为拼接的效率高
​
StringBuffer是jdk1.0出现的,线程安全(同步):效率低.  ------主要因为StringBuffer很多方法都是synchronized 修饰的
StringBuilder是jdk1.5出现的,线程不安全(不同步):效率高.

6 反射常见API

 获取类的实例
   类名.Class
   类对象.getClass()
   class.forName(全类名)   --静态的
 getDeclaredField() getFields()  --获取属性
 getMethod()  ---获取方法
 newInstance()  ---创建实例对象(依赖无参构造创建)
 getClassLoader()  --获取类加载器
 getName()  ---返回该类对象的全限定名称

7 Java中finally和return执行顺序

finally语句在return语句执行之后 return返回之前执行的
finally块中的return语句会覆盖try块中的return返回
如果finally语句中没有return语句覆盖返回值,那么原来的返回值可能因为finally里的修改而改变也可能不变
try块里的return语句在异常的情况下不会被执行,这样具体返回哪个看情况
当发生异常后,catch中的return执行情况与未发生异常时try中return的执行情况完全一样

8 基本排序

冒泡排序
public void bubbleSort(int[] arr) { //从小到大
    int temp = 0;
    for(int i = 0; i < arr.length -1; i++){ //控制趟数,到倒数第二个为止
        for(int j = arr.length-1; j>i; j--){ //从最后一个值开始冒泡,将后面的小值与前面的大值进行交换,并且保证循环到前面已经排序完的索引为止
            if(arr[j-1] > arr[j]){
                temp = arr[j];
                arr[j] = arr[j-1];
                arr[j-1] = temp;
            }
        }
    }
}
选择排序:
public void selectionSort(int[] arr){
    int temp = 0;
    int k = 0; //存储最小值的索引
    for(int i = 0; i<arr.lengrh - 1; i++){ //控制趟数,到倒数第二个为止
        k = i;
        for(int j = i; j<arr.length;j++){  //将第一个数默认为最小值,将其索引赋值给k,从k索引开始,将后面每个数与k索引对应的值比较,如果值小了,就将其索引赋值给k
            if(arr[j] < arr[k]){
                k = j;
            }
        }
        //遍历完后,k就指向了最小的值,将其与i对应的值交换(也可 以先做个判断,判断k的索引是否有变化,无变化可以不交换)
        temp = arr[k];
        arr[k] = arr[i];
        arr[i] = temp;
    }
    
}

9 JDK 1.8新特性

1.default关键字
    jdk1.8引⼊了新的关键字default,通过使⽤default修饰⽅法,可以让我们在接⼝⾥⾯定义具体的⽅法实现
2.Lambda表达式
    函数式编程
3.Date Api更新
    LocalDate/LocalTime/LocalDateTime
    TemporalAdjusters
    DateTimeFormatter  前⽇期格式化
4.流
5.Objects⽅法新特性
    isNull() 判断传⼊的obj是否为null
    nonNull() 判断传⼊的obj是否不为null
    requireNonNull()    如果传⼊的obj为null抛出NullPointerException并且使⽤参数messageSupplier指定错误信息,否者返回obj 

JVM篇

1 JVM内存结构

JVM主要有三个核心组成部分,分别是类加载的一个系统,运行时数据区和执行引擎
1. 类加载系统,它的主要功能是去查找和验证.class文件并且去完成内存空间的分配和对象的赋值
2. 运行时数据区 包括 方法区,堆, 本地方法栈,虚拟机栈和程序计数器
    方法区 用来存储 静态变量+常量+类信息  JDK1.8 使用元空间 MetaSpace 替代方法区,元空间并不在 JVM中,而是使用本地内存。
    堆 主要用来存储Java对象的实例
            而堆又分为新生代和老年代,新生代又有伊甸园区,survivor0区和survivor1区  默认情况下按照8:1:1的比例进行分配。
                    //survivor区域是将在Eden中未被清理的对象存放到该区域中,采用的是标记复制算法
    本地方法栈  负责加载并运行native方法
    虚拟机栈 为java执行Java方法服务的
    程序计数器 保存每个线程执行方法的地址
    
    方法区和堆是线程共享的,而栈和程序计数器是线程私有的
3. 执行引擎包含即时编译器和垃圾回收器
    即时编译器负责将字节码翻译成CPU指令,可以通过JVM参数来设置它的执行方式
    垃圾回收器 负责对运行时数据区的数据进行回收和管理,本质是对各种垃圾回收算法的实现

2 JVM如何确定对象要被回收

JVM在进行垃圾回收时会通过一个 可达性分析 的算法来确定对象是否可达,如果可达就不能回收
​
可达性分析的主要逻辑: JVM会从GC ROOT开始,顺着对象的引用去查找对象,能被查找到的对象就是可达的对象

3 垃圾回收算法

1 标记清除算法
    核心逻辑: 是会先标记出存活的对象,然后没有标记的对象就是需要回收的垃圾对象
    缺点: 会产生很多内存碎片,导致GC频率增加
2 标记复制算法(年轻代)
    核心逻辑: 是会把内存分为两个相等的部分,每次只使用其中一部分,在使用的这部分内存满了之后,
    会先标记处存活的对象,把存活的对象拷贝到另一半闲置的内存中,剩下的对象就会被回收
    缺点: 只有50%的空间使用,存活对象较多时,复制会非常耗时
         适用于存活对象少,垃圾对象多的场景
3 标记整理法(老年代)
    核心逻辑: 会先标记出存活的对象,把所有存活的对象整理到内存空间的一端,内有被标记的就会被覆盖或者释放

4 JVM参数

Xms: 主要作用是用来分配内存的初始化大小
Xmx: 用来配置最大堆内存
    //一般 Xmx和Xms设置为相同大小,可以避免内存的自动扩展
Xmn: 设置新生代的内存大小
Xss: 每个线程的虚拟机栈大小

5 类加载过程

加载: 获取类的二进制字节流,在堆中生成class字节码对象
验证: 确保被加载类的正确性
准备: 为类的静态变量分配内存并初始化为默认值
解析: 虚拟机将常量池中的符号引用替换成直接引用
初始化: 为类的静态变量赋予正确的初始值
​
类实例化方法调用顺序
父类静态变量--父类静态代码块--子类静态变量--子类静态代码块
父类成员变量--父类构造函数--子类成员变量--子类构造函数

6 双亲委派

指优先委派上级类加载器进行加载,
如果上级类加载器能找到这个类,就由上级加载,加载后的类对下级类加载器可见,
如果找不到,则下级类加载器才有资格加载
​
目的: 
1 让上级类加载器中的类对下级共享,反之不行;即让你的类能依赖到jdk的核心类
2 让类的加载有优先次序,保证核心类优先加载

7 对象创建过程

1. JVM会先去方法区下检查类是否已经被加载,有就可以创建对象了,没有则把该类加载到方法区
2. 在堆内存中为该对象分配内存空间
3. 加载对象中所有的非静态成员变量到该空间下
4. 所有的非静态成员变量加载完成之后,对所有的非静态成员进行默认初始化
5. 所有的非静态成员默认初始化完成之后,调用相应的构造方法到栈中
6. 在栈中执行构造函数时,先执行隐式,再执行构造方法中书写的代码
7. 执行顺序:静态代码库,构造代码块,构造方法
8. 当整个构造方法全部执行完,此对象创建完成,并把堆内存中分配的空间地址赋给对象名(此时对象名就指向了该空间)

8 Java内存溢出(泄露)

1. Java堆溢出
    Java堆用于存储对象实例,只要不断地创建对象,当对象数量到达最大堆的容量限制后就会产生内存溢出异常。最常见的内存溢出就是存在大    的容器,而没法回收,比如:Map,List等。
​
    内存溢出:内存空间不足导致,新对象无法分配到足够的内存;
    内存泄漏:应该释放的对象没有被释放,多见于自己使用容器保存元素的情况下。
​
2. 虚拟机栈和本地方法栈溢出
    如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常,请见情况:递归调用,无法退出。
    如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常,线程创建过多导致,每个线程需要申请栈空间。

多线程篇

1 创建线程方式

继承Thread类  //线程启动 start()
实现Runnable接口 //区别
实现Callable接口    //Callabe可以有返回值
线程池方式

2 线程的状态

新建
就绪      //可以分到CPU时间
运行      //已经分到CPU时间
阻塞      //分不到CPU时间
终结

3 sleep()和wait()区别

1. sleep()是Thread类的静态方法,wait()是Object中的成员方法
2. sleep()可以在任意地方使用,而wait()必须获取锁对象才能使用
3. sleep()会设置一个时间,时间到了会自动释放,而wait()必须通过notify()或notifyAll()进行释放
​
   * sleep的线程不占用CPU,wait会占用CPU

4 线程池种类

newCachedThreadPool  可缓存线程池
newFixedThreadPool   固定线程数的线程池
newSingleThreadExecutor   单线程的线程池
newScheduledThreadPool   支持定时及周期性任务执行
    //都是通过jdk工具类Executors来构建的
    1.通过 ThreadPoolExecutor 手动创建线程池 (有1种实现方法)。
    2.使用 Executors 自动创建线程池 (共有6种实现方法)。

5 线程池核心参数

核心线程数
最大线程数
存活时间  //针对救急线程
时间单位
任务队列
线程工厂
拒绝策略  //丢弃并抛出异常  丢弃不抛异常  丢弃最早加入任务队列的  交给调用者线程自己处理
    
线程池创建后,任务提交进来,首先会创建核心线程数,核心线程数满了以后,会往任务队列里塞,此时有两种情况
    1 如果是无界队列,最终有可能会导致OOM
    2 如果是有界队列,队列满了以后,会继续创建线程,知道达到最大线程数,然后开始触发拒绝策略
最终如果流量降下来,当临时线程到达存活时间,会进行销毁
核心线程数默认是不会销毁的,但是它有一个超时设置,手动设置为true或执行run方法抛出异常,核心线程都会被销毁

6 volatile关键字的理解

volatile能保证数据的一致性和可见性,但是不能保证原子性
因为我们都是多核CPU,多核CPU说白了就是线程对共享变量进行修改,其他线程可以马上感知到,
在实现MESI协议(CPU缓存一致性)的基础上,它底层是用内存屏障,就是一个store和load指令,在防止指令的重排序,来保证数据一致性,
但是不能保证原子性,想要保证原子性,我们通常的做法就是再去加把锁
​
//volatile和synchronized区别
volatile只能作用于变量,synchronized可以使用在变量和方法上
volatile能保证可见性和一致性,但是不能保证原子性,synchronized可以
volatile不会造成线程阻塞,synchronized会造成线程阻塞

7 Lock锁和synchronized区别

1. synchronized是java内置的关键字,而Lock是J.U.C包下的接口,实现类ReentrantLock(悲观锁),ReentrantReadWriteLock(乐观锁)
2. synchronized可以写在需要同步的对象、方法或特定代码块中
    Lock则是用lock()和unLock()方法进行添加锁和释放锁,可以保证lock()和unLock()之间的代码是线程安全的
3. synchronized是自动释放锁的,而Lock锁需要手动调用unLock()释放锁
    而为了避免死锁,一般将unLock()方法写在finally块中,Lock锁还提供了tryLock()方法,通过返回true或false判断锁是否被占用
4. synchronized使用的是悲观锁机制,在JDK1.6对synchronized进行了优化

8 synchronized优化 JDK1.6

synchronized默认采用的是偏向锁,在程序运行的过程中,有一个线程去获得锁,在java对象中会记录下这个线程ID,在下次需要获取锁的时候,只需要去比较这个线程ID就行
如果出现第二个线程去请求获取锁的时候,这里有两种情况
1. 在没有发生并发争抢锁的情况下,这个synchronized就会自动升级为轻量级锁,此时第二个线程就会尝试通过自旋锁的方式来获取锁,因为很快就能拿到锁,所以第二个线程也不会阻塞.
2. 在发生并发争抢锁的情况下,synchronized会自动升级为重量级锁,此时只有一个线程能获得锁,其他线程就会阻塞等待锁的释放,才能拿到锁

9 死锁

概念: 多个并发进程因争夺系统资源而产生相互等待的现象。
​
//死锁产生的4个必要条件
互斥
占有且等待
不可抢占
循环等待
​
//防止死锁
尽量使用 tryLock(long timeout, TimeUnit unit)的方法(ReentrantLock、ReentrantReadWriteLock),设置超时时间,超时可以退出防止死锁。
尽量使用 Java.util.concurrent 并发类代替自己手写锁。
尽量降低锁的使用粒度,尽量不要几个功能用同一把锁。
尽量减少同步的代码块。

10 ThreadLocal内存泄露

理解: ThreadLocal可以实现 线程间互相隔离,线程内资源共享
​
采用"空间换时间"的策略,本身是有两层Map嵌套在里面,每个线程内都有一个ThreadLocalMap集合,key是ThreadLocal,value是资源对象
​
​
内存泄露原因:
如果ThreadLocal没有外部强引用,在GC时,ThreadLocal就会被回收,而ThreadLocal作为key,被回收就会导致一个key为null的Entry
外部则无法来访问这个entry,无法回收,造成内存泄漏
​
解决方法:
1. 每次用完ThreadLocal,调用remove()方法清除数据
2. 将ThreadLocal变量定义成static,将它变为外部强引用

IO模型篇

IO对比总结

IO 的方式通常分为几种:同步阻塞的 BIO、同步非阻塞的 NIO、异步非阻塞的 AIO。 
BIO 方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4 以前的唯一选择,但程序直观简单易理解。 
NIO 方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4 开始支持。 
AIO 方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用 OS 参与并发操作,编程比较复杂,JDK7 开始支持。 
​
举个例子: 
同步阻塞:你到饭馆点餐,然后在那等着,啥都干不了,饭馆没做好,你就必须等着! 
同步非阻塞:你在饭馆点完餐,就去玩儿了。不过玩一会儿,就回饭馆问一声:好了没 啊! 
异步非阻塞:饭馆打电话说,我们知道您的位置,一会给你送过来,安心玩儿就可以了, 类似于现在的外卖。

集合篇

1 ArrayList

ArrayListi属于线性表结构,是JDK封装的动态数组
当且仅当第一个元素添加进来时,会将数组长度设置为10
(若使用addAll()方法添加,则初始化大小为10 和 添加集合长度的 较大值)
后面再添加元素,每次都会检查数组容量大小,如果超出容量,底层会调用C++的arraycopy()方法,将数组长度扩容为原来的1.5倍


//删除元素?
比如说删除一个中间元素,会采用一个覆盖的形式,将后面的元素依次往前挪,最后一个位置置为null,等待GC回收

2 ArrayList和LinkedList区别

1. ArrayList基于动态数组,LinkedList基于链表
2. 对于随机访问,ArrayList的时间复杂度是O(1),效率要比LinkedList高
3. 对于新增和删除,LinkedList要优于ArrayList,因为ArrayList会涉及到数据的移动

3 数组和链表的区别

1. 数组在内存中连续,且长度固定;链表在内存中不要求连续,每个元素都指向了下一个元素的地址
2. 数组从栈上分配内存,链表从堆上分配内存
3. 数组插入数据,如果内存空间不连续,需要数据迁移,还可能会出现索引越界;链表则不需要移动数据,只需要更改地址指向,所以速度快
4. 数组在内存中顺序存储,随机访问效率高;而链表因为不连续,随机访问慢

4 HashMap工作原理

HashMap在jdk1.7采用数组加链表,1.8以后引入了红黑树. 在数组的每一个元素都是链表结构,而链表中每个节点又是一个entry对象,用来存储k-v键值对;hashmap中有两个比较重要的方法,get()和put()

put(): 在存储k-v键值对的时候,首先会调用一个hash方法,计算出key的hash值,再和数组的长度进行按位与计算,得到对应的桶下标,然后遍历该桶下标下链表上的每个节点的key,和put的key进行equals比较;如果相等,就更新这个key对应的value,如果不相等,就将这个k-v键值对put到链表上
	//扩容
	1.在put()过程中,当数组元素超过了数组长度乘负载因子(默认为0.75)的时候,会将数组扩容为两倍
	//树化
	2.在插入链表时,如果链表长度超过了默认树化阈值8 且 数组长度大于等于64的时候,链表节点的数据结构就会变成红黑树
get(): get()方法再调用时,和put()方法类似,会计算出key的hash值和数组长度进行按位与计算得到桶下标,然后遍历该桶下标下链表上所有节点的key,和查询的key进行equals比较,如果相等就返回value给用户.

总结: HashMap 最核心的原理就是利用hash值来计算出桶下标,再进行equals比较
		equals比较主要是为了解决hash冲突的问题

5 为什么要引入红黑树

红黑树是二叉查找树的一种,它的查找算法就相当于二分的查找,
红黑树的时间复杂度O(log n)在数据较多的时候会比链表的时间复杂度O(n)要好很多

6 为什么不一上来就用红黑树

我认为是时间和空间的折中考虑,在hash冲突比较小的时候,即使转化为红黑树,在时间复杂度上所产生的效果也不是特别大
而且在put的时候,效率可能会降低,毕竟每次put都要进行非常复杂的红黑树的旋转算法操作,就显得有点得不偿失了

7 红黑树何时退化为链表

1. 扩容时,如果进行了拆分树,且树的长度小于等于6
2. remove树节点时,如果根节点,左右孩子,左孙子有一个为null

8 HashMap的长度为什么是2的幂次方

因为在hashmap长度为2的幂次方时,才会有hash%length==hash&(length-1)

9 HashMap和HashTable区别

HashMap允许存储null键和null值,线程不安全,效率高
HashTable不允许存储null键和null值,线程安全,效率低

10 CurrentHashMap底层实现

1. 在JDK1.7,它的底层是使用数组加链表来实现的,使用了分段锁来保证线程安全,它是将数组分成16段,
	给每个segment来配一把锁,在读每个segment的时候都要先获取锁,所以它最多能支持16个线程去并发操作
2. 到了JDK1.8以后,它跟HashMap一样,也引入了红黑树,同时在并发处理方面,不再使用分段锁,
	而是采用CAS和synchronized关键字的方式来实现一种更加细粒度的锁,
	在写入键值对时,可以锁住链表的头节点,就不会影响其他哈希桶的写入,从而提高并发处理能力

WEB篇

1 cookie和session的区别与联系

一、cookie数据存放在客户的浏览器上,session数据存放在服务器上.
二、很多浏览器限制站点最多保存20个cookie,单个cookie保存的数据不能超过4k.
三、cookie不是很安全,考虑安全应当使用session.
四、可以考虑将登录信息等重要信息存放为session,其它信息如果需要保留,可以放在cookie中.
五、session会在一定时间内保存在服务器上.
六、session会在浏览器关闭或者一段时间内销毁,也可以通过setMaxInactiveInterval(int)方法进行设置,或是通过invalidate()方法强制结束当前会话.cookie可以通过setMaxAge(int)方法设置缓存在客户端的时间.
七、一般情况下,session生成的sessionid都是保存在cookie中.

总结:
cookie:在客户端保存数据,不安全.只能保存字符串,且是少量数据.
session:在服务器端保存数据,安全.可以保存对象数据,数据无限制.
Session是基于cookie的,session的id是存在cookie中的

2 Get方法和Post方法区别

1. post请求放在body中,没有长度限制,且不会被缓存更加安全
	get请求参数放在url中,且有长度限制,安全性较差
2. post请求用于修改、写入数据,get一般用于获取数据
3. post能发送更多的数据类型,get只能发送ASCLL码字符

3 servlet的生命周期及常用方法

init()方法:在servlet的生命周期中,仅执行一次init()方法.
service()方法:它是servlet的核心,每当客户请求一个httpservlet对象,该对象的service()方法就要调用,而且传递给这个方法一个”请求”对象和一个”响应”对象作为参数.
destory()方法:仅执行一次,在服务器端停止且卸载servlet时执行该方法.

解决servlet线程安全
一、继承SingleThreadModel,消耗服务器内存,降低性能.并且过时,不推荐.
二、尽量避免使用全局变量,推荐.
三、通过使用ThreadLocal.

4 转发和重定向的区别

一、重定向是浏览器发送请求并收到响应以后再次向一个新地址发请求;转发是服务器收到请求后为了完成响应转到另一个资源.
二、重定向中有两次请求对象,不共享数据;转发只产生一次请求对象且在组件间共享数据.
三、重定向后地址栏地址改变,而转发不会.
四、重定向的新地址可以是任意地址;转发必须是同一个应用内的某个资源.

获取servlet的转发和响应重定向的方式
转发的方法:
通过HttpServletRequest的getRequestDispatcher()方法获得
通过ServletContext的getRequestDispatcher()方法获得
重定向的方法:
HttpServletResponse的sendRedirect()方法.

5 TCP和UDP的区别,HTTP协议

TCP协议提供安全可靠的网络传输服务,它是一种面向连接的服务.类似于打电话,必须先拨号.双方建立一个传递信息的通道传输.
UDP协议是一种数据报协议,它传输的数据是分组报文,它是无连接的,不需要和目标通信方建立连接,类似于写信,所以它的传输不保证安全可靠.但适合大数据量的传输.
HTTP协议是超文本传输协议,是一种相对于TCP来说更细致的协议,TCP以及UDP协议规范的是网络设备之间的通信规范,HTTP实在TCP协议的基础上针对用户的协议,用户服务具体体现在应用程序之间的交互,比如javaweb中客户端服务端体系就要用http协议来规范通信.

TCP和UDP在开发中很少见到,但是网络底层都有他们的影子,正常的会话级别的服务:如客户端服务器体系底层就说基于TCP协议.而邮件发送,短信发送等底层使用的是UDP协议.
HTTP协议,客户端/服务器体系的程序都使用HTTP协议来规范通信.

6 三次握手和四次挥手

1)第一次握手:建立连接。客户端发送连接请求报文段,将 SYN 位置为 1,Sequence Number 为 x;然后,客户端进入 SYN_SEND 状态,等待服务器的确认;
2)第二次握手:服务器收到 SYN 报文段。服务器收到客户端的 SYN 报文段,需要对这个 SYN 报文段进行确认,设置 Acknowledgment Number 为x+1(Sequence Number+1);同时,自己自己还要发送 SYN 请求信息,将 SYN 位置为 1,Sequence Number 为y;服务器端将上述所有信息放到一个报文段(即 SYN+ACK 报文段)中,一并发送给客户端,此时服务器进入 SYN_RECV 状态;
3)第三次握手:客户端收到服务器的 SYN+ACK 报文段。然后将 Acknowledgment Number设置为 y+1,向服务器发送 ACK 报文段,这个报文段发送完毕以后,客户端和服务器端都进入 ESTABLISHED 状态,完成 TCP 三次握手。

		1 客户端发送一个信号用来关闭客户端到服务端的数据传输
		2 服务端接受这个信号,向客户端回发一个信号表示收到
		3 服务端关闭与客户端的连接,然后再发一个信号给客户端
		4 客户端接受信号之后确认, 再向服务端发信息验证是否已经断开

数据库篇

1 事务的特性和隔离级别

事务的特性:
  原子性(Atomicity)指事务不可分割,要么都成功,要么都失败
  一致性(Consistency) 事务必须使数据库从一个一致性状态变换到另外一个一致性状态
  隔离性(Isolation)事务的隔离性是指一个事务的执行不能被其他事务干扰
  持久性(Durability)持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的
  

隔离级别:
未提交读read uncommitted
		会发生 脏读、不可重复读、虚读
已提交读read committed			//Oracle   	SQL Server(系统事务)
		解决脏读,但是不可重复读和虚读有可能发生
重复读repeatable read		//Mysql
		解决脏读和不可重复读,但是虚读有可能发生.
串行化serializable (只有读读可以并发,读写和写写都会阻塞)
		避免脏读,不可重复读,虚读的发生
		
脏读:读到了别的事务回滚前的脏数据
不可重复读:在一个事务的两次查询中数据不一致,可能是两次查询过程中,数据被更新了
幻读:事务两次查询得到的数据个数不一致,中途被其他事务插入了几行数据

rr读到的是可重复的,为什么update能更新到最新?  update会加锁,会首先做一个当前读

2 谈谈你对MySQL索引的理解

    MySQL索引的底层数据结构是通过B+树或hash表来实现的,对于不同类型的数据结构是和存储引擎相关的。如果使用的是InnoDB或MyISAM存储引擎,对应的底层数据结构是B+树,如果使用的是Memory存储引擎,对应的底层数据结构是hash表。
    一般,我们是根据某个key去找到value,key就是我们索引的某个列的值,value就是这一行的数据。当我们选择用k-v格式存储的时候,可以选择用hash表、二叉树、AVL树、红黑树。但是这最终都会导致树变高,树变高会影响磁盘I/O次数增加,从而导致访问的效率变低。所以选择使用B+树的结构,可以在某一个数据节点里面尽可能多的去存储数据,让树变低,从而减少磁盘I/O的次数,提高访问效率。
    同时,在MySQL中有普通索引、唯一索引、主键索引、组合索引、全文索引等各种分类,我们用到的最多的是主键索引和组合索引,但在使用时,会存在回表、覆盖索引、最左匹配、索引下推等细节问题。
    我们还可以通过索引来对SQL语句进行优化,提高我们对数据的访问效率
    
    
回表: 从某一个索引的叶子节点中获取聚簇索引的id值,根据id再去聚簇索引中获取全量数据
索引覆盖: 从索引的叶子节点中能获取到全量查询列的过程
最左原则: 最左优先,以最左边的为起点任何连续的索引都能匹配上。同时遇到范围查询(>、<、between、like)就会停止匹配。
索引下推: 索引下推是数据库检索数据过程中为减少回表次数而做的优化

3 MySQL为什么采用B+树而不是B树?

1.从磁盘I/O效率来看:
	B+树的非叶子节点不存储数据,所以树的每一层能存储更多的索引数据,间接地减少了磁盘I/O的次数,提高查询效率
2.从范围查询效率来看:
	MySQL对B+数的优化,使B+树存储在叶子节点的数据使用双向链表来关联,因此B+树查询只需要查询两个叶子节点进行遍历就行,而B树要查询	所有节点进行遍历,因此B+树范围查询效率高
3.从全表扫描来看:
	因为B+树叶子节点存储了所有数据,所以只需要扫描所有的叶子节点进行遍历,而B树要遍历整个树

4 聚簇索引和非聚簇索引

聚簇索引的叶子节点存储的是行数据,索引和数据紧密联系,可以通过聚簇索引直接查询到数据
非聚簇索引的叶子节点存储的是主键信息,因此需要回表查询,所以聚簇索引的查询效率高

在InnoDB存储引擎中,既存在聚簇索引,又存在非聚簇索引
在MyISAM存储引擎中,只有非聚簇索引,因为它的叶子节点存储的是地址

5 MySQL日志

1.binlog
	binlog 用于记录数据库执行的写入性操作[不包括查询信息],以二进制的形式保存在磁盘中。
	//使用场景
	主从复制 :在 Master 端开启 binlog ,然后将 binlog发送到各个 Slave 端, Slave 端重放 binlog 从而达到主从数据一致。
	数据恢复 :通过使用 mysqlbinlog 工具来恢复数据。
2.redo log
    实现ACID中的持久性,保证提交数据不丢失
    事务提交时,首先将redo log记录变更,事务就会视为成功
3.undo log
    回滚数据
    多版本并发控制: 快照读(一致性)可重复读
    每条数据修改(insert、update或delete)操作都伴随一条undo log的生成,并且回滚日志必须先于数据持久化到磁盘上

6 sql优化

1. select子句中避免使用‘*’
2. 避免在索引列上使用计算,not,in和<>等操作
3. 当只需要一行数据的时候使用limit 1
4. 保证表单数据不超过200w,适时分割表
5. 针对查询较慢的语句,可以使用explain来分析该语句具体的执行情况
6. 避免查询时判断null,否则可能会导致全表扫描,无法使用索引;
7. 避免like查询,否则可能导致全表扫描,可以考虑使用全文索引
8. 能用union all的时候就不用union,union过滤重复数据要耗费更多的CPU资源

7 索引失效

1. 如果条件中有or,即使其中有部分条件带索引也不会使用(这也是为什么尽量少用or的原因),例子中user_id无索引。注意:要想使用or,又	想让索引生效,只能将or条件中的每个列都加上索引
2. 对于复合索引,如果不使用前列,后续列也将无法使用,类电话簿。
3. like查询是以%开头
4. 存在索引列的数据类型隐形转换,则用不上索引,比如列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不使用索引
5. where 子句里对索引列上有数学运算,用不上索引
6. where 子句里对有索引列使用函数,用不上索引
7. 如果mysql估计使用全表扫描要比使用索引快,则不使用索引

8 三范式

第一范式:强调的是列的原子性,即数据库表的每一列都是不可分割的原子数据项。
第二范式:要求实体的属性完全依赖于主关键字。所谓完全依赖是指不能存在仅依赖主关键字一部分的属性。
第三范式:任何非主属性不依赖于其它非主属性。

9 乐观锁和悲观锁

乐观锁:无需加锁,每次只有一个线程能成功修改共享变量,其他失败的不停止,不断重试直至成功
悲观锁:线程只有占了锁,才能去操作变量,每次只有一个线程占锁成功,获取锁失败的线程,都得停下来等待

10 主键使用自增ID还是UUID?

推荐自增ID,因为在InnoDB存储引擎中,主键是作为聚簇索引存在的,也就是说,主键索引的B+树叶子节点上存储了主键索引及全部的数据(按照顺序),如果主键使用自增ID,那么只需要不断向后排列即可,如果是UUID,想要顺序存储,则需要数据移动,会产生很多内存碎片,造成插入性能下降

11 Mysql的体系结构

//连接层 
最上层是一些客户端和链接服务,包含本地sock 通信和大多数基于客户端/服务端工具实现的类似于 TCP/IP的通 信。主要完成一些类似于连接处理、授权认证、及相关的安全方案。在该层上引入了线程池的概念,为通过认证安全接入的客户端提供线程。同样在该层上可以实现基于SSL的安全链接。服务器也会为安全接入的每个客户端验证 它所具有的操作权限。 
//服务层
第二层架构主要完成大多数的核心服务功能,如SQL接口,并完成缓存的查询,SQL的分析和优化,部分内置函数的执行。所有跨存储引擎的功能也在这一层实现,如 过程、函数等。在该层,服务器会解析查询并创建相应的内部 解析树,并对其完成相应的优化如确定表的查询的顺序,是否利用索引等, 最后生成相应的执行操作。如果是 select语句,服务器还会查询内部的缓存,如果缓存空间足够大,这样在解决大量读操作的环境中能够很好的提升系统的性能。 
//引擎层 
存储引擎层,存储引擎真正的负责了MySQL中数据的存储和提取, 
//存储层 
服务器通过API和存储引擎进行通信。不同的存储引擎具有不同的功能,这样我们可以根据自己的需要,来选取合适的存储引擎。
数据存储层,主要是将数据存储在文件系统之上,并完成与存储引擎的交互。和其他数据库相比,MySQL有点与众不同,它的架构可以在多种不同场景中应用并发挥良好作用。主要体现在存储引擎上,插件式的存储引擎架构,将查询处理和其他的系统任务以及数据的存储提取分离。这种架构可以根据业务的需求和实际需要选择合适的存储引擎。

12 两表关联

1、INNER JOIN (内连接)      -----只返回两个表中连接字段相等的行
2、LEFT OUTER JOIN (左连接) -----返回包括左表中的所有记录和右表中连接字段相等的记录
3、RIGHT OUTER JOIN (右连接)-----返回包括右表中的所有记录和左表中连接字段相等的记录
4、union      ------作用很简单用来合并两条sql的结果集

13 mysql常用函数

CONCAT(s1,s2…sn)字符串 s1,s2 等多个字符串合并为一个字符串
case when  类似于编程语言中的if else判断、switch case语句。该语句执行时先对条件进行判断,然后根据判断结果做出相应的操作
FORMAT(x,n)函数  可以将数字x进行格式化,将x保留到小数点后n位

Spring篇

1 spring模块

spring core:框架的最基础部分,提供 ioc 和依赖注入特性。
spring context:构建于 core 封装包基础上的 context 封装包,提供了一种框架式的对象访问方法。
spring dao:Data Access Object 提供了JDBC的抽象层。
spring aop:提供了面向切面的编程实现,让你可以自定义拦截器、切点等。
spring Web:提供了针对 Web 开发的集成特性,例如文件上传,利用 servlet listeners 进行 ioc 容器初始化和针对 Web 的 ApplicationContext。
spring Web mvc:spring 中的 mvc 封装包提供了 Web 应用的 Model-View-Controller(MVC)的实现。

2 对spring的理解

Spring是使用了分层思想的,针对Java 开发的轻量级开源框架。以IOC和AOP(面向切面)为内核,提供了展现层Spring MVC,持久层Spring JDBC及业务层事务管理等一站式企业级应用技术。并整合了很多的开源第三方框架。

好处:
方便解耦,简化开发。–通过IOC解耦。
AOP的编程的支持。–面向切面编程。
声明式事务的支持。
方便程序测试
集成各种优秀框架

3 bean的生命周期

①实例化bean
	通过反射创建对象,在堆内存中开辟空间,属性是默认值
②设置对象属性
	populateBean方法-----这里会出现循环依赖问题----使用三级缓存解决
③调用aware接口并设置相关依赖
	完成beanName,beanFactory,beanClassLoader对象的属性设置
④调用beanPostProcessor中的前置处理方法
	使用较多的是:ApplicationContextPostProcessor来设置ApplicationContext,Environment,ResourceLoader等对象
⑤调用initMethod方法,完成初始化
⑥调用BeanPostProcessor的后置处理方法
	spring的aop就是在这里实现的
⑦通过getBean方法获取完整的对象
⑧调用destroyMethod方法进行销毁

4 谈谈对IOC的理解

①控制反转: 原来的对象是由使用者进行创建和控制的,有了spring之后,可以把对象交给spring来进行管理
	DI: 依赖注入,通过@AutoWired或者populateBean完成对象的属性注入
②同时IOC还是一个容器,使用Map结构来存储对象,bean的整个生命周期,从创建到使用到销毁都是有容器来管理

容器在创建的时候,有一个最上层的根接口BeanFactory,我们在实际调用过程中,最多用到的是DefaultListableBeanFactory,
在使用时会先创建一个bean工厂,向工厂中设置一些参数,如BeanPostProcessor,Aware接口的子类等等
接着就是加载并解析bean对象,创建一个beanDefinition对象,
通过反射将benaDefinition对象实例化成具体对象,并进行初始化,执行BeanPostProcessor的一些前置后置处理方法
通过getBean获取bean对象使用,最后可以通过destoryMethod方法进行销毁
这些操作都是有IOC容器来进行管理的

3 IOC底层实现

1. 先通过createBeanFactory创建出一个bean工厂,使用较多的是DefaultListableBeanFactory
2. 开始循环创建对象,因为容器中bean默认是单例的,所以优先通过getBean,doGetBean去容器中查找
3. 找不到的话,通过createBean和doCreateBean方法,以反射的方式去创建对象,一般使用无参构造
4. 通过populateBean进行对象的属性填充
5. 进行其他初始化操作

5 循环依赖--三级缓存解决

a依赖b,b依赖c,而c又依赖a,最终形成闭环

这时候我们需要使用三级缓存来解决,核心是提前暴露引用

一级缓存  singletonObjects  放的是完整的bean
二级缓存  earlySingletonObjects  放的是实例化但未初始化的bean,是个半成品
三级缓存  singletonFatories   存储能够建立这个Bean的一个工厂,通过工厂来获取这个Bean,延迟Bean的生成

流程:
首先在一级缓存和二级缓存中找不到a对象,就会去三级缓存用工厂进行创建,并提前暴露引用.放入二级缓存,a对象属性注入,
会去找b,找不到就会去创建b对象,同样提前暴露引用,放入二级缓存,
b对象开始属性注入,在二级缓存中找到a,完成属性注入,放入一级缓存
a对象同样的也会创建成功,放入一级缓存

理论上二级缓存就能解决,但是为了使用工厂来创建代理对象,引入了三级缓存

这三个 map 是如何配合的呢?
一级缓存找不到,找二级缓存,二级缓存找不到就去三级缓存用工厂来创建代理对象,提前暴露引用,放入二级缓存


多实例的bean(创建多个bean,死循环)  构造器注入的bean  不能解决循环依赖问题

6 BeanFactory和FactoryBean区别

同: 都是用来创建bean的
异: BeanFactory是IOC容器的顶级接口,能通过getBean去容器中获取指定的bean的实例
    FactoryBean是一个工厂Bean,它的主要功能是创建bean,我们可以自定义一个bean加载到IOC容器中

7 spring中用到的设计模式

单例模式: spring中bean默认是单例的
原型模式: bean指定作用域 prototype
(它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。根据经验,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。)
工厂模式: BeanFactory
责任链模式: 使用AOP的时候,会生成一个拦截器链
代理模式: AOP动态代理
观察者模式: listener  在Spring中定义了一个ApplicationListener接口,用来监听Application的事件

8 AOP的底层实现原理

AOP:面向切面编程,它是IOC的一个扩展功能,能在不修改原代码的基础上,对方法进行增强

bean在创建的过程中,有一个步骤可以对bean进行扩展实现,AOP本身就具备扩展功能,
所以在BeanPostProcessor的后置处理方法中就是AOP来实现的

首先AOP底层会通过jdk或cglib动态代理来创建代理对象,
在执行方法调用时,会找到一个叫intercept方法,从此方法开始执行
根据之前定义好的通知生成一个拦截器链
在拦截器链中依次获取每一个通知 开始执行

9 AOP应用场景

使用AOP做统一的日志处理
自定义注解想要生效,也需要使用AOP来实现
权限控制....

10Bean的作用域

singleton(单例模式): IOC容器每次返回的是同一个Bean实例.
prototype(原型模式): IOC容器每次返回的是一个新的实例.
request(HTTP请求): 每次HTTP请求都会创建一个新的Bean
session(会话): 同一个session共享一个Bean实例.不同session使用不同的实例.

11 singleton并发线程安全问题

不安全
对于无状态的bean,如DAO类,是安全的
对于有状态的bean,即有数据存储功能的,如VO视图对象,是不安全的

解决方案: 
1. 设置bean的scope属性为prototype多例
2. 使用threadLocal,为每个线程提供一个独立的变量副本,从而隔离多个线程对数据的访问冲突

12 bean实例化和依赖注入

实例化:
一、构造器实例化Bean
二、静态工厂方式实例化Bean
三、实例工厂方式实例化Bean
依赖注入:
一、基于构造函数的注入
二、基于set方法的注入
三、基于自动装配的注入
四、基于注解的依赖注入

13 spring常用注解

@Component:用于标记在一个类上,表示当前类是spring的一个组件,是ioc的一个容器.他有三个衍生注解:@Controller、@Service、@Repository
@Controller:用于标记在一个类上,代表这个类是控制层组件.
@Service:用于标记在一个类上,代表这个类是业务层组件.
@Repository:用于标记在一个类上,代表这个类是数据访问层组件.、
@Bean
@Autowired
@Value
@Transactional:写在类上用于指定当前类中的方法支持事务,写在方法上表示当前的方法支持事务

14 spring事务传播行为

PROPAGATION_REQUIRED: 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
PROPAGATION_SUPPORTS: 支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY: 支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW: 新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED: 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER: 以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。

SpringMVC篇

1 SpringMVC执行流程

1) 用户发送请求至前端控制器DispatcherServlet
2) DispatcherServlet收到请求调用HandlerMapping处理器映射器。
3) 处理器映射器根据请求url找到具体的处理器,生成处理器对象返回给DispatcherServlet。
4) DispatcherServlet通过HandlerAdapter处理器适配器调用处理器
5) HandlerAdapter执行处理器(handler,也叫后端控制器)。
6) Controller执行完成返回ModelAndView
7) HandlerAdapter将handler执行结果ModelAndView返回给DispatcherServlet
8) DispatcherServlet将ModelAndView传给ViewReslover视图解析器
9) ViewReslover解析后返回具体View对象
10) DispatcherServlet对View进行渲染视图
11) DispatcherServlet响应用户

2 SpringMVC常用注解

@RequestMapping:是一个用于处理请求地址映射的注解,可用于类或方法上.用于类上,表示类中所有响应请求的方法都是以该地址作为父路径

@RequestParam:用于将指定的请求参数赋给方法中的形参.
@PathVariable:可以获取URL中的动态参数.
@RequestBody:用于读取request请求的body部分数据.

@ResponseBody:用于将controller方法返回的对象,用流响应给客户端.

@RestController:@Controller+@ResponseBody,用于标记在一个类上

3 SpringMVC异常处理

第1种. 编写异常处理类@ControllerAdvice/@RestControllerAdvice  +  在类中编写异常处理方法@ExceptionHandler(Exception.class)

第2种. 编写异常处理类实现接口 HandlerExceptionResolver

4 SpringMVC文件上传

MultipartFile 接收文件  ->  FileUpload提供api去做的 -> request的请求体

5 SpringMVC拦截器

实现HandlerInterceptor接口

MyBatis篇

1 MyBatis和JDBC区别,优化

1. MyBatis是对JDBC进行封装的一个持久层框架
2. jdbc的sql是在java代码中的,如果sql语句修改,就需要重新编译;而mybatis在xml文件中编写sql语句,不需要重新编译
3. jdbc 数据库连接,使用时就创建,不使用立即释放,对数据库进行频繁连接开启和关闭,造成数据库资源浪费,影响数据库性能.
	mybatis则在XML文件中配置数据链接池,使用连接池管理数据库链接。
4. 对于重复性高的sql,如select后面where条件不同,使用jdbc就要写多遍,而使用mybatis可以SQL片段模块化,将重复的SQL片段独立成一个  	  SQL块,然后在各个SQL语句引用重复的SQL块。

2 Mybatis 和 Mybatis Plus 的区别和优化

Mybatis-Plus是一个Mybatis的增强工具,只是在Mybatis的基础上做了增强却不做改变,MyBatis-Plus支持所有Mybatis原生的特性,所以引入Mybatis-Plus不会对现有的Mybatis构架产生任何影响。

MyBatis:
所有SQL语句全部自己写
手动解析实体关系映射转换为MyBatis内部对象注入容器
不支持Lambda形式调用

Mybatis Plus:
强大的条件构造器,满足各类使用需求
内置的Mapper,通用的Service,少量配置即可实现单表大部分CRUD操作
支持Lambda形式调用
提供了基本的CRUD功能,连SQL语句都不需要编写
自动解析实体关系映射转换为MyBatis内部对象注入容器

SpringBoot篇

1 SpringBoot自动装配原理(@AutoWired)

在启动IOC容器时,容器会自动加载一个叫AutoWiredAnnotationBeanPostProcessor后置处理器,
当容器扫描到@AutoWired注解时,会在IOC容器中查找需要加载的bean对象,并装配给该对象的属性

@AutoWired会先根据类型找,然后会根据名字找,如果没有找到会抛出异常

2 SpringBoot自动配置原理....(启动过程)

在启动类上加上@SpringBootApplication注解,他是一个复合注解,真正去实现自动配置的注解是@EnableAutoConfiguration注解
首先,会引入starter启动依赖组件,必须包含@Configuration配置类,配置类里需要通过@Bean去声明需要配置到IOC容器里的bean对象
然后,这个配置类是放在一个第三方的Jar包里,springBoot会通过SpringFactoriesLoader来读取META-INF下spring.properties文件中所有的自动配置类
最后,通过spring提供的ImportSlector接口来加载这些自动配置类,从而得到它们的BeanDefinition,完成实例化

3 SpringBoot常用注解

@SpringBootApplication:
包含@Configuration、@EnableAutoConfiguration、@ComponentScan通常用在主类上;
@ComponentScan:组件扫描。个人理解相当于,如果扫描到有@Component @Controller @Service等这些注解的类,则把这些类注册为bean*;
@Configuration:指出该类是 Bean 配置的信息源,相当于XML中的,一般加在主类上;
@Bean:相当于XML中的,放在方法的上面,而不是类,意思是产生一个bean,并交给spring管理;
@Import:用来导入其他配置类。
@ImportResource:用来加载xml配置文件。
@ControllerAdvice:包含@Component。可以被扫描到。统一处理异常。
@ExceptionHandler(Exception.class):用在方法上面表示遇到这个异常就执行以下方法。

3 SpringBoot读取文件配置方式

方式一: 通过@Value("${spring.datasource.url}")这样的方式读取
方式二:通过@ConfigurationProperties,建立配置文件和对象的映射关系,然后使用@AutoWired注入
方式三: 我们可以直接注入Environment对象示例并读取properties对象属性值,通过key-value方式读取

4 SpringBoot事务

首先使用注解EnableTransactionManagement开启事物之后,
然后在Service方法上添加注解Transactional便可。

5 SpringBoot Async异步调用

只需要在方法上使用@Async注解
在启动类加入@EnableAsync使异步调用@Async注解生效。

6 Spring Boot 全局异常处理

定义一个全局异常类,使用@RestControllerAdvice和@ExceptionHandler注解
@RestControllerAdvice是@ControllerAdvice的派生注解,能拦截相应的请求,将信息通过responsebody响应给前端
@ExceptionHandler的作用是拦截和处理具体的某个异常

7 SpringBoot 实现 session 共享

Spring Session + Redis 来实现
将所有微服务的session 统一保存在 Redis 上,当各个微服务对 session 有相关的读写操作时,都去操作 Redis 上的 session

Spring Session 基于 Spring 中的代理过滤器实现,使得 session 的同步操作对开发人员而言是透明的,非常简便。

SpringCloud篇

1 常用注册中心,基本原理

Nacos,Eureka,zookpeer,Consoul
注册中心本质上就是存放接口地址,本质思想都差不多,都是使用服务注册,服务发现,服务续约,健康检查之类的

基本原理:
1. 我们在做RPC远程调用的过程中,项目启动完成后,生产者会将他的ip地址和端口号告诉注册中心,我们的注册中心就会来存放接口地址,它底层会有一个map集合,key就是服务名称,value就存放生产者的IP地址和端口号;当然,因为我们是集群部署,有多个地址,所以在我们的注册中心上,key是一个键值,value是一个数组类型,存放多个生产者接口地址
2. 然后我们消费者一方,它会根据服务名称去连接注册中心,获取接口地址,在本地使用负载均衡算法选择一个接口地址,作为我们RPC的远程调用

// 如果消费者从注册中心获取到一个宕机的地址,如何处理?
    一般会先做重试,如果重试多次还是不行,则从本地缓存中选取其他地址

2 对SpringCloud理解

springcloud是一套分布式微服务的技术解决方案,他提供了快速去构建分布式系统的常用的一些组件,比如说配置管理、服务的注册与发现、服务调用的负载均衡、资源隔离、熔断降级等等.

3 Eureka工作流程

1. Eureka Server启动成功,等待Eureka Server注册,每个 Eureka Server 都存在独立完整的服务注册表信息

2. Eureka Client启动时根据配置的Eureka Server地址去注册中心注册服务

3. Eureka Client每30s向Eureka Server发送一次心跳续约请求,证明客户端服务正常

4. 当Eureka Server 90s内没有收到Eureka Client的心跳,注册中心则认为该节点失效,会注销该实例

5. 单位时间内 Eureka Server 统计到有大量的 Eureka Client 没有上送心跳,则认为可能为网络异常,进入自我保护机制,不再剔除没有上送心跳的客户端

6. 当 Eureka Client 心跳请求恢复正常之后,Eureka Server 自动退出自我保护模式

7. Eureka Client 定时(每30秒钟)全量或者增量从注册中心获取服务注册表,并且将获取到的信息缓存到本地

8. 服务调用时,Eureka Client 会先从本地缓存找寻调取的服务。如果获取不到,先从注册中心刷新注册表,再同步到本地缓存

9. Eureka Client 获取到目标服务器信息,发起服务调用

10. Eureka Client 程序关闭时向 Eureka Server 发送取消请求,Eureka Server 将实例从注册表中删除

4 网关GateWay的作用

权限控制:网关作为微服务入口,需要校验用户是是否有请求资格,如果没有则进行拦截。

路由和负载均衡:一切请求都必须先经过gateway,但网关不处理业务,而是根据某种规则,把请求转发到某个微服务,这个过程叫做路由。当然路由的目标服务有多个时,还需要做负载均衡。

限流:当请求流量过高时,在网关中按照下流的微服务能够接受的速度来放行请求,避免服务压力过大。

Redis篇

1 redis存储类型

存储类型--------数据结构
string        字符串
list          双向链表,压缩列表
hash          哈希表,压缩列表
set           哈希表,整数数组
SortedSet     压缩列表,跳表-------SortedSet中每个元素会关联一个double类型的分数,redis通过分数来排序

2 Redis持久化

RDB : 将Redis中全部数据生成快照,保存在磁盘上
	原理: fork主进程得到一个子进程,共享内存空间,子进程读取内存数据并写入新的RDB文件,用新RDB文件替换旧的RDB文件。
	缺点: RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发生故障,会发生数据丢失。
	
AOF : 将每次写的命令追加写入日志,需要恢复数据时重新执行命令
	优点: 1、数据安全,aof 持久化可以配置 appendfsync 属性,有 always,每进行一次命令操作就记录到 aof 文件中一次。
         2、通过 append 模式写文件,即使中途服务器宕机,可以通过 redis-check-aof工具解决数据一致性问题。
		 3、AOF 机制的 rewrite 模式。文件过大时会对命令进行合并重写。
	缺点: AOF 文件比 RDB 文件大,且恢复速度慢。

//混合持久化
结合了 RDB 与 AOF 的优点,恢复速度快,增量用 AOF 表示,数据更完整(取决于同步策略)、也无需 AOF 重写

3 Redis主从集群 数据同步

1. 全量同步:master将完整内存数据生成RDB,发送RDB到slave。后续命令则记录在repl_baklog,逐个发送给slave。
2. 增量同步:slave提交自己的offset到master,master获取repl_baklog中从offset之后的命令给slave

同步流程:
1. slave节点请求增量同步
2. master节点判断replid,发现不一致,拒绝增量同步
3. master将完整内存数据生成RDB,发送RDB到slave
4. slave清空本地数据,加载master的RDB
5. master将RDB期间的命令记录在repl_baklog,并持续将log中的命令发送给slave
6. slave执行接收到的命令,保持与master之间的同步

什么时候执行全量同步?
	slave节点第一次连接master节点时
	slave节点断开时间太久,repl_baklog中的offset已经被覆盖时

什么时候执行增量同步?
	slave节点断开又恢复,并且在repl_baklog中能找到offset时

4 Redis哨兵

Redis Sentinel (哨兵)着眼于高可用,在 master 宕机时会自动将 slave 提升为master,继续提供服务。

哨兵的作用如下:
- 监控:Sentinel 会不断检查您的master和slave是否按预期工作
- 自动故障恢复:如果master故障,Sentinel会将一个slave提升为master。当故障实例恢复后也以新的master为主
- 通知:Sentinel充当Redis客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给Redis的客户端

//集群监控的原理(如何判断redis实例是否健康)
    基于心跳机制,每隔1s发送一次ping命令,如果超过一定时间没有响应,主观下线
    如果大多数sentinel都认为实例主观下线,则该实例客观下线

//故障转移步骤
    - 首先选定一个slave作为新的master,执行slaveof no one
	- 然后让所有节点都执行slaveof 新master
	- 修改故障节点配置,添加slaveof 新master

5 Redis分片集群

Redis Cluster 着眼于扩展性,在单个 redis 内存不足时,使用 Cluster 进行分片存储。

Redis会把每一个master节点映射到0~16383共16384个插槽(hash slot)上

数据key不是与节点绑定,而是与插槽绑定。redis会根据key的有效部分计算插槽值,分两种情况:

- key中包含"{}",且“{}”中至少包含1个字符,“{}”中的部分是有效部分
- key中不包含“{}”,整个key都是有效部分

//Redis如何判断某个key应该在哪个实例?
- 将16384个插槽分配到不同的实例
- 根据key的有效部分计算哈希值,对16384取余
- 余数作为插槽,寻找插槽所在实例即可

6 缓存穿透、击穿、雪崩

-- 缓存穿透
	缓存穿透是指:如果一个 key 在缓存和数据库都不存在,那么访问这个 key 每次都会进入数据库
		* 很可能被恶意请求利用
		* 缓存雪崩与缓存击穿都是数据库中有,但缓存暂时缺失
		* 缓存雪崩与缓存击穿都能自然恢复,但缓存穿透则不能
		解决方案:
			* 设置一个key-null到redis中(不建议)
			* 布隆过滤器
				① 过滤器可以用来判定 key 不存在,发现这些不存在的 key,把它们过滤掉就好
				② 需要将所有的 key 都预先加载至布隆过滤器
				③ 布隆过滤器不能删除,因此查询删除的数据一定会发生穿透
			
-- 缓存击穿
	某一热点 key 在缓存和数据库中都存在,它过期时,这时由于并发用户特别多,同时读缓存没读到,又同时去数据库去读,压垮数据库
	
		- 解决方案
			* 热点数据不过期
			* 对【查询缓存没有,查询数据库,结果放入缓存】这三步进行加锁,这时只有一个客户端能获得锁,其它客户端会被阻塞,等锁				释放开,缓存已有了数据,其它客户端就不必访问数据库了。但会影响吞吐量(有损方案)
		
-- 缓存雪崩
	情况1:由于大量 key 设置了相同的过期时间(数据在缓存和数据库都存在),一旦到达过期时间点,这些 key 集体失效,造成访问这些 		key 的请求全部进入数据库。
		解决方案
			* 错开过期时间:在过期时间上加上随机值(比如 1~5 分钟)
			* 服务降级:暂停非核心数据查询缓存,返回预定义信息(错误页面,空值等)
	情况2:Redis 实例宕机,大量请求进入数据库 
		- 解决方案
			* 事前预防:搭建高可用集群
			* 多级缓存:缺点是实现复杂度高
			* 熔断:通过监控一旦雪崩出现,暂停缓存访问待实例恢复,返回预定义信息(有损方案)
            * 限流:通过监控一旦发现数据库访问量超过阈值,限制访问数据库的请求数(有损方案)

7 redis 过期键的删除策略

1、定时删除:在设置键的过期时间的同时,创建一个定时器 timer). 让定时器在键的过期时间来临时,立即执行对键的删除操作。
2、惰性删除:放任键过期不管,但是每次从键空间中获取键时,都检查取得的键是否过期,如果过期的话,就删除该键;如果没有过期,就返回键。
3、定期删除:每隔一段时间程序就对数据库进行一次检查,删除里面的过期键。至于要删除多少过期键,以及要检查多少个数据库,则由算法决定。

8 Redis 的回收(淘汰)策略

volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
no-enviction(驱逐):禁止驱逐数据

9 系统的高并发问题怎么解决

数据层
	集群
	开启索引
	分表分库
	开启缓存
	表设计优化
	sql语句优化
	缓存服务器
	搜索服务器
项目层
	采用分布式架构
	做页面缓存
	使用rabbitMQ解耦,异步处理
	分布式文件系统存储海量文件
应用层
	nginx做负载均衡

10 MySQL和Redis数据一致性

第一种,先更新数据库,再更新缓存,如果缓存更新失败,就会导致数据库和redis中的数据不一致
第二种,先删除缓存,再更新数据库,理想情况下,是下次访问redis的时候,发现数据是空的,就会去查询数据库并保存到redis中,但是删除redis和更新数据库并不是原子性操作,还是会存在数据不一致的问题

第三种,采用rabbitmq的可靠性消息通信达到数据的最终一致性,
		还能通过canal组件,监控MySQL中的binlog日志,把更新后的数据同步到redis中. 因为是基于最终一致性来实现的,

延迟双删?第二次删除时间不好把控(不推荐)

在分布式领域做数据同步,必然会出现延迟,几十毫秒一般都是被允许的

11 Redis性能优化

1. 生产环境禁用keys命令  因为keys命令需要遍历存储的键值对,所以操作延时很高,在生产环境使用很可能导致Redis阻塞
2. keys需要设置过期时间  Redis作为内存数据库,一切的数据都是在内存中,一旦内存占用过大则会大大影响性能,因此要设置过期时间
3. 禁止批量的给keys设置相同的过期时间   防止出现缓存雪崩
4. 谨慎选择数据结构
5. 检查持久化策略
6. 增加机器内存或者使用Redis集群

RabbitMQ篇

1 RabbitMQ组件

vHost 虚拟主机
Channel 通道
Exchange 交换机 --作用就是根据路由规则,将消息转发到对应的队列上。
Queue 消息队列,具有缓存消息的能力,rabbitmq中,队列消息可以设置为持久化,临时或者自动删除
Routing key 路由key 来指定这个消息的路由规则。
Binding   作用就是把exchange和queue按照路由规则绑定起来。
Binding key  在绑定Exchange与Queue时,一般会指定一个binding key

2 中间件,交换机,优点

RabbitMQ(延迟低)  RocketMQ  ActiveMQ  Kafka(吞吐量高)

交换机: fanout direct topic

优点: 解耦,异步,削峰

RabbitMQ 的使用场景有哪些?
	抢购活动,削峰填谷,防止系统崩塌。
	延迟信息处理,比如 10 分钟之后给下单未付款的用户发送邮件提醒
	解耦系统,对于新增的功能可以单独写模块扩展,比如用户确认评价之后,新增了给用户返积分的功能

3 谈谈对MQ的理解

我们项目中主要使用MQ来做一些异步的操作,比如说我们的接口是属于http协议的接口,属于同步调用,同步调用过程中,如果我们接口响应慢的情况下,会导致客户端超时阻塞.此时客户端就会进行一些重试策略,在重试的过程中,就会导致我们的业务重复执行,所以我们就需要注意一些幂等性问题,所以我们会将一些执行业务逻辑比较耗时的接口,改成使用MQ异步去实现,能够提高我们http协议接口的响应速度

同时我们还会使用MQ去异步的发优惠券,异步的去扣减库存,异步的去发短信等

4 保证幂等性(不被重复消费)

可以在把数据先存在set或者redis中,消费前,先去里面查看,数据是否已存在,已存在就丢弃这数据

在数据库中设置唯一约束,就不会导致重复数据的多次插入

5 消息堆积问题

要分析是生产者生产过快还是消费者消费能力不足

1.临时启动多个消费者,并发处理消息;
2.每个消费者一般都是批量的形式来进行消费,以此来提高消费速率,如果说在以后我们消费跟不上的情况下
	可以不断横向扩张,对消费者不断做集群,从而解决消息堆积问题

6 消息不丢失

消息丢失的情况:
	生产者写的消息在到中间件的网络传输过程中就丢了,或者是消息到了中间件,但是内部出错,消息没保存下来
	中间件(交换机)将消息保存下来,还没等到消费者消费完,就自己挂掉,导致消息丢失
	消费者取到消息还没来得及消费就自己挂掉了

解决方案:
	1.生产者消息丢失,可以通过开启事务功能,如果消息没有发送成功,发生异常就回滚,然后重试发送消息,发送成功就提交事务
	2.关于中间件的数据丢失,可以开启中间件的持久化,将消息持久化磁盘中,中间件挂了恢复之后自动读取之前存储的数据。
	3.消费者数据丢失,关闭rabbitMQ的autoACK机制,自己手动提交完成信息	

7 消息的顺序性

rabbitMQ为多个消费者开辟多个queue队列(先进先出),根据队列特性来保证数据的顺序性。

8 MQ如何保证分布式事务的最终一致性

分布式事务: 业务相关的多个操作,保证他们同时成功同时失败
最终一致性: 与之对应的是强一致性(在任意时刻,所有节点中的数据是一样的)

MQ要保证事务的最终一致性,就需要做到两点
	1. 生产者要保证100%的消息投递   事务消息机制
	2. 消费者端需要保证幂等消费   唯一id+业务自己实现幂等

Elasticsearch篇

1 elasticsearch 了解多少,说说你们公司 es 的集群架构,索引数据大小,分片有多少,以及一些调优手段

ES 集群架构 13 个节点,索引根据通道不同共 20+索引,根据日期,每日递增 20+,索引:10分片,每日递增 1 亿+数据,每个通道每天索引大小控制:150GB 之内。

2 elasticsearch 倒排索引

传统的我们的检索是通过文章,逐个遍历找到对应关键词的位置。
而倒排索引,是通过分词策略,形成了词和文章的映射关系表,这种词典+映射表即为倒排索引。
有了倒排索引,就能实现 o(1)时间复杂度的效率检索文章了,极大的提高了检索效率。

Linxu篇

1 常用命令

cd 切换目录
pwd 显示当前的绝对路径
cat 查看文件命令
more 分页查看文件命令(不能快速定位到最后一页)
less 分页查看文件命令(可以快速定位到最后一页)
tail -10 ;文件名 看最后10行

tar (解压 压缩 命令)
常用的组合命令: -z 是否需要用gzip压缩。 
			  -c 建立一个压缩文件的参数指令(create) –压缩 
			  -x 解开一个压缩文件的参数指令(extract) –解压 
			  -v 压缩的过程中显示文件(verbose) 
			  -f 使用档名,在f之后要立即接档中(file) 
	常用解压参数组合:zxvf 
	常用压缩参数组合:zcvf 
解压命令: tar -zxvf redis-3.2.8.tar.gz ;解压到当前文件夹 
		 tar -zxvf redis-3.2.8.tar.gz -C /opt/java/ ;
解压到指定目录 压缩命令:(注意 语法有点反了,我反正每次都搞反) tar -zcvf redis-3.2.8.tar.gz redis-3.2.8/ ;
语法 tar -zcvf 压缩后的名称 要压缩的 文件tar -zcvf 压缩后的文件(可指定目录) 要压缩的文件(可指定目录)

2 linux查看日志命令

	vi 文件名 #编辑方式查看,可修改
	cat 文件名 #显示全部文件内容
	more 文件名 #分页显示文件内容
	less 文件名 #与 more 相似,更好的是可以往前翻页
	tail 文件名 #仅查看尾部,还可以指定行数
	head 文件名 #仅查看头部,还可以指定行数

        建议:根据自身情况,适当改变回答内容逻辑,便于理解记忆,最后祝大家能找到一份心意的工作!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

10JQK炸

如果对您有所帮助,请给点鼓励吧

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值