0%

一、指板音阶

二、吉他和弦汇总

2.1 常用和弦

  • 大三和弦:C(1、3、5),D(2、#4、6),E(3、#5、7),F(4、6、1),G(5、7、2),A(6、#1、3),B(7、#2、#4)
  • 小三和弦:Cm(1、b3、5)、Dm(2、4、6)、Em(3、5、7)、Fm(4、b6、1)、Gm(5、b7、2)、Am(6、1、3)、Bm(7、2、#4)

2.1 调式和弦

2.1.1 三和弦

2.1.2 七和弦

三、吉他指型

四、CAGED系统

CAGED是一套快速推测和弦指法的系统,即任何一个和弦都有五种按法,它能帮助我们快速记忆指板音阶

以C和弦为例,和弦音为1-3-5(CEG),它在Mi把位上的指法是典型的C和弦指法,但如果把C和弦升高一个把位,即Sol把位,应该怎么按呢?

按照CAGED的排列顺序,C和弦到Sol把位应该按照A指型来按和弦,同理如果再升高到La把位,C和弦就应该按照G指型来按和弦,13品之后就进入下一个循环,即D指型后循环到C指型来按和弦

再以D和弦为例,如果再Mi把位上升高一个把位到Sol把位,那么D和弦就应该按照C指型来按和弦

比较明显的是,在不同把位上的相同和弦弹奏的声音给人的感觉是不同的

五、十二调音阶

一、单例模式

1.1 概述

单例模式就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法,即一次创建,终生使用

它有四种实现方式:饿汉式(静态常量/静态代码块)、懒汉式(线程不安全/安全)、静态内部类、枚举

饿汉式意思是提前实现对象的实例化

懒汉式的意思是需要的时候再进行对象的实例化,即Lazy Loading(懒加载)

1.1 饿汉式

1.1.1 静态变量

优点:

  • 实现简单
  • 在类装载时就完成了实例化,避免了线程同步问题

缺点:

  • 在类装载的时候就完成了实例化,没有达到Lazy Loading(懒加载)的效果,容易造成内存的浪费

结论:如果实例经常被调用,则可以使用

1
2
3
4
5
6
7
public class HungrySingleton{
private static final HungrySingleton instance=new HungrySingleton();
private HungrySingleton(){}
public static HungrySingleton getInstance(){
return instance;
}
}

1.1.2 静态代码块

优缺点和静态变量方式一致,就是将对象的初始化放在了静态代码块中

1
2
3
4
5
6
7
8
9
10
11
12
13
public class HungrySingleton{
private static final HungrySingleton instance;

// 在静态代码块中,初始化单例对象
static{
instance=new HungrySingleton();
}

private HungrySingleton(){}
public static HungrySingleton getInstance(){
return instance;
}
}

1.2 懒汉式

1.2.1 线程不安全

这种方式容易造成线程不安全,在多线程的情况下,如线程a、b、c,a线程通过了判断体后进行了线程的切换,切换到了b线程,那么b线程也能通过判断体,这样就不符合单例模式的要求了,等于对instance进行了两次实例化。所以多线程环境下不能使用这种方式。

结论:不推荐使用

1
2
3
4
5
6
7
8
9
public class LazySingleton{
private static LazySingleton instance; //保证instance在所有线程中同步
private LazySingleton(){} // private避免类在外部被实例化
public static LazySingleton getInstance(){ //getInstance方法前加同步
if(instance==null)
instance=new LazySingleton();
return instance;
}
}

1.2.1 双重校验

解决了线程不同步的问题,在多个线程执行的时候,会有多个线程同时进入第一个判断体中,而第一个判断体有一个同步代码块。同步代码块是需要线程完全执行完里面的操作才进行线程的切换,那么如果前一个线程实现了对象的实例化,那么后面的线程进入同步代码块进行判断的时候,instance就不为空了,保证了线程安全。

声明的变量前的volatile关键字有两个作用:

  • 保证变量修改的可见性,如a线程对变量进行修改后,其他线程也能看见修改后的值
  • 禁止指令重排

结论:推荐使用

1
2
3
4
5
6
7
8
9
10
11
12
13
public class LazySingleton{
private static volatile LazySingleton instance=null; //保证instance在所有线程中同步
private LazySingleton(){} // private避免类在外部被实例化
public static LazySingleton getInstance(){
if(instance==null){
synchronized(LazySingleton.class){
if(instance==null)
instance=new LazySingleton();
}
}
return instance;
}
}

1.3 静态内部类

静态内部类方式在类被装载时不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化

类的静态属性只会在第一次加载类的时候初始化,

优点:

  • 避免了线程不安全
  • 利用静态内部类特点实现延迟加载,效率高

结论:推荐使用

1
2
3
4
5
6
7
8
9
10
11
12
class Singleton{
private Singleton(){}

// 静态内部类SingletonInstance
private static class SingletonInstance{
private static final Singleton INSTANCE = new Singleton();

}
public static synchronized Singleton getInstance(){
return SingletonInstance.INSTANCE;
}
}

1.4 枚举

优点:

  • 实现简单
  • 避免了多线程同步问题

结论:推荐使用(Effective Java作者推荐)

1
2
3
4
5
6
enum Singleton{
INSTANCE,
public void method(){
// 方法
}
}

二、工厂模式

2.1 简单工厂模式

简单工厂设计模式类体如下:

简单来说就是:工厂类Factory可以createProduct()方法传入的标签选择对应的产品类对象的进行创建,这些具体产品类同属于一个抽象的公共产品类

简单工厂的设计在于简单,只有一个工厂类和一个抽象产品类

如果存在多个工厂,多种产品类型,这时就需要用到工厂模式

2.1.1 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 工厂类
public class SimpleFactory {
public static Product createProduct(String tag){
if ("A".equals(tag))
return new ProductA();
else
return new ProductB();
}

public static void main(String[] args) {
Product a = SimpleFactory.createProduct("B");
a.use();
}
}

// 抽象产品类
abstract class Product{
public void use(){
System.out.print("抽象产品类");
};
}

// 具体产品类A
class ProductA extends Product{
@Override
public void use() {
super.use();
System.out.println("--->"+"具体产品类A");
}
}

// 具体产品类B
class ProductB extends Product{
@Override
public void use() {
super.use();
System.out.println("--->"+"具体产品类B");
}
}

2.2 工厂模式

在简单工厂的基础上对工厂类进行抽象,现在可以构建多个具体工厂类了,不同的工厂用于生产不同类的产品

在具体工厂的扩展并不会修改源代码,只需要新建一个工厂类,比如FactoryC用于生产其他产品对象

但如果要新增一个产品类型,并让其中一个工厂进行生产时,这时则需要修改具体工厂类的源代码,不符合开闭原则

2.2.1 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
// 抽象工厂类
public abstract class AbstractFactory {
abstract Product createProduct(String type);

public Factory(){
Product product = null;

do{
String type = getType();
// 产品对象的创建推迟到具体工厂类中
product = createProduct(type);

if (product == null){
break;
}
product.use();
}while (true);

}

//写一个方法 可以获取客户希望订购的pizza
private String getType() {
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
System.out.println("请在下面输入你要购买产品类型:");
String pizza = reader.readLine();
return pizza;
} catch (IOException e) {
e.printStackTrace();
return "";
}
}
}

// 抽象产品类
public abstract class Product {
public abstract void use();
}

// 具体工厂类A
class FactoryA extends Factory{
@Override
Product createProduct(String type) {
Product product = null;
if("A1".equals(type))
return new ProductA1();
else if("A2".equals(type))
return new ProductA2();
return product;
}
}

// 具体工厂类B
class FactoryB extends Factory{
@Override
Product createProduct(String type) {
Product product = null;
if("B1".equals(type))
return new ProductB1();
else if("B2".equals(type))
return new ProductB2();
return product;
}
}

// A工厂生产产品A1
class ProductA1 extends Product{
@Override
public void use() {
System.out.println("使用产品A1");
}
}

// A工厂生产产品A2
class ProductA2 extends Product{
@Override
public void use() {
System.out.println("使用产品A2");
}
}

// B工厂生产产品B1
class ProductB1 extends Product{
@Override
public void use() {
System.out.println("使用产品B1");
}
}

// B工厂生产产品B1
class ProductB2 extends Product{
@Override
public void use() {
System.out.println("使用产品B2");
}
}

// 访问类
public class Client {
public static void main(String[] args) {
// 使用A工厂进行产品生产
Factory factoryA = new FactoryA();
}

}

2.3 抽象工厂模式

抽象工厂模式与工厂模式的区别在于将抽象工厂类改成了接口,接口的抽象层次更高,因此称为抽象工厂模式

2.4 应用

三、原型模式

原型模式提供对象复制的一种方式,即对象的克隆,克隆又分为深克隆和浅克隆

浅克隆:复制某一对象时,只复制其本身值类型的成员变量,不复制引用变量

深克隆:复制某一对象时,不仅复制其本身值类型的成员变量,还复制引用变量

原型模式结构:

  • 抽象原型类
  • 具体原型类
  • 访问类

3.1 实现

可以按照原型模式的结构来创建对应的类,但是Java中的Object类提供了一个clone()方法,可以将一个Java对象复制一份。因此在Java中可以直接使用Object提供clone()方法来实现对象的浅克隆

此时Object类相当于抽象原型类,所有实现了Cloneable接口的类相当于具体原型类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 具体原型类(浅克隆)
public class ConcretePrototype implements Cloneable {
private String name; // 值类型
private Product product; // 应用类型,深克隆需要调用product.clone()进行引用类型的复制

public Prototype clone() {
Object object = null;
try {
object = super.clone(); // 浅克隆
} catch (CloneNotSupportedException exception) {
System.err.println("Not support Cloneable");
}
return (Prototype)object;
}
}

3.2 应用

四、建造者模式

建造者模式结构:

  • 抽象建造者:规范产品对象的各个组成部分的建造
  • 具体建造者:具体化对象的各个组成部分的创建
  • 复杂产品类:需要被构建的复杂对象,包含多个组成部件,具体建造者创建该产品的内部表示并定义它的装配过程
  • 指挥者类:指挥建造者创建产品的各个部分,并返回产品对象

4.1 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// 复杂产品类
public class Product {
private String partA; // 定义部件,部件可以是任意类型,包括值类型和引用类型
private String partB;
private String partC;
// 属性的Getter和Setter方法省略
}

// 抽象建造者类
public abstract class Builder {
// 创建产品对象
protected Product product = new Product();

public abstract void buildPartA();
public abstract void buildPartB();
public abstract void buildPartC();

// 返回产品对象
public Product getResult() {
return product;
}
}

// 具体建造者1
public class ConcreteBuilder1 extends Builder {
public void buildPartA() {
product.setPartA("A1");
}
public void buildPartB() {
product.setPartA("B1");
}
public void buildPartC() {
product.setPartA("C1");
}
}

// 指挥者类
public class Director {
private Builder builder;

public Director(Builder builder) {
this.builder = builder;
}
public void setBuilder(Builder builder) {
this.builder = builder;
}
// 产品的构建与组装方法
public Product construct() {
builder.buildPartA();
builder.buildPartB();
builder.buildPartC();
return builder.getResult();
}
}

4.2 应用

一、类的表示

  1. 类名(上)、属性(中)、方法(下)
    • 接口(上)需要在接口名上标注"<<interface>>"
  2. 属性的表示语法:[可见性]属性名[:类型][=初始值]
  3. 类方法的表示语法:[可见性]操作名[(参数表)][:返回类型]
    • 参数表表示:”名称:类型”,多个参数用逗号隔开,可以有默认值
    • 可见性:”-“表示private;”+”表示public;”#”表示protected

二、类间关系

2.1 泛化关系

  • 表示“一般-具体”关系
  • 高层次的类被称为基类或者超类,具有一般的公共信息
  • 低层次的类被称为派生类或子类,公共信息继承自基类,具体信息可以自定义
  • 如:

    • 交通工具为基类,定义基本功能
    • 汽车等具体交通工具为派生类,自定义其特性
  • 泛化关系的表示:带三角形的实线箭头,箭头指向父类

2.2 关联关系

2.2.1 组合关系

  • 表示“整体-部分”关系
  • 部分==不能==离开整体而单独存在
  • 如:教材 包含 封面、前言、目录等,他们之间是整体与部分的关系
  • 组合关系的表示:实心菱形的实线表示,菱形指向整体

2.2.2 聚合关系

  • 表示“整体-部分”关系
  • 部分==可以==离开整体而存在
  • 如:车 包含 轮胎、引擎等
    • 车胎、引擎等可以不依赖车子而单独存在
  • 聚合关系的表示:空心菱形的实线表示,菱形指向整体

2.2.3 一般关联关系

  • 表示两个类的对象之间可以相互通讯,或者一个对象能够感知另一方
  • 如:朋友关系
  • 一般关联关系的表示:单向或双向,双向关联的箭头可加或不加,线段上的数字表示关联的数量和范围

2.3 依赖关系

  • 表示元素A的变化会影响到元素B,或一个类的实现需要另一个类的协助
  • 如:打电话需要手机
  • 代码上表现为一个类的使用需要另一个类的对象作为参数
  • 依赖关系的表示:带箭头的虚线,箭头指向被依赖方

2.4 实现关系

  • 表示类实现接口的功能
  • 实现关系的表示:带空心箭头的虚线,箭头指向接口

一、概述

1.1 JVM结构

  • 方法区/堆是多线程共享的
  • 虚拟机栈/本地方法栈/程序计数器是每个线程独有一份的,即线程私有

1.2 虚拟机历史

1.3 结构分析

1.3.1 类加载器

类的生命周期

加载、验证、准备、解析、初始化、使用、卸载

1.3.2 运行时数据区

(1)程序计数器

程序计数器Program Counter Register是一小块内存空间,它表示的是执行字节码指令的行号字节码解释器工作时就是通过改变程序计数器来选取下一条需要执行的字节码指令

程序计数器有以下特点:

  • 线程私有
  • 线程执行的是Java方法,则程序计数器记录的是字节码指令的地址
  • 线程执行的是本地(Native)方法,那么程序计数器的值为空(Undefined)

(2)虚拟机栈

一个程序的运行都要维护一个堆栈,这里的堆用于存放数据,而栈用于处理数据是如何执行的,JVM也包含这样的设计思想,这里所要介绍的虚拟机栈就是用来存放方法执行的线程内存模型。

它有以下几个特点:

  • 每个线程创建时,都会创建一个虚拟机栈
  • 当线程中的方法被调用时,都会将其作为一个栈帧压入到虚拟机栈中
  • 虚拟机栈是线程私有

虚拟机栈存在以下异常:

  • StackOverflowError:线程请求的栈的深度大于虚拟机所允许的深度,则抛出此异常
  • OutOfMemoryError:虚拟机栈容量可以动态扩展,当栈扩展时无法申请到足够的内存是,则抛出此异常

(3)本地方法栈

本地方法栈是服务于本地(Native)方法的,虚拟机栈是服务于Java方法的,这两个本质上没什么区别,在HotSpot虚拟机中直接将两者合二为一了

(4)Java堆

Java堆的作用是存放对象的实例,它有以下特点:

  • 线程共享
  • Java堆是被垃圾收集器管理的内存区域

存在的异常:

  • OutOfMemoryError:当Java堆无法再为实例分配足够的内存,且堆无法再扩展时,抛出此异常

(5)方法区

方法区用于存储已被虚拟机加载的类型信息常量静态变量、即时编译器编译后的代码缓存等数据

方法区还包含一个运行时常量池(Runtime Constant Pool),Java编译为Class文件后,Class文件除了有类的版本、字段、方法、接口等描述信息外,还包含常量池表(Constant Pool Table),用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池

存在的异常:

  • OutOfMemoryError:当方法区无法满足新的内存分配需求时,抛出此异常

1.3.4 执行引擎

执行引擎包含三个部分:解释器、JIT即时编译器、垃圾回收器

JVM执行引擎的主要任务就是将字节码指令解释为对应平台上的本地机器指令

二、类加载器

2.1 类加载过程

2.2 类加载器分类

2.2.1 启动(引导)类加载器

  • 这个类加载器使用C/C++实现,嵌套在JVM内部
  • 用来加载Java的核心库(JAVA_HOME/jre/lib/rt.jar、resources.jar或sun.boot.class.path路径下的内容),用于提供JVM自身需要的类
  • 没有父加载器,并不继承java.lang.ClassLoader
  • 负责加载扩展类加载器和系统类加载器,并指定未他们的父类加载器
  • 出于安全考虑,Bootstrap启动类加载器只加载包名未java、javax、sun等开头的类

2.2.2 扩展类加载器

  • Java语言编写,由sun.misc.Launcher$ExtClassLoader实现
  • 派生于ClassLoader类
  • 父类加载器为启动类加载器
  • 从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录jre/lib/ext子目录(扩展目录)下加载类库。如果用户创建的JAR放在此目录下,也会自动由扩展类加载器加载

2.2.3 系统(应用程序)类加载器

  • Java语言编写,由sun.misc.Launcher$AppClassLoader实现
  • 派生于ClassLoader类
  • 父类加载器为扩展类加载器
  • 负责加载环境变量classpath或系统属性java.class.path指定路径下的类库
  • 该类加载是程序中默认的类加载器,一般来说,Java应用的类都是由它来完成加载
  • 通过ClassLoader.getSystemClassLoader()方法可以获取到该类加载器

2.2.4 用户自定义类加载器

  • 隔离加载类
  • 修改类加载的方式
  • 扩展加载源
  • 防止源码泄漏

2.3 双亲委派机制

双亲委派机制是指子加载器加载时会向上传递依靠父加载器进行加载,如果父加载器无法进行处理才会一步步的交给子加载器进行类的加载操作

三、程序寄存器(PC寄存器)

3.1 概念

程序计数器(Program Counter Register),寄存器Register的命名来源于CPU的寄存器,寄存器存储指令相关的信息,CPU只有把数据装载到寄存器中才能运行

JVM的PC寄存器的概念跟物理上CPU的寄存器有些区别,PC寄存器是一种对物理寄存器的抽象模拟

3.2 作用

PC寄存器用来存储指向下一条指令的地址,也即将要执行的指令代码,由执行引擎读取下一条指令

3.3 特点

  • 所占空间内容小
  • 线程私有,生命周期与线程一致
  • 任何时间一个线程都只有一个方法在执行,也就是所谓的当前方法。程序计数器会存储当前程序正在执行的Java方法的JVM指令地址;如果是在执行native方法,则是未指定值(undefined)
  • 它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成
  • 此区域不会出现OurOfMemoryError(OOM)

四、虚拟机栈

4.1 概念

Java虚拟机栈也叫Java栈,每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧(Stack Frame),对应着一次次的Java方法调用

  • 特点:线程私有

  • 生命周期:与线程一致

  • 作用:主管Java程序的运行,保存方法的局部变量、部分结果,并参与方法的调用和返回

4.2 栈帧的内部结构

  • 局部变量表Local Variables
  • 操作数栈Operate Stack:
  • 动态链接Dynamic Linking:指向运行时常量池的方法引用
  • 方法返回地址Return Address:
  • 一些附加信息

4.3 局部变量表

定义为一个数字数组,主要用于存储方法参数和定义在方法体内的局部变量,这些数据包括各类基本数据类型、对象引用(reference),以及returnAddress类型

局部变量表所需的容量大小是在编译器期确定下来的,并保存在方法的Code属性的maximum local variables数据项中。在方法运行期间是不会改变局部变量表的大小的

boolean、byte、short、char在存储前会被转换位int

4.4 slot的理解

Slot(变量槽)是局部变量表的最基础的存储单元

在局部变量表里,32位以内的类型只占用一个slot(包括returnAddress类型),64位的类型(long和double)占用两个slot

JVM会为局部变量表中的每个Slot都分配一个访问索引,通过这个索即可成功访问到局部变量表中指定的局部变量值

4.5 操作数栈

主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间

32位的类型占用一个栈单位深度

64位类型占用两个栈单位深度

4.6 动态链接(方法的调用)

动态链接:栈帧内部指向运行时常量池该栈帧所属方法的引用,包含这个引用的目的就是为了支持当前方法的代码能够实现动态链接,比如invokedynamic指令

动态链接的作用:在Java源文件被编译到字节码文件中时,所有的变量和方法引用都作为符号引用( symbolic Reference)保存在class文件的常量池里。比如:描述一个方法调用了另外的其他方法时,就是通过常量池中指向方法的符号引用来表示的,那么动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用

4.6.1 方法的绑定机制

在JVM中,将符号引用转换为调用方法的直接引用与方法的绑定机制相关。

静态链接:当一个字节码文件被装载进JVM内部时,如果被调用的目标方法在编译期可知且运行期保持不变时。这种情况下将调用方法的符号引用转换为直接引用的过程称之为静态链接。

动态链接:如果被调用的方法在编译期无法被确定下来,也就是说,只能够在程序运行期将调用方法的符号引用转换为直接引用,由于这种引用转换过程具备动态性,因此也就被称之为动态链接。

对应的方法的绑定机制为:早期绑定(Early Binding)晚期绑定(Late Binding)。绑定是一个字段、方法或者类在符号引用被替换为直接引用的过程,这仅仅发生一次。

早期绑定:早期绑定就是指被调用的目标方法如果在编译期可知,且运行期保持不变时即可将这个方法与所属的类型进行绑定,这样一来,由于明确了被调用的目标方法究竟是哪一个,因此也就可以使用静态链接的方式将符号引用转换为直接引用。

晚期绑定:如果被调用的方法在编译期无法被确定下来,只能够在程序运行期根据实际的类型绑定相关的方法,这种绑定方式也就被称之为晚期绑定。这里就体现出了多态的特性

4.6.2 虚方法、非虚方法和虚方法表

非虚方法:如果方法在编译期就确定了具体的调用版本,这个版本在运行时是不可变的。这样的方法称为非虚方法。如静态方法、私有方法、final方法、实例构造器、父类方法都是非虚方法。其他方法称为虚方法。

子类多态性的前提:1、类的继承关系;2、方法的重写

虚方法表:由于Java中会经常使用到动态分派,如果在每次动态分派过程中都要重新在类的方法元数据中搜索合适的目标就容易影响效率,所以JVM在每个类的方法区中都建立一个虚方法表,使用索引表来查找虚方法

虚方法表会在类加载的链接阶段被创建并开始初始化,类的变量初始值准备完成之后,JVM会把该类的方法表也初始化完毕

如果Animal中重写了toString()方法,则toString()的就会指向Animal,否则就会指向Object

4.6.3 方法调用指令

虚拟机中提供了以下几条方法调用指令:·普通调用指令:

  • invokestatic:调用静态方法,解析阶段确定唯一方法版本

  • invokespecial:调用<init>方法、私有及父类方法,解析阶段确定唯一方法版本

  • invokevirtual:调用所有虚方法

  • invokeinterface:调用接口方法

动态调用指令:

  • invokedynamic:动态解析出需要调用的方法,然后执行前四条指令固化在虚拟机内部,方法的调用执行不可人为干预,而invokedynamic指令则支持由用户确定方法版本。其中invokestatic指令和invokespecial指令调用的方法称为非虚方法,其余的(final修饰的除外)称为虚方法。

4.6.4 invokedynamic指令

动态类型语言和静态类型语言两者的区别就在于对类型的检查是在编译期还是在运行期,满足前者就是静态类型语言,反之是动态类型语言。

说的再直白一点就是,静态类型语言是判断变量自身的类型信息;动态类型语言是判断变量值的类型信息,变量没有类型信息,变量值才有类型信息,这是动态语言的一个重要特征,比如js、python。

Java仍然属于静态语言,但拥有动态特性,动态性体现在lambda表达式上

4.6.5 方法重写的本质

  1. 找到操作数栈顶的第一个元素所执行的对象的实际类型,记作C

  2. 先从C开始查找,如果从类型c中找到与常量池中的描述与名称都相符的方法则进行访问权限校验(即public/protected/private访问权限的设置或者不同包之间的访问权限),如果通过则返回这个方法的直接引用,如果不通过,则返回java.lang.IllegalAccessError异常。

  3. 否则,按照继承关系从下往上依次对C的各个父类进行第2步的搜索和验证过程。

  4. 如果始终没有找到合适的方法,则抛出java.lang.AbstractMethodError异常。

4.7 方法返回地址return address

方法返回地址存放该方法的PC寄存器的值,即调用该方法的指令的下一条指令的地址

一个方法的结束,有两种方式:

  • 正常执行完成,方法退出后返回到该方法被调用的位置
  • 出现未处理的异常,非正常退出,返回地址要通过异常表来确定,栈帧不会保存这部分信息

方法正常调用后使用哪一条返回指令需要根据方法返回值的实际数据类型确定,方法返回指令:ireturn(byte、char、short、int、boolearn)、lreturn、freturn、dreturn、areturn(引用类型),最后return指令供void、实例初始化方法、类和接口的初始化方法使用

方法的退出就是当前栈帧出栈的过程,此时,需要恢复上层方法的局部变量表、操作数栈、将返回值压入调用者栈帧的操作数栈、设置PC寄存器等,让调用者方法继续执行下去

4.8、本地方法接口

Native Method就是一个Java调用非Java代码的接口,比如C/C++

Java在定义一个native method时,并不提供实现体(有些像定义一个Java接口),因为其实现体是由非java语言在外面实现的

Java中的本地方法用native进行修饰,native可以与所有其它的java标识符连用,但是abstract除外

4.8.1 使用本地方法的原因

4.8.2 本地方法栈

五、堆

5.1 概述

  • 一个JVM实例只存在一个堆内存,堆也是Java内存管理的核心区域,Java堆区在JVM启动时候即被创建,其空间大小也就确定了。堆是JVM管理的最大一块内存空间,堆的大小是可以调节的

  • 堆,是GC(Garbage Collection,垃圾收集器)执行垃圾回收的重点区域

  • 《Java虚拟机规范》规定,堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为连续的

  • 《Java虚拟机规范》中对Java堆的描述是:所有的对象实例以及数组都应当在运行时分配在堆上
    • 从实际使用角度看:“几乎”所有的对象实例都在堆分配内存,但并非全部。因为还有一些对象是在栈上分配的(逃逸分析,标量替换)
  • 在方法结束后,堆中的对象不会马上被移除,仅仅在垃圾收集的时候才会被移除

5.2 堆空间结构

  • JDK7把堆空间分为新生代+老年代+永久代
  • JDK8把堆空间分为新生代+老年代+元空间
  • 年轻代中的S0和S1分别表示幸存者一区(from区)和幸存者二区(to区),程序运行时只会选择其中一个存放数据
  • 新生代与老年代的比例为1:2

5.2.1 新生代

新生成的对象优先存放在新生区中,新生区对象朝生夕死,存活率很低,在新生代中,常规应用进行一次垃圾收集一般可以回收70% ~ 95% 的空间,回收效率很高,默认的新生代与老年代的比例为1:2

HotSpot将新生区划分为三块,一块较大的Eden(伊甸)空间和两块较小的Survivor(幸存者)空间,分别称为From区和To区,默认比例为8:1:1

划分的目的是因为HotSpot采用复制算法来回收新生代,设置这个比例是为了充分利用内存空间,减少浪费。新生成的对象在Eden区分配(大对象除外,大对象直接进入老年代),当Eden区没有足够的空间进行分配时,虚拟机将发起一次Minor GC

5.2.2 老年代

在新生代中经历了多次(具体看虚拟机配置的阀值)GC后仍然存活下来的对象会进入老年代中。老年代中的对象生命周期较长,存活率比较高,在老年代中进行GC的频率相对而言较低,而且回收的速度也比较慢。

  • 大对象直接进入老年代:JVM中有这样一个参数 -XX: PretenureSizeThreshold ,指定大于该设置值的对象直接在老年代分配,这样做的目的就是避免在Eden区以及2个Survivor区之间来回复制,产生大量的内存复制操作
  • 对象年龄:对象通常在Eden区诞生,如果经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,该对象会被移动到Survivor中,并且将其对象设为1岁,对象在Survivor区中每熬过一次Minor GC,年龄就增加一岁,当它的年龄增加到一定程度(默认15),就会被晋升到老年代中,对象晋升老年代的年龄阈值, 可以通过参数-XX:MaxTenuringThreshold设置

5.2.3 永久代/元空间

永久区存储类信息、常量、静态变量、即时编译器编译后的代码等数据,对这一区域而言,Java虚拟机规范指出可以不进行垃圾收集,一般而言不会进行垃圾回收。

  • JDK1.6之前,永久代,常量池在方法区
  • JDK1.7,永久代,但是慢慢退化了,去永久代,常量池在堆中
  • JDK1.8之后,无永久代,常量池在元空间

这个区域常驻内存的,用来存放JDK自身携带的Class对象,Interface元数据,存储的是Java运行的一些环境或者类信息,这个区域不存在垃圾回收。关闭VM虚拟机就会释放这个区域的内存。

5.3 对象内存分配过程

  • 对象优先在Eden区分配,当进行YGC时,会将存活的对象放到From区,To区空着不用。
  • 当第二次进行YGC时,会将From区和Eden区存活的对象复制到To区,此时Eden区和From区就为空。
  • 当第三次进行YGC时,会将To区和Eden区存活的对象复制到From区,此时Eden区和To就为空。
  • 按照此规律,循环往复下去

5.3.1 TLAB

TLAB全称为Thread Local Allocation Buffer,由于以下原因,从而导致TLAB的产生

  • 堆区是线程共享区域,任何线程都可以访问到堆区中的共享数据
  • 由于对象实例的创建在JVM中非常频繁,因此在并发环境下从堆区中划分内
    存空间是线程不安全的
  • 为避免多个线程操作同一地址,需要使用加锁等机制,进而影响分配速度。

从内存模型而不是垃圾收集的角度,对Eden区域继续进行划分,JVM为每个线程分配了一个私有缓存区域,它包含在Eden空间内。

多线程同时分配内存时,使用TLAB可以避免一系列的非线程安全问题,同时还能够提升内存分配的吞吐量,因此我们可以将这种内存分配方式称之为快速分配策略

现在new一个对象,然后TLAB分配,如果TLAB空间够用,那么就对象实例化,如果不够用就只能用Eden公共的部分,如果还不够用,那么就触发GC

5.4 GC垃圾回收

JVM在进行Gc时,并非每次都对新生代、老年代、方法区一起回收的,大部分时候回收的都是指新生代。

针对HotSpot VM的实现,它里面的GC按照回收区域又分为两大种类型:一种是部分收集(Partial GC),一种是整堆收集(Full GC)

  • 部分收集:不是完整收集整个Java堆的垃圾收集。其中又分为:
    • 新生代收集(Minor GC / Young Gc):只是新生代的垃圾收集
    • 老年代收集(Major Gc / old GC):只是老年代的垃圾收集。
      目前,只有CMS GC会有单独收集老年代的行为。注意,很多时候Major Gc会和Full GC混淆使用,需要具体分辨是老年代回收还是整堆回收。
    • 混合收集(Mixed Gc):收集整个新生代以及部分老年代的垃圾收集。
      目前,只有G1 Gc会有这种行为
  • 整堆收集(Full GC):收集整个java堆和方法区的垃圾收集。

5.4.1 触发条件

  • 年轻代Minor GC:当年轻代空间不足时,就会触发Minor Gc,这里的年轻代满指的是Eden代满,Survivor满不会引发GC。(每次 Minor GC会清理年轻代的内存)。
    • 因为Java对象大多都具备朝生夕灭的特性,所以 Minor GC非常频繁,一般回收速度也比较快。这一定义既清晰又易于理解。Minor GC会引发STW,即暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行。
  • 老年代Major Gc:指发生在老年代的Gc,对象从老年代消失时,我们说“Major GC”或“Full GC”发生了
    • 在老年代空间不足时,会先尝试触发Minor Gc。如果之后空间还不足,则触发Major GC
    • Major GC的速度一般会比Minor GC慢10倍以上,STW的时间更长
    • 如果Major GC后,内存还不足,就报OOM了
  • 整堆收集(Full GC)
    • 调用System.gc()时,系统建议执行Full GC,但是不必然执行
    • 老年代空间不足
    • 方法区空间不足
    • 通过Minor GC后进入老年代的平均大小大于老年代的可用内存
    • Edan区、survivor space0(From Space)区向survivor space1(To Space)区复制时,对象大小大于To Space可用内存,则把对象转存到老年代,且老年代的可用内存小于该对象大小
    • Full GC是开发或调优中尽量避免的

5.5 栈上分配

5.6 代码优化

六、方法区

6.1 概述

《Java虚拟机规范》中明确说明:”尽管所有的方法区在逻辑上是属于堆的一部分,但一些简单的实现可能不会选择去进行垃圾收集或者进行压缩。”但对于HotSpotJVM而言,方法区还有一个别名叫做Non-Heap(非堆),目的就是要和堆分开。

方法区具有以下特点:

  • 方法区((Method Area)与Java堆一样,是各个线程共享的内存区域。
  • 方法区在JVM启动的时候被创建,并且它的实际的物理内存空间中和Java堆区一样都可以是不连续的。
  • 方法区的大小,跟堆空间一样,可以选择固定大小或者可扩展。
  • 方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类,导致方法区溢出,虚拟机同样会抛出内存溢出错误: java.lang.outOfMemoryError
  • java.lang. outOfMemoryError: PermGen space或者java.lang. outOfMemoryError: Metaspace
    • 加载大量的第三方的Jar包
    • Tomcat部署的工程太多
    • 大量动态的生成反射类
  • 关闭JVM就会释放这个区域的内存。

本质上,永久代、元空间都是对方法区的实现,不能直接对其画等号,因为还有其他的方法区实现方式,永久代和永久代存在本质区别:

  • 元空间不在虚拟机设置的内存中,而是使用本地内存

6.2 方法区大小设置

  • jdk7及以前:
    • 通过-XX:PermSize来设置永久代初始分配空间。默认值是20.75M
    • -XX:MaxPermSize来设定永久代最大可分配空间。32位机器默认是64M,64位机器模式是82M
    • 当JV8M加载的类信息容量超过了这个值,会报异常outOfMemoryError:PermGen space。
  • jdk8及以后:
    • 元数据区大小可以使用参数-XX:Metaspacesize和-XX:MaxMetaspaceSize指定,替代上述原有的两个参数。
    • 默认值依赖于平台。windows下,-XX:MetaspaceSize是21M,-XX:MaxMetaspacesize 的值是-1,即没有限制。
    • 与永久代不同,如果不指定大小,默认情况下,虚拟机会耗尽所有的可用系统内存。如果元数据区发生溢出,虚拟机一样会抛出异常outOfMemoryError: Metaspace
    • -XX:MetaspaceSize:设置初始的元空间大小。对于一个64位的服务器端JVM来说,其默认的-XX:Metaspacesize值为21MB。这就是初始的高水位线,一旦触及这个水位线,Full GC将会被触发并卸载没用的类(即这些类对应的类加载器不再存活),然后这个高水位线将会重置。新的高水位线的值取决于Gc后释放了多少元空间。如果释放的空间不足,那么在不超过MaxMetaspacesize时,适当提高该值。如果释放空间过多,则适当降低该值。
    • 如果初始化的高水位线设置过低,上述高水位线调整情况会发生很多次。通过垃圾回收器的日志可以观察到Full GC多次调用。为了避免频繁地GC ,建议将-XX:Metaspacesize设置为一个相对较高的值。

6.3 内部结构

6.3.1 存放内容

  • 类型信息
    • 这个类型的完整有效名称(全名=包名.类名)
    • 这个类型直接父类的完整有效名(对于interface或是java.lang.0bject,都没有父类)
    • 这个类型的修饰符(public, abstract,final的某个子集)
    • 这个类型直接接口的一个有序列表
  • 域信息(成员变量)
    • JVM必须在方法区中保存类型的所有域的相关信息以及域的声明顺序。
    • 域的相关信息包括:域名称、域类型、域修饰符(public, private,protected,static,final, volatile, transient的某个子集)
  • 方法信息
    • 方法名称
    • 方法的返回类型(或void)·方法参数的数量和类型(按顺序)
    • 方法的修饰符(public, private,protected,static, final,synchronized,native,abstract的一个子集)
    • 方法的字节码(bytecodes)、操作数栈、局部变量表及大小(abstract和native方法除外)
    • 异常表(abstract和native方法除外)
      • 每个异常处理的开始位置、结束位置、代码处理在程序计数器中的偏移地址、被捕获的异常类的常量池索引
  • 静态变量、常量、即时编译器编译后的代码缓存

6.4 运行时常量池

一个有效的字节码文件中除了包含类的版本信息、字段、方法以及接口等描述信息外,还包含一项信息那就是常量池表(constant pool Table),包括各种字面量和对类型、域和方法的符号引用。

常量池,可以看做是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等类型。简单来说就是存放字面量和符号引用

常量池是 *.class 文件中的。当类的字节码被加载到内存中后,他的常量池信息就会集中放入到一块内存,这块内存就称为运行时常量池,并且把里面的符号地址为真实地址

以下为java代码编译后的*.class文件的常量池内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// ===========================================常量池===============================================
Constant pool:
#1 = Methodref #6.#20 // java/lang/Object."<init>":()V
#2 = Fieldref #21.#22 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #23 // hello world
#4 = Methodref #24.#25 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #26 // org/memory/jvm/t5/HelloWorld
#6 = Class #27 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lorg/memory/jvm/t5/HelloWorld;
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = Utf8 args
#17 = Utf8 [Ljava/lang/String;
#18 = Utf8 SourceFile
#19 = Utf8 HelloWorld.java
#20 = NameAndType #7:#8 // "<init>":()V
#21 = Class #28 // java/lang/System
#22 = NameAndType #29:#30 // out:Ljava/io/PrintStream;
#23 = Utf8 hello world
#24 = Class #31 // java/io/PrintStream
#25 = NameAndType #32:#33 // println:(Ljava/lang/String;)V
#26 = Utf8 org/memory/jvm/t5/HelloWorld
#27 = Utf8 java/lang/Object
#28 = Utf8 java/lang/System
#29 = Utf8 out
#30 = Utf8 Ljava/io/PrintStream;
#31 = Utf8 java/io/PrintStream
#32 = Utf8 println
#33 = Utf8 (Ljava/lang/String;)V

一、常用对象和方法

判断键盘按键

  • 为GameObject绑定一个脚本组件
  • 脚本代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class cubetest : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
Debug.Log("测试键盘控制");
}

// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.D))
{
this.transform.Translate(Vector3.right * 1);
}
else if (Input.GetKeyDown(KeyCode.A))
{
this.transform.Translate(Vector3.left * 1);
}
else if (Input.GetKeyDown(KeyCode.S))
{
this.transform.Translate(Vector3.down * 1);
}
else if (Input.GetKeyDown(KeyCode.W))
{
this.transform.Translate(Vector3.up * 1);
}

}
}

一、简介

  • Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程
  • 特点:约定大于配置

二、快速创建Springboot web应用

三、自动配置原理

3.1 pom依赖

  • pom.xml文件的父依赖中有Springboot的所有依赖包
  • 所有我们在导入依赖包时不需要亲自指定包的版本就可以导入包了

3.2 启动器

  • 如spring-boot-start-web,他就会帮我们自动导入web环境所有的依赖
  • springboot会将所有的功能场景都变成一个个的启动器

四、yaml配置文件

4.1 基本配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
server:
port: 8080

spring:
datasource:
url: jdbc:mysql://localhost:3306/vue?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&serverTimezone=GMT%2B8&useSSL=false
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver

# jpa配置
jpa:
show-sql: true
properties:
hibernate:
format_sql: true
# Mybatis配置
mybatis:
# 配置XML映射文件中指定的实体类别名路径
type-aliases-package: com.example.demo3.pojo
# 配置MyBatis的xml配置文件路径
mapper-locations: classpath:mapper/*.xml
# 开启驼峰uName自动映射到u_name
map-underscore-to-camel-case: true

# 打印sql
logging:
level:
ssm.app.mapper : debug

4.2 属性注入

  • 使用yaml完成对对象的属性注入
1
2
3
4
5
6
7
8
9
10
11
# 与注解@ConfigurationProperties(prefix = "user")的user对应
user:
username: gaoyue
password: 123456
age: ${random.int} # 使用${}输入内置表达式
pets:
- cat
- dog
friends: {John: China, Mary: USA} # 注意:后必须有空格
dog:
name: ${user.username}_wancai #表达式和字符拼接注入
  • 注意需要导入以下依赖,否则报错:
1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
  • pojo类使用注解@ConfigurationProperties绑定配置文件,prefix为配置文件中的根变量名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Data
@Component
@ConfigurationProperties(prefix = "user") //这里的user对应配置文件的user
public class User {
private String username;
private String password;
private Integer age;
private List<String> pets;
private Map<String,Object> friends;
private Dog dog;
}

@Data
@Component
public class Dog {
private String name;
}

4.3 JSR303数据校验

  • 数据校验是yaml配置文件才有的,这也是它比properties强大的地方

  • 在对应类上加上注解@Validated,即可以对类中属性添加数据校验对应的注解,如果不满足数据校验的格式就会直接报错,包括以下几个:

注解 作用
@Null 限制对象只能为null
@NotNull 限制对象必须不为null
@AssertTrue 限制Boolean必须为true
@AssertFalse 限制Boolean必须为false
@Min(value) 验证Number和String对象是否大于等于指定的值
@Max(value) 验证Number和String对象是否小于等于指定的值
@DecimalMin(value) 验证Number和String对象是否大于等于指定的值,小数存在精度
@DecimalMax(value) 验证Number和String对象是否小于等于指定的值,小数存在精度
@Size(max,min) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Digits 验证Number和String的构成是否合法
@Past 验证Date和Calendar对象是否在当前时间之前
@Future 验证Date和Calendar对象是否在当前时间之后
@Pattern(value) 限制String必须符合指定的正则表达式
@NotEmpty 验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0)
@NotBlank 验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,只应用于字符串且在比较时会去除字符串的空格
@Email 验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式

五、Web开发

5.1 静态资源分析

  • 静态资源一般保存在以下几个路径,优先级resources>public>static
    • webjars
    • resources/public
    • resources/resources
    • resources/static
    • /**
  • 以上的资源文件都可以通过“localhost:8080/文件名”访问到文件内容

5.2 Thymeleaf入门

  • 表达式:
    • ${expression}:取出后端传递的值
    • @{link url}:引用外部链接时,需把链接放到@{}中

5.3 拦截器

5.3.1 创建拦截器配置类

1
2
3
4
5
6
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return false;
}
}

5.3.2 注入拦截器

1
2
3
4
5
6
7
8
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**") // 拦截的路径
.excludePathPatterns("/","/index.html"); //放行的路径
}
}

六、连接数据库

6.1 yaml配置

1
2
3
4
5
6
spring:
datasource:
url: jdbc:mysql://localhost:3306/vue?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&serverTimezone=GMT%2B8&useSSL=false
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver

6.2、整合Mybatis

6.2.1 导入依赖包

  • 导入Mybatis Spring Boot Start依赖包
1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
  • 版本要求:

6.2.2 环境搭建

  • @Mapper注解标注Mapper类
  • @Repository标注Dao层
  • 创建xxxMapper.xml编写sql
  • yaml配置xxxMapper.xml的文件路径:
1
2
3
4
5
6
7
8
# Mybatis配置
mybatis:
# 配置XML映射文件中指定的实体类别名路径
type-aliases-package: com.example.demo3.pojo
# 配置MyBatis的xml配置文件路径
mapper-locations: classpath:mapper/*.xml
# 开启驼峰uName自动映射到u_name
map-underscore-to-camel-case: true

七、认证与授权

一、常用依赖包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<dependencies>
<!-- spring-webmvc包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.15</version>
</dependency>

<!-- servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>

<!-- thymeleaf-spring5视图解析 -->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
<version>3.0.12.RELEASE</version>
</dependency>

<!-- mysql连接器 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>

<!-- mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>

<!-- junit单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>

<!-- lombok插件快速生成get/set -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>

</dependencies>

二、环境配置

2.1 创建webapp

  • 在main目录下创建webapp目录
    • 创建webapp/WEB-INF
    • 创建webapp/WEB-INF/templates
    • 创建webapp/WEB-INF/web.xml
  • web.xml配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<!--
<?xml version="1.0" encoding="UTF-8"?>
-->

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!-- 配置过滤器 -->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

<!--配置过滤器,用于将前端表单的get/post请求转换为put/delete请求-->
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

<!-- 配置SpringMVC前端控制器,对浏览器发送的请求进行统一处理 -->
<servlet>
<servlet-name>springMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

<!-- 配置SpringMVC配置文件的位置和名称,然后在reources中创建xml配置文件 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springMVC.xml</param-value>
</init-param>

<!-- 将前端控制器DispatcherServlet启动时间提前到服务器启动的时候 -->
<!-- 提升第一次访问的速度 -->
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<!--
设置SpringMVC核心控制器所能处理的请求路径
/ 所匹配的请求可以是/login或.html/.css/.js等方式的请求路径
但不匹配.jsp请求路径的请求

-->
<servlet-name>springMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>

2.1 SpringMVC配置

  • 自动扫描包要改成自己的包环境
  • 配置Thymeleaf视图解析器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<!--
<?xml version="1.0" encoding="UTF-8"?>
-->

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 自动扫描包,包命改成自己的包环境 -->
<context:component-scan base-package="com.personal"/>

<!-- 配置Thymeleaf视图解析器 -->
<bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
<property name="order" value="1"/>
<property name="characterEncoding" value="UTF-8"/>
<property name="templateEngine">
<bean class="org.thymeleaf.spring5.SpringTemplateEngine">
<property name="templateResolver">
<bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">

<!-- 视图前缀 -->
<property name="prefix" value="/WEB-INF/templates/"/>

<!-- 视图后缀 -->
<property name="suffix" value=".html"/>
<property name="templateMode" value="HTML5"/>
<property name="characterEncoding" value="UTF-8" />
</bean>
</property>
</bean>
</property>
</bean>
</beans>

2.2 Mybatis配置

  • 创建resources/db.properties数据库配置文件
1
2
3
4
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/personalsystem?useSSL=true&useUnicode=true&characterEncoding=UTF-8
username=root
password=xxx
  • 创建resources/mybatis.xml
    • properties加载数据库配置文件标签不能少
    • mappers标签配置我们的dao层配置文件路径
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>
<properties resource="db.properties"/>

<settings>
<!--显示开启全局缓存,默认是开启的-->
<setting name="cacheEnabled" value="true"/>
</settings>

<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>

<!--对应我们的xxxMapper.xml路径-->
<mappers>
<mapper resource="com/personal/dao/UserMapper.xml"/>
</mappers>
</configuration>
  • 开启资源路径访问,请配置到pom.xml文件中,否则xxxMapper.xml无法被访问到而导致程序报错
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!--  配置资源导出,防止dao层中的xml无法访问而报错  -->
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
</resource>

<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>

2.3 创建实体类和Mapper

2.3.1 创建实体类

1
2
3
4
5
6
7
8
9
10
11
12
package com.personal.pojo;

import lombok.Data;

@Data
public class User {
private Integer id;
private String username;
private String password;
private Integer role;
}

2.3.2 创建Mapper

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!--绑定命名空间,绑定自己的dao层接口-->
<mapper namespace="com.personal.dao.UserMapper">
<select id="getUserList" resultType="com.personal.pojo.User">
select * from personalsystem.user;
</select>
</mapper>

三、日志

一、变量自增

1.1 示例

  • 这种计算跟之前数据结构和算法学过的计算器一致,都是要维护一个栈,保存操作数,判断加减乘除运算符,最后栈中经过计算后只剩下一个数,就是我们需要的运算结果
  • Java也需要维护一个操作数栈,来计算我们的最终结果
1
2
3
4
5
6
7
int i = 1;
i = i++;
int j = i++;
int k = i + ++i * i++;
System.out.println("i="+i);5
System.out.println("j="+j);2
System.out.println("k="+k);12

1.2 分析

1.2.1 前两步

  1. 赋值操作更新局部变量表,变量操作会先将变量值压入操作数栈
  2. 自增操作会更新变量的局部变量表,但不会影响操作数栈的值,存在操作数就会把变量的值压入操作数栈中
  3. 赋值操作是把操作数栈的最终结果赋值给变量,也就是说i经过前两步后值为1

1.2.2 第三步

  • 后增操作:先赋值j,再自增i

1.2.3 第四步

1.3 小结

  • 总的来说在运算的时候维护一个局部变量表和一个操作数栈,来保存更新我们的数据

  • ++i会先修改i保存到局部变量表中的值,再将自增后的值放入到操作数栈中

  • i++与++i相反,会先将i的值放入到操作数栈中,而后再更新i的局部变量表
  • 赋值操作是在操作数栈计算完之后才执行的

二、JDK/JRE/JVM的区别

  • JDK:Java标准开发包,提供编译、运行Java程序所需的各种工具和资源,包括Java编译器、Java运行时环境,以及常用的Java类库等
  • JRE,Java运行环境,用于运行Java的字节码文件,JRE中包括了JVM工作所需要的类库,普通用户只需要按照JRE来运行Java程序,而程序开发者必须安装JDK来编译、调试程序
  • JVM:Java虚拟机,是JRE的一部分,它是整个Java实现跨平台的最核心的部分,负责运行字节码文件

三、Java数据类型

基础数据类型

  • 整数类型(byte、short、int、long)
  • 浮点类型(float、double)
  • 数值型
  • 字符型(char)
  • 布尔型(boolean)

引用数据类型

  • 类(class)
  • 接口(interface)
  • 数组([])

四、hashCode()与equals()之间的关系

在了解hashCode()之前,首先要了解哈希表这种数据结构,这里假设存在一个哈希函数f(x),那么它的映射关系是数据->存储地址,即对于一个数据x,它存放的地址为经过函数f(x)得到的地址。

但由于计算机的存储空间不是无限的,所以哈希地址的计算一般会出现重复的情况,这里可以采用链地址法,即如果两条数据计算到的哈希值相同,那么就在这个位置上创建一个链表来存放这两个相同哈希值的数据。

有了哈希表的概念,那么这里Java对象中的hashCode就是经过某一哈希函数得到的哈希值,在应用时参考以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class Key {
Integer id;

public Key(Integer id){
this.id = id;
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

// @Override
// public int hashCode() {
// return id.hashCode();
// }

// @Override
// public boolean equals(Object obj) {
// if(obj == null || !(obj instanceof Key))
// return false;
// else
// return this.getId().equals(((Key) obj).getId());
// }

public static void main(String[] args) {
Key k1 = new Key(1);
Key k2 = new Key(1);
HashMap<Key,String> hm = new HashMap<Key, String>();

hm.put(k1,"Key with id is 1");
System.out.println(hm.get(k2));
}
}

这段代码测试了使用相同id值的对象在HashMap中进行查找(工作会经常使用到的操作),测试结果如下:

  • 不重写hashCode()和equals()方法,结果输出”null“
  • 重写hashCode()但不重写equals(),结果仍输出“null”
  • 同时重写hashCode()和equals()方法,输出结果”Key with id is 1“,内容可以正常查询到

对以上三种测试情况有以下说明:

  • 集合类HashMap在进行对象查找(get()方法)时,会先根据对象的hashCode进行查找,
    • 如果两个对象的hashCode不同,则说明两个对象绝对是不同的;
    • 如果两个对象的hashCode相同,则调用对象的equals()方法继续进行比较,比较内容相同则两个对象一定是相同的
  • 如果不重写hashCode()和equals()方法,那么对象会采用Object父类实现的默认的hashCode()和equals()方法,默认的hashCode是对象的内存地址,默认equals()的比较方式也是比较两个对象的内存地址是否相同

在使用时一般会遵循以下原则:

  • 重写equals()方法必须重写hashCode()方法
  • 两对象equals()相等,则这两个对象的hashCode()应该相等
  • hashCode()和equals()的返回值应该是稳定的,不应具有随机性
  • 如果要在HashMap的”键“部分存放自定义的对象,一定要在这个对象里重写equals和hashCode方法

在面试时会经常问到比如:

  • 有没有重写过hashCode方法
  • 在使用HashMap时有没有重写hashCode和equals方法,怎么写的

五、==与equals的区别

参考第三题,equals()方法在不重写的情况下,默认比较的是两个对象的内存地址,但我们往往会在类中重写equals()来比较对象之间的内容是否相等equals()只能被对象调用

==是Java中的一种操作符,它有两种比较方式

  • 对于基础数据类型来说,==判断的是两边的值是否相等
  • 对于引用类型来说,==判断的是两边的引用是否相等,也就是判断两个对象是否指向了同一块内存区域

equals()方法的特性

  • 自反性:对于任何非空引用值x来说,x.equals(x)返回true
  • 对称性:对于任何非空引用值x、y来说,如果x.equals(y)为true,则y.equals(x)为true
  • 传递性:对于任何非空引用值x、y、z来说,如果x.equals(y)为true,y.equals(z)为true,那么x.equals(z)也为true
  • 一致性:对于任何非空引用值x、y来说,如果x.equals(y)为true,那么它们要始终相等
  • 非空性:对于任何非空引用值x来说,x.equals(null)必须返回false

六、String中的equals是如何重写的

String是Java中的字符串类,它整个类都是被final修饰的,这意味着String类是不能被任何类继承,任何修改String字符串的方法都是创建了一个新的字符串

equals方法是Object类定义的方法,Object是所有类的父类,当然也包括String,String重写的equals方法如下

  • 首先是引用的判断,引用相等则直接返回true
  • 接着判断对象是否是String实例,不是则直接返回false,否则将对象强转后,比较字符串的长度,长度不等肯定返回false
  • 长度相等的情况下,再逐个字符进行比较,存在不同的字符则返回false

注意JDK1.8以后,new String(“abc”)操作和普通的字符串赋值操作作用是一样的,以下的intern()方法是获取常量池中的字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Test {
public static void main(String[] args) {
String s1 = "abc";

String s2 = "a" + new String("bc");
String s3 = new String("abc");

System.out.println(s1.intern()); //abc

// 以下输出均为true
System.out.println(s2.equals("abc"));
System.out.println(s3.equals(s1));
System.out.println(s1.intern().equals(s2));
System.out.println(s3.intern().equals(s1));
}
}

七、String s1 = new String(“abc”)在内存中创建了几个对象

一个或者两个,首先new操作一定会在堆中创建一个对象,如果常量池中不存在“abc”对象,那么就会在常量池中创建一个“abc”,否则就不进行创建

观察下面String构造器的源码可以发现,String对象的hash值和常量池“abc”对象的hash值是一致的

new操作的内存情况如下:

八、String为什么是不可变的、jdk源码中的String如何定义的、为什么这么设计

不可变对象:不可变对象就是一经创建后,其对象的内部状态不能被修改,即:

  • 不可变对象内部属性都是final的
  • 不可变对象的内部属性都是private的
  • 不可变对象不能提供任何可以修改内部状态的方法,setter方法也不行
  • 不可变对象不能被继承和扩展

String类是一种对象,它独立于Java基本数据类型而存在的,可以理解为字符串的集合

String被设计为final的,表示String对象一经创建后,它的值就不能再被修改了,任何对String值进行修改的方法就是重新创建一个字符串

String对象创建后会存在于运行时常量池中,运行时常量池属于方法区的一部分,JDK1.7后把它移到了堆中

九、String/StringBuffer/StringBuilder的区别

String类型是不可变的,属于字符串常量

StringBuffer和StringBuilder都是可修改的字符串对象,区别是StringBuffer是线程安全的,StringBuilder是线程不安全的

十、static关键字是干什么用的

  • 修饰变量,static修饰的变量称为静态变量、也称为类变量,类变量属于类所有,对于不同的类来说,static变量只有一份,static修饰的变量位于方法区;static修饰的变量能够直接通过类名.变量名来进行访问,不用通过实例化类再进行使用
  • 修饰方法,static修饰的方法称为静态方法,静态方法能够直接通过类名.方法名来使用,在静态方法内部不能使用非静态属性和方法
  • static可以修饰代码块,主要分为两种,一种直接定义在类中,使用static{},这种被称为静态代码块,一种是在类中定义静态内部类,使用static class xxx来进行定义

  • static可用用于静态导包,通过使用import static xxx来实现,这种方式一般不推荐使用

  • static可以和单例模式一起使用,通过双重检查锁来实现线程安全的单例模式

十一、final关键字是干什么用的

final 是 Java 中的关键字,它表示的意思是 不可变的,在 Java 中,final 主要用来

  • 修饰类,final 修饰的类不能被继承,不能被继承的意思就是不能使用 extends 来继承被 final 修饰的类。
  • 修饰变量,final 修饰的变量不能被改写,不能被改写的意思有两种,对于基本数据类型来说,final 修饰的变量,其值不能被改变,final 修饰的对象对象的引用不能被改变,但是对象内部的属性可以被修改。final 修饰的变量在某种程度上起到了不可变的效果,所以,可以用来保护只读数据,尤其是在并发编程中,因为明确的不能再为 final 变量进行赋值,有利于减少额外的同步开销
  • 修饰方法,final 修饰的方法不能被重写
  • final 修饰符和 Java 程序性能优化没有必然联系

十二、抽象类和接口的区别是什么

抽象类abstract和接口interface都是Java中的关键字,

抽象类和接口的相同点:

  • 都允许进行方法的定义,而不用具体的方法实现
  • 都允许被继承
  • 广泛的应用于 JDK 和框架的源码中,来实现多态和不同的设计模式。

抽象类和接口的不同点:

  • 抽象级别不同:类、抽象类、接口其实是三种不同的抽象级别,抽象程度依次是 接口 > 抽象类 > 类。在接口中,只允许进行方法的定义,不允许有方法的实现,抽象类中可以进行方法的定义和实现;而类中只允许进行方法的实现
  • 使用的关键字不同:类使用 class 来表示;抽象类使用 abstract class 来表示;接口使用 interface 来表示
  • 变量:接口中定义的变量只能是公共的静态常量,抽象类中的变量是普通变量。

十三、重写和重载的区别

  • 重写是针对子类和父类的表现形式,而重载是在同一类中的不同表现形式
  • 子类重写父类的方法一般使用@override来表示,重写后的方法其方法的声明和参数类型、顺序必须要和父类完全一致
  • 重载是针对同一类中概念,它要求重载的方法必须满足下面任何一个要求:方法参数的顺序、参数的个数、参数的类型任意一个保持不同即可

十四、byte的取值范围

Java的Byte的取值范围为(-128,127),占用一个字节,即8bit

Java中使用补码来表示二进制数,因此最高位为符号位,最高位为0表示正数,最高为为1表示负数

先来复习下源码、反码、补码

原码:正常的二进制计算,存在符号位,0表示正数,最高为为1表示负数

反码:正数的反码与其原码相同;负数在原码基础上,按位取反,符号位除外

补码:正数的补码与其原码相同;负数的补码是在其反码的末位+1

byte中,正数的最大值就是0111 1111

因为存在符号位,所以存在两个0,即-0:1000 0000和+0:0000 0000,因此用1000 0000来表示负数的最小值,即-128

1000 0000减1后为0111 1111,再取反1000 0000,原码的值128,在负数中就应该为-128

十五、HashMap和HashTable的区别

不同点:

  • 父类不同:HashMap继承了AbstractMap类,而HashTable继承了Dictionary类
  • 空值不同:HashMap允许空的key和value值,HashTable不允许空的key和value值。HashMap会把Null key当做普通的key对待,且不允许null key重复。HashTable存null的key就会报空指针异常

  • 线程安全性:HashMap是线程不安全的,如果多个外部操作同时修改HashMap的数据结构比如add或者是delete,必须进行同步操作,即仅仅对key或者value的修改不是改变数据结构的操作

  • 性能方面:虽然HashMap和HashTable都是基于单链表的,但是HashMap进行put或者get操作,可以达到常数时间的性能;而HashTable的put和get操作都是加了synchronized,所以效率很差
  • 初始容量不同:HashTable的初始长度是11,之后每次扩充容量变为之前的2n+1(n为上一次的长度)而HashMap的初始长度为16,之后每次扩充变为原来的两倍。创建时,如果给定了容量初始值,那么HashTable会直接使用你给定的大小,而HashMap会将其扩充为2的幂次方大小