哈希表的意义就是快速定位查找,使用key值快速定位出数据位置。
流程图
查找时根据key值算出nodes的下标地址:(h->can_ch + h->can_id) % bucktes;,这样就能快速定位,时间复杂度O(1)。
代码实现
头文件:hash.h
#ifndef __NEV__HEAD__
#define __NEV__HEAD__
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
/*此方法采用的是连地址法*/
/*为了不将hash结构暴露在外面,所以就必须typedef*/
typedef struct hash hash_t;
//这是一个hash函数指针,对用户来说应该需要使用一个合适的hash函数
//第一个参数unsigned int指的是桶的大小 第二个参数是key值
typedef unsigned int(*hash_func_t)(unsigned int,void *);
/*返回的是hash表指针,创建hash表*/
hash_t *hash_alloc(unsigned int,hash_func_t);
void *hash_lookup_entry(hash_t *,void *,unsigned int );
void hash_add_entry(hash_t *, void *, unsigned int ,void *, unsigned int );
void hash_free_entry(hash_t *,void *,int );
unsigned int hash_func(unsigned int bucktes, void *key);
struct nev_hash_key {
int can_ch; /* can通道号 */
unsigned int can_id; /* canID */
};
struct nev_hash_s {
int can_ch; /* can通道号 */
unsigned int can_id; /* canID */
int fd; /* fd */
};
#define MAX_BUCKETS 10
#define CAN_CH 1
#define CAN_FD 8848
#endif
hash文件:hash.c
#include "hash.h"
typedef struct hash_node {
void *key;//查找依据
void *data;//数据块,一般是结构体
struct hash_node *prev;
struct hash_node *next;
}hash_node_t;
struct hash {
unsigned int buckets;//桶的个数(大小)
hash_func_t hash_func;//hash函数指针
hash_node_t **nodes;//hash表中存放的链表地址
};
//由于获得桶,和得到node节点的接口并不需要外部看见,所以定义成两个内部函数
/*根据key,得到桶*/
static hash_node_t **hash_get_buckets(hash_t *hash,void *key);
/*根据key得到node节点*/
static hash_node_t * hash_get_node_by_key(hash_t *hash,void *key,unsigned int key_size);
//创建hash表
hash_t *hash_alloc(unsigned int buckets, hash_func_t hash_func)
{
hash_t *hash = (hash_t *)malloc(sizeof(hash_t));
hash->buckets = buckets;
hash->hash_func = hash_func;
unsigned int size = buckets * sizeof(hash_node_t *);
hash->nodes = (hash_node_t **)malloc(size);
memset(hash->nodes,0x00, size);
return hash;
}
//判断
void* hash_lookup_entry(hash_t *hash, void *key, unsigned int key_size)
{
hash_node_t *node = hash_get_node_by_key(hash,key, key_size);
if (NULL == node) return NULL;
return node->data;
}
//添加hash节点
void hash_add_entry(hash_t *hash, void *key, unsigned int key_size,void *data, unsigned int data_size)
{
//首先需要查找看此数据项是否存在,如果存在则表明重复了,需要返回
if (hash_lookup_entry(hash, key, key_size)) {
printf("duplicate hash key\n");
return;
}
//创建节点,申请内存
hash_node_t *node = (hash_node_t*)malloc(sizeof(hash_node_t));
node->next = NULL;
node->prev = NULL;
node->key = malloc(key_size);
memcpy(node->key,key,key_size);
node->data = malloc(data_size);
memcpy(node->data, data, data_size);
//采用头插法,将节点插入到hash表中
//1.首先得取到桶
hash_node_t **bucket = hash_get_buckets(hash,key);
//如果没有节点
if (*bucket == NULL) {
*bucket = node;
}
else {
node->next = *bucket;
(*bucket)->prev = node;
*bucket = node;
}
}
//释放节点
void hash_free_entry(hash_t *hash, void *key, int key_size)
{
//释放节点首先得找到节点,
hash_node_t *node = hash_get_node_by_key(hash,key,key_size);
if (node == NULL) return;
if (node->prev) {
node->prev->next = node->next;
}
else{
//如果是第一个节点,就必须获得桶节点
hash_node_t **bucket = hash_get_buckets(hash,key);
*bucket = node->next;
}
if (node->next) {
node->next->prev = node->prev;
}
free(node->key);
free(node->data);
free(node);
}
//根据key得到buckets号
static hash_node_t **hash_get_buckets(hash_t *hash, void *key)
{
unsigned int buckets = hash->hash_func(hash->buckets,key);
if (buckets >= hash->buckets){
printf("bad buckets loockup %d %d\n",hash->buckets, buckets);
}
return &(hash->nodes[buckets]);
}
//通过key值,得到node节点
static hash_node_t * hash_get_node_by_key(hash_t *hash, void *key, unsigned int key_size)
{
hash_node_t **buckets = hash_get_buckets(hash,key);
hash_node_t *node = *buckets;
if (node == NULL) return NULL;
//没有找到,有那两种可能,一种是节点就不存在,另一种是没有找到
while (node != NULL && memcmp(node->key, key, key_size) != 0) {
node = node->next;
}
return node;
}
//返回下标地址
unsigned int hash_func(unsigned int bucktes, void *key)
{
struct nev_hash_key *h = (struct nev_hash_key *)key;
return (h->can_ch + h->can_id) % bucktes;
}
main文件:main.c
#include <stdio.h>
#include "hash.h"
int main()
{
int i = 0;
//创建hash表
hash_t *hash = hash_alloc(MAX_BUCKETS, hash_func);
if(NULL == hash) {
printf("error hash alloc failed! return.");
return;
}
//添加节点
for(i=0; i<MAX_BUCKETS; i++) {
struct nev_hash_s *c = NULL;
struct nev_hash_key key;
c = calloc(1, sizeof(struct nev_hash_s));
if(NULL == c) {
printf("error calloc failed! return.");
return;
}
c->can_ch = CAN_CH;
c->can_id = i;
c->fd = i;
key.can_ch = CAN_CH;
key.can_id = i;
if(hash != NULL)
hash_add_entry(hash, &key, sizeof(key), c, sizeof(struct nev_hash_s));
else
printf("eason hash == NULL,ERROR");
free(c);
}
//索引节点
if (hash != NULL) {
printf("find fd=");
for(i=0; i<MAX_BUCKETS; i++) {
struct nev_hash_key key;
struct nev_hash_s *cc = NULL;
key.can_ch = CAN_CH;
key.can_id = i;
cc = (struct nev_hash_s *)hash_lookup_entry(hash, &key, sizeof(key));
if (cc != NULL) {
printf("%d/%d ",i,cc->fd);
}
else {
printf("no hash %d\n",i);
}
}
printf("\n");
}
return;
}
编译:gcc main.c hash.c -o test
测试打印
[yubo.wang@localhost hash-test]$ ./test
find fd=0/0 1/1 2/2 3/3 4/4 5/5 6/6 7/7 8/8 9/9
扩展
散列函数构造方法:
直接定址法:f(key)=axkey+b (a,b为常数)
数字分析法:抽取数字串的后面4位,应用在电话号码
平方取中法:key平方后取中间的3位数
折叠法:数字串分成几组然后相加得出数字
除留余数法:key%p,
处理散列冲突的方法:
开放地址发:一旦发生冲突就寻找下一个空的散列地址,只要散列表足够大就能装下
链地址法:把冲突的地址形成一个链表挂在同一个地址上面
公共溢出区法:把冲突的地址添加在一个新表中维护,查找时先查找基本表再查找溢出表
销毁hash表
//销毁hash表
void hash_free(hash_t *hash)
{
int i;
hash_node_t *temp = NULL;
hash_node_t *node = NULL;
hash_node_t **buckets = NULL;
if(NULL == hash) {
printf("free hash is NULL,return!\n");
return;
}
//遍历node节点并释放
for(i=0; i<hash->buckets; i++) {
buckets = &(hash->nodes[i]);
if (*buckets == NULL) continue;
node = *buckets;
while (node != NULL) {
temp = node;
node = node->next;
free(temp->key);
free(temp->data);
free(temp);
}
*buckets = NULL;
}
//释放nodes节点二级指针
free(hash->nodes);
hash->nodes = NULL;
}
使用方法:
//释放hash
hash_free(hash);
free(hash);
hash = NULL;