SkipList_DB
目录
1. 源码解析
核心代码文件
SkipList.h // 节点类和跳表类的实现文件
核心函数
insert_element // 插入元素
delete_element // 删除元素
search_element // 查找元素
2. Node节点类
成员属性
// 私有成员
K key // 键
V value // 值
// 公有成员
Node<K, V> **forward; // 线性表中指向不同层的下一个节点
int node_level; // 当前节点所处哪一个节点层级
成员函数
无参构造函数和析构函数
// 无参构造
Node() {}
// 析构函数
~Node();
template<typename K, typename V>
Node<K, V>::~Node() {
// 做一些内存释放操作
delete []forward;
};
有参构造函数
Node(K k, V v, int);
template<typename K, typename V>
Node<K, V>::Node(const K k, const V v, int level) {
this->key = k;
this->value = v;
this->node_level = level;
// level + 1, 索引是从0 - level
this->forward = new Node<K, V>*[level+1];
// Fill forward array with 0(NULL)
memset(this->forward, 0, sizeof(Node<K, V>*)*(level+1));
};
问题1:为什么这里在函数声明和实现的时候参数列表有差异
函数声明的时候,形参可以没有参数名,但函数定义的时候,形参一定要有参数名。
在C和C++中都能有,但都不建议省略
问题2:这里的forward的初始化操作是不是应该整的更清晰一些
// 开辟一块 level+1大小的内存空间,其中要放的是 Node<K,V>类型的地址
this->forward = new Node<K,V>*[level+1];
// memset函数对这块内存进行初始化
// sizeof 函数先得到一个Node<K,V>*类型的内存块大小,然后乘以level+1个
memset(this->forward, 0, sizeof(Node<K, V>*)*(level+1));
key和value的get方法
K get_key() const;
template<typename K, typename V>
K Node<K, V>::get_key() const {
return key;
};
V get_value() const;
template<typename K, typename V>
V Node<K, V>::get_value() const {
return value;
};
value的set方法
void set_value(V);
template<typename K, typename V>
void Node<K, V>::set_value(V value) {
this->value=value;
};
3. SkipList跳表类
成员属性
// 全部是私有属性
// 跳表的最大层数
int _max_level;
// 跳表的当前层数
int _skip_list_level;
// 跳表的头节点指针
Node<K, V> *_header;
// 文件操作
std::ofstream _file_writer;
std::ifstream _file_reader;
// 跳表当前的元素个数
int _element_count;
问题5:文件操作,ofstream和ifstream
成员方法
1.构造和析构函数
构造函数
SkipList(int);
// 构造函数
template<typename K, typename V>
SkipList<K, V>::SkipList(int max_level) {
this->_max_level = max_level;
this->_skip_list_level = 0;
this->_element_count = 0;
// create header node and initialize key and value to null
K k;
V v;
this->_header = new Node<K, V>(k, v, _max_level);
};
对跳表进行初始化,设置最大高度和当前节点的数量等信息,创建一个头节点,头节点的键值均为空,但是level设置成最大level(这里是因为虚拟头节点应该存在于每一层)。
析构函数
~SkipList();
template<typename K, typename V>
SkipList<K, V>::~SkipList() {
if (_file_writer.is_open()) {
_file_writer.close();
}
if (_file_reader.is_open()) {
_file_reader.close();
}
delete _header;
}
执行一些回收资源的操作,关闭文件描述符号,删除虚拟头节点。
2. get_random_value
函数实现
template<typename K, typename V>
int SkipList<K, V>::get_random_level(){
int k = 1;
while (rand() % 2) {
k++;
}
k = (k < _max_level) ? k : _max_level;
return k;
};
c++中rand()函数会返回一个从0到最大随机数的任意整数。
这样使得每次循环中k++能执行的概率都是1/2,那么该函数就可以实现
- 返回1的概率是1/2
- 返回2的概率是1/4
- 返回3的概率是1/8,以此类推
3. create_node
根据传入的k,v,level创建一个新的node
// create new node
template<typename K, typename V>
Node<K, V>* SkipList<K, V>::create_node(const K k, const V v, int level) {
Node<K, V> *n = new Node<K, V>(k, v, level);
return n;
}
4. 插入数据insert_element
返回值
- 1表示当前节点已经存在
- 0表示插入成功
流程
- 初始化update数组,用来存放要插入的节点的前置节点
- 从跳表最高level开始,找到每一层中要插入节点对应的前置节点,存放在对应的update[i]中
- 如果存在跟当前要插入的值相等的节点,返回1
- 调用get_random_level得到当前节点的level
- 如果random_level > 当前的level,说明需要增加层数,然后新的节点在新增加的层数中应该放在_header之后,并且更新当前跳表的层数
- 创建新节点,并在每一层中将当前节点插入在update[i]之后
template<typename K, typename V>
int SkipList<K, V>::insert_element(const K key, const V value) {
mtx.lock(); // 对共享资源进行操作要加互斥锁
Node<K, V> *current = this->_header;
// 创建update数组,并进行初始化,
// update数组表示在这一插入过程中,每一层需要更新的节点
Node<K, V> *update[_max_level+1];
memset(update, 0, sizeof(Node<K, V>*)*(_max_level+1));
// 从跳表中的最高level开始进行操作
for(int i = _skip_list_level; i >= 0; i--) {
while(current->forward[i] != NULL && current->forward[i]->get_key() < key) {
current = current->forward[i];
}
update[i] = current;
}
// reached level 0 and forward pointer to right node, which is desired to insert key.
// 到达level 0,实际链表层次
current = current->forward[0];
// 如果存在跟当前的值相等的节点,说明不能插入了,返回1
if (current != NULL && current->get_key() == key) {
std::cout << "key: " << key << ", exists" << std::endl;
mtx.unlock();
return 1;
}
// 如果 current == NULL,表示到了这一层的末尾
// 如果 current's key is not equal to key 意味着我们需要在update[0] 和 current节点之间插入数据
if (current == NULL || current->get_key() != key ) {
// 随机获得当前这个节点,应该在哪些层次进行插入
int random_level = get_random_level();
// 如果随机返回的层次大于跳表当前的层数,那么要插入的这个数在比当前层数大的层应该放在_header后面
if (random_level > _skip_list_level) {
for (int i = _skip_list_level+1; i < random_level+1; i++) {
update[i] = _header;
}
_skip_list_level = random_level;
}
// 使用key,value,random_level创建新节点
Node<K, V>* inserted_node = create_node(key, value, random_level);
// 在每一层插入节点
for (int i = 0; i <= random_level; i++) {
inserted_node->forward[i] = update[i]->forward[i];
update[i]->forward[i] = inserted_node;
}
std::cout << "Successfully inserted key:" << key << ", value:" << value << std::endl;
_element_count ++;
}
mtx.unlock();
return 0;
}
5. 展示数据display_list
打印格式化的信息,每一层的信息都有
// Display skip list
// 按层遍历,打印输出
template<typename K, typename V>
void SkipList<K, V>::display_list() {
std::cout << "\n*****Skip List*****"<<"\n";
// 遍历每一层
for (int i = 0; i <= _skip_list_level; i++) {
Node<K, V> *node = this->_header->forward[i];
std::cout << "Level " << i << ": ";
while (node != NULL) {
std::cout << node->get_key() << ":" << node->get_value() << ";";
node = node->forward[i];
}
std::cout << std::endl;
}
}
6. 查找数据search_element
流程:相对比较简单,因为在添加和删除的时候也会执行相应的查找操作
最后需要判断是否找到了对应的key
问题:这里为什么使用了and操作符号
答案:and,or,not是c++中的关键字,但不是c的,c++中的and,or,not关键字是&&、||、!的备胎,这是因为以前有些机器不支持ios646编码
template<typename K, typename V>
bool SkipList<K, V>::search_element(K key) {
std::cout << "search_element-----------------" << std::endl;
Node<K, V> *current = _header;
// 从跳表的最高层开始找
for (int i = _skip_list_level; i >= 0; i--) {
while (current->forward[i] && current->forward[i]->get_key() < key) {
current = current->forward[i];
}
}
// 到达level 0的时候,取出current->forward[0]
current = current->forward[0];
// 如果不为空且key等于我们要的key,就找到了
if (current and current->get_key() == key) {
std::cout << "Found key: " << key << ", value: " << current->get_value() << std::endl;
return true;
}
std::cout << "Not Found Key:" << key << std::endl;
return false;
}
7. 删除数据 delete_element
流程
- 初始化update数组,用来存放待删除节点的前置节点
- 找到待删除节点,代码中如果没找到的话默认不执行任何操作
- 从level 0开始,如果update[i]的后置节点等于待删除节点,那么update[i]的后置节点指向待删除节点的后置节点
- 删除没有节点的层,也就是当前层数减少
// Delete element from skip list
// 从跳表中删除
template<typename K, typename V>
void SkipList<K, V>::delete_element(K key) {
mtx.lock();
Node<K, V> *current = this->_header;
// 这里还是要找到待删除节点的前置节点
Node<K, V> *update[_max_level+1];
memset(update, 0, sizeof(Node<K, V>*)*(_max_level+1));
// start from highest level of skip list
for (int i = _skip_list_level; i >= 0; i--) {
while (current->forward[i] !=NULL && current->forward[i]->get_key() < key) {
current = current->forward[i];
}
update[i] = current;
}
current = current->forward[0];
if (current != NULL && current->get_key() == key) {
// 从level 0开始,删除每一层中要删除的那个点,其实就是改变指针的关系
for (int i = 0; i <= _skip_list_level; i++) {
// 如果从level i开始,update[i]的后继不是要删除的节点了,就跳出
if (update[i]->forward[i] != current)
break;
update[i]->forward[i] = current->forward[i];
}
// 删除没有节点的层,其实也就是当前层数减少即可
// 这里可以跟0进行比较是因为在初始化的时候memset函数全部初始化为了0
while (_skip_list_level > 0 && _header->forward[_skip_list_level] == 0) {
_skip_list_level --;
}
std::cout << "Successfully deleted key "<< key << std::endl;
_element_count --;
}
mtx.unlock();
return;
}
8. 数据落盘dump_file
// Dump data in memory to file
template<typename K, typename V>
void SkipList<K, V>::dump_file() {
std::cout << "dump_file-----------------" << std::endl;
// 打开文件
_file_writer.open(STORE_FILE);
Node<K, V> *node = this->_header->forward[0];
while (node != NULL) {
// 一边写入文件,一边在控制台打印,只操作level 0
_file_writer << node->get_key() << ":" << node->get_value() << "\n";
std::cout << node->get_key() << ":" << node->get_value() << ";\n";
node = node->forward[0];
}
_file_writer.flush();
_file_writer.close();
return ;
}
9. 加载数据load_file
// Load data from disk
// 从磁盘文件中载入数据
template<typename K, typename V>
void SkipList<K, V>::load_file() {
_file_reader.open(STORE_FILE);
std::cout << "load_file-----------------" << std::endl;
std::string line;
std::string* key = new std::string();
std::string* value = new std::string();
while (getline(_file_reader, line)) {
// 对每行数据进行解析,每行数据是一个键值对
// 这里的line是传入参数,key和value是传出参数
get_key_value_from_string(line, key, value);
if (key->empty() || value->empty()) {
continue;
}
// 插入每个数据
insert_element(*key, *value);
std::cout << "key:" << *key << "value:" << *value << std::endl;
}
_file_reader.close();
}
10. 返回数据规模 size
// Get current SkipList size
template<typename K, typename V>
int SkipList<K, V>::size() {
return _element_count;
}
11. 数据预处理操作
从string中分离出key和value
template<typename K, typename V>
void SkipList<K, V>::get_key_value_from_string(const std::string& str, std::string* key, std::string* value) {
if(!is_valid_string(str)) {
return;
}
*key = str.substr(0, str.find(delimiter));
*value = str.substr(str.find(delimiter)+1, str.length());
}
判断string是否合法
template<typename K, typename V>
bool SkipList<K, V>::is_valid_string(const std::string& str) {
if (str.empty()) {
return false;
}
if (str.find(delimiter) == std::string::npos) {
return false;
}
return true;
}
string中的find()函数
如果没有找到匹配的字符,就会返回std::string::nops
,std::string::npos
表示的是一个size_type
类型的常量,其值等于size_type
类型可以表示的最大值。