1 哈希表原理

这里不讲高深理论,只说直观感受。哈希表的目的就是为了根据数据的部分内容(关键字),直接计算出存放完整数据的内存地址。

试想一下,如果从链表中根据关键字查找一个元素,那么就需要遍历才能得到这个元素的内存地址,如果链表长度很大,查找就需要更多的时间.

void* list_find_by_key(list,key)
{
for(p=list;p!=NULL; p=p->next){
if(p->key == key){
return p;
}
return p;
}
}

为了解决根据关键字快速找到元素的存放地址,哈希表应运而生。它通过某种算法(哈希函数)直接根据关键字计算出元素的存放地址,由于无需遍历,所以效率很高。

void* hash_table_find_by_key(table, key)
{
void* p = hash(key);
return p;
}

当然,上面的伪代码忽略了一个重要的事实:那就是不同的关键字可能产生出同样的hash值。

hash("张三") = 23;
hash("李四") = 30;
hash("王五") = 23;

这种情况称为“冲突”,为了解决这个问题,有两种方法:一是链式扩展;二是开放寻址。这里只讲第一种:链式扩展。

也就是把具有相同hash值的元素放到一起,形成一个链表。这样在插入和寻找数据的时候就需要进一步判断。

void* hash_table_find_by_key(table, key)
{
void* list = hash(key);
return list_find_by_key(list, key);
}

需要注意的是,只要hash函数合适,这里的链表通常都长度不大,所以查找效率依然很高。

下图是一个哈希表运行时内存布局:

2 纯C实现源码

实际工作中,大多数情况下,关键字都是字符串的形式,而大多数教科书上却使用整数关键字来举例,这非常脱离实际。为此,本人决定使用纯C语言开发一个哈希表结构,供大家参考。主要特点:

  • 基于接口开发,对外彻底隐藏实现细节
  • 具有自动释放客户结构内存的回调功能
  • 采用经典的Times33哈希算法
  • 采用纯C开发,可供C和C++客户使用

HashTable.h 头文件

#pragma once
typedef struct HashTable HashTable; #ifdef __cplusplus
extern "C" {
#endif /* new an instance of HashTable */
HashTable* hash_table_new(); /*
delete an instance of HashTable,
all values are removed auotmatically.
*/
void hash_table_delete(HashTable* ht); /*
add or update a value to ht,
free_value(if not NULL) is called automatically when the value is removed.
return 0 if success, -1 if error occurred.
*/
#define hash_table_put(ht,key,value) hash_table_put2(ht,key,value,NULL);
int hash_table_put2(HashTable* ht, char* key, void* value, void(*free_value)(void*)); /* get a value indexed by key, return NULL if not found. */
void* hash_table_get(HashTable* ht, char* key); /* remove a value indexed by key */
void hash_table_rm(HashTable* ht, char* key); #ifdef __cplusplus
}
#endif

HashTable.c 实现文件

#include "HashTable.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h> #define TABLE_SIZE (1024*1024) /* element of the hash table's chain list */
struct kv
{
struct kv* next;
char* key;
void* value;
void(*free_value)(void*);
}; /* HashTable */
struct HashTable
{
struct kv ** table;
}; /* constructor of struct kv */
static void init_kv(struct kv* kv)
{
kv->next = NULL;
kv->key = NULL;
kv->value = NULL;
kv->free_value = NULL;
}
/* destructor of struct kv */
static void free_kv(struct kv* kv)
{
if (kv) {
if (kv->free_value) {
kv->free_value(kv->value);
}
free(kv->key);
kv->key = NULL;
free(kv);
}
}
/* the classic Times33 hash function */
static unsigned int hash_33(char* key)
{
unsigned int hash = 0;
while (*key) {
hash = (hash << 5) + hash + *key++;
}
return hash;
} /* new a HashTable instance */
HashTable* hash_table_new()
{
HashTable* ht = malloc(sizeof(HashTable));
if (NULL == ht) {
hash_table_delete(ht);
return NULL;
}
ht->table = malloc(sizeof(struct kv*) * TABLE_SIZE);
if (NULL == ht->table) {
hash_table_delete(ht);
return NULL;
}
memset(ht->table, 0, sizeof(struct kv*) * TABLE_SIZE); return ht;
}
/* delete a HashTable instance */
void hash_table_delete(HashTable* ht)
{
if (ht) {
if (ht->table) {
int i = 0;
for (i = 0; i<TABLE_SIZE; i++) {
struct kv* p = ht->table[i];
struct kv* q = NULL;
while (p) {
q = p->next;
free_kv(p);
p = q;
}
}
free(ht->table);
ht->table = NULL;
}
free(ht);
}
} /* insert or update a value indexed by key */
int hash_table_put2(HashTable* ht, char* key, void* value, void(*free_value)(void*))
{
int i = hash_33(key) % TABLE_SIZE;
struct kv* p = ht->table[i];
struct kv* prep = p; while (p) { /* if key is already stroed, update its value */
if (strcmp(p->key, key) == 0) {
if (p->free_value) {
p->free_value(p->value);
}
p->value = value;
p->free_value = free_value;
break;
}
prep = p;
p = p->next;
} if (p == NULL) {/* if key has not been stored, then add it */
char* kstr = malloc(strlen(key) + 1);
if (kstr == NULL) {
return -1;
}
struct kv * kv = malloc(sizeof(struct kv));
if (NULL == kv) {
free(kstr);
kstr = NULL;
return -1;
}
init_kv(kv);
kv->next = NULL;
strcpy(kstr, key);
kv->key = kstr;
kv->value = value;
kv->free_value = free_value; if (prep == NULL) {
ht->table[i] = kv;
}
else {
prep->next = kv;
}
}
return 0;
} /* get a value indexed by key */
void* hash_table_get(HashTable* ht, char* key)
{
int i = hash_33(key) % TABLE_SIZE;
struct kv* p = ht->table[i];
while (p) {
if (strcmp(key, p->key) == 0) {
return p->value;
}
p = p->next;
}
return NULL;
} /* remove a value indexed by key */
void hash_table_rm(HashTable* ht, char* key)
{
int i = hash_33(key) % TABLE_SIZE; struct kv* p = ht->table[i];
struct kv* prep = p;
while (p) {
if (strcmp(key, p->key) == 0) {
free_kv(p);
if (p == prep) {
ht->table[i] = NULL;
}
else {
prep->next = p->next;
}
}
prep = p;
p = p->next;
}
}

3 测试程序

下面是测试程序源码,基于C++。

测试程序test.cpp

#include <stdio.h>
#include <stdlib.h> #include "HashTable.h" // 要放入哈希表中的结构体
struct Student
{
int age;
float score;
char name[32];
char data[1024 * 1024* 10];
}; // 结构体内存释放函数
static void free_student(void* stu)
{
free(stu);
} // 显示学生信息的函数
static void show_student(struct Student* p)
{
printf("姓名:%s, 年龄:%d, 学分:%.2f\n", p->name, p->age, p->score);
} int main()
{
// 新建一个HashTable实例
HashTable* ht = hash_table_new();
if (NULL == ht) {
return -1;
} // 向哈希表中加入多个学生结构体
for (int i = 0; i < 100; i++) {
struct Student * stu = (struct Student*)malloc(sizeof(struct Student));
stu->age = 18 + rand()%5;
stu->score = 50.0f + rand() % 100;
sprintf(stu->name, "同学%d", i);
hash_table_put2(ht, stu->name, stu, free_student);
} // 根据学生姓名查找学生结构
for (int i = 0; i < 100; i++) {
char name[32];
sprintf(name, "同学%d", i);
struct Student * stu = (struct Student*)hash_table_get(ht, name);
show_student(stu);
} // 销毁哈希表实例
hash_table_delete(ht);
return 0;
}
 

C实现哈希表的更多相关文章

  1. [PHP内核探索]PHP中的哈希表

    在PHP内核中,其中一个很重要的数据结构就是HashTable.我们常用的数组,在内核中就是用HashTable来实现.那么,PHP的HashTable是怎么实现的呢?最近在看HashTable的数据 ...

  2. Java 哈希表运用-LeetCode 1 Two Sum

    Given an array of integers, find two numbers such that they add up to a specific target number. The ...

  3. ELF Format 笔记(十五)—— 符号哈希表

    ilocker:关注 Android 安全(新手) QQ: 2597294287 符号哈希表用于支援符号表的访问,能够提高符号搜索速度. 下表用于解释该哈希表的组织,但该格式并不属于 ELF 规范. ...

  4. Java基础知识笔记(一:修饰词、向量、哈希表)

    一.Java语言的特点(养成经常查看Java在线帮助文档的习惯) (1)简单性:Java语言是在C和C++计算机语言的基础上进行简化和改进的一种新型计算机语言.它去掉了C和C++最难正确应用的指针和最 ...

  5. 什么叫哈希表(Hash Table)

    散列表(也叫哈希表),是根据关键码值直接进行访问的数据结构,也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度.这个映射函数叫做散列函数,存放记录的数组叫做散列表. - 数据结构 ...

  6. 【哈希表】CodeVs1230元素查找

    一.写在前面 哈希表(Hash Table),又称散列表,是一种可以快速处理插入和查询操作的数据结构.哈希表体现着函数映射的思想,它将数据与其存储位置通过某种函数联系起来,其在查询时的高效性也体现在这 ...

  7. openssl lhash 数据结构哈希表

    哈希表是一种数据结构,通过在记录的存储位置和它的关键字之间建立确定的对应关系,来快速查询表中的数据: openssl lhash.h 为我们提供了哈希表OPENSSL_LHASH 的相关接口,我们可以 ...

  8. Berkeley DB的数据存储结构——哈希表(Hash Table)、B树(BTree)、队列(Queue)、记录号(Recno)

    Berkeley DB的数据存储结构 BDB支持四种数据存储结构及相应算法,官方称为访问方法(Access Method),分别是哈希表(Hash Table).B树(BTree).队列(Queue) ...

  9. python数据结构与算法——哈希表

    哈希表 学习笔记 参考翻译自:<复杂性思考> 及对应的online版本:http://greenteapress.com/complexity/html/thinkcomplexity00 ...

  10. [转]:Delphi 中的哈希表(1): THashedStringList

    unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms ...

随机推荐

  1. AD域策略启动关机脚本不执行的注意事项

    其实主要是脚本路径的问题. 错误一: 直接使用右侧的添加按钮,添加了预控的本地路径.如上图第二行. 错误二: 直接使用右侧的添加按钮,添加了脚本的网络路径,如上图第三行. 正确的方法: 点击下方的显示 ...

  2. Find minimum number of people to reach to spread a message across all people in twitter

    Considering that I'ld would like to spread a promotion message across all people in twitter. Assumin ...

  3. spring boot如何打印mybatis的执行sql

    方案一 application.properties配置: logging.level.com,后面的路径指的是mybatis对应的方法接口所在的包.并不是mapper.xml所在的包. 1. log ...

  4. 2019年Java面试题基础系列228道(6)

    51.ArrayList 与 LinkedList 的不区别? 最明显的区别是 ArrrayList 底层的数据结构是数组,支持随机访问,而LinkedList 的底层数据结构书链表,不支持随机访问. ...

  5. PHP与Cookie

    不管什么语言写的cookie,本质上没区别. cookie 常用于识别用户.cookie 是服务器留在用户计算机中的小文件.每当相同的计算机通过浏览器请求页面时,它同时会发送 cookie.通过 PH ...

  6. Cannot assign requested address的解决办法

    今天想试一下redis,写了个程序,对redis连续进行100000访问,却出现以了Cannot assign requested address的问题,我起先是以为是redis的问题(可能承受不了这 ...

  7. 038 Android Magicindicator开源框架实现viewpager底部圆形指示器

    1.Magicindicator介绍 Magicindicator是一个强大.可定制.易扩展的 ViewPager 指示器框架.是ViewPagerIndicator.TabLayout.PagerS ...

  8. 022 Android .9图片的含义及制作教程

    1.图片(.9.png格式)的概念 (1)9patch图片是andriod app开发里一种特殊的图片形式,文件的扩展名为:.9.png (2)9patch图片的作用就是在图片拉伸的时候保证其不会失真 ...

  9. Java中关于时间日期格式保存到mysql的问题

    首先在设置数据库的时间日期字段的时候要先确定好采用何种类型,DATETIME. TIMESTAMP.DATE.TIME.YEAR. 其中datetime.time用的比较多,对应java中生成的poj ...

  10. 消息中间件——RabbitMQ(十)RabbitMQ整合SpringBoot实战!(全)

    前言 1. SpringBoot整合配置详解 publisher-confirms,实现一个监听器用于监听Broker端给我们返回的确认请求:RabbitTemplate.ConfirmCallbac ...