面试习题
JAVA
一、JAVA简介
1.特性:跨平台、可移植、面向对象、多线程
2.JVM:java虚拟机,java跨平台的核心,只能识别字节码,将.java文件编译为.class文件
3.JRE:java运行时的环境,JVM+类库函数+java运行时所需的文件
4.JDK:java开发工具包,java开发的核心,JRE+编译器和调试器等程序调试二、面向对象的三大特征
一、JAVA简介
1.特性:跨平台、可移植、面向对象、多线程
2.JVM:java虚拟机,java跨平台的核心,只能识别字节码,将.java文件编译为.class文件
3.JRE:java运行时的环境,JVM+类库函数+java运行时所需的文件
4.JDK:java开发工具包,java开发的核心,JRE+编译器和调试器等程序调试二、面向对象的三大特征
1. 封装:将数据和操作数据的方法包装在一起,形成一个“类”的概念。封装的目的是隐藏内部实现细节,使得外部无法直接访问和修改对象的内部状态,只能通过对外接口来操作
2. 继承:子类继承父类的和行为,使得子类对象(实例)具有父类的实例域和方法
3. 多态:允许一个变量引用不同类型的对象,以实现同一个方法的不同行为(父类引用指向子类对象(向上转型,安全))
(1)多态有两种形式:编译时多态(静态多态)和运行时多态(动态多态)
①静态多态:即方法重载,在编译时根据方法的参数类型和个数来决定要调用的方法,常用于构造器的重载
1)方法重载:在同一个类及其子类中可以定义多个具有相同名称但参数列表不同的方法
a.方法名称相同、参数列表不同(参数类型、数量、顺序必须有一个不同
b.修饰符、返回类型可不同(除构造方法)、抛出异常可不同
c.可重载静态方法
① 运行多态:即方法重写,是在运行时根据对象的实际类型来决定要调用的方法(反射)
1)方法重写:发生在继承关系中,指子类对父类中同名、参数列表相同的方法进行重新实现
a.方法名称、参数列表和返回类型与父类相同
b.修饰符不能比父类更严格、不能抛出比父类更广泛的异常
c.父类的抽象方法要么被重写,要么被子类也定义为抽象类
d.子类可以通过super关键字调用父类被重写的方法
e.不可重写静态方法
4. 构造器
(1)用来初始化成员属性、方法,对象一旦创建就运行,且只运行一次
(2)函数名与类名一致,无返回值与返回类型,故也无return语句
(3)当一个类没有定义时,系统会默认给该类添加一个不可见的无参构造函数
(4)当一个类定义了有参构造函数后,系统不会再给该类默认添加无参构造函数
(5)构造方法不能被重写,但可在一个类或在其子类中重载
(6)在子类的构造函数中,通过super关键字调用父类的构造函数,因为构造函数的调用顺序是自顶向下的,所以必须在子类构造函数的第一行调用父类的构造函数,先初始化父类的部分,再初始化子类的部分。如果子类的构造函数没有显式调用父类的构造函数,编译器会默认在子类的构造函数中隐式地插入super()语句来调用父类的无参构造函数。
三、实例变量、静态变量、局部变量和静态方法
1.实例变量(成员变量):定义在类中、方法之外的变量,属于类的成员,只能被对象调用
,随着对象创建回收而存在与释放,所以存在于堆内存中。
2.静态变量:定义在类中、方法之外使用 static 关键字声明的变量,与类的实例(对象)关联,只会在内存中分配一次,所有实例对象,共享同一个静态变量的内存空间,随着类的加载而存在,随着类的消失而消失,所以存在于方法区中。也称为类变量,可以被对象或类名调用
3.局部变量:在方法、构造函数或代码块中声明的变量,只在声明它们的代码块内可见,声明时必须初始化,在方法执行期间分配内存,方法执行结束后释放局部变量的内存。
4.静态方法
① 含义:是属于类而不是类的实例的方法。它可以在不创建类的实例的情况下直接通过类名来调用。静态方法被声明为静态的,因此它们不依赖于类的实例变量,也无法访问非静态的实例变量。
② 特点
1)通过类名直接调用,而不需要创建类的实例。
2)不能访问非静态的实例变量,因为它们没有隶属于任何实例
3)不能使用this关键字,因为this关键字指向当前对象的实例,而静态方法没有当前对象的实例
4)静态方法可以访问和修改静态变量,因为静态变量是属于整个类的
5)静态方法不能被重写:因为静态方法是属于类的,而不是类的实例,因此无法被子类重写。如果在子类中声明与父类中的静态方法具有相同名称和参数列表的方法时,实际上是创建了一个与父类静态方法同名但与之无关的新方法。这被称为"隐藏"而不是"重写"。当通过子类的引用调用该方法时,实际上是调用了子类中的静态方法,而不是父类中的静态方法
6)静态方法可以被子类继承,所以可以通过类名直接调用或引用变量调用,静态方法在编译时确定的调用的方法,所有要求在编译时引用变量的静态类型是本类类型及其子类类型时可以调用(支持子类是因为多态机制)
③ 适用情况:
1)当一个方法不需要访问实例变量或调用实例方法时
2)当一个方法的功能与特定实例无关,而是属于整个类时
3)当一个方法需要在类的不同实例之间共享时
四、this和super
1. this:代表当前对象的引用,用于访问当前对象的实例变量、实例方法和构造方法
① 使用情况:
1)当实例变量和局部变量同名时,使用this关键字来引用实例变量
2)使用this关键字来调用同一类中的其他构造方法
3)在方法中返回当前对象的引用,以支持方法链式调用
2.super:代表父类对象的引用,用于访问引用父类的实例变量、实例方法和构造方法
① 使用情况
1)在子类构造方法中,使用super调用父类的构造方法
2)当子类和父类具有同名的实例变量或实例方法时,使用super关键字来引用父类的成员
3)在子类中使用super调用父类的实例方法
3.区别:this只能在非静态方法(因为静态方法属于类,所以无法访问实例变量)和构造方法中使用,而super可以在子类的任何方法中使用(super 关键字在静态方法中可以用于调用父类的静态方法或访问父类的静态变量)。
五、深拷贝和浅拷贝区别
1.数据分为基本数据类型和引用数据类型。基本数据类型:数据直接存储在栈中;引用数据类型:存储在栈中的是对象的引用地址,真实的对象数据存放在堆内存里
2.浅拷贝:对于基础数据类型:直接复制数据值;对于引用数据类型:只是复制了对象的引用地址,新旧对象指向同一个内存地址,修改其中一个对象的值,另一个对象的值随之改变。
3.深拷贝:对于引用数据类型:开辟新的内存空间,在新的内存空间里复制一个一模一样的对象,新老对象不共享内存,修改其中一个对象的值,不会影响另一个对象。相比于浅拷贝速度较慢并且花销较大
六、抽象类与接口
1. 抽象类和接口都是Java中用于实现抽象和多态的关键,允许将一个对象引用赋值给父型的变量,以实现不同对象的同一行为
2. 区别
定义和使用方式:抽象类是一个类,主要为子类提供一个通用的模板和行为规范,可包含抽象方法和具体方法、成员变量,使用abstract关键字声明,需要继承后使用,接口更抽象,只包含抽象方法和常量,使用interface关键字来声明,通过被类实现来使用
继承关系: 一个类只能继承一个抽象类,通过继承子类可以获得抽象类中的属性和方法的实现;一个类可以实现多个接口,但需要提供接口中定义的所有方法的具体实现
构造方法:虽然抽象类不能被直接实例化,但抽象类可以有构造方法,是用于初始化抽象类的子类对象,因为子类继承了父类的属性和行为,需要保证这些父类的属性在子类对象创建时都得到了正确的初始化。当创建子类对象时,子类的构造方法会首先调用父类的构造方法(使用super()显示调用,没有调用系统会默认调用父类的无参构造方法),接口不能有构造方法
默认实现:抽象类可实现具体方法,子类可直接继承使用,接口不可实现具体方法,只能定义抽象方法的签名,需要实现接口的类提供方法的具体实现
多态性:通过继承抽象类或实现接口,可以在不同的类中实现相同的方法,一个类可以实现多个接口,从而实现更强大的多态性
七、反射
1.是指在运行时动态地获取和操作类的信息,包括类的属性、方法、构造函数等
2.反射的常见用途包括
(1)动态加载类:在运行时根据类名动态加载类,可以实现灵活的类加载机制。
(2)创建实例:通过反射可以在运行时动态地创建类的实例。
(3)调用方法:通过反射可以在运行时动态地调用类的方法。
(4)访问和修改属性:通过反射可以在运行时动态地访问和修改类的属性值。
2. 继承:子类继承父类的和行为,使得子类对象(实例)具有父类的实例域和方法
3. 多态:允许一个变量引用不同类型的对象,以实现同一个方法的不同行为(父类引用指向子类对象(向上转型,安全))
(1)多态有两种形式:编译时多态(静态多态)和运行时多态(动态多态)
①静态多态:即方法重载,在编译时根据方法的参数类型和个数来决定要调用的方法,常用于构造器的重载
1)方法重载:在同一个类及其子类中可以定义多个具有相同名称但参数列表不同的方法
a.方法名称相同、参数列表不同(参数类型、数量、顺序必须有一个不同
b.修饰符、返回类型可不同(除构造方法)、抛出异常可不同
c.可重载静态方法
① 运行多态:即方法重写,是在运行时根据对象的实际类型来决定要调用的方法(反射)
1)方法重写:发生在继承关系中,指子类对父类中同名、参数列表相同的方法进行重新实现
a.方法名称、参数列表和返回类型与父类相同
b.修饰符不能比父类更严格、不能抛出比父类更广泛的异常
c.父类的抽象方法要么被重写,要么被子类也定义为抽象类
d.子类可以通过super关键字调用父类被重写的方法
e.不可重写静态方法
4. 构造器
(1)用来初始化成员属性、方法,对象一旦创建就运行,且只运行一次
(2)函数名与类名一致,无返回值与返回类型,故也无return语句
(3)当一个类没有定义时,系统会默认给该类添加一个不可见的无参构造函数
(4)当一个类定义了有参构造函数后,系统不会再给该类默认添加无参构造函数
(5)构造方法不能被重写,但可在一个类或在其子类中重载
(6)在子类的构造函数中,通过super关键字调用父类的构造函数,因为构造函数的调用顺序是自顶向下的,所以必须在子类构造函数的第一行调用父类的构造函数,先初始化父类的部分,再初始化子类的部分。如果子类的构造函数没有显式调用父类的构造函数,编译器会默认在子类的构造函数中隐式地插入super()语句来调用父类的无参构造函数。
三、实例变量、静态变量、局部变量和静态方法
1.实例变量(成员变量):定义在类中、方法之外的变量,属于类的成员,只能被对象调用
,随着对象创建回收而存在与释放,所以存在于堆内存中。
2.静态变量:定义在类中、方法之外使用 static 关键字声明的变量,与类的实例(对象)关联,只会在内存中分配一次,所有实例对象,共享同一个静态变量的内存空间,随着类的加载而存在,随着类的消失而消失,所以存在于方法区中。也称为类变量,可以被对象或类名调用
3.局部变量:在方法、构造函数或代码块中声明的变量,只在声明它们的代码块内可见,声明时必须初始化,在方法执行期间分配内存,方法执行结束后释放局部变量的内存。
4.静态方法
① 含义:是属于类而不是类的实例的方法。它可以在不创建类的实例的情况下直接通过类名来调用。静态方法被声明为静态的,因此它们不依赖于类的实例变量,也无法访问非静态的实例变量。
② 特点
1)通过类名直接调用,而不需要创建类的实例。
2)不能访问非静态的实例变量,因为它们没有隶属于任何实例
3)不能使用this关键字,因为this关键字指向当前对象的实例,而静态方法没有当前对象的实例
4)静态方法可以访问和修改静态变量,因为静态变量是属于整个类的
5)静态方法不能被重写:因为静态方法是属于类的,而不是类的实例,因此无法被子类重写。如果在子类中声明与父类中的静态方法具有相同名称和参数列表的方法时,实际上是创建了一个与父类静态方法同名但与之无关的新方法。这被称为"隐藏"而不是"重写"。当通过子类的引用调用该方法时,实际上是调用了子类中的静态方法,而不是父类中的静态方法
6)静态方法可以被子类继承,所以可以通过类名直接调用或引用变量调用,静态方法在编译时确定的调用的方法,所有要求在编译时引用变量的静态类型是本类类型及其子类类型时可以调用(支持子类是因为多态机制)
③ 适用情况:
1)当一个方法不需要访问实例变量或调用实例方法时
2)当一个方法的功能与特定实例无关,而是属于整个类时
3)当一个方法需要在类的不同实例之间共享时
四、this和super
1. this:代表当前对象的引用,用于访问当前对象的实例变量、实例方法和构造方法
① 使用情况:
1)当实例变量和局部变量同名时,使用this关键字来引用实例变量
2)使用this关键字来调用同一类中的其他构造方法
3)在方法中返回当前对象的引用,以支持方法链式调用
2.super:代表父类对象的引用,用于访问引用父类的实例变量、实例方法和构造方法
① 使用情况
1)在子类构造方法中,使用super调用父类的构造方法
2)当子类和父类具有同名的实例变量或实例方法时,使用super关键字来引用父类的成员
3)在子类中使用super调用父类的实例方法
3.区别:this只能在非静态方法(因为静态方法属于类,所以无法访问实例变量)和构造方法中使用,而super可以在子类的任何方法中使用(super 关键字在静态方法中可以用于调用父类的静态方法或访问父类的静态变量)。
五、深拷贝和浅拷贝区别
1.数据分为基本数据类型和引用数据类型。基本数据类型:数据直接存储在栈中;引用数据类型:存储在栈中的是对象的引用地址,真实的对象数据存放在堆内存里
2.浅拷贝:对于基础数据类型:直接复制数据值;对于引用数据类型:只是复制了对象的引用地址,新旧对象指向同一个内存地址,修改其中一个对象的值,另一个对象的值随之改变。
3.深拷贝:对于引用数据类型:开辟新的内存空间,在新的内存空间里复制一个一模一样的对象,新老对象不共享内存,修改其中一个对象的值,不会影响另一个对象。相比于浅拷贝速度较慢并且花销较大
六、抽象类与接口
1. 抽象类和接口都是Java中用于实现抽象和多态的关键,允许将一个对象引用赋值给父型的变量,以实现不同对象的同一行为
2. 区别
定义和使用方式:抽象类是一个类,主要为子类提供一个通用的模板和行为规范,可包含抽象方法和具体方法、成员变量,使用abstract关键字声明,需要继承后使用,接口更抽象,只包含抽象方法和常量,使用interface关键字来声明,通过被类实现来使用
继承关系: 一个类只能继承一个抽象类,通过继承子类可以获得抽象类中的属性和方法的实现;一个类可以实现多个接口,但需要提供接口中定义的所有方法的具体实现
构造方法:虽然抽象类不能被直接实例化,但抽象类可以有构造方法,是用于初始化抽象类的子类对象,因为子类继承了父类的属性和行为,需要保证这些父类的属性在子类对象创建时都得到了正确的初始化。当创建子类对象时,子类的构造方法会首先调用父类的构造方法(使用super()显示调用,没有调用系统会默认调用父类的无参构造方法),接口不能有构造方法
默认实现:抽象类可实现具体方法,子类可直接继承使用,接口不可实现具体方法,只能定义抽象方法的签名,需要实现接口的类提供方法的具体实现
多态性:通过继承抽象类或实现接口,可以在不同的类中实现相同的方法,一个类可以实现多个接口,从而实现更强大的多态性
七、反射
1.是指在运行时动态地获取和操作类的信息,包括类的属性、方法、构造函数等
2.反射的常见用途包括
(1)动态加载类:在运行时根据类名动态加载类,可以实现灵活的类加载机制。
(2)创建实例:通过反射可以在运行时动态地创建类的实例。
(3)调用方法:通过反射可以在运行时动态地调用类的方法。
(4)访问和修改属性:通过反射可以在运行时动态地访问和修改类的属性值。
八、 final 关键字
1. 修饰类:表示不能作为父类被继承
2. 修饰方法:表示该方法不能被子类重写
3. 修饰变量:表示该变量不可被修改,且必须在声明时给定初值,要么在创建时即被初始化赋值,要么在其所属类的所有构造函数中将其初始化赋值。 如果变量是对象,则指的是引用不可修改,但是对象的属性还是可以修改的
九、String和StringBuilder、StringBuffer
1.String:其类及其方法均使用 final 修饰,无法被继承、底层使用的是不可变的字符数组(char[]),所以String 的值一旦被创建后无法被修改,会生成新的 String 对象(故需要创建和销毁操作)
2.StringBuffer:与String类似,但值可以被修改,它使用 synchronized 保证线程安全
3.StringBuilder:因为底层使用的是可变的字符数组(char[])来存储字符串内容,支持在原有字符串的基础上进行修改,而不需要创建新的字符串对象(性能更高)。它是StringBuffer 的非线程安全版
1. 修饰类:表示不能作为父类被继承
2. 修饰方法:表示该方法不能被子类重写
3. 修饰变量:表示该变量不可被修改,且必须在声明时给定初值,要么在创建时即被初始化赋值,要么在其所属类的所有构造函数中将其初始化赋值。 如果变量是对象,则指的是引用不可修改,但是对象的属性还是可以修改的
九、String和StringBuilder、StringBuffer
1.String:其类及其方法均使用 final 修饰,无法被继承、底层使用的是不可变的字符数组(char[]),所以String 的值一旦被创建后无法被修改,会生成新的 String 对象(故需要创建和销毁操作)
2.StringBuffer:与String类似,但值可以被修改,它使用 synchronized 保证线程安全
3.StringBuilder:因为底层使用的是可变的字符数组(char[])来存储字符串内容,支持在原有字符串的基础上进行修改,而不需要创建新的字符串对象(性能更高)。它是StringBuffer 的非线程安全版
十、队列和栈的区别
1.队列:先入先出,新元素添加到队尾,从队头删除,可使用链表或数组实现
2.栈:先入后出,新元素添加或删除都是从栈顶,可使用链表或数组实现
3.使用场景:
(1)队列常用于需要按照先后顺序处理数据的场景,例如任务调度、消息队列等。
(2)栈常用于需要实现后进先出或者逆序处理数据的场景,例如函数调用栈、浏览器的页面后退等
十一、int与Integer
1.int是基本数据类型,表示整数。
2.Integer是一个类,属于Java的包装类,用于将基本类型的int包装成一个对象
空值表示:int无法表示空值,只能存储非空的整数值;Integer可以表示空值,可以赋值为null表示没有值
操作和方法:int直接支持基本的算术运算和比较操作,Integer不能直接进行算术运算和比较操作,需要调用其提供的方法,如intValue()用于获取整数值
自动装箱和拆箱:int和Integer之间可以进行自动装箱和拆箱的转换。
自动装箱是指将int自动转换为对应的Integer对象,而自动拆箱是指将Integer对象自动转换为对应的int值
1.队列:先入先出,新元素添加到队尾,从队头删除,可使用链表或数组实现
2.栈:先入后出,新元素添加或删除都是从栈顶,可使用链表或数组实现
3.使用场景:
(1)队列常用于需要按照先后顺序处理数据的场景,例如任务调度、消息队列等。
(2)栈常用于需要实现后进先出或者逆序处理数据的场景,例如函数调用栈、浏览器的页面后退等
十一、int与Integer
1.int是基本数据类型,表示整数。
2.Integer是一个类,属于Java的包装类,用于将基本类型的int包装成一个对象
空值表示:int无法表示空值,只能存储非空的整数值;Integer可以表示空值,可以赋值为null表示没有值
操作和方法:int直接支持基本的算术运算和比较操作,Integer不能直接进行算术运算和比较操作,需要调用其提供的方法,如intValue()用于获取整数值
自动装箱和拆箱:int和Integer之间可以进行自动装箱和拆箱的转换。
自动装箱是指将int自动转换为对应的Integer对象,而自动拆箱是指将Integer对象自动转换为对应的int值
十二、List、Set、Map三者的区别
1.List接口: 继承了collection接口,是一种有序、可重复的集合
(1)ArrayList:基于动态数组实现,底层是object[],数组是一种连续的数据结构,它将元素存储在内存中的连续地址上,查询速度快、插入、删除速度慢,非线程安全
(2)LinkedList:基于链表实现,链表是一种非连续的数据结构,它使用指针将元素存储在内存中的不同地址上,并通过指针链接它们,查询速度慢、插入、删除速度快
(3)Vector:线程安全
2.Set:无序、不允许重复的集合
3.Map: 使用键值对存储,Key 不能重复,value可以重复
4.HashMap
(1)hashcode:在散列存储中确定对象的存储位置,用于查找的便捷性
(2)equals:用于比较两个对象是否相等(需类型与内容一致),两个key的hashcode相同不代表equals相同
(3)基于HashTable继承自 AbstractMap 类的非同步实现接口,HashTable继承自 Dictionary 类,线程安全,直接使用 hashCode计算
(4)基于“数组+链表+红黑树”实现,为提升 hash 冲突严重时(链表过长)的查找性能
(5)读取:先计算出key的hash值,再比较equals
(6)插入:根据key的hashcode计算出hash值得到key的插入位置,若该位置没有元素,则插入,若该位置有元素(hash冲突),则将这个位置的元素以链表的形式存放,新加入的放入链头,最先加入的放入链尾
(7)Hash冲突
使用链地址法解决冲突,将具有相同hash值的键值对存储在同一个哈希桶中的链表或者红黑树中
当发生冲突时,HashMap会将新的键值对默认插入到链表当同一个索引位置的节点在新增后超过8个(阈值8,时间和空间上权衡的结果):如果此时数组长度大于等于 64,则会触发链表节点转红黑树节点(treeifyBin);而如果数组长度小于64,则不会触发链表转红黑树,而是会进行扩容
当进行查找、插入或删除操作时,HashMap会根据键的哈希值找到对应的桶,在桶中遍历链表或者红黑树,找到对应的键值对进行操作
(8)移除,当同一个索引位置的节点在移除后达到 6 个,并且该索引位置的节点为红黑树节点,会触发红黑树节点转链表节点
(9)可通过调整负载因子和初始容量来优化HashMap的性能:扩容阈值 = 容量 * 负载因子,初始容量为16
(10)底层将key-value当做一个整体的entry对象处理,一个entry对象就是一个单向链表,每个链接保存了key和value
5.TreeMap:有序的散列表,通过红黑树实现
1.List接口: 继承了collection接口,是一种有序、可重复的集合
(1)ArrayList:基于动态数组实现,底层是object[],数组是一种连续的数据结构,它将元素存储在内存中的连续地址上,查询速度快、插入、删除速度慢,非线程安全
(2)LinkedList:基于链表实现,链表是一种非连续的数据结构,它使用指针将元素存储在内存中的不同地址上,并通过指针链接它们,查询速度慢、插入、删除速度快
(3)Vector:线程安全
2.Set:无序、不允许重复的集合
3.Map: 使用键值对存储,Key 不能重复,value可以重复
4.HashMap
(1)hashcode:在散列存储中确定对象的存储位置,用于查找的便捷性
(2)equals:用于比较两个对象是否相等(需类型与内容一致),两个key的hashcode相同不代表equals相同
(3)基于HashTable继承自 AbstractMap 类的非同步实现接口,HashTable继承自 Dictionary 类,线程安全,直接使用 hashCode计算
(4)基于“数组+链表+红黑树”实现,为提升 hash 冲突严重时(链表过长)的查找性能
(5)读取:先计算出key的hash值,再比较equals
(6)插入:根据key的hashcode计算出hash值得到key的插入位置,若该位置没有元素,则插入,若该位置有元素(hash冲突),则将这个位置的元素以链表的形式存放,新加入的放入链头,最先加入的放入链尾
(7)Hash冲突
使用链地址法解决冲突,将具有相同hash值的键值对存储在同一个哈希桶中的链表或者红黑树中
当发生冲突时,HashMap会将新的键值对默认插入到链表当同一个索引位置的节点在新增后超过8个(阈值8,时间和空间上权衡的结果):如果此时数组长度大于等于 64,则会触发链表节点转红黑树节点(treeifyBin);而如果数组长度小于64,则不会触发链表转红黑树,而是会进行扩容
当进行查找、插入或删除操作时,HashMap会根据键的哈希值找到对应的桶,在桶中遍历链表或者红黑树,找到对应的键值对进行操作
(8)移除,当同一个索引位置的节点在移除后达到 6 个,并且该索引位置的节点为红黑树节点,会触发红黑树节点转链表节点
(9)可通过调整负载因子和初始容量来优化HashMap的性能:扩容阈值 = 容量 * 负载因子,初始容量为16
(10)底层将key-value当做一个整体的entry对象处理,一个entry对象就是一个单向链表,每个链接保存了key和value
5.TreeMap:有序的散列表,通过红黑树实现
十三、内存结构
1. 程序计数器:线程私有
2. 栈:线程私有。每个方法在执行的同时都会创建一个栈帧,从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程
3. 本地方法栈:线程私有
6.堆:是所有线程共享的一块内存区域,在虚拟机启动时创建。唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。
7.方法区:各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息(构造方法、接口定义)、常量、静态变量、即时编译器编译后的代码(字节码)等数据、运行时常量池是方法区的一部分
十四、内存溢出与内存泄漏
1.内存溢出
(1)程序在申请内存时,申请的内存>系统目前可提供的内存,会抛异常
(2)原因
①启动参数内存设定过小
②内存加载的数据量过大
③集合类中有对对象的引用,使用后未回收
④代码中存在死循环或循环中产生过多重复的对象
2.内存泄漏
(1)程序在申请内存后,无法释放已申请的内存,无法被回收,程序可以正常运行,不抛异常
(2)最终可能会导致内存溢出
1. 程序计数器:线程私有
2. 栈:线程私有。每个方法在执行的同时都会创建一个栈帧,从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程
3. 本地方法栈:线程私有
6.堆:是所有线程共享的一块内存区域,在虚拟机启动时创建。唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。
7.方法区:各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息(构造方法、接口定义)、常量、静态变量、即时编译器编译后的代码(字节码)等数据、运行时常量池是方法区的一部分
十四、内存溢出与内存泄漏
1.内存溢出
(1)程序在申请内存时,申请的内存>系统目前可提供的内存,会抛异常
(2)原因
①启动参数内存设定过小
②内存加载的数据量过大
③集合类中有对对象的引用,使用后未回收
④代码中存在死循环或循环中产生过多重复的对象
2.内存泄漏
(1)程序在申请内存后,无法释放已申请的内存,无法被回收,程序可以正常运行,不抛异常
(2)最终可能会导致内存溢出
十五、垃圾回收
1.GC:分代收集法
2.根据对象存活的生命周期将内存划分为若干个不同的区域:年老区、年轻区、永久代(废弃常量、无用类)
2. 触发时刻
Young GC:当新生代中的 Eden 区没有足够空间进行分配时会触发Young GC。
Full GC:当准备要触发一次Young GC时,发现目前年轻代比老年代剩余的空间大,则转为触发Full GC;永久代满了时;调用System.gc();heap dump带GC;CMS GC时出现Concurrent Mode Failure
3.回收对象:JVM通过可达性分析法从根对象(如堆栈、静态变量等)出发,递归遍历所有引用链,如果对象不可达,则被判定为死亡。
4.如何回收:新生代使用复制算法(回收量大),老年代使用标记-清除和标记-整理算法(回收量小)
5. 引用类型回收
(1)强引用:不回收
(2)软引用:根据堆使用情况决定是否被回收
(3)弱引用:gc时,就回收
(4)虚引用:随时回收
1.GC:分代收集法
2.根据对象存活的生命周期将内存划分为若干个不同的区域:年老区、年轻区、永久代(废弃常量、无用类)
2. 触发时刻
Young GC:当新生代中的 Eden 区没有足够空间进行分配时会触发Young GC。
Full GC:当准备要触发一次Young GC时,发现目前年轻代比老年代剩余的空间大,则转为触发Full GC;永久代满了时;调用System.gc();heap dump带GC;CMS GC时出现Concurrent Mode Failure
3.回收对象:JVM通过可达性分析法从根对象(如堆栈、静态变量等)出发,递归遍历所有引用链,如果对象不可达,则被判定为死亡。
4.如何回收:新生代使用复制算法(回收量大),老年代使用标记-清除和标记-整理算法(回收量小)
5. 引用类型回收
(1)强引用:不回收
(2)软引用:根据堆使用情况决定是否被回收
(3)弱引用:gc时,就回收
(4)虚引用:随时回收
十六、进程与线程
1.进程:系统进行资源调度与分配的最小单位,实现操作系统的并发
2.线程:进程的子任务,CPU进行资源调度与分配的基本单位,实现进程的并发
3.区别
(1)进程在执行时,在堆内有独立的内存单元,线程可以共享,线程只拥有且都有独立的栈内存,不拥有系统资源
(2)进程创建销毁大(因为需要分配资源)、线程创建销毁小,切换速度小
(3)一个进程挂掉,不影响其他进程;一个线程挂掉,影响所有其他线程以及进程
4. 通信方式
(1)进程:管道、消息队列、信号量机制、内存共享、socket
(2)线程:锁机制(互斥锁、共享锁、可重入锁)、信号量机制、信号机制
5.进程的切换与调度(调度是决策过程,而切换是执行过程)
(1)由操作系统的调度器通过进程调度算法决定何时切换当前正在执行的进程,以及选择下一个要执行的进程
(2)判断进程切换的一些常见条件:时间片用完、高优先级进程就绪、阻塞等待、进程终止、进程中断
(3)常见的进程调度算法包括:先来先服务、最短作业优先、轮转调度、最高优先级调度、多级反馈队列调度
1.进程:系统进行资源调度与分配的最小单位,实现操作系统的并发
2.线程:进程的子任务,CPU进行资源调度与分配的基本单位,实现进程的并发
3.区别
(1)进程在执行时,在堆内有独立的内存单元,线程可以共享,线程只拥有且都有独立的栈内存,不拥有系统资源
(2)进程创建销毁大(因为需要分配资源)、线程创建销毁小,切换速度小
(3)一个进程挂掉,不影响其他进程;一个线程挂掉,影响所有其他线程以及进程
4. 通信方式
(1)进程:管道、消息队列、信号量机制、内存共享、socket
(2)线程:锁机制(互斥锁、共享锁、可重入锁)、信号量机制、信号机制
5.进程的切换与调度(调度是决策过程,而切换是执行过程)
(1)由操作系统的调度器通过进程调度算法决定何时切换当前正在执行的进程,以及选择下一个要执行的进程
(2)判断进程切换的一些常见条件:时间片用完、高优先级进程就绪、阻塞等待、进程终止、进程中断
(3)常见的进程调度算法包括:先来先服务、最短作业优先、轮转调度、最高优先级调度、多级反馈队列调度
十七、线程的状态流转
1.NEW:新建但是尚未调用 start() 方法的线程处于此状态
2.RUNNABLE:包含就绪(READY)和运行中(RUNNING)两种状态。线程调用 start() 方法进入就绪状态,等待获取 CPU 时间片,如果成功获取到 CPU 时间片,则会进入运行中状态
3.BLOCKED(被动阻塞):线程在进入同步方法/同步块时被阻塞,等待同步锁的线程处于此状态
4.WAITING(主动阻塞):无限期等待另一个线程执行特定操作的线程处于此状态,需要被显示的唤醒,否则会一直等待下去。例如 Object.wait()需要等待另一个线程执行 Object.notify() 或 Object.notifyAll(),Thread.join()需要等待指定的线程终止
5.TIMED_WAITING:在指定的时间内等待另一个线程执行某项操作的线程处于此状态。跟 WAITING 类似,区别在于该状态有超时时间参数,在超时时间到了后会自动唤醒,避免了无期限的等待
6.TERMINATED:执行完毕已经退出的线程处于此状态
I/0阻塞:线程处于RUNNABLE状态,I/O操作指将输入的数据从系统内核拷贝到操作进程中
十八、实现多线程的方式
1. 继承 Thread 类,重写run()方法(Thread类其实也是实现了 Runable 接口)
2. 实现 Runnable 接口,重写run()方法,准确来说是创建一个任务而不是一个线程,无返回值
3. 实现 Callable 接口,任务而非线程,有返回值
4. 通过线程池(底层HashTable实现)创建
降低资源消耗(通过重复利用已创建的线程,降低线程创建和销毁造成的消耗)
提高响应速度,创建线程需要申请资源
增加线程的可管理型,使用线程池可以进行统一分配,调优和监控。
工作原理:当线程池中有提交来的任务需要执行时,先判断当前线程数量是否超过核心线程数,没有超过则新建线程执行任务,超过则将任务添加到队列等待执行,如果任务队列超过最大队列数,但线程池没有达到最大线程数,则会新建线程来执行任务,如果超过了最大线程数,则执行拒绝执行策略
十九、实现多线程同步的方式
1.线程是实现异步的方式
2.为什么需要同步:为防止多个线程并发对同一个数据修改造成数据不一致
3.实现同步的思想:加锁,把共享资源加锁,每次只允许一个线程进行使用,使用完毕后释放锁
(1)同步代码块/同步代码加锁(synchronized关键字):将代码块加锁,实例方法使用this,静态方法使用类名.class作为锁对象
(2)使用Condition接口进行while查询
(3)volatile关键字:使用volatile关键字修饰的变量可以保证线程之间的可见性,即一个线程对该变量的修改对其他线程是可见的。
(4)synchronized集合(线程安全的集合类):如Vector、Hashtable
(5)原子类(Atomic类)
(6)线程间的通信机制,如wait()、notify()、notifyAll()等方法来实现线程之间的同步
1.NEW:新建但是尚未调用 start() 方法的线程处于此状态
2.RUNNABLE:包含就绪(READY)和运行中(RUNNING)两种状态。线程调用 start() 方法进入就绪状态,等待获取 CPU 时间片,如果成功获取到 CPU 时间片,则会进入运行中状态
3.BLOCKED(被动阻塞):线程在进入同步方法/同步块时被阻塞,等待同步锁的线程处于此状态
4.WAITING(主动阻塞):无限期等待另一个线程执行特定操作的线程处于此状态,需要被显示的唤醒,否则会一直等待下去。例如 Object.wait()需要等待另一个线程执行 Object.notify() 或 Object.notifyAll(),Thread.join()需要等待指定的线程终止
5.TIMED_WAITING:在指定的时间内等待另一个线程执行某项操作的线程处于此状态。跟 WAITING 类似,区别在于该状态有超时时间参数,在超时时间到了后会自动唤醒,避免了无期限的等待
6.TERMINATED:执行完毕已经退出的线程处于此状态
I/0阻塞:线程处于RUNNABLE状态,I/O操作指将输入的数据从系统内核拷贝到操作进程中
十八、实现多线程的方式
1. 继承 Thread 类,重写run()方法(Thread类其实也是实现了 Runable 接口)
2. 实现 Runnable 接口,重写run()方法,准确来说是创建一个任务而不是一个线程,无返回值
3. 实现 Callable 接口,任务而非线程,有返回值
4. 通过线程池(底层HashTable实现)创建
降低资源消耗(通过重复利用已创建的线程,降低线程创建和销毁造成的消耗)
提高响应速度,创建线程需要申请资源
增加线程的可管理型,使用线程池可以进行统一分配,调优和监控。
工作原理:当线程池中有提交来的任务需要执行时,先判断当前线程数量是否超过核心线程数,没有超过则新建线程执行任务,超过则将任务添加到队列等待执行,如果任务队列超过最大队列数,但线程池没有达到最大线程数,则会新建线程来执行任务,如果超过了最大线程数,则执行拒绝执行策略
十九、实现多线程同步的方式
1.线程是实现异步的方式
2.为什么需要同步:为防止多个线程并发对同一个数据修改造成数据不一致
3.实现同步的思想:加锁,把共享资源加锁,每次只允许一个线程进行使用,使用完毕后释放锁
(1)同步代码块/同步代码加锁(synchronized关键字):将代码块加锁,实例方法使用this,静态方法使用类名.class作为锁对象
(2)使用Condition接口进行while查询
(3)volatile关键字:使用volatile关键字修饰的变量可以保证线程之间的可见性,即一个线程对该变量的修改对其他线程是可见的。
(4)synchronized集合(线程安全的集合类):如Vector、Hashtable
(5)原子类(Atomic类)
(6)线程间的通信机制,如wait()、notify()、notifyAll()等方法来实现线程之间的同步
二十、wait() 和 sleep() 方法,及yield() 、 join() 、notify()/nofityAll()
1.wait() 和 sleep() 区别
(1)所属类不同:sleep() 属于Thread 类,wait() 属于 Object 类
(2)对于同步锁的影响不同:
sleep()只会暂停当前线程,并释放cpu资源,但不会释放当前线程持有的同步锁(如果有的话)
wait() 是在已经获得同步锁时,暂定当前线程,释放cpu资源,释放同步锁让其他线程进入 synchronized 代码块执行,然后等待被其他线程通知,再次获取同步锁后被唤醒运行
(3)使用范围不同:
sleep() 可以在任何地方使用
wait() 只能在同步控制方法或者同步控制块里面使用(notify()/nofityAll() 也是),否则会抛 IllegalMonitorStateException
(4)恢复方式不同:
sleep() 在时间到了之后会重新恢复,最终会醒来(需要设置一个超时时间)
wait() 则需要其他线程调用同一对象的 notify()/nofityAll() 才能重新恢复
2.sleep() 和 yield() 区别:
(1)线程执行 sleep() 后进入超时等待状态,执行 yield() 后进入就绪状态
(2) sleep() 给其他线程运行机会时不考虑线程的优先级;yield() 方法只会给相同或更高优先级的线程以运行的机会
3. join() 方法:用于等待当前线程终止
4. notify()/nofityAll() :释放对象锁
1.wait() 和 sleep() 区别
(1)所属类不同:sleep() 属于Thread 类,wait() 属于 Object 类
(2)对于同步锁的影响不同:
sleep()只会暂停当前线程,并释放cpu资源,但不会释放当前线程持有的同步锁(如果有的话)
wait() 是在已经获得同步锁时,暂定当前线程,释放cpu资源,释放同步锁让其他线程进入 synchronized 代码块执行,然后等待被其他线程通知,再次获取同步锁后被唤醒运行
(3)使用范围不同:
sleep() 可以在任何地方使用
wait() 只能在同步控制方法或者同步控制块里面使用(notify()/nofityAll() 也是),否则会抛 IllegalMonitorStateException
(4)恢复方式不同:
sleep() 在时间到了之后会重新恢复,最终会醒来(需要设置一个超时时间)
wait() 则需要其他线程调用同一对象的 notify()/nofityAll() 才能重新恢复
2.sleep() 和 yield() 区别:
(1)线程执行 sleep() 后进入超时等待状态,执行 yield() 后进入就绪状态
(2) sleep() 给其他线程运行机会时不考虑线程的优先级;yield() 方法只会给相同或更高优先级的线程以运行的机会
3. join() 方法:用于等待当前线程终止
4. notify()/nofityAll() :释放对象锁
二十一、死锁
1.使用锁的目的:保证资源被互斥反问
2.公平锁和非公平锁:根据锁的实现策略来决定是否为非公平锁(可重入的互斥锁既可以是公平锁也可是非公平锁)
(1)公平锁:按照线程的请求顺序进行获取锁,即先到先得,先来的线程先获取到锁,后来的线程排队等待
(2)非公平锁:不保证锁的获取顺序与线程请求顺序一致。可能是之前等待的线程,也可能是刚刚到达的线程获取到锁。
3.乐观锁与悲观锁
(1)悲观锁: 它悲观的假设并发访问会导致冲突,因此在访问数据之前会先加锁(互斥锁或共享锁),确保在整个操作期间其他线程无法修改数据。会导致线程的阻塞和等待,可能降低系统的并发性能。
(2)乐观锁: 它乐观的假设并发访问不会导致冲突,所以不加锁,而是在数据更新的时候,检查在操作期间是否有其他线程对数据进行了修改。如果有修改发生,则放弃当前操作,重新尝试,适用于并发冲突较少发生的场景。
4.死锁:多个线程因为彼此占有,对方想要的共享资源而造成的一种互相等待的阻塞状态,如果没有外力推进,则它们都无法向前推进
5.产生死锁的四个必要条件
(1)互斥条件:进程对所分配到的资源进行排他性控制,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待
(2)占有且等待:对其他资源发出请求时,不释放对自己获占有的资源保持不放
(3)不可抢占:进程不能强行剥夺其他进程已占有的资源
(4)循环等待
6. 预防死锁的方式:打破四个必要条件中的任意一个即可(互斥条件无法破坏)
(1)打破占有且等待:采用资源预先分配策略,即进程运行前申请全部资源,满足则运行,不然就等待
(2)打破不可抢占:当进程占有某些资源后又进一步申请其他资源,该进程必须释放它原来占有的资源
(3)打破循环等待:资源有序分配策略,将系统的所有资源统一编号,所有进程只能采用按序号递增的形式申请资源
(4)加锁顺序、加锁时限、死锁检测
7.死锁解除:资源剥夺法、终止进程法、进程回退法
二十二、java常用排序算法
1.Arrays.sort():Java提供的标准排序方法,底层使用的是快速排序或归并排序。
(1)快速排序:通过选择一个基准元素,将数组分成两个子数组,小于基准的元素放在左边,大于基准的元素放在右边,然后对子数组递归地应用快速排序。
(2)归并排序:将待排序的数组递归地分成两个子数组,分别进行归并排序,然后将两个有序的子数组合并成一个有序的数组。
2.Collections.sort():用于对集合进行排序,底层也使用的是快速排序或归并排序
3.冒泡排序:比较相邻的元素,如果前面的元素大于后面的元素,则交换它们的位置。重复这个过程,直到整个数组排序完成
4.选择排序:每次从未排序的部分中选取最小(或最大)的元素,将其与未排序部分的第一个元素交换位置。重复这个过程,直到整个数组排序完成
5.插入排序:将数组分为已排序和未排序两部分,每次从未排序的部分中取一个元素,插入到已排序的部分的适当位置,使得已排序部分依然有序
6.堆排序:将待排序的数组看作是一个完全二叉树的顺序存储结构,通过构建大顶堆(或小顶堆),将堆顶元素与堆尾元素交换位置,然后重新调整堆
1.使用锁的目的:保证资源被互斥反问
2.公平锁和非公平锁:根据锁的实现策略来决定是否为非公平锁(可重入的互斥锁既可以是公平锁也可是非公平锁)
(1)公平锁:按照线程的请求顺序进行获取锁,即先到先得,先来的线程先获取到锁,后来的线程排队等待
(2)非公平锁:不保证锁的获取顺序与线程请求顺序一致。可能是之前等待的线程,也可能是刚刚到达的线程获取到锁。
3.乐观锁与悲观锁
(1)悲观锁: 它悲观的假设并发访问会导致冲突,因此在访问数据之前会先加锁(互斥锁或共享锁),确保在整个操作期间其他线程无法修改数据。会导致线程的阻塞和等待,可能降低系统的并发性能。
(2)乐观锁: 它乐观的假设并发访问不会导致冲突,所以不加锁,而是在数据更新的时候,检查在操作期间是否有其他线程对数据进行了修改。如果有修改发生,则放弃当前操作,重新尝试,适用于并发冲突较少发生的场景。
4.死锁:多个线程因为彼此占有,对方想要的共享资源而造成的一种互相等待的阻塞状态,如果没有外力推进,则它们都无法向前推进
5.产生死锁的四个必要条件
(1)互斥条件:进程对所分配到的资源进行排他性控制,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待
(2)占有且等待:对其他资源发出请求时,不释放对自己获占有的资源保持不放
(3)不可抢占:进程不能强行剥夺其他进程已占有的资源
(4)循环等待
6. 预防死锁的方式:打破四个必要条件中的任意一个即可(互斥条件无法破坏)
(1)打破占有且等待:采用资源预先分配策略,即进程运行前申请全部资源,满足则运行,不然就等待
(2)打破不可抢占:当进程占有某些资源后又进一步申请其他资源,该进程必须释放它原来占有的资源
(3)打破循环等待:资源有序分配策略,将系统的所有资源统一编号,所有进程只能采用按序号递增的形式申请资源
(4)加锁顺序、加锁时限、死锁检测
7.死锁解除:资源剥夺法、终止进程法、进程回退法
二十二、java常用排序算法
1.Arrays.sort():Java提供的标准排序方法,底层使用的是快速排序或归并排序。
(1)快速排序:通过选择一个基准元素,将数组分成两个子数组,小于基准的元素放在左边,大于基准的元素放在右边,然后对子数组递归地应用快速排序。
(2)归并排序:将待排序的数组递归地分成两个子数组,分别进行归并排序,然后将两个有序的子数组合并成一个有序的数组。
2.Collections.sort():用于对集合进行排序,底层也使用的是快速排序或归并排序
3.冒泡排序:比较相邻的元素,如果前面的元素大于后面的元素,则交换它们的位置。重复这个过程,直到整个数组排序完成
4.选择排序:每次从未排序的部分中选取最小(或最大)的元素,将其与未排序部分的第一个元素交换位置。重复这个过程,直到整个数组排序完成
5.插入排序:将数组分为已排序和未排序两部分,每次从未排序的部分中取一个元素,插入到已排序的部分的适当位置,使得已排序部分依然有序
6.堆排序:将待排序的数组看作是一个完全二叉树的顺序存储结构,通过构建大顶堆(或小顶堆),将堆顶元素与堆尾元素交换位置,然后重新调整堆
二十三、分页和分段
1.指在不同层面上对数据或代码进行划分
2.分页
(1)指将大量数据划分成一定大小的页或块,每次只加载或显示一页数据,减少数据传输和页面加载时间
(2)分页的好处:
提高查询性能,减少数据的加载时间
减少内存消耗,避免一次性加载大量数据
提升用户体验,减轻页面的加载压力,提供更快的响应
3.分段:指将程序的逻辑划分成多个独立的功能模块或代码段,例如,对于一个Web应用程序,可以划分为数据访问层、业务逻辑层和表示层
二十四、查看JVM性能常用命令
1.查看java进程及相关信息:jps
2.输出jar包路径,类全名,-m 输出main参数,-v 输出JVM参数
3.查看JVM运行时的状态信息,包括内存状态、垃圾回收: jstat
4.查看JVM线程快照:jstack,可以定位线程出现长时间卡顿的原因,例如死锁,死循环
5.查看内存信息:jmap
6.查看网络连接(TCP与UDP连接-a)、正在监听的端口(-l)与进程(-p):netstat
7.使用 top 命令,查找到使用 CPU 最多的某个进程,记录它的 pid
8.ps命令用于查看当前系统中正在运行的进程的信息
9.终止进程:kill (-9:强制终止,进程无法进行清理)
二十五、滑动窗口
1. 通过维护一个窗口来滑动,处理连续子数组或子字符串的问题
2. 原理
初始化窗口的起始位置和结束位置
将窗口从左向右滑动,每次滑动一个位置
在每个滑动位置,根据当前窗口的内容进行相应的操作。可以是计算子数组或子字符串的某些属性,或者判断窗口内的数据是否满足特定条件
根据窗口内容的处理结果,更新窗口的起始位置和结束位置
重复步骤2到步骤4,直到窗口滑动到最右边或满足终止条件
优点:通过只遍历一次数据,可将时间复杂度从O(n^2)降低到O(n)。
1.指在不同层面上对数据或代码进行划分
2.分页
(1)指将大量数据划分成一定大小的页或块,每次只加载或显示一页数据,减少数据传输和页面加载时间
(2)分页的好处:
提高查询性能,减少数据的加载时间
减少内存消耗,避免一次性加载大量数据
提升用户体验,减轻页面的加载压力,提供更快的响应
3.分段:指将程序的逻辑划分成多个独立的功能模块或代码段,例如,对于一个Web应用程序,可以划分为数据访问层、业务逻辑层和表示层
二十四、查看JVM性能常用命令
1.查看java进程及相关信息:jps
2.输出jar包路径,类全名,-m 输出main参数,-v 输出JVM参数
3.查看JVM运行时的状态信息,包括内存状态、垃圾回收: jstat
4.查看JVM线程快照:jstack,可以定位线程出现长时间卡顿的原因,例如死锁,死循环
5.查看内存信息:jmap
6.查看网络连接(TCP与UDP连接-a)、正在监听的端口(-l)与进程(-p):netstat
7.使用 top 命令,查找到使用 CPU 最多的某个进程,记录它的 pid
8.ps命令用于查看当前系统中正在运行的进程的信息
9.终止进程:kill (-9:强制终止,进程无法进行清理)
二十五、滑动窗口
1. 通过维护一个窗口来滑动,处理连续子数组或子字符串的问题
2. 原理
初始化窗口的起始位置和结束位置
将窗口从左向右滑动,每次滑动一个位置
在每个滑动位置,根据当前窗口的内容进行相应的操作。可以是计算子数组或子字符串的某些属性,或者判断窗口内的数据是否满足特定条件
根据窗口内容的处理结果,更新窗口的起始位置和结束位置
重复步骤2到步骤4,直到窗口滑动到最右边或满足终止条件
优点:通过只遍历一次数据,可将时间复杂度从O(n^2)降低到O(n)。
数据库
一、非关系型数据库(NoSQL)
列存储 Hbase
K-V存储 Redis
图像存储 Neo4j
文档存储 MongoDB
二、关系型数据库与非关系型数据库的区别
1.关系型:通过外键关联建立表与表之间的关系,非关系型:数据以对象的形式存储在数据库中,对象之间的关系通过那个对象自身的属性来决定
2.关系型:将复杂的数据结构归结为简单的二元关系(二维表格),查询一条数据,结果为一个数组,非关系型:查询一条数据,结果是一个对象
3.非关系型:部署简单价格便宜,适用于高并发、高性能,数据一致性要求不高的场景,不支持事务,所以读写性能高;关系型:通过事务保证数据的一致性,因此读写性能差
三、关系型数据库
1.MySQL、PostgreSQL、Oracle Database、SQL Server
2.数据库事务的4大特性:ACID
(1)原子性:事务是最⼩的执⾏单位,不允许分割
(2)一致性:执⾏事务前后,数据保持⼀致,多个事务对同⼀个数据读取的结果是相同的
(3)隔离性:并发访问数据库时,⼀个⽤户的事务不被其他事务所⼲扰,各并发事务之间数据库是独⽴的
(4)持久性:⼀个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发⽣故障也不应该对其有任何影响。
3.事务隔离:用于解决脏读、幻读、不可重复读
(1)脏读:一个事务读取到另一个事务还未提交的数据
(2)幻读:在一个事务中使用相同的 SQL 两次读取,第二次读取到了其他事务新“插入“的行
(3)不可重复读:在一个事务中多次读取同一个数据时,结果不一致,在于数据的修改
4.事务隔离级别
①读未提交:允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
(1)读已提交:允许读取并发事务已经提交的数据,只可阻⽌脏读
(2)可重复读:默认级别,存在有幻读。
(3)串行化:最⾼级别,表级锁(还有行级锁、间隙锁),完全服从ACID的隔离级别。所有的事务依次逐个执⾏,这样事务之间就完全不可能产⽣⼲扰。
5.数据库操作
(1)创建数据库、表(Create Database、Table)
(2)删除数据(Delete):从数据库表中删除指定的数据行,从表中移除数据。
(3)删除表(Drop Table):从数据库中删除已存在的表,包括表的结构和数据。
(4)创建索引(Create Index):创建索引以提高对数据库表的查询性能。
(5)修改表结构(Alter Table):修改已存在的数据库表的结构,如添加、删除或修改字段
6. 数据库语句执行过程:应用程序发送sql到数据库服务器;数据库服务器查询是否有该sql的缓存;没有命中缓存,则生成执行计划:解析sql-生成解析树验证关键字如select等是否正确;预处理-进一步检查解析树是否合理,如列表是否存在等;决定使用哪个索引以及表的连接顺序,然后将sql语句转为执行计划;最后将查询结果返回客户端或缓存
7.GROUP BY 与ORDER BY与HAVING的区别
(1)GROUP BY:用于将查询结果按照一个或多个列进行分组,将具有相同值的行分组到一起,并对每个组执行聚合函数(如 COUNT、SUM、AVG 等)来生成汇总数据
(2)ORDER BY:按照一个或多个列对查询结果进行指定的升序(ASC)或降序(DESC)方式返回,通常位于 SQL 查询的最后
(3)HAVING:用于在分组后对结果进行过滤,在 GROUP BY 后面使用,用于指定一个条件来筛选分组的结果。与 WHERE 关键字不同,HAVING 可以使用聚合函数和分组后的列进行过滤。它允许在分组后对结果进行进一步的过滤和筛选。
8.数据库存储方式:底层是使用数据页存储的(行存储或列存储),一条记录占用空间过大会导致跨页,造成额外的性能开销
9.慢SQL优化
(1)分析原因:查询条件是否命中索引,是否查询了不需要的数据列、是否查询数据量太大
(2)通过 explain 分析语句的执行计划
①查看使用索引的情况,如果可以加索引解决,优先采用加索引解 查询语句需优化
②查询了不需要的数据列:优化语句
③数据量太大:分库分表,单库不超过200张表,单表不超过500w数据,单表不超过40列,单表索引不超过5个
④锁竞争和并发导致查询等待时间增长
⑤数据库配置参数或服务器配置需优化::连接数、增加内存、升级ssd硬盘
⑥网络延迟
10.数据库主从复制
(1)原理:将主库的binlog日志复制到从库上执行一遍,达到主从数据的一致状态
(2)步骤
①从库开启一个I/O线程,向主库请求binlog日志
②主节点开启一个binlog dump线程,检查自己的二进制日志并发送给从节点
③从库将接收到的数据保存到中继日志(Relay log)中,另外开启一个SQL线程,把Relay中的操作在自身机器上执行一遍(异步过程)
(3)可通过半同步复制、全同步复制解决异步复制中数据丢失的问题
(4)可使用并行复制(并行是指从库多个SQL线程并行执行 relay log),解决从库复制延迟的问题(数据一致性需保证)
(5)主从复制的好处:
①作为实时备份
②作为备用数据库
③可做读写分离
11.数据库外键(Foreign Key)
(1)是指一个表中的列或一组列,用于建立与另一个表的关联关系。用于定义表之间的关联约束,确保数据的一致性和完整性、查询数据关联。
(2)在关系型数据库中,外键通常由一个表中的列引用另一个表的主键列。这种关系被称为父表/主表与子表/从表之间的关系
12.数据库索引
(1)对数据库表中一列或多列的值进行排序的一种存储的数据结构
(2)本质:通过不断地缩小想要获取数据的范围来筛选出最终想要的结果,同时把随机的事件变成顺序的事件提高了查询效率,实际是因为减少了查询过程中的I/O次数
(3)应用层次划分索引
①普通索引index:一个索引只包含单个列,但一个表可以有多个单列索引
②唯一索引unique:索引值必须唯一,不能重复,但允许有空值,如果是主键索引primary key:主键用于唯一标识每一行数据且只能有一个,不可为空,不可重复
联合索引:一个索引包含多个列
(4)从表记录的排列顺序与索引的排列顺序是否一致来划分
聚集索引:表记录的排列顺序与索引的排列顺序一致,查询效率快,新增慢因为需要对数据页重新排序,叶子节点存储的是表中的数据
非聚集索引:表记录的排列顺序与索引的排列顺序不一致,叶子节点存储的是主键和索引列
(4)索引类型
①B+树索引:关系型数据库默认索引类型,支持精确查找和范围查找,查询次数更少,查询效率更高
②哈希索引:用索引列的值计算该值的hashCode,然后在hashCode相应的位置存执该值所在行数据的物理位置,因为使用散列算法,因此访问速度非常快,但是一个值只能对应一个hashCode,而且是散列的分布方式,因此哈希索引要求完全匹配
(5)索引失效原因:
多列索引违反最左前缀法则:查询从索引的最左列开始并且不跳过索引的列
列类型不匹配:当查询中的列类型与索引的列类型不匹配时,数据库无法使用索引
索引上进行了函数或表达式的使用
索引范围条件右边的列失效:如大于小于号,不等于,like以通配符开头、or的使用
索引参与了排序或分组
非前缀索引的使用:索引是创建在多列上,但查询中只使用了索引的部分列
数据分布不均匀
(6)缺点:索引文件占用磁盘空间、写性能下降(需更新索引)、维护开销12.数据库(1)第一范示(1NF):列不可拆
一、非关系型数据库(NoSQL)
列存储 Hbase
K-V存储 Redis
图像存储 Neo4j
文档存储 MongoDB
二、关系型数据库与非关系型数据库的区别
1.关系型:通过外键关联建立表与表之间的关系,非关系型:数据以对象的形式存储在数据库中,对象之间的关系通过那个对象自身的属性来决定
2.关系型:将复杂的数据结构归结为简单的二元关系(二维表格),查询一条数据,结果为一个数组,非关系型:查询一条数据,结果是一个对象
3.非关系型:部署简单价格便宜,适用于高并发、高性能,数据一致性要求不高的场景,不支持事务,所以读写性能高;关系型:通过事务保证数据的一致性,因此读写性能差
三、关系型数据库
1.MySQL、PostgreSQL、Oracle Database、SQL Server
2.数据库事务的4大特性:ACID
(1)原子性:事务是最⼩的执⾏单位,不允许分割
(2)一致性:执⾏事务前后,数据保持⼀致,多个事务对同⼀个数据读取的结果是相同的
(3)隔离性:并发访问数据库时,⼀个⽤户的事务不被其他事务所⼲扰,各并发事务之间数据库是独⽴的
(4)持久性:⼀个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发⽣故障也不应该对其有任何影响。
3.事务隔离:用于解决脏读、幻读、不可重复读
(1)脏读:一个事务读取到另一个事务还未提交的数据
(2)幻读:在一个事务中使用相同的 SQL 两次读取,第二次读取到了其他事务新“插入“的行
(3)不可重复读:在一个事务中多次读取同一个数据时,结果不一致,在于数据的修改
4.事务隔离级别
①读未提交:允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
(1)读已提交:允许读取并发事务已经提交的数据,只可阻⽌脏读
(2)可重复读:默认级别,存在有幻读。
(3)串行化:最⾼级别,表级锁(还有行级锁、间隙锁),完全服从ACID的隔离级别。所有的事务依次逐个执⾏,这样事务之间就完全不可能产⽣⼲扰。
5.数据库操作
(1)创建数据库、表(Create Database、Table)
(2)删除数据(Delete):从数据库表中删除指定的数据行,从表中移除数据。
(3)删除表(Drop Table):从数据库中删除已存在的表,包括表的结构和数据。
(4)创建索引(Create Index):创建索引以提高对数据库表的查询性能。
(5)修改表结构(Alter Table):修改已存在的数据库表的结构,如添加、删除或修改字段
6. 数据库语句执行过程:应用程序发送sql到数据库服务器;数据库服务器查询是否有该sql的缓存;没有命中缓存,则生成执行计划:解析sql-生成解析树验证关键字如select等是否正确;预处理-进一步检查解析树是否合理,如列表是否存在等;决定使用哪个索引以及表的连接顺序,然后将sql语句转为执行计划;最后将查询结果返回客户端或缓存
7.GROUP BY 与ORDER BY与HAVING的区别
(1)GROUP BY:用于将查询结果按照一个或多个列进行分组,将具有相同值的行分组到一起,并对每个组执行聚合函数(如 COUNT、SUM、AVG 等)来生成汇总数据
(2)ORDER BY:按照一个或多个列对查询结果进行指定的升序(ASC)或降序(DESC)方式返回,通常位于 SQL 查询的最后
(3)HAVING:用于在分组后对结果进行过滤,在 GROUP BY 后面使用,用于指定一个条件来筛选分组的结果。与 WHERE 关键字不同,HAVING 可以使用聚合函数和分组后的列进行过滤。它允许在分组后对结果进行进一步的过滤和筛选。
8.数据库存储方式:底层是使用数据页存储的(行存储或列存储),一条记录占用空间过大会导致跨页,造成额外的性能开销
9.慢SQL优化
(1)分析原因:查询条件是否命中索引,是否查询了不需要的数据列、是否查询数据量太大
(2)通过 explain 分析语句的执行计划
①查看使用索引的情况,如果可以加索引解决,优先采用加索引解 查询语句需优化
②查询了不需要的数据列:优化语句
③数据量太大:分库分表,单库不超过200张表,单表不超过500w数据,单表不超过40列,单表索引不超过5个
④锁竞争和并发导致查询等待时间增长
⑤数据库配置参数或服务器配置需优化::连接数、增加内存、升级ssd硬盘
⑥网络延迟
10.数据库主从复制
(1)原理:将主库的binlog日志复制到从库上执行一遍,达到主从数据的一致状态
(2)步骤
①从库开启一个I/O线程,向主库请求binlog日志
②主节点开启一个binlog dump线程,检查自己的二进制日志并发送给从节点
③从库将接收到的数据保存到中继日志(Relay log)中,另外开启一个SQL线程,把Relay中的操作在自身机器上执行一遍(异步过程)
(3)可通过半同步复制、全同步复制解决异步复制中数据丢失的问题
(4)可使用并行复制(并行是指从库多个SQL线程并行执行 relay log),解决从库复制延迟的问题(数据一致性需保证)
(5)主从复制的好处:
①作为实时备份
②作为备用数据库
③可做读写分离
11.数据库外键(Foreign Key)
(1)是指一个表中的列或一组列,用于建立与另一个表的关联关系。用于定义表之间的关联约束,确保数据的一致性和完整性、查询数据关联。
(2)在关系型数据库中,外键通常由一个表中的列引用另一个表的主键列。这种关系被称为父表/主表与子表/从表之间的关系
12.数据库索引
(1)对数据库表中一列或多列的值进行排序的一种存储的数据结构
(2)本质:通过不断地缩小想要获取数据的范围来筛选出最终想要的结果,同时把随机的事件变成顺序的事件提高了查询效率,实际是因为减少了查询过程中的I/O次数
(3)应用层次划分索引
①普通索引index:一个索引只包含单个列,但一个表可以有多个单列索引
②唯一索引unique:索引值必须唯一,不能重复,但允许有空值,如果是主键索引primary key:主键用于唯一标识每一行数据且只能有一个,不可为空,不可重复
联合索引:一个索引包含多个列
(4)从表记录的排列顺序与索引的排列顺序是否一致来划分
聚集索引:表记录的排列顺序与索引的排列顺序一致,查询效率快,新增慢因为需要对数据页重新排序,叶子节点存储的是表中的数据
非聚集索引:表记录的排列顺序与索引的排列顺序不一致,叶子节点存储的是主键和索引列
(4)索引类型
①B+树索引:关系型数据库默认索引类型,支持精确查找和范围查找,查询次数更少,查询效率更高
②哈希索引:用索引列的值计算该值的hashCode,然后在hashCode相应的位置存执该值所在行数据的物理位置,因为使用散列算法,因此访问速度非常快,但是一个值只能对应一个hashCode,而且是散列的分布方式,因此哈希索引要求完全匹配
(5)索引失效原因:
多列索引违反最左前缀法则:查询从索引的最左列开始并且不跳过索引的列
列类型不匹配:当查询中的列类型与索引的列类型不匹配时,数据库无法使用索引
索引上进行了函数或表达式的使用
索引范围条件右边的列失效:如大于小于号,不等于,like以通配符开头、or的使用
索引参与了排序或分组
非前缀索引的使用:索引是创建在多列上,但查询中只使用了索引的部分列
数据分布不均匀
(6)缺点:索引文件占用磁盘空间、写性能下降(需更新索引)、维护开销12.数据库(1)第一范示(1NF):列不可拆
(2)第二范示(2NF):主键唯一
(3)第三范示(2NF):外键关联,避免冗余
三、系统由单体模式切换为分布式系统,数据库需要做什么(可扩展、负载均衡、性能要求)
(1)分库分表:
①垂直拆分:根据不同的业务属性拆分成不同的数据表,用户信息、订单信息、商品信息
②水平拆分:将单一数据库拆分成多个数据库实例,每个实例处理不同的数据分片
(2)主从复制
(3)数据分片
(4)
(1)SQL优化
(2)添加合适的索引
(3)分区
(4)主从复制、读写分离
(5)缓存银弹
(6)部署集群
(7)向上扩展(硬件)
(8)分库分表
①垂直拆分:垂直分库(根据业务耦合性,将关联度低的不同表存储在不同的数据库)、垂直分表(新增扩展表)
②水平拆分:库内分表、分库分表:将同一个表按不同的条件分散到多个数据库或多个表中,每个表中只包含一部分数据,从而使得单个表的数据量变小,达到分布式的效果
(9)使用非关系型数据库
四、Redis
1.优点:完全基于内存,所以读写速度快,所以被广泛应用于缓存,也常用作分布式,因此瓶颈最有可能是机器内存的大小或者网络带宽,而不是CPU
2.基于C语言实现,单线程模型避免了不必要的上下文切换及资源竞争
3.支持的数据类型:string、list、hash、set、sortedset
4.数据存储方式:
Redis 将数据存储在内存中,因此当redis重启或故障时存在数据丢失的情况,所以需要将内存中的数据持久化存储到磁盘。
Redis 提供了两种方式进行持久化,分别是RDB(快照(Snapshot))和日志(AOF)
RDB:根据指定的规则,定时将数据写入磁盘中,但因为操作系统缓存机制,数据并没有直接写入磁盘中,而是进入硬盘缓存,一般每隔30s写入一次磁盘
AOF:将每次的执行命令记录下来
5. Redis通过【主从复制(数据持久化)、哨兵模式、集群模式】保证高可用
五、缓存
1.缓存与数据库
(1)先操作数据库和先操作缓存都会存在脏数据的情况,但是相比之下,先操作数据库,再操作缓存是更优的方式,即使在并发极端情况下,也只会出现很小量的脏数据
(2)如何保证缓存与数据库的数据一致性:分布式锁
2.缓存失效比更新缓存,数据一致性更高
3.缓存雪崩
(1)大量的热点 key 设置了相同的过期时间,导致缓存在同一时刻全部失效,造成瞬时数据库请求量大,压力骤增引起雪崩,甚至导致数据库被打挂
(2)解决
①设置阶梯形的过期时间
②热点数据设置不过期
③加互斥锁
4.缓存击穿
(1)某个存在的key,在缓存过期的一瞬间,同时有大量的请求打进来
(2)解决
①加互斥锁
②直接将热点数据缓存设置为不过期,然后由定时任务去异步加载数据,更新缓存
5.缓存穿透
(1)访问一个缓存和数据库都不存在的 key,此时会直接打到数据库上,并且查不到数据,没法写缓存,所以下一次同样会打到数据库上
(2)解决
①设置缓存空值
②布隆过滤器
6.多级缓存:浏览器本地内存缓存、浏览器本地磁盘缓存、服务端本地内存缓存、服务端网络缓存(redis)
7.缓存测试重点
缓存设置、删除、读取是否正常
缓存与数据库的数据一致性(处理顺序)
缓存更新的原子性
缓存过期时间
并发时缓存是否加锁处理
热点数据的选取是否正确
六、分布式锁
1.CAP原则:指在一个分布式系统中,一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance),CP模型/AP模型(最多只能同时实现两点,不可能三者兼顾)
2.目的:通过一种跨机器的互斥机制解决分布式系统中多线程、多进程并且共享资源分布在不同机器上的问题
3.请求链路:app—网管—nginx—redis/zk(获取分布式锁)—业务层—DAO—DB
4.实现方式
基于缓存(Redis等):AP模型
基于Zookeeper实现分布式锁:CP模型,保证数据强一致性
基于ETCD实现分布式锁:CP模型
5.测试分布式锁场景
加锁成功:配置超时时间,同样的请求重复调用,只有首次才能成功
加锁失败
锁释放(手动或超时自动)
七、消息队列
1.把要传输的消息放在队列中
2.主要解决问题(作用)
(1)异步:解决数据一致性,对数据实时性要求不高
(2)削峰/限流:将短时间内高并发产生的事务存储在消息队列中(可直接抛弃无效请求),从而降低并发事务(秒杀)
(3)解耦:交互系统之间没有直接调用关系,只通过消息传输(库存变动推送商详)
3.消费模型
(1)队列模型:
①本质:一发(生产者)一存(队列)一消费(消费者),允许多个生产者发送消息到队列中,但一条消息只允许被消费一次,消费完即删除
(2)发布订阅模型(支持一对一、一对多、广播):订阅者先订阅主题,每一个订阅者都可以收到同一个订阅主题的全量消息(即一个消息可被多次消费),消费者按照先进先出的规则进行消费
4.主流中间件:
MQ:数据可靠性、实时性要求高,性能一般
kafka:性能高,数据可靠性要求低,主要用于日志传输
5.应用场景
分布式事务消息:对数据实时性要求不高(异步),解决数据一致性,例如打包出库时wms出库,tms运单初始化
严格的顺序消费:交易订单创建、支付、完成
定时消息:延迟消费,发送消息时,指定消费的时间点,消费失败后,指定下次重试的时间点,更新物流轨迹
6.测试重点
消息丢失:生产者丢失、消息队列丢失、消费者丢失
消息幂等:数据消费重复问题,生产者多发、消费者多收
性能:模拟大量消息堆积,检测消费能力
八、将单体应用改为集群部署后,QPS 仍然过高
(1)水平扩展:增加集群中的节点数量,将负载分散到多个节点上。可通过添加更多的服务器来增加整个系统的处理能力。
(2)缓存优化:使用缓存来减轻数据库的负载,减少对数据库的频繁读写操作。将热点数据缓存到内存中,以提高访问速度和减轻数据库的负载。
(3)异步处理:将一些耗时的操作转变为异步处理,通过消息队列或任务队列将请求放入队列中,然后异步处理。这样可以减少请求的响应时间,提高系统的吞吐量。mq异步、削峰、解耦
(4)数据库优化:对数据库进行优化,包括索引的添加、查询语句的优化、数据库参数的调整等,以提高数据库的查询性能和响应速度。
(5)请求限流与熔断:通过限制并发请求数或者设置 QPS 限制来控制系统的负载。可以使用限流算法、熔断机制等来保护系统免受过高的请求压力。
(6)异地部署与负载均衡:将服务器部署在不同的地理位置,通过负载均衡将请求分发到不同的服务器上,提高系统的可用性和负载均衡性。
(7)代码优化与性能调优:对代码进行优化,减少不必要的计算和数据库访问,提高代码的执行效率。可以使用性能分析工具来找到性能瓶颈并进行优化。
操作系统
一、操作系统的原子性
1. 原子性是指一个/组操作被视为一个不可分割的整体,只能完全执行/完全不执行,无中间状态
2. 适用场景:是并发控制的一个重要概念,特别是在多线程或多进程的环境下同时访问和修改共享资源时。如果不保证操作的原子性,可能会导致数据不一致等问题。
3. 如何保证原子性
(3)第三范示(2NF):外键关联,避免冗余
三、系统由单体模式切换为分布式系统,数据库需要做什么(可扩展、负载均衡、性能要求)
(1)分库分表:
①垂直拆分:根据不同的业务属性拆分成不同的数据表,用户信息、订单信息、商品信息
②水平拆分:将单一数据库拆分成多个数据库实例,每个实例处理不同的数据分片
(2)主从复制
(3)数据分片
(4)
(1)SQL优化
(2)添加合适的索引
(3)分区
(4)主从复制、读写分离
(5)缓存银弹
(6)部署集群
(7)向上扩展(硬件)
(8)分库分表
①垂直拆分:垂直分库(根据业务耦合性,将关联度低的不同表存储在不同的数据库)、垂直分表(新增扩展表)
②水平拆分:库内分表、分库分表:将同一个表按不同的条件分散到多个数据库或多个表中,每个表中只包含一部分数据,从而使得单个表的数据量变小,达到分布式的效果
(9)使用非关系型数据库
四、Redis
1.优点:完全基于内存,所以读写速度快,所以被广泛应用于缓存,也常用作分布式,因此瓶颈最有可能是机器内存的大小或者网络带宽,而不是CPU
2.基于C语言实现,单线程模型避免了不必要的上下文切换及资源竞争
3.支持的数据类型:string、list、hash、set、sortedset
4.数据存储方式:
Redis 将数据存储在内存中,因此当redis重启或故障时存在数据丢失的情况,所以需要将内存中的数据持久化存储到磁盘。
Redis 提供了两种方式进行持久化,分别是RDB(快照(Snapshot))和日志(AOF)
RDB:根据指定的规则,定时将数据写入磁盘中,但因为操作系统缓存机制,数据并没有直接写入磁盘中,而是进入硬盘缓存,一般每隔30s写入一次磁盘
AOF:将每次的执行命令记录下来
5. Redis通过【主从复制(数据持久化)、哨兵模式、集群模式】保证高可用
五、缓存
1.缓存与数据库
(1)先操作数据库和先操作缓存都会存在脏数据的情况,但是相比之下,先操作数据库,再操作缓存是更优的方式,即使在并发极端情况下,也只会出现很小量的脏数据
(2)如何保证缓存与数据库的数据一致性:分布式锁
2.缓存失效比更新缓存,数据一致性更高
3.缓存雪崩
(1)大量的热点 key 设置了相同的过期时间,导致缓存在同一时刻全部失效,造成瞬时数据库请求量大,压力骤增引起雪崩,甚至导致数据库被打挂
(2)解决
①设置阶梯形的过期时间
②热点数据设置不过期
③加互斥锁
4.缓存击穿
(1)某个存在的key,在缓存过期的一瞬间,同时有大量的请求打进来
(2)解决
①加互斥锁
②直接将热点数据缓存设置为不过期,然后由定时任务去异步加载数据,更新缓存
5.缓存穿透
(1)访问一个缓存和数据库都不存在的 key,此时会直接打到数据库上,并且查不到数据,没法写缓存,所以下一次同样会打到数据库上
(2)解决
①设置缓存空值
②布隆过滤器
6.多级缓存:浏览器本地内存缓存、浏览器本地磁盘缓存、服务端本地内存缓存、服务端网络缓存(redis)
7.缓存测试重点
缓存设置、删除、读取是否正常
缓存与数据库的数据一致性(处理顺序)
缓存更新的原子性
缓存过期时间
并发时缓存是否加锁处理
热点数据的选取是否正确
六、分布式锁
1.CAP原则:指在一个分布式系统中,一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance),CP模型/AP模型(最多只能同时实现两点,不可能三者兼顾)
2.目的:通过一种跨机器的互斥机制解决分布式系统中多线程、多进程并且共享资源分布在不同机器上的问题
3.请求链路:app—网管—nginx—redis/zk(获取分布式锁)—业务层—DAO—DB
4.实现方式
基于缓存(Redis等):AP模型
基于Zookeeper实现分布式锁:CP模型,保证数据强一致性
基于ETCD实现分布式锁:CP模型
5.测试分布式锁场景
加锁成功:配置超时时间,同样的请求重复调用,只有首次才能成功
加锁失败
锁释放(手动或超时自动)
七、消息队列
1.把要传输的消息放在队列中
2.主要解决问题(作用)
(1)异步:解决数据一致性,对数据实时性要求不高
(2)削峰/限流:将短时间内高并发产生的事务存储在消息队列中(可直接抛弃无效请求),从而降低并发事务(秒杀)
(3)解耦:交互系统之间没有直接调用关系,只通过消息传输(库存变动推送商详)
3.消费模型
(1)队列模型:
①本质:一发(生产者)一存(队列)一消费(消费者),允许多个生产者发送消息到队列中,但一条消息只允许被消费一次,消费完即删除
(2)发布订阅模型(支持一对一、一对多、广播):订阅者先订阅主题,每一个订阅者都可以收到同一个订阅主题的全量消息(即一个消息可被多次消费),消费者按照先进先出的规则进行消费
4.主流中间件:
MQ:数据可靠性、实时性要求高,性能一般
kafka:性能高,数据可靠性要求低,主要用于日志传输
5.应用场景
分布式事务消息:对数据实时性要求不高(异步),解决数据一致性,例如打包出库时wms出库,tms运单初始化
严格的顺序消费:交易订单创建、支付、完成
定时消息:延迟消费,发送消息时,指定消费的时间点,消费失败后,指定下次重试的时间点,更新物流轨迹
6.测试重点
消息丢失:生产者丢失、消息队列丢失、消费者丢失
消息幂等:数据消费重复问题,生产者多发、消费者多收
性能:模拟大量消息堆积,检测消费能力
八、将单体应用改为集群部署后,QPS 仍然过高
(1)水平扩展:增加集群中的节点数量,将负载分散到多个节点上。可通过添加更多的服务器来增加整个系统的处理能力。
(2)缓存优化:使用缓存来减轻数据库的负载,减少对数据库的频繁读写操作。将热点数据缓存到内存中,以提高访问速度和减轻数据库的负载。
(3)异步处理:将一些耗时的操作转变为异步处理,通过消息队列或任务队列将请求放入队列中,然后异步处理。这样可以减少请求的响应时间,提高系统的吞吐量。mq异步、削峰、解耦
(4)数据库优化:对数据库进行优化,包括索引的添加、查询语句的优化、数据库参数的调整等,以提高数据库的查询性能和响应速度。
(5)请求限流与熔断:通过限制并发请求数或者设置 QPS 限制来控制系统的负载。可以使用限流算法、熔断机制等来保护系统免受过高的请求压力。
(6)异地部署与负载均衡:将服务器部署在不同的地理位置,通过负载均衡将请求分发到不同的服务器上,提高系统的可用性和负载均衡性。
(7)代码优化与性能调优:对代码进行优化,减少不必要的计算和数据库访问,提高代码的执行效率。可以使用性能分析工具来找到性能瓶颈并进行优化。
操作系统
一、操作系统的原子性
1. 原子性是指一个/组操作被视为一个不可分割的整体,只能完全执行/完全不执行,无中间状态
2. 适用场景:是并发控制的一个重要概念,特别是在多线程或多进程的环境下同时访问和修改共享资源时。如果不保证操作的原子性,可能会导致数据不一致等问题。
3. 如何保证原子性
原子操作:指一个操作在执行过程中不可被打断,要么执行完全,要么不执行,不会出现中间状态。通常由底层硬件指令提供支持,如比较并交换(Compare-and-Swap)指令。
锁(Lock):锁是一种同步机制,用于保护共享资源的访问,以确保原子性。常见的锁包括互斥锁、读写锁、自旋锁等。
事务(Transaction):事务是一组操作的集合,要么全部执行,要么全部回滚。在数据库管理系统中,事务通常用于保证一组操作的原子性。如果事务中任何一个操作失败,整个事务将被回滚。
二、虚拟内存和物理内存
1.物理内存:指计算机实际的硬件内存。计算机用于存储正在运行的程序和数据的地方,可以直接被处理器访问。物理内存的大小通常是有限的,取决于计算机硬件的能力。
2.虚拟内存是计算机系统为了扩展可用内存而使用的一种技术。它提供了一个抽象的内存概念,使得计算机能够运行比物理内存更大的程序
3.页面置换算法是用于管理虚拟内存的重要技术。当物理内存不足以容纳全部需要的页面时,需要将某些页面从内存中置换出去,以便为新的页面腾出空间
web端
一、拥塞控制
当网络中的流量超过了网络的处理能力时,就会发生拥塞,拥塞会导致网络性能下降、延迟增加、丢包率增加等问题。
拥塞控制是一种网络流量控制的技术,通过控制发送方的发送速率,使得网络中的流量不会超过网络的处理能力,以避免拥塞的发生
二、RPC:远程过程调用
1.RPC是一种计算机通信协议,用于在分布式系统中实现不同计算机之间的远程通信和函数调用,允许客户端通过网络请求调用远程服务端上的函数时,就像调用本地函数一样
2.HSF也是RPC框架
3.rpc可以使用http协议,也可以使用tcp协议
4.RPC调用流程:
(1)定义服务接口并实现服务提供方、服务消费方
(2)配置服务提供者和服务消费者的配置文件
(3)服务容器启动、加载、运行服务提供方
(4)服务提供方启动后把自己提供的服务的唯一ID、 IP 地址和端口信息等注册到注册中心
(5)实现服务消费方,并启动服务消费方后,根据服务唯一 ID 去注册中心查找在线可供调用的服务
(6)注册中心根据消费方所求服务信息返回对应的一个IP列表至消费方的应用缓存,且如若有变更,注册中心将基于长连接推送变更给服务消费方
(7)服务消费方根据一定的策略,比如随机/轮询从提供的可用IP列表中,选择一个服务进行调用,如果调用失败,再选另外一个服务调用
(8)服务消费方和提供方,在内存中累计调用次数和调用时间,定时发送一次统计数据到监控中心
(9)RPC 通过监控中心,监控服务健康状况,控制服务线上扩展和上下线
5.服务掉线:「主动下线」和「心跳检测」
6.注册中心集群挂掉,服务提供方与消费方是否还可以通信
(1)数据库挂了,注册中心还是能用的(本地缓存)
(2)注册中心挂了
①一台机器挂了,会选举出集群中的其他机器作为 Master 继续提供服务
②整个集群都挂了:旧服务依旧可以调用方
三、OSI与TCP/IP 模型
1.OSI七层:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层
2.TCP/IP五层:物理层、数据链路层、网络层、传输层、应用层
3.应用层:HTTP、SMTP、DNS、FTP
4.传输层:TCP 、UDP
5.网络层:ICMP 、IP、路由器、防火墙
6.数据链路层:网卡、网桥、交换机
7.物理层:中继器、集线器
锁(Lock):锁是一种同步机制,用于保护共享资源的访问,以确保原子性。常见的锁包括互斥锁、读写锁、自旋锁等。
事务(Transaction):事务是一组操作的集合,要么全部执行,要么全部回滚。在数据库管理系统中,事务通常用于保证一组操作的原子性。如果事务中任何一个操作失败,整个事务将被回滚。
二、虚拟内存和物理内存
1.物理内存:指计算机实际的硬件内存。计算机用于存储正在运行的程序和数据的地方,可以直接被处理器访问。物理内存的大小通常是有限的,取决于计算机硬件的能力。
2.虚拟内存是计算机系统为了扩展可用内存而使用的一种技术。它提供了一个抽象的内存概念,使得计算机能够运行比物理内存更大的程序
3.页面置换算法是用于管理虚拟内存的重要技术。当物理内存不足以容纳全部需要的页面时,需要将某些页面从内存中置换出去,以便为新的页面腾出空间
web端
一、拥塞控制
当网络中的流量超过了网络的处理能力时,就会发生拥塞,拥塞会导致网络性能下降、延迟增加、丢包率增加等问题。
拥塞控制是一种网络流量控制的技术,通过控制发送方的发送速率,使得网络中的流量不会超过网络的处理能力,以避免拥塞的发生
二、RPC:远程过程调用
1.RPC是一种计算机通信协议,用于在分布式系统中实现不同计算机之间的远程通信和函数调用,允许客户端通过网络请求调用远程服务端上的函数时,就像调用本地函数一样
2.HSF也是RPC框架
3.rpc可以使用http协议,也可以使用tcp协议
4.RPC调用流程:
(1)定义服务接口并实现服务提供方、服务消费方
(2)配置服务提供者和服务消费者的配置文件
(3)服务容器启动、加载、运行服务提供方
(4)服务提供方启动后把自己提供的服务的唯一ID、 IP 地址和端口信息等注册到注册中心
(5)实现服务消费方,并启动服务消费方后,根据服务唯一 ID 去注册中心查找在线可供调用的服务
(6)注册中心根据消费方所求服务信息返回对应的一个IP列表至消费方的应用缓存,且如若有变更,注册中心将基于长连接推送变更给服务消费方
(7)服务消费方根据一定的策略,比如随机/轮询从提供的可用IP列表中,选择一个服务进行调用,如果调用失败,再选另外一个服务调用
(8)服务消费方和提供方,在内存中累计调用次数和调用时间,定时发送一次统计数据到监控中心
(9)RPC 通过监控中心,监控服务健康状况,控制服务线上扩展和上下线
5.服务掉线:「主动下线」和「心跳检测」
6.注册中心集群挂掉,服务提供方与消费方是否还可以通信
(1)数据库挂了,注册中心还是能用的(本地缓存)
(2)注册中心挂了
①一台机器挂了,会选举出集群中的其他机器作为 Master 继续提供服务
②整个集群都挂了:旧服务依旧可以调用方
三、OSI与TCP/IP 模型
1.OSI七层:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层
2.TCP/IP五层:物理层、数据链路层、网络层、传输层、应用层
3.应用层:HTTP、SMTP、DNS、FTP
4.传输层:TCP 、UDP
5.网络层:ICMP 、IP、路由器、防火墙
6.数据链路层:网卡、网桥、交换机
7.物理层:中继器、集线器
四、TCP与UDP的区别
1.TCP:传输控制协议
(1)面向连接、可靠、有序、字节流、流量控制和拥塞控制技术
(2)传输效率慢、所需资源多
(3)适用场景:文件、数据库传输、网页浏览等
2.UDP:用户数据报协议
(1)无连接、不可靠、无序、数据报文段
(2)传输效率快、低延迟(因为不需要建立连接)、可靠性低
(3)适用场景:音频、视频的实时传输
3.基于TCP的协议:HTTP、FTP、SMTP
4.基于UDP的协议:RIP、DNS、SNMP
五、浏览器输入URL过程
1. 进行DNS解析:通过域名DNS查找对应的IP地址(通过浏览器缓存、路由器缓存、DNS缓存一次查找),因为ip地址过长,url更方便记忆
2. 得到ip地址后,将本地请求封装成HTTP请求包:请求头、请求行、空行、消息体
3. 因为HTTP依赖TCP协议实现,所以会进一步将HTTP请求封装成TCP请求数据包
2. 建立TCP连接(三次握手)
3. 发送HTTP请求给服务端
4. 服务器处理请求并返回HTTP报文:状态头、响应报头、空行、消息体
5. 断开TCP连接(四次挥手)
5. 浏览器渲染并结束
TCP三次握手
(1)第一次握手:客户端—发送带有SYN标志的数据包(TCP包:包含一个随机生成的初始序列号ISN)—服务端:表示客户端请求建立连接,客户端进入syn_sent状态
(2)第二次握手:服务端—发送带有SYN+ACK标志(ACK字段为客户端发送的ISN加1)的TCP包—客户端:表示同意建立连接,服务端进入syn_rcvd状态
(3)第三次握手:客户端—发送带有ACK标记(将服务端发送的ISN加1作为ACK字段的值发送回去)的数据包—服务端:表示已收到了服务端的确认,连接进入Established状态
(4)为什么三次:两次握手只能保证单向连接是畅通的,三次可保证客户端与服务端同时具备发送、接收数据的能力,从而确保可靠性
TCP四次挥手
(1)第一次挥手:客户端—发送带有FIN标志的TCP包—服务端:表示客户端已经没有要发送的数据了,请求关闭连接
(2)第二次挥手:服务端—发送带有ACK标志的TCP包—客户端:表示服务端已经收到了关闭请求
(3)第三次挥手:服务端—发送带有FIN标记的TCP包—客户端:表示服务端已经准备关闭连接
(4)第四次挥手:客户端—发送带有ACK标志的TCP包—服务端:表示客户端已经收到了关闭请求
(5)四次挥手确保客户端和服务器都能正确地关闭连接,确保连接的可靠关闭,从而确保可靠性与完整性
六、HTTP常见响应状态码
100:Continue --- 继续。客户端应继续其请求。
200:OK --- 请求成功。一般用于GET与POST请求。
301:Moved Permanently --- 永久重定向。
302:Found --- 暂时重定向。
4开头是客户端错误
400:Bad Request --- 客户端请求的语法错误,服务器无法理解。
401 Unauthorized:请求需要进行身份验证,但客户端未提供有效的身份凭证。
403:Forbideen --- 服务器理解请求客户端的请求,但是拒绝执行此请求,通常由于权限限制。
404:Not Found --- 服务器无法根据客户端的请求找到资源(网页),可通过修改服务器配置或自定义代码。
405: Method Not Allowed:请求中的HTTP
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)