Ⅵ. 哈希算法

哈希技术既是一种存储方式,也是一种查找方法

哈希算法的实现步骤:
  1. 初始化创建Hash表(散列表)
    • 给定哈希函数构建Hash表
    • 选择合适的冲突处理方法解决地址冲突
  2. 在Hash表上 Add 或 Del 关键字
  • Hash_Add
  • Hash_Del
  1. 在Hash表的基础上执行哈希查找 --- Hash_Find
  • 给定关键字k,根据构建Hash表的哈希函数H计算出关键字k的地址H(k)
  • 若表中该位置为空,则查找失败
  • 否则,比较关键字,若相等,查找成功,否则根据构造表时所采用的冲突处理方法来找下一个地址,直至找到关键字=k的元素(成功)或者空位置(失败)

建立Hash表的步骤
1. 哈希函数构造方法:(常见的6种)
  • (1)直接定址法:
    函数公式:f(key)=a*key+b (a,b为常数)
    这种方法的优点是:简单,均匀,不会产生冲突。但是需要事先知道关键字的分布情况,适合查找表较小并且连续的情况。
  • (2)除留余数法:
    函数公式:f(key)=key mod p (p<=m)m为哈希表表长。
    这种方法是最常用的哈希函数构造方法。
  • (3) 数字分析法:
    比如我们的11位手机号码“136XXXX7887”,其中前三位是接入号,一般对应不同运营公司的子品牌,如130是联通如意通,136是移动神州行,153是电信等。中间四们是HLR识别号,表示用户归属地。最后四们才是真正的用户号。
    若我们现在要存储某家公司员工登记表,如果用手机号码作为关键字,那么极有可能前7位都是相同的,所以我们选择后面的四们作为哈希地址就是不错的选择。
  • (4)平方取中法:
    故名思义,比如关键字是1234,那么它的平方就是1522756,再抽取中间的3位就是227作为哈希地址。
  • (5)折叠法:
    折叠法是将关键字从左到右分割成位数相等的几个部分(最后一部分位数不够可以短些),然后将这几部分叠加求和,并按哈希表表长,取后几位作为哈希地址。
    比如我们的关键字是9876543210,哈希表表长三位,我们将它分为四组,987|654|321|0 ,然后将它们叠加求和987+654+321+0=1962,再求后3位即得到哈希地址为962,哈哈,是不是很有意思。
  • (6)随机数法:
    函数公式:f(key)= random(key)。
    这里random是随机函数,当关键字的长度不等是,采用这种方法比较合适。
2. 哈希函数冲突解决方法:(常见的2种)

设计得最好的哈希函数也不可能完全避免冲突,当我们在使用哈希函数后发现两个关键字key1!=key2,但是却有f(key1)=f(key2),即发生冲突。

  • S1:开放定址法:(用此法来构造散列表可能会造成数据堆积)
    开放定址法就是一旦发生了冲突,就去寻找下一个空的哈希地址,只要哈希表足够大,空的哈希地址总是能找到,然后将记录插入。这种方法是最常用的解决冲突的方法。
  • S2:链地址法:
    将哈希值相同的数据元素存放在一个链表中,在查找哈希表的过程中,当查找到这个链表时,必须采用线性查找方法

CODE : 实现哈希函数为“除法取余法”,解决冲突为“开放地址线性探测法”的代码实现. http://blog.csdn.net/xiaoping8411/article/details/7706376


[补充] 哈希查找的产生有这样一种背景

有些数据本身是无法排序的(如图像),有些数据是很难比较的(如图像)。如果数据本身是无法排序的,就不能对它们进行比较查找。如果数据是很难比较的,即使采用折半查找,要比较的次数也是非常多的。因此,哈希查找并不查找数据本身,而是先将数据映射为一个整数(它的哈希值),并将哈希值相同的数据存放在同一个位置一即以哈希值为索引构造一个数组



哈希表的拓展补充

  • 除上面介绍的几个简单常用的散列函数,工业界比较著名的哈希函数(算法)包括MD4,MD5,SHA-1,它们都是以MD4为基础设计的
  • 计算Hash查找成功的平均查找长度ASL时,平均的概念是对当前表中非空元素而言,并非是整个表长
  • 计算查找失败的平均查找长度ASL时,平均的概念是针对整个表长


复用性高的代码实现:

[注]
Hash函数: 除留取余
解决冲突: 拉链法

示意图
processon上有

hash.h

typedef DL_NODE_S HASH_NODE_S;      /*hash table node*/
typedef DL_HEAD_S HASH_LIST_S;      /*hash confic list*/

typedef struct tagDL_NODE
{
    struct tagDL_NODE* pstNext;      /* the next element */
    struct tagDL_NODE** ppstPre;     /* the address of previous element's pstNext */
}DL_NODE_S;

typedef struct tagDL_HEAD
{
    DL_NODE_S * pstFirst;        /* the first element */
}DL_HEAD_S;

typedef struct tagHASH_TABLE
{
    ULONG ulSize;
    ULONG (*pfHash)(const VOID*)
    HASH_LIST_S *pstBckt;
}HASH_TABLE_S;

/*******************************************************************/

#define container_of(ptr, type, member)  ({  \
const typeof( ((type*)0)->member) * __mptr = (ptr);   \
(type *)( (char *)__mptr - offsetof(type, member) ); })

#define HASH_ENTRY(ptr, type, member)  container_of(ptr, type, member)

/* 遍历Hash桶 */
#define HASH_TBL_FOREACH(pstTbl, uiIndex)  \
    for ((ulIndex)=0; (ulIndex) < (pstTbl)->ulSize; (ulIndex)++)

#define HASH_GET_INDEX(pstTbl, pKey)    ((pstTbl)->pfHash(pKey))
#define HASH_IS_VALID_INDEX(pstTbl, ulIndex)  ( (ulIndex) < (pstTbl)->ulSize )

/* 获取Hash桶的链头 */
#define HASH_GET_LIST(pstTbl, ulIndex)  (&(pstTbl)->pstBckt[ulIndex])

#define HASH_IS_HASHED(pstNode)  (NULL != (pstNode)->ppstpre )


VOID HASH_Add(INOUT HASH_TABLE_S * pstTbl,
                             IN HASH_NODE_S * pstNode,
                             IN const VOID * pKey)
{
    HASH_LIST_S * pstList;
    ULONG ulIndex;

    ulIndex = HASH_GET_INDEX (pstTbl , pKey);
    pstList = HASH_GET_LIST (pstTbl, ulIndex);
    
    HASH_ListAdd(pstList, pstNode);     // 此为双向链表增加节点的操作
    return;
}

VOID HASH_Del(INOUT HASH_NODE_S * pstNode)
{
    HASH_ListDel ( pstNode );      //双向链表删除节点的操作
    return;
}


通用Hash接口

/* 创建Hash表,哈希表大小ulsize,并挂 Hash回调函数(根据Key计算索引:index = hash(key)) */
HASH_TABLE_S *HASH_Create(IN ULONG ulSize, In ULONG(*pfHash)(const VOID *), IN UINT uiTag)
{
    HASH_TABLE_S * pstTbl;
    ULONG ulLen,i;

    if (0 == ulSize || NULL == pfHash)
    {
        return NULL;
    }

    ulLen = sizeof(HASH_TABLE_S) + sizeof(HASH_LIST_S) * ulSize;
    pstTbl = (HASH_TABLE_S *) malloc(ulLen);

    if (NULL != pstTbl)
    {
        memset(pstTbl, 0, ulLen);

        pstTbl->pstBckt = (HASH_LIST_S *)(pstTbl + 1);        // 指向大小为ulSize的动态空间,指针类型为HASH_LIST_S *
        pstTbl->ulSize = ulSize;
        pstTbl->pfHash = pfHash;

        for (i = 0; i < ulSize; i++)
        {
            DL_Init (&( (pstTbl->pstBckt)[ i ] ));          
        }
    }

    return pstTbl;
}

/* pfFree : Free哈希表节点回调函数 */
VOID HASH_Destory(IN HASH_TABLE_S * pstTbl, IN VOID (*pfFree)(HASH_NODE_S*))
{
    HASH_NODE_S *pstNode;
    HASH_NODE_S *pstNext;
    ULONG  ulIndex;

    if (NULL == pstTbl)
    {
        return;
    }

    if (NULL != pfFree)
    {
        HASH_SCAN_TBL_SAFE(pstTbl, ulIndex, pstNode, pstNext)
        {
            (*pfFree)(pstNode);
        }
    }
    
    free(pstTbl);

    return;
}

/* Hash节点关键字比较回调函数 */
HASH_NODE_S *HASH_Find(IN const HASH_TABLE_S *pstTbl,
                                                 IN const VOID * pKey,
                                                 IN LONG (*pfKeyCmp)(const HASH_NODE_S *, const VOID*) )
{
    HASH_NODE_S * pstNode;
    ULONG ulIndex;

    if ( (NULL == pstTbl) || (NULL == pKey) || (NULL == pfKeyCmp) )
    {
        return NULL;
    }

    ulIndex = HASH_GET_INDEX(pstTbl, pKey);       // 获取Hash桶Index
    if (!HSAH_IS_VALID_INDEX(pstTbl, ulIndex))
    {
        return NULL;
    }

    HASH_SCAN_BCKT(pstTbl, ulIndex, pstNode)
    {
        if (0 == (*pfKeyCmp)(pstNode, pKey))
        {
            break;
        }
    }
}


CODE
Hash桶的实现代码:
/***************************************************************************/

/* identity user HASH table*/
typedef struct tagIDENTITY_USER_TABLE
{
    UINT uiUserCount;                              /* user count */ 
    HASH_TABLE_S *pstUserHashTbl;    /* user HASH table */
}IDENTITY_USER_TABLE_S;

/* identity user hash node truct */
typedef struct tagIDENTITY_USER_NODE
{
    HASH_NODE_S stHashNode;                             /* HASH node */
    IDENTITY_USER_S *pstIdentityUser;                  /* 身份识别用户结构 */
}IDENTITY_USER_NODE_S;

/* 根据key获取索引 */
_identityHashUser()
{
}

/* 全局User哈希表指针 */
IDENTITY_USER_TABLE_S *g_pstUserHashTbl = NULL;

/* 初始化 全局User哈希表 */
STATIC ULONG _identity_CreateUserTable(VOID)
{
    IDENTITY_USER_TABLE_S * pstUserHashStruct = NULL;
    HASH_TABLE_S * pstUserHashTbl = NULL;
    pstUserHashStruct  = (IDENTITY_USER_TABLE_S *)malloc(sizeof(IDENTITY_USER_TABLE_S ));
    if (NULL == pstUserHashStruct)
    {
        return ERROR_FAILED;
    }

    pstUserHashTbl  = HASH_Create(IDENTITY_USER_HASH_BUCKET_SIZE,  _identityHashUser);
    if (NULL != pstUserHashTbl)
    {
        memset(pstUserHashStruct , 0, sizeof(IDENTITY_USER_TABLE_S ));
        pstUserHashStruct ->uiUserCount = 0;
        pstUserHashStruct ->pstUserHashTbl = pstUserHashTbl;
        g_pstUserHashTbl = pstUserHashStruct;

        return ERROR_SUCCESS;
    }

    free(pstUserHashStruct);

    return ERROR_FAILED;
}

/* 释放 全局User哈希表 */
STATIC VOID _identity_DestoryUserTable(VOID)
{
    /* 释放Hash桶*/
    if (NULL != g_pstUserHashTbl)
    {
        free(g_pstUserHashTbl);
        g_pstUserHashTbl = NULL;
    }

    return;
}

/*用户删除Hash节点回调函数 */
VOID _identityUserHashFreeCbk(HASH_NODE_S *pstHashNode)
{
    IDENTITY_USER_NDOE_S *pstUserDataNode;
    Assert(NULL != pstHashNode);

    pstUserDataNode = HASH_ENTRY(pstHashNode, IDENTITY_USER_NODE_S, stHashNode);
    HASH_Del(&(pstUserDataNode ->stHashNode));
    
    /* 释放用户空间 */
    if (NULL != pstUserDataNode)
    {
        free(pstUserDataNode);
    }

    return;
}

ULONG IDENTITY_data_Init()
{
    ULONG ulErrCode = ERROR_SUCCESS;
    
    /* 初始化Hash表 */
    ulErrCode = _identity_CreateUserTable();

    return ulErrCode
}

VOID IDENTITY_data_Fini()
{
    /* 销毁Hash表节点 */
    Hash_Destory(g_pstUserHashTbl ->pstUserHashTbl, _identityUserHashFreeCbk);
    
    /* 释放HASH表结构*/
    _identity_DestoryUserTable();

    return;
}

其他补充

随着硬件价格的下降,当前互联网服务提供商通常采用服务器集群来提高服务的质量,这就带来了数据的分布式存储问题。
如何快速定位数据在集群中的存储位置,关系到集群的性能

如下介绍几种常见的分布式存储方式
  1. 普通集群:将固定的key映射到固定的结点中,结点只存放各自key的数据,此法将key和结点的关系作为一张单独的表格进行维护
    有一个结点宕机,结点上的数据需要迁移,表格也需要重新维护
    问题:查一个Key值需要遍历整个表格,直到找到存放Key值的结点!查找速度慢
  2. Hash集群
  3. 一致性哈希:是一种哈希算法,简单来说,在移除或添加一个结点时,它能够尽可能小的改变已存在Key的映射关系
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容

  • 一.概念 哈希表就是一种以 键-值(key-indexed) 存储数据的结构,我们只要输入待查找的值即key,即可...
    lfp901020阅读 2,956评论 0 2
  • 哈希表定义 散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结...
    n油炸小朋友阅读 4,844评论 0 22
  • 分析阅读的第六个规则:将一本书中最重要的句子圈出来,找出其中的主旨。 一本书中大部分的文字,作者一读就可以明白,只...
    飞鹰于凯阅读 154评论 0 0
  • 我犹如一片枫叶静静的挂在枝头 直到有一天你从我身下走过 我会静静的飘落在你的肩头 一直这样安...
    情已断心已冷阅读 147评论 0 1
  • 斜阳冉冉 云在缓缓流淌长堤上青草散发阵阵馨香回首望谁与我渡横塘 月台花已谢朱户深锁窗莫问飘渺孤鸿影何方 平桥外满城...
    宁木紫菀阅读 440评论 8 8