红黑树—libhl库的使用(二)

红黑树—libhl库的使用(二)

本次示例是红黑树使用测试。

libhl开源库编译安装过程,参看往期文章:https://www.madbull.site/?p=897 C语言实现基础数据结构库

注意:此红黑树 不是线程安全的

所以在多线程使用时,如果同时涉及读写,则需要上锁或者通过无锁编程实现互斥访问;

如果把数据一次性且是单线程加载到红黑树中,然后不再修改,此后只读取数据,则不用考虑互斥的问题。

测试代码

示例把 /etc/os-release 文件每一行去掉换行符 \n 后,计算哈希值,每一行数据写入到红黑树中,涉及到 红黑树创建、数据增加、数据查询、数据删除、红黑树遍历红黑树销毁操作

本文对数据做哈希算法使用的 cityhash,具体用法参看往期文章:https://www.madbull.site/?p=1322 cityhash–对字符串的哈希算法

创建.c文件 rbttst.c,内容如下:

// gcc -Wall -o rbttst rbttst.c -lhl -lcityhash

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

#include "cityhash.h"
#include "rbtree.h"
#include "comparators.h"

const char * filepath = "/etc/os-release" ;

// 对数据 计算哈希值 作为key,把对应的 data 存入到 rbt 的一个节点中。
int rbt_add_data( char * data, size_t data_size, rbt_t * rbt_p ) {

    uint64 key = CityHash64(data, data_size);

    // 1、key 的空间不需要保留,rbt_add不需要key的空间
    // 2、data 的空间需要保留,data指针直接赋值给了 rbt_node_t 结构的 void *value 字段
    // 3、如果 key 已经存在,则自动删除 rbt 里的旧数据,把新数据重新加入到 rbt 中
    rbt_add(rbt_p, &key, sizeof(uint64), data);
    return 0 ;
}

// 对数据 计算哈希值 作为key,并删除此节点
int rbt_del_data( char * data, size_t data_size, rbt_t * rbt_p ) {
    // void * value = NULL ; // 接收删除节点的 value 指针指向的空间

    // 计算 key 
    uint64 key = CityHash64(data, data_size);

    //  从 rbt 红黑树里查询到 key 之后,再删除此数据。 
    // 如果 void ** value 参数给的是NULL,则会自动调用 内存释放函数,释放 rbt_node_t 中存值的空间。
    // 否则,会把 rbt_node_t 中value指向的空间地址,赋值给value指针,
    int ret = rbt_remove(rbt_p, &key, sizeof(uint64), NULL);
    if ( ret == 0 ) {
        printf("%s:%s : remove node key[%lu] value[%s]\n", __FILE__, __func__, key, data) ;
    } else {
        fprintf(stderr, "%s:%s : can not find key[%lu] value[%s]\n", __FILE__, __func__, key, data) ;
    }
    return ret ;
}

// 从文件中读取数据,每一行条数据,存入到 rbt--红黑树中
int file_to_rbt(const char * filepath, rbt_t * rbt_p ) {
    FILE * fd = NULL ;
    char read_buf[2048] ;
    char * data = NULL ; 
    size_t data_len = 0 ;

    // 1、 打开文件
    fd = fopen( filepath, "r") ;
    if ( fd == NULL ) {
        fprintf(stderr, "%s:%s : open file [%s] error !", __FILE__, __func__, filepath) ;
        return -1 ;
    }

    // 2、逐行读取数据,并加入到 rbt 红黑树中。
    memset(read_buf, 0x00, sizeof(read_buf) ) ;
    while (fgets(read_buf, sizeof(read_buf), fd) != NULL ) {

        // 计算长度
        data_len = strlen(read_buf) ;
        //  处理换行符号
        if ( '\n' == read_buf[data_len-1] ) {
            read_buf[data_len-1] = '\0' ;
        }
        // 创建 rbt_node_t 结构中 value 指针指向的数据
        data = (char *)calloc(1, data_len) ; 
        memcpy(data, read_buf, data_len) ;

        rbt_add_data(data, data_len, rbt_p) ;

        memset(read_buf, 0x00, sizeof(read_buf) ) ;
    }
    fclose(fd) ;
    return 0 ;
}

// 测试 查询 功能
int rbt_search_data( char * data, size_t data_size, rbt_t * rbt_p ) {
    int ret = 0 ;
    uint64 key = CityHash64(data, data_size);
    char * value = NULL;

    ret = rbt_find(rbt_p, &key, sizeof(uint64), (void**)&value);
    if( ret == 0 ) {
        printf("%s:%s : key[%lu] value[%s]\n", __FILE__, __func__, key, value) ;
        return 0 ;
    } else {
        fprintf(stderr, "%s:%s : can not find key[%lu] value[%s]\n", __FILE__, __func__, key, value ) ;
        return -1 ;
    }
}

void my_free(void *v) {
    printf("%s:%s : value = [%s] delete\n", __FILE__, __func__, (char *) v) ;
    free(v) ;
}

rbt_walk_return_code_t walk_cb(rbt_t *rbt, void *key, size_t klen, void *value, void *priv) {

    static int i = 0 ;
    char * prestr = (char *)priv ;

    ++i ;
    printf("%s:%s %s: i[%d]  walk key[%lu] value[%s]\n", __FILE__, __func__, prestr, i, *(uint64 *)key, (char *) value ) ;

    return RBT_WALK_CONTINUE  ;
}

int main(int argc, char * argv[]) {

    rbt_t * rbt_p = NULL ; 

    // 测试创建
    rbt_p = rbt_create( libhl_cmp_keys_int64, my_free);

    // 测试添加数据
    printf("\n\n===================测试添加数据=====================\n" ) ;
    file_to_rbt(filepath, rbt_p ) ;

    // 测试查询
    printf("\n\n===================测试查询=====================\n" ) ;
    char * test1 = "ID_LIKE=debian" ;  // 从 filepath 文件中随便找了一行作为测试项
    rbt_search_data( test1, strlen(test1)+1, rbt_p ) ;

    // 测试删除数据
    printf("\n\n===================测试删除数据=====================\n" ) ;
    rbt_del_data( test1, strlen(test1)+1, rbt_p ) ;
    
    // 测试查询一个已经删除的数据
    printf("\n\n===================测试查询已删除的数据=====================\n" ) ;
    rbt_search_data( test1, strlen(test1)+1, rbt_p ) ;

    // 遍历整个rbt
    printf("\n\n===================遍历数据=====================\n" ) ;
    char * pre = "this line is " ;
    rbt_walk(rbt_p , walk_cb, pre) ;

    // 销毁 , 会自动调用 my_free 
    printf("\n\n===================销毁rbt=====================\n" ) ;
    rbt_destroy(rbt_p);
    return 0 ;
}

编译测试

编译指令:gcc -Wall -o rbttst rbttst.c -lhl -lcityhash

1、在创建数据时,相同哈希的数据,会用新数据把旧数据更新,同时销毁旧数据;

2、添加数据、查询数据、删除数据,这三个操作中,哈希的计算方法和提供的数据要完全一致。如果提供的数据结构中包含指针,确保指针值一致。

3、自测红黑树遍历存在BUG,遍历的回调函数返回 RBT_WALK_CONTINUE 时好使,其他返回值不好使。这是因为红黑树调整以及数据递归遍历导致的,还需要修改libhl的开源的代码才可以,以后用空了调试调试再更新。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注