小米面经

武培轩 2020年02月06日 100次浏览

[toc]

一、Java

java容器类

|Collection 
|  ├List 
|  │-├LinkedList 
|  │-├ArrayList 
|  │-└Vector 
|  │ └Stack 
|  ├Set 
|  │├HashSet 
|  │├TreeSet 
|  │└LinkedSet 
|  ├Queue 
|  │├ArrayDeque 
|  │└PriorityQueue 
|
|Map 
|   ├Hashtable
|   ├HashMap 
|   └WeakHashMap

ArrayList与LinkedList默认空间是多少

ArrayList默认容量为10,Linkedlist基于链表实现的,因此没有默认的空间。

ArrayList和LinkedList

  • ArrayList是实现了基于动态数组的数据结构,LinkedList基于双向链表的数据结构。
  • 对于随机访问get和set,ArrayList优于LinkedList,因为LinkedList要移动指针。
  • 对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。

常用的map有哪些 说三种

HashMap,LinkedHashMap,TreeMap

String与stringbuffer和stringbuilder区别

  • String内容不可变,StringBuffer和StringBuilder内容可变;
  • StringBuilder非线程安全(单线程使用),String与StringBuffer线程安全(多线程使用);
  • 如果程序不是多线程的,那么使用StringBuilder效率高于StringBuffer。

hashmap的实现原理

HashMap是常用的Java集合之一,是基于哈希表的Map接口的实现。不支持同步和允许null作为key和value。

HashMap非线程安全,即任一时刻可以有多个线程同时写HashMap,可能会导致数据的不一致。

在JDK1.6中,HashMap采用数组+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里。
但是当位于一个数组中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。
而JDK1.8中,HashMap采用数组+链表+红黑树实现,当链表长度超过阈值8时,将链表转换为红黑树,这样大大减少了查找时间。

    /**
     * 根据key的哈希值和key获取对应的节点
     * getNode可分为以下几个步骤:
     * 1.如果哈希表为空,或key对应的桶为空,返回null
     * 2.如果桶中的第一个节点就和指定参数hash和key匹配上了,返回这个节点。
     * 3.如果桶中的第一个节点没有匹配上,而且有后续节点
     * 3.1如果当前的桶采用红黑树,则调用红黑树的get方法去获取节点
     * 3.2如果当前的桶不采用红黑树,即桶中节点结构为链式结构,遍历链表,直到key匹配
     * 4.找到节点返回null,否则返回null。
     */

    /**
     * putVal方法可以分为下面的几个步骤:
     * 1.如果哈希表为空,调用resize()创建一个哈希表。
     * 2.如果指定参数hash在表中没有对应的桶,即为没有碰撞,直接将键值对插入到哈希表中即可。
     * 3.如果有碰撞,遍历桶,找到key映射的节点
     * 3.1桶中的第一个节点就匹配了,将桶中的第一个节点记录起来。
     * 3.2如果桶中的第一个节点没有匹配,且桶中结构为红黑树,则调用红黑树对应的方法插入键值对。
     * 3.3如果不是红黑树,那么就肯定是链表。遍历链表,如果找到了key映射的节点,就记录这个节点,退出循环。如果没有找到,在链表尾部插入节点。插入后,如果链的长度大于TREEIFY_THRESHOLD这个临界值,则使用treeifyBin方法把链表转为红黑树。
     * 4.如果找到了key映射的节点,且节点不为null
     * 4.1记录节点的vlaue。
     * 4.2如果参数onlyIfAbsent为false,或者oldValue为null,替换value,否则不替换。
     * 4.3返回记录下来的节点的value。
     * 5.如果没有找到key映射的节点(2、3步中讲了,这种情况会插入到hashMap中),插入节点后size会加1,这时要检查size是否大于临界值threshold,如果大于会使用resize方法进行扩容。
     */
    
    /**
     * 对table进行初始化或者扩容。
     * 如果table为null,则对table进行初始化
     * 如果对table扩容,因为每次扩容都是翻倍,与原来计算(n-1)&hash的结果相比,节点要么就在原来的位置,要么就被分配到“原位置+旧容量”这个位置
     * resize的步骤总结为:
     * 1.计算扩容后的容量,临界值。
     * 2.将hashMap的临界值修改为扩容后的临界值
     * 3.根据扩容后的容量新建数组,然后将hashMap的table的引用指向新数组。
     * 4.将旧数组的元素复制到table中。
     */

hashtable和hashmap区别

ConcurrentHashMap

volatile

volatile是变量修饰符,其修饰的变量具有可见性,Java的做法是将该变量的操作放在寄存器或者CPU缓存上进行,之后才会同步到主存,使用volatile修饰符的变量是直接读写主存,volatile不保证原子性,同时volatile禁止指令重排。

synchronized实现原理

同步代码块是使用monitorenter和monitorexit指令实现的,同步方法依靠的是方法修饰符上的ACC_SYNCHRONIZED实现。

同步代码块:monitorenter指令插入到同步代码块的开始位置,monitorexit指令插入到同步代码块的结束位置,JVM需要保证每一个monitorenter都有一个monitorexit与之相对应。任何对象都有一个monitor与之相关联,当且一个monitor被持有之后,他将处于锁定状态。线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor所有权,即尝试获取对象的锁;

同步方法:synchronized方法则会被翻译成普通的方法调用和返回指令如:invokevirtual、areturn指令,在JVM字节码层面并没有任何特别的指令来实现被synchronized修饰的方法,而是在Class文件的方法表中将该方法的access_flags字段中的synchronized标志位置1,表示该方法是同步方法并使用调用该方法的对象或该方法所属的Class在JVM的内部对象表示Class做为锁对象。

线程池有哪几种

java8了解吗?

  • Lambda 表达式 − Lambda允许把函数作为一个方法的参数。
  • 方法引用 − 可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
  • 默认方法 − 默认方法就是一个在接口里面有了一个实现的方法。
  • Stream API −新添加的Stream API(java.util.stream)把真正的函数式编程风格引入到Java中。

Lamda表达式的优缺点

wait与sleep谁占有资源

  1. wait和notify方法定义在Object类中,因此会被所有的类所继承。 这些方法都是final的,即它们都是不能被重写的,不能通过子类覆写去改变它们的行为。 而sleep方法是在Thread类中是由native修饰的,本地方法。

  2. 当线程调用了wait()方法时,它会释放掉对象的锁。
    另一个会导致线程暂停的方法:Thread.sleep(),它会导致线程睡眠指定的毫秒数,但线程在睡眠的过程中是不会释放掉对象的锁的。

  3. 因为wait方法会释放锁,所以调用该方法时,当前的线程必须拥有当前对象的monitor,也即lock,就是锁。要确保调用wait()方法的时候拥有锁,即wait()方法的调用必须放在synchronized方法或synchronized块中。

synchronize 与reentralock哪个是公平锁

怎么保证锁的高效

怎么让一个线程休息5秒后再恢复

Thread.Sleep(5000);

线程状态

新建、就绪、运行、阻塞、死亡

阻塞和等待之间的区别

线程池工作原理

手写多线程计数

内部类

抽象类和接口的区别

含有abstract修饰符的class即为抽象类,abstract类不能创建的实例对象。含有abstract方法的类必须定义为abstract class,abstract class类中的方法不必是抽象的。abstract class类中定义抽象方法必须在具体(Concrete)子类中实现,所以,不能有抽象构造方法或抽象静态方法。如果的子类没有实现抽象父类中的所有抽象方法,那么子类也必须定义为abstract类型。

接口(interface)可以说成是抽象类的一种特例,接口中的所有方法都必须是抽象的。接口中的方法定义默认为public abstract类型,接口中的成员变量类型默认为public static final。

下面比较一下两者的语法区别:

  1. 抽象类可以有构造方法,接口中不能有构造方法。
  2. 抽象类中可以有普通成员变量,接口中没有普通成员变量
  3. 抽象类中可以包含非抽象的普通方法,接口中的可以有非抽象方法,比如deaflut方法
  4. 抽象类中的抽象方法的访问类型可以是public,protected和(默认类型,虽然
    eclipse下不报错,但应该也不行),但接口中的抽象方法只能是public类型的,并且默认即为public abstract类型。
  5. 抽象类中可以包含静态方法,接口中不能包含静态方法
  6. 抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是public static final类型,并且默认即为public static final类型。
  7. 一个类可以实现多个接口,但只能继承一个抽象类。

线程间通信

Java IO/NIO 区别

BIO和NIO

悲观锁,乐观锁

悲观锁:假定会发生并发冲突,则屏蔽一切可能违反数据完整性的操作

乐观锁:假定不会发生并发冲突,只在数据提交时检查是否违反了数据完整性(不能解决脏读问题)

解决hash冲突的方法

  • 开放定址法
  • 链地址法
  • 再哈希
  • 建立公共溢出区

跳跃表

跳跃表是基于有序链表的扩展,简称跳表,具有如下性质:

  • 由很多层结构组成
  • 每一层都是一个有序的链表
  • 最底层(Level 1)的链表包含所有元素
  • 如果一个元素出现在 Level i 的链表中,则它在 Level i 之下的链表也都会出现。
  • 每个节点包含两个指针,一个指向同一链表中的下一个元素,一个指向下面一层的元素。

O(logn)的平均时间复杂度,其空间复杂度为O(n)。

一致性哈希算法

wait方法底层原理

新建线程的方式,有什么区别

怎么关闭线程

  1. 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
  2. 使用stop方法强行终止线程。
  3. 使用interrupt方法中断线程。

对同步、异步、阻塞、非阻塞的理解

==和equels的区别

  • 对于==,如果作用于基本数据类型的变量,则直接比较其存储的值是否相等;如果作用于引用类型的变量,则比较的是所指向的对象的地址
  • equals方法(equals方法不能作用于基本数据类型的变量),如果没有对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址

object类equals方法是怎么实现的

对于==,如果作用于基本数据类型的变量,则直接比较其存储的值是否相等;如果作用于引用类型的变量,则比较的是所指向的对象的地址

public boolean equals(Object obj) {
        return (this == obj);
    }

自旋锁

new String("abc")过程发生了什么

异常的分类

二、JVM

jvm分区

JVM内存模型

程序计数器:记录正在执行的虚拟机字节码指令的地址(如果正在执行的是本地方法则为空)。

Java虚拟机栈:每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。

本地方法栈:与 Java 虚拟机栈类似,它们之间的区别只不过是本地方法栈为本地方法服务。

Java堆:几乎所有对象实例都在这里分配内存。是垃圾收集的主要区域("GC 堆"),虚拟机把 Java 堆分成以下三块:

  • 新生代
  • 老年代
  • 永久代

新生代又可细分为Eden空间、From Survivor空间、To Survivor空间,默认比例为8:1:1。

方法区:方法区(Method Area)与Java堆一样,是各个线程共享的内存区域。Object Class Data(类定义数据)是存储在方法区的,此外,常量、静态变量、JIT编译后的代码也存储在方法区。

运行时常量池:运行时常量池是方法区的一部分。Class 文件中的常量池(编译器生成的各种字面量和符号引用)会在类加载后被放入这个区域。除了在编译期生成的常量,还允许动态生成,例如 String 类的 intern()。这部分常量也会被放入运行时常量池。

直接内存:直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用,而且也可能导致OutOfMemoryError 异常出现。避免在Java堆和Native堆中来回复制数据。

gc算法

垃圾回收算法包括:标记-清除算法,复制算法,标记-整理算法,分代收集算法。

标记—清除算法:

标记/清除算法,分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。

标记阶段:标记的过程其实就是前面介绍的可达性分析算法的过程,遍历所有的GC Roots对象,对从GC Roots对象可达的对象都打上一个标识,一般是在对象的header中,将其记录为可达对象;

清除阶段:清除的过程是对堆内存进行遍历,如果发现某个对象没有被标记为可达对象,则将其回收。

标记-清除

复制算法:

将内存划分为大小相等的两块,每次只使用其中一块,当这一块内存用完了就将还存活的对象复制到另一块上面,然后再把使用过的内存空间进行一次清理。

将内存分为一块较大的 Eden 空间和两块较小的 Survior 空间,每次使用 Eden 空间和其中一块 Survivor。在回收时,将 Eden 和 Survivor 中还存活着的对象一次性复制到另一块 Survivor 空间上,最后清理 Eden 和 使用过的那一块 Survivor。HotSpot 虚拟机的 Eden 和 Survivor 的大小比例默认为 8:1,保证了内存的利用率达到 90 %。如果每次回收有多于 10% 的对象存活,那么一块 Survivor 空间就不够用了,此时需要依赖于老年代进行分配担保,也就是借用老年代的空间。

复制

标记—整理算法:

标记—整理算法和标记—清除算法一样,但是标记—整理算法不是把存活对象复制到另一块内存,而是把存活对象往内存的一端移动,然后直接回收边界以外的内存,因此其不会产生内存碎片。标记—整理算法提高了内存的利用率,并且它适合在收集对象存活时间较长的老年代。

标记—整理

分代收集算法:

分代回收算法实际上是把复制算法和标记整理法的结合,并不是真正一个新的算法,一般分为:老年代和新生代,老年代就是很少垃圾需要进行回收的,新生代就是有很多的内存空间需要回收,所以不同代就采用不同的回收算法,以此来达到高效的回收算法。

新生代:由于新生代产生很多临时对象,大量对象需要进行回收,所以采用复制算法是最高效的。

老年代:回收的对象很少,都是经过几次标记后都不是可回收的状态转移到老年代的,所以仅有少量对象需要回收,故采用标记清除或者标记整理算法

垃圾收集器

G1垃圾回收器

怎么判断对象存活

引用计数法,可达性分析法

gc roots

在Java语言中,可以作为GC Roots的对象包括下面几种:

  • 虚拟机栈(栈帧中的本地变量表)中的引用对象。
  • 方法区中的类静态属性引用的对象。
  • 方法区中的常量引用的对象。
  • 本地方法栈中JNI(Native方法)的引用对象

类加载过程

类加载的过程主要分为三个部分:

  • 加载:指的是把class字节码文件从各个来源通过类加载器装载入内存中。
  • 链接
  • 初始化:对类变量初始化,是执行类构造器的过程。

链接又可以细分为

  • 验证:为了保证加载进来的字节流符合虚拟机规范,不会造成安全错误。
  • 准备:为类变量(注意,不是实例变量)分配内存,并且赋予初值。
  • 解析:将常量池内的符号引用替换为直接引用的过程。

如果你的项目出现了内存泄露,怎么监控这个问题呢

Jvm的参数设置

三、算法与数据结构

快排

    private static void quickSort(int[] array, int _left, int _right) {
        int left = _left;//
        int right = _right;
        int pivot;//基准线
        if (left < right) {
            pivot = array[left];
            while (left != right) {
                //从右往左找到比基准线小的数
                while (left < right && pivot <= array[right]) {
                    right--;
                }
                //将右边比基准线小的数换到左边
                array[left] = array[right];
                //从左往右找到比基准线大的数
                while (left < right && pivot >= array[left]) {
                    left++;
                }
                //将左边比基准线大的数换到右边
                array[right] = array[left];
            }
            //此时left和right指向同一位置
            array[left] = pivot;
            quickSort(array, _left, left - 1);
            quickSort(array, left + 1, _right);
        }
    }

手写求二叉树的高

已知有A、B两个增序数组,先将A、B合成一个新的增序数组C,该如何操作?

给定1/2/3/4/5五个数,已知现在有m = 12543。求用这五个数凑出大于m的最小值(数字不能重复,如:111111)。

希尔排序

    private static void shellSort(int[] array) {
        if (array == null || array.length == 0 || array.length == 1)
            return;
        int gap = array.length / 2;
        while (gap > 0) {// 逐渐减小gap,最终为1,进行直接插入排序
            for (int i = 0; i < gap; i++) {// 排序第i组,每一组内部进行插入排序
                for (int j = gap + i; j < array.length; j += gap) {// 直接插入排序的间隔1变为gap即可
                    int index = j;
                    int insertVal = array[j];
                    while (index > gap - 1 && insertVal < array[index - gap]) {
                        array[index] = array[index - gap];
                        index -= gap;
                    }
                    array[index] = insertVal;//放置insertVal
                }
            }
            gap = gap >> 1;
        }
    }

桶排序

堆排序

public class HeapSort {
    /**
     * 构建大顶堆
     */
    public static void adjustHeap(int[] a, int i, int len) {
        int temp, j;
        temp = a[i];
        for (j = 2 * i; j < len; j *= 2) {// 沿关键字较大的孩子结点向下筛选
            if (j < len && a[j] < a[j + 1])
                ++j; // j为关键字中较大记录的下标
            if (temp >= a[j])
                break;
            a[i] = a[j];
            i = j;
        }
        a[i] = temp;
    }

    public static void heapSort(int[] a) {
        int i;
        for (i = a.length / 2 - 1; i >= 0; i--) {// 构建一个大顶堆
            adjustHeap(a, i, a.length - 1);
        }
        for (i = a.length - 1; i >= 0; i--) {// 将堆顶记录和当前未经排序子序列的最后一个记录交换
            int temp = a[0];
            a[0] = a[i];
            a[i] = temp;
            adjustHeap(a, 0, i - 1);// 将a中前i-1个记录重新调整为大顶堆
        }
    }
} 

最大堆怎么实现

二叉树的反序列化

两个单链表相交,找到交点

两个有序链表的拼接

广度优先搜索

带状态的广度优先搜索

假设电脑内存4kb,然后有10G的数据,怎么排序?

红黑树原理

介绍一下红黑树的特点?介绍一下红黑树插入的一个结点的操作(单侧旋转,双侧旋转)?

LRU

反转链表

将当前节点和下一节点保存起来,然后将当前节点反转。

    public ListNode ReverseList(ListNode head) {
        //head为当前节点,如果当前节点为空的话,那就什么也不做,直接返回null
        ListNode pre = null;//pre为当前节点的前一节点
        ListNode next = null;//next为当前节点的下一节点
        //需要pre和next的目的是让当前节点从pre.head.next1.next2变成pre<-head next1.next2
        //即pre让节点可以反转所指方向,但反转之后如果不用next节点保存next1节点的话,此单链表就此断开了
        //所以需要用到pre和next两个节点
        //1.2.3.4.5
        //1<-2<-3 4.5
        //做循环,如果当前节点不为空的话,始终执行此循环,此循环的目的就是让当前节点从指向next到指向pre
        while (head != null) {
            //先用next保存head的下一个节点的信息,保证单链表不会因为失去head节点的原next节点而就此断裂
            next = head.next;
            //保存完next,就可以让head从指向next变成指向pre了
            head.next = pre;
            //head指向pre后,就继续依次反转下一个节点
            //让pre,head,next依次向后移动一个节点,继续下一次的指针反转
            pre = head;
            head = next;
        }
        //如果head为null的时候,pre就为最后一个节点了,但是链表已经反转完毕,pre就是反转后链表的第一个节点
        //直接输出pre就是我们想要得到的反转后的链表
        return pre;
    }

利用递归走到链表的末端,然后再更新每一个节点的next值 ,实现链表的反转。

    public ListNode ReverseList(ListNode head) {
        //如果链表为空或者链表中只有一个元素 
        if (head == null || head.next == null) return head;
        //先递归找到到链表的末端结点,从后依次反转整个链表
        ListNode reverseHead = ReverseList(head.next);
        //再将当前节点设置为后面节点的后续节点 
        head.next.next = head;
        head.next = null;
        return reverseHead;
    }

判断二叉树

两个字符串的最大连续子序列

两个未排序数组求中位数

求一棵树中的最长路径

二叉树镜像

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;

public class Solution15 {
    /**
     * 用栈遍历二叉树
     *
     * @param root
     */
    public void Mirror_2(TreeNode root) {
        if (root == null) {
            return;
        }
        Deque<TreeNode> deque = new ArrayDeque<>();
        deque.push(root);
        while (!deque.isEmpty()) {
            TreeNode node = deque.pop();
            if (node.left != null || node.right != null) {
                TreeNode temp = node.left;
                node.left = node.right;
                node.right = temp;
            }
            if (node.left != null) {
                deque.push(node.left);
            }
            if (node.right != null) {
                deque.push(node.right);
            }
        }
    }

    /**
     * 递归
     *
     * @param root
     */
    public void Mirror(TreeNode root) {
        if (root == null) {
            return;
        }
        if (root.left == null && root.right == null) {
            return;
        }
        TreeNode temp = root.left;
        root.left = root.right;
        root.right = temp;
        if (root.left != null) {
            Mirror(root.left);
        }
        if (root.right != null) {
            Mirror(root.right);
        }
    }

    public class TreeNode {
        int val = 0;
        TreeNode left = null;
        TreeNode right = null;

        public TreeNode(int val) {
            this.val = val;
        }
    }
}

集合的所有子集

汉诺塔

平衡树

求单向链表倒数第N个结点

判断回文字符串

二叉树前序遍历

二叉树层次遍历

单链表删除一个结点

对单向链表归并排序

实现生产者与消费者问题

生产者消费者如何加锁

矩阵从左上角走到右下角

微信红包分发策略

无序数组的n个数之和等于k,找出这样的数组

统计网站的最大在线人数

最大子数组乘积

在一个数组中,统计出现频率最高的元素

把字符串转换成int型。

顺时针打印矩阵

括号匹配

k路归并

二分查找

public class BinarySearch {
    public static int binarySearch(int[] array, int key) {
        int start = 0;
        int end = array.length - 1;
        while (start <= end) {
            int mid = (end - start) / 2 + start;
            if (key < array[mid]) {
                end = mid - 1;
            } else if (key > array[mid]) {
                start = mid + 1;
            } else {
                return mid;
            }
        }
        return -1;
    }

    public static int binarySearch_2(int[] array, int start, int end, int key) {
        int mid = (end - start) / 2 + start;
        if (array[mid] == key) {
            return mid;
        }
        if (start >= end) {
            return -1;
        } else if (key > array[mid]) {
            return binarySearch_2(array, mid + 1, end, key);
        } else if (key < array[mid]) {
            return binarySearch_2(array, start, mid - 1, key);
        }
        return -1;
    }
}

二分查找第一个大于等于

最小字典序

四、数据库

mysql主从复制

数据库ACID的特性

  1. 原子性(Atomicity):事务被视为不可分割的最小单元,要么全部提交成功,要么全部失败回滚。
  2. 一致性(Consistency):事务执行前后都保持一致性状态。在一致性状态下,所有事务对一个数据的读取结果都是相同的。
  3. 隔离性(Isolation):一个事务所做的修改在最终提交以前,对其它事务是不可见的。
  4. 持久性(Durability):一旦事务提交,则其所做的修改将会永远保存到数据库中。即使系统发生崩溃,事务执行的结果也不能丢失。可以通过数据库备份和恢复来保证持久性。

说一下B+树和B-树。

事务的实现原理(redo+undo)

MVCC

Mysql 的锁,默认的锁程度

mysql的引擎

innodb实现原理

innodb和myisam的区别

b+索引和hash索引啥区别

数据库隔离级别

  1. 未提交读(READ UNCOMMITTED):事务中的修改,即使没有提交,对其它事务也是可见的。最低级别,任何情况都无法保证。
  2. 提交读(READ COMMITTED):一个事务只能读取已经提交的事务所做的修改。换句话说,一个事务所做的修改在提交之前对其它事务是不可见的。可避免脏读的发生。
  3. 可重复读(REPEATABLE READ):保证在同一个事务中多次读取同样数据的结果是一样的。可避免脏读、不可重复读的发生。
  4. 可串行化(SERIALIXABLE):强制事务串行执行。可避免脏读、不可重复读、幻读的发生。

四种隔离级别的对比

隔离级别脏读不可重复读幻影读
未提交读YESYESYES
提交读NOYESYES
可重复读NONOYES
可串行化NONONO

介绍redis和mysql的区别

Redis的应用场景

redis为何效率高

五、网络

结合OSI七层模型讲一下,一次http网络请求的全过程。

TCP三次握手

所谓三次握手(Three-Way Handshake)即建立TCP连接,就是指建立一个TCP连接时,需要客户端和服务端总共发送3个包以确认连接的建立。整个流程如下图所示:

TCP三次握手

  1. 第一次握手:Client将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给Server,Client进入SYN_SENT状态,等待Server确认。
  2. 第二次握手:Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。
  3. 第三次握手:Client收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给Server,Server检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以开始传输数据了。

为什么不是两次和四次握手

主要是为了防止两次握手情况下已失效的连接请求报文段突然又传送到服务端,而产生的错误。举例如下:

Client向Server发出TCP连接请求,第一个连接请求报文在网络的某个节点长时间滞留,Client超时后认为报文丢失,于是再重传一次连接请求,Server收到后建立连接。数据传输完毕后双方断开连接。而此时,前一个滞留在网络中的连接请求到达了服务端Server,而Server认为Client又发来连接请求,若采用的是“两次握手”,则这种情况下Server认为传输连接已经建立,并一直等待Client传输数据,而Client此时并无连接请求,因此不予理睬,这样就造成了Server的资源白白浪费了;但此时若是使用“三次握手”,则Server向Client返回确认报文段,由于是一个失效的请求,因此Client不予理睬,建立连接失败。第三次握手的作用:防止已失效的连接请求报文段突然又传送到了服务器。

TCP四次挥手

所谓四次挥手(Four-Way Wavehand)即终止TCP连接,就是指断开一个TCP连接时,需要客户端和服务端总共发送4个包以确认连接的断开。整个流程如下图所示:

TCP四次挥手

  1. 第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。
  2. 第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。
  3. 第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。
  4. 第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。

http2.0和http1.0区别

HTTP/1.0:

  • 请求与响应支持头域
  • 响应对象以一个响应状态行开始
  • 响应对象不只限于超文本
  • 开始支持客户端通过POST方法向Web服务器提交数据,支持GET、HEAD、POST方法
  • 支持长连接(但默认还是使用短连接),缓存机制,以及身份认证

HTTP/1.1:

  1. 默认为长连接
  2. 提供了范围请求功能(宽带优化)
  3. 提供了虚拟主机的功能(HOST域)
  4. 多了一些缓存处理字段
  5. 错误通知的管理

HTTP/2.0:

  1. 二进制分帧
  2. 多路复用
  3. 头部压缩
  4. 请求优先级
  5. 服务端推送

长连接和短连接

tcp5层结构

image

  1. 物理层:考虑的是怎样在传输媒体上传输数据比特流,而不是指具体的传输媒体。物理层的作用是尽可能屏蔽传输媒体和通信手段的差异,使数据链路层感觉不到这些差异。
  2. 数据链路层:网络层针对的还是主机之间的数据传输服务,而主机之间可以有很多链路,链路层协议就是为相邻结点之间提供服务。数据链路层把网络层传来的分组封装成帧。
  3. 网络层:为主机之间提供数据传输服务,而像运输层协议那样是为主机中的进程提供服务。网络层把运输层传递下来的报文段或者用户数据报封装成分组。
  4. 运输层:提供的是进程间的通用数据传输服务。由于应用层协议很多,定义通用的运输层协议就可以支持不断增多的应用层协议。运输层包括两种协议:传输控制协议 TCP,提供面向连接、可靠的数据传输服务,数据单位为报文段;用户数据报协议 UDP,提供无连接、尽最大努力的数据传输服务,数据单位为用户数据报。TCP 主要提供完整性服务,UDP 主要提供及时性服务。
  5. 应用层:为特定应用程序提供数据传输服务,例如 HTTP、DNS 等。数据单位为报文。

三类IP地址

A类地址

  • A类地址第1字节为网络地址(最高位固定是0),另外3个字节为主机地址。
  • A类地址范围:1.0.0.0 - 126.255.255.255,其中0和127作为特殊地址。
  • A类网络默认子网掩码为255.0.0.0,也可写作/8。
  • A类网络最大主机数量是256×256×256-2=166777214(减去1个主机位为0的网络地址和1个广播地址)。

B类地址

  • B类地址第1字节(最高位固定是10)和第2字节为网络地址,另外2个字节为主机地址。
  • B类地址范围:128.0.0.0 - 191.255.255.255。
  • B类网络默认子网掩码为255.255.0.0,也可写作/16。
  • B类网络最大主机数量256×256-2=6554。

C类地址

  • C类地址第1字节(最高位固定是110)、第2字节和第3个字节,另外1个字节为主机地址。
  • C类地址范围:192.0.0.0 - 223.255.255.255。
  • C类网络默认子网掩码为255.255.255.0,也可写作/24。
  • C类网络最大主机数量256-2=254。

输入URL之后发生了什么

域名解析 --> 发起TCP的3次握手 --> 建立TCP连接后发起http请求 --> 服务器响应http请求,浏览器得到html代码 --> 浏览器解析html代码,并请求html代码中的资源(如js、css、图片等) --> 浏览器对页面进行渲染呈现给用户

如果 POST 请求被拦截然后 token 被获取怎么办

HTTPS

ca证书里有什么

DNS 解析

浏览器缓存 --> 系统缓存 --> 路由器缓存 --> ISP(互联网服务提供商)DNS缓存 --> 根域名服务器 --> 顶级域名服务器 --> 主域名服务器 --> 保存结果至缓存

滑动窗口

拥塞控制

cookie和session区别

联系:

Cookie与Session都是用来跟踪浏览器用户身份的会话方式。

区别:

  • Cookie数据存放在客户的浏览器上,Session数据放在服务器上。
  • Cookie不是很安全,别人可以分析存放在本地的Cookie并进行Cookie欺骗,如果主要考虑到安全应当使用加密的Cookie或者Session。
  • Session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,如果主要考虑到减轻服务器性能方面,应当使用Cookie。
  • 单个Cookie在客户端的限制是4K,很多浏览器都限制一个站点最多保存20个Cookie。

cookie欺诈

http协议中状态码

  • 1XX 信息,服务器收到请求,需要请求者继续执行操作
  • 2XX 成功,操作被成功接收并处理
  • 3XX 重定向,需要进一步的操作以完成请求
  • 4XX 客户端错误,请求包含语法错误或无法完成请求
  • 5XX 服务器错误,服务器在处理请求的过程中发生了错误

get和post区别

GET 请求:

  • GET 请求可被缓存
  • GET 请求保留在浏览器历史记录中
  • GET 请求可被收藏为书签
  • GET 请求不应在处理敏感数据时使用
  • GET 请求有长度限制
  • GET 请求只应当用于取回数据

POST 请求 :

  • POST 请求不会被缓存
  • POST 请求不会保留在浏览器历史记录中
  • POST 不能被收藏为书签
  • POST 请求对数据长度没有要求

六、操作系统

进程间通信

  • 消息传递
    • 管道
    • 消息队列
    • 套接字
  • 共享内存

进程和线程

进程:进程是操作系统资源分配的基本单位。每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1–n个线程。

线程:线程是CPU独立调度的基本单位。同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。

线程和进程的生命周期:新建、就绪、运行、阻塞、死亡

单核,一个时间片上,可以有多个线程吗?

linux下排查某个死循环的线程

select poll epoll区别

常用Linux命令

linux查找一个文件中的某个字

linux的ping命令是什么

ping 192.168.1.206

ping -c 10 192.168.1.206 在发送指定数目的包后停止

Linux管道的作用

Linux启动过程

  1. 开机BIOS自检,加载硬盘。
  2. 读取MBR,进行MBR引导。
  3. grub引导菜单(Boot Loader)。
  4. 加载内核kernel。
  5. 启动init进程,依据inittab文件设定运行级别
  6. init进程,执行rc.sysinit文件。
  7. 启动内核模块,执行不同级别的脚本程序。
  8. 执行/etc/rc.d/rc.local
  9. 启动mingetty,进入系统登陆界面。

死锁的成因和解决死锁的方法

僵尸进程

七、设计模式

说下常用设计模式

单例模式,工厂模式,装饰模式,代理模式,观察者模式

设计模式分类

创建模式:单例模式、工厂模式、抽象工厂模式、生成器模式、原型模式。

结构模式:适配器模式、桥接模式、组成模式、装饰模式、外观模式、享元模式、代理模式。

行为模式:中介者模式、命令模式、备忘录模式、状态模式、策略模式、解释器模式、迭代器模式、观察者模式、访问者模式、模板方法模式。

单例模式

public class Singleton {
    private volatile static Singleton instance = null;

    private Singleton() {

    }
    
    /**
     * 当第一次调用getInstance()方法时,instance为空,同步操作,保证多线程实例唯一
     * 当第一次后调用getInstance()方法时,instance不为空,不进入同步代码块,减少了不必要的同步
     */
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

观察者模式

八、其他

zookeeper介绍下

zookeeper的用途

springboot介绍下

IOC原理

SpringMVC请求流程

  1. 客户端发送HTTP请求到服务器
  2. SpringMVC的核心DispatcherServlet将请求交给HandlerMapping处理
  3. HandlerMapping通过查询机制找到处理当前请求的Handler
  4. DispatcherServlet将请求交给这个Handler处理
  5. Handler处理完成后返回一个ModleAndView对象,这个对象包含视图逻辑名和数据对象
  6. 返回的视图逻辑名会通过视图解析器解析成真正的视图,并交给DispatcherServlet处理
  7. DispatcherServlet将请求分派给真正的视图对象,并反映到客户端

CAP

spring设计模式

spring bean生命周期

SpringMvc常用注解

@Controller:用于标记在一个类上,使用它标记的类就是一个SpringMVC Controller 对象。

@RequestMapping:是一个用来处理请求地址映射的注解,可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。

@Resource和@Autowired:@Resource和@Autowired都是做bean的注入时使用,其实@Resource并不是Spring的注解,它的包是javax.annotation.Resource,需要导入,但是Spring支持该注解的注入。

@ResponseBody:返回的数据不是html标签的页面,而是其他某种格式的数据时(如json、xml等)使用。

@Repository:DAO层

@Service:服务

maven常用命令

git常用命令

#查看分支
$ git branch
#创建分支
$ git branch f_20180428_orderMigration
#切换分支
$ git checkout f_20180428_orderMigration
#创建+切换分支
$ git checkout -b f_20180428_orderMigration
#合并某分支到当前分支
$ git merge origin master
#删除分支
$ git branch -d f_20180428_orderMigration
#查看变更历史
$ git log

和同事一起Git同一分支的代码怎么合并

ngix+redis集群+session共享或者同步

设计一个秒杀系统

分布式环境下如何保持session一致性

rpc了解吗

负载均衡

netty

二阶段提交

25匹马,5条赛道,得出前三,最少次数

spring事务,如果一个事务中还有一个事务,该怎么处理