1、概念:
1)散列:使用一个散列函数,将一个键映射到一个索引上。
2)散列函数:将键映射到散列表中的索引上的函数称为散列函数。
3)冲突:当两个键映射到散列表中的同一个索引上,冲突发生
a:使用开发地址法解决处理冲突(线性探测、二次探测法、再哈希法)
b:使用链地址法处理冲突:将具有同样的散列索引的条目都放在一个位置,每个位置使用一个桶来放置多个条目;通常使用LinkedList来实现一个桶。
4)装填因子和再散列:装填因子用来衡量一个散列表有多满。再散列:如果装填因子溢出,则增加散列表的大小,并重新装载条目到一个新的更大的散列表中。
2、使用散列实现映射表:用一个哈希表存储,每个下表对应可能有多个条目,因此每个下表相当于对应一个桶,装了多个条目,而桶这里采用LinkedList实现。
存储原理图:
实现代码:
接口定义:
package hash; public interface MyMap<K,V> { /**Remove all of the entries from this map*/ public void clear(); /**是否存在特定的键值*/ public boolean containKey(K key); /**是否存在特定的值*/ public boolean containValue(V value); /**Return a set of entries in the map*/ public java.util.Set<Entry<K, V>> entrySet(); /**返回特定的键对应的值*/ public V get(K key); /**是否为空*/ public boolean isEmpty(); /**返回键值集合*/ public java.util.Set<K> keySet(); /**Add a entry(key,value) into the map*/ public V put(K key,V value); /**Remove the number for the specified key*/ public void remove(K key); /**返回Entry个数*/ public int size(); /**返回map中的所有的值集合*/ public java.util.Set<V> values(); /**定义内部类Entry*/ public static class Entry<K,V>{ K key; V value; public Entry(K key,V value) { this.key = key; this.value = value; } public K getKey() { return key; } public V getValue() { return value; } @Override public String toString() { return "[" + key + ", " + value + "]"; } } }
接口实现:
package hash; import java.util.LinkedList; import java.util.Set; public class MyHashMap<K,V> implements MyMap<K, V> { /**初始哈希表大小,必须是2的幂次方*/ private static int DEFAULT_INITIAL_CAPACITY = 4; /**默认最大哈希表容量,2^30*/ private static int MAXIMUM_CAPACITY = 1 << 30; /**哈希表容量*/ private int capacity; /**默认装载因子*/ private static float DEFAULT_MAX_LOAD_FACTOR = 0.75f; /**指出特定的装载因子*/ private float loadFactorThreshold; /**entry数量*/ private int size = 0; /**定义哈希表*/ LinkedList<MyMap.Entry<K, V>>[] table; /**使用默认容量和装载因子的构造函数*/ public MyHashMap() { this(DEFAULT_INITIAL_CAPACITY,DEFAULT_MAX_LOAD_FACTOR); } /**指定初始容量*/ public MyHashMap(int initialCapacity) { this(initialCapacity,DEFAULT_MAX_LOAD_FACTOR); } /**指定初始容量及装载因子*/ public MyHashMap(int initialCapacity,float loadFactorThreshold) { if(initialCapacity > MAXIMUM_CAPACITY) this.capacity = MAXIMUM_CAPACITY; else this.capacity = trimToPowerOf2(initialCapacity); this.loadFactorThreshold = loadFactorThreshold; table = new LinkedList[capacity]; } /**Return a power of 2 for initialCapacity*/ private int trimToPowerOf2(int initialCapacity) { int capacity = 1; while(capacity < initialCapacity) { capacity <<= 1; } return capacity; } /**Remove all of the entries from this map*/ @Override public void clear() { size = 0; removeEntries(); } private void removeEntries() { for(int i = 0; i < capacity; i++) { if(table[i] != null) { table[i].clear(); } } } /**Return true if the specified key is in the map*/ @Override public boolean containKey(K key) { if(get(key) != null) return true; else return false; } /**Return true if this map contains the value*/ @Override public boolean containValue(V value) { for(int i = 0; i < capacity; i++) { if(table[i] != null) { LinkedList<Entry<K, V>> bucket = table[i]; for(Entry<K, V> entry : bucket) { if(entry.getValue().equals(value)) return true; } } } return false; } /**Return a set of entries in the map*/ @Override public Set<Entry<K, V>> entrySet() { java.util.Set<Entry<K, V>> set = new java.util.HashSet<>(); for(int i = 0; i < capacity; i++) { if(table[i] != null) { LinkedList<Entry<K, V>> bucket = table[i]; for(Entry<K, V> entry : bucket) set.add(entry); } } return set; } /**Return the value that mathces the specified key*/ @Override public V get(K key) { int bucketIndex = hash(key.hashCode()); if(table[bucketIndex] != null) { LinkedList<Entry<K, V>> bucket = table[bucketIndex]; for(Entry<K, V> entry : bucket) if(entry.getKey().equals(key)) return entry.getValue(); } return null; } /**hash fucntion*/ private int hash(int hashCode) { return supplementalHash(hashCode) & (capacity - 1); } /**确保hash值均匀分布*/ private int supplementalHash(int h) { h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } /**Return a set consisting of the keys in this map*/ @Override public Set<K> keySet() { java.util.Set<K> set = new java.util.HashSet<>(); for(int i = 0; i < capacity; i++) { if(table[i] != null) { LinkedList<Entry<K, V>> bucket = table[i]; for(Entry<K, V> entry : bucket) { set.add(entry.getKey()); } } } return set; } /**Add an entry(key,value) into the map*/ @Override public V put(K key, V value) { if(get(key) != null) { int bucketIndex = hash(key.hashCode()); LinkedList<Entry<K, V>> bucket = table[bucketIndex]; for(Entry<K, V> entry : bucket) { if(entry.getKey().equals(key)) { V oldValue = entry.getValue(); entry.value = value; return oldValue; } } } //Check load factor if(size >= capacity * loadFactorThreshold) { if(capacity == MAXIMUM_CAPACITY) throw new RuntimeException("Exceeding maximum capacity"); rehash(); } int bucketIndex = hash(key.hashCode()); //Create a linked list for the bucket if not already created if(table[bucketIndex] == null) { table[bucketIndex] = new LinkedList<>(); } table[bucketIndex].add(new MyMap.Entry<K, V>(key, value)); size++; return value; } /**Rehash the map*/ private void rehash() { java.util.Set<Entry<K, V>> set = entrySet(); capacity <<= 1; table = new LinkedList[capacity]; size = 0; for(Entry<K, V> entry : set) { put(entry.getKey(), entry.getValue()); } } /**Remove the entries for the specified key*/ @Override public void remove(K key) { int bucketIndex = hash(key.hashCode()); if(table[bucketIndex] != null) { LinkedList<Entry<K, V>> bucket = table[bucketIndex]; for(Entry<K, V> entry : bucket) { if(entry.getKey().equals(key)) { bucket.remove(entry); size--; break; } } } } /**Return the number of entries in this map*/ @Override public int size() { return size; } /**Return a set consisting of the valuse in this map*/ @Override public Set<V> values() { java.util.Set<V> set = new java.util.HashSet<>(); for(int i = 0; i < capacity; i++) { if(table[i] != null) { LinkedList<Entry<K, V>> bucket = table[i]; for(Entry<K, V> entry : bucket) { set.add(entry.getValue()); } } } return null; } /**Return true if this map contains no entries*/ @Override public boolean isEmpty() { return size == 0; } @Override public String toString() { StringBuilder builder = new StringBuilder("["); for(int i = 0; i < capacity; i++) { if(table[i] != null && table[i].size() > 0) { for(Entry<K, V> entry : table[i]) builder.append(entry); } } builder.append("]"); return builder.toString(); } }
测试代码:
package hash; public class TestMyhashMap { public static void main(String[] args) { MyHashMap<String, Integer> map = new MyHashMap<>(); map.put("Smith", 30); map.put("Anderson", 31); map.put("Lewis", 29); map.put("Cook", 29); map.put("Smith", 65); System.out.println("Entries in map: "+ map); System.out.println("The age for Lewis is " + map.get("Lewis")); System.out.println("Is Smith in the map?" + map.containKey("Smith")); System.out.println("Is age 33 in the map?" + map.containValue(33)); map.remove("Smith"); System.out.println("Entries in map: "+ map); map.clear(); System.out.println("Entries in map: " + map); } } /** Entries in map: [[Anderson, 31][Smith, 65][Lewis, 29][Cook, 29]] The age for Lewis is 29 Is Smith in the map?true Is age 33 in the map?false Entries in map: [[Anderson, 31][Lewis, 29][Cook, 29]] Entries in map: [] */
3、效率分析:
1)clear方法删除映射表中所有条目,时间复杂度O(capacity)
2)containsKey(key)方法,由于get方法耗费O(1)时间,故containsKey(key)也耗费O(1)时间。
3)containsValue(value),O(capacity + size) -----> capacity > size ,故为O(capacity)
4)keySet() : O(capacity)
5)remove():O(1)
散列非常高效,使用散列将耗费O(1)时间来查找、插入以及删除一个元素