Hash简介:
Hash,一般翻译做“散列”,也有直接音译为“哈希”的,就是把任意长度的输入(又叫做预映射pre-image)通过散列算法变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来确定唯一的输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。
若结构中存在和关键字K相等的记录,则必定在f(K)的存储位置上。由此,不需比较便可直接取得所查记录。称这个对应关系f为散列函数(Hash function),按这个事先建立的表为散列表。
对不同的关键字可能得到同一散列地址,即key1≠key2,而f(key1)=f(key2),这种现象称碰撞或者冲突。具有相同函数值的关键字对该散列函数来说称做同义词。综上所述,根据散列函数H(key)和处理冲突的方法将一组关键字映象到一个有限的连续的地址集(区间)上,并以关键字在地址集中的“象” 作为记录在表中的存储位置,这种表便称为散列表,这一映象过程称为散列造表或散列,所得的存储位置称散列地址。
若对于关键字集合中的任一个关键字,经散列函数映象到地址集合中任何一个地址的概率是相等的,则称此类散列函数为均匀散列函数(Uniform Hash function),这就是使关键字经过散列函数得到一个“随机的地址”,从而减少冲突。
常见的散列函数:
1.直接寻址法:取关键字或关键字的某个线性函数值为散列地址。即H(key)=key或H(key) = a·key + b,其中a和b为常数(这种散列函数叫做自身函数)
2. 数字分析法
3. 平方取中法:f(x):=(x*x div 1000 ) mod 1000000); 平方后取中间的,每位包含信息比较多。
4. 折叠法
5. 随机数法
6. 除留余数法:取关键字被某个不大于散列表表长m的数p除后所得的余数为散列地址。即 H(key) = key MOD p,p<=m。不仅可以对关键字直接取模,也可在折叠、平方取中等运算之后取模。对p的选择很重要,一般取素数或m,若p选的不好,容易产生同义词。
1 public class HashFunctions { 2 /* 取余法 */ 3 static int hash1(Object x, int prime) { 4 return x.hashCode() % prime; 5 } 6 7 /* 加法 */ 8 static int additiveHash(Object key, int prime) { 9 String objStr = key.toString(); 10 int hash = objStr.length(), i = 0; 11 // 遍历每个字符 12 for (; i < objStr.length(); i++) 13 hash += objStr.charAt(i); 14 return (hash % prime); 15 } 16 17 /* 利用位运算 旋转hash */ 18 static int rotatingHash(String key, int prime) { 19 20 int hash = key.length(), i = 0; 21 22 for (; i < key.length(); ++i) 23 24 hash = (hash << 4) ^ (hash >> 28) ^ key.charAt(i); 25 26 return (hash % prime); 27 28 } 29 30 /* 乘法 */ 31 static long bernstein(String key, int prime) { 32 long h = 0; 33 long seed = 31;// 素数 34 for (int i = 0; i != key.length(); ++i) { 35 h = seed * h + key.charAt(i); 36 } 37 return h % prime; 38 } 39 }
冲突解决:
1.开放寻址法:算出hash如果冲突,那么在这个基础上往下hash,直至不冲突为止。
2. 再散列法:重复hash直到不冲突为止。
3. 链地址法(拉链法):典型的是桶排序,采用的是链表的方式来进行解决,因为有可能若干个元素是落在同一个桶中的,那么在桶中我们可以采用链表的形式来链接桶中的各个元素来解决冲突的存在。
4. 建立一个公共溢出区
哈希表:相比前面学习的几种数据结构,在哈希表中进行添加,删除,查找等操作,性能十分之高,不考虑哈希冲突的情况下,仅需一次定位即可完成,时间复杂度为O(1)。
HashMap简介:
简单来说,HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的,如果定位到的数组位置不含链表(当前entry的next指向null),那么对于查找,添加等操作很快,仅需一次寻址即可;如果定位到的数组包含链表,对于添加操作,其时间复杂度为O(n),首先遍历链表,存在即覆盖,否则新增;对于查找操作来讲,仍需遍历链表,然后通过key对象的equals方法逐一比对查找。所以,性能考虑,HashMap中的链表出现越少,性能才会越好。原理可以和桶排序做对比理解。
Java中HashMap常见函数的使用:
View Code
1 import java.util.HashMap; 2 import java.util.Iterator; 3 import java.util.Map; 4 import java.util.Random; 5 6 public class HashMapTest { 7 8 public static void main(String[] args) { 9 testHashMapAPIs(); 10 } 11 12 private static void testHashMapAPIs() { 13 // 初始化随机种子 14 Random r = new Random(); 15 // 新建HashMap 16 HashMap map = new HashMap(); 17 // 添加操作 18 map.put("one", r.nextInt(10)); 19 map.put("one", r.nextInt(10)); 20 map.put("two", r.nextInt(10)); 21 map.put("three", r.nextInt(10)); 22 23 // 打印出map 24 System.out.println("map:" + map); 25 26 // 通过Iterator遍历key-value 27 Iterator iter = map.entrySet().iterator(); 28 while (iter.hasNext()) { 29 Map.Entry entry = (Map.Entry) iter.next(); 30 System.out.println("next : " + entry.getKey() + " - " + entry.getValue()); 31 } 32 33 // HashMap的键值对个数 34 System.out.println("size:" + map.size()); 35 36 // containsKey(Object key) :是否包含键key 37 System.out.println("contains key two : " + map.containsKey("two")); 38 System.out.println("contains key five : " + map.containsKey("five")); 39 40 // containsValue(Object value) :是否包含值value 41 System.out.println("contains value 0 : " + map.containsValue(new Integer(0))); 42 43 // remove(Object key) : 删除键key对应的键值对 44 map.remove("three"); 45 46 System.out.println("map:" + map); 47 48 // clear() : 清空HashMap 49 map.clear(); 50 51 // isEmpty() : HashMap是否为空 52 System.out.println((map.isEmpty() ? "map is empty" : "map is not empty")); 53 } 54 }
结果: