1.面向对象
面向对象注重的是某件事的参与者
面向过程注重的是某件事的执行流程
特性
封装:暴露可以展示的接口,隐藏细节。外面无需关心内部实现,比如私有属性的改变应该提供get,set对外访问
继承:继承父类的方法,子类共有的方法及属性直接调用父类的,不需要自己再定义,自己可以扩展自己的特有功能
多态:基于对象所属类不同,外部调用相同方法,实际执行逻辑不同
用到继承,重写,父类引用指向子类对象(无法调用子类特有功能)
父类类型 变量名 = new 子类对象
变量名.方法名()
2.JDK,JRE,JVM三者的区别及联系
jdk:是一个java开发工具,提供给开发者使用的
jre :ava运行时环境,用户需要运行java程序就需要安装jre
jvm:虚拟机,是用来解释我们的class文件,解释成机器码,让操作系统能够执行
3.==和equals
==比较的是栈中的值,基本类型比较的是常量值,引用类型是堆内存中对象的地址
堆中还有一块常量池,直接使用String str = “abc”。会把“abc放入常量池”
4.简述final作用
final可以修饰变量,方法,类,
在变量上,说明不可修改
在方法上,说明方法不可被覆盖,但是可以重载
在类上,说明不可继承
1)final修饰成员变量
-
final修饰类变量,只能在静态代码块中指定初始值或者声明该变量的指定初始值
final static a = 0;
{
a=0;
} -
final修饰成员变量,可以在非静态代码块声明该变量或者构造器指定初始值
final int a = 0;
{
a = 0;
}
2)final修饰局部变量
系统不会为局部变量初始化,局部变量必须由开发中初始化。final修饰局部变量,后面代码不能再对此变量进行赋值
3)final修饰基本类型数据和引用类型数据
- final修饰基本类型数据,该数值在初始化后便不能改变
- final修饰引用类型数据,其初始化后不能再指向另一个对象,但是引用的值可以改变
5.为什么局部内部类和匿名内部类只能访问局部final变量
内部类和外部类是同一个级别,内部类不会因定义在方法中就会随方法的执行完毕销毁
外部类的方法执行结束之后,局部变量就会被销毁,但是内部类对象还可能存在(直到没人使用才会死亡)。局部变量会复制一份作为内部类的成员变量,局部变量死亡后,内部类仍然可以使用,为了保证这个变量是相同的,就将局部变量设置为final.保证内部类和方法的局部变量建立的拷贝一致
6.String,StringBuffer,StringBuilder的区别及使用场景
String是Final修饰的,不可变,每次操作都会产生新的String对象,比如说abc + d,要是多次改变,会导致内存浪费
StringBuffer,StringBuilder都是在原对象上操作
StringBuffer线程安全,都是synchronized修饰的
StringBuilder线程不安全
性能StringBuilder>StringBuffer>String
场景:经常需要改变字符串内容用StringBuffer,StringBuilder
优先使用StringBuilder,多线程使用共享变量时StringBuffer
7.重载和重写的区别
重载发生在同一个类中
方法名必须相同,参数类型,顺序,个数必须不一致
返回值,访问修饰符可以不同,发生在编译时
重写发生在子类继承父类时
方法名,参数列表必须相同,访问修饰符大于等于父类,抛出异常小于等于父类,返回值范围小于等于父类。如果父类方法的访问修饰符为private则子类不能重写该方法
8.接口和抽象类的区别
- 抽象类可以存在普通成员函数,接口中只能存在public abstract方法
- 抽象类的成员变量可以是各种类型,接口中的成员变量只能是public final static类型
- 抽象类只能继承一个,接口可以实现多个
接口设计目的,是对类的行为约束,只关心有没有这个方法,不关心怎么实现
接口是对行为的抽象,表达的是is a的关系。核心是定义行为。不关心如何去实现
抽象类的设计目的是代码复用,把子类具有相同的行为抽取出来。因为有些行为是子类特有的,父类无法实现,所以抽象类不允许实例化
抽象类是对类本质的抽象,表示的是is a的关系,比如黄雀是鸟。抽象类包含并实现子类的通用特性,子类特有的自己实现
抽象类的功能要远超接口,但是定义抽象类代价高。我们可以在一个类同时实现多个接口,降低设计难度
9.list和set的区别
- list:有序,按对象进入的顺序保存对象,可重复,运行多个null元素对象,可以使用iterator取出所有元素,再逐一遍历。还可以使用get(int index)获取指定下标的元素
- set:无序,不可重复,最多有一个null元素对象,取元素时只能用iterator取出所有元素,再逐一遍历
10.HashCode与equals
HashCode介绍
HashCode的作用是获取哈希码。它实际返回的是一个int整数。对象时存储在队中,怎么找到对象在堆中的位置,实际上是维护了一张hash表,hash表的索引就是hashcode,可以快速通过索引找到这个对象在hash表中的位置。作用是确定该对象在哈希表中的索引位置。
为什么要有hashCode
以HashSet如何检查重复值说明
对象在加入hashset之前,hashset会计算对象的hashcode来判断对象加入的位置,判断该位置是否有值,如果没有,hashset会假设没有重复值出现。如果发现有值,会调用equals()方法来检查两个对象是否真的相同。如果两者相同,hashset则不会再让其加入。如果不同,就会重新散列到其他位置。这样就大大减少了equals的次数,就会相应的提高执行的效率。
- 如果两个对象相等,他的hashcode一定相同
- 两个对象相等,分别调用equals也相同
- 两个对象有不同的hashcode值,他们也不一定相等。因此覆盖equals方法,也必须覆盖hashcode方法
- hashcode是对堆上的对象产生独特值。如果没有重写hashcode方法。则两个对象一定不会相等。
11.AraayList和LinkedList区别
AraayList基于动态数组,连续内存存储,适合下标访问。
扩容机制上,因为数组长度固定,超出长度时,会新建数组,将原来数组的内容拷贝到新数组上。如果不是尾部插入数据时还会涉及到元素的移动(往后复制一份,插入新元素)。尾插法并指定初始容量可以极大的提高性能,甚至超过LinkedList
LinkedList基于链表。可以存储在分散的内存中,就算部分碎片空间也可以存储。适合数据的插入和删除,不适合查询:需要逐一遍历
遍历LinkedList必须使用iterator不能使用for循环,因为每次for循环体内通过get(i)去某一元素是都需要对list重新遍历,消耗性能极大。
使用indexIOf会对list进行遍历,结果为空时,会遍历整个列表
12.HashMap和HashTable的区别,底层实现是什么?
13.ConcurrentHashMap原理,Jdk7和JDK8版本的区别
ConcurrentHashMap线程安全
jDK8:
数据结构:synchronized+CAS+node+红黑树,Node的val和next都用volatitle修饰,保证可见性
查找,替换,赋值操作都用CAS(乐观锁)
锁:锁链表的head节点,不影响其他元素的读写,锁粒度更细,效率更高,扩容时,因为是并发扩容,阻塞所有的读写操作
读操作无锁:
node的val和next都用volatitle修饰,读写线程对该变量互相可见
数组用volatitle修饰,保证寇蓉是被读线程感知
14.如何实现一个IOC容器
1.写一个配置文件,配置包扫描路径
2.定义一些注解
3.从配置文件中获取扫描的包路径,获取到当前路径下的文件信息和文件夹信息,将当前路径下所有以.class结尾的文件添加到set集合中进行存储
4.变量这个set集合,获取在类上有指定注解的类并交给IOC容器,定义一个安全的map来存储这些对象
5.遍历这个IOC容器,通过反射获取到每一个类的实例,判断里面是否有依赖其他类的实例额,然后进行递归注入
15.什么是字节码?采用字节码的好处?
16.java类加载器有哪些
JDK 有三个自带的类加载器:
bootstrap ClassLoader(启动类(根)加载器):
c++编写,加载java核心库java.*,构造ExtClassLoader和AppClassLoader。由于启动类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以获取到的最上一级的加载器返回结果为空
extension ClassLoader(扩展类加载器):
java编写,加载扩展库,如classpath中的jre ,javax.*或者
java.ext.dir 指定位置中的类,开发者可以直接使用标准扩展类加载器。
Application ClassLoader(应用程序加载器):
java编写,加载程序所在的目录,如user.dir所在的位置的class
CustomClassLoader(用户自定义类加载器)
java编写,用户自定义的类加载器,继承Application ClassLoader,可加载指定路径的class文件,负责加载classpath下的类文件
17.双亲委派机制
1.类加载器受到类加载的请求
2.向上委派:实际上就是查找缓存,是否加载了该类,如果有直接返回,如果没有则继续向上。
3.委派到顶层之后,缓存中还是没有,则到加载路径中查找,有则返回,没有则向下查找
4.向下查找,查找加载路径。到发起加载的加载器为止
好处:
- 安全性,避免用户自己写的类替换java的核心类,比如String
- 避免类的重复加载,全路径加上被哪个类加载器加载,才是唯一确定这个类的方式。
18.java中的异常体系
oom(内存溢出),error,程序无法处理
java中所有的异常都来自throwable。
throwable下有两个子类Exception和Error。
Error是程序无法处理的错误,程序一旦发生这个错误,会立即停止。
Exception不会导致程序停止,分为RunTimeException(运行时异常)和CheckedException(检查异常)
运行异常常常发生在程序运行过程中,导致当前程序执行失败
检查异常常常发生在编译过程中,会导致程序编译不通过
19.JVM
方法区(Method Area):被线程共享,主要存放静态变量,常量,类信息(构造方法,接口定义)
程序计数器:每个线程都有一个程序计数器,是线程私有的。
栈(stack):主要存放8大基本数据类型,对象引用,实例的方法。jvm会为每一个线程分配一个栈区,一旦线程结束,栈也被清空,所有不存在垃圾回收问题
堆(HEAP):主要存放类,方法,常量,变量,所有的引用类型的真实对象。一个JVM只有一个堆内存,堆内存大小是可以调节的。堆是GC的主要区域
堆还可以分为新生区,老年区,永久区
新生区又分为伊甸区,幸存者0区,幸存者1区
GC垃圾回收主要发生在伊甸区和老年区
20.GC
标记清除法,标记压缩,复制算法,引用计数器
复制算法
优点:没有内存的碎片
缺点:浪费了内存空间;多了一半空间永远是空to.假设对象100%存活(就是一种极端情况)
复制算法最佳使用场景:对象存活度较低时,新生区。
标记清除算法
扫描所有对象,对活着的对象进行标记
清除:对没有标记的对象进行清除
优点:不需要额外的空间
缺点:两次扫描,严重浪费时间,会产生内存碎片
标记压缩
压缩:在标记清除算法的基础上,再次扫描向前移动存活的对象,防止内存碎片产生
缺点:多了一份移动成本
内存效率:复制算法 》 标记清除算法 》 标记压缩算法 (时间复杂度)
内存整齐度: 复制算法 = 标记压缩算法 》 标记清除算法
内存利用率: 标记压缩算法 = 标记清除算法 》 复制算法
没有最优的算法,只有最合适的算法
现在最合适的是GC分代收集算法
21.线程的生命周期,线程有哪些状态?
线程通常有5中状态,创建(创建一个线程对象),就绪(等待获取CPU的使用权),运行(获取CPU),阻塞(因为某种原因放弃CPU使用权),死亡(线程执行完毕或者因异常退出。该线程结束生命周期)
阻塞分为:
- 等待阻塞:运行的线程执行wait方法,该线程会释放所有的资源,JVM会把该线程放入“等待池”中。进入这个状态后是不能自动唤醒的,只能通过其他线程通过notify或者notifyall方法才能唤醒
- 同步阻塞:运行的线程在获取对象的同步锁时,若该线程被其他的线程占用,则会把改线程放入锁池中。
- 其他阻塞:运行的线程执行sleep或者join方法,或者发出i/o请求,JVM会把该线程置为阻塞状态,当sleep超时,join等待线程终止或超时,i/o处理完毕,线程重新转为就绪状态。
22.wait,sleep,yield,join
wait,sleep
- sleep不会释放锁,wait会释放锁,而且会加入等待队列中
- sleep不依赖synchronized,wait依赖synchronized
- sleep一般会让当前线程休眠,wait一般用于多线程的通信
- sleep会让CPU执行时间强制上下文切换,wait不一定,wait有可能进入等待池中竞争到锁重新执行
yeild执行后直接就如就绪状态,马上释放cpu执行权,但依然保留CPU的执行权,有可能下次线程调度还会让这个线程获取执行权执行
join执行后进入阻塞状态,阻塞的是其他线程
23.对线程安全的理解
堆是共享内存的可以被所有线程访问,当多个线程访问一个对象时,如果不做额外的同步控制或者协调控制,调用整个对象的行为都能获取到单线程操作该对象获取到的结果。就理解这个对象是线程安全的。
栈是每个线程都独立的,栈在线程开始时初始化,所有栈是线程安全的
24.线程的三种创建方式
如果有复杂的线程操作需求,那就继承Thread,执行一个简单的任务,那就实现runnable
- 继承Thread
1.自定义类继承Thread类
2.重写run方法,编写线程执行体
3.创建线程,调用start()方法启动线程
public class test2 extends Thread{
long minPrime;
public test2(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
for (int i = 0; i < 5;i++) {
System.out.println("aaaaaaaaaa");
}
}
public static void main(String[] args) {
test2 p1 = new test2(143);
p1.start();
}
}
- 实现runnable接口
1.实现runnable接口
2.重写run方法
3.执行线程需要吧执行的对象丢入runnable接口实现类,调用start方法
注意:推荐使用,避免单继承局限性,灵活多变,方便同一个对象被多个线程使用
public class TestThread2 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 5;i++) {
System.out.println("多线程");
}
}
public static void main(String[] args) {
TestThread2 testThread2 = new TestThread2();
new Thread(testThread2).start();;
for (int i = 0; i < 5;i++) {
System.out.println("主线程");
}
}
}
- 实现callable接口
1)实现Callable接口,需要返回值类型
2)重写call方法,需要抛出异常
3)创建目标对象
4)创建执行服务
ExecutorService service = Executors.newFixedThreadPool(1);
5)提交执行
Future submit1 = service.submit(testTread1);
6)获取结果
Boolean aBoolean1 = submit1.get();
7)关闭服务
service.shutdown();
25.对守护线程的理解
为所有非守护线程提供服务的线程;任何一个守护线程都是JVM中所有非守护线程的保姆。
26.ThreadLocal的原理和使用场景
每一个Thead对象都含有一个ThreadLocalMap类型的成员变量ThreadLocals,它存储本线程中所有的ThreadLocal对象及其相应的值
由于每一条对象均含有各自私有的ThreadLocalMap容器,这些容器互不影响,因此不会存在线程安全问题。
使用场景:
- 对象跨层传递时,可避免多次传递。(比较controller层向service层传数据)
- 线程间数据隔离(只有自己的线程可以访问自己的数据,进而实现数据的隔离效果)
- 进行事务操作,用于存储线程事务信息
- 数据库连接,Session会话管理
spring框架在事务开始时会绑定一个JDBC connection,整个事务过程中都是使用这个connection来进行数据库操作,实现了事务的隔离性。spring框架就是使用ThreadLocal实现这种隔离
27.ThreadLocal内存泄漏原因,如何避免
28.并发,并行,串行
29.并发三大特性
30.为什么使用线程池,参数解释
31.线程池处理流程
32.线程池阻塞队列的作用
33.线程池复用的原理
34.spring是什么?
spring是一个轻量级的控制反转(ioc)和面向切面(aop)的容器框架。
- 从大小与开销两方面而言spring都是轻量级的
- 通过ioc达到松耦合的目的
- 提供面向切面编程支持,支持将一些通用任务复用。比如说开发一个切面,然后装配。完整自动打日志的操作。不用再写更多的打印日志代码
- 管理对象(bean)的生命周期
- 将简单的组件配置,组合成复杂的应用。可以把mybatis等框架整合到应用中
35.对AOP的理解
oop面向对象编程,运行定义从上到下的关系,但不是和从左到右,比如日志系统。
oop设计中,会导致大量代码的重复,不利于各个模块之间的重用
aop将程序中交叉的业务逻辑(比如日志,安全权限,事务等),封装成一个切面,然后注入到对象中,Aop可以对某些对象的功能进行增强,比如说在执行某个逻辑之前,之后做一些额外的事
36.对IOC的理解
容器概念,控制反转,依赖注入
ioc容器实际就是一个map,存放着各种对象(@bean, @reposiry, @Service, @Controller, @compont这些注解修饰的对象实例)。在项目启动时根据扫描这些带注解的类使用反射创建对象放到map中。
在我们需要使用这个对象时,通过DI(依赖注入)(@AutoWired,@Resource等注解)向这些对象注解数据。
把对对象的控制权交给IOC容器。对象与对象之间不管有没有依赖关系。IOC会根据需要主动创建对象之间的联系
依赖注入
获取依赖的过程由自身管理变为由IOC容器主动注入。在IOC容器运行期间,动态的将某种依赖关系注入到对象中。
37.BeanFactory 和 ApplicationContext有什么区别?
ApplicationContext是BeanFactory 的子接口
ApplicationContext既然是继承自ApplicationContext,所以提供了更多的功能
- 支持国际化
- 统一资源访问方式
- 提供在监视器中注入Bean的事件
- 可以同时加载多个配置文件
- 载入多个上下文,使每个上下文都专注于一个特定的层次,比如说应用的WEB层
加载方式:
BeanFactory 采用延迟加载,使用某个Bean才去去加载。坏处是不能自动检查Bean的完整性,如果有Bean某个属性没有注入,在调用getBean才会抛出异常
ApplicationContext在容器启动时一次性创建所有的Bean。有利于检查依赖属性是否注入。在需要时也不需要等待。坏处是占用内存空间,配置Bean过多时,第一次启动较慢。
注册方式:
BeanFactory 通常以编程的方式手动注册,ApplicationContext则是自动注册
38.Spring Bean的生命周期
- 1.扫描路径找出相应的类,然后实例化得到一个对象
- 2.对对象中添加了@Autowired注解的属性进行属性填充
- 3.回调Aware方法,比如BeanNameAware,BeanFactoryAware
- 4.调用BeanPostProcessor(后缀处理器)的初始化前方法
- 5.调用初始化方法
- 6.调用BeanPostProcessor(后缀处理器)的初始化后方法,(哪些类需要动态代理,也会在这儿创建)
- 7.如果当前创建的Bean是单例的,就会把Bean放入单例池中。
- 8.使用Bean
- 9.Spring容器关闭时调用DisposableBean的destory()方法
39.Spring支持的几种Bean的作用域
singleton(单例):默认,每个容器都只有一个bean的实例,该对象的生命周期与spring IOC容器一致
prototype(原型):每次注入(每次getBean())都会创建一个实例
request:每次http请求创建一个实例,但这个实例也是单例,这次会话结束前使用的都是一个实例。请求结束就会销毁
session:每个Session也只有一个bean实例,session失效,bean也会失效
application:bean被定义在ServletContext的生命周期中复用一个单例
websocket:bean被websokect的生命周期复用一个单例对象
global-session:全局作用域
40.Spring框架中的单例bean是线程安全的吗?
不是线程安全的。框架并没有对bean做多线程的封装处理
大部分的bean是无状态的,不会保存数据,在某种程度来说是安全的。
如果bean有状态,需要开发者自己保证线程的安全。最简单的方法就是把bean的作用域从单例改为原型。每次使用都创建一个新的bean就能保证线程的安全。
41.Spring 框架中都用到了哪些设计模式?
简单工厂:由一个工厂类根据传入的参数,动态的决定一个创建哪一个产品
spring中BeanFactory就是简单工厂的实现,根据传入一个唯一的标识来获取Bean对象。
工厂方法
单例模式:保证一个类只有一个实例。
适配器模式:
装饰器模式:动态的给一个对象添加一些额外的责任。类名含有wrapper或者decortor
动态代理:aop容器会为对象动态的创建一个代理对象
观察者模式:监听器使用的就是观察者模式
策略模式:spring框架资源访问Resource接口,不同类型的文件用不同的resource访问
42.Spring事务的实现方式和实现原理,隔离级别
事务实现的两种方式
- 编程式
- 申明式,就是使用注解(@Transactional)
spring事务的本质就是数据库对事务的支持。
比如说在某个方法上加上@Transactional,他会把该方法的事务的自动提交设置为false。再去执行原来的业务逻辑,没有出现异常就提交。如果有异常就回滚。
spring事务的隔离级别就是数据库的隔离级别,外加一个默认级别
- 未提交读
最低隔离级别、事务未提交前,就可被其他事务读取(会出现幻读、脏读、不可重复读); - 提交读,不可重复读(spring默认)
提交读,一个事务提交后才能被其他事务读取到(会造成幻读、不可重复读),SQL server 的默认级别; - 可重复读(mysql默认)
- 可重复读,保证多次读取同一个数据时,其值都和事务开始时候的内容是一致,禁止读取到别的事务未提交的数据(会造成幻读),MySQL 的默认级别;
- 可串行化
代价最高最可靠的隔离级别,该隔离级别能防止脏读、不可重复读、幻读。
**脏读 :**表示一个事务能够读取另一个事务中还未提交的数据。比如,某个事务尝试插入记录 A,此时该事务还未提交,然后另一个事务尝试读取到了记录 A。
**不可重复读 :**是指在一个事务内,两次查询之中的数据不一致,有可能是两次查询过程中插入了一个事务更新原有的数据
**幻读 :**指同一个事务内多次查询返回的结果集不一样。比如同一个事务 A 第一次查询时候有 n 条记录,但是第二次同等条件下查询却有 n+1 条记录,这就好像产生了幻觉。发生幻读的原因也是另外一个事务新增或者删除或者修改了第一个事务结果集里面的数据,同一个记录的数据内容被修改了,所有数据行的记录就变多或者变少了。
spring的隔离级别以spring的配置为准,如果数据库不支持,那就取决于数据库的隔离级别
42.Spring事务的传播机制
多个事务方法之间相互调用,事务怎么传播
- requirede(必须)(默认):如果当前没有事务,就创建一个事务,如果当前有事务,就加入这个事务(比如说A没有事务,要调用B,B有事务,A加入B事务。)
- supports(支持):当前存在事务就加入当前事务,当前不存在,就以非事务方式执行(A有事务,调用B,就使用A事务。如果A没有事务,那两个都不要事务)
- mandatory(强制):当前存在事务,就加入当前事务,如果不存在,则抛出异常
- requires_new:如果没有,则创建一个事务,如果有,就两个各管各的。
- not_supported:不要事务,有事务则挂起,
- nerver:不使用事务,如果当前事务存在,则抛出异常
- **nested(嵌套)**如果A没有事务,和requirede一样。如果A有事务,B就嵌套在A事务里面,A如果回滚,B也会回滚。
44.spring事务什么时候会失效
spring事务的原理是AOP,进行了切面增强。失效的原因是因为Aop失效了。
常见原因:
- 1.使用getBean调用的才是代理对象。如果发生自调用,类中使用this调用本类方法,那么这类不是代理类。
- 2.@Transactional只能使用在public方法上,否则事务失效。如果非要使用非public方法。使用aspectj代理模式。
- 3.数据库不支持事务
- 4.这个方法的类并没有放到ioc容器中。
- 5.事务异常需要向上抛出的。如果使用catch,事务不会回滚
45.什么bean的自动装配,有哪些方式
在spring中,对象无需自己查找或创建与其关联的其他对象,由容器负责把需要相互协作的对象引用赋予各个对象,使用autowire来配置自动装载模式。
在Spring框架xml配置中共有5种自动装配:
- no:默认的方式是不进行自动装配的,通过手工设置ref属性来进行装配bean。
- byName:通过bean的名称进行自动装配,如果一个bean的 property 与另一bean 的name 相同,就进行自动装配。
- byType:通过参数的数据类型进行自动装配。
- constructor:利用构造函数进行装配,并且构造函数的参数通过byType进行装配。
- autodetect:如果有默认的构造器,通过 construct的方式自动装配,否则使用 byType的方式自动装配。
@Autowired自动装配时,可以在字段,setter,构造函数上使用。
46.springboot,springmvc,spring有什么区别
spring是一个IOC容器框架,用来管理Bean,依赖控制反转和切面来简化程序
springmvc是spring对web框架的一个解决方案,提供了一个总的前端控制器servlet,用来接收请求,然后定义了一套路由规则(url到handle的映射)以及适配handle,将handle的结果使用视图解析器生成视图展示给前端
springboot是spring提供的一个快速开发工具包,能让开发者更快的开发spring+springmvc应用,简发了很多配置,基本都有默认配置。做到了开箱即用。
47.springmvc的工作流程
- 1.用户发送请求到前端控制器DispatServlet
- 2.DispatServlet收到请求后调用handlerMapping处理映射器
- 3.处理映射器找到具体的出来,生成处理器和处理拦截器一并返回给DispatServlet
- 4.DispatServlet调用handlerAdapter处理适配器
- 5.handlerAdapter调用具体处理的controller
- 6.controller执行完后返回给MondAndView
- 7.handlerAdapter将controller的执行结果MondAndView返回给DispatServlet
- 8.DispatServlet将MondAndView传给视图解析器
- 9.视图解析器解析后返回具体的view
- 10.DispatServlet根据view渲染视图
- DispatServlet响应用户
48.spring mvc的主要组件?
49.spring boot自动配置原理
selectimport扫描返回一个字符串数组,字符串数组中装的是类的全路径。spring在读到这些类的全路径时,会通过反射把这些类的全路径加载到IOC容器中。
50.如何理解spring boot 中的start
start就是定义一个start的jar包。写一个@Configuration配置类,将需要的Bean定义在里面。然后再starter包中的META-INF/spring.factories中写入该配置类,springboot会加载该配置类。
所以开发人员只需要把响应的starter包依赖进应用,就可以使用对应的功能了。
51.什么是嵌入式服务器?为什么要使用嵌入式服务器?
tomcat本来就是一个服务器,把tomcat嵌入进springboot中。
我们就不用再单独下载tomcat,也不需要再打jar包。然后放到webapp目录下再运行
只需要安装java虚拟机jvm,就可以直接在上面部署应用程序
springboot已经内置了tomcat.jar,运行main方法时会启动tomcat。
52.mybatis的优缺点
优点
- 基于sql语句编程,不会影响程序和数据库原有设计,sql写在xml中,解决了sql与代码的耦合,方便统一管理,提供xml标签,支持编写动态sql,并且可以重用。
- 与jdbc相比,减少了大量代码。不需要手动开启关闭连接。
- 很好的数据库兼容。(因为mybatis是使用jdbc连接数据库,只要是jdbc支持的数据库,mybatis都支持)
- 能与spring很好的集成
- 提供映射标签,支持对象与数据库的ORM字段映射
缺点
- sql编写的工作量比较大,尤其是字段多,表关联多,要求开发人员熟练掌握sql语言
- sql语句依赖数据库,数据库移植性差,不能随意更换数据库。
##53.mybatis与Hibernate对比
ORM:对象关系映射
将javabean与数据库之间进行一个关系的映射
将对象映射到数据库中,再将数据回映射给对象
开发速度上:
要是一些比较简单的查询,因为Hibernate中基本sql都是封装好了的,所有开发起来比较快。但是对于复杂查询来说,mybatis就会快很多
开发工作量:
两者都有相应的代码生成工具,可以生成简单基本的dao方法。对于高级查询,mybatis需要手写sql,以及ResultMap.Hibernate有相应的映射机制,开发者无需关系sql的生成与结果映射
sql优化方面
Hibernate的查询会把数据库所有的字段都查询出来,对于大数量查询消耗性能。
mybatis手写sql,可以按需求查询指定的字段。
Hibernate语句调优需要把sql打印出来。
缓存机制对比:
相同点:都实现了自己的缓存或者使用第三方缓存方案,可以创建适配器来覆盖程序默认的缓存行为
不同点:
Hibernate在使用二级缓存时出现脏数据,系统会提示报错
mybatis使用二级缓存容易造成脏数据。
53.#{}和${}的区别是什么
#{}是占位符,KaTeX parse error: Expected 'EOF', got '#' at position 17: …}是字符串替换,是拼接符 处理#̲{}时,会把sql中的#{}替…{}时,会把${}替换成变量的值,调用Statement来赋值
#{}的变量替换是在DBMS中,变量替换后,#{}对应的变量自动加上单引号
的
变
量
替
换
是
在
D
B
M
S
外
,
变
量
替
换
后
,
{}的变量替换是在DBMS外,变量替换后,
的变量替换是在DBMS外,变量替换后,{}对应的变量不会自动加上单引号
使用#{}可以有效的防止sql注入,提高系统安全性
54.简述mybatis的插件运行原理,如何编写一个插件
mybatis插件指的就是mybatis的拦截器
mybatis只支持
parameterhandlei(将数据类型进行转换,将java中的数据类型转化为sql可以识别的)
ResultsHandler(接收数据,数据库中的数据映射成结果集)
StatementHandler(映射的转换动作在此执行)
55.索引的基本原理
索引就是可以快速查找具体特定值的记录。
索引的原理:
1.把创建了索引的列进行排序
2.对排序结果生成一张倒排表
3.在倒排表内容中拼上数据地址链
4.在查询时,先拿到倒排表内容,然后取出其中的数据地址链,从而拿到相应的数据
56.mysql聚簇索引和非聚簇索引的区别
都是B+树的数据结构
- 聚簇索引:将数据存储和索引放在了一起,并且是按照一定顺序组织的。找到了索引就找到了数据。数据的物理存放顺序和索引是一致的。
- 非聚簇索引:叶子节点不存储数据,存储数据行地址,也就是说根据索引查找到了数据行的位置再去磁盘查找数据。类似书,找某个章节,需要先到目录中找,找到对应的页码再去相应的页码找数据。
优势:
1.查询聚簇索引可以直接获取数据,相比非聚簇索引需要第二次查询效率更高。
2.聚簇索引对范围查询效率比较高,因为数据是按大小排列的
3.聚簇索引适合用在排序的情况,非聚簇索引不适合。
劣势:
1.维护索引成本很高。
2.表因为使用UUID(随机ID)作为主键,数据存储分散,可能出现聚簇索引比全表扫描更慢的情况,建议使用int的auto_increment作为主键
3.如果主键索引比较大,辅助索引将变得更大,因为辅助索引就是除了主键索引建立的其他索引,相当于非聚簇索引,只不过叶子结点存储的是主键索引的主键值。
innodb一定有主键,主键一定是聚簇索引。如果不设置主键,就会使用unique索引,没有唯一索引,则会使用数据库内部的一个行隐藏的id来当做主键索引。在聚簇索引之上创建的索引称之为辅助索引,辅助索引访问数据总是需要二次查找的,非聚簇索引都是辅助索引比如说复合索引,前缀索引,唯一索引。辅助索引叶子结点存储的不再是行的物理地址,而是存放的主键值。
myism使用的是非聚簇索引,每个索引都有一棵树,B+树的叶子节点存储的行数据,指向真正的表数据。由于索引树都是相互独立的,通过辅助索引检索无需再访问主键的索引树。
如果涉及到大数据量的排序,全表扫描,Count之类的操作,myisam占优势,因为索引空间小,这些操作是在内存中完成的。
57.mysql索引的数据结构,各自优劣
索引的数据结构和存储引擎有关,在mysql中使用的比较多的所有有Hash索引,B+数索引等,InnoDB存储引擎默认实现是:B+索引。对于hash索引,底层的数据表就是哈希表,在大多数需求为单条数据查询时,可以使用哈希索引,查询性能最快。其他大部分场景,建议选择B+Tree索引。
B+树是一个平衡的多叉树,同层级的节点间有指针相互连接。在B+树的常规检索中,因为基于索引的顺序扫描时,也是利用双向指针快速移动,效率非常高。
哈希索引就是采用一定的哈希算法,把键值换算成新的哈希值,只需要一次哈希算法就可以定位相应的位置。但是前提是键值都是唯一的,不然先要找到键所在位置,然后再根据链表向后扫描,直到找到相应的数据
如果是范围查询,哈希索引就没有意义了,原来有序的键值,进过哈希算法后,有可能变得不连续了,办法再利用索引完成范围查询。
58.索引的设计原则
索引的目的是为了查询更快,占用空间更小
适合建索引
- 1.适合索引的的列是出现在where子句中的列,或者连接子句指定的列
- 2.数据比较少的表,索引效果比较差,没有必要在此列上建立索引,因为还需重新维护一张索引表
- 3.使用短索引,如果对长字符串列进行索引,一个指定一个前缀长度,这样能节省大量的索引空间,如果搜索字段超过索引前缀长度,则使用索引排除不匹配的行,然后检查其余行是否能匹配
- 4.不要建立过多的索引。索引需要更多额外的磁盘空间,并降低写操作的性能。修改表内容时,索引会进行更新甚至重构,索引列越多,这个时间就越长。所以只需要保持的需要的索引方便查询就行。
- 5.定义外键时一定要建立索引。
不适合建索引
- 1.更新频繁的字段不适合建索引
- 2.不能有效的区分数据的列不适合做索引列(比如说性别等数据种类太少的)
- 3.尽量扩展索引,不要新建索引。比如说表中已经有A的索引,现在要加(A,B)的索引,那么只需要修改原来的索引即可。
- 4.查询中很少涉及的列,重复值比较多的列不适合建立索引
- 5.对于定义为Text(现在可以使用全文索引),image和bit的数据类型的列不要建立索引。
59.mysql锁的类型有哪些
基于锁的属性分类:共享锁,排他锁
基于锁的粒度分类,行级锁(innidb),表级锁(innodb,myisam),页级锁(BDB引擎),记录锁,间隙锁,临键锁
-
共享锁(又称读锁)
当一个事务为数据加上读锁,其他事务只能对数据加读锁,而不能对数据加写锁,知道所有的读锁释放之后其他事务才能对其进行加持写锁。共享锁的特性主要是为了支持并发的读取数据,读取数据的时候不支持修改。 -
排他锁
排他锁又称为写锁:当一个事务为数据加上写锁,其他数据不能为该数据加任何锁,直到该锁释放之后,其他事务才能对该数据加锁。避免了脏数据和脏读的问题。 -
表锁
表锁是指上锁的时候锁住的是整个表,当下一个事务访问该表是,必须等前一个事务释放了锁才能进行对表的访问。
特点:粒度大,加锁简单,容易冲突。 -
行锁
行锁是指上锁的时候锁住的是表的一行数据或多行数据,当其他事务访问同一张表时,只有被锁住的记录是不能访问,其他的记录可正常访问。
特点:粒度小,加锁比表锁麻烦,不容易冲突,相比表锁支持的并发要高 -
记录锁
记录锁也是行锁的一种,只不过记录锁的范围只是表中的某一条记录,记录锁是说事务在加锁后锁住的只是表的一条记录。
加了记录锁之后数据可以避免数据在查询的时候被修改和重复的问题,也避免了在修改的事务未提交前被其他事务读取的脏读问题 -
页锁
页锁是mysql中锁定粒度介于行级锁和表级锁中间的一种锁。表锁速度快,但冲突多。行锁冲突少,但是速度慢。页锁一次锁定相邻的一组数据。
特点:开销介于表锁和行锁之间,会出现死锁,锁定粒度介于表锁和行锁之间,并发度一般。
60.mysql执行计划怎么看
执行计划就是sql的执行查询的顺序,以及如何使用索引查询,返回的结果集的行数
-
1.id:有几个select就显示几行。id的顺序是select出现的顺序增长的。id列的值越大,执行的优先级越高越先执行,id列的值相同则从上到下执行,id列的值为NULL最后执行
-
2.selectType:表示每个select子句的类型
-
3.table:表示该语句查询的是哪张表
-
4.type:优化sql的重要字段,也是判断sql性能和优化程度的重要指标。他的取值范围:
唯一性索引表示只有一条数据
-
5.possible_keys:表示Mysql在执行该sql语句的时候,可能用到的索引信息。
-
6.key:此字段是mysql当前查询真正用到的索引,是possible_keys的子集
-
7.key_len:表示查询优化器使用索引的字节数,可以评估索引是否完全被引用,是评估索引的重要指标
-
8.rows:估计该sql返回结果集需要扫描读取的行数,索引优化过后,扫描的行数越多,说明索引设置不对。说明优化空间越大
-
9.filterd:返回结果的行占需要读到的行(rows列的值)的百分比,百分比越高,说明需要查询到的数据越准确,百分比越小,说明查询到的数据量大,而结果集很少。
-
11.extra
61.事务的基本特性和隔离级别
事务的基本特性ACID
原子性:一个事务的操作要么全部成功,要么全部失败
一致性:从一个一致性的状态到另一个一致性的状态
隔离性:一个事务的修改在提交之前,另一个事务不可查看
持久性:一个事务一旦提交,就永久保存到数据库
隔离性又4个隔离级别:
读未提交:一个事务还提交,另一个事务就可以查看这个事务的数据
读已提交:一个事务必须等另一个事务提交才能查看该事务提交的数据,两次读取的结果不一致
可重复读:每次读取的结果都一样,可能出现幻读
串行:给每一行读取的数据加锁,会造成大量超时和锁竞争的问题