Java

发布时间 2023-04-04 18:38:55作者: mosha12

变量

数据类型

基本数据类型

数据类型 长度
byte 1B
short 2B
char 2B
int 4B
long 8B
float 4B
double 8B
boolean 1B true/false

引用数据类型

主要有对象、数组、String

定义方式

1.定义时不赋值

类型标识符 变量名;

int a;

2.定义时赋值

类型标识符 变量名 = 值;

int a = 3;

类型转换

自动类型转换

原则:低精度可以自动转换为高精度,高精度不能转换为低精度

具体使用方式

高精度数据类型 变量名 = 低精度数据类型变量名;

double a;
float b = 3.14;
a = b;

注意:两条路线

  1. byte->short->int->long->float->double
  2. char->int->long->float->double

byte、short不能和char进行转换

强制类型转换

变量名 = (数据类型)(变量名或者表达式);

int a;
float b = 3.14;
float c = 6.28;
a = (int)(b + c);

若只对一个变量进行强转,则变量名可以不加括号

int a;
float b = 3.14;
a = (int)b;

变量的使用

原则:变量需要先赋值再使用

int a = 3;
System.out.println(a);

注意:1.整数常量默认是int类型
2.浮点常量默认是double类型
3.byte(-128127),short(-3276832767),char(0~65535)只要赋值号右边的值不超过其范围就可以使用

short a = 300;
System.out.println(a);

运算符

算术运算符

运算符 作用 注意事项
+ 计算运算符两边的和
- 计算运算符两边的差
* 计算两边的积
/ 计算两边的商 若两边均为整数类型,则值为整数;否则值为小数
% 计算两边的余数 可以用于浮点类型(注意,这里与c语言不同),计算公式为a-a/b*b

关系运算符

值为boolean类型

运算符 作用 注意事项
> 若左边大于右边,则值为true;否则值为false
< 若左边小于于右边,则值为true;否则值为false
== 若左边等于右边,则值为true;否则值为false 注意==和=的区别
!= 若左边不等于右边,则值为true;否则值为false
>= 若左边大于或等于右边,则值为true;否则值为false 只需满足一个条件即可
<= 若左边小于或等于右边,则值为true;否则值为false 只需满足一个条件即可

逻辑运算符

返回的值为boolean类型

运算符 作用 注意事项
& 两边均为true,则返回true,否则返回false 即使左边已经为false,右边还是会执行
| 只要有一边为true,则返回true 即使左边已经为true,右边还是会执行;只有左右两边同时为false,才会返回false
&& 与&相同 若左边已经为false,则右边不会执行
|| 与|相同 若左边已经为true,则右边不会执行
取反,true变false,false变true

位运算符

二进制形式的数据最高位是符号位

1表示负数,0表示正数

原码、反码、补码

数据以二进制形式存储在计算机中
以十进制-8演示数值的原码、反码、补码

原码(数据的二进制形式)

如10001000

反码(数据的二进制形式按位取反)

如11110111

补码(数据的反码加1)

如11111000

常见的位运算符

运算符 作用 注意事项
>> 右移运算符,高位用符号位补齐
<< 左移运算符,低位补零
& 按位与
| 按位或
~ 按位取反
>>> 逻辑右移,高位补零
^ 按位异或,相同位为0,不同为1

注意运算时符号位也参与运算,然后看补码的符号位是正是负,若为正,则直接转化为对应进制即可,若为负,则应转化为对应原码,再转化为对应进制

条件运算符

格式

表达式1?表达式2:表达式3;

使用方法

  1. 判断表达式1的值
  2. 若为真,执行表达式2;否则执行表达式3

赋值运算符

运算符 作用 注意事项
= 将右边的值赋给左边的变量 左边必须是变量,右边可以是常量,变量和表达式
+= a+=b相当于a=a+b
-= a-=b相当于a=a-b
*= a=b相当于a=ab
/= a/=b相当于a=a/b
%= a%=b相当于a=a%b

控制结构

任何程序都是由顺序、分支、循环中的一种或者多种构成的

顺序结构

int a = 10;
System.out.println(a);

分支结构

单分支结构

主要使用if语句进行控制

基本语法

if (表达式) {
语句...
}
注:表达式的值需要是boolean类型的值

执行过程

双分支结构

主要使用if...else...语句进行控制

基本语法

if (表达式) {
语句...
} else {
语句...
}

执行过程

多分支结构

主要使用if...else if...else...或者switch进行控制

基本语法

  1. if (表达式) {

语句...
} else if (表达式) {
语句...
} else {
语句...
}
2.switch (表达式) {
case 常量:
语句...
break;
case 常量:
语句...
break;
case 常量:
语句...
break;
case 常量:
语句...
break;
...
default:
语句...
break;
}

  1. 表达式类型应和case后面的常量类型一致,或者是兼容的类型
  2. 表达式的返回值必须是byte,short,int,char,enum,String

执行过程

1.if...else if...else结构

2.switch结构

嵌套分支

尽量别超过三层

循环结构

while循环

基本语法

while (表达式) {
语句...
循环变量迭代;
}

执行过程

do...while循环

基本语法

do {
语句...
循环变量迭代;
} while(表达式);

执行过程

for()循环

基本语法

for (表达式1; 表达式2; 表达式3) {
语句...
}

执行流程

嵌套循环

一般不要超过三层

break及continue语句

break

用于跳出swithch结构或者循环结构,只能跳出一层

continue

立即结束当前循环,执行下一次循环

数组

数组主要用来存储相同类型的数据

数组的声明

定义数组但不赋值

1.数据类型[] 数组名 = new 数据类型[数组长度];
2.数据类型 数组名[] = new 数据类型[数组长度];

int [] a = new int[10];
int b[] = new int[10];

先定义后赋值

1.数据类型[] 数组名 = new 数据类型[数组长度];
2.数据类型 数组名[] = new 数据类型[数组长度];
通过遍历数组进行赋值

int[] a = new int[10];
for (int i = 0; i < a.length; i++) {
    a[i] = 1;
}
int b[] = new int[10];
for (int i = 0; i < b.length; i++) {
    b[i] = 1;
}

定义时不说明长度

  1. 数据类型[] 数组名 = new 数据类型[];
  2. 数据类型 数组名[] = new 数据类型[];

定义时赋值

  1. 数据类型[] 数组名 = {};
  2. 数据类型 数组名[] = {};
int[] a = {10, 20, 30};
int a[] = {10, 20, 30};

数组的使用

数组名[下标];
注:下标的范围是从0到数组的长度-1。

数组在内存中的表现形式

数组的注意事项

数组是引用数据类型,即数组名存储的是数组在内存中的首地址

int arr[] = {100, 200, 300};
int arr1[] = new int[3];
for (int i = 0; i < arr.length; i++) {
    arr1[i] = arr[i];
}

上图代码是将arr数组中的值赋给arr1数组的对应元素

int arr[] = {100, 200, 300};
int arr1[] = new int[3];
arr1 = arr;

上述代码将arr1指向arr数组,即两者指向同一个内存地址

多维数组—二维数组

二维数组可以理解为其中的每个元素都是一个一维数组

二维数组在内存中的表现形式

使用方式

动态初始化

  1. 类型[][] 数组名 = new 类型[大小][大小]

int a[][] = new int[2][3];

  1. 类型 数组名[][];

数组名 = new 类型[大小][大小]

int[][] arr;
arr = new arr[3][4];
  1. 类型[][] 数组名 = new 类型[大小][];

然后给每个一维数组单独开辟空间即可

int[][] arr = new int[2][];
for (int i = 0; i < arr.length; i++) {
    arr[i] = new int[i + 1];
}

上述代码是初始化了一个两行的数组,第一行是1列,第二行是二列

静态初始化

类型 数组名[][] = {{值1,值2...},{值1,值2...},{值1,值2}};
注意:每行的元素个数可以不同

注意事项

  1. 一维数组的声明方式
  • int[] x
  • int x[]
  1. 二维数组的声明方式
  • int[][] y
  • int[] y[]
  • int y[][]
  1. 二维数组由多个一维数组组成,各个一维数组的长度可以相同,也可以不同

数组的默认值

数据类型 默认值
int 0
short 0
byte 0
long 0
float 0.0
double 0.0
char \u0000
boolean false
String null

类与对象

类与对象的关系示意图

对象在内存中的表现形式


当执行第一条语句时,会加载类信息

属性/成员变量

基本介绍

  1. 成员变量 = 属性 = field
  2. 属性一般是基本数据类型,也可是引用类型(对象,数组)

注意事项和细节

  1. 属性的定义语法通变量

访问修饰符 属性类型 属性名;

  1. 属性的定义类型可以为任意类型,包含基本类型或引用类型
  2. 属性若不赋值,有默认值
    | 数据类型 | 默认值 |
    | --- | --- |
    | int | 0 |
    | short | 0 |
    | byte | 0 |
    | long | 0 |
    | float | 0.0 |
    | double | 0.0 |
    | char | \u0000 |
    | boolean | false |
    | String | null |

如何创建对象

  1. 先声明再创建
Cat cat;
cat = new Cat();
  1. 直接创建
Cat cat = new Cat();

如何访问属性

基本语法

对象名.属性名

cat.name;

类和对象的内存分配机制

java内存结构分析

  1. 栈:一般存放基本数据类型(局部变量)
  2. 堆:存放对象(Cat cat, 数组等)
  3. 方法区:常量池(常量,比如字符串),类加载信息

java创建对象流程简单分析

Person p = new Person();
p.name = "jack";
p.age = 10;
  1. 先加载Person类信息(属性和方法信息,只会加载一次)
  2. 在堆中分配空间,进行默认初始化(看规则)
  3. 把地址赋给p,p就指向对象
  4. 进行指定初始化,比如p.name = "jack" p.age = 10

示意图

成员方法

方法的调用机制


执行完return语句后或者方法执行完后,在栈中的独立空间会被释放

方法调用小结

  1. 当程序执行到方法时,就会开辟一个独立的空间(栈空间)
  2. 当方法执行完毕,或者执行到return语句时,就会返回
  3. 返回到调用方法的地方
  4. 返回后,继续执行方法后面的代码
  5. 当main方法(栈)执行完毕,整个程序退出

成员方法的好处

  1. 提高代码的复用性
  2. 可以将实现的细节封装起来,供其他用户调用

成员方法的定义

public 返回数据类型 方法名 (形参列表) {
语句...
return 返回值;
}

  1. 形参列表:表示成员方法输入
  2. 返回数据类型:表示成员方法输出,void表示没有返回值
  3. 方法主体:表现为了实现某一功能代码块
  4. return语句不是必须的

注意事项和使用细节

访问修饰符(作用时控制方法使用的范围)

如果不写默认访问

返回数据类型

  1. 一个方法最多有一个返回值
  2. 返回类型可以为任意数据类型,包含基本数据类型或引用数据类型
  3. 如果方法要求有返回数据类型,则方法体中最后的执行语句必须为return值;而且返回值类型必须和return的值类型一致或兼容
  4. 如果方法是void,则方法体中可以没有return语句,或者只写return

方法名

遵循驼峰命名法,最好能够见名知义

形参列表

  1. 一个方法可以有0~n个参数,中间用逗号隔开
  2. 参数类型可以为任意类型,包含基本数据类型或引用类型
  3. 调用带参方法时,要对应着参数列表传入相同类型或者兼容类型的实参
  4. 实参和形参的类型要一致或者兼容,个数、顺序必须一致

方法体

方法不能嵌套定义

方法调用

  1. 同一个类中,直接调用即可
  2. 跨类调用,需要通过对象名调用

对象名.方法名(参数)
3.跨类的方法调用和方法的访问修饰符相关

成员方法传参机制

基本数据类型的传参机制

值传递,形参的任何改变都不会影响实参

引用数据类型的传参机制

传递地址,可以通过形参影响实参

方法递归调用

递归的规则

  1. 执行一个方法,就创建一个新的受保护的独立空间(栈空间)
  2. 方法的局部变量是独立的
  3. 当方法中使用的是引用类型变量,会共享该引用类型的数据
  4. 递归必须向退出递归的条件逼近,否则会造成无限递归
  5. 当一个方法执行完毕或者遇到return,就会返回,谁调用,就返回给谁。同时当方法执行完毕或者返回时,该方法也就执行完毕

方法重载(overload)

同一个类中的多个形参列表不同的同名方法

使用细节

  1. 方法名必须相同
  2. 参数列表必须不同(参数类型或个数或顺序至少有一样不同,方法名无要求)
  3. 返回类型无要求

可变参数

类似于数组

基本语法

访问修饰符 返回类型 方法名(数据类型...形参名) {
语句...
}

使用细节

  1. 可变参数的实参可以为0~n个
  2. 可变参数的实参可以是数组
  3. 本质是数组
  4. 若和普通类型的参数一起放在形参列表,必须保证可变参数在最后
  5. 一个形参列表只能出现一个可变参数

作用域

基本使用

  1. 在java中,主要的变量就是属性(成员变量)和局部变量
  2. 局部变量一般指在成员方法中定义的变量
  3. 分类
  • 全局变量:也就是属性,作用域为整个类体
  • 局部变量:只能在定义它的代码块中
  1. 全局变脸那个可以不赋值,直接使用;局部变量必须先赋值后使用

使用细节

  1. 属性和局部变量可以重名,访问时遵循就近原则
  2. 在同一个作用域中,两个局部变量不能重名
  3. 属性跟对象的生命周期一致,局部变量跟代码块生命周期一致
  4. 作用域范围不同
  • 全局变量/属性:可以被本类使用,或其他类使用(通过对象调用)
  • 局部变量:只能在本类中对应的方法中使用
  1. 修饰符不同
  • 全局变量/属性可以加修饰符
  • 局部变量不能加修饰符

构造器/构造方法

基本语法

[修饰符] 方法名(形参列表) {
方法体;
}

  1. 构造器的修饰符可以默认,也可以是其他三个之一
  2. 没有返回值
  3. 和类名字必须一样
  4. 参数列表和成员方法一样的规则
  5. 构造器的调用,由系统完成
  6. 创建对象时,系统自动调用该类的构造器,完成对对象的初始化

注意事项

  1. 一个类可以有多个不同的构造器,即构造器重载
  2. 构造器名要和类名相同
  3. 构造器没有返回值
  4. 完成的是对对象的初始化,并不是创建对象
  5. 创建对象时,系统自动调用该类的构造器,完成对对象的初始化
  6. 构造器不能主动调用
  7. 如果没有定义构造器,系统会给类生成一个默认无参构造器
  8. 一旦定义了自己的构造器,默认的无参构造器会被覆盖,若要使用默认的无参构造器,需要显示的声明一下

对象创建流程分析

this关键字

哪个对象调用,this就代表哪个对象

注意事项

  1. 可以用来访问本类的属性、方法、构造器
  2. 用于区分当前类的属性和局部变量
  3. 访问成员方法的语法:this.方法名(参数列表);
  4. 访问构造器语法:this(参数列表);

只能在构造器中使用(只能在构造器中访问另外一个构造器,且要为构造器的第一条语句)

  1. this不能在类定义的外部使用,只能在类定义的方法中使用

面向对象编程(中级)

IDEA

基本介绍及使用

设置字体

  1. file->settings->Appearance&Behavior->Appearance->Size
  2. file->settings->Editor->Font->Size

字符编码设置

file->Editor->File Encodings->Global Encoding

IDEA快捷键

配置快捷键

file->settings->Keymap

常用快捷键

  1. 删除当前行ctrl+y()自己配置
  2. 复制当前行ctrl+alt+下光标(自己配置)
  3. 补全代码alt+/
  4. 添加注释ctrl+/
  5. 导入该行所需的类alt+enter,在file->settings->Editor->General->Auto Import中勾选Add unambiguous imports on the fly和Optimize imports on the fly后才能使用
  6. 快速格式化代码ctrl alt+L
  7. 快速运行程序alt+r(自己定义)
  8. 生成构造器alt+insert
  9. 查看一个类的层级关系ctrl+h
  10. 将光标放在一个方法上,输入ctrl+b,可以选择定义到哪个类的方法
  11. 自动分配变量名,在后面加.var

模板/自定义模板

file->settings->editior->Live templates

包的作用

  1. 区分相同名字的类
  2. 管理类
  3. 控制访问范围

包基本语法

package com.edu;
注:1. package关键字表示打包
2.com.edu表示包名

包的本质分析(原理)

实际上是创建不同的文件夹来保存类文件

包的命名

命名规则

只能包含数字、字母、下划线、小圆点,不能用数字开头,不能是关键字或者保留字

命名规范

  1. 一般是小写字母加小圆点
  2. com.公司名.项目名.业务模块名

常用的包

包名 作用
java.lang.* 基本包,默认引入,不需要再引入
java.util.* 系统提供的工具包,工具类,不需要再引入
java.net.* 网络包,网络开发
java.awt.* 做java的界面开发,GUI

包的引入

import 包;

  1. import 包名.类名; (引入Scanner类)
  2. import 包名.*;(将该包下的所有类都引入)

注:需要用那个类,就导入那个类

注意事项和使用细节

  1. package的作用是声明当前类所在的包,需要放在类的最上面,一个类中最多只有一句package
  2. import指令放在package的下面,在类定义前面,可以有多句且没有顺序要求

访问修饰符

java提供四种访问控制修饰符号控制方法和属性(成员变量)的访问权限(范围)

访问级别 访问修饰符 同类 同包 子类 不同包
公开 public ✔️ ✔️ ✔️ ✔️
受保护 protected ✔️ ✔️ ✔️
默认 没有修饰符 ✔️ ✔️
私有 private ✔️

注意事项

  1. 只能修饰类中的属性,成员方法以及类
  2. 只有默认和public可以修饰类
  3. 成员方法的访问规则和属性一样

面向对象编程三大特征(封装、继承、多态)

封装

把抽象出的数据(属性)和对数据的操作(方法)封装在一起,数据被保护在内部,程序的其他部分只有通过被授权的操作(方法),才能对数据进行操作。

封装的理解和好处

  1. 隐藏实现细节
  2. 可以对数据进行验证,保证安全合理

封装的实现步骤

  1. 将属性私有化(不能直接修改属性)
  2. 提供一个public修饰的set方法,对属性判断并赋值
public void setXxx(类型 参数名) {
    //数据验证的业务逻辑
    属性 = 参数名;
}
  1. 提供一个public 修饰的get方法,用于获取属性的值
public 数据类型 getXxx() {
    //权限判断
    return xx;
}

封装和构造器结合

public Person(String name, int age, double salary, Stirng job) {
    this.setName(name);
    this.setAge(age);
    this.setJob(job);
    this.setSalary(salary);
}

继承

继承能够提高代码复用性

基本介绍

继承能够提高代码的复用性,当多个类中存在相同的属性和方法时,可以从这些类中抽象出父类,子类只需要通通过extends继承父类

语法

class 子类 extends 父类 {
语句...
}

  1. 子类会自动拥有父类定义的属性和方法
  2. 父类又叫超类,基类
  3. 子类又叫派生类

继承的便利

  1. 提高代码的复用性
  2. 提高代码的扩展性和维护性

继承的细节

  1. 子类继承了父类所有的属性和方法,但子类只能直接访问父类非私有的属性和方法,子类想要访问父类的私有属性和方法需要通过父类提供的公共方法访问
  2. 子类必须调用父类的构造器,完成父类的初始化
  3. 创建子类对象时,默认情况下总会去调用父类的无参构造器,如果父类没有提供无参构造器的,则必须在子类的构造器中用super去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不会通过
  4. 当需要指定调用父类的某个构造器时,使用super(参数列表)指定
  5. super()在使用时,需要放在构造器第一行
  6. super()和this()都只能放在构造器第一行,因此这两个方法不能共存在一个构造器中
  7. 所有类都是Object类的子类
  8. 父类构造器的调用不限于直接父类,将一直向上追溯到Object类(顶级父类)
  9. java是单继承机制

继承的本质


super关键字

super代表父类的引用,用于访问父类的属性、方法、构造器

基本语法

  1. 访问父类的属性,但不能访问父类的私有属性

    super.属性名;

  2. 访问父类的方法,但不能访问父类的私有方法

super.方法名(参数列表);

  1. 访问父类的构造器

super(参数列表);
注:只能放在构造器的第一句,只能出现一句

super的好处及细节

  1. 调用父类构造器,分工明确,父类属性由父类初始化,子类属性由子类初始化
  2. 当子类和父类的成员重名时,若要访问父类成员,必须通过super


super.cal()的顺序是直接查找父类,其他规则一样

  1. 不限于直接父类,遵循就近原则以及访问权限

super和this的比较

区别点 this super
访问属性 先访问本类中的属性,若没有则从父类中继续查找 直接访问父类中的属性
调用方法 先访问本类中的方法,若没有则从父类中继续查找 直接访问父类中的方法
调用构造器 调用本类的构造器,必须放在构造器的首行 调用父类构造器,必须放在子类构造器的首行
特殊 表示当前对象 子类中访问父类对象

方法重写

子类的一个方法和父类的某个方法的名称、返回类型、参数一样

重载和重写的区别

名称 发生范围 方法名 形参列表 返回类型 修饰符
重载(overload) 本类 必须一样 类型,个数或者顺序至少一个不同 无要求 无要求
重写(override) 父子类 必须一样 相同 子类重写的方法返回类型必须和父类相同或者是父类返回类型的子类 不能缩小父类方法的访问范围

多态

方法或对象具有多种形态,多态是建立在封装和继承基础上的

具体表现

  1. 方法的多态(重写和重载)
  2. 对象的多态
  • 对象的编译类型和运行类型可以不一致
  • 编译类型定义对象时,就确定了,不能改变
  • 运行类型可以改变
  • =号左边为编译类型,=右边为运行类型
Animal animal = new Dog();//对象animal的编译类型为Animal,运行类型为Dog
animal = new Cat();//animal的运行类型由Dog变成了Cat,但其编译类型没有改变

注意事项和细节

两个对象(类)需存在继承关系,才能构成多态

向上转型

  1. 本质:父类引用指向子类对象
  2. 语法:父类类型 引用名 = new 子类类型();
  3. 可以调用父类中的所有成员(需要遵守访问权限),不能调用子类特有成员
  4. 最终运行效果看子类的具体体现,调用方法时,是从子类开始查找的

向下转型

  1. 语法:子类类型 引用名 = (子类类型)父类引用;
  2. 只能强转父类引用,不能强转父类对象
  3. 父类的引用必须指向的是当前目标类型的对象
  4. 向下转型后,可以调用子类类型中所有成员,也可以调用父类成员(遵循访问规则)

属性不能重写

instanceof比较操作符

判断对象的运行类型是否为XX类型或XX类型的子类型

java的动态绑定机制

  1. 调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定
  2. 调用对象属性时,没有动态绑定机制

多态的应用

  1. 多态数组(数组的定义类型为父类类型,保存的实际元素为子类类型)
  2. 多态参数(定义的形参类型为父类类型,实参类型允许为子类类型)

Object类详解

equals()方法

==和equals方法的对比
运算符/方法 基本类型 引用类型
== 判断值是否相等 判断地址是否相等
equals() 不能判断基本类型,只能判断引用类型 默认判断地址是否相等,子类中通常重写此方法,用于判断内容是否相等

注意,==只要有基本数据类型,就会根据值进行判断

hashcode()方法

  1. 提高具有哈希结构容器的效率
  2. 两个引用,若指向同一个对象,则哈希值一样
  3. 两个引用,若指向不同对象,哈希值不一样
  4. 哈希值不完全等同于地址
  5. hashcode()方法看情况可能会重写

toString()方法

默认返回全类名(包名加类名)+ @ + 十六进制哈希值,子类通常重写该方法,用于返回对象的属性信息

注意事项
  1. 重写toString方法,打印对象或拼接对象时,都会自动调用该对象的toString形式
  2. 直接输出一个对象时,toString()方法会默认被调用

finalize()方法

对象被回收时,系统会自动调用,可重写

断点调试

断点调试过程中处于运行状态,以对象的运行类型执行

快捷键

面向对象编程(高级)

类变量和类方法

类变量(静态变量)

同一个类所有对象共享的变量

class child {
    private String name;
    public static int totalNum = 0;
}

类变量内存剖析

jdk8以前放在方法区,jdk8以后放在堆中

  1. static变量是同一个类所有对象共享
  2. static变量在类加载时生成
  3. 类变量随着内存加载而创建,没有对象实例也可以访问
关于类变量在内存中的存放地址分析相关文献

类变量的定义

  1. 访问修饰符 static 数据类型 变量名;【比较推荐的定义方法】

public static String name = "abc";

  1. static 访问修饰符 数据类型 变量名;

static public int totalNum = 100;

类变量的访问

  1. 类名.类变量名【推荐】
  2. 对象名.类变量名【静态变量的访问修饰符的访问权限和范围和普通属性一样】

注意事项

  1. 当需要让某个类的所有对象共享一个变量时,可以使用类变量
  2. 类变量该类所有对象共享,实例变量(普通属性)是每个对象独享
  3. 类变量又称为静态变量,实例变量又称为普通变量或非静态变量
  4. 类变量通过类名.类变量名或对象名.类变量名来访问,推荐使用类名.类变量名来访问(两种都需要在满足访问修饰符的访问权限和范围的情况下才能使用)
  5. 实例变量不能通过类名.变量名访问
  6. 类变量在加载时进行初始化,即使没有创建对象,只要类加载了,就可以使用
  7. 类变量的生命周期跟类一致

类方法(静态方法)

当方法中不涉及到任何和对象相关的成员,则可以将方法设计成静态方法,提高开发效率

类方法的定义

  1. 访问修饰符 static 数据返回类型 方法名() {}【推荐】
public static void myWay {
    System.out.println("示例");
}
  1. static 访问修饰符 数据返回类型 方法名() {}
static public void myWay {
    System.out.println("示例");
}

类方法的调用

  1. 类名.类方法名
  2. 对象名.类方法名

两种都需要在满足访问修饰符的访问权限和范围的情况下使用

注意事项

  1. 类方法和类变量都是随着类的加载而加载,将结构信息存储在方法区:
  • 类方法中无this的参数
  • 普通方法中隐含着this的参数
  1. 类方法可以通过类名或者对象名调用
  2. 普通方法和对象有关,需要通过对象名调用,不能通过类名调用、
  3. 类方法中不能使用和对象有关的关键字(this和super),普通方法(成员方法)可以
  4. 类方法(静态方法)中只能访问静态属性或者静态方法
  5. 普通成员方法既可以访问静态成员,也可以访问非静态成员

静态方法只能访问静态成员;非静态方法,两者都可以访问(必须遵守访问权限)

main方法

语法说明

  1. main方法是java虚拟机调用
  2. java虚拟机调用类的main()方法,所以main()方法的访问权限必须是public
  3. java虚拟机在执行main()方法时不必创建对象,所以该方法的访问权限必须是static
  4. 该方法接收String类型的数组参数,该数组中保存执行java命令时传递给所运行的类的参数
  5. java执行的程序 参数1 参数2 参数3

注意事项

  1. 在main()方法中,可以直接调用main方法所在类的静态成员
  2. 不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员

动态传值

代码块(初始化块)

  • 属于类中的成员,类似于方法,将逻辑语句封装在方法体中,通过{}包围起来
  • 和方法不同之处在于没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显式调用,而是加载类时,或创建对象时隐式调用

基本语法

[修饰符] {
代码
};

说明

  1. 修饰符可有可无,若有,只能写static
  2. 代码块分为静态代码块(使用static修饰)和普通代码块(没有static修饰)
  3. 逻辑语句可以为任何逻辑语句(输入、输出、方法调用、循环、判断等)
  4. ;号可以写上,也可以省略

代码块的好处和应用场景

  1. 相当于另一种形式的构造器,可以做初始化操作
  2. 当多个构造器中都有重复的语句,可以抽取到初始化块中,提高代码的复用性
class Movie {
    private String name;
    private double price;
    private String director;
    {
        System.out.println("电影屏幕打开...");
        System.out.println("电影广告...");
        System.out.println("电影开始播放...");
    }
}

注意事项

  1. static代码块(静态代码块)的作用是对类进行初始化,随着类的加载而执行,并且只会执行一次;普通代码块,每创建一个对象,执行一次
  2. 类什么时候被加载
  • 创建对象实例时(new)
  • 创建子类对象实例,父类也会被加载
  • 使用类的静态成员时(静态属性,静态方法)
  1. 普通代码块,在创建对象实例时会被隐式调用,被创建一次,就会调用一次;如果只是使用类的静态成员,普通代码块并不会执行,即类加载时不会调用普通代码块
  2. 创建一个对象时,在一个类的调用顺序
  • 调用静态代码块和静态属性初始化(两者优先级相同,按定义顺序调用)
  • 调用普通代码块和普通属性初始化(两者优先级相同,按定义顺序调用)
  • 调用构造方法
  1. 构造器的最前面隐含了super()和调用普通代码块。静态相关的代码块,属性初始化,在类加载时就执行完毕,优先于构造器和普通代码块执行
  2. 创建一个子类时(继承关系),他们的静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造器的调用顺序如下:
  • 父类的静态代码块和静态属性(优先级相同,按定义顺序执行)
  • 子类的静态代码块和静态属性(优先级相同,按定义顺序执行)
  • 父类的普通代码块和普通属性初始化(优先级相同,按定义顺序执行)
  • 父类的构造方法
  • 子类的普通代码块和普通属性初始化(优先级相同,按定义顺序执行)
  • 子类的构造方法
  1. 静态代码块只能直接调用静态成员,普通代码块可以调用任意成员

单例设计模式

类的单例设计模式,就是采取一定的方法保证在整个软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法

饿汉式

  1. 构造器私有化,防止在类的外部直接创建
  2. 类的内部创建静态对象
  3. 向外暴露一个静态的公共方法
class SingleTon01 {
    private SingleTon01() {}
    private static SingleTon01 instance = new SingleTon01();
    public  static SingleTon01 getInstance() {
        return instance;
    }
}

懒汉式

  1. 构造器私有化
  2. 定义一个静态属性对象
  3. 提供一个public的static方法,可以返回一个对应的类对象
class SingleTon02 {
    private SingleTon02() {}
        private static SingleTon02 instance;
        public static SingleTon02 getInstance() {
            if (instance == null) {
                instance = new SingleTon02();
            }
            return instance;
        }
}

饿汉式与懒汉式的区别

  1. 二者最主要的区别在于创建对象的时机不同:
  • 饿汉式类加载时创建对象实例
  • 懒汉式使用时创建对象实例
  1. 饿汉式没有线程安全问题,懒汉式存在线程安全问题
  2. 饿汉式有可能浪费资源,而懒汉式不存在这个问题
  3. 在javaSE标准类中,java.lang.Runtime就是经典的单例模式

final关键字

可以修饰类、属性、方法和局部变量

应用场景

  1. 不希望类被继承时
  2. 不希望父类的某个方法被子类重写时
  3. 不希望类的某个属性被修改
  4. 不希望某个局部变量被修改时,final修饰的局部变量又被称为局部常量

注意事项

  1. final修饰的属性又叫常量,一般用XX_XX_XX来命名
  2. final修饰的属性在定义时必须赋初值,并且以后不能再修改,可以赋值的位置如下:
  • 定义时
  • 在构造器中
  • 在代码块中
  1. 若final修饰静态属性,则初始化的位置只能是以下几种
  • 定义时
  • 在静态代码块中,不能在构造器中
  1. final类不能继承,但可以实例化对象
  2. 若类不是final类,但含有final方法,则该方法可以被继承,但不能被重写
  3. 若一个类已经是final类,没有必要再将方法修饰成final方法
  4. final不能修饰构造方法(构造器)
  5. final合static搭配使用,效率更高,底层编译器做了优化处理,不会导致类的加载
class Demo {
    public static final in i = 16;
    static {
        System.out.println("韩顺平教育~");
    }
}
  1. 包装类(Integer,Double,Float,Boolean等都是final),String也是final类

抽象类

使用场景

父类的某些方法需要声明,但不确定如何实现,可将其声明为抽象方法,这个类就是抽象类

抽象方法

  1. 没有实现的方法,即没有方法体
  2. 当一个类中存在抽象方法时,需要将该类声明为抽象类
  3. 一般来说,抽象类会被继承,由子类来实现

抽象类的基本介绍

  1. 用abstract关键字修饰的类即为抽象类

访问修饰符 abstract 类名 {
}

  1. 用abstract关键字修饰的方法即为抽象方法

访问修饰符 abstract 返回类型 方法名 (参数列表);//没有方法体

  1. 更多作用在设计层面,设计好后,用子类继承并实现抽象类

注意事项

  1. 抽象类不能被实例化
  2. 抽象类不一定要包含abstract方法,即抽象类可以没有abstract方法,也可以有实现方法
  3. 一旦类包含abstract方法,则该类必须声明为abstract
  4. abstract只能修饰类和方法,不能修饰属性和其它的
  5. 抽象类可以有任意成员
  6. 抽象方法不能有主体,即不能实现(只要有方法体就算实现)
  7. 若一个类继承抽象类,则他必须实现抽象类的所有抽象方法,除非他自己也是抽象类
abstract class E {
    public abstract void hi();
}
class G extends E {
    public void hi() {
        
    }
}
  1. 抽象方法不能使用private、final、static修饰,因为这些关键字修饰的方法不能被重写

抽象类最佳实践-模板设计

使用场景

代码中有大部分重复时

使用方式

  1. 定义抽象类
  2. 在抽象类中定义抽象方法
  3. 在抽象类中定义实现方法
  4. 创建类,继承抽象类
  5. 在子类中重写抽象方法
public abstract class Template {
    public abstract void job();
    public void calculateTimes() {
        long start = System.currentTimeMillis();
        job();
        long end = System.currentTimeMillis();
        System.out.println("执行时间 " + (end - start));
    }
}
public class AA extends  Template{
    //计算任务
    public void job() {

        long num = 0;
        for (long i = 1; i < 10000000; i++) {
            num += i;
        }
    }
}

接口

基本介绍

接口将一些没有实现的方法,封装到一起,到某个类要使用的时候,根据具体情况,把这些方法写出来

基本语法

interface 接口名 {
属性
方法(1. 静态方法 2. 默认实现方法 3. 抽象方法)
}
class 类名 implements 接口 {
自己属性
自己方法
必须实现的接口的抽象方法
}

小结

  1. 在jdk7.0前,接口里的所有方法都没有方法体,即都为抽象方法
  2. jdk8.0后接口类可以有静态方法,默认方法,即接口中可以有方法的具体实现,但需要添加default关键字,静态方法不需要加default,就可以有方法的实现
  3. 接口中,抽象方法可以省略abstract关键字

注意事项

  1. 接口不能被实例化

  2. 接口中的所有方法都是public方法(定义方法时可以省略public),接口中抽象方法可以不用abstract修饰

    void aaa();
    实际上是public abstract void aa();

  3. 一个普通类实现接口,就必须将该接口的所有方法都实现(可以用快捷键alt+enter来快速实现方法)

  4. 抽象类实现接口,可以不用实现接口的方法

interface IA {
    void say();
}

abstract class AA implements IA {
    
}
  1. 一个类同时可以实现多个接口
  2. 接口中的属性只能是final属性,而且是public static final修饰符(且必须初始化)
interface A {
	int a = 1;//相当于public static final int a = 1;
}
  1. 接口中属性的访问形式:接口名.属性名
  2. 接口不能继承其他类,但可以继承多个别的接口
  3. 接口的修饰符只能是默认或者public

接口与继承类的区别

接口是对Java单继承机制的补充

  1. 当子类继承父类,就自动拥有父类的功能
  2. 若子类要扩展功能,可通过接口扩展
  3. 接口和继承解决的问题不同
  • 继承主要解决代码的复用性和可维护性
  • 接口·这要在于设计,设计好各种方法,让其他类去实现
  1. 接口比继承更灵活
  • 继承满足is-a的关系
  • 接口满足like-a的关系
  1. 接口在一定程度上实现代码解耦【接口规范性+动态绑定机制】

接口的多态

  1. 多态参数(接口引用可以指向实现了该接口的类的对象)
public class InterfacePolyParameter {
    public static void main(String[] args) {
        IF if01 = new Monster();
        if01 = new Car();
    }
}

interface IF{}
class Monster implements IF{}
class Car implements IF {}
  1. 多态数组
public class InterfacePolyArr {
    public static void main(String[] args) {
        Usb[] usbs = new Usb[2];
        usbs[0] = new Phone_();
        usbs[1] = new Camera_();
    }
}

interface Usb{}
class Phone_ implements Usb {}
class Camera_ implements Usb {}
  1. 接口存在多态传递现象
public class InterfacePolyPass {
    public static void main(String[] args) {
        IG ig = new Teacher();
        IH ih = new Teacher();
    }
}

interface IH {}
interface IG extends IH {}
class Teacher implements IG {
    
}

内部类

基本介绍

一个类的内部又完整的嵌套了另一个类结构。被嵌套的类称为内部类(inner class),嵌套其他类的类称为外部类(outer class)。是类的第五大成员(属性,方法,构造器,代码块,内部类),最大特点是可以直接访问私有属性,并且可以体现类与类之间的包含关系

基本语法

class Outer {//外部类
class Inner{//内部类
}
}
class Other {//外部其他类(其他类)
}

分类

  1. 定义在外部类局部位置上(比如方法内):
  • 局部内部类(有类名)
  • 匿名内部类(没有类名)
  1. 定义在外部类的成员位置上:
  • 成员内部类(没有static修饰)
  • 静态内部类(使用static修饰)

局部内部类

定义在外部类的局部位置

  1. 可以直接访问外部类的所有成员,包含私有的
  2. 不能添加访问修饰符,相当于局部变量,可以用final修饰
  3. 作用域:仅仅在定义它的方法或代码块中
  4. 局部内部类访问外部类的成员采用直接访问的方式
  5. 外部类访问局部内部类的成员:创建对象,再访问(必须在作用域内)
  6. 外部其他类不能访问局部内部类(因为局部内部类地位是一个局部变量)
  7. 若外部类和局部内部类的成员重名,则遵循就近原则,访问外部类的成员,则可以使用外部类名.this.成员访问

System.out.println("外部类的n2=" + 外部类名.this.n2);外部类名.this相当于外部类的一个对象,即那个对象调用它所在的方法,它就指向哪个对象

匿名内部类

定义在外部类的局部位置,并且没有类名

  1. 本质是类
  2. 内部类
  3. 没有名字(实际有名字,但看不到)
  4. 同时是一个对象

基本语法

new 类或接口(参数列表) {
类体
};

注意事项

  1. 匿名内部类既是一个类的定义,同时其本身也是一个对象。从语法上看,它既有定义类的特征,也有创建对象的特征,因此可以调用匿名内部类的方法
new A() {
    public void cry() {
        System.out.println("hello~");
    }
}.cry();
A a = new A() {
  public void cry() {
    System.out.println("hello~");
  }
};
a.cry();
  1. 可以直接访问外部类的所有成员,包含私有的
  2. 不能添加访问修饰符
  3. 作用域仅在定义它的方法或代码块中,地位相当于局部变量
  4. 匿名内部类访问外部成员,直接访问即可
  5. 外部其他类不能访问匿名内部类
  6. 如果外部类和匿名内部类成员重名,内部类访问的话,默认遵循就近原则,若想访问外部类的成员,则可以使用外部类名.this.成员访问

应用场景

当作实参直接传递,简介高效

public class InnerClassExercise01 {
    public static void main(String[] args) {
        f1(new IL() {
            public void show() {
                System.out.println("这是一副名画");
            }
        });
    }
    public static void f1(IL il) {
        il.show();
    }
}
interface IL {
    void show();
}

成员内部类

定义在外部类的成员位置,并且没有static修饰

  1. 可以直接访问外部类的所有成员,包括私有的
class Outer01 {
    private int n1 = 10;
    public String name = "张三";
    class Innter01 {
        public void say() {
            System.out.println("Outer01的n1=" + n1 + " outer01的name=" + name);
        }
    }
}
  1. 可以添加任意访问修饰符(public,protected,默认,private),地位相当于类的成员
  2. 作用域和外部类其他成员一样
  3. 成员内部类访问外部类的成员,直接访问即可
  4. 外部类访问内部类,创建对象,再访问
  5. 外部其他类访问成员内部类
  • 使用成员内部类的对象实例来访问(相当于把Inner08当作Outer08的一个成员)
Outer08.Inner08 inner08 = outer08.new Inner08();
  • 在外部类中编写一个方法,可以返回Inner08的一个对象实例
public class Outer08 {
	public class Inner08{}
    //以下方法返回一个Inner08的对象实例
    public Inner08 getInner08Instance() {
        return new Inner08();
    }
}
  1. 若外部类成员和内部类成员重名,内部类访问时,默认遵循就近原则,如果想访问外部类的成员,可以使用

外部类名.this.成员去访问

静态内部类

静态内部类定义在外部类成员位置,并且用static修饰

  1. 可以直接访问外部类的所有静态成员,包含私有成员,但非静态成员不能直接访问
  2. 可以添加任意访问修饰符,地位相当于成员
  3. 作用域跟其他成员一样,为整个类体
  4. 静态内部类访问外部类(比如静态属性),直接访问所有静态成员
  5. 外部类访问静态内部类,创建对象,再访问
  6. 外部其他类访问静态内部类,创建一个静态内部类的对象实例,然后调用即可
  7. 若外部类和静态内部类的成员重名时,静态内部类访问时,默认遵循就近原则,若想访问外部类成员,则可以使用外部类名.成员访问

枚举和注解

枚举(enumeration,简写enum)

  1. 是一组常量的集合
  2. 可以理解为一种特殊的类,里面只包含一组有限的特定的对象

实现方式

  1. 自定义实现枚举
  • 步骤:1.构造器私有化 2.本类内部创建一组对象 3.对外暴露对象(通过为对象添加public static final修饰 符) 4.可以提供get方法,但不要提供set()方法
  • 不需要提供setXXX方法,因为枚举对象值通常为只读
  • 对枚举对象/属性使用final static修饰,实现底层优化
  • 名字通常全部使用大写,即常量的命名规范
  • 枚举对象根据需要,可以有多个属性
  1. 使用enum关键字实现枚举
  • 使用关键字enum替代class
  • 常量名(实参列表),注意要跟实参列表要跟构造器参数对应
  • 如果有多个常量对象,使用逗号间隔
  • 如果使用enum实现枚举,要求将定义的常量对象写在最前面
enum Season {
    SPRING("春天", "温暖"), WINTER("冬天", "寒冷");
    private String name;
    private String desc;
    private Season(String name. String desc) {
        this.name = name;
        this.desc = desc;
    }
}
  1. 使用时直接类名.对象名

enum关键字注意事项

  1. 当使用enum关键字开发一个枚举类时,默认会继承Enum类,是一个fianl类
  • javap工具可以对.class文件进行反编译
  1. 使用enum关键字时必须知道调用的哪个构造器
  2. 使用无参构造器创建枚举对象,则实参列表小括号都可以省略
  3. 当有多个枚举对象时,使用,间隔,最后有一个分号结尾
  4. 枚举对象必须放在枚举类的行首
  5. 使用enum关键字后,不能再继承其他类,因为enum会隐式继承Enum类,而Java是单继承机制
  6. 枚举类和普通类一样,可以实现接口

Enum成员方法

  1. toString:Enum类已经重写过,返回当前对象名(常量名),子类可以重写该方法,用于返回对象的属性信息
  2. name:返回当前对象名(常量名),子类中不能重写
  3. ordinal:返回当前对象的位置号,默认从0开始
  4. values:返回当前枚举类中所有的常量,返回值的数据类型为数组,该数组含有定义的所有枚举对象(常量名)
  5. valuesOf:将字符串转换成枚举对象,要求字符串为已有的常量名,否则报异常,所转换成的枚举对象和原来的枚举对象为同一个
  6. compareTo:按位置号比较两个枚举常量,将第一个编号和第二个编号相减得到的值返回

增强for循环

for(循环变量:数组名) {
循环体
}

在switch中使用enum

    	Color color = Color.BLUE;
        color.show();
        switch (color) {
            case RED:
                System.out.println("匹配到红色");
                break;
            case BLUE:
                System.out.println("匹配到蓝色");
                break;
            case BLACK:
                System.out.println("匹配到黑色");
                break;
            case GREEN:
                System.out.println("匹配到绿色");
                break;
            case YELLOW:
                System.out.println("匹配到黄色");
                break;
            default:
                System.out.println("没有匹配到");
        }

注解

  1. 注解(Annotation)也被称为元数据(Metadata)用于修饰解释包、类、方法、属性、构造器、局部变量等数据信息
  2. 不影响程序逻辑,但可以被编译或运行,相当于嵌入在代码中的补充信息
  3. 在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在JavaEE中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗代码和XML配置等

基本的Annotation介绍

使用时要在前面加@符号,并把其当成一个修饰符使用,用于修饰它支持的程序元素

  1. @Override:限定某个方法,是重写父类方法,该注解只能用于方法
  2. @Deprecated: 表示某个程序元素(类、方法等)已过时
  3. @SuppressWarnings:抑制编译器警告

@Override

若写了该注解,编译器就会去检查该方法是否真的重写了父类的方法,若的确重写了,则编译通过,如果没有构成重写,则编译错误

  1. 表示指定重写父类的方法(从编译层面验证),如果没有重写父类方法,则会报错
  2. 并不会决定方法是否构成重写,只是起一个检查作用
  3. 只能修饰方法,不能修饰类、包、属性等
  4. @Target是修饰注解的注解,称为元注解

@interface

表示后面是一个注解类,不是接口,在jdk1.5之后加入

public @interface Override {
    
}

@Deprecated

  1. 表示某个程序元素(类、方法等)已过时
  2. 不推荐使用,但仍可以使用
  3. 可以修饰方法、类、字段、包、参数等
  4. 可以做到新旧版本的兼容和过渡

@SuppressWarnings

  1. 当不希望看到不影响程序运行的警告时,可以使用SuppressWarnings注解来抑制警告信息
  2. SuppressWarnings{""}

{""}中可以写入希望抑制(不显示)的警告信息

  • all:抑制所有警告
  • unchecked:忽略没有检查的警告
  • rawtypes:忽略没有指定泛型的警告
  • unused:忽略没有使用某个变量的警告错误
  • 可以传入一个关于要抑制的警告的数组
  1. 作用范围跟放置位置相关,若放在main方法上,则抑制警告的范围就是main方法
  2. @SuppressWarings可以放在类型、字段、方法、参数、构造器、局部变量上

元注解

修饰注解的注解

元注解的种类

  1. Retention:指定注解的作用范围(SOURCE,CLASS,RUNTIME)
  2. Target:指定注解可以在哪些地方使用
  3. Documented:指定该注解是否会在javadoc体现
  4. Inherited:子类会继承父类注解

@Retention注解

只能用于修饰一个注解定义,用于指定该注解能够保留的时间,该元注解包含一个RetentionPolicy类型的成员变量,使用该元注解时,必须为该成员变量赋值

  • RetentionPolicy.SOURCE:编译器使用后,直接丢弃这种策略的注解
  • RetentionPolicy.CLASS:编译器将注解记录在class文件中,当运行JAVA程序时,jvm不会保留注解,该值为默认值
  • RetentionPolicy.RUNTIME:编译器将把注解记录在class文件中,当运行java程序时,jvm会保留注释,程序可以通过反射获取该注释

@Target

修饰注解定义,用于指定被修饰的注解能修饰哪些程序元素,该元注解包含一个名为value的成员变量

@Documented

用于指定该元注解修饰的注解类将被javadoc工具提取成文档,即在生成文档时,可以看到该注解,定义为Documented的注解必须设置Retention的值为RUNTIME

@Inherited注解

被其修饰的注解将具有继承性,如果某个类使用了被该元注解修饰的注解,则其子类自动具有该注解

异常(Exception)

将可能出现异常的代码块选中,快捷键ctrl alt+t,选择try-catch。如果进行了异常处理,即使出现异常,程序也会继续运行,异常信息可以用getMassage()方法获得

基本概念

java语言中,将程序执行中发生的不正常情况称为异常(开发过程中的语法错误和逻辑错误不是异常)

分类

  1. Error(错误):Java虚拟机无法解决的严重问题。如:JVM系统内部错误,资源耗尽等严重情况。比如:StackOverflowError[栈溢出]和OOM(out of memory),Error是严重错误,程序会崩溃
  2. Exception:其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。例如空指针访问,试图读取不存在的文件,网络连接中断等等,Exception分为两大类:运行时异常[程序运行时发生的异常]和编译时异常[编译时,编译器检查出的异常]

异常体系图

  1. 异常分为运行时异常和编译时异常
  2. 运行时异常,编译器不会要求强制处理。一般是编程时的逻辑错误,java,lang,RuntimeException类以及其子类都是运行时异常
  3. 运行时异常可以不做处理
  4. 编译时异常是编译器要求必须处理的异常

五大运行时异常

  1. NullPointerException空指针异常,当应用程序试图在需要对象的地方使用null时抛出的异常
String name = null;
System.out.println(name.length());
  1. ArithmeticException数学运算异常,当出现异常的运算条件时抛出的异常
  2. ArrayIndexOutOfBoundsException数组下标越界异常,用非法索引(索引为负或大于等于数组大小)访问数组时抛出的异常
int[] arr = new int[4];
System.out.println(arr[4]);
  1. ClassCastException类型转换异常,当试图将对象强制转换为不是实例的子类时抛出的异常
class A {}
class B extends A {}
class C extends A {}
A a = new B();
B b = (B)a;
C c = (C)b;
  1. NumberFormatException数字格式不正确异常,当程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常=>使用异常我们可以确保输入是否满足条件数字
String name = "啦啦啦啦";
int num = Integer.parseInt(name);

编译异常

在编译期间就必须处理的异常,否则代码不能通过编译

  1. SQLException:操作数据库时,查询表可能出现异常
  2. IOException:操作文件时,发生的异常
  3. FileNotFoundException:当操作一个不存在的文件时,发生异常
  4. ClassNotFoundException:加载类,而该类不存在时发生的异常
  5. EOFException:操作文件,到文件末尾,发生异常
  6. IIlegalArgumentException:参数异常

异常处理

基本介绍

当异常发生时,对异常处理的方式

异常处理的方式

  1. try-catch-finally
  • 程序员在代码中捕获发生的异常,自行处理
  • 示意图

  1. throws
  • 将发生的异常抛出,交给调用者(方法)自行处理,最顶级的处理者就是JVM
  • 示意图


jvm处理异常的机制:直接输出异常信息,然后退出程序
注意,如果程序员没有显式处理异常,默认throws

try-catch异常处理

  1. try块用于包含可能出错的代码,catch块用于处理try块中发生的异常,可以根据需要在程序中有多个数量的try...catch块
  2. 基本语法

try {
可疑代码
将异常生成对应的异常对象,传递给catch块
} catch(异常) {
对异常的处理
}
注意,通常是t-c-f,但finally省略也不会有编译错误

注意事项

  1. 若异常发生,则异常发生后面的代码不会执行,直接进入catch块
  2. 若异常没有发生,则顺序执行try的代码块,不会进入到catch
  3. 如果希望不管是否发生异常,都执行某段代码,则使用finally{}
  4. 可以有多个catch语句,捕获不同的异常(进行不同的业务处理),要求父类异常在后,子类异常在前,比如(Exception在后,NullPointerException在前),如果发生异常,只会匹配一个catch
  5. 可以try-finally配合使用,相当于没有捕获异常,程序会直接崩掉。应用场景就是执行一段代码,不管是否发生异常,都必须执行某个业务逻辑,若代码正常,还会继续向下执行,若有异常,在执行完finally后,会直接退出程序
  6. finally的优先级比return高

练习中出现的值得注意的事

小结

  1. 如果没有出现异常,则执行try块中所有语句,不执行catch块中语句,若有finally,最后还需要执行finally里面的语句
  2. 如果出现异常,则try块中异常发生后,try块剩下的语句不再执行,将执行catch块中的语句,如果有fianlly,最后还需要执行finally里面的语句

throws异常处理

基本介绍

  1. 如果一个方法(中的语句执行时)可能生成某种异常,但是并不能确定如何处理这种异常,则此方法应显式地声明抛出异常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理
  2. 在方法声明中用throws语句可以声明抛出异常的列表,throws后面的异常类型可以是方法中产生的异常类型,也可以是该异常类型的父类

注意事项

  1. 对于编译异常,程序中必须处理,比如try-catch或者throws
  2. 对于运行时异常,程序中如果没有处理,默认是throws的方式处理
  3. 子类重写父类的方法时,对抛出异常的规定:子类重写的方法,所抛出的异常类型要么和父类抛出的异常一致,要么为父类抛出的异常的子类型
  4. 在throws过程中,如果有方法try-catch,就相当于处理异常,就可以不必throws

自定义异常

当程序中出现某些”错误“,但该错误信息并没有在Throwable子类中描述处理,这个时候可以自己设计异常类,用于描述该错误信息

步骤

  1. 定义类:自定义异常类名继承Exception或RuntimeException
  2. 若继承Exception,属于编译异常
  3. 若继承RuntimeException,属于运行异常(一般来说,继承RuntimeException)

throw和throws的区别

意义 位置 后面跟的东西
throws 异常处理的一种方式 方法声明处 异常类型
throw 手动生成异常对象的关键字 方法体中 异常对象

常用类

包装类(Wrapper)

分类

  1. 针对八种基本数据类型相应的引用类型-包装类
  2. 有了类的特点,就可以调用类中的方法
    | 基本数据类型 | 包装类 |
    | --- | --- |
    | boolean | Boolean |
    | char | Character |
    | byte | Byte |
    | short | Short |
    | int | Integer |
    | long | Long |
    | float | Float |
    | double | Double |

表中高亮部分的父类为Number

包装类和基本数据类型的转换

  1. jdk5前的手动装箱和拆箱方式,装箱:基本类型->包装类型,反之,拆箱
//手动装箱
int n1 = 100;
Integer integer = new Integer(n1);//Integer integer1 = Integer.valueOf(n1);
//手动拆箱
int i = integer.intValue();
  1. jdk5后(含jdk5),自动装箱和拆箱
//自动装箱
int n2 = 200;
Integer integer2 = n2;//底层使用Integer.valueOf(n2)
//自动拆箱
int n3 = integer2;//底层仍然使用intValue()方法
  1. 自动装箱底层调用valueOf方法,比如Integer.valueOf()

包装类型和String类型的相互转换

包装类->String

Integer i = 100;
//方式1
String str1 = i + "";
//方式2
String str2 = i.toString();
//方式3
String str3 = String.valueOf(i);

String->包装类

String str4 = "12345";
//方式1
Integer i2 = Integer.parseInt(str4);
//方式2
Integer i3 = new Integer(str4);

包装类的常用方法

方法 作用
Integer.MIN_VALUE 返回最小值
Integer.MAX_VALUE 返回最大值
Character.isDigit('a') 判断是不是数字
Character.isLetter('a') 判断是不是字母
Character.isUpperCase('a') 判断是不是大写
Character.isLowerCase('a') 判断是不是小写
Character.isWhitespace('a') 判断是不是空格
Character.toUpperCase('a') 转成大写
Character.toLowerCase('A') 转成小写

String类

  1. String对象用于保存字符串,也就是一组字符序列
  2. 字符串常量对象是用双引号括起的字符序列
  3. 字符串的字符使用Unicode字符编码,一个字符(不区分字母还是汉字)占两个字节
  4. String类较常用构造方法
  • String s1 = new String();
  • String s2 = new String(String original);
  • String s3 = new String(char[] a);
  • String s4 = new String(char[] a,int startIndex,int count)
  • String s5 = new String(byte b)
  1. String类实现了Serializable接口,这意味着String类可以串行化,即可以在网络上传输
  2. String类实现了Comparable接口,这意味着String对象可以比较大小
  3. String类是一个final类,即String类不能被继承
  4. String类有一个value属性,是一个char类型的数组,用于存储字符串内容,且value数组是一个final类型的,指向的地址不可以修改,但里面的值是可以修改的
  5. String类的equals方法经过重写,比较的是内容

String对象的两种创建方式

方式一:直接赋值String s = "hspedu";

先从常量池查看是否有"hspedu"数据空间,如果有,直接指向;如果没有则重新创建,然后指向。s最终指向的是常量池的空间地址

方式二:调用构造器String s = new String("hspedu");

先在堆中创建空间,里面维护了value属性,指向常量池的hspedu空间。如果常量池没有"hspedu",重新创建,如果有,直接通过value指向。最终指向的是堆中的空间地址

String对象特性

  1. String对象是一个final类,代表不可变的字符序列
  2. 字符串是不可变的,一个字符串对象一旦被分配,其内容是不可变的
  3. 当进行连接操作且赋值号的右边是字符串常量时,会直接创建一个已经连接好的字符串对象,以下两行代码等价

String a = "hello" + "abc";
String a = "helloabc";

  1. 以下代码的执行过程和内存示意图如图所示
String a = "hello";
String b = "abc";
String c = a + b;


即当连接对象是两个String类型的变量时,会在堆中开创一个新空间。
上述代码的底层逻辑如下

StringBuilder sb = new StringBuilder();
sb.append(a);
sb.append(b);

sb是在中,并且append是在原来字符串的基础上追加的。
一个非常重要的规则,String c1 = "ab" + "cd";常量相加,看的是String c1 = a + b;变量相加,是在堆中

String常用方法

  • equals,区分大小写,判断内容是否相等
  • equalsIgnoreCase,判断内容是否相等,忽略大小写
  • length,获取字符的个数,即字符串的长度
  • indexOf,获取字符或字符串在字符串中第一次出现的索引,索引从0开始,如果找不到,返回-1
  • lastIndexOf,获取字符或字符串在字符串中最后一次出现的索引,索引从0开始,若找不到,返回-1
  • substring,截取指定范围的子串
String name = "hello,张三";
name.substring(6);//从索引为6的字符截取,一直截到最后一个字符
name.substring(2,5);//从索引2开始截取,截取到索引5(不包含索引5)
  • trim,去前后空格
  • charAt,获取某索引处的字符,注意不能使用Str[index]这种方式
  • toUpperCase,把字符串转成大写字符
  • toLowerCase,把字符串转成小写字符
  • concat,拼接字符串
  • replace,替换字符串中的字符
s1 = "宝玉 and 林黛玉 林黛玉 林黛玉";
s1 = s1.replace("林黛玉","薛宝钗");//该代码将字符串中的林黛玉替换成薛宝钗
  • split,分割字符串,返回一个·数组,特殊字符需要使用转义字符
  • compareTo,比较两个字符串的大小,前者大则返回正数,后者大则返回负数,相等则返回0
  • toCharArray,将字符串转成字符数组
  • format,通过占位符(%s,%d,%f,%c)来控制格式,占位符由后面的变量替换

%s表示由字符串来替换
%d表示由整数替换
%.2f表示由小数来替换,替换后只会替换小数点两位,并且进行四舍五入的处理
%c由char类型替换

String name = "join";
int age = 10;
double score = 56.857;
char gender = '男';
String info = "我的姓名是%s,年龄是%d,成绩是%.2f,性别是%c,希望大家喜欢我";
String info2 = String.format(info, name, age, score, gender);
System.out.println(info2);

StringBuffer类

  1. java.lang.StringBuffer代表可变的字符序列,可以对字符串内容进行增删
  2. 很多方法与String类相同,但StringBuffer是可变长度的
  3. StringBuffer是一个容器
  4. StringBuffer的直接父类是AbstractStringBuilder
  5. StringBuffer实现了Serializable,即StringBuffer的对象可以串行化
  6. StringBuffer的内容存放在父类的char类型的value数组中,而且该value数组没有final修饰,因此该数组的内容存放在堆中的空间里
  7. StringBuffer是一个final类,不能被继承
  8. 因为StringBuffer字符内容存放在char[] value,所有变化不用每次都更换地址(不用每次都创建新的对象),所以效率高于String

String类与StringBuffer类的区别

  1. String保存的是字符串常量,里面的值不能更改,每次String类的更新实际上是更改地址,效率较低
  2. StringBuffer保存的是字符串变量,里面的值可以更改,每次StringBuffer的更新实际上可以更新内容,不用每次更新地址,效率较高

StringBuffer的构造器

  1. StringBuffer(),该构造器会创建一个大小为16的字符数组,用于存放字符内容
  2. StringBuffer(CharSequence seq),构造一个字符串缓冲区,包含与传入的字符串相同的字符
  3. StringBuffer(int capacity),构造一个指定大小的字符数组
  4. StringBuffer(String str),构造一个str.length+16长度的字符数组,并将该数组的内容初始化为str

String类与StringBuffer类的相互转换

  1. String->StringBuffer
String str = "hello tom";
//方式1
StringBuffer stringBuffer = new StringBuffer(str);//该方式对str没有影响
//方式2
StringBuffer stringBuffer1 = new StringBuffer();
stringBufer1 = stringBuffer1.append(str);//该方式对str没有影响

  1. StringBuffer->String
StringBuffer stringBuffer3 = new StringBuffer("韩顺平教育");
//方式1
String s = StringBuffer3.toString();
//方式2
String s1 = new String(stringBuffer3);

StringBuffer类的常用方法

  1. append,追加之后,返回的是StirngBuffer类型
  2. delete(start,end),删除索引大于等于start,小于end的字符
  3. replace(start,end,string),将start---end间的内容替换掉,不包含end
  4. indexOf,查找子串在字符串第一次出现的索引,若找不到返回-1
  5. insert

s.insert(9,"赵敏");
在索引为9的位置插入”赵敏“,原来索引为9的内容自动后移

  1. length

StringBuilder类

  1. 一个可变的字符序列,此类提供一个与StringBuffer兼容的API,但不保证同步(StringBuilder不一定线程安全)。该类被设计用作StringBuffer的一个简易替换,用在字符串缓冲区被单个线程使用的时候。如果可能,建议优先采用该类,因为在大多数实现中,它比StringBuffer要快
  2. 在StringBuilder上的主要操作是append和insert方法,可重载这些方法,以接受任意类型的数据
  3. 能够使用的方法跟StringBuffer相同
  4. 继承了AbstractStringBuilder类
  5. 实现了Serializable接口,说明StringBuilder的对象可以串行化(即该对象可以网络传输,也可以保存在文件中)
  6. 是一个final类,不可以被继承
  7. StringBuilder类的对象字符序列也是存放在AbstractStringBuilder的char[] value数组中,因此它的字符序列存放在堆中
  8. StringBuilder的方法没有作互斥处理,即没有synchronized关键字,因此在单线程的情况下使用StringBuilder

String、StringBuffer和StringBuilder的比较

  1. StringBuilder和StringBuffer非常类似,均代表可变的字符序列,而且方法也一样
  2. String是不可变字符序列,效率低,但是复用率高
  3. StringBuffer是可变字符序列,效率较高(增删),线程安全
  4. StringBuilder是可变字符序列,效率最高,线程不安全

注意事项

String s = "a";创建了一个字符串
s += "b";实际上原来的"a"字符串对象已经被丢弃了,现在又产生了一个字符串s+"b"(也就是"ab")。如果多次执行这些改变串内容的操作,会导致大量副本字符串对象驻留在内存中,降低效率。如果这样的操作放到循环中,会极大影响程序的性能==》如果我们对String作大量修改,不要使用String

String,StringBuffer,StringBuilder类的选择原则

  1. 如果字符串存在大量的修改操作,一般使用StringBuffer或StringBuilder
  2. 如果字符串存在大量的修改操作,并在单线程的情况下,使用StringBuilder
  3. 如果字符串存在大量的修改操作,并在多线程的情况下,使用StringBuffer
  4. 如果字符串很少修改,被多个对象引用,使用String,比如配置信息等

Math类

主要用于数学运算,如初等指数、对数、平方根和三角函数等

常用方法(基本都是静态方法)

  1. abs 绝对值
  2. pow 求幂
  3. ceil 向上取整,返回的值为double类型,该值为大于等于该参数的最小整数(double)
  4. floor 向下取整,返回的值为double类型,该值为小于等于该参数的最大整数(double)
  5. round 四舍五入,Math.floor(该参数+0.5)
  6. sqrt 求开方,参数需要为正数
  7. random 求随机数,返回的值大于等于0,小于1
//获取a-b之间的一个随机整数,a、b均为整数
//(int)(a + Math.random() * (b - a + 1))返回的数大于等于a且小于等于b
System.out.println((int)(2 + Math.random() * (7 - 2 + 1)));//输出一个大于等于2且小于等于7的随机整数
  1. max 求两个数的最大值
  2. min 求两个数的最小值

Arrays类

包含了一系列用于管理或操作数组(比如排序和搜索)的静态方法

常用方法

  1. toString 返回数组的字符串形式
  2. sort排序(自然排序和定制排序)
  • 可以通过传入Comparator接口实现定制排序
  • 调用定制排序时,传入了两个参数
    • 排序的数组
    • 实现了Comparator接口的匿名内部类
  • 该代码体现了一种接口编程的方式该代码返回的值大于0还是小于0,会决定sort方法使数组升序还是降序
//该代码使数组降序,若返回i1-i2,则按升序排列;若返回i2-i1,则降序排列
Arrays.sort(arr, new Comparator() {
    public int compare(Object o1, Object o2) {
        Integer i1 = (Integer)o1;
        Integer i2 = (Integer)o2;
        return i2 - i1;//返回的值会影响排序结果,若大于0,则按升序排列;若小于0,则按降序排列。
    }
});
  1. binarySearch 通过二分搜索法进行查找,要求必须排好序

int index = Arrays.binarySearch(arr,3);

  • 若该数组是无序,则不能使用
  • 若数组中不存在该元素,则返回该元素应该存在的位置加1然后加个负号
  • 若数组中存在该元素,则返回该元素的索引
  1. copyOf 数组元素的复制,第一个参数是要拷贝的数组,第二个元素指要拷贝的元素个数;如果要拷贝的长度大于要拷贝的数组的长度,则会拷贝一个null;如果拷贝长度小于0,则抛出异常NegativeArraySizeException

Integer[] newArr = Arrays.copyOf(arr, arr.length);

  1. fill 数组元素的填充
Integer[] num = new Integer[]{9,3,2};
Arrays.fill(num, 99);//该语句的含义是使用99去填充num数组,即替换原来的元素
  1. equals 比较两个数组元素内容是否完全一致

boolean equals = Arrays.equals(arr,arr2);

  1. asList 将一组值,转换成list集合
//1. asList方法,会将2,3,4,5,6,1转成一个集合
//2. 返回的集合编译类型为List(接口)
//3. asList的运行类型为 java.util.Arrays#ArrayList,,即Arrays类的一个静态内部类
List<Integer> asList = Arrays.asList(2,3,4,5,6,1);
System.out.println("asList=" + asList);

System类

常用方法

  1. exit 退出当前程序
    1. exit(0)表示程序退出
    2. 0表示正常状态
  2. arraycopy 复制数组元素,比较适合底层调用,一般使用Arrays.copyOf完成复制数组
    1. 第一个参数为源数组
    2. 第二个参数代表从那个元素开始拷贝
    3. 第三个参数为目标数组
    4. 第四个参数指把数据拷贝到目标数组的那个位置
    5. 第五个参数指从源数组拷贝多少个元素到目标数组
int[] src = {1, 2, 3};
int[] dest = new int[3];
System.arraycopy(src, 0, dest, 0, 3);
  1. currentTimeMillens 返回当前时间距离1970-1-1的毫秒数
  2. gc 运行垃圾回收机制 System.gc();

BigInteger类和BigDecimal类

  1. BigInteger类适合保存比较大的整型
    1. BigInteger类不能直接进行加减乘除,但可以通过相应的方法实现上述运算
    2. 可以创建一个要操作的BigInteger类,然后进行相应操作
    3. 可以使用字符串进行构建BigInteger b1 = new BigInteger("10000000");
  2. BigDecimal适合保存精度更高的浮点型(小数)
    1. 如果对BigDecimal进行运算,需要使用相应方法
    2. 创建一个需要操作的BigDecimal类,然后调用相应方法即可
    3. 进行除法运算时可能会因为除不尽抛出异常ArithmeticException
    • 解决方法:在调用divide方法时指定精度即可bigDecimal.divide(bigDeimal2,BigDecimal.ROUND_CEILING);若有无限小数,则会保留到分子的精度

常用方法

  1. add 加
  2. substract 减
  3. multiply 乘
  4. divide 除

日期类

第一代日期类

  1. Date:精确到毫秒,代表特定的瞬间

    Date d1 = new Date();

    1. 获取当前系统时间
    2. 这里的Date类在java.util包
    3. 默认输出的日期格式是国外的方式,通常需要对格式进行转换
    4. 可以通过指定毫秒数获取时间Date d2 = new Date(9234567);
    5. 可以把一个格式化的字符串转成对应的Date
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss E");
String s = "1996年01月01日 10:20:30 星期一";
Date parse = sdf.parse(s);//此处可能会出现ParseException,直接抛出即可
  1. SimpleDateFormat:格式和解析日期的类,允许进行格式化(日期->文本)、解析(文本->日期)和规范化

//1. 创建SimpleDateFormat对象,可以指定相应的格式
//2. 使用的字母是规定好的,不能乱写
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss E");
String format = sdf.format(d1);//这个是将原日期格式转换成指定的日期格式
/*将String转换成对应的Date*/
//1. 得到的Date在输出时,按照国外的格式,如果希望按照指定格式输出,需要转换
//2. 在把String->Date,使用的sdg格式需要和你给的String格式一样,否则会抛出转换异常
String s = "1996年01月01日 10:20:30 星期一";
Date parse = sdf.parse(s);

第二代日期类

  1. 主要就是Calendar类(日历)
  2. Calendar类是一个抽象类,为特定瞬间与一组诸如YEAR、MONTH、DAY_OF_MONTH、HOUR等日历字段之间的转换提供了一些方法,并为操作日历字段(例如获得下星期的日期)提供了一些方法
  3. Calendar是一个抽象类,并且构造器是private
  4. 可以通过getInstance方法来获取实例
  5. 提供了大量的方法和字段
  6. Calendar在返回月的时候,是按照0开始编号,所以要加1
  7. 该类没有提供对应的格式化列,所以需要自己组合输出
  8. 其拿到的小时为十二进制,若要转成二十四进制,可以采用以下方式Calendar.HOUR_OF-DAY

第三代日期类

前两代日期类的不足

JDK1.0中包含了一个java.util.Date类,但是其大多数方法在JDK1.1导入Calendar类后就被弃用了,而Calendar类存在的问题有:

  1. 可变性:日期和时间这样的类应该是不可变的
  2. 偏移性:Date中的年份从1900开始,月份却从0开始
  3. 格式化:格式化只对Date有用,对Calendar没用
  4. 线程不安全,不能处理闰秒(每隔两天,多出一秒)等

常见方法

  1. LocalDate、LocalTime、LocalDateTime在JDK8加入
    1. LocalDate只包含日期,可以获取日期字段
    2. LocalTime只包含时间,可以获取时间字段
    3. LocalDateTime包含日期加时间,可以获取日期和时间字段
/* 
   1. 使用now()返回表示当前日期时间的对象
   2. 有对应的到到年月日的方法
   3. getMonth方法返回英文,getMonthValue返回数字
*/
LocalDateTime ldt = LocalDateTime.now();
  1. DateTimeFormatter格式日期类,跟SimoleDateFormat类使用方式类似
LocalDateTime dtf = DateTimeFormatter.ofPattern(格式);
String str = dtf.format(日期对象);
  1. Instant 时间戳
    1. 类似于Date
    2. 提供了一系列和Date类转换的方式
//通过静态方法 now() 获取表示当前时间戳的对象
Instant instant = Instant.now();
//Instant->Date
Date date = Date.from(instant);
//Date->Instant
Instant instant = date.toInstant();
  1. 其他方法
  • LocalDateTime类
  • MonthDay类:检查重复事件
  • 使用plus方法增加时间的某个部分
  • 使用minus方法测试查看一年前和一年后的日期

还有很多,根据需要查找手册即可,在开发时尽量使用第三类日期

集合

特点

  1. 可以动态保存任意多个元素,数据类型也可不同
  2. 提供了一系列方便的操作对象的方法:add、remove、set、get等
  3. 使用集合添加,删除新元素的代码简洁明了
  4. 集合主要分为两组(单列集合,双列集合)
  5. Collection接口有两个重要的子接口(List,Set),他们的实现子类都是单列集合(集合里存放的是单个单个的元素)
  6. Map接口的实现子类是双列集合,存放的是键值对

集合的框架体系

Collection接口的实现子类

Map接口的实现子类

Collection接口

Collection接口实现类的特点

  1. Collection实现子类可以存放多个元素,每个元素可以是Object
  2. 有些Collection的实现类,可以存放重复的元素,有些不可以
  3. 有些Collection的实现类,有些是有序的(List),有些不是有序(Set)
  4. Collection接口没有直接的实现子类,是通过它的子接口Set和List来实现的

Collection接口的常用方法

  1. add:添加单个元素
  2. remove:删除指定元素,若传入的参数为索引,则返回的是被删除的元素;若传入的参数为元素,则返回布尔值
  3. contains:查找元素是否存在,返回布尔值
  4. size:获取元素个数
  5. isEmpty:判断是否为空,返回布尔值
  6. clear:清空
  7. containsAll:查找多个元素是否都存在,参数为一个集合
  8. removeAll:删除多个元素,参数为一个集合
  9. addAll:添加多个元素,参数为一个集合

Collection接口遍历元素的方式

使用Iterator(迭代器)

  1. Iterator对象称为迭代器,主要用于遍历Collection集合中的元素
  2. 所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象,即可以返回一个迭代器
  3. Iterator的结构



图中it都为iterator

  1. Iterator仅用于遍历集合,其本身并不存放对象
  2. 使用itit可快速生成while循环遍历,使用ctrl+j可显示所有的模板
  3. 当迭代器退出while循环后,则是iterator迭代器指向最后的元素
  4. 若希望再次遍历,则需要重置迭代器

使用增强for循环(实际上是迭代器的简化版)

Collection cojl = new ArrayList();

for (Object book : col) {
    System.out.println("book=" + book);
}
  1. 可以用于遍历Collection集合
  2. 底层仍然是迭代器
  3. 输入I即可生成

List接口

List接口是Collection接口的子接口

  1. List集合类中元素有序(即添加顺序和取出顺序一致),且可重复
  2. List集合中的每个元素都有其对应的顺序索引,即支持索引list.get(index);
  3. List容器中的元素都对应一个·整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素
  4. List接口的实现类常用的有:ArrayList,LinkedList和Vector

常用方法

List集合里添加了一些根据索引来操作集合元素的方法

  1. void add(int index, Object ele):在index位置插入ele元素
  2. boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素插入进来
  3. Object get(int index):获取指定index位置的元素
  4. int indexOf(Object obj):返回obj在集合中首次出现的位置
  5. int lastIndexOf(Object obj):返回obj在集合中最后一次出现的位置
  6. Object remove(int index):移除指定index位置的元素,并返回此元素
  7. Object set(int index, Object ele):设置指定index位置的元素为ele,相当于替换
  8. List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合,前闭后开

List的三种遍历方式

  1. 使用iterator
Iterator iter = col.iterator();
while (iter.hasNext()) {
    Object o = iter.next();
}
  1. 使用增强for循环
for (Object o : col) {
    
}
  1. 使用使用普通for
for (int i = 0; i < list.size(); i++) {
    Object object = list.get(i);
    System.out.println(object);
}

注意:使用LinkedList完成,使用方式和ArrayList一样

ArrayList

  1. 可以放所有的元素,甚至包括null元素
  2. ArrayList由数组来实现数据存储
  3. ArrayList基本等同于Vector,ArrayList是线程不安全(执行效率高)的,在多线程情况下,不建议使用ArrayList
ArrayList底层操作机制源码分析
  1. ArrayList中维护了一个Object类型的数组elementDatatransient Object[] elementDate,transient表示该属性不会被序列化
  2. 当创建ArrayList对象时,若使用的是无参构造器,则初始elementData容量为0,第一次添加,则扩容elementData为10,若需再次扩容,则扩容elementData为1.5倍
  3. 若使用的是指定大小的构造器,则初始elementData容量为指定大小,若需再次扩容,则直接扩容为1.5倍

Vector

  1. 底层是一个对象数组protected Object[] elementData;
  2. 线程同步(安全),其操作方法有synchronized修饰
public synchronized E get(int index) {
    if (index >= elementCount)
        throw new ArrayIndexOutOfBoundsException(index);
    return elementData(index);
}
  1. 在开发中,需要线程同步安全时,考虑使用Vector

Vector和ArrayList的比较

底层结构 版本 线程安全(同步)效率 扩容倍数
ArrayList 可变数组 jdk1.2 不安全,效率高
  1. 扩容如果有参构造,则按1.5倍进行
  2. 若为无参构造器:
    1. 第一次扩为10
    2. 扩容从第二次开始按1.5倍
      |
      | Vector | 可变数组 | jdk1.0 | 安全,效率不高 |
  3. 如果是无参,默认10,满了之后,按2倍扩容
  4. 如果是有参构造,则直接按2倍扩
    |

LinkedList底层结构

  1. 底层实现了双向链表和双端队列
  2. 可以添加任意元素(元素可以重复),包括null
  3. 线程不安全,没有实现同步
底层操作机制
  1. 底层维护了一个双向链表
  2. 维护了两个属性first和last分别指向首节点和尾节点
  3. 每个节点(Node对象),里面又维护了prev、next、item三个属性,通过prev指向前一个,通过next指向后一个节点,最终实现双向链表
  4. LinkedList的元素的添加和删除,不是通过数组完成的,相对来说效率较高
  5. 若调用无参构造器,则first和last都为null

ArrayList和LinkedList的比较

底层结构 增删的效率 改查的效率
ArrayList 可变数组 较低,通过数组扩容的方式 较高
LinkedList 双向链表 较高,通过链表增加 较低
  1. 改查操作较多,则选择ArrayList
  2. 增删操作较多,选择LinkedList
  3. 一般来说,在程序中,80%-90%都是查询,因此大部分情况下会选择ArrayList
  4. 在一个项目中,根据业务灵活选择,有可能会出现一个模块使用的是ArrayList,另一个模块是LinkedList

Set接口

  1. 无序(添加和取出的顺序不一致),没有索引
  2. 取出的顺序是固定的
  3. 不允许重复元素,所以最多包括一个null
  4. Set的实现类中常用的有HashSet和TreeSet

常用方法

和List接口一样,Set接口也是Collection接口的子接口,常用方法和Collection接口一样

遍历方式

同Collection的遍历方式一样,因为Set接口是Collection接口的子接口

  1. 可以使用迭代器
  2. 增强for
  3. 不能使用索引的方式来获取

HashSet

  1. HashSet实现了Set接口
  2. HashSet实际上是HashMap,HashMap底层是数组+链表+红黑树,HashSet的源码如下
public HashSet() {
    map = new HashMap<>();
}
  1. 可以存放null值,但是只能有一个null
  2. HashSet不保证元素是有序的,取决于hash后,再确定索引的结果。(即不保证存放元素的顺序和取出顺序一致)
  3. 不能有重复元素/对象
  4. 在执行add方法后,会返回一个布尔值,若添加成功,返回true,若失败,返回false
  5. 可以通过remove删除对象
HashSet扩容机制
  1. HashSet底层是HashMap(里面维护着的是单向链表)
  2. 添加一个元素时,先得到hash值,然后转换成索引值
  3. 找到存储数据表table,看这个索引位置是否已经存放元素
  4. 如果没有,直接加入
  5. 如果有,调用equals比较,若相同,则放弃添加;若不相同,则添加到最后
  6. 在Java8中,若一条链表的元素个数等于TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树);若一条链表的元素个数到达八个但table的大小没有到达64时,则会对table表进行扩容,table表按两倍进行扩容
  7. 第一次添加时,table数组扩容到16,临界值(threshold)是16*加载因子(loadFactor=0.75)=12
  8. 若table数组到了临界值,就会扩容,临界也会发生改变(临界值=table的当前容量*0.75)
  9. 在Java8中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树),否则仍然继续对table进行扩容
  10. 只要是加入一个节点,不管是加在table表的某一个位置还是table表的某一条链表上,size都会增1
HashSet源码解读(以以下代码为例)
public class HashSetSource {
    public static void main(String[] args) {
        HashSet hashSet = new HashSet();
        hashSet.add("java");
        hashSet.add("php");
        hashSet.add("java");
        System.out.println("set=" + hashSet);
    }
}
  1. 首先执行HashSet()
public HashSet() {
    map = new HashMap<>();
}
  1. 执行add()方法
public boolean add(E e) {//e = "java"
    return map.put(e, PRESENT) == null;//PRESENT = (static) PRESENT = new Object();实际起一个占位的作用
}
  1. 执行put()方法,该方法会执行hash(key)方法,得到key对应的hash值(算法:h = key.hashCode() ^ (h >>> 16),即这个hash值跟真正的hashcode不同)
public V put(K key, V value) {//key
    resturn putVal(hash(key), key, value, false, true);
}
  1. 执行putVal()方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;//定义了辅助变量
         //table是HashMap的一个数组,类型是Node[]
         //if语句表示如果当前table为null,或者大小=0
         //就会第一次扩容到16个空间
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        //(1)根据key,得到,去计算该key应该存放到table表的哪个索引位置
        //并把这个位置的对象,赋给p
        //(2)判断p是否为null
        //(2.1)若p为null,表示还没存放过元素,就创建一个NOde(key="java",value=PRESERNT)
        //(2.2)就放在该位置,tab[i]=newNode(hash, key, value, null);
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            //在需要局部变量(辅助变量)时候,再创建
            Node<K,V> e; K k;
            //如果当前索引位置对应的链表的第一个元素和准备添加的key的hash值一样
            //并且满足以下两个条件之一:
            //(1)准备加入的key和p指向的Node节点的key是同一个对象
            //(2)p指向的Node节点的key的equals()和准备加入的key比较后相同
            //就不能加入
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            //再判断p是不是一颗红黑树,
            //如果是一颗红黑树,就调用putTreeVal()方法,进行添加
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {//如果table对应的索引位置,已经是一个链表,就使用for循环比较
                  //(1)依次和该链表的每一个元素比较后,都不相同,则加入到该链表的最后
                  //	注意在把元素添加到链表后,立即判断该链表是否已经达到8个节点
                  //	,如果达到8个,就对达到8个节点的链表进行树化(转成红黑树)
                  //	注意,在转成红黑树时,要进行判断,判断条件:
                  //	if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY(64)
                  //		resize();
                  //	如果上面条件成立,先对table数组扩容,而不是对链表进行树化
                  //	只有上面条件不成立时,才进行树化,转成红黑树
                  //(2)依次和该链表的每一个元素比较过程中,如果有相同情况,就直接break;
                
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);/是一个空方法
        return null;
    }

LinkedHashSet

  1. 是HashSet的子类
  2. LinkedHashSet底层是一个LinkedHashMap,底层维护了一个数组+双向链表(LinkedHashSet有head和tail)
  3. LinkedHashSet根据元素的hashCode值来决定元素的存储位置,同时使用链表维护元素的次序,使得元素看起来是以插入顺序保存的,即元素取出的顺序跟放入的顺序一致
  4. 不允许添加重复元素
  5. 每一个节点有before和after属性,这样可以形成双向链表
  6. 在添加一个元素时,先求hash值,再求索引,确定该元素在table的位置,然后将添加的元素加入到双向链表中(如果已经存在,不添加,原则和hashset一样)
LinkedHashSet源码解读(以以下代码为例)
Set set = new LinkedHashSet();
set.add(new String("AA"));
set.add(456);
set.add(456);
set.add(new Customer("刘", 1001));
set.add(123);
set.add("HSP");
  1. 添加第一个元素时,直接将数组table扩容到16,存放的节点类型是LinkedHashMap$Entry
  2. table是一个HashMap$Node[]数组,存放的元素是LinkedHashMap$Entry类型。
static class Entry<K,V> extends HashMap.Node<K,V> {
    Entry<K,V> before, after;
    Entry(int hash, K key, V value, Node<K,V> next) {
        super(hash, key, value, next);
    }
}

TreeSet

  1. 当使用无参构造器创建TreeSet时,是无序的
  2. 若希望添加的元素按字符串大小排序,可以使用TreeSet提供的构造器,传入比较器(匿名内部类),并指定排序规则,以以下代码为例
TreeSet treeSet = new TreeSet(new Comparator() {
    @Override
    public int compare(Object o1, Object o2) {
        //下面调用String的CompareTo方法进行字符串大小比较
        return ((String) o1).compareTo((String) o2);
    }
});
源码解读(以以下代码为例)
TreeSet treeSet = new TreeSet(new Comparator() {
    @Override
    public int compare(Object o1, Object o2) {
        //下面调用String的CompareTo方法进行字符串大小比较
        return ((String) o1).compareTo((String) o2);
    }
});
treeSet.add("jack");
treeSet.add("tom");
treeSet.add("sp");
treeSet.add("a");
  1. 构造器把传入的比较器对象,赋给了TreeSet底层的TreeMap的属性this.comparator
public TreeMa(Comparator<? super K> comparator) {
    this.comparator = comparator;
}
  1. 在调用treeSet.add("tom")后,底层会执行以下代码
if (cpr != null) {//cpr就是传入的匿名内部类(对象)
    do {
        parent = t;
        cmp = cpr.compare(key, t.key);//动态绑定到传入的匿名内部类(对象)compare
        if (cmp < 0)
            t = t.left;
        else if (cmp > 0)
            t = t.right;
        else//如果相等,即返回0,这个key就没有加入了
            return t.setValue(value);
    } while (t != null);
}

Map接口(以JDK8为主)

  1. Map与Collection并列存在,用于保存具有映射关系的数据:Key-Value(双列元素)
  2. Map中的key和value可以是任何引用类型的数据,会封装到HashMap$Node对象中
  3. Map中的key不允许重复,原因和HashSet一样。当有相同的key时,就相当于替换
  4. Map中的value可以重复
  5. Map中的key可以为null,value也可以为null。key为null,只能有一个;value为null,可以有多个
  6. 常用String类作为Map的key(其他类型也可以)
  7. key和value之间存在单向一对一关系,即总能通过指定的key找到对应的value
  8. Map存放数据的key-value示意图,一对k-v使存放在一个Node中的,又因为Node实现了Entry接口,有些书上也说,一对k-v就是一个Entry

  1. k-v最后是HashMap$Node node = newNode(hash, key, value, null)
  2. k-v为了方便程序员的遍历,还会创建EntrySet集合,该集合存放的元素类型为Entry,而一个Entry对象就有k,v EntrySet<Entry<K,V>>,即:transient Set<Map.Entry<K,V>> entrySet;
  3. entrySet中,定义的类型是Map.Entry,但是实际上存放的还是HashMap$Node。因为HashMap$Node implements Map.Entry<K,V>
  4. 这样当把HashMap$Node对象存放到entrySet中就方便我们的遍历,因为Map.Entry中提供了两个重要方法
    1. K getKey();
    2. V get Value();
  5. 实际上entrySet中存放的元素和原来table中的元素是同一个对象,不会额外创建新对象,实际上是一种引用关系

常用方法

  1. put:添加
  2. remove:根据键删除映射关系
  3. get:根据键获取值
  4. size:获取元素个数
  5. isEmpty:判断个数是否为0
  6. clear:清除
  7. containsKey:查找键是否存在
  8. KeySet:获取所有的键
  9. entrySet:获取所有关系
  10. values:获取所有的值

Map接口遍历方式

  1. 先取出所有的key,再通过Key取出对应的value
Set keyset = map.KeySet();
//增强for循环
for (Object key : keyset) {
    System.out.println(key + "-" + map.get(key));
}
//通过迭代器
Iterator iterator = keyset.iterator();
while (iterator.hashNext()) {
    Object next = iterator.next();
     System.out.println(key + "-" + map.get(key));
}
  1. 把所有的value取出
Collection values = map.values();
//增强for循环
for (Object value : values) {
    System.out.println(value);
}
//迭代器
Iterator iterator2 = values.iterator();
while (iterator2.hashNext()) {
    Object next = iterator2.next();
    System.out.println(next);
}
  1. 通过EntrySet获取k-v
Set entrySet = map.entrySet();//EntrySet<Map.Entry<K,V>>
//增强for循环
for (Object entry : entrySet) {
    //将entry转成Map.Entry()
    Map.Entry m = (Map.Entry) entry;
    System.out.prinln(m.getKey() + "-" + m.getValue());
}
//迭代器
Iterator iterator3 = entrySet.iterator();
while (iterator3.hashNext()) {
    Object next = iterator3.next();//实际运行类型为HashMap$Node -实现-> Map.Entry
    Map.Entry m = (Map.Entry) next;
    System.out.println(m.getKey() + "-" + m.getValue());
}

HashMap

  1. Map接口的常用类:HashMap,Hashtable,Properties
  2. HashMap是Map接口使用频率最高的实现类
  3. HashMap以k-v对的方式来存储数据
  4. key不能重复,但是值可以重复,允许使用null键和null值
  5. 若添加相同的key,会覆盖原来的键值对,等同于修改(key不会替换,val会替换)
  6. 与HashSet一样,不保证映射的顺序,因为底层是以hash表的方式来存储的。(jdk8的hashMap底层 数组+链表+红黑树)
  7. HashMap没有实现同步,线程是不安全的,方法没有做同步互斥,没有synchronized

底层机制

  1. (k,v)是一个Node实现了Map.Entry<K,V>接口
  2. jdk7.0的hashmap底层实现[数组+链表],jdk8.0底层实现[数组+链表+红黑树
扩容机制
  1. HashMap底层维护了Node类型的数组table,默认为null
  2. 当创建对象时,将加载因子(loadfactor)初始化为0.75
  3. 当添加key-val时,通过key的哈希值得到在table的索引。然后判断该索引处是否有元素,如果没有元素直接添加。如果该索引处有元素,继续判断该元素的key是否和准备加入的key相等,若相等,则直接替换val;如果不相等,需要判断是树结构还是链表结构,做出相应处理。若添加时发现容量不够,则需要扩容
  4. 第一次添加,将table表扩容为16,临界值(threshold)为12
  5. 以后再扩容,则按原来的2倍进行扩容,临界值也按原来的两倍进行扩容
  6. 在java8中,如果一条链表的元素个数超过TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN_CAPACITY(默认64),就会进行树化(红黑树)
源码解读(以以下代码为例)
HashMap map = new HashMap();
map.put("java", 10);
map.out("php", 10);
map.put("java", 20);
  1. 执行构造器new HashMap()初始化加载因子loadfactor = 0.75,HashMap$Node[] table = null;
  2. 执行put,会调用hash方法计算key的hash值(h = key.hashCode()) ^ (h >>>16)
public V put(K key, V value) {//key="java" value=10
    return putVal(hash(key), key, value, false, true);
}
  1. 执行putVal
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;//辅助变量
        //如果底层的table数组为null,或者length=0,就扩容为16
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        //取出hash值对应的table表的索引位置的Node,如果为null,就直接把加入的key-value,
    	//创建成一个Node,加入到该位置
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;//辅助变量
            if (p.hash == hash &&//如果table表的索引位置的key的hash和新的key的hash相同,
                //并且满足table现有的节点的key和准备添加的key是同一个对象或者equals返回真
                //就认为不能添加新的value
               ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)//如果当前table的已有Node是红黑树,就按照红黑树
                //的方式处理
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                //如果找到的节点,后面是链表,就循环比较
                for (int binCount = 0; ; ++binCount) {//死循环
                    if ((e = p.next) == null) {//如果整个链表没有和他相同的,就加到该链表的
                        //最后。加入后,判断该链表的个数是否到八个,到八个后就调用trrifyBin
                        //方法进行红黑树的转化工作
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&//如果在循环比较过程中,,发现有相同,就break,就只
                        //是替换value
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;//替换key对应的值
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;//每增加一个Node,就size++
        if (++size > threshold)//如果size>=临界值,就对table表进行扩容
            resize();
        afterNodeInsertion(evict);
        return null;
    }
  1. 关于树化(转成红黑树),如果table表为null,或者大小还没有到·64,就暂时不树化,而是进行扩容;否则才会真正的树化 -> 剪枝(树 -> 链表,当树化后数据量太少时触发)
final void treeifyBin(Node<K,V>[] tab, int hash) {
    int n, index; Node<K,v> e;
    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
        resize();
}
扩容树化机制(以以下代码为例)
public class HashMapSource02 {
    public static void main(String[] args) {
        HashMap hashMap = new HashMap();
        for (int i = 1; i <= 12; i++) {
            hashMap.put(new A(i), "hello");
        }
        System.out.println("hashMap=" + hashMap);
    }
}

class A {
    private int num;
    public A(int num) {
        this.num = num;
    }

    @Override
    public int hashCode() {
        return 100;
    }
 }

Hashtable

  1. 存放的元素是键值对:即k-v
  2. Hashtable的键和值都不能为null,否则会抛出NullPointerException
  3. Hashtable的方法基本上和HashMap一样
  4. Hashtable线程安全,HashMap线程不安全
  5. 底层有一个数组Hashtable$Entry[] ,初始化大小为11,临界值为8
  6. threshold(临界值) = 数组当前容量 * 0.75

扩容机制(以以下代码为例)

Hashtable table = new Hashtable();
table.put("john", 100);
table.put("lucy", 100);
table.put("lic", 100);
table.put("lic", 88);
table.put("hello1", 1);
table.put("hello2", 1);
table.put("hello3", 1);
table.put("hello4", 1);
table.put("hello5", 1);
table.put("hello6", 1);
System.out.println(table);
  1. 执行方法addEntry(hash, key, value, index);添加k-v,封装到Entry中
  2. if (count >= threshold)满足时,按int newCapacity = (oldCapacity << 1) + 1;,即原来容量的两倍加一进行扩容

Hashtable 和 HashMap对比

版本 线程安全(同步) 效率 允许null键null值
HashMap 1.2 不安全 可以
Hashtable 1.0 安全 较低 不可以

Properties

  1. 继承Hashtable类并实现了Map接口,使用键值对保存数据
  2. 使用特点和Hashtable类似
  3. 可以用于从xxx.properties文件中,加载数据到Properties类对象,并进行读取和修改
  4. 工作后,xxx.properties文件通常作为配置文件,相关使用会在IO流举例
  5. key和value不能为null
  6. 如果有相同的key,则会替换value值

TreeMap

  1. 使用默认的构造器,创造TreeMap,是无序的(也没有排序)
  2. 可以使用传入匿名内部类来实现定制排序

源码解读(以以下代码为例)

TreeMap treeMap = new TreeMap(new Comparator() {
    @Override
    public int compare(Object o1, Object o2) {
        return ((String) o1).compareTo(((String) o2));
    }
});
treeMap.put("jack", "杰克");
treeMap.put("tom", "汤姆");
treeMap.put("kristina", "克瑞斯提诺");
treeMap.put("smith", "斯密斯");
  1. 构造器,把传入的实现了Comparator接口的匿名内部类(对象),传给TreeMap的comparator
public TreeMap(Comparator<? super K> comparator) {
    this.comparator = comparator;
}
  1. 调用put方法
    1. 第一次添加,执行以下代码
Entry<K,V> t = root;
if (t == null) {
    compare(key,key);//type (and possibly null) check

    root = new Entry<>(key, value, null);
    size = 1;
    modCount++;
    return null
}
  1. 以后添加,执行以下代码
Comparator<? super K> cpr = comparator;
if (cpr != null) {
    do {//遍历所有的key
        parent = t;
        cmp = cpr.compare(key, t.key);//动态绑定到传入的匿名内部类的compare
        if (cmp < 0)
            t = t.left;
        else if (cmp > 0)
            t = t.right;
        else//若遍历过程中,发现准备添加Key和当前已有的Key相等,就不添加
            return t.setValue(value);
    } while (t != null);
}

集合选型规则

  1. 先判断存储的类型(一组对象[单列]或一组键值对[双列])
  2. 一组对象:Collection接口
  • 允许重复:List
    • 增删多:LinkedList(底层维护了一个双向链表)
    • 改查多:ArrayList(底层维护Object类型的可变数组)
  • 不允许重复:Set
    • 无序:HashSet(底层是HashMap,维护了一个哈希表,即数组+链表+红黑树)
    • 排序:TreeSet
    • 插入和取出顺序一致:LinkedHashSet,维护数组+双向链表
  1. 一组键值对:Map
  • 键无序:HashMap(底层是:哈希表 jdk7:数组+链表,jdk8:数组+链表+红黑树)
  • 键排序:TreeMap
  • 键插入和取出顺序一致:LinkedHashMap
  • 读取文件:Properties

Collections工具类

  1. Collections是一个操作Set、List和Map等集合的工具类
  2. Collections中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作

排序操作(均为static方法)

  1. reverse(List):反转List中元素的顺序
  2. shuffle(List):对List集合元素进行随机排序
  3. sort(List):根据元素的自然顺序对指定List集合元素按升序排序
  4. sort(List, Comparator):根据指定的Comparator产生的顺序对List集合元素进行排序
  5. swap(List, int, int):将指定List集合中的i处元素和j处元素进行交换

查找、替换

  1. Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
  2. Object max(Collection, Comparator):根据Comparator指定顺序,返回给定集合中的最大元素
  3. Object min(Collection)
  4. Object min(Collection, Comparator)
  5. int frequency(Collection, Object):返回集合中指定元素的出现次数
  6. void copy(List dest, List src):将src中的内容复制到dest中,注意dest的容量需要大于等于src的容量,否则会抛异常
  7. boolean replaceAll(List list, Object oldVal, Object newVal):使用新值替换List对象的所有旧值

泛型

可以理解为表示数据类型的数据类型,其具体的数据类型在编译时确定

  1. 泛型又称参数化类型,是jdk5.0出现的新特性,解决数据类型的安全性问题
  2. 在类声明或实例化时只要指定好需要的具体的类型即可
  3. Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastException异常,同时,代码更加简洁、健壮
  4. 泛型的作用是:可以在类声明时通过一个表示类中某个属性的类型,或者是某个方法的返回值的类型,或者是参数类型

泛型的语法

泛型的声明

interface 接口<T>{}或者class 类<K,V>{}

  1. T,K,V不代表值,而是表示类型
  2. 任意字母都可以,常用T表示,是Type的缩写

泛型的实例化

在类名后面指定类型参数的值(类型)

  • List<String> strList = new ArrayList<String>();
  • Iterator<Customer> iterator = customers.iterator();

注意事项和使用细节

  1. T,E只能是引用类型
  2. 在指定泛型具体类型后,可以传入该类型或其子类类型
  3. 泛型使用形式
  • List<Integer> list1 = new ArrayList<Integer>();
  • List<Integer> list2 = new ArrayList<>();
  • 在实际开发中通常用第二种形式,编译器会进行类型推断
  1. 若这样写List list3 = new ArrayList();默认给他的泛型是 E就是Object

自定义泛型类

  1. 基本语法
class 类名<T,R...> {
    成员
}
  1. 普通成员可以使用泛型(属性、方法)
  2. 使用泛型的数组,不能初始化
  3. 静态方法中不能使用类的泛型
  4. 泛型类的类型,是在创建对象时确定的(因为创建对象时,需要指定确定类型)
  5. 若在创建对象时,没有指定类型,默认为Object

应用实例(以以下代码为例)

class Tiger<T, R, M> {
    String name;
    R r;
    M m;
    T t;
}
  1. Tiger后面泛型,所以Tiger类为泛型类
  2. T,R,M是泛型的标识符,一般是单个写字母
  3. 泛型的标识符可以有多个
  4. 普通成员可以使用泛型(属性和方法)
  5. 数组不能使用泛型,但可以先定义,后面声明泛型后,再初始化
  6. 静态方法不能使用泛型,因为静态和类相关,因为类加载时,对象还没有创建
  7. 若在创建对象时,没有指定类型,默认为Object

自定义泛型接口

  1. 基本语法
interface 接口名<T, R...> {
    
}
  1. 在接口中,静态成员也不能使用泛型(这个和泛型类规定一样)
  2. 泛型接口的类型,在继承接口或者实现接口时确定
  3. 没有指定类型,默认为Object

自定义泛型方法

  1. 基本语法
修饰符 <T, R,...> 返回类型 方法名(参数列表) {
    
}
  1. 泛型方法可以定义在普通类中,也可以定义在泛型类中
  2. 当泛型方法被调用时,类型会确定
  3. public void eat(E e) {}修饰符后面没有<T, R,...>,所以eat方法不是泛型方法,而是使用了泛型

使用说明

class Car {
    public void run() {
        
    }

    public <T, R> void fly(T t, R r) {
        
    }
}

在调用时根据传入的值的数据类型来确定泛型的类型,泛型方法可以使用类声明的泛型,也可以使用自己声明的泛型

泛型的继承和通配符

  1. 泛型不具备继承性
  2. :支持任意泛型类型
  3. :支持A类以及A的子类,规定了泛型的上限
  4. :支持A类以及A类的父类,不限于直接父类,规定了泛型的下限

Java绘图坐标体系

坐标原点位于左上角,以像素为单位。在Java坐标系中,第一个是x坐标,表示当前位置为水平方向,距离坐标原点x个像素;第二个是y坐标,表示当前位置为垂直方向,距离坐标原点y个像素

像素

计算机在屏幕上显示的内容是由屏幕上的每一个像素组成的。例如,计算机显示器的分辨率是800*600,表示计算机屏幕上的每一行由800个点组成,共有600行,整个计算机屏幕共有480000个像素。像素是一个密度单位,而厘米是长度单位,两者无法比较

画一个圆

以下是绘制一个圆的代码

public class DrawCircle extends JFrame {
    private MyPanel mp = null;
    public static void main(String[] args) {
        new DrawCircle();
    }
    public DrawCircle() {
        mp = new MyPanel();
        this.add(mp);
        this.setSize(400, 300);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setVisible(true);
    }
 }
//1. MyPanel对象就是一个画板
//2. Graphics g,把g理解成一支画笔
//3. Graphics提供了很多绘图的方法
class MyPanel extends JPanel {
    @Override
    public void paint(Graphics g) {
        super.paint(g);//一定要保留
        g.drawOval(10, 10, 100, 100);
    }
}

绘图原理

  1. Component类提供了两个和绘图相关最重要的方法:
    1. paint(Graphics g)绘制组件的外观
    2. repaint()刷新组件的外观
  2. 当组件第一次在屏幕显示的时候,程序会自动的调用paint()方法来绘制组件
  3. 在以下情况paint()将会被调用
    1. 窗口最小化,再最大化
    2. 窗口的大小发生变化
    3. repaint函数被调用

Graphics类

这个类相当于画笔,提供了各种绘制图形的方法

  1. 画直线 drawLine(int x1, int y1, int x2, int y2)
  2. 画矩形边框 drawRect(int x, int y, int width, int height)
  3. 画椭圆边框 drawOval(int x, int y, int width, int height)
  4. 填充矩形 fillRect(int x, int y, int width, int height)
  5. 填充椭圆 fillOval(int x, int y, int width, int height)
  6. 画图片 drawImage(Image img, int x, int y,..)
  7. 画字符串 drawString(String str, int x, int y)
  8. 设置画笔的字体 setFont(Font font)
  9. 设置画笔的颜色 setColor(Color c)

Java事件处理机制

java事件处理是采取“委派事件模型”。当事件发生时,产生事件的对象看,会把此“信息”传递给“事件的监听者”处理,这里所说的“信息”实际上就是java.awt.event事件类库里某个类所创建的对象,把它称为”事件的对象“

示意图

深入理解

  1. 事件源:事件源是一个产生事件的对象,比如按钮、窗口等

  2. 事件:事件就是承载事件源状态改变时的对象,比如当键盘事件、鼠标事件、窗口事件等等,会生成一个事件对象,该对象保存着当前事件很多信息,比如KeyEvent对象有含义被按下键的Code值。java.awt.event包和javax.swing.event包中定义了各种事件类型

  3. 常见的事件类型
    | 事件类 | 说明 |
    | --- | --- |
    | ActionEvent | 通常在按下按钮,或双击一个列表项或选中某个菜单时发生 |
    | AdjustmentEvent | 当操作一个滚动条时发生 |
    | ComponentEvent | 当一个组件隐藏,移动,改变大小时发送 |
    | ContainerEvent | 当一个组件从容器中加入或者删除时发生 |
    | FocusEvent | 当一个组件获得或是失去焦点时发生 |
    | ItemEvent | 当一个复选框或是列表项被选中时,当一个选择框或选择菜单被选中 |
    | KeyEvent | 当从键盘的按键被按下,松开时发生 |
    | MouseEvent | 当鼠标被拖动,移动,点击,按下... |
    | TextEvent | 当文本区和文本域的文本发生改变时发生 |
    | WindowEvent | 当一个窗口激活,关闭,失效,恢复,最小化 |

  4. 事件监听器接口

    1. 当事件源产生一个事件,可以传送给事件监听者处理
    2. 事件监听者实际上就是一个类,该类实现了某个事件监听器接口,比如前面的MyPanle就是一个类,它实现了KeyListener接口,它就可以作为一个事件监听者,对接收到的事件进行处理
    3. 事件监听器有多种,不同的事件监听器接口可以监听不同的事件,一个类可以实现多个监听接口
    4. 这些接口在java.awt.event和javax.swing.event包中定义。

多线程基础

线程相关概念

程序(program)

为完成特定任务、用某种语言编写的一组指令的集合。简单说,就是代码

进程

  1. 进程是指运行中的程序,比如使用QQ,就启动了一个进程,操作系统就会为该进程分配内存空间。当使用迅雷,又启动了一个进程,操作系统将为迅雷分配新的内存空间
  2. 进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程:有它自身的产生、存在和消亡的过程

线程

  1. 线程由进程创建,是进程的一个实体
  2. 一个进程可以拥有多个线程

其他相关概念

  1. 单线程:同一个时刻,只允许执行一个线程
  2. 多线程:同一个时刻,可以执行多个线程,比如:一个QQ进程,可以同时打开多个聊天窗口;一个迅雷进程,可以同时下载多个文件
  3. 并发:同一个时刻,多个任务交替执行,造成一种”貌似同时“的错觉,简单的说,单核cpu实现的多任务就是并发
  4. 并行:同一个时刻,多个任务同时执行,多个cpu可以实现并行

线程基本使用

创建线程的两种方式

在Java中线程使用有两种方法

  1. 继承Thread类,重写run方法
public class Thread01 {
    public static void main(String[] args) {
        //创建Cat对象,可以当作线程使用
        Cat cat = new Cat();
        cat.start();//启动线程
    }
}

class Cat extends Thread {

    int times = 0;
    @Override
    public void run() {//往往重写run方法,写上自己的业务逻辑
        while (true) {
            //每隔一秒,输出”喵喵,我是小猫咪“
            System.out.println("喵喵,我是小猫咪" + (++times));
            //让线程休眠1秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if (times == 8) {
                break;
            }
        }
    }
}
  1. 当一个类继承了Thread类,该类就可以当作线程使用
  2. 重写run方法,写上自己的业务代码
  3. run Thread类实现了Runnable接口的run方法
public void run() {
    if (target != null) {
        target.run();
    }
}
  1. 实现Runnable接口,重写run方法,以以下代码为例,这里底层使用一个设计模式(代理模式)
public class Thread02 {
    public static void main(String[] args) {
        Dog dog = new Dog();
        //这里不能调用start
        //创建了Thread对象,把dog对象(实现Runnable),放入Thread
        Thread thread = new Thread(dog);
        thread.start();
    }
}

class Dog implements Runnable {//通过实现Runnable接口,开发线程
    
	int count = 0;
    @Override
    public void run() {
        while (true) {
            System.out.println("小狗汪汪叫..hi" + (++count) + Thread.currentThread().getName());

            //休眠1秒
            try {
                Thread.sleep(1000);
            } catch(InterruptedException e) {
                e.printStackTrace();
            }

            if (count == 10) {
                break;
            }
        }
    }
}
  1. java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时在用继承Thread类方法来创建线程显然不可能了
  2. java设计者们提供了另外一个方式创建线程,就是通过实现Runnable接口来创建线程

start源码分析

  1. 首先执行以下代码
public synchronized void start() {
    start0();
}
  1. 然后执行以下代码
//start0()是本地方法,是由JVM调用,底层是c/c++实现
//真正实现多线程的效果,是start0()方法,而不是run()方法
private native void start0();

代理模式(以以下代码为例)

public class Thread02 {
    public static void main(String[] args) {
        Tiger tiger = new Tiger();//实现了Runnable接口
        ThreadProxy threadProxy = new ThreadProxy(tiger);
        threadProxy.start();
    }
}

class Animal {}
class Tiger extends Animal implements Runnable {

    @Override
    public void run() {
        System.out.println("老虎嗷嗷叫....");
    }
}
//线程代理类,模拟了一个极简的Thread类
class ThreadProxy implements Runnable {//Proxy类当作ThreadProxy类来看

    private Runnable target = null;//属性,类型是Runnable
    
    @Override
    public void run() {
        if (target != null) {
            target.run();//动态绑定
        }    
    }

    public ThreadProxy(Runnable target) {
        this.target = target;
    }

    public void start() {
        start0();//这个方法是真正实现多线程的方法
    }
    public void start0() {
        run();
    }
}

继承Thread vs 实现Runnable的区别

  1. 从Java的设计来看,通过继承Thread或者实现Runnable接口来创建线程本质上没有区别,从jdk帮助文档可以看到Thread类本身就实现了Runnable接口
  2. 实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制,建议使用Runnable接口

线程终止

  1. 当线程完成任务后,会自动退出
  2. 还可以通过使用变量来控制run方法退出的方式停止线程,即通知方式

线程常用方法

第一组常用方法

  1. setName:设置线程名称,使之与参数name相同
  2. getName:返回该线程的名称
  3. start:使该线程开始执行;Java虚拟机底层调用该线程的start0方法
  4. run:调用线程对象run方法;
  5. setPriority:更改线程的优先级
  6. getPriority:获取线程的优先级
  7. sleep:在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
  8. interrupt:中断线程

第一组常用方法注意细节

  1. start底层会创建新的线程,调用run,run就是一个简单的方法调用,不会启动新线程
  2. 线程优先级的范围
  • MAX_PRIORITY 10
  • MIN_PRIORITY 1
  • NORM_PRIORITY 5
  1. interrupt,中断线程,但没有真正的结束线程。所以一般用于中断正在休眠线程
  2. sleep:线程的静态方法,使当前线程休眠

第二组常用方法

  1. yield:线程的礼让。让出cpu,让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功
  2. join:线程的插队。插队的线程一旦插队成功,则肯定先执行插入的线程所有的任务,插队是一定会成功的

用户线程和守护线程

  1. 用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
  2. 守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束,可以使用setDaemon(true)方法将线程设置为守护线程
  3. 常见的守护线程:垃圾回收机制

线程的生命周期

JDK中用Thread.State枚举表示了线程的几种状态
image.png

线程同步机制

  1. 在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性
  2. 也可以理解为:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作

同步具体方法

  1. 同步代码块
synchronized (对象) {//得到对象的锁,才能操作同步代码
    //需要被同步代码;
}
  1. synchronized还可以放在方法声明中,表示整个方法为同步方法
public synchronized void m (String name) {
    //需要被同步的代码
}
  1. 可以理解为上厕所前先把门关上(上锁),完事后再出来(解锁),那么其他人就可以使用厕所了

互斥锁

  1. 在Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性
  2. 每个对象都对应于一个可称为”互斥锁“的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象
  3. 关键字synchronized来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问
  4. 同步的局限性:导致程序的执行效率要降低
  5. 同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象)
  6. 同步方法(静态的)锁为当前类本身

注意事项和细节

  1. 同步方法如果没有使用static修饰:默认锁对象为this
  2. 如果方法使用static修饰,默认锁对象:当前类.class
  3. 实现的落地步骤
    1. 需要先分析上锁的代码
    2. 选择同步代码块或同步方法,尽量使用同步代码块
    3. 要求多个线程的锁对象为同一个即可

线程的死锁

多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程时一定要避免死锁的发生

释放锁

  1. 当前线程的同步方法、同步代码块执行结束。比如上厕所,完事出来
  2. 当前线程在同步代码块、同步方法中遇到break、return。比如没有正常的完事,经理叫他修改bug,不得已出来
  3. 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束。比如没有正常的完事,发现忘带纸,不得已出来
  4. 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。比如没有正常完事,觉得需要酝酿下,所以出来等会再进去

不会释放锁的情况

  1. 线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行,不会释放锁。比如上厕所太困了,在坑位上咪了一会
  2. 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁。注意:应尽量避免使用suspend()和resume()来控制线程,方法不再推荐使用

IO流

文件

文件就是保存数据的地方

文件流

文件在程序中是以流的形式来操作的

  • 流:数据在数据源(文件)和程序(内存)之间经历的路径
  • 输入流:数据从数据源(文件)到程序(内存)的路径
  • 输出流:数据从程序(内存)到数据源(文件)的路径

常用的文件操作

创建文件对象相关构造器和方法

  • new File(String pathname) 根据路径构建一个File对象
  • new File(File parent, String child) 根据父目录文件+子路径构建
  • new File(String parent, String child) 根据父目录+子路径创建
  • creatNewFile() 创建新文件

获取文件的相关信息

  • getName 获取文件名字
  • getAbsolutePath 获取文件的绝对路径
  • getParent 获取文件的父级目录
  • length 获取文件的大小(字节)
  • exists 是否存在该文件
  • isFile 是不是一个文件
  • isDirectory 是不是一个目录

目录的操作和文件删除

mkdir创建一级目录、mdirs创建多级目录、delete删除空目录或文件。在Java编程中,目录也被当作文件

IO流原理及流的分类

Java IO流原理

  1. I/O是Input/Output的缩写,I/O技术是非常实用的技术,用于处理数据传输。如读/写文件,网络通讯等
  2. Java程序中,对于数据的输入/输出操作以”流(stream)“的方式进行
  3. java.io包下提供了各种”流“类和接口,用以获取不同种类的数据,并通过方法输入或输出数据
  4. 输入input:读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中
  5. 输出output:将程序(内存)数据输出到磁盘、光盘等存储设备中

流的分类

  • 按操作数据单位不同分为:字节流(8bit)二进制文件,字符流(按字符)文本文件
  • 按数据流的流向不同分为:输入流,输出流
  • 按流的角色不同分为:节点流,处理流/包装流
    | (抽象基类) | 字节流 | 字符流 |
    | --- | --- | --- |
    | 输入流 | InputStream | Reader |
    | 输出流 | OutputStream | Writer |
  1. Java的IO流共涉及40多个类,实际上非常规则,都是从如上4个抽象基类派生的
  2. 由这四个类派生出的子类名称都是以其父类作为子类名后缀

IO流体系图-常用的类

InputStream:字节输入流

InputStream抽象类是所有类字节输入流的超类

常用子类

  1. FileInputStream:文件输入流

  1. BufferedInputStream:缓冲字节输入流
  2. ObjectInputStream:对象字节输入流

FileOutputStream

常用方法

  1. new FileOutputStream(filePath) 创建方式,当写入内容时,会覆盖原来的内容
  2. new FileOutputStream(filePath, true) 创建方式,当写入内容时,是追加到文件后面

FileReader和FileWriter

FileReader和FileWriter是字符流,即按照字符来操作io

FileReader

FileReader相关方法
  1. new FileReader(File/String)
  2. read:每次读取单个字符,返回该字符,如果到文件末尾返回-1
  3. read(char[]):批量读取多个字符到数组,返回读取到的字符数,如果到文件末尾返回-1
相关API
  1. new String(char[]):将char转换成String
  2. new String(char[], off, len):将char[]的指定部分转换成String
FileWriter

常用方法
  1. new FileWriter(File/String):覆盖模式,相当于流的指针在首端
  2. new FileWriter(File/String, true):追加模式,相当于流的在尾端
  3. write(int):写入单个字符
  4. write(char[]):写入指定数组
  5. write(char[], off, len):写入指定数组的指定部分
  6. write(String):写入整个字符串
  7. write(String, off, len):写入字符串的指定部分
相关API

String类:toCharArray:将String转成char[]

注意

FileWriter使用后,必须要关闭(close)或刷新(flush),否则写入不到指定的文件。关闭文件流,等价flush() + 关闭

节点流和处理流

  1. 节点流可以从一个特定的数据源读写数据,如FileReader、FileWriter
  2. 处理流(也叫包装流)是”连接“在已存在的流(节点流或处理流)之上,为程序提供更为强大的读写功能,如BufferedReader、BufferedWriter

  1. BufferedReader类中,有属性Reader,即可以封装一个节点流,该节点流可以是任意的,只要是Reader子类

节点流和处理流的区别和联系

  1. 节点流是底层流/低级流,直接跟数据源相接
  2. 处理流(包装流)包装节点流,即可以消除不同节点流的实现差异,也可以提供更方便的方法来完成输入输出
  3. 处理流(包装流)对节点流进行包装,使用了修饰器设计模式,不会直接与数据源相连

处理流的功能主要体现在以下两个方面:

  1. 性能的提高:主要以增加缓冲的方式来提高输入输出的效率
  2. 操作的便捷:处理流提供了一系列便捷的方法来一次输入输出大批量的数据,使用更加灵活方便

处理流BufferedReader和BufferedWriter

以下是BufferedReader的类图,BufferdeWriter的类图省略

  1. BufferedReader和BufferedWriter属于字符流,是按照字符来读取数据的
  2. 关闭处理流时,只需关闭外层流即可

处理流BufferedInputStream和BufferedOutputStream

BufferedInputStream

BufferedInputStream是字节流,在创建BufferedInputStream时,会创建一个内部缓冲区数组

BufferedOutputStream

BufferedOutputStream是字节流,可以实现缓冲的输出流,可以将多个字节写入底层输出流中,而不必对每次字节写入调用底层系统

对象流ObjectInputStream和ObjectOutputStream

  1. 功能:提供了对基本类型或对象类型的序列化和反序列化的方法
  2. ObjectOutputStream提供序列化功能

  1. ObjectInputStream提供反序列化功能

  1. ObjectInputStream和ObjectOutputStream也是处理流
序列化和反序列化
  1. 序列化就是在保存数据时,保存的值和数据类型
  2. 反序列化就是在恢复数据时,恢复数据的值和数据类型
  3. 需要让某个对象支持序列化机制,则必须让其类是可序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一:
    1. Serializable 这是一个标记接口,推荐使用,没有方法
    2. Externalizable 该接口有方法需要使用
注意事项和细节说明
  1. 读写顺序要一致
  2. 要求实现序列化或反序列化对象,需要实现Serializable
  3. 序列化的类中添加SerialVersionUID,为了提高版本的兼容性
  4. 序列化对象时,默认将里面所有属性都进行序列化,但除了static或transient修饰的成员
  5. 序列化对象时,要求里面属性的类型也需要实现序列化接口
  6. 序列化具备可继承性,也就是如果某类已经实现了序列化,则它的所有子类也已经默认实现了序列化

标准输入输出流

类型 默认设备
System.in标准输入 InputStream 键盘
System.out标准输出 PrintStream 显示器
  1. System.in的编译类型为InputStream,运行类型为BufferedInputStream
  2. System.out的编译类型和编译类型都是PrintStream

转换流InputStreamReader和OutputStreamWriter

  1. InputStreamReader:Reader的子类,可以将InputStream(字节流)包装成Reader(字符流)

  1. OutputStreamWriter:Writer的子类,实现将OutputStream(字节流)包装成Writer(字符流)
  2. 效率更高,当处理纯文本数据时,如果使用字符流并且可以有效解决中文问题,所以建议将字节流转换成字符流
  3. 可以在使用时指定编码格式(比如utf-8,gbk,gb2312,ISO8859-1等)

打印流PrintStream和PrintWriter

打印流只有输出流,没有输入流

Properties类

  1. 专门用于读写配置文件的集合类

配置文件的格式:
键=值

  1. 注意:键值对不需要有空格,值不需要用引号括起来,默认类型是String

常用方法

  • load:加载配置文件的键值到Properties对象
  • list:将数据显示到指定设备
  • getProperty(key):根据键获取值
  • setProperty(key, value):设置键值对到Properties对象
  • store:将Properties中的键值对存储到配置文件中,在idea中,保存信息到配置文件,如果含有中文,会存储为unicode码值

网络编程

网络的相关概念

网络通信

  1. 概念:两台设备之间通过网络实现数据传输
  2. 网络通信:将数据通过网络从一台设备传输到另一台设备
  3. java.net包下提供了一系列的类或接口,供程序员使用,完成网络通信

网络

  1. 概念:两台或多台设备通过一定物理设备连接起来构成了网络
  2. 根据网络的覆盖范围不同,对网络进行分类:
    1. 局域网:覆盖范围最小,仅仅覆盖一个教室或一个机房
    2. 城域网:覆盖范围较大,可以覆盖一个城市
    3. 广域网:覆盖范围最大,可以覆盖全国,甚至全球,万维网是广域网的代表

ip地址

  1. 概念:用于唯一标识网络中的每台计算机/主机
  2. 查看ip地址:ipconfig
  3. ip地址的表示形式:点分十进制 xx.xx.xx.xx
  4. 每一个十进制数的范围:0~255
  5. ip地址的组成=网络地址+主机地址,比如:192.168.16.69
  6. IPv6是互联网工程任务组设计的用于替代IPv4的下一代IP协议,其地址数量号称可以为全世界的每一粒沙子编上一个地址
  7. 由于IPv4最大的问题在于网络地址资源有限,严重制约了互联网的应用和发展。IPv6的使用通过,不仅能解决网络地址资源数量的问题,而且也解决了多种接入设备连入互联网的障碍

IPv4地址分类

域名

  1. www.baidu.com
  2. 好处:为了方便记忆,解决记ip的困难
  3. 概念:将ip地址映射成域名

端口号

  1. 概念:用于表示计算机上某个特定的网络程序
  2. 表示形式:以整数形式,范围065535[2个字节表示端口,02^16-1]
  3. 0~1024已经被占用,比如ssh 22, ftp 21, smtp 25, http 80。在网络开发中,不要使用0~1024的端口
  4. 常见的网络程序端口号:
    1. tomcat:8080
    2. mysql:3306
    3. oracle:1521
    4. sqlserver:1433

网络通信协议

协议(TCP/IP)

TCP/IP(Transmission Control Protocol/Internet Protocol)的简写,中文译名为传输控制协议/因特网互联协议,又叫网络通讯协议,这个协议是Internet最基本的协议,Internet国际互联网络的基础,简单地说,就是由网络层的IP协议和传输层的TCP协议组成的

TCP和UDP

TCP协议(传输控制协议)

  1. 使用TCP协议前,须先建立TCP连接,形成传输数据通道
  2. 传输前,采用“三次握手”方式,是可靠的
  3. TCP协议进行通信的两个应用进程:客户端、服务端
  4. 在连接中可进行大数据量的传输
  5. 传输完毕,需释放已建立的连接,效率低

UDP协议(用户数据协议)

  1. 将数据、源、目的封装成数据包,不需要建立连接
  2. 每个数据包的大小限制在64K内,不适合传输大量数据
  3. 因无需连接,故是不可靠的
  4. 发送数据结束时无需释放资源(因为不是面向连接的),速度快
  5. 举例:厕所通知;发短信

InetAddress类

image.png

常用方法

  1. 获取本机InetAddress对象 getLocalHost
  2. 根据指定主机名/域名获取ip地址对象 getByName
  3. 获取InetAddress对象的主机名 getHostName
  4. 获取InetAddress对象的地址 getHostAddress

Socket

  1. 套接字(Socket)开发网络应用程序被广泛采用,以至于成为事实上的标准
  2. 通信的两端都要有Socket,是两台机器间通信的端点
  3. 网络通信其实就是Socket间的通信
  4. Socket允许程序把网络连接当成一个流,数据在两个Socket间通过IO传输
  5. 一般主动发起通信的应用程序称为客户端,等待通信请求的为服务端
  6. 基于Socket有两种编程方式,一种为TCP编程(可靠),另一种为UDP编程(不可靠)

TCP网络通信编程

  1. 基于客户端—服务端的网络通信,底层使用的是TCP/IP协议
  2. 应用场景举例:客户端发送数据看,服务端接收并显示
  3. 基于Socket的TCP编程

TCP字节流编程

 //1. 在本机的9999端口监听,等待连接
        //   细节: 要求在本机没有其他服务器监听9999
        //   细节: 这个SeverSocket可以通过accept()返回多个Socket(多个客户端连接服务器的并发)
        ServerSocket serverSocket = new ServerSocket(9999);
        System.out.println("服务端在9999端口监听,等待连接");
        //2. 当没有客户端连接9999端口时,程序会堵塞,等待连接
        //   如果有客户端连接,则会返回一个Socket对象,然后程序继续
        Socket socket = serverSocket.accept();
        System.out.println("服务端socket=" + socket.getClass());
        //3. 通过socket.getInputStream()读取客户端写入到数据通道的数据,显示
                    //客户端写入到数据通道的数据,显示
        InputStream inputStream = socket.getInputStream();
        //4. IO读取
        byte[] buf = new byte[1024];
        int readLen = 0;
        while ((readLen = inputStream.read(buf)) != -1) {
            System.out.println(new String(buf, 0, readLen));//根据读取到的实际长度显示内容
        }
        //5. 关闭流和socket
        inputStream.close();
        socket.close();
        serverSocket.close();
 //1. 连接服务端(ip, 端口)
        //连接本机的9999端口,如果连接成功,返回Socket对象
        Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
        System.out.println("客户端socket返回=" + socket.getClass());
        //2. 连接上后,生成Socket,通过
        //   得到和socket对象关联的输出流对象
        OutputStream outputStream = socket.getOutputStream();
        //3. 通过输出流,写入数据到数据通道
        outputStream.write("hello, sever".getBytes());
        //4. 关闭流对象和socket,必须关闭
        outputStream.close();
        socket.close();
        System.out.println("客户端退出");

可以使用shutdownOutput()方法设置写入结束标记

TCP字符流编程

可以使用write.line()方法设置写入结束标记,若使用这种方式,在读取时需要使用readLine()方法读取;也可以使用shutdownOutputStream()方法

netstat指令

  1. netstat -an 可以查看当前主机网络情况,包括端口监听情况和网络连接情况
  2. netstat -an | more 可以分页显示
  3. 要求在dos控制台下执行
  4. LISTENLING表示某个端口在监听
  5. 如果有一个外部程序(客户端)连接到该端口,就会显示一条连接信息
  6. 可以输入ctrl+c退出指令

TCP网络通讯不为人知的秘密

当客户端连接到服务端后,实际上客户端也是通过一个端口和服务端进行通讯的,这个端口是TCP/IP来分配的

UDP网络通信编程

  1. 类DatagramSocket和DatagramPacket实现了基于UDP协议网络程序
  2. UDP数据报通过数据报套接字DatagramSocket发送和接收,系统不保证UDP数据报一定能够安全送送到目的地,也不能确定什么时候可以抵达
  3. DatagramPacket对象封装了UDP数据报,在数据报中包含了发送端的IP地址和端口号以及接收端的IP地址和端口号
  4. UDP协议中每个数据报都给出了完整的地址信息,因此无需建立发送方和接收方的连接

基本流程

//1. 创建一个DatagramSocket对象,准备在9999接收数据
        DatagramSocket datagramSocket = new DatagramSocket(9999);
        //2. 构建一个DatagramPacket对象,准备接收数据
        //   UDP协议一个数据包最大为64K
        byte[] buf = new byte[1024];
        DatagramPacket datagramPacket = new DatagramPacket(buf, buf.length);
        //3. 调用接收方法,将通过网络传输的DatagramPacket
        //   填充到datagramPacket对象中
        // 当有数据报发送到本机9999端口时,就会接收到数据
        // 如果没有数据包发送到本机的9999端口,就会阻塞等待。
        System.out.println("接收端A等待接收数据...");
        datagramSocket.receive(datagramPacket);
        //4. 可以把packet进行拆包,取出数据并显示。
        int length = datagramPacket.getLength();//实际接收到的数据字节长度
        byte[] data = datagramPacket.getData();//接收到数据
        String s = new String(data, 0, length);
		System.out.println(s);
        //5. 关闭资源
        datagramSocket.close();
//1. 创建DatagramSocket对象,准备在9998端口接收数据
        DatagramSocket datagramSocket = new DatagramSocket(9998);
        //2. 将需要发送的数据封装到DatagramPacket对象中
        byte[] data = "hello,明天吃火锅".getBytes();
        //封装的DatagramPacket对象data内容字节数组
        DatagramPacket datagramPacket = new DatagramPacket(data, data.length, InetAddress.getByName("192.168.0.105"), 9999);
        datagramSocket.send(datagramPacket);
        //关闭资源
        datagramSocket.close();
        System.out.println("B端退出");
  1. 核心的两个类/对象 DatagramSocket与DatagramPacket
  2. 建立发送端,接收端(没有服务端和客户端概念)
  3. 发送数据前,建立数据包 DatagramPacket对象
  4. 调用DatagramSocket的发送、接收方法
  5. 关闭DatagramSocket

说明

  1. 没有明确的服务端和客户端,演变成数据的发送端和接收端
  2. 接收数据和发送数据是通过DatagramSocket对象完成
  3. 将数据封装DatagramPacket对象/装包
  4. 当接收到DatagramPacket对象,需要进行拆包,取出数据
  5. DatagramSocket可以指定在哪个端口接收数据

反射

反射机制

Java Reflection

  1. 反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息(比如成员变量,构造器,成员方法等),并能操作对象的属性及方法。反射在设计模式和框架底层都会用到
  2. 加载完类后,在堆中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象包含了类的完整结构信息。通过这个对象得到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,形象的称之为:反射

Java反射机制可以完成的事情

  1. 在运行时判断任意一个对象所属的类
  2. 在运行时构造任意一个类的对象
  3. 在运行时得到任意一个类所具有的成员变量和方法
  4. 在运行时调用任意一个对象的成员变量和方法
  5. 生成动态代理

反射相关的主要类

  1. java.lang.Class:代表一个类,Class对象表示某个类加载后在堆中的对象
  2. java.lang.reflect.Method:代表类的方法,Method对象表示某个类的方法
  3. java.lang.reflect.Field:代表类的成员变量,Field对象表示某个类的成员变量
  4. java.lang.reflect.Constructor:代表类的构造方法,Constructor对象表示构造器

这些类在java.lang.reflect

反射的优点和缺点

  1. 优点:可以动态的创建和使用对象(也是矿建底层核心),使用灵活,没有反射机制,框架技术就失去底层支撑
  2. 缺点:使用反射基本是解释执行,对执行速度有影响

反射调用优化-关闭访问检查

  1. Method和Field、Constuctor对象都有setAccessible()方法
  2. setAccessible作用是启动和禁用访问安全检查的开关
  3. 参数值为true表示反射的对象在使用时取消访问检查,提高反射的效率。参数值为false则表示反射的对象执行访问检查

Class类

  1. Class也是类,因此也继承Object类
  2. Class类不是new出来的,而是系统创建的
  3. 对于某个类的Class类对象,在内存中只有一份,因为类只加载一次
  4. 每个类的实例都会记得自己是由哪个Class实例所生成
  5. 通过Class是可以完整地得到一个类的完整结构,通过一系列API
  6. Class对象是存放在堆的
  7. 类的字节码二进制数据,是放在方法区的,有的地方成为类的元数据(包括方法代码,变量名,方法名,访问权限等等)

常用方法

方法名 功能说明
static Class forName(String name) 返回指定类名name的Class对象
Object newInstance() 调用缺省构造函数,返回该Class对象的一个实例
getName() 返回此Class对象所表示的实体(类、接口、数组类、基本类型等)名称
class[] getInterfaces() 获取当前Class对象的接口
ClassLoader getClassLoader() 返回该类的类加载器
Class getSuperclass() 返回表示此Class所表示的实体的超类的Class
Constructor[] getConstructors() 返回一个包含某些Constructor对象的数组
Field[] getDeclaredField() 返回Field对象的一个数组
Method getMethod(String name, Class ... paramTypes) 返回一个Method对象,此对象的形参类型为paramType

获取Class类对象

  1. 前提:已知一个类的全类名,且该类在类路径下,可以通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException,实例:Class cls1 = Class.forName("java.lang.Cat");

应用场景:多用于配置文件,读取类全路径,加载类

  1. 前提:若已知具体的类,通过类的class获取,该方式最为安全可靠,程序性能最高。实例:Class cls2 = Cat.class;

应用场景:多用于参数传递,比如通过反射得到对应构造器对象

  1. 前提:已知某个类的实例,调用该实例的getClass()方法获取Class对象,实例:Class clazz = 对象.getClass();

应用场景:通过创建好的对象,获取Class对象

  1. 其他方式

ClassLoader cl = 对象.getClass().getClassLoader();
Class clazz4 = cl.loadClass("类的全类名");

  1. 基本数据(int,char,boolean,float,double,byte,long,short) 按如下方式得到Class类类对象Class cls = 基本数据类型.class
  2. 基本数据类型对应的包装类,可以通过.type得到Class对象`Class cls = 包装类.TYPE

哪些类型有Class对象

  1. 外部类,成员内部类,静态内部类看,局部内部类,匿名内部类
  2. interface:接口
  3. 数组
  4. enum:枚举
  5. annotation:注解
  6. 基本数据类型
  7. void

类加载

反射机制是java实现动态语言的关键,也就是通过反射实现类动态加载

  1. 静态加载:编译时加载相关的类,如果没有则报错,依赖性太强
  2. 动态加载:运行时加载需要的类,如果运行时不用该类,则不报错,降低了依赖性

类加载时机

  1. 当创建对象时(new)
  2. 当子类被加载时,父类也加载
  3. 调用类中的静态成员时
  4. 通过反射

1~3都为静态加载,4是动态加载

类加载过程

各阶段完成的任务

加载和连接由JVM控制看,初始化程序员控制

  1. 加载阶段:JVM在该阶段的主要目的是将字节码从不同的数据源(可能是class文件、也可能是jar包,甚至网络)转化为二进制字节流加载到内存中,并生成一个代表该类的java.lang.Class对象
  2. 连接阶段-验证
    1. 目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全
    2. 包括:文件格式验证(是否以魔数oxcafebabe开头)、元数据验证、字节码验证和符号引用验证
    3. 可以考虑使用-Xverify:none参数来关闭大部分的类验证措施,缩短虚拟机类加载的时间
  3. 连接阶段-准备:JVM在该阶段对静态变量,分配内存并默认初始化(对应数据类型的默认初始值,如0、0L、null、false等)。这些变量所使用的内存都将在方法区中进行分配
  4. 连接阶段-解析:虚拟机将常量池内的符号引用替换为直接引用的过程
  5. 初始化
    1. 到初始化阶段,才真正开始执行类中定义的Java程序代码,此阶段是执行()方法的过程
    2. ()方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,并进行合并
    3. 虚拟机会保证一个类的()方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的()方法,其他线程都需要阻塞等待,直到活动线程执行()方法完毕

通过反射获取类的结构信息

第一组:java.lang.Class类

  1. getName:获取全类名
  2. getSimpleName:获取简单类名
  3. getFields:获取所有public修饰的属性,包含本类以及父类的
  4. getDeclaredFields:获取本类中所有属性
  5. getMethods:获取所有public修饰的方法,包含本类以及父类的
  6. getDeclaredMethods:获取本类中所有方法
  7. getConstructors:获取本类所有public修饰的构造器
  8. getDeclaredConstructors:获取本类中所有构造器
  9. getPackage:以Package形式返回包信息
  10. getSuperClass:以Class形式返回父类信息
  11. getInterfaces:以Class[]形式返回接口信息
  12. getAnnotations:以Annotation[]形式返回注解信息

第二组:java.lang.reflect.Field类

  1. getModifiers:以int形式返回修饰符
    1. 默认修饰符是0
    2. public是1
    3. private是2
    4. protected是4
    5. static是8
    6. final是16
  2. getType:以Class形式返回类型
  3. getName:返回属性名

第三组:java.lang.reflect.Method类

  1. getModifiers:以int形式返回修饰符
  2. getModifiers:以int形式返回修饰符
    1. 默认修饰符是0
    2. public是1
    3. private是2
    4. protected是4
    5. static是8
    6. final是16
  3. getReturnType:以Class形式获取返回类型
  4. getName:返回方法名
  5. getParameterTypes:以Class[]返回参数类型数组

第四组:java.lang.reflect.Constructors类

  1. getModifiers:以int形式返回修饰符
  2. getName:返回构造器名(全类名)
  3. getParameterTypes:以Class[]返回参数类型数组

通过反射创建对象

  1. 方式一:调用类中的public修饰的无参构造器
  2. 方式二:调用类中的指定构造器
  3. Class类相关方法
    1. newInstance:调用类中的无参构造器,获取对应类的对象
    2. getConstructor(Class...clazz):根据参数列表,获取对应的public构造器对象
    3. getDeclaredConstructor(Class...class):根据参数列表,获取所有的对应的构造器对象
  4. Constructor类相关方法
    1. setAccessible:暴破,使用反射可以访问private构造器
    2. newInstance(Object...obj):调用构造器

通过反射访问类中的成员

访问属性

  1. 根据属性名获取Field对象Field f = class对象.getDeclaredField(属性名);
  2. 暴破:f.setAccessible(true);//f是Field
  3. 访问

f.set(o, 值);
f.get(o);

  1. 如果是静态属性,则set和get中的参数o,可以写成null

访问方法

  1. 根据方法名和参数列表获取Method方法对象Method m = class.getDeclaredMethod(方法名, XX.class);
  2. 获取对象Object o = class.newInstance();
  3. 暴破m.setAccessible(true);
  4. 访问Object returnValue = m.invoke(o, 实参列表);
  5. 注意:如果是静态方法,则invoke的参数o,可以写成null

Mysql

启动mysql数据库的常用方式[Dos命令]

  1. 服务方式启动(界面)
  2. net stop mysql服务名(停止mysql服务)
  3. net start mysql服务名

登录

  1. mysql -h 主机名 -P 端口 -u 用户名 -p密码
    1. 如果没有写-h 主机,默认就是本机
    2. 如果没有写-P 端口,默认就是3306,在实际工作中,3306一般会修改
  2. 登录前,保证服务启动

数据库三层结构

  1. 所谓安装Mysql数据库,就是在主机安装一个数据库管理系统(DBMS),这个管理系统可以管理多个数据库。DBMS(database manage system)
  2. 一个数据库中可以创建多个表,以保存数据(信息)
  3. 数据库管理系统、数据库和表的关系示意图

  1. Mysql数据库-普通表的本质仍然是文件

数据在数据库中的存储方式

  1. 数据在数据库存储在表中
  2. 表中的一行称之为·一条记录->在Java程序中,一行记录往往使用对象表示

SQL语句分类

  1. DDL:数据定义语句[creat 表,库...]
  2. DML:数据操作语句[增加insert,修改update,删除delete]
  3. DQL:数据查询语句[select]
  4. DCL:数据控制语句[管理数据库,比如用户权限 grant revoke]

创建数据库

CREATE DATABASE [IF NOT EXISTS] db_name
      	[creat_specification[, create specification]...]
create_specification:
	[DEFAULT] CHARACTER SET charset_name
	[DEFAULT] COLLATE collation_name
  1. CHARACTER SET:指定数据库采用的字符集,如果不指定字符集,默认utf8
  2. COLLATE:指定数据库字符集的校对规则(常用的 utf8_bin[区分大小写]、utf8_general_ci[不区分大小写],默认是utf8_general_ci)
  3. 在创建数据库、表的时候,为了规避关键字,可以使用反引号解决

查看、删除数据库

  1. 显示数据库语句SHOWDATABASES
  2. 显示数据库创建语句SHOW CREATE DATABASE db_name
  3. 数据库删除语句DROP DATABASE [IF EXISTS] db_name

备份和恢复数据库

  1. 备份数据库(注意:在DOS执行)mysqldump -u 用户名 -p -B 数据库1 数据库2 数据库n > 文件名.sql,这个指令在mysql的安装目录\bin下
  2. 恢复数据库(注意:进入Mysql命令行再执行)source 文件名.sql

备份恢复数据库的表

备份库的表mysqldump -u 用户名 -p密码 数据库 表1 表2 表n > 文件名.sql

创建表

CREATE TABLE table_name
(
  field1 datatype,
  field2 datatype,
  field3 datatype
)character set 字符集 collate 校对规则 engine 引擎
field:指定列名 datatype: 指定列类型(字段类型)
character set: 如不指定则为所在数据库字符集
collate: 如不指定则为所在数据库校对规则
engine:引擎

Mysql常用数据类型(列类型)


image.png

整形的基本使用

类型 字节 最小值 最大值
带符号的/无符号的 带符号的/无符号的
TINYINT 1 -128 127
0 255
SMALLINT 2 -32768 32767
0 65535
MEDIUMINT 3 -8388608 8388607
0 16777215
INT 4 -2147483648 2147483647
0 4294967295
BIGINT 8 -9223372036854775808 9233372036854775807
0 18446744073709551615
  1. 使用规范:在能够满足要求的情况下,尽量使用占用空间小的数据类型
  2. 定义无符号整数和有符号整数
create table t10 (id tinyint);#默认有符号
create table t10 (id tinyint unsigned); #无符号

bit的使用

  1. 基本使用
CREATE TABLE t02 (num BIT(8));
INSERT INTO t02 VALUES(1);
INSERT INTO t02 VALUES(2);
  1. 细节说明
    1. bit字段显示时,按照位的方式显示
    2. 查询的时候仍然可以用添加的数值
    3. 如果一个值只有0,1 可以考虑使用bit(1)节约空间
    4. 位类型。M指定位数,默认值1,范围1~64
    5. 使用不多

数值型(小数)的基本使用

  1. FLOAT/DOUBLE [UNSIGNED]
  2. DECIMAL[M,D] [UNSIGNED]
    1. 可以支撑更加精确的小数位。M是小数位数(精度)的总数,D是小数点(标度)后面的位数
    2. 如果D是0,则值没有小数点或分数部分。M最大65。D最大30。如果D被省略,默认是0。如果M被省略,默认是10
    3. 如果希望小数的精度高,推荐使用decimal

字符串的基本使用

  1. CHAR(size) 固定长度字符串,最大255字符
  2. VARCHAR(size) 0~65535,可变长度字符串,最大65532字节 [utf8编码最大21844字符 1-3个字节用于记录大小],size=(字节数-3)/一个字符占用的最大字节

使用细节

  1. 细节1
    1. char(4) 这个4表示字符数(最大255),不是字节数,不管中文还是字母,都是放4个,按字符计算
    2. vachar(4) 这个4也是表示字符数,不管是字母还是中文都以定义好的表的编码来存放数据。不管是中文还是英文字母,都是最多存放4个
  2. 细节2
    1. char(4)是定长(固定的大小),就是说,即使插入'aa',也会占用分配的四个字符的空间
    2. varchar(4)是变长,就是说,如果插入了'aa',实际占用空间大小并不是4个字符,而是按照实际占用空间来分配(注意:varchar本身还需要占用1~3个字节来记录存放内容长度)L(实际数据大小) + (1~3)字节
  3. 细节3:什么时候使用char,什么时候使用varchar
    1. 如果数据是定长,推荐使用char,比如md5的密码,邮编,手机号,身份证号码等。
    2. 如果一个字段的长度不确定,则使用varchar,比如留言,文章
    3. 查询速度:char>varchar
  4. 细节4
  5. 在存放文本时,也可以使用TEXT数据类型。可以将TEXT列视为VARCHAR列,注意TEXT不能有默认值。大小为0~2^16-1字节,如果希望存放更多字符,可以选择MEDIUMTEXT 0~2^24-1 或者 LONGTEXT 0~2^32-1

日期类型的使用

CREATE TABLE birthday6
( t1 DATE, t2 DATATIME, t3 TIMESTAMP NOT NULL DEFAULT
 CURRENT_TIMESTAMP ON UPDATE
 CURRENT_TIMESTAMP); timestamp时间戳

TIMESTAMP在INSERT和UPDATE时,自动更新,前提是配置了

修改表

  1. 添加列
ALter TABLE tablename
ADD					(column datatype [DEFAULT expr]
            [, column datatype]...);
  1. 修改列
ALter TABLE tablename
MODIFY					(column datatype [DEFAULT expr]
            [, column datatype]...);
  1. 删除列
ALter TABLE tablename
DROP					(column);
#查看表的结构
desc 表名;
  1. 修改表名Rename table 表名 to 新表名
  2. 修改表字符集alter table 表名 character set 字符集;

数据库CRUD语句

  1. NSERT语句 添加数据
  2. UPDATE语句 更新数据
  3. DELETE语句 删除数据
  4. SELECT语句 查找数据

INSERT语句

INSERT INTO table_name [(column [, colum...])]
VALUES			(value [,value...]);
  1. 插入的数据应与字段的数据类型相同或能够转换
  2. 数据的长度应在列的规定范围内
  3. 在values中列出的数据位置必须与被加入的列的排列位置相对应
  4. 字符和日期型数据应包含在单引号中
  5. 列可以插入空值[前提是该字段允许为空]
  6. insert into tab_name(列名...) values (), (), () 形式添加多条记录
  7. 如果是给表中的所有字段添加数据,可以不写前面的字段名称
  8. 默认值的使用,当不给某个字段值时,如果有默认值就会添加默认值,否则报错。

UPDATE语句

UPDATE tb1_name
       SET col_name1=expr1 [, col_name2=expr2...]
       [WHERE where_definition]
  1. UPDATE语句可以用新值更新原有表中的各列
  2. SET子句指示要修改哪些列和要给予哪些值
  3. WHERE子句指定应更新哪些行。如果没有WHERE子句,则更新所有的行
  4. 如果需要修改多个字段,可以通过 set 字段1=值1,字段2=值2

delete语句

delete from tb1_name
        	[WHERE where_definition]
  1. 如果不使用where子句,将删除表中所有数据
  2. DELETE语句不能删除某一列的值(可使用UPDATE设为null或者'')
  3. 使用DELETE语句仅删除记录,不删除表本身。如果删除表,使用drop table语句。

select语句

SELECT [DISTINCT] *|{column1, column2, column3...}
            	FROM tablename;
  1. select指定查询哪些列的数据
  2. column指定列名
  3. *代表查询所有列
  4. FROM指定查询哪张表
  5. DISTINCT可选,指显示结果时,是否去掉重复数据
  6. 使用表达式对查询的列进行运算
SELECT *|{column1 | expression, column2 | expression...}
              	FROM tablename;
  1. 在select语句中可使用as语句
SELECT columnname as 别名 from 表名;

where子句中经常使用的运算符

使用oreder by 子句排序查询结果

SELECT column1, column2, column3...
            FROM table;
          	order by column asc|desc,...
  1. Order by指定排序的列,排序的列中既可以是表中的列名,也可以是select语句后指定的列名
  2. asc升序[默认]、desc降序
  3. order by 子句应位于select语句的结尾

合计/统计函数

count

count返回行的总数

select count(*) | count(列名) from tablename
              [where where_definition]
  1. count(*)返回满足条件的记录的行数
  2. count(列)统计满足条件的某列有多少个,但是会排除null
sum

sum函数返回满足where条件的行的和,一般使用在数值列

select sum(列名) {,sum(列名)...} from tablename [WHERE where_definition]
  1. sum仅对数值起作用,否则会报错
  2. 对多列求和,“,”不能少
avg

avg函数返回满足where条件的一列的平均值

select avg(列名) {, avg(列名)...} from tablename [WHERE where_definition]
max/min

max/min函数返回满足where条件的一列的最大/最小值

select max(列名) from tablename [WHERE where_definition]

使用group by子句对列进行分组

select column1, column2, column3...from table group by column

使用having子句对分组后的结果进行过滤

select column1, column2, column3... from table group by column having...

字符串相关函数

CHARSET(str) 返回字串字符集
CONCAT(string2 [,...]) 连接字串
INSTR(string,substring) 返回substring在string中出现的位置,没有则返回0
UCASE(string2) 转换成大写
LCASE(string2) 转换成小写
LEFT(string2, length) 从string2的左边起取length个字符
LENGTH(string) string长度[按照字节]
REPLACE(str,search_str, repalce_str) 在str中用replace_str替换search_str
STRCMP(string1,string2) 逐字符比较两字串大小
SUBSTRING(str, position [,length]) 从str的position开始[从1开始计算],取length个字符
LTRIM(string2) RIRM(string2) trim(字符串) 去除前端空格或后端空格

数学相关函数

ABS(num) 绝对值
BIN(decimal_number) 十进制转二进制
CEILING(number2) 向上取整,得到比num2大的最小整数
CONV(number2,from_base,to_base) 进制转换
FLOOR(number2) 向下取整,得到比num2小的最大整数
FORMAT(number,decimal_places) 保留小数位数
HEX(DecimalNumber) 转十六进制
LEAST(number,number2 [,...]) 求最小值
MOD(numerator,denominator) 求余
RAND([seed]) rand()返回一个随机浮点数v,范围在0到1之间(即,其范围为0<=v<=1.0),若已指定一个整数参数N,则它被用作种子值,用来产生重复序列

时间日期相关函数

CURRENT_DATE() 当前日期
CURRENT_TIME() 当前时间
CURRENT_TIMESTAMP() 当前时间戳
DATE(datetime) 返回datetime的日期部分
DATE_ADD(date2, INTERVAL d_value d_type) 在date2中加上日期或时间
DATE_SUB(date2,INTERVAL d_value d_type) 在date2上减去一个时间
DATEDIFF(date1,date2) 两个日期差(结果是天)
TIMEDIFF(date1,date2) 两个时间差(多少小时多少分钟多少秒)
NOW() 当前时间
YEAR|Month|DAY|DATE (datetime) 年月日
unix_timestamp() 返回的是1970-1-1到现在的秒数
FROM_UNIXTIME() 可以把一个unix_timestamp()秒数转成一个指定格式的日期
  1. DATE_ADD()中的interval后面可以是year minute second day等
  2. DATE_SUB()中的interval后面可以是year minute second day等
  3. DATEDIFF(date1,date2)的到的是天数,而且是date1-date2的天数,因此可以取负数
  4. 从DATE到DATEDIFF这四个函数的日期类型可以是date,datetime或者timestamp

加密和系统函数

USER() 查询用户
DATABASE() 数据库名称
MA5(str) 为字符串腾出一个MD5 32的字符串,(用户密码)加密
PASSWORD(str) 从原文密码str计算并返回密码字符串,通常用于对mysql数据库的密码加密

流程控制函数

IF(expr1, expr2, expr3) 如果expr1为True,则返回expr2,否则返回expr3
IFNULL(expr1,expr2) 如果expr1不为空,则返回expr1,否则返回expr2
SELECT CASE WHEN expr1 THEN expr2 WHEN
expr3 THEN expr4 ELSE expr5 END;(类似多重分支) 如果expr1为TRUE,则返回expr2,如果expr3为t,则返回expr4,否则返回expr5

查询加强

  1. 日期类型可以直接比较,但需要注意格式
  2. like操作符
    1. %:表示0到多个字符
    2. _:表示单个字符
  3. 查询表结构DESC 表名

分页查询

select ... limit start, rows表示从第start+1行开始取,取出rows行,start从0开始计算

-- 一个公式
SELECT * FROM emp ORDER BY empno LIMIT 每页显示记录数 * (第几页-1), 每页显示记录数

总结

如果select语句同时含有group by,having,limit,order by,那么他们的顺序是group by,having,order by,limit

多表查询

多表查询是指基于两个和两个以上的表查询。在实际应用中,查询单个表可能不能满足你的需求,需要使用到(dept表和emp表)
在默认情况下,当两个表查询时,规则:

  1. 从第一张表中,取出一行和第二张表的每一行进行组合,返回结果[含有两张表的所有列]
  2. 一共返回的记录数=第一张表的行数*第二张表的行数
  3. 这样多表查询默认处理返回的结果,称为笛卡尔集
  4. 解决多表的关键就是要写出正确的过滤条件
  5. 多表查询的条件不能少于表的个数-1,否则会出现笛卡尔集

自连接

自连接是指在同一张表的连接查询[将同一张表看作两张表]

SELECT worker.`ename`, boss.`ename` AS bossName
	FROM emp worker, emp boss
	WHERE worker.`mgr` = boss.`empno`;
  1. 把同一张表当两张表使用
  2. 需要给这个表起别名:表名 表别名
  3. 如果列名不明确,可以指定列的别名:列名 as 列的别名

子查询

子查询是指嵌入在其它sql语句中的select语句,也叫嵌套查询

  1. 单行子查询是指指返回一行数据的子查询语句
  2. 多行子查询指返回多行数据的子查询,有的时候可以使用关键字in
  3. 子查询可以当作临时表使用
  4. 在多行子查询中可以使用all(全部)或者any(其中一个)操作符
SELECT ename,sal,deptno
	FROM emp
	WHERE sal > ALL(
		SELECT sal
			FROM emp
			WHERE deptno = 30
		)
  1. 多列子查询:查询返回多个列数据的子查询语句(字段1, 字段2...)=(select 字段1, 字段2 from ...)

表复制

需要海量数据时,可以用此法为表创建海量数据

  1. 自我复制(蠕虫复制)
INSERT INTO my_tab01
	SELECT * FROM my_tab01;
  1. 如何删除掉一张表的重复记录
CREATE TABLE my_tab02 LIKE emp; -- 这个语句把emp表的结构复制到my_tab02
INSERT INTO my_tab02
	SELECT * FROM my_tab02;

CREATE TABLE my_tmp LIKE my_tab02;
INSERT INTO my_tmp
	SELECT DISTINCT * FROM my_tab02;
DELETE FROM my_tab02;
INSERT INTO my_tab02
	SELECT * FROM my_tmp;
DROP TABLE my_tmp;

合并查询

在实际应用中,为了合并多个select语句的结果,可以使用集合操作符号union,union all

  1. union all: 该操作用于取得两个结果集的并集。当使用该操作符时,不会取消重复行
select ename, sal, job from emp where sal > 2500 
union all
select ename, sal, job from emp where job = 'MANAGER';
  1. union:该操作与union相似,但是会自动去掉结果集中重复行
select ename, sal, job from emp where sal > 2500
union all select ename, sal, job from emp where
job = 'manager';

表外连接

  1. 左外连接(左边的表完全显示)select .. from 表1 left join 表2 on 条件[表1:就是左表 表2:就是右表]
SELECT `name`, stu.id,grade
	FROM stu LEFT JOIN exam
	ON stu.id = exam.id
  1. 右外连接(右边的表完全显示)select .. from 表1 right join 表2 on 条件【表1:就是左表 表2:就是右表】

约束

约束用于确保数据库数据满足特定得商业规则。在mysql中,约束包括:not null、unique、primary key、foreign key和check五种

primary key(主键)

  1. 基本语法字段名 字段类型 primary key
  2. 用于唯一的标识表行的数据,当定义逐渐约束后,该列不能重复

细节说明

  1. primary key不能重复而且不能为null
  2. 一张表最多只能有一个主键,但可以是符合主键
  3. 主键的指定方式有两种
    1. 直接在字段名后指定字段名 primary key
    2. 在表定义最后写primary key(列名);
  4. 使用desc 表名,可以看到primary key的情况
  5. 实际开发中,每个表往往都会设计一个主键

not null(非空)

  1. 如果在列上定义了not null,那么当插入数据时,必须为列提供数据
  2. 基本语法字段名 字段类型 not null

unique(唯一)

  1. 当定义了唯一约束后,该列值是不能重复的
  2. 基本语法字段名 字段类型 unique

细节说明

  1. 如果没有指定not null,则unique字段可以有多个null
  2. 一张表可以有多个unique字段

foreign key(外键)

  1. 用于定义主表和从表之间的关系:外键约束要定义在从表上,主表则必须具有主键约束或是unique约束,当定义外键约束后,要求外键列数据必须在主表的主键列存在或是为null
  2. 基本语法FOREIGN KEY (本表字段名) REFERENCES 主表名(主键名或unique字段名)

细节说明

  1. 外键指向的表的字段,要求是primary key或者是unique
  2. 表的类型是innodb,这样的表才支持外键
  3. 外键字段的类型要和主键字段的类型一致(长度可以不同)
  4. 外键字段的值,必须在主键字段中出现过,或者为null[前提是外键字段允许为null]
  5. 一旦建立主外键的关系,数据就不能随意删除了

check

  1. 用于强制行数据必须满足的条件,假定在sal列上定义了check约束,并要求sal列值在10002000,如果不在10002000之间就会提示出错。oracle和sql server均支持check,但是mysql5.7目前还不支持check,只做语法校验,但不会生效。在mysql中实现check的功能,一般是在程序中控制,或者通过触发器完成
  2. 基本语法列名 类型 check (check条件)

自增长

  1. 基本语法字段名 整形 primary key auto_increment
  2. 添加自增长字段的方式
insert into xxx(字段1,字段2...) values(null,'值',...);
insert into xxx(字段2...) values('值1','值2'...);
insert into xxx values(null,'值1',...);

使用细节

  1. 一般来说自增长是和primary key配合使用的
  2. 自增长也可以单独使用(但是需要配合一个unique)
  3. 自增长修饰的字符为整数型(虽然小数也可以但是使用的非常非常少)
  4. 自增长默认从1开始,可以通过alter table 表名 auto_increment = xxx;进行修改
  5. 如果添加数据时,给自增长字段(列)指定的有值,则以指定的值为准,如果指定了自增长,一般来说,按照自增长的规则来添加数据

索引

索引是最具性价比的提高数据库性能的方式

索引的类型

  1. 主键索引,主键自动的为主索引(类型primary)
  2. 唯一索引(UNIQUE)
  3. 普通索引(INDEX)
  4. 全文索引(FULLTEXT) [适用于MyISAM]

开发中考虑使用全文搜索Solr和和ElasticSearch(ES)

索引使用

  1. 添加索引
-- 第一种方式
create [UNIQUE] index index_name on tbl_name (col_name[(length)][ASC|DESC],...)
-- 第二种方式
alter table table_name ADD INDEX [index_name] (index_col_name)
  1. 添加主键(索引)
ALTER TABLE 表名 ADD PRIMARY KEY(列名,...);
  1. 删除索引
-- 第一种方式
DROP INDEX index_name ON tbl_name;
-- 第二种方式
alter table table_name drop index index_name;
  1. 删除主键索引
alter table t_b drop primary key;
  1. 查询索引
    1. show index from 表名
    2. show indexes from 表名
    3. show keys from 表名
    4. desc 表名

创建索引规则

  1. 较频繁的作为查询条件字段应该创建索引
  2. 唯一性太差的字段不适合单独创建索引,即使频繁作为查询条件
  3. 更新非常频繁的字段不适合创建索引
  4. 不会出现在where子句中字段不该创建索引

事务

事务用于保证数据的一致性,它由一组相关的dml语句组成,该组的dml语句要么全部成功,要么全部失败。如:转账就要用事务来保证数据的一致性

事务和锁

当执行事务操作时(dml语句),mysql会在表上加锁,防止其他用户改表的数据

mysql数据库控制台事务的几个重要操作

  1. start transaction -- 开始一个事务
  2. savepoint 保存点名-- 设置保存点
  3. rollback to 保存点名 -- 回退事务
  4. rollback --回退全部事务
  5. commit -- 提交事务,所有的操作生效,不能回退

注意事项

  1. 如果不开始事务,默认情况下,dml操作是自动提交的,不能回滚
  2. 如果开始一个事务,但没有创建保存点,可以执行rollback,默认就是回到事务开始的状态
  3. 可以在这个事务中(还没有提交时),创建多个保存点
  4. 可以在事务没有提交前,选择回退到哪个保存点
  5. mysql的事务机制需要innodb的存储引擎才可以使用,myisam不支持
  6. 开始一个事务start transaction,set autocommit=off;这两种;方式都可以

回退事务

保存点是事务中的点,用于去取消部分事务,当结束事务时,会自动的删除该事务所定义的所有保存点。当执行回退事务时,通过指定保存点可以回退到指定的点

提交事务

使用commit语句可以提交事务,当执行了commit语句后,会确认事务的变化、结束事务、删除保存点、释放锁,数据生效。当使用commit语句结束事务后,其他会话将可以查看到事务变化后的新数据【所有数据正式生效】

事务隔离级别(✓可能出现 x不会出现)

Mysql隔离级别定义了事务与事务之间的隔离程度

Mysql隔离级别 脏读 不可重复读 幻读 加锁读
读未提交(read uncommitted) 不加锁
读已提交(read committed) x 不加锁
可重复读(repeatable read) x x x 不加锁
可串行化(serializable) x x x 加锁
  1. 多个连接开启各自事务操作数据库中数据时,数据库系统要负责隔离操作,以保证各个连接在获取数据时的准确性
  2. 如果不考虑隔离性,可能会引发如下问题:
    1. 脏读:当一个事务读取另一个事务尚未提交的修改时,产生脏读
    2. 不可重复读:同一查询在同一事务中多次进行,由于其他提交事务所做的修改或删除,每次返回不同的结果集,此时产生不可重复读
    3. 幻读:同一查询在同一事务中多次进行,由于其他提交事务所做的插入操作,每次返回不同的结果集,此时发生幻读

事务隔离级别相关操作

  1. 查看当前会话隔离级别select @@tx_isolation;
  2. 查看系统当前隔离级别select @@global.tx_isolation;
  3. 设置当前会话隔离级别set session transaction isolation level repeatable read;
  4. 设置系统当前隔离级别set global transaction isolation level repeatable read;
  5. mysql默认的事务隔离级别是repeatable read,一般情况下,没有特殊要求,没有必要修改(因为该级别可以满足绝大部分项目需求)
  6. 全局修改,修改my.ini配置文件,在最后加上参数
#可选参数有:READ-UNCOMMITTED,READ-COMMITTED,REPEATABLE-READ,SERIALIZABLE.
[mysql]
transaction-isolation = REPEATAVKE-READ

事务ACID特性

  1. 原子性(Atomicity):原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生
  2. 一致性(Consistency):事务必须使数据库从一个一致性状态变换到另外一个一致性状态
  3. 隔离性(Isolation):事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离
  4. 持久性(Durability):持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响

mysql表类型和存储引擎

  1. MySQL的表类型由存储引擎(Storage Engined)决定,主要包括MyISAM、innoDB、Memory等
  2. MySQL数据表主要支持六种类型,分别是:CSV、Memory、ARCHIVE、MRG_MYISAM、MYISAM、InnoBDB。
  3. 这六种类型又分为两类,一类是“事务安全型”(transaction-safe),比如:InnoDB;其余都属于第二类,称为“非事务安全型”(non-transaction-safe)[mysiam和memory]。

细节说明

  1. MyISAM不支持事务、也不支持外键,但其访问速度快,对事物完整性没有要求
  2. InnoDB存储引擎提供了具有提交、回滚和崩溃恢复能力的事务安全。但是比起MyISAM存储引擎,InnoDB写的处理效率差一些且会占用更多的磁盘空间以保留数据和索引
  3. MEMORY存储引擎使用存在内存中的内容来创建表,每个MEMORY表只实际对应一个磁盘文件。MEMORY类型的表访问非常的快,因为它的数据是放在内存中的,并且默认使用HASH索引。但是一旦服务关闭,表中的数据就会丢失掉,表的结构还在。

表的存储引擎的选择

  1. 如果你的应用不需要事务,处理的只是基本的CRUD操作,那么MyISAM是不二选择,速度快
  2. 如果需要支持事务,选择InnoDB
  3. Memory存储引擎就是将数据存储在内存中,由于没有磁盘I/O的等待,速度极快。但由于是内存存储引擎,所做的任何修改在服务器重启后都将消失
  4. 修改存储引擎alter table 表名 engine = 储存引擎;

视图(view)

视图是一个虚拟表,其内容由查询定义。同真实的表一样,视图包含列,其数据来自对应的真实表(基表)

  1. 视图是根据基表(可以是多个基表)来创建的, 视图是虚拟的表
  2. 视图也有列,数据来自基表
  3. 通过视图可以修改基表的数据
  4. 基表的改变,也会影响到视图的数据

基本使用

  1. create view 视图名 as select语句
  2. alter view 视图名 as select语句
  3. show create view 视图名
  4. drop view 视图名1,视图名2

使用细节

  1. 创建视图后,到数据库去看,对应视图只有一个视图结构文件(形式:视图名.frm)
  2. 视图的数据变化会影响到基表,基表的数据变化也会影响到视图
  3. 视图中可以再使用视图

最佳实践

  1. 安全。一些数据表有着重要的信息。有些字段是保密的,不能让用户直接看到。这时就可以创建一个视图,在这张视图中只保留一部分字段。这样,用户就可以查询自己需要的字段,不能查看保密的字段
  2. 性能。关系数据库的数据常常会分表存储,使用外键建立这些表之间的关系。这时,数据库查询经常会用到连接(JOIN)。这样做不但麻烦,效率相对也比较低。如果建立一个视图,将相关的表和字段组合在一起,就可以避免使用JOIN查询数据
  3. 灵活。如果系统中有一张旧的表,这张表由于设计的问题,即将被废弃。然而,很多应用都是基于这张表,不易修改。这时就可以建立一张视图,视图中的数据直接映射到新建的表。这样,就可以少做很多改动,也达到了升级数据表的目的

MySQL管理

mysql用户

mysql中的用户,都存储在系统数据库mysql中的user表中,以下是user表的重要字段说明

  1. host:允许登录的“位置”,localhost表示该用户只允许本机登录,也可以指定ip地址,比如:192.168.1.100
  2. user:用户名
  3. authentication_string:密码,是通过mysql的password()函数加密之后的密码
  4. 创建用户create user '用户名' @ '允许登录位置' identified by '密码',该操作在创建用户的同时会指定密码
  5. 删除用户drop user '用户名' @ '允许登录位置';
  6. 不同的数据库用户,登录到DBMS后,根据相应的权限,可以操作的数据库和数据对象(表、视图、触发器)都不一样
  7. 修改密码
    1. 修改自己的密码set password = password('密码');
    2. 修改他人的密码(需要有修改用户密码权限)set password for '用户名' @ '登录位置' = password('密码');

mysql权限

给用户授权

基本语法

grant 权限列表 on 库.对象 to '用户名' @ '登录位置' [identified by '密码']

说明
  1. 权限列表,多个权限用逗号隔开
    1. grant select on ......
    2. grant select,delete,create on ......
    3. grant all [privileges] on ......表示赋予该用户在该对象上的所有权限
  2. 特别说明
    1. *.*代表本系统中的所有数据库的所有对象(表,视图,存储过程)
    2. 库.*表示某个数据库中的所有数据对象(表,视图,存储过程等)
    3. identified by可以省略,也可以写出
      1. 若用户存在,就是修改用户的密码。
      2. 如果该用户不存在,就是创建该用户!
  3. 回收用户授权revoke 权限列表 on 库.对象名 from '用户名' @ '登录位置';
  4. 权限生效指令,若权限没有生效,可以执行FLUSH PRIVILEGES;

mysql管理细节

  1. 在创建用户的时候,如果不指定Host,则为%,%表示所有IP都有连接权限
  2. 也可以使用create user 'xxx'@'192.168.1.%'表示xxx用户在192.168.1.*的ip可以登录mysql
  3. 在删除用户的时候看,如果host不是%,需要明确指定'用户'@'host值'

JDBC和连接池

  1. JDBC为访问不同的数据库提供了统一的接口,为使用者屏蔽了细节问题
  2. Java程序员使用JDBC,可以连接任何提供了JDBC驱动 程序的数据库系统,从而完成对数据库的各种操作

JDBC API

JDBC API是一系列的接口,它统一和规范了应用程序与数据库的连接,执行SQL语句,并得到返回结果等各类操作,相关类和接口在java.sql与javax.sql包中

JDBC程序编写步骤

  1. 注册驱动 - 加载Driver类
  2. 获取连接- 得到Connection
  3. 执行增删改查 - 发送SQL给mysql执行
  4. 释放资源 - 关闭相关连接

获取数据库连接的方式

  1. 获取Driver实现类对象,这种方式属于静态加载,灵活性差,依赖强
Driver driver = new com.mysql.jdbc.Driver();

String url = "jdbc:mysql://localhost:3306/jdbc_db";

Properties info = new Properties();
info.setProperty("user", "root");
info.setProperty("password", "hsp");
Connection conn = driver.connect(url, info);
System.out.println(conn);
  1. 使用反射机制
Class clazz = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver) clazz.newInstant();

String url = "jdbc:mysql://localhost:3306/jdbc_db";

Properties info = new Properties();
info.setProperty("user", "root");
info.setProperty("password", "abc123");

Connection conn = driver.connect(url,info);
System.out.println(conn);
  1. 使用DriverManager替换Driver
Class clazz = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver) clazz.newInstance();

String url = "jdbc:mysql://localhost:3306/jdbc_db";
String user = "root";
String password = "hsp";

DriverManager.registerDriver(driver);

Connection conn = DriverManager.getConnection(url, user, password);
System.out.println(conn);
  1. 使用Class.forName自动完成注册驱动,一般使用这种方式
    1. mysql驱动5.1.6后可以无需Class.forName("com.mysql.jdbc.Driver");
    2. 从jdk1.5以后使用了jdbc4,不再需要显示调用class.forName()注册驱动而是自动调用驱动jar包下META_INF\services\java.sql.Driver文本中的类名称去注册
    3. 建议还是写上
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/jdbc_db";
String user = "root";
String password = "hsp";

Connection conn = DriverManager.getConnection(url, user, password);
System.out.println(conn);
  1. 使用配置文件,连接数据库,Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "root");中的字符串各个值,比如端口,数据库,用户名,密码为了方便,我们可以将信息写到.properties文件中,方便操作
user=root
password=root
url=jdbc:mysql://localhost:3306/girls
driver=com.mysql.jdbc.Driver
public void connect05() throws IOException {
    Properties properties = new Properties();
    properties.load(new FileInputStream("src\\mysql.properties"));
	String user = properties.getProperty("user");
	String password = properties.getProperty("password");
	String driver = properties.getProperty("driver");
	String url = properties.getProperty("url");

	Class.froName(driver);
	Connection connection = DriverManager.getConnection(url, user, password);

	System.out.println("方式5 " + connection);
}

ResultSet[结果集]

  1. 表示数据库结果集的数据集,通常通过执行查询数据库的语句生成
  2. ResultSet对象保持一个光标指向其当前的数据行。最初,光标位于第一行之前
  3. next方法将光标移动到下一行,并且由于在ResultSet对象中没有更多行时返回false,因此可以在while循环中使用循环来遍历结果集

Statement

  1. Statement对象用于执行静态SQL语句并返回其生成的结果的对象
  2. 在连接建立后,需要对数据库进行访问,执行命名或是SQL语句,可以通过
    1. Statement[存在SQL注入]
    2. PreparedStatement[预处理]
    3. CallableStatement[存储过程]
  3. Statement对象执行SQL语句,存在SQL注入风险
  4. SQL注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入2数据中注入非法的SQL语句或段命令,恶意攻击数据库
  5. 要防范SQL注入,只要用PreparedStatement(从Statement扩展而来)取代Statement就可以了

PreparedStatement

  1. PreparedStatement执行的SQL语句中的参数用问号(?)来表示,调用PreparedStatement对象的setXxx()方法来设置这些参数,setXxx()方法有两个参数,第一个参数是要设置的SQL语句中的参数的索引(从1开始),第二个是设置的SQL语句中的参数的值
  2. 调用executeQuery(),返回ResultSet对象
  3. 调用executeUpdate(),执行更新,包括增删改

预处理好处

  1. 不再使用+拼接sql语句,减少语法错误
  2. 有效的解决了sql注入问题
  3. 大大减少了编译次数,效率较高

小结

事务

  1. JDBC程序中当一个Connection对象创建时,默认情况下自动提交事务:每次执行一个SQL语句时,如果执行成功,就会向数据库自动提交,而不能回滚
  2. JDBC程序中为了让多个SQL语句作为一个整体执行,需要使用事务
  3. 调用Connection的setAutoCommit(false) 可以取消自动提交事务
  4. 在所有的SQL语句都成功执行后,调用Connection的commit();方法提交事务
  5. 在其中某个操作失败或出现异常时,调用Connection的rollback();方法回滚事务,默认回滚到事务开始的状态

批处理

  1. 当需要成批插入或者更新记录时,可以采用Java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理,通常情况下比单独提交处理更有效率
  2. JDBC的批量处理语句包括下面方法
    1. addBatch():添加需要批量处理的SQL语句或参数
    2. excuteBatch():执行批量处理语句
    3. clearBatch():清空批处理包的语句
  3. JDBC连接MySQL时,如果要使用批处理功能,请在url中加参数?rewriteBatchedStatement=true
  4. 批处理往往和PreparedStatement一起搭配使用,可以既减少编译次数,又减少运行次数,效率大大提高

数据库连接池

  1. 预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去
  2. 数据库连接池负责分配、管理和释放数据库连接,而不是重新建立一个
  3. 当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中

数据库连接池种类

  1. JDBC的数据库连接池使用javax.sql.DataSource来表示,DataSource只是一个接口,该接口通常由第三方提供实现
  2. C3P0数据库连接池,速度相对较慢,稳定性不错(hibernate, spring)
  3. DBCP数据库连接池,速度相对c3p0较快,但不稳定
  4. Proxool数据库连接池,有监控连接池状态的功能,稳定性较c3p0差一点
  5. BoneCP 数据库连接池,速度快
  6. Druid(德鲁伊)是阿里提供的数据库连接,集DBCP、C3P0、Proxool优点于一身的数据库连接池

C3P0连接方式

  1. 方式1
        //1. 创建一个数据源对象
        ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
        //2. 通过配置文件换取相关连接的信息
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\jdbc.properties"));
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String driver = properties.getProperty("driver");
        String url = properties.getProperty("url");

        //给数据源comboPooledDataSource设置相关的参数
        //连接管理由comboPooledDataSource来管理
        comboPooledDataSource.setDriverClass(driver);
        comboPooledDataSource.setJdbcUrl(url);
        comboPooledDataSource.setUser(user);
        comboPooledDataSource.setPassword(password);

        //设置初始化连接数
        comboPooledDataSource.setInitialPoolSize(10);
        //最大连接数
        comboPooledDataSource.setMaxPoolSize(50);
        Connection connection = comboPooledDataSource.getConnection();
        System.out.println("连接成功");
        connection.close();
  1. 方式2
// 将c3p0提供的c3p0.config.xml拷贝到src文件目录下

 ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("hello");

        Connection connection = comboPooledDataSource.getConnection();
        System.out.println("连接OK");
        connection.close();
<c3p0-config>

  <named-config name="hello"> 
<!-- 驱动类 -->
  <property name="driverClass">com.mysql.jdbc.Driver</property>
  <!-- url-->
  	<property name="jdbcUrl">jdbc:mysql://127.0.0.1:3306/qmd_db02</property>
  <!-- 用户名 -->
  		<property name="user">root</property>
  		<!-- 密码 -->
  	<property name="password">826431</property>
  	<!-- 每次增长的连接数-->
    <property name="acquireIncrement">5</property>
    <!-- 初始的连接数 -->
    <property name="initialPoolSize">10</property>
    <!-- 最小连接数 -->
    <property name="minPoolSize">5</property>
   <!-- 最大连接数 -->
    <property name="maxPoolSize">10</property>

	<!-- 可连接的最多的命令对象数 -->
    <property name="maxStatements">5</property> 
    
    <!-- 每个连接对象可连接的最多的命令对象数 -->
    <property name="maxStatementsPerConnection">2</property>
  </named-config>
</c3p0-config>

德鲁伊连接池

    	//1. 加入Druid jar包
        //2. 加入配置文件,将该文件拷贝到项目的src目录
        //3. 创建Properties对象,读取配置文件
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\druid.properties"));

        //4. 创建一个指定参数的数据库连接池
        DataSource dataSource =
                DruidDataSourceFactory.createDataSource(properties);
        Connection connection = dataSource.getConnection();
        System.out.println("连接成功");
        connection.close();

Apache-DBUtils

commons-dbutils是Apache组织提供的一个开源JDBC工具库类,它是对JDBC的封装,使用dbutils能极大简化jdbc编码的工作量

DbUtils类

  1. QueryRunner类:该类封装了SQL的执行,是 线程安全的。可以实现增、删、改、查、批处理
  2. 使用QueryRunner类实现查询
  3. ResultSetHandler接口:该接口用于处理java.sql.ResultSet,将数据按要求转换为另一种形式
    1. ArrayHandler:把结果集中的第一行数据转换成对象数组
    2. ArrayListHandler:把结果集中的每一行数据都转成一个数组,再存放到List中
    3. BeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中
    4. BeanListHandler:将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里
    5. ColumnListHandler:将结果集中某一列的数据存放到List中
    6. KeyedHandler(name):将结果集中的每行数据都封装到Map里,再把这些map再存到一个map里,其key为指定的key
    7. MapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值
    8. MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后再存放到List
package com.edu.jdbc.datasource;

import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.junit.jupiter.api.Test;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;

public class DBUtils_USE {
    @Test
    public static void main(String[] args) throws SQLException {
        //1. 得到连接(druid)
        Connection connection = JDBCUtilsByDruid.getConnection();
        //2. 使用DBUtils类和接口,先引入DBUtils相关的jar,加入到本Project
        //3. 创建QueryRunner
        QueryRunner queryRunner = new QueryRunner();
        //4. 现在可以执行相关方法,返回ArrayList结果集了
        String sql = "select id, name from actor where id >= ?";
        //(1) query方法执行sql语句,得到resultset ---封装到 --> ArrayList集合中
        //(2) 返回集合
        //(3) connection:连接
        //(4) sql : 执行的sql语句
        //(5) new BeanListHandler<>(Actor.class):在将resultset -> Actor对象 -> 封装到ArrayList
        //    底层使用反射机制,去获取Actor类的属性,然后进行封装
        //(6) 把1赋值给sql语句中的?,可以有多个值,是可变参数Object... params
        //(7) 底层得到的resultset,会在query关闭,关闭PreparedStatement
        List<Actor> list =
                queryRunner.query(connection, sql, new BeanListHandler<>(Actor.class), 1);
        for (Actor actor : list) {
            System.out.println(actor);
        }
        //释放资源
        JDBCUtilsByDruid.close(null, null, connection);
    }
}

源码分析

public  <T> T query(Connection conn, String sql, ResultSetHandler<T> rsh, Object... params) {
    PreparedStatement stmt = null;//定义一个PreparedStatement都西昂
    ResultSet rs = null;//接收返回的结果集
    Object result = null;//返回ArrayList

    try {
        stmt = this.preparedStatement(conn, sql);//创建PreparedStatement
        this.fillStatement(stmt, params);//对sql语句的?赋值
        rs = this.wrap(stmt.executeQuery());//执行sql,返回resultset
        result = rsh.handle(rs);//返回的result --> arrayList[result] [使用到反射,对传入的class独享进行处理]
    } catch (SQLException var33) {
        this.rethrow(var33, sql, params);
    } finally {
        try {
            this.close(rs);//挂你resultset
        } finally {
            this.close((Statement) stmt);//关闭preparedstatement对象
        }
    }
}

表和JavaBean的类型映射关系

int,double等在Java中都用包装类,因为mysql中的所有类型都可能是null,而Java只有引用数据类型才有null值,注意日期类型在JavaBean中用Date类或者String类

BasicDao

  1. DAO:data access object 数据访问对象
  2. 这样的通用类,称为BasicDao,是专门和数据库交互的,即完成对数据库(表)的crud操作
  3. 在BasicDao的基础上,实现一张表对应一个Dao,更好的完成功能,比如Customer表-Customer.java类(javabean)-CustomerDao.java

正则表达式(regular expression)

正则表达式是对字符串执行模式匹配的技术

  1. 一个正则表达式,就是用某种模式去匹配字符串的一个公式。很多人因为它们看上去比较古怪而且复杂所以不敢去使用,不过经过练习后,就觉得这些复杂的表达式写起来还是相当简单的,而且,一旦弄懂它们后,就能把数小时辛苦而且易错的文本处理工作缩短在几分钟(甚至几秒钟)内完成
  2. 正则表达式不是只有java才有,实际上很多编程语言都支持正则表达式进行字符串操作

正则底层实现

String regstr = "\\d\\d\\d\\d";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
  1. \\d表示一个任意的数字
  2. matcher.find()完成的任务
    1. 根据指定的规则,定位满足规则的子字符串
    2. 找到后,将子字符串的开始的索引,记录到matcher对象的属性int[] groups;groups[0];,把该子字符串结束的索引+1的值记录到groups[1];
    3. 同时记录oldLast的值为该子字符串结束的索引+1的值即4,即下次执行find时,就从4开始匹配
  3. mtcher.group(0)分析
public String group(int group) {
    if (first < 0)
    	throw new IllegalStateException("No match found");
    if (group < 0 || group > groupCount()) 
        throw new IndexOutOfBoundsException("No group " + group);
	if ((groups[group*2] == -1) || (groups[group*2+1] == -1))
        return null;
	return getSubSequence(groups[group * 2], groups[group * 2 + 1]).toString;
}
/*
	1. 根据groups[0] 和 group[1] 的记录的位置,从content开始截取子串返回,前闭后开
	2. 如果再次执行find方法,仍然按第四条执行
*/
  1. 接下来按以下代码进行分析,考虑分组的情况
String regstr = "(\\d\\d)(\\d\\d)";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
  1. matcher.find()完成的任务(考虑分组)
    1. 正则表达式中有() 表示分组,第一个()表示第一组
    2. 根据指定的规则,定位满足规则的子字符串
    3. 找到后,将子字符串的开始的索引,记录到matcher对象的属性int[] groups;
      1. groups[0];将子字符串的开始的索引记录到该位置,把该子字符串结束的索引+1的值记录到groups[1];
      2. groups[2]记录第一组()匹配到的字符串的开始位置,groups[3]记录改组字符串结束的位置+1
      3. 第二组小括号匹配到的字符串开始的位置记录到groups[4],结束的位置+1记录到groups[5]
      4. 若有更多的组依次类推

总结

  1. 如果正则表达式有()即分组
  2. 取出匹配的字符串规则
    1. group(0) 表示匹配到的子字符串
    2. group(1) 表示匹配到的子字符串的第一组子字符串
    3. group(2) 表示匹配到的子字符串的第二组子字符串
    4. 依次类推,但是分组的数不能越界

正则表达式语法

如果想要灵活的使用正则表达式,必须了解其中各种元字符的功能,元字符从功能上大致分为

  1. 限定符
  2. 选择匹配符
  3. 分组组合和反向引用符
  4. 特殊字符
  5. 字符匹配符
  6. 定位符

元字符(Metacharacter)-转义符\

在使用正则表达式去检索某些特殊字符的时候,需要用到转义符号,否则检索不到结果,甚至会报错。在Java的正则表达式中,两个\代表其他语言中的一个\。需要用到转义符号的字符有: . * + ( ) $ / \ ? [ ] ^ { }

元字符-字符匹配符

符号 符号 示例 解释 匹配输入
[ ] 可接收的字符列表 [efgh] e f g h中的任意一个字符
[^] 不接收的字符列表 [^abc] 除a b c之外的任意一个字符,包括数字和特殊符号
- 连字符 A-Z 任意单个大写字母
. 匹配除\n以外的任何字符 a..b 以a开头,b结尾,中间包括2个任意字符的长度为4的字符串 aaab
aefb
a35b
a#*b
\\d 匹配单个数字字符,相当于[0-9] \\d{3}(\\d)? 包含3个或4个数字的字符串 123
9876
\\D 匹配单个非数字字符相当于[^0-9] \\D(\\d)* 以单个非数字字符开头,后接任意个数字字符串 a
A342
\\w 匹配单个数字、大小写字母字符以及_相当于[0-9a-zA-Z] \\d{3}\\w 以3个数字字符开头的长度为7的数字字母字符串 234abcd
12345pe
\\W 匹配单个非数字、大小写字母字符以及不是_,相当于[^0-9a-zA-Z] \\W+\\d 以至少1个非数字字母字符开头,2个数字字符结尾的字符串 #29

?@10 |

| \\s | 匹配任何空白字符(空格、制表符等) | | | |
| \\S | 匹配任何非空白字符,和\s相反 | | | |
| \\. | 匹配.本身 | | | |

Java正则表达式默认是区分字母大小写的。

  1. (?i)abc表示abc都不区分大小写
  2. a(?i)bc表示bc不区分大小写
  3. a((?i)b)c表示只有b不区分大小写
  4. Pattern pat = Pattern.compile(regEx, Pattern.CASE_INSENSITIVE);

元字符-选择匹配符

在匹配某个字符串的时候是选择性的,即:既可以匹配这个,又可以匹配那个

符号 符号 示例 解释
| 匹配"|"之前或之后的表达式 ab|cd ab或者cd

元字符-限定符

用于指定其前面的字符和组合项连续出现多少次

符号 含义 示例 说明 匹配输入
* 指定字符重复0次或n次(无要求) (abc)* 仅包含任意个abc的字符串,等效于\w* abc
abcabcabc
+ 指定字符重复一次或n次(至少一次) m+(abc)* 以至少1个m开头,后接任意个abc的字符串 m
mabc
mabcabc
? 指定字符重复0次或1次(最多一次) m+abc? 以至少1个m开头,后接ab或abc的字符串 mab
mabc
mmmab
mmabc
只能输入n个字符 [abcd] 由abcd中字母组成的任意长度为3的的字符串 abc
dbc
adc
指定至少n个匹配 [abcd] 由abcd中字母组成的任意长度不小于3的字符串 aab
dbc
aaabdc
指定至少n个但不多于m个匹配 [abcd] 由abcd中字母组成的任意长度不小于3,不大于5的字符串 abc
abcd
aaaaa
bcdab

注意:java匹配默认贪婪匹配,即尽可能匹配多的,若要实现非贪婪匹配,则需在限定符后面加个?

元字符-定位符

定位符,规定要匹配的字符串出现的位置,比如在字符串的开始还是在结束的位置

符号 含义 示例 说明 匹配输入
^ 指定起始字符 [1]+[a-z]* 以至少1个数字开头,后接任意个小写字母的字符串 123
6aa
555edf
$ 指定结束字符 [2]\\-[a-z]+$ 以1个数字开头后连接字符"-",并以至少1个小写字母结尾的字符串 1-a
\\b 匹配目标字符串的边界 han\\b 这里说的字符串的边界指的是子串间有空格,或者是目标字符串的结束位置 hanshunping
sphan
nnhan
\\B 匹配目标字符串的非边界 han\\B 和\b的含义刚刚相反 hanshunping
sphan
nnhan

分组

常用分组

常用分组构造形式 说明
(pattern) 非命名捕获。捕获匹配的子字符串。编号为0的第一个捕获是由整个正则表达式模式匹配的文本,其它捕获结果则根据左括号的顺序从1开始自动编号
(?pattern) 命名捕获。将匹配的子字符串捕获到一个组名称或编号名称中。用于name的字符串不能抱哈任何标点符号,并且不能以数字开头。可以使用单引号替代尖括号,例如(?'name')

特别分组

常用分组构造形式 说明
(?:pattern) 匹配pattern但不捕获该匹配的子表达式,即它是一个非捕获匹配,不存储供以后使用的匹配。这对于用"or"字符(|)组合模式部件的情况很有用。例如,industr(?:y|ies)是比'industry|industries'更经济的表达式
(?=pattern) 它是一个非捕获匹配。例如,'windows(?=95|98|NT|2000)'匹配"Windows 2000"中的"Windows",但不匹配"Windows 3.1"中的"Windows"
(?!pattern) 该表达式匹配不处于匹配pattern的字符串的起始点的搜索字符串。它是一个非捕获匹配。例如,'Windows(?!95|98|NT|2000)'匹配"Windows 3.1"中的"Windows",但不匹配"Windows 2000"中的"Windows"

正则表达式三个常用类

java.util.regex包主要包括以下三个类Pattern类、Matcher类和PatternSyntaxException

Pattern类

pattern对象是一个正则表达式对象。Pattern类没有公共构造方法。要创建一个Pattern对象,调用其公共静态方法,它返回一个Pattern对象。该方法接受一个正则表达式作为它的第一个参数,比如:Pattern r = Pattern.compile(pattern);。find方法是部分匹配,matches方法是整体匹配

Matcher类

Matcher对象是对输入字符串进行解释和匹配的引擎。与Pattern类一样,Matcher也没有公共构造方法。需要调用Pattern对象的matcher方法来获得一个Matcher对象

相关方法

  1. public int start() 返回以前匹配的初始索引
  2. public int start(int group) 返回在以前的匹配操作期间,由给定组所捕获的子序列的初始索引
  3. public int end() 返回最后匹配字符之后的偏移量
  4. public int end(int group) 返回在以前的匹配操作期间就,由给定组所捕获子序列的最后字符之后的偏移量
  5. public boolean lookingAt() 尝试将从区域开头开始的输入序列与该模式匹配
  6. public boolean find() 尝试查找与该模式匹配的输入序列的下一个子序列
  7. public boolean find(int start) 重置此匹配器,然后尝试查找 匹配该模式、从指定索引开始的输入序列的下一个子序列
  8. public boolean matches() 尝试将整个区域与模式匹配
  9. public Matcher appendReplacement(StringBuffer sb, String replacement) 实现非终端添加和替换步骤
  10. public StringBuffer appendTail(StringBuffer sb) 实现终端添加和替换步骤
  11. public String replaceAll(String replacement) 替换模式与给定替换字符串相匹配的输入序列的每个子序列
  12. public String replaceFirst(String replacement) 替换模式与给定替换字符串匹配的输入序列的第一个子序列
  13. public static String quoteReplacement(String s) 返回指定字符串的字面替换字符串。这个方法返回一个字符串,就像传递给Matcher类的aappendReplacement方法一个字面字符串一样工作

PatternSyntaxException

该类是一个非强制异常类,表示一个正则表达式模式中的语法错误

分组、捕获、反向引用

  1. 分组:可以用圆括号组成一个比较复杂的匹配模式,那么一个圆括号的部分可以看作是一个子表达式/一个分组
  2. 捕获:把正则表达式中子表达式/分组匹配的内容,保存到内存中以数字编号或显式命名的组里,方便后面引用,从左向右,以分组的左括号为标志,第一个出现的分组的组号为1,第二个为2,以此类推。组0代表的是整个正则式
  3. 反向引用:圆括号的内容被捕获后,可以在这个括号后被使用,从而写出一个比较实用的匹配模式,这个称为反向引用,这种引用即可以在正则表达式内部,也可以在正则表达式外部,内部反向引用\分组号,外部反向引用$分组号

补充

Java8新特性
MySQL


  1. 0-9 ↩︎

  2. 0-9 ↩︎