1、什么是面向对象,谈谈你对面向对象的理解
-
面向过程更注重事情的每一个步骤和顺序,面向对象更注重事情有哪些参与者(对象),以及参与者需要做什么
-
面向过程更加高效,而面向对象更易于复用,扩展和维护。-》降耦合,提扩展
-
面向对象三大特性-》封装、继承、多态
-
封装:明确标识出允许外部使用的所有成员函数和数据项,外部调用无需修改或者关注内部实现。private,getter,setter。再比如就是 操作数据库我们不需要关心如何建立连接,如何执行mysql 我们只需要引入mysql并且调用方法即可。
-
继承:子类继承父类方法,并且作出自己的改变/扩展,父类已有的不需要在定义
-
多态:一种事物的多种状态。基于对象所属类的不同,外部对同一个方法的调用,实际执行的逻辑不同。要有继承,方法重写,父类引用指向子类对象。无法调用子类特有的功能
-
拓展,可以说一些jvm如何继承,多态的、
- Animal a = new Dog();
- java面向对象,面向对象关注左边,调用子类复写的方法,在编译期间无法知道运行的具体方法
- 属于虚方法字节码invokeVitural方法,因此在类的方法区加载类的时候会建立虚方法表,可以引用到父类对象,避免每次执行的时候动态分派。
2、jdk,jre,jvm三者区别和联系
-
jdk:java development kit开发工具
-
jre:java runtime environment运行环境
-
jvm:java virtual machine虚拟机
jdk>jre>jvm
.java文件编译成.class文件,之后可以运行在各个系统的jvm中 一次编译,多次运行。
3、==和equals区别
-
==对比的是栈中的值,基本数据类型是变量值,引用类型是堆中内存对象的地址
-
equals:object中默认采用==比较,通常会重写
4、简述final作用,为什么局部内部类和匿名内部类只能访问局部final变量
- 类:不能被继承
- 方法:方法不能被子类覆盖,可以重载
- 变量:不能改变其值 如果修饰类变量(static final),声明或者静态代码块赋值
如果修饰成员变量,声明的时候就要有初始值,或者在代码块,构造器赋值
- 无static是成员变量,有static是类变量
如果修饰局部变量,声明可以不赋值,使用前一定赋值(如果是引用类型变量,赋值后不能再指向其他对象,但对象的值可以变化)
匿名内部类形如
public void test(final int a) {
final int b = 10;
new Thread(){
public void run(){
sout(a);
sout(b);
}
}
}
局部内部类形如
public void test(final int x) {
class InClass{
public void InTest(){
sout(x);
}
}
}
- 内部类和外部类是一个级别的,内部类不会因为定义在方法中就会随着方法的执行完毕而销毁,所以当外部类方法结束时,局部变量就会销毁了,然而内部类对象可能还存在(只有没人在引用它时,才会死亡)
- 因此可能出现内部类访问了一个不存在的变量。为解决,将局部变量复制一份作为内部类的成员变量
- 将局部变量复制给内部类的成员变量时,必须保证这两个变量是一样的,也就是说如果在内部类中修改了成员变量,方法的局部变量也要随着改变
- 因此将局部变量设置为final,初始化后,就不让再修改,保证内部类成员变量和方法的局部变量的一致性
6、String StringBuilder StringBuffer区别和使用场景
-
String是final修饰的,不可变,每次操作都会产生新的String对象
-
另外两个都是在原对象上操作的,StringBuffer线程安全,StringBuilder线程不安全
-
StringBuffer是Sychronized修饰的
-
性能:StringBuilder>StringBuffer>String
经常改变字符串时候后两个,优先StringBuilder,多线程共享变量使用StringBuffer
-
含1.8前用的是char数组,1.9用的是byte数组,存储的大部分都是拉丁文,1byte就能实现,因此换为byte数组,但是如果是汉字又没办法表示了,因此补上一个字符编码集的标识。因此StringBuffer、StringBuilder和固有的一些字符也做了相应的修改。
-
String 含1.6前在永久代的字符串常量池,后在堆中,便于调优,仅仅调整堆空间大小,并且方法区收集垃圾过于苛刻,而使用的字符串常量池挺多的,便于GC,同时堆空间大
-
字符串拼接,如果是new String()拼接底层调用了StringBuilder进行append操作,创建了很多对象,并且最终结果并不在字符串常量池中。调用intern方法含1.6以前会在字符串常量池中创建一个该字符串,String指向该字符串,但是之后只会创建指向你堆中该String的指针。
-
扩容机制:StringBuilder和StringBuffer默认长度16,默认扩容2 * n + 2,如果加入内容后长度比这个还大,以新的长度作为扩容后长度
7、重载和重写的区别
-
重载:发生在同一个类中,方法名相同,参数、个数、顺序不同,方法返回值和访问修饰符可以不同(但是返回值不同在编译就会报错)
-
重写:子类重写父类方法,方法名,参数列表必须向同,返回值范围小于等于父类,抛出异常范围小于等于父类,访问修饰符大于等于父类
父类方法private修饰则不能重写,认为是新方法。所有的private方法默认是final的
8、接口和抽象类的区别
-
抽象类可以存在普通成员函数,接口中只能有public abstract方法
-
抽象类中的成员变量是各种类型的,接口中成员变量只能public static final类型,因为接口是高度抽象的,是规范,确保变量只有一个,并且大家公用不能更改。
-
抽象类单继承,接口可以实现多个
以上为基础三点
-
接口的设计目的是对类的行为进行约束,提供一种机制,可以强制要求不同类有相同行为,只约束了行为的有无,对如何实现没有限制
-
抽象类的设计目的是代码复用,当不同类具有某些相同的行为(行为集合A),且其中一部分行为的实现方法一致时(A的非真子集B),可以让这些类派生于一个抽象类
-
在这个抽象类中实现了B,避免让所有子类实现B,达到了代码复用,而A-B的部分,留给各个子类实现,因为A-B没有实现,所以抽象类无法实例化
-
先有子类再有父类,把子类共性抽出来成父类
-
抽象类是对类本质的抽象,表达的是is a的关系如 bmw is a car,抽象类包含并实现子类的通用特性,将子类存在差异化的特性进行抽象,交由子类实现。
-
接口是对行为的抽象,表达的然是like a的关系,bird like a aricraft,其本质是bird,接口的核心是定义行为,即实现类可以做什么,至于实现主体是谁,如何实现无所谓
-
使用场景,关注事物本质,用抽象类,关注操作,用接口
-
抽象类功能远超接口,但定义抽象类代价高
9、list和set的区别
-
list:有序,按照对象进入顺序保存对象,可重复,允许多个null对象,可以使用iterator取出所有元素再遍历,也可使用get(int index)获取指定下标的元素
-
arrayList:基于动态数组,连续内存存储,适合下标访问,扩容机制:因为长度固定,超出需要新建数组,将旧的拷贝到新书组,如果不是尾插涉及到元素移动,使用尾插并指定初始容量可以极大提升性能甚至超过LinkedList
-
LinkedList:基于链表,可以存储在分散的内存总,适合做数据插入和删除操作,不适合查询,需要逐个遍历。
-
变量里LinkedList必须使用iterator,不能用for,因为for每次get(i)取得某医院素都需要对List重新遍历,性能消耗极大。
-
不要使用indexOf返回索引,利用其遍历,使用IndexOf会对list遍历,结果位空时会遍历整个列表。
-
-
-
set:无序,不可重复,最多有一个null,取元素时只能用iterator接口取得所有元素
10、hashCode和equals
- hashcode作用是获取哈希码,返回int整数,这个hash码作用是确定对象在hash表中索引位置。java中任何类都有hashCode()函数,通过键值对可以通过键快速检索对应的值
为什么要有hashCode
以hashSet如何检查重复的例子来说明为什么要有hashCode
对象加入hashset时,hashset先计算对象的hashcode来判断对象加入的位置是否有值,如果没有,hashset会假设对象没有重复出现,如果有毁掉用equals来检查两个对象是否相同,如果相投,hashset不让其加入,不同则会在链表中链接,减少equals次数,提高执行速度。
两对象相等,hashcode相同
两对象相等,equals返回true
两对象hashcode相同,对象不一定相等
因此equals覆盖过,hashcode也必须覆盖
如果没重写hashcode,则class的两个对象无论如何也不会相等,即使两个对象数据相同
11、ArrayList和LinkedList区别
-
arrayList:基于动态数组,连续内存存储,适合下标访问,扩容机制:因为长度固定,超出需要新建数组,将旧的拷贝到新书组,如果不是尾插涉及到元素移动,使用尾插并指定初始容量可以极大提升性能甚至超过LinkedList
-
LinkedList:基于链表,可以存储在分散的内存总,适合做数据插入和删除操作,不适合查询,需要逐个遍历。
-
变量里LinkedList必须使用iterator,不能用for,因为for每次get(i)取得某一个元素都需要对List重新遍历,性能消耗极大。
-
不要使用indexOf返回索引,利用其遍历,使用IndexOf会对list遍历,结果位空时会遍历整个列表。
12、HashMap和HashTable有什么区别,底层是什么
HashMap没有使用sychronized线程不安全,hashTable反之
HashMap允许key value位null hashTable不允许
- HashMap底层实现原理
- 1.7数组+链表。头插法,多线程出现循环链,死循环。Entry[16]
- 1.8数组+链表+红黑树。尾插法,多线程丢数据。Node[],首次put才会创建长度16的数组
- todo:负载因子
- Node包含了hash值,这个值时key.hashcode再次hash得到的,高16位和低16位置异或,降低hash碰撞,更好的散列化
- hashMap扩容策略
- 1.7直接扩容两倍
- 1.8数组长度小于64,直接两倍扩容,容量 * 加载因子 = 扩容阈值 16 * 0.75 = 12 存入12个元素即扩容到32,数组长度>64当前位置链表长度>8转为红黑树。当红黑树元素小于6还原链表。
13、ConcurrentHashMap原理
线程安全版本hashMap
-
jdk7:reentrantLock+segment+HashEntry,一个segment中包含了一个table数组,每个位置包含一个HashEntry链表。
- 元素查询:二次hash,第一次定位到segment,第二次定位到table链表头部
- 锁:segment分段锁,segment继承了reentrantlock,锁定操作的segment,其他的不受影响,并发度为segment个数,可以通过构造函数指定,数组扩容不会影响其他的segment,get方法无需加锁,volatile保证。
-
jdk8:(数组+链表)sychronized+cas+node+红黑树,node的val和next都用volatile修饰,保证可见性,查找,替换,赋值操作都使用cas。
- 锁:锁链表的head节点,不影响其他元素的读写,锁的粒度更细,效率更高,扩容时,阻塞所有的读写操作,并发扩容。头部多一个treebin节点,syc锁住链表头部。
- 读操作无锁:node的val和next使用volatile修饰,时刻更新,数组用volatile修饰,保证扩容时被读线程感知
14、ioc容器
15、什么是字节嘛,采用字节码的好处是什么
jvm理解的代码是字节码.class文件,跨平台,通过字节码解决了执行效率低的问题(编译和运行拆开了),保留了可移植特点,一次编译,多次运行
16、java类加载器 classloader
bootstrap:ext父类加载器,负责加载java home/lib下的jar和class,引导类加载器
ext:app父类加载器,负责加载java home/lib/ext下的jar和class,扩展加载器
app:自定义加载器的父类,负责加载classpath 系统加载器,系统类加载器
17、双亲委派模型
加载一个类的时候先向上委派,查找缓存,是否加载了该类,有则返回(加载过的不用再加载),没有继续向上,直到bootstrap都没加载过后,向下查找,查找加载路径,有则加载返回,没有则继续向下查找。
主要是为了安全,避免自己写的类替换java核心类,如String
避免类重复加载,jvm中区分不同类,不仅仅根据类名,相同的class文件被不同的classloader加载就是2个类
18、java中的异常体系
所有的异常来自顶级父类Throwable
旗下有两个子类Exception和Error
Error是程序无法处理的错误,一旦出现这个错误,程序被迫停止运行
Exception不会让程序停止,又分为了RuntimeException运行时异常和CheckedException检查异常
RunTime常常发生在程序运行过程中,会导致当前线程执行失败,checked常常发生在程序编译过程中,导致程序编译不通过
19、gc如何判断对象可以被回收
引用计数法:每个对象有一个引用计数属性,新增一个引用计数增加1,引用释放时计数-1,计数位0可以回收,java没有使用,比如a引用b,b引用a,他们都不再使用,但是相互引用,计数器为1,永远无法被回收,但是效率非常高
可达性分析法:从GC Roots开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GCRoots没有任何引用链相连时,证明对象不可用,判断为回收对象
GCRoot的对象有:
-
虚拟机栈(栈帧中的本地变量表)中的引用的对象(比如new usr() 其他对象被usr引用,无法被回收,称usr就是gcRoots,使用完被弹出)
-
方法区中静态属性引用的对象static
-
方法区中常量引用的对象static final
-
本地方法栈JNI(即一般说的Native方法)引用的对象
可达性算法的不可达对象并不是立刻死亡的,对象拥有一次自我拯救的机会:
第一次可达性分析发现没有与GCRoots相连接的引用链
第二次是在由虚拟机自动建立的finalizer队列中判断是否要执行finalize()方法
-
当对象变成不可达时,GC会判断该对象是否覆盖了finalize方法,没覆盖则直接回收,否则,若对象未执行finalize方法,将其放入F-Queue队列
-
执行该队列对象的finalize方法,执行完毕后,判断是否可达,不可达回收,可达复活
-
每个对象只能触发一次finalize方法,运行代价高,不确定性大,不推荐使用
20、线程的生命周期和状态
五种状态:创建,就绪,运行,阻塞,死亡
阻塞的三种:
- 等待阻塞:运行线程执行wait方法,释放线程资源,jvm把线程放入等待池,释放锁,不能自动唤醒,必须依靠notify,wait是object的方法
- 同步阻塞:运行的线程获取对象的同步锁,若同步锁被别的线程占用,jvm会把它放入锁池
- 其他阻塞:运行的线程执行sleep或者join方法,或者io请求,会成阻塞状态,当sleep,join等超时,io处理完毕,线程重回就绪状态,是thread的方法
新建状态:新创建了一个对象
就绪状态:线程对象创建后,其他线程调用了该对象的start方法,该状态的线程位于可运行线程池中,变得可运行,等待cpu使用权
运行状态:就绪状态线程获取了cpu使用权,执行代码
阻塞状态:因为某种原因放弃cpu使用权,停止运行,直到线程重新进入就绪状态
死亡状态:程序执行完了或者异常推出了run方法,结束生命周期
21、sleep wait join yield区别
锁池:竞争同步锁的线程都会放在锁池中,比如当前对象的锁已经被某一个线程得到,其他线程需要在这个锁池等待,前面线程释放同步锁后锁池线程竞争同步锁,当某个线程得到后进入就绪队列等待cpu资源分配
等待池:调用wait方法后,线程放入等待池中,等待池不会竞争同步锁,只有调用了notify后等待池线程才会竞争锁,notify是随机从等待池中选一个放到锁池,notifyall是将等待池所有线程放到锁池中
-
sleep是thread的静态本地方法,wait是object的本地方法
-
sleep不会释放锁,wait释放锁,加入等待队列中
-
sleep方法不依赖同步器sychronized但是wait依赖
-
sleep不需要被唤醒,wait需要
-
sleep一般使用域当前线程休眠,或者论循进行操作,wait多用于多线程之间的通信
-
sleep让出cpu执行时间,强制上下文切换,wait不一定,wait后还是有可能重新竞争到锁执行的,提供了带时间的wait方法(wait notify)
注:sleep实则是把cpu运行资格和执行权释放,不再运行线程,当定时时间结束在取回cpu资源,即带着锁进入了冻结状态,其他线程无法获得这个锁,如果睡眠期间其他线程调用了这个线程interrupt方法,那么这个线程会抛出interruptexception异常返回,这点和wait一样。
yield后线程进入就绪状态,释放cpu执行权,保留cpu执行资格,有可能cpu下次线程调度还会让这个线程获取到执行权继续执行
join后线程进入阻塞状态,b线程调用a的join,线程b进入阻塞队列,直到线程a结束或者中断
22、对线程安全的理解
应该叫内存安全,堆是共享内存,可被所有线程访问,多个线程访问一个对象时,如果不用进行额外的同步控制或者协调操作,调用这个对象的行为都可以获得正确的结果,这个线程就是安全的
堆是进程和线程的共有空间,分全局堆和局部堆。全局堆是所有没分配的空间,局部对就是用户分配的空间,堆在操作系统对进程初始化时分配,运行过程中也可以向系统要额外的堆,但是用完了要还给操作系统,不然造成内存泄漏,对象和数组放在此处
栈是每个线程独有的,保存运行状态和局部变量,栈在线程开始的时候初始化,每个线程的栈独立,栈是安全的,切换线程会自动切换栈
23、Thread和runnable区别
Thread和runnable是继承关系,没有可比性,两者都会newThread,然后执行run方法,没有复杂操作选择继承Thread,简单执行一个任务,实现runnable
继承Thread只能单继承,runnable是接口可以多实现,更加灵活,同时runnable可以数据共享,如Mythread mt = new Mythread() 之后new Thread(mt)多个,其中mt中的属性共用的,便于多个线程操作一个资源。
callable:有返回值,可以抛异常。FutureTask实现runnable接口,同时与callable接口有关联(new FutureTask<>(new MyThread()))。new Thread(runnable),runnable中传入实现类futureTask即可。
24、说说你对守护线程的理解
守护线程:为所有非守护线程提供服务的线程,任何一个守护线程都是整个jvm中所有非守护线程(用户线程)的保姆 用户线程执行完毕,守护线程就中断了,因此无法控制终止,不要将io file等重要操作逻辑给他分配。
例:GC垃圾回收,当程序中不再有任何运行的Thread,程序不再产生垃圾,即gc是jvm仅剩的线程时,GC自动离开。
应用场景:为其他线程提供服务,任何情况下,程序结束时,这个线程必须立即正常关闭,就可以作为守护线程 守护线程中创建线程也是守护线程。java自带的多线程的框架如executorservice会将守护线程自动转为用户线程 所以如果时候后台线程就不能使用java自带的线程池
25、ThreadLocal的原理和适合用场景
每个Thread对象都有一个ThreadLocalMap类型的成员变量threadLocals,它储存本线程中所有ThreadLocal对象及其对应的值
创建一个ThreadLocal,在两个不同线程里分别设置值,可以不一样,并且同时存在,因为里面有个ThreadLocalMap,key位Thread,value是值
即ThreadLocalMap中有个Entry,key是ThreadLocal,value是存的值
static final ThreadLocal<Object> tl = new ThreadLocal<>();
线程中调用tl.get/set/remove相当于从当前Thread对象中的ThreadLocalMap通过key tl获取值。
场景:
- 对象跨层传递,使用ThreadLocal避免多次传递 如service,dao等不用传参,只需要用ThreadLocal获取就行
- 线程之间的数据隔离
- 事务操作,用于存储线程事务信息
- 数据库链接,session会话管理
26、ThreadLocal内存泄露原因,如何避免
内存泄漏为不再被使用的对象的内存不能被回收,就是内存泄漏,长期导致oom内存溢出
强引用:例如new,强引用的对象不会被垃圾回收器回收,内存不足,java宁愿抛出oom错误。
如果想取消强引用和某个对象的关联,可以赋值null,这样jvm会在合适的时间回收
弱引用:jvm回收时,无论内存是否充足,都会回收被弱引用关联的对象,在java中,用java.lang.ref.WeakReference来标识。在缓存中使用弱引用较多
ThreadLocal的实现是Thread维护一个ThreadLocalMap,key为使用弱引用的ThreadLocal实例,value是线程变量的副本
ThreadLocal的key是弱引用,会被回收,但是value是强引用不会被回收,所以会产生内存泄漏
为什么要使用弱引用(key):如果使用强引用,回收ThreadLocal时,ThreadLocalMap还有个ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,Entry导致内存泄漏。反之使用弱引用,当key是null时,下一次使用ThreadLocalMap调用set,get,remove方法会清除value值。
- 内存泄漏本质:ThreadLocalMap和Thread生命周期一样长,没有手动删除对应key就会导致内存泄漏。
使用ThreadLocal正确方法:
- 每次用好ThreadLocal调用他的remove方法清除数据
- 将ThreadLocal变量定义成private static,这样就一直存在ThreadLocal的强引用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除。不用每次都实例化。
27、并发,并行,串行的区别
串行在时间上不能重叠,前一个任务没完成,下一个任务要等着
并行在时间上是重叠的,两个任务同一时刻互不干扰同时运行
并发允许两个任务彼此干扰,同一时间点只有一个任务运行,交替执行
28、并发的三大特性
原子性、可见性、有序性。
29、为什么使用线程池?解释下线程池参数?
- 降低资源消耗,提高利用率
- 提高响应速度(不用先创建线程)
- 提升可管理性,统一分配调度
| 参数 | 意义 |
|---|---|
| corePoolSize | 线程池常驻核心线程数 |
| maximumPoolSize | 能够容纳的最大线程数 |
| keepAliveTime | 空闲线程存活时间 |
| unit | 存活时间单位 |
| workQueue | 存放提交但未执行任务的队列 |
| threadFactory | 创建线程的工厂类 |
| handler | 等待队列满后的拒绝策略 |
30、简述线程池处理流程
graph TD
线程池执行任务 --> conditionA{核心线程数是否已满}
conditionA -- 已满 -->conditionB{任务队列是否已满}
conditionA -- 未满 -->创建核心线程执行
conditionB -- 已满 --> conditionC{最大线程数是否达到}
conditionB -- 未满 -->将任务放入到队列中
conditionC -- 已达到 --> 根据拒绝策略处理
conditionC -- 未达到 --> 创建临时线程执行
31、线程池阻塞队列的作用,为什么先添加队列,而不是先创建最大线程。
- 一般队列只能保证有限长度,超出缓冲长度,就报错,阻塞队列只是阻塞,可以保留继续想要入队的任务。不用手动控制阻塞与否。
- 阻塞队列可以保证任务队列中没有任务时阻塞获取任务的线程,使线程进入wait状态,释放cpu资源
- 阻塞队列自带阻塞和唤醒功能,不需要额外处理,无任务执行时,线程池利用阻塞队列的take方法挂起,维持核心现成的存活,不一直占用cpu资源
- 创建新线程,要获取全局锁,其他线程就要阻塞,影响整体效率。
32、线程池中复用原理
线程池将线程和任务解耦,线程是线程,任务是任务,摆脱了之前通过Thread创建线程时一个线程必须一个任务的限制。
线程池中,同一个线程可以从阻塞队列中不断获取新任务执行,核心原理在于线程池对Thread进行了封装,并不是每次执行任务都会调用Thread.start来创建新县城 ,而是让线程执行一个循环任务,不停检查是否有任务需要执行,有就执行,调用任务的run方法,将run方法当成普通方法执行。这种方式是达成了使用固定线程将所有任务的run泛该法串联起来。线程调用任务的run方法,而不是调用线程的start方法。
33、Spring是什么
轻量级的J2EE框架,是容器框架,装javabean,中间层框架可以起一个连接作用,让开发更快更简洁。
Spring是一个轻量级的控制反转ioc和面向切面aop的容器框架
-
大小和开销而言,spring都是轻量级
-
ioc达到降耦合
-
提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑和系统级服务进行内聚性的开发
-
包含并管理应用对象bean的配置和生命周期
-
将简单的组件配置组合成复杂的应用。
34、谈谈你对AOP的理解
系统的不同组件专注于自身核心业务,其他功能如打印日志,安全,事务等交叉逻辑业务,封装成一个切面,注入到目标对象(具体业务逻辑)中使用。AOP可以对某个对象或某些功能增强,可以在执行某方法前后做一些额外的事。
相比于OOP(允许定义从上到下的关系,但是不支持从左到右的关系(如日志等)),AOP可以避免大量代码的重复,利于各个模块的复用。
35、谈谈你对IOC的理解
从容器概念,控制反转,依赖注入回答。
ioc容器:ioc实际是map(k,v),存放各种对象(如,xml里配置bean节点,@repository,@service,@controller,@component),项目启动会读取bean节点,跟去全限定类名使用反射创建对象放到map中。我们在代码里使用对象时,通过DI注入(autowired, resource,etc),会读取xml节点ref属性根据id注入。
控制反转: 没有IOC前,A依赖B,对象A在初始化时,需要主动创建对象B或者使用已经创建的B,控制权在自己手中
引入IOC后,A与B失去了直接联系,相当于A依赖IOC容器,当A需要B时,IOC主动创建B,之后注入到A所需的位置。(IOC也叫粘合剂,万能胶)
对象A获得依赖对象B的过程,从主动变成被动,控制权颠倒,因此叫控制反转。
依赖注入: 获得依赖对象的过程反转了,以前是A创建B,现在是创建B注入到A中。
36、BeanFactory和ApplicationContext有什么区别
ApplicationContext是BeanFactory的子接口
ApplicationContext提供了更完整的功能:
1、继承MessageSource,因此支持国际化
2、统一的资源文件访问方式
3、提供在监听器中注册Bean的事件
4、同时加载多个配置文件
5、载入多个(有继承关系)上下文,使得每一个上下文都专注于一个特定的层次,比如应用层的web层。 。。。。。
37、简述Spring bean的生命周期
38、Spring支持的bean作用域
39、Spring框架中的单例Bean是线程安全的吗
40、Spring框架中使用了哪些设计模式
41、Spring事务的实现方式原理以及隔离
42、Spring事务的传播机制
43、事务什么时候失效
44、什么是bean的自动装配,有哪些方式
45、spring springmvc,springboot的
46、springmvc工作流程
47、springmvc的九大组件
48、springboot自动配置原理
49、如何理解springboot的starter
50、什么是嵌入式服务器,为什么使用嵌入式服务器
51、mybatis的优缺点
优点:
- 基于sql语句编成,灵活,不会对应用程序或者数据库现有设计造成任何影响,sql写xml中,解除sql与程序代码的耦合,便于统一管理,提供xml标签,支持编写动态sql语句,可重用。
- 和jdbc相比,减少了50%以上的代码两,消除了其冗余代码,不需要手动开关链接
- 很好的与各种数据库兼容(因为mybatis使用jdbc来链接数据库,所以只需要jdbc支持的数据库mybatis都支持)
- 能够与spring很好的集成
- 提供映射标签,支持对象与数据库的ORM字段关系映射;提供对象关系映射标签,支持对象关系组件维护
缺点:
- sql语句编写工作量打,尤其是字段多,关联表多
- sql语句依赖数据库,数据库移植性差,不能随意更换数据库
52、 mybatis和hibernate对比
53、#{}和${}区别
54、mybatis插件运行原理及开发流程
55、索引的基本原理
索引用来快速搜寻具有特定值的记录,没索引则需要遍历整张表
索引的原理:把无序的数据变为有序的查询
- 把创建了索引的列的内容排序
- 对排序结果生成倒排表
- 在倒排表内容尚拼上数据的地址链
- 在查询的时候先拿到倒排表的内容,再取出数据地址链,从而拿到具体数据。
56、 聚簇索引和非聚簇索引区别
聚簇索引:数据存储和索引放到了一块,并且按照一定顺序组织的,找到索引就找到了数据,数据的物理存放顺序和索引顺序一致,即索引相邻,对应数据也在相邻物理地址存放
非聚簇索引:叶子节点不存数据,存储数据行地址,也就是说根据所因查询到数据行的位置,再取磁盘找到数据。相当于书的目录。
聚簇索引优势
- 可以直接获得数据,不需要二次查询(回表),效率高
- 范围查询效率高,因为数据按照大小排列
- 适合用在排序的场合
聚簇索引劣势
- 维护索引昂贵,插入或者主键更新导致分页(页分裂,降低数据存储率)。建议在大量插入新行后,选在负载较低的时间段通过OPTIMIZE TABLE优化表,因为被一懂得行数聚可能造成碎片,使用独享表空间可以弱化碎片
- 使用UUID作为主键,使数据存储稀疏,这样聚簇索引会比扫描全表更慢,所以建议使用自增的int作为主键
- 如果主键比较大,辅助索引会变得更大,因为辅助索引的叶子存储的主键值,过长的主键值,会导致非叶子节点占用更多的物理空间
innodb因定有主键,主键是聚簇索引,不受动设置则会使用unique索引,如果没有则会使用数据库内一个行的隐藏id当做主键索引。
57、mysql索引的数据结构,各自优劣
哈希表、有序数组、搜索树、b+tree
58、索引的设计原则
- 适合索引的列出现在where子句中,或者连接子句指定的列。
- 基数较小的类没必要创建索引,效果差
- 使用短索引,如果对长字符串索引,应该指定前缀长度,可以节省大量空间,如果搜索词超过前缀长度,则使用索引排除不匹配的行,然后检查其余行是否可能匹配。
- 不要过度使用索引,会占用额外磁盘空间,降低读写性能,修改表内容时,索引可能重构,索引列越多时间越久,因此保持索引有利于查询即可。
- 定义有外键的数据列一定要建立索引
- 如果不查询整行数据,建立联合索引,二级索引产生回表。
- 主键设定应当为自增主键,否则B+树造成数据分页,降低每页的使用率,造成不必要的硬盘读写,降低效率
三次握手
- 第一次:客户端给服务器发送syn报文
- 第二次:服务器收到syn后应答syn+ack报文
- 第三次:客户端收到syn+ack后回应一个ack
- 服务端收到ack后,建立完成
为什么需要三次?
- 第一次确认客户端发送能力和服务端接收能力
- 第二次确认服务端发送能力和客户端接受能力
- 第三次服务端收到了客户端的包,确认能力。
四次挥手
初始双方处于established状态,假如是客户端先发起请求
- 第一次:客户端发送fin报文,报文指定序列号,客户端处于FIN_WAIT1状态
- 第二次:服务端接到fin报文,发送ack报文,序列号+1,表示接到客户端报文,处于CLOSE_WATI状态
- 第三次:服务端也想断开链接了,给客户端发送FIN报文指定序列号,处于LAST_ACK状态。
- 第四次:客户端收到fin,发送ack应答,序列号+1,处于TIME_WAIT2状态,过一阵确保服务端收到自己的ACK报文后进入CLOSED状态
- 服务端收到ACK后关闭连接,处于CLOSED状态。
为什么客户端发送 ACK 之后不直接关闭,而是要等一阵子才关闭?
- 确保服务器是否已经收到了我们的 ACK 报文,如果没有收到的话,服务器会重新发 FIN 报文给客户端,客户端再次收到 ACK 报文之后,就知道之前的 ACK 报文丢失了,然后再次发送 ACK 报文。至于 TIME_WAIT 持续的时间至少是一个报文的来回时间。一般会设置一个计时,如果过了这个计时没有再次收到 FIN 报文,则代表对方成功就是 ACK 报文,此时处于 CLOSED 状态。
comparable和Comparator
- comparable写在类中,实现compareTo方法。
- comparator单独写一个比较类,继承comparator,实现compare方法,传入两个对象比较。
throw和throws区别
- throw:运行时异常,抛出对象实例,一个具体的异常类型,手动抛出给上级。--处理异常的过程
- throws:抛出可能产生的所有异常,在方法声明后添加,多个用逗号隔开,不需要处理,返回给调用者异常名,但是返回的异常不一定发生了--生成异常的过程
tcp滑动窗口
tcp一次传输数据数量。大小动态调整
- ack丢失无妨,后续ack也会通知对方自己收到了什么
- 数据包丢失,发送端会一直收到我要收到xxx(序号)的ack,连续收到三次后发送端会重新发送数据包
- 如果发送过快,接收端缓冲区填满,接收端动态调整大小,通过ack通知发送端,开始变小,如果缓冲区满了会调整为0,发送发不在发送数据,但是定期发送窗口探测数据段,接收新的窗口大小。
tcp拥塞控制
如果网络状况一般,上来使用滑动窗口发送大量数据,雪上加霜。因此tcp引入慢启动,先发少量数据探路,摸清网络情况在根据情况调整传输速度。
- 拥塞窗口:最开始的时候定义拥塞窗口大小为1,没接收到一个ack应答,拥塞窗口+1,发送数据包的时候使用拥塞窗口大小和接收端主机ack的窗口大小比较,取较小值。
- 实际上这种增长速度很快,指数级别,初始慢,增长快,为了不增长那么快,引入慢启动阈值,超过这个大小不再按照指数增长,按照线性增长
- tcp开始启动,慢启动阈值等于窗口最大值,网络阻塞时,慢启动阈值变为一半,拥塞窗口重置为1。
- 少量丢包触发超时重传,大量丢包认定为网络阻塞。
延迟应答
- 接收端立刻返回ack,说明窗口小
- 如果接收缓冲区为1m,一次收到了0.5m数据,立刻应答则返回滑动窗口值为500k,但是可能处理的很快,这500k在10ms内就已经被消费掉了,接收端没有达到自己极限,窗口再大点也能处理,可以稍后在发送ack,同时设置窗口大一点。
try catch finall执行顺序
- try正常执行:try->finally->finally外的
- try出现异常:try异常前的->catch中没有throw和return则执行完catch,有则执行throw和return前面的->finally,如果有return或者throw直接执行完跳出该线程->执行catch中没执行完的throw和return后返回
String[] args
主函数中的传参,run->edit configuration中可以设置,默认为空,长度为0