面试题整理

news2024/11/18 13:27:01

面试题整理

一、Java基础

1、Java 语言有哪些特点

  1. 简单易学;

  2. 面向对象(封装,继承,多态);

  3. 平台无关性( Java 虚拟机实现平台无关性);

  4. 支持多线程( C++ 语言没有内置的多线程机制,因此必须调用操作系统的多线程功能来进行多线程程序设计,而 Java 语言却提供了多线程支持);

  5. 可靠性;

  6. 安全性;

  7. 支持网络编程并且很方便( Java 语言诞生本身就是为简化网络编程设计的,因此 Java 语言不仅支持网络编程而且很方便);

  8. 编译与解释并存;

2、JVM、JRE和JDK的关系

JVM
Java Virtual Machine是Java虚拟机,Java程序需要运行在虚拟机上,不同的平台有自己的虚拟机,因此Java语言可以实现跨平台。

JRE
Java Runtime Environment包括Java虚拟机和Java程序所需的核心类库等。核心类库主要是java.lang包:包含了运行Java程序必不可少的系统类,如基本数据类型、基本数学函数、字符串处理、线程、异常处理类等,系统缺省加载这个包

如果想要运行一个开发好的Java程序,计算机中只需要安装JRE即可。

JDK
Java Development Kit是提供给Java开发人员使用的,其中包含了Java的开发工具,也包括了JRE。所以安装了JDK,就无需再单独安装JRE了。其中的开发工具:编译工具(javac.exe),打包工具(jar.exe)等

image-20230205143341812

3、字符型常量和字符串常量的区别?

  1. 形式 : 字符常量是单引号引起的一个字符,字符串常量是双引号引起的 0 个或若干个字符
  2. 含义 : 字符常量相当于一个整型值( ASCII 值),可以参加表达式运算; 字符串常量代表一个地址值(该字符串在内存中存放位置)
  3. 占内存大小 : 字符常量只占 2 个字节; 字符串常量占若干个字节 (注意: char 在 Java 中占两个字节),

4、注释有哪几种

Java 中的注释有三种:

  1. 单行注释
  2. 多行注释
  3. 文档注释。

5、标识符和关键字的区别

在我们编写程序的时候,需要大量地为程序、类、变量、方法等取名字,于是就有了标识符,简单来说,标识符就是一个名字。但是有一些标识符,Java 语言已经赋予了其特殊的含义,只能用于特定的地方,这种特殊的标识符就是关键字。因此,关键字是被赋予特殊含义的标识符。比如,在我们的日常生活中 ,“警察局”这个名字已经被赋予了特殊的含义,所以如果你开一家店,店的名字不能叫“警察局”,“警察局”就是我们日常生活中的关键字。

Java 中常见的关键字

分类关键字
访问控制privateprotectedpublic
类,方法和变量修饰符abstractclassextendsfinalimplementsinterfacenative
newstaticstrictfpsynchronizedtransientvolatile
程序控制breakcontinuereturndowhileifelse
forinstanceofswitchcasedefault
错误处理trycatchthrowthrowsfinally
包相关importpackage
基本类型booleanbytechardoublefloatintlong
shortnulltruefalse
变量引用superthisvoid
保留字gotoconst

Final

(1) 类不能被继承

(2) 子类中,方法不能被重写,JVM会尝试将其关联。

(3) 变量为常量,编译阶段存储在常量池中。

Static

(1) 修饰的类可以直接通过类来调用,而不需要new

(2) 修饰的方法块,JVM会有限加载。

(3) 修饰的变量,分配在内存堆上,引用时指向同一个地质,而不会重新分配内存空间。

6、自增自减运算符

在写代码的过程中,常见的一种情况是需要某个整数类型变量增加 1 或减少 1,Java 提供了一种特殊的运算符,用于这种表达式,叫做自增运算符(++)和自减运算符(–)。

++ 和 – 运算符可以放在变量之前,也可以放在变量之后,当运算符放在变量之前时(前缀),先自增/减,再赋值;当运算符放在变量之后时(后缀),先赋值,再自增/减。例如,当 b = ++a 时,先自增(自己增加 1),再赋值(赋值给 b);当 b = a++ 时,先赋值(赋值给 b),再自增(自己增加 1)。也就是,++a 输出的是 a+1 的值,a++输出的是 a 值。用一句口诀就是:“符号在前就先加/减,符号在后就后加/减”。

7、continue、break 和 return 的区别

在循环结构中,当循环条件不满足或者循环次数达到要求时,循环会正常结束。但是,有时候可能需要在循环的过程中,当发生了某种条件之后 ,提前终止循环,这就需要用到下面几个关键词:

  1. continue :指跳出当前的这一次循环,继续下一次循环。
  2. break :指跳出整个循环体,继续执行循环下面的语句。

return 用于跳出所在方法,结束该方法的运行。return 一般有两种用法:

  1. return; :直接使用 return 结束方法执行,用于没有返回值函数的方法
  2. return value; :return 一个特定值,用于有返回值函数的方法

8、静态方法为什么不能调用非静态成员?

这个需要结合 JVM 的相关知识,主要原因如下:

  1. 静态方法是属于类的,在类加载的时候就会分配内存,可以通过类名直接访问。而非静态成员属于实例对象,只有在对象实例化之后才存在,需要通过类的实例对象去访问。

  2. 在类的非静态成员不存在的时候静态成员就已经存在了,此时调用在内存中还不存在的非静态成员,属于非法操作。

9、静态方法和实例方法有何不同?

1、调用方式

在外部调用静态方法时,可以使用 类名.方法名 的方式,也可以使用 对象.方法名 的方式,而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象

不过,需要注意的是一般不建议使用 对象.方法名 的方式来调用静态方法。这种方式非常容易造成混淆,静态方法不属于类的某个对象而是属于这个类。

因此,一般建议使用 类名.方法名 的方式来调用静态方法。

2、访问类成员是否存在限制

静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),不允许访问实例成员(即实例成员变量和实例方法),而实例方法不存在这个限制。

10、重载和重写的区别

重载就是同样的一个方法能够根据输入数据的不同,做出不同的处理

重写就是当子类继承自父类的相同方法,输入数据一样,但要做出有别于父类的响应时,你就要覆盖父类方法

11、== 和 equals() 的区别

== 对于基本类型和引用类型的作用效果是不同的:

  • 对于基本数据类型来说,== 比较的是值。
  • 对于引用数据类型来说,== 比较的是对象的内存地址。

因为 Java 只有值传递,所以,对于 == 来说,不管是比较基本数据类型,还是引用数据类型的变量,其本质比较的都是值,只是引用类型变量存的值是对象的地址。

equals() 不能用于判断基本数据类型的变量,只能用来判断两个对象是否相等。equals()方法存在于Object类中,而Object类是所有类的直接或间接父类。

12、hashCode() 与 equals()

hashCode() 的作用是获取哈希码(int 整数),也称为散列码。这个哈希码的作用是确定该对象在哈希表中的索引位置。

hashCode()定义在 JDK 的 Object 类中,这就意味着 Java 中的任何类都包含有 hashCode() 函数。另外需要注意的是: ObjecthashCode() 方法是本地方法,也就是用 C 语言或 C++ 实现的,该方法通常用来将对象的内存地址转换为整数之后返回。

散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)

那为什么两个对象有相同的 hashCode 值,它们也不一定是相等的?

  • 如果两个对象的hashCode 值相等,那这两个对象不一定相等(哈希碰撞)。

  • 如果两个对象的hashCode 值相等并且equals()方法返回 true,我们才认为这两个对象相等。

  • 如果两个对象的hashCode 值不相等,我们就可以直接认为这两个对象不相等。

13、为什么重写 equals() 时必须重写 hashCode() 方法?

因为两个相等的对象的 hashCode 值必须是相等。也就是说如果 equals 方法判断两个对象是相等的,那这两个对象的 hashCode 值也要相等。

如果重写 equals() 时没有重写 hashCode() 方法的话就可能会导致 equals 方法判断是相等的两个对象,hashCode 值却不相等。

思考 :重写 equals() 时没有重写 hashCode() 方法的话,使用 HashMap 可能会出现什么问题。

总结

  • equals 方法判断两个对象是相等的,那这两个对象的 hashCode 值也要相等。

  • 两个对象有相同的 hashCode 值,他们也不一定是相等的(哈希碰撞)。

     java规定:
    如果两个对象的hashCode()相等,那么他们的equals()不一定相等。
    如果两个对象的equals()相等,那么他们的hashCode()必定相等。
    

14、基本数据类型

Java 中有 8 种基本数据类型,分别为:

  1. 6 种数字类型 :byteshortintlongfloatdouble
  2. 1 种字符类型:char
  3. 1 种布尔型:boolean

这 8 种基本数据类型的默认值以及所占空间的大小如下:

基本类型位数字节默认值
int3240
short1620
long6480L
byte810
char162‘u0000’
float3240f
double6480d
boolean1false

另外,对于 boolean,官方文档未明确定义,它依赖于 JVM 厂商的具体实现。逻辑上理解是占用 1 位,但是实际中会考虑计算机高效存储因素。

注意:

  1. Java 里使用 long 类型的数据一定要在数值后面加上 L,否则将作为整型解析。
  2. char a = 'h'char :单引号,String a = "hello" :双引号。

这八种基本类型都有对应的包装类分别为:ByteShortIntegerLongFloatDoubleCharacterBoolean

包装类型不赋值就是 Null ,而基本类型有默认值且不是 Null

另外,这个问题建议还可以先从 JVM 层面来分析。

基本数据类型直接存放在 Java 虚拟机栈中的局部变量表中,而包装类型属于对象类型,我们知道对象实例都存在于堆中。相比于对象类型, 基本数据类型占用的空间非常小。

15、包装类型的常量池技术

Java 基本类型的包装类的大部分都实现了常量池技术。

Byte,Short,Integer,Long 这 4 种包装类默认创建了数值 [-128,127] 的相应类型的缓存数据,Character 创建了数值在 [0,127] 范围的缓存数据,Boolean 直接返回 True or False

所有整型包装类对象之间值的比较,全部使用 equals 方法比较

16、内部类

什么是内部类?

在Java中,可以将一个类的定义放在另外一个类的定义内部,这就是内部类。内部类本身就是类的一个属性,与其他属性定义方式一致。

内部类的分类

内部类可以分为四种:成员内部类、局部内部类、匿名内部类和静态内部类

静态内部类

定义在类内部的静态类,就是静态内部类。

成员内部类

定义在类内部,成员位置上的非静态类,就是成员内部类。

局部内部类

定义在方法中的内部类,就是局部内部类。

匿名内部类

匿名内部类就是没有名字的内部类,日常开发中使用的比较多。

除了没有名字,匿名内部类还有以下特点:

  • 匿名内部类必须继承一个抽象类或者实现一个接口。
  • 匿名内部类不能定义任何静态成员和静态方法。
  • 当所在的方法的形参需要被匿名内部类使用时,必须声明为 final。
  • 匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。

内部类的优点

我们为什么要使用内部类呢?因为它有以下优点:

  • 一个内部类对象可以访问创建它的外部类对象的内容,包括私有数据!
  • 内部类不为同一包的其他类所见,具有很好的封装性;
  • 内部类有效实现了“多重继承”,优化 java 单继承的缺陷。
  • 匿名内部类可以很方便的定义回调。

为什么局部内部类和厦名内部类只能访问局部final变量?

因为局部内部类和匿名内部类是在方法体内部定义的,而局部变量是在方法体内部定义的,而且局部变量的生命周期只在方法体内部,当方法体执行完毕后,局部变量就会被销毁,而局部内部类和匿名内部类可能会在方法体外部使用,所以为了保证局部内部类和匿名内部类可以正常使用,只能访问局部final变量,因为final变量的生命周期比局部变量更长,所以可以保证局部内部类和匿名内部类可以正常使用。

17、成员变量与局部变量的区别有哪些?

  • 语法形式 :从语法形式上看,成员变量是属于类的,而局部变量是在代码块或方法中定义的变量或是方法的参数;成员变量可以被 public,private,static 等修饰符所修饰,而局部变量不能被访问控制修饰符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰。

  • 存储方式 :从变量在内存中的存储方式来看,如果成员变量是使用 static 修饰的,那么这个成员变量是属于类的,如果没有使用 static 修饰,这个成员变量是属于实例的。而对象存在于堆内存,局部变量则存在于栈内存。

  • 生存时间 :从变量在内存中的生存时间上看,成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动消失。

  • 默认值 :从变量是否有默认值来看,成员变量如果没有被赋初,则会自动以类型的默认值而赋值(一种情况例外:被 final 修饰的成员变量也必须显式地赋值),而局部变量则不会自动赋值。

18、创建一个对象用什么运算符?对象实体与对象引用有何不同?

new 运算符,new 创建对象实例(对象实例在堆内存中),对象引用指向对象实例(对象引用存放在栈内存中)。

一个对象引用可以指向 0 个或 1 个对象(一根绳子可以不系气球,也可以系一个气球);一个对象可以有 n 个引用指向它(可以用 n 条绳子系住一个气球)。

19、对象的相等与指向他们的引用相等,两者有什么不同?

  • 对象的相等一般比较的是内存中存放的内容是否相等。

  • 引用相等一般比较的是他们指向的内存地址是否相等。

20、一个类的构造方法的作用是什么? 若一个类没有声明构造方法,该程序能正确执行吗?

构造方法主要作用是完成对类对象的初始化工作。

如果一个类没有声明构造方法,也可以执行!因为一个类即使没有声明构造方法也会有默认的不带参数的构造方法。如果我们自己添加了类的构造方法(无论是否有参),Java 就不会再添加默认的无参数的构造方法了,这时候,就不能直接 new 一个对象而不传递参数了,所以我们一直在不知不觉地使用构造方法,这也是为什么我们在创建对象的时候后面要加一个括号(因为要调用无参的构造方法)。如果我们重载了有参的构造方法,记得都要把无参的构造方法也写出来(无论是否用到),因为这可以帮助我们在创建对象的时候少踩坑。

21、构造方法有哪些特点?是否可被 override?

构造方法特点如下:

  • 名字与类名相同。
  • 没有返回值,但不能用 void 声明构造函数。
  • 生成类的对象时自动执行,无需调用。

构造方法不能被 override(重写),但是可以 overload(重载),所以你可以看到一个类中有多个构造函数的情况。

22、面向对象三大特征

封装

封装是指把一个对象的状态信息(也就是属性)隐藏在对象内部,不允许外部对象直接访问对象的内部信息。但是可以提供一些可以被外界访问的方法来操作属性。就好像我们看不到挂在墙上的空调的内部的零件信息(也就是属性),但是可以通过遥控器(方法)来控制空调。如果属性不想被外界访问,我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了。就好像如果没有空调遥控器,那么我们就无法操控空凋制冷,空调本身就没有意义了(当然现在还有很多其他方法 ,这里只是为了举例子)。

继承

不同类型的对象,相互之间经常有一定数量的共同点。例如,小明同学、小红同学、小李同学,都共享学生的特性(班级、学号等)。同时,每一个对象还定义了额外的特性使得他们与众不同。例如小明的数学比较好,小红的性格惹人喜爱;小李的力气比较大。继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承,可以快速地创建新的类,可以提高代码的重用,程序的可维护性,节省大量创建新类的时间 ,提高我们的开发效率。

关于继承如下 3 点请记住:

  1. 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,只是拥有
  2. 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
  3. 子类可以用自己的方式实现父类的方法。(以后介绍)。

多态

多态,顾名思义,表示一个对象具有多种的状态,具体表现为父类的引用指向子类的实例。

多态的特点:

  • 对象类型和引用类型之间具有继承(类)/实现(接口)的关系;
  • 引用类型变量发出的方法调用的到底是哪个类中的方法,必须在程序运行期间才能确定;
  • 多态不能调用“只在子类存在但在父类不存在”的方法;
  • 如果子类重写了父类的方法,真正执行的是子类覆盖的方法,如果子类没有覆盖父类的方法,执行的是父类的方法。

面向对象五大基本原则是什么

  • 单一职责原则SRP(Single Responsibility Principle)
    类的功能要单一,不能包罗万象,跟杂货铺似的。
  • 开放封闭原则OCP(Open-Close Principle)
    一个模块对于拓展是开放的,对于修改是封闭的,想要增加功能热烈欢迎,想要修改,哼,一万个不乐意。
  • 里式替换原则LSP(the Liskov Substitution Principle LSP)
    子类可以替换父类出现在父类能够出现的任何地方。比如你能代表你爸去你姥姥家干活。哈哈~~
  • 依赖倒置原则DIP(the Dependency Inversion Principle DIP)
    高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。抽象不应该依赖于具体实现,具体实现应该依赖于抽象。就是你出国要说你是中国人,而不能说你是哪个村子的。比如说中国人是抽象的,下面有具体的xx省,xx市,xx县。你要依赖的抽象是中国人,而不是你是xx村的。
  • 接口分离原则ISP(the Interface Segregation Principle ISP)
    设计时采用多个与特定客户类有关的接口比采用一个通用的接口要好。就比如一个手机拥有打电话,看视频,玩游戏等功能,把这几个功能拆分成不同的接口,比在一个接口里要好的多。

23、深拷贝和浅拷贝区别了解吗?什么是引用拷贝?

  • 浅拷贝:浅拷贝会在堆上创建一个新的对象(区别于引用拷贝的一点),不过,如果原对象内部的属性是引用类型的话,浅拷贝会直接复制内部对象的引用地址,也就是说拷贝对象和原对象共用同一个内部对象。

  • 深拷贝 :深拷贝会完全复制整个对象,包括这个对象所包含的内部对象。

  • 引用拷贝: 两个不同的引用指向同一个对象。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-34d6hOdk-1676522455028)(https://snailclimb.gitee.io/javaguide/docs/java/basis/images/shallow&deep-copy.png)]

24、String、StringBuffer、StringBuilder 的区别?String 为什么是不可变的?

可变性

String内部的value值是final修饰的,所以它是不可变类。所以每次修改String的值,都会产生一个新的对象。StringBuffer和StringBuilder是可变类,字符串的变更不会产生新的对象。

线程安全性

String是不可变类,所以它是线程安全的。StringBuffer是线程安全的,因为它每个操作方法都加了synchronized同步关键字。StringBuilder不是线程安全的,所以在多线程环境下对字符串进行操作,应该使用StringBuffer,否则使用StringBuilder

性能

String的性能是最的低的,因为不可变意味着在做字符串拼接和修改的时候,需要重新创建新的对象以及分配内存。其次是StringBuffer要比String性能高,因为它的可变性使得字符串可以直接被修改最后是StringBuilder,它比StringBuffer的性能高,因为StringBuffer加了同步锁。

存储方面

String存储在字符串常量池里面StringBuffer和StringBuilder存储在堆内存空间。

总结:

  1. 操作少量的数据: 适用 String

  2. 单线程操作字符串缓冲区下操作大量数据: 适用 StringBuilder

  3. 多线程操作字符串缓冲区下操作大量数据: 适用 StringBuffer

25、字符串拼接用“+” 还是 StringBuilder?

Java 语言本身并不支持运算符重载,“+”和“+=”是专门为 String 类重载过的运算符,也是 Java 中仅有的两个重载过的元素符。

对象引用和“+”的字符串拼接方式,实际上是通过 StringBuilder 调用 append() 方法实现的,拼接完成之后调用 toString() 得到一个 String 对象 。

不过,在循环内使用“+”进行字符串的拼接的话,存在比较明显的缺陷:编译器不会创建单个 StringBuilder 以复用,会导致创建过多的 StringBuilder 对象。如果直接使用 StringBuilder 对象进行字符串拼接的话,就不会存在这个问题了。

26、字符串常量池的作用了解吗?

字符串常量池 是 JVM 为了提升性能和减少内存消耗针对字符串(String 类)专门开辟的一块区域,主要目的是为了避免字符串的重复创建。

String aa = "ab"; // 放在常量池中
String bb = "ab"; // 从常量池中查找
System.out.println(aa==bb);// true

27、final

final 有什么用?

用于修饰类、属性和方法;

  • 被final修饰的类不可以被继承
  • 被final修饰的方法不可以被重写
  • 被final修饰的变量不可以被改变,被final修饰不可变的是变量的引用,而不是引用指向的内容,引用指向的内容是可以改变的

final finally finalize区别

  • final可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表示该变量是一个常量不能被重新赋值。
  • finally一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码方法finally代码块中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。
  • finalize是一个方法,属于Object类的一个方法,而Object类是所有类的父类,该方法一般由垃圾回收器来调用,当我们调用System.gc() 方法的时候,由垃圾回收器调用finalize(),回收垃圾,一个对象是否可回收的最后判断。

28、泛型

Java 泛型(generics) 是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。

Java 的泛型是伪泛型,这是因为 Java 在运行期间,所有的泛型信息都会被擦掉,这也就是通常所说类型擦除 。

泛型的分类

泛型一般有三种使用方式: 泛型类、泛型接口、泛型方法。

1.泛型类

//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
//在实例化泛型类时,必须指定T的具体类型
public class Generic<T> {
    private T key;
    public Generic(T key) {
        this.key = key;
    }
    public T getKey() {
        return key;
    }
}Copy to clipboardErrorCopied

如何实例化泛型类:

Generic<Integer> genericInteger = new Generic<Integer>(123456);Copy to clipboardErrorCopied

2.泛型接口

public interface Generator<T> {
    public T method();
}Copy to clipboardErrorCopied

实现泛型接口,不指定类型:

class GeneratorImpl<T> implements Generator<T>{
    @Override
    public T method() {
        return null;
    }
}Copy to clipboardErrorCopied

实现泛型接口,指定类型:

class GeneratorImpl implements Generator<String>{
    @Override
    public String method() {
        return "hello";
    }
}Copy to clipboardErrorCopied

3.泛型方法

public static <E> void printArray(E[] inputArray) {
    for (E element : inputArray) {
        System.out.printf("%s ", element);
    }
    System.out.println();
}Copy to clipboardErrorCopied

使用:

// 创建不同类型数组: Integer, Double 和 Character
Integer[] intArray = { 1, 2, 3 };
String[] stringArray = { "Hello", "World" };
printArray(intArray);
printArray(stringArray);Copy to clipboardErrorCopied

常用的通配符有哪些?

常用的通配符为: T,E,K,V,?

  • ? 表示不确定的 Java 类型
  • T (type) 表示具体的一个 Java 类型
  • K V (key value) 分别代表 Java 键值中的 Key Value
  • E (element) 代表 Element

你的项目中哪里用到了泛型?

  • 可用于定义通用返回结果 CommonResult<T> 通过参数 T 可根据具体的返回类型动态指定结果的数据类型

  • 定义 Excel 处理类 ExcelUtil<T> 用于动态指定 Excel 导出的数据类型

  • 用于构建集合工具类。参考 Collections 中的 sort, binarySearch 方法

29、反射

1、谈谈你对反射的理解?

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

2、反射机制优缺点

  • 优点
    • 增加程序的灵活性,可以在运行的过程中动态对类进行修改和操作
    • 提高代码的复用率,比如动态代理,就是用到了反射来实现
    • 可以在运行时轻松获取任意一个类的方法、属性,并且还能通过反射进行
    • 动态调用
  • 缺点
    • 反射会涉及到动态类型的解析,所以JVM无法对这些代码进行优化,导致性能要比非反射调用更低。
    • 使用反射以后,代码的可读性会下降
    • 反射可以绕过一些限制访问的属性或者方法,可能会导致破坏了代码本身的抽象性

3、反射的应用场景

①我们在使用JDBC连接数据库时使用Class.forName()通过反射加载数据库的驱动程序;

②Spring框架也用到很多反射机制, 经典的就是xml的配置模式。Spring 通过 XML 配置模式装载 Bean 的过程:1) 将程序内所有 XML 或 Properties 配置文件加载入内存中; 2)Java类里面解析xml或properties里面的内容,得到对应实体类的字节码字符串以及相关的属性信息; 3)使用反射机制,根据这个字符串获得某个类的Class实例; 4)动态配置实例的属性

另外,像 Java 中的一大利器 注解 的实现也用到了反射。

为什么你使用 Spring 的时候 ,一个@Component注解就声明了一个类为 Spring Bean 呢?为什么你通过一个 @Value注解就读取到配置文件中的值呢?究竟是怎么起作用的呢?

这些都是因为你可以基于反射分析类,然后获取到类/属性/方法/方法的参数上的注解。你获取到注解之后,就可以做进一步的处理。

30、注解

Annontation (注解) 是Java5 开始引入的新特性,可以看作是一种特殊的注释,主要用于修饰类、方法或者变量。

注解本质是一个继承了Annotation 的特殊接口:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {

}

public interface Override extends Annotation{
    
}Copy to clipboardErrorCopied

注解只有被解析之后才会生效,常见的解析方法有两种:

  • 编译期直接扫描 :编译器在编译 Java 代码的时候扫描对应的注解并处理,比如某个方法使用@Override 注解,编译器在编译的时候就会检测当前的方法是否重写了父类对应的方法。
  • 运行期通过反射处理 :像框架中自带的注解(比如 Spring 框架的 @Value@Component)都是通过反射来进行处理的。

JDK 提供了很多内置的注解(比如 @Override@Deprecated),同时,我们还可以自定义注解。

31、I/O

什么是序列化?什么是反序列化?

如果我们需要持久化 Java 对象比如将 Java 对象保存在文件中,或者在网络传输 Java 对象,这些场景都需要用到序列化。

简单来说:

  • 序列化: 将数据结构或对象转换成二进制字节流的过程
  • 反序列化:将在序列化过程中所生成的二进制字节流转换成数据结构或者对象的过程

对于 Java 这种面向对象编程语言来说,我们序列化的都是对象(Object)也就是实例化后的类(Class),但是在 C++这种半面向对象的语言中,struct(结构体)定义的是数据结构类型,而 class 对应的是对象类型。

维基百科是如是介绍序列化的:

序列化(serialization)在计算机科学的数据处理中,是指将数据结构或对象状态转换成可取用格式(例如存成文件,存于缓冲,或经由网络中发送),以留待后续在相同或另一台计算机环境中,能恢复原先状态的过程。依照序列化格式重新获取字节的结果时,可以利用它来产生与原始对象相同语义的副本。对于许多对象,像是使用大量引用的复杂对象,这种序列化重建的过程并不容易。面向对象中的对象序列化,并不概括之前原始对象所关系的函数。这种过程也称为对象编组(marshalling)。从一系列字节提取数据结构的反向操作,是反序列化(也称为解编组、deserialization、unmarshalling)。

综上:序列化的主要目的是通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中。

img

Java 序列化中如果有些字段不想进行序列化,怎么办?

对于不想进行序列化的变量,使用 transient 关键字修饰。

transient 关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复。

关于 transient 还有几点注意:

  • transient 只能修饰变量,不能修饰类和方法。
  • transient 修饰的变量,在反序列化后变量值将会被置成类型的默认值。例如,如果是修饰 int 类型,那么反序列后结果就是 0
  • static 变量因为不属于任何对象(Object),所以无论有没有 transient 关键字修饰,均不会被序列化。

获取用键盘输入常用的两种方法

方法 1:通过 Scanner

Scanner input = new Scanner(System.in);
String s  = input.nextLine();
input.close();Copy to clipboardErrorCopied

方法 2:通过 BufferedReader

BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
String s = input.readLine();Copy to clipboardErrorCopied

Java 中 IO 流分为几种?

  • 按照流的流向分,可以分为输入流和输出流;
  • 按照操作单元划分,可以划分为字节流和字符流;
  • 按照流的角色划分为节点流和处理流。

Java IO 流共涉及 40 多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系, Java IO 流的 40 多个类都是从如下 4 个抽象类基类中派生出来的。

  • InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
  • OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。

按操作方式分类结构图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x99o3fi0-1676522455030)(https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/IO-%E6%93%8D%E4%BD%9C%E6%96%B9%E5%BC%8F%E5%88%86%E7%B1%BB.png)]

按操作对象分类结构图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oS1276nI-1676522455031)(https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/IO-%E6%93%8D%E4%BD%9C%E5%AF%B9%E8%B1%A1%E5%88%86%E7%B1%BB.png)]

既然有了字节流,为什么还要有字符流?

问题本质想问:不管是文件读写还是网络发送接收,信息的最小存储单元都是字节,那为什么 I/O 流操作要分为字节流操作和字符流操作呢?

回答:字符流是由 Java 虚拟机将字节转换得到的,问题就出在这个过程还算是非常耗时,并且,如果我们不知道编码类型就很容易出现乱码问题。所以, I/O 流就干脆提供了一个直接操作字符的接口,方便我们平时对字符进行流操作。如果音频文件、图片等媒体文件用字节流比较好,如果涉及到字符的话使用字符流比较好

32、Java 中 3 种常见 IO 模型

BIO (Blocking I/O)

BIO 属于同步阻塞 IO 模型

同步阻塞 IO 模型中,应用程序发起 read 调用后,会一直阻塞,直到内核把数据拷贝到用户空间。

在客户端连接数量不高的情况下,是没问题的。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。

NIO (Non-blocking/New I/O)

Java 中的 NIO 于 Java 1.4 中引入,对应 java.nio 包,提供了 Channel , SelectorBuffer 等抽象。NIO 中的 N 可以理解为 Non-blocking,不单纯是 New。它支持面向缓冲的,基于通道的 I/O 操作方法。 对于高负载、高并发的(网络)应用,应使用 NIO 。

Java 中的 NIO 可以看作是 I/O 多路复用模型。也有很多人认为,Java 中的 NIO 属于同步非阻塞 IO 模型。

我们先来看看 同步非阻塞 IO 模型

同步非阻塞 IO 模型中,应用程序会一直发起 read 调用,等待数据从内核空间拷贝到用户空间的这段时间里,线程依然是阻塞的,直到在内核把数据拷贝到用户空间。

相比于同步阻塞 IO 模型,同步非阻塞 IO 模型确实有了很大改进。通过轮询操作,避免了一直阻塞。

但是,这种 IO 模型同样存在问题:应用程序不断进行 I/O 系统调用轮询数据是否已经准备好的过程是十分消耗 CPU 资源的。

这个时候,I/O 多路复用模型 就上场了。

IO 多路复用模型中,线程首先发起 select 调用,询问内核数据是否准备就绪,等内核把数据准备好了,用户线程再发起 read 调用。read 调用的过程(数据从内核空间->用户空间)还是阻塞的。

目前支持 IO 多路复用的系统调用,有 select,epoll 等等。select 系统调用,是目前几乎在所有的操作系统上都有支持

  • select 调用 :内核提供的系统调用,它支持一次查询多个系统调用的可用状态。几乎所有的操作系统都支持。
  • epoll 调用 :linux 2.6 内核,属于 select 调用的增强版本,优化了 IO 的执行效率。

IO 多路复用模型,通过减少无效的系统调用,减少了对 CPU 资源的消耗。

Java 中的 NIO ,有一个非常重要的选择器 ( Selector ) 的概念,也可以被称为 多路复用器。通过它,只需要一个线程便可以管理多个客户端连接。当客户端数据到了之后,才会为其服务。

AIO

也就是 NIO 2。Java 7 中引入了 NIO 的改进版 NIO 2,它是异步 IO 模型。

异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。

目前来说 AIO 的应用还不是很广泛。Netty 之前也尝试使用过 AIO,不过又放弃了。这是因为,Netty 使用了 AIO 之后,在 Linux 系统上的性能并没有多少提升。

最后,来一张图,简单总结一下 Java 中的 BIO、NIO、AIO。

img

33、

34、&和&&的区别

&是位运算符。&&是布尔逻辑运算符,在进行逻辑判断时用&处理的前面为false后面的内容仍需处理,用&&处理的前面为false不再处理后面的内容。

35、抽象类和接口的区别?

抽象类:

  1. 抽象方法,只有行为的概念,没有具体的行为实现。使用abstract关键字修饰,没有方法体。子类必须重写这些抽象方法。
  2. 包含抽象方法的类,一定是抽象类。
  3. 抽象类只能被继承,一个类只能继承一个抽象类。

接口:

  1. 全部的方法都是抽象方法,属型都是常量

  2. 不能实例化,可以定义变量。

  3. 接口变量可以引用具体实现类的实例

  4. 接口只能被实现,一个具体类实现接口,必须实现全部的抽象方法

  5. 接口之间可以多实现

  6. 一个具体类可以实现多个接口,实现多继承现象

36、this关键字的用法

this是自身的一个对象,代表对象本身,可以理解为:指向对象本身的一个指针。

this的用法在java中大体可以分为3种:

1.普通的直接引用,this相当于是指向当前对象本身。

2.形参与成员名字重名,用this来区分

3.引用本类的构造函数

37、super关键字的用法

super可以理解为是指向自己超(父)类对象的一个指针,而这个超类指的是离自己最近的一个父类。

super也有三种用法:

1.普通的直接引用

​ 与this类似,super相当于是指向当前对象的父类的引用,这样就可以用super.xxx来引用父类的成员。

2.子类中的成员变量或方法与父类中的成员变量或方法同名时,用super进行区分

3、引用父类构造函数

  • super(参数):调用父类中的某一个构造函数(应该为构造函数中的第一条语句)。
  • this(参数):调用本类中另一种形式的构造函数(应该为构造函数中的第一条语句)。

38、是否了解连接池,使用连接池有什么好处?

数据库连接是非常消耗资源的,影响到程序的性能指标。连接池是用来分配、管理、释放数据库连接的,可以使应用程序重复使用同一个数据库连接,而不是每次都创建一个新的数据库连接。通过释放空闲时间较长的数据库连接避免数据库因为创建太多的连接而造成的连接遗漏问题,提高了程序性能。

39、hashCode()与equals()的相关规定

image-20230205212750204

二、Java异常面试题

1、异常的含义

Java提供的一种识别及响应错误的一致性机制。

2、异常的作用

使程序中异常处理代码和正常业务代码分离,保证代码更加优雅,提高程序健壮性。

异常类型:“什么被抛出”;异常堆栈:“在哪儿抛出”;异常信息:“为什么会抛出”。

3、运行时异常与一般异常

运行时异常一般异常
虚拟机的通常操作中可能遇到的异常程序运行过程中可能出现的非正常状态
Java编译器没有要求必须声明抛出Java编译器要求方法必须声明抛出可能发生的非运行时异常

4、 Error 和 Exception 区别是什么?

  • Error 类型的错误通常为虚拟机相关错误,如系统崩溃,内存不足,堆栈溢出等,编译器不会对这类错误进行检测,JAVA 应用程序也不应对这类错误进行捕获,一旦这类错误发生,通常应用程序会被终止,仅靠应用程序本身无法恢复;
  • Exception 类的错误是可以在应用程序中进行捕获并处理的,通常遇到这种错误,应对其进行处理,使应用程序可以继续正常运行。

5、throw 和 throws 的区别是什么?

  • throw 关键字用在方法内部,只能用于抛出一种异常,用来抛出方法或代码块中的异常,受查异常和非受查异常都可以被抛出。
  • throws 关键字用在方法声明上,可以抛出多个异常,用来标识该方法可能抛出的异常列表。一个方法用 throws 标识了可能抛出的异常列表,调用该方法的方法中必须包含可处理异常的代码,否则也要在方法签名中用 throws 关键字声明相应的异常。

6、JVM如何处理异常

在一个方法中发生异常,这个方法会创建一个异常对象,转交给JVM(抛出)。可能有一系列的方法调用,最终才进入抛出异常的方法,这一系列的方法调用的有序列表叫做调用栈。

JVM会顺着调用栈去查看是否有可以处理异常的代码,有则调用。若没有,则将异常转交给默认的异常处理器,默认异常处理器打印出异常信息并终止应用程序。

7、Java异常关键字

try:监听,监听包含的代码块,若发生异常则抛出。

catch:捕获异常,捕获try抛出的异常。

finally:语句块总会被执行,用于回收在try块里打开的物力资源(如数据库连接、网络连接、磁盘文件)。

8、final、finally、finalize 有什么区别?

  • final可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表示该变量是一个常量不能被重新赋值。
  • finally一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码方法finally代码块中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。
  • finalize是一个方法,属于Object类的一个方法,而Object类是所有类的父类,Java 中允许使用 finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。

9、try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?

答:会执行,在 return 前执行。

注意 :

在 finally 中改变返回值的做法是不好的,因为如果存在 finally 代码块,try中的 return 语句不会立马返回调用者,而是记录下返回值待 finally 代码块执行完毕之后再向调用者返回其值,然后如果在 finally 中修改了返回值,就会返回修改后的值。

10、 Java常见异常有哪些

image-20230205214256411

三、Java集合面试题

1、集合概述:

用于存储数据的容器。

2、集合框架:

集合框架是为表示和操作集合而规定的一种统一的标准的体系结构。

任何集合框架都包含三大块内容:对外的接口、接口的实现和对集合运算的算法。

3、集合和数组的区别:

数组集合
长度固定可变
存储类型基本数据类型、引用数据类型引用数据类型
存储约束必须存储想同数据类型可以存储不同数据类型

4、List、Set两者的区别

List、Set都继承Collection,都是单链集合

ListSet
无序无重复无索引有序有重复有索引
可存入多个null只可存入一个null
可以通过for循环、迭代器遍历只能通过迭代器遍历
检索效率高检索效率低
插入删除效率低,因为会引起其他元素位置变化插入删除效率高

5、HashMap、HashTable、HashSet

Map
键值对集合,存储间、值之间的映射
Key无序且唯一
value不要求有序,可重复
常用接口为HashMap、HashTable
HashMapHashTabale
性能
线程不安全安全
可否存null键和值都可为null不可
迭代器不同
HashMapHashSet
实现接口MapSet
存储键值对对象
添加元素put()add()
hashCode计算使用建计算HashCode使用成员对象计算HashCode,相同返回true
性能快,使用唯一的键获取对象

6、HashMap的存储原理

定义:基于哈希表(关联数组)实现的Map。

原理:

基于Hash算法,通过put存储,get取值。传入key值时,根据key.hashCode()计算出hash值,再根据hash值将value保存到bucket中。

优先使用数组存储。当计算的hash值相同时,也就是hash冲突。此时转换为链表存储hash值相同的value。当链表长度大于8时,为提高寻址速度,改为红黑树存储。

7、迭代器lterator

作用:提供遍历任何Collection的接口

特点:单向遍历,更加安全。若在遍历时集合元素被更改,就会抛出异常。

8、ArrayList、LinkedList、Vector

ArrayListLinkedListVector
结构基于动态数组基于链表基于动态数组
擅长访问(get、set)移动数据新增、删除(add、remove)移动指针
线程不安全不安全安全
性能

9、集合框架底层数据结构

  • Collection
    1. List
      • Arraylist: Object数组
      • LinkedList: 双向循环链表
      • Vector: Object数组
    2. Set
      • HashSet(无序,唯一):基于 HashMap 实现的,底层采用 HashMap 来保存元素
    3. LinkedHashSet: LinkedHashSet 继承与 HashSet,并且其内部是通过 LinkedHashMap 来实现的。有点类似于我们之前说的LinkedHashMap 其内部是基于 Hashmap 实现一样,不过还是有一点点区别的。
    4. TreeSet(有序,唯一): 红黑树(自平衡的排序二叉树。)
  • Map
    • HashMap: JDK1.8之前HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突).JDK1.8以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间
    • LinkedHashMap:LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。
    • HashTable: 数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的
    • TreeMap: 红黑树(自平衡的排序二叉树

10、怎么确保一个集合不能被修改?

image-20230205211501596

12、迭代器 Iterator 是什么?

  • Iterator 接口提供遍历任何 Collection 的接口。我们可以从一个 Collection 中使用迭代器方法来获取迭代器实例。迭代器取代了 Java 集合框架中的 Enumeration,迭代器允许调用者在迭代过程中移除元素。
  • 因为所有Collection接口继承了Iterator迭代器

13、Iterator 和 ListIterator 有什么区别?

  • Iterator 可以遍历 Set 和 List 集合,而 ListIterator 只能遍历 List。
  • Iterator 只能单向遍历,而 ListIterator 可以双向遍历(向前/后遍历)。
  • ListIterator 实现 Iterator 接口,然后添加了一些额外的功能,比如添加一个元素、替换一个元素、获取前面或后面元素的索引位置。

14、List和Set的区别

  • List: 有序,按对象进入的顺序保存对象,可重复,允许多个Null元素对象,可以使用Iterator取出所有元素,在逐个遍历,还可以使用get(int index)获取指定下标的元素
  • Set: 无序,不可重复,最够允许有一个Null元素对象,取元素时只能用Iterator接口取得所有元素,在逐一遍历各个元素

15、ArrayList和LinkedList的区别

image-20230205212453694

16、HashMap和hasptable有什么区别

  1. 从功能特性的角度来说HashTable是线程安全的,而HashMap不是。
  2. HashMap的性能要比HashTable更好,因为,HashTable采用了全局同步锁来保证安全性,对性能影响较大
  3. 从内部实现的角度来说HashTable使用数组加链表、HashMap采用了数组+链表+红黑树。
  4. HashMap初始容量是16、HashTable初始容量是11
  5. HashMap可以使用null作为key,HashMap会把null转化为0进行存储,而Hashtable不允许。
  6. 最后,他们两个的key的散列算法不同,HashTable直接是使用key的hashcode对数组长度做取模。而HashMap对key的hashcode做了二次散列,从而避免key的分布不均匀问题影响到查询性能。

image-20230205212132766

17、说一下HashMap的Put方法

先说HashMap的Put方法的大体流程:

  1. 根据Key通过哈希算法与与运算得出数组下标
  2. 如果数组下标位置元素为空,则将key和value封装为Entry对象 (JDK1.7中是Entry对象, JDK1.8中是Node对象)并放入该位置
  3. 如果数组下标位置元素不为空,则要分情况讨论
    1. 如果是JDK1.7,则先判断是否需要扩容,如果要扩容就进行扩容,如果不用扩容就生成Entry对象,并使用头插法添加到当前位置的链表中
      1. 如果是JDK1.8,则会先判断当前位置上的Node的类型,看是红黑树Node,还是链表Node
      2. 如果是红黑树Node,则将key和value封装为一个红黑树节点并添加到红黑树中去,在这个过程中会判断红黑树中是否存在当前key,如果存在则更新value
      3. 如果此位置上的Node对象是链表节点,则将key和value封装为一 个链表Node并通过尾插法插入到链表的最后位置去,因为是尾插法,所以需要遍历链表,在
        遍历链表的过程中会判断是否存在当前key,如果存在则更新vatue,当遍历完链表后,将新链表Node插入到链表中,插入到链表后,会看当前链表的节点个
        数,如果大于等于8,那么则会将该链表转成红黑树
      4. 将key和value封装为Node插入到链表或红黑树中后,再判断是否需要进行扩容,如果需要就扩容,如果不需要就结束PUT方法

18、HashMap的扩容机制原理

1.7版本

1.先生成新数组
2.遍历原来数组中的每个位置上的链表上的每个元素
3.取每个元素 的key,并基于新数组长度,计算出每个元素在新数组中的下标
4.将元素添加到新数组中去
5.所有元素转移完了之后,将新数组赋值给HashMap对象的table属性

1.8版本

  1. 先生成新数组
  2. 遍历原来数组中的每个位置上的链表或红黑树
  3. 如果是链表,则直接将链表中的每个元索重新计算下标,并添加到新数组中去
  4. 如果是红黑树,则先遍历红黑树,先计算出红黑树中每个元索元素在新数组中的下标位置。
    1. 统计每个下标位置的元素个数
    2. 如果该位置下的元素个数超过了8,则生成一个新的红黑树,并将根节点的添加到新数组的对应位置
    3. 如果该位置下的元素个数没有超过8,那么则生成一个链表, 并将链表的头节点添加到新数组的对应位置

5.所有元素转移完了之后,将新数组赋值给HashMap对象的table属性

19、说一下 ArrayList 的优缺点

  • ArrayList的优点如下:

    • ArrayList 底层以数组实现,是一种随机访问模式。ArrayList 实现了 RandomAccess 接口,因此查找的时候非常快。
    • ArrayList 在顺序添加一个元素的时候非常方便。
  • ArrayList 的缺点如下:

    • 删除元素的时候,需要做一次元素复制操作。如果要复制的元素很多,那么就会比较耗费性能。
    • 插入元素的时候,也需要做一次元素复制操作,缺点同上。
    • ArrayList 比较适合顺序添加、随机访问的场景。

    ArrayList的扩容机制

    默认情况下,新的容量会是原容量的1.5倍。 新容量=旧容量右移一位(相当于除于2)在加上旧容量

    ArrayList 的底层是用动态数组来实现的。我们初始化一个ArrayList 集合还没有添加元素时,其实它是个空数组,只有当我们添加第一个元素时,内部会调用扩容方法并返回最小容量10,也就是说ArrayList 初始化容量为10。 当前数组长度小于最小容量的长度时(前期容量是10,当添加第11个元素时就就扩容),便开始可以扩容了,ArrayList 扩容的真正计算是在一个grow()里面,新数组大小是旧数组的1.5倍,如果扩容后的新数组大小还是小于最小容量,那新数组的大小就是最小容量的大小,后面会调用一个Arrays.copyof方法,这个方法是真正实现扩容的步骤。

20、如何实现数组和 List 之间的转换?

  • 数组转 List:使用 Arrays. asList(array) 进行转换
  • List 转数组:使用 List 自带的 toArray() 方法。

21、ArrayList 和 Vector 的区别是什么?

image-20230205212519700

22、说一下 HashSet 的实现原理?

HashSet 是基于 HashMap 实现的,HashSet的值存放于HashMap的key上,HashMap的value统一为present,因此 HashSet 的实现比较简单,相关 HashSet 的操作,基本上都是直接调用底层 HashMap 的相关方法来完成,HashSet 不允许重复的值。

23、 HashSet如何检查重复?HashSet是如何保证数据不可重复的?

  • 向HashSet 中add ()元素时,判断元素是否存在的依据,不仅要比较hash值,同时还要结合equles 方法比较。
  • HashSet 中的add ()方法会使用HashMap 的put()方法。
  • HashMap 的 key 是唯一的,由源码可以看出 HashSet 添加进去的值就是作为HashMap 的key,并且在HashMap中如果K/V相同时,会用新的V覆盖掉旧的V,然后返回旧的V。所以不会重复( HashMap 比较key是否相等是先比较hashcode 再比较equals )。

24、说一下HashMap的实现原理?

image-20230205212832233

25、HashMap在JDK1.7和JDK1.8中有哪些不同?HashMap的底层实现

HashMap JDK1.8之前

  • JDK1.8之前采用的是拉链法。拉链法:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。

    image-20230205213005614

HashMap JDK1.8之后

  • 相比于之前的版本,jdk1.8在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。

image-20230205212953233

26、说说什么是红黑树

image-20230205213218019

27、能否使用任何类作为 Map 的 key?

image-20230205213333907

28、HashMap 的长度为什么是2的幂次方

image-20230205213416274

29、HashMap 与 HashTable 有什么区别?

image-20230205213522037

30、Array 和 ArrayList 有何区别?

  • Array 可以存储基本数据类型和对象,ArrayList 只能存储对象。
  • Array 是指定固定大小的,而 ArrayList 大小是自动扩展的。
  • Array 内置方法没有 ArrayList 多,比如 addAll、removeAll、iteration 等方法只有 ArrayList 有。

31、comparable 和 comparator的区别?

  • comparable接口实际上是出自java.lang包,它有一个 compareTo(Object obj)方法用来排序
  • comparator接口实际上是出自 java.util 包,它有一个compare(Object obj1, Object obj2)方法用来排序
  • 一般我们需要对一个集合使用自定义排序时,我们就要重写compareTo方法或compare方法,当我们需要对某一个集合实现两种排序方式,比如一个song对象中的歌名和歌手名分别采用一种排序方法的话,我们可以重写compareTo方法和使用自制的Comparator方法或者以两个Comparator来实现歌名排序和歌星名排序,第二种代表我们只能使用两个参数版的Collections.sort().

32、Collection和Collections有什么区别?

  • java.util.Collection 是一个集合接口(集合类的一个顶级接口)。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式,其直接继承接口有List与Set。
  • Collections则是集合类的一个工具类/帮助类,其中提供了一系列静态方法,用于对集合中元素进行排序、搜索以及线程安全等各种操作。

四、Java线程面试题

1、为什么要使用多线程?

(1)充分发挥多核CPU的优势

(2)防止程序阻塞

(3)便于建模。

2、单线程和多线程的区别。

(1)单核CPU中,每个时间片段,只能有一个线程运行。

(2)单线程程序执行效率更快,多线程用户响应时间更少。

3、进程和线程

进程:程序(有指令和数据的文件)的一次执行过程。

线程:轻量级进程。进程的组成单元。同类多个线程共享同一块内存空间和一组资源。

4、并行和并发

并发:一个CPU在多个线程之间快速切换,达到同时执行多个任务的目的。

并行:多个CPU可以同时执行一个进程中的多个线程。

5、线程的创建方式

方法一:继承Thread类,作为线程对象存在(继承Thread对象)

方法二:实现runnable接口,作为线程任务存在

方法三:匿名内部类创建线程对象

方法四:创建带返回值的线程

方法五:定时器Timer

方法六:线程池创建线程

方法七:利用java8新特性stream 实现并发

让线程等待的方法

Thread.sleep(200); //线程休息2ms

Object.wait(); //让线程进入等待,直到调用Object的notify或者notifyAll时,线程停止休眠

6、线程的六种状态()

(1)初始状态:线程构建,但还没有调用start()方法。

(2)运行状态:操作系统中的“就绪”和“运行”。

(3)阻塞状态:线程阻塞于锁。

(4)等待状态:当前线程需要等待其他线程做出特定动作(通知/中断)。

(5)超时等待状态:在特定的时间自行返回。

(6)终止状态:当前线程已执行完毕。

img

7、如何停止一个正在运行的线程?

(1)使用推出标致(当run()方法完成后线程终止,正常退出)。

(2)使用stop方法强行终止,(不推荐,过期作废)。

(3)使用interrupt方法中断线程

8、start()和run()方法的区别?

Start():调用后才能出现多线程的特性,不同线程的run()方法里面的代码交替执行。

run():如果调用后,代码还是同步执行的,必须等待一个线程的run()方法里的代码全部执行完毕,另一个run()才能开始执行。

9、为什么调用start()方法的时候会执行run()方法,而不能直接调用run()方法?

JVM执行start方法,会令其一条线程执行继承的thread的run方法,这才起到多线程的效果。

如果直接调用thread的run方法,其方法还是在运行的主线程中,没有起到多线程的效果。

10、什么是线程安全?

多线程访问同一代码,而不会产生不确定的结果。

11、Java中堆和栈的区别?

栈:在函数中定义的基本类型的变量和对象的引用变量都在函数的栈内存中。(用来存放执行程序)

堆:用于存放由new创建的对象和数组。

12、如何确保线程安全?

(1)对非安全的代码进行“加锁”。

(2)使用线程安全的类。

(3)多线程高并发情况下,线程共享的变量改为“方法级的局部变量”。

13、线程的安全级别?

线程安全:在多 线程下执行和在单线程下执行永远都能获得一样的结果,那么你的代码就是线程安全的。

(1)不可变:不可变的对象一定是线程安全的,永远也不需要额外的同步。

(2)无条件的线程安全:由类的规格说明所规定的的约束,在对象被多个线程访问时仍然有效,不管运行时环境如何排列,线程都不需要额外的同步。

(3)有条件的线程安全:有条件的线程安全类对于单独的操作可以是线程安全,但是某些操作序列可能需要额外的同步。

(4)非线程安全(线程兼容):线程兼容类不是线程安全的,但是可以通过正确使用同步,在并发环境中安全地使用。

(5)线程对立:是那些不管是否采用了“同步措施”,都不能在多线程环境中并发使用的代码。

14、线程类的构造方法和静态块是被哪个线程调用的?

是被new这个线程类所在的线程所调用的,而run里面的代码才是被线程自身调用。

15、线程池的作用

(1)降低资源消耗。(重复利用已创建的线程降低线程创建和销毁所造成的消耗)

(2)提高响应速度。(任务到达时,任务可以不需要等到线程创建就能立即执行)

(3)提高线程的可管理性。(线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行同一的分配,调优和监控)

16、Java中的死锁问题

死锁:死锁是指两个或两个以上的进程(线程)在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。

必要条件:

(1)互斥条件:进程对所分配到的资源进行排他性使用。

(2)请求和保持条件:进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已经被其他进程占有。

(3)不剥夺条件:进程已获得的资源,在未使用完之前,不能被剥夺。

(4)环路等待条件:在发生死锁时,必然存在一个进程–资源的环形链。

如何解决死锁问题

程序出现死锁,是因为在多线程环境里面两个或两个以上的线程同时满足互斥条件、请求保持条件、不可抢占条件、循环等待条件。出现死锁以后,可以通过jstack命令去导出线程的dump日志,然后从dump日志里面定位到具体死锁的程序代码。通过修改程序代码去破坏这四个条件里面的任意一个,就可以解决死锁问题。当然,因为互斥条件因为是锁本身的特性,所以不能被破坏。

17、如何避免死锁和检测

(1)预防死锁:破坏互斥关系、破坏请求和保持条件、破坏不剥夺条件、破坏循环等待条件。

(2)设置加锁顺序。

破坏互斥关系无法破坏(临界资源需要互斥访问)
破坏请求和保持条件一次性申请所有的资源
破坏不剥夺条件占用部分资源的线程进一步申请其他资源时,若申请不到,则主动释放它占有的资源
破坏循环等待条件靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。

18、synchronized底层原理?

同步代码块:

synchronized(锁对象){
    需要同步执行的代码
}

原理:一旦线程进入了该代码块,就持有锁,JVM会有监视器监视进入锁的线程,其他线程想进入代码,监视器就会拒绝访问;一旦持有锁的线程执行完代码,锁就会被释放,其他线程才可进入。

注意:任何对象都可以作为锁,必须是成员变量。

19、生产者消费者模型的作用是什么?

(1)平衡生产者的生产能力和消费者的消费能力,提升整个系统的运行效率。

(2)解耦。

20、生产者消费者模式

生产者是生产数据的线程,消费者是消费数据的线程。

如果生产者处理速度远快过消费数据的速度,那么生产者必须等待消费者处理完,才能继续生产数据。

反之,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这种生产消费能力不均衡的问题,所以便有了生产者和消费者模式。

21、创建线程池的几种方法

方式一:通过构造方法实现

方式二:通过****Executor 框架的工具类Executors来实现 我们可以创建三种类型的

返回线程数量无限的线程池ExecutorService newCachedThreadPool()
返回固定长度的线程池,好处是可以控制并发量ExecutorService newFixedThreadPool(int size)
返回单一线程的线程池ExecutorService newSingleThreadExecutor()
返回可以调度的线程池ScheduledExecutorService newScheduledThreadPool(int size)

22、自定义线程池的配置

ThreadPoolExecutor的构造方法:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue)

参数说明:

corePoolSize核心线程数
maximumPoolSize最大线程数
keepAliveTime临时的线程能够存活的时间
unit时间单位
workQueue用于保存任务的阻塞队列

优化配置:

  1. 核心线程数大于或等于CPU内核的数量(如果是CPU密集型任务,就需要尽量压榨CPU,参考值可以设为 NCPU+1,如果是IO密集型任务,参考值可以设置为2*NCPU)获得CPU的内核数:Runtime.getRuntime().availableProcessors()

  2. 最大线程数和核心线程数配置一样,性能比较高,因为避免临时线程的创建和销毁

  3. 如果临时线程比较多,可以将存活时间配置稍微长点,减少临时线程的创建和销毁
    4、阻塞队列LinkedBlockingQueue的性能比较高

23、什么是乐观锁和悲观锁

  • 悲观锁
    想法悲观,认为当前的资源存在竞争,所以每次获得资源时都会上锁,阻塞住其它线程。
    数据库中的行锁、表锁、读锁、写锁都属于悲观锁,Java的synchronized和ReentrantLock也属于悲观锁。
    悲观锁会降低系统性能和吞吐量,提高数据的安全性,适用于多写少读的场景。
  • 乐观锁
    想法乐观,认为当前的资源不存在竞争,所以每次获得资源时都不上锁
    乐观锁执行效率高,有利于提高系统吞吐量,适用于多读少写的场景。

24、并发编程三要素是什么?在Java程序中怎么保证多线程的运行安全?

三要素出现安全问题的原因解决办法
原子性一个或多个操作要么全部执行,要么全部执行失败。线程切换JDK Atomic开头的原子类、synchronized、LOCK,可以解决原子性问题
可见性一个线程对共享变量的修改,另一个线程能够立刻看到。缓存导致synchronized、volatile、LOCK,可以解决可见性问题
有序性程序执行的顺序按照代码的先后顺序执行。编译优化Happens-Before 规则可以解决有序性问题

25、volatile 关键字

用来确保将变量的更新操作通知到其他线程。可以将 volatile 看做一个轻量级的锁,但是又与锁有些不同:

  1. 对于多线程,不是一种互斥关系
  2. 不能保证变量状态的“原子性操作”

被volatile修饰的共享变量,就具有了以下两点特性:

  • 1.保证了不同线程对该变量操作的内存可见性;
  • 2.禁止指令重排序

26、乐观锁如何实现

乐观锁常用的两种实现方式:

  1. 版本号机制
    利用版本号version记录数据被修改的次数,当数据被修改时,version加一。当线程要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前version值相等时才更新,否则重试更新操作,直到更新成功。
  2. CAS算法
    Compare and Swap(比较与交换)
    CAS涉及三个操作数:
    1. 读写变量的内存位置
    2. 预期值
    3. 写入的新值
      CAS实现过程是:先判断内存位置上的值是否和预期值一致,如果一致就修改为新值,否则不执行。

27、sleep方法和wait方法有什么区别和共同点

两者最主要的区别在于:sleep() 方法没有释放锁,而 wait() 方法释放了锁 。
两者都可以暂停线程的执行。
wait() 通常被用于线程间交互/通信,sleep() 通常被用于暂停执行。
wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify() 或者 notifyAll() 方法。sleep() 方法执行完成后,线程会自动苏醒。或者可以使用 wait(long timeout) 超时后线程会自动苏醒。

28、CAS算法

CAS (Compare-And-Swap 比较与交换) 是一种硬件对并发的支持,针对多处理器操作而设计的处理器中的一种特殊指令,用于管理对共享数据的并发访问。

  • CAS 是一种无锁的非阻塞算法的实现。
  • CAS 包含了 3 个操作数:需要读写的内存值 V、进行比较的值 A、拟写入的新值 B
  • 当且仅当 V 的值等于 A 时,CAS 通过原子方式用新值 B 来更新 V 的值,否则不会执行任何操作。

这是一个模拟实现,是通过synchronizes加锁的阻塞算法,跟真正的CAS不同。

五、Java虚拟机面试题

image-20230205193547603

1、什么是Java虚拟机?为何被称作“平台无关的编程语言”?

Java虚拟机是一个可以执行Java字节码(.class)的虚拟机进程。

Java源文件(.java)被编译成Java字节码(.class)。允许应用程序可以在任意平台。

因为Java虚拟机知道底层硬件平台的指令长度和其他特性。

2、Java的内存结构

(1)Java堆:虚拟机启动时创建,存放对象实例(new)。各线程共享。

(2)方法区:存储加载的类的信息、常量、静态变量、编译后的代码等。各线程共享。

(3)程序计数器:当前线程所执行的字节码的行号指示器。线程私有。

(4)JVM栈:描述Java方法执行的内存模型,每个栈帧对应一个方法,线程私有。

(5)本地方法栈:为虚拟机使用到的Native方法服务。

img

image-20230205193831242

Java虚拟机会出现的两种错误 :

  1. StackOverFlowError: 若Java虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前Java虚拟机栈的最大深度的时候,就抛出StackOverFlowError 错误。
  2. OutOfMemoryError:若Java虚拟机栈的内存大小允许动态扩展,且当线程请求栈时内存用完了,无法再动态扩展了,此时抛出 OutOfMemoryError错误。

扩展:那么方法/函数如何调用?

Java 栈可用类比数据结构中栈,Java 栈中保存的主要内容是栈帧,

每一次函数调用都会有一个对应的栈帧被压入 Java 栈,

每一个函数调用结束后,都会有一个栈帧被弹出。Java 方法有两种返回方式:

  1. return 语句。
    1. 抛出异常。
    2. 不管哪种返回方式都会导致栈帧被弹出。

3、内存分配

(1)寄存器:无法控制。

(2)静态域:static定义的成员。

(3)常量池:编译时被确定并保存到.class文件中的(final)常量值和一些文本修饰的符号引用。

(4)非RAM存储:硬盘等永久存储空间。

(5)堆内存:new创建的对象和数组,由Java虚拟机自动垃圾回收器管理,存取速度慢。

栈内存:基本类型的变量和对象的引用变量(对内存空间的访问地址),速度快,可以共享,但是大小与生存周期必须确定,缺乏灵活性。

4、类加载器

(1)启动类加载器:负责加载JDK\jre\lib下的文件。

(2)扩展类加载器:负责加载JDK\jre\lib\ext目录中的所有类库。

(3)应用程序类加载器:负责加载用户类路径所指定的类。

5、JVM如何加载class文件?

类加载器:运行时查找和装入类文件中的类。

加载:把类的.class文件中的数据读入到内存之中(通过创建字节数组读入.class文件),然后产生与所加载类对应的Class对象。

连接:验证、准备(为静态变量分配内存并设置默认的初始值)和解析。

初始化:(1)如果类存在直接的父类,且这个父类还没有被初始化,则先初始化父类。

​ (2)如果类中存在初始化语句,就依次执行这些初始化语句。

6、Java对象创建的过程

(1)拿到内存创建的指令(new 类名),来到方法区,根据new的参数,在常量池找到一个类的符号引用。

(2)检查符号引用,检查是否被加载、解析和初始化过。

(3)分配内存。

(4)初始化:抽象数据类型默认初始化为null,基本数据类型为0,布尔为false…

(5)调用对象的初始化方法。

img

7、Java对象的结构

(1)对象头:运行时数据、指针类型。

(2)实例数据:存储对象真正的有效信息。

(3)对齐填充:要求对象初始地址必须是8字节的整数倍。

8、类的生命周期

  • 加载:根据查找路径找到相应的 class 文件然后导入;
  • 验证:检查加载的 class 文件的正确性;
  • 准备:给类中的静态变量分配内存空间;
  • 解析:虚拟机将常量池中的符号引用替换成直接引用的过程。符号引用就理解为一个标示,而在直接引用直接指向内存中的地址;
  • 初始化:对静态变量和静态代码块执行初始化工作。

9、什么情况下会发生栈溢出?

栈的大小可以通过-Xss参数设置,当递归层次太深的时候就会发生栈溢出。(例循环、递归)。

10、Java垃圾回收机制,GC是什么?为什么要GC?

Java垃圾回收机制:低优先级,只有在JVM空闲和当前堆内存不足时,会执行。扫描出那些没有被引用过的类,将他们添加到要回收的集合中,进行回收。

GC 是垃圾收集的意思。

Java 提供的 GC 功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的。

11、JVM新生代、老年代、持久代都存储些哪些东西?

(1)新生代:new出的对象。

(2)老年代:新生代中经历了N次垃圾回收仍存活的对象、大对象直接存入、Survivor空间不足。

(3)持久代:指方法区。

12、Java会存在内存泄漏吗?

内存泄漏:指一个不再被程序使用的对象或变量一直被占据在内存中。

情况:长生命周期的对象只有断声明周期对象的引用时,可能发生内存泄漏。对生命周期对象已经不需要,但是因为长生命周期对象对其的引用,而无法回收。

13、JVM调优有哪些?

(1)设定堆内存大小。-Mmx:堆内存最大限制。

(2)设定新生代大小:(不能太小,否则会大量涌入老年代)

​ -XX:NewSize:新生代大小

​ -XX:NewRatio:新生代和老生代占比

​ -XX:SurvivorRatio:伊甸园空间和幸存者空间占比

(3)设定垃圾回收器:年轻代:-XX:+UseParNewGC

​ 老年代:-XX:+UseConcMarkSweepGC

说一下 JVM 调优的工具?

JDK 自带了很多监控工具,都位于 JDK 的 bin 目录下,其中最常用的是 jconsole 和 jvisualvm 这两款视图监控工具。

  • jconsole:用于对 JVM 中的内存、线程和类等进行监控;
  • jvisualvm:JDK 自带的全能分析工具,可以分析:内存快照、线程快照、程序死锁、监控内存的变化、gc 变化等。

14、如何判断对象可以被回收?

  1. 引用计数法
    1. 在JDK1.2之前,使用的是引用计数器算法。在对象中添加一个引用计数器,当有地方引用这个对象的时候,引用计数器的值就+1,当引用失效的时候,计数器的值就-1,当引用计数器被减为零的时候,标志着这个对象已经没有引用了,可以回收了
  2. 可达性分析法
    1. 通过一系列的称为 GC Roots 的对象作为起点, 然后向下搜索; 搜索所走过的路径称为引用链/Reference Chain, 当一个对象到 GC Roots 没有任何引用链相连时, 即该对象不可达, 也就说明此对象是不可用的。

15、 JVM的永久代中会发生垃圾回收么?

垃圾回收不会发生在永久代,如果永久代满了或者是超过了临界值,会触发完全垃圾回收(Full GC)。

注:Java 8 中已经移除了永久代,新加了一个叫做元数据区的native 内存区。

16、栈帧有哪些数据?

每个线程栈里的元素就是“栈帧”。

局部变量表、操作数栈、动态连接、返回地址。

17、Java的双亲委托机制是什么?

每一个类都有一个对应它的类加载器。系统中的 ClassLoder 在协同工作的时候会默认使用 双亲委派模型 。即在类加载的时候,系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载。加载的时候,首先会把该请求委派该父类加载器的 loadClass() 处理,因此所有的请求最终都应该传送到顶层的启动类加载器 BootstrapClassLoader 中。当父类加载器无法处理时,才由自己来处理。当父类加载器为null时,会使用启动类加载器 BootstrapClassLoader 作为父类加载器。

18、说一下堆栈的区别?

物理地址栈使用的是数据结构中的栈,先进后出的原则,物理地址分配是连续的。所以性能快。堆的物理地址分配对对象是不连续的。因此性能慢些。
内存分别栈是连续的,所以分配的内存大小要在编译期就确认,大小是固定的。堆因为是不连续的,所以分配的内存是在运行期确认的,因此大小不固定。一般堆大小远远大于栈。
存放的内容栈存放:局部变量,操作数栈,返回结果。该区更关注的是程序方法的执行。堆存放的是对象的实例和数组。因此该区更关注的是数据的存储
PS静态变量放在方法区静态的对象还是放在堆。
程序的可见度栈只对于线程是可见的。所以也是线程私有。他的生命周期和线程相同。堆对于整个应用程序都是共享、可见的。

19、队列和栈是什么?有什么区别?

队列和栈都是被用来预存储数据的。

队列
操作的名称队列的插入称为入队,队列的删除称为出队。栈的插入称为进栈,栈的删除称为出栈。
可操作的方式队列是在队尾入队,队头出队,即两边都可操作。而栈的进栈和出栈都是在栈顶进行的,无法对栈底直接进行操作。
操作的方法队列是先进先出(FIFO),即队列的修改是依先进先出的原则进行的。新来的成员总是加入队尾(不能从中间插入),每次离开的成员总是队列头上(不允许中途离队)。而栈为后进先出(LIFO),即每次删除(出栈)的总是当前栈中最新的元素,即最后插入(进栈)的元素,而最先插入的被放在栈的底部,要到最后才能删除。

20、为对象分配内存

指针碰撞:如果Java堆的内存是规整,即所有用过的内存放在一边,而空闲的的放在另一边。分配内存时将位于中间的指针指示器向空闲的内存移动一段与对象大小相等的距离,这样便完成分配内存工作。
空闲列表:如果Java堆的内存不是规整的,则需要由虚拟机维护一个列表来记录那些内存是可用的,这样在分配的时候可以从列表中查询到足够大的内存分配给对象,并在分配后更新列表记录。

img

21、处理并发安全问题

对分配内存空间的动作进行同步处理(采用 CAS + 失败重试来保障更新操作的原子性);
把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在 Java 堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer, TLAB)。哪个线程要分配内存,就在哪个线程的 TLAB 上分配。只有 TLAB 用完并分配新的 TLAB 时,才需要同步锁。通过-XX:+/-UserTLAB参数来设定虚拟机是否使用TLAB。
img

22、说一下 JVM 有哪些垃圾回收算法?

  • 标记-清除算法:标记无用对象,然后进行清除回收。缺点:效率不高,无法清除垃圾碎片。

    优点解决了标记-清理算法存在的内存碎片问题。
    缺点仍需要进行局部对象移动,一定程度上降低了效率。
  • 复制算法:按照容量划分二个大小相等的内存区域,当一块用完的时候将活着的对象复制到另一块上,然后再把已使用的内存空间一次清理掉。缺点:内存使用率不高,只有原来的一半。 适用于新生代。

    优点按顺序分配内存即可,实现简单、运行高效,不用考虑内存碎片。
    优点解决了标记-清理算法存在的内存碎片问题。可用的内存大小缩小为原来的一半,对象存活率高时会频繁进行复制。
  • 标记-整理算法:标记无用对象,让所有存活的对象都向一端移动,然后直接清除掉端边界以外的内存。适用于老年代。

    优点解决了标记-清理算法存在的内存碎片问题。
    缺点仍需要进行局部对象移动,一定程度上降低了效率。
  • 分代算法:根据对象的存活周期将内存划分为几块。一般包括年轻代老年代永久代,新生代基本采用复制算法,老年代采用标记整理算法。

23、分代概念

新生成的对象首先放到年轻代Eden区,当Eden空间满了,触发Minor GC,存活下来的对象移动到Survivor0区,Survivor0区满后触发执行Minor GC,Survivor0区存活对象移动到Suvivor1区,这样保证了一段时间内总有一个survivor区为空。经过多次Minor GC仍然存活的对象移动到老年代。
老年代存储长期存活的对象,占满时会触发Major GC=Full GC,GC期间会停止所有线程等待GC完成,所以对响应要求高的应用尽量减少发生Major GC,避免响应超时。
Minor GC : 清理年轻代
Major GC : 清理老年代
Full GC : 清理整个堆空间,包括年轻代和永久代
所有GC都会停止应用所有线程。

24、为什么分代?

将对象根据存活概率进行分类,对存活时间长的对象,放到固定区,从而减少扫描垃圾时间及GC频率。针对分类进行不同的垃圾回收算法,对算法扬长避短。

25、JVM堆内存常用参数

参数描述
-Xms堆内存初始大小,单位m、g
-Xmx(MaxHeapSize)堆内存最大允许大小,一般不要大于物理内存的80%
-XX:PermSize非堆内存初始大小,一般应用设置初始化200m,最大1024m就够了
-XX:MaxPermSize非堆内存最大允许大小
-XX:NewSize(-Xns)年轻代内存初始大小
-XX:MaxNewSize(-Xmn)年轻代内存最大允许大小,也可以缩写
-XX:SurvivorRatio=8年轻代中Eden区与Survivor区的容量比例值,默认为8,即8:1
-Xss堆栈内存大小

26、类加载的过程

JVM的类加载分为5个阶段:加载、验证、准备、解析、初始化。在类初始化完成后就可以使用该类的信息,在一个类不再被需要时可以从JVM中卸载

  1. 加载

    1. 指JVM读取Class文件,并且根据Class文件描述创建java. lang. Class对象的过程。类加载过程主要包含将Class文件读取到运行时区域的方法区内,在堆中创建java.lang.Class 对象,并封装类在方法区的数据结构的过程,在i卖取Class文件时既可以通过文件的形式读取,也可以通过jar包、war包读取,还可以通过代理自动生成Class或其他方式读取。
  2. 验证

    1. 主要用于确保Class文件符合当前虚拟机的要求,保障虚拟机自身的安全,只有通过验证的Class文件才能被JVM加载。
  3. 准备

    1. 主要工作是在方法区中为类变量分配内存空间并设置类中变量的初始值。初始值指不同数据类型的默认值,这里需要注意final类型的变量和非final类型的变量在准备阶段的数据初始化过程不同。
  4. 解析

    1. JVM会将常量池中的符号引用替换为直接引用。
  5. 初始化

    1. 要通过执行类构造器的<client>方法为类进行初始化。<client>方法是在编译阶段由编译器自动收集类中静态语句块和变量的赋值操作组成的。JVM规定,只有在父类的<client>方法都执行成功后,子类中的<client>方法才可以被执行。在一个类中既没有静态变量赋值操作也没有静态语句块时,编译器不会为该类生成<client>方法

27、Java中的4种引用类型

  1. 强引用:在Java中最常见的就是强引用。在把一个对象赋给一个引用变量时,这个引用变量就是一个强引用。有强引用的对象一定为可达性状态,所以不会被垃圾回收机制回收。因此,强引用是造成Java内存泄漏(Memory Link )的主要原因。
  2. 软引用:软引用通过SoftReference类实现。如果一个对象只有软引用,则在系统内存空间不足时该对象将被回收。
  3. 弱引用:弱引用通过WeakReference类实现,如果一个对象只有弱引用,则在垃圾回收过程中一定会被回收。
  4. 虚引用:虚引用通过PhantomReference类实现,虚引用和引用队列联合使用,主要用于跟踪对象的垃圾回收状态。

六、MySQL面试题

1、什么是MySQL

MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS (Relational Database Management System,关系数据库管理系统) 应用软件之一。在Java企业级开发中非常常用,因为 MySQL 是开源免费的,并且方便扩展。

2、数据库三大范式

  1. 第一范式:每个列都不可以再拆分。
  2. 第二范式:在第一范式的基础上,非主键列完全依赖于主键,而不能是依赖于主键的一部分。
  3. 第三范式:在第二范式的基础上,非主键列只依赖于主键,不依赖于其他非主键。

在设计数据库结构的时候,要尽量遵守三范式,如果不遵守,必须有足够的理由。比如性能。事实上我们经常会为了性能而妥协数据库的设计。

3、对慢查询怎么样优化过?

(1)分析语句,看是否查询了多余的行并且抛弃掉了,是否加载了许多结果中不需要的列,对语句进行分析以及重写;

(2)分析语句的执行计划,获得其使用索引的情况,进行修改,使语句可以尽可能的命中索引;

(3)考虑数据量是否太大,可以考虑进行横向或者纵向分表。

4、mysql有关权限的表都有哪几个

MySQL服务器通过权限表来控制用户对数据库的访问,权限表存放在mysql数据库里,由mysql_install_db脚本初始化。这些权限表分别user,db,table_priv,columns_priv和host。下面分别介绍一下这些表的结构和内容:

  • user权限表:记录允许连接到服务器的用户帐号信息,里面的权限是全局级的。
  • db权限表:记录各个帐号在各个数据库上的操作权限。
  • table_priv权限表:记录数据表级的操作权限。
  • columns_priv权限表:记录数据列级的操作权限。
  • host权限表:配合db权限表对给定主机上数据库级操作权限作更细致的控制。这个权限表不受GRANT和REVOKE语句的影响。

5、MySQL由哪些部分组成?

(1)Server

连接器:管理连接、权限验证

分析器:词法分析,语法分析

优化器:执行计划生成,索引的选择

执行器:操作存储引擎,返回执行结果

(2)存储引擎

存储数据,提供读写接口。

6、优化数据库的方法

(1)选取最合适的字段,尽可能减少定义阻断宽度,尽量NOTNULL。

(2)使用JOIN(连接)来代替子查询。

(3)使用UNION(联合)来代替手动创建临时表。

(4)事务处理。

(5)锁定表,优化事务处理。

(6)使用外键,优化锁定表。

(7)建立索引。

(8)优化查询语句。

优化查询过程中的数据访问

  1. 访问数据太多导致查询性能下降
  2. 确定应用程序是否在检索大量超过需要的数据,可能是太多行或列
  3. 确认MySQL服务器是否在分析大量不必要的数据行
  4. 避免犯如下SQL语句错误
  5. 查询不需要的数据。解决办法:使用limit解决
  6. 多表关联返回全部列。解决办法:指定列名
  7. 总是返回全部列。解决办法:避免使用SELECT *
  8. 重复查询相同的数据。解决办法:可以缓存数据,下次直接读取缓存
  9. 是否在扫描额外的记录。解决办法:
  10. 使用explain进行分析,如果发现查询需要扫描大量的数据,但只返回少数的行,可以通过如下技巧去优化:
  11. 使用索引覆盖扫描,把所有的列都放到索引中,这样存储引擎不需要回表获取对应行就可以返回结果。
  12. 改变数据库和表的结构,修改数据表范式
  13. 重写SQL语句,让优化器可以以更优的方式执行查询。

优化长难的查询语句

  1. 一个复杂查询还是多个简单查询
  2. MySQL内部每秒能扫描内存中上百万行数据,相比之下,响应数据给客户端就要慢得多
  3. 使用尽可能小的查询是好的,但是有时将一个大的查询分解为多个小的查询是很有必要的。
  4. 切分查询
  5. 将一个大的查询分为多个小的相同的查询
  6. 一次性删除1000万的数据要比一次删除1万,暂停一会的方案更加损耗服务器开销。
  7. 分解关联查询,让缓存的效率更高。
  8. 执行单个查询可以减少锁的竞争。
  9. 在应用层做关联更容易对数据库进行拆分。
  10. 查询效率会有大幅提升。
  11. 较少冗余记录的查询。

优化特定类型的查询语句

  1. count()会忽略所有的列,直接统计所有列数,不要使用count(列名)
  2. MyISAM中,没有任何where条件的count(*)非常快。
  3. 当有where条件时,MyISAM的count统计不一定比其它引擎快。
  4. 可以使用explain查询近似值,用近似值替代count(*)
  5. 增加汇总表
  6. 使用缓存

优化关联查询

  • 确定ON或者USING子句中是否有索引。
  • 确保GROUP BY和ORDER BY只有一个表中的列,这样MySQL才有可能使用索引。

优化子查询

用关联查询替代

优化GROUP BY和DISTINCT

  1. 这两种查询据可以使用索引来优化,是最有效的优化方法
  2. 关联查询中,使用标识列分组的效率更高
  3. 如果不需要ORDER BY,进行GROUP BY时加ORDER BY NULL,MySQL不会再进行文件排序。
  4. WITH ROLLUP超级聚合,可以挪到应用程序处理

优化LIMIT分页

  • LIMIT偏移量大的时候,查询效率较低
  • 可以记录上次查询的最大ID,下次查询时直接根据该ID来查询

优化UNION查询

UNION ALL的效率高于UNION

7、解释一下交叉连接、内连接、外连接

(1)交叉连接(笛卡尔积):不使用任何条件,直接将一个表的所有记录和另一个表中的所有记录一一匹配。

(2)内连接:只有条件的交叉连接,根据某个条件筛选出符合的记录。

(3)外连接:不仅包含符合连接条件的行,而且还会包括左表、右表或两个表中的所有数据行。

六种关联查询

  • 交叉连接(CROSS JOIN)
  • 内连接(INNER JOIN)
  • 外连接(LEFT JOIN/RIGHT JOIN)
  • 联合查询(UNION与UNION ALL)
  • 全连接(FULL JOIN)

外连接(LEFT JOIN/RIGHT JOIN)

左外连接:LEFT OUTER JOIN, 以左表为主,先查询出左表,按照ON后的关联条件匹配右表,没有匹配到的用NULL填充,可以简写成LEFT JOIN
右外连接:RIGHT OUTER JOIN, 以右表为主,先查询出右表,按照ON后的关联条件匹配左表,没有匹配到的用NULL填充,可以简写成RIGHT JOIN

联合查询(UNION与UNION ALL)

  • 就是把多个结果集集中在一起,UNION前的结果为基准,需要注意的是联合查询的列数要相等,相同的记录行会合并
  • 如果使用UNION ALL,不会合并重复的记录行
  • 效率 UNION 高于 UNION ALL

全连接(FULL JOIN)

  • MySQL不支持全连接
  • 可以使用LEFT JOIN 和UNION和RIGHT JOIN联合使用

8、为什么要使用NOTNULL?

Null值会占用更多的字节,并且会在程序中造成很多与预期不符的结果。

9、什么是索引?

是一种数据结构,可以帮助我们快速的进行数据的查找。

数据库索引,是数据库管理系统中一个排序的数据结构,以协助快速查询、更新数据库表中数据。索引的实现通常使用B树及其变种B+树。

更通俗的说,索引就相当于目录。为了方便查找书中的内容,通过对内容建立索引形成目录。

索引是一个文件,它是要占据物理空间的。

优点:(1)提高数据检索的效率,降低数据库的IO成本。

​ (2)通过索引列对数据进行排序,降低数据排序的成本,降低CPU的消耗。

缺点:(1)降低更新表的速度。

​ (2)索引也是一张表,该表保存了主键与索引字段,并且指向实体表的记录,所以索引表也需要占用空间。

​ (3)创建索引和维护索引需要消耗时间,并且随着数据了的增加,所消耗的时间也会增加。

索引有哪几种类型?

主键索引: 数据列不允许重复,不允许为NULL,一个表只能有一个主键。

唯一索引: 数据列不允许重复,允许为NULL值,一个表允许多个列创建唯一索引。

普通索引: 基本的索引类型,没有唯一性的限制,允许为NULL值。

全文索引: 是目前搜索引擎使用的一种关键技术。

索引的基本原理

  1. 把创建了索引的列的内容进行排序
  2. 对排序结果生成倒排表
  3. 在倒排表内容上拼上数据地址链
  4. 在查询的时候,先拿到倒排表内容,再取出数据地址链,从而拿到具体数据

索引设计的原则?

  1. 适合索引的列是出现在where子句中的列,或者连接子句中指定的列
  2. 基数较小的类,索引效果较差,没有必要在此列建立索引
  3. 使用短索引,如果对长字符串列进行索引,应该指定一个前缀长度,这样能够节省大量索引空间
  4. 不要过度索引。索引需要额外的磁盘空间,并降低写操作的性能。在修改表内容的时候,索引会进行更新甚至重构,索引列越多,这个时间就会越长。所以只保持需要的索引有利于查询即可。

创建索引的原则(重中之重)

索引虽好,但也不是无限制的使用,最好符合一下几个原则

1) 最左前缀匹配原则,组合索引非常重要的原则,mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。

2)较频繁作为查询条件的字段才去创建索引

3)更新频繁字段不适合创建索引

4)若是不能有效区分数据的列不适合做索引列(如性别,男女未知,最多也就三种,区分度实在太低)

5)尽量的扩展索引,不要新建索引。比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。

6)定义有外键的数据列一定要建立索引。

7)对于那些查询中很少涉及的列,重复值比较多的列不要建立索引。

8)对于定义为text、image和bit的数据类型的列不要建立索引。

使用索引查询一定能提高查询的性能吗?为什么

通常,通过索引查询数据比全表扫描要快。但是我们也必须注意到它的代价。

  • 索引需要空间来存储,也需要定期维护, 每当有记录在表中增减或索引列被修改时,索引本身也会被修改。 这意味着每条记录的INSERT,DELETE,UPDATE将为此多付出4,5 次的磁盘I/O。 因为索引需要额外的存储空间和处理,那些不必要的索引反而会使查询反应时间变慢。使用索引查询不一定能提高查询性能,索引范围查询(INDEX RANGE SCAN)适用于两种情况:
  • 基于一个范围的检索,一般查询返回结果集小于表中记录数的30%
  • 基于非唯一性索引的检索

10、什么是事务?

事务是一个不可分割的数据库操作序列,也是数据库并发控制的基本单位,其执行的结果必须使数据库从一种一致性状态变到另一种一致性状态。事务是逻辑上的一组操作,要么都执行,要么都不执行。

MySQL 事务隔离级别

  • 未提交读 - 读到其它事务未提交的数据(最新的版本)
    • 错误现象:有脏读、不可重复读、幻读现象
  • 提交读(RC) - 读到其它事务已提交的数据(最新已提交的版本)
    • 错误现象:有不可重复读、幻读现象
    • 使用场景:希望看到最新的有效值
  • 可重复读(RR) - 在事务范围内,多次读能够保证一致性(快照建立时最新已提交版本)
    • 错误现象:有幻读现象,可以用加锁避免
    • 使用场景:事务内要求更强的一致性,但看到的未必是最新的有效值
  • 串行读 - 在事务范围内,仅有读读可以并发,读写或写写会阻塞其它事务,用这种办法保证更强的一致性
    • 错误现象:无

11、ACID事务的特性

image-20230206131028026

原子性:事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;

一致性: 执行事务前后,数据保持一致,多个事务对同一个数据读取的结果是相同的;

隔离性:并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;

持久性:一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。

12、并发事务带来了哪些问题?

(1)脏读:一个事务对数据进行了修改,还未提交到数据库;另一个事务使用了未修改的数据,依据这个脏数据所做的操作可能是不正确的。

(2)丢失修改:两个事务同时访问并修改同一个数据,那么第一个事务修改的结果就会被丢失。

(3)不可重复读:在一个事务内多次读取同一数据。在这个事务结束之前,另一个事务进修改了数据,那么第一个事务两次读取的数据就会不一样了。

(4)幻读:发生在一个事务读取了几行数据,接着另一个并发事务插入了一些数据,在随后的查询中,第一个事务就会发现多了一些原本不存在的数据。

13、MySQL的四种隔离级别

image-20230206131720416

(1)读未提交:最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。

(2)读已提交:允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。

(3)可重复读:对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。

(4)可串行化:最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。

隔离级别与锁的关系

在Read Uncommitted级别下,读取数据不需要加共享锁,这样就不会跟被修改的数据上的排他锁冲突

在Read Committed级别下,读操作需要加共享锁,但是在语句执行完以后释放共享锁;

在Repeatable Read级别下,读操作需要加共享锁,但是在事务提交之前并不释放共享锁,也就是必须等待事务执行完毕以后才释放共享锁。

SERIALIZABLE 是限制性最强的隔离级别,因为该级别锁定整个范围的键,并一直持有锁,直到事务完成。

14、MySQL存储引擎MyISAM与InnoDB区别

存储引擎Storage engine:MySQL中的数据、索引以及其他对象是如何存储的,是一套文件系统的实现。

常用的存储引擎有以下:

  • Innodb引擎:Innodb引擎提供了对数据库ACID事务的支持。并且还提供了行级锁和外键的约束。它的设计的目标就是处理大数据容量的数据库系统。
  • MyIASM引擎(原本Mysql的默认引擎):不提供事务的支持,也不支持行级锁和外键。
  • MEMORY引擎:所有的数据都在内存中,数据的处理速度快,但是安全性不高。

MyISAM索引与InnoDB索引的区别?

  • InnoDB索引是聚簇索引,MyISAM索引是非聚簇索引。
  • InnoDB的主键索引的叶子节点存储着行数据,因此主键索引非常高效。
  • MyISAM索引的叶子节点存储的是行数据地址,需要再寻址一次才能得到数据。
  • InnoDB非主键索引的叶子节点存储的是主键和其他带索引的列数据,因此查询时做到覆盖索引会非常高效。

MyISAM与InnoDB区别

  • InnoDB支持事务,MyISAM不支持事务
  • InnoDB支持外键,MyISAM不支持外键
  • InnoDB 支持 MVCC(多版本并发控制),MyISAM 不支持
  • select count(*) from table时,MyISAM更快,因为它有一个变量保存了整个表的总行数,可以直接读取,InnoDB就需要全表扫描。
  • Innodb不支持全文索引,而MyISAM支持全文索引(5.7以后的InnoDB也支持全文索引)
  • InnoDB支持表、行级锁,而MyISAM支持表级锁。
  • InnoDB表必须有主键,而MyISAM可以没有主键
  • Innodb表需要更多的内存和存储,而MyISAM可被压缩,存储空间较小,。
  • Innodb按主键大小有序插入,MyISAM记录插入顺序是,按记录插入顺序保存。
  • InnoDB 存储引擎提供了具有提交、回滚、崩溃恢复能力的事务安全,与 MyISAM 比 InnoDB 写的效率差一些,并且会占用更多的磁盘空间以保留数据和索引

InnoDB引擎的4大特性

  • 插入缓冲(insert buffer)
  • 二次写(double write)
  • 自适应哈希索引(ahi)
  • 预读(read ahead)

15、百万级别或以上的数据如何删除

  1. 所以我们想要删除百万数据的时候可以先删除索引(此时大概耗时三分多钟)
  2. 然后删除其中无用数据(此过程需要不到两分钟)
  3. 删除完成后重新创建索引(此时数据较少了)创建索引也非常快,约十分钟左右。
  4. 与之前的直接删除绝对是要快速很多,更别说万一删除中断,一切删除会回滚。那更是坑了。

16、数据库为什么使用B+树而不是B树

  • B树只适合随机检索
  • B+树空间利用率更高,可减少I/O次数,磁盘读写代价更低。
  • B+树的查询效率更加稳定。
  • B树在提高了磁盘IO性能的同时并没有解决元素遍历的效率低下的问题。
  • 增删文件(节点)时,效率更高。

17、对MySQL的锁了解吗?

当数据库有并发事务的时候,可能会产生数据的不一致,这时候需要一些机制来保证访问的次序,锁机制就是这样的一个机制。

什么是死锁?怎么解决?
死锁是指两个或多个事务在同一资源上相互占用,并请求锁定对方的资源,从而导致恶性循环的现象。

常见的解决死锁的方法

1、如果不同程序会并发存取多个表,尽量约定以相同的顺序访问表,可以大大降低死锁机会。

2、在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁产生概率;

3、对于非常容易产生死锁的业务部分,可以尝试使用升级锁定颗粒度,通过表级锁定来减少死锁产生的概率;

如果业务处理不好可以用分布式事务锁或者使用乐观锁

数据库的乐观锁和悲观锁是什么?怎么实现的?

数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和统一性以及数据库的统一性。乐观并发控制(乐观锁)和悲观并发控制(悲观锁)是并发控制主要采用的技术手段。

悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。在查询完数据的时候就把事务锁起来,直到提交事务。实现方式:使用数据库中的锁机制

乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。在修改数据的时候把事务锁起来,通过version的方式来进行锁定。实现方式:乐一般会使用版本号机制或CAS算法实现。

18、为什么要使用视图?什么是视图?

为了提高复杂SQL语句的复用性和表操作的安全性。

一种虚拟表,在物理上是不存在的,其内容与真实的表相似,包含一系列带有名称的列和行数据。

视图并不在数据库中以储存的数据值形式存在。行和列数据来自定义视图的查询所引用基本表,并且在具体引用视图时动态生成。

19、视图有哪些特点?

  • 视图的列可以来自不同的表,是表的抽象和在逻辑意义上建立的新关系。
  • 视图是由基本表(实表)产生的表(虚表)。
  • 视图的建立和删除不影响基本表。
  • 对视图内容的更新(添加,删除和修改)直接影响基本表。
  • 当视图来自多个基本表时,不允许添加和删除数据。

20、视图的使用场景有哪些?

  • 重用SQL语句;
  • 简化复杂的SQL操作。在编写查询后,可以方便的重用它而不必知道它的基本查询细节;
  • 使用表的组成部分而不是整个表;
  • 保护数据。可以给用户授予表的特定部分的访问权限而不是整个表的访问权限;
  • 更改数据格式和表示。视图可返回与底层表的表示和格式不同的数据。

21、视图的优点

  1. 查询简单化。视图能简化用户的操作
  2. 数据安全性。视图使用户能以多种角度看待同一数据,能够对机密数据提供安全保护
  3. 逻辑数据独立性。视图对重构数据库提供了一定程度的逻辑独立性

22、视图的缺点

  • 性能。数据库必须把视图的查询转化成对基本表的查询,如果这个视图是由一个复杂的多表查询所定义,那么,即使是视图的一个简单查询,数据库也把它变成一个复杂的结合体,需要花费一定的时间。

  • 修改限制。当用户试图修改视图的某些行时,数据库必须把它转化为对基本表的某些行的修改。事实上,当从视图中插入或者删除时,情况也是这样。对于简单视图来说,这是很方便的,但是,对于比较复杂的视图,可能是不可修改的

  • 这些视图有如下特征:1.有UNIQUE等集合操作符的视图。2.有GROUP BY子句的视图。3.有诸如AVG\SUM\MAX等聚合函数的视图。 4.使用DISTINCT关键字的视图。5.连接表的视图(其中有些例外)

23、存储过程与函数

什么是存储过程?有哪些优缺点?
存储过程是一个预编译的SQL语句,优点是允许模块化的设计,就是说只需要创建一次,以后在该程序中就可以调用多次。如果某次操作需要执行多次SQL,使用存储过程比单纯SQL语句执行要快。

优点

1)存储过程是预编译过的,执行效率高。

2)存储过程的代码直接存放于数据库中,通过存储过程名直接调用,减少网络通讯。

3)安全性高,执行存储过程需要有一定权限的用户。

4)存储过程可以重复使用,减少数据库开发人员的工作量。

缺点

1)调试麻烦,但是用 PL/SQL Developer 调试很方便!弥补这个缺点。

2)移植问题,数据库端代码当然是与数据库相关的。但是如果是做工程型项目,基本不存在移植问题。

3)重新编译问题,因为后端代码是运行前编译的,如果带有引用关系的对象发生改变时,受影响的存储过程、包将需要重新编译(不过也可以设置成运行时刻自动编译)。

4)如果在一个程序系统中大量的使用存储过程,到程序交付使用的时候随着用户需求的增加会导致数据结构的变化,接着就是系统的相关问题了,最后如果用户想维护该系统可以说是很难很难、而且代价是空前的,维护起来更麻烦。

24、什么是触发器?触发器的使用场景有哪些?

触发器是用户定义在关系表上的一类由事件驱动的特殊的存储过程。触发器是指一段代码,当触发某个事件时,自动执行这些代码。

使用场景

  • 可以通过数据库中的相关表实现级联更改。
  • 实时监控某张表中的某个字段的更改而需要做出相应的处理。
  • 例如可以生成某些业务的编号。
  • 注意不要滥用,否则会造成数据库及应用程序的维护困难。
  • 大家需要牢记以上基础知识点,重点是理解数据类型CHAR和VARCHAR的差异,表存储引擎InnoDB和MyISAM的区别。

25、超键、候选键、主键、外键分别是什么?

  • 超键:在关系中能唯一标识元组的属性集称为关系模式的超键。一个属性可以为作为一个超键,多个属性组合在一起也可以作为一个超键。超键包含候选键和主键。
  • 候选键:是最小超键,即没有冗余元素的超键。
  • 主键:数据库表中对储存数据对象予以唯一和完整标识的数据列或属性的组合。一个数据列只能有一个主键,且主键的取值不能缺失,即不能为空值(Null)。
  • 外键:在一个表中存在的另一个表的主键称此表的外键。

26、什么是子查询?

  1. 条件:一条SQL语句的查询结果做为另一条查询语句的条件或查询结果
  2. 嵌套:多条SQL语句嵌套使用,内部的SQL查询语句称为子查询。

27、mysql中 in 和 exists 区别?

mysql中的in语句是把外表和内表作hash 连接,而exists语句是对外表作loop循环,每次loop循环再对内表进行查询。一直大家都认为exists比in语句的效率要高,这种说法其实是不准确的。这个是要区分环境的。

28、大表数据查询,怎么优化?

  1. 优化shema、sql语句+索引;
  2. 第二加缓存,memcached, redis;
  3. 主从复制,读写分离;
  4. 垂直拆分,根据你模块的耦合度,将一个大的系统分为多个小的系统,也就是分布式系统;
  5. 水平切分,针对数据量大的表,这一步最麻烦,最能考验技术水平,要选择一个合理的sharding key, 为了有好的查询效率,表结构也要改动,做一定的冗余,应用也要改,sql中尽量带sharding key,将数据定位到限定的表上去查,而不是扫描全部的表;

29、超大分页怎么处理?

  • 数据库层面,这也是我们主要集中关注的(虽然收效没那么大),类似于select * from table where age > 20 limit 1000000,10这种查询其实也是有可以优化的余地的. 这条语句需要load1000000数据然后基本上全部丢弃,只取10条当然比较慢. 当时我们可以修改为select * from table where id in (select id from table where age > 20 limit 1000000,10).这样虽然也load了一百万的数据,但是由于索引覆盖,要查询的所有字段都在索引中,所以速度会很快. 同时如果ID连续的好,我们还可以select * from table where id > 1000000 limit 10,效率也是不错的,优化的可能性有许多种,但是核心思想都一样,就是减少load的数据.
  • 从需求的角度减少这种请求…主要是不做类似的需求(直接跳转到几百万页之后的具体某一页.只允许逐页查看或者按照给定的路线走,这样可预测,可缓存)以及防止ID泄漏且连续被人恶意攻击.

30、关心过业务系统里面的sql耗时吗?统计过慢查询吗?对慢查询都怎么优化过?

  • 首先分析语句,看看是否load了额外的数据,可能是查询了多余的行并且抛弃掉了,可能是加载了许多结果中并不需要的列,对语句进行分析以及重写。
  • 分析语句的执行计划,然后获得其使用索引的情况,之后修改语句或者修改索引,使得语句可以尽可能的命中索引。
    如果对语句的优化已经无法进行,可以考虑表中的数据量是否太大,如果是的话可以进行横向或者纵向的分表。

31、CHAR 和VARCHAR 的区别?

  • CHAR 和 VARCHAR 类型在存储和检索方面有所不同
  • CHAR 列长度固定为创建表时声明的长度, 长度值范围是 1 到 255 当 CHAR 值被存储时, 它们被用空格填充到特定长度, 检索 CHAR 值时需删除尾随空格。

32、数据库索引的原理,为什么要用B+树,为什么不用二叉树?

为什么不是一般二叉树?

如果二叉树特殊化为一个链表,相当于全表扫描。平衡二叉树相比于二叉查找树来说,查找效率更稳定,总体的查找速度也更快。

为什么不是平衡二叉树呢?

我们知道,在内存比在磁盘的数据,查询效率快得多。如果树这种数据结构作为索引,那我们每查找一次数据就需要从磁盘中读取一个节点,也就是我们说的一个磁盘块,但是平衡二叉树可是每个节点只存储一个键值和数据的,如果是B树,可以存储更多的节点数据,树的高度也会降低,因此读取磁盘的次数就降下来啦,查询效率就快啦。

那为什么不是B树而是B+树呢?

  • B+树非叶子节点上是不存储数据的,仅存储键值,而B树节点中不仅存储键值,也会存储数据。innodb中页的默认大小是16KB,如果不存储数据,那么就会存储更多的键值,相应的树的阶数(节点的子节点树)就会更大,树就会更矮更胖,如此一来我们查找数据进行磁盘的IO次数有会再次减少,数据查询的效率也会更快。
  • B+树索引的所有数据均存储在叶子节点,而且数据是按照顺序排列的,链表连着的。那么B+树使得范围查找,排序查找,分组查找以及去重查找变得异常简单。

33、数据库自增主键可能遇到什么问题

  • 使用自增主键对数据库做分库分表,可能出现诸如主键重复等的问题。解决方案的话,简单点的话可以考虑使用UUID解决,复杂的可以考虑前面提到的分布式主键方案
  • 自增主键会产生表锁,从而引发问题
  • 自增主键可能用完问题。

34、Blob和text有什么区别?

  • Blob用于存储二进制数据,而Text用于存储大字符串。
  • Blob值被视为二进制字符串(字节字符串),它们没有字符集,并且排序和比较基于列值中的字节的数值。
  • text值被视为非二进制字符串(字符字符串)。它们有一个字符集,并根据字符集的排序规则对值进行排序和比较

35、count(1)、count(*) 与 count(列名) 的区别?

  • count(*):包括了所有的列,相当于行数,在统计结果的时候,不会忽略列值为NULL
  • count(1):包括了忽略所有列,用1代表代码行,在统计结果的时候,不会忽略列值为NULL
  • count(列名):只包括列名那一列,在统计结果的时候,会忽略列值为空(这里的空不是指空字符串或者0,而是表示null)的计数,即某个字段值为NULL时,不统计。

36、查询语句执行流程

image-20230206113648340

  1. 连接器:负责建立连接、检查权限、连接超时时间由 wait_timeout 控制,默认 8 小时
  2. 查询缓存:会将 SQL 和查询结果以键值对方式进行缓存,修改操作会以表单位导致缓存失效
  3. 分析器:词法、语法分析
  4. 优化器:决定用哪个索引,决定表的连接顺序等
  5. 执行器:根据存储引擎类型,调用存储引擎接口
  6. 存储引擎:数据的读写接口,索引、表都在此层实现

37、MySQL锁

1、全局锁

用作全量备份时,保证表与表之间的数据一致性

如果不加任何包含,数据备份时就可能产生不一致的情况,如下图所示

image-20230206113816398
  • 使用全局读锁锁定所有数据库的所有表。这时会阻塞其它所有 DML 以及 DDL 操作,这样可以避免备份过程中的数据不一致。接下来可以执行备份,最后用 unlock tables 来解锁

2、表级锁

1、表锁

  • 语法:加锁 lock tables 表名 read/write,解锁 unlock tables
  • 缺点:粒度较粗,在 InnoDB 引擎很少使用

2、元数据锁

  • 即 metadata-lock(MDL),主要是为了避免 DML 与 DDL 冲突,DML 的元数据锁之间不互斥

  • 加元数据锁的几种情况

    • lock tables read/write,类型为 SHARED_READ_ONLY 和 SHARED_NO_READ_WRITE
    • alter table,类型为 EXCLUSIVE,与其它 MDL 都互斥
    • select,select … lock in share mode,类型为 SHARED_READ
    • insert,update,delete,select for update,类型为 SHARED_WRITE
  • 查看元数据锁(适用于 MySQL 8.0 以上版本)

    select object_type,object_schema,object_name,lock_type,lock_duration from performance_schema.metadata_locks;

3、行级锁

  • 种类
    • 行锁 – 在 RC 下,锁住的是行,防止其他事务对此行 update 或 delete
    • 间隙锁 – 在 RR 下,锁住的是间隙,防止其他事务在这个间隙 insert 产生幻读
    • 临键锁 – 在 RR 下,锁住的是前面间隙+行,特定条件下可优化为行锁
  • 查看行级锁
    • select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks where object_name='表名';

注意

  • 它们锁定的其实都是索引上的行与间隙,根据索引的有序性来确定间隙

38、什么是最左前缀原则?什么是最左匹配原则

  • 顾名思义,就是最左优先,在创建多列索引时,要根据业务需求,where子句中使用最频繁的一列放在最左边。
  • 最左前缀匹配原则,非常重要的原则,mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。
  • =和in可以乱序,比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序,mysql的查询优化器会帮你优化成索引可以识别的形式

39、什么是聚簇索引?何时使用聚簇索引与非聚簇索引

  • 聚簇索引:将数据存储与索引放到了一块,找到索引也就找到了数据
  • 非聚簇索引:将数据存储于索引分开结构,索引结构的叶子节点指向了数据的对应行,myisam通过key_buffer把索引先缓存到内存中,当需要访问数据时(通过索引访问数据),在内存中直接搜索索引,然后通过索引找到磁盘相应数据,这也就是为什么索引不在key buffer命中时,速度慢的原因。

40、联合索引是什么?为什么需要注意联合索引中的顺序?

MySQL可以使用多个字段同时建立一个索引,叫做联合索引。在联合索引中,如果想要命中索引,需要按照建立索引时的字段顺序挨个使用,否则无法命中索引。

具体原因为:

MySQL使用索引时需要索引有序,假设现在建立了"name,age,school"的联合索引,那么索引的排序为: 先按照name排序,如果name相同,则按照age排序,如果age的值也相等,则按照school进行排序。

当进行查询时,此时索引仅仅按照name严格有序,因此必须首先使用name字段进行等值查询,之后对于匹配到的列而言,其按照age字段严格有序,此时可以使用age字段用做索引查找,以此类推。因此在建立联合索引的时候应该注意索引列的顺序,一般情况下,将查询需求频繁或者字段选择性高的列放在前面。此外可以根据特例的查询或者表结构进行单独的调整。

41、SQL语句主要分为哪几类

数据定义语言DDL(Data Ddefinition Language)CREATE,DROP,ALTER

主要为以上操作 即对逻辑结构等有操作的,其中包括表结构,视图和索引。

数据查询语言DQL(Data Query Language)SELECT

这个较为好理解 即查询操作,以select关键字。各种简单查询,连接查询等 都属于DQL。

数据操纵语言DML(Data Manipulation Language)INSERT,UPDATE,DELETE

主要为以上操作 即对数据进行操作的,对应上面所说的查询操作 DQL与DML共同构建了多数初级程序员常用的增删改查操作。而查询是较为特殊的一种 被划分到DQL中。

数据控制功能DCL(Data Control Language)GRANT,REVOKE,COMMIT,ROLLBACK

主要为以上操作 即对数据库安全性完整性等有操作的,可以简单的理解为权限控制等。

42、超键、候选键、主键、外键分别是什么?

  • 超键:在关系中能唯一标识元组的属性集称为关系模式的超键。一个属性可以为作为一
  • 超键,多个属性组合在一起也可以作为一个超键。超键包含候选键和主键。
  • 候选键:是最小超键,即没有冗余元素的超键。
  • 主键:数据库表中对储存数据对象予以唯一和完整标识的数据列或属性的组合。一个数据列只能有一个主键,且主键的取值不能缺失,即不能为空值(Null)。
  • 外键:在一个表中存在的另一个表的主键称此表的外键。

43、SQL 约束有哪几种?

  1. NOT NULL: 用于控制字段的内容一定不能为空(NULL)。
  2. UNIQUE: 控件字段内容不能重复,一个表允许有多个 Unique 约束。
  3. PRIMARY KEY: 也是用于控件字段内容不能重复,但它在一个表只允许出现一个。
  4. FOREIGN KEY: 用于预防破坏表之间连接的动作,也能防止非法数据插入外键列,因为它必须是它指向的那个表中的值之一。
  5. CHECK: 用于控制字段的值范围。

44、varchar(50)中50的涵义

最多存放50个字符,varchar(50)和(200)存储hello所占空间一样,但后者在排序时会消耗更多内存,因为order by col采用fixed_length计算col长度(memory引擎也一样)。在早期 MySQL 版本中, 50 代表字节数,现在代表字符数。

45、int(20)中20的涵义

是指显示字符的长度。20表示最大显示宽度为20,但仍占4字节存储,存储范围不变;

不影响内部存储,只是影响带 zerofill 定义的 int 时,前面补多少个 0,易于报表展示

46、为什么要尽量设定一个主键?

主键是数据库确保数据行在整张表唯一性的保障,即使业务上本张表没有主键,也建议添加一个自增长的ID列作为主键。设定了主键之后,在后续的删改查的时候可能更加快速以及确保操作数据范围安全。

47、MySQL的复制原理以及流程

主从复制:将主数据库中的DDL和DML操作通过二进制日志(BINLOG)传输到从数据库上,然后将这些日志重新执行(重做);从而使得从数据库的数据与主数据库保持一致。

48、MySQL主从复制的作用

  1. 主数据库出现问题,可以切换到从数据库。
  2. 可以进行数据库层面的读写分离。
  3. 可以在从数据库上进行日常备份。

49、MySQL主从复制工作原理

  • 在主库上把数据更高记录到二进制日志
  • 从库将主库的日志复制到自己的中继日志
  • 从库读取中继日志的事件,将其重放到从库数据中

50、什么是死锁?怎么解决?

死锁是指两个或多个事务在同一资源上相互占用,并请求锁定对方的资源,从而导致恶性循环的现象。

常见的解决死锁的方法

1、如果不同程序会并发存取多个表,尽量约定以相同的顺序访问表,可以大大降低死锁机会。

2、在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁产生概率;

3、对于非常容易产生死锁的业务部分,可以尝试使用升级锁定颗粒度,通过表级锁定来减少死锁产生的概率;

如果业务处理不好可以用分布式事务锁或者使用乐观锁

51、数据库的乐观锁和悲观锁是什么?怎么实现的?

数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和统一性以及数据库的统一性。乐观并发控制(乐观锁)和悲观并发控制(悲观锁)是并发控制主要采用的技术手段。

悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。在查询完数据的时候就把事务锁起来,直到提交事务。实现方式:使用数据库中的锁机制

乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。在修改数据的时候把事务锁起来,通过version的方式来进行锁定。实现方式:乐观锁一般会使用版本号机制或CAS算法实现。

两种锁的使用场景

从上面对两种锁的介绍,我们知道两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。

但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。

51、drop、delete与truncate的区别

三者都是表示删除,但是三者都有一些差别:

image-20230207095219639

因此,在不再需要一张表的时候,用drop;在想删除部分数据行时候,用 delete;在保留表而删除所有数据的时候用truncate。

52、UNION与UNIONALL的区别?

如果使用UNION ALL,不会合并重复的记录行 效率 UNION 高于 UNION ALL

53、按照锁的粒度分数据库锁有哪些?

在关系型数据库中,可以按照锁的粒度把数据库锁分为行级锁(INNODB引擎)、表级锁(MYISAM引擎)和页级锁(BDB引擎 )。

MyISAM和InnoDB存储引擎使用的锁:

MyISAM采用表级锁(table-level locking)。

InnoDB支持行级锁(row-level locking)和表级锁,默认为行级锁

行级锁,表级锁和页级锁对比

  1. 行级锁 行级锁是Mysql中锁定粒度最细的一种锁,表示只针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突。其加锁粒度最小,但加锁的开销也最大。行级锁分为共享锁 和 排他锁。
    1. 特点:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
  2. 表级锁 表级锁是MySQL中锁定粒度最大的一种锁,表示对当前操作的整张表加锁,它实现简单,资源消耗较少,被大部分MySQL引擎支持。最常使用的MYISAM与INNODB都支持表级锁定。表级锁定分为表共享读锁(共享锁)与表独占写锁(排他锁)。
    1. 特点:开销小,加锁快;不会出现死锁;锁定粒度大,发出锁冲突的概率最高,并发度最低。
  3. 页级锁 页级锁是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。
    1. 特点:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般

54、数据库中有六种触发器

  • Before Insert
  • After Insert
  • Before Update
  • After Update
  • Before Delete
  • After Delete

55、Hash索引和B+树索引有什么区别或者说优劣呢?

首先要知道Hash索引和B+树索引的底层实现原理:

hash索引底层就是hash表,进行查找时,调用一次hash函数就可以获取到相应的键值,之后进行回表查询获得实际数据.B+树底层实现是多路平衡查找树.对于每一次的查询都是从根节点出发,查找到叶子节点方可以获得所查键值,然后根据查询判断是否需要回表查询数据.

那么可以看出他们有以下的不同:hash索引进行等值查询更快(一般情况下),但是却无法进行范围查询.

因为在hash索引中经过hash函数建立索引之后,索引的顺序与原顺序无法保持一致,不能支持范围查询.而B+树的的所有节点皆遵循(左节点小于父节点,右节点大于父节点,多叉树也类似),天然支持范围.

  1. hash索引不支持使用索引进行排序,原理同上.

  2. hash索引不支持模糊查询以及多列索引的最左前缀匹配.原理也是因为hash函数的不可预测.AAAA和AAAAB的索引没有相关性.

  3. hash索引任何时候都避免不了回表查询数据,而B+树在符合某些条件(聚簇索引,覆盖索引等)的时候可以只通过索引完成查询.

  4. hash索引虽然在等值查询上较快,但是不稳定.性能不可预测,当某个键值存在大量重复的时候,发生hash碰撞,此时效率可能极差.而B+树的查询效率比较稳定,对于所有的查询都是从根节点到叶子节点,且树的高度较低.

    因此,在大多数情况下,直接选择B+树索引可以获得稳定且较好的查询速度.而不需要使用hash索引.

七、SSM面试题

Spring

1、什么是Spring:

Spring 是个java企业级应用的开源开发框架。Spring主要用来开发Java应用,但是有些扩展是针对构建J2EE平台的web应用。Spring 框架目标是简化Java企业级应用开发,并通过POJO为基础的编程模型促进良好的编程习惯。

2、你们项目中为什么使用Spring框架?

  • 轻量:Spring 是轻量的,基本的版本大约2MB。
  • 控制反转:Spring通过控制反转实现了松散耦合,对象们给出它们的依赖,而不是创建或查找依赖的对象们。
  • 面向切面的编程(AOP):Spring支持面向切面的编程,并且把应用业务逻辑和系统服务分开。
  • 容器:Spring 包含并管理应用中对象的生命周期和配置。
  • MVC框架:Spring的WEB框架是个精心设计的框架,是Web框架的一个很好的替代品。
  • 事务管理:Spring 提供一个持续的事务管理接口,可以扩展到上至本地事务下至全局事务
  • 异常处理:Spring 提供方便的API把具体技术相关的异常(比如由JDBC,Hibernate or JDO抛出的)转化为一致的unchecked 异常。

3、Autowired和Resource关键字的区别?

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

  1. 共同点两者都可以写在字段和setter方法上。两者如果都写在字段上,那么就不需要再写setter方法。
  2. 不同点(1)@Autowired为Spring提供的注解,需要导入包org.springframework.beans.factory.annotation.Autowired;只按照byType注入。
  3. @Autowired注解是按照类型(byType)装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它的required属性为false。如果我们想使用按照名称(byName)来装配,可以结合@Qualifier注解一起使用。
  4. (2)@Resource默认按照ByName自动注入,由J2EE提供,需要导入包javax.annotation.Resource。@Resource有两个重要的属性:name和type,而Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以,如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不制定name也不制定type属性,这时将通过反射机制使用byName自动注入策略。

4、依赖注入的方式有几种,各是什么?

  1. 构造器注入 将被依赖对象通过构造函数的参数注入给依赖对象,并且在初始化对象的时候注入。优点: 对象初始化完成后便可获得可使用的对象。缺点: 当需要注入的对象很多时,构造器参数列表将会很长; 不够灵活。若有多种注入方式,每种方式只需注入指定几个依赖,那么就需要提供多个重载的构造函数,麻烦。
  2. setter方法注入 IoC Service Provider通过调用成员变量提供的setter函数将被依赖对象注入给依赖类。优点: 灵活。可以选择性地注入需要的对象。缺点: 依赖对象初始化完成后由于尚未注入被依赖对象,因此还不能使用。
  3. 通过接口

5、说说你对Spring的IOC是怎么理解的?

  1. IOC就是控制反转,是指创建对象的控制权的转移。以前创建对象的主动权和时机是由自己把控的,而现在这种权力转移到Spring容器中,并由容器根据配置文件去创建实例和管理各个实例之间的依赖关系。对象与对象之间松散耦合,也利于功能的复用。DI依赖注入,和控制反转是同一个概念的不同角度的描述,即 应用程序在运行时依赖IoC容器来动态注入对象需要的外部资源。
  2. 最直观的表达就是,IOC让对象的创建不用去new了,可以由spring自动生产,使用java的反射机制,根据配置文件在运行时动态的去创建对象以及管理对象,并调用对象的方法的。
  3. Spring的IOC有三种注入方式 :构造器注入、setter方法注入、根据注解注入。IoC让相互协作的组件保持松散的耦合,而AOP编程允许你把遍布于应用各层的功能分离出来形成可重用的功能组件。

6、谈谈你对Spring的AOP理解

  • AOP(Aspect-Oriented Programming,面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可扩展性和可维护性。
  • Spring AOP是基于动态代理的,如果要代理的对象实现了某个接口,那么Spring AOP就会使用JDK动态代理去创建代理对象;而对于没有实现接口的对象,就无法使用JDK动态代理,转而使用CGlib动态代理生成一个被代理对象的子类来作为代理。
  • 当然也可以使用AspectJ,Spring AOP中已经集成了AspectJ,AspectJ应该算得上是Java生态系统中最完整的AOP框架了。使用AOP之后我们可以把一些通用功能抽象出来,在需要用到的地方直接使用即可,这样可以大大简化代码量。我们需要增加新功能也方便,提高了系统的扩展性。日志功能、事务管理和权限管理等场景都用到了AOP。

7、Spring AOP和AspectJ AOP有什么区别?

  • Spring AOP是属于运行时增强,而AspectJ是编译时增强。
  • Spring AOP基于代理(Proxying),而AspectJ基于字节码操作(Bytecode Manipulation)。
  • Spring AOP已经集成了AspectJ,AspectJ应该算得上是Java生态系统中最完整的AOP框架了。
  • AspectJ相比于Spring AOP功能更加强大,但是Spring AOP相对来说更简单。如果我们的切面比较少,那么两者性能差异不大。
  • 但是,当切面太多的话,最好选择AspectJ,它比SpringAOP快很多。

8、在Spring AOP 中,关注点和横切关注的区别是什么?

  • 关注点是应用中一个模块的行为,一个关注点可能会被定义成一个我们想实现的一个功能。 横切关注点是一个关注点,此关注点是整个应用都会使用的功能,并影响整个应用,比如日志,安全和数据传输,几乎应用的每个模块都需要的功能。因此这些都属于横切关注点。那什么是连接点呢?连接点代表一个应用程序的某个位置,在这个位置我们可以插入一个AOP切面,它实际上是个应用程序执行Spring AOP的位置。
  • 切入点是什么?切入点是一个或一组连接点,通知将在这些位置执行。可以通过表达式或匹配的方式指明切入点。

9、什么是通知呢?有哪些类型呢?

通知是个在方法执行前或执行后要做的动作,实际上是程序执行时要通过SpringAOP框架触发的代码段。

Spring切面可以应用五种类型的通知:

  • before:前置通知,在一个方法执行前被调用。
  • after: 在方法执行之后调用的通知,无论方法执行是否成功。
  • after-returning: 仅当方法成功完成后执行的通知。
  • after-throwing: 在方法抛出异常退出时执行的通知。
  • around: 在方法执行之前和之后调用的通知。

10、解释一下spring bean的生命周期

  • Bean 容器找到配置文件中 Spring Bean 的定义。
  • Bean 容器利用 Java Reflection API 创建一个 Bean 的实例。
  • 如果涉及到一些属性值 利用 set()方法设置一些属性值。
  • 如果 Bean 实现了 BeanNameAware 接口,调用 setBeanName()方法,传入 Bean 的名字。
  • 如果 Bean 实现了 BeanClassLoaderAware 接口,调用 setBeanClassLoader()方法,传入 ClassLoader对象的实例。
  • 如果 Bean 实现了 BeanFactoryAware 接口,调用 setBeanFactory()方法,传入 BeanFactory对象的实例。
  • 如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执行postProcessBeforeInitialization() 方法
  • 如果 Bean 实现了InitializingBean接口,执行afterPropertiesSet()方法。
  • 如果 Bean 在配置文件中的定义包含 init-method 属性,执行指定的方法。
  • 当要销毁 Bean 的时候,如果 Bean 实现了 DisposableBean 接口,执行 destroy() 方法。
  • 当要销毁 Bean 的时候,如果 Bean 在配置文件中的定义包含 destroy-method 属性,执行指定的方法。

11、解释Spring支持的几种bean的作用域?

  1. singleton:默认,每个容器中只有一个bean的实例,单例的模式由BeanFactory自身来维护。
  2. prototype:每次获取都会创建一个新的 bean 实例。也就是说,连续 getBean() 两次,得到的是不同的 Bean 实例。
  3. request:每一次 HTTP 请求都会产生一个新的 bean(请求 bean),该 bean 仅在当前 HTTP request 内有效。
  4. session: 每一次来自新 session 的 HTTP 请求都会产生一个新的 bean(会话 bean),该 bean 仅在当前 HTTP session 内有效。
  5. global-session:每个 Web 应用在启动时创建一个 Bean(应用 Bean),该 bean 仅在当前应用启动时间内有效。

12、静态代理和动态代理的区别

静态代理:只能代理一种业务,如果有多种业务需要编写多个代理类

动态代理:通过反射机制动态生成代理类,不需要手动编写,能够代理多种业务,更加灵活。

13、JDK动态代理和CGLIB动态代理的区别

Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理:

  • JDK动态代理只提供接口的代理,不支持类的代理。核心InvocationHandler接口和Proxy类,InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy利用 InvocationHandler动态创建一个符合某一接口的的实例, 生成目标类的代理对象。
  • 如果代理类没有实现 InvocationHandler 接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。

静态代理与动态代理区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。

14、Spring框架中都用到了哪些设计模式?

  1. 工厂设计模式 : Spring 使用工厂模式通过 BeanFactoryApplicationContext 创建 bean 对象。
  2. 代理设计模式 : Spring AOP 功能的实现。
  3. 单例设计模式 : Spring 中的 Bean 默认都是单例的。
  4. 模板方法模式 : Spring 中 jdbcTemplatehibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。
  5. 包装器设计模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
  6. 观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。
  7. 适配器模式 : Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配

15、说说Spring 中 ApplicationContext 和 BeanFactory 的区别

16、Spring 框架中的单例 Bean 是线程安全的么?

Spring 框架并没有对单例 Bean 进行任何多线程的封装处理。

  1. 关于单例 Bean 的线程安全和并发问题,需要开发者自行去搞定。
  2. 单例的线程安全问题,并不是 Spring 应该去关心的。Spring 应该做的是,提供根据配置,创建单例 Bean 或多例 Bean 的功能。

但实际上,大部分的 Spring Bean 并没有可变的状态,所以在某种程度上说 Spring 的单例Bean 是线程安全的。如果你的 Bean 有多种状态的话,就需要自行保证线程安全。最浅显的解决办法,就是将多态 Bean 的作用域(Scope)由 Singleton 变更为 Prototype。

17、Spring 是怎么解决循环依赖的?

循环依赖是指一个或多个Bean实例之间存在直接或间接的依赖关系,构成循环调用。通常表现为三种形态。互相依赖,也就是A依赖B,B依赖A

Spring设计了三级缓存来解决循环依赖问题。

  • 第一级缓存里面存储完整的Bean实例,这些实例是可以直接被使用的。
  • 第二级缓存里面存储的是实例化以后,但是还没有设置属性值的Bean实例,也就是Bean里面的依赖注入还没有做。
  • 第三级缓存用来存放Bean工厂,它主要用来生成原始Bean对象并且放到第二级缓存里面。三级缓存的核心思想,就是把Bean的实例化,和Bean里面的依赖注入进行分离。
  • 采用一级缓存存储完整的Bean实例,采用二级缓存来存储不完整的Bean实例,通过不完整的Bean实例作为突破口,解决循环依赖的问题。至于第三级缓存,主要是解决代理对象的循环依赖问题

18、Spring 管理事务的方式有几种?

  1. 编程式事务 :在代码中硬编码(不推荐使用) : 通过 TransactionTemplate或者 TransactionManager 手动管理事务,实际应用中很少使用,但是对于你理解 Spring 事务管理原理有帮助。
  2. 声明式事务 :在 XML 配置文件中配置或者直接基于注解(推荐使用) : 实际是通过 AOP 实现(基于@Transactional 的全注解方式使用最多)

19、Spring 事务中的隔离级别有哪几种?

  • TransactionDefinition.ISOLATION_DEFAULT :使用后端数据库默认的隔离级别,MySQL 默认采用的 REPEATABLE_READ 隔离级别 Oracle 默认采用的 READ_COMMITTED 隔离级别.
  • TransactionDefinition.ISOLATION_READ_UNCOMMITTED :最低的隔离级别,使用这个隔离级别很少,因为它允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
  • TransactionDefinition.ISOLATION_READ_COMMITTED : 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
  • TransactionDefinition.ISOLATION_REPEATABLE_READ : 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
  • TransactionDefinition.ISOLATION_SERIALIZABLE : 最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别

SpringMVC

1、SpringMVC的执行流程

image-20230209083915471

  1. 用户发送请求给前端控制器
  2. 前端控制器将请求的URL发送给处理器映射
  3. 处理器映射将请求里面的handler(包装的信息和方法)返回给前端控制器
  4. 前端控制器发送handler给适配器,适配器执行handler里的方法
  5. 适配器执行完毕后返回逻辑视图给前端控制器
  6. 前端控制器再将逻辑视图发送给视图解析器,解析成物理视图返回给前端控制器
  7. 前端控制器渲染视图,发送视图给用户

2、SpringMVC中的拦截器和Servlet中的Filter过滤器的区别?

拦截器:用于过滤请求,只能在SpringMVC中使用,效率高

过滤器:拦截范围更大

3、什么是Spring MVC?简单介绍下你对Spring MVC的理解?

首先,SpringMVC是是属于SpringFramework生态里面的一个模块,它是在Servlet基础上构建并且使用MVC模式设计的一个Web框架,主要的目的是简化传统Servlet+JSP模式下的Web开发方式。其次,SpringMVC的整体架构设计对JavaWeb里面的MVC架构模式做了增强和扩展,主要有几个方面。把传统MVC框架里面的Controller控制器做了拆分,分成了前端控制器DispatcherServlet和后端控制器Controller。把Model模型拆分成业务层Service和数据访问层Repository。在视图层,可以支持不同的视图,比如Freemark、velocity、JSP等等。所以,SpringMVC天生就是为了MVC模式而设计的,因此在开发MVC应用的时候会更加方便和灵活。SpringMVC的具体工作流程是,浏览器的请求首先会经过SpringMVC里面的核心控制器DispatcherServlet,它负责对请求进行分发到对应的Controller。Controller里面处理完业务逻辑之后,返回ModeAndView。然后DispatcherServlet寻找一个或者多个ViewResolver视图解析器,找到ModeAndView指定的视图,并把数据显示到客户端。

4、Spring MVC的控制器是不是单例模式,如果是,有什么问题,怎么解决?

答:是单例模式,所以在多线程访问的时候有线程安全问题,不要用同步,会影响性能的,解决方案是在控制器里面不能写字段。

5、MVC是什么?MVC设计模式的好处有哪些

mvc是一种设计模式(设计模式就是日常开发中编写代码的一种好的方法和经验的总结)。模型(model)-视图(view)-控制器(controller),三层架构的设计模式。用于实现前端页面的展现与后端业务数据处理的分离。

mvc设计模式的好处

  1. 分层设计,实现了业务系统各个组件之间的解耦,有利于业务系统的可扩展性,可维护性。
  2. 有利于系统的并行开发,提升开发效率。

6、Spring MVC常用的注解有哪些?

image-20230212224145023

7、Spring MVC怎么样设定重定向和转发的?

  1. 转发:在返回值前面加"forward:“,譬如"forward:user.do?name=method4”
  2. 重定向:在返回值前面加"redirect:“,如"redirect:http://www.baidu.com”

MyBatis

1、介绍一下MyBatis:

MyBatis 是一款优秀的持久层框架,一个半 ORM(对象关系映射)框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

2、ORM是什么

ORM(Object Relational Mapping),对象关系映射,是一种为了解决关系型数据库数据与简单Java对象(POJO)的映射关系的技术。简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系型数据库中。

3、为什么说Mybatis是半自动ORM映射工具?它与全自动的区别在哪里?

Hibernate属于全自动ORM映射工具,使用Hibernate查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的。

而Mybatis在查询关联对象或关联集合对象时,需要手动编写sql来完成,所以,称之为半自动ORM映射工具。

4、传统JDBC开发存在的问题

  • 频繁创建数据库连接对象、释放,容易造成系统资源浪费,影响系统性能。可以使用连接池解决这个问题。但是使用jdbc需要自己实现连接池。
  • sql语句定义、参数设置、结果集处理存在硬编码。实际项目中sql语句变化的可能性较大,一旦发生变化,需要修改java代码,系统需要重新编译,重新发布。不好维护。
  • 使用preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不一定,可能多也可能少,修改sql还要修改代码,系统不易维护。
  • 结果集处理存在重复代码,处理麻烦。如果可以映射成Java对象会比较方便

5、JDBC编程有哪些不足之处,MyBatis是如何解决这些问题的?

image-20230212221427378

6、Mybatis优缺点

7、Hibernate 和 MyBatis 的区别

相同点

都是对jdbc的封装,都是持久层的框架,都用于dao层的开发。

不同点

映射关系

  • MyBatis 是一个半自动映射的框架,配置Java对象与sql语句执行结果的对应关系,多表关联关系配置简单
  • Hibernate 是一个全表映射的框架,配置Java对象与数据库表的对应关系,多表关联关系配置复杂

SQL优化和移植性

  • Hibernate 对SQL语句封装,提供了日志、缓存、级联(级联比 MyBatis 强大)等特性,此外还提供 HQL(Hibernate Query Language)操作数据库,数据库无关性支持好,但会多消耗性能。如果项目需要支持多种数据库,代码开发量少,但SQL语句优化困难。
  • MyBatis 需要手动编写 SQL,支持动态 SQL、处理列表、动态生成表名、支持存储过程。开发工作量相对大些。直接使用SQL语句操作数据库,不支持数据库无关性,但sql语句优化容易。

开发难易程度和学习成本

  • Hibernate 是重量级框架,学习使用门槛高,适合于需求相对稳定,中小型的项目,比如:办公自动化系统
  • MyBatis 是轻量级框架,学习使用门槛低,适合于需求变化频繁,大型的项目,比如:互联网电子商务系统

总结

  • MyBatis 是一个小巧、方便、高效、简单、直接、半自动化的持久层框架,
  • Hibernate 是一个强大、方便、高效、复杂、间接、全自动化的持久层框架。

8、MyBatis编程步骤是什么样的?

1、 创建SqlSessionFactory

2、 通过SqlSessionFactory创建SqlSession

3、 通过sqlsession执行数据库操作

4、 调用session.commit()提交事务

5、 调用session.close()关闭会话

9、请说说MyBatis的工作原理

  1. 读取 MyBatis 配置文件:mybatis-config.xml 为 MyBatis 的全局配置文件,配置了 MyBatis 的运行环境等信息,例如数据库连接信息。
  2. 加载映射文件。映射文件即 SQL 映射文件,该文件中配置了操作数据库的 SQL 语句,需要在 MyBatis 配置文件 mybatis-config.xml 中加载。mybatis-config.xml 文件可以加载多个映射文件,每个文件对应数据库中的一张表。
  3. 造会话工厂:通过 MyBatis 的环境等配置信息构建会话工厂 SqlSessionFactory。
  4. 创建会话对象:由会话工厂创建 SqlSession 对象,该对象中包含了执行 SQL 语句的所有方法。
  5. Executor 执行器:MyBatis 底层定义了一个 Executor 接口来操作数据库,它将根据 SqlSession 传递的参数动态地生成需要执行的 SQL 语句,同时负责查询缓存的维护。
  6. MappedStatement 对象:在 Executor 接口的执行方法中有一个 MappedStatement 类型的参数,该参数是对映射信息的封装,用于存储要映射的 SQL 语句的 id、参数等信息。
  7. 输入参数映射:输入参数类型可以是 Map、List 等集合类型,也可以是基本数据类型和 POJO 类型。输入参数映射过程类似于 JDBC 对 preparedStatement 对象设置参数的过程。
  8. 输出结果映射:输出结果类型可以是 Map、 List 等集合类型,也可以是基本数据类型和 POJO 类型。输出结果映射过程类似于 JDBC 对结果集的解析过程。

10、Mybatis都有哪些Executor执行器?它们之间的区别是什么?

  • Mybatis有三种基本的Executor执行器,SimpleExecutor、ReuseExecutor、BatchExecutor。
    • SimpleExecutor:每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。
    • ReuseExecutor:执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map<String, Statement>内,供下一次使用。简言之,就是重复使用Statement对象。
    • BatchExecutor:执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。与JDBC批处理相同。

作用范围:Executor的这些特点,都严格限制在SqlSession生命周期范围内。

11、Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?

Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,association指的就是一对一,collection指的就是一对多查询。在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false。

它的原理是,使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB().getName()方法的调用。这就是延迟加载的基本原理。

当然了,不光是Mybatis,几乎所有的包括Hibernate,支持延迟加载的原理都是一样的。

12、MyBatis#{}和${}的区别

  • #{}是占位符,预编译处理;${}是拼接符,字符串替换,没有预编译处理。
  • Mybatis在处理#{}时,#{}传入参数是以字符串传入,会将SQL中的#{}替换为?号,调用PreparedStatement的set方法来赋值。
  • Mybatis在处理时 , 是 原 值 传 入 , 就 是 把 {}时,是原值传入,就是把时,是原值传入,就是把{}替换成变量的值,相当于JDBC中的Statement编译。
  • 变量替换后,#{} 对应的变量自动加上单引号 ‘’;变量替换后,${} 对应的变量不会加上单引号 ‘’
  • #{} 可以有效的防止SQL注入,提高系统安全性;${} 不能防止SQL 注入
  • #{} 的变量替换是在DBMS 中;${} 的变量替换是在 DBMS 外

13、MyBatis如何获取生成的主键

对于支持主键自增的数据库(MySQL)

image-20230212223456678

14、使用MyBatis的mapper接口调用时有哪些要求?

  1. Mapper接口方法名和mapper.xml中定义的每个sql的id相同。
  2. Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的parameterType的类型相同。
  3. Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同。
  4. Mapper.xml文件中的namespace即是mapper接口的类路径。

15、Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?

第一种是使用标签,逐一定义列名和对象属性名之间的映射关系。

第二种是使用sql列的别名功能,将列别名书写为对象属性名,比如T_NAME AS NAME,对象属性名一般是name,小写,但是列名不区分大小写,Mybatis会忽略列名大小写,智能找到与之对应对象属性名,你甚至可以写成T_NAME AS NaMe,Mybatis一样可以正常工作。

有了列名与属性名的映射关系后,Mybatis通过反射创建对象,同时使用反射给对象的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。

16、MyBatis实现一对一,一对多有几种方式,怎么操作的?

有联合查询和嵌套查询。联合查询是几个表联合查询,只查询一次,通过在resultMap里面的association,collection节点配置一对一,一对多的类就可以完成

嵌套查询是先查一个表,根据这个表里面的结果的外键id,去再另外一个表里面查询数据,也是通过配置association,collection,但另外一个表的查询通过select节点配置。

17、Mybatis动态sql是做什么的?都有哪些动态sql?能简述一下动态sql的执行原理不?

Mybatis动态sql可以让我们在Xml映射文件内,以标签的形式编写动态sql,完成逻辑判断和动态拼接sql的功能,Mybatis提供了9种动态sql标签trim|where|set|foreach|if|choose|when|otherwise|bind。

其执行原理为,使用OGNL从sql参数对象中计算表达式的值,根据表达式的值动态拼接sql,以此来完成动态sql的功能。

18、Mybatis是如何进行分页的?分页插件的原理是什么?

Mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页,可以在sql内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。

分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。
举例:select * from student,拦截sql后重写为:select t.* from (select * from student) t limit 0, 10

19、Mybatis的一级、二级缓存

  1. 一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存。
  2. 二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现Serializable序列化接口(可用来保存对象的状态),可在它的映射文件中配置 ;
  3. 对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear。

SSM

1、SSM分工:

(1)Spring 作为基础框架,整合其他框架

(2)SpringMVC 作为Web开发框架,提供服务期开发支持

(3)MyBatis 作为ORM框架,提供数据库开发支持

八、SpringBoot面试题

1、什么是SpringBoot

Spring Boot 是 Spring 开源组织下的子项目,是 Spring 组件一站式解决方案,主要是简化了使用 Spring 的难度,简省了繁重的配置,提供了各种启动器,开发者能快速上手。

2、Spring Boot 有哪些优点?

  1. 容易上手,提升开发效率,为 Spring 开发提供一个更快、更广泛的入门体验。
  2. 开箱即用,远离繁琐的配置。
  3. 提供了一系列大型项目通用的非业务性功能,例如:内嵌服务器、安全管理、运行数据监控、运行状况检查和外部化配置等。
  4. 没有代码生成,也不需要XML配置。
  5. 避免大量的 Maven 导入和各种版本冲突。

3、Spring Boot 的核心注解是哪个?它主要由哪几个注解组成的?

启动类上面的注解是@SpringBootApplication,它也是 Spring Boot 的核心注解,主要组合包含了以下 3 个注解:

@SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能。

@EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项,如关闭数据源自动配置功能: @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。

@ComponentScan:Spring组件扫描。

4、Spring Boot 自动配置原理是什么?

  • 自动装配,简单来说就是自动把第三方组件的Bean装载到SpringIOC器里面,不需要开发人员再去写Bean的装配配置。

  • 在SpringBoot应用里面,只需要在启动类加上@SpringBootApplication注解就可以实现自动装配。

  • @SpringBootApplication是一个复合注解,真正实现自动装配的注解是@EnableAutoConfiguration。

    自动装配的实现主要依靠三个核心关键技术。

  • 引入Starter启动依赖组件的时候,这个组件里面必须要包含@Configuration配置类,在这个配置类里面通过@Bean注解声明需要装配到IOC容器的Bean对象。

  • 这个配置类是放在第三方的jar包里面,然后通过SpringBoot中的约定优于配置思想,把这个配置类的全路径放在classpath:/META-INF/spring.factories文件中。这样SpringBoot就可以知道第三方jar包里面的配置类的位置,这个步骤主要是用到了Spring里面的SpringFactoriesLoader来完成的。

  • SpringBoot拿到所第三方jar包里面声明的配置类以后,再通过Spring提供的ImportSelector接口,实现对这些配置类的动态加载。

  • 在我看来,SpringBoot是约定优于配置这一理念下的产物,所以在很多的地方,都会看到这类的思想。它的出现,让开发人员更加聚焦在了业务代码的编写上,而不需要去关心和业务无关的配置。

  • 其实,自动装配的思想,在SpringFramework3.x版本里面的@Enable注解,就有了实现的雏形。@Enable注解是模块驱动的意思,我们只需要增加某个@Enable注解,就自动打开某个功能,而不需要针对这个功能去做Bean的配置,@Enable底层也是帮我们去自动完成这个模块相关Bean的注入。以上,就是我对SpringBoot自动装配机制的理解。

5、Spring Boot 配置加载顺序详解

1、properties文件;

2、YAML文件;

3、系统环境变量;

4、命令行参数;

6、YAML 配置的优势在哪里 ?

YAML 现在可以算是非常流行的一种配置文件格式了,无论是前端还是后端,都可以见到 YAML 配置。那么 YAML 配置和传统的 properties 配置相比到底有哪些优势呢?

  1. 配置有序,在一些特殊的场景下,配置有序很关键
  2. 支持数组,数组中的元素可以是基本数据类型也可以是对象
  3. 简洁

7、Spring Boot 中如何解决跨域问题 ?

跨域可以在前端通过 JSONP 来解决,但是 JSONP 只可以发送 GET 请求,无法发送其他类型的请求,在 RESTful 风格的应用中,就显得非常鸡肋,因此我们推荐在后端通过 (CORS,Cross-origin resource sharing) 来解决跨域问题。这种解决方案并非 Spring Boot 特有的,在传统的 SSM 框架中,就可以通过 CORS 来解决跨域问题,只不过之前我们是在 XML 文件中配置 CORS ,现在可以通过实现WebMvcConfigurer接口然后重写addCorsMappings方法解决跨域问题。

8、SpringBoot打成的jar和普通的jar有什么区别?

Spring Boot 项目最终打包成的 jar 是可执行 jar ,这种 jar 可以直接通过 java -jar xxx.jar 命令来运行,

这种 jar 不可以作为普通的 jar 被其他项目依赖,即使依赖了也无法使用其中的类。

Spring Boot 的 jar 无法被其他项目依赖,主要还是他和普通 jar 的结构不同。普通的 jar 包,解压后直、接就是包名,包里就是我们的代码,而 Spring Boot 打包成的可执行 jar 解压后,在 \BOOT-INF\classes目录下才是我们的代码,因此无法被直接引用。如果非要引用,可以在 pom.xml 文件中增加配置,将Spring Boot 项目打包成两个 jar ,一个可执行,一个可引用。

9、什么是YAML?

YAML是一种人类可读的数据序列化语言。它通常用于配置文件。 与属性文件相比,如果我们想要在配置文件中添加复杂的属性,YAML文件就更加结构化,而且更少混淆。可以看出YAML具有分层配置数据。

10、Spring Boot、Spring MVC 和 Spring 有什么区别?

SpringSpring最重要的特征是依赖注入。所有 SpringModules 不是依赖注入就是 IOC 控制反转。当我们恰当的使用 DI 或者是 IOC 的时候,我们可以开发松耦合应用。松耦合应用的单元测试可以很容易的进行。
Spring MVCSpring MVC 提供了一种分离式的方法来开发 Web 应用。通过运用像 DispatcherServelet,MoudlAndView 和 ViewResolver 等一些简单的概念,开发 Web 应用将会变的非常简单。
SpringBootSpring 和 SpringMVC 的问题在于需要配置大量的参数。

11、Spring Boot 中如何解决跨域问题 ?

跨域可以在前端通过 JSONP 来解决,但是 JSONP 只可以发送 GET 请求,无法发送其他类型的请求,在RESTful 风格的应用中,就显得非常鸡肋,因此我们推荐在后端通过 (CORS,Cross-origin resourcesharing) 来解决跨域问题。这种解决方案并非 Spring Boot 特有的,在传统的 SSM 框架中,就可以通过 CORS 来解决跨域问题,只不过之前我们是在 XML 文件中配置 CORS ,现在可以通过实现

WebMvcConfigurer接口然后重写addCorsMappings方法解决跨域问题。

12、什么是 CSRF 攻击?

CSRF 代表跨站请求伪造。这是一种攻击,迫使最终用户在当前通过身份验证的Web 应用程序上执行不需要的操作。CSRF 攻击专门针对状态改变请求,而不是数据窃取,因为攻击者无法查看对伪造请求的响应。

14、SpringBoot支持哪些日志框架?推荐和默认的日志框架是哪个?

Spring Boot 支持 Java Util Logging, Log4j2, Lockback 作为日志框架,如果你使用 Starters 启动器,Spring Boot 将使用 Logback 作为默认日志框架,但是不管是那种日志框架他都支持将配置文件输出到控制台或者文件中。

15、SpringBoot Starter的工作原理

我个人理解SpringBoot就是由各种Starter组合起来的,我们自己也可以开发Starter在sprinBoot启动时由@SpringBootApplication注解会自动去maven中读取每个starter中的spring.factories文件,该文件里配置了所有需要被创建spring容器中的bean,并且进行自动配置把bean注入SpringContext中 //(SpringContext是Spring的配置文件)

16、SpringBoot多数据源事务如何管理

  • 第一种方式是在service层的@TransactionManager中使用transactionManager指定DataSourceConfig中配置的事务
  • 第二种是使用jta-atomikos实现分布式事务管理

17、SpringBoot中如何实现定时任务 ?

  • 在 Spring Boot 中使用定时任务主要有两种不同的方式,一个就是使用 Spring 中的 @Scheduled 注解,另一-个则是使用第三方框架 Quartz。
  • 使用 Spring 中的 @Scheduled 的方式主要通过 @Scheduled 注解来实现。

18、spring-boot-starter-parent有什么用?

我们都知道,新创建一个 Spring Boot 项目,默认都是有 parent 的,这个 parent 就是 springboot-starter-parent ,spring-boot-starter-parent 主要有如下作用:1. 定义了 Java 编译版本为 1.8 。2. 使用 UTF-8 格式编码。3. 继承自 spring-boot-dependencies,这个里边定义了依赖的版本,也正是因为继承了这个依赖,所以我们在写依赖时才不需要写版本号。4. 执行打包操作的配置。5. 自动化的资源过滤。6. 自动化的插件配置。7. 针对 application.properties 和 application.yml 的资源过滤,包括通过 profile 定义的不同环境的配置文件,例如 application-dev.properties 和 application-dev.yml。

九、SpringCloud微服务面试题

1、什么是SpringCloud

Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、智能路由、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。Spring Cloud并没有重复制造轮子,它只是将各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。

2、互联网架构演进

1、单体应用架构

项目所有功能模块放在一个工程中编码、编译、打包部署在一个tomcat容器中。

简单实用、便于维护、成本低。

**优点:**高效开发、架构简单、易于测试、易于部署。

**缺点:**可靠性差、复杂性高、扩展能力受限。

2、垂直应用架构

为了业务之间互不影响,提高效率、减少组件之间的依赖。

优点:流量分担解决并发问题;

可以针对不同模块进行优化;

方便水平扩展、负载均衡、容错率高;

系统间相互独立,互不影响,业务迭代更高效。

**缺点:**服务之间相互调用,端口发生改变得手动改变;

搭建集群之后,实现负载均衡比较复杂;

服务之间调用方式不统一;

服务监控不到位。

3、SOA应用架构

Dubbo 高性能、轻量级的开源java RPC框架,可以和Spring框架无缝集成。

核心功能:面向接口的远程方法调用;

智能容错和负载均衡;

服务自动注册和发现。

**优点:**分布式、松耦合、扩展灵活、可重用。

缺点:服务抽取力度较大,服务调用方和提供耦合度较高。

4、微服务应用架构

拆分粒度更小,服务更独立。微小、独立、轻量级通信。

重点:业务需要彻底组件化服务化。

优点:

微小,便于特定服务的聚焦,解耦、便于实施敏捷开发。

便于重用和模块之间的组装

独立,松耦合,不同的微服务可以使用不同的语言开发。

更容易引进新的技术。

缺点:

分布式复杂难以管理。

分布式链路跟踪难。

3、什么是服务注册与服务发现。

服务注册:提供者将所提供服务的信息(服务器IP和端口、访问协议)

服务发现:消费者从中获取到服务列表。

Eureka 服务注册和发现可以在这种情况下提供帮助。由于所有服务都在 Eureka 服务器上注册并通过调用 Eureka 服务器完成查找,因此无需处理服务地点的任何更改和处理。

4、SpringBoot和SpringCloud的区别?

  • SpringBoot专注于快速方便的开发单个个体微服务。
  • SpringCloud是关注全局的微服务协调整理治理框架,它将SpringBoot开发的一个个单体微服务整合并管理起来,
  • 为各个微服务之间提供,配置管理、服务发现、断路器、路由、微代理、事件总线、全局锁、决策竞选、分布式会话等等集成服务
  • SpringBoot可以离开SpringCloud独立使用开发项目, 但是SpringCloud离不开SpringBoot ,属于依赖的关系
  • SpringBoot专注于快速、方便的开发单个微服务个体,SpringCloud关注全局的服务治理框架。

5、Spring Cloud的优缺点有哪些?

优点:

  1. 服务拆分粒度更细,有利于资源重复利用,有利于提高开发效率
  2. 可以更精准的制定优化服务方案,提高系统的可维护性
  3. 微服务架构采用去中心化思想,服务之间采用Restful等轻量级通讯,比ESB更轻量
  4. 适于互联网时代,产品迭代周期更短

缺点:

  1. 微服务过多,治理成本高,不利于维护系统
  2. 分布式系统开发的成本高(容错,分布式事务等)对团队挑战大

总的来说优点大过于缺点,目前看来SpringCloud是一套非常完善的分布式框架,目前很多企业开始用微服务、Spring Cloud的优势是显而易见的。因此对于想研究微服务架构的同学来说,学习 Spring Cloud 是一个不错的选择。

6、Eureka的服务治理是什么?

Spring Cloud 封装了 Netflix 公司开发的 Eureka 模块来实现服务治理。

在传统的rpc远程调用框架中,管理每个服务与服务之间依赖关系比较复杂,管理比较复杂,所以需要使用服务治理,管理服务于服务之间依赖关系,可以实现服务调用、负载均衡、容错等,实现服务发现与注册。

7、Eureka的服务注册是什么?

Eureka采用了CS的设计架构,Eureka Server 作为服务注册功能的服务器,它是服务注册中心。而系统中的其他微服务,使用 Eureka的客户端连接到 Eureka Server并维持心跳连接。这样系统的维护人员就可以通过 Eureka Server 来监控系统中各个微服务是否正常运行。

在服务注册与发现中,有一个注册中心。当服务器启动的时候,会把当前自己服务器的信息 比如 服务地址通讯地址等以别名方式注册到注册中心上。另一方(消费者|服务提供者),以该别名的方式去注册中心上获取到实际的服务通讯地址,然后再实现本地RPC调用RPC远程调用框架核心设计思想:在于注册中心,因为使用注册中心管理每个服务与服务之间的一个依赖关系(服务治理概念)。在任何rpc远程框架中,都会有一个注册中心(存放服务地址相关信息(接口地址))

8、什么是Spring Cloud Ribbon?

Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。

9、Nginx和Ribbon的区别

Nginx是反向代理同时可以实现负载均衡,nginx拦截客户端请求采用负载均衡策略根据upstream配置进行转发,相当于请求通过nginx服务器进行转发。Ribbon是客户端负载均衡,从注册中心读取目标服务器信息,然后客户端采用轮询策略对服务直接访问,全程在客户端操作

10、Ribbon支持的负载均衡算法

  1. 轮询策略
  2. 随机策略
  3. 重试策略
  4. 最小连接数策略
  5. 可用过略策略
  6. 区域权衡策略

11、服务器雪崩

一种因“服务提供者不可用”导致“服务调用这不可用”,并将不可用逐渐扩大的现象。

形成原因:

(1)服务提供者不可用(硬件故障、程序bug、缓存击穿、用户大量请求)

(2)重试加大请求流量(用户重试、代码逻辑重试)

(3)服务调者不可用(同步等待造成的资源耗尽)

解决方案:

(1)服务器熔断

(2)服务降级

(3)服务限流

12、Hystrix熔断器

一个延迟和容错库,用于隔离访问远程系统、服务或者第三方库,防止级联失败,提升系统的可用性与容错性。

  1. 包裹请求
  2. 跳闸机制
  3. 资源隔离
  4. 监控
  5. 回退机制
  6. 自我修复

13、Feign

轻量级HTTP服务客户端(发送请求、远程调用)。

本质:封装了Http调用流程,更符合面向接口化的编程习惯,类似于Dubbo的服务调用。

对熔断的支持:

客户端工程配置文件中开启支持,超时时长设置。

(1)开启Hystrix后,Feign中的方法进行一个管理,一旦出问题就进入对应的回退逻辑处理。

(2)当有两个超时时间设置,熔断的时候根据两个时间的最小值进行。

14、Gateway的作用

网关的作用是负载均衡、路由转发、请求过虑等。项目中网关与Nginx配合使用

15、Gateway的工作流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TwNNoD45-1676522455054)(https://edu–learn.oss-cn-beijing.aliyuncs.com/weixin/%E5%9B%BE%E7%89%873.png)]

16、Gateway的路由规则

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zjenkbPD-1676522455055)(https://edu–learn.oss-cn-beijing.aliyuncs.com/weixin/%E5%9B%BE%E7%89%874.png)]

17、Gateway的过滤使用

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qStL5wli-1676522455056)(https://edu–learn.oss-cn-beijing.aliyuncs.com/weixin/%E5%9B%BE%E7%89%875.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gvwwDUdy-1676522455056)(https://edu–learn.oss-cn-beijing.aliyuncs.com/weixin/%E5%9B%BE%E7%89%876.png)]

GlobalFilter全局过滤器是程序员使用比较多的过滤器,可以在过滤器中完成鉴权、流量控制、IP黑白名单、权限控制、日志监控等。

18、什么是Spring Cloud Alibaba?

Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案。此项目包含开发分布式应用微服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。

19、Spring Cloud Alibaba有哪些功能?

  • 服务限流降级:默认支持 WebServlet、WebFlux, OpenFeign、RestTemplate、Spring Cloud Gateway, Zuul, Dubbo 和 RocketMQ 限流降级功能的接入,可以在运行时通过控制台实时修改限流降级规则,还支持查看限流降级 Metrics 监控。
  • 服务注册与发现:适配 Spring Cloud 服务注册与发现标准,默认集成了 Ribbon 的支持。
  • 分布式配置管理:支持分布式系统中的外部化配置,配置更改时自动刷新。
  • 消息驱动能力:基于 Spring Cloud Stream 为微服务应用构建消息驱动能力。
  • 分布式事务:使用 @GlobalTransactional 注解, 高效并且对业务零侵入地解决分布式事务问题。
  • 阿里云对象存储:阿里云提供的海量、安全、低成本、高可靠的云存储服务。支持在任何应用、任何时间、任何地点存储和访问任意类型的数据。
  • 分布式任务调度:提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。同时提供分布式的任务执行模型,如网格任务。网格任务支持海量子任务均匀分配到所有 Worker(schedulerx-client)上执行。
  • 阿里云短信服务:覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建客户触达通道。

20、Spring Cloud Alibaba的常用组件有哪些?

Sentinel (opens new window):把流量作为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。

Nacos (opens new window):一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。

RocketMQ (opens new window):一款开源的分布式消息系统,基于高可用分布式集群技术,提供低延时的、高可靠的消息发布与订阅服务。

Dubbo (opens new window):Apache Dubbo™ 是一款高性能 Java RPC 框架。

Seata (opens new window):阿里巴巴开源产品,一个易于使用的高性能微服务分布式事务解决方案。

Alibaba Cloud OSS (opens new window): 阿里云对象存储服务(Object Storage Service,简称 OSS),是阿里云提供的海量、安全、低成本、高可靠的云存储服务。您可以在任何应用、任何时间、任何地点存储和访问任意类型的数据。

Alibaba Cloud SchedulerX (opens new window): 阿里中间件团队开发的一款分布式任务调度产品,提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。

Alibaba Cloud SMS (opens new window): 覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建客户触达通道。

21、什么是Nacos?

前面组件中也有简单介绍;Nacos命名的由来:前四个字母分别是Naming和Configuration的前两个字母,后面的s是Service

Nacos是一个易于使用的动态服务发现,配置和服务管理平台,用于构建云本机应用程序。使用Spring Cloud Alibaba Nacos Discovery,您可以基于Spring Cloud的编程模型快速访问Nacos服务注册功能。

Nacos就是注册中心+配置中心的组合

22、Nacos有什么作用?

  • 替代Eureka做服务注册
  • 替代Config做配置中心
  1. 服务发现与服务健康检查 Nacos使服务更容易注册,并通过DNS或HTTP接口发现其他服务,Nacos还提供服务的实时健康检查,以防 止向不健康的主机或服务实例发送请求。
  2. 动态配置管理 动态配置服务允许您在所有环境中以集中和动态的方式管理所有服务的配置。Nacos消除了在更新配置时重新 部署应用程序,这使配置的更改更加高效和灵活。
  3. 动态DNS服务 Nacos提供基于DNS 协议的服务发现能力,旨在支持异构语言的服务发现,支持将注册在Nacos上的服务以 域名的方式暴露端点,让三方应用方便的查阅及发现。
  4. 服务和元数据管理 Nacos 能让您从微服务平台建设的视角管理数据中心的所有服务及元数据,包括管理服务的描述、生命周 期、服务的静态依赖分析、服务的健康状态、服务的流量管理、路由及安全策略。

23、什么是Spring Cloud Seata?

Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。

十、Redis面试题

1、为什么要用缓存

使用缓存的目的就是提升读写性能。而实际业务场景下,更多的是为了提升读性能,带来更好的性能,带来更高的并发量。Redis的读写性能比Mysql好的多,我们就可以把Mysql中的热点数据缓存到Redis中,提升读取性能,同时也减轻了Mysql的读取压力。

2、使用 Redis有哪些好处?

具有以下好处:

  • 读取速度快,因为数据存在内存中,所以数据获取快;
  • 支持多种数据结构,包括字符串、列表、集合、有序集合、哈希等;
  • 支持事务,且操作遵守原子性,即对数据的操作要么都执行,要么都不支持;
  • 还拥有其他丰富的功能,队列、主从复制、集群、数据持久化等功能。

3、什么是 Redis?

Redis 是一个开源(BSD 许可)、基于内存、支持多种数据结构的存储系统,可以作为数据库、缓存和消息中间件。它支持的数据结构有字符串(strings)、哈希(hashes)、列表(lists)、集合(sets)、有序集合(sorted sets)等,除此之外还支持 bitmaps、hyperloglogs 和地理空间(geospatial )索引半径查询等功能。

4、为什么Redis单线程模型效率也能那么高?

  1. C语言实现,效率高
  2. 纯内存操作
  3. 基于非阻塞的IO复用模型机制
  4. 单线程的话就能避免多线程的频繁上下文切换问题
  5. 丰富的数据结构(全称采用hash结构,读取速度非常快,对数据存储进行了一些优化,比如亚索表,跳表等)

5、为什么 Redis 需要把所有数据放到内存中?

Redis 将数据放在内存中有一个好处,那就是可以实现最快的对数据读取,如果数据存储在硬盘中,磁盘 I/O 会严重影响 Redis 的性能。而且 Redis 还提供了数据持久化功能,不用担心服务器重启对内存中数据的影响。其次现在硬件越来越便宜的情况下,Redis 的使用也被应用得越来越多,使得它拥有很大的优势。

6、Redis 的同步机制了解是什么?

Redis 支持主从同步、从从同步。如果是第一次进行主从同步,主节点需要使用 bgsave 命令,再将后续修改操作记录到内存的缓冲区,等 RDB 文件全部同步到复制节点,复制节点接受完成后将RDB 镜像记载到内存中。等加载完成后,复制节点通知主节点将复制期间修改的操作记录同步到复制节点,即可完成同步过程。

7、说一下Redis有什么优点和缺点

优点

  • 速度快:因为数据存在内存中,类似于HashMap ,HashMap的优势就是查找和操作的时间复杂度都是O (1) 。
  • 支持丰富的数据结构:支持 String ,List,Set,Sorted Set,Hash 五种基础的数据结构。
  • 持久化存储:Redis 提供 RDB 和 AOF 两种数据的持久化存储方案,解决内存数据库最担心的万一 Redis 挂掉,数据会消失掉
  • 高可用:内置 Redis Sentinel ,提供高可用方案,实现主从故障自动转移。 内置Redis Cluster,提供集群方案,实现基于槽的分片方案,从而支持更大的 Redis 规模。
  • 丰富的特性:Key过期、计数、分布式锁、消息队列等。

缺点

  • 由于 Redis 是内存数据库,所以,单台机器,存储的数据量,跟机器本身的内存大小。虽然 Redis本身有 Key 过期策略,但是还是需要提前预估和节约内存。如果内存增长过快,需要定期删除数据。
  • 如果进行完整重同步,由于需要生成 RDB文件,并进行传输,会占用主机的 CPU ,并会消耗现网的带宽。不过 Redis2.8 版本,已经有部分重同步的功能,但是还是有可能有完整重同步的。比如,新上线的备机。
  • 修改配置文件,进行重启,将硬盘中的数据加载进内存,时间比较久。在这个过程中,Redis不能提供服务。

8、Redis的淘汰策略

Redis中共有八种内存淘汰策略:

  1. Volatile-lru: 设置了过期时间的Key使用了LRU算法淘汰;
  2. Allkeys-lru: 所有key使用LRU算法;
  3. Volatile-lfu: 设置了过期时间的key使用了LFU算法淘汰;
  4. Allkeys-lfu: 所有key使用了LFU算法淘汰;
  5. Volatile-random: 设置了过期时间的key使用随机淘汰;
  6. Allkeys-random: 所有key使用随机淘汰;
  7. Volatile-ttl: 设置了过期时间的key根据过期时间淘汰,越早过期越早淘汰;
  8. Noeviction: 默认策略,当内存达到设置的最大值时,所有申请内存的操作都会报错,(如set,ipush等),只读操作如get命令可以正常执行.

主要是4种算法,针对不同的key,形成的策略。

算法

  1. lru 最近很少的使用的key(根据时间,最不常用的淘汰)
  2. lfu 最近很少的使用的key (根据计数器,用的次数最少的key淘汰)
  3. random 随机淘汰
  4. ttl 快要过期的先淘汰

9、Redis 为什么设计成单线程的?

多线程处理会涉及到锁,并且多线程处理会涉及到线程切···换而消耗 CPU。采用单线程,避免了不必要的上下文切换和竞争条件。其次 CPU 不是 Redis 的瓶颈,Redis 的瓶颈最有可能是机器内存或者网络带宽。

10、Redis五种数据类型

  • String:key-value(做缓存)
  • Hash:key-fields-values(做缓存)
  • List:有顺序可重复Set:无顺序,不能重复
  • SortedSet:有顺序,不能重复

11、Redis集群一般需要几台服务器?

因为redis集群至少需要三个节点,要保证集群的高可用,每个节点都要一个备份机.理论上也需要6台虚拟机

12、什么是缓存穿透,如何避免

  • 一般的缓存系统,都是按照key去缓存查询,如果不存在对应的value,就应该去后端系统查找(比如DB)。一些恶意的请求会故意查询不存在的key,请求量很大,就会对后端系统造成很大的压力。这就叫做缓存穿透
  • 解决方案
    • 对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该key对应的数据insert了之后清理缓存
    • 对一定不存在的key进行过滤。可以把所有的可能存在的key放到一个大的Bitmap中,查询时通过该bitmap过滤

13、什么是缓存雪崩,如何避免

  • 当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,会给后端系统带来很大压力。导致系统崩溃
  • 解决方案:
    • 在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待
    • 做二级缓存,A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期
    • 不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀

14、Redis 的持久化机制是什么?各自的优缺点?

持久化机制:

  • RDB(默认)Redis DataBase
  • AOF Append Only File

RDB:

RDB是Redis默认的持久化方式。

工作机制:每隔一段时间,就把内存中的数据保存到硬盘上的指定文件中。对应产生的数据文件为dump.rdb

触发RDB的方式有两种:手动触发和自动触发

优点:

  • 只有一个文件 dump.rdb,方便持久化。
  • 容灾性好,一个文件可以保存到安全的磁盘。
  • 性能最大化,fork 子进程来完成写操作,让主进程继续处理命令,所以是 IO 最大化。使用单独子进程来进行持久化,主进程不会进行任何 IO 操作,保证了 redis 的高性能
  • 相对于数据集大时,比 AOF 的启动效率更高。

缺点:

  • 数据安全性低。RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候。如果redis要故障时要尽可能少的丢失数据,RDB没有AOF好,例如1:00进行的快照,在1:10又要进行快照的时候宕机了,这个时候就会丢失10分钟的数据。
  • RDB每次fork出子进程来执行RDB快照生成文件时,如果文件特别大,可能会导致客户端提供服务暂停数毫秒或者几秒

AOF:

AOF 是 以日志的形式来记录每个写操作,将每一次对数据进行修改,都把新建、修改数据的命令保存到指 定文件中。Redis 重新启

动时读取这个文件,重新执行新建、修改数据的命令恢复数据。

当两种方式同时开启时,数据恢复Redis会优先选择AOF恢复

优点:

  • 数据安全,AOF持久化可以配置 appendfsync 属性,有 always,每进行一次 命令操作就记录到 AOF文件中一次。

  • 通过 append 模式写文件,即使中途服务器宕机,可以通过 redis-check-aof 工具解决数据一致性问题。

  • AOF日志文件的命令通过非常可读的方式进行记录,这个非常适合做灾难性的误删除紧急恢复,如果某人不小心用flushall命令

    清空了所有数据,只要这个时候还没有执行rewrite,那么就可以将日志文件中的flushall删除,进行恢复

缺点:

  • 对于同一份文件AOF文件比RDB数据快照要大。
  • AOF开启后支持写的QPS会比RDB支持的写的QPS低,因为AOF一般会配置成每秒fsync操作,每秒的fsync操作还是很高的、
  • 数据恢复比较慢,不适合做冷备。

十一、中间件相关面试题

RabbitMQ

1、MQ的优势

  1. 应用解耦 : 服务与服务之间不再约定协议而对接接口,而是通过生产者/消费者的模式让中间件的MQ来对接两边的数据通信实现解耦合(扩展性更强)。
  2. 一步提速: 常见的服务通信:需要阻塞成功获取到响应状态在写入数据到数据库,阻塞同步往下执行。加入MQ后:提升用户体验和系统吞吐量。
  3. 削峰填谷 :
    1. 常见的服务通信:当一瞬间有5000个请求给到服务器时,服务器最大能处理1000请求,受不了了当场去世。
    2. 加入MQ后:先把请求丢到队列中等待处理,系统每秒从mq中拉取1000个请求进行处理(刚好卡在能处理的1000个,真实压榨案例),这样就变成了从1秒钟处理5000个请求的高峰期,拆分成了5秒钟,每秒钟处理1000请求的平缓处理器,哦不,是满载处理器。

2、你们项目中用到过 MQ 吗?谈谈你对 MQ 的理解?

我们项目中使用 MQ 主要是做一些异步的操作,比如说我们的接口是属于http协议接口,它是同步调用,如果说接口响应比较慢的情况下,会导致客户端同步阻塞等待,客户端同步阻塞等待的情况下,会引发客户端超时,客户端一旦发生超时,就会导致客户端触发一些重试的策略,而在触发重试的策略过程中会导致业务可能会发生重复执行,所以我们就需要注意一些幂等性问题,而接口因为它执行比较耗时,所以呢,我们会将一些执行比较耗时的业务逻辑代码把他直接改成使用 mq 异步去实现,能够提高就是我们的 http 协议接口的响应速度,比如说异步的去扣库存呢,异步的去发短信呢这些场景下我们都是使用到 MQ来进行实现落地的。

3、常用的MQ

(1)ActiveMQ:支持万级的吞吐量,较成熟完善;

(2)RabbitMQ:延时低,微妙级延时,

(3)RocketMQ:阿里维护的消息中间件,可以达到十万级的吞吐量,支持分布式事务。

(4)Kafka:阿里维护的消息中间件,可以达到十万级的吞吐量,支持分布式事务。

4、MQ消费者消费消息的顺序一致性问题?

生产者投递消息过程当中可以设定一个消息 key,相同的业务逻辑呢可以设定一个相同的消息 key 在做 hash 的时候最终都会落地到同一个分区来就行存放,最终就被同一个消费者进行消费,所以想解决消息顺序这些问题,它的核心思想就是,让我们的这个消息投递存放到同一个分区里面,最终被同一个消费者消费。

5、RabbitMQ如何保证数据⼀致性?

  1. ⽣产者确认机制:消息持久化后异步回调通知⽣产者,保证消息已经发出去;
  2. 消息持久化:设置消息持久化;
  3. 消费者确认机制:消费者成功消费消息之后,⼿动确认,保证消息已经消费。

6、如何确保消息正确地发送⾄RabbitMQ?

RabbitMQ使⽤发送⽅确认模式,确保消息正确地发送到RabbitMQ。
发送⽅确认模式:将信道设置成confirm模式(发送⽅确认模式),则所有在信道上发布的消息都会被指派⼀个唯⼀的ID。⼀旦消息被投递到⽬的队列后,或者消息被写⼊磁盘后(可持久化的消息),信道会发送⼀个确认给⽣产者(包含消息唯⼀ID)。如果RabbitMQ发⽣内部错误从⽽导致消息丢失,会发送⼀条nack(not acknowledged,未确认)消息。发送⽅确认模式是异步的,⽣产者应⽤程序在等待确认的同时,可以继续发送消息。当确认消息到达⽣产者应⽤程序,⽣产者应⽤程序的回调⽅法就会被触发来处理确认消息。

7、如何避免消息重复投递或重复消费?

在消息⽣产时,MQ内部针对每条⽣产者发送的消息⽣成⼀个inner-msg-id,作为去重和幂等的依据(消息投递失败并重传),避免重复的消息进⼊队列;在消息消费时,要求消息体中必须要有⼀个bizId(对于同⼀业务全局唯⼀,如⽀付ID、订单ID、帖⼦ID等)作为去重和幂等的依据,避免同⼀条消息被重复消费。

8、RabbitMQ的工作模式

一.simple模式(即最简单的收发模式)

image-20230213113843710

二.work工作模式(资源的竞争)

image-20230213113858209

三.publish/subscribe发布订阅(共享资源)

image-20230213113921225

四.routing路由模式

image-20230213113949489

五.topic 主题模式(路由模式的一种)

image-20230213114007036

ElasticSearch

1、elasticsearch 了解多少,说说你们公司 es 的集群架构,索引数据大小,分片有多少,以及一些调优手段

  • 面试官:想了解应聘者之前公司接触的 ES 使用场景、规模,有没有做过比较大规模的索引设计、规划、调优。
  • 解答:如实结合自己的实践场景回答即可。
  • 比如:ES 集群架构 13 个节点,索引根据通道不同共 20+索引,根据日期,每日递增 20+,索引:10分片,每日递增 1 亿+数据,每个通道每天索引大小控制:150GB 之内。
  • 仅索引层面调优手段:
    • 根据业务增量需求,采取基于日期模板创建索引,通过 roll over API 滚动索引;
    • 使用别名进行索引管理;
    • 每天凌晨定时对索引做 force_merge 操作,以释放空间;
    • 采取冷热分离机制,热数据存储到 SSD,提高检索效率;冷数据定期进行 shrink操作,以缩减存储;
    • 采取 curator 进行索引的生命周期管理;
    • 仅针对需要分词的字段,合理的设置分词器;
    • Mapping 阶段充分结合各个字段的属性,是否需要检索、是否需要存储等。

2、为什么要使用elasticsearch

因为在我们课程中的数据,将来会非常多,所以采用以往的模糊查询,模糊查询前置配置,会放弃索引,导致课程查询是全表扫面,在百万级别的数据库中,效率非常低下,而我们使用ES做一个全文索引,我们将经常查询的课程的某些字段,比如说课程名,描述、价格还有id这些字段我们放入我们索引库里,可以提高查询速度。

3、项目中如何使用elasticsearch

在服务启动时将课程数据从数据库查询出来压入es中,在用户进行课程搜索时,可以通过课程关键词、讲师名称和价格区间进行数据搜索,通过关键词进行搜索时,使用了es 的高亮和分页的工具类,对查询出来的数据与查询字段相同的进行高亮处理并通过匹配度的高低进行排序。

4、谈谈分词与倒排索引的原理

首先说分词是给检索用的。

  • 英文:一个单词一个词,很简单。I am a student,词与词之间空格分隔。
  • 中文:我是学生,就不能一个字一个字地分,我-是-学生。这是好分的。还有歧义的,使用户放心,使用-户,使-用户。人很容易看出,机器就难多了。所以市面上有各种各样的分词器,一个强调的效率一个强调的准确率。

倒排索引:倒排针对的是正排。

  1. 正排就是我记得我电脑有个文档,讲了 ES 的常见问题总结。那么我就找到文档,从上往下翻页,找到ES的部分。通过文档找文档内容。
  2. 倒排:一个txt文件ES的常见问题 -> D:/分布式问题总结.doc。

所以倒排就是文档内容找文档。当然内容不是全部的,否则也不需要找文档了,内容就是几个分词而已。这里的txt就是搜索引擎。

Sharing-JDBC

1、Shards&Replicas分片和复制:

****分片:****将索引划分成多份,可以放置在集群任何节点上。1、允许水平分割/扩展内容数量;2、允许在分片上进行分布式、并行。

****复制:****创建分片的一份或多份拷贝。1、在分片/节点失败的情况下,提高了高可用性;2、搜索可以在所有复制上运行;3、分片和复制的数量可以在索引创建的时候指定。4、复制数量可以在之后动态改变,分片数量事后不能再改变。

2、、分库分表

*作用:缓解单机和单库带来的性能瓶颈和压力,突破网络IO、硬件资源、连接数的瓶颈。*

****方式:****垂直分库、水平分库、垂直分表、水平分表。

*垂直拆分原则:*(1)不常用的字段单独放一张表。

​ (2)text、blob等大字段拆分放在附表。

​ (3)经常组合查询的列放在同一张表中。

3、垂直分库

按照业务进行分类,分不到不同的数据库上,每个库可以放在不同的数据库上(专库专用)。

*好处:*(1)业务层面解耦

(2)对不同的业务数据进行分级管理、维护、监控、扩展。

(3)高并发场景下,提升IO、数据库连接数、降低单机硬件资源的瓶颈。

4、水平分库

把同一张表的数据按一定规则拆到不同的数据库中,每个库可以放在不同的服务器上。

****好处:****解决了单库大数据,高并发的性能瓶颈,提高了系统的稳定性和可用性。

5、垂直分表

将一个表按照字段分成多表,每个表存储一部分字段。

****好处:(****1)避免IO争抢,减少锁表的几率。

(2)充分发挥热门数据的操作效率。

6、水平分表

在同一个数据库内,把同一个表的数据按一定规则拆到多个表中。

****好处:****优化单一表数据量过大而产生的性能问题,避免IO争抢,减少锁表的几率。

7、Sharing-JDBC定义

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7CeRcw3T-1676522455060)(https://edu–learn.oss-cn-beijing.aliyuncs.com/weixin/%E5%9B%BE%E7%89%877.png)]

*当当网研发的开源分布式数据库中间件。*

Docker

1、简介

Docker是一个开源的应用容器引擎,开发者可以打包自己的应用到容器里面,然后迁移到其他机器的docker应用中,可以实现快速部署。

2、Docker基本概念

Client(客户端): 是Docker的用户端,可以接受用户命令和配置标识,并与Docker daemon通信。

Images(镜像): 是一个只读模板,含创建Docker容器的说明,它与操作系统的安装光盘有点像。

Containers(容器): 镜像的运行实例,镜像与容器的关系类比面向对象中的类和对象。

Registry(仓库): 是一个集中存储与分发镜像的服务。最常用的Registry是官方的Docker Hub 。

十二、数据结构与算法面试题

1.1 概述

​ 数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。数据结构往往同高效的检索算法和索引技术有关。

​ JAVA中常见的数据结构有

  • 数组
  • 栈(Stack)
  • 队列(Queue)
  • 链表(LinkedList)
  • 树(Tree)
  • 哈希表(Hash)

1.2 数组

​ 数组是内存中连续保存的多个相同类型的元素的数据结构。通过下标索引访问数组中的元素,索引从0开始。根据维度可以分为1维数组、2维数组……N维数组。

  • 优点

    通过下标直接定位元素位置,查找效率高。

  • 缺点

    • 长度固定
    • 数据类型都必须相同
    • 插入跟删除元素时效率比较低。

适合场景:大量查询,很少删除和插入。

1.3 栈

1.3.1 概述

​ 栈结构只能在一端操作,该操作端叫做栈顶,另一端叫做栈底。栈结构按照“后进先出”(Last In First Out, LIFO)的方式处理结点数据。

1.4 队列

​ 队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。

1.5 链表

​ 链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针连接次序实现的。每一个链表都包含多个节点,节点又包含两个部分,一个是数据域(储存节点含有的信息),一个是引用域(储存下一个节点或者上一个节点的地址)。

链表的特点

  • 获取数据麻烦,需要遍历查找,比数组慢
  • 插入、删除的效率比较高

链表从结构上分为单向链表双向链表

1.6 树

1.6.1 树

​ 树(Tree)是n(n≥0)个结点的有限集T,并且当n>0时满足下列条件:
​ (1)有且仅有一个特定的称为根(Root)的结点;
​ (2)当n>1时,其余结点可以划分为m(m>0)个互不相交的有限集T1、T2 、…、Tm,每个集Ti(1≤i≤m)均为树,且称为树T的子树(SubTree)。
​ 特别地,不含任何结点(即n=0)的树,称为空树。

1.6.2 二叉树

​ 二叉树(Binary Tree)是有限个节点的集合,这个集合可以是空集,也可以是一个根节点和颗不相交的子二叉树组成的集合,其中一颗树叫根的左子树,另一颗树叫右子树。所以二叉树是一个递归地概念。

1.6.3 满二叉树

​ 一棵满二叉树就是高度为k,且拥有(2^k)-1个节点的二叉树,一棵满二叉树每个节点,要么都有两棵子树,要么都没有子树;而且每一层所有的节点之间必须要么都有两棵子树,要么都没子树。

1.6.4 完全二叉树

完全二叉树是一颗特殊的二叉树,它遵循以下规则:

假设完全二叉树高度为k,则完全二叉树需要符合以下两点:

  • 所有叶子节点都出现在k层或k-1层,并且从1~k-1层必须达到最大节点数。
  • 第k层可以是不满的,但是第k层的所有节点必须集中在最左边。

1.6.5 二叉查找树

​ 二叉查找树(BinarySearch Tree,也叫二叉搜索树,或二叉排序树BinarySort Tree)或者是一棵空树,或者是具有下列性质的二叉树:

​ 1、若任意结点的左子树不空,则左子树上所有结点的值均小于它的根结点的值;

​ 2、若任意结点的右子树不空,则右子树上所有节点的值均大于它的根结点的值;

​ 3、任意结点的左右子树也分别为二叉查找树;

​ 4、没有键值相等的结点。

在实际应用中,二叉查找树的使用比较多。

1.6.6 平衡二叉树

​ 平衡二叉树(AVL)是一种特殊的二叉查找树,其中每一个节点的左子树和右子树的高度差至多等于1。从平衡二叉树的名字中可以看出来,它是一种高度平衡的二叉排序树。那么什么叫做高度平衡呢?意思就是要么它是一颗空树,要么它的左子树和右子树都是平衡二叉树,且左子树和右子树的深度只差的绝对值绝对不超过1。

1.6.7 红黑树

​ 红黑树(Red Black Tree) 是一种自平衡二叉查找树。

红黑树的特性
(1)每个节点或者是黑色,或者是红色。
(2)根节点是黑色。
(3)每个叶子节点(NIL)是黑色。
(4)如果一个节点是红色的,则它的子节点必须是黑色的。[注意:这里叶子节点,是指为空(NIL)的虚节点!]
(5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。

红黑树跟平衡二叉树的区别

  1. 红黑树放弃了追求完全平衡,追求大致平衡,保证每次插入最多只需要三次旋转就能达到平衡,实现起来也更为简单。

  2. 平衡二叉树追求绝对平衡,条件比较苛刻,实现起来比较麻烦,每次插入新节点之后需要旋转的次数不能预知。

1.7 哈希表

​ 哈希表(Hash table,也叫散列表),是根据关键码值(Key)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列(哈希)函数,存放记录的数组叫做散列表。

1.7.2 哈希冲突

​ 对应不同的关键字可能获得相同的hash地址,即 key1≠key2,但是f(key1)=f(key2)。这种现象就是冲突,而且这种冲突只能尽可能的减少,不能完全避免。

常用的冲突解决办法:

  • 开放地址法(线性探测法)

    当冲突法生时,继续往后查找数组的下一个空位,并将数据填入。比如1和101,1占据了一个位置,101进入时候就向下查找,找到下面的一个空位插入, 如果没有继续查找空位,直到找到为止并进行插入。

  • 链地址法(拉链法)

    将产生冲突的值以链表的形式连起来。

1.8 对比

每种数据结构有自己的特点跟优缺点,见如下表

数据结构优 点缺 点
数组插入快查找慢,删除慢,大小固定,只能存储单一元素
提供后进先出的存取方式存取其他项很慢
队列提供先进先出的存取方式存取其他项很慢
链表插入快,删除快查找慢
二叉树如果树是平衡的,则查找、插入、删除都快删除算法复杂
红黑树查找、删除、插入都快。树总是平衡算法复杂
哈希表如果关键字已知则存取极快删除慢,如果不知道关键字存取慢,对存储空间使用不充分

2、简单算法

    /**
     * 冒泡排序
     * @param arr
     * @return
     */
    public static int[] MaoPao(int [] arr){
        int temp = 0;
        for (int i = 1; i < arr.length; i++) {
            for (int j = 1; j < arr.length-1; j++) {
                if (arr[j]>arr[j+1]){
                    temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                }
            }
        }
        return arr;
    }

    /**
     * 选择排序
     * @param arr
     * @return
     */
    public static int [] XuanZe(int [] arr){
        for (int i = 0; i < arr.length-1; i++) {
            int temp = 0;
            int index = i;
            for (int j = i+1; j < arr.length; j++) {
                if (arr[index]>arr[j]){
                    index = j;
                }
            }
            temp = arr[index];
            arr[index] = arr[i];
            arr[i] = temp;
        }
        return arr;
    }

    /**
     * 二分查找
     * @param arr
     * @param value
     * @return
     */
    public static int ErFen(int[] arr,int value){
        int low = 0;
        int high = arr.length-1;
        while (low<=high){
            int mid = (low+high)/2;
            if (arr[mid]==value){
                return mid;
            }else if(arr[mid]<value){
                low = mid +1;
            }else {
                high = mid -1;
            }
        }
        return -1;
    }

3、懒汉和饿汉

/**
 * 饿汉
 */
public class EHan {
    private static EHan eHan = new EHan();
    private EHan(){}
    private static EHan getEHan(){
        return eHan;
    }
}

/**
 * 懒汉
 */
public class LanHan {
    private static LanHan lanHan = null;
    private LanHan(){
    }
    private static LanHan getLanHan(){
        if (lanHan == null){
            lanHan = new LanHan();
        }
        return lanHan;
    }
}

冒泡排序

// 优化后的冒泡排序
    public static void main(String[] args) {
        int[] arr = {3,5,6,8,9,1,2,4,7,7};
        int k = arr.length-1;//初始化第一次排序的遍历范围
        if (k > 0){
            // 循环次数
            for (int i = arr.length-1; i>1; i--) {
                // 比较次数
                for (int j = 0; j < i; j++) {
                    // 交换位置
                    if(arr[j] > arr[j+1]){
                        arr[j] = arr[j]^arr[j+1];
                        arr[j+1] = arr[j]^arr[j+1];
                        arr[j] = arr[j]^arr[j+1];
                        k = j;
                        System.out.println("j = " + j);
                    }
                }
            }
        }
        System.out.println("arr = " + Arrays.toString(arr));
    }

选择排序

int[] arr = {3,5,2,6,1,8,9,1,2,4,0};
        int count = 0;
        // 循环次数
        for (int i = 0; i <arr.length-1; i++) {
            count++;
            // 第二层循环 i正好是 第二层比较的数中最小的下标int
            int min = i ;
            for (int j = i+1; j < arr.length; j++) {
                if (arr[j] < arr[min]){
                    min = j;
                }
            }
            if (i != min){
                int temp = arr[min];
                arr[min] = arr[i];
                arr[i] = temp;
            }
        }
        System.out.println("比较次数 = " + count);
        System.out.println("arr = " + Arrays.toString(arr));

二分法查找

public static void main(String[] args) {
        int[ ] arr = { 30,20,50,10,80,9,7,12,100,40,8};
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入你要查找的数 : ");
        int searchWord = sc.nextInt();
        Arrays.sort(arr); //二分法查找之前,一定要对数组元素排序
        System.out.println(Arrays.toString(arr));
        System.out.println(searchWord+"元素的索引:"+binarySearch(arr,searchWord));
        System.out.println("========================");

    }

    public static int binarySearch(int[] arr, int value) {

        // 开始位置
        int begin = 0;
        // 结束 位置
        int end = arr.length - 1;

        while(begin <= end) {
            int middle = (begin + end) / 2;

            //返回查询到的索引位置
            if (value == arr[middle]) {
                return middle;
            }

            if (arr[middle] < value) {
                begin = middle + 1;
            }

            if (arr[middle] > value) {
                end = middle - 1;
            }
        }
        //上面循环完毕,说明未找到,返回-1
        return -1;
    }

十三、项目相关

1、OA工作流Activiti

定义:业务过程部分或整体,在计算机应用环境下的自动化

主要解决:使在多个参与者之间,按照某种预定义的规则传递文档、信息或任务的过程”自动进行“,从而实现某个预期的业务目标,或者促使此目标的实现。

工作流管理系统:

一个完成工作量的定义和管理的软件系统。按照在系统中预先定义好的工作流规则进行工作流实例的执行。

(工作流管理系统不是企业的业务系统,而是为企业的业务系统的运行提供了一个软件的支撑环境)。

主要作用:

1、定义工作流:包括具体的活动、规则等。

2、执行工作流:按照流程定义的规则执行,并由多个参与者进行控制。

2、微信登录

1.AppID

  • 应用ID,唯一标识(身份证号)

2.AppSecret

  • 应用的密钥(密码)

3.code

  • 授权的临时凭证(例如:临时身份证)

4. access_token

  • 接口调用凭证(例如:真正的身份证,虎符,令牌)

img

3、微信支付

img

4、单点登录

img

5、并发量大的话如何处理?

1、数据库优化、分库分表、读写分离、消息队列肖锋

2、单体式架构:多线程

分布式微服务、集群、负载均衡熔断、反向代理

6、预计吞吐量

吞入量TPS:单位时间内处理请求的数量上线

QPS:秒为单位

RT:响应时间(一般为1-2秒)

7、文件上传如何处理断点续传?

断点续传:断点续传指的是在下载或上传时,将下载或上传任务人为的划分为几个部分。

整体思路:

拿到文件,保存文件唯一性标识,切割文件,分段上传,每次上传一段,根据唯一性标识判断文件上传进度,直到文件的全部片段上传完毕

8、redis分布式锁怎么实现的,当同时有多个······他是怎么处理的

1、秒杀案例需要达到的预期特性

即流量符合预期时整体架构要满足高可用,就算超出预期也不能掉链子
要保证数据的一致性,不能超卖
系统性能要足够高,尽可能做好系统优化

版本一:

 快速搭建一个简单的秒杀系统,只需要把你的商品购买页面增加一个“定时上架”功能,仅在秒杀开始时才让用户看到购买按钮,当商品的库存卖完了也就结束了。

版本二:

随着请求量的加大(比如从 1w/s 到了 10w/s 的量级),版本一的架构很快就遇到了瓶颈,因此需要做架构改造来提升系统性能。这些架构改造包括:

 1)把秒杀系统独立出来打造一个单独的系统,这样可以有针对性的优化,例如减少淘宝店铺装修的功能,减少页面复杂度。

 2)系统独立部署一个集群,这样秒杀的大流量不会影响到正常的商品购买集群的机器负载。

 3)将热点数据单独放到一个缓存系统中,以提高读性能。

 4)增加秒杀答题,防止有秒杀器。

img

版本三:

这个架构仍然支持不了超过 100w/s 的请求量,所以为了进一步提升秒杀系统的性能,我们又对架构做进一步升级。
   1)对页面进行彻底的动静分离,使得用户秒杀时,不需要刷新整个页面,只需要点击抢宝按钮,借此把页面刷新的数据降到最少。

   2)在服务端对秒杀商品进行本地缓存,不需要再调用依赖系统的后台服务获取数据,甚至不需要去公共的缓存集群中查询数据,这样不仅可以减少系统调用,而且能够避免压垮公共缓存集群。

   3)增加系统限流保护,防止最坏情况发生。

 经过这些优化,系统架构变成了下图中的样子。在这里,我们对页面进行了进一步的静态化,秒杀过程中不需要刷新整个页面,而只需要向服务端请求很少的动态数据。而且,最关键的详情和交易系统都增加了本地缓存,来提前缓存秒杀商品的信息,热点数据库也做了独立部署等。

img

一个人进去,其他人阻塞进入等待,redis持久化数据-1,传递到消息队列到数据库。

1、实现分布式锁的思路

  1. 因为redis是单线程的,所以命令也就具备原子性,使用setnx命令实现锁,保存k-v
    • 如果k不存在,保存(当前线程加锁),执行完成后,删除k表示释放锁
    • 如果k已存在,阻塞线程执行,表示有锁
  2. 如果加锁成功,在执行业务代码的过程中出现异常,导致没有删除k(释放锁失败),那么就会造成死锁(后面的所有线程都无法执行)!
    • 设置过期时间,例如10秒后,redis自动删除
  3. 高并发下,由于时间段等因素导致服务器压力过大或过小,每个线程执行的时间不同
    • 第一个线程,执行需要13秒,执行到第10秒时,redis自动过期了k(释放锁)
    • 第二个线程,执行需要7秒,加锁,执行第3秒(锁 被释放了,为什么,是被第一个线程的finally主动deleteKey释放掉了)
    • 。。。连锁反应,当前线程刚加的锁,就被其他线程释放掉了,周而复始,导致锁会永久失效
  4. 给每个线程加上唯一的标识UUID随机生成,释放的时候判断是否是当前的标识即可

2、Redisson

  • Redis 是最流行的 NoSQL 数据库解决方案之一,而 Java 是世界上最流行(注意,我没有说“最好”)的编程语言之一。
  • 虽然两者看起来很自然地在一起“工作”,但是要知道,Redis 其实并没有对 Java 提供原生支持。
  • 相反,作为 Java 开发人员,我们若想在程序中集成 Redis,必须使用 Redis 的第三方库。
  • 而 Redisson 就是用于在 Java 程序中操作 Redis 的库,它使得我们可以在程序中轻松地使用 Redis。
  • Redisson 在 java.util 中常用接口的基础上,为我们提供了一系列具有分布式特性的工具类。

十四、Linux常用命令

文件与目录操作

命令解析
cd /home进入 ‘/home’ 目录
cd …返回上一级目录
cd …/…返回上两级目录
cd -返回上次所在目录
cp file1 file2将file1复制为file2
cp -a dir1 dir2复制一个目录
cp -a /tmp/dir1 .复制一个目录到当前工作目录(.代表当前目录)
ls查看目录中的文件
ls -a显示隐藏文件
ls -l显示详细信息
ls -lrt按时间显示文件(l表示详细列表,r表示反向排序,t表示按时间排序)
pwd显示工作路径
mkdir dir1创建 ‘dir1’ 目录
mkdir dir1 dir2同时创建两个目录
mkdir -p /tmp/dir1/dir2创建一个目录树
mv dir1 dir2移动/重命名一个目录
rm -f file1删除 ‘file1’
rm -rf dir1删除 ‘dir1’ 目录及其子目录内容

查看文件内容

命令解析
cat file1从第一个字节开始正向查看文件的内容
head -2 file1查看一个文件的前两行
more file1查看一个长文件的内容
tac file1从最后一行开始反向查看一个文件的内容
tail -3 file1查看一个文件的最后三行
vi file打开并浏览文件

文本内容处理

命令解析
grep str /tmp/test在文件 ‘/tmp/test’ 中查找 “str”
grep ^str /tmp/test在文件 ‘/tmp/test’ 中查找以 “str” 开始的行
grep [0-9] /tmp/test查找 ‘/tmp/test’ 文件中所有包含数字的行
grep str -r /tmp/*在目录 ‘/tmp’ 及其子目录中查找 “str”
diff file1 file2找出两个文件的不同处
sdiff file1 file2以对比的方式显示两个文件的不同
vi file
操作解析
i进入编辑文本模式
Esc退出编辑文本模式
:w保存当前修改
:q不保存退出vi
:wq保存当前修改并退出vi

查询操作

命令解析
find / -name file1从 ‘/’ 开始进入根文件系统查找文件和目录
find / -user user1查找属于用户 ‘user1’ 的文件和目录
find /home/user1 -name *.bin在目录 ‘/ home/user1’ 中查找以 ‘.bin’ 结尾的文件
find /usr/bin -type f -atime +100查找在过去100天内未被使用过的执行文件
find /usr/bin -type f -mtime -10查找在10天内被创建或者修改过的文件
locate *.ps寻找以 ‘.ps’ 结尾的文件,先运行 ‘updatedb’ 命令
find -name ‘*.[ch]’ | xargs grep -E ‘expr’在当前目录及其子目录所有.c和.h文件中查找 ‘expr’
find -type f -print0 | xargs -r0 grep -F ‘expr’在当前目录及其子目录的常规文件中查找 ‘expr’
find -maxdepth 1 -type f | xargs grep -F ‘expr’在当前目录中查找 ‘expr’

压缩、解压

命令解析
bzip2 file1压缩 file1
bunzip2 file1.bz2解压 file1.bz2
gzip file1压缩 file1
gzip -9 file1最大程度压缩 file1
gunzip file1.gz解压 file1.gz
tar -cvf archive.tar file1把file1打包成 archive.tar(-c: 建立压缩档案;-v: 显示所有过程;-f: 使用档案名字,是必须的,是最后一个参数)
tar -cvf archive.tar file1 dir1把 file1,dir1 打包成 archive.tar
tar -tf archive.tar显示一个包中的内容
tar -xvf archive.tar释放一个包
tar -xvf archive.tar -C /tmp把压缩包释放到 /tmp目录下
zip file1.zip file1创建一个zip格式的压缩包
zip -r file1.zip file1 dir1把文件和目录压缩成一个zip格式的压缩包
unzip file1.zip解压一个zip格式的压缩包到当前目录
unzip test.zip -d /tmp/解压一个zip格式的压缩包到 /tmp 目录

yum安装器

命令解析
yum -y install [package]下载并安装一个rpm包
yum localinstall [package.rpm]安装一个rpm包,使用你自己的软件仓库解决所有依赖关系
yum -y update更新当前系统中安装的所有rpm包
yum update [package]更新一个rpm包
yum remove [package]删除一个rpm包
yum list列出当前系统中安装的所有包
yum search [package]在rpm仓库中搜寻软件包
yum clean [package]清除缓存目录(/var/cache/yum)下的软件包
yum clean headers删除所有头文件
yum clean all删除所有缓存的包和头文件

网络相关

命令解析
ifconfig eth0显示一个以太网卡的配置
ifconfig eth0 192.168.1.1 netmask 255.255.255.0配置网卡的IP地址
ifdown eth0禁用 ‘eth0’ 网络设备
ifup eth0启用 ‘eth0’ 网络设备
iwconfig eth1显示一个无线网卡的配置
iwlist scan显示无线网络
ip addr show显示网卡的IP地址

系统相关

命令解析
su -切换到root权限(与su有区别)
shutdown -h now关机
shutdown -r now重启
top罗列使用CPU资源最多的linux任务 (输入q退出)
pstree以树状图显示程序
man ping查看参考手册(例如ping 命令)
passwd修改密码
df -h显示磁盘的使用情况
cal -3显示前一个月,当前月以及下一个月的月历
cal 10 1988显示指定月,年的月历
date –date ‘1970-01-01 UTC 1427888888 seconds’把一相对于1970-01-01 00:00的秒数转换成时间

十五、JavaWeb面试题

1、什么是Servlet

Servlet是一门用于开发动态web资源的技术,它是运行在服务器端的小程序。

Servlet就是一个接口,定义了Java类被浏览器访问到(tomcat识别)的规则。

作用:Servlet主要用于处理客户端传来的HTTP请求,并返回一个响应,它能够处理的请求有doGet()和doPost()等方法。

用户若想用发一个动态web资源(即开发一个Java程序向浏览器输出数据),需要完成以下2个步骤:

  1. 编写一个Java类,实现Servlet接口。
  2. 把开发好的Java类部署到web服务器中。

2、什么是Servlet对象的生命周期

  • Servlet对象什么时候被创建。
  • Servlet对象什么时候被销毁。
  • Servlet对象创建了几个?
  • Servlet对象的生命周期表示:一个Servlet对象从出生在最后的死亡,整个过程是怎样的。

我们不需要在程序创建Servlet的对象,也没有去调用对象上的方法。Servlet对象的生命周期由web服务器负责

3、JSP九大内置对象

  1. pageContext,页面上下文对象,相当于页面中所有功能的集合,通过它可以获取JSP页面的out、request、response、session、application对象。
  2. request
  3. response
  4. session
  5. application,应用程序对象,application实现了用户间数据的共享,可存放全局变量,它开始于服务器启动,知道服务器关闭。
  6. page,就是JSP本身。
  7. exception
  8. out,out用于在web浏览器内输出信息,并且管理应用服务器上的输出缓冲区,作用域page。
  9. config,取得服务器的配置信息。

4、JSP和Servlet的区别

(1)servlet是服务器端的Java程序,它担当客户端和服务端的中间层。

(2)JSP全名为Java server pages,中文名叫Java服务器页面,其本质是一个简化的servlet设计。JSP是一种动态页面设计,它的主要目的是将表示逻辑从servlet中分离出来。

(3)JVM只能识别Java代码,不能识别JSP,JSP编译后变成了servlet,web容器将JSP的代码编译成JVM能够识别的Java类(servlet)。

(4)JSP有内置对象、servlet没有内置对象。

5、转发和重定向的区别

  1. 重定向访问服务器两次,转发只访问服务器一次。
  2. 转发页面的URL不会改变,而重定向地址会改变
  3. 转发只能转发到自己的web应用内,重定向可以重定义到任意资源路径。
  4. 转发相当于服务器跳转,相当于方法调用,在执行当前文件的过程中转向执行目标文件,两个文件(当前文件和目标文件)属于同一次请求,前后页 共用一个request,可以通过此来传递一些数据或者session信息,request.setAttribute()和 request.getAttribute()。而重定向会产生一个新的request,不能共享request域信息与请求参数
  5. 由于转发相当于服务器内部方法调用,所以转发后面的代码仍然会执行(转发之后记得return);重定向代码执行之后是方法执行完成之后进行重定向操作,也就是访问第二个请求,如果是方法的最后一行进行重定向那就会马上进行重定向(重定向也需要return)。
  6. 无论是RequestDispatcher.forward方法,还是HttpServletResponse.sendRedirect方法,在调用它们之前,都不能有内容已经被实际输出到了客户端。如果缓冲区中已经有了一些内容,这些内容将被从缓冲区中移除。

6、session 和 cookie 有什么区别?

(1)存储位置不同

  • cookie在客户端浏览器;
  • session在服务器;

(2)存储容量不同

  • cookie<=4K,一个站点最多保留20个cookie;
  • session没有上线,出于对服务器的保护,session内不可存过多东西,并且要设置session删除机制;

(3)存储方式不同

  • cookie只能保存ASCII字符串,并需要通过编码方式存储为Unicode字符或者二进制数据;
  • session中能存储任何类型的数据,包括并不局限于String、integer、list、map等;

(4)隐私策略不同

  • cookie对客户端是可见的,不安全;
  • session存储在服务器上,安全;

(5)有效期不同

  • 开发可以通过设置cookie的属性,达到使cookie长期有效的效果;
  • session依赖于名为JESSIONID的cookie,而cookie JSESSIONID的过期时间默认为-1,只需关闭窗口该session就会失效,因而session达不到长期有效的效果;

(6)跨域支持上不同

  • cookie支持跨域;
  • session不支持跨域;

7、get和post的区别

  1. get请求参数是连接在url后面的,而post请求参数是存放在requestbody内的;
  2. get请求因为浏览器对url长度有限制,所以参数个数有限制,而post请求参数个数没有限制;
  3. 因为get请求参数暴露在url上,所以安全方面post比get更加安全;
  4. get请求只能进行url编码,而post请求可以支持多种编码方式;
  5. get请求参数会保存在浏览器历史记录内,post请求并不会;
  6. get请求浏览器会主动cache,post并不会,除非主动设置;
  7. get请求产生1个tcp数据包,post请求产生2个tcp数据包;
  8. 在浏览器进行回退操作时,get请求是无害的,而post请求则会重新请求一次;
  9. 浏览器在发送get请求时会将header和data一起发送给服务器,服务器返回200状态码,而在发送post请求时,会先将header发送给服务器,服务器返回100,之后再将data发送给服务器,服务器返回200 OK;

8、说一下 session 的工作原理?

当客户端登录完成后,会在服务端产生一个session,此时服务端会将sessionid返回给客户端浏览器。客户端将sessionid储存在浏览器的cookie中,当用户再次登录时,会获得对应的sessionid,然后将sessionid发送到服务端请求登录,服务端在内存中找到对应的sessionid,完成登录,如果找不到,返回登录页面。

9、简述 TCP 和 UDP的区别?

  1. TCP是传输控制协议,UDP是用户数据表协议;
  2. TCP长连接,UDP无连接;
  3. UDP程序结构较简单,只需发送,无须接收;
  4. TCP可靠,保证数据正确性、顺序性;UDP不可靠,可能丢数据;
  5. TCP适用于少量数据,UDP适用于大量数据传输;
  6. TCP速度慢,UDP速度快;

10、什么是三次握手,四次挥手 ?

消息类型描述
SYN这个消息是用来初始化和建立连接的 Synchronize Sequence Numbers
ACK帮助对方确认收到的SYN信息 Acknowledge character
SYN-ACK本地的SYN信息和较早的ACK数据包
FIN用来断开连接的
  1. 在客户机和服务器之间建立TCP连接时,首先会发送的一个信号。客户端在接受到SYN消息时,就会在自己的段内生成一个随机值X。

  2. 服务器收到SYN后,打开客户端连接,发送一个SYN-ACK作为答复。确认后设置为比接收到的序列号多一个,即X+1,服务器为数据包选择的序列号是另一个随机数Y。

  3. 确认字符,表示发来的数据已确认接收无误。最后,客户端将ACK发送给服务器。序列号被设置为所接收的确认值即Y+1。

举例

image-20230202164718805

四次挥手

  1. 首先,客户端应用程序决定要终止连接(这里服务端也可以选择断开连接)。这会使客户端将FIN发送到服务器,并进入FIN_WAIT_1状态。当客户端处于FIN_WAIT_1状态时,它会等待来自服务器的ACK响应。
  2. 第二步,当服务器收到FIN消息时,服务器会立刻向客户端发送ACK确认消息。
  3. 当客户端收到服务器发送的ACK响应后,客户端就进入FIN_WAIT_2状态,然后等待来自服务器的FIN 消息
  4. 服务器发送ACK确认消息后,一段时间(可以进行关闭后)会发送FIN消息给客户端,告知客户端可以进行关闭。
  5. 当客户端收到从服务端发送的FIN消息时,客户端就会由FIN_WAIT_2状态变为TIME_WAIT状态。处于TIME_ WAIT 状态的客户端允许重新发送ACK到服务器为了防止信息丢失。客户端在TIME_ WAIT 状态下花费的时间取决于它的实现,在等待一段时间后,连接关闭,客户端上所有的资源. (包括端口号和缓冲区数据)都被释放。

举例

image-20230202164744429

11、tcp 为什么要三次握手,两次不行吗?为什么?

因为客户端和服务端都要确认连接,①客户端请求连接服务端;②针对客户端的请求确认应答,并请求建立连接;③针对服务端的请求确认应答,建立连接;

两次无法确保A能收到B的数据;

十六、Git面试题

1、Git和SVN的区别

image-20230207203405254

2、什么是Git

  • Git 是分布式版本控制系统(DVCS)。它可以跟踪文件的更改,并允许你恢复到任何特定版本的更改。
  • 与 SVN 等其他版本控制系统(VCS)相比,其分布式架构具有许多优势,一个主要优点是它不依赖于中央服务器来存储项目文件的所有版本。
  • 每个开发人员都可以“克隆”我在图中用“Local repository”标注的存储库的副本,并且在他的硬盘驱动器上具有项目的完整历史记录,因此当服务器中断时,你需要的所有恢复数据都在你队友的本地 Git 存储库中。
  • 还有一个中央云存储库,开发人员可以向其提交更改,并与其他团队成员进行共享,如图所示,所有协作者都在提交更改“远程存储库”。

image-20230207203436038

3、Git的工作流程

十七、Maven面试题

1、什么是maven?

Maven主要服务于基于java平台的项目构建,依赖管理和项目信息管理。maven项目对象模型(POM),可以通过一小段描述信息来管理项目的构建,报告和文档的项目管理工具软件。它包含了一个项目对象模型,一组标准集合,一个项目生命周期,一个依赖管理系统和用来运行定义在生命周期阶段中插件目标的逻辑。当使用Maven的时候,你用一个明确定义的项目对象模型来描述你的项目,然后Maven可以应用横切的逻辑,这些逻辑来自于一组共享的(或自定义的)插件。

2、说说maven有什么优缺点?

优点

  • 简化了项目依赖管理
  • 易于上手,对于新手来说了解几个常用命令即可满足日常工作
  • 便于与持续集成工具(jenkins)整合便于项目升级,无论是项目本身还是项目使用的依赖
  • maven有很多插件,便于功能扩展,比如生产站点,自动发布版本等为什么使用Maven中的各点

缺点

  • Maven是一个庞大的构建系统,学习难度大。(很多都可以这样说,入门容易[优点]但是精通难[缺点])
  • Maven采用约定约定优于配置的策略,虽然上手容易但是一旦出现问题,难于调试中网络环境较差,很多repository无法访问

3、讲一下maven的生命周期

Maven的 生命周期:从我们的项目构建,一直到项目发布的这个过程。

image-20230213160027538

4、说说你熟悉哪些maven命令?

image-20230213160052003

5、

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/349795.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

#Paper Reading# Improving Language Understanding by Generative Pre-Training

论文题目: Improving Language Understanding by Generative Pre-Training 论文地址: https://www.cs.ubc.ca/~amuham01/LING530/papers/radford2018improving.pdf 论文发表于: OpenAI 2018 论文所属单位: OpenAI 论文大体内容&#xff1a; 本文主要提出了GPT&#xff08;Gene…

【SQL server】视图和索引的创建与管理

本实验数据来源课参照一下本专栏文章&#xff1a;【SQL server】进行简单查询分组、连接查询子查询和汇总&#xff08;含teaching数据库创建及实验拓展&#xff09;_Deep-sea shark的博客-CSDN博客_sql 分组汇总在SSMS中创建视图视图是一张虚表&#xff0c;数据库中只存储视图的…

关于git仓库的一些使用

配置多个ssh-key 1.生成不同的key名 如github key ssh-keygen -t rsa -C "exampleemail.com" -f ~/.ssh/github_id-rsa如gitlab key ssh-keygen -t rsa -C "examlpe企业邮箱.com" -f ~/.ssh/gitlab_id-rsa创建完成后的 macbookMacBookProdeMacBook-Pr…

STL——容器适配器、deque

一、容器适配器 1.适配器 适配器是一种设计模式&#xff08;设计模式是一套被反复使用的、多数人所知晓的、经过分类编目的、代码设计经验的总结&#xff09;&#xff0c;该种模式是将一个类的接口转换成客户希望的另外一个接口。 2.STL标准库中stack和queue的底层结构 stack…

数据结构与算法(Java版) | 就让我们来看看几个实际编程中遇到的问题吧!

上一讲&#xff0c;我给大家简单介绍了一下数据结构&#xff0c;以及数据结构与算法之间的关系&#xff0c;照理来说&#xff0c;接下来我就应该要给大家详细介绍线性结构和非线性结构了&#xff0c;但是在此之前&#xff0c;我决定还是先带着大家看几个实际编程中遇到的问题&a…

UE4 编写着色器以及各种宏的理解

参考链接&#xff1a;如何为 UE4 添加全局着色器&#xff08;Global Shaders&#xff09; - Unreal Enginehttps://docs.unrealengine.com/5.1/zh-CN/adding-global-shaders-to-unreal-engine/如何为 UE4 添加全局着色器&#xff08;Global Shaders&#xff09; - Unreal Engin…

睡眠影响寿命,这几个睡眠习惯赶紧改掉!

我们知道&#xff0c;现在睡眠不足已经成为普遍问题&#xff0c;但你知道睡眠的时长会影响寿命吗&#xff1f;熬夜对身体不好&#xff0c;已是老生常谈。但睡得过早&#xff0c;也可能影响寿命&#xff01;2021年《睡眠医学》杂志一项针对21个国家11万名参与者的研究中发现&…

重生之我是赏金猎人-SRC漏洞挖掘(十)-某大厂从废弃sso登陆口到多思路fuzz获取各地高管信息

0x01 前言 https://github.com/J0o1ey/BountyHunterInChina 欢迎亲们点个star 作者Catm78sec 前期通过灯塔 ffuf oneforall 等工具组合进行子域名收集&#xff0c;得到目标站点&#xff0c;漏洞挖掘中多次踩坑成功get腾讯某后台 0x02 渗透日常——单点登录 目标URL&…

【vcpkg】cpprestsdk之64位编译链接及踩坑

▒ 目录 ▒&#x1f6eb; 问题描述1️⃣ 多版本vs报错指定VS路径2️⃣ error LNK2001: 问题排查通过IDA打开lib文件&#xff0c;确认导出内容查看源码增加参数--editable&#xff0c;重新编译3️⃣ error LNK2001: 外部符号__imp_?close_...去除__imp_&#x1f6ec; 结论vcpkg…

浅谈估值模型:从Grinold Kroner(GK)模型看投资的本质

摘要及声明 1&#xff1a;本文主要介绍Grinold Kroner(GK)模型的运用&#xff0c;并以上证指数为例实现一个GK模型&#xff1b; 2&#xff1a;本文主要为理念的讲解&#xff0c;模型也是笔者自建&#xff0c;文中假设与观点是基于笔者对模型及数据的一孔之见&#xff0c;若有…

buffer和cache的区别

一&#xff0c;计算机硬件组成 计算机硬件组成&#xff1a;CPU&#xff0c;存储器&#xff0c;输入输出设备&#xff08;I/O&#xff09;&#xff0c;其他&#xff08;主板&#xff0c;电源等&#xff09; CPU&#xff1a;运算器&#xff0c;控制器 存储器&#xff1a;内部存储…

蓝桥云课-声网编程赛(声网编程竞赛7月专场)题解

比赛题目快速链接&#xff1a;https://www.lanqiao.cn/contests/lqENT02/challenges/ 让时钟转起来&#xff08;考点&#xff1a;css&#xff1a;transform&#xff09; // index.js function main() {// 题解前理解一个东西&#xff1a;// 时针每过一小时&#xff0c;转30 原…

博客等级说明

CSDN 博客等级是按照用户的博客积分数量进行的设定&#xff0c;为 Lv1 至 Lv10 共 10 个等级&#xff0c;不同的等级创作者可以享受到不同的权益待遇。例如&#xff0c;皮肤奖励、自定义域名、客服优先处理、自定义文章标签等特权。您需要提高博客积分进一步提升等级&#xff0…

矩阵理论复习(十二)

已知方阵A的不变因子&#xff1a; 求谱半径求矩阵级数判断矩阵幂级数的收敛性 若矩阵B的某个算子范数小于1&#xff0c;则I-B可逆。 矩阵分析 任何相容矩阵范数都存在与之相容的向量范数。 盖尔圆盘定理一的证明 椭圆范数的证明 若||.||是Cm上的向量范数&#xff0c;A为…

单元测试工具——JUnit的使用

⭐️前言⭐️ 本篇文章主要介绍单元测试工具JUnit的使用。 &#x1f349;欢迎点赞 &#x1f44d; 收藏 ⭐留言评论 &#x1f4dd;私信必回哟&#x1f601; &#x1f349;博主将持续更新学习记录收获&#xff0c;友友们有任何问题可以在评论区留言 &#x1f349;博客中涉及源码…

C++11--lambda表达式

目录 lambda表达式的概念 lambda表达式语法 lambda表达式的书写格式 捕捉列表 参数列表 mutable 返回值类型 函数体 lambda表达式交换两个数 函数对象与lambda表达式 lambda表达式的概念 lambda表达式是一个匿名函数 它能让代码更加地简洁 提高了代码可读性 首先定义…

2021年欧空局10米土地覆盖数据(分省/分市)

土地覆盖数据是我们平时最常用的地理数据之一&#xff0c;土地覆盖数据的来源也有很多种&#xff0c;之前我们介绍共过两个的30米精度的土地覆盖数据&#xff0c;分别为GlobeLand30土地覆盖数据和CLCD土地覆盖数据&#xff0c;&#xff08;可查看之前推送的文章&#xff09;&am…

佳能镜头EOS系统EF协议逆向工程(三)解码算法

目录 数据结构 解码算法 解码效果 这篇文章基于上两篇文章继续&#xff0c; 佳能镜头EOS系统EF协议逆向工程&#xff08;一&#xff09;转接环电路设计_佳能ef自动对焦协议_岬淢箫声的博客-CSDN博客本文属于专栏——工业相机。此专栏首先提供我人工翻译的法语文档部分&…

Python解题 - CSDN周赛第29期 - 争抢糖豆

本期问哥是志在必得&#xff0c;这本算法书我已经觊觎许久&#xff0c;而之前两次因为种种原因未能如愿。因此&#xff0c;问哥这几天花了不少时间&#xff0c;把所有之前在每日一练做过的题目重新梳理了一遍。苦心人&#xff0c;天不负&#xff0c;感谢官方大大&#xff01; 第…

ChatGPT 人工智能革命从实验室走入公众生活

11 月底&#xff0c;人工智能研究实验室OpenAI 发布了 ChatGPT 聊天机器人首个测试版本&#xff0c;这是一款基于人工智能的新型聊天机器人&#xff0c;可以与人类进行对话&#xff0c;经过测试后&#xff0c;新款机器人便踏上了社交网站之旅&#xff0c;尤其是在推特平台上&am…