分析Hash

  • 列表内容

    Hash表中的一些原理/概念,及依据这些原理/概念,自己设计一个用来存放/查找数据的Hash表,而且与JDK中的HashMap类进行比較。

    我们分一下七个步骤来进行。
  • Hash表概念

    在Hash表中。记录在表中的位置和其关键字之间存在着一种确定的关系。这样 我们就能预先知道所查关键字在表中的位置,从而直接通过下标找到记录。

    1) 哈希(Hash)函数是一个映象,即: 将关键字的集合映射到某个地址集合上,它的设置非常灵活。

    仅仅要这个地址集合的大小不超出同意范围就可以。

    2) 由于哈希函数是一个压缩映象,因此。在普通情况下。非常easy产生“冲突”现象。

    即: key1!=key2,而 f (key1) = f(key2)。

    3). 仅仅能尽量降低冲突而不能全然避免冲突,这是由于通常关键字集合比較大。其元素包含全部可能的关键字,而地址集合的元素仅为哈希表中的地址值.在构造这样的特殊的“查找表” 时,除了须要选择一个“好”(尽可能少产生冲突)的哈希函数之外;还须要找到一 种“处理冲突” 的方法。
  • Hash构造函数的方法,及适用范围

    直接定址法

    数字分析法

    平方取中法

    折叠法

    除留余数法

    随机数法

    (1)直接定址法:

    哈希函数为关键字的线性函数。H(key) = key 或者 H(key) = a * key + b

    (2)数字分析法:

    假设关键字集合中的每一个关键字都是由 s 位数字组成 (u1, u2, …, us),分析关键字集中的全体,

    并从中提取分布均匀的若干位或它们的组合作为地址。

    此法适于:能预先预计出全体关键字的每一位上各种数字出现的频度。

    (3)平方取中法:

    以关键字的平方值的中间几位作为存储地址。求“关键字的平方值” 的目的是“扩大区别” 。

    同 时平方值的中间各位又能受到整个关键字中各位的影响。

    (4)折叠法:

    将关键字切割成若干部分。然后取它们的叠加和为哈希地址。两种叠加处理的方法:移位叠加:

    将切割后的几部分低位对齐相加。间界叠加:从一端沿切割界来回折叠,然后对齐相加。

    此法适于:关键字的数字位数特别多。

    (5)除留余数法:

    设定哈希函数为:H(key) = key MOD p ( p≤m ),当中。 m为表长,p 为不大于 m 的素数,或 是不含 20 下面的质因子

    (6)随机数法:

    设定哈希函数为:H(key) = Random(key)当中,Random 为伪随机函数

    实际造表时,採用何种构造哈希函数的方法取决于建表的关键字集合的情况(包含关键字的范围和形态),

    以及哈希表长度(哈希地址范围)。总的原则是使产生冲突的可能性降到尽可能地小。

  • Hash处理冲突方法,各自特征

    “处理冲突” 的实际含义是:为产生冲突的关键字寻找下一个哈希地址。

    开放定址法

    再哈希法

    链地址法

    (1)开放定址法:

    为产生冲突的关键字地址 H(key) 求得一个地址序列: H0, H1, H2, …, Hs 1≤s≤m-1,Hi = ( H(key) +di ) MOD m。

    当中: i=1, 2, …, s,H(key)为哈希函数;m为哈希表长;

    (2)链地址法:将全部哈希地址同样的记录都链接在同一链表中。

    (3)再哈希法:

    方法:构造若干个哈希函数。当发生冲突时,依据还有一个哈希函数计算下一个哈希地址。直到冲突不再发 生。

    即:Hi=Rhi(key) i=1,2,……k,当中:Rhi——不同的哈希函数。特点:计算时间添加

  • Hash查找过程

    对于给定值 K,计算哈希地址 i = H(K),若 r[i] = NULL 则查找不成功,若 r[i].key = K 则查找成功。

    否则 “求 下一地址 Hi” 。直至r[Hi] = NULL (查找不成功) 或r[Hi].key = K (查找成功) 为止。
  • 实现一个使用Hash存数据的场景——-Hash查找算法。插入算法

    假设我们要设计的是一个用来保存中南大学全部在校学生个人信息的数据表。由于在校学生数量也不是特别巨大(8W)。每一个学生的学号是唯一的,因此。我们能够简单的应用直接定址法,声明一个10W大小的数组,每一个学生的学号作为主键。

    然后每次要加入或者查找学生。仅仅须要依据须要去操作就可以。可是,显然这样做是非常脑残的。

    这样做系统的可拓展性和复用性就非常差了,比方有一天人数超过10W了?

    假设是用来保存别的数据呢?或者我仅仅须要保存20条记录呢?声明大小为10W的数组显然是太浪费了的。假设我们是用来保存大数据量(比方银行的用户数。4大的用户数都应该有3-5亿了吧?),这时候我们计算出来的

    HashCode就非常可能会有冲突了。 我们的系统应该有“处理冲突”的能力,此处我们通过挂链法“处理冲突”。

    假设我们的数据量非常巨大。而且还持续在添加。假设我们仅仅仅仅是通过挂链法来处理冲突。可能我们的链上挂了

    上万个数据后,这个时候再通过静态搜索来查找链表,显然性能也是非常低的。

    所以我们的系统应该还能实现自己主动扩容。

    当容量达到某比例后,即自己主动扩容,使装载因子保存在一个固定的水平上。

    综上所述,我们对这个Hash容器的基本要求应该有例如以下几点:

    满足Hash表的查找要求(废话)

    能支持从小数据量到大数据量的自己主动转变(自己主动扩容)

    使用挂链法解决冲突

測试代码

package da;
public class MyMap< K, V> {
private int size;// 当前容量
private static int INIT_CAPACITY = 16;// 默认容量
private Entry< K, V>[] container;// 实际存储数据的数组对象
private static float LOAD_FACTOR = 0.75f;// 装载因子
private int max;// 能存的最大的数=capacity*factor // 自己设置容量和装载因子的构造器
public MyMap(int init_Capaticy, float load_factor) {
if (init_Capaticy < 0)
throw new IllegalArgumentException("Illegal initial capacity: "
+ init_Capaticy);
if (load_factor <= 0 || Float.isNaN(load_factor))
throw new IllegalArgumentException("Illegal load factor: " + load_factor);
this.LOAD_FACTOR = load_factor;
max = (int) (init_Capaticy * load_factor);
container = new Entry[init_Capaticy];
} // 使用默认參数的构造器
public MyMap() {
this(INIT_CAPACITY, LOAD_FACTOR);
} /**
* 存
*
* @param k
* @param v
* @return
*/
public boolean put(K k, V v) {
// 1.计算K的hash值
// 由于自己非常难写出对不同的类型都适用的Hash算法,故调用JDK给出的hashCode()方法来计算hash值
int hash = k.hashCode();
//将全部信息封装为一个Entry
Entry< K,V> temp=new Entry(k,v,hash);
if(setEntry(temp, container)){
// 大小加一
size++;
return true;
}
return false;
} /**
* 扩容的方法
*
* @param newSize
* 新的容器大小
*/
private void reSize(int newSize) {
// 1.声明新数组
Entry< K, V>[] newTable = new Entry[newSize];
max = (int) (newSize * LOAD_FACTOR);
// 2.复制已有元素,即遍历全部元素。每一个元素再存一遍
for (int j = 0; j < container.length; j++) {
Entry< K, V> entry = container[j];
//由于每一个数组元素事实上为链表,所以…………
while (null != entry) {
setEntry(entry, newTable);
entry = entry.next;
}
}
// 3.改变指向
container = newTable; } /**
*将指定的结点temp加入到指定的hash表table当中
* 加入时推断该结点是否已经存在
* 假设已经存在。返回false
* 加入成功返回true
* @param temp
* @param table
* @return
*/
private boolean setEntry(Entry< K,V> temp,Entry[] table){
// 依据hash值找到下标
int index = indexFor(temp.hash, table.length);
//依据下标找到相应元素
Entry< K, V> entry = table[index];
// 3.若存在
if (null != entry) {
// 3.1遍历整个链表,推断是否相等
while (null != entry) {
//推断相等的条件时应该注意。除了比較地址同样外。引用传递的相等用equals()方法比較
//相等则不存。返回false
if ((temp.key == entry.key||temp.key.equals(entry.key)) &&
temp.hash == entry.hash&&(temp.value==entry.value||temp.value.equals(entry.value))) {
return false;
} else if(temp.key == entry.key && temp.value != entry.value) {
entry.value = temp.value;
return true;
} //不相等则比較下一个元素
else if (temp.key != entry.key) {
//到达队尾,中断循环
if(null==entry.next){
break;
}
// 没有到达队尾。继续遍历下一个元素
entry = entry.next;
}
}
// 3.2当遍历到了队尾。假设都没有同样的元素,则将该元素挂在队尾
addEntry2Last(entry,temp);
return true;
}
// 4.若不存在,直接设置初始化元素
setFirstEntry(temp,index,table);
return true;
} private void addEntry2Last(Entry< K, V> entry, Entry< K, V> temp) {
if (size > max) {
reSize(container.length * 4);
}
entry.next=temp; } /**
* 将指定结点temp。加入到指定的hash表table的指定下标index中
* @param temp
* @param index
* @param table
*/
private void setFirstEntry(Entry< K, V> temp, int index, Entry[] table) {
// 1.推断当前容量是否超标。假设超标,调用扩容方法
if (size > max) {
reSize(table.length * 4);
}
// 2.不超标,或者扩容以后,设置元素
table[index] = temp;
//! !!!!。!!。。!!!!!
//由于每次设置后都是新的链表,须要将其后接的结点都去掉
//NND,少这一行代码卡了哥哥7个小时(代码重构)
temp.next=null;
} /**
* 取
*
* @param k
* @return
*/
public V get(K k) {
Entry< K, V> entry = null;
// 1.计算K的hash值
int hash = k.hashCode();
// 2.依据hash值找到下标
int index = indexFor(hash, container.length);
// 3。依据index找到链表
entry = container[index];
// 3。若链表为空。返回null
if (null == entry) {
return null;
}
// 4。若不为空。遍历链表。比較k是否相等,假设k相等。则返回该value
while (null != entry) {
if (k == entry.key||entry.key.equals(k)) {
return entry.value;
}
entry = entry.next;
}
// 假设遍历完了不相等,则返回空
return null; } /**
* 依据hash码,容器数组的长度,计算该哈希码在容器数组中的下标值
*
* @param hashcode
* @param containerLength
* @return
*/
public int indexFor(int hashcode, int containerLength) {
return hashcode & (containerLength - 1); } /**
* 用来实际保存数据的内部类,由于採用挂链法解决冲突,此内部类设计为链表形式
*
* @param < K>key
* @param < V>
* value
*/
class Entry< K, V> {
Entry< K, V> next;// 下一个结点
K key;// key
V value;// value
int hash;// 这个key相应的hash码。作为一个成员变量。当下次须要用的时候能够不用又一次计算 // 构造方法
Entry(K k, V v, int hash) {
this.key = k;
this.value = v;
this.hash = hash; } //相应的getter()方法 }
} package da; public class Main {
public static void main(String[] args) {
MyMap< String, String> mm = new MyMap< String, String>(); Long aBeginTime=System.currentTimeMillis();//记录BeginTime for(int i=0;i< 1000000;i++){
mm.put(""+i, ""+i*100);
} Long aEndTime=System.currentTimeMillis();//记录EndTime
System.out.println("insert time-->"+(aEndTime-aBeginTime)); Long lBeginTime=System.currentTimeMillis();//记录BeginTime
mm.get(""+100000); Long lEndTime=System.currentTimeMillis();//记录EndTime
System.out.println("seach time--->"+(lEndTime-lBeginTime));
}
} package da; import java.util.Random; public class Main {
public static void main(String[] args) {
MyMap< String, String> mm = new MyMap< String, String>(); Long aBeginTime=System.currentTimeMillis();//记录BeginTime String a[]={"李锦根","金行","成龙","客户"}; for(int i=0;i< 1000000;i++){
Random s=new Random();
//int temp=s.nextInt();
int temp=(int) ((Math.random() * 3 + 1) * 1);
mm.put(""+i, ""+a[temp]);
//mm.put(""+100000000+i,""+"金");
System.out.println(i+a[temp]);
}
mm.put(""+100000000,""+"金");
Long aEndTime=System.currentTimeMillis();//记录EndTime
System.out.println("insert time-->"+(aEndTime-aBeginTime)); Long lBeginTime=System.currentTimeMillis();//记录BeginTime
//mm.get(""+3+a[0]);
//System.out.println(mm.get(""+100));
int coun=0;
for(int i=1;i< 1000000;i++){
//System.out.println(mm.get(""+i));
if(mm.get(""+i)!=null&&mm.get(""+i).equals("金行")){
coun++;
}
}
System.out.println(coun);
Long lEndTime=System.currentTimeMillis();//记录EndTime
System.out.println("seach time--->"+(lEndTime-lBeginTime));
}
}
999958成龙
999959成龙
999960客户
999961成龙
999962成龙
999963金行
999964成龙
999965成龙
999966金行
999967金行
999968成龙
999969金行
999970客户
999971金行
999972成龙
999973客户
999974成龙
999975客户
999976客户
999977金行
999978客户
999979成龙
999980成龙
999981金行
999982客户
999983成龙
999984成龙
999985客户
999986成龙
999987金行
999988金行
999989客户
999990金行
999991客户
999992金行
999993金行
999994金行
999995成龙
999996客户
999997成龙
999998金行
999999金行
insert time-->11621
219770
seach time--->430

Hash分析的更多相关文章

  1. 全网把Map中的hash()分析的最透彻的文章,别无二家。

    你知道HashMap中hash方法的具体实现吗?你知道HashTable.ConcurrentHashMap中hash方法的实现以及原因吗?你知道为什么要这么实现吗?你知道为什么JDK 7和JDK 8 ...

  2. 一致性Hash 分析和实现

    一致性Hash 分析和实现 ---title: 1.一致性Hashdate: 2018-02-05 12:03:22categories:- 一致性Hash--- 一下分析来源于网络总结:算法参照自己 ...

  3. HashMap的hash分析

    哈希 Hash,一般翻译做“散列”,也有直接音译为“哈希”的,就是把任意长度的输入,通过散列算法,变换成固定长度的输出,该输出就是散列值.这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空 ...

  4. 【原】 twemproxy ketama一致性hash分析

    转贴请注明原帖位置:http://www.cnblogs.com/basecn/p/4288456.html 测试Twemproxy集群,双主双活 向twemproxy集群做写操作时,发现key的分布 ...

  5. [转] twemproxy ketama一致性hash分析

    评注:提到HAProxy业务层proxy, twemproxy存储的proxy. 其中还提到了ketama算法的实现源码 转自:http://www.cnblogs.com/basecn/p/4288 ...

  6. 【转】【java源码分析】Map中的hash算法分析

    全网把Map中的hash()分析的最透彻的文章,别无二家. 2018年05月09日 09:08:08 阅读数:957 你知道HashMap中hash方法的具体实现吗?你知道HashTable.Conc ...

  7. java 散列运算浅分析 hash()

            文章部分代码图片和总结来自参考资料 哈希和常用的方法 散列,从中文字面意思就很好理解了,分散排列,我们知道数组地址空间连续,查找快,增删慢,而链表,查找慢,增删快,两者结合起来形成散列 ...

  8. String源码分析

    前言:String类在日常开发过程中使用频率非常高,平时大家可能看过String的源码,但是真的认真了解过它么,笔者在一次笔试过程中要求写出String的equals方法,瞬间有点懵逼,凭着大致的理解 ...

  9. hashmap C++实现分析及std::unordered_map拓展

    今天想到哈希函数,好像解决冲突的只了解了一种链地址法而且也很模糊,就查了些资料复习一下 1.哈希Hash 就是把任意长度的输入,通过哈希算法,变换成固定长度的输出(通常是整型),该输出就是哈希值. 这 ...

随机推荐

  1. python计算机基础(二)

    1. 操作系统有什么用? #1外部指令转化成0和1:#2.翻译所写的字符从繁(高低电压)至简(想做什么就做什么) :#3把一些硬件的复杂操作简化成一个一个接口. 2. 计算机由哪三大部分组成? 1.应 ...

  2. I2C驱动框架(二)

    参考:I2C子系统之I2C bus初始化——I2C_init() 在linux内核启动的时候最先执行的和I2C子系统相关的函数应该是driver/i2c/i2c-core.c文件中的i2c_init( ...

  3. 【笔记】PIL 中的 Image 模块

    Image 模块提供了一个同名类(Image),也提供了一些工厂函数,包括从文件中载入图片和创建新图片.例如,以下的脚本先载入一幅图片,将它旋转 45 度角,并显示出来: 1 >>> ...

  4. 【ORACLE】调整序列的当前种子值

    [ORACLE]调整序列的当前种子值 --必须用SYS用户执行脚本:或具有SYSDBA角色登录: CREATE OR replace ); v_step ):;--步进 tsql ); BEGIN E ...

  5. 组合数学的卡特兰数 TOJ 3551: Game of Connections

    这个就是卡特兰数的经典问题 直接用这个公式就好了,但是这个题涉及大数的处理h(n)=h(n-1)*(4*n-2)/(n+1) 其实见过好几次大数的处理了,有一次他存的恰好不多于30位,直接分成两部分l ...

  6. POJ 1038 Bugs Integrated, Inc. ——状压DP

    状态压缩一下当前各格子以及上面总共放了几块,只有012三种情况,直接三进制保存即可. 然后转移的时候用搜索找出所有的状态进行转移. #include <map> #include < ...

  7. UOJ 274 【清华集训2016】温暖会指引我们前行 ——Link-Cut Tree

    魔法森林高清重置, 只需要维护关于t的最大生成树,然后链上边权求和即可. 直接上LCT 调了将近2h 吃枣药丸 #include <cstdio> #include <cstring ...

  8. HDU 1724 Ellipse ——Simpson积分

    [题目分析] 一看题目,直接把椭圆积分起来就可以了嘛. 然后发现椭圆比较难积分,还是算了吧. 用Simpson积分硬上. 大概就是用二次函数去拟合面积. [代码] #include <cstdi ...

  9. Tomcat 调优技巧

    Tomcat 调优技巧:1.Tomcat自身调优: ①采用动静分离节约Tomcat的性能: ②调整Tomcat的线程池: ③调整Tomcat的连接器: ④修改Tomcat的运行模式: ⑤禁用AJP连接 ...

  10. LA 2218 半平面交

     题目大意:n名选手参加铁人三项赛,比赛按照选手在三个赛段中所用的总时间排定名次.已知每名选手在三个项目中的速度Ui.Vi.Wi.问对于选手i,能否通过适当的安排三个赛段的长度(但每个赛段的长度都不能 ...