![]() |
java面试知识点精华提炼 |
旁观者
L21
• 2021-03-11 • 回复 1 • 只看楼主
• 举报
|
本篇文章整理的都是常问的问题,如果有些没整理到,可打在评论区,看到会帮大家整理。
java基础
== 和 equals 的区别?
- equals 和 == 最大的区别是一个是方法一个是运算符。
- ==如果比较的对象是基本数据类型,则比较的是数值是否相等;如果比较的是引用数据类型,则比较的是对象的地址值是否相等。
- equals():用来比较方法两个对象的内容是否相等。
注意:equals 方法不能用于基本数据类型的变量,如果没有对 equals 方法进行重写,则比较的是引用类型的变量所指向的对象的地址。
int 和 Integer 的区别
int是基本数据类型,Integer是包装类(Integer是对象),Integer可以像对象一样被操作
java集合
- Collection:Collection 是集合 List、Set、Queue 的最基本的接口。
- Iterator:迭代器,可以通过迭代器遍历集合中的数据
- Map:是映射表的基础接口
1、Collection 和 Map 的继承体系:
list 集合
- List 是有序、且可以重复的 Collection集合。List 一共三个实现类,分别是 ArrayList、Vector 和 LinkedList。
ArraList
ArrayList 内部是通过数组实现,特点:读取快,增删慢。并且它是线程不安全的。
- 因为数组元素之间不能有间隔,所以从中间位置增删元素,需要对数组进行复制、移动、代价比较高。因此读取快,增删慢。
- ArrayList 初始大小是10,每次扩容是原来的1.5倍。
- ArrayList每次扩容都是通过Arrays.copyof(elementData,newCapacity)来实现的。
- 在知道元素的大致数量时提前指定集合的大小,可以做到优化作用。
LinkList
- LinkedList 是用链表结构存储数据的双向链表、支持序列化,特点:读取慢,增删快。并且它是线程不安全的。
- LinkedList 提供了 List 接口中没有定义的方法,用于操作表头和表尾元素,可以当作堆栈、队列和双向队列使用。
Vector(数组实现、线程同步)
- Vector 也是通过数组实现,不同的是它支持线程的同步,所以是线程安全的。
- 实现同步需要很高的花费,因此访问它比访问 ArrayList 慢。
Set 集合
- Set 是无序、且不可以重复的 Collection集合。
- set中是以对象的hashcode值作为判断,判断两个对象是否相等。
- 如果想要让两个不同的对象视为相等的,就必须覆盖 Object 的 hashCode 方法和 equals 方法。
HashSet(Hash 表)
- HashSet存放的是哈希值,存储元素的顺序是按照哈希值来存的。
- 取数据也是按照哈希值取得。元素的哈希值是通过元素的hashcode 方法来获取的。
- HashSet 首先判断两个元素的哈希值如果一样,会接着比较equals 方法,如果equals 方法结果相等就视为同一个元素,结果不相等就视为不同元素。
- 哈希值相同 equals 方法不相等的元素,是在同样的哈希值下顺延(可以认为哈希值相同的元素放在一个哈希桶中),也就是哈希一样的存一列
- HashSet 通过 hashCode 值来确定元素在内存中的位置,一个 hashCode 位置上可以存放多个元素。
如图 1 表示 hashCode 值不相同的情况; 图2 表示 hashCode 值相同,但 equals 不相同的情况。
- 当new 一个HashSet实例时, 其实底层是新创建了一个HashMap实例。 HashSet中的元素实际上由HashMap的key来保存,而HashMap的value则存储了一个PRESENT,它是一个静态的Object对象。
TreeSet(二叉树)
- TreeSet 是使用二叉树的原理对 add 的对象按照指定的顺序排序(升序、降序),每增加一个对象都会进行排序,将对象插入到二叉树指定的位置。
- Integer 和 String 对象都可以进行默认的 TreeSet 排序,而自定义类的对象是不可以的,自己定义的类必须实现 Comparable 接口,并且覆写相应的 compareTo()函数,才可以正常使用。
- 在覆写 compare()函数时,要返回相应的值才能使 TreeSet 按照一定的规则来排序,比较此对象与指定对象的顺序。
- 如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。
LinkHashSet(HashSet+LinkedHashMap)
- LinkedHashSet 继承与 HashSet、又基于 LinkedHashMap 来实现的。
- LinkedHashSet 通过传递一个标识参数,调用父类的构造器,底层构造一个 LinkedHashMap 来实现,在相关操
作上与父类 HashSet 的操作相同,直接调用父类 HashSet 的方法即可。
Map
HashMap(数组+链表+红黑树)
- java7中 hashmap 使用,数组+链表实现
- java8以后 hashmap 使用,数组+链表+红黑树实现
- HashMap 根据键的 hashCode 值存储数据,具有很快的访问速度,但遍历顺序却是不确定的。 查询快无序
- HashMap 中 key 不可重复,value可以重复,且线程不安全。
- HashMap 可以用 Collections 的 synchronizedMap 方法实现线程安全的能力,或者使用 ConcurrentHashMap。
- 大方向上,HashMap 里面是一个数组,然后数组中每个元素是一个单向链表。
- 上图中每个绿色的实体是嵌套类 Entry 的实例,Entry 包含四个属性:key, value, hash 值和用于单向链表的 next。
- Java8 以后当链表中的元素超过了 8 个以后,会将链表转换为红黑树,
- 红黑树为了降低时间复杂度,由原来的 O(n) 降为 O(LogN),以加快检索速度。
- capacity:当前数组容量,始终保持 2^n,可以扩容,扩容后数组大小为当前的 2 倍。
- loadFactor:负载因子,默认为 0.75。
- threshold:扩容的阈值,等于 capacity * loadFactor
ConcurrentHashMap
- Java7 以前使用 segment(分段) + 数组 + 链表 实现,使用对 segment(分段)加锁实现线程安全,简称分段锁。
- Java8 以后使用 数组 + 链表 + 红黑树 实现,使用Synchronized和CAS实现线程安全。
1、Java7 以前 ConcurrentHashMap 由一个个 Segment(分段) 组成。
- Segment(分段) 通过继承 ReentrantLock 来进行加锁,每次加锁的操作锁住的是一个 segment(分段),这样只要保证每个 Segment 是线程安全的,也就实现了全局的线程安全。
- Segment(分段) 数量默认是 16,初始化时可以设置为其他值,但是一旦初始化以后,它是不可以扩容的。
- 因为是对 Segment(分段)单独加锁,所以理论上,最多可以同时支持 16 个线程并发写,只要它们的操作分别分布在不同的 Segment 上
2、Java8以后 ConcurrentHashMap 和 HashMap 实现是差不多的,只是比HashMap多了Synchronized和CAS实现线程安全。
- 在 put 时首先计算 key 的 hash 值,判断 hash 值有没有冲突,没有冲突直接 CAS 插入。
- 如果 hash 值存在冲突,就使用 Synchronized 加锁,加锁时会只锁住单一链表或者红黑树的头结点。
HashTable(线程安全)
- Hashtable 很多的常用功能与 HashMap 类似,不同的是它继承自 Dictionary 类,并且是线程安全的。
- 并发性不如 ConcurrentHashMap,因为 ConcurrentHashMap 引入了分段锁。
- Hashtable 不建议使用,可以使用 HashMap 和 ConcurrentHashMap 代替。
TreeMap(可排序)
- TreeMap 实现 SortedMap 接口,根据键排序,默认是按键值的升序排序,也可以自定义排序。
- 如果使用到排序的功能,建议使用 TreeMap。
在使用 TreeMap 时,key 必须实现 Comparable 接口或者在构造 TreeMap 传入自定义的
Comparator,否则会在运行时抛出 java.lang.ClassCastException 类型的异常。
LinkHashMap(记录插入顺序)
- LinkedHashMap 是 HashMap 的一个子类,保存了元素的插入顺序,在用 Iterator 遍历
- LinkedHashMap 时,先得到的记录肯定是先插入的,也可以在构造时带参数,按照访问次序排序。
反射机制
- 反射机制是程序在运行中,获取任意一个类的属性和方法,并且可以调用。以达到动态获取类信息、动态调用对象的方法。
- 反射将类的各个组成部分封装成其他对象,这就是反射机制。
反射的应用场合
- Java 对象在运行时可能会出现两种类型:编译时类型和运行时类型。
- 编译时的类型由声明对象时用的类型来决定,运行时的类型由实际赋值给对象的类型决定 。
如:Person p=new Student();
其中编译时类型为 Person,运行时类型为 Student。
程序在运行时想要获取 Student 对象的真实信息,就只能依靠运行时信息来发现该对象和类的真实信息,此时就必须使用到反射了
反射 API
- Class 类:反射的核心类,可以获取类的属性,方法等信息。
- Field 类:Java.lang.reflec 包中的类,表示类的成员变量,可以用来获取和设置类之中的属性值。
- Method 类: Java.lang.reflec 包中的类,表示类的方法,它可以用来获取类中的方法信息或者执行方法。
- Constructor 类: Java.lang.reflec 包中的类,表示类的构造方法。
- 获取 Class 对象的 3 种方法
// 1 调用某个对象的 getClass()方法
Person p=new Person();
Class clazz=p.getClass();
// 2 调用某个类的 class 属性来获取该类对应的 Class 对象
Class clazz=Person.class;
// 3 使用 Class 类中的 forName()静态方法(最安全/性能最好/最常用)
Class clazz=Class.forName("类的全路径");
- 通过 Class 类中的方法获取并查看该类中的方法和属性。
// 获取 Person 类的 Class 对象
Class clazz=Class.forName("reflection.Person");
// 使用.newInstane 方法创建对象
Person p=(Person) clazz.newInstance();
// 获取构造方法创建对象并设置属性
Constructor c=clazz.getDeclaredConstructor(String.class,String.class,int.class);
Person p1=(Person) c.newInstance("李四","男",20);
//获取 Person 类的所有方法信息
// getMethods(),该方法是获取本类以及父类或者父接口中所有的公共方法(public修饰符修饰的)
// getDeclaredMethods(),该方法是获取本类中的所有方法,包括私有的(private、protected、默认以及public)的方法。
Method[] method=clazz.getDeclaredMethods();
for(Method m:method){
System.out.println(m.toString());
// 调用方法 使方法执行
m.invoke(p, 20);//需要两个参数,一个是要调用的对象(获取有反射),一个是实参
}
//获取 Person 类的所有成员属性信息
Field[] field=clazz.getDeclaredFields();
for(Field f:field){
System.out.println(f.toString());
}
//获取 Person 类的所有构造方法信息
Constructor[] constructor=clazz.getDeclaredConstructors();
for(Constructor c:constructor){
System.out.println(c.toString());
}
序列化和反序列化
- 序列化:将对象写入到IO流中
- 反序列化:从IO流中恢复对象
- 在类中增加 writeObject 和 readObject 方法可以实现自定义序列化策略。
- 通过 ObjectOutputStream 和 ObjectInputStream 对对象进行序列化及反序列化。
- 意义:序列化机制允许将实现序列化的Java对象转换为字节序列,这些字节序列可以保存在磁盘上,或通过网络传输,以达到以后恢复成原来的对象。序列化机制使得对象可以脱离程序的运行而独立存在。
- 使用场景:所有可在网络上传输的对象都必须是可序列化的,比如RMI(remote method invoke,即远程方法调用),传入的参数或返回的对象都是可序列化的,否则会出错;所有需要保存到磁盘的java对象都必须是可序列化的。
- 通常建议:程序创建的每个JavaBean类都实现Serializeable接口。并且创建序列化ID,用来判断是否可以反序列化。
- 序列化并不保存静态变量
- 要想将父类对象也序列化,就需要让父类也实现 Serializable 接口。
- 如果不想让某个变量被序列化,使用transient修饰,反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。
- 对象的类名、实例变量(包括基本类型,数组,对其他对象的引用)都会被序列化;方法、类变量、transient实例变量都不会被序列化。
- 序列化对象的引用类型成员变量,也必须是可序列化的,否则,会报错。
- 反序列化时必须有序列化对象的class文件。
- 同一对象序列化多次,只有第一次序列化为二进制流,以后都只是保存序列化编号,不会重复序列化。
IO和NIO
1、主要区别:
- io是面向流、阻塞的。 Nio是面向缓存、非阻塞的。
- 传统IO基于字节流和字符流进行操作。
- NIO基于Channel(通道)、Buffer(缓冲区)进行操作,数据从通道读取到缓冲区中,或者从缓冲区读取到通道中。
- NIO中使用Selector(选择区)监听多个Channel(通道)事件,因此单个线程可以监听多个数据通道。(比如:连接打开,数据到达)
2、IO 工作流程:
- 由于Java IO是阻塞的,所以当面对多个流的读写时需要多个线程处理。例如在网络IO中,Server端使用一个线程监听一个端口,一旦某个连接被accept,创建新的线程来处理新建立的连接。其中 read/write 是阻塞的。
3、NIO 工作流程:
- NIO 提供 Selector 实现单个线程管理多个channel的功能。select 调用可能是阻塞的,也可以是非阻塞的。但是read/write是非阻塞的!
4、NIO为什么会被阻塞:
//这个方法可能会阻塞,直到有一个已注册的事件发生,或者当一个或者更多的事件发生时
//可以设置超时时间,防止进程阻塞
selector.select(long timeout);
//使用此方法可以防止阻塞,阻塞在select()方法上的线程也可以立刻返回,不阻塞
selector.selectNow();
//可以唤醒阻塞状态下的selector
selector.wakeup();
5、BIO、NIO、AIO 有什么区别:
- BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方 便,并发处理能力低。
- NIO:New IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道) 通讯,实现了多路复用。
- AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的 操作基于事件和回调机制。
代码执行顺序:父类子类 静态代码块、构造代码块、构造方法执行顺序
父静态、子静态、父构造代码块、父构造方法、子构造代码块、子构造方法
java多线程实现
- 创建多线程有4种方式,其中两种有返回值,两种没有返回值。
1.继承Thread类,重写run方法(其实Thread类本身也实现了Runnable接口)
2.实现Runnable接口,重写run方法
3.实现Callable接口,重写call方法(有返回值)
4.使用线程池(有返回值)
四种线程池
- Java 里面线程池的顶级接口是 Executor,但是严格意义上讲 Executor 并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是 ExecutorService。
- newSingleThreadExecutor
创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
- newFixedThreadPool
创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
new ThreadPoolExecutor(int, int, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
- newCachedThreadPool
创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
new ThreadPoolExecutor(0, Integer.MAX_VALUE, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
- newScheduledThreadPool
创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
new ScheduledThreadPoolExecutor(10);
线程生命周期(状态)
- 线程有五种状态 新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)、死亡(Dead) 。
- 新建(New):使用 new 创建了一个线程之后,该线程就处于新建状态,此时仅由 JVM 为其分配内存,并初始化其成员变量的值
- 就绪(Runnable):线程对象调用 start()方法之后,该线程处于就绪状态。JVM 为其创建方法调用栈和程序计数器,等待调度运行。
- 运行(Running):如果处于就绪状态的线程获得了 CPU,开始执行 run() 方法的线程执行体,则该线程处于运行状态。
- 阻塞(Blocked):
指线程因为某种原因放弃了 cpu 使用权,也即让出了 cpu timeslice,暂时停止运行。
直到线程进入可运行(runnable)状态,才有机会再次获得 cpu timeslice 转到运行(running)状态。 - 阻塞的情况分三种:
- 等待阻塞(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)状态。
- 线程死亡(DEAD):线程会以下面三种方式结束,结束后就是死亡状态。
- 正常结束,run()或 call()方法执行完成,线程正常结束。
- 异常结束,线程抛出一个未捕获的 Exception 或 Error。
- 调用 stop,直接调用该线程的 stop()方法来结束该线程—该方法通常容易导致死锁,不推荐使用。
sleep 与 wait 区别
- sleep()方法属于 Thread 类,而 wait()方法,则是属于Object 类中的。
- sleep()方法是暂停执行指定的时间,让出 cpu 给其他线程,但是他的监控状态依然保持,当指定的时间到了又会自动恢复运行状态。
- 在调用 sleep()方法的过程中,线程不会释放对象锁。
- 在调用 wait()方法的时候,线程会放弃对象锁,并进入等待队列,当其他线程调用notify()或者notifyAll()方法时,当前线程进入就绪状态
start 与 run 区别
- start()方法使用来启动线程,真正实现了多线程运行。这时无需等待 run 方法体代码执行完毕,可以直接继续执行下面的代码。
- run()方法是线程体,包含了要执行的内容,直接调用run()方法,并不是启动线程,和普通方法是一样的。
java框架
spring框架
spring核心
- IOC(Inverse of Control 控制反转):Spring 通过配置文件来利用 Java 反射,实例化并控制对象的,生命周期和对象间的关系。还提供了 Bean 实例缓存、生命周期管理、 Bean 实例代理、事件发布、资源装载等高级服务。
- AOP(Aspect Oriented Programming 面向切面编程):AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面。
Spring 常用模块
- spring Beans:
- spring context:
- spring AOP:
- spring DAO:
- spring ORM:
- spring web:
spring常用注解
- @Controller(@RestController):使用在类上,提供分发器扫描使用,@RestController相当于@ResponseBody+@Controller
- @RequestMapping(@GetMapping、@PostMapping):使用在类上,指定URL在哪里处理Get,Post使用在方法上,用来处理请求。
- @ResponseBody:用于方法上,用来返回JSON格式
- @RequestBody:用于参数上,表示改参数是JSON字符串
- @RequestParam:用于参数上,用来修改接收参数名称
- @PathVariable:使用在方法上,把参数绑定到路径上
- @Service:使用在Service层类上用于实例化Bean
- @Repository:使用在Dao层类上用于实例化Bean
- @Autowired:使用在字段上用于根据类型依赖注入
- @Component:使用在类上用于实例化Bean
- @Configuration:使用在类上,用于定义配置类
- @ModeAttribute:将参数和返回值绑定到Model中
- @SessionAttribute:设置在Model中的属性名称
- @Valid:使用在属性上,用于校验数据
- @CookieValue:用来获取Cokkie中的值
Spring IOC
IOC容器初始化时序图:
Spring Bean 作用域
- Bean的五中作用域:singleton(单例)、prototype(原型)、request(请求)、session(会话)、global session(全局会话)
- singleton:(默认)每一个Spring IoC容器都拥有唯一的一个实例对象,该模式在多线程下是不安全的。
- prototype:每次通过 Spring 容器获取 prototype 定义的 bean 时,容器都将创建一个新的 Bean 实例,每个 Bean 实例都有自己的属性和状态,而 singleton 全局只有一个对象。根据经验,对有状态的bean使用prototype作用域,而对无状态的bean使用singleton作用域。
- request:每个HTTP请求会产生一个Bean对象,Http 请求结束,该 bean实例也将会被销毁。只在基于web的SpringApplicationContext中可用。
- session:限定一个Bean的作用域为HTTPsession的生命周期,session结束bean销毁,只有基于web的Spring ApplicationContext才能使用
- global Session:限定一个Bean的作用域为全局HTTPSession的生命周期,只有基于web的SpringApplicationContext可用
Spring Bean 生命周期
- Spring Bean的生命周期分为四个阶段和多个扩展点。扩展点又可以分为影响多个Bean和影响单个Bean
- 四个阶段
实例化 Instantiation:createBeanInstance()
属性赋值 Populate:populateBean()
初始化 Initialization:initializeBean()
销毁 Destruction:至于销毁,是在容器关闭时调用的,详见ConfigurableApplicationContext#close() - 多个扩展点
生命周期:InitializingBean、DisposableBean
影响多个Bean:BeanPostProcessor、InstantiationAwareBeanPostProcessor
影响单个Bean:
Aware Group1:BeanNameAware、BeanClassLoaderAware、BeanFactoryAware
Aware Group2:EnvironmentAware、EmbeddedValueResolverAware、ApplicationContextAware(ResourceLoaderAware\ApplicationEventPublisherAware\MessageSourceAware)
Spring 依赖注入四种方式
- 构造器注入、setter 方法注入、静态工厂注入、实例工厂
Spring 自动装配
Spring 装配包括手动装配和自动装配,手动装配是有基于 xml 装配、构造方法、setter 方法等。
自动装配有五种自动装配的方式,可以用来指导 Spring 容器用自动装配方式来进行依赖注入。
- no:默认的方式是不进行自动装配,通过显式设置 ref 属性来进行装配。
- byName:通过参数名自动装配,Spring 容器在配置文件中发现 bean 的 autowire 属性被设置成 byname,之后容器试图匹配、装配和该 bean 的属性具有相同名字的 bean。
- byType:通过参数类型自动装配,Spring 容器在配置文件中发现 bean 的 autowire 属性被设置成 byType,之后容器试图匹配、装配和该 bean 的属性具有相同类型的 bean。如果有多个 bean 符合条件,则抛出错误。
- constructor:这个方式类似于 byType, 但是要提供给构造器参数,如果没有确定的带参数的构造器参数类型,将会抛出异常。
- autodetect:首先尝试使用 constructor 来自动装配,如果无法工作,则使用 byType 方式。
Spring AOP
AOP初始化时序图
AOP核心概念
- 切面(aspect):类是对物体特征的抽象,切面就是对横切关注点的抽象
- 横切关注点:对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点。
- 连接点(joinpoint):被拦截到的点,Spring 只支持方法类型的连接点,连接点指的是被拦截到的方法,连接点还可以是字段或者构造器。
- 切入点(pointcut):对连接点进行拦截的定义
- 通知(advice):所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类。
- 目标对象:代理的目标对象
- 织入(weave):将切面应用到目标对象并导致代理对象创建的过程
- 引入(introduction):在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段。
AOP 主要应用场景有
- Authentication 权限
- Caching 缓存
- Context passing 内容传递
- Error handling 错误处理
- Lazy loading 懒加载
- Debugging 调试
- logging, tracing, profiling and monitoring 记录跟踪 优化 校准
- Performance optimization 性能优化
- Persistence 持久化
- Resource pooling 资源池
- Synchronization 同步
- Transactions 事务
AOP 两种代理方式
Spring 提供了两种方式来生成代理对象: JDKProxy 和 Cglib,具体使用哪种方式生成由AopProxyFactory 根据 AdvisedSupport 对象的配置来决定。默认的策略是如果目标类是接口,则使用 JDK 动态代理技术,否则使用 Cglib 来生成代理。
- JDK 动态接口代理
JDK 动态代理主要涉及到 java.lang.reflect 包中的两个类:Proxy 和 InvocationHandler。InvocationHandler是一个接口,通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编制在一起。Proxy 利用 InvocationHandler 动态创建一个符合某一接口的实例,生成目标类的代理对象。 - CGLib 动态代理
CGLib 全称为 Code Generation Library,是一个强大的高性能,高质量的代码生成类库,可以在运行期扩展 Java 类与实现 Java 接口,CGLib 封装了 asm,可以再运行期动态生成新的 class。
和 JDK 动态代理相比较:JDK 创建代理有一个限制,就是只能为接口创建代理实例,而对于没有通过接口定义业务方法的类,则可以通过 CGLib 创建动态代理。 - AOP简单使用
@Component
@Aspect
public class AspectText {
/*
aop的重点概念:
Pointcut(切入点):被增强的方法
Advice(通知/ 增强):封装增强业务逻辑的方法
Aspect(切面):切点+通知
Weaving(织入):将切点与通知结合的过程
前置通知 <aop:before> 用于配置前置通知。指定增强的方法在切入点方法之前执行
后置通知 <aop:after-returning> 用于配置后置通知。指定增强的方法在切入点方法之后执行
环绕通知 <aop:around> 用于配置环绕通知。指定增强的方法在切入点方法之前和之后都执行
异常抛出通知 <aop:throwing> 用于配置异常抛出通知。指定增强的方法在出现异常时执行
最终通知 <aop:after> 用于配置最终通知。无论增强方式执行是否有异常都会执行
* */
@Before("execution(* com.sandwich.controller.admin.*(..))")
public void before(){
System.out.println("before增强方法");
}
@Around("pointcut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("around增强前");
Object proceed = pjp.proceed();
System.out.println("around增强后");
return proceed;
}
@AfterReturning("pointcut()")
public void afterReturning(){
System.out.println("afterReturning返回后");
}
//设置切点表达式:可以共用
@Pointcut("execution(* com.sandwich.controller.admin.*(..))")
public void pointcut(){ }
}
Spring MVC 框架
Spring MVC执行流程
Spring Boot
Spring Boot特性
- 快速创建基于Spring的独立应用程序。
- 内置Serverlet容器,无需单独部署Tomcat等服务器,可直接运行jar包。
- 提供约定的starter POM来简化Maven的配置,让Maven的配置变得更简单。
- 根据项目的Maven依赖配置,Spring Boot自动配置Spring、Spring MVC等等。
- 提供生产就绪型功能,如指标,健康检查和外部配置。
- 基本可以完全不使用XML配置文件,采用注解进行配置。
Spring Boot四大核心:
- 自动配置:针对很多Spring应用程序和常用的应用功能,Spring Boot能自动提供相关配置;
- starter 组件:starter 是 SpringBoot 的一个重要的组成部分,它相当于一个集成的模块;
- Actuator:让你能深入运行中的Spring Boot应用程序,监控程序的内部信息;
- 命令行界面:这个为Spring Boot的可选特性,主要是针对Groovy的。
Mybatis 框架
- Mybatis是如何进行分页的?分页插件的原理是什么?
Mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页,可以在sql内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。
分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。
举例:select * from student,拦截sql后重写为:select t.* from (select * from student)t limit 0,10 - 当实体类中的属性名和表中的字段名不一样 ,怎么办 ?
第1种: 通过在查询的sql语句中定义字段名的别名,让字段名的别名和实体类的属性名一致。
第2种: 通过来映射字段名和实体类属性名的一一对应的关系。 - Mybatis动态sql是做什么的?都有哪些动态sql?能简述一下动态sql的执行原理不?
Mybatis动态sql可以让我们在Xml映射文件内,以标签的形式编写动态sql,完成逻辑判断和动态拼接sql的功能。
Mybatis提供了9种动态sql标签:trim|where|set|foreach|if|choose|when|otherwise|bind。
其执行原理为,使用OGNL从sql参数对象中计算表达式的值,根据表达式的值动态拼接sql,以此来完成动态sql的功能。 - Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重复?
如果配置了namespace那么当然是可以重复的,因为我们的Statement实际上就是namespace+id
如果没有配置namespace的话,那么相同的id就会导致覆盖了。
Mybatis 缓存
Mybatis 的一级缓存原理(sqlsession 级别)
- 第一次查询 sql,sql 查询结果写入 sqlsession 的一级缓存中,缓存使用的数据结构是一个 map。
- key:MapperID+offset+limit+Sql+所有的入参,value:用户信息
- 同一个 sqlsession 再次发出相同的 sql,就从缓存中取出数据。如果两次中间出现 commit 操作(修改、添加、删除),
- 本 sqlsession 中的一级缓存区域全部清空,缓存中查询不到从数据库查询,从数据库查询到再写入缓存。
二级缓存原理(mapper 基本)
- 二级缓存的范围是 mapper 级别(mapper 同一个命名空间),mapper 以命名空间为单位创建缓存数据结构,结构是 map。
- mybatis 的二级缓存是通过 CacheExecutor 实现的。CacheExecutor其实是 Executor 的代理对象。
- 所有的查询操作,在 CacheExecutor 中都会先匹配缓存中是否存在,不存在则查询数据库。
- key:MapperID+offset+limit+Sql+所有的入参
- 具体使用需要配置:
- Mybatis 全局配置中启用二级缓存配置
- 在对应的 Mapper.xml 中配置 cache 节点
- 在对应的 select 查询节点中添加 useCache=true
mysql数据库
- 聚集索引(clustered index):聚集索引决定数据在磁盘上的物理排序,一个表只能有一个聚集索引,一般用primary key来约束。
- 非聚集索引(non-clustered index):它并不决定数据在磁盘上的物理排序,索引上只包含被建立索引的数据,以及一个行定位符row-locator,这个行定位符,可以理解为一个聚集索引物理排序的指针,通过这个指针,可以找到行数据。
- 联合索引:多个字段上建立的索引,能够加速复合查询条件的检索。
- 索引覆盖:被查询的列,数据能从索引中取得,而不是通过行定位符row-locator再到row上获取,即“被查询列要被所建的索引覆盖”,这能够加速查询速度。
事务隔离级别
- 事务的基本要素(ACID):原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)
- 原子性(Atomicity):事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节。事务执行过程中出错,会回滚到事务开始前的状态,所有的操作就像没有发生一样。也就是说事务是一个不可分割的整体,就像化学中学过的原子,是物质构成的基本单位。
- 一致性(Consistency):事务开始前和结束后,数据库的完整性约束没有被破坏 。比如A向B转账,不可能A扣了钱,B却没收到。
- 隔离性(Isolation):同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱,在A取钱的过程结束前,B不能向这张卡转账。
- 持久性(Durability):事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。
- 事务的并发问题
- 脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据
- 不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果 不一致。
- 幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交(read-uncommitted) | 是 | 是 | 是 |
不可重复读(read-committed) | 否 | 是 | 是 |
可重复读(repeatable-read) | 否 | 否 | 是 |
串行化(serializable) | 否 | 否 | 否 |
sql优化的几种方式
- 尽量避免全表扫描,应考虑在 where 及 order by 涉及的列上建立索引。
- 尽量避免在where子句中使用以下查询,会导致放弃索引全表扫描。
is null :使用字段默认值代替
!=或<>操作符:
or :使用 union all 代替
in 和 not in:连续数值使用 between 代替in,或者使用 exists 代替 in
where name like ‘%abc%’ :
where num/2=100 : 应该为 where num=100*2 ,不要在“=”左边进行函数、算术运算或其他表达式运算 - 当索引列有大量数据重复时,SQL查询可能不会去利用索引。
- 一个表的索引数最好不要超过6个,因为 insert 或 update 时有可能会重建索引,执行效率会下降。
- 用具体的字段列表代替“*”,不要返回用不到的任何字段。
- .尽量避免大事务操作,提高系统并发能力。
- 在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致
- 尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。
这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。 - 使用 varchar 代替 char ,长字段存储空间小,节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。
Redis
简单介绍一下 redis
- redis是一个key-value类型的非关系型数据库,基于内存也可持久化的数据库,相对于关系 型数据库(数据主要存在硬盘中),性能高,因此我们一般用redis来做缓存使用;
- Redis支持5种数据类型,string、hash、list、set、zset(sorted set);
Redis key过期的方式
- 被动删除:当读/写一个已经过期的key时,会触发惰性删除策略,直接删除掉这个过期key。
只有key被操作时(如GET),REDIS才会被动检查该key是否过期,如果过期则删除之并且返回NIL。 - 主动删除:由于惰性删除策略无法保证冷数据被及时删掉,所以Redis会定期主动淘汰一批已过期的key。
- maxmemory当前已用内存超过maxmemory限定时,触发主动清理策略。
volatile-lru:只对设置了过期时间的key进行LRU(默认值)
allkeys-lru : 删除lru算法的key
volatile-random:随机删除即将过期key
allkeys-random:随机删除
volatile-ttl : 删除即将过期的
noeviction : 永不过期
基于 redis 的分布式锁
获取锁的时候调用 setnx,如果返回 0,则该锁正在被别人使用,返回 1 则成功获取 锁。 还设置一个获取的超时时间,若超过这个时间则放弃获取锁。
redis集群同步数据
一个集群只能有16384个槽,编号0-16383。这些槽会分配给集群中的所有主节点,分配策略没有要求。可以指定哪些编号的槽分配给哪个主节点。集群会记录节点和槽的对应关系。
接下来就需要对key求哈希值,然后对16384取余,余数是几key就落入对应的槽里。slot = CRC16(key) % 16384。
以槽为单位移动数据,因为槽的数目是固定的,处理起来比较容易,这样数据移动问题就解决了。
使用哈希函数计算出key的哈希值,这样就可以算出它对应的槽,然后利用集群存储的槽和节点的映射关系查询出槽所在的节点,于是数据和节点就映射起来了,这样数据分配问题就解决了。
原文:https://blog.csdn.net/weixin_37865312/article/details/114486549
声明:本文系转载文章,仅供学习交流使用,侵权请联系删除