# Java
## 初识Java
### 一次编译,到处运行
Java 是一种编译解释型语言,先编译,后解释。
> 编译型:将源代码编译成平台识别的机器码,然后在该平台独立运行,运行效率高。
>
> 解释型:使用平台相关的解释器对源代码逐行解释成机器码,并立即执行,平台移植性好。
>
> Java 则是在保证移植性的前提下,做出尽可能的优化用于提高运行效率,所以同时需要编译和解释。
![1565427739913](Java.assets/1565427739913.png)
> **字节码的执行基础 JRE**
>
> - Java 核心类库:包含了 Java 语言的核心特性
> - JVM :所有平台的 JVM 向上提供相同的编程接口,从而可以识别平台无关的字节码,向下则适应不同平台的执行环境。
> - 其他环境支持文件(类加载器、字节码校验器...)
### Hello Java
```java
public class HelloWorld {
public static void main(String[] args) { //Java程序执行的入口
System.out.println("Hello Java!");
}
}
```
### javac 命令
```bash
javac HelloWorld.java
# javac [options]
# -cp xxx:xxx 设置Java类的搜索路径
# -d xxx 设置字节码文件的存放路径
# -Jxxx 向java启动器传递参数
```
### java 命令
```bash
java HelloWorld
# java [options] [args...] !!必须包含main方法
# -cp xxx:xxx
# -Dxxx=xxx 设置系统参数
```
### jar 命令
> JAR 文件是一种 Java 代码资源归档文件,以 ZIP 格式构建,默认包含 META-INF/MANIFEST.MF 清单文件。如果添加到类搜索路径中,会被当成一个路径,搜索类文件。
jar 命令用于创建和维护 JAR 文件。
```bash
# jar 命令示例
# 创建 jar包
jar -c -v -f /path/to/jarfile/jarname.jar -e MainClassName -C /path/to/classes
# 查看 jar包
jar -t -v -f /path/to/jarfile/jarname.jar
# 解压 jar包
jar -x -v -f /path/to/jarfile/jarname.jar
```
> 设置Java命令行的显示语言:添加环境变量`JAVA_TOOL_OPTIONS` = `-Duser.language=en`
## Java 基础语法
### 注释
```java
单行注释
// xxx
多行注释
/*
xxx
xxx
*/
文档注释
/**
* xxx
* xxx
*/
```
> **文档注释**
>
> javadoc 命令用于生成 API 文档,它会自动识别源代码元素之前的文档注释。
>
> ```bash
> # javadoc [options]
> # -d API文档存放路径
> ```
>
> 以@开头东东被称之为Javadoc文档标记,是JDK定义好的,如@author、@version、@since、@see、@link、@code、@param、@return、@exception、@throws等。
### 变量
Java 语言是一门强类型语言。
- 所有的变量必须先声明类型,后使用
- 指定类型的变量只能接受匹配的类型值
- 变量名需要满足的条件:
- 由字母、数字、下划线`_`、美元符`$`组成
- 不能以数字开头
- 不能直接使用`_`作为变量名
- 不能与 Java 关键字同名
- 被 final 修饰的变量只能被赋值一次
```java
type varName[ = initValue];
```
Java 的数据类型分为两种:
| 数据类型 | 种类 | 访问方式 |
| -------- | ---------------------------------------------------- | ------------ |
| 基本类型 | byte、short、int、long、char、float、double、boolean | 直接访问 |
| 引用类型 | 数组、类 | 通过指针访问 |
#### 整型
| 类型名称 | 占用位数 | 表示范围 |
| -------- | -------- | ------------------------------ |
| byte | 8 bits | -128 ~ 127 |
| short | 16 bits | -32,768 ~ 32,767 |
| int | 32 bits | -2,147,483,648 ~ 2,147,483,647 |
| long | 64 bits | -9.22e18 ~ 9.22e18 |
> **整型直接量表示法**
>
> - 十进制 `nnn`
>
> - 二进制 `0Bnnn`
>
> - 八进制 `0nnn`
>
> - 十六进制 `0Xnnn`
>
> - 所有整形直接量默认按照 int 类型来处理,后缀`L`可以改为 long 处理
>
#### 字符型
char 代表字符型。Java 采用 16 位的 Unicode 字符集作为编码方式。
> **字符型直接量表示法**
>
> - 单个字符 `'A'`
> - 转义字符 `'\n'`
> - Unicode 值 `\uXXXX`
> - 整形直接量直接赋值
#### 浮点型
| 类型名称 | 占用位数 | IEEE 754 标准 |
| -------- | -------- | ------------- |
| float | 32 bits | 1/8/23 |
| double | 64 bits | 1/11/52 |
> **进制转换中的精度损失**
>
> 当浮点数进行从十进制转换成二进制时则可能存在精度损失。解决方案是采用 BigDecimal 类。
> **浮点型直接量表示法**
>
> - 小数点 `0.1`
> - 科学计数法 `5.12E2`
> - 所有浮点型直接量默认按照 double 类型来处理,后缀`F`可以改为 float 处理
> - 浮点数支持正无穷大、负无穷大、非数
>
#### 布尔型
boolean 代表布尔型,占用 8 bits ,使用 true / false 表示
#### 基本类型转换
![1565522372900](Java.assets/1565522372900.png)
#### 数组
#### 类和接口
### 运算符
运算符用于实现最基本的逻辑功能。
#### 分隔符
#### 算术运算符
- `+` 加、字符串连接
- `-` 减
- `*` 乘
- `/` 除
- `%` 求余
- `++` 自加
- `--` 自减
> - 浮点数可以对 0.0 作除或者求余
> - 自加和自减:放在左,先操作;放在右,后操作
#### 位运算符
- `&` 按位与
- `|` 按位或
- `~` 按位非
- `^` 按位异或
- `<
- `>>` 右移补符号位
- `>>>` 右移补零
#### 赋值运算符
- `=` 直接赋值
- `+= -= *= /= %= &= |= ^= <<= >>= >>>=`
#### 比较运算符
- `>`
- `
- `>=`
- `<=`
- `==`
- `!=`
#### 逻辑运算符
- `&&`
- `&`
- `||`
- `|`
- `!`
- `^`
#### 条件运算符
- `xxx?xxx:xxx`
### 流程控制
#### 分支结构
#### 循环结构
## 类与对象
### 面向对象思想
“物以类聚”,面向对象开发思想是从现实世界中客观存在的事物出发,归纳总结事物的公共特征,加以描述形成类,再使用类创建各种各样的事物。
- 类是代表了具有某一特征的一类事物,比如人类、猫类。
- 对象则是类的具体存在,比如叫张三的人、叫小黄的猫。
![1565512177565](Java.assets/1565512177565.png)
面向对象的程序语言具有三个基本特征:
- 封装:图中的虚线代表封装,隐藏内部细节,选择性地暴露部分属性和行为
- 继承:图中类B包含类A代表继承,类B就具有的类A的暴露出来的属性和方法
- 多态:图中红箭头代表多态,类A对象可以表现出类B对象的行为特征
> 面向对象编程之前,要经过分析和设计,这个过程形成了现在统一的建模语言 UML。运用好 UML 可以加大的提高软件开发效率。
### 定义类
类中可以定义5种成员:变量、初始化块、构造器、方法、内部类(或接口、枚举)
- 变量指定了属性
- 方法指定了行为
- 初始化块负责类或对象的初始化
- 构造器负责创建对象
```java
[] class extends implements {
[] [ = ]; //变量
[static] {...} //初始化块
[] () {...} //构造器
[] () {...} //方法
[] class {...} //内部类
}
```
随后就可以使用 new 选择合适的构造器创建对象了。
```java
//匿名使用
new MyClass().xxx();
//引用变量使用
MyClass mc = new MyClass();
mc.xxx();
```
#### 类成员与对象成员
在内存中,其实类也是一种特殊的对象,它们都是 java.lang.Class 的实例对象。因此类也可以具有自己的属性和行为,也需要初始化。Java 使用 static 修饰符界别类成员和对象成员。
| | 类成员 | 对象成员 |
| -------- | ---------------- | ---------------- |
| 修饰符 | static | 无 static |
| 所属 | 整个类 | 单个对象实例 |
| 访问方式 | 通过类名进行访问 | 创建对象进行访问 |
| 生命周期 | 长 | 短 |
| 相互访问 | 不能访问对象成员 | 能够访问类成员 |
> **构造器**只是负责创建对象,无所谓类成员和对象成员,所以不能用 static 修饰,不需要通过类或者对象访问。
#### 变量成员
在类中定义的变量称为变量成员。
- 如果想让某个变量成员只允许赋值一次,可以使用 final 修饰。
> **局部变量**:在代码块`{...}`中定义的变量只在代码块中生效,并且必须先显式初始化才能使用。流程控制、方法、构造器等充斥着很多代码块,代码块也可以单独使用,可以有效释放不用的变量空间。
>
> **形参变量**:在形参列表定义的变量只在相对应的方法或者构造器中生效,初始化由传入实参负责。
#### 方法成员
- 方法不能独立存在,必须定义在合适的类中。
- 方法如果没有返回值,则使用 void 声明。
- 方法参数传递采用的是值传递,基本类型参数传递的是值本身,引用类型参数传递的是指针地址。
- 编译器根据方法名和方法形参列表来识别方法,如果两个方法的方法名和形参列表都相同,将无法通过编译。
> 方法名相同但形参不同的情况称为**方法重载**,调用重载方法时会找到所有满足形参类型的方法,然后根据最小类型原则选择一个。如果按照最小类型原则还无法确定将无法通过编译。
- 支持递归调用。
#### 初始化块成员
- 赋初值应该写在变量定义的后面,而初始化块应该被用来完成其他相关的初始化操作。
- 初始化块和定义变量赋初值都是有前后顺序的。
#### 构造器成员
- 构造器是一种特殊的方法,直接使用类名作为方法名。
- 返回值就是新创建的对象,无需显示指定。
- 方法体再负责初始化该对象并返回。
- 如果有父类,优先执行父类的构造器,创建父类对象。
- 编译器根据方法形参列表来识别构造器。
> 类与对象的初始化流程
>
> 1. 类的默认初始化
> 2. 执行类的初始化块
> 3. 对象的默认初始化
> 4. 执行对象的初始化块
> 5. 执行构造器初始化
> 6. 返回创建的对象
### 封装
| 修饰符名称 | 同一个类中 | 同一个包中 | 子类中 | 全局范围内 |
| ---------- | :--------: | :--------: | :----: | :--------: |
| private | O | X | X | X |
| default | O | O | X | X |
| protected | O | O | O | X |
| public | O | O | O | O |
> default 表示不加任何访问控制符
> 外部类也可以使用访问控制符来修饰,支持 public 和 default。当外部类使用 public 修饰时,文件名必须和该类名一致,因此一个源文件中只能有一个 public 类。
### 继承
- 类的封装会暴露一些属性或者行为,这些都可以被子类继承
- this 代表当前对象,super 代表当前对象的父类对象。
- 在构造器中,this(xxx) 调用其他构造器,super(xxx) 调用父类构造器。
- 被 final 修饰的类不可以被继承。
> **初始化的先后顺序**
>
> ![1565855768599](Java.assets/1565855768599.png)
> 继承与组合
### 多态
![1565699780886](Java.assets/1565699780886.png)
> Java 是强类型语言,引用变量都有类型属性,这个被称为编译时类型,当对变量赋值时,所指向的类型属性被称为运行时类型。运行时类型必须是编译时类型的子类或者本身。
可以通过变量调用编译时类型的方法,但如果运行时类型重写了编译时类型的方法,则变量会优先调用运行时类型的方法。由于运行时类型不固定,相同类型的变量在调用同一个方法时就可能表现出不同的行为特征,这就是多态。
- 方法重写必须满足,形参列表相同,返回值类型相同或更小,访问控制符相同或更大,捕获的异常类型相同或者更小,异常数量相同或更少。
- 多态只适用于成员方法,对于成员变量来说,不会优先访问运行时类型的重名成员变量。
- 如果不想让某一个成员方法表现出多态的特性,可以用 final 修饰,阻止方法被重写。
> **强制类型转换**
>
> | | 自动类型转换 | 强制类型转换 |
> | -------------- | ------------------------ | ------------------- |
> | 基本类型 | 小类型 `-->` 大类型 | 大类型 `-->` 小类型 |
> | 引用类型(类) | 子类 `-->` 父类 (多态) | 父类 `-->` 子类 |
>
> 强制类型转换的格式 `(targetType)xxx`
#### 抽象类
如果父类中的某方法必须在子类中才知道如何实现,这时就用到了“抽象类”。
#### 接口
接口是彻底抽象的一种类,因为彻底抽象,放宽了限制,原本类仅支持单继承,接口则支持多实现。
### 内部类
内部类可以更好地封装在外部类中。
#### 非静态内部类
- 支持4种访问控制模式
- 可直接访问外部类的所有成员
- 不允许在非静态内部类定义静态成员。
- 外部非静态类成员可以通过创建内部类对象间接访问内部类成员
- 同名对策:外部类名.this.外部类非静态成员名;外部类名.外部类静态成员名
- 在外部类之外访问非静态内部类
```java
Out.In varName = OutInstance.new In();
```
- 定义非静态内部类的子类
```java
public class SubClass extends Out.In {
//必须显式定义
public SubClass(Out out) {
out.super(xxx)
}
}
```
#### 静态内部类
- 支持4种访问控制模式
- 可直接访问外部类的所有静态成员
- 允许在静态内部类定义静态成员。
- 外部类成员可以通过内部类名或者创建内部类对象间接访问内部类成员
- 同名对策:外部类名.外部类静态成员名
- 在外部类之外访问静态内部类
```java
Out.StaticIn varName = new Out.StaticIn();
```
- 定义静态内部类的子类
```java
public class SubClass extends Out.StaticIn {...}
```
#### 匿名内部类对象
只需要使用一次的类,会立即创建对象。
```java
new 接口名() {...}
new 抽象类名(形参列表) {...}
```
> 被匿名内部类访问的局部变量,会自动被 final 修饰。
### 枚举类
枚举类的对象是有限且固定的。
```java
public enum Season {
SPRING,SUMMER,FALL,WINTER; //必须写在开头处
}
// 外部使用 . 来使用
```
- 枚举类可以定义成员变量、方法、构造器。
- 构造器只能是私有的,且如果有参数,每一个枚举值也要加上实参。
- 如果包含抽象方法或者实现了接口,每一个枚举值也要实现这些抽象方法和接口方法。
- 枚举类可以实现多个接口,但不能继承,也不可以被继承。
### 垃圾回收
当某一对象失去所有引用时,就变成垃圾等待回收。
![1565683593252](Java.assets/1565683593252.png)
- 不要主动调用 finalize()
- 可能程序执行完毕也不会调用 finalize()
- `System.gc()` 通知系统进行垃圾回收
## Java 高级语法
### 包机制
### Lambda 表达式
Lambda 表达式已被很多编程语言所支持,它本身代表一个匿名函数。但是 Java 中,方法必须定义在类里。为了支持Lambda 表达式特性,Java 提出了“函数式接口”的概念,即只包含一个抽象方法的接口,用于支持 Lambda 表达式。
```java
interface $函数式接口名 {
//一个抽象方法
$返回值类型 $方法名($形参列表);
}
```
```java
//对应哪个函数式接口则取决于上下文
($形参列表) -> { $抽象方法体 }
```
- 如果参数只有一个,可以省略括号。
- 形参列表中的类型声明可以省略。
- 如果方法体只有一句,可以省略花括号,如果有,则可以省略句中的 return 。
- 进一步,根据这一句不同情形,可以使用 `::` 让其更加简洁
| 情形 | 原始 Lambda 表达式 | 简化后的表达式 |
| -------------- | ------------------------------------- | ---------------- |
| 调用类方法 | `(a,b,...) -> 类名.类方法(a,b,...)` | `类名::类方法` |
| 调用对象方法 | `(a,b,...) -> 对象.对象方法(a,b,...)` | `对象::对象方法` |
| 调用类对象方法 | `(a,b,...) -> a.对象方法(a,b,...)` | `类名::对象方法` |
| 调用构造器 | `(a,b,...) -> new 类名(a,b,...)` | `类名::new` |
### 泛型
> 引用类型的强制类型转换有个弊端,编译时并不会去检查是否可以转换成功,运行时就容易引起 ClassCastException 异常。Java 5 以后,引入了泛型,将类型检查提前到了编译阶段,让程序更加健壮。
泛型的原义是“参数化类型”,即在定义类或者方法时指定一个参数化的类型,调用时则传入具体的类型,提供语言内置的类型检查机制,如果出现与类型不符的情况将无法通过编译。
#### 泛型类、接口
```java
$修饰符 class/interface $类名 { // 泛型声明在类名之后
// 内部或者继承或者实现里都可以使用类型 E T,就像函数里使用参数一样。
}
```
- 创建泛型类对象时需要显式指定类型参数值。
- 声明的泛型不能直接作为父类或者接口,但可以作为父类或者接口的泛型参数使用。
- 声明的泛型不能创建泛型类型数组,编译器无法确定实际类型,也就无法分配内存。
- 泛型类这个概念本身就是针对对象而言的,所有的类成员不允许使用泛型。
#### 泛型方法
```java
public class MyClass {
//泛型方法
$修饰符 $返回值类型 $方法名($形参列表) { //泛型声明在修饰符之后
//内部或返回值类型或形参列表都可以使用类型 E T
}
}
```
- 访问泛型方法时不需要显式指定类型参数值,编译器将根据调用上下文来推断类型参数值。
- 访问泛型方法时也可以显式指定类型参数值,在方法名前添加`<>`即可
- 声明的泛型能够直接作为返回值类型、形参类型。
- 声明的泛型允许创建泛型类型数组。
- 泛型方法这个概念被用来表示方法的形参与形参、形参与返回值之间的类型依赖关系。
> **泛型构造器**同泛型方法的用法一样。
#### 类型通配符
在使用泛型类的过程中,必须向运行时类型传入具体的类型,而 Java 允许向编译时类型传入一个类型范围,比如是某类的子类、是某类的父类等等。此时就需要用到类型通配符 `?` 。
```java
MyClass> mc = new MyClass();
MyClass extends Father> mc = new MyClass();
MyClass super Child> mc = new MyClass();
```
由于使用通配符,导致泛型的类型不确定,所以变量无法访问一切与泛型相关的方法成员和变量成员,比如List> list 不可以访问 add() 方法,但可以访问 get() 方法。
> 声明泛型时也可以使用 extends、super 来限定泛型的范围,并且能够访问范围上限类型里面的方法成员和变量成员。
#### 原始类型
如果没有为泛型类指定实际的类型参数,将成为“原始类型”,类型参数为上限类型。
- 任何泛型类对象都可以赋值给原始类型变量,这称为“类型擦除”
- 原始类型对象可以赋值给任何泛型类变量,这称为“类型转换”,但系统会发出警告。
- Java 不允许直接创建泛型数组。如果想要使用泛型类数组,必须通过原始类型创建:
```java
List[] lsa = new ArrayList[10];
lsa[0] = new ArrayList();
...
```
### 异常处理
| | 含义 | 程序流 |
| ------------ | --------------------- | -------- |
| Checked 异常 | 继承 Exception | 强制处理 |
| Runtime 异常 | 继承 RuntimeException | 无需处理 |
#### 异常处理机制
```java
class MyClass {
//方法、构造器
xxx xxx xxx(xxx) throws XxxException {
//...
try {
//...
} catch (XxxException e1) {
//...
} catch (XxxException e2) {
//...
} finally {
//...
}
}
}
```
1. try `()` 中创建可自动回收的资源
2. 运行 try 块代码
3. 遇到异常,不再继续执行 try 块,依次寻找 catch 块是否与异常匹配
4. 如果匹配,则执行相应的 catch 块,再执行 finally 块
5. 如果未匹配,则直接执行 finally 块
6. 方法体可以继续抛出未处理的异常,交给外部程序处理。
> Check 异常必须被 catch 或者被 throws 。Runtime 异常则没有限制,但是如果没有得到处理,将直接终止程序运行。
> 如果 catch 的多个异常中有父子关系,需将子类异常放到前面。
> 方法体抛出异常,当重写该方法时,抛出的异常只能更小,且只能更少。
#### 异常类
![1565832450216](Java.assets/1565832450216.png)
Throwable 常用方法:
- `String getMessage()` 返回此throwable的详细消息字符串。
- `void printStackTrace()` 将此throwable和其追溯打印到标准错误流。
- `void printStackTrace(PrintStream s)` 将此throwable和其追溯打印到指定的打印流。
自定义异常类:
```java
public class MyException extends Exception/RuntimeException/XxxException {
public MyException() {}
public MyException(String msg) {
super(msg);
}
}
```
#### 可自动回收资源
### 注解
![1565943858961](Java.assets/1565943858961.png)
对于小型项目来说,开发的类集通常直接拿来用即可,但是这种项目随着规模不断扩大,会出现功能单一、扩展性差的问题。现代化的工程项目中,用户扮演的往往是加工的“小角色”,它不仅需要使用其中的功能类实现某种功能,还需要使用工具类根据配置信息进行后处理,才能得出想要的结果。而注解就可以轻松地完成“加工”的过程。
注解是代码里的代码元素的特殊标记,可以在编译、类加载、运行时被读取,并作出相应的处理。
```java
public @interface $注解名 {
//定义多个注解
$数据类型 $数据名();
$数据类型 $数据名() default xxx;
}
//注解的使用
$注解名($数据名=xxx,...)
```
写在代码元素之前的注解可以被读取到。代码元素类 Class、Constructor、Field、Method、Package,都实现了注解元素接口,可以通过其中的方法获取注解:
- `default boolean isAnnotationPresent(Class extends Annotation> annotationClass)` 判断是否存在
- ` T getAnnotation(Class annotationClass)` 获取
- ` T getAnnotations()` 获取全部
获取注解后可通过 `注解对象.$数据名()` 访问元数据信息。
#### 内置注解
## Java 基础类
### System
### Object
- `String toString()` 返回对象的字符串表示形式。如果不重写,默认返回“类名@hashCode”。
- `boolean equals(Object obj)` 判断对象是否相等,默认与 == 效果一致。通常会被重写。
```java
//equals方法模板
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj != null && obj instanceof MyType)) {
MyType cmp = (MyType) obj;
//...
}
return false;
}
```
- `int hashCode()`
### 包装类
八种基本类型不具备面向对象的特征,而八种对应的包装类则具有面向对象的特征:
| 基本类型 | 包装类 |
| -------- | --------- |
| byte | Byte |
| short | Short |
| int | Integer |
| long | Long |
| float | Float |
| double | Double |
| char | Character |
| boolean | Boolean |
> JDK 1.5 之后提供了自动装箱和自动拆箱的功能
> **Integer 的缓存陷阱**
>
> 系统会对 -128 ~ 127 之间的 Integer 对象进行缓存,下一次新建相同数值的 Integer 对象时会直接取出现成的缓存对象使用。
> **包装类对象的大小比较**
>
> 基本类型可以直接用比较运算符进行比较,但包装类对象不行。因为增添了对象的特性,所以应该用对象的处理方式来进行比较,那就是调用实例方法 `compareTo(xxx)`
### 字符串类
> 基本类型和字符串之间的转换
>
> - 基本类型 >> 字符串
> - `String.valueof(123)`
> - `123 + ""`
> - 基本类型 << 字符串
> - `Integer.parseInt("123")`
> - `Integer.valueof("123")`
### 数学类
### 日期时间类
### 正则表达式
### 国际化
## 集合
Java集合被用作对象元素的容器,特点如下:
- 可以存储数量不等的对象,但不支持存储基本类型
- 封装了常用的数据结构,功能丰富
- 支持泛型元素
根据元素**有无映射关系**,集合类拥有两个根接口:Collection 和 Map
### Collection
![1566008108534](Java.assets/1566008108534.png)
Collection 接口的主要方法:
- `boolean add(E e)`
- `boolean addAll(Collection extends E> c)`
- `void clear()`
- `boolean contains(Object o)`
- `boolean containsAll(Collection> c)`
- `boolean isEmpty()`
- `Iterator iterator()`
- `boolean remove(Object o)`
- `boolean removeAll(Collection> c)`
- `default boolean removeIf(Predicate super E> filter)`
- `boolean retainAll(Collection> c)`
- `int size()`
#### Set
没有提供任何多余的方法,只是实现上,Set 不允许添加重复元素。
> 当可变元素添加到Set中时,通常不建议改变元素的属性。
##### HashSet
HashSet 利用 Hash 算法进行实现,通过 `hashCode()`和 `equals()`两个方法判断元素是否相等。
> **`hashCode()`方法**
>
> 该方法是 Object 的方法,重写方法如下:
>
> 1. 计算对象中每一个有意义的实例变量的哈希值
>
> | 实例变量类型 | 计算方式 |
> | ---------------------- | -------------------------------------------- |
> | boolean | `(f?1:0)` |
> | byte、short、char、int | `(int)f` |
> | long | `(int)(f^(f>>>32))` |
> | float | `Float.floatToIntBits(f)` |
> | double | `Double.doubleToLongBits(f)`(再用long的方法) |
> | 引用类型 | `f.hashCode()` |
>
> 2. 对每一个哈希值乘以不同的质数再相加,即可得到最终的哈希值
> 当两个对象通过equals()方法比较返回true时,这两个对象的hashCode()方法返回值应该相等。
###### LinkedHashSet
LinkedHashSet 对 HashSet 进行封装,利用链表的方式维护元素的添加顺序。
##### SortedSet
SortedSet 接口的主要方法:
- `E first()`
- `E lase()`
- `SortedSet headSet(E toElement)`截取前部分(不包含 to)
- `SortedSet subSet(E fromElement, E toElement)`截取中部分(包含 from ,不包含 to)
- `SortedSet tailSet(E fromElement)`截取后部分(包含 from)
- `Comparator super E> comparator()` 如果使用定制排序,则返回比较器对象,否则返回 null
###### TreeSet
TreeSet 利用红黑树算法实现。
- `E lower(E e)`
- `E higher(E e)`
###### 排序
- 自然排序:元素需要实现 `Comparable` 接口的唯一方法`int compareTo(T o)`
- 定制排序:利用构造器传入指定的 `Comparator`比较器对象
> `Comparator` 是函数式接口,唯一方法为`int compare(T t1, T t2)`
#### List
要求元素有序,可以通过索引进行集合操作,主要方法有:
- `void add(int index, E element)`
- `boolean addAll(int index, Collection extends E> c)`
- `int indexOf(Object o)`
- `int lastIndexOf(Object o)`
- `E remove(int index)`
- `E set(int index, E element)`
- `E get(int index)`
- `default void sort(Comparator super E> c)`
- `ListIterator listIterator()`
- `List subList(int fromIndex, int toIndex)`
##### ArrayList
ArrayList 基于可更新数组来实现
- `void ensureCapacity(int minCapacity)`
- `void trimToSize()` 使数组长度等于集合长度
#### Queue
表示一个队列,主要方法有:
- `boolean offer(E e)`
- `E poll()`
- `E peek()`
##### PriorityQueue
优先队列,采用小顶堆的算法实现,和 TreeSet 一样可以使用自然排序和定制排序
##### Deque
表示一个双端队列,主要方法有:
- `void push(E e)`
- `E pop()`
###### ArrayDeque
Deque的数组实现是`ArrayDeque`。
###### LinkedList
Deque的链表实现是`LinkedList`,同时它也实现了 List 接口。
### Map
![1566032722574](Java.assets/1566032722574.png)
主要方法有:
- `void clear()`
- `boolean containsKey(Object key)`
- `boolean containsValue(Object value)`
- `V get(Object key)`
- `boolean isEmpty()`
- `Set keySet()`
- `Set> entrySet()`
> Map.Entry 是 Map 的内部类,主要的方法有:
>
> - (Map.Entry) `K getKey()`
> - (Map.Entry) `V getValue()`
> - (Map.Entry) `V setValue(V value)`
- `V put(K key, V value)`
- `void putAll(Map m)`
- `V remove(Object key)`
- `int size()`
- `Collection values()`
> 集合与 null 元素
#### HashMap
HashMap 利用 Hash 算法进行实现,通过 `hashCode()`和 `equals()`两个方法判断 Key 元素是否相等,通过`equals()`判断 Value 元素是否相等。
##### LinkedHashMap
LinkedHashMap 对 HashMap 进行封装,利用链表的方式维护映射元素的添加顺序。
#### SortedMap
主要方法有:
- `Comparator super K> comparator()`
- `SortedMap subMap(K fromKey, K toKey)`
- `SortedMap headMap(K toKey)`
- `SortedMap subMap(K fromKey, K toKey)`
- `SortedMap tailMap(K fromKey)`
- `K firstKey()`
- `K lastKey()`
##### TreeMap
TreeMap 利用红黑树算法实现。
- `K lowerKey(K e)`
- `K higherKey(K e)`
- `Map.Entry lowerEntry(K e)`
- `Map.Entry higherEntry(K e)`
- `Map.Entry firstEntry()`
- `Map.Entry lastEntry()`
### Collection 的遍历
#### foreach 遍历
```java
for (E e: $(Iterable对象)) { ... }
//简化形式
$(Iterable对象).forEach(obj -> {...});
```
> Collection 接口的父接口就是 Iterable
#### Iterator 遍历
Iterator 迭代器接口
- `boolean hasNext()`
- `T next()`
- `void remove()`
- `default void forEachRemaining(Consumer super T> action)`
```java
Iterator it = ...;
while (it.hasNext()) {
Xxx obj = it.next();
...
}
//简化形式
Iterator it = ...;
it.forEachRemaining(obj -> {...});
```
> ListIterator是 Iterator 的子接口
>
> - `boolean hasPrevious()`
> - `T previous()`
> - `void add(T t)`
> - `void set(T t)`
### Collections
## JDBC
JDBC(Java Database Connectivity) ,是一种可以执行 SQL语句的 Java API 标准
> SQL (Structured Query Language) 是关系数据库的标准操作语言
![jdbc](Java.assets/jdbc.png)
### API 介绍
- [DriverManager](https://docs.oracle.com/en/java/javase/11/docs/api/java.sql/java/sql/DriverManager.html) 驱动管理,搜索驱动的实现类,并创建连接
- [Connection](https://docs.oracle.com/en/java/javase/11/docs/api/java.sql/java/sql/Connection.html) 数据库连接,可以创建语句对象,控制事务
- [Statement](https://docs.oracle.com/en/java/javase/11/docs/api/java.sql/java/sql/Statement.html) SQL语句,执行SQL语句
- [PreparedStatement](https://docs.oracle.com/en/java/javase/11/docs/api/java.sql/java/sql/PreparedStatement.html) 预编译语句
- [CallableStatement](https://docs.oracle.com/en/java/javase/11/docs/api/java.sql/java/sql/CallableStatement.html) 可调用语句
- [ResultSet](https://docs.oracle.com/en/java/javase/11/docs/api/java.sql/java/sql/ResultSet.html) 查询结果集,可以处理结果集
- [RowSet](https://docs.oracle.com/en/java/javase/11/docs/api/java.sql/javax/sql/RowSet.html) 是可滚动、可更新、可序列化的结果集
- [ResultSetMetaData](https://docs.oracle.com/en/java/javase/11/docs/api/java.sql/java/sql/ResultSetMetaData.html) 结果集描述信息
- [DatabaseMetaData](https://docs.oracle.com/en/java/javase/11/docs/api/java.sql/java/sql/DatabaseMetaData.html) 数据库描述信息
### 一般流程
1. 加载数据库驱动
2. 获取数据库连接
3. 配置事务控制
4. 创建语句实例
5. 执行SQL语句
6. 处理结果集
7. 回收数据库资源
```java
Class.forName("com.mysql.cj.jdbc.Driver");
try (
Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/?=&...",
"",
"");
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("")
) {
}
```
### 数据库连接池
也称为数据源,用 [DataSource](https://docs.oracle.com/en/java/javase/11/docs/api/java.sql/javax/sql/DataSource.html) 来表示,基本思路是建立足够多的数据库连接进行缓存,以避免每次都要创建连接和关闭带来的系统开销浪费。
## 输入与输出
### IO
#### File
File 表示一个文件或者目录对象,主要的 API 如下:
- `static final String separator` 分割符(WIN -> `\\`;UNIX -> `/`)
- `static final String pathSeparator` 路径分隔符 (WIN -> `;` ; UNIX -> `:`)
- `File(String pathname)`
- `File(String parent, String child)`
- `File(File parent, String child)`
- `File(URI uri)`
- `String getName()`
- `String getPath()`
- `String getAbsolutePath()` or `File getAbsoluteFile()`
- `String getCanonicalPath()` or `File getCanonicalFile()`
- `String getParent()` or `File getParentFile()`
- `boolean exists()`
- `boolean canWrite()`
- `boolean canRead()`
- `boolean isFile()`
- `boolean isDirectory()`
- `boolean isAbsolute()`
- `boolean isHidden()`
- `long length()`
- `long lastModified()`
- `boolean createNewFile()`
- `boolean renameTo(File dest)`
- `boolean mkdir()`
- `boolean mkdirs()`
- `boolean delete()`
- `String[] list()`
- `File[] listFiles()`
- `void deleteOnExit()`
底层依赖于一个静态的 FileSystem 对象,会根据不同的操作系统配置不同的 FileSystem 实现
#### 流
“流”(Stream)是Java对输入输出源的一种抽象,表示了一种逐一读取或写出的处理方式。通过“流”的方式允许程序使用相同的方式访问不同的源。
- 输入流、输出流:通常以程序运行所在内存来区分
- 字节流、字符流:所操作的数据单元不同,字节 8 bits,字符 16 bits
- 节点流、处理流:直接关联IO设备的流称为节点流,而在现有流的基础上封装而成的流称为处理流。
![img](Java.assets/iostream2xx.png)
> 图中的四个基类都实现了 Closeable 接口,适用于 try - resource 语句。
##### InputStream/Reader
- `int read()`
- `int read(byte[]/char[] buf)`
- `int read(byte[]/char[] buf, int off, int len)`
- `String readLine()` BufferedReader
- `void mark(int readlimit)`
- `void reset()`
- `long skip(long n)`
```java
InputStream is = ...
length = ...
byte[] buf = new byte[length];
for (int hasRead = 0; (hasRead = is.read(buf)) != -1; ) {
// 处理 buf 的 从 0 开始 hasRead 长度的数据
}
```
##### OutputStream/Writer
- `void write(int c)`
- `void write(byte[]/char[] buf)`
- `void write(byte[]/char[] buf, int off, int len)`
- `void newLine()` BufferedWriter
- `void write(String str)` Writer、PrintStream
- `void write(String str, int off, int len)` Writer
- `void print/println(xxx)` PrintStream、PrintWriter
- `void flush()`
#### FileXXX
#### ObjectXXX
#### StringXXX
#### CharArrayXXX
#### ByteArrayXXX
#### InputStreamReader
```java
Reader readerObj = new InputStreamReader(inputStreamObj);
```
#### OutputStreamWriter
```java
Writer writerObj = new OutputStreamWriter(outputStreamObj);
```
#### BufferedXXX
缓冲流:Buffered~ 提供缓冲功能
- 输出基类中的 flush() 方法不做任何处理,缓冲流重写了该方法,用于刷新缓冲区
- BufferedReader 实例的 `String readLine()` 用于读取一个文本行
- BufferedWriter 实例的 `void newLine()` 用于输出一个行分隔符
#### PrintXXX
打印流:Print~ 提供与打印相关的方法,比如向屏幕打印等
- print 方法用于打印各种类型的数据
- println 方法会在末尾添加换行符
#### DataXXX
数据流:Data~ 提供了解析二进制数据的方法,比如可以直接读或写一个 int 等
- readXxx() / writeXxx(Xxx xxx)
#### 标准输入输出流
- `System.in` 默认为键盘输入
- `System.err` / `System.out` 默认为控制台输出
System 的静态方法可以重定向:
- `static void setErr(PrintStream err)`
- `static void setIn(InputStream in)`
- `sattic void setOut(PrintStream out)`
子进程(Process 对象)的输入输出:
- `InputStream getErrorStream()` 获取子进程的错误输出流,对主进程是输入
- `InputStream getInputStream()` 获取子进程的输出流,对主进程是输入
- `OutputStream getOutputStream()` 获取子进程的输入流,对主进程是输出
#### RandomAccessFile
随机访问文件内容。与流相比,不是逐一读写,而是将文件内容放入内存,任意定位指针的访问方式。该类实现了DataInput、DataOutput 接口,所以可以像数据流那样,实现底层二进制数据和Java类型的交互。
- 添加了定位指针的方法:
- `long getFilePointer()`
- `void seek(long pos)`
- `long length()`
- `void setLength(long l)`
- 创建该对象是可以指定文件访问模式,`"r"`只读`"rw"`读写
- 写数据的时候是以覆盖的形式写入的。
#### Scanner
基于正则表达式的字符扫描解析器,可以以字符串、文件、输入流等源作为构造参数创建(二进制源需要指定编码),功能上和 DataInputStream 非常相似,不同的是 DataInputStream 解析的是二进制数据,且不需要扫描,比如要读一个 int 就直接解析当前的4个字节的数据;而 Scanner 每次扫描一个字符,碰到分割符进行解析,比如 `"... 1234 34 ..."` 将分割出 1234 和 34 进行解析。
- `boolean hasNextXxx()`
- `Xxx nextXxx()`
#### 序列化
程序中,Java类对象是和运行环境绑定在一起的,运行环境包含了类的信息和运行基础等,可以操纵类对象。对象的序列化实现了对象自身数据的存储,脱离了运行环境而独立存在。
- 远程方法调用
- 停机配置还原
- ...
##### 序列化的一般流程
- 实现 Serializable 标记接口
- ObjectOutputStream 实例的 writeObject(Object obj) 完成序列化
- ObjectInputStream 实例的 readObject() 并强制类型转换,完成反序列化
##### 深入理解序列化原理
- 本质上是对对象属性的记录,并没有保存程序运行所必须的类信息,所以反序列化时需要提供对应的类文件
- 对象的引用属性对象也必须是可序列化的类型
- 可以一次性序列化多个对象,反序列化时按照顺序读取
- 为了避免重复序列化一个对象,已序列化的对象都有一个序列化编号来标示内存中的对象。这样,反序列化后,原先指向同一个对象的两个引用,现在还是指向同一个对象。
> 陷阱:序列化过程中如果对象在序列化之后发生变化,这种变化将不可能被保存,即使是再次序列化
- transient 修饰的实例变量不会被序列化,反序列化时的值为对象默认值
- `private static final long serialVersionUID` 用于控制类文件的版本
##### 自定义序列化
需要添加额外的特殊方法来完成自定义过程:
- `private void writeObject(ObjectOutputStream out) throws IOException`
自定义序列化的过程,可以对属性进行加密,或者决定那些属性被序列化
- `private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException`
自定义反序列化的过程,根据上面的方法按顺序进行解密等处理
- `private void readObjectNoData() throws ObjectStreamException`
当数据异常,调用该方法初始化对象
- `private/protected Object writeReplace() throws ObjectStreamException`
序列化过程中,完全替换当前对象,程序将序列化该方法返回值
- `private/protected Object readResolve() throws ObjectStreamException`
反序列化过程中,完全替换当前对象,程序将返回该方法的返回值(主要用于枚举类的反序列化)
实现 Externalizable 接口,不同之处在与该接口是非标记的接口,需要实现两个方法和提供无参数的构造器:
- `void writeObject(ObjectOutput out) throws IOException`
- `void readExternal(ObjectInput in) throws IOException, ClassNotFoundException`
### NIO
#### Path
#### 块
“块”代表了一次性处理一块数据的处理方式,被抽象成 Buffer ,所以比“流”的处理效率更高。与此同时, NIO 将 IO 设备抽象成 Channal ,Buffer 与 Channal 之间的数据是双向流动的:
![img](Java.assets/IquiIb5mAarBIotYIWQndJCIy_9ISxWWOWeskaMPwHabkdOm2G00.png)
##### Buffer
底层维护了四个整型变量:
- `mark` 标记
- `position` 光标
- `limit` 边界
- `capacity` 容量
主要的 API 如下:
- `boolean hasRemaining()`
- `Buffer mark()`
```java
mark = position;
```
- `Buffer reset()`
```java
position = mark;
```
- `Buffer rewind()`
```java
position=0;
```
- `void flip()`
```java
limit = position; position = 0; mark = -1;
```
- `Buffer clear()`
```java
position = 0; limit = capacity; mark = -1;
```
Buffer 分为两个状态,IN 状态和 OUT 状态。访问 Buffer 一般遵循以下四个步骤:
1. 写入数据到 Buffer
2. 调用 `void flip()` 进入 OUT 状态
3. 从 Buffer 中读取数据
4. 调用 `void clear()` 进入 IN 状态
非 boolean 的基本数据类型都有对应的 XXXBuffer 实现,每一个都定义了一系列 get 和 put 方法用于将数据移入或移出 Buffer ,compact 方法用于清空已读数据,duplicate 和 slice 方法用于共享缓存内容,静态方法 allocate 方法分配一个新的 Buffer 对象。
##### ByteBuffer
相比于其他 Buffer 实现,ByteBuffer 有如下特性:
- 可以用作I/O操作的源和目标
- 可以分配“直接缓存区”
- 可以直接映射文件的一个区域到内存缓冲区中
- 可以直接转换成其他类型 Buffer
##### Channel
代表数据源,可以是硬件设备、文件、网络、程序对象等。
- `void close()`
- `boolean isOpen()`
###### ByteChannel
- `int read(ByteBuffer bbf)`
- `int write(ByteBuffer bbf)`
###### NetworkChannel
- `NetworkChannel bind(SocketAddress local)`
- `SocketAddress getLocalAddress()`
##### Selector
代表一个多通道选择器,可以注册多个处于非阻塞状态下的 SelectableChannel 对象,注册时指定了通道的敏感操作,并生成一个 SelectionKey 映射相应的通道并返回。一旦注册到 Selector 后,每当一个通道有敏感的操作已准备好执行,就会向 Selector 的 selected-key 集合中添加当前通道的 key ,这样 Selector 就可以检测到可执行的通道并进行调度,将开启一个新线程或从线程池里拿线程来执行已准备好的操作,不会阻塞当前线程。
##### Charset
编码集对象,使用流程:
```java
Charset cs = Charset.forName("xxx");
CharSetEncoder ec = cs.newEncoder();
CharSetDecoder dc = cs.newDecoder();
ByteBuffer bbf = ec.encode(cbf);
CharBuffer cbf = dc.decode(bbf);
```
#### ByteBuffer
相比于其他 Buffer 实现,ByteBuffer 有如下特性:
- 可以用作I/O操作的源和目标
- 可以分配“直接缓存区”
- 可以直接映射文件的一个区域到内存缓冲区中
- 可以直接转换成其他类型 Buffer
#### Channels
Channel 的工具类,用于与 java.io 包下的 API 交互,可以通过流对象获得相应的 Channel 对象,也可以通过 Channel 对象获得相应的流对象。
#### EPollSelectorImpl
## 多线程
进程是对一个正在执行的程序的抽象,操作系统会保存进程运行过程中的所有状态信息,即进程上下文。线程是对进程中的一个顺序执行流的抽象,操作系统也会保存线程运行过程中的相对轻量级的状态信息,即线程上下文。一个进程可以包含多个线程,且这些线程共享进程中的上下文信息。
> **并发与并行**
>
> - 早期,计算机系统只有一个计算核心,即单核,同一时间只能处理一个进程里的一个线程,那么为了处理多进程多线程的情景,需要让计算核心进行上下文切换,交替处理不同的线程,这就是并发。
> - 现如今,随着科技水平的提高,计算机系统普遍拥有多个计算核心,即多核,同一时间可以让多核同时处理多个线程,这就是并行。
> **为什么要使用多线程**
>
> - 在单核时代,如果采用多线程模型,反而因为不必要的上下文切换降低了性能。但是如果主线程经常被阻塞,会导致程序的执行效率低下。引入多线程模型后,某一个线程阻塞不会影响其他线程的执行。
> - 在多核时代,为了充分利用多核的计算资源,引入多线程模型后可以并行处理多个任务。
### Thread
Thread 类是线程在 Java 中的抽象。主要 **API** 如下
- `static Thread currentThread()`
- `Thread(Runnable r)` 使用当前线程的线程组创建
- `Thread(ThreadGroup g, Runnable r)`
- `void setPriority(int p)`
> 每当调度器决定运行一个新线程时, 首先会在具有高优先级的线程中进行选择, 尽管这样会使低优先级的线程完全饿死
`static int Thread.MAX_PRIORITY`
`static int Thread.MIN_PRIORITY`
`static int Thread.NORM_PRIORITY`
- `void setDaemon(boolean b)` 设置是否为后台线程
> 后台线程的任务是为其他线程提供服务,如果全部前台线程死亡,后台线程会自动死亡
- `void start()` 激活线程,进入执行状态,不可以二次调用。JVM 会自动注册一个该线程的强引用,所以即使没有显式的变量引用线程,活跃的线程依然不会被垃圾回收器回收。
- `static void sleep(long millis)` 当前线程进入限时等待状态,指定时间长度后自动恢复执行状态
- `static void yield()` 主动让当前线程失去CPU
- `void join()` 等待该线程实例执行完毕
- `void interrupt()` 将线程标记为“中断”,具体如何中断由 `run()` 的顺序流决定
> 如果线程调用了对象的 `wait`方法,或者线程对象的 `join` 方法,或者调用了 `Thread.sleep` 方法,该方法将清除中断状态,并抛出`InterruptedException` 给这个线程。
>
> 如果线程阻塞在 `InterruptibleChannel` 上,该方法将设置中断状态,并抛出 `ClosedByInterruptException` 给这个线程。
- `boolean isInterrupted()` 判断线程是否为“中断”
- `static boolean interrupted()` 判断当前线程是否为“中断”,并清除中断状态
#### Thread.State
Thread.State 表示线程状态的枚举类
- `NEW` 新建的线程对象还未执行
- `RUNNABLE` 线程正在 JVM 中被执行
- `BLOCKED` 等待获取监视器锁
> 进入synchronized 临界区或者调用 Object#wait 后唤醒时,进入 BLOCKED 状态
- `WAITING` 无限期等待,直到被其他线程唤醒(Object)
> Object#wait()、Thread#join()、LockSupport#park() 会使线程进入 WAITING 状态
- `TIMED_WAITING` 限时等待,直到被其他线程唤醒或者时限达到自我唤醒
> Thread.sleep(xxx)、Object#wait(xxx)、Thread#join(xxx)、LockSupport#parkXXX(xxx) 会使线程进入 TIMED_WAITING 状态
- `TERMINATED` 线程终止,不可二次执行
![image-20191204154335243](C:\Users\sunny\AppData\Roaming\Typora\typora-user-images\image-20191204154335243.png)
> 因 IO 操作而阻塞的线程其实还是 RUNNABLE 的状态
#### ThreadGroup
ThreadGroup 类是线程组在 Java 中的抽象,主要的 API 如下:
- `ThreadGroup(String name)`
- `ThreadGroup(ThreadGroup g, String name)`
- `void interrupt()`
- `void setDaemon(boolean b)`
##### Thread.UncaughtExceptionHandler
表示一个非受检异常处理器,ThreadGroup 实现了 Thread.UncaughtExceptionHandler ,API:
- `void uncaughtException(Thread t, Throwable e)`
设置处理器的方法如下:
- `void Thread#setUncaughtExceptionHandler(UncaughtExceptionHandler h)` 设置线程的处理器
- `void Thread.setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler h)` 设置默认的处理器
用于处理线程执行过程中的非受检异常,其处理流程如下:
1. 如果线程设置了处理器,那么交给线程处理器处理
2. 否则交给线程所在的线程组处理,其中默认实现如下
1. 如果线程组有父线程组,那么就给父线程组处理
2. 否则,如果有默认异常处理器,则交给该处理器处理
3. 否则,输出栈轨迹到标准错误流上
#### ThreadLocal\
ThreadLocal 是局部线程变量的抽象。主要 API 如下:
- `T get()` 获取与当前线程关联的指定类型的对象
- `void set(T value)` 设置与当前线程关联的指定类型的对象
- `void remove()` 删除与当前线程关联的指定类型的对象
- `static ThreadLocal withInitial(Supplier s)` 生成一个有初始值的 ThreadLocal 对象
每一个线程都会持有一个 ThreadLocal.ThreadLocalMap 对象,该对象负责管理以 ThreadLocal 对象为 key 本地值为 value 的映射。而 ThreadLocal 对象自身不负责存储,而只负责访问或设置或删除当前线程的 ThreadLocal.ThreadLocalMap 对象的与自身对应的值对象。
> ThreadLocalMap 对象持有 ThreadLocal key 的弱引用,所以当所有强引用失效后,即使没有手动删除,ThreadLocal 对象也会被回收,从而出现 null key 现象。但是 ThreadLocal 对象对应的值对象并没有自动删除,而是在下一次 ThreadLocalMap 调用set, get,remove的时候才会被清除,有内存泄漏的隐患。所以不要忽略 remove 方法的调用。
### Runnable
Runnable 代表一个无返回值的执行体,该接口只包含一个方法:
- `void run()`
Thread 实现了 Runnable 接口。默认将执行构造方法传入的 Runnable 对象的 run 方法。
```java
new Thread(runnableObj).start();
```
### Callable\
Callable 代表一个有返回值的执行体,该接口只包含一个方法:
- `V call()`
#### Future\
Future\ 代表一个 Callable 执行体的管理器,提供了以下方法:
- `boolean cancel(boolean i)` 取消执行体,如果已经开始执行且 i 为 true ,则会尝试中断
- `boolean isCancelled()`
- `boolean isDone()`
- `V get()` 尝试获取执行体的返回值,如果还未完成,线程会阻塞至任务完成
- `V get(long timeout, TimeUnit unit)`
#### RunnableFuture\
Callable 的执行体不能直接被线程对象接受,需要包装成 Runnable 对象。RunnableFuture\ 同时继承了 Runnable 、 Future\ 接口。
#### FutureTask\
FutureTask 同时实现了 RunnableFuture\ 接口。FutureTask 对象的 run 方法会调用内置的 Callable 对象的执行体,并随后保存返回值。
```java
FutureTask task = new FutureTask(callableObj)
new Thread(task).start();
...
XXX value = task.get();
```
### Executor
Executor 表示一个执行器,
- `void execute(Runnable command)`
#### ExecutorService
ExecutorService 接口是 Executor 的子接口,增加了执行器需要的完备的服务方法,主要有:
- ` Future submit(Callable task)`
- ` List> invokeAll(Collection> tasks)`
- ` T invokeAny(Collection> tasks)`
- `void shutdown()`
- `List shutdownNow()`
#### ThreadPoolExecutor
ThreadPoolExecutor 使用池化算法实现了 ExecutorService 。
- 减少创建和销毁线程的性能损耗
- 有效地管理系统资源,防止系统崩溃
构造一个完整的 ThreadPoolExecutor 对象依次需要以下参数:
- `int corePoolSize` 线程池核心线程数
- `int maximumPoolSize` 线程池最大线程数
- `long keepAliveTime` 当线程数大于核心线程数时,空闲线程的等待时限
- `TimeUnit unit`
- `BlockingQueue workQueue` 该阻塞队列用于存放 execute 方法提交的等待执行的任务
- `ThreadFactory threadFactory` 线程工厂
- `RejectedExecutionHandler handler` 拒绝执行处理器
> java.util.concurrent.Executors 是一个工具类,可以创建简单的线程池对象,但不推荐在生产环境中使用。
底层的执行流程如下:
1. execute 或 submit 方法提交任务
2. 如果当前线程数小于核心线程数,新建核心线程执行当前任务
1. 执行完后,将一直从任务队列中取任务执行
3. 否则,向任务队列添加当前任务,如果添加成功,返回
4. 否则,如果当前线程数小于最大线程数,新建非核心线程执行当前任务
1. 执行完后,将一直从任务队列中取任务执行,如果一直闲置了足够长时间,将销毁该线程
5. 否则,调用拒绝执行处理器的相关方法
#### ForkJoinPool
##### ForkJoinTask
#### ScheduledExecutorService
ScheduledExecutorService 接口是 ExecutorService 的子接口,增加了任务调度方法:
- `ScheduledFuture> schedule(Runnable command, long delay, TimeUnit unit)`
- ` ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit)`
- `ScheduledFuture> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)`
- `ScheduledFuture> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)`
#### ScheduledThreadPoolExecutor
### CompletionService\
CompletionService\ 表示一个即可以接收任务也可以处理任务结果的服务,主要 API 如下:
- `Future submit(Callable task)` 提交任务
- `Future take()` 返回一个已经完成的任务结果,如果没有则阻塞
- `Future poll()` 非阻塞,如果没有任务完成则返回 null
> ExecutorService 只负责接收任务,接收的同时会返回 Future 对象给主调,所以不负责处理任务结果。
#### ExecutorCompletionService\
ExecutorCompletionService\ 是 CompletionService\ 的一种实现:
- `ExecutorCompletionService(Executor e)`
- `ExecutorCompletionService(Executor e, BlockingQueue> q)`
底层维护了一个 Executor 对象和一个 BlockingQueue\> 对象,当接收任务是会将任务对象包装成 QueueingFuture\ 对象,该对象会在任务完成时自动将任务结果添加到阻塞队列中。
### CompletionStage\
CompetionStage\ 表示一个异步计算的阶段,可以在计算完成时自动执行相应的处理:
- ` CompletionStage thenApply(Function fn)`
- ` CompletionStage applyToEither(CompletionStage other, Function fn)`
- ` CompletionStage thenCombine(CompletionStage other,BiFunction fn)`
- `CompletionStage thenRun(Runnable action)`
- `CompletionStage runAfterBoth(CompletionStage> other, Runnable action)`
- `CompletionStage runAfterEither(CompletionStage> other, Runnable action)`
- `CompletionStage thenAccept(Consumer super T> action)`
- `CompletionStage acceptEither(CompletionStage other, Consumer action)`
- ` CompletionStage thenAcceptBoth(CompletionStage other,BiConsumer action)`
- ` CompletionStage thenCompose(Function> fn)`
- `CompletionStage whenComplete(BiConsumer action)`
- `CompletionStage exceptionally(Function fn)`
- ` CompletionStage handle(BiFunction fn)`
#### CompletableFuture\
同时实现了 Future\ 、 CompletionStage\ 两个接口,即实现了异步执行和结果访问两个功能。该类是 Java 实现“函数是编程”的关键。主要 API 如下:
- `CompletableFuture()` 构造一个处于未完成阶段的对象,没有实际意义
- `static CompletableFuture completedFuture(U value)`
- `static CompletableFuture runAsync(Runnable runnable)`
- `static CompletableFuture supplyAsync(Supplier supplier)`
### 线程同步
多线程编程会出现不同线程访问和修改同一个资源的现象,线程之间就会相互干扰,破坏顺序执行逻辑。故引入“同步安全机制”,即使得线程可以阻塞或者唤醒其他线程,来完成正确的处理逻辑。
#### 内存屏障
由于 CPU 缓存数据过期以及指令重排序,可能导致程序读取到错误的数据,因此,硬件层面上,引入内存屏障标准。
- Store:将处理器缓存的数据刷新到内存中。
- Load:将内存存储的数据拷贝到处理器的缓存中。
| 屏障类型 | 指令示例 | 说明 |
| :------------------ | :----------------------- | :----------------------------------------------------------- |
| LoadLoad Barriers | Load1;LoadLoad;Load2 | 该屏障确保Load1数据的装载先于Load2及其后所有装载指令的的操作 |
| StoreStore Barriers | Store1;StoreStore;Store2 | 该屏障确保Store1立刻刷新数据到内存(使其对其他处理器可见)的操作先于Store2及其后所有存储指令的操作 |
| LoadStore Barriers | Load1;LoadStore;Store2 | 确保Load1的数据装载先于Store2及其后所有的存储指令刷新数据到内存的操作 |
| StoreLoad Barriers | Store1;StoreLoad;Load2 | 该屏障确保Store1立刻刷新数据到内存的操作先于Load2及其后所有装载装载指令的操作。它会使该屏障之前的所有内存访问指令(存储指令和访问指令)完成之后,才执行该屏障之后的内存访问指令 |
> 不同硬件架构的实现可能有所差异,以 x86 架构为例:
>
> - lfence 指令:LoadLoad
> - sfence 指令:StoreStore
> - mfence 指令:StoreLoad
>
> lfence、mfence 都可以保证屏障之后的 load 操作的可见性,即会先刷新无效缓存,得到最新的修改值。
#### 原子性
原子性就是一个或多个操作要么全部执行并且不会被打断,要么就不执行。
JVM 内存模型定义了一共 8 个原子操作,都具有原子性,并且执行过程不会被 CPU 上下文切换所打断:
![image-20191209161114922](Java.assets/image-20191209161114922.png)
read 和 write 操作可以满足基本类型(除long和double)的变量的读或写具有原子性。
#### volatile 关键字
volatile 的标准定义是:对volatile变量的写入操作必须在对该变量的读操作之前执行。(可见性)
以x86架构为例,JVM对volatile变量的处理如下:
- 在写volatile变量v之后,插入一个sfence。这样,sfence之前的所有store(包括写v)不会被重排序到sfence之后,sfence之后的所有store不会被重排序到sfence之前,禁用跨sfence的store重排序;且sfence之前修改的值都会被写回缓存,并标记其他CPU中的缓存失效。
- 在读volatile变量v之前,插入一个lfence。这样,lfence之后的load(包括读v)不会被重排序到lfence之前,lfence之前的load不会被重排序到lfence之后,禁用跨lfence的load重排序;且lfence之后,会首先刷新无效缓存,从而得到最新的修改值,与sfence配合保证内存可见性。
#### synchronized 关键字
JVM 没有把 lock 和 unlock 开放给字节码使用,但 JVM 开放了更高层次的指令 monitorenter 和 monitorexit ,反应到java代码中就是 synchronized 关键字。要想执行同步安全的代码段就先获取附属的同步监视器的锁定,否则将阻塞,直到获取锁定,执行过程中,其他线程会因无法获取锁定而被阻塞。
- 代码块同步
```java
synchronized(obj) {
...
} // 同步监视器是 obj
```
- 方法同步
```java
class xxx {
xxx synchronized xxx methodname(xxx) {
...
}
} // 同步监视器是 this
```
Object 类提供了三个方法用于实现通信,前提是对象必须被当做同步监视器
- `void wait()` 放弃锁定,阻塞当前线程,等待直到被唤醒
- `void wait(long time)` 可以自动唤醒
- `void notify()` 随机唤醒一个在此同步监听器上等待的线程
- `void notifyAll()` 唤醒所有在此同步监听器上等待的线程
#### CAS 操作
CAS 全名 Compare and Swap ,是一项乐观锁技术,也称为无锁技术。CAS 定义了一个原子性的操作:执行写入之前判断内存值与期望值是否相等,如果相等则执行写入,返回成功信号,如果不相等则放弃写入,返回失败信号。
> 这种无锁技术只能适用于一些资源竞争不激烈的情景,它可以避免加锁解锁带来的性能损耗;但是如果资源竞争过于激烈,可能出现多次尝试 CAS 都失败而带来的性能损耗。
> 现如今很多 CPU 都支持 CAS 指令。
sun.misc.Unsafe 对象提供了对 CAS 的支持:
- `boolean compareAndSwapInt(Object o, long offset, int expected, int x)`
- `boolean compareAndSwapLong(Object o, long offset, long expected, long x)`
- `boolean compareAndSwapObject(Object o, long offset, Object expected, Object x)`
> sun.misc.Unsafe 包装了 jdk.internal.misc.Unsafe 。
VarHandle
#### Atomic 类
Unsafe 类对于开发者不是很友好,java.util.concurrent.atomic 包提供了更友好的 API,并且增加了很多常用的原子操作方法以供开发者使用。
##### AtomicInteger
AtomicInteger 是最常用的原子类,主要的 API 如下:
- `int addAndGet(int delta)`
- `int getAndAdd(int delta)`
- `int incrementAndGet()`
- `int getAndIncrement()`
> 除了 AtomicInteger ,该包下还支持了 boolean、long、对象引用、数组元素、对象字段等功能。
> **解决 CAS 操作的 ABA 问题**
>
> ```java
> AtomicStampedReference reference = new AtomicStampedReference(str1,1);
> reference.compareAndSet(str1,str2,reference.getStamp(),reference.getStamp()+1);
> ```
#### AQS 框架
> 协调多个线程运行状态的工具对象称为同步器。
AQS 全名 AbstractQueuedSynchronizer ,即抽象队列同步器,是一种同步器的实现框架类,可以实现阻塞锁和顶层的一些同步器。
底层维护了一个 volatile 的 int 的变量 state ,需要重写以下 API 来改变 state :
- `protected boolean tryAcquire(int arg)`
- `protected boolean tryRelease(int arg)`
- `protected int tryAcquireShared(int arg)`
- `protected boolean tryReleaseShared(int arg)`
- `protected boolean isHeldExclusively()`
> AQS定义两种资源共享方式:Exclusive 和 Shared
外部通过以下 API 来访问 AQS 实现类:
- `final void acquire(int arg)`
- `final void acquireInterruptibly(int arg)`
- `final void acquireShared(int arg)`
- `final void acquireSharedInterruptibly(int arg)`
- `final boolean release(int arg)`
- `final boolean releaseShared(int arg)`
##### acquire - release 流程
**acquire**
1. 尝试获得锁,如果获得锁,直接返回
2. 否则,将当前线程包装成 Node 对象放入等待队列的尾部,激活自旋
1. 如果前驱为 head 且尝试获得锁成功,删除旧 head ,设置自身为 head,停止自旋
2. 否则,阻塞当前线程,等待被唤醒,唤醒后重新自旋
3. 线程中断自处理。
**release**
1. 尝试释放锁,一般都会成功的
2. 唤醒 head 的后继 Node
##### acquireShared - releaseShared 流程
#### Lock
Lock 接口的方法提供了更加灵活的锁定操作:
- `void lock()`
- `void lockInterruptibly()` 可被中断
- `boolean tryLock()`
- `boolean tryLock(long time, TimeUnit unit)` 可被中断
- `void unlock()`
- `Condition newCondition()` 获取条件对象
用法如下:
```java
lock.lock();
try{
//处理任务
}catch(Exception ex){
}finally{
lock.unlock(); //释放锁
}
```
```java
if(lock.tryLock()) {
try{
//处理任务
}catch(Exception ex){
}finally{
lock.unlock(); //释放锁
}
}else {
//如果不能获取锁,则直接做其他事情
}
```
```java
lock.lockInterruptibly();// 如果等待过程中当前线程被标记为“中断”,则会抛出异常,必须被捕获
try {
//.....
}
finally {
lock.unlock();
}
```
##### ReentrantLock
ReentrantLock 实现了 Lock ,称为可重入锁,内部定义了一个非公平同步器和一个公平同步器,都继承自 AQS 。同步器内部维护了一个 state ,当获取锁时会判断当前线程是否持有锁,从而实现重入逻辑。
> Lock 对象也是一种的同步器,实现类中借助了 AQS 的支持。
> 可重入的意思是,获取锁的线程可以再次获取锁。
##### ReadWriteLock
ReadWriteLock 代表读写锁,API 如下:
- `Lock readLock()` 获取读锁
- `Lock writeLock()` 获取写锁
##### ReentrantReadWriteLock
ReentrantReadWriteLock 实现了 ReadWriteLock ,称为可重入读写锁,内部也是定义了一个非公平同步器和一个公平同步器,都继承自 AQS 。此外还定义了一个读锁和一个写锁,这两个锁将共享宿主对象的同步器,巧妙的是,同步器里维护的 state 整型变量一分为二,分别由读锁和写锁维护。
![image-20191210175611043](Java.assets/image-20191210175611043.png)
当获得锁时,会通过复合变量 state 实现读写锁之间的制约关系。
| 当前锁状态 | 读锁请求 | 写锁请求 |
| ---------- | -------------- | ---------------- |
| 无锁 | 可以 | 可以 |
| 读锁 | 可以(可重入) | 阻塞(不可重入) |
| 写锁 | 阻塞(可重入) | 阻塞(可重入) |
##### Condition
通过Lock实例的 `newCondition()` 方法,获取对应的Condition实例,该接口有如下方法:
- `void await()`
- `await`的其他变体,功能丰富
- `void signal()`
- `void signalAll()`
该接口的实现类一般为 AQS 的内部类 ConditionObject 。
> 监视器同时承担了锁和线程通信两个功能,而同步锁只负责锁功能,线程通信则交给 Condition 对象来负责,这样,一个同步锁可以产生多个 Condition 对象,且多个 Condition 对象只负责自己的等待线程,不会相互影响。
###### ConditionObject
#### 常用的顶层同步器
##### CountDownLatch
称为倒计时门栓,这种同步器可以实现:在一个或多个线程中等待其他线程完成一定数量的操作后继续执行。
- `CountDownLatch(int count)` count 表示倒计时总数
- `void await()` 等待至倒计时为零,或被中断
- `void countDown()` 倒计时减一
内部定义了一个同步器继承自 AQS ,倒计时就是 state 变量,每当调用 countDown 时,释放一次锁,state 减一,当调用 await 方法时,获取锁,如果 state 为零则获取成功,否则进入队列等待。
##### CyclicBarrier
称为栅栏,这种同步器可以实现:让多个线程彼此等待,直到等待的线程达到一定数量后继续执行。
- `CyclicBarrier(int parties)`
- `CyclicBarrier(int parties, Runnable barrierAction)` 定义栅栏任务
- `int await()`
- `void reset()` 重置
内部维护了一个 ReentrantLock 对象 lock 和一个锁对象的 Condition 对象 trip ,原理就是通过在 trip 上等待栅点实现的。因为考虑到了重置,所以内部还维护了一个 Generation 对象,其中的 broken 表示当前代是否被破坏,当调用 reset 时,将设置当前代的 broken ,每一个被唤醒的线程都会抛出 BrokenBarrierException。
##### Semaphore
称为信号量,这种同步器可以实现:指定一定数量的“许可证”,线程可以获取许可和释放许可,如果没有空闲的许可证,则阻塞当前线程至获得许可证。
- `Semaphore(int permits)`
- `Semaphore(int permits, boolean fair)`
- `void acquire()`
- `void release()`
内部定义了一个非公平同步器和一个公平同步器,都继承自 AQS 。
##### Exchanger
##### BlockingQueue
当生产者线程试图向满的队列中添加元素,会被阻塞,直到不满的时候自动被唤醒;当消费者线程从空大队列中取出元素时,会被阻塞,直到不空的时候自动被唤醒。
常用的接口实现有 ArrayBlockingQueue、LinkedBlockingQueue ...
### 线程安全的集合
- 主流的集合类都是线程不安全的,Collections 工具类提供了多个静态方法来解决这个问题。实现原理是整体锁定,性能较差。
- ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet、ConcurrentLinkedQueue、ConcurrentLinkedDeque 采用 CAS 算法保证线程同步
- CopyOnWriteArrayList、CopyOnWriteArraySet 采用先复制后修改副本再替换的方式保证线程安全
> ConcurrentMap 接口提供了几个原子操作方法,常见的如下:
>
> - `V compute(K k, BiFunction f)` 以旧换新
> - `V computeIfAbsent(K k, Function f)` 不存在时,根据 k 计算新值并添加
> - `V computeIfPresent(K k, BiFunction f)` 存在时,根据 k 和 旧 v 计算新值并添加
> - `V merge(K k, V v, BiFunction f)` 不存在时,应用 v,存在时,应用 f
#### ConcurrentHashMap
1.7 版本时,采用分段锁保证线程安全,数据结构为“分段锁+数组+链表”;1.8 版本时,采用 synchronized+CAS 保证线程安全,数据结构为“数组+链表/红黑树”。
官方没有提供 ConcurrentHashSet 类,需要通过 ConcurrentHashMap 对象的如下方法得到对应的 Set 对象
- `Set newKeySet()` 返回键集,不能添加集元素
- `Set keySet(V v)` 参数是默认键值,将返回键集,可以添加集元素
或者调用 `Collections.newSetFromMap(concurrentHashMapObj)` 得到对应的 Set 对象
#### ConcurrentSkipListMap 与 ConcurrentSkipListSet
主要的 API 如下:
#### ConcurrentLinkedQueue 与 ConcurrentLinkedDeque
#### CopyOnWriteArrayList 与 CopyOnWriteArraySet
## 网络编程
Java 为网络编程提供了 java.net 包。
### 网络基础类
#### InetAddress
代表 IP 地址,其下有两个子类: Inet4Address、Inet6Address
- `static InetAddress getByName(String host)`
- `static InetAddress getByAddress(byte[] addr)`
- `static InetAddress getLocalHost()`
- `String getHostAddress()`
- `String getHostName()`
- `boolean isReachable(int timeout)`
### TCP 协议编程
## 深入理解类
### 加载 Class 对象
Java 为了统一管理类定义,在类的概念之上又抽象出了一个 java.lang.Class ,为了区分,将 Class 称为”本类“,它的作用就是描述其他类的定义,比如一个类里有多少成员、成员是否有注解等。而类的定义都被写进 .class 文件中,即类文件,为了让计算机能够使用这些定义好的类,需要将类文件加载到内存中,其结果就是创建了一个 Class 实例。
#### 内存分配
Java 程序运行时,需要在内存中分配空间,并对空间进行了不同区域的划分,因为每一片区域都有特定的处理数据方式和内存管理方式。主要的区域有两种:**栈、堆**。
- 栈:存放方法中的基本类型的变量数据和对象的引用。
- 堆:存放程序运行所需的对象实例。类也是一种对象,类初始化后都会在堆中找到相应的类实例信息。
![1566094724005](Java.assets/1566094724005.png)
#### 加载步骤
1. 预先加载核心类,生成 JVM 、类加载器。
2. 发出类请求(最开始是主类的请求)
3. 加载:加载器对类文件进行加载,生成 class 对象
4. 验证:类的内部结构是否合法
5. 准备:为静态成员分配内存空间,并默认初始化
7. 类初始化与解析:执行初始化代码(父类优先),并将符号引用替换为直接引用,这个过程会触发父类或者引用类的加载步骤。
##### 类初始化的时机
- 遇到new、getstatic、putstatic或invokestatic这四条字节码指令时,如果类没有进行过初始化,则需要先对其进行初始化。生成这四条指令的最常见的Java代码场景是:
- 使用new关键字实例化对象的时候;
- 读取或设置一个类的静态字段(被final修饰,已在编译器把结果放入常量池的静态字段除外)的时候;
- 调用一个类的静态方法的时候
- 通过子类调用父类的公开静态成员时,只会初始化父类
- 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。
- 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
- 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
- 当使用jdk1.7动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getstatic,REF_putstatic,REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行初始化,则需要先出触发其初始化
#### 类加载器
上面的所有步骤都是通过类加载器来实现的,类加载器的基类为 ClassLoader。当JVM启动时会形成由三个类加载器组成的层次结构:
- 启动类加载器。
- 由C++实现,用户无法获得实例。
- 加载JAVE_HOME\lib目录,或者-Xbootclasspath参数的路径。
- 扩展类加载器。
- sun.misc.Launcher$ExtClassLoader实现。
- 加载JAVA_HOME\lib\ext目录,或者java.ext.dirs系统变量指定的路径。
- 系统类加载器
- sun.misc.Launcher$AppClassLoader实现。
- 加载用户类路径Classpath上的类,即-classpath或java.class.path。
类加载器实例之间存在父子关系(通过组合实现的父子关系),即子类加载器含有一个父类加载器变量,如果这个变量不为 null ,则父亲是指定的父类加载器,如果为 null ,则父亲是启动类加载器,而 Java 无法获取启动类加载器的控制权。所以暴露在外的类加载器都有父亲。
#### 加载策略
1. 如果显式指定了类加载器,则使用该类加载器负责加载,如`MyClassLoader.loadClass("xxx")`。如果没有指定,则使用被依赖类所使用的类加载器负责加载。
> `java -cp xxx -d xxx 主类名` :主类所使用的类加载器就是系统类加载器
2. 确定加载器之后,在缓存里查询自身是否已经加载了目标类,如果找到,则直接返回,如果没有,继续下一步
> 不同的类加载器可以加载同名类,所以需要使用类的权限定名和类加载器共同标识了内存中缓存的类。
3. 自身加载时,如果该加载器有父类加载器,则委托给父类加载器加载,此时检测异常,如果加载成功,则返回,如果出现异常,继续下一步
> 父类加载器限定了子类加载器能够加载的类名称,例如任何类无法加载`java.lang.String`
4. 根据自身的加载方法加载目标类,如果加载成功,则返回,如果不成功,则抛出异常
> 以上策略暗含了三种类加载机制:全盘负责、父类委托、缓存机制
#### 自定义类加载器
需要继承 ClassLoader,API 如下:
- `public Class> loadClass(String name) throws ClassNotFoundException`
负责加载类对象
- `protected Class> findClass(String name) throws ClassNotFoundException`
负责加载的实现
- `public static ClassLoader getSystemClassLoader()` 获取系统类加载器
- `public final ClassLoader getParent()` 获取父类加载器
- 其他的一些辅助方法,如查询缓存、文件系统加载、连接等
> 只需要重写 findClass 方法,即可自定义加载规则。但如果需要改变加载策略,可以重写 loadClass 方法,但一般不会这么做。
#### 线程上下文类加载器与 SPI
线程上下文类加载器是伴随线程的一种类加载器引用,其默认引用的就是系统类加载器,可以自行设置,这种设定方便了类加载器的访问(当然也可以直接利用 ClassLoader 来访问系统类加载器,但这够方式不够灵活,不够优雅)。一个重要的应用就是 Java 的 SPI 机制。
SPI 是服务提供者接口机制,是一种动态加载服务提供者类的方法:
```java
//load方法会搜索类路径下的 META-INF/services/$(MyServices全路径名) 这个文件
//文件中罗列了这个服务的实现类的全路径名
//根据这些名利用线程上下文类加载器去加载类
ServiceLoader myServices = ServiceLoader.load(MyService.class);
Iterator myServiceIterator = myServices.iterator();
while (myServiceIterator.hasNext()) {
MyService myService = myServiceIterator.next();
...
}
```
### 反射编程
在运行状态中,对于任意一个类,都能够获取到这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性(包括私有的方法和属性),这种动态获取的信息以及动态调用对象的方法的功能就称为反射。
![1566123408704](Java.assets/1566123408704.png)
> 这些类提供了非常丰富的方法用于反射编程,其功能错综复杂,自由度非常高,难已归档,建议参考官方 API 进行相关开发。
### JDK 动态代理
- InvocationHandler 代表代理对象的处理方法,调用代理对象的方法都会转换为调用 invoke 方法并将相应的方法以 Method 的形式传给 invoke ,进一步,会在内部存储一个 target 实例指向被代理对象,这样就可以通过反射在 invoke 里调用被代理对象的方法了。
`public Object invoke(Object proxy, Method method, Object[] args)`
- Proxy 用于动态生成代理对象。
`static Object newProxyInstance(ClassLoader loader, Class>[] interfaces, InvocationHandler h)`
> 该方法常常被封装在工厂类里面,用来输出动态生成的代理对象,注意要强制转换一下
```java
//JDK 动态代理实例
import java.lang.reflect.*;
public class Test {
public static void main(String[] args) throws Exception {
MyInterface mi = new ProxyHandler(new MyImpl()).getProxy();
mi.print();
}
}
interface MyInterface {
public void print();
}
class MyImpl implements MyInterface {
public void print() {
System.out.println("Impl");
}
}
class ProxyHandler implements InvocationHandler {
private Object target;
public ProxyHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Exception {
System.out.println("before");
Object result = method.invoke(target, args);
System.out.println("after");
return result;
}
public T getProxy() {
Class> clazz = target.getClass();
return (T)Proxy.newProxyInstance(
clazz.getClassLoader(), clazz.getInterfaces(), this
);
}
}
```
## JVM
JVM 可以看做是一个抽象的计算机,其本质上是一个宿主机中的进程,其作用是保证了 Java 程序的平台无关性。
### JMM
Java Memory Model 就是一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了Java程序在各种平台下对内存的访问都能保证效果一致的机制及规范。
- 堆:存储对象数据
- 方法区:存储类信息以及类加载器信息
- 栈:每一次方法调用都会创建栈帧放入栈中
- 本地方法栈:同栈,只不过针对 native 方法
- 程序计数器:记录当前线程的指令执行位置
两种内存溢出异常
- StackOverFlowError:当请求的栈深度大于虚拟机所允许的最大深度
- OutOfMemoryError:虚拟机在扩展栈时无法申请到足够的内存空间
### GC
GC 主要指的是自动释放堆中的已经无用的对象所占用的内存空间。至于方法区的 GC 暂不考虑。
关于堆的参数设置:
- -Xms :初始堆大小
- -Xmx :堆最大
- -Xss :线程栈空间大小
#### 垃圾标记算法
- 引用计数:给对象添加一个引用计数器,每当对象被引用一次就加1,引用失效时就减1。当为0的时候就判断对象不会再被引用。但是无法解决循环引用问题。应用:Python
- 可达性分析:通过一个称为“GC Roots”的对象为起始点,搜索所经过的路径称为引用链,当一个对象到GC Roots没有任何引用跟它连接则证明对象是不可用的。应用:Java、C#
JVM 中的“GC Roots”有:
- 栈中的引用的对象
- 本地方法栈中引用的对象
- 方法区中的类静态属性引用的对象
- 方法区中的常量引用的对象
#### 垃圾回收算法
- 清除:⚪🔵🔴⚪🔴🔵⚪🔵 ➤ ⚪🔵⚪⚪⚪🔵⚪🔵
- 清除-整理:⚪🔵🔴⚪🔴🔵⚪🔵 ➤ 🔵🔵🔵⚪⚪⚪⚪⚪
- 复制-清除:【⚪🔵🔴🔵】【⚪⚪⚪⚪】 ➤ 【⚪⚪⚪⚪】【🔵🔵⚪⚪】
#### 分代垃圾回收
一般回收流程:
1. 对象创建,如果对象过大,直接放在 Tenured,否则放在 Eden
2. 当 Eden 空间不足时,进行 Minor GC ,当前 Survivor 和 Eden 中幸存的对象中,如果进过多轮幸存,则放在 Tenured ,否则放在另一个 Survivor ,并将另一个 Survivor 设为当前 Survivor
3. 当 Tenured 空间不足时,进行 Major GC ,将 Eden 的垃圾对象清除。
典型垃圾回收器实现
- Serial `Minor GC` `单线程` `STW` `复制-清除`
- ParNew `Minor GC` `多线程` `STW` `复制-清除`
- Parallel Scavenge `Minor GC` `多线程` `STW` `复制-清除` `吞吐量大`
- Serial Old `Major GC` `单线程` `STW` `清除-整理`
- Parallel Old `Major GC` `多线程` `STW` `清除-整理`
- Concurrent Mark Sweep `Major GC` `多线程` `part-STW` `停顿小`
垃圾回收器使用
- -XX:+UseSerialGC:Serial + Serial Old
- -XX:+UseParallelGC:Parallel Scavenge + Parallel Old
- -XX:+UseConcMarkSweepGC:ParNew + Concurrent Mark Sweep + Serial Old(备选)
- -Xmn :年轻代大小
- -XX:NewRatio=n
- -XX:SurvivorRatio=n
- -XX:MaxTenuringThreshold=n
#### 分域垃圾回收(G1)
JDK 7 u9 开始,提供了新的垃圾收集器 G1 ,弱化了分代,引入了分域
一般回收流程:
1. 对象创建,如果对象大于域的 50% ,则放在新建的 Humongous ,如果大于 100% ,则会分配连续的 Humongous ,否则,放入已有 Eden 或新建的 Eden
2. 如果新建 Eden 失败,进行 Young GC ,Eden 和 Survivor 中的幸存对象,如果进过多轮幸存,会被放入以后 Old 或新建的 Old ,否则,会被整理复制到新的 Survivor 。整个过程会`STW` ,并使用多线程执行
3. 如果新建 Old 失败,进行 Mixed GC (会伴随 Young GC),Old 中幸存的对象会被复制整理到新的 Old 。整个过程也是多线程的,但是会`part-STW`
垃圾回收器使用:
- -XX:+UseG1GC
- -XX:MaxGCPauseMillis=200
- -XX:InitiatingHeapOccupancyPercent=45
- -XX:NewRatio=n
- -XX:SurvivorRatio=n
- -XX:MaxTenuringThreshold=n
- -XX:G1HeapRegionSize=n
一键复制
编辑
Web IDE
原始数据
按行查看
历史