面试题总结

2022/3/26 23:25:17

本文主要是介绍面试题总结,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

JVM

作用

通过编译器把java代码转换成字节码,类加载器再把字节码加载到内存中,将其放在运行时数据区的方法区内,而字节码文件只是jvm的一套指令集规范,并不能直接交给底层操作系统去执行,因此需要特定的命令解析器执行引擎将字节码翻译成底层系统指令,再交由cpu去执行,这个过程需要调用其他语言的本地库接口来实现整个程序的功能。

1.主要组成

a.方法区

存储已经被jvm加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。

b.堆

存储类对象实例,数组,jdk8以后将字符串常量池和静态变量移入堆中,被所有线程共享的一块内存区域,在虚拟机启动时创建。

c.虚拟机栈

指的是Java方法执行的内存模型:每个方法在执行时都会创建一个栈帧(stack frame)用于存储局部变量表,操作数栈,动态链接,方法出口等信息。每一个方法从调用到执行结束,就对应一个栈帧从虚拟机栈中入栈到出栈的过程。

d.本地方法栈

功能与虚拟机栈类似,只不过本地方法栈是为虚拟机使用的native方法服务。

e.程序计数器

主要作用是记住下一条jvm指令的执行地址,当前指令执行同时会将下条jvm命令的执行内置放入程序计数器中。当前指令执行完毕后,解释器会通过程序计数器的地址继续执行指令。

jvm中唯一没有内存泄漏风险的区域。
通过改变这个计数器的值来选取下一条需要执行指令的字节码指令。
分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖程序计数器完成。

f.执行引擎

g.本地库接口

h.直接内存

非虚拟机运行时数据区的部分,是java虚拟机向操作系统申请的一片内存空间。

常量池

1.字符串常量池

1.7之前是在方法区中,之后移到了堆里。只存放字符串常量和堆内字符串对象的引用。

2.class常量池

我们写的每一个Java类被编译后,就会形成一份class文件;class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池(constant pool table),用于存放编译器生成的各种字面量(Literal)和符号引用(Symbolic References);一个class文件对应一个class常量池。

3.运行时常量池

属于方法区一部分,用于存放编译期生成的各种字面量和符号引用。编译期和运行期都可以将常量放入池中。

问题:

1.堆和栈的区别?

物理地址

堆的物理地址分配对对象是不连续的。因此性能慢些。在GC的时候也要考虑到不连续的分配,所以有各种算法。比如,标记-消除,复制,标记-压缩,分代(即新生代使用复制算法,老年代使用标记——压缩)

栈使用的是数据结构中的栈,先进后出(FIFO)的原则,物理地址分配是连续的。所以性能快。

内存分别

堆的大小可以人为控制,一般都会比栈的内存大。

栈是连续的,所以分配的内存大小要在编译期就确认,大小是固定的。

存放的内容

堆存放的是对象的实例和数组,因此该区更关注的是数据的存储

栈存放:局部变量,操作数栈,返回结果。该区更关注的是程序方法的执行。

静态变量放在方法区,静态的对象还是放在堆。

程序的可见度

堆对于整个应用程序都是共享、可见的。

栈只对于线程是可见的。所以也是线程私有。他的生命周期和线程相同。

2.方法内的局部变量是否线程安全?

  • 如果方法内局部变量没有逃离方法的作用范围,它是线程安全的。
  • 如果局部变量引用了变量,并逃离方法的作用范围,它是线程不安全的

3.栈内存溢出(java.lang.StackOverflowError

  • 栈帧过多导致栈内存溢出
  • 栈帧过大导致内存溢出

4.线程运行诊断

  • CPU占用过多
诊断步骤:
1.通过top命令查询进程占用
2.ps命令定位线程占用
3.jstack id(根据线程id找到有问题的线程,定位到源代码)
jstack 进程id(pid)  # 控制台输出指定进程的信息
jstack  进程id >文件     #打印堆栈信息到文件中 
jstack -l  进程id    #除堆栈外,会打印出额外的锁信息,在发生死锁时可以用jstack -l pid来观察锁持有情况  

注意:

  1. jstack只能分析JVM虚拟机开启的进程

  2. jstack生成快照中,tid 是java中为这个线程的id,nid 是这个线程对应的操作系统本地线程id,每一个java线程都有一个对应的操作系统线程,且它们都是通过16进制表示的

  • 程序运行长时间没有结果
诊断步骤同上

4.队列和栈的区别?

  • 队列是先进先出(FIFO),即队列的修改是依先进先出的原则进行的。新来的成员总是加入队尾(不能从中间插入),每次离开的成员总是队列头上(不允许中途离队)。
  • 而栈为后进先出(LIFO),即每次删除(出栈)的总是当前栈中最新的元素,即最后插入(进栈)的元素,而最先插入的被放在栈的底部,要到最后才能删除。

2.java垃圾回收机制(泛指堆中的垃圾回收)

在java中,程序员是不需要显示的去释放一个对象的内存的,而是由虚拟机自行执行。在JVM中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只有在虚拟机(CPU)空闲或者当前堆内存不足时,才会触发执行,扫面那些没有被任何引用的对象,并将它们添加到要回收的集合中,进行回收。

1.gc判断回收算法

引用计数器法
当这个类被加载到内存以后,就会产生方法区,堆栈,程序计数器等一系列信息,当创建对象的时候,为这个对象在堆栈空间中分配对象,同时会产生一个引用计数器,同时引用计数器+1,当有新的引用的时候,引用计数器继续+1,而当其中一个引用销毁的时候,引用计数器-1,当引用计数器被减为零的时候,标志着这个对象已经没有引用了,可以回收了!

缺点:增加了时间和空间消耗以及不能解决循环引用的问题;

可达性分析算法:从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是可以被回收的。

可以作为GC Root的对象:

1,虚拟机栈中引用的对象(本地变量表)

2,方法区中静态属性引用的对象

3,方法区中常量引用的对象

4,本地方法栈中引用的对象(Native Object)

方法区中的垃圾判断

主要回收废弃常量无用的类,需要满足下面所有条件:

  1. 该类的所有实例都已经被回收,即Java堆中不存在该类的任何实例。

  2. 加载该类的ClassLoader已经被回收。

  3. 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

2.gc垃圾回收算法

标记-清除算法:标记需要被清除对象,然后进行清除回收。

缺点:效率不高,无法清除垃圾碎片。
复制算法:按照容量划分二个大小相等的内存区域,当一块用完的时候将活着的对象复制到另一块上,然后再把已使用的内存空间一次清理掉。HotSpot虚拟机默认Eden区和Survivor区的比例为8:1

缺点:内存使用率不高,只有原来的一半,消耗内存。
标记-整理算法:标记无用对象,让所有存活的对象都向一端移动,然后直接清除掉端边界以外的内存。
分代算法:根据对象存活周期的不同将内存划分为几块,一般是新生代和老年代,新生代基本采用复制算法,老年代采用标记整理算法。

3.gc垃圾收集器

1.Serial/Serial Old

  Serial/Serial Old收集器是最基本最古老的收集器,它是一个单线程收集器,并且在它进行垃圾收集时,必须暂停所有用户线程。Serial收集器是针对新生代的收集器,采用的是Copying算法,Serial Old收集器是针对老年代的收集器,采用的是Mark-Compact算法。它的优点是实现简单高效,但是缺点是会给用户带来停顿。

2.ParNew

  ParNew收集器是Serial收集器的多线程版本,使用多个线程进行垃圾收集。

3.Parallel Scavenge

  Parallel Scavenge收集器是一个新生代的多线程收集器(并行收集器),它在回收期间不需要暂停其他用户线程,其采用的是Copying算法,该收集器与前两个收集器有所不同,它主要是为了达到一个可控的吞吐量。

4.Parallel Old

  Parallel Old是Parallel Scavenge收集器的老年代版本(并行收集器),使用多线程和标记整理算法。

5.CMS

  CMS(Current Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,它是一种并发收集器,采用的是标记清除算法。

6.G1

  G1收集器是当今收集器技术发展最前沿的成果,它是一款面向服务端应用的收集器,它能充分利用多CPU、多核环境。因此它是一款并行与并发收集器,并且它能建立可预测的停顿时间模型。

G1收集器有以下特点:

(1). 并行和并发。使用多个CPU来缩短Stop The World停顿时间,与用户线程并发执行。

(2). 分代收集。独立管理整个堆,但是能够采用不同的方式去处理新创建对象和已经存活了一段时间、熬过多次GC的旧对象,以获取更好的收集效果。

(3). 空间整合。基于标记 - 整理算法,无内存碎片产生。

(4). 可预测的停顿。指定在一个毫秒级的消耗在垃圾收集上的时间。

在G1之前的垃圾收集器,收集的范围都是整个新生代或者老年代,而G1不再是这样。使用G1收集器时,Java堆的内存布局与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分(可以不连续)Region的集合。

4.Minor GC ,Major GC,Full GC是什么?以及它的触发条件

Minor GC(复制)

当年轻代空间不足时,就会触发MinorGC,这里的年轻代满指的是Eden代满,Survivor满不会引发GC。(每次Minor GC会清理年轻代的内存。)

因为Java对象大多都具备 朝生夕灭 的特性,所以Minor GC非常频繁,一般回收速度也比较快。这一定义既清晰又易于理解。

Minor GC会引发STW,暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行

Major GC(标记整理)

指发生在老年代的GC,对象从老年代消失时,我们说 “Major Gc” 或 “Full GC” 发生了

出现了MajorGc,经常会伴随至少一次的Minor GC(但非绝对的,在Paralle1 Scavenge收集器的收集策略里就有直接进行MajorGC的策略选择过程)

也就是在老年代空间不足时,会先尝试触发MinorGc。
如果之后空间还不足,则触发Major GC

Major GC的速度一般会比MinorGc慢1e倍以上,STW的时间更长,如果Major GC后,内存还不足,就报OOM了

Full GC(标记清除)

对年轻代和老年代都进行垃圾回收,Full GC 是开发或调优中尽量要避免的。这样暂时时间会短一些

Minor GC触发条件: 当Eden区满时,触发Minor GC。

Full GC触发条件:
(1)调用System.gc时,系统建议执行Full GC,但是不必然执行
(2)老年代空间不足
(3)方法区空间不足
(4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存

5.jvm中对象的存储结构

在hotspot虚拟机中,对象的存储区域分为3块:对象头,实际数据,对其填充。

a.对象头(16字节)

1.MarkWord(8字节,32位4字节)

用于存储对象自身运行时数据,如hashcode,gc分代年龄,锁状态标志,偏向线程id,偏向时间戳。

2.klass指针(64位下8字节,32位4字节)

对象指向实例类型的指针,jvm会通过这个指针来确定这个对象是哪个类的实例。

3.数组长度

只有数组对象有

b.实例数据

存放类的属性数据信息,包括父类的属性信息,如果对象是数组的话会包括数组长度,这部分占4字节。

c.对其填充

当对象的大小不是8的整数倍时,会通过进行对齐填充的数据进行补全。

开启指针压缩的情况下对象头的大小为12字节;

数组对象长度(指针压缩):数组长度4字节+数组对象头8字节+数组markWord4字节+对齐4字节=16字节;

JavaSE

8大基本数据类型

整型:byte,short,int,long。
浮点型:float,double
字符型:char
布尔型:boolean

switch语句中可以跟byte,short,int,char,string

引用类型

强引用(Strong Reference):即使进行了多次的GC回收,即使JVM真的已经不够用了,即使JVM最终不得已抛出了OOM错误,那么该引用继续抢占;
软引用(Soft Reference):当内存空间不足时,可以回收此内存空间。如果充足则不回收,可以用其完成缓存的一些处理操作开发。
缓存:保证数据更快的操作(读取)。是不重要的数据。可以作为牺牲来释放空间。
弱引用(Weak Reference):不管内存是否紧张,只要一出现GC处理,则立即回收。
幽灵引用(Phantom Reference):和没有引用是一样的。

final关键字

修饰变量:一次赋值,不能在修改。
修饰方法:当前方法不能被重写。
修饰类:当前类不能被继承。

final、finally、finallize有何区别?

1、final修饰符(关键字)。被final修饰的类,就意味着不能再派生出新的子类,不能作为父类而被子类继承。因此一个类不能既被abstract声明,又被final声明。将变量或方法声明为final,可以保证他们在使用的过程中不被修改。被声明为final的变量必须在声明时给出变量的初始值,而在以后的引用中只能读取。被final声明的方法也同样只能使用,即不能方法重写。

2、finally是在异常处理时提供finally块来执行任何清除操作。不管有没有异常被抛出、捕获,finally块都会被执行。try块中的内容是在无异常时执行到结束。catch块中的内容,是在try块内容发生catch所声明的异常时,跳转到catch块中执行。finally块则是无论异常是否发生,都会执行finally块的内容,所以在代码逻辑中有需要无论发生什么都必须执行的代码,就可以放在finally块中。

3、finalize是方法名。java技术允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。它是在object类中定义的,因此所有的类都继承了它。子类覆盖finalize()方法以整理系统资源或者被执行其他清理工作。finalize()方法是在垃圾收集器删除对象之前对这个对象调用的。

static关键字

修饰方法,变量,代码块时,这些都可以通过,类名.调用,并且会优先初始化。
好处:节省内存,只用初始化一次即可。速度快,静态属性优先加载。
使用场景:如果一个类的某个属性被所有实例所共享那么这个属性适合用static修饰。

super和this关键字

this:指的是当前类对象
super:当前对象中父对象的引用
这两个关键字不能同时出现在一个构造函数中。

权限修饰符

private(私有的):本类调用,非本类需要通过get/set调用和修改。
public(公开的):当前项目都可以调用。
protected(受保护的):本包都可以调用,非本包的子类内部可以访问。
default:默认的,本包都可以访问。

抽象类和接口的区别

语法上的区别:
    1.抽象类可以有构造方法,接口中不能有构造方法。

    2.抽象类中可以有普通成员变量,接口中没有普通成员变量

    3.抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法。

    4. 抽象类中的抽象方法的访问类型可以是public,protected和(默认类型,虽然eclipse下不报错,但应该也不行),但接口中的抽象方法只能是public类型的,并且默认即为public abstract类型。

    5. 抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是public static final类型,并且默认即为public static final类型。

    6. 一个类可以实现多个接口,但只能继承一个抽象类。
应用上的区别:
	接口一般用于对模块系统架构设计,对模块之间方法的调用进行解耦。抽象类在代码实现方面发挥作用,可以实现代码的重写复用。

重载与重写的区别

方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。
重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载,重载对返回类型没有特殊的要求,不能根据返回类型进行区分。
重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的参数列表,有兼容的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。

代码块

1.静态代码块
带有static关键字,跟成员属性同一级,随着类的加载而执行,只执行一次。

2.局部代码块
在方法体里的代码块,方法被调用就会执行。

3.构造代码块
跟成员属性同一级,当前类对象每创建一次就会执行一次,优先于构造方法。

面向对象理解

1.封装
隐藏内部实现细节,对外暴露可访问的方法。get、set方法访问类中的成员属性。
2.继承
一个子类只能继承一个父类,子类继承父类的属性和方法,不能继承构造器和私有属性,子类的构造器会默认调用父类的构造器。提高了代码的复用性。
3.多态
向下转型:父类引用指向子类对象,此时父类引用不能调用子类独有的成员属性。
向上转型:子类引用指向父类对象。

数据结构

数据结构包含线性结构和非线性结构。

1.线性结构

数据元素之间存在一对一的线性关系。

数组(array)

长度固定,内存地址连续,查询快,修改慢。

栈(stack)

先进后出。

队列(queue)

先进先出

链表(linked)

底层结构有三个区域:左指针域(指向上个值的内存地址),值域,右指针域(指向下一个值的内存地址);内存地址随机,没有索引查询慢,增删快。

2.非线性结构

多维数组,广义表 ,树结构,图结构。

红黑树

一种自平衡的二叉查找树。
1.每个根节点必须是黑色
2.每个节点的颜色只能是红色或者黑色
3.每个叶子节点的颜色为黑色
4.如果一个节点的颜色为红色,它的子节点的颜色必须是黑色
5.每个节点的左右两侧黑色节点的数量必须相同

二叉树

只有一个根节点,每一个非根节点只有一个父节点。

左大右小,每个子节点最多有两个子树。

哈希表


日常工作中String判空的方法

if(s=null||s.length()<=0){}
if(s=null||s.equels("")){}
if(StringUtils.isBlank(s)){}
if(s == null || s.isEmpty()){}

==和equals()的区别?

1)对于==,比较的是值是否相等如果作用于基本数据类型的变量,则直接比较其存储的 值是否相等,如果作用于引用类型的变量,则比较的是所指向的对象的地址是否相等。
其实==比较的不管是基本数据类型,还是引用数据类型的变量,比较的都是值,只是引用类型变量存的值是对象的地址
2)equals()方法不能作用于基本数据类型的变量,equals()方法存在于Object类中,所以说所有类中的equals()方法都继承自Object类,在没有重写equals()方法的类中,调用equals()方法其实和使用==的效果一样,也是比较的是引用类型的变量所指向的对象的地址,不过,Java提供的类中,有些类都重写了equals()方法,重写后的equals()方法一般都是比较两个对象的值,比如String类。

String、Stringbuffer、StringBuilder的区别以及使用场景

String声明的是不可变的对象,每次操作都会生成新的string对象,然后将指针指向新的string对象,而StringBuffer,StringBuilder可以在原有对象的基础上进行操作,若是在经常改变字符串内容的情况下最好不要使用string

StringBuffer是线程安全的,StringBuilder是非线程安全的,但stringbuilder的性能却高于stringbuffer,单线程环境下推荐使用stringbuilder,多线程环境下推荐使用stringbuffer

hashCode()和equals()方法有何重要性?

Java中的HashMap使用hashCode()和equals()方法来确定键值对的索引,当根据键获取值的时候也会用到这两个方法。
如果没有正确的实现这两个方法,两个不同的键可能会有相同的hash值,因此可能会被集合认为是相等的。
而且,这两个方法也用来发现重复元素,所以这两个方法的实现对HashMap的精确性和正确性是至关重要的。

int和Integer有何区别?

1、Integer是int的包装类,int则是java的一种基本数据类型 
2、Integer变量必须实例化后才能使用,而int变量不需要 
3、Integer实际是对象的引用,当new一个Integer时,实际上是生成一个指针指向此对象;而int则是直接存储数据值 
4、Integer的默认值是null,int的默认值是0

throw和throws有何区别

抛出异常有三种形式,一是throw,一个throws,还有一种系统自动抛异常
1.系统自动抛异常
	当程序出现一些逻辑错误,主义错误或者类型转换错误时,系统会自动抛异常。
2.throw
	throw是有语句抛出一个异常,一般是在代码块的内部,当程序出现某张逻辑错误时由程序员主动抛出某种特定类型的异常。通过throw抛异常会创建一个异常对象。(ClassFormatException,ConcurrentModificationException,NullPointerException,DateTimeException)
3.throws
	在方法参数列表后,throws后可以跟着多个异常名,表示向调用该类的位置抛出异常,不在该类解决。

throws表示出现异常的一种可能性,并不一定会发生这些异常,throw则是抛出了异常,执行throw则一定抛出了某种异常。

1.集合

list、set、map的区别

list存储的元素是有序的,可以重复
set存储的数据是无序的,并且不允许重复,但元素在集合中的位置是由元素的hashcode决定,即位置是固定的(Set集合是根据hashcode来进行数据存储的,所以位置是固定的,但是这个位置不是用户可以控制的,所以对于用户来说set中的元素还是无序的)。
Map中存储的数据是无序的,它的键是不允许重复的,但是值是允许重复的;

list 的实现类

arraylist
底层是数组
线程不安全,效率高
linkedlist
底层数据结构是双向链表,占用内存
线程不安全,效率高
vector
底层数据结构是数组,查询快,增删慢。
线程安全,效率低, 已给舍弃了

set的实现类

hashset
底层数据结构是哈希表(无序,唯一),底层是一个hashmap,存的元素是的map的key,value是一个固定的object对象。
linkedHashSet(有序,唯一)
链表和哈希表
由链表保证元素有序,哈希表保证元素唯一
treeset(唯一,有序)
底层红黑树

map的实现类

hashMap
底层哈希表,支持null键null值。
hashtable
null会有空指针异常,线程安全
treemap
默认升序排序,线程不安全
linkedhashmap
是hashmap的子类,存取有序

arraylist的扩容规则

add()首次扩容为10,再次扩容为上次容量的1.5倍
addAll()没有元素时,扩容为 Math.max(10, 实际元素个数),有元素时为 Math.max(原容量 1.5 倍, 实际元素个数)

ConcurrentModificationException 并发修改异常

1. 导致原因
		并发修改,导致数据不一致异常。
2. 解决方案
		a.使用Vatcor
		b.使用Collections.synchronizedList
		c.CopyOnWriteArrayList<>(写时复制)		
3. 优化建议
		 

写时复制:往一个容器添加元素的时候,不直接往当前容器Object[]添加,而是先将当前容器Object[]进行Copy,复制一个新的容器Object[]数组 newElements,然后往新的容器里添加容器,添加完元素之后,再将原容器的引用指向新的容器setArray(newElements);这样做的好处是可以对容器进行并发读,而不需要加锁,因为当前容器不会添加任何元素,所以copyOnWrite容器也是一种读写分离的思想,读和写不同的容器

哪些集合类是线程安全的?

vector:多了个同步化机制(线程安全),因为效率较低,现在已经不太建议使用。
statck:堆栈类,先进后出
hashtable:其中的方法加入同步锁
enumeration:枚举,相当于迭代器

如何实现集合排序

实现comparable接口
通过匿名内部类Comoparator

ArrayList和LinkedList的区别

数据结构不同
ArrayList是数组结构,LinkedList是双向链表结构
数组:
	数据的内存的地址是连续的,数据查询快,增删慢
链表:
	数据的内存地址是随机的,数据查询慢,增删快

HashMap底层原理

  1. 数据存储机制
    jdk8之前是头插法
    头插法:新插入的节点是链表的头节点
    jdk8及之后是尾插法
    尾插法:新插入的节点是尾节点
  2. 数据结构
    JDK1.7=数组+链表
    JDK1.8=数组+链表+红黑树
    数组的长度大于64的时候,链表长度大于8才会从链表转换为红黑树
  3. 容量
    HashMap的最大容量值为2^30,负载因子为0.75,默认初始容量16,扩容因子2.
  4. hash算法
    hash算法就是通过hashcode与自己进行向右位移16的异或运算。这样做是为了计算出来的hash值足够随机,足够分散,还有产生的数组下标足够随机。
# 1. 数据存储机制
		jdk8之前是头插法
			头插法:新插入的节点是链表的头节点
		jdk8及之后是尾插法
			尾插法:新插入的节点是尾节点
# 2. 数据结构
		JDK1.7=数组+链表
		JDK1.8=数组+链表+红黑树
		当数组的长度大于64的时候,链表长度大于8才会从链表转换为红黑树
# 3. 容量
        HashMap的最大容量值为2^30,负载因子为0.75,默认初始容量16,扩容因子:2

4. hash算法
        hash算法就是通过hashcode与自己进行向右位移16的异或运算。这样做是为了计算出来的hash值足够随机,足够分散,还有产生的数组下标足够随机。
	扰动处理
		JDK1.7=4次位运算+5次异或运算
		JDK1.8=1次位运算+1次异或
		JDK1.7用了9次扰动处理=4次位运算+5次异或,而JDK1.8只用了2次扰动处理=1次位运算+1次异或。
	特点:
		键-值(key-value)都允许为空、线程不安全、不保证有序、存储位置随时间变化
		当key为空时,hash值默认设置为0,只能有一个key为null

5. new HashMap():
        底层没有创建数组,首次调用put()方法示时,底层创建长度为16的数组,jdk8底层的数组是:Node[],而非Entry[],用数组容量大小乘以加载因子得到一个值,一旦数组中存储的元素个数超过该值就会调用rehash方法将数组容量增加到原来的两倍,专业术语叫做扩容,在做扩容的时候会生成一个新的数组,原来的所有数据需要重新计算哈希码值重新分配到新的数组,所以扩容的操作非常消耗性能.



# 6. put原理:
        table[i]:当前下标所对应的节点
        1).判断数组是否初始化,没有则进行初始化。
        2).根据键值对的key的hash值,通过hash算法计算值得到要插入的数组索引i,如果对应节点为null,直接新建节点添加进去,转向下面第六步;
        3).如果对应节点不为空,判断对应节点的首个元素是否和key一样,如果相同就直接覆盖value,否则转向下面第四步,这里的相同值得是hashCode和equals
        4).判断对应节点是否为treeNode,也就是是否是红黑树,如果是红黑树,则直接在树中插入键值对;
        5).是链表,就进行循环遍历链表,判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作,遍历链表如果发现有相同的key,说明已经存在则直接覆盖value
        6).插入成功后,判断实际存入的键值对数量对数量size是否超过了最大容量threshold(阈值),如果超过进行扩容.



# 7. get()实现原理
        1.先调用k的hashCode()方法得出哈希值,并通过哈希算法转换成数组的下标,通过下标定位对应数组中的位置,也就是节点
        2.如果节点上的key就是要查找的key,则直接命中返回
        3.如果节点上的key不是要查找的key,则判断是否有后续节点:
            1).如果后续节点是红黑树节点,则通过调用树的方法查找该key
            2).如果后续节点是普通链表结构,则通过循环遍历链表查找该key




8. 常见问题
1.为什么 HashMap 中 String、Integer 这样的包装类适合作为 key 键
	因为String和Interger等包装类型是final修饰的,具有不可变性,保证了key的不可更改性,内部重写了equals和hashcoad方法,不容易出现hash值得计算错误,有效减少hash碰撞的几率
2.为什么转换成红黑树的阈值是8,而不是7或者是20呢
	1:如果选择6和8(如果链表小于等于6树还原转为链表,大于等于8转为树),中间有个差值7可以有效防止链表和树频繁转换。假设一下,如果设计成链表个数超过8则链表转换成树结构,链表个数小于8则树结构转换成链表,如果一个HashMap不停的插入、删除元素,链表个数在8左右徘徊,就会频繁的发生树转链表、链表转树,效率会很低。
	2:由于treenodes的大小大约是常规节点的两倍,因此我们仅在容器包含足够的节点以保证使用时才使用它们,当它们变得太小(由于移除或调整大小)时,它们会被转换回普通的node节点,容器中节点分布在hash桶中的频率遵循泊松分布,桶的长度超过8的概率非常非常小。所以作者应该是根据概率统计而选择了8作为阀值
3.为什么在JDK1.7的时候是先进行扩容后进行插入,而在JDK1.8的时候则是先插入后进行扩容的呢?
	JDK1.7=4次位运算+5次异或运算
	JDK1.8=1次位运算+1次异或
	JDK1.7用了9次扰动处理=4次位运算+5次异或运算效率较低,而JDK1.8只用了2次扰动处理=1次位运算+1次异或。
4.扩容后的数组下标问题
	如果旧值的hash和旧的容量计算&为0,则扩容后的位置等于原来坐标。
	如果旧值的hash和旧的容量计算&为1,则扩容后的位置等于原来坐标+旧的容量
5.数组大小为什么是2的n次方?
	数组的下标是用hash值&(数组大小-1)计算的,2的n次方的转换为二进制最高位是1,后面都是0,例如2的4次方是16,二进制表示为10000,16-1=15(二进制表示1111),如果不是2的n次方,比如数组大小为20。20-1=19(10011),这样于hashcode与运算,第2.3位为0,这样得到的数组下标只由第1.4.5位决定,大大减小了数组下标的利用率。
6.hash算法为什么要高16位不变,低16位与高16位异或作为key的最终hash值?
	因为数组的大小是2的n次方并且初始大小为16,假设目前数组大小为16,如果不进行这样运算的话,直接进行hash值与16-1=15的二进制与运算的话,其实只有hash值的后四位参与了运算,这样发生碰撞的概率会提高,而且高16位只有在数组很大的时候才能参与运算,所以用高16位和低16位进行运算,让高位也参与运算,在数组很小的时候增加运算可能性,减少碰撞。
7.负载因子为什么是0.75
	负载因子选得太大了,访问的时候冲突太多,链表长度会比较长,会降低效率;选得太小了,扩容会频繁,会浪费大量存储空间

ConcurrentHashMap

1、整体结构
    1.7:Segment锁分段的技术保证线程安全;
    1.8: 移除Segment,使锁的粒度更小,采用Synchronized + CAS保证线程安全,synchronized在底层只锁定当前链表或红黑树的首节点,这样只要hash不冲突,就不会产生并发
2、put()
    1.7:先定位Segment,再定位桶,put全程加锁,没有获取锁的线程提前找桶的位置,然后进行自旋,并最多自旋64次获取锁,超过则挂起。
    1.8:由于移除了Segment,类似HashMap,可以直接定位到桶,拿到first节点后进行判断,1、为空则CAS插入;2、为-1则说明在扩容,则跟着一起扩容;3、则对当前节点进行synchronize加锁然后进行put操作,操作跟hashmap一样。
3、get()
    基本类似,由于node数组还有其他变量声明为volatile,保证了修改的可见性,因此不需要加锁。
4、resize()【扩容】
    1.7:跟HashMap步骤一样,只不过是搬到单线程中执行,避免了HashMap在1.7中扩容时死循环的问题,保证线程安全。
    1.8:支持并发扩容,HashMap扩容在1.8中由头插改为尾插(为了避免死循环问题),ConcurrentHashmap也是,迁移也是从尾部开始,扩容前在桶的头部放置一个hash值为-1的节点,这样别的线程访问时就能判断是否该桶已经被其他线程处理过了。
5、size()
    1.7:很经典的思路:计算两次,如果不变则返回计算结果,若不一致,则锁住所有的Segment求和。
    1.8:用baseCount来存储当前的节点个数,这就设计到baseCount并发环境下修改的问题(说实话我没看懂-_-!)。

有一组数据12,1,3,8,10,5,20,31,请写出数组从小到大java排序算法

int[] nums = {12, 1, 3, 8, 10, 5, 20, 31};
        for (int i = 0; i < nums.length - 1; i++) {
            boolean flag = true;
            for (int j = 0; j < nums.length - 1 - i; j++) {
                if (nums[j] > nums[j + 1]) {
                    int temp = nums[j];
                    nums[j] = nums[j + 1];
                    nums[j + 1] = temp;
                    flag = false;
                }
            }
            if (flag) {
                break;
            }
        }
        System.out.println("nums = " + Arrays.toString(nums));

在迭代一个集合的时候,如何避免ConcurrejtModificationException

在我用迭代器遍历数组的时候,每次.next方法都会调用checkForComodification放伐,而checkForComodification方法就是用来判断集合发生结构性变化(加入元素,删除元素等操作)的次数是否发生改,如果发生改变就会抛出concurrentmodificationexception异常。所以连起来就是在使用Iterator.next方法的时候如果对集合进行了结构性操作就会抛出此异常
若是在使用迭代器遍历集合的同时修改集合,比如删除元素,要使用iterator.remove(),不要使用集合中的remove方法

有一个List的实例,需要将其中的内容按照字典顺序升序排序,
请编写代码

ArrayList<String> list = new ArrayList<>();

Collections.sort(list, new Comparator<String>() {
    @Override
    public int compare(String o1, String o2) {
        return o1.hashCode()-o2.hashCode();
    }
});

遍历打印一个map中所有的key和value

HashMap<String, Object> map = new HashMap<>();
Set<Map.Entry<String, Object>> entrySet = map.entrySet();
for (Map.Entry<String, Object> entry : entrySet) {
    String key = entry.getKey();
    System.out.println("key = " + key);
    Object value = entry.getValue();
    System.out.println("value = " + value);
}

Executors类是什么

Executors类也是一种常用的创建线程池的方式。

2.线程

并发的三大特性

1.可见性
		当某个线程修改了其内存中共享变量的值时,其他线程能立刻感知到其值的变化
2.原子性
		一个操作是不可中断,即使有多个线程执行,一个操作开始也不会受其他线程影响,即可以理解为线程的最小执行单元,不可被分割
3.有序性
		程序按一定规则进行顺序的执行,期间会进行编译器优化重排、指令重排、内存重排等,执行规则遵循as-if-serial语义规则和happens-before 原则

JMM内存模型

工作流程
    由于JVM运行程序的实体是线程,每个线程创建jvm都会为其创建一个工作内存(有些地方称为栈空间),工作内存是每个线程的私有数据区域,而java内存模型中规定所有变量都存储在主内存,主内存是共享内存,所有的线程都可以访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝的自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,各个线程中的工作内存中存储着主内存中的变量副本拷贝,因此不同的线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成。
规定:		
		1.线程解锁前,必须把共享变量的值刷新回主内存。
		2.线程加锁前,必须读取主内存的最新值到自己的工作内存。
		3.加锁解锁是同一把锁。

58、java创建线程的方式有哪些

1.继承 Thread 类
2.实现 Runnable 接口
3.实现 Callable 接口
4.线程池

线程的状态和状态之间的转换

1. new 新建状态:当使用new关键字创建一个线程之后,这个线程就处于新建状态,此时jvm会为其分配内存并初始化其成员变量的值。
2. runnable就绪状态,当调用线程对象的start方法之后,线程就进入了就绪状态,jvm会创建方法栈和程序计数器,等待线程的掉调度运行。
3. run(运行状态):处于就绪状态的线程获得cpu,开始执行run函数的中的代码,则线程就处于运行状态。
4. 阻塞状态:当前线程因为某种原因放弃cpu使用权,暂时停止运行;直到线程进入就绪状态,才用机会获得cpu转到运行状态。
        阻塞分为三种:
        等待阻塞(wait):调用线程的wait方法就会进入等待阻塞,让当前线程等待某种工作的完成。
        同步阻塞(lock):线程在获取同步锁失败,就会进入同步阻塞状态。
        超时等待阻塞(sleep/join):调用线程的sleep/join方法,或者发出io请求时,线程就会进入阻塞状态,当线程sleep超时,join等待线程终止或者超时,io处理完毕后,线程就会重新进入就绪状态。
5. 死亡状态:线程执行完毕或者因为异常退出run方法。

59、Thread类中start()和run()方法区别

线程对象调用run方法不开启线程。仅是对象调用方法。
线程对象调用start开启线程,并让jvm调用run方法在开启的线程中执行
调用start方法可以启动线程,并且使得线程进入就绪状态,而run方法只是thread的一个普通方法,还是在主线程中执行。

60、Thread类中wait()和sleep()方法区别

wait和sleep方法都会使当前线程暂时放弃cpu使用权,进入阻塞状态,都可以配interrupt打断唤醒。
wait是object类的方法,sleep是thread类的方法。
wait在阻塞中会释放锁,sleep不会释放锁。
wait必须在同步代码块中调用,sleep可以在任何地方调用。

Java中Runnable和Callable有什么不同

runnable接口中run方法是没有返回值的,只是纯粹的去执行run()方法中的代码
callable中的call()方法是有返回值的,是一个泛型,和Future、FutureTask配合可以用来获取异步执行的结果。

java中ThreadLocal是做什么用的,你是怎么理解这个类的,你在什么场景用过这个类?

1.传递数据
2.线程隔离
在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。

而ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。
ThreadLocal在每个本地线程中创建了一个ThreadLocalMap对象,每个线程可以访问自己内部ThreadLocalMap对象里的value。通过这种方式,实现线程之间的数据隔离。

为每个线程分配一个JDBC连接的Connection。这样可以保证每个线程都在各自的Connection上进行数据库的操作,不会出现A线程关闭了线程B正在使用的Connection。

62、什么是死锁,多线程如何避免死锁

就是多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。
1. 设置超时时间
使用JUC包中的Lock接口提供的tryLock方法时,可以设置超时时间,如果超过了这个时间还没拿到这把锁,那么就先做其他的事情,而不是像synchronize一样没拿到锁就一直等下去。
2. 多使用JUC包提供的并发类,而不是自己设计锁
比如juc里atomic包里的原子类,一些并发集合如ConcurrentHashMap
3.尽量降低锁的使用粒度
4. 尽量使用同步方法 而不是同步代码块
    同步方法是把整个方法给加上锁给同步了, 范围较大,造成性能低下, 使用同步代码块范围小,性能高.
    使用同步代码块, 可以自己指定锁的对象, 这样有了锁的控制权, 这样也能避免发生死锁
5. 给线程起有意义的名字
6. 避免锁的嵌套
7. 分配锁资源之前先看能不能收回来资源
如果不能回收回来, 那么就会造成死锁, 那就不分配锁资源给这个线程 , 如果能回收回来, 那么就分配资源下去.
8. 专锁专用
尽量不要几个功能用同一把锁. 来避免锁的冲突, 如果都用同一把锁, 那么就容易造成死锁.

# 公平锁:
	指多个线程按照申请锁的顺序来获取锁.

# 非公平锁:
	指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁在高并发的情况下,有可能造成优先级反转或者饥饿现象
区别:
		1.公平锁在并发环境下,每个线程在获取锁时会先查看此锁维护的等待队列,如果为空或者当前线程是等待队列的第一个,就会占有锁,否则就会加入到等待队列,按照FIFO规则(先进先出)从队列中取到自己
        2.非公平锁上来就会尝试占有锁,如果尝试失败,就采用类似公平锁的方式加入等待队列
		3.非公平锁的吞吐量比公平锁大

# 可重入锁(递归锁)
1. 概念:
		1.同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。
2. 作用:
		避免死锁

# 自旋锁(SpinLock)
1. 概念:
		指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁。
		循环比较获取直到成功为止,没有类似wait的阻塞
2. 优缺点:
		减少线程上下文切换的消耗.
		循环会消耗CPU.

# 独占锁(写锁)
1. 概念
		该锁只能被一个线程所持有

# 共享锁(读锁)
1. 概念
		该锁可被多个线程所持有

# 乐观锁
	总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。
在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。AtomicInteger

# 悲观锁
	总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。
数据库中的写锁就是悲观锁。

reentrantlock:默认非公平锁,可重入锁,悲观锁,独占锁

synchronized:非公平锁,可重入锁,悲观锁,独占锁

CAS

1. 应用场景:
        整个AQS同步组件、Atomic原子类操作等等都是以CAS实现的
        ConcurrentHashMap在1.8的版本中也调整为了CAS+Synchronized。可以说CAS是整个JUC的基石。
2. 概念
    	物理内存中的值:主内存值
    	期望值:工作内存值
    	值偏移量:值的地址
    	当前线程会携带自身工作内存中的值(主内存值的快照)和值偏移量进入方法,通过getIntVolatile方法取出主内存的值,调用compareAndSwapInt与主内存中值进行比较,如果相等会进行修改操作,不相等就会进行do-while循环,重新获取工作内存的资源。
3. 工作原理
    	调用Unsafe类中的CAS方法,JVM会帮我们实现CAS汇编指令,是一种完全依赖于硬件的功能,通过它实现了原子操作,是由多个指令组成的,用于完成某个功能的一个过程,并且这些指令的执行必须是连续的,在执行过程中不允许中断,不会造成数据不一致的问题。
4. 缺点
    	1.循环时间长,开销大
    		如果CAS失败,会一直进行尝试,会一直占用CPU带来很大的开销。
    	2.只能保证一个变量的原子性,对于多个共享变量操作,要使用加锁来保证原子性。
    	3.ABA问题
    		通过AtimicStampedReference<V>原子类解决,原子类+时间戳(版本号);

sychronized

1. 实现原理:
		jvm基于进入和退出Monitor对象来实现方法的同步和代码块的同步
2. 方法
		方法的同步是隐式的,不需要通过字节码指令来进行控制,主要实现在方法调用和返回操作之中,jvm可以从方法常量池中的方法表结构中的访问标识区分一个方法是否被同步,当方法调用时,调用指令会检查访问标识是否被设置,如果被设置了,执行线程将先持有monitor对象,然后再执行方法,方法完成,释放monitor对象
3. 代码块		
		代码块同步,利用monitorenter和monitorexit这两个字节码指令分别位于同步代码块开始和结束位置,当jvm执行monitorenter指令时,当前线程试图获取monitor对象的所有权,如果未加锁或者被当前线程持有,就把锁的计数器+1,当执行exit指令时,锁的计数器-1,当计数器为0时,该锁就被释放,如果获取失败就会进行阻塞
		
1.当修饰静态方法的时候,锁的是当前类的class对象。
2.当修饰实例方法时,锁的就是当前类的实例对象。
3.当修饰代码块的时候,锁的是同步代块里的对象实例。

synchronized与lock的区别

1. Lock 是 API层面,是java5以后新出现的一个类,synchronized 是 JVM 级别的是java关键字
2. synchronized不需要用户手动释放锁对象,当synchronized代码执行完后系统会自动让线程释放对锁的占用,而lock需要手动释放锁对象,若没有主动释放锁,有可能导致死锁现象出现
3. synchronized不可中断,除非抛出异常或者正常运行完成
		1.lock可中断,设置超时方法tryLock(Long time,TimeUnit unit),到指定时间后会获取尝试获取锁,获取不到就直接中断。
		2.lockInterruptibly()放代码块中,调用interrupt()方法可中断
3. synchronized默认是非公平锁,reentrantlock默认是非公平锁
4. synchronized不可以绑定多个条件Condition, ReentrantLock 通过 Condition 可以绑定多个条件,达到精确唤醒线程。
5. ReentrantLock底层调用的是Unsafe的park方法加锁,synchronized操作的应该是对象头中mark word.

interrupt()是给线程设置中断标志;

interrupted()是检测中断并清除中断状态;

isInterrupted()只检测中断。

还有重要的一点就是interrupted()作用于当前线程,interrupt()和isInterrupted()作用于此线程,即代码中调用此方法的实例所代表的线程。

volatile

1. 概念:
		一种java虚拟机提供的轻量级的同步机制
2. 特性:
		a.保证可见性(通过内存屏障保证可见性)
		b.不保证原子性
		c.禁止指令重排(保证有序性)
		
volatile实现禁止指令重排优化,从而避免多线程环境下程序出现乱序的现象。

3. 适用场景(在哪些地方用过volatile):
	    1.单例模式,通过DCL机制
	    2.读写锁,手写缓存
	    3.CAS底层的juc也用volatile

1.doouble check lock双端检锁机制,底层变量使用volatile修饰,对象创建时进行synchronized加锁

2.dcl机制存在线程安全问题,当对象初始化时,由于指令重排可能会导致对象未初始化时,就获取实例对象来指定内存地址,导致异常报错

内存屏障(Memory Barrier)

概念:内存栅栏,是一个CPU指令
作用:
	1.保证特定操作的执行顺序
	2.保证某些变量的内存可见性(利用该特性实现volatile的内存可见性)

工作原理:
	由于编译器和处理器都能执行指令重排优化。如果在指令间插入一条Memory Barrier则会告诉编译器和CPU,不管什么指令都不能和这条Memory Barrier指令重排序,通过插入内存屏障禁止在内存屏障前后的指令执行重排序优化。内存屏障另一个作用就强制刷出各种CPU的缓存数据,因此任何CPU上的线程都能读取到这些数据的最性版本。
	
	1.对Volatile变量进行写操作时:
		会在写操作后加入一条store屏障指令,将工作内存中的共享变量值刷性回到主内存。
	2. 对Volatile变量进行读操作时:
		会在读操作前加入一条load屏障指令,从主内存中读取共享变量。

指令重排只会保证串行语句的一致性

问题

  1. 工作内存和主内存同步延迟现象导致的可见性问题

    可以使用synchronize或volatile关键字解决,它们都可以使一个线程修改后的变量立即对其他线程可见

  2. 对于指令重排导致的可见性问题和有序性问题。

    可以利用volatile关键字解决,因为volatile的另外一个作用就是禁止重排序优化。

3.线程池

1. 线程池做的工作主要是控制运的线程的数量,处理过程中将任务放入队列,然后
2. 特点:线程复用,控制最大并发数,管理线程
3. 好处:
		1.降低资源消耗,通过重复利用已创建的线程降低线程的创建和销毁时的资源消耗。
		2.提高响应速度,当任务到达时,任务可以不需要等待线程创建就能立即执行。
		3.提高线程的可管理性,使用线程池可以对线程进行统一的分配,调优和监控
4. 线程池的创建方式:
		new ThreadPoolExecutor();
# 5. 七大参数:
		int corePoolSize:核心线程数
        int maximumPoolSize:线程池最大容量
        long keepAliveTime:非核心线程保留时间
        TimeUnit unit:时间类型
        BlockingQueue<Runnable> workQueue:工作队列
        ThreadFactory threadFactory:线程工厂
        RejectedExecutionHandler handler:拒绝策略
# 6. 工作原理:
		任务进来时,首先执行判断,判断核心线程是否处于空闲状态,如果不是,核心线程就先就执行任务,如果核心线程已满,则判断任务队列是否有地方存放该任务,若果有,就将任务保存在任务队列中,等待执行,如果满了,在判断最大可容纳的线程数,如果没有超出这个数量,就开创非核心线程执行任务,如果超出了,就调用handler实现拒绝策略。
7. 拒绝策略
		第一种AbortPolicy:不执行新任务,直接抛出异常,提示线程池已满
        第二种DisCardPolicy:不执行新任务,也不抛出异常
        第三种DisCardOldSetPolicy:将消息队列中的第一个任务替换为当前新进来的任务执行
        第四种CallerRunsPolicy:直接调用execute来执行当前任务
		
# 8. 线程池类型
        CachedThreadPool:可缓存的线程池,该线程池中没有核心线程,非核心线程的数量为Integer.max_value,就是无限大,当有需要时创建线程来执行任务,没有需要时回收线程,适用于耗时少,任务量大的情况。
        SecudleThreadPool:周期性执行任务的线程池,按照某种特定的计划执行线程中的任务,有核心线程,但也有非核心线程,非核心线程的大小也为无限大。适用于执行周期性的任务。
        SingleThreadPool:只有一条线程来执行任务,适用于有顺序的任务的应用场景。
        FixedThreadPool:定长的线程池,有核心线程,核心线程的即为最大的线程数量,没有非核心线程
        workStealingPool:如果一个线程任务执行完毕进入空闲时期时,它会从其他队列里获取任务

4.设计模式

请结合参与过的项目,说明在哪些场景使用过哪些设计模式?

适配器模式:JDBC给出一个客户端通用的抽象接口,每一个具体数据库引擎(如SQL Server、Oracle、MySQL等)的JDBC驱动软件都是一个介于JDBC接口和数据库引擎接口之间的适配器软件

装饰模式:spring的aop就是采用的代理和装饰模式

代理模式:

责任链模式:spring拦截器

DTO传输对象设计模式:对于一段查询请求所返回的数据进行做两次封装,也就是相当于创建两个实体类,第一个是普通pojo类其中的属性对应数据库中字段,当数据从数据库查询出时会封装成此pojo对象,然后在服务层将其pojo类中的数据,转换为vo实体类,这样做可以保证数据的安全性,也方便数据的传输

5.jdk8新特性

1.Lambda 表达式

Lambda 允许把函数作为一个方法的参数。

img

2.方法引用

方法引用允许直接引用已有 Java 类或对象的方法或构造方法。

上例中我们将 System.out::println 方法作为静态方法来引用。

3.函数式接口

有且仅有一个抽象方法的接口叫做函数式接口,函数式接口可以被隐式转换为 Lambda 表达式。通常函数式接口

上会添加@FunctionalInterface 注解。

4.接口允许定义默认方法和静态方法

从 JDK8 开始,允许接口中存在一个或多个默认非抽象方法和静态方法。

5.Stream API

可以进行链式编程,里面的特殊的方法会结束这个链式,foreach(遍历),count(聚合);skip(跳过前几个),limit(取用前几个),map(映射),filter(过滤),concat(组合)
流的特性:支持并行流与顺序流,
并行流:多个线程同时运行(.parallel())
顺序流:使用主线程,单线程(.sequential())

6.日期/时间类改进

之前的 JDK 自带的日期处理类非常不方便,我们处理的时候经常是使用的第三方工具包,比如 commons-lang 包等。不过 JDK8 出现之后这个改观了很多,比如日期时间的创建、比较、调整、格式化、时间间隔等。这些类都在 java.time 包下,LocalDate/LocalTime/LocalDateTime。

7.Optional

Optional 类是一个可以为 null 的容器对象。如果值存在则 isPresent()方法会返回 true,调用 get()方法会返回该对象。

8.Java8 Base64 实现

Java 8 内置了 Base64 编码的编码器和解码器。

JavaWeb

TCP

  1. TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的通信协议,数据在传输前要建立连接,传输完毕后还要断开连接。
  2. 客户端在收发数据前要使用 connect() 函数和服务器建立连接。建立连接的目的是保证IP地址、端口、物理链路等正确无误,为数据的传输开辟通道。
  3. TCP建立连接时要传输三个数据包,俗称三次握手(Three-way Handshaking)

TCP数据报结构

①序号:Seq(Sequence Number)序号占32位,用来标识从计算机A发送到计算机B的数据包的序号,计算机发送数据时对此进行标记。
②确认号:Ack(Acknowledge Number)确认号占32位,客户端和服务器端都可以发送,Ack = Seq + 1。
③标志位:每个标志位占用1Bit,共有6个,分别为 URG、ACK、PSH、RST、SYN、FIN,具体含义如下:
        URG:紧急指针(urgent pointer)有效。
        ACK:确认序号有效。
        PSH:接收方应该尽快将这个报文交给应用层。
        RST:重置连接。
        SYN:建立一个新连接。
        FIN:断开一个连接。

tcp三次握手

1. 过程:
        ①首先 Client 端发送连接请求报文
        ②Server 段接受连接后回复 ACK 报文,并为这次连接分配资源。
        ③Client 端接收到 ACK 报文后也向 Server 段发生 ACK 报文,并分配资源,这样 TCP 连接就建立了。
2. 注意:
        三次握手的关键是要确认对方收到了自己的数据包,这个目标就是通过“确认号(Ack)”字段实现的。计算机会记录下自己发送的数据包序号Seq,待收到对方的数据包后,检测“确认号(Ack)”字段,看Ack = Seq + 1是否成立,如果成立说明对方正确收到了自己的数据包。

TCP四次挥手

建立连接非常重要,它是数据正确传输的前提;断开连接同样重要,它让计算机释放不再使用的资源。如果连接不能正常断开,不仅会造成数据传输错误,还会导致套接字不能关闭,持续占用资源,如果并发量高,服务器压力堪忧。

1. 过程:
        ①服务端申请断开连接即FIN,发送Seq+Ack
        ②客户端接收信息返回,表示我已经接收到
        ③客户端发送信息表示可以断开连接
        ④服务端接受信息,返回数据表示已接受信息
        
数据传输完毕后,双方都可释放连接。最开始的时候,客户端和服务器都是处于ESTABLISHED状态,然后客户端主动关闭,服务器被动关闭。

问题

1.为什么需要三次通信

  • 在第一次通信过程中,A向B发送信息之后,B收到信息后可以确认自己的收信能力和A的发信能力没有问题。

  • 在第二次通信中,B向A发送信息之后,A可以确认自己的发信能力和B的收信能力没有问题,但是B不知道自己的发信能力到底如何,所以就需要第三次通信。

  • 在第三次通信中,A向B发送信息之后,B就可以确认自己的发信能力没有问题。

小结:3次握手完成两个重要的功能,既要双方做好发送数据的准备工作(双方都知道彼此已准备好),也要允许双方就初始序列号进行协商,这个序列号在握手过程中被发送和确认。

tcp/ip协议底层是字节流,有可能由于网络原因,会导致client端和server端宕机,如果client端发出两次请求,第一次请求由于网络原因,但由于网络原因导致client端死机或者宕机,此时对于client端请求是失效的,如果是两次握手的话,对于server端并不知情,server端收到请求,会改变自己的状态,但是client已经不会再发送请求,而server端会一直等待,导致资源的浪费

2.为什么连接的时候是三次握手,关闭的时候却是四次握手?

  • 因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。
  • 但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,“你发的FIN报文我收到了”。
  • 只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。

3.TCP的三次握手一定能保证传输可靠吗?

不能

  • 三次握手比两次更可靠,但也不是完全可靠,而追加更多次握手也不能使连接更可靠了。因此选择了三次握手。
  • 世界上不存在完全可靠的通信协议。从通信时间成本空间成本以及可靠度来讲,选择了“三次握手”作为点对点通信的一般规则。

tcp和udp二者区别

  1. 连接类型:TCP是面向连接的协议,要传输数据必须先进行连接,就是常说的“三次握手”,握手成功建立连接之后才能进行数据的传输交互,如同微信视频聊天需要对方接受才能彼此看到。UDP是非面向连接的协议,发送数据时不管对方状态直接发送,无需建立连接,如同微信发送一个消息或者语音信息,对面在不在线无所谓。

  2. 传输开销: 由于二者特性的差异,TCP在传输数据过程中,接收方接收数据包时也会向发送方反馈数据包,因为会造成额外通信开销。UDP接收方则不会进行反馈,因此不会有这方面的额外开销。

  3. 速度:TCP相较于UDP较慢,这也主要是因为TCP有一个连接的过程而UDP没有

转发与重定向的区别

1.对象不同
	请求转发 : request
	重定向 : response
2.次数不同
	请求转发:1
	重定向:至少2次
3.地址栏指向
	请求转发:原始路径
	重定向:最终指向
4.行为对象不同
	请求转发:服务器内部行为
	重定向:浏览器
5.数据共享
	请求转发可以传递

HTTP请求GET和POST两种方式的区别

1. Get是不安全的,因为在传输过程,数据被放在请求的URL中;Post的所有操作对用户来说都是不可见的。
2. Get传送的数据量较小,这主要是因为受URL长度限制;Post传送的数据量较大,一般被默认为不受限制。
3. Get限制Form表单的数据集的值必须为ASCII字符;而Post支持整个ISO10646字符集。
4. Get执行效率却比Post方法好。Get是form提交的默认方法。
5. GET产生一个TCP数据包,浏览器会header和data一并发送出去;POST产生两个TCP数据包,浏览器会先发送header,服务器响应100 continue,浏览器再发送data。

cookie和session的区别?

session存放在服务器上
session中存放的数据相对安全
session中存放的数据不受限制
session是由服务器创建的
session中存放Object类型的数据
session默认存活30m

cookie存放在浏览器上
cookie中存放的数据不安全
cookie中存放的数据量受限(4kb)
cookie对象是由服务器创建的
cookie中存放String类型的数据
cookie中不能存放特殊字符
cookie不能跨浏览器

HTTP请求有几种,请说一说他们的区别和应用场景

get:从指定资源中请求数据,
post:向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST 请求可能会导致新的资源的建立和/或已有资源的修改。
delete:删除指定的资源
put:将数据发送到服务器以创建或更新资源,它可以用上传的内容替换目标资源中的所有当前内容。
trace:回显服务器收到的请求,主要用于测试或诊断。
head:类似于 GET 请求,只不过返回的响应中没有具体的内容,用于获取报头
options:允许客户端查看服务器的性能。

HttpRequest中常见的contentType有哪些,简单藐视其作用

application/xhtml+xml:XHTML格式
application/xml:XML数据格式
application/json:JSON数据格式
application/octet-stream:二进制流数据(常见的文件下载)
application/x-www-form-urlencoded:表单中默认的encType,表单数据被编码为key/value格式发送到服务器
另外一种常见的媒体格式是上传文件时使用:

multipart/form-data:需要在表单中进行文件上传时,就需要使用该格式

text/html:HTML格式
text/plain:纯文本格式
text/xml:XML格式

image/gif:gif图片格式
image/jpeg:jpg图片格式
image/png:png图片格式

Mysql

存储引擎

mysql数据库存储引擎有啥?区别是什么

1.myisam是mysql5.5以前的默认引擎,不支持事务和外键,支持表锁
	存储特点:
	会在磁盘上生成3个文件,文件名和表名相同,扩展名分别为:
	.frm----储存表定义
	.MYD----存储表中的数据
	.MYI----存储索引
2.innodb(5.5之后的默认引擎)支持事务,支持行锁,支持外键,支持聚集索引的方式存储数据。

SQL

MySQL查询执行路径

  1. 客户端发送一条查询给服务器;

  2. 服务器先会检查查询缓存,如果命中了缓存,则立即返回存储在缓存中的结果。否则进入下一阶段;

  3. 服务器端进行SQL解析、预处理,再由优化器生成对应的执行计划;

  4. MySQL根据优化器生成的执行计划,调用存储引擎的API来执行查询;

  5. 将结果返回给客户端。

img

SqL中inner join,left join , right join 的区别

1.inner join(内连接),在两张表进行连接查询时,只保留两张表中完全匹配的结果集。
2.left join(左连接),在两张表进行连接查询时,会返回左表所有的行,即使在右表中没有匹配的记录。
3.right join(右连接),在两张表进行连接查询时,会返回右表所有的行,即使在左表中没有匹配的记录。
4.full join,在两张表进行连接查询时,返回左表和右表中所有没有匹配的行。

Truncate,delete,drop区别

一、相同点:
    truncate和不带where自居的delete,以及drop都会删除表内的数据
二、不同点:
    1、truncate和delete只删除数据不删除表的结构(定义),而drop语句将删除表的结构被依赖的约束(constrain),触发器(trigger),索引(index);依赖于该表的存储过程/函数将保留,但是变为invalid状态。
    2、delete命令是DML,删除的数据将存储在系统回滚段中,需要的时候,数据可以回滚恢复。
         而truncate,drop命令是DDL,删除的数据是操作立即生效,原数据不放到rollback segment中,不能回滚,数据不可以回滚恢复。
    3、delete命令,不会自动提交事务,操作会触发trigger;而truncate,drop命令,执行后会自动提交事务,操作不触发trigger。
    4、速度:一般来说:drop > truncate > delete

聚合函数

count:统计数量
sum:求和
avg:求平均值
max:求最大值
min:求最小值

关键字

limit:分页
group by:分组
distinct:去重

form...on...left join...where...group by...avg()/sum()...having..select...
order by...asc/desc...limit...

数据库三范式

第一范式:要求表中的每一列保持原子性(不可分割)
第二范式:也就是说在一个数据库表中,一个表中只能保存一种数据,不可以把多种数据保存在同一张数据库表中
第三范式:确保数据表中的每一列数据都和主键直接相关,而不能间接相关

数据库事务

特性

原子性(a):事务是最小的工作单位不能分割,要么全部被执行,要么都不执行。
一致性(c):必须保证事务中的语句全部执行成功,要么就全部失败。
隔离性(i):事务之间相互个隔离,不能被其他事务干扰。
持久性(d):事务正确提交后需要把数据持久化到硬盘中。

多事务并发问题

脏读
	一个事务读取到了另一个事务尚未提交的数据
不可重复读
	一个事务多次读取到的数据内容不一致(读取到了另一个事务update并提交的数据)
幻读
	一个事务前后读取的数据记录行数不同(读取到别的事务insert和delete并提交的数据)

隔离级别

读未提交:不能解决任务问题。
读已提交:解决了脏读。是数据库的默认隔离级别,
可重复读:不能解决幻读。是mysql的默认隔离级别。
可串行化读:解决任务问题。主要是通过在每个读的数据行上加上共享锁。

索引

聚簇索引的文件存放在主键索引的叶子节点上,因此 InnoDB 必须要有主键,通过主键索引效率很高。但是辅助索引需要两次查询,先查询到主键,然后再通过主键查询到数据。因此,主键不应该过大,因为主键太大,其他索引也都会很大。
而 MyISAM 是非聚集索引,数据文件是分离的,索引保存的是数据文件的指针。主键索引和辅助索引是独立的。

索引分类

主键索引:通过主键约束创建的索引。
唯一索引:通过唯一约束创建的索引。
普通索引:单个字段添加索引
联合索引:多个字段组合成索引,能够加快复合查询的速度。查询的时候会进行最左匹配原则。

索引的优缺点

优点:
1.提高了数据检索的效率,降低数据库的成本
2.索引底层是二叉树,会自动排序,降低了CPU的消耗
缺点:
1.在海量数据前提下,创建索引成本高
2.会额外占用内存
3.维护的成本高

索引创建

1. 字段内容可识别度不能低于70%,字段内数据唯一值的个数不能低于70%

2. 经常使用where条件搜索的字段,例如user表的id name等字段。

3. 经常使用表连接的字段(内连接、外连接),可以加快连接的速度。

4. 经常排序的字段 order by,因为索引已经是排过序的,这样一来可以利用索引的排序,加快排序查 询速度。

5. 空间原则(字段占用空间越小也好)

mysql优化,什么时候索引会失效

- 最左匹配原则  
- 范围条件右边的索引失效
- 不再索引列上做任何操作 
- 在使用(!=或者<>)索引失效
- is not null无法使用索引
- like以通配符开头(%qw)索引失效 
- 字符串不加引号索引失效
- 使用or连接索引失效
- 尽量使用覆盖索引  覆盖索引: 要查询的字段全部是索引字段

sql优化

1. explian关键字
explain这个命令来查看一个这些SQL语句的执行计划,查看该SQL语句有没有使用上了索引,有没有做全表扫描,这都可以通过explain命令来查看
2. 插入时尽量使用批量插入
3. 排序的时候不要使用select * 
4. 尽量不要使用子查询,可以使用连接查询替代

Spring

spring事物管理机制原理是什么?他是如何确保同一请求下不同类,不同方法在同一个事务中运行?

spring有aop编程的支持,可以对handler进行增强,而spring事务的管理机制就是通过动态代理对所有需要事务管理的bean进行加强,并根据配置在invoke方法中对当前调用的方法名进行判定,在method.invoke方法前后为其加上合适的事务管理代码,这样实了同一请求下不同类,不同方法在同一事务中运行

spring源码,spring都用了哪些设计模式

1.简单工厂模式
	由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类。 spring中的BeanFactory就是简单工厂模式的体现,根据传入一个唯一的标识来获得bean对象,但是否是在传入参数后创建还是传入参数前创建这个要根据具体情况来定。
2.单例模式
	保证一个类仅有一个实例,并提供一个访问它的全局访问点。spring中的单例模式完成了后半句话,即提供了全局的访问点BeanFactory。在 spring 的配置文件中设置 bean 默认为单例模式。
3.适配器模式
	在Spring的Aop中,使用的Advice(通知)来增强被代理类的功能。spring通过生成被代理类的代理类,并在代理类的方法前,设置拦截器,通过执行拦截器重的内容增强了代理方法的功能,实现的面向切面编程。
4.装饰者模式
	Spring 中配置 DataSource 的时候,DataSource 可能是不同的数据库和数据源。我们能否根据客户的需求在少修改原有类的代码下动态切换不同的数据源?这个时候就要用到装饰者模式(这一点我自己还没太理解具体原理)。Spring 中用到的包装器模式在类名上含有 Wrapper或者 Decorator。这些类基本上都是动态地给一个对象添加一些额外的职责
5. 模板方式模式
	用来解决代码重复的问题。RestTemplate、JdbcTemplate、MongoTemplate
6.观察者模式
	定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。spring的定时器用到了这种设计模式,如ApplicationListener
7.代理模式
	为其他对象提供一种代理以控制对这个对象的访问。  从结构上来看和Decorator模式类似,但Proxy是控制,更像是一种对功能的限制,而Decorator是增加职责。
8.前端控制器模式
	spring 提供了前端控制器DispatherServlet 来对请求进行分发。

JPA如何设置CascadeTpe才会级联查询?


谈谈你对Spring 的理解

Spring 是一个开源框架,为简化企业级应用开发而生。Spring 可以是使简单的JavaBean 实现以前只有EJB 才能实 现的功能。Spring 是一个 IOC 和 AOP 容器框架。 
Spring 容器的主要核心是: 
    1.控制反转(IOC),传统的 java 开发模式中,当需要一个对象时,我们会自己使用 new 或者 getInstance 等直接或 者间接调用构造方法创建一个对象。而在 spring 开发模式中,spring 容器使用了工厂模式为我们创建了所需要的对象,不需要我们自己创建了,直接调用spring 提供的对象就可以了,这是控制反转的思想。 
    2.依赖注入(DI),spring 使用 javaBean 对象的 set 方法或者带参数的构造方法为我们在创建所需对象时将其 属性自动设置所需要的值的过程,就是依赖注入的思想。 
    3.面向切面编程(AOP),在面向对象编程(oop)思想中,我们将事物纵向抽成一个个的对象。而在面向切面编程中,我们将一个个的对象某些类似的方面横向抽成一个切面,对这个切面进行一些如权限控制、事物管理,记录日志等公用操作处理的过程就是面向切面编程的思想。简单来说,就是在不修改原代码码的情况下对方法进行功能增强。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。AOP 底层是动态代理,如果是接口采用 JDK 动态代理,如果是类采用CGLIB 方式实现动态代理

springbean初始化流程

实例化bean对象(通过构造方法或者工厂方法)
设置对象属性(setter等)(依赖注入)
如果Bean实现了BeanNameAware接口,工厂调用Bean的setBeanName()方法传递Bean的ID。(和下面的一条均属于检查Aware接口)
如果Bean实现了BeanFactoryAware接口,工厂调用setBeanFactory()方法传入工厂自身
将Bean实例传递给Bean的前置处理器的postProcessBeforeInitialization(Object bean, String beanname)方法
调用Bean的初始化方法
将Bean实例传递给Bean的后置处理器的postProcessAfterInitialization(Object bean, String beanname)方法
使用Bean
容器关闭之前,调用Bean的销毁方法

springBean的作用域

singleton:单例模式,在spring IoC容器仅存在一个Bean实例,Bean以单例方式存在,bean作用域范围的默认值。
prototype:多例模式,每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行newXxxBean()。
request:每次请求共享一个bean,每次HTTP请求都会创建一个新的Bean,该作用域仅适用于web的Spring WebApplicationContext环境。
session:同一个session共享一个bean,同一个HTTP Session共享一个Bean,不同Session使用不同的Bean。该作用域仅适用于web的Spring WebApplicationContext环境。
globalSession【spring4之后称为application】:限定一个Bean的作用域为ServletContext的生命周期。该作用域仅适用于web的Spring WebApplicationContext环境。

微服务

是一种架构模式或者说是一种架构风格,它提倡将单一应用程序划分成一组小的服务,每个服务运行在其独立的自己的进程中,服务之间互相协调、互相配合,为用户提供最终价值。 服务之间采用轻量级的通信机制互相沟通(通常是基于HTTP的RESTful API)。每个服务都围绕着具体业务进行构建,并且能够被独立地部署到生产环境、类生产环境等。另外,应尽量避免统一的、集中式的服务管理机制,对具体的一个服务而言,应根据业务上下文,选择合适的语言、工具对其进行构建,可以有一个非常轻量级的集中式管理来协调这些服务,可以使用不同的语言来编写服务,也可以使用不同的数据存储。

spring常用注解

@Configuration:指定配置类
@PropertySource:引入外部配置文件
@ContextConfiguration:指定配置文件或配置类
@Transactional:在Service中,被 @Transactional 注解的方法,将支持事务。如果注解在类上,则整个类的所有方法都默认支持事务。
@Autowired:自动注入依赖
@Value: 注入基本数据类型

Spring bean 的生命周期

bean 定义:可以通过xml文件,注解的方式进行定义。 
bean 初始化:有两种方式初始化: 
 1.在配置文件中通过指定 init-method 属性来完成 
 2.实现 org.springframwork.beans.factory.InitializingBean 接口 
bean 调用:有三种方式可以得到 bean 实例,并进行调用 
bean 销毁:销毁有两种方式 
 1.使用配置文件指定的 destroy-method 属性 
 2.实现 org.springframwork.bean.factory.DisposeableBean 接口

SpringMVC

springmvc的理解与执行流程

springMVC是spring基础上的一个mvc框架,主要负责处理web开发的路径映射与视图渲染,属于spring框架中web层开发的一部分。
当前端发送请求,前端控制器dispatcherServlet接收到请求后,会调用HandlerMapping(处理器响应器),通过@RequestMapping注解找到对应的controller下的handler(处理请求的方法),响应器返回handlerExecutionChain执行器链里面包含了handler和拦截器,前端控制器调用handlerAdapter(适配器)来执行handlder,此时会返回包含视图名称或视图对象以及一些模型数据modelandview到前端控制器,然后前端控制器调用viewResolver(视图解析器)来解析适配器返回的结果,找到对应的资源位置,最后进行视图渲染操作,做出响应

SpringMVC 常用注解都有哪些?

@requestMapping 用于请求 url 映射。 
@RequestBody 注解实现接收 http 请求的 json 数据,将 json 数据转换为 java 对象。 
@ResponseBody 注解实现将 controller 方法返回对象转化为 json 响应给客户

servlet需要的配置文件中进行哪些的配置?可以使用spring来管理servlet和filter吗?


如果可以需要如何配置?


在Controller或者servlet中如何获取页面提交的参数?


Spring的配置中有一个scope的参数,请问在web应用中一般需要如何配置


为什么这么配置?


SpringBoot

springboot的启动类核心注解是哪个?它主要由那几个注解组成?

1、SpringBootApplication 
2、@Target   注解的作用目标,可以是类,接口
3、@Retention  注解生命周期
4、@Documented 注解表明这个注解应该被 javadoc工具记录
	5、@Inherited  如果一个类用上了@Inherited修饰的注解,那么其子类也会继承这个注解
	6、@SpringBootConfiguration  标注当前类是配置类,
	7、@EnableAutoConfiguration 注释自动载入应用程序所需的所有Bean——这依赖于Spring Boot在类路径中的查找。
	8、@ComponentScan  注解默认会扫描该类所在的包下所有的配置类

SpringCloud

springcloud与dubbo区别?

1.初始定位不同:SpringCloud定位为微服务架构下的一站式解决方案;Dubbo 是 SOA 时代的产物,它的关注点主要在于服务的调用和治理
2.生态环境不同:SpringCloud依托于Spring平台,具备更加完善的生态体系;而Dubbo一开始只是做RPC远程调用,生态相对匮乏,现在逐渐丰富起来。
3.调用方式:SpringCloud是采用Http协议做远程调用,接口一般是Rest风格,比较灵活;Dubbo是采用Dubbo协议,接口一般是Java的Service接口,格式固定。但调用时采用Netty的NIO方式,性能较好。
4.组件差异比较多,例如SpringCloud注册中心一般用Eureka,而Dubbo用的是Zookeeper

springcloud常用组件以及作用,服务之间是怎么调用的?核心组件有哪些?简单描述其作用

1.Eureka:注册中心,提供了服务注册,发现的功能,能对服务状态进行监控
2.Ribbon:负载均衡	
3.Histryix:熔断器
4.Feign:服务调用
5.gateway:网关

springcloud服务之间通过http协议调用,因为它对服务的提供和调用没有任何技术限定,自由领过,更符合微服务架构的理念,并且现在热门的Rest风格可以通过http协议来实现

如何做到springcloud数据库变化,每个服务间数据如何同步


Redis

请简述一种分布式锁的实现方式?


Redis支持的数据类型?使用redis有哪些好处?

字符串类型 	string
散列类型 	 hash
列表类型 	 list : 有序可重复
集合类型 	 set : 无序不可重复
有序集合类型 	sortedset : 有序不可重复
好处:
	(1) 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)

	(2) 支持丰富数据类型,支持string,list,set,sorted set,hash

	(3) 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行

	(4) 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除

描述一下Redis最适合的场景和支持的数据类型

场景:
	




数据类型:
    字符串类型 	string
    散列类型 	 hash
    列表类型 	 list : 有序可重复
    集合类型 	 set : 无序不可重复
    有序集合类型 	sortedset : 有序不可重复

mysql和redis的区别


Redis持久化操作


项目中缓存是如何使用的?为什么要用缓存?缓存使用不当会造成什么后果?

当数据量大,并且需要多次查询,不会频繁修改,不是特别重要的数据,会考虑通过Redis做缓存使用解决程序的三高问题	高并发:在同一个时间点,同时有海量的用户并发访问(秒杀)	高负载:数据库中数据量特别大,数据表中的每天产生海量的数据	高扩展(高可用):非关系型数据库可以通过不断添加服务器站点来实现扩展		数据与数据之间没有任何关系后果:	数据不能及时保存,导致数据的丢失

MQ

RocketMQ

工作流程

1.启动时会将mq的broker,producer,consumer注册到nameserver里2.生产者会先从nameserver中获取broker信息,然后将消息发送到对应的broker中,broker会返回接收消息的回执给生产者3.消费者消费消息有两种一种是broker主动推送到消费者,一种是消费者从nameserver中获取对应的broker信息,然后主动拉取broker中的消息4.而整体架构中各个服务器的健康问题都有一个监视器进行监视,并且整个架构中的服务器每个一段时间会主动向命名服务器中发送请求

消息丢失

1. 生产者:
		事务消息
2. broker:
		1.通过持久化机制将消息支持持久化到Commitlog里面,即使宕机后重启,未消费的消息也是可以加载出来的
		2.使用刷盘机制,mq支持同步刷盘,异步刷盘
            同步刷盘:数据安全性较高,io次数多,效率低,速度慢。(适用于数据安全性较高的业务)
            异步输盘:数据安全性较低,io次数少,所以效率高,速度快。(适用于追求高效率处理数据的业务)
3. 消费者:
		重试机制,默认16失败后会将此消息放入死信队列

消息重复消费

42、RabbitMQ有哪几种通信方式,分别适合什么场景?


MAVEM

Linux

linux常用命令

pwd :返回当前所在目录位置
cd : 切换目录
	可以是相对路径,绝对路径
ls : 简略查看当前目录下的资源文件
ll:详细查看当前目录下的资源文件,(权限,创建时间,大小...)
mkdir:创建文件夹
	-p: 没有父目录情况下,创建父目录
	-v: 显示详细信息
rmdir: 删除空文件夹
rm -rf:递归删除文件夹和其中的文件
	-r:递归,从内往外
	-f:不询问,强制删除
	
vi编辑器,有3种模式
1.命令行模式:接受键盘录入的命令,进行高级操作
    nyy:复制光标所在以下n行内容
    p:粘贴
    ndd:删除光标所在以下n行内容
    u:撤销
    1.1 通过a,i,o进入编辑模式
    1.2 通过 : 进入底行模式
2.编辑模式:键盘录入内容,进行文件编辑
	a:append在光标后追加内容
	i:insert在当前位置插入内容
	o:换行录入内容
	通过esc进入命令行模式
3.底行模式:用于回到终端
	wq:保存并退出
	q!:不保存,强制退出
	
cat 文件名:查看文件所有内容
more 文件名:翻页查看文件内容
less 文件名:翻页查看文件内容
tail -n 20 -f 文件名:实时查看文件最新内容,显示20行
head 文件名:默认查看文件头的10行信息

find 目录名 -name 文件名:检索指定目录下指定文件
mv 文件名 目标位置:  剪切
cp 文件名 目标位置:  复制
	使用绝对路径 : /+目录名
touch 文件名.后缀名 : 创建一个空文件

压缩文件扩展名:
	.tar :将多个文件打包成一个文件,内存变大(没有被压缩)
	.gz :将文件压缩
	.tar.gz :既打包,又压缩

tar -zcvf 压缩包名 指定文件名 :压缩文件
tar -xvf 压缩包名 -C 指定位置 :解压
	-z : 压缩(没有默认打包)
	-c : 新建压缩文件
	-v : 显示详情
	-f : 指定文件名
	-C : 指定解压目录

MyBatis

mybatis中如何批量添加数据?

在mapper文件中使用<foreach>标签来进行批量添加数据
    可以避免程序和数据库建立多次连接,从而增加服务器负荷。
<!--  遍历数组时
      collection="array"
      遍历集合时
      collection="集合名";
      -->
<insert id="AddRole">
   insert into user_role_tb values
   <foreach collection="ri" item="role_id" separator=",">
        (#{userid},#{role_id})
	</foreach>
</insert>

ES

es怎么用的,倒排索引原理

1.es会将文档逐个编号,存储到文档列表中,给每个编号创建索引
2.对文档中的数据按照算法做分词,得到一个个的词条,记录词条和词条出现的文档的编号、位置、频率信息,产生词汇表
3.词汇表中的每个词汇,会关文档id
4.进行查询时,将查询的条件内容拆分成词汇,去词汇表中匹配
5.根据匹配到的词汇得到关联的id,然后根据id值去文档列表中获取对应的文档内容

MongoDB

MongoDB分页查询实现方式是什么以及如何优化?


请简述几个你说了解的设计模式


什么是事务?并写一段必须使用事务的伪代码举例说明


一直猴子吃香蕉,每天吃一半后又多吃一个,直到第10天,吃完,请最初多少


nginx负载均衡


秒杀


maven的特性


消息中间件


服务注册中心


设计模式


手写spring框架


                   

这篇关于面试题总结的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程