面试题2.0

面经试题

Java基础

为什么会出现4-3.6=0.400001

在二进制系统中无法精确的表示1/10

所以浮点数不适用于无法接受舍弃误差的金融计算中,应使用BigDecimal类型

本质:

0.5能够表示,因为它可以表示成为1/2
0.75也能够表示,因为它可以表示成为1/2+1/(2^2)
0.875也能够表示,因为它可以表示成为1/2+1/(22)+1/(23)
0.9375也能够表示,因为它可以表示成为1/2+1/(22)+1/(23)+1/(2^4)
但是0.1不能够精确表示,因为它不能表示成为1/(2^n)的和的形式

finall作用

  1. 修饰引用
    • 基本类型:标记该引用为常量
    • 引用类型:对象本身可修改,指向对象的引用不可修改
  2. 方法:可被继承,不可重写
  3. 类:无法继承

重写和重载的区别

重写发生在子类继承父类时,对父类的方法进行的改写操作

重载是发生在一个类中,相同的函数名,但是有不同的参数,参数的个数不一样,参数的位置不一样,参数的类型不同,这就是重载,常见的有构造函数,有参构造和无参构造。

sleep和wait的区别

相同点:一旦执行方法,可以使当前线程进入等待状态

区别:

  • 声明位置不同,sleep在Threa类中,wait在Object类中

  • sleep必须指定睡眠时间,wait不要求

  • sleep可在任何地方使用,wait只能在同步方法代码块中使用

  • sleep中没有释放锁,而wait释放锁,导致其他线程可以使用同步控制块

  • sleep必须捕获异常,而wait不用

Java的集合类

  • List
    • ArrayList,基于数组
    • LinkList,基于链表
  • set
    • HashSet,基于哈希表
    • LinkHashSet,基于链表
    • TreeSet,基于树
  • map
    • HashMap,基于哈希表
    • LinkHashMap,基于数组
    • TreeMap,基于哈希表

ArrayList

  • 底层是一个Object[]
  • 在jdk1.8中默认创建容量为0的数组
  • 在容量用完时会自动扩容
  • 优化:可以使用ArrayList(int capacity)设置初始化的容量,可以避免一开始就不断扩充容量,影响效率
  • 查询效率高,增删效率差

LinkedList

  • 底层是一个双向链表
  • 增删效率高,查询效率差
  • 查询指定下标的元素时,需要从头节点开始遍历
  • LinkedList.add方法只能将元素添加道链表末尾,如果要添加到中间,需要使用迭代器方法:ListIterator.add

Vector

  • 底层是一个数组
  • 当容量用完是,默认扩充为原容量的两倍
  • 线程安全的,内置方法都带有synchronized关键字
  • 如何将ArrayList变成线程安全的,调用Collections工具类中的synchronizedList(List list)方法

Set

  • 是一种泛型
  • 在jdk1.5引入,之前都是使用Object[]
  • Object[]的缺点
    • 需要强制转换类型
    • 使用方法前需要用instanceof判断对象类型
  • 使用Set优点
    • 减少强制转换类型的次数
    • 类型安全
    • 运行阶段在JVM看不到泛型类型
    • 支持lambda表达式

HashSet

  • 基于哈希表
  • 无序,不可重复

TreeSet

  • 底层是树
  • 无序,不可重复,但是可以排序
  • HashMap的key部分底层是HashSet,TreeMap的key部分底层是TreeSet

Map

  • Map和Collections没有继承关系
  • Map以键值对形式存储数据,key和value都是存储对象的内存地址(引用)
  • Map的迭代方法:
    • 第一类:效率低下,得到Map的key集合keySet,再取出对应的value
      1. 通过foreach遍历map.keySet,取出对应的value
      2. 使用迭代器迭代keySet,取出Value
    • 第二类:效率高,直接从结点中取出key和value
      1. 调用map.entrySet,然后foreach遍历keySet
      2. 调用map.entrySet,然后通过迭代器遍历keySet

HashMap

  • 基于数组和单链表
  • 数组中每一个元素都是单链表
  • 单链表中每一个结点Node的hash值不一定相同,但是下标一定是相同的,因为下标都是通过hash值哈希算法计算的
  • 数组查询效率高,单链表增删效率高,HashMap结合了两者的优点
  • 无序:因为不知道值挂到哪一个单链表上去了
  • 不可重复:使用equals实现不可重复
  • 默认初始容量为16(必须为2的次方),加载因子为0.75,即容量使用了3/4就开始扩容
  • 在jdk1.7及其以前,使用数组+单链表实现;在jdk1.8时,使用数组+单链表+红黑树实现
    • 单链表元素超过8个,将单链表转换成红黑树
    • 当红黑树结点小于6个,将红黑树转换成单链表
    • 这样做是为了提高检索效率,二叉树的检索会缩小检索范围,提高效率

put()方法原理

  1. 将key,value封装到Node中
  2. 调用key的hashCode()获得hash值
  3. 使用hash值进行哈希函数计算得到下标
    • 如果该下标没有元素,将该结点放入
    • 如果有元素,将Node的key与该链表上的每个结点的key进行equals对比
      • 如果都返回false,将该节点放入到该链表末尾
      • 如果有一个返回了true,那么链表中对应这个结点的value会被Node的value覆盖(保证了不可重复)

注:

  • HashMap的key,value可以为null,但是只能有一个(不可重复)
  • HashTable的key,value都不能为null

get()方法原理

  1. 先调用key的hashCode()函数计算hash值
  2. 通过哈希算法,将hash值转化为数组的下标
  3. 通过下标定位数组的某个位置:
    • 如果这个位置什么都没有,返回null
    • 如果这个位置有一个链表,将链表所有结点的key与当前key进行equals比对
      • 如果都返回false,那么get方法返回null
      • 如果有一个equals返回了true,那么返回这个结点的value

注:放在HashMap中的key的元素需要同时重写hashCode和equals方法

Hash冲突及其解决

hash冲突:键(key)通过hash函数得到的结果作为地址去存放键值对(key-value)(这就是hashmap的存值方式),当时计算发现这个地址已经存放了键值对,就会产生冲突,这就是hash冲突

解决方法:

**1、开放定址法:**当冲突发生时,通过某种探测技术在散列表上形成一个探测序列,沿着此序列逐个查找,直到找出一个开放的地址。常见的探测技术有:线性探测、再平方探测、伪随机探测

**2、再哈希法:**对冲突的哈希值再次进行哈希处理,直到没有哈希冲突。

**3、链地址法:**将所有哈希地址为i的元素构成一个称为同义词链的单链表,并将单链表的头指针放在哈希表的第i个单元中。链地址法适用于经常进行插入和删除的情况。

**4、建立公共溢出区:**将哈希表分为基本表和溢出表两部分,凡是和基本表发生冲突的元素,都放入溢出表。

ArrayList和LinkLsit的区别

ArrayList基于数组,对随机index的访问比较快,空间浪费体现在列表结尾的预留空间

LinkList基于链表,增加和删除元素比较快,空间浪费体现在每个元素都需要消耗一定的空间

ArrayList和Vector的区别

Vector是线程安全的类,而ArrayList是非线程安全的

Java8新特性

  • Lambda表达式和函数式编程
  • 接口的默认方法和静态方法
  • 方法引用
  • 重复注解

HashMap在jdk1.7和jdk1.8的区别

  • 底层结构:1.7采用数组+链表,1.8采用数组+链表+红黑树
  • 初始化方式:哈希表为空时,1.7采用inflateTable()初始化一个数组;1.8采用resize()扩容
  • put()实现方式:1.7采用头插法;1.8采用尾插法
  • hash()实现方式:1.7直接计算key的hashCode值;1.8采用key的hashCode异或key的hashCode进行无符号右移16位的结果

HashMap和Hashtable的区别

HashMap可以存放null

HashTable不能存放null

HashMap不是线程安全的类

HashTable是线程安全的类

StringBuffer和StringBuilder的区别

StringBuffer 是线程安全的

StringBuilder 是非线程安全的

Object类中的方法

  • getClass:返回这个实体的Class对象
  • clone:拷贝这个对象
  • toString:返回对象的字符串形式
  • hashCode:生成这个对象的哈希值
  • equals:比较两个对象是否相等
  • finalize:重置系统资源
  • wait:线程等待
  • notify,notifyAll:线程唤醒

synchronized使用及其原理

synchronized关键字是用来控制线程同步

synchronized的使用

public class testThead implements Runnable {

    int count = 0;

    @Override
    public void run() {
        synchronized (this) {
            System.out.println(count++);
        }
    }
}

testThead的调用

testThead thead = new testThead();
thead.run();
thead.run();

synchronized关键字的作用:

  1. 原子性:保证线程互斥的访问同步代码
  2. 可见性:保证共享变量的的修改随时可见。通过Java内存模型实现:“对一个变量unlock前,必须同步到内存中;对一个变量lock,会清空内存中此变量的值“。
  3. 有序性:有效解决重排序问题。

synchronized关键字可以把任何一个非null的对象作为”锁“,在JVM中,锁也叫对象监听器(Object Monitor)

实现原理

Java对象在内存中的布局主要由:对象头、实例数据和填充对齐组成。而synchronized的锁就存放在对象头中

synchronized只能实现重量级的锁,而在Java中,重量级的锁是由Monitor(监听器)对象实现的。

  1. 当多个线程访问同步代码块时,首先会进入EntryList(重量级锁采用双向链表实现),会通过CAS(比较再交换算法)的方式,将Monitor中的owner字段设置为当前线程,同时count加1,如果发现之前的ower的值指向当前的线程,那么recursions也会加1。如果其他线程想要调用,导致CAS尝试获取失败,那么将会回到EntryList。
  2. 获得锁的线程调用wait()方法,会将ower的值置为null,同时count减1,recursions减1,当前线程加入到WaitSet中,等待被唤醒。
  3. 同步代码块执行完成时,释放锁,count减1,recursions减1,当recursions为0时,说明线程释放了锁

wait()、notify()方法需要再同步代码块中执行的原因:因为这些方法需要调用ObjectMonitor(对象监听器)对象的内部方法来完成。

总结:synchronized实现重量级的锁,采用Monitor实现,通过操作Monitor中的ower、recursions的值来实现线程调用的成功与失败。


参考链接:https://zhuanlan.zhihu.com/p/377423211

Java如何保证多线程安全

java的多线程体现在:

  • 原子性:一个或多个操作在CPU中的执行不被打断
  • 可见性:一个线程对共享变量的修改,其他线程能够马上看到
  • 有序性:程序执行的顺序按照代码的先后顺序执行

导致原因:

  • 线程切换带来的原子性问题
  • 缓存导致的可见性问题
  • 编译优化带来的有序性问题

解决方法:

  • 使用synchronized、LOCK解决原子性问题
  • 使用synchronized、LOCK、volatile解决可见性问题
  • Happens-Before 规则可以解决有序性问题

volatile作用

1、保证内存可见性

可见性:一个线程对共享变量的修改,其他线程能够马上看到

实现原理

(1)当对非volatile变量进行读写时,每个线程先从主内存拷贝到CPU缓存中,如果计算机有多个CPU,每个线程可能在不同的CPU上被处理,这意味着每个线程可以拷贝到不同的CPU cache中,也就不能保证可见性。

(2)volatile变量不会被缓存到寄存器或者其他处理器不可见的地方,保证了每次读写变量都是从主内存中读,跳过了CPU cache这一步。所以一个线程改变了这个变量的值,其他线程能够马上看到。

2、禁止指令重排

(1)指令重排是JVM为了优化指令、提高程序运行效率,在不影响单线程程序执行结果的前提下,尽可能地提高并行度

(2)指令重排包括编译器重排序运行时重排序

(3)volatile变量禁止指令重排序。针对volatile变量,在读写操作指令前后会插入内存屏障,指令重排序时不能把后面的指令重排序到内存屏障前,这样避免了多线程环境下程序乱序执行的情况。

volatile与synchronized的对比

synchronized可以保证并发编程的三大特性:原子性、可见性、有序性,而volatile只能保证可见性和有序性,不能保证原子性,也被称为轻量级的synchronize

  • volatile是线程同步的轻量实现,所以性能肯定会比synchronized的好
  • volatile只能用于修饰变量,而synchronized可以修饰方法和代码块
  • volatile能保证数据的可见性,不能保证原子性,而synchronized两者都可以保证
  • volatile主要用于解决多个线程之间的可见性,而synchronized主要解决多个线程之间访问资源的同步性

Synchronized和Lock的原理和区别

可重入锁:当线程获取某个锁后,还可以继续获取它,可以递归调用,而不会发生死锁

不可重入锁:与可重入相反,获取锁之后不能重复获取,否则会死锁

非公平的锁:获取锁的时候,并不是根据所申请的时间先后给等待的线程分配锁

相同点:

  • 都是可重入锁
  • 保证了可见性和互斥性
  • 都可以用于控制多线程对共享对象的访问

不同点:

  • ReentrantLock等待可中断(等待锁的释放)
  • Synchronized是Java中的关键字是JVM级别的锁,而ReentrantLock是一个Lock接口下的实现类,是API层面的锁
  • Synchronized中的锁是非公平的,ReentrantLock默认是非公平的,但可以通过修改参数实现公平锁
  • Synchronized隐式获取锁和释放锁,ReentrantLock显式获取锁和释放锁,需要再finally控制块中释放锁,否则会造成死锁

NIO的实现原理

java.nio全称java non-blocking IO,是指jdk1.4 及以上版本里提供的新api(New IO) ,为所有的原始类型(boolean类型除外)提供缓存支持的数据容器,使用它可以提供非阻塞式的高伸缩性网络

NIO在JDK1.4引入的,弥补了IO的不足,提供了高速的、面向块的IO

NIO核心组件:

  • 通道(Channels)
  • 缓存区(Buffers)
  • 选择器(Selectors)

NIO和IO的区别:

  • 面向流的IO一次处理一个字节数据。
  • 面向块的NIO一次处理一个数据块,按块处理数据比按流处理数据快很多,但是却缺少了一些面向流IO的优雅性和简单性

底层原理

  • Channels是一个通道,可以通过它读取和写入数据,和流不同的是,通道是双向的,而流是单向的。可以通过通道将数据读出到Buffer中,也可以将数据写入到Buffer中。
  • NIO是面向缓冲区的。所有的数据都通过缓冲区进行处理,Buffer是缓冲区对象,无论是读取还是写入,数据都会先进入Buffer。Buffer本质是一个数组,通常是一个字符数组,也可以是其他类型的数组。
  • Selectors选择器是多路复用器,可以监听网络IO的状态。它可以不断轮询(轮流询问)channel,如果某个channel有连接、读写事件发生,那么这个channel就处于就绪状态,就会被Selectors轮询到

加分部分

Buffer有3中属性:

  • capacity:最大容量;
  • position:当前已经读写的字节数;
  • limit:还可以读写的字节数。

单点登录

在多个应用系统中,只需要登录一次,就可以访问其他相互信任的应用系统。

同域下的单点登录

面试题2.0_第1张图片

那么我们如何解决这两个问题呢?针对第一个问题,sso登录以后,可以将Cookie的域设置为顶域,即.a.com,这样所有子域的系统都可以访问到顶域的Cookie。我们在设置Cookie时,只能设置顶域和自己的域,不能设置其他的域。比如:我们不能在自己的系统中给baidu.com的域设置Cookie。

Cookie的问题解决了,我们再来看看session的问题。我们在sso系统登录了,这时再访问app1,Cookie也带到了app1的服务端(Server),app1的服务端怎么找到这个Cookie对应的Session呢?这里就要把3个系统的Session共享,如图所示。共享Session的解决方案有很多,例如:Spring-Session。这样第2个问题也解决了。

不同域下的单点登录

同域下的单点登录就实现了,但这还不是真正的单点登录。

同域下的单点登录是巧用了Cookie顶域的特性。如果是不同域呢?不同域之间Cookie是不共享的,怎么办?

这里我们就要说一说CAS流程了,这个流程是单点登录的标准流程。

参考链接:https://developer.aliyun.com/article/636281#slide-2

用户自己写一个String类,会发生什么?

假设用户自己写了一个String类,会加载不进内存

原因

基于JVM的双亲委派机制,类加载器收到了类加载的请求,会将这个请求委派给他的父类加载器。而只有父类加载器无法完成加载请求时,子类才会被加载。这样用户自定义的String的加载请求最终到达顶层BootStrap ClassLoader启动类加载器,启动类加载器加载的是系统中的String对象,而用户写的String对象不会被加载。

面试题2.0_第2张图片

equals()和hashcode()什么时候重写,为什么重写,怎么重写?

  • 在判断两个对象相等时,该对象需要同时重写equals()和hashcode()
  • 同时重写的原因:hashcode()根据一定的算法将对象的信息映射成一个散列值,所以会出现不同对象的hashCode可能相同,但hashCode不同的对象一定不相等。而equals()用于比较两个对象是否具有相同的引用,所以 不管两个对象的值是否相同,equals()比较都为false。所以,单独重写其中一个都会出现异常,需要同时重写。举个例子,在set集合中,重复的数据会被去重,但set中的对象只重写equals()方法时,相同数据的对象会在set中重复存在
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

public class EqualsExample {
    public static void main(String[] args) {
        // 对象 1
        Persion p1 = new Persion();
        p1.setName("Java");
        p1.setAge(18);
		// 对象 2
        Persion p2 = new Persion();
        p2.setName("Java");
        p2.setAge(18);
		// 创建 Set 集合
        Set<Persion> set = new HashSet<Persion>();
        set.add(p1);
        set.add(p2);
		// 打印 Set 中的所有数据
        set.forEach(p -> {
            System.out.println(p);
        });
    }
}


class Persion {
    private String name;
    private int age;

    // 只重写了 equals 方法
    @Override
    public boolean equals(Object o) {
        if (this == o) return true; // 引用相等返回 true
        // 如果等于 null,或者对象类型不同返回 false
        if (o == null || getClass() != o.getClass()) return false;
        // 强转为自定义 Persion 类型
        Persion persion = (Persion) o;
        // 如果 age 和 name 都相等,就返回 true
        return age == persion.age &&
                Objects.equals(name, persion.name);
    }
	
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    
     @Override
    public String toString() {
        return "Persion{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

面试题2.0_第3张图片

  • 重写方式:
@Override
public boolean equals(Object o) {
    if (this == o) return true; // 引用相等返回 true
    // 如果等于 null,或者对象类型不同返回 false
    if (o == null || getClass() != o.getClass()) return false;
    // 强转为自定义 Persion 类型
    Persion persion = (Persion) o;
    // 如果 age 和 name 都相等,就返回 true
    return age == persion.age &&
        Objects.equals(name, persion.name);
}

@Override
public int hashCode() {
    // 对比 name 和 age 是否相等
    return Objects.hash(name, age);
}

Java多态,动态绑定

同一个接口,使用不同的实例会执行不同的方法。

实现多态的三个条件:

  • 继承
  • 子类重写父类的方法
  • 父类引用变量指向子类对象

好处:

  • 消除耦合关系

Java的静态绑定和动态绑定

  • 绑定:通俗的讲绑定就是指你调用的方法是绑定在哪个类上的。例如父类 Father 有 方法 run();子类 Son 也有方法 run();那么你调用的是哪个类的方法?

  • 静态绑定:程序编译期间的绑定(编译成.class文件就完成了绑定),java中的方法被final、static、private修饰的和构造方法都是静态绑定。

  • 动态绑定:在程序中根据对象的类型进行绑定

java的四种引用

在jdk1.2之前

若一个对象不被任何变量引用,那么程序就无法再使用这个对象,也就是说对象处于可触及状态(reachable),程序才能够使用它。如果reachable类型的数据存储的是另一块内存的地址,那么就称这块内存代表一个引用。

在jdk1.2之后

对引用的概念进行了扩充,将引用分为强引用、软引用、弱引用、虚引用(引用强度逐渐减弱)

1、强引用(StrongReference)

我们使用的引用大部分都是强引用。

如果一个对象具有强引用,那么垃圾回收器一定不会回收它。当内存空间不足时,JVM宁愿抛出 OutOfMemoryError 错误,使程序异常终止,也不会随意回收具有强引用的对象来解决内存不足的问题。

2、软引用(SoftReference)

可有可无的,如果内存空间不足,垃圾回收器就会回收这些对象的内存。软引用可以用来实现内存敏感的高速缓存。

3、弱引用

也是可有可无的。

软引用和弱引用的区别在于:

具有弱引用的对象拥有更加短暂的生命周期。在垃圾回收器线程扫描它所管辖的区域时,只要发现了具有弱引用的对象,不管内存空间是否充足,都会回收它的内存。不过由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现具有弱引用的对象。

4、虚引用(PhantomReference)

  • 顾名思义,是形同虚设的。与其他引用不同的是,虚引用不能决定对象的生命周期。如果一个对象具有虚引用,那么它跟没有引用一样,随时都可能被垃圾回收器回收
  • 虚引用常用于跟踪对象被垃圾回收的活动
  • 虚引用与软引用和弱引用的区别:
    • 虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有一个虚引用,在内存回收前,就会把这个虚引用加入到引用队列中。
    • 程序通过判断引用队列中是否加入了虚引用,来了解被引用的对象是否将要被回收,从而在内存回收前,做出一定的操作。
  • 注意:在程序设计中,一般很少使用弱引用和虚引用,使用软引用的情况比较多。软引用可以加速JVM对垃圾回收的回收速度,维护系统安全,防止内存溢出等问题。

JVM

G1垃圾回收器(TODO)

CMS垃圾回收器(TODO)

CMS(Concurrent Mark Sweep)是一种以获取最短回收停顿时间为目标的收集器。

CMS是基于“标记-清除”算法实现的。

CMS的工作流程分为7步:

1、初始标记

2、并发标记

3、预清理

4、可被终止的预清理

5、重新标记

6、并发清理

7、并发重置

初始标记

暂停所有的其他线程,并记录下直接与root相连的对象

框架

SpringBoot

谈谈SpringBoot和Spring的关系,核心功能、优点

SpringBoot是为了简化Spring应用的初始化搭建而诞生的。该框架使用了特定的方式来进行配置。

Spring Boot本身并不提供Spring的核心功能,而是作为Spring框架的脚手架框架,以达到快速构建项目,预置第三方配置,开箱即用的目的。

SpringBoot基于Spring4.0设计,本质上来说SpringBoot就是Spring,它帮你完成了一些bean的配置

核心功能

  • 自动配置:针对Spring的很多应用功能,SpringBoot自动提供相应配置
  • 起步依赖:为项目的依赖管理提供帮助。也就是特殊的Maven依赖和Gradle依赖
  • 端点控制:对正在运行的项目提供监控

优点

  • 快速搭建项目
  • 内嵌Tomcat或Jetty等Servlet容器
  • 提供运行时的应用监控
  • 与云计算天然集成
  • 自动配置项目以简化Maven配置
  • 自动配置Spring容器
  • 没有代码生成,不需要XML配置
  • 可以极大的提高开发、部署效率

SSM开发流程,以及各层作用

在SSM框架下,我们通常会分为四层:DAO层(MyBatis模块)、Service层(Spring模块)、Controller层(SpringMVC模块)、View层(SpringMVC模块)

  • DAO层主要负责与数据库进行交互设计,用来处理数据的持久化操作。

    在DAO里面我们首先要设计DAO的接口,然后需要编写对应接口的xml配置文件(具体的sql语句就写在xml中)

  • Service层主要负责具体业务的逻辑设计

    同样的,首选需要设计Service的接口以及实现类,然后在实现类中调用DAO层的接口,来完成业务的具体实现。

  • Controller层主要负责业务的流程控制

    Controller层通过调用Service层的接口来控制业务流程,比如页面的转发,重定向等操作。

  • View层主要负责前台页面的展示

    需要与Controller结合起来进行开发,Jsp发送请求,Controller接收请求,处理,返回,Jsp回显数据

三层之间的联系

  • DAO、Service层之间耦合度低,可以单独开发
  • Controller、View之间耦合度高,需要结合起来开发
  • Service层是简历在Dao层之上,Controller层之下的,处在一个中间层的位置,所以每个模块都应该有一个Service接口,每个接口都封装了具体的业务处理方法

Spring的IOC和AOP是什么?有哪些优点?

IOC(DI)控制反转,一种通过描述(XML或注解)并通过第三方生产或获得特定对象的方式。

AOP面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。

  1. 降低组件之间的耦合度,实现解耦
  2. 可以提供众多服务,如事务管理,消息服务等
  3. 容器提供了单例模式
  4. 可以使用AOP实现全线拦截,运行期监控等功能
  5. 低侵入设计,代码污染极低

说说你对Spring Boot的理解

【得分点】

    与Spring的关系 、核心功能 、优点

【参考答案】

标准回答

    Spring Boot本身并不提供Spring的核心功能,而是作为Spring框架的脚手架框架,以达到快速构建项目,预置第三方配置,开箱即用的目的。

    Spring Boot 核心功能:

1. 自动配置:针对很多Spring应用程序常见的应用功能,Spring Boot能自动提供相关配置。

2. 起步依赖:Spring Boot通过起步依赖为项目的依赖管理提供帮助。起步依赖其实就是特殊的Maven依赖和Gradle依赖,利用了传递依赖解析,把常用库聚合在一起,组成了几个为特定功能而定制的依赖。

3. 端点监控:Spring Boot 可以对正在运行的项目提供监控。

    Spring Boot 优点如下:

· 可以快速构建项目;

· 可以对主流开发框架的无配置集成;

· 项目可独立运行,无需外部依赖Servlet容器;

· 提供运行时的应用监控;

· 可以极大地提高开发、部署效率;

· 可以与云计算天然集成。

加分回答

    其实从本质上来说,Spring Boot就是Spring,它帮你完成了一些Spring Bean配置。Spring Boot使用“习惯优于配置”的理念让你的项目快速地运行起来,使用Spring Boot能很快的创建一个能独立运行、准生产级别、基于Spring框架的项目。

【延伸阅读】

Spring Boot常用注解:

1. @SpringBootApplication

在Spring Boot入口类中,唯一的一个注解就是@SpringBootApplication。它是Spring Boot项目的核心注解,用于开启自动配置,准确说是通过该注解内组合的@EnableAutoConfiguration开启了自动配置。

2. @EnableAutoConfiguration

@EnableAutoConfiguration的主要功能是启动Spring应用程序上下文时进行自动配置,它会尝试猜测并配置项目可能需要的Bean。自动配置通常是基于项目classpath中引入的类和已定义的Bean来实现的。在此过程中,被自动配置的组件来自项目自身和项目依赖的jar包中。

3. @Import

@EnableAutoConfiguration的关键功能是通过@Import注解导入的ImportSelector来完成的。从源代码得知@Import(AutoConfigurationImportSelector.class)是@EnableAutoConfiguration注解的组成部分,也是自动配置功能的核心实现者。

4. @Conditional

@Conditional注解是由Spring 4.0版本引入的新特性,可根据是否满足指定的条件来决定是否进行Bean的实例化及装配,比如,设定当类路径下包含某个jar包的时候才会对注解的类进行实例化操作。总之,就是根据一些特定条件来控制Bean实例化的行为。

5. @Conditional衍生注解

在Spring Boot的autoconfigure项目中提供了各类基于@Conditional注解的衍生注解,它们适用不同的场景并提供了不同的功能。通过阅读这些注解的源码,你会发现它们其实都组合了@Conditional注解,不同之处是它们在注解中指定的条件(Condition)不同。常见的衍生注解如下:

o @ConditionalOnBean:在容器中有指定Bean的条件下。

o @ConditionalOnClass:在classpath类路径下有指定类的条件下。

o @ConditionalOnMissingBean:当容器里没有指定Bean的条件时。

o @ConditionalOnMissingClass:当类路径下没有指定类的条件时。

Spring使用到了哪些设计模式?

  1. 工厂模式(IOC,通过工厂模式创建bean对象)
  2. 单例模式(Bean默认都是单例的,唯一的线程池、缓存、对话框等)
  3. 代理设计模式(AOP实现)
  4. 模板方法(jdbcTemplate、hibernateTemplate等模板类的实现)
  5. 观察者模式(监听器,驱动模型)
  6. 适配器模式(AOP中的Advice增强或通知)
  7. 包装器模式(项目连接多个数据库,可以通过切换数据源访问不同的数据库)

Spring Bean的生命周期==

其中第3、4步为初始化前执行,5~6步为初始化操作,第7步在初始化后执行

面试题2.0_第4张图片

  1. 实例化
  2. 属性赋值
    • 设置相关属性和依赖
  3. 初始化
    • Aware接口的依赖注入
    • BeanPostProcessor的前置处理
    • InitializingBean(初始化bean)处理
    • init-method处理
    • BeanPostProcessor的后置处理
  4. 销毁
    • 注册销毁相关的回调接口
    • DisposableBean 处理
    • destory-method处理

MyBatis如何实现延迟加载

延迟加载:在真正的使用数据时才发起查询,不用的时候不用查,按需查询(懒加载)

延迟加载参数配置:

<settings>
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="aggressiveLazyLoading" value="false"/>
    settings>

一对一的查询:

<association property="user" column="uid" javaType="com.mybatis.domain.User" select="com.mybatis.dao.IUserDao.findById"></association>

一对多的查询:

<collection property="accounts" ofType="com.mybatis.domain.Account" select="com.mybatis.dao.IAccountDao.findAccountByUid" column="id">collection>

MyBatis的多级缓存机制

  • 一级缓存基于SqlSession,也叫本地缓存,第一次查询数据库会写在缓存中,第二次查询直接在缓存中读取,两次查询语句之间发生了增删改的操作,会清空该缓存(默认开启)
  • 二级缓存:基于namespace,如果两个mapper的namespace相同,则共用一个二级缓存(默认不开启)

Spring框架中,前端是如何调用后端的方法的

后端解析前端的请求,封装request和response对象,创建servlet对象,调用service方法。

操作系统

进程和线程的区别

进程是CPU资源分配的最小单位,线程是CPU调度的基本单位

可以比喻为:进程=火车,线程=车厢

一个进程包含多个线程,一个线程只能在一个进程中,每个进程至少包含一个线程

同一进程的多个线程之间共享地址空间,进程之间是独立的地址空间

进程之间的通信方式

(1)基于内存的方式

**1、匿名管道:**主要用于父子进程之间的通信

2、信号量

信号是一种比较复杂的通信方式,信号产生的条件:按键、硬件异常等等

信号量是一个计数器,可以控制多个进程对共享资源的访问。它通常作为一种锁机制,防止某进程在访问共享资源时,其他进程也访问该资源。因此,主要用于进程间以及同一进程不同线程之间的同步手段

**3、消息队列:**消息队列是消息的链表,消息队列克服了信号传递信息系少,管道只能承载无格式字节流以及缓冲区大小受限等特点。消息队列起信箱作用,提供了一种在两个不相关进程间传递数据的简单方法。

**4、共享内存:**共享内存就是映射一段能够被其他进程所访问的内存,该内存由一个进程创建,但多个进程可以访问。

(2)基于磁盘的方式

1、文件

2、命名管道

(3)基于网络的方式

1、socket(套接字)

2、消息队列

3、RPC

4、HTTP等网络协议

僵尸进程是什么,为什么会产生

僵尸进程是指终止但未被回收的进程。如果一个进程已经终止,但是它的父进程尚未调用wait()或wait()对它进行清理,这是的进程状态称为僵死状态,处于僵死状态的进程称为僵尸进程。

产生原因:

当一个进程(init除外)在exit()时,并不会马上被内核清除,而是进程会处在一种"已终止"的状态,并留下一个称为“僵尸进程”的数据结构,等待父进程的回收处理。

解决办法:

当出现僵尸进程时,我们无法通过kill命令清除。但是我们可以杀死它的父进程,让它变成孤儿进程,并进一步被系统中管理孤儿进程的进程收养并处理

孤儿进程: 其父进程执行完成或被终止后仍继续运行的一类进程。

多进程和多线程的区别以及使用场景?

维度 多进程 多线程 总结
数据共享、同步 数据是分开的:共享复杂,需要用IPC;同步简单 多线程共享进程数据:共享简单;同步复杂 各有优势
内存、CPU 占用内存多,切换复杂,CPU利用率低 占用内存少,切换简单,CPU利用率高 线程占优
创建销毁、切换 创建销毁、切换复杂,速度慢 创建销毁、切换简单,速度快 线程占优
编程调试 编程简单,调试简单 编程复杂,调试复杂 进程占优
可靠性 进程间不会相互影响 一个线程挂掉将导致整个进程挂掉 进程占优
分布式 适应于多核、多机分布 ;如果一台机器不够,扩展到多台机器比较简单 适应于多核分布 进程占优

多进程模式的优势是CPU,适用于CPU密集性的工作场景(nginx、chrome浏览器、redis、大部分web server)

多线程模型的主要优势为线程之间切换代价小,适用于IO密集型的工作场景,同时也适用于单机多核分布式场景(桌面软件等等)

线程的生命周期

线程的生命周期包含5个阶段:新建、就绪、运行、阻塞、死亡

新建:刚使用new方法,创建了一个新的线程

就绪:调用线程的start方法后,线程等待cpu的调用

运行:获得了cpu的时间片,正在执行程序代码

阻塞:由于某些原因,放弃了cpu的使用权,如sleep()、wait(),他们需要等待cpu分配资源才能进入运行状态

死亡:main方法结束了,线程被销毁,不可复生

代码解释:

public class threadDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread();    //新建状态
        t.start();  //就绪状态,等待cpu选中调用
        //当线程选中后,线程进入运行状态
        t.wait();   //阻塞状态,线程放弃cpu使用权,当cpu分配资源可唤醒
    }
    //main方法结束,线程销毁
}

哪些进程调度算法?

  1. 先来先服务
  2. 时间片轮转
  3. 短作业优先:运行时间最短
  4. 最短剩余时间优先:相比短作业优先增加抢占机制,优先选取剩余时间最短的进程
  5. 高响应度优先:通过等待时间和运行时间计算响应比
  6. 优先级调度:根据优先级选取

页面置换算法(TODO)

1、最优页面置换算法(OPT)

  • 替换未来很长时间不会被访问的页面
  • 可以保证最低缺页率
  • 理想状态,需要预知未来

2、先进先出置换算法(FIFO)

  • 选择在内存中驻留时间最长的页面置换

实现方式

把调入内存的页面根据调入顺序排成一个队列,需要换出页面时选择队头

3、最近最久未使用算法(LRU)

4、最不常用算法(LFU)

什么是缓冲区溢出?

计算机向缓冲区填写数据时超出了缓冲区本身的容量,溢出的数据覆盖在合法的数据上。

危害:

  • 程序奔溃,导致拒绝服务
  • 跳转并执行一段恶意代码

原因:程序中没有仔细检查用户的输入

计算机网络

面试题2.0_第5张图片

计算机网络OSI七层模型和TCP五层模型

OSI七层模型从上到下依次是:

  • 应用层:为应用程序提供网络服务

STMP邮件协议、FTP文件传输协议、DNS域名解析协议、HTTP超文本传输协议 将超文本从服务器传输到本地服务器

  • 表示层:数据格式转换、数据压缩、数据加密

数据格式的转换、对图片的编解码、数据的压缩与加密解密

  • 会话层:建立、断开和维护通信连接

保持两个网络节点之间的通信,包括通信链接的建立、维护通信链接的通畅,决定了通信是否中断,以及中断时从何处重新发送通信请求

  • 传输层:为上层协议提供端到端的可靠传输(TCP/UDP)/数据段

是最重要的一层,可以对传输进行流量控制,如果数据包过大,可以将数据包分解

  • 网络层:寻址和路由(IP)/数据包

路由器,将网络地址翻译成对应的物理地址

  • 数据链路层:定义通信媒介互连设备之间的传输规范/数据帧

交换机,控制物理层和网络层之间的通讯,将网络层的数据包封装成帧,传输给物理层

  • 物理层:利用物理传输介质为数据层提供物理连接/比特流

电缆连接、网卡等,为数据链路层提供物理连接,根据它们的机械、电气、功能和过程特性,规定了电缆和接头的类型,传送信号的电平电压等。

TCP五层模型相比于OSI七层模型,将OSI七层模型的应用层、表示层、会话层合并为一层:应用层,其他不变。

TCP和UDP的区别

TCP UDP
面向有连接 面向无连接
可靠的 不可靠的
基于字节流 基于报文
点对点 一对一、一对多、多对一、多对多
工作效率低 工作效率高,适用于高速传输或广播通信
资源要求多 资源要求低

计算机网络各层有什么协议

  • 应用层:DHCP、FTP、HTTP、SMTP、RPC
  • 传输层:TCP/UDP 数据单元:数据段
  • 网络层:IP、ARP 数据单元:数据包
  • 数据链路层:PPP、ARQ 数据单元:帧
  • 物理层:数据单元:比特

https和http的区别

  1. 端口:http默认适用80端口,https默认适用443端口
  2. 安全性和资源消耗:http适用明文传输,未加密,安全性差,资源消耗少;https使用SSL协议加密,安全性高,但资源消耗大。

https采用哪些加密技术

对称加密算法:RSA,DSA/DSS

特点是文件加密和解密使用相同的密钥加密;对称加密算法使用起来简单快捷,密钥较短,且破译困难,解密速度快。除了数据加密标准(DES),另一个对称密钥加密系统是国际数据加密算法(IDEA),它比DES的加密性好,而且对计算机功能要求也没有那么高。

非对称加密:RSA,DSA/DSS

需要两个密钥:公开密钥和私有密钥;公开密钥与私有密钥是一对。如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密;如果用私有密钥对数据进行加密,那么只有用对应的公开密钥才能解密。因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。主要是用来保护传输客户端生成的用于对称加密的随机数私钥

三次握手,四次挥手的过程

三次握手

面试题2.0_第6张图片

第一次握手

客户端发送一个SYN标记包,发送完之后,客户端进入SYN-SEND状态

第二次握手

服务端同意连接,给客户端发送SYN+ACK标记包,服务端进入SYN-RCVD状态

第三次握手

客户端收到之后,给服务端发送ACK包,连接建立,两者都进入ESTABLISHED状态

为什么是三次握手而不是两次握手?

为了防止已经失效的请求报文突然有传到服务端,引起错误

如果客户端一开始发送的那个连接由于网络堵塞等原因导致请求超时,并且放弃了连接。等网络回复正常后,服务端会收到那个已经超时的连接请求。如果时两次握手就可以建立连接,那么此时对于服务端来说连接时成功的。但是此时客户端已经放弃了连接,这就造成了错误。

所以,综上,三次握手是为了保证在不可靠的信道上进行可靠的连接所创建的。

所以三次握手本质上是为了解决在不可靠的信道上进行可靠连接

通俗解释:

A:我爱你

B:我也爱你

A:那我们在一起吧

四次挥手

面试题2.0_第7张图片

第一次挥手

客户端发送FIN标记包给服务端,表示要关闭连接,客户端进入终止等待1状态(FIN-WAIT-1)

第二次挥手

服务端接到FIN包,发送ACK标记包给客户端,服务端进入关闭等待状态(CLOSE-WAIT),客户端进入终止等待2状态(FIN-WAIT-2),此时客户端可以接收数据,服务端可以发送数据

第三次挥手

服务端发送FIN包,进入最后确认状态(LAST-ACK)

第四次挥手

客户端收到后发送ACK标记包给服务端,客户端进入超时等待状态(TIME-WAIT),经过超时时间后关闭连接,服务端收到ACK后马上关闭连接

为什么客户端需要等待时间?

为什么需要等待2倍最大报文生存时间之后再关闭连接,原因有两个:

  1. 保证TCP协议的全双工通信连接能够可靠关闭
  2. 保证这次连接的重复数据段在网络中消失,防止端口被重用时可能产生数据混淆。

这是为了保证对方已收到了ACK包,假设客户端发送ACK包后马上关闭连接,如果ACK包在网络中丢失,那么服务端将永远停留在最后确认状态

如果客户端发送ACK包后,服务端没有收到,服务端会重发FIN包,客户端响应FIN包,重发ACK包,并刷新超时时间

通俗解释:

A:分手吧

B:好吧

B:是我哪里做的不对吗?

A:是的,我们分手吧(此时B收到信息后直接就断了念想,而A却在等待B的挽回,过一段时间后A也就直接拉黑删除了对方)

打开一个网页使用了哪些协议?

**DNS协议(应用层):**将域名解析为IP,输入域名时,将其发送给“DNS服务器”,就会返回对应的IP。本机在解析域名时,首先会在host文件中查找,如果查到就直接使用,否则就发送给DNS服务器。

TCP/IP协议:IP协议用来寻找地址(即传输数据的目标节点)对应网络层;TCP协议用来规范传输规则的,对应传输层。IP负责找地址,TCP负责具体的传输。TCP/IP是一套规则,并不能具体工作,就像程序中的接口一样,而Socket是TCP/IP协议的一个具体实现。

**HTTP协议:**应用层协议,在TCP/IP协议接收到数据之后,需要通过HTTP协议来解析。HTTP报文分为请求报文和响应报文,都包括三部分:首行、头部和主体。

HTTP状态码

面试题2.0_第8张图片

一、常见

200——服务器成功返回页面

304——(未修改)自从上次请求后,请求的网页未修改过。服务器返回此响应时,不会返回网页内容。

400——(错误请求)服务器不理解请求的语法

404——请求的网页不存在

500——(服务器内存错误)服务器遇到错误,无法完成请求

二、1xx 临时响应

表示临时响应,需要请求者继续执行操作。

100——(继续)服务器表示已经收到请求的第一部分,正在等待请求者发送其他部分

101——(切换协议)请求者已要求服务器切换协议,服务器确认并准备切换

三、2xx 成功

200——服务器成功处理并返回网页

201——(已创建)请求成功,服务器创建了新的资源

202——(已接收)服务器已接收请求,但没有处理

203——(非授权信息)服务器成功处理,但返回的信息可能来自其他源

204——(无内容),服务器成功处理请求,但没有返回任何内容

205(重置内容)服务器成功处理了请求,但没有返回任何内容。与 204 响应不同,此响应要求请求者重置文档视图(例如,清除表单内容以输入新内容)。

206(部分内容)服务器成功处理了部分 GET 请求。

四、3xx 重定向

要完成请求,需要进一步操作。通常,这些状态码用来重定向。Google 建议您在每次请求中使用重定向不要超过 5 次。您可以使用网站管理员工具查看一下 Googlebot 在抓取重定向网页时是否遇到问题。诊断下的网络抓取页列出了由于重定向错误导致 Googlebot 无法抓取的网址。

300(多种选择)针对请求,服务器可执行多种操作。服务器可根据请求者 (user agent) 选择一项操作,或提供操作列表供请求者选择。

301(永久移动)请求的网页已永久移动到新位置。服务器返回此响应(对 GET 或 HEAD 请求的响应)时,会自动将请求者转到新位置。您应使用此代码告诉 Googlebot 某个网页或网站已永久移动到新位置。

302(临时移动)服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来响应以后的请求。此代码与响应 GET 和 HEAD 请求的 301 代码类似,会自动将请求者转到不同的位置,但您不应使用此代码来告诉 Googlebot 某个网页或网站已经移动,因为 Googlebot 会继续抓取原有位置并编制索引。

303(查看其他位置)请求者应当对不同的位置使用单独的 GET 请求来检索响应时,服务器返回此代码。对于除 HEAD 之外的所有请求,服务器会自动转到其他位置。

304(未修改)自从上次请求后,请求的网页未修改过。服务器返回此响应时,不会返回网页内容。

如果网页自请求者上次请求后再也没有更改过,您应将服务器配置为返回此响应(称为 If-Modified-Since HTTP 标头)。服务器可以告诉 Googlebot 自从上次抓取后网页没有变更,进而节省带宽和开销。

.

305(使用代理)请求者只能使用代理访问请求的网页。如果服务器返回此响应,还表示请求者应使用代理。

307(临时重定向)服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来响应以后的请求。此代码与响应 GET 和 HEAD 请求的 301 代码类似,会自动将请求者转到不同的位置,但您不应使用此代码来告诉 Googlebot 某个页面或网站已经移动,因为 Googlebot 会继续抓取原有位置并编制索引。

五、4xx 请求错误

这些状态码表示请求可能出错,妨碍了服务器的处理。

400(错误请求)服务器不理解请求的语法。

401(未授权)请求要求身份验证。对于登录后请求的网页,服务器可能返回此响应。

403(禁止)服务器拒绝请求。如果您在 Googlebot 尝试抓取您网站上的有效网页时看到此状态码(您可以在 Google 网站管理员工具诊断下的网络抓取页面上看到此信息),可能是您的服务器或主机拒绝了 Googlebot 访问。

404(未找到)服务器找不到请求的网页。例如,对于服务器上不存在的网页经常会返回此代码。

如果您的网站上没有 robots.txt 文件,而您在 Google 网站管理员工具"诊断"标签的 robots.txt 页上看到此状态码,则这是正确的状态码。但是,如果您有 robots.txt 文件而又看到此状态码,则说明您的 robots.txt 文件可能命名错误或位于错误的位置(该文件应当位于顶级域,名为 robots.txt)。

如果对于 Googlebot 抓取的网址看到此状态码(在"诊断"标签的 HTTP 错误页面上),则表示 Googlebot 跟随的可能是另一个页面的无效链接(是旧链接或输入有误的链接)。

405(方法禁用)禁用请求中指定的方法。

406(不接受)无法使用请求的内容特性响应请求的网页。

407(需要代理授权)此状态码与 401(未授权)类似,但指定请求者应当授权使用代理。如果服务器返回此响应,还表示请求者应当使用代理。

408(请求超时)服务器等候请求时发生超时。

409(冲突)服务器在完成请求时发生冲突。服务器必须在响应中包含有关冲突的信息。服务器在响应与前一个请求相冲突的 PUT 请求时可能会返回此代码,以及两个请求的差异列表。

410(已删除)如果请求的资源已永久删除,服务器就会返回此响应。该代码与 404(未找到)代码类似,但在资源以前存在而现在不存在的情况下,有时会用来替代 404 代码。如果资源已永久移动,您应使用 301 指定资源的新位置。

411(需要有效长度)服务器不接受不含有效内容长度标头字段的请求。

412(未满足前提条件)服务器未满足请求者在请求中设置的其中一个前提条件。

413(请求实体过大)服务器无法处理请求,因为请求实体过大,超出服务器的处理能力。

414(请求的 URI 过长)请求的 URI(通常为网址)过长,服务器无法处理。

415(不支持的媒体类型)请求的格式不受请求页面的支持。

416(请求范围不符合要求)如果页面无法提供请求的范围,则服务器会返回此状态码。

417(未满足期望值)服务器未满足"期望"请求标头字段的要求。

六、5xx 服务器错误

这些状态码表示服务器在处理请求时发生内部错误。这些错误可能是服务器本身的错误,而不是请求出错。

500(服务器内部错误)服务器遇到错误,无法完成请求。

501(尚未实施)服务器不具备完成请求的功能。例如,服务器无法识别请求方法时可能会返回此代码。

502(错误网关)服务器作为网关或代理,从上游服务器收到无效响应。

503(服务不可用)服务器目前无法使用(由于超载或停机维护)。通常,这只是暂时状态。

504(网关超时)服务器作为网关或代理,但是没有及时从上游服务器收到请求。

505(HTTP 版本不受支持)服务器不支持请求中所用的 HTTP 协议版本。

tcp四次挥手中,time-wait,close-wait是干嘛的?在哪一阶段出现?

close-wait状态出现在TCP第二次挥手时,服务端给客户端发送ACK数据包后,服务端进入关闭等待状态(close-wait)

time-wait状态出现在TCP第四次挥手时,客户端给服务端发送ACK数据包后,客户端进入超时等待状态(time-wait)

挥手时为什么会有time-wait这个状态

这是为了保证对方已经收到了ACK包,假设客户端发送ACK包后马上关闭连接,如果ACK包在网络中丢失,那么服务端将永远停留在最后确认状态。

如果客户端发送ACK包后,服务端没有收到,服务端会重发FIN包,客户端响应FIN包,重发ACK包,并刷新超时时间。

tcp如何保证数据的可靠性

1、检验和:在数据传输过程中,将发送的数据作为一个16位的整数,将这些整数加起来,并且前面的进位不要丢器,补在后面,最后取反,得到检验和。

发送方:在发送数据之前计算检验和,并进行校验和的填充。
接收方:收到数据后,对数据以同样的方式进行计算,求出校验和,与发送方的进行比对。

注意:如果接收方比对校验和与发送方不一致,那么数据一定传输有误。但是如果接收方比对校验和与发送方一致,数据不一定传输成功。

面试题2.0_第9张图片

2、确认应答和序列号

序列号:TCP传输时给每个字节的数据都进行了编号,这就是序列号。

确认应答:TCP传输过程中,每次接收放接收到数据后,都会对传输方进行确认应答,也就是发送ACK报文。这个ACK报文中中会带有对应的确认序列号,也就是告诉发送方,就收到了哪些数据,下一次的数据该从哪里发。

面试题2.0_第10张图片

3、超时重传

如果发送方发送数据一段时间后没有收到ACK包,就会重新发送数据。

4、连接管理

TCP通过三次握手,四次挥手保证连接的可靠性。

5、流量控制

接收端使用TCP报文的窗口字段,来控制发送方的发送速率。

6、拥塞控制

TCP通过维护一个拥塞窗口来进行拥塞控制,通过检测网络的状态来调整拥塞窗口的大小,目的是防止过多的数据注入到网络,引起路由器过载。

其他方式:

7、应用数据被分割成TCP认为最合适发送的数据块。

8、TCP给每个需要发送的包编号,接收方对数据包进行排序,有序将数据传送给应用层。

9、TCP接收端会丢弃重复的数据

TCP拥塞控制和流量控制区别

目的

  • 流量控制是为了防止接收方的缓冲区大小不够发生溢出,所以减小接收窗口(rwnd)。
  • 拥塞控制是防止过多的数据注入网络,造成网络中的路由器过载。

方式

  • 流量控制:接收方在TCP报文的窗口字段限制发送方的发送速率。
  • 拥塞控制:发送方自己检测网络的状态,如遇到超时、重复确认包时,对拥塞窗口进行调整。

区别

  • 流量控制作用于接收方,用于控制发送方的发送速率使接收方来得及接受
  • 拥塞控制作用于发送方,防止过多数据注入网络,引发路由器过载

其实真正的发送窗口只有一个,发送窗口选取的是接收窗口和拥塞窗口中较小的一个。比如网络状况较好,传输速率为10G/S,那么网络拥塞这一因素就不是主要的影响,主要看接收方的缓冲区大小。相反,如果链路的传输速率只有10M/S,又有大量的节点在传输,此时拥塞控制就成了主要因素。

流量控制是点对点的通信量控制拥塞控制是考虑了全局的网络负荷,是一个全局性的概念。

http强制缓存和协商缓存

**浏览器缓存(Brower Caching)**是浏览器对之前请求过的文件进行缓存,以便下一次进行重复利用,节省带宽,提升访问速度,减轻服务器压力。

http缓存机制主要在http响应头实现,响应头相关字段有Expires、Cache-Control、Last-Modified、Etag。

浏览器是如何判断是否使用缓存的

第一次请求:

面试题2.0_第11张图片

第二次请求相同网页:

面试题2.0_第12张图片

缓存类型

强缓存

面试题2.0_第13张图片

面试题2.0_第14张图片

200 form memory cache:不访问服务器,一般已经加载了该资源在内存中,直接从内存中读取缓存。浏览器关闭后,数据将不存在。

200 from disk cache:不访问服务器,已经在之前某个时间访问了该资源,直接从硬盘中读取缓存。关闭浏览器后,数据依旧存在。

优先访问 memory cache ,其次是 disk cache ,最后是请求网络资源。

协商缓存

向服务器发起请求,服务器根据请求的request header的一些参数,来判断是否命中协商缓存,如果命中,则返回304并带上新的reponse header并通知浏览器从缓存中读取资源。

image-20220524190108850

总结: 强制缓存根据过期时间来使用,而协商缓存根据文件是否被修改来使用,如果过期了就需要通过协商缓存来确定文件有没有被修改,如果修改了就请求服务器返回修改后的文件,如果没有修改就直接使用缓存中的资源。

数据库

索引

B-树

  • B-树,这里的 B 表示 balance( 平衡的意思),B-树是一种多路自平衡的搜索树(B树是一颗多路平衡查找树
  • 它类似普通的平衡二叉树,不同的一点是B-树允许每个节点有更多的子节点(多叉树)

面试题2.0_第15张图片

B-树有如下特点:

  1. 所有键值分布在整颗树中(索引值和具体data都在每个节点里);
  2. 任何一个关键字出现且只出现在一个结点中;
  3. 搜索有可能在非叶子结点结束(最好情况O(1)就能找到数据);
  4. 在关键字全集内做一次查找,性能逼近二分查找;

B+树

B+树是B-树的变体,也是一种多路搜索树,它与B-树不同之处在于:

  1. 所有的关键字存储在叶子节点出现,内部节点(非叶子节点并不存储真正的data
  2. 为所有的叶子节点增加了一个链指针

面试题2.0_第16张图片

面试题2.0_第17张图片

因为内节点并不存储 data,所以一般B+树的叶节点和内节点大小不同,而B-树的每个节点大小一般是相同的,为一页。

B-树和B+树的区别

  • B+树内节点不存储数据,所有data存储在叶子节点导致查询时间复杂度为log n。而B-树查询时间复杂度不固定,与key在树中的位置有关。

索引采用B+树的原因

  • 索引是加快查询的一种数据结构,其原理是插入时对数据排序,缺点是会影响插入的性能。
  • MySQL当前支持B+树索引、全文索引、R树索引
  • 为什么关系型数据库热衷于B+树索引?因为它是目前为止排序最有效率的数据结构

使用B+树而不是B树的原因

  1. 方便扫库

B树必须用中序遍历的方法按序扫库,而B+树直接从叶子节点挨个遍历就行。B+树支持range-query(范围搜索),而B树不支持,这也是数据库选用B+树的主要原因。

  1. B+树的磁盘读写代价低

B+树节点大小更小,一次IO读入的节点数更多 ,磁盘读写代价更低

  1. B+树查询效率稳定

任何关键字的查找都必须走一条从根节点到叶子节点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。

使用B+树而不是AVL树、红黑树的原因

  • B+树的树高比AVL树、红黑树低,IO次数少

Mysql的事务,以及事务的ACID

事务是指逻辑上的一组操作,要么都执行,要么都不执行

事务具有四大特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)

  • 原子性:事务是一个不可分割的工作单位,其中的操作要么都做,要么都不做。
  • 一致性:事务执行前后,数据处于一种合法的状态
  • 隔离性:多个事务并发执行时,事务内部的操作是与其他事务隔离的,并发执行的各个事务之间相互不干扰
  • 持久性:事务一旦提交,它对数据库的修改就应该是永久性的

mysql怎么保证原子性

利用Innodb的 undo log(回滚日志),是实现原子性的关键,它保存了事务发生前的数据的一个版本,可以将数据回滚到修改前的样子。

mysql怎么保证一致性

从两个层面上来说:

  1. 从数据库层面

数据库通过原子性、隔离性、持久性来保证一致性。也就是说,四大特性中C(一致性)是目的,而A(原子性)、I(隔离性)、D(持久性)是手段。数据库必须实现AID三大原则,才有可能实现一致性。

  1. 从应用层面

通过代码判断数据库数据是否有效,然后决定回滚还是提交数据!

mysql怎么保证隔离性

利用锁和MVCC机制,MVCC即多版本并发控制(Multi Version Concurrency Control)

mysql怎么保证持久性

利用Innodb的 redo log(重做日志)。

正如之前所说,mysql先将磁盘上的数据加载到内存,在内存中对数据进行修改,再刷回磁盘。如果在此时突然宕机,内存上的数据就会丢失,这就不能保证持久性。

但是如果在事务提交之后直接写入到磁盘,会非常的消耗资源,而且事务的SQL涉及多个数据表的修改时,效率会非常的慢。

所以引入了redo log解决这个问题,当数据修改时,不仅在内存中操作,而且在redo log中记录这次操作。当事务提交时,会将redo log进行刷盘(redo log一部分在内存中,一部分在磁盘上)。当数据库宕机时,会将redo log的内容恢复到数据库,再根据redo log(重做日志)和binlog(归档日志)内容决定回滚数据还是提交数据。

MySQL为什么要有事务回滚机制?

MySQL的事务回滚是通过回滚日志(undo log)实现的,所有事务进行的修改都会先记录到回滚日志中,然后再对数据库进行对应的操作。

保证MySQL操作的原子性,事务中的操作要么全部执行,要么全部不执行。

数据库引擎,MylSAM和InnoDB的区别

数据库引擎:用于存储、处理、保护数据的核心服务。

常见的数据库引擎:InnodbMyISAM 、MEMORY 、MERGE

MySQL存储引擎中,MylSAM和InnoDB的区别

InnoDB MyISAM
支持事务 不支持事务
支持外键 不支持外键
聚集索引 非聚集索引
支持行锁、表锁 支持表锁
必须有主键 可以没有
不保存表的行数 保存了表的行数

索引和主键的区别

  • 主键一定是唯一性索引,唯一性索引不一定是主键
  • 一个表可以有多个唯一性索引,但只能有一个主键
  • 主键不允许空值,但唯一性索引可以为空值
  • 主键可以由多个字段组成,组成复合主键,同时主键是唯一性索引
  • 索引可以提高查询速度,它就相当于字典的目录,可以通过它快速查询到想要的数据,而不需要全表查询
  • 唯一索引则表示该索引值唯一,可以由一个或多个字段组成,一个表可以有多个唯一索引

MySQL索引的最左原则

在联合索引中,如果你的SQL语句用到了联合索引中的最左边的索引,那么这条SQL语句就可以利用这个联合索引进行匹配

例如:某表现有索引(a,b,c)

select * from t where a=1 and b=1;     #这样可以利用到定义的索引(a,b,c),用上a,b
select * from t where a=1 and b=1;     #这样可以利用到定义的索引(a,b,c),用上a,b
select * from t where a=1 and c=1;     #这样可以利用到定义的索引(a,b,c),但只用上a索引,b,c索引用不到

优化数据库性能的方法

数据库设计:

  1. 选取何使的字段属性(VARCHAR、CHAR)
  2. 数据库索引

SQL语句优化:

  1. 避免使用子查询,使用JOIN连接查询代替
  2. 能使用UNION ALL就不要用UNION、UNION会自动压缩集合的重复结果
  3. 皮面使用!=或<>操作符,使用该操作符会不使用索引
  4. 对OR子句的优化,如使用UNION ALL代替OR
  5. IN或NOT IN的优化
    • BETWEEN AND替换IN
    • EXISTS替换IN
    • LEFT JOIN替换IN
  6. IS NULL或IS NOT NULL优化,因为它会放弃使用索引
  7. LIKE优化
  8. 避免SQL语句中的表达式计算
  9. LIMIT分页优化

如何定位慢查询

  1. 使用慢查询日志
  2. 使用explain等工具分析sql
  3. show processlist查看正在执行的慢查询

MySQL支持行锁还是表锁?分别有什么优缺点

MySQL常用的引擎是InnoDB,支持行锁和表锁

多个事务操作同一行数据时,后来的事务处于阻塞状态,后来的事务可以操作其他的行数据,解决了表锁高并发性能低的问题。

行锁的优点:锁的粒度小,发生锁冲突的概率低;处理并发的能力强

行锁的缺点:开销大;加锁慢;会出现死锁

雪花算法生成id,为什么不使用数据库自增

常见的数据库id解决方案:

  • 数据库自增
  • UUID
  • 雪花算法

先着重介绍一下雪花算法,这是最常用的,也是重点。

**雪花算法:**snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。可以保证几乎全球唯一!

三种解决方案的优缺点:

解决方案 特点 优点 缺点
数据库自增 数值类型,值递增,由数据库内部生成 最简单的方法,性能优秀 不适合分库分表场景,会出现主键冲突,ID重复
UUID 字符串类型 1. 适用分库分表场景
2. UUID一般由Java代码生成,java不需要查询就能知道ID
1. 性能不如自增
2. 可读性差,不能根据ID进行范围查询
雪花算法 数值类型,有Twitter提供的分布式ID算法,递增 1. 适用分库分表场景,就是为了这个场景而生的
2. 由Java代码生成,不需要查询就可以知道ID
3. 所有需要唯一ID的都可以适用雪花算法,如登录token、日志编号
1. 性能稍不如自增
2. 生成的值稍长,传递到前端number容易造成精度丢失,可转化成字符串解决

综上,使用雪花算法而不使用数据库自增,是为了适应分布式场景

算法与数据结构

常见的算法:

  • 穷举法:对于小数据量,穷举法是最优秀的算法,直接将所有结果穷举出来
  • 贪心算法:又称为贪婪算法,在求解问题时,总是找到当前来看的最好选择,也就是局部最优解,从而得到全局最优解。但是并不是所有问题都能得到全局最优解,获取最优解的好坏取决于贪心策略的选择。
  • 动态规划算法:简称DP,是运筹学的一个分支,通常用于求解某种最优特质的问题。它和贪心算法的区别是,贪心算法没有状态推导,而是从局部直接选取最优解,而在动态规划中,每一个状态都是由上一个状态推导出来的,需要有一个具体的推导公式。
  • 回溯算法:也叫做回溯搜索法,主要是在搜索的过程中,寻找问题的解,如果不满足条件时,就会“回溯”返回,尝试别的路径。回溯的本质是穷举,所以效率并不高,但是有些问题只能使用回溯方法。比如:组合问题、子集问题、排列问题
  • 分治算法:使用分而治之的思想,将一个大的问题分为若干个子问题,再在子问题的基础上继续分解,最终解决最初的大问题。

回溯和递归的区别

回溯是递归的一种表现形式

递归算法是为了描述问题的某一状态,必须用到上一个状态,循环调用方法本身,以此得到目标状态,强调的是状态本身。

回溯的本质是为了得到可能存在的所有情况,强调的是穷举所有可能的情况。

什么时候会用到动态规划?

动态规划常常适用于有重叠子问题(比如子串)和最优子结构性质的问题。当我们需要优化递归时,也会用到动态规划。

动态规划的三个关键点:

  • 最优子结构(大问题拆分成小问题)
  • 边界(初始条件)
  • 状态转移方程(推导公式)

什么时候使用动态规划?

  • 重复子问题

  • 最优子结构

  • 优化递归

待解决

Java基础

  1. synchronized原理✔
  2. 阐述HashMap
    • 使用的数据结构,如何解决hash冲突
    • HashMap的具体实现细节,CRUD逻辑,equals,hashcode
  3. final关键字
    • 修饰对象:类、方法、成员变量
    • 作用:保证可见性
  4. Integer和int的区别
  5. 泛型
  6. String、StringBuffer、StringBuilder区别

设计模式

  1. 基础饿汉模式
  2. 基础懒汉模式

计算机网络

  1. 打开一个网页使用了哪些协议?✔
  2. tcp四次挥手中,time-wait,close-wait是干嘛的?在哪一阶段出现?✔
  3. 为什么是三次握手,两次会怎么样✔
  4. 挥手时为什么会有time-wait这个状态✔
  5. tcp如何保证数据的可靠性
  6. TCP和UDP的区别
  7. HTTP常见的状态码✔
  8. HTTP如何禁用缓存?如何确认缓存?

操作系统

  1. 进程和线程的区别
  2. 进程之间的通信方式
  3. 什么是僵尸进程,为什么会产生
  4. 虚拟内存到物理内存的寻址方式
  5. 页表的作用
  6. 进程调度算法

数据库

  1. MySQL数据库是如何保证ACID的
  2. MVCC
  3. B+树的数据结构,B+树是怎么分裂的
  4. 雪花算法自动生成id,为什么不用数据库自增
  5. 数据库索引使用B+树而不是B树的原因?
  6. MySQl中为什么要有事务回滚机制?
  7. 索引和主键的区别?
  8. 索引的优缺点
  9. 使用索引的原因

Java多线程

  1. volatile
  2. threadLocal是什么?key是什么?
  3. Java的四种引用
  4. 线程安全
    • 核心问题:原子性、可见性、有序性
    • 实现方式:阻塞同步(加锁)、非阻塞同步(cas)、无同步方案(本地存储)

JVM

  1. CMS垃圾回收器介绍
  2. volite

面试实战总结

  1. 对SpringBoot的了解
  2. 使用SSM进行开发的流程,比如表单的数据如何存储到数据库中
  3. 你了解哪些算法?描述一些这些算法的使用
    • 回溯和递归的区别
    • 什么时候会用到动态规划
  4. 对我们公司,你有什么要了解的吗?
  5. Java集合类有哪几种
  6. 计算机网络五层模型,具体有哪些协议?
  7. HashMap在jdk1.7和1.8的区别
  8. 你遇到最难的功能实现,最后是怎么解决的?(问解决方案,而不是解决过程)提前准备项目的重难点和解决方案
  9. 什么是单点登录?

测试代码

你可能感兴趣的:(java,链表,数据结构)