SkipList_DB源码解析

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表示插入成功

流程

  1. 初始化update数组,用来存放要插入的节点的前置节点
  2. 从跳表最高level开始,找到每一层中要插入节点对应的前置节点,存放在对应的update[i]中
  3. 如果存在跟当前要插入的值相等的节点,返回1
  4. 调用get_random_level得到当前节点的level
  5. 如果random_level > 当前的level,说明需要增加层数,然后新的节点在新增加的层数中应该放在_header之后,并且更新当前跳表的层数
  6. 创建新节点,并在每一层中将当前节点插入在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类型可以表示的最大值。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值