个人整理面试大宝典
个人整理面试超级大宝典
源自于JAVA面试宝典,个人学习笔记等,整合出来的用于面试使用的终极面试大纲,希望可以通过面试
经过再三考虑,考虑到时间成本等关系,所以我打算采用难点补充来完善这份笔记,所以简单的,或者我认为没有什么必要去归纳在这个文章里面的我就不做进来了
重点会集中放在集合,反射,序列化,多线程&并发,JVM,String,spring boot,Mybatis
==这个里面的内容必须!必须!必须!全部背记熟练,而且可以做出延伸和联系==
[toc]
面向对象
面向对象的设计原则
类的设计原则有七个,包括:开闭原则、里氏代换原则、迪米特原则(最少知道原则)、单一职责原则、接口分隔原则、依赖倒置原则、组合/聚合复用原则。
什么是B/S架构,什么是C/S架构
- B/S(Browser/Server),浏览器/服务器
- C/S(Client/Server),客户端/服务器
Java语言的特点
面向对象
跨平台
可靠安全
支持多线程
JDK(Java Development Kit)是开发工具包(含编译器),JRE(Java Runtime Environment)是运行环境(含JVM),JVM(Java Virtual Machine)是执行字节码的虚拟机。
- JDK = 开发工具(javac等) + JRE
- JRE = 运行库 + JVM
- JVM = 执行Java程序的虚拟引擎
面向对象和面向过程的区别
面向过程:是一种以过程为中心的编程思想。把事情拆分成几个步骤,然后按照一定的顺序执行。
面向过程:就是把现实中的事物都抽象为“对象”。每个对象是唯一的,且都可以拥有它的属性与行为。我们就可以通过调用这些对象的方法、属性去解决问题
java中常见的数据结构
什么是数据结构,数据结构是指,计算机保存,组织数据的方式
在java中的数据结构有以下几种:
1.线性表(ArrayList)
2.链表(LinkedList)
3.栈(Stack)
4.队列(Queue)
5.图(Map)
6.树(Tree
java中的几种数据类型
基本数据类型:
整形:byte,short,int,long
浮点型:float,double
字符型:char
布尔型:boolean
引用数据类型:
类(Class)、接口(Interface)、数组(Array)
讲讲什么是显示转换,什么是隐式转换
显示转换就是类型强转,把一个大类型的数据强制赋值给小类型的数据;隐式转换就是大范围的变量能够接受小范围的数据;隐式转换和显式转换其实就是自动类型转换和强制类型转换。
什么是拆装箱机制?什么是包装类?为什么要使用包装类
装箱就是自动将基本数据类型转换为包装器类型,通过调用方法integer的valueOf方法,拆箱就是自动将包装器类型转换为基本数据类型,与装箱相反
java中的包装类都是哪些
byte:Byte,short:Short,int:Integer,long:Long,float:Float,double:Double,char:Character ,boolean:Boolean
一个java类中包含哪些内容
属性、方法、内部类、构造方法、代码块。
面向对象的特征有哪些方面?
1. 抽象 (Abstraction)
抽象是将一类对象的共同特征提取出来构造类的过程,包括:
- 数据抽象:提取对象的属性
- 行为抽象:提取对象的方法
特点:
- 只关注对象”有什么”属性和行为,不关注”如何实现”
- 例如:定义”汽车”类时,只关心它有”颜色”、”品牌”等属性和”启动”、”加速”等方法,不关心这些方法的具体实现
2. 封装 (Encapsulation)
封装是将数据和操作数据的方法绑定在一起,并隐藏内部实现细节:
- 对数据的访问只能通过定义好的接口
- 隐藏一切可隐藏的实现细节,只暴露必要的接口
特点:
- 提高了安全性和易用性
- 例如:全自动洗衣机封装了所有洗衣流程,用户只需按几个按钮
3. 继承 (Inheritance)
继承是从已有类派生出新类的机制:
- 被继承的类称为父类/超类/基类
- 新类称为子类/派生类
特点:
- 子类继承父类的属性和方法
- 可以实现代码复用和扩展
- 是封装程序中可变因素的重要手段
4. 多态 (Polymorphism)
多态是指同一操作作用于不同对象可以有不同的行为表现:
多态类型
- 编译时多态(静态多态):
- 通过方法重载(overload)实现
- 在编译时确定调用哪个方法
- 运行时多态(动态多态):
- 通过方法重写(override)实现
- 在运行时根据对象类型确定调用哪个方法
实现多态的条件
- 方法重写(子类重写父类方法)
- 对象造型(父类引用指向子类对象)
多态示例
1 | // 父类 |
访问修饰符public,private,protected,以及不写时的区别?
| 修饰符 | 当前类 | 同包 | 子类 | 其他包 |
|---|---|---|---|---|
| public | ✓ | ✓ | ✓ | ✓ |
| protected | ✓ | ✓ | ✓ | ✗ |
| default | ✓ | ✓ | ✗ | ✗ |
| private | ✓ | ✗ | ✗ | ✗ |
类的成员不写访问修饰时默认为 default。默认对于同一个包中的其他类相当于公 开(public),对于不是同一个包中的其他类相当于私有(private)。受保护 (protected)对子类相当于公开,对不是同一包中的没有父子关系的类相当于私 有。Java 中,外部类的修饰符只能是 public 或默认,类的成员(包括内部类)的 修饰符可以是以上四种。
String是最基本的数据类型吗?
不是。Java 中的基本数据类型只有 8 个:byte、short、int、long、float、double、 char,boolean;除了基本类型(primitie type),剩下的都是引用类型(reference type),Java 5 以后引入的枚举类型也算是一种比较特殊的引用型。
方法重写(Override)与方法重载(Overload)的区别
方法重写(Override)
定义
在子类中重新定义父类已有的方法,方法签名完全相同但实现不同。
特点
- 作用范围:发生在父类与子类之间(继承关系)
- 方法签名:
- 方法名必须相同
- 参数列表必须相同
- 返回类型相同或为父类方法返回类型的子类(协变返回类型)
- 访问权限:
- 子类方法的访问修饰符不能比父类更严格
- 即访问权限要≥父类方法(public > protected > default > private)
- 异常处理:
- 不能抛出比父类方法更宽泛的检查异常
- 可以抛出相同、更具体或不抛出检查异常
- 其他限制:
- 不能重写被final、static或private修饰的方法
示例
1 | class Animal { |
方法重载(Overload)
定义
在同一个类中,多个方法使用相同名称但参数列表不同。
特点
- 作用范围:发生在同一个类中(或父子类之间)
- 方法签名:
- 方法名相同
- 参数列表必须不同(类型、数量或顺序不同)
- 返回类型:
- 可以相同也可以不同
- 不能仅通过返回类型不同来区分重载方法
- 访问权限:可以任意修改,没有限制
- 异常处理:可以抛出任意异常,没有限制
示例
1 | class Calculator { |
重写与重载的关键区别
| 特性 | 重写(Override) | 重载(Overload) |
|---|---|---|
| 作用范围 | 不同类(父子类) | 同一个类 |
| 方法签名 | 必须完全相同 | 必须参数列表不同 |
| 返回类型 | 相同或子类 | 可以不同 |
| 访问权限 | 不能比父类更严格 | 可以任意修改 |
| 异常处理 | 不能抛出更宽泛的检查异常 | 可以抛出任意异常 |
| 多态类型 | 运行时多态(动态绑定) | 编译时多态(静态绑定) |
| 目的 | 改变已有方法的行为 | 提供同一功能的不同实现方式 |
记忆技巧
- 重写:垂直关系(父子类),”覆盖”父类方法
- 重载:水平关系(同类中),”扩展”方法功能
equals与==的区别
==(运算符)
- 比较什么:内存地址(引用类型)或原始值(基本类型)
- 特点:
- 引用类型:判断是否同一个对象
- 基本类型:直接比较数值
- 对Integer等包装类:-128~127有缓存,之外是新对象
equals()(方法)
- 比较什么:对象内容(需重写)
- 特点:
- 默认行为同==(比较地址)
- 通常需要重写(如String、Integer已重写)
- 必须与hashCode()一起重写
✅ 黄金法则
- 永远用equals()比较对象内容
- 常量放前面防NPE:
"abc".equals(str) - 基本类型用==,对象用equals()
- 阿里规范禁止用==比较对象
⚠️ 典型陷阱
1 | Integer a = 100, b = 100; |
java的各种数据默认值
- Byte,short,int,long默认是都是0
- Boolean默认值是false
- Char类型的默认值是’’
- Float与double类型的默认是0.0
- 对象类型的默认值是null
java常用包有哪些
1 | Java.lang |
java中的是值传递还是引用传递
理论上说,java都是引用传递,对于基本数据类型,传递是值的副本,而不是值本身。对于对象类型,传递是对象的引用,当在一个方法操作操作参数的时候,其实操作的是引用所指向的对象。
形参与实参的区别
本质区别
- 形参(parameter):函数定义时声明的虚拟变量,用于接收传入值
- 实参(argument):函数调用时传入的具体值或表达式
五大核心区别
- 生命周期不同
- 形参:函数调用时创建,调用结束销毁
- 实参:在调用前必须已存在且有确定值
- 内存关系
- 基本类型:形参是实参的副本(值传递)
- 引用类型:形参复制实参的引用地址(共享对象)
- 数据流向
- 单向传递:只能实参→形参
- 形参修改不影响实参(除非操作引用对象的属性)
- 匹配规则
- 数量、类型、顺序必须严格一致
- 自动类型转换仅适用于基本类型
- 使用场景
- 形参:只能在函数体内使用
- 实参:可以是常量/变量/表达式/函数返回值
Java关键方法(Object类)
equals():对象相等比较hashCode():哈希值生成toString():对象字符串表示wait()/notify():线程通信clone():对象复制getClass():获取运行时类
值传递 vs 引用传递
基本类型:值传递(形参修改不影响实参)
1
2
3void change(int a) { a = 100; }
int x = 1;
change(x); // x仍为1引用类型:传递引用地址(可修改对象属性)
1
2
3void changeName(Person p) { p.name = "Tom"; }
Person obj = new Person();
changeName(obj); // obj.name变为"Tom"
Stetic关键字有什么作用?
- Static可以修饰内部类、方法、变量、代码块
- Static修饰的类是静态内部类
- Static修饰的方法是静态方法,表示该方法属于当前类的,而不属于某个对象的,静态方法也不被重写,可以直接使用类名来调用。
- 在static方法中不能使用this或者super关键字。
- Static修饰变量是静态变量或者叫类变量,静态变量被所有实例所共享,不会依赖于对象。静态变量在内存中只有一份拷贝,在JVM加载类的时候,只为静态分配一次内存。
- Static修饰的代码块叫静态代码块,通常用来做程序优化的。静态代码块中的代码在整个类加载的时候只会执行一次。静态代码块可以有多个,如果有多个,按照先后顺序依次执行。
final在java中的作用,有哪些用法?
- 被fifinal修饰的类不可以被继承
- 被fifinal修饰的方法不可以被重写
- 被fifinal修饰的变量不可以被改变.如果修饰引用,那么表示引用不可变,引用指向的内容可变.
- 被fifinal修饰的方法,JVM会尝试将其内联,以提高运行效率
- 被fifinal修饰的常量,在编译阶段会存入常量池中.
StringString StringBuffffer 和 StringBuilder 的区别是什么?
在 Java 中,StringBuilder 和 StringBuffer 都是用于修改字符串的类,与 String 类不同,它们的对象可以被多次修改,而不会生成新的未使用对象。
线程安全性
StringBuffer 是线程安全的,因为它的所有公开方法都使用了 synchronized 关键字进行同步。这意味着多个线程可以安全地访问同一个 StringBuffer 实例。例如:
1 | public synchronized StringBuffer append(String str) { |
因此,在单线程环境下,建议使用 StringBuilder,因为它的性能比 StringBuffer 更高。
性能
由于 StringBuilder 没有同步机制,所以它的性能优于 StringBuffer。在需要高性能且不涉及多线程的情况下,StringBuilder 是更好的选择。
缓冲区
StringBuffer 在每次调用 toString 方法时,会直接使用缓存区的 toStringCache 值来构造一个字符串。而 StringBuilder 每次都需要复制一次字符数组,再构造一个字符串。
String类的常用方法有哪些?
- charAt:返回指定索引处的字符
- indexOf():返回指定字符的索引
- replace():字符串替换
- trim():去除字符串两端空白
- split():分割字符串,返回一个分割后的字符串数组
- getBytes():返回字符串的byte类型数组
- length():返回字符串长度
- toLowerCase():将字符串转成小写字母
- toUpperCase():将字符串转成大写字符
- substring():截取字符串
- format():格式化字符串
- equals():字符串比较
java中的继承是单继承还是多继承
Java中既有单继承,又有多继承。对于java类来说只能有一个父类,对于接口来说可以同时继承多个接口
Super与this表示什么?
Super表示当前类的父类对象
This表示当前类的对象
抽象类和接口的区别?
抽象类:
- 抽象方法,只有行为的概念,没有具体的行为实现。使用abstract关键字修饰,没有方法体。子类必须重写这些抽象方法。
- 包含抽象方法的类,一定是抽象类。
- 抽象类只能被继承,一个类只能继承一个抽象类。
接口:
- 全部的方法都是抽象方法,属性都是常量
- 不能实例化,可以定义变量。
- 接口变量可以引用具体实现类的实例
- 接口只能被实现,一个具体类实现接口,必须实现全部的抽象方法
- 接口之间可以多实现
- 一个具体类可以实现多个接口,实现多继承现象
Hashcode的作用
hashCode() 是一个Java中的方法,它返回对象的哈希码(hash code)。hashCode是由对象根据其特征属性计算得出的一个整数值。它用于快速识别对象并在哈希表等数据结构中进行高效的存储和检索。
hashcode的实现机制
hashCode方法可以这样理解:它返回的就是根据对象的内存地址换算出的一个值。这样一来,当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。
HashMap的扩容机制
HashMap 的底层用的是数组。向 HashMap 里不停地添加元素,当数组无法装载更多元素时,就需要对数组进行扩容,以便装入更多的元素;除此之外,容量的提升也会相应地提高查询效率,因为”桶(坑)”更多了嘛,原来需要通过链表存储的(查询的时候需要遍历),扩容后可能就有自己专属的”坑位”了(直接就能查出来)。
数组是无法自动扩容 的,所以如果要扩容的话,就需要新建一个大的数组,然后把之前小的数组的元素复制过去,并且要重新计算哈希值和重新分配桶(重新散列),这个过程也是挺耗时的。
HashMap 的扩容是通过resize 方法 来实现的,JDK 8 中融入了红黑树
一句话总结出来就是,当我们往 HashMap 中不断添加元素时,HashMap 会自动进行扩容操作(条件是元素数量达到负载因子(load factor)乘以数组长度时),以保证其存储的元素数量不会超出其容量限制。
在进行扩容操作时,HashMap 会先将数组的长度扩大一倍,然后将原来的元素重新散列到新的数组中。
由于元素的位置是通过 key 的 hash 和数组长度进行与运算得到的,因此在数组长度扩大后,元素的位置也会发生一些改变。一部分索引不变,另一部分索引为”原索引+旧容量”。
java创建对象的方式有?
new创建新对象
通过反射机制
采用clone机制
通过序列化机制
拷贝和浅拷贝的区别是什么
我的记忆方法是,一个诛九族,一个杀自己
浅拷贝:
被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象.换言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象.
深拷贝:
被复制对象的所有变量都含有与原来的对象相同的值.而那些引用其他对象的变量将指向被复制过的新对象.而不再是原有的那些被引用的对象.换言之.深拷贝把要复制的对象所引用的对象都复制了一遍.
final、finalize()、finally
这三个长的挺像的吧……
性质不同
- final为关键字;
- finalize()为方法;
- finally为区块标志,用于try语句中;
作用
- final为用于标识常量的关键字,final标识的关键字存储在常量池中(在这里final常量的具体用法将在下面进行介绍);
- finalize()方法在Object中进行了定义,用于在对象“消失”时,由JVM进行调用用于对对象进行垃圾回收,类似于C++中的析构函数;用
户自定义时,用于释放对象占用的资源(比如进行I/0操作); - finally{}用于标识代码块,与try{}进行配合,不论try中的代码执行完或没有执行完(这里指有异常),该代码块之中的程序必定会进
行;
==注意在回答的时候请务必展开到final的具体用法上==
JDBC操作的步骤
加载数据库驱动类
打开数据库连接
执行sql语句
处理返回结果
关闭资源
在使用jdbc的时候,如何防止出现sql注入的问题。
使用PreparedStatement类,而不是使用Statement类即可
是否了解连接池,使用连接池的好处
所有的池子作用基本上都差不多,所以一定要公式化记忆,并答出具体的内容
可以说:通过预先创建并维护一定数量的数据库连接,减少频繁创建和销毁连接的开销,提升系统性能和资源利用率。
数据库连接是非常消耗资源的,影响到程序的性能指标。连接池是用来分配、管理、释放数据库连接的,可以使应用程序重复使用同一个数据库连接,而不是每次都创建一个新的数据库连接。通过释放空闲时间较长的数据库连接避免数据库因为创建太多的连接而造成的连接遗漏问题,提高了程序性能。
连接池的作用
- 减少资源消耗:避免每次数据库操作都创建新连接。
- 提升性能:预分配的连接可快速响应请求,降低等待时间。
- 优化资源利用:通过连接池统一管理连接的生命周期,避免无效连接占用资源。
TCP和DUP的区别
TCP:传输控制协议,他是一种面向连接的传输层协议,能够提供高可靠性的通信。
能够保证数据无误,无丢失,无失序,无重复。
适用情况:
1、适合于对传输质量要求较高,以及传输大量数据的通信。
2、在需要可靠数据传输的场合,通常使用TCP协议
3、MSN/QQ等即时通讯软件的用户登录账户管理相关的功能通常采用TCP协议
UDP:用户数据报协议,提供的是非面向连接的、不可靠的数据流传输。
在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接。
UDP不提供可靠性,也不提供报文到达确认、排序以及流量控制等功能。他只是把应用程序传给IP层的数据报发送出去,但是不能保证他们能到达目的地。因此报文可能会丢失、重复以及乱序等。在数据发送前不需要连接,所以可以进行高效率通信,适合于广播/组播式通信中。
TCP的三次握手
第一次握手:客户通过调用connect进行主动打开(active open)。这引起客户TCP发送一个SYN(表示同步)分节(SYN=J),它告诉服务器客户将在连接中发送到数据的初始序列号。并进入SYN_SEND状态,等待服务器的确认。
第二次握手:服务器必须确认客户的SYN,同时自己也得发送一个SYN分节,它含有服务器将在同一连接中发送的数据的初始序列号。服务器以单个字节向客户发送SYN和对客户SYN的ACK(表示确认),此时服务器进入SYN_RECV状态。
第三次握手:客户收到服务器的SYN+ACK。向服务器发送确认分节,此分节发送完毕,客户服务器进入ESTABLISHED状态,完成三次握手。
tcp 为什么要三次握手,两次不行吗?为什么?
TCP采用三次握手是为了确保连接的可靠性和防止历史连接导致的资源浪费,两次握手无法满足这些需求:
通过三次交互确认双方的收发能力。第一次握手验证客户端的发送能力;第二次握手验证服务端的收发能力;第三次握手最终确认客户端的接收能力。
GET请求和POST请求区别
- Post和Get的共同点:
①都是HTTP协议中的两个发送请求的方法,底层都是 基于TCP/IP协议 。 - post
- ①post传递数据,不需要在url中显式出来,而get方法需要在 url中显式 。
②post传输的 数据量大,可以达到2M ,而get传参受url长度限制, 最多传递1024字节 。
③post请求是 将数据传输到服务器端 ,而get请求是为了 从服务器端取数据(为什么get也能传参?为了告诉服务端需要什么数据)
④post在真正接受数据之前会 先将请求头发送给服务器进行确认,然后才真正发送数据 。
- ①post传递数据,不需要在url中显式出来,而get方法需要在 url中显式 。
- Get
- ①get传递的参数在页面可以看见,安全性低。(post更安全,不会作为url的一部分,不会被缓存、保存在服务器日志、以及浏览器浏览记录中)
②GET请求请求 参数在请求行中,没有请求体 。POST请求请求 参数在请求体中 。
③get传参 速度更快 。
④get不能 传递中文,会乱码 ,而post不会出现乱码问题。
- ①get传递的参数在页面可以看见,安全性低。(post更安全,不会作为url的一部分,不会被缓存、保存在服务器日志、以及浏览器浏览记录中)
什么是TomCat?
Tomcat是一个开源免费的轻量级Web服务器,支持Servlet/JSP少量JavaEE规范。Tomcat也被称为Web容器、Servlet容器。Servlet需要依赖于Tomcat才能运行。
什么是Servlet?Servlet由谁来创建?Servlet方法由谁调用?
Servlet是Java提供的一门 动态web 资源开发技术,Servlet由web服务器创建,Servlet方法由web服务器调用。
Servlet的生命周期
对象的生命周期指一个对象从被创建到被销毁的整个过程。
Servlet运行在Servlet容器(web服务器)中,其生命周期由容器来管理,分为4个阶段:
① 加载和实例化:默认情况下,当servlet第一次被访问时,由容器创建servlet对象。
② 初始化:在Servlet实例化之后,容器将调用servlet的init()方法初始化这个对象,完成一些如加载配置文件、创建连接等初始化的工作。该方法只调用一次。
③ 请求处理:每次请求servlet时,Servlet容器都会调用Servlet的service()方法对请求进行处理。
④ 服务终止:当需要释放内存或者容器关闭时,容器就会调用Servlet实例的destroy()方法完成资源的释放。在destroy()方法调用之后,容器会释放这个Servlet实例,该实例随后会被Java的垃圾收集器所回收
什么是请求转发
请求转发(forward):一种在 服务器内部的资源跳转方式。
实现方式req.getRequestDispatcher(“资源B路径”).forward(req,resp);
请求转发资源间共享数据:使用Request对象 void setAttribute(String name,Object o):存储数据到request域中。 Object getAttribute(String name):根据key,获取值。 void removeAttribute(String name):根据key,删除该键值对。
请求转发的特点
① 浏览器地址栏路径不发生变化。
② 只能转发到当前服务器的内部资源。
③ 一次请求,可以在转发的资源间使用request共享数据。
请求重定向的特点
① 浏览器地址栏路径发生变化。
② 可以重定向到任意位置的资源(服务器内部、外部均可)。
③ 两次请求,不能在多个资源使用request共享数据。
Cookie & Session的用途和区别
Cookie和Session都是来完成一次会话内多次请求间 数据共享 的。
区别:
存储位置:Cookie是将数据存储在客户端,Session 将数据存储在服务端。
安全性:Cookie 不安全,Session安全。
数据大小:Cookie最大3KB,Session无大小限制。
存储时间:Cookie可以长期存储,Session 默认30分钟。
服务器性能:Cookie不占服务器资源,Session占用服务器资源。
token和上述两者的区别:
Token是一个令牌,通常用于身份验证和授权。它可以在客户端和服务器之间传递,用于验证用户的身份。与Cookie和Session不同,Token通常是无状态的,即服务器不需要存储Token的状态信息。
Filter是什么,什么作用?
- 概念:Filter表示过滤器,是JavaWeb三大组件(Servlet、Filter、Listener)之一。
- 过滤器可以把对资源的请求 拦截 下来,从而实现一些特殊的功能。
- 过滤器一般完成一些 通用的操作 ,比如:权限控制、统一编码处理、敏感字符处理等等。
Listener是什么?有什么作用?
- 概念:Listener表示监听器,是JavaWeb三大组件(Servlet、Filter、Listener)之一。
- 监听器可以监听就是在application、session、request三个对象创建、销毁或者往其中添加修改删除属性时自动执行代码的功能组件。
AJAX是什么?有什么作用?
AJAX(Asynchronous JavaScript And XML):异步 的JavaScript和XML。
AJAX作用:
① 与服务器进行数据交换:通过AJAX可以给服务器发送请求,并获取服务器响应的数据。(使用了AJAX和服务器进行通信,就可以使用HTML+AJAX来替换JSP页面了)
② 异步交互:可以在 不重新加载整个页面 的情况下,与服务器交换数据并 更新部分网页 的技术,如:搜索联想、用户名是否可用校验等。
OSI七层模型
应用层(Application Layer):这是网络体系结构中的最顶层,提供用户接口和应用程序之间的通信服务。在这一层,用户可以访问各种网络应用程序,如电子邮件、文件传输和远程登录。
表示层(Presentation Layer):该层负责数据的格式化、加密和压缩,以确保数据在不同系统之间的交换是有效的和安全的。它还提供了数据格式转换和语法转换的功能。\
会话层(Session Layer):会话层管理应用程序之间的通信会话,负责建立、维护和终止会话。它还提供了数据的同步和检查点恢复功能,以确保通信的完整性和持续性。
传输层(Transport Layer):传输层为应用程序提供端到端的数据传输服务,负责数据的分段、传输控制、错误恢复和流量控制。它主要使用 TCP(传输控制协议)和 UDP(用户数据报协议)来实现这些功能。
网络层(Network Layer):网络层负责数据包的路由和转发,以及网络中的寻址和拥塞控制。它选择最佳的路径来传输数据包,以确保它们能够从源主机到目标主机进行传输。
数据链路层(Data Link Layer):数据链路层提供点对点的数据传输服务,负责将原始比特流转换为数据帧,并检测和纠正传输中出现的错误。它还控制访问物理媒介的方式,以及数据帧的传输和接收。
物理层(Physical Layer):物理层在物理媒介上传输原始比特流,定义了连接主机的硬件设备和传输媒介的规范。它确保比特流能够在网络中准确地传输,例如通过以太网、光纤和无线电波等媒介。
TCP/IP四层模型
应用层(Application Layer)类似于 OSI 模型中的应用层,负责处理用户与网络应用程序之间的通信。它包括诸如 HTTP、FTP、SMTP 等协议,用于实现不同类型的网络服务和应用。
传输层(Transport Layer):与 OSI 模型中的传输层相对应,提供端到端的数据传输服务。在 TCP/IP 模型中,主要有两个协议:TCP(传输控制协议)和 UDP(用户数据报协议),用于确保可靠的数据传输和简单的数据传输。
网络层(Internet Layer):相当于 OSI 模型中的网络层,负责数据包的路由和转发。它使用 IP(Internet Protocol)协议来定义数据包的传输路径,并处理不同网络之间的通信。
网络接口层(Link Layer):与 OSI 模型中的数据链路层和物理层相对应,负责管理网络硬件设备和物理媒介之间的通信。它包括以太网、Wi-Fi、蓝牙等各种物理层和数据链路层协议。
静态内部类如何定义,什么叫成员内部类
定义在类内部的静态类,就是静态内部类。
- 静态内部类可以访问外部类所有的静态变量和方法,即使是 private 的也一样。
- 静态内部类和一般类一致,可以定义静态变量、方法,构造方法等。
- 其它类使用静态内部类需要使用“外部类.静态内部类”方式,如下所示:Out.Inner inner = new Out.Inner();inner.print();
- Java集合类HashMap内部就有一个静态内部类Entry。Entry是HashMap存放元素的抽象,HashMap 内部维护 Entry 数组用了存放元素,但是 Entry 对使用者是透明的。像这种和外部类关系密切的,且不依赖外部类实例的,都可以使用静态内部类。
定义在类内部的非静态类,就是成员内部类。
成员内部类不能定义静态方法和变量(final修饰的除外)。这是因为成员内部类是非静态的,
类初始化的时候先初始化静态成员,如果允许成员内部类定义静态变量,那么成员内部类的静态变量初始化顺序是有歧义的。
排序都有哪几种方法?请列举
插入排序
- 直接插入排序
- 希尔排序
选择排序 - 直接选择排序
- 堆排序
交换排序 - 冒泡排序
- 快速排序
并归排序
说出一些常用的类,包,接口,请各举5个
- 常用的类:BufferedReader BufferedWriter FileReader FileWirter String Integer常用的包:java.lang java.awt java.io java.util java.sql
- 常用的接口:Remote List Map Document NodeList
Java 中会存在内存泄漏吗,请简单描述。
理论上 Java 因为有垃圾回收机制( GC)不会存在内存泄露问 题( 这也是 Java 被广泛使用于服务器端编程的一个重要 原因 );然而在实际开发中 ,可能会存在无用但可达的对象,这些对象不能被 GC 回收 ,因此也会导致内存 泄露的发生 。
如何实现对象克隆?
有两种方式:
1). 实现 Cloneable 接口并重写 Object 类中的 clone()方法;
2). 实现 Serializable 接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆
一个”.java”源文件中是否可以包含多个类(不是内部类)?有什么限制?
可以,但一个源文件中最多只能有一个公开类(public class)而且文件名必须和公开类的类名完全保持一致。
集合、泛型
ArrayList和linkedList的区别
Array(数组)是基于索引(index)的数据结构,它使用索引在数组中搜索和读取数据是很快的。
Array获取数据的时间复杂度是O(1),但是要删除数据却是开销很大,因为这需要重排数组中的所有数据,(因为删除数据以后, 需要把后面所有的数据前移)
缺点: 数组初始化必须指定初始化的长度, 否则报错
- List—是一个有序的集合,可以包含重复的元素,提供了按索引访问的方式,它继承Collection。
- List有两个重要的实现类:ArrayList和LinkedList
ArrayList: 可以看作是能够自动增长容量的数组 - ArrayList的toArray方法返回一个数组
- ArrayList的asList方法返回一个列表
- ArrayList底层的实现是Array, 数组扩容实现
- LinkList是一个双链表,在添加和删除元素时具有比ArrayList更好的性能.但在get与set方面弱于ArrayList.当然,这些对比都是指数据量很大或者操作很频繁。【相对而言】
HashMap和HashTable的区别
这个部分的引申部分可以很多,抓到什么部分就拐进去多讲点,但是自己不要硬撑,讲慢点过过脑子
1、两者父类不同
HashMap是继承自AbstractMap类,而Hashtable是继承自Dictionary类。不过它们都实现了同时实现
了map、Cloneable(可复制)、Serializable(可序列化)这三个接口。
2、对外提供的接口不同
Hashtable比HashMap多提供了elments() 和contains() 两个方法。
elments() 方法继承自Hashtable的父类Dictionnary。elements() 方法用于返回此Hashtable中的
value的枚举。
contains()方法判断该Hashtable是否包含传入的value。它的作用与containsValue()一致。事实上,
contansValue() 就只是调用了一下contains() 方法。
3、对null的支持不同
Hashtable:key和value都不能为null。
HashMap:key可以为null,但是这样的key只能有一个,因为必须保证key的唯一性;可以有多个key
值对应的value为null。
4、安全性不同
HashMap是线程不安全的,在多线程并发的环境下,可能会产生死锁等问题,因此需要开发人员自己
处理多线程的安全问题。
Hashtable是线程安全的,它的每个方法上都有synchronized 关键字,因此可直接用于多线程中。
虽然HashMap是线程不安全的,但是它的效率远远高于Hashtable,这样设计是合理的,因为大部分的使用场景都是单线程。当需要多线程操作的时候可以使用线程安全的ConcurrentHashMap。
ConcurrentHashMap虽然也是线程安全的,但是它的效率比Hashtable要高好多倍。因为
ConcurrentHashMap使用了分段锁,并不对整个数据进行锁定。
5、初始容量大小和每次扩充容量大小不同
6、计算hash值的方法不同
Collection包结构,与Collections的区别
- Collection是集合类的上级接口,子接口有 Set、List、LinkedList、ArrayList、Vector、Stack、Set;
- Collections是集合类的一个帮助类, 它包含有各种有关集合操作的静态多态方法,用于实现对各种集合的搜索、排序、线程安全化等操作。此类不能实例化,就像一个工具类,服务于Java的Collection框架。
简单的说一下List,Set,Map三者的区别
List(对付顺序的好帮手): List接口存储一组不唯一(可以有多个元素引用相同的对象),有序的对象
最常用实现类ArrayList:基于动态数组实现,随机访问元素快,增删元素慢。LinkedList:基于双向链表实现,适合频繁插入和删除操作,特别是操作列表两端元素时,但随机访问元素慢。Vector:类似于 ArrayList ,但线程安全Set(注重独一无二的性质):不允许重复的集合。不会有多个元素引用相同的对象。
常见实现类HashSet:基于哈希表实现,提供快速的插入、删除和查找操作,但不保证元素的顺序。LinkedHashSet:是 HashSet 的有序版本,维护插入元素的顺序。TreeSet:基于红黑树实现,存储元素时会自动排序,适合需要排序操作的场景。Map(用Key来搜索的专一): 使用键值对存储。Map会维护与Key有关联的值。两个Key可以引用相同的对象,但Key不能重复,典型的Key是String类型,但也可以是任何对象。
常见实现类HashMap:基于哈希表实现,提供快速的插入、删除和查找操作,但不保证键值对的顺序。LinkedHashMap:是 HashMap 的有序版本,维护键值对的插入顺序。TreeMap:基于红黑树实现,存储键值对时会自动按照键的自然顺序或自定义顺序进行排序。Hashtable:类似于 HashMap,但线程安全。
异常
Java中异常分为哪两种?
可以分为编译时异常和运行时异常这两种大类
异常的处理机制有几种
异常捕捉:try…catch…finally,异常抛出:throws。
他们一个在代码块中,排除特定部分,一个在方法体处抛出
关于异常而言try catch fifinally,try里有return,finally还执行么?
执行,并且finally的执行早于try里面的return
结论:
1、不管有木有出现异常,finally块中代码都会执行;
2、当try和catch中有return时,finally仍然会执行;
3、finally是在return后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,管finally中的代码怎么样,
返回的值都不会改变,任然是之前保存的值),所以函数返回值是在finally执行前确定的;
4、finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值。
Thow与thorws区别
位置不同
- throws 用在函数上,后面跟的是异常类,可以跟多个;而 throw 用在函数内,后面跟的
是异常对象。
功能不同:
- throws 用来声明异常,让调用者只知道该功能可能出现的问题,可以给出预先的处理方
式;throw 抛出具体的问题对象,执行到 throw,功能就已经结束了,跳转到调用者,并
将具体的问题对象抛给调用者。也就是说 throw 语句独立存在时,下面不要定义其他语
句,因为执行不到。 - throws 表示出现异常的一种可能性,并不一定会发生这些异常;throw 则是抛出了异常,
执行 throw 则一定抛出了某种异常对象。 - 两者都是消极处理异常的方式,只是抛出或者可能抛出异常,但是不会由函数去处理异
常,真正的处理异常由函数的上层调用处理。
error和exception有什么区别
error 表示恢复不是不可能但很困难的情况下的一种严重问题。比如说内存溢出。不可能指望程序能处理这样的况 exception 表示一种设计或实现问题。也就是说,它表示如果程序运行正常,从不会发生的情况
IO&& NIO
java中的IO流
Java 中 IO 流分为几种
Java 中 IO 流?
- 按照流的向分,可以分为输入流和输出流;
- 按照操作单元划分,可以划分为字节流和字符流;
- 按照流的角色划分为节点流和处理流。
典型基类
- InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
- OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
java中的IO和NIO的区别
- Java IO和NIO之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的
- Java IO的各种流是阻塞的,Java NIO的非阻塞模式
- Java NIO的选择器允许一个单独的线程来监视多个输入通道
字节流和字符流的区别
以字节为单位输入输出数据,字节流按照8位传输
以字符为单位输入输出数据,字符流按照16位传输
javaNIO
NIO 主要有三大核心部分: Channel(通道), Buffer(缓冲区), Selector。传统 IO 基于字节流和字符流进行操作, 而 NIO 基于 Channel 和Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。 Selector(选择区)用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个线程可以监听多个数据通道。 NIO 和传统 IO 之间第一个最大的区别是, IO 是面向流的, NIO 是面向缓冲区的。
NIO的缓冲区
Java IO 面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据, 需要先将它缓存到一个缓冲区。 NIO 的缓冲导向方法不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有您需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。
反射
Q:除开使用new来创建一个对象,还可以通过那些方法来创建一个对象
A:可以使用反射机制来创建一个对象
通过new创建对象的效率比较高。通过反射时,先找查找类资源,使用类加载器创建,过程比较繁琐,所以效率较低
java反射的作用
反射机制是在运行时,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意个对象,都能够调用其任意一个方法,在java中,只要给定一个类的名字,就可以通过反射机制来获得类的所有信息
反射的实现方式
可以通过四种方式来进行获得一个反射对象
1)Class.forName(“类的路径”);
2)类名.class
3)对象名.getClass()
4)基本类型的包装类,可以调用包装类的Type属性来获得该包装类的Class对象
可以实现java反射的类为:
1)Class:表示正在运行的Java应用程序中的类和接口
注意: 所有获取对象的信息都需要Class类来实现。
2)Field:提供有关类和接口的属性信息,以及对它的动态访问权限。
3)Constructor:提供关于类的单个构造方法的信息以及它的访问权限
4)Method:提供类或接口中某个方法的信息
如何通过反射机制来创建对象实例呢
Class 对象的newInstance()
使用 Class 对象的 newInstance()方法来创建该 Class 对象对应类的实例,但是这种方法要求
该 Class 对象对应的类有默认的空构造器。
调用 Constructor 对象的 newInstance()
先使用 Class 对象获取指定的 Constructor 对象,再调用 Constructor 对象的 newInstance()
方法来创建 Class 对象对应类的实例,通过这种方法可以选定构造方法创建实例。
序列化
什么是Java序列化,如何实现Java序列化?
序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决在对对象流进行读写操作时所引发的问题。
实现序列化的步骤
- 标记可序列化:让类实现
Serializable接口 - 序列化对象:使用
ObjectOutputStream - 反序列化:使用
ObjectInputStream
Java序列化是指将Java对象转换为字节流的过程,以便存储或网络传输。其本质是把对象的内存状态转化为可重建的二进制格式,就像把立体模型拆解成平面图纸。
保存(持久化)对象及其状态到内存或者磁盘
Java 平台允许我们在内存中创建可复用的 Java 对象,但一般情况下,只有当 JVM 处于运行时,这些对象才可能存在,即,这些对象的生命周期不会比 JVM 的生命周期更长。 但在现实应用中,就可能要求在JVM停止运行之后能够保存(持久化)指定的对象,并在将来重新读取被保存的对象。Java 对象序列化就能够帮助我们实现该功能。
序列化对象以字节数组保持-静态成员不保存
使用 Java 对象序列化, 在保存对象时,会把其状态保存为一组字节,在未来, 再将这些字节组装成对象。必须注意地是, 对象序列化保存的是对象的”状态”,即它的成员变量。由此可知,对象序列化不会关注类中的静态变量。
序列化用户远程对象传输
除了在持久化对象时会用到对象序列化之外,当使用 RMI(远程方法调用),或在网络中传递对象时,都会用到对象序列化。 Java序列化API为处理对象序列化提供了一个标准机制,该API简单易用。
注解
注解是什么,Annotation(注解)是 Java 提供的一种对元程序中元素关联信息和元数据(metadata)的途径和方法。 Annatation(注解)是一个接口,程
序可以通过反射来获取指定程序中元素的 Annotation对象,然后通过该 Annotation 对象来获取注解中的元数据信息。
Java中的四种标准元注解(用于注解其他注解的注解)包括:
- @Target - 指定注解可以应用的目标元素类型(如类、方法、字段等)
1 | @Target(ElementType.METHOD) // 只能用在方法上 |
- @Retention - 定义注解的保留策略(源码/编译时/运行时)
1 | @Retention(RetentionPolicy.RUNTIME) // 运行时保留 |
- @Documented - 标记注解是否包含在Javadoc中
1 | @Documented // 生成文档时会包含此注解 |
- @Inherited - 允许子类继承父类的注解
1 | @Inherited // 子类会自动继承此注解 |
这些元注解都位于java.lang.annotation包中,是定义自定义注解的基础构件。
多线程并发
多线程的实现方式
- 继承Thread类;
- 实现Runnable接口;
- 实现Callable接口通过FutureTask包装器来创建Thread线程;
- 使用ExecutorService
伊克斯科一欧的、Callable、Future实现有返回结果的多线程(也就是使用了ExecutorService来管理前面的三种方式)。 - 基于线程池的方式
如何停止一个正在运行的线程
- 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
- 使用stop方法强行终止,但是不推荐这个方法,因为stop和suspend及resume一样都是过期作废的方法。
- 使用interrupt方法中断线程。
线程基本方法
线程相关的基本方法有 wait(线程等待), notify(线程唤醒), notifyAll(线程全唤醒), sleep(线程睡眠), join(等待线程终止), yield(线程让步) 等。
线程等待(wait):调用该方法的线程进入 WAITING 状态,只有等待另外线程的通知或被中断才会返回,需要注意的是调用 wait()方法后, 会释放对象的锁。因此, wait 方法一般用在同步方法或同步代码块中。
线程睡眠(sleep):sleep 导致当前线程休眠,与 wait 方法不同的是 sleep 不会释放当前占有锁,sleep(long)会导致线程进入 TIMED-WATING 状态,而 wait()方法会导致当前线程进入 WATING 状态
线程让步(yield):
yield 会使当前线程让出 CPU 执行时间片,与其他线程一起重新竞争 CPU 时间片。一般情况下,优先级高的线程有更大的可能性成功竞争得到 CPU 时间片, 但这又不是绝对的,有的操作系统对线程优先级并不敏感。
等待线程终止(join):join() 方法,等待其他线程终止,在当前线程中调用一个线程的 join() 方法,则当前线程转为阻塞状态,回到另一个线程结束,当前线程再由阻塞状态变为就绪状态,等待 cpu 的宠幸。
线程唤醒(notify):Object 类中的 notify() 方法, 唤醒在此对象监视器上等待的单个线程,如果所有线程都在此对象上等待,则会选择唤醒其中一个线程,选择是任意的,并在对实现做出决定时发生,线程通过调用其中一个 wait() 方法,在对象的监视器上等待, 直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程,被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争。类似的方法还有 notifyAll() ,唤醒再次监视器上等待的所有线程。
说人话理解版本:
1. wait()、notify()、notifyAll() —— 餐厅等位系统
- wait():就像在餐厅取号后去休息区等待,把座位(锁)让给别人
- notify():服务员叫一个号(随机唤醒一个等待线程)
- notifyAll():服务员大喊”现在有空位了”(唤醒所有等待线程)
关键点:必须先在收银台登记(synchronized块内)才能使用这些方法
2. sleep() —— 手机定时勿扰模式
- 设定闹钟睡1小时(Thread.sleep(1000))
- 睡着时手机还在你手里(不释放锁)
- 闹钟响了才会醒(时间到自动恢复)
3. yield() —— 公交车让座
- 主动站起来说”您坐这儿吧”(让出CPU)
- 实际是否有人来坐不确定(JVM决定是否切换线程)
- 让座后自己还站在车上(线程仍处于可运行状态)
4. join() —— 等同事下班拼车
- 给同事发微信”下班叫我”(t.join())
- 一直等到同事忙完(目标线程终止)
- 最多等30分钟,不来我就自己走(join(1800000))
5. 对比总结
方法 类比场景 关键特点 注意事项 wait() 餐厅等位 让出座位+等待通知 必须提前拿到号牌(synchronized) sleep() 定时午睡 睡着但手机不借人 别睡太久影响效率 yield() 公交车让座 客气一下未必真让 不能指望一定有效 join() 等同事一起走 死等或定时等待 别让同事等太久(避免无限阻塞)
sleep()和wait()有什么异同点
说人话就是,sleep()是让线程睡一会儿,不释放锁;wait()是让线程等着,会释放锁,等别人叫醒它。一个是小憩一下,到点自然醒,一个是睡美人,得有人亲一下
- sleep() 是 Thread 类的静态本地方法;wait() 是Object类的成员本地方法;
- JDK1.8 sleep() wait() 均需要捕获 InterruptedException 异常;
- sleep() 方法可以在任何地方使用;wait() 方法则只能在同步方法或同步代码块中使用;
- sleep() 会休眠当前线程指定时间,释放 CPU 资源,不释放对象锁,休眠时间到自动苏醒继续执行;wait() 方法放弃持有的对象锁,进入等待队列,当该对象被调用 notify() / notifyAll() 方法后才有机会竞争获取对象锁,进入运行状态。
sleep()与yield()的区别?
sleep()是强制让线程睡指定时间;yield()是礼貌性让出CPU,但可能马上又被调度回来。
- sleep() 方法给其他线程运行机会时不考虑线程的优先级;yield() 方法只会给相同优先级或更高优先级的线程运行的机会;
- sleep() 方法声明抛出 InterruptedException;yield() 方法没有声明抛出异常;
- 线程执行 sleep() 方法后进入超时等待状态;线程执行 yield() 方法转入就绪状态,可能马上又得得到执行;
- sleep() 方法需要指定时间参数;yield() 方法出让 CPU 的执行权时间由 JVM 控制。
Theard类中的Yield方法有什么作用?
Yield方法可以暂停当前正在执行的线程对象,让其它有相同优先级的线程执行。它是一个静态方法而且只保证当前线程放弃CPU占用而不能保证使其它线程一定能占用CPU,执行yield()的线程有可能在进入到暂停状态后马上又被执行。
sleep()与join()的区别?
sleep()是单纯睡觉不释放锁;join()是等另一个线程干完活再继续,期间会释放锁。
- JDK1.8 sleep() join() 均需要捕获 InterruptedException 异常;
- sleep()是Thread的静态本地方法,join()是Thread的普通方法;
- sleep()不会释放锁资源,join()底层是wait方法,会释放锁。
notify()和notifyAll有什么区别?
这两个都是宿管,一个叫醒全部人,一个只叫醒一个,所以全部叫醒不会抢饭,但是只叫醒一个大概率会抢饭
- notify可能会导致死锁,而notifyAll则不会
- 任何时候只有一个线程可以获得锁,也就是说只有一个线程可以运行synchronized 的代码使用notifyall,可以唤醒
- 所有处于wait状态的线程,使其重新进入锁的争夺队列中,而notify只能唤醒一个
- wait() 应配合while循环使用,不应使用if,务必在wait()调用前后都检查条件,如果不满足,必须调用notify()唤醒另外的线程来处理,自己继续wait()直至条件满足再往下执行。
- notify() 是对notifyAll()的一个优化,但它有很精确的应用场景,并且要求正确使用。不然可能导致死锁。正确的场景应该是 WaitSet中等待的是相同的条件,唤醒任一个都能正确处理接下来的事项,如果唤醒的线程无法正确处理,务必确保继续notify()下一个线程,并且自身需要重新回到WaitSet中.
什么是线程安全
线程安全就是说多线程访问同一代码,不会产生不确定的结果。
在多线程环境中,当各线程不共享数据的时候,即都是私有(private)成员,那么一定是线程安全的。但这种情况并不多见,在多数情况下需要共享数据,这时就需要进行适当的同步控制了。
线程安全一般都涉及到synchronized, 就是一段代码同时只能有一个线程来操作 不然中间过程可能会产生不可预知的结果。
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
引申问题
什么是线程安全?Vector是一个线程安全类吗?
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量 的值也和预期的是一样的,就是线程安全的。一个线程安全的计数器类的同一个实例对象在被多个线程使用的情况下也不会出现计算失误。很显然你可以将集合类分 成两组,线程安全和非线程安全的。Vector 是用同步方法来实现线程安全的, 而和它相似 的ArrayList不是线程安全的。
什么是线程池,线程池的优点,线程池的工作流程
什么是线程池
线程池是一种基于池化技术的线程使用模式。它预先创建一定数量的线程,将这些线程放在一个池子中,当有任务需要执行时,从池中取出空闲线程来执行任务,任务执行完毕后,线程不会被销毁,而是重新放回池中等待下一个任务。
为什么需要线程池
降低资源消耗:通过重复利用已创建的线程,减少线程创建和销毁造成的消耗
提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行
提高线程的可管理性:统一管理线程,避免系统中线程数量失控
提供更多功能:线程池具备可拓展性,提供定时执行、定期执行等功能
线程池的核心优势
资源控制:限制并发线程数,避免系统资源耗尽
任务管理:提供任务队列,支持任务的缓存和调度
监控统计:提供线程池运行状态的监控和统计信息
灵活配置:支持多种配置参数,适应不同的业务场景
线程池的工作流程可以用以下步骤描述:
任务提交:客户端提交任务到线程池
核心线程检查:如果运行线程数少于核心线程数,创建新线程执行任务
队列缓存:如果核心线程都在忙碌,任务被放入工作队列
扩容处理:如果队列满了且线程数未达到最大值,创建新的非核心线程
拒绝策略:如果队列满了且线程数已达最大值,执行拒绝策略
核心线程 vs 非核心线程
核心线程(Core Threads):线程池中始终保持活跃的线程,即使它们处于空闲状态
非核心线程(Non-Core Threads):当核心线程不足以处理任务时创建的额外线程,空闲时会被回收
任务队列的作用
任务队列用于保存等待执行的任务,它是连接任务提交和任务执行的桥梁。不同类型的队列会影响线程池的行为:
有界队列:防止内存溢出,但可能导致任务被拒绝
无界队列:可以接受大量任务,但可能导致内存问题
同步队列:不存储任务,直接传递给线程
线程池的生命周期
线程池具有以下几种状态:
RUNNING:接受新任务,处理队列中的任务
SHUTDOWN:不接受新任务,但会处理队列中的任务
STOP:不接受新任务,不处理队列中的任务,中断正在执行的任务
TIDYING:所有任务已终止,workerCount为0
TERMINATED:terminated()方法执行完成
通常我们常说的是,
新建状态,就绪状态,运行状态,阻塞状态,线程死亡其中,阻塞状态还有三个状态
等待阻塞(o.wait->等待对列) :
运行(running)的线程执行 o.wait()方法, JVM 会把该线程放入等待队列(waitting queue)中。
同步阻塞(lock->锁池)
运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM 会把该线程放入锁池(lock pool)中。
其他阻塞(sleep/join)
运行(running)的线程执行 Thread.sleep(long ms)或 t.join()方法,或者发出了 I/O 请求时,JVM 会把该线程置为阻塞状态。当 sleep()状态超时、 join()等待线程终止或者超时、或者 I/O处理完毕时,线程重新转入可运行(runnable)状态。
终止线程的四种方式
正常运行结束
线程自动结束的方式,最健康了
使用退出标志退出线程
一般 run()方法执行完,线程就会正常结束,然而,常常有些线程是伺服线程。它们需要长时间的运行,只有在外部某些条件满足的情况下,才能关闭这些线程。
interrupt方法结束线程
使用 interrupt()方法来中断线程有两种情况:
1.线程处于阻塞状态: 如使用了 sleep,同步锁的 wait,socket 中的 receiver,accept 等方法时,会使线程处于阻塞状态。当调用线程的interrupt()方法时,会抛出 InterruptException 异常。阻塞中的那个方法抛出这个异常,通过代码捕获该异常,然后 break 跳出循环状态,从而让我们有机会结束这个线程的执行。 通常很多人认为只要调用 interrupt 方法线程就会结束,实际上是错的, 一定要先捕获InterruptedException 异常之后通过 break 来跳出循环,才能正常结束 run 方法。
2.线程未处于阻塞状态: 使用 isInterrupted()判断线程的中断标志来退出循环。当使用interrupt()方法时,中断标志就会置 true,和使用自定义的标志来控制循环是一样的道理。
stop方法终止线程(线程不安全)
实现线程同步的三个方式
采用同步代码块
特点:
- 只锁定代码块而不是整个方法
- 需要显式指定锁对象
- 锁对象可以是任意对象,但通常使用专门的对象或this
- 进入同步块前必须获得锁,否则线程会阻塞
采用同步方法
特点:
- 整个方法都是同步的
- 对于实例方法,锁是当前实例对象(this)
- 对于静态方法,锁是当前类的Class对象
- 实现简单,但粒度较粗可能影响性能
采用 Lock 锁对象实例
特点:
- 比synchronized更灵活,可以尝试获取锁、定时获取锁等
- 需要显式地获取和释放锁
- 必须在finally块中释放锁以防止死锁
- 提供了更多高级功能如公平锁、读写锁等
- 性能通常优于synchronized
start()和run()的区别
start中包含了run的方法部分,这一点请一定要描述出来
start() 方法来启动线程,真正实现了多线程运行。这时无需等待 run 方法体代码执行完毕,可以直接继续执行下面的代码。
通过调用 Thread 类的 start()方法来启动一个线程, 这时此线程是处于就绪状态, 并没有运行。
方法 run()称为线程体,它包含了要执行的这个线程的内容,线程就进入了运行状态,开始运行 run 函数当中的代码。 Run 方法运行结束, 此线程终止。然后 CPU 再调度其它线程。
java后台线程
后台线程也称为守护线程 (比如JVM处理垃圾回收 使用的就是后台线程), 我们创建的线程一般用于处理我们自己的某些任务,而后台线程主要用于一些公共的任务以提供服务。
我们创建的线程可以在start()启动前使用 thread.setDaemon(true); 来将其设置为后台线程,使用isDaemon()来判断线程是否为后台线程。
要是你说什么是非后台线程,这纯属废话,除了后台线程外其他线程都属于非后台线程
常用线程池(请说英文—中文)
FixedThreadPool(固定大小线程池)
特点:
- 核心线程数和最大线程数相等
- 使用无界队列LinkedBlockingQueue
- 适合负载比较重的服务器
使用场景:已知并发量,需要控制线程数的场景
CachedThreadPool(缓存线程池)
特点:
- 核心线程数为0,最大线程数为Integer.MAX_VALUE
- 使用SynchronousQueue
- 线程空闲60秒后被回收
- 适合执行很多短期异步任务
使用场景:任务量变化较大,任务执行时间较短的场景
SingleThreadExecutor(单线程池)
特点:
- 只有一个线程的线程池
- 保证任务按提交顺序执行
- 适合需要保证顺序执行的场景
ScheduledThreadPool(定时任务线程池)
特点:
- 支持定时和周期性任务执行
- 使用DelayedWorkQueue
- 适合需要定时执行任务的场景
java中线程池框架
Executor框架概述
Java的Executor框架为线程池提供了统一的接口和实现。该框架的核心组件包括:
- Executor:最基础的接口,只定义了execute方法
- ExecutorService:扩展了Executor,提供了更丰富的线程池管理功能
- ThreadPoolExecutor:ExecutorService的具体实现类
- Executors:提供静态工厂方法创建各种类型的线程池
ExecutorService接口
- ExecutorService提供了线程池的核心功能:
ThreadPoolExecutor类详解
- ThreadPoolExecutor是线程池的核心实现类,提供了完整的线程池功能。它的构造函数参数决定了线程池的行为特征。
Executors工具类
- Executors类提供了创建常用线程池的静态方法,简化了线程池的创建过程。但在生产环境中,建议直接使用ThreadPoolExecutor构造函数,以获得更好的控制力
线程池七大核心参数
在Java开发中,线程池是一个常用的技术。创建线程池时需要指定七个参数,分别是核心线程数(corePoolSize)、最大线程数(maximumPoolSize)、空闲线程存活时间(keepAliveTime)、时间单位(unit)、工作队列(workQueue)、线程工厂(threadFactory)和拒绝策略(handler)。
核心线程数(corePoolSize)
核心线程数是线程池中维护的最小线程数量,即使这些线程处于空闲状态,它们也不会被销毁,除非设置了allowCoreThreadTimeOut。当任务提交到线程池时,如果当前线程数小于核心线程数,则会创建一个新线程来处理任务。
最大线程数(maximumPoolSize)
最大线程数是线程池允许创建的最大线程数量。当核心线程数已满且工作队列也已满时,线程池会创建新线程来处理任务,但不会超过最大线程数。
空闲线程存活时间(keepAliveTime)
空闲线程存活时间是指当线程处于空闲状态且当前线程数大于核心线程数时,线程在指定时间后会被销毁。这个时间由keepAliveTime参数设定。
时间单位(unit)
时间单位是指keepAliveTime的计量单位,可以是纳秒、微秒、毫秒、秒、分钟、小时或天。
工作队列(workQueue)
工作队列用于存放待执行的任务。Java提供了四种工作队列:
- ArrayBlockingQueue:基于数组的有界阻塞队列,按FIFO排序。
- LinkedBlockingQueue:基于链表的无界阻塞队列,按FIFO排序。
- SynchronousQueue:不缓存任务的阻塞队列,生产者放入任务必须等待消费者取出任务。
- PriorityBlockingQueue:具有优先级的无界阻塞队列,优先级通过Comparator实现。
线程工厂(threadFactory)
线程工厂用于创建新线程,可以设定线程名、是否为守护线程等。默认的线程工厂会为每个线程池创建一个新的线程组,并为每个线程分配一个唯一的名称。
拒绝策略(handler)
当工作队列已满且线程池中的线程数量达到最大限制时,新任务会被拒绝。Java提供了四种拒绝策略:
- CallerRunsPolicy:在调用者线程中直接执行被拒绝的任务。
- AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
- DiscardPolicy:丢弃任务但不抛出异常。
- DiscardOldestPolicy:丢弃队列中最早的任务,然后尝试重新提交被拒绝的任务。
工作队列类型详解
工作队列主要有ArrayBlockingQueue(有界队列),LinkedBlockingQueue(无界队列),SynchronousQueue(同步队列),PriorityBlockingQueue(优先级队列),DelayedWorkQueue(延迟队列)
有界,无界,同步队列,优先,延迟,拒绝策略——小口诀
ArrayBlockingQueue(有界队列)
基于数组实现的有界阻塞队列,按FIFO原则对任务进行排序。
特点:
- 队列容量固定,防止内存溢出
- 当队列满时,新任务会触发拒绝策略
- 适合对内存使用有严格要求的场景
LinkedBlockingQueue(无界队列)
基于链表实现的阻塞队列,理论上可以无限存储任务。
特点:
- 默认容量为Integer.MAX_VALUE,实际上是无界的
- maximumPoolSize参数失效
- 可能导致内存溢出
- 适合任务量不可预测的场景
SynchronousQueue(同步队列)
一个不存储元素的阻塞队列,每个插入操作必须等待对应的删除操作。
特点:
- 没有容量,不存储任务
- 每个任务都会直接传递给线程
- 适合任务量变化较大的场景
- CachedThreadPool使用此队列
PriorityBlockingQueue(优先级队列)
基于优先级的无界阻塞队列,支持任务按优先级执行。
特点:
- 任务必须实现Comparable接口或提供Comparator
- 高优先级任务会优先执行
- 适合有明确优先级要求的场景
DelayedWorkQueue(延迟队列)
- ScheduledThreadPoolExecutor内部使用的延迟队列,支持定时任务。
拒绝策略
AbortPolicy(抛出异常)
默认的拒绝策略,直接抛出RejectedExecutionException异常。
CallerRunsPolicy(调用者执行)
由调用线程执行被拒绝的任务,这是一种负反馈机制。
DiscardPolicy(丢弃任务)
静默丢弃被拒绝的任务,不做任何处理。
DiscardOldestPolicy(丢弃最老任务)
丢弃队列中最老的任务,然后重新提交当前任务。
自定义拒绝策略
自行定义的,可以根据实际业务需求的拒绝策略
如何选择合适的线程池大小
这个题目相对灵活的多,但是一般性遵照某些基本原则
基本原则:
- 线程数过少:无法充分利用系统资源,吞吐量低
- 线程数过多:增加上下文切换开销,可能导致系统资源耗尽
线程池调优技巧
动态调整线程池参数:
1
2
3
4
5
6
7
8
9// 运行时调整核心线程数
executor.setCorePoolSize(newCoreSize);
// 运行时调整最大线程数
executor.setMaximumPoolSize(newMaxSize);
// 运行时调整拒绝策略
executor.setRejectedExecutionHandler(newHandler);预热线程池
1
2// 预创建所有核心线程
executor.prestartAllCoreThreads();合理设置队列容量
1
2
3// 根据内存和响应时间要求设置合适的队列大小
int queueCapacity = calculateOptimalQueueSize();
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(queueCapacity);
什么是乐观锁,什么是悲观锁
乐观锁是一种乐观思想,即认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,采取在写时先读出当前版本号,然后加锁操作(比较跟上一次的版本号,如果一样则更新),如果失败则要重复读-比较-写的操作。
说人话就是先放着给你,纯看自觉,你没动就无所谓,你动了被他发现他就给你锁上了
悲观锁是就是悲观思想,即认为写多,遇到并发写的可能性高,每次去拿数据的时候都认为别人会修改,所以每次在读写数据的时候都会上锁,这样别人想读写这个数据就会 block 直到拿到锁。java中的悲观锁就是Synchronized,AQS框架下的锁则是先尝试cas乐观锁去获取锁,获取不到,才会转换为悲观锁,如 RetreenLock。
说人话就是疑心太重,不管你感不感兴趣都会把资源给锁上
此处提前占位,是锁相关的部分
线程与进程的区别?
进程是操作系统分配资源的最小单元,线程是操作系统调度的最小单元。
一个程序至少有一个进程,一个进程至少有一个线程。
什么是多线程中的上下文切换?
多线程会共同使用一组计算机上的 CPU,而线程数大于给程序分配的 CPU 数量时,为了让各个线程都有执行的机会,就需要轮转使用 CPU。不同的线程切换使用 CPU发生的切换数据等就是上下文切换。
Java 中用到的线程调度算法是什么?
采用时间片轮转的方式。可以设置线程的优先级,会映射到下层的系统上面的优先级上,如非特别需要,尽量不要用,防止线程饥饿。
JVM
Java 中的四种引用
Java中提供了四种类型的引用:强、软、弱、虚
强引用
强引用是我们最经常使用的引用方式,强引用的对象将不会被垃圾回收器回收,如果引用过多,可能会导致内存泄漏。
软引用
软引用对象一般情况下不会被回收,但是当内存不足时,软引用将会被垃圾回收器收。
弱引用
当垃圾回收器(GC)运行时,无论内存是否充足,弱引用指向的对象都会被回收。
虚引用
虚引用和前面的软引用、弱引用不同,它并不影响对象的生命周期,如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收
常见垃圾回收算法
1).清除算法
- 工作原理
标记阶段:遍历对象图,标记所有可达对象。
清除阶段:清除未标记的不可达对象,回收内存。
2).复制算法
- 工作原理
将内存分为两块,每次只使用其中一块。
当一块内存满了时,将可达对象复制到另一块内存,并清空原来的内存。
3).整理算法
- 工作原理
标记阶段:标记所有可达对象。
整理阶段:将存活对象移动到内存的一端,清理掉其他空间。
4).分代回收算法
- 工作原理
将堆内存划分为多个区域:
新生代:存放短生命周期的对象。采用复制算法
老年代:存放长生命周期的对象。采用标记-清除或标记-整理算法
GC 是什么?为什么要有 GC?
在Java中,GC指的是垃圾回收,是一种自动内存管理的机制。在Java中,程序员不需要手动地去释放内存,垃圾回收器会自动识别和回收不再使用的对象,从而释放内存空间。
为什么需要GC
- 防止内存泄漏(该回收的对象没有被回收)
- 避免手动管理内存的复杂性
- 提高开发效率,程序员不用操心内存释放
GC的工作原理
Java的垃圾回收器通过不断扫描内存空间,识别并回收不再使用的对象。当一个对象不再被任何引用指向时,垃圾回收器就会将其标记为垃圾对象,并释放其所占用的内存空间。
垃圾回收器的工作可以分为三个阶段:标记、清除和压缩。
- 标记阶段(Marking):垃圾回收器会从方法区中的类开始,递归地遍历所有可达(存活的)对象,像侦探一样追踪所有可达对象,并打上标记。没被标记的就是垃圾。
- 清除(Sweep):垃圾回收器会扫描整个堆内存,清除所有未被标记的对象,这些未被标记的对象就是垃圾对象,释放它们占用的内存。
- 压缩(Compact):在清除完成后,垃圾回收器会对存活对象进行压缩操作,以便整理出更大的连续内存空间。
Java堆内存的分代设计
Java 的内存管理涉及多个部分,最主要的包括 堆内存 和 栈内存,以及 方法区。JVM 会将内存划分为多个区域。
堆内存
Java把堆内存分成几个”小区”,不同”年龄”的对象住在不同区域:
新生代:用于存放新创建的对象,对象的生命周期较短,大部分对象都会在短时间内被回收。最终经过多次垃圾回收后仍然存活的对象会被晋升到老年代。
老年代:用于存放存活时间较长的对象,对象的生命周期较长,需要经过多次垃圾回收才能被回收。
栈内存
栈内存存储的是方法调用时创建的局部变量。栈内存由 JVM 自动管理,栈空间是线程私有的,并且随着方法的调用和结束而自动分配和释放。
方法区
方法区(也叫 永久代,但在 Java 8 之后被替换为 元空间 Metaspace)存储类信息、常量、静态变量等。
常用的 jvm 调优的参数都有哪些?(列出几种就可以)
- -Xmx20M :表示设置 JVM 启动内存的最大值为 20M,
- -verbose:gc:表示输出虚拟机中 GC 的详细情况
- -Xss128k :表示可以设置虚拟机栈的大小为 128k
- -Xoss128k :表示设置本地方法栈的大小为 128k
- -Xnoclassgc :表示关闭 JVM 对类的垃圾回收
- -Xmn20M :表示设置年轻代的大小为 20M
说一下 jvm 调优的工具?
- Jconsole : jdk 自带,功能简单,但是可以在系统有一定负荷的情况下使用。对垃圾回收算法有很
详细的跟踪。 - JProfiler:商业软件,需要付费。功能强大。
- VisualVM:JDK 自带,功能强大,与 JProfiler 类似。
加载class文件的原理机制
Java文件通过Java 编译器(如 javac)将 Java 源代码(.java 文件)编译而生成的。编译器将 Java 代码转换成字节码,存储在 Class 文件中,Class 文件需要加载到虚拟机中之后才能运行和使用。
类加载的过程
类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期可以简单概括为 7 个阶段:加载、验证、准备、解析、初始化、使用和卸载(Unloading)。
JVM中内置的三个重要类加载器
- 启动类加载器(Bootstrap ClassLoader)
- 扩展类加载器(Extension ClassLoader)
- 系统类加载器(System ClassLoader)
特殊提示:final static常量在准备阶段就赋值,是唯一例外
堆内存和栈内存的区别
栈是运行时单位,代表着逻辑,内含基本数据类型和堆中对象引用,所在区域连续,没有碎片;
堆是存储单位,代表着数据,可被多个栈共享(包括成员中基本数据类型、引用和引用对象),所在区域不连
续,会有碎片。
1、功能不同
栈内存用来存储局部变量和方法调用,而堆内存用来存储Java中的对象。无论是成员变量,局部变量,还是类变量,它们指向的对象都存储在堆内存中。
2、共享性不同
栈内存是线程私有的。
堆内存是所有线程共有的。
3、异常错误不同
如果栈内存或者堆内存不足都会抛出异常。
- 栈空间不足:java.lang.StackOverFlowError。
- 堆空间不足:java.lang.OutOfMemoryError。
4、空间大小
栈的空间大小远远小于堆的
双亲委派机制
如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最 终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式
JVM内存模型包括哪些部分
JVM内存模型包括:
方法区(Method Area):存储类信息、常量、静态变量等。
堆(Heap):存储对象实例。
栈(Stack):存储局部变量和方法调用。
程序计数器(Program Counter Register):记录当前线程执行的字节码指令地址。
本地方法栈(Native Method Stack):用于执行本地方法。
死锁
何为死锁,就是多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放
造成死锁的四个条件:
- 互斥条件:一个资源每次只能被一个进程使用。
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
synchronized 和 volatile 的区别是什么?
这两位的中文为:同步锁和易失性变量
volatile只能修饰变量,synchronized 既能修饰变量,也能修饰方法和代码块。
volatile保证变量的可见性,但不保证原子性;synchronized同时保证可见性和原子性。
volatile不会造成线程阻塞,而 synchronized 可能会导致线程阻塞。
volatile不能被编译器优化,而 synchronized 通过 JVM 的优化(如偏向锁、轻量级锁)能提高性能。
synchronized 和 Lock 有什么区别?
- synchronized是Java关键字,基于JVM实现自动锁管理;Lock是接口,需手动加锁解锁。
- Lock支持非阻塞尝试锁(tryLock)、可中断锁、超时锁,synchronized无法实现。
- Lock可设置公平/非公平策略,synchronized仅支持非公平锁。
- Lock可通过Condition绑定多个条件,synchronized仅一个等待队列。
- 高并发时Lock更高效,synchronized优化后差距缩小,但Lock需显式释放避免死锁。
MyBatis
什么是 Mybatis?
1、Mybatis 是一个半 ORM(对象关系映射)框架,它内部封装了 JDBC,开发时只需要关注 SQL 语句本身,不需要花费精力去处理加载驱动、创建连接、创建statement 等繁杂的过程。程序员直接编写原生态 sql,可以严格控制 sql 执行性能,灵活度高。
2、MyBatis 可以使用 XML 或注解来配置和映射原生信息,将 POJO 映射成数据库中的记录,避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
3、通过 xml 文件或注解的方式将要执行的各种 statement配置起来,并通过java 对象和 statement 中 sql 的动态参数进行映射生成最终执行的 sql 语句,最后由 mybatis 框架执行 sql 并将结果映射为 java 对象并返回。(从执行 sql 到返回 result 的过程)。
半 ORM(对象关系映射)框架,指的是它不完全是一个 ORM 框架,因为 MyBatis 需要程序员自己编写 Sql 语句。
设计模式
- 单例(Singleton)模式:某个类只能生成一个实例(单例模式常见的实现方法有恶汉式、懒汉式)
- 工厂方法(FactoryMethod)模式:定义一个用于创建产品的接口,由子类决定生产什么产品。
- 代理(Proxy)模式:为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。
- 装饰(Decorator)模式:动态地给对象增加一些职责,即增加其额外的功能
- 建造者(Builder)模式:将一个复杂对象分解成多个相对简单的部分
“#{}和${}”的区别是什么?
“#{}是预编译处理,${}是字符串替换。”
Mybatis 在处理#{}时,会将 sql 中的#{}替换为?号,调用 PreparedStatement 的set 方法来赋值;
Mybatis 在处理${}时,就是把${}替换成变量的值。
使用#{}可以有效的防止 SQL 注入,提高系统安全性
他们都是在sql语句中进行赋值操作的部分的语法格式,至于他们两个的区别,#{}就像是在平台上下单,而${}是直接给店家打电话叫人送,前者的安全性和保密性明显更好
- 用#{}防黑客(用户输入)
- 用${}拼SQL(结构部分)
Mybatis 动态 sql 有什么用?执行原理?有哪些动态 sql?
Mybatis 动态 sql可在Xml映射文件内,以标签的形式编写动态 sql,执行原理是根据表达式的值完成逻辑判断并动态拼接 sql 的功能。
Mybatis 提供了9 种动态 sql 标签 :trim | where | set | foreach | if | choose| when | otherwise | bind。
MyBatis 实现一对一有几种方式?具体怎么操作的?
有联合查询和嵌套查询,联合查询是几个表联合查询,只查询一次, 通过在resultMap 里面配置 association 节点配置一对一的类就可以完成;
嵌套查询是先查一个表,根据这个表里面的结果的 外键 id,去再另外一个表里面查询数据,也是通过 association 配置,但另外一个表的查询通过 select 属性配置
MyBatis 实现一对多有几种方式,怎么操作的?
有联合查询和嵌套查询。联合查询是几个表联合查询,只查询一次,通过在resultMap 里面的 collection 节点配置一对多的类就可以完成;嵌套查询是先查一个表,根据这个表里面的
结果的外键 id,去再另外一个表里面查询数据,也是通过配置 collection,但另外一个表的查询通过 select 节点配置。
Mybatis 的一级、二级缓存
一级缓存:SqlSession级别的缓存,默认开启
MyBatis的一级缓存也是SqlSession的缓存,SqlSession对象中维护了一个Map集合,用于存储相互的缓存数据。
查询的时候,先从SqlSession的缓存中查找,如果有,直接返回。如果没有,查询数据库。
二级缓存:全局缓存,基于Mapper实现,默认关闭
开启二级缓存配置:
1.全局启用(在 mybatis-config.xml 中配置):cacheEnabled 为true(默认true)
2.Mapper配置文件中配置:
mybatis 有几种分页方式?
数组分页,SQL 分页,拦截器分页,RowBounds 分页
数据库部分
数据库存储引擎
数据库存储引擎决定了数据的存储和检索方式,以及如何处理数据的查询、插入、更新和删除操作。从MySQL 5.5版本开始,MySQL支持多种存储引擎:InnoDB:默认存储引擎支持事务处理(ACID兼容)、行级锁定、外键约束MyISAM:不支持事务处理,但支持全文索引、压缩表、空间函数MEMORY:数据存储在内存中,速度快,适合临时表或需要快速访问的数据
InnoDB与MyISAM的区别
- InnoDB支持事务,MyISAM不支持,对于InnoDB每一条SQL语言都默认封装成事务,自动提交,这样会影响速度,所以最好把多条SQL语言放在begin和commit之间,组成一个事务;
- InnoDB支持外键,而MyISAM不支持。对一个包含外键的InnoDB表转为MYISAM会失败;
- InnoDB是聚集索引,数据文件是和索引绑在一起的,必须要有主键,通过主键索引效率很高。但是辅助索引需要两次查询,先查询到主键,然后再通过主键查询到数据。因此,主键不应该过大,因为主键太大,其他索引也都会很大。而MyISAM是非聚集索引,数据文件是分离的,索引保存的是数据文件的指针。主键索引和辅助索引是独立的。
- InnoDB不保存表的具体行数,执行select count(*) from table时需要全表扫描。而MyISAM用一个变量保存了整个表的行数,执行上述语句时只需要读出该变量即可,速度很快;
- Innodb不支持全文索引,而MyISAM支持全文索引,查询效率上MyISAM要高
什么是索引,索引的作用?
索引就是加快检索表中数据的方法。数据库的索引类似于书籍的索引。在书籍中,索引允许用户不必翻阅完整个书就能迅速地找到所需要的信息。在数据库中,索引也允许数据库程序迅速地找到表中的数据,而不必扫描整个数据库。
索引是一种特殊的文件(InnoDB数据表上的索引是表空间的一个组成部分),它们包含着对数据表里所有记录的引用指针。索引是一种数据结构。数据库索引,是数据库管理系统中一个排序的数据结构,以协助快速查询、更新数据库表中数据。索引的实现通常使用B树及其变种B+树。更通俗的说,索引就相当于目录。为了方便查找书中的内容,通过对内容建立索引形成目录。索引是一个文件,它是要占据物理空间的。
mysql 有4种不同的索引:
主键索引: 数据列不允许重复,不允许为NULL,一个表只能有一个主键。
唯一索引: 数据列不允许重复,允许为NULL值,一个表允许多个列创建唯一索引。
- 可以通过
ALTER TABLE table_name ADD UNIQUE (column);创建唯一索引 - 可以通过
ALTER TABLE table_name ADD UNIQUE (column1,column2);创建唯一组合索引
普通索引: 基本的索引类型,没有唯一性的限制,允许为NULL值。
- 可以通过
ALTER TABLE table_name ADD INDEX index_name (column);创建普通索引 - 可以通过
ALTER TABLE table_name ADD INDEX index_name(column1, column2, column3);创建组合索引
全文索引: 是目前搜索引擎使用的一种关键技术。
- 可以通过
ALTER TABLE table_name ADD FULLTEXT (column);创建全文索引
索引的优缺点
索引的优点
- 可以大大加快数据的检索速度,这也是创建索引的最主要的原因。
- 通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。
索引的缺点
- 时间方面:创建索引和维护索引要耗费时间,具体地,当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,会降低增/改/删的执行效率;
- 空间方面:索引需要占物理空间。
索引的使用场景
where
在where子句中使用索引列进行查询,能够快速定位到符合条件的记录。
可以尝试在一个字段未建立索引时,根据该字段查询的效率,然后对该字段建立索引(
alter table 表名 add index(字段名)),同样的SQL执行的效率,你会发现查询效率会有明显的提升(数据量越大越明显)。
group by
在group by子句中使用索引列进行分组,能够快速分组。
order by
当我们使用order by将查询结果按照某个字段排序时,如果该字段没有建立索引,那么执行计划会将查询出的所有数据使用外部排序(将数据从硬盘分批读取到内存使用内部排序,最后合并排序结果),这个操作是很影响性能的,因为需要将查询涉及到的所有数据从磁盘中读到内存(如果单条数据过大或者数据量过多都会降低效率),更无论读到内存之后的排序了。
但是如果我们对该字段建立索引alter table 表名 add index(字段名),那么由于索引本身是有序的,因此直接按照索引的顺序和映射关系逐条取出数据即可。而且如果分页的,那么只用取出索引表某个范围内的索引对应的数据,而不用像上述那取出所有数据进行排序再返回某个范围内的数据。(从磁盘取数据是最影响性能的)
join
在join子句中使用索引列进行关联查询,能够快速关联查询。
join
对
join语句匹配关系(on)涉及的字段建立索引能够提高效率
索引覆盖
如果要查询的字段都建立过索引,那么引擎会直接在索引表中查询而不会访问原始数据(否则只要有一个字段没有建立索引就会做全表扫描),这叫索引覆盖。因此我们需要尽可能的在select后只写必要的查询字段,以增加索引覆盖的几率。
这里值得注意的是不要想着为每个字段建立索引,因为优先使用索引的优势就在于其体积小。如果一个字段的值重复率高(比如性别字段),那么建立索引的效率就会低。
mysql有关权限的表都有哪几个
MySQL服务器通过权限表来控制用户对数据库的访问,权限表存放在mysql数据库里,由mysql_install_db脚本初始化。这些权限表分别user,db,table_priv,columns_priv和host。下面分别介绍一下这些表的结构和内容:
- user权限表:记录允许连接到服务器的用户帐号信息,里面的权限是全局级的。
- db权限表:记录各个帐号在各个数据库上的操作权限。
- table_priv权限表:记录数据表级的操作权限。
- columns_priv权限表:记录数据列级的操作权限。
- host权限表:配合db权限表对给定主机上数据库级操作权限作更细致的控制。这个权限表不受GRANT和REVOKE语句的影响。
数据库的三范式是什么
第一范式:列不可再分
第二范式:行可以唯一区分,主键约束
第三范式:表的非主属性不能依赖与其他表的非主属性 外键约束
且三大范式是一级一级依赖的,第二范式建立在第一范式上,第三范式建立第一第二范式上。
MySQL的binlog有有几种录入格式?分别有什么区别?
有三种格式,statement,row和mixed。
- statement模式下,每一条会修改数据的sql都会记录在binlog中。不需要记录每一行的变化,减少了binlog日志量,节约了IO,提高性能。由于sql的执行是有上下文的,因此在保存的时候需要保存相关的信息,同时还有一些使用了函数之类的语句无法被记录复制。
- row级别下,不记录sql语句上下文相关信息,仅保存哪条记录被修改。记录单元为每一行的改动,基本是可以全部记下来但是由于很多操作,会导致大量行的改动(比如alter table),因此这种模式的文件保存的信息太多,日志量太大。
- mixed,一种折中的方案,普通操作使用statement记录,当无法使用statement的时候使用row。
此外,新版的MySQL中对row级别也做了一些优化,当表结构发生变化的时候,会记录语句而不是逐行记录。
创建索引的原则(重中之重)
索引虽好,但也不是无限制的使用,最好符合一下几个原则
1) 最左前缀匹配原则,组合索引非常重要的原则,mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。
2)较频繁作为查询条件的字段才去创建索引
3)更新频繁字段不适合创建索引
4)若是不能有效区分数据的列不适合做索引列(如性别,男女未知,最多也就三种,区分度实在太低)
5)尽量的扩展索引,不要新建索引。比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。
6)定义有外键的数据列一定要建立索引。
7)对于那些查询中很少涉及的列,重复值比较多的列不要建立索引。
8)对于定义为text、image和bit的数据类型的列不要建立索引。
创建索引的三种方式,删除索引
创建索引的三种方式:
- 第一种方式:在执行CREATE TABLE时创建索引
- 第二种方式:在执行ALTER TABLE时创建索引
- 第三种方式:使用CREATE INDEX命令创建
创建索引时需要注意什么
- 非空字段:应该指定列为NOT NULL,除非你想存储NULL。在mysql中,含有空值的列很难进行查询优化,因为它们使得索引、索引的统计信息以及比较运算更加复杂。你应该用0、一个特殊的值或者一个空串代替空值;
- 取值离散大的字段:(变量各个取值之间的差异程度)的列放到联合索引的前面,可以通过count()函数查看字段的差异值,返回值越大说明字段的唯一值越多字段的离散程度高;
- 索引字段越小越好:数据库的数据存储以页为单位一页存储的数据越多一次IO操作获取的数据越大效率越高。
使用索引查询一定能提高查询的性能吗?为什么
通常,通过索引查询数据比全表扫描要快。但是我们也必须注意到它的代价。
- 索引需要空间来存储,也需要定期维护, 每当有记录在表中增减或索引列被修改时,索引本身也会被修改。 这意味着每条记录的INSERT,DELETE,UPDATE将为此多付出4,5 次的磁盘I/O。 因为索引需要额外的存储空间和处理,那些不必要的索引反而会使查询反应时间变慢。使用索引查询不一定能提高查询性能,索引范围查询(INDEX RANGE SCAN)适用于两种情况:
- 基于一个范围的检索,一般查询返回结果集小于表中记录数的30%
- 基于非唯一性索引的检索
百万级别或以上的数据如何删除
关于索引:由于索引需要额外的维护成本,因为索引文件是单独存在的文件,所以当我们对数据的增加,修改,删除,都会产生额外的对索引文件的操作,这些操作需要消耗额外的IO,会降低增/改/删的执行效率。所以,在我们删除数据库百万级别数据的时候,查询MySQL官方手册得知删除数据的速度和创建的索引数量是成正比的。
- 所以我们想要删除百万数据的时候可以先删除索引(此时大概耗时三分多钟)
- 然后删除其中无用数据(此过程需要不到两分钟)
- 删除完成后重新创建索引(此时数据较少了)创建索引也非常快,约十分钟左右。
- 与之前的直接删除绝对是要快速很多,更别说万一删除中断,一切删除会回滚。那更是坑了。
什么是最左前缀原则?什么是最左匹配原则
- 顾名思义,就是最左优先,在创建多列索引时,要根据业务需求,where子句中使用最频繁的一列放在最左边。
- 最左前缀匹配原则,非常重要的原则,mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。
- =和in可以乱序,比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序,mysql的查询优化器会帮你优化成索引可以识别的形式
B树
B树和B+树的区别
在B树中,你可以将键和值存放在内部节点和叶子节点;但在B+树中,内部节点都是键,没有值,叶子节点同时存放键和值。
B+树的叶子节点有一条链相连,而B树的叶子节点各自独立。
使用B树的好处
B树可以在内部节点同时存储键和值,因此,把频繁访问的数据放在靠近根节点的地方将会大大提高热点数据的查询效率。这种特性使得B树在特定数据重复多次查询的场景中更加高效。
使用B+树的好处
由于B+树的内部节点只存放键,不存放值,因此,一次读取,可以在内存页中获取更多的键,有利于更快地缩小查找范围。 B+树的叶节点由一条链相连,因此,当需要进行一次全数据遍历的时候,B+树只需要使用O(logN)时间找到最小的一个节点,然后通过链进行O(N)的顺序遍历即可。而B树则需要对树的每一层进行遍历,这会需要更多的内存置换次数,因此也就需要花费更多的时间
Hash索引和B+树所有有什么区别或者说优劣呢?
首先要知道Hash索引和B+树索引的底层实现原理:
hash索引底层就是hash表,进行查找时,调用一次hash函数就可以获取到相应的键值,之后进行回表查询获得实际数据。B+树底层实现是多路平衡查找树。对于每一次的查询都是从根节点出发,查找到叶子节点方可以获得所查键值,然后根据查询判断是否需要回表查询数据。
那么可以看出他们有以下的不同:
- hash索引进行等值查询更快(一般情况下),但是却无法进行范围查询。
因为在hash索引中经过hash函数建立索引之后,索引的顺序与原顺序无法保持一致,不能支持范围查询。而B+树的的所有节点皆遵循(左节点小于父节点,右节点大于父节点,多叉树也类似),天然支持范围。
- hash索引不支持使用索引进行排序,原理同上。
- hash索引不支持模糊查询以及多列索引的最左前缀匹配。原理也是因为hash函数的不可预测。AAAA和AAAAB的索引没有相关性。
- hash索引任何时候都避免不了回表查询数据,而B+树在符合某些条件(聚簇索引,覆盖索引等)的时候可以只通过索引完成查询。
- hash索引虽然在等值查询上较快,但是不稳定。性能不可预测,当某个键值存在大量重复的时候,发生hash碰撞,此时效率可能极差。而B+树的查询效率比较稳定,对于所有的查询都是从根节点到叶子节点,且树的高度较低。
因此,在大多数情况下,直接选择B+树索引可以获得稳定且较好的查询速度。而不需要使用hash索引。
数据库为什么使用B+树而不是B树
- B树只适合随机检索,而B+树同时支持随机检索和顺序检索;
- B+树空间利用率更高,可减少I/O次数,磁盘读写代价更低。一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。这样的话,索引查找过程中就要产生磁盘I/O消耗。B+树的内部结点并没有指向关键字具体信息的指针,只是作为索引使用,其内部结点比B树小,盘块能容纳的结点中关键字数量更多,一次性读入内存中可以查找的关键字也就越多,相对的,IO读写次数也就降低了。而IO读写次数是影响索引检索效率的最大因素;
- B+树的查询效率更加稳定。B树搜索有可能会在非叶子结点结束,越靠近根节点的记录查找时间越短,只要找到关键字即可确定记录的存在,其性能等价于在关键字全集内做一次二分查找。而在B+树中,顺序检索比较明显,随机检索时,任何关键字的查找都必须走一条从根节点到叶节点的路,所有关键字的查找路径长度相同,导致每一个关键字的查询效率相当。
- B-树在提高了磁盘IO性能的同时并没有解决元素遍历的效率低下的问题。B+树的叶子节点使用指针顺序连接在一起,只要遍历叶子节点就可以实现整棵树的遍历。而且在数据库中基于范围的查询是非常频繁的,而B树不支持这样的操作。
- 增删文件(节点)时,效率更高。因为B+树的叶子节点包含所有关键字,并以有序的链表结构存储,这样可很好提高增删效率。
B+树在满足聚簇索引和覆盖索引的时候不需要回表查询数据,
在B+树的索引中,叶子节点可能存储了当前的key值,也可能存储了当前的key值以及整行的数据,这就是聚簇索引和非聚簇索引。 在InnoDB中,只有主键索引是聚簇索引,如果没有主键,则挑选一个唯一键建立聚簇索引。如果没有唯一键,则隐式的生成一个键来建立聚簇索引。
当查询使用聚簇索引时,在对应的叶子节点,可以获取到整行数据,因此不用再次进行回表查询。
联合索引是什么?为什么需要注意联合索引中的顺序?
MySQL可以使用多个字段同时建立一个索引,叫做联合索引。在联合索引中,如果想要命中索引,需要按照建立索引时的字段顺序挨个使用,否则无法命中索引。
具体原因为:
MySQL使用索引时需要索引有序,假设现在建立了”name,age,school”的联合索引,那么索引的排序为: 先按照name排序,如果name相同,则按照age排序,如果age的值也相等,则按照school进行排序。
当进行查询时,此时索引仅仅按照name严格有序,因此必须首先使用name字段进行等值查询,之后对于匹配到的列而言,其按照age字段严格有序,此时可以使用age字段用做索引查找,以此类推。因此在建立联合索引的时候应该注意索引列的顺序,一般情况下,将查询需求频繁或者字段选择性高的列放在前面。此外可以根据特例的查询或者表结构进行单独的调整。
什么是数据库事务?事务的特性有哪些?事务的隔离级别?分别处理那些问题
事务(TRANSACTION)是作为单个逻辑工作单元执行的一系列操作, 这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行 。事务是一个不可分割的工作逻辑单元事务必须具备以下四个属性,简称 ACID 属性:
原子性(Atomicity)
事务是一个完整的操作。事务的各步操作是不可分的(原子的);要么都执行,要么都不执
行。
一致性(Consistency)
当事务完成时,数据必须处于一致状态。
隔离性(Isolation)
对数据进行修改的所有并发事务是彼此隔离的, 这表明事务必须是独立的,它不应以任何方
式依赖于或影响其他事务。
永久性(Durability)
事务完成后,它对数据库的修改被永久保持,事务日志能够保持事务的永久性
SQL 标准定义了四个隔离级别:
READ-UNCOMMITTED(读取未提交): 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
READ-COMMITTED(读取已提交): 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
REPEATABLE-READ(可重复读): 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生
SERIALIZABLE(可串行化): 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰
事务的并发问题
事务在操作时的理想状态: 所有的事务之间保持隔离,互不影响。因为并发操作,多个用户同时访问同一个数据,可能引发并发访问的问题。
1). 赃读:一个事务读到另外一个事务还没有提交的数据。
2). 不可重复读:一个事务中两次读取的数据内容不一致,要求的是一个事务中多次读取时数据是一致
的,这是事务update时引发的问题。
3).幻读:一个事务按照条件查询数据时,没有对应的数据行,但是在插入数据时,又发现这行数据已经
存在,好像出现了 “幻影”。
如何对SQL优化
1、查询语句中不要使用select *
2、尽量减少子查询,使用关联查询(left join,right join,inner join)替代
3、减少使用IN或者NOT IN ,使用exists,not exists或者关联查询语句替代
4、or 的查询尽量用 union或者union all 代替(在确认没有重复数据或者不用剔除重复数据时,union all会更好)
5、应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。
简单说一说drop、delete与truncate的区别
SQL中的drop、delete、truncate都表示删除,但是三者有一些差别
delete和truncate只删除表的数据不删除表的结构
速度上,一般来说: drop> truncate >delete
delete语句是dml,这个操作会放到rollback segement中,事务提交之后才生效;
如果有相应的trigger,执行的时候将被触发. truncate,drop是ddl, 操作立即生效,原数据不放到rollbacksegment中,不能回滚. 操作不触发trigger
什么是视图,视图的优点是什么?
视图是一种虚拟的表,具有和物理表相同的功能。可以对视图进行增,改,查,操作,试图通常是有一个表或者多个表的行或列的子集。对视图的修改不影响基本表。它使得我们获取数据更容易,相比多表查询
视图的优点(视图的作用):
(1) 视图能够简化用户的操作
(2) 视图使用户能以多种角度看待同一数据;
(3) 视图为数据库提供了一定程度的逻辑独立性;
(4) 视图能够对机密数据提供安全保护
多表查询的分类
交叉连接:返回两表所有可能的组合(笛卡尔积)
内连接(INNER JOIN):返回两个表中有匹配关系的记录。隐式内连接使用逗号分隔表名,显式内连接需明确指定INNER关键字。
外连接
- 左外连接(LEFT JOIN):返回左表所有记录,右表无匹配时对应列填充NULL。
- 右外连接(RIGHT JOIN):返回右表所有记录,左表无匹配时对应列填充NULL。
- 全外连接(FULL JOIN):结合左、右外连接的结果,只要任一表存在匹配则返回行。
自连接:当前表与自身进行连接查询,需使用别名区分不同实例
子查询: - 列子查询:返回单列值
- 行子查询:返回单行数据
- 表子查询:返回整个子查询结果集作为临时表
并发事务带来哪些问题?
脏读(Dirty read): 当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。
丢失修改(Lost to modify): 指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修。 例如:事务1读取某表中的数据A=20,事务2也读取A=20,事务1修改A=A-1,事务2也修改A=A-1,最终结果A=19,事务1的修改被丢失。
不可重复读(Unrepeatableread): 指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。
幻读(Phantom read): 幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。
不可重复读和幻读区别:
不可重复读的重点是修改比如多次读取一条记录发现其中某些列的值被修改,幻读的重点在于新增或者删除比如多次读取一条记录发现记录增多或减少了
遇上大表如何优化?
限定数据的范围
务必禁止不带任何限制数据范围条件的查询语句。比如:我们当用户在查询订单历史的时候,我们可以控制在一个月的范围内;
读/写分离
经典的数据库拆分方案,主库负责写,从库负责读;
垂直分区
根据数据库里面数据表的相关性进行拆分。 例如,用户表中既有用户的登录信息又有用户的基本信息,可以将用户表拆分成两个单独的表,甚至放到单独的库做分库。
数据库并发策略
并发控制一般采用三种方法,分别是乐观锁和悲观锁以及时间戳。
MySQL 中有哪几种锁?
1、表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
2、行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
3、页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般
CHAR 和 VARCHAR 的区别?
1、CHAR 和 VARCHAR 类型在存储和检索方面有所不同
2、CHAR 列长度固定为创建表时声明的长度,长度值范围是 1 到 255 当 CHAR值被存储时,它们被用空格填充到特定长度,检索 CHAR 值时需删除尾随空格。
对于关系型数据库而言,索引是相当重要的概念,请回答有关索引的几个问题
1、索引的目的是什么?
快速访问数据表中的特定信息,提高检索速度
创建唯一性索引,保证数据库表中每一行数据的唯一性。
加速表和表之间的连接
使用分组和排序子句进行数据检索时,可以显著减少查询中分组和排序的时间
2、索引对数据库系统的负面影响是什么?
负面影响:
创建索引和维护索引需要耗费时间,这个时间随着数据量的增加而增加;索引需要占用物理空间,不光是表需要占用数据空间,每个索引也
需要占用物理空间;当对表进行增、删、改、的时候索引也要动态维护,这样就降低了数据的维护速度。
3、为数据表建立索引的原则有哪些?
在最频繁使用的、用以缩小查询范围的字段上建立索引。
在频繁使用的、需要排序的字段上建立索引
4、什么情况下不宜建立索引?什么情况下宜建立索引?
哪些情况需要创建索引
- 主键自动建立唯一索引
- 频繁作为查询条件的字段应该创建索引
- 查询中与其他表关联的字段,外键关系建立索引
哪些情况不需要创建索引
【2】索引的缺点
- 创建索引和维护索引要 耗费时间 ,并 且随着数据量的增加,所耗费的时间也会增加
- 索引需要占用磁盘空间
- 虽然索引大大提高了查询速度,同时却会 降低更新表的速度 。当对表 中的数据进行增加、删除和修改的时候,索引也要动态地维护,这样就降低了数据的维护速度。 因此,选择使用索引时,需要综合考虑索引的优点和缺点。
SQL 语言包括哪几部分?每部分都有哪些操作关键
SQL 语言包括数据定义(DDL)、数据操纵(DML),数据控制(DCL)和数据查询(DQL)四个部分。
数据定义:Create Table,Alter Table,Drop Table, Craete/Drop Index 等
数据操纵:Select ,insert,update,delete,
数据控制:grant,revoke
数据查询:select
说说对 SQL 语句优化有哪些方法?
1、Where 子句中:where 表之间的连接必须写在其他 Where 条件之前,那些可以过滤掉最大数量记录的条件必须写在 Where 子句的末尾.HAVING 最后。
2、用 EXISTS 替代 IN、用 NOT EXISTS 替代 NOT IN。
3、 避免在索引列上使用计算
4、避免在索引列上使用 IS NULL 和 IS NOT NULL
5、对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。
6、应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描
7、应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描
什么是锁
在并发访问的情况下,多个用户可能同时对同一张表进行读取或写入操作,这些操作可能会导致数据的不一致或损坏。而锁是实现一致性的最终兜底方案,在某些特殊场景下,锁的使用不可避免
在 MySQL 中,锁是数据库管理系统用于控制并发访问的机制,确保数据的安全性和一致性。
MySQL中的锁,分为以下三类:
全局锁:锁定数据库中的所有表。表级锁:每次操作锁住整张表。行级锁:每次操作锁住对应的行数据。全局锁:全局读锁会让所有的表都加上一个读锁,阻止对这些表的写操作,但是仍然允许读操作。表级锁:意思就是每次操作锁住整张表。表级锁定分为表共享读锁(共享锁)与表独占写锁(排他锁)。行级锁:每次操作锁住对应的行数据,InnoDB 的数据是基于索引组织的,行锁是通过对索引上的索引项加锁来实现的,而不是对记录加的锁,对于行级锁,主要分为三类:
- 记录锁(Record Lock):直接锁定被操作的数据行。用于保护单个记录,防止其他事务对此行进
行 update 和 delete 操作。 - 间隙锁(Gap Lock):锁定索引记录间隙,防止别人插新页(新数据),防幻读。
- 临键锁(Next-Key Lock):行锁 + 间隙锁一起上,同时锁住数据。
两种类型的行锁:共享锁:又称为S锁,允许一个事务去读一行,阻止其他事务获得相同数据集的排它锁。排他锁:又称为X锁,允许获取排他锁的事务更新数据,阻止其他事务获得相同数据集的共享锁和排他锁。
什么是时间戳
时间戳就是在数据库表中单独加一列时间戳,比如“TimeStamp”, 每次读出来的时候,把该字段也读出来,当写回去的时候,把该字段加1,提交之前 ,跟数据库的该字段比较一次,如果比数据库的值大的话,就允许保存,否则不允许保存,这种处理方法虽然不使用数据库系统提供的锁机制,但是这种方法可以大大提高数据库处理的并发量,以上悲观锁所说的加“锁”,其实分为几种锁,分别是: 排它锁(写锁)和共享锁(读锁) 。
LINUX部分
绝对路径用什么符号表示?当前目录、上层目录用什么表示?主目录用什么表示? 切换目录用什么命令?
绝对路径: 如/etc/init.d
当前目录和上层目录: ./ ../
主目录: ~/
切换目录: cd
怎么查看当前进程?怎么执行退出?怎么查看当前路径?
查看当前进程: ps
执行退出: exit
查看当前路径: pwd
怎么清屏?怎么退出当前命令?怎么执行睡眠?怎么查看当前用户 id?查看指定帮助用什么命令?
清屏: clear
退出当前命令: ctrl+c 彻底退出
执行睡眠 : ctrl+z 挂起当前进程 fg 恢复后台 查看当前用户 id: ”id“:查看显示目前登陆账户的 uid 和 gid 及所属分组及用户名
查看指定帮助: 如 man adduser 这个很全 而且有例子; adduser —help 这个告诉你一些常用参数; info adduesr;
Ls 命令执行什么功能? 可以带哪些参数,有什么区别?
ls 执行的功能: 列出指定目录中的目录,以及文件哪些参数以及区别: a 所有文件 l 详细信息,包括大小字节数,可读可写可执行的权限等
列举几个常用的Linux命令
列出文件列表:ls【参数 -a -l】
创建目录和移除目录:mkdir rm-dir
用于显示文件后几行内容:tail,例如: tail -n 1000:显示最后1000行
解压:tar -xvf
打包并压缩:tar -zcvf
查找字符串:grep
显示当前所在目录:pwd创建空文件:touch
编辑器:vim vi
要看更多命令,请见点击这里看更多
使用什么命令查看磁盘使用空间? 空闲空间呢?
1 | df -hl |
使用什么命令查看网络是否连通,使用什么命令查看 ip 地址及接口信息?
1 | # 查看网络是否连通 |
查看各类环境变量用什么命令?
1 | 查看所有 env |
其余指令和多余的命令指正在施工
Redis
使用Redis有哪些好处?
- 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是0
- 支持丰富数据类型,支持string,list,set,sorted set,hash
- 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行
- 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除
Redis的数据类型?
Redis 支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(有序集合)。
什么是Redis事务?
Redis 事务提供了一种将多个命令请求打包的功能。然后,再按顺序执行打包的所有命令,并且不会被中途打断。
Redis 可以通过 MULTI、EXEC、DISCARD 和 WATCH 等命令来实现事务(Transaction)功能
缓存雪崩、缓存穿透、缓存预热、等问题
【1】缓存雪崩
由于原有缓存失效,新缓存未到期间,所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃。
解决办法:
大多数系统设计者考虑用加锁( 最多的解决方案)或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。还有一个简单方案就时讲缓存失效时间分散开。
【2】缓存穿透
缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到,每次都要去数据库再查询一遍,然后返回空(相当于进行了两次无用的查询)。这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题。
解决办法
最常见的则是采用布隆过滤器
【3】缓存预热
缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据。
解决思路:
- 直接写个缓存刷新页面,上线时手工操作下;
- 数据量不大,可以在项目启动的时候自动进行加载;
- 定时刷新缓存
Redis 持久化有几种方式?
- RDB:RDB 持久化机制,是对 redis 中的数据执行周期性的持久化。
- AOF:AOF 机制对每条写入命令作为日志,以 append-only 的模式写入一个日志文件中,在redis重启的时候,可以通过回放 AOF 日志中的写入指令来重新构建整个数据集。
Redis是单进程单线程的?
Redis 是单进程单线程的,redis 利用队列技术将并发访问变为串行访问,消除了传统数据库串行控制的开销。
单线程的redis为什么这么快
- 纯内存操作
- 单线程操作,避免了频繁的上下文切换
- 采用了非阻塞I/O多路复用机制
操作系统部分
并发和并行的区别?
并发是多个任务在同一时间段内交替执行
并行是多个任务在同一时刻真正同时执行
线程和进程的区别
进程:内存中运行的运用程序,每个进程都有自己独立的内存空间,一个进程可以由多个线程,例如在Windows系统中,xxx.exe就是一个进程。
线程:进程中的一个控制单元,负责当前进程中的程序执行,一个进程至少有一个线程,一个进程可以运行多个线程,多个线程可以共享数据。
Java用到的线程调度算法是什么?
计算机通常只有一个CPU,在任意时刻只能执行一条机器指令,每个线程只有获取到CPU的使用权才能执行指令,所谓多线程的并发运行,其实从宏观上看,各线程轮流获取CPU的使用权,分别执行各自的任务。在运行池中,会有多个处于就绪状态的线程在等待,CPU的调度,JVM有一项任务就是负责CPU的调度,线程调度就是按照特定的机制为多个线程分配CPU的使用权。
有两种调度模型:分时调度和抢占式调度
分时调度:就是让所有的线程轮流获得CPU的使用权,并且平均分配到各个线程占有CPU的时间片。
抢占式调度:Java虚拟机采用抢占式调度模型,是指优先让线程池中优先级高的线程首先占用
CPU,如果线程池中优先级相同,那么随机选择一个线程,使其占有CPU,处于这个状态的CPU会一直运行,优先级高的分的CPU的时间片相对会多一点
什么是线程调度(Thread Scheduler)和时间分片(Time Slicing )
线程调度是一个操作系统服务,它负责为储在Runnable状态的线程分配CPU时间片,一旦我们创建一个线程并启动它,它的执行便依赖线程调度器的实现。
时间分片是指CPU可用时间分配给Runnable的过程,分配的时间可以根据线程优先级或线程等待时间。
spring部分
什么是spring?spring的核心机制是什么?
Spring的核心控制反转(IOC)和面向切面(AOP),简单来说Spring是一个分层JavaEE一站式轻量级开源框架。
IOC和AOP是spring的核心机制
IOC:控制反转/依赖注入,在之前学习的过程中,比如有一个类,我们想要调用类里面的方法,就要创建该类的对象,使用对象调用方法来实现。但是对于Spring来说,不用再自己创建要使用的对象,而是由Spring容器统一管理,自动注入,注入就是赋值
AOP:面向切面编程,简单来说就是我们可以在不修改源码的情况下,对程序的方法进行增强,将涉及多业务流程的通用功能抽取并单独封装,形成独立的切面,再合适的时机将这些切面横向切入到业务流程的指定的位置中
spring中有多少个模块?有那几部分构成
核心容器模块,数据访问和集成模块,web模块,AOP 和 Aspect 模块,测试模块
首先是核心容器模块,这是 Spring 的基础,包括 Core、Bean、Context 和 SpEL。Core 和 Bean 提供了 IoC 容器的核心实现,负责 Bean 的定义、创建和依赖注入;Context 基于前两者扩展,增加了国际化、事件传播等企业级功能;SpEL 是表达式语言,方便在配置中动态操作对象。
然后是 数据访问与集成模块,主要包括 JDBC、ORM、Transactions 等。JDBC 提供模板类简化数据库操作;ORM 集成了 Hibernate、MyBatis 等框架,统一了持久层编程模型;Transactions 支持声明式事务,通过注解或配置就能管理事务,不用手动写代码控制。
接下来是 Web 模块,分为 Web、Web MVC、Web Socket 等。Web 模块支持在 Web 环境下初始化 IoC 容器;Web MVC 就是 Spring MVC,负责请求分发、视图解析等,是 Web 开发的核心;还有针对 WebSocket 等实时通信的支持。
另外还有 AOP 和 Aspect 模块,提供面向切面编程的能力,比如日志、权限、事务等横切逻辑可以通过 AOP 统一处理,Aspect 模块则集成了 AspectJ 框架,增强了 AOP 的功能。
最后还有 测试模块,比如 Test 模块,支持用 JUnit、Mockito 等框架测试 Spring 组件,提供了依赖注入、事务回滚等测试支持,方便单元测试和集成测试。
Spring的IOC和AOP机制(这个相当的重要)
什么是控制反转(IOC)?什么是依赖注入(DI)
IOC:就是对象之间的依赖关系由容器来创建,在我们使用Spring框架后,对象之间的关系由容器来创建和维护,将开发者做的事让容器做,这就是控制反转。BeanFactory接口是Spring Ioc容器的核心接口。
DI:将属性值注入给了属性,将属性注入给了bean,将bean注入给了ioc容器。
AOP即面向切面编程
Spring 中的 AOP是一种编程思想,它将程序中的横切关注点(如日志、事务等)从业务逻辑代码中分离出来,以提高代码的可重用性和可维护性。
在 Spring 框架中,AOP 是通过代理模式实现的,即在运行时动态地生成一个代理类,这个代理类会拦截目标对象的方法调用,并在方法执行前后添加相应的横切逻辑。
AOP 有哪些实现方式?
实现AOP的技术,主要分为两大类:
静态代理:
指的是使用的AOP框架提供的命令进行编译,而在编译阶段就可以生成AOP代理类,因此也称为编译时增强
- 编译时编织
- 类加载的时候编织
动态代理
指的是在内存中临时生成的AOP动态代理类,因此也被称为运行时增强
- JDK动态代理
- CGLIB
何为编织?
为了创建一个 advice 对象而链接一个 aspect 和其它应用类型或对象,称为编织(Weaving)。
什么是依赖注入?可以通过多少种方式完成依赖注入?
在依赖注入中,您不必创建对象,但必须描述如何创建它们。您不是直接在代码中将组件和服务连接在一起,而是描述配置文件中哪些组件需要哪些服务。由 IoC容器将它们装配在一起。
通常我们我们可以通过三种方式来完成依赖注入
使用构造函数注入,setter注入,接口注入
一、构造器注入
将被依赖对象通过构造函数的参数注入给依赖对象,并且在初始化对象的时候注入。
优点:对象初始化完成后便可获得可使用的对象。
缺点:当需要注入的对象很多时,构造器参数列表将会很长;不够灵活。若有多种注入方式,每种方式只需注入指定几个依赖,那么就需要提供多个重载的构造函数,麻烦。
二、setter方法注入
IoC Service Provider通过调用成员变量提供的setter函数将被依赖对象注入给依赖类。
优点:灵活。可以选择性地注入需要的对象。
缺点:依赖对象初始化完成后由于尚未注入被依赖对象,因此还不能使用。
三、接口注入
依赖类必须要实现指定的接口,然后实现该接口中的一个函数,该函数就是用于依赖注入。该函数的参数就是要注入的对象
优点:接口注入中,接口的名字、函数的名字都不重要,只要保证函数的参数是要注入的对象类型即可。
缺点:侵入行太强,不建议使用。
PS:什么是侵入行?
如果类A要使用别人提供的一个功能,若为了使用这功能,需要在自己的类中增加额外的代码,这就是侵入性
spring 提供了哪些配置方式?
基于 xml 配置
bean 所需的依赖项和服务在 XML 格式的配置文件中指定。这些配置文件通常包含许多 bean 定义和特定于应用程序的配置选项。它们通常以 bean 标签开头。
基于注解配置
您可以通过在相关的类,方法或字段声明上使用注解,将 bean 配置为组件类本身,而不是使用 XML 来描述 bean 装配。默认情况下,Spring 容器中未打开注解装配。因此,您需要在使用它之前在 Spring 配置文件中启用它。
基于 Java API 配置
Spring 的 Java 配置是通过使用 @Bean 和 @Configuration 来实现。
1、 @Bean 注解扮演与 元素相同的角色。
2、 @Configuration 类允许通过简单地调用同一个类中的其他 @Bean 方法来定义 bean 间依赖关系。
spring四大注解
以下注解在某个类上加入任何一个注解能够快速的将这个组件加入到IOC容器的管理中:
@Componet:(万物起源)容器管理bean的基础注解,应用于具体的实现类上,不属于以下三类时,用此注解。
这将 java 类标记为 bean。它是任何 Spring 管理组件的通用构造型。spring 的组件扫描机制现在可以将其拾取并将其拉入应用程序环境中。
@Service:一般应用于业务层(PersonService)
此注解是组件注解的特化。它不会对 @Component 注解提供任何其他行为。您可以在服务层类中使用@Service 而不是@Component,因为它以更好的方式指定了意图。
@Repository:一般应用于持久层(PersonDao)
这个注解是具有类似用途和功能的 @Component 注解的特化。它为 DAO 提供了额外的好处。它将 DAO 导入 IoC 容器,并使未经检查的异常有资格转换为 Spring DataAccessException。
@Controller:一般应用于控制层(PersonServlet)
以上注解可以随便加,Spring底层不会去验证你的组件,但是我们推荐各层用各自的注解。
Spring中重要的注解
说人话解释:
@Required:属性必须赋值,不赋值就报错
@Autowired:自动找匹配类型的 Bean 塞进来
@Qualifier:配合 @Autowired,在多个同类型 Bean 中挑指定名字的
@RequestMapping:指定哪个 URL 和请求方式由哪个方法处理
@Required 应用于 bean 属性 setter 方法。此注解仅指示必须在配置时使用bean 定义中的显式属性值或使用自动装配填充受影响的 bean属性。如果尚未填充受影响的 bean 属性,则容器将抛出 eanInitializationException。
@Autowired 可以更准确地控制应该在何处以及如何进行自动装配。此注解用于在 setter 方法,构造函数,具有任意名称或多个参数的属性或方法上自动装配bean。默认情况下,它是类型驱动的注入。
@Qualifier 当您创建多个相同类型的 bean 并希望仅使用属性装配其中一个 bean 时,您可以使用@Qualifier 注解和 @Autowired 通过指定应该装配哪个确切的 bean
@RequestMapping 注解用于将特定 HTTP 请求方法映射到将处理相应请求的控制器中的特定类/方法。此注释可应用于两个级别:
类级别:映射请求的 URL 方法级别:映射 URL 以及 HTTP 请求方法
Autowired和Resource的区别
- @Autowired是Spring框架提供的注解,而@Resource是JDK提供的注解。
- @Autowired先根据类型查找,如果存在多个Bean,再根据名称查找,
- @Resource是按照名称注入,先根据名称查找,如果查找不到,再根据类型查找
spring bean容器的生命周期
生命周期由:实例化——》属性注入——》初始化——》使用状态——》销毁状态
首先是实例化阶段,容器通过反射调用 Bean 的无参构造方法创建实例,这一步只是生成了对象,还没设置任何属性。
接下来是属性注入,容器会根据配置(比如 XML 或注解),将依赖的属性值或其他 Bean 注入到当前实例中,完成对象的基本初始化准备。
然后进入初始化阶段,这个阶段比较关键,包含多个步骤:
- 如果 Bean 实现了
BeanNameAware接口,会调用setBeanName()方法,传入 Bean 的 ID; - 若实现
BeanFactoryAware接口,调用setBeanFactory()方法,获取当前 BeanFactory 的引用; - 若实现
ApplicationContextAware接口,会通过setApplicationContext()方法拿到 ApplicationContext; - 之后会执行
BeanPostProcessor的postProcessBeforeInitialization()方法,这是初始化前的扩展点; - 接着调用 Bean 自身定义的初始化方法,比如 XML 配置的
init-method或@PostConstruct注解标注的方法; - 最后执行
BeanPostProcessor的postProcessAfterInitialization()方法,像 AOP 代理通常就在这一步生成。
初始化完成后,Bean 就进入使用阶段,容器会将其提供给应用程序调用。
当容器关闭时,进入销毁阶段:
- 若 Bean 实现
DisposableBean接口,会调用destroy()方法; - 同时会执行自定义的销毁方法,比如 XML 配置的
destroy-method或@PreDestroy注解的方法; - 最终 Bean 实例被回收,生命周期结束。
自动装配有哪些方式?
Spring 容器能够自动装配 bean。也就是说,可以通过检查 BeanFactory 的内容让 Spring 自动解析 bean 的协作者。
自动装配的不同模式:
- 按名称自动装配(byName)
- 按类型自动装配(byType)
- 构造函数自动装配(constructor)
- 按照默认方式自动装配(default): byType 进行自动装配
设计模式列表(背常见那几个就行)
工厂模式用途:通过一个专门的 “工厂” 类来创建对象,你只需要告诉工厂你要什么类型的对象,不用自己动手创建。
单例模式用途:保证一个类只有一个实例,并且提供一个全局访问点。
代理模式用途:找一个 “代理人” 来帮你处理事情,代理人可以在真正做事之前 / 之后做一些额外操作。
| 目的 | 范围 | 名称 | 描述 |
|---|---|---|---|
| 创建型 | 类 | 工厂方法模式 (Factory Method) | 定义一个用于创建对象的接口,让子类决定将哪一个类实例化。Factory Method使一个类的实例化延迟到其子类。 |
| 对象 | 抽象工厂模式 (Abstract Factory) | 提供一个创建一系列相关或互相依赖对象的接口,而无需指定它们具体的类。 | |
| 建造者模式 (Builder) | 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。 | ||
| 原型模式 (Prototype) | 用原型实例指定创建对象的种类,并且通过拷贝这个原型来创建新的对象。 | ||
| 单例模式 (Singleton) | 保证一个类仅有一个实例;并提供一个访问它的全局访问点。 | ||
| 结构型 | 类 | 适配器模式 (Adapter) | 将一个类的接口转换成客户希望的另一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。 |
| 桥接模式 (Bridge) | 将抽象部分与它的实现部分分离,使它们都可以独立地变化。 | ||
| 对象 | 组合模式 (Composite) | 将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使得客户对单个对象和复合对象的使用具有一致性。 | |
| 装饰模式 (Decorator) | 动态地给一个对象添加一些额外的职责。就扩展功能而言,Decorator模式比生成子类方式更为灵活。 | ||
| 外观模式 (Facade) | 为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。 | ||
| 享元模式 (Flyweight) | 运用共享技术有效地支持大量细粒度的对象。 | ||
| 代理模式 (Proxy) | 为其他对象提供一个代理以控制对这个对象的访问。 | ||
| 行为型 | 类 | 解释器模式 (Interpreter) | 给定一个语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。 |
| 模板方法模式 (Template Method) | 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。 | ||
| 责任链模式 (Chain of Responsibility) | 为解除请求的发送者和接收者之间耦合,而使多个对象都有机会处理这个请求。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它。 | ||
| 命令模式 (Command) | 将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可取消的操作。 | ||
| 迭代器模式 (Iterator) | 提供一种方法顺序访问一个聚合对象中各个元素,而又不需要暴露该对象的内部表示。 | ||
| 中介者模式 (Mediator) | 用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。 | ||
| 备忘录模式 (Memento) | 在不破坏封装性的前提下,捕获一个对象的内部状态。 | ||
| 观察者模式 (Observer) | 定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动刷新。 | ||
| 状态模式 (State) | 允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它所属的类。 | ||
| 策略模式 (Strategy) | 定义一系列的算法,把它们一个封装起来,并且使它们可相互替换。本模式使得算法的变化可独立于使用它的客户。 | ||
| 访问者模式 (Visitor) | 表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。 |
MVC模式、饿汉模式 和 懒汉模式 在上面的表格中没有直接列出,但它们都属于设计模式的范畴,具体分类如下:
| 模式名称 | 分类 | 关联的经典设计模式 |
|---|---|---|
| MVC模式 | 架构模式(架构模式本质上不是设计模式) | 观察者模式、策略模式 |
| 饿汉模式 | 单例模式(创建型) | 单例模式 |
| 懒汉模式 | 单例模式(创建型) | 单例模式 |
什么是 Aspect?切点?通知?
aspect 由 pointcount 和 advice 组成, 它既包含了横切逻辑的定义, 也包括了连接点的定义. Spring AOP 就是负责实施切面的框架, 它将切面所定义的横切逻辑编织到切面所指定的连接点中. AOP 的工作重心在于如何将增强编织目标对象的连接点上, 这里包含两个工作:
1、如何通过 pointcut 和 advice 定位到特定的 joinpoint 上
2、如何在advice 中编写切面代码.
说人话就是使用 @Aspect 注解的类就是切面
切点:程序运行中的一些时间点, 例如一个方法的执行, 或者是一个异常的处理.在 Spring AOP 中, join point 总是方法的执行点
通知:特定 JoinPoint 处的 Aspect 所采取的动作称为 Advice。Spring AOP 使用一个 Advice 作为拦截器,在 JoinPoint “周围”维护一系列的拦截器。
MVC模型
MVC全称Model View Controller,是一种设计创建Web应用程序的模式。这三个单词分别代表Web应用程序的三个部分:Model(模型):指数据模型。用于存储数据以及处理用户请求的业务逻辑。在Web应用中,JavaBean对象,用来进行数据封装
View(视图):用于展示模型中的数据的,一般为jsp或html文件。
Controller(控制器):用来接受用户的请求。
简述 MVC、MVVM 的关系与区别?
MVC:Model-View-Controller 模型-视图-控制器
MVVM:Model-View-ViewModel 模型-视图-视图模型
相同点
- 都是为了分离 View 和 Model,M 注重数据,V 注重视图,使 Model 和 View 更易于维护。
不同点
- MVC 是系统架构级别的,MVVM 是用于单页面上的,MVVM 的灵活性大于 MVC。
- MVC 是 Controller 从 View 视图层收集数据,然后向相关模型请求数据并返回相应的视图来完成交互请求。
- MVVM 本质上是 MVC 的改进版,其最重要的特性是数据绑定,此外还包括依赖注入,路由配置,数据模板等一些特性。
ORM思想
对象关系映射(Object Relational Mapping),它的作用是在关系型数据库和业务实体对象之间作一个映射,这样,我们在具体的操作业务对象的时候,就不需要再去和复杂的SQL语句打交道,只需简单的操作对象的属性和方法。
Spring MVC框架的作用
Spring Web MVC 框架提供 模型-视图-控制器 架构和随时可用的组件,用于开发灵活且松散耦合的 Web 应用程序。MVC 模式有助于分离应用程序的不同方面,如输入逻辑,业务逻辑和 UI 逻辑,同时在所有这些元素之间提供松散耦合。
DispatcherServlet 的工作流程(Spring MVC流程)
这里建议是找个图,我这里就不污染图床了,请自行寻找网图来辅助背诵
1、 用户发送请求至前端控制器DispatcherServlet。
2、 DispatcherServlet收到请求调用HandlerMapping处理器映射器。
3、 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
4、 DispatcherServlet调用HandlerAdapter处理器适配器。
5、 HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。
6、 Controller执行完成返回ModelAndView。
7、 HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。
8、 DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
9、 ViewReslover解析后返回具体View。
10、DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
11、 DispatcherServlet响应用户
以下是Spring MVC请求处理流程中涉及的英文术语及其中文音译对照表:
| 英文术语 | 中文音译(谐音参考) | 实际含义说明 |
|---|---|---|
| DispatcherServlet | 迪斯佩彻-色尔维特 | 前端控制器(中央调度器) |
| HandlerMapping | 汉德勒-马平 | 处理器映射器(路由匹配器) |
| HandlerAdapter | 汉德勒-阿达普特 | 处理器适配器(接口适配器) |
| Controller | 康特罗勒 | 后端控制器(业务控制器) |
| ModelAndView | 莫德尔-安德-维尤 | 模型和视图(数据视图容器) |
| ViewReslover | 维尤-瑞佐尔沃 | 视图解析器(模板引擎管理器) |
| Interceptor | 因特塞普特 | 拦截器(预处理/后处理组件) |
拦截器和控制器的区别
拦截器 vs 过滤器的区别?
- 拦截器是springMVC组件,过滤器servlet的三大组件之一
- 拦截器只能对控制器请求起作用,而过滤器则可以对所有的请求起作用
- 拦截器可以直接获取IOC容器中的对象,而过滤器就不太方便获取
Spring框架中都用到了哪些设计模式
(1)工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例;
(2)单例模式:Bean默认为单例模式。
(3)代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术;
(4)模板方法:用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate。
(5)观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如Spring中listener的实现—ApplicationListener
@Required 注解,@Autowired 注解,@Qualifier 注解,作用和区别
@Required 注解这个注解表明 bean 的属性必须在配置的时候设置,通过一个 bean 定义的显**式的属性值或通过自动装配,若@Required 注解的 bean 属性未被设置,容器将抛出BeanInitializationException。
@Autowired 注解提供了更细粒度的控制,包括在何处以及如何完成自动装配。它的用法和@Required 一样,修饰 setter 方法、构造器、属性或者具有任意名称和/或多个参数的 PN 方法。
@Qualifier当有多个相同类型的 bean 却只有一个需要自动装配时,将@Qualifier 注解和@Autowire 注解结合使用以消除这种混淆,指定需要装配的
确切的 bean。
@Autowired vs @Resource的区别
● @Autowired是Spring框架提供的注解,而@Resource是JDK提供的注解。
● @Autowired默认是按照类型注入,@Resource是按照名称注入
列举 spring 支持的事务管理类型
Spring 支持两种类型的事务管理:
1、 程序化事务管理:在此过程中,在编程的帮助下管理事务。它为您提供极大的灵活性,但维护起来非常困难。
2、 声明式事务管理:在此,事务管理与业务代码分离。仅使用注解或基于 XML的配置来管理事务。
- XML的声明式事务
- 基于注解的声明式事务
Spring Boot部分
什么是 Spring Boot?为什么要用SpringBoot?
pring Boot是Spring官方推出的一个快速开发框架,他的优点非常的多例如:
一、独立运行
Spring Boot而且内嵌了各种servlet容器,Tomcat、Jetty等,现在不再需要打成war包部署到容器中,Spring Boot只要打成一个可执行的jar包就能独立运行,所有的依赖包都在一个jar包内。
二、简化配置
spring-boot-starter-web启动器自动依赖其他组件,简少了maven的配置。
三、自动配置
Spring Boot能根据当前类路径下的类、jar包来自动配置bean,如添加一个spring-boot-starter-web启动器就能拥有web的功能,无需其他配置。
四、无代码生成和XML配置
Spring Boot配置过程中无代码生成,也无需XML配置文件就能完成所有配置工作,这一切都是借助于条件注解完成的,这也是Spring4.x的核心功能之一。
五、应用监控
Spring Boot提供一系列端点可以监控服务及应用,做健康检测
Spring Boot的核心注解是哪个?它主要由哪几个注解组成的?
启动类上面的注解是@SpringBootApplication,它也是 Spring Boot 的核心注解,主要组合包含了以下3 个注解:@SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能。@EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项,如关闭数据源自动配置功能:@ComponentScan:Spring组件扫描
Spring boot 配置文件有哪几种类型?
Spring Boot 的核心配置文件是 application 和 bootstrap 配置文件。
- application 是用户级别的配置文件,主要用于 Spring Boot 项目的自动化配置。
- bootstrap 是系统级别的配置文件
如何理解 Spring Boot 中的 Starters?
Starters是什么:
Starters可以理解为启动器,它包含了一系列可以集成到应用里面的依赖包,你可以一站式集成Spring及其他技术,而不需要到处找示例代码和依赖包。如你想使用Spring JPA访问数据库,只要加入springboot-starter-data-jpa启动器依赖就能使用了。Starters包含了许多项目中需要用到的依赖,它们能快速持续的运行,都是一系列得到支持的管理传递性依赖。
springboot常用的starter有哪些
spring-boot-starter-web 嵌入tomcat和web开发需要servlet与jsp支持
spring-boot-starter-data-jpa 数据库支持
spring-boot-starter-data-redis redis数据库支持
spring-boot-starter-data-solr solr支持
mybatis-spring-boot-starter 第三方的mybatis集成starter
Spring Boot 需要独立的容器运行吗?
基本上不需要,但是也可以部署到其他容器中,Spring Boot内置了Tomcat/ Jetty等容器
Spring Boot中的监视器是什么?
Spring boot actuator是spring启动框架中的重要功能之一。Spring boot监视器可帮助您访问生产环境中正在运行的应用程序的当前状态。有几个指标必须在生产环境中进行检查和监控。即使一些外部应用程序可能正在使用这些服务来向相关人员触发警报消息。监视器模块公开了一组可直接作为HTTP URL访问的REST端点来检查状态
如何使用Spring Boot实现异常处理?
Spring提供了一种使用ControllerAdvice处理异常的非常有用的方法。 我们通过实现一个ControlerAdvice类,来处理控制器类抛出的所有异常
如何理解 Spring Boot 配置加载顺序
在 Spring Boot 里面,可以使用以下几种方式来加载配置。
1)properties文件;
2)YAML文件;
3)系统环境变量;
4)命令行参数;
等等……
Spring Boot 的核心配置文件有哪几个?它们的区别是什么?
Spring Boot 的核心配置文件是 application 和 bootstrap配置文件。
application 配置文件这个容易理解,主要用于 Spring Boot 项目的自动化配置。
bootstrap 配置文件有以下几个应用场景。
- 使用 Spring Cloud Config 配置中心时,这时需要在 bootstrap 配置文件中添加连接到配置中心的配置属性来加载外部配置中心的配置
信息; - 一些固定的不能被覆盖的属性;
- 一些加密/解密的场景
什么是 JavaConfig?
Spring JavaConfig 是 Spring 社区的产品,它提供了配置 Spring IoC 容器的纯Java 方法。因此它有助于避免使用 XML 配置。使用JavaConfig 的优点在于:
1、面向对象的配置:由于配置被定义为 JavaConfig 中的类,因此用户可以充分利用 Java 中的面向对象功能。一个配置类可以继承另一个,重写它的@Bean 方法等。
2、减少或消除 XML 配置:基于依赖注入原则的外化配置的好处已被证明。但是,许多开发人员不希望在 XML 和 Java 之间来回切换。JavaConfig 为开发人员提供了一种纯 Java 方法来配置与 XML 配置概念相似的 Spring 容器。从技术角度来讲,只使用 JavaConfig 配置类来配置容器是可行的,但实际上很多人认为将JavaConfig 与 XML 混合匹配是理想的。
3、类型安全和重构友好:JavaConfig 提供了一种类型安全的方法来配置 Spring容器。由于 Java 5.0 对泛型的支持,现在可以按类型而不是按名称检索 bean,不需要任何强制转换或基于字符串的查找。
什么是Spring cloud
Spring Cloud 是一系列框架的有序集合,它利用 Spring Boot 的开发便利性简化了分布式系统的开发,比如服务发现、服务网关、服务路由、链路追踪等。Spring Cloud 并不重复造轮子,而是将市面上开发得比较好的模块集成进去,进行封装,从而减少了各模块的开发成本。换句话说:Spring Cloud 提供了构建分布式系统所需的“全家桶”。
