Android BlueDroid分析: OSI中的HashMap的实现

2024-03-04 12:18

本文主要是介绍Android BlueDroid分析: OSI中的HashMap的实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

说明

hash map在C语言标准库中并没有封装, 不像其他语言那么方便, 例如python中有Dictionary, 而hashmap又非常有用, 因此Bluedroid自己封装了一套.封装实现的文件列表如下:

osi/src/hash_functions.c  
osi/src/hash_map.c
osi/include/hash_functions.h
osi/include/hash_map.h
其中hash_functions.c中是对几个 特殊类型的hashmap封装. 因此我们先说明hash map, 然后再看hash functions.

HashMap概念与术语

首先如果要实现hashmap,那么需要做什么呢? 下面是引用StackOverFlow中的回答:

Well if you know the basics behind them, it shouldn't be too hard.Generally you create an array called "buckets" that contain the key and value, with an optional pointer to create a linked list.When you access the hash table with a key, you process the key with a custom hash function which will return an integer. You then take the modulus of the result and that is the location of your array index or "bucket". Then you check the unhashed key with the stored key, and if it matches, then you found the right place.Otherwise, you've had a "collision" and must crawl through the linked list and compare keys until you match. (note some implementations use a binary tree instead of linked list for collisions).Check out this fast hash table implementation:http://attractivechaos.awardspace.com/khash.h.html

简单来说就是:

  • A1. 创建: 创建一个数组bucket,其中bucket包含有key value对, 还有一个可选的list指针
  • A2. 访问查询: 使用key来查询时, 先将key转换(通过自定义的转换函数)成一个整数, 然后取模得到一个数, 再使用这个数到bucket(或者数组对应下表元素)查找是否有相同的key.如果有且只有一个的话,那么就找到了.
  • A3. 如果一个key找到value的list不为空,即有发生了哈希冲突,那么就需要遍历List中的所有元素.

上面的取模仅仅只是其中的一种哈希函数(散列函数), 可以参考下面文章中的说明:哈希表的C实现(一)

关于hash冲突(Hash collision), 也可以参考上面链接中的说明.下面是其中的链接地址法的引用:

链地址法:举例说明: 设有 8 个元素 { a,b,c,d,e,f,g,h } ,采用某种哈希函数得到的地址分别为: {0 , 2 , 4 , 1 , 0 , 8 , 7 , 2} ,当哈希表长度为 10 时,采用链地址法解决冲突的哈希表如下图所示。


                 (下面文中说的前面图中,均指这个图片)


结合这个图片来说, 前面的A2与A3这两个步骤就是横向与纵向的查找,先是纵向的查找(0~8, bucket), 然后如果有冲突, 例如index=0的就有a与e两个, 这个时候就再横向的List遍历查找.

对于哈希冲突还有另一种使用表来完成的做法可以参考: 哈希桶的分析与实现

结构定义

结合前面的说明,对于代码中的桶的定义也就很容易看明白了. 可以认为每一个hash都是由下面这个hash_map_entry来表示的, 这个是最小的单元,含有key value(这里面为*data指针)对.

typedef struct hash_map_entry_t {const void *key;void *data;const hash_map_t *hash_map;  // 指向所在的hash_map
} hash_map_entry_t;

hash_map_bucket_t:为list指针, 即前面图中的横向结构

hash_map_t

struct hash_map_t;typedef struct hash_map_bucket_t {list_t *list;              // 链表指针,表示
} hash_map_bucket_t;typedef struct hash_map_t {hash_map_bucket_t *bucket;  // 是一个指向list*的指针, 即相当于List指针数组size_t num_bucket;          // 有多少个entry, 即相当于前面图中的纵向元素.size_t hash_size;           // 有多少个hash元素(key/value对),即为前面图中的横向所有List中的hash总数
																														//   这个统计的是前面的hash_map_entry_t元素hash_index_fn hash_fn;      // 获取取模强的整数函数key_free_fn key_fn;         data_free_fn data_fn;const allocator_t *allocator;key_equality_fn keys_are_equal; //key的判断函数,即前面A1中说到的自定义的equal判断函数指针
} hash_map_t;

hash的各种功能函数

hash_map.c中的information如下, 其中我们需要注意的是new/clear/free, 然后使用的时候会有set/erase,查询的时候是get/empty/size/has_key:

查找是否有key这个/条item

第一步和前面说的A1是对应的,先取模获取index, 然后根据index作为数组下标获取bucket的List, 然后再到List中遍历:

bool hash_map_has_key(const hash_map_t *hash_map, const void *key) {assert(hash_map != NULL);hash_index_t hash_key = hash_map->hash_fn(key) % hash_map->num_bucket;list_t *hash_bucket_list = hash_map->bucket[hash_key].list;hash_map_entry_t *hash_map_entry = find_bucket_entry_(hash_bucket_list, key);return (hash_map_entry != NULL);
}

里面的find_bucket_entry_, 即为前面A3步骤的函数实现为find_bucket_entry_, 传入的参数为List + key:

static hash_map_entry_t * find_bucket_entry_(list_t *hash_bucket_list,const void *key) {if (hash_bucket_list == NULL)return NULL;for (const list_node_t *iter = list_begin(hash_bucket_list);iter != list_end(hash_bucket_list);iter = list_next(iter)) {hash_map_entry_t *hash_map_entry = (hash_map_entry_t *)list_node(iter);if (hash_map_entry->hash_map->keys_are_equal(hash_map_entry->key, key)) {return hash_map_entry;}}return NULL;
}

向hashmap中插入一个key/value Pair

下面是函数以及注释

bool hash_map_set(hash_map_t *hash_map, const void *key, void *data) {assert(hash_map != NULL);assert(data != NULL);hash_index_t hash_key = hash_map->hash_fn(key) % hash_map->num_bucket; //哈希函数获取Index整数, 即得到前面图中纵向的入口// 如果这个key还不存在,那么创建一个List Entry,即图中纵向创建一个Entryif (hash_map->bucket[hash_key].list == NULL) {hash_map->bucket[hash_key].list = list_new_internal(bucket_free_, hash_map->allocator);if (hash_map->bucket[hash_key].list == NULL)return false;}list_t *hash_bucket_list = hash_map->bucket[hash_key].list; //获取横向的List用于相同key的操作hash_map_entry_t *hash_map_entry = find_bucket_entry_(hash_bucket_list, key);//已经存在相同的值了, 那么先remove掉原来的,然后覆盖进去if (hash_map_entry) {// Calls hash_map callback to delete the hash_map_entry.bool rc = list_remove(hash_bucket_list, hash_map_entry);assert(rc == true);} else { // 还不存在,先增加hash element的计数hash_map->hash_size++; }hash_map_entry = hash_map->allocator->alloc(sizeof(hash_map_entry_t)); //为存放key/value即元素分配空间if (hash_map_entry == NULL)return false;// 完成element的填充hash_map_entry->key = key;hash_map_entry->data = data;hash_map_entry->hash_map = hash_map;// 将这个hash元素追加到list中,完成添加工作return list_append(hash_bucket_list, hash_map_entry);
}
弄明白了这两个函数后其他函数都比较容易了, 就不再贴出.

hash_functions.c的分析

前面是hashmap的实现分析, 里面实现了对hash的创建查询等, 而hash_functions.c中则是对一些特别的类型的进行的一些key的预分配, 这个如何Linux Driver中的DMA与CMA很类似, 相当于特殊化几个key地址值, 而这个特殊化和Linux中的线性映射区Physical Address到Virtual Memory Address的很类似(这里面由+0xX0000000的偏移,变成了乘以某个数值), :

#include "osi/include/hash_functions.h"hash_index_t hash_function_naive(const void *key) {return (hash_index_t)key;
}
// 对于Interger与pointer将key的地址值特殊化, 
hash_index_t hash_function_integer(const void *key) {return ((hash_index_t)key) * 2654435761;
}hash_index_t hash_function_pointer(const void *key) {return ((hash_index_t)key) * 2654435761;
}hash_index_t hash_function_string(const void *key) {hash_index_t hash = 5381;const char *name = (const char *)key;size_t string_len = strlen(name);for (size_t i = 0; i < string_len; ++i)hash = ((hash << 5) + hash ) + name[i];return hash;
}

例如:

bta_alarm_hash_map = hash_map_new(BTA_ALARM_HASH_MAP_SIZE,hash_function_pointer, NULL, (data_free_fn)alarm_free, NULL);

hash_map_new的实现为:

hash_map_t *hash_map_new(size_t num_bucket,hash_index_fn hash_fn,key_free_fn key_fn,data_free_fn data_fn,key_equality_fn equality_fn) {return hash_map_new_internal(num_bucket, hash_fn, key_fn, data_fn, equality_fn, &allocator_calloc);
}

即: hash_index_fn = hash_function_pointer. 这个有什么用处呢?

例如刚刚给出的这个例子位于:bta_sys_main.c中, 其使用如下:

void bta_sys_start_timer(TIMER_LIST_ENT *p_tle, UINT16 type, INT32 timeout_ms) {assert(p_tle != NULL);// Get the alarm for this p_tle.pthread_mutex_lock(&bta_alarm_lock);if (!hash_map_has_key(bta_alarm_hash_map, p_tle)) {hash_map_set(bta_alarm_hash_map, p_tle, alarm_new());//p_tle为key,new了一个alarm放在这个key对应的List上面}pthread_mutex_unlock(&bta_alarm_lock);alarm_t *alarm = hash_map_get(bta_alarm_hash_map, p_tle);if (alarm == NULL) {LOG_ERROR("%s unable to create alarm.", __func__);return;}p_tle->event = type;p_tle->ticks = timeout_ms;alarm_set(alarm, (period_ms_t)timeout_ms, bta_alarm_cb, p_tle);
}

这里p_tle为key, 有一组的timer在这个key对应的List的element上, 假设HCI下面的Controller的buffer size很大,即可以发送与处理多条commands, 那么Host需要等待这些commands的Event回来, 那么每个commands发送完成后都有一个timer定时,这些timer均相关, 那么就可以用这种方式来管理.

这篇关于Android BlueDroid分析: OSI中的HashMap的实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/773139

相关文章

SpringBoot集成redisson实现延时队列教程

《SpringBoot集成redisson实现延时队列教程》文章介绍了使用Redisson实现延迟队列的完整步骤,包括依赖导入、Redis配置、工具类封装、业务枚举定义、执行器实现、Bean创建、消费... 目录1、先给项目导入Redisson依赖2、配置redis3、创建 RedissonConfig 配

Python的Darts库实现时间序列预测

《Python的Darts库实现时间序列预测》Darts一个集统计、机器学习与深度学习模型于一体的Python时间序列预测库,本文主要介绍了Python的Darts库实现时间序列预测,感兴趣的可以了解... 目录目录一、什么是 Darts?二、安装与基本配置安装 Darts导入基础模块三、时间序列数据结构与

Python使用FastAPI实现大文件分片上传与断点续传功能

《Python使用FastAPI实现大文件分片上传与断点续传功能》大文件直传常遇到超时、网络抖动失败、失败后只能重传的问题,分片上传+断点续传可以把大文件拆成若干小块逐个上传,并在中断后从已完成分片继... 目录一、接口设计二、服务端实现(FastAPI)2.1 运行环境2.2 目录结构建议2.3 serv

C#实现千万数据秒级导入的代码

《C#实现千万数据秒级导入的代码》在实际开发中excel导入很常见,现代社会中很容易遇到大数据处理业务,所以本文我就给大家分享一下千万数据秒级导入怎么实现,文中有详细的代码示例供大家参考,需要的朋友可... 目录前言一、数据存储二、处理逻辑优化前代码处理逻辑优化后的代码总结前言在实际开发中excel导入很

SpringBoot+RustFS 实现文件切片极速上传的实例代码

《SpringBoot+RustFS实现文件切片极速上传的实例代码》本文介绍利用SpringBoot和RustFS构建高性能文件切片上传系统,实现大文件秒传、断点续传和分片上传等功能,具有一定的参考... 目录一、为什么选择 RustFS + SpringBoot?二、环境准备与部署2.1 安装 RustF

Nginx部署HTTP/3的实现步骤

《Nginx部署HTTP/3的实现步骤》本文介绍了在Nginx中部署HTTP/3的详细步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学... 目录前提条件第一步:安装必要的依赖库第二步:获取并构建 BoringSSL第三步:获取 Nginx

MyBatis Plus实现时间字段自动填充的完整方案

《MyBatisPlus实现时间字段自动填充的完整方案》在日常开发中,我们经常需要记录数据的创建时间和更新时间,传统的做法是在每次插入或更新操作时手动设置这些时间字段,这种方式不仅繁琐,还容易遗漏,... 目录前言解决目标技术栈实现步骤1. 实体类注解配置2. 创建元数据处理器3. 服务层代码优化填充机制详

Python实现Excel批量样式修改器(附完整代码)

《Python实现Excel批量样式修改器(附完整代码)》这篇文章主要为大家详细介绍了如何使用Python实现一个Excel批量样式修改器,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一... 目录前言功能特性核心功能界面特性系统要求安装说明使用指南基本操作流程高级功能技术实现核心技术栈关键函

Java实现字节字符转bcd编码

《Java实现字节字符转bcd编码》BCD是一种将十进制数字编码为二进制的表示方式,常用于数字显示和存储,本文将介绍如何在Java中实现字节字符转BCD码的过程,需要的小伙伴可以了解下... 目录前言BCD码是什么Java实现字节转bcd编码方法补充总结前言BCD码(Binary-Coded Decima

SpringBoot全局域名替换的实现

《SpringBoot全局域名替换的实现》本文主要介绍了SpringBoot全局域名替换的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录 项目结构⚙️ 配置文件application.yml️ 配置类AppProperties.Ja