YongKJ 勇往直前,永不言弃 2023-06-04T06:12:04.372Z https://blog.yongkj.cn/ yongkj Hexo MySQL 面试题 https://blog.yongkj.cn/2023/06/04/mysql-interview-questions/ 2023-06-04T05:20:21.000Z 2023-06-04T06:12:04.372Z

网页连接收藏

MySQL 查询语句的内部执行过程

  1. 首先,MySQL 解析查询语句,并将其分解成一个或多个子任务。

  2. 然后,MySQL 将子任务交给它的优化器来优化查询计划。

  3. 优化器根据数据库中的表结构和索引信息,选择最佳的执行方法来实现目标。

  4. 最后,MySQL 执行者将优化器生成的执行计划进行执行,并将最终的结果集返回给用户。

MySQL 查询缓存有什么优缺点?

  • MySQL 查询缓存的优点:
  1. 减少数据库服务器的压力,提高数据库性能。因为缓存中的结果集不需要重新执行 SQL 语句即可得到,所以大大减轻了数据库服务器的压力。

  2. 由于多次使用相同 SQL 语句时,MySQL 会将其结果集保存在缓存中,因此不用重复计算,这样也能够显著地减少 SQL 语句执行时间。

  3. 查询结果集会被共享,而不是仅对特定客户端而言。如果一个客户端执行了一条 SQL 语句并将其结果集保存在 MySQL 查询缓存中,那么其他客户端也可以使用这个相同的 SQL 结果集。

  • MySQL 查询缓存的缺点:
  1. 如果数据底层表内容发生了变化(如 INSERT、UPDATE、DELETE 等 DML 语句对表内容进行修改时,那么 MySQL 查询缓存中对应的内容也会随之失效。这意味着即使是已有的相同 SQL 语句也会重新执行并把最新的数据写入到 MySQL 查询缓存中。因此 MySQL 查询缓存不适合于快速发生数据内容变化的情形。

MySQL 的常用引擎

  • MyISAM、InnoDB(MySQL 5.5.5)、Memory、CSV、Archive、Federated。

常用的存储引擎 InnoDB 和 MyISAM 有什么区别?

  • InnoDB:支持事务,具有行级锁定,外键等特性;MyISAM:不支持事务,具有表级锁定。

  • InnoDB 提供更好的并发性和安全性;MyISAM 提供更好的数据库压缩和速度优化。

  • InnoDB 在处理大量数据时能够保证一致性和正确性;MyISAM 在处理大量数据时可能会降低速度。

什么叫回表查询?

  • 普通索引查询到主键索引后,回到主键索引树搜索的过程。

如果把一个 InnoDB 表的主键删掉,是不是就没有主键,就没办法进行回表查询了?

  • 不是,主键删掉后,InnoDB 会生成长度为 6 字节的 rowid 作为主键。

一张自增表中有三条数据,删除两条数据之后重启数据库,再新增一条数据,此时这条数据的 ID 是几?

  • 如果表的引擎是 MyISAM,那么 ID=4,如果是 InnoDB 那么 ID=2(MySQL 8 之前的版本)

什么是独立表空间和共享表空间?它们的区别是什么?

  • 共享表空间指的是数据库的所有表数据,索引文件全部放在一个文件中

  • 独立表空间:每一个表都将会生成以独立的文件方式来进行存储

  • 区别在于如果把表放在共享表空间,即使表删除了空间也不会删除,因此表依然很大,而独立表空间如果删除表就会清除空间

清空表的所有数据性能最好的语句是?

A:delete from t
B:delete t
C:drop table t
D:truncate table t

答:D

唯一索引和普通索引哪个性能更好?

  • 对于查询来说两者都是从索引树进行查询,性能几乎没有任何区别;

  • 对于更新操作来说,因为唯一索引需要先将数据读取到内存,然后需要判断是否有冲突,因此比唯一索引要多了判断操作,从而性能就比普通索引性能要低。

left join 和 right join 的区别是什么?

  • left join(左联结),返回左表全部记录和右表联结字段相等的记录;

  • right join(右联结),返回右表全部记录和左表联结字段相等的记录。

什么是最左匹配原则?它的生效原则有哪些?

  • 最左匹配原则也叫最左前缀原则,是 MySQL 中的一个重要原则,指的是索引以最左边为起点任何连续的索引都能匹配上,当遇到范围查询(>、<、between、like)就会停止匹配。 生效原则来看以下示例,比如表中有一个联合索引字段 index(a,b,c):

where a=1 只使用了索引 a;
where a=1 and b=2 只使用了索引 a,b;
where a=1 and b=2 and c=3 使用a,b,c;
where b=1 or where c=1 不使用索引;
where a=1 and c=3 只使用了索引 a;
where a=3 and b like ‘xx%’ and c=3 只使用了索引 a,b。

以下 or 查询有什么问题吗?该如何优化?

select * from t where num=10 or num=20;

答:如果使用 or 查询会使 MySQL 放弃索引而全表扫描,可以改为:

select * from t where num=10
union
select * from t where num=20;

事务是什么,它有什么特性?

  • 事务是一种数据库处理机制,它用来保证数据的一致性和完整性。

  • 事务具有以下几个特性:原子性(A)、一致性(C)、隔离性(I)、持久性(D)。

  1. 原子性表示事务是不可分割的最小工作单位,要么全部成功,要么全部失败;

  2. 一致性表示事务必须使所有数据从一个一致状态变更到另外一个一致状态;

  3. 隔离性表示在并发的情况下多个事务之间不能相互影响;

  4. 持久性表示即使在发生意外的情况下也能保证所做的更改永远不会丢失。

MySQL 中有几种事务隔离级别?分别是什么?

  • read uncommited,未提交读,一个事务可以看到其他事务未提交的修改;
  • read committed,读已提交,也叫不可重复读,两次读取到的数据不一致;
  • repetable read,可重复读,事务可以多次读取同一数据而得到相同的结果;
  • serializable,串行化,所有的事务都必须一个接一个地执行,读写数据都会锁住整张表,并发性能极低,开发中很少用到。

MySQL 默认使用 repetable read 的事务隔离级别。

InnoDB 为什么要使用 B+ 树,而不是 B 树、Hash、红黑树或二叉树?

  1. 可以高效地存储数据,B+ 树可以将多个数据存储在一个节点中,而不是其他树结构中的一个节点。这样可以减少树的高度,使得搜索和遍历时更快。
  2. 可以高效地进行范围查询,因为 B+ 树中所有关键字都存储在叶子节点中,所以我们可以快速找到大于或小于特定值的所有关键字。
  3. B+ 树还有一些内部性能优化来加快对数据的读取速度。例如,B+ 树可以将相邻的叶子节点连接到一起来加快遍历速度

MySQL 是如何处理死锁?

  • 通过 innodb_lock_wait_timeout 来设置超时时间,一直等待直到超时;
  • 发起死锁检测,发现死锁之后,主动回滚死锁中的某一个事务,让其他事务继续执行。

什么是全局锁?它的应用场景有哪些?

  • 全局锁就是对整个数据库实例加锁,它的典型使用场景就是做全量逻辑备份,这个时候整个库会处于完全的只读状态。

使用全局锁会导致什么问题?

  • 使用全局锁会使整个系统不能执行更新操作,所有的更新业务会出于等待状态;如果你是在从库进行备份,则会导致主从同步严重延迟。

InnoDB 存储引擎有几种锁算法?

  • 行级锁和表级锁

  • 行级锁是InnoDB存储引擎的最常用的锁定机制,它能够将冲突访问控制在单独的记录上。这样可以有效地避免因冲突而导致的性能问题。

  • 表级锁则更加彻底,它会锁住整个表,使得其他事务无法对该表进行任何操作。

InnoDB 如何实现行锁?

  • 通过索引条件检索数据,InnoDB 才使用行级
  • 使用 for update 来实现行锁,具体脚本如下:

select * from t where id=1 for update

其中 id 字段必须有索引。

MySQL 中的重要日志分为哪几个?

  1. Error log:错误日志,记录服务器发生的所有错误。
  2. Slow query log:慢查询日志,记录执行时超过一定值的查询。
  3. General log: 普通日志,记录数据库服务器的活动情况。
  4. Binary log: 二进制日志,记录数据库中所有变化的信息。
  5. Relay log: 转发日志,记录复制环境中master-slave之间传递的数据

如何定位慢查询?

  • 使用 MySQL 中的 explain 分析执行语句,比如: explain select * from t where id=5;

MySQL 中常见的读写分离方案有哪些?

  • 使用 MySQL 官方提供的数据库代理产品 MySql ProxySQL 搭建自动分配的数据库读写分离环境;

  • 在程序层面配置多数据源使用代码实现读写分离。

怎样保证主备数据库无延迟?

  1. 检查网络带宽:可以检查两个数据库之间的带宽是否足够,如果不够则需要增加带宽。

  2. 数据库性能:可以优化数据库的相关性能参数,以便尽可能减少无用的IO开销。

表的优化策略有哪些?

  1. 采用合理的数据类型:尽量使用最小的字节来表示数据,从而减少存储空间的占用。

  2. 合理创建索引:根据查询需要选择合适的索引方式,这将有助于快速定位记录。

  3. 合理规划表与表之间的关系:正确规划数据库中表与表之间的关系,可以减少数据冗余。

  4. 优化SQL语句:使用合理、高效的SQL语句,避免不必要的运行时间浪费。

  5. 精确定义字段长度:如字符串字段可以根据实际情况来定义最大长度来减少存储量。

数据库分片方案有哪些?

  1. 水平分片:基于某个表的某一个字段来进行分片,将数据库中同一类型的记录存储在不同的数据库实例中;

  2. 垂直分片:将一张表拆分成多张表,根据具体的业务需要将不同的字段存储在不同的数据库实例中;

  3. 分区分片:将数据库表根据时间或者其他规则划分成不同的区间,然后将这些区间映射到不同的数据库实例上。

查询语句的优化方案有哪些?

  1. 使用索引:通过在表中创建索引来改善SQL查询的性能,特别是在执行复杂的查询时。

  2. 合并查询:尽量减少查询的数量,将多个查询合并成一个查询。

  3. 使用内联子查询:使用内联子查询可以减少代码量并改善性能。

  4. 限制检索记录数:限制检索的记录数可以减少SQL的运行时间。

  5. 简化条件:尽量减少where子句中的条件个数,避免复杂耗时的运算。

  6. 排序优化:选择最佳的字段进行升序或降序优化。

MySQL 毫无规律的异常重启,可能产生的原因是什么?该如何解决?

  • 可能是积累的长连接导致内存占用太多,被系统强行杀掉导致的异常重启

  • 因为在 MySQL 中长连接在执行过程中使用的临时内存对象,只有在连接断开的时候才会释放,这就会导致内存不断飙升

  • 定期断开空闲的长连接;

  • 如果是用的是 MySQL 5.7 以上的版本,可以定期执行 mysql_reset_connection 重新初始化连接资源

]]>
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" cla
输入输出流 https://blog.yongkj.cn/2021/02/09/java-io-stream/ 2021-02-09T13:07:52.000Z 2023-05-30T04:26:16.704Z

基本概念

  1. Java对数据的操作是通过流的方式,IO流用来处理设备之间的数据传输,上传文件和下载文件,Java用于操作流的对象都在IO包中。

IO流的分类

20160522165107051.jpg

字节流

  1. 字节流基类

​ 1)InputStream

InputStream:字节输入流基类,抽象类是表示字节输入流的所有类的超类。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 从输入流中读取数据的下一个字节
abstract int read()
// 从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b中
int read(byte[] b)
// 将输入流中最多 len 个数据字节读入 byte 数组
int read(byte[] b, int off, int len)


// 跳过和丢弃此输入流中数据的 n个字节
long skip(long n)

// 关闭此输入流并释放与该流关联的所有系统资源
void close()

​ 2)OutputStream

OutputStream:字节输出流基类,抽象类是表示输出字节流的所有类的超类。

1
2
3
4
5
6
7
8
9
10
11
12
// 将 b.length 个字节从指定的 byte 数组写入此输出流
void write(byte[] b)
// 将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流
void write(byte[] b, int off, int len)
// 将指定的字节写入此输出流
abstract void write(int b)

// 关闭此输出流并释放与此流有关的所有系统资源
void close()

// 刷新此输出流并强制写出所有缓冲的输出字节
void flush()
  1. 字节文件操作流

​ 1)FileInputStream

FileInputStream:字节文件输入流,从文件系统中的某个文件中获得输入字节,用于读取诸如图像数据之类的原始字节流。

1
2
3
4
5
//构造方法:
// 通过打开一个到实际文件的连接来创建一个FileInputStream,该文件通过文件系统中的File对象file指定
FileInputStream(File file)
// 通过打开一个到实际文件的连接来创建一个FileInputStream,该文件通过文件系统中的路径name指定
FileInputStream(String name)
1
2
3
4
5
6
7
8
9
10
11
12
13
// 读取f盘下该文件f://hell/test.txt
//构造方法1
InputStream inputStream = new FileInputStream(new File("f://hello//test.txt"));
int i = 0;
//一次读取一个字节
while ((i = inputStream.read()) != -1) {

// System.out.print(i + " ");// 65 66 67 68
//为什么会输出65 66 67 68?因为字符在底层存储的时候就是存储的数值。即字符对应的ASCII码。
System.out.print((char) i + " ");// A B C D
}
//关闭IO流
inputStream.close();
1
2
3
4
5
6
7
8
9
10
11
12
13
// 读取f盘下该文件f://hell/test.txt
//构造方法2
InputStream inputStream2 = new FileInputStream("f://hello/test.txt");
// 字节数组
byte[] b = new byte[2];
int i2 = 0;
// 一次读取一个字节数组
while ((i2 = inputStream2.read(b)) != -1) {

System.out.print(new String(b, 0, i2) + " ");// AB CD
}
//关闭IO流
inputStream2.close();

注: 一次读取一个字节数组,提高了操作效率,IO流使用完毕一定要关闭。

​ 2)FileOutputStream

FileOutputStream:字节文件输出流是用于将数据写入到File,从程序中写入到其他位置。

1
2
3
4
5
6
7
8
9
//构造方法:
// 创建一个向指定File对象表示的文件中写入数据的文件输出流
FileOutputStream(File file)
// 创建一个向指定File对象表示的文件中写入数据的文件输出流
FileOutputStream(File file, boolean append)
// 创建一个向具有指定名称的文件中写入数据的输出文件流
FileOutputStream(String name)
// 创建一个向具有指定name的文件中写入数据的输出文件流
FileOutputStream(String name, boolean append)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
OutputStream outputStream = new FileOutputStream(new File("test.txt"));
// 写出数据
outputStream.write("ABCD".getBytes());
// 关闭IO流
outputStream.close();

// 内容追加写入
OutputStream outputStream2 = new FileOutputStream("test.txt", true);
// 输出换行符
outputStream2.write("\r\n".getBytes());
// 输出追加内容
outputStream2.write("hello".getBytes());
// 关闭IO流
outputStream2.close();

注;输出的目的地文件不存在,则会自动创建,不指定盘符的话,默认创建在项目目录下;输出换行符时一定要写\r\n不能只写\n,因为不同文本编辑器对换行符的识别存在差异性。

  1. 字节缓冲流(高效流)

​ 1)BufferedInputStream

BufferedInputStream:字节缓冲输入流,提高了读取效率。

1
2
3
4
5
//构造方法:
// 创建一个 BufferedInputStream并保存其参数,即输入流in,以便将来使用。
BufferedInputStream(InputStream in)
// 创建具有指定缓冲区大小的 BufferedInputStream并保存其参数,即输入流in以便将来使用
BufferedInputStream(InputStream in, int size)
1
2
3
4
5
6
7
8
9
10
11
12
13
InputStream in = new FileInputStream("test.txt");
// 字节缓存流
BufferedInputStream bis = new BufferedInputStream(in);
byte[] bs = new byte[20];
int len = 0;
while ((len = bis.read(bs)) != -1) {

System.out.print(new String(bs, 0, len));
// ABCD
// hello
}
// 关闭流
bis.close();

​ 2)BufferedOutputStream

BufferedOutputStream:字节缓冲输出流,提高了写出效率。

1
2
3
4
5
6
7
8
9
10
11
12
13
//构造方法:
// 创建一个新的缓冲输出流,以将数据写入指定的底层输出流
BufferedOutputStream(OutputStream out)
// 创建一个新的缓冲输出流,以将具有指定缓冲区大小的数据写入指定的底层输出流
BufferedOutputStream(OutputStream out, int size)

常用方法:
// 将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此缓冲的输出流
void write(byte[] b, int off, int len)
// 将指定的字节写入此缓冲的输出流
void write(int b)
// 刷新此缓冲的输出流
void flush()
1
2
3
4
5
6
7
8
9
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("test.txt", true));
// 输出换行符
bos.write("\r\n".getBytes());
// 输出内容
bos.write("Hello Android".getBytes());
// 刷新此缓冲的输出流
bos.flush();
// 关闭流
bos.close();

字符流

  1. 字符流基类

​ 1)Reader

Reader:读取字符流的抽象类

1
2
3
4
5
6
7
8
9
10
11
12
//常用方法:
// 读取单个字符
int read()
// 将字符读入数组
int read(char[] cbuf)
// 将字符读入数组的某一部分
abstract int read(char[] cbuf, int off, int len)
// 跳过字符
long skip(long n)

// 关闭该流并释放与之关联的所有资源
abstract void close()

​ 2)Writer

Writer:写入字符流的抽象类.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//常用方法:
// 写入字符数组
void write(char[] cbuf)
// 写入字符数组的某一部分
abstract void write(char[] cbuf, int off, int len)
// 写入单个字符
void write(int c)
// 写入字符串
void write(String str)
// 写入字符串的某一部分
void write(String str, int off, int len)

// 将指定字符添加到此 writer
Writer append(char c)
// 将指定字符序列添加到此 writer
Writer append(CharSequence csq)
// 将指定字符序列的子序列添加到此 writer.Appendable
Writer append(CharSequence csq, int start, int end)

// 关闭此流,但要先刷新它
abstract void close()
// 刷新该流的缓冲
abstract void flush()
  1. 字符转换流

​ 1)InputStreamReader

InputStreamReader:字节流转字符流,它使用的字符集可以由名称指定或显式给定,否则将接受平台默认的字符集。

1
2
3
4
5
6
7
8
9
10
11
12
//构造方法:
// 创建一个使用默认字符集的 InputStreamReader
InputStreamReader(InputStream in)
// 创建使用给定字符集的 InputStreamReader
InputStreamReader(InputStream in, Charset cs)
// 创建使用给定字符集解码器的 InputStreamReader
InputStreamReader(InputStream in, CharsetDecoder dec)
// 创建使用指定字符集的 InputStreamReader
InputStreamReader(InputStream in, String charsetName)
//特有方法:
//返回此流使用的字符编码的名称
String getEncoding()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//使用默认编码        
InputStreamReader reader = new InputStreamReader(new FileInputStream("test.txt"));
int len;
while ((len = reader.read()) != -1) {
System.out.print((char) len);//爱生活,爱Android

}
reader.close();

//指定编码
InputStreamReader reader = new InputStreamReader(new FileInputStream("test.txt"),"utf-8");
int len;
while ((len = reader.read()) != -1) {
System.out.print((char) len);//????????Android
}
reader.close();

注:Eclipse默认使用GBK编码,test.txt文件所以是GBK编码,当指定utf-8编码时所以会乱码。

​ 2)OutputStreamWriter

OutputStreamWriter:字节流转字符流。

1
2
3
4
5
6
7
8
9
10
11
12
//构造方法:
// 创建使用默认字符编码的 OutputStreamWriter
OutputStreamWriter(OutputStream out)
// 创建使用给定字符集的 OutputStreamWriter
OutputStreamWriter(OutputStream out, Charset cs)
// 创建使用给定字符集编码器的 OutputStreamWriter
OutputStreamWriter(OutputStream out, CharsetEncoder enc)
// 创建使用指定字符集的 OutputStreamWriter
OutputStreamWriter(OutputStream out, String charsetName)
//特有方法:
//返回此流使用的字符编码的名称
String getEncoding()
  1. 字符缓冲流(高效流)

​ 1)BufferedReader

BufferedReader:字符缓冲流,从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。

1
2
3
4
5
6
7
8
//构造方法:
// 创建一个使用默认大小输入缓冲区的缓冲字符输入流
BufferedReader(Reader in)
// 创建一个使用指定大小输入缓冲区的缓冲字符输入流
BufferedReader(Reader in, int sz)
//特有方法:
// 读取一个文本行
String readLine()
1
2
3
4
5
6
7
8
9
10
//生成字符缓冲流对象
BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("test.txt")));
String str;
//一次性读取一行
while ((str = reader.readLine()) != null) {
System.out.println(str);// 爱生活,爱Android
}

//关闭流
reader.close();

​ 2)BufferedWriter

BufferedWriter:字符缓冲流,将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。

1
2
3
4
5
6
7
8
//构造方法:
// 创建一个使用默认大小输出缓冲区的缓冲字符输出流
BufferedWriter(Writer out)
// 创建一个使用给定大小输出缓冲区的新缓冲字符输出流
BufferedWriter(Writer out, int sz)
//特有方法:
// 写入一个行分隔符
void newLine()
  1. FileReader、FileWriter

​ 1)FileReader:InputStreamReader类的直接子类,用来读取字符文件的便捷类,使用默认字符编码。
​ 2)FileWriter:OutputStreamWriter类的直接子类,用来写入字符文件的便捷类,使用默认字符编码。

]]>
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" cla
类加载 https://blog.yongkj.cn/2021/02/08/java-class-loading/ 2021-02-08T12:59:52.000Z 2023-05-30T04:26:16.688Z

定义

  1. 当程序主动使用某个类时,如果该类还未被加载到内存中,则JVM会通过加载、连接、初始化3个步骤来对该类进行初始化。如果没有意外,JVM将会连续完成3个步骤,所以有时也把这个3个步骤统称为类加载或类初始化。

过程

  1. 加载

​ 1)加载指的是将类的class文件读入到内存,并为之创建一个java.lang.Class对象,也就是说,当程序中使用任何类时,系统都会为之建立一个java.lang.Class对象。

​ 2)类的加载由类加载器完成,类加载器通常由JVM提供,这些类加载器也是前面所有程序运行的基础,JVM提供的这些类加载器通常被称为系统类加载器。除此之外,开发者可以通过继承ClassLoader基类来创建自己的类加载器。

​ 3)通过使用不同的类加载器,可以从不同来源加载类的二进制数据,通常有如下几种来源。

  • 从本地文件系统加载class文件,这是前面绝大部分示例程序的类加载方式。
  • 从JAR包加载class文件,这种方式也是很常见的,JDBC编程时用到的数据库驱动类就放在JAR文件中,JVM可以从JAR文件中直接加载该class文件。
  • 通过网络加载class文件。
  • 把一个Java源文件动态编译,并执行加载。

类加载器通常无须等到“首次使用”该类时才加载该类,Java虚拟机规范允许系统预先加载某些类。

  1. 链接

当类被加载之后,系统为之生成一个对应的Class对象,接着将会进入连接阶段,连接阶段负责把类的二进制数据合并到JRE中。类连接又可分为如下3个阶段。

​ 1)验证:验证阶段用于检验被加载的类是否有正确的内部结构,并和其他类协调一致。Java是相对C++语言是安全的语言,例如它有C++不具有的数组越界的检查。这本身就是对自身安全的一种保护。验证阶段是Java非常重要的一个阶段,它会直接的保证应用是否会被恶意入侵的一道重要的防线,越是严谨的验证机制越安全。验证的目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身安全。其主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证。

四种验证做进一步说明:

文件格式验证:主要验证字节流是否符合Class文件格式规范,并且能被当前的虚拟机加载处理。例如:主,次版本号是否在当前虚拟机处理的范围之内。常量池中是否有不被支持的常量类型。指向常量的中的索引值是否存在不存在的常量或不符合类型的常量。

元数据验证:对字节码描述的信息进行语义的分析,分析是否符合java的语言语法的规范。

字节码验证:最重要的验证环节,分析数据流和控制,确定语义是合法的,符合逻辑的。主要的针对元数据验证后对方法体的验证。保证类方法在运行时不会有危害出现。

符号引用验证:主要是针对符号引用转换为直接引用的时候,是会延伸到第三解析阶段,主要去确定访问类型等涉及到引用的情况,主要是要保证引用一定会被访问到,不会出现类等无法访问的问题。

​ 2)准备:类准备阶段负责为类的静态变量分配内存,并设置默认初始值。

​ 3)解析:将类的二进制数据中的符号引用替换成直接引用。说明一下:符号引用:符号引用是以一组符号来描述所引用的目标,符号可以是任何的字面形式的字面量,只要不会出现冲突能够定位到就行。布局和内存无关。直接引用:是指向目标的指针,偏移量或者能够直接定位的句柄。该引用是和内存中的布局有关的,并且一定加载进来的。

  1. 初始化

​ 1)初始化是为类的静态变量赋予正确的初始值,准备阶段和初始化阶段看似有点矛盾,其实是不矛盾的,如果类中有语句:private static int a = 10,它的执行过程是这样的,首先字节码文件被加载到内存后,先进行链接的验证这一步骤,验证通过后准备阶段,给a分配内存,因为变量a是static的,所以此时a等于int类型的默认初始值0,即a=0,然后到解析(后面在说),到初始化这一步骤时,才把a的真正的值10赋给a,此时a=10。

类加载时机

  1. 创建类的实例,也就是new一个对象
  2. 访问某个类或接口的静态变量,或者对该静态变量赋值
  3. 调用类的静态方法
  4. 反射(Class.forName(“com.lyj.load”))
  5. 初始化一个类的子类(会首先初始化子类的父类)
  6. JVM启动时标明的启动类,即文件名和类名相同的那个类

除此之外,下面几种情形需要特别指出:

对于一个final类型的静态变量,如果该变量的值在编译时就可以确定下来,那么这个变量相当于“宏变量”。Java编译器会在编译时直接把这个变量出现的地方替换成它的值,因此即使程序使用该静态变量,也不会导致该类的初始化。反之,如果final类型的静态Field的值不能在编译时确定下来,则必须等到运行时才可以确定该变量的值,如果通过该类来访问它的静态变量,则会导致该类被初始化。

类加载器

类加载器负责加载所有的类,其为所有被载入内存中的类生成一个java.lang.Class实例对象。一旦一个类被加载如JVM中,同一个类就不会被再次载入了。正如一个对象有一个唯一的标识一样,一个载入JVM的类也有一个唯一的标识。在Java中,一个类用其全限定类名(包括包名和类名)作为标识;但在JVM中,一个类用其全限定类名和其类加载器作为其唯一标识。例如,如果在pg的包中有一个名为Person的类,被类加载器ClassLoader的实例kl负责加载,则该Person类对应的Class对象在JVM中表示为(Person.pg.kl)。这意味着两个类加载器加载的同名类:(Person.pg.kl)和(Person.pg.kl2)是不同的、它们所加载的类也是完全不同、互不兼容的。

JVM预定义有三种类加载器,当一个 JVM启动的时候,Java开始使用如下三种类加载器:

  1. 根类加载器(bootstrap class loader):它用来加载 Java 的核心类,是用原生代码来实现的,并不继承自 java.lang.ClassLoader(负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类)。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。

下面程序可以获得根类加载器所加载的核心类库,并会看到本机安装的Java环境变量指定的jdk中提供的核心jar包路径:

1
2
3
4
5
6
7
8
9
10
public class ClassLoaderTest {

public static void main(String[] args) {

URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
for(URL url : urls){
System.out.println(url.toExternalForm());
}
}
}
  1. 扩展类加载器(extensions class loader):它负责加载JRE的扩展目录,lib/ext或者由java.ext.dirs系统属性指定的目录中的JAR包的类。由Java语言实现,父类加载器为null。

  2. 系统类加载器(system class loader):被称为系统(也称为应用)类加载器,它负责在JVM启动时加载来自Java命令的-classpath选项、java.class.path系统属性,或者CLASSPATH换将变量所指定的JAR包和类路径。程序可以通过ClassLoader的静态方法getSystemClassLoader()来获取系统类加载器。如果没有特别指定,则用户自定义的类加载器都以此类加载器作为父加载器。由Java语言实现,父类加载器为ExtClassLoader。

类加载器加载Class大致要经过如下8个步骤:

  1. 检测此Class是否载入过,即在缓冲区中是否有此Class,如果有直接进入第8步,否则进入第2步。
  2. 如果没有父类加载器,则要么Parent是根类加载器,要么本身就是根类加载器,则跳到第4步,如果父类加载器存在,则进入第3步。
  3. 请求使用父类加载器去载入目标类,如果载入成功则跳至第8步,否则接着执行第5步。
  4. 请求使用根类加载器去载入目标类,如果载入成功则跳至第8步,否则跳至第7步。
  5. 当前类加载器尝试寻找Class文件,如果找到则执行第6步,如果找不到则执行第7步。
  6. 从文件中载入Class,成功后跳至第8步。
  7. 抛出ClassNotFountException异常。
  8. 返回对应的java.lang.Class对象。

类加载机制

  1. JVM的类加载机制主要有如下3种

​ 1)全盘负责:所谓全盘负责,就是当一个类加载器负责加载某个Class时,该Class所依赖和引用其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入。

​ 2)双亲委派:所谓的双亲委派,则是先让父类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类。通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父加载器,依次递归,如果父加载器可以完成类加载任务,就成功返回;只有父加载器无法完成此加载任务时,才自己去加载。

​ 3)缓存机制。缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区中搜寻该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓冲区中。这就是为很么修改了Class后,必须重新启动JVM,程序所做的修改才会生效的原因。

  1. 这里说明一下双亲委派机制:

20180813145521896.png

​ 1)双亲委派机制,其工作原理的是,如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式,即每个儿子都很懒,每次有活就丢给父亲去干,直到父亲说这件事我也干不了时,儿子自己才想办法去完成。

​ 2)双亲委派机制的优势:采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。其次是考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。

]]>
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" cla
注解 https://blog.yongkj.cn/2021/02/07/java-annotation/ 2021-02-07T13:00:52.000Z 2023-05-30T04:26:16.679Z

注解的概念

  1. 注解(Annotation),也叫元数据(Metadata),是Java5的新特性,JDK5引入了Metadata很容易的就能够调用Annotations。注解与类、接口、枚举在同一个层次,并可以应用于包、类型、构造方法、方法、成员变量、参数、本地变量的声明中,用来对这些元素进行说明注释。

注解的语法与定义形式

  1. 以@interface关键字定义
  2. 注解包含成员,成员以无参数的方法的形式被声明。其方法名和返回值定义了该成员的名字和类型。
  3. 成员赋值是通过@Annotation(name=value)的形式。
  4. 注解需要标明注解的生命周期,注解的修饰目标等信息,这些信息是通过元注解实现。

java.lang.annotation 中定义的 Target 注解来说明:

1
2
3
4
5
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = { ElementType.ANNOTATION_TYPE } )
public @interface Target {
ElementType[] value();
}

源码分析如下:
第一:元注解@Retention,成员value的值为RetentionPolicy.RUNTIME。
第二:元注解@Target,成员value是个数组,用{}形式赋值,值为ElementType.ANNOTATION_TYPE
第三:成员名称为value,类型为ElementType[]
另外,需要注意一下,如果成员名称是value,在赋值过程中可以简写。如果成员类型为数组,但是只赋值一个元素,则也可以简写。如上面的简写形式为:

1
2
3
4
5
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}

注解的分类

注解的分类有两种分法:

  1. 第一种分法

​ 1)基本内置注解,是指Java自带的几个Annotation,如@Override、Deprecated、@SuppressWarnings等;

​ 2)元注解(meta-annotation),是指负责注解其他注解的注解,JDK 1.5及以后版本定义了4个标准的元注解类型,如下:

  • @Target
  • @Retention
  • @Documented
  • @Inherited

​ 3)自定义注解,根据需要可以自定义注解,自定义注解需要用到上面的meta-annotation

  1. 第二种分法,根据作用域分类

​ 1)源码时注解(RetentionPolicy.SOURCE)
​ 2)编译时注解(RetentionPolicy.CLASS)
​ 3)运行时注解(RetentionPolicy.RUNTIME)

注解需要标明注解的生命周期,这些信息是通过元注解 @Retention 实现,注解的值是 enum 类型的 RetentionPolicy,包括以下几种情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public enum RetentionPolicy {
/**
* 注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃.
* 这意味着:Annotation仅存在于编译器处理期间,编译器处理完之后,该Annotation就没用了
*/
SOURCE,

/**
* 注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期.
*/
CLASS,

/**
* 注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在,
* 保存到class对象中,可以通过反射来获取
*/
RUNTIME
}

元注解

如上所介绍的Java定义了4个标准的元注解:

  1. @Documented:标记注解,用于描述其它类型的注解应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。
1
2
3
4
5
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}
  1. @Inherited:标记注解,允许子类继承父类的注解
1
2
3
4
5
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}
  1. @Retention:指Annotation被保留的时间长短,标明注解的生命周期,3种RetentionPolicy取值含义上面以说明
1
2
3
4
5
6
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
RetentionPolicy value();
}
  1. @Target:标明注解的修饰目标,共有
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
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}

// ElementType取值
public enum ElementType {
/** 类、接口(包括注解类型)或枚举 */
TYPE,
/** field属性,也包括enum常量使用的注解 */
FIELD,
/** 方法 */
METHOD,
/** 参数 */
PARAMETER,
/** 构造函数 */
CONSTRUCTOR,
/** 局部变量 */
LOCAL_VARIABLE,
/** 注解上使用的元注解 */
ANNOTATION_TYPE,
/** 包 */
PACKAGE
}

使用反射API读取注解

  1. MyAnno自定义注解类
1
2
3
4
5
6
7
8
@Target({ElementType.TYPE,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnno{
String value() default "猿同学";
String name() default "猿同学";
int id() default -1;//用户不存在
int age() default 18;
}
  1. POJO类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
@MyAnno("java")
public class User {
@MyAnno(id=20)
private int id;
@MyAnno(name="java")
private String name;
@MyAnno(age=20)
private int age;

public User() {
}

public User(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}

public int getId() {
return id;
}

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

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
  1. 利用反射读取注解
1
2
3
4
5
6
7
8
9
10
public class TestAnnoReflat {
public static void main(String[] args) throws Exception {
Class cls = Class.forName("com.sc.domain.User");
MyAnno annotation = (MyAnno) cls.getDeclaredAnnotation(MyAnno.class);
System.out.println(annotation.value());

MyAnno name = cls.getDeclaredField("name").getAnnotation(MyAnno.class);
System.out.println(name.name());
}
}
]]>
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" cla
反射 https://blog.yongkj.cn/2021/02/06/java-reflect/ 2021-02-06T12:58:52.000Z 2023-05-30T04:26:16.710Z

什么叫java反射

  1. Java反射是Java被视为动态(或准动态)语言的一个关键性质。

  2. 这个机制允许程序在运行时透过Reflection APIs取得任何一个已知名称的class的内部信息,包括其modifiers(诸如public, static 等)、superclass(例如Object)、实现之interfaces(例如Cloneable),也包括fields和methods的所有信息,并可于运行时改变fields内容或唤起methods。

  3. Java反射机制容许程序在运行时加载、探知、使用编译期间完全未知的classes。

  4. 换言之,Java可以加载一个运行时才得知名称的class,获得其完整结构。

为什么要使用反射

  1. 如果正常实例化去一个对象,去调用它的任意方法或者属性跟通过反射去实例化一个对象到底有什么区别呢?
  2. 其实在编译阶段是没有任何区别,但是根据反射的定义:反射的重点是在于运行阶段来获取类的信息,因为在编译阶段有部分的信息是不清晰的,需要在运行的阶段去加载的。
  3. 简单来说:在编译阶段不知道需要实例化哪个对象,需要在运行阶段去从配置文件去加载,或者在运行阶段,需要临时访问类的信息。

反射API

  1. java.lang.Class 反射的核心,可以获取类的属性,方法等等
  2. java.lang.reflect.Constructor 类的构造方法
  3. java.lang.reflect.Field 类的成员变量,可以用来获取和设置类之中的属性值
  4. java.lang.reflect.Method 类的方法,可以获取类中的方法信息
  5. java.lang.reflect.Modifier 类的访问权限

Java反射相关的API在包java.lang.reflect中,JDK 1.6.0的reflect包如下图

Member接口该接口可以获取有关类成员(域或者方法)后者构造函数的信息。
AccessibleObject类该类是域(field)对象、方法(method)对象、构造函数(constructor)对象的基础类。它提供了将反射的对象标记为在使用时取消默认 Java 语言访问控制检查的能力。
Array类该类提供动态地生成和访问JAVA数组的方法。
Constructor类提供一个类的构造函数的信息以及访问类的构造函数的接口。
Field类提供一个类的域的信息以及访问类的域的接口。
Method类提供一个类的方法的信息以及访问类的方法的接口。
Modifier类提供了 static 方法和常量,对类和成员访问修饰符进行解码。
Proxy类提供动态地生成代理类和类实例的静态方法。

JAVA反射机制提供了什么功能

  1. 在运行时判断任意一个对象所属的类
  2. 在运行时构造任意一个类的对象
  3. 在运行时判段任意一个类所具有的成员变量和方法
  4. 在运行时调用任一个对象的方法
  5. 在运行时创建新类对象
  6. 在使用Java的反射功能时,基本首先都要获取类的Class对象,再通过Class对象获取其他的对象。

使用反射的步骤

  1. 获取想要的类的class对象
  2. 调用类中的方法
  3. 使用反射API来操作这些信息

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
class Type{
public int pubIntField;
public String pubStringField;
private int prvIntField;

public Type(){
Log("Default Constructor");
}

Type(int arg1, String arg2){
pubIntField = arg1;
pubStringField = arg2;

Log("Constructor with parameters");
}

public void setIntField(int val) {
this.prvIntField = val;
}
public int getIntField() {
return prvIntField;
}

private void Log(String msg){
System.out.println("Type:" + msg);
}
}

class ExtendType extends Type{
public int pubIntExtendField;
public String pubStringExtendField;
private int prvIntExtendField;

public ExtendType(){
Log("Default Constructor");
}

ExtendType(int arg1, String arg2){
pubIntExtendField = arg1;
pubStringExtendField = arg2;

Log("Constructor with parameters");
}

public void setIntExtendField(int field7) {
this.prvIntExtendField = field7;
}
public int getIntExtendField() {
return prvIntExtendField;
}

private void Log(String msg){
System.out.println("ExtendType:" + msg);
}
}
  1. 获取类的Class对象

Class 类的实例表示正在运行的 Java 应用程序中的类和接口。获取类的Class对象有多种方式:

方法调用结果
调用getClassBoolean var1 = true;Class<?> classType2 = var1.getClass();System.out.println(classType2);输出:class java.lang.Boolean
运用.class 语法Class<?> classType4 = Boolean.class;System.out.println(classType4);输出:class java.lang.Boolean
运用static method Class.forName()Class<?> classType5 = Class.forName(“java.lang.Boolean”);System.out.println(classType5);输出:class java.lang.Boolean
运用primitive wrapper classes的TYPE 语法这里返回的是原生类型,和Boolean.class返回的不同Class<?> classType3 = Boolean.TYPE;System.out.println(classType3); 输出:boolean
  1. 获取类的Fields

可以通过反射机制得到某个类的某个属性,然后改变对应于这个类的某个实例的该属性值。JAVA 的Class类提供了几个方法获取类的属性。

方法调用结果
public Field getField(String name)返回一个 Field 对象,它反映此 Class 对象所表示的类或接口的指定公共成员字段
public Field[] getFields()返回一个包含某些 Field 对象的数组,这些对象反映此 Class 对象所表示的类或接口的所有可访问公共字段
public Field getDeclaredField(String name)返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段
public Field[] getDeclaredFields()返回 Field 对象的一个数组,这些对象反映此 Class 对象所表示的类或接口所声明的所有字段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
Class<?> classType = ExtendType.class;

// 使用getFields获取属性
Field[] fields = classType.getFields();
for (Field f : fields)
{
System.out.println(f);
}

System.out.println();

// 使用getDeclaredFields获取属性
fields = classType.getDeclaredFields();
for (Field f : fields)
{
System.out.println(f);
}

/*
输出:

public int com.quincy.ExtendType.pubIntExtendField

public java.lang.String com.quincy.ExtendType.pubStringExtendField

public int com.quincy.Type.pubIntField

public java.lang.String com.quincy.Type.pubStringField

public int com.quincy.ExtendType.pubIntExtendField

public java.lang.String com.quincy.ExtendType.pubStringExtendField

private int com.quincy.ExtendType.prvIntExtendField
*/

可见getFields和getDeclaredFields区别:

getFields返回的是申明为public的属性,包括父类中定义,

getDeclaredFields返回的是指定类定义的所有定义的属性,不包括父类的。

  1. 获取类的Method

通过反射机制得到某个类的某个方法,然后调用对应于这个类的某个实例的该方法

Class类提供了几个方法获取类的方法。

方法调用结果
public Method getMethod(String name, Class<?>… parameterTypes)返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法
public Method[] getMethods()返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的公共 member 方法
public Method getDeclaredMethod(String name,Class<?>… parameterTypes)返回一个 Method 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法
public Method[] getDeclaredMethods()返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// 使用getMethods获取函数 
Class<?> classType = ExtendType.class;
Method[] methods = classType.getMethods();
for (Method m : methods)
{
System.out.println(m);
}

System.out.println();

// 使用getDeclaredMethods获取函数
methods = classType.getDeclaredMethods();
for (Method m : methods)
{
System.out.println(m);
}

/*
输出:

public void com.quincy.ExtendType.setIntExtendField(int)

public int com.quincy.ExtendType.getIntExtendField()

public void com.quincy.Type.setIntField(int)

public int com.quincy.Type.getIntField()

public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException

public final void java.lang.Object.wait() throws java.lang.InterruptedException

public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException

public boolean java.lang.Object.equals(java.lang.Object)

public java.lang.String java.lang.Object.toString()

public native int java.lang.Object.hashCode()

public final native java.lang.Class java.lang.Object.getClass()

public final native void java.lang.Object.notify()

public final native void java.lang.Object.notifyAll()

private void com.quincy.ExtendType.Log(java.lang.String)

public void com.quincy.ExtendType.setIntExtendField(int)

public int com.quincy.ExtendType.getIntExtendField()
*/
  1. 获取类的Constructor

通过反射机制得到某个类的构造器,然后调用该构造器创建该类的一个实例

Class类提供了几个方法获取类的构造器。

方法调用结果
public Constructor getConstructor(Class<?>… parameterTypes)返回一个 Constructor 对象,它反映此 Class 对象所表示的类的指定公共构造方法
public Constructor<?>[] getConstructors()返回一个包含某些 Constructor 对象的数组,这些对象反映此 Class 对象所表示的类的所有公共构造方法
public Constructor getDeclaredConstructor(Class<?>… parameterTypes)返回一个 Constructor 对象,该对象反映此 Class 对象所表示的类或接口的指定构造方法
public Constructor<?>[] getDeclaredConstructors()返回 Constructor 对象的一个数组,这些对象反映此 Class 对象表示的类声明的所有构造方法。它们是公共、保护、默认(包)访问和私有构造方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 使用getConstructors获取构造器  
Constructor<?>[] constructors = classType.getConstructors();
for (Constructor<?> m : constructors)
{
System.out.println(m);
}

System.out.println();

// 使用getDeclaredConstructors获取构造器
constructors = classType.getDeclaredConstructors();
for (Constructor<?> m : constructors)
{
System.out.println(m);
}

/*
输出:
public com.quincy.ExtendType()

public com.quincy.ExtendType()

com.quincy.ExtendType(int,java.lang.String)
*/
  1. 新建类的实例

通过反射机制创建新类的实例,有几种方法可以创建

方法调用结果
调用无自变量ctor1、调用类的Class对象的newInstance方法,该方法会调用对象的默认构造器,如果没有默认构造器,会调用失败.Class classType = ExtendType.class;Object inst = classType.newInstance();System.out.println(inst);输出:Type:Default ConstructorExtendType:Default Constructorcom.quincy.ExtendType@d80be3 2、调用默认Constructor对象的newInstance方法Class classType = ExtendType.class;Constructor<?> constructor1 = classType.getConstructor();Object inst = constructor1.newInstance();System.out.println(inst);输出:Type:Default ConstructorExtendType:Default Constructorcom.quincy.ExtendType@1006d75
调用带参数ctor3、调用带参数Constructor对象的newInstance方法Constructor<?> constructor2 =classType.getDeclaredConstructor(int.class, String.class);Object inst = constructor2.newInstance(1, “123”);System.out.println(inst);输出:Type:Default ConstructorExtendType:Constructor with parameterscom.quincy.ExtendType@15e83f9
  1. 调用类的函数

通过反射获取类Method对象,调用Field的Invoke方法调用函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Class<?> classType = ExtendType.class;
Object inst = classType.newInstance();
Method logMethod = classType.getDeclaredMethod("Log", String.class);
logMethod.invoke(inst, "test");

输出:
Type:Default Constructor
ExtendType:Default Constructor



Class<?> classType = ExtendType.class;
Object inst = classType.newInstance();
Method logMethod = classType.getDeclaredMethod("Log", String.class);

logMethod.invoke(inst, "test");
  1. 设置/获取类的属性值

通过反射获取类的Field对象,调用Field方法设置或获取值

1
2
3
4
5
Class<?> classType = ExtendType.class;
Object inst = classType.newInstance();
Field intField = classType.getField("pubIntExtendField");
intField.setInt(inst, 100);
int value = intField.getInt(inst);
]]>
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" cla
框架注解 https://blog.yongkj.cn/2021/02/05/java-framework-annotations/ 2021-02-05T12:56:52.000Z 2023-05-30T04:26:16.701Z

SpringBoot之常用注解

在spring boot中,摒弃了spring以往项目中大量繁琐的配置,遵循约定大于配置的原则,通过自身默认配置,极大的降低了项目搭建的复杂度。同样在spring boot中,大量注解的使用,使得代码看起来更加简洁,提高开发的效率。这些注解不光包括spring boot自有,也有一些是继承自spring的。

  1. 项目配置注解

​ 1)@SpringBootApplication 注解

@SpringBootApplication是一个复合注解,包含了@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan这三个注解。

  • @SpringBootConfiguration:标注当前类是配置类,这个注解继承自@Configuration。并会将当前类内声明的一个或多个以@Bean注解标记的方法的实例纳入到srping容器中,并且实例名就是方法名。

  • @EnableAutoConfiguration:是自动配置的注解,这个注解会根据我们添加的组件jar来完成一些默认配置,我们做微服时会添加spring-boot-starter-web这个组件jar的pom依赖,这样配置会默认配置springmvc 和tomcat。

  • @ComponentScan:扫描当前包及其子包下被@Component,@Controller,@Service,@Repository注解标记的类并纳入到spring容器中进行管理。等价于context:component-scan的xml配置文件中的配置项。

    大多数情况下,这3个注解会被同时使用,基于最佳实践,这三个注解就被做了包装,成为了@SpringBootApplication注解

​ 2)@ServletComponentScan 注解

  • Servlet、Filter、Listener 可以直接通过 @WebServlet、@WebFilter、@WebListener 注解自动注册,这样通过注解servlet ,拦截器,监听器的功能而无需其他配置,所以这次相中使用到了filter的实现,用到了这个注解

​ 3)@MapperScan注解

  • spring-boot支持mybatis组件的一个注解,通过此注解指定mybatis接口类的路径,即可完成对mybatis接口的扫描
  • 它和@mapper注解是一样的作用,不同的地方是扫描入口不一样。@mapper需要加在每一个mapper接口类上面。所以大多数情况下,都是在规划好工程目录之后,通过@MapperScan注解配置路径完成mapper接口的注入。
  • 添加mybatis相应组建依赖之后。就可以使用该注解。

​ 4)资源导入注解

  • @ImportResource @Import @PropertySource 这三个注解都是用来导入自定义的一些配置文件。

  • @ImportResource(locations={}) 导入其他xml配置文件,需要标准在主配置类上。

  • 导入property的配置文件 @PropertySource指定文件路径,这个相当于使用spring的标签来完成配置项的引入。

  • @import注解是一个可以将普通类导入到spring容器中做管理

  1. controller 层

​ 1)@Controller注解

  • @Controller 表明这个类是一个控制器类,和@RequestMapping来配合使用拦截请求,如果不在method中注明请求的方式,默认是拦截get和post请求。这样请求会完成后转向一个视图解析器。但是在大多微服务搭建的时候,前后端会做分离。所以请求后端只关注数据处理,后端返回json数据的话,需要配合@ResponseBody注解来完成。

  • 这样一个只需要返回数据的接口就需要3个注解来完成,大多情况我们都是需要返回数据。也是基于最佳实践,所以将这三个注解进一步整合。

  • @RestController 是@Controller 和@ResponseBody的结合,一个类被加上@RestController 注解,数据接口中就不再需要添加@ResponseBody。更加简洁。

  • 同样的情况,@RequestMapping(value=””,method= RequestMethod.GET ),我们都需要明确请求方式。这样的写法又会显得比较繁琐,于是乎就有了如下的几个注解。

普通风格Rest风格
@RequestMapping(value=“”,method = RequestMethod.GET)@GetMapping(value =“”)
@RequestMapping(value=“”,method = RequestMethod.POST)@PostMapping(value =“”)
@RequestMapping(value=“”,method = RequestMethod.PUT)@PutMapping(value =“”)
@RequestMapping(value=“”,method = RequestMethod.DELETE)@DeleteMapping(value =“”)

这几个注解是 @RequestMapping(value=””,method= RequestMethod.xxx )的最佳实践。为了代码的更加简洁。

​ 2)@CrossOrigin 注解

  • @CrossOrigin(origins = “”, maxAge = 1000) 这个注解主要是为了解决跨域访问的问题。这个注解可以为整个controller配置启用跨域,也可以在方法级别启用。

  • 我们在项目中使用这个注解是为了解决微服在做定时任务调度编排的时候,会访问不同的spider节点而出现跨域问题。

​ 3)@Autowired 注解

  • 这是个最熟悉的注解,是spring的自动装配,这个个注解可以用到构造器,变量域,方法,注解类型上。当我们需要从bean 工厂中获取一个bean时,Spring会自动为我们装配该bean中标记为@Autowired的元素。

​ 4)@EnableCaching 注解

  • 个注解是spring framework中的注解驱动的缓存管理功能。自spring版本3.1起加入了该注解。其作用相当于spring配置文件中的cache manager标签。

​ 5)@PathVariable 注解

  • 路径变量注解,@RequestMapping中用{}来定义url部分的变量名
  1. servcie层注解

​ 1)@Service

  • 这个注解用来标记业务层的组件,我们会将业务逻辑处理的类都会加上这个注解交给spring容器。事务的切面也会配置在这一层。当让 这个注解不是一定要用。有个泛指组件的注解,当我们不能确定具体作用的时候 可以用泛指组件的注解托付给spring容器。

​ 2)@Resource

  • @Resource和@Autowired一样都可以用来装配bean,都可以标注字段上,或者方法上。 @resource注解不是spring提供的,是属于J2EE规范的注解。

  • 两个之前的区别就是匹配方式上有点不同,@Resource默认按照名称方式进行bean匹配,@Autowired默认按照类型方式进行bean匹配。

  1. 持久层注解

​ 1)@Repository

  • @Repository注解类作为DAO对象,管理操作数据库的对象。

  • 总得来看,@Component, @Service, @Controller, @Repository是spring注解,注解后可以被spring框架所扫描并注入到spring容器来进行管理

  • @Component是通用注解,其他三个注解是这个注解的拓展,并且具有了特定的功能。

  • 通过这些注解的分层管理,就能将请求处理,义务逻辑处理,数据库操作处理分离出来,为代码解耦,也方便了以后项目的维护和开发。

  • 所以我们在正常开发中,如果能用@Service, @Controller, @Repository其中一个标注这个类的定位的时候,就不要用@Component来标注。

​ 2)@Transactional

  • 通过这个注解可以声明事务,可以添加在类上或者方法上。

  • 在spring boot中 不用再单独配置事务管理,一般情况是我们会在servcie层添加了事务注解,即可开启事务。要注意的是,事务的开启只能在public 方法上。并且主要事务切面的回滚条件。正常我们配置rollbackfor exception时 ,如果在方法里捕获了异常就会导致事务切面配置的失效。

  1. 其他相关注解
  • @ControllerAdvice 和 @RestControllerAdvice:通常和@ExceptionHandler、@InitBinder、@ModelAttribute一起配合使用。

  • @ControllerAdvice 和 @ExceptionHandler 配合完成统一异常拦截处理。

  • @RestControllerAdvice 是 @ControllerAdvice 和 @ResponseBody的合集,可以将异常以json的格式返回数据。

]]>
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" cla
基础知识 https://blog.yongkj.cn/2021/02/04/java-basic-knowledge/ 2021-02-04T12:50:52.000Z 2023-05-30T04:26:16.685Z

8种基本数据类型

  1. 关于Java的8种基本数据类型,其名称、位数、默认值、取值范围及示例如下表所示:
序号数据类型位数默认值取值范围举例说明
1byte(位)80-2^7 ~ +2^7-1byte b = 10;
2short(短整数)160-2^15 ~ +2^15-1short s = 10;
3int(整数)320-2^31 ~ +2^31-1int i = 10;
4long(长整数)640-2^63 ~ +2^63-1long l = 10l;
5float(单精度)320.0-2^128 ~ +2^127float f = 10.0f;
6double(双精度)640.0-2^1024 ~ +2^1023double d = 10.0d;
7char(字符)160 ~ +2^16-1char c = ‘c’;
8boolean(布尔值)8falsetrue、falseboolean b = true;

自动装箱拆箱

  1. 装箱就是自动将基本数据类型转换为包装器类型;
  2. 拆箱就是自动将包装器类型转换为基本数据类型。
1
2
3
4
5
//自动装箱
Integer total = 99;

//自动拆箱
int totalprim = total;

equals()和==的区别

  1. equals是判断两个变量或者实例指向同一个内存空间的值是否相同
  2. ==是判断两个变量或者实例是不是指向同一个内存空间
  3. ==是判断两个人是不是住在同一个地址,而equals是判断同一个地址里住的人是不是同一个

String、StringBuffer和StringBuilder的区别

  1. 可变与不可变:String是不可变字符串对象,StringBuilder和StringBuffer是可变字符串对象(其内部的字符数组长度可变)。
  2. 是否多线程安全:String 是线程安全的。StringBuffer 也是线程安全的,而 StringBuilder 是非线程安全的。
  3. String、StringBuilder、StringBuffer三者的执行效率:

​ 1)StringBuilder > StringBuffer > String

  1. 应当根据不同的情况来进行选择使用:

​ 1)当字符串相加操作或者改动较少的情况下,建议使用 String str=”hello”这种形式;

​ 2)当字符串相加操作较多的情况下,建议使用StringBuilder,如果采用了多线程,则使用StringBuffer。

字节流和字符流

  1. 什么是字节流?

​ 1)传输数据的最基本单位是字节的流。

  1. 什么是字符流?

​ 1)传输数据的最基本单位是字符的流。

  1. 实现一个拷贝文件的类使用字节流还是字符流?

​ 1)字符类型一般包括:word、txt、文本类型。

​ 2)字节类型一般包括:图片、声音、图像等。

​ 3)因为一般字符流最终都要转换成字节流,所以为考虑到通用性,要用字节流。

异常

  1. 程序错误分为三种:编译错误;运行时错误;逻辑错误。

​ 1)编译错误:程序没有遵循语法规则,编译程序发现并提示错误的原因和位置。

​ 2)运行时错误:程序在执行时,运行环境发现了不能执行的操作

​ 3)逻辑错误:程序没有按照预期的逻辑顺序执行

​ 4)异常:指程序运行时发生错误,而异常处理就是对这些错误进行处理和控制

  1. 分类

​ 1)Error(错误):是程序无法处理的错误,表示运行应用程序中较严重问题。

​ 2)Exception(异常):是程序本身可以处理的异常。

  1. Exception(异常)分类

​ 1)运行时异常:一般是由程序逻辑错误引起的,当程序中可能出现这类异常,即使没有用try-catch语句捕获它或throws子句声明抛出它,也会编译通过。

​ 2)非运行时异常 (编译异常):从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。

  1. 异常的处理

​ 1)抛出异常:throw,throws

​ 2)捕获异常:try,catch,finally

&和&&的区别

  1. &运算符有两种用法:(1)按位与;(2)逻辑与。
  2. &&运算符是短路与运算。
  3. 逻辑与跟短路与的差别是非常巨大的,虽然二者都要求运算符左右两端的布尔值都是true整个表达式的值才是true。
  4. &&之所以称为短路运算是因为,如果&&左边的表达式的值是false,右边的表达式会被直接短路掉,不会进行运算。
  5. 很多时候我们可能都需要用&&而不是&,例如在验证用户登录时判定用户名不是null而且不是空字符串,应当写为:username != null &&!username.equals(“”)
  6. 二者的顺序不能交换,更不能用&运算符,因为第一个条件如果不成立,根本不能进行字符串的equals比较,否则会产生NullPointerException异常。

int和Integer有什么区别

  1. Java是一个近乎纯洁的面向对象编程语言,但是为了编程的方便还是引入了基本数据类型,但是为了能够将这些基本数据类型当成对象操作,Java为每一个基本数据类型都引入了对应的包装类型(wrapper class)
  2. int的包装类就是Integer,从Java 5开始引入了自动装箱/拆箱机制,使得二者可以相互转换
  3. Java 为每个原始类型提供了包装类型:

​ 1)原始类型: boolean,char,byte,short,int,long,float,double

​ 2)包装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double

值传递和引用传递

  1. 值传递是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量
  2. 引用传递一般是对于对象型变量而言的,传递的是该对象地址的一个副本, 并不是原对象本身
  3. 所以对引用对象进行操作会同时改变原对象
  4. 一般认为,java内的传递都是值传递

Lamda表达式的优缺点

  1. 优点:

​ 1)简洁。

​ 2)非常容易并行计算。

​ 3)可能代表未来的编程趋势。

  1. 缺点:

​ 1)若不用并行计算,很多时候计算速度没有比传统的 for 循环快。(并行计算有时需要预热才显示出效率优势)

​ 2)不容易调试。

​ 3)若其他程序员没有学过 lambda 表达式,代码不容易让其他语言的程序员看懂。

]]>
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" cla
面向对象 https://blog.yongkj.cn/2021/02/03/java-object-oriented/ 2021-02-03T10:30:52.000Z 2023-05-30T04:26:16.707Z

面向对象的特征

  1. java面向对象的三大特征是什么?
    封装、继承、多态。
  2. 封装
    把属性和方法隐藏起来,只保留一些对外的接口和外部进行交互。
  3. 继承
    子类继承父类的特征和行为,使得子类具有父类的非private属性和方法。
  4. 多态
    多态就是同一个接口,使用不同的实现,而执行不同的操作。
  5. 抽象
    将一类对象的共同特征总结出来构造类的过程,数据抽象指的是属性,行为抽象指的是方法。

抽象类与接口

  1. 抽象类

​ 1)抽象类是用来捕捉子类的通用特性的

​ 2)它不能被实例化,只能被用作子类的超类

​ 3)抽象类是被用来创建继承层级里子类的模板

  1. 接口

​ 1)接口是抽象方法的集合

​ 2)如果一个类实现了某个接口,那么它就继承了这个接口的抽象方法

​ 3)如果实现了这个接口,那么就必须确保使用这些方法

​ 4)接口只是一种形式,接口自身不能做任何事情

  1. 抽象类和接口的对比
参数抽象类接口
默认的方法实现它可以有默认的方法实现接口完全是抽象的。它根本不存在方法的实现
实现子类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现。子类使用关键字implements来实现接口。它需要提供接口中所有声明的方法的实现
构造器抽象类可以有构造器接口不能有构造器
与正常Java类的区别除了你不能实例化抽象类之外,它和普通Java类没有任何区别接口是完全不同的类型
访问修饰符抽象方法可以有publicprotecteddefault这些修饰符接口方法默认修饰符是public。你不可以使用其它修饰符。
main方法抽象方法可以有main方法并且我们可以运行接口没有main方法,因此我们不能运行它
多继承抽象方法可以继承一个类和实现多个接口接口只可以继承一个或多个其它接口
速度它比接口速度要快接口是稍微有点慢的,因为它需要时间去寻找在类中实现的方法。
添加新方法如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。如果你往接口中添加方法,那么你必须改变实现该接口的类。

内部类

  1. 概述

​ 1)把类定义在另一个类的内部,该类就被称为内部类

​ 2)把类Inner定义在类Outer中,类Inner就被称为内部类

  1. 访问规则

​ 1)可以直接访问外部类的成员,包括私有

​ 2)外部类要想访问内部类成员,必须创建对象

  1. 分类

​ 1)成员内部类:位于外部类成员位置的类,可以使用外部类中所有的成员变量和成员方法(包括private的)

​ 2)局部内部类:定义在一个方法或者一个作用域里面的类,主要是作用域发生了变化,只能在自身所在方法和属性中被使用

​ 3)静态内部类:用static修饰的内部类,不能使用外部类的非static成员变量和成员方法

​ 4)匿名内部类:一个没有名字的类,是内部类的简化写法,其实是继承该类或者实现接口的子类匿名对象

重载与重写

  1. 方法重载

​ 1)同一个类中的多个方法具有相同的名字,这些方法具有不同的参数列表

​ 2)参数类型和个数不一样,返回值类型可以相同也可以不相同

​ 3)无法以返回型别作为重载函数的区分标准

​ 4)重载Overloading是一个类中多态性的一种表现。

  1. 方法重写

​ 1)子类定义的方法与父类中的方法具有相同的方法名字,相同的参数表和相同的返回类型

​ 2)子类中不能重写父类中的final方法

​ 3)子类中必须重写父类中的abstract方法

hashCode()和equals()方法的联系

  1. 相等(相同)的对象必须具有相等的哈希码(或者散列码)。

  2. 如果两个对象的hashCode相同,它们并不一定相同。

final, finally, finalize的区别

  1. final 用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,类不可继承。
  2. finally是异常处理语句结构的一部分,表示总是执行。
  3. finalize是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源回收,例如关闭文件等。

Comparable和Comparator接口的作用以及区别

  1. Comparable接口包含compareTo()方法。这个方法可以个给两个对象排序。
  2. Comparator接口包含compare()和equals()方法。
  3. compare()方法用来给两个输入参数排序,返回负数,0,正数表明第一个参数是小于,等于,大于第二个参数。
  4. equals()方法需要一个对象作为参数,它用来决定输入参数是否和comparator相等。
  5. 只有当输入参数也是一个comparator并且输入参数和当前comparator的排序结果是相同的时候,这个方法才返回true。

Java是否支持多继承

  1. Java中类不支持多继承,只支持单继承(即一个类只有一个父类)。
  2. 但是java中的接口支持多继承,,即一个子接口可以有多个父接口。
  3. 接口的作用是用来扩展对象的功能,一个子接口继承多个父接口,说明子接口扩展了多个功能。
  4. 当类实现接口时,类就扩展了相应的功能。

extends 和super 泛型限定符

  1. 泛型中上界和下界的定义

​ 1)上界<? extend Fruit>

​ 2)下界<? super Apple>

  1. 上界和下界的特点

​ 1)上界的list只能get,不能add(确切地说不能add出除null之外的对象,包括Object)

​ 3)下界的list只能add,不能get

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
import java.util.ArrayList;
import java.util.List;

class Fruit {}
class Apple extends Fruit {}
class Jonathan extends Apple {}
class Orange extends Fruit {}

public class CovariantArrays {
public static void main(String[] args) {
//上界
List<? extends Fruit> flistTop = new ArrayList<Apple>();
flistTop.add(null);
//add Fruit对象会报错
//flist.add(new Fruit());
Fruit fruit1 = flistTop.get(0);

//下界
List<? super Apple> flistBottem = new ArrayList<Apple>();
flistBottem.add(new Apple());
flistBottem.add(new Jonathan());
//get Apple对象会报错
//Apple apple = flistBottem.get(0);
}
}
  1. 上界<? extend Fruit> ,表示所有继承Fruit的子类,但是具体是哪个子类,无法确定,所以调用add的时候,要add什么类型,谁也不知道。但是get的时候,不管是什么子类,不管追溯多少辈,肯定有个父类是Fruit,所以,我都可以用最大的父类Fruit接着,也就是把所有的子类向上转型为Fruit。
  2. 下界<? super Apple>,表示Apple的所有父类,包括Fruit,一直可以追溯到老祖宗Object 。那么当我add的时候,我不能add Apple的父类,因为不能确定List里面存放的到底是哪个父类。但是我可以add Apple及其子类。因为不管我的子类是什么类型,它都可以向上转型为Apple及其所有的父类甚至转型为Object 。但是当我get的时候,Apple的父类这么多,我用什么接着呢,除了Object,其他的都接不住。

什么是泛型

  1. 泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?
  2. 顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class GenericTest {

public static void main(String[] args) {
/*
List list = new ArrayList();
list.add("qqyumidi");
list.add("corn");
list.add(100);
*/

List<String> list = new ArrayList<String>();
list.add("qqyumidi");
list.add("corn");
//list.add(100); // 1 提示编译错误

for (int i = 0; i < list.size(); i++) {
String name = list.get(i); // 2
System.out.println("name:" + name);
}
}
}
  1. 采用泛型写法后,在//1处想加入一个Integer类型的对象时会出现编译错误,通过List,直接限定了list集合中只能含有String类型的元素,从而在//2处无须进行强制类型转换,因为此时,集合能够记住元素的类型信息,编译器已经能够确认它是String类型了。
]]>
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" cla
设计模式 https://blog.yongkj.cn/2021/02/02/java-design-mode/ 2021-02-02T07:24:52.000Z 2023-05-30T04:26:16.696Z

设计模式分类

  1. 创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
  2. 结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
  3. 行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

单例模式

所谓的单例设计指的是一个类只允许产生一个实例化对象。
最好理解的一种设计模式,分为懒汉式和饿汉式。

  1. 饿汉式

​ 1)构造方法私有化,外部无法产生新的实例化对象

​ 2)只能通过static方法取得实例化对象

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
class Singleton {
/**
* 在类的内部可以访问私有结构,所以可以在类的内部产生实例化对象
*/
private static Singleton instance = new Singleton();

/**
* private 声明构造
*/
private Singleton() {

}

/**
* 返回对象实例
*/
public static Singleton getInstance() {
return instance;
}

public void print() {
System.out.println("Hello Singleton...");
}
}

public class Main {
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
singleton.print();
}
}
  1. 懒汉式

​ 1)当第一次去使用Singleton对象的时候才会为其产生实例化对象的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class Singleton {
/**
* 声明变量
*/
private static volatile Singleton singleton = null;

/**
* 私有构造方法
*/
private Singleton() {

}

/**
* 提供对外方法
* @return
*/
public static Singleton getInstance() {
// 还未实例化
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}

public void print() {
System.out.println("Hello World");
}
}

public class Main {
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
singleton.print();
}
}

工厂方法模式

工厂方法模式分为三种:

​ (1)普通工厂模式,就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建。

​ (2)多个工厂方法模式,是对普通工厂方法模式的改进,在普通工厂方法模式中,如果传递的字符串出错,则不能正确创建对象,而多个工厂方法模式是提供多个工厂方法,分别创建对象。

​ (3)静态工厂方法模式,将上面的多个工厂方法模式里的方法置为静态的,不需要创建实例,直接调用即可。

  1. 普通工厂模式

​ 1)建立一个工厂类,对实现了同一接口的一些类进行实例的创建。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
interface Sender {
void Send();
}

class MailSender implements Sender {
@Override
public void Send() {
System.out.println("This is mail sender...");
}
}

class SmsSender implements Sender {
@Override
public void Send() {
System.out.println("This is sms sender...");
}
}

class FactoryPattern {
public static Sender produce(String str) {
if ("mail".equals(str)) {
return new MailSender();
} else if ("sms".equals(str)) {
return new SmsSender();
} else {
System.out.println("输入错误...");
return null;
}
}
}

public class Main {
public static void main(String[] args) {
Sender sender = FactoryPattern.produce("mail");
sender.Send();
}
}
  1. 多个工厂方法模式

​ 1)该模式是对普通工厂方法模式的改进,在普通工厂方法模式中,如果传递的字符串出错,则不能正确创建对象,而多个工厂方法模式是提供多个工厂方法,分别创建对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
interface Sender {
void Send();
}

class MailSender implements Sender {
@Override
public void Send() {
System.out.println("This is mail sender...");
}
}

class SmsSender implements Sender {
@Override
public void Send() {
System.out.println("This is sms sender...");
}
}

class SendFactory {
public Sender produceMail() {
return new MailSender();
}

public Sender produceSms() {
return new SmsSender();
}
}

public class Main {
public static void main(String[] args) {
SendFactory factory = new SendFactory();
Sender sender = factory.produceMail();
sender.Send();
}
}
  1. 静态工厂方法模式

​ 1)将上面的多个工厂方法模式里的方法置为静态的,不需要创建实例,直接调用即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
interface Sender {
void Send();
}

class MailSender implements Sender {
@Override
public void Send() {
System.out.println("This is mail sender...");
}
}

class SmsSender implements Sender {
@Override
public void Send() {
System.out.println("This is sms sender...");
}
}

class SendFactory {
public static Sender produceMail() {
return new MailSender();
}

public static Sender produceSms() {
return new SmsSender();
}
}

public class Main {
public static void main(String[] args) {
Sender sender = SendFactory.produceMail();
sender.Send();
}
}

抽象工厂模式

工厂方法模式有一个问题就是,类的创建依赖工厂类,也就是说,如果想要扩展程序,必须对工厂类进行修改,这违背了闭包原则,所以,从设计角度考虑,有一定的问题,如何解决?

那么这就用到了抽象工厂模式,创建多个工厂类,这样一旦需要增加新的功能,直接增加新的工厂类就可以了,不需要修改之前的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
interface Provider {
Sender produce();
}

interface Sender {
void Send();
}

class MailSender implements Sender {
public void Send() {
System.out.println("This is mail sender...");
}
}

class SmsSender implements Sender {
public void Send() {
System.out.println("This is sms sender...");
}
}

class SendMailFactory implements Provider {
public Sender produce() {
return new MailSender();
}
}

class SendSmsFactory implements Provider {

public Sender produce() {
return new SmsSender();
}
}

public class Main {
public static void main(String[] args) {
Provider provider = new SendMailFactory();
Sender sender = provider.produce();
sender.Send();
}
}

建造者模式

工厂类模式提供的是创建单个类的模式,而建造者模式则是将各种产品集中起来管理,用来创建复合对象,所谓复合对象就是指某个类具有不同的属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
import java.util.ArrayList;
import java.util.List;

abstract class Builder {
/**
* 第一步:装CPU
*/
public abstract void buildCPU();

/**
* 第二步:装主板
*/
public abstract void buildMainBoard();

/**
* 第三步:装硬盘
*/
public abstract void buildHD();

/**
* 获得组装好的电脑
* @return
*/
public abstract Computer getComputer();
}

/**
* 装机人员装机
*/
class Director {
public void Construct(Builder builder) {
builder.buildCPU();
builder.buildMainBoard();
builder.buildHD();
}
}

/**
* 具体的装机人员
*/
class ConcreteBuilder extends Builder {
Computer computer = new Computer();

@Override
public void buildCPU() {
computer.Add("装CPU");
}

@Override
public void buildMainBoard() {
computer.Add("装主板");
}

@Override
public void buildHD() {
computer.Add("装硬盘");
}

@Override
public Computer getComputer() {
return computer;
}
}

class Computer {
/**
* 电脑组件集合
*/
private List<String> parts = new ArrayList<String>();

public void Add(String part) {
parts.add(part);
}

public void print() {
for (int i = 0; i < parts.size(); i++) {
System.out.println("组件:" + parts.get(i) + "装好了...");
}
System.out.println("电脑组装完毕...");
}
}

public class Main {
public static void main(String[] args) {
Director director = new Director();
Builder builder = new ConcreteBuilder();
director.Construct(builder);
Computer computer = builder.getComputer();
computer.print();
}
}

适配器设计模式

适配器模式是将某个类的接口转换成客户端期望的另一个接口表示,目的是消除由于接口不匹配所造成的的类的兼容性问题。主要分三类:类的适配器模式、对象的适配器模式、接口的适配器模式。

  1. 类的适配器模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Source {
public void method1() {
System.out.println("This is original method...");
}
}

interface Targetable {
/**
* 与原类中的方法相同
*/
public void method1();

/**
* 新类的方法
*/
public void method2();
}

class Adapter extends Source implements Targetable {
@Override
public void method2() {
System.out.println("This is the targetable method...");
}
}

public class Main {
public static void main(String[] args) {
Targetable targetable = new Adapter();
targetable.method1();
targetable.method2();
}
}
  1. 对象的适配器模式

​ 1)基本思路和类的适配器模式相同,只是将Adapter 类作修改,这次不继承Source 类,而是持有Source 类的实例,以达到解决兼容性的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class Source {
public void method1() {
System.out.println("This is original method...");
}
}

interface Targetable {
/**
* 与原类中的方法相同
*/
public void method1();

/**
* 新类的方法
*/
public void method2();
}

class Wrapper implements Targetable {
private Source source;

public Wrapper(Source source) {
super();
this.source = source;
}

@Override
public void method1() {
source.method1();
}

@Override
public void method2() {
System.out.println("This is the targetable method...");
}
}

public class Main {
public static void main(String[] args) {
Source source = new Source();
Targetable targetable = new Wrapper(source);
targetable.method1();
targetable.method2();
}
}
  1. 接口的适配器模式

​ 1)有时我们写的一个接口中有多个抽象方法,当我们写该接口的实现类时,必须实现该接口的所有方法,这明显有时比较浪费,因为并不是所有的方法都是我们需要的,有时只需要某一些,此处为了解决这个问题,我们引入了接口的适配器模式,借助于一个抽象类,该抽象类实现了该接口,实现了所有的方法,而我们不和原始的接口打交道,只和该抽象类取得联系,所以我们写一个类,继承该抽象类,重写我们需要的方法就行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
/**
* 定义端口接口,提供通信服务
*/
interface Port {
/**
* 远程SSH端口为22
*/
void SSH();

/**
* 网络端口为80
*/
void NET();

/**
* Tomcat容器端口为8080
*/
void Tomcat();

/**
* MySQL数据库端口为3306
*/
void MySQL();
}

/**
* 定义抽象类实现端口接口,但是什么事情都不做
*/
abstract class Wrapper implements Port {
@Override
public void SSH() {

}

@Override
public void NET() {

}

@Override
public void Tomcat() {

}

@Override
public void MySQL() {

}
}

/**
* 提供聊天服务
* 需要网络功能
*/
class Chat extends Wrapper {
@Override
public void NET() {
System.out.println("Hello World...");
}
}

/**
* 网站服务器
* 需要Tomcat容器,Mysql数据库,网络服务,远程服务
*/
class Server extends Wrapper {
@Override
public void SSH() {
System.out.println("Connect success...");
}

@Override
public void NET() {
System.out.println("WWW...");
}

@Override
public void Tomcat() {
System.out.println("Tomcat is running...");
}

@Override
public void MySQL() {
System.out.println("MySQL is running...");
}
}

public class Main {
private static Port chatPort = new Chat();
private static Port serverPort = new Server();

public static void main(String[] args) {
// 聊天服务
chatPort.NET();

// 服务器
serverPort.SSH();
serverPort.NET();
serverPort.Tomcat();
serverPort.MySQL();
}
}

装饰模式

顾名思义,装饰模式就是给一个对象增加一些新的功能,而且是动态的,要求装饰对象和被装饰对象实现同一个接口,装饰对象持有被装饰对象的实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
interface Shape {
void draw();
}

/**
* 实现接口的实体类
*/
class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Shape: Rectangle...");
}
}

class Circle implements Shape {
@Override
public void draw() {
System.out.println("Shape: Circle...");
}
}

/**
* 创建实现了 Shape 接口的抽象装饰类。
*/
abstract class ShapeDecorator implements Shape {
protected Shape decoratedShape;

public ShapeDecorator(Shape decoratedShape) {
this.decoratedShape = decoratedShape;
}

@Override
public void draw() {
decoratedShape.draw();
}
}

/**
* 创建扩展自 ShapeDecorator 类的实体装饰类。
*/
class RedShapeDecorator extends ShapeDecorator {
public RedShapeDecorator(Shape decoratedShape) {
super(decoratedShape);
}

@Override
public void draw() {
decoratedShape.draw();
setRedBorder(decoratedShape);
}

private void setRedBorder(Shape decoratedShape) {
System.out.println("Border Color: Red");
}
}

/**
* 使用 RedShapeDecorator 来装饰 Shape 对象。
*/
public class Main {
public static void main(String[] args) {
Shape circle = new Circle();
Shape redCircle = new RedShapeDecorator(new Circle());
Shape redRectangle = new RedShapeDecorator(new Rectangle());
System.out.println("Circle with normal border");
circle.draw();

System.out.println("\nCircle of red border");
redCircle.draw();

System.out.println("\nRectangle of red border");
redRectangle.draw();
}
}

代理模式

代理模式指给一个对象提供一个代理对象,并由代理对象控制对原对象的引用。代理可以分为静态代理和动态代理。

通过代理模式,可以利用代理对象为被代理对象添加额外的功能,以此来拓展被代理对象的功能。可以用于计算某个方法执行时间,在某个方法执行前后记录日志等操作。

  1. 静态代理

​ 1)静态代理需要我们写出代理类和被代理类,而且一个代理类和一个被代理类一一对应。代理类和被代理类需要实现同一个接口,通过聚合使得代理对象中有被代理对象的引用,以此实现代理对象控制被代理对象的目的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/**
* 代理类和被代理类共同实现的接口
*/
interface IService {
void service();
}

/**
* 被代理类
*/
class Service implements IService{
@Override
public void service() {
System.out.println("被代理对象执行相关操作");
}
}

/**
* 代理类
*/
class ProxyService implements IService{
/**
* 持有被代理对象的引用
*/
private IService service;

/**
* 默认代理Service类
*/
public ProxyService() {
this.service = new Service();
}

/**
* 也可以代理实现相同接口的其他类
* @param service
*/
public ProxyService(IService service) {
this.service = service;
}

@Override
public void service() {
System.out.println("开始执行service()方法");
service.service();
System.out.println("service()方法执行完毕");
}
}

//测试类
public class Main {
public static void main(String[] args) {
IService service = new Service();
//传入被代理类的对象
ProxyService proxyService = new ProxyService(service);
proxyService.service();
}
}
  1. 动态代理

JDK 1.3 之后,Java通过java.lang.reflect包中的三个类Proxy、InvocationHandler、Method来支持动态代理。动态代理常用于有若干个被代理的对象,且为每个被代理对象添加的功能是相同的(例如在每个方法运行前后记录日志)。

动态代理的代理类不需要我们编写,由Java自动产生代理类源代码并进行编译最后生成代理对象。

创建动态代理对象的步骤:

​ (1)指明一系列的接口来创建一个代理对象
​ (2) 创建一个调用处理器(InvocationHandler)对象
​ (3)将这个代理指定为某个其他对象的代理对象
​ (4)在调用处理器的invoke()方法中采取代理,一方面将调用传递给真实对象,另一方面执行各种需要的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
* 代理类和被代理类共同实现的接口
*/
interface IService {
void service();
}

class Service implements IService{
@Override
public void service() {
System.out.println("被代理对象执行相关操作");
}
}

class ServiceInvocationHandler implements InvocationHandler {
/**
* 被代理的对象
*/
private Object srcObject;

public ServiceInvocationHandler(Object srcObject) {
this.srcObject = srcObject;
}

@Override
public Object invoke(Object proxyObj, Method method, Object[] args) throws Throwable {
System.out.println("开始执行"+method.getName()+"方法");
//执行原对象的相关操作,容易忘记
Object returnObj = method.invoke(srcObject,args);
System.out.println(method.getName()+"方法执行完毕");
return returnObj;
}
}

public class Main {
public static void main(String[] args) {
IService service = new Service();
Class<? extends IService> clazz = service.getClass();

IService proxyService = (IService) Proxy.newProxyInstance(clazz.getClassLoader(),
clazz.getInterfaces(), new ServiceInvocationHandler(service));
proxyService.service();
}
}

策略模式

策略模式定义了一系列算法,并将每个算法封装起来,使他们可以相互替换,且算法的变化不会影响到使用算法的客户。

需要设计一个接口,为一系列实现类提供统一的方法,多个实现类实现该接口,设计一个抽象类(可有可无,属于辅助类),提供辅助函数。策略模式的决定权在用户,系统本身提供不同算法的实现,新增或者删除算法,对各种算法做封装。

因此,策略模式多用在算法决策系统中,外部用户只需要决定用哪个算法即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
/**
* 抽象算法的策略类,定义所有支持的算法的公共接口
*/
abstract class Strategy {
/**
* 算法方法
*/
public abstract void AlgorithmInterface();
}

/**
* 具体算法A
*/
class ConcreteStrategyA extends Strategy {
//算法A实现方法
@Override
public void AlgorithmInterface() {
System.out.println("算法A的实现");
}
}

/**
* 具体算法B
*/
class ConcreteStrategyB extends Strategy {
/**
* 算法B实现方法
*/
@Override
public void AlgorithmInterface() {
System.out.println("算法B的实现");
}
}

/**
* 具体算法C
*/
class ConcreteStrategyC extends Strategy {
@Override
public void AlgorithmInterface() {
System.out.println("算法C的实现");
}
}

/**
* 上下文,维护一个对策略类对象的引用
*/
class Context {
Strategy strategy;

public Context(Strategy strategy) {
this.strategy = strategy;
}

public void contextInterface(){
strategy.AlgorithmInterface();
}
}

/**
* 客户端代码:实现不同的策略
*/
public class Main {
public static void main(String[] args) {
Context context;

context = new Context(new ConcreteStrategyA());
context.contextInterface();

context = new Context(new ConcreteStrategyB());
context.contextInterface();

context = new Context(new ConcreteStrategyC());
context.contextInterface();
}
}
]]>
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" cla
版本控制 https://blog.yongkj.cn/2021/02/01/java-version-control/ 2021-02-01T02:35:52.000Z 2023-05-30T04:26:16.716Z

为什么要使用版本管理工具

  1. 备份文件:我们在日常开发中,代码备份必不可少。可以采用移动硬盘、网盘的形式来备份,但是以这种形式也有很多弊端。我们程序员一天的工作量都写在几个文件里面,如果说因为一些未知因素导致丢失了,这种情况损失还是挺大的。
  2. 历史记录:即使我们上面所说,通过硬盘、网盘来备份,也只能保存当前最新的文件。而版本工具可以备份每一次所提交的代码,以及可以记录详细的修改信息,比如说某一行代码是谁在什么时候进行提交的。
  3. 版本回退:当我们在开发过程中,也难免一些刚刚入职的同事不小心对代码所造成的伤害难以弥补的时候,这个时候我们也可以通版本管理工具,将当前的代码回退到之前提交的某个版本。
  4. 多端共享:提供进行团队合作使用,总不能同事A写了一个方法,同事B需要用到这个方法,总不能让同事A拿着硬盘拷贝过去吧,所以采用管理工具只需将代码提交即可。

版本控制(Version Control System)作用

  1. 记录文件的所有历史变化
  2. 错误恢复到某个历史版本
  3. 多人协作开发编辑同一个文件

主流的版本控制产品

名称模型并发模式历史模式变更范围网络协议原子提交性
CVSClient-serverMergeChangesetFilePserver,sshNo
SVNClient-server3-way merge, recursive merge, octopus mergeChangeset and SnapshotTreecustom (svn), custom (svn) over ssh, HTTP and SSL (usingWebDAV)Yes
GitDistributedMerge or lockSnapshotTreecustom, custom over ssh, rsync, HTTP/HTTPS, email, bundlesYes
  1. 版本库模型(Repository model):描述了多个源码版本库副本间的关系,有客户端/服务器和分布式两种模式。在客户端/服务器模式下,每一用户通过客户端访问位于服务器的主版本库,每一客户机只需保存它所关注的文件副本,对当前工作副本(working copy)的更改只有在提交到服务器之后,其它用户才能看到对应文件的修改。而在分布式模式下,这些源码版本库副本间是对等的实体,用户的机器出了保存他们的工作副本外,还拥有本地版本库的历史信息。
  2. 并发模式(Concurrency model):描述了当同时对同一工作副本/文件进行更改或编辑时,如何管理这种冲突以避免产生无意义的数据,有排它锁和合并模式。在排它锁模式下,只有发出请求并获得当前文件排它锁的用户才能对对该文件进行更改。而在合并模式下,用户可以随意编辑或更改文件,但可能随时会被通知存在冲突(两个或多个用户同时编辑同一文件),于是版本控制工具或用户需要合并更改以解决这种冲突。因此,几乎所有的分布式版本控制软件采用合并方式解决并发冲突。
  3. 历史模式(History model):描述了如何在版本库中存贮文件的更改信息,有快照和改变集两种模式。在快照模式下,版本库会分别存储更改发生前后的工作副本;而在改变集模式下,版本库除了保存更改发生前的工作副本外,只保存更改发生后的改变信息。
  4. 变更范围(Scope of change):描述了版本编号是针对单个文件还是整个目录树。
  5. 网络协议(Network protocols):描述了多个版本库间进行同步时采用的网络协议。
  6. 原子提交性(Atomic commit):描述了在提交更改时,能否保证所有更改要么全部提交或合并,要么不会发生任何改变。

简而言之,各有优缺点,git要配合hub,可以避免分布式损坏。svn有权限控制,避免全被clone走。git适合纯代码,svn适合综合性文档管理,结合起来就完美。显然最大的不同在于git是分布式的。

SVN

  1. 优点:团队协作开发,代码集中化管理。
  2. 缺点:单点故障,必须联网工作,无法单机本地工作。

Git

  1. git的概念

​ 1)git是世界上目前最先进的分布式版本控制系统,致力于团队、个人进行项目版本管理,完美的解决难以比较代码、难以合并代码、难以取消修改、难以在写当前代码的过程中保存未完成的修改去修改线上版本的bug等的痛点。

​ 2)git是一个非常强大的工具,但作为一个git使用者来说,不用完全学习Git的知识点与命令,因为有的命令的使用频率非常的低甚至数年都不会用到。

  1. git的历史

​ 1)Linus在1991年创建了开源的Linux,从此,Linux系统不断发展,已经成为最大的服务器系统软件了。Linus虽然创建了Linux的核心,但Linux的壮大是靠全世界热心的志愿者参与的,这么多人在世界各地为Linux编写代码,那Linux的代码是如何管理的呢?

​ 2)事实是,在2002年以前,世界各地的志愿者把源代码文件通过diff的方式发给Linus,然后由Linus本人通过手工方式合并代码!你也许会想,为什么Linus不把Linux代码放到版本控制系统里呢?不是有CVS、SVN这些免费的版本控制系统吗?因为Linus坚定地反对CVS和SVN,这些集中式的版本控制系统不但速度慢,而且必须联网才能使用。有一些商用的版本控制系统,虽然比CVS、SVN好用,但那是付费的,和Linux的开源精神不符。不过,到了2002年,Linux系统已经发展了十年了,代码库之大让Linus很难继续通过手工方式管理了,社区的弟兄们也对这种方式表达了强烈不满,于是Linus选择了一个商业的版本控制系统BitKeeper,BitKeeper的东家BitMover公司出于人道主义精神,授权Linux社区免费使用这个版本控制系统。

​ 3)安定团结的大好局面在2005年就被打破了,原因是Linux社区牛人聚集,不免沾染了一些梁山好汉的江湖习气。开发Samba的Andrew试图破解BitKeeper的协议(这么干的其实也不只他一个),被BitMover公司发现了(监控工作做得不错!),于是BitMover公司怒了,要收回Linux社区的免费使用权。Linus可以向BitMover公司道个歉,保证以后严格管教弟兄们,嗯,这是不可能的。实际情况是这样的:Linus花了两周时间自己用C写了一个分布式版本控制系统,这就是Git!一个月之内,Linux系统的源码已经由Git管理了!牛是怎么定义的呢?大家可以体会一下。

​ 4)Git迅速成为最流行的分布式版本控制系统,尤其是2008年,GitHub网站上线了,它为开源项目免费提供Git存储,无数开源项目开始迁移至GitHub,包括jQuery,PHP,Ruby等等。历史就是这么偶然,如果不是当年BitMover公司威胁Linux社区,可能现在我们就没有免费而超级好用的Git了。

git是linux的创始人linus,在付费版本控制工具BitMover收回对Linux社区免费使用权利的时候,一怒之下花费两个星期的时间写出来的。(不要逼牛笔的人)

  1. 组成结构

​ 1)工作区:用来保存项目的元数据和对象数据库的地方。 这是 Git 中最重要的部分,从其它计算机克隆仓库时,拷贝的就是这里的数据。

​ 2)暂存区:保存了下次将提交的文件列表信息,一般在 Git 仓库目录中。有时候也被称作“索引”,不过一般说法还是叫暂存区域。

​ 3)版本库:也叫本地版本库,之所以说git 快,大部分提交都是对本地仓库而言的,不依赖网络,最后一次会推送的到远程仓库。

​ 4)远程仓库:可以看做是github,它是一个远程仓库,它提供web服务的 供大家方便下载、查看、提交、存储。文件的状态

  1. 文件状态

​ 1)新建文件状态为untracked

​ 2)add命令执行后状态变为staged

​ 3)已存在的文件状态为unmodified

​ 4)修改文件内容,文件状态变为modified

​ 5)commit提交,文件状态编程unmodifed。

Git 常用基础命令

  1. 配置用户信息
1
2
3
git config --global user.email "dxj1718874198@gmail.com"

git config --global user.name "YongKJ"
  1. 初始化本地 Git 仓库
1
git init
  1. 查看 Git 仓库状态
1
2
3
git status

git status -s #得到一种更为紧凑的格式输出

​ 1)新添加的未跟踪文件前面有 ?? 标记;

​ 2)新添加到暂存区中的文件前面有 A 标记;

​ 3)修改过的文件前面有 M 标记。 M 有两个可以出现的位置:

​ 4)出现在右边的 M 表示该文件被修改了但是还没放入暂存区,

​ 5)出现在靠左边的 M 表示该文件被修改了并放入了暂存区。

​ 6)加到暂存区之后,又修改了一次,修改之后,并没有添加到暂存区,前面有MM标记。

​ 7)以此类推,如果加到暂存区之后,被修改了两次,修改之后,并没有添加到暂存区,前面有MMM标记。…

  1. 文件添加到暂存区
1
2
3
4
5
6
7
8
9
git add file1

git add -A # 提交所有变化

git add -u # 提交被修改(modified)和被删除(deleted)文件,不包括新文件(new)

git add . # 提交新文件(new)和被修改(modified)文件,不包括被删除(deleted)文件

git add * # 会忽略.gitignore把任何文件都加入
  1. 将本地暂存的修改提交到版本库
1
2
3
4
5
git commit -m "add file1" file1

git commit -m '提交信息' # -m 参数是输入提交信息

git commit -a -m '提交信息' # -a 参数可以把还没有执行add命令的修改一起提交
  1. 查看文件的修改内容
1
git diff file1
  1. 查看git仓库提交日志
1
2
3
4
5
git log

git log --stat # 在git log 的基础上输出文件增删改的统计数据

git log --oneline # 简化git log的默认的输出,仅仅输出commit hash 前7个字符串和commit
  1. 查看所有分支的所有操作记录
1
git reflog # 包括已经被删除的 commit 记录和 reset 的操作
  1. 忽略文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
.DS_Store
node_modules
/dist

# local env files
.env.local
.env.*.local

# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

​ 1)创建一个名为 .gitignore 的文件,列出要忽略的文件模式

​ 2)星号(*)匹配零个或多个任意字符;

​ 3)[abc] 匹配任何一个列在方括号中的字符(这个例子要么匹配一个 a,要么匹配一个 b,要么匹配一个c);

​ 4)问号(?)只匹配一个任意字符;

​ 5)如果在方括号中使用短划线分隔两个字符,表示所有在这两个字符范围内的都可以匹配(比如 [0-9]表示匹配所有 0 到 9 的数字)。

​ 6)使用两个星号(*) 表示匹配任意中间目录,比如a/**/z 可以匹配 a/z, a/b/z 或a/b/c/z等

  1. 工作区和暂存区

​ 1)工作区有一个隐藏目录.git,这个不算工作区,而是Git的版本库。

​ 2)Git的版本库里存了很多东西,其中最重要的就是称为stage(或者叫index)的暂存区,还有Git为我们自动创建的第一个分支master,以及指向master的一个指针叫HEAD。

​ 3)第一步是用git add把文件添加进去,实际上就是把文件修改添加到暂存区;

​ 4)第二步是用git commit提交更改,实际上就是把暂存区的所有内容提交到当前分支。

  1. 丢弃工作区的修改
1
git checkout -- file1
  1. 撤销暂存区的修改(unstage),重新放回工作区
1
git reset HEAD
  1. 版本回退
1
2
3
git reflog

git reset --hard ID号码
  1. 误删,还原
1
git checkout file1
  1. 删除版本库中文件
1
2
3
git rm file1

git commit -m "del file1"
  1. 创建分支
1
git branch <branch_name>
  1. 查看分支
1
2
3
git branch # 查看所有分支

git branch -v # 查看所有分支详情
  1. 切换分支
1
2
3
git checkout <branch_name> # 切换到指定分支

git checkout -b <branch_name> # 创建并切换到指定分支
  1. 删除分支
1
2
3
git branch -d <branch_name> # 删除一个干净的分支(即相对当前分支而言该分支没有新的提交记录)

git branch -D <branch_name> # 强制删除一个分支,该分支有没有合并到当前分支的提交记录

注意:删除分支前都需要先切换到其他分支才能进行删除操作

  1. 分支恢复
1
2
3
git reflog # 查找该分支指向的commitId

git branch <branch_name> <hash_val> # 根据指定commit创建新分支
  1. 重命名分支
1
git branch -m <branch_name> newname
  1. 分支合并
1
git merge <branch_name> # 将指定分支合并到当前分支,如果两个分支没有产生分叉情况,那么会进行快速合并,即fast-forward方式
  1. 分支合并细节
1
git merge --no-ff -m "msg" <branch_name> # 合并分支时禁用Fast forward模式
  1. 冲突解决

​ 1)当对分叉分支进行合并时,如果两个分支都对同一文件进行了修改,那么合并时就有可能会产生冲突情况。

​ 2)如果两个分支对同一文件的修改是有规律的,比如对不同地方的修改,那么git工具可以实现自动合并

​ 3)如果无法自动合并,则需要对冲突文件进行手动修改,修改完成后使用git add表示冲突已经解决,然后使用git commit进行提交

  1. 分支暂存
1
2
3
git stash # 将工作暂存

git stash list # 所有的暂存状态
  1. 从暂存区之中进行恢复,有两种处理方式:

​ 1)先恢复,而后再删除暂存

1
2
3
git stash apply

git stash drop

​ 2)恢复的同时也将stash内容删除

1
git stash pop
  1. 本地仓库推送到远程仓库
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
git remote add origin 远程仓库地址 # 将本地仓库和远程仓库进行关联

git push -u origin master # 把本地仓库的项目推送到远程仓, 第一次推送

git push origin master # 第一次推送后,直接使用该命令即可推送修改

git push origin 本地分支:远端希望创建的分支

git push origin master:my_remote_new_branch # 远端即可创建新的分支my_remote_new_branch,提交本地修改

git remote -v # 查看远程仓库地址命令

git remote set-url origin url # 直接修改远程仓库地址

git remote rm origin # 删除本地远程仓库地址

git remote add origin url # 然后添加新的仓库地址

git config --global http.proxy 'http://127.0.0.1:58591'

git config --global https.proxy 'socks5://127.0.0.1:51837'

git config --global --unset http.proxy

git config --global --unset https.proxy

注意有坑: 新建远程仓库的时候如果你勾选了Initialize this repository with a README(就是创建仓库的时候自动给你创建一个README文件),那么到了第7步你将本地仓库内容推送到远程仓库的时候就会报一个failed to push some refs to 远程仓库地址的错。

原因: 由于你新创建的那个仓库里面的README.md文件不在本地仓库目录中,这时我们可以通过git pull --rebase origin master命令先将内容合并,此时再push就能成功了。

  1. 克隆远程仓库
1
git clone 远程仓库地址 # 从远程服务器克隆一个一模一样的版本库到本地,复制的是整个版本库
  1. 获取最新版本远程仓库
1
2
3
git fetch

git pull # 从远程获取最新版本并merge(合并)到本地,git pull = git fetch + git merge,git fetch更安全一些
  1. 清理本地仓库(以Hexo静态博客为例)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 由于现在的.git文件夹里累积了太多辣鸡数据,甚至已经超过博客本身的大小了,于是打算从头开始重新部署一下博客的所有静态资源。

git checkout --orphan new_branch # 新建空白分枝

npm run build # 生成静态文件

git add . # 添加文件到暂存区

git commit -m '提交信息' # 提交分枝更改

git branch -D master # 删除旧主分枝

git branch -m master # 改名为主分枝

git push -f origin master # 强制推至上游

# 干净的博客出现了!
]]>
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" cla
数据库 https://blog.yongkj.cn/2021/01/31/java-database/ 2021-01-31T12:55:52.000Z 2023-05-30T04:26:16.692Z

数据库事务

  1. 定义

​ 1)数据库事务是构成单一逻辑工作单元的操作集合,包含一个或多个数据库操作,这些操作构成一个逻辑上的整体。

​ 2)一个典型的数据库事务如下所示

1
2
3
4
BEGIN TRANSACTION  //事务开始
SQL1
SQL2
COMMIT/ROLLBACK //事务提交或回滚
  1. 事务的ACID特性

​ 1)**原子性(Atomicity)**:事务中的所有操作作为一个整体像原子一样不可分割,要么全部成功,要么全部失败。

​ 2)**一致性(Consistency)**:事务的执行结果必须使数据库从一个一致性状态到另一个一致性状态。

​ 3)**隔离性(Isolation)**:并发执行的事务不会相互影响,其对数据库的影响和它们串行执行时一样。比如多个用户同时往一个账户转账,最后账户的结果应该和他们按先后次序转账的结果一样。

​ 4)**持久性(Durability)**:事务一旦提交,其对数据库的更新就是持久的。任何事务或系统故障都不会导致数据丢失。

一致性状态:

  1. 系统的状态满足数据的完整性约束(主码,参照完整性,check约束等)

  2. 系统的状态反应数据库本应描述的现实世界的真实状态,比如转账前后两个账户的金额总和应该保持不变。

数据库的脏读、幻读、不可重复读

  1. 脏读:

​ 1)指一个事务A正在访问数据,并且对该数据进行了修改,但是这种修改还没有提交到数据库中(也可能因为某些原因Rollback了)。这时候另外一个事务B也访问这个数据,然后使用了这个被A修改的数据,那么这个数据就是脏的,并不是数据库中真实的数据。这就被称作脏读。

​ 2)解决办法:把数据库事务隔离级别调整到READ_COMMITTED

​ 3)即让用户在更新时锁定数据库,阻止其他用户读取,直到更新全部完成才让你读取。

  1. 幻读:

​ 1)指一个事务A对一个表中的数据进行了修改,而且该修改涉及到表中所有的数据行;同时另一个事务B也在修改表中的数据,该修改是向表中插入一行新数据。那么经过这一番操作之后,操作事务A的用户就会发现表中还有没修改的数据行,就像发生了幻觉一样。这就被称作幻读。

​ 2)解决办法:把数据库事务隔离级别调整到SERIALIZABLE_READ

  1. 不可重复读:

​ 1)指在一个事务A内,多次读同一个数据,但是事务A没有结束时,另外一个事务B也访问该同一数据。那么在事务A的两次读数据之间,由于事务B的修改导致事务A两次读到的数据可能是不一样的。这就发生了在一个事务内两次读到的数据不一样,这就被称作不可重复读。

​ 2)解决办法:把数据库事务隔离级别调整到REPEATABLE_READ

注:

级别高低:脏读 < 不可重复读 < 幻读

所以设置了最高级别的SERIALIZABLE_READ就不需要设置其他的了,即解决了幻读问题那么脏度和不可重复读自然就都解决了。

索引

  1. 为什么需要索引?

​ 1)索引其实是一种数据结构,能够帮助我们快速的检索数据库中的数据,解决效率低问题

  1. 优点

​ 1)可以通过建立唯一索引或者主键索引,保证数据库表中每一行数据的唯一性

​ 2)建立索引可以大大提高检索的数据,以及减少表的检索行数

​ 3)在表连接的连接条件,可以加速表与表直接的相连

​ 4)在分组和排序字句进行数据检索,可以减少查询时间中分组和排序时所消耗的时间(数据库的记录会重新排序)

​ 5)建立索引,在查询中使用索引 可以提高性能

  1. 索引采取什么样的结构

​ 1)hash(哈希)

​ 2)二叉树

​ 3)红黑树

​ 4)B+树

SQL基础

  1. 多表联合查询
1
SELECT * FROM py_user u, py_paths p where u.userUUID = p.userUUID;
  1. 内连接查询(与多表联合查询效果一样)
1
SELECT * FROM py_user u INNER JOIN py_paths p ON u.userUUID = p.userUUID;
  1. 左外连接查询 (左边表中的数据优先全部显示)
1
SELECT * FROM py_user u LEFT JOIN py_paths p ON u.userUUID = p.userUUID;
  1. 右外连接查询 (右边表中的数据优先全部显示)
1
SELECT * FROM py_user u RIGHT JOIN py_paths p ON u.userUUID = p.userUUID;
  1. 全连接查询(显示左右表中全部数据,使用 UNION 可以间接实现 full JOIN 功能)
1
2
3
SELECT * FROM py_user u LEFT JOIN py_paths p ON u.userUUID = p.userUUID;
UNION
SELECT * FROM py_user u RIGHT JOIN py_paths p ON u.userUUID = p.userUUID;
  1. 子查询(嵌套查询): 查多次,多个select

​ 1)第一次的查询结果可以作为第二次的查询的条件或者表名使用.

​ 2)子查询中可以包含:IN、NOT IN、ANY、ALL、EXISTS 和 NOT EXISTS等关键字. 还可以包含比较运算符:= 、 !=、> 、< 等

1
select * from (select * from person) as 表名;
  1. ORDER BY 排序,ASC默认升序,降序后面接”DESC”即可
1
SELECT * FROM table_name ORDER BY column_name1, column_name2 DESC;
  1. GROUP BY 分组,count(*) 包括所有列,返回表中的记录数
1
2
3
SELECT u.userName, COUNT(*) FROM py_user u, py_paths p WHERE u.userUUID = p.userUUID GROUP BY regEmail;

SELECT u.userName, COUNT(*) FROM py_user u INNER JOIN py_paths p ON u.userUUID = p.userUUID GROUP BY regEmail;
  1. AVG() 求平均工资
1
SELECT AVG(salary) FROM person;
  1. MAX() 求最大工资
1
SELECT MAX(salary) FROM person;
  1. MIN() 求最小工资
1
SELECT MIN(salary) FROM person;
  1. HAVING 和 WHERE 的区别

​ 1)作用的对象不同。WHERE 子句作用于表和视图,HAVING 子句作用于组。

​ 2)WHERE 在分组和聚集计算之前选取输入行(因此,它控制哪些行进入聚集计算), 而 HAVING 在分组和聚集之后选取分组的行。

​ 3)WHERE 子句不能包含聚集函数,HAVING 子句总是包含聚集函数。

​ 4)HAVING 一般跟在 GROUP BY 之后,执行记录组选择的一部分来工作的。WHERE 则是执行所有数据来工作的

​ 5)HAVING 可以用聚合函数,如 HAVING SUM(salary) > 1000

1
SELECT city FROM weather WHERE temp_lo = (SELECT MAX(temp_lo) FROM weather);
]]>
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" cla
线程 https://blog.yongkj.cn/2021/01/30/java-thread/ 2021-01-30T12:16:52.000Z 2023-05-30T04:26:16.712Z

进程与线程的关系

  1. 进程是操作系统资源分配的基本单位
  2. 线程是cpu调度和分配的基本单位
  3. 一个进程可以有多个线程
  4. 进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率
  5. 线程的划分尺度小于进程,使得多线程程序的并发性高

线程的基本状态

  1. 新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();
  2. 就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;
  3. 运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
  4. 阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:
  • 等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
  • 同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
  • 其他阻塞 – 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
  1. 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

线程创建方式

  1. 继承Thread类

​ 1)创建一个线程子类继承Thread类

​ 2)重写run() 方法,把需要线程执行的程序放入run方法,线程启动后方法里的程序就会运行

​ 3)创建该类的实例,并调用对象的start()方法启动线程

1
2
3
4
5
6
7
8
9
10
11
12
public class ThreadDemo extends Thread {
@Override
public void run() {
super.run();
System.out.println("需要运行的程序。。。。。。。。");
}

public static void main(String[] args) {
Thread thread = new ThreadDemo();
thread.start();
}
}
  1. 实现Runnable接口

​ 1)定义一个线程类实现Runnable接口,并重写该接口的run()方法,方法中依然是包含指定执行的程序

​ 2)创建一个Runnable实现类实例,将其作为target参数传入,并创建Thread类实例

​ 3)调用Thread类实例的start()方法启动线程

1
2
3
4
5
6
7
8
9
10
11
12
public class RunnableDemo implements Runnable{
@Override
public void run() {
System.out.println("我是Runnable接口......");
}

public static void main(String[] args) {
RunnableDemo demo = new RunnableDemo();
Thread thread = new Thread(demo);
thread.start();
}
}
  1. 使用Callable和Future创建线程

​ 1)创建Callable接口的实现类,实现call() 方法

​ 2)创建Callable实现类实例,通过FutureTask类来包装Callable对象,该对象封装了Callable对象的call()方法的返回值

​ 3)将创建的FutureTask对象作为target参数传入,创建Thread线程实例并启动新线程

​ 4)调用FutureTask对象的get方法获取返回值。

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

private StudentService studentService;

public ThreadUtil(StudentService studentService) {
this.studentService = studentService;
}

@Override
public List<Student> call() throws Exception {
List<Student> students = studentService.allStudentsList();
return students;
}
}

public class ThreadTest {

private StudentService studentService = new StudentService();

public void testThread1() {
// 1.获取FutureTask对象
ThreadUtil threadUtil = new ThreadUtil(studentService);
FutureTask futureTask = new FutureTask(threadUtil);

// 2.开启线程
new Thread(futureTask).start();

try {
// 3.使用Futura#get()方法获取线程的返回值
List<Student> studentList = (List<Student>) futureTask.get();
studentList.forEach(student -> System.out.println(student));
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}

线程同步

  1. wait():使一个线程处于等待状态,并且释放所持有的对象的lock。
  2. sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException异常。
  3. notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。
  4. notifyAll():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争。
  5. yield():暂停当前线程,以便其他线程有机会执行,不过不能指定暂停的时间,并且也不能保证当前线程马上停止。yield方法只是将Running状态转变为Runnable状态。
  6. join():父线程等待子线程执行完成后再执行,换句话说就是将异步执行的线程合并为同步的线程。
  7. synchronized:Java中的关键字,是一种同步锁。用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。
  8. volatile:保证了变量的可见性(visibility)。被volatile关键字修饰的变量,如果值发生了变更,其他线程立马可见,避免出现脏读的现象
  9. lock:加锁限定线程间的互斥,保持线程同步实现线程安全

Java 并发编程:线程间的协作(wait/notify/sleep/yield/join)

volatile和synchronized的区别

  1. volatile本质是在告诉jvm当前变量在寄存器中的值是不确定的,需要从主存中读取,synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住

  2. volatile仅能使用在变量级别,synchronized则可以使用在变量、方法

  3. volatile仅能实现变量的修改可见性,而synchronized则可以保证变量的修改可见性和原子性

  4. volatile不会造成线程的阻塞,而synchronized可能会造成线程的阻塞

  5. 当一个域的值依赖于它之前的值时,volatile就无法工作了,如n=n+1,n++等。如果某个域的值受到其他域的值的限制,那么volatile也无法工作,如Range类的lower和upper边界,必须遵循lower<=upper的限制。

  6. 使用volatile而不是synchronized的唯一安全的情况是类中只有一个可变的域。

synchronized与lock的异同

  1. Lock能完成synchronized所实现的所有功能
  2. Lock有比synchronized更精确的线程语义和更好的性能。synchronized会自动释放锁,而Lock一定要求程序员手工释放,并且必须在finally从句中释放,否则会引起死锁。
  3. synchronized既可以加在方法上,也可以加载特定代码块上,而lock需要显示地指定起始位置和终止位置。
  4. synchronized是托管给JVM执行的,lock的锁定是通过代码实现的,它有比synchronized更精确的线程语义。
  5. lock接口的实现类ReentrantLock,不仅具有和synchronized相同的并发性和内存语义,还多了超时的获取锁、定时锁、等候和中断锁等。
  6. 在竞争不是很激烈的情况下,synchronized的性能优于ReentrantLock,竞争激烈的情况下synchronized的性能会下降的非常快,而ReentrantLock则基本不变。

sleep() 和 yield() 的区别

  1. sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会;yield()方法只会给相同优先级或更高优先级的线程以运行的机会;
  2. 线程执行sleep()方法后转入阻塞(blocked)状态,而执行yield()方法后转入就绪(ready)状态;
  3. sleep()方法声明抛出InterruptedException,而yield()方法没有声明任何异常;
  4. sleep()方法比yield()方法(跟操作系统CPU调度相关)具有更好的可移植性。

sleep() 和 wait() 有的区别

  1. sleep是线程类(Thread)的方法,导致此线程暂停执行指定时间,把执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用sleep不会释放对象锁。
  2. wait是Object类的方法,对此对象调用wait方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。

线程池

  1. 定义

​ 1)线程池顾名思义就是事先创建若干个可执行的线程放入一个池(容器)中,需要的时候从池中获取线程不用自行创建,使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销。

  1. 优点

​ 1)降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

​ 2)提高响应速度。当任务到达时,任务可以不需要等到线程创建就能执行。

​ 3)提高线程的可管理性,线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。

  1. 分类

​ 1)newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

​ 2)newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

​ 3)newScheduledThreadPool:创建一个可定期或者延时执行任务的定长线程池,支持定时及周期性任务执行。

​ 4)newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

  1. 状态

​ 1)Running:能接受新任务以及处理已添加的任务

​ 2)Shutdown:不接受新任务,可以处理已添加的任务

​ 3)Stop:不接受新任务,不处理已添加的任务,并且中断正在处理的任务

​ 4)Tidying:所有任务已终止,ctl记录为”任务数量”为0,ctl负责记录线程池的运行状态与活动线程数量

​ 5)Terminated:线程池彻底终止

]]>
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" cla
集合 https://blog.yongkj.cn/2021/01/29/java-assemble/ 2021-01-29T12:38:52.000Z 2023-05-30T04:26:16.681Z

集合的分类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Collection 接口的接口 对象的集合(单列集合)
├——-List 接口:元素按进入先后有序保存,可重复
│—————-├ LinkedList 接口实现类, 链表, 插入删除, 没有同步, 线程不安全
│—————-├ ArrayList 接口实现类, 数组, 随机访问, 没有同步, 线程不安全
│—————-└ Vector 接口实现类 数组, 同步, 线程安全
│ ———————-└ Stack 是Vector类的实现类
└——-Set 接口: 仅接收一次,不可重复,并做内部排序
├—————-└HashSet 使用hash表(数组)存储元素
│————————└ LinkedHashSet 链表维护元素的插入次序
└ —————-TreeSet 底层实现为二叉树,元素排好序

Map 接口 键值对的集合 (双列集合)
├———Hashtable 接口实现类, 同步, 线程安全
├———HashMap 接口实现类 ,没有同步, 线程不安全
│—————–├ LinkedHashMap 双向链表和哈希表实现
│—————–└ WeakHashMap
├ ——–TreeMap 红黑树对所有的key进行排序
└———IdentifyHashMap

List、Map、Set 接口存取元素特点

  1. List 以特定索引来存取元素,可以有重复元素。

  2. Set 不能存放重复元素(用对象的equals()方法来区分元素是否重复)。

  3. Map 保存键值对(key-value pair)映射,映射关系可以是一对一或多对一。

Collection 和 Collections 的区别

  1. Collection是集合类的上级接口,继承与它的接口主要有Set 和 List.
  2. Collections是针对集合类的一个帮助类,它提供一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作。

ArrayList 和 LinkedList 的区别

  1. ArrayList 和 LinkedList 都实现了List接口
  2. ArrayList是实现了基于动态数组的数据结构,LinkedList实现了基于链表的数据结构。
  3. 对于随机访问get和set,ArrayList绝对优于LinkedList,因为LinkedList要移动指针。
  4. 对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。
  5. ArrayList和LinkedListed都是非线程安全的,但可通过工具类Collections中的synchronizedList方法转换成线程安全容器

HashMap 和 Hashtable 的区别

  1. 继承的父类
  • 都实现了Map、Cloneable(可复制)、Serializable(可序列化)接口。
  • HashMap: 继承自AbstractMap类。
  • HashTable: 继承自Dictionary类。
  1. HashMap 是线程不安全的,效率高;HashTable 线程安全,效率低。

  2. HashMap允许键和值是null,而Hashtable不允许键或者值是null

Iterator 和 ListIterator 的区别

  1. Iterator可用来遍历Set和List集合,但是ListIterator只能用来遍历List。
  2. Iterator对集合只能是前向遍历,ListIterator既可以前向也可以后向。
  3. ListIterator实现了Iterator接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等。

Java集合类里面最基本的接口

  • Collection:代表一组对象,每一个对象都是它的子元素。
  • Set:不包含重复元素的Collection。
  • List:有顺序的collection,并且可以包含重复元素。
  • Map:可以把键(key)映射到值(value)的对象,键不能重复。

ConcurrentHashMap

  1. ConcurrentHashMap 类中包含两个静态内部类 HashEntry 和 Segment
  2. HashEntry 用来封装映射表的键 / 值对
  3. Segment 用来充当锁的角色
  4. Concurrenthashmap线程安全的

TreeMap

  1. TreeMap是一个有序的key-value集合,基于红黑树(Red-Black tree)的 NavigableMap实现。
  2. 该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator进行排序,具体取决于使用的构造方法。
  3. 红黑树性质:
  • 每个节点要么是红色,要么是黑色。
  • 根节点永远是黑色的。
  • 所有的叶节点都是空节点(即 null),并且是黑色的。
  • 每个红色节点的两个子节点都是黑色。(从每个叶子到根的路径上不会有两个连续的红色节点)
  • 从任一节点到其子树中每个叶子节点的路径都包含相同数量的黑色节点。

HashMap

  1. 如果HashMap的key是自定义的类,就必须重写hashcode()和equals()。
  2. HashMap在JDK1.8的版本中引入了红黑树结构做优化
  3. 当链表元素个数大于等于8时,链表转换成树结构,小于等于6时,树结构还原成链表。
  4. 如果一个HashMap不停的插入、删除元素,并且链表个数在8左右徘徊,就会频繁的发生树转链表、链表转树,效率会很低
]]>
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" cla
Education https://blog.yongkj.cn/2020/11/23/education/ 2020-11-23T07:34:03.000Z 2023-05-30T04:26:16.664Z

  Chinese families attach great importance to their children’s education. Many parents believe that they should work hard to ensure that their children are well educated. Not noly are they perfectly willing to invest in their children’s education, but they also spend a lot of time urging their children to learn. Most parents hope that their children will attend a prestigious university. Due to the reform and opening up, more and more parents can send their children abroad to study or participate in international exchange programs to broaden their horizons. Through these efforts, they expect their children to grow healthily and contribute to the development and prosperity of the country.

  中国家庭十分重视孩子的教育。许多父母认为应该努力工作,确保孩子受到良好教育。他们不仅非常情愿为孩子的教育投资,而且花很多时间督促他们学习。多数家长希望孩子能上名牌大学。由于改革开放,越来越多的家长能送孩子到国外学习或参与国际交流项目,以拓宽其视野。通过这些努力,他们期望孩子健康成长,为国家的发展和繁荣作出贡献。

]]>
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" cla
A Letter to a Foreign Friend Who Wants to Teach English in China https://blog.yongkj.cn/2020/11/23/teach-English-in-China/ 2020-11-23T06:46:52.000Z 2023-05-30T04:26:16.742Z

Dear Tom,
  On hearing that you are planning to teach English in China and inquire which city to work in, I’d like to recommend our capital city Beijing to you, which is an international metropolis.
  The reasons why I recommend Beijing can be listed as follows. First of all, there are a lot of English-speaking foreigners in Beijing, which could help you adapt to life here very quickly. Furthermore, as the capital of several dynasties, Beijing has a profound cultural background, so you can better experience the extensive and profound traditional Chinese culture in Beijing. Most importantly, parents in Beijing attach great importance to their children’s English learning and many people who work in multinational companies alse need to learn English.
  I truly hope that you can come to Beijing to start your teaching life and I’m looking forward to your arrival. If you have any question about the city, please feel free to contact me for further information.

Yours sincerely,
Li Ming

]]>
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" cla
Subway https://blog.yongkj.cn/2020/11/23/subway/ 2020-11-23T01:10:51.000Z 2023-05-30T04:26:16.739Z

  In recent years, more and more cities in China have begun to build subways. The development of subways can help reduce traffic congestion and air pollution in cities. The subway has the advantages of safety, speed and comfort. More and more people choose the subway as the main means of transportation to work or school every day. Nowadays, it is becoming more and more convenient to take the subway in China. In some cities, passengers can use a card or a mobile phone to take the subway. Many local elderly citizens can also take the subway for free.

  近年来,中国有越来越多的城市开始建设地铁。发展地铁有助于减少城市的交通拥挤和空气污染。地铁具有安全、快捷和舒适的优点。越来越多的人选择地铁作为每天上班或上学的主要交通工具。如今,在中国乘坐地铁正变得越来越方便。在有些城市里,乘客只需用卡或手机就可以乘坐地铁。许多当地老年市民还可以免费乘坐地铁。

]]>
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" cla
The Important of Speaking Ability and How to Develop It https://blog.yongkj.cn/2020/11/23/improve-speaking-skills/ 2020-11-23T00:18:30.000Z 2023-05-30T04:26:16.673Z

  As we all know, proficiency in speaking is necessary for us to become well-rounded communicators. However, the capacity to put words together in a meaningful way to reflect thoughts, opinions, and feelings is not something we’re born with but needs some techniques and practice.
  Firstly, build confidence and concentrate on getting our message across, which help us gain the attention of the audience in return. Secondly, experiment with the things we know well instead of challenging ourselves with difficult words since fluency appears more important during oral communication. Lastly, create some opportunities to practice like narrating our daily life to ourselves or maintaining a regular chat with friends.
  To sum up, only by being confident enough and using efficient methods can we enhance our speaking ability. Follow the steps to improve our speaking skills in order to achieve a higher standard in communication.

]]>
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" cla
Buses https://blog.yongkj.cn/2020/11/06/travel-by-bus/ 2020-11-06T01:26:49.000Z 2023-05-30T04:26:16.744Z

  Buses used to be the main means of transportation for the Chinese people. In recent years, with the number of private cars increasing, cities have been facing increasingly severe traffic problems. To encourage more people to travel by bus, many cities have been making efforts to improve bus services. Bus facilities have been continuously renovated, and bus speed has also increased dramatically. However, bus fares are still cheap. Now, in most cities, many local elderly citizens can take a bus for free.

  公交车曾是中国人出行的主要交通工具。近年来,由于私家车数量不断增多,城市的交通问题越来越严重。许多城市为了鼓励更多人乘坐公交车出行,一直在努力改善公交车的服务质量。车辆的设施不断更新,车速也有了显著提高。然而,公交车的票价却依然相当低廉。现在,在大多数城市,许多当地老年市民都可以免费乘坐公交车。

]]>
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" cla
The Importance of Writing Ability and How to Develop It https://blog.yongkj.cn/2020/11/06/improve-writing-skills/ 2020-11-06T01:00:29.000Z 2023-05-30T04:26:16.677Z

  As the most productive and communicative way to express ourselves, writing is attached great importance to in all ages. Whether we want to improve our writing skills as a creative writer or simply perfect our skills for schoolwork, we can take some steps to learn how to be a better writer.
  Firstly, in order to make our writing creative and imaginative, brainstorming is one of the key elements to build up a unique topic. Don’t hesitate to take down all the ideas that come into our mind. Secondly, a good development of our writing is based on a clear structure or paragraph organization. Even a simple outline will help us see the big picture and save us hours of rewriting. Finally, diversity of vocabulary and grammar used in writing is highly recommended for the reason that one of the most common manifestations of bad writing is overuse or reuse of simple language.
  To sum up, we should take practice and expand our knowledge to become a great writer. With ecough hard work and scientific techniques, we will amaze not only ourselves but also anybody else.

]]>
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" cla
Traveling by Plane https://blog.yongkj.cn/2020/11/05/traveling-by-plane/ 2020-11-05T08:30:04.000Z 2023-05-30T04:26:16.747Z

  In the past, traveling by plane was unimaginable for most Chinese people. Today, with development of China’s economy and the improvement of people’s living standards, more and more Chinese people, including many farmers and migrant workers, can travel by air. They can fly to all major cities, and many other cities are also planning to build airports. Air services continue to improve, and there are often cheap flights. In recent years, the number of people choosing to travel by air during holidays has been increasing.

  过去,乘飞机出行对大多数中国人来说是难以想象的。如今,随着经济的发展和生活水平的提高,越来越多的中国人包括许多农民和外出务工人员都能乘飞机出行。他们可以乘飞机到达所有大城市,还有很多城市也在筹建机场。航空服务不断改进,而且经常会有廉价机票。近年来,节假日期间选择乘飞机外出旅游的人数在不断增加。

]]>
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" cla