数据结构 -- 散列表
散列表作为一种能够提供高效插入,查找,删除 以及遍历的数据结构,被应用在很多不同的存储组件之中。
就像rocksdb中的hashskiplist,redis的有序集合,java的 LinkedHashMap 等 都是一些非常有特色的核心数据结构,来提供线上高效的数据操作能力。
本节对工业级散列表的基本实现 探索一番,希望加深自己对存储产品设计理念的理解。
工业级的散列表需要具有如下能力:
初始大小
散列表的初始大小,刚开始的时候需要拥有一定量的存储空间,根据实际应用情况可以通过设置散列表的初始大小,从而减少动态扩容的次数,依次提升性能。装载因子 和 动态扩容
最大装载因子默认是0.75, 即散列表中已存储的元素个数达到了总大小的0.75,则开始扩容散列冲突的解决
根据实际情况选择通用的两种方案: 开放寻址法 和 链表法
开放寻址法:使用数组进行底层存储,出现冲突时重新探测数据中的空闲位置,进一步插入,该方法能够利用CPU缓存功能进行加速,但是比较耗费内存空间。
基本过程如下:
插入key3的时候hash函数计算的散列值也为3,和key2的散列值冲突,那么将向key2之后插入,但是发现key2之后没有空间了,则跳到数据开头重新遍历找到第一个空闲的位置插入。链表法:将相同散列值的元素放入到相同的槽位,每一个槽位用链表管理相同hash值的元素
该方法能够高效利用内存(链表节点生成新节点的时候才会分配空间),只是对CPU缓存不太友好,地址之间并不是连续的,CPU缓存基本不能生效。(这里可以通过一些有序的数据结构进行优化-- 跳表和红黑树)
插入的key3有和key1相同的散列值,则将key3直接插入到key1对应的bucket链表末尾,实际过程需要有序,所以这里插入到hashtab[2]的时候还需要找到对应的链表节点前驱。
散列函数
散列函数的设计不追求复杂,但是需要高效,计算但散列值要分布均匀。
java的LinkedHashMap的散列函数设计如下:int hash(Object key) {int h = key.hashCode();return (h ^ (h >>> 16)) & (capitity -1); //capicity表示散列表的大小 }
其中,hashCode()返回的是Java对象的hash code。比如String类型的对象的hashCode()就是下面这样:
public int hashCode() {int var1 = this.hash;if(var1 == 0 && this.value.length > 0) {char[] var2 = this.value;for(int var3 = 0; var3 < this.value.length; ++var3) {var1 = 31 * var1 + var2[var3];}this.hash = var1;}return var1; }
设计的过程中尽可能保证数据的随机性,就像手机号的后四位 一般是随机均匀分布,这样取用数据的过程即可作为hash函数。
通过以上四点的设计,我们基本能够完成一个工业级的散列表实现,再做一个总结,工业级的散列表的特性:
- 支持快速的查询、插入、删除操作;
- 内存占用合理,不能浪费过多的内存空间;
- 性能稳定,极端情况下,散列表的性能也不会退化到无法接受的情况
工业级散列表的设计实现思路:
- 设计一个合适的散列函数;
- 定义装载因子阈值,并且设计动态扩容策略
- 选择合适的散列冲突解决方法
通过以上设计,使用C语言编写一个简单的工业级散列表实现如下,散列冲突是通过链表解决的
listhash.h
#ifndef __HASHTAB_H__
#define __HASHTAB_H__typedef struct _hashtab_node
{void * key;void * data;struct _hashtab_node *next;
}hashtab_node;typedef struct _hashtab
{hashtab_node **htables; /*哈希桶*/int size; /*哈希桶的最大数量*/int nel; /*哈希桶中元素的个数*/int (*hash_value)(struct _hashtab *h,const void *key); /*哈希函数*/int (*keycmp)(struct _hashtab*h,const void *key1,const void *key2);/*哈希key比较函数,当哈希数值一致时使用*/void (*hash_node_free)(hashtab_node *node);
}hashtab;#define HASHTAB_MAX_NODES (0xffffffff)typedef int (*hash_key_func)(struct _hashtab *h,const void *key); /*哈希函数*/
typedef int (*keycmp_func)(struct _hashtab*h,const void *key1,const void *key2);/*哈希key比较函数,当哈希数值一致时使用*/
typedef void (*hash_node_free_func)(hashtab_node *node);
/*根据当前结构体元素的地址,获取到结构体首地址*/
#define offsetof(TYPE,MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define container(ptr,type,member) ({\const typeof( ((type *)0)->member) *__mptr = (ptr);\(type *) ( (char *)__mptr - offsetof(type,member));})hashtab * hashtab_create(int size,hash_key_func hash_value,keycmp_func keycmp,hash_node_free_func hash_node_free);
void *hashtab_expand(hashtab*h);
void hashtab_destory(hashtab *h);
int hashtab_insert(hashtab * h,void *key,void *data);
hashtab_node *hashtab_delete(hashtab *h, void *key);
void *hashtab_search(hashtab*h,void *key);#endif
listhash.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include"listhash.h"/* 创建 */
hashtab *hashtab_create(int size,hash_key_func hash_value,keycmp_func keycmp,hash_node_free_func hash_node_free)
{hashtab *h = NULL;int hash_num = 0; // 初始化hash元素的个数if(size < 0 || hash_value == NULL || keycmp == NULL) {return NULL;}h = (hashtab *)malloc(sizeof(hashtab));if(h == NULL) {return NULL;}h->htables = (hashtab_node**)malloc(size * sizeof(hashtab_node*));if(h->htables == NULL) {return NULL;}h->size = size;h->hash_value = hash_value;h->keycmp = keycmp;h->hash_node_free = hash_node_free;h->nel = 0;for(;hash_num < size; hash_num++) {h->htables[hash_num] = NULL;}return h;
}/* 销毁 */
void hashtab_destory(hashtab *h)
{int i = 0;hashtab_node *cur = NULL;hashtab_node *tmp = NULL;if(h == NULL) {return;}for (;i < h->size; ++i) {cur = h->htables[i];while(cur != NULL) {tmp = cur;cur = cur->next;h->hash_node_free(tmp);}}free(h->htables);free(h);return ;
}/* 插入 */
int hashtab_insert(hashtab *h ,void *key, void *data) {if(h == NULL || key == NULL || data == NULL) {return -1;}unsigned int hvalue = 0;hashtab_node *cur = NULL;hashtab_node *prev = NULL;hashtab_node *tmp = NULL;hvalue = h->hash_value(h,key);cur = h->htables[hvalue];/* hashtable 中的元素在hash链上也是有序的 */while(cur != NULL && (h->keycmp(h,key,cur->key) > 0)) {// 找到待插入key的前驱节点prev = cur;cur = cur->next;}if(cur != NULL && (h->keycmp(h, key, cur->key) == 0)) { // 当前key存在return 1;}tmp = (hashtab_node *)malloc(sizeof(hashtab_node));if(tmp == NULL) {return -1;}tmp->key = key;tmp->data = data;if(prev == NULL) {tmp->next = h->htables[hvalue];h->htables[hvalue] = tmp;}else {tmp->next = prev->next;prev->next = tmp;}h->nel ++;// if(h->size * 3 / 4 <= h->nel) {// hashtab_expand(h);// }return 0;
}/* 删除 */
hashtab_node *hashtab_delete(hashtab *h, void *key)
{if(h == NULL || key == NULL) {return NULL;}unsigned int hvalue = 0;hashtab_node *cur = NULL;hashtab_node *prev = NULL;hvalue = h->hash_value(h,key);cur = h->htables[hvalue];while(cur != NULL && (h->keycmp(h,key,cur->key) >= 0) ) {// 找到待删除节点的前驱节点if(h->keycmp(h,key,cur->key) == 0) { // 找到匹配的keyif(prev == NULL) { // 需要删除的key就是hvalue所在hash链上的第一个keyh->htables[hvalue] = cur -> next;}else {prev->next = cur->next;}h->nel --;return cur;}prev = cur;cur = cur ->next;}return NULL;
}/* 查找 */
void *hashtab_search(hashtab*h,void *key)
{if(h == NULL || key == NULL) {return NULL;}unsigned int hvalue = 0;hashtab_node *cur = NULL;hvalue = h->hash_value(h,key);cur = h->htables[hvalue];if(cur == NULL) { // 先确认hash桶是否存在return NULL;}while(cur != NULL) {if(h->keycmp(h,key,cur->key) == 0) {return cur->data;}cur = cur ->next;}return NULL;
}void hashtab_dump(hashtab *h)
{int i = 0;hashtab_node *cur = NULL;if(h == NULL) {return;}printf("\r\n----开始--size[%d],nel[%d]------------", h->size, h->nel);for(;i< h->size; ++i) {printf("\r\n htables[%d]:",i);cur = h->htables[i];while(cur != NULL) {printf("key[%s],data[%s] ", cur->key, cur->data);cur = cur ->next;}}printf("\r\n----结束--size[%d],nel[%d]------------", h->size, h->nel);
}/* 扩容 */
void *hashtab_expand(hashtab *tmp_h) {if(tmp_h == NULL || (tmp_h ->size * 3 / 4 > tmp_h->nel)) {return NULL;}printf("begin expand\n");hashtab *new_h = NULL;hashtab *h = tmp_h;hashtab_node *cur = NULL;int i = 0;new_h = hashtab_create(h->size * 2, h->hash_value,h->keycmp, h->hash_node_free);for (;i < h->size; ++i) {cur = h->htables[i];while(cur != NULL) {hashtab_insert(new_h, cur->key, cur->data);cur = cur->next;}}printf("before destory\n");hashtab_destory(tmp_h);printf("end destory\n");// hashtab_dump(new_h);tmp_h = new_h;return NULL;
}struct test_node
{/* data */char key[30];char data[30];
};unsigned int simple_hash(const char *str)
{register unsigned int hash = 0;register unsigned int seed = 131;while(*str){hash = hash*seed + *str++;}return hash & (0x7FFFFFFF);
}int hashtable_hvalue(hashtab *h, const void *key)
{return simple_hash(key) % h->size;
}int hashtable_compare(hashtab*h, const void *key1, const void *key2)
{return strcmp(key1, key2);
}void hashtable_node_free(hashtab_node *cur)
{struct test_node *tmp = NULL;tmp = container(cur->key,struct test_node,key);free(tmp);free(cur);
}int main ()
{int res = 0;char *pres = NULL;hashtab_node * node = NULL;struct test_node *p = NULL;hashtab *h = NULL;h = hashtab_create(6,hashtable_hvalue,hashtable_compare,hashtable_node_free);// 创建一个hash桶大小为5的hash表assert(h!= NULL);while(1){p = (struct test_node*)malloc(sizeof(struct test_node));assert(p != NULL);printf("\r\n 输入key value,输入\"quit\"退出");scanf("%s",p->key);scanf("%s",p->data);if(strcmp(p->key,"quit") == 0){free(p);break;}res = hashtab_insert(h,p->key,p->data);if (res != 0){free(p);printf("\r\n key[%s],data[%s] insert failed %d",p->key,p->data,res);}else{printf("\r\n key[%s],data[%s] insert success %d",p->key,p->data,res);}}hashtab_dump(h);while(1){p = (struct test_node*)malloc(sizeof(struct test_node));assert(p != NULL);printf("\r\n 请输入key 查询value的数值,当可以等于\"quit\"时退出");scanf("%s",p->key);if(strcmp(p->key,"quit") == 0){free(p);break;}pres = hashtab_search(h,p->key);if (pres == NULL){printf("\r\n key[%s] search data failed",p->key);}else{printf("\r\n key[%s],search data[%s] success",p->key,pres);}free(p);}hashtab_dump(h);while(1){p = (struct test_node*)malloc(sizeof(struct test_node));assert(p != NULL);printf("\r\n 请输入key 删除节点的数值,当可以等于\"quit\"时退出");scanf("%s",p->key);if(strcmp(p->key,"quit") == 0){free(p);break;}node = hashtab_delete(h,p->key);if (node == NULL){printf("\r\n key[%s] delete node failed ",p->key);}else{printf("\r\n key[%s],delete data[%s] success",node->key,node->data);h->hash_node_free(node);}free(p);hashtab_dump(h);}hashtab_destory(h);return 0;}
相关文章:

c语言编程题餐饮服务打分,求详细分析C语言题餐饮服务质量调查打分题和答案..._质量员考试_帮考网...
bangsaizhuo新兵答主11-09TA获得超过6761个赞二、填空题1. ___变量__是指在程序运行过程中,值可以发生变化的量。2.C语言是一种____区分_(区分/不…

为什么阿里巴巴修正了HashMap关于1024个元素扩容的次数?(典藏版)
此番修正主要是每个人对「扩容」定义存在了分歧,在JDK1.8中如果没有给HashMap设置初始容量,那么在第一次put()操作的时候会进行resize()。而有的人认为这算一次扩容,有的人认为这不是一次扩容,这只是HashMap容量的初始化。所以存储1024的元素时:前者的人认为扩容次数为8次。后者的人认为扩容次数为7次。孤尽老师说对此分歧,希望用没有「二义性」的语言来表示,所以「扩容次数」修正为「resize次数」。

【转】每天一个linux命令(31): /etc/group文件详解
原文网址:http://www.cnblogs.com/peida/archive/2012/12/05/2802419.html Linux /etc/group文件与/etc/passwd和/etc/shadow文件都是有关于系统管理员对用户和用户组管理时相关的文件。linux /etc/group文件是有关于系统管理员对用户和用户组管理的文件,linux用户组…

C#设计模式(7)——适配器模式(Adapter Pattern)
一、引言在实际的开发过程中,由于应用环境的变化(例如使用语言的变化),我们需要的实现在新的环境中没有现存对象可以满足,但是其他环境却存在这样现存的对象。那么如果将“将现存的对象”在新的环境中进行调用呢&#…

强烈建议你不要再使用Date类了!!!
这里就不细说修改流程了,主要说一下我们在改造的时候遇到的一些问题。(Date从现在开始)是一个糟糕的类型,这解释了为什么它的大部分内容在 Java 1.1 中被弃用(但不幸的是仍在使用)。只能说这种基础的类改起来牵一发动全身,需要从DO实体类看起,然后就是各种Converter,最后是DTO。这个改造难度不高,但是复杂度非常高,一个地方没改好,轻则接口报错,重则启动失败,非常耗费精力,真不想改。我们要改的原因很简单,我们的代码缺陷扫描规则认为这是一个必须修改的缺陷,否则不给发布,不改不行,服了。

windows 安装MySQL服务 zip解压程序
1:配置 my.ini 文件 如下: [mysql] default-character-setutf8[mysqld] port3306basedirD:\\Program Files\\databases\\mysql-5.7.24datadirD:\\Program Files\\databases\\mysql-5.7.24\\datamax_connections200max_connections200character-set-serve…

数据结构 -- 图与图存储
我们在使用像QQ ,微信,微博,快手,抖音等社交软件的过程中经常需要添加好友,关注好友和被好友关注。这个过程中 这样的社交网络中的好友关系就需要被存储下来,存储在各个公司的后台服务器之上,都…

Struts2 验证规则配置文件
1. Action级别校验命名格式: ActionClassName-validation.xml 2. Action中某个方法的校验命名格式: ActionClassName-ActionAliasName-validation.xml 注意:这里的ActionAliasName(action别名)指的是struts.xml中Action name"XX"的…

c语言中手机系统,一种手机课堂C语言编程系统的制作方法
技术特征:1.一种手机课堂C语言编程系统,其特征在于:该系统由手机端C语言编译运行单元、嵌入式主机端传输单元、台式机端显示单元和投影仪端显示单元组成;所述手机端C语言编译运行单元、嵌入式主机端传输单元、台式机端显示单元和投…

cpp中sizeof与指针
一直不清楚c的sizeof,现在通过实验得到了一些了解。 1 #include<iostream>2 3 using namespace std;4 5 class A{6 private:7 char * a1;8 // ! static int totalPeople0; //error: ISO C forbids in-class initialization of non-const static me…

利用Python制作简单的小程序:IP查看器
前言 说实话,查看电脑的IP,也挺无聊的,但是够简单,所以就从这里开始吧。IP地址在操作系统里就可以直接查看。但是除了IP地址,我们也想通过IP获取地理地址和网络运营商情况。IP地址和地理地址并没有固定的关系ÿ…

一文带你看透 GDB 的 实现原理 -- ptrace真香
文章目录Ptrace 的使用GDB 的基本实现原理Example1 通过ptrace 修改 被追踪进程的内存数据Example2 通过ptrace 对被追踪进程进行单步调试Ptrace的实现PTRACE_TRACEMEPTRACE_ATTACHPTRACE_CONTPTRACE_SINGLESTEPPTRACE_PEEKDATAPTRACE_POKEDATAPTRACE_GETREGSGDB本身能够attach…

线程使用 c语言,如何用C语言实现多线程
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼Windows操作系统,C语言实现多线程:#include #include DWORD APIENTRY ThreadOne ( LPVOID threadArg ){printf ( "线程开始啦,参数是:%s\n" , (char *)threadArg );return …

eclipse设置保护色非原创
eclipse操作界面默认颜色为白色。对于我们长期使用电脑编程的人来说,白色很刺激我们的眼睛,所以我经常会改变workspace的背景色,使眼睛舒服一些。设置方法如下:1、打开window->Preference,弹出Preference面板2、展开General标签…

markdown 使用
1:新手建议 2:windows下使用 http://markdownpad.com/ 3:linux http://benweet.github.io/stackedit/# 二:使用工具 mac http://moustand.com/windows http://markdownpad.com/http://wowubuntu.com/markdown/注:建议 …

python第九章:面向对象--小白博客
面向对象介绍 一、面向对象和面向过程 面向过程:核心过程二字,过程即解决问题的步骤,就是先干什么后干什么 基于该思想写程序就好比在这是一条流水线,是一种机械式的思维方式 优点:复杂的过程流程化 缺点&…

分布式一致性(共识)算法(Paxos,raft,ZAB)的一些总结
文章目录前言CAP理论C consistency 一致性A availability 可用性P partition tolerance 分区容错性一致性模型弱一致性强一致性强一致性算法需要明确的问题强一致算法: 主从同步强一致性算法:多数派强一致算法:PaxosBasic PaxosMulti Paxos第…

dedecms 财付通接口
用织梦做了个旅游网站,网址:http://www.redtourism.cn/ 客户要求财付通支付,上网找了下 不是要买就是要钱,只有自己写了。 代码: <?phpif(!defined(DEDEINC)) exit(Request Error!);/** *财付通接口类 */class ten…
r语言手动算两个C指数p值,如何用R语言进行Pvalue显著性标记?
作者:一只想飞的喵审稿:童蒙编辑:angelica箱线图是统计学中较常见的图形之一。这篇文章将讲述如何简单比较两组或多组的平均值,且添加显著性标记。通常情况根据显著性p值的数值大小,分为四类:(1)0.01≤p<…

linux yum命令详解
yum(全称为 Yellow dog Updater, Modified)是一个在Fedora和RedHat以及SUSE中的Shell前端软件包管理器。基於RPM包管理,能够从指定的服务器自动下载RPM包并且安装,可以自动处理依赖性关系,并且一次安装所有依赖的软体包…

OpenCV编译viz模块
首先需要编译vtk。注意不要使用最新的master版本,而是使用tag分支下的最新版本。当前最新版本是https://gitlab.kitware.com/vtk/vtk/tree/v8.2.0版本。直接点击下载源码即可。 Cmake选项设置: 如果需要编译成静态库,需要在CXX_FLAGS、C_FLAG…

vim 成“神“之路 (一)
文章目录1. 安装1.1 linux1.2 MacOs的安装1.3 Windows的安装1.4 vim中文帮助文档安装2. vim基本概念和基础命令2.1 基本的键位映射如下:2.2 vim模式2.3 vim的选项和基本配置2.3.1 备份和跨会话撤销文件2.3.2 vim中支持鼠标3. vim 常用命令 -- 应对稍复杂任务3.1 光标移动3.2 文…

android 添加头参数,Retrofit添加header参数的几种方法
(1)使用注解的方式添加一个Header参数publicinterfaceUserService {Headers("Cache-Control: max-age640000")GET("/tasks")Call> getTasks();}添加多个Header参数publicinterfaceUserService {Headers({"Accept: application/vnd.yourapi.v1.full…

redis下载地址
http://www.newasp.net/soft/67186.html转载于:https://www.cnblogs.com/phpxuetang/p/4190999.html

Ubuntu 13.10 安装软件失败后出现的问题——已安装 post-installation 脚本 返回了错误号 1...
安装Oracle-java7-installer失败后,再次重新安装后出现错误~~ dpkg: error processing oracle-java7-installer (--configure): 子进程 已安装 post-installation 脚本 返回了错误号 1 索性执行 sudo apt-get install -f,我勒个…

C. Edgy Trees Codeforces Round #548 (Div. 2) 【连通块】
一、题面 here 二、分析 这题刚开始没读懂题意,后来明白了,原来就是一个数连通块里点数的问题。首先在建图的时候,只考虑红色路径上的点。为什么呢,因为为了不走红色的快,那么我们可以反着想只走红色的路径,…

设计模式 之美 -- 策略模式
策略模式作为行为型设计模式中的一种,主要封装相同功能的不同实现算法,用于在用户程序内部灵活切换。对用户来说能够快速替换对应的算法,能够让算法的实现独立于使用的用户。 基本的UML类图如下: 用户使用Stratey的实例能够快速…

Good Bye 2014 B. New Year Permutation(floyd )
题目链接 题意:给n个数,要求这n个数字小的尽量放到前面,求一个最小的。 给一个矩阵s[i][j]1,表示位置 i 的数字可以和 位置 j 的数字交换。 分析: 刚开始用的是3个循环,每次都找一个能直接连接的最小的放到前面&#x…

android数据库查找一个字符,Android - 如何在Firebase数据库中对字符串进行简单搜索?_android_开发99编程知识库...
这个问题可能很旧,但是,有一种文档化方式,如何实现这种方式,很简单,引用 :要启用云Firestore数据的全文搜索,请使用第三方搜索服务(如Algolia ,考虑一个笔记记录应用程序,…

料酒有什么用?
http://zhidao.baidu.com/question/201086759.html?frala&devicemobile&ssid0&from844b&uid0&pusz%401320_1001%2Cta%40iphone_2_4.1_3_537%2Cusm%400&bd_page_type1&baiduidDFA2DBA38D5C3AEB12431C4258DC1F40&tjzhidao_1_0_10_title