哈希表(散列表)
key value 形式的数据结构,其基础是一个数组。哈希表在特定元素的插入,删除和查询时都能够达到O(1)常数的时间复杂度,除非发生hash碰撞
-
哈希算法
把任意长度的输入通过哈希算法转换映射为固定长度的输出,所得到的输出被称为哈希值
-
哈希冲突解决
- 开放地址法
- 重新哈希法
- 拉链法
-
负载因子
负载因子代表着存储的总数据量和内部数组大小的比值。插入新数据时,判断哈希表当前的存储量和内部数组的比值是否超过了负载因子。当比值超过了负载因子时,哈希表认为内部过于拥挤,查询效率太低,会触发一次扩容操作。
指定的负载因子越大,哈希表越拥挤(负载高,紧凑),查询效率越低,空间效率越高。
指定的负载因子越小,哈希表越稀疏(负载小,松散),查询效率越高,空间效率越低。
-
代码(使用了拉链法解决hash冲突)
-
示例
public static void main(String[] args) { MyMap<String, String> map = new MyHashMap<>(10); map.put("zlz","zlz"); map.put("zlz","zlz-zlz"); map.put("zlz1","zlz1"); map.put("zlz2","zlz2"); map.put("zlz3","zlz3"); map.put("zlz4","zlz4"); System.out.println("-----------remove-test-------------"); System.out.println("Key-zlz4存在:" + map.containsKey("zlz4")); map.remove("zlz4"); System.out.println("Key-zlz4存在:" + map.containsKey("zlz4")); System.out.println("-----------containsValue-test-----------"); System.out.println("Value-zlz存在:" + map.containsValue("zlz")); System.out.println("Value-zlz-zlz存在:" + map.containsValue("zlz-zlz")); } out: -----------remove-test------------- Key-zlz4存在:true 移除了元素:zlz4 Key-zlz4存在:false -----------containsValue-test----------- Value-zlz存在:false Value-zlz-zlz存在:true
-
MyMap(接口+内部类EntryNode(键值对))
package hash; /** * @author zlz * @version 1.0 CreateTime:2020-11-11 16:45 * @description hash表,java中的Map结构 */ public interface MyMap<K, V> { /** * 存入元素 * @param k key * @param v value * @return key */ V put(K k, V v); /** * 通过key移除元素 * @param k key * @return value */ V remove(K k); /** * 通过key获取value * @param k key * @return value */ V get(K k); /** * 是否包含该key * @param k key * @return boolean */ boolean containsKey(K k); /** * 是否包含该value * @param v value * @return boolean */ boolean containsValue(V v); /** * 元素个数 * @return 元素个数 */ int size(); /** * hash表是否为空 * @return boolean */ boolean isEmpty(); /** * 清空hash表 * @return 成功或者失败 */ void clear(); /** * 键值对类型,表示一个节点 * @param <K> * @param <V> */ class EntryNode<K, V>{ K key; V value; EntryNode<K,V> next; EntryNode(K key, V value) { assert key != null; this.key = key; this.value = value; } /** * 判断传入的key与对象的key是否相等 * @param k key * @return boolean */ boolean equalsKey(K k){ return this.key.equals(k); } public K getKey() { return key; } public void setKey(K key) { this.key = key; } public V getValue() { return value; } public void setValue(V value) { this.value = value; } public EntryNode<K, V> getNext() { return next; } public void setNext(EntryNode<K, V> next) { this.next = next; } @Override public String toString() { return "{" + "key=" + key + ", value=" + value + '}'; } } }
-
MyHashMap(实现类)
/** * @author zlz * @version 1.0 CreateTime:2020-11-11 17:00 * @description hashmap */ public class MyHashMap<K,V> implements MyMap<K, V>{ /** * 存储所有的键值对 */ private EntryNode<K, V>[] nodes; /** * 当前哈希表的大小 * */ private int size; /** * 负载因子,负载因子决定了hash表何时进行resize(扩容)操作 */ private final float loadFactor; /** * 默认的哈希表容量 */ private final static int DEFAULT_CAPACITY = 16; /** * 扩容翻倍的基数 */ private final static int REHASH_BASE = 2; /** * 默认的负载因子 */ private final static float DEFAULT_LOAD_FACTOR = 0.75f; /** * 使用默认容量初始化 */ public MyHashMap() { this.size = 0; this.loadFactor = DEFAULT_LOAD_FACTOR; nodes = new EntryNode[DEFAULT_CAPACITY]; } /** * 指定初始化的容量大小 * @param capacity 初始化的容量 */ public MyHashMap(int capacity) { this.size = 0; this.loadFactor = DEFAULT_LOAD_FACTOR; nodes = new EntryNode[capacity]; } /** * 通过hash计算获取数组的下标 * @param key key * @return hash桶坐标 */ private int getIndex(K key){ return getIndex(key,this.nodes); } /** * 通过hash计算获取数组的下标 * @param key key * @param nodes 节点 * @return hash桶坐标 */ private int getIndex(K key,EntryNode<K,V>[] nodes){ if(key == null){ return 0; }else{ int hashCode = key.hashCode(); // 异或运算,获得最终的hash映射,减少碰撞的几率 int finalHashCode = hashCode ^ (hashCode >>> 16); return (nodes.length-1) & finalHashCode; } } /** * 试图获得目标节点,获取不到则返回链表的最后一个节点 * @param currentNode 链表的第一个节点 * @param key key * @return EntryNode */ private EntryNode<K,V> getTargetEntryNode(EntryNode<K,V> currentNode, K key){ // 不匹配 EntryNode<K,V> nextNode = currentNode.next; // 遍历当前桶后面的所有节点 while(true){ if(nextNode == null || currentNode.key.equals(key)){ return currentNode; } currentNode = nextNode; nextNode = currentNode.next; } } @Override public V put(K k, V v) { //创建键值对 EntryNode<K, V> entryNode = new EntryNode<>(k, v); //通过key获取数组的下标 int index = getIndex(k); EntryNode<K, V> firstNode = this.nodes[index]; //若是此节点不存在数据,则直接插入该数据作为第一个节点 if(firstNode == null){ this.nodes[index] = entryNode; this.size++; if(this.size > this.nodes.length * this.loadFactor){ this.resize(); } return v; } // 若是此桶的第一个节点就是当前的key,就将该key对应的value替换掉,并且返回此oldValue if(firstNode.equalsKey(k)){ V oldValue = firstNode.value; firstNode.value = v; return oldValue; //若是不匹配,则去链表的下一个节点查找,若是全部不匹配,则新增一个节点放在链表的末尾 }else { EntryNode<K, V> targetEntryNode = getTargetEntryNode(firstNode, k); // 若是查找到了key相同的节点,则替换节点的value if(targetEntryNode.key.equals(k)){ V oldValue = targetEntryNode.value; targetEntryNode.value = v; return oldValue; //未查询到匹配的节点 }else { targetEntryNode.next = entryNode; this.size++; if(this.size > this.nodes.length * this.loadFactor){ this.resize(); } return v; } } } @Override public V remove(K k) { int index = getIndex(k); EntryNode<K, V> firstNode = this.nodes[index]; if(null == firstNode){ return null; } // 链表第一个节点即为要移除的节点 if(firstNode.key.equals(k)){ this.nodes[index] = firstNode.next; System.out.println("移除了元素:" + k); this.size --; return firstNode.value; // 链表第一个节点不是要移除的节点 } else { // 从第二个节点开始遍历 EntryNode<K, V> preEntryNode = firstNode; EntryNode<K, V> curEntryNode = firstNode.next; while (null != curEntryNode) { if(curEntryNode.key.equals(k)){ break; } // 若是下一个节点为空,则说明链表遍历完成,但是也没有匹配到对应的节点 if(null == curEntryNode.next){ return null; } preEntryNode = curEntryNode; curEntryNode = curEntryNode.next; } // 将其之前的节点连接到他的下一个节点上 if(null != curEntryNode){ System.out.println("移除了元素:" + k); this.size --; preEntryNode.next = curEntryNode.next; return curEntryNode.value; } return null; } } @Override public V get(K k) { int index = getIndex(k); EntryNode<K, V> node = this.nodes[index]; while (null != node){ if(node.key.equals(k)){ return node.value; } node = node.next; } return null; } @Override public boolean containsKey(K k) { int index = getIndex(k); EntryNode<K, V> node = this.nodes[index]; while (null != node){ if(node.key.equals(k)){ return true; } node = node.next; } return false; } @Override public boolean containsValue(V v) { for (EntryNode<K, V> node : this.nodes) { while (null != node){ if(node.value.equals(v)){ return true; } node = node.next; } } return false; } @Override public int size() { return this.size; } @Override public boolean isEmpty() { for (EntryNode<K, V> node : this.nodes) { if (null != node) { return false; } } return true; } @Override public void clear() { this.nodes = new EntryNode[this.nodes.length]; } private void resize() { EntryNode<K, V>[] resizeArray = this.nodes; this.nodes = new EntryNode[this.nodes.length * REHASH_BASE]; this.size = 0; for (EntryNode<K, V> node : resizeArray) { while (null != node){ this.put(node.key, node.value); node = node.next; } } System.out.println("hash表已经扩容,目前容量:" + this.nodes.length); } }
-