Clay 的技术空间

用进废退 | 艺不压身

前言

Docker 镜像说明

本文使用了 DockerHub 平台上的 Oracle 11g 镜像,基于 Ubuntu 18.04 LTS 系统,数据库版本是 Oracle Express Edition 11g Release 2 (11.2.0.2.0)。

Oracle 各版本介绍

在 Oracle 数据库的发展中,数据库一直处于不断升级状态,一共有以下几个版本:

  • Oracle 8i:Oracle 8i 表示 Oracle 正式向 Internet 上发展,其中 i 表示就是 internet。
  • Oracle 9i:Oracle 8i 是一个过渡版本,Oracle 9i 是一个更加完善的数据库版本。
  • Oracle 10g:g 表示 grid,代表网格的意思,即这种数据库采用网格计算的方式进行操作。
  • Oracle 11g:是 Oracle 10g 的稳定版本,Oracle 11g 是目前使用最广泛的版本。
  • Oracle 12c:是 Oracle 2013 年推出的数据库版本,c 代表 Cloud,代表云计算的意思,同时 Oracle 12c 支持大数据的处理能力。
  • Oracle 18c、Oracle 19c 是对 12c 版本的完善和发展。
阅读全文 »

大纲

算法

算法的基本概念

算法的简介

函数库对数据类型的选择对其可重用性起着至关重要的作用。举例来说,一个求方根的函数,在使用浮点数作为其参数类型的情况下的可重用性肯定比使用整型作为它的参数类性要高。而 C++ 通过模板的机制允许推迟对某些类型的选择,直到真正想使用模板或者说对模板进行特化的时候,STL 就利用了这一点提供了相当多的算法。它是在一个有效的框架中完成这些算法的 —— 可以将所有的类型划分为少数的几类,然后就可以在模板的参数中使用一种类型替换掉同一种类中的其他类型。

阅读全文 »

大纲

前言

学习资源

Kafka 生产者

生产者消息发送流程

生产者消息发送原理

Kafka 的 Producer 发送消息采用的是异步发送的方式。在消息发送的过程中,涉及到了两个线程 — main 线程和 sender 线程。在 main 线程中,会创建一个双端队列 RecordAccumulator。值得一提的是,main 线程将消息发送给 RecordAccumulator 时,sender 线程会不断从 RecordAccumulator 中拉取消息并发送到 Kafka Broker。

阅读全文 »

大纲

函数对象

函数对象的概念

尽管函数指针被广泛用于实现函数回调,但 C++ 还提供了一个重要的实现回调函数的方法,那就是函数对象。重载函数调用操作符 () 的类,其对象常称为函数对象(Function Object),即它们是行为类似函数的对象。一个类对象,表现出一个函数的特征,就是通过 对象名 + (参数列表) 的方式使用一个类对象,如果没有上下文,完全可以把它看作一个函数对待,这是通过重载类的 () 操作符来实现的。在 C++ 标准库中,函数对象被广泛地使用以获得弹性,并且标准库中的很多算法都可以使用函数对象或者函数来作为自定义的回调行为。值得一提的是,函数对象的别名是 仿函数,或者是 伪函数

阅读全文 »

大纲

前言

学习资源

消息队列

目前企业中比较常见的消息队列产品主要有 Kafka、RabbitMQ、RocketMQ、ActiveMQ 等。在大数据场景主要采用 Kafka 作为消息队列,而在 JavaEE 开发中主要采用 RabbitMQ、RocketMQ、ActiveMQ。

消息队列的优势

  • 解耦 - 允许独立地扩展或修改不同业务的处理过程,只要确保它们遵守同样的接口约束

阅读全文 »

大纲

前言

本文将使用多台物理机器(至少三台)搭建 Kafka 集群,适用于 CentOS/Debian/Ubuntu 等发行版。

Zookeeper 集群搭建

本文的 Kafka 集群搭建依赖于 Zookeeper,因此生产环境需要将 Zookeeper 集群提前搭建起来。值得一提的是,从 Kafka 2.8.0 版本开始,Kafka 自身实现了 Raft 分布式一致性机制,这意味着 Kafka 集群是可以脱离 ZooKeeper 独立运行的。

集群规划

节点 IP 地址端口版本号
Zookeeper 节点 1192.168.1.121813.4.10
Zookeeper 节点 2192.168.1.221813.4.10
Zookeeper 节点 3192.168.1.321813.4.10
阅读全文 »

大纲

前言

本文适用于在 Centos/Debian/Ubuntu 等 Linux 发行版系统上,使用单机搭建 Kafka 集群。

Zookeeper 集群搭建

本文的 Kafka 集群搭建依赖于 Zookeeper,因此需要将 Zookeeper 单机集群提前搭建起来。值得一提的是,从 Kafka 2.8.0 版本开始,Kafka 自身实现了 Raft 分布式一致性机制,这意味着 Kafka 集群是可以脱离 ZooKeeper 独立运行的。

集群规划

节点 IP 地址端口版本号
Zookeeper 节点 1127.0.0.121813.4.10
Zookeeper 节点 2127.0.0.121823.4.10
Zookeeper 节点 3127.0.0.121833.4.10
阅读全文 »

前言

在企业级开发中,我们经常会有编写数据库文档的时间付出,关于数据库文档的状态:要么没有、要么有但都是手写、后期运维开发都需要手动对文档进行维护,很是繁琐。如果忘记一次维护就会给以后的工作造成很多困扰,这无形中留了很多坑给自己和后人。screw 是一款简洁好用的数据库文档生成工具,专为解决这一开发痛点而生。

screw 介绍

特色功能

  • 灵活扩展
  • 支持自定义模板
  • 支持多种数据库
  • 支持多种格式的文档
  • 简洁、轻量、设计良好
阅读全文 »

大纲

set 与 multiset 容器

set 与 multiset 容器的概念

set 容器的概念

set 是一个集合容器,其中所包含的元素是唯一的,集合中的元素会自动按一定的顺序排列。元素插入过程是按排序规则插入,所以不能指定插入位置。set 的元素不像 map 那样可以同时拥有实值和键值,set 的元素即是键值又是实值。Set 不允许两个元素有相同的键,也不可以通过 set 的迭代器改变 set 元素的值,因为 set 元素值就是其键值,关系到 set 元素的排序规则。如果任意改变 set 元素值,会严重破坏 set 的组织。换句话说 set 的 iterator 是一种 const iterator。set 拥有和 list 某些相同的性质,当对容器中的元素进行插入操作或者删除操作的时候,操作之前所有的迭代器,在操作完成之后依然有效,被删除的那个元素的迭代器必然是一个例外。

阅读全文 »

大纲

list 容器

list 容器的概念

list 是一个双向链表容器,而且还是一个双向循环链表,可以高效地进行插入和删除元素。链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的(如下图所示)。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。相较于 vector 的连续线性空间,list 就显得负责许多,它的好处是每次插入或者删除一个元素,就是配置或者释放一个元素的空间。因此,list 对于空间的运用有绝对的精准,一点也不浪费。值得一提的是,对于任何位置的元素插入或元素的移除,list 永远是常数时间的耗时(效率较高);但对于查询操作来说,list 的执行效率较低。

总结

  • 链表采用动态内存分配,不会造成内存浪费和溢出。
  • 链表虽然灵活,但是空间和时间的额外耗费较大。
  • 链表执行插入和删除操作都十分方便,仅修改指针即可实现,不需要移动大量元素。
  • 链表不可以随机存取元素,所以不支持 at.(pos) 函数与 [] 操作符的使用。

list 容器的迭代器

list 容器不能像 vector 一样以普通指针作为迭代器,因为其节点不能保证都在同一块连续的内存空间上。list 迭代器必须有能力指向 list 的节点,并有能力进行正确的递增、递减、取值、成员存取操作。所谓 “list 正确的递增、递减、取值、成员取用” 是指,递增时指向下一个节点,递减时指向上一个节点,取值时取的是节点的数据值,成员取用时取的是节点的成员。由于 list 是一个双向链表,迭代器必须能够具备前移、后移的能力,所以 list 容器提供的迭代器是 BidirectionalIterators。list 有一个重要的性质,插入操作和删除操作都不会造成原有 list 送代器的失效。这在 vector 是不成立的,因为 vector 的插入操作可能造成记忆体重新配置,导致原有的送代器全部失效;甚至 list 元素的删除,也只有被删除的那个元素的迭代器失效,其他迭代器不受任何影响。

list 容器的使用

list 的构造与赋值

默认构造函数

list 采用模板类实现,对象的默认构造形式:list<T> lstT;,其中 <> 尖括号内还可以设置指针类型或自定义类型

1
2
3
list<int> lstInt;         // 定义一个存放 int 数据的 list 容器
list<float> lstFloat; // 定义一个存放 float 数据的 list 容器
list<string> lstString; // 定义一个存放 string 数据的 list 容器
有参构造函数
1
2
3
list(beg, end);           // 构造函数将 [beg, end) 区间中的元素拷贝给本身,注意该区间是左闭右开的区间
list(n, elem); // 构造函数将 n 个 elem 拷贝给本身
list(const list &lst); // 拷贝构造函数
赋值操作说明
1
2
3
4
list.assign(beg, end);               // 构造函数将 [beg, end) 区间中的元素拷贝给本身,注意该区间是左闭右开的区间
list.assign(n, elem); // 将 n 个 elem 拷贝赋值给本身
list& operator=(const list &lst); // 重载等号操作符
list.swap(lst); // 将 lst 与本身的元素互换
构造与赋值的使用
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
#include <iostream>
#include <list>

using namespace std;

void printList(list<int> &L) {
// 遍历容器
for (list<int>::iterator it = L.begin(); it != L.end(); it++) {
cout << *it << " ";
}
cout << endl;
}

int main() {

cout << "------ list 构造函数 ------" << endl;

// 默认构造函数
list<int> myList1;

// 有参构造函数,将 n 个 elem 元素拷贝给本身
list<int> myList2(5, 10);
printList(myList2);

// 有参构造函数,将 [begin, end) 区间中的元素拷贝给本身
list<int> myList3(myList2.begin(), myList2.end());
printList(myList3);

// 拷贝构造函数
list<int> myList4 = myList2;
printList(myList4);

cout << "------ list 赋值操作 ------" << endl;

// 赋值操作,将 [begin, end) 区间中的元素拷贝给本身
list<int> myList5;
myList5.assign(myList2.begin(), myList2.end());
printList(myList5);

// 赋值操作,将 n 个 elem 元素拷贝给本身
list<int> myList6;
myList6.assign(5, 8);
printList(myList6);

// 赋值操作,重载等号操作符
list<int> myList7;
myList7 = myList2;
printList(myList7);

// 赋值操作,将其他容器与本身的元素互换
myList6.swap(myList7);
printList(myList6);
printList(myList7);

return 0;
}

程序运行输出的结果如下:

1
2
3
4
5
6
7
8
9
10
------ list 构造函数 ------
10 10 10 10 10
10 10 10 10 10
10 10 10 10 10
------ list 赋值操作 ------
10 10 10 10 10
8 8 8 8 8
10 10 10 10 10
10 10 10 10 10
8 8 8 8 8

list 的常用操作

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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#include <iostream>
#include <list>

using namespace std;

void printList(list<int> &L) {
// 遍历容器
for (list<int>::iterator it = L.begin(); it != L.end(); it++) {
cout << *it << " ";
}
cout << endl;
}

void reversePrintList(list<int> &L) {
// 逆向遍历
for (list<int>::reverse_iterator it = L.rbegin(); it != L.rend(); it++) {
cout << *it << " ";
}
cout << endl;
}

int main() {
cout << "------ list 插入操作 ------" << endl;

list<int> myList1;

// 往容器的尾部插入元素
myList1.push_back(10);
myList1.push_back(20);
myList1.push_back(30);
printList(myList1);

// 往容器的头部插入元素
myList1.push_front(300);
myList1.push_front(200);
myList1.push_front(100);
printList(myList1);

// 往 pos 位置插入 elem 数据的拷贝,返回新数据的位置
myList1.insert(myList1.begin(), 400);
printList(myList1);

// 往 pos 位置插入 n 个 elem 数据,无返回值
myList1.insert(myList1.begin(), 2, 500);
printList(myList1);

// 往 pos 位置插入 [begin, end) 区间的数据,无返回值
list<int> myList2;
myList2.push_back(1);
myList2.push_back(2);
myList2.push_back(3);
myList1.insert(myList1.begin(), myList2.begin(), myList2.end());
printList(myList1);

cout << "------ list 删除操作 ------" << endl;

// 删除容器头部的数据
myList1.pop_front();
printList(myList1);

// 删除容器尾部的数据
myList1.pop_back();
printList(myList1);

// 删除 pos 位置的数据,返回下一个数据的位置
myList1.erase(myList1.begin());
printList(myList1);

// 删除容器中所有与 elem 值匹配的元素
myList1.remove(100);
printList(myList1);

cout << "------ list 读取操作 ------" << endl;

// 获取第一个元素
cout << myList1.front() << endl;

// 获取最后一个元素
cout << myList1.back() << endl;

cout << "------ list 清空操作 ------" << endl;

list<int> myList3;
myList3.push_back(10);
myList3.push_back(10);
myList3.clear();
printList(myList3);

cout << "------ list 大小操作 ------" << endl;

// 返回容器中元素的个数
cout << "size = " << myList1.size() << endl;

// 判断容器是否为空
bool isEmpty = myList1.empty();
cout << "isEmpty = " << (isEmpty ? "true" : "false") << endl;

// 重新指定容器的长度 num,若容器变长,则以默认值填充新位置,若容器变短,则末尾超出容器长度的元素会被删除
myList1.resize(6);
cout << "size = " << myList1.size() << endl;

// 重新指定容器的长度 num,若容器变长,则以 elem 值填充新位置,若容器变短,则末尾超出容器长度的元素会被删除
myList1.resize(9, 11);
printList(myList1);
cout << "size = " << myList1.size() << endl;

cout << "------ list 逆向遍历操作 ------" << endl;

list<int> myList11;
myList11.push_back(1);
myList11.push_back(2);
myList11.push_back(3);
reversePrintList(myList11);

return 0;
}

程序运行输出的结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
------ list 插入操作 ------
10 20 30
100 200 300 10 20 30
400 100 200 300 10 20 30
500 500 400 100 200 300 10 20 30
1 2 3 500 500 400 100 200 300 10 20 30
------ list 删除操作 ------
2 3 500 500 400 100 200 300 10 20 30
2 3 500 500 400 100 200 300 10 20
3 500 500 400 100 200 300 10 20
3 500 500 400 200 300 10 20
------ list 读取操作 ------
3
20
------ list 清空操作 ------

------ list 大小操作 ------
size = 8
isEmpty = false
size = 6
3 500 500 400 200 300 11 11 11
size = 9
------ list 逆向遍历操作 ------
3 2 1

list 的反转与排序操作

提示

  • 所有不支持随机访问的容器,都不可以使用系统提供的排序算法。
  • 如果容器不支持使用系统提供的排序算法,那么这个容器的内部往往会提供对应的排序算法。
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
#include <iostream>
#include <list>

using namespace std;

void printList(list<int> &L) {
// 遍历容器
for (list<int>::iterator it = L.begin(); it != L.end(); it++) {
cout << *it << " ";
}
cout << endl;
}

bool myCompare(int v1, int v2) {
// 从大到小排序
return v1 > v2;
}

int main() {
list<int> myList;
myList.push_back(1);
myList.push_back(3);
myList.push_back(2);

cout << "------ list 反转操作 ------" << endl;

myList.reverse();
printList(myList);

cout << "------ list 排序操作 ------" << endl;

// 排序(从小到大)
myList.sort();
printList(myList);

// 排序(从大到小)
myList.sort(myCompare);
printList(myList);

return 0;
}

程序运行输出的结果如下:

1
2
3
4
5
------ list 反转操作 ------
2 3 1
------ list 排序操作 ------
1 2 3
3 2 1

list 的自定义数据类型操作

提示

  • 对 list 的自定义类型数据类型进行排序时,必须指定排序规则。
  • 调用 remove() 函数移除 list 容器中的元素时,自定义数据类型必须重载 == 双等号操作符,否则移除操作会执行失败。
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
#include <iostream>
#include <string>
#include <list>

using namespace std;

class Person {

public:
Person(string name, int age) {
this->name = name;
this->age = age;
}

// 获取名称
string getName() const {
return this->name;
}

// 获取年龄
int getAge() const {
return this->age;
}

// 重载 == 双等号操作符
bool operator==(const Person &p) {
return this->name == p.name && this->age == p.age;
}

private:
string name;
int age;

};

void printList(list<Person> &L) {
// 遍历容器
for (list<Person>::iterator it = L.begin(); it != L.end(); it++) {
cout << "name: " << it->getName() << ", age: " << it->getAge() << endl;
}
}

bool myCompare(Person &p1, Person &p2) {
// 按年龄从大到小排序
return p1.getAge() > p2.getAge();
}

int main() {
list<Person> myList;

Person p1("Tom", 17);
Person p2("Jim", 18);
Person p3("Peter", 19);
Person p4("David", 20);

cout << "------ list 插入操作(自定义数据类型) ------" << endl;

myList.push_back(p1);
myList.push_back(p2);
myList.push_back(p3);
myList.push_back(p4);
printList(myList);

cout << "------ list 删除操作(自定义数据类型) ------" << endl;

// 调用 remove() 函数移除 list 容器中的元素时,自定义数据类型必须重载 `==` 双等号操作符,否则移除操作会执行失败
myList.remove(p3);
printList(myList);

cout << "------ list 排序操作(自定义数据类型) ------" << endl;

// 对 list 的自定义类型数据类型进行排序时,必须指定排序规则
myList.sort(myCompare);
printList(myList);

return 0;
}

程序运行输出的结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
------ list 插入操作(自定义数据类型) ------
name: Tom, age: 17
name: Jim, age: 18
name: Peter, age: 19
name: David, age: 20
------ list 删除操作(自定义数据类型) ------
name: Tom, age: 17
name: Jim, age: 18
name: David, age: 20
------ list 排序操作(自定义数据类型) ------
name: David, age: 20
name: Jim, age: 18
name: Tom, age: 17

stack 容器

stack 容器的概念

stack 是堆栈容器,属于一种先进后出(First In Last Out,FILO)的数据结构,它只有一个出口。stack 容器允许新增元素、移除元素、取得栈顶元素,但是除了最顶端的元素外,没有任何其他方法可以存取 stack 中的其他元素。stack 没有迭代器,容器中所有元素的进出都必须符合 “先进后出” 的规则,只有 stack 最顶端的元素,才有机会被外界取用。换言之,stack 不提供遍历功能,也不提供迭代器。deque 是双向开口的数据结构,若以 deque 为底部结构并封闭其头端开口,便轻而易举地形成一个 stack。因此,SGI STL 便以 deque 作为缺省情况下的 stack 底部结构。由于 stack 以 deque 做为底部容器完成其所有工作,而具有这种 “修改某物接口,形成另一种风貌” 的性质者,称为 adapter(配接器),因此,stack 往往不被归类为 container(容器),而被归类为 container adapter(容器配接器)。简而言之,stack 是简单地装饰 deque 容器而成为另外的一种容器。

阅读全文 »