Day01
类名命名:每个单词首字母大写
main方法是程序的入口
一个java源文件中,可以出现多个class定义,比如有5个class,那么编译之后,就会生成5个class文件,有多少class定义,就会生成多少个class文件
public : 公共的,并且一个java文件中,只能有一个public类,且public的类的类名,必须和文件名一致
class :类,定义类的关键字
HelloWorld : 类名,只能是: 大小写字母,美元符号$,下划线_,数字,且数字不能开头,不能使用关键字和保留字,而且public的类的类名,必须和文件名一致
命名时需要注意:
只能有大小写字母 A-Za-z 数字0-9 下划线 _ 美元符号 $ 数字不能打头
不能使用关键字和保留字
关键字 : java中已经被使用的单词 比如 public static void class
保留字 : java中还没有使用的关键字,但是可能在后续新版本中使用到 goto
不能单独使用关键字和保留字,比如 public1 或者 classpublic 都是可以的
JVM特性:
四大特性:
跨平台
自动垃圾回收机制
多线程
面向对象
是JVM跨平台
数据类型
命名规则
强制 :
数字,大小写字母,美元符号,下划线且不能数字开头
不能使用关键字和保留字
非强制 :
建议望文知义
驼峰命名法
ZhanGao
所有需要命名的地方,都要符合这个标准
文件名
类名
方法名
变量名
包名
数据存储
1 程序
一堆命令的集合
是个静态概念,一般保存在硬盘中
2 文件类型
文本,二进制,可执行
文本文件 给人看的
二进制文件 给电脑看的
3 编辑器
用于编写文本文件的工具程序叫编辑器
4 数据的存储
数据要运算,必须先存储
5 存储方式
内存 : 无限延伸的线
硬盘 : 螺旋线
光盘 : 同心圆
6 存储单位
bit : 比特,一个比特就是一个电子位
byte : 字节 = 8 bit
为了表示符号位(负数),所以最高位作为符号位, 1 表示负数 0 表示 正数
-128 ~ 127
Day02
1.3 进制
Java中没有办法直接表示二进制
十进制表示法
int i_1 = 10;
八进制表示法
int i_2 = 012;
十六进制表示法
int i_4 = 0x1f;
双引号是字符串,里面的数据不会被解释,所以 i_4 是找不到内存中的0x1f的
System.out.println("i_4");
System.out.println(i_4);
以十六进制展示
System.out.println(Integer.toHexString(i_4));
1.4 类型使用
1.4.1 整数型
short s_1 = 123;
byte b_1 = 123;
int i_5 = 2147483647;
java中整数默认为 int(整型)
所以 以下这个代码,是进行了自动类型转换,把int值复制给long类型
long l_1 = 2147483647;
// 超过int上限 报错
// long l_2 = 2147483648 ;
// 如何声明long的值, 加 L/l 建议 L ,因为避免 l和1不容易区分
long l_3 = 1232167864124L;
// 最大值 : 9223372036854775807
System.out.println(Long.MAX_VALUE);
1.4.2 浮点型
double d_1 = 1.2;
// 因为浮点型默认是double,所以 float声明必须要加F/f才可以
float f_1 = 1.2f;
// 强制类型转换为float,double强制转换为float
float f_2 = (float) 1.34;
System.out.println(f_2);
1.4.3 字符型
'a' 97
'b' 98
'A' 65
'B' 66
'0' 48
'1' 49
采用unicode编码
char c_1 = 'a';
char c_2 = '2';
char c_3 = '张';
char c_4 = ' ';
System.out.println((int)c_1);
System.out.println((int)c_3);
// 测
char c_5 = '\u6D4B';
System.out.println(c_5);
把有意义字符转换为无意义字符
java 中 \ 表示转移符
\t : 制表符
\\ : 转义转移符
\r\n : 换行符
char c_1 = '\'';
char c_2 = '\\';
char c_3 = '\t';
System.out.println(c_1);
System.out.println(c_2);
System.out.println('\n');
System.out.println(c_3);
Question 各种转义符需要了解 \n还是\r是换行符?
1.4.4 布尔型
boolean 不是 bool
1 只有两个值 : true/false,没有 1和0
2 默认值为false
3 不参与任何类型转换
4 占用1字节,全是0表示false, 0000001表示true
一般用于流程控制或者逻辑判断
boolean b_1 = false;
boolean b_2 = true;
// 不能转换
// boolean b_3 = (boolean)1;
if (b_2) {
System.out.println("为真");
}else{
System.out.println("为假");
}
1.5 类型转换
char --> char和int并列,并且char可以自动转换成int,可以向更高精度自动转换
强制转换有可能损失精度,造成数据不准确
byte b1 = 1;
short s1 = b1;
int i1 = s1;
// char c1 = s1;
char c1 = '1';
i1 = c1; char可以自动转换成int
// s1 = c1;
long l1 = c1;
float f1 = l1;
// l1 = f1; float需要强制转换成long,高精度转换成低精度
byte b2 = (byte) 131;
System.out.println(b2);
int i2 = 10;
byte b3 = (byte) i2;
System.out.println(i2);
1.6 混合运算
// 混合运算
// 当一个运算中出现多个类型的值的时候,结果一定是最大类型
int i_1 = 10;
long l_1 = 10;
long b_1 = i_1+l_1;
// byte , short , char , int 四种中任意一种或多种进行运算,结果都是int类型
int b_2 = b3+s1;
1.7 小结
Float,char,boolean三个的使用
自动转换和强制转换
转移符
混合运算
2.1 常量
常量 : 整个程序生命周期中,值不能更改
字面量/直接量:也是有数据类型的, 整数默认int,小数默认double
Final
// final 修饰的 不可更改
final int i1 = 1;
// i1 = 2;
System.out.println(1);
2.2 变量
可以更改的量,可以再程序执行中对值进行更改
可以复用
2.2.1 全局变量
什么是全局变量的概念 : 允许在类之外创建变量,和类同级别,那么所有的类都可以直接访问该变量,不需要二次引用
所谓二次引用,就是没有使用 . 操作符 xxx.xxxxx
首先 java不允许在类之外创建变量,但是可以将一个类的变量设置为public static修饰的,那么其他类就可以通过 该类的类名.变量名 来进行访问
全局变量的确定 : 安全性较差,容易命名冲突
所以 java引入了包概念,引用一个变量的时候必须通过该包和类才能访问
2.2.2 变量定义/声明
数据类型 变量名 ;
数据类型 变量名 = 赋值
数据类型划分内存空间 给空间命名 赋值(把数据保存到空间中)
空间范围,内存空间的划分
一个 {} 大括号 就是一个作用域,变量不能超过向上碰到的第一个大括号,但是向下可以穿透域中嵌套的所有子大括号
Int i = 10;
int a=3,b=2,c=10;
2.2.3 变量分类
局部变量 : 方法内部声明的变量是局部变量
静态变量 : 类体中使用static修饰的变量是静态变量
成员变量 : 类体中没有使用static修饰的变量是成员变量
2.2.4 变量调用
局部变量 : 当前方法中,声明之后,直接写变量名调用即可
静态变量 : 类名.静态变量名/当前类中调用当前类的静态变量时,类名可以省略,编译之后,JVM会自动补齐为类名调用
成员变量 : 对象引用.成员变量名
System.out.println(x1);
System.out.println(_02_Var.x1);
_02_Var aa = new _02_Var();
System.out.println(aa.x);
2.2.5 默认值
2.2.6 变量优先级
变量可以重名吗?
静态变量和成员变量 不能同名
但是局部变量和 静态变量/成员变量 是可以同名的
也就是说 有一个局部变量 i 那么 还可以再创建一个 静态变量/成员变量 i
如果静态变量和局部变量重名,那么优先使用局部变量
如果就想使用静态变量的话,可以使用类名来区分,也就是二次调用
static int age = 18;
public static void main(String[] args) {
int age = 22;
System.out.println(_04_Var.age);
}
运算操作符 +,-,*,/,% ++,–
运算符优先级 单目/单元运算符优先级最高 > 双目 > 三目
i++ 和 ++i 的区别
int a = 10;
int b = 3;
System.out.println(a + b);
System.out.println(a - b);
System.out.println(a * b);
System.out.println(a / b);
System.out.println(a % b);
double d1 = 0.1;
double d2 = 0.2;
// 0.30000000000000004
// 注意 不要使用小数做比较
System.out.println(d1 + d2);
System.out.println("----------------------");
// ++ : 把值取出,+1,再放回去
int k = 100;
// 只要是单独出现,不管k在前还是k在后,都一样,原数+1即可
// k++;
// ++k;
Question k++和++k区别需要完全搞明白
// 201 如果是 k++ ,就会申请一块临时空间,把k中的值先保存到临时空间中,然后k++的这个k 就指向临时空间中的值
// 然后计算++ , 计算完++ 后,继续后续操作 100+k
// 如果是 ++k 那么 k 就不需要指向临时空间了
// k++ : k使用的是++之前的值
// ++k : k使用的是++之后的值
// 但是 ++ 优先级 大于 双目和三目
k = k++ +k;
System.out.println(k);
int w = 10;
w = w++ * w;
System.out.println(w);
int m = 10;
int e = 2+m++;
System.out.println(e);
System.out.println(m);
int p= 2;
p = 2 + p++ + ++p + p++ + ++p;
// p = 2 + 2 + ++p + p++ + ++p; p=3
// p = 2 + 2 + 4 + 4 + 6; p=6
System.out.println§;
int x = 10;
x = 10 + x++ + ++x +(10 * x++) + ++x;
// x = 10 + 10 + 12 +120 + 14; x=14
System.out.println(x);
int i = 10;
i= i++ + (i +1);
System.out.println(i);
3.2 关系运算符
运算结果 是 boolean型 (true/false),可以叫布尔型表达式
= < <=
如果是基本类型,比较值的大小
如果是引用类型,比较内存地址
3.3 位运算符
& 位与 , 两边都是true 结果才是true,也可以两边是数字
| 位或 一边为true 结果就为true
! 位非,取反 !true 就是false , !false 就是true
^ 异或 ,两边不一样就是true
~ 按位非,转换为二进制,然后 每位取反
&& : 短路与,如果 第一个就为假,第二个表达式就不执行了,结果为假
|| : 短路或,或,如果第一个为真,第二个就不执行了
: 右移运算符(考虑符号位,符号位不移动,正数就是正数,负数就是负数)
8 >> 2
0000000 10 转换为二进制,然后符号位之后补两个0,最右边删除两位
右移 8 >> n 就等于 8 / 2^n
如果是 -8 >>2 就是补1
: 右移运算符(不考虑符号位)
如果是正数 和>> 没有区别,因为最前面都是0
但是负数就不一样了, >> 只会在符号位之后补1 , 而 >>> 在符号位之前 补0
所以 -1 >>> 1 就成了 2147483647 question 位移运算符需要搞清楚
day03
4. 运算符
4.1 赋值运算符
= : 把右边的赋值给左边,如果右边是个运算,需要把运算结果赋值给左边
+= : 左边和右边相加,结果赋值给左边
-= : 左边 减去 右边 结果赋值给左边
*= .....
/= .....
%= .....
虽然两个式子是等价的,但是还不一样, i++ 和 += 这些写法,不需要强制转换,当然精度还是会丢失,这种写法会自动帮我们强制转换
但是像 i = i+10; 这种 如果 i 是byte类型 或者short类型 就需要强制转换,因为byte,short,int,char四种类型进行混合运算时结果是int类型的
4.2 字符串连接符
4.3 三目运算符
也可以叫三元运算符
boolean条件表达式 ? 真语句 : 假语句;
5.2.1 if…else…
if( boolean表达式 ){ 表达式为true 就执行java代码 为false就不执行
java代码;
java代码;
}
if( boolean表达式 ){ 表达式为true 就执行java代码1 为false就执行 else 中 java代码2
java代码1;
java代码1;
}else{
java代码2;
......
}
if( boolean表达式 ){
java代码1;
java代码1;
}else if( boolean表达式 ){
java代码2;
......
}
if( boolean表达式 ){
java代码1;
java代码1;
}else if( boolean表达式 ){
java代码2;
......
}else{
.....
}
上面 1,3 有不执行情况, 2,4 一定会有一个分支执行
5.2.2 Switch
1.7之前 只能传入 int整型(能传入整型,也就可以传入 byte,short,char,自动类型转换)
1.7开始,包括1.7,可以传入 字符串
switch( 值 ){
case 值 :
java代码;
......
break;
case 值 :
java代码;
......
break;
case 值 :
java代码;
......
break;
default :
java代码;
}
如果所有的case 都不符合条件,就执行default
注意 break : 必须要写,如果不写 就会发生case穿透现象
合并 利用case穿透完成
5.3 循环结构
重复执行某些代码很多次
5.3.1 For
计数循环,在某个次数范围内,重复执行某些代码
for( 表达式1 ; 表达式2 ; 表达式3 ){
// 循环体
java代码;
}
初始值,终止条件,步长
缺一不可
5.3.2 While
while( boolean表达式 ){
循环体;
}
5.3.3 Do…while…
do{
}while(boolean表达式);
5.4 跳转语句
5.4.1 Break
1 用于switch中,结束分支语句,避免发生case穿透
2 用于循环中,终止当前循环
5.4.2 Continue
continue : 跳过当前次循环,继续下次
day04
6. 方法
6.1 概述和作用
一堆代码的集合,可重复使用
1)使程序变得更简短更清晰
2)有利于程序维护
3)提高程序开发效率
4)提高代码重用性
方法目的 : 代码重用
相同的操作,不用重复写多遍代码
方法就是有名字的代码块 : 一个大括号{} 就是一个代码块/语句块/代码段
java 中只有方法, c 中只有函数, C++就不一样了,写在类中,叫方法,写在类外叫函数
或者说 面向对象语言中 只有方法,面向过程中只有函数
函数 是可以直接调用的,不需要二次引用
6.2 方法声明
方法声明 :
结构 : [修饰符列表] 返回值类型 方法名 (参数1,参数2,参数3......) { 方法体; }
[] 加中括号 说明 可以有,可以没有,可以有多个
修饰符 :
权限控制 : public , protected , private 三选一 ,
其他修饰 : static ,synchronized
abstract , final 二选一
返回值类型 : 11种数据类型任意一种即可,如果不需要 返回值,就写 void
方法名 : 合法标识符即可
参数列表
形参 : 方法的声明处,规定传入的数据的类型以及个数
实参 : 方法的调用处,传入具体的数据
方法体 : java代码
return : 终止方法执行,并且return后不可以写代码
1 没有返回值类型的时候(void) : return可以有 可以没有,如果有,return只能起到结束方法运行的作用
并且 语法是 return;
2 如果有返回值的情况下(不是void) : 方法体中 必须要有return语句,并且return语句需要跟一个返回的数据
语法是 : 比如返回值为 int return int值 ;
6.3 方法调用
方法不调用,不执行,调用才执行,并返回到调用处
只需要考虑 调用顺序即可
JVM自动调用main方法
6.4 方法分类
变量分类 :
局部变量 : 方法内声明的变量
静态变量 : 类体中使用static修饰的变量
成员变量 : 类体中没有使用static修饰的变量
变量调用 :
局部变量 : 方法内部之间写变量名调用
静态变量 : 类名.静态变量名,并且当前类中类名可以省略
成员变量 : 对象引用.成员变量名
如何区分同名的静态变量和局部变量 : 类名调用
方法分类 :
静态方法 : 使用static修饰的方法,并且静态方法中不能有非静态引用
成员方法 : 没有使用static修饰的方法
构造方法 : 先不做了解
方法调用 :
静态方法 : 类名.静态方法名(参数);,当前类中 类名 可以省略
成员方法 : 对象引用.成员方法名(参数);
6.5 方法重载
名字 参数列表
参数列表不同 分为 : 个数不同和类型不同
运行起来的程序,就是指载入到内存中的可执行文件,这个时候操作系统会开启一个进程来运行这个文件对象,如果我们想要关闭某个程序,只需要杀死这个进程即可
java Runtime Data Area : java运行时数据区域,我们也可以叫JVM内存
首先,内存被划分为 五个区域 : 程序计数器 , 方法区/静态区/静态代码段 , 栈内存(虚拟机栈/VM栈) , 堆内存 , 本地方法栈
程序计数器 :
占用较小的一块空间,作用可以看做是当前线程执行的字节码的位置的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖于这个计数器来完成
可以理解为保存下一条语句的行号,依次向后递增
静态区 :
是用来存放我们的程序文件的,载入到内存之后的程序文件对象的一段空间,java中就是指我们的class文件
包括方法,方法在被调用之前,都是以静态的形式被保存到静态区的
静态区中还有运行时常量池
栈内存 :
以栈数据结构为模型,创建一段内存空间
栈数据结构特性 : 先进后出,像弹夹
用来执行方法,局部变量也是在栈内存
栈的构成
栈空间 : 就是指以栈数据结构为模型的一段内存空间
栈帧 : 栈内存中,每一个栈元素,叫栈帧
栈顶元素 : 最后一个放进去的栈帧
栈底元素 : 第一个放进去的栈帧
栈的操作 :
压栈 : 就是指把元素放入栈空间的过程
弹栈 : 就是把元素从栈空间中弹出的过程
java中,方法调用,就是压栈操作,方法执行完 就是弹栈操作
堆内存 :
用来存放对象,根据静态区中的class文件对象,创建的对象
每个对象空间,分为3块
数据部分 : 成员变量
头部 : hashCode值
类型 : 是哪个类创建的对象,保存的是类文件在静态区的地址,所以在成员方法中是可以直接访问静态属性的
本地方法栈 :
用来执行native声明的方法,比如Object类中的hashCode()方法
1 java程序编写
文本编辑器,按照java规范编写代码
2 java程序的编译
javac 进行编译 生成 class文件
3 java程序调用
java xxxx
3.1 开启java虚拟机,然后把程序对应的class文件,载入静态区
3.2 jvm自动调用该class的main方法,并且只有一个main方法,固定写法,java是单入口程序
3.3 main被调用之后,jvm就会在栈内存开辟栈帧,把main中的代码复制进去,然后开始执行
3.4 main方法执行流程
3.4.1 在main方法中没有其他方法调用
执行完main方法之后,main栈帧弹栈,结束,jvm关机
3.4.2 main中有其他方法调用
如果调用了当前类的方法,会在main方法栈帧之上,开辟新的栈帧,并把对应的代码复制进去开始执行,直到执行结束,返回到main栈帧的调用处,继续执行main方法
如果调用了其他类的方法,把对应的类载入到静态区,然后再开辟栈帧,复制代码,执行,执行完返回main,继续执行
如果被调用方法中,还有其他方法调用,就继续压栈,一直到main方法执行结束,弹栈,jvm关机
程序加载 :
静态加载 : 程序开始执行,首先把所有和该程序相关的文件,全部一次性从硬盘载入内存
动态加载 : 程序开始执行,只会载入当前用到的文件,如果执行过程中,需要其他文件,再去硬盘中载入
11种基本数据类型 8种基本 3种引用
TODO 点击直接到那个地方
day05
8. 递归
8.1 概述和基本应用
在方法体中 调用当前方法
以此类推是递归的基本思想
也是循环的基本思想 所以循环和迭代是等价的(迭代就是循环)
初始值,终止条件,步长
循环是重复执行循环体,而递归是重复执行方法体
一般树状结构的都可以使用递归查询
比如 文件目录,因为不清楚到底有多少子目录,所以没办法使用for循环嵌套
累加加和
阶乘
斐波那契数列
汉诺塔
文件目录操作
8.2 斐波那契数列
前两位是1 ,每位都等于前两位的和
1,1,2,3,5,8,13,21,34,55......
day06
9. 数组
数组 是引用数据类型
之前我们学习的变量,都只能存储单个元素,想要存储多个元素,就需要使用数组
数组 是一个源自底层的数据结构,并且几乎在任何语言中,都是最基础的数据结构
数组 又称为 索引数组(index)
9.1 数据结构
数据结构 : 计算机存储、组织数据的方式
就是计算机对数据存储的形式,和数据操作的形式
精心选择的数据结构可以带来更高的运行或者存储效率
数据操作
增删改查
9.2 数组概述和特性
并且 数组中元素的类型必须一致,意味着空间大小一致
数组在内存中是连续的内存空间,也就意味着,找到一个就能找到其他所有
默认使用第一个空间作为整个数组的地址,然后通过偏移量 找到其他元素
偏移量为 0 就是第一个元素 , 偏移量为1 找到的就是第二个元素, 偏移量为11 找到的就是第12个元素
像这种 通过内存地址,直寻法,查找效率极高
所以 数组的索引(下标) 是从0开始的
因为能够快速找到某个元素,所以想要更改某个元素的时候,只需要使用 = 赋值即可
所以 数组 查询和更改 效率极高
数组一旦确定,长度不能更改,想要添加或者删除元素,必须新建数组,然后把原数组中需要保留的元素,以及新插入的元素依次复制到新数组中
所以这样效率较低
数组特性 : 查询更改快,添加删除慢
数组 :
1 是引用类型
会占用两块内存空间 ,栈内存一块,堆内存一块,栈内存保存的是堆内存数组对象的地址
2 数组是一种线性连续存储的数据结构
3 数组就是一个容器,用来存储其他数据
数组可以存储任意元素,不过每一维元素类型必须一致(数组可以多维)
4 数组长度一旦确定,不可更改
数组中 默认有个length属性,保存数组的长度
5 数组下标 从 0 开始
6 数组查询更改快,添加删除慢
9.3 数组声明
1 静态声明 : 就是预先知道数组中的每位元素分别是什么
数据类型[] 变量名= {类型值1,类型值2,....};
int i = 1;
int[] is = {1,2,3};
int[][] iss = { {1,2,3}, {1,2,3}, {1,2,3}};
int[][][] isss = {{ {1,2,3}, {1,2,3}, {1,2,3}},{ {1,2,3}, {1,2,3}, {1,2,3}},{ {1,2,3}, {1,2,3}, {1,2,3}}};
.....
2 动态声明 : 就是预先不知道数组中每位元素分别是什么,不过需要确定数组类型以及长度,先用对应的类型默认值占位
数据类型[] 变量名 = new 数据类型[长度];
int[] is = new int[5];
int[][] iss = new int[5][2];
int[][][] isss = new int[3][5][2];
9.4 数组使用和遍历
9.4.1 获取数据
数组变量[下标];
9.4.2 设置数据
数组遍历[下标] = 值;
9.4.3 遍历数组
遍历
for(int i = 0; i< arr.length ; i++){
int value = arr[i];
System.out.println(value);
}
增强for循环 foreach
把数组中每一位元素拿出来,赋值给 变量
for(数据类型 变量 : 数组 ){
}
9.4.4 常见异常
9.5 数组传递
方法名 ( new 数据类型[]{类型值1,类型值2,....} );
9.6 Main方法传参
动态 :
二维数组中 有5个一维数组,并且每个一维 数组都有3个元素
Int[][] arr = new int[5][3];
二维数组中有5个一维数组,并且这五个一维数组都是空的
Int[][] arr = new int[5][];
10.2 存储方式
10.3 二维数组使用
10.3.1 获取数据
变量[下标][下标]
10.3.2 设置数据
变量[下标][下标] = 值
10.3.3 数组遍历
10.3.4 二维数组中一维数组元素个数不同
使用动态声明的方式,定义二维数组,并且使二维数组中的一维数组元素个数不同
传值 : 指的是基本类型传递
传引用 : 指的是引用类型的传递
数组复制
Scanner工具类
交换变量的值
14.1 借助中间变量
14.2 加减运算
14.3 移位运算交换
day08
15. 排序
就是让元素按照一个大小规则进行排序存储
1,3,2,5,7,4
1,2,3,4,5,7
7,5,4,3,2,1
比如 我们存储了班级内所有学生考试成绩
Double [] scores = {xxx,xxx,xxx,xxx,xxx,xxx};
想要查看前三名怎么办?
降序排序,取前三个即可
15.1 冒泡排序
1 比较相邻的两个元素,如果第一个比第二大,就交换位置
2 对每一对相邻的元素做同样的工作,从开始一对到最后一对,当一轮比较完之后,最后的元素,一定是最大的(最小的)
3 针对所有的元素,重复执行上面操作,除最后一个元素
4 持续每次需要比较的队伍越来越少,一直到没有任何一对需要比较,终止
15.2 选择排序
1 每次都把最小的/最大的放到最左边
先拿出第一个,假设是最小的,然后挨个和后面的比较,全部比较完之后,如果有比这个元素小的,就换位
2 嵌套循环比较
3 中间变量
15.3 API排序
需求 :
给定一个数组,判断是否包含某个值,如果包含返回其对应的下标,如果不包含,返回-1
16.1 顺序查找
顺序查找 :
1 遍历数组,挨个比较
2 如果有和目标元素相等的,就返回该下标
3 如果循环完,都没有发现相等的,就返回-1
优点 :
编码简单,没啥逻辑,挨个比较嘛...
运气好的话,碰巧前几个就是要查找的数据,
缺点 :
查询效率相对较低
不能只拼运气好的时候
比如 有100W条数据,如果数据在最后几个的话,那么就需要循环执行100W次
16.2 二分查找
1 建立在排序的基础之上
2 数据没有重复元素,如果有,先找到那个算哪个
3 用于查找固定有序的数据
1 确定起始和结束位置
2 确定中间数据,判断中间数据是否为目标数据,如果是直接返回
3 如果目标数据小于中间数据,则 起始值不变,结束值 为 中间值 -1
4 如果目标数据大于中间数据,则 结束值不变,起始值 为 中间值 +1
5 如果 起始值 大于 结束值 终止比较,说明不存在
6 重复执行以上操作即可
day09
17. 面向对象
17.1 概述
面向过程 : 侧重分步骤
比如做菜
1 买菜,买各种食材,买各种调料
2 开火,烧油
3 翻炒
4 出锅
…
面向对象 : 侧重分类/模块
比如做菜
1 完成做菜,涉及到的事物有 : 厨师,食材,工具
2 找个厨师,交给他
3 厨师.买食材
4 厨师.使用工具做菜
17.2 构造方法
1 作用 : 创建当前类的实例化对象,初始化成员属性
2 初始化 : 赋值
3 静态变量什么时候初始化 : 类加载阶段(main方法执行之前)
4 如果不定义构造方法的话,JVM会默认帮我们创建一个公共的无参构造
5 如果我们定义了构造方法的话,不管我们定义的是有参还是无参,那么JVM都不会再帮我们默认创建无参构造
6 构造方法默认是 : 公共的,静态的,没有返回值的,方法名和类名相同
public 类名(){}
但是我们声明的时候 不能加static,因为构造方法具有双重性
如果 只是静态方法,那么 里面不能操作成员变量,也就不能对成员变量初始化赋值
如果 只是成员方法,那么就没有办法创建对象了,因为想要创建对象必须调用构造方法,而构造方法如果是成员的,想要调用成员方法必须先有对象
所以 构造方法 具有双重性,我们再声明的时候 不可以加static
构造方法比较特殊
7 语法
[权限控制修饰符] 类名(参数列表){方法体}
17.3 类和对象
1 代码角度 : new 的一个实例,封装了特有的数据
2 数据角度 : 封装数据和逻辑的一种方式
3 现实角度 : 对象就是某一个具体的东西,一切皆对象
4 设计角度 : 从一个实际的实体中抽象出来某些属性的一种实体表示
概念 : 我们再思想上对某个事物/东西/某一类东西的唯一性标识
是我们人,在思想上对某个事情/事物的一个界定(定义)
是我们大脑对客观事物描述的一个标准,一个模板
我们再抽离某个概念,就能建立相关事物的类,一定通过他的数学来形成这个概念,通过这些属性来形成这个类
然后通过不同的属性值,形成了实体,就是那个具体的对象
通过不同的属性,划分不同的类,通过不同的属性值,划分不同的个体
属性分为两种 :
1 静态属性 : 类的行为和功能
2 动态属性 : 类对象的行为和功能
17.4 实例化
类 变量 = new 构造方法(参数);
1 加载类到静态区
2 调用构造方法(栈内存压栈,开辟栈帧),在堆内存开辟内存空间
3 把对应类中的成员属性复制到堆内存中
4 再把堆内存内存地址赋值给栈内存遍历
17.5 对象使用
17.6 类的构成
17.7 Bean
成员变量私有化
有对应的getter/setter方法
对应的构造方法
17.8 常见异常
空指针异常,是运行时异常
当使用null值(空的引用) 去访问成员属性的时候,就会出现空指针异常
跨平台,面向对象,多线程,垃圾自动回收机制
18.2 区分成员和构造
[权限修饰符] 类名(参数) {方法体}
1 成员方法的方法名可以和类名相同吗?(成员方法可以和构造方法同名吗?)
可以和类名相同
2 如何区分同名的成员方法和构造方法?
看返回值,构造方法木有返回值,成员方法必须有返回值类型,如果没有用void表示
18.3 This
18.3.1 是什么
this是每个对象中,保存自身内存地址的一个引用类型的成员变量
所以说,this就表示对象自己,相当于我们说 "我" 一样
18.3.2 能干什么
this能干什么?
1 在成员/构造方法中,能够区分同名的局部变量和成员变量
2 在构造方法中,也可以用于重载调用当前类中其他的构造方法(必须在第一行)
3 return this 返回当前对象内存地址,可以做到链式调用
18.3.3 怎么用
18.3.3.1 区分局部变量和成员变量
18.3.3.2 重载调用构造方法
必须出现在构造方法第一行
this(参数);
18.3.3.3 链式调用
18.3.4 注意
This不能出现在静态上下文中
18.4 Static
18.4.1 是什么
static是一个修饰符关键字,用来区别静态和动态属性
18.4.2 能干什么
1 static修饰的类体中的变量是静态变量
2 static修饰的方法是静态方法
3 static修饰的语句块是静态语句块
18.4.3 怎么用
18.4.4 实例语句块
语法 : {
java代码;
}
实例语句块等同于成员方法,只是没有名字
18.4.5 静态调用
18.4.6 静态和成员的区别及应用场景
静态变量 : 类中使用static修饰
成员变量 : 类中非static修饰
局部变量 : 方法中声明的变量是局部变量,作用域让当前方法使用
静态变量 : 类加载阶段初始化
成员变量 : 创建对象的时候初始化(构造方法)
静态变量 : 类级别的,是所有对象共享的,比如一个静态变量 age = 10 ,那么说明所有对象都有这个age属性,并且值都是18
所有对象共有的属性和值
成员变量 : 对象和对象之间有相同的属性,但是可能有不同的值,非对象共享
18.5 封装
封装是把对象的所有组成部分组合在一起,封装使用访问控制符将类的数据隐藏起来,控制用户对类的修改和访问数据的程度。
作用
适当的封装可以让代码更容易理解和维护,也加强了代码的安全性。
18.5.1 软件包机制
18.5.1.1 Package
1 为了解决类的命名冲突问题
2 在java中使用package语句定义包
3 package语句只能出现在java源文件中的第一行
4 包命名通常采用公司域名倒叙
5 package限制的是class文件的放置位置(编译之后,把class文件放到哪里去)
com.tledu.oa.system
以上包命名可以体现出来 : 这个项目是天亮教育开发的OA项目,当前处于OA项目中的system模块
6 完整的类名 是带有包名的
7 带有包名的文件,编译应该是这样
javac -d 生成路径 java源文件路径
javac -d ./ -encoding utf-8 _01_Package.java
8 运行
java 包名.类名
18.5.1.2 Import
// 导入单个类
//import _05_Package.com.B;
//import _05_Package.com.C;
import java.util.Date;
// 导入该包下所有类
import _05_Package.com.*;
// 导入该类中所有的静态属性(静态变量/静态方法),让当前类可以不加前缀,直接调用
import static _05_Package.com.B.*;
/**
1 引入其他需要的类
2 只能出现在package语句执行,class语句之上
3 java核心类不需要导入,可以直接用,其他都不行
java.lang
import 包名.类名; 导入单个类
import 包名.*; 导入该包下所有的类
import static _05_Package.com.B.*;
不建议这样使用,在当前类中,不容看出来,这个变量是谁的变量
18.5.2 权限修饰符
Day11
19. 继承
19.1 是什么
在java中 使用extends 关键字表示
语法 : public class 类名 extends 父类名{ 类体 }
java.lang.Object 是java中的祖类(祖宗)
也就意味着 Object中的属性 是所有类都有的
19.2 能做什么
代码重用,子类可以直接使用父类的属性和方法
如果父类功能不能满足子类需求,还可以对父类方法进行重写
使用多态
19.3 怎么用
使用extends关键字
1 用于成员/构造方法中区分子类和父类同名的成员属性
2 用于子类构造方法中,调用父类构造方法(必须出现在子类构造方法的第一行)
语法 : super(参数);
如果 子类构造方法中 没有this(xxx) 也没有 super(xxx) 那么 默认会有一个 super();
去调用父类的无参构造
this() 和 super() 都必须出现在构造方法第一行,那么就意味着 它俩不能同时出现
20.3 调用父类构造
1 创建对象
2 初始化父类属性
super() : 用来调用父类构造,初始化父类属性,并且创建对象(最终肯定会调用到Object,通过Object创建对象)
既然是创建对象的,那么只能在构造方法第一行,因为构造方法还有初始化成员属性的功能
成员属性想要初始化,必须有存储它的空间,而这个空间就在对象中
所以要保证先有对象,再有数据初始化
所以 super()必须在第一行
this() : 重载调用当前类的其他构造方法
保证先有对象
20.4 注意
this() 和 super() 不能同时出现,因为两个都必须写在第一行
20.5 实例语句块和构造方法
不需要
1 子类也有自己的特有属性
2 可以覆写父类的成员方法
21.2 应用场景
1 什么时候需要进行覆写
如果父类的方法已经无法满足子类的需求的时候,需要将父类中的方法进行重写
2 重写的条件
1 必须是有继承关系的体系中
2 方法名必须相同,返回值必须相同,参数列表必须相同
3 不能比原方法有更宽泛的异常
4 不能比原方法有更低的访问权限
5 覆写特指成员方法
3 继承最基本的作用 : 代码重用
4 继承最重要的功能 : 方法可以覆写,多态
重写的目的 :
1 满足当前需求 , 方法体编码
2 错误越来越少,不能有更宽泛的异常
3 使用范围越来越广,不能有更低的访问权限
4 功能越来越强
1 修饰的类 不能被继承
2 修饰的变量 就是常量,不能二次赋值(整个程序生命周期中不可更改),没有默认值
常量一般是public static final 的
3 修饰的成员方法不能被覆写
22.3 怎么用
深入final :
final修饰的变量,内存空间中的值不可更改
如果修饰的是引用类型变量,也是一样的道理,但是和引用类型的地址指向的堆内存空间,没有关系
Day12
23. 多态
23.1 相关知识
软件设计六大原则
里氏替换原则 : 能够使用父类的地方就一定可以使用子类
1 单一职责原则 : 功能职责单一,只拥抱一种变化
2 里氏替换原则 : 所有在使用父类的情况下,都可以使用子类
3 依赖倒置原则 : 高层通过抽象依赖底层,
4 接口隔离原则 : 不应该依赖于它不需要的接口
5 迪米特原则 : 最少知识原则
6 开闭原则 : 对扩展开放,对修改关闭
http://baijiahao.baidu.com/s?id=1645013441658118287&wfr=spider&for=pc
23.2 是什么
多态就是里氏替换原则的一种体现
什么是多态 : 父类引用 指向 子类对象
父类引用 : 父类型声明的引用类型变量
指向 : 就是通过这个引用类型的变量可以找到谁
子类对象 : new 的子类对象
通过父类创建一个引用类型的变量,可以找到子类的对象
父类 变量 = new 子类();
Animal a = new Cat();
变量声明 :
数据类型 变量名 = 值;
变量分类 :
局部变量
1 方法内部声明
2 参数列表声明
静态变量
成员变量
有变量的地方 就可以发生多态,并且多态是发生在赋值的时候
Animal a = new Cat();
多态发生的几种形式
1 直接多态
不管成员变量还是局部变量 直接声明式多态 父类 变量 = new 子类();
2 形参和实参
方法参数定义时 使用父类定义,调用方法时,传递子类对象
3 返回值
返回值类型定义为父类, 返回数据的时候返回子类对象
多态的缺点 : 丢失子类特有的属性
多态调用属性 :
1 如果调用的是父类没有的,直接报错,都访问不了
2 如果调用的是子类没有的,都访问父类的
3 如果调用的是父类和子类都有的,那么除了成员方法调用子类的之外,其他的都调用父类的,因为成员方法可以覆写
多态得前提条件
1 必须有继承关系的体系中(父,子,爷,孙 都可以)
2 多态又叫向上转型
23.3 怎么用
23.4 为什么这么用(优点)
把不同的子类对象都当作父类来看,可以屏蔽不同子类对象之间的差异,写出通用的代码,做出通用的编程,以适应需求的不断变化。
23.5 缺点(扩展)
丢失 子类特有的属性
为什么?
编译阶段 : 在编译阶段的时候,父类文件有,子类文件也有,子类对象没有,但是我们在代码中已经定义了多态
这个时候,编译器会直接按照父类类型来划分空间,来说明空间中有啥,来检查语法结构
这个时候只能找到父类里面的属性说明,因为空间是父类声明的
什么时候有的子类对象? 运行时
运行阶段
各个类载入方法区,开始运行,执行new 创建对象,对象用子类Cat创建的,所以 对象中是包含子类 特有属性的
但是引用变量的类型是父类类型,并且在编译时就已经进行了静态绑定,所以 使用这个父类声明的空间的时候就只能找到父类中有的
而 子类对象中 有的特有属性 是没有办法通过父类的变量找到的
因为 父类的变量声明的时候 只能够说明父类的属性列表,而创建对象之后,使用子类声明的属性值列表
按说 创建了对象,对象中有子类属性,为什么找不到? 因为在编译时就报错了,语句不通过,压根等不到运行时
在编译的时候,会把父类的属性生成一个属性列表,父类属性有了,这叫编译时绑定,绑定完之后,程序就有了,属性就已经固定了
属性值没有固定(都没有运行,没有空间,哪来的值?)
这种编译时绑定,又可以叫做静态绑定/前绑定
运行的时候,把类生成对应的对象,也有了子类的属性列表,因为创建了对象,有了空间.进行了初始化操作,所以属性的值也就有了
这种在运行阶段进行的数据绑定叫运行时绑定/动态绑定/后绑定
使用父类的引用,调用子类对象的属性的时候(特有的就访问不到了)
也可以理解为 使用父类的属性列表 去调用子类的属性值列表
所以问题就出现了,这样子类特有的就没有办法找到了
因为 使用的是父类的属性列表进行调用匹配,所以在找不到的时候语法就不通过了,所以在编译时就会报错,压根不能运行,不运行就没有对象,没有对象就更别提能不能找到特有数据了
*
1 父类没有的,不管子类有没有 都调用不了,直接报错
2 只要父类有的,子类没有的,都执行父类的
3 父类和子类都有的时候,除了成员方法调用子类,其他都执行父类
3 父类和子类都有的时候,除了成员方法调用子类,其他都执行父类
3 父类和子类都有的时候,除了成员方法调用子类,其他都执行父类
3 父类和子类都有的时候,除了成员方法调用子类,其他都执行父类
23.6 隐式多态(扩展)
* this : 谁调用,this就是谁 所以 这里this 一定是 subClass 这个子类对象
*
* this 是保存当前对象的内存地址,并且是第一个成员变量,既然是变量,那么总得有数据类型吧?
*
* this 用来存储当前对象的内存地址,那么 this的数据类型为 什么的时候 可以存储当前对象内存地址?
* 1 当前类类型
* 2 父类类型
* 如果是父类类型的话,就是多态,父类引用指向子类对象,缺点就是不能调用子类特有的属性,而 this 是可以调用当前类的特有属性的
* 所以 this的类型 是 当前类 类型
*
* 什么是当前类? : 当前类 就是 this所在的类(在哪个类出现的 ,哪个就是当前类)
*
* 那么 这里的this 就等于是 SupClass this
*
* 那么 this的引用 是子类对象,再结合this 是当前类类型 所以成了这样 SupClass this = new SubClass();
23.7 注意
以上这种,在第一行 p下面报错,一般是因为有类名冲突
可以检查一下 是否 存在 一个java文件中 创建了多个 class 并且有class同名
导包出错,傻眼了吧,因为当前包下,有一个和导入同名的类,就会出现这种问题
可以使用类全名解决
建议 不要和已有类同名,尤其是系统类
可以避免
抽象类往往用来表示设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。
比如:动物,它只是一个抽象的概念,并没有一个 “东西”叫做 “动物”。所以,它并不能代表一个实体,这种情况下,我们就适合把它定义成抽象类。
abstract 是修饰符
使用abstract 修饰的类,是抽象类
使用abstract 修饰的方法,是抽象方法,抽象方法没有方法体,只有功能 定义,没有功能实现,用于让子类实现
实现抽象方法,就是给抽象方法加上方法体,去掉abstract修饰
抽象类的目的 : 该类不能被实例化对象,只能被继承
抽象方法一定在抽象类 中,成立
抽象类中一定有抽象方法,不成立,也可以没有,如果就想让某个类不能创建对象,也可以把该类创建成抽象类
final修饰的类不能被继承,
final修饰的成员方法不能被覆写
而 抽象类 就是用于被继承的,抽象方法就是用于被实现覆写的
所以 final 和 abstract 不能同时出现
一个类 如果要继承一个抽象类,必须要实现该类的所有抽象方法
一个抽象类 如果要继承一个抽象类 可以实现 0~N个抽象方法
25.2 怎么用
25.3 注意
引用数据类型 : 类 数组 接口
接口是一种引用数据类型,可以看做是一种特殊的类,是java为了解决没有多继承引起的功能变弱的问题
一个类只能有一个父类,但是可以实现N个接口
创建接口的方式 由 class 变成了 interface 而 父子关系 用 extends 变成了 implements
类和类 使用extends,并且单继承
类和接口 使用 implements 多实现 多个以逗号隔开,比如 class A implements V,C,D
并且 非抽象类实现接口后,必须实现所有的抽象方法
抽象类实现接口后 可以实现 0~N个抽象方法
接口和接口 使用 extends,并且是多继承 比如 interface A extends B,C,D
语法 :
public interface 接口名{
}
1 接口中 所有的变量 默认都是 public static final 的 也就是常量 ,而且 public static final 可以省略
2 接口中的方法 默认都是 public abstract 的 也就是抽象方法,而且 public abstract 可以省略
3 接口中没有构造方法,不能被实例化对象
从 java1.8开始 接口中 可以有 默认方法和 静态方法
接口可以看做是一个特殊的抽象类,所以很多时候 接口和抽象类都能做到某一件事
优先使用接口,因为 类和接口之间是多实现, 当使用接口之后,还可以保留类的继承
26.2 怎么用
26.3 接口和抽象类
Day13
27. Object
27.1 概述
今天我们只讲解 : equals() toString() hashCode() finalize()
27.2 Equals
关于Object中的equals方法
public boolean equals(Object obj){
return (this == obj);
}
== : 两边如果是基本类型,则比较值的大小,但是两边如果是引用类型,则比较内存地址
而Object中的equals方法,默认比较 内存地址,因为 就是 ==
设计目的 : 比较两个对象是否相等
但是 它没有实现该功能,因为不知道怎么实现,不知道你要比较什么属性,所以没法实现
需要根据自己的需求进行重写,因为使用者一定知道怎么判断相等
两个对象可以是任何东西,但是我们肯定要拿两个对象的有意义属性进行比较
而不是比较内存地址,所以 这个时候要将两个对象对应的equals方法进行重写,以满足我们的需求
但是不同类的对象,没有可比性
比较字符串是否相等
应该用 equals 因为 == 比较内存地址
并且 String类中 已经重写了equals方法,比较的是值,而不是地址
任何的引用类型比较,都必须转换为基本类型比较,除非就是想知道他们内存地址是否一致
因为 == , != 这些 只能比较基本数据类型,当两个对象比较的时候,会比较内存地址
而我们比较的时候应该拿某一个相同的属性的值去比较,所以这样最终还是会变成基本数据类型
所以说,面向对象就是一种基本数据封装的形式,所以我们比较的时候最终都要转换为基本类型比较
27.3 toString
27.4 hashCode
1 先比较哈希,如果哈希不同,对象则不同
2 如果哈希值相同,再比较对象是否相同(equals)
27.5 Finalize
垃圾被回收之前 自动调用该方法,想做什么,自己覆写 Object
二、实现关系 实现指的是一个class类实现interface接口(可以是多个)的功能,实现是类与接口之间最常见的关系。在Java中此类关系通过关键字 implements明确标识,在设计时一般没有争议性。在UML类图设计中,实现用一条带空心三角箭头的虚线表示,从类指向实现的接口。
三、依赖关系 简单的理解,依赖就是一个类A使用到了另一个类B,而这种使用关系是具有偶然性的、临时性的、非常弱的,方法执行完,这个关系就不存在了,就是临时使用了一下功能,但是类B的变化会影响到类A。比如某人要过河,需要借用一条船,此时人与船之间的关系就是依赖。表现在代码层面,为类B作为参数被类A在某个method方法中使用。在UML类图设计中,依赖关系用由类 A指向类B的带箭头虚线表示。
在程序的整个流程中(程序的生命周期),某个步骤,某几个有限的步骤,需要另外一个类的功能才能完成
比如在一个方法中调用了另外一个类
说白话:就是一个类的局部变量是另一个类的对象的引用.
四、关联关系 关联体现的是两个类之间语义级别的一种强依赖关系,比如我和我的朋友,这种关系比依赖更强、不存在依赖关系的偶然性、关系也不是临时性的,一般是长期性 的,而且双方的关系一般是平等的。关联可以是单向、双向的。表现在代码层面,为被关联类B以类的属性形式出现在关联类A中,
ClassC cc = new ClassC()
ClassC.staticVar
全局变量,就是指他的成员变量,静态变量
也可能是关联类A引用了一个类 型为被关联类B的全局变量。在UML类图设计中,关联关系用由关联类A指向被关联类B的带箭头实线表示,在关联的两端可以标注关联双方的角色和多重性标 记。
就是一个类成员变量或者静态变量是另外一个类的引用或者是类对象的引用
五、聚合关系 聚合是关联关系的一种特例,它体现的是整体与部分的关系,即has-a的关系。此时整体与部分之间是可分离的,它们可以具有各自的生命周期,部分可以属于 多个整体对象,也可以为多个整体对象共享。比如计算机与CPU、公司与员工的关系等,比如一个航母编队包括海空母舰、驱护舰艇、舰载飞机及核动力攻击潜艇 等。表现在代码层面,和关联关系是一致的,只能从语义级别来区分。在UML类图设计中,聚合关系以空心菱形加实线箭头表示。
一类中的集合元素是另外一个类的对象的引用
六、组合关系/合成关系 组合也是关联关系的一种特例,它体现的是一种contains-a的关系,这种关系比聚合更强,也称为强聚合。它同样体现整体与部分间的关系,但此 时整体与部分是不可分的,整体的生命周期结束也就意味着部分的生命周期结束,比如人和人的大脑。表现在代码层面,和关联关系是一致的,只能从语义级别来区 分。在UML类图设计中,组合关系以实心菱形加实线箭头表示。
一个类中的集合中的元素是另外一个类对象的引用
七、总结 对于继承、实现这两种关系没多少疑问,它们体现的是一种类和类、或者类与接口间的纵向关系。其他的四种关系体现的是类和类、或者类与接口间的引用、 横向关系,是比较难区分的,有很多事物间的关系要想准确定位是很难的。前面也提到,这四种关系都是语义级别的,所以从代码层面并不能完全区分各种关系,但 总的来说,后几种关系所表现的强弱程度依次为:组合>聚合>关联>依赖。
软件开发的流程
项目->确定需求->设计模块(模型)->编码->测试->交付和部署->后期维护
当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内 部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构最好使 用内部类
在Java中,允许一个类的定义位于另一个类的内部,前者称为内部类,后者 称为外部类。
Inner class一般用在定义它的类或语句块之内,在外部引用它时必须给出完 整的名称
29.2 分类
Static成员内部类
非static成员内部类
局部内部类
匿名内部类
29.3 成员内部类
成员变量 : 成员内部类
静态变量 : 静态内部类
局部变量 : 局部内部类
形参和实参变量 : 匿名内部类
1 可以等同看做成员变量
2 成员内部类中不能有静态声明
3 成员内部类中可以直接访问外部类所有的属性
29.4 静态内部类
1 可以看做静态变量
2 静态内部类中,不能直接访问成员数据,需要有对象才行
3 静态内部类中可以声明所有东西
Day14
30. 内部类
30.1 概述
当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内 部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构最好使 用内部类
在Java中,允许一个类的定义位于另一个类的内部,前者称为内部类,后者 称为外部类。
Inner class一般用在定义它的类或语句块之内,在外部引用它时必须给出完 整的名称
30.2 分类
Static成员内部类
非static成员内部类
局部内部类
匿名内部类
30.3 成员内部类
成员变量 : 成员内部类
静态变量 : 静态内部类
局部变量 : 局部内部类
形参和实参变量 : 匿名内部类
1 可以等同看做成员变量
2 成员内部类中不能有静态声明
3 成员内部类中可以直接访问外部类所有的属性
30.4 静态内部类
1 可以看做静态变量
2 静态内部类中,不能直接访问成员数据,需要有对象才行
3 静态内部类中可以声明所有东西
30.5 局部内部类
30.6 匿名内部类
31.1 概述
代表了最佳的实现方式,是一些有经验的开发人员,通过开发中总结出来的一些经验和错误,用来解决一系列问题
是前辈们对代码开发经验的总结,不是特定的语法结构,而是一套用来提供可复用性,可维护性,可读性,健壮性,安全性的解决方案
就是在编码实践中发现很多的编码方式经常会被用到,于是就总结出来 形成固定的结构
简单来说,世上本没有路,走的人多了,便有了路
如果开发中,用到了一些设计模式的话,就等于使用了他人总结的经验成果,可以避免很多问题
目前比较成熟的设计模式 有23种
31.2 单例模式
目的 : 让某个类只创建一个对象
首先我们要控制创建对象的数量,就不能让用户决定创建和不创建,应该由我们去控制
构造方法是用来创建对象的,想要把创建对象的权利 控制在我们手中的话,那么就不能让用户直接访问我们的构造方法
1 构造方法私有化
我们已经把构造方法私有化了,就意味着用户创建不了对象了
那么这个时候,我们应该创建一个对象给用户,怎么给?
对外提供一个公共的静态方法,调用该方法就能返回一个对象,
2 创建一个公共的静态的方法用来返回当前类的对象,并且保证 只实例化一次
当调用处通过该方法获取对象的时候,先判断是否已经创建了对象,如果没有就创建一个并存储
如果已经创建 就直接返回该对象
所以 需要 一个存储对象的变量,这个变量必须是和类生命周期相关的
如果是一个局部变量的话,方法每次调用,都会重新初始化局部变量,所以 每次都会创建对象
如果是个成员变量,那么问题更严重,静态方法中不能直接操作成员变量
所以 只能是静态变量,并且这个变量要私有化不能被外部访问,万一用户直接更改变量的值咋办?
3 创建一个私有化的静态变量,用于存储当前类的对象(变量的类型,是当前类类型)
1 懒汉模式 : 第一次获取对象的时候,再创建对象
2 饿汉模式 : 加载类的时候,就创建对象
31.3 工厂模式
Day15-常用API
32. String
32.1 概述
底层就是char数组 private final char value[];
所以 字符串很多特性 就是数组的特性
1 字符串一旦创建不可更改
2 为了提升字符串的访问效率,Java中提出了字符串常量池,相当于是一个缓存区
引用类型对象应该保存在堆内存,但是字符串不同,保存在静态区的字符串常量池中
3 在程序的执行过程中,如果程序要用到某个字符串,如"abc",虚拟机会先去常量池中搜索,有没有这个字符串
如果已经有了,就直接指向该字符串即可,如果没有就新建一个字符串对象,并指向
32.2 基本使用
32.3 不要频繁拼接
32.4 构造方法
32.5 常用方法
int length():返回字符串的长度: return value.length
char charAt(int index): 返回某索引处的字符return value[index]
boolean isEmpty():判断是否是空字符串:return value.length == 0
String toLowerCase():使用默认语言环境,将 String 中的所有字符转换为小写
String toUpperCase():使用默认语言环境,将 String 中的所有字符转换为大写
String trim():返回字符串的副本,忽略前导空白和尾部空白
boolean equals(Object obj):比较字符串的内容是否相同
boolean equalsIgnoreCase(String anotherString):与equals方法类似,忽略大
小写
String concat(String str):将指定字符串连接到此字符串的结尾。 等价于用“+”
int compareTo(String anotherString):比较两个字符串的大小
String substring(int beginIndex): 返回一个新的字符串, 它是此字符串的从
beginIndex开始截取到最后的一个子字符串。
String substring(int beginIndex, int endIndex) :返回一个新字符串,它是此字 符串从beginIndex开始截取到endIndex(不包含)的一个子字符串。
boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束
boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始
boolean startsWith(String prefix, int toffset):测试此字符串从指定索引开始的
子字符串是否以指定前缀开始
boolean contains(CharSequence s):当且仅当此字符串包含指定的 char 值序列 时,返回 true
int indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引
int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出 现处的索引,从指定的索引开始
int lastIndexOf(String str):返回指定子字符串在此字符串中最右边出现处的索引
int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后 一次出现处的索引,从指定的索引开始反向搜索
注:indexOf和lastIndexOf方法如果未找到都是返回-1
String replace(char oldChar, char newChar):返回一个新的字符串,它是
通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。
String replace(CharSequence target, CharSequence replacement): 使 用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串。
String replaceAll(String regex, String replacement) : 使 用 给 定 的
replacement 替换此字符串所有匹配给定的正则表达式的子字符串。
String replaceFirst(String regex, String replacement) : 使用给定的
replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。
boolean matches(String regex):告知此字符串是否匹配给定的正则表达式。
String[] split(String regex):根据给定正则表达式的匹配拆分此字符串。
String[] split(String regex, int limit):根据匹配给定的正则表达式来拆分此 字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中
32.6 注意
String s2 = "a" + "b"; 这种写法,在编译阶段,就会把+ 去掉
"a" +"b" a和b都是字面量,需要在编译阶段说明临时空间,所以需要通过值确定类型
编译时看到是两个字符串相加,就直接把+省略,保存ab
所以 s1 ==s2 是true
String s3 = a + b;
a和b是变量,编译阶段是不确定变量的值的
在运行的时候,由于是两个字符串变量相加,
所以会自动创建一个StringBuffer对象,然后把两个变量拼接到一起,最终转换为String类型
而是以 return new String(value, 0, count); 所以 s3是指向堆内存的
是一个可变的字符串缓冲区
预先在内存中申请一块空间,可以容纳字符序列(字符数组)
如果 预留空间不够,会进行自动扩容
底层都是char[] ,并且默认初始化容量是16个字符
1 String不可变字符序列,而StringBuilder和StringBuffer是可变字符序列
2 StringBuffer是线程安全,在多线程环境下,不会出现问题,所以效率低,一般常用于类中
3 StringBuilder是非线程安全,在多线程环境下可能出现问题,效率高,一般用于方法中
多线程环境下,是否有可能出现多个线程同时操作同一个数据 的可能(增,删,改)
33.2 常用方法
33.3 使用
byte -- java.lang.Byte
short -- java.lang.Short
int -- java.lang.Integer
long -- java.lang.Long
float -- java.lang.Float
double -- java.lang.Double
char -- java.lang.Character
boolean -- java.lang.Boolean
1 方便
2 为了理论上的完整(面向对象)
Object ,因为Object是所有类的祖类,由于多态的原因,可以接收任何对象
但是基本类型呢? 基本类型并不是Object的子类,怎么接收?
包装类 , 可以把基本类型转换为对应的包装类类型,而包装类也是个类,也是Object的子类
34.2 使用
34.3 Integer
34.3.1 基本使用
以Integer为例 讲解八种包装类
34.3.2 常用方法
34.3.3 类型转换
用到parseInt方法居多
34.3.4 自动装箱和自动拆箱
自动装箱
把 基本数据类型 自动转换为 对应的包装类
自动拆箱
把 包装类 自动转换为 基本数据类型
34.3.5 扩展之整型常量池
大概意思是 : 这是一个缓存机制,如果值再-128到127之间,就不再创建新对象,而是直接把数组中的引用赋值给你
如果不再这个范围内,就创建 新的Integer对象
当这个内部类加载,会执行静态语句块,那么就会对这个缓存初始化赋值
共 256个Integer对象
-128,-127,-126,…126,127
0 1 2 …254 255
Day16-常用API
35. String
35.1 概述
底层就是char数组 private final char value[];
所以 字符串很多特性 就是数组的特性
1 字符串一旦创建不可更改
2 为了提升字符串的访问效率,Java中提出了字符串常量池,相当于是一个缓存区
引用类型对象应该保存在堆内存,但是字符串不同,保存在静态区的字符串常量池中
3 在程序的执行过程中,如果程序要用到某个字符串,如"abc",虚拟机会先去常量池中搜索,有没有这个字符串
如果已经有了,就直接指向该字符串即可,如果没有就新建一个字符串对象,并指向
35.2 基本使用
35.3 不要频繁拼接
35.4 构造方法
35.5 常用方法
int length():返回字符串的长度: return value.length
char charAt(int index): 返回某索引处的字符return value[index]
boolean isEmpty():判断是否是空字符串:return value.length == 0
String toLowerCase():使用默认语言环境,将 String 中的所有字符转换为小写
String toUpperCase():使用默认语言环境,将 String 中的所有字符转换为大写
String trim():返回字符串的副本,忽略前导空白和尾部空白
boolean equals(Object obj):比较字符串的内容是否相同
boolean equalsIgnoreCase(String anotherString):与equals方法类似,忽略大
小写
String concat(String str):将指定字符串连接到此字符串的结尾。 等价于用“+”
int compareTo(String anotherString):比较两个字符串的大小
String substring(int beginIndex): 返回一个新的字符串, 它是此字符串的从
beginIndex开始截取到最后的一个子字符串。
String substring(int beginIndex, int endIndex) :返回一个新字符串,它是此字 符串从beginIndex开始截取到endIndex(不包含)的一个子字符串。
boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束
boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始
boolean startsWith(String prefix, int toffset):测试此字符串从指定索引开始的
子字符串是否以指定前缀开始
boolean contains(CharSequence s):当且仅当此字符串包含指定的 char 值序列 时,返回 true
int indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引
int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出 现处的索引,从指定的索引开始
int lastIndexOf(String str):返回指定子字符串在此字符串中最右边出现处的索引
int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后 一次出现处的索引,从指定的索引开始反向搜索
注:indexOf和lastIndexOf方法如果未找到都是返回-1
String replace(char oldChar, char newChar):返回一个新的字符串,它是
通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。
String replace(CharSequence target, CharSequence replacement): 使 用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串。
String replaceAll(String regex, String replacement) : 使 用 给 定 的
replacement 替换此字符串所有匹配给定的正则表达式的子字符串。
String replaceFirst(String regex, String replacement) : 使用给定的
replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。
boolean matches(String regex):告知此字符串是否匹配给定的正则表达式。
String[] split(String regex):根据给定正则表达式的匹配拆分此字符串。
String[] split(String regex, int limit):根据匹配给定的正则表达式来拆分此 字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中
35.6 注意
String s2 = "a" + "b"; 这种写法,在编译阶段,就会把+ 去掉
"a" +"b" a和b都是字面量,需要在编译阶段说明临时空间,所以需要通过值确定类型
编译时看到是两个字符串相加,就直接把+省略,保存ab
所以 s1 ==s2 是true
String s3 = a + b;
a和b是变量,编译阶段是不确定变量的值的
在运行的时候,由于是两个字符串变量相加,
所以会自动创建一个StringBuffer对象,然后把两个变量拼接到一起,最终转换为String类型
而是以 return new String(value, 0, count); 所以 s3是指向堆内存的
是一个可变的字符串缓冲区
预先在内存中申请一块空间,可以容纳字符序列(字符数组)
如果 预留空间不够,会进行自动扩容
底层都是char[] ,并且默认初始化容量是16个字符
1 String不可变字符序列,而StringBuilder和StringBuffer是可变字符序列
2 StringBuffer是线程安全,在多线程环境下,不会出现问题,所以效率低,一般常用于类中
3 StringBuilder是非线程安全,在多线程环境下可能出现问题,效率高,一般用于方法中
多线程环境下,是否有可能出现多个线程同时操作同一个数据 的可能(增,删,改)
36.2 常用方法
36.3 使用
byte -- java.lang.Byte
short -- java.lang.Short
int -- java.lang.Integer
long -- java.lang.Long
float -- java.lang.Float
double -- java.lang.Double
char -- java.lang.Character
boolean -- java.lang.Boolean
1 方便
2 为了理论上的完整(面向对象)
Object ,因为Object是所有类的祖类,由于多态的原因,可以接收任何对象
但是基本类型呢? 基本类型并不是Object的子类,怎么接收?
包装类 , 可以把基本类型转换为对应的包装类类型,而包装类也是个类,也是Object的子类
37.2 使用
37.3 Integer
37.3.1 基本使用
以Integer为例 讲解八种包装类
37.3.2 常用方法
37.3.3 类型转换
37.3.4 自动装箱和自动拆箱
自动装箱
把 基本数据类型 自动转换为 对应的包装类
自动拆箱
把 包装类 自动转换为 基本数据类型
37.3.5 扩展之整型常量池
大概意思是 : 这是一个缓存机制,如果值再-128到127之间,就不再创建新对象,而是直接把数组中的引用赋值给你
如果不再这个范围内,就创建 新的Integer对象
当这个内部类加载,会执行静态语句块,那么就会对这个缓存初始化赋值
共 256个Integer对象
-128,-127,-126,…126,127
0 1 2 …254 255
System类提供的public static long currentTimeMillis()用来返回当前时 间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差。
此方法适于计算时间差。
System类代表系统,系统级的很多属性和控制方法都放置在该类的内部。 该类位于java.lang包。
由于该类的构造器是private的,所以无法创建该类的对象,也就是无法实 例化该类。其内部的成员变量和成员方法都是static的,所以也可以很方便 的进行调用。
成员变量
System类内部包含in、out和err三个成员变量,分别代表标准输入流
(键盘输入),标准输出流(显示器)和标准错误输出流(显示器)。
成员方法
native long currentTimeMillis():
该方法的作用是返回当前的计算机时间,时间的表达格式为当前计算机时
间和GMT时间(格林威治时间)1970年1月1号0时0分0秒所差的毫秒数。
北京时间 : 1970.1.1 8:00 00 000
void exit(int status):
该方法的作用是退出程序。其中status的值为0代表正常退出,非零代表
异常退出。使用该方法可以在图形界面编程中实现程序的退出功能等
表示特定的瞬间,精确到毫秒
39.1 构造方法
Date():使用无参构造器创建的对象可以获取本地当前时间。
Date(long date)
39.2 常用方法
getTime():返回自 1970 年 1 月 1 日 00:00:00 GMT 以来此 Date 对象
表示的毫秒数。
toString():把此 Date 对象转换为以下形式的 String: dow mon dd hh:mm:ss zzz yyyy 其中: dow 是一周中的某一天 (Sun, Mon, Tue, Wed, Thu, Fri, Sat),zzz是时间标准。
其它很多方法都过时了。
39.3 SimpleDateFormat
Date类的API不易于国际化,大部分被废弃了,java.text.SimpleDateFormat
类是一个不与语言环境有关的方式来格式化和解析日期的具体类。
它允许进行格式化:日期à文本、解析:文本à日期
SimpleDateFormat() :默认的模式和语言环境创建对象
public SimpleDateFormat(String pattern):该构造方法可以用参数pattern
指定的格式创建一个对象,该对象调用:
public String format(Date date):方法格式化时间对象date
public Date parse(String source):从给定字符串的开始解析文本,以生成
一个日期。
39.4 使用方式
39.4.1 字符串转Date
39.4.2 练习
Calendar
1、Calendar是一个抽象基类,主用用于完成日期字段之间相互操作的功能。
2、获取Calendar实例的方法
使用Calendar.getInstance()方法
调用它的子类GregorianCalendar的构造器。
3、一个Calendar的实例是系统时间的抽象表示,通过get(int field)方法来取得想 要的时间信息。比如YEAR、MONTH、DAY_OF_WEEK、HOUR_OF_DAY 、 MINUTE、SECOND
public void set(int field,int value)
public void add(int field,int amount)
public final Date getTime()
public final void setTime(Date date)
4、注意:
获取月份时:一月是0,二月是1,以此类推,12月是11
获取星期时:周日是1,周二是2 , 。。。。周六是7
// 创建日历对象
Calendar c = Calendar.getInstance();
// 获取当前是本周第几天,周日是第一天
System.out.println(c.get(Calendar.DAY_OF_WEEK));
// 获取今天是多少号(本月第几天)
System.out.println(c.get(Calendar.DAY_OF_MONTH));
// 获取年
int year = c.get(Calendar.YEAR);
// 获取月 从0开始,所以结果+1
int month = c.get(Calendar.MONTH) + 1;
// 获取日
int day = c.get(Calendar.DAY_OF_MONTH);
// 获取时
int hour = c.get(Calendar.HOUR);
// 获取分
int minute = c.get(Calendar.MINUTE);
// 获取秒
int second = c.get(Calendar.SECOND);
// 获取本周第几天
int weekday = c.get(Calendar.DAY_OF_WEEK);
// 把本周第几天转换为星期
String weekdayStr = "星期";
switch (weekday) {
case 1:
weekdayStr += "日";
break;
case 2:
weekdayStr += "一";
break;
case 3:
weekdayStr += "二";
break;
case 4:
weekdayStr += "三";
break;
case 5:
weekdayStr += "四";
break;
case 6:
weekdayStr += "五";
break;
case 7:
weekdayStr += "六";
break;
}
System.out.println(year+"年"+month+"月"+day+"日 "+hour+":"+minute+":"+second+" "+weekdayStr);
扩展之Enum
https://www.cnblogs.com/zhanqing/p/11076646.html
枚举类型是Java 5中新增特性的一部分,它是一种特殊的数据类型,之所以特殊是因为它既是一种类(class)类型却又比类类型多了些特殊的约束,但是这些约束的存在也造就了枚举类型的简洁性、安全性以及便捷性。下面先来看看什么是枚举?如何定义枚举?
41.1 Enum之前定义枚举的方式
上述的常量定义常量的方式称为int枚举模式,这样的定义方式并没有什么错,但它存在许多不足,如在类型安全和使用方便性上并没有多少好处,如果存在定义int值相同的变量,混淆的几率还是很大的,编译器也不会提出任何警告,因此这种方式在枚举出现后并不提倡,现在我们利用枚举类型来重新定义上述的常量,同时也感受一把枚举定义的方式,如下定义周一到周日的常量
41.2 Enum之后定义枚举的方式
//枚举类型,使用关键字enum
enum Day {
MONDAY, TUESDAY, WEDNESDAY,
THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
相当简洁,在定义枚举类型时我们使用的关键字是enum,与class关键字类似,只不过前者是定义枚举类型,后者是定义类类型。枚举类型Day中分别定义了从周一到周日的值,这里要注意,值一般是大写的字母,多个值之间以逗号分隔。同时我们应该知道的是枚举类型可以像类(class)类型一样,定义为一个单独的文件,当然也可以定义在其他类内部,更重要的是枚举常量在类型安全性和便捷性都很有保证,如果出现类型问题编译器也会提示我们改进,但务必记住枚举表示的类型其取值是必须有限的,也就是说每个值都是可以枚举出来的,比如上述描述的一周共有七天
41.3 Enum使用
41.4 Enum优点
以上程序中,本来应该是登陆成功,由于我们不小心拼写错误,导致结果为登陆失败
但是 并不会提示我们拼写错误了,比如我们要使用SUCCESS和FAIL 的话,一般会定义为常量
这样的话,可以避免我们拼写错误,只需要保证Result类没拼错即可
但是从java1.5开始 引入enum之后,这种写法 显得比较繁琐
随机数生成器 从0开始
42.2 练习
1 生成 10~20 之间的数
abs 绝对值 acos,asin,atan,cos,sin,tan 三角函数 sqrt 平方根
pow(double a,doble b) a的b次幂 log 自然对数
exp e为底指数
max(double a,double b)
min(double a,double b)
random() 返回0.0到1.0的随机数
long round(double a) double型数据a转换为long型(四舍五入)
toDegrees(double angrad) 弧度—>角度
toRadians(double angdeg) 角度—>弧度
43.2 使用方式
# 表示任意数字,0-9任意单个数字
, 千分位
. 小数点
0 补位
44.2 BigDecimal和BigInteger
44.2.1 概述
8.1 BigInteger
1、Integer类作为int的包装类,能存储的最大整型值为2^31-1,Long类也是有限的, 最大为2^63-1。如果要表示再大的整数,不管是基本数据类型还是他们的包装类 都无能为力,更不用说进行运算了。
2、java.math包的BigInteger可以表示不可变的任意精度的整数。BigInteger 提供
所有 Java 的基本整数操作符的对应物,并提供 java.lang.Math 的所有相关方法。 另外,BigInteger 还提供以下运算:模算术、GCD 计算、质数测试、素数生成、 位操作以及一些其他操作。
3、构造器
BigInteger(String val):根据字符串构建BigInteger对象
4、常用方法
public BigInteger abs():返回此 BigInteger 的绝对值的 BigInteger。
BigInteger add(BigInteger val) :返回其值为 (this + val) 的 BigInteger
BigInteger subtract(BigInteger val) :返回其值为 (this - val) 的 BigInteger
BigInteger multiply(BigInteger val) :返回其值为 (this * val) 的 BigInteger
BigInteger divide(BigInteger val) :返回其值为 (this / val) 的 BigInteger。整数 相除只保留整数部分。
BigInteger remainder(BigInteger val) :返回其值为 (this % val) 的 BigInteger。
BigInteger[] divideAndRemainder(BigInteger val):返回包含 (this / val) 后跟(this % val) 的两个 BigInteger 的数组。
BigInteger pow(int exponent) :返回其值为 (thisexponent) 的 BigInteger。
8.2 BigDecimal
一般的Float类和Double类可以用来做科学计算或工程计算,但在商业计算中, 要求数字精度比较高,故用到java.math.BigDecimal类。
BigDecimal类支持不可变的、任意精度的有符号十进制定点数。
构造器
public BigDecimal(double val)
public BigDecimal(String val)
常用方法
public BigDecimal add(BigDecimal augend)
public BigDecimal subtract(BigDecimal subtrahend)
public BigDecimal multiply(BigDecimal multiplicand)
public BigDecimal divide(BigDecimal divisor, int scale, int roundingMode)
44.2.2 使用
44.2.3 练习
Day16-异常机制
异常是Java中提供的一种识别及响应错误情况的一致性机制。有效地异常处理能使程序更加健壮、易于调试。
异常发生的原因有很多,比如:
1)用户输入了非法数据
2)要打开的文件不存在
3)网络通信时连接中断
4)JVM内存溢出
5)这些异常有的是因为用户错误引起,有的是程序错误引起的,还有其它一些是因为物理错误引起的。
异常就是错误的另一种说法
在java中有一个专门模拟所有异常和错误的类(Throwable),所有的异常类都必须继承这个类
try...catch... : 解决异常,一般用在客户端
throws : 抛出异常,一般用在类库端(服务端)
throw : 制造异常,异常源点,创建一个异常对象
有些错误我们不想处理,或者没有办法处理,或者不知道应该怎么处理(通常是类库端),一般使用throws把问题抛给调用处
在一个就是知道怎么处理,直接使用try...catch...处理问题即可,一般是main方法
必须执行的语句块
最大的异常类是 Throwable
直接两个子类 : Error(我们解决不了,比如栈溢出)和 Exception(所有的子类除了一个RunTimeException之外,其他全是编译时异常)
try{
高风险代码;
}catch(异常类 变量){
处理措施;
}
目的 : 增强程序的鲁棒性,健壮性
异常继承体系(掌握)
Error
概念
系统内部错误,这类错误由系统进行处理,程序本身无需捕获处理。
比如:OOM(内存溢出错误)、VirtualMachineError(虚拟机错误)、StackOverflowError(堆栈溢出错误)等,一般发生这种情况,JVM会选择终止程序。
示例
//堆栈溢出错误
public class TestError {
public static void recursionMethod() {
recursionMethod();// 无限递归下去
}
public static void main(String[] args) {
recursionMethod();
}
}
报错信息:
Exception in thread “main” java.lang.StackOverflowError
at com.TestError.recursionMethod(TestError.java:5)
at com.TestError.recursionMethod(TestError.java:5)
at com.TestError.recursionMethod(TestError.java:5)
at com.TestError.recursionMethod(TestError.java:5)
at com.TestError.recursionMethod(TestError.java:5)
at com.TestError.recursionMethod(TestError.java:5)
… …
5. Exception
5.1 概述
Exception是所有异常类的父类。分为非RuntimeException和RuntimeException 。
非RuntimeException
指程序编译时需要捕获或处理的异常,如IOException、自定义异常等。属于checked异常。
RuntimeException
指程序编译时不需要捕获或处理的异常,如:NullPointerException等。属于unchecked异常。一般是由程序员粗心导致的。如空指针异常、数组越界、类型转换异常等。
5.2 常用方法
5.3 TryCatch
5.3.1 第一种
这种就是编译时异常,如果不出来,压根不能运行,这时候 我们要么抛出,要么处理
5.3.2 第二种
高风险代码;
异常操作
异常操作
比如 用户登陆,如果使用exception捕捉,你是不知道是用户名不对还是密码不对的,那么就只能提醒 "用户名或密码不对"
如果想分开提醒 到底是用户名错了还是密码错了 就只能分开捕捉
5.3.3 第三种
5.3.4 第四种
1 多个异常可以在一个catch中同时捕捉, 需要使用 | 隔开
2 自动关闭资源
try(开启资源语句){
高风险代码;
}catch(异常类 变量){
处理语句;
}
5.4 Throws
5.4.1 第一种
5.4.2 第二种
5.4.3 注意-覆写不能有更宽泛的异常
5.5 Finally
5.5.1 基本使用
比如关闭资源语句
System.exit() : 关闭虚拟机
5.5.2 注意事项(return)
5.5.3 面试题
谈谈你对final的理解
1 final是什么,怎么用
2 finally是什么,怎么用
3 finalize是什么,怎么用
5.5.4 Finally一个应用场景
5.6 Throw
异常源点,创建一个异常
throw new 异常类( 错误信息 );
详细用法参考 自定义异常
6. 自定义异常类
6.1 定义异常类
6.2 应用场景
1 如果用户名不是 admin 则 提示 用户名不存在
2 如果密码不是 root 则 提示 密码不正确
6.2.1用户类
6.2.2业务类
6.2.3客户端类
Day17-集合
45. Collection
45.1 概述
boolean add() : 添加元素
void clear() : 清空集合
boolean remove() 删除某个元素
boolean isEmpty() 判断集合中是否为空
boolean contains() : 是否包含某个元素
int size() 返回集合中 元素个数
java集合是使程序能够存储和操纵元素不固定的一组数据。 所有Java集合类都位于java.uti包中。
【问】:之前我们需要把多个元素放到一起的时候,使用的是数组。那么为何还要提供Java集合工具类呢?
我们通过对比数组和Java集合工具类来解释Java集合工具类的必要性。
注意:如果集合中存放基本类型,一定要将其 “装箱”成对应的”基本类型包装类”。
45.2 继承体系
由以上两图我们可以看出Java集合类有清晰的继承关系,有很多子接口和实现类。但是,并不是所有子接口或实现类都是最常用的。
下面我们列举出最常用的几个子接口和实现类:
Collection ——> List ——> ArrayList类
Collection ——> List ——> LinkedList类
Collection ——> Set ——> HashSet类
Collection ——> Set ——> SortedSet接口 ——> TreeSet类
Map ——> HashMap类
Map ——> SortedMap ——> TreeMap类
45.3 Collection和Iterator
45.3.1 Collection概述
Collection接口是List接口和Set接口的父接口,它定义的方法可以用于操作List集合和Set集合。
Collection接口定义的方法
45.3.2 Collection使用
45.3.3 Iterator概述
也就是 我们无需关心该序列底层数据结构是什么样子的,只要拿到这个对象,使用迭代器就可以遍历这个对象的内部数据
Iterator it = 集合对象.iterator();
调用集合对象自己的iterator方法就可以创建属于自己的迭代器
1 boolean hasNext() : 判断光标下一位 是否还有元素,有就返回true,没有就返回false,
生成迭代器的时候,光标不是指向第一个元素的,而是在最顶端,没有指向任何元素
光标不会自动复位,使用完之后,必须重新生成
2 E next() : 把光标向下移动一位,并返回该位上的数据
3 remove() : 删除当前光标指向的元素
三个方法的使用顺序,就是 1,2,3
注意 使用迭代器进行遍历操作时 如果需要删除元素,只能使用 迭代器的remove方法,不能使用集合的remove方法
45.3.4 Iterator使用
45.3.5 注意 : Contains和remove
判断集合中是否包含某个元素
在集合中删除指定元素
c.contains("asd") , 会用 asd 调用String的equals方法 挨个和集合中元素进行比较
判断谁,就用谁调用equals方法,和集合中元素进行比较
所以如果是存储的自定义类型(类对象),就需要根据需求覆写equals方法
45.4 List
存入顺序和取出顺序是一致的,并且有指定的下标可以表示数据的唯一性,所以可以存在重复数据
ArrayList : 底层是数组,查询效率较高,添加和删除效率较低
LinkedList : 底层是双向链表,查询效率较低,添加和删除效率较高
Vector 已经过时了,ArrayList是Vector的升级版,而Vector属于线程安全,ArrayList属于非线程安全,所以ArrayList效率较高
45.4.1 ArrayList
while , for, foreach , iterator
常用方法
45.4.2 LinkedList
45.4.2.1 基本使用
LinkedList : 底层是双向链表
链表 : 链表中保存的是节点,每个节点中有三个元素
1 自身对象(添加的元素) , 2 上一个节点的地址 , 3 下一个节点的地址
链表是没有下标的,内存空间也不是连续的,所以查询比较慢
由于内存空间不是连续的,只是保存上下节点的引用,随意添加删除比较快
add(E e) : 添加到尾部
push(E e) : 添加到头部
addFirst(E e) : 添加到头部
addLast(E e) : 添加到尾部
offerFirst(E e) 添加到头部,成功返回true
offerLast(E e) : 添加到尾部,成功返回true
get(int index) : 返回指定下标对应的数据(链表没有下标,只是模拟下标,方便我们查询使用)
本质 就调用了两个方法 : linkLast 和 linkFirst 所以没任何区别
底层实现
45.4.2.2 底层实现
45.4.2.2.1 Node节点类
节点类其实是 LinkedList中的一个静态内部类,并且该类中保存三个属性
添加的数据 item
上一个节点内存地址 next
下一个节点内存地址 prev
根据Node节点类的实现方式,就能看出,该节点是双向的
45.4.2.2.2 LinkedList类
而LinkedList中 保存了两个节点类的对象,分别是首节点和尾结点,方便我们使用,所以我们可以首部添加,也可以尾部添加
已添加为例,讲解LinkedList的底层实现
45.4.2.2.3 添加-add
根据add方法,最终发现实现尾部添加的是 linkLast方法
下一步 进行代码分析
45.4.2.2.4 获取-get
校验下标
获取数据
Get只是模拟下标获取的方式而已,其实本质就是遍历,从头挨个找
只不过做了一定的判断,判断是找前半截还是后半截,来决定 是正序遍历还是倒叙遍历
本质就是循环通过next一个一个的节点去找,所以这个get和ArrayList中的get完全不是一回事,ArrayList中就是封装了下标访问方式而已,下面是ArrayList的get方法实现
45.4.2.2.5 删除原理
45.5 Set和排序
45.5.1 TreeSet
数字 : 默认从小到大
字符串 : 默认比较每位ASCII码
日期 : 默认比较自然日期 昨天-今天-明天
45.5.2 排序
45.5.2.1 Comparable
因为都实现了 implements Comparable
需要实现该接口才行
添加的时候 会自动调用该对象的compareTo方法,而该方法就在Comparable接口中,所以 必须要实现,否则不能添加到TreeSet中
’’
Day19-IO
46. 复习之数据结构
数据结构是计算机存储和组织数据的方式,简单来说 就是用来存储数据的,只不过根据存储的方式和操作以及特性不同,分为几类
数组 : 有序可重复,查询快更改快,删除添加慢
链表 : 有序可重复,添加删除快,查询更改慢
散列表 : 无序不可重复,存储键值对,key唯一,value可以重复
Tree : 无序不可重复,元素按照某种规则自动排序,(Comparator和Comparable)
他们的本质都是用来保存数据的,我们需要把他们的特征记住,然后再根据我们的需求合理选择数据结构进行存储数据,效率会有所提升
输入 : 指往内存中导入数据
输出 : 指从内存中写出数据
按功能不同,分为节点流和处理流
节点流:直接操作数据源
47.3 四大抽象类
InputStream 字节输入
OutputStream 字节 输出
Reader 字符输入
Writer 字符输出
47.3.1 InputStream
47.3.2 OutputStream
47.3.3 Reader
47.3.4 Writer
47.4 文件流
1 FileInputStream 字节输入
2 FileOutputStream 字节输出
3 FileReader 字符输入
4 FileWriter 字符输出
47.4.1 FileInputStream
47.4.1.1 概述
java.io.InputStream
java.io.FileInputStream; 按照自己的方式在原始文件中读取数据
想要读取一个文件,必须要找到它
怎么找? 定位到某个文件 有两种方式, 1 绝对路径 2 相对路径
相对路径 : 相对当前文件,如何找到 ./ 当前目录 , …/ 上级目录, …/…/…/ 上上上级目录 , / 下级
绝对路径 : 以系统跟目录为准,如何找到这个文件
47.4.1.2 常用方法
// 读取数据
// 读取一个字节,返回下一个字节的值,因为开始光标在顶端,如果到达文件末尾(后面没有了) 就返回-1
int i1 = fis.read();
47.4.1.3 Read
Read( ) 返回下一个字节的值,如果到达文件末尾 返回 -1
47.4.1.4 Read(byte[] bytes)
可以无参,可以传入一个byte数组,
read(byte[] b) : 使用字节数组来存储读取的数据,并一次返回(要么数组装满,要么到达文件末尾),可以提高读取效率
返回值 是本次读取到的字节个数,如果到达文件末尾 返回-1
47.4.2 FileReader
47.4.2.1 概述
FileRead : 一次读一个字符,也就是2字节,而 unicode编码也是2字节
所以 字符串输入流 一般用于读取纯文本文件,像压缩包,图片等还是使用字节流
47.4.2.2 常用方法
read一样有方法重载,
read() : 读取一个字符,返回下一个的字符数据,到达文件末尾返回-1
read(char[] c) : 读取一个字符数组,返回读取到的字符数,到达文件末尾返回-1
47.4.2.3 使用方式
47.4.3 FileOutputStream
47.4.3.1 概述
java.io.OutputStream
java.io.FileOutputStream
将程序中内容 写出到硬盘中
输入流 找不到指定文件 会报错 , 但是输出流不会报错,会自动创建该文件,但是不会创建文件夹(目录)
47.4.3.2 常用方法
构造方法 :
FileOutputStream(String) : 把内容输出到指定文件中,并会覆盖原文件中内容
FileOutputStream(String,boolean) : 如果第二个参数是true,把内容输出到指定文件中,并在原文件中追加数据
成员方法 :
write(int i) : 写出整型
write(byte[] b) : 写出字节数组,想要输出字符串可以利用字符串中的getBytes()方法,把字符串转换为字节数组
flush() : 刷缓存,强制把缓冲区写出,避免造成数据遗漏
47.4.3.3 使用方式
47.4.4 FileWriter
47.4.4.1 概述
java.io.File.Writer 字符输出流
47.4.4.3 使用方式
47.5 缓冲流
47.5.1 概述
把输出的数据存入一个缓冲区,然后一次写出
BufferedInputStream,BufferedOutputStream,BufferedReader,BufferedWriter
针对字符操作,提供了两个新方法
readLine() : 读一行,返回值就是读到的这一行的数据,到达文件末尾,返回null
newLine() : 换行,就等于\n
【特点】
主要是为了提高效率而存在的,减少物理读取次数
提供readLine()、newLine()这样的便捷的方法(针对缓冲字符流)
47.5.2 BufferedInputStream
和 FileInputStream方法几乎一致,只是效率提高
47.5.3 BufferedOutputStream
47.5.4 BufferedReader
新增方法 readLine() 读取一行
47.5.5 BufferedWriter
新增输出换行方法 newLine()
47.6 转换流
47.6.1 概述
并且 还可以再转换过程中 指定字符编码
特点
转换流是指将字节流向字符流的转换,主要有InputStreamReader和OutputStreamWriter
InputStreamReader主要是将字节流输入流转换成字符输入流
OutputStreamWriter主要是将字节流输出流转换成字符输出流
47.6.2 InputStreamReader
47.6.2.1 常用方法
47.6.2.2 使用方式
47.6.3 OutputStreamWriter
参考上面InputStreamReader的字节 转字符即可
47.7 打印流
47.7.1 概述
【特点】
打印流是输出最方便的类
包含字节打印流PrintStream,字符打印流PrintWriter
PrintStream是OutputStream的子类,把一个输出流的实例传递到打印流之后,可以更加方便地输出内容,相当于把输出流重新包装一下
PrintStream类的print()方法被重载很多次print(int i)、print(boolean b)、print(char c)
【标准输入/输出】
Java的标准输入/输出分别通过System.in和System.out来代表,在默认的情况下分别代表键盘和显示器,当程序通过System.in来获得输入时,实际上是通过键盘获得输入。当程序通过System.out执行输出时,程序总是输出到屏幕。
在System类中提供了三个重定向标准输入/输出的方法
static void setErr(PrintStream err) 重定向“标准”错误输出流
static void setIn(InputStream in) 重定向“标准”输入流
static void setOut(PrintStream out)重定向“标准”输出流
47.7.2 使用方式
Day21-IO
48. IO
48.1 数据流
48.1.1 概述
为了方便地操作Java语言的基本数据类型和String的数据,可以使用数据流。
数据流有两个类:(用于读取和写出基本数据类型、String类的数据)
DataOutputStream按照一定的格式输出,再通过DataInputStream以一定格式读入。由于可以得到java的各种基本类型甚至字符串,这样对得到的数据便可以方便地处理。这在通过协议传输的信息的网络上是非常适用的。
DataInputStream 和 DataOutputStream
分别“套接”在 InputStream 和 OutputStream 子类的流上
DataInputStream中的方法
boolean readBoolean()
char readChar()
double readDouble()
long readLong()
String readUTF()
byte readByte()
float readFloat()
short readShort()
int readInt()
void readFully(byte[] b)
48.1.2 使用方式
48.2 对象流
48.2.1 概述
创建对象的4种方式
1 new 用的最多
2 反射机制
3 反序列化
4 clone 不用了,被序列化代替 , Object.clone()
之前我们都是把数据放到硬盘中,对象是在内存中的,从来没有把对象放到过硬盘中
但是 硬盘中的数据是可以持久化的(长期保存) , 但是内存中不同,比如关机之后 就没了
而有时候我们的对象也是需要长期保存的,所以出现了序列化技术,就是为了把对象持久化保存到硬盘
序列化 : 把对象保存到硬盘中,可以持久化存储 ObjectOutputStream
反序列化 : 把硬盘中的对象文件,载入到内存中 ObjectInputStream
并且 要被序列化的对象所在的类,必须实现Serializable接口,该接口中没有任何方法,仅仅是一种标记,以被编译器做特殊的处理
目的 :
1 长时间存储对象
2 对象传递
应用场景 :
序列化就是将数据对象转换为二进制流,从而能够进行数据持久化和网络传输的过程
如果对象不进行序列化操作,那么就没有办法存储和传输
反序列化就是序列化的逆向处理过程
传递过程 :
数据对象--> 序列化-->二进制流--> 加密处理--> 网络传输 --> 数据解密--> 反序列化--> 数据对象
48.2.2 序列化
48.2.3 反序列化
48.2.4 serualVersionUID
* 如果不加UID,每次更改User类,都需要重新序列化,否则反序列化会报错
*
* 目的就是序列化对象版本控制,一旦反序列化时,版本不对应会抛出InvalidClassException
*
* 如果不指定 每次更改会自动生成一个新的
加上该常量后,更改类,不需要重新序列化 直接反序列化也是可以用的
48.2.5 Transient
* transient 使用该修饰符修饰,则该属性不会被序列化
*
* 当反序列化的时候,再访问该属性,就是对应数据类型的默认值
48.3 File
48.3.1 概述
java.io.File类:文件和文件目录路径的抽象表示形式,与平台无关
File 能新建、删除、重命名文件和目录,但 File 不能访问文件内容本身。
如果需要访问文件内容本身,则需要使用输入/输出流。
想要在Java程序中表示一个真实存在的文件或目录,那么必须有一个File对 象,但是Java程序中的一个File对象,可能没有一个真实存在的文件或目录。
File对象可以作为参数传递给流的构造器
48.3.2 构造方法
public File(String pathname)以pathname为路径创建File对象,可以是绝对路径或者相对路径,如果pathname是相对路径,则默认的当前路径在系统属性user.dir中存储。
绝对路径:是一个固定的路径,从盘符开始
相对路径:是相对于某个位置开始
public File(String parent,String child)以parent为父路径,child为子路径创建File对象。
public File(File parent,String child)根据一个父File对象和子文件路径创建File对象
48.3.3 路径使用
路径中的每级目录之间用一个路径分隔符隔开。
路径分隔符和系统有关:
windows和DOS系统默认使用“\”来表示
UNIX和URL使用“/”来表示
Java程序支持跨平台运行,因此路径分隔符要慎用。
为了解决这个隐患,File类提供了一个常量:public static final String separator。根据操作系统,动态的提供分隔符
举例:
File file1 = new File(“d:\shangyun\info.txt”);
File file2 = new File(“d:” + File.separator + " shangyun " + File.separator + “info.txt”);
File file3 = new File("d:/ shangyun ");
48.3.4 常用方法
获取功能:
public String getAbsolutePath():获取绝对路径
public String getPath() :获取路径
public String getName() :获取名称
public String getParent():获取上层文件目录路径。若无,返回null
public long length() :获取文件长度(即:字节数)。不能获取目录的长度。
public long lastModified() :获取最后一次的修改时间,毫秒值
public String[] list() :获取指定目录下的所有文件或者文件目录的名称数组
public File[] listFiles() :获取指定目录下的所有文件或者文件目录的File数组
重命名功能:
public boolean renameTo(File dest):把文件重命名为指定的文件路径
判断功能:
public boolean isDirectory():判断是否是文件目录
public boolean isFile() :判断是否是文件
public boolean exists() :判断是否存在
public boolean canRead() :判断是否可读
public boolean canWrite() :判断是否可写
public boolean isHidden() :判断是否隐藏
创建删除功能:
public boolean createNewFile() :创建文件。若文件存在,则不创建,返回false
public boolean mkdir() :创建文件目录。如果此文件目录存在,就不创建了。 如果此文件目录的上层目录不存在,也不创建。
public boolean mkdirs() :创建文件目录。如果上层文件目录不存在,一并创建
注意事项:如果你创建文件或者文件目录没有写盘符路径,那么,默认在项目 路径下。
public boolean delete():删除文件或者文件夹
删除注意事项:
Java中的删除不走回收站。
要删除一个文件目录,请注意该文件目录内不能包含文件或者文件目录
48.3.5 使用方式
48.3.6 练习 : 复制目录
复制文件夹
48.3.6.1 思路
1 文件复制 : 本质就是输入和输出
a 完成文件输入
b 完成文件输出
c 整合输入和输出 完成文件复制
2 获取目录下所有子目录
a 获取目录对象
b 获取目录下所有直接子目录
c 获取目录下所有后代目录(子 : 父子关系,后代 : 长辈关系,比如爷爷和孙子属于后代不属于子)
3 整合所有后代目录,和文件复制
a 获取所有后代目录后,得到每一个后代文件对象
b 通过文件对象可以获取文件的全路径
c 通过全路径,就可以创建输入流
d 然后创建输出流输出
48.3.6.2 注意
输入流的路径和输出流的路径不能一模一样,否则会导致两种情况
1 文件清空 false
2 死循环写数据 true
因为创建输出流对象的时候,构造方法的第二个参数可以传递一个boolean值
True 说明向该文件中追加写入新数据
False 说明覆盖该文件,重新写入数据
1 false情况下
并且 如果没有传递boolean值,默认是覆盖写入
如果是覆盖写入,则创建该输出流对象时,就会先把该文件清空,导致使用输入流去读的时候,读不到,返回-1(因为已经没有数据了)
2 true情况下
如果是true,说明是追加写入,那么在创建输出流对象的时候就不会清空该文件
但是,读的时候,就会导致有读不完的数据
因为一边读,一边向这个文件中追加,并且读了多少,就追加多少,肯定没完没了了
3 总结
所以 我们再复制文件的时候,不能把输入和输出的路径写的一样
48.3.6.3 编码
public static void main(String[] args) {
File file = new File("D:\\16期\\课件");
checkMenu(file);
}
private static void checkMenu(File file) {
// 判断是否是文件
if (file.isFile()) {
// 文件全路径(源文件)
String filePath = file.getAbsolutePath();
// 截取并拼接字符串,把源目录中内容复制到E盘
// D:\16期\课件\day_21_File类、序列化流、Properties\作业\作业.txt
// E:\16期\课件\day_21_File类、序列化流、Properties\作业\作业.txt
String newFilePath = "E" + filePath.substring(1);
// 判断目录目录是否存在,因为输出流只会创建文件,不会创建目录
// newFilePath一定是一个文件对象,这里需要判断该文件所在的目录是否存在
// 获取上级对象
File parentFile = new File(newFilePath).getParentFile();
if (!parentFile.exists()) {
parentFile.mkdirs();
}
// 到这里 目标目录就已经有了,就该复制操作了
if (filePath.trim().equals(newFilePath.trim())) {
throw new FileException("源文件路径不能和目标文件路径一致");
}
try (FileInputStream fis = new FileInputStream(filePath);
FileOutputStream fos = new FileOutputStream(newFilePath);
BufferedInputStream bis = new BufferedInputStream(fis);
BufferedOutputStream bos = new BufferedOutputStream(fos);) {
byte[] bytes = new byte[102400];
int temp = 0;
while ((temp = bis.read(bytes)) != -1) {
bos.write(bytes, 0, temp);
}
bos.flush();
} catch (IOException e) {
e.printStackTrace();
}
return;
}
// 到这里说明是目录
// 获取所有子文件
File[] files = file.listFiles();
for (File file2 : files) {
checkMenu(file2);
}
}
48.3.6.4 扩展之代码标准化
Day22-多线程
49. 多线程
49.1 基本概念:程序、进程、线程
程序(program)是为完成特定任务、用某种语言编写的一组指令的集合。即指一 段静态的代码,静态对象。
进程(process)是程序的一次执行过程,或是正在运行的一个程序。是一个动态
的过程:有它自身的产生、存在和消亡的过程。——生命周期
如:运行中的QQ,运行中的MP3播放器
程序是静态的,进程是动态的
进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。
若一个进程同一时间并行执行多个线程,就是支持多线程的
线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开
销小
一个进程中的多个线程共享相同的内存单元/内存地址空间à它们从同一堆中分配对象,可以 访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资 源可能就会带来安全的隐患。
示意图:
49.2 单核CPU和多核CPU的理解
a)单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程 的任务。例如:虽然有多车道,但是收费站只有一个工作人员在收费,只有收了费 才能通过,那么CPU就好比收费人员。如果有某个人不想交钱,那么收费人员可以 把他“挂起”(晾着他,等他想通了,准备好了钱,再去收费)。但是因为CPU时 间单元特别短,因此感觉不出来。
b)如果是多核的话,才能更好的发挥多线程的效率。(现在的服务器都是多核的)
c)一个Java应用程序java.exe,其实至少有三个线程:main()主线程,gc() 垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。
49.3 并行与并发
并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事1
比如可以同时处理所有请求,就是并行,并且并行必须在多处理器中(大于等于2)
不能同时处理多个请求,需要间隔切换执行,就是并发(单处理器和多处理器都可以存在)
注意 : 单线程其实是比多线程效率高的,因为多线程需要线程切换,比较耗费资源,只不过多线程给用户的响应时间要快
49.4 使用多线程优点
背景:以单核CPU为例,只使用单个线程先后完成多个任务(调用多个方 法),肯定比用多个线程来完成用的时间更短,为何仍需多线程呢?
多线程程序的优点:
1.提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
2.提高计算机系统CPU的利用率
3.改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和
修改
49.5 何事需要多线程
程序需要同时执行两个或多个任务。
程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜 索等。
需要一些后台运行的程序时。
49.6 创建方式
49.6.1 Thread
Java语言的JVM允许程序运行多个线程,它通过java.lang.Thread
类来体现。
Thread类的特性
每个线程都是通过某个特定Thread对象的run()方法来完成操作的,经常 把run()方法的主体称为线程体
通过该Thread对象的start()方法来启动这个线程,而非直接调用run()
构造器
Thread():创建新的Thread对象
Thread(String threadname):创建线程并指定线程实例名
Thread(Runnable target):指定创建线程的目标对象,它实现了Runnable接
口中的run方法
Thread(Runnable target, String name):创建新的Thread对象
创建过程
继承Thread类
1)定义子类继承Thread类。
2)子类中重写Thread类中的run方法。
3)创建Thread子类对象,即创建了线程对象。
4)调用线程对象start方法:启动线程,调用run方法。
49.6.2 Runnable
49.6.3 继承和实现的区别
继承方式和实现方式的联系与区别
public class Thread extends Object implements Runnable
区别
继承Thread:线程代码存放Thread子类run方法中。
实现Runnable:线程代码存在接口的子类的run方法。
实现方式的好处
避免了单继承的局限性
多个线程可以共享同一个接口实现类的对象,非常适合多个相同线 程来处理同一份资源。
49.7 常用方法
start() : 启动线程唯一方式
setName( ) : 设置线程的名字,默认是 Thread-0 , Thread-1 … 以此类推
getName( ) : 获取线程的名字
setPriority() : 设置线程优先级
getPriority() : 获取线程优先级
static currentThread() : 获取当前线程的内存地址(获取当前线程对象)
static sleep() : 让当前线程进入睡眠,参数是毫秒数
49.8 线程优先级
49.8.1 时间片分配
Java的调度方法
同优先级线程组成先进先出队列(先到先服务),使用时间片策略
对高优先级,使用优先调度的抢占式策略
49.8.2 优先级使用
优先级 : java中分为10个等级,分别是1-10
另外在Thread类中还提供了三个常量,分别保存1,5,10 优先级
最高 : 10 Thread.MAX_PRIORITY
正常 : 5 Thread.NORM_PRIORITY
最低 : 1 Thread.MIN_PRIORITY
getPriority() : 获取该线程优先级
setPriority() : 设置该线程优先级
线程创建时继承父线程的优先级Thread的优先级是5
低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用
49.9 生命周期
49.9.1 概述
创建 --> 就绪 --> 运行 --> 阻塞 --> 复活 --> 阻塞… --> 死亡
JDK中用Thread.State类定义了线程的几种状态
要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类 及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五 种状态:
新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建 状态
就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态, run()方法定义了线 程的操作和功能
阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态
死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束
49.9.2 代码示例
49.10 线程控制
Interrupt() : 强制唤醒某一个线程
49.10.1 Interrupt
1 正常唤醒 : 就是睡眠时间到了
2 异常唤醒 : 强制打断睡眠,会报异常
强制唤醒 会报异常
49.10.2 Join
Join : 线程合并,让指定线程在该线程运行完之后再执行
49.10.3 Yield
49.10.4 stop
49.11 线程同步机制
49.11.1 概述
问题的提出
多个线程执行的不确定性引起执行结果的不稳定
多个线程对账本的共享,会造成操作的不完整性,会破坏数据
线程同步 :
当多个线程操作同一个数据的时候,为了保证数据的一致
线程同步本质就是数据同步,是一种安全机制
异步编程 :
线程之间是完全独立的,谁的运行也不会受到别的线程的影响
同步编程 :
线程之间不是独立的,相互之间是有影响的,某个功能必须必只能让一个线程同时执行,主要为了数据安全
同步的原因 :
1 数据同步,为了数据安全,某种情况来讲,同步可以理解为暂时把多线程转换为单线程
2 什么时候需要同步
1 必须多线程(单线程没有啥并发和冲突的情况)
2 多个线程有可能同时操作同一个数据的可能性
3 主要是数据的更改操作
只要对方法加上 synchronized的成员方法,就代表该方法不能被多个线程同时访问
锁是每个对象都有的,synchronized只是把锁锁住的一个持续动作,而多个线程必须保存同一个对象,才能使用同一把锁,才能相互排斥,才能保证数据安全
如果多个线程之间保存的不是同一个对象,尽管是同一个类的不同对象,也是没有办法相互排斥的
49.11.2 示例,暴露问题
结果
49.11.3 解决方案
Java对于多线程的安全问题提供了专业的解决方式:同步机制
1.同步代码块:
synchronized (对象){
// 需要被同步的代码;
}
2.synchronized还可以放在方法声明中,表示整个方法为同步方法。
例如:
public synchronized void show (String name){
….
}
未加锁
因为未加锁,所以该方法可以N个线程同时访问
以上示例可以看出,加上synchornized之后,该方法不能被多个线程同时执行
49.12 Lock
49.12.1 概述
从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同
步锁对象来实现同步。同步锁使用Lock对象充当。
java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的 工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象 加锁,线程开始访问共享资源之前应先获得Lock对象。
ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和 内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以 显式加锁、释放锁。
49.12.2 使用
49.12.3 synchronized 与 Lock 的对比
1.Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是 隐式锁,出了作用域自动释放
2.Lock只有代码块锁,synchronized有代码块锁和方法锁
3.使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有
更好的扩展性(提供更多的子类)
优先使用顺序:
Lock 同步代码块(已经进入了方法体,分配了相应资源) à 同步方法
(在方法体之外)
Day23-多线程
50. 多线程
50.1 守护线程
50.1.1 概述
50.2 Timer
50.2.1 概述
50.2.2 使用
50.3 死锁
50.3.1 锁相关知识
50.3.2 概述
死锁
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃
自己需要的同步资源,就形成了线程的死锁
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于 阻塞状态,无法继续
解决方法
专门的算法、原则
尽量减少同步资源的定义
尽量避免嵌套同步
1 某个线程的执行完成,需要先后,嵌套,锁定执行两个对象,并且先执行对象1
2 另一个线程的执行完成,需要先后,嵌套,锁定执行两个对象,并且先执行对象2
3 在第一个线程中,要去访问对象2的时候,发现被锁定 了,只能等待
3 在第二个线程中,要去访问对象1的时候,发现被锁定了,只能等待
synchronized(xxx){} 代码块锁,可以锁类,也可以锁对象
如果锁对象,当访问该代码块锁的时候,该对象中所有的代码块锁和加锁的成员方法都被锁定
同理 访问对象中加锁的成员方法的时候,代码块锁也会被锁定
如果是锁类,当访问该代码块的时候,该类中所有的代码块锁和加锁的静态方法都被锁定
同理 访问类中加锁的静态方法的时候,代码块锁也会被锁定
50.3.3 代码实现
50.4 线程通信
50.4.1 概述
wait() 与 notify() 和 notifyAll()
a)wait():令当前线程挂起并放弃CPU、同步资源并等待,使别的线程可访问并修改共享资源,而当 前线程排队等候其他线程调用notify()或notifyAll()方法唤醒,唤醒后等待重新获得对监视器的所有 权后才能继续执行。
b)notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待
c)notifyAll ():唤醒正在排队等待资源的所有线程结束等待.
注意:这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报
java.lang.IllegalMonitorStateException异常
因为这三个方法必须有锁对象调用,而任意对象都可以作为synchronized的同步锁, 因此这三个方法只能在Object类中声
而 wait是Object中的成员方法,也就是每个对象都有的方法,挂起,会交出锁(其他线程就可以访问该方法了)
50.4.2 使用方式
50.4.3 面试题之生产者与消费者
1 有一个业务类,SynStack 其中有一个成员变量 cnt 用来保存生产的个数,还需要一个容器来存储生产的数据char[]
2 业务类需要有对应的生产方法和消费方法
1 生产 push : 向数组中添加元素,需要判断数组是否满了,如果满了 就不生产,唤醒消费者
2 消费 pop : 同上,判断使用没有数据,如果没有 就唤醒生产者
3 两个线程分别调用生产和消费
50.5 单例模式
50.5.1 概述
懒汉模式 : 第一次使用的时候创建对象
饿汉模式 : 类加载的时候创建对象
1 构造方法私有化
2 公共的静态方法用于获取对象
3 私有化静态变量存储创建后的对象
50.5.2 之前的编码
50.5.3 问题-多线程环境下不行
50.5.3.1 分析原因
因为多线程环境存在并发性和并行性 , 有可能同时执行到这个方法,导致创建多个对象
通过结果测试,创建时不一定是一个对象,原因是因为这样…
50.5.3.2 解决方案1
加锁 可以解决,一定不会出现问题
但是会有新的问题,
没有加锁前,出现问题,是因为,第一次执行的时候,多个线程并行执行到这个判断了
因为第一次执行,s是null,没有对象,所以导致创建多个对象
但是 一旦跳过第一次,后续不管多少个并发/并行 s都不再等于null,就不会再创建
而我们如果使用synchronized修饰方法的话,虽然结果一定没有问题,但是效率降低太多了
因为不仅仅是第一次,任何时候只要想来获取对象,都需要排队等待
而 我们这个程序中,其实只需要保证第一次排队等待即可,一旦创建了对象之后,则不需要排队,即使在这时候有很多并行线程同时执行,判断s==null的时候 也是false,因为有对象了
所以 以上编码 , 不是最佳选择
50.5.3.3 解决方案2
通过上面程序,我们不能再方法加锁了,因为影响效率
这样的话,只有第一次请求需要排队,一会就算有很多线程同时执行这个方法,也不会排队等待,因为方法没有加锁,多个线程 可以同时进来执行该方法
另外s已经在第一次请求的时候赋值过了,所以判断s==null时 也是false
所以 这种写法才是多线程环境下的最佳选择
50.6 线程池
50.6.1 概述
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程, 对性能影响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完 放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交 通工具。
好处:
a)提高响应速度(减少了创建新线程的时间)
b)降低资源消耗(重复利用线程池中线程,不需要每次都创建)
c)便于线程管理
i.corePoolSize:核心池的大小
ii.maximumPoolSize:最大线程数
iii.keepAliveTime:线程没有任务时最多保持多长时间后会终止
1 减少了创建 和销毁线程的次数,因为每个工作线程都可以被重复使用,可执行多个任务
2 可以根据系统的承受能力,调整线程池中的线程数量,防止因为消耗过多的内存,导致服务器死机
(每个线程需要大概1MB内存,线程开的越多,消耗内存越大,最后导致死机)
50.6.2 使用方式
JDK 5.0起提供了线程池相关API:ExecutorService 和 Executors
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行Runnable
Future submit(Callable task):执行任务,有返回值,一般又来执行Callable
void shutdown() :关闭连接池
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
50.6.2.1 NewCachedThreadPool
创建一个可根据需要创建新线程的线程池
// 创建一个可缓存线程池,如果线程池长度超过处理需要,可以灵活回收空闲线程
// 若没有可以回收的,则新建线程,线程池规模不存在限制,数量不固定
50.6.2.2 NewFixedThreadPool
50.6.2.3 NewScheduledThreadPool
创建一个固定长度线程池,支持定时及周期性执行任务
50.6.2.4 NewSingleThreadExecutor
Day24-网络编程&正则表达式
51. 网络编程
51.1 概述
Java是 Internet 上的语言,它从语言级上提供了对网络应用程 序的支持,程序员能够很容易开发常见的网络应用程序。
Java提供的网络类库,可以实现无痛的网络连接,联网的底层 细节被隐藏在 Java 的本机安装系统里,由 JVM 进行控制。并 且 Java 实现了一个跨平台的网络库,程序员面对的是一个统一 的网络编程环境。
51.2 网络基础
计算机网络:
把分布在不同地理区域的计算机与专门的外部设备用通信线路互连成一个规 模大、功能强的网络系统,从而使众多的计算机可以方便地互相传递信息、 共享硬件、软件、数据信息等资源。
网络编程的目的:
直接或间接地通过网络协议与其它计算机实现数据交换,进行通讯。
网络编程中有两个主要的问题:
如何准确地定位网络上一台或多台主机;定位主机上的特定的应用
找到主机后如何可靠高效地进行数据传输
51.3 网络通信
通信双方地址
a)IP
b)端口号
一定的规则(即:网络通信协议。有两套参考模型)
c)OSI参考模型:模型过于理想化,未能在因特网上进行广泛推广
d)TCP/IP参考模型(或TCP/IP协议):事实上的国际标准。
1.3.1 通信要素1:IP地址
IP 地址:InetAddress
唯一的标识 Internet 上的计算机(通信实体)
本地回环地址(hostAddress):127.0.0.1 主机名(hostName):localhost
IP地址分类方式1:IPV4 和 IPV6
IPV4:4个字节组成,4个0-255。大概42亿,30亿都在北美,亚洲4亿。2011年初已经用尽。以点分十进制表示,如192.168.0.1
IPV6:128位(16个字节),写成8个无符号整数,每个整数用四个十六进制位表示, 数之间用冒号(:)分开,如:3ffe:3201:1401:1280:c8ff:fe4d:db39:1984
IP地址分类方式2:公网地址(万维网使用)和私有地址(局域网使用)。192.168. 开头的就是私有址址,范围即为192.168.0.0–192.168.255.255,专门为组织机 构内部使用
特点:不易记忆
1.3.2 通信要素2:端口号
端口号标识正在计算机上运行的进程(程序)
不同的进程有不同的端口号
被规定为一个 16 位的整数 0~65535。
端口分类:
公认端口:0~1023。被预先定义的服务通信占用(如:HTTP占用端口80,FTP占用端口21,Telnet占用端口23)
注册端口:1024~49151。分配给用户进程或应用程序。(如:Tomcat占用端口8080,MySQL占用端口3306,Oracle占用端口1521等)。
动态/私有端口:49152~65535。
端口号与IP地址的组合得出一个网络套接字:Socket。
51.4 OSI七层
应用层,表示层,会话层,传输层,数据链路层,物理层,网络层
应用层 : 应用层是网络向用户提供的服务窗口,主要用来支持用户的需求,人的需求不同,应用层技术也就不同
提供了多种的应用服务 : 电子邮件(MHS),文件传输(FTAM),虚拟终端(VT).电子数据交换(EDI)等
主要的协议 : FTP(21),SMTP(25),DNS.HTTP(80)
表示层 : 为通信提供一种公共的语言,方便交互,因为不同的计算机系统结构使用的数据表示不同,例如 : IBM主机使用的是EBCDIC编码,而大部分PC机使用的是ASCII编码
其他功能例如 数据加密,数据压缩
会话层 : 提供的服务可以使应用建立和维持会话,并且能使会话同步
传输层 : 两天计算机经过网络进行数据通信时,是一个端到端的层次,具有缓冲作用
协议 : TCP/UDP
物理层 : 为数据段设备提供传送数据的通路,数据通路可以是一个物理媒体,也可以 是多个物理媒体连接而成
数据链路层 : 可以理解为数据通道,
MAC地址表示唯一性
网络层 : IP,以IP报文形式进行数据传递
51.5 网络协议
51.5.1 TCP/IP
51.5.1.1 概述
传输层协议中有两个非常重要的协议:
传输控制协议TCP(Transmission Control Protocol)
用户数据报协议UDP(User Datagram Protocol)。
TCP/IP 以其两个主要协议:传输控制协议(TCP)和网络互联协议(IP)而得名,实际上是一组协议,包括多个具有不同功能且互为关联的协议。
IP(Internet Protocol)协议是网络层的主要协议,支持网间互连的数据通信。
TCP/IP协议模型从更实用的角度出发,形成了高效的四层体系结构,即物理链路层、IP层、传输层和应用层。
能重传,不丢包,如果丢失会记录,重新发送,但是不会重复
可靠
有序,顺序不会错,比如给你发送了123,2丢失了,重传,那么你接收到的可能是132,但是你看到的一定是123
面向链接,如果连接不上去的话,数据不会发送
三次握手,可以保证数据的安全性,能够保证交互
相当于打电话,打不通的话,数据是传达不过去的
51.5.1.2 Socket
利用套接字(Socket)开发网络应用程序早已被广泛的采用,以至于成为事实 上的标准。
网络上具有唯一标识的IP地址和端口号组合在一起才能构成唯一能识别的标 识符套接字。
通信的两端都要有Socket,是两台机器间通信的端点。
网络通信其实就是Socket间的通信。
Socket允许程序把网络连接当成一个流,数据在两个Socket间通过IO传输。
一般主动发起通信的应用程序属客户端,等待通信请求的为服务端。
Socket分类:
流套接字(stream socket):使用TCP提供可依赖的字节流服务数据报套接字(datagram socket):使用UDP提供“尽力而为”的数据报服务
51.5.1.3 常用方法
Socket类的常用构造器:
public Socket(InetAddress address,int port)创建一个流套接字并将其连接到指定IP 地址的指定端口号。
public Socket(String host,int port)创建一个流套接字并将其连接到指定主机上的指定端口号。
Socket类的常用方法:
public InputStream getInputStream()返回此套接字的输入流。可以用于接收网络消息
public OutputStream getOutputStream()返回此套接字的输出流。可以用于发送网络消息
public InetAddress getInetAddress()此套接字连接到的远程 IP 地址;如果套接字是未连接的,则返回 null。
public InetAddress getLocalAddress()获取套接字绑定的本地地址。 即本端的IP地址
public int getPort()此套接字连接到的远程端口号;如果尚未连接套接字,则返回 0。
public int getLocalPort()返回此套接字绑定到的本地端口。 如果尚未绑定套接字,则返回 -1。即本端的 端口号。
public void close()关闭此套接字。套接字被关闭后,便不可在以后的网络连接中使用(即无法重新连接 或重新绑定)。需要创建新的套接字对象。 关闭此套接字也将会关闭该套接字的 InputStream 和 OutputStream。
public void shutdownInput()如果在套接字上调用 shutdownInput() 后从套接字输入流读取内容,则流将 返回EOF(文件结束符)。 即不能在从此套接字的输入流中接收任何数据。
public void shutdownOutput()禁用此套接字的输出流。对于 TCP 套接字,任何以前写入的数据都将被发 送,并且后跟 TCP 的正常连接终止序列。 如果在套接字上调用 shutdownOutput() 后写入套接字输出流, 则该流将抛出 IOException。 即不能通过此套接字的输出流发送任何数据。
51.5.1.4 服务端
服务器程序的工作过程包含以下四个基本的步骤:
调用 ServerSocket(int port) :创建一个服务器端套接字,并绑定到指定端口 上。用于监听客户端的请求。
调用 accept():监听连接请求,如果客户端请求连接,则接受连接,返回通信 套接字对象。
调用 该Socket类对象的 getOutputStream() 和 getInputStream ():获取输出流和输入流,开始网络数据的发送和接收。
关闭ServerSocket和Socket对象:客户端访问结束,关闭通信套接字。
ServerSocket 对象负责等待客户端请求建立套接字连接,类似邮局某个窗口 中的业务员。也就是说,服务器必须事先建立一个等待客户请求建立套接字 连接的ServerSocket对象。
所谓“接收”客户的套接字请求,就是accept()方法会返回一个 Socket 对象
// 阻塞式接收和发送
public static void test2()throws Exception {
// 1 创建对象并开启端口
ServerSocket ss = new ServerSocket(10000);
System.out.println("服务器已经启动,等待客户端链接......");
// 等待客户端链接,执行到这里线程会停下来,直到客户端链接成功
// 客户端链接之后,会得到该Socket对象
// 可以把这个Socket对象 理解为客户端,包含客户端给你传递的信息等
Socket skt = ss.accept();
System.out.println("客户端已链接...");
// 向客户端返回数据
// 拿到输出流
OutputStream os = skt.getOutputStream();
// 转换为字符输出,并指定编码为utf-8
OutputStreamWriter osw = new OutputStreamWriter(os,"utf-8");
// 封装缓冲流
PrintWriter bw = new PrintWriter(osw);
// 接收数据 用输入流
InputStream is = skt.getInputStream();
InputStreamReader isr = new InputStreamReader(is,"utf-8");
BufferedReader br = new BufferedReader(isr);
// 读数据
String line = null;
// 阻塞式接收
while ((line = br.readLine()) != null) {
System.out.println("客户端发来信息 : "+line);
bw.println("您给服务端发送的数据 : "+line+" ,我已接收到.");
bw.flush();
}
// 先开启的后关闭
bw.close();
skt.close();
ss.close();
System.out.println("链接已关闭");
}
51.5.1.5 客户端
户端Socket的工作过程包含以下四个基本的步骤:
创建 Socket:根据指定服务端的 IP 地址或端口号构造 Socket 类对象。若服务器端响应,则建立客户端到服务器的通信线路。若连接失败,会出现异常。
打开连接到Socket 的输入/出流: 使用 getInputStream()方法获得输入流,使用 getOutputStream()方法获得输出流,进行数据传输
按照一定的协议对Socket 进行读/写操作:通过输入流读取服务器放入线路的信息(但不能读取自己放入线路的信息),通过输出流将信息写入线程。
关闭 Socket:断开客户端到服务器的连接,释放线路
客户端程序可以使用Socket类创建对象,创建的同时会自动向服务器方发起连 接。Socket的构造器是:
Socket(String host,int port)throws UnknownHostException,IOException:向服务器(域名是host。端口号为port)发起TCP连接,若成功,则创建Socket对象,否则抛出异常。
Socket(InetAddress address,int port)throws IOException:根据InetAddress对象所表示的 IP地址以及端口号port发起连接。
客户端建立socketAtClient对象的过程就是向服务器发出套接字连接请求
// 阻塞式接收和发送
public static void test2() throws Exception{
// 创建对象,指定IP和端口
Socket skt = new Socket("127.0.0.1", 10000);
// 拿到输出流
OutputStream os = skt.getOutputStream();
// 转换为字符输出,并指定编码为utf-8
OutputStreamWriter osw = new OutputStreamWriter(os,"utf-8");
// 封装缓冲流
PrintWriter bw = new PrintWriter(osw);
// 接收数据 用输入流
InputStream is = skt.getInputStream();
InputStreamReader isr = new InputStreamReader(is,"utf-8");
BufferedReader br = new BufferedReader(isr);
// 控制台输入
Scanner sc = new Scanner(System.in);
// 读取一行数据
String msg = sc.nextLine();
while (true) {
// 发送服务端
bw.println(msg);
bw.flush();
// 接收返回数据
System.out.println(br.readLine());
// 控制台输入
msg = sc.nextLine();
}
}
51.5.2 UDP/IP
51.5.2.1 概述
速度快
不保证可靠
可能丢包
无连接
相当于发短信,不管你能不能收到,反正发送给你了
类 DatagramSocket 和 DatagramPacket 实现了基于 UDP 协议网络程序。
UDP数据报通过数据报套接字 DatagramSocket 发送和接收,系统不保证UDP数据报一定能够安全送到目的地,也不能确定什么时候可以抵达。
DatagramPacket 对象封装了UDP数据报,在数据报中包含了发送端的IP 地址和端口号以及接收端的IP地址和端口号。
UDP协议中每个数据报都给出了完整的地址信息,因此无须建立发送方和 接收方的连接。如同发快递包裹一样。
51.5.2.2 常用方法
DatagramSocket 类的常用方法
public DatagramSocket(int port)创建数据报套接字并将其绑定到本地主机上的指定端口。套接字将被绑定到通配符地址,IP 地址由内核来选择。
public DatagramSocket(int port,InetAddress laddr)创建数据报套接字,将其绑定到指定的本地地址。 本地端口必须在 0 到 65535 之间(包括两者)。如果 IP 地址为 0.0.0.0,套接字将被绑定到通配符地 址,IP 地址由内核选择。
public void close()关闭此数据报套接字。
public void send(DatagramPacket p)从此套接字发送数据报包。DatagramPacket 包含的信息指示:将要发送的数据、其长度、远程主机的 IP 地址和远程主机的端口号。
public void receive(DatagramPacket p)从此套接字接收数据报包。当此方法返回时,DatagramPacket 的缓冲区填充了接收的数据。数据报包也包含发送方的 IP 地址和发送方机器上的端口号。 此方法 在接收到数据报前一直阻塞。数据报包对象的 length 字段包含所接收信息的长度。如果信息比包的 长度长,该信息将被截短。
public InetAddress getLocalAddress()获取套接字绑定的本地地址。
public int getLocalPort()返回此套接字绑定的本地主机上的端口号。
public InetAddress getInetAddress()返回此套接字连接的地址。如果套接字未连接,则返回null。
public int getPort()返回此套接字的端口。如果套接字未连接,则返回 -1。
DatagramPacket类的常用方法
public DatagramPacket(byte[] buf,int length)构造 DatagramPacket,用来接收长
度为length 的数据包。 length 参数必须小于等于 buf.length。
public DatagramPacket(byte[] buf,int length,InetAddress address,int port)构造数 据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。length 参数必须小于等于buf.length。
public InetAddress getAddress()返回某台机器的 IP 地址,此数据报将要发往该
机器或者是从该机器接收到的。
public int getPort()返回某台远程主机的端口号,此数据报将要发往该主机或 者是从该主机接收到的。
public byte[] getData()返回数据缓冲区。接收到的或将要发送的数据从缓冲区
中的偏移量 offset 处开始,持续length 长度。
public int getLength()返回将要发送或接收到的数据的长度。
51.5.2.3 服务端
流 程:
1.DatagramSocket与DatagramPacket
2.建立发送端,接收端
3.建立数据包
4.调用Socket的发送、接收方法
5.关闭Socket
发送端与接收端是两个独立的运行程序
51.5.2.4 客户端
public static void main(String[] args) throws Exception {
// test01();
test02();
}
// 一次发送
public static void test01() throws Exception {
// 发送的数据
String str = "你好";
// 字节数字输出流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
// 写出数据到baos
dos.writeUTF(str);
// 把baos转换为字节数组
byte[] buf = baos.toByteArray();
// 发送数据,大小限制是64K,绑定地址
DatagramPacket dp = new DatagramPacket(buf, buf.length,
new InetSocketAddress("127.0.0.1", 10000));
// 发送,需要通过电脑中一个端口发送出去
DatagramSocket ds = new DatagramSocket(9999);
// DatagramPacket 数据包
// DatagramSocket 通信
ds.send(dp);
ds.close();
}
// 持续发送
public static void test02() throws Exception {
// 发送的数据
String str = null;
Scanner sc = new Scanner(System.in);
System.out.println("请输入需要传递的信息 : ");
// 获取输入的数据
str = sc.nextLine();
while (str != null ) {
// 字节数字输出流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
// 写出数据到baos
dos.writeUTF(str);
// 把baos转换为字节数组
byte[] buf = baos.toByteArray();
// 发送数据,大小限制是64K,绑定地址
DatagramPacket dp = new DatagramPacket(buf, buf.length,
new InetSocketAddress("127.0.0.1", 10000));
// 发送,需要通过电脑中一个端口发送出去
DatagramSocket ds = new DatagramSocket(9999);
// DatagramPacket 数据包
// DatagramSocket 通信
ds.send(dp);
ds.close();
// 如果是exit 则退出客户端
if (str.equalsIgnoreCase("exit")) {
break;
}
System.out.println("请输入需要传递的信息 : ");
// 获取输入的数据
str = sc.nextLine();
}
}
Java 源代码的字符串中的反斜线被解释为 Unicode 转义或其他字符转义。因此必须在字符串字面值中使用两个反斜线,表示正则表达式受到保护,不被 Java 字节码编译器解释。例如,当解释为正则表达式时,字符串字面值 “\b” 与单个退格字符匹配,而 “\b” 与单词边界匹配。字符串字面值 “(hello)” 是非法的,将导致编译时错误;要与字符串 (hello) 匹配,必须使用字符串字面值 “\(hello\)”。
(),[],{}的区别
1>. 小括号():匹配小括号内的字符串,可以是一个,也可以是多个,常跟“|”(或)符号搭配使用,是多选结构的
示例1:string name = “way2014”; regex:(way|zgw) result:结果是可以匹配出way的,因为是多选结构,小括号是匹配字符串的
示例2:string text = “123456789”; regex:(0-9) result:结果是什么都匹配不到的,它只匹配字符串"0-9"而不是匹配数字, [0-9]这个字符组才是匹配0-9的数字
2>.中括号[]:匹配字符组内的字符,比如咱们常用的[0-9a-zA-Z.?!]等,在[]内的字符都是字符,不是元字符,比如“0-9”、“a-z”这中间的“-”就是连接符号,表示范围的元字符,如果写成[-!?(]这样的话,就是普通字符
示例1: string text = “1234567890”; regex:[0-9] result:结果是可以匹配出字符串text内的任意数字了,像上边的【或符号“|”在字符组内就是一个普通字符】
示例2:string text = “a|e|s|v”; regex:[a|e|s] result:结果就是匹配字符a、e、s三个字符,这个跟(a|e|s)有区别的,区别就是(a|e|s)匹配的是a、e、s三个字符的随意一个,三个 中的任意一个,这里|是元字符
3>.大括号{}:匹配次数,匹配在它之前表达式匹配出来的元素出现的次数,{n}出现n次、{n,}匹配最少出现n次、{n,m}匹配最少出现n次,最多出现m次
52.3 练习
1 匹配整数和小数
^\d 以数字打头
.匹配任意字符,需要转义 .
? 出现0次或1次
^\d+ (.\d+)?
2 匹配电话
^\d{11}$
^1([358][0-9]|4[579]|66|7[0135678]|9[89])[0-9]{8}$
52.4 Java中支持正则表达式的类
PatternSyntaxException : 正则表达式异常类
Pattern : 正则表达式类,只能做简单操作
Matcher : 支持强大的正则表达式匹配操作
52.4.1 Pattern
52.4.1.1 概述
用于创建一个正则表达式,也可以说创建一个匹配模式,它的构造方法是私有的,不可以直接创建
可以通过Pattern.complie(String regex)创建一个正则表达式
只能做一些简单的匹配操作
使用:
Pattern.split(CharSequence input) ,成员方法, 用于分隔字符串
Pattern.matches (String regex,CharSequence input),静态方法,用于快速匹配字符串,该方法适合用于只匹配一次,且匹配全部字符串。
boolean matches(String regex) 验证
String[] split(String regex) 分割
String replaceAll(String regex,String replacement) 替换
52.4.1.2 使用
52.4.2 Matcher
52.4.2.1 概述
构造方法也是私有的,不能随意创建,只能通过Pattern.matcher(CharSequence input)方法得到该类的实例 Matcher m = p.matcher(“aaaaab”);
支持便捷强大的正则匹配操作,包括分组、多次匹配支持
三大方法
Matcher.matches():对整个字符串进行匹配,只有整个字符串都匹配了才返回true
Matcher.lookingAt():对前面的字符串进行匹配,只有匹配到的字符串在最前面才返回true
Matcher.find():对字符串进行匹配,匹配到的字符串可以在任何位置
.匹配任何字符,* 匹配任意次数
matches : 全词匹配
find : 在任意位置均可 .*xxxx .*
lookingAt : 从前向后匹配 xxxx.*
// 注意 ! 一个matcher对象,和相应的 find/matches/lookingAt 是配对的,
// 不要一起使用同一个matcher对象
// 如果一定要连用,必须重新打开matcher就可以
// 调用相同方法是可以连用的,比如调用多次find方法
52.4.2.2 字符串匹配使用
52.4.2.3 字符串提取使用
52.4.2.4 叠词匹配去除重复
// 还原成 我要学编程
String string = "我我我,,,我我,我,我要要要要,,,,,,要要,要学,学学学,学,编编程程程,程程程,,,,,程";
// 1 把逗号 先去掉
string = string.replaceAll(",", "");
// 我我我我我我我要要要要要要要学学学学学编程程程程程程程
// System.out.println(string);
/**
* 使用 find和group 获取数据
*/
// \\1 获取前面组中的数据
// (\\d)\\1 : 表示连续出现的数字字符 , 比如 11,22,333,44444
// (\\d)(a)\\1 : 匹配第一个是数字,第二个是a,第三个和第一个是相同的数字 ,比如 1a1 , 2a2, 4a4
// (\\d)(a)\\2 : 匹配第一个是数字,第二个是a,第三个和第二个是相同的 , 比如 1aa , 2aa , 8aa
// 对每个字进行分组
String regex = "(.)(\\1+)";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(string);
// System.out.println(matcher.find());
while (matcher.find()) {
// 获取每个字的叠词
// 而 group (1) 就是只要 . ,也就是叠词中只要1个
// 所有 我我我我我我我
System.out.println(matcher.group(0));
// 第一组 我
System.out.println(matcher.group(1));
// 第二组 我我我我我我 比所有的少一个
System.out.println(matcher.group(2));
}
/**
* 还原成 我要学编程
*/
// $1 就等于 group(1)
// $1 等于去重.因为$1就是group(1) 而我们这个正则表达式中 第一组 就是叠词中的一个
string = string.replaceAll(regex, "$1");
// $2 就是重复就删一个,因为第二组是叠词中删除一个相同的叠词
// string = string.replaceAll(regex, "$2");
System.out.println(string);
Day25 1.8新特性
53. Lambda
53.1 概述
Lambda 是一个匿名函数,我们可以把Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。
53.3 和匿名内部类对比
53.4 特点
允许把方法作为一个参数,进行传递
使用Lambda表达式可以使代码变的更加简洁紧凑
53.5 语法结构
53.5.1 结构图
Lambda 表达式:在Java 8 语言中引入的一种新的语法元素和操作符。这个操作符为“->” ,该操作符被称为Lambda 操作符或箭头操作符。它将Lambda 分为两个部分:
左侧:指定了Lambda 表达式需要的参数列表
右侧:指定了Lambda 体,是抽象方法的实现逻辑,也即Lambda 表达式要执行的功能。
53.5.2 语法特点
可选类型声明 : 不需要声明数据类型,编译器可以识别参数值
可选的参数()括号 : 一个参数无需定义括号,但是多个参数必须要定义括号
可选的大括号 : 如果主体内包含一个语句,就不需要大括号
可选的返回关键字 : 如果主体中只有一个表达式,并且是返回值语句的话,大括号中需要指明表达式返回的一个数据
如果只有一条语句,并且是返回值语句的话,return和{} 都可以不写
如果写上{} 那么 return和; 必须要写
如果有多条语句,必须写{} 和return;
53.5.3 语法案例
1 不需要参数,返回值为5
() -> 5
2 接收一个参数(数字类型),返回该数的2倍值
x -> 2*x
3 接收2个参数(数字),返回差值
(x,y) -> x-y
4 接收2个int整数,返回他们的和
(int x , int y) -> x+y
5 接收一个String类型,并把该字符串打印到控制台,不需要返回值(void)
(String str) -> System.out.println(str)
53.6 练习
53.6.1 集合遍历
53.6.1.1 1.7写法1
53.6.1.2 1.7写法2
本质 就是传递一个对象,然后forEach方法自动遍历集合,并且把集合中每个元素依次调用该对象中的accept方法,并把值传递进去
53.6.1.3 1.8写法
简化了匿名内部类,和匿名内部类是等价的
x -> System.out.println(x)
就等于
new Consumer() {
@Override
public void accept(String t) {
System.out.println(t);
}
}
而这个表达式 就是在描述 该匿名内部类中这个方法的,返回值,参数,方法体
只不过是这个方法的一种简单写法
不过该接口 只能有一个抽象方法
53.6.2 集合排序
53.6.2.1 1.7写法
53.6.2.2 1.8写法
54.2 特点
函数式接口是仅指定一个抽象方法的接口
可以保护多个静态方法和默认方法
专用注解 @FunctionalInterface ,检查是否是一个函数式接口,也可以不写该注解
如果有两个或以上个抽象方法,就不能当做函数式接口去使用,同时也不能添加 @FunctionalInterface 这个注解
54.3 回调函数
简单来说,就是方法需要传入一个参数也是方法,并且在该方法中对这个传入的方法进行调用
54.4 自定义函数式接口
54.4.1 无参情况
54.4.2 有参情况
54.5 JDK自带的函数式接口
54.5.1 Supplier
54.5.2 Consumer
54.5.3 Function
54.5.4 Predicate
当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!
方法引用可以看做是Lambda表达式深层次的表达。换句话说,方法引用就是Lambda表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是Lambda表达式的一个语法糖。
要求:实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致!
格式:使用操作符“::” 将类(或对象) 与方法名分隔开来。
如下三种主要使用情况:
对象::实例方法名
类::静态方法名
类::实例方法名
55.3.1 对象引用::实例方法名
55.3.2 类名::静态方法名
55.3.3 类名::实例方法名
55.4 构造器引用
格式:ClassName::new
与函数式接口相结合,自动与函数式接口中方法兼容。
可以把构造器引用赋值给定义的方法,要求构造器参数列表要与接口中抽象方法的参数列表一致!且方法的返回值即为构造器对应类的对象。
55.5 数组引用
就是用来处理集合、数组的API,集合讲的是数据,而流是计算
注意:
①Stream 自己不会存储元素。
②Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
③Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
56.2 运行机制
Stream分为源source , 中间操作,终止操作
1-创建Stream
一个数据源(如:集合、数组),获取一个流
2-中间操作
一个中间操作链,对数据源的数据进行处理
一个流可以有0~N个中间操作,每一个中间操作都会返回一个新的流,方便下一个操作使用
一个流只能有一个终止操作
中间操作也称为转换算子-transformation
3-终止操作(终端操作)
一旦执行终止操作,就执行中间操作链,并产生结果。之后,不会再被使用
Stream只有遇到终止操作,它对策数据源才会开始执行遍历等操作
终止操作也称为动作算子
因为动作算子的返回值不再是Stream,所以这个计算就终止
只有碰到动作算子的时候,才会真正的计算
56.3 创建Stream的五种方式
56.4 常用的中间操作-转换算子
56.4.1 概述
一个中间操作链,对数据源的数据进行处理
一个流可以有0~N个中间操作,每一个中间操作都会返回一个新的流,方便下一个操作使用
一个流只能有一个终止操作
中间操作也称为转换算子-transformation
56.4.2 常用的转换算子
比如进行判断,集合元素是否大于4 ,返回值为boolean类型
或者对集合元素进行更改,比如每个元素都自身+1
Stream 使用之后,必须重新生成新的Stream,不能使用原来的stream
可以链式调用,是因为转换 算子的返回值都是一个新的Stream,而这个新的Stream还没有操作过 ,所以可以链式调用
但是原来的stream就不能再使用了,否则报错
56.4.4 使用方式
Filter
Skip
Map
Distinct
Limit
flatMap
56.5 常用的终止操作-动作算子
56.5.1 概述
一旦执行终止操作,就执行中间操作链,并产生结果。之后,不会再被使用
Stream只有遇到终止操作,它对策数据源才会开始执行遍历等操作
终止操作也称为动作算子
因为动作算子的返回值不再是Stream,所以这个计算就终止
只有碰到动作算子的时候,才会真正的计算
56.5.2 常用动作算子
56.5.3 使用方式
forEach
Count
Collect
Day26 MySQL
57. 软件安装
参考教程
1 检查是否安装(安装就卸载,未安装就安装,保证版本一致)
2 Navicat安装
3 测试
1.4数据库分类
画红框的是我们重点关注的。
Oracle
这是一种关系型数据库。它的特点是闭源收费,但是功能强大稳定,而且有一支专业的技术支撑团队。使用这种数据库的一般是大型企业、银行业、金融业。
MySQL
它也是关系型数据库。它的特点是开源免费,功能还是不错的,也比较稳定。通常使用这种数据库的,是中小企业等。因为它是开源的,所以我们有些企业可以对它做定制化、二次开发,以支持自己特殊的业务。比如阿里。说它是目前应用最广泛的DBMS,是MySQL的订制二次开发,也是比较好用。
Memcached、Redis
这2者是非关系型数据库(Not Only SQL)。它们通常用K-V键值对的形式存储数据,使用场景为缓存(可做为关系型数据库的补充)。它俩都是基于内存存储的,它们的数据都是在内存中,所以访问速度要快。Redis还提供了定期向磁盘中进行序列化的机制,它的IO性能也是非常高的。
MongoDB
这个也是NOSQL数据库。它使用文档形式存储数据。它使用文档形式存储数据。使用场景,通常是大数据量高并发访问。它的性能也非常高。
MySQL安装
参考 相关软件中的 安装教程
开发工具
我们主要用的MySQL开发工具有 Navicat。
学习阶段,方便我们进行操作,图形化显示,功能比较抢到,很适合入门,后续我们就需要脱离Navicat,通过SQL在程序中进行操作
参考 相关软件中的 安装教程
3.MySQL介绍
MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,目前属于 Oracle旗下产品。MySQL 是最流行的关系型数据库关系系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS(Relational Database Management System,关系数据库管理系统) 应用软件之一。
MySQL是一种关系数据库管理系统,关系数据库将数据保存在不同的表中,而不是将所有数据放在一个大仓库内,这样就增加了速度并提高了灵活性。
MySQL所使用的 SQL 语言是用于访问数据库的最常用标准化语言。MySQL 软件采用了双授权政策,分为社区版和商业版,由于其体积小、速度快、总体拥有成本低,尤其是开放源码这一特点,一般中小型网站的开发都选择 MySQL 作为网站数据库。
数据库对象
表,视图,函数,存储过程,索引等。
表
就是存储数据的,里面有一条条的记录,记录是由字段组成的。每个字段都有自己的字段名、字段值、字段类型(数据类型)。字段是存储数据的基本单元。类似Java中的对象,用于存储数据。
表(table)是一种结构化的文件,可以用来存储特定类型的数据,如:学生信息,课程信息,都可以放到表中。另外表都有特定的名称,而且不能重复。表中具有几个概念:列、行、主键。 列叫做字段(Column),行叫做表中的记录,每一个字段都有:字段名称/字段数据类型/字段约束/字段长度
学生信息表
学号(主键) 姓名 性别 年龄
00001 张三 男 20
00002 李四 女 20
视图
我们可以理解为一张虚表(它在物理磁盘上并不真实存在)。视图是基于select语句查询生成的结果集。当一条select语句比较复杂而且调用的频率也比较高时,我们不想再写一遍语句,这时候可以把这条select语句创建为一个视图。每次使用视图的时候,就可以把它当成一张表来用。
函数
通常是一个功能的实现,我们大部分使用的是系统函数(MYSQL提供的),我们也可以自定义函数。它的调用套路通过使用select 函数名(参数1,参数2…);来调用。它只有一个返回值。
存储过程
也是一个功能的实现。我们所说的数据库编程,SQL语句编程,就主要是指自定义一个存储过程。调用存储过程是使用exec 存储过程名 参数1等来调用。它是可以返回多个值,也可以没有返回值。
索引
我们之前接触过,它就像一本书的目录一样,是为了加快我们查询速度而建立的。
表结构
表名称:dept
描述:部门信息表
英文字段名称 中文描述 类型
DEPTNO 部门编号 INT(2)
DNAME 部门名称 VARCHAR(14)
LOC 位置 VARCHAR(13)
表名称:emp
描述:员工信息表
英文字段名称 中文描述 类型
EMPNO 员工编号 INT (4)
ENAME 员工姓名 VARCHAR(10)
JOB 工作岗位 VARCHAR(9)
MGR 上级领导 INT (4)
HIREDATE 入职日期 DATE
SAL 薪水 DOUBLE(7,2)
COMM 津贴 DOUBLE (7,2)
DEPTNO 部门编号 INT(2)
注:DEPTNO字段是外键,DEPTNO的值来源于dept表的主键,起到了约束的作用
表名称:salgrade
描述:薪水等级信息表
英文字段名称 中文描述 类型
GRADE 等级 INT
LOSAL 最低薪水 INT
HISAL 最高薪水 INT
数据类型
刚才创建创建表的时候,我们用到了数据类型,类型目的就是限制数据的类型,那么接下来我们看一下常用的几种类型
整型,浮点型,字符型,日期时间型等。
数值型
时间类型
字符串型
数据库/SQL的数据类型也是挺多的,但是我们掌握几个常用的就是,比如整型int,浮点型decimal(18,2)(共18位,16位整数部分与2位小数部分),字符型就是字符串类型varchar(100)(可以存储100个字符),日期时间型datetime(‘2018-05-23 15:00:00’)。当然还有其他的具体类型,但是我们在基础阶段就先知道这些就可以了。
小知识
char与varchar的区别。char(100)类型的字段一旦定义,不管里面是否真的有值,它就固定会占用100个字符对应的存储空间,这种类型对空间的利用率其实并不高。varchar(100)类型的字段定义后,它所占用的空间就是里面存储内容占用的空间,但是最大不会超过100个字符,这种类型对空间的利用率是很高的。
比如 手机号码(目前都是11位,前缀不考虑),就可以使用char定长
比如 姓名或者家庭地址,就可以使用varchar,
毕竟家庭地址和名字的长度共多少位,不好确定
(有的可能说,名字不是最多就3位吗?那你把迪丽热巴放到哪里 - - ! 你把 奥斯夫托洛夫斯基放到哪里 - - !)
当然我们还是需要看具体需求而定
演示示例:char和varchar
4.SQL的分类
数据查询语言(DQL-Data Query Language)
代表关键字:select
数据操纵语言(DML-Data Manipulation Language)
代表关键字:insert,delete,update
数据定义语言(DDL-Data Definition Language)
代表关键字:create ,drop,alter,
事务控制语言(TCL-Transactional Control Language)
代表关键字:commit ,rollback;
数据控制语言(DCL-Data Control Language)
代表关键字:grant,revoke.
5.MySQL基本使用
5.1 TCL–用户创建和授权
MySQL中是以数据库做区分,但是用户可以操作数据库,权限最大的用户是root,在安装MySQL的时候我们已经设置了root的密码
但是真正开发中,不会让我们使用root用户,权限太大,会为我们单独创建用户,这个用户只能操作某个数据库
1、创建用户:CREATE USER ‘username’@‘host’ IDENTIFIED BY ‘password’;
需要切换到mysql数据库下,操作user表
username:用户名;host:指定在哪个主机上可以登录,本机可用localhost,%通配所有远程主机;password:用户登录密码;
2、授权:GRANT ALL PRIVILEGES ON . TO ‘username’@’%’ IDENTIFIED BY ‘password’ ;
刷新权限:FLUSH PRIVILEGES;
格式:grant 权限 on 数据库名.表名 to 用户@登录主机 identified by “用户密码”;.代表所有数据库和所有表;
@ 后面是访问MySQL的客户端IP地址(或是 主机名) % 代表任意的客户端,如果填写 localhost 为本地访问(那此用户就不能远程访问该mysql数据库了)。
如 GRANT ALL PRIVILEGES ON 08. TO ‘tianliang’@’%’ IDENTIFIED BY ‘tianliang’ ;
把 _08_数据库的操作权限,授权给tianliang用户
All privileges 是所有权限
GRANT select,insert,update,drop,create,delete ON 08. TO ‘c’@’%’ IDENTIFIED BY ‘c’ ;
上面是同时设置查询,添加,更新等操作
当然 也可以只写select , 是只能查询
Select 查询
Insert 插入数据
Update 更新数据
Delete 删除数据
Drop 删除表
Create 创建表
撤销授权
revoke 跟 grant 的语法差不多,只需要把关键字 “to” 换成 “from” 即可,并且IDENTIFIED BY ‘password’ ,不管是授权还是撤销,都可以不加:
grant all on . to tledu@localhost;
revoke all on . from tledu@localhost;
删除用户,会同步把user和db表都删除对应的数据
drop user 用户名@’%’;
create USER ‘tledu’@‘localhost’ identified by ‘tledu’;
grant All privileges on . to ‘tledu’@‘localhost’ identified BY ‘tledu’;
FLUSH PRIVILEGES;
revoke All privileges on 16.* from ‘tledu’@‘localhost’ identified BY ‘tledu’;
FLUSH PRIVILEGES;
drop user ‘tledu’@‘localhost’;
5.2 DDL–数据库创建
上面我们在介绍MySQL的时候提了,MySQL中是以数据库为区分是以表来存储数据,那么接下来我们就来学习如何创建数据库,然后再去学习如何创建表
命令行中的注释
– 单行注释
/*
多行注释
*/
命令行基本使用
查看当前链接的MySQL服务器的版本
select version();
显示当前链接的MySQL服务器中的所有的数据库
show databases;
输出指定内容, 字符串需要用单引号括起来,不区分字符还是字符串,都用单引号就行,数值可以直接写
select ‘xxxx’;
设置字段名
select ‘xxxx’ as 字段名;
as也可以省略
select ‘xxxx’ 字段名;
切换数据库
use test;
查看当前库下面的所有表,使用这个命令的时候,通常会跟着一条use xxx; 切换数据库,如果当前命令行,没有在库里面打开,直接使用show tables 会报错
show tables;
数据库创建和删除
在当前数据库服务器上创建一个新库
create database day01;
在当前数据库服务器上删除一个库
drop database day01;
select version();
show databases;
select ‘xxxxx’ as 列名;
create database day_01;
use day_01;
show tables;
drop database day_01;
5.3 DDL–表创建和删除
在数据库day01下创建表student_info
use day01;
create table student_info (
id int,
name
varchar(20),
salary decimal(18,2)
)ENGINE = innodb default charset = utf8;
show create table t_student;
create table if not exists t_student(
id int,
name
varchar(20)
);
存储引擎 :
MyISAM
InnoDB
– 在数据库day01下删除表student_info
use day01;
drop table student_info;
6.DML操作
DML : Data Manipulate Language(数据操作语言) ,主要用于向数据库插入,修改,删除数据使用
涉及的关键字有 : insert delete update
上面我们提到,数据库主要是用于数据存储和操作,而刚才我们讲的那些,只是结构,和数据并没有关系,下面我们来使用一下DML
使用方式如下
Insert (用于向表中添加数据):
格式 :
insert into 表名 (列名1,列名2) values (值1,值2);
示例 :
insert into teacher (name, id, salary) values (‘老刘’,1,56.23);
insert into teacher (name, salary) values (‘老王’,56.23);
insert into student values(1,‘张三’); 这种方式不建议使用,需要添加表中所有的列,并且需要一一对应
Delete(用于删除表中的数据):
格式 :
delete from 表名 where 列名 = 值;
示例 :
delete from teacher where name=‘老王’;可以把符合where条件的记录全部删除。一定要加where条件,否则会删除全表的数据。
delete from teacher where id is null也可以起到上面的作用,判断为null 不能使用 = ,is null 为空 is not null 不为空。
Update(用于更新表中的数据):
格式 :
update 表名 set 列名1=值 , 列名2=值 where 列名 = 值;
示例 :
update teacher set salary=9999.45, name=‘dave’ where id = 1;
会将符合where条件的所有记录对应的字段都更新为新值
Select(用于查询表中的数据,属于DQL):
格式 :
select 列限定 from 表限定 where 行限定
示例 :
select * from teacher where id= 1;
查询teacher表中id为1 所有信息
select * from teacher;
查询teacher表中的所有信息
select name from teacher where id= 1;
查询teacher表中 id为1 的name值
注意:无论是delete还是update,只要你确定不是全表操作,就一定要使用where条件!!!
Day27 MySQL
昨天我们讲了表的创建和删除,可以规定表名是什么,可以有多少列,数据类型分别是什么,那么比如创建错了,想更改,就要涉及到一个关键字 alter
比如更改表名
alter table 表名 rename 新表名;
如 alter table teacher rename t_teacher;
更改字段名
alter table 表名 change 列名 新列名 数据类型;
更改表的列名 和 数据类型 当然数据类型可以不改,但是必须得写,
如 alter table t_teacher change name teacher_name varchar(20);
添加字段
alter table 表名add 列名类型;
如 alter table t_teacher add birthday datetime; 默认添加到尾部
alter table t_teacher add birthday datetime after teacher_name; 把列添加到指定列的后面
alter table t_teacher add sex2 char(2) first; 添加到第一列,需要把表关掉,刷新一下,再打开才能显示出来
删除字段
alter table 表名 drop 列名;
如 alter table t_teacher drop birthday;
更改字段类型(尽量不要更改)
alter table 表名 modify 列名 新数据类型;
如 alter table t_teacher modify sex2 varchar(20);
alter table 表名 modify 列名 数据类型 comment ‘该列的注释说明’; 更改类型的同时,还能添加注释说明
查看建表语句
show create table 表名;
1.3DDL增强
上面讲的DDL只是一系列基础操作,它让我们有库有表可以插入数据。但是对于插入的数据是否是有效数据,并不能保证。比如我们可以插入一条所有字段都是NULL的记录:
命令insert into student(id,name,score) values (null,null,null);
这种记录白白地占用了我们的存储空间,但是实际上并没有用处,为了防止表中插入这种数据,MYSQL提供了一系列的完整性验证机制。
约束分类
实体完整性(主键)
我们的java类,对应的就是一张表,成员变量对应一个字段,一个类对象对应一条数据,那么对象都有一定的唯一性
比如判断对象是否相等,我们通常使用equals()方法和hashCode()方法,那么怎么在数据库中表示数据的唯一性呢?主键
主键通常用于唯一确定表中的一条记录,设置为主键的字段是不能为NULL并且不能重复的。
主键可以设置在一个字段上,也可以设置在多个字段上。(但大多数场景都是设置在一个字段上,这个字段通常是业务主键或者流水号)
主键设置可以划分为两种
第一种 : 创建表语句时,添加主键约束
第二种 : 创建表完成之后,通过alter添加主键约束
下面是使用方式
第一种 : 创建表语句时,添加主键约束
create table person(
id int ,
name varchar(100),
income decimal(18,2),
primary key (id,name)
);
上面代码设置了两个主键
create table person1(
id int ,
name varchar(100),
income decimal(18,2),
primary key (id)
);
上面代码设置了一个主键
如果只有一列主键,也可以直接写在字段后面
create table person2(
id int primary key,
name varchar(100) ,
income decimal(18,2)
);
第二种 : 创建表完成之后,通过alter添加主键约束
语法 : alter table 表名 add primary key(列名,列名…);
create table person3(
id int ,
name varchar(100),
income decimal(18,2)
);
比如要对person3表添加id列主键
alter table person3 add primary key(id);
主键自增
上面我们已经对表添加了主键,主键值不能为空且不能重复,那么问题来了…
如果主键的值让客户输入的话,很容易就重复了,比如888,666等数字大家都喜欢使用,导致一直输入不正确,非常不方便
所以又有了自增的概念,所谓自增,望文知意,就是自动增加,不用我们输入值
但是自增的列,必须为主键列,关键字 auto_increment
设置自增的两种方式 :
第一种 : 建表时,添加自增
第二种 : 创建表之后,添加自增
下面是使用方式
第一种 : 建表时,添加自增
create table person4(
id int auto_increment ,
name varchar(200),
primary key(id)
);
测试语句 :
insert into person4(name)values(‘测试’);
并未输入id的值,但是可以自动填充
第二种 : 创建表之后,添加自增
语法 : alter table 表名modify 主键列名 类型 auto_increment;
create table person5(
id int ,
name varchar(200),
primary key(id)
);
alter table person5 modify id int auto_increment;
测试语句 :
insert into person5 (name)values(‘测试’);
并未输入id的值,但是可以自动填充
设置自增的起始值
语法 : alter table 表名auto_increment=值;
create table person6(
id int auto_increment ,
name varchar(200),
primary key(id)
);
alter table person6 auto_increment=10000;
测试语句 :
insert into person6 (name)values(‘测试’);
Id值从10000开始
关联完整性(外键)
对应java代码来说,外键就是类的关联关系(一个类的成员变量是另外一个类的对象引用)
像这种一个类的变量可以找到另外一个类对象的这种关联关系,在数据库中怎么体现呢? 外键
一个表中的外键列,需要参照另一个表的主键值生成,并且一个表的外键列的值和另一个表的主键值的数据类型必须一致,
然后就可以通过这个表中的外键 去找另一个表的主键,能找到主键就能根据主键找到对应的一行数据
常用于有关联关系的两个表中
外键列的值,必须是关联表中的已有主键值,也可以为空
具体外键中的查询,现在不考虑,到DQL的时候咱们再说,现在子查询都还没讲,所以先了解什么是外键
设置外键有两种方式 :
第一种 : 创建表时添加外键约束
第二种 : 创建完表之后,添加外键约束
下面是使用方式
第一种 : 创建表时添加外键约束
create table teacher(
id int ,
name varchar(20),
primary key (id)
);
create table student (
id int ,
name varchar(20),
teacher_id int ,
primary key (id),
– foreign key(外键列) REFERENCES 关联表(关联表中主键列)
foreign key (teacher_id) references teacher(id)
);
注意 : 引用student中添加外键列,指向teacher表,所以必须先创建teacher表才行
测试语句
添加一个讲师
insert into teacher (id,name) values(1,‘张老师’);
添加一个学生小明,学生通过teacher_id可以指向张老师
insert into student (id,name,teacher_id) values(1,‘小明’,1);
添加一个学生小红,teacher_id没有设置值
insert into student (id,name) values(2,‘小红’);
添加一个小黑,teacher_id指向一个不存在的讲师,报错
insert into student (id,name,teacher_id) values(3,‘小黑’,2);
第二种 : 创建完表之后,添加外键约束
create table student1 (
id int ,
name varchar(20),
teacher_id int,
primary key (id)
);
create table teacher1(
id int ,
name varchar(20),
primary key (id)
);
语法 : alter table 表名 add foreign key (外键列列名) references 指向的表名 (主键列列名);
alter table student1 add foreign key (teacher_id) references teacher1 (id);
测试语句
添加一个讲师
insert into teacher1 (id,name) values(1,‘张老师’);
添加一个学生小明,学生通过teacher_id可以指向张老师
insert into student1 (id,name,teacher_id) values(1,‘小明’,1);
添加一个学生小红,teacher_id没有设置值
insert into student1 (id,name) values(2,‘小红’);
添加一个小黑,teacher_id指向一个不存在的讲师,报错
insert into student1 (id,name,teacher_id) values(3,‘小黑’,2);
唯一约束unique
唯一约束是指定table的列或列组合不能重复,保证数据的唯一性。
唯一约束不允许出现重复的值,但是可以为多个null.
设置unique约束有两种方式 :
第一种 : 创建表时,添加unique约束
第二种 : 创建表之后,添加unique约束
下面是使用方式
第一种 : 创建表时,添加unique约束
create table temp (
id int ,
name
varchar(20),
unique(id)
);
或
create table temp (
id int unique ,
name
varchar(20)
);
添加一条没有id的数据
insert into temp (name)values(‘张三’);
再添加一条没有id的数据,可以添加(唯一约束,又不是不为空约束)
insert into temp (name)values(‘李四’);
添加一条id为1 的数据
insert into temp (id,name)values(1,‘王五’);
再添加一条id为1的数据,报错,因为已经有了id为1了,不可重复
insert into temp (id,name)values(1,‘赵六’);
第二种 : 创建表之后,添加unique约束
create table temp1 (
id int ,
name
varchar(20)
);
alter table temp1 add unique (id);
添加一条没有id的数据
insert into temp1 (name)values(‘张三’);
再添加一条没有id的数据,可以添加(唯一约束,又不是不为空约束)
insert into temp1 (name)values(‘李四’);
添加一条id为1 的数据
insert into temp1 (id,name)values(1,‘王五’);
再添加一条id为1的数据,报错,因为已经有了id为1了,不可重复
insert into temp1 (id,name)values(1,‘赵六’);
非空约束 not null与 默认值 default
所有的类型的值都可以是null,包括int、float 等数据类型,设置为not null的字段,必须填入数据
经常和default一起使用,当不填写数据的时候,把默认值设置成指定的值
设置not null 与 default有两种方式 :
第一种 : 创建表时,添加约束
第二种 : 创建表之后,添加约束
下面是使用方式
第一种 : 创建表时,添加约束
create table temp2(
id int not null,
name
varchar(30) default ‘abc’,
sex varchar(10) not null default ‘男’
);
测试语句 :
只添加id值,可以,因为name和sex都有默认值
insert into temp2 (id) values (1);
如果设置了值,默认值就不再设置
insert into temp2 (id,name,sex) values (2,‘张三’,‘女’);
注意 : 没有添加id的值,而id又设置不能为空,并且也没有默认值,所以报错
insert into temp2 (name,sex) values (‘李四’,‘女’);
第二种 : 创建表之后,添加约束
语法 : alter table 表名 modify 列名 数据类型 not null default 默认值;
create table temp3(
id int,
name
varchar(30) ,
sex varchar(10)
);
alter table temp3 modify id int not null ;
alter table temp3 modify name varchar(30) default ‘abc’;
alter table temp3 modify sex varchar(10) not null default ‘男’;
测试语句 :
只添加id值,可以,因为name和sex都有默认值
insert into temp3 (id) values (1);
如果设置了值,默认值就不再设置
insert into temp3 (id,name,sex) values (2,‘张三’,‘女’);
没有添加id的值,而id又设置不能为空,并且也没有默认值,所以报错
insert into temp3 (name,sex) values (‘李四’,‘女’);
Check扩展约束
CHECK:检查约束(MySql不支持),检查字段的值是否为指定的值
一般用于性别等,对列的值做一些限制,比如 性别列的值只能是男或者女
但是MySQL这里给舍弃了,不再支持,所以六大约束就成了五大约束
扩展之数量关系
上面我们讲过关联关系(类成员变量保存另一个类的引用),我们再数据库中使用外键进行约束列的值
如果类中保存多个对象呢?
比如 类A种 有个集合 保存B的对象,此时 类A中需要引用多个B对象,在数据库中,一个类只能保存一个值,那么怎么存储关系?
Class A{
List bs;
}
Class B{
}
表的数量关系分为几种 :
1 一对一
2 一对多
3 多对一
4 多对多
多对多
多对多 可以理解为多个 一对多
1.4基础DQL
DQL : Data Query Language,数据查询语言,主要用于查询表。
它通常用来从一张表或者多张表(视图或者子查询等)中按指定的条件筛选出某此记录。涉及到的命令有select。
语法 :
select 列限定 from 表限定 where 行限定;
示例代码 :
create table teacher(
id int,
name
varchar(30)
);
insert into teacher (id,name) values (1,‘张老师’);
insert into teacher (id,name) values (2,‘王老师’);
最简单粗暴的一个DQL :
select * from teacher;
会查询到teacher表中所有的数据
如果想查看所有行的name
select name from teacher;
如果想查看id为1的讲师姓名
select name from teacher where id = 1;
name
varchar(30),and
且,和,的意思,一般用于 必须符合两个条件的判断,等同于java中的 &&
语法 :
select 列限定 from 表限定 where A表达式 and B表达式;
如 : 查询学生表中,name是张三且成绩大于90分
select * from student where name=‘张三’ and score > 90;
只会查询出符合两个条件的学生
or
或的意思,一般用于 符合一个添加判断的情况下,等同于java中的 ||
语法 :
select 列限定 from 表限定 where A表达式 or B表达式;
如 : 查询学生表中,name是张三 或 成绩大于90分
select * from student where name=‘张三’ or score > 90;
只要符合两个条件中的任何一个条件,就可以
注意 : 如果 一个语句中,同时出现了and和or的话,and优先级高
如果不加括号 就会把 name=’小明’ and score>=98 当做一个整体 和 id=2进行或
关系表达式
, >= , < , <= ,<>,=
- 大于
< : 小于
= : 大于等于
<= : 小于等于
= : 相等
<> : 不等于
注意 : = 和 <> 额外留意,和java中有所不同,java中判断相等用 == , 这里只用 = , java中判断不相等用 != , 这里使用 <>
如 : 查询学生表中,成绩大于90分的
select * from student where score > 90;
如 : 查询学生中,成绩为空的学生
错误 判断为空不能使用 = null ,应该使用 is null
select * from student where score = null;
select * from student where score is null;
如 : 查询学生中,成绩不为空的学生
错误 判断不为空 不能使用 <>null,应该使用 is not null
select * from student where score <> null;
select * from student where score is not null;
注意 : 判断是否为空,应该使用is null,而不是 = null , 同理,判断不为空应该使用 is not null ,而不是 <>null,并且and和or同时出现的话,and优先级比or要高
between and
在…之间
语法 :
select 列限定 from 表限定 where 列名 between 值1 and 值1;
如 : 查询学生表中 成绩在98到100之间 (包含98和100)
select * from student where score >= 98 and score<=100;
等价于
select * from student where score between 98 and 100;
In
在指定数据中
语法 :
select 列限定 from 表限定 where 列名 in(值1,值2…);
如 : 给出一个数据集合(1,3,10,20),获取学生id在这个数据集合中的学生信息
select * from student where id in (1,3,10,20);
模糊查询like
我们经常会用到搜索功能,比如百度,搜索功能实现,就是使用like模糊查询技术点
其中 % 匹配任意个数的任意字符
_ 匹配单个任意字符
语法 :
select 列限定 from 表限定 where 列名 like ‘值’ ;
如 : 把name中,把姓张的查询出来
select * from student where name like ‘张%’;
如 : 把 name中,姓名有两个字的查询出来
select * from student where name like ‘__’;
如果想要查询 _ 或者 % 需要转义 % _
Order by 排序
排序,望文知意,能够让我们查询的数据进行排序展示
语法 :
select 列限定 from 表限定 order by 列名 asc/desc;
Asc : 升序
Desc : 降序
如 : 查询所有学生信息,以成绩降序
select * from student order by score desc;
如 : 查询所有学生信息,按成绩降序,如果成绩相同,按照id升序
select * from student order by score desc , id asc;
Limit
限制条数,通常和order by一起使用,因为我们使用排序之后,再去获取前几条数据,比较有价值,比如成绩前三名
语法 :
select 列限定 from 表限定 limit 条数;
select 列限定 from 表限定 limit 开始值(不包含) ,条数;
如 : 查询学生表,分数前三名的信息
select * from student order by score desc limit 3;
如 : 查询学生表,分数第二名和第三名
select * from student order by score desc limit 1,2;
select teacher_id, avg(score) as avg from student where avg > 60 group by teacher_id;
这个时候就需要使用having进行过滤
select teacher_id, avg(score) as avg from student group by teacher_id having avg > 60;
Order by ,group by , having 执行顺序 : 先分组,再对结果进行过滤,最后再排序
6.子查询
子查询又叫嵌套查询。它通常可以位于SELECT后面 FROM后面 WHERE后面,共三种使用场景。当我们查询一个表没有办法实现功能的时候,就需要使用子查询
上面我们讲到了分组查询,可以查询每个老师所带学生的最低分,
但是我们刚才查出来之后,我们只能看到teacher_id,但是我们并不知道teacher_id对应的是那个老师,这个时候我们最好是显示老师的名字是比较好的,可以用子查询实现
场景一 : select后面
语法 :
select 字段名,(查询语句) from 表名;
如 : 查询所有学生的信息并显示老师的名字
select ,(
select name from teacher where id=teacher_id
) as teacher_name from student ;
如 : 查询每个老师的学生的 最大分数,最小分数,平均分数,分数总和,学生人数,老师名字
select max(score),min(score),sum(score),avg(score),count(),(
select name from teacher where id=teacher_id
) as teacher_name from student group by teacher_id ;
注意 :
当位于SELECT后面时,要注意
1.一定要在两个表之间找好对应关系(teacher.id必须是主键或者必须保证teacher.id在teacher表中是唯一的)
2.子查询中只能有一个字段(子查询的结果必须是一行一列)
使用子查询的时候,建议大家养成使用别名的好习惯,这样可以让我们的查询语句更加清晰。别名可以用来命令新字段,也可以用来命名新表.
场景二 : from后面
还是学生表student,我们要将成绩进行分级,并且显示汉字的分级与字母的分级。这里可以使用子查询。相当于给student“新增”了2个字段
如 : 使用子查询 对成绩划分等级, score<60 ,评级C 并且是差,score>=60 且 score<80 评级B并且是良,score>=80 评级是A并且是优
select *,
case rank
when ‘A’ then ‘优’
when ‘B’ then ‘良’
when ‘C’ then ‘差’
end rank_ch
from (
select *,
case
when score < 60 then ‘C’
when score >=60 and score <80 then ‘B’
when score >=80 then ‘A’
end as rank
from student
) a;
注意 :
当位于FROM后面时,要注意
1.我们可以把子查询当成一张表
2.必须要有别名,因为子查询优先被执行,子查询的别名,可以让别的查询当做表或者列去操作
场景三 : where后面
如 : 在不知道teacher_id 和 老师名字的对应关系的情况下,想查询出张老师下面的所有学生信息
select * from student where teacher_id in (
select id from teacher where name=‘张老师’
);
注意 :
当位于WHERE后面时,要注意
1.多条数据要用in而不要用=,如果确定子查询的结果为一行一列的话,就可以用 = 等于
2.如果返回结果为多行一列的话 要用 in , 一列是必须的,必须是一列
3.子查询中的SELECT后面只能有一个字段(多个字段的话会报错)
5. Union与 union all
合并查询,合并查询的结果
Union 会去除重复项
Union all 不会去除重复项
如 : 查询出 teacher_id = 1 的所有学生信息
select * from student where teacher_id=1;
如 : 查询出 学生分数大于60的所有学生信息
select * from student where score > 60;
如 : 查询出 学生分数大于60 或 teacher_id = 1 的所有学生信息(去除重复)
// 用 or 实现
select * from student where teacher_id=1 or score > 60;
// 用 union实现
select * from student where teacher_id=1
union
select * from student where score > 60;
如 : 查询出 学生分数大于60 或 teacher_id = 1 的所有学生信息(可重复)
select * from student where teacher_id=1
union all
select * from student where score > 60;
总结和注意 :
union / union all
它俩的作用是把两张表或者更多表合并成一张表
前者会去重(去重的依据是,UNION时SELECT出来的字段如果对应相等则认为是同一条记录,这的逻辑我们可以参考Java equals)
但是or 尽管两行数据每个字段都相等,也不会去重
后者则不会去重,它会保留两张表中的所有记录,但是它性能高(因为去重操作要花时间),
尽量使用union all,把去重这个工作交给代码去完成,这样可以减少MYSQL服务器的压力
使用union / union all的时候要注意:
1.参与合并的表,它们SELECT出来的字段数量必须一致(强制规则)
2.参与合并的表,它们SELECT出来的字段的类型建议一一对应(非强制,但是最好遵循这条规则)
3.参与合并的表,它们SELECT出来的字段的顺序建议一致(非强制,但是最好遵循这条规则)
演示示例:子查询
Day28 MySQL
可是我想要的结果是这样…
通过SQL语句 实现这样的功能,就叫行转列
1.2示例数据准备
有 id,名字,课程,成绩
create table test_9(
id int,
name varchar(22),
course varchar(22),
score decimal(18,2)
);
insert into test_9 (id,name,course,score)values(1,‘小王’,‘java’,99);
insert into test_9 (id,name,course,score)values(2,‘小张’,‘java’,89.2);
insert into test_9 (id,name,course,score)values(3,‘小李’,‘java’,88);
insert into test_9 (id,name,course,score)values(4,‘小王’,‘MySQL’,92.2);
insert into test_9 (id,name,course,score)values(5,‘小张’,‘MySQL’,42.2);
insert into test_9 (id,name,course,score)values(6,‘小李’,‘MySQL’,59.2);
1.3场景一(多行转一行多列)
可以使用下面的SQL语句(group by 与 case when结合使用即可实现):
select name,max(
case course
when ‘java’ then score
end) Java, max(
case course
when ‘MySQL’ then score
end) MySQL
from test_9
group by name;
思路分析 :
首先我们默认的情况 每个名字都输出两次,而最终结果只有一次名字,所以肯定是 以名字分组 group by
select * from test_9 group by name;
对我们来说 ,id,课程,和分数都不需要了,只需要有名字 然后再把java和mysql放上去
select name , 1 as java , 1 as MySQL from test_9 group by name;
然后再使用聚合函数聚合(此处理解“聚合”,相当于把多行数据压扁成一行)
select name,max(
case course
when ‘java’ then score
end) Java, max(
case course
when ‘MySQL’ then score
end) MySQL
from test_9
group by name;
1.4场景二(多行转一行一列)
相关函数
concat(值,’拼接符’,值 ) : 拼接,多行数据只会拼接一行
group_concat(值,’拼接符’,值 ) : 拼接,多行压扁到一行
思路分析 :
第一步:拆分问题,先按分组的思路
select name,1 as ‘各科成绩’ from test_9 group by name;
第二步:将课程名与成绩拼接成一列
select name,
concat(course,’=’,score) as ‘各科成绩’
from test_9 group by name;
第三步:利用group_concat函数将多行压扁到一行
select name,
group_concat(course,’=’,score) as ‘各科成绩’
from test_9 group by name;
第四步:修改分隔符(默认是逗号)
select name,
group_concat(course,’=’,score separator ’ | ') as ‘各科成绩’
from test_9 group by name;
第五步:按课程名称排序
select name,
group_concat(course,’=’,score order by course asc separator ’ | ') as ‘各科成绩’
from test_9 group by name;
笛卡尔积,也有的叫笛卡尔乘积
多表查询中,链接的where限定条件,不能少于 表的个数-1 , 否则就会发生笛卡尔乘积 , 这个限定条件并不是随便一个限定条件,而是用于维护映射两个表的条件,比如 外键
笛卡尔乘积是一个很消耗内存的运算,笛卡尔积产生的新表,行数是原来两个表行数的乘积,列数是原来两个表列数的和。所以我们在表连接时要使用一些优化手段,避免出现笛卡尔乘积。
最简单的多表查询 : select * from 表1,表2;
示例数据 :
create table teacher(
id int ,
name varchar(20),
primary key (id)
);
create table student (
id int ,
name varchar(20),
teacher_id int ,
primary key (id),
foreign key (teacher_id) references teacher(id)
);
insert into teacher (id,name) values(1,‘张老师’);
insert into teacher (id,name) values(2,‘王老师’);
insert into student (id,name,teacher_id) values(1,‘小明’,1);
insert into student (id,name) values(2,‘小红’);
insert into student (id,name,teacher_id) values(3,‘小黑’,2);
每个行都有两次
如果直接就是写的select * from teacher,student; ,那么 结果的条数等于两个表的乘积
所以 判断条件最少也要是1个,也就是两个表的个数-1
条数对了,因为小红没有teacher_id所以不会被查询出来
虽然条数对了,但是也会先发生一个完全的笛卡尔乘积,然后在新视图中找匹配的数据,再展示匹配的数据,会消耗内存一些
所以不推荐使用,推荐使用链接查询
优化上面的笛卡尔乘积的方式 :
优化一:使用等值连接条件,比如上面的where s.teahcer_id = t.id。
优化二:能使用inner join的就不要使用left join。
优化三:使用记录数更少的表当左表。
但是如果业务上有要求:
比如,我们有一张用户的基本信息表,我们还有一张用户的订单表
现在我们要求在页面上展示,所有用户的订单记录
这种情况下我们就必须使用left join了,因为inner join 会丢数据
假设基本信息表中有A B C三个用户(3条记录)
订单表中有A B两个人的100条订单记录
这种情况下,我们除了使用left join外,还必须要让基本信息表当左表,订单表当右表。
MYSQL支持的表连接查询有inner join,left join,right join(right join我们工作中基本不用)。
2.2 inner join
插入一条示例数据
INSERT INTO teacher (id
, name
) VALUES (‘3’, ‘孙老师’);
1
select *
from teacher tea
inner join student stu on tea.id = stu.teacher_id;
2
select *
from student stu
inner join teacher tea on tea.id = stu.teacher_id;
总结 :
数据库在通过连接两张或多张表来返回记录时,都会生成一张中间的临时表,然后再将这张临时表返回给用户。
在使用 join 连接查询 时,on和where条件的区别如下:
1、on条件是在生成临时表时使用的条件,需要和链接查询一起使用。
2、where条件是在临时表生成好后,再对临时表进行过滤的条件。这时已经没有join的含义(必须返回左边表的记录)了,条件不为真的就全部过滤掉。
链接查询,会发生笛卡尔乘积,但是不是完全的笛卡尔乘积,在生成视图的时候,会进行匹配,不符合条件的就不要了
结果数据是以左表数据为准,先生成左表数据,再生成右表数据
使用内连接的话,会以左边表为基准(student),生成新视图的时候,先生成左边表中的数据,然后再去匹配右边表中是否有符合条件的,没有的话,就不生成这一行
同时左表中有的,右表中没有的数据,都不会生成
右表中有的,左表中没有的也一样不会生成,所以 左表和右表就算换了位置,数据行数不会变多
但是会丢失数据,不符合 条件的数据不会查询出来,所以 刚添加的 孙老师就不会查询出来的,就算是teacher表在左边,也一样不会查询出来孙老师,并且学生小红也没有被查询处理
因为学生表中 teacher_id列 没有保存孙老师的ID,并且小红也没有保存老师的ID,所以都不要
多表查询是有左右表之分的,一般左表是主表,以左边为主
Inner join 也可以直接写join 不写inner
2.3 left join
left join on : 左连接,又称左外链接,是 left outer join 的简写 ,使用left join 和 使用 left outer join 是一样的效果
1 查询结果,显示小红,但是不显示孙老师
select * from student s
left join teacher t on s.teacher_id = t.id;
2 查询结果显示孙老师,但是不显示小红
select * from teacher t
left join student s on s.teacher_id = t.id;
总结 :
以左边的表为基准,左表中数据都有,右表中不符合条件的就没有,就在指定列上用null代替
生成视图的时候,也是先生成左表的数据
2.4 right join
right join on : 右链接,又称右外连接,是 right outer join 的简写,使用right join 和 使用 right outer join 是一样的
1 查询结果,显示孙老师,但是不显示小红
select * from student s
right join teacher t on s.teacher_id = t.id;
2 查询结果显示小红,但是不显示孙老师
select * from teacher t
right join student s on s.teacher_id = t.id;
总结 :
以右表为基准,右表中数据都有,左表中不符合条件的就没有,就在指定列上用null代替
但是视图生成的时候,还是会先生成左表数据
以上可以看出,student right join teacher 显示的内容是与teacher left join student相同的。而teacher right join student 显示的内容又与student left join teacher 相同。
所以,我们工作中,right join基本不用。用的最多的是inner join 与left join。
PS:外键与表连接没有任何关系,不要混淆。
外键是为了保证你不能随便删除/插入/修改数据,是数据完整性的一种约束机制。
而表连接是因为一张表的字段无法满足业务需求(你想查的字段来自于2张甚至多张表)
一个是为了增删改,一个是为了查,它俩之间没有联系。
能使用 inner join 就不使用 left join
能使用 left join 就不使用 right join
2.4 模拟Oracle中的full join
上面几个链接查询中
inner是两个表都符合条件的数据
left join 是 左表都有,右表符合条件才有
right join 是 右表都有,左表符合条件才有
那么能不能让两个表,别管符合不符合,都有呢?
full join / full outer join ,但是MySQL这里给取消了,比如Oracle就有
模拟一下 full join 的功能
使用 union 获取并集
select * from student s
left join teacher t on s.teacher_id = t.id
union
select * from student s
right join teacher t on s.teacher_id = t.id;
然后会生成一个sql文件
导入
右键数据库
运行 SQL文件
会弹出一个窗口
选择完成之后会有相应的地址填写进去
点击开始就行
然后刷新一下表/数据库就可以了
3.2 使用CMD数据导入和导出
导出
打开CMD控制台 进入到 MySQL安装路径下的bin目录下
或者直接在地址地方输入cmd,如下图
也可以直接打开
mysqldump -u用户名 -p密码 需要导出的数据库 > d:\tianliang.sql(导出的文件的保存位置)
-u用户名 -p密码 要备份的数据库, > 是把文件保存到哪里, < 是文件在哪里导进来
执行命令 mysqldump -uroot -proot 06> d:\tianliang.sql
就被导出了
导入
还是进入到bin目录下
mysql -u用户名 -p密码 导入到哪个数据库< d:\tianliang.sql(被导入的文件路径)
-u用户名 -p密码 需要导入的数据库 需要导入的sql文件
执行命令 mysql -uroot -proot 06< d:\tianliang.sql
导入完成
3.3 使用Java执行CMD数据导入和导出
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
public class MySQLUtil {
/**
* 备份的文件名
/
private static String fileName;
/*
* 备份后保存的位置
/
private static String backupDir;
/*
* 数据库信息相关
*/
private static String database;
private static String username;
private static String password;
public static void setConfig(String fileName, String backupDir,
String database, String username, String password) {
MySQLUtil.fileName = fileName;
MySQLUtil.backupDir = backupDir;
MySQLUtil.username = username;
MySQLUtil.database = database;
MySQLUtil.password = password;
}
/**
* 备份
*/
public static void backup() {
// 手动指定MySQL安装位置
// String command = "E:\\SoftWare\\Work\\MySQL\\bin\\mysqldump -u"
// + username + " -p" + password + " " + database;
// 自动匹配mysql安装位置
String command = "cmd /c mysqldump -u" + username + " -p" + password
+ " " + database;
// 执行CMD操作的类
Process process = null;
try {
process = Runtime.getRuntime().exec(command);
} catch (IOException e) {
e.printStackTrace();
}
try (
// 获取输入流
InputStream is = process.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
BufferedWriter bw = new BufferedWriter(new FileWriter(backupDir
+ File.separator + fileName + ".sql"));) {
String tmp = null;
while ((tmp = br.readLine()) != null) {
// System.out.println(tmp);
bw.write(tmp);
bw.newLine();
bw.flush();
}
System.out.println("备份成功 : "+backupDir+File.separator+fileName+".sql");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 恢复操作
*/
public static void resume() {
// 手动指定MySQL安装位置
// String command = "E:\\SoftWare\\Work\\MySQL\\bin\\mysqldump -u"
// + username + " -p" + password + " " + database;
// 自动匹配mysql安装位置
String command = "cmd /c mysql -u" + username + " -p" + password
+ " " + database;
// 执行CMD操作的类
Process process = null;
try {
process = Runtime.getRuntime().exec(command);
} catch (IOException e) {
e.printStackTrace();
}
try (
BufferedReader br = new BufferedReader(new FileReader(backupDir
+ File.separator + fileName + ".sql"));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()));) {
String tmp = null;
while ((tmp = br.readLine()) != null) {
// System.out.println(tmp);
bw.write(tmp);
bw.newLine();
bw.flush();
}
System.out.println("恢复成功 : "+database+" 数据库");
} catch (Exception e) {
e.printStackTrace();
}
}
}
JDBC : Java DataBase Connectivity (java数据库链接)
是让java链接数据库的API
API : Application Programming Intergace (应用程序接口)
就是函数库
所以 JDBC 就是提供java连接数据库的应用程序接口的,只是接口或者抽象类
而JDBC就是java中提供的一个规范,基本都是接口和抽象类作为父类,具体的实现,是数据库厂商去弄的,只不过这些厂商需要按照我的接口标准来实现
如果我们要想操作数据库,就需要把厂商开发的实现类,导入进来
然后在项目上右键 -> Build Path -> Configure Build Path…,将之加入我们项目的CLASSPATH。
创建java项目
创建lib文件夹
把mysql-connector-java-5.1.38-bin.jar复制到lib中
右键 -> Build Path -> Add to Build Path
5.1 注冊驱动
创建java类 JDBC_01_Base_DQL
Class.forName(“com.mysql.jdbc.Driver”);
5.2 建立连接(Connection)
第一个参数是url
jdbc:mysql://IP:端口/数据库
第二个参数是数据库用户名
第三个参数是数据库密码
如果设置了编码,还是乱码 可以再url后加东西 : jdbc:mysql://IP:端口/数据库?useUnicode=true&characterEncoding=utf8
5.3 创建运行SQL的语句(Statement)
5.4 运行语句
5.5 处理运行结果(ResultSet)
while (rs.next()) {
// 在循环遍历中,把数据取出来
System.out.print(rs.getInt(“id”) + " ");
// 如果传入是整型值 就会获取对应的列,比如下面 就是获取第一列的值,不建议使用
System.out.print(rs.getInt(1) + " ");
System.out.print(rs.getString(“id”) + " “);
// 字符串不能用int来接收,除非这个 字符串是纯数字
// System.out.print(rs.getInt(“name”) +” ");
System.out.print(rs.getString(“name”) + " ");
System.out.print(rs.getString(“course”) + " “);
System.out.print(rs.getDouble(“score”)+” ");
// 3 对应的是name列,如果更改表结构,把name列放到第四位了,那么这里就获取不到name了
// 所以 不灵活,不推荐使用
System.out.println(rs.getString(3));
}
5.6 释放资源
先打开的资源后关闭
6 代码优化
上面程序中,有可能会导致释放资源出现问题
比如查询语句写错了等,这时候会抛出异常,那么关闭语句就不会执行
所以我们应该使用try…catch…finally来优化一下
以刚才的练习为例,对test_jdbc表的查询进行优化
Connection conn = null;
Statement stmt = null;
ResultSet rs = null ;
try {
// 1 加载驱动
Class.forName("com.mysql.jdbc.Driver");
// 2 创建数据库连接对象
// 导包使用的都是java.sql的
conn = DriverManager.getConnection(
"jdbc:mysql://127.0.0.1:3306/_06_", "root", "root");
// 3 创建语句传输对象
String sql = "select * from test_jdbc";
stmt = conn.createStatement();
// 4 接收数据库结果集
rs = stmt.executeQuery(sql);
while (rs.next()) {
// 在循环遍历中,把数据取出来
System.out.print(rs.getInt("id") + " ");
System.out.print(rs.getString("name") + " ");
System.out.println(rs.getDouble("money")+" ");
}
} catch (Exception e) {
e.printStackTrace();
}finally{
try {
if (rs != null) {
rs.close();
}
if (stmt != null) {
stmt.close();
}
if (conn != null) {
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
7 DML
Data Manipulation Language : 数据操作语言
涉及的关键字有 : delete,update,insert
和查询的操作几乎一样,就是把第4步和第5步更改一下
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
// 1 加载驱动
Class.forName("com.mysql.jdbc.Driver");
// 2 创建数据库连接对象
conn = DriverManager.getConnection(
"jdbc:mysql://127.0.0.1:3306/_06_", "root", "root");
// 3 语句传输对象
stmt = conn.createStatement();
String sql = "insert into test_jdbc (id,name,money) values (4,'小小',999.9)";
// sql = "update test_jdbc set money=money+1000 where id=1 ";
// sql = "delete from test_jdbc where id = 1";
// 如果是查询,就返回true,不是就返回false,价值不大,所以用的不多,添加,删除,更新都可以用这个方法
// stmt.execute(sql);
// 返回值是int,返回影响了几条数据(更改了几条/删除了几条/添加了几条),添加,删除,更新都可以用这个方法
int count = stmt.executeUpdate(sql);
System.out.println("影响了 " + count + " 条数据");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
// 关闭资源,从上到下依次关闭,后打开的先关闭
if (rs != null) {
rs.close();
}
if (stmt != null) {
stmt.close();
}
if (conn != null) {
conn.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
Statement用于执行静态SQL语句,在执行的时候,必须指定一个事先准备好的SQL语句,并且相对不安全,会有SQL注入的风险
PreparedStatement是预编译的SQL语句对象,sql语句被预编译并保存在对象中, 被封装的sql语句中可以使用动态包含的参数 ? ,
在执行的时候,可以为?传递参数
使用PreparedStatement对象执行sql的时候,sql被数据库进行预编译和预解析,然后被放到缓冲区,
每当执行同一个PreparedStatement对象时,他就会被解析一次,但不会被再次编译 可以重复使用,可以减少编译次数,提高数据库性能
并且能够避免SQL注入,相对安全(把’ 单引号 使用 \ 转义,避免SQL注入 )
8.1 DQL
使用PreparedStatement 执行查询
public static void load(int id) {
Connection conn = null;
PreparedStatement prst = null;
ResultSet rs = null;
try {
// 1 加载驱动
Class.forName("com.mysql.jdbc.Driver");
// 2 创建数据库连接对象
conn = DriverManager.getConnection(
"jdbc:mysql://127.0.0.1:3306/_06_", "root", "root");
// 这里我们用? 问号代替值,可以叫占位符,也可以叫通配符
String sql = "select * from test_jdbc where id = ?";
// 3 语句传输对象
prst = conn.prepareStatement(sql);
// 设置第一个?的值
prst.setInt(1, id);
rs = prst.executeQuery();
while (rs.next()) {
System.out.print(rs.getInt("id") + " ");
System.out.print(rs.getString("name") + " ");
System.out.println(rs.getString("money") + " ");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
// 关闭资源,从上到下依次关闭,后打开的先关闭
if (rs != null) {
rs.close();
}
if (prst != null) {
prst.close();
}
if (conn != null) {
conn.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
}
演示示例:JDBC_03_PreparedStatement_load
8.1 DML
使用PreparedStatement 执行增删改,以添加为例
public static void add(int id, String name, double money) {
Connection conn = null;
PreparedStatement prst = null;
try {
// 1 加载驱动
Class.forName("com.mysql.jdbc.Driver");
// 2 创建数据库连接对象
conn = DriverManager.getConnection(
"jdbc:mysql://127.0.0.1:3306/_06_", "root", "root");
// 这里我们用? 问号代替值,可以叫占位符,也可以叫通配符
String sql = "insert into test_jdbc (id,name,money) values (?,?,?)";
// 3 语句传输对象
prst = conn.prepareStatement(sql);
// 设置第一个?的值
prst.setInt(1, id);
prst.setString(2, name);
prst.setDouble(3, money);
// 返回也是影响的条数
int count = prst.executeUpdate();
System.out.println("影响了 "+count+" 条数据");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
// 关闭资源,从上到下依次关闭,后打开的先关闭
if (prst != null) {
prst.close();
}
if (conn != null) {
conn.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
}
1 创建连接这些
2 关闭资源这些
创建链接这些可以这样进行优化
public static Connection getConnection() throws ClassNotFoundException,
SQLException {
String username = “root”;
String password = “root”;
String url = “jdbc:mysql://127.0.0.1:3306/06”;
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection(url, username,
password);
return connection;
}
关闭资源这些可以这样进行优化
因为Connection和Statement/PreparedStatement以及ResultSet都实现了AutoCloseable接口
所以我们可以直接写AutoCloseable
public static void close(AutoCloseable obj) {
if (obj != null) {
try {
obj.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Batch多语句操作
在一次任务中,执行多条数据
10.1 Statement实现
Connection conn = null;
Statement stmt = null;
try {
conn = DBUtil.getConnection();
stmt = conn.createStatement();
stmt.addBatch("insert into test_jdbc (id,name,money) values(21,'stmt多条测试1',99.12)");
stmt.addBatch("insert into test_jdbc (id,name,money) values(22,'stmt多条测试2',99.22)");
stmt.addBatch("insert into test_jdbc (id,name,money) values(23,'stmt多条测试3',99.32)");
stmt.addBatch("insert into test_jdbc (id,name,money) values(24,'stmt多条测试4',99.42)");
stmt.executeBatch();
System.out.println("执行成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
DBUtil.close(stmt);
DBUtil.close(conn);
}
10.2 PreparedStatement实现
Connection conn = null;
PreparedStatement prst = null;
try {
conn = DBUtil.getConnection();
String sql = "insert into test_jdbc (id,name,money) values(?,?,?)";
prst = conn.prepareStatement(sql);
prst.setInt(1, 31);
prst.setString(2, "prst多条测试1");
prst.setDouble(3, 11.1);
prst.addBatch();
prst.setInt(1, 32);
prst.setString(2, "prst多条测试2");
prst.setDouble(3, 21.1);
prst.addBatch();
prst.setInt(1, 33);
prst.setString(2, "prst多条测试3");
prst.setDouble(3, 31.1);
prst.addBatch();
prst.executeBatch();
System.out.println("执行成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
DBUtil.close(prst);
DBUtil.close(conn);
}