【java八股】

JAVA

1、你是怎样理解OOP面向对象?

面向对象是利于语言对现实事物进行抽象。面向对象具有以下特征:
在这里插入图片描述

2、重载与重写区别?

在这里插入图片描述

3、接口与抽象类的区别?

在这里插入图片描述

4、引用拷贝、深拷贝与浅拷贝的理解?

引用拷贝:
拷贝的对象指向同一块地址
浅拷贝:
只会拷贝基本数据类型的值,以及实例对象的引用地址,并不会复制一份引用地址所指向的对象,也就是浅拷贝出来的对象,内部的类属性指向的是同一个对象
深拷贝:
既会拷贝基本数据类型的值,也会针对实例对象的引用地址所指向的对象进行复制,深拷贝出来的对象,内部的类执行指向的不是同一个对象

5、sleep和wait区别?

sleep方法:
1、属于Thread类中的方法
2、释放cpu给其它线程 不释放锁资源
3、sleep(1000) 等待超过1s被唤醒

wait方法:
1、属于Object类中的方法
2、释放cpu给其它线程,同时释放锁资源
3、wait(1000) 等待超过1s被唤醒
4、wait() 一直等待需要通过notify或者notifyAll进行唤醒
5、wait 方法必须配合 synchronized 一起使用,不然在运行时就会抛出IllegalMonitorStateException异常

6、什么是自动拆装箱 int和Integer有什么区别?

在这里插入图片描述

7、==和equals区别?

在这里插入图片描述

8、为什么重写equals时必须重写hashCode方法?

如果重写了equals方法,而没有重写hashCode方法,那么被认为相等的对象可能会有不同的hashcode

9、为什么两个对象有相同的hashcode值,它们也不一定相等?

由于hash码的本质决定的,当两个对象的hashcode相同时,有可能是发生了哈希冲突,
为了解决哈希冲突的问题,哈希表在处理键值时,不仅会比较对象的哈希码,还会调用equals方法检查对象是否真的相同,当hashcode相同,而equals方法的返回值为false时,那么这两个对象不认为是相等的。

10、String能被继承吗 为什么用final修饰 ?

1、不能被继承,因为String类有final修饰符,而final修饰的类是不能被继承的。
2、String 类是最常用的类之一,为了效率,禁止被继承和重写。
3、为了安全。String 类中有native关键字修饰的调用系统级别的本地方法,调用了操作系统的 API,如果方法可以重写,可能被植入恶意代码,破坏程序。Java 的安全性也体现在这里。

String、StringBuffer、StringBuilder的区别?

● String内部维护的是private final char/byte数组,不可变线程安全, 任何对 String 的修改都会引发新的 String 对象的生成。
● StringBuilder可变,线程不安全但性能好,可以使用append进行拼接字符串
● StringBuffer可变,通过synchronized来保证线程安全,操作和StringBuilder一样
○ synchronized对于出现在循环中会进行锁粗化,会将锁的范围扩展到整个操作,从而避免频繁进行锁操作造成性能开销。

字符串拼接用“+” 还是 StringBuilder?

● 字符串对象通过“+”的字符串拼接方式,实际上是通过 StringBuilder 调用 append() 方法实现的,拼接完成之后调用 toString() 得到一个 String 对象 。
● 频繁使用“+” ,会导致创建过多的 String 对象。直接使用 StringBuilder 对象进行字符串拼接的话,就不会存在这个问题了。

11、String buffer和String builder区别?

在这里插入图片描述

12、final、finally、finalize

final:修饰符(关键字)有三种用法:修饰类、变量和方法。
1、修饰类时,意味着它不能再派生出新的子类,即不能被继承,因此它和abstract是反义词。
2、修饰变量时,该变量使用中不被改变,必须在声明时给定初值,在引用中只能读取不可修改,即为常量。
3、修饰方法时,也同样只能使用,不能在子类中被重写。
finally:通常放在try…catch的后面构造最终执行代码块,这就意味着程序无论正常执行还是发生异常,这里的代码只要JVM不关闭都能执行,可以将释放外部资源的代码写在finally块中。
finalize:Object类中定义的方法,Java中允许使用finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在销毁对象时调用的,通过重写finalize() 方法可以整理系统资源或者执行其他清理工作。

13、Object中有哪些方法?

在这里插入图片描述

14、说一下集合体系?

在这里插入图片描述

15、ArrarList和LinkedList区别?

1、ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
2、对于随机访问get和set,ArrayList效率优于LinkedList,因为LinkedList要移动指针。
3、对于新增和删除操作add和remove,LinkedList比较占优势,因为ArrayList要移动数据。 这一点要看实际情况的。若只对单条数据插入或删除,ArrayList的速度反而优于LinkedList。但若是批量随机的插入删除数据,LinkedList的速度大大优于ArrayList. 因为ArrayList每插入一条数据,要移动插入点及之后的所有数据。

16、HashMap底层是 数组+链表+红黑树,为什么要用这几类结构?

1、数组作为哈希表,根据对象的key的hash值进行在数组里面是哪个节点。
2、链表的作用是解决hash冲突,将hash值取模之后的对象存在一个链表放在hash值对应的槽位
3、红黑树 JDK8使用红黑树来替代超过8个节点的链表,主要是查询性能的提升,从原来的O(n)到O(logn),

17、什么是红黑树?

红黑树就是有特定约束的平衡二叉树。
在这里插入图片描述

18、 HashMap的put方法原理?

在jdk1.7中由数组+链表实现,在jdk1.8中由数组+链表+红黑树实现
一、通过hash方法计数key的hash值
在这里插入图片描述
二、数组进行一次扩容
当hashmap中的元素个数超过数组大小loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75,也就是说,默认情况下,数组大小为16,那么当hashmap中元素个数超过160.75=12的时候,就把数组的大小扩展为2*16=32
在这里插入图片描述
三、根据hash值计算key在数组中的下标,如果对应的下标正好没有存放数据,则直接插入
在这里插入图片描述
如果对应下标有数据了,就需要判断是否为相同的key,是则覆盖value,
否则需要判断的是否为树节点,
是则向树中插入节点,
否则向链表中插入数据
注:(当链表长度大于8,数组长度大于64时,会转换为红黑树,当红黑树节点小于6时,会回退成链表

19、Hashtable与HashMap扩容?

Hashtable 默认的初始大小为 11,之后每次扩充,容量变为原来的 2n+1。
HashMap 默认的初始化大小为 16。之后每次扩充,容量变为原来的 2 倍。

20、HashMap和HashTable区别?

1、线程安全性不同
HashMap是线程不安全的;
HashTable是线程安全的,其中的方法是Synchronized,在多线程并发的情况下,可以直接使用HashTable,但是使用HashMap时必须自己增加同步处理。
2、是否提供contains方法
HashMap只有containsValue和containsKey方法;
HashTable有contains、containsKey和containsValue三个方法,其中contains和containsValue方法功能相同。
3、key和value是否允许null值
HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。
Hashtable中,key和value都不允许出现null值。

21、线程的创建方式?

1、继承Thread类创建线程
2、实现Runnable接口创建线程
3、使用Callable和Future创建线程 有返回值
4、使用线程池创建线程

#### 代码演示
import java.util.concurrent.*;
public class threadTest{
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //继承thread
        ThreadClass thread = new ThreadClass();
        thread.start();
        Thread.sleep(100);
        System.out.println("#####################");
 
        //实现runnable
        RunnableClass runnable = new RunnableClass();
        new Thread(runnable).start();
        Thread.sleep(100);
        System.out.println("#####################");
 
        //实现callable
        FutureTask futureTask = new FutureTask(new CallableClass());
        futureTask.run();
        System.out.println("callable返回值:" + futureTask.get());
        Thread.sleep(100);
        System.out.println("#####################");
 
        //线程池
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1, 2, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10));
        threadPoolExecutor.execute(thread);
        threadPoolExecutor.shutdown();
        Thread.sleep(100);
        System.out.println("#####################");
 
        //使用并发包Executors
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        executorService.execute(thread);
        executorService.shutdown();
    }
}
 
class ThreadClass extends Thread{
    @Override
    public void run() {
        System.out.println("我是继承thread形式:" + Thread.currentThread().getName());
    }
}
 
class RunnableClass implements Runnable{
    @Override
    public void run(){
        System.out.println("我是实现runnable接口:" + Thread.currentThread().getName());
    }
}
 
class CallableClass  implements Callable<String> {
    @Override
    public String call(){
        System.out.println("我是实现callable接口:");
        return "我是返回值,可以通过get方法获取";
    }
}

22、线程的状态转换有什么(生命周期)

在这里插入图片描述
在这里插入图片描述

23、Java中有几种类型的流?

在这里插入图片描述
在这里插入图片描述

24、请写出你最常见的5个RuntimeException?

1、java.lang.NullPointerException
空指针异常;出现原因:调用了未经初始化的对象或者是不存在的对象。
2、java.lang.ClassNotFoundException
指定的类找不到;出现原因:类的名称和路径加载错误;通常都是程序试图通过字符串来加载某个类时可能引发异常。
3、java.lang.NumberFormatException
字符串转换为数字异常;出现原因:字符型数据中包含非数字型字符。
4、java.lang.IndexOutOfBoundsException
数组角标越界异常,常见于操作数组对象时发生。
5、java.lang.IllegalArgumentException
方法传递参数错误。

25、谈谈你对反射的理解?

反射机制:
java语言在运行时拥有的一项自观的能力,
可以在运行时:
判断任意一个对象所属类,所具有的成员变量和方法
构造任意一个类的对象
调用任意一个对象的方法

26、讲讲动态代理?那三种代理模式怎么实现的?

动态代理:
在运行时,创建目标对象的代理对象,并对目标对象中的方法进行功能性增强的一种技术,AOP就是基于这种思想
三种代理模式:
1、基于接口,和继承实现(静态代理),
2、基于接口的jdk动态代理(通过反射实现)
3、不基于接口的cglib代理

27、什么是 java 序列化,如何实现 java 序列化?

1、序列化是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决在对对象流进行读写操作时所引发的问题。
2、序 列 化 的 实 现 :
将 需 要 被 序 列 化 的 类 实 现 Serializable 接 口 , 该 接 口 没 有 需 要 实 现 的 方 法 , implements Serializable 只是为了标注该对象是可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,接着,使用 ObjectOutputStream 对象的 writeObject(Object obj)方法就可以将参数为 obj 的对象写出(即保存其状态),要恢复的话则用输入流。

28、Http 常见的状态码?

在这里插入图片描述

29、GET 和POST 的区别 ?

1、语义:
GET 通常用于获取或查询资源,而 POST 通常用于创建或修改资源。
2、格式:
GET 请求的参数通常放在 URL 中,而 POST 请求的参数通常放在请求体(body)中,可以有多种编码格式。
GET 请求的 URL 长度受到浏览器和服务器的限制,而 POST 请求的 body 大小则没有明确的限制。不过,实际上 GET 请求也可以用 body 传输数据,只是并不推荐这样做,因为这样可能会导致一些兼容性或者语义上的问题。
3、安全性:
GET 请求和 POST 请求如果使用 HTTP 协议的话,那都不安全,因为 HTTP 协议本身是明文传输的,必须使用 HTTPS 协议来加密传输数据。另外,GET 请求相比 POST 请求更容易泄露敏感数据,因为 GET 请求的参数通常放在 URL 中
4、缓存:
由于 GET 请求是幂等的,它可以被浏览器或其他中间节点(如代理、网关)缓存起来,以提高性能和效率。
而 POST 请求则不适合被缓存,因为它可能有副作用,每次执行可能需要实时的响应。

30、 跨域?

从一个域名的网页去请求另一个域名的资源时,域名、端口、协议任一不同,都是跨域

31、JVM内存分哪几个区,每个区的作用是什么 ?

在这里插入图片描述
方法区(有时候也叫永久代):
1、该区内很少发生垃圾回收,但是并不代表不发生GC,在这里进行的GC主要是对方法区的常量池和对类型的卸载
2、方法区主要用来存储 类的信息,常量、静态变量、JIT编译后的代码
3、线程共享
4、方法区有一个运行常量池,用于存放静态编译产生的字面量和符号引用
虚拟机栈:
1、虚拟机栈也就是我们平常所称的栈内存,为java方法服务,每个方法在执行时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接和方法出口。
2、线程私有
本地方法栈:
本地方法栈和虚拟机栈类似,只不过本地方法栈为Native方法服务。
堆:
java堆是所有线程所共享的一块内存,在虚拟机启动时创建,几乎所有的对象实例都在这里创建,因此该区域经常发生垃圾回收操作。
程序计数器:
通过改变这个计数值可以选取下一条需要执行的字节码指令

32、Java中垃圾收集的方法有哪些?

复制算法
a) 效率高,缺点:需要内存容量大,比较耗内存
b) 使用在占空间比较小、刷新次数多的新生区
标记-清除
a) 效率比较低,会产生碎片。
标记-整理
a) 效率低速度慢,需要移动对象,但不会产生碎片。

33、如何判断一个对象是否存活(或者GC对象的判定方法) ?

1、引用计数法
所谓引用计数法就是给每一个对象设置一个引用计数器,每当有一个地方引用这个对象时,就将计数器加一,引用失效时,计数器就减一。当一个对象的引用计数器为零时,说明此对象没有被引用,也就是“死对象”,将会被垃圾回收。
引用计数法有一个缺陷就是无法解决循环引用问题
2、可达性算法
该算法的基本思路就是通过一些被称为引用链(GC Roots)的对象作为起点,从这些节点开始向下搜索,搜索走过的路径被称为(Reference Chain),当一个对象到GC Roots没有任何引用链相连时(即从GC Roots节点到该节点不可达),则证明该对象是不可用的。

34、什么情况下会产生StackOverflowError(栈溢出)和OutOfMemoryError(堆溢出)怎么排查 ?

在这里插入图片描述
在这里插入图片描述

35、什么是线程池,线程池有哪些(创建)?

线程池就是事先将多个线程对象放到一个容器中,当使用的时候就不用 new 线程而是直接去池中拿线程即可,节省了开辟子线程的时间,提高的代码执行效率。
在这里插入图片描述
1、newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。这种类型的线程池特点是:
工作线程的创建数量几乎没有限制(其实也有限制的,数目为Interger. MAX_VALUE), 这样可灵活的往线程池中添加线程。
如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间(默认为1分钟),则该工作线程将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个工作线程。
在使用CachedThreadPool时,一定要注意控制任务的数量,否则,由于大量线程同时运行,很有会造成系统瘫痪。
2、newFixedThreadPool
创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。FixedThreadPool是一个典型且优秀的线程池,它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但是,在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源。
3、newSingleThreadExecutor
创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。如果这个线程异常结束,会有另一个取代它,保证顺序执行。单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。
4、newScheduleThreadPool
创建一个定长的线程池,而且支持定时的以及周期性的任务执行。例如延迟3秒执行

36、为什么要使用线程池?

线程复用
第一:降低资源消耗。通过重复利用己创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进 行统一的分配,调优和监控

37、线程池底层工作原理?

线程池刚创建的时候,里面没有任何线程,等到有任务过来的时候才会创建线程
在这里插入图片描述

38、ThreadPoolExecutor对象有哪些参数 怎么设定核心线程数和最大线程数 拒绝策略有哪些?

核心线程数
最大线程数
非核心线程最大空闲时间
、、、时间单位
任务队列
线程工厂
拒绝策略
在这里插入图片描述

39、常见线程安全的并发容器有哪些 ?

CopyOnWriteArrayList、CopyOnWriteArraySet、ConcurrentHashMap
CopyOnWriteArrayList、CopyOnWriteArraySet
采用写时复制实现线程安全
ConcurrentHashMap
采用分段锁的方式实现线程安全

40、Atomic原子类了解多少 原理是什么 ?

在这里插入图片描述
AtomicInteger 类利用 CAS (Compare and Swap) + volatile + native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。

41、CAS 的原理 ?

CAS 的原理 是拿期望值和原本的值作比较,如果相同,则更新成新的值。
CAS 算法(比较与交换)
● V:要更新的变量值(Var)
● E:期望值(Expected)
● N:拟写入的新值(New)
当且仅当 V 的值等于 E 时,CAS 通过原子方式用新值 N 来更新 V 的值。如果不等,说明已经有其它线程更新了 V,则当前线程放弃更新。

42、synchronized底层实现是什么 lock底层是什么 有什么区别 ?

在这里插入图片描述
synchronized底层实现:
JVM从方法常量池中的方法表结构中的访问标志 区分一个方法是不是同步方法。当方法调用时,调用指令将会检查方法的访问标志是否被设置,如果设置了,执行线程将先持有monitor,然后再执行方法,最后在方法完成时释放monitor。
在这里插入图片描述
在这里插入图片描述

43、ConcurrentHashMap底层原理?

Java7 中 ConcurrentHashMap 使用的分段锁,
也就是每一个 Segment 上同时只有一个线程可以操作,每一个 Segment 都是一个类似 HashMap 数组的结构,它可以扩容,它的冲突会转化为链表。但是 Segment 的个数一但初始化就不能改变。
Java8 中的 ConcurrentHashMap 使用的 Synchronized 锁加 CAS 的机制。
结构Node 数组 + 链表 / 红黑树,Node 是类似于一个 HashEntry 的结构。它的冲突再达到一定大小时会转化成红黑树,在冲突小于一定数量时又退回链表。

44、了解volatile关键字不 ?

1、volatile是Java提供的最轻量级的同步机制,保证了共享变量的可见性,被volatile关键字修饰的变量,如果值发生了变化,其他线程立刻可见,避免出现脏读现象。
2、volatile禁止了指令重排,可以保证程序执行的有序性,但是由于禁止了指令重排,所以JVM相关的优化没了,效率会偏弱

45、synchronized和volatile有什么区别 ?

在这里插入图片描述

46、类加载过程?

● 类加载过程:加载->链接->初始化。
● 连接过程又可分为三步:验证->准备->解析。

加载:将字节码文件加载到方法区中
验证:对字节码的规范进行—些校验,保证我们加载类的正确性
准备:为类的静态变量分配内存,并初始化
解析:把类中的符号引用转化为直接引用
○ 符号引用是在字节码文件中以字符串形式表示的
○ 直接引用则是指向内存中的实际对象或函数的指针
初始化:对类中的静态变量赋预期值以及执行静态代码块

47、什么是类加载器,类加载器有哪些?

类加载器的主要作用就是加载 Java 类的字节码( .class 文件)到 JVM 中(在内存中生成一个代表该类的 Class 对象)。
3个重要的ClassLoader:
● BootstrapClassLoader(启动类加载器):最顶层的加载类,由 C++实现,通常表示为 null,
● ExtensionClassLoader(扩展类加载器):用来加载 Java 的扩展库
● AppClassLoader(应用程序类加载器):面向我们用户的加载器,负责加载当前应用 classpath 下的所有 jar 包和类。
自定义类加载器
如果我们要自定义自己的类加载器,很明显需要继承 ClassLoader抽象类。
ClassLoader 类有两个关键的方法:
● protected Class loadClass(String name, boolean resolve):加载指定二进制名称的类,实现了双亲委派机制
● protected Class findClass(String name):根据类的二进制名称来查找类
(打破双亲委派机制需要重写 loadClass() 方法以及findClass方法)
双亲委派模型
● 双亲委派机制是一个自上而下的类加载过程,首先会委托父加载器去加载目标类,如果能找到目标类就加载,如果找不到就继续委托它的父加载器去加载,如果父加载器都加载不了,就自己去加载
为什么要有双亲委派机制
● 首先自上而下的过程可以避免重复类加载
● 可以实现安全机制防止核心类被篡改

48、简述java内存分配与回收策略以及Minor GC和Major GC(full GC)?

在这里插入图片描述
当前商用的垃圾收集器都采用的是分代垃圾回收,
1、分代分为年轻代和老年代,年期代里头又分为Eden区(伊甸园区)和Survivor区,通常默认的比例为8:1:1;每次只保留90%的空间可以用作新生对象。
2、每一次垃圾回收之后,存活的对象年龄对应+1,当经历15次还依然存活的对象,我们让它直接进入老年代
3、另外一种进入老年代的方式是内存担保机制,也就是说当新生代空间不够的时候,对象直接进入老年代
4、新生代中发生的垃圾回收叫Minor GC 和 full GC;

49、并发编程三要素?

在这里插入图片描述

50、extends和super的区别?

在这里插入图片描述

51、垃圾回收器

Serial 、Serial Old
● Serial 和 Serial Old 是 单线程垃圾收集器,在GC时,只允许一个线程进行
● Serial 用在年轻代采用的是 复制算法、Serial Old 用在老年代采用的是 标记整理算法
● 在单核处理器的情况下,简单高效,但是多核处理器下无法发挥多核的性能不推荐使用,适合 100M以内 内存
Parallel、Parallel Old
● Parallel 和 Parallel Old 是 多线程垃圾收集器,是serial系列的多线程版本
● Parallel 用在年轻代采用的是 复制算法,Parallel Old采用的是 标记整理算法
● 关注点在于吞吐量,比较适合CPU密集型场景,一般 4G以下 内存推荐使用
ParNew、CMS
● ParNew 与 Parallel类似,只是为了配合 CMS 才出现的
● ParNew 用在年轻代采用的是 复制算法, CMS 用在老年代采用的是 标记清除算法
● CMS关注点是最大停顿时间,也就是用户的体验度,比较适合 4~8G 内存的情况使用

Exception

Exception 程序本身可以处理的异常,可以通过 catch 来进行捕获。 Exception 是程序问题导致的异常,又分为两种:
● CheckedException受检异常:编译器会强制检查并要求处理的异常。
● RuntimeException运行时异常:程序运行中出现异常,如我们熟悉的空指针、 数组下标越界等等
Error:Error 属于程序无法处理的错误 ,例如 Java 虚拟机运行错误(Virtual MachineError)、虚拟机内存不够错误(OutOfMemoryError)、类定义错误(NoClassDefFoundError)等 。发生这些异常jvm一般会终止线程。
在这里插入图片描述
在这里插入图片描述
异常处理
● 遇到异常不进行具体处理,而是继续抛给调用者( throw , throws )
● try catch捕获异常

Spring

1、Spring 中Bean的生命周期有哪些步骤:

Aware回调方法
是Spring框架中的一个设计模式,它允许Bean在Spring容器中获取其所在环境的信息或资源。这个设计模式通过提供一系列预定义的接口,如BeanFactoryAware和ApplicationContextAware,使得Bean能够与容器进行交互。当Bean被初始化时,Spring容器会自动调用这些接口中定义的回调方法,并传递相关的上下文或资源对象给Bean。这样,Bean就能够根据这些信息或资源灵活地执行其功能

Bean的生命周期
(1)默认情况下,IOC容器中bean的生命周期分为五个阶段:
1、调用构造器 或者是通过工厂的方式创建Bean对象
2、给bean对象的属性注入值
3、调用初始化方法,进行初始化, 初始化方法是通过init-method来指定的.
4、使用
5、IOC容器关闭时, 销毁Bean对象.

(2)当加入了Bean的后置处理器后,IOC容器中bean的生命周期分为七个阶段:(了解)
1、调用构造器 或者是通过工厂的方式创建Bean对象
2、给bean对象的属性注入值
3、执行Bean后置处理器中的 postProcessBeforeInitialization
4、调用初始化方法,进行初始化, 初始化方法是通过init-method来指定的.x
5、执行Bean的后置处理器中 postProcessAfterInitialization
6、使用
7、IOC容器关闭时, 销毁Bean对象

注入方式:
1、通过 setter 方法注入
2、通过构造方法注入

Bean的作用域
在这里插入图片描述
在这里插入图片描述

2、说说Spring事务是怎么工作的?

在这里插入图片描述
在这里插入图片描述

3、ApplicationContext 和BeanFactory有什么区别?

ApplicationContext是一种BeanFactory,从源码中可以看到Application是继承了BeanFactory接口的,但是ApplicationContext还继承了一些其他的接口,所以除了获取Bean和创建Bean,它还有一些其他的功能。
在这里插入图片描述

4、SpringBoot的自动配置是如何实现的?

springboot通过@enableautoconfiguration注解开启自动配置
对jar包下的spring.factories文件进行扫描,这个文件中包含了可以进行自动配置的类,当满足@condition注解指定的条件时,便在依赖的支持下进行实例化,注册到spring容器中。
通俗的来讲,我们之前在写ssm项目时候,配置了大量坐标和配置内容,搭环境的过程在项目开发中占据了大量时间,SpringBoot的最大的特点就是简化了各种xml配置内容,所以springboot的自动配置就是用注解来对一些常规的配置做默认配置,简化xml配置内容,使你的项目能够快速运行。

MVC自动失效的问题:
在这里插入图片描述
在老版本中我们常用的做法就是继承WebMvcConfigurerAdapter类,这个类本身是实现了WebMvcConfigurer接口的,因为老版本JDK接口没有默认方法,直接实现WebMvcConfigurer比较繁琐,而后来接口可以有默认方法了,WebMvcConfigurerAdapter就被标记为过时了,所以我们现在配置MVC只需要实现WebMvcConfigurer接口或者继承WebMvcConfigurationSupport,但是后者会导致SpringBoot的配置失效,因为在自动配置类上有@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)这样一个注解,表示没有WebMvcConfigurationSupport类及其子类的实例时才会加载自动配置(另外使用@EnableWebMvc注解也会导致自动配置失效)。

5、SpringMVC 的工作流程?

SpringMVC 的工作流程

6、SpringMVC中如何返回JSON数据?

1、在项目中加入json转换的依赖,例如jackson
2、在请求处理的方法中将返回值改为具体返回的数据类型,例如数据的集合类List等。
3、通过在请求处理方法上使用@ResponseBody注解,对Handler方法返回的结果进行转换。

7、Spring是如何解决循环依赖?

Spring是如何解决循环依赖

8、谈谈你对Spring的理解?

1、spring 是一个基础的框架,同时提供了一个Bean 的容器,用来装载Bean对象
2、spring会帮我们创建Bean 对象并维护Bean对象 的生命周期。
3、在spring 框架上,还有springCloud,spring Boot 的技术框架,都是以Spring为基石的
4、spring 有两个核心,就是IOC和AOP

在Spring里,创建被调用者的工作不再由调用者来完成,因此称为控制反转;创建被调用者 实例的工作通常由Spring容器来完成,然后注入调用者,因此也称为依赖注入。

控制反转(IOC),IOC本质上就是一个对象工厂
传统的 java 开发模式中,当需要一个对象时,我们会自己使用 new 调用构造方法创建一个对象。而在 spring 开发模式中,spring 容器使用了工厂模式为我们创建了所需要的对象,不需要我们自己创建了,直接调用spring 提供的对象就可以了,这是控制反转的思想。本质是通过JAVA的反射机制实现的。

依赖注入(DI)
依赖注入是指在 Spring IOC 容器创建对象的过程中,将所依赖的对象通过配置进行注入。我们可以通过依赖注入的方式来降低对象间的耦合度。

面向切面编程(AOP)
在面向对象编程(OOP)思想中,我们将事物纵向抽成一个个的对象。而在面向切面编程中,我们将一个个的对象某些类似的方面横向抽成一个切面,对这个切面进行一些如权限控制、事物管理,记录日志等公用操作处理的过程就是面向切面编程的思想。AOP 底层是动态代理,如果是接口采用 JDK 动态代理,如果是类采用CGLIB 方式实现动态代理。

9、SpringBoot 中常用注解及底层实现

核心注解

1、@SpringBootApplication注解:
这个注解标识了一个Spring工程,它其实是另外3个注解的组合,这3个注解是:
@SpringBootConfiguration:
实际就是一个@Configuration,表示启动类是一个配置类
@EnableAutoConfiguration:
用来加载ClassPath下Spring.Fatories中所定义的自动配置类,将这些自动加载为配置Bean
@ComponentScan:
标识扫描路径,因为默认是没有配置扫描路径的,所以SpringBoot的扫描路径是启动类所在的当前目录
2、@Bean注解:
用来定义Bean,类似于XML中的Bean标签,Spring在启动时,会对加了@Bean注解的方法进行解析,将方法的名字作为BeanName,并通过执行方法得到Bean对象

常用注解

Spring和SpringMVC中常用注解
@Component 基本注解,标识一个受Spring管理的组件
@Controller 标识为一个表示层的组件
@Service 标识为一个业务层的组件
@Repository 标识为一个持久层的组件
@Autowired 自动装配
@Qualifier("") 具体指定要装配的组件的id值
@RequestMapping() 完成请求映射
@PathVariable 映射请求URL中占位符到请求处理方法的形参

10、SpringBoot是如何启动Tomcat的?

在这里插入图片描述

11、简述SpringMVC中如何返回JSON数据?

Step1:在项目中加入json转换的依赖,例如jackson,fastjson,gson等
Step2:在请求处理方法中将返回值改为具体返回的数据的类型, 例如数据的集合类List等
Step3:在请求处理方法上使用@ResponseBody注解

12、Spring中常用的设计模式

1、代理模式
spring 中两种代理方式,若目标对象实现了若干接口,spring 使用jdk 的java.lang.reflect.Proxy类代理。若目标兑现没有实现任何接口,spring 使用 CGLIB 库生成目标类的子类。
2、单例模式
在 spring 的配置文件中设置 bean 默认为单例模式

/**
 * 懒汉式
 */
public class LazySingleton {
    private static LazySingleton lazySingleton = null;
    private LazySingleton(){
 
    }
    public static LazySingleton getInstance(){
        if (lazySingleton == null){
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }
}

//饿汉式单例类.在类初始化时,已经自行实例化
public class Singleton1{
//私有的默认构造子
private Singleton1(){}
//已经自行实例化
private static final Singleton1 single=new Singleton1();
//静态工厂方法
public static Singleton1 getInstance(){
return single;
}
}

3、模板方法模式
用来解决代码重复的问题
4、工厂模式
在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用同一个接口来指向新创建的对象。Spring 中使用 beanFactory 来创建对象的实例。

13、MyBatis中 #{}和${}的区别是什么?

在这里插入图片描述

14、Mybatis 中一级缓存与二级缓存?

MyBatis的缓存分为一级缓存和 二级缓存。
一级缓存是SqlSession级别的缓存,默认开启。
二级缓存是NameSpace级别(Mapper)的缓存,多个SqlSession可以共享,使用时需要进行配置开启。
缓存的查找顺序:二级缓存 => 一级缓存 => 数据库

15、MyBatis如何获取自动生成的(主)键值?

在这里插入图片描述

16、简述Mybatis的动态SQL,列出常用的6个标签及作用?

在这里插入图片描述

17、Mybatis 如何完成MySQL的批量操作?

在这里插入图片描述

18、谈谈怎么理解SpringBoot框架 ?

Spring Boot 是 Spring 开源组织下的子项目,是 Spring 组件一站式解决方案,主要是简化了使用 Spring 的难度,简省了繁重的配置,提供了各种启动器,开发者能快速上手。
在这里插入图片描述

19、SpringBoot配置文件有哪些 怎么实现多环境配置?

在这里插入图片描述

20、SpringBoot和SpringCloud是什么关系?

Spring Boot 是 Spring 的一套快速配置脚手架,可以基于Spring Boot 快速开发单个微服务,Spring Cloud是一个基于Spring Boot实现的开发工具;Spring Boot专注于快速、方便集成的单个微服务个体,Spring Cloud关注全局的服务治理框架; Spring Boot使用了默认大于配置的理念,很多集成方案已经帮你选择好了,能不配置就不配置,Spring Cloud很大的一部分是基于Spring Boot来实现,必须基于Spring Boot开发。

可以单独使用Spring Boot开发项目,但是Spring Cloud离不开 Spring Boot。

21、SpringCloud都用过哪些组件 介绍一下作用 ?

在这里插入图片描述

22、Nacos作用以及注册中心的原理?

Nacos:作为注册中心和配置中心
Nacos注册中心分为server与client,server采用Java编写,为client提供注册发现服务与配置服务。而client可以用多语言实现,client与微服务嵌套在一起,nacos提供sdk和openApi,如果没有sdk也可以根据openApi手动写服务注册与发现和配置拉取的逻辑。
在这里插入图片描述
服务注册原理
服务注册方法:以Java nacos client v1.0.1 为例子,服务注册的策略的是每5秒向nacos server发送一次心跳,心跳带上了服务名,服务ip,服务端口等信息。同时 nacos server也会向client 主动发起健康检查,支持tcp/http检查。如果15秒内无心跳且健康检查失败则认为实例不健康,如果30秒内健康检查失败则剔除实例。
在这里插入图片描述

23、Feign工作原理?

主程序入口添加了@EnableFeignClients注解开启对FeignClient扫描加载处理。根据Feign Client的开发规范,定义接口并加@FeignClient注解。
当程序启动时,会进行包扫描,扫描所有@FeignClient的注解的类,并且讲这些信息注入Spring IOC容器中,当定义的的Feign接口中的方法被调用时,通过JDK的代理方式,来生成具体的RequestTemplate.当生成代理时,Feign会为每个接口方法创建一个RequestTemplate。
当生成代理时,Feign会为每个接口方法创建一个RequestTemplate对象,该对象封装HTTP请求需要的全部信息,如请求参数名,请求方法等信息都是在这个过程中确定的。然后RequestTemplate生成Request,然后把Request交给Client去处理,这里指的时Client可以时JDK原生的URLConnection,Apache的HttpClient,也可以时OKhttp,最后Client被封装到LoadBalanceClient类,这个类结合Ribbon负载均衡发器服务之间的调用。
在这里插入图片描述

Mysql

1、Select 语句完整的执行顺序:

(1)from 子句组装来自不同数据源的数据;
(2)where 子句基于指定的条件对记录行进行筛选;
(3)group by 子句将数据划分为多个分组;
(4)使用聚集函数进行计算;
(5)使用 having 子句筛选分组;
(6)计算所有的表达式;
(7)select 的字段;
(8)使用order by 对结果集进行排序。

2、MySQL事务

事务的四大特性:
原子性:事务是不可分割的最小操作单元,要么全部成功,要么全部失败
一致性:事务完成时,必须使所有数据都保持一致状态
隔离性:数据库的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行
持久性:事务一旦提交或回滚,它对数据库中的数据的改变是永久的

MySQL事务隔离级别:
在这里插入图片描述
并发的问题:
脏读: 一个事务读取到另外一个事务还没提交的数据
不可重复读: 一个事务先后读取同一条记录,但是两次读取的数据不同
幻读:一个事务按照条件查询数据时,没有对应的数据行,但是在插入数据时,又发现该数据又存在(先查询没有,但是插入时又有

这四种隔离级别具体是如何实现的呢?

对于**「读未提交」隔离级别的事务来说,因为可以读到未提交事务修改的数据,所以直接读取最新的数据就好了;
对于
「串行化」隔离级别的事务来说,通过加读写锁的方式来避免并行访问;
对于
「读提交」和「可重复读」隔离级别**的事务来说,它们是通过 Read View 来实现的,它们的区别在于创建 Read View 的时机不同,大家可以把 Read View 理解成一个数据快照,就像相机拍照那样,定格某一时刻的风景。「读提交」隔离级别是在「每个语句执行前」都会重新生成一个 Read View,而「可重复读」隔离级别是「启动事务时」生成一个 Read View,然后整个事务期间都在用这个 Read View。

常见锁举例

1、表级锁(Table-level Lock):表级锁是对整张表进行锁定,可以分为两种类型:
共享锁(Shared Lock):也称为读锁(Read Lock),多个事务可以同时持有共享锁,且不互斥。共享锁适用于读操作,不阻塞其他事务的读操作。
排他锁(Exclusive Lock):也称为写锁(Write Lock),排他锁在事务对表进行更新、删除等写操作时使用。排他锁只允许一个事务持有,其他事务不能同时持有共享锁或排他锁。
2、行级锁(Row-level Lock):行级锁是针对数据表中的行进行锁定,锁定指定的行,其他事务对同一行的其他操作会被阻塞。MySQL中的行级锁主要有以下两种实现方式:
共享行级锁(Shared Row-level Lock):允许多个事务同时持有共享锁,适用于读操作。
排他行级锁(Exclusive Row-level Lock):一次只允许一个事务持有排他锁,适用于写操作。
3、间隙锁(Gap Lock):间隙锁是一种特殊的锁,用于防止幻读(Phantom Read)的情况发生。间隙锁锁定的是一个范围,如果其他事务在给定范围内进行插入操作,就会被间隙锁阻塞。

作用:
保证某个间隙内的数据在锁定情况下不会发生任何变化。比如mysql默认隔离级别下的可重复读(RR)。

幻读的问题存在是因为新增或者更新操作,这时如果进行范围查询的时候(加锁查询),会出现不一致的问题,这时使用不同的行锁已经没有办法满足要求,需要对一定范围内的数据进行加锁,间隙锁就是解决这类问题的。在可重复读隔离级别下,数据库是通过行锁和间隙锁共同组成的(next-key lock),来实现的。

4、意向锁(Intention Lock):意向锁是为了提高并发性能而引入的锁机制。它是一种表级锁,用于表示事务对表中某些行或某个范围的锁定意向,是给其他事务提供信息的标记,表明它们有意向获取特定的锁。

触发机制

1、共享锁(读锁):事务在读取数据时可以获取,共享资源
2、排他锁(写锁):事务在修改数据时需要获取排他锁,互斥资源
3、行级锁:行级锁是在表的行级上进行锁定,只影响特定的行的读写操作,当事务修改某一行时,会请求该行的排他锁;当事务读取某一行时,会请求该行的共享锁。
4、间隙锁:间隙锁是在索引范围内的间隙上进行锁定,用于防止幻读的发生。当事务在一个范围内读取数据时,会在范围的间隙上设置间隙锁,阻止其他事务在范围内插入数据。
5、意向锁:意向锁是用于提供锁的意向信息的标记,它在表级别上设置。当一个事务持有某个行的共享锁或排他锁时,会在表上设置意向锁,表示其他事务有意向获取行级锁。

Mysql死锁的场景

行锁导致死锁
间隙锁导致死锁
index merge 导致死锁
唯一索引冲突导致死锁
以下场景隔离级别均为默认的Repeatable Read(可重复读);

行锁导致死锁

前提:表 t_user 的 uid 字段创建了唯一索引,并拥有可更新字段age。
场景复现:
在这里插入图片描述
死锁原因详解:
1、两个事务执行过程时间上有交集,并且过程发生在两者提交之前
2、事务1更新uid=1的记录,事务2更新uid=2的记录,在RR级别,由于uid是唯一索引,因此两个事务将分别持有uid=1和2所在行的独占锁
3、事务1执行到第二条更新语句时,发现uid=2的行被锁住,进入阻塞等待锁释放;
4、事务2执行到第二条语句时发现uid=1的行被锁,同样进入阻塞
5、两个事务互相等待,死锁产生。
相应业务案例和解决方案:
1、避免循环更新,优化为一条where锁定要更新的记录批量更新
2、如果非要循环更新,尝试取消事务(能接受的话),即每一条更新为一个独立的事务

间隙锁导致死锁 (gap lock/next keys lock导致死锁)

场景复现:
首先查询表中目前存在的记录:
在这里插入图片描述
执行两个事务的操作:
在这里插入图片描述
死锁原因分析:
1、事务1执行delete age = 27,务2执行delete age = 31,在RR级别,操作条件不是唯一索引时,行锁会升级为next keys lock(可以理解为间隙锁),因此事务1锁住了25到27和27到29的区间,事务2锁住了29到31的区间
2、事务1执行insert age = 30,等待事务2释放锁
3、事务2执行insert age = 28,等待事务1释放锁
4、死锁产生,死锁日志显示lock_mode X locks gap before rec insert intention waiting
解决方案:
1、降低事务隔离级别到Read Committed,该隔离级别下间隙锁降级为行锁,可以减少死锁发生的概率
2、避免这种场景

MVCC(多版本并发控制)

顾名思义,MVCC是通过数据行的多个版本管理来实现数据库的并发控制。这项技术使得在InnoDB的事务隔离级别下执行一致性读操作有了保证。换言之,就是为了查询一些正在被另一个事务更新的行,并且可以看到它们被更新之前的值,这样在做查询的时候就不用等待另一个事务释放锁。

快照读与当前读

MVCC在MySQL InnoDB中的实现主要是为了提高数据库并发性能,用更好的方式去处理读-写冲突,做到即使有读写冲突时,也能做到不加锁,非阻塞并发读,而这个读指的就是快照读,而非当前读。当前读实际上是一种加锁的操作,是悲观锁的实现。而MVCC本质是采用乐观锁思想的一种方式。
1、快照读
快照读又叫一致性读,读取的是快照数据。不加锁的简单的 SELECT 都属于快照读,即不加锁的非阻塞读;
快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读。
2、当前读
当前读读取的是记录的最新版本(最新数据,而不是历史版本的数据),读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。
针对当前读(select … for update 等语句),是通过 next-key lock(记录锁+间隙锁)方式解决了幻读,因为当执行 select … for update 语句的时候,会加上 next-key lock,如果有其他事务在 next-key lock 锁范围内插入了一条记录,那么这个插入语句就会被阻塞,无法成功插入,所以就很好了避免幻读问题。

Read View

**在MVCC机制中,多个事务对同一个行记录进行更新会产生多个历史快照,这些历史快照保存在Undo Log里。**如果一个事务想要查询这个行记录,需要读取哪个版本的行记录呢?这时就需要用到ReadView了,它解决了行的可见性问题

ReadView就是事务在使用MVCC机制进行快照读操作时产生的读视图。当事务启动时,会生成数据库系统当前的一个快照,InnoDB为每个事务构造了一个数组,用来记录并维护系统当前活跃事务的ID(“活跃"指的就是,启动了但还没提交)

3、MyISAM和InnoDB的区别

在这里插入图片描述

4、悲观锁和乐观锁的怎么实现

在这里插入图片描述

5、聚簇索引与非聚簇索引区别

在这里插入图片描述

6、什么情况下mysql会索引失效

在这里插入图片描述

7、B+tree 与 B-tree区别

原理
分批次的将磁盘块加载进内存中进行检索,若查到数据,则直接返回,若查不到,则释放内存,并重新加载同等数据量的索引进内存,重新遍历
从结构上看,B+Tree 相较于 B-Tree 而言 缺少了指向数据的指针
在这里插入图片描述
B+树了解嘛?
在B+树上增加了顺序访问指针,也就是每个叶子节点增加一个指向相邻叶子节点的指针,这样一棵树成了数据库系统实现索引的首选数据结构。
MySQL 默认的存储引擎 InnoDB 采用的是 B+ 作为索引的数据结构,原因有:(为什么使用这种结构)
1、B+ 树的非叶子节点不存放实际的记录数据,仅存放索引,因此数据量相同的情况下,相比存储即存索引又存记录的 B 树,B+树的非叶子节点可以存放更多的索引,因此 B+ 树可以比 B 树更「矮胖」,查询底层节点的磁盘 I/O次数会更少。
2、B+ 树有大量的冗余节点(所有非叶子节点都是冗余索引),这些冗余索引让 B+ 树在插入、删除的效率都更高,比如删除根节点的时候,不会像 B 树那样会发生复杂的树的变化;

8、SQL性能分析:

在这里插入图片描述
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
注:where条件只需存在,与位置无关
在这里插入图片描述
** 注:使用联合索引时,尽可能使用>=,其右侧索引不会失效**

以MySQL为例Linux下如何排查问题

类似提问方式:如果线上环境出现问题比如网站卡顿重则瘫痪 如何是好?
以mysql为例
1,架构层面 是否使用主从
2,表结构层面 是否满足常规的表设计规范(大量冗余字段会导致查询会变得很复杂)
3,sql语句层面前提:由于慢查询日志记录默认是关闭的,所以开启数据库mysql的慢查询记录 的功能 从慢查询日志中去获取哪些sql语句时慢查询 默认10S ,从中获取到sql语句进行分析
3.1 explain 分析一条sql

索引设计(联合索引)

尽量覆盖要查询的字段,防止回表查询

9、如何处理慢查询

慢查询的优化首先要搞明白慢的原因是什么?
是查询条件没有命中索引?
是加载了不需要的数据列?
还是数据量太大?

10、数据库分表操作

如何分库分表看这篇很清晰
在这里插入图片描述

11、MySQL优化

在这里插入图片描述

12、SQL语句优化案例

在这里插入图片描述

13、有没有设计过数据表?你是如何设计的

在这里插入图片描述
第一范式主要是保证数据表中的每一个字段的值必须具有原子性,也就是数据表中的每个字段的值是不可再拆分的最小数据单元
第二范式要求在满足第一范式的基础上,非主属性必须完全依赖于候选字,也就是要消除部分依赖。(完全依赖,部分依赖)
第三范式要求在满足第二范式的基础上,任何非主属性不依赖于其他非主属性,即在第二范式的基础上消除传递依赖。第三范式要求数据表的每一列都与主键直接相关,而不是间接相关。

Redis

1、介绍下Redis Redis有哪些数据类型?

Redis全称(Remote Dictionary Server):本质上是一个Key-Value类型的内存数据库,整个数据库统统加载在内存当中进行操作,定期通过异步操作把数据库数据flush到硬盘上进行保存。因为是纯内存操作,Redis的性能非常出色,每秒可以处理超过 10万次读写操作,是已知性能最快的Key-Value DB。

1、String(k-v)
使用场景:常用于缓存用户信息,因为字符串的操作是原子的,可以避免多线程同时修改同一个用户信息导致数据不一致的问题。
2、list: 可以重复的集合
使用场景:可以用来作为消息队列,生产者消费者模型,生产者将任务放入列表,消费者从列表取出任务执行。
3、set:不可以重复的集合
使用场景:可以用来实现关注、好友功能,因为好友或关注者都是唯一的。
4、hash:类似于Map<String,String>
使用场景:常用于缓存对象信息,如用户的头像、昵称、等级等信息。
5、zset(sorted set):带分数的set
使用场景:可以用来实现排行榜,如游戏的排名,用户的积分排名等。
原理:
○ 压缩列表zipList:若有序集合的元素个数小于 128 个,并且每个元素的值小于 64 字节,Redis 会使用压缩列表作为 Zset 类型的底层数据结构;
○ 跳表skipList:如果有序集合的元素不满足上面的条件(元素较多或字符串元素较长),Redis 会使用跳表作为 Zset 类型的底层数据结构;
■ 跳跃表是基于多指针有序链表实现的,可以看成多个有序链表。
■ 在查找时,从上层指针 最高层开始查找,找到对应的区间之后再找下一层 时间复杂度为O(logn)

Redis的持久化:

RDB:

在指定的时间间隔内,将内存中的数据集快照写入磁盘。
Redis会单独创建(fork)一个子进程进行持久化,会将数据写入到一个临时文件中,待持久化过程结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的。
优点:
适合大规模的数据恢复
对数据的完整性要求不高
缺点:
1、需要一定的时间间隔进行操作,如果redis意外宕机,这个最后一次的修改数据就没有了
2、fork进程时,会占用一定的内存空间

AOF:

以日志的形式来记录每一个写操作,将redis执行过的所有指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之redis重启的话就是根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作
优点:
1、每一次修改都同步,文件的完整性更好
缺点:
1、相对于数据文件来说,aof远远大于rdb,修复的速度也比rdb慢!
2、aof运行效率也要比rdb慢,所以我们redis默认的配置就是redis持久化!

3、Redis为什么快?

1)完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)

2)数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的

3)采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗

4、Redis为什么是单线程的?

因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了Redis利用队列技术将并发访问变为串行访问

1)绝大部分请求是纯粹的内存操作
2)采用单线程,避免了不必要的上下文切换和竞争条件

5、Redis服务器的的内存是多大?

配置文件中设置redis内存的参数:
该参数如果不设置或者设置为0,则redis默认的内存大小为:
32位下默认是3G
64位下不受限制
一般推荐Redis设置内存为最大物理内存的四分之三,也就是0.75
命令行设置config set maxmemory <内存大小,单位字节>,服务器重启失效
config get maxmemory获取当前内存大小
永久则需要设置maxmemory参数,maxmemory是bytes字节类型,注意转换

6、为什么Redis的操作是原子性的,怎么保证原子性的?

对于Redis而言,命令的原子性指的是:一个操作的不可以再分,操作要么执行,要么不执行。
Redis的操作之所以是原子性的,是因为Redis是单线程的。
Redis本身提供的所有API都是原子操作,Redis中的事务其实是要保证批量操作的原子性。
多个命令在并发中也是原子性的吗?
不一定, 将get和set改成单命令操作,incr 。使用Redis的事务,或者使用Redis+Lua==的方式实现.

7、Redis有事务吗?

Redis是有事务的,redis中的事务是一组命令的集合,这组命令要么都执行,要不都不执行,
redis事务的实现,需要用到MULTI(事务的开始)和EXEC(事务的结束)命令 ;
在这里插入图片描述当输入MULTI命令后,服务器返回OK表示事务开始成功,然后依次输入需要在本次事务中执行的所有命令,每次输入一个命令服务器并不会马上执行,而是返回”QUEUED”,这表示命令已经被服务器接受并且暂时保存起来,最后输入EXEC命令后,本次事务中的所有命令才会被依次执行,可以看到最后服务器一次性返回了两个OK,这里返回的结果与发送的命令是按顺序一一对应的,这说明这次事务中的命令全都执行成功了。
Redis的事务除了保证所有命令要不全部执行,要不全部不执行外,还能保证一个事务中的命令依次执行而不被其他命令插入。同时,redis的事务是不支持回滚操作的。

8、Redis数据和MySQL数据库的一致性如何实现?

一、 延时双删策略
在写库前后都进行redis.del(key)操作,并且设定合理的超时时间。
二、设置缓存的过期时间
从理论上来说,给缓存设置过期时间,是保证最终一致性的解决方案。所有的写操作以数据库为准,只要到达缓存过期时间,则后面的读请求自然会从数据库中读取新值然后回填缓存
三、如何写完数据库后,再次删除缓存成功?
上述的方案有一个缺点,那就是操作完数据库后,由于种种原因删除缓存失败,这时,可能就会出现数据不一致的情况。这里,我们需要提供一个保障重试的方案。
在这里插入图片描述

9、缓存击穿,缓存穿透,缓存雪崩的原因和解决方案(或者说使用缓存的过程中有没有遇到什么问题,怎么解决的)?

缓存穿透(缓存查不到)

缓存穿透的概念很简单,用户想要查询一个数据,发现redis内存数据库没有,也就缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。
当用户很多的时候,缓存都没有命中,于是都去请持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。
解决办法:
1、布隆过滤器
布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力;
由二进制向量(位数组)和一系列随机映射函数(哈希函数)组成:
相比我们平时常用的List,Map,Set等数据结构,它占用的空间更少且效率更高,但是缺点是其返回的结果是概率性的,而不是非常的准确,理论情况下添加到集合中的元素越多,误报的可能性就约大,并且存放在布隆过滤器中的数据不容删除。
原理图:
在这里插入图片描述
Bloom Filter会使用一个较大的bit 数组来保存所有的数据,数组中的每个元素都只占用1bit,并且每个元素只能是0或者1(代表false或者true),这也是Bloom Filter节省内存的核心所在。这样来算的话,申请一个100W个元素的位数组只占用1000000Bit/8=125000Byte=125000/1024KB122KB的空间。
具体是这样做的:
把所有可能存在的请求的值都存放在布隆过滤器中,当用户请求过来,先判断用户发来的请求的值是否存在于布降过滤器中。不存在的话,直接返回请求参数错误信息给客户端,存在的话才会走下面的流程。

2、缓存空对象
当存储层不命中后,即使返回空对象也将其缓存起来,同时设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源;

缓存击穿(量太大)

例如微博热搜!
缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中这个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开一个洞。
当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并且回写缓存,会导致数据库压力瞬间增大。
解决办法:
1、设置热点数据永不过期
2、加互斥锁
在这里插入图片描述

缓存雪崩

1、集中过期
2、redis宕机

在这里插入图片描述
解决办法:
在这里插入图片描述

10、哨兵模式是什么样的?

failover:故障转移
在这里插入图片描述

哨兵机制

如果主节点挂了,就选择一个「从节点」切换为「主节点」,实现主从节点故障转移。
哨兵机制是如何工作的?
哨兵其实是一个运行在特殊模式下的 Redis 进程,所以它也是一个节点。
哨兵节点主要负责三件事情:监控、选主、通知。
如何判断主节点真的故障了?(监控)
哨兵会每隔 1 秒给所有主从节点发送 PING 命令,当主从节点收到 PING 命令后,会发送一个响应命令给哨兵,这样就可以判断它们是否在正常运行。
由哪个哨兵进行主从故障转移?(选主)
第一轮投票:判断主节点下线
如果主节点或者从节点没有在规定的时间内响应哨兵的 PING 命令,哨兵就会将它们标记为「主观下线」,当一个哨兵判断主节点为「主观下线」后,就会向其他哨兵发起命令,其他哨兵收到这个命令后,就会根据自身和主节点的网络状况,做出赞成投票或者拒绝投票的响应。
当这个哨兵的赞同票数达到哨兵配置文件中的 quorum 配置项设定的值后,这时主节点就会被该哨兵标记为「客观下线」。
第二轮投票:选出哨兵 leader
需要在哨兵集群中选出一个 leader,让 leader 来执行主从切换。某个哨兵判定主节点客观下线后,首先推选自己为leader,拿到的票数大于等于哨兵配置文件中的 quorum 值,就会成为新的主节点,然后告知集群的其它节点,自己负责主从切换的工作。
由哨兵 leader 进行主从故障转移(通知)
选举出了哨兵 leader 后,就可以进行主从故障转移的过程了,具体如下:
第一步:在已下线主节点(旧主节点)属下的所有「从节点」里面,挑选出一个从节点,并将其转换为主节点,选择的规则:
○ 过滤掉已经离线的从节点;
○ 过滤掉历史网络连接状态不好的从节点;
○ 将剩下的从节点,进行三轮考察:优先级、复制进度、ID 号。在每一轮考察过程中,如果找到了一个胜出的从节点,就将其作为新主节点。
第二步:让已下线主节点属下的所有「从节点」修改复制目标,修改为复制「新主节点」;
第三步:将新主节点的 IP 地址和信息,通过「发布者/订阅者机制」通知给客户端;
第四步:继续监视旧主节点,当这个旧主节点重新上线时,将它设置为新主节点的从节点;

11、Redis常见性能问题和解决方案?

在这里插入图片描述

12、MySQL里有大量数据,如何保证Redis中的数据都是热点数据?

在这里插入图片描述

13、Redis集群方案应该怎么做 都有哪些方案 ?

在这里插入图片描述

14、说说Redis哈希槽的概念?

在这里插入图片描述

15、Redis有哪些适合的场景?

(1)会话缓存(Session Cache)
(2)全页缓存(FPC)
(3)队列
(4)排行榜/计数器
(5)发布/订阅

16、Redis在项目中的应用?

Redis一般来说在项目中有几方面的应用

  1. 作为缓存,将热点数据进行缓存,减少和数据库的交互,提高系统的效率
  2. 作为分布式锁的解决方案,解决缓存击穿等问题
  3. 作为消息队列,使用Redis的发布订阅功能进行消息的发布和订阅

17、redis分布式锁是怎么设计的、除了redis还有什么实现方法?

关于分布式锁

在一个分布式系统中,当一个线程去读取数据并修改的时候,因为读取和更新保存不是一个原子操作,在并发时就很容易遇到并发问题,进而导致数据的不正确。这种场景很常见,比如电商秒杀活动,库存数量的更新就会遇到。如果是单机应用,直接使用本地锁就可以避免。如果是分布式应用,本地锁派不上用场,这时就需要引入分布式锁来解决。
方式:
使用 MySQL:这种方式是通过在数据库中创建一个唯一索引的表,然后通过插入一条数据来获取锁,如果插入成功则获取锁成功,否则获取锁失败。释放锁的操作就是删除这条数据。这种方式的优点是实现简单,缺点是性能较低,因为涉及到数据库的操作。
使用 ZooKeeper:ZooKeeper 提供了一个原生的分布式锁实现。其基本思想是创建一个临时有序节点,然后判断自己是否是所有子节点中序号最小的,如果是则获取锁成功,否则监听比自己序号小的节点,当该节点删除时再次尝试获取锁。这种方式的优点是能够保证公平性,缺点是实现较为复杂。
使用 Redis:这种方式是通过 Redis 的 SETNX 命令来实现的,这个命令可以在键不存在时设置值,如果键已存在则不做任何操作。通过这个原子操作,我们可以实现在多个节点之间的互斥访问。这种方式的优点是性能高,实现简单,缺点是需要处理锁的超时和续期问题。
一、Redis分布式锁概述:
在 Redis 中,我们可以使用 SETNX 命令来实现分布式锁。以下是具体的步骤:
1、加锁:客户端使用 SETNX key value 命令尝试设置一个键,其中 key 是锁的名称,value 是一个唯一标识符(例如 UUID),用于标识加锁的客户端。如果键不存在,SETNX 命令会设置键的值并返回 1,表示加锁成功;如果键已存在,SETNX 命令不会改变键的值并返回 0,表示加锁失败。
2、执行业务操作:客户端在成功获取锁后,可以执行需要保护的业务操作。
3、解锁:客户端在完成业务操作后,需要释放锁以让其他客户端可以获取锁。为了确保只有加锁的客户端可以解锁,客户端需要先获取锁的值(即唯一标识符),然后比较锁的值和自己的唯一标识符是否相同,如果相同则使用 DEL key 命令删除键以释放锁。
二、Redis分布式锁的问题及解决方案:
2.1.锁超时机制
以下是一个基本的 Redis 分布式锁的使用流程:
客户端 A 发送一个 SETNX lock.key 命令,如果返回 1,那么客户端 A 获得锁。
客户端 A 执行完毕后,通过 DEL lock.key 命令释放锁。
然而,这种最基本的锁存在一个问题,那就是如果客户端 A 在执行完毕后,因为某些原因(比如崩溃或网络问题)无法发送 DEL 命令来释放锁,那么其他客户端将永远无法获得锁。为了解决这个问题,我们需要引入锁的超时机制。
2.2、锁续期机制
然而,这种带有超时机制的锁还存在一个问题,那就是如果客户端 A 在锁即将超时时仍在执行,那么锁可能会被其他客户端获得,从而导致多个客户端同时持有锁。为了解决这个问题,我们需要引入锁的续期机制。
在执行过程中,定期通过 EXPIRE lock.key timeout 命令续期锁。

GIT

//一次提交过程
git add ...
git commit -m "描述"
git commit --amend //修改提交
git push origin HEAD:refs/for/oem-beta-dev

//一些git常用命令
git status //状态
git log  //commit 日志
git checkout 分支/commitID //切换分支
git pull //拉取代码
git push origin HEAD:refs/for/ome-beta-dev //推送代码
git merge 分支名 --no-ff // 合并当前分支到指定分支
git reset --hard  commitID  //回退本地commit版本,不影响远程
git commit -m "描述" // 提交修改
git commit --amend //修改提交
git format-patch -1 commitID //提取补丁

Linux

在这里插入图片描述

pwd //显示用户当前所在目录
ls [选项][目录或者文件] //列出该目录下的所有子目录与文件
ls -a //查看当前目录下文件,包括隐藏文件
ls -l //长格式显示文件
ls -lh //以方便阅读的长格式显示
cd  // 改变工作路径
grep //用于查找文件里符合条件的字符串
find //用来在指定目录下查找文件
chmod //控制用户对文件的权限的命令 (777==赋予可读、可写、可执行权限)
ps //用来列出系统中当前正在运行的那些进程,类似于 windows 的任务管理器
kill //用于删除执行中的程序或工作
tail //查看测试项目的日志
netstat //查看端口
date //查看当前系统时间
echo //打印
ping 地址 //检测是否主机连通
makdir //创建空目录
rmdir //删除空目录
touch //新建空文件
rm //删除文件或目录
mv //移动文件或改名
cp // 复制文件或目录
cat //查看目标文件的内容
vim // 编辑文件

操作系统

参考

重点:

用户态和内核态

1、什么是用户态和内核态?

用户态(User Mode) : 用户态运行的进程可以直接读取用户程序的数据,拥有较低的权限。
当应用程序需要执行某些需要特殊权限的操作,例如读写磁盘、网络通信等,就需要向操作系统发起系统调用请求,进入内核态。
内核态(Kernel Mode):内核态运行的进程几乎可以访问计算机的任何资源包括系统的内存空间、设备、驱动程序等不受限制,拥有非常高的权限
当操作系统接收到进程的系统调用请求时,就会从用户态切换到内核态,执行相应的系统调用,并将结果返回给进程,最后再从内核态切换回用户态。

2、为什么要有用户态和内核态?只有一个内核态不行么?

1、CPU中有些指令比较危险
2、如果计算机系统中只有一个内核态,那么所有程序或进程都必须共享系统资源,例如内存、CPU、硬盘等

进程和线程

在这里插入图片描述
进程是资源分配的最小单位,线程是资源调度的最小单位

什么是上下文切换?(了解)
上下文切换指的是操作系统停止当前运行进程(从运行状态改变成其它状态)并且调度其它进程(就绪态转变成运行状态)。操作系统必须在切换之前存储许多部分的进程上下文,必须能够在之后恢复他们,所以进程不能显示它曾经被暂停过,同时切换上下文这个过程必须快速,因为上下文切换操作是非常频繁的。**那上下文指的是什么呢?**指的是任务所有共享资源的工作现场,每一个共享资源都有一个工作现场,包括用于处理函数调用、局部变量分配以及工作现场保护的栈顶指针,和用于指令执行等功能的各种寄存器

进程间的通信方式有哪些?

管道/匿名管道(Pipes):半双工,数据只能单向流动,必须要有血缘关系,父子进程
有名管道:半双工,数据只能单向流动,支持没有血缘关系的进程之间通信。
**共享内存:**开辟中一块内存,用于各个进程间共享,使得各个进程可以直接读写同一块内存空间
信号
消息队列
信号量
套接字

线程间通信方式?

锁机制:包括互斥锁、条件变量、读写锁
(1)互斥锁提供了以排他方式防止数据结构被并发修改的方法。
  (2)读写锁允许多个线程同时读共享数据,而对写操作是互斥的。
  (3)条件变量可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下
信号量机制
信号机制

死锁

什么是死锁?
两个或两个以上的线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。

产生死锁的4个必要条件?
互斥
占有并等待
非抢占
循环等待

模拟死锁的代码:

public class Test {
    public static void main(String[] args) {

        MyThread tA = new MyThread();
        MyThread tB = new MyThread();

        tA.setName("tA");
        tB.setName("tB");
        tA.start();
        tB.start();

    }
}
public class MyThread extends Thread{
    static Object objA = new Object();
    static Object objB = new Object();

    @Override
    public void run() {
        while(true){
           if ("tA".equals(getName())){
               synchronized (objA){
                   System.out.println("线程A拿到了A锁,准备拿B锁");
                   synchronized (objB){
                       System.out.println("线程A拿到B锁,顺利执行完一轮");
                   }
               }
           }else if ("tB".equals(getName())){
               synchronized (objB){
                   System.out.println("线程B拿到了B锁,准备拿A锁");
                   synchronized (objA){
                       System.out.println("线程B拿到A锁,顺利执行完一轮");
                   }
               }
           }
        }
    }
}

在开发中:
1.要注意加锁顺序,保证每个线程按同样的顺序进行加锁
2.要注意加锁时限,可以针对锁设置一个超时时间

进程的调度算法:

在这里插入图片描述

磁盘调度算法

在这里插入图片描述

linux链接

inode(索引节点)是文件系统中用于存储文件和目录元数据信息的数据结构。
每个文件系统和目录都有唯一的inode,用于标识和管理文件系统中的文件。

硬链接
硬链接通过 inode 节点号建立连接,硬链接和源文件的 inode 节点号相同,两者对文件系统来说是完全平等的(可以看作是互为硬链接,源头是同一份文件),删除其中任何一个对另外一个没有影响,可以通过给文件设置硬链接文件来防止重要文件被误删。

软链接
软链接和源文件的 inode 节点号不同,而是指向一个文件路径。源文件删除后,软链接依然存在,但是指向的是一个无效的文件路径。软连接类似于 Windows 系统中的快捷方式。不同于硬链接,可以对目录或者不存在的文件创建软链接,并且,软链接可以跨越文件系统。

计算机网络

参考

重点

OSI七层模型

在这里插入图片描述

应用层有哪些常见协议

在这里插入图片描述

传输层有哪些常见的协议?

在这里插入图片描述
在这里插入图片描述

网络层有哪些常见的协议?

在这里插入图片描述

HTTP 和 HTTPS 有什么区别?(重要)

在这里插入图片描述
在这里插入图片描述

从输入 URL 到页面展示到底发生了什么?(非常重要)

1、在浏览器中输入指定网页的 URL。
2、浏览器通过 DNS 协议,获取域名对应的 IP 地址。
3、浏览器根据 IP 地址和端口号,向目标服务器发起一个 TCP 连接请求。
4、浏览器在 TCP 连接上,向服务器发送一个 HTTP 请求报文,请求获取网页的内容。
5、服务器收到 HTTP 请求报文后,处理请求,并返回 HTTP 响应报文给浏览器
6、浏览器收到 HTTP 响应报文后,解析响应体中的 HTML 代码,渲染网页的结构和样式,同时根据 HTML 中的其他资源的 URL(如图片、CSS、JS 等),再次发起 HTTP 请求,获取这些资源的内容,直到网页完全加载显示。
7、浏览器在不需要和服务器通信时,可以主动关闭 TCP 连接,或者等待服务器的关闭请求。

WebSocket

WebSocket 是一种双向实时通信协议,而 HTTP 是一种单向通信协议。并且,HTTP 协议下的通信只能由客户端发起,服务器无法主动通知客户端。

常见应用场景
视频弹幕
实时消息推送
实时游戏对战
多用户协同编辑
社交聊天

Cookie 和 Session 有什么区别?

Cookie:
是存储在浏览器端的一小段文本数据,cookie里的内容会随着http请求一起发送到服务器端。
Session:
是存储在服务器端的一组数据,有些网站是采用session机制来验证用户身份的,通常会把session存储在cookie中。
Token:
代表一小段字符串。

Cookie 被禁用怎么办?

最常用的就是利用 URL 重写把 Session ID 直接附加在 URL 路径的后面。

Ping

PING 命令是一种常用的网络诊断工具,经常用来测试网络中主机之间的连通性和网络延迟。

DNS

DNS(Domain Name System)域名管理系统,是当用户使用浏览器访问网址之后,使用的第一个重要协议。DNS 要解决的是域名和 IP 地址的映射问题。
在这里插入图片描述
目前 DNS 的设计采用的是分布式、层次数据库结构,DNS 是应用层协议,它可以在 UDP 或 TCP 协议之上运行,端口为 53 。

TCP 与 UDP 的区别(重要):

是否面向连接:UDP 在传送数据之前不需要先建立连接。而 TCP 提供面向连接的服务,在传送数据之前必须先建立连接,数据传送结束后要释放连接。
是否是可靠传输:远地主机在收到 UDP 报文后,不需要给出任何确认,并且不保证数据不丢失,不保证是否顺序到达。TCP 提供可靠的传输服务,TCP 在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制。通过 TCP 连接传输的数据,无差错、不丢失、不重复、并且按序到达。
是否有状态:这个和上面的“是否可靠传输”相对应。TCP 传输是有状态的,这个有状态说的是 TCP 会去记录自己发送消息的状态比如消息是否发送了、是否被接收了等等。为此 ,TCP 需要维持复杂的连接状态表。而 UDP 是无状态服务,简单来说就是不管发出去之后的事情了(这很渣男!)。
传输效率:由于使用 TCP 进行传输的时候多了连接、确认、重传等机制,所以 TCP 的传输效率要比 UDP 低很多。
传输形式:TCP 是面向字节流的,UDP 是面向报文的。
首部开销:TCP 首部开销(20 ~ 60 字节)比 UDP 首部开销(8 字节)要大。是否提供广播或多播服务:TCP 只支持点对点通信,UDP 支持一对一、一对多、多对一、多对多;
在这里插入图片描述

什么时候选择 TCP,什么时候选 UDP?

在这里插入图片描述
运行于 UDP 协议之上的协议:
HTTP (3.0)协议
DHCP 协议
DNS

TCP三次握手四次挥手:

SYN (synchronize)是请求同步的意思,ACK是确认同步的意思。RST是重置连接。
**SYN_SENT:**请求连接状态
**SYN_RCVD:**状态表示服务器已经接收到客户端发送的SYN连接请求,并正在等待客户端返回ACK确认包,以完成TCP三次握手的过程。
ESTABLISHED: 表示一个TCP连接已经建立并且正在传输数据

三次握手:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

为什么是三次握手?不是两次、四次?

小林coding
TCP 四次挥手过程是怎样的?
FIN-WAIT-1:表示想主动关闭连接
CLOSE-WAIT:表示在等待关闭
FIN-WAIT-2:只要对方发送ACK确认后,主动方就会处于FIN-WAIT-2状态,然后等待对方发送FIN报文
LAST-ACK:被动关闭一方在发送FIN报文后,最后等待对方的ACK报文
TIME-WAIT:表示收到了对方的FIN报文,并发送出了ACK报文,就等 2MSL 后即可进入CLOSED状态了
FIN标志位:用于关闭连接
MSL:报文最大生存时间
在这里插入图片描述
在这里插入图片描述

为什么需要 TIME_WAIT 状态?

在这里插入图片描述
在这里插入图片描述
MSL为报文最大生存时间
在这里插入图片描述

项目(瑞吉外卖)

部署:

部署架构

在这里插入图片描述
在这里插入图片描述

问题:

基于静态ThreadLocal封装了线程隔离的全局上下文对象,便于在请求内部存取用户信息,减少用户远程查询次数?

/**
 * 基于ThreadLocal封装工具类,用于保护和获取当前用户id
 */
public class BaseContext {
    public static ThreadLocal<Long> threadLocal = new ThreadLocal<>();

    public static void setCurrentId(Long id){
        threadLocal.set(id);
    }

    public static Long getCurrentId(){

        return threadLocal.get();
    }
}

在登录检查过滤器中, 进行拦截请求获取userid,并通过threadLocal静态变量设置当前线程的id

ThreadLocal为每一个线程都提供了变量的副本,并且变量在整个线程的生命周期有效,形成了线程与线程之间的隔离,只有同一个线程才能操作变量,是一种”以空间换时间”的形式,可以用来记录一些上下文数据。
ThreadLocal内部通过
Map
来储存每一个线程的变量副本,map的key就是threadLocal,value就是我们set的那个值,每次线程在get的时候,都从自己的变量中取值,所以肯定就不存在线程安全问题。
使用ThreadLocal后,一定要注意手动remove()否则会造成OOM异常。

ThreadLocal是Java中的一个线程级别的变量,它可以在同一线程内共享数据,并且每个线程都有自己独立的副本
ThreadLocal通常被用于在多线程环境下记录和管理线程的上下文信息。

使用ThreadLocal可以解决多线程环境下的上下文传递和数据共享的问题,它提供了以下主要功能:

上下文隔离: 每个线程可以通过ThreadLocal独立存储和访问自己的数据,不同线程之间的数据互相隔离,避免了线程间数据的冲突和竞争条件。
线程安全: ThreadLocal提供了线程级别的数据副本,每个线程都可以独立地操作自己的数据,无需考虑并发访问的同步问题。这样可以简化并发编程的复杂性,并提高程序的性能。
线程上下文传递: 在多线程环境中,可能需要在不同的方法或组件中传递上下文信息,例如用户认证信息、请求跟踪ID等。ThreadLocal可以在同一线程内共享上下文信息,而无需显式传递参数。

Spring Cache注解+ Redis

在这里插入图片描述
开始是通过redis实现的,后面通过SpringCache简化了代码
redis实现新增套餐时,会删除redis数据中原有的缓存套餐数据

整合shiro、jwt和redis来实现前后端权限管理。通过使用shiro来管理用户的身份认证和权限控制,使用jwt来生成和验证token,以及使用redis来存储token,实现安全可靠的权限管理?

shiro作用:
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。
Subject, SecurityManager 和 Realms.
①Subject:即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。但考虑到大多数目的和用途,你可以把它认为是Shiro的“用户”概念。Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。
②SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。
③Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。

jwt作用:
由三部分组成: header(头)、payload(载体)、signature(签名)
随着之后jwt的出现,校验方式更加简单便捷化,无需通过redis缓存,而是直接根据token取出保存的用户信息,以及对token可用性校验,单点登录更为简单
在这里插入图片描述
redis作用:
本项目中使用redis来存储用户数据,避免每次都需要token校验都需要去访问持久层数据库获取用户信息来验证token的正确性。

算法

排序评价指标

在这里插入图片描述

分治算法(归并,快速排序,最大子端和)

分治法产生的子问题,往往是原问题的较小模式,这些问题是相互独立且与原问题相同的
(1)分解
(2)解决
(3)合并

动态规划

(1)最优子结构
(2)重叠子问题

贪心

(1)最优子结构
(2)最优选择的性质

快速排序

    public static int Partition(int A[],int low,int high){
        int pivot = A[low];
        while (low<high){
            while (low<high && A[high] >= pivot){
                high--;
            }
            A[low] =A[high]; // 比枢轴元素小的元素移动到左端
            while (low<high && A[low]<=pivot){
                low++;
            }
            A[high] =A[low]; // 比枢轴元素大的元素移动到右端
        }
        A[low] = pivot; // 枢轴元素放到最终位置
        return low;  // 枢轴元素的最终位置,左边元素小于枢轴元素,右边元素大于枢轴元素

    }

     public static void quickSort(int A[],int low,int high){
        if (low <high){
            int pivotPos = Partition(A,low,high);
            quickSort(A,low,pivotPos-1);
            quickSort(A,pivotPos+1,high);

        }

    }

冒泡排序

public class salution {
    public static void main(String []args) {
        Scanner in = new Scanner(System.in);
        int l = in.nextInt();
        long[] arr = new long[l];
        for (int i = 0; i < l; i++) {
            arr[i] = in.nextInt();
        }
        System.out.println("arr的排序前:\n18  13  50  15  4  17  18 ");
        long temp  = 0 ;
        for(int i = 0 ;i< arr.length -1; i++){
            for(int j = 0; j<arr.length-1-i; j++){
                if(arr[j]>arr[j+1]){
                    temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                }
            }

        }
        System.out.println("arr排序后:");
        for(int i = 0; i<arr.length; i++){
            System.out.print(arr[i]+"\t");
        }
    }
}

二分查找

左闭右开

class Solution {
    public int search(int[] nums, int target) {
        int left = 0, right = nums.length;
        while (left < right) {
            int mid = left + ((right - left) >> 1);
            if (nums[mid] == target)
                return mid;
            else if (nums[mid] < target)
                left = mid + 1;
            else if (nums[mid] > target)
                right = mid;
        }
        return -1;
    }
}

ACM模式

在这里插入图片描述
重点:
在这里插入图片描述

scanner.nextLine(); // 消费掉输入行末的换行符

Java输入建立链表

头插法
        //头插法
        Scanner in = new Scanner(System.in);
        LinkList head = new LinkList();
        int x = in.nextInt();
        while(x!=-1){
            LinkList node = new LinkList(x);
            node.next = head.next;
            head.next = node;
            x = in.nextInt();
        }
        LinkList p = head.next;
        while (p!=null){
            System.out.println(p.val);
            p = p.next;
        }
尾插法
        //尾插法
        Scanner in = new Scanner(System.in);
        LinkList head = new LinkList();
        LinkList tail;
        //尾插法得先输入一个元素
        int x1 = in.nextInt();
        LinkList node1 = new LinkList(x1);
        head.next = node1;
        tail = node1;
        //后面再继续输入
        int x = in.nextInt();
        while(x!=-1){
            LinkList node = new LinkList(x);
            tail.next = node;
            tail = node;
            x = in.nextInt();
        }
        //打印链表
        LinkList p = head.next;
        while (p!=null){
            System.out.print(p.val+" ");
            p = p.next;
        }

Java输入建立二叉树

public class test2 {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int l = in.nextInt();
        int[] arr = new int[l];
        for (int i = 0; i < l; i++) {
            arr[i] = in.nextInt();
        }
        TreeNode treeNode = constructBinaryTree1(arr);
        ans(treeNode);
        
    }
    // 方法用于插入节点
    public static TreeNode constructBinaryTree1(int[] arr){
        List<TreeNode> treeNodeList = arr.length>0?new ArrayList<>(arr.length): null;
        TreeNode root=null;
        for (int i = 0; i < arr.length; i++) {
            TreeNode node =null;
            if(arr[i]!= -1){
                node = new TreeNode(arr[i]);
            }
            treeNodeList.add(node);
            if (i==0){
                root = node;
            }
        }
        for (int i = 0; i*2+1 < arr.length; i++) {
            TreeNode treeNode = treeNodeList.get(i);
            if (treeNode!=null){
                treeNode.left = treeNodeList.get(2*i+1);
                if (2*i+2<arr.length){
                    treeNode.right = treeNodeList.get(2*i+2);
                }
            }

        }
        return root;
    }
    private static void ans(TreeNode root){
        if (root!=null){
            System.out.println(root.val);
            ans(root.left);
            ans(root.right);
        }
    }
}

设计模式

创建型

类:
工厂方法模式
对象:
抽象工厂模式
原型模式
生成器模式(建造者模式)
单例模式

结构型

类:
适配器模式
对象:
桥接模式
代理模式
组合模式
适配器模式
亨元模式
装饰模式
外观模式
桥接代理组合适配器,亨元装饰外观

行为型

类:
解释器模式
模版方法模式
对象:
责任链模式
命令模式
迭代器模式
中介者模式
备忘录模式
观察者模式
状态模式
策略模式
访问者模式

  • 14
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值