前言

模拟登陆

数据结构

算法

衡量算法的两个指标:

  • 时间复杂度
    • 算法执行了多少时间
      • 看代码执行的次数
        • 只看循环次数
  • 空间复杂度

时间复杂度如何计算

1
2
3
for(int i=0;i<n;i++){
s=s+n;
}

时间复杂度O(n),称为线性空间。

lim{f(n)/G(n)}=常量
O(G(n))=

1
2
3
4
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
for(int k=1;k<=j;k++)
s=s+k*i*j;

算法的复杂度是多少
设置循环变量最大值的情况,i=n,j=i=n,k=j=i=n
所以是n*n*n,算法福再度为O(n^3)

1
2
3
4
int i=1;
while(i<=n){
i=i*2;
}

假设执行f(n)次数
2^{f(n)}=n

f(n)=log_2{n}
lim_{n->+无穷}{f(n)/G(n)}=常量
所以G(n)可以设置为log_2{n}
所以算法复杂度为O(log_2{n})

空间复杂度

空间复杂度,不计入要处理的数据所占用的空间。
算法中额外使用的空间
循环变量可以不算

主要的存储方式

  • 顺序存储
  • 链式存储
  • 哈希存储
  • 索引存储

前三种存储主要存储在内存中。

索引存储—数据库
主要存储在在硬盘。

顺序存储

链式存储

插入和删除很方便
查找很麻烦。必须从头往后查找

分页显示
先从数据库中读取数据到后端,显示的时候前端在分批请求。

数据结构习题

链表

  • 头插入
    • 输入一组数,建立一个链表
  • 未插入
  • 倒置
    • O(n)时间复杂度实现,(一个循环实现)
      • 头插法实现
      • 尾插罚实现
  • 有序插入
    • 找插入点
      • 插入点在头部
      • 插入点在中间
      • 插入点在尾部
  • 删除
  • 重复的都要删除
  • 两条有序的合并成一条有序的
  • 两个单链表是否有公用节点,返回第一个公用的节点
    • 方法1:从第一个链表找那个取出一个节点,到另一个链表中取查找(O(n^2))
    • 方法2:算出两个链表的长度,从长的链表开始,去掉长度部分,等到相等的时候,循环2
  • 求链表倒数第n个节点()
    • 方法1:1个循环统计长度,然后再循环长度-n就是倒数第n个
    • 方法2:使用两个指针,a指向头,b指向第3个,等b走到最后的时候,a就是倒数第3个。
  • 判断链表是否有尾环
    • 使用两个指针,a,b,a走1步,b走2步,如果b等于a,的时候就是有环了
    • 追加提问:并找出环的入口点,在b等于a的时候,一个往前,一个往后,这样就变成了求公共节点了

明天下午上课

前言

在上篇文章:win10 更新后 指点杆无法使用中,我介绍了win10更新后ThinkPad的指点杆无法使用,可以通过卸载Synaptics Pointing Device来恢复指点杆的功能。
但是,不就之后,win10会自动安装上Synaptics Pointing Device。
也就是说我就算卸载掉Synaptics Pointing Device,过两天win10就又自动给安装上了。这就很烦。

Synaptics Pointing Device安装不完整会 造成屏幕闪烁

Synaptics Pointing Device还有另一个问题,就是会造成电脑的屏幕闪烁,这同样需要卸载,重启。
这样才会解决闪烁问题。

win10专业版 卸载触摸板驱动后不再安装

https://answers.microsoft.com/zh-hans/windows/forum/all/%E8%A7%A6%E6%91%B8%E6%9D%BF%E6%97%A0%E6%B3%95/cc068155-6afd-42db-9045-7e6ff63c2566?auth=1

打开组策略

重要提示
我在这一步误操作,会可能会造成电脑的键盘和鼠标无法使用。
后来我使用外接键盘才可以操作电脑,所以请先准备好外接键盘

Win+R运行,调出运行框,输入“gpedit.msc”打开组策略。
依次找到: 计算机配置-管理模版-系统-设备安装设备安装限制

image-20210724191531

接着在右侧找到“阻止使用与下列设备安装程序类相匹配的驱动程序安装设备”, 在上面右键,选择“编辑

image-20210724191652

image-20210724191740

打开设置对话框。 勾选“已启用”后点击“显示”输入设备的类GUID

image-20210724191914

获取Synaptics Pointing Device的 类GUID

Win+X打开设备管理器,然后点开鼠标和其他指针设备,在Synaptics Pointing Device上面右键,选择属性。

image-20210724191724

然后点开详细信息选项卡,点开属性选择列表,选择类 Guid

image-20210724191733

然后在类 Guid值上右键,选择复制。

image-20210724191749

在组策略中 粘贴类Guid

image-20210724192243

卸载Synaptics Pointing Device

image-20210724192556

image-20210724192602

image-20210724192758

3.3 SQL数据查询

3.3.1 SELECT命令的格式与基本使用

数据查询是数据库中最常见的操作。
SQL语言提供SELECT语句,通过查询操作可得到所需的信息。

SELECT语句的一般格式为:

1
2
3
4
5
select〈列名〉[{,〈列名〉}]
from〈表名或视图名〉[{,〈表名或视图名〉}]
[where〈检索条件〉]
[group by <列名1>[having <条件表达式>]]
[order by <列名2>[asc|desc]];
1
2
3
4
5
6
7
8
9
select	[all|distinct][top n [percent][with ties]]
列名1 [as 别名1]
[, 列名2 [ as 别名2]…]
[into 新表名]
from 表名 1[[as] 表1别名]
[inner|right|full|outer][outer]join
表名2 [[as] 表2别名]
on 条件
;

查询的结果是仍是一个表。

SELECT语句的执行过程是:

  • 根据WHERE子句的检索条件,从FROM子句指定的基本表或视图中选取满足条件的元组,再按照SELECT子句中指定的列,投影得到结果表。
  • 如果有GROUP子句,则将查询结果按照<列名1>相同的值进行分组。
  • 如果GROUP子句后有HAVING短语,则只输出满足HAVING条件的元组。
  • 如果有ORDER子句,查询结果还要按照<列名2>的值进行排序。

相关表格

S表

1
2
3
4
5
6
7
8
9
use student
create table s
(
sno char(10) constraint s_cons not null,
sn varchar(20),
age int,
sex char(2) default '男' ,
dept varchar(20)
);
1
2
3
4
5
6
7
8
9
use student
create table s
(
sno char(10) not null ,
sn varchar(20),
age int,
sex char(2) default '男' ,
dept varchar(20)
);
1
2
3
4
5
6
7
8
use student
create table s
(
sno char(6),
sn char(8) constraint sn_uniq unique,
sex char(2),
age numeric(2)
);
1
2
3
4
5
6
7
use student create table s 
(
sno char(6),
sn char(8) unique,
sex char(2),
age numeric(2)
);
1
2
3
4
5
6
use student create table s (
sno char(5),
sn char(8),
sex char(2),
constraint s_uniq unique(sn, sex)
);
1
2
3
4
5
use student create table s (
sno char(5) not null constraint s_prim primary key,
sn char(8),
age numeric(2)
);
1
2
3
4
5
6
7
8
9
10
use student create table s (
sno char(6) constraint s_prim primary key,
sn char(8) constraint sn_cons not null,
age numeric(2) constraint age_cons not null constraint age_chk check (
age between 15
and 50
),
sex char(2) default '男',
dept char(10) constraint dept_cons not null
);
1
2
3
4
5
6
use student
alter table
s
add
class_no char(6),
address char(40);
1
2
3
4
5
use student
alter table
s
alter column
sno char(8);
1
2
3
4
5
use student
alter table s
drop
constraint age_chk
;

SC

1
2
3
4
5
6
use student create table sc (
sno char(5) not null,
cno char(5) not null,
score numeric(3),
constraint sc_prim primary key(sno, cno)
);
1
2
3
4
5
6
use student create table sc (
sno char(5) not null constraint s_fore foreign key references s(sno),
cno char(5) not null constraint c_fore foreign key references c(cno),
score numeric(3),
constraint s_c_prim primary key (sno, cno)
);
1
2
3
4
5
6
7
8
use student create table sc (
sno char(5),
cno char(5),
score numeric(5, 1) constraint score_chk check(
score >= 0
and score <= 100
)
);
1
2
3
4
5
6
7
8
use student
alter table
sc
add
constraint score_chk check(
score between 0
and 100
);
1
2
use student
create unique index sci on sc(sno,cno);

T表

1
create cluster index ti on t(tn);
1
drop index sc.sci;

例3.21 查询全体学生的学号、姓名和年龄。

1
select sno, sn, age from s;

例3.22 查询学生的全部信息。

1
select * from s;

用‘ * ’表示S表的全部列名,而不必逐一列出。

例3.23 查询选修了课程的学生号。

1
select distinct sno from sc;

查询结果中的重复行被去掉

投影查询

上述查询均为不使用WHERE子句的无条件查询,也称作投影查询。

另外,利用投影查询可控制列名的顺序,并可通过指定别名改变查询结果的列标题的名字。

例3.24 查询全体学生的姓名、学号和年龄。

1
select sname name, sno, age from s;

其中,name为sname的别名

3.3.2 条件查询

当要在表中找出满足某些条件的行时,则需使用WHERE子句指定查询条件。
WHERE子句中,条件通常通过三部分来描述:

1.列名;

2.比较运算符;

3.列名、常数

where子句常用的比较运算符

image-20210723095914883

3.3.2.1 比较大小

1
2
3
4
5
6
7
8
9
10
use student create table s (
sno char(6) constraint s_prim primary key,
sn char(8) constraint sn_cons not null,
age numeric(2) constraint age_cons not null constraint age_chk check (
age between 15
and 50
),
sex char(2) default '男',
dept char(10) constraint dept_cons not null
);
1
2
3
4
5
6
use student create table sc (
sno char(5) not null constraint s_fore foreign key references s(sno),
cno char(5) not null constraint c_fore foreign key references c(cno),
score numeric(3),
constraint s_c_prim primary key (sno, cno)
);

例3.25 查询选修课程号为‘C1’的学生的学号和成绩

1
2
S(sno,sn,age,sex,dept)
SC(sno,cno,score)
展开/折叠
select sno,score
from SC
where cno='C1';

例3.26 查询成绩高于85分的学生的学号、课程号和成绩。

展开/折叠
select sno,cno,score
from SC
where score > 85;

3.3.2.2 多重条件查询

where子句需要指定一个以上的查询条件时,则需要使用逻辑运算符andornot将其连结成复合的逻辑表达式。

其优先级由高到低为:notandor,用户可以使用括号改变优先级。

例3.27 查询选修C1或C2且分数大于等于85分学生的的学号、课程号和成绩。

1
2
3
S(sno,sn,age,sex,dept)
SC(sno,cno,score)
t(tno,tn,sex,prof,sal)
展开/折叠
select sno,cno,score
from SC
where (cno='C1' or cno = 'C2') and score >=85;

3.3.2.3 确定范围

例3.28 查询工资在1000至1500之间的教师的教师号、姓名及职称。

展开/折叠
select tno,tn,prof
from T
where sal>=1000 and sal <=1500;

例3.29 查询工资不在1000至1500之间的教师的教师号、姓名及职称。

展开/折叠
select tno,tn,prof
from T
where sal<1000 or sal>1500;
展开/折叠
select tno,tn,prof
from t
where sal not between 1000 and 1500 ;

3.2.2.4 确定集合

in

利用“in”操作可以查询属性值属于指定集合的元组

例3.30 查询选修C1或C2的学生的学号、课程号和成绩。

1
2
3
4
5
S(sno,sn,age,sex,dept)
C(cno,cn)
SC(sno,cno,score)
T(tno,tn,sex,dept,prof,sal)
TC(tno,cno)
展开/折叠
select sno,cno,score
from SC
where cno in ('C1','C2');

此语句也可以使用逻辑运算符“OR”实现。

展开/折叠
select sno,cno,score
from sc
where cno='C1' or cno='C2';

not in

利用“not in”可以查询指定集合外的元组。

例3.31 查询没有选修C1,也没有选修C2的学生的学号、课程号和成绩。

展开/折叠
select sno,cno,score
from sc
where cno not in ('C1','C2');

等价于

展开/折叠
select sno,cno,score
from sc
where cno!='C1' and cno!='C2';

3.3.2.5 部分匹配查询

上例均属于完全匹配查询,当不知道完全精确的値时,用户还可以使用likenot like进行部分匹配查询(也称模糊查询)。
LIKE定义的一般格式为:

1
<属性名> LIKE <字符串常量>;

属性名必须为字符型,字符串常量的字符可以包含如下两个特殊符号:

  • %:表示任意知长度的字符串;
  • _:表示任意单个字符。

例3.32 查询所有姓张的教师的教师号和姓名。

1
2
3
4
5
S(sno,sn,age,sex,dept)
C(cno,cn)
SC(sno,cno,score)
T(tno,tn,sex,dept,prof,sal)
TC(tno,cno)
展开/折叠
select tno,tn
from T
where tn like "张%";

例3.33 查询姓名中第二个汉字是“力”的教师号和姓名。

展开/折叠
selec tno,tn
form t
where tn like '__力%';

注:一个汉字占两个字符

3.3.2.6 空值查询

某个字段没有值称之为具有空值(NULL)。

通常没有为一个列输入值时,该列的值就是空值。

空值不同于零和空格,它不占任何存储空间。

例如,某些学生选课后没有参加考试,有选课记录,但没有考试成绩,考试成绩为空值,这与参加考试,成绩为零分的不同。

例3.34 查询没有考试成绩的学生的学号和相应的课程号。

展开/折叠
select sno,cno
from sc
where score is null;

注意:这里的空值条件为IS NULL,不能写成SCORE=NULL。

3.2.2 常用库函数及统计汇总查询

image-20210723141859053

函数名称 功能
AVG 按列计算平均值
SUM 按列计算值的总和
MAX 求一列中的最大值
MIN 求一列中的最小值
COUNT 按列值计个数

例3.35 求学号为S1学生的总分和平均分。

1
2
3
4
5
S(sno,sn,age,sex,dept)
C(cno,cn)
SC(sno,cno,score)
T(tno,tn,sex,dept,prof,sal)
TC(tno,cno)
展开/折叠
select sum(score) as totalScore,avg(score) as avgScore
from SC
where sno='S1';

注意:函数SUM和AVG只能对数值型字段进行计算。

例3.36 求选修C1号课程的最高分、最低分及之间相差的分数

展开/折叠
select max(score) as max_score,min(score) as min_score max(score)-min(score) as diff_score
from sc
where cno='C1';

例3.37 求计算机系学生的总数

展开/折叠
select count(sno)
from s
where dept='计算机';

例3.38 求学校中共有多少个系

展开/折叠
select count(distinct dept) as deptNum
from s;

注意:加入关键字distinct后表示消去重复行,可计算字段“dept“不同值的数目。
count函数对空值不计算,但对零进行计算

例3.39 统计有成绩同学的人数

展开/折叠
select count(score) 
from sc;

上例中成绩为零的同学计算在内,没有成绩(即为空值)的不计算。

上面的写法中,凡是有成绩的学生都统计,如果一个学生选了多门课,并且他都去考试了,都有分数,那么将会有多个成绩。
这样的写法应该是有问题的,个人觉得应该改为如下的写法:
我还没验证,等验证之后再看

1
2
3
select count(distinct sno)
from sc
where score is not null;

例3.40 利用特殊函数COUNT(*)求计算机系学生的总数

展开/折叠
select count(*) from s
where dept='计算机';

count(*)用来统计元组的个数
不消除重复行,不允许使用distinct关键字。

如下写法是否可以

1
2
3
select count(sno)
from s
where dept='计算机';

3.3.3 分组查询

group by子句可以将查询结果按属性列属性列组合在==行的方向上进行分组==,每组在属性列或属性列组合上==具有相同的值==。

例3.42 查询各位教师的教师号及其任课的门数。

1
2
3
4
5
S(sno,sn,age,sex,dept)
C(cno,cn)
SC(sno,cno,score)
T(tno,tn,sex,dept,prof,sal)
TC(tno,cno)
展开/折叠
select tno count(*) as c_num
from tc
group by tno;

group by子句按tno的值分组,所有具有相同tno的元组为一组,对每一组使用函数count进行计算,统计出各位教师任课的门数。

若在分组后还要按照一定的条件进行筛选,则需使用having子句。

例3.43 查询选修两门以上课程的学生学号和选课门数

展开/折叠
select sno,count(*) as sc_num
from sc
group by sno
having count(*)>=2;

group by子句按sno的值分组,所有具有相同sno的元组为一组,对每一组使用函数count进行计算,统计出每位学生选课的门数。
having子句去掉不满足count(*)>=2的组。

where子句group by和having子句的执行顺序

当在一个sql查询中同时使用where子句,group by 子句和having子句时,其顺序是where–>group by–> having

wherehaving子句的根本区别在于作用对象不同。

  • where子句作用于基本表或视图,从中选择满足条件的元组;
  • having子句作用于组,选择满足条件的组,必须用于group by子句之后,但group by子句可没有having子句。

3.3.5 查询的排序

  • 当需要对查询结果排序时,应该使用order by子句
  • order by子句必须出现在其他子句之后
  • 排序方式可以指定,desc为降序,asc为升序,缺省时为升序

例3.44 查询选修C1 的学生学号和成绩,并按成绩降序排列。

1
2
3
4
5
S(sno,sn,age,sex,dept)
C(cno,cn)
SC(sno,cno,score)
T(tno,tn,sex,dept,prof,sal)
TC(tno,cno)
展开/折叠
selec sno,score
from sc
where cno='C1'
order by score desc;

例3.45 查询选修C2、C3、C4,或C5课程的学号、课程号和成绩,查询结果按学号升序排列,学号相同再按成绩降序排列。

展开/折叠
select sno,cno,score
from sc
where (cno in ('C2','C3','C4')) or cno='C5'
order by sno asc,score desc;

例3.46 求选课在三门以上且各门课程均及格的学生的学号及其总成绩,查询结果按总成绩降序列出。

展开/折叠
select sno,sum(score) as total_score
from sc
where score>=60
group by sno
having count(*)>=3
order by sum(score) desc;

分组排序执行过程

此语句为分组排序,执行过程如下:
1.(from)取出整个sc
2.(where)筛选score>=60元组
3.(group by)将选出的元组sno分组
4.(having)筛选选课三门以上的分组
5.(select)以剩下的组中提取学号和总成绩
6.(order by)将选取结果排序

简写

order by sum(score) desc可以改写成
order by 2 desc
2代表查询结果的第二列。

3.3.6 数据表连接及连接查询

  • 数据表之间的联系是通过表的字段值来体现的,这种字段称为连接字段。
  • 连接操作的目的就是通过加在连接字段的条件将多个表连接起来,以便从多个表中查询数据。
  • 前面的查询都是针对一个表进行的,当查询同时涉及两个以上的表时,称为连接查询。
  • 表的连接方法有两种:
    • 方法1:表之间满足一定的条件的行进行连接,此时from子句中指明进行连接的表名,where子句指明连接的列名及其连接条件。
    • 方法2:利用关键字join进行连接。

具体分为以下几种:

  • inner join :显示符合条件的记录,此为默认值
  • leftouterjoin显示符合条件的数据行,以及显示左边表中不符合条件的数据行,此时右边数据行会以null来显示,此称为左连接;
  • rightouterjoin显示符合条件的数据行以及右边表中不符合条件的数据行,此时左边数据行会以null来显示,此称为右连接;
  • fullouterjoin显示符合条件的数据行,以及左边表和右边表中不符合条件的数据行,此时缺乏数据的数据行会以null来显示;
  • cross join:会将一个表的每一笔数据和另一表的每笔数据匹配成新的数据行。

当将join关键词放于from子句中时,应有关键词on与之相对应,以表明连接的条件。

inner join(内连接)

内连接是一种一一映射关系,就是两张表都有的字段才能显示出来
用韦恩图表示是两个集合的交集,如图:

image-20210723193443904

以下维恩图说明了INNER JOIN子句的工作原理。结果集中的行必须出现在两个表中:t1和t2,如两个圆的交叉部分所示:

image-20210723193743319

https://www.yiibai.com/sql/sql-inner-join.html

https://www.runoob.com/sql/sql-join.html

左外连接

image-20210723194720749

左连接将返回左表中的所有行,而不管右表中是否存在匹配的行。
假设有两个表A和B。表A有四行:1,2,3和4。表B还有四行:3,4,5,6。
当将表A与表B连接时,表A中的所有行(左表)都包含在结果集中,而不管无论表B中是否存在匹配的行。

image-20210723194942631

//原文出自【易百教程】,商业转载请联系作者获得授权,非商业请保留原文链接:https://www.yiibai.com/sql/sql-left-join.html

3.3.6.1 等值连接与非等值连接

例3.47 查询刘伟老师所讲授的课程。

1
2
3
4
5
S(sno,sn,age,sex,dept)
C(cno,cn)
SC(sno,cno,score)
T(tno,tn,sex,dept,prof,sal)
TC(tno,cno)
方法1:
展开/折叠
select t.tno,tn,cno
from t tc
where (t.tno=tc.tno)and tn='刘伟';
查询条件 连接条件 连接字段

这里,tn='刘伟'为查询条件,而t.tno = tc.tno为连接条件,tno为连接字段。连接条件的一般格式为:

1
[<表名1>.] <列名1> <比较运算符> [<表名2>.] <列名2>;

其中,比较运算符主要有:=><>=<=!=

等值连接的定义

当比较运算符为“=“时,称为等值连接,其他情况为非等值连接

引用列名tno时要加上表名前缀,是因为两个表中的列名相同,必须用表名前缀来确切说明所指列属于哪个表,以避免二义性。
如果列名是唯一的,比如tn,就不必须加前缀。
上面的操作是将t表中的tnotc表中的tno相等的行连接,同时选取tn'刘伟'的行,然后再在tncno列上投影,这是连接、选取和投影的操作组合

方法2:
1
2
3
select t.tno,tn,cno
from t inner join tc
on t.tno=tc.tno and t.tn='刘伟';
方法3:

先尽量精简两张表的中的记录,然后再做连接操作

1
2
3
4
5
6
7
select r2.tno,r2.tn, r1.cno 
from
(select tno,cno from tc ) as r1
inner join
(select tno ,tn from t where tn='刘伟') as r2
on r1.tno=r2.tno
;

例3.48 查询所有选课学生的学号、姓名、选课名称及成绩

1
2
3
4
5
S(sno,sn,age,sex,dept)
C(cno,cn)
SC(sno,cno,score)
T(tno,tn,sex,dept,prof,sal)
TC(tno,cno)

先分析这些要查询的字段在那些表中:

1
2
3
4
sno,--->S,SC
sn,----->S
cn,----->C
score----->SC

可以看到涉及到三张表

这其中学生表S和课程表C是实体表,他们之间是多对多联系,SC表为联系表,所以可以使用SC表中的两个外键把S表和C表串联起来:

1
2
3
select s.sno,s.sn,c.cn,sc.score
from s,c,sc
where sc.sno=s.sno and sc.cno=c.cno;

本例涉及三个表,where子句中有两个连接条件。当有两个以上的表进行连接时,称为多表连接

1
2
3
4
5
6
select R1.sno,sn,cn,score
from
(select sno,sn from s) as R1
inner join
(select sno,cn,score from C SC where C.cno=SC.cno) as R2
on R1.sno=R2.sno;

3.3.6.2 自身连接

当一个表与其自已进行连接操作时,称为表的自身连接

例3.49 查询所有比刘伟工资高的教师姓名、性别、工资和刘伟的工资。

1
2
3
4
5
S(sno,sn,age,sex,dept)
C(cno,cn)
SC(sno,cno,score)
T(tno,tn,sex,dept,prof,sal)
TC(tno,cno)

要查询的内容均在同一表T中,可以将表T分别取两个别名,一个是X,一个是Y。将X, Y 中满足比刘伟工资高的行连接起来。这实际上是同一表T的自身连接。

1
2
3
select Y.tn,Y.sex,Y.sal,X.sal
from t as X,t as Y
where X.tn='刘伟' and Y.sal>X.sal;
其他写法
展开/折叠
select Y.tn,Y.sex,Y.sal as sal_a,X.sal as sal_b
from 
    T as X
    inner join
    T as Y
on X.tn='刘伟' and Y.sal>X.sal;
1
2
3
4
5
select Y.tn,Y.sex,Y.sal,X.sal
from
(select sal from t where tn='刘伟') as X,
t as Y
where Y.sal>X.sal;
1
2
3
4
5
6
select R1.sn,R1.sex,R1.sal as sal_a,R2.sal as sal_b
from
(select sn,sex,sal from t) as R1
inner join
(select sal from t where sn='刘伟') as R2
on R1.sal>R2.sal;

例3.50 检索所有学生姓名,年龄和选课名称。

1
2
3
4
5
S(sno,sn,age,sex,dept)
C(cno,cn)
SC(sno,cno,score)
T(tno,tn,sex,dept,prof,sal)
TC(tno,cno)

分析这些字段所在的表格:

1
2
3
sn---S表
age----S表
cn----C表
方法1

这需要三张表,三张表连接:

1
2
3
select s.sn,s.age,c.cn
from s,c,sc
where sc.sno=s.sno and sc.cno=c.cno

方法2

1
2
3
4
5
6
7
8
9
10
11
12
13
select R3.sno,R3.sn,R3.age,R4.cn
from
(select sno,sn,age from s) as R3
inner join
(
select R2.sno,R1.cn
from
(select cno,cn from c) as R1
inner join
(select sno,cno from sc) as R2
on R1.cno=R2.cno
) as R4
on R3.sno=R4.sno;

或者:

1
2
3
4
5
6
7
8
9
10
11
12
select R3.sn,R3.age c.cn
from
C,
(
select R1.sn,R1.age,R2.cno
from
(select sno,sn,age from S) as R1
inner join
(select sno,cno from SC) as R2
on R1.sno=R2.sno
) as R3
where C.cno=R3.cno;

3.3.6.3 外连接

在上面的连接操作中,不满足连接条件的元组不能作为查询结果输出。
如例3.48的查询结果只包括有选课记录的学生,而不会有吴丽同学的信息。若将例3.48改成:

例3.51 查询所有学生的学号、姓名、选课名称及成绩。(没有选课的同学的选课信息显示为空)则应写成如下的SQL语句

1
2
3
4
5
S(sno,sn,age,sex,dept)
C(cno,cn)
SC(sno,cno,score)
T(tno,tn,sex,dept,prof,sal)
TC(tno,cno)

字段所在的表:

1
2
3
sno sn--S
cn------C
score---SC
1
2
3
4
5
6
select s.sno,sn,cn,score
from s
left outer join sc
on s.sno=sc.sno
left outer join c
on c.cno=sc.cno;

则查询结果包括所有的学生,没有选课的吴丽同学的选课信息显示为空。

S left outer join SC效果:
字段名 sno sn age sex dept sno cno score
字段所在的表 S S S S S SC SC SC
字段所在的表 非空 非空 非空 非空 非空 连接条件 可以为空 可以为空
SC left outer join C效果
字段名 sno cno score cn
字段所在的表 SC的字段 SC的字段,C的字段,连接字段 SC的字段 C独有的字段,可以为NULL

3.3.7 子查询

where子句中包含一个形如select-from-where的查询块,此查询块称为子查询或嵌套查询,包含子查询的语句称为父查询或外部查询。

嵌套查询可以将一系列简单查询构成复杂查询,增强查询能力。

子查询的嵌套层次最多可达到255层,以层层嵌套的方式构造查询充分体现了SQL“结构化”的特点。

嵌套查询在执行时==由里向外==处理,每个子查询是在上一级外部查询处理之前完成,父查询要用到子查询的结果。

3.3.7.1 返回一个值的子查询

当子查询的返回值只有一个时,可以使用比较运算符=, >, <, >=, <=, !=将父查询和子查询连接起来。

例3.52 查询与刘伟教师职称相同的教师号、姓名。

1
2
3
4
5
S(sno,sn,age,sex,dept)
C(cno,cn)
SC(sno,cno,score)
T(tno,tn,sex,dept,prof,sal)
TC(tno,cno)

tno,tn–T,条件prof==刘伟的prof
—->先查询出刘伟的prof,tn=’刘伟’

1
2
3
select tno,tn from T where prof=(
select prof from T where tn='刘伟'
);

此查询相当于分成两个查询块来执行。先执行子查询:

1
2
3
select prof
from T
where tn='刘伟';

子查询向主查询只返回一个值,即刘伟教师的职称“讲师”,然后以此作为父查询的条件,相当于再执行父查询, 查询所有职称为“讲师”的教师号、姓名。

1
2
3
select tno,tn
from T
where prof='讲师';
1
2
select tno,tn from T where T.prof 
in (select prof from T where tn='刘伟');

3.3.7.2 返回一组值的子查询

如果子查询的返回值不止一个,而是一个集合时,则不能直接使用比较运算符,可以在比较运算符和子查询之间插入ANY或ALL。其具体含义详见以下各例。

1.使用ANY

例3.53 查询讲授课程号为C5的教师姓名。
1
2
3
4
5
S(sno,sn,age,sex,dept)
C(cno,cn)
SC(sno,cno,score)
T(tno,tn,sex,dept,prof,sal)
TC(tno,cno)
1
2
3
4
5
6
7
select tn
from t
where tno=any(
select tno
from tc
where cno='c5'
);

先执行子查询,找到讲授课程号为C5的教师号,为一组值构成的集合(T2,T3,T5);
再执行父查询,其中ANY的含义为任意一个,查询教师号为T2、T3、T5的教师的姓名。

该例也可以使用前面所讲的连接操作来实现:

tn—-T表
cno—TC

1
2
3
4
select tn
from
T inner join TC
on T.tno=TC.tno and tc.cno='C5';
1
2
3
select tn
from T,TC
where T.tno=TC.tno and tc.cno='C5';

可见,对于同一查询可使用子查询和连接两种方法来解决,可根据习惯任意选用。

例3.54 查询其他系中比计算机系某一教师工资高的教师的姓名和工资。
1
2
3
4
5
S(sno,sn,age,sex,dept)
C(cno,cn)
SC(sno,cno,score)
T(tno,tn,sex,dept,prof,sal)
TC(tno,cno)
1
2
3
4
5
select tn,sal from T
where
sal > any(select sal from T where dept='计算机')
and
dept!='计算机';
  • 先执行子查询,找到计算机系中所有教师的工资集合(1500,900);
  • 再执行父查询,查询所有不是计算机系且工资高于1500或900的教师姓名和工资。

此查询也可以写成:

1
2
3
4
5
6
select tn,sal from T
where
dept!='计算机'
and
sal >(select min(sal) from T where dept='计算机')
;
  • 先执行子查询,利用库函数min找到计算机系中所有教师的最低工资——900;
  • 再执行父查询,查询所有不是计算机系且工资高于900的教师。
1
2
3
4
select tn,sal from 
T as R1
(select sal from T where dept='计算机') as R2
where R1.dept!='计算机' and R1.sal>R2.sal;

2. 使用IN

可以使用IN代替“=ANY”。

例3.55(题目同3.53)查询讲授课程号为C5的教师姓名。
1
2
3
4
5
S(sno,sn,age,sex,dept)
C(cno,cn)
SC(sno,cno,score)
T(tno,tn,sex,dept,prof,sal)
TC(tno,cno)
1
2
3
4
select tn from T
where tno in(
select tno from TC where cno='C4'
);

3. 使用ALL

ALL的含义为全部。

例3.56 查询其他系中比计算机系所有教师工资都高的教师的姓名和工资。
1
2
3
4
5
select tn,sal from T
where
dept!='计算机'
and
sal > all (select sal from T where dept='计算机');
  • 子查询找到计算机系中所有教师的工资集合(1500,900);
  • 父查询找到所有不是计算机系且工资高于1500的教师姓名和工资。
使用库函数max实现

此查询也可以写成:

1
2
3
4
5
select tn,sal from T
where
dept!='计算机'
and
sal >(select max(sal) from T where dept='计算机');

库函数max的作用是找到计算机系中所有教师的最高工资1500。

例3.57 查询不讲授课程号为C5的教师姓名。
1
2
3
4
5
S(sno,sn,age,sex,dept)
C(cno,cn)
SC(sno,cno,score)
T(tno,tn,sex,dept,prof,sal)
TC(tno,cno)

课程号cno—TC表
教师姓名tn—T表

1
2
3
4
5
6
7
8
select distinct tn
from t
where 'c5' !=all
(
select cno
from tc
where tno=t.tno
);

!=ALL的含义为不等于子查询结果中的任何一个值,也可使用NOT IN代替!=ALL

相关子查询

子查询包含普通子查询和相关子查询。

前面所讲的子查询均为普通子查询,而本例中子查询的查询条件引用了父查询表中的属性值(T表的TNO值),我们把这类查询称为相关子查询

事务

示例 转账操作

使用同一个连接,操作同一个记录,关闭自动提交(也就是开启事务)

事务的特性(ACID)

自己写的事务管理,开发起来困难,后面使用Spring框架里面的事务管理来实现事务

更新或者修改的时候才会考虑事务的特性。

提交事务之后,程序抛出异常,事务不会再回滚。

脏读

对方读到了你没有提交的信息。
不可重复读
混读

依赖增强

IoC
控制反转
控制权的转移
依赖注入

反射和泛型很重要,不然读不懂框架的源码。

大学生MOOC
腾讯课堂 李晓明

image-20210722174259391

原因

我更新win10之后,键盘上的小红帽(指点杆)就无法使用了,经过我的排查,发现是因为:

win10更新时,会更新Synaptics Pointing Device这个驱动造成的,把这个更新后的驱动卸载掉之后,指点杆就可以了使用了。

卸载Synaptics Pointing Device

打开设备管理器

按快捷键win+x打开设备管理器,然后点开鼠标和其他指针设备,然后在Snaptics Pointing Device上面右键,然后选择卸载设备

image-20210721092523064

image-20210721093149204

然后在弹出的对话框中勾选上删除此设备的驱动程序软件,然后点击卸载即可

image-20210721093424814

如果没有勾选删除此设备的驱动程序软件可能会出错,我的错误是电脑屏幕闪烁,但是我并不确定到底是不是卸载驱动不完全造成的。也有可能是我电脑的屏幕坏掉了

重启电脑

卸载后,会要求重启电脑,按要求重启即可。

课前准备

下载工具

image-20210720090747895

Maven仓库下载

搜索mysql

https://mvnrepository.com/search?q=mysql

选择对应的版本

https://mvnrepository.com/artifact/mysql/mysql-connector-java

例如选择5.x的最新版本的驱动

https://mvnrepository.com/artifact/mysql/mysql-connector-java/5.1.49

image-20210720095949430

JDBC实例1:加载数据库驱动

mysql驱动下载

导入mysql驱动

JDBC 数据库连接步骤

1 加载驱动

1
Class.forName("com.mysql.jdbc.Driver");

2 获取数据库的连接

1
2
3
4
5
6
7
8
9
10
11
12
//通过driver管理器类的DriverManager的getConnection方法来获取数据库的连接
Connetcion con=null;
// 数据库服务器的网络地址
String dburl="jdbc:mysql://localhost:3306/jdbc_cpp";
// 权限 ,也就是用户名
String user="root";
String password="root";
con=DriverManager.getConnetion(dburl,user,password);
// 打印数据库连接对象,看是否成功获取连接
/**
*/
// System.out.println(con);

抛出ClassNotFound异常的情况:

  • 没有导入mysql的驱动到classPath
  • Class.forName(“驱动类全路径”),路径写错

数据库相关命令

显示有多少个数据库
1
show databases;
创建数据库命令
1
create database 数据库名;

例子

1
create database jdbc_cpp;
进入某一个数据库
1
use 数据库名;

例子

1
use jdbc_cpp;

java代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class JDBCCon {
public static void main(String[] args) throws SQLException {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Connection con = null;
String url="jdbc:mysql://localhost:3306/java_app";
String user = "root";
String password = "root";
con=DriverManager.getConnection(url,user,password);
System.out.println(con);
}
}

错误排查1

没有这个数据库

1
Exception in thread "main" com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown database 'jdbc_app'

原因,创建数据库的时候,写错了名字,修改正确的名字即可。

错误排查2 找不到驱动

1
2
3
4
5
6
7
8
9
10
11
12
java.lang.ClassNotFoundException: com.mysql.jdbc.Driver
at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:264)
at time20210720.jdbc.JDBCCon.main(JDBCCon.java:11)
Exception in thread "main" java.sql.SQLException: No suitable driver found for jdbc:mysql://localhost:3306/java_app
at java.sql.DriverManager.getConnection(DriverManager.java:689)
at java.sql.DriverManager.getConnection(DriverManager.java:247)
at time20210720.jdbc.JDBCCon.main(JDBCCon.java:20)
项目中找不到驱动的jar包

image-20210720094615128

添加jar包到项目下的lib目录,然后add to Build Path

image-20210720094804976

错误排查3

1
2
3
4
5
6
7
8
9
java.lang.ClassNotFoundException: com.mysql.jdbc.Dirver
at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:264)
at time20210720.jdbc.JDBCCon.main(JDBCCon.java:12)
com.mysql.jdbc.JDBC4Connection@3d646c37

原因:

1
Class.forName("com.mysql.jdbc.Dirver");

驱动的名称写错了,改成正确的驱动名称即可:

1
Class.forName("com.mysql.jdbc.Driver");

DriverManager类

1
2
createStatement();
p

Statement

用来执行sql语句

1
2
3
execute(String sql);
executeQuery(String selectSQL);//执行select语句
executeUpdate(String sql);//执行insert,update,delete语句

JDBC实例2:执行SQL语句 创建表格

创建表

1
2
3
create table tb_user(

);

image-20210720100640563

查看有多少表

1
show tables;

查看表的结构

1
desc 表名;

最后打开的先关闭

执行单元测试

单元测试错误排查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
private static void testConnetion() throws SQLException {
try {
Class.forName("com.mysql.jdbc.Driver");
// Class.forName("com.mysql.jdbc.Dirver");
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Connection con = null;
String url = "jdbc:mysql://localhost:3306/java_app";
String user = "root";
String password = "root";
con = DriverManager.getConnection(url, user, password);
System.out.println(con);
}

错误代码:

1
2
3
4
5
6
org.junit.runners.model.InvalidTestClassError: Invalid test class 'time20210720.jdbc.JDBCCon':
1. Method testConnetion() should not be static
2. Method testConnetion() should be public
at org.junit.runners.ParentRunner.validate(ParentRunner.java:525)
......

image-20210720104512452

原因Test方法不能使用static修饰,public修饰。修改测试方法如下即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void testConnetion() throws SQLException {
try {
Class.forName("com.mysql.jdbc.Driver");
// Class.forName("com.mysql.jdbc.Dirver");
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Connection con = null;
String url = "jdbc:mysql://localhost:3306/java_app";
String user = "root";
String password = "root";
con = DriverManager.getConnection(url, user, password);
System.out.println(con);
}

推荐

  • Java编程思想
  • Java web

一万以上,亚信8000
一年半的工作经验普通程序员一万二到一万五
两万五没问题
要是三万以上,就需要很深入了。

JDBC实例2 插入数据

PreparedStatement

是Statement的子接口用来执行预编译(动态的)的SQL,能够避免非法注入问题。
在正式开发里面,百分之90的情况都是使用PreparStatement接口。

JDBC实例3 查询数据

只用sql语句是select的才使用executeQuery,其他的都使用executeUpdate()

编程题

增删改查

传统的方法写一遍

使用工具类的写一遍

总结

对单表的操作,

项目的编写

作业,对ATM取款机的增删改查的操作

引言 自动扩容的数组

创建一个更大长度的数字,然后把原来满的数组中拷贝到新数组。

自动一个保存int的ArrayList

写一个可以保存所有类型的ArrayList

使用Object数组来实现。
可以存放多种类型的数据,取出需要强制类型转换,如果存入了其他类型的数据,则强制类型转换会出错。

写一个保存所有类型的ArrayList

要求只能存放一种类型的数据,使用泛型

泛型擦除

后续规划

明天休息

JDBC

包装类

Integer类中如何存储int

image-20210718153828988

原码里面存储基本类型的变量使用final修饰,所以对包装类进行的数值运算都会返回新的对象。

自动拆箱

1
2
3
4
5
6
Intege m=new Integer(666);
int k=2;
k=m;//自动拆箱
//k=m.intValue()
m=k;//自动装箱
//m=Integer.valueOf(k)

Integer缓存问题 Integer和int的比较

valueOf缓存问题

1
2
3
4
5
6
7
8
public static Integer valueOf(int i) {
// 当输入的i在缓存范围内时
if (i >= IntegerCache.low && i <= IntegerCache.high)
// 直接返回缓存数组中的元素
return IntegerCache.cache[i + (-IntegerCache.low)];
// 当i不再缓存范围内时,创建一个新的对象,返回
return new Integer(i);
}

IntegerCache内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];

static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
// 创建能保存low到high之间的元素的数组
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
// 给缓存数组赋值
cache[k] = new Integer(j++);

// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}

private IntegerCache() {}
}

由上面两段源码可以以看出,使用valueOf(int i)方法创建的Interger,如果值在[-128~+127]之间的,返回的是静态缓存里面早就创建好的Integer对象。

验证:

1
2
3
4
5
6
Integer a1 = Integer.valueOf(23);
Integer b1 = Integer.valueOf(23);
System.out.println("是否是同一个对象:" + (a1 == b1));
Integer a2 = Integer.valueOf(239);
Integer b2 = Integer.valueOf(239);
System.out.println("是否是同一个对象:" + (a2 == b2));

运行结果:

1
2
是否是同一个对象:true
是否是同一个对象:false

自动拆箱

当Integer赋值给int或与int比较时,会自动转换成int,然后再进行赋值或比较。

1
2
Integer b4 = Integer.valueOf(239);
System.out.println("是否相等:" + (b3 == b4));

运行结果:

1
是否相等:true

手动拆箱

调用.intValue()方法

1
2
3
int a3 = 23;
Integer a4 = Integer.valueOf(23);
System.out.println("是否相等:" + (a4.intValue() == a3));

运行结果:

1
是否相等:true

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class IntergerValueOfTest {

public static void main(String[] args) {
// TODO Auto-generated method stub
Integer a1 = Integer.valueOf(23);
Integer a2 = Integer.valueOf(23);
System.out.println("_____________________________Interger和Interger相互比较_____________________________");
System.out.println("是否是同一个对象:" + (a1 == a2));
Integer b1 = Integer.valueOf(239);
Integer b2 = Integer.valueOf(239);
System.out.println("是否是同一个对象:" + (b1 == b2));
System.out.println("_____________________________int和Interger相互比较_____________________________");
int b3 = 239;
Integer b4 = Integer.valueOf(239);
System.out.println("是否相等:" + (b3 == b4));
int a3 = 23;
Integer a4 = Integer.valueOf(23);
System.out.println("是否相等:" + (a4.intValue() == a3));
}
}

image-20210718154521531

Java异常

Java异常类的族谱

image-20210718143204269

Error程序员不关心

RuntimeException

运行时异常,是非必检异常,编译器不回报错。

常见的有:空指针,角标越界,除零异常

数字格式化异常。

image-20210718150432208

非运行时异常

继承RuntimeException的是运行时异常,其他的Exception是非运行时异常。

非运行时异常编译器会检查
常见的有:
IOException
FileNotFoundException

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import java.io.FileNotFoundException;

public class App {
/**
* 抛出非运行时异常。
*
* @throws FileNotFoundException
* @throws ClassNotFoundException
*/
static void test() throws FileNotFoundException, ClassNotFoundException {

}
/**
* 抛出运行时异常
*
* @throws NullPointerException
* @throws ArithmeticException
*/
static void print() throws NullPointerException, ArithmeticException {

}

public static void main(String[] args) {
// 运行时异常不会被编译器检查,这样写编译器不会报错。
print();
// 非运行时异常,编译器要检查,必须要处理,不然编译器会报错
// test();
// 处理运行时异常
try {
test();
} catch (FileNotFoundException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}

上面的print()方法抛出的NullPointerException,ArithmeticException是运行时异常,这类异常编译器不会检查,直接在main方法里面调用用,编译器不会报错。
而test()方法抛出的FileNotFoundException,ClassNotFoundException是非运行时异常,编译器要进行检查,在main方法调用时必须进行处理,可以在main方法体力使用try-catch语句块进行捕捉,也可以使用throws关键字向上一级方法抛出异常。

自定义异常

一般继承Exception,不要继承RuntimException(编译器不检查)

异常的捕获

1
2
3
4
5
6
7
8
9
10
try{
//1
//2
//3
}catch(异常类型 e){

}
catch(异常类型 e){

}

如果catch里面的异常可以引用 抛出的异常类 则运行catch的代码块,catch的代码块运行结束以后,会这几退回整个try-catch块,不回再运行下面的catch.
所以,小的异常类型的catch块要写在上面。

try块中的代码越少越好

问 下面代码是否有语法错误?

1
2
3
4
5
6
7
try{
//1
//2
//3
}catch(Object e){

}

没有

异常的捕获过程

finally

finally语句块一定会执行,

一般用在资源释放的,如果有多个进程引用同一个资源,而一个进程出现了异常,然后不释放掉该资源,造成其他进程等待,可能会造成死锁。

新版本的try-catch

1
2
3
4
5
6
7
try{
//1
//2
//3
}catch(类型1|类型2 e ){

}

返回值问题

问 下面代码的返回值是多少

1
2
3
4
5
6
7
8
public int m()
{
try{
retrun 1;
} finally{
return 0;
}
}

因为finally一定会执行的,所以try中的返回值会被覆盖掉,返回0

finally语句里面不要写return语句

所以为了避免这个被覆盖的问题,finally语句块不要写return语句

积极处理 消极处理

使用try-catch-finally叫做积极处理

使用throws叫消极处理

方法重写时 的(非运行时)异常处理

  • 父类方法声明异常,子类重写的方法可以不声明异常
  • 但是如果子类也要声明异常,则该异常时只能是父类声明的异常本事或者父类声明的异常的子类

子类重写父类方法时 抛出的异常不能更大,只能相等或更小

父类的方法不抛出异常,则子类重写的方法也不能抛出异常

子类重写的父类方法抛出异常A,则子类可以抛出异常A的家族,

子类方法可以抛出多个异常,但是只能是异常类A的家族类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 父类
*/
class Father {
// 父类方法中抛出的异常
public void test() throws IOException {

}
}

/**
* 子类
*/
public class ThrowsExceptionWhereOverride extends Father {
/**
* 子类重写的方法抛出的异常需要比父类抛出的要相等或更小
* 子类重写的方法可以抛出比父类更多的异常,但是一定要被父类方法抛出的相等或更小
*/
@Override
public void test() throws IOException, FileNotFoundException {

}
}

自定义异常