面试总结

下面介绍JAVA分布式锁的三种常用实现方式:
1.基于数据库实现分布式锁
要实现分布式锁,最简单的方式可能就是直接创建一张锁表,然后通过操作该表中的数据来实现了。当我们要锁住某个方法或资源时,我们就在该表中增加一条记录,想要释放锁的时候就删除这条记录.
具体操作就是在数据库中创建一个表,表中包含方法名等字段,并在方法名字段上创建唯一索引,想要执行某个方法,就使用这个方法名向表中插入数据,成功插入则获取锁,执行完成后删除对应的行数据释放锁。
上面这种简单的实现有以下几个问题:
1、这把锁强依赖数据库的可用性,数据库是一个单点,一旦数据库挂掉,会导致业务系统不可用。
2、这把锁没有失效时间,一旦解锁操作失败,就会导致锁记录一直在数据库中,其他线程无法再获得到锁。
3、这把锁只能是非阻塞的,因为数据的insert操作,一旦插入失败就会直接报错。没有获得锁的线程并不会进入排队队列,要想再次获得锁就要再次触发获得锁操作。
4、这把锁是非重入的,同一个线程在没有释放锁之前无法再次获得该锁。因为数据中数据已经存在了。
当然,我们也可以有其他方式解决上面的问题。
数据库是单点?搞两个数据库,数据之前双向同步。一旦挂掉快速切换到备库上。
没有失效时间?只要做一个定时任务,每隔一定时间把数据库中的超时数据清理一遍。
非阻塞的?搞一个while循环,直到insert成功再返回成功。
非重入的?在数据库表中加个字段,记录当前获得锁的机器的主机信息和线程信息,那么下次再获取锁的时候先查询数据库,如果当前机器的主机信息和线程信息在数据库可以查到的话,直接把锁分配给他就可以了。
优点:借助数据库,方案简单。
缺点:在实际实施的过程中会遇到各种不同的问题,为了解决这些问题,实现方式将会越来越复杂;依赖数据库需要一定的资源开销,性能问题需要考虑
2.基于Redis实现分布式锁
在Redis2.6.12版本之前,使用setnx命令设置key-value、使用expire命令设置key的过期时间获取分布式锁,使用del命令释放分布式锁,但是这种实现有如下一些问题:
setnx命令设置完key-value后,还没来得及使用expire命令设置过期时间,当前线程挂掉了,会导致当前线程设置的key一直有效,后续线程无法正常通过setnx获取锁,造成死锁。
出现这个问题是因为两个命令是分开执行并且不具备原子特性,如果能将这两个命令合二为一就可以解决问题了。在Redis2.6.12版本中实现了这个功能,Redis为set命令增加了一系列选项。也就是说现在set命令就可以实现分布式锁,下面我们来了解一下set命令(set(keyName, lockValue, “NX”, “EX”, expireSeconds)):

  1. SET命令是原子性操作,NX指令保证只要当key不存在时才会设置value
  2. 设置的value要有唯一性,来确保锁不会被误删(value=系统时间戳+UUID)
  3. 当上述命令执行返回OK时,客户端获取锁成功,否则失败
  4. 客户端可以通过redis释放脚本来释放锁(del 命令)
    5.如果锁到达了最大生存时间将会自动释放
    只有当前key的value和传入的value相同才会执行DEL命令。
    优点:高性能,借助Redis实现比较方便。
    缺点:线程获取锁后,如果处理时间过长会导致锁超时失效(失效时间我设置多长时间为好?如何设置的失效时间太短,方法没等执行完,锁就自动释放了,那么就会产生并发问题。如果设置的时间太长,其他获取锁的线程就可能要平白的多等一段时间。这个问题使用数据库实现分布式锁同样存在),所以,通过超时时间来控制锁的失效时间并不是十分的靠谱。
    3.基于Zookeeper实现分布式锁
    ZooKeeper是一个为分布式应用提供一致性服务的开源组件,它内部是一个分层的文件系统目录树结构,规定同一个目录下只能有一个唯一文件名。大致思想即为:每个客户端对某个方法加锁时,在zookeeper上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题。
    基于ZooKeeper实现分布式锁的步骤如下:
    创建一个目录mylock;
    线程A想获取锁就在mylock目录下创建临时顺序节点;
    获取mylock目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁;
    线程B获取所有节点,判断自己不是最小节点,设置监听比自己次小的节点;
    线程A处理完,删除自己的节点,线程B监听到变更事件,判断自己是不是最小的节点,如果是则获得锁。
    优点:具备高可用、可重入、阻塞锁特性,可解决失效死锁问题。具体说明如下:
    锁无法释放,造成死锁!使用Zookeeper可以有效的解决锁无法释放的问题,因为在创建锁的时候,客户端会在ZK中创建一个临时节点,一旦客户端获取到锁之后突然挂掉(Session连接断开),那么这个临时节点就会自动删除掉。其他客户端就可以再次获得锁。
    阻塞锁特性!使用Zookeeper可以实现阻塞的锁,客户端可以通过在ZK中创建顺序节点,并且在节点上绑定监听器,一旦节点有变化,Zookeeper会通知客户端,客户端可以检查自己创建的节点是不是当前所有节点中序号最小的,如果是,那么自己就获取到锁,便可以执行业务逻辑了。
    可重入!使用Zookeeper也可以有效的解决不可重入的问题,客户端在创建节点的时候,把当前客户端的主机信息和线程信息直接写入到节点中,下次想要获取锁的时候和当前最小的节点中的数据比对一下就可以了。如果和自己的信息一样,那么自己直接获取到锁,如果不一样就再创建一个临时的顺序节点,参与排队。
    单点问题?使用Zookeeper可以有效的解决单点问题,ZK是集群部署的,只要集群中有半数以上的机器存活,就可以对外提供服务。
    缺点:因为需要频繁的创建和删除节点,性能上不如Redis方式。

说hashmap工作原理
HashMap是基于hashing的原理,我们使用put(key, value)存储对象到HashMap中,使用get(key)从HashMap中获取对象。当我们给put()方法传递键和值时,我们先对键调用hashCode()方法,返回的hashCode用于找到bucket位置来储存Entry对象。”这里关键点在于指出,HashMap是在bucket中储存键对象和值对象,作为Map.Entry。
如果两个对象的hashcode相同会发生什么
两个对象hashcode相同,但它们可能并不相等。hashcode相同,所以它们的bucket位置相同,‘碰撞’会发生。因为HashMap使用LinkedList存储对象,这个Entry(包含有键值对的Map.Entry对象)会存储在LinkedList中。虽然有很多种处理碰撞的方法,这种方法是最简单的,也正是HashMap的处理方法。
Hashmap 的table的容量怎么确定 为什么不是0.9或者0.5
HashMap有两个参数影响其性能:初始容量和加载因子。容量是哈希表中桶的数量,初始容量只是哈希表在创建时的容量。加载因子是哈希表在其容量自动扩容之前可以达到多满的一种度量。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行扩容、rehash操作(即重建内部数据结构),扩容后的哈希表将具有两倍的原容量。
通常,加载因子需要在时间和空间成本上寻求一种折衷。
加载因子过高,例如为1,虽然减少了空间开销,提高了空间利用率,但同时也增加了查询时间成本;
加载因子过低,例如0.5,虽然可以减少查询时间成本,但是空间利用率很低,同时提高了rehash操作的次数。
在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地减少rehash操作次数,所以,一般在使用HashMap时建议根据预估值设置初始容量,减少扩容操作。
选择0.75作为默认的加载因子,完全是时间和空间成本上寻求的一种折衷选择,

简答说一下java内存的分配
. 寄存器:它是唯一位域处理器内部的存储区。所以它是最快的,数量也是极其有限的,并且和 C,C++不一样的是,Java寄存器是根据程序需求进行分配的,你不能控制、也不能向它“建议“分配方式。在 java 中寄存器对于程序员来说是感受不到其存在的。
. 栈:位于随机访问存储器(RAM)中,但通过堆栈指针可以从处理器获得直接支持 。它的运行速度仅次于寄存器。储存在它内部的项的生命周期对与栈来说必须是透明的,并根据其生命周期堆栈指针进行上下移动(向下分配内存,向上释放内存)。所以其灵活性不高,适合存储一些基本类型(如:boolean、byte、char、short、float、int、long、double)。它的声明方式的意思是:不是用 new 来创建变量,二是创建一个并非是引用的“自动”变量。这个变量直接存储“值”,并置于堆栈中。
堆:位于随机访问存储器(RAM)中,用于存放所有的 Java 对象(通过 new 关键字产生的都是对象,new 关键字的意思是“给我一个对象”=号左边为对象的一个引用)。它不需要知道存储的数据在堆中的生命周期,因而更加灵活。如果可以在栈中存储对象的话,堆进行存储分配和清理要比用栈花费更多的时间。
常量存储:由于常量永远不会改变,所以它通常存放在程序代码内部。在嵌入式系统中,常量本身会和其他部分隔离开,所以在这种情况下可以选择将常量放在只读存储器(ROM)中。
非RAM存储:通过将对象转化成可以存放在其他媒介(硬盘)上的事务,在需要时,它们可恢复成常规的、基于RAM的对象。如流对象和持久化对象。在 流对象 中,对象转换为字节流被发送给另一台机器。在 持久化对象 中,对象被存放于磁盘上。因此,即使程序终止,它们依然可以保持自己的状态。
Java堆结构
堆:堆是一种树,由它实现的优先级队列的插入和删除的时间复杂度都是O(logn),用堆实现的优先级队列虽然和数组实现相比较删除慢了些,但插入的时间快的多了。当速度很重要且有很多插入操作时,可以选择堆来实现优先级队列。
2.java的堆和数据结构堆:java的堆是程序员用new能得到的计算机内存的可用部分。而数据结构的堆是一种特殊的二叉树。
Springmvc的控制器 是不是单例模式 如果是有什么问题 怎么解决
默认情况下是单例模式,
在多线程进行访问的时候,有线程安全问题.
但是不建议使用同步,因为会影响性能.
解决方案,是在控制器里面不能写成员变量.

简单说一下ActiveMq
JMS体系结构
描述如下:
JMS提供者(JMS的实现者,比如activemq jbossmq等)
JMS客户(使用提供者发送消息的程序或对象,例如在12306中,负责发送一条购票消息到处理队列中,用来解决购票高峰问题,那么,发送消息到队列的程序和从队列获取消息的程序都叫做客户)
JMS生产者,JMS消费者(生产者及负责创建并发送消息的客户,消费者是负责接收并处理消息的客户)
JMS消息(在JMS客户之间传递数据的对象)
JMS队列(一个容纳那些被发送的等待阅读的消息的区域)
JMS主题(一种支持发送消息给多个订阅者的机制)
JMS对象模型
连接工厂(connectionfactory)客户端使用JNDI查找连接工厂,然后利用连接工厂创建一个JMS连接。
JMS连接 表示JMS客户端和服务器端之间的一个活动的连接,是由客户端通过调用连接工厂的方法建立的。
JMS会话 session 标识JMS客户端和服务端的会话状态。会话建立在JMS连接上,标识客户与服务器之间的一个会话进程。
JMS目的 Destinatio 又称为消息队列,是实际的消息源
生产者和消费者
消息类型,分为队列类型(优先先进先出)以及订阅类型
项目的权限如何设计 让你设计一个权限 大概有那些表
(1)、用户表(UserInfo):Id、UserName、UserPwd

(2)、角色表(RoleInfo):Id、RoleName

(3)、菜单表(MenuInfo):Id、MenuN3ame

(4)、用户角色表(UserRole):Id、UserId、RoleId

(5)、角色菜单表(RoleMenu):Id、RoleId、MenuId

设计思路:用户、角色、权限三大核心表,加上用户角色、角色权限两个映射表(用于给用户表联系上权限表)。这样就可以通过登录的用户来获取权限列表,或判断是否拥有某个权限。
为什么用ajax
(1)、最大的一点是页面无刷新,在页面内与服务器通信,给用户的体验非常好。
(2)、使用异步方式与服务器通信,不需要打断用户的操作,具有更加迅速的响应能力。
(3)、可以把以前一些服务器负担的工作转嫁到客户端,利用客户端闲置的能力来处理,减轻服器和带宽的负担,节约空间和宽带租用成本。并且减轻服务器的负担,ajax的原则是“按需取数据”可以最大程度的减少冗余请求,和响应对服务器造成的负担。
(4)、基于标准化的并被广泛支持的技术,不需要下载插件或者小程序。)
Ajax的xmlhttprequest对象

XMLHttpRequest可以使用js向服务器发送请求并处理相应,而不阻塞其他用户的操作,可以不刷新页面和服务器进行交互,减少用户等待时间,减轻服务器压力。

为什么使用springboot?他的核心配置文件都有哪些 区别是什么
简化配置、实现自动化配置
核心配置文件:application和bootstrap
区别:application文件主要用于springboot自动化配置
Bootstrap主要用于: 1、使用Spring Cloud Config 注册中心时要在bootstrap配置文件中添加链接到配置中心的配置属性来加载外部配置中心的配置信息
2.一些固定的不能被覆盖的属性0
3.加密和节目的场景

白名单怎么写的 在哪添加的
用户登录时从Redis中查询是否存在该ip 有进行下一步操作 没有查询到使用户的ip提示用户登录失败

过滤器如何实现的
1.判断request是否是一个httpServletRquest请求
2.转换请求的类型
3.获取要过滤的参数名称
4.判断参数是否为空或者跳过dispath(满足其一不进行过滤)
5.进行过滤
说一下mysql的索引
索引是用于快速找出在某个列中的特定的行
索引的优点:
1.所有的MySQL列类型(字段类型)都可以被索引
2.加快数据的查询速度
索引的缺点:
1.创建索引和维护索引要耗费时间,随着数据量的增加耗费时间也会增加
2.索引需要占空间
3.对表中的数据进行增加、删除、修改时,索引不便于数据维护
索引的类型:
单列索引
普通索引:
Mysql中基本索引类型,没有限制,允许在索引中插入重复值和空值,加快查询速度

唯一索引
索引列必须是唯一的,允许有空值
主键索引
特殊的索引,不允许有空值
组合索引
在表中多个子弹组合上创建索引,只有在查询条件中使用这些字段的左边字段时索引才会被使用 遵循左前缀集合

全文索引
必须要用MyISAM引擎,只能再char varchar text类型字段上使用
空间索引
空间索引是通过空间数据类型的字段建立的索引
MySql中的 空间数据类型有4种:GEOMETRY、POINT、LINESTRING、POYGON
在创建空间索引时要使用spatial关键字
引擎为MyISAM创建空间索引的列必须声明为not null

乐观锁和悲观锁的区别
首先说一下乐观锁和悲观锁,悲观锁,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会被阻塞挂起,直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁,读锁,写锁等,都是在做操作之前先上锁。乐观锁,就是很乐观,每次去拿数据的时候都认为别人不会修改数据,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,乐观锁适用于多读的应用类型,这样可以提高吞吐量。那么这两种锁的主要的区别是,乐观锁适用于写比较少的情况下,即冲突的很少发生的时候情况,这样就可以省去锁的开销,加大系统的整个吞吐量。但如果经常产生冲突,上层应用会不断的进行retry(重试),这样反倒是降低了性能,所以这种情况下用悲观锁就比较合适。

超键,候选键,主键,外键的区别
超键是在关系中能唯一标识元组的属性集称为关系模式的超键。
候选键是不含有多余属性的超键称为候选键。
主键是用户选作元组标识的一个候选键称为主键。
外键是在一个表中存在的另一个表的主键称为此表的外键。
数据库三范式
第一范式是原子性,即字段不可再分。
第二范式是唯一性,即一个表只说明一个事务。
第三范式每列都与主键有直接关系,不存在传递依赖。
Mybatis #和$的区别 为什么使用#不使用 $
#{}表示一个占位符号,标识sql的拼接,通过${}接收参数,将参数的内容不加任何修饰拼接在sql中。
标 识 s q l 的 拼 接 , 通 过 {}标识sql的拼接,通过 sql{}接收参数,将参数的内容不加任何修饰拼接在sql中。
因为使用${}会引起sql注入的安全问题,使用#{}则可以避免这个问题。
线上项目怎么实现的负载均衡
可以使用nginx做负载均衡,用户访问网站的时候首先会访问nginx服务器,然后nginx服务器再从服务器集群中选择压力较小的服务器,将该访问请求引向该服务器。
常见的负载均衡算法
1.轮询:将请求按顺序轮流分配到后台服务器上。
2.随机:通过系统随机函数,根据后台服务器列表的大小值来随机选取其中一台进行访问。
3.源地址哈希法:通过哈希函数计算得到一个哈希值,将此哈希值和服务器列表的大小进行取模运算,得到的结果便是要访问的服务器地址的序号。
4.加权轮询:跟配置高、负载低的机器分配更高的权重,使其能处理更多的请求,而配置低、负载高的机器,则给其分配较低的权重,降低其系统负载,加权轮询很好的处理了这一问题,并将请求按照顺序且根据权重分配给后端。
5.加权随机:加权随机法跟加权轮询法类似,根据后台服务器不同的配置和负载情况,配置不同的权重。
6.最小连接数法:根据后端服务器当前的连接情况,动态的选取其中当前积压连接数最少的一台服务器来处理当前请求,尽可能的提高后台服务器利用率,将负载合理的分流到每一台服务器。

  1. 一致性 hash 算法 一致性怎么说的
    https://www.cnblogs.com/jingangtx/p/11338592.html
    写一个 快排 冒泡排序优化
    快排逻辑:先从数列中取出一个数作为基数,
    将比这个数大的数放在右边,小的放在左边,直到顺序从小到大为止
    /**
    快速排序快速排序采用了分治策略。就是在一个数组中取一个基准数字,把小的数放基准的左边,大的数放基准的右边。
    基准左边和右边分别是新的序列。在新的序列中再取一个基准数字,小的放左边,大的放右边。
    这个里面用到的递归。我们需要三个参数,一个是数组,另外两个是序列的边界
    */
    public static class QuickSort {

    void sort(int num[], int left, int right) {
    if (left < right) {
    int index = partition(num, left, right); //算出枢轴值
    sort(num, left, index - 1); //对低子表递归排序
    sort(num, index + 1, right); //对高子表递归排序
    }
    }

    /**

    • 调用partition(num,left,right)时,对num[]做划分,
    • 并返回基准记录的位置
      */
      public int partition(int[] num, int left, int right) {
      if (num == null || num.length <= 0 || left < 0 || right >= num.length) {
      return 0;
      }
      int prio = num[left + (right - left) / 2]; //获取数组中间元素的下标
      while (left <= right) { //从两端交替向中间扫描
      while (num[left] < prio)
      left++;
      while (num[right] > prio)
      right–;
      if (left <= right) {
      swap(num, left, right); //最终将基准数归位
      left++;
      right–;
      }
      }
      return left;
      }

    public void swap(int[] num, int left, int right) {
    int temp = num[left];
    num[left] = num[right];
    num[right] = temp;
    }

    public static void main(String args[]) {
    int[] num = {7, 3, 5, 1, 2, 8, 9, 2, 6};
    new QuickSort().sort(num, 0, num.length - 1);
    for (int n : num) {
    System.out.print(n + " ");
    }
    }
    }
    冒泡排序:
    1.比较相邻的元素,如果没有第二个大就交换位置。
    2.每个相邻的元素做同样操作直到执行到最后一个元素
    3.直到没有任何一对数字需要比较
    int[] n = new int[]{1,6,3,8,33,27,66,9,7,88};
    int temp;
    for (int i = 0; i < n.length-1; i++) {
    for (int j = 0; j <n.length-1; j++) {
    if(n[j]>n[j+1]){
    temp = n[j];
    n[j] = n[j+1];
    n[j+1] = temp;
    }
    }
    }
    优化方案:
    优化方案一:为了避免待排序数组已经有序后,再次进行的冗余排序,加入flag标签,一旦数组有序,立即跳出循环。
    优化方案二: 随着循环的进行,后半部分逐渐变成了有序数组,因此定义有序边界border,作为内层比较循环的限制条件,示例代码如下:
    优化方案三:鸡尾酒排序(Cocktail Sort)(又名:双向冒泡排序 (Bidirectional Bubble Sort)、波浪排序 (Ripple Sort)、摇曳排序 (Shuffle Sort)、飞梭排序 (Shuttle Sort) 和欢乐时光排序 (Happy Hour Sort)
    原理:此算法以双向进行排序,鸡尾酒排序等于是冒泡排序的轻微变形

和传统冒泡的比较:不同的地方在于从低到高然后从高到低,而冒泡排序每次都是从低到高去比较序列里的每个元素。可以得到比冒泡排序稍微好一点的效能,原因是冒泡排序只能从一个方向进行比对,每次循环只移动一个项目

项目中主要负责哪些模块
了解所负责项目模块的流程,逻辑,实现原理。
硬件怎么对接
经理讲
车联网项目的报文重复提交
车辆启动发送报文以及车辆vin码,sleep一个小的随机时间,判断vin码出现次数,短时间出现多次,进行拦截请求,确保第一次请求正常运行。
主要负责那一块 做什么
了解所负责项目模块的流程,逻辑,实现原理。
用户输入了一些特殊字符 保存不到数据库 怎么解决
解决办法是将字段改成NChar,NVarchar等类型,在入库的时候每个字符串插入前都加入一个N,如N’lily’、N’Male’,兼容性会更好,作为Nvarchar字段,这是一种推荐做法!
内存泄漏的问题 oom 如何排查解决
什么是OOM? OOM,全称“Out Of Memory”,翻译成中文就是“内存用完了”,来源于java.lang.OutOfMemoryError。看下关于的官方说明: Thrown when the Java Virtual Machine cannot allocate an object because it is out of memory, and no more memory could be made available by the garbage collector. 意思就是说,当JVM因为没有足够的内存来为对象分配空间并且垃圾回收器也已经没有空间可回收时,就会抛出这个error(注:非exception,因为这个问题已经严重到不足以被应用处理)。

1)分配的少了:比如虚拟机本身可使用的内存(一般通过启动时的VM参数指定)太少。

2)应用用的太多,并且用完没释放,浪费了。此时就会造成内存泄露或者内存溢出。

内存泄露:申请使用完的内存没有释放,导致虚拟机不能再次使用该内存,此时这段内存就泄露了,因为申请者不用了,而又不能被虚拟机分配给别人用。
内存溢出:申请的内存超出了JVM能提供的内存大小,此时称之为溢出。

在之前没有垃圾自动回收的日子里,比如C语言和C++语言,我们必须亲自负责内存的申请与释放操作,如果申请了内存,用完后又忘记了释放,比如C++中的new了但是没有delete,那么就可能造成内存泄露。偶尔的内存泄露可能不会造成问题,而大量的内存泄露可能会导致内存溢出。

而在Java语言中,由于存在了垃圾自动回收机制,所以,我们一般不用去主动释放不用的对象所占的内存,也就是理论上来说,是不会存在“内存泄露”的。但是,如果编码不当,比如,将某个对象的引用放到了全局的Map中,虽然方法结束了,但是由于垃圾回收器会根据对象的引用情况来回收内存,导致该对象不能被及时的回收。如果该种情况出现次数多了,就会导致内存溢出,比如系统中经常使用的缓存机制。Java中的内存泄露,不同于C++中的忘了delete,往往是逻辑上的原因泄露。

六.内存泄露
内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。
memory leak会最终会导致out of memory!
内存溢出就是你要求分配的内存超出了系统能给你的,系统不能满足需求,于是产生溢出。 内存泄漏是指你向系统申请分配内存进行使用(new),可是使用完了以后却不归还(delete),结果你申请到的那块内存你自己也不能再访问(也许你把它的地址给弄丢了),而系统也不能再次将它分配给需要的程序。
以发生的方式来分类,内存泄漏可以分为4类:

  1. 常发性内存泄漏。发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。
  2. 偶发性内存泄漏。发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。
  3. 一次性内存泄漏。发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块仅且一块内存发生泄漏。比如,在类的构造函数中分配内存,在析构函数中却没有释放该内存,所以内存泄漏只会发生一次。
  4. 隐式内存泄漏。程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。
    .Jdk1.8对hashmap做了什么改变
    1.Jdk1.8中没有indexFor函数,直接使用table[index = (n – 1) & hash](与运算交换左右,结果不变)。其中table数组为HashMap解决哈希冲突的数组+链表法中的数组。
    2.旧版本的HashMap存在一个问题,即使负载因子和Hash算法设计的再合理,也免不了会出现拉链过长的情况,一旦出现拉链过长,则会严重影响HashMap的性能。于是,在JDK1.8版本中,对数据结构做了进一步的优化,引入了红黑树。而当链表长度太长(TREEIFY_THRESHOLD默认超过8)时,链表就转换为红黑树,利用红黑树快速增删改查的特点提高HashMap的性能(O(logn))。当长度小于(UNTREEIFY_THRESHOLD默认为6),就会退化成链表。
  5. 经过观测可以发现,HashMap使用的是2次幂的扩容(指长度扩为原来2倍)。所以,元素的位置要么是在原位置,要么是在原位置再移动2次幂的位置。看下图可以明白这句话的意思,n为table的长度,图(a)表示扩容前的key1和key2两种key确定索引位置的示例,图(b)表示扩容后key1和key2两种key确定索引位置的示例,其中hash1是key1对应的哈希与高位运算(即hash()函数)结果。
    

扩容后,table长度(n)变为原来两倍,即n由 0000 0000 0000 0000 0000 0000 0001 0000变为 0000 0000 0000 0000 0000 0000 0010 0000,因此n-1由 0000 0000 0000 0000 0000 0000 0000 1111变为 0000 0000 0000 0000 0000 0000 0001 1111。
元素在重新计算hash之后,因为n变为2倍,那么n-1的mask范围在高位多1bit(红色),因此新的index就会发生这样的变化:

   因此,我们在扩充HashMap的时候,不需要像JDK1.7的实现那样重新计算hash,只需要看看原来的hash值新增的那个bit是1还是0就好了,是0的话索引没变,是1的话索引变成“原索引+oldCap”。(每个节点e的hash早就计算好,并保存在final hash中)。通过if ((e.hash & oldCap) == 0)判定前面那个bit是不是1,如果是1则加上oldCap。

static class Node<K,V> implements Map.Entry<K,V> {}
jdk1.8中用Node替代了Entry。
Currenthashmap的简单介绍
主要就是为了应对hashmap在并发环境下不安全而诞生的,ConcurrentHashMap的设计与实现非常精巧,大量的利用了volatile,final,CAS等lock-free技术来减少锁竞争对于性能的影响。
我们都知道Map一般都是数组+链表结构(JDK1.8该为数组+红黑树)。ConcurrentHashMap避免了对全局加锁改成了局部加锁操作,这样就极大地提高了并发环境下的操作速度
Hashmap为什么使用异或运算符
保证了对象的 hashCode 的 32 位值只要有一位发生改变,整个 hash() 返回值就会改变。尽可能的减少碰撞。
Hash碰撞会发生什么问题
对象Hash的前提是实现equals()和hashCode()两个方法,那么HashCode()的作用就是保证对象返回唯一hash值,但当两个对象计算值一样时,这就发生了碰撞冲突
JAVA内存分配
Java 程序运行时,需要在内存中分配空间。为了提高运算效率,就对空间进行了不同区域的划分,因为每一片区域都有特定的处理数据方式和内存管理方式。

一、栈:储存局部变量
· 局部变量:在方法的定义中或者在方法声明上的变量称为局部变量。
· 特点:栈内存的数据用完就释放。
二、堆:储存 new 出来的东西
· 特点:
· 每一个 new 出来的东西都有地址值;
· 每个变量都有默认值 (byte, short, int, long 的默认值为 0;float, double 的默认值为 0.0;char 的默认值为 “\u0000”;boolean 的默认值为 false;引用类型为 null);
· 使用完毕就变成垃圾,但是并没有立即回收。会有垃圾回收器空闲的时候回收。
三、方法区:
一个对象的运行过程:

  1. 程序从 main 方法中进入;运行到 Phone p 时,在栈中开辟了一个空间;
  2. new Phone() 时,在队中开了一个内存空间,此时会有一个内存值为 0x0001;此时会找到对应的 Phone 的 class 文件,发现有三个变量和三个方法,于是将三个成员变量放在了堆中,但是此时的值为默认值(具体默认值见上)。注意,在方法区里也有一个地址值,假设为 0x001,可以认为在堆中也有一个位置,在堆中的位置,可以找到方法区中相对应的方法;
  3. 继续运行,p.brand = “三星”;将三星赋值给 p.brand,通过栈中的 p 找到了堆中的 brand,此时的 null 值变为“三星”。剩下的类似;
  4. 当运行到 p.call(“乔布斯”) 时,通过栈中的 p 找到堆中存在的方法区的内存地址,从而指引到方法区中的 Phone.class 中的方法。从而将 call 方法加载到栈内存中,注意:当执行完毕后,call 方法就从栈内存中消失!剩余的如上。
  5. 最后,main 方法消失!
    两个对象的运行过程:
  6. 程序从 main() 方法进入,运行到 Phone p 时,栈内存中开内存空间;
  7. new Phone() 时,在队中开了一个内存空间,内存值为 0x0001;此时会找到对应的 Phone 类,发现有三个变量,于是将三个成员变量放在了堆中,但是此时的值为默认值。又发现该类还存在方法,于是将该方法的内存值留在了堆中,在方法区里也有一个地址值,假设为 0x001,这个值与堆中的值相对应;
  8. 程序继续运行,到 p.brand 时,进行了负值,同上;
  9. 当程序运行到 Phone p2 时;到 new Phone() 时,在堆内存中开辟了内存空间 0x0002,赋值给 Phone p2;
  10. 剩下跟一个对象的内存相同。
    三个对象的运行过程:
  11. 基本流程跟前两个无差别;
  12. 但是当运行到 Phone p3 时,在栈内存中分配了一个空间,然后将 p1 的内存赋值给了 p3,即此时 Phone p3 的内存是指向 0x0001 的;
  13. 继续给变量赋值,会将原来已经赋值的变量给替换掉。
    四、本地方法区(和系统相关)
    五、寄存器(给 CPU 使用)

什么是类加载器
Java类加载器是Java运行时环境的一部分,负责动态加载Java类到Java虚拟机的内存空间中。类通常是按需加载,即第一次使用该类时才加载。由于有了类加载器,Java运行时系统不需要知道文件与文件系统。学习类加载器时,掌握Java的委派概念很重要。
类加载器它是在虚拟机中完成的,负责动态加载Java类到Java虚拟机的内存空间中,在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码,并转换成 java.lang.Class类的一个实例。
类加载器的双亲委派机制
双亲委派机制:

如上图箭头所示,除了启动类加载器,每个类加载器都有一个父加载器。构成了一个类加载器结构系统。这个系统就是用来完成双亲委派机制的。

观察loadClass源码可以明白,一个类加载器加载一个类时,首先会把加载动作委派给他的父加载器,如果父加载器无法完成这个加载动作时才由该类加载器进行加载。由于类加载器会向上传递加载请求,所以一个类加载时,首先尝试加载它的肯定是启动类加载器(逐级向上传递请求,直到启动类加载器,它没有父加载器),之后根据是否能加载的结果逐级让子类加载器尝试加载,直到加载成功。

比如我们自己写了一个com.test.T1类,假设我们没有自定义类加载器,那么这个类会由应用程序类加载器加载。应用程序类加载器加载时先把加载任务委派给扩展类加载器(父加载器),然后扩展类加载器再把加载任务委托给启动类加载器(父加载器),启动类加载器没有父加载器。于是它自己尝试加载,结果发现T1类并不在自己的记载类路径之中,于是告诉扩展类加载器(子加载器)自己无法加载该类。这时扩展类只好自己加载这个类,结果发现也无法加载该类,于是它也告诉应用程序类加载器这个消息,这时应用程序类加载器自己进行T1类的加载动作,加载成功。

可以把每个类加载都想成一个大懒汉,每次让他办事时他都让爸爸代办。没想到爸爸也是个大懒汉,于是爸爸也让他的爸爸代办。这是到了爷爷那里,爷爷也很懒,但是他没有爸爸了,于是只能一边抱怨一边干,然后发现自己做不了,又骂骂咧咧的把活儿交给了自己的儿子,然后爸爸开始干活,发现自己也不能完成这个任务,于是他也是骂骂咧咧的把活交给了儿子,儿子挨了一顿骂,然后开始干活,经过了1小时的苦干,这个活儿终于完事了。

双亲委派机制的存在意义:
双亲委派是Java语言的一大创新。表明看起来,由于双亲委派机制的存在,类加载器的数量增多了不少,增加了程序的复杂性。不过存在既有道理。双亲委派机制让Java类体系变得稳定,有层次性能。特定的类由特定的类加载器加载,每次加载都委托父类的过程让类对象在内存中的数量保持为一个,让同类名的类无法被替换。

如果没有双亲委派机制,只有一个类加载器,我们自己写一个java.lang.Object类,也可以被加载,结果就是内存中有两个Object类,引用的时候会造成安全的问题。而且一些核心的类可能会被替换,导致重大的安全问题。

有了双亲委派机制,我们自己写的java.lang.Object类在加载时会被加载器委托给父加载器,在某一个父加载器中发现内存中已经存在了java.lang.Object类,那么将不会进行加载,这样就保证了特定的类只能有一个在内存中。

什么是gc
Java GC(Garbage Collection,垃圾收集,垃圾回收)机制,是Java与C++/C的主要区别之一,在使用JAVA的时候,一般不需要专门编写内存回收和垃圾清理代 码。这是因为在Java虚拟机中,存在自动内存管理和垃圾清扫机制。
电脑的内存大小的不变的,当我们使用对象的时候,如使用New关键字的时候,就会在内存中生产一个对象,但是我们在使用JAVA开发的时候,当一个对象使用完毕之后我们并没有手动的释放那个对象所占用的内存,就这样在使用程序的过程中,对象越来越多,当内存存放不了这么多对象的时候,电脑就会崩溃了,JAVA为了解决这个问题就推出了这个自动清除无用对象的功能,或者叫机制,这就是GC,有个好听是名字叫垃圾回收,其实就在用来帮你擦屁股的,好让你安心写代码,不用管内存释放,对象清理的事情了。

如何判断一个对像是否存活
引用计数法:该种方法是每一个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时表示没有引用,则代表该对象可以回收。这种方法简单,但是无法解决对象相互循环引用的问题。
可达性分析:该种方法是从GC Roots开始向下搜索,搜索所走过得路径为引用链。当一个对象GC Roots没有任何引用链时,则证明此对象是不可用的,表示可以回收

垃圾回收器可以主动回收吗?
可以。程序员可以手动执行System.gc(),通知GC运行,但是Java语言规范并不保证GC一定会执行。
Java中的内存分配是随着new一个新的对象来实现的,这个很简单,而且也还是有一些可以“改进”内存回收的机制的,其中最显眼的就是这个System.gc()函数。
乍一看这个函数似乎是可以进行垃圾回收的,可事实并不是那么简单。
其实这个gc()函数的作用只是提醒虚拟机:程序员希望进行一次垃圾回收。但是它不能保证垃圾回收一定会进行,而且具体什么时候进行是取决于具体的虚拟机的,不同的虚拟机有不同的对策。
那么下一个问题就是:gc()进行回收的准则是什么?也就是说什么样的对象可以被回收?
简单来说就是:没有被任何可达变量指向的对象。

深克隆和浅克隆
克隆的对象可能包含一些已经修改过的属性,保留着你想克隆对象的值,而新new出来的属性是一个全新的对象,对应属性没有纸,所以我们还要重新给这个对象赋值。即地需要一个新对象来保存当前对象的“状态”就靠clone方法
克隆(复制)在Java中是一种常见的操作,目的是快速获取一个对象副本。克隆分为深克隆和浅克隆。
浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,扔指向原有属性所指向的对象的内存地址。
深克隆:创建一个新对象,属性中引用的其它对象也会被克隆,不在指向原有对象地址。
总之深浅克隆都会在堆中新分配一块区域,区别在于对象属性引用的对象是否需要进行克隆(递归性的)
对象实现Cloneable并重写clone方法不进行任何操作时,调用clone是进行的浅克隆,而使用对象流将对象写入流然后再读出是进行深克隆
Java中对象什么时候可以垃圾回收 怎么定义 使用频率不高

  1. 引用计数器算法
    引用计数器算法:
    系统给每个对象添加一个引用计数器,每当有一个地方引用这个对象的时候,计数器就加1,当引用失效的时候,计数器就减1,在任何一个时刻计数器为0的对象就是不可能被使用的对象,因为没有任何地方持有这个引用,这时这个对象就被视为内存垃圾,等待被虚拟机回收
    优点:客观的说,引用计数器算法,他的实现很简单,判定的效率很高,在大部分情况下这都是相当不错的算法
    缺点:无法循环使用;比如说 A对象的一个属性引用B,B对象的一个属性同时引用A A.b = B() B.a = A(); 这个A,B对象的计数器都是1,可是,如果没有其他任何地方引用A,B对象的时候,A,B对象其实在系统中是无法发挥任何作用的,既然无法发挥作用,那就应该被视作内存垃圾予以清理掉,可是因为此时A,B的计数器的值都是1,虚拟机就无法回收A,B对象,这样就会造成内存浪费,这在计算机系统中是不可容忍的.

可达性分析算法:
这种算法通过一系列成为 "GC Roots " 的对象作为起始点,从这些节点开始向下搜索所有走过的路径成为引用链(Reference Chain) , 当一个对象GC Roots没有任何引用链相连(用图论的话来说就是从GC Roots到这个对象不可达),则证明此对象是不可用的
优点
这个算法可以轻松的解决循环引用的问题
大部分的主流java虚拟机使用的都是这种算法

Spring为什么使用代理模式
Spring通过使用代理,可以简化暴露的façade,从而更容易被调用程序使用,通过动态代理,可以对目标类加入通知或者拦截器,从而可以提供切面功能,或者提供灵活的可配置的参数,参考spring的声明式事物管理部分
讲一下 aop和 的 oop 对比
AOP是解决横向的问题
OOP是解决竖向的问题

Jdk动态代理和cglib代理 的区别
Java动态代理是利用反射机制生成一个实现代理接口匿名类,在调用具体方法前调用invokenHandler来处理。而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理

  1.   如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
    
  2.   如果目标对象实现了接口,可以强制使用CGLIB实现AOP
    
  3.   如果目标对象没有实现接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换
    

Spring框架中基于aop实现事务管理
事物有四大特性:ACID

  1.   原子性:事物是一个原子操作,有一系列动作组成。原子性保证所有动作都完成,或者不执行任何动作
    
  2.   一致性:一旦事物完成(不论成败),系统必须确保它所建模的业务处于一致的状态。
    
  3.   隔离性:可能有很多事物会同时处理相同的数据,因此每个事物都应该与其他事物隔离开来,防止数据损坏。
    
  4.   持久性:一旦事物完成,无论系统发生什么系统错误,它的结果都不会受到影响,保证能从系统崩溃中恢复过来,通常事物的结果会被写入到持久化存储器中。
    

Springioc的单例模式和高级特性
ioc单列:
如果一个bean被声明为单列的时候,在处理多次请求的时候,在spring容器里只实例化出一个bean,后续的请求都公用这个对象,这个对象会保存在一个map里面,当有请求来的时候,会先冲缓存(map)里查看有没有,有的话直接使用这个对象,没有的话才实例化一个新的对象,所以这个单列的。

ioc高级特性:
Lookuo方法注入 (在大部分的应用场景中,容器的大部分bean是singleton类型的。当一个单列bean需要和灵位一个单列bean协作时,或者一个非单列bean要引用另一个非单列bean时,通常情况下将一个bean定义为另一个bean的属性值就行了。不过对于具有不同生命周期的bean来说这样做就会有问题了,比如在调用一个单列类型bean A的某个方法,需要引用另一个非单列(prototype)类型bean B,对于bean A来说,容器只会创建一次,这样就没法在需要的时候每次让容器为bean A 提供一个新的bean实例。)

Springmvc 的工作原理
用户发送请求由前置控制器DispatcherServlet来决定哪一个页面的控制器进行处理并把请求委托给它,在由HandlerMappering将请求映射为HandlerExecutionChain对象(包含Handler处理器对象,多个HandlerInterceptor对象即拦截器),在返回给DispatcherServlet,DispatcherServlet再次发送请求给HandlerAdapter,HandlerAdapter将处理器包装为适配器,调用处理器相应功能处理方法,Handler返回ModelAnView给HandlerAdapter,HandlerAdapter发送DispatcherServlet进行视图的解析,VuewResolver将逻辑视图解析为具体的视图,返回给DispatcherServlet,在进行视图的渲染,返回给DispatcherServlet,最后通过DispatcherServlet将视图返回给用户。

Springmvc如何重定向和转发
请求转发是服务器内的跳转

  1.   地址栏不发生变化、
    
  2.   只有一个请求、
    
  3.   可以通过reqquest域传递数据
    

请求重定向是浏览器自动发起对跳转目标的请求

  1.   地址栏会发生变化
    
  2.   两次请求响应
    
  3.   无法通过request域传递对象
    
  4. Mysql的基本索引类型
    Mysql目前主要有以下几种索引类型:FULLTEXT,HASH,BTREE,RTREE。

  5. FULLTEXT
    即为全文索引,目前只有MyISAM引擎支持。其可以在CREATE TABLE ,ALTER TABLE ,. CREATE INDEX 使用,不过目前只有 CHAR、VARCHAR ,TEXT 列上可以创建全文索引。
    全文索引并不是和MyISAM一起诞生的,它的出现是为了解决WHERE name LIKE “%word%"这类针对文本的模糊查询效率较低的问题。

  6. HASH
    由于HASH的唯一(几乎100%的唯一)及类似键值对的形式,很适合作为索引。
    HASH索引可以一次定位,不需要像树形索引那样逐层查找,因此具有极高的效率。但是, 这种高效是有条件的,即只在“=”和“in”条件下高效,对于范围查询、排序及组合 索引仍然效率不高。

  7. BTREE
    BTREE索引就是一种将索引值按一定的算法,存入一个树形的数据结构中(二叉树), 每次查询都是从树的入口root开始,依次遍历node,获取leaf。这是MySQL里默认和 最常用的索引类型。

  8. RTREE
    RTREE在MySQL很少使用,仅支持geometry数据类型,支持该类型的存储引擎只有 MyISAM、BDb、InnoDb、NDb、Archive几种。

  9. 读写分离

insert/update/delete语句操作一台服务器,select操作另一个服务器
从库生成两个线程,一个I/O线程,一个SQL线程;
i/o线程去请求主库 的binlog,并将得到的binlog日志写到relay log(中继日志) 文件中;
主库会生成一个 log dump 线程,用来给从库 i/o线程传binlog;
SQL 线程,会读取relay log文件中的日志,并解析成具体操作,来实现主从的操作一致,而最终数据一致

  1. Springboot的核心配置文件和他们的区别

配置文件:SpringBoot的核心配置文件有application和bootstarp配置文件。

区 别:
① application文件主要用于Springboot自动化配置文件。
② bootstarp文件主要有以下几种用途:使用Spring Cloud Config注册 中心 时 需要在bootStarp配置文件中添加链接到配置中心的配置属性来 加载外 部配置中心的配置信息。
一些固定的不能被覆盖的属性
一些加密/解密的场景

  1. Springboot的核心注解 主要由那几个注解组成
    核心注解是:@SpringBootApplication
    组 成:
    1.@SpringBootConfiguration:它组合了Configuration注解实现了 配置文件的功能。
    2.@EnableAutoConfiguration:打开自动配置功能,也可以关闭某个指定的自动配置选项 如关闭数据源自动配置功能:
    @SpringBootApplication(exclude{ DataSourceAutoConfiguration.class })。
    3.@ComponentScan:Spring扫描组件。

  2. 开启springboot的特性有哪几种方式
    1)继承spring-boot-starter-parent项目
    2)导入spring-boot-dependencies项目依赖

  3. Springboot2.0有什么新特性 和1.0有什么区别

SpringBoot新特性
https://www.cnblogs.com/lizm166/p/10272541.html
Springboot2.0和1.0有什么区别
https://blog.csdn.net/zhiquanzhou/article/details/80566630

  1. http协议 uri和url的区别.

URI:统一资源标识符,是一类通用的资源标识符,URL实际上是URI的子集,URI是一个通用的概念,URI有两种形式来实现对资源的统一标识:URL和URN;由于URL大量使用,URN基本不用,我们往往把URI和URL不加区分的使用

55.如何去自己去学习新技术 新项目如何上手 如何开始
答:去学习新技术
是什么? 1、首先我们学习一门新的技术,很多时候是因为业务需要,这个时候可以通过各种渠道获取到一个名词,也就是能够解决我们业务需求的技术。然后去查这个技术是什么,用百度去找资料。如果英文好的话,可以去翻翻官方文档。 2、查询同类技术,然后对比同类技术,根据业务需求及以后扩展,选择一个比较稳定并且适合自己的技术,不要追新,因为有时候有很多坑,我们没有那么多精力去踩,尤其是对于比较急的项目
为什么出现? 1、一门新的技术的出现肯定是因为现有的技术解决不了,或者不能很好的解决目前的问题,就像Spring框架的出现,是为了解决ejb依赖过重和复杂的问题。类似这样的很多
怎么做? 这个是关键点 1、一般是baidu查资料,官网找demo,简单看下运行起来,看效果 2、查找项目中遇到的坑,然后进行项目实战,实践过程中也要看下相关的技术底层实现以及注意点,还有各个版本之间的区别,一般选择最新稳定版本。
分享? 接下来就是写博客,和同事朋友讨论,这样才能更深入的进入了解。 博客不光是给别人看,也是给自己,不同的时间看同一个东西理解是不一样的,并且很多时候过一段时间,我们就会遗忘了之前的知识,用的时候翻下博客,是最快速有效的复习方式。
新项目如何上手
1、向同事问清楚当前的开发环境,而且现今的发展,要区分64位和32位
2、搞明白当前项目的运行环境,如果是多项目的话,要搞清楚依赖关系
3、让同事帮忙搞定本机可运行项目的环境
4、当环境都弄好、项目能启动后,开始代码之旅,也是最重要的一步。   
a) 看配置。通看一遍配置文件,了解当前项目用了哪些框架,做到心中有数   
b) 学业务(重点*)。了解各页面间的跳转及异步请求,写一个临时Filter,拦截/*的所有请求,在doFilter()方法中,打印出每次请求的路径( System.out.println(((HttpServletRequest)   request).getServletPath()); ),根据每次跳转路径寻找对应的处理类,了解每一步的业务需求。
c) 读代码。备份项目,根据业务逻辑逐行读代码,读过的代码加上自己的注释。  
d)写备注。当任何文档都没有的情况下,多搞一套本机可以运行的环境,在读代码的同时,为自己的项目加注释,为本机的数据库及其他数据存储方式中的结构增加字段备注。 5、要文档,不过不要抱太大希望。不过有文档总好过没有,任谁也不喜欢两眼一抹黑的感觉,当然,没有文档就只能自己为自己写注释,加备注了。

56.用的什么消息中间件 技术选型为什么rabbitmq 选型的依据
答:用的RabbitMq,
RabbitMQ优点:RabbitMQ结合erlang语言本身的并发优势,性能较好,社区活跃度也比较高,但是不利于做二次开发和维护。不过,RabbitMQ的社区十分活跃,可以解决开发过程中遇到的bug。如果你的数据量没有那么大,小公司优先选择功能比较完备的RabbitMQ。
同时rabbitmq采用信道通信。不采用tcp直接通信。
A. tcp的创建和销毁开销大,创建三次握手,销毁四次握手。
B. 高峰时成千上万条的链接会造成资源的巨大浪费,而且操作系统没秒处理tcp的数量也是有数量限制的,必定造成性能瓶颈
C. 一条线程一条信道,多条线程多条通道,公用一个tcp链接。一条tcp连接可以容纳无限条信道(硬盘容量足够的话)不会造成性能瓶颈。

57.怎么保证消息中间件的高可用 避免系统故障
答:以RabbitMq为例,最好采用镜像集群模式,跟普通集群模式不一样的是,你创建的queue,无论元数据还是queue里的消息都会存在于多个实例上,然后每次你写消息到queue的时候,都会自动把消息到多个实例的queue里进行消息同步。这样的话,好处在于,你任何一个机器宕机了,没事儿,别的机器都可以用。
镜像模式:基于普通模式集群,在策略里面添加镜像策略即可。把需要的队列做成镜像队列,属于HA方案。该模式解决了普通模式中的问题,根本区别在于消息实体会主动在镜像节点间同步,而不是在客户端拉取数据时临时拉取。当然弊端就是降低系统性能,大量消息写入,集群内的网络宽带会被大大消耗。

58.如何去保证投递出的消息 不会出现重复
答:其实还是得结合业务来思考,我这里给几个思路:
A. 比如你拿个数据库要写库,你先根据主键查一下,如果这数据都有了,你就别插了,update一下就好了
B. 比如你是写redis,那没问题了,反正每次都是set,天然幂等性。
C. 比如你不是上面两个场景,那做的稍微复杂一点,你需要让生产者发送每条数据的时候,里面加一个全局唯一的id,类似订单id之类的东西,然后你这里消费到了之后,先根据这个id去redis里查一下,之前消费过吗?如果没有消费过,你就处理,然后这个id写redis。如果消费过了,那你就别处理了,保证别重复处理相同的消息即可。

59.快速排序
答:快速排序是C.R.A.Hoare于1962年提出的一种划分交换排序。它采用了一种分治的策略,通常称其为分治法(Divide-and-ConquerMethod)。
它的基本思想是:
A.先从数列中取出一个数作为基准数。
B分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。 C.再对左右区间重复第二步,直到各区间只有一个数。
package com.company;
public class Test1 {
//#low 开始时最左边的索引=0
//#high 开始时最右边的索引=arr.length-1
public static void quickSort(int[] arr,int low,int high){
int i,j,temp,t;
if(low>high){
return;
}
i=low;
j=high;
//temp就是基准位
temp = arr[low];

    while (i<j) {
        while (temp<=arr[j]&&i<j) {
            j--;
        }

        //再看左边,依次往右递增
        while (temp>=arr[i]&&i<j) {
            i++;
        }

        //如果满足条件则交换
        if (i<j) {

            int z = arr[i];
             int y = arr[j];

            arr[i] = y;
            arr[j] = z;

        }

    }

    //最后将基准为与i和j相等位置的数字交换
    arr[low] = arr[i];
    arr[i] = temp;

    //递归调用左半数组
    quickSort(arr, low, j-1);
    //递归调用右半数组
    quickSort(arr, j+1, high);
}

public static void main(String[] args){
    int[] arr = {10,7,2,4,7,100,3,4,2,1,8,9,19};
    quickSort(arr, 0, arr.length-1);
    for (int i = 0; i < arr.length; i++) {
        System.out.print(arr[i]);
    }
}

}

package com.company;
public class Test2 {
/**
* 快速排序
* 快速排序采用了分治策略。就是在一个数组中取一个基准数字,把小的数放基准的左边,大的数放基准的右边。
* 基准左边和右边分别是新的序列。在新的序列中再取一个基准数字,小的放左边,大的放右边。
* 这个里面用到的递归。我们需要三个参数,一个是数组,另外两个是序列的边界
*
* @author HJS
*/
public static class QuickSort {

    void sort(int num[], int left, int right) {
        if (left < right) {
            int index = partition(num, left, right); //算出枢轴值
            sort(num, left, index - 1);    //对低子表递归排序
            sort(num, index + 1, right);    //对高子表递归排序
        }
    }

    /**
     * 调用partition(num,left,right)时,对num[]做划分,
     * 并返回基准记录的位置
     *
     * @param num
     * @param left
     * @param right
     * @return
     */
    public int partition(int[] num, int left, int right) {
        if (num == null || num.length <= 0 || left < 0 || right >= num.length) {
            return 0;
        }
        int prio = num[left + (right - left) / 2];  //获取数组中间元素的下标
        while (left <= right) {         //从两端交替向中间扫描
            while (num[left] < prio)
                left++;
            while (num[right] > prio)
                right--;
            if (left <= right) {
                swap(num, left, right);    //最终将基准数归位
                left++;
                right--;
            }
        }
        return left;
    }


    public void swap(int[] num, int left, int right) {
        int temp = num[left];
        num[left] = num[right];
        num[right] = temp;
    }

    public static void main(String args[]) {
        int[] num = {7, 3, 5, 1, 2, 8, 9, 2, 6};
        new QuickSort().sort(num, 0, num.length - 1);
        for (int n : num) {
            System.out.print(n + " ");
        }
    }
}

}

60.负责哪些模块
答:在车联网项目,负责了国家平台中的电池回收管理模块;这个模块主要是通过国家平台来实时地展示回收的电池情况,在此过程会给电池厂和车辆厂商提供数据调用接口,对方按照约定好的请求地址、请求参数和返回值类型格式来进行电池回收数据的交互与上报。
在数据清洗项目中,负责了jdbc数据源设计模块,这个模块相当于是提供了一个数据库连接池的作用,对各种数据库连接进行管理。jdbc数据源设计模块主要是把相关功能封装成一个工具类以供其他模块调用,里面包含了创建jdbc的连接方法、通过key来获取容器中连接的方法、校验连接的方法和关闭流的方法。

  1. Hashmap数组扩容的过程
    创建一个新的数组,其容量为旧数组的两倍,并重新计算旧数组中结点的存储位置。结点在新数组中的位置只有两种,原下标位置或原下标+旧数组的大小。

  2. Hashmap中put方法的过程
    1、执行hash(Object key)得到hash值,再判断table是否为空,为空表明这是第一个元素插入,则先resize,初次大小默认16。
    2、若不需要初始化,则判断要插入结点的位置是否为空,也就是没有产生Hash地址冲突,是则直接放入table。
    3、否则产生了冲突,那么有两种情况:key相同,key不同。
    4、如果p是TreeNode的实例,说明p下面是红黑树,需要在树中找到一个合适的位置插入。
    5、p下面的结点数未超过8,则以单向链表的形式存在,逐个往下判断:①如果下一个位为空,插入,并且判断当插入后容量超过8则转化成红黑树,break。②如果下一个位有相等的hash值,则覆盖,break。
    6、判断新插入这个值是否导致size已经超过了阈值,是则进行扩容。
    详细:https://www.cnblogs.com/LinsenLi/p/9765337.html

  3. 对红黑树的一个见解
    红黑树(Red-Black Tree,简称R-B Tree),它一种特殊的二叉查找树。
    红黑树是特殊的二叉查找树,意味着它满足二叉查找树的特征:任意一个节点所包含的键值,大于等于左孩子的键值,小于等于右孩子的键值。
    除了具备该特性之外,红黑树还包括许多额外的信息。
    红黑树的每个节点上都有存储位表示节点的颜色,颜色是红(Red)或黑(Black)。
    红黑树的特性:
    1.每个节点或者是黑色,或者是红色。
    2.根节点是黑色。
    3.每个叶子节点是黑色。 [注意:这里叶子节点,是指为空的叶子节点!]
    4.如果一个节点是红色的,则它的子节点必须是黑色的。
    5.从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。

  4. Hashmap在jdk1.8为什么使用内置锁 来代替重入锁
    ①、粒度降低了;
    ②、JVM 开发团队没有放弃 synchronized,而且基于 JVM 的 synchronized 优化空间更大,更加自然。
    ③、在大量的数据操作下,对于 JVM 的内存压力,基于 API 的 ReentrantLock 会开销更多的内存。

  5. B+树索引和 hash索引的区别
    首先要知道Hash索引和B+树索引的底层实现原理:
    hash索引底层就是hash表,进行查询时,调用一次hash函数就可以获取到相应的键值,之后进行回表查询获得实际数据.
    B+树底层实现原理是多路平衡查找树,对于每一次的查询都是从根节点出发,查询到叶子节点方可以获得所查键值,然后查询判断是否需要回表查询.
    区别:
    hash索引:
    1.hash索引进行等值查询更快(一般情况下)但是却无法进行范围查询.因为在hash索引中经过hash函数建立索引之后,索引的顺序与原顺序无法保持一致,不能支持范围查询.
    2.hash索引不支持模糊查询以及多列索引的最左前缀匹配,因为hash函数的不可预测,eg:AAAA和AAAAB的索引没有相关性.
    3.hash索引任何时候都避免不了回表查询数据.
    4.hash索引虽然在等值上查询叫快,但是不稳定,性能不可预测,当某个键值存在大量重复的时候,发生hash碰撞,此时查询效率可能极差.
    5.hash索引不支持使用索引进行排序,因为hash函数的不可预测.
    B+树:
    1.B+树的所有节点皆遵循(左节点小于父节点,右节点大于父节点,多叉树也类似)自然支持范围查询.
    2.在符合某些条件(聚簇索引,覆盖索引等)的时候可以只通过索引完成查询.不需要回表查询.
    3.查询效率比较稳定,对于查询都是从根节点到叶子节点,且树的高度较低.

Hash索引和B+树索引有什么区别或者说优劣势?
https://blog.csdn.net/qq_44590469/article/details/97877397
6. InnerDB如何解决幻读
表象:快照读(非阻塞读),伪MVCC
底层:next-key(行锁+Gap锁)
a. 在RU、RC隔离级别下不存在Gap锁,所以在RU、RC隔离级别下无法解决幻读;在RR、Serializable隔离级别下都实现了Gap锁,所以解决了幻读现象。
b. 在RR隔离级别下,如果删、改、查语句的where条件走的是主键索引或者唯一索引
i. where条件全部命中,则给该记录加上记录锁。
ii. where条件不全部命中,则给该记录周围加上Gap锁。
iii. 加上记录锁或者是Gap锁都是为了防止RR隔离级别下发生幻读现象。
c. 在RR隔离级别下,如果删、改、查语句的where条件没有走索引或者是非唯一索引或非主键索引
在当前读where条件如果没有走非唯一索引或者没有走索引,则会使用Gap锁锁住当前记录的Gap,防止幻读的发生

SERIALIZABLE 级别则是悲观的认为幻读时刻都会发生,故会自动的隐式的对事务所需资源加排它锁,其他事务访问此资源会被阻塞等待,故事务是安全的,但需要认真考虑性能。

详细:https://segmentfault.com/a/1190000016566788#item-3

Mysql的隔离级别
1.未提交读(read-uncommitted)
在一个事物中,可以读取到其他事物未提交的数据变化,这种读取其他会话还没提交的事物,叫做脏读现象,再生产环境中切勿使用。
2.已提交读(read-committed)
在一个事物中,可以读取到其他事物已经提交的数据变化,这种读取也叫做不可重复读,因为两次同样的查询可能会得到不一样的结果。
3.可重复读(repetable-read)
MYsql默认隔离级别,在一个事物中,知道事物结束前,都可以反复读取到是无内刚开始时看待的数据,并一直不会发生变化,避免了脏读、不可重复读现象,但是他还是无法结局幻读的问题
4.可串行化(serializable)
这是最高的隔离级别,它强制事物串行执行,避免了前面说的幻读现象,简单来说,它会在读取的每一行数据上都加锁,所以可能会导致大量的超时和锁争用问题

项目在高并发场景下如何保证数据的一致性
读写分离+延迟双删
基本原理:
①所有的数据读取操作全从缓存中读取
②所有的数据写操作全部以关系数据的save结果为准
③写数据进入数据库前先删除缓存,写完后再删除一次缓存
操作步骤:
①先删除缓存
②再写入数据库
③休眠500毫秒
④再次删除缓存
(方案利弊:这个方案简单清晰,易于理解,在绝大部分的场景下能满足性能和数据库一致性的合理要求,频繁写和频繁读的场景下会增加缓存写入的耗时,极端情况会存在缓存击穿,即每次都不要不能在缓存中命中,都要直接访问数据库)

读写分离 和分库分表
什么是读写分离:读写分离,基本原理是让主数据库处理事物的增、改、删除操作,而从数据库处理查询操作。
为什要用读写分离:因为数据库在“写”操作时比较耗时,但是数据库在“读”操作的非常快,所以要读写分离,解决数据的写入,影响查询效率。
什么时候用读写分离:当数据库读远大于写操作时,查询多的情况下,从数据库负责读操作,一主多从,从而把数据读写分离,还可以结合redis等缓存来配合分担数据库读操作,大大降低数据库的压力

	分库分表:就是把存于一个库的数据分散到多个库中,把存于一个表的数据分散到多个表中。
	什么时候用到分库分表:创建一个数据库,随着时间推移,数据库中的表以及表中的数据会越来越多,可能会出现两种弊端:(1)数据库的存储资源是有限的,其负载能力也是有限的,数据大量累积会导致处理数据能力下降;(2)数据越多,对数据的增删改查操作的开销越大;这两种情况会用到分库分表
	分库分表的方式:
		1、垂直切分
			适用场景:如果因为表的个数多而让数据多,可以按照功能划分,把密切的表分出来放在一个库中(分库);如果表字段太多,可以以列为出发点,将字段进行拆分(分表)
		2、水平切分
			适用场景:如果是因为表中的数据庞大,可以用水平拆分,按照某种约定好的规则将数据切分到不同的数据库

Springmvc中的控制器用到的注解
@Controller
@RequesMapping
@Resource和@Autowired
@ModelAttribute和SessionAttributes
@PathVariable
@RequestParam
@ResponseBody
@Component
@Repository
如何把特定的请求映射到特定的方法中
通过@Controller、@RequestMapping 等注解实现
给你一个长度为n的数组,其中只有一个数字出现了奇数次,其他均出现偶数次,问如何使用优秀的时空复杂度快速找到这个数字

方法1:
最基本的方法就是双重循环遍历数组,可以直接找出只出现一次的数,但是,显然这种方法时间复杂度高,效率低下。
方法2:
使用异或运算符^,0与其他数字异或的结果是哪个数字,相等的数字异或得0.要操作的数组中除了某个数字只出现一次之外,其他数字都出现了两次,所以可以定义一个变量赋初始值为0,用这个变量与数组中每个数字做异或运算,并将这个变量值更新为那个运算结果,直到数组遍历完毕,最后得到的变量的值就是数组中出现了一次的数字了,这种方法只需遍历一次数组,提高了程序运行的效率。

冒泡排序的时间复杂度
O(n²)
Springboot需要独立的容器运行吗

SpringBoot 不需要独立的容器就可以运行,应为在SpringBoot工程发布的jar
文件里已经包含了Tomcat的jar文件。SpringBoot运行的时候。会创建Tomcat
对象,实现web服务功能。也可以将SpringBoot发布成war文件,放到tomcat
里运行。

Jvm加载class文件的原理机制
JVM中类的封装是由类加载器(ClassLoader)和它的子类来实现的,Java中的类加载器是一个重要的Java运行时系统组件,他负责在运行时查找和装入类文件中的类。
由于Java的跨平台性,经过编译的Java源程序并不是一个可执行程序,而是一个或多个类文件。当Java程序需要使用某个类时,JVM会确保这个类已经被加载、连接(验证、准备和解析)和初始化。累的加载是指把类的.class文件中的数据读入到内存中,通常是创建一个字节数组读入.class文件,然后产生与所加载类对应的Class对象。加载完成后,Class对象还不完整,所以此时的类还不可用。当类被加载后就进入连接阶段,这一阶段包括验证、准备(为静态变量分配内存并设置默认的初始值)和解析(将符号引用替换为直接引用)三个步骤。最后JVM对类进行初始化,包括:1)如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类;2)如果类中存在初始化语句,就依次执行这些初始化语句。
类的加载石油类加载器完成的,类加载器包括:根加载器(BootStrap)、扩展加载器(Extension)、系统加载器(System)和用户自定义类加载器(java.lang.ClassLoader的子类)。从Java 2(JDK 1.2)开始,类加载过程采取了父亲委托机制(PDM)。PDM更好的保证了Java平台的安全性,在该机制中,JVM自带的Bootstrap是根加载器,其他的加载器都有且仅有一个父类加载器。类的加载首先请求父类加载器加载,父类加载器无能为力时才由其子类加载器自行加载。JVM不会向Java程序提供对Bootstrap的引用。下面是关于几个类加载器的说明:
Bootstrap:一般用本地代码实现,负责加载JVM基础核心类库(rt.jar);
Extension:从java.ext.dirs系统属性所指定的目录中加载类库,它的父加载器是Bootstrap;
System:又叫应用类加载器,其父类是Extension。它是应用最广泛的类加载器。它从环境变量classpath或者系统属性java.class.path所指定的目录中记载类,是用户自定义加载器的默认父加载器。

安全访问策略 为什么这么设计
保证数据交互安全性
数据清洗是用来干嘛的 为什么不用开源的 要自己开发
这个项目是公司内部开发的项目,数据迁移时一种将离线存储与在线存储融合的技术,主要的作用是为了方便公司的DBA(数据库管理员),因为现在需要把那些辅助的数据库移入核心数据库,而且当时公司的数据库很复杂,比如说mysql、Oracle、文件等大量的数据文件,对于这些文件仅仅靠公司的DBA(数据库管理员)用存储过程去完成的话耗时十分巨大,所以需要有第三方来辅助完成这些工作。
自己开发的无上限只供本公司使用,外界无法使用。
开源的已上限可供所以人使用。

什么是存储过程 有哪些优缺点

存储过程是实现经过编译并存储在数据库中的一段SQL语句的集合,调用存储
过程可以简化应用开发人员的很多工作,减少数据在数据库和应用服务器之间的
传输,对于提高数据处理的效率是很有好处的。
优点:

  1. 重复使用:存储过程可以重复使用,从而可以减少数据库开发人员的工作量。
  2. 减少网络流量:存储过程位于服务器上,调用的时候只需要传递存储过程的名称以及产生就可以了,因此降低了网络传输的数据量。
  3. 安全性:参数化的存储过程可以防止SQL注入式攻击,而且可以将Grant 、
    Deny以及Revoke权限应用于存储过程。
    缺点:
  4. 更改比较繁琐:如果更改范围大到需要对输入存储过程的参数进行更改,或者要更改由其返回的数据,则仍需要更新程序集中的代码以添加参数、更新GetValue()调用,等等,这时候估计比较繁琐。
  5. 可移植性差:由于存储过程将应用程序绑定到SQLServer,因此使用存储过程封装业务逻辑将限制应用程序的可移植性。如果应用程序的可移植性在您的环境中非常重要,则需要将业务逻辑封装在不特定于RDBMS的中间层中。

80.Requestbody和requestparam 在传输的时候有什么区别

RequestParam:Http协议中,如果不指定Content-Type,则默认传递的参数就是application/x-www-form-urlencoded类型RequestParam可以接受简单类型的属性,也可以接受对象类型。
Requestbody:处理HttpEntity传递过来的数据,一般用来处理非Content-Type: application/x-www-form-urlencoded编码格式的数据。GET请求中,因为没有HttpEntity,所以@RequestBody并不适用。
POST请求中,通过HttpEntity传递的参数,必须要在请求头中声明数据的类型Content-Type,SpringMVC通过使用HandlerAdapter 配置的HttpMessageConverters来解析HttpEntity中的数据,然后绑定到相应的bean上。
在GET请求中,不能使用@RequestBody。
在POST请求,可以使用@RequestBody和@RequestParam,但是如果使用@RequestBody,对于参数转化的配置必须统一

81.Hashmap拉链法导致的链表过剩 为什么不用 二X树代替 什么的

https://blog.csdn.net/a544879146/article/details/71122725

82.进程和线程的区别
根本区别:进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位
在开销方面:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。

所处环境:在操作系统中能同时运行多个进程(程序);而在同一个进程(程序)中有多个线程同时执行(通过CPU调度,在每个时间片中只有一个线程执行)

内存分配方面:系统在运行的时候会为每个进程分配不同的内存空间;而对线程而言,除了CPU外,系统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),线程组之间只能共享资源。

包含关系:没有线程的进程可以看做是单线程的,如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。
83.对线程安全的理解
用专业的描述是,当多个线程访问一个对象时,如果不用进行额外的同步控制或其他的协调操作,调用这个对象的行为都可以获得正确的结果,我们就说这个对象是线程安全的

84.造成死锁的原因有哪些 如何预防
造成死锁的原因有如下几条(需同时满足条件):
1、互斥条件:任务使用的资源中至少有一个是不能共享的,资源的使用和释放方法都使用了synchronized关键字修饰
2、至少有一个任务它必须持有一个资源并且这个任务正在等待获取另一个当前正在被别的任务持有的资源
3、资源不能被项目抢占,任务必须把资源释放当做普通事件,资源只能被释放后才能被其他任务获取到
4、必须有循环等待,这时一个任务等待其他任务释放资源,其他任务又在等待另一个任务释放资源,且直到最后有一个任务在等待第一个任务释放资源,使得大家都被锁住,就造成了死锁
那如何避免死锁的情况发生:
1、加锁顺序,线程按照一定的顺序枷锁
2、加锁时限,线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并主动释放自己已经占有的锁,可以考虑使用lock.trylock(timeout)来代替使用内部锁机制
3、避免一个线程同时获取多个锁
4、避免一个线程在锁内同时占用多个资源
5、对于数据库锁,加锁和解锁必须在一个数据库连接里,否则有可能出现解锁失败的情况
85.Springboot的自动配置原理
SpringBoot启动的时候加载主配置类,开启了自动配置功能@EnableAutoConfiguration。
@EnableAutoConfiguration的作用是利用AutoConfigurationImportSelector给容器中导入一些组件。
可以查看public String[] selectImports(AnnotationMetadata annotationMetadata)方法的内容。
通过protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes)获取候选的配置,这个是扫描所有jar包类路径下"META-INF/spring.factories";
然后把扫描到的这些文件包装成Properties对象。
从properties中获取到EnableAutoConfiguration.class类名对应的值,然后把他们添加在容器中。
整个过程就是将类路径下"META-INF/spring.factories"里面配置的所有EnableAutoConfiguration的值加入到容器中。

86.Springboot实现线上热部署有几种方式
直接替换war包就可以了

  1. Springboot如何兼容老的spring项目
    通过使用@ImportResource注解导入就配置文件(注解写在启动类)
    88.Spring2.x和1.x有什么区别 有什么新特性
    https://www.cnblogs.com/lizm166/p/10272541.html
    (1)基于java8,支持Java9
    也就是说Spring Boot2.0的最低版本要求为JDK8,据了解国内大部分的互联网公司系统都还跑在JDK1.6/7上,因此想要升级到Spring Boot2.0的同学们注意啦,同时支持了Java9,也仅仅是支持而已。
    (2)响应式编程
    使用 Spring WebFlux/WebFlux.fn提供响应式 Web 编程支持, Webflux 是一个全新的非堵塞的函数式 Reactive Web 框架,可以用来构建异步的、非堵塞的、事件驱动的服务,在伸缩性方面表现非常好,此功能来源于Spring5.0。
    Spring Boot2.0也提供对响应式编程的自动化配置,如:Reactive Spring Data、Reactive Spring Security 等
    (3)HTTP/2支持
    在Tomcat, Undertow 和 Jetty 中均已支持 HTTP/2
    (4)全新的执行器Quartz
    全新的执行器架构,支持 Spring MVC, WebFlux 和 Jersey
    (5) 依赖组件的更新
    Jetty 9.4,Tomcat 8.5,Flyway 5,Hibernate 5.2,Gradle 3.4,Thymeleaf 3.0
    89.token是怎么设计的
    用用户信息加密生成的

90.简单说springboot
(1)独立运行
Spring Boot而且内嵌了各种servlet容器,Tomcat,Jetty等,现在不再需要打成war包部署到容器中,Spring Boot只要打成一个替换的jar包就能独立运行,所有的依赖包都在一个罐包内。
(2)简化配置
spring-boot-starter-web启动器自动依赖其他组件,简少了maven的配置。
自动配置
(3)Spring Boot能根据当前类路径下的类,jar包来自动配置bean,如添加一个spring-boot-starter-web启动器可以拥有web的功能,无需其他配置。
(3)无代码生成和XML配置
Spring Boot配置过程中无代码生成,也无需XML配置文件即可完成所有配置工作,这一切都是嵌套条件注解完成的,这也是Spring4.x的核心功能之一。
(4)应用监控
Spring Boot提供双向端点可以监控服务及应用,做健康检测。
91.Springboot怎么解决跨域
(1)注解 :@CrossOrigin注解
(2)使用接口编程的方式进行统一配置:
创建WebConfigurer,实现WebMvcConfigurer,重写addCorsMappings默认实现:
https://segmentfault.com/a/1190000019550329

92.微服务项目中如何实现session共享
(1)通过组播的方式进行集群间的共享
(2)零NFS多呢高一些共享存储来共享Session数据
(3)利用Memcache来存储共享session
(4)完全用cookie
http://www.imooc.com/wenda/detail/485979
93.Springboot如何实现线上热部署
(1) 模板引擎:
(2) Spring Loaded
(3) JRebel 收费的热部署软件,只需安装插件即可
(4) SpringBoot Devtools
https://blog.csdn.net/qq_29645505/article/details/94592527

  1. Springboot打成的jar包和普通jar包有什么区别
    Spring Boot的项目终止以jar包的形式进行打包,这种jar包可以通过可以通过命令(java -jar xxx.jar)来运行的,这种jar包不能被其他项目 所依赖,即使被依赖了也不能直接使用其中的类。普通的jar包,解压后直接就是包名,包里就是我们的代码,而 Spring Boot 打包成的可 执行 jar 解压后,在 \BOOT-INF\classes 目录下才是我们的代码,因此无法被直接引用。如果非要引用,可以在 pom.xml 文件中增加配置, 将 Spring Boot 项目打包成两个 jar ,一个可执行,一个可引用。
  2. Jvm的内存结构他们的对象分配规则
    JVM内存结构主要有三大块:堆内存、方法区和栈。
    1、对象优先分配在Eden区,如果Eden区没有足够的空间时,虚拟机执行一次Minor GC。
    2、大对象直接进入老年代(大对象是指需要大量连续内存空间的对象)。这样做的目的是避免在Eden区和两个
    Survivor区之间发生大量的内存拷贝(新生代采用复制算法收集内存)。
    3、长期存活的对象进入老年代。虚拟机为每个对象定义了一个年龄计数器,如果对象经过了1次Minor GC那么对象会进入Survivor区,之后每经过一次Minor GC 那么对象的年龄加1,知道达到阀值对象进入老年区。
    4、动态判断对象的年龄。如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代。
    5、空间分配担保。每次进行Minor GC时,JVM会计算Survivor区移至老年区的对象的平均大小,如果这个值大于老年区的剩余值大小则进行一次Full GC,如果小于 检查HandlePromotionFailure设置,如果true则只进行Monitor GC,如果false则进行Full GC。
  3. Jvm加载class文件的原理机制
    第一步:加载,双亲委派:启动类加载器(jre/lib),系统扩展类加载器(ext/lib),应用类加载器(classpath),前者为c++编写,所以系统加载器的parent为空,后面两个类加载器都是通过启动类加载器加载完成后才能使用。加载的过程就是查找字节流,可以通过网络,也可以自己在代码生成,也可以来源一个jar包。另外,同一个类,被不同的类加载器加载,那么他们将不是同一个类,java中通过类加载器和类的名称来界定唯一,所以我们可以在一个应用成存在多个同名的类的不同实现。

第二步:链接:(验证,准备,解析) 验证主要是校验字节码是否符合约束条件,一般在字节码注入的时候关注的比较多。准备:给静态字段分配内存,但是不会初始化,解析主要是为了将符号引用转换为实际引用,可能会触发方法中引用的类的加载。

第三步:初始化,如果赋值的静态变量是基础类型或者字符串并且是final的话,该字段将被标记为常量池字段,另外静态变量的赋值和静态代码块,将被放在一个叫cinit的方法内被执行,为了保证cinit方法只会被执行一次,这个方法会加锁,我们一般实现单例模式的时候为保证线程安全,会利用类的初始化上的锁。 初始化只有在特定条件下才会被触发,例如new 一个对象,反射被调用,静态方法被调用等。

  1. Java对象的创建过程
    1.分配空间。要注意的是,分配空间不光是分配子类的空间,子类对象中包含的父类对象所需要的空间,一样在这一步统一分配。在分配的空间的时候,会把所有的属性设置为默认值。
    2.递归的构造父类对象。
    3.初始化本类属性。
    4.调用本类的构造方法。

  2. Java对象的结构
    对象头,示例数据,对齐填充

  3. Mysql的数据引擎有哪些有什么区别
    最常使用的2种存储引擎:
    1.Myisam是Mysql的默认存储引擎,当create创建新表时,未指定新表的存储引擎时,默认使用Myisam。每个MyISAM在磁盘上存储成三个文件。文件名都和表名相同,扩展名分 别是.frm(存储表定义)、.MYD(MYData,存储数据)、.MYI(MYIndex,存储索引)。数据文件和索引文件可以放置在不同的目录,平均分布io,获得更快的速度。
    2.InnoDB存储引擎提供了具有提交、回滚和崩溃恢复能力的事务安全。但是对比Myisam的存储引擎,InnoDB写的处理效率差一些并且会占用更多的磁盘空间以保留数据和索引。

  4. 对事务的理解

将多条sql语句操作看作为一个整体
举个例子:我向你转账,这其中包括了俩个步骤。(1)从我的账户上扣钱(2)你的账户上加钱。俩个步骤其中任意一个有可能出现问题。例如:扣钱成功,增加钱的操作没有成功,程序抛异常了,数据库服务器炸了。。。看到这里时,你应该理解了为何把SQL操作看做一个整体,如果其中任意一步出错,整个转账的过程应该是不成功的。

  1. Springboot的工作原理 如何启动
    http://www.yund.tech/zdetail.html?type=1&id=2b2a032bd98dfeb5e4113ef6624722d2
  2. Springboot读取配置的方式

1.读取application文件
在application.yml或者properties文件中添加:
info.address=USA
info.company=Spring
info.degree=high
@Value注解读取方式
@ConfigurationProperties注解读取方式
2. 读取指定文件
资源目录下建立config/db-config.properties:
db.username=root
db.password=123456
@PropertySource+@Value注解读取方式(@PropertySource不支持yml文件读取。)
注入Environment读取方式

  1. 前后端分离是怎么测试
    Postman
  2. Appkey怎么设计的
    自定义md5加密生成的
  3. 如何保证投递的消息 只有一条 不会重复
    我在程序中开了两个线程:一个是主线程用来显示和交互,另外一个是监听线程,用来检测输入的变化。

由于输入的数据非常多,为了防止数据丢失,我使用了两个缓冲区。首先用一个接收数据,当收到任何一个数据以后就向主线程投递一个消息,主线程处理这个消息时将该缓冲区交给主线程分析,而通知监听线程用另外一个缓冲区取数。当主线程处理完毕以后,通过投递消息告诉监听线程,这样监听线程就可以将主线程处理期间所收到的数据再次投递一个消息,重复上述的步骤。

可是这里需要解决消息投递的问题PostMessage异步方式中,当收到数据后,监听线程需要检查主线程的消息队列中是否存在之前投递的消息,如果存在则不能够重复投递消息。否则由于数据变化太快,导致消息队列中全部都是这些消息,不能够正常处理别的事件了,甚至于当外界数据停止变化时,主线程仍然在不停的处理之前投递的消息。
106. 如果消费了重复的数据 如何保证消息的

(1)、可在内存中维护一个set,只要从消息队列里面获取到一个消息,先查询这个消息在不在set里面,如果在表示已消费过,直接丢弃;如果不在,则在消费后将其加入set当中。
(2)、如何要写数据库,可以拿唯一键先去数据库查询一下,如果不存在在写,如果存在直接更新或者丢弃消息。
(3)、如果是写redis那没有问题,每次都是set,天然的幂等性。
(4)、让生产者发送消息时,每条消息加一个全局的唯一id,然后消费时,将该id保存到redis里面。消费时先去redis里面查一下有么有,没有再消费。
(5)、数据库操作可以设置唯一键,防止重复数据的插入,这样插入只会报错而不会插入重复数据。

107、sql调优
创建索引
(1) .要尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引
(2) 在经常需要进行检索的字段上创建索引
(3) 一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有 必要。
避免在索引上使用计算
(1) 在where字句中,如果索引列是计算或者函数的一部分,DBMS的优化器将不会使用索引而使用全表查询,函数
属于计算的一种,同时在in和exists中通常情况下使用EXISTS,因为in不走索引
效率低:select * from user where salary*22>11000(salary是索引词)
效率高:select * from user where salary>11000/22(salary是索引词)
使用预编译查询
(1) 程序中通常是根据用户的输入来动态执行SQL,这时应该尽量使用参数化SQL,这样不仅可以避免SQL注入漏洞 攻击,这样以后再执行这个SQL的时候就直接使用预编译的结果,这样可以大大提高执行的速度。
调整Where字句中的连接顺序
(1) DBMS一般采用自下而上的顺序解析where字句,根据这个原理表连接最好写在其他where条件之前,那些可以 过滤掉最大数量记录。
尽量将多条sql语句压缩到一句sql中
(1) 每次执行SQL的时候都要建立网络连接、进行权限校验、进行SQL语句的查询优化、发送执行结果,这个过程 是非常耗时的
用where字句替换HAVING字句:
(1) 避免使用HAVING字句,因为HAVING只会在检索出所有记录之后才对结果集进行过滤,而where则是在聚合前 刷选记录,如果能通过where字句限制记录的数目,那就能减少这方面的开销
使用表的别名:这样就可以减少解析的时间并减
用union all替换union
(1) 当SQL语句需要union两个查询结果集合时,即使检索结果中不会有重复的记录,union同样会尝试进行合并,然后在输出最终结果前进行排序,所以在判断检索结果中不会有重复的记录时候,应 该用union all
考虑使用“临时表”暂存中间结果
(1) 可以避免程序中多次扫描主表,也大大减少了程序执行中“共享锁”阻塞“更新锁”,减少了阻塞,提高了并发性能。
只在必要的情况下才使用事务begin translation
(1) Begin tran使用的原则是,在保证数据一致性的前提下,begin tran 套住的SQL语句越少越好!有些情况下可以采用触发器同步数据,不一定要用begin tran。
尽量避免使用游标
用varchar/nvarchar 代替 char/nchar
因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。

查询select语句优化
(1) 任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段
(2) 应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描
(3) like尽量不要前置百分号
(4) 对于连续的数值,能用 between 就不要用 in 了

更新Update语句优化
(1) 如果只更改1、2个字段,不要Update全部字段,否则频繁调用会引起明显的性能消耗,同时带来大量日志
插入Insert语句优化
(2) 1.在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,以提高速度

108、游标
游标是一个存储在mysql服务器上的数据库查询,它不是一条SELECT语句,而是被该语句检索出来的结果集。把游标当作一个指针,它可以指定结果中的任何位置,然后允许用户对指定位置的数据进行处理。
109、Mysql的索引
类型:
1、普通索引:最基本的索引,没有任何限制
2、唯一索引:与普通索引类似,但是索引列的值必须唯一
3、主键索引:以主键进行索引
4、组合索引:指多个字段上创建索引
5、全文索引:主要用来查找文本中的关键字,而不是直接与索引中的值相比较
110、视图
1、视图是一种虚表
2、视图建立在已有表的基础上,视图依赖以建立的这些表称为基表
3、向视图提供数据内容的语句为select语句,可以将视图理解为存储起来的 select语句
4、视图向用户提供基表数据的另一种表现形式
5、视图没有存储真正的数据,真正的数据还是存储在基表中
6、程序员虽然操作的是视图,但最终视图还会转成操作基表
7、一个基表可以有0个或者多个视图
111、TCP三次握手
为了建立TCP连接,通信双方必须从对方了解如下信息:
1、对方报文发送的开始序号
2、对方发送数据的缓冲区大小
3、能被接受的最大报文长度MSS
4、被支持的TCP特选
在TCP协议中,通信双方将通过三次TCP报文核对以上的信息,并在此基础上建立一个TCP连接。
第一次握手:
建立连接时,客户端发送syn包到服务器,并进入syn_sent状态,等待服务器确认。syn:同步序列号
第二次握手:
服务受到syn包,必须确认客户的syn,同时自己也发送一个syn包,是syn+ack包,此时服务器进入syn-recv状态
第三次握手:
客户端收到服务器的syn+ack包,向服务器发送确认包ack,此包发送完毕,客户端和服务器计入established(TCP连接成功)状态,完成三次握手。
112、元数据
元数据是关于数据的数据,当人们描述现实世界的现象时,就会产生抽象信息,这些抽象信息便可以看作是元数据,元数据主要用来描述数据上下文信息。通俗的来讲,假若图书馆的每本书中的内容是数据的话,那么找到每本书的索引则是元数据,元数据可以帮助人们更好的理解数据 ,发现和描述数据的来龙去脉。
112.项目中学到了什么:
简述项目中的技术亮点 或者 业务亮点 感觉做完该项目之后 自己的代码能力 业务能力 项目理解能力 逻辑能力 等等各方面能力 说一下
113.spring cloud中配置文件的几种格式:
spring cloud config
application
bootstrap
114.springmvc 的流程:
第一步:发起请求到前端控制器(DispatcherServlet)
第二步:前端控制器请求HandlerMapping查找 Handler (可以根据xml配置、注解进行查找)
第三步:处理器映射器HandlerMapping向前端控制器返回Handler,HandlerMapping会把请求映射为HandlerExecutionChain对象(包含一个Handler处理器(页面控制器)对象,多个HandlerInterceptor拦截器对象),通过这种策略模式,很容易添加新的映射策略
第四步:前端控制器调用处理器适配器去执行Handler
第五步:处理器适配器HandlerAdapter将会根据适配的结果去执行Handler
第六步:Handler执行完成给适配器返回ModelAndView
第七步:处理器适配器向前端控制器返回ModelAndView (ModelAndView是springmvc框架的一个底层对象,包括 Model和view)
第八步:前端控制器请求视图解析器去进行视图解析 (根据逻辑视图名解析成真正的视图(jsp)),通过这种策略很容易更换其他视图技术,只需要更改视图解析器即可
第九步:视图解析器向前端控制器返回View
第十步:前端控制器进行视图渲染 (视图渲染将模型数据(在ModelAndView对象中)填充到request域)
第十一步:前端控制器向用户响应结果

能说出来大体流程就好,不需要上面这么详细的解释,大体的流程能描述清楚就好

先请求到DispatcherServlet 然后去通过 HandlerMapping 寻找 handler 返回,接着DispatcherServlet去根据 返回结果 执行对应方法,执行完成返回ModelAndView,
根据ModelAndView进行视图解析,然后响应结果..

115.Struts2和spring mvc的区别:
1、Struts2是类级别的拦截, 一个类对应一个request上下文,SpringMVC是方法级别的拦截,一个方法对应一个request上下文,而方法同时又跟一个url对应,所以说从架构本身上SpringMVC就容易实现restful url,而struts2的架构实现起来要费劲,因为Struts2中Action的一个方法可以对应一个url,而其类属性却被所有方法共享,这也就无法用注解或其他方式标识其所属方法了。
2、由上边原因,SpringMVC的方法之间基本上独立的,独享request response数据,请求数据通过参数获取,处理结果通过ModelMap交回给框架,方法之间不共享变量,而Struts2搞的就比较乱,虽然方法之间也是独立的,但其所有Action变量是共享的,这不会影响程序运行,却给我们编码 读程序时带来麻烦,每次来了请求就创建一个Action,一个Action对象对应一个request上下文。
3、由于Struts2需要针对每个request进行封装,把request,session等servlet生命周期的变量封装成一个一个Map,供给每个Action使用,并保证线程安全,所以在原则上,是比较耗费内存的。
4、 拦截器实现机制上,Struts2有以自己的interceptor机制,SpringMVC用的是独立的AOP方式,这样导致Struts2的配置文件量还是比SpringMVC大。
5、SpringMVC的入口是servlet,而Struts2是filter(这里要指出,filter和servlet是不同的。以前认为filter是servlet的一种特殊),这就导致了二者的机制不同,这里就牵涉到servlet和filter的区别了。
6、SpringMVC集成了Ajax,使用非常方便,只需一个注解@ResponseBody就可以实现,然后直接返回响应文本即可,而Struts2拦截器集成了Ajax,在Action中处理时一般必须安装插件或者自己写代码集成进去,使用起来也相对不方便。
7、SpringMVC验证支持JSR303,处理起来相对更加灵活方便,而Struts2验证比较繁琐,感觉太烦乱。
8、Spring MVC和Spring是无缝的。从这个项目的管理和安全上也比Struts2高(当然Struts2也可以通过不同的目录结构和相关配置做到SpringMVC一样的效果,但是需要xml配置的地方不少)。
9、 设计思想上,Struts2更加符合OOP的编程思想, SpringMVC就比较谨慎,在servlet上扩展。
10、SpringMVC开发效率和性能高于Struts2。
11、SpringMVC可以认为已经100%零配置。
不需要全部说

大概说一下 Struts2是类级别的拦截,SpringMVC是方法级别的拦截 ,SpringMVC的方法之间基本上独立的,Struts2的变量就是同一个action共享,比较乱
spring mvc的开发效率以及性能 比 struts2的效率高, 不需要什么配置,spring mvc 和spring 无缝集成,比Struts2和spring 好一些, 而且两者机制不同
SpringMVC的入口是servlet,而Struts2是filter,区别大概就是这样子的..

116.拦截器中如何拦截get提交方式:
采用 request.getMethod() 方法。获取请求方式 , 当为get请求方式 进行拦截.
117.如何在方法内获取request和session:
通过方法参数 , 注入 , 上下文 获取到 request 和 session ,并且大部分 对象都可以通过 方法参数 和 注入获得.

107,http工作原理
HTTP协议(HyperText Transfer Protocol,超文本传输协议)是用于从WWW服务器传输超文本到本地浏览器的传送协议。它可以使浏览器更加高效,使网络传输减少。它不仅保证计算机正确快速地传输超文本文档,还确定传输文档中的哪一部分,以及哪部分内容首先显示(如文本先于图形)等。
一次HTTP操作称为一个事务,其工作整个过程如下:

 1 ) 、地址解析,

 如用客户端浏览器请求这个页面:http://localhost.com:8080/index.htm

 从中分解出协议名、主机名、端口、对象路径等部分,对于我们的这个地址,解析得到的结果如下:
 协议名:http
 主机名:localhost.com
 端口:8080
 对象路径:/index.htm

  在这一步,需要域名系统DNS解析域名localhost.com,得主机的IP地址。


2)、封装HTTP请求数据包

 把以上部分结合本机自己的信息,封装成一个HTTP请求数据包


 3)封装成TCP包,建立TCP连接(TCP的三次握手)

   在HTTP工作开始之前,客户机(Web浏览器)首先要通过网络与服务器建立连接,该连接是通过TCP来完成的,该协议与IP协议共同构建Internet,即著名的TCP/IP协议族,因此Internet又被称作是TCP/IP网络。HTTP是比TCP更高层次的应用层协议,根据规则,只有低层协议建立之后才能进行更高层协议的连接,因此,首先要建立TCP连接,一般TCP连接的端口号是80。这里是8080端口

 4)客户机发送请求命令

   建立连接后,客户机发送一个请求给服务器,请求方式的格式为:统一资源标识符(URI:Uniform Resource Identifier)、协议版本号,后边是MIME信息包括请求修饰符、客户机信息和可能的内容。

5)服务器响应

 服务器接到请求后,给予相应的响应信息,其格式为一个状态行,包括信息的协议版本号、一个成功或错误的代码,后边是MIME信息包括服务器信息、实体信息和可能的内容。

    实体消息是服务器向浏览器发送头信息后,它会发送一个空白行来表示头信息的发送到此结束,接着,它就以Content-Type应答头信息所描述的格式发送用户所请求的实际数据

 6)服务器关闭TCP连接

 一般情况下,一旦Web服务器向浏览器发送了请求数据,它就要关闭TCP连接,然后如果浏览器或者服务器在其头信息加入了这行代码

Connection:keep-alive

TCP连接在发送后将仍然保持打开状态,于是,浏览器可以继续通过相同的连接发送请求。保持连接节省了为每个请求建立新连接所需的时间,还节约了网络带宽。

108, hashmap什么情况下会转成红黑树
一个是链表长度到8,一个是数组长度到64.
判断链表长度到达8调用treeifyBin方法转换红黑树,TREEIFY_THRESHOLD的值为8
在treeifyBin的方法代码,开头有判断数组长度是否小于64,小于则进行扩容,否则转红黑树.MIN_TREEIFY_CAPACITY的值为64.

109,hashmap为什么要用异或运算符
也是为了解决hash冲突的问题,提高解决冲突的效率,在java8之后只需要做一次就可以,因为hashmap的数组长度要取2的整次幂,取出来的结果就是散列值,要把散列值的高位全部归零,只保留低位值,用来做数组的下标访问
以初始长度16为例,16-1=15。2进制表示是00000000 00000000 00001111。和某散列值做“与”操作如下,结果就是截取了最低的四位值。
这样就算我的散列值分布再松散,要是只取最后几位的话,碰撞也会很严重。更要命的是如果散列本身做得不好,分布上成等差数列的漏洞,恰好使最后几个低位呈现规律性重复
这时候就需要异或运算来解决

110,hashmap为什么线程不安全
HashMap在put的时候,插入的元素超过了容量(由负载因子决定)的范围就会触发扩容操作,就是rehash,这个会重新将原数组的内容重新hash到新的扩容数组中,在多线程的环境下,存在同时其他的元素也在进行put操作,如果hash值相同,可能出现同时在同一数组下用链表表示,造成闭环,导致在get时会出现死循环,所以HashMap是线程不安全的。

111,float,decimal存储金额的区别
decimal 数据类型最多可存储 38 个数字,所有数字都能够放到小数点的右边。decimal 数据类型存储了一个准确(精确)的数字表达法;不存储值的近似值。
定义 decimal 的列、变量和参数的两种特性如下:
· p 小数点左边和右边数字之和,不包括小数点。如 123.45,则 p=5,s=2。
指定精度或对象能够控制的数字个数。
· s
指定可放到小数点右边的小数位数或数字个数。
p 和 s 必须遵守以下规则:0 <= s <= p <= 38。
Float 的科学计数法与值的问题,问题的根源在于 float 类型本身是一种不精确的数据表示方法, 也就是说, 你放一个数据进去, 拿出来的时候可能会存在一点点点误差, 而这点点点误差在做数据比较的时候就会导致数据不一致.

112,b+树如何优化
创建索引的时候尽量使用唯一性大的列来创建索引,由于使用b+tree做为索引,以innodb为例,一个树节点的大小由“innodb_page_size”,为了减少树的高度,同时让一个节点能存放更多的值,索引列尽量在整数类型上创建,如果必须使用字符类型,也应该使用长度较少的字符类型。
B+树分裂操作的优化
由于传统50%分裂的策略,有不足之处,因此,目前所有的关系型数据库,包括Oracle/InnoDB/PostgreSQL,以及本人以前参与研发的Oscar数据库,目前正在研发的NTSE、TNT存储引擎,都针对B+树索引的递增/递减插入进行了优化
新的分裂策略,在插入7时,不移动原有页面的任何记录,只是将新插入的记录7写到新页面之中;
原有页面的利用率,仍旧是100%;
优化分裂策略的优势:
索引分裂的代价小:不需要移动记录;
索引分裂的概率降低:如果接下来的插入,仍旧是递增插入,那么需要插入4条记录,才能再次引起页面的分裂。相对于50%分裂策略,分裂的概率降低了一半;
索引页面的空间利用率提高:新的分裂策略,能够保证分裂前的页面,仍旧保持100%的利用率,提高了索引的空间利用率;
优化分裂策略的劣势:
如果新的插入,不再满足递增插入的条件,而是插入到原有页面,那么就会导致原有页面再次分裂,增加了分裂的概率。

因此,此优化分裂策略,仅仅是针对递增递减插入有效,针对随机插入,就失去了优化的意义,反而带来了更高的分裂概率。

在InnoDB的实现中,为每个索引页面维护了一个上次插入的位置,以及上次的插入是递增/递减的标识。根据这些信息,InnoDB能够判断出新插入到页面中的记录,是否仍旧满足递增/递减的约束,若满足约束,则采用优化后的分裂策略;若不满足约束,则退回到50%的分裂策略。
113,事务失效的几种场景
@Transactional 失效场景
1、检查你方法是不是public的
2、你的异常类型是不是unchecked异常
如果我想check异常也想回滚怎么办,注解上面写明异常类型即可
@Transactional(rollbackFor=Exception.class) 1
类似的还有norollbackFor,自定义不回滚的异常
3、数据库引擎要支持事务,如果是MySQL,注意表要使用支持事务的引擎,比如innodb,如果是myisam,事务是不起作用的
4、是否开启了对注解的解析
<tx:annotation-driven transaction-manager=“transactionManager” proxy-target-class=“true”/>
5、spring是否扫描到你这个包,如下是扫描到org.test下面的包
<context:component-scan base-package=“org.test” ></context:component-scan>1
6、检查是不是同一个类中的方法调用(如a方法调用同一个类中的b方法)
同类方法相互调用可以使用代理对象调方法解决事务失效的问题.
7、异常是不是被你catch住了

@Async异步注解失效的场景
1、异步注解@Async介绍
基于@Async标注的方法,称之为异步方法,这些方法在执行的时候,spring将会为其开辟独立的线程执行,调用者无需等待它的完成,即可继续其他的操作。
2.失效原因
spring声明式事务和异步注解的实现都是基于spring aop,即对标识的方法进行增强。spring aop的底层实现原理是 jdk 动态代理。因为事务注解方法,异步方法的调用的方法在同一个类中,所以异步方法是在调用方法的代理对象中执行的,没有对异步方法进行增强
3.解决方案
1、在spring配置文件xml新增如下语句:
先开启cglib代理,开启 exposeProxy=true,暴露代理对象
<aop:aspectj-autoproxy expose-proxy=“true”/>
2、使用AopContext 获取当前对象的动态代理。
修改配置文件后,代码修改,用获取到的动态代理去执行发送短信方法,如下:

TourServer currentProxy = (TourServer )AopContext.currentProxy();

    currentProxy.sendMesg(orderInfo);

    currentProxy.sendEmail(orderInfo);

还有spring的事务失效场景

在调用层一个@service注解没有加都可以事务时效
还有数据库的引擎不支持事务
数据库的默认引擎是innodb是支持事务,myisam是不支持事务的
4、数据源没有配置事务管理器
7、异常类型错误或格式配置错误
抛出一个异常的例子:
@Service
public class OrderServiceImpl implements OrderService {
@Transactional
// @Transactional(rollbackFor = SQLException.class)
public void updateOrder(Order order) {
try { // update order
}catch (Exception e){
throw new Exception(“更新错误”);
}
}

114.SpringMVC怎么和Ajax相互调用
方法一:通过URL传参
方法二:单值传参
方法三:对象传参
方法四:对象序列化传参
方法五:List传参

115.SpringMVC的执行流程
一个请求匹配前端控制器 DispatcherServlet 的请求映射路径(在 web.xml中指定), WEB 容器将该请求转交给 DispatcherServlet 处理
DispatcherServlet 接收到请求后, 将根据 请求信息 交给 处理器映射器 (HandlerMapping)
HandlerMapping 根据用户的url请求 查找匹配该url的 Handler,并返回一个执行链
DispatcherServlet 再请求 处理器适配器(HandlerAdapter) 调用相应的 Handler 进行处理并返回 ModelAndView 给 DispatcherServlet
DispatcherServlet 将 ModelAndView 请求 ViewReslover(视图解析器)解析,返回具体 View
DispatcherServlet 对 View 进行渲染视图(即将模型数据填充至视图中)
DispatcherServlet 将页面响应给用户

116.MySQL锁的并发
从我们的直观理解上来看,要实现数据库的并发访问控制,最简单的做法就是加锁访问,即读的时候不能写(允许多个西线程同时读,即共享锁,S锁),写的时候不能读(一次最多只能有一个线程对同一份数据进行写操作,即排它锁,X锁)。这样的加锁访问,其实并不算是真正的并发,或者说它只能实现并发的读,因为它最终实现的是读写串行化,这样就大大降低了数据库的读写性能。加锁访问其实就是和MVCC相对的LBCC,即基于锁的并发控制(Lock-Based Concurrent Control),是四种隔离级别中级别最高的Serialize隔离级别。

117.Redis的数据结构都有哪些
List set zset string hash

118.如何使用Redis实现分布式锁
利用redis中的set命令来实现分布式锁。
从Redis 2.6.12版本开始,set可以使用下列参数:
SET KEY VALUE [EX seconds] [PX milliseconds] [NX|XX]
EX second :设置键的过期时间为second秒。 SET key value EX second效果等同于SETEX key second value 。
PX millisecond :设置键的过期时间为millisecond毫秒。 SET key value PX millisecond效果等同于PSETEX key millisecond value 。
NX :只在键不存在时,才对键进行设置操作。 SET key value NX效果等同于SETNX key value 。
XX :只在键已经存在时,才对键进行设置操作。
返回值:
SET 在设置操作成功完成时,才返回OK 。
如果设置了NX或者XX ,但因为条件没达到而造成设置操作未执行,那么命令返回空批量回复(NULL Bulk Reply)。
命令:

SET key value EX ttl NX

119.Redis的缓存穿透和缓存雪崩
缓存穿透:
缓存穿透的概念很简单,用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。
缓存雪崩:
Redis的缓存穿透和缓存雪崩因为缓存层承载了大量的请求,有效的保护了存储 层,但是如果缓存由于某些原因,整体不能够提供服务,于是所有的请求,就会到达存储层,存储层的调用量就会暴增,造成存储层也会挂掉的情况。缓存雪崩的英文解释是奔逃的野牛,指的是缓存层当掉之后,并发流量会像奔腾的野牛一样,大量后端存储。存在这种问题的一个场景是:当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,大量数据会去直接访问DB,此时给DB很大的压力。

  • 0
    点赞
  • 0
    评论
  • 1
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 深蓝海洋 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值