java总结2024
java篇:
一、设计原则:
六大设计原则:
1、单一职责原则:
定义:即一个类或者模块只完成一个职责
好处:使用更灵活,效率更高
例子:例如篮球队,足球队,每个位置的人只需要负责自己位置的职责。
2、里氏替换原则:
定义:所有使用父类的地方可以使用子类的对象,子类可以扩展父类的功能,但是不是替换父类的功能,如果需求替换父类的功能,建议多用组合,少用继承
好处:(1)里氏替换原则就是针对继承而言的,如果继承是为了实现代码的重用,也就是为了共享方法,那么共享的父类的方法就应该保持不变,子类只能通过添加新方法来扩展功能。
(2)如果继承是为了多态,而多态的前提就是子类覆盖并重新定义父类的方法,我们应该将父类定义为抽象类,并定义抽象方法
3、迪米特原则:
定义:一个类对与其耦合或调用类所了解的越少越好
4、依赖倒置原则:
定义:下层模块引入上层模块的依赖,改变原有的自上而下的依赖方向
例子:基于上层提供的N个特征,提供符合特征的底层类,基于接口开发
5、开闭原则:
定义:类、方法、模块应该对扩展开放,对修改关闭,即添加一个功能,应该是在已有的代码基础上进行扩展,而不是修改
6、接口隔离原则:
定义:建立单一接口,不要建立臃肿庞大的接口,接口尽量细化,,同时接口的方法要尽量少。
好处:(1)接口要尽量小,不要违反单一职责原则,要适度
(2)接口要高内聚,提高模块、接口类的处理能力,减少对外交互。
(3) 定制服务,通过对高质量接口的组装,实现服务的定制化。
二、23种设计模式:(按面试出现频率排序)
1、单例模式:
定义:一个对象只能被实例化一次,并提供一个全局访问点
单例模式五种方式:
(1)饿汉式:
使用效率高,线程安全,但是不能延时加载。
因为还没有调用getInstance的时候,对象已经被实例化了。
public class Singleton1{
private Singleton1();
private static final Singleton1 single = new Singleton1();//此时已经实例化
public static Singleton1 getInstance(){
return single;
}
}
(2)懒汉式:
使用效率不高,线程安全,可以延时加载
因为懒汉式是在调用getInstance时,才实例化对象,但是如果多线程的情况下,当第一个线程抢到锁之后,对single对象进行校验,校验为true,实例化对象,释放锁,由下一个线程获得锁,需要重新进行校验,此刻single已经实例化过了,不在是null了,校验为false,返回single对象。由于在多线程的情况下,每个线程都要获得锁,释放锁,校验是否实例化,降低了使用效率。
public class Singleton2{
private Singleton2();
private static Singleton2 single = null; // 此时对象并没有实例化
public static synchronized Singleton2 getInstance(){
if(single == null){
single = new Singleton2();
}
return single;
}
}
(3)双重校验:
使用效率高,线程安全,可以延时加载
public class Singleton3{
private Singleton3();
private static Singleton3 single = null;
public static Singleton3 getInstance() {
if(single == null) {// 1
synchhronized(Singleton3.class){
if(single == null) {// 2
single = new Singleton3();
}
}
}
return single;
}
}
(4)静态内部类:
线程安全,使用效率高,延时加载
这种的好处,是解决了双重校验代码中的繁杂的if,private 静态内部类对外的getInstance方法返回静态类的instance对象,只有第一次访问的时候,才会被创建,类的初始化本身就是执行类的构造器的<clinit>方法,该方法是由javac编译器生成的,他是由一个类里面所有静态成员的赋值语句,和静态代码块组成的,jvm会保证一个类的clinit方法在多线程的环境下也可以正确的加锁同步,只有一个线程会执行clinit方法,在该线程执行的时候,其他线程进入阻塞等待状态,直到这个线程执行完,其他线程才被唤醒,但不会再进入clinit方法,也就是说一个加载器下,一个类只会被初始化一次。
public class Singleton4{
private static class lazyLoader{
private static final Singleton4 INSTANCE = new Singleton4
}
private Singleton(){};
public static final Singleton4 getInstance(){
return lazyLoader.INSTANCE;
}
}
(5)枚举类:
枚举本身是单例的,使用较少。一般用来定义内存字典的较多。
2、代理模式:
定义:代理模式为另一个对象提供一个替身或占位符,以控制对这个对象的访问,springAop采用的是动态代理
解决问题:当我们想要对一个业务进行横切性增强时,例如:增加请求与响应的日志、增加权限校验、增加远程请求对象封装等,可以采用代码模式实现,无需改变原有的类。
3、适配器模式:
定义:将一个类的接口,转换为客户期望的另一个接口,适配器让原本不兼容的两个类可以合作无间
4、模板模式:
定义:在一个方法中定义一个算法骨架,将一些步骤延迟到子类中,模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中某些步骤
5、中介者模式:
定义:使用中介者模式来集中相关对象之间复杂沟通和控制方法
6、建造者模式/生成器模式:
定义:使用生成器模式,可以封装一个产品的构造过程,并允许按步骤构造产品。
优点:将一个复杂的对象的创建过程封装起来,允许对象通过多个步骤来创建,并可以改变步骤。向客户隐藏产品内部表现,产品的实现可以替换,因为客户看到的只是一个抽象接口。
例子:可以类比购买车模型,可以直接购买成品,如果对内部构造比较感兴趣,可以购买乐高自行组装。
7、装饰模式:
定义:动态的将责任附加到对象上,若要扩展功能,装饰者提供了比继承更弹性的替代方案,
例如:煎饼,化妆等。
8、门面模式:
定义:提供了一个统一接口,用来访问子系统中的一群接口,外观定义了一个高层接口,让系统更容易使用。
9:命令模式:
定义:将“请求”封装成命令对象,以便使用不同的请求,队列,日志来参数化其他对象。
上游的命令不关心下游的命令谁去做,下游去做的也不关心上游的命令是谁发出的。上下游解耦。
10:职责链模式:
定义:使多个对现象都有机会处理请求,从而避免了请求的发送者和接收者的耦合关系,将这些对象连成一个链条,并沿着这条链传递请求,直到有对象处理它为止。
11:观察者模式:
定义:定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它所依赖者都会收到通知并自动更新。
12:访问者模式:
定义:表示一个作用于某个对象结构中的各个元素的操作,它使你在不改变各元素的类额前提下定义作用于这些元素的新操作。
三、反射:
1、什么是反射:
反射是在运行状态中,对于任意一个类,都可以获取该类的方法和属性;对于任意一个对象,都能调用其方法和属性,这种动态获取信息和动态调用对象方法的功能,称为java语言的反射机制
2、在什么地方用到反射:
在jdbc中,利用反射动态加载数据库驱动程序
开发中比较常见的入参的对象和我们要存储的对象属性基本相同,这种情况下可以使用反射,将对象添加进去,避免了代码中大量的get和set。
3、如何用反射将一个对象里属性相同的值,赋值给另外一个对象:
①使用反射实现,BeanUtil工具
Public class BeanUtil{
public static void convert(Object orginObj, Object targetObj) throws Throwable{
Class orginClazz = orginObj.getClass();
Class targetClazz = targetObj.getClass();
Map<String, Field> fieldCache = new HashMap<>();
Field[] orginFields = orginClazz.getDeclaredFields();
for(Field field : orginFields) {
fieldCache.put(field.getName(), field);
}
for(Field targetField : targetClazz.getDeclaredFields()) {
if(!fieldCache.containsKey(targetField.getName())) {
continue;
}
Field orginField = fieldCache.get(targetField.getName());
orginField.setAccessable(true);
targetField.setAccessable(true);
targetField.set(targetObj, targetField.get(OrginObj));
}
}
}
②还可以使用mapStruct工具
步骤:Ⅰ.先引入org.mapStruct依赖
Ⅱ.@Mapper
public interface CarMapper{
CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
@Mapping(source = "numberOfSeats", target="seatCount");
@Mapping(source="type", taarget = "typeDto")
CarDto carToCarDto(Car car);
}
其底层原理依旧是setxxx(getxxx());的方式,可以避免手写。
③还可以使用JsonUtil工具,转成String,在通过JsonUtil.toObject(指定的实体类)实现
4、反射机制和new对象在类加载的过程中有什么区别
不管是反射还是new对象的形式在类加载的过程中,都需要打开&检查class文件,区别就在于new对象的时候,是在类加载去做的,反射是在运行期做的。
5、jvm类加载流程和内存结构:
类加载器ClassLoader(将class文件加载到jvm中)
加载-->验证-->准备-->解析-->初始化
new对象和反射都需要解析class文件,class文件中包含类相关的所有相关信息,如:类名,包名,属性,方法等。class对象就是我们获得某一各类型的字节码的引用对象。
6、反射的方式有几种:
三种
1、Class personClazz = Person.class();
2、Person person = new Person();
Class personClazz1 = person.getClass();
3、Class personClazz2 = class.forName("com.xxx.xxx.Person");
personClazz == personClazz1;
personClazz == personClazz2;
三种方式获得的class实例相同
6、反射的步骤:
无论是new对象还是使用反射机制,创建实例对象的步骤,都需要以下三步:
1、加载class文件
2、查找入参匹配的构造函数
3、通过构造函数创建实例对象
Class personClazz = Class.forName("com.xxx.xxx.Person");
Constructor constructor = personClazz.getContructor();
Person person = (Person) constructor.newInstance();
7、单例模式可以被破坏么,如何破坏?
通过反射可以在运行期动态加载想要加载的类,通过反射可以破坏单例模式,使一个对象可以多次实例化
通过getDeclaredConstructor的方式即可
第一步:通过反射创建对象
Class singlePersonClazz = Class.forName("com.xxx.Person");
第二步:获得构造函数
Constructor constructor = singlePersonClazz.getDeclaredConstructor();// 即可获得私有化构造函数,但无使用权限
constructor.setAccessable(true);// 可以使用私有构造函数,也可以再次实例化对象。
8、getDeclaredField和getField有什么区别
getDeclaredField可以获取一个类里的所有修饰的字段,但无法获取父类的字段
getField可以获取一个类里所有的public修饰的字段,也可以获取到父类字段,但无法获取到public以外的字段。
9、什么是双亲委派机制
执行类加载的时候,从哪个类开始的,也从哪个类结束,比如:某个类从Extension ClassLoader开始加载,Extension ClassLoader委派给父类Bootstrap ClassLoader,若BootStrap ClassLoader依旧没有加载上,则开始向下加载,加载到Extension ClassLoader结束。
加载器加载类时先把请求委托给自己的父类加载器执行,直到顶层的启动类加载器,父类加载器能够完成则返回成功,不能完成则子类加载器自己尝试加载。
优点:避免类的重复加载。
避免Java的核心Api被篡改。

10、为什么需要spi破坏双亲委派机制:
SPI是Service provider Interface的缩写,是java提供的一套用于被第三方开发或者实现的API接口,可以用于模块化,解耦,插件机制等。如果本身是启动类加载器,双亲委派机制,无法加载到下面的层级,通过线程,可以获得当前线程的应用类加载器,应用类加载器可以去获得ClassPath里的相关类,即可见性原则,上层类加载器对下层类加载器是可见的,然而下层类加载起对上层类加载器是不可见的,所有由java团队设计了线程上下文类加载器,如果不破坏双亲委派机制,无法从ClassPath里获取到相应的类
四、泛型:
1、什么是泛型,为什么要用泛型:
早期java使用Object类型来代表任意类型,但是向下转型有强转的问题,线程也不安全,所以针对List、Set、Map等集合类型,它们对存储的元素是没有任何限制的,假如向List中存储一个Dog类型的对象,但是有人把Cat类型也存储到List中,编译上没有任何语法错误,所以把所有使用该泛型参数的地方都被统一化,保证类型一致,如果未指定具体的类型,默认是Object类型,集合体系中是所有的类型都增加了泛型,泛型也主要用于集合。
2、什么是泛型类:
泛型类就是把泛型定义在类上,用户使用该类的时候,才把类型明确下来,用户明确了什么类型,该类就代表什么类型,也不用担心强转的问题和运行时转换异常的问题。
3、为什么使用泛型方法:
除了在类上使用泛型,我们可能就要仅仅某个方法上需要使用泛型,外界仅仅关心该方法,不关心其他的属性,如果在整个类上定义泛型,未免小题大做,所以采用泛型方法。
4、什么是类型擦除:
泛型是提供给java编译器使用的,它可以作为类型的限制,让编译器在源码级别上挡住非法类型的数据,在java1.5之后,编译器编译完带有泛型的java程序后,生成的class文件里将不再带有泛型的信息,这个过程叫做类型擦除,擦除掉“T”由Object替代,编译器会自动生成一个桥方法,因为擦除掉之后,就会失去了类型的校验,可以用method.briged()来判断一个method是不是桥方法。
5、类型通配符(不常用)
List<?>表示类型未知的list,可以匹配任何类型的元素,声明List<?>后,不能向集合中添加元素,因为无法确定集合的元素类型,唯一例外的是可以添加null
6、泛型的上限和下限
泛型的上限:
格式:<? extends ? 类> 对象名称
意义:只能接收该类型及其子类

泛型的下限:
格式:<? super ?类> 对象名称
意义:只能接收该类型及其父类类型

五、ArrayList和LinkedList:
1、ArrayList的底层逻辑:
创建一个ArrayList就是创建了一个空数组,Object [],初始值的大小是0,存入一个元素,首次扩容至默认值为10,之后按1.5倍向下取整进行扩容。
2、为什么是按照1.5倍扩容?
通过源码来看,int newCapacity = oldCapacity + (oldCapacity >> 1);
右移一位表示÷2,左移一位表示乘2,
举例说明:32 = 2^5,BIN = 0100000。即从右向左查,1在下标5的位置,即2^5;右移一位之后,BIN变成0010000也就是2^4 = 16;左移一位BIN变成01000000 1在下标6的位置,也就是2^6 = 64。
3、ArrayList与LinkedList的区别?
①ArrayList的底层是数组,LinkedList的底层是双向链表。
②ArrayList增删比较慢,但是查询很快,LinkedList增删很快,查询很慢
③ArrayList的内存空间是连续的,LinkedList的内存空间可以是不连续的
④两者都不是线程安全的。
4、为什么ArrayList增删比较慢,查询较快,而LinkedList的增删很快,查询很慢:
ArrayList的查询比较快是因为ArrayList的内存空间是连续的,CPU内部缓存结构会缓存连续的内存片段,可以大幅降低读取内存的性能开销。而linkedList查询时根据index,如果index在前半段则是从头节点开始向后遍历,如果index在后半段则是从尾节点开始遍历。ArrayList增删的时候,可以有两种方式,一种是指定index插入的,一种是尾插法,无论是那种方法,ArrayList在新增的时候,都会去调用ensurecapacityInternal来校验数组长度,如果长度不够,会进行扩容,以确保已存在的数组有足够的容量来存储这个新元素,旧数组会被使用Arrays.copyOf方法复制到新的数组中去,现有的数组引用指向了新的数组。而linkedList在新增的时候采用的是尾插法,并且无需遍历尾节点,因为在LinkedList的代码中已经保存了尾节点,直接在尾节点后插入就可以了。
5、如何复制一个ArrayList到另外一个ArrayList中去:
根据业务场景的不同,可以采用深克隆和浅克隆,或者ArrayList的构造方法,或者Collection的copy方法
6、既然说ArrayList和LinkedList都是线程不安全的,为什么还要使用呢
因为在使用ArrayList的场景尽量使用多查询少增删的地方,不会太涉及增删操作。而如果碰到频繁的增删操作的时候采用linkedList,如果需要使用线程安全的可以使用vector和CopyOrWriteArrayList。
7、ArrayList和LinkedList如何相互转换
采用构造方法转换。或者list的add方法
六、HashMap:
1、HashMap的数据结构
数组加链表加红黑树
其中链表为单项链表,红黑树则为双向的,当链表长度超过8时,链表转换为红黑树
2、HashMap的底层逻辑及工作原理
hashMap的底层原理是hash数组➕单项链表实现的,数组中每个元素都是链表,由node的内部类Map.entry接口实现,hashMap通过put和get方法存储和获取。
①存储对象时,将K / V键值对传给put()方法,调用hash(K)方法计算K的hash值,然后结合数组长度,计算数组下标,
②根据元素个数调整数组大小,当元素的个数大于capacity*loadFactor时,会进行resize的二倍扩容。
③Ⅰ、如果K的hash值在hashMap中不存在则执行插入,若存在,则发生碰撞。
Ⅱ、如果K的hash值相同,且两者equals返回为true,则执行更新操作。
Ⅲ、如果K的hash值相同,且两者的equals的返回为false,则在尾部插入数据。
获取对象的时候,将K传给get()方法:
①调用hash(K)方法,计算K的hash值,从而获取该键值的所在链表的数组下标,
②顺序遍历列表,equals()方法,查找相同Node链表中K值对应的V值。
3、hash如何实现的
通过HashCode()的高16位异或低16位实现的,可以有效的节省系统开销,规避碰撞,高16位异或低16位的的也叫扰动函数,高16位的值右移到低16位,原低16位的值去掉,现在低16位的值变成原高16位的值,现在的高16位的值补0。然后做异或操作,当前是1,现在还是1的变成0,原先是0的现在还是0的依旧是0,原先是1,现在是0的变成1,原先是0现在是1的也变成1
4、两个对象hashCode相同会怎么样?
hashCode相同,并不代表两个对象相等,用equals方法进行比较,所以两个对象所在的数组下标相同,就会发生碰撞,因为HashMap是使用的链表存储对象,所以这个node会存储到链表中。
5、hashMap的table容量如何确定
table数组的大小是用capacity这个参数决定的,默认是16,也可以构造时传入,最大值是1<<30.10亿多。
装载因子LoadFactor,主要用来确认table数组是否需要动态扩展的,默认值是0.75。比如说table的大小是16,默认值是0.75,阈值就是12,当table的实际大小超过12时,就需要进行扩容。
扩容时调用resize()方法,将table的长度变为原来的两倍。
6、hashMap里put和get的过程。
hashMap的put的过程,首先将K / V键值传给put方法,put方法调用hash(K),计算K的hash值,结合数组长度,计算数组下标。
根据元素个数,调整数组大小,当元素个数大于capacity*loadfactor时,进行resize的二倍扩容。
如果K的hash值在hashMap中不存在,则直接可以在链表的尾部插入数据,或者红黑树中。
如果存在,则调用equals方法,如果返回true,则进行更新操作。如果返回为false,则在链表后插入数据或者在红黑中添加数据。
get的过程是将K传给get()方法,调用hash(K)方法,计算K的hash值,从而获得该键值的所在链表的数组下标。顺序遍历列表,用equals()方法,查找相同的Node链表中K值对应的V值。
7、可以使用二叉树么,为什么使用红黑树,不用二叉树:
可以使用二叉树,不过二叉树在特殊情况下可能会变成一个线性结构,和链表一样了,这样会导致遍历查询变得非常慢
8、什么是红黑树
①只有红色和黑色两种节点
②所有的叶子节点都是黑色的
③根节点也是黑色的
④红色节点的左右子节点都是黑色的
⑤除去红色的节点,任意一个节点到其中一个节点的距离都是相同的
9、hashMap、LinkedHashMap、TreeMap有什么区别及使用场景
hashMap是比较常用的,hashMap只允许一条记录的建为null,允许多条记录的值为null,hashMap不支持线程同步,即在任一时刻,有多个线程在同时写HashMap可能会导致数据不一致,如果想要同步的话,可以使用Collections的synchronized方法使HashMap具备同步能力。或者使用ConcurrentHashMap,在map中插入,删除,定位数据的时候,用hashMap的比较多。
LinkedHashMap会保存存储的顺序,遍历LinkedHashMap的时候,也是按照存储的顺序进行输出的。LinkedHashMap遍历的速度和容量无关,只和数据相关。而hashMap的遍历速率和他的容量相关。输出的顺序和输入的顺序一致的时候使用LinkedHashMap
TreeMap实现sortMap接口,可以把保存的数据按照键值排序,默认升序,遍历TreeMap的时候,数据已经是排序后的,也可以指定排序的比较器。
10、hashMap和hashTable有什么区别
hashMap是线程不安全的,hashTable是线程安全的,hashMap的效率更高,。
hashMap最多只允许一条记录的键为null,可以有许多值为null。hashTable则不允许有空键值对。
hashMap默认初始化数组大小是16,hashTable的默认初始化大小是11,hashMap是二倍扩容,hashTable是二倍加1扩容
hashMap需要重新计算hash值,hashTable则直接使用对象的hashCode。
11、HashMap和ConcurrentHashMap有什么区别
concurrentHashMap是在hashMap的基础上加了锁。原理是并无太大区别,hashMap可以空键值对,concurrenthashMap不允许有空键值对。
12、concurrentHashMap锁机制怎么理解
concurrentHashMap采用的是Node+CAS+synchronized来保证并发安全的,直接用table数组保存键值对,当hashEntry的长度超过阈值,链表转为红黑树,底层变更为数组+链表+红黑树。
13、hashmap什么时候采用红黑树,什么时候不用红黑树
当hashMap中的元素超过8个的时候,采用红黑树,当元素为6的时候退化为链表,其实6和8都是大量的经验结果,是经过数学计算的,当元素小于8的时候,使用红黑树的话,元素比较少,新增的效率比较慢,而链表结构是可以保障查询性能的。如果一个hashMap不停的存储和删除的话,链表个数始终在8徘徊,就会导致链表和红黑树之间的不断切换,效率也会很低,所以有一个中间值7,防止链表和红黑树之间的频繁转换。
14、红黑树是如何做到自平衡的,如何理解红黑树的左旋和右旋
当红黑树插入或者删除节点的时候,可能会造成不平衡,这个时候红黑树就可以通过左旋和右旋以及变色来维持自平衡,假设有两个子节点,分别是x和y,y是x的右子节点,这个时候可以进行左旋操作,通过左旋使x和y互换,变成x是y的左子节点,这个过程就是左旋,同理,假设最开始的时候,y是x的左子节点,通过右旋,是x和y互换,变成x是y的右子节点,这个过程叫做右旋。
15、jdk1.7采用的是头插法,jdk1.8采用的是尾插法,为什么头插法会导致死循环,尾插法不会?
因为当有两个线程的时候,分别是线程A和线程B,两个线程同时对table进行数据迁移,两个线程都读取了待迁移元素A,和下一个元素B,线程A正常执行元素迁移,线程B由于某种原因,还未执行数据迁移操作,当线程A执行完之后,会将当前的table数组,替换为新的Table数组,等线程B开始执行数据迁移操作的时候,当前使用的table数组就不再是旧的Table数组,而是线程A执行后的新的table数组,由于原本的关系是A的next是B,现在变成B的next是A,会形成循环链表,迁移操作将无限循环。
七、jvm
1、jvm的内存模型:
堆、栈、程序计数器、方法区、本地方法栈
其中线程独占的有本地方法栈,栈,程序计数器
线程共享的有堆、方法区
2、栈:线程私有,线程执行方法都会创建一个栈帧,用来存储局部变量表,操作栈,动态链接,方法出口等信息,调用方法时执行入栈,方法返回时执行出栈。
3、本地方法栈:与栈类似,也是线程私有的,执行java方法的时候使用的是栈,执行native方法的时候,使用的是本地方法栈。
4、程序计数器:保存着当前线程执行的字节码位置,每个线程工作时都有独立的计数器,只为执行java方法服务,执行Native方法的时候,程序计数器为空。
5、堆:jvm内存中管理最大的一块区域,线程共享,目的是存放对象实例,几乎所有的对象实例都会放在这里,当堆没有可用空间的时候,会抛出oom异常,根据对象存活的生命周期不同,jvm把对象进行分代管理,由垃圾回收器进行垃圾回收的管理。
6、方法区:又称非堆区,用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器优化后的代码等数据,1.7的永久代和1.8的元空间都是方法区的实现。
7、类加载过程:加载、验证、准备、解析、初始化、使用、卸载。
其中验证、准备、解析合成为链接
加载:通过类的完全限定名查找此类的字节码文件,利用字节码文件创建class对象
验证:验证加载的class文件是否符合当前虚拟机要求,不会危害到虚拟机自身
准备:准备阶段进行内存分配,为static修饰的类分配内存空间,并设置初始值0或者null,其中不包含final修饰的静态变量,因为final修饰的静态变量在编译时分配。
解析:解析常量池中的符号引用替换为直接引用,直接引用为直接指向目标的指针或相对偏移量。
初始化:主要完成静态代码块的执行及静态变量的赋值。先初始化父类,再初始化当前类。只有对类主动使用时才会初始化。
使用:使用的触发条件包括创建类的实例时,访问类的静态方法或静态变量时,使用class.forName反射类的时候,或者某个子类初始化的时候。
卸载:java自带的加载器加载的类在虚拟机的生命周期内不会被卸载,只有自定义的加载器加载的类才会被卸载
8、分代回收:
分代回收基于两个事实,大部分对象很快就不使用了,还有一部分不会立即无用,但也不会持续很长时间。
堆分代:年轻代、老年代、永久代
年轻代:标记-复制,老年代:标记-清除
9、回收算法:
1.9后默认的垃圾回收算法,特点是保持高回收率的同时减少停顿。采用的是每次只清理一部分,而不是清理全部的增量式清理,来保证停顿时间不会过长。其取消了年轻代和老年代的物理划分,但扔属于分代收集器。算法将堆分成了若干个逻辑区域,一部分用作年轻代,一部分用做老年代,还有一部分用来存储巨型对象的分区。遍历所有对象,标记引用情况,清除对象后会对区域进行复制移动,以整合碎片空间,年轻代采用复制算法,并行收集,会stop The World。老年代会对年轻代一并回收。
10、堆和栈的区别:
共享性不同,堆是线程共享的,栈是线程私有的。
功能不同,栈内存是用来存储局部变量和方法调用的,而堆内存是用来存储Java中的对象,无论是局部变量,成员变量还是类变量,他们都指向的对象都存储在堆内存中
异常错误不同,堆和栈的内存不足都会抛出异常,堆的内存不足时java.long.OutOfMemmoryError。栈的内存不足时抛出的是java.long.StackOverFlowError。
空间大小不同,栈的空间远远小于堆的空间。
11、对象分配规则:
对象优先分配在Eden区,如果Eden区没有足够的空间时,虚拟机执行一次MinorGC
大对象直接进入老年代,大对象指的是大量连续的内存空间的对象,这样做的目的是避免在Eden区和两个Survivor区之间发生大量的内存拷贝。
长期存活的对象进入老年代,虚拟机为每个对象定义了一个年龄计数器,如果对象经过了一个minorGC,那么对象就会进入survivor区,之后每经过一次minorGC,对象的年龄都会+1直到达到阈值,对象进入老年区。
动态判断对象的年龄,如果survivor区的相同年龄的所有对象大小的总和大于survivor空间的一半,年龄大于或等于该年龄的对象直接进入老年代。
空间分配担保。每次进入minorGC时,jvm会计算survivor区移至老年区的对象的平均大小,如果这个值大于老年区的剩余值大小则进行一次fullGC,如果小于检查handlePromotionFailure设置,如果为true则只进行monitorGC,如果为false则进行fullGC
12、java对象创建的过程
jvm遇到一个创建对象的指令时,首先去检查这个指令的参数是否能在常量池中定义一个类的符号引用,然后加载这个类。第二步要为这个对象分配内存空间,一种是指针碰撞法,一种是空闲列表法,比较常用的一种的本地线程缓冲分配(TLAB)。第三步将对象除对象头之外的对象内存空间初始化为0,第四步设置对象头。
13、java对象的结构
java对象由三部分组成,对象头,实例数据,对齐填充。
对象头由两部分组成,第一部分存储对象自身运行时的数据,hash码,GC分代年龄,锁标识状态,线程持有锁,偏向线程id。第二部分是指针类型,指向对象的类元数据类型,如果是数组,对象头还有一部分是记录数组长度。
实例数据用来存储对象真正的有效信息
对齐填充,jvm要求对象起始地址必须是8字节的整数倍。
14、如何判断一个对象是否可以被回收
判断一个对象是否存活有两种方式
1、引用计数:每个对象有一个引用计数器,新增一个引用时计数加1,引用释放时,计数减一,计数为0时可以回收,但是无法解决相互循环引用的问题。
2、可达性分析:从GCRoots开始向下搜索,搜索所走过的路径称之为引用链,当一个对象到GCRoots时没有任何的引用链相连时,证明这个对象不可用,即不可达对象。
15、jvm永久代会发生垃圾回收么
垃圾回收不会发生在永久代,如果永久代满了或者超过了临界值,会发生fullGC完全垃圾回收。
16、了解哪些垃圾回收算法
a、标记清除算法:算法分为标记和清除两个阶段,首先标记出需要回收的对象,在标记完成后同意回收掉所有被标记的对象
b、复制算法:它将内存按容量划分为大小相等的两块,每次只使用其中一块,当这块内存满了之后,把还存活的对象复制到另一块上,对使用过的那一块内存一次清理。
c、标记压缩算法:也是分为标记和压缩两个阶段,标记阶段和标记清除算法一致,标记出还存活的对象,压缩极端将还存活的对象向一端移动,直接清理掉端边界以外的内存。
d、分代收集算法:将java堆分为新生代和老年代根据各个年代的特点采用最适当的收集方法。
17、调优的命令有哪些?
jps:显示系统内存所有的hotspot虚拟机进程
jstat:用于监视虚拟机运行时状态信息的命令,可以显示虚拟机进程中的类装载、内存、垃圾收集等运行数据
jmap:用于生成heap dump文件
jhat:用来分析生成的dump文件,可以在浏览器中查看,jhat中内置了一个http/html服务器。
jstack:用于生成java虚拟机当前时刻的线程快照
jinfo:用于实时查看和调整虚拟机运行参数
18、调优工具有哪些?
有两种,一种是jdk自带的,jconsole和jvisualvm,第三方的有MAT和GChisto
jconsole:jdk自带的java监控和管理控制台。用于对jvm线程、类、内存的监控
jvisualvm:可以用于分析内存快照,线程快照,监控内存变化,GC变化等
MAT:基于eclipse内存工具,查找内存泄漏和减少内存消耗
GChisto:分析GC日志工具
19、Minor GC和FullGC都是什么时候发生的
MinorGC是在新生代内存不足的时候发生的,也叫YGC,jvm内存不足的时候触发FGC
20、jvm性能调优参数
-Xmx:堆内存最大限制
-XX:NewSize:新生代大小
-XX:NewRatio:新生代和老年代占比
-XX:Survivor Ratio:伊甸园空间和幸存者空间占比
-XX:+UseParNewGC新生代设置垃圾回收器
-XX:+UseConcMarkSweepGC 老年代设置垃圾回收器
21、虚拟机为什么使用元空间替代永久代
虚拟机中的方法区在1.7之前都是永久代实现的,永久代的内存默认是64M并且和堆使用的物理内存是连续的,可以通过设置PremSize参数来设置永久代大小,如果动态生成很多class的话,容易造成java.long.OutOfMemmeryError。因为永久代配置的空间有限,元空间使用的时本地内存,而且和堆使用的物理内存不在连续了,理论上机器内存有多大,元空间的内存就有多大。可以通过MataSpaceSize参数来控制元空间初始值大小等。
22、对象一定分配在堆中么,有没有了解过逃逸分析
不一定分配在堆中的,jvm通过逃逸分析,那些逃不出方法的对象会被分配在栈上。如果一个对象被多个线程或者方法引用的时候,我们称这个对象逃逸了。逃逸是一种全局数据流分析算法,通过逃逸分析可以分析出一个新对象的引用的使用范围,从而决定是否将这个对象分配到堆上。
优点:栈上分配可以降低垃圾收集器的运行频率
同步消除,如果发现某个对象只能从一个线程访问,那么在这个对象上的操作可以不需要同步
标量替换,把对象分解成一个一个的基本类型,并且内存分配不再分配在堆上,而是分配在栈上,可以减少内存使用,不用再生成对象头,程序内存回收率高,GC频率减少。
23、什么是Stop The World
在进行垃圾回收的过程中,会涉及对象的移动,为了保证对象引用更新的正确性,必须暂停所有的用户线程,这种停顿就叫Stop The World 也叫STW
24、什么是指针碰撞
一般情况下,jvm的对象都会被放在堆内存中,当类加载检查过后,java虚拟机开始为新生的对象分配内存。如果java堆中内存绝对规整的,所有被使用过的内存都被放到一边,空闲的内存放在另一边,中间放这一个指针,作为分界点的指示器,所分配的内存仅仅是把那个向空闲空间方向挪动一段与对象大小相等的实例,这种分配方式就是指针碰撞。
八、多线程:
1、说说java中实现多线程的几种方法
创建线程常用三种方式:
a、继承Thread类
b、实现Runnable接口
c、实现Callable接口
d、使用线程池方式创建
实现Runnable和Callable接口都可以实现多线程,不过实现Runnable与实现Callable接口的方式基本相同,只是callable接口里定义方法返回值,可以声明抛出异常而已,因此将实现Runnable和Callable接口归为一种方式,这种方式与继承Thread的区别:
实现Runnable和Callable接口的优缺点:
优点:
①线程类只是实现了Runnable和Callable接口,还可以继承其他的类。
②这种方式下,多个线程可以共享一个target对象,适合多个相同线程来处理同一份资源的情况,从而将cpu、代码、和数据分开,形成了清晰的模型,较好的体现了面向对象思想。
缺点:编程稍微复杂,如果需要访问当前线程,必须使用Thread.currentThread() 方法
继承Thread类方式的优缺点:
优点:编写简单,如果需要访问当前线程无需使用Thread.currentThread() 方法,只需要使用this即可获取当前线程。
缺点:java都是单继承,一旦继承了Thread类,则无法再继承其他的类。
2、如何停止一个正在运行的线程
①使用退出标志,使线程正常退出,当run方法完成后线程终止。
②使用stop方法强行终止,不推荐,因为stop和suspend以及resume方法都是过期作废的方法
③使用interrupt方法中断线程。
3、notify()和notifyAll()有什么区别
notify() 会导致死锁,notifyAll不会
任何时候只有一个线程可以获得锁,也就是说只有一个线程可以运行synchronized中的代码,使用notifyAll可以唤醒所有处在wait()状态的线程,使其重新进入锁的争夺队列中,而notify只能唤醒一个。
wait()应配合while循环使用,不应使用if,务必在wait()调用前后都检查条件,如果不满足,必须调用notify()唤醒另外的线程来处理,自己继续wait()直到条件满足再往下执行。
notify是对notifyAll的一种优化,不过有很精确的使用场景,并且要求正确使用,不然可能会导致死锁。
4、sleep和wait有什么区别
sleep方法是thread类,wait方法是object类
sleep方法导致了程序暂停执行指定的时间,让出cpu给其他线程,但是他的监控状态依旧是保持者。当指定的时间到了又会恢复运行状态,在调用sleep方法的过程中,线程不会释放对象锁。
当调用wait方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备,获取对象所进入运行状态。
5、volatile是什么
volatile有两层语义,①保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量值,这个新值对其他线程来说是立即可见的,volatitle关键词会强制将修改的值立即写入主存。②禁止指令重排序,volatile不是原子性操作。可以保证部分有序性。
6、Thread类中的start()和run()方法有什么区别
start()方法用来启用新创建的线程,而且start()内部调用了run()方法,和直接调用run()方法是不一样的,调用run方法的时候,只会在原来的线程中调用,没有新的线程启动,start()方法才会启动新线程。
7、为什么wait、notify、notifyAll这些方法不再thread类里面?
简单来说就是由于wait、notify、notifyAll都是锁级别的操作,所以把它们定义在object类里是因为锁属于对象。
8、什么是线程安全的
个人理解,就是一段代码在多线程下执行和单线程下执行的结果是一样的,那么就可以说这段代码是线程安全的。理论上说就是多线程访问同一段代码,不会产生不确定的结果。线程安全有以下几个级别:
①不可变:像string、integer、long这些都是final类型的类,任何一个线程都改变不了他们的值,要想改变除非创建一个新的。这些不可变对象不需要任何同步手段就可以在多线程的环境下使用。
②绝对线程安全:不管运行时环境如何,调用者都不需要额为的同步措施,java中标注自己是线程安全的类大多数都不是线程安全的,绝对线程安全的也有,CopyOnWriteArrayList、CopyOnWriteSet
③相对线程安全:相对线程安全就是我们通常意义上所说的线程安全的,像vector这种,add、remove方法都是原子操作,不会被打断。如果有个线程在遍历vector,有个线程同时在add这个vector,99%的情况下都会出现ConcurrentModificationException。
④线程非安全:ArrayList、LinkedList、hashMap都是线程非安全的。
框架篇:
九、Mybatis
1、采用JDBC、Hibernate、mybatis三种方式访问数据库对比
使用JDBC的5个步骤
①注册驱动和数据库信息(jdbcDriver)
②获得Connection,并使用他打开statement对象
③通过statement对象执行sql语句,并获得结果对象的ResultSet
④通过代码将ResultSet对象转化为POJO对象
⑤关闭数据库资源
优点:
①我们只需要会调用JDBC接口中的方法即可,使用简单
②使用同一套java代码,进行少量的修改就可以访问其他JDBC支持的数据库
缺点:
①代码量很大,编码麻烦
②需要我们对异常进行正确的捕获并关闭链接。
采用Hibernate访问数据库
使用Hibernate的3个步骤
①引入Hibernate的Maven依赖
②在hibernate.cfg.xml配置文件中配置数据库数据源
③编写User.hbm.xml配置文件,配置User和tb_user表的映射关系
优点:
①将映射规则分离到xml/注解中,减少代码耦合度。
②无需管理数据库链接,只需配置相应的xml配置文件
③会话只需要操作Session对象即可,关闭资源也只需要关闭Session即可。
缺点:
①全表映射不便利,更新是需要发送所有字段
②无法根据不同的条件组装不同的sql
③对于多表关联和复杂的sql查询支持较差
④HQL性能较差,无法优化sql
采用Mybatis访问数据库
使用Mybatis的四个步骤:
①引入Mybatis的maven依赖
②在mybatis-config.xml文件中配置数据库数据源
③编写UserMapper.xml配置文件,编写sql和配置User以及tb_user表之间的映射关系
④编写数据操作接口,UserMapper.java
优点:
①可以动态配置sql,可以对sql进行优化,并通过配置来决定sql的映射关系。
②具有自动映射功能,在注意命名规则的基础上,无需再配置映射规则。
③提供接口编程的映射器,只需要一个接口和映射文件便可以运行,代码耦合度低。
缺点:
①需要编写大量的sql,对开发人员的sql功底有一定要求。
②sql依赖数据库,导致数据库的一致性较差,不能随意切换数据库
2、mybatis和hibernate的区别,以及什么情况下使用mybatis,什么情况使用hibernate
①mybatis是半自动的ORM框架,hibernate是全自动的ORM的框架
②mybatis需要自己写sql,hibernate则不需要。
③mybatis可以对sql进行优化,hibernate无法对sql进行优化
④mybatis无法随意切换数据库,hibernate数据库无关性强
当需求变化较为频繁且关系数据模型要求不高的时候采用mybatis会好一点,例如互联网软件,企业运营类软件,这类软件需求变化繁复,且一旦变化就要有成果输出。
对于需要变化较少,且对关系数据模型要求较高的使用hibernate。
3、mybatis的映射方式有几种?分别说一下(mybatis是如何将sql执行结果封装为目标对象并返回的)
一、自动映射:
自动映射的配置:
AutoMappingBehavior:
NONE:取消自动映射
PARTIAL:只会自动映射,没有定义嵌套结果集映射的结果集(默认值)
FULL:会自动映射任意复杂的结果集。
代码:
<settings>
<setting name = "autoMappingBehavior" value = "true">
<setting name = "mapUnderscoreToCamelCase" value = "true">
</settings>
mapUnderscoreToCamelCase必须配置,否则失效。
二、手动映射:
手动映射操作步骤
①使用<resultMap>标签配置映射关系
②<select> 标签中使用resultMap作为结果集类型
4、mybatis的入参方式有几种
①基础入参:如Long、String等基础类型 parameterType = "Long" 、parameterType = "String"
②注解入参:@param("id") Long id,当入参大于5个的时候,不建议使用注解入参
③map方式入参:parameterType = "map"
④javaBean的方式入参:对象入参
5、mybatis如何主键回填
在新增的时候,想拿到当前新增的数据的主键,在<insert>标签中添加"useGeneratedKeys" = "true",
即
<insert id = "saveUser" parameterType = "xxx.User" keyProperty ="id" "useGeneratedKeys" = "true">
insert ...
</insert>
6、mybatis的工作流程
mybatis的工作流程是开启sqlSession之后,先查看是否开启二级缓存,如果二级缓存开启,去二级缓存中查找时候有需要的数据,如果有则返回,如果没有则去一级缓存中查找,如果一级缓存中有需要的数据,则返回结果,如果一级缓存中没有数据,则执行sql,执行完的sql,会将消息保存在本地缓存中。
SqlMapConfig.xml,此文件作为MyBatis的全局配置文件,配置了mybatis的运行环境等信息。Mapper.xml文件即SQL映射文件,文件中配置了操作数据库的sql语句。此文件需要在 SqlMapConfig.xml中加载。
通过MyBatis环境等配置信息构造SqlSessionFactory即会话工厂
由会话工厂创建SqlSession即会话,操作数据库需要通过SqlSession进行。
Mybatis底层自定义了Executor执行器接口操作数据库,Executor接口有两个实现,一个是基本执行器、一个是缓存执行器。
Mapped Statement也是MyBatis一个底层封装对象,它包装了MyBatis配置信息及sql映射信息等。mapper.xml文件中一个sql对应一个Mapped Statement对象,sql的id即是Mapped statement的id。
Mapped Statement对sql执行输入参数进行定义,包括HashMap、基本类型、pojo,Executor通过Mapped Statement在执行sql前将输入的java对象映射至sql中,输入参数映射就是jdbc编程中对PreparedStatement设置参数。
Mapped Statement对sql执行输出结果进行定义,包括HashMap、基本类型、Pojo,Executor通过Mapped Statement在执行sql后将输出结果映射至Java对象中,输出结果映射过程相当于Jdbc编程中对结果的解析处理过程。
7、说一下mybatis的缓存机制
mybatis的缓存机制分为两种,一级缓存和二级缓存,默认为一级缓存,即一个sqlSession对象调用一个mapper的方法,如果没有声明需要刷新,并且缓存没有超时的情况下,一般只会执行一次sql,其他的sqlSession都只会取出当前缓存的数据。
二级缓存需要手动开启,在xxx.mapper.xml中添加<cache/>标签,或者在对应的类上增加@CacheNamespace(blocking = true)注解。以开启二级缓存,并且需要执行sqlSession.commit()方法。使得二级缓存生效。其中pojo必须实现serializable接口
8、mybatis如何动态配置sql,都有哪些标签,各个标签的含义是什么
mybatis动态配置sql是通过<where>,<trim>,<if>,<choose>,<test>,<when>,<otherwise>等标签来动态配置sql
<choose> ,<when>,<otherwise>标签类似于if、else-if、else
<trim>标签的作用,是用来去掉一些特殊的sql语法,例如and or,其中参数为:
prefix:在trim标签内的sql语句加上前缀
suffix:在trim标签的sql语句加上后缀
prefixOverrides:在trim标签的语句去掉前缀
suffixoverrides:在trim标签内的语句上去掉后缀
<set>标签,set元素会默认把最后一个“,”去掉
<foreach>标签,用于循环遍历传入的集合数据,有如下参数:
collection:传递进来的参数名称
index:当前元素在集合中的位置
item:循环中当前的元素
open 和close:使用什么符号包装集合
separator:每个元素的间隔符号
代码:
接口定义:
public interface UserMapper{
List<User> getUserByIds(@Param("idList") List<Long> idLIst);
}
xml定义:
<select id = "getUserByIds" resultMap = "resultMap">
select * from user where id in
<foreach collection = "idList" item = "id" index = "index" open = "(" close = ")" separator = ",">
#{id}
</foreach>
</select>

9、说一下mybatis的级联
mybatis的级联有两种,一种是一对一级联,即一个对象最多只能关联另外一个对象,采用的assocation表示:
例:MessageDetailMapper.xml
<select id = “getMessageByMsgId” parameterType = “string” resultType = “messageDetail”>
Select * from tb_message_detail where msg_id = #{msgId}
</select>
MessageMapper.xml
<resultMap id = “messageAndDetailResultMap” type=”vo.Message”>
<id column = ”id” property = “id”/>
<result column=”msg_id” property = “msgId”/>
......
<association property = “messageDetail” column=”msgId” select= “com.xxx.xxx.MessageDetailMapper.getMessageByMsgId”/>
</resultMap>
<select id = “getMessageAndMessageDetailById”parameterType = “long” resultMap = “messageAndDetailResultMap”>
Select * from tb_message where id = #{id}
</select>
这种情况是数据库链接一次,查询两次,先执行tb_message表的查询,根据然后的结果,执行这个表tb_message_detail的查询
还有一种级联关系,为一对多的关系,使用collection表示
Mybatis中级联的标签collection主要用于解决has many类型的关系,它表示一个对象有多个关联对象,比如一般情况下,每个人都有多个联系方式,人和联系方式之间的关系就 可以使用collection表示
配置示例:
UserContactMapper.xml
<select id = “getUserContactByUserId” parameterType = “long” resultType = “xxx.UserContact”>
Select
<include refid = “allColumn”/>
from tb_user_contact where user_id = #{userId}
</select>
UserMapper.xml
<resultMap id = “userResult” type = “xxx.User”>
<id column = “id” property = “id”/>
<result column = “name” property = “name”/>
<result column = “age” property = “age”/>
<collection property = “userContacts” column = “id” select = “xxx.mapper.UserContactMapper.getUserContactByUserId”
</resultMap>
<select id = “getUserAndUserContactById” parameterType = “long” resultMap=”userResult”>
Select
<include refid = “allColumn”/>
From tb_user where id = #{id}
</select>
10、mybatis的分页是什么做的
可以使用mybatis-pageHelper插件来做
11、说一下mybatis的xml映射文件和mybatis内部数据结构之间的映射关系
Mybatis将所有Xml配置信息都封装到All-In-One重量级对象Configuration内部。在Xml映射文件中,<parameterMap>标签会被解析为ParameterMap对象,其每个子元素会被解析为ParameterMapping对象。<resultMap>标签会被解析为ResultMap对象,其每个子元素会被解析为ResultMapping对象。每一个<select>、<insert>、<update>、<delete>标签均会被解析为MappedStatement对象,标签内的sql会被解析为BoundSql对象。
12、mybatis不同的映射文件中的id是否可以重复
可以重复,但是需要映射文件的namespace不同
不同的 Xml 映射文件,如果配置了 namespace,那么 id 可以重复;如果没有配置 namespace,那么 id 不能重复。
原因就是 namespace+id 是作为 Map<String, MapperStatement>的 key使用的,如果没有 namespace,就剩下 id,那么,id 重复会导致数据互相覆盖。
有了 namespace,自然 id 就可以重复,namespace 不同,namespace+id 自然也就不同。
13、通常一个mapper.xml对象一个dao接口,dao是否可以重载
不能重载,方法名对应的 mapper.xml 文件里的一个 id,这个与方法名对应,系统会根据 namespace+id 找到对应的方法对应。Dao 接口即 Mapper 接口。接口的全限名,就是映射文件中的 namespace 的值;接口的方法名,就是映射文件中 Mapper 的 Statement 的 id 值;接口方法内的参数,就是传递给 sql 的参数。Mapper 接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为 key 值,可唯一定位一个 MapperStatement。在 Mybatis 中,每一个标签,都会被解析为一个MapperStatement 对象。
举例:com.mybatis3.mappers.StudentDao.findStudentById,可以唯一找到 namespace 为 com.mybatis3.mappers.StudentDao 下面 id 为findStudentById 的 MapperStatement。
Mapper 接口里的方法,是不能重载的,因为是使用 全限名+方法名 的保存和寻找策略。Mapper 接口的工作原理是 JDK 动态代理,Mybatis 运行时会使用 JDK动态代理为 Mapper 接口生成代理对象 proxy,代理对象会拦截接口方法,转而执行 MapperStatement 所代表的 sql,然后将 sql 执行结果返回。
14、${}和#{}的区别,什么是预编译,为什么需要预编译,sql注入是怎么做的
#{}是参数占位符,是采用预编译的方式,在预处理时,会把参数用占位符“?”代替为select * from user where name = ?;
可以有效的方式sql注入,
${}是字符替换符,是将入参拼接到sql语句中,容易产生sql注入的问题,
例如:select * from user where name = '';这样的一个sql语句,如果采用${}的形式,入参设置为:zhangsan' or '1' = '1
拼接后为select * from user where name = 'zhangsan' or '1' = '1'则会将所有的数据都查询出来。
预编译是指在数据库驱动发生sql语句和参数给DBMS之前,对sql语句进行预编译,这样DBMS在执行sql的时候,就不需要重新编译了,JDBC中使用了对象PreparedStatement来抽象编译语句,预编译阶段可以优化sql的执行,预编译后的sql多数情况下可以直接执行,DBMS不需要再编译,同时预编译的语句对象可以重复利用,把一个sql的预编译后产生的PreparedStatement对象缓存下来,对于同一个sql可以直接执行缓存对象,mybatis默认对所有的sql进行预编译
15、mybatis的延迟加载
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"></setting>
</settings>
16、mybatis是有几种executor执行器,区别是什么,如何选定某种执行器。
SimpleExecutor是最简单的执行器,根据对应的sql直接执行即可,不会做一些额外的操作;
BatchExecutor执行器,顾名思义,通过批量操作来优化性能。通常需要注意的是批量更新操作,由于内部有缓存的实现,使用完成后记得调用flushStatements来清除缓存。
ReuseExecutor 可重用的执行器,重用的对象是Statement,也就是说该执行器会缓存同一个sql的Statement,省去Statement的重新创建,优化性能。内部的实现是通过一个HashMap来维护Statement对象的。由于当前Map只在该session中有效,所以使用完成后记得调用flushStatements来清除Map。
在Mybatis配置文件中,在设置(settings)可以指定默认的ExecutorType执行器类型,也可以手动给DefaultSqlSessionFactory的创建SqlSession的方法传递ExecutorType类型参数,如SqlSession openSession(ExecutorType execType)。
配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements); BATCH 执行器将重用语句并执行批量更新。
17、mybatis的编程步骤是什么样的
(1)获取SqlSessionFactory对象
解析文件的每一个信息保存在Configuration中,返回包含Configuration的DefaultSqlSession;
注意:【MappedStatement】:代表一个增删改查的详细信息
(2)获取SqlSession对象
返回一个DefaultSqlSession对象,包含Executor和Configuration;
这一步会创建Executor;
(3)获取接口的代理对象(MapperProxy)
getMapper,使用MapperProxyFactory创建一个MapperProxy的代理对象,代理对象里包含了DefaultSqlSession(Executor)
(4)执行增删改查方法
(5)关闭会话
十、springBoot
1.springBoot、springMVC、和spring有什么区别
springFrame最重要的特征是依赖注入和控制反转,恰当使用DI和IOC时,可以松耦合
springMVC提供了一种分离式的方法来开发web应用,通过运用像dispatcherServelet,ModelAndView和viewResolver等一些概念,开发web应用。
spring和SpringMVC的问题在于:需要配置大量的参数
springBoot通过一个自动配置和启动的项来解决这个问题,为了更快的构建产品就绪的应用程序springBoot提供了一些非功能性特征
2.springBoot有哪些优点
容易上手,提升开发效率,为spring开发更快,更广泛的入门体验
开箱即用,远离繁琐配置
提供了一系列大型项目通用的非业务性功能,例如内嵌服务器,安全管理,运行数据监控、运行状况检查和外部化配置等
没有代码生成,也不需要xml配置
避免大量的maven导入和各种版本冲突
3.springBoot的核心功能
核心功能一:独立运行spring项目,springBoot可以以jar包的形式独立运行,运行一个springBoot项目只需要java -jar xxx.jar来运行
核心功能二:内嵌servlet容器,可以内嵌tomcat,jetty或者undertow这样我们就不用以war包的形式部署项目
核心功能三:提供start简化maven配置,spring提供了一系列start pom来简化maven的依赖加载,当使用了spring-boot-satrter-web的时候,会自动加载所需要的依赖包
核心功能四:自动配置spring,springBoot会根据在类路径的jar包,类,为jar包中的类自动配置bean,这样会极大的减少使用的配置,会根据启动类所在的目录,自动配置bean
4.springBoot最核心的注解是哪个?这个注解又是由那几个注解组成的?
springBoot最核心的注解是@SpringBootApplication,它是由@EnableAutoConfiguration,@SpringBootConfiguration和@ComponentScan三个注解组成的
@SpringBootConfiguration注解组合了@Configuration注解,实现了配置文件的功能
@EnableAutoConfiguration:打开了自动配置的功能,也可以关闭某个自动配置的选项,如关闭数据圆自动配置功能:@SpringBootApplication(enclude = {DataSourceAutoConfiguration.class})
5.springBoot自动配置原理是什么
注解@EnableAutoConfiguration,@Configuration,@ConditionalOnClass就是自动配置的核心
EnableAutoConfiguration给容器导入META-INF/spring.factories里定义的自动配置类
筛选有效的自动配置类,没一个自动配置类结合对应的的xxProperties.java 读取配置文件进行自动配置功能。
6.springBoot的核心配置文件是什么?bootStrap.properties和application.properties有何区别
单纯做springBoot开发的时候,不太容易遇到bootStrap.properties配置文件,但是在结合springCloud时,这个配置会经常遇到,特别是加载远程配置文件的时候。
springBoot核心的两个配置文件:
bootStrap.properties或者是bootStrap.yml。bootstrap由父ApplicationContext加载的,比application优先加载,配置在应用程序上下文的引导阶段生效,
一般来说我们在springCloud config会用到它,bootStrap里面的属性,不能被覆盖,
application.properties或者application.yml,由ApplicationContext加载,用于springBoot项目的自动化配置
7.微服务中如何实现session共享
常见的方案就是Spring Session + Redis来实现session共享,将所有微服务的session统一保存在redis上,当各个微服务对session有相关的读写操作时,都去操作redis上的session,
这样就实现了session共享,springSession基于spring中的代理过滤器实现,使得session的同步对开发人员而言是透明的,非常简便。
数据库篇:
十一、数据库:
1、mysql数据库引擎有哪些?
mysql常用的引擎:MYISAM、Innodb、Memory、MERGE
MYISAM:全表锁,拥有较高的执行速度,不支持事务,不支持外键,并发性能差,占用空间相对较小,对事务完整性没有要求,以select和insert为主的应用基本上可以使用这引擎。
Innodb:行级锁,提供了具有提交、回滚和崩溃回复能力的事务安全,支持自动增长列,支持外键约束,并发能力强,占用空间是MYISAM的2.5倍,处理效率相对会差一些
Memory:全表锁,存储在内容中,速度快,但会占用和数据量成正比的内存空间且数据在mysql重启时会丢失,默认使用HASH索引,检索效率非常高,但不适用于精确查找,主要用于哪些内容变化不频繁的代码表
MERGE:是一组MYSAIAM表组合
2、说说Innodb和MYISAM的区别
a、innodb支持事务,MyISAM不支持事务,对于Innodb每条sql语句都默认封装成事务,自动提交,这样会营销速度。所以最好是把多条语言放在begin和commit之间,组成一个事务。
b、Innodb支持外键,而MyISAM不支持外键。对一个包含外键的InnoDB表转为MyISAM会失败,
c、Innodb是聚集索引,数据文件和索引绑在一起的,必须要有主键,通过主键索引效率很高,但是辅助索引需要两次查询,先查询到主键,然后通过主键查询到数据,因此主键不应该过大,因为主键过大,其他索引也都会很大,而MyISAM是非聚集索引,数据文件是分离的,索引保存的是数据文件的指针,主键索引和辅助索引是独立的。
d、Innodb里不保存表的具体行数,,执行select count(*) from 时需要全表扫描,而MyISAM用一个变量保存了整个表的行数,执行上述语句时只需要读出变量即可,速度很快。
e、Innodb不支持全文索引,而MyISAM支持全文索引,查询效率上MyISAM要高。
3、什么是索引的最左匹配原则
建立一个索引,对于索引中的字段,mysql会一直向右匹配直到遇到范围查询,直到碰到(>, <, betwoon,like)结束,如果where a= 1, b = 2, c> 3, d = 4.如果建立a,b,c,d顺序的索引,这种情况d是用不到索引的,如果是abdc则都可以用到索引,abd的顺序可以随意调整,=和in可以乱序,mysql的查询优化器会优化成索引可以识别的形式。
中间件篇:
十二、Redis:
1、项目中缓存如何使用,为什么要用缓存,缓存使用不当会造成什么后果?
高性能:对于一些需要复杂操作耗时查出来的结果,且确定后面不怎么变化,但是有很多读的请求,那么直接将查询出来的结果放在缓存中,直接读取缓存即可。
高并发:对于系统高峰期一秒钟过来的请求有1万,一个mysql单机会死掉,使用缓存,把部份数据放缓存,不妨mysql,单机支撑的并发量可一秒几万十几万,
因为缓存走内存,内存支持高并发
用了缓存之后容易有缓存与数据库双写不一致、缓存雪崩、缓存穿透、缓存并发竞争的问题。
2、redis和memcached有什么区别?redis的线程模型是什么?为什么redis单线程却能支撑高并发?
redis支持复杂的数据结构,redis相比memcached拥有更多的数据结构,能支持更丰富的数据操作。如果需要缓存能够支持更复杂的结构和操作,redis会是不错的选择
redis原生支持集群模式,在redis3以上版本,便能支持cluster模式,memcached没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据
redis只使用单核,而memcached可以使用多核,存储大数据memcached的性能高于redis
redis内部使用文件事件处理器file event handler,这个文件事件处理器是单线程的,所以redis叫做单线程模型,纯内存操作,核心是基于非阻塞的IO多路复用机制,
单线程反而避免了多线程的频繁上下文切换问题
3、redis都有哪些数据类型?
string、hash、list、set、sorted set
string这是最简单的类型,就是普通的set和get,做简单的kv缓存
hash类似map的一种结构,这个一般就是可以将结构化的数据,比如一个对象(前提是这个对象没钱套其他对象)给缓存在redis里,然后每次读写缓存的时候,就可以操作hash里的某个字段
list是有序列表,可以通过list存储一些列表型的数据结构,可以通过lrange命令,读取某个闭区间内的元素,可以基于list实现分页查询,基于redis实现简单的高性能分页,
可以做类似微博那种下拉不断分页的东西,性能高,就一页一页走 lrange mylist 0 -1 #0表示开始的位置,-1表示结束的位置,结束位置为-1时,表示查看所有。
示例:lpush mylist 1
lpush mylist 2
lpush mylist 3 4 5
#1
rpop mylist
上面示例是将1、2、3、4、5一次放入在mylist中,前三行执行完,mylist[5, 4, 3, 2, 1], rpop命令是用于从列表尾部移除并返回一个元素当执行rpop命令时,mylist变成[5,4,3,2]并返回1
set 是无序集合,自动去重。如果某个系统部署在多台机器上,得基于redis进行全局的set去重,也可以基于set做交集,并集,差集的操作
#添加元素 sadd mySet 1
#查看全部元素smembers mySet
#判断是否包含某个值 sismember mySet 3
#删除某个/某些元素 srem mySet 1/srem mtSet 2 4
#查看元素个数 scard mySet
#随机删除一个元素 spop mySet
#将一个set的元素移动到另外一个 set smove yourSet mySet 2 ps.该操作会yourSet里是否有2这个元素,如果不存在,则这条命令不会发生任何改动,返回为0,表示移动操作为成功,
如果存在2这个元素,且成功移动到mySet中,则会返回1
#求两个set的交集 sinter yourSet mySet
#求两个set的并集 sunion youSet mySet
#求在youSet中而不在mySet中的元素 sdiff yourSet mySet
sorted set是排序的set,去重但可以排序,写进去的时候给一个分数,自动根据分数排序
zadd board 85 zhangsan
zadd board 72 lisi
zadd board 96 wangwu
zadd borad 63 zhaoliu
在这个board 集合中,成员按照分值从高到低的顺序排列,{wangwu:96, zhangsan:85, lisi:72,zhaoliu:63}
#获取排名前三的用户(默认升序,所以需要rev改为降序)
zrevrange board 0 2
ps.0和2这两个参数指定了获取成员的范围,在redis中索引是从0开始的,所以0表示获取的气势位置,2表示获取的结束位置(包含该位置)
#获取某用户的排名
zrank board zhaoliu
4、redis的过期策略都有哪些?内存淘汰机制都有哪些?手写LRU代码?
redis写入的数据怎么没了?
内存空间有限,redis主要是基于内存来进行高性能,高并发的读写操作的,比如redis只能用10G,如果往里面写入了20G的数据,则会干掉10G,保留10G的数据,保留常用数据,干掉不常用的数据。
redis的过期策略
redis的过期策略是:定期删除+惰性删除
定期删除指的是redis默认每隔100ms就随机抽取一些设置了过期时间的key,检查其是否过去,如果过期就删除
但是定期删除可能会导致很多过期的key到了时间并没有删除,所以需要采用惰性删除,即在你获取某个key的时候,redis会检查一下,如果这个key设置了过期时间那么是否过期了,
如果过期了,此时就会删除,不会返回任何东西,
内存淘汰机制
redis的内存淘汰机制有以下几个
noeviction:当内存不足以容纳新写入的数据时,新写入操作会报错,很少有人会用
allkeys-lru:当内存不足以容纳新写入的数据时,在键空间中,移除最近最少使用的key,常用
allkeys-random:当内存不足以容纳新写入的数据时,在键空间中,随机移除某个key,一般没人用
volatile-lru:当内存不足以容纳新写入数据时,在设置了过去时间的键空间中,移除最近最少使用的key
volatile-random:当内存不足以容纳新写入数据时,在设置了过去时间的键空间中,随机移除某个key
volatile-ttl当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除
5、如何保证redis的高并发和高可用?redis的主从复制原理?redis的哨兵原理?

浙公网安备 33010602011771号