Java 面试题
????一. 容器部分
????二. 多线程部分
????三. SpringMvc部分
????四. Mybatis部分
????五. MySQL部分
????六. Redis部分
????七. RabbitMQ部分
????八. JVM 虚拟机部分
????九. 其他面试部分
本人正在努力复习,总结面试经验并记录 Java 面试常见问题,欢迎留言监督
一直在更新
Collection的子类List、SetList的子类ArrayList、LinkedList等Set的子类HashSet、TreeSet等Map的子类HashMap、TreeMao等
java.util,Collection是一个集合的顶级接口,它提供了对集合对象进行基本操作的通用接口方法,Collection 接口的意义是为各种具体的集合提供了最大化的统一操作方式,其直接继承接口的有 List 和 Setjava.util.Collections是一个包装类(工具类),它包含了各种有关集合操作的静态多态方法。此类不能被实例化,用于对集合中元素进行排序、搜索以及线程安全等各种操作,服务于 Java 的 Collection 框架
- List、Set 都继承 Collection 接口,而 Map 则不是
- List 是一个有序集合,元素可重复,可有多个NULL值。可以使用各种循环遍历集合,因为它是有序的
- Set 是一个无序集合,元素不可重复,重复元素会被覆盖掉(注意:元素虽然无放入顺序,但元素在 Set 中的位置是由该元素的 HashCode 决定的,其位置是固定的,加入 Set 的 Object 必须定义
equals()方法),Set 只能用迭代,因为它是无序的- Map 是由一系列键值对组成的集合,提供了 key 和 value 的映射。在 Map 中保证 key 与 value 一一对应的关系。一个 key 对应一个 value ,不能存在相同的 key,但 value 可以相同
- Set 与 List 相比较
- Set 检索元素效率较低,删除和插入效率高,因为删除和插入不会引起元素的位置变化
- List 可动态增长,查找元素效率高,但是删除和插入效率低,因为删除或插入一条元素,会引起其他元素位置变化
- Map 适合存储键值对的数据
- 继承的父类不同,但二者都实现了 Map接口
- HashTable 继承自
Dictionary类- HashMap 继承自
AbstractMap类
- 线程安全不同
- HashMap 在缺省的情况下是非Synchronize的
- HashTable 的方法是Synchronize的
- 在多线程下直接使用 HashTable 不需要自己为它的方法实现同步。但使用 HashMap 时需要手动增加同步处理
Map m = Collections.synchronizeMap(hashMap);
- 是否提供 contains() 方法
- HashMap 把 HashTable 的 contains() 方法去掉了,改成了 containsValue() 和 containsKey(),因为 contains 容易让人误解
- HashTable 则保留了 contains()、containsValue()、containsKey() 三个方法,其中 contains() 与 containsValue() 功能相同
- key 和 value 是否可以为 null 值
- HashTable 中,key 和 value 都不能为 null 值
- HashMap 中,null 作为键,但这样的键只有一个。可以有多个 value 为 null 值的键。当 get() 方式返回 null 有可能是 HashMap 中没有该键,也有可能返回的 value 为 null。所以 HashMap 用
containsKey()方法判断是否存在键
- 遍历方式不同
- HashTable 与 HashMap 都是使用 Iterator 迭代器遍历,而由于历史的原因,HashTable 还使用了
Enumeration的方式
- hash 值不同
- 哈希值的使用不同,HashTable直接使用对象的HashCode,而 HashMap 重新计算哈希值
- hashCode是jdk根据对象的地址或者字符串或者数字算出来的int类型的数值
- HashTable 使用的取模运算
- HashMap 使用的与运算,先用
hash & 0x7FFFFFFF后,再对 length 取模,&0x7FFFFFFF的目的是为了将负的 hash 值转化为正值,因为 hash 值有可能为负数,而&0x7FFFFFFF后,只有符号外改变,而后面的位都不变
- 内部实现使用的数组初始化和扩容方式不同
- HashTable 在不指定容量的情况下默认是11,而 HashMap 为16,HashTable 不要求底层数组的容量一定要是2的整数次幂,而 HashMap 底层数组则一定为2的整数次幂
- HashTable 扩容时,将容量变成原来的2倍+1 (old * 2 + 1),而 HashMap 则直接改为原来的2倍 (old * 2)
- 如果需要得到一个有序的 Map 集合就应该使用 TreeMap (因为 HashMap 的排序顺序不是固定的)除此之外,由于 HashMap 有比 TreeMap 更好的性能,在不需要使用排序的情况下使用 HashMap 会更好
- 利用key的hashCode重新hash计算出当前对象的元素在数组中的下标
存储时,如果出现hash值相同的key,此时有两种情况。(1)如果key相同,则覆盖原始值;(2)如果key不同(出现冲突),则将当前的key-value放入链表中
获取时,直接找到hash值对应的下标,在进一步判断key是否相同,从而找到对应值。
理解了以上过程就不难明白HashMap是如何解决hash冲突的问题,核心就是使用了数组的存储方式,然后将冲突的key的对象放入链表中,一旦发现冲突就在链表中做进一步的对比。
- HashSet 实际上是一个
HashMap实例,都是一个存放链表的数组。它不保证存储元素的迭代顺序;此类允许使用 null 元素。HashSet 中不允许有重复元素,这是因为 HashSet 是基于 HashMap 实现的,HashSet 中的元素都存放在 HashMap 的 key 上面,而 value 中的值都是统一的一个固定对象private static final Object PRESENT = new Object();- HashSet 中 add() 方法调用的是底层 HashMap 中的 put() 方法,而如果是在 HashMap 中调用 put() ,首先会判断 key 是否存在,如果 key 存在则修改 value 值,如果 key 不存在这插入这个 key-value。而在 set 中,因为 value 值没有用,也就不存在修改 value 值的说法,因此往 HashSet 中添加元素,首先判断元素(也就是key)是否存在,如果不存在这插入,如果存在着不插入,这样 HashSet 中就不存在重复值。所以判断 key 是否存在就要重写元素的类的 equals() 和 hashCode() 方法,当向 Set 中添加对象时,首先调用此对象所在类的 hashCode() 方法,计算次对象的哈希值,此哈希值决定了此对象在Set中存放的位置;若此位置没有被存储对象则直接存储,若已有对象则通过对象所在类的 equals() 比较两个对象是否相同,相同则不能被添加。
- AarrayList 是动态数组构成 LinkList 是链表组成
- AarrayList 常用于经常查询的集合,因为 LinkList 是线性存储方式,需要移动指针从前往后查找
- LinkList 常用于新增和删除的集合,因为 ArrayList 是数组构成,删除某个值会对下标影响,需要进行数据的移动
- AarrayList 自由度较低,需要手动设置固定的大小,但是它的操作比较方便的,①直接创建②添加对象③根据下标进行使用
- LinkList 自由度较高,能够动态的随数组的数据量而变化
- ArrayList 主要开销在List需要预留一定空间
- LinkList 主要开销在需要存储结点信息以及结点指针信息
- List to Array : 可以使用 List 的
toArray()方法,传入一个数组的类型例如Stirng[] strs = strList.toArray(new String[strList.size()]);- Array to List : 可以使用
java.util.Arrays的asList()方法 例如List<String> strList = Arrays.asList(strs);
- ArrayList 是非线程安全的,而 Vector 使用了
Synchronized来实现线程同步的- ArrayList 在性能方面要优于 Vector
- ArrayList 和 Vector 都会根据实际情况来动态扩容的,不同的是 ArrayList 扩容到原大小的1.5倍,而 Vector 扩容到原大小的2倍
- Array 是数组,当定义数组时,必须指定数据类型及数组长度
- ArrayList 是动态数组,长度可以动态改变,会自动扩容,不使用泛型的时候,可以添加不同类型元素
- poll() 和 remove() 都是从队列头删除一个元素,如果队列元素为空,remove() 方法会抛出
NoSuchElementException异常,而 poll() 方法只会返回 null
- Vector :比 ArrayList 多了同步化机制(线程安全)
- HashTable :比 HashMap 多了线程安全
- ConcurrentHashMap :是一种高效但是线程安全的集合
- Stack :栈,继承于 Vector 也是线程安全
Iterator是集合专用的遍历方式Iterator<E> iterator(): 返回此集合中元素的迭代器,通过集合的iterator()方法得到,所以Iterator是依赖于集合而存在的
Iterator 的使用方法
java.lang.Iterable接口被java.util.Collection接口继承,java.util.Collection接口的iterator()方法返回一个Iterator对象next()方法获取集合中下一个元素hasNext()方法检查集合中是否还有元素remove()方法将迭代器新返回的元素删除
Iterator 的特点
- Iterator 遍历集合过程中不允许线程对集合元素进行修改
- Iterator 遍历集合过程中可以用
remove()方法来移除元素,移除的元素是上一次Iterator.next()返回的元素- Iterator 的
next()方法是通过游标指向的形式返回Iterator下一个元素
- 使用范围不同
- Iterator 适用于所有集合, Set、List、Map以及这些集合的子类型,而 ListIterator 只适用于 List 及其子类型
- ListIterator 有 add() 方法,可以向 List 中添加元素,而 Iterator 不能
- ListIterator 和 Iterator 都有 hasNext() 和 next() 方法,来实现顺序向后遍历。而 ListIterator 有 hasPrevious() 和 previous() 方法,可以实现逆向遍历,但是 Iterator 不能
- ListIterator 可以使用 nextIdnex() 和 previousIndex() 方法定位到当前索引位置,而 Iterator 不能
- 它们都可以实现 remove() 删除操作,但是 ListIterator 可以使用 set() 方法实现对象修改,而 Iterator 不能
- 可以采用
java.util.Collections工具类Collections.unmodifiableMap(map)Collections.unmodifiableList(list)Collections.unmodifiableSet(set)- 如诺修改则会报错
java.lang.UnsupportedOperationException
- 并发:不同的代码块交替执行
- 并行:不同的代码块同时执行
- 个人理解
- 并发就是放下手头的任务A去执行另外一个任务B,执行完任务B后,再回来执行任务A,就比如说吃饭时来电话了,去接电话,打完电话后又回来吃饭
- 并行就是执行A的同时,接受到任务B,然后我一起执行,就比如说吃饭时来电话了,一边吃饭一边打电话
- 根本区别 :进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位
- 在操作系统中能同时运行多个进程,进程中会执行多个线程
- 线程是操作系统能够进行运算调度的最小单位
- JVM内部的实现是如果运行的程序只剩下守护线程的话,程序将终止运行,直接结束。所以守护线程是作为辅助线程存在的
- 继承Thread类创建线程类
- 定义
Thread类的子类,并重写该类的run()方法- 创建
Thread子类的实例,即创建了线程对象- 调用线程对象的
start()方法来启动该线程
- 实现Runnable接口创建线程类
- 创建
runnable接口的实现类,并重写该接口的run()方法- 创建
Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象- 调用线程对象的
start()方法来启动该线程
- 通过 Callable 和 Future 创建线程
- 创建
Callable接口的实现类,并重写call()方法,该call()方法将作为线程执行体,并且有返回值- 创建
Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值- 使用
FutureTask对象作为Thread对象的target创建并启动新线程- 调用
FutureTask对象的get()方法来获得子线程执行结束后的返回值
- 相同点
- 都是接口
- 都可以编写多线程程序
- 都是采用
Thread.start()启动线程
- 不同点
Runnable没有返回值,Callable可以返回执行结果,是个泛型和Future、FutureTask配合可以用来获取异步执行的结果Callable接口的call()方法允许抛出异常,Runnable的run()方法异常只能在内部消化,不能往上继续抛注意
Callalble接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞
- 新建 就绪 运行 阻塞 死亡
- 最大区别是sleep() 休眠时不释放同步对象锁,其他线程无法访问 而wait()休眠时,释放同步对象锁其他线程可以访问
- sleep() 执行完后会自动恢复运行不需要其他线程唤起.而 wait() 执行后会放弃对象的使用权,其他线程可以访问到,需要其他线程调用 Monitor.Pulse() 或者 Monitor.PulseAll() 来唤醒或者通知等待队列
- start() 是来启动线程的
- run() 只是 Thread 类中一个普通方法,还是在主线程中执行
notify()方法随机唤醒对象的等待池中的一个线程,进入锁池notifyAll()唤醒对象的等待池中的所有线程,进入锁池。
- 利用
Executors创建线程池
newCachedThreadPool(),它是用来处理大量短时间工作任务的线程池newFixedThreadPool(int nThreads),重用指定数目nThreads的线程newSingleThreadExecutor(),它的特点在于工作线程数目限制为1newSingleThreadScheduledExecutor()和newScheduledThreadPool(int corePoolSize),创建的是个ScheduledExecutorService,可以进行定时或周期性的工作调度,区别在于单一工作线程还是多个工作线程。newWorkStealingPool(int parallelism),这是一个经常被人忽略的线程池,Java 8才加入这个创建方法,其内部会构建ForkJoinPool,利用Work-Stealing算法,并行地处理任务,不保证处理顺序
- 运行 RUNNING:线程池一旦被创建,就处于
RUNNING状态,任务数为 0,能够接收新任务,对已排队的任务进行处理- 关闭 SHUTDOWN:不接收新任务,但能处理已排队的任务。调用线程池的
shutdown()方法,线程池由RUNNING转变为SHUTDOWN状态- 停止 STOP:不接收新任务,不处理已排队的任务,并且会中断正在处理的任务。调用线程池的
shutdownNow()方法,线程池由(RUNNING或SHUTDOWN) 转变为STOP状态- 整理 TIDYING:
SHUTDOWN状态下,任务数为 0, 其他所有任务已终止,线程池会变为 TIDYING 状态,会执行terminated()方法。线程池中的terminated()方法是空实现,可以重写该方法进行相应的处理- 线程池在
SHUTDOWN状态,任务队列为空且执行中任务为空,线程池就会由 SHUTDOWN 转变为TIDYING状态- 线程池在
STOP状态,线程池中执行中任务为空时,就会由STOP转变为TIDYING状态
- 终止 TERMINATED:线程池彻底终止。线程池在 TIDYING 状态执行完 terminated() 方法就会由 TIDYING 转变为
TERMINATED状态
- 两个方法都可以向线程池提交任务
execute()方法的返回类型是void,它定义在Executor接口中- 而
submit()方法可以返回持有计算结果的Future对象,它定义在ExecutorService接口中
- 使用synchronied关键字
- 使用volatile 关键字,防止指令重排,所有对该变量读写都是直接操作共享内存
- lock锁机制
lock()与unlock()- 使用线程安全的类 比如
StringBuffer、HashTable、Vector等线程安全问题主要是:原子性,可见性,有序性
- 作用
- synchronized 表示只有一个线程可以获取作用对象的锁,执行代码,阻塞其他线程
- volatile 表示变量在
CPU的寄存器中是不确定的,必须从主存中读取。保证多线程环境下变量的可见性,禁止指令重排序
- 区别
synchronized可以作用于变量、方法、对象。volatile只能作用于变量synchronized可以保证线程间的有序性、原子性和可见性。volatile只保证了可见性和有序性,无法保证原子性synchronized线程阻塞,volatile线程不阻塞
synchronized是一个Java关键字,在jvm层面上,而Lock是一个类synchronized以获取锁的线程执行完同步代码,释放锁,如果线程中发生异常,jvm会让线程释放锁。而Lock必须在finally中释放锁,否则容易造成线程死锁Lock可以查看锁的状态,而synchronized不能- 在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时
Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择synchronized是非公平锁,而Lock是可公平锁
- 轻量
非侵入型框架- 控制反转
IOC,促进了松耦合- 面向切面编程
AOP- 容器 采用Java Bean 代替 沉重的
EJB容器- 方便集成 可以和很多框架相互集成如
Mybatis、Shiro- 丰富的功能 Spring 已经写好了很多常的模板如
JDBC抽象类、事务管理、JMS、JMX、Web Service...等
AOP是面向切面编程,是OOP编程的有效补充AOP包括Aspect切面,Join Point连接点,Advice通知,Ponitcut切点其中
Advice通知包括5中模式
Before advice:方法执行前‘增强’After returning advice:方法执行后‘增强’After throwing advice:方法执行中抛出异常时‘增强’After finally advice:方法执行完后‘增强’Around advice:环绕增强‘以上四种的整合’
- IOC是控制反转,是将代码本身的控制权移到外部Spring容器中进行集中管理,也是为了达到松耦合
- Spring Core
- 框架基础部分,提供了
IOC容器,对Bean进行集中管理
- Spring Context
- 基于
Bean,提供上下文信息
- Spring Dao
- 提供了
JDBC的抽象层,它可消除冗长的JDBC编码,提供了声明性事务管理方法
- Spring ORM
- 提供了
对象/关系映射常用的API集成层,如Mybatis、Hibernate等
- Spring Aop
- 提供了
AOP面向切面的编程实现
- Spring Web
- 提供了
Web开发的信息上下文,可与其他的Web集成开发
- Spring Web Mvc
- 提供了
Web应用的Model - View - Controller全功能的实现
- 构造方法注入
- Setter方法注入
不过值得一提的是:Setter注入时如果写了带参构造方法,那么无参构造方法必须也要写上,否则注入失败
- 基于注解得注入
基本上有五种常用注册
Bean的注解
@Component:通用注解@Controller:注册控制层Bean@Service:注册服务层Bean@Repository:注册Dao层Bean@Configuration:注册配置类
- 线程不安全
- Spring容器中的Bean是否线程安全,容器本身并没有提供Bean的线程安全策略,因此可以说Spring容器中的Bean本身不具备线程安全的特性
singleton:单例,默认的作用域prototype:原型,每次都会创建新对象request:请求,每次Http请求都会创建一个新对象session:会话,同一个会话创建一个实例,不同会话使用不同实例global-session:全局会话,所有会话共享一个实例
- 后面三种只有在Web应用中使用Spring才有效
- 编程式事务管理对基于
POJO的应用来说是唯一选择,我们需要在代码中调用beginTransaction()、commit()、rollback()等事务管理相关的方法,这就是编程式事务管理- 基于 TransactionProxyFactoryBean的声明式事务管理
- 基于 @Transactional 的声明式事务管理
- 基于Aspectj AOP配置事务

- 用户发送一个请求至前端控制器
DispatcherServletDispatcherServlet收到请求调用处理器映射器HandlerMapping- 处理器映射器根据请求
url找到具体的处理器,生成处理器执行链HandlerExecutionChain(包括处理器对象和处理器拦截器)一并返回给DispatcherServletDispatcherServlet根据处理器Handler获取处理器适配器HandlerAdapter执行HandlerAdapter处理一系列的操作- 执行处理器
Handler(Controller,也叫页面控制器)Handler执行完成返回ModelAndView到HandlerAdapterHandlerAdapter将Handler执行结果ModelAndView返回到DispatcherServletDispatcherServlet``将ModelAndView传给ViewReslover视图解析器ViewReslover解析后返回具体ViewDispatcherServlet对View进行渲染视图(即将模型数据model填充至视图中)DispatcherServlet响应用户
DispatcherServlet:前端控制器HandlerMapping:处理器映射器HandlerAdapter:处理器适配器HandlerInterceptor:拦截器ViewResolver:视图解析器MultipartResolver:文件上传处理器HandlerExceptionResolver:异常处理器DataBinder:数据转换HttpMessageConverter:消息转换器FlashMapManager:页面跳转参数管理器HandlerExecutionChain:处理程序执行链RequestToViewNameTranslator:请求转视图翻译器ThemeResolver:LocaleResolver:语言环境处理器
@RequestMapping是一个注解,用来标识http请求地址与Controller类的方法之间的映射- 指定 http 请求的类型使用
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
@Autowired为Spring提供的注解,需要导入包org.springframework.beans.factory.annotation.Autowired,采取的策略为按照类型注入@Resource注解由J2EE提供,需要导入包javax.annotation.Resource,默认按照ByName自动注入
- #{} 表示一个占位符
可以有效的防止SQL注入
- ${} 表示拼接SQL串
可以用于动态判断字段
order by ${field} desc可以动态修改fieId来达到动态根据字段降序查询
- 原始分割
取出数据后,进行手动分割
- LIMIT关键字
修改执行SQL语句
- RowBounds实现分页
将
PageInfo信息封装成RowBounds,调用DAO层方法
- Mybatis的Interceptor实现
原理就是执行SQL语句前,在原SQL语句后加上limit关键字,不用自己去手动添加
RowBounds表面是在“所有”数据中检索数据,其实并非是一次性查询出所有数据,因为 MyBatis 是对 jdbc 的封装,在 jdbc 驱动中有一个 Fetch Size 的配置,它规定了每次最多从数据库查询多少条数据,假如你要查询更多数据,它会在你执行 next()的时候,去查询更多的数据。就好比你去自动取款机取 10000 元,但取款机每次最多能取 2500 元,所以你要取 4 次才能把钱取完。只是对于 jdbc 来说,当你调用 next()的时候会自动帮你完成查询工作。这样做的好处可以有效的防止内存溢出。
- 逻辑分页是一次性查询很多数据,然后再在结果中检索分页的数据。这样做弊端是需要消耗大量的内存、有内存溢出的风险、对数据库压力较大。
- 物理分页是从数据库查询指定条数的数据,弥补了一次性全部查出的所有数据的种种缺点,比如需要大量的内存,对数据库查询压力较大等问题
- MyBatis 支持延迟加载,设置
lazyLoadingEnabled=true即可- 延迟加载的原理的是调用的时候触发加载,而不是在初始化的时候就加载信息。比如调用
a.getB().getName(),这个时候发现a.getB()的值为null,此时会单独触发事先保存好的关联 B 对象的 SQL,先查询出来 B,然后再调用a.setB(b),而这时候再调用a.getB(). getName()就有值了,这就是延迟加载的基本原理
- 一级缓存
- 基于
PerpetualCache的HashMap本地缓存,它的声明周期是和SQLSession一致的,有多个SQLSession或者分布式的环境中数据库操作,可能会出现脏数据- 当
Session flush或close之后,该Sessio中的所有Cache就将清空,默认一级缓存是开启的
- 二级缓存
- 也是基于
PerpetualCache的HashMap本地缓存,不同在于其存储作用域为Mapper级别的,如果多个SQLSession之间需要共享缓存,则需要使用到二级缓存- 二级缓存可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现
Serializable序列化接口(可用来保存对象的状态)
- 扩展
- 开启二级缓存后的查询流程:
二级缓存 -> 一级缓存 -> 数据库- 缓存更新机制:当某一个作用域(一级缓存 Session/二级缓存 Mapper)进行了C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear
- Mybatis 更加灵活,可以自己写SQL语句
- 也正是自己写了很多SQL语句,所以移植性比较差
- Mybatis 入门比较简单,使用门槛也低
- hibernate 可以自行把二级缓存更换为第三方的
SimpleExecutor:每执行一次UPDATE\SELECT就开启一个Statement对象,用完后立即关闭ReuseExecutor:执行 UPDATE\SELECT,以 SQL 作为 key 查找Statement对象,存在就使用,不存在就创建,用完后不关闭Statement对象,而是放置于 Map 内供下一次使用。简言之,就是重复使用Statement`对象BatchExecutor:执行UPDATE(没有 select,jdbc 批处理不支持 select),将所有 SQL 都添加到批处理中addBatch(),等待统一执行executeBatch(),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理,与 jdbc 批处理相同
- 分页插件的基本原理是使用 MyBatis 提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的 SQL,然后重写 SQL,根据 dialect 方言,添加对应的物理分页语句和物理分页参数
MyBatis 自定义插件针对 MyBatis 四大对象(Executor、StatementHandler、ParameterHandler、ResultSetHandler)进行拦截
- Executor:拦截内部执行器,它负责调用 StatementHandler 操作数据库,并把结果集通过 ResultSetHandler 进行自动映射,另外它还处理了二级缓存的操作
. StatementHandler:拦截 SQL 语法构建的处理,它是 MyBatis 直接和数据库执行 SQL 脚本的对象,另外它也实现了 MyBatis 的一级缓存- ParameterHandler:拦截参数的处理
- ResultSetHandler:拦截结果集的处理
MyBatis 插件要实现 Interceptor 接口
- setProperties 方法是在 MyBatis 进行配置插件的时候可以配置自定义相关属性,即:接口实现对象的参数配置
- plugin 方法是插件用于封装目标对象的,通过该方法我们可以返回目标对象本身,也可以返回一个它的代理,可以决定是否要进行拦截进而决定要返回一个什么样的目标对象,官方提供了示例:return Plugin. wrap(target, this)
- intercept 方法就是要进行拦截的时候要执行的方法
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
Object plugin(Object target);
void setProperties(Properties properties);
}
官方插件实现:
@Intercepts({@Signature(type = Executor. class, method = "query",
args = {MappedStatement. class, Object. class, RowBounds. class, ResultHandler. class})})
public class TestInterceptor implements Interceptor {
public Object intercept(Invocation invocation) throws Throwable {
Object target = invocation. getTarget(); //被代理对象
Method method = invocation. getMethod(); //代理方法
Object[] args = invocation. getArgs(); //方法参数
// do something . . . . . . 方法拦截前执行代码块
Object result = invocation. proceed();
// do something . . . . . . . 方法拦截后执行代码块
return result;
}
public Object plugin(Object target) {
return Plugin. wrap(target, this);
}
}
具体案例请看 Mybatis 实现自定义插件 通俗易懂
InnoDB 和 Myisam 都是用 B+Tree 来存储数据的
- InnoDB 支持事务,且支持四种隔离级别(读未提交、读已提交、可重复读、串行化),默认的为可重复读.
- Myisam 只支持表锁,且不支持事务.Myisam 由于有单独的索引文件,在读取数据方面的性能很高.
- 在项目配置文件中生命自定义的
key,在项目启动时会判断redis是否存在key,如果没有就会创建一个key传入null值- 数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中
缓存雪崩是指,缓存层出现了错误,不能正常工作了.于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况.
解决方案
- redis 高可用 就是搭建 redis 集群,其中一台redis挂掉后 可以使用其他的 redis
- 限流降级 就是每一个 key 只能一个线程来查询数据和缓存,其他线程等待
- 数据预热 数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中.在即将发生大并发访问前手动触发加载缓存不同的 key ,设置不同的过期时间,让缓存失效的时间点尽量均匀.*
就是访问redis数据库,查不到数据,就是没有命中,会去持久化数据库查询,还是没有查到.假如高并发的情况下,持久化数据库一下增加了很大压力,就相当于出现了缓存穿透
解决方案
- 缓存空对象 在存储层命中失败后,即使返回空对象也将其缓存,并设置一个过期时间,之后访问的这个数据将会从缓存中取出,很好的保护了后端数据源,这样也会有出现问题 例如空值被缓存也就会增加大量的缓存空间,设置了过期时间还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响
- 布隆过滤器 对所有可能查询的参数以 hash 形式存储,查询时发现值不存在就直接丢弃,不会去持久层查询
RabbitMQ 对于 queue 中的 message 的保存方式有两种方式:disc 和 ram.如果采用disc,则需要对 exchange/queue/delivery mode 都要设置成 durable 模式. Disc 方式的好处是当 RabbitMQ 失效了, message 仍然可以在重启之后恢复.而使用 ram 方式, RabbitMQ 处理 message 的效率要高很多, ram 和 disc 两种方式的效率比大概是 3:1.所以如果在有其它 HA 手段保障的情况下,选用 ram 方式是可以提高消息队列的工作效率的.
- 栈内存用来存储局部变量和方法调用
- 而堆内存用来存储 Java 中的对象.无论是成员变量,局部变量,还是类变量.,它们指向的对象都存储在堆内存中.
- 栈内存归属单个线程,一个栈对应一个线程,其中储存的变量只能在该线程中可以访问到,也可以理解为私有内存
- 而堆内存中的对象 所有线程均可见,堆内存中对象可以被所有线程访问到
- 栈内存要远小于堆内存
在类里使用 implements 关键字实现 Api 接口
- Mybatis 框架
- Hibernate 框架
- JDBC 技术
- c3p0 连接池
- dbcp 连接池
在 pom.xml 文件引入 redis 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
在 application 配置文件中 书写 redis 配置
spring.redis.host=127.0.0.1
#Redis服务器连接端口
spring.redis.port=6379
#Redis服务器连接密码(默认为空)
#spring.redis.password=
- 服务之间采用Restful等轻量级通讯
- 精准的制定优化服务方案,提高系统的可维护性
- 服务之间拆分细致,资源可重复利用,提高开发效率
- netflix Eureka 注册中心
- netflix Ribbon 负载均衡
- netflix Zuul 网关
- netflix Hystrix 熔断器
- feign 服务调用
- List 允许有多个重复对象,而 Set 不允许有重复对象
- List 允许有多个NULL值,而 Set 只允许有一个NULL值
- List 是一个有序的容器,输出顺序即是输入顺序
- Set 是一个无序的容器无法保证每个元素的顺序,但是可以用 TreeSet 通过 Comparator 或者 Comparable 维护排序顺序
- List的实现类有 ArrayList、LinkList、Vector 其中 ArrayList 最常用于查询较多,随意访问.LinkList 同于新增和删除较多的情况,Vector 表示底层数组,线程安全象
- Set的实现类有 HashSet、TreeSet、LinkedHashSet 其中基于 HashMap 实现的 HashSet 最为流行,TreeSet 是一个有序的Set容器象
扩展
Map的实现类有HashMap、HashTable、TreeMap
- 表示全局或静态的意思、用来修饰成员变量和成员方法,也可以形成静态代码块
- 达到了不用实例化就可以使用被 public static 修饰的变量或方法
保证整个项目中一个类只有一个对象的实例,实现这种功能就叫做单例模式
- 单例模式的好处:
- 单例模式节省公共资源
- 单例模式方便控制
- 如何保证是单利模式 ?
- 构造私有化
- 以静态方法返回实例
- 确保对象实例只有一个
- 单例模式有哪几个 ?
- 饿汉模式
把对象创建好,需要使用的时候直接拿到就行
- 懒汉模式
等你需要的时候在创建对象,后边就不会再次创建
- @SpringBootApplication SpringBoot的核心注解 启动类
- @EnableAutoConfiguration 开启SpringBoot自动配置
- @RestController 在控制层 是@ResponseBody注解与@Controller注解的合集
- @RequestMapper 处理请求地址映射的注解
- @RequestParam 获取url上传过来的参数
- @Configuration 声明配置类
- @Component 通用注解
- @Service 业务逻辑层
char 字符型 byte 字节型 boolean 布尔型
float 单浮点型 double 双浮点型
int 整数型 short 短整数型 long 长整数型
- 可以用 limit
select
name,age,sexfrom t_student limit(0,5);
- 升序 order by xx asc
select
name,age,sexfrom t_student order byageasc;
- 降序 order by xx desc
select
name,age,sexfrom t_student order byagedesc;
- maven 专门构建和管理Java项目的工具
- maven的好处在于可以将项目过程规范化、自动化、高效化以及强大的可扩展性
- 创建索引 尽量避免全盘扫描 首先考虑在 where 和 order by 涉及的列上创建索引
- 避免在索引上使用计算 注意就是IN关键字不走索引,它是走全盘扫描
- 使用预编译防止 sql 注入
- 尽量将多条 SQL语句压缩到一条 SQL 语句中
- 最最最好的就是少用 * , 应该写成要查询的字段名,尽量避免在 where 条件中判断 null
- 尽量不用like 的前置百分比
- 对于连续的数值,能用 between 就不要用 in
- 在新建临时表时,如果一次性插入数据量较大.可以用 select into 代替 create table
- PRIMARY KEY (主键索引)
- 添加INDEX(普通索引) ALTER TABLE
table_nameADD INDEX index_name (column)- 添加FULLTEXT(全文索引) ALTER TABLE
table_nameADD FULLTEXT (column)- 添加多列索引 ALTER TABLE
table_nameADD INDEX index_name (column1,column2,column3)
MySQL 索引底层的实现方式是 B+Tree也就是B+树 具体查看 B+Tree实现方式
使用v-mode属性, 它的原理是利用了Object.defineProperty()方法重新定义了对象获取属性值(get)和设置属性值(set)的操作来实现的
- 采取先进先出模式,同一时间,消息只会发送给某一个消费者,只有当该消息被消费并告知已收到时,它才能在代理的存储中被删除.
- 对于持久性订阅来说,每一个消费者都会获取消息的拷贝.为了节约空间,代理的存储介质中只存储了一份消息,存储介质的持久订阅对象为其以后的被存储的消息维护了一个指针,消费者消费时,从存储介质中复制一个消息.消息被所有订阅者获取后才能删除.
基于文件的消息存储机制,为了提高消息存储的可靠性和可恢复性,它整合了一个事务日志.KahaDB拥有高性能和可扩展性等特点.由于KahaDB使用的是基于文件的存储,所以不需要使用第三方数据库
原文:https://www.cnblogs.com/beixuan/p/13381722.html