120道面试题总结

1冒泡排序

public int[] bubbleSort(int arr[]) {
    int len = arr.length;
    for (int i = 0; i < len - 1; i++) {
        for (int j = 0; j < len - 1 - i; j++) {
            if (arr[j] > arr[j + 1]) { // 相邻元素两两对比
                int temp = arr[j + 1]; // 元素交换
                arr[j + 1] = arr[j];
                arr[j] = temp;
            }
        }
    }
    return arr;
}

2选择排序

    public int[] selectionSort(int arr[]) {
        int len = arr.length;
        int minIndex, temp;
        for (int i = 0; i < len - 1; i++) {
            minIndex = i;
            for (int j = i + 1; j < len; j++) {
                if (arr[j] < arr[minIndex]) { // 寻找最小的数
                    minIndex = j; // 将最小数的索引保存
                }
            }
            temp = arr[i];
            arr[i] = arr[minIndex];
            arr[minIndex] = temp;
        }
        return arr;
    }

3插入排序

    public int[] insertSort(int arr[]) {
        int len = arr.length;
        int preIndex, currentIndex;
        for (int i = 1; i < len; i++) {
            preIndex = i - 1;
            currentIndex = arr[i];
            while (preIndex >= 0 && currentIndex < arr[preIndex]) {
                arr[preIndex + 1] = arr[preIndex];
                preIndex--;
            }
            arr[preIndex + 1] = currentIndex;
        }
        return arr;
    }

4快速排序(分治法O(nlogn))--用递归实现

    public class QuickSort1 {
        public static void quickSort(int[] arr, int startIndex, int endIndex){
            // 递归结束条件:startIndex大于或等于endIndex时
            if (startIndex >= endIndex) {
                return;
            }
            得到基准元素位置
            int pivotIndex = partition(arr, startIndex, endIndex);
根据基准元素,分成两部分进行递归排序
            前半段
            quickSort(arr, startIndex, pivotIndex - 1);
            后半段
            quickSort(arr, pivotIndex + 1, endIndex);
        }
分治(双边循环法)
arr 待交换的数组
startIndex 起始下标
endIndex 结束下标
    private static int partition(int[] arr, int startIndex, int endIndex){
        取第1个位置(也可以选择随机位置)的元素作为基准元素
        int pivot = arr[startIndex];
        左指针
        int left = startIndex;
        右指针
        int right = endIndex;
        while( left != right) {
            比较右边,如果元素比基准元素大,基准元素左移,右指针--
            while(left<right && arr[right] > pivot){
                right--;
            }
            比较左边,如果选中元素比基准元素小,基准元素右移,左指针++
            while( left<right && arr[left] <= pivot){
                left++;
            }
            比较到中间位置要交换left和right
            if(left<right) {
                int p = arr[left];
                arr[left] = arr[right];
                arr[right] = p;
            }
        }
        中间只有一个元素的时候pivot和指针重合点(left=right)交换
        arr[startIndex] = arr[left];
        arr[left] = pivot;
        return left;
    }
 

5常问数据结构

1. 线性表结构 ( 重点 )
线性表是由 N 个元素组成的有序序列,也是最常见的一种数据结构。重点有两个数组和链表。
数组 :数组是一种存储单元连续,用来存储固定大小元素的线性表。 java 中对应的集合实现,比如ArrayList。
链表 :链表又分单链表和双链表,是在物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中指针的链接次序实现的,可以在头尾添加元素.java 中对应的集合实现,比如 LinkedList
2. 栈与队列
后进先出, 表的末端叫栈顶,基本操作有push(进栈 ) pop( 出栈 ) java stack 就是简单的栈实现。
队列 先进先出, 表的前端只允许进行删除操作, 表的后端进行插入操作。进行插入操作的端称为队尾,进行删除操作的端称为队头。java 中很多Queue的实现,消息中间件的队列本质也是基于此的。
3 ( 重点 )
在非线性结构里面,树是非常非常重要的一种数据结构。 基于其本身的结构优势,尤其在查找领域,应用广泛,其中又以二叉树最为重要。树的话我们这里只重点说一下二叉树。
二叉搜索树( 二叉查找树,二叉排序树)
左子树中每个节点的值都不大于该节点值;
右子树中每个节点的值都不小于该节点值。
没有键值相等的结点
平衡二叉树 :是进化版的二叉查找树, 这个方案很好的解决了二叉查找树 退化成链表的问题
它的左右子树的高度之差的绝对值不能超过 1 ,如果插入或者删除一个节点使得高度之差大于 1 就要进行节点之间的旋转 (左旋右旋)    将二叉树重新维持在一个平衡状态。
红黑树 :红黑树是一种特殊的平衡二叉树,它保证在最坏情况下基本动态集合操作的事件复杂度为O(log n)。
红黑树放弃了追求完全平衡,追求大致平衡,在与平衡二叉树的时间复杂度相差不大的情况下,保证每次插入最多只需要三次旋转就能达到平衡,实现起来也更为简单。
树的根都是黑色的
不可能有连着的红色节点(红色节点不能有红色父节点或红色子节点,黑色节点可以连续)
所有叶子节点都是黑色(叶子是 NIL 节点)
从任一节点到其叶子的所有简单路径都包含相同数目的黑色节点

HashMap底层
Java7 : 数组 + 链表
Java8 : 数组 + 链表 或 红黑树 ( 链表超过 8 则转为红黑树,小于 6 则变会链表) >> 为啥呢? 为了加快查询

根据统计概率学的泊松分布,在负载因子默认为0.75的时候,单个hash槽内元素个数为8的概率小于百万分之一,所以将7作为一个分水岭, 等于7的时候不转换,大于等于8的时候才进行转换为红黑树,小于等于6的时候就化为链表

1 hashmap的数据结构:源码中每个节点用 Node<K V>表示,Node 是一个内部类,这里的 key 为键, value 为值, next 指向下一个元素,看出 HashMap 中元素不是一个单纯的键值对,还包含下一个元素的引用
    public class HashMap<K, V> extends AbstractMap<K, V> implements Map<K, V>, Cloneable, Serializable {
        private static final long serialVersionUID = 362498820763181265L;

        static class Node<K, V> implements Map.Entry<K, V> {
            final int hash;
            final K key;
            V value;
            Node<K, V> next;
        }
    }

为什么采用这种结构来存储元素呢?

数组的特点:查询效率高,插入,删除效率低
链表的特点:查询效率低,插入删除效率高
HashMap 底层使用数组加(链表或红黑树)的结构完美的解决了数组和链表的问题,使得查询和插入,删除的效率都很高。
 
2 hashmap的存储元素的过程
1. 计算出key键“老孙 hashcode ,该值用来定位要将这个元素存放到数组中的什么位置
2. 假设老孙的 hashcode 12345678 ,数组长度为 8 ,则要存储在数组索引为 6 的位置( 12345678% 8=6)
可以分两种情况 :
1. 数组索引为 6 的地方是空的,这种情况很简单,直接将元素放进去就好了。
2. 已经有元素占据了索引为 6 的位置,这种情况下我们需要判断一下该位置的元素和当前元素是否相等,使用equals 来比较。
如果使用默认的规则是比较两个对象的地址。也就是两者需要是同一个对象才相等,当然我们也可以重写equals 方法来实现我们自己的比较规则,最常见的是通过比较属性值来判断是否相等。
如果两者相等则直接覆盖,如果不等则在原元素下面使用 链表 的结构存储该元素,如图所示,每个元素节点都有一个 next 属性指向下一个节点,这里由数组结构变成了 数组 + 链表结构

因为链表中元素太多的时候会影响查找效率,所以当链表的元素个数达到8的时候使用链表存储就转变成了使用红黑树存储,原因就是红黑树是平衡二叉树,在查找性能方面比链表要高.

6volatile关键字

volatile 通常被比喻成 " 轻量级的 synchronized ",和 synchronized 不同, volatile 是一个变量修饰符,只能用来修饰变量。无法修饰方法及代码块等。
volatile 修饰的共享变量,就具有了以下两点特性:
保证了不同线程对该变量操作的内存可见性( 可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值 )
没有volatile修饰,main主线程和子线程1之间是彼此见不到i的,所以主线程修改i也没用。循环不会停止!
public class Test1 {
        private volatile static int i = 0;
        public static void main(String[] args) throws InterruptedException {
            new Thread(() -> {
                while (i==0){
                //随便干点啥
            }
            System.out.println("结束了!");
            },"线程1").start();

            Thread.sleep(1000);
            i = 1;
        }
    }

禁止cpu指令重排序:是为了提高程序的性能(我们爬楼梯,你走的慢,我走的快,你让开点,我先走上去)

Thread t1 = new Thread ( new Runnable () {
public void run () {
a = 1 ; // 1
x = b ; // 2
}
});
Thread t2 = new Thread ( new Runnable () {
public void run () {
b = 1 ; // 3
y = a ; // 4
}
t1 . start ();
t2 . start ();
t1 . join ();
t2 . join ();

前提是12没有依赖关系34没有依赖关系,才会发生指令重排

而我们的 volatile 可以防止这种情况发生,为什么 volatile可以防止重排呢?volatile支持内存屏障, 指令和指令之间加一堵墙(屏障),不能随意挪动窜位
LoadLoad 屏障: 读与读指令之间加屏障
StoreStore 屏障:写与写指令之间加屏障
LoadStore 屏障:读与写指令之间加屏障
StoreLoad 屏障:写与读指令之间加屏障

 

7为什么要用Dubbo

因为Dubbo是阿里开源的RPC框架,国内很多互联网公司都在用,现在进入了 Apache 项目 ,内部使用了 Zookeeper ,保证了高性能高可用性
Dubbo 可以将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,可用于提高业务复用灵活扩展,使前端应用能更快速的响应多变的市场需求
最重要的一点是,分布式架构可以承受更大规模的并发流量
dubbo即是求职的人,也是招聘单位,而zookeeper就是人才市场/招聘网站;

8dubbo的工作流程

首先在客户端发出一个请求,也就是客户端调用,然后将本地的客户端的参数数据进行一个序列化(把对象转换成一个字节码文件),把序列化好的文件通过NIO发送信息给服务层,(dubbo底层用的是netty通过NIO发送信息),服务层接收到信息会进行反序列化,调用本地服务执行业务,然后把操作完的信息返回,还是要进行序列化,发给客户端,客户端再进行反序列化处理对应的业务

9Dubbo支持哪些协议,每种协议的应用场景,优缺点?

dubbo 单一长连接和 NIO 异步通讯,适合大并发小数据量的服务调用,以及消费者远大于提供者。传输协议TCP ,异步, Hessian 序列化;
默认使用dubbo协议,dubbo协议底层采用的是netty4,性能高效!
redis 基于 redis 实现的 RPC 协议
webservice 基于 WebService 的远程调用协议,集成 CXF 实现,提供和原生 WebService 的互操作。多个短连接,基于HTTP 传输,同步传输,适用系统集成和跨语言调用;
http 基于 Http 表单提交的远程调用协议,使用 Spring HttpInvoke 实现。多个短连接,传输协议HTTP ,传入参数大小混合,提供者个数多于消费者,需要给应用程序和浏览器 JS 调用;

rmi采用JDK标准的rmi协议实现,传输参数和返回参数对象需要实现Serializable接口,使用java标准序列化机制,使用阻塞式短连接,传输数据包大小混合,消费者和提供者个数差不多,可传文件,传输协议TCP。多个短连接,TCP协议传输,同步传输,适用常规的远程服务调用和rmi互操作。在依赖低版本的Common-Collections包,java序列化存在安全漏洞;

10RPC使用了哪些关键技术?

1 、动态代理
生成 Client Stub (客户端存根)和 Server Stub (服务端存根)的时候需要用到 Java 动态代理技术,可以使用JDK 提供的原生的动态代理机制,也可以使用开源的: CGLib 代理, Javassist 字节码生成技术。
2 、序列化和反序列化(netty)
在网络中,所有的数据都将会被转化为字节进行传送,所以为了能够使参数对象在网络中进行传输,需要对这些参数进行序列化和反序列化操作。
序列化:把对象转换为字节序列的过程称为对象的序列化,也就是编码的过程。
反序列化:把字节序列恢复为对象的过程称为对象的反序列化,也就是解码的过程。
目前比较高效的开源序列化框架:如 Kryo FastJson Protobuf 等。
3 NIO 通信
出于并发性能的考虑,传统的阻塞式 IO 显然不太合适,因此我们需要异步的 IO ,即 NIO
Java 提供了 NIO 的解决方案, Java 7 也提供了更优秀的 NIO.2 支持。可以选择 Netty 来解决NIO数据传输的问题。
4 、服务注册中心
可选: Redis Zookeeper
一般使用 ZooKeeper 提供服务注册与发现功能,解决单点故障以及分布式部署的问题 ( 注册中心 )

 

11算法思维

贪心算法:是一种在每一步选中都采取在当前状态下最好或最优的选择,从而希望导致结果是全局最好或最优的算法。

贪心算法的应用
部分背包:某件物品是一堆,可以带走其一部分
0-1 背包:对于某件物品,要么被带走(选择了它),要么不被带走(没有选择它),不存在只带走一部分的情况。
public class BagDemo1 {
        最大承重
        double bag;
        public void take(Goods[] goodslist) {
            // 对物品按照价值排序从高到低
            Goods[] goodslist2 = sort(goodslist);
            当前总重
            double sum_w = 0;
            //取出价值最高的
            for (int i = 0; i < goodslist2.length; i++) {
                sum_w += goodslist2[i].weight;
                if (sum_w <= bag) {
                    System.out.println(goodslist2[i].name + "取" +goodslist2[i].weight +"kg");
                }else{
                    System.out.println(goodslist2[i].name + "取" +(bag-(sum_w-goodslist2[i].weight)) +"kg");
                }
            }
        }
        // 按物品的每kg 价值排序 由高到低 price/weight
        private Goods[] sort(Goods[] goodslist) {
            return goodslist;
        }

public static void main(String[] args) {
BagDemo1 bd = new BagDemo1();
Goods goods1 = new Goods("A", 10, 60);
Goods goods2 = new Goods("B", 20, 100);
Goods goods3 = new Goods("C", 30, 120);
Goods[] goodslist = {goods1, goods2, goods3};
bd.bag = 50;
bd.take(goodslist);
}

回溯算法

public class NQueens {
    皇后数
    static int QUEENS = 8;
    下标是行,result表示queen存储在哪一列  
    int[] result = new int[QUEENS];
在每行放置Queen
    public void setQueens(int row) {
        //递归中断
        if (row == QUEENS) {
            printQueens();
            return ;
        }
        //在每行依次放置列 没有合适的则回到上一层
        for(int col=0;col<QUEENS;col++){
            if(isOk(row,col)){
                //设置列
                result[row]=col;
                //开始下一行
                setQueens(row+1);
打印输出
    private void printQueens() {
        for (int i = 0; i < QUEENS; i++) {
            for (int j = 0; j < QUEENS; j++) {
                if (result[i] == j) {
                    System.out.print("Q| ");
                }
                else {
                    System.out.print("*| ");
                }
            }
        }
判断是否可以放置
    private boolean isOk(int row, int col) {
        int leftup = col - 1;
        int rightup = col + 1;
        // 逐行往上考察每一行
        for (int i = row - 1; i >= 0; i--) {
            //列上存在queen
            if (result[i] == col) return false;
            //左上对角线存在queen
            if (leftup >= 0) {
                if (result[i] == leftup) return false;
            }
            //右下对角线存在queen
            if (rightup < QUEENS) {
                if (result[i] == rightup) return false;
            }
            leftup--;
            rightup++;
        }
        return true;
    }

 

12redis是单线程的吗?那他是怎么支持高并发请求的?

redis 内部使用了一个叫 文件事件处理器, 最重要的是单线程   所以才有了 redis 是单线程的这一说法。
它采用 IO多路复用机制 来同时监听多个Socket ,根据 Socket 上的事件类型来选择对应的事件处理器来处理这个事件。

redis为什么快?

1. Redis 采用了单线程的模型,保证了每个操作的原子性,也避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗CPU ,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗
2. 灵活多样的数据结构。 redis 内部使用一个 redisObject 对象来表示所有的 key value redisObject主要的信息包括数据类型、编码方式、数据指针、虚拟内存等。它包含String Hash List ,Set, Sorted Set 五种数据类型,针对不同的场景使用对应的数据类型,减少内存使用的同时,节
省网络流量传输。
3. Redis是纯内存数据库,一般都是简单的存取操作,所以读取速度快。没有多线程的切换
4. 再说一下 IO Redis 使用的是非阻塞 IO 多路复用,使用了单线程来轮询描述,将数据库的开、关、读、写都转换成了事件,减少了线程切换时上下文的切换和竞争。

超市老板:今天情人节,水果特价,30个顾客消费,排队结账!

顾客1 想买苹果,咱有苹果,拿给你,结账完成!
阻塞 IO:     顾客2 想买香蕉,咱没货了,去新发地进货,此时顾客 2 等着,后面的 28 个顾客都在等待!
非阻塞 IO: 请您旁边等待,我是店里的销货员,我将你的需求记录下来了,你在这喝点茶,货马上就来,不会耽误顾客3,顾客2也不会生气!
 
起初当看到别人说 redis 是单线程时,很容易想成 redis 服务端只开启了一个线程用于做所有事情,然而实际上我们所说的redis 单线程 只是针对 redis 网络请求模块,即上文提到的 件事件处理器

 

13缓存穿透、缓存雪崩、缓存击穿

缓存雪崩
在高并发下,大量的缓存 key 同一时间 失效,导致大量的请求落到数据库上,如活动系统里面同时进行着非常多的活动,但是在某个时间点所有的活动缓存全部过期。
解决方案:
1. 设置热点数据永远不过期。
2. 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
3. 如果缓存数据库是分布式部署,将热点数据均匀分布在不同的缓存数据库中。
缓存穿透
访问一个 不存在的 key (查询 userid = -10 ),缓存不起作用,请求会穿透到 DB ,流量大时 DB 会挂掉
解决方案:
1. 接口层增加校验,如用户鉴权校验, id 做基础校验, id<=0 的直接拦截;
2. 从缓存取不到的数据,在数据库中也没有取到,这时也可以将redis中把这个 key对应的value值设为空值直接返回,拦截了再次访问数据库了
3. 设置 缓存有效时间短点,如30 秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id 暴力攻击
缓存击穿
访问一个 存在的 key 缓存过期的那一刻同时有大量的请求 ,这些请求都会击穿到 DB ,造成瞬时 DB 请求量大、压力骤增
解决方案:
1. 设置热点数据永远不过期。
2. 加互斥锁:业界比较常用的做法。简单地来说,就是在缓存失效的时候(判断拿出来的值是否为空),不是立即去加载数据库,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX )去 set 一个 mutex key ,当操作返回成功时,再进行加载数据库的操作并回设缓存;否则,就重试整个get 缓存的方法。
    public String get(key) {
        String value = redis.get(key);
        if (value == null) {代表缓存值过期
            if (redis.setnx(key_mutex, 1, 3 * 60) == 1) {代表设置成功
                value = db.get(key);去加载数据库
                redis.set(key, value, expire_secs);将数据库的数据放在redis中,并设置过期时间
                redis.del(key_mutex);
            } else {这个时候代表同时候的其他线程已经load db并回设到缓存了,这时候重试获取缓存值即可
                sleep(50);
                get(key);重试
            }
        } else {
            return value;
        }
    }

14项目解决方案:MQ消息积压以及处理方案

只能操作临时扩容,以更快的速度去消费数据:
1增加多个消费者,加速消费:新建topic引流,将消息引导别的程序中,洪水一个道理

15如何保证MQ中的消息不丢失

生产者 丢失消息
1. 可以选择使用 rabbitmq 提供的事务功能,就是生产者在发送数据之前开启事务,然后发送消息,如果消息没有成功被rabbitmq接收到,那么生产者会受到异常报错,这时就可以回滚事务,然后尝试重新发送;如果收到了消息,那么就可以提交事务. 缺点: rabbitmq 事务已开启,就会变为同步阻塞操作,生产者会阻塞等待是否发送成功,太 耗性能会造成吞吐量的下降。
2. 可以开启消息确认confirm模式 。在生产者设置开启了 confirm 模式之后,每次写的消息都会分配一个唯一的id ,如果写入了 rabbitmq 中, rabbitmq 会给你回传一个 ack 消息,告诉你这个消息发送OK
如果 rabbitmq 没能处理这个消息,会回调你一个 nack 接口,告诉你这个消息失败了,你可以进行重试。而且你可以结合这个机制知道自己在内存里维护每个消息的id,如果超过一定时间还没接收到这个消息的回调,那么你可以进行重发二者不同在于 事务机制是同步的,你提交了一个事务之后会阻塞住,但是confirm机制是异步的,发送消息之后可以接着发送下一个消息,然后rabbitmq 回调告知成功与否。一般在生产者这块避免丢失,都是用confirm 机制。
rabbitmq 自己 弄丢了数据
设置消息持久化到磁盘。设置持久化有两个步骤:
①创建 queue 的时候将其设置为持久化的,这样就可以保证 rabbitmq 持久化 queue 的元数据(数据的描述而不是数据本身),但是不会持久化queue 里面的数据。
②发送消息的时候将消息的 deliveryMode 设置为 2 ,这样消息就会被设为持久化方式,此时rabbitmq就会将消息持久化到磁盘上。
必须要同时开启这两个才可以。
而且持久化可以跟生产的 confirm 机制配合起来,只有消息持久化到了磁盘之后,才会通知生产者ack ,这样就算是在持久化之前 rabbitmq 挂了,数据丢了,生产者收不到 ack 回调也会进行消息重发。
消费者 弄丢了数据
使用 rabbitmq 提供的 ack 机制,首先关闭 rabbitmq 的自动 ack ,然后每次在确保处理完这个消息之后,在代码里手动调用ack 。这样就可以避免消息还没有处理完就 ack

 

16如何保证MQ中消息的顺序性

为什么要保证顺序
消息队列中的若干消息如果是对同一个数据进行操作,这些操作具有前后的关系,必须要按前后的顺序执行,否则就会造成数据异常。
比如数据库对一条数据依次进行了 插入-> 更新 -> 删除操作,这个顺序必须是这样,如果在同步过程中,消息的顺序变成了 删除-> 插入 -> 更新,那么原本应该被删除的数据,就没有被删除,造成数据不一致问题

保证消息的消费顺序
1. 拆分多个 queue ,每个 queue 一个 consumer ,就是多一些 queue 而已,确实是麻烦点;这样也会造成吞吐量下降,可以在消费者内部采用多线程的方式去消费。

2. 一个 queue 对应一个 consumer ,然后这个 consumer 内部用内存队列做排队,然后分发给底层不同的worker 来处理  在java程序中自己写一个队列

 

17什么是服务熔断?什么是服务降级?怎么操作?

在复杂的分布式系统中,微服务之间的相互调用,有可能出现各种各样的原因导致服务的阻塞,在高并发场景下,服务的阻塞意味着线程的阻塞,导致当前线程不可用,服务器的线程全部阻塞,导致服务器崩溃,由于服务之间的调用关系是同步的,会对整个微服务系统造成服务雪崩

为了解决某个微服务的调用响应时间过长或者不可用进而占用越来越多的系统资源引起雪崩效应就需要进行服务熔断和服务降级处理。
所谓的服务熔断指的是某个服务故障或异常一起类似现实世界中的“ 保险丝 " 当某个异常条件被触发就直接熔断整个服务,而不是一直等到此服务超时。
服务熔断就是相当于我们电闸的保险丝,一旦发生服务雪崩的,就会熔断整个服务,通过维护一个自己的线程池,当线程达到阈值的时候就启动服务降级,如果其他请求继续访问就直接返回fallback的默认值

 

18EurekaZooKeeper都可以提供服务注册与发现的功能,请说说两个的区别

  • ZooKeeper保证的是CPEureka保证的是AP
  • (C所有节点在同一时间的数据是一致的:A:高可用:这个服务一直能用 P是分区容错性:分布式系统里面的某个节点有故障仍然可以对外满足一致性或者可用性的服务,允许犯一点错)
  • ZooKeeperLeaderFollower角色,Eureka各个节点平等
  • ZooKeeper采用过半数存活原则,Eureka采用自我保护机制解决分区问题
  • Eureka本质上是一个工程,而ZooKeeper只是一个进程
  • 出现故障时,Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其他节点(高可用)

19负载均衡策略(雨露均沾)

使用负载均衡带来的好处很明显: 当集群里的1 台或者多台服务器 down 的时候,剩余的没有 down 的服务器可以保证服务的继续使用,使用了更多的机器保证了机器的良性使用,不会由于某一高峰时刻导致系统cpu 急剧上升
负载均衡有好几种实现策略,常见的有: 随机  轮询    哈希(ip 信息%机器数量)   加权( 开挂 )    一致性哈希(服务器ip 信息 % 2 32 次方 -1 的结果落在哈希环上,顺时针取最近的)

20Ribbon内置的负载均衡策略

21Ribbon可以使用一致性哈希算法的策略吗 ?

可以,我之前使用过guava的一致性哈希算法,然后自己写一个类,继承 AbstractLoadBalancerRule
    @Component
    public class ConsistentHash extends AbstractLoadBalancerRule {
        private Logger log = LoggerFactory.getLogger(ConsistentHash.class);
        public Server choose(ILoadBalancer lb, Object key) {
            if (lb == null) {log.warn("没有负载均衡策略!");
                return null;
            }
            Server server = null;
            int count = 0;
            while (server == null && count++ < 10) {
                List<Server> reachableServers = lb.getReachableServers();
                List<Server> allServers = lb.getAllServers();
                int upCount = reachableServers.size();
                int serverCount = allServers.size();
                if ((upCount == 0) || (serverCount == 0)) {
                    log.warn("没有获取到可用的服务器进行负载均衡: " + lb);
                    return null;
                }
                //获取请求URI
                RequestContext ctx = RequestContext.getCurrentContext();
                HttpServletRequest request = ctx.getRequest();
                String URI = request.getServletPath()+"?"+request.getQueryString();
                int hashcode = URI.hashCode();
                int model = Hashing.consistentHash(hashcode, serverCount); //一致性哈希,直接返回第几个数
                        server = allServers.get(model);
                if (server == null) {
                    /* Transient. */
                    Thread.yield();
                    continue;
                }
                if (server.isAlive() && (server.isReadyToServe())) {
                    return (server);
                }
                // Next.
                server = null;
            }
            if (count >= 10) {
                log.warn("重试了10次,仍然没有从负载均衡中获取活动的服务器: " + lb);
            }
            return server;
        }
        @Override
        public Server choose(Object key) {
            return choose(getLoadBalancer(), key);
        }
        @Override
        public void initWithNiwsConfig(IClientConfig clientConfig) {
        // TODO Auto-generated method stub
        }
    }

 

22String底层使用的是char数组还是byte数组

JDK 1.8 及之前 , 底层是 char类型的数组,JDK 1.9 及以后 , 底层是 byte 类型的数组
因为开发人员发现人们使用的字符串值是拉丁字符居多而之前使用的 char 数组每一个 char 占用两个字节 ,而拉丁字符只需要一个字节就可以存储,剩下的一个字节就浪费了,造成内存的浪费,gc 的更加频繁。
因此在 jdk9 中将 String 底层的实现改为了 byte 数组。

23类加载时的执行顺序

1. 首先加载父类的静态字段或者静态语句块
2. 子类的静态字段或静态语句块
3. 父类普通变量以及语句块
4. 父类构造方法被加载
5. 子类变量或者语句块被加载
6. 子类构造方法被加载

24面向对象的特性及基本原则

1. 封装
封装是保证软件部件具有优良的模块性的基础,封装的目标就是要实现软件部件的 高内聚、低耦合” ,防止程序相互依赖性而带来的变动影响
好处 :
1. 提高代码的复用性
2. 隐藏了实现的细节 , 对外提供一个公共的访问方式 .
3. 提高了安全性
2. 继承
Java , 类的继承是指 , 在一个现有的类的基础上 , 构建一个新的类 , 构建的新的类 , 被称作子类 . 现有的类 被称为父类.
优点 :
1. 继承可以提高代码的复用性
2. 继承让类和类之间产生了关系 , 提供了多态的前提
缺点 :
1. 类和类之间产生了耦合 , 不符合 OOP 的开发原则
3. 多态
同一个行为具有多种不同的形态 . 多态是指子类对象可以直接赋给父类变量(父类引用指向子类对象),但运行时依然表现出子类的行为特征,也就是说,同一类型的对象在执行同一个方法时,可能表现出多种行为特征。 使用多态的三个前提: 语法上: 父类引用指向子类对象 必须有 继承或者实现关系  必须有方法的重写
优点
1. 隐藏了子类的类型 , 提高了代码的扩展性
缺点 :
1. 只能使用父类共性的内容 , 无法使用子类独有的功能 . 有功能上的限制 .

 

25编写Singleton 单例设计模式,

  • 单例模式的应用场景
  • 双重校验锁为什么需要双重校验?双重校验锁第二次进行判空原因:假设有两个线程A和B,都进行完第一次判空了,A和B都阻塞,这个时候A线程获取了类锁,然后B线程被阻塞,A线程新建了一个实例后释放了锁,B线程获取锁,又新建了一个实例,这破坏了单例设计模式的初衷。
Java 应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在.单例类给所有其他所有对象提供这一实例
饿汉式 1:
优点 : 在类加载的时候 , 就完成初始化
缺点 : 如果没有用到该实例则造成内存资源的浪费.
public class Singleton01 {
    //1.创建静态实例变量
    private static final Singleton01 INSTANCE = new Singleton01();
    //2.构造方法私有化
    private Singleton01(){}
    //3.获取实例对象
    public static Singleton01 getInstance(){
        return INSTANCE;
    }
懒汉式 (适用于多线程 )
1. 属性和构造方法私有化
2. 属性用 static volatile 修饰
3. 获取方法双重检查锁
    class Singleton04{
        //1.先new一个空的对象,用到的时候 再去实例化
        private static Singleton04 singleton04 = null;
        //2.构造方法用 private实例化
        private Singleton04(){}
        //3.双重锁检查
        public static Singleton04 getInstance(){
            if(singleton04 == null){
                //此处添加同步锁,锁对象选择当前类的class对象, 线程获取到锁之后
                synchronized (Singleton04.class){
                    if(singleton04 == null){
                        singleton04 = new Singleton04();
                    }
                }
            }
            return singleton04;
        }
    }

26hashcode 和 equals和==之间的关系

1.equals()和hashCode()的规定
如果两个对象equals()方法相等则它们的hashCode返回值一定要相同,如果两个对象的hashCode返回值相同,但它们的equals()方法不一定相等。
两个对象的hashCode()返回值相等不能判断这两个对象是相等的,但两个对象的hashcode()返回值不相等则可以判定两个对象一定不相等。
2.hashCode()返回值和 == 的关系
若 == 返回true,则两边的对象的hashCode()返回值必须相等,若 == 返回false,则两边对象的hashCode()返回值可能相等,也可能不等,因为Java中对象默认的equals()方法就是用==实现的。而Java对于equals和hashCode的规定是如果两个对象equals()方法相等,则hashCode值一定会相同,如果两个对象的hashCode值相同,则它们的equals()方法不一定相等。
3.Java中的hashCode()的作用
hashCode()的作用是为了提高在散列结构存储中元素的查找效率,在线性表中没有作用;只有每个对象的 hash 码尽可能不同才能保证散列的存取性能,,因为hashCode是Object方法,是一个共有方法,在 Java 有些集合类(HashSet)中要想保证元素不重复可以在每增加一个元素就通过对象的 equals 方法比较一次,那么当元素很多时后添加到集合中的元素比较的次数就非常多了,也就是说如果集合中现在已经有 3000 个元素则第 3001 个元素加入集合时就要调用 3000 次 equals 方法,这显然会大大降低效率,于是 Java 采用了哈希表的原理,这样当集合要添加新的元素时会先调用这个元素的 hashCode 方法然后经过一个特殊算法其实也就是通过调用hashCode方法得到一个值然后再根据数组的长度取余得到元素存放的位置。如果这个位置上没有元素则它就可以直接存储在这个位置上而不用再进行任何比较了,如果这个位置上已经有元素了则就调用它的 equals 方法与新元素进行比较,相同的话就不存,不相同就加一个链表,这样一来实际调用 equals 方法的次数就大大降低了,几乎只需要一两次,而 hashCode 的值对于每个对象实例来说是一个固定值。所以很快
4.Java中重写equals()方法时尽量要重写hashCode()方法的原因
当 equals 方法和hashCode 方法有一个规定
如果两个对象根据 equals 方法比较是相等的则他们的hash值也必须相等
如果两个对象根据 equals 方法比较是不相等的,则调用这两个对象中任意一个对象的 hashCode 方法不一定要产生相同的整数结果(尽量保证不相等的对象产生截然不同的整数结果是可以提高散列表性能)。

27深拷贝、浅拷贝区别

他们都能复制基本类型的属性;引用类型的属性复制,复制栈中的变量 和 变量指向堆内存中的对象的指针  但是浅拷贝不能复制堆内存中的对象,深拷贝可以

28异常

空指针异常NullPointerException:这个是开发中最常见的异常了,如果对象为空,然后调其他方法的时候就会报空指针

算术异常ArithmeticException:举个最简单的例子,定义两个变量,让被除数为0

类型转换异常ClassCastException:比如说我创建一个日期类Date的对象,返回值是Object,也就是多态,然后我强转成string类型,编译器也不会报错因为Object类是顶级父类,运行时就会报错,因为      Date类型和String完全是不搭边的两个类

输入类型不匹配异常InputMismatchException:比如说我写一个Scanner,调nextInt方法,如果输入一个字符串就会报这个异常

数据格式异常NumberFormatException:比如说定义一个Stirng类型变量,然后调包装类Integer的parseInt方法,但是我传的String是一个非整型的值,就会报错

数组下标越界异常IndexOutOfBoundsException:调用数组没有的下标

29三次握手,四次挥手

TCP/IP 协议是传输层的一个面向连接的安全可靠的一个传输协议,三次握手的机制是为了保证能建立一个安全可靠的连接,那么第一次握手是由客户端发起,客户端会向服务端发送一个报文,在报文里面:SYN标志位置为1,表示发起新的连接。当服务端收到这个报文之后就知道客户端要和我建立一个新的连接,于是服务端就向客户端发送一个确认消息包,在这个消息包里面:ack标志位置为1,表示确认客户端发起的第一次连接请求。以上两次握手之后,对于客户端而言:已经明确了我既能给服务端成功发消息,也能成功收到服务端的响应。但是对于服务端而言:两次握手是不够的,因为到目前为止,服务端只知道一件事,客户端发给我的消息我能收到,但是我响应给客户端的消息,客户端能不能收到我是不知道的。所以,还需要进行第三次握手,第三次握手就是当客户端收到服务端发送的确认响应报文之后,还要继续去给服务端进行回应,也是一个ack标志位置1的确认消息。通过以上三次连接,不管是客户端还是服务端,都知道我既能给对方发送消息,也能收到对方的响应。那么,这个连接就被安全的建了。

四次握手机制也是由客户端去发起,客户端会发送一个报文,在报文里面FIN位标志位置一,当服务端收到这个报文之后,我就知道了客户端想要和我断开连接,但是此时服务端不一定能做好准备,因为当客户端发起断开连接的这个消息的时候,对于服务端而言,他和还有可能有未发送完的消息,他还要继续发送,所以呢,此时对于服务端而言,我只能进行一个消息确认,就是我先告诉服务端,我知道你要给我断开连接了,但是我这里边还可能没有做好准备,你需要等我一下,等会儿我会告诉你,于是呢,发完这个消息确认包之后,可能稍过片刻它就会继续发送一个断开连接的一个报文啊,也是一个FIN位置1的报文也是由服务端发给客户端的啊,这个报文表示服务端已经做好了断开连接的准备,那么当这个报文发给客户端的时候,客户端同样要给服务端继续发送一个消息确认的报文一共有四次,那么,通过这四次的相互沟通和连接,我就知道了,不管是服务端还是客户端都已经做好了断开连接的准备,于是连接就可以被断开啊,这是我对三次握手和四次挥手的一个理解。

30HTTP、TCP、UDP

TCP协议提供安全可靠的网络传输服务,它是一种面向连接的服务。类似于打电话,必须先拨号,双方建立一个传递信息的通道传输。

而UDP协议是一种数据报协议,它传输的数据是分组报文,它是无连接的,不需要和目标通信方建立连接,类似于写信,所以它的传输不保证安全可靠。但适合大数据量的传输。

Http.协议是超文本传输协议,是一.种相对于TCP来说更细致的协议,TCP以及UDP协议规范的是网络设备之间的通信规范,HTTP是在TCP协议的基础上针对用户服务的协议,用户服务具体体现在应用程序之间的交互,比如我们的javaweb中客户端服务端体系就要用http.协议来规范通信。

计算机网络中有这样一个术语,TIP/IP 网络参考模型,整个计算机网络系统被分为4层,从底层到顶层分别为:网络接口层,网际层,传输层,应用层,每层的通信都有 专门的协议,底层是为上一层提供服务的。我们的TCP以及UDP是传输层的协议,而HTTP协议是处在应用层的协议。结合项目使用TCP和UDP在开发中我们很少见到,但是网络底层都有它们的影子,正常的会话级别的服务:如客户端服务器体系底层就说基于TCP协议的。而邮件发送,短信发送等底层使用的是UDP协议。HTTP协议,客户端/服务器体系的程序都使用HTTP协议来规范通信。

31OutOfMemoryError 异常产生原因及其解决方案(***)   内存泄露和内存溢出OOM

在 Java 虚拟机规范的描述中,除了程序计数器外,虚拟机内存的其它几个运行时区域都有发生 OutOfMemoryError 异常的可能

1.Java 堆溢出

Java 堆用于存储对象实例,那么在对象数量达到最大堆的容量限制后就会产生内存溢出异常。解决办法是一下虚拟机的堆参数(-Xmx 与 -Xms)

32死锁产生的原因和解决办法

死锁是指多个线程因竞争资源而造成的一种僵局(互相等待),如果没有外力作用,这些进程都将无法向前推进。

1.请求和释放资源的顺序不当,会导致死锁。例如,并发进程P1、P2分别持有资源R1、R2,而进程P1申请资源R2,进程P2申请资源R1时,两者都会因为所需资源被占用而阻塞。

2.信号量使用不当也会造成死锁。进程间彼此相互等待对方发来的消息,结果也会使得这些进程间无法继续向前推进。比如我之前写一个程序想让两个线程实现交替输出,我new两个Thread对象,加了wait方法但是忘记加notify通知,结果线程1和2就一直处于阻塞状态.线程1在等待线程2叫醒他,线程2也一直等待线程1叫醒他,结果两个线程都wait了,都处于阻塞状态.

尽量减少同步的资源,减少同步代码块的嵌套结构的使用!

33ArrayList和LinkedList的区别(有序可重复)

ArrayList

底层采用动态数组(自动扩容调整数组容量大小的1.5倍),内存空间连续,查询方便,增删元素不方便。我记得源码写的时oldCapacity加上oldCapacity右移一位也就是除以2

通过源码知道new ArrayList对象时并没有申请数组内存空间,而是在调用add方法添加元素时申请了长度为10的数组,然后把e放到下标为0的位置,当需要扩容时扩展为原来的1.5倍

调用add方法后执行的源码:grow()---Arrays.copeof--newCapacity()---Math.max(10,1)---申请了长度为10的数组,然后把e放到下标为0的位置

LinkedList

底层采用双向链表,内存空间不连续,所以访问不方便,增删元素方便。

源码:linkLast()---Node类型的引用值为last--调用到静态内部类Node创建node节点包含了三个属性pre+item+last----last负责记录最后一个节点指向了node

底层其实就是一直创建节点的过程,始终把元素往后面链

34Set底层原理(无序不可重复)

HashSet底层原理(是Hashmap)

LinkedHashSet类与HashSet类的不同之处在于内部维护了一个双向链表,元素插入集合中的先后顺序,便于迭代

底层是哈希表,元素调用hashCode方法获取对应的哈希码值,再由某种哈希算法计算出该元素在数组中的索引位置。若该位置没有元素,则将该元素直接放入即可。若该位置有元素,首先比较两者的哈希值,若哈希值不相同,则将该元素直接插入到已有元素的后面。若哈希值相同,则调用equals方法与已有元素依次比较。不相等元素直接放入,相等插入失败.

思考:为什么要求重写equals方法后要重写hashCode方法呢?
当两个元素调用equals方法相等时证明这两个元素相同,重写hashCode方法后保证这两个元素得到的哈希码值相同,由同一个哈希算法生成的索引位置相同,此时只需要与该索引位置已有元素比较即可,从而提高效率并避免重复元素的出现。
TreeSet类的底层是红黑树

35哈希冲突的定义和解决方法

因为通过哈希函数产生的哈希值是有限的,而数据可能比较多,导致经过哈希函数处理后仍然有不同的数据对应相同的值。这时候就产生哈希冲突

36进程和线程的区别

进程是重量级的,也就是新建一个进程会消耗CPU和内存空间等系统资源,而线程是轻量级的,一个进程可以包含多个线程,线程就是进程内部的程序流举个例子比如说360杀毒软件,它就相当于一个进程,里面的功能比如说杀毒,清理垃圾就是一个个线程,所以说进程支持多线程
 

37进程之间如何通信 

管道,消息队列,共享内存,socket

38如何保证线程安全

1.原子性:提供互斥访问,同一时刻只能有一个线程对数据进行操作,(atomic,synchronized);

synchronized是一种同步锁,通过锁实现原子操作。

JDK提供锁分两种:

一种是synchronized,依赖JVM实现锁,因此在这个关键字作用对象的作用范围内是同一时刻只能有一个线程进行操作;

另一种是LOCK,是JDK提供的代码层面的锁,依赖CPU指令,代表性的是ReentrantLock。

2.可见性:一个线程对主内存的修改可以及时地被其他线程看到,(synchronized,volatile);

volatile

3.有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序,该观察结果一般杂乱无序,(happens-before原则)。

有序性是指,在JMM中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。

可以通过volatile、synchronized、lock保证有序性。有一个叫happens-before原则,即不需要通过任何手段就可以得到保证的有序性

39线程池的概念和创建方法

首先创建一些线程,它们的集合称为线程池,当服务器接受到一个客户请求后,就从线程池中取出一个空闲的线程为之服务,服务完后不关闭该线程,而是将该线程还回到线程池中。
在线程池的编程模式下,任务是提交给整个线程池,而不是直接交给某个线程,线程池在拿到任务后,它就在内部找有无空闲的线程,再把任务交给内部某个空闲的线程,任务是提交给整个线程池,一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务。
Executors 是个工具类和线程池的工厂类,可以创建并返回不同类型的线程池
 
static ExecutorService newCachedThreadPool()                  创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
static ExecutorService newFixedThreadPool(int nThreads)  创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
static ExecutorService newScheduledThreadPool               创建一个定长线程池,支持定时及周期性任务执行
static ExecutorService newSingleThreadExecutor()             创建一个只有一个线程的线程池
 
其中 ExecutorService 接口是真正的线程池接口,主要实现类是 ThreadPoolExecutor ,常用方法如下:
void execute (Runnable command) 执行任务和命令,通常用于执行Runnable
Future submit (Callable task) 执行任务和命令,通常用于执行Callable
void shutdown() 启动有序关闭

40static、final、static final的区别

final:

final可以修饰:属性,方法,类,局部变量(方法中的变量)

final修饰的属性的初始化可以在编译期,也可以在运行期,初始化后不能被改变。

final修饰的属性跟具体对象有关,在运行期初始化的final属性,不同对象可以有不同的值。

final修饰的属性表明是一个常数(创建后不能被修改)。

final修饰的方法表示该方法在子类中不能被重写,final修饰的类表示该类不能被继承。

对于基本类型数据,final会将值变为一个常数(创建后不能被修改);但是对于对象句柄(亦可称作引用或者指针),final会将句柄变为一个常数(进行声明时,必须将句柄初始化到一个具体的对象。而且不能再将句柄指向另一个对象。但是,对象的本身是可以修改的。这一限制也适用于数组,数组也属于对象,数组本身也是可以修改的。方法参数中的final句柄,意味着在该方法内部,我们不能改变参数句柄指向的实际东西,也就是说在方法内部不能给形参句柄再另外赋值)。

static:

static可以修饰:属性,方法,代码段,内部类(静态内部类或嵌套内部类)

static修饰的属性的初始化在编译期(类加载的时候),初始化后能改变。

static修饰的属性所有对象都只有一个值。

static修饰的属性强调它们只有一个。

static修饰的属性、方法、代码段跟该类的具体对象无关,不创建对象也能调用static修饰的属性、方法等

static和“this、super”势不两立,static跟具体对象无关,而this、super正好跟具体对象有关。

static不可以修饰局部变量。

static final

static修饰的属性强调它们只有一个,final修饰的属性表明是一个常数(创建后不能被修改)。static final修饰的属性表示一旦给值,就不可修改,并且可以通过类名访问。

static final也可以修饰方法,表示该方法不能重写,可以在不new对象的情况下调用

41反射的定义和作用及原理

在写代码时不确定要创建什么类型的对象,也不确定要调用什么样的方法,这些都希望通过运行时传递的参数来决定,这就是反射机制。反射机制就是在运行阶段决定创建什么样的对象并且动态调用方法的机制。只要给定类的名字,就可以通过反射机制来获得类的所有信息。

在哪里使用反射机制?

Class.forName('com.mysql.jdbc.Driver.class');//加载MySQL的驱动类

反射的实现方式:

获取Class对象,有3种方法:
1 Class.forName(“类的全路径”);
2 类名.class
3 对象名.getClass()

实现Java反射的类:
1  Class:该类的实例由Java虚拟机和类加载器自动构造完成,本质上就是加载到内存中的运行时类然后获取对象的信息
2  Field:主要用于描述获取到的单个成员变量信息
3  Constructor:先获取构造方法然后构造对象(跟以前正好相反)

4  Method:提供类或接口中某个方法的信息

42threadlocal是什么?

ThreadLocal 是线程本地存储,在每个线程中都创建了一个 ThreadLocalMap 对象,每个线程可以访问自己内部 ThreadLocalMap 对象内的 value。

使用场景是为每个线程分配一个JDBC 连接 Connection。这样就可以保证每个线程的都在各自的 Connection 上进行数据库的操作,不会出现 A 线程关了 B线程正在使用的 Connection; 还有 Session 管理 等问题。

43线程池实现原理,参数,拒绝策略默认有哪些?

说说线程池的底层工作原理

1、在创建了线程池后,等待提交过来的任务请求。

2、当调用execute()方法添加一个任务请求,线程池会做如下判断:

      2.1 如果正在运行的线程数小于或者等于corePoolSize,那么马上会创建线程运行这个任务;
      2.2 如果正在运行的线程数大于corePoolSize,那么会将这个任务放入队列;
      2.3 如果这时候队列满了并且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程运行这个任务;
      2.4 如果队列满了并且线程数大于或者等于maximumPoolSize,那么会启动饱和拒绝策略来执行。
3、当一个线程完成时,它会从队列中取下一个任务来执行。

4、当一个线程无事可做,且超过一定的时间(keepAliveTime)时,线程池会判断:

       如果当前运行的线程数大于corePoolSize,那么这个线程会停掉。

       所以线程池的所有任务完成后,它最终会收缩到corePoolSize的大小。

线程池七大参数
(1)corePoolSize:线程池中常驻核心线程数
(2)maximumPoolSize:线程池能够容纳同时执行的最大线程数,此值必须大于等于1
(3)keepAliveTime:多余的空闲线程存活时间。当前线程池数量超过corePoolSize时,当空闲时间到达keepAliveTime值时,多余空闲线程会被销毁直到只剩下corePoolSize个线程为止。
(4)unit:keepAliveTime的时间单位
(5)workQueue:任务队列,被提交但尚未执行的任务
(6)threadFactory:表示生成线程池中的工作线程的线程工厂,用于创建线程,一般为默认线程工厂即可
(7)handler:拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数(maximumPoolSize)时如何来拒绝来请求的Runnable的策略

线程池四种拒绝策略
(1)AbortPolicy(默认)  直接抛出RejectedExecutionException异常阻止系统正常运行。
(2)CallerRunsPolicy      “调用者运行”一种调节机制,该策略既不会丢弃任务,也不会抛出异常,而是将某些任务回退给调用者,从而降低新任务的流量。
(3)DiscardOldestPolicy    抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务。   
(4)DiscardPolicy    直接丢弃任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种方案。

44工厂方法模式和抽象工厂模式的区别

工厂方法模式只有一个抽象产品类,而抽象工厂模式有多个。   
工厂方法模式的具体工厂类只能创建一个具体产品类的实例,而抽象工厂模式可以创建多个。

45装饰器模式和代理模式的区别

1、装饰器模式强调的是增强自身,在被装饰之后你能够在被增强的类上使用增强后的功能。增强后你还是你,只不过能力更强了而已;代理模式强调要让别人帮你去做一些本身与你业务没有太多关系的职责(记录日志、设置缓存)。代理模式是为了实现对象的控制,因为被代理的对象往往难以直接获得或者是其内部不想暴露出来。

2、装饰模式是以对客户端透明的方式扩展对象的功能,是继承方案的一个替代方案;代理模式则是给一个对象提供一个代理对象,并由代理对象来控制对原有对象的引用;

3、装饰模式是为装饰的对象增强功能;而代理模式对代理的对象施加控制,但不对对象本身的功能进行增强;

46MySQL、Redis 如何做集群

47volatile与synchronized的区别和使用场景

48Cookie与Session的作用与原理

49wait() 和 sleep() 的区别

50Java内存回收机制

51Java 中实现线程的几种方式?(Thread,Runnable,Callable)区别在哪?

52mysql索引存储的数据结构

53Spring的加载过程

54AOP的使用

55介绍一下AQS

56垃圾回收机制

57线程池参数

58InnerDB

  1. InnerDB 相对传统数据库的优势
  2. InnerDB 索引底层数据结构是什么?
  3. InnerDB 事务如何执行?
  4. innodb隔离级别

59get和post区别

60http状态码

61redis的运行机制

62悲观锁和乐观锁

63数据库如何保证并发安全

64jvm内存结构,为什么年轻带分两个区域

65什么是OOM

Out Of Memory ,当 JVM 因为没有足够的内存来为对象分配空间并且垃圾回收器也已经没有空间可回收时,就会抛出这个error
常见的内存溢出现象
堆内存溢出
JVM 可使用的最大堆内存可以在启动的时候通过 -Xmx 参数指定。堆内存溢出是最为常见的内存溢出问题,发生堆内存溢出时,JVM 会报告如下错误: java.lang.OutOfMemoryError : java heap space。
分析 : 内存溢出顾名思义就是,堆内存不够用了,如果程序设计的最大对内存已经耗尽,那说明程序设计存在问题,不该申请很多内存的逻辑申请了很多的内存,该释放的对象没有释放
解决方式 :
检查程序,看是否有死循环或不必要地重复创建大量对象。找到原因后,修改程序和算法
增加 Java 虚拟机中 Xms (初始堆大小)和 Xmx (最大堆大小)参数的大小。

 

66JVM参数的含义

-Xms:520M 初始堆内存
-Xmx:1024M 最大堆内存
-Xmn:256M 新生代大小
-XX:NewSize=256M 设置新生代初始大小
-XX:MaxNewSize=256M 设置新生代最大值内存
-XX:PermSize=256M 设置永久代初始值大小
-XX:MaxPermSize=256M 设置永久最大值大小
-XX:NewRatio=4 设置老年代和新生代的比值。表示老年代比新生代为 4:1
-XX:SurvivorRatio=8 设置新生代中 Survivor 区和 eden 区的比值,该比值为 Eden 区比 Survivor 区为8: 2
-XX:MaxTenuringThreshold=7 表示一个对象在 Survivor 区移动了 7 次还没有被回收,则进入老年代。该值可减少full GC

 

67请说一下堆与栈的区别

栈为线程私有 , 而堆为线程共享
栈在编译时即可确定内存大小,堆是运行时确定内存大小
栈内存会被自动释放,一旦方法执行完毕就弹栈了,堆内存由用户管理 (Java 中由 JVM 管理,可以手动设置 )
栈实现方式采用数据结构中的栈实现,具有先进后出的顺序特点,main方法压栈是最后一个出栈的    堆为一块一块的内存
栈由于其实现方式,在分配速度上比堆快的多。分配一块栈内存不过是简单的移动一个指针
 

68谈谈你对MySQL索引的理解

在mysql存储的数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式指向数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引。

简单来说索引就是排好序的,帮助我们快速查找的一种数据结构,因为一个索引是由表中的某一列或多列数据组成,这些数据被存储在某个数据结构中. MySQL查找时可以先去包含索引的数据结构里面搜索,找到对应的值之后呢再找到包含这个值对应行的信息返回,提高查询效率,mysql底层是采用的b+树的结构,当然也不是说索引越多越好,因为维护索引也需要占用一定的磁盘空间,所以在设计一个表的索引时要根据规范去设计,索引的字段要经常出现在查询条件,或者分组排序中再添加索引,一旦索引添加过多,当我们进行增删改去操作数据的时候,添加索引的这一列数据要进行维护,也会造成一定性能的损耗

69MySQL如何为表字段添加索引

70索引在什么情况下会失效

1. 如果条件中有 or ,即使其中有条件带索引也不会使用 ( 这也是为什么尽量少用 or 的原因 )
2. 对于多列索引,不是使用的第一部分,则不会使用索引
3. 如果列类型是字符串,那一定要在条件中将数据使用引号引用起来 , 否则不使用索引
4. 如果 mysql 估计使用全表扫描要比使用索引快 , 则不使用索引

 

71什么是事务? 事务的特性有哪些?

 

72并发事务会带来哪些问题?

 

73介绍一下MySQL数据库的锁机制

 

74唯一索引比普通索引快吗? 为什么?

1. 查询效率上比较 : 效率相差不大
普通索引查询流程 :
根据索引找到第一条满足查询条件的记录
找到下一行记录
判断下一行是否满足查询条件
满足重复步骤 2 ,不满足返回满足条件的结果集
唯一索引查询流程 : 根据索引找到第一条满足查询条件的记录
返回该行记录
可以看到,普通索引和唯一索引的差异就是 是否读取并判断下一行记录 InnoDB 中,数据
是按页为单位来读写,读取一行记录会将包含该行的整个页读到内存,因此这个差异只是一
次指针寻找和一次比较判断,对性能的影响微乎其微。
2. 从更新角度看,普通索引可以利用 change buffffer 更新操作的性能比唯一索引要更好

 

75hashmap和hashtable区别

 

76Zookeeper内部原理

1.选举机制

虽然在配置文件中并没有指定MasterSlave。但是Zookeeper工作时,有一个节点为Leader,其他则为FollowerLeader是通过内部的选举机制临时产生的,怎么选举的呢,就是假如我有五台服务器,那么第三台服务器就是leader,因为他的票数超过了半数,后面第四台四五台也是follower  半数机制:集群中半数以上机器存活,集群可用。所以Zookeeper适合安装奇数台服务器

2.监听器原理

1. main 方法(进程)中创建 Zookeeper 客户端的同时就会创建两个线程,一个负责网络连接通信,一个负责监听
2. 监听事件就会通过网络通信发送给 zookeeper
3. zookeeper 获得注册的监听事件后,立刻将监听事件添加到监听列表里
4. 一旦zookeeper 监听到数据变化或路径变化,就会将这个消息发送给监听线程

5.监听线程就会在内部调用process方法(需要我们实现process方法内容,发生变化我怎么应对)

3.写数据流程

1. Client 想向 ZooKeeper Server1 上写数据,必须的先发送一个写的请求
2. 如果 Server1 不是 Leader ,那么 Server1 会把接收到的请求进一步转发给 Leader
3. 这个 Leader 会将写请求广播给各个 Server ,各个 Server 写成功后就会通知 Leader
4. Leader 收到半数以上的 Server 数据写成功了,那么就说明数据写成功了。
5. 随后, Leader 会告诉 Server1 数据写成功了。
6. Server1 会反馈通知 Client 数据写成功了,整个流程结束

 

 

77Linux命令

1.查找

 find /root/tomcat/(指定路径) -name bin(指定名称)

2.查看进程

查看某个进程是否存在  ps -ef | grep mysql

ps -le # 查看系统中所有的进程,使用 Linux 标准命令格式
ps aux # 查看系统中所有的进程,使用 BS 操作系统格式

USER : 该进程是由哪个用户产生的。
PID: 进程的 ID
%CPU : 该进程占用 CPU 资源的百分比,占用的百分比越高,进程越耗费资源。
%MEM : 该进程占用物理内存的百分比,占用的百分比越高,进程越耗费资源。
VSZ : 该进程占用虚拟内存的大小,单位为 KB
RSS : 该进程占用实际物理内存的大小,单位为 KB

3.创建文件

vim haha.txt
touch xixi.txt
echo " 明天你好 " > hehe.txt

4.查看文件

cat catalina.out
more catalina.out
tail可以实时打印最新的日志信息
tail -f catalina.out
# 用于自动刷新的显示文件后 n 行数据内容。
tail -10f catalina.out
# 打印文件最后 n 行数据 .
tail -5 catalina.out
#head 用于显示文件的前 n 行内容。
head -n5 catalina.out
 
5.ls 命令执行什么功能?可以带哪些参数,有什么区别?
ls -a可以查看到隐藏文件
 
6.授予所属用户rwx权限和用户组rw权限,组外用户r权限, 该如何操作 ?
-- 授予权限
chmod 764 test .txt
-rwxrw-r -- 1 root root 6 Dec 2 23:42 test.txt
 
 
 

78spring配置文件常用标签

<bean>标签:创建对象并放到spring的IOC容器

id属性:在容器中Bean实例的唯一标识,不允许重复  class属性:要实例化的Bean的全限定名   scope属性:Bean的作用范围,常用是Singleton(默认)和prototype

<constructor-arg>和<property>标签:属性注入

name属性:属性名称     value属性:注入的普通属性值   ref属性:注入的对象引用值

<context:component-scan base-package="com.lagou"></context:component-scan>包扫描

79spring常用注解

@Autowired根据类型依赖注入

@Value注入普通属性

@Scope标注Bean的作用范围,默认是单例的 

@Component定义在类上实例化bean 

@ComponentScan 用于指定 Spring 在初始化容器时要扫描的包

@Configuration 用于指定当前类是一个Spring 配置类,当创建容器时会从该类上加载注解

@Bean 使用在方法上,标注将该方法的返回值存储到 Spring 容器中

@PropertySource 用于加载 properties 文件中的配置

@Import 用于导入其他配置类

80介绍一下spring,说说为什么要用spring,spring优点?

spring的体系结构由几部分组成的:

第一部分是测试模块

spring提供了对junit的支持,可以帮助我们进行单元测试

第二部分是核心容器

Spring-core模块:提供了框架的基本组成部分,包括控制反转(Inversion of Control,IOC)和依赖注入(Dependency Injection,DI)功能。

Spring-beans模块:提供了BeanFactory,是工厂模式的一个经典实现,Spring将管理对象称为Bean。

Spring-context模块:建立在Core和Beans模块的基础之上,提供一个框架式的对象访问方式, 是访问定义和配置的任何对象的媒介。ApplicationContext接口是Context模块的焦点。

Spring-expression模块:提供了强大的表达式语言去支持运行时查询和操作对象图。

第三部分是aop模块和植入消息传输模块

Spring-aop模块:提供了一个符合AOP要求的面向切面的编程实现,允许定义方法拦截器和切入点,将代码按照功能进行分离,以便干净地解耦。

Spring-aspects模块:提供了与AspectJ的集成功能,AspectJ是一个功能强大且成熟的AOP框架。

Spring-instrument模块:提供了类植入(Instrumentation)支持和类加载器的实现,可以在特定的应用服务器中使用。

第四部分是数据访问/集成模块

数据访问/集成层由JDBC、ORM、OXM、JMS和事务模块组成。

Spring-jdbc模块:提供了一个JDBC的抽象层,消除了烦琐的JDBC编码和数据库厂商特有的错误代码解析

Spring-orm模块:对象关系映射。MyBatis也是一个orm的框架,使用Spring-orm模块可以将这些O/R映射框架与Spring提供的所有其他功能结合使用,例如声明式事务管理功能。

Spring-tx模块(事务模块): 支持用于实现特殊接口和所有POJO(普通Java对象)类的编程和声明式事务管理。

第五部分是Web模块

Web层由Spring-web、Spring-webmvc、Spring-websocket和Portlet模块组成。

Spring-web模块:提供了基本的Web开发集成功能,例如多文件上传功能、使用Servlet监听器初始化一个IOC容器以及Web应用上下文。

Spring-webmvc模块:也称为Web-Servlet模块,包含用于web应用程序的Spring MVC和REST Web Services实现。Spring MVC框架提供了领域模型代码和Web表单之间的清晰分离,并与Spring Framework的所有其他功能集成。

Spring-websocket模块:Spring4.0以后新增的模块,它提供了WebSocket和SocketJS的实现。

 

优点:

1方便解耦,简化开发      Spring就是一个容器,通过控制反转(IOC)可以将所有对象创建和关系维护交给Spring管理,通过xml 配置或注解即可完成 bean 的依赖注入,体现了松耦合

什么是耦合度?对象之间的关系,通常说当一个对象更改时也需要更改其他对象,这就是耦合,耦合度过高会使代码的维护成本增加。要尽量解耦

2AOP编程的支持             Spring提供面向切面编程,方便实现程序进行权限拦截,可以对日志,事务等进行统一处理

3声明式事务的支持          通过配置完成事务的管理,无需手动编程

4方便测试                        降低JavaEE API的使用  Spring对Junit4支持,可以使用注解测试

5方便集成各种优秀框架  不排除各种优秀的开源框架,内部提供了对各种优秀框架的直接支持

 

81什么是ioc?

是一种设计思想,之前都是我们自己new对象,现在把这个权利交给spring,这就是控制反转的意思,在Java开发中,将设计好的对象交给容器控制,而不是显示地用代码进行对象的创建.这样子对象与对象之间是松耦合、使得程序的整个体系结构可维护性、灵活性、扩展性变高。同时相同的功能可以复用,减少对象的创建和内存消耗

82什么是DI?

它是 Spring 框架核心 IOC 的具体实现。在编写程序时,通过控制反转,把对象的创建交给了 Spring,但是代码中不可能出现没有依赖的情况。IOC 解耦只是降低他们的依赖关系,但不会消除。

例如:业务层仍会调用持久层的方法。那这种业务层和持久层的依赖关系,在使用 Spring 之后,就让 Spring 来维护了。简单的说,就是通过框架把持久层对象传入业务层,而不用我们自己去获取。

83什么是aop?

"切面"就是将那些与业务无关,却为业务模块所调用的共同逻辑封装起来,便于减少系统的重复代码,降低模块间的耦合度,利于可操作性和可维护性。 
1. 在程序运行期间,在不修改源码的情况下对方法进行功能增强
2. 逻辑清晰,开发核心业务的时候,不必关注增强业务的代码
3. 减少重复代码,提高开发效率,便于后期维护

 

AOP 的底层是通过 Spring 提供的的动态代理技术实现的。在运行期间,Spring通过动态代理技术动态的生成代理对象,代理对象方法执行时进行增强功能的介入,在去调用目标对象的方法,从

而完成功能的增强。

常用的动态代理技术

JDK 代理 : 基于接口的动态代理技术·:利用拦截器(必须实现invocationHandler)加上反射机制生成一个代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理,从而实现方法增强

CGLIB代理:基于父类的动态代理技术:动态生成一个要代理的子类,子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截技术拦截所有的父类方法的调用,顺势织入横切逻辑,对方法进行增

目标对象:代理的目标对象

Proxy 代理:一个类被 AOP 织入增强后,就产生一个结果代理类

连接点:是指那些可以被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点

 Pointcut(切入点):所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义

Advice(通知/ 增强):所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知 分类:前置通知、后置通知、异常通知、最终通知、环绕通知

Aspect(切面):是切入点和通知(引介)的结合

Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入

 

84Spring常用的注入方式有哪些?

1.xml方式

1.通过有参构造方法完成依赖注入bean并且配置xml的bean标签

<bean id="userDao" class="com.lagou.dao.impl.UserDaoImpl"></bean>
<bean id="userService" class="com.lagou.service.impl.UserServiceImpl">
      <constructor-arg name="userDao" ref="userDao"/>
</bean>

2.通过Set方法完成依赖注入bean同时也是配置xml的bean标签,

<bean id="userService" class="com.lagou.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao"/>
</bean>

2.注解方式简单,直接把创建对象的权利交给spring,在类上加对应的标签即可

@Component //注册所有bean  @Controller //注册控制层的bean  @Service //注册服务层的bean  @Repository //注册dao层的bean

 

85spring中的bean是线程安全的吗?

Spring 不保证 bean 的线程安全。默认 spring 容器中的 bean 是单例的。当单例中存在竞态条件,即有线程安全问题。比如说我之前写过一个小demo,写一个计数器并且让她休眠0.1s,输出的时候让他自增1,测试同时启动多个线程让他们同时执行计数器开始的方法,发现打印的数据是乱七八糟的,也就是说我第一个线程还没执行完第二个线程就开始执行了,可能有多个相同的数字,这里面就存在线程安全问题,

一段程序被多个线程执行,线程执行的先后顺序不一致会导致最终的状态(结果)不同,我们就称这段代码有竟态条件

86Spring中BeanFactory.getBean是否线程安全?

是线程安全的,执行过程中加了 synchronized 互斥锁

 

87spring支持几种bean的作用域?

singleton(单例):只有一个共享的实例存在,所有对这个bean的请求都会返回这个唯一的实例。不管new多少次,只生成一个对象。

prototype(多例):对这个bean的每次请求都会创建一个新的bean实例,类似于new。

所谓单例,就是所有的请求都由一个对象来进行处理,比如我们常用的service和dao层的对象通常都是单例的,而多例则指每个请求用一个新的对象来处理

1. 什么时候用单例? 什么时候用多例 ?

当对象含有可改变的状态时(更精确的说就是在实际应用中该状态会改变),则多例,否则单例。就如dao和service这两层的数据一般不会有相应的属性修改,所以可以考虑用单例,而Controller层会存储很多需要操作的vo类(value object值对象),此时这个对象的状态就会被改变,就要使用多例。

2. 如何配置多例 ? 在xml生成beans时加一个scope属性设置prototype

3.单例模式分为?

饿汉模式

spring singleton的缺省是饿汉模式:启动容器时(即实例化容器时), 为所有spring配置文件中定义的bean都生成一个实例

懒汉模式

在第一个请求时才生成一个实例,以后的请求都调用这个实例 spring singleton设置为懒汉模式:

88spring事务实现方式有哪些?

1编程式事务管理,在代码中调用 commit()、rollback()等事务管理相关的方法

2基于XML的声明式事务控制

3基于注解的声明式事务控制

89tomcat 与 spring、SpringMVC、SpringBoot的关系

Tomcat是一个Web应用服务器,可以作为Servlet容器。它的作用是,解析客户端client发起的request,并组装出HttpRequest、创建HttpResponse,将二者交于内部的HttpServlet处理和填充,如图所示

Tomcat映射处理请求的Servlet是通过web.xml做的。

SpringMVC使用一个DispatcherServlet来接收所有的请求,并把它们分发到不同的controller中来做进一步处理。

SpringMVC = Spring + Web框架,Spring这部分主要是AOP/IOC容器。

SpringBoot是Spring的扩展,简化了Spring的配置,通过starter的方式简化了常用组件依赖的引入,使其更加易用。

SpringBoot内置了tomcat。

90spring 中用到了哪些设计模式

工厂设计模式 : Spring使用工厂模式通过 BeanFactory、ApplicationContext 创建 bean 对象。

代理设计模式 : Spring AOP 功能的实现。

单例设计模式 : Spring 中的 Bean 默认都是单例的。

模板方法模式 : Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。

包装器设计模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。

观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。

适配器模式 :Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller。

91介绍一下spring mvc?

spring mvc 是spring 框架的一部分,一个 mvc 设计模型的表现层框架。

M(model)模型:处理业务逻辑,封装实体    V(view) 视图:展示内容     C(controller)控制器:负责调度分发(1.接收请求、2.调用模型、3.转发到视图)

92springmvc运行流程

1、用户发送请求至前端控制器DispatcherServlet
2、DispatcherServlet收到请求调用HandlerMapping处理器映射器查找Handler。
3、处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
4、DispatcherServlet通过HandlerAdapter处理器适配器调用处理器
5、HandlerAdapter调用处理器Handler
6、Handler执行完成返回ModelAndView
7、HandlerAdapter将Handler执行结果ModelAndView返回给DispatcherServlet
8、DispatcherServlet将ModelAndView传给ViewReslover视图解析器,ViewReslover根据逻辑视图名解析View
9、ViewReslover返回View
10、DispatcherServlet对View进行渲染视图(即将模型数据填充至request域)。
11、DispatcherServlet响应用户

93springmvc三大组件是什么?

1. 前端控制器:DispatcherServlet

用户请求到达前端控制器,它就相当于 MVC 模式中的 C,DispatcherServlet 是整个流程控制的中心,由它调用其它组件处理用户的请求,DispatcherServlet 的存在降低了组件之间的耦合性。

2. 处理器映射器:HandlerMapping

HandlerMapping 负责根据用户请求找到 Handler 即处理器,SpringMVC 提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。

3. 处理器适配器:HandlerAdapter

通过 HandlerAdapter 对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。

 

4. 处理器:Handler【**开发者编写**】

它就是我们开发中要编写的具体业务控制器。由 DispatcherServlet 把用户请求转发到

Handler。由Handler 对具体的用户请求进行处理。

5. 视图解析器:ViewResolver

View Resolver 负责将处理结果生成 View 视图,View Resolver 首先根据逻辑视图名解析成物

理视图名,即具体的页面地址,再生成 View 视图对象,最后对 View 进行渲染将处理结果通过页面展示给

用户。

6. 视图:View 【**开发者编写**】

SpringMVC 框架提供了很多的 View 视图类型的支持,包括:jstlView、freemarkerView、

pdfView等。最常用的视图就是 jsp。一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展

示给用户,需要由程序员根据业务需求开发具体的页面。

 

94spring与Spring mvc 与 Spring boot 有什么区别?

Spring 是一个框架,核心功能是 aop 和 ioc,aop 提供了面向切面编程的能力,ioc 提供了依赖注入的容器。提供了丰富的功能:JDBC 层抽象、事务管理、MVC、Java Mail、任务调度、JMX、JMS、JNDI、EJB、动态语言、远程访问、Web Service... 基于 Spring 衍生出 mvc、boot、security、jpa、cloud 等产品,组成了 Spring 家族产品。

Spring MVC 是基于 Spring 实现了 servlet 规范的 MVC 框架,用于 Java Web 开发。

Spring Boot 是基于 Spring 的一套快速开发整合包。Spring 的配置非常复杂,同时每次开发都需要写很多模板代码与配置,为了简化开发流程,官方推出了 Spring Boot,实现了自动配置,降低项目搭建的复杂度。本质上 Spring Boot 只是配置、整合、辅助的工具,如果是 Java Web 应用,Web 功能的实现还是依赖于 Spring MVC。

 

95 SpringMVC中常用的注解都有哪些,作用分别是什么?

 
互斥锁
 
 

96.介绍一下mybatis

MyBatis是一个优秀的基于ORM(对象关系映射)半自动轻量级持久层框架,它对jdbc的操作数据库的过程进行封装,我们只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、遍历结果集等jdbc繁杂的过程代码

为什么说 MyBatis 是半自动 ORM?它需要在 XML 或者注解里通过手动或插件生成 SQL,才能完成 SQL 执行结果与对象映射绑定。

原始jdbc开发存在的问题如下:

① 数据库连接创建、释放频繁造成系统资源浪费从而影响系统性能

② sql 语句在代码中硬编码,造成代码不易维护,实际应用 sql 变化的可能较大,sql变动需要改变java代码。

③ 查询操作时,需要手动将结果集中的数据手动封装到实体中。

应对上述问题给出的解决方案:

① 使用数据库连接池初始化连接资源

② 将sql语句抽取到xml配置文件中

③ 使用反射、内省等底层技术,自动将实体与表进行属性与字段的自动映射

97.什么是ORM?

mybatis采用ORM思想解决了实体和数据库映射的问题,对jdbc 进行了封装,屏蔽了jdbc api 底层访问细节,使我们不用与jdbc api 打交道,就可以完成对数据库的持久化操作

对象关系映射

O(对象模型): 实体对象,即我们在程序中根据数据库表结构建立的一个个实体javaBean

R(关系型数据库的数据结构): 关系数据库领域的Relational(建立的数据库表)

M(映射): 从R(数据库)到O(对象模型)的映射,可通过XML文件映射

实现:

1让实体类和数据库表进行一一对应关系   

       先让实体类和数据库表对应 再让实体类属性和表里面字段对应

2不需要直接操作数据库表,直接操作表对应的实体类对象

98.配置文件

映射文件UserMapper.xml

<mapper namespace="user">
    <select id="findAll" resultType="com.lagou.domain.User">
        select  * from user
    </select>
</mapper>

核心文件SqlMapConfig.xml

99.Mybaits 的优缺点

优点:

可以在 XML 或注解中直接编写 SQL 语句,比较灵活,方便对 SQL 的优化与调整

SQL 写在 XML 中,与代码解耦,按照对应关系方便管理

XML 中提供了动态 SQL 的标签,方便根据条件拼接 SQL

提供了 XML、注解与 Java 对象的映射机制

与 Spring 集成比较方便

缺点:

参数的数据类型支持不完善。(如参数为Date类型时,容易报没有get、set方法,需在参数上加@param)

100.MyBatis 的适用场景

直接编写 SQL,对应多变的需求改动较小

101.#{} 和 ${} 的区别

MyBatis 在处理 #{} 时,会将 SQL 中的 #{} 替换为 ?,预编译 SQL,再通过 PreparedStatement 的 setXxxx 的方法进行参数赋值。使用 #{} 可以有效地防止 SQL 注入。

SQL 注入是在编译的过程中,注入了某些特殊的恶意 SQL 片段,被编译成了恶意的 SQL 执行操作。预编译是提前对 SQL 进行编译,后面注入的参数不会对 SQL 的结构产生影响,从而避免安全风险。

MyBatis 在处理 ${} 时,会直接把 ${} 替换为参数值,存在 SQL 注入的风险。

#{} 比 ${} 安全,但还是提供了 ${} 这种动态替换参数的方式,是因为有些复杂的 SQL 使用场景通过预编译的方式比较麻烦,且在代码中完全可以做到控制非法参数,有些参数可能是一些常量或字段值。

102.MyBatis 中实体类的属性名与表中的字段名不一致怎么处理?

修改 SQL,给查询字段重命名,如 将 user_id  重命名为 userId

但是如果修改的字段很多的话,可以使用MyBatis 的 XML 映射文件中,使用 <resultMap> 标签,定义数据库字段名与实体 bean 的属性字段名的映射关系

<select id="getUser" parameterType="int" resultMap="”UserMap”">
    select * from user where user_id=#{id}
</select>
<resultMap id=”UserMap” type=”User”>
id标签映射主键字段
    <id property=”id” column=user_id>
result 标签映射非主键字段,property 为实体 bean 属性名,column 为数据库表中字段名
    <result property=“userName” column =”user_name”/>
</reslutMap>

103.MyBatis 中如何配置连接中断或执行超时?

Mybatis 的 XML 配置中,在 <settings> 节点中添加子节点 <setting>,name=defaultStatementTimeout,设置等待数据库响应的超时时间。

<settings>
设置超时时间,它决定数据库驱动等待数据库响应的秒数
  <setting name="defaultStatementTimeout" value="25"/>
</settings>

104.用 MyBatis 如何使用模糊查询?

1.XML 中使用 #{},Java 代码中传入 "%参数值%"

XML:
    <select id=”select” resultMap="User" parameterType="String">
      select * from user where name like #{name}
    </select>
Java:
    list<User> users = mapper.select(Collections.singleMap("name", "%constxiong%"));

2.XML 中使用 ${},Java 代码中传入属性值

XML:
    <select id=”select” resultMap="User" parameterType="String">
        select * from user where name like '%${name}%'
    </select>
Java:
    list<User> users = mapper.select(Collections.singleMap("name", "constxiong"));

105.Mapper 接口如何与写 SQL 的 XML 文件进行绑定的?

  • Mapper 接口与 XML 文件的绑定是通过 XML 里 mapper 标签的 namespace 值与 Mapper 接口的 包路径.接口名 进行绑定

  • Mapper 接口的方法名与 XML 文件中的sql标签的id参数值进行绑定

106.Mapper 接口方法如何与注解里的 SQL 进行绑定的?

在MapperRegistry这个类下有一个addMapper方法是专门解析生成MapperStatement对象,根据这个MappedStatement,执行 SQL

107.Mapper 接口并没有实现类,它是如何工作的?

在MapperRegistry这个类下有一个getMapper方法是专门解析生成MapperProxyFactory 对象

  • Mapper 接口的 Class 对象,被解析包装成 MapperProxyFactory 对象

  • SqlSession 获取 Mapper 接口时,通过 MapperProxyFactory 对象实例化 MapperProxy 动态代理 Mapper 接口

  • 执行 Mapper 接口的方法时,动态代理反射调用 MapperProxy 的 invoke 方法,根据接口与方法找到对应 MappedStatement 执行 SQL

//xml配置文件路径
String resource = "mybatis-config.xml";
//读取配置
InputStream inputStream = Resources.getResourceAsStream(resource);
//构建 SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//通过 SqlSessionfactory 获取 SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession(true);
//通过 SqlSession 获取 Mapper 接口
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

108.Mapper 接口中能不能根据参数不同进行重载?no

MapperedStatement 的 id 属性值等于 Mapper 接口的 包名.接口名.方法名 作为 key 添加到 Configuration 对象的 Map 结构的 mappedStatements 属性里

查找 MapperedStatement 执行 SQL 时,也是根据 Mapper 接口的 包名.接口名.方法名 作为 SqlCommand 的 name 属性值,在 Configuration 对象的 mappedStatements 找到对应的 MapperedStatement 对象

即接口中方法名相同 key 就相同,只能获取一个 MapperedStatement 对象,无法重载

109.MyBatis 有哪些分页的方式?分页插件的原理是什么?

在 xml 或者 注解的 SQL 中传递分页参数    使用分页插件 Mybatis-PageHelper

分页插件的原理是,使用 MyBatis 提供的插件接口,拦截待执行的 SQL,根据数据库种类的配置与分页参数,生成带分页 SQL 语句,执行。

110.MyBatis 是如何将 sql 执行结果转换为目标对象并返回的?有哪些映射形式?

方式一、<select> 标签使用 resultType 参数,传递 Java 类,sql 中 select 的字段名保持与 Java 类属性名称一致

方式二、使用 <resultMap> 标签,定义数据库列名和对象属性名之间的映射关系

方式三、使用注解 select 的字段名保持与接口方法返回的 Java 类或集合的元素类的属性名称一致

根据解析得到 ResultMap 结合 sql 执行结果,通过反射创建对象,根据映射关系反射填充返回对象的属性,源码体现在 DefaultResultSetHandler 的 handleResultSets 方法

16.MyBatis 如何批量插入?

方式一、打开批量插入的 SqlSession

SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
for (int i = 36; i <= 45; i++) {
    userMapper.insertUser(new User(i, "ConstXiong" + i));
}
sqlSession.commit();
sqlSession.close();

方式二、拼接批量插入的 insert SQL

List<User> userList = new ArrayList<>();
for (int i = 46; i <= 55; i++) {
    userList.add(new User(i,"ConstXiong" + i));
}
userMapper.insertUserBatch(userList);

<insert id="insertUserBatch" parameterType="java.util.List">
    insert into user values
    <foreach collection="list" item="item" separator =",">
        (#{item.id}, #{item.name})
    </foreach>
</insert>

111.MyBatis 如何获取返回自增主键值?

可以在 insert 方法执行完之后把 获取到对象的id传入的对象的属性

for (int i = 0; i <10; i++) {
    User user = new User(null, "constxiong" + i);这里 user.id = null
    userMapper.insertUser(user);
    System.out.println("id:" + user.getId());插入数据库后,这里的 user.id 为主键值
}

112.Mapper 接口如何传递多个参数?

方式一、接口中传多个参数,在 xml 中使用 #{param0}、#{param1}…

方式二、使用 @param 注解指定名称,在 xml 中使用 #{名称}

方式三、多个参数封装到 Java bean 中

方式四、多个参数指定 key,put 到 Map 中

113.MyBatis 中有哪些动态 SQL 标签?它们的作用分别是什么?如何实现的?

  • if: 根据条件判断

  • where: where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除

  • foreach: 对集合进行遍历

  • choose、when、otherwise: 组合使用,选择多个条件中的一个

  • trim: 定制类似 where 标签的功能

  • set: 用于动态包含需要更新的列,忽略其它不更新的列

  • bind: 允许你在 OGNL 表达式以外创建一个变量,并将其绑定到当前的上下文

  • script: 要在带注解的映射器接口类中使用动态 SQL,可以使用 script 元素

114.Mapper XML 映射文件中支持哪些标签?分别什么作用?

  • resultMap: 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素

  • sql: 定义可被其它语句引用的可重用语句块

  • include: 引入 sql 片段

  • selectKey: 为不支持自增的主键生成策略标签

  • cache: 该命名空间的缓存配置

  • cache-ref: 引用其它命名空间的缓存配置

  • 9 种动态 SQL 标签

115.MyBatis 如何进行 1对1 和 1对多 的关联查询?

public class User {
    private Integer id;

    private String name;

    private String mc;

    private Info info;

    private List<Article> articles;

一对一:

<!-- 1对1 -->
<select id="selectUserWithInfo" resultMap="UserWithInfo">
    select user.id, user.name, info.user_id, info.name as info_name from user,info where user.id = info.user_id
</select>

<resultMap id="UserWithInfo" type="constxiong.po.User">
    <id property="id" column="id"/>
    <result property="name" column="name"/>
    <!-- 1对1 -->
    <association property="info" javaType="constxiong.po.Info">
        <id property="userId" column="user_id"/>
        <result property="name" column="info_name"/>
    </association>
</resultMap>

一对多:

<!-- 1对多 -->
<select id="selectUserWithArticles" resultMap="UserWithArticles">
    select user.id, user.name, article.user_id, article.title from user,article where user.id = article.user_id
</select>

<resultMap id="UserWithArticles" type="constxiong.po.User">
    <id property="id" column="id"/>
    <result property="name" column="name"/>
    <!-- 1对多 -->
    <collection property="articles" ofType="constxiong.po.Article">
        <result property="userId" column="user_id"/>
        <result property="title" column="title"/>
    </collection>
</resultMap>

116.什么是 MyBatis 的接口绑定?有哪些实现方式?

接口绑定就是把接口里的方法与对应执行的 SQL 进行绑定,以及 SQL 执行的结果与方法的返回值进行转换匹配。

  • 接口与对应 namespace 的 xml 进行绑定,接口方法名与 xml 中sql标签的 id 参数值进行绑定

  • 接口方法与方法上的 @Select的注解及注解里 SQL 进行绑定

117.MyBatis 的 SQL 执行日志如何开启?

<settings>
    <setting name="logImpl" value="STDOUT_LOGGING"></setting>
</settings>

118.MyBatis 中注册 Mapper映射文件有哪些方式?

方式一:在配置文件 mybatis-config.xml 中添加及其子标签,编写对应的 Mapper 接口与 XML

<mappers>
    <mapper resource="constxiong/mapper/UserMapper.xml"/>
</mappers>

方式二、硬编码方式在 configuration 对象中注册 Mapper 接口

//配置
Configuration configuration = new Configuration(environment);
//注册
configuration.addMapper(UserMapper.class);

119.MyBatis 的源码中的核心类有哪些?如何实现框架功能的?

  • Configuration: 配置类

  • Environment: 环境信息

  • SqlSessionFactoryBuilder: SqlSessionFactory 构造者类

  • SqlSessionFactory: SqlSession 工厂类

  • SqlSession: 执行 SQL 的一次会话

  • XMLStatementBuilder: Mapper xml 配置文件中 SQL 标签的构造者类,构造 MappedStatement

  • MappedStatement: 通过 Mapper xml 或注解,生成的 select|update|delete|insert Statement 的封装

  • MapperProxy: Mapper 接口的代理类

  • MapperMethod: Mapper 接口的方法,包含匹配的 SQL 执行种类和具体方法签名等信息

  • Executor: 执行器,是 MyBatis 调度的核心,负责 SQL 语句的生成和查询缓存的维护

  • ResultMap: 返回值类型匹配的类

  • StatementHandler: Statement 处理接口,封装 JDBC Statement 操作

  • ParameterHandler: 参数处理接口,负责对用户传递的参数转换成 JDBC Statement 所需要的参数

  • ResultSethandler: 执行结果处理接口

  • TypeHandler: 返回类型处理接口

120.Spring 中如何配置 MyBatis?

<context:property-placeholder location="classpath:db.properties" ignore-unresolvable="true" />
    <bean id="dataSource" class="org.apache.ibatis.datasource.pooled.PooledDataSource">
        <property name="driver" value="${jdbc_driver}" />
        <property name="url" value="${jdbc_url}"/>
        <property name="username" value="${jdbc_username}"/>
        <property name="password" value="${jdbc_password}"/>
    </bean>

    <!-- spring 和 Mybatis整合 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="mapperLocations" value="classpath:constxiong/mapper/*.xml" />
    </bean>

    <!-- DAO接口所在包,配置自动扫描 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="constxiong.mapper"/>
    </bean>

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值