基于"哈希"查找的Flash中数据存储

本文主要阐述当需要将数据存放到flash中,并要求能够快速查找到相应内容的需求下,如何利用“哈希”表来存储数据和查找。该方法是本人的一个设计思路,只作参考,若有不对之处,还请指出。
        接下来通过一个具体的例子,来阐述这种方法:

1、背景需求

 

I.需要存储至少5000份名单在Flash中;II.一份名单内容包括卡号、有效期、权限、密码、用户名等等信息;
III.需要根据卡号快速地从Flash中找到对应的名单,要求时间为毫秒级;

 

 

2、“哈希”表查找算法概述

 

     “哈希”表的存储方法,首先是根据与一份数据唯一对应的一个关键值(key值)通过某个公式函数(哈希函数)算出其存储地址(哈希值),然后直接到该对应的存储地址上去拿这份数据。这样就避免了遍历查找的过程,大大节省了查找时间。

2.1、key值

      一般是与一份数据相关的唯一对应的一个关键值,比如这个例子中的卡号,卡号是唯一的,一份卡号对应一份名单。所以卡号作为一份名单的key值是很合适的。

2.2、哈希函数

 

      哈希函数是自定义的,当然网上也总结了很多可供参考的例子。主要目的都是根据不同的key值的特性,能达到不同的key值算出来的哈希值尽可能地随机均匀分布。这里有个链接有总结了一些哈希函数,可供参考:点击打开:链接哈希函数的构造方法

2.3、哈希值

       哈希值就是哈希函数算出来的值,也就是数据的存储地址。

2.4、哈希值“冲突”

       哈希表查找算法中无可避免的问题是会产生哈希值“冲突”,即是无论哈希函数如何地随机均匀,也是无法保障不会产生不同的key值算出相同的哈希值的问题。这就意味着两份数据要存储在相同的存储地址,这显然是不行的。所以如何解决哈希值“冲突”,也是哈希表查找算法中的重点。关于哈希值“冲突”的解决方法也是有多种方法的,这里有个链接总结了一些方法,可供参考:1.  点击打开链接:Hash算法冲突解决方法分析; 2.  点击打开链接:linux下C语言实现的哈希链表

3、具体实现

3.1、实现思路选择

I、key值:在我的这个例子中,我的key值是卡号,是个4字节或者8字节的数;这里我统一认定为是个8字节的数。

II、哈希函数:对于一个长度很长的数来说,该数的每一位数字都是随机均匀的。利用这个特性,我的哈希函数的算法是将这个数转成BCD码,然后将这个BCD码的每一个字节依次相加,得出的最后结果,再取这个结果一个字节的数值。这样得到的哈希值就相对地随机均匀分布。

char hashCalculate(unsigned char *pCardno,unsigned char *ucFindex)
{
	unsigned char ucVcardno[10];
	
	memcpy((char *)ucVcardno,(char *)pCardno,8);
	
	hextobcd(ucVcardno);

	*ucFindex = ucVcardno[0]+ucVcardno[1]+ucVcardno[2]+ucVcardno[3]+ucVcardno[4]+ucVcardno[5]+ucVcardno[6]+ucVcardno[7]+ucVcardno[8]+ucVcardno[9];
	
	return OK;
}

hextobcd函数是将该8字节的数转换成了10字节的BCD码,然后将这10个字节依次相加,最后将值赋给一个字节的ucFindex,也就是只取一个字节的数值。该值即为存储地址,可见算出来最多可能有256个位置,即我所规划的Flash中的某一个区域的第0~256处位置。

III、哈希值“冲突”解决方法:我采用的哈希值“冲突”的解决方法主要参考“拉链法”,但是由于我的数据不是存放在内存中,而是存放在Flash中,考虑到擦除的时候会擦除一页等特性。与“拉链法”又有些不同之处,但是本质思路是相似的。

这里就先要阐述在这块Flash中我如何存储哈希表的。在该例子中,我使用的Flash型号是M45PE80,共有4096页,每一页是256个字节;擦除的最小单位是一页。对于名单的存储,我分为索引表和名单表两块区域,索引表的第0~256处位置即是上述哈希函数算出来的哈希值。但是真正的名单存储地址并不在索引表上,而是在名单表上。索引表中哈希值对应的位置其实是个“拉链”头,存放着该名单真正存储的在名单表中的第几页的数值(页号)。这样,假如几份名单算出来的哈希值一样,即是在索引表中同个“拉链”头,则他们都会存放到名单表中的同一页。这样就解决了哈希值“冲突”的问题。

在Flash中的存储示意图如下:

当名单表中的某一页存满了名单,而下一份名单也同样是算出来同个哈希值,也就是也要存放在同一页的时候,就会找到另外一页去存放,同时通过索引头建立链接。就类似“拉链”法中的可无限扩充“拉链”的长度。只不过如果是 存放在内存中是使用链表的方式,每个结点存放一份数据;而这里我是存放到Flash中,每个结点是一页,每一页中存放几份数据。

3.2、查找过程

I、当要查找一份数据的时候,会根据卡号,算出哈希值,即是在索引表中的位置;

II、到索引表中对应的位置拿到对应的页号;

III、遍历该页中的名单,找到对应名单返回;若该页找不到,到其链接的下一页中查找对应名单返回;

IV、若已经没有下一页了还找不到,则说明没有对应的名单;

分析上述查找过程:在该例子中,假如有5000份名单,且均匀分布;索引表中共有256个位置,平均下来每个位置需要大约存放20份名单;而每一页大约能存12分名单,也就是说每个位置大约要链接着2页;在这样的假设中,每一次查找的过程,需要花费读取Flash中的两页数据的时间,从中遍历查找对应名单;

而如果不采用算法,5000份名单是直接按顺序存在Flash中的话,是存放大约417页。假设要查找的名单处于中间位置,则查找的时候,需要花费读取Flash中的200页左右的时间来遍历。显然,采用“哈希”算法来存储查找,大大节省了时间。

不过这里是假设了均价分布的情况,实际应用中是不可能如此均匀的。但是哪怕不均匀,链接的页数也肯定不会很多,也就是读取Flash的页数不会很多,读取的时间也就不会花费太多。

当然这里还有极端的情况,就是当所有名单的哈希值都是相同的,那这样的话,算法的时间复杂度就退化为完全遍历一样了。不过这样的情况也是几乎不会发生的。

  

3.2、增加过程

名单表其实就是一大块空白的区域,名单的链接也并不是按照顺序的。

当增加一份名单的时候,增加的过程如下:

I、根据卡号,算出哈希值,即是在索引表中的位置;

II、到索引表中对应的位置拿到对应的页号;

III、将名单存放到对应的页号中;

IV、若该页存满,而链接的下一页中未存满,则存放到下一页中;

V、若没有下一页了,即链接的所有页都存满了,这时候就需要到名单表中查找一页空白页,然后添加索引头,建立链接,然后存放到该页中;

VI、如此循环III~V。

3.3、删除过程

删除名单的过程其实与查找的过程类似,只不过在查找到的时候,将该名单删除;只是要注意几点:

I、删除后该页为空了,则要将该页索引头等全清空,并调整好链接;

II、删除的时候由于至少会擦除一页,所以要注意名单的备份,防止删除过程擦除完一页的时候断电了,导致原本不打算删除的名单丢失了。

             以上便是我的整个思路过程,纯属个人设计思路,若有不对之处,还请指出。 

 

  • 0
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值