# JAVA入门
# JAVA基础
# 认识Linux系统
- Linux起源于1991年,在1995年开始流行,是一个开源的操作系统,是一个类unix的操作系统。
- 相对路径:文件或目录相对当前工作目录的位置。
- 绝对路径:文件或目录相对当前根目录的位置。
1
2
3
4
2
3
4
# Java编译运行过程
程序员编写的Java源文件(.java)首先要经过javac命令编译,生成所谓字节码文件(.class)。JVM为字节码文件提供运行环境。只要是标准的.class文件,就可以在不同的JVM上运行,而且运行的效果相同,这样就实现了所谓的“一次编译,到处运行”。
1
# JDK、JRE、JVM关系
- JDK:Java Development Kit(Java开发工具包)
- JRE:Java Runtime Enviroment(Java运行时环境)
- JVM:Java Virtual Machines(Java虚拟机)
- 运行一个Java程序所需要的最小环境为JRE,开发一个Java程序所需要的最小为JDK
1
2
3
4
2
3
4
# 变量的命名
可以由字母、数字、“_”和“$”组成。首字母不能用数字,大小写敏感,不能使用Java保留字,不提倡使用中文。
1
# Java 8种基本数据类型
| 类型名称 | 字节空间 | 使用场景 |
|---|---|---|
| byte | 1字节(8位) | 存储字节数据 |
| short | 2字节(16位) | 较少使用 |
| int | 4字节(32位) | 存储普通整数 |
| long | 8字节(64位) | 存储长整数 |
| float | 4字节(32位) | 存储浮点数 |
| double | 8字节(64位) | 存储双精度浮点数 |
| char | 2字节(16位) | 存储一个字符 |
| boolean | 1字节(8位) | 存储逻辑变量(true、false) |
int是最常用的整数类型,最大表示范围是 $$ -2^{31}-(2^{31}-1) $$
- 两个整数进行运算时,其结果可能会超过整数的范围而溢出,正数过大而产生的溢出,结果为负数,负数过大而产生的溢出,结果为正数。
- 如果int类型的范围不够,可以使用long型。如果需要表示long的直接量,需要以L或l结尾。必须有一个long型数据参与的运算结果才为long型。(JDK提供System.currentTimeMillis()方法,返回类型为long,常用于计时。)
1
2
2
long类型最大表示范围为 $$ -2^{63}-(2^{63}-1) $$
- 默认的浮点数直接量为double型,如果需要表示float类型的直接量,需要加“f”或“F”后缀。
- 二进制系统中无法精确的表示1/10,如果需要精确的运算,可以采用BigDecimal类来实现。
1
2
2
# 基本数据类型间的转换
- 自动类型转换(隐式类型转换):从小类型到大类型可以自动完成。
- 强制转换:从大类型到小类型需要强制转换符。这样转换有可能造成精度损失或溢出。
- 数值运算时的自动转换:多种基本类型参与的表达运算中,运算结果会自动的向较大的类型进行转换。
1
2
3
2
3
# byte、char、short转换为int
byte、char、short三种类型实际存储的数据都是整数,在实际使用中遵循如下规则:
- int直接量可以直接赋值给byte、char和short,只要不超过其表示范围。
- byte、char、short三种类型参与运算时,先一律转换为int类型,再进行计算。
1
2
3
2
3
# switch
switch中能否使用string做参数:
- 在idk 1.7之前,switch只能支持byte, short, char, int或者其对应的封装类以及Enum类型。从idk 1.7之后switch开始支持String。
switch能否作用在byte, long上:
- 可以用在byte上,但是不能用在long上。
1
2
3
4
5
2
3
4
5
# 数组
程序 = 算法 + 数据结构
- 所谓数据结构,简单来说就是就是把数据按照特定的某种结构来保存。数组就是最基本的一种数据结构。
数组:
- 相同数据类型的元素组成的集合。
- new关键字分配空间时需指定分配的空间大小。
- 基本类型的数组创建后,元素有默认初始值。
1
2
3
4
5
6
2
3
4
5
6
# 数组的复制
//System.arrayCopy()方法可以实现数组的复制。
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
- src:源数组
- srcPos:源数组的起始位置
- dest:目标数组
- destPos:目标数组中的起始位置
- length:要复制的数组元素的数量
//Arrays.copyOf()方法用于复制数组。
int[] newArray = Arrays.copyOf(int[] origina,int newLength);
public static <T> T[] copyOf(T[] original, int newLength) {
return (T[]) copyOf(original, newLength, original.getClass());
}
- 特点:生成的新数组是原始数组的副本。
- newLength小于源数组,则进行截取。
- newLength大于源数组,则用0或null进行填充。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 数组的扩容
数组的长度在创建后是不可改变的,所谓扩容是指创建一个更大的新数组并将原有数组的内容复制到其中。
- 可以使用Arrays.copyOf()方法简便实现数组的扩展。
1
2
2
# 数组的排序
//经典型冒泡排序
int[] arr = new int[]{1,2,6,8,4,0,3,5,7,9,14,12,17,19};
for (int i = 0; i < arr.length-1; i++) {
for (int j = 0; j < arr.length-1-i ; j++) {
if (arr[j] > arr[j+1]) {
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
//JDK提供的Arrays.sort()方法封装了数组的排序算法。
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 面向对象
# 面向过程
结构化程序的弊端:
- 缺乏对数据的封装。
- 数据和方法(对数据的操作)的分离。
1
2
3
2
3
# 抽象数据类型
将不同类型的数据的集合组成一个整体用来描述一种新的事物。
- char型的默认初始值\u0000
1
2
2
# 类
类定义了一种抽象数据类型。
类不但定义了抽象数据类型的组成(成员变量),同时还定义了可以对该类型实施的操作(方法)。
类的实例化顺序:
- 父类静态变量
- 父类静态代码块
- 子类静态变量
- 子类静态代码块
- 父类非静态变量(父类实例成员变量)
- 父类构造函数
- 子类非静态变量(子类实例成员变量)
- 子类构造函数
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 引用
除8种基本数据类型之外,用类、接口、数组等声明的变量都称为引用类型变量,简称“引用”。
1
# 方法重载
在Java语言中,允许多个方法名相同,但参数列表不同,称之为方法的重载(overload)
1
# 引用类型数组
数组是对象
- 在Java中,数组属于引用数据类型。
- 数组对象在堆中存储,数组变量属于引用类型,存储数组对象的地址信息,指向数组对象。
- 数组的元素可以看成数组对象的成员变量(类型全都相同)
1
2
3
4
2
3
4
# 成员变量的生命周期
- 当一个对象没有任何引用时,被视为废弃的对象,属于被回收的范围。该对象中的所有成员变量也随之被回收。
成员变量的生命周期为:
- 从对象在堆中创建开始到对象从堆中被回收结束。
1
2
3
4
2
3
4
# 垃圾回收机制
垃圾回收器(Garbage Collection,GC)是JVM自带的一个线程(自动运行着的程序),用于回收没有任何引用指向的对象。
内存泄露问题:
- 内存泄露:是指不再使用的内存没有被及时的回收。严重的内存泄漏会因过多的内存占用而导致程序的崩溃。
- GC线程判断对象是否可以回收的依据是该对象是否有引用指向,因此,当确定该对象不再使用时,应该及时将其引用设置为null。
System.gc()方法:
- GC的回收对于程序员来说是透明的,并不一定一发现有无引用的对象,就立即回收。
- 一般情况下。当我们需要GC线程即刻回收无用对象时,可以调用System.gc()方法。
- System.gc()用于建议虚拟机马上调度GC线程回收资源,具体的实现策略取决于不同的JVM系统。
GC(垃圾收集):
- 内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃。
- Java提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java语言没有提供释放已分配内存的显示操作方法。
- Java程序员不用担心内存管理,因为垃圾收集器会自动进行管理。
- 要请求垃圾收集,可以调用下面的方法之一:System.gc() 或Runtime.getRuntime().gc() ,但JVM可以屏蔽掉显示的垃圾回收调用。
- 垃圾回收可以有效的防止内存泄露,有效的使用可以使用的内存。
- 垃圾回收器通常是作为一个单独的低优先级的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 堆、栈、方法区
JVM内存分为“堆”、“栈”和“方法区”三个区域。
- 对象存储在堆中
- JVM在其内存空间开辟一个称为“堆”的存储空间。
- 这部分空间用于存储使用new关键字所创建的对象。
- 类在实例化对象时,多个对象会拥有各自在堆中的空间,但是所有实例化对象是共用在方法区的一份方法定义的。
栈:
- 栈用于存放方法中的局部变量。
- 基础数据类型 byte short int long float double char boolean
- 方法的形式参数,方法调用完后从栈空间回收
- 引用对象的地址,引用完后,栈空间地址立即被回收,堆空间等待GC
- 栈内的数据线程之间独立
- 具体细分为:
- 基本类型变量区
- 执行环境上下文
- 操作指令区
堆:
- this
- new出来的对象
- 数组
- JVM只有一个堆区,并被所有线程共享。
方法区域(又叫静态区) :
- 方法区用来存放类的信息,Java程序运行时,首先会通过类装载器载入类文件字节码信息,经过解析后将其装入方法区。
- 字符串常量
- static
- 所有的class
- 被所有线程共享, 其内存放程序中永远唯一的元素,eg: static 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
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
# 局部变量的生命周期
- 一个运行的Java程序从开始到结束会有多次方法的调用。JVM会为每一个方法的调用在栈中分配一个对应的空间,这个空间成为该方法的栈帧。
- 一个栈桢对应一个正在调用的方法,栈桢中存储了该方法的参数、局部变量等数据。当某一个方法调用完成后。其对应的栈桢将被清除,局部变量失效。
1
2
2
# 成员变量和局部变量的差别
局部变量:
- 定义在方法中。
- 没有默认值,必须自行设定初始值。
- 方法被调用时,存在栈中,方法调用结束,从栈中清除。
成员变量:
- 定义在类中,方法外。
- 有默认初始值,可以不显式初始化。
- 所有类被实例化后,存在堆中,对象被回收时,成员变量失效。
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 抽象
- 抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。
- 抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。
1
2
2
# 继承
extends关键字
- 子类可以继承父类的成员变量和成员方法,也可以定义自己的成员变量和成员方法。
- Java语言不支持多重继承,一个类只能继承一个父类,但一个父类可以有多个子类。
继承构造方法
- 子类的构造方法中必须通过super关键字调用父类的构造方法,这样可以妥善的初始化继承自父类的成员变量。
- 如果子类子类的构造方法没有调用父类的构造方法,Java编译器会自动的加入对父类无参构造器方法的调用。
父类的引用可以指向子类的对象,但通过父类的引用只能访问父类所定义的成员,不能访问子类扩展的部分。
- Java编译器会根据引用的类型而不是对象的类型来检查调用的方法是否匹配。
定义:
- 继承是从已有类得到继承信息创建新类的过程。
- 提供继承信息的类被称为父类(超类、基类),得到继承信息的类被称为子类(派生类)。
- 继承让变化中的软件系统有了一定的延续性,同时继承也是封装程序中可变因素的重要手段。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 方法重写
- 子类可以重写(覆盖)继承自父类的方法。即方法名和参数列表与父类的方法相同,但方法的实现不同。
- 当子类对象的重写方法被调用时(无论是通过子类的引用调用还是父类的引用调用),运行的都是子类的重写后的版本。
- 子类在重写父类的方法时,可以通过super关键字调用父类的版本。
1
2
3
2
3
# 重写和重载的区别
重载:
- 重载是指在一个类中定义多个方法名相同但参数列表不同的方法,遵循“编译期绑定”,在编译时,根据参数的个数和类型来确定绑定那个方法。
- 重写是指在子类中定义和父类完全相同的方法,遵循“运行期绑定”,在程序运行时,根据对象的类型不同(而不是引用类型)而调用不同的版本。
注意:
- 子类覆盖父类的方法时,只能比父类抛出更少的异常,或者是抛出父类抛出异常的子异常。
- 子类方法的访问权限只能比父类方法的访问权限更大。
- 不能通过访问权限、返回类型、抛出的异常进行重载
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 封装
- 通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。类就是对数据和数据操作的封装。
- 可以说,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口。
- 降低代码出错的可能性,便于维护。
- 封装的目标就是实现软件内部的“高内聚、低耦合”,防止程序相互依赖性而带来的变动影响。
1
2
3
4
2
3
4
# 访问权限
| 作用域 | 当前类 | 同一package | 子孙类 | 其他package |
|---|---|---|---|---|
| public | √ | √ | √ | √ |
| protected | √ | √ | √ | × |
| friendly | √ | √ | × | × |
| private | √ | × | × | × |
# static关键字
static修饰成员变量
- 用static修饰的成员变量不属于对象的数据结构。
- static变量是属于类的变量,通常可以通过类名来引用static成员。
- static成员变量和类的信息一起存储在方法区,而不是在堆中,一个类的static成员变量只有一份,无论该类创建了多少对象。
static修饰方法
- 由于static在调用时没有具体的对象。因此在static方法中不能对非static成员(对象成员)进行访问。static方法的作用在于提供一些“工具方法”和“工厂方法”等。
static块
- 属于类的代码块,在类加载期间执行的代码块,只执行一次,可以用来在软件中加载静态资源。
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# final关键字
final修饰变量
- final修饰成员变量,意为不可改变。可以声明同时初始化,也可以在构造函数中初始化。
- final也可以修饰局部变量,使用之前初始化即可。
final修饰方法
- final修饰的方法不可以被重写。
- 使一个方法不能被重写的意义在于:防止子类在定义新方法时造成的“不经意重写”。
final修饰类
- final修饰的类不可以被继承。
- JDK中的一些基础类库被定义为final的,例如:String、Math、Integer、Double等等。
- 使一个类不能被继承的意义在于:可以保护类不被继承修改,可以控制滥用继承对系统造成的危害。
- 使用final修饰一个变量时,是引用变量不能变,引用变量所指向的对象中的内容还是可以改变的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# static final常量
- static final修饰的成员变量称为常量,必须声明同时初始化,不可被改变。
- static final常量会在编译期被替换。
1
2
2
# 抽象方法和抽象类
- 由abstract修饰的方法为抽象方法,抽象方法只有方法的定义,没有方法体实现,用一个分号结尾。
- 一个类中如果包含抽象方法,该类应该用abstract关键字声明为抽象类。
- 如果一个类继承了抽象类,必须重写其抽象方法,除非这个类也声明为抽象类。
- 抽象类不可以实例化
- 如果一个类没有抽象方法,也可以将其定义为抽象类,同样,该类不可以实例化。
- abstract和final不可以同时用于修饰一个类
抽象类的意义:
- 为其子类提供一个公共的类型。
- 封装子类中的重复内容(成员变量和方法)。
- 定义有抽象方法,子类虽然有不同的实现,但该方法的定义是一致的。
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 接口
接口可以看成是特殊的抽象类,即只包含有抽象方法的抽象类。
- 通过interface关键字定义接口
- 接口中不可以定义成员变量,但可以定义常量。
- 接口中只可以定义没有实现的方法(可以省略 public abstract)。
实现接口:
- 与继承不同,一个类可以实现多个接口,实现的接口直接用逗号分隔。当然,该类需要实现这些接口中定义的所有方法。
接口的继承:
- 接口间可以存在继承关系,一个接口可以通过extends关键字继承另外一个接口。子接口继承了父接口中定义的所有方法。
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 接口与抽象类的区别
抽象类和普通类的唯一区别就是不能创建实例对象和允许有abstract方法。
- 抽象类和接口都不能够实例化,但可以定义抽象类和接口类型的引用。
- 接口比抽象类更加抽象,因为抽象类中可以定义构造器,可以有抽象方法和具体方法,而接口中不能定义构造器,而且其中的方法全部都是抽象方法。
- 抽象类中的成员可以是任意的,而接口中的成员全都是public的。
- 抽象类中可以定义成员变量,而接口中定义的成员变量实际上都是常量。
- 有抽象方法的类必须被声明为抽象类,而抽象类未必要有抽象方法。
1
2
3
4
5
6
2
3
4
5
6
# 多态
定义:
- 多态性是指一个类型的引用在指向不同的对象时会有不同的实现,同样一个对象,造型成不同的类型时,会有不同的功能。
- 多态性分为编译时的多态性和运行时的多态性。
- 方法重载(overload)实现的是编译时的多态性(也称为前绑定),而方法重写(override)实现的是运行时的多态性(也称为后绑 定)。
- 运行时的多态是面向对象最精髓的东西,要实现多态需要做两件事:1.方法重写 2.对象造型。
一个类的对象可以向上造型的类型有:
- 父类的类型
- 其实现的接口类型
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 强制转换
- 可以通过强制转换将父类型变量转换为子类型变量,前提是该变量指向的对象确实是该子类类型。
- 也可以通过强制转换将变量转换为某种接口类型,前提是该变量指向的对象确实实现了该接口。
- 如果在强制转换过程中出现违背上述两个前提,将会抛出ClassCastException。
在强制转换中,为了避免出现ClassCastException,可以通过instanceof关键字判断某个引用是否为指定类型。
1
2
3
4
5
2
3
4
5
# 内部类
- 一个类可以定义在另一个类的内部,定义在类内部的类称之为Inner,其所在的类称之为Outer。
- Ineer定义在Outer的内部,通常只服务于Outer,对外不具备可见性。Inner可以直接调用Outer的成员及方法(包括私有的)。
1
2
2
# JAVA SE
# String及常用API
- String使用了final修饰,不能被继承。
- 字符串一旦被创建,对象永远无法改变,但字符串引用可以重新赋值。
- 底层为字符数组,采用Unicode编码方式,一个字符对应两个字节的定长编码。
String常量池
- 对于重复出现的字符串常量,JVM会首先在常量池中查找,如果存在即返回该对象。
StringBuffer和StringBuilder
- StringBuffer是线程安全的,同步处理,性能稍慢。
- StringBulider是非线程安全的,并发处理的,性能稍快。
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
/**
* String s1="java"这种形式,
* 其实是放在字符串池里面的,
* 那么当s2再创建同名的String类型时,
* 其实s2是指向常量池的,
* 那么s1==s2 指向了同一个对象,结果是true。
* 还有一点需要补充的是new出来的对象,
* 都是放在堆里面的,对于字符串new出来的这种方式,
* 其实首先也是向字符串池里面放入,
* 如果字符串池里面有了那么就不在放了,
* 同时,也在堆里面创建了一个对象,
* 也就是说new出来这种形式,如果字符串池里没有就创建了2个对象。
*/
//测试1
String s1 = "java";
String s2 = "java";
String s3 = new String("java");//创建了一个对象
String s4 = new String("java");//创建了一个对象
System.out.println(s1 == s2); true
System.out.println(s1 == s3); false
System.out.println(s3 == s4); false
System.out.println(s3.equals(s4)); true
//测试2
String s3 = new String("java");//创建了两个对象
String s4 = new String("java");//创建了一个对象
String s1 = "java";
String s2 = "java";
System.out.println(s1 == s2); true
System.out.println(s1 == s3); false
System.out.println(s3 == s4); false
System.out.println(s3.equals(s4)); 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
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
# equals和“==”的区别
- “==”用于比较变量的值,可以应用于任何类型,如果用于引用类型,比较的是两个引用变量中存储的值(地址信息),判断两个变量是否指向同一个对象。
- equals是Object的方法,默认比较规则同“==”,重写后,可以用于比较两个对象的内容是否相等。
对象比较为什么要重写hashcode 和 equals:
- 默认equals()方法只比较两个对象是否相同,相当于“==”。
- 由于默认的hashcode方法是根据对象的内存地址经哈希算法得来的,故对象内容相等但hashcode不一定相等。
这就出现了equals方法相等,但是hashcode不相等的情况。这不符合hashcode的规则,不利于哈希表的性能。
1
2
3
4
5
6
7
2
3
4
5
6
7
# 集合框架
Collection
- Collection是一个接口,定义了集合相关的操作方法,其中有两个子接口:List与Set
- List(可重复集) Set(不可重复集)
集合持有对象的引用
- 集合中存储的都是引用类型元素,并且集合只保存每个元素对象的引用,而非将元素对象本身存入集合。
ArrayList和LinkedList:
- List接口是Collection的子接口,用于定义线性表数据结构。两个常见实现类为ArrayList和LinkedList,分别用动态数组和链表的方式实现了List接口。
- ArrayList更适合于随机访问,而LinkedList更适合于插入和删除。(ArrayList可以通过下标迅速的索引到对应的元素,但在删除和插入时移动较多元素。LinkedList在删除或插入时只需要改变链接“指针”即可实现。)
- Collections.reverse(controlCarList):集合倒序API
- ArrayList:底层数据结构是数组,线程不安全
- LinkedList:底层数据结构是链表,线程不安全
- Vector:底层数据结构是数组,线程安全
- ArrayList使用for循环遍历优于迭代器,遍历LinkedList使用迭代器遍历优于for循环遍历。
- 链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点组成,每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。相比于线性表顺序结构,操作复杂。
- 双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向循环链表。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# ArrayList
ArrayList初始化:
- ArrayList的底层是一个动态数组,ArrayList首先会对传进来的初始化参数initalCapacity进行判断
- 如果参数等于0,则将数组初始化为一个空数组。
- 如果不等于0,将数组初始化为一个容量为10的数组。
- 扩容时机
- 当数组的大小大于初始容量的时候(比如初始为10,当添加第11个元素的时候),就会进行扩容,新的容量为旧的容量的1.5倍。
- 扩容方式
- 扩容的时候,会以新的容量建一个原数组的拷贝,修改原数组,指向这个新数组,原数组被抛弃,会被GC回收。
ArrayList实现的三个接口:
- 实现了RandomAccess接口,这个标记接口就是标记能够随机访问元素的集合,因为 ArrayList 是基于数组实现的。
- 实现了Serializable接口,该接口主要是在序列化的时候起作用。
- 实现了Cloneable接口,它允许在堆中克隆出一块和原对象一样的对象,并将这个对象的地址赋予新的引用,这样显然对新引用的操作,不会影响到原对象。
- 删除数据并不会更改数组的长度,只会将数据从数组中移除,如果目标没有其他有效引用,则在GC时会进行回收。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 泛型
- 泛型允许我们为集合提供一个可以容纳的对象类型,因此,如果你添加其它类型的任何元素,它会在编译时报错。
- 这避免了在运行时出现ClassCastException,因为你将会在编译时得到报错信息。
- 泛型也使得代码整洁,我们不需要使用显式转换和instanceOf操作符。
- 它也给运行时带来好处,因为不会产生类型检查的字节码指令。
1
2
3
4
2
3
4
# 迭代器
迭代器本质是一种设计模式,为了解决为不同的集合类提供统一的遍历操作接口。
- Iterator接口提供了很多对集合元素进行迭代的方法。
- 每一个集合类都包含了可以返回迭代器实例的迭代方法。
- 迭代器可以在迭代的过程中删除底层集合的元素,但是不可以直接调用集合的remove(Object Obj)删除,可以通过迭代器的remove()方法删除。
- 顺序访问
- 增强for循环即使用迭代器的方式进行遍历。
- 多线程下不可对集合长度进行修改,否则抛出并发修改异常。
- 解决方式:使用CopyOnWriteArrayList(适用于读写混合)
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 队列和栈
队列(Queue)
- 队列是常用的数据结构,可以将队列看成特殊的线性表,队列限制了对线性表的访问方式:只能从线性表的一端添加元素,从另一端取出元素。
- 队列遵循先进先出的原则。LinkedList实现了JDK的Queue接口。
双端队列(Deque)
- Deque是Queue的子接口,定义了所谓的“双端队列”:从队列两边分别可以“入队”和“出队”。
- 如果将Deque限制为只能从一端“入队”和“出队”,则可以实现栈(Stack)的数据结构,栈遵循先进后出的原则。
队列和栈的区别:
- 队列(Queue):是限定只能在表的一端进行插入和在另一端进行删除操作的线性表
- 栈(Stack):是限定只能在表的一端进行插入和删除操作的线性表
- 队列先进先出(FIFO),栈先进后出(FILO)
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# Map接口
Map接口定义的集合又称查找表,用于存储所谓“key-value”键值对。key可以看成是value的索引,作为key的对象,在集合中不可重复。
Map接口有多种实现类,常用的有内部为hash表实现的HashMap和内部为排序二叉树实现的TreeMap。
HashMap:
- 获取Key的hashCode值,通过hash算法确定将要存储的空间。
- 调用equals方法依次和hash桶(bucket)的值进行比较。
- 以链表的形式存入对应的hash桶中。(扩展)
装载因子及HashMap优化:
- Capacity:容量,hash表里bucket(桶)的数量,也就是散列数组大小。
- 默认构建容量16,默认加载因子0.75,发生扩容会重新散列(rehash)。
- 创建散列表时应指定合理容量,减少rehash提高性能。
Map的遍历:
- keySet:该方法会将当前Map中所有的key存入一个Set集合后返回。
- entrySet:该方法会将当前Map中每一组key-value对封装为一个entry对象并存入一个Set集合后返回。
HashMap和Hashtable的区别:
- HashMap是非线程安全的,HashTable是线程安全的。
- HashMap的键和值都允许有null值存在,而HashTable则不行。
- 因为线程安全的问题,HashMap效率比HashTable的要高。
- Hashtable是同步的,而HashMap不是。因此,HashMap更适合于单线程环境,而Hashtable适合于多线程环境。
- 一般现在不建议用HashTable。
- HashTable是遗留类,内部实现很多没优化和冗余。
- 即使在多线程环境下,现在也有同步的ConcurrentHashMap替代,没有必要因为是多线程而用HashTable。
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
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
# IO流
- 流按照流向数据流向可以分为输入流和输出流。
- 流按照处理数据类型的单位不同可以分为字节流和字符流。
- 流按照功能不同可以分为节点流和处理流。
输入流和输出流:
- 输入是一个从外界进入到程序的方向,输入是用来读取数据的。
- 输出是一个从程序发送到外界的方向,输出是用来写出数据的。
字节流和字符流:
- 字节流:InputStream和OutputStream是java中可以按照最小字节单位读取的流,即每次读写一个字节,字节流是直接连接到输入源的流。
- 字符流:是以字符为单位进行数据处理的IO流。本质其实就是基于字节流读取时,去查找指定的码表。
节点流与处理流:
- 节点流:低级流,可以从一个特定的节点读写数据。
- 处理流:高级流,是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。
InputStream和OutputStream:
- InputStream是所有字节输入流的父类,常用的方法read()。
- OutputStream是所有字节输出流的父类,常用的方法write()。
字符流原理:
- Reader是字符输入流的父类。
- Writer是字符输出流的父类。
- 字符流是是以字符(char)为单位读写数据的,一次处理一个Unicode。
- 字符流的底层依然是基本的字节流。
对象序列化概念:
- 对象是存在于内存中的,我们需要将对象转换为一个字节序列,这个过程称为对象序列化,相反,如果我们有这样一个字节序列需要转换为对象,这个过程就称为对象的反序列化。
- ObjectOutputStream在对对象进行序列化时,需要序列化的对象所属的类必须实现Serializable接口。
- 通常实现该接口的类需要提供一个常量serialVersionUID表明该类的版本。
transient关键字:
- 被该关键字修饰的属性在序列化时其值将被忽略。
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
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
# 异常处理
异常的捕获和处理:
- Java异常结构中定义有Throwable类,Exception和Error是其派生的两个子类。
- Exception表示由于网络故障、文件损坏、用户非法输入等情况导致的异常。
- Error表示Java运行时环境出现的错误,例如:JVM内存资源耗尽等。
- catch捕获的异常类型由上至下的顺序应该是子类到父类的。
throw和throws:
- 可以使用指定的“throw”关键词,并生成指定的异常对象后抛出。
- 可以使用指定的“throws”关键词来声明这个方法将会抛出异常。
可检测异常与非检测异常:
- 可检测异常:可检测异常经编译器验证,对于声明抛出异常的任何方法,编译器将强制进行处理或声明规则,不捕捉这个异常,编译器就不通过,不允许编译。
- 非检测异常:非检测异常不遵循处理或声明规则,编译器不会检查是否已经解决了这样一个异常。
- RuntimeException类属于非检测型异常。
RuntimeException常见类型有:
NumberFormatException(数据转换异常)
ClassCastException(强制转换时出现的异常)
NullPointerException(空指针异常)
ArrayIndexOutOfBoundsException(数组越界异常)
IllegalArgumentException(非法参数)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Error和Exception的区别
- Error表示系统级的错误和程序不必处理的异常,是恢复不是不可能但很困难的情况下的一种严重问题,比如内存溢出,不可能指望程序能处理这样的情况;
- Exception表示需要捕捉或者需要程序进行处理的异常,是一种设计或实现问题,也就是说,它表示如果程序运行正常,从不会发生的情况。
- Error表示系统致命的错误,程序没法处理。
- 一般是与JVM相关的问题,如系统崩溃,内存溢出,方法调用栈溢出等。
- 这种类型的错误,编译器不做检查,都是系统运行过程中发生的。
- Exception异常是程序能够捕获的,也可以做异常处理,
- 我们要尽可能的去处理,使程序继续运行,而不是中止程序。
总结一下就是Error是程序无法处理的错误,Exception是可以处理的异常。
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 多线程
进程与线程:
- 进程是操作系统中运行的一个任务(一个应用程序运行在一个进程中)。
- 进程中所包含的一个或多个执行单元被称为线程,线程只能归属于一个进程,并且它只能访问该进程所拥有的资源。
- 当操作系统创建一个进程后,该进程会自动申请一个名为主线程或首要线程的线程。
- 进程是系统分配资源的最小单位,使用独立的内存空间。
- 线程是程序执行的最小单位,共享进程的内存空间。
创建线程的两种方式:
- 可以通过继承Thread类并重写其run()方法,来定义一个具体的线程。重写run()方法的目的是定义该线程要执行的逻辑。启动线程时调用线程的start()方法而非直接调用run()方法。start()方法会将当前线程纳入线程调度,使当前线程可以开始并发运行。当线程获取时间片段后会自动开始执行run()方法的逻辑。
- 实现Runable接口并重写run()方法来定义线程体,然后在创建线程的时候将Runable的实例传入并启动线程。这种方法可以更好的去实现其他父类或接口,因为类是单继承,接口是多继承。
线程的生命周期大体可分为5种状态:
- 新建(NEW):新创建了一个线程对象。
- 可运行(RUNNABLE):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu的使用权 。
- 运行(RUNNING):可运行状态(runnable)的线程获得了cpu时间片(timeslice),执行程序代码。
- 阻塞(BLOCKED):阻塞状态是指线程因为某种原因放弃了cpu使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得cpu timeslice 转到运行(running)状态。
- 死亡(DEAD):线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。
守护线程:
- thread.setDaemon(true);
- 特点是:当线程中只剩下守护线程时,所有守护线程强制终止。
- GC就是运行在一个守护线程上的。
wait和notify:
- 如果条件不满足,则等待(wait),当条件满足时,等待该条件的线程将被唤醒(notify)。
sleep()方法和wait()方法:
- sleep()方法是线程类(Thread)的静态方法,调用此方法会让当前线程暂停执行指定的时间,将执行机会(CPU)让给其他线程,但是对象的锁依然保持,因此休眠时间结束后会自动恢复(线程回到就绪状态)。
- wait()是Object类的方法,调用对象的wait()方法导致当前线程放弃对象的锁,进入对象的等待池(wait pool),只有调用对象的notify()方法(或notifyAll()方法)时才能唤醒等待池中的线程进入等锁池(lock pool),如果线程重新获得对象的锁就可以进入就绪状态。
synchronized关键字:
- 若想解决线程安全问题,需要将线程异步的操作变成同步操作。
synchronized关键字的使用方式主要有以下三种:
- 修饰非静态方法:修饰非静态方法的方式保证同一个对象(注意与静态方法进行区分)的这个方法内部同一时刻仅有一个线程可以进入。
- 修饰静态方法:修饰静态方法的方式保证的是同一个类的这个方法内部同一时刻仅有一个线程可以进入。
- 修饰语句块:修饰语句块的方式与修饰非静态方法的方式类似。
- 异步操作:多线程并发的操作,相当于各干各的。
- 同步操作:有先后顺序的操作,相当于你干完我再干。
Java中的锁机制:
- 每个Java对象可以用做一个实现同步的锁,线程进入同步代码块之前会自动获得锁,并且在退出同步代码块时自动释放锁,获得内置锁的唯一途径就是进入由这个锁保护的同步代码块或方法。
当一个线程进入一个对象的synchronized方法A之后,其它线程是否可进入此对象的synchronized方法B?
- 不能。其它线程只能访问该对象的非同步方法,同步方法则不能进入。
- 因为非静态方法上的synchronized修饰符要求执行方法时要获得对象的锁,如果已经进入A方法说明对象锁已经被取走,那么试图进入B方法的线程就只能在等锁池(注意不是等待池)中等待对象的锁。
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
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
# 线程池
线程池的概念:
- 首先创建一些线程,他们的集合称为线程池,当服务器接收到一个客户的请求后,就从线程池中取出一个空闲的线程为之服务,服务完后不关闭该线程,而是将该线程还回到线程池中。
线程池的两个主要作用:
- 控制线程数量
- 重用线程
线程池的实现策略:
- Executors.newCachedThreadPool();
- 可缓存线程池,先查看池中有没有以前建立的线程,如果有,就直接使用。如果没有,就建一个新的线程加入池中。
- Executors.newFixedThreadPool(int nThreads);
- 创建一个可重用固定线程集合的线程池,以共享的无界队列方式来运行这些线程。
- Executors.newScheduledThreadPool(int corePoolSize);
- 创建一个定长线程池,支持定时及周期性任务执行。
- Executors.newSingleThreadExecutor();
- 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序执行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Socket原理
Socket简介:
- Socket通常被称为“套接字”,用于描述IP地址和端口,是一个通信链的句柄。
- Socket是建立网络连接时使用的,在连接成功时,应用程序两端都会产生一个Socket实例。操作这个实例,完成所需的会话。
1
2
3
2
3
# 数据库基础
# 关系型数据库
是指采用了关系模型来组织数据的数据库,其以行和列的形式存储数据。
主流关系型数据库:
- Oracle、MySQL、DB2、SQL Server等。
关系型数据库和非关系型数据库区别:
关系型数据库(MySQL):
- 关系型数据库,是指采用了关系模型来组织数据的数据库。
- 关系型数据库的最大特点就是事务的一致性。
优点:
- 容易理解:二维表结构是非常贴近逻辑世界一个概念,关系模型相对网状、层次等其他模型来说更容易理解;
- 使用方便:通用的SQL语言使得操作关系型数据库非常方便;
- 易于维护:丰富的完整性大大减低了数据冗余和数据不一致的概率;
- 支持SQL,可用于复杂的查询。
缺点:
- 为了维护一致性所付出的巨大代价就是其读写性能比较差;
- 固定的表结构;
- 不支持高并发读写需求;
- 不支持海量数据的高效率读写;
非关系型数据库(HBase):
- 使用键值对存储数据;
- 分布式;
优点:
- 无需经过sql层的解析,读写性能很高
- 基于键值对,数据没有耦合性,容易扩展
- 存储数据的格式:nosql的存储格式是key,value形式
缺点:
- 不支持事务
- 不提供sql支持
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
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
# 结构化查询语言
SQL(Structured Query Language):结构化查询语言。
SQL可分为:
- 数据定义语言(DDL)
- 数据操纵语言(DML)
- 事务控制语言(TCL)
- 数据查询语言(DQL)
- 数据控制语言(DCL)
数据定义语言(DDL):
- 用于建立、修改、删除数据库对象。
- CREATE:创建表或其他对象的结构。
- ALTER:修改表或其他对象的结构。
- DROP:删除表或其他对象的结构。
- TRUNCATE:删除表数据,保留表结构。
数据操纵语言(DML):
- 用于改变数据表中的数据。
- INSERT:将数据插入到数据表中。
- UPDATE:更新数据表中已存在的数据。
- DELETE:删除数据表中的数据。
事务控制语言(TCL):
- 用来维护数据一致性的语句。
- COMMIT:提交,确认已经进行的数据改变。
- ROLLBACK:回滚,取消已经进行的数据改变。
- SAVEPOINT:保存点,使当前的事务可以回退到指定的保存点,便于取消部分改变。
数据查询语言(DQL):
- 用来查询所需要语句。
- SELECT语句。
数据控制语言(DCL):
- 用于执行权限的授予和收回操作。
- GRANT:授予,用于给用户或者角色授予权限。
- REVOKE:用于收回用户或者角色已有的权限。
- CREATE USER:创建用户。
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
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
# 数据类型
Oracle数据类型:
字符串类型:
- char:存放定长符,存不满补空格。最大取值2000字节。(浪费空间,节省时间)
- varchar2:存放变长字符,存多少占用多少。最大取值4000字节。(浪费时间,节省空间)
- 默认存储单位是字节,每个英文字符占用一个字节,GBK占2个字节,utf-8占2~4个字节。
- char可以不指定长度,默认为1,varchar2必须指定长度。
相关函数:
- CONCAT(char1,char2):返回两个字符串连接后的结果。
- UPPER(char):将字符串转换为大写形式。
- LOWER(char):将字符串转换为小写形式。
- INITCAP(char):将字符串中每个单词的首字符大写,其他字符小写,单词之间用空格分隔。
- ...
数值类型:
- number:表示整数。
- round:用于四舍五入。
- trunc:用于截取。
- mod:取余。
日期类型:
- date:在数据库中的存储默认为7个字节。
- timestamp:与date的区别是不仅可以保存时间,还能保存小数秒。精度为0.用7字节存储,精度大于0,用11字节存储。
相关函数:
- sysdate:其本质是oracle的内部函数,返回当前的系统时间,精确到秒。
- systimestamp:内部函数,返回当前系统日期和时间,精确到毫秒。
- to_date:将字符串按照定制格式转换为日期类型。
- to_char:将其他类型的数据转换为字符类型。
- ...
- nvl(expr1,expr2):将null值转变为非null值。
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
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
# 函数
聚合函数:
- max:用来取得列或表达式的最大值。
- min:用来取得列或表达式的最小值。
- avg:用来统计列或表达式的平均值。
- sum:用来统计列或表达式的总值。
- count:用来记录表中的记录条数。
分组:
- group by。
- having子句:必须跟在group by后面,不能单独存在。
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 关联查询
- 内连接:返回所有满足条件的记录。
SELECT e.ename,d.dname
FROM emp e JOIN dept d
ON(e.deptno=d.deptno);
- 外连接:外连接(全外连接)不仅返回满足连接条件的记录,还将返回不满足连接条件的记录。
SELECT table1.column1,table2.column2
FROM
table1 LEFT|RIGHT|FULL [OUTER] JOIN table2
ON table1.column1=table2.column2;
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 分页查询
Oracle分页查询:
- ROWNUM:被称作为伪列,用于返回标识行数据顺序的数字。
- where rn between ((n-1) * pagesize + 1) and (n * pagesize);
MySQL分页查询:
- limit分页公式:limit (n-1) * pageSize,pageSize
1
2
3
4
5
6
2
3
4
5
6
# 集合操作
UNION和UNION ALL:
- 用来获取两个或两个以上结果集的并集。
- UNION操作符会自动去掉合并后的重复记录。
- UNION ALL返回两个结果集中的所有行,包括重复的行。
- UNION对查询结果排序,UNION ALL对查询结果不排序。
INTERSECT:
- 获得两个结果集的交集。
- 结果集会以第一列的数据作升序排列。
MINUS:
- 获得两个结果集的差集。
- 结果集一减去结果集二的结果。
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 视图
- 视图也被称作虚表,是一组数据的逻辑表示。
- 视图本身并不包含任何数据,它只包含一个映射到基表的一个查询语句,当基表数据发生变化,视图数据也随之变化。
视图的作用:
- 如果需要经常执行某项复杂查询,可以基于这个复杂查询建立视图,以后查询此视图即可,简化复杂查询。
- 访问视图时,只能访问到所对应的SELECT语句中涉及到的列,对基表中的其他列起到安全和保密的作用,限制数据访问。
简单视图可以进行DML操作,会影响到基表数据,可以声明为只读保证安全。复杂视图不允许DML操作。
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 序列
- 序列(SEQUENCE)是一种用来生成唯一数字值的数据库对象。
- 序列是独立的数据库对象,并不依附于表。
- 一个序列可以为多个表提供主键值。
1
2
3
2
3
# 索引
- 索引是一种允许直接访问数据表中某一数据行的树型结构,为了提高查询效率而引用,是独立于表的对象,可以存放在与表不同的表空间中。
- 索引记录中存有索引关键字和指向表中数据的指针(地址)。
合理使用索引提高查询效率:
- 为经常出现在where子句中的列创建索引。
- 为经常出现在order by、distinct后面的字段建立索引。
- 为经常作为表的连接条件的列上创建索引。
- 不要在经常做DML操作的表上建立索引。
- 不要在小表上建立索引。
- 限制表上的索引数目,索引并不是越多越好。
- 删除很少被使用的,不合理的索引。
缺点:
- 它减慢了数据录入的速度,同时也增加了数据库的尺寸大小。
什么样的字段适合建索引:
- 唯一、不为空、经常被查询的字段。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 约束
- 约束是在数据表上强制执行的一些数据校验规则,当执行DNL操作时,数据必须符合这些规则,如果不符合则无法执行。
- 约束条件可以保证表中数据的完整性,保证数据间的商业逻辑。
约束的类型:
- 非空约束:(Not Null)
- 唯一性约束:(Unique)
- 主键约束:(Primary Key)
- 外键约束:(Foreign Key)
- 检查性约束:(Check)
外键约束:
- 外键约束条件定义在两个表的字段或一个表的两个字段上,用于保证相关两个字段的关系。
- 从表上定义的外键的列值,必须从主表被参照的列值中选取,或者为null值。
- 当主表参照列的值被从表参照时,主表的该行记录不允许被删除。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 数据库的优化
- sql尽量使用索引
- 子查询变成left join
- limit分布优化,先利用ID定位,再分页
- or条件优化,多个or条件可以用union all对结果进行合并(union all结果可能重复)
- where代替having,having检索完所有记录,才进行过滤
- 避免嵌套查询
- 对多个字段进行等值查询时,联合索引
总结:
- 合并数据+事务+有序数据的优化插入方式
- 注意SQL批量插入的大小必须合理
- 事务执行时间不要太长了
- 实际开发时需要合理设置MYSQL相应配置参数,增加缓存或减少不必要日志磁盘读写
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# varchar2和varchar
- varChar的长度是固定的,而varchar2的长度是可以变化的,按实际长度存储。
- varchar的效率要被varchar2的效率高。
- varchar是标准sql提供的数据类型。
- varchar2是oracle提供的独特的数据类型。
- 如果想和其他数据库兼容就不要用varchar2。
1
2
3
4
5
2
3
4
5
# 三范式
- 1NF 属性不可分
- 2NF 非主键属性,完全依赖于主键属性
- 3NF 非主键属性无传递依赖
1
2
3
2
3
# in 和 exists
- 如果查询的两个表大小相当,那么用in和exists差别不大。
- 如果两个表中一个较小,一个是大表,则子查询表大的用exists,子查询表小的用in:
- 如果查询语句使用了not in 那么内外表都进行全表扫描,没有用到索引;
- 而not extsts 的子查询依然能用到表上的索引。所以无论那个表大,用not exists都比not in要快。
- EXISTS只返回TRUE或FALSE,不会返回UNKNOWN。
- IN当遇到包含NULL的情况,那么就会返回UNKNOWN。
1
2
3
4
5
6
2
3
4
5
6
# JDBC
# JDBC概述
- Java Database Connectivity:Java访问数据库的解决方案。
- 希望用相同的方式访问不同的数据库,以实现与具体数据库无关的Java操作界面。
JDBC工作原理:
- 加载驱动,建立连接:DriverManger(驱动管理)、Connection(连接接口)
- 创建语句对象:Statement、PreparedStatement(语句对象接口)
- 执行SQL语句:Excute、executeQuery 、executeUpdate
- 处理结果集:ResultSet(结果集接口)
- 关闭连接:
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# JDBC流程
//1.导入驱动jar包
//2.注册驱动
Class.forName("oracle.jdbc.OracleDriver");
//3.获取数据库连接对象
Connection conn =DriverManager.getConnection(
"jdbc:oracle:thin:@localhost:1521:orcl",
"jmzhao",
"itek"
);
//4.定义sql语句
String sql = "update account set balance = 500 where id = 1 ";
//5.获取执行sql的对象
Statement stmt = conn.createStatement();
//6.执行sql
int count = stmt.executeUpdate(sql);
//7.处理结果
System.out.println(count);
//8.释放资源
stmt.close();
conn.close();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# JDBC配置
#oracle配置
driver-class-name: oracle.jdbc.OracleDriver
url: jdbc:oracle:thin:@localhost:1521:orcl
username: root
password: itek
#mysql配置
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/zjm
username: root
password: itek
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 数据库连接池
- 解决了数据库连接及关闭资源消耗巨大的问题。
为确保连接池中最小的连接数的策略:
- 动态检查:定时检查连接池,一旦发现数量小于最小连接数,则补充相应的新连接,保证连接池正常运转。
- 静态检查:空闲连接不足时,系统才检测是否达到最小连接数。
1
2
3
4
5
2
3
4
5
# PreparedStatement
- Statement:主要用于执行静态SQL语句,每执行一次就要对传入的SQL语句编译一次,效率极差。
- PreparedStatement:实例包含已事先编译的SQL语句,执行速度快。可以预防SQL注入攻击,不允许在插入参数的时候改变数据结构。
1
2
2
# 事务
事务(Transaction):数据库中保证交易可靠的机制。
事务特性ACID:
- 原子性(Atomicity):事务必须是原子工作单元,对于其数据修改,要么全部执行,要么全都不执行。
- 一致性(Consistency):事务在完成时,必须使所有的数据都保持一致状态。
- 隔离性(Isolation):由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。
- 持久性(Durability):事务完成之后,它对于系统的影响是永久性的。
1
2
3
4
5
6
7
2
3
4
5
6
7
# 批量更新
批处理:发送到数据库作为一个单元执行的一组更新语句。
- 降低了应用程序和数据库之间的网络调用。
- 批处理较单个SQL语句处理更有效。
批量更新API:
- addBatch():将多条预编译的SQL语句添加到语句对象的SQL语句列表中。
- executeBatch():将语句对象语句列表中的所有SQL语句发送给数据库进行处理。
- clearBatch():清空当前SQL语句列表。
使用JDBC操作数据库时,如何提升读取数据的性能?如何提升更新数据的性能?
- 要提升读取数据的性能,可以指定通过结果集(ResultSet)对象的setFetchSize()方法指定每次抓取的记录数(典型的空间换时间策略)。
- 要提升更新数据的性能可以使用PreparedStatement语句构建批处理,将若干SQL语句置于一个批处理中执行。
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# DAO
DAO(Data Access Object):数据访问对象。
- 建立在数据库和业务层之间,封装所有对数据库的访问。
- 目的:数据访问逻辑和业务逻辑分开。
1
2
3
2
3
# JavaScript
# 简介
- JavaScript是Netscape公司的产品,一种可以嵌入Web页面中运行的基于对象和事件驱动的解释性语言。
- 其源代码不需经过编译,由浏览器解释执行。
- 基于对象:内置大量现成对象。
- JavaScript中变量是弱类型的,甚至在使用变量前可以不作声明,JavaScript的解释器在运行时检查推断其数据类型。
1
2
3
4
2
3
4
# 数据类型
内置对象(基本类型):
- Number:数字
- String:字符串
- Boolean:布尔
- Function:函数
- Array:数组
外部对象:
- window:浏览器对象
- document:文档对象
自定义对象:
- Object:自定义对象
特殊类型:
- null:空
- undefined:未定义
- String类型:
- 由Unicode字符、数字、标点符号组成的字符序列。
- 没有字符类型,字符就是长度为一的字符串。
- Number类型:
- 不区分整型数值和浮点型数值。
- 16进制整数前面加上0x,八进制前面加0。
- Boolean类型:
- 可以自动转型作为数值参与运算,运算时true=1,false=0。
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
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
# 数据类型转换函数
- toString:所有数据类型均可转换为String类型。
- parseInt:强制转换成整数,如果不能转换,则返回NAN(not a number)。
- parseFloat:强制转换成浮点数,如果不能转换,则返回NAN(not a number)。
- typeof:查询当前类型,返回类型名称。
- isNAN(is not a number?):
- 判断被检测表达式经过转换后是否不是一个数。
- 如果被检测表达式不是数则返回true,否则返回false。
- arr.reverse():反向数组
注意:
- JS中 5/2 = 2.5
- 一切表示空的值都是false
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# BOM
BOM:
- Browser Object Model:浏览器对象模型,用来访问和操纵浏览器窗口。
- 通过使用BOM,可移动窗口、更改状态栏文本、执行其他不与页面发生直接联系的操作。
window对象:
- 所有JavaScript全局对象、函数以及变量均自动成为window对象的成员。
- 常用属性:
- document:窗口中显示的HTML文档对象。
- history:浏览过窗口的历史记录对象,
- location:窗口文件地址对象。
- screen:当前屏幕对象。
- navigator:浏览器相关信息。
定时器:
- 多用于网页动态时钟、制作倒计时等。
- 周期性时钟:
- 以一定的间隔执行代码,循环往复。
- setInterval(exp,time);
- exp:执行语句
- time:时间周期,单位为毫秒
- clearInterval(tID):停止启动的定时器
- tID:启动的定时器对象
- 一次性时钟:
- 在一个设定的时间间隔之后执行代码。
- setTimeOut(exp,time);
- exp:执行语句
- time:间隔时间,单位为毫秒
- clearTimeOut(tID):停止启动的定时器
- tID:启动的定时器对象
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
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
# DOM
DOM:
- Document Object Model:文档对象模型,用来操作文档。
- 定义了访问和操作HTML文档的标准方法。
- 应用程序通过对DOM树的操作,来实现对HTML文档数据的操作。
DOM提供了如下操作:
- 查找节点
- 读取节点信息
- 修改节点信息
- 创建新节点
- 删除节点
元素节点的内容:
- innerText:对象起始和结束标签内的文本。
- innerHtml:对象起始和结束标签内的html。
节点属性:
- getAttribute():根据属性名称获取属性的值。
- getElementByTagName:根据指定的标签名称返回所有的元素。
- document.createElement(elementName):创建新节点。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# jQuery
- jQuery是一个优秀的JavaScript框架,一个轻量级的JS库。
- 本质是一个DOM对象数组,它在该数组上扩展了一些操作数组中元素的方法。
DOM对象转换为jQuery对象:
- $(DOM对象)
1
2
3
4
5
2
3
4
5
# Servlet
# 简介
- Servlet:是Sun(Oracle)公司制定的一种用来扩展Web服务器功能的组件规范。
- 组件:符合一定规范,实现部分功能,并且需要部署到容器中才能运行的软件模块。
- 容器:符合一定规范,提供组件运行环境的一个程序。
1
2
3
2
3
# B/S架构
特点:
- 数据库只负责数据的管理。
- Web服务器负责业务的处理。
- 浏览器负责提供页面。
优点:
- 不需要单独安装客户端。
- 开发相对于C/S简单,客户端和服务器的通信模块都是使用标准的HTTP协议进行通信。
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 常用响应值
- 400:错误的请求。
- 404:Web服务器根据请求地址找不到对应的资源。
- 405:Web服务器找不到Service()方法处理请求。
- 500:程序在运行过程中出错。
- 302:临时性重定向。
1
2
3
4
5
2
3
4
5
# HTTP协议
- HyperText Transfer Protocol:是由w3c(万维网联盟)制定的一种应用层协议,用来定义浏览器与web服务器之间如何通信以及通信的数据格式。
请求数据包组成:
- 请求行:请求方式+请求资源路径+协议版本。
- 消息头:消息头是一些键值对,大部分为自动生成。
- 实体内容:只有当请求方式为post时,实体内容才会有数据(即请求参数)。
响应数据包组成:
- 状态行:协议类型+版本+状态码+状态描述。
- 消息头:web服务器返回一些消息头给浏览器,告诉浏览器服务器返回的数据类型和字符集。
- 实体类型:程序处理的结果。
HttpServletRequest对象:
- 代表客户端的请求,当客户端通过http协议访问服务器时,请求中的所有消息都封装在这个对象中。
- 读取和写入http请求数据。
- 取得和设置cookies。
- 取得路径信息。
- 实现请求转发。
HttpServletResponse对象:
- 代表提供给客户端的响应,封装了http的响应数据。
- 设置对客户端的输出内容。
- 设置响应的状态码。
- 设置浏览器的解码方式。
- 设置cookies。
- 实现重定向。
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
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
# 请求方式
- get:指定请求的资源。
- 只能提交少量的数据给web服务器,请求参数显示在浏览器地址栏上,不安全。
- post:向指定的资源提交需要处理的数据。
- 请求参数添加到实体内容中,可提交大量数据,较get相对安全。
- head:要求响应与相应的get一样,但没有响应体。
- delete:删除指定资源。
- put:上传指定资源。
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 设置字符编码
//解决post方式乱码问题
request.setCharacterEncoding("UTF-8");
//解决get方式乱码问题1
String userName = request.getParameter("");
userName = new String(userName.getBytes("iso-8859-1"), "utf-8");
//解决get方式乱码问题2,修改tomcat的server.xml配置文件。
<Connector connectionTimeout="20000" port="8082" protocol="HTTP/1.1" redirectPort="8443" URIEncoding="UTF-8"/>
//解决输出内容乱码
response.setContentType("text/html;charset=UTF-8");
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# Servlet运行的详细步骤
- step1:浏览器依据IP建立与容器的连接。
- step2:浏览器请求数据打包。
- step3:容器解析请求数据包,封装对象。
- step4:容器依据路径找到servlet创建对象。
- step5:容器调用servlet对象的service()方法
- step6:容器将响应打包发给浏览器。
- step7:浏览器取出结果,生成页面。
1
2
3
4
5
6
7
2
3
4
5
6
7
# Servlet的生命周期
- 实例化:容器调用Servlet的构造器,创建一个Servlet对象。
- 初始化:容器在创建好Servlet对象之后,会立即调用该对象的init()方法。
- 就绪:容器收到请求之后调用Servlet对象的service()来处理请求。
- 销毁:容器依据自身的算法删除servlet()对象,删除前会调用destroy()。
1
2
3
4
2
3
4
# Servlet上下文
- 容器启动之后,会为每一个Web应用创建唯一一个符合ServletConfig接口要求的对象,该对象就是Servlet上下文。
- 唯一性:一个Web应用对应一个Servlet。
- 一致性:只要容器不关闭,应用没有被卸载删除,servlet上下文就一致存在
1
2
3
2
3
# JSP
- sun公司制定的一种服务器端动态页面技术的组件规范。
- 在.jsp文件中,主要是HTML和少量java代码。JSP文件会被容器转化成一个Servlet类,然后执行。
JSP有9个内置对象:
- request:封装客户端的请求,其中包含来自GET或POST请求的参数;
- response:封装服务器对客户端的响应;
- pageContext:通过该对象可以获取其他对象;
- session:封装用户会话的对象;
- application:封装服务器运行环境的对象;
- out:输出服务器响应的输出流对象;
- config:Web应用的配置对象;
- page:JSP页面本身(相当于Java程序中的this);
- exception:封装页面抛出异常的对象。
JSP中的四种作用域:
- page:代表与一个页面相关的对象和属性。
- request:代表与Web客户机发出的一个请求相关的对象和属性。一个请求可能跨越多个页面,涉及多个Web组件,需要在页面显示的临时数据可以置于此作用域。
- session:代表与某个用户与服务器建立的一次会话相关的对象和属性。跟某个用户相关的数据应该放在用户自己的session中。
- application:代表与整个Web应用程序相关的对象和属性,它实质上是跨越整个Web应用程序,包括多个页面、请求和会话的一个全局作用域。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# JSP标签库
JSTL的核心标签库,包括<c:if>、<c:choose>、<c: when>、<c: otherwise>、<c:forEach>等,主要用于构造循环和分支结构以控制显示逻辑。
使用标签库的好处包括以下几个方面:
- 分离JSP页面的内容和逻辑,简化了Web开发。
- 开发者可以创建自定义标签来封装业务逻辑和显示逻辑。
- 标签具有很好的可移植性、可维护性和可重用性。
- 避免了对Scriptlet(小脚本)的使用。
1
2
3
4
5
6
7
2
3
4
5
6
7
# 转发和重定向的区别
转发:
- 服务器向浏览器发送一个302状态码及一个location消息头(该消息头的值是一个地址,称之为重定向地址),浏览器收到后会立即重定向地址发出请求。
特点:
- 重定向的地址可以是任意的地址。
- 重定向之后,浏览器地址栏的地址会发生改变。
- 重定向过程中涉及到的web组件并不会共享同一个request和response对象。
重定向:
- 一个web组件将未完成的处理通过容器转交给另外一个web组件继续完成。
- req.getRequestDispatcher(uri).forward(req,resp);
特点:
- 转发之后,地址栏地址不会发生变化。原因是转发的过程是内部的,浏览器并不知道。
- 转发的目的地必须是同一个应用内部的某个地址。
- 转发所涉及的各个web组件会共享同一个request对象和response对象。
注意:
- 在forward之后的语句一定会执行,只要不报异常。
两者的区别:
- 重定向是浏览器发送请求并受到响应以后再次向一个新地址发送请求,转发是服务器收到请求后为了完成响应转到一个新的地址。
- 重定向有两次请求对象,不共享数据,转发只产生一次请求对象并且在组件之间共享数据。
- 重定向之后地址栏地址改变,而转发则不会。
- 重定向的新地址可以是任意地址,转发到的新地址必须是同一应用内的某个地址。
绝对路径的处理技巧:
- 链接地址、表单提交、重定向是从应用名开始写。
- 转发是从应用名之后开始写。
- 获取应用的实际部署名称:request.getContextPath();
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
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
# 状态管理
- 客户端状态管理技术:将状态保存在客户端,代表性的是cookie技术。
- 服务器状态管理技术:将状态保存在服务器端,代表性的是session技术。
cookie:
- 浏览器向web服务器发送请求时,服务器会将少量的数据以set-cookie消息头的方式发送给浏览器,浏览器将这些数据保存下来。
- 当浏览器再次访问服务器时,会将这些数据以cookie消息头的方式发送给服务器。
- cookie的生存时间:默认情况下,浏览器会将cookie保存在内存中,只要浏览器不关闭,cookie就一直存在。可以通过设置过期时间,在浏览器关闭后,cookie仍存在。(cookie.setMaxAge(int seconds))
- cookie只能保存少量的数据,大约4kb左右。
- cookie的个数是有限制的且只能保存字符串。
session:
- 浏览器访问web应用程序时,服务器会为每个浏览器在服务端的内存中分配空间,单独创建一个session对象,该对象有唯一的id属性(sessionid)。
- 服务器会将sessionid使用cookie的方式发送给浏览器,浏览器再次访问服务器时,会将sessionid发送给服务器,服务器可以根据sessionid找到session对象。
- 立即删除session对象:session.invalidate()。
浏览器禁用cookie:
- 如果用户禁用cookie。则服务器无法通过cookie的方式将sessionid发送给浏览器,此时,服务器可以使用如URL重写的方式发送sessionid。
- url重写方式:respose.encodURL(String url)。
session的优点:
- 安全(将状态保存在服务器端)。
- session能够保存的数据类型更丰富,cookie只能够保存字符串。、
- session能够保存更多的数据。
session的缺点:
- session将状态保存在服务器端,占用服务器内存,如果用户量过大,会严重影响服务器的性能。
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 过滤器、监听器
过滤器:
- 过滤器是servlet2.3规范中定义的一种小型的、可插入的web组件。用来拦截servlet容器的请求和响应过程,以便查看或提取正在客户机和服务器之间交换的数据。
过滤器的优点:
- 实现代码的“可插拔性”,即增加或减少某个功能模块,不会影响程序的正常执行。
- 可以将多个相同处理逻辑的模块集中写在过滤器里面,有利于代码的维护。
监听器:
- servlet规范中定义的一种特殊的组件,用来监听servlet容器产生的事件并进行相应的处理。
监听器有哪些作用和用法:
Java Web开发中的监听器(listener)就是application、session、request三个对象创建、销毁或者往其中添加修改删除属性时自动执行代码的功能组件,如下所示:
- ServletContextListener:对Servlet上下文的创建和销毁进行监听。
- ServletContextAttributeListener:监听Servlet上下文属性的添加、删除和替换。
- HttpSessionListener:对Session的创建和销毁进行监听。
- HttpSessionAttributeListener:对Session对象中属性的添加、删除和替换进行监听。
- ServletRequestListener:对请求对象的初始化和销毁进行监听。
- ServletRequestAttributeListener:对请求对象属性的添加、删除和替换进行监听。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# AJAX
# 简介
- Asynchronous JavaScript and Xml:异步的JavaScript和xml。
- 实质是使用XMLHttpRequest对象异步地向服务器发送请求。
- 服务器返回部分数据,以页面无刷新的效果改变页面中的局部内容。
1
2
3
2
3
# 常用属性
status(http请求响应值):
- 200:请求成功。
- 202:请求被接受但处理未完成。
- 400:错误的请求。
readyState(请求的状态):
- 0:尚未初始化。
- 1:正在发送请求。
- 2:请求完成。
- 3:请求成功,正在接受数据。
- 4:数据接受成功。
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# AJAX使用步骤
- 获取AJAX对象:获取XMLHttpRequest实例。
- 设置回调函数:为Ajax对象的onreadystatechange事件设定相应函数。
- 创建请求:调用XMLHttpRequest对象的open方法。
- 发送请求:调用Ajax对象的send方法。
1
2
3
4
2
3
4
# jQuary对AJAX的支持
$.get(
"/ajax/area/get?proCode=" + proCode,//请求地址
function (data) {
//回调函数
}
},
"json" //服务其返回的数据类型
)
$.ajax({
url: "/ajax/province/get",//请求地址
type: "get",//请求方式
dataType: "json",//服务器返回的数据类型
cache: false,
success: function (data) {
//服务器处理正常对应的回调函数
}
}
})
//date:请求参数。
//error:服务器出错对应的回调函数
//async:true(缺省),当值为false时发送同步请求。
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# SSM
# Spring
- Spring是一个开源的轻量级的应用开发框架,其目的是用于简化企业级应用程序开发,降低侵入性。
- Spring提供的IOC和AOP功能,简化了Bean对象的创建和Bean对象之间的解耦,便于系统日后的维护和升级。
- Spring的本质是管理软件中的对象,即创建对象和维护对象之间的关系。
JavaBean:
- 一种简单规范的Java对象。
Bean的实例化:
- 用构造器来实例化。
- 使用静态工厂方法实例化。
- 使用实例工厂方法实例化。
Bean的生命周期:
- Spring IoC容器找到关于Bean的定义并实例化该Bean。
- Spring IoC容器对Bean进行依赖注入。
- 如果Bean实现了BeanNameAware接口,则将该Bean的id传给setBeanName方法。
- 如果Bean实现了BeanFactoryAware接口,则将BeanFactory对象传给setBeanFactory方法。
- 如果Bean实现了BeanPostProcessor接口,则调用其postProcessBeforeInitialization方法。
- 如果Bean实现了InitializingBean接口,则调用其afterPropertySet方法。
- 如果有和Bean关联的BeanPostProcessors对象,则这些对象的postProcessAfterInitialization方法被调用。
- 当销毁Bean实例时,如果Bean实现了DisposableBean接口,则调用其destroy方法。
Spring框架为企业级开发带来的好处:
- 非侵入式:支持基于POJO的编程模式,不强制性的要求实现Spring框架中的接口或继承Spring框架中的类。
- IoC容器:IoC容器帮助应用程序管理对象以及对象之间的依赖关系,对象之间的依赖关系如果发生了改变只需要修改配置文件而不是修改代码。
- AOP(面向切面编程):将所有的横切关注功能封装到切面(aspect)中,通过配置的方式将横切关注功能动态添加到目标代码上,进一步实现了业务逻辑和系统服务之间的分离。
- MVC:Spring的MVC框架是非常优秀的,为Web表示层提供了更好的解决方案。
- 事务管理:Spring以宽广的胸怀接纳多种持久层技术,并且为其提供了声明式的事务管理,在不需要任何一行代码的情况下就能够完成事务管理。
- 其他:Spring为Java企业级开发提供了一站式选择,你可以在需要的时候使用它的部分和全部,甚至可以在感觉不到Spring存在的情况下,在项目中使用Spring提供的各种优秀的功能。
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
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
# AOP
- 通过预编译方式和运行期动态代理方式实现程序功能的统一维护的一种技术。
- 面向切面的核心思想就是,让核心的业务逻辑代码,不需要去管理一些通用的逻辑,比如说事务,安全等这方面的共同逻辑,解耦业务逻辑和通用逻辑。
- 主要功能:日志记录、性能统计、安全控制、事务处理、异常处理等等。
1
2
3
2
3
# IOC
- IOC全称是Inversion of Control,被翻译为控制反转。
- IOC是指程序中对象的获取方式发生反转,由最初的new方式创建,转变为由第三方框架创建,注入(DI),它降低了对象之间的耦合度。
DI:
- DI的基本原理是将一起工作具有关系的对象,通过构造方法参数传入建立关联,因此容器的工作就是创建bean时注入那些依赖关系。
- DI是实现IOC的主要技术途径,主要有两种注入方式,即Setter注入和构造器注入。
获取IOC容器:
- WebApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(servletContext);
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 自动装配
Spring中自动装配的方式:
- no:不进行自动装配,手动设置Bean的依赖关系。
- byName:根据Bean的名字进行自动装配。
- byType:根据Bean的类型进行自动装配。
- constructor:类似于byType,不过是应用于构造器的参数,如果正好有一个Bean与构造器的参数类型相同则可以自动装配,否则会导致错误。
- autodetect:如果有默认的构造器,则通过constructor的方式进行自动装配,否则使用byType的方式进行自动装配。
自动装配有哪些限制:
- 如果使用了构造器注入或者setter注入,那么将覆盖自动装配的依赖关系。
- 基本数据类型的值、字符串字面量、类字面量无法使用自动装配来注入。
- 优先考虑使用显式的装配来进行更精确的依赖注入而不是使用自动装配。
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 事务管理类型
Spring支持编程式事务管理和声明式事务管理。
- 许多Spring框架的用户选择声明式事务管理,因为这种方式和应用程序的关联较少,因此更加符合轻量级容器的概念。
- 声明式事务管理要优于编程式事务管理,尽管在灵活性方面它弱于编程式事务管理,因为编程式事务允许你通过代码控制业务。
事务分为全局事务和局部事务:
- 全局事务由应用服务器管理,需要底层服务器JTA支持(如WebLogic)。
- 局部事务和底层采用的持久化方案有关,例如使用JDBC进行持久化时,需要使用Connetion对象来操作事务;而采用Hibernate进行持久化时,需要使用Session对象来操作事务。
1
2
3
4
5
6
7
2
3
4
5
6
7
# MVC
MVC模式简介:
- M-Model模型:模型的职责是负责业务处理逻辑。包含两层:业务数据和业务处理逻辑。
- V-View视图:视图的职责是负责显示界面和用户交互。
- C-Controller控制器:控制器是模型层和视图层之间的桥梁,用于控制流程。
Spring Web MVC:
- 是spring框架一个非常重要的功能模块。实现了MVC结构,便于简化、快速开发MVC结构的Web程序。
Spring Web MVC核心组件:
- DispatcherServlet:控制器,请求入口。
- HandlerMapping:控制器,请求派发。
- Controller:控制器,请求处理流程。
- ModelAndView:模型,封装业务处理结果和视图。
- ViewResolver:视图,视图显示处理器。
Spring Web MVC处理流程:
- 浏览器向Spring发出请求,请求交给前端控制器DispatcherServlet处理。
- 控制器通过HandlerMapping找到相应的Controller组件处理请求。
- 执行Controller约定方法处理请求,在约定方法调用模型组件完成业务处理,约定方法可以返回一个ModelAndView对象,封装了处理结果数据和视图名称信息。
- 控制器接收ModelAndView之后,调用ViewResolver组件,定位View(JSP)并传递数据信息,生成响应界面结果。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# MyBatis
MyBatis是支持普通SQL查询,存储过程和高级映射的优秀持久层框架。
MyBatis体系结构:
- 加载配置
- SQL解析
- SQL执行
- 结果映射
MyBatis框架API:
- SqlSessionFactoryBuilder:该对象负责根据MyBatis配置文件SqlMapConfig.xml构建SqlSessionFactory实例。
- SqlSessionFactory:每一个MyBatis的应用程序都以一个SqlSessionFactory对象为核心。该对象负责创建SqlSession对象实例。
- SqlSession:该对象包含了所有执行SQL操作的方法,用于执行已映射的SQL语句。
MyBatis中使用#和$书写占位符有什么区别:
- #将传入的数据都当成一个字符串,会对传入的数据自动加上引号。
- $将传入的数据直接显示生成在SQL中。
- 注意:使用$占位符可能会导致SQL注射攻击,能用#的地方就不要使用$。
- 写order by子句的时候应该用$而不是#。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 一级缓存和二级缓存
- 一级缓存基于sqlSession默认开启,在操作数据库时需要构造SqlSession对象,在对象中有一个HashMap用于存储缓存数据。不同的SqlSession之间的缓存数据区域是互相不影响的。
- 一级缓存的作用域是SqlSession范围的,当在同一个sqlSession中执行两次相同的sql语句时,第一次执行完毕会将数据库中查询的数据写到缓存(内存),
- 第二次查询时会从缓存中获取数据,不再去底层数据库查询,从而提高查询效率。
- 需要注意的是,如果SqlSession执行了DML操作(增删改),并且提交到数据库,MyBatis则会清空SqlSession中的一级缓存,这样做的目的是为了保证缓存中存储的是最新的信息,避免出现脏读现象。
- 当一个SqlSession结束后该SqlSession中的一级缓存也就不存在了。
- 二级缓存是mapper级别的缓存。
- 使用二级缓存时,多个SqlSession使用同一个Mapper的sql语句去操作数据库,得到的数据会存在二级缓存区域,它同样是使用HashMap进行数据存储。
- 相比一级缓存SqlSession,二级缓存的范围更大,多个Sqlsession可以共用二级缓存,二级缓存是跨SqlSession的。
- 二级缓存的作用域是mapper的同一个namespace。
- 不同的sqlSession两次执行相同的namespace下的sql语句,且向sql中传递的参数也相同,即最终执行相同的sql语句,则第一次执行完毕会将数据库中查询的数据写到缓存,第二次查询会从缓存中获取数据,不再去底层数据库查询,从而提高效率。
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# ORM
对象关系映射(Object-Relational Mapping,简称ORM):
- 是一种为了解决程序的面向对象模型与数据库的关系模型互不匹配问题的技术。
- 简单的说,ORM是通过使用描述对象和数据库之间映射的元数据(在Java中可以用XML或者是注解),将程序中的对象自动持久化到关系数据库中或者将关系数据库表中的行转换成Java对象,其本质上就是将数据从一种形式转换到另外一种形式。
1
2
3
2
3
# 命名空间
namespace:
- 在大型项目中,可能存在大量的SQL语句,这时候为每个SQL语句起一个唯一的标识(ID)就变得并不容易了。
- 为了解决这个问题,在MyBatis中,可以为每个映射文件起一个唯一的命名空间,这样定义在这个映射文件中的每个SQL语句就成了定义在这个命名空间中的一个ID。
- 只要我们能够保证每个命名空间中这个ID是唯一的,即使在不同映射文件中的语句ID相同,也不会再产生冲突了。
1
2
3
4
2
3
4
# 动态SQL
对于一些复杂的查询,我们可能会指定多个查询条件,但是这些条件可能存在也可能不存在,此时就需要根据用户指定的条件动态生成SQL语句。MyBatis提供了动态SQL的功能来解决这个问题。
MyBatis中用于实现动态SQL的元素主要有:
- if
- choose / when / otherwise
- trim
- where
- set
- foreach
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# lo4g4j
- Loggers:记录器 (日志文件基本配置)
- Appenders:附加器 (日志文件去哪里)
- Layouts:布局器 (日志文件输出具体格式)这里可简单理解为日志类别,日志要输出的地方和日志以何种形式输出
log4j.xml核心标签:
- level:日志级别
- appender:日志文件输出地
- layout:输出具体格式
- appender:一般一个日志文件就对应一个,依赖layout file、append、encoding
- logger:一般代码模块类路径一一对应,如 com.程序汪公司.系统.模块名,依赖level appender
- root:默认日志,依赖level appender
Loggers组件在此系统中被分为五个级别:
- DEBUG、INFO、WARN、ERROR和FATAL。
- 这五个级别是有顺序的,DEBUG < INFO < WARN < ERROR < FATAL
- 企业项目生产环境一般都是设置INFO 级别
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Spring Boot
# 简介
Spring Boot主要是用来简化Spring应用的开发,约定大于配置,简化Spring项目开发中大量繁琐的xml配置,去繁从简,just run就能创建一个独立的,产品级别的应用。
Spring Boot的优点:
- 快速创建独立运行的Spring项目以及主流框架的集成。
- 使用内嵌的Servlet容器,项目无需打包,部署到容器即可运行。
- 使用starters启动器,进行自动依赖和版本控制。
- 无需配置xml文件,无代码生成,开箱即用。
- 与云计算的天然集成。
- 提供运行时的应用监控。
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 父项目和Starters
<!--父项目-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
</parent>
<!--Starters-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
父项目:
- <parent>标签中声明了该项目继承了父项目中的依赖和配置等内容:包括了JDK的版本,依赖Jar包的版本等。
- 在子项目中对于大部分包可以直接引入依赖包的groupId和artifactId即可,不需要配置version版本号,父项目中的配置控制引入依赖包的版本。
Starters
- Spring Boot为我们提供了简化企业及开发绝大多数技术场景的starter pom(启动器)。项目中需要哪些技术,只要引入相应技术场景的starter pom文件,相关技术的依赖会自动引入并且会进行自动配置,从而简化我们开发。
- Spring Boot提供的Starters几乎涵盖了J2EE所有的场景,并且对技术依赖的Jar包做了严格的测试和版本控制,不需要担心冲突问题。
1
2
3
4
5
6
7
2
3
4
5
6
7
# YAML语法
yml基本语法:
- 使用缩进表示层级关系。
- 缩进时不允许使用Tab键,只允许使用空格。
- 缩进空格数目不重要,只需要相同层次的元素左对齐即可。
- 大小写敏感。
yml支持的三种数据格式:
- 对象:键值对的集合。
- 数组:一组按次序排列的值。
- 字面量:单个的、不可再分的值。
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# Spring Cloud
# 简介
Spring Cloud:基于Spring Boot提供了一套微服务解决方案,简化了分布式系统基础设施的开发,SpringCloud为开发人员提供了快速构建分布式系统的一些工具,包括配置管理、服务发现、断路器、路由、微代理、事件总线、全局锁、决策竞选、分布式会话等等,他们都可以用SpringBoot的开发风格做到一键启动和部署。
1
# 微服务的进化史
传统应用-单体应用:
- 一个单体应用程序把所有的功能放置在一个单一的应用程序。
- 通过在多个服务器上复制这个单体进行扩展。
优点:
- 部署简单
- 技术单一
- 用人成本低
缺点:
- 系统启动慢
- 可伸缩性差
- 问题修复周期长
现代应用-微服务:
- 微服务的概念源于2014年Martin Flower所写的文章“Microservices”。
- 微服务架构是一种架构模式,它提倡将单一应用程序划分为一组小的服务,服务之间互相协调、互相配合。每个服务运行在其独立的进程中,每个服务都围绕着具体业务进行构建,并且能够独立地部署到生产环境、类生产环境等。各个微服务之间是松耦合的,每个微服务仅关注于完成一件任务,每个任务代表一个小的业务能力。
- 一个微服务架构把每个功能元素放进一个独立的服务中。
- 通过跨服务器分发这些服务进行扩展,只在需要的时候才复制。
优点:
- 易于开发和维护
- 单个微服务启动快
- 局部修改,容易部署
- 技术栈多样性
- 按需扩展
- 支持高并发
缺点:
- 运维要求高
- 分布式系统复杂性
- 服务调整成本高
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
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
# RabbitMQ
# 应用解耦
有的时候由于有着不同的服务或者不同的应用之间互相调用,导致系统的复杂度与耦合度过高,由于我们的每个服务都是独立的,不想因为某些依赖或者引用而要被迫共同使用,所以我们可以考虑使用rabbitmq来对服务间解耦合,这样即使有着相互的依赖,我们也可以通过消息队列对不同的服务进行解耦,保证了服务的独立性
1
# 异步调用
异步调用同样很容易理解,单模块调用多模块儿,将消息做分发,一个动作发送多条消息到不同的队列中,相应的模块对收到的消息依次消费,在异步消费的同时也会提高效率,因为我们将本来串行的内容改为了并行。
1
# 流量削峰
对突然出现的大量流量进行削峰,将发送到服务端的大量请求进行收集到队列中,而且可以控制队列长度,超过阈值直接丢弃,这样就可保证数据平缓输入,控制消息队列在可以承受的范围内依次消费消息,这样就可以为系统减压了
1
# 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
1
2
3
4
2
3
4
# 六种工作模式
- 简单模式:一个生产者,一个消费者
- work模式:一个生产者,多个消费者,每个消费者获取到的消息唯一。
- 订阅模式:一个生产者发送的消息会被多个消费者获取。
- 路由模式:发送消息到交换机并且要指定路由key ,消费者将队列绑定到交换机时需要指定路由key
- topic模式:将路由键和某模式进行匹配,此时队列需要绑定在一个模式上,“#”匹配一个词或多个词,“*”只匹配一个词。
1
2
3
4
5
2
3
4
5
# Nginx
# 配置
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 66;
server {
listen 80;
server_name 120.76.193.47;
location / {
proxy_pass http://120.76.193.47:8080;
index index.html index.htm;
}
}
server {
listen 80;
server_name www.jmzhao.club;
location / {
proxy_pass http://120.76.193.47:8080;
index index.html index.htm;
}
}
}
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
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
# 常用命令
#交互式访问
docker exec -it 7ae899e4f03e /bin/bash
docker exec -it nginx /bin/bash
#查看文件
ls -al
#相对目录
/data/css
ctrl + d 退出
ctrl + c 终止
#maven打包(跳过测试)
mvn clean package -Dmaven.test.skip=true
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 提高篇
# HashMap
JDK1.7与JDK1.8 HashMap的变化:
- JDK1.7中hashmap是由数组加链表组成的,数组是hashmap的主体,链表则主要是为了解决哈希冲突而存在的。
- JDK1.8以后,解决哈希冲突时的较大的变化,当链表长度达到阈值(默认为8)并且当前数组的长度大于等于64时,此时该索引位置上的所有数据将改为使用红黑树存储。
- JDK1.8源码中,会先检查数组长度,如果长度小于64,则对数组进行扩容,而不是进行树形化。
- JDK1.7中HashMap在执行构造方法时创建数组。
- JDK1.8中HashMap在第一次调用put方法时创建数组。
- ArrayList 底层是数组 LinkedList底层是双链表
- 哈希冲突:两个对象调用的hashcode的方法计算的哈希码一致导致计算的数组的索引值相同。
- HashMap的初始容量16,装载因子0.75 。
- 合理定义初始容量,减少扩容次数,因为会调用rehash,降低性能。
- 红黑数的变化:单个索引上的链表长度达到8时转化为红黑树,长度降到6时红黑树会再转化为链表(泊松分布)
- 长度为8时:链表 O(8/2)=4 红黑树 O(log(8))=3
- 根据两者的函数图也可以知道随着bin中的数量越多那么红黑树花的时间远远比链表少,这也是原因之一。
- 长度为6,红黑树会再转化为链表,可以理解为为了过渡作用,如果一个HashMap在这数段,频繁put or remove,那么将不断的进行转换,毫无疑问这十分消耗资源。
扩展:
- 不过理想情况下随机hashCode算法下所有bin中节点的分布频率会遵循泊松分布,我们可以看到,一个bin中链表长度达到8个元素的概率为0.00000006,几乎是不可能事件。
- 通俗点将就是put进去的key进行计算hashCode时 只要选择计算hash值的算法足够好(hash碰撞率极低),从而遵循泊松分布,使得桶中挂载的bin的数量等于8的概率非常小,从而转换为红黑树的概率也小,反之则概率大。
两种方式解决hash碰撞:
- 探测(线性寻址)+链表
为什么HashMap扩容总是2的n次幂?
- 自定义容量向上取整为大于等于该自定义容量最小的2的n次幂。向集合中添加元素时,会使用(n - 1) & hash的计算方法,不同的hash值,和(n-1)进行位运算后,能够得出不同的值,使得添加的元素能够均匀分布在集合中不同的位置上,避免hash碰撞,避免形成链表的结构,防止查询效率降低!
ConcurrentHashMap的高并发性主要来自于三个方面:
- 用分离锁实现多个线程间的更深层次的共享访问。
- 用HashEntery对象的不变性来降低执行读操作的线程在遍历链表期间对加锁的需求。
- 通过对同一个Volatile变量的写/读访问,协调不同线程间读/写操作的内存可见性。
- 使用分离锁,减小了请求同一个锁的频率。
- 扩展:
- 在ConcurrentHashMap中,大量使用了U.compareAndSwapXXX的方法,这个方法是利用一个CAS算法实现无锁化的修改值的操作,他可以大大降低锁代理的性能消耗。这个算法的基本思想就是不断地去比较当前内存中的变量值与你指定的一个变量值是否相等,如果相等,则接受你指定的修改的值,否则拒绝你的操作。因为当前线程中的值已经不是最新的值,你的修改很可能会覆盖掉其他线程修改的结果。
- 有顺序的Map实现类:
- TreeMap和LinkedHashMap是有序的(TreeMap默认升序,LinkedHashMap则记录了插入顺序)。
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
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
# JVM加载class文件的原理机制
- JVM中类的装载是由类加载器(ClassLoader)和它的子类来实现的。
- Java中的类加载器是一个重要的Java运行时系统组件,它负责在运行时查找和装入类文件中的类。
- 当Java程序需要使用某个类时,JVM会确保这个类已经被加载、连接(验证、准备和解析)和初始化。
- 类的加载是指把类的.class文件中的数据读入到内存中,通常是创建一个字节数组读入.class文件,然后产生与所加载类对应的Class对象。
- 加载完成后,Class对象还不完整,所以此时的类还不可用。当类被加载后就进入连接阶段,这一阶段包括验证、准备(为静态变量分配内存并设置默认的初始值)和解析(将符号引用替换为直接引用)三个步骤。最后JVM对类进行初始化,包括:1.如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类;2.如果类中存在初始化语句,就依次执行这些初始化语句。
类的加载是由类加载器完成的,类加载器包括:
- 根加载器(BootStrap)
- 扩展加载器(Extension)
- 系统加载器(System)
- 用户自定义类加载器(java.lang.ClassLoader的子类)
双亲委派机制:
- 指先委派父类加载器寻找目标类,在找不到的情况下在自己的路径中查找并载入目标类。
优势:
- 沙箱安全机制:比如自己写的String.class不会被加载,这样可以防止核心库被篡改。
- 避免类的重复加载:当父ClassLoader已经加载了该类的时候,就不需要子ClassLoader再加载一次。
- Tomcat通过用户自定义的类加载器打破了双亲委派模型,因为要实现war包隔离。避免不同的war包引用了同一个jar包会产生冲突(全限定类名相同)。
下面是关于几个类加载器的说明:
- Bootstrap:一般用本地代码实现,负责加载JVM基础核心类库(rt.jar);
- Extension:从java.ext.dirs系统属性所指定的目录中加载类库,它的父加载器是Bootstrap;
- System:又叫应用类加载器,其父类是Extension。它是应用最广泛的类加载器。它从环境变量classpath或者系统属性java.class.path所指定的目录中记载类,是用户自定义加载器的默认父加载器。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 内存泄漏
一般我们所说的内存泄漏指的是堆内存的泄漏。
- 堆内存是程序从堆中为其分配的,大小任意的,使用完后要显示释放内存。
- 当应用程序用关键字new等创建对象时,就从堆中为它分配一块内存,使用完后程序调用free或者delete释放该内存,否则就说该内存就不能被使用,我们就说该内存被泄漏了。
- 理论上Java因为有垃圾回收机制(GC)不会存在内存泄露问题。
- 然而在实际开发中,可能会存在无用但可达的对象,这些对象不能被GC回收,因此也会导致内存泄露的发生。
- 例如Hibernate的Session(一级缓存)中的对象属于持久态,垃圾回收器是不会回收这些对象的,然而这些对象中可能存在无用的垃圾对象,如果不及时关闭(close)或清空(flush)一级缓存就可能导致内存泄露。
1
2
3
4
5
6
7
2
3
4
5
6
7
# 实现对象克隆
前提条件:
- 被克隆对象所在的类必须实现Cloneable接口。
- 必须重写clone方法。
实现方式:
- 方式一:实现Cloneable接口并重写Object类中的clone()方法;
- 方式二:实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆。
为什么要使用clone:
- JAVA中默认的“=”赋值操作,其实是将一个引用复制过去了,二者指向的还是同一块内存。
- clone方法是在复制一个对象,赋值的对象是单独独立的,有独自的内存空间。
- clone方法是Object中的一个方法,是一个native方法,也就是本地方法(可以调用底层操作系统的方法),在调用本地方法创建对象,比直接new创建对象效率高。
浅克隆的局限性:
- 基本数据类型可以达到完全复制,引用数据类型则不可以。
- 在对象被克隆的时候,其引用数据类型的属性仅仅是拷贝了一份引用,当该属性的值发生改变时,被克隆对象的值也会发生改变。
- 可以对其引用数据类型的属性再进行克隆解决该问题。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 阐述final、finally、finalize的区别
- final:略。
- finally:通常放在try…catch…的后面构造总是执行代码块,这就意味着程序无论正常执行还是发生异常,这里的代码只要JVM不关闭都能执行,可以将释放外部资源的代码写在finally块中。
- finalize:Object类中定义的方法,Java中允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在销毁对象时调用的,通过重写finalize()方法可以整理系统资源或者执行其他清理工作。
1
2
3
2
3
# Collection和Collections的区别
- Collection是一个接口,它是Set、List等容器的父接口。
- Collections是个一个工具类,提供了一系列的静态方法来辅助容器操作,这些方法包括对容器的搜索、排序、线程安全化等等。
1
2
2
# sleep()方法和yield()方法
- sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会。
- yield()方法只会给相同优先级或更高优先级的线程以运行的机会;。
- 线程执行sleep()方法后转入阻塞(blocked)状态,而执行yield()方法后转入就绪(ready)状态。
- sleep()方法声明抛出InterruptedException,而yield()方法没有声明任何异常;
- sleep()方法比yield()方法具有更好的可移植性。
1
2
3
4
5
2
3
4
5
# 数据读取问题
- 脏读:A事务读取B事务尚未提交的数据并在此基础上操作,而B事务执行回滚,那么A读取到的数据就是脏数据。
- 不可重复读:事务A重新读取前面读取过的数据,发现该数据已经被另一个已提交的事务B修改过了。
- 幻读:事务A重新执行一个查询,返回一系列符合查询条件的行,发现其中插入了被事务B提交的行
1
2
3
2
3
# 获得一个类的类对象有哪些方式
- 方法1:类型.class,例如:String.class
- 方法2:对象.getClass(),例如:"hello".getClass()
- 方法3:Class.forName(),例如:Class.forName("java.lang.String")
1
2
3
2
3
# JAVA的反射机制
Java反射是Java被视为动态(或准动态)语言的一个关键性质。这个机制允许程序在运行时透过Reflection APIs取得任何一个已知名称的class的内部信息,包括其modifiers(诸如public, static 等)、superclass(例如Object)、实现之interfaces(例如Cloneable),也包括fields和methods的所有信息,并可于运行时改变fields内容或唤起methods。
- Java反射机制容许程序在运行时加载、探知、使用编译期间完全未知的classes。
- 换言之,Java可以加载一个运行时才得知名称的class,获得其完整结构。
如何通过反射创建对象:
- 方法1:通过类对象调用newInstance()方法,例如:String.class.newInstance()
- 方法2:通过类对象的getConstructor()或getDeclaredConstructor()方法获得构造器对象并调用其newInstance()方法创建对象, - eg:String.class.getConstructor(String.class).newInstance("Hello")。
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 设计模式
设计模式:就是一套被反复使用的代码设计经验的总结。
- 使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。
- 设计模式使人们可以更加简单方便的复用成功的设计和体系结构。
- 将已证实的技术表述成设计模式也会使新系统开发者更加容易理解其设计思路。
工厂模式:
- 工厂类可以根据条件生成不同的子类实例,这些子类有一个公共的抽象父类并且实现了相同的方法,但是这些方法针对不同的数据进行了不同的操作(多态方法)。
- 当得到子类的实例后,开发人员可以调用基类中的方法而不必考虑到底返回的是哪一个子类的实例。
代理模式:
- 给一个对象提供一个代理对象,并由代理对象控制原对象的引用。
- 功能提供类可以更加专注于主要功能的实现,代理类可以在功能提供类提供的功能上添加一下功能。
适配器模式:
- 把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起使用的类能够一起工作。
单例模式:
- 顾名思义就是只有一个实例,并且她自己负责创建自己的对象。
- 这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
- 实现一个单例有两点注意事项:①将构造器私有,不允许外界通过构造器创建对象;②通过公开的静态方法向外界返回类的唯一实例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 持久层设计要考虑的问题有哪些
持久层设计的目标包括:
- 数据存储逻辑的分离,提供抽象化的数据访问接口。
- 数据访问底层实现的分离,可以在不修改代码的情况下切换底层实现。
- 资源管理和调度的分离,在数据访问层实现统一的资源调度(如缓存机制)。
- 数据抽象,提供更面向对象的数据操作。
1
2
3
4
5
2
3
4
5
# 大型网站在架构上应当考虑哪些问题
- 分层:分层是处理任何复杂系统最常见的手段之一,将系统横向切分成若干个层面,每个层面只承担单一的职责,然后通过下层为上层提供的基础设施和服务以及上层对下层的调用来形成一个完整的复杂的系统。
- 分割:分割是对软件的纵向切分。我们可以将大型网站的不同功能和服务分割开,形成高内聚低耦合的功能模块(单元)。
- 分布式:除了上面提到的内容,网站的静态资源(JavaScript、CSS、图片等)也可以采用独立分布式部署并采用独立的域名,这样可以减轻应用服务器的负载压力,也使得浏览器对资源的加载更快。
- 集群:集群使得有更多的服务器提供相同的服务,可以更好的提供对并发的支持。
- 缓存:所谓缓存就是用空间换取时间的技术,将数据尽可能放在距离计算最近的位置。使用缓存是网站优化的第一定律。我们通常说的CDN、反向代理、热点数据都是对缓存技术的使用。
- 异步:异步是实现软件实体之间解耦合的又一重要手段。使用异步处理还可以提高系统可用性,加快网站的响应速度,同时还可以起到削峰作用(应对瞬时高并发)。
- 冗余:各种服务器都要提供相应的冗余服务器以便在某台或某些服务器宕机时还能保证网站可以正常工作,同时也提供了灾难恢复的可能性。冗余是网站高可用性的重要保证。
1
2
3
4
5
6
7
2
3
4
5
6
7
# 优化SQL语句的一般步骤
- 通过show status命令了解各种SQL的执行频率
- 定位执行效率较低的SQL语句
- 通过explain分析低效SQL的执行计划
- 确定问题并采取相应的优化措施
批量往MySQL导入1000万数据有什么方法:
- 减少IO次数
- SQL写法优化
- 合理设置批量大小
- 尽量顺序插入
eg:
- 1.一条SQL语句插入多条数据
- 2.在事务中进行插入处理,切记不要1条数据提交一下,肯定要分批处理
- 3.数据有序插入,是为了减少索引的维护压力
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 数据库死锁
产生死锁的原因主要是:
- 系统资源不足。
- 进程运行推进的顺序不合适。
- 资源分配不当等。
MySQL怎么解决死锁:
- 重启数据库
- 杀掉抢资源的进程
- 先查哪些进程在抢资源:SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;
- 杀掉它们:Kill trx_mysql_thread_id;
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 如何决定选用HashMap还是TreeMap
- 对于在Map中插入、删除和定位元素这类操作,HashMap是最好的选择。
- 如果需要对一个有序的key集合进行遍历,TreeMap是更好的选择。
- 基于collection的大小,也许向HashMap中添加元素会更快,将map换为TreeMap进行有序key的遍历。
1
2
3
2
3
# 为什么要用动态代理
- 可以在不修改别代理对象代码的基础上,通过扩展代理类,进行一些功能的附加与增强。
静态代理与动态代理的区别:
- 动态代理使我们免于去重写接口中的方法,而着重于去扩展相应的功能或是方法的增强,与静态代理相比简单了不少,减少了项目中的业务量。
1
2
3
4
2
3
4
# Java内存模型(JMM)
- Java内存模型,其实是保证了Java程序在各种平台下对内存的访问都能够得到一致效果的机制及规范。
- 目的是解决由于多线程通过共享内存进行通信时,存在的原子性、可见性(缓存一致性)以及有序性问题。
- 除此之外,Java内存模型还提供了一系列原语,封装了底层实现后,供开发者直接使用。如我们常用的一些关键字:synchronized、volatile以及并发包等。
1
2
3
2
3
# 怎么防止前端重复提交
- 提交按钮后屏蔽提交按钮(前端js控制)
- 前端生产唯一id,后端通过唯一索引(简单粗暴,我喜欢)
- 利用Session防止表单重复提交
1
2
3
2
3
# 数据库乐观锁或悲观锁
乐观锁:
- 一般利用表字段的version+1来实现(如SVN、GIT提交代码就是这样的)
- 性能高、重试失败成本不高建议乐观
悲观锁:
- 一般是 where id=XX for update 来实现 (一般银行转账、工单审批)
- 性能低,但安全,失败成功高建议悲观,使用不当有死锁风险
1
2
3
4
5
6
7
2
3
4
5
6
7
# redis的场景数据类型和应用场景
- String
- List
- Hash
- Set
- Sorted Set
应用场景:
- String类型是最基础的一种key-value存储形式,value其实不仅仅可以是String,也可以是数值类型。
- 常常用来做计数器这类自增自减的功能。
- 也可以用在分布式session 用户的登录信息缓存在redis中
key:value
key加密过的签名
value就是对应用户信息json格式存储
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# Java如何实现线程同步
- 同步方法 synchronized关键字修饰的方法(悲观锁)
- 使用特殊域变量(volatile)实现线程同步(保持可见性,多线程更新某一个值时,案例线程安全单例双检查锁)
- ThreadLocal(每个线程获取的都是该变量的副本)
- 使用重入锁实现线程同步(相对synchronized锁粒度更细了,效率高)
1
2
3
4
2
3
4
# i++是线程安全的吗
- 局部变量肯定是线程安全的(原因:方法内局部变量是线程私有的)
- 成员变量多个线程共享时,就不是线程安全的(原因:成员变量是线程共享的)
1
2
2
# Singleton
/**
*
* @ClassName: Singleton3
* @Description: 双检锁/双重校验锁(DCL,即 double-checked locking)
* 这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
*/
class Singleton3{
//私有构造函数
private Singleton3(){}
private static Singleton3 singleton3 = null;
public static Singleton3 getSingleton3(){
//先检查实例是否存在,如果不存在才进入下面的同步块
if(singleton3 == null){
//同步块,线程安全的创建实例
synchronized (Singleton3.class) {
//再次检查实例是否存在,如果不存在才真正的创建实例
if(singleton3 == null){
singleton3 = new Singleton3();
}
}
}
return singleton3;
}
}
/**
*
* @ClassName: Singleton4
* @Description: 静态(类级)内部类:这种方式能达到双检锁方式一样的功效,但实现更简单。
* 对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。
* 这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
*/
class Singleton4{
private Singleton4(){};
/**
*
* @ClassName: Singleton4Holder
* @Description: 类级内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例
* 没有绑定关系,而且只有被调用到时才会装载,从而实现了延迟加载。
*/
private static class Singleton4Holder{
//静态初始化器,由JVM来保证线程安全
private static Singleton4 singleton4 = new Singleton4();
}
public static Singleton4 getSingleton4(){
return Singleton4Holder.singleton4;
}
}
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
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
# RESTful 风格
REST意思是表征性状态转移。
- restful风格:一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件。它主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。
- 可以通过 GET、 POST、 PUT、 PATCH、 DELETE 等方式对服务端的资源进行操作。
- GET 用于查询资源
- POST 用于创建资源
- PUT 用于更新服务端的资源的全部信息
- PATCH 用于更新服务端的资源的部分信息
- DELETE 用于删除服务端的资源。
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# volidate关键字
- 主要作用:用在多线程编程时,当在jvm开启优化性能的时候,强制线程在使用volidate修饰的变量的时候,从主内存中读取,不从线程副本内存中读取变量,从而保证了变量的同步安全。
1
# NIO和 BIO
- IO(BIO)是面向流的,NIO是面向缓冲区的
- BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。
- NIO:New IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。
- AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操作基于事件和回调机制。
1
2
3
4
2
3
4
# ActiveMQ
package com.itek.consumer;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
/**
* @Author:ZJM
* @Date:2020/3/15 22:24
* @Version 1.0
* @description:
*/
public class PTP_Consumer2 {
public static void main(String[] args) throws JMSException {
//1.创建连接工厂
ConnectionFactory factory =
new ActiveMQConnectionFactory("tcp://120.76.193.47:61616");
//2.创建连接
Connection connection = factory.createConnection();
//3.打开连接
connection.start();
//4.创建session
/**
* 参数一:是否开启事务
* 参数二:消息确认机制(自动确认)
*/
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//5.创建目标地址(Queue:点对点消息 Topic:发布订阅消息)
Queue queue01 = session.createQueue("queue01");
//6.创建消息生产者
MessageConsumer consumer = session.createConsumer(queue01);
//7.接收消息
consumer.setMessageListener(new MessageListener() {
//处理消息
@Override
public void onMessage(Message message) {
if(message instanceof TextMessage){
TextMessage textMessage = (TextMessage)message;
try {
System.out.println("接收到的消息:"+textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
}
});
}
}
package com.itek.producer;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
/**
* @Author:ZJM
* @Date:2020/3/15 21:49
* @Version 1.0
* @description:点对点模式,消息生产者
*/
public class PTP_Producer {
public static void main(String[] args) throws JMSException {
//1.创建连接工厂
ConnectionFactory factory =
new ActiveMQConnectionFactory("tcp://120.76.193.47:61616");
//2.创建连接
Connection connection = factory.createConnection();
//3.打开连接
connection.start();
//4.创建session
/**
* 参数一:是否开启事务
* 参数二:消息确认机制(自动确认)
*/
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//5.创建目标地址(Queue:点对点消息 Topic:发布订阅消息)
Queue queue01 = session.createQueue("queue01");
//6.创建消息生产者
MessageProducer producer = session.createProducer(queue01);
//7.创建消息
//createTextMessage 文本类型
TextMessage textMessage = session.createTextMessage("test message");
//8.发送消息
producer.send(textMessage);
//9.释放资源
session.close();
connection.close();
}
}
package com.itek.consumer;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
/**
* @Author:ZJM
* @Date:2020/3/15 22:24
* @Version 1.0
* @description:
*/
public class PS_Consumer2 {
public static void main(String[] args) throws JMSException {
//1.创建连接工厂
ConnectionFactory factory =
new ActiveMQConnectionFactory("tcp://120.76.193.47:61616");
//2.创建连接
Connection connection = factory.createConnection();
//3.打开连接
connection.start();
//4.创建session
/**
* 参数一:是否开启事务
* 参数二:消息确认机制(自动确认)
*/
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//5.创建目标地址(Queue:点对点消息 Topic:发布订阅消息)
Topic topic = session.createTopic("topic");
//6.创建消息生产者
MessageConsumer consumer = session.createConsumer(topic);
//7.接收消息
consumer.setMessageListener(new MessageListener() {
//处理消息
@Override
public void onMessage(Message message) {
if(message instanceof TextMessage){
TextMessage textMessage = (TextMessage)message;
try {
System.out.println("接收到的消息:"+textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
}
});
}
}
package com.itek.producer;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
/**
* @Author:ZJM
* @Date:2020/3/15 21:49
* @Version 1.0
* @description:发布订阅模式,消息生产者
*/
public class PS_Producer {
public static void main(String[] args) throws JMSException {
//1.创建连接工厂
ConnectionFactory factory =
new ActiveMQConnectionFactory("
");
//2.创建连接
Connection connection = factory.createConnection();
//3.打开连接
connection.start();
//4.创建session
/**
* 参数一:是否开启事务
* 参数二:消息确认机制(自动确认)
*/
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//5.创建目标地址(Queue:点对点消息 Topic:发布订阅消息)
Topic topic = session.createTopic("topic");
//6.创建消息生产者
MessageProducer producer = session.createProducer(topic);
//7.创建消息
//createTextMessage 文本类型
TextMessage textMessage = session.createTextMessage("test message");
//8.发送消息
producer.send(textMessage);
//9.释放资源
session.close();
connection.close();
}
}
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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# LINUX部署
# 远程连接
- 使用finalshell:安全终端模拟软件。
- 查看系统版本 lsb_release -a (LTS:长期维护版)
- ps:Codename 为 bionic,该名称为我们 Ubuntu 系统的名称,修改数据源需要用到该名称
1
2
3
2
3
# 数据源
- 修改数据源:vi /etc/apt/sources.list
deb http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse
- 更新数据源:apt-get update
常用APT命令:
- 安装软件包:apt-get install packagename
- 删除软件包:apt-get remove packagename
- 清理无用的包:apt-get clean && apt-get autoclean
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# Linux安装Java
- 手动安装软件,使用工具将tar包上传至/usr/local/目录下。
- 此处以 JDK 1.8.0_152 为例:
- 解压缩:tar -zxvf jdk-8u152-linux-x64.tar.gz
- 创建目录:mkdir -p /usr/local/java
- 移动安装包:mv jdk1.8.0_152/ /usr/local/java/
- 设置所有者:chown -R root:root /usr/local/java/
- 配置环境变量:
- 配置系统环境变量:vi /etc/environment
- 修改配置文件为:(第一行为默认)
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games"
export JAVA_HOME=/usr/local/java/jdk1.8.0_152
export JRE_HOME=/usr/local/java/jdk1.8.0_152/jre
export CLASSPATH=$CLASSPATH:$JAVA_HOME/lib:$JAVA_HOME/jre/lib
- 配置用户环境变量:vi /etc/profile
#在原有配置文件中添加以下四句
export JAVA_HOME=/usr/local/java/jdk1.8.0_152
export JRE_HOME=/usr/local/java/jdk1.8.0_152/jre
export CLASSPATH=$CLASSPATH:$JAVA_HOME/lib:$JAVA_HOME/jre/lib
export PATH=$JAVA_HOME/bin:$JAVA_HOME/jre/bin:$PATH:$HOME/bin
- 使用户环境变量生效:source /etc/profile
- 测试是否安装成功:java -version
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Linux安装Tomcat
- 此处以 Tomcat 8.5.23 为例
- 解压缩:tar -zxvf apache-tomcat-8.5.23.tar.gz
- 变更目录名:mv apache-tomcat-8.5.23 tomcat
常用命令:
- 启动:/usr/local/tomcat/bin/startup.sh
- 停止:/usr/local/tomcat/bin/shutdown.sh
- 目录内执行脚本:./startup.sh
- 访问Tomcat前确认已更改安全组,添加自定义规则(开放8080端口)
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# Linux安装MySQL
- 更新数据源:apt-get update
- 安装 MySQL:apt-get install mysql-server
- 查看MySQL状态:systemctl status mysql
- 配置远程访问:
- 修改配置文件:vi /etc/mysql/mysql.conf.d/mysqld.cnf
- 注释掉(语句前面加上 # 即可):#bind-address = 127.0.0.1
- 重启MySQL:service mysql restart / systemctl restart mysql
- 登录MySQL:mysql -u root -p
- 使用MySQL:use mysql
- 更该插件:update user set plugin = "mysql_native_password";
- 修改密码:update user set authentication_string=password('123456') where user='root';
- 刷新:flush privileges;
- 为远程连接添加权限:update user set host = '%' where user ='root';
- 刷新:flush privileges;
- 因弱口令无法成功授权解决步骤:
- 查看和设置密码安全级别
- select @@validate_password_policy;
- set global validate_password_policy=0;
- 查看和设置密码长度限制
- select @@validate_password_length;
- set global validate_password_length=6;
- 常用命令
- 启动:service mysql start
- 停止:service mysql stop
- 重启:service mysql restart
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
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
# VUE
# 概述
- Vue是一套用于构建用户界面的渐进式框架,发布于2014年2月。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库(如:vue-router,vue-resource,vuex)或既有项目整合。
- vue-cli 脚手架:官方提供的一个脚手架,用于快速生成一个 vue 的项目模板
- vue-router 路由:Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。
- vuex 状态管理:Vuex 是一个专为 Vue.js 应用程序开发的 状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
1
2
3
4
5
2
3
4
5
# MVVM
- MVVM(Model-View-ViewModel)是一种软件架构设计模式,是一种简化用户界面的“事件驱动编程方式”。
- MVVM 源自于经典的 MVC(Model-View-Controller)模式。MVVM 的核心是 ViewModel 层,负责转换 Model 中的数据对象来让数据变得更容易管理和使用,其作用如下:
- 该层向上与视图层进行双向数据绑定
- 向下与 Model 层通过接口请求进行数据交互
MVVM 模式和 MVC 模式一样,主要目的是分离视图(View)和模型(Model),有几大好处:
- 低耦合: 视图(View)可以独立于 Model 变化和修改,一个 ViewModel 可以绑定到不同的 View 上,当 View 变化的时候 Model 可以不变,当 Model 变化的时候 View 也可以不变。
- 可复用: 你可以把一些视图逻辑放在一个 ViewModel 里面,让很多 View 重用这段视图逻辑。
- 独立开发: 开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计。
- 可测试: 界面素来是比较难于测试的,而现在测试可以针对 ViewModel 来写。
MVVM 的组成部分:
- View:View是视图层,也就是用户界面。前端主要由 HTML 和 CSS 来构建,为了更方便地展现 ViewModel 或者 Model 层的数据,已经产生了各种各样的前后端模板语言,比如 FreeMarker、Thymeleaf 等等,各大 MVVM 框架如 Vue.js,AngularJS,EJS 等也都有自己用来构建用户界面的内置模板语言。
- Model:Model是指数据模型,泛指后端进行的各种业务逻辑处理和数据操控,主要围绕数据库系统展开。这里的难点主要在于需要和前端约定统一的 接口规则
- ViewModel:ViewModel是由前端开发人员组织生成和维护的视图数据层。在这一层,前端开发者对从后端获取的 Model 数据进行转换处理,做二次封装,以生成符合 View 层使用预期的视图数据模型。
需要注意的是 ViewModel 所封装出来的数据模型包括视图的状态和行为两部分,而 Model 层的数据模型是只包含状态的
比如页面的这一块展示什么,那一块展示什么这些都属于视图状态(展示)
页面加载进来时发生什么,点击这一块发生什么,这一块滚动时发生什么这些都属于视图行为(交互)
视图状态和行为都封装在了 ViewModel 里。这样的封装使得 ViewModel 可以完整地去描述 View 层`。由于实现了双向绑定,ViewModel 的内容会实时展现在 View 层,这是激动人心的,因为前端开发者再也不必低效又麻烦地通过操纵 DOM 去更新视图。
MVVM 框架已经把最脏最累的一块做好了,我们开发者只需要处理和维护 ViewModel,更新数据视图就会自动得到相应更新,真正实现 事件驱动编程。
- View层展现的不是 Model 层的数据,而是 ViewModel 的数据,由 ViewModel 负责与 Model 层交互,这就完全解耦了 View 层和 Model 层,这个解耦是至关重要的,它是前后端分离方案实施的重要一环。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Vue.js 的两大核心要素
数据驱动:
- 当你把一个普通的 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。
- 每个组件实例都有相应的 watcher 实例对象,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会通知 watcher 重新计算,从而致使它关联的组件得以更新。
组件化:
- 页面上每个独立的可交互的区域视为一个组件
- 每个组件对应一个工程目录,组件所需的各种资源在这个目录下就近维护
- 页面不过是组件的容器,组件可以嵌套自由组合(复用)形成完整的页面
1
2
3
4
5
6
7
2
3
4
5
6
7
# 生命周期
Vue 实例有一个完整的生命周期,也就是从开始创建、初始化数据、编译模板、挂载 DOM、渲染→更新→渲染、卸载等一系列过程,我们称这是 Vue 的生命周期。通俗说就是 Vue 实例从创建到销毁的过程,就是生命周期。
在 Vue 的整个生命周期中,它提供了一系列的事件,可以让我们在事件触发时注册 JS 方法,可以让我们用自己注册的 JS 方法控制整个大局,在这些事件响应方法中的 this 直接指向的是 Vue 的实例。
1
2
2
# 钩子函数的触发时机
- beforeCreate:在实例初始化之后,数据观测(data observer) 和 event/watcher 事件配置之前被调用。
- created:实例已经创建完成之后被调用。在这一步,实例已完成以下的配置:数据观测(data observer),属性和方法的运算,watch/event 事件回调。然而,挂载阶段还没开始,$el 属性目前不可见。
- beforeMount:在挂载开始之前被调用,相关的 render 函数首次被调用。
- mounted:el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。
- beforeUpdate:数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。 你可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。
- updated:由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。
当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。
- beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。
- destroyed:Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# Axios
Axios 是一个开源的可以用在浏览器端和 NodeJS 的异步通信框架,她的主要作用就是实现 AJAX 异步通信,其功能特点如下:
- 从浏览器中创建 XMLHttpRequests
- 从 node.js 创建 http 请求
- 支持 Promise API
- 拦截请求和响应
- 转换请求数据和响应数据
- 取消请求
- 自动转换 JSON 数据
- 客户端支持防御 XSRF(跨站请求伪造)
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
Java异步编程 →