超全面试-笔记(java)

 class ListNode {
      int val;
     ListNode next;
      ListNode() {}
      ListNode(int val) { this.val = val; }
      ListNode(int val, ListNode next) { this.val = val; this.next = next; }
  }
class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }

笔记

1.LRU算法java数据结构实现

链接: LRUCache-Java实现.
LRU:(least recently used) 最近最少使用算法。LRU算法的java中数据结构的实现是一个LRU算法在面试中经常被问到的问题,我们可以用LinkedList来表示最近和最少使用。将访问过的数据用LinkedList数据结构进行存储,如果查找最少使用,直接返回LinkedList的尾节点即可。如果添加一个最近访问的数据a,可以将a从链表中的位置删除,移到链表的头部。

但是链表中查询数据DATA的位置一般访问速度慢,所以需要借助其它数据结构来完成查找,我们知道HashMap是一个高效的查询数据结构。如果我们将该数据DATA作为键,DATA对应的节点作为值,存储在HashMap中,那么,我们可以得到查询复杂度为O(1)的操作。

JAVA中已经帮助我们实现了一个这样的数据结构,LinkedHashMap.LinkedHashMap存放的键值对和存放时的位置一致。不会变化,可以设置LinkedHashMap按照插入的顺序排序,也可以按照访问的顺序排序,最先访问的放置在最后面,最近访问的在最前面。

LinkedHashMap自身已经实现了顺序存储,默认情况下是按照元素的添加顺序存储,也可以启用按照访问顺序存储,即最近读取的数据放在最前面,最早读取的数据放在最后面,然后它还有一个判断是否删除最老数据的方法,默认是返回false,即不删除数据,我们使用LinkedHashMap实现LRU缓存的方法就是对LinkedHashMap实现简单的扩展,

2.红黑树

在这里插入图片描述

  1. 节点是红色或者是黑色;
  2. 每个叶节点(NIL或空节点)是黑色;
  3. 每个红色节点的两个子节点都是黑色的(也就是说不存在两个连续的红色节点);
  4. 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。

优势:红黑树相比avl树,在检索的时候效率其实差不多,都是通过平衡来二分查找。但对于插入删除等操作效率提高很多。红黑树不像avl树一样追求绝对的平衡,他允许局部很少的不完全平衡,这样对于效率影响不大,但省去了很多没有必要的调平衡操作,avl树调平衡有时候代价较大,所以效率不如红黑树,在现在很多地方都是底层都是红黑树的天下啦。
红黑树和AVL树都是最常用的平衡二叉搜索树,它们的查找、删除、修改都是O(lgn) time

2.1 AVL树和红黑树有几点比较和区别:

(1)AVL树是更加严格的平衡,因此可以提供更快的查找速度,一般读取查找密集型任务,适用AVL树。
(2)红黑树更适合于插入修改密集型任务。
(3)通常,AVL树的旋转比红黑树的旋转更加难以平衡和调试。
总结:
(1)AVL以及红黑树是高度平衡的树数据结构。它们非常相似,真正的区别在于在任何添加/删除操作时完成的旋转操作次数。
(2)两种实现都缩放为a O(lg N),其中N是叶子的数量,但实际上AVL树在查找密集型任务上更快:利用更好的平衡,树遍历平均更短。另一方面,插入和删除方面,AVL树速度较慢:需要更高的旋转次数才能在修改时正确地重新平衡数据结构。
(3)在AVL树中,从根到任何叶子的最短路径和最长路径之间的差异最多为1。在红黑树中,差异可以是2倍。
(4)两个都给O(log n)查找,但平衡AVL树可能需要O(log n)旋转,而红黑树将需要最多两次旋转使其达到平衡(尽管可能需要检查O(log n)节点以确定旋转的位置)。旋转本身是O(1)操作,因为你只是移动指针。

2.2HasMap为什么会引入红黑树

在JDK1.6,JDK1.7中,HashMap采用位桶+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。而JDK1.8中,HashMap采用位桶+链表+红黑树实现,当链表长度超过阈值(8)时,并且数组长度大于等我64时。将链表转换为红黑树,这样大大减少了查找时间。在jdk1.8版本后,java对HashMap做了改进,在链表长度大于8的时候,将后面的数据存在红黑树中,以加快检索速度。

JDK1.8HashMap的红黑树是这样解决的:
如果某个桶中的记录过大的话(当前是TREEIFY_THRESHOLD = 8),HashMap会动态的使用一个专门的treemap实现来替换掉它。这样做的结果会更好
它是如何工作的?前面产生冲突的那些KEY对应的记录只是简单的追加到一个链表后面,这些记录只能通过遍历来进行查找。但是超过这个阈值后HashMap开始将列表升级成一个二叉树,使用哈希值作为树的分支变量,如果两个哈希值不等,但指向同一个桶的话,较大的那个会插入到右子树里。如果哈希值相等,HashMap希望key值最好是实现了Comparable接口的,这样它可以按照顺序来进行插入。这对HashMap的key来说并不是必须的,不过如果实现了当然最好。如果没有实现这个接口,在出现严重的哈希碰撞的时候,你就并别指望能获得性能提升了。

为什么是红黑树?为什么不直接采用红黑树还要用链表?

  • 因为红黑树需要进行左旋,右旋操作, 而单链表不需要
  • 如果元素小于8个,查询成本高,新增成本低
  • 如果元素大于8个,查询成本低,新增成本高

3.HashMap的长度为什么要是2的n次方

HashMap为了存取高效,要尽量较少碰撞,就是要尽量把数据分配均匀,每个链表长度大致相同,这个实现就在把数据存到哪个链表中的算法;
这个算法实际就是取模,hash%length,计算机中直接求余效率不如位移运算,源码中做了优化hash&(length-1),
hash%length==hash&(length-1)的前提是length是2的n次方;
为什么这样能均匀分布减少碰撞呢?2的n次方实际就是1后面n个0,2的n次方-1 实际就是n个1;

4.协程

协程诞生解决的是低速IO和高速的CPU的协调问题,解决这类问题主要有三个有效途径:

异步非阻塞网络编程(libevent、libev、redis、Nginx、memcached这类)
协程(golang、gevent)
“轻量级线程”,相当于是在语言层面做抽象(Erlang)

协程和线程差异

  1. 协程其实可以认为是比线程更小的执行单元。为啥说他是一个执行单元,因为他自带CPU上下文。这样只要在合适的时机,我们可以把一个协程 切换到 另一个协程。只要这个过程中保存或恢复 CPU上下文那么程序还是可以运行的。

  2. 一个线程可以多个协程,一个进程也可以单独拥有多个协程,这样python中则能使用多核CPU。

  3. 线程进程都是同步机制,而协程则是异步

  4. 协程的开销远远小于线程的开销

  5. 协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的

1.协程(用户级线程)完全由用户自己的程序进行调度(协作式调度),需要协程 自己主动把控制权转让出去之后,其他协程才能被执行到。
2.协程,是在应用层模拟的线程,他避免了上下文切换的额外耗费,兼顾了多线程的优点。简化了高并发程序的复杂度。协程还是通过共享内存通讯.
目前的协程框架一般都是设计成 1:N 模式。
所谓 1:N 就是一个线程作为一个容器里面放置多个协程。
那么谁来适时的切换这些协程?答案是有协程自己主动让出CPU,
也就是每个协程池里面有一个调度器,这个调度器是被动调度的。
意思就是他不会主动调度。
而且当一个协程发现自己执行不下去了(比如异步等待网络的数据回来,但是当前还没有数据到),
这个时候就可以由这个协程通知调度器,
这个时候执行到调度器的代码,调度器根据事先设计好的调度算法找到当前最需要CPU的协程。
切换这个协程的CPU上下文把CPU的运行权交个这个协程,直到这个协程出现执行不下去需要等等的情况,
或者它调用主动让出CPU的API之类,触发下一次调度。对的没错就是类似于 领导人模式那么这个实现有没有问题?
其实是有问题的,假设这个线程中有一个协程是CPU密集型的他没有IO操作,也就是自己不会主动触发调度器调度的过程,
那么就会出现其他协程得不到执行的情况,所以这种情况下需要程序员自己避免。

5.HashMap为什么不是线程安全?

jdk7中由于头插法,会存在resize后链表中形成环的情况。
线程1阻塞前链表情况:a->b->null
线程2此时执行,会采用头插法将该链表放入新的table,放入后变成b->a->null
线程1此时返回,同样采用头插法插入该线程自己的新的table中,两次循环后变为b->a,但是由于线程2执行时的后果会导致b的next不是null而是a,所以循环多执行一次,e此时变为a会导致a.next = newTablei然后发生循环。

jdk8造成线程不安全分2中情况;

  1. 假设现在有线程A 和线程B 共同对同一个HashMap进行PU操作,假设A和B插入的Key-Value中key的hashcode是相同的,这说明该键值对将会插入到Table的同一个下标的,也就是会发生哈希碰撞,此时HashMap按照平时的做法是形成一个链表(若超过八个节点则是红黑树),现在我们插入的下标为null(Table[i]==null)则进行正常的插入,此时线程A进行到了这一步正准备插入,这时候线程A堵塞,线程B获得运行时间,进行同样操作,也是Table[i]==null , 此时它直接运行完整个PUT方法,成功将元素插入. 随后线程A获得运行时间接上上面的判断继续运行,进行了Table[i]==null的插入(此时其实应该是Table[i]!=null的操作,因为前面线程B已经插入了一个元素了),这样就会直接把原来线程B插入的数据直接覆盖了,如此一来就造成了线程不安全问题.
    链接: hsahmap为什么线程不安全 https://blog.csdn.net/weixin_43092168/article/details/89791106
  2. resize的时候造成的,jdk8在(++size>threshold-头插法)代码片段,如果并发操作,可能导致两次扩容,但最终结果只有一次扩容的效果,从而线程不安全(循环链表)

6.如何处理TCP的黏包和拆包问题:

通常会有以下一些常用的方法:
1.使用带消息头的协议、消息头存储消息开始标识及消息长度信息,服务端获取消息头的时候解析出消息长度,然后向后读取该长度的内容。
2.设置定长消息,服务端每次读取既定长度的内容作为一条完整消息,当消息不够长时,空位补上固定字符。
3.设置消息边界,服务端从网络流中按消息编辑分离出消息内容,一般使用‘\n’。

TCP粘包原因

1、发送方原因:
要发送的数据小于TCP发送缓冲区的大小,TCP将多次写入缓冲区的数据一次发送出去,将会发生粘包。

TCP默认使用Nagle算法,Nagle算法可能造出现粘包问题。

Nagle算法主要作用:减少网络中报文段的数量。而Nagle算法主要做两件事:

  • 1.只有上一个分组得到确认,才会发送下一个分组
  • 2.收集多个小分组,在一个确认到来时一起发送

2、接收方原因
接收数据端的应用层没有及时读取接收缓存区中的数据,将发生粘包。

3. 什么时候需要处理粘包现象
1.若粘包的多组数据为同一块数据(文件)的不同部分,则无需处理。
2.若多个分组毫无关联,则需要处理粘包现象。

7.Spring 中使用了哪些设计模式?

工厂模式:
spring中的BeanFactory就是简单工厂模式的体现,根据传入唯一的标识来获得bean对象;
单例模式:
提供了全局的访问点BeanFactory;
代理模式:
AOP功能的原理就使用代理模式(1、JDK动态代理。2、CGLib字节码生成技术代理。)
装饰器模式:
依赖注入就需要使用BeanWrapper;
观察者模式:
spring中Observer模式常用的地方是listener的实现。如ApplicationListener。
策略模式:
Bean的实例化的时候决定采用何种方式初始化bean实例(反射或者CGLIB动态字节码生成)。
链接: 设计模式https://blog.csdn.net/qq_35190492/article/details/105992063.

8.springMVC流程:

(1):用户请求发送给DispatcherServlet,DispatcherServlet调用HandlerMapping处理器映射器;

(2):HandlerMapping根据xml或注解找到对应的处理器,生成处理器对象返回给DispatcherServlet;

(3):DispatcherServlet会调用相应的HandlerAdapter;

(4):HandlerAdapter经过适配调用具体的处理器去处理请求,生成ModelAndView返回给DispatcherServlet

(5):DispatcherServlet将ModelAndView传给ViewReslover解析生成View返回给DispatcherServlet;

(6):DispatcherServlet根据View进行渲染视图;

->DispatcherServlet->HandlerMapping->Handler
->DispatcherServlet->HandlerAdapter处理handler->ModelAndView
->DispatcherServlet->ModelAndView->ViewReslover->View
->DispatcherServlet->返回给客户

9.skiplist与平衡树、哈希表的比较

1.skiplist和各种平衡树(如AVL、红黑树等)的元素是有序排列的,而哈希表不是有序的。因此,在哈希表上只能做单个key的查找,不适宜做范围查找。所谓范围查找,指的是查找那些大小在指定的两个值之间的所有节点。
2.在做范围查找的时候,平衡树比skiplist操作要复杂。在平衡树上,我们找到指定范围的小值之后,还需要以中序遍历的顺序继续寻找其它不超过大值的节点。如果不对平衡树进行一定的改造,这里的中序遍历并不容易实现。而在skiplist上进行范围查找就非常简单,只需要在找到小值之后,对第1层链表进行若干步的遍历就可以实现。
3.平衡树的插入和删除操作可能引发子树的调整,逻辑复杂,而skiplist的插入和删除只需要修改相邻节点的指针,操作简单又快速。
4.从内存占用上来说,skiplist比平衡树更灵活一些。一般来说,平衡树每个节点包含2个指针(分别指向左右子树),而skiplist每个节点包含的指针数目平均为1/(1-p),具体取决于参数p的大小。如果像Redis里的实现一样,取p=1/4,那么平均每个节点包含1.33个指针,比平衡树更有优势。
5.查找单个key,skiplist和平衡树的时间复杂度都为O(log n),大体相当;而哈希表在保持较低的哈希值冲突概率的前提下,查找时间复杂度接近O(1),性能更高一些。所以我们平常使用的各种Map或dictionary结构,大都是基于哈希表实现的。
6.从算法实现难度上来比较,skiplist比平衡树要简单得多。

10.线程间通信的几种实现方式

方式一:使用 volatile 关键字
基于 volatile 关键字来实现线程间相互通信是使用共享内存的思想,大致意思就是多个线程同时监听一个变量,当这个变量发生变化的时候 ,线程能够感知并执行相应的业务。这也是最简单的一种实现方式
方式二:使用Object类的wait() 和 notify() 方法
众所周知,Object类提供了线程间通信的方法:wait()、notify()、notifyaAl(),它们是多线程通信的基础,而这种实现方式的思想自然是线程间通信。
注意: wait和 notify必须配合synchronized使用,wait方法释放锁,notify方法不释放锁

11.JVM内存模型

JVM内存空间分为五部分,分别是:方法区、堆、Java虚拟机栈、本地方法栈、程序计数器
1.方法区主要用来存放类信息、类的静态变量、常量、运行时常量池等,方法区的大小是可以动态扩展的,
2.堆主要存放的是数组、类的实例对象、字符串常量池等。
3.Java虚拟机栈是描述JAVA方法运行过程的内存模型,Java虚拟机栈会为每一个即将执行的方法创建一个叫做“栈帧”的区域,该区域用来存储该方法运行时需要的一些信息,包括:局部变量表、操作数栈、动态链接、方法返回地址等。比如我们方法执行过程中需要创建变量时,就会将局部变量插入到局部变量表中,局部变量的运算、传递等在操作数栈中进行,当方法执行结束后,这个方法对应的栈帧将出栈,并释放内存空间。栈中会发生的两种异常,StackOverFlowError和OutOfMemoryError,StackOverFlowError表示当前线程申请的栈超过了事先定好的栈的最大深度,但内存空间可能还有很多。 而OutOfMemoryError是指当线程申请栈时发现栈已经满了,而且内存也全都用光了。

4.本地方法栈结构上和Java虚拟机栈一样,只不过Java虚拟机栈是运行Java方法的区域,而本地方法栈是运行本地方法的内存模型。运行本地方法时也会创建栈帧,同样栈帧里也有局部变量表、操作数栈、动态链接和方法返回地址等,在本地方法执行结束后栈帧也会出栈并释放内存资源,也会发生OutOfMemoryError。

5.最后是程序计数器,程序计数器是一个比较小的内存空间,用来记录当前线程正在执行的那一条字节码指令的地址。如果当前线程正在执行的是本地方法,那么此时程序计数器为空。程序计数器有两个作用,1、字节码解释器通过改变程序计数器来一次读取指令,从而实现代码的流程控制,比如我们常见的顺序、循环、选择、异常处理等。2、在多线程的情况下,程序计数器用来记录当前线程执行的位置,当线程切换回来的时候仍然可以知道该线程上次执行到了哪里。而且程序计数器是唯一一个不会出现OutOfMeroryError的内存区域。

12.谈谈synchronized与ReentrantLock的区别?

21.synchronized和lock区别以及底层原理

  • ① 底层实现上来说,synchronized 是JVM层面的锁,是Java关键字,通过monitor对象来完成(monitorenter与monitorexit),对象只有在同步块或同步方法中才能调用wait/notify方法,ReentrantLock 是从jdk1.5以来(java.util.concurrent.locks.Lock)提供的API层面的锁。
    synchronized 的实现涉及到锁的升级,具体为无锁、偏向锁、自旋锁、向OS申请重量级锁,ReentrantLock实现则是通过利用CAS(CompareAndSwap)自旋机制保证线程操作的原子性和volatile保证数据可见性以实现锁的功能。
  • ② 是否可手动释放:
    synchronized 不需要用户去手动释放锁,synchronized 代码执行完后系统会自动让线程释放对锁的占用; ReentrantLock则需要用户去手动释放锁,如果没有手动释放锁,就可能导致死锁现象。一般通过lock()和unlock()方法配合try/finally语句块来完成,使用释放更加灵活。
  • ③ 是否可中断
    synchronized是不可中断类型的锁,除非加锁的代码中出现异常或正常执行完成; ReentrantLock则可以中断,可通过trylock(long timeout,TimeUnit unit)设置超时方法或者将lockInterruptibly()放到代码块中,调用interrupt方法进行中断。
  • ④ 是否公平锁
    synchronized为非公平锁 ReentrantLock则即可以选公平锁也可以选非公平锁,通过构造方法new ReentrantLock时传入boolean值进行选择,为空默认false非公平锁,true为公平锁。
  • ⑤ 锁是否可绑定条件Condition
    synchronized不能绑定; ReentrantLock通过绑定Condition结合await()/singal()方法实现线程的精确唤醒,而不是像synchronized通过Object类的wait()/notify()/notifyAll()方法要么随机唤醒一个线程要么唤醒全部线程。
  • ⑥ 锁的对象
    synchronzied锁的是对象,锁是保存在对象头里面的,根据对象头数据来标识是否有线程获得锁/争抢锁;ReentrantLock锁的是线程,根据进入的线程和int类型的state标识锁的获得/争抢。

13.ArrayList的大小是如何自动增加的?

1、添加元素时,首先进行判断是否大于默认容量10

2、如果,小于默认容量,直接在原来基础上+1,元素添加完毕

3、如果,大于默认容量,则需要进行扩容,扩容核心是grow()方法

  • 3.1 扩容之前,首先创建一个新的数组,且旧数组被复制到新的数组中
    这样就得到了一个全新的副本,我们在操作时就不会影响原来数组了
  • 3.2 然后通过位运算符将新的容量更新为旧容量的1.5陪(原来长度的一半再加上原长度也就是每次扩容是原来的1.5倍)
  • 3.3 如果新的容量-旧的容量<=0,就拿新的容量-最大容量长度如果<=0的,那么最终容量就是扩容后的容量

4.添加(add)方法的实现,添加时首先检查数组的容量是否满足

5.ArrayList线程不安全与LinkList线程都不安全

  • 体现:值的覆盖问题

在单线程运行的情况下,如果 Size = 0,添加一个元素后,此元素在位置 0,而且 Size=1;
而如果是在多线程情况下,比如有两个线程,线程 A 先将元素存放在位置 0。但是此时 CPU 调度线程A暂停,线程 B 得到运行的机会。线程B也向此 ArrayList 添加元素,因为此时 Size 仍然等于 0 (注意哦,我们假设的是添加一个元素是要两个步骤哦,而线程A仅仅完成了步骤1),所以线程B也将元素存放在位置0。然后线程A和线程B都继续运行,都增加 Size 的值。
那好,现在我们来看看 ArrayList 的情况,元素实际上只有一个,存放在位置 0,而 Size 却等于 2。这就是“线程不安全”了。

14.索引的数据结构

为什么数据库要用b+树而不用b树,平衡二叉树

1.平衡二叉树

平衡二叉树是采用二分法思维,平衡二叉查找树除了具备二叉树的特点,最主要的特征是树的左右两个子树的层级最多相差1。在插入删除数据时通过左旋/右旋操作保持二叉树的平衡,不会出现左子树很高、右子树很矮的情况。
使用平衡二叉查找树查询的性能接近于二分查找法,时间复杂度是 O(log2n)。查询id=6,只需要两次IO。
就这个特点来看,可能各位会觉得这就很好,可以达到二叉树的理想的情况了。然而依然存在一些问题:
1.时间复杂度和树高相关。树有多高就需要检索多少次,每个节点的读取,都对应一次磁盘 IO 操作。树的高度就等于每次查询数据时磁盘 IO 操作的次数。磁盘每次寻道时间为10ms,在表数据量大时,查询性能就会很差。(1百万的数据量,log2n约等于20次磁盘IO,时间20*10=0.2s)

3.平衡二叉树不支持范围查询快速查找,范围查询时需要从根节点多次遍历,查询效率不高。

2.B树:改造二叉树

MySQL的数据是存储在磁盘文件中的,查询处理数据时,需要先把磁盘中的数据加载到内存中,磁盘IO 操作非常耗时,所以我们优化的重点就是尽量减少磁盘 IO 操作。访问二叉树的每个节点就会发生一次IO,如果想要减少磁盘IO操作,就需要尽量降低树的高度。那如何降低树的高度呢?

  • 这种数据结构我们称为B树,B树是一种多叉平衡查找树,如下图主要特点:
    • 1.B树的节点中存储着多个元素,每个内节点有多个分叉。
    • 2.节点中的元素包含键值和数据,节点中的键值从大到小排列。也就是说,在所有的节点都储存数据。
    • 3.父节点当中的元素不会出现在子节点中。
    • 4.所有的叶子结点都位于同一层,叶节点具有相同的深度,叶节点之间没有指针连接。
  • 缺点:
    • 1.B树不支持范围查询的快速查找,你想想这么一个情况如果我们想要查找10和35之间的数据,查找到15之后,需要回到根节点重新遍历查找,需要从根节点进行多次遍历,查询效率有待提高。

    • 2.如果data存储的是行记录,行的大小随着列数的增多,所占空间会变大。这时,一个页中可存储的数据量就会变少,树相应就会变高,磁盘IO次数就会变大。

15.Nginx

工作流程:

1、用户通过域名发出访问Web服务器的请求,该域名被DNS服务器解析为反向代理服务器的IP地址;
2、反向代理服务器接受用户的请求;
3、反向代理服务器在本地缓存中查找请求的内容,找到后直接把内容发送给用户;
4、如果本地缓存里没有用户所请求的信息内容,反向代理服务器会代替用户向源服务器请求同样的信息内容,并把信息内容发给用户,如果信息内容是非缓存的还会把它保存到缓存中。


Nginx模块:

Nginx有五大优点:模块化、事件驱动、异步、非阻塞、多进程单线程。由内核和模块组成的,其中内核完成的工作比较简单,仅仅通过查找配置文件将客户端请求映射到一个location block,然后又将这个location block中所配置的每个指令将会启动不同的模块去完成相应的工作。


作用:

  • 保护了真实的web服务器,保证了web服务器的资源安全
  • 节约了有限的IP地址资源
  • 减少WEB服务器压力,提高响应速度
    • 反向代理就是通常所说的web服务器加速,它是一种通过在繁忙的web服务器和外部网络之间增加一个高速的web缓冲服务器来降低实际的web服务器的负载的一种技术。反向代理是针对web服务器提高加速功能,作为代理缓存,它并不是针对浏览器用户,而针对一台或多台特定的web服务器,它可以代理外部网络对内部网络的访问请求。
    • 反向代理服务器会强制将外部网络对要代理的服务器的访问经过它,这样反向代理服务器负责接收客户端的请求,然后到源服务器上获取内容,把内容返回给用户,并把内容保存到本地,以便日后再收到同样的信息请求时,它会把本地缓存里的内容直接发给用户,以减少后端web服务器的压力,提高响应速度。因此Nginx还具有缓存功能。
  • 请求的统一控制,包括设置权限、过滤规则等;
  • 区分动态和静态可缓存内容;
  • 实现负载均衡,内部可以采用多台服务器来组成服务器集群,外部还是可以采用一个地址访问;
  • 解决Ajax跨域问题;
  • 作为真实服务器的缓冲,解决瞬间负载量大的问题;

16.理解restful(Representational State Transfer)

(1)每一个URI代表一种资源;
(2)客户端和服务器之间,传递这种资源的某种表现层;
(3)客户端通过四个HTTP动词,对服务器端资源进行操作,实现"表现层状态转化"。

RESTful 架构的核心规范与约束:统一接口

分为四个子约束:
1.每个资源都拥有一个资源标识,每个资源的资源标识可以用来唯一地标明该资源
2.消息的自描述性
3.资源的自描述性。
4.HATEOAS Hypermedia As The Engine Of Application State(超媒体作为应用状态引擎)
即客户只可以通过服务端所返回各结果中所包含的信息来得到下一步操作所需要的信息,如到底是向哪个URL发送请求等。也就是说,一个典型的REST服务不需要额外的文档标示通过哪些URL访问特定类型的资源,而是通过服务端返回的响应来标示到底能在该资源上执行什么样的操作

17.为啥redis zset使用跳跃链表而不用红黑树实现

18.线程状态

  1. 新建(NEW):新创建了一个线程对象。
  2. 就绪(RUNNABLE):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权 。
  3. 运行(RUNNING):可运行状态(runnable)的线程获得了cpu 时间片(timeslice) ,执行程序代码。
  4. 阻塞(BLOCKED):阻塞状态是指线程因为某种原因放弃了cpu 使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得cpu timeslice 转到运行(running)状态。阻塞的情况分三种:

(一). 等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放入等待队列(waitting queue)中。
(二). 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。
(三). 其他阻塞:运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态

  1. 死亡(DEAD):线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。

19.jvm内存模型

链接: JVM内存结构和Java内存模型别再傻傻分不清了.

20.Spring的IOC和AOP

1.IOC本质

控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法 也有人认为DI只是IoC的另一种说法。没有IoC的程序中,我们使用面向对象编程,对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,个人认为所谓控制反转就是:获得依赖对象的方式反转了。

采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。
控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection,DI)。

2.AOP

  • 什么是AOP

AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

  • AOP在Spring中的作用
    提供声明式事务;允许用户自定义切面
  • 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志,安全,缓存,事务等等…
  • 切面(ASPECT):横切关注点被模块化的特殊对象。即,它是一个类。
  • 通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。
  • 目标(Target):被通知对象。
  • 代理(Proxy):向目标对象应用通知之后创建的对象。
  • 切入点(PointCut):切面通知执行的“地点”的定义。
  • 连接点(JointPoint):与切入点匹配的执行点。

21.synchronized和lock区别以及底层原理

区别:
1.首先synchronized是java内置关键字,在jvm层面,Lock是个java类;
2.synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
3.synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
4.用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
5.synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可中断、可公平(两者皆可)
6.Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。

底层原理

synchronized:
原子性:保证语句块内是原子的
可见性:通过在unlock前需要将变量同步回主存,其他线程需要重新获取
有序性:一个变量在同一时刻只允许一条线程对其操作
方法级的同步是通过方法调用和返回中实现的,方法常量池的ACC_SYNCHRONIZED标志是否为同步方法,如果方法是同步方法,则执行线程需要先持有monitor然后执行方法,返回后释放。
代码块的同步是通过monitorenter和monitorexit实现的。遇到monitorenter试图获取monitor对象,如果未加锁或者已经被自己持有,则锁计数器+1,执行,遇到monitorexit则锁计数-1.计数器为0代表锁释放。如果获取monitor对象失败会进入阻塞。且是可重入的。
1.6前慢的原因:对象内部的监视器锁是通过底层OS的Mutex实现的,存在用户态到内核态的切换,成本极高
1.6后的优化:四种锁状态 无锁——偏向锁——轻量级锁——重量级锁。(不可降级)

偏向锁:无实际竞争,且将来只有第一个申请锁的线程会使用锁。只有一次CAS
锁对象第一次被获取时,jvm将对象头锁标志位设为01偏向模式,然后通过CAS将线程id记录到对象的markword中。如果成功,该线程在以后每次进入该同步块时,jvm不进行任何操作。如果不是第一次获取锁,则判断偏向线程id是否为当前线程,是的话就进入同步块。否则则根据当前偏向的线程是否存活,未存活则取消锁到无锁状态,存活则升级为轻量级锁。

轻量级锁:无实际竞争,多个线程交替使用锁;允许短时间的锁竞争。申请和释放需要CAS
轻量级锁是相对于重量级锁而言的。使用轻量级锁时,不需要申请互斥量,而是在当前线程栈帧中开辟空间Lock Record用来记录当前对象markword的拷贝。然后将Mark Word中的部分字节CAS更新指向线程栈中的Lock Record,如果更新成功,则轻量级锁获取成功,记录锁状态为00轻量级锁;否则,说明已经有线程获得了轻量级锁,如果指向的是当前线程的栈帧,则重入代码块,否则出现了竞争,会尝试几次CAS,如果不行,升级为重量级锁,标志位11,markword中指针指向重量级锁。

重量级锁:有实际竞争,且锁竞争时间长。monitor实现。

Lock: 有三个实现类,ReentrantLock, ReentrantReadWriteLock类中的两个静态内部类ReadLock和WriteLock。
底层实现为AQS。
AQS:CLH锁队列(双向链表)+state状态变量,线程通过CAS去改变状态,成功则获取锁成功,失败则进入等待队列,等待被唤醒。
lock的存储结构:一个int类型状态值(用于锁的状态变更),一个双向链表(用于存储等待中的线程)
lock获取锁的过程:本质上是通过 CAS 来获取状态值修改,如果当场没获取到,会将该线程放在线程等待链表中。
lock释放锁的过程:修改状态值,调整等待链表。
lock()-acquire()-tryAcquire()-未成功获取锁-addwaiter()-acquireQueued()
acquireQueued的主要作用是把已经追加到队列的线程节点进行阻塞,但阻塞前又通过tryAccquire重试是否能获得锁,如果重试成功能则无需阻塞,直接返回。

22.Redis持久化

  • RDB:RDB 持久化机制,是对 Redis 中的数据执行周期性的持久化。
  • AOF:AOF 机制对每条写入命令作为日志,以 append-only 的模式写入一个日志文件中,因为这个模式是只追加的方式,所以没有任何磁盘寻址的开销,所以很快,有点像Mysql中的binlog。

两种方式都可以把Redis内存中的数据持久化到磁盘上,然后再将这些数据备份到别的地方去,RDB更适合做冷备,AOF更适合做热备,比如我杭州的某电商公司有这两个数据,我备份一份到我杭州的节点,再备份一个到上海的,就算发生无法避免的自然灾害,也不会两个地方都一起挂吧,这灾备也就是异地容灾,地球毁灭他没办法。

RDB优缺点

优点:

1、适合大规模的数据行恢复,比aof的效率要高
2、对于RDB方式,redis会单独创建(fork)一个子进程来进行持久化,而主进程是不会进行任何IO操作的,这样就确保了redis极高的性能。
3、如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。

缺点:

1.RDB都是快照文件,都是默认五分钟甚至更久的时间才会生成一次,这意味着你这次同步到下次同步这中间五分钟的数据都很可能全部丢失掉。AOF则最多丢一秒的数据,数据完整性上高下立判。
还有就是RDB在生成数据快照的时候,如果文件很大,客户端可能会暂停几毫秒甚至几秒,
2.对数据的完整行不高

AOF优缺点

优点:

上面提到了,RDB五分钟一次生成快照,但是AOF是一秒一次去通过一个后台的线程fsync操作,那最多丢这一秒的数据。
AOF在对日志文件进行操作的时候是以append-only的方式去写的,他只是追加的方式写数据,自然就少了很多磁盘寻址的开销了,写入性能惊人,文件也不容易破损。

AOF的日志是通过一个叫非常可读的方式记录的,这样的特性就适合做灾难性数据误删除的紧急恢复了,比如公司的实习生通过flushall清空了所有的数据,只要这个时候后台重写还没发生,你马上拷贝一份AOF日志文件,把最后一条flushall命令删了就完事了。

缺点:

一样的数据,AOF文件比RDB还要大,修复的速度也比 rdb慢
AOF开启后,Redis支持写的QPS会比RDB支持写的要低,他不是每秒都要去异步刷新一次日志嘛fsync,当然即使这样性能还是很高,我记得ElasticSearch也是这样的,异步刷新缓存区的数据去持久化,为啥这么做呢,不直接来一条怼一条呢,那我会告诉你这样性能可能低到没办法用的,大家可以思考下为啥哟。

23.Redis和Memcache区别,优缺点对比

redis和memecache的不同在于:

  • 1、存储方式:
    memecache 把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小
    redis有部份存在硬盘上,这样能保证数据的持久性,支持数据的持久化(笔者注:有快照和AOF日志两种持久化方式,在实际应用的时候,要特别注意配置文件快照参数,要不就很有可能服务器频繁满载做dump)。
  • 2、数据支持类型:
    redis在数据支持上要比memecache多的多。 例如 list、set、sorted set、hash 等。
  • 3、使用底层模型不同:
    新版本的redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。
  • 4、Memcache .处理请求时使用多线程异步 IO 的方式,可以合理利用 CPU 多核的优势,提高性能,与 Memcache不同的是,Redis 采用单线程模式处理请求。这样做的原因有 2 个:一个是因为采用了非阻塞的异步事件处理机制;另一个是缓存数据都是内存操作 IO 时间不会太长,单线程可以避免线程上下文切换产生的代价。
  • 5、Redis 提供持久化,主从同步机制,以及 Cluster 集群部署能力,能够提供高可用服务。

个人总结一下,有持久化需求或者对数据结构和处理有高级要求的应用,选择redis,其他简单的key/value存储,选择memcache。

24.单线程的redis为什么这么快

(一)纯内存操作
(二)单线程操作,避免了频繁的上下文切换
(三)采用了非阻塞I/O多路复用机制

25.Redis 为什么是单线程的

官方FAQ表示,因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了(毕竟采用多线程会有很多麻烦!)Redis利用队列技术将并发访问变为串行访问
1)绝大部分请求是纯粹的内存操作(非常快速)2)采用单线程,避免了不必要的上下文切换和竞争条件
3)非阻塞IO优点:
1.速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)
2. 支持丰富数据类型,支持string,list,set,sorted set,hash
3.支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行
4. 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除如何解决redis的并发竞争key问题

同时有多个子系统去set一个key。这个时候要注意什么呢? 不推荐使用redis的事务机制。因为我们的生产环境,基本都是redis集群环境,做了数据分片操作。你一个事务中有涉及到多个key操作的时候,这多个key不一定都存储在同一个redis-server上。因此,redis的事务机制,十分鸡肋。
(1)如果对这个key操作,不要求顺序: 准备一个分布式锁,大家去抢锁,抢到锁就做set操作即可
(2)如果对这个key操作,要求顺序: 分布式锁+时间戳。 假设这会系统B先抢到锁,将key1设置为{valueB 3:05}。接下来系统A抢到锁,发现自己的valueA的时间戳早于缓存中的时间戳,那就不做set操作了。以此类推。
(3) 利用队列,将set方法变成串行访问也可以redis遇到高并发,如果保证读写key的一致性
对redis的操作都是具有原子性的,是线程安全的操作,你不用考虑并发问题,redis内部已经帮你处理好并发的问题了。

26.MySQL中索引失效的常见场景与规避方法以及MVCC详解

  1. where语句中包含or时,可能会导致索引失效
  2. where语句中索引列使用了负向查询,可能会导致索引失效
  3. 索引字段可以为null,使用is null或is not null时,可能会导致索引失效
  4. 在索引列上使用内置函数,一定会导致索引失效
  5. like查询以%开头时,会导致索引失效
  6. 对索引列进行运算,一定会导致索引失效
    链接: MySQL中索引失效的常见场景与规避方法.

27.InnoDB事务日志(redo log 和 undo log)详解

1.redo log 和undo log

为了满足事务的持久性,防止buffer pool数据丢失,innodb引入了redo log。为了满足事务的原子性,innodb引入了undo log。

2.undo log

Undo log 是为了实现事务的原子性。还用Undo Log来实现多版本并发控制(简称:MVCC)。

delete/update操作的内部机制
当事务提交的时候,innodb不会立即删除undo log,因为后续还可能会用到undo log,如隔离级别为repeatable read时,事务读取的都是开启事务时的最新提交行版本,只要该事务不结束,该行版本就不能删除,即undo log不能删除。
但是在事务提交的时候,会将该事务对应的undo log放入到删除列表中,未来通过purge来删除。并且提交事务时,还会判断undo log分配的页是否可以重用,如果可以重用,则会分配给后面来的事务,避免为每个独立的事务分配独立的undo log页而浪费存储空间和性能。

通过undo log记录delete和update操作的结果发现:(insert操作无需分析,就是插入行而已)
delete操作实际上不会直接删除,而是将delete对象打上delete flag,标记为删除,最终的删除操作是purge线程完成的。
update分为两种情况:update的列是否是主键列。
如果不是主键列,在undo log中直接反向记录是如何update的。即update是直接进行的。
如果是主键列,update分两部执行:先删除该行,再插入一行目标行。

  • ①事务的原子性
  • 事务的所有操作,要么全部完成,要不都不做,不能只做一半。如果在执行的过程中发生了错误,要回到事务开始时的状态,所有的操作都要回滚。
  • 原理
      Undo Log的原理很简单,为了满足事务的原子性,在操作任何数据之前,首先将数据备份到一个地方(这个存储数据备份的地方称为Undo Log)。然后进行数据的修改。如果出现了错误或者用户执行了ROLLBACK语句,系统可以利用Undo Log中的备份将数据恢复到事务开始之前的状态。

3.Redo Log

redo log就是保存执行的SQL语句到一个指定的Log文件,当mysql执行数据恢复时,重新执行redo log记录的SQL操作即可。引入buffer pool会导致更新的数据不会实时持久化到磁盘,当系统崩溃时,虽然buffer pool中的数据丢失,数据没有持久化,但是系统可以根据Redo Log的内容,将所有数据恢复到最新的状态。redo log在磁盘上作为一个独立的文件存在。默认情况下会有两个文件,名称分别为 ib_logfile0和ib_logfile1。

①原理

和Undo Log相反,Redo Log记录的是新数据的备份。在事务提交前,只要将Redo Log持久化即可,不需要将数据持久化。当系统崩溃时,虽然数据没有持久化,但是Redo Log已经持久化。系统可以根据Redo Log的内容,将所有数据恢复到最新的状态。

②Undo + Redo事务的简化过程

redo & undo log的作用

  • 数据持久化
  • buffer pool中维护一个按脏页修改先后顺序排列的链表,叫flush_list。根据flush_list中页的顺序刷数据到持久存储。按页面最早一次被修改的顺序排列。正常情况下,dirty page什么时候flush到磁盘上呢?

1.当redo空间占满时,将会将部分dirty page flush到disk上,然后释放部分redo log。
2.当需要在Buffer pool分配一个page,但是已经满了,这时候必须 flush dirty pages to disk。一般地,可以通过启动参数 innodb_max_dirty_pages_pct控制这种情况,当buffer pool中的dirty page到达这个比例的时候,把dirty page flush到disk中。
3.检测到系统空闲的时候,会flush。

  • 数据恢复

随着时间的积累,Redo Log会变的很大。如果每次都从第一条记录开始恢复,恢复的过程就会很慢,从而无法被容忍。为了减少恢复的时间,就引入了Checkpoint机制。假设在某个时间点,所有的脏页都被刷新到了磁盘上。这个时间点之前的所有Redo Log就不需要重做了。系统记录下这个时间点时redo log的结尾位置作为checkpoint。在进行恢复时,从这个checkpoint的位置开始即可。Checkpoint点之前的日志也就不再需要了,可以被删除掉。

链接: redo log 和 undo log的详解.

redis的过期策略以及内存淘汰机制

redis采用的是定期删除+惰性删除策略。

为什么不用定时删除策略?

  • 1.定时删除,用一个定时器来负责监视key,过期则自动删除。虽然内存及时释放,但是十分消耗CPU资源。在大并发请求下,CPU要将时间应用在处理请求,而不是删除key,因此没有采用这一策略.
  • 定期删除+惰性删除是如何工作的呢?
    定期删除,redis默认每个100ms检查,是否有过期的key,有过期key则删除。需要说明的是,redis不是每个100ms将所有的key检查一次,而是随机抽取进行检查(如果每隔100ms,全部key进行检查,redis岂不是卡死)。因此,如果只采用定期删除策略,会导致很多key到时间没有删除。
    于是,惰性删除派上用场。也就是说在你获取某个key的时候,redis会检查一下,这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除。
    采用定期删除+惰性删除就没其他问题了么?
    不是的,如果定期删除没删除key。然后你也没即时去请求key,也就是说惰性删除也没生效。这样,redis的内存会越来越高。那么就应该采用内存淘汰机制。
    在redis.conf中有一行配置
maxmemory-policy volatile-lru

该配置就是配内存淘汰策略的
volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
no-enviction(驱逐):禁止驱逐数据,新写入操作会报错
ps:如果没有设置 expire 的key, 不满足先决条件(prerequisites); 那么 volatile-lru, volatile-random 和 volatile-ttl 策略的行为, 和 noeviction(不删除) 基本上一致。

28.数据库中索引的介绍

索引是什么?

  • 官方介绍索引是帮助MySQL高效获取数据的数据结构。更通俗的说,数据库索引好比是一本书前面的目录,能加快数据库的查询速度。

  • 一般来说索引本身也很大,不可能全部存储在内存中,因此索引往往是存储在磁盘上的文件中的(可能存储在单独的索引文件中,也可能和数据一起存储在数据文件中)。

  • 我们通常所说的索引,包括聚集索引、覆盖索引、组合索引、前缀索引、唯一索引等,没有特别说明,默认都是使用B+树结构组织(多路搜索树,并不一定是二叉的)的索引。

索引的优势和劣势

优势:

  • 可以提高数据检索的效率,降低数据库的IO成本,类似于书的目录。
  • 通过索引列对数据进行排序,降低数据排序的成本,降低了CPU的消耗。
    • 被索引的列会自动进行排序,包括【单列索引】和【组合索引】,只是组合索引的排序要复杂一些。
    • 如果按照索引列的顺序进行排序,对应order by语句来说,效率就会提高很多。

劣势:

  • 索引会占据磁盘空间
  • 索引虽然会提高查询效率,但是会降低更新表的效率。比如每次对表进行增删改操作,MySQL不仅要保存数据,还有保存或者更新对应的索引文件。

29.动态代理的几种实现方式及优缺点

链接: 动态代理.

30.MySQL的乐观锁和悲观锁

链接: Mysql的乐观锁和悲观锁.

31 Https和Http

1. Http是什么?

  • HTTP 是一种 超文本传输协议(Hypertext Transfer Protocol) 协议,它是一个在计算机世界里专门在两点之间传输文字、图片、音频、视频等超文本数据的约定和规范。

2.HTTP的原理:

  1. 客户端浏览器通过网络与服务器建立连接(通过TCP实现,一般端口号为80),建立连接后客户端可发送请求给服务器(请求的格式为:统一资源标识符(URL)、协议版本号,后边是 MIME 信息包括请求修饰符、客户机信息和许可内容)
  2. 服务器接收到请求之后会返回一定的相应(其格式为一个状态行,包括信息的协议版本号、一个成功或错误的代码,后边是 MIME 信息包括服务器信息、实体信息和可能的内容)

3.Http报文段

报文段详解.
浅谈http协议(三):HTTP 报文及其结构.

详解HTTP和HTTPS.

4.HTTPS如何解决HTTP的问题:

  • HTTPS 只是在 HTTP 的基础之上增加了加密处理、认证机制和完整性保护,即 HTTPS = HTTP + 加密 + 认证 + 完整性保护
    • 加密,HTTPS 通过对数据加密来使其免受窃听者对数据的监听,这就意味着当用户在浏览网站时,没有人能够监听他和网站之间的信息交换,或者跟踪用户的活动,访问记录等,从而窃取用户信息。

    • 数据一致性,数据在传输的过程中不会被窃听者所修改,用户发送的数据会完整的传输到服务端,保证用户发的是什么,服务器接收的就是什么。

    • 身份认证,是指确认对方的真实身份,也就是证明你是你(可以比作人脸识别),它可以防止中间人攻击并建立用户信任。

  • HTTPS 不是应用层的一种新协议,只是 HTTP 协议的改进(HTTP协议中的部分通信接口被ssl/tls协议代替),通常 HTTP 直接与 TCP 进行通信,当使用 ssl 协议时则先与 ssl 通信,再由 ssl 和 tcp 通信,
  • 通过上面的分析可以看出来,HTTPS 只是在 HTTP 的基础上增加了 ssl 协议;

5.HTTPS如何解决HTTP的问题:

  1. 客户使用https的URL访问Web服务器,要求与Web服务器建立SSL连接。
  2. Web服务器收到客户端请求后,会将网站的证书信息(证书中包含公钥)传送一份给客户端。
  3. 客户端的浏览器与Web服务器开始协商SSL连接的安全等级,也就是信息加密的等级。
  4. 客户端的浏览器根据双方同意的安全等级,建立会话密钥,然后利用网站的公钥将会话密钥加密,并 传送给网站。
  5. Web服务器利用自己的私钥解密出会话密钥。
  6. Web服务器利用会话密钥加密与客户端之间的通信。

区别:

1.http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。
2.http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
3.http的连接很简单,是无状态的。Https协议是由SSL+Http协议构建的可进行加密传输、身份认证的网 络协议,比http协议安全。(无状态的意思是其数据包的发送、传输和接收都是相互独立的。无连接的意思是指通信双方都不长久的维持对方的任何信息。)

6.SSL和TSL

SSL是“Secure Sockets Layer”的缩写,中文叫做“安全套接层”,其出现就是为了解决HTTP传输不安全的问题;到了1999年,SSL被标准化,标准化之后的名称改为 TLS(是“Transport Layer Security”的缩写),中文叫做“传输层安全协议”,所以这两者其实就是同一种协议,只不过是在不同阶段的不同称呼。

SSL协议是位于TCP/IP协议与各种应用层协议之间,为数据通信提供安全支持,可分为两层:

  • SSL记录协议(SSL Record Protocol):它建立在可靠的传输协议(如TCP)之上,为高层协议提供数据封装、压缩、加密等基本功能的支持。
  • SSL握手协议(SSL Handshake Protocol):它建立在SSL记录协议之上,用于在实际的数据传输开始前,通讯双方进行身份认证、协商加密算法、交换加密密钥等。

所以,SSL协议提供的服务有:

  1. 认证用户和服务器,确保数据发送到正确的客户端和服务器;

  2. 加密数据以防止数据中途被窃取;

  3. 维护数据的完整性,确保数据在传输过程中不被改变。

细心的同学可能会发现,这不还是HTTPS解决HTTP的那几个问题吗?对,还是实现加密处理、认证机制和完整性保护这三点,由此也可以看出SSL协议的重要性,这也是HTTP和HTTPS之间的主要区别。

7.HTTP协议几个版本的理解

HTTP协议几个版本的理解.

8.长连接/短连接应用场景

1.http的长连接和短连接

  • http的长连接和短连接实际上是TCP的长连接和短连接

  • http是一个轻量级的超文本传输协议主要负责网络层(IP协议)和传输层(TCP协议)。

  • IP协议主要负责寻址和分段以及网络路由(网络连接 信息导通)。

  • 长连接可以理解为整个通讯的过程。例如:client和server只用一个Socket,从而保持长期的通讯连接;-
  • 短链接可以理解为每次client向Socket发送请求都会新建一个Socket,当处理完一个请求时就直接关闭掉Socket;
  • 区分长/短连接:整个客户端和服务端的通讯过程是利用一个Socket还是多个Socket进行的。

http的长/短连接是什么?

  • http 1.0默认使用的是短连接。当client请求Server会建立一次连接,用完中断连接。client访问的html中有js文件/图像文件/css等,那么client会建立一个http会话。
  • http 1.1以后默认使用的是长连接。主要解决保持连接的特性。如果使用长连接的http协议,则在请求响应头加入代码:
  • Connection:keep-alive,那么client与Server之间用于传输http数据的TCP连接不会关闭 以便于再次访问时会使用这一次建立的连接。但要记住Keep-Aive不是永久的保持连接,这个时间可以在服务器软件中进行设定(Apache)。

TCP协议(面向连接)主要负责在IP层之上传递可靠的数据包,从而保证在网络另一端能接受的发出端的所有数据包。
长连接/短连接应用场景.

32.java中ThreadLocal详解

1.ThreadLocal简介

  • 多线程访问同一个共享变量的时候容易出现并发问题,特别是多个线程对一个变量进行写入的时候,为了保证线程安全,一般使用者在访问共享变量的时候需要进行额外的同步措施才能保证线程安全性。ThreadLocal是除了加锁这种同步方式之外的一种保证一种规避多线程访问出现线程不安全的方法,当我们在创建一个变量后,如果每个线程对其进行访问的时候访问的都是线程自己的变量这样就不会存在线程不安全问题。

  • ThreadLocal是JDK包提供的,它提供线程本地变量,如果创建一乐ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,操作的是自己本地内存中的变量,从而规避了线程安全问题,如下图所示
    java中ThreadLocal详解.

33. MySQL如何处理死锁

MySQL有两种死锁处理方式:
  1. 等待,直到超时(innodb_lock_wait_timeout=50s)。
  2. 发起死锁检测,主动回滚一条事务,让其他事务继续执行(innodb_deadlock_detect=on)。
    由于性能原因,一般都是使用死锁检测来进行处理死锁。
死锁检测

死锁检测的原理是构建一个以事务为顶点、锁为边的有向图,判断有向图是否存在环,存在即有死锁。

回滚

检测到死锁之后,选择插入更新或者删除的行数最少的事务回滚,基于 INFORMATION_SCHEMA.INNODB_TRX 表中的 trx_weight 字段来判断。

如何避免发生死锁

收集死锁信息:

  1. 利用命令 SHOW ENGINE INNODB STATUS查看死锁原因。
  2. 调试阶段开启 innodb_print_all_deadlocks,收集所有死锁日志。

减少死锁

  1. 使用事务,不使用 lock tables 。
  2. 保证没有长事务。
  3. 操作完之后立即提交事务,特别是在交互式命令行中。
  4. 如果在用 (SELECT … FOR UPDATE or SELECT … LOCK IN SHARE MODE),尝试降低隔离级别。
  5. 修改多个表或者多个行的时候,将修改的顺序保持一致。
  6. 创建索引,可以使创建的锁更少。
  7. 最好不要用 (SELECT … FOR UPDATE or SELECT … LOCK IN SHARE MODE)。
  8. 如果上述都无法解决问题,那么尝试使用 lock tables t1, t2, t3 锁多张表

34.Java并发之AQS详解

Java并发之AQS详解.

35.线程池的工作原理

在这里插入图片描述
任务被提交到线程池,会先判断当前线程数量是否小于corePoolSize,如果小于则创建线程来执行提交的任务,否则将任务放入workQueue队列,如果workQueue满了,则判断当前线程数量是否小于maximumPoolSize,如果小于则创建线程执行任务,否则就会调用handler,以表示线程池拒绝接收任务。

线程池的7大参数

1.corePoolSize:线程池的核心线程数,说白了就是,即便是线程池里没有任何任务,也会有corePoolSize个线程在候着等任务。
2.maximumPoolSize:最大线程数,不管你提交多少任务,线程池里最多工作线程数就是maximumPoolSize。
3.keepAliveTime:线程的存活时间。当线程池里的线程数大于corePoolSize时,如果等了keepAliveTime时长还没有任务可执行,则线程退出。
4.unit:这个用来指定keepAliveTime的单位,比如秒:TimeUnit.SECONDS。
5.workQueue:一个阻塞队列,提交的任务将会被放到这个队列里。
6.threadFactory:线程工厂,用来创建线程,主要是为了给线程起名字,默认工厂的线程名字:pool-1-thread-3。
7.handler:拒绝策略,当线程池里线程被耗尽,且队列也满了的时候会调用。

36.spring事物传播机制

spring事物传播机制.

37.spring注解详解

它基于java的接口进行实现,是一种特殊的接口类型,通常对于注解来说,三种情况,一个是在编译前就会被丢弃的,一个是编译后留在class中的,另一种是会一直存在,运行的时候注解也会被保留,而框架的注解一般都是第三种。Class对象,Method对象,Parameter对象,Constructor对象等java反射对象通常都具有getAnnotation方法可以直接获取保留到运行时的注解实例。

37.操作系统内存管理

1. 操作系统的内存管理主要是做什么?

操作系统的内存管理主要负责内存的分配与回收(malloc 函数:申请内存, free 函数:释放内存),另外地址转换也就是将逻辑地址转换成相应的物理地址等功能也是操作系统内存管理做的事情。

2. 操作系统的内存管理机制了解吗?内存管理有哪几种方式?

简单分为连续分配管理⽅式和⾮连续分配管理⽅式这两种。

连续分配管理⽅式是指为⼀个⽤户程序分配⼀个连续的内存空间,常⻅的如块式管理 。同样地,⾮连续分配管理⽅式允许⼀个程序使⽤的内存分布在离散或者说不相邻的内存中,常⻅的如⻚式管理 和 段式管理:

1.块式管理 : 远古时代的计算机操系统的内存管理⽅式。将内存分为⼏个固定⼤⼩的块,每个块中只包含⼀个进程。如果程序运⾏需要内存的话,操作系统就分配给它⼀块,如果程序运⾏只需要很⼩的空间话,分配的这块内存很⼤⼀部分⼏乎被浪费了。这些在每个块中未被利⽤的空间,我们称之为碎⽚。

2.页式管理 :把主存分为⼤⼩相等且固定的⼀⻚⼀⻚的形式,⻚较⼩,相对相⽐于块式管理的划分⼒度更⼤,提⾼了内存利⽤率,减少了碎⽚。⻚式管理通过⻚表对应逻辑地址和物理地址。

3.段式管理 : ⻚式管理虽然提⾼了内存利⽤率,但是⻚式管理其中的⻚实际并⽆任何实际意义。段式管理把主存分为⼀段段的,每⼀段的空间⼜要⽐⼀⻚的空间⼩很多 。但是,最重要的是段是有实际意义的,每个段定义了⼀组逻辑信息,例如,有主程序段 MAIN、⼦程序段 X、数据段 D及栈段 S 等。 段式管理通过段表对应逻辑地址和物理地址。
最后还有一个很重要的段页式管理:段⻚式管理机制结合了段式管理和⻚式管理的优点。简单来说段⻚式管理机制就是把主存先分成若⼲段,每个段⼜分成若⼲⻚,也就是说 段⻚式管理机制 中段与段之间以及段的内部的都是离散的。

3. 快表和多级页表

快表

  • 为了解决虚拟地址到物理地址的转换速度,操作系统在页表方案基础之上引入了快表来加速虚拟地址到物理地址的转换。我们可以把快表理解为一种特殊的高速缓冲存储器(Cache) ,其中的内容是页表的一部分或者全部内容。作为页表的Cache, 它的作用与页表相似,但是提高了访问速率。由于采用页表做地址转换,读写内存数据时CPU要访问两次主存。有了快表,有时只要访问一次高速缓冲存储器,一次主存,这样可加速查找并提高指令执行速度。

使用快表之后的地址转换流程是这样的:

  1. 根据虚拟地址中的页号查快表;
  2. 如果该页在快表中,直接从快表中读取相应的物理地址;
  3. 如果该页不在快表中,就访问内存中的页表,再从页表中得到物理地址,同时将页表中的该映射表项添加到快表中;
  4. 当快表填满后, 又要登记新页时,就按照一-定的淘汰策略淘汰掉快表中的一个页。
    看完了之后你会发现快表和我们平时经常在我们开发的系统使用的缓存(比如Redis) 很像,的确是这样的,操作系统中的很多思想、很多经典的算法,你都可以在我们日常开发使用的各种工具或者框架中找到它们的影子。

多级页表

  • 引入多级页表的主要目的是为了避免把全部页表一直放在内存中占用过多空间,特别是那些根本就不需
    要的页表就不需要保留在内存中,多级页表属于时间换空间的典型场景。

总结

  • 为了提高内存的空间性能,提出了多级页表的概念;但是提到空间性能是以浪费时间性能为基础的,因
    此为了补充损失的时间性能,提出了快表(即TLB) 的概念。不论是快表还是多级页表实际上都利用到了程序的局部性原理。

4. 分页机制和分段机制有哪些共同点和区别呢?

共同点:
分页机制和分段机制都是为了提高内存利用率,较少内存碎片。
页和段都是离散存储的,所以两者都是离散分配内存的方式。但是,每个页和段中的内存是连续的。
区别:
页的大小是固定的,由操作系统决定;而段的大小不固定,取决于我们当前运行的程序。
分页仅仅是为了满足操作系统内存管理的需求,而段是逻辑信息的单位,在程序中可以体现为代码段,数据段,能够更好满足用户的需要。
分页是一维地址空间,分段是二维的。

5. 解释一下逻辑(虚拟)地址和物理地址

我们编程一般只有 可能和逻辑地址打交道, 比如在C语言中, 指针里面存储的数值就可以理解成为内存里的一个地址,这个地址也就是我们说的逻辑地址,逻辑地址由操作系统决定。物理地址指的是真实物理内存中地址,更具体一点来说就是内存地址寄存器中的地址。物理地址是内存单元真正的地址。

6. CPU寻址了解吗?为什么需要虚拟地址空间?

现代处理器使用的是- -种称为虚拟寻址(Virtual Addressing) 的寻址方式。使用虚拟寻址,CPU 需要
将虚拟地址翻译成物理地址,这样才能访问到真实的物理内存。实际 上完成虚拟地址转换为物理地址
转换的硬件是CPU中含有一个被称为内存管理单元(Memory Management Unit, MMU) 的硬件。

为什么要有虚拟地址空间呢?

  • 没有虚拟地址空间的时候, 程序都是直接访问和操作的都是物理内存 。
    如果直接把物理地址暴露出来的话会带来严重问题,⽐如可能对操作系统造成伤害以及给同时运⾏多个程序造成困难。
    通过虚拟地址访问内存有以下优势:

  • 程序可以使用一系列相邻的虚拟地址来访问物理内存中不相邻的大内存缓冲区。

  • 程序可以使用一系列虚拟地址来访问大于可用物理内存的内存缓冲区。当物理内存的供应量变小时,内存管理器会将物理内存页(通常大小为4 KB) 保存到磁盘文件。数据或代码页会根据需要在物理内存与磁盘之间移动。

  • 不同进程使用的虚拟地址彼此隔离。一个进程中的代码无法更改正在由另一进程或操作系统使用的物理内存。

什么是虚拟内存?解决了什么问题

虚拟内存是操作系统内存管理的一种技术,每个进程启动时,操作系统会提供一个独立的虚拟地址空间,这个地址空间是连续的,进程可以很方便的访问内存,这里的内存指的是访问虚拟内存。虚拟内存的目的,一是方便进程进行内存的访问,二是可以使有限的物理内存运行一个比它大很多的程序。

虚拟内存的基本思想:每个程序拥有自己的地址空间,这个空间被分割成很多块,每块称为一页,每一页地址都是连续的地址范围。这些页被映射到物理内存,但不要求是连续的物理内存,也不需要所有的页都映射到物理内存,而是按需分配,在程序片段需要分配内存时由硬件执行映射(通常是 MMU),调入内存中执行。

链接: 内存管理基础.

38.分布式锁机制

链接: 分布式锁.

39.浅析DNS域名解析过程

链接: 浅析DNS域名解析过程.

40 设计模式六大原则详解

链接: 设计模式六大原则详解.

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值