JAVA 150道笔试题知识点整理
JAVA 笔试题
整理了几天才整理的题目,都是在笔试或者面试碰到的,好好理解消化下,对你会有帮助,祝你找工作顺利,收到满意的 offer 。
1.Java 基础知识
1.1 Java SE 语法
&和&&的区别
答:& 运算符:两种用法 按位与、逻辑与,&& 运算符:短路与 运算
区别
如果 && 左边的表达式的值是 false,将不会再进行右边的运算,整体直接为 false
而 & 即便左边的表达式的值是false,也会进行右边的运算后,再对整体赋值为falsebreak 和 continue 的区别?
答:break和continue都是用来控制循环的语句。
break用于完全结束一个循环,跳出循环体执行循环后面的语句。
continue用于跳过本次循环,执行下次循环。字符串分为两大类:一类是字符串常量( String );另一类是字符串变量( StringBuffer/StringBuilder )
Object 类的常用方法
• equals():对比两个对象是否相同
• getClass():返回一个对象的运行时类
• finalize():在垃圾收集器执行的时候会调用被回收对象的方法
• hashCode():返回该对象的哈希码值
• toString():返回该对象的字符串描述
• wait():使当前的线程等待
• notify():唤醒在此对象监视器上等待的单个线程
• notifyAll():唤醒在此对象监视器上等待的所有线程
• clone():克隆一个新对象Java 中只能有一个 main 方法吗?
答:一个文件中可以有多个类,可以是多个并列的类,也可以是外部类、内部类结合。一个类中,可以有多个main方法,这是重载,但是public static void main(String[] args)的方法只能有一个。一个类中,可以有main方法,也可以没有main方法,而有一个main()方法的时候,也可以是任意访问权限。因为这个类不一定要执行,可以只是辅助类。
数组命名时名称与 [] 可以随意排列,但声明的二维数组中第一个中括号中 [10][] 必须要有值,它代表的是在该二维数组中有多少个一维数组。
在Java 中,声明一个数组时,不能直接限定数组长度,只有在创建实例化对象时,才能对给定数组长度
在Java中,使用字符串对char数组赋值,必须使用toCharArray()方法进行转换
short s=2;s=s+1; 会报错吗?short s=2;s+=1; 会报错吗?
答:s=s+1 会报错,s+=1 不会报错,因为 s=s+1 会导致 short 类型升级为 int 类型,所以会报错,而 s+=1 还是原来的 short 类型,所以不会报错。
float f=3.4; 会报错吗?为什么?
答:会报错,因为值 3.4 是 double 类型,float 类型级别小于 double 类型
Math.round(11.5)等于多少?Math.round(-11.5)又等于多少?
答:Math.round(11.5)的返回值是 12,Math.round(-11.5)返回值是 -11。
“==” 和 equals 的区别是什么?
答:== 对基本类型来说是值比较,比较的是值是否相同;对于引用类型来说是比较的是引用,比较的是对象的地址值是否相同;而 equals 默认情况下是引用比较,只是很多类重写了 equals 方法,比如 String、Integer 等把它变成了值比较,所以一般情况下 equals 比较的是值是否相等
hashcode 和 equals 的约定关系
答:如果两个对象相等(equals),那么他们一定有相同的哈希值(hash code)。
如果两个对象的哈希值相等,那么这两个对象有可能相等也有可能不相等。(需要再通过equals来判断)switch 能否用于 byte 类型的判断上?能否用于 long 类型的判断上?
答:switch 支持 byte 类型的判断,不支持 long 类型的判断,switch 支持的全部类型(JDK 8):char、byte、short、Charachter、Byte、Short、Integer、String、enumint 和Integer有什么区别
答:int 是基本数据类型,Integer 是 int 的包装类;int 的初始值为 0,Integer 的初始值为null。
int 类型直接存储数值,Integer 需要实例化对象,指向对象的地址。
两个都是封装 (Integer) 类,都是 new 出来的,比较肯定不相等。因为对象的内存地址不一样。
两个都是封装 (Integer) 类,都不是 new 出来的 (Integer a = 1) ,如果值在-128~127之间,不在该范围,就会 new 一个,那就相等,否则不相等。
如果是封装类和基本类型进行比较,只要数值相等那就相等,否则就不相等。因为封装类和基本数据类型进行比较的时候会有一个自动装/拆箱操作。
如果两个都是基本数据类型,如果数值相等,那就相等;否则不相等
基本数据类型是没有静态方法的,但是基本数据类型的包装类有静态方法
基本数据类型含有的字节数:
整数类型:byte(1个字节)short(2个字节)int(4个字节)long(8个字节)
字符类型:char(2个字节)
浮点类型:float(4个字节)double(8个字节)类型转换:
低级向高级是隐式类型转换,高级向低级必须强制类型转换 byte<char<short<int<long<float<double
Java语言使用的是 Unicode 字符集。而 ASCII 是国际上使用最广泛的字符编码;
final, finally, finalize的区别
final 用来声明属性,方法和类,分别表示属性不可变,方法不可重写,类不可继承
finally 是异常处理结构的一部分,表示任何情况下,都会执行(会在return前面先执行)
finalize 是Object类的一个方法,在垃圾回收期执行的时候调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源回收,例如关闭文件等。关于final的重要知识点:
• final关键字可以用来修饰成员变量、本地变量、方法以及类。执行子类的静态成员;
• final成员变量必须在声明的时候初始化或者在构造器中初始化,否则就会报编译错误。
• 本地变量必须在声明时赋值。
• 在匿名类中所有变量都必须是final变量。
• 不能够对final变量再次赋值,final方法不能被重写,final类不能被继承。
• 没有在声明时初始化final变量的称为空白final变量(blank final variable),它们必须在构造器中始化,或者调用this()初始化。
• 使用 final 关键字修饰一个变量时,是指引用变量不能变,引用变量所指向的对象中的内容还是可以改变的finally不会执行的四种情况
• finally块中发生了异常
• 程序所在线程死亡
• 在前面的代码中用了System.exit()
• 关闭了CPU什么是 finalize 方法?
答:finalize是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源的回收,例如关闭文件等。
这个方法一个对象只能执行一次,只能在第一次进入被回收的队列,而且对象所属于的类重写了finalize方法才会被执行。第二次进入回收队列的时候,不会再执行其finalize方法,而是直接被二次标记,在下一次GC的时候被GC。常见字符的 ASCII 码值如下:空格的 ASCII 码值为 32 ;数字 0 到 9 的 ASCII 码值分别为 48 到 57 ;大写字母 “A” 到 “Z” 的 ASCII 码值分别为 65 到 90 ;小写字母 “a” 到 “z” 的 ASCII 码值分别为 97 到 122 。
在 Java 中一个 Unicode 占 2 个字节(byte),一个字节等于8比特位(bit),所以每个 Unicode 码占用16个比特位。
Java 五大原则
• 单一职责原则(SRP)
• 开放封闭原则(OCP)
• 里氏替换原则(LSP)
• 依赖倒置原则(DIP)
• 接口隔离原则(ISP)常见的 OOM 原因有哪些?
答:常见的 OOM 原因有以下几个:
• 数据库资源没有关闭;
• 加载特别大的图片;
• 递归次数过多,并一直操作未释放的变量。
1.2 Java OOP
面向对象都有哪些特性以及含义?
答:面向对象有四大特性,分别是:
继承:基于已存在的类构造一个新类,继承已存在的类就是复用(继承)这些类的方法和域。已存在的类称为超类、基类或父类;新类称为子类、派生类或者孩子类。
封装:把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。
多态:允许不同子类型的对象对同一消息作出不同的响应。多态性分为编译时多态(方法重载 overload) 和运行时多态(方法重写 override)
抽象:将一类对象的共同特征总结出来构造类的过程,抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。Abstract 类的子类必须实现其超类的所有 abstract 方法对么?
答:错, 一个抽象类派生子类,子类通常提供了实现父类的抽象方法。如果它不实现,那么子类也必须声明 abstract 类。
子类可以继承父类的构造方法吗?
答:不可以,子类无法继承父类的构造方法。
成员变量可以在类外面使用吗?
答:不可以,局部变量只在定义它的代码块或者函数内部可见,跟类的成员变量一样,需要指定对象才能引用,外部需要 “对象名.变量名” 来引用。
abstract 为什么不能修饰属性?
答:abstract 不能够修饰属性,被 abstract 修饰的内容都是暂未被实现的,比如类(修饰类,不能被初始化)、方法(修饰方法不能被实现),属性之所以不能被 abstract 修饰,是因为属性不存在"尚未被实现"的状态。比如你可能会联想到 int age ; 或是 String name ; 但可惜的是,在申明变量时,int 会默认给 age 赋予初始值 0 ,String 会默认给 name 赋予初始值""。因此属性达不到"尚未被实现的状态",从而不能被 abstract 修饰。
覆盖和重载的区别如下:
覆盖: 子类对父类方法的一种重写,只能比父类抛出更少的异常,访问权限不能比父类的小,被覆盖的方法不能是 private ,否则只是在子类中重新定义了一个方法。
重载: 同一个类中可以有多个名称相同的方法,但这些方法的参数列表各不相同。
如何区分重载函数?函数可以根据返回类型来区分重载函数吗?
答:为了区分类中重载的同名的不同方法,要求:采用不同的形式参数列表,与不同的返回值类型无关。
Java重载的时候以参数个数和类型作为区分,方法名相同,返回类型可以相同也可以不同,但 不以返回类型作为区分 。因为调用时不能指定类型信息,编译器不知道你要调用哪个函数;例如 float max(int a, int b); int max(int a, int b); 当调用 max(1,2) 时无法确定调用的是哪个方法。初始化过程顺序
执行父类的带参构造前要先对父类中的对象进行初始化
初始化过程是这样的:
首先,初始化父类中的静态成员变量和静态代码块,按照在程序中出现的顺序初始化
然后,初始化子类中的静态成员变量和静态代码块,按照在程序中出现的顺序初始化
其次,初始化父类的普通成员变量和代码块,在执行父类的构造方法
最后,初始化子类的普通成员变量和代码块,在执行子类的构造方法接口中的成员变量和方法默认是什么?
答:Java 接口中的成员变量默认为(public static final)、成员方法为(public abstract)
抽象类和接口的区别
接口(interface)可以说成是抽象类的一种特例,接口中的所有方法都必须是抽象的。
接口中的方法定义默认为public abstract 类型,接口中的成员变量类型默认为public static final。另外,接口和抽象类在方法上有区别:
区别 | 抽象类 | 接口 |
---|---|---|
构造方法 | 可以有构造方法 | 不能有构造方法 |
普通方法 | 可以包含非抽象的普通方法 | 所有方法必须都是抽象的,不能有非抽象的普通方法 |
抽象方法访问类型 | 访问类型可以是public,protected和 default 类型 | 只能为 public abstract 类型 |
静态方法 | 可以包含静态方法 | 不能包含静态方法(JDK 8.0之后可以有静态方法) |
静态成员变量访问类型 | 访问类型可以任意 | 变量只能是 public static final 类型,并且默认即为 public static final 类型 |
扩展性 | 一个类只能继承一个抽象类 | 一个类可以实现多个接口 |
抽象的方法是否可同时是静态(static)的, 是否可同时是本地方法(native)的,是否可同时是同步方法(synchronized)的。
答:都不可以。抽象方法需要子类重写,而静态的方法是无法被重写的,因此二者是矛盾的。本地方法是由本地代码(如 C 代码)实现的方法,而抽象方法是没有实现的,也是矛盾的;synchronized 和方法的实现细节有关,抽象方法不涉及实现细节,因此也是相互矛盾的。
使用匿名内部类时,必须继承一个类或实现一个接口,匿名内部类由于没有名字,因此不能定义构造函数,匿名内部类中不能含有静态成员变量和静态方法。
内部类可以是静态 static 的,也可用 public,default,protected 和 private 修饰,外部类的修饰符只能是 public,abstract,final
什么是构造方法,有什么特点?
答:构造方法 也叫构造器或构造函数,是类在实例化时自动会调用的,用于预处理。主要作用是完成对象的初始化工作
构造方法八大原则
1、构造方法必须与类的名字相同,并且不能有返回值(返回值也不能为void)
2、每个类可以有多个构造方法。当开发人员没有提供构造方法时,编译器在把源代码编译成字节码的过程中会提供一个没有参数的默认构造方法,但该构造方法不会执行任何代码。如果开发人员提供了构造方法,那么编译器就不会再创建默认的构造方法;此外,默认构造器的修饰符只与当前类的修饰符有关(例如如果一个类被定义为public,那么它的构造方法也是public)
3、构造方法可以有0个、1个或1个以上的参数
4、构造方法总是伴随着new操作一起调用,不能由程序的编写者直接调用,必须要由系统调用。
5、构造方法在对象实例化时会被自动调用,且只运行一次,而普通的方法是在程序执行到它时才被调用,可以被该对象调用多次
6、构造方法不能被继承,因此,它不能被覆盖,但是构造方法能够被重载,可以使用不同的参数个数或参数类型来定义多个构造方法
7、子类可以通过关键字super来显式地调用父类的构造方法,当父类没有提供无参数的构造方法时,子类的构造方法中必须显式地调用父类的构造方法,如果父类中提供了无参数的构造方法,此时子类的构造方法就可以不显式地调用父类的构造方法,在这种情况下,编译器会默认调用父类的无参数的构造方法。当有父类时,在实例化对象时,会首先执行父类的构造方法,然后才执行子类的构造方法
8、构造方法可以私有,外部无法使用私有构造方法创建对象。
什么是静态方法,有什么特点?
答:Java 中用 static 修饰符修饰的方法被称为静态方法,static 静态方法是属于整个类的类方法。不用 static 修饰符限定的方法,是属于某个具体类对象的方法。以下是 static 方法的特点:
1、引用静态方法时,可以使用对象名做前缀,也可以使用类名做前缀
2、static 方法不能被覆盖,也就是说,这个类的子类,不能有相同名、相同参数的方法
3、static 方法只能访问 static 方法/数据成员,不能访问非 static 方法/数据成员,但非 static 方法可以访问 static 方法/数据成员
4、main 方法是静态方法。在 Java 的每个 Application 程序中,都必须有且只能有一个 main 方法,它是 Application 程序运行的入口点。
5、static 方法是属于整个类的,它在内存中的代码段将随着类的定义而分配和装载。而非 static 的方法是属于某个对象的方法,在这个对象创建时,在对象的内存中拥有这个方法的专用代码段
6、static 方法中不能使用实例成员变量和实例方法
7、static 方法中不能使用 this 和 super
如果类没有构造方法,编译器会自动生成构造方法吗?
答:如果类没有构造方法,JVM会生成一个默认构造方法,如果定义了任意类型的构造方法,编译器都不会自动生成构造方法
static 可以修饰局部变量吗?
答:被static修饰的变量称为静态变量,静态变量属于整个类,而局部变量属于方法,只在该方法内有效,所以static不能修饰局部变量
静态变量会默认赋初值,局部变量和final声明的变量必须手动赋初值,静态方法中不能调用对象的变量,因为静态方法在类加载时就初始化,对象变量需要在新建对象后才能使用
阐述静态变量和实例变量的区别?
静态变量:是被 static 修饰符修饰的变量,也称为类变量,属于类,不属于类的任何一个对象,一个类不管创建多少个对象,静态变量在内存中有且仅有一个拷贝。
实例变量:必须依存于某一实例,需要先创建对象然后通过对象才能访问到。静态变量可以实现让多个对象共享内存。
讲讲对反射的理解
答:Java 中的反射首先是能够获取到 Java 中要获取类的字节码,获取字节码有三种方法:①Class.forName(className) ②类名.class ③this.getClass()。然后将字节码中的方法,变量,构造函数等映射成相应的Method、Filed、Constructor等类,这些类提供了丰富的方法可以被我们所使用。
反射的功能有什么?
答:Java反射机制主要提供了以下功能:
在运行时判断任意一个对象所属的类;
在运行时构造任意一个类的对象;
在运行时判断任意一个类所具有的成员变量和方法;
在运行时调用任意一个对象的方法;
生成动态代理。
1.3 Java String 语法
String 不管在笔试还是面试,出场率极高,所有这里单独进行归纳
String 是最基本的数据类型吗?
答:不是,基本类型包括:byte,short,int,long,float,double,boolean,char;
而String属于字符串,是final修饰的java类,是引用类型,引用类型:类、接口、数组等等String 是否可以被继承?为什么?
答:String 不能被继承。因为 String 被声明为 final(最终类),所以不能被继承
String str1 = “helloworld”; String str2 = “hello” + new String(“world”),str1 和 str2 分别存放在哪?
答: str1 存储在常量区,str2的 world 是在堆空间申请的另外一块存储空间
String s = new String(“hello”) 创建了几个对象?
答:总共创建了两个对象,一个是字符串 “hello”,另一个是指向字符串的变量 s
String str = “hello” 与 String str = new String(“hello”)意思是一样的吗?
答:不一样,因为内存的分配方式不一样,String str="hello"的方式,Java 虚拟机会将其分配到常量池中;而 String str=new String(“hello”) 则会被分到堆内存中
String 类常用的基本操作方法有哪些?
equals():进行相等判断,区分大小写
equalsIgnore():进行相等判断,不区分大小写
compareTo():判断两个字符串的大小(按照字符编码比较)
contains():判断指定的内容是否存在
indexOf():由前向后查找指定字符串的位置,查找到返回位置(第一个字母)索引,找不到返回 -1
replace():字符串替换
substring():截取字符串
split():分割字符串,返回一个分割后的字符串数组
concat():字符串连接,与 “+”类似
toLower()/toUpper():转大/小写
trim():去掉字符串中左右两边的空格
length():取得字符串长度
intern():数据入池
isEmpty():判断是否是空字符串(不是 null,而是"",长度 0)在方法内对 String 修改的时候,值是否会被改变?
答:不会,String 为不可变类型,在方法内对 String 修改的时候,相当修改传递过来的是一个 String 副本,所以 String 本身的值是不会被修改的,而 StringBuffer 为可变类型,参数传递过来的是对象的引用,对其修改它本身就会发生改变
String 对象的 intern() 有什么作用?
答:intern() 方法用于查找常量池中是否存在该字符值,如果常量池中不存在则现在常量池中创建,如果已经存在则直接返回
String、StringBuffer、StringBuild 的区别?
区别 | String | StringBuffer | StringBuild |
---|---|---|---|
值可变 | 值不可变 | 值可变 | 值可变 |
线程安全 | 非线程安全类 | 线程安全类 | 非线程安全类 |
性能 | 使用了 synchronized 保障了线程的安全,性能较差 | 比 StringBuffer 高 | |
实例化方式 | 可以通过构造函数或者直接赋值 | 只能通过构造函数 | 只能通过构造函数 |
使用场景 | 适合少量的字符串操作的情况 | 适用于多线程下载字符缓冲区进行大量操作的情况 | 适用于单线程下载字符缓冲区进行大量操作的情况 |
String 不可变性都有哪些好处?
答:不可变的好处如下:
只有当字符串是不可变的,字符串常量池才能实现,字符串池的实现可以在运行时节约很多堆空间,因为不同的字符串变量都指向池中的同一个字符串;
可以避免一些安全漏洞,比如在 Socket 编程中,主机名和端口都是以字符串的形式传入,因为字符串是不可变的,所以它的值是不可改变的,否则黑客们可以钻到空子,改变字符串指向的对象的值,造成安全漏洞;s
适合做缓存的 key,因为字符串是不可变的,所以在它创建的时候哈希值就被缓存了,不需要重新计算速度更快,所以字符串很适合作缓存的中的 key
1.4 Java IO
Java 中有几种类型的流
按照流的方法:输入流(InputStream)和输出流(OutputStream)
按照实现功能:节点流(FileReader)和处理流(BufferedReader)
按照处理数据:字节流(字节流继承于InputStream和OutputStream)和字符流(InputStreamReader和OutputStreamWriter)字节流如何转为字符流
字节输入流转字符输入流通过 InputStreamReader 实现,该类的构造函数可以传入InputStream 对象
字节输出流转字符输出流通过 OutputStreamWriter 实现,该类的构造函数可以传入OutputStream 对象字节流和字符流的区别
区别 | 字节流 | 字符流 |
---|---|---|
读取数据 | 字节流读取的时候,读到一个字节就返回一个字节 | 字符流使用了字节流读到一个或多个字节时。先去查指定的编码表,将查到的字符返回 |
处理数据 | 字节流可以处理所有类型数据,如:图片,MP3,AVI 视频文件 | 字符流只能处理字符数据。只要是处理纯文本数据,就要优先考虑使用字符流 |
操作数据 | 字节流主要是操作 byte类型数据,以 byte 数组为准,主要操作类就是OutputStream、InputStream;字节流处理单元为1个字节,操作字节和字节数组 | 字符流处理的单元为2个字节的Unicode字符,分别操作字符、字符数组或字符串 |
什么是java序列化,如何实现java序列化?
答:序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决在对对象流进行读写操作时所引发的问题。
序列化的实现:将需要被序列化的类实现 Serializable 接口,该接口没有需要实现的方法,implements Serializable 只是为了标注该对象是可被序列化的
1.5 Java 多线程
进程和线程的区别
进程:具有一定独立功能的程序关于某个数据集合上的一次运行活动,是操作系统进行资源分配和调度的一个独立单位。
线程:是进程的一个实体,是cpu调度和分派的基本单位,是比进程更小的可以独立运行的基本单位。特点:线程的划分尺度小于进程,这使多线程程序拥有高并发性,进程在运行时各自内存单元相互独立,线程之间内存共享,这使多线程编程可以拥有更好的性能和用户体验
多线程的创建方式
继承 Thread 类:Thread 本质上也是实现了 Runnable 接口的一个实例,代表一个线程的实例,并且,启动一个线程的唯一方法就是通过 Thread 类的 start() 实例方法。这种方式实现多线程简单,通过自己的类直接 extends Thread ,再重写 run() 方法,就可以启动新线程并执行自定义的 run() 方法。
public class MyThread extends Thread {
public void run() {
System.out.println("MyThread.run()");
}
public static void main(String[] args) {
MyThread myThread = new MyThread ();
myThread.start();
}
}
实现 Runnable 接口的方式实现多线程,并且实例化 Thread ,传入自己的 Thread 实例,调用 run() 方法。
public class MyThread implements Runnable {
public void run() {
System.out.println("MyThread.run()");
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
thread.start();
}
}
线程5个状态:
新建状态:在程序中用构造方法创建一个线程对象后,新的线程对象便处于新建状态,此时,它已经有相应的内存空间和其他资源,但还处于不可运行状态。
就绪状态:新建线程对象后,调用该线程的 start() 方法就可以启动线程。当线程启动后,线程进入就绪状态。
运行状态:当就绪状态的线程被调用并获得处理器资源时,线程就进入了运行状态。此时,自动调用该线程对象的 run() 方法。
堵塞状态:在特殊情况下,被人为挂起或需要执行耗时的输入输出操作时,将让出 CPU 并暂时终止自己的执行,进入堵塞状态。
终止状态:线程调用 stop() 方法时或 run() 方法执行结束后,就处于终止状态。说出以下线程方法的涵义
wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;
sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理InterruptedException异常;
notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关;
notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;
补上一条线程状态以及状态之间的转换
通过实现 Runnable 接口或继承 Thread 类可以得到一个线程类,通过 new 一个实例,线程就进入到了 新建状态,调用 start 方法就进入到可运行状态(就绪状态),如果被 os 选中,获得时间片,就会进入运行状态。此时,对运行中的线程索要调用的方法进行解析:
调用 yield 方法,就有可能让出当前线程,至于让不让,取决于 os 的调度。
调用 sleep 方法,则会进入堵塞状态, 只会让出 CPU ,不会放弃锁,sleep 方法结束,就重新回到可运行状态。
调用 synchronized 方法/方法块,如果未获得到锁,也会进入到堵塞状态,同时进入到锁对象的锁池中。
调用 wait 方法,被放入到等待队列中,处于等待队列的线程 如果wait 方法时间到或者被 notify/notifyAll 唤醒,则会被放入锁池当中,处于锁池的线程一旦获得到锁,则再次进入可运行状态,被 os 选择,进入运行状态。
当运行状态中的线程执行完毕或异常退出,线程就进入了死亡状态。
- wait 和 sleep 方法的区别
区别 | wait | sleep |
---|---|---|
方法 | 属于Object类的方法 | 属于Thread类的方法 |
使用场景 | 只能在synchronized方法或者synchronized块中使用 | 可以在任何地方使用 |
本质区别 | 不仅会让出CPU,还会释放已经占有的同步资源锁 | 只会让出CPU,不会导致锁行为的改变 |
作用 | 通常被用于线程间交互 | 通常被用于暂停执行 |
线程的启动方式
答:线程的启动方式只能通过 start 这种方式启动才能真正的实现多线程的效果,使线程所代表的虚拟处理机处于可运行状态,这意味着它可以由 JVM 调度并执行,这并不意味着线程就会立即运行。
如果是手动调用 run() 方法和普通方法调用没有区别,run()方法是线程启动后要进行回调(callback)的方法。
线程结束的三个原因:
1、run方法执行完成,线程正常结束
2、线程抛出一个未捕获的Exception或者Error
3、直接调用该线程的stop方法结束线程(不建议使用,容易导致死锁)volatile 与 synchronized 的区别:
区别 | volatile | synchronized |
---|---|---|
本质区别 | 告诉 JVM 当前变量在寄存器中的值是不确定的,需要从主存中读取 | 锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住 |
使用级别 | 变量级别 | 变量,方法,代码段 |
实现 | 仅能实现变量的修改可见性,但不具备原子特性 | 可以保证变量的修改可见性和原子性 |
线程堵塞 | 不会造成线程的阻塞 | 可能会造成线程的阻塞 |
标记变量 | 标记的变量不会被编译器优化 | 标记的变量可以被编译器优化 |
线程函数 join 的作用
答:Thread.join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B
notify()和 notifyAll()有什么区别?
notifyAll()会唤醒所有的线程,notify()之后唤醒一个线程。notifyAll() 调用后,会将全部线程由等待池移到锁池,然后参与锁的竞争,竞争成功则继续执行,如果不成功则留在锁池等待锁被释放后再次参与竞争。而 notify()只会唤醒一个线程,具体唤醒哪一个线程由虚拟机控制。
什么是守护线程?
答:守护线程是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。可以通过调用 t.setDaemon(true) 将线程转换为守护线程;在 Java 中垃圾回收线程就是特殊的守护线程。
什么是死锁?
答:死锁就是多个进程去抢夺资源或者进行通信而发生堵塞的现象。举个例子,当线程 A 持有独占锁a,并尝试去获取独占锁 b 的同时,线程 B 持有独占锁 b,并尝试获取独占锁 a 的情况下,就会发生 AB 两个线程由于互相持有对方需要的锁,而发生的阻塞现象,我们称为死锁。wait() 表示进入等待状态,释放当前的锁让出 CPU 资源,并且只能等程序执行 notify()/notifyAll() 方法才会被重写唤醒。
死锁产生的必要条件,以及如何防止死锁?
互斥条件:线程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某 资源仅为一个线程所占有。此时若有其他线程请求该资源,则请求线程只能等待。
不剥夺条件:线程所获得的资源在未使用完毕之前,不能被其他线程强行夺走,即只能由获得该资源的线程自己来释放(只能是主动释放)。
请求和保持条件:线程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他线程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
循环等待条件:存在一种线程资源的循环等待链,链中每一个线程已获得的资源同时被链中下一个线程所请求。防止死锁
1、尽量使用 try Lock(long timeout,TimeUnit unit)的方法(ReentrantLock,ReentrantReadWriteLock)
2、设置超时时间,超时可以退出防止死锁。
3、尽量使用 Java.util.concurrent 并发类代替自己手写锁。
4、尽量不要几个功能用同一把锁。
5、尽量减少同步的代码块。多线程中 synchronized 锁升级的原理是什么?
synchronized 锁升级原理:在锁对象的对象头里面有一个 threadId 字段。
在第一次访问的时候 threadId 为空,JVM 让其持有偏向锁,并将 threadId 设置为其线程 id;再次进入的时候会先判断 threadId 是否与其线程 id 一致,如果一致则可以直接使用此对象,如果不一致,则升级偏向锁为轻量级锁;通过自旋循环一定次数来获取锁,执行一定次数之后,如果还没有正常获取到要使用的对象,此时就会把锁从轻量级升级为重量级锁,此过程就构成了 synchronized 锁的升级。锁的升级的目的:锁升级是为了减低了锁带来的性能消耗。在 Java 6 之后优化 synchronized 的实现方式,使用了偏向锁升级为轻量级锁再升级到重量级锁的方式,从而减低了锁带来的性能消耗。
在 Java 程序中怎么保证多线程的运行安全?
方法一:使用安全类,比如 Java. util. concurrent 下的类。
方法二:使用自动锁 synchronized。
方法三:使用手动锁 Lock。
手动锁 Java 示例代码如下:Lock lock = new ReentrantLock();
lock. lock();
try {
System. out. println("获得锁");
} catch (Exception e) {
// TODO: handle exception
} finally {
System. out. println("释放锁");
lock. unlock();
}
synchronized 和 Lock 有什么区别?
区别 | synchronized | Lock |
---|---|---|
加锁 | 可以给类、方法、代码块加锁 | 只能给代码块加锁 |
获取/释放锁 | 不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁,不会造成死锁 | 需要自己加锁和释放锁,如果使用不当没有 unLock()去释放锁就会造成死锁 |
扩展 | 通过 Lock 可以知道有没有成功获取锁 | synchronized 却无法办到 |
ThreadLocal 是什么?有哪些使用场景?
答:ThreadLocal 为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本
ThreadLocal 的经典使用场景:数据库连接和 session 管理等
什么是线程池?
答:事先将多个线程对象放到一个容器中,当使用的时候就不用 new 线程而是直接去池中拿线程即可,节省了开辟子线程的时间,提高代码的执行效率。
说说常用的线程池。
newSingleThreadExecutor:创建一个单线程的线程池,此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
newFixedThreadPool:创建固定大小的线程池,每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。
newCachedThreadPool:创建一个可缓存的线程池,此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
newScheduledThreadPool:创建一个大小无限的线程池,此线程池支持定时以及周期性执行任务的需求。
newSingleThreadExecutor:创建一个单线程的线程池。此线程池支持定时以及周期性执行任务的需求。线程池能够带来的好处
1、降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
2、提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
3、提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
1.6 异常
Exception 和 Error 有什么区别?
答:Exception 和 Error 都属于 Throwable 的子类,在 Java 中只有 Throwable 及其之类才能被捕获或抛出,它们的区别如下:
区别 | Exception | Error |
---|---|---|
含义 | 程序正常运行中,可以预期的意外情况,并且可以使用 try/catch 进行捕获处理的 | 突发的非正常情况,一般是指与虚拟机相关的问题,通常是不可以恢复的,只能重启系统解决 |
分类 | 分为 运行时异常(Runtime Exception)和 受检查的异常(Checked Exception) | |
捕获 | 非运行时异常需要我们自己补获,要么用 try/catch 捕获,要么用 throws 字句声明抛出,否则编译不会通过;而运行异常是程序运行时由虚拟机帮助我们补获 | 无法捕获 |
例子 | 运行时异常包括数组的溢出,内存的溢出空指针,分母为0等 ;非运行时异常包括 IOException、SqlException | java 虚拟机内存溢出,系统崩溃,虚拟机错误,内存空间不足 |
throw 和 throws 的区别是什么?
答:它们的区别如下:
区别 | throw | throws |
---|---|---|
用途 | 用在方法体内,表示抛出异常,由方法体内的语句处理 | 用在方法声明的后面,该方法的调用者要对异常进行处理 |
抛出异常 | 具体向外抛出异常的动作,所以它抛出的是一个异常实例,执行 throw 一定是抛出了某种异常 | 声明这个方法会抛出某种类型的异常,让使用者知道需要捕获的异常的类型;可能会出现某种异常,并不一定会发生这种异常 |
线程抛InterruptedException的代表方法有:
java.lang.Object 类的 wait 方法
java.lang.Thread 类的 sleep 方法
java.lang.Thread 类的 join 方法常见的运行时异常和非运行异常有哪些?
答:运行时异常不需要程序员去处理,当异常出现时,JVM会帮助处理。常见的运行时异常有:
ClassCastException(类转换异常)
ClassNotFoundException(指定类找不到异常)
IndexOutOfBoundsException(数组越界异常)
NullPointerException(空指针异常)
ArrayStoreException(数组存储异常,即数组存储类型不一致)
还有IO操作的BufferOverflowException异常非运行异常需要程序员手动去捕获或者抛出异常进行显示的处理,因为Java认为Checked异常都是可以被修复的异常。常见的异常有:IOException、SqlException
1.7 Java 集合
- 说出ArrayList, Vector, LinkedList的存储性能和特性
区别 | ArrayList | Vector | LinkedList |
---|---|---|---|
存储方式 | 动态数组 | 动态数组 | 双向链表 |
线程安全 | 不是 | 是 | 不是 |
查找速度 | 快,随机访问效率高 | 慢,需要进行前、后遍历 | |
增删元素 | 慢,涉及元素的移动 | 快,只需记录元素前、后项 | |
扩容 | 是原来的50% | 默认增长为原来一倍 |
- HashMap 和Hashtable的区别
区别 | HashMap | HashTable |
---|---|---|
本质 | 实现的map接口 | 实现的map接口 |
线程安全 | 是 | 不是 |
执行效率 | 高于 HashTable | 较慢 |
Null 键值插入 | 允许空的键值插入(只能允许一个,多个不行) | 不允许 |
实现同步 | 被多个线程访问的时候需要自己为它的方法实现同步 | 多个线程访问时不需要自己为它的方法实现同步 |
下列关于容器集合类的说法正确的是 C (出场蛮高的)
A.LinkedList继承自List
B.AbstractSet继承自Set
C.HashSet继承自AbstractSet
D.WeakMap继承自HashMap
解析:
LinkedList是继承自AbstractSequentialList(抽象类,实现了List接口)的,并且实现了 List 接口
AbstractSet是实现了Set接口的,本身是一个抽象类。继承自AbstractCollection(抽象类,实现了Collection接口)。
HashSet是继承自AbstractSet,实现了Set接口。
WeakMap不存在于java集合框架的。只有一个叫做WeakHashMap(继承自AbstractMap)。Collection 和 Map 存放的是什么?
答:Collection中存放的是一组各自独立的对象,Map 是键值对集合,存储的数据是没有顺序的,键不能重复,值可重复。List和Set都是Collection的子接口,List是一个有序可重复列表,Set是一个无序不重复集,但元素在集合中的位置由元素的 hashCode 决定,位置是固定的。而Array是数组,并不继承Collection接口。
Collection 和 Collections 有什么区别?
Collection 是一个集合接口,它提供了对集合对象进行基本操作的通用接口方法,所有集合都是它的子类,比如 List、Set 等。
Collections 是一个包装类,包含了很多静态方法,不能被实例化,就像一个工具类,比如提供的排序方法: Collections. sort(list)。Iterator 和 ListIterator 有什么区别?
Iterator 可以遍历 Set 和 List 集合,而 ListIterator 只能遍历 List。
Iterator 只能单向遍历,而 ListIterator 可以双向遍历(向前/后遍历)。
ListIterator 从 Iterator 接口继承,然后添加了一些额外的功能,比如添加一个元素、替换一个元素、获取前面或后面元素的索引位置。List、Map、Set的实现类
List 接口有三个实现类:
LinkedList:基于链表实现,链表内存是散乱的,每一个元素存储本身内存地址的同时还存储下一个元素的地址。链表增删快,查找慢;
ArrayList:基于数组实现,非线程安全的,效率高,便于索引,但不便于插入删除;
Vector:基于数组实现,线程安全的,效率低。Map 接口有三个实现类:
HashMap:基于 hash 表的 Map 接口实现,非线程安全,高效,支持 null 值和 null 键;
HashTable:线程安全,低效,不支持null值和null键;
LinkedHashMap:是 HashMap的一个子类,保存了记录的插入顺序;
TreeMap:属于SortMap接口,能够把它保存的记录根据键排序,默认是键值的升序排序Set 接口有两个实现类:
HashSet:底层是由 HashMap 实现,不允许集合中有重复的值,使用该方式时需要重写 equals()和 hashCode()方法;
LinkedHashSet:继承与 HashSet,同时又基于 LinkedHashMap 来进行实现,底层使用的是LinkedHashMp说下 HashMap、ConcurrentHashMap、ArrayList的底层实现原理
HashMap:HashMap 基于 Hash 算法实现的,我们通过 put(key,value)存储,get(key)来获取。HashMap 底层的数据是数组转换成哈希桶,桶里装的是链表,当链表里面的长度小于8时是单链表,等于8的时候链表转换为红黑树
HashSet:HashSet 是基于 HashMap 实现的,HashSet 底层使用 HashMap 来保存所有元素,因此 HashSet 的实现比较简单,相关 HashSet 的操作,基本上都是直接调用底层 HashMap 的相关方法来完成,HashSet 不允许重复的值
ConcurrentHashMap:ConcurrentHashMap 的底层实现原理采用锁分段技术,将整个哈希桶分成多个部分,每个部分上了锁。同一个上了锁的部分的多线程并发操作都是线程安全的,不同锁的部分的多线程并发操作不是线程安全的。
ArrayList:ArrayList 的底层是数组,数组中的每个元素的内存空间是固定的,查询数组只需对应位置的内存空间,就能找到数据。数组,集合,字符串的转换
数组转字符串
使用 Arrays.toString() 方法,请参考以下代码:String[] arr = {"laowan g", "stone", "wanglei"};
String str = Arrays.toString(arr);
System.out.println(str);
数组转集合
使用 Arrays.asList() 方法,请参考以下代码:String[] strArr = {"cat", "dog"};
List list = Arrays.asList(strArr);
System.out.println(list);
集合转数组
使用 List.toArrray() 方法,请参考以下代码:List<String> list = new ArrayList<String>();
list.add("cat");
list.add("dog"); // 集合转换为数组
String[] arr = list.toArray(new String[list.size()]);
System.out.println(Arrays.toString(arr));
Arrays 对象有哪些常用的方法?
答:Arrays 常用方法如下:
Arrays.copyOf() 数组拷贝
Arrays.asList() 数组转为 List 集合
Arrays.fill() 数组赋值
Arrays.sort() 数组排序
Arrays.toString() 数组转字符串
Arrays.binarySearch() 二分法查询元素
Arrays.equals() 比较两个数组的值哈希冲突如何解决?
答:常见的哈希冲突解决方法:1.开放地址法、2.链地址法(拉链法)、3.再散列、4.建立一个公共溢出区;Threadlocalmap使用开放定址法解决hash冲突,Hashmap使用链地址法解决hash冲突
数组和链表的区别以及应用场景
区别 | 数组 | 链表 |
---|---|---|
本质区别 | 将元素在内存中连续存储的。优点是:因为数据连续存储的,内存地址连续,所以在查找数据的时候效率比较高;缺点是:在存储之前,我们需要申请一块连续的内存空间,需要在编译时确定好空间大小;在改变数据个数时,增加、插入、删除数据效率较低。 | 动态申请空间,无需向数组那样提前申请好内存的大小,链表只需在用的时候申请就可以,根据需要来动态申请或者删除内存空间,对于数据增加和删除以及插入比数组灵活。 |
场景 | 数据比较少,经常要做的运算是按序号访问数据元素;数组更容易实现,支持任何高级语言;构建的线性表较稳定 | 对线性表的长度或规模难以估计;频繁做插入删除操作;构建动态性比较强的线性表。 |
1.8 Java 设计模式
你了解哪些Java的设计模式?请说出五种
单例模式、工厂模式、代理模式、观察者模式、建造者模式
什么是单例模式?
一种常用的软件设计模式,在应用这个模式时,单例对象的类必须保证只有一个实例存在,整个系统只能使用一个对象实例
优点:不会频繁地创建和销毁对象,浪费系统资源。
使用场景:IO 、数据库连接、Redis 连接等。
代码
class SingleObject {
private static SingleObject instance = new SingleObject(); //创建 SingleObject 的一个对象,单例类只能有一个实例。
private SingleObject() {} //让构造函数为 private,这样该类就不会被实例化
public static SingleObject getInstance() { //获取唯一可用的对象
return instance;
}
}
Spring 中有哪些设计模式?
代理模式:在 AOP 中有使用
单例模式:bean 默认是单例模式
模板方法模式:jdbcTemplate
工厂模式:BeanFactory
适配器模式:Spring MVC 中也是用到了适配器模式适配 Controller
1.9 Jvm
String存放的对象,用new创建的对象在堆区,函数中的临时变量在栈区,Java中的字符串在字符串常量区,常量池属于 PermGen(方法区)
私有:Java虚拟机栈,程序计数器,本地方法栈 共享:Java堆,方法区
Java中的变量和基本类型的值存放于栈内存,而new出来的对象本身存放于堆内存,指向对象的引用还是存放在栈内存。
栈内存的一个特点是数据共享,这样设计是为了减小内存消耗,前面定义了 i = 1,i 和 1 都在栈内存内,如果再定义一个 j = 1 ,此时将 j 放入栈内存,然后查找栈内存中是否有 1 ,如果有则 j 指向 1 。如果再给 j 赋值 2 ,则在栈内存中查找是否有 2 ,如果没有就在栈内存中放一个 2 ,然后 j 指向 2 。也就是如果常量在栈内存中,就将变量指向该常量,如果没有就在该栈内存增加一个该常量,并将变量指向该常量。对于字符串常量的相加,在编译时直接将字符串合并,而不是等到运行时再合并。也就是说 String a = “tao” + “bao”; 和 String a = “taobao”; 编译出的字节码是一样的。Java对String的相加是通过StringBuffer实现的,先构造一个StringBuffer里面存放”tao”,然后调用append()方法追加”bao”,然后将值为”taobao”的StringBuffer转化成String对象。StringBuffer对象在堆内存中,那转换成的String对象理所应当的也是在堆内存中。
JVM 堆分为:新生代(一般是一个 Eden 区,两个Survivor区),老年代(old区)。
JVM 垃圾堆大小分配
-Xmx:最大堆大小
-Xms:初始堆大小
-Xmn:年轻代大小
-XXSurvivorRatio:年轻代中Eden区与Survivor区的大小比值
年轻代5120m, Eden:Survivor=3,Survivor区大小=1024m(Survivor区有两个,即将年轻代分为5份,每个Survivor区占一份),总大小为2048m。
-Xms初始堆大小即最小内存值为10240m说一下 JVM 的主要组成部分及其加载过程?
类加载器(ClassLoader)
运行时数据区(Runtime Data Area)
执行引擎(Execution Engine)
本地库接口(Native Interface)JVN的加载过程:
首先通过类加载器把Java代码转换成字节码,运行时数据区再把字节码加载到内存中,由于字节码文件只是jvm的一套指令集规范,并不能直接交给底层操作系统去执行,然后需要特定的命令解析器执行引擎,将字节码翻译成底层系统指令,再交由CPU去执行,而这个过程中需要调用其他语言的本地库接口实现整个程序的功能。
说一下 JVM 运行时数据区?
不同虚拟机的运行时数据区可能略微有所不同,但都会遵从 Java 虚拟机规范, Java 虚拟机规范规定的区域分为以下 5 个部分:程序计数器(Program Counter Register):当前线程所执行的字节码的行号指示器,字节码解析器的工作是通过改变这个计数器的值,来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能,都需要依赖这个计数器来完成;
Java 虚拟机栈(Java Virtual Machine Stacks):用于存储局部变量表、操作数栈、动态链接、方法出口等信息;
本地方法栈(Native Method Stack):与虚拟机栈的作用是一样的,只不过虚拟机栈是服务 Java 方法的,而本地方法栈是为虚拟机调用 Native 方法服务的;
Java 堆(Java Heap):Java 虚拟机中内存最大的一块,是被所有线程共享的,几乎所有的对象实例都在这里分配内存;
方法区(Methed Area):用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。说一下堆和栈的区别?
区别 | 堆 | 栈 |
---|---|---|
功能方面 | 堆是用来存放对象的 | 栈是用来执行程序的 |
共享性 | 线程共享 | 线程私有 |
空间大小 | 堆容量远远大于栈 | 小于堆 |
说一下类装载的执行过程?
类装载分为以下 5 个步骤:
加载:根据查找路径找到相应的class文件然后导入
检查:检查加载的class文件的正确性
准备:给类中的静态变量分配内存空间
解析:虚拟机将常量池中的符号引用替换成直接引用的过程。
初始化:对静态变量和静态代码块执行初始化工作。怎么判断对象是否可以被回收?
一般有两种方法来判断:
引用计数器:为每个对象创建一个引用计数,有对象引用时计数器+1,引用被释放时计数-1,当计数器为 0 时就可以被回收。但是不能解决循环引用的问题。
可达性分析:从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是可以被回收的。什么是双亲委派模型?
在介绍双亲委派模型之前先说下类加载器。对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立在 JVM 中的唯一性,每一个类加载器,都有一个独立的类名称空间。类加载器就是根据指定全限定名称将 class 文件加载到 JVM 内存,然后再转化为 class 对象。
类加载器分类:
启动类加载器(Bootstrap ClassLoader),是虚拟机自身的一部分,用来加载Java_HOME/lib/目录中的,或者被 -Xbootclasspath 参数所指定的路径中并且被虚拟机识别的类库;
其他类加载器:
扩展类加载器(Extension ClassLoader):负责加载<Java_HOME>\lib\ext目录或Java. ext. dirs系统变量指定的路径中的所有类库;
应用程序类加载器(Application ClassLoader)。负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器。一般情况,如果我们没有自定义类加载器默认就是用这个加载器。
双亲委派模型:如果一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,而是把这个请求委派给父类加载器去完成,每一层的类加载器都是如此,这样所有的加载请求都会被传送到顶层的启动类加载器中,只有当父加载无法完成加载请求(它的搜索范围中没找到所需的类)时,子加载器才会尝试去加载类。Java 中都有哪些引用类型?
强引用:发生 gc 的时候不会被回收。
软引用:有用但不是必须的对象,在发生内存溢出之前会被回收。
弱引用:有用但不是必须的对象,在下一次GC时会被回收。
虚引用(幽灵引用/幻影引用):无法通过虚引用获得对象,用 PhantomReference 实现虚引用,虚引用的用途是在 gc 时返回一个通知。说一下 JVM 有哪些垃圾回收算法?
标记-清除算法:标记无用对象,然后进行清除回收。缺点:效率不高,无法清除垃圾碎片。
标记-整理算法:标记无用对象,让所有存活的对象都向一端移动,然后直接清除掉端边界以外的内存。
复制算法:按照容量划分二个大小相等的内存区域,当一块用完的时候将活着的对象复制到另一块上,然后再把已使用的内存空间一次清理掉。缺点:内存使用率不高,只有原来的一半。
分代算法:根据对象存活周期的不同将内存划分为几块,一般是新生代和老年代,新生代基本采用复制算法,老年代采用标记整理算法。说一下 JVM 有哪些垃圾回收器?
Serial:最早的单线程串行垃圾回收器。
Serial Old:Serial 垃圾回收器的老年版本,同样也是单线程的,可以作为 CMS 垃圾回收器的备选预案。
ParNew:是 Serial 的多线程版本。Parallel 和 ParNew 收集器类似是多线程的,但 Parallel 是吞吐量优先的收集器,可以牺牲等待时间换取系统的吞吐量。Parallel Old 是 Parallel 老生代版本,Parallel 使用的是复制的内存回收算法,Parallel Old 使用的是标记-整理的内存回收算法。
CMS:一种以获得最短停顿时间为目标的收集器,非常适用 B/S 系统。
G1:一种兼顾吞吐量和停顿时间的 GC 实现,是 JDK 9 以后的默认 GC 选项。介绍一下 CMS 垃圾回收器?
CMS 是英文 Concurrent Mark-Sweep 的简称,是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。对于要求服务器响应速度的应用上,这种垃圾回收器非常适合。在启动 JVM 的参数加上“-XX:+UseConcMarkSweepGC”来指定使用 CMS 垃圾回收器。
CMS 使用的是标记-清除的算法实现的,所以在 gc 的时候回产生大量的内存碎片,当剩余内存不能满足程序运行要求时,系统将会出现 Concurrent Mode Failure,临时 CMS 会采用 Serial Old 回收器进行垃圾清除,此时的性能将会被降低。新生代垃圾回收器和老生代垃圾回收器都有哪些?有什么区别?
新生代回收器:Serial、ParNew、Parallel Scavenge
老年代回收器:Serial Old、Parallel Old、CMS
整堆回收器:G1
新生代垃圾回收器一般采用的是复制算法,复制算法的优点是效率高,缺点是内存利用率低;老年代回收器一般采用的是标记-整理的算法进行垃圾回收。简述分代垃圾回收器是怎么工作的?
分代回收器有两个分区:老生代和新生代,新生代默认的空间占比总空间的 1/3,老生代的默认占比是 2/3。
新生代使用的是复制算法,新生代里有 3 个分区:Eden、To Survivor、From Survivor,它们的默认占比是 8:1:1,它的执行流程如下:
把 Eden + From Survivor 存活的对象放入 To Survivor 区;
清空 Eden 和 From Survivor 分区;
From Survivor 和 To Survivor 分区交换,From Survivor 变 To Survivor,To Survivor 变 From Survivor。
每次在From Survivor到To Survivor移动时都存活的对象,年龄就+1,当年龄到达15(默认配置是 15)时,升级为老生代。大对象也会直接进入老生代。
老生代当空间占用到达某个值之后就会触发全局垃圾收回,一般使用标记整理的执行算法。以上这些循环往复就构成了整个分代垃圾回收的整体执行流程。常用的 JVM 调优的参数都有哪些?
-Xms2g:初始化推大小为 2g;
-Xmx2g:堆最大内存为 2g;
-XX:NewRatio=4:设置年轻的和老年代的内存比例为 1:4;
-XX:SurvivorRatio=8:设置新生代 Eden 和 Survivor 比例为 8:2;
–XX:+UseParNewGC:指定使用 ParNew + Serial Old 垃圾回收器组合;
-XX:+UseParallelOldGC:指定使用 ParNew + ParNew Old 垃圾回收器组合;
-XX:+UseConcMarkSweepGC:指定使用 CMS + Serial Old 垃圾回收器组合;
-XX:+PrintGC:开启打印 gc 信息;
-XX:+PrintGCDetails:打印 gc 详细信息。
2.Java WEB
- Jsp 和 Servlet 有什么区别?
区别 | Jsp | Servlet |
---|---|---|
本质区别 | servlet 技术的扩展 | servlet 的简易方式 |
逻辑 | Java 和 html 可以组合成一个扩展名为 JSP 的文件 | 在 Java 文件中,并且完全从表示层中的 html 里分离开来 |
扩展 | 侧重于视图 | 主要用于控制逻辑 |
- servlet 的生命周期
Servlet 是用 Java 编写的服务器端程序。而这些 Servlet 都要实现 Servlet 这个接口。
加载Servlet的class---->实例化Servlet----->调用Servlet的init完成初始化---->响应请求(Servlet的service方法)----->Servlet容器关闭时(Servlet的destory方法)
web 容器 加载 servlet,生命周期开始。通过调用 servlet 的 init() 方法进行 servlet 的 初始化。通过调用 service() 实现,处理不同的请求,调用 doGet() 或者 doPost() 方法,服务结束后,调用 web 容器中 destroy() 方法结束生命周期。
servlet 什么时候被初始化?
答:接收到用户请求的时候被实例化
如何实现 servlet 的单线程模式?
答:在 jsp 加入
<%@ page isThreadSafe = "false"%>
JSP 九大内置对象和属性列举如下:
对象 | 实例 | 属性 |
---|---|---|
request | HttpServletRequest | 客户端的请求信息被封装在request对象中,通过它才能了解到客户的需求,然后做出响应 |
response | HttpServletResponse | response对象包含了响应客户请求的有关信息,但在JSP中很少直接用到它 |
session | HttpSession | session对象指的是客户端与服务器的一次会话,从客户连到服务器的一个WebApplication开始,直到客户端与服务器断开连接为止 |
out | JspWriter | 是向客户端输出内容常用的对象 |
page | java.lang.Object | 当前JSP页面本身 |
application | ServletContext | 实现了用户间数据的共享,可存放全局变量 |
exception | java.lang.Throwable | exception对象是一个例外对象,当一个页面在运行过程中发生了异常,就产生这个对象 |
pageContext | 提供了对JSP页面内所有的对象及名字空间的访问 | |
config | 在一个Servlet初始化时,JSP引擎向它传递信息用的,此信息包括Servlet初始化时所要用到的参数以及服务器的有关信息 |
说一下 JSP 的 4 种作用域?
page:代表与一个页面相关的对象和属性。
request:代表与客户端发出的一个请求相关的对象和属性。一个请求可能跨越多个页面,涉及多个 Web 组件;需要在页面显示的临时数据可以置于此作用域。
session:代表与某个用户与服务器建立的一次会话相关的对象和属性。跟某个用户相关的数据应该放在用户自己的 session 中。
application:代表与整个 Web 应用程序相关的对象和属性,它实质上是跨越整个 Web 应用程序,包括多个页面、请求和会话的一个全局作用域。说一下 JSP 的 4 种会话跟踪技术?
会话跟踪是一种灵活、轻便的机制,它使Web上的状态编程变为可能。
HTTP是一种无状态协议,每当用户发出请求时,服务器就会做出响应,客户端与服务器之间的联系是离散的、非连续的。当用户在同一网站的多个页面之间转换时,根本无法确定是否是同一个客户,会话跟踪技术就可以解决这个问题。当一个客户在多个页面间切换时,服务器会保存该用户的信息。
有四种方法可以实现会话跟踪技术:URL重写、隐藏表单域、Cookie、Session
隐藏表单域:< input type=“hidden”>,非常适合步需要大量数据存储的会话应用
URL 重写:URL 可以在后面附加参数,和服务器的请求一起发送,这些参数为名字/值对
Cookie:服务器使用 setCookie 头标将它作为 HTTP响应的一部分传送到客户端,客户端被请求保存 Cookie 值,在对同一服务器的后续请求使用一个Cookie头标将之返回到服务器。与其它技术比较,Cookie 的一个优点是在浏览器会话结束后,甚至在客户端计算机重启后它仍可以保留其值
Session:使用 setAttribute(String str,Object obj)方法将对象捆绑到一个会话GET 和 POST 的区别
区别 | GET | POST |
---|---|---|
操作 | 一般用于获取/查询资源信息 | 一般用于更新资源 |
长度 | 长度有限 | 长度没有限制 |
安全性 | 客户端可以看到URL 地址的值,容易造成信息泄露 | 服务器提交的数据,用户查看不到,安全性较高 |
请求数据 | GET 请求的数据会附加在 URL 之后,以 ? 分割 URL 和传输数据,参数之间以 & 相连 | POST 把提交的数据则放置在是 HTTP 包的包体中 |
- request 和 sendRedirect的区别
区别 | request | sendRedirect |
---|---|---|
本质区别 | 服务器跳转 | 客户端跳转 |
特点 | 一次请求,浏览器地址不会改变,访问的是自己本身的 web 资源,传输的数据不会丢失 | 两次请求,浏览器地址发生改变,可以访问自己 web 之外的资源,传输的数据会丢失 |
- session 和 cookie 的区别?
区别 | session | cookie |
---|---|---|
存储位置 | 存储在服务器端 | 存储在客户端 |
安全性 | 较为安全 | 安全性一般,在浏览器存储,可以被伪造和修改 |
存储的数据量 | 能够存储任意的 Java 对象 | 只能存储 String 类型的对象 |
存储多样性 | 可以存储在 Redis 中、数据库中、应用程序中 | 只能存储在客户端中 |
说一下 session 的工作原理?
session 的工作原理是客户端登录完成之后,服务器会创建对应的 session,session 创建完之后,会把 session 的 id 发送给客户端,客户端再存储到浏览器中。这样客户端每次访问服务器时,都会带着 sessionid,服务器拿到 sessionid 之后,在内存找到与之对应的 session 这样就可以正常工作了。
如果客户端禁止 cookie 能实现 session 还能用吗?
可以用,session 只是依赖 cookie 存储 sessionId,如果 cookie 被禁用了,可以使用 url 中添加 sessionId 的方式保证 session 能正常使用。
在单点登录中,如果 cookie 被禁用了怎么办?(与上题类似)
单点登录的原理是后端生成一个 sessionId ,然后设置到 cookie,后面的所有请求浏览器都会带上 cookie ,然后服务端从 cookie 里获取 sessionId ,再查询到用户信息。所以,保存登录的关键不是 cookie,而是通过 cookie 保存和传输的 sessionId ,其本质是能获取用户信息的数据。除了 cookie ,还通常使用 HTTP 请求头来传输。
spring mvc 和 struts2 的区别是什么?
区别 | spring mvc | struts2 |
---|---|---|
拦截级别 | 方法级别的拦截 | 类级别的拦截 |
拦截机制 | 用的是独立的 aop 方式 | 有以自己的 interceptor 机制 |
对 ajax 的支持 | 集成了ajax,所有 ajax 使用很方便,只需要一个注解@ResponseBody 就可以实现了 | 一般需要安装插件或者自己写代码才行 |
数据独立性 | 方法之间基本上独立的,独享 request 和 response 数据,请求数据通过参数获取,处理结果通过 ModelMap 交回给框架,方法之间不共享变量 | struts2 虽然方法之间也是独立的,但其所有 action 变量是共享的,这不会影响程序运行,却给我们编码和读程序时带来了一定的麻烦 |
Ajax 是什么?有什么特点?
含义:是一种创建交互式网页应用的网页开发技术,“Asynchronous JavaScript and XML” 的简写
特点:异步模式,提升了用户体验;优化了服务器和浏览器之间的传输;在客户端运行,承担了一部分本来由服务器承担的工作,从而减少了大用户量的服务器负载;实现了局部刷新,在不更新整个页面的情况下维护数据,提高了用户体验度。
Http 常见的状态码有哪些?
状态码 | 含义 |
---|---|
200 | 客户端请求成功 |
302 | 重定向 |
401 | 请求未经授权 |
403 | 服务器收到请求,但是拒绝提供服务 |
404 | 请求资源不存在 |
500 | 服务器发生不可预期的错误 |
503 | 服务器当前不能处理客户的请求,一段时间后可能恢复正常 |
- 简述 tcp 和 udp的区别?
tcp 和 udp 是 OSI 模型中的运输层中的协议。tcp 提供可靠的通信传输,而 udp 则常被用于让广播和细节控制交给应用的通信传输。
两者的区别大致如下:
区别 | TCP | UDP |
---|---|---|
面向连接 | 面向连接 | 面向非连接即发送数据前不需要建立链接 |
可靠性 | 无法保证可靠性 | |
面向对象 | 面向字节流 | 面向报文 |
传输速度 | 数据传输慢 | 数据传输快 |
3.Java 框架
因为之前整理过 SSM 框架的知识,这里就不再作详述了,如果需要,参考下列一些本人自己写的博文:
Spring面试复习整理
Spring MVC面试复习整理
MyBatis 面试复习整理
4. 数据库
数据库考察的也蛮多的,建议多刷点 SQL 语句题,多表关联语句肯定会有的,然后一些基础知识也要知道。
数据分为哪两种数据库?分别有哪些?
数据库分为:关系型数据库和非关系型数据库
关系型:mysql oracle sqlserver等
非关系型: redis,memcache,mogodb,hadoop等什么是 SQL?SQL 有哪些分类?
答案:SQL 代表结构化查询语言,它是访问关系数据库的通用语言,支持数据的各种增删改查操作。SQL 语句可以分为以下子类:
DQL:数据查询语言。这个就是 SELECT 语句,用于查询数据库中的数据和信息。
DML:数据操作语言。包括 INSERT、UPDATE、DELETE 和 MERGE 语句,主要用于 数据的增加、修改和删除。
DDL:数据定义语言。主要包括 CREATE、ALTER 和 DROP 语句,用于定义数据库中的对象,例如表和索引。
TCL:事务控制语言;主要包括 COMMIT、ROLLBACK 和 SAVEPOINT 语句,用于管理数据库的事务。
DCL:数据控制语言。主要包括 GRANT 和 REVOKE 语句,用于控制对象的访问权限。SQL select 语句完整的执行顺序
from 表名
where 指定条件
group by 分组数据
having 筛选分组
order by 对结果集进行排序SQL 的聚合函数
AVG:返回组中的平均值,忽略空值
COUNT:返回指定组中的项目个数
MAX:返回指定数据中的最大值
MIN:返回指定数据中的最小值
SUM:返回指定数据的和,只能用于数字列,忽略空值
GROUP BY:对数据进行分组,计算每一组的值SQL 的连接查询
SQL的连接分为两种:外连接和内连接;外连接又分为三种:左连接、右连接、全连接。
左连接:返回左表全部记录和右表联结字段相等的记录,不匹配显示为 null
右连接:返回右表全部记录和左表联结字段相等的记录,不匹配显示为 null
全连接:返回全部记录内连接:返回两个表中相等的记录
MySQL 性能优化
1、已知数据只有一行时,使用 limit 1
2、选择正确的数据库引擎
3、垂直分割分表
4、合理创建索引,为搜索字段创建索引
5、SQL 语句调优,例如: 用 not exists 代替 not in,尽量不用 select *等;操作符的优化,尽量不要采用不利于索引的操作符,例如:in ,not in,is null,<>等DROP TABLE 和 TRUNCATE TABLE 的区别?
答案:DROP TABLE 用于从数据库中删除表,包括表中的数据和表结构自身。同时还会删除与表相关的的所有对象,包括索引、约束以及访问该表的授权。TRUNCATE TABLE 只是快速删除表中的所有数据,回收表占用的空间,但是会保留表的结构。
JDBC 中的 PrepareStatement 和Statement的区别
区别 | PrepareStatement | Statement |
---|---|---|
编译性 | 预编译 | 不支持 |
代码的可读性和可维护性 | 高 | 低 |
安全性 | 可以防止 SQL 注入 | 低 |
如何避免 SQL 注入?
使用预处理 PreparedStatement。
使用正则表达式过滤掉字符中的特殊字符。数据库的三范式是什么?
第一范式:强调的是列的原子性,即数据库表的每一列都是不可分割的原子数据项。
第二范式:要求实体的属性完全依赖于主关键字。所谓完全依赖是指不能存在仅依赖主关键字一部分的属性。
第三范式:任何非主属性不依赖于其它非主属性。说一下 MySQL InnoDB 和 MyIASM 这两种引擎的区别?
区别 | InnoDB 引擎 | MyIASM 引擎 |
---|---|---|
事务 | 支持 | 不支持 |
崩溃安全恢复 | 支持 | 不支持 |
行级锁 | 支持 | 不支持,只支持表锁 |
外键 | 支持 | 不支持 |
性能 | 低 | 高 |
主键查询性能 | 高 | 低 |
使用场景 | 并发度较高的情况 | 表的读操作远远多于写操作并且不支持事务支持 |
事物的特性
数据库事物的有四大特性:原子性、一致性、隔离性、持久性
原子性:整个事务中的所有操作,要么全部完成,要么全部不完成
一致性:在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏
隔离性:确保每一事务在系统中认为只有该事务在使用系统
持久性:事务完成之后,该事务对数据库所作的更改便持久的保存在数据库之中,并不会被回滚MySQL 的四大隔离级别
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
Read uncommitted(读未提交) | √ | √ | √ |
Read committed (读已提交) | × | √ | √ |
Repeatable read (可重复读) | × | × | √ |
Serializable (序列化) | × | × | × |
乐观锁和悲观锁
乐观锁:总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所有不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据
悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会堵塞直到它拿到锁
MySQL 和 Oracle 的区别
区别 | MySQL | Oracle |
---|---|---|
本质区别 | 关系数据库管理系统 | 对象关系数据库管理系统 |
安全性 | 用户名,密码,配置文件验证 | 用户名,密码,配置文件验证,本地身份验证,外部身份验证,高级安全增强功能 |
表空间 | 无表空间 | 有表空间 |
事务 | 需要在 InnoDB 存储引擎的行级锁的情况下才支持事务 | 完全支持事务 |
性能诊断 | 诊断调优方法较少,主要由慢查询日志 | 有各种成熟的性能诊断调优工具,能实现很多自动分析、诊断功能。比如 awr,addm,sqltrace,tkproof等 |
并发 | 以表级锁为主,对资源锁定的粒度很大 | 以行级锁为主,对资源锁定的粒度要小很多 |
持久性 | 在数据库更新或者重启,则会丢失数据 | 把提交的 SQL 操作线写入了在线联机日志中,保持到了磁盘上,可以随时恢复 |
事务隔离级别 | read commited 的隔离级别,支持 serializable 隔离级别 | repeatable read 的隔离级别,支持 serializable 隔离级别 |
主键 | 一般使用自动增长类型,在创建表的时候只要指定表的主键为 auto_increment | 无自动增长类型,主键一般使用的序列,插入记录时将序列号的下一个值赋给该字段 |
单引号 | 可以用双引号包起字符串 | 只能用单引号包起字符串 |
分页 | 采用 limit 语句进行分页 | 采用 rownum 语句进行分页 |
空字符 | 非空字段可以有空内容 | 非空字段不允许有空内容 |
模糊查询 | 用字段名 like ‘%字符串%’ | 也可以使用字段名 like ‘%字符串%’,但是这种方法不能使用索引,速度慢 |
5. Redis
之前写过的一篇文章,如果想要了解,可以去看看
制作不易,路过点个赞,谢谢
JAVA 150道笔试题知识点整理的更多相关文章
- 【笔试题】Java笔试题知识点
Java高概率笔试题知识点 Java语法基础部分 [解析]java命令程序执行字节码文件是,不能跟文件的后缀名! 1.包的名字都应该是由小写单词组成,它们全都是小写字母,即便中间的单词亦是如此 2.类 ...
- 剑指Offer——CVTE校招笔试题+知识点总结(Java岗)
剑指Offer(Java岗)--CVTE校招笔试题+知识点总结 2016.9.3 19:00参加CVTE笔试,笔试内容如下: 需要掌握的知识:Linux基本命令.网络协议.数据库.数据结构. 选择题 ...
- Java 面试/笔试题神整理 [Java web and android]
Java 面试/笔试题神整理 一.Java web 相关基础知识 1.面向对象的特征有哪些方面 1.抽象: 抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面.抽象并 ...
- 剑指Offer——京东校招笔试题+知识点总结
剑指Offer--京东校招笔试题+知识点总结 笔试感言 经过一系列的笔试,发觉自己的基础知识还是比较薄弱的,尤其是数据结构和网络,还有操作系统.工作量还是很大的.做到精确制导的好方法就是在网上刷题,包 ...
- 剑指Offer——完美+今日头条笔试题+知识点总结
剑指Offer--完美+今日头条笔试题+知识点总结 情景回顾 时间:2016.9.28 16:00-18:00 19:00-21:00 地点:山东省网络环境智能计算技术重点实验室 事件:完美世界笔试 ...
- 剑指Offer——乐视笔试题+知识点总结
剑指Offer--乐视笔试题+知识点总结 情景回顾 时间:2016.9.19 15:10-17:10 地点:山东省网络环境智能计算技术重点实验室 事件:乐视笔试 总体来说,乐视笔试内容体量不算少, ...
- 剑指Offer——滴滴笔试题+知识点总结
剑指Offer--滴滴笔试题+知识点总结 情景回顾 时间:2016.9.18 15:00-17:00 地点:山东省网络环境智能计算技术重点实验室 事件:滴滴笔试 总体来说,滴滴笔试内容体量不算多, ...
- 剑指Offer——迅雷笔试题+知识点总结
剑指Offer--迅雷笔试题+知识点总结 情景回顾 时间:2016.9.19 19:00-21:00 地点:山东省网络环境智能计算技术重点实验室 事件:迅雷笔试 总体来说,迅雷笔试内容体量不算多,主要 ...
- 剑指Offer——携程笔试题+知识点总结
剑指Offer--携程笔试题+知识点总结 情景回顾 时间:2016.9.17 19:10-21:10 地点:山东省网络环境智能计算技术重点实验室 事件:携程笔试 总体来说,携程笔试内容与其它企业笔试题 ...
随机推荐
- 【转】SpringCloud学习
Spring Cloud Alibaba与Spring Boot.Spring Cloud之间不得不说的版本关系 这篇博文是临时增加出来的内容,主要是由于最近连载<Spring Cloud ...
- 虚拟机--第一章走进java--(抄书)
这是本人阅读周志明老师的<深入理解Java虚拟机>第二版抄写的,有很多省略,不适合直接阅读,需要阅读请出门左转淘宝,右转京东,支持周老师(侵权请联系删除) 第一章走近java 世界上并没有 ...
- mzy,struts学习(三):action中获得servlet中三域一参的三种方法
package com.mzy.servlet; import java.util.Arrays; import java.util.Map; import javax.servlet.Servlet ...
- 模拟文件上传(二):使用apache fileupload组件进行文件上传
其中涉及到的jar包: jsp显示层: <%@ page language="java" import="java.util.*" pageEncodin ...
- 详细解读go语言中的map
Map map底层是由哈希表实现的 Go使用链地址法来解决键冲突. map本质上是一个指针,指向hmap 这里的buckets就是桶,bmap 每一个bucket最多可以放8个键值对,但是为了让内存排 ...
- 新东方APP技术架构演进, 分布式系统架构经验分享
今天的演讲题目是"新东方APP技术架构演进, C端技术经验分享" 作者:张建鑫, 曾任IBM高级软件架构师, 滴滴高级技术专家, 现任新东方集团高级技术总监 古代东西方的思想家都产 ...
- Spring 事务回滚机制详解
1:事务原理 1.2:aop/动态代理 类路径:org/springframework/aop/framework/CglibAopProxy.java ReflectiveMethodInvocat ...
- 老司机带你体验SYS库多种新玩法
导读 如何更加愉快地利用sys库做一些监控? 快来,跟上老司机,体验sys库的多种新玩法~ MySQL5.7的新特性中,非常突出的特性之一就是sys库,不仅可以通过sys库完成MySQL信息的收集,还 ...
- Qt5之事件学习总结
首先要明白一个概念,事件和信号并不一样,比如单击一下鼠标,就会产生鼠标事件(QMouseEvent),是对这个动作的描述,而因为按钮被按下了,按钮会发出clicked()的单击信号(是按钮控件产生的) ...
- (四)羽夏看C语言——循环与跳转
写在前面 由于此系列是本人一个字一个字码出来的,包括示例和实验截图.本人非计算机专业,可能对本教程涉及的事物没有了解的足够深入,如有错误,欢迎批评指正. 如有好的建议,欢迎反馈.码字不易,如果本篇 ...