Content Table

Java 操作 Yaml

Java 里可以使用类 Yaml 序列化和反序列化 Bean,有 2 个反序列化 Yaml 文件为 Java Bean 的方式:

  • 在 Yaml 的构造函数中指定 Bean 的类型,然后调用方法 load() 生成 Bean:

    1
    2
    Yaml yaml = new Yaml(new Constructor(User.class));
    User user = yaml.load(inputStream);
  • 使用 Yaml 的默认构造函数,然后调用方法 loadAs() 生成 Bean:

    1
    2
    Yaml yaml = new Yaml();
    User user = yaml.loadAs(inputStream, User.class);
  • 需要注意的是,下面的代码报错:

    1
    2
    Yaml yaml = new Yaml();
    User user = yaml.load(inputStream); // Error: java.util.LinkedHashMap cannot be cast to yaml.User

下面就以类 User 以及类把 User 作为属性时的多种情况为例进行演示,其中:

  • Yaml 反序列化为 User 的代码是一样的
  • Yaml 文件的写法需要注意

快捷键复制粘贴组件

访问剪贴板

使用 navigator.clipboard 对象访问系统的剪贴板:

  • readText: 读取剪贴板的内容
  • writeText: 设置剪贴板的内容
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>System Clipboard</title>
</head>

<body>
<button id="button-copy">Copy</button>
<button id="button-paste">Paste</button>

<script>
// 设置系统剪贴板的内容
document.querySelector('#button-copy').addEventListener('click', () => {
navigator.clipboard.writeText('Hello');
});

// 读取系统剪贴板的内容
document.querySelector('#button-paste').addEventListener('click', () => {
navigator.clipboard.readText().then(text => {
console.log(text);
});
});
</script>
</body>
</html>

提示: IE 不支持 Navigator.clipboard,但 Edge 支持。

Vue :key

我们都知道使用 v-for 渲染数组时都要给每个元素绑定一个 key,官方文档也说的很清 (挠) 楚 (头):

当 Vue 正在更新使用 v-for 渲染的元素列表时,它默认使用“就地更新”的策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染。

key 的特殊 attribute 主要用在 Vue 的虚拟 DOM 算法,在新旧 nodes 对比时辨识 VNodes。如果不使用 key,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法。而使用 key 时,它会基于 key 的变化重新排列元素顺序,并且会移除 key 不存在的元素。

相同父元素的子元素必须有独特的 key,重复的 key 会造成渲染错误。

需要记住一点: 当用来创建 DOM 元素的数据变化后,就会更新 DOM (更新了才能在界面上看到),为了提高效率,vue 使用了 key 来判断是复用已有的 DOM 还是创建新的 DOM (在同一个 parent DOM element 下进行比较):

  • 如果能找到相同 key 的 DOM 元素,则更新它的内容
  • 如果找不到相同 key 的 DOM 元素,则删除旧的 DOM 元素,新创建一个

上面的内容,懂的人一看就懂,不懂的人仍然是一头雾水。下面我们以一个很简单的修改用户名的例子演示一下应该就能明白什么时候复用,什么时候创建了。

JS 的正则表达式

JS 中可以用下面 3 种方式使用正则表达式获取数据:

  • RegExp.exec()
  • String.match()
  • String.replace(): 这个用法比较奇葩,但是我喜欢

下面就简单介绍下每一个的用法。

常用 SQL

创建数据库

1
CREATE DATABASE ebag DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci

增加新列

1
ALTER TABLE table_name ADD COLUMN column_name varchar(64)

分页查询

MySQL 中使用 LIMIT 进行分页,第一个参数是起始位置 offset,从 0 开始,第二个参数是要取多少条记录

1
SELECT * FROM question WHERE subject_code='XXX' LIMIT 0, 30

数据库规范

标准化和规范化

数据的标准化有助于消除数据库中的数据冗余。标准化有好几种形式,但 Third Normal Form (3NF) 通常被认为在性能、扩展性和数据完整性方面达到了最好平衡。简单来说,遵守 3NF 标准的数据库的表设计原则是:

  • 第一范式 (1NF) 无重复的列

    所谓第一范式 (1NF) 是指数据库表的每一列都是不可分割的基本数据项,同一列中不能有多个值,即实体中的某个属性不能有多个值或者不能有重复的属性。如果出现重复的属性,就可能需要定义一个新的实体,新的实体由重复的属性构成,新实体与原实体之间为一对多关系。在第一范式 (1NF) 中表的每一行只包含一个实例的信息。简而言之,第一范式就是无重复的列。说明:在任何一个关系数据库中,第一范式 (1NF) 是对关系模式的基本要求,不满足第一范式 (1NF) 的数据库就不是关系数据库。

  • 第二范式 (2NF) 属性完全依赖于主键 (每个表要定义主键,如无意义自增长 id)

    第二范式 (2NF) 是在第一范式 (1NF) 的基础上建立起来的,即满足第二范式 (2NF) 必须先满足第一范式 (1NF)。第二范式 (2NF) 要求数据库表中的每个实例或行必须可以被唯一地区分。为实现区分通常需要为表加上一个列,以存储各个实例的惟一标识。例如员工信息表中加上了员工编号 (emp_id) 列,因为每个员工的员工编号是惟一的,因此每个员工可以被惟一区分。这个惟一属性列被称为主关键字或主键、主码。

    第二范式 (2NF) 要求实体的属性完全依赖于主关键字。所谓完全依赖是指不能存在仅依赖主关键字一部分的属性,如果存在,那么这个属性和主关键字的这一部分应该分离出来形成一个新的实体,新实体与原实体之间是一对多的关系。为实现区分通常需要为表加上一个列,以存储各个实例的惟一标识。简而言之,第二范式就是属性完全依赖于主键。

  • 第三范式(3NF)属性不依赖于其它非主属性 (表中最多包含其他表中的主键,即外键)

    满足第三范式 (3NF) 必须先满足第二范式 (2NF)。简而言之,第三范式 (3NF) 要求一个数据库表中不包含已在其它表中已包含的非主关键字信息。例如,存在一个部门信息表,其中每个部门有部门编号 (dept_id)、部门名称、部门简介等信息。那么在的员工信息表中列出部门编号后就不能再将部门名称、部门简介等与部门有关的信息再加入员工信息表中。如果不存在部门信息表,则根据第三范式 (3NF) 也应该构建它,否则就会有大量的数据冗余。简而言之,第三范式就是属性不依赖于其它非主属性。

拓扑排序在并行任务调度中的运用

如下图所示的一个任务设计:

  • 任务 1 和任务 2 可以同时执行,等他们都结束后才能执行任务 3
  • 任务 4 执行结束后可以同时执行任务 5 和任务 6
  • 任务 8 执行结束后则任务执行完成

如果用程序来实现这个任务的调度,触发任务的执行,应该怎么做呢?

通过观察,这是一个有向无环图,要访问某个节点 (执行任务),需要先访问它的所有前驱节点,而且每个节点只能访问一次,这是一个典型的可以使用图的拓扑排序解决的问题。

Stream 笔记

Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。

Stream 中的方法单独拿出来使用的话体现不出其优点,在复杂的操作情况下,Stream 的优势才明显,例如下面这样生成不重复有序随机数的例子,如果用传统的写法代码会很长,还需要 if else 判断,使用临时集合等截断,去重等复杂操作,使用 Stream 的方式的话,逻辑看上去就清晰很多:

1
2
3
4
5
6
7
Stream<Double> stream = Stream.generate(Math::random) // double 无限流
.limit(20) // 截断,取前 20 个
.filter(x -> x > 0.3) // 过滤,取大于 0.3 的元素
.skip(1) // 忽略,丢弃第一个元素
.distinct() // 去重
.map(x -> x * 10) // 映射,将每个元素扩大 10 倍
.sorted(); // 对 double 流进行排序

Lambda 笔记

Lambda 是什么 ?

简单的说,Lambda 就是匿名函数。由于 Java 是完全面向对象的,不能像 C 语言那样独立的定义函数以及函数变量,Java 中的方法 (函数) 只能定义在类或者接口中,所以 Java 采取了一种折衷的方案实现 Lambda: 通过有且只有一个未实现的方法的接口来实现 Lambda,所以这样的接口又叫做 Functional Interface

在 Java 中,Lambda 的本质是匿名内部类的语法糖,用于简化匿名内部类的编码。也许您要问,匿名内部类在编译的时候都会成功 class 文件,但 Lambda 却没有看到对应的 class 文件啊。其实这也好理解,class 文件最后会被 JVM 加载到 byte[] 中,用于生成 Class 对象,Lambda 实现的匿名内部类在运行时生成对应的 bytes[] 来生成 Class 对象就可以了,class 文件只是一个载体,其内容也可以通过 URL 远程加载呢,这样本地是不是也一样看不到 class 文件了呢?

Lambda comes from the Lambda Calculus and refers to anonymous functions in programming.