为校招搜集整理的10万字java后端面试题ing...,基础不太好不知道从哪儿开头的冲它就完事了

java后端面试题

Java基础

1.什么是Java

java是一们面向对象的具有跨平台性的功能强大简单易用的编程语言,它基于c++开发,吸收了c++的各种优点,摒弃了c++多继承,指针等概念,更加安全,它作为静态面向对象编程语言的代表,极好的实现了面向对象编程理论。

2.jdk,jre和jvm的区别

JDK中包含有JRE,JRE中包含有JVM。

  • JDK::jdk全称是Java development Kit,其中包含了Java的开发工具,是提供给Java开发人员使用的,包括编译工具(javac.exe)、打包工具(jar.exe)等。
  • JRE::jre大部分是由c和c++语言编写的,它提供Java编译时所需要的基础类库,Java运行环境就包括Java虚拟机和Java程序所需的核心类库等;Java的核心类库是java.lang包,它包括基本数据类型,基本数学函数,字符串处理,线程,异常处理类等。
  • JVM:JVMD的全称是Java virtual machine,Java程序需要运行在虚拟上,所以如果只是需要运行Java程序,安装一个JVM即可,不同的平台有自己的虚拟机,所以Java语言具有跨平台性。

3.什么是跨平台性及原理

跨平台性:即Java程序编译一次后就可以在多个平台上运行。
原理:Java程序是通过Java虚拟机运行的,只要在要运行Java程序的系统上安装相应的Java虚拟机即可运行Java程序。

4.Java语言的特点

-简单易学 (语法与c语言和c++相似)

  • 面向对象(封装,继承,多态)
  • 平台无关性(Java程序可跨平台运行,只要有相应Java虚拟机即可)
  • 支持网络编程(简化网络编程,TCP,UDP,Socket)
  • 支持多线程(多线程的支持是程序在同一时间并行多项任务)
  • 健壮性(Java的强类型机制,异常处理机制,垃圾的自动收集机制)
  • 安全性好(没有直接指向内存的指针)

5.什么是字节码

字节码即是Java源代码经过Java虚拟机编译器编译后产生的.class文件,它不面向任何特定的机器,只面向虚拟机。

6.采用字节码的好处

Java采用字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点,所以Java运行时是比较高效的,由于字节码并不专一对特定的机器,所以Java语言具有跨平台性。
Java源代码–>编译器–>jvm可执行的字节码文件–>jvm–>jvm中的解释器–>机器可执行的二进制代码–>程序运行

7.Java和C++的区别

  • 都是面向对象语言,支持封装继承多态
  • Java不提供指针直接访问内存,层序内存更安全
  • Java不支持多继承,但是可以实现多个接口
  • Java有自动内存管理机制,不需要程序员手动释放垃圾

8.Oracle JDK和OpenJDK的对比

Oracle JDKOpenJDK
三年发布一次三个月发布一次
是OpenJDK的一个实现,对里面的bug进行了修复,更稳定,不完全开源开源
性能更好正常性能
根据二进制代码许可协议获得许可根据GPL v2获得许可

9.Java的数据类型

基本数据类型:

  • 整型
    - 字节型(byte) 1字节
    - 短整型(short) 2字节
    - 整型(int) 4字节
    - 长整型(long) 8字节
  • 浮点型
    - 单精度浮点型(float) 4字节
    - 双精度浮点型(double) 8字节
  • 字符型(char) 2字节
  • 布尔型(boolean) 1字节

引用数据类型:类(接口)

10.switch的作用范围

Java5以前只能作用在byte,int,short,char上,Java5时可作用在枚举(enum),Java7时可以作用在字符串(String)上,长整型(long)一直都不行。

11.最有效率的方法计算2乘以8

乘:左移位(<<) 除:右移位(>>)

12.Math.round()的取值规则

参数在原来的基础上加上0.5向下取整
例:

		System.out.println(Math.round(11.4)); //11
        System.out.println(Math.round(-11.4));//-11

13.float f = 3.4;是否正确

不正确,3.4是双精度数,将双精度数赋值给单精度数属于下转型,会造成精度损失(强转类型就好)
float f = (float) 3.4或者 float f = 3.4f

14.short s1 = 1;s1 = s1 + 1;有错吗?short s1 = 1; s1 += 1;有错吗

short s1 = 1;s1 = s1 + 1有错,1是int型,赋值给short型需要强转
short s1 = 1; s1 += 1可以正确编译,因为代码包含自动强转

15.Java语言采用的编码方案

Unicode(标准码),因为它为每个字符制定了唯一数值,因此在任何语言,平台,程序都可以使用。

16.Java注释

用于解释说明程序的文字

//单行注释

/*
多行注释
*/

/**文档注释*/

17.Java语言四大访问修饰符

在Java中,支持使用访问修饰符来保护类,方法,变量的访问。

名称/访问范围同类同包子类不同包适用对象
private变量,方法
缺省变量,方法,接口,类
protected变量,方法
public变量,方法,接口,类

18.Java语言运算符

逻辑运算符
&&短路

位运算符
按位异或^:两个二进制位相同,结果是0,不相同是1
按位取反~:该二进制位0变1,1变0

19.final的作用

用于修饰类,属性和方法

  • 被final修饰的类不能被继承
  • 被final修饰的方法不能被重写
  • 被final修饰的变量不可改变(引用不可变,但引用指向的内容能变)
    注:
    final不能修饰抽象类(抽象类需要被继承)
    final不能修饰接口(接口方法需要被重写)

20.final,finally和finalize的区别

final:修饰类,变量,方法,修饰类表示该类不能被继承,修饰方法表示该方法不能被重写(override),修饰变量表示该变量是一个常量,有且只有一个值,不能改变。

finally一般放在try-catch的后面,不管是否出现异常,finally中的代码都会执行,一般将要关闭的资源放在该代码块中。

finalize是一个方法,在Object类中,Object类是超类,finalize()方法一般被垃圾回收器调用,当调用system.gc()方法是,由垃圾回收器调用finalize(),回收垃圾,作为一个对象是否可以回收的最后判断。

21.this关键字的用法

this表示对象本身,可以理解为指向对象本身的一个指针
三个用法:

  • 普通的引用,this相当于当前对象
  • 当构造方法中形参与成员变量重名时用this来区分
  • 在多参数的构造函数中引用本类少参数的构造函数

22.supper关键字的用法

super关键字表示该对象的父类对象
三个用法:

  • 普通的引用,就当一个父类对象来用
  • 当子类的成员变量与父类的成员变量同名时用来区分
  • 引用父类的构造函数

23.this与supper的区别

  • super指父类对象,this指当前对象
  • super()和this()在构造函数中使用时均需放在构造函数的第一行
  • this和super不能出现在同一个构造函数中,因为用this必然调用其他的构造函数,其他构造函数中必然调用了super,编译不会通过
  • this()和super()都指代的对象,所以不能在static环境中使用,包括静态变量,静态方法,静态语句块

24.static的作用、应用场景及注意事项

static作用:主要是创建独立于对象的变量或方法,即被static修饰的变量或方法不属于任何一个实例对象,而是被类的实例对象所共享的,static静态代码块还可以用来优化程序性能,一个类中可以有多个静态代码块,静态代码块只会在类初次加载时被执行,只会按照顺序执行一次,其中可以用来放一些初始化操作,static变量值是在类最初加载时分配空间,它修饰的变量或方法是优先于对象存在的,这样才有了该对象即使不实例化也能去访问。

应用场景:修饰成员变量,修饰成员方法,静态代码块,静态导包。

注意事项:静态只能访问静态,非静态既可以访问静态也可以访问非静态。

25.break,continue,return的区别及作用

break是直接结束掉当前循环
continue是跳出本次循环,继续执行下一个循环条件的循环
return是程序返回,不再执行下面的代码(结束当前方法,直接返回)

26.在Java中如何跳出多重循环

可在相应循环层数设置一个标号,用带标号的break退出多重循环
例:

		ok:
        for (int i = 0; i < 10; i++) {
            for (int j = 0; j < 10; j++) {
                System.out.println("i=" + i + ",j=" + j);
                if (j == 5) {
                    break ok;
                }
            }
        }

在这里插入图片描述

面向对象

27.面向对象的四大特征

抽象:抽象是将一类对象的共同特征抽取出来构造成类的过程,抽象分为数据抽象和行为抽象,抽象关注的是对象的属性和行为,并不关注对象的实现细节。

封装:将一个对象的属性私有化,即用private修饰,然后对外提供可访问的方法,提高了程序的安全性。

继承:即基于一个 已存在的类定义一个新类的技术,新类可以定义新的属性和方法,也可以沿用父类的功能,继承时程序员能过够轻松复用代码。
关于继承有以下三个特点:

  • 子类拥有父类的非private属性和方法
  • 子类可以拥有自己的功能,即对父类进行扩展
  • 子类可以重写父类的方法

多态:父类 变量名 = new 子类(),向上转型,提高了程序的拓展性

28.Java语言如何实现多态

多态分为编译时多态和运行时多态,其中编译时多态是静态多态,体现为方法的重载,它是通过参数列表中参数的不同(顺序,个数,类型)来区分函数名相同的函数,通过编译后就会变成两个不同的函数;运行时多态是动态的,即向下转型的过程。
多态实现的三大必要条件:继承、重写、向上转型

  • 继承:在多态中必须存在有继承关系的子类和父类
  • 重写:子类对父类的方法重新定义,在调用时就会调用子类的方法
  • 向上转型:在多态中需要将子类的引用赋值给父类的对象,这样该引用既可以调用父类方法,也可以调用子类方法。

注:重写和重载的对比

区别点重载方法重写方法
参数列表必须改变,可以是个数不同,类型不同,顺序不同不能改变
返回类型可以改变不能改变
异常可以新定义,拓广,不能窄化可以删除,但不能抛出新的异常或更广的异常
访问修饰符可以改变可以降低限制,比如private改为public

29.面向对象七大原则

  • 单一职责原则:一个类对应一个职责
  • 开闭原则:对拓展开放,对修改关闭
  • 里氏替换原则:父类A和子类B有结构相似的方法,重新建立父类C,接触A和B的继承关系,使他们变为关联或依赖关系
  • 依赖倒置原则:抽象不依赖细节,细节依赖抽象
  • 接口隔离原则:即一个接口对应一件事物
  • 合成复用原则:即少用继承
  • 洛必达法则:即封装,对外透明

30.抽象类与接口的异同

抽象类是对子类通用特性的捕捉,接口是对行为的抽象
相同点:

  • 抽象类和接口都不能实例化
  • 都用于被其他类实现或继承
  • 都包含抽象方法

不同点:

参数抽象类接口
声明abstractinterface
实现extends,如果子类不是抽象类,需实现父类中的所有方法implements,java8后不需要实现所有父类中声明的方法
构造器可以有不能有
访问修饰符抽象类中的方法可以是任意访问修饰符默认为public,不能是private或protected
多继承单继承 一继承一,多继承一多实现,一实现N
字段声明任意默认为static和final

注:抽象类除了必须要有abstract修饰的方法外,其余与普通类相似。

31.普通类和抽象类的区别

普通类不能包含抽象方法,抽象类可以包含普通方法。
普通类可以直接实例化,抽象类必须上转型才能实例化。

32.抽象类能用final修饰吗

不能,因为抽象类是用来被继承的,被final修饰的类不能被继承。

33.对象实例与对象引用的异同

储存位置:对象实例存在堆中,对象引用存在栈中(对象引用指向对象实例)。
一个对象引用可以指向0或1个对象,一个对象可以有N个引用指向它。

34.成员变量与局部变量的区别

变量:在程序执行过程中,在某个范围内其值可以发生改变的量,从本质上来说,变量其实是内存中的一小块区域。

成员变量局部变量
位置类内部,方法以外方法内部
作用域整个类相应方法或语句体中
存储位置堆内存栈内存
生命周期随对象的创建而存在,随对象的消失而消失当方法调用完后就自动释放
初始值有默认初始值需要赋初值

35.基类的无参构造函数对子类的作用

帮助子类初始化。

36.一个类的构造方法的作用是什么,若一个类没有声明构造方法,程序能执行吗

一个类如果没有声明构造方法则其默认有一个无参构造函数,其作用就是帮助类对象完成初始化工作,所以若一个类没有声明构造方法也是能够执行的。

37.构造方法的特性

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

38.静态变量和实例变量的区别

静态变量:静态变量不属于任何实例对象,是属于类的,所以静态变量只会在类加载初期被JVM分配一次内存空间。

实例变量:实例变量是属于对象的,每次创建对象,都会为每个对象分配成员变量的内存空间,在内存中,创建几次对象,就会有几份成员变量。

39.静态变量与普通变量的区别

static静态变量是属于类的,是被所有类共享的,他只会被分配一次内存,且static变量的执行顺序与类中定义顺序相同;普通变量是对象所拥有的,各个对象的普通变量互不影响。

40.静态方法和实例方法的区别

方法的调用方式;静态方法只能访问本类静态成员,实例方法可以无此限制。

41.在静态方法中能调用非静态成员吗

不能。因为静态方法不是通过对象进行调用的,所以不能调用非静态成员变量和方法。

42.什么是方法的返回值及它的作用

返回值是执行完方法后返回的一个结果,作用是接收此结果,使得他可以用于其他操作。

43.什么是内部类

一个类中定义的一个类。使用方式与一个类中的属性相似。

44.内部类的分类

  • 静态内部类:定义在类内部的被static修饰的类。

    public class innerClasstest {
            private static int radius = 1;
    
            static class StaticInner {
                public void visit() {
                    System.out.println("visit outer static variable:" + radius);
                }
            }
    
    
        public static void main(String[] args) {
            StaticInner staticInner = new StaticInner();
            staticInner.visit();
        }
    }
    

    在这里插入图片描述

  • 成员内部类:定义在类内部的成员变量位置的非静态类

  • 局部内部类:定义在方法中的内部类

  • 匿名内部类:就是没有名字的内部类,匿名内部类必须实现一个已有的接口

    public class innerClasstest {
        private void test(final int i) {
            new Service() {
                public void method() {
                    for (int j = 0; j < i; j++) {
                        System.out.println("匿名内部类");
                    }
                }
            }.method();
        }
    }
    //匿名内部类必须继承或实现一个已有的接口 
    interface Service {
        void method();
    }
    
     - 匿名内部类必须实现一个接口或继承一个抽象类
     - 匿名内部类不能定义任何静态的变量或方法
     - 当所在的方法的形参需要被匿名内部类用到时必须用final声明
     - 匿名内部类不能是抽象的,他必须对方法进行重写
    

45.内部类的优点

  • 一个内部类可以访问外部类的私有属性
  • 内部类不同被同包的其他方法所见,符合面向对象的封装思想,具有封装性
  • 优化了Java只能单继承的短板
  • 匿名内部类可以很方便的定义回调

46.内部类的应用场景

当某个类只能被他的外部类使用时;一些多算法场合。

47.局部内部类与匿名内部类访问局部变量时为什么变量必须要加上final

因为生命周期不同。局部变量是放在栈内存中的,当方法使用完后会被销毁,如果此时内部内中用到了这个变量就会报错。

48.构造器能否被重写(override)

因为构造器名只能与类名相同,所以不能被继承,所以不能被重写。可以被重载,不能被重写。

49.重载和重写的区别

重载和重写都是多态的体现,区别在于重载是编译型多态,重写是运行时多态。
重载发生在同一个类中,方法名必须相同方法的参数不能相同(顺序,个数,类型),与方法的返回值或访问修饰符无关。
重写是发生在父子类中,方法名和参数都必须相同,发生的异常不能大于原本的方法,访问修饰符必须大于父类的。

50.==和equals的区别

代码示意:

public static void main(String[] args) {
        String a = "11";
        String b = "11";
        String d = new String("11");
        String c = b;
        System.out.println("===================equals=================");
        System.out.println(a.equals(b));//true
        System.out.println(a.equals(d));//true
        System.out.println(a.equals(c));//true
        System.out.println("==================="+"|==|"+"=================");
        System.out.println(a==b);//true
        System.out.println(a==d);//false
        System.out.println(a==c);//true
        
    }

==比较的是两个对象的地址是否相等,即判断两个对象是不是同一个对象(基本数据类型= =比较的是值,引用数据类型= =比较的是地址)。
equals()如果没有重写则等于= =,重写后则视具体代码而定,一般是对象内容相等就返回true。
Object类中的equals

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

String类中的equals

public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        return (anObject instanceof String aString)
                && (!COMPACT_STRINGS || this.coder == aString.coder)
                && StringLatin1.equals(value, aString.value);
    }
//StringLatin1.equals()
@IntrinsicCandidate
    public static boolean equals(byte[] value, byte[] other) {
        if (value.length == other.length) {
            for (int i = 0; i < value.length; i++) {
                if (value[i] != other[i]) {
                    return false;
                }
            }
            return true;
        }
        return false;
    }

注:
string类中的equals()方法是重写过的,Object中的equals对比的是对象内存地址,String中的比较的是对象的值。
当创建一个String对象时,虚拟机会先在常量池中查找有没有已存在的值和要创建的值相等的对像,如果有,则直接把他赋值给当前引用,没有就在常量池中创建一个String对象。

51.hashcode与equals的异同

hashcode是什么

hashcode就是哈希码,也称为散列码,实际上是由hashcode()方法产生的一个int型整数,作用是用来确定该对象在哈希表中的索引位置。哈希表存储的是键值对(key-value),特点是能根据键快速找到值,其中就用到了哈希值。

为什么要有hashcode

Hashset中检查重复就用到了hashcode
在加入新元素是,HashSet会先计算其hashcode值来判断加入对象的位置同时与其他已加入的元素的hashcode值作比较,如不同,则hashset则会假设没有重复元素,可以加入该元素,如果相同,则会继续调用equals方法来判断两个元素的值是否相等。这个过程大量的减少了equals方法的调用次数,提高了效率。

hashcode与equals相关规定

  • 如果两个对象相等,则他们的hashcode一定相同
  • 两个对象相等,则相互调用equals方法一定为true
  • 两个对象的hashcode值相同,他们不一定相等

52.对象相等与指向他们的引用相等有什么不同

对象相等比的是内容,引用相等比的是引用的对象的内存地址是否相等。

53.当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递

值传递,因为Java编程语言只有值传递参数。

54.为什么Java中只有值传递

Java程序设计语言总是采用按值调用。也就是说,方法得到的是所有参数值的一个拷贝,也就是
说,方法不能修改传递给它的任何参数变量的内容。

55.值传递和引用传递有什么区别

值传递传递的是值的一份拷贝,引用传递传的是地址而不是本身。

56.JDK中常用包有哪些

  • java.lang包:这个是系统的基础类
  • java.io包:这里面是所有输入输出相关类,如文件操作
  • java.nio包:io包的完善
  • java.net包:这是与网络编程有关的类
  • java.util包:这是系统的辅助类,集合类就在此包
  • java.sql包:这是与数据库操作相关的类

Java高级

io

57.Java中的io流分为几种

  • 流向:输入流/输出流
  • 单元:字节流/字符流
  • 角色:节点流/处理流

58.BIO,NIO,AIO有什么区别

BIO:同步阻塞io
NIO:同步非阻塞io
AIO:异步非阻塞io

59.files的常用方法有哪些

简单使用实例跳转----->🤚

Files.exists();//判断文件是否存在
Files.createFile();//创建文件
Files.createDirector();//创建文件夹
Files.delete();//删除文件或目录
Files.copy();//复制文件
Files.move();//移动文件
Files.size();//查看文件个数
Files.read();//读取文件
Files.write();//写入文件

反射

60.什么是反射机制

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

  • 静态编译:在编译时确定类型,绑定对象。
  • 动态编译:运行时确定类型,绑定对象。

61.反射机制优缺点

  • 优点:运行时确定类型,动态加载类,提高代码灵活性。
  • 缺点:性能瓶颈:反射相当于一系列解释操作,通知jvm要做的事情,性能比直接的Java代码要慢很多。

62.反射机制的应用场景

常用于框架的设计。
例:
spring框架的xml文件装载bean的过程。

  • 将配置文件加载入内存中
  • Java类里面解析xml配置文件,得到对应的实体类的字节码及相关属性信息
  • 使用反射机制根据这个类获得实例
  • 动态配置实例的属性

连接JDBC的时候使用的Class.forName()。

63.Java获取反射的三种方法

.Class;
对象.getClass();
Class.forname(类路径);

64.反射常用方法

简单使用实例跳转----->🤚

//反射
.java-->.class  编译
.class-->.java  反编译

//获取类对象
类名.Class
对象.getClass()
Class.forName("全限定名").getClassLoader().loadClass("全限定名")
子类.class.getSuperClass()
包装类.class

//根据类得到类名(全限定名)
getName()//全限定名
getsimpleName()//类名
getPackage()//包名

//Field类(属性)
getFileds("属性名") //获取公共属性
getName() //属性名
getModifiers() //修饰符
getType() //数据类型
set(对象名.属性值) = 对象名.set属性名 //属性赋值
get(对象名) = 对象名.get属性名 //属性取值
getDeclaredField("属性名") //获取属性
setAccessible(true) //设置私有属性可访问
getDeclaredFields() //所有属性

//Method类(方法)
getMethod(方法名.参数数据类型(无参数传null)//获取公共方法
getDeclaredMethod(方法名.参数数据类型(无参数传null) //获取私有方法
invoke(对象名.参数列表) = 对象名.方法名 //执行方法
getParameterTypes() //得到返回参数列表
getDeclaredMethods() // 得到类的所有方法
getReturnType() //得到返回值方法的数据类型

//构造方法
Class对象.getConstructor() //得到构造方法
Class对象.getConstructors() //得到所有的构造方法


//注:

 - isinstance()//用于判断是否某个类的实例
 - newInstance()//生成对应类的实例

String

65.字符型常量和字符串常量的区别

形式上:字符常量是单引号引起的一个字符,字符串常量是双引号引起的若干个字符(如char(字符型),String(字符串型))
含义上:字符型常量相当于一个整型值,可以参加表达式运算,字符串常量代表的是一个地址值(该字符串在内存中的存放位值)
占用内存大小:字符型占一个字节,字符串型至少占一个字符位的结束标志。

66.什么是字符串常量池

字符串常量池是位于堆内存中专门用来存储字符串常量的一片空间,可以提高内存的使用率,避免开辟多块空间存储相同的值,在创建字符串时,jvm会首先检测常量池中是否已经存在该值,如存在,则返回他的引用,如不存在,则实例化一个字符串到常量池中,并返回其引用。

67.String是最基本的数据结构吗

不是,Java中只有8中基本数据类型,其他都是引用类型,Java5后新增的枚举类也是引用类型。

68.String有哪些特性

  • 不变性:String是只读字符串,对它进行任何操作时,都是新建一个对象,再把引用指向该对象。不变模式的主要作用是当一个对象被多线程共享并频繁访问时,可保证数据的一致性。
  • 常量池优化:String对像创建后,会在常量池中进行缓存,如果下次创建同样的对象时,会直接返回缓存的引用。
  • final:使用final修饰的String类,表示String不能被继承,提高了系统的安全性。

69.String可变吗

不可变,String底层是一个char类型的数组,数组具有不可变性。

70.String类是否可以继承

不能,被final修饰。

71.String str = "i"与String str = new String(“i”)一样吗

内存分配的方式不一样,第一个会被分配到常量池中;第二个会被分配到堆内存中。

72.String str = new String(“ly”);创建了几个字符串对象

等价于:string s = “ly”; string str = new string(); str = s;
两个对象,一个静态区的“ly”,一个用new创建在堆上的对象。

73.字符串反转

将字符串append到StringBuilder或StringBuffer中去,直接使用reverse()方法。

74.数组有没有length()方法?String有没有length()方法

数组没有length()方法,但有length属性;String有length()方法。在javascript中,获取字符串长度时是通过length属性得到的。

75.String类的常用方法

简单使用实例跳转----->🤚

indexOf()//返回指定字符索引
charAt()//返回指定索引处的字符
replace()//字符串替换
trim()//去除字符串两端空白
split()//分割字符串,返回一个分割后的字符串数组
getBytes()//返回字符串的byte类型数组
length()//返回字符串长度
toLowerCase()//将字符串转成小写字母
toUpperCase()//将字符串转成大写字母
substring()//截取字符串
equals()//字符串比较

76.在使用HashMap时,用Stirng做key有什么好处

HashMap内部是通过key的hashcode来确定value的存储位置的,因为字符串是不可变的,所以当字符串被创建时,他的hashcode被缓存了下来,不需要被再次计算,相比于其他对象更快。

77.String和StringBuffer、StringBuilder的区别是什么

  • 可变性:String不可变,StringBuilder,StringBuffer 可变。
  • 线程安全性:String中的对象是不可变的,可以理解为常量,是线程安全的;StringBuffer对方法加了同步锁,是线程安全的;StringBuilder是非线程安全的。
  • 性能:String不可变,所以每对String对象进行改变时,都是生成一个新的对象,然后将指针指向新的对象,StringBuffer是对StringBuffer对象本身进行操作,而不是生成新的对象行改变对象引用;相同情况下,StringBuilder能获得10%–15%的性能提升,但是多线程情况下不安全。

注:

  1. 如果操作少量的数据,用String;
  2. 单线程操作字符串缓冲区下大量的数据,用StringBuilder
  3. 多线程操作字符串缓冲区下大量的数据,用StringBuffer

Date

78.Clock 时钟

Clock类提供了访问当前日期和时间的方法,Clock是时区敏感的,可以用来取代 System.currentTimeMillis() 来获取当前的微秒数。某一个特定的时间点也可以使用Instant类来表示,Instant类也可以用来创建老的java.util.Date对象。

代码如下:

Clock clock = Clock.systemDefaultZone(); long millis = clock.millis();

Instant instant = clock.instant(); Date legacyDate = Date.from(instant); // legacy java.util.Date

79.Timezones 时区

在新API中时区使用ZoneId来表示。时区可以很方便的使用静态方法of来获取到。 时区定义了到UTS时间的时间差,在Instant时间点对象到本地日期对象之间转换的时候是极其重要的。

代码如下:

System.out.println(ZoneId.getAvailableZoneIds()); // prints all available timezone ids

ZoneId zone1 = ZoneId.of("Europe/Berlin"); ZoneId zone2 = ZoneId.of("Brazil/East"); System.out.println(zone1.getRules()); System.out.println(zone2.getRules());

// ZoneRules[currentStandardOffset=+01:00] // ZoneRules[currentStandardOffset=-03:00]

80.LocalTime 本地时间

LocalTime 定义了一个没有时区信息的时间,例如 晚上10点,或者 17:30:15。下面的例子使用前面代码创建的时区创建了两个本地时间。之后比较时间并以小时和分钟为单位计算两个时间的时间差:

代码如下:

LocalTime now1 = LocalTime.now(zone1); LocalTime now2 = LocalTime.now(zone2);

System.out.println(now1.isBefore(now2)); // false

long hoursBetween = ChronoUnit.HOURS.between(now1, now2); long minutesBetween = ChronoUnit.MINUTES.between(now1, now2);

System.out.println(hoursBetween); // -3 System.out.println(minutesBetween); // -239

LocalTime 提供了多种工厂方法来简化对象的创建,包括解析时间字符串。

代码如下:

LocalTime late = LocalTime.of(23, 59, 59); System.out.println(late); // 23:59:59

DateTimeFormatter germanFormatter = DateTimeFormatter .ofLocalizedTime(FormatStyle.SHORT) .withLocale(Locale.GERMAN);

LocalTime leetTime = LocalTime.parse("13:37", germanFormatter); System.out.println(leetTime); // 13:37

81.LocalDate 本地日期

LocalDate 表示了一个确切的日期,比如 2014-03-11。该对象值是不可变的,用起来和LocalTime基本一致。下面的例子展示了如何给Date对象加减天/月/年。另外要注意的是这些对象是不可变的,操作返回的总是一个新实例。

代码如下:

LocalDate today = LocalDate.now(); LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS); LocalDate yesterday = tomorrow.minusDays(2);

LocalDate independenceDay = LocalDate.of(2014, Month.JULY, 4); DayOfWeek dayOfWeek = independenceDay.getDayOfWeek();

System.out.println(dayOfWeek); // FRIDAY 从字符串解析一个LocalDate类型和解析LocalTime一样简单:

代码如下:

DateTimeFormatter germanFormatter = DateTimeFormatter .ofLocalizedDate(FormatStyle.MEDIUM) .withLocale(Locale.GERMAN);

LocalDate xmas = LocalDate.parse("24.12.2014", germanFormatter); System.out.println(xmas); // 2014-12-24

82.LocalDateTime 本地日期时间

LocalDateTime 同时表示了时间和日期,相当于前两节内容合并到一个对象上了。LocalDateTime和LocalTime还有LocalDate一样,都是不可变的。LocalDateTime提供了一些能访问具体字段的方法。

代码如下:

LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59);

DayOfWeek dayOfWeek = sylvester.getDayOfWeek(); System.out.println(dayOfWeek); // WEDNESDAY

Month month = sylvester.getMonth(); System.out.println(month); // DECEMBER

long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY); System.out.println(minuteOfDay); // 1439

只要附加上时区信息,就可以将其转换为一个时间点Instant对象,Instant时间点对象可以很容易的转换为老式的java.util.Date。

代码如下:

Instant instant = sylvester .atZone(ZoneId.systemDefault()) .toInstant();

Date legacyDate = Date.from(instant); System.out.println(legacyDate); // Wed Dec 31 23:59:59 CET 2014

格式化LocalDateTime和格式化时间和日期一样的,除了使用预定义好的格式外,我们也可以自己定义格式:

代码如下:

DateTimeFormatter formatter = DateTimeFormatter .ofPattern("MMM dd, yyyy - HH:mm");

LocalDateTime parsed = LocalDateTime.parse("Nov 03, 2014 - 07:13", formatter); String string = formatter.format(parsed); System.out.println(string); // Nov 03, 2014 - 07:13

和java.text.NumberFormat不一样的是新版的DateTimeFormatter是不可变的,所以它是线程安全的。

包装类

83.自动装箱与拆箱

  • 装箱:将基本类型用他们对应的引用类型包装起来;
  • 拆箱:将包装类型转换为基本数据类型;

84.int与integer的区别

为了能够将基本类型当作对象操作,Java为每个基本数据类型都引入了包装类,Integer就是int的包装类,Java5后引入了自动装箱/箱,是的二者可以相互转换。

85.Integer a = 127 与Integer b = 127 相等吗

是相等的

  • 对于基本数据类型:==比较的是值。

  • 对于引用类型:==比较的是对象的内存地址。

    如果整型变量的值在-128到127之间,那么自动装箱时不会new新的Integer对象,而是直接引用常量池中的Integer对象,超过范围的a==b结果是false

public static void main(String[] args) { 
		Integer a = new Integer(3); 
		Integer b = 3; // 将3自动装箱成Integer类型 
		int c = 3; 
		System.out.println(a == b); // false 两个引用没有引用同一对象 
		System.out.println(a == c); // true a自动拆箱成int类型再和c比较 
		System.out.println(b == c); // true 

		Integer a1 = 128; 
		Integer b1 = 128; 
		System.out.println(a1 == b1); // false 

		Integer a2 = 127; 
		Integer b2 = 127; 
		System.out.println(a2 == b2); // true
}

异常

86. 什么是Java异常

Java异常是Java提供的一种识别及响应错误的一致性机制。
Java异常机制可以是程序中的异常处理代码和正常业务代码分开,保证程序代码更加优雅,并提高程序的健壮性。在有效使用异常的情况下异常类型回答了“什么”被抛出,异常堆栈跟踪回答了“在哪”被抛出,异常信息回答了“为什么”被抛出。

87.Java异常架构

1. Throwable

  • Throwable是Java语言中所有错误与异常的超类。
  • Throwable包含两个子类:Error和Exception。
  • Throwable包含了其线程创建时线程执行堆栈的快照,它提供了printStack()等接口用于获取堆栈跟踪数据等信息。

2.Error

  • 定义:Error及其子类。程序中无法处理的错误,表示运行应用程序中出现了严重的错误。
  • 特点:此类错误一般表示代码运行时JVM出现错误。此类错误出现时,JVM将终止线程。
  • 这些错误是不受检异常。当此类错误发生时,应用程序不应该处理此类错误。

3.Exception

  • 程序本身可以捕获和处理的异常。异常又分为编译时异常和运行时异常(受检异常与非受检异常)。

运行时异常

  • 定义:RunTimeException及其子类,表示JVM在运行期间可能出现的异常。
  • 特点:Java编译器不会检查运行时异常。

编译时异常

  • 定义:Exception中除RuntimeException及其子类以外的异常。
  • 特点:Java编译器会检查它。该异常我们必须手动在代码里捕获语句来处理该异常,否则编译不会通过。

88.Java异常关键字

  • try :用于监听。将要被监听的代码放在try语句块内,当其发生异常时,异常就被抛出。
  • catch :用于捕获异常。catch用来捕获try语句块中发生的异常。
  • finally :finally语句块总是会被执行。它主要用于关闭资源(如数据库连接,网络连接,文件关闭等)。只有finally语句块执行完了之后才会执行try或catch语句块中的return或throw语句,如果finally中用了return等终止方法的语句,则不会跳回执行,直接停止。
  • throw :用于抛出异常,用于方法内。
  • throws :用于方法签名中,声明将要抛出的异常。

89.Java异常处理

  • 声明异常(throws):运行时异常(非受检异常)不能在方法签名处声明。
  • 抛出异常(throw):如果解决不了某些异常问题,且不需要调用者处理,可以直接抛出异常,throw关键字可以用于任何Java代码中。
  • 捕获异常(try-catch-finally):程序在运行之前不会报错,但是运行后可能会出现错误,但是不想直接抛出到上一级,就需要用try–catch来捕获异常。

90.Error和Exception的区别是什么

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

91.运行时异常和一般异常(受检异常)的区别

  • 运行时异常包括RuntimeException及其子类,表示虚拟机在运行时可能出现的异常,Java编译器不会检测运行时异常。
  • 受检异常就是Exception中除RuntimeException及其子类之外的异常,Java编译器会检测受检异常。
  • 区别:是否要求调用者必须处理此异常,如果强制要求,那就使用受检异常,否则就选择非受检异常。一般来讲,没有特殊要求都是用非受检异常。

92.JVM如何处理异常

  • 在一个方法中如果发生异常,这个方法会创建一个异常对象,并转交给JVM,该异常对象包含异常名称,异常描述以及异常发生时应用程序的状态。创建异常对象并转交给JVM的过程称为抛出异常。可能经过了一系列的方法调用,最终才进入了抛出异常的方法,这一系列方法调用的有序列表叫做调用栈。
  • JVM会顺着调用栈取查找看是否有可以处理异常的代码,如果有则调用异常处理代码,如果没找到,JVM则会经异常交给默认的异常处理器(JVM的一部分),默认异常处理器打印出异常并终止应用程序。

93.throws关键字和throw关键字的区别是什么

  • throws:声明异常。此关键字用于方法声明上,可以抛出多个异常,用来表示该方法可能抛出的异常列表。
  • throw:抛出异常。该关键字用在方法内部,只能用于抛出一种异常,用来抛出方法或代码块中 的受检或非受检异常。

final、finally和finalize的区别

  • final可用于修饰类,方法,变量。修饰类表示该类不能被继承;修饰方法表示该方法不能被重写;修饰变量表示该变量是一个常量,不能被重新赋值。
  • finally一般存在于try-catch代码块中,表示是否出现异常,该代码块中的内容都会执行,常用于释放资源。
  • finalize是一个Object的方法,作用是做最后一步垃圾收集器是否可以处理内存中的相关信息。

94、NoClassDefFoundError和ClassNotFoundException区别

  • NoClassDefFoundError是JVM引起的,是一个Error类型的异常,表示的是编译时该类存在,运行时找不到该类。
  • ClassNotFoundException是一个受检异常,表示该类路径传入错误,找不到该类。

95.try-catch-finally哪部分可以省略

catch或finally,可以省略其中一个,不能同时省略。

96.try-catch-finally如果catch中执行了return,finally还会执行吗

会执行,且会在try-catch语句中return执行前执行。如果finally中也有return,finally中的return会覆盖catch中的return

97.常见的运行时异常(RuntimeException)有哪些

 - ClassCastException()//类转换异常
 - IndexOutOfBoundsException()//数组越界
 - NullPointException()//空指针
 - ArraysStoreException()//数组存储异常,操作数组时,类型不一致
 - IO操作的BufferOverFlowException()

98.Java常见异常有哪些

  • java.lang.IllegalAccessError:违法访问错误。当一个应用试图访问、修改某个类的域(Field)或
    者调用其方法,但是又违反域或方法的可见性声明,则抛出该异常。
  • java.lang.InstantiationError:实例化错误。当一个应用试图通过Java的new操作符构造一个抽象
    类或者接口时抛出该异常.
  • java.lang.OutOfMemoryError:内存不足错误。当可用内存不足以让Java虚拟机分配给一个对象 时抛出该错误。
  • java.lang.StackOverflowError:堆栈溢出错误。当一个应用递归调用的层次太深而导致堆栈溢出
    或者陷入死循环时抛出该错误。
  • java.lang.ClassCastException:类造型异常。假设有类A和B(A不是B的父类或子类),O是A的
    实例,那么当强制将O构造为类B的实例时抛出该异常。该异常经常被称为强制类型转换异常。
  • java.lang.ClassNotFoundException:找不到类异常。当应用试图根据字符串形式的类名构造类,
    而在遍历CLASSPAH之后找不到对应名称的class文件时,抛出该异常。
  • java.lang.ArithmeticException:算术条件异常。譬如:整数除零等。
  • java.lang.ArrayIndexOutOfBoundsException:数组索引越界异常。当对数组的索引值为负数或
    大于等于数组大小时抛出。
  • java.lang.IndexOutOfBoundsException:索引越界异常。当访问某个序列的索引值小于0或大于
    等于序列大小时,抛出该异常。
  • java.lang.InstantiationException:实例化异常。当试图通过newInstance()方法创建某个类的实
    例,而该类是一个抽象类或接口时,抛出该异常。
  • java.lang.NoSuchFieldException:属性不存在异常。当访问某个类的不存在的属性时抛出该异 常。
  • java.lang.NoSuchMethodException:方法不存在异常。当访问某个类的不存在的方法时抛出该 异常。
  • java.lang.NullPointerException:空指针异常。当应用试图在要求使用对象的地方使用了null时,
    抛出该异常。譬如:调用null对象的实例方法、访问null对象的属性、计算null对象的长度、使用 throw语句抛出null等等。
  • java.lang.NumberFormatException:数字格式异常。当试图将一个String转换为指定的数字类
    型,而该字符串确不满足数字类型要求的格式时,抛出该异常。
  • java.lang.StringIndexOutOfBoundsException:字符串索引越界异常。当使用索引值访问某个字
    符串中的字符,而该索引值小于0或大于等于序列大小时,抛出该异常。

99.自定义异常在生产中的使用场景

  • 系统中有些错误是符合Java语法,但不符合业务逻辑。
  • 在分层的软件结构中,通常是在表现层统一对系统其他层次的异常进行捕获处理。

集合

100.什么是集合

  • 集合是一个存放数据的容器,准确的说是放数据对象引用的容器。
  • 集合存放的都是对象的引用,而不是对象本身
  • 集合类型主要有三种:Set(集),list(列表),map(映射)。

101.集合的特点

  • 集合是用于存储对象的容器,对象是用来封装数据,对象多了也需要存储集中式管理。
  • 和数组对比集合的大小不确定。因为集合的长度似乎可变的,数组的长度需要提前定义。

102.集合和数组的区别

  • 数组的长度是固定的,集合的长度是可变的。
  • 数组可以存放基本数据类型,也可以存储引用数据类型;集合只能存储引用数据类型(存放基本数据类型是都经过了自动装箱成为了一个对象)
  • 数组存储的元素必须是同一个类型的元素,集合存储对象可以是不同类型。

103.使用集合框架的好处

  • 容量自增长。
  • 提供了高性能的数据结构和算法,使编码更轻松,提高了程序的运行速度和质量。
  • 可以方便的扩展和改写 集合,提高了代码复用性和可操作性。
  • 通过JDK自带的集合类,可以降低代码维护和学习新的API的成本。

104.常用的集合类有哪些

  • Map接口和Collection接口是所有集合框架的父接口;

     1. Collection接口子接口包括:Set和List接口
     2. Map接口的实现类主要有:HashMap、TreeMap、HashTable、ConcurrentHashMap、Properties等
     3. Set接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等
     4. List接口的主要实现类有:ArrayList、LinkedList、Stack、Vector等
    

105.List,Set,Map三者的区别

  • java容器分为Collection和Map两大类,Collection(单列集合)集合的子接口有Set、List、Queue三种子接口,比较常用的是List和Set。Map不是Collection的子接口。
  • Collection集合主要有Set和List两大接口:
    - List:有序(存取顺序一致),元素可以重复,可以插入多个null元素,元素都有索引。常用的实现类有ArrayList、LinkedList、Vector。
    - Set无序(存取顺序不一致),元素不能重复,只允许存入一个null元素,必须保证元素的唯一性。Set接口常用的实现类有HashSet、LinkedHashSet以及TreeSet。
  • Map是一个双列集合(键值对集合),存储键、值和之间的映射。key无序,唯一;value不要求有序,允许重复。Map集合中检索元素,只要给出键对象,就会返回对应的值对象。
    - Map的常用实现类:HashMap、TreeMap、HashTable、LinkedHashMap、ConcurrentHashMap。

106.集合框架底层数据结构

List:ArrayList和Vector的底层是Object数组,LinkedList的底层是双向循环链表。
Set:HashSet(无序,唯一)的底层是HashMap;TreeSet(有序,唯一):红黑树(自平衡的排序二叉树)
Map:JDK1.8之前是数组加链表,JDK1.8之后是数组+链表+红黑树。TreeMap:红黑树(自平衡的排序二叉树)

107.哪些集合类是线程安全的

  • Vector:所有方法都比ArrayList多了个synchronized(线程安全),因为效率太低,现在已经不建议使用。
  • HashTable:所有方法比HashMap多了个synchronized(线程安全),不建议使用。
  • ConcurrentHashMap:是Java5中支持高并发、高吞吐量的线程安全HashMap实现。它由Segment数组结构和HashEntry数组结构组成。Segment在其中扮演锁的角色,HashEntry则用于存储键-值对数据。一个ConcurrentHashMap中包含一个Segment数组,segment数组的结构和hashMap类似,是一种数组和链表结构;一个Segment里面包含一个HashEntry数组,每个HashEntry是一个链表结构的元素;每个segment守护着一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须先获取它对应的Segment锁。(推荐使用)

108.集合中的快速失败机制“fail-fast”

  • 是Java中的一种错误检测机制,当多个线程对集合进行结构上的改变的操作时,有可能产生fail-fast机制。
  • 例如存在两个线程,线程一在遍历集合A中的元素的过程中线程二来改变集合A的结构,此时将会抛出C哦那current ModificationException异常,从而产生fail-fast机制
  • 原因:迭代器在遍历集合时会使用到一个modCount变量,集合被遍历期间如果内容发生改变,modCount就会发生变化,当迭代器使用hasNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是就返回遍历,否则抛出异常,终止遍历。
  • 解决办法:在遍历过程中,所有涉及改变modCount的地方全部加上锁,或者使用CopOnWriteArrayList替换ArrayList。

109.怎么确保一个集合不能被修改

可以使用Collections.unmodifiableCollection(Collection c)方法来创建一个只读集合,这样任何改变该集合的操作都会抛出java.lang.UnsupportedOperationException异常。

List<String> list = new ArrayList<>();
list.add("x");
Collection<String> clist = Collections.unmodifiableCollection(list);
clist.add("y");//运行时报错

List

110.迭代器Iterator是什么

迭代器接口提供任何遍历collection的接口,因为Collection接口继承了Iterator迭代器;迭代器的特点是只能单向遍历,但是更安全,因为它可以确保在当前遍历的集合元素被更改的时候,就会抛出ConcurrntModificationException异常。

111.如何遍历移除Collection中的元素

Iterator<Integer> it = list.iterator();
while(it.hasNext()){
    it.remove();
}

112.Iterator和ListIterator的区别

  • Iterator可以遍历List和Set集合,ListIterator只能遍历List集合。
  • Iterator只能单向遍历,ListIterator可以双向遍历(向前/后遍历)。
  • ListIterator实现Iterator接口,添加了一些额外的功能。

113.遍历一个 List 有哪些不同的方式?每种方法的实现原理是什么?Java 中 List遍历的最佳实践是什么?

  • for循环:基于计数器,通过在集合外部维护一个计数器,然后依次读取每一个位置的元素,当读取到最后一个元素后停止。
  • 迭代器:
  • foreach:foreach内部也是采用的iterator迭代器,使用时不需要显示声明iterator或计数器。缺点是只能在内部遍历,不能在遍历过程中操作数据集合。

114.ArrayList的优缺点

优点:

  • ArrayList底层是数组,是一种随机访问模式。ArrayList实现了RandomAccess接口,查询时非常快。
  • ArrayList顺序添加一个元素非常方便。
    缺点:
  • 删除元素时,需要做一次元素复制操作,如果复制的元素较多,就比较耗费性能。
  • 插入元素时,需要做一次元素的复制操作。
    注:ArrayList比较适合做顺序添加和查询的场景。

115.如何实现数组和List之间的转换

  • 数组转List:使用Arrays.asList(array)进行转换。
  • List转数组:list.toArray()。

116.ArrayList和LinkedList的区别

  • 数据结构实现:ArrayList是动态数组的数据结构实现,LinkedList是双向链表的数据结构实现。
  • 随机访问效率:ArrayList比LinkList在随机访问的时候效率要高,因为LinkedList是线性的数据存储方式,所以需要移动指针从前往后依次寻找。
  • 增加和删除效率:在非首尾的增加或删除操作时,LinkedList要比ArrayList快,因为ArrayList增删操作要影响数组内其他数据的下标。
  • 内存空间占用:LinkedList比ArrayList更占内存,因为LinkedList的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素。
  • 线程安全:ArrayList和LinkedList都是不同步的,都不是线程安全的。

117.ArraList和Vector的区别是什么

  • 线程安全:Vector使用了Synchronized来实现线程同步,是线程安全的,ArrayList是非线程安全的。
  • 性能:ArrayList性能由于Vector。
  • 扩容:ArrayList和LikedList都会根据实际的需要动态的调整容量,Vector在每次扩容时会增加一倍,而ArrayList只会增加50%。
  • 他们都实现了List接口,都是有序集合。

118.插入数据时,ArrayList、LinkedList、Vector谁速度较快?阐述ArrayList、Vector、LinkedList 的存储性能和特性?

  • ArrayList和Vector底层的实现都是使用数组方式存储数据。数组元素数大于实际存储的数据以便增加和插入元素,他们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以查询数据快,插入数据慢。
  • Vector由于是线程安全的,内部加了synchronized关键字修饰,所以性能比ArrayList更差。
  • LinkedList使用双向链表实现存储,按序号查询数据需要进行向前或向后遍历,但插入数据时只需要记录当前项的前后项即可,所以LinkedList插入数据速度快。

119.多线程场景下如何安全使用ArrayList

可通过工具类的Collections的sychronizedList方法将其转换为线程安全的容器再使用。

List<String> synchronizedList = collections.synchronizedList(list);

120.为什么 ArrayList 的 elementData 加上 transient 修饰?

Java中transient关键字的详细总结
用transient表示不想被序列化。
ArrayList实现类Serializable接口,表示它支持序列化,transient的作用表示表示不希望elementData数组被序列化,重新写了一个writeObject方法实现;每次序列化时,先调用defaultWriteObject()方法序列化ArrayList中的非transient元素,然后遍历elementData,只序列化已存入的元素,这样既加快了序列化的速度,又减小了序列化之后的文件大小。

121.List和Set的区别

  • 都继承自Collection接口
  • List是一个元素存取顺序一致的有序容器,元素可以重复,可以有多个null值,元素都有索引,常用的实现类有ArrayList,LinkedList,Vector。
  • Set是一个存取顺序不一致的无序容器,不能重复存储元素,只允许存入一个null元素必须保证元素的唯一性。Set接口常用实现类有HashSet、LinkedHashSet、TreeSet。
  • 遍历元素时,List可以通过for循环和迭代器,Set只能通过迭代器,因为set无序,无法通过下标来取得想要的值。
  • Set和List对比:
    - Set检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置变化。
    - List可以动态增长,查找元素效率高,插入删除元素效率低,因为会引起其他元素位置改变。

Set

122.HashSet的实现原理

  • HashSet是基于HashMap实现的,HashSet的值放在HashMap的key上,HashMap的value统一为PRESENT(是一个常量:new Object()),因此HashSet的相关操作都是调用底层的HashMap的相关方法实现的,所以HashSet不允许有重复的值。

123.HashSet如何保证数据不可重复的

  • 向hashset中add()数据时,判断元素是否已存在,不仅要比较hash值,还要结合equals方法比较。
  • hashset的add方法会调用hashmap的put方法。
  • HashMap的key是唯一的,由源码可以看出hashset添加进去的值就是作为hashmap的key。
  • hashset部分源码
private static final Object PRESENT = new Object(); 
private transient HashMap<E,Object> map; 
public HashSet() {
 map = new HashMap<>(); 
 }
public boolean add(E e) { 
// 调用HashMap的put方法,PRESENT是一个至始至终都相同的虚值 
return map.put(e, PRESENT)==null; 
}

124.HashSet与HashMap的区别

HashMapHashSet
实现了Map接口实现了Set接口
存储键值对仅存储对象
添加元素put添加元素add
HashMap使用key计算HashcodeHashSet使用成员对象来计算Hashcode值,对于两个对象来说,hashcode值有可能相同,所以会结合equals方法来判断
HashMap相对于HashSet较快,因为它使用唯一的键获取对应的值HashSet较HashMap来说比较慢

Map接口

125.什么是Hash算法

哈希算法是指将任意长度的二进制映射为固定长度的较小的二进制值,这个较小的二进制值就是哈希值,是一个整型。

126.什么是链表

  • 链表可以将物理地址上不连续的数据连接起来,通过指针来对物理地址进行操作,实现增删改查等功能。
  • 链表可以分为单线链表和双向链表
    - 单链表:每个节点包含两部分,一部分存放数据变量的data,一部分是指向下一节点的next指针。
    在这里插入图片描述
    - 双链表:除了单链表包含的两部分还增加了pre前一个节点的指针。
    在这里插入图片描述
  • 链表的优点
    • 插入删除速度快(因为next指针指向下一个节点,通过改变指针的指向可以方便的增加删除元素)
    • 内存利用率高,不会浪费内存(可以使用内存中细小的不连续的空间,并且在需要空间时才创建空间)
    • 大小没有固定,拓展很灵活。
  • 链表的缺点
    • 不能随即查找,必须从第一个开始遍历,查找效率低。

127.HashMap的实现原理

HashMap的实现原理详细介绍----->🤚

  • HashMap是基于哈希表的Map接口的非同步实现。此实现提供了所有可选映射操作,允许null值和null键且不保证映射的顺序。
  • 在Java编程语言中,最基本的数据结构就两种,一个是数组,另一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造,HashMap是一个链表散列的数据结构,即数组和链表的结合体。
  • HashMap是基于Hash算法实现的
    • 当往HashMap中put元素时,利用key的hashcode重新hash计算出当前对象的元素在数组中的下标。
    • 存储时,如果出现hash值相同的key,有两种情况:
      • 如果key相同,则覆盖原始值。
      • 如果不同(出现冲突),则将当前key-value放入链表中
    • 获取时,直接找到hash值对应的下标,在进一步判断key是否相同,从而找到对象值。

128.HashMap在JDK1.7和1.8中有哪些不同,HashMap的底层实现

在Java中,数组和链表是保存数据的两种比较简单的结构。数组的特点是寻址容易,插入和删除难;链表的特点是寻址困难,但插入和删除容易;所以将数组和链表结合到一起,用“拉链法”解决哈希冲突。

  • JDK1.8之前:
    • 采用的拉链法解决哈希冲突,拉链法是将链表和数组结合,即创建一个链表数组,数组中每一个位置就是一个链表,当发生冲突时,将冲突的值加到相应位置的链表中即可。
  • JDK1.8之后:
    • 当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。
  • 比较:
    • JDK1.7VSJDK1.8

    1.resize扩容优化
    2.引入了红黑树,目的是避免单条链表过长而影响查询效率
    解决了多线程死循环问题,但还是非线程安全的,多线程时可能会造成数据丢失问题。

不同JDK1.7JDK1.8
存储结构数组+链表数组+链表+红黑树
初始化方式inflateTable()集成到了扩容函数resize()中
hash值计算方式扰动处理=9次扰动=4次位运算+5次异或运算扰动次数=2次扰动=1次位运算+1次异或运算
存放数据的规则无冲突时,存放数组;冲突时,粗放链表无冲突时,存放数组;冲突时链表长度<8:存放单链表;冲突时链表长度>8:树化并存放到红黑树
插入数据的方式头插法(先将原位置的数据往后移1位,在插入数据到该位置)尾插法,直接插入到链表或树尾部
扩容后存储位置的计算方式全部按照原来的方式进行计算按照扩容后的规律(即扩容后的位置=原位置或者原位置+旧容量)

129.什么是红黑树

什么是红黑树漫画版通俗易懂------>🤚

  • 红黑树是一种特殊的二叉查找树。红黑树的每个节点上都有存储位表示结点的颜色,可以是红或黑。
  • 根节点是黑色。
  • 每个叶子节点都是黑色的空结点。
  • 每个红色节点的两个子节点都是黑色(即不能有两个连续的红色节点)。
  • 从任意节点到其每个叶子的所有路径都包含相同数目的黑色节点。
  • 红黑树的基本操作是添加、修改。此过程会涉及到红黑树的左右旋转或变色。

130.HahMap的put方法具体流程

源码展示:

public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

//实现map.put的相关方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        //tab为空,创建
        //table未进行初始化长度为0,进行扩容(resize())
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
            //计算hash值确定index,将新生成的结点放入数组中,
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
            //若该数组位置已存在元素
        else {
            Node<K,V> e; K k;
            //key相等且不为空,将第一个元素用e来保存
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
                //若不相等怎将原来的key作为红黑树的结点
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
            //在链表最末位置插入结点
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                    //如果链表为空,在尾部插入新结点
                        p.next = newNode(hash, key, value, null);
                        //判断链表长度是否达到了转化为红黑树的阈值,临界值为8
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        //链表结构转红黑树
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            //key相等且不为空,新值覆盖旧值
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        //超过最大容量就扩容
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
  1. 判断键值对数组table[]是否为空,如果是则进行扩容resize();
  2. 根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建结点添加,进行第3步;如果table[i]不为空,则进一步判断是覆盖还是加入链表,进行第6步;
  3. 判断table[i]的首个元素是否和key一样,如果相同则新值覆盖旧值,如果不同,则进行第4步(相同指的是哈希值与equals都反映出相同);
  4. 判断是否是红黑树,如果是就在树中插入键值对;不是就进行第5步 ;
  5. 判断链表长度是否大于阈值(8),大于就转红黑树,小于则插入链表末;
  6. 插入成功后判断实际存在的键值对数量是否超过了最大容量,超过就需要扩容。

131.HashMap的扩容操作是怎么实现的

多个判断+执行
源码展示:

    final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        //原如果大于最大容量了,则赋值为整数最大的值为阈值
        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            ///如果当前hash桶数组的长度在扩容后仍然小于最大容量,则双倍扩容阈值
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // 手动赋值了初始容量
            newCap = oldThr;
        else {               // 无参构造创建的map,给出默认容量是16,阈值是12
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        if (newThr == 0) {//新的阈值等于新的cup*0.75
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        计算出新数组的长度然后赋值给成员变量table
        @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];//新建哈希数组
        table = newTab;//将新数组复制给旧数组
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                //下面即是hash元素的重排序
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

132.HashMap怎样解决哈希冲突的

  • 什么是哈希:散列,就是指用哈希算法把任意长度的二进制映射为固定长度的较小的二级制值。
  • 什么是哈希冲突:当两个不同的输入值,根据同一散列函数计算出相同散列值的现象。
  • HahMap的位置即是由hash(key.hashCode())决定。
  • hash()函数
static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);//与自己右移16位进行异或运算(高低位异或)
    }
    //这比JDK1.8之前更简洁,JDK1.7中有4次位运算和5次异或运算(9次扰动),1.8中只进行2次扰动
  • 哈希冲突的解决办法
    开放定址法:通过一个探测算法,当某个数组位已经被占据的情况下继续查找下一个可以使用的数组位。
    重Hash法:同时构造多个不同的hash函数,直到某个函数计算出不重复的hash值,这样时间开销比较大。
    链表法:将相同哈希值的对象组织成一个链表放在哈希值对应的数组位。
    建立公共溢出区:当哈希表分为公共表和溢出表,当溢出发生时,将所有溢出数据放到溢出区。

133.能否使用任何类作为Map的key

可以使用任何类作为Map的key,但在使用前需要考虑如下情况:

  • 如果类重写了equals()方法,也应该重写hashCode()方法。
  • 用户自定义key类最佳实践是使之为不可变的,这样hashCode()值就可以被缓存起来,拥有更好的性能。不可变的类也可以确保hashcode()和equals()在未来不会改变,这样可解决与可变相关的问题。
  • 如果一个类没有使用equals(),不应该在hashCode()中使用它。
  • 类的所有实例需要遵循与equals()和hashCode()相关的规则。

134.为什么HashMap中String、Integer这样的包装类适合作为key

  • 都是final类型,保证了key的不可更改性,能有效减少哈希冲突,不会存在获取hash值不同的情况。
  • 内部已重写了equals(),hashCode()等方法,不容易出现Hash值计算错误的情况。

135.如果使用Object作为HashMap的Key,应该怎么办

  • 重写hashCode()和equals()方法
    • 重写hashCode()是因为需要计算存储数据的存储位置。
    • 重写equals(),需要遵守自反性、对称性、一致性以及对于任何非null的引用值x,x.equals(null)必须返回false的几个特性,目的是为了保证key在哈希表中的唯一性。

136.HashMap为什么不直接使用hashCode()处理后的哈希值直接作为table的下标

hashcode返回的是一个int值,范围为-(231)–(231-1),约有40亿个映射空间,而hashmap的容量范围在16—2^30,可能导致hashcode计算出来的值不在数组大小范围内,导致无法匹配存储位置。
解决方案:

  • HashMap实现了自己的hash方法,通过两次扰动,降低了哈希冲突发生的概率,也使数据分布得更平均。
  • 在保证数组长度为2的幂次方是,hash(key.hashCode())&(table.length-1)来获取数组下标的方式进行存储。这样比取余操作更高效;只有当数组长度为2的幂次方时,hash(key.hashCode())&(table.length-1)才等价于hash(key.hashCode())&table.length;还解决了哈希值与数组大小范围不匹配的问题。

137.HashMap 的长度为什么是2的幂次方

哈希部分详解—>🤚

138.HashMap 与 HashTable 有什么区别

区别HashMapHashTable
线程安全非线程安全线程安全
效率更高基本被淘汰,建议用concurrentHashMap代替
对null键和null值的支持支持一个键,多个值为null只要put一个null键值,直接抛空指针异常
初始容量大小和每次扩容量的大小16,2倍11,2n+1
底层数据结构转红黑树没有转红黑树机制
如果创建时给了初始值,HashMap会优化扩充为2的幂次方,HashTable是直接用赋的初值

139.什么是TreeMap

  • 是一个有序的key-value集合,是通过红黑树实现的。
  • 该映射根据其键的自然顺序或创建映射时提供的comparator进行排序,具体取决于使用的构造方法。
  • 非线程安全。

140.如何决定使用 HashMap 还是 TreeMap

插入删除时用HashMap,遍历时用TreeMap,因为这样更高效。

141.HashMap 和 ConcurrentHashMap 的区别

  • ConcurrentHashMap对整个数组进行了分割分段(Segment),然后在每一个分段上都用lock锁来进行保护,相对于HashTable的同步锁的粒度更细,并发性能更好,HashMap没有锁,不是线程安全的。(JDK1.8之前,之后是通过CAS算法实现的)。
  • Hashmap的键值对允许有null,但是ConcurrentHashMap都不允许。

142.ConcurrentHashMap 和 Hashtable 的区别

  • 底层数据结构
    • ConcurrentHashMap的底层数据结构:JDK1.8之前是分段的数组(Segment数组+HashEntry数组)+链表,1.8之后是分段的数组+链表/红黑树;HashTable的底层是数组+链表。
  • 实现线程安全的方式
    • JDK1.8之前,ConcurrentHashMap(分段锁)对整个数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,都熬线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率(默认分配16个Segment)。JDK1.8的时候已经摒弃了Segment的概念,直接用Node数组+链表+红黑树的数据结构来实现,并发控制使用了同步锁和CAS来操作。
    • HashTable使用同步锁来保证线程安全,由于是一个排他锁,导致效率极低。
      注:从hashTable到JDK1.8之前的ConcurrentHashMap到JDK1.8之后的ConcurrentHashMap就是一个锁的方式不断细粒度的过程,实现过程中锁的粒度越来越细,提高了并发访问效率且线程安全。

143.ConcurrentHashMap 底层具体实现知道吗?实现原理是什么

首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。
在JDK1.7中,ConcurrentHashMap采用Segment + HashEntry的方式进行实现,结构如下:一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和HashMap类似,是一种数组和链表结构,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素,每个 Segment 守护着一个HashEntry数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment的锁。

  1. 该类包含两个静态内部类 HashEntry 和 Segment ;前者用来封装映射表的键值对,后者用来充当
    锁的角色;
  2. Segment 是一种可重入的锁 ReentrantLock,每个 Segment 守护一个HashEntry 数组里得元
    素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment 锁。

在JDK1.8中,放弃了Segment臃肿的设计,取而代之的是采用Node + CAS + Synchronized来保证并发安全进行实现,synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提升N倍。

辅助工具类

144.Array 和 ArrayList 有何区别

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

145.如何实现 Array 和 List 之间的转换

  • Array转List:Arrays.asList(array)。
  • List转Array:list.toArray()。

146.comparable 和 comparator的区别

  • comparable出自java.lang包,它有一个compareTo(Object obj)方法用来排序。
  • comparator出自java.util包,它有一个compare(Object obj1,Object obj2)方法用来排序。

147.Collection 和 Collections 有什么区别

  • Collection是一个集合的顶级接口,它提供了对集合对象进行基本操作的通用接口方法。
  • Collections是一个工具类,其中提供了一系列静态方法,用于对集合中元素进行排序,搜索以及线程安全等操作。

148. TreeMap 和 TreeSet 在排序时如何比较元素?Collections 工具类中的 sort()方法如何比较元素

泛型

首先了解一下常用的通配符:T,E,K,V,?

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

🤚Java泛型详解

149. Java中的泛型是什么 ? 使用泛型的好处是什么

  • Java泛型(generics)是JDK5中引入的一个新特性,泛型提供了编译时类型安全监测机制,该机制允许我们在编译时检测到非法的数据类型结构。泛型的本质是参数化类型,即所操作的数据类型被指定为一个参数。
  • 好处:
    1. 类型安全。
    2. 消除强制类型转换。
    3. 潜在的性能收益,指定了集合中的参数类型后JVM会减少类型强制转换的操作,有提高性能的可能性。

150.Java的泛型是如何工作的 ? 什么是类型擦除

泛型是通过类型擦除实现的,编译器在编译源代码时,擦除了所有类型相关的信息,所以在运行时不存在任何类型相关信息。例如List 在运行时用的是List来表示。这样做是为了确保能和Java5之前的二进制类库进行兼容。你无法在运行时访问到参数类型,因为编译器已经把泛型类型转换为了原始类型。

151.什么是泛型中的限定通配符和非限定通配符

  • 限定通配符对类型进行了限定。
  • 两种限定通配符
    1. <? extends T>,它通过确保类型必须是T的子类来设定类型的上界。
    2. <? super T>,它通过确保类型必须是T的父类来设定类型的下界。
  • 泛型类型必须使用限定内的类型来进行初始化,否则会导致编译错误;<?>表示非限定通配符,因为<?>可以用任意类型来替代。

152.List<? extends T>和List <? super T>之间有什么区别

  • List<? extends T>可以接受任何继承自T的类型的List,包括T类型;List <? super T>可以接受任何T的父类类型的List,包括T类型。

153.如何编写一个泛型方法,让它能接受泛型参数并返回泛型类型

用相应的占位符代替参数位置

public V put(K key, V value) {

              return cache.put(key, value);

      }

154.Java中如何使用泛型编写带有参数的类

同方法

155.可以把List传递给一个接受List参数的方法吗

不能。会导致编译错误。

156.Array中可以用泛型吗

不能。因为数组运行时才会检测类型,但是泛型在运行时已经擦掉了。

157.如何阻止Java中的类型未检查的警告

使用注解@SuppressWarnings(“unchecked”)

JUC

158.为什么要使用并发编程

  • 充分利用多核CPU的计算能力。
  • 方便进行业务拆分,提升应用性能。

159.多线程应用场景

  • 迅雷多线程下载。
  • 数据库连接池。
  • 分批发送短信。

160.并发编程的特点

并发编程是为了提高程序的执行效率,提高程序运行速度,但并发编程并不是总是能提高运行速度,还可能引发跟中问题,比如:内存泄漏,上线文切换,线程安全,死锁等。

161.并发编程的三个必要因素

  • 原子性:原子,即一个不可分割的颗粒。原子性指的是一个或多个操作要么全部执行成功,要么全部执行失败。
  • 可见性:一个线程对共享变量的修改,另一个线程能够立刻看到。
  • 有序性:程序执行的顺序是按照代码的先后顺序执行。

162.Java程序中怎样保证多线程的运行安全

出现线程安全问题的原因一般有三个:

  • 线程切换时带来的原子性问题,解决办法是使用多线程之间同步synchronized或使用锁(lock)。
  • 缓存导致的可见性问题,解决办法是:synchronized,volatile,lock,可以解决可见性问题。
  • 编译优化带来的有序性问题,解决办法是:Happens-Before规则可以解决有序性问题。

163.并行和并发的区别

  • 并发:多个任务在同一个CPU核上,按细分的时间片轮流交替执行,从逻辑上来看那些任务是同时执行。
  • 并行:单位时间内,多个处理器或多核处理器同时处理多个任务,是真正意义上的“同时进行”。
  • 串行:有n个任务,由一个线程顺序执行。由于任务、方法都在一个线程执行所以不存在线程不安全情况,也就不存在临界区的问题。
  • 并发即相当于两个人一起用一台电脑,一人用一会;并行即两个人分配了两台电脑;串行即相当于两个人排队使用一台电脑。

164.什么是多线程

  • 多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务。

165.多线程的优缺点

  • 优点:可以提高CPU的利用率。在多线程程序中,一个线程必须等待的时候,CPU可以运行其他的线程而不是等待,这样就大大提高了程序的效率。也就是说一个程序可以创建多个并行执行的线程来完成各自的任务。
  • 缺点:线程也是程序,所以线程越多内存占用也越多;多线程需要协调和管理,所以需要CPU时间跟踪线程;线程之间对共享资源的访问会相互影响,必须解决竟用共享资源的问题。

166.线程和进程的区别

  • 进程:一个在内存中运行的应用程序。每个正在系统上运行的程序都是一个进程。
  • 线程:进程中的一个执行任务(控制单元),它负责在程序里独立执行。
  • 区别:
    • 根本区别:进程是操作系统资源分配的基本单位,线程是处理器任务调度和执行的基本单位。
    • 资源开销:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看作轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。
    • 包含关系:如果一个进程内有多个线程,则执行过程不是一条线,而是多条线共同完成;线程是进程的一部分,所以线程也被称为轻量级的进程或轻权进程。
    • 内存分配:同一进程的线程共享本进程的地址空间和资源,而进程与进程之间的地址空间和资源是相互独立的。
    • 影响关系:一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃有可能造成整个进程死掉。所以多线程要比多进程健壮。
    • 执行过程:每个独立的进程有程序运行的入口、顺序执行序列和程序出口。但是线程不能独立执行,必须已存在应用程序中,由应用程序提供多个线程执行控制,两者均可并发执行。

167.什么是上下文切换

  • 多线程编程中一般线程的个数大于CPU核心的个数,而一个CPU核心在任意时刻只能被一个线程使用,为了让这些线程都能得到有效执行,CPU采取的策略是为每个线程分配时间片并轮转的形式。当一个线程的时间片用完后就会重新处于就绪状态让给其他线程使用,这个过程就属于一次上下文切换。
  • 当前任务在执行完CPU时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换回这个任务时,可以再加载这个任务的状态。任务从保存到在加载的过程就是一次上下文切换。
  • 上下文切换是计算密集型的,它需要相当可观的处理器时间,在每秒几十上百次的切换中,每次切换都需要纳秒量级的时间,上下文切换对系统来说意味着消耗大量的CPU时间,这类过程可能是操作系统中时间消耗最大的操作。
  • Linux系统相比于其它系统其上下文切换和模式切换的时间消耗更少。

168.守护线程和用户线程有什么区别

  • 用户(user)线程:运行在前台,执行具体的任务,如程序的主线程、连接网络的子线程都是用户线程。
  • 守护(daemon)线程:运行在后台,为其他前台线程服务,一旦所有用户线程结束,守护线程也会随JVM一起结束工作。

169.如何在win和Linux上查看哪个线程的cpu利用率最高

  • Windows:Ctrl+Shift+Esc直接打开任务管理器查看。
  • Linux:先使用top命令列出所有进程,按Shift+p,查出cpu利用最厉害的进程号(pid),在执行top -H -p pid(加上-H参数的目的是为了让终端top一行显示一个线程,否则显示的是进程),按下Shift+p,查出cpu利用最厉害的线程号,然后执行printf “%x\n” pid打印出线程号,然后使用ps -T -p 线程号查看信息。

170.什么是线程死锁

  • 死锁是指两个或以上的进程(线程)在执行过程中,由于竞争资源或由于彼此通信而造成的一种阻塞的现象,若无外力作用,他们将无法继续下去,此时称系统进入了死锁状态或是产生了死锁这些永远在互相等待的进程或线程称为死锁进程或死锁线程。
  • 多个线程同时被阻塞,他们中的一个或全部都在等待某个资源被释放。由于线程被无限期的阻塞,因此程序不可能被正常终止。

171.形成死锁的四个必要条件是什么

  • 互斥条件:在一段时间内某个资源只能由一个进程占用,如果此时还有其他进程请求资源,就只能等待,直至占有资源的进程使用完后释放该资源。
  • 占有且等待条件:指进程已经占有至少一个资源但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程阻塞,但又对自己已获得的资源保持不放。
  • 不可抢占条件:别的进程已经占用了某项资源,你不能因为自己也需要该资源,就去把别人的资源抢过来。
  • 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系(比如说一个进程集合,A在等B,B在等C,C在等A)

172.如何避免线程死锁

  • 避免一个线程同时获得多个锁。
  • 避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源
  • 使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制

173.创建线程的四种方式

  • 继承Tread类
//继承thread类
class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"thread run()方法执行");
    }
}
  • 实现Runable接口
//实现Runable接口
class MyRunable implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"runable run()方法执行");
    }
}
  • 实现callable接口
//实现Callable接口
class MyCallable implements Callable{
    @Override
    public Integer call() throws Exception {
        System.out.println(Thread.currentThread().getName()+"call()方法执行");
        return 1;
    }
}
  • 使用匿名内部类的方式
    public static void main(String[] args) throws Exception {
        //匿名内部类
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0;i<10;i++){
                    System.out.println("i:"+i);
                }
            }
        }).start();
        new MyThread().start();
        new Thread(new MyRunable()).start();
        FutureTask<Integer> futureTask = new FutureTask<>(new MyCallable());
        new Thread(futureTask).start();
        System.out.println(Thread.currentThread().getName()+"运行中");
    }
}

创建线程四种方式执行结果

174.runnable和callable的区别

相同点:

  • 都是接口。
  • 都可以编写多线程程序
  • 都可以采用Thread.start()启动
    区别:
  • Runable接口run方法无返回值;callable接口call方法有返回值,是个泛型,和Future、FutureTask配合可以用来获取异步执行的结果。
  • Runable接口run方法只能抛出运行时异常,且无法捕捉处理;callable接口call方法允许抛出异常,可以获取异常信息。
    注:callable接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行。

175.线程的run()和start()有什么区别

  • 每个线程都是通过某个特定的Thread对象所对应的run()方法来完成操作,run()方法被称为线程体。通过调用Thread的start()方法来启动一个线程。
  • start()方法用于启动线程,run()方法用来执行线程运行时代码。run()可以重复调用,start只能调用一次。
  • start()方法启动线程,真正实现了多线程运行。调用start()方法不用等到run()方法执行完毕,可以直接继续执行其他代码;此时线程是处于就绪状态的,并没有开始运行,然后通过对应的Thread类调用run(0方法来完成其运行状态,run()方法运行结束,此线程终止。然后CPU在调度其他线程。
  • run()方法是在本线程内的方法,只是该线程的一个函数,而不是多线程的。如果直接调用run(),其实就相当于地调用了一个普通函数,直接调用run()方法只有等run()方法执行完后才会继续执行下面的代码,根本就没有实现多线程,所以多线程需要start()方法来调用。

176.为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用run() 方法

  • 创建一个Thread类,线程进入新建状态,调用start()方法,会启动一个线程并使线程进入就绪状态,当分配到时间片时就开始运行了。start()会执行线程的相应准备工作,然后自动执行run()方法里面的内容,主线程的代码并不受阻塞,可以继续执行,这才是多线程。
  • 直接调用run()方法,会把run()当作main线程下的一个普通方法来执行,并不会在某个线程中执行它,并不是多线程。

177.什么是 Callable 和 Future

  • Callable接口类似于Runable接口,但是Callable接口被线程执行后可以有返回值,这个返回值可以被Future拿到。
  • Future表示异步任务,是一个可能还没有完成的异步任务的结果。Callable用于产生结果,Future用于获取结果。

178.什么是 FutureTask

  • FutureTask表示一个异步运算的任务。FutureTask里面可以传一个Callable的具体实现类,可以对这个异步运算的任务的结果进行等待获取、判断是否已经完成、取消任务等操作。只有当运算完成时结果才能取回,如果运行尚未完成,get方法将会阻塞。由于FutureTask也是Runable接口的实现类,FutureTask也可以放入线程池中。

179.线程的五大状态

  • 新建(new):新创建一个线程对象。
  • 就绪(可运行的状态start):线程对象创建后,当调用线程对象的start()方法,该线程就处于就绪状态,等待被线程调度选中,获得cpu的使用权。
  • 运行(running):处于就绪状态的线程获得了cpu的时间片,执行程序代码。
  • 阻塞(block):处于运行状态中的线程由于某种原因,暂时放弃了对cpu的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才有可能再次被cpu调度进入运行状态。
    • 等待阻塞:运行中的线程执行wait()方法,JVM会把该线程放入等待队列(waiting queue)中,使本线程进入阻塞状态。
    • 同步阻塞:线程在获取synchronized同步锁时失败(因为锁被其他线程占用),JVM会把该线程放入锁池(lock pool)中,线程会进入同步阻塞状态。
    • 其他阻塞:通过调用现成的sleep()或join()或发出I/O请求时,线程会进入到阻塞状态。当sleep()状态超时时、join()等待线程终止或者超时、或I/O处理完毕时,线程重新转入就绪状态。
  • 死亡(dead):线程run()、main()方法执行结束,或因异常退出了run()方法,则该线程结束生命周期。已结束的线程不可再次复生。

注:就绪状态是进入运行状态的唯一接口,想要进行如运行状态执行,必须先处于就绪状态。

180. Java 中用到的线程调度算法是什么

  • 计算机通常只有一个CPU,在任意时刻只能执行一条机器指令,每个线程只有获取CPU的使用权才能执行命令在运行池中,会有多个处于就绪状态的线程在等待CPU,Java虚拟机的一项任务就是负责线程调度,线程调度是指按照特定机制为多个线程分配CPU的使用权。(Java是由JVM中的线程计数器来实现线程调度)
  • 调度模型:分时调度模型和抢占式调度模型
    • 分时调度模型:指让所有的线程轮流获得cpu的使用权,并且平均分配每个线程占用的CPU时间片。
    • 抢占式调度模型:指优先让可运行池中优先级高的线程占用CPU,如果可运行线程池中线程的优先级相同,就随机选一个线程占用CPU,直到它结束。

181.线程的调度策略

线程调度器优先选择优先级最高的线程执行,如果发生以下情况,就会终止线程执行:

  • 线程中调用yield方法让出对CPU占用的权力
  • 线程中调用了sleep方法使线程进入睡眠状态
  • 线程由于I/O操作受到阻塞
  • 另一个更高优先级线程出现
  • 在支持时间片的系统中,某个线程的时间片用完

182.什么是线程调度器(Thread Scheduler)和时间分片(Time Slicing )?

  • 线程调度器是一个操作系统服务,它负责为就绪状态的线程分配时间片,一旦我们创建了线程并使用了start方法,线程的执行就依赖于线程调度器的调度。
  • 时间分片时指将可用的CPU时间分配给就绪状态的线程的过程。分配CPU时间可以基于线程优先级或者线程等待的时间。
    注:线程调度不受Java虚拟机控制,即不要让程序依赖于线程的优先级。

183.请说出与线程同步以及线程调度相关的方法

  • wait():使一个线程处于等待(阻塞)状态,并且释放所持有对象的锁。
  • sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理InterruptedException异常。
  • notify():唤醒一个处于等待状态的线程(在调用此方法时,并不能精确地唤醒某一个等待的线程,而是由JVM决定唤醒哪一个,且与优先级无关)
  • notifyAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让他们竞争,只有获得锁的线程才能进入就绪状态。

184.sleep() 和 wait() 有什么区别

两者都可以暂停线程的执行

区别sleepwait
Thread类的静态方法Object类的方法
是否释放锁
用途通常用于暂停执行通常用于线程间的通信/交互
用法被调用后线程会自动苏醒调用后只能被别的线程用同一个对象上的notify唤醒

185.你是如何调用 wait() 方法的?使用 if 块还是循环?为什么

应该用循环,因为处于等待状态的线程可能会收到伪唤醒和错误警报,如果不在循环中检查等待条件,程序可能会在没有满足结束条件下退出。当线程获取到CPU的使用权限时,其他条件可能还没满足,所以在处理前,循环检测条件是否满足会更好。

synchronized(monitor){
	//判断条件是否满足
	while(!locked){
		//等待唤醒
		monitor.wait();
		}
	//处理其他的业务逻辑
}

186.为什么线程通信的方法 wait(), notify()和 notifyAll()被定义在 Object 类里

  • 因为Java所有类都继承了Object类,Java想让任何对象都可以作为锁,在Java中线程并没有可供任何对象使用的锁,所以任意对象调用方法一定定义在Object类中。

187.为什么 wait(), notify()和 notifyAll()必须在同步方法或者同步块中被调用

  • 因为不管是等待还是唤醒的过程,调用wait(),notify()方法的前提都是该对象本身需要带锁,它才能释放锁,所以这三个方法只能在同步方法或同步块中调用。

188. Thread 类中的 yield 方法有什么作用

  • 使当前线程从运行状态变为就绪状态。

189.为什么 Thread 类的 sleep()和 yield ()方法是静态的

网上搜到的都是这句话:Thread 类的 sleep()和 yield()方法将在当前正在执行的线程上运行。所以在其他处于等待状态的线程上调用这些方法是没有意义的。这就是为什么这些方法是静态的。

此处我感觉比较难理解,摆个在这里,表示自己很懵。

public class whySleepAndYieldStaitc {

    public static void main(String[] args) {
        new test1().start();
        new test2().start();
    }
}
class test1 extends Thread{
    @Override
    public void run() {
        System.out.println("线程test1启动");
        try {
            sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程Test1调用线程2的sleep-------------------------------------");
        try {
            test2.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 10; i++) {
            System.out.println(test1.currentThread().getName()+"运行中test1"+i);
        }
    }
}
class test2 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(test2.currentThread().getName()+"运行中test2"+i);
            try {
                sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
//结果太长,建议直接看文字描述

线程test1启动
Thread-1运行中test20
Thread-1运行中test21
Thread-1运行中test22
Thread-1运行中test23
Thread-1运行中test24
Thread-1运行中test25
Thread-1运行中test26
Thread-1运行中test27
Thread-1运行中test28
Thread-1运行中test29
线程Test1调用线程2的sleep-------------------------------------
Thread-1运行中test210
Thread-1运行中test211
Thread-1运行中test212
Thread-1运行中test213
Thread-1运行中test214
Thread-1运行中test215
Thread-1运行中test216
Thread-1运行中test217
Thread-1运行中test218
Thread-0运行中test10
Thread-0运行中test11
Thread-0运行中test12
Thread-0运行中test13
Thread-0运行中test14
Thread-0运行中test15
Thread-0运行中test16
Thread-0运行中test17
Thread-0运行中test18
Thread-0运行中test19
Thread-1运行中test219
Thread-1运行中test220
Thread-1运行中test221
Thread-1运行中test222
Thread-1运行中test223
Thread-1运行中test224
Thread-1运行中test225
Thread-1运行中test226
Thread-1运行中test227
Thread-1运行中test228
Thread-1运行中test229
Thread-1运行中test230
Thread-1运行中test231
Thread-1运行中test232
Thread-1运行中test233
Thread-1运行中test234
Thread-1运行中test235
Thread-1运行中test236
Thread-1运行中test237
Thread-1运行中test238
Thread-1运行中test239
Thread-1运行中test240
Thread-1运行中test241
Thread-1运行中test242
Thread-1运行中test243
Thread-1运行中test244
Thread-1运行中test245
Thread-1运行中test246
Thread-1运行中test247
Thread-1运行中test248
Thread-1运行中test249
Thread-1运行中test250
Thread-1运行中test251
Thread-1运行中test252
Thread-1运行中test253
Thread-1运行中test254
Thread-1运行中test255
Thread-1运行中test256
Thread-1运行中test257
Thread-1运行中test258
Thread-1运行中test259
Thread-1运行中test260
Thread-1运行中test261
Thread-1运行中test262
Thread-1运行中test263
Thread-1运行中test264
Thread-1运行中test265
Thread-1运行中test266
Thread-1运行中test267
Thread-1运行中test268
Thread-1运行中test269
Thread-1运行中test270
Thread-1运行中test271
Thread-1运行中test272
Thread-1运行中test273
Thread-1运行中test274
Thread-1运行中test275
Thread-1运行中test276
Thread-1运行中test277
Thread-1运行中test278
Thread-1运行中test279
Thread-1运行中test280
Thread-1运行中test281
Thread-1运行中test282
Thread-1运行中test283
Thread-1运行中test284
Thread-1运行中test285
Thread-1运行中test286
Thread-1运行中test287
Thread-1运行中test288
Thread-1运行中test289
Thread-1运行中test290
Thread-1运行中test291
Thread-1运行中test292
Thread-1运行中test293
Thread-1运行中test294
Thread-1运行中test295
Thread-1运行中test296
Thread-1运行中test297
Thread-1运行中test298
Thread-1运行中test299
Process finished with exit code 0

此处我在main方法中先使线程test1进入了就绪状态,在使线程test2进入了就绪状态,可以看到控制台打印出的语句,线程test1启动,然后便睡眠了一秒,test2线程此时开始执行,在test2线程执行过程中,test1线程执行了test2.sleep(10000),例子在test1线程中让test2线程睡眠了10秒,但是test2线程并没有睡眠并继续执行,反而是test1线程自己睡眠了10秒后才开始执行,然后test1线程执行完了再执行10秒期间test2线程未执行完的部分。

所以由例子得出自己的结论,哪个线程调用的方法对哪个线程起作用,与在哪个线程中哪个对象调用无关,即使使用对象调用了也没意义,所以该方法为静态方法。yield同sleep。

190.线程的 sleep()方法和 yield()方法有什么区别

sleep()yield()
不考虑优先级,会给低优先级线程执行机会只会给相同优先级或高优先级线程执行机会
执行后进入阻塞状态执行后进入就绪状态
声明抛出InterruptedException没有声明异常
sleep具有更好的可移植性(跟操作系统的CPU调度有关)

191.如何停止一个正在运行的线程

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

192. Java 中 interrupted 和 isInterrupted 方法的区别

  • interrupt:用于中断线程。调用该方法的线程的状态将被置为“中断”状态(不会停止线程)。
  • interrupted:是一个静态方法,查看当前中断信号是true还是false并且清除中断信号。如果一个线程被中断了,第一次调用interrupted则返回true,第二次和后面就返回false。
  • isInterrupted:返回当前中断信号是true还是false。
interrupt()isInterruptedstatic interrupted
打断某个线程(设置标志位)查询某个线程是否被打断过(查询标志位)查询当前线程是否被打断过,并重置打断标志

193.什么是阻塞式方法

阻塞式方法是指程序会一直等待该方法完成,期间不做任何事。这里的阻塞是指调用结果返回之前,当前线程会被挂起,直到得到结果之后才会返回。此外,还有异步和非阻塞式方法在任务完成之前就返回。

194.Java 中你怎样唤醒一个阻塞的线程

  • 调用wait方法阻塞本线程,唤醒其他线程;调用notify方法随机唤醒一个线程。
  • wait()、notify()都是针对对象的方法,调用任意对象的wait方法都将导致线程阻塞,阻塞的同时也将释放该对象的锁,相应地,调用任意对象的notify方法将随机接触该对象阻塞的线程,但它需要重新获得该对象的锁,直到获取成功才能往下执行。
  • wait、notify方法必须在同步块或方法中被调用,并且要保证同步块或方法的锁对象与调用wait、notify方法的对象是同一个,如此一来在调用wait之前当前线程就已经获取了某个对象的锁,执行wait后锁被释放。

195.notify() 和 notifyAll() 有什么区别

  • 如果线程调用了对象的wait方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。
  • notifyAll会唤醒所有的线程,notify只会唤醒一个线程。
  • notifyAll调用后,会将全部线程由等待池移到锁池,然后参与锁的竞争,竞争成功则继续执行,失败则留在锁池等待锁被释放后再次参与竞争。notify只会唤醒一个线程,具体唤醒哪一个线程由虚拟机控制。

196. 如何在两个线程间共享数据

在两个线程之间共享变量即可实现共享。(此过程要求变量本身是线程安全的)

197.Java 如何实现多线程之间的通讯和协作

  • 可通过中断,共享变量的方式实现线程间的通讯和协作(如常见的生产者-消费者模型)
  • 常见方式:
    • synchronized加锁的线程的Object类的wait()/notify()/notifyAll()。
    • ReentrantLock类加锁的线程的Condition类的await()/signal()/signalAll()。
  • 线程间直接的数据交换:通过管道进行线程间通信:字节流、字符流。

198.同步方法和同步块,哪个是更好的选择

  • 同步块更好。因为他不会锁住整个对象,同步方法会锁住整个对象,哪怕这个类中有多个不相关的同步块,这通常会导致他们停止执行并等待获取这个对象的锁。
  • 同步块更符合开放调用的原则,同步范围越小越好,只在需要锁住的代码块锁住相应的对象,这样从侧面来说也可以减少死锁发生的概率。

199.什么是线程同步和线程互斥,有哪几种实现方式

  • 线程同步:当一个线程对共享的数据进行操作时,应使之成为一个“原子操作”,即在没有完成相关操作之前,不允许其他线程打断他,否则会破坏数据的完整性,这就是线程的同步。
  • 线程互斥:指对于共享的进程系统资源,在各单个线程访问时的排他性。当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其他要使用的必须等待,直到该资源被释放。线程互斥可以看成是一种特殊的线程同步。
  • 线程间的同步方法可分为用户模式,内核模式两类。
    • 内核模式:利用系统内核对象的单一性来进行同步,使用时需要切换内核态和用户态,内核模式下的方法有事件,信号量,互斥量。
    • 用户模式:不需要切换到内核态,只在用户态完成操作,用户模式下的方法有原子操作(例如一个单一的全局变量),临界区。
  • 同步代码方法:synchronized关键字修饰的方法
  • 同步代码块:synchronized关键字修饰的代码块
  • 使用特殊变量域volatile实现线程同步:volatile关键字为域变量的访问提供了一种免锁机制
  • 使用重入锁实现线程同步:reentranlock类是可重入、互斥、实现类lock接口的锁。它与synchronized方法具有相同的基本行为和语义。

200.在监视器(Monitor)内部,是如何做线程同步的?程序应该做哪种级别的同步

  • 在Java虚拟机中,监视器和锁在Java虚拟机中是一起使用的。监视器监视一块同步代码块,确保一次只有一个线程执行同步代码块。每一个监视器都和一个对象引用相关。线程在获取锁之前不允许执行同步代码。
  • 一旦方法或代码块被synchronized修饰,那么这个部分就放入了监视器的监视区域内,确保一次只能有一个线程执行该部分的代码,线程在获取锁之前不允许执行synchronized修饰的代码。
  • Java提供了显示监视器(Lock)和隐式监视器(synchronized)两种锁方案。

201.如果你提交任务时,线程池队列已满,这时会发生什么

  • 如果是使用的无界队列LinkedBkockingQueue,继续添加到阻塞队列中等待执行,因为LinkedBlockingQueue可以近乎被认为是一个无穷大的队列,可以无限存放任务。
  • 如果使用的是有界队列ArrayBlockingQueue,任务首先会被添加到队列中,队列满了,会根据maximumPoolSize的值增加线程数量,如果增加了还处理不了,队列继续是满的状态,则会使用拒绝策略RejectedExecutionHandler处理满了的任务,默认是AbortPolicy。

202.什么叫线程安全?servlet 是线程安全吗

  • 线程安全是指某个方法在多线程环境中被调用时,能够正确的处理多个线程之间的共享变量,使程序功能正确完成。
  • Servlet不是线程安全的,servlet是单实例多线程的,当多个线程访问同一个方法,是不同保证共享变量的线程安全性的。

203.在 Java 程序中怎么保证多线程的运行安全

  • 使用安全类,如java.util.current下的类
  • 使用自动锁synchronized。
  • 使用手动锁Lock。
Lock lock = new ReentrantLock();
lock.lock();
try{
	System.out.println("获得锁");
}catch(Exception e){
//处理异常
}finally{
	Systen.out.println("释放锁");
	lock.unlock();
}

204.你对线程优先级的理解是什么

每一个线程都是有优先级的,线程优先级是一个int变量(从1-10,1代表最低优先级,10代表最高优先级),java的线程优先级调度会委托给操作系统处理,与具体的操作系统优先级有关,可以通过setPriority()方法设置优先级。

205.线程类的构造方法、静态块是被哪个线程调用的

线程类的构造方法、静态块是被new这个线程类所在的线程所调用的,run方法是被自身所调用的。
例:main线程中创建了A线程,A线程中创建了B线程。

  • A线程的构造方法和静态块是被main线程调用的,A线程的run方法是被A线程调用的。
  • B线程的构造方法和静态块是被A线程调用的,B线程的run方法是被B线程调用的。

206.Java 中怎么获取一份线程 dump 文件?你如何在 Java 中获取线程堆栈

  • dump文件是进程的内存镜像。可以把程序的执行状态通过调试器保存到dump文件中。
  • 在Linux系统中可通过kill -3 pid来获取Java的 dump文件(pid--------->ps -ef | grep java)。
  • Thread 类提供了一个 getStackTrace() 方法也可以用于获取线程堆栈。

207.一个线程运行时发生异常会怎样

如果异常没有被捕捉线程将会被终止。T和read。UncaughtExceptionHandler是用于处理未捕获异常造成线程突然中断情况的一个内嵌接口。当一个未捕获异常将造成线程中断的时候,JVM会使用该接口来查询线程的UncaughtExceptionHandler并将线程和异常作为参数传递给handler和uncaughtException()方法进行处理。

208.Java 线程数过多会造成什么异常

  • 线程的生命周期开销非常高。
  • 消耗过多的CPU。
  • 降低JVM的稳定性。OutOfMemoryError(堆溢出)

209.多线程的常用方法

方法名描述
sleep()强迫一个线程睡眠N毫秒
isAlive()判断一个线程是否存活
join()等待线程终止
activeCount()程序中活跃的线程数
enumerate()枚举程序中的线程
currentThread()得到当前线程
isDaemon()一个线程是否为守护线程
setDaemon设置一个线程为守护线程
setName()为线程设置一个名称
wait()强迫一个线程等待
notify()通知一个线程继续执行(唤醒线程)
setPriority设置一个线程的优先级

210.Java中垃圾回收有什么目的?什么时候进行垃圾回收

  • 垃圾回收是在内存中存在没有引用的对象或超过作用域的对象时进行的。
  • 垃圾回收的目的是识别并丢弃应用不再使用的对象来释放和重用资源。

211.线程之间如何通信及线程之间如何同步

  • 通信:之线程之间交换消息(共享内存,消息传递)
  • Java的并发采用的是共享内存模型,Java线程之间的通信总是隐式进行的,整个通信过程对程序员完全透明。

212.Java内存模型

Java内存模型简称JMM,定义了一个线程对另一个线程可见,共享变量存放在主内存中,每个线程都有自己的本地内存,当多个线程同时访问一个数据时,可能本地内存没有及时刷新到主内存,就会发生线程安全问题。

213.如果对象的引用被置为null,垃圾收集器是否会立即释放对象占用的内存

  • 不会,在下一个垃圾回调周期中,这个对象将是被可收回的。即并不会被垃圾收集器立刻回收,而是在下一次垃圾回收时才会释放其占用的内存。

214.finalize()方法什么时候被调用?析构函数(finalization)的目的是什么

  • 垃圾回收器(gc,garbage colector)决定回收某对象时,就会运行该对象的finalize()方法;一旦垃圾回收器准备释放对象占用的内存,将首先调用该对象的finalize()方法,并且在下一次垃圾回收动作发生时,才真正回收该对象占用的内存空间。
  • finalization的目的是在调用了一些native(C语言写的)方法后,可以在finalization里去调用C的释放函数。

215.什么是重排序

  • 程序执行的顺序按照代码的先后顺序执行。
  • 一般来说,处理器为了提高程序运行效率,可能会对输入代码进行优化,进行重排序。重排序他不保证程序中各个语句的执行先后顺序与原来一致,但会保证最终执行结果相同。重排序对单线程运行不会有不利影响,但是多线程需要另外考虑。

216.重排序实际执行的指令步骤

  1. 源代码。
  2. 编译器优化重排序。编译器不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
  3. 指令级并行的重排序。现代处理器采用了指令级并行技术(ILP)来重叠执行多条指令。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
  4. 内存系统重排序。由于处理器使用缓存和读写缓冲区,使得加载和存储看起来像是乱序执行。
  5. 最终执行的指令序列。
    注:重排序对于单线程没问题,对于多线程可能会发生内存可见性问题。

217.重排序遵守的规则

as-if-serial:

  • 不管怎么排序,结果不能变。
  • 不存在数据依赖的可以被编译器和处理器重排序。
  • 一个操作依赖两个操作,这两个操作如果不存在依赖可以重排序。
  • 单线程根据此规则不会有问题,但是重排序后多线程会出现问题。

218.as-if-serial规则和happens-before规则的区别

as-if-serial予以保证单线程内冲虚的执行结果不被改变,happens-before关系保证正确同步的多线程程序的执行结果不被改变。

219.并发关键字 synchronized

在Java中,synchronized关键在是用来控制线程同步的,就是在多线程环境下,控制synchronized代码段不被多个线程同时执行。synchronized可以修饰类,方法,变量。

220.说说自己是怎么使用 synchronized 关键字,在项目中用到了吗

  • 修饰实例方法:此过程是给当前对象实例加锁,进入同步代码块之前需要该对象实例的锁。
  • 修饰静态方法:给当前类加锁。会作用于所有要调用该类变量方法的对象实例。
  • 修饰代码块:指定加锁对象,对给定对象加锁,计入同步代码块之前要获得该对象的锁。

221.单例模式了解吗?给我解释一下双重检验锁方式实现单例模式

双重校验锁实现对象单例(线程安全)

public class Singleton {
    private volatile static Singleton singleton;
    private Singleton(){}
    
    public static Singleton getSingleton(){
        //先判断对象是否已经实例过,没有才能进入加锁代码
        if(singleton==null){
            //类对象加锁
            synchronized (Singleton.class){
                if (singleton==null){
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

注:使用volatile的目的是禁止JVM的指令重排,保证在多线程环境下能正常运行。

222.说一下 synchronized 底层实现原理

  • synchronized的底层是通过一个monitor(监视器锁)的对象来完成的。
  • 每个对象有一个监视器锁(monitor)。每个Synchronized修饰过的代码当它的monitor被占用是就会处于锁定状态并且尝试获取monitor的所有权。过程如下:如果monitor的进入数为0,怎该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者;如果线程已经占有该monitor,只是重新进入monitor,则monitor的进入数加1;如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。

223.synchronized可重入的原理

重入锁是指一个线程获取到该锁之后,该线程可以继续获得该锁。底层原理是维护一个计数器,当线程获取该锁时,计数器加1,在此获得该锁时再加1,释放锁时,计数器减1,当计数器值为0时,表明该锁未被任何线程持有,其他线程可以竞争获取锁。

224.什么是自旋

  • 很多synchronized里面的代码只是一些很简单的代码,执行时间非常快,此时等待的线程都加锁是一种不太值得的操作,因为线程阻塞涉及到用户态和内核态切换的问题。既然synchronized里面的代码执行得很快,不妨让等待锁的线程不要被阻塞,而是在synchronized边界做忙循环,这就是自旋。如果做了多次循环发现还没有获得锁,再阻塞,这样可能是一种更好的策略。
  • 忙循环:就是程序员用循环让一个线程等待。他不会像wait,sleep,yield一样放弃CPU控制,他会保留CPU缓存,执行一个空循环,这样可以减少等待重建缓存的时间。

225.多线程中 synchronized 锁升级的原理是什么

  • synchronized锁升级原理:在锁对象的对象头里有一个threadid字段,在第一次访问的时候为空,jvm让其持有偏向锁,并将threadid设置为其线程id,再次进入的时候会判断两id是否一致,如果一直则可以直接使用此对象,不一致则升级偏向锁为轻量级锁,通过自旋循环来获取锁,执行一定次数后,如果还没有正常获取到使用的对象,此时就会把轻量级锁升级为重量级锁,这个过程就是synchronized锁升级的过程。
  • 偏向锁会偏向第一个访问它的线程,如果在运行过程中,遇到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM会消除它身上的偏向锁,将锁恢复到标准的轻量级锁;偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入到锁的争用时,轻量级锁就会升级为重量级锁。

226.线程 B 怎么知道线程 A 修改了变量

即实现共享变量的可见性。一种是线程 b 不断轮循共享变量,看一下是否变量有被修改,当然该变量要加上 volatile 关键字 当然还可以利用 wait(),notify()方法,即生产者、消费者模式,让生产者线程改变变量之后,主动通知、激活消费者线程,反之亦然。
总结:

  • 用volatile修饰变量。
  • synchronized修饰修改变量的方法。
  • wait/notify(生产者消费者模式)。
  • while轮询。

227.当一个线程进入一个对象的 synchronized 方法 A 之后,其它线程是否可进入此对象的 synchronized 方法 B

不能。其他线程只能访问该对象的非同步方法。因为非静态方法上的同步锁是对象锁,如果已经进入了A方法说明锁已被获得,B方法此时要获得这个锁只能等待。

228.synchronized、volatile、CAS 比较

  • synchronized是悲观锁,属于抢占式,会引起其他线程阻塞。
  • volatile提供多线程共享变量可见性和禁止指令重排序优化。
  • CAS是基于冲突检测的乐观锁(非阻塞)。

229.synchronized 和 Lock 有什么区别

synchronizedLock
是Java内置关键字是个Java类
可以给类,方法,代码块加锁只能给代码块加锁
自动获取和释放锁,不会造成死锁需要手动加锁,释放锁,如果忘记unLock()去释放锁就会造成死锁
synchronized不能知道有没有成功获得锁Lock可以知道是否成功获得锁,如tryLock()方法,获取锁返回true,反之返回false

230.synchronized 和 ReentrantLock 区别是什么

两者都是可重入锁(如果不是可重入锁,则该线程若再需获得该对象的锁时,则会造成死锁)。Java中每个对象都可以作为锁,这是synchronized实现同步的基础:普通同步方法锁是当前实例对象;静态同步方法,锁是当前类的class对象;同步静态块,锁是括号里面的对象。

synchronizedReentrantLock
自动获取/释放锁手动获取/释放锁
可以修饰类,方法,变量等只能修饰代码块
锁机制为对象头中的mark wordUnsafez的park方法加锁

231.volatile 关键字的作用

  • volatile关键字用来保证可见性和禁止指令重排。volatile提供happens-before的规则,确保一个线程的修改能对其他线程是可见的。当一个共享变量被volatile修饰时,他会保证修改的值被立刻更新到主内存中,当有其他线程需要读取时,它会去主内存中读取新值。
  • volatile和CAS结合,保证了原子性。
  • volatile常用于多线程环境下的单次操作。

232.Java 中能创建 volatile 数组吗

能。但只能创建一个指向数组的引用,如果具体到数组里面的元素操作则不能起到保护作用。

233.volatile 变量和 atomic 变量有什么不同

volatile不能保证原子性,atomic可以。

234.volatile 能使得一个非原子操作变成原子操作吗

不能。volatile只能保证单个变量赋值的原子性。

235.synchronized 和 volatile 的区别是什么

  • synchronized表示只有一个线程可以获取作用于对象的锁,执行时阻塞其他线程。
  • volatile表示变量在CPU寄存器中是不确定的,必须从主存中读取。保证多线程环境下变量的可见性;禁止指令重排序。
volatilesynchronized
变量修饰符可修饰类,方法,变量
能实现变量修改的可见性,不能保证原子性修改可见性和原子性都可保证
不会造成线程阻塞可能会造成线程阻塞
修饰的变量禁止指令重排序,不会被编译器优化修饰的变量可以被编译器优化
volatile关键字是线程同步的轻量级实现,性能好,但作用范围小,所以使用率比synchronized低

236.final不可变对象,它对写并发应用有什么帮助

不可变对象(不可变类)即所有域都是final类型的一旦被创建状态,属性就不能改变,如String类,基本数据类型的包装类都是不可变类。不可变对象保证了对象内存可见性,对不可变对象的读取不需要其他的同步手段,提升了代码执行效率。

237.Lock 接口和synchronized 对比同步它有什么优势

  • 使锁更公平。
  • 使线程在等待锁时响应中断。
  • 可以让线程尝试获取锁,并在无法获取锁时立即返回或者等待一段时间(tryLock(可带参))。
  • 可以在不同的范围以不同的顺序获取和释放锁。

238.乐观锁和悲观锁的理解及如何实现,有哪些实现方式

  • 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据时都会上锁,这样别人拿这个数据就会被阻塞。传统的关系型数据库里面的行锁,表锁,读锁,写锁等,都是在操作之前先加锁,Java里面的synchronized关键字也是悲观锁。
  • 乐观锁:每次去拿数据时总认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下此期间别人有没有去更新这个数据,可以使用版本号机制,乐观锁适用于大量的读操作,这样可以提高吞吐量,向数据库的write_condition机制就是乐观锁。

239.什么是 CAS

  • CAS是compare and swap的缩写,即比较交换。
  • cas是一种基于锁的操作,是乐观锁。
  • cas操作包括三个操作数(内存位置V,预期原值A和新值B)。如果内存里面的值和A是一样的,那么将内存里面的值更新为B。CAS是通过无限循环来获取数据的,如果在第一轮循环中,a线程获取地址里面的值被b线程修改了,那么a线程需要自旋,到下次循环时才有可能有机会执行。(java.util.concurrent.atomic包下的类大多是使用CAS操作实现的)

240.CAS 的会产生什么问题

  • ABA问题:
  • 循环时间长开销大:
  • 只能保证一个共享变量的原子操作:

241.什么是原子类

java.util.concurrent.atomic包

242.原子类的常用类

  • AtomicBoolean
  • AtomicInteger
  • AtomicLong
  • AtomicReference

243. 说一下 Atomic的原理

Atomic包中的类基本的特性就是在多线程环境下,当有多个线程同时对单个(包括基本类型及引用类型)变量进行操作时,具有排他性,即当多个线程同时对该变量的值进行更新时,仅有一个线程能成功 额,而未成功的线程可像自旋锁一样,循环到能执行成功为止。

244.死锁与活锁的区别,死锁与饥饿的区别

  • 死锁:指两个或以上的线程在执行过程中,因争夺资源而造成一种互相等待的现象,若无外力作用,他们都将无法推进下去。
  • 活锁:任务或执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试,处于活锁的实体处于不断改变的状态,有可能自行解开。
  • 饥饿:一个或多个线程因为种种原因无法获得所需要的资源,导致一直无法执行的状体。
    Java中导致饥饿发生的原因:
  • 高优先级的线程吞噬所有的低优先级线程的CPU时间片。
  • 线程被永久的堵塞在一个等待入同步块的状态,因为其他线程总是能在他之前持续的对该同步块进行访问。
  • 线程在等待一个本身处于永久等待的对象(比如调用这个对象的wait方法),因为其他线程总是被持续的唤醒。

线程池

245.什么是线程池

线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。

246.线程池作用

  • 线程池是为突然大量爆发的线程设计的,通过有限的几个固定线程为大量的操作服务,减少了创建和销毁线程所需的时间,从而提高效率。
  • 如果一个线程所执行的时间非常长,那建议不配合线程池使用。因为程序员不能控制线程池中线程的开始,挂起和终止。

247.线程池有什么优点

  • 降低资源消耗。重用存在的线程,减少对象创建销毁的开销。
  • 提高响应速度。可有效地控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。当任务到达时,任务可以不需要等到线程创建就能立即执行。
  • 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
  • 提供定时执行、定期执行、单线程、并发数控制等功能。

248.什么是ThreadPoolExecutor

就是线程池,它是Java的一个类,一般通过Executors工厂类的方法,通过传入不同的参数,就可以构造出适用于不同场景的线程池。

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
    }

corePoolSize 核心线程数量
maximumPoolSize 最大线程数量
keepAliveTime 线程保持时间,N个时间单位
unit 时间单位(秒,分)
workQueue 阻塞队列
threadFactory 线程工厂
handler 线程池拒绝策略

249.什么是Executors

  • Executors框架实现的就是线程池的功能。
    Executors工厂类中的newCachedThreadPool、newFixedThreadPool、newScheduledThreadExecutor、newSinglrThreadExecutor等方法其实也只是ThreadPoolExecutor的构造函数参数不同。通过传入不同的参数,构造出不同应用场景下的线程池。
//源码里面基本上都是通过传入不同的参数返回一个新的线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }


public static ExecutorService newWorkStealingPool(int parallelism) {
        return new ForkJoinPool
            (parallelism,
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }

public static ExecutorService newWorkStealingPool() {
        return new ForkJoinPool
            (Runtime.getRuntime().availableProcessors(),
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }

public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>(),
                                      threadFactory);
    }
    ......

250.线程池四种创建方式

  • newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则创建新线程。
  • newFixedThreadPool创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
  • newScheduledThreadPool创建一个定长线程池,支持定时及周期性任务执行的。
  • newSingleThreadExecutor创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务保证所有任务按照指定顺序(FIFO,LIFO,优先级)执行。

251.在 Java 中 Executor 和 Executors 的区别

  • Executors工具类的不同方法按照我们的需求创建了不同的线程池,来满足业务的需求。
  • Executor接口对象能执行我们的线程任务。
  • ExecutorService接口继承了Executor接口并进行了扩展,提供了更多的方法我们能获得任务执行的状态并且可以获取任务的返回值。
  • 使用ThreadPoolExecutor可以创建自定义线程池。

252.四种构建线程池的区别及特点

  • newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则创建新线程,不会对线程池的长度有任何限制。缺点是它的最大值在初始化是时设置为了Integer.MAX_VALUE,一般来说机器没有这么大的内存给他不断使用,容易造成内存溢出。
 public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 1; i <=1000 ; i++) {
            final int temp = i;
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"i的值是:"+temp);
                }
            });
        }
    }
  • newFixedThreadPool创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。缺点是线程数量是固定的,但阻塞队列是无界队列,有可能造成任务积压导致OOM(超出内存空间)使用前需查询电脑cpu核心数量(Runtime.getRuntime().availableProcessors()),给定合适的线程池大小。
public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(8);
        for (int i = 1; i <=20 ; i++) {
            final int temp = i;
            executorService.execute(new Runnable() {

                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"------"+temp);
                }
            });
        }
    }

始终只8个线程

  • newScheduledThreadPool创建一个定长线程池,支持定时及周期性任务。缺点是由于所有任务都由同一个线程来调度,因此所有任务是串行执行的,前一个任务因某些因素影响延时或阻塞会影响后面的任务。(可设置初始线程池大小)
public static void main(String[] args) {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
        for (int i = 1; i <=10; i++) {
            final int temp = i;
            scheduledExecutorService.execute(new Runnable() {
                @Override
                public void run() {
                    if (temp==3){
                        try {
                            Thread.sleep(5000);
                            System.out.println("我是第三次循环,睡眠了5秒");
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println(Thread.currentThread().getName()+"-----i="+temp);
                }
            });
        }
    }

在这里插入图片描述

  • new SingleThreadExecutor创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO,LIFO,优先级)执行。缺点是它是单线程的,高并发业务下很无力。它可以保证所有任务都是按照顺序执行的,如果这唯一一个线程因为异常结束,那么会有一个新的线程替代它。
public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        for (int i = 1; i <=10 ; i++) {
            final int temp = i;
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"-------i="+temp);
                }
            });
        }
    }

只有一个线程,所有任务排队执行,效率低但稳定

253.线程池都有哪些状态

  • RUNNING:正常状态,接受新的任务,处理等待队列中的任务。
  • SHUTDOWN:不接受新的任务提交,但是会继续处理等待队列中的任务。
  • STOP:不接受新的任务提交,不在处理等待队列中的任务,中断正在执行任务的线程。
  • TIDYING:所有的任务都销毁了,workCount为0,线程池的状态在转换为TIDYING状态时,会执行钩子方法terminated()。
  • TERMINATED:terminated()方法结束后,线程的状态就变成这个。

254.线程池中 submit() 和 execute() 方法有什么区别

  • 相同点:都可以开启线程执行池中的任务。
  • 不同点:
    • 接收参数:executor()只能执行Runnable类型的任务。submit()可以执行Runnable和Callable类型的任务。
    • 返回值:submit()方法可以返回持有计算结果的Future对象,而execute()没有。
    • 异常处理:submit()方便Exception处理。

255.什么是线程组,为什么在 Java 中不推荐使用

ThreadGroup类,可以把线程归属到一个线程组中,线程组中可以有线程对象,线程组,组中还可以有线程;线程组是为了方便线程的管理,线程池是为了管理线程的生命周期,复用线程,减少创建和销毁线程的开销。因为线程组存在很多隐患,所以推荐使用线程池。

256. ThreadPoolExecutor饱和策略有哪些

当线程池满了时,有一些应对策略。

  • AbortPolicy:抛出RejectedExecutionException来拒绝新任务的处理。
  • CallerRunsPolicy:调用执行自己的线程运行任务。这种策略会降低对新任务提交速度,影响程序的整体性能,这个策略还喜欢增加队列容量。如果应用程序可以承受这个延迟且不能丢弃任何一个任务请求时可选择这个测i略。
  • DiscardPolicy:不处理新任务,直接丢弃掉。
  • DiscardOldestPolicy:此策略将丢弃最早的未处理的任务请求。

257.如何自定义线程线程池

根据线程池构造函数传相应的参数

 public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,4,60L, TimeUnit.SECONDS,new ArrayBlockingQueue<>(6));
        for (int i = 1; i <=20; i++) {
            final int temp = i;

            threadPoolExecutor.execute(new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"-----i=="+temp);
                }
            }));//执行线程
            if (temp==5){
                threadPoolExecutor.shutdown();//不接受新的任务提交,但是会继续处理等待队列中的已提交的任务,阻塞中的任务不会再执行
            }
        }
    }

执行了第一个任务后因为shutdownl,所以不再执行

258.线程池的执行原理

提交一个任务到线程池中,先判断线程池中的核心线程是否都在执行任务,如果不是,如果不是,则使用一个线程来执行任务,如果是,则判断线程池工作队列是否已满,如果工作队列没满,则将新任务添加到队列里,如果满了,在判断线程池里的线程是否都处于工作状态,如果没有,则创建一个新的线程来执行任务,如果线程池满了,则交给饱和策略
来处理这个任务。

259.如何合理分配线程池大小

CPU密集io密集
该任务需要大量的运算,而没有阻塞,CPU会一直运行该任务需要大量的IO(阻塞)
少配置线程数,大概与cpu核数相当,这样可以使每条线程都在执行任务大部分任务都再阻塞,需要多配置线程数,2*cpu核数

结论:

  • 线程等待时间与CPU执行时间比例越高,需要越多线程。

并发容器

260.你经常使用什么并发容器,为什么

  • Vector、HashTable、CurrentHashMap。
  • 在多线程中使用的容器必须是加锁的,否则存储的数据会非常混乱。

261.什么是Vector

  • Vector是一个通过数组实现的只能存储对象的类似于ArrayList的容器,它支持线程的同步,即某一时刻只能有一个线程操作他,但是它实现同步的花费较高,所以效率较低。

262.ArrayList和Vector有什么不同之处

Vector是线程安全的,与ArrayList的区别就是源码的所有方法上都用了synchronized关键字修饰。

263.为什么HashTable是线程安全的

内部方法都加了synchronized修饰。

264.用过ConcurrentHashMap,讲一下他和HashTable的不同之处

锁的粒度不同,具体的请参考前面集合部份。

265.Collections.synchronized * 是什么

是一个将非线程安全的容器变成线程安全容器的一个辅助类。
在这里插入图片描述

266.Java 中 ConcurrentHashMap 的并发度是什么

ConcurrentHahMap实际上是把Map划分为多个部分来实现线程安全,并发度是ConcurrentHashMap类构造函数的一个可选参数,默认值是16,这样在多线程情况下才能避免争用。

267.什么是并发容器的实现

  1. 什么是同步容器:通过synchronized来实现同步的容器,如果有多个线程调用同步容器的方法,则会串行执行,如Vector,HashTable,Collections.synchronizedSet(List)等。
  2. 并发容器使用了与同步容器完全不同的加锁策略来提高并发性和伸缩性,例如ConcurrentHashMap中就使用了一种粒度更细的锁(分段锁),这种锁机制可以允许任意数量的读线程并发的访问容器,并且执行读和写操作的线程可以并发访问容器,且允许一定量的写线程同时操作容器,在并发环境下,并发容器可实现更高的吞吐量。

268.Java 中的同步集合与并发集合有什么区别

同步集合与并发集合都为多线程和并发提供了合适的线程安全的集合,但并发集合的可拓展性更高。同步集合就是类似于HashTable、Vector之类的只能串行执行任务的集合,并发集合就是类似ConcurrentHashMap的可多个线程同时操作的的集合。

269.SynchronizedMap 和 ConcurrentHashMap 有什么区别

  • SynchronizedMap一次所锁住一整张表来保证线程安全,所以每次只能有一个线程访问。
  • ConcurrentHashMap实行的是分段锁来保证多线程的性能,它是一次锁住一个数组位,这样就能有多个线程同时进行写操作,位与位的链表(红黑树之间)写操作互不影响。
  • ConcurrentHashMap使用了一种新的迭代方式,在迭代过程中执行了写操作不再ConcurrentModificationException,而是在改变时new新的数据而不影响原来的数据,当遍历完后在将头指针替换为新的数据,这样迭代器可以正常遍历原集合,写操作也可以并发的完成改变。

270.CopyOnWriteArrayList 是什么

  • 它是一个并发容器,非复合场景下操作他是线程安全的
  • 免锁容器的好处之一就是当多个迭代器同时遍历和修改这个列表时,不会抛出ConcurrentModificationException。在CopyOnWriteArrayList中,写入操作将导致创建整个底层数组的副本,而原数组将会保留在原地,使得复制的数组在被修改时,读取操作可以安全的执行。

271.CopyOnWriteArrayList 的使用场景

适合读多写少的场景。

272.CopyOnWriteArrayList 的缺点

  • 写操作时,需要拷贝数组,会消耗内存,如果原数组的内容比较多的情况下,可能会导致yong gc或full gc。
    什么是young gc和full gc------🤚
  • 不能用于实时读的场景。有可能读取到旧数据。
  • 在写操作次数比较多时,每次add/set都要重新复制数组,代价比较高昂。

273.CopyOnWriteArrayList 的设计思想

  • 读写分离,读和写分开。
  • 最终一致性。
  • 使用另外开辟空间的思路来解决并发冲突。

并发队列

274.什么是并发队列

  • 消息队列时分布式系统中的重要组件,是系统与系统直接的通信。
  • 并发队列是使多个线程有次序共享数据的重要组件。

275.并发队列和并发集合的区别

  • 队列遵循先进先出的规则,一般用来解决大数据量采集处理和显示的。
  • 并发集合就是在多个线程中共享数据的。

276.怎么判断并发队列是阻塞队列还是非阻塞队列

在并发队列上JDK提供了Queue接口,一个是以Queue接口下的BlockingQueue接口为代表的阻塞队列,另一个是高性能(无堵塞)队列。

277.阻塞队列和非阻塞队列区别

  • 这里的非阻塞与阻塞在于有界与否,也就是在初始化时有没有给它一个默认的容量大小。
  • 当阻塞队列为空时从列表中获取元素的操作将会被阻塞,直到其他线程往空的队列中插入新的元素。
  • 当阻塞队列是满时,往队列里添加元素的操作会被阻塞,直到其他线程使队列重新变得空闲。

278.常用并发列队列的介绍

非阻塞队列:

  • ArrayDeque(数组双端队列):是JDK容器中的一个双端队列实现,内部使用数组进行元素存储,不允许存储null值,可以高效地进行元素查找和尾部插入取出,是用作队列,双端队列,栈的绝佳选择,性能比LinkedList好。
  • PriortyQueue(优先级队列):一个基于优先级的无界优先级队列,优先级队列按照元素的自然顺序进行排序或根据构造时提供的Comparetor进行排序,具体取决于所使用的构造方法。该队列不允许null元素,也不允许插入任何不能比较的对象。
  • ConcurrentLinkedQueue(基于链表的并发队列):是一个适用于高并发的队列,通过无锁的方式,实现了高并发状态下的高性能。它是一个基于链结点的无界线程安全队列。该队列元素遵循先进先出的原则,不允许null元素。

阻塞队列:

  • DelayQueue(基于时间优先级的队列,延期阻塞队列):是一个无界的BlockingQueue的实现,加入其中的元素必须实现Delayed接口。该队列中的元素是按到期时间排序的,到期时间越早越靠前,不是按照进入队列的顺序。
  • ArrayBlockingQueue(基于数组的并发阻塞队列):是一个有边界的阻塞队列,它的内部是一个数组,该队列必须在初始化时指定它的大小,且大小经设定后无法改变,该队列遵循先进先出规则。
  • LinkedBlockingQueue(基于链表的FIFO阻塞队列):该阻塞队列的大小是可选的,如果初始化时指定大小,它是有边界的,如果没指定,它是无界的,它的内部实现是一个链表。
  • LinkedBlochingDeque(基于链表的FIFO双端阻塞队列):由一个链表结构组成的双向队列,即可以从队列的两端插入和移除元素。双向队列因为多了一个操作队列的入口,在多线程进入是就少了一半的竞争。该队列容量是可选的,设置初始是有界,未设置是无界,和其他队列相比,该队列多了addFirst,addLast,peekFirst,peekLast等方法,以first结尾的表示插入、获取或移除双端队列的第一个元素;以last结尾的表示插入、获取或移除双端队列的最后一个元素。
  • PriorityBlockingQueue(带优先级的无界阻塞队列):是一个无界队列,在内存允许的情况下可以无限添加元素;同时他又是一个通过构造函数传入的对象(必须实现comparable接口)来判断的具有优先级的队列。
  • SynchronousQueue(并发同步阻塞队列):是一个内部只能包含一个元素的队列。简单来说就是一个线程放元素进去必须等另一个线程拿出后才可以继续放,不然会阻塞;线程拿元素也是同样,只有里面有元素才能拿,不然会阻塞到有元素且拿到时。

279.并发队列的常用方法

方法名描述
add()在不超出队列长度的情况下插入元素,可以立即执行,成功返回true,如果队列满了就抛出异常
offer()在不超出队列长度的情况下插入元素的时候则可以立即在队列尾部插入指定元素,成功返回true,如果队列已满,则返回false
put()插入元素时,如果队列满了就等待,知道队列可用
take()从队列中获取值,如果队列中没有值,线程会一直阻塞,直到队列中有值并且该方法取得了该值
poll(long timeout,TimeUnit unit)在给定的时间内从队列中获取值,如果超出时间没取到则抛出异常
remainingCapacity()获取队列中剩余的空间
remove(Object o)从队列中移除指定的值
contains(Object o)判断队列中是否拥有该值
drainTo(Collection c)将队列中的值全部移除并发设置到给定的集合
getFirst()/getLast()获取第一个或最后一个元素,如果没有抛异常
还有size,isEmpty,iterator,toArray,clear,clone,push,pop等辅助操作方法。

并发工具类

280.常用的并发工具类有哪些

作用说明
Semaphore信号量,可以通过控制“许可证”的数量来保证线程之间的配合(允许自定义多少线程同时访问)线程只有拿到许可证后才能继续运行
CyclicBarrier让所有线程都等待完成后才会继续下一步行动。即线程会等待,知道足够多线程达到事先规定的数目,一旦达到条件,就可以进行下一步动作适用于线程之间相互等待处理结果就绪的场景
Phaser和CyclicBarrier 类似,但是计数可改变java7之后才有
CountDownLatch实现类似计数器的功能,数量递减到0时,触发动作(比如说某个任务要等其他三个任务执行完了才能执行)不可重复使用
Exchanger让两个线程在合适的时候交换对象使用场景:当两个线程工作在同一个类的不同实例上时,用于交换数据
Condition可以控制线程的等待和唤醒是Object类中wait,notify的升级版

网络编程

281.网络编程中两个主要的问题

  • 网络编程的本质是多台计算机之间的数据交换。
  • 一个是如何准确定位网络上的一台或多台主机。
  • 另一个是找到主机后如何可靠高效的进行数据传输。

282.网络协议是什么

在计算机网络中要做到井井有条的交换数据,就必须遵循一些事先约定好的规则,比如说数据交换的格式、是否需要发送一个应答消息。这些规则被称为网络协议。

283.为什么要对网络协议分层

  • 简化问题难度和复杂度。由于各层之间独立,可以分割大问题为小问题。
  • 灵活性好。当其中一层的技术变化时,只要接口关系保持不变,其他层不受影响。
  • 易于实现和维护。
  • 促进标准化工作。分开后,每层功能可以相对简单的被描述。

284.计算机网络体系结构

OSI七层模型功能对应的网络协议
应用层文件传输,文件管理,电子邮件的信息处理,最小单位–apduHTTP,TFTP,FTP,NFS,WAIS,SMTP
表示层确保一个系统的应用层发送的消息可以被另一个系统的应用层读取,编码转换,数据解析,管理数据的解密和加密,最小单位–ppduTelnet,Rlogin,SNMP,Gopher
会话层负责在网络中的两点建立,维持和终止通信,在会话层中,可以解决节点连接的协调和管理问题。包括通信连接的建立,保持会话过程中通信连接的畅通,最小单位–spduSMTP,DNS
传输层定义一些传输数据的协议和端口。传输协议同时进行流量控制,或是根据接收方接受数据的快慢程度,规定适当的发送速率,解决传输效率及能力的问题,最小单位–tpduTCP,UDP
网络层控制子网的运行,如逻辑编址,分组传输,路由选择,最小单位–分组报文IP,ICMP,ARP,RARP,AKP,UUCP
数据链路层主要是对物理层传输的比特流包装,检测保证数据传输的可靠性,将物理层接受的数据进行MAC(媒体访问控制)地址的封装和解封装,也可以简单理解为物理寻址。交换机就处于这一层,最小的传输单位–帧FDDI,Ethernet,Arpanet,PDN,SLIP,PPP,STP
物理层定义物理设备的标准,主要对物理连接方式,电气特性,机械特性等制定统一标准,传输比特流,最小传输单位–比特流IEEE

TCP/IP参考模型
在此模型中,应用层表示层会话层合为了应用层,数据链路层物理层合为了数据链路层。

  1. 应用层 应用层最靠近用户的一层,是为计算机用户提供应用接口,也为用户直接提供各种网络服务。我们常见应用层的网络服务协议有:HTTP,HTTPS,FTP,TELNET等。
  2. 传输层 建立了主机端到端的链接,传输层的作用是为上层协议提供端到端的可靠和透明的数据传输服务,包括处理差错控制和流量控制等问题。该层向高层屏蔽了下层数据通信的细节,使高层用户看到的只是在两个传输实体间的一条主机到主机的、可由用户控制和设定的、可靠的数据通路。我们通常说的,TCP UDP就是在这一层。端口号既是这里的“端”。
  3. 网络层 本层通过IP寻址来建立两个节点之间的连接,为源端的运输层送来的分组,选择合适的路由和交换节点,正确无误地按照地址传送给目的端的运输层。就是通常说的IP层。这一层就是我们经常说的IP协议层。IP协议是Internet的基础。
  4. 数据链路层 通过一些规程或协议来控制这些数据的传输,以保证被传输数据的正确性。

TCP/UDP

285.什么是TCP/IP和UDP

  • TCP/IP即传输控制/网络协议,是面向连接的协议,发送数据前要先建立连接(发送方和接收方的成对的两个之间必须建立连接),TCP提供可靠的服务,也就是说,通过TCP连接传输的数据不会丢失,没有重复,并且按顺序到达。
  • UDP它是属于TCP/IP协议族中的一种。是无连接的协议,发送数据前不需要建立连接,是没有可靠性的协议。因为不需要建立连接所以可以在网络上已任何可能的路径传输,因此能否到达目的地,到达目的地的时间及内容的正确性都是不能被保证的。

286.TCP与UDP区别

  • TCP是面向连接的协议,发送数据前要先建立连接,TCP提供可靠的服务,通过TCP连接传输的数据不会丢失,没有重复,并且按照顺序到达。
  • UDP是无连接的协议,发送数据前不需要建立连接,是没有可靠性。
  • TCP通信类似于要打个电话,接通了,确认身份,才开始进行通信。
  • UDP通信类似于广播,靠着广播播报直接进行通信。
  • TCP只支持点对点通信,UDP支持一对一、一对多、多对一、多对多。
  • TCP是面向字节流的,UDP是面向报文的;面向字节流是指发送数据时以字节为单位,一个数据包可以拆分成若干组进行发送,而UDP一个报文只能一次发完。
  • TCP首部开销(20字节)比UDP首部开销(8字节)要大。
  • UDP的主机不需要维持复杂的连接状态表。

287.TCP和UDP的应用场景

对某些实时性要求比较高的情况使用UDP,如游戏,媒体通信,实时直播,即使出现传输错误也可以容忍;其他大部分情况下,HTTP都是用TCP,因为要求传输的内容可靠,不出现丢失的情况。

288.运行在TCP 或UDP的应用层协议分析

运行在TCP协议上的协议:
HTTP(Hypertext Transfer Protocol,超文本传输协议),主要用于普通浏览。
HTTPS(HTTP over SSL,安全超文本传输协议),HTTP协议的安全版本。
FTP(File Transfer Protocol,文件传输协议),用于文件传输。
POP3(Post Office Protocol, version 3,邮局协议),收邮件用。
SMTP(Simple Mail Transfer Protocol,简单邮件传输协议),用来发送电子邮件。
TELNET(Teletype over the Network,网络电传),通过一个终端(terminal)登陆到网络。
SSH(Secure Shell,用于替代安全性差的TELNET),用于加密安全登陆用。
运行在UDP协议上的协议:
BOOTP(Boot Protocol,启动协议),应用于无盘设备。
NTP(Network Time Protocol,网络时间协议),用于网络同步。
DHCP(Dynamic Host Configuration Protocol,动态主机配置协议),动态配置IP地址。
运行在TCP和UDP协议上:
DNS(Domain Name Service,域名服务),用于完成地址查找,邮件转发等工作。
ECHO(Echo Protocol,回绕协议),用于查错及测量应答时间(运行在TCP和UDP协议上)。
SNMP(Simple Network Management Protocol,简单网络管理协议),用于网络信息的收集和网络管理。
DHCP(Dynamic Host Configuration Protocol,动态主机配置协议),动态配置IP地址。
ARP(Address Resolution Protocol,地址解析协议),用于动态解析以太网硬件的地址。

289.什么是ARP协议

ARP协议完成了IP地址与物理地址的映射。

290.什么是NAT (Network Address Translation, 网络地址转换)

用于解决内网中的主机要和因特网中的主机通信。由NAT路由器将主机地址的本地IP地址转换为全球IP地址,分为静态转换(转换得到的全球IP地址固定不变)和动态NAT转换。

291.从输入网址到获得页面的过程

应用层进行DNS解析,生成HTTP请求报文,传输层建立TCP连接,传输数据,浏览器接受并渲染。

  1. 浏览器查询 DNS,获取域名对应的IP地址:具体过程包括浏览器搜索自身的DNS缓存、搜索操作系统的DNS缓存、读取本地的Host文件和向本地DNS服务器进行查询等。对于向本地DNS服务器进行查询,如果要查询的域名包含在本地配置区域资源中,则返回解析结果给客户机,完成域名解析(此解析具有权威性);如果要查询的域名不由本地DNS服务器区域解析,但该服务器已缓存了此网址映射关系,则调用这个IP地址映射,完成域名解析(此解析不具有权威性)。如果本地域名服务器并未缓存该网址映射关系,那么将根据其设置发起递归查询或者迭代查询;
  2. 浏览器获得域名对应的IP地址以后,浏览器向服务器请求建立链接,发起三次握手;
  3. TCP/IP链接建立起来后,浏览器向服务器发送HTTP请求;
  4. 服务器接收到这个请求,并根据路径参数映射到特定的请求处理器进行处理,并将处理结果及相应的视图返回给浏览器;
  5. 浏览器解析并渲染视图,若遇到对js文件、css文件及图片等静态资源的引用,则重复上述步骤并向服务器请求这些资源;
  6. 浏览器根据其请求到的资源、数据渲染页面,最终向用户呈现一个完整的页面。

292. TCP的三次握手

  • 在网络数据传输中,传输协议TCP是建立连接的可靠传输,TCP建立连接的过程被称为三次握手。
    主机A通过向主机B 发送一个含有同步序列号标志位的数据段(SYN)给主机B ,向主机B 请求建立连接,通过这个数据段,主机A告诉主机B 两件事:我想要和你通信;你可以用哪个序列号作为起始数据段来回应我。

主机B 收到主机A的请求后,用一个带有确认应答(ACK)和同步序列号(SYN)标志位的数据段响应主机A,也告诉主机A两件事:我已经收到你的请求了,你可以传输数据了;你要用哪个序列号作为起始数据段来回应我。

主机A收到这个数据段后,再发送一个确认应答,确认已收到主机B 的数据段:“我已收到回复,我现在要开始传输实际数据了”。

这样3次握手就完成了,主机A和主机B 就可以传输数据了。

293.TCP的四次挥手

当主机A完成数据传输后,将控制位FIN置1,提出停止TCP连接的请求。

主机B收到FIN后对其作出响应,确认这一方向上的TCP连接将关闭,将ACK置1。

由B 端再提出反方向的关闭请求,将FIN置1。

主机A对主机B的请求进行确认,将ACK置1,双方向的关闭结束。

Socket

294.什么是Socket

  • 网络上的两个程序通过一个双向的通讯连接实现数据的交换,这个双向链路的一端称为一个Socket。Socket通常用来实现客户方和服务方的连接。Socket是TCP/IP协议的一个十分流行的编程界面,一个Socket由一个IP地址和一个端口号唯一确定。
  • Socket所支持的协议种类也不光TCP/IP、UDP,因此两者之间是没有必然联系的。在Java环境下,Socket编程主要是指基于TCP/IP协议的网络编程。
  • socket连接就是所谓的长连接,客户端和服务器需要互相连接,理论上客户端和服务器端一旦建立起连接将不会主动断掉的,但是有时候网络波动还是有可能的Socket偏向于底层。一般很少直接使用Socket来编程,框架底层使用Socket比较多。

295.socket属于网络的那个层面

Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个外观模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

296.Socket通讯的过程

基于TCP:服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。
基于UDP:UDP 协议是用户数据报协议的简称,也用于网络数据的传输。虽然 UDP 协议是一种不太可靠的协议,但有时在需要较快地接收数据并且可以忍受较小错误的情况下,UDP 就会表现出更大的优势。我客户端只需要发送,服务端能不能接收的到我不管。

297.TCP协议Socket代码示例

298.UDP协议Socket代码示例

299.Socket的常用类

在这里插入图片描述

Http

300.什么是Http协议

Http协议是对客户端和服务器端之间数据之间实现可靠性的传输文字、图片、音频、视频等超文本数据的规范,格式简称为“超文本传输协议”。
Http协议属于应用层,即用户访问的第一层就是http。

301.Socket和http的区别和应用场景

Socket连接就是所谓的长连接,理论上客户端和服务器端一旦建立起连接将不会主动断掉;
Socket适用场景:网络游戏,银行持续交互,直播,在线视屏等。
http连接就是所谓的短连接,即客户端向服务器端发送一次请求,服务器端响应后连接即会断开等待下次连接
http适用场景:公司OA服务,互联网服务,电商,办公,网站等等等等

302.什么是http的请求体

HTTP请求体是我们请求数据时先发送给服务器的数据。
HTTP请求体由:请求行 、请求头、请求数据组成的。
注意:GIT请求是没有请求体的

303.http的响应报文有哪些

http的响应报是服务器返回给我们的数据,必须先有请求体再有响应报文
响应报文包含三部分 状态行、响应首部字段、响应内容实体实现

304.http和https的区别

  1. http需要拿到ca证书,需要钱的
  2. 端口不一样,http是80,https443
  3. http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。
  4. http和https使用的是完全不同的连接方式(http的连接很简单,是无状态的;HTTPS 协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。)

305.HTTPS工作原理

一、首先HTTP请求服务端生成证书,客户端对证书的有效期、合法性、域名是否与请求的域名一致、证书的公钥(RSA加密)等进行校验;
二、客户端如果校验通过后,就根据证书的公钥的有效, 生成随机数,随机数使用公钥进行加密(RSA加密);
三、消息体产生的后,对它的摘要进行MD5(或者SHA1)算法加密,此时就得到了RSA签名;
四、发送给服务端,此时只有服务端(RSA私钥)能解密。
五、解密得到的随机数,再用AES加密,作为密钥(此时的密钥只有客户端和服务端知道)。

306.一次完整的HTTP请求所经历几个步骤

  1. 建立TCP连接

  2. Web浏览器向Web服务器发送请求行
    一旦建立了TCP连接,Web浏览器就会向Web服务器发送请求命令。例如:GET /sample/hello.jsp

  3. Web浏览器发送请求头
    浏览器发送其请求命令之后,还要以头信息的形式向Web服务器发送一些别的信息,之后浏览器发送了一空白行来通知服务器,它已经结束了该头信息的发送。

  4. Web服务器应答
    客户机向服务器发出请求后,服务器会客户机回送应答, HTTP/1.1 200 OK ,应答的第一部分是协议的版本号和应答状态码。

  5. Web服务器发送应答头
    类别 描述
    1xx: 指示信息–表示请求已接收,正在处理
    2xx: 成功–表示请求已被成功接收、理解、接受
    3xx: 重定向–要完成请求必须进行更进一步的操作
    4xx: 客户端错误–请求有语法错误或请求无法实现
    5xx: 服务器端错误–服务器未能实现合法的请求

  6. Web服务器向浏览器发送数据
    Web服务器向浏览器发送头信息后,它会发送一个空白行来表示头信息的发送到此为结束,接着,它就以Content-Type应答头信息所描述的格式发送用户所请求的实际数据。

  7. Web服务器关闭TCP连接

307.常用HTTP状态码是怎么分类的,有哪些常见的状态码

  • http状态码表示客户端http请求的返回结果、表示服务器处理是否异常、表明请求出现的错误等。
  • 状态码的类别:
类别描述
1xx指示信息,表示请求已接收,正在处理
2xx成功,表示请求已经被成功接收、理解、接受
3xx重定向,表示要完成请求必须进行更进一步的操作
4xx客户端错误,请求有语法错误或者是请求无法实现
5xx服务器端错误,服务器未能实现合法的请求
状态码描述
200请求被正常处理
204请求被受理但没有资源可以返回
301永久性重定向
302临时重定向
304发送附带条件的请求时,条件不满足时返回,与重定向无关
307临时重定向,与302类似,但是强制要求使用post方法
400请求报文语法有误,服务器无法识别
401请求需要认证
403请求的对应资源禁止被访问
404服务器无法找到对应资源
500服务器内部错误
503服务器正忙

308.Http协议中有那些请求方式

请求方式描述
GET用于请求访问已经被URL(统一资源标识符)识别的资源,可以通过URL传参给服务器
POST用于传输信息给服务器,主要功能与get方法类似,但一般推荐使用post方式
PUT传输文件,报文主体中包含文件内容,保存到对应URL位置
HEAD获得报文首部,与get方法类似,只是不返回报文主体,一般用于验证URL是否有效
PATCH客户端向服务器传送的数据取代指定的文档的内容
TRACE回显客户端请求服务器的原始请求报文,用于回环诊断
DELETE删除文件,与put方法相反,删除对应URL位置的文件
OPTIONS查询相应URL支持的http方法

309.GET方法与POST方法的区别

  • get重点是向服务器获取资源,post重点在先服务器发送数据。
  • get传输的数据量小,因为受url长度限制,但效率较高;post可以大量传输数据,所以上传文件时只能用post方式。
  • get是不安全的,因为get请求发送数据时在url上,是可见的,可能会泄露私密信息,如密码等;post是放在请求头部的,是安全的。

310.cookie和session对于HTTP有什么用

  • http协议本身是无法判断用户身份,所以需要cookie或session。
  • cookie:是由web服务器保存在用户浏览器上的文件(key-value格式),可以包含用户相关的信息。客户端向服务器发送请求,就提取浏览器中的用户信息由http发送给服务器。
  • session:是浏览器和服务器会话过程中,服务器会分配一块储存空间给session,服务器默认用户浏览器的cookie中设置sessionid,这个sessionid就和cookie对应,浏览器在向服务器请求过程中传输的cookie包含sessionid,服务器根据传输cookie中的sessionid获取出会话中存储的信息,然后确定会话的身份信息。
  • cookie和session的区别:
    • cookie存放数据在客户端上,安全性较差;session数据放在服务器上,安全性相对更高。
    • 单个cookie保存的数据不能超过4k,session无此限制。
    • session一定时间内保存在服务器上,当访问增多,占用服务器性能,考虑到服务器性能方面,应该使用cookie。

spring

概述

311.什么是Spring

  • Spring是轻量级的开源的javaEE框架
  • Spring可以解决企业应用开发的复杂性
  • Spring有两个核心部分:IOC和Aop
    • IOC:控制反转,把创建对象的过程交给Spring进行管理。
    • Aop:面向切面,不修改源代码的情况下进行功能增强。
  • 降低开发复杂性的策略:
    • 基于POJO的轻量级和最小侵入编程。
    • 通过依赖注入和面向接口实现松耦合。
    • 基于切面进行声明式编程。
    • 通过切面和模板减少样板式代码。

312.Spring两大核心概念

  • IOC:控制反转(依赖注入),即不会直接创建对象,只是把对象声明出来,在代码中不直接与对象进行连接,但是在配置文件中描述了哪一组件需要哪一服务,容器将他们组合起来。在一般的IOC场景中容器创建了所有对象并设置了必要的属性将他们联系在一起,等到需要的时候才把他们声明出来。
  • AOP:面向切面编程,是一种编程模式,它允许程序员通过自定义的横切点进行模块化,将那些影响多个类的行为封装到可重用的模块中。比如日志输出,不使用AOP就需要把日志的输出语句放在所有类、方法中,但是用AOP就可以把日志输出语句封装到一个可重用模块,在以声明的方式将他们放在类中,每次使用类就自动完成了日志输出。

313.Spring框架的设计目标,设计理念,核心是什么

  • 设计目标:为开发者提供一个一站式轻量级应用开发平台。
  • 设计理念:在JavaEE开发中,支持POJO和javaBean开发方式,使应用面向接口开发,充分支持OOP(面向对象)设计方法;spring通过IOC容器实现对象耦合关系的管理,并实现依赖反转,将对象之间的依赖关系交给IOC容器,实现解耦。
  • 核心:IOC和AOP。通过IOC容器管理对象及它们之间的耦合关系;通过AOP以动态非入侵的方式增强服务。
    注:IOC让相互协作的组件保持松耦合,AOP编程允许你把遍布于应用各层的功能分离出来形成可重用的功能组件。

314.Spring的优缺点

优点:

  • 方便解耦,简化开发。Spring就像一个大工厂,可以将所有对象的创建和依赖关系的维护交给spring管理。
  • AOP编程支持。面向切面编程可以方便的实现对程序进行权限拦截、运行监控等功能。
  • 声明式事务支持。只需要通过配置就可以完成对事务的管理,无需再手动编程。
  • 方便程序的测试。spring对junit的支持使程序员可直接用注解方便的测试spring程序。
  • 方便集成框架。
  • 降低javaEE API使用难度(spring内部对JDBC等比较难用的API进行了封装,使他们的应用难度大大降低)
    缺点:
  • spring依赖反射,反射影响性能。

315.Spring应用场景

Java企业级应用开发,如ssm,ssh。

316.Spring组成模块

  • spring core:提供了框架的基本组成部分,包括控制反转和依赖注入功能。
  • spring beans:提供了BeanFactory,使工厂模式的一个经典实现,Spring将管理对象称为Bean。
  • spring context:提供了一种框架式的对象访问方法。
  • spring jdbc:提供了一个JDBC的抽象层,简化了JDBC的使用。
  • spring aop:提供了面向切面编程的实现,让用户可以自定义拦截器、切点等。
  • spring web:提供了针对Web开发的集成特性,如文件上传等。
  • spring test:主要为测试提供支持的,支持用JUnit对spring组件进行单元测试和集成测试。

317.Spring框架中用到了哪些设计模式

  • 工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例。
  • 单例模式:Bean默认为单例模式。
  • 代理模式:spring AOP功能用到了JDK的动态代理和CGLIB的字节码生成技术。
  • 模板方法:用来解决代码重复的问题。如RestTemplate,JpaTemplate。
  • 观察者模式:定义对象间一种一对多的依赖关系,当一个对象发生改变时,所有依赖于它的对象都会收到通知被制动更新。如spring中listener的实现ApplicationListener。

318.详细讲解一下核心容器(spring context应用上下文)模块

  • 这是基本的Spring模块,提供spring框架的基础功功能,BeanFactory是任何以spring为基础的应用的核心。Spring框架建立在此模块上,它使spring成为一个容器。
  • Bean工厂是工厂模式的一个实现,提供了控制反转功能,用来把应用的配置和依赖从真正的应用代码中分离。

319.Spring框架中有哪些不同类型的事件

  • 上下文更新事件(ContextRefreshedEvent):在调用ConfigurableAplicationContext接口中的refresh()方法时被触发。
  • 上下文开始事件(ContextStartedEvent):当容器调用ConfigurableApplicationContext的Start()方法开始/重新开始容器时触发该事件。
  • 上下文停止事件(ContextStoppedEvent):当容器调用ConfigurableApplicationContext的Stop()方法停止容器时触发该事件。
  • 上下文关闭事件(ContextClosedEvent):当ApplicationContext被关闭时触发该事件。容器被关闭时,其管理的所有单例Bean都被销毁。
  • 请求处理事件(RequestHandledEvent):在Web应用中,当一个http请求(request)结束触发该事件。如果一个bean实现了ApplicationListener接口,当一个ApplicationEvent 被发布以后,bean会自动被通知。

320.Spring应用程序有哪些不同组件

  • 接口:定义功能。
  • Bean类:它包含属性,getter和setter方法、函数等。
  • Bean配置文件:包含类的信息以及如何配置他们。
  • Spring面向切面编程(AOP):提供面向切面编程的功能。
  • 用户程序:它使用接口。

321.使用Spring有哪些方式

  • 作为一个成熟的Spring Web应用程序。
  • 作为第三方Web框架,使用Spring Frameworks中间层。
  • 作为企业级Java Bean,他可以包装现有的POJO。
  • 用于远程调用。

控制反转IOC

322.什么是Spring IOC 容器

  • 控制反转即IOC(Inversion of Control),它把传统上由程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理。控制反转就是对组件对象控制权的转移,从程序代码本身转移到了外部容器。
  • Spring IOC负责创建对象,管理对象(通过依赖注入DI),装配对象,配置对象,并且管理这些对象的生命周期。

323.控制反转有什么作用

  • 管理对象的创建和依赖关系的维护。
  • 解耦,由容器去维护具体的对象。
  • 托管了类的产生过程,比如我们需要在类的产生过程中做一些处理(如代理),如果有容器程序就可以把这部分处理交给容器,应用程序则无需去关心类是如何完成代理的。

324.IOC的优点是什么

  • IOC或依赖注入把应用的代码量降低了。
  • 它使程序容易测试。
  • 最小的侵入性实现了松散耦合。
  • IOC容器支持加载服务时的饿汉式初始化和懒加载。

325.Spring IOC 的实现机制

Spring IOC的实现原理就是xml解析+工厂模式+反射机制。
第一步xml配置文件配置创建的对象:
第二步创建工厂类(通过反射创建对象)

interface Fruit {
    public abstract void eat();
}

class Apple implements Fruit {
    public void eat() {
        System.out.println("Apple");
    }
}

class Orange implements Fruit {
    public void eat() {
        System.out.println("Orange");
    }
}

class Factory {
    public static Fruit getInstance(String ClassName) {
        Fruit f = null;
        try {
            f = (Fruit) Class.forName(ClassName).newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return f;
    }
}

class Client {
    public static void main(String[] a) {
        Fruit f = Factory.getInstance("com.ly.spring.Apple");
        if (f != null) {
            f.eat();
        }
    }
}

326.Spring的IOC支持哪些功能

依赖注入 依赖检查 自动装配 支持集合 指定初始化方法和销毁方法 支持回调某些方法(但是需要实现 Spring 接口,略有侵入) 其中,最重要的就是依赖注入,从 XML 的配置上说,即 ref 标签。对应 Spring RuntimeBeanReference 对象。

对于 IOC 来说,最重要的就是容器。容器管理着 Bean 的生命周期,控制着 Bean 的依赖注入。

327.BeanFactory和ApplicationContext有什么区别

  • BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当作Spring容器。其中ApplicationContext是BeanFactory的子接口。
  • 依赖关系:BeanFactory是Spring里面最底层的接口,包含了各种Bean的定义,读取bean配置文档,管理bean的加载、实例化,控制bean的生命周期,维护bean之间的依赖关系。
  • ApplicationContext接口作为BeanFactory的子接口,除了BeanFactoryj具有的功能外,还提供了更完整的框架功能:
    • 继承MessageSource,支持国际化。
    • 统一的文件访问方式。
    • 提供在监听器中注册bean的事件。
    • 同时加载多个配置文件。
    • 载入多个(有继承关系)上下文,使得每一个上下文都专注于一个特定的层次,比如应用的web层。
  • 加载方式:
    • BeanFactory采用的是延迟加载的形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该bean进行加载实例化。这样,我们就不能发现一些存在的Spring配置问题。如果Bean的某一个属性没有注入,BeanFactory加载后,直至第一次使用调用getBean()方法才会抛出异常。
    • ApplicationContext,他是在容器启动时,一次性创建了所有Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。ApplicationContext启动后预加载所有的单实例Bean,通过预载入单实例bean,确保当你需要的时候,你不用等待,因为他们已经创建好了。
      注:相对于BeanFactory,ApplicationContext的唯一不足是占用内存,当应用程序配置bean较多时,程序启动较慢。
  • 创建方式:BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用ContextLoader。
  • 注册方式:BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但BeanFactory需要手动注册,ApplicationContext自动注册。

328.Spring如何设计容器的,BeanFactory和ApplicationContext关系详解

329.ApplicationContext通常实现是什么

  • FileSystemXmlApplicationContext:此容器从一个xml文件中加载beans的定义,XML Bean配置文件的全路径名必须提供给它的构造函数。
  • ClassPathXmlApplicationContext:此容器也从一个xml文件中加载beans的定义,这里需要设置classpath,因为这个容器是在classpath里面找bean配置。
  • WebXmlApplicationContext:此容器加载一个xml文件,此文件定义了一个web应用的所有bean。

330.什么是Spring的依赖注入

控制反转的主要实现方式有两种:依赖注入和依赖查找。
依赖注入:组件之间的依赖关系由容器在应用程序运行期间来决定,即由容器动态的将某种依赖关系的目标对象实例注入到应用系统中的各个关联的组件之中。组件不做定位查询,只提供普通的Java方法让容器去决定依赖关系。

331.依赖注入的基本原则

应用组件不应该负责查找资源或者其他依赖的协作对象。配置对象的工作应由IOC容器负责,查找资源逻辑应该从应用的组件的代码中抽取出来,交给IOC容器负责。容器全权负责组建的装配,它会把符合依赖关系的对象通过属性(setter)或是构造器传递给需要的对象。

332.依赖注入有什么优势

依赖注入让容器全权负责依赖查询,受管组件只需要暴露JavaBeansetter方法或带参数的构造器或者接口,使容器可以在初始化时组装对象的依赖关系。

  • 查找定位操作与应用代码完全无关。
  • 不依赖于容器的API,可以很容易的在任何容器以外使用对象。
  • 不需要特殊的接口,绝大多数对象可以做到完全不必以来容器。

333.有哪些不同类型的依赖注入的实现方式

  • 构造器依赖注入:构造器依赖注入通过容器触发一个类的构造器来实现的,该类有一系列参数,每个参数代表一个对其他类的依赖。
  • Setter方法注入:setter方法注入式容器通过调用无参构造函数或无参静态工厂方法实例化bean后,调用bean的setter方法,即实现了基于setter的依赖注入。

334.构造器依赖注入和setter方法注入的区别

  • 部分依赖:可以使用setter注入来注入, 但构造函数无法实现。假设一个类中有3个属性, 具有3个arg构造函数和setters方法。在这种情况下, 如果只想传递一个属性的信息, 则只能通过setter方法进行传递。
  • 覆盖:Setter注入将覆盖构造函数注入。如果我们同时使用构造函数和setter注入, 则IOC容器将使用setter注入。
  • 更改:我们可以通过二传手注入轻松地更改值。它不会像构造函数那样创建新的bean实例。因此, setter注入比构造函数注入更灵活。

Spring Beans

335.什么是Spring beans

Spring beans是形成Spring应用主干的Java对象。它们被IOC容器初始化,装配和管理。这些beans通过容器中配置的元数据创建。如XML文件中定义。

336.一个Spring Bean定义包含什么

包含容器笔直的所有配置元数据,包括如何创建一个bean,它的生命周期详情及它的依赖。

337.如何给Spring容器提供配置元数据?Spring有几种配置方式

  • xml配置文件。
  • 基于注解的配置。
  • 基于Java的配置。

338.Spring配置文件包含了哪些信息

spring配置文件是个xml文件,包含了类信息,描述了如何配置他们,以及如何相互调用。

339.Spring基于xml注入bean的几种方式

  • set方法注入。
  • 构造器注入
    • 通过index设置参数的位置。
    • 通过type设置参数类型。
  • 静态工厂注入。
  • 实例工厂注入。
  • 注解注入。

340.怎样定义类的作用域

它可以通过bean 定义中的scope属性来定义。如,当Spring要在需要的时候每次生产一个新的bean实例,bean的scope属性被指定为prototype。另一方面,一个bean每次使用的时候必须返回同一个实例,这个bean的scope属性必须设为 singleton。

341.Spring支持几种bean的作用域

  • singleton:bean在每个IOC容器中只有一个实例。
  • prototype:一个bean的定义可以有多个实例。
  • request:每次http请求都会创建一个bean,该作用域仅在基于web的Spring ApplicationContext情形下有效。
  • session:在一个HTTPSession中,yigebean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
  • global-session:在一个全局的HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
    注:默认状态下spring bean的作用于是singleton。使用prototype作用域需要考虑因为频繁创建和销毁bean带来的性能开销。

342.Spring框架中的单例bean是线程安全的吗

不是。spring中的bean默认是单例模式,spring框架并没有对单例bean进行多线程的封装处理。

343.Spring如何处理线程并发问题

344.Spring框架中bean的生命周期

  • Spring对bean进行实例化;
  • Spring将值和bean的引用注入到bean对应的属性中;
  • 如果bean实现了BeanNameAware接口,Spring将bean的ID传递给setBean-Name()方法;
  • 如果bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入;
  • 如果bean实现了ApplicationContextAware接口,Spring将调用setApplicationContext()方法,将bean所在的应用上下文的引用传入进来;
  • 如果bean实现了BeanPostProcessor接口,Spring将调用它们的postProcessBeforeInitialization()方法;
  • 如果bean实现了InitializingBean接口,Spring将调用它们的after-PropertiesSet()方法。类似地,
  • 如果bean使用initmethod声明了初始化方法,该方法也会被调用;
  • 如果bean实现了BeanPostProcessor接口,Spring将调用它们的post-ProcessAfterInitialization()方法;
    此时,bean已经准备就绪,可以被应用程序使用了,它们将一直驻留在应用上下文中,直到该应用上下文被销毁;
  • 如果bean实现了DisposableBean接口,Spring将调用它的destroy()接口方法。同样,如果bean使用destroy-method声明了销毁方法,该方法也会被调用。

345.哪些是重要的bean的生命周期方法

  • 有两个重要的bean 生命周期方法,第一个是setup , 它是在容器加载bean的时候被调用。第二个方法是 teardown 它是在容器卸载类的时候被调用。
  • bean 标签有两个重要的属性(init-method和destroy-method)。用它们你可以自己定制初始化和注销方法。它们也有相应的注解(@PostConstruct和@PreDestroy)。

346.什么是Spring的内部bean,什么是Spring inner beans

在spring框架中,当一个bean仅被用作另一个bean的属性时,它能被声明为一个内部bean。内部bean可以用setter注入属性和构造方法注入,内部bean通常是匿名的,他们的Scope一般是prototype。

347.什么是bean装配

指spring容器把有依赖关系的bean组装到一起,就是将bean注入到其他bean的property中。

348.什么是bean的自动装配

在配置文件中设定bean的依赖关系后,Spring可以通过向beanfactory中注入的方式自动搞定bean之间的依赖关系。

349.spring自动装配bean有哪些方式

  • 在spring中,对象无需自己查找或创建与其关联的其他对象,由容器负责把需要相互协作的对象引
    用赋予各个对象,使用autowire来配置自动装载模式。
  • 在Spring框架xml配置中共有5种自动装配:
    • no:默认的方式是不进行自动装配的,通过手工设置ref属性来进行装配bean。
    • byName:通过bean的名称进行自动装配,如果一个bean的 property 与另一bean 的name 相同,就进行自动装配。
    • byType:通过参数的数据类型进行自动装配。
    • constructor:利用构造函数进行装配,并且构造函数的参数通过byType进行装配。
    • autodetect:自动探测,如果有构造方法,通过 construct的方式自动装配,否则使用 byType的方式自动装配。

350.使用@Autowired注解自动装配的过程是怎样的

使用@Autowired注解来自动装配指定的bean。在使用@Autowired注解之前需要在Spring配置文件进行配置,<context:annotation-config />。
在启动spring IOC时,容器自动装载了一个AutowiredAnnotationBeanPostProcessor后置处理器,当容器扫描到@Autowied、@Resource或@Inject时,就会在IOC容器自动查找需要的bean,并装配给该对象的属性。在使用@Autowired时,首先在容器中查询对应类型的bean:如果查询结果刚好为一个,就将该bean装配给@Autowired指定的数据;如果查询的结果不止一个,那么@Autowired会根据名称来查找;如果上述查找的结果为空,那么会抛出异常。解决方法时,使用required=false。

351.自动装配有哪些局限性

  • 重写:你仍需用 和 配置来定义依赖,意味着总要重写自动装配。
  • 基本数据类型:你不能自动装配简单的属性,如基本数据类型,String字符串,和类。
  • 模糊特性:自动装配不如显式装配精确,如果有可能,建议使用显式装配。

352.可以在spring中注入一个null和一个空字符串吗

可以。

Spring注解

353.什么是基于Java的Spring注解配置

就是在少量Java注解的帮助下,进行大部分spring配置而非通过xml文件。
下面以@Configuration(用来标记类可以当作一个bean的定义,被Spring IOC容器使用)和@Bean(表示此方法将要返回一个对象,作为一个bean注册进spring应用上下文)为例

@Configuration
public class TestConfig {
    @Bean
    public StudentBean myStudent() {
        return new StudentBean();
    }
}

354.怎样开启注解装配

注解装配在默认情况下是关闭的,要使用须在配置文件中配置

context:annotation-config/

355.@Component,@Controller,@Repository,@Service有何区别

  • @Component:将Java类标记为bean。它是任何spring管理组件的通用构造型。spring的组件扫描机制体现在可以将其拾取并拉入应用程序环境中。
  • @Controller:将一个类标记为Spring Web MVC控制器。标有它的bean将自动导入到IOC容器中。
  • @Repository:专用于dao层的Component。
  • @Service:专用于service层的Component。

356.@Required注解有什么用

@Required 注解应用于 bean 属性的 setter 方法,它表明受影响的 bean 属性在配置时必须放在 XML 配置文件中,否则容器就会抛出一个 BeanInitializationException 异常。下面显示的是一个使用 @Required 注解的示例。

357.@Autowired注解有什么用

@Autowired 是一个注解,它可以对类成员变量、方法及构造函数进行标注,让 spring 完成 bean 自动装配的工作。
@Autowired 默认是按照类去匹配,配合 @Qualifier 指定按照名称去装配 bean。

358.@Autowired和@Resource之间的区别

  • @Autowired和@Resource可用于:构造函数、成员变量、setter方法。
  • @Autowired默认是按照类型装配注入的,默认情况下它要求依赖对象必须存在(可以设置required属性为false)
  • @Resource默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入。

359.@Qualifier注解有什么作用

@Qualifier多与@Autowired联用,当创建多个相同类型的bean并希望仅使用属性装配其中一个bean时,可以通过它们的联用来指定应该装配哪个确切的bean。

360.@RequestMapping注解有什么作用

@RequestMapping注解用于将特定的HTTP请求方法映射到将处理相应请求的控制器中的特定类/方法。类级别映射请求的url;方法级别,映射URL以及HTTP请求方法。

Spring数据访问

361.对象/关系映射集成模块是什么

spring通过提供orm模块,支持我们在jdbc上使用一个对象/关系映射(ORM)工具,spring支持集成主流的ORM框架,如Hibernate,Mybatis,JPA等等;spring的事务管理同样支持以上所有ORM框架以及JDBC。

362.在spring框架中如何更有效地使用JDBC

使用spring框架,Spring 的JDBC框架承担了资源管理和异常处理的工作,从而简化了JDBC代码,让我们只需要编写从数据库读写数据的必须代码。

363.描述一下JDBC抽象和dao模块

通过使用JDBC抽象和DAO模块,保证数据库代码的简洁,并能避免数据库资源错误关闭导致的问
题,它在各种不同的数据库的错误信息之上,提供了一个统一的异常访问层。它还利用Spring的 AOP 模块给Spring应用中的对象提供事务管理服务。

364.spring DAO的作用

Spring DAO(数据访问对象) 使得 JDBC,Hibernate 或 JDO 这样的数据访问技术更容易以一种
统一的方式工作。这使得用户容易在持久性技术之间切换。它还允许您在编写代码时,无需考虑捕
获每种技术不同的异常。

365.spring JDBC API中存在哪些类

JdbcTemplate
SimpleJdbcTemplate
NamedParameterJdbcTemplate
SimpleJdbcInsert
SimpleJdbcCall

366.JDBCTemplate是什么

JdbcTemplate 类提供了很多便利的方法解决诸如把数据库数据转变成基本数据类型或对象,执行写好的或可调用的数据库操作语句,提供自定义的数据错误处理。

367.spring事务实现方式有哪些

  • 编程式事务管理:通过编程的方式管理事务,灵活但难维护。
  • 声明式事务管理:将业务代码与事务管理分离,只需用注解和xml配置来管理事务。

368.spring事务的实现原理

Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的。

369.spring事务传播行为

事务传播行为详解----->🤚
事务传播行为是spring提供的一套事务管理方案。

  • PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事物,如果当前存在在事务,就加入该事务,该设置是最常用的设置。
  • PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。
  • PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。
  • PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。
  • PROPAGATION_NOT_SUPPORTED:以非事务的方式执行操作,如果当前存在事务,则将当前事务挂起。
  • PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
  • PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。

370.spring事务隔离级别

spring有五大隔离级别,默认值为ISOLATION_DEFAULT(使用数据库的设置),其他四个隔离级别与数据库的隔离级别一致。

  • ISOLATION_DEFAULT:用数据库设置的隔离级别,数据库设置的什么就用什么。
  • ISOLATION_READ_UNCOMMOTTED:读未提交,最低隔离级别,事务未提交前,就可被其他事务读取(会出现幻读、脏读、不可重复读)。
  • ISOLATION_READ_COMMITTED:读已提交,一个事务提交后才能被其他事务读取到(会造成幻读,不可重复读),SQL server的默认级别。
  • ISOLATION_REPEATABLE_READ:可重复读,保证多次读取同一个数据时,其值和事务开始时的内容是一致的,禁止读取到别的事务未提交的数据(会造成幻读),MySQL的默认级别。
  • ISOLATION_SERIALIZABLE:序列化,代价最高最可靠的隔离级别,该隔离级别能防止脏读、幻读、不可重复读。

脏读:表示一个事务能够读取另外一个事务中还未提交的数据。
不可重复读:指在一个事务内,多次读同一数据。
幻读:指同一个事务内多次查询返回的结果集不一样。比如同一个事务 A 第一次查询时候有 n 条记录,但是第二次同等条件下查询却有 n+1 条记录,这就好像产生了幻觉。发生幻读的原因也是另外一个事务新增或者删除或者修改了第一个事务结果集里面的数据,同一个记录的数据内容被修改了,所有数据行的记录就变多或者变少了。

371.spring事务管理的优点

  • 为不同的事务API提供了一个不变的编程环境。
  • 为编程式事务管理提供了一套简单的API。
  • 支持声明式事务管理。
  • 和spring各种数据访问抽象层很好的集成。

spring面向切面编程(AOP)

372.什么是AOP

  • OOP:面向对象编程,允许开发者定义纵向关系,并不适用于定义横向关系,导致大量代码的重复,不利于各个模块的重用。
  • AOP:面向切面编程,用于将那些与业务无关,但对多个对象产生影响的公共行为和逻辑,抽取并封装成一个可重用的模块,这个模块被命名为切面(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。可用于权限认证、日志、事务处理等。

373.AOP在spring中的实现方式及区别

AOP的实现在于代理模式,AOP代理主要分为静态代理和动态代理。静态代理的代表为AspectJ,动态代理代表为SpringAOP.

  • AspectJ是静态代理的增强,静态代,即AOP框架会在编译时生成AOP代理类,因此称为编译时增强,它会在编译阶段将AspectJ(切面)植入到Java字节码中,运行的时候就是增强之后的AOP对象。
  • 动态代理中AOP不会去修改字节码,而是每次在运行时内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。

374.Spring AOP的动态代理方式及区别

  • JDK动态代理只提供接口的代理,不支持类的代理。核心InvocationHandler接口和Proxy类,InvocationHandler通过invoke()方法反射来调动目标类中的代码,动态的将横切逻辑和业务逻辑编制在一起;接着,Proxy利用InvocationHandler动态创建一个符合某一接口的实例,生成目标类的代理对象。
  • 之所以 JDK 的动态代理只能通过接口实现,原因是因为运行时 newProxyInstance 内部会缓存形式先通过字节码生成一个代理类,这个代理类默认已经继承了 Proxy 类,同时实现了我们传入的一堆接口;由于 Java 是单继承的,所以 JDK 动态代理只能代理接口,接口可以实现多个,但是类只能继承实现一个。
  • 如果代理类没有实现InvocationHandler接口,那么SpringAOP会使用CGLIB来动态代理目标类。CGLIB是一个代码生成的类库,可以在运动时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。

375.Aop名词解释

  • 切面(Aspect):切面是通知和切点的结合。通知和切点共同定义了切面的全部内容。在SpringAOP中,切面可以使用通用类或者在普通类中已以@AspectJ注解来实现。
  • 连接点(Join point):指方法,SpringAOP中,一个连接点总是代表一个方法的执行。应用可能有数以千计的时机应用通知。这些时机被称为连接点。连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、修改字段时。切面代码可以利用这些点插入到应用的正常流程中,并添加新的行为。
  • 通知(advice):切面的工作被称为通知。
  • 切入点(Pointcut):切点的定义会匹配通知所要织入的一个或多个连接点,通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点。
  • 引入(introduction):允许我们向现有类添加新方法或属性。
  • 目标对象(Target Object):被一个或者多个切面所通知的对象。
  • 织入(Weaving):即把切面应用到目标对象并创建新的代理对象的过程。在目标对象的生命周期里可以有多个点进行织入:
    • 编译期:切面在目标类编译时被织入。AspectJ就是以这种方式织入的。
    • 类加载期:切面在目标类加载到JVM时被织入。需要特殊的类加载器,它可以在目标类被引入应用之前增强该目标类的字节码。
    • 运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态创建一个代理对象,SpringAOP就是以这种方式织入切面。

376.Spring在运行时怎样通知对象

  • 通过代理类中包裹切面,Spring在运行期间把切面织入到Spring管理的bean中,代理封装了目标类,并拦截被通知方法的调用,再把调用转发给真正地目标bean。当代理拦截到方法调用时,在调用目标bean方法之前,会执行切面逻辑。
  • 直到应用需要被代理的bean时,Spring才创建代理对象。如果使用的是ApplicationContext的话,在ApplicationContext从BeanFactory中加载所有bean的时候,Spring才会创建被代理的对象。因为Spring运行时才创建代理对象,所以我们不需要特殊的编译器来织入SpringAOP的切面。

377.Spring为什么只支持方法级别的连接点

因为Spring基于动态代理,所以Spring只支持方法连接点。Spring缺少对字段连接点的支持,而且
它不支持构造器连接点。方法之外的连接点拦截功能,我们可以利用Aspect来补充。

378.在springAOP中,关注点(concern)和横切关注(cross-cutting concern)的区别是什么

  • 关注点(concern):是应用中一个模块的行为,一个关注点可能会被定义成一个功能。
  • 横切关注点(cross-cutting concern):此关注点是整个应用都会使用的功能,影响整个应用,如日志等几乎应用的每个模块都需要的功能。

379.spring通知类型

  • 前置通知(Before):在目标方法被调用之前调用通知功能。
  • 后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么。
  • 返回通知(After-returning):在目标方法成功执行之后调用通知。
  • 异常通知(After-throwing):在目标方法抛出异常之后调用通知。
  • 环绕通知(Around):包围一个连接点的通知,这是最强大的一种通知类型。 环绕通知可以在方法调用前后完成自定义的行为。它也可以选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。

380.什么是切面Aspect

切入点+通知。使用@Aspect注解的类就是切面。

SpringMVC

概述

381.什么是SpringMVC(说自己对springMVC的理解)

SpringMVC是一个spring的一个模块,是一个基于Java的实现了MVC设计模式的请求驱动类型的轻量级web框架,通过把模型-试图-控制器分离,将web层进行职责解耦,把复杂的web应用分成逻辑清晰的几部分,简化开发,减少出错,方便开发人员之间的配合。

382.SpringMVC的优点

  • 它是基于组件技术的。全部的应用对象,无论控制器和试图,还是业务对象之类的都是Java组件。并且和spring提供的其他基础结构紧密集成(如IOC、AOP)。
  • 可以支持各种视图技术不仅限于JSP。
  • 清晰的角色分配:前端控制器(dispatcherServlet),请求的处理映射器(handlerMapping),处理器适配器(HandlerAdapter),视图解析器(ViewResolver)。
  • 支持各种请求资源的映射策略。

核心组件

383.SpringMVC的主要组件

  • 前端控制器DispatcherServlet(不需要程序员开发):接收请求,响应结果,可以减少其他组件之间的耦合度。
  • 处理器映射器HandlerMapping(不需要程序员开发):根据请求的URL查找Handler.
  • 处理器适配器HandlerAdapter:执行Handler。
  • 处理器Handler(需要程序员开发)
  • 视图解析器ViewResolver(不需要程序员开发):进行视图的解析,根据视图逻辑名解析成真正地视图。
  • 视图View(需要程序员开发):它是一个接口,它的实现类支持不同类型的视图(jsp,freemarker,thymeleaf等)

384.什么是DispatcherServlet

前端控制器,它是用来统一处理所有的HTTP请求和响应的,是springmvc的入口。

385.什么是SpringMVC的控制器

“Controller(控制器): 是应用程序中处理用户交互的部分,作用一般就是处理程序逻辑的。它提供一个访问应用程序的行为,此行为通常通过接口实现。控制器解析用户输入的信息,并将其转换为一个由视图呈现给用户的模型。现在常见的框架中都有一个控制层。

386.SpringMVC的控制器是不是单例模式

是单例模式。所以在多线程访问的时候有线程安全问题;解决方案不能用同步(太影响性能),解决方案是不在控制器里面写字段。单例模式中方法是私有的,成员变量是共享的,所以方法是安全的,变量是共享的,所以只要不在控制器中定义属性,单例模式的springMVC在高并发情况下就是安全的,如果必须要添加属性,则在类上面使用@Scope(“prototype”)将本身改为多例的模式。

工作原理

387.SpringMVC的工作流程

  1. 用户发送请求到前端控制器;
  2. 前端控制器收到请求后调用处理器映射器,请求获取handler;
  3. 处理器映射器根据请求的URL找到具体的处理器,生成处理器对象及处理器拦截器一并返回给前端控制器;
  4. 前端控制器调用处理器适配器;
  5. 处理器适配器经过适配调用具体的处理器(Handler,也叫后端控制器);
  6. 处理器执行完成后返回ModelAndView;
  7. 处理器适配器将处理器执行结果返回给前端控制器;
  8. 前端控制器将结果ModelAndView传给视图解析器进行解析;
  9. 视图解析器解析后返回具体的视图;
  10. 前端控制器进行渲染视图(将模型数据填充到视图中);
  11. 前端控制器响应用户。

388.SpringMVC的工作原理

  1. 客户端发送请求到前端控制器;
  2. 前端控制器查询处理器映射器找到处理请求的控制器(Controller);
  3. 控制器调用业务逻辑后返回ModelAndView;
  4. 前端控制器查询ModelAndView,找到指定视图;
  5. 视图将结果返回到客户端。

MVC框架

389.MVC是什么,它有什么好处

MVC是一种设计模式,即模型(model)—视图(view)—控制器(Controller),三层架构的设计模式.用于实现前端页面的展现和后端业务数据处理的分离。
好处就是分层设计实现了业务系统各个组件之间的解耦,有利于系统业务的可拓展性,可维护性。有利于系统的并行开发,提升开发效率。

常用注解

390.SpringMVC常用注解

  • @RequestMapping:用于处理请求url映射的注解,可用于类或方法上。用于类上,则表示类中所有响应请求的方法都是以此地址作为父路径。
  • @RequestBody:注解实现接受http请求的json数据,将json数据转换为Java对象。
  • @ResponseBody:注解实现将Controller方法返回对象转化为json对象响应给客户。
  • @Controller:控制器的注解,表示表现层,不能用别的注解代替。

391.SpringMVC控制器的注解一般用哪个,能否代替

@Controller,也可以用@RestController(@ResponseBody+@Controller),表示是表现层,不能用别的注解代替。

392.@Controller的作用

393.@RequestMapping的作用

394.@ResponseBody的作用

395.@PathVariable和@RequestParam的区别

@PathVariable:用于获取请求路径上得变量值(@RequestMapping(value=“/list/{uid}”),method = RequestMethod.GET)
@RequestParam:用于获取静态的URL请求参数,将请求参数和控制器方法的形参创建映射关系。

其他

396.SpringMVC怎样设置重定向和转发

  • 转发:在返回值前面加“forward:”,比如“forward:user.do?name=method4”。
  • 重定向:在返回值面前加“redirect:”,比如“redirect:www.baidu.com”。

397.SpringMVC和ajax怎样相互调用

398.怎样解决get,post请求中文乱码问题

解决post请求乱码问题:
在web.xml中配置一个CharacterEncodingFilter过滤器,设置成utf-8;

request.setCharacterEncoding("UTF-8");
<filter>
    <description>字符集过滤器</description>
    <filter-name>encodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <description>字符集编码</description>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

get请求中文参数出现乱码
修改tomcat配置文件添加编码与工程编码一致

<Connector connectionTimeout="50000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/>

修改为

<Connector connectionTimeout="50000" port="8080" protocol="HTTP/1.1" redirectPort="8443" URIEncoding="UTF-8"/>

或者是

String name = request.getParameter("name");
String orgname = URLDecoder.decode(name,"UTF-8");

399.SpringMVC的异常处理

将异常抛给Spring框架,由Spring框架来处理;程序员需要配置简单的异常处理器,在异常处理器中添加视图页面。

400.怎样配置拦截get方式提交的请求

在@RequestMapping注解里面加上method=RequestMethod.GET。

401.怎样在方法里面得到Request或Session

直接在方法的形参中声明request,SpringMVC会自动把request对象传入。

402.怎样获得拦截的方法里面从前台传入的参数

直接在形参里面声明这个参数,但名字必须和传过来的参数一样。

403.如果前台有很多个参数传入,这些参数都是一个对象的,怎样快速得到这个对象

直接在方法中声明这个对象,SpringMVC会把属性赋值到这个对象里面。

404.SpringMVC的函数返回值是什么

405.怎样把ModelMap里面的数据放入Session里面

通过在类上面使用@SessionAttributes注解,里面包含的字符串就是要放入session里面的key。

406.SpringMVC的拦截器怎样实现

407.什么是WebApplicationContext

WebApplicationContext继承了ApplicationContext并扩展了一些功能。

408.注解的原理

409.SpringMVC后台用什么向前台传输数据

通过ModelMap对象,可以在这个对象里面调用put方法,把对象加到里面,前台就可以通过el表达式拿到。

mybatis

概括

410.什么是Mybatis

  • Mybatis是一个半自动的ORM(对象关系映射)框架,它内部封装了JDBC,开发时只需要关注SQL语句本身,不需要花精力去处理加载驱动、创建链接、创建statement等繁杂的过程。程序员直接编写原生态的SQL,可以更灵活的控制SQL执行性能。
  • Mybatis使用xml或注解来配置和映射原生信息将POJO映射成数据库中的记录,避免了几乎所有JDBC代码和手动设置参数及获取结果集。

411.Mybatis的优缺点

优点:

  • 基于SQL语句编程,比较灵活,不会对应用程序或数据库的现有设计造成任何影响,SQL语句写在XML里,解除了sql语句与程序代码的耦合,便于统一管理;提供xml标签,支持编写动态sql语句,并且可以重用.
  • 与JDBC相比,减少了代码量,不需要手动开关连接。
  • 很好的与数据库兼容。
  • 提供映射标签,支持对象与数据库映射;提供对象关系映射标签,支持对象关系组件维护。
  • 能够与spring很好的集成。

缺点:

  • SQL语句的编写工作量较大,尤其是当字段多,关联表多时。
  • SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。

412.Hibernate和Mybatis的区别

相同点:都是对JDBC的封装,都是持久层的框架,都用于dao层的开发。
不同点:
映射关系:

  • Mybatis是一个半自动映射的框架,配置Java对象与sql语句执行结果的对应关系,多表关联关系配置简单。
  • Hibernate是一个全表映射框架,配置Java对象与数据库表的对应关系,多表关联关系配置复杂。
    sql语句优化:Mybatis更容易,Hibernate优化sql语句困难。
    移植性:
  • Mybatis需要手动编写sql语句,支持动态SQL,处理列表,动态生成表名,支持存储过程。开发工作量相对较大,直接使用SQL语句操作数据库,不支持数据库无关性,即移植性较差。
  • Hibernate对SQL语句封装,提供了日志,缓存,级联等特性还提供了HQL操作数据库,数据库无关性支持好(即移植性好),但会消耗更多性能。适用于开发支持多种数据库,代码开发量少的项目。

413.半自动ORM映射工具Mybatis与全自动的区别在哪

  • Hibernate属于全自动ORM映射工具,使用Hibernate查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的。
  • Mybatis在查询关联对象或关联集合对象时,需要手动编写sql来完成,所以称为半自动ORM映射工具。

414.传统JDBC开发存在什么缺点

  • 频繁创建数据库连接对象、释放,容易造成系统资源浪费,影响系统性能。可以用连接池解决这个问题。
  • sql语句都是在代码中写死了的,存在硬编码,当sql需要动态发生变化时,需要修改Java源代码,系统需要重新编译,重新发布。不好维护。
  • 使用prepareStatement传参是存在硬编码,因为sql语句的where条件是变化的(不是仅仅只有一个条件),又涉及到了修改源代码,影响性能。
  • 结果处理集中存在重复编码,处理麻烦。如果可以映射成Java对象会比较方便。

415.JDBC的不足之处,Mybatis是怎么解决的

  • 在mybatis的配置文件部分配置连接池,使用连接池管理数据库连接。
  • 将sql语句写在Mapper.xml文件中,与Java代码实现分离。
  • Mybatis将对象自动映射到sql语句,还提供各种标签及动态sql。
  • Mybatis自动将sql执行结果映射至Java对象。

416.Mybatis和Hibernate的使用场景

  • Mybatis是一个小巧、方便、高效、简单、直接、半自动化的轻量级持久层框架,适用于需求变化频繁,大型的项目。
  • 如商城项目。
  • Hibernate是一个强大、方便、高效、复杂、间接、全自动化的重量级持久层框架,适用于需求稳定 ,中小型项目。如员工管理系统。

417.Mybatis的编程步骤

  1. 创建SqlSessionFactory。
  2. 通过SqlSessionFactory创建Session。
  3. 通过Session执行数据库操作。
  4. 通过session.commit()提交事务。
  5. 通过session.close()关闭会话。

418.Mybatis的工作原理

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

419.Mybatis的功能架构

Mybatis的功能架构分为三层

  • API接口层:提供给外部使用的接口API,开发人员通过这些API来操作数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。
  • 数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等,主要目的是根据调用的请求完成一次数据库操作。
  • 基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载、缓存处理,这些都是公用的东西,将他们抽取出来作为最基础的组件,为上层的数据处理层提供最基础的支撑。

420.Mybatis的框架架构设计

  1. 加载配置:配置来源于配置文件和Java代码里的注解,将sql的配置信息加载成为一个个MappedStatement对象(包括传入的参数映射配置、执行的sql语句、结果映射配置)存储在内存中。
  2. SQL解析:当API接口层几位收到调用请求时,会接收到出入SQL的ID和传入对象(可以是Map,javaBean或者基本数据类型),Mybatis会根据SQL的ID找到对应的MappedStatement,然后根据传入参数对象对MappedStatement进行解析,解析后可以得到最终执行的sql语句和参数。
  3. SQL执行:将最终得到的SQL和参数拿到数据库进行执行,得到操作数据库的结果。
  4. 结果映射:将操作数据库的结果按照映射的配置进行转换,可以转换成HashMap、JavaBean或者基本数据类型,并将最终结果返回。

421.什么是DBMS

数据库管理系统(database management system)是一种操纵和管理数据库的大型软件,用于建立、使用和维护数据库,简称dbms。他对数据库进行统一的管理和控制,以保证数据库的安全性和完整性。DBMS提供数据定义语言DDL域数据库操作语言DML,供用户定义数据库的模式结构与权限约束,实现对数据的追加权,删除等操作。

422.为什么需要预编译

  • 预编译指的是数据库驱动在发送sql语句和参数在给DBMS之前对sql语句进行编译,这样DBMS执行SQL时就不需要重新编译。
  • JDBC中使用prepareStatement来抽象预编译语句。预编译阶段可以优化SQL的执行。
  • 预编译后的SQL多数情况下可以直接执行,DBMS不需要再次编译。
  • 越复杂的SQL编译的复杂度越大,预编译阶段可以合并多次操作为一个操作。
  • 预编译语句对象可以重复利用。
  • Mybatis默认情况下将对所有的SQL进行预编译。
  • 防止SQL注入。

423.Mybatis有哪些Executor执行器及他们的区别

424.Mybatis中如何指定用哪一种执行器

425.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()方法的调用。这就是延迟加载的基本原理。

映射器

426.#{}和${}的区别

  • #{}是占位符,预编译处理;${}是拼接符,字符串替换,没有预编译处理。
  • Mybatis在处理#{}时,会将sql中的#{}处理为?,调用PreparedStatement的set方法来赋值;处理 时,就是把 {}时,就是把 时,就是把{}替换成变量的值。
  • 使用#{}可以有效地防止SQL注入,提高系统安全性。
  • #{}的变量替换是在DBMS中;${}的变量提换是在DBMS外。

427.模糊查询like语句怎么写

  • ’%${condition}%‘---->可能会引起sql注入,不推荐。
  • “%”#{condition}“%”---->因为#{}解析成sql语句时,会在变量外侧自动加单引号’‘,所以这里的%需要使用双引号“”,不然会查不到结果。
  • CONCAT(’%‘,#{condition},’%‘)---->使用concat()函数。推荐。
  • 使用bind标签,不推荐。

428.在mapper中如何传递多个参数

  • 顺序传参
  • @Param注解传参
  • Map传参
  • JavaBean传参

429.通常一个xml映射文件都会写一个Dao接口与之对应,Dao接口的工作原理是什么,dao接口的方法能重载吗

  • Dao接口,接口的全限名(全限定类名=包名+类型),就是映射文件中namespace的值,接口的方法名就是映射文件中MapperStatement的id值,接口方法内的参数,就是传递给sql的参数。Mapper接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为key值,可唯一定位一个MapperStatement,如com.ly.dao.UserDao.findById可以找到namespace为com.ly.dao.UserDao下面的id=findById的MapperStatement。在Mybatis中,每一个、、、标签,都会被解析成为一个MapperStatement对象。
  • Dao接口的方法是不能重载的,因为Mybatis是全限名+方法名的保存和寻找策略。Dao接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Dao接口生成代理proxy对象,代理对象proxy会拦截接口方法,转而执行MapperStatement所代表的sql,然后将执行结果返回。

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

1、当列名和封装查询结果的类的属性名一一对应时:这时MyBatis 有自动映射功能,将查询的记录封装到resultType 指定的类的对象中去

<mapper namespace="com.hanT.dao.UserDao">
    <!--id对应接口中的方法名,resultType为返回结果的类型,要用类的全限定名-->
    <select id="getUserList" resultType="com.hanT.pojo.User">
        select * from mybatis.user
    </select>
</mapper>

2、当列名和封装查询结果的类的属性名不对应时:使用resultMap 标签,在标签中配置属性名和列名的映射关系

<resultMap type="cn.com.mybatis.pojo.User" id="UserResult">
    <result property="username" column="name"/>
</resultMap>
<select id="findUserById" parameterType="java.lang.Long" resultMap="UserResult">
    select id,name,email from t_user where id=#{id}
</select>

431.xml映射文件中,除了常用的select|insert|update|delete标签,还有哪些标签

有< resultMap >,< parameterMap >,< sql >,< include >,< selectKey >,加上动态sql的9个标签,trim,where,s et,foreach,if,choose,when,otherwise,bind等,其中为sql片段标签,通过include标签引入sql片段,< selectKey >为不支持自增的主键生成策略标签。

432.如何获取自增的主键

#方式一: 使用 useGeneratedKeys + keyProperty 属性,这种在项目中比较常用
<insert id="insert" parameterType="User" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO user(name, password)
    VALUE (#{name}, #{password})
</insert>
    
#方式二: 使用 `<selectKey />` 标签
<insert id="insert" parameterType="User" useGeneratedKeys="true" keyProperty="id">
    <selectKey keyProperty="id" resultType="long" order="AFTER">
        SELECT LAST_INSERT_ID()
    </selectKey>
        
    INSERT INTO user(name, password)
    VALUE (#{name}, #{password})
</insert>

433.当实体类中的属性名和表中的字段名不一样时有哪些处理方式

  • 通过写查询语句时建立别名,让字段的别名和属性名一致。
  • 通过resultMap来映射字段名和实体类属性名的一一对应关系。

434.Mapper编写有哪几种方式

435.使用Mybatis的mapper接口调用时有哪些要求

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

436.Mybatis的xml映射文件中,不同的XML映射文件id能不能重复

如果都配置了namespace,那么是可以重复的,因为namespace+id是Map<String,MappedStatement>的key使用,如果没有配置namespace且id相同,会导致数据覆盖。

437.Mybatis映射文件中,如果A标签通过include引用了B标签的内容,B标签能否定义在A标签的后面

虽然Mabatis是按照顺序解析xml文件的,但是此种情况是可以的;因为当Mybatis解析到A标签时发现A标签引用的B标签还没有解析,所以会将A标签标记为未标记状态,继续向下解析,当解析完后再来解析被标记的为解析的标签,此时A标签引用的B标签已经解析了,所以A标签也可以被解析了。

438.Mybatis能用哪些方式执行一对一,一对多的联系查询

439.Mybatis是否可以映射Enum枚举类

  • Mybatis可以映射枚举类,不单可以映射枚举类,Mybatis可以映射任何对象到表的一列上。映射
    方式为自定义一个TypeHandler,实现TypeHandler的setParameter()和getResult()接口方法。
  • TypeHandler有两个作用,一是完成从javaType至jdbcType的转换,二是完成jdbcType至
    javaType的转换,体现为setParameter()和getResult()两个方法,分别代表设置sql问号占位符参
    数和获取列查询结果。

440.Mybatis动态sql的作用及执行原理

  • Mybatis动态sql可以让我们在XML映射文件内,以标签的形式编写动态sql,(trim,where,set,foreach,if,choose,when,otherwise,bind)。
  • 执行原理为使用OGNL从sql参数对象中计算表达式的值,根据表达式的值动态拼接sql。

441.Mybatis是如何进行分页的,分页插件的原理

  • Mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,可以在sql中直接书写带有物理分页的参数来完成物理分页功能,也可以用插件完成。
  • 分页插件的原理是使用Mybatis提供的插件接口,实现自定义分页插件,在插件的拦截方法中,拦截执行的sql,然后重写sql,根据相应的数据库方言,添加对应的物理分页语句和参数。

注:数据库方言,MySQL 是一种方言,Oracle 也是一种方言,MSSQL 也是一种方言,他们之间在遵循 SQL 规范的前提下,都有各自的扩展特性。拿分页来说,MySQL 的分页是用关键字 limit, 而 Oracle 用的是 ROWNUM,MSSQL 可能又是另一种分页方式。

442.Mybatis插件是怎样运行的,如何编写一个插件

443.Mybatis的一级、二级缓存

一级缓存:
在应用程序运行过程中,有可能在一次数据库会话中执行多次查询条件完全相同的sql,一级缓存则是为了优化这一情况,如果是相同的sql,hui优先命中一级缓存,避免直接对数据库进行查询,提高性能。每个会话中都持有Executor,每个Executor中有一个LocalCache,当用户发起查询时,Mybatis根据当前执行的语句生成MapperedStatement,在LocalCache进行查询,如果缓存中有,直接返回给用户,如果没有,查询数据库,结果写入LocalCache,在返回结果给用户。一级缓存有两个级别session(此次会话)和statement(当前sql对象)
在这里插入图片描述

  1. Mybatis一级缓存的生命周期和SqlSession一致。
  2. Mybatis一级缓存内部设计只是一个没有限定容量的HashMap.
  3. Mybatis一级缓存最大范围是SqlSession内部,有多个SqlSession或者分布式环境下,数据库写操作会引起脏数据,此时应将缓存级别设置为Statement。

二级缓存:
二级缓存与一级缓存的机制相同,默认采用PerpetualCache,HashMap存储,但其储存作用域为namespace,并且可以自定义存储源,如Ehcache,如果要开启二级缓存,需要在sql映射文件中添加
二级缓存可实现多个会话之间共享缓存,当二级缓存开启后,数据的查询流程是二级缓存–》一级缓存–》数据库。
在这里插入图片描述
注:对于缓存数据更新机制,当某一个作用域进行了C/U/D操作,默认该作用域下所有select中的缓存将被clear。

444.Mybatis如何执行批量操作

445.Mybatis的xml映射文件和Mybatis内部数据结构之间的映射关系

Mybatis将所有XML配置信息都封装到ALL-In-One重量级对象Configuration内部。在xml映射文件中,标签会被解析为ParameterMap对象,其每个子元素会被解析为ParameterMapping对象。标签会被解析为ResultMap对象,其每个子元素会被解析为ResultMapping对象。每一个、、、标签均会被解析为MappedStatement对象,标签内的sql会被解析为BoundSql对象。

MySQL

一个大佬整理的MySQL基础,半天就入门MySQL----->🤚

446.MySQL中有哪几种锁

  • 表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
  • 行级锁:开销大,加锁慢;会出现死锁;锁定粒度小,发生锁冲突的概率最低,并发度也最高。
  • 页面所:开销和加锁时间介于表锁和行锁之间;会出现死锁;锁定粒度介于表锁和行锁之间,并发度一般。

447.MySQL中有哪些不同的表格

共有5种类型的表格:MyISAM2、Heap3、Merge4、INNODB5、MISAM。

448.MySQL数据库中MySAM和InnoDB的区别

  • InnoDB支持事务,MyISAM不支持;
  • InnoDB支持行级锁,MyISAM支持表级锁;
  • InnoDB支持多版本并发控制(MVVC),MyISAM不支持。
  • InnoDB支持外键,MyISAM不支持。
  • MyISAM支持全文索引,InnoDB部分版本不支持(但可以使用Sphinx插件)

449.InnoDB的四种隔离级别

  1. read uncommited:读到未提交数据。一个事务还没提交时,它做的变更就能被别的事务看到。
  2. read committed:脏读,不可重复读。一个事务提交后,它做的变更才会被其他事务看到。
  3. repeatable read:可重读。一个事务在执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的,在可重复读的隔离级别下,未提交变更对其他事务也是不可见的。
  4. serializable:串行事物。对于同一行记录,读写都会加锁。当出现读写锁冲突时,后访问的事物必须等前一个事务执行完成才能继续执行。

450.CHAR和VARCHAR的区别

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

451.主键和候选键的区别

表格的每一行都有主键唯一标识,一个表只有一个主键。主键也是候选键。

候选键是一个属性或一组属性,用于唯一标识表或关系中的每条记录,但请注意,一个表可能包含多个候选键。此键可以存储与主键相反的NULL值。例如,假设我们有一个名为students的表,其中包含 ID、姓名、出生日期、年龄和地址列。在这里,我们可以找出两个候选键,即 {ID} 和 {Name, DOB}。因此,它阐明了不止一个候选键可用于唯一地标识表或关系。候选键可以被指定为主键,并且可以用于任何外键引用。

主键候选键
定义它是唯一且非空的键,用于唯一标识模式中每个表的记录它也是唯一标识关系或表中记录的唯一键
定位一个表或关系只能有一个主键一个表或关系可以有多个候选键
空值主键的任何列都不能为null候选列可以包含null
客观的它是表或关系的重要组成部分他表示哪个键可以用作主键
采用它可以用作候选键它可以也可以不用作主键
指定不必为任何关系指定主键如果不指定候选键,就不可能会存在关系
例子考虑一个包含列(roll_no.、name、class、DOB、email、mobile)的表“ student ”。这里roll_no列可以作为关系的主键,因为它唯一地标识了学生的记录。roll_no 、mobile和email列可以是给定表中的候选键,因为它们可以唯一标识学生的记录。

452.怎样看到为表格定义的所有索引

SHOW INDEX FROM TABLENAME;

453.like声明中的%和_是什么意思

%对应于0个或更多字符,_只是like语句中的一个字符。

454.列对比运算符是什么

在select语句的列比较中使用=,>,<,AND,OR,LIKE运算符。

455.BLOB和TEXT的区别

BLOB是一个二进制对象,可以容纳可变数量的数据,有四种BLOB对象,他们只是所能容纳的最大长度不同;Text是一个不区分大小写的BLOB,有四种TEXT类型。

BLOBTEXT
TINYBLOBTINYTEXT
BLOBTEXT
MEDIUMBLOBMEDIUMTEXT
LONGBLOBLONGTEXT

BLOB和TEXT之间的唯一区别是在对其值进行排序和比较大小时,对BLOB值区分大小写,TEXT值不区分。

456.MySQL如何优化DISTINCT

  • DISTINCT 关键字的主要作用就是对数据表中一个或多个字段重复的数据进行过滤,只返回其中的一条数据给用户。. DISTINCT 关键字的语法格式为:. SELECT DISTINCT <字段名> FROM <表名>;
  • 将DISTINCT 在所有列上转换为 GROUP BY,然后根据GROUP BY的优化策略进一步优化。
  • 例:
SELECT DISTINCT c1, c2, c3 FROM t1 WHERE c1 > const;
#换成
SELECT c1, c2, c3 FROM t1 WHERE c1 > const GROUP BY c1, c2, c3;

457.如何显示前50行

SELECT * FROM tablename LIMIT 0,50;#实际开发中最好不使用*,写出详细的字段,这样能提高sql效率。

458.可以使用多少列创建索引

任何标准表最多可以创建16个索引列。

459.NOW()和CURRENT_DATE()和CURRENT_TIME()有什么区别

  • current_date()只显示的是当前时间的日期
select current_date() from a ;
结果:2022-12-15
  • current_time()只显示当前时间的时分秒
select CURRENT_TIME() from a ;
14:07:06
  • now()显示全部
select now() from a ;
结果:2021-08-25 14:07:56

460.通用SQL函数

  • CONCAT(A,B)—连接两个字符串值以创建单个字符串输出(拼接)。
  • FORMAT(X,D)—格式化数字,四舍五入,保留D位小数,返回String类型。
  • CURRDATE(),CURRTIME()—返回当前日期或时间。
  • NOW()—将当前日期和时间作为一个值返回。
  • MONTH(),DAY(),YEAR(),WEEK(),WEEKDAY()—从日期值中提取给定数据(需要将字段名传入函数)。
  • HOUR(),MINUTE(),SECOND()—从时间值中提取给定数据。
  • DATEDIFF(A,B)—确定两个日期之间的差异,通常用于计算年龄。
  • SUBTIMES(A,B)—确定两次之间的差异。
  • FROMDAYS(INT)—将整数天数转换为日期值。

461.MySQL中允许有多少个触发器(TRIGGERS)

6个;

  • BEFORE INSERT
  • AFTER INSERT
  • BEFORE UPDATE
  • AFTER UPDATE
  • BEFORE DELETE
  • AFTER DELETE

462.锁的优化策略

  • 读写分离
  • 分段加锁
  • 减少锁持有时间
  • 多个线程尽量以相同的顺序去获取资源
    不能将锁的粒度过于细化,不然可能会出现线程的加锁和释放次数过多,反而影响效率。

463.索引的底层实现原理和优化

B+树

主要是在所有的叶子节点中增加指向下一个叶子节点的指针,因此InnoDB建议为大部分表使用默认的主键作为索引。

464.什么情况下设置了索引但无法使用

  • 以“%”开头的LIKE语句,模糊查询。
  • OR语句前后没有同时使用索引。
  • 数据类型出现隐式转化(如)
explain select * from actor where last_name=1\G;
explain select * from actor where last_name='1'\G; 

#Explain可以模拟优化器执行SQL查询语句,从而知道MySQL是如何处理你的SQL语句的,分析所查询的语句或者表结构的性能瓶颈。
能够获取以下信息:
 - 表的读取顺序
 - 哪些索引可以使用
 - 数据读取操作的操作类型
 - 哪些索引被实际使用
 - 表之间的引用
 - 每张表有多少行被物理查询
 - 语句性能分析

465.实践中如何优化MySQL

  1. SQL语句及索引的优化。
  2. 数据库表结构的优化。
  3. 系统配置的优化。
  4. 硬件的优化。

466.优化数据库的方法

  • 选取最适用的字段属性,尽可能减少定义字段宽度,尽量把字段设置为NOTNULL,例如省份性别之类的字段最好用ENUM。
  • 使用连接(JOIN)来代替子查询。
  • 使用联合(UNION)来代替手动创建临时表。
  • 事务处理。
  • 锁定表、优化事务处理。
  • 适用外键,优化锁定表。
  • 建立索引。
  • 优化查询语句。

467.MySQL中的索引,主键,唯一索引,联合索引有什么区别,对数据库有什么影响

索引是一种特殊的文件(InnDB数据表上的索引是表空间的一个组成部分),他们包含着对数据表里所有记录的引用指针。
普通索引(由关键字KEY或INDEX定义的索引)唯一作用是加快对数据的访问速度。
唯一索引:普通索引允许被索引的数据包含重复的值。如果能确定某个数据列只包含彼此各不相同的值,在为这个数据列创建索引的时候就应该用关键字UNIQUE把他定义为一个唯一索引,唯一索引可以保证数据记录的唯一性。
主键:是一种特殊的唯一索引,在一张表中只能定义一个主键索引,主键用于唯一标识一条记录,使用关键字PRIMERY KEY来创建。
联合索引:可以覆盖多个数据列的索引,如INDEX(columnA,columnB)。
索引可以极大的提高数据的查询速度,但会降低插入、删除、更新表的速度,因为在执行写操作时,还要操作索引文件。

468.数据库中的事务是什么

事务是作为一个单元的一组有序的数据库操作,如果组中的所有操作都成功,则认为事务成功,即使有一个操作失败,事务也不成功,如果所有操作完成,事务则提交,其修改将作用于所有其他数据库进程,如果一个操作失败,则事务将回滚,该事务所有操作的影响都将取消。

事务特性:

  • 原子性。即不可分割性,事务要么全部执行完,要么就全部不执行。
  • 一致性。事务的执行使得数据库从一种正确状态转换成另一种正确状态。
  • 隔离性。事务正确提交前,不允许吧该事务对数据的任何改变提供给任何其他事务。
  • 持久性。事务正确提交后,其结果将永久保存在数据库中。

469.如何防止sql注入漏洞产生

程序开发中不注意sql的书写规范和对特殊字符进行过滤,导致客户端可以通过全局变量POST和GET提交一些sql语句正常执行。
如何防止:

  • 书写sql尽量不省略单引号和双引号。
  • 过滤掉sql语句中的一些关键词:update,insert,delete,select,*。
  • 提高数据库和字段的命名技巧,对一些重要字段根据程序的特点命名,取不易被猜到的。

470.为表中的字段选择合适的数据类型

字段类型优先级,当一个列可以选择多种数据类型时,应该优先考虑数字类型,其次是日期和二进制类型,最后是字符串类型。整型>data,time>enum,char>varchar>blob,text。
同级别的数据类型应该优先占用空间小的数据类型。

471.存储时间相关字段

  • Datatime:以YYYY-MM-DD HH:MM:SS格式存储时期时间,精确到秒,占用8个字节存储空间,datatime类型于时区无关。
  • Timestamp:以时间戳格式存储,占用4个字节,范围1970-1-1到2038-1-19,显示依赖于所指定的时区,默认在第一个列行的数据修改时可以自动得修改timestamp 列的值。
  • Date:占用字节比使用字符串,datatime,int储存要少,使用date只需要三个字节,存储日期月份,还可以利用日期时间函数进行日期间的计算。
  • Time:存储时间部分的数据。

472.索引的目的

  • 快速访问数据表中的特定信息,提高检索速度。
  • 创建唯一索引,保证数据库表中的每一行行数据的唯一性,加速表和表之间的连接。
  • 使用分组和排序子句进行数据检索时,可以显著减少查询中分组和排序的时间。

473.索引的缺点

创建索引和维护索引需要耗费时间,这个时间随着数据量的增加而增加;索引需要占用物理空间,不光是表需要占用数据空间,每个索引也需要占用物理空间;当对表进行增删改的时候索引也需要动态维护,这样就降低了数据的维护速度。

474.建立索引的原则

在最频繁使用的、用以缩小查询范围的字段上建立索引。在频繁使用的、需要排序的字段上建立索引。

475.什么时候不宜建立索引

对于查询中很少涉及的列或者重复值比较多的列,不宜建立索引。对于有一些特殊的数据类型(BLOB,TEXT等),不宜建立索引。

476.MySQL外连接、内连接和自连接的区别

交叉连接:又叫笛卡尔积,指不使用任何条件,直接将一个表的所有记录和另一个表中的所有记录一一匹配。内连接则是指有条件的交叉连接,根据某个条件筛选出符合条件的记录,不符合条件的记录不会出现在结果集中,即内连接只连接匹配的行(hang)。
外连接其结果集中不仅包含符合连接条件的行,而且还会包括左表、右表或两个表中的所有数据行,这三种情况依次称之为左外连接,右外连接和全外连接。左外连接也称左连接,左表为主表,左表中的所有记录都会出现在结果集中,对于那些右表中没有匹配的记录,任然会显示,右边对应的字段值用NULL填充。右外连接又称右连接,右表为主表,右表中所有的记录都会出现在结果集中。全外连接目前mysql不支持。

477.MySQL中的事务回滚机制

事务是用户定义的一个数据库操作序列,这些操作要么全部做,要么全部不做,是一个不可分割的工作单位,事务回滚是指将该事务已经完成的对数据库的更新操作撤销。要同时修改数据库中两个不同的表时,如果他们不是一个事务,当地一个表修改完,可能第二个表修改过程中出现了异常而没能继续修改,此时第一个表已经修改完毕但第二个表依旧是未修改状态。如果他们被设定为一个事务时,当地一个表修改完毕,第二个表出现异常没能修改,第一个表和第二个表都要回到未修改的状态,这就是事务回滚。

478.sql语言包括哪几个部分

  • 数据定义:DDL,Create Table,Alter Table,Drop Table,Create/Drop Index。
  • 数据操纵:DML,insert,update delete。
  • 数据控制:DCL,grant,revoke。
  • 数据查询:DQL,select。

479.完整性约束包括

数据完整性指的是数据的精确和可靠性。

  • 实体完整性:规定表的每一行在表中是唯一的实体。
  • 域完整性:指表中的列必须满足某种特定的数据类型约束,其中约束又包括取值范围、精度等。
  • 参照完整性:指两个表的主键和外键的数据应一致,保证了表之间的数据一致性,防止数据丢失或无意义的数据在数据库中扩散。
  • 用户定义的完整性:不同的关系型数据库系统根据其应用环境的不同,有可能需要一些特殊的约束。用户定义的完整性即是针对某个特定的关系型数据库的约束条件,它反映某一具体应用必须满足的语义要求。

480.mysql数据表在什么情况下容易损坏

  • 服务器突然断电导致数据文件损坏。
  • 强制关机没有事先关闭mysql服务。
  • mysqld进程在写表时被杀掉。
  • 使用myisamchk的同时,mysqld也在操作表。
  • 磁盘故障。
  • 服务器死机。
  • mysql本身的bug。

481.一千万条数据的表,如何分页查询

数据量过大的情况下,limite offset分页会由于扫描数据太多而越往后查询速度越慢,可以配合当前页最后一条id进行查询,select * from Tablename where id > #{id} limit #{limit} ,这种情况id必须是有序的,这也是有序id的好处之一。

482.怎样处理订单表数据量越来越大导致查询缓慢的情况

分库分表。由于历史订单使用率并不高,高频的可能只是近期订单,因此,将订单表按照时间进行拆分,根据数据量的大小考虑按月分表或按年分表,订单id最好包含时间(如根据雪花算法生成的id),此时既能根据订单id直接查询订单记录,也能按照时间进行查询。

483.MySQL数据库存储有一天五万条以上的增量,预计运维三年,怎么优化

  • 设计良好的数据库结构,允许部分数据冗余,尽量避免join查询,提高效率。
  • 选择合适的表字段数据类型和存储殷勤,适当的添加索引。
  • MySQL库主从读写分离。
  • 找规律分表,减少单表中的数据量提高查询速度。
  • 添加缓存机制。
  • 不经常改动的页面将其生成为静态页面。
  • 书写高效率的sql,如将*写为具体的字段。

484.MySQL索引相关的优化

  • 尽量使用主键查询:聚簇索引上存储了全部数据,相比于普通索引查询,减少了回表的消耗。
  • 通过适当的使用联合索引,减少回表判断的消耗。
  • 若频繁查询某一列数据,可以考虑利用覆盖索引避免回表。
  • 联合索引将高频字段放在最左边。

485.简要说一下数据库范式

  • 第一范式:属性不可再分。
  • 第二范式:在第一范式的基础上,要求数据库表中每个实例或行必须可以被唯一的区分,通常需要为表加上一个列,以存储各个实例的唯一标识,这个唯一属性列被称为主键。
  • 第三范式:在第二范式的基础上,要求一个数据库表中不包含其他表中已包含的非主关键字信息(每一列只有一个值;每一行都能区分;每一个表都不包含其他表已经包含的非主键信息)

486.MySQL怎样恢复半个月前的数据

通过整库备份+binlog进行恢复,前提是要有定期整库备份且保存了binlog日志。

487.如何得到受查询影响的行数

select count(id) from user;

488.列设置为自增长id时,如果表中达到了最大值,会发生什么情况

它将会停止递增,任何进一步的插入都将会产生错误,因为密钥已被使用。

489.怎样找出最后一次插入时分配了哪个自动增量

LAST_INSERT_ID将返回由Auto_increment分配的最后一个值,并且不需要指定表名称。

SpringBoot

490.什么是springboot

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

491.spring boot的特征

  • 创建独立的spring应用程序。
  • 内嵌Tomcat,Jetty或Undertow(无需部署war文件)
  • 提供固化的starter依赖项,以简化构建配置。
  • 提供可用于生产的功能。
  • 不需要XML配置。

492.spring boot与spring cloud的区别

spring boot是快速开发的spring框架,spring cloud是完整的微服务框架,spring cloud依赖于spring boot。

493.spring boot的优点

  • 独立运行:
  • 简化配置:
  • 自动配置:
  • 无代码生成和XML配置:
  • 避免大量的Maven导入和各版本冲突:
  • 应用监控:

494.怎样快速创建一个spring boot项目

  • 通过Web界面使用。http://start.spring.io
  • 通过Spring Tool Suite使用。(eclipse)
  • 通过IntelliJ IDEA使用。
  • 使用Spring Boot CLI使用。(命令行工具)

495.spring boot核心(启动类)注解是哪个,它有哪些注解组成

启动类上@SpringBootApplication是spring boot的核心注解。
主要组合包含了以下三个注解:

  • @SpringBootConfiguration:组合了@Configuration注解,实现配置文件的功能。
  • @EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项,如关闭数据源自动配置功能:@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})。
  • @ComponentScan:spring组件扫描。

496.spring boot推荐和默认的日志框架

Spring Boot 支持 Java Util Logging, Log4j2, Lockback 作为日志框架,如果你使用 Starters 启动器,Spring Boot 将使用 Logback 作为默认日志框架,但是不管是那种日志框架他都支持将配置文件输出到控制台或者文件中。

497.如何在spring boot启动时运行一些特定的代码

可以实现接口 ApplicationRunner 或者 CommandLineRunner,这两个接口实现方式一样,它们都只提供了一个 run 方法。

498.spring boot有哪几种读取配置的方式

通过@PropertySource,@Value,@Environment,@ConfigurationProperties注解来绑定变量。

499.运行springboot有哪些方式

  • 打包用命令或者放到容器中运行。
  • 用Maven/Gradle插件运行。
  • main方法运行。

500.spring boot需要独立的容器运行吗

可以不需要,内置了Tomcat,Jetty等容器。

501.开启spring boot特性有哪几种方式

  • 继承spring-boot-starter-parent项目。
  • 导入spring-boot-dependencies项目依赖。

502.什么是yaml

是一个可读性高,用来表达数据序列化的格式。更具结构性。

503.spring boot支持配置文件的格式

.properties

spring.mybatis.username = username

.yml

spring:
	mybatis:
		username: username #后面有值时冒号后要有一个空格

504.spring boot starter的工作原理

spring boot启动时@SpringBootApplication注解会自动去maven中读取每个starter中的spring.factories文件,该文件里配置了所有需要被创建的spring容器中的bean,并且进行自动配置把bean注入SpringContext中(spring Context是spring的配置文件)。

505.spring boot热部署有哪几种方式

  • spring-boot-devtools
  • Spring Loaded
  • Jrebel
  • 模板热部署

506.spring boot事务的使用方式

首先使用注解EnableTransactionManagement开启事务之后,然后再Service方法上添加注解@Transactional。

507.什么是JavaConfig

Spring JavaConfig 是 Spring 社区的产品,它提供了配置 Spring IoC 容器的纯Java 方法。因此它有助于避免使用 XML 配置。使用 JavaConfig 的优点在于:

  • 面向对象的配置。
    由于配置被定义为 JavaConfig 中的类,因此用户可以充分利用 Java 中的面向对象功能。一个配置类可以继承另一个,重写它的@Bean 方法等。

  • 减少或消除 XML 配置。
    基于依赖注入原则的外化配置的好处已被证明。但是,许多开发人员不希望在 XML 和 Java 之间来回切换。JavaConfig 为开发人员提供了一种纯 Java 方法来配置与 XML 配置概念相似的 Spring 容器。从技术角度来讲,只使用 JavaConfig 配置类来配置容器是可行的,但实际上很多人认为将JavaConfig 与 XML 混合匹配是理想的。

  • 类型安全和重构友好。
    JavaConfig 提供了一种类型安全的方法来配置 Spring容器。由于 Java 5.0 对泛型的支持,现在可以按类型而不是按名称检索 bean,不需要任何强制转换或基于字符串的查找。

常用的Java config:

  • @Configuration:在类上打上写下此注解,表示这个类是配置类
  • @ComponentScan:在配置类上添加 @ComponentScan 注解。该注解默认会扫描该类所在的包下所有的配置类,相当于之前的 <context:component-scan >。
  • @Bean:bean的注入:相当于以前的< bean id=“objectMapper” class=“org.codehaus.jackson.map.ObjectMapper” />
  • @EnableWebMvc:相当于xml的<mvc:annotation-driven >
  • @ImportResource: 相当于xml的 < import resource=“applicationContextcache.xml”>

508.spring boot自动配置原理

主要是Spring Boot的启动类上的核心注解SpringBootApplication注解主配置类,有了这个主配置类启动时就会为SpringBoot开启一个@EnableAutoConfiguration注解自动配置功能。
有了这个EnableAutoConfiguration的话就会:

  1. 从配置文件META_INF/Spring.factories加载可能用到的自动配置类。
  2. 去重,并将exclude和excludeName属性携带的类排除。
  3. 过滤,将满足条件(@Conditional)的自动配置类返回。

509.spring boot配置途径

  • 命令行参数。
  • java:comp/env里的JNDI属性(JNDI是 Java 命名与目录接口(Java Naming and Directory Interface),在J2EE规范中是重要的规范之一)。
  • JVM系统属性。
  • 操作系统环境变量。
  • 随机生成的带random.*前缀的属性(在设置其他属性时,可以引用他们,如${random.long})。
  • 应用程序以外的application.properties或者application.yml文件。
  • 打包在程序内的application.properties或者application.yml文件。
  • 通过@PropertySource标注的属性源。
  • 默认属性。
    注:高优先级的配置会覆盖低优先级的配置。

510.配置文件可放位置及优先级

  • 外置,在相当于应用程序运行目录的/config子目录里。
  • 外置,在应用程序运行的目录里。
  • 内置,在config包中。
  • 内置,在Classpath根目录。
  • 还可以在运行项目时指定配置文件
java -jar springbootDemo.jar --spring.config.location=/home/application.yml

511.「bootstrap.yml」 和「application.yml」

bootstrap由父ApplicationContext加载,比application优先级高,配置在应用程序上下文的引导阶段生效。一般来说在SpringCloud配置会使用这个文件,且bootstrap里面的属性不能被覆盖。
application是由ApplicationContext加载,用于spring boot项目的自动化配置。

512.spring boot如何修改端口号

yml:

server :
	port : 8888

properties:

server.port = 8888

命令行:

java -jar springbootDemo.jar --server.port=8888
or
java - Dserver.port=8888 -jar springbootDemo.jar

513.spring boot如何兼容spring项目

在启动类加:
@ImportResource(locations={“classpath:spring.xml”})

514.spring boot启动时都做了什么

  • springboot在启动时从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的指。
  • 将这些值作为自动配置类导入容器,自动配置类就生效,帮我们进行自动配置工作。
  • 整个J2EE的整体解决方案和自动配置都在spring boot-autoconfigure的jar包中。
  • 他会给容器中导入非常多的自动配置类,就是给容器中导入这个场景需要的所有组件,并配置好这些组件。
  • 有了自动配置类,免去了我们手动编写配置注入功能组件等的工作。

515.针对请求访问的几个组合注解

@PatchMapping
@PostMapping
@GetMapping
@PutMapping
@DeleteMapping

516.springboot中的starter

可以理解成对依赖的一种合成,starter会把一个或一套功能相关依赖都包含进来,避免了自己去依赖费事,还有各种包的冲突问题。大大的提升了开发效率。并且相关配置会有一个默认值,如果我们自己去配置,就会覆盖默认值。

517.什么是SpringProfiles

主要用来区分环境,一般来说从开发到生产,经过开发(dev),测试(test),上线(prod)。不同的时刻我们会用不同的配置。Spring Profiles允许用户根据配置文件(dev,test,prod等)来注册bean。它们可以让我们自己选择什么时候用什么配置。

518.不同环境的配置文件

  • application.properties:主配置文件。
  • application-dev.properties:开发环境配置文件。
  • application-test.properties:测试环境配置文件。
  • application-prod.properties:生产环境配置文件。

519.如何激活某个环境的配置

yml:

spring:
	profiles:
		active: dev

properties

spring.profiles.active=dev

命令行:

java -jar springbootDemo.jar --spring.profiles.active=dev

520.spring boot是否可以使用XML配置

springboot推荐使用Java配置,但是也可以使用XML配置,通过@ImportResource注解可以引入一个XML配置。

520.编写测试用例的注解

@SpringBootTest

521.spring boot异常处理相关注解

spring提供了一种使用ControllerAdvice处理异常的方案。通过实现一个ControllerAdvice类,来处理控制器抛出的所有异常。
@ControllerAdvice
@ExceptionHandler

522.spring boot集成Mybatis

mybatis-spring-boot-starter

523.spring boot1.X和spring boot2.X的区别

  • springboot2基于spring5 和JDK8,Spring1更低。
  • 配置变更,参数名等。
  • springboot2的相关插件最低支持版本变高。
  • springboot2配置文件中的中文可直接读取,不用转码。
  • Actuator变化(程序监控器)。
  • CacheManager的变化(缓存管理器)。

524.spring boot多数据源拆分的思路

先在properties配置文件里配置两个数据源,创建分包mapper,使用@ConfigurationProperties读取properties 中的配置,使用@MapperScan注册到对应的mapper包中。

525.spring boot多数据源事务如何管理

  • 第一种方式是在service层的@TransactionManager中使用transaction Manager指定DataSourceConfig中配置的事务。
  • 使用Atomikos(是一个为 Java平台 提供增值服务的并且开源类事务管理器)实现JTA分布式事务。(atomikos也支持与spring事务整合。)

526.如何实现SpringBoot应用程序的安全性

为了实现 Spring Boot 的安全性,我们使用 spring-boot-starter-security 依赖项,并且必须添加安全配置。它只需要很少的代码。配置类将必须扩展WebSecurityConfigurerAdapter 并覆盖其方法。

527. 比较一下Spring Security 和Shiro各自的优缺点

由于SpringBoot官方提供了大量的非常方便的开箱即用的Starter,包括Spring Security的Starter ,使得在 SpringBoot中使用Spring Security变得更加容易,甚至只需要添加一个依赖就可以保护所有的接口,所以,如果是SpringBoot 项目,一般选择 Spring Security 。当然这只是一个建议的组合,单纯从技术上来说,无论怎么组合,都是没有问题的。Shiro和Spring Security相比,主要有如下一些特点:

Spring Security 是一个重量级的安全管理框架;Shiro 则是一个轻量级的安全管理框架。
Spring Security 概念复杂,配置繁琐;Shiro 概念简单、配置简单。
Spring Security 功能强大;Shiro 功能简单。

528.SpringBoot中如何解决跨域问题

跨域可以在前端通过 JSONP 来解决,但是 JSONP 只可以发送 GET 请求,无法发送其他类型的请求,在 RESTful 风格的应用中,就显得非常鸡肋,因此我们推荐在后端通过 (CORS,Crossorigin resource sharing) 来解决跨域问题。这种解决方案并非 Spring Boot 特有的,在传统的SSM 框架中,就可以通过 CORS 来解决跨域问题,只不过之前我们是在 XML 文件中配置 CORS ,现在可以通过实现WebMvcConfigurer接口然后重写addCorsMappings方法解决跨域问题。

@Configuration
public class CrossConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**").
                allowedOrigins("*").
                allowCredentials(true).
                allowedMethods("GET","POST","DELETE","PUT","OPTIONS").
                maxAge(3600);
    }
}

529.Spring Boot 中的监视器是什么

Spring boot actuator 是 spring 启动框架中的重要功能之一。Spring boot 监视器可帮助您访问生产环境中正在运行的应用程序的当前状态。有几个指标必须在生产环境中进行检查和监控。即使一些外部应用程序可能正在使用这些服务来向相关人员触发警报消息。监视器模块公开了一组可直接作为 HTTP URL 访问的REST 端点来检查状态。

530.如何监视所有 Spring Boot 微服务

Spring Boot 提供监视器端点以监控各个微服务的度量。这些端点对于获取有关应用程序的信息(如它们是否已启动)以及它们的组件(如数据库等)是否正常运行很有帮助。但是,使用监视器的一个主要缺点或困难是,我们必须单独打开应用程序的知识点以了解其状态或健康状况。想象一下涉及 50 个应用程序的微服务,管理员将不得不击中所有 50 个应用程序的执行终端。为了帮助我们处理这种情况,我们将使用开源项目。 它建立在 Spring Boot Actuator 之上,它提供了一个 Web UI,使我们能够可视化多个应用程序的度量。

531. SpringBoot性能如何优化

  • 如果项目比较大,类比较多,不使用@SpringBootApplication,采用@Compoment指定扫包范围。
  • 在项目启动时设置JVM初始内存和最大内存相同。
  • 将springboot内置服务器由tomcat设置为undertow。

532.SpringBoot微服务中如何实现 session 共享

在微服务中,一个完整的项目被拆分成多个不相同的独立的服务,各个服务独立部署在不同的服务器上,各自的 ession 被从物理空间上隔离开了,但是经常,我们需要在不同微服务之间共享session ,常见的方案就是 Spring Session + Redis 来实现 session 共享。将所有微服务的session 统一保存在 Redis 上,当各个微服务对 session 有相关的读写操作时,都去操作 Redis 上 的 session 。这样就实现了 session 共享,Spring Session 基于 Spring 中的代理过滤器实现,使得 session 的同步操作对开发人员而言是透明的,非常简便。

533.您使用了哪些 starter maven 依赖项

  • spring-boot-starter-web 嵌入tomcat和web开发需要servlet与jsp支持
  • spring-boot-starter-data-jpa 数据库支持
  • spring-boot-starter-data-redis redis数据库支持
  • spring-boot-starter-data-solr solr支持
  • mybatis-spring-boot-starter 第三方的mybatis集成starter

534.Spring Boot 中如何实现定时任务

  • 使用spring中的@Scheduled注解。
  • 使用第三方框架Quartz。

535. spring-boot-starter-parent 有什么用

  • 定义Java编译版本为1.8。
  • 使用UTF-8格式编码。
  • 继承自spring-boot-dependencies,里面定义了依赖的版本,所以我们在写依赖时不需要写版本号。
  • 执行打包操作的配置。
  • 自动化的资源过滤。
  • 自动化的插件配置。
  • 针对 application.properties 和 application.yml 的资源过滤,包括通过 profile 定义的不同环境的配置文件,例如 application-dev.properties 和 application-dev.yml。

536.SpringBoot如何实现打包

  • 命令行:mvn clean package。
  • 直接通过项目中的maven点击。

537.Spring Boot 打成的 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 ,一个可执行,一个可引用。

Git

git基础----->🤚

Maven

Maven初级----->🤚

Redis

redis基本数据类型常用命令----->🤚

Linux

Java设计模式

JVM

补:

Java基础

1.Java中创建对象有几种方式

(1)new 一个对象
(2)克隆一个对象
(3)类派发一个对象(反射)class.newInstance()
(4)动态加载一个对象(反射)class.forName().newInstance()
(5)构造一个对象(反射)class.getConstructor().newInstance()
(6)反序列化一个对象

2.Object类中的常用方法

(1)getClass方法(获取对象的运行时class对象,class对象就是描述对象所属类的对象,通常与反射联用)
(2)hashcode方法,用于获取对象散列值(默认返回对象的对内存地址)
(3)equals方法,用于比较两个对象引用是否指向同一个对象(子类一般重写此方法)
(4)clone方法,实现对象的浅复制,只有继承了Cloneable接口才可调用该方法(对象内属性地址拷贝后指向同一地址,深拷贝则是重新创建)
(5)toString方法,返回一个String对象(对象的class名称+@+hashcode的十六进制字符串)
(6)notify方法,唤醒在该对象上等待的某个线程
(7)notifyAll方法,唤醒在该对象上等待的所有线程
(8)wait方法,(三个:一个无参,一个有一个参数,一个有两个参数)使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。wait() 方法一直等待,直到获得锁或者被中断。
(9)finalize方法,该方法是保护方法,主要用于在 GC 的时候再次被调用,如果我们实现了这个方法,对象可能在这个方法中再次复活,从而避免被 GC 回收。

3获取一个类对象有哪些方式

(1)通过类.class获取
(2)通过对象.getClass()获取
(3)通过class.forName()获取
(4)通过ClassLoader.loadClass()获取

4.fail_fast是什么

fail-fast是一种错误检测机制,一旦检测到可能发生错误,就立马抛出异常,程序不继续往下执行。当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。
例如:当某一个线程A通过iterator去遍历某集合的过程中,若该集合的内容被其他线程所改变了;那么线程A访问集合时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。

为什么重写了equals后必须重写hashcode方法

举例:hashset不能有重复元素,他最先是根据哈希值判断的,如哈希值相等才会继续用equals判断,当重写了equals方法使其类似于String类的equals的判定规则后,两个对象有可能出现对象equals返回true但哈希值不同的情况,这样hashset里面就会存储相同元素。

悲观锁

什么都加锁

乐观锁

什么都不加锁

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值