Java后台面试知识储备(整理中)
1、java基础
1.1、 封装、继承、多态
1、封装
把一个个的事物变成一个个的类:类中包含成员对象和成员方法。
权限大小顺序;
public(所有类都可以访问)>protected(子类访问权限)>default(包访问权限)>private(类内部访问权限)
2、继承
让子类可以获得父类的属性和方法。
3、多态
一句话描述多态就是:多态可以通过一个指向父类的指针使用子类的方法;
案例
定义一个动物类,一个狗类,一个猫类,狗和猫都去继承动物类,三个类都写了自己的eat()方法。
Animal dog=new Dog();
Animal cat=new Cat();
dog.eat();
cat.eat();
狗吃肉
猫吃鱼
1.2、 核心类源码
Object源码
java体系中每个类默认有一个超类就是这个Object,每个对象都默认实现了Object类中的方法。
Object类中主要的方法有getClass()、equals()、hashcode()、clone()、notify()、notifyAll()、wait()。
getClass方法
public final native Class<?> getClass();
1、该方法使用native去修饰,表示该方法需要jvm自己去调用本地c或者c++已经实现的方法;
2、该方法使用final修饰,表示该方法不可被重写;
3、该方法的主要作用获取当前正在运行的类。
hashCode()方法
public native int hashCode();
1、该方法的作用是返回对象的hash值,主要服务于HashMap等集合;
2、如果两个对象相等,则hashCode的值相等;但如果两个对象的hashCode的值相等,但两个对象不一定相等;
equals()方法
public boolean equals(Object obj) {
return (this == obj);
}
1、该方法可以被重写;
2、该方法用于比较两个独享是否相等;
3、如果两个对象相等,则hashCode的值相等;但如果两个对象的hashCode的值相等,但两个对象不一定相等;
clone()方法
protected native Object clone() throws CloneNotSupportedException;
1、该方法用于对象的复制,当我们使用该方法时,需要实现一个Cloneable()接口;
2、(1)浅拷贝:创建一个对象指针,让它指向原有的对象所在的内存;(2)深拷贝:创建一个新的对象,复制原有对象的内容。Object中clone方法采用的时浅拷贝。
nofity方法、notifyAll()方法、wait()方法
1、wait()方法是让当前线程进入等待状态,释放锁资源;
2、notify()方法的作用是唤醒指定的处于等待状态的线程,notifyAll()是唤醒所有处于等待状态的线程;
3、wait()方法和sleep()方法的区别:wait方法会释放锁资源,sleep方法不会释放锁资源。
1.2、java集合容器
HashMap集合
Hashmap集合类可以把任意长度的输入转化为有限长度的输出。
Hashmap的结构是什么样的
数组+链表+红黑树
为什么需要链表
hash冲突,当key经过hash后得到的值是相同的,比较它们原来的key,如果key相同,replace;key不相同,在当前桶上添加链表。
Hashmap的容量是怎么样的
Hashmap默认是一个散列表,它是懒加载的,只有在第一次put数据的时候,才会创建出这张散列表。散列表的默认大小为2^4=16,默认扩容因子为0.75,也就是当散列表的使用空间达到16乘以0.75=12,散列表扩容到32。
扰动函数
扰动函数的作用是为了优化hash函数,它采用的方法是高8位异或低8位,因为高8位一般是用不到的,通过这种方式可以减少hash冲突。
Hashmap的put流程
主要分为4中情况:
- 当桶为空,无链表时,直接放入key与value;
- 当桶不为空,无链表时,比较原来的key值,当key值相等,替换,key不相等,在桶后插入链表;
- 当桶不为空,有链表时,依次判断每个节点,key值相等,替换。key值不相等,插入节点并判断长度是否大于8,如果大于8,转换为红黑树;
- 当桶不为空,有红黑树,插入节点,根据红黑树的算法左旋右旋。
1.3、java并发编程
1.3.1、并发包常用类实现原理(lock、atomic)
什么是Lock?
Lock是concurent下的类,通过Lock类加锁,可以防止多个线程同时访问到资源。在Lock包出来之前,一直使用的是synchronized关键字。
lock和synchronized的区别是什么?
lock | synchronized |
---|---|
lock需要手动上锁和解锁 | synchronized不需要手动上锁和解锁 |
lock可以判断锁的状态 | synchronized不可以可以判断锁的状态 |
lock是一个类 | synchronized是java的关键字,属于JVM层面的 |
lock用于大量资源需要同步 | synchronized用于少量资源需要同步,因为效率低 |
**AQS(抽象队列同步器)**参考博客https://segmentfault.com/a/1190000015739343
state状态属性
AQS中有一个state的属性,该属性使用volatile修饰。当state=1时,表示该锁已被占用;当state=0时,表示该锁没有被占用;当state>1时,表示该锁已被重入。
private volatile int state;
光知道所被占用了还是不够的,还需要知道占用了锁的是哪一个线程。
exclusiveOwnerThread属性
private transient Thread exclusiveOwnerThread; //继承自AbstractOwnableSynchronizer
exclusiveOwnerThread属性表示当前持有锁的线程。
双端队列
AQS中,保存着一个双向链表,每一个想要获取锁的线程会被封装成一个Node对象放入这个链表中。
上图就是AQS中线程等待队列的模型图,其中头结点为一个哑结点,其中的thread指向的永远为空,因此第一个进入队列的线程会被包装成第二个节点进入等待。
- thread属性代表当前等待的线程为哪一个;
- waitStatus代表该线程目前的状态;
- pre、和next代表前驱和后继指针;
- nextWaiter:永远指向为空,表示处于独占模式。
CAS(比较并交换)
AQS中有大量该方法的使用,CAS(V,E,N)中V代表要修改的变量,E代表期望的值,N代表新的值。如果E和N不相等,会开始自旋。
可重入锁
可重入锁和不可重入锁的区别:
可重入锁当一个线程获得锁后,状态加一,这个线程可以再次进入这个锁,状态再加一。当这个线程释放一次锁,状态减一,只有当这个线程状态为0的时候,别的线程才能够获取这个锁。
不可重入锁当一个线程获取锁资源后,不可再次进入,其他线程等待该线程执行完毕释放锁资源。
1.3.2、常用关键字(synchronized、volatile)
synchronized
synchronized的作用是使多个线程可以共享同一个变量,但同一个时间只能有一个线程调用。synchronized是重量级锁,效率非常的低。
synchronized效率为什么低
java中的线程会映射到操作系统中的线程上,synchronized通过操作系统完成互斥锁,需要对线程挂起和唤醒。而线程之间的切换需要在用户态和内核态之间转换,因此性能消耗非常的高。
java是如何解决synchronized效率低的问题
java在1.6之后加入了自旋锁、偏向锁、轻量级锁,极大地优化了性能。
synchronized有哪些应用场景?
- synchronized void method() { //业务代码 }这种方式就是对当前对象加锁
- synchronized void staic method() { //业务代码 }这种方式是对当前类加锁,因为静态方法是属于类的,不属于任何一个对象。因此,当线程A调用类的静态方法,线程B调用类的非静态方法,这两者之间是不会互斥的。(也就是说,对象锁和类锁相互不影响)。
- synchronized(this) { //业务代码 }这样代表锁住了这个对象,但是synchronized(类.class) { //业务代码 }代表锁住了这个类。
synchronized 的应用场景(双重检测锁可以回答面试题:写一个你认为最好的单例模式)
//双重检测锁
public class Singleton {
private volatile static Singleton singleton;
private Singleton(){};
public Singleton getSingleton(){
if (singleton==null){
synchronized (Singleton.class){
if (singleton==null){
singleton=new Singleton();
}
}
}
return singleton;
}
}
- 第一次检测是为了避免没有必要的锁资源争夺
- 第二次检测是为了防止多个进程抢到锁资源二导致的对象重复创建
- singleton对象设置为volatile主要是为了防止指令重排,因为在new Singleton()的时候其实是分三步执行的(1)在内存中为该对象开辟一块内存;(2)创建singleton的对象指针;(3)将该指针指向新开辟的内存地址。
如果不用volatie会出现什么问题?
如果上面三步的执行顺序变为(1)(3)(2),可能会出现一个线程获得了一个还没有初始化的实例。
sychronized底层原理是什么样的?
主要分为两种情况:
1、 修饰同步代码块,用到了c++底层的两个变量monitorenter和mnitorexit,可以获取monitor对象。wait方法和notify方法也是通过这个对象,这个对象可以控制一个计数器。
2、 修饰成员方法,监控一个标识ACC_SYNCHRONIZED,该标识表示该方法为一个同步方法。
volatile
volatile主要有以下三点特性:
- 保证可见性;
- 不保证原子性;
- 禁止指令重排。
1.3.3、多线程基本概念、共享内存、消息传递
多线程其实就是并发,因为一个进程中可以包含多个线程,这多个线程可以在一段时间内一起执行,因为系统会给每个线程分配时间片。
线程之间是如何通信的?
1、共享内存
共享内存就是指多个线程可以读取内存中同一块公共区域来传递信息,这是隐式通信。
2、消息传递
消息传递就是指多个线程通过发送消息的方式传递信息,这是显式通信。
java中实现多线程的方式有哪些?
1、继承Thread类
public class MyThread extends Thread{
@Override
public void run() {
System.out.println("线程开始啦");
}
public static void main(String[] args) {
for (int i=0;i<4;i++){
new MyThread().start();
}
System.out.println("主线程====");
}
}
2、实现Runnallable接口
class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("MyRunnable方式实现多线程=====");
}
public static void main(String[] args) {
for (int i = 0; i < 4; i++) {
new Thread(new MyRunnable()).start();
}
System.out.println("主线程====");
}
}
3、实现Callable接口
class MyCallable implements Callable{
int i=0;
@Override
public Object call() throws Exception {
System.out.println(Thread.currentThread().getName()+" i的值为"+i);
return i++;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable myCallable=new MyCallable();
for (int i = 0; i < 10; i++) {
FutureTask futureTask=new FutureTask(myCallable);
new Thread(futureTask,"子线程"+i).start();
System.out.println("子线程的返回值"+futureTask.get()+"\n");
}
}
}
2、线程池
下面会有详解~
1.3.4、JMM内存模型(主存和工作内存)、三大特性(原子性、可见性、有序性)
JMM内存模型:分为主存和工作内存,数据放在主存中,当一个线程需要使用数据时,先将数据从主存中拷贝到本地的工作内存中,在线程使用完后再将数据放回主存。
1、原子性
原子性表示是不可分割的,是线程执行的最小单位。
原子性通俗说明
i++这条语句就是非原子性,它分为三步,(1)从内存中取出i;(2)i=i+1;(3)将i放回内存中。
2、可见性
可见性是为了保证多线程情况下当一个线程对数据进行了修改,其他线程应该立刻知道这个修改。
可见性通俗说明
线程A对a++,线程B对a++,如果不保证可见性a=1;
3、有序性
JVM会将代码转化为指令供CPU执行,而JVM是允许指令重排的,使用volatile可以禁止指令重排,保证有序性。
有序性通俗说明
Singleton singleton=new Singleton();
双重检测锁中定义的对象需要用volatile,就是为了保证他的有序性,如果不使用volatile,可能获取到一个没有初始化的对象。
1.3.5、线程池的使用、原理(参考博客https://www.jianshu.com/p/7726c70cdc40)
线程池出现的目的
线程池是池化技术中的一种,由于每次调用线程都需要创建和销毁线程,资源有所浪费。
线程池采用的方法是预先创建好一定数量的线程,当需要使用线程时,直接使用线程池中的线程,用完放回线程池即可。
线程池有什么好处?
- 增强了线程的管理,如果无限制的创建线程会出现OOM异常;
- 加快了请求响应的速度,因为省去了创建线程的过程;
- 减少了资源的损耗,线程使用完会放回到线程池中。
线程池的主要参数有哪些?
- 核心线程数
- 最大线程数
- 非核心线程空闲存活时间
- 非核心线程空闲存活时间的单位
- 任务阻塞队列
- 拒绝策略
线程池的主要流程是什么样的?
如果核心线程池满了,就将任务添加到任务阻塞队列中,当任务队列已满,且任务队列中任务的数量加上核心线程数<最大线程数,就创建一个救急线程处理任务请求。如果已经达到最大线程数,则采用拒绝策略。
1.3.6、锁优化、锁膨胀
1.4、java i/o
1.4.1、BIO、NIO、AIO
1. BIO(同步阻塞)
客户端向服务端发送请求,在服务端处理完请求前,客户端一直处于阻塞状态,不允许做其他事情。
这样的IO模型存在什么问题
很明显,这种客户端来一个请求,服务端创建一个线程去处理,如果请求数量变多了,服务器过载。
BIO模型的代码是怎样实现的?
public class SocketBIO {
public static void main(String[] args) throws IOException {
//创建服务端,设置端口号
ServerSocket server = new ServerSocket(9090);
System.out.println("new ServerSocket(9090)");
while (true){
//接受服务端,这个位置阻塞,等待客户端连接
Socket client = server.accept();
System.out.println("客户端端口号:"+client.getPort());
//每次连接给他开辟一个线程,也是BIO最大的弊端
new Thread(new Runnable() {
@Override
public void run() {
//创建输入流
InputStream in=null;
try {
in = client.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
while (true){
//阻塞,等待传输数据
String dataLine = reader.readLine();
if (dataLine!=null){
System.out.println(dataLine);
}else {
client.close();
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
}
}
2. NIO(同步非阻塞)
NIO模型是对BIO模型的一个改进,因为BIO模型每来一个连接就会开启一个线程,也不管这个线程是否需要使用I/O资源。在NIO模式中,所有的请求首先会来到多路复用器上,多路复用器轮询所有的请求,然后为有I/O需求的请求开启一个线程。
3. AIO(异步非阻塞)
AIO模型是对NIO模型的一个改进,AIO模型需要操作系统首先为请求准备好所需要的资源,然后再通知服务器开启线程去处理请求。
1.4.2、阻塞、非阻塞、同步、异步
1.5、深入理解JVM
1.5.1、垃圾回收机制(可达性分析、垃圾回收算法)
GC(垃圾回收)是如何分类的?
垃圾回收主要分为两类:
1、部分回收:
- 新生代收集
- 老年代收集
- 混合收集
2、整堆回收
对整个java堆和方法区回收。
垃圾回收时如何判断对象是否死亡?
- 引用计数法:给每个对象添加一个引用计数器,每当一个对象被引用,
- 可达性分析(JVM使用的就是可达性分析):GC Roots向下搜索对象的引用链,对没有搜索到的对象打上一次标记。接着看这个对象是否覆盖了finalize方法,如果在finalize方法中还是没有引用对象,就会打上第二次标记,被二次标记的对象就会被回收。
垃圾回收的各种算法?
1. 标记清除法
对不用回收的对象打上标记,回收没有打上标记的对象。
这样的垃圾回收算法存在什么样的问题?
- 效率低下;
- 会产生很多的不连续碎片。
2. 复制算法
内存会被分为大小相等的快,当一半被回收完,剩余的对象复制到另一半。
3. 标记整理法
前面的步骤和标记清除一样,将存活下来的对象向一端移动。
4. 分代收集算法
根据各个代的特点使用最适合的垃圾回收算法。
新生代垃圾回收会有大量对象死亡,使用复制算法;老年代对象存活几率高,使用标记清除或者标记整理法。
常见的垃圾收集器有哪些?
1. Serial收集器
单线程垃圾收集器,进行垃圾回收的时候暂停其他的工作线程。
新生代用复制算法,老年代标记-整理算法。
2. ParNew收集器
多线程版本的Serial收集器。
新生代用复制算法,老年代标记-整理算法
3. Parallel Scavenge收集器
重点关注:吞吐量
4. Serial Old收集器
Serial收集器老年代版本
5. Parallel Old收集器
Parallel Scavenge收集器老年代版本。
6. CMS收集器
重点关注:线程停顿时间
7. G1收集器
以极高的概率满足线程停顿时间的要求,还具备高吞吐量。
Full GC的触发条件
- System.gc建议JVM进行Full GC;
- 老年代空间不足,执行Full GC。如果Full GC后空间仍然不足,则会报出内存溢出异常;
- 永久代空间不足;
- gc担保失败:发生minor gc时,老年代最大连续空间小于平均晋升空间。
1.5.2、内存分配策略(堆、方法区、虚拟机栈、本地方法栈、程序计数器)
java内存结构主要分为两块:内存共享区(多个线程共享堆、方法区)、内存独占区(每个线程私有虚拟机栈、本地方法栈、程序计数器)。
java1.8之后内存分配有什么变化?
1.8之后方法区取消了,变成元空间,放入了直接内存中。
1、堆
- java1.7之前所有对象的内存都是在堆分配内存的,后来开启了逃逸分析(如果一个对象未被返回或者被外面调用),对象可以在栈中分配内存;
- java1.7将堆划分为新生代(新生代分为Eden、From Survival、To Survial,它们的比例为8:1:1)、老年代、永久代(方法区);
- jdk1.8将方法区变为元空间放入了直接内存。
对象在堆中的流程是怎样的?
一个对象首先会在Eden区中创建, 当进行一次新生代垃圾回收,对象年龄+1,当对象的年龄达到15, 对象被转移到老年代。
2、方法区
方法区是线程共享的,它记录着类的信息,常量和静态变量。
方法区和永久代是什么关系?
方法区只在Hotspot中才有,方法区是逻辑上的概念,方法区是对永久代的一个理论实现。
字符串常量池
运行时常量池逻辑包含字符串常量池位于方法区中。
字符串常量池在java的各个版本有什么变化?
- java1.7以前字符串常量池在方法区中;
- java1.7时字符串常量池转移到了堆中,其他的不动;
- jdk1.8时,方法区取消,字符串常量池依然待在堆中,其他的变为元空间到了直接内存中
jdk1.8为什么要将方法区变为元空间,放入直接内存中?
- 方法区受到JVM设置的限制,容易发生溢出;
- 元空间存放类的元数据,不受参数的影响,可以加载更多的类;
- 只有Hotspot有方法区,合并代码的时候,需要单独为它留下一片内存空间。
3、虚拟机栈
每执行一个方法会创建一个栈帧,当一个方法执行完毕,栈帧弹出。
java虚拟机栈会出现什么异常?
- 栈溢出异常:当一个线程创建的栈帧超过JVM允许的栈的最大深度,就会出现此异常;
- 内存溢出异常:当JVM的堆中没有足够的内存,去垃圾回收无法获得足够的内存,出现此异常。
4、本地方法栈
虚拟机栈是执行java方法,本地方法栈执行本地的Native方法,它的结构和虚拟机栈是相同的。
5、程序计数器
定义:程序计数器占用一小块的内存,用于记录当前字节码执行到的行号
程序计数器有什么作用?
主要有以下两点作用:
- 控制代码的执行顺序;
- 多线程线程切换,记录下切换前线程执行到的位置。
程序计数器是否会内存溢出?
不会,内存中唯一不会溢出的区域。
程序计数器的生命周期?
程序计数器的生命周期从线程创建直到线程。
1.5.3、类加载机制(加载、验证、准备、解析、初始化)
1、加载
将字节码文件转变为二进制流,加载到内存中去。
2、验证
验证二进制流是否符合JVM的规范,用于安全验证。
3、准备
为静态变量赋上默认的值,如0,null。
public String a=“a”;(1)
public static b=“b”;(2)
public static final c=“c”;(3)
上面这个案例中(1)在准备阶段不会赋值,(2)在准备阶段会赋值为null,(3)为字符串常量,与类变量不同,因此会被赋值为“c”;
4、解析
将常量池中的符号引用(在编译期间,不知道类的具体位置,用符号代替)转化为直接引用(指向实际的内存地址)。
5、初始化
准备阶段是赋上默认的值,初始化阶段是赋上期望的值。
类加载器有哪些种类?
- 启动类加载器
- 拓展类加载器
- 应用程序类加载器
- 自定义类加载器
双亲委派模式是什么?
双亲委派模式简单来讲,就是一个类的在加载的时候会委托给上层的类加载器。
不能理解,通过一个案例来讲解
如果我们自定义一个Object类,那如果我们创建一个Object对象时类是如何加载的。实际上会一直向上委托,一直到启动类加载器,发现已经存在Object类,然后就不会加载自定义的Object类。
1.5.4、常用的调优方法(堆内存使用、GC情况、线程堆栈)
1.6、java框架
1.6.1、Spring框架
spring的核心:IOC和AOP
什么是IOC?
IOC就是控制反转,顾名思义就是把对象的创建和调用交给spring容器。
为什么要使用IOC?
减少代码之间的耦合,每当你的需求变更的时候,你就需要对你的代码进行修改,但是使用IOC只需要修改XML文件即可。
IOC的底层原理使用了哪些技术?
工厂模式+XML解析+反射
原始调用对象的方式?
//原始方式
class UserService{
public void execute(){
UserDao userDao=new UserDao();
userDao.add();
}
}
class UserDao{
public void add(){
}
}
使用工厂的方式创建对象?
//工厂模式
class UserFactory{
public static UserDao getDao(){
return new UserDao();
}
}
class UserService1{
public void execute(){
UserDao userDao = UserFactory.getDao();
userDao.add();
}
}
IOC底层的执行流程
- 第一步,在XML文件中配置我需要创建的对象,设置好对象的属性;
- 第二步,通过XML文件解析获取到配置对象的class值;
- 第三步,通过forClass方法加载类,在通过newInstance方法创建对象。
Spring提供了两种IOC容器实现的方式BeanFactory和ApplicationContext。
BeanFactory和ApplicationContext有什么区别?
- ApplicationContext是BeanFactory的子接口,因此它具有更多的功能。
- BeanFactory只有在获取对象的时候才会创建对象,ApplicationContext在加载配置文件的时候就创建好了对象。
AOP原理
手写动态代理
public interface UserService01 {
public void say();
public static void main(String[] args) {
JdkProxy jdkProxy=new JdkProxy();
UserService01 userService= (UserService01) jdkProxy.getProxy(new UserService01Impl());
userService.say();
}
}
class UserService01Impl implements UserService01{
@Override
public void say() {
System.out.println("Hello World");
}
}
class JdkProxy implements InvocationHandler {
private Object object;
public JdkProxy(){}
public Object getProxy(Object object){
this.object=object;
return Proxy.newProxyInstance(getClass().getClassLoader(),object.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理执行前");
Object result = method.invoke(object, args);
System.out.println("代理执行后");
return result;
}
}
spring容器初始化bean的过程
spring事务
事务的传播性(参考博客https://blog.csdn.net/gao2175/article/details/107914689)
事务传播的7大特性
1、无事务
2、@Transactional(propagation=PROPAGATION_REQUIRED)
存在事务,加入事务,不存在,创建事务
3、@Transactional(propagation=Propagation.SUPPORTS)
调用方有事务,事务运行,没有就非事务
4、mandatory(强制性的)
没有事务就报错,不能创建事务,所以用required
5、propagation_ required _new
不管有没有事务,创建新事务执行
6、propagation_ not _support
有事务就挂起,以非实物执行
7、propagation_never
有事务,就出异常
事务的隔离性
1.6.1、MyBatis框架
MyBatis是一款优秀的持久层框架,他可以将java对象映射成MySql中的数据。
MyBatis常问的面试题
#{}和${}的区别
这两者的区别主要分为以下三点:
- #{}是预编译处理,${}是简单的字符串替换;
- #Mybatis将#{}中的参数替换成?,然后使用PreparedStatement的参数设置;
- #{}可以防止sql注入,更加的安全。
Mybatis中Dao的底层原理,Dao中的方法是否可以重载?
每个Dao会对应一个xml文件,xml文件中会有namespace(对应dao的全类名)、id(对应Dao类中的方法名),将两者结合可以得到key,根据这个key可以找MappedStatement(四种类型:select、delete、update、add)。
Dao中的方法不可以重写,因为程序是根据namespace+id组成的key去查询MappedStatement的,如果重载就找不到了。
Dao接口的底层原理是动态代理,代理对象proxy拦截接口的方法,去执行 MappedStatement中的sql,返回执行结果。
常见的分页方式有哪些?
- sql分页;
- 拦截器分页;
- RowBounds;
- 数组分类。
Mybatis是怎么实现分页的,分页原理是什么?
mybatis使用的是RowBounds,它针对结果集ResultSet采用的是逻辑分页,它的底层原理是拦截sql语句,对sql语句进行修改,例如select *from student;变为select ._ from (select *from student)s limit (0,10);
1.6.1、SpringSecurity框架
什么是授权?
定义:根据权拥有的限访问对应的资源。
权限管理模型是什么样的?
模型分三层:主体、权限、资源
三层关系图:
1. RBAC权限管理模型
1. 基于角色的权限控制(Role-based access conroller)
if(主体.hasRole(“总经理id”)){
查询工资
}
2. 基于资源的权限控制
if(主体.hasPermission(“”查询工资权限标识)){
查询工资
}
2. 基于属性的权限验证(ABAC: Attribute-Based Access Control)
属性分为四类:
用户:用户年龄、地址
环境:当前时间
操作:增、删、改、查
对象:资源属性
案例:允许班主任在上课时间进出校门
用户:班主任;环境:上课时间;操作:进出;对象:校门
认证和授权有什么区别?
1、认证是验证你的账号和密码是否符合要求
2、授权是管理哪些用户可以访问哪些资源
Cookie
cookie是保存在客户端的,用于存储用户的信息和记录用户的行为。
Cookie可以用于哪些应用场景?
1、 比如保存账号密码、设置信息啥的;
2、 还可以保存session和token;
3、 记录和分析用户的行为:比如用户在哪个商品停留。
Cookie和Session有什么区别,如何使用session进行身份验证
session是服务器端用于记录用户的状态,应用场景:购物车,用session记录哪个用户选择哪件商品。
Cookie是存储在客户端,记录用户信息和行为,没session安全
session是如何进行身份验证的?
SessionID存放在Redis数据库中,登录成功,返回给客户端带有SessionID的cookie,客户端请求服务器端的时候,带上SessionID,就能验证用户的身份。
主要步骤:
1、 用户输入账号和密码
2、 服务端创建session,将session的信息存入数据库
3、 服务端返回SessionID,存入用户Cookie
4、 用户用Cookie+SessionID登录
5、 服务器端用SessionID和数据库的session中的信息对比,进行验证
注意点:
1、 使用seeion的关键业务注意开启cookie
2、 注意session的过期时间
如果禁用了cookie,session还能用吗?
如果你方案使用的session保存sessionID,就不能
但是你可以吧sessionID通过url传输,但是安全性会小很多
为什么Cookie无法防止CSRF攻击,但token可以?
CSRF(跨站请求伪造)通过让用户点击链接获取用户cookie中的sessionID向服务端发起请求。tocken因为在用户登录成功后,在每个向服务器的请求中后面跟上tocken令牌,而攻击者是无法获得令牌的。注意:cookie和token都无法阻止XSS(跨站脚本攻击)攻击。
session存在什么问题?
1、不安全,无法防止SCRF攻击
2、不适合移动端,因为需要cookie
3、需要服务端可用性保证,比如你用redis保存,就需要保证服务器redis不挂掉
JWT(Json Web Token)
主要由以下三部分组成:
1、Header:签名算法+token类型
2、payLoad(负载):里面放置和需要传递的信息
3、signature(签名):使用header+payload+secret(秘钥)生成一个签名
主要步骤(4步):
1、 用户成功登陆服务器
2、 服务器端为该用户生成jwt:其中包含了该用户的信息
3、 用户在每个请求中加上jwt
4、 服务器端在请求Header的Authorization字段中找到用户的信息比对
1.6.1、Redis框架
Redis数据库有什么特点?
- C语言写的,内存数据库,读写贼快;
- Redis可以用来做分布式锁、消息队列。
redis和memcached的区别:
1、redis做分布式缓存(问题:单机缓存容量受限,分布式两个服务可以共用缓存)更好
2、redis数据类型更丰富,有list、set、zset、hash
3、支持持久化,aof和rdb
4、有灾难恢复机制
5、单线程,多路IO复用
用redis缓存访问数据有什么好处?
高并发,mysql的QPS(每秒访问次数)是1w,redis可以10w到30w;
Redis有哪些数据类型?
String
//添加数据
set k1 v1
//获取数据
get k1
//批量插入获取数据
mset k1 v1 k2 v2
mget k1 k2
//设置数据过期
expire key 60
List
结构:双向链表
//左压,左弹出,右压,右弹出
lpush、lpop、rpush、rpop
Hash
数组+链表,类似于HashMap
//增加,删除
hset、hget
set
定义:类似于HashSet,去重无序集合
set类型有什么特别的方法?
1、 判断一个元素是否在某个集合;
2、 求交集、并集
应用场景:将一个用户所有关注、所有粉丝分别放入集合,方便共同好友、共同粉丝
sorted set
定义:和set相比,增加权重score,根据score排序
应用场景:礼物排行
I/O多路复用模型是什么样的
- 多个socket连接
- I/O多路复用程序
- 文件事件分派器(把每个socket连接到对应的事件处理器)
- 事件处理器
为什么Redis不采用多线程的方式去实现
1、 单线程容易维护
2、 redis性能瓶颈不在cpu,而在内存和网络
3、 多线程就要处理死锁、上下文切换,性能消耗
Redis热点过期时间是什么,有什么作用?
防止内存溢出,一般只会给热点数据永不过期
应用场景:短信验证码,1分钟过期
Redis是如何判断数据过期的?
采用了过期字典:字典的key指向redis数据库中某个key,value为long long类型的时间
Redis过期数据删除策略是什么样的?
惰性删除:取到过期==》删除,对cpu友好
定期删除:定期取出一批删除过期,对内存友好
redis采用定期+惰性
问题:两种方式都漏掉的数据堆积
Redis内存淘汰机制有哪些?
1、lru:最近最少使用
2、ttl:即将要过期的
3、random:随机淘汰
4、key-lru:最近最少使用的key
5、eviction:禁止写入数据
Redis持久化机制是什么样的?
RDB
数据库当前数据快照
AOF
数据库操作记录
Redis事务
ACID
注意:redis不支持回滚,因此没有原子性
缓存穿透
缓存中没有,请求全部到达数据库
解决方法:
1、缓存无效key:写一个到缓存中
2、布隆过滤器:把有效数据存进去,里面没有的,返回参数异常
缓存雪崩
缓存失效,大量请求到达数据库
解决方案:
1、 redis集群,防止单点故障
2、 限流
缓存和数据库一致性
解决问题:更新数据库,删除缓存失败
1、 缓存失效时间设置短一点
2、 删除失败,多次重试
2、算法部分
2.1、链表、队列、二叉树、栈等数据结构,时间和空间复杂度
2.2、各种排序算法的时间空间复杂度
2.3、查找算法原理(二分查找)
2.4、其他常用算法的思想(分治法、动态规划、回溯法、剪枝法、贪心算法)
3、计算机网络
3.1、七层协议、子网划分,了解tcp/ip协议栈
ISO七层协议有哪几层?
应用层、会话层、表示层、传输层、网络层、数据链路层、物理层。
TCP/IP四层协议分别是哪几层?
应用层、传输层、网络层、网络接口层。
3.2、应用层中http、https协议
Http1.0协议和Http1.1协议有什么区别?
主要有以下4点的不同:
- Http1.0协议采用的是短连接(短连接指一次请求后,连接断开。如果需要再次请求,需要再次建立连接。),Http1.1协议采用的是长连接(长连接在一次请求后会一直保持连接,直到全部请求完毕才会断开连接),长连接又分为2种模式(1)流水线模式;(2)非流水线模式(非流水线模式下,客户端只有在接收到Http响应报文后才会发送下一次请求);
- 增加了26个错误状态码,比如410(服务器资源永久删除);
- Http1.1添加了许多缓存控制策略。
- 网络传输优化:Http1.1允许传输部分对象,而在Http1.0时,必须要传输完整的对象。
Http协议和Https协议的区别?
- Http的端口号是80,Https的端口号为443;
- Https是加密传输协议,传输的内容采用的是对称加密(秘钥只有一个,加密和解密使用同一个秘钥),加密的秘钥在服务端采用非对称加密(秘钥是成对出现的,公钥私钥无法相互推测)。
3.3、传输层TCP、UDP协议
TCP协议和UDP协议的区别?
主要有以下4点:
- TCP是面向连接的,UDP是面向无连接的;
- TCP传输效率低,UDP传输效率高;
- TCP是可靠的,UDP不可靠;
- TCP是以字节流传输的,UDP是以数据报传输的。
3.4、DNS解析过程
3.5、计算机网络常问面试题
URI和URL的区别?
URI:统一资源标识符,定位某一种资源,相当于一个人的身份证。
URL:统一资源定位符,标识某一种资源,相当于一个人的家庭地址。
浏览器输入URL到浏览器显示页面的过程?
该过程主要分为6步:
- 通过DNS解析将域名转化为ip地址;
- 建立TCP连接;
- 发送Http请求;
- 服务器处理请求返回Http报文;
- 浏览器渲染界面;
- 结束连接。
4、操作系统
什么是操作系统?(操作系统有什么用?)
- 操作系统是用来管理系统的硬件资源和软件资源的;
- 操作系统的核心是内核,它主要用于系统的管理。主要分为以下四部分的管理:(1)应用程序;(2)文件系统;(3)内存管理;(4)硬件管理;
- 操作系统屏蔽了底层硬件的复杂性。
4.1、换页算法(OPT、FIFO、LRU)
4.2、进程和线程
进程是资源分配的最小单位,线程是CPU调度的最小单位。
进程和线程有什么区别?
- 一个进程包含多个线程;
- 进程之间很难进行数据交换,但是统一进程下的线程很容易资源共享;
- 进程之间相互是不会影响的,但是一个线程挂掉了他所在的进程也会挂掉。
4.3、死锁的防治和避免
死锁产生的原因
线程A获得一部分资源,但是它还需要另一个线程B的资源;线程B获得一部分资源,但是它还需要另一个线程A的资源。两个线程都无法推进,从而形成了死锁。
死锁产生的必要条件(必须满足所有条件,缺一不可)
- 互斥条件:资源只能一个进程拥有,不能共享;
- 不可剥夺条件:资源在一个进程用完后,别的进程拿不走;
- 请求和保持条件:进程要拥有一部分资源,还需要请求一部分资源;
- 循环等待:请求之间要形成一个闭环。
预防死锁的四种方式
- 破坏互斥条件:使用spooling技术;
- 破坏不可剥夺条件:当一个进程请求资源失败,释放拥有的资源;
- 破坏请求和保持条件:一开始就让一个进程获取所有的资源;
- 破坏循环等待:给每个进程一个执行顺序。
避免死锁的方式(银行家算法)
银行家算法的场景
银行家算法的核心是判断每次分配资源后,系统是否会进入不安全状态(所谓的不安全状态就是系统剩下的资源无法满足任何一个进程的需求)。
4.4、虚拟内存
4.5、操作系统常问面试题
除了上面需要掌握的知识点,操作系统还经常会问到如下的面试题。
什么是系统调用?
我们调用一般的应用程序都是用户态,只有调用内核态的资源才是系统调用。
那么,什么是用户态和内核态?
用户态:处于用户态的进程可以调用用户程序所有的资源;
内核态:处于内核态的进程可以调用系统几乎所有的资源。
系统调用有哪几种类别?
- 进程控制;
- 进程通信;
- 内存管理
- 设备管理
- 文件管理。
进程调度的5中算法是什么?
- 先来先服务:先进入队列的进程先会被调用;
- 短作业优先:执行时间短的进程先会被执行;
- 优先级调度:优先级高的线程先会被执行;
- 时间片轮转:系统根据每个进程被分配的时间片调用进程;
- 多级反馈队列调度:同时采用优先级调度和短作业优先(这是目前公认的最好的调度算法,UNIX系统就是使用的这种调度算法)
5、数据库
5.1、数据库三大范式(参考博客https://www.cnblogs.com/wsg25/p/9615100.html)
三大范式有什么作用?
防止信息的重复导致增删改查发生错误。
1、第一范式
第一范式要求每一个列都是不可分割的,上图中家庭信息和学校信息都是可以分割的。
2、第二范式
第二范式要求在第一范式的基础上,所有的普通列都得依赖于主键,上图中的订单号和产品号各自管理着一些属性。改成如下图就好了。
3、第三范式
第三范式要在第二范式的基础上消除传递依赖,班主任姓名依赖于学号,班主任其他属性依赖于版这人姓名,应该修改为如下图。
5.2、触发器、存储过程
1. 触发器
对特定的数据进行增删改是触发,执行触发器中的语句。
触发器的应用场景有哪些?
1. 数据的安全性检查
//禁止在工作时间插入员工
create or replace trigger security_trigger
before insert on emp
begin
if 条件一 or 条件2
操作
end if
end
2. 数据的验证
//涨工资后的工资要高于原有的工资
create or replace trigger check_salary
before update on emp for each row
begin
if 条件一 or 条件2
操作
end if
end
5.3、数据库索引使用场景,B树、B+树概念
B树
- 索引不重复,从左向右是递增的;
- 索引和数据放在了一起;
- 叶子结点都指向null。
B+树
- B+树的非叶子结点只存放索引,这样它可以存放更多的索引;
- 叶子结点添加了指针,可以横向查找;
- 叶子结点默认大小16KB。
5.4、事务的特性,事务的隔离级别,对应的锁
ACID(原子性、一致性、隔离性、持久性)
原子性:事务是不可分割的最小单位;
一致性:事务执行前后处于一致性状态,A给B转账,A减少500块,B必须要增加500块,不然不满足一致性;
隔离性:多个并发事务之间相互不影响的;
持久性:事务一旦被提交,对数据库中的数据修改是永久的。
脏读
事务一读取了事务二更新但是没有提交的数据。
幻读
事务一读取数据,事务二插入数据,事务二多读了几行。
不可重复读
事务一读取数据,事务二把数据更新,事务一再次读取数据,两次数据不一样。
事务的隔离级别有哪几种?
- 可重复读
- 读已提交
- 读未提交
- 串行读
5.5、熟悉sql语句,联表查询优缺点
5.6、sql优化手段
1. 数据库中有哪些锁?
根据数据的操作类型锁可以分为读锁和写锁
读锁
读锁是共享锁,允许多个线程读取同一份数据。
写锁
写锁是排他锁,同一时间只允许一个线程进行写数据。
根据锁的细粒度锁可以分为行锁和表锁
行锁
行锁只对特定的行上锁,因此行锁可以支持事务。
数据库引擎Innodb使用的是行锁。
表锁
当一个线程操作表中的数据时,对整张表进行上锁,其他线程无法对表进行修改,很明显表锁的并发度非常的低。
比如数据库引擎MyIsam使用的是表锁。
间隙锁
间隙锁的作用是让某个处于间隙的数据被锁定,不会发生变化。
间隙锁有什么危害?
间隙锁可能会锁定一个根部不存在的键值,导致无法对间隙进行操作。