java技术整理

一、基础

1.字节码

1.1 源代码到运行过程
在这里插入图片描述
1.2 .class -----> 机器码:
(1)一般是JVM类加载器首先加载字节码文件(.class文件),然后通过解释器逐行解释执行,速度慢。
(2)但是热点代码会使用JIT(just-in-time compilation)运行时编译后,会将字节码对应的机器码保存下来,下次可以直接使用。而机器码的运行效率高于java解释器。
综合以上,所以说Java 是编译与解释共存的语言 (并存指的是:先经过编译再通过java解释器解释)。

1.3 AOT
(1)JDK9引入了新编译模式:AOT(Ahead of Time Compilation),直接将字节码编译成机器码,避免了JIT预热等开销。JDK支持分层编译和AOT协作使用。

1.4 不全部使用AOT原因
Java的动态特性,如CGLIB动态代理使用的是ASM技术 ---- 原理是运行时直接在内存中生成并加载修改后的字节码文件。如果全部使用AOT提前编译,就不能使用ASM技术了。而使用JIT技术就可以。

2.Java 中有三种移位运算符?

2.1 << :左移运算符,向左移若干位,高位丢弃,低位补零。x << 1,相当于 x 乘以 2(不溢出的情况下)。
2.2 >> :带符号右移,向右移若干位,高位补符号位,低位丢弃。正数高位补 0,负数高位补 1。
x >> 1,相当于 x 除以 2。
2.3 >>> :无符号右移,忽略符号位,空位都以 0 补齐。

&& 只有int和long进行位移;double、float不能位移;编译器在对short、byte、char类型会将他们先转成int。

3.成员变量、局部变量区别?

3.1 语法形式:
(1)成员属于类的,而局部是在代码块或方法中定义的变量或者是方法的参数。
(2)两者都能被final修饰;但是只有成员能被public、private、static修饰。
3.2 存储方式:成员如果被static修饰则属于类存储在方法区,否则属于实例,存储在堆;而局部则存储在栈;
3.3 生存时间:成员随着对象创建而存在;而局部随着方法调用生成,调用结束而消亡。
3.4 默认值:成员若未赋予初始值则按默认值赋值(final修饰的则必须显式赋值);而局部不会自动赋值。

4.静态方法为什么不能调用非静态成员?

4.1 静态方法属于类的,类加载时就会分配内存,可以通过类名直接访问;而非静态成员属于实例对象,只有对象实例化后通过类的对象才能访问
4.2 已存在的静态方法去调用内存中还不存在的非静态成员,属于非法操作。

5.重载和重写区别?

1.1 图
在这里插入图片描述
1.2 重写注意点
(1)如果⽗类⽅法访问修饰符为 private/final/static 则⼦类就不能重写该⽅法,但是被 static 修饰的⽅法能够被再次声明
(2)构造⽅法⽆法被重写

1.3 重写:两同两小一大
(1)“两同”:方法名相同、形参列表相同;
(2)“两小”指的是子类方法返回值类型应比父类方法返回值类型更小或相等,子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等;
(3)“一大”指的是子类方法的访问权限应比父类方法的访问权限更大或相等。

⭐️ 补充:如果方法的返回类型是 void 和基本数据类型,则返回值重写时不可修改。
但是如果方法的返回值是引用类型,重写时是可以返回该引用类型的子类的。

6.Java可变长参数?

6.1 JDK5开始可以,如String … args,但只能是最后一个参数,其前面可以有多个其他参数
6.2 重载会优先匹配固定参数
6.3 可变长参数会被编译成数组·

7.基本类型和包装类型的区别?

基本类型:
4整型:byte、short、int、long
2浮点:float、double
1字符:char
1布尔:boolean

包装类型:
4整形对应:Byte、Short、Integer、Long
2浮点对应:Float、Double
1字符对应:Character
1布尔对应:Boolean

7.1 默认值:成员变量包装类型不赋值就是null,而基本类型有默认值不是null。
7.2 存储:包装类型属于对象类型,存储堆中;基本类型成员变量(被static修饰属于静态变量存储于方法区中除外)存储于堆中,局部变量存储栈的局部变量表里。
&& 基本类型若没有被static修饰,不建议作为成员变量,应该要使用其对应的包装类型。
7.3 大小:基本类型占用空间更小。
7.4 泛型:包装类型可用于泛型而基本类型不可以。

8.包装类型的缓存机制?

8.1 Byte、Short、Integer、Long默认创建了数值【-128,127】的相应类型的缓存数据
8.2 Float、Double没有实现
8.3 Character则是【0,127】
8.4 Boolean则是True或False

&& 所有包装值之间值的比较,全部使用equals方法

9.自动装箱与拆箱?

9.1
(1)装箱:将基本类型⽤它们对应的引⽤类型包装起来;
如 Integer i = 10 等价于 Integer i = Integer.valueOf(10):这里10原本是基本数据类型自动装箱成Integer

(2)拆箱:将包装类型转换为基本数据类型;
如 int n = i 等价于 int n = i.intValue() :这里i原本是包装类型自动拆箱成基本类型的n

9.2 频繁拆装箱会严重影响系统性能,尽量避免

10.浮点运算精度丢失?

&& 原理不明白:计算机系统基础(四)浮点数

10.1 这个和计算机保存浮点数的机制有很⼤关系。我们知道计算机是⼆进制的,⽽且计算机在表示⼀个数
字时,宽度是有限的,⽆限循环的⼩数存储在计算机时,只能被截断,所以就会导致⼩数精度发⽣损失的情况。

10.2 解决:BigDecimal

11.超过long整形数据如何表示?

最大64,超过就使用BigInteger的内部Int[]数组,但运算效率相对较低

12.Java 3大特性?

1.1 封装
把一个对象的属性隐藏在对象内部,不允许外部对象直接访问,但可以提供可以被外界访问的方法来操作属性。
1.2 继承
多个子类中存在共同的属性及方法,将这些属性及行为抽取到一个父类中,子类只需要继承父类就不用创建这些属性及方法。
(1)子类拥有父类对象所有属性和方法(包括私有属性及私有方法),但子类无法直接访问(必须通过从父类中继承得到的protected、public方法,如getter、setter方法,来访问。)
(2) 子类可以拥有自己的方法,即可以对父类方法进行扩展
(3) 子类可以用自己的方式实现父类的方法(重写)
1.3 多态
一个对象有多种状态,具体表现为父类的引用指向子类的实例
(1)对象类型和引⽤类型之间具有继承(类)/实现(接⼝)的关系;
(2) 引⽤类型变量发出的⽅法调⽤的到底是哪个类中的⽅法,必须在程序运⾏期间才能确定;
(3) 多态不能调⽤“只在⼦类存在但在⽗类不存在”的⽅法;
(4)如果⼦类重写了⽗类的⽅法,真正执⾏的是⼦类覆盖的⽅法,如果⼦类没有覆盖⽗类的⽅法,执⾏的是⽗类的⽅法。

13.接口和抽象类异同?

1.1 同:
(1)都不能被实例
(2)都可以包含抽象方法
(3)都可以有默认实现的方法(JDK8可以用default关键字在接口定义默认方法)

1.2 异:
(1)
接口主要用于对类的行为进行约束,实现某个接口就有了对应的行为(即是 "有没有"的关系)。
抽象类主要用于代码复用,强调所属关系(即 "是不是"的关系)

(2)
一个类只能继承一个类,但可以实现多个接口

(3)
接口中的成员变量只能是public、static、final类型的,不能被修改且必须有初始值;
抽象类的成员变量默认default,可在子类中重新定义及重新赋值。

14.深拷贝、浅拷贝、引用拷贝?

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

实现了Cloneable 接⼝,并重写了Object的 clone() ⽅法

(2) 深拷贝
深拷贝会完全复制整个对象,包括这个对象所包含的内部对象

(3) 引用拷贝
引用拷贝就是两个不同的引用指向同一个对象。
在这里插入图片描述

15.Object类常见方法?
/**
* native ⽅法,⽤于返回当前运⾏时对象的 Class 对象,使⽤了 final 关键字修饰,故不允许⼦类
重写。
*/
public final native Class<?> getClass()
/**
* native ⽅法,⽤于返回对象的哈希码,主要使⽤在哈希表中,⽐如 JDK 中的HashMap。
*/
public native int hashCode()
/**
* ⽤于⽐较2个对象的内存地址是否相等,String 类对该⽅法进⾏了重写以⽤于⽐较字符串的值是否相
等。
*/
public boolean equals(Object obj)
/**
* naitive ⽅法,⽤于创建并返回当前对象的⼀份拷⻉。
*/
protected native Object clone() throws CloneNotSupportedException
/**
* 返回类的名字实例的哈希码的16进制的字符串。建议 Object 所有的⼦类都重写这个⽅法。
*/
public String toString()
/**
* native ⽅法,并且不能重写。唤醒⼀个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。
如果有多个线程在等待只会任意唤醒⼀个。
*/
public final native void notify()
/**
* native ⽅法,并且不能重写。跟 notify ⼀样,唯⼀的区别就是会唤醒在此对象监视器上等待的所有
线程,⽽不是⼀个线程。
*/
public final native void notifyAll()
/**
* native⽅法,并且不能重写。暂停线程的执⾏。注意:sleep ⽅法没有释放锁,⽽ wait ⽅法释放了
锁 ,timeout 是等待时间。
*/
public final native void wait(long timeout) throws InterruptedException
/**
* 多了 nanos 参数,这个参数表示额外时间(以毫微秒为单位,范围是 0-999999)。 所以超时的时间
还需要加上 nanos 毫秒。。
*/
public final void wait(long timeout, int nanos) throws InterruptedException
/**
* 跟之前的2个wait⽅法⼀样,只不过该⽅法⼀直等待,没有超时时间这个概念
*/
public final void wait() throws InterruptedException
/**
* 实例被垃圾回收器回收的时候触发的操作
*/
protected void finalize() throws Throwable { }
16.== 和 equals() 的区别?

1.1 ==
(1)对于基本数据类型,比较的是值
(2)对于引用数据类型,比较的是对象的内存地址

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

1.2 equals()
(1)不能用于判断基本数据类型的变量,只能用来判断两个对象是否相等。
equals() ⽅法存在于 Object 类中,⽽ Object 类是所有类的直接或间接⽗类,因此所有的类都有 equals() ⽅法。

2种使用情况:
a.类没有重写equals()方法:
通过equals()比较该类的两个对象时,等价于通过==比较这两个对象,使⽤的默认是 Object 类 equals() ⽅法。

b.类重写了equals()方法:
⼀般我们都重写 equals() ⽅法来⽐较两个对象中的属性是否相等;若它们的属性相等,则返回 true(即,认为这两个对象相等)。

1.3 如下:

String a = new String("ab"); // a 为⼀个引⽤
String b = new String("ab"); // b为另⼀个引⽤,对象的内容⼀样
String aa = "ab"; // 放在常量池中
String bb = "ab"; // 从常量池中查找
System.out.println(a == b);// false
System.out.println(aa == bb);// true
System.out.println(a.equals(b));// true
System.out.println(42 == 42.0);// true

(1)String 中的 equals ⽅法是被重写过的,因为 Object 的 equals ⽅法是⽐较的对象的内存地址,⽽ String 的 equals ⽅法⽐较的是对象的值。
(2)当创建 String 类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对
象,如果有就把它赋给当前引⽤。如果没有就在常量池中重新创建⼀个 String 对象。

17.hashCode() 有什么⽤??

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

1.2 hashCode() 定义在 JDK 的Object类中,这就意味着 Java 中的任何类都包含有 hashCode() 函
数。
1.3 Object 的 hashCode() ⽅法是本地方法,也就是⽤ C 语⾔或 C++ 实现
的,该⽅法通常⽤来将对象的内存地址转换成整数之后返回
1.4 散列表存储的是键值对(key-value),它的特点是:能根据键快速检索出对应的值。这其中就利
⽤到了散列码!(可以快速找到所需要的对象)

18.为什么要有 hashCode?

以HashSet如何检查重复为例:
1.1 当你把对象加⼊ HashSet 时:
HashSet 会先计算对象的hashCode值来判断对象加入的位置,
同时也会与同时也会与其他已经加入的对象的hashCode值作比较。
1.2 如果没有相等的hashCode , HashSet 会假设对象没有重复出现。
1.3 但是如果发现有相同 hashCode 值的对象,这时会调⽤ equls()⽅法来检查hashCode值相同的对象是否真的相同。
1.4 如果两者相同, HashSet 就不会让其加⼊操作成功,就会重新散列到其他位置。
1.5 这样我们就⼤⼤减少了equals的次数,相应就⼤⼤提高了执行速度。

&& hashCode() 和 equals() 都是⽤于⽐较两个对象是否相等。

另外注意如下:
(1)如果两个对象的 hashCode 值相等,那这两个对象不⼀定相等。
因为hash碰撞 ---- hashCode() 所使⽤的哈希算法也许让多个对象传回相同的哈希值
(2)如果两个对象的hashCode值相等并且equals()方法也返回true ,我们才认为这两个对象相

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

19.为什么重写 equals() 时必须重写 hashCode() ⽅法?

1.1 因为两个相等的对象的 hashCode 值必须是相等。也就是说如果 equals ⽅法判断两个对象是相等
的,那这两个对象的 hashCode 值也要相等。
1.2 如果重写 equals() 时没有重写 hashCode() ⽅法的,就可能会导致 equals ⽅法判断是相等的两个对象,hashCode值却不相等。

&& 如上18-1.2,如果没有相等的hashCode值,两个对象也有可能相等,HashSet就无法判断对象到底有没有重复即无法去重,也就是hashSet就有可能存在重复的对象(同理,hashMap的key就有可能重复,正常:hashmap只能有唯一的key,hashSet只能有唯一的对象)

20.String、StringBuffer、StringBuilder 的区别?

1.1 可变
(1)String 是不可变的。
(2)StringBuilder 与 StringBuffer可变:
a.都继承⾃ AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串,
b.AbstractStringBuilder 没有使用final和private关键字修饰,
c.最关键的是这个AbstractStringBuilder 类还提供了很多修改字符串的方法比如append。

1.2 线程安全性
(1)String 中的对象是不可变的,也就可以理解为常量,线程安全。
(2)AbstractStringBuilder 是StringBuilder 与 StringBuffer 的公共⽗类,定义了⼀些字符串的基本操作,如expandCapacity 、 append 、 insert 、 indexOf 等公共⽅法。
a.StringBuffer 对⽅法加了同步锁或者对调⽤的⽅法加了同步锁,所以是线程安全的。

b. StringBuilder 并没有对⽅法进⾏加同步锁,所以是⾮线程安全的。

1.3 性能
(1)每次对 String 类型进⾏改变的时候,都会⽣成⼀个新的 String 对象,然后将指针指向新的
String 对象。
(2)StringBuffer 每次都会对 StringBuffer 对象本身进⾏操作,⽽不是⽣成新的对象并改变对象引⽤。相
(3)同情况下使⽤ StringBuilder 相⽐使⽤ StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的⻛险

1.4 总结
(1)操作少量数据: 适⽤ String
(2)单线程操作字符串缓冲区下操作⼤量数据: 适⽤ StringBuilder
(3)多线程操作字符串缓冲区下操作⼤量数据: 适⽤ StringBuffer

21.String 为什么是不可变的?

1.1
(1)我们知道被 final 关键字修饰的类不能被继承,修饰的⽅法不能被重写,修饰的变量是基本数据类型则值不能改变,修饰的变量是引⽤类型则不能再指向其他对象。
(2)但是 final关键字修饰的数组保存字符串并不是 String 不可变的根本原因,因为这个数组保存的字符串是可变的( final 修饰引⽤类型变量的情况)

1.2 不可变真正的原因

public final class String implements java.io.Serializable, Comparable<String>,
CharSequence {
private final char value[];
 //...
}

(1)保存字符串的数组被 final 修饰且为私有的,并且 String 类没有提供/暴露修改这个字符串的⽅法。
(2)String 类被 final 修饰导致其不能被继承,进⽽避免了⼦类破坏 String 不可变。

&& 在 Java 9 之后, String 、 StringBuilder 与 StringBuffer 的实现改⽤ byte 数组存储字符串。

23.字符串拼接⽤“+” 还是 StringBuilder?

1.1 +
Java 语⾔本身并不⽀持运算符重载,“+”和“+=”是专⻔为 String 类重载过的运算符,也是 Java 中仅有的两个重载过的运算符。
(1)字符串对象通过“+”的字符串拼接⽅式,实际上是通过 StringBuilder 调⽤ append() ⽅法实现的,拼接完成之后调⽤ toString() 得到⼀个 String 对象 。

(2)不过,在循环内使⽤“+”进⾏字符串的拼接的话,存在⽐较明显的缺陷:编译器不会创建单个
StringBuilder 以复⽤,会导致创建过多的 StringBuilder 对象。

(3)如下:

String str1 = "he";
String str2 = "llo";
String str3 = "world";
String str4 = str1 + str2 + str3;
String[] arr = {"he", "llo", "world"};

// 相当于
String s = "";
for (int i = 0; i < arr.length; i++) {
s += arr[i];
}
System.out.println(s);

1.2 StringBuilder
如果直接使⽤ StringBuilder 对象进⾏字符串拼接的话,就不会存在这个问题了。

String[] arr = {"he", "llo", "world"};
StringBuilder s = new StringBuilder();
for (String value : arr) {
	s.append(value);
}
System.out.println(s);
24.String s1 = new String(“abc”);这句话创建了⼏个字符串对象?

会创建 1 或 2 个字符串对象。

1.1 如果字符串常量池中不存在字符串对象“abc”的引⽤,那么会在堆中创建 2 个字符串对象“abc”。

如:String s1 = new String(“abc”);
对应的字节码:
在这里插入图片描述
&& ldc 命令⽤于判断字符串常量池中是否保存了对应的字符串对象的引⽤,如果保存了的话直接返回,如果没有保存的话,会在堆中创建对应的字符串对象并将该字符串对象的引⽤保存到字符串常量
池中。

1.2 如果字符串常量池中已存在字符串对象“abc”的引⽤,则只会在堆中创建 1 个字符串对象“abc”。
如:

// 字符串常量池中已存在字符串对象“abc”的引⽤
String s1 = "abc";
// 下⾯这段代码只会在堆中创建 1 个字符串对象“abc”
String s2 = new String("abc")

对应的字节码:
在这里插入图片描述
7 这个位置的 ldc 命令不会在堆中创建新的字符串对象“abc”,这是因为 0 这个位置已经执⾏了⼀次 ldc 命令,已经在堆中创建过⼀次字符串对象“abc”了。
7 这个位置执⾏ ldc 命令会直接返回字符串常量池中字符串对象“abc”对应的引⽤。

25.intern ⽅法有什么作⽤?

String.intern() 是⼀个 native(本地)⽅法,其作⽤是将指定的字符串对象的引⽤保存在字符串常量池中,可以简单分为2种情况:

1.1 如果字符串常量池中保存了对应的字符串对象的引用,就直接返回该引用。

1.2 如果字符串常量池中没有保存了对应的字符串对象的引⽤,那就在常量池中创建一个指向该字符串对象的引用并返回。

如下:

// 在堆中创建字符串对象”Java“
// 将字符串对象”Java“的引⽤保存在字符串常量池中
String s1 = "Java";
// 直接返回字符串常量池中字符串对象”Java“对应的引⽤
String s2 = s1.intern();
// 会在堆中在单独创建⼀个字符串对象
String s3 = new String("Java");
// 直接返回字符串常量池中字符串对象”Java“对应的引⽤
String s4 = s3.intern();
// s1 和 s2 指向的是堆中的同⼀个对象
System.out.println(s1 == s2); // true
// s3 和 s4 指向的是堆中不同的对象
System.out.println(s3 == s4); // false
// s1 和 s4 指向的是堆中的同⼀个对象
System.out.println(s1 == s4); // false
26.String 类型的变量和常量做“+”运算时发⽣了什么?

1.1 先来看字符串不加 final 关键字拼接的情况(JDK1.8):

String str1 = "str";
String str2 = "ing";
String str3 = "str" + "ing";
String str4 = str1 + str2;
String str5 = "string";
System.out.println(str3 == str4);//false
System.out.println(str3 == str5);//true
System.out.println(str4 == str5);//false

&& 注意 :
a.⽐较 String 字符串的值是否相等,可以使⽤ equals() ⽅法。String 中的 equals ⽅法是被重写过的。
b. Object 的 equals ⽅法是⽐较的对象的内存地址,⽽ String 的 equals⽅法⽐较的是字符串的值是否相等。
c.如果你使⽤ == ⽐较两个字符串是否相等的话,IDEA 还是提示你使⽤ equals() ⽅法替换。

(1)对于编译期间可以确定的字符串,也就是常量字符串 ,jvm 会将其存⼊字符串常量池。
并且,字符串常量拼接得到的字符串常量在编译阶段就已经被存放字符串常量池,这个得益于编译器的优化

(2)在编译过程中,Javac 编译器会进⾏⼀个叫做 常量折叠(ConstantFolding) 的代码优化。

(3)常量折叠会把常量表达式的值求出来作为常量嵌在最终生成的代码中,
这是 Javac 编译器会对源代码做的极少量优化措施之⼀(代码优化⼏乎都在即时编译器中进⾏)。

如:对于 String str3 = “str” + “ing”; 编译器会给你优化成 String str3 = “string”; 。

(4)并不是所有的常量都会进⾏折叠,只有编译器在程序编译期就可以确定值的常量才可以:
a.8大基本数据类型以及字符串常量

b.final 修饰的基本数据类型和字符串变量

c.字符串通过 “+”拼接得到的字符串、基本数据类型之间算术运算(加减乘除)、基本数据类型的
位运算(<<、>>、>>> )

(5)引用的值在程序编译期是⽆法确定的,编译器⽆法对其进⾏优化。
a.对象引⽤和“+”的字符串拼接⽅式,实际上是通过 StringBuilder 调⽤ append() ⽅法实现的,拼接完成之后调⽤ toString() 得到⼀个 String 对象 。

String str4 = new StringBuilder().append(str1).append(str2).toString();

b.我们在平时写代码的时候,尽量避免多个字符串对象拼接,因为这样会重新创建对象。如果需要改变字符串的话,可以使⽤ StringBuilder 或者 StringBuffer(如上) 。

c.不过,字符串使⽤ final 关键字声明之后,可以让编译器当做常量来处理。

如下:

final String str1 = "str";
final String str2 = "ing";
// 下⾯两个表达式其实是等价的
String c = "str" + "ing";// 常量池中的对象
String d = str1 + str2; // 常量池中的对象
System.out.println(c == d);// true

d.被 final 关键字修改之后的 String 会被编译器当做常量来处理,编译器在程序编译期就可以确定它的值,其效果就相当于访问常量。(如上)

(6)如果 ,编译器在运⾏时才能知道其确切值的话,就⽆法对其优化。

如下:(str2 在运⾏时才能确定其值)

final String str1 = "str";
final String str2 = getStr();
String c = "str" + "ing";// 常量池中的对象
String d = str1 + str2; // 在堆上创建的新的对象
System.out.println(c == d);// false
public static String getStr() {
return "ing";
}
27.Exception 和 Error 有什么区别?

在 Java 中,所有的异常都有⼀个共同的祖先 java.lang 包中的 Throwable 类。
如图:
在这里插入图片描述

1.1 Throwable 类有两个重要的⼦类:
(1)Exception:
程序本身可以处理的异常,可以通过 catch 来进⾏捕获。
Exception ⼜可以分为:
a.Checked Exception (受检查异常,必须处理)
<1> 如果受检查异常没有被 catch 或者 throws 关键字处理的话,就没办法通过编译

<2> 除了 RuntimeException 及其⼦类以外,其他的 Exception 类及其⼦类都属于受检查异常 。
常⻅的受检查异常有:IO相关的异常、 ClassNotFoundException 、 SQLException …。

b.Unchecked Exception (不受检查异常,可以不处理)。
Java 代码在编译过程中 ,我们即使不处理不受检查异常也可以正常通过编译。
RuntimeException 及其子类都统称为⾮受检查异常,常⻅的有:
<1> NullPointerException (空指针异常)
<2> IllegalArgumentException (参数错误如方法入参类型错误)
<3> NumberFormatException (字符串转换为数字格式错误, IllegalArgumentException 的⼦类)
<4> ArrayIndexOutOfBoundsException (数组越界错误)
<5> ClassCastException (类型转换错误)
<6> ArithmeticException (算术错误)
<7> SecurityException (安全错误如权限不够)
<8> UnsupportedOperationException (不支持的操作错误⽐如重复创建同⼀⽤户)

(2)Error:
Error 属于程序无法处理的错误 ,我们没办法通过 catch 来进⾏捕获不建议通过catch 捕获 。
这些异常发⽣时,Java 虚拟机(JVM)⼀般会选择线程终⽌。
例如 :
a.Java 虚拟机运⾏错误( Virtual MachineError )、
b.虚拟机内存不够错误( OutOfMemoryError )、
c.类定义错误( NoClassDefFoundError )等 。

28.Throwable 类常⽤⽅法有哪些?

1.1 String getMessage() : 返回异常发⽣时的简要描述

1.2 String toString() : 返回异常发⽣时的详细信息

1.3 String getLocalizedMessage() : 返回异常对象的本地化信息。
a.使⽤ Throwable 的⼦类覆盖这个⽅法,可以⽣成本地化信息。
b.如果⼦类没有覆盖该⽅法,则该⽅法返回的信息与 getMessage() 返回的结果相同

1.4 void printStackTrace() : 在控制台上打印 Throwable 对象封装的异常信息

29.try-catch-finally 如何使⽤?

1.1 try 块 : ⽤于捕获异常。其后可接零个或多个 catch 块,如果没有catch块,则必须跟⼀个finally 块。

1.2 catch 块 : ⽤于处理 try 捕获到的异常。

1.3 finally 块 : ⽆论是否捕获或处理异常, finally 块⾥的语句都会被执⾏。
当在 try 块或catch 块中遇到 return 语句时, finally 语句块将在方法返回之前被执⾏。

1.4 注意:不要在finally块中使用return!
(1)当 try 语句和 finally 语句中都有 return 语句时,try块中的return就会被忽略。
(2)这是因为 try 语句中的 return 返回值会先被暂存在⼀个本地变量中。
(3)当执⾏到 finally 语句中的 return 之后,这个本地变量的值就变为了 finally 语句中的 return 返回值。

代码如下:

public static void main(String[] args) {
System.out.println(f(2));
}
public static int f(int value) {
try {
return value * value;
 } finally {
if (value == 2) {
return 0;
 }
 }
}

// 结果输出:0
30.finally 中的代码⼀定会执⾏吗?

不⼀定的!在某些情况下,finally 中的代码不会被执⾏。

1.1 finally之前虚拟机被终止运行的话,finally 中的代码就不会被执⾏。
如下:

try {
System.out.println("Try to do something");
throw new RuntimeException("RuntimeException");
} catch (Exception e) {
System.out.println("Catch Exception -> " + e.getMessage());
// 终⽌当前正在运⾏的Java虚拟机
System.exit(1);
} finally {
System.out.println("Finally");
}

// 结果输出:
**Try to do something
Catch Exception -> RuntimeException**

1.2 程序所在的线程死亡

1.3 关闭 CPU

31.如何使⽤ try-with-resources 代替 try-catch-finally ?

1.1
(1) 适⽤范围(资源的定义): 任何实现 java.lang.AutoCloseable 或者 java.io.Closeable 的对象

(2)关闭资源和 finally 块的执⾏顺序: 在 try-with-resources 语句中,任何 catch 或 finally 块在声明的资源关闭后运⾏

(3)《Effective Java》中明确指出:
a.⾯对必须要关闭的资源,我们总是应该优先使⽤ try-with-resources ⽽不是 try-finally 。
b.随之产⽣的代码更简短,更清晰,产⽣的异常对我们也更有⽤。
c.try-with-resources 语句让我们更容易编写必须要关闭的资源的代码,若采⽤ try-finally 则⼏乎做不到这点。

1.2 Java 中类似于 InputStream 、 OutputStream 、 Scanner 、 PrintWriter 等的资源都需要我们调
⽤ close() ⽅法来⼿动关闭,⼀般情况下我们都是通过 try-catch-finally 语句来实现这个需求,
(1)
a.如下:

//读取⽂本⽂件的内容
Scanner scanner = null;
try {
scanner = new Scanner(new File("D://read.txt"));
while (scanner.hasNext()) {
System.out.println(scanner.nextLine());
 }
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (scanner != null) {
scanner.close();
 }
}

b.但是:当然多个资源需要关闭的时候,使⽤ try-with-resources 实现起来也⾮常简单,如果你还是⽤ trycatch-finally 可能会带来很多问题。

(2)使⽤ Java 7 之后的 try-with-resources 语句改造上⾯的代码:
a.如下:

try (Scanner scanner = new Scanner(new File("test.txt"))) {
while (scanner.hasNext()) {
System.out.println(scanner.nextLine());
 }
} catch (FileNotFoundException fnfe) {
fnfe.printStackTrace();
}

b.另外:通过使⽤分号分隔,可以在 try-with-resources 块中声明多个资源。

如下:

try (BufferedInputStream bin = new BufferedInputStream(new FileInputStream(new
File("test.txt")));
BufferedOutputStream bout = new BufferedOutputStream(new
FileOutputStream(new File("out.txt")))) {
int b;
while ((b = bin.read()) != -1) {
bout.write(b);
 }
}
catch (IOException e) {
e.printStackTrace();
}
32.异常使⽤有哪些需要注意的地⽅?

1.1 不要把异常定义为静态变量,因为这样会导致异常栈信息错乱。
每次⼿动抛出异常,我们都需要⼿动 new ⼀个异常对象抛出。
如下:

public class Exceptions {
    // 错误的定义法
    public static BusinessException ORDEREXISTS = new BusinessException("订单已存在", 3001);
}


// 要修改成以下:

public class Exceptions {
    // 正确的定义法
    public static BusinessException orderExists() {
        return new BusinessException("订单已经存在", 3001);
    }
}

1.2 抛出的异常信息⼀定要有意义

1.3 建议抛出更加具体的异常⽐如字符串转换为数字格式错误的时候应该抛出NumberFormatException ⽽不是其⽗类 IllegalArgumentException 。

1.4 使⽤⽇志打印异常之后就不要再抛出异常了(两者不要同时存在⼀段代码逻辑中)
反例如下:

log.error("Runtime exception", e);
throw new RuntimeException(e);

这样实现的话,通常会把栈信息打印两次,导致你的日志看起来会让人很迷惑。

&& 另外参考
(1.1):http://events.jianshu.io/p/90c7170bd2b0
(1.4) 日志基础-003-打印日志的五不要与五要

&& 关于打印日志的15个建议:
https://wenku.baidu.com/view/7aa2a433fc00bed5b9f3f90f76c66137ee064ff3.html?

33.反射?

1.1 概念
在程序运行期间可以获取任意⼀个类的所有属性和⽅法,还可以调⽤这些⽅法和属性。

1.2 反射优缺点
(1)优点:灵活 ---- 可以动态的创建和使用对象(即是框架底层的核心)
(2)缺点:
<1> 增加了安全问题,⽐如可以⽆视泛型参数的安全检查(泛型参数的安全检查只发⽣在编译时)。
<2> 使用反射基本是解释执行,会降低执行的速度。
解决办法:关闭访问检查
通过调用Method、Field和Constructor对象的**setAccessible()**方法,去启用和禁用安全检查,参数值为true时,取消访问检查,可提高反射效率。

1.3 应用场景:框架底层
(1)框架中动态代理的实现依赖反射
如:JDK实现动态代理使用了反射类Method来调用指定方法,
代码如下:

public class DebugInvocationHandler implements InvocationHandler {
/**
* 代理类中的真实对象
*/
private final Object target;
public DebugInvocationHandler(Object target) {
this.target = target;
 }
public Object invoke(Object proxy, Method method, Object[] args) throws
InvocationTargetException, IllegalAccessException {
System.out.println("before method " + method.getName());
Object result = method.invoke(target, args);
System.out.println("after method " + method.getName());
return result;
 }
}

(2)注解的实现也用到了反射
基于反射分析类,然后获取到类/属性/方法/方法上参数的注解。

如:
使⽤ Spring 的时候 ,⼀个 @Component 注解就声明了⼀个类为 Spring Bean;
通过⼀个 @Value 注解就读取到配置⽂件中的值;

另外参考:https://blog.csdn.net/qq_46159183/article/details/120112117

34. SPI?

1.1 什么是SPI
(1)即 Service Provider Interface – “服务提供者的接⼝”,专⻔提供给服务提供者或者扩展框架功能的开发者去使⽤的⼀个接⼝。

(2)SPI 将服务接口和具体的服务实现分离开来,将服务调⽤⽅和服务实现者解耦,能够提升程序的扩展
性、可维护性。修改或者替换服务实现并不需要修改调⽤⽅。

(3)很多框架都使⽤了 Java 的 SPI 机制,⽐如:Spring 框架、数据库加载驱动、⽇志接⼝、以及
Dubbo 的扩展实现等等。

1.2 SPI 和 API 有什么区别?
在这里插入图片描述

(1)⼀般模块之间都是通过通过接⼝进⾏通讯,那我们在服务调⽤⽅和服务实现⽅(也称服务提供者)之间引⼊⼀个“接⼝”。

(2)当实现⽅提供了接⼝和实现,我们可以通过调⽤实现⽅的接⼝从⽽拥有实现⽅给我们提供的能⼒,这就是 API ,这种接⼝和实现都是放在实现⽅的。

(3)当接⼝存在于调⽤⽅这边时,就是 SPI ,由接⼝调⽤⽅确定接⼝规则,然后由不同的⼚商去根据这个规则对这个接⼝进⾏实现,从⽽提供服务。

(4)举个通俗易懂的例⼦:公司 H 是⼀家科技公司,新设计了⼀款芯⽚,然后现在需要量产了,⽽市⾯上有好⼏家芯⽚制造业公司,这个时候,只要 H 公司指定好了这芯⽚⽣产的标准(定义好了接⼝标准),那么这些合作的芯⽚公司(服务提供者)就按照标准交付⾃家特⾊的芯⽚(提供不同⽅案的实现,但是给出来的结果是⼀样的)。

1.3 SPI 的优缺点?
(1)优点
⼤⼤地提⾼接⼝设计的灵活性

(2)缺点
a.需要遍历加载所有的实现类,不能做到按需加载,这样效率还是相对较低的。

b.当多个 ServiceLoader 同时 load 时,会有并发问题。

35.什么是序列化、反序列化?

1.1 概述
在这里插入图片描述

(1)序列化: 将数据结构或对象转换成⼆进制字节流的过程(java主要是对象,而c++还有struct结构体)

(2)将在序列化过程中所⽣成的⼆进制字节流转换成数据结构或者对象的过程

(3)综上:序列化的主要⽬的是通过⽹络传输对象或者说是将对象存储到⽂件系统、数据库、内存中。

1.2 如果有些字段不想进⾏序列化怎么办?

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

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

注意以下几点:
<1> 只能修饰变量,不能修饰类和⽅法。
<2> 修饰的变量,在反序列化后变量值将会被置成类型的默认值。例如,如果是修饰 int类型,那么反序列后结果就是 0 。
<3> static变量因为不属于任何对象(Object),所以⽆论有没有 transient 关键字修饰,均不会被序列化。

36.集合框架?

框架图如下:


在这里插入图片描述


在这里插入图片描述


1.1 为什么要使⽤集合?
(1)当我们需要保存⼀组类型相同的数据的时候,我们应该是⽤⼀个容器来保存,这个容器就是数组,但
是,使⽤数组存储对象具有⼀定的弊端:
<1> 声明数组必须声明长度,声明之后,⻓度就不可变了。

<2> 声明数组时必须声明该数组存储的数据的类型,声明之后,只能存储声明类型的数据类型

<3> 数组存储的数据是有序的、可重复的,特点单⼀。

(2)但是实际开发中,存储的数据的类型是多种多样的,于是出现了集合。
<1> 集合可以存储存储不同类型、不同数量、多种特点的对象

<2> 还可以保存具有映射关系的数据。

1.2 Java集合如何选择

主要根据场景需要和集合的特点来选⽤

(1)需要根据键值获取到元素值时就选⽤ Map 接⼝下的集合:
<1> 不需要排序时就选择 HashMap

<2> 需要排序时选择 TreeMap

<3> 需要保证线程安全就选⽤ConcurrentHashMap

(2)当我们只需要存放元素值时,就选择实现 Collection 接⼝的集合

<1> 不需要保证元素唯⼀时就选择实现 List 接⼝的⽐如 ArrayList 或
LinkedList

<2> 需要保证元素唯⼀时选择实现Set 接⼝的集合,如HashSet 或 TreeSet

1.3 集合框架底层数据结构总结
(1)List
<1> ArrayList(有序、可重复) : Object[] 数组,

<2> LinkedList(有序、可重复的) : 双向链表(JDK1.6 之前为循环链表,JDK1.7 取消了循环),

<3> Vector : Object[] 数组

(2)Set
<1> HashSet (⽆序,唯⼀):
基于 HashMap 实现的,底层采⽤ HashMap 来保存元素

<2> LinkedHashSet (有序,唯⼀) :
LinkedHashSet 是 HashSet 的⼦类,并且其内部是通过 LinkedHashMap 来实现的。
有点类似于我们之前说的 LinkedHashMap 其内部是基于 HashMap 实现⼀样,不过还是有⼀点点区别的

<3> TreeSet (有序,唯⼀):
红黑树(⾃平衡的排序⼆叉树)

(3)Queue
<1> PriorityQueue : Object[] 数组来实现⼆叉堆

<2> ArrayQueue : Object[] 数组 + 双指针

1.4 List, Set, Queue, Map 四者的区别?
(1)List (对付顺序的好帮⼿): 存储的元素是有序、可重复的。

(2)Set (注重独⼀⽆⼆的性质): 存储的元素是无序、不可重复的。

(3)Map (⽤ key 来搜索的专家):
使⽤键值对(key-value)存储,key 是无序、不可重复的,value 是无序、可重复的,每个键最
多映射到⼀个值。

(4)Queue (实现排队功能的叫号机):
按特定的排队规则来确定先后顺序,存储的元素是有序、可重复的。

1.5 Map
<1> HashMap :
JDK1.8 之前 HashMap 由数组+链表组成的,数组是 HashMap 的主体,链表则
是主要为了解决哈希冲突⽽存在的(“拉链法”解决冲突)。
JDK1.8 以后在解决哈希冲突时有了比较⼤的变化,当链表⻓度⼤于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的⻓度⼩于 64,那么会选择先进⾏数组扩容,⽽不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间

<2> LinkedHashMap :
LinkedHashMap 继承⾃ HashMap ,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。
另外, LinkedHashMap 在上⾯结构的基础上,增加了⼀条双向链表,使得上⾯的结构可以保持键值对的插⼊顺序。同时通过对链表进⾏相应的操作,实现了访问顺序相关逻辑。

<3> Hashtable : 数组+链表组成的,数组是 Hashtable 的主体,链表则是主要为了解决哈希冲突⽽存在的

<4> TreeMap : 红黑树(⾃平衡的排序⼆叉树)


37.Java IO 流?

类图如下:
在这里插入图片描述
1.1 概念
(1)即Input/Output ,输⼊和输出。

(2)数据输入到计算机内存的过程即输⼊,反之从计算机内存输出到外部存储(⽐如数据库,⽂件,远程主机)的过程即输出。

(3)另外根据数据的处理方式⼜分为字节流和字符流。

(4)Java IO 流的 40 多个类都是从如下 4 个抽象类基类中派⽣出来的。
<1> InputStream / Reader : 所有的输⼊流的基类,前者是字节输⼊流,后者是字符输⼊流。
<2> OutputStream / Writer : 所有输出流的基类,前者是字节输出流,后者是字符输出流。

1.hashmap:

1.1 转红黑树条件:
a.数组长度大于等于64(默认16,要经过2次扩容–当达到16*默认扩容因子0.75=12就扩容)
b.链表长度大于8
1.2 hashmap先计算hash值,再用hash值计算下标。

2.sleep与await:

1.1 sleep是线程方法,await是object方法
1.2 sleep不会释放锁,而await会
1.3 sleep不依赖于同步器synchronize,而await要
1.4 sleep不需要被唤醒,而await要

3.hashtable与hashmap:

1.1 前者线程安全,而后者不是
1.2 前者不可以把null作为key,而后者可以,并放在第一个节点上
1.3 两者扩容因子默认都是0.75;前者初始容量为11,扩容是当前容量2+1;后者初始容量是16,扩容是当前容量2

4.cookie与session的区别:

前者cookie:
1.1 存放在客户端浏览器中;
1.2 大小受限制,单个不超过4K,一般1个站点最多保存20个;
1.3 String类型(ASCII);
1.4 不太安全(可以加密)
1.5 可设置长期有效
1.6 支持跨域名访问
后者session:
2.1 存放在服务器中;
2.2 大小一般不受限制;
2.3 Key-Value(Object类型);
2.4 安全性更高;
2.5 依赖JSESSIONID这个cookie,过期时间默认-1,即关闭窗口session会失效,因而不能达到长期有效。
2.6 不支持跨域名访问

二、JVM

4.用户线程与内存线程:

1.1 多对一:不需要切换,线程创建、调度、同步非常快;但是如果其中一个用户线程阻塞会造成其他线程无法执行,且无法像内核线程一样实现较完整的调度、优先级;
1.2 一对一:java的jvm几乎把所有对线程的操作都交给了系统内核操作,线程真正启动顺序不一定是按我们启动的顺序,会引起用户态和内核态的频繁切换;如果系统出现大量线程,会降低系统性能。

5.运行时数据区:

在这里插入图片描述

6.内存回收:

在这里插入图片描述

7.内存溢出:

1.1 栈溢出


1.2 堆溢出
在这里插入图片描述
》第8行设置会在堆溢出会导出Damping日志

1.3 方法区溢出
1.4 本机直接内存溢出

8.内存泄漏:

1.1 不使用的内存,却没有被释放;
1.2 每一次请求进来或者每一次操作处理都分配了内存,却有部分不能回收(或未释放),随着请求越来越多,内存泄漏就会越来越严重,必然造成内存溢出。
1.3 内存泄漏一般是资源管理问题或者程序bug,内存溢出则是内存空间不足和内存泄漏的最终结果。

9.hotspot对象头包含哪些部分:

在这里插入图片描述

10.根据类分析对象的内存占用:

在这里插入图片描述

11.jvm启动参数:在哪里配?

在这里插入图片描述

12.堆空间最大值设置:
13.G1垃圾收集器特点:

1.空间整合:哪块垃圾最多优先清理
在这里插入图片描述
2.多线程+并发+可预测停顿
在这里插入图片描述

14.排查OOM的方法:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

14.jvm的相关命令工具:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

15.java8默认垃圾收集器:

在这里插入图片描述

16.并行垃圾收集器:

在这里插入图片描述

17.swt:

在这里插入图片描述
安全点:方法调用、循环跳转、异常跳转;设置标志位,并不断轮询,主动停止。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

17.cpu使用率飙升,怎么排查?

先通过top命令找到cpu使用率高的线程;top -p 进程号;该界面输入H查找最高cpu的线程;执行jstack 进程号做dump输出线程信息; 同时根据线程的16进制找到对应的堆信息,然后再找出对应的代码
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
6、最后根据线程信息定位到具体代码

18.垃圾回收器的三色标记:

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

19.类加载、类加载器:

在这里插入图片描述
在这里插入图片描述

三、消息中间件

1.为什么用rocketmq:

在这里插入图片描述
在这里插入图片描述

3.各种消息列队:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4.mq的消息重复:

在这里插入图片描述
在这里插入图片描述

5.mq的消息重复:

在这里插入图片描述
在这里插入图片描述在这里插入图片描述

4.解决MQ重复消息:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

6.roketmq性能优化:

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

四、缓存

1.Redis概念:

Nosql,C编写,包含多种数据结构(字符串、列表、集合、散列表、有序集合),支持网络,基于内存还能持久化性能高效每秒可以处理超过10万次读写操作,遵守BSD协议,支持分布式易拓展、支持多种语言的k-v存储数据库。

2.Redis数据类型:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.Redis与Memcached区别:

在这里插入图片描述

3.Redis的应用场景:

在这里插入图片描述
在这里插入图片描述

4.Redis为什么速度:

在这里插入图片描述

5.为什么用Redis而不用map/guava做缓存:

在这里插入图片描述

6.Redis的持久化机制:

在这里插入图片描述
在这里插入图片描述

7.如何保持缓存与数据库双写时一致:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

8.缓存穿透:

在这里插入图片描述

五、并发编程

Java之并发编程(一)----点击跳转



六、分布式

分布式(一)----点击跳转



七、数据库

数据库及缓存之MySQL(一)----点击跳转



本篇文章主要参考链接如下:

参考链接1

参考链接2



持续更新中…

随心所往,看见未来。Follow your heart,see light!

欢迎点赞、关注、留言,一起学习、交流!

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值