HashMap的实现机制、实现自己的HashMap
2016-10-04 09:22
309 查看
在数据结构中,数组列表插入删除不方便,链表查找效率不高。而HashMap拥有了这两者的优点。HashMap本质上是由数组+链表组成的结构。
java.util.HashMap.java
这是说hashmap所能拓展的最大值为 2^30=1,073,741,824
java.util.HashMap.java
这是说数组默认的大小是16(数组的大小总是以2为底的幂,所以很容易理解为什么其扩容会如此快,数学中称为
java.util.HashMap.java
put方法主要有几个过程:
java.util.HashMap.java
indexFor方法中的h是hashCode,length默认是数组容量,即分桶的数量。
hashMap实现自己的哈希函数来弥补对象自身质量不佳的哈希函数。因为容量总是2的N次方法,使用二次hash函数使高位也参与运算,防止低位不变造成严重哈希冲突。简而言之,目的是为了使key分布得更均匀,提高操作效率。
java.util.HashMap.java
put和get计算哈希值分桶的算法是相同的。这里值得关注的是,当两个key有相同的hash值时,如何将新值传进去?
1)通过计算所得hashCode找到数组元素,即分桶(bucket),每个桶都是一个链表结果,遍历链表寻找具有相同的key的节点,如果找到覆盖其value
2)如果找不到,使用要插入的key和value新建一个节点,next指向原来头节点,将桶的头节点重新赋值为新的节点。
源码分析
注意:此部分参考自[]https://dzone.com/articles/hashmap-internal]java.util.HashMap.java
/** * The maximum capacity, used if a higher value is implicitly specified * by either of the constructors with arguments. * MUST be a power of two <= 1<<30. */ static final int MAXIMUM_CAPACITY = 1 << 30;
这是说hashmap所能拓展的最大值为 2^30=1,073,741,824
java.util.HashMap.java
/** * The default initial capacity - MUST be a power of two. */ static final int DEFAULT_INITIAL_CAPACITY = 16; /** * The load factor used when none specified in constructor. */ static final float DEFAULT_LOAD_FACTOR = 0.75f;
这是说数组默认的大小是16(数组的大小总是以2为底的幂,所以很容易理解为什么其扩容会如此快,数学中称为
指数爆炸),另一个常量
DEFAULT_LOAD_FACTOR代表负载系数,即当HashMap的规模达到其现有规模的75%,就会扩容两倍。
java.util.HashMap.java
/** * Associates the specified value with the specified key in this map. * If the map previously contained a mapping for the key, the old * value is replaced. * * @param key key with which the specified value is to be associated * @param value value to be associated with the specified key * @return the previous value associated with <tt>key</tt>, or * <tt>null</tt> if there was no mapping for <tt>key</tt>. * (A <tt>null</tt> return can also indicate that the map * previously associated <tt>null</tt> with <tt>key</tt>.) */ public V put(K key, V value) { if (key == null) return putForNullKey(value); int hash = hash(key.hashCode()); int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); return null; }
put方法主要有几个过程:
1)使用用户自宝义的hashCode()作为hash()方法的参数重新生成hashcode 2)使用重新生成的hashcode和数组的长度计算数组下标索引 3)如果key存在,则覆盖元素,否则创建一个新的实体(Entry)放在上面计算位置(index)中
java.util.HashMap.java
/** * Applies a supplemental hash function to a given hashCode, which * defends against poor quality hash functions. This is critical * because HashMap uses power-of-two length hash tables, that * otherwise encounter collisions for hashCodes that do not differ * in lower bits. Note: Null keys always map to hash 0, thus index 0. */ static int hash(int h) { // This function ensures that hashCodes that differ only by // constant multiples at each bit position have a bounded // number of collisions (approximately 8 at default load factor). h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } /** * Returns index for hash code h. */ static int indexFor(int h, int length) { return h & (length-1); }
indexFor方法中的h是hashCode,length默认是数组容量,即分桶的数量。
h&(length-1)这里的
&运算相当于取模运算,但是
&比
%效率更高。
hashMap实现自己的哈希函数来弥补对象自身质量不佳的哈希函数。因为容量总是2的N次方法,使用二次hash函数使高位也参与运算,防止低位不变造成严重哈希冲突。简而言之,目的是为了使key分布得更均匀,提高操作效率。
java.util.HashMap.java
/** * Associates the specified value with the specified key in this map. * If the map previously contained a mapping for the key, the old * value is replaced. * * @param key key with which the specified value is to be associated * @param value value to be associated with the specified key * @return the previous value associated with <tt>key</tt>, or * <tt>null</tt> if there was no mapping for <tt>key</tt>. * (A <tt>null</tt> return can also indicate that the map * previously associated <tt>null</tt> with <tt>key</tt>.) */ public V put(K key, V value) { if (key == null) return putForNullKey(value); int hash = hash(key.hashCode()); int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); return null; }
put和get计算哈希值分桶的算法是相同的。这里值得关注的是,当两个key有相同的hash值时,如何将新值传进去?
1)通过计算所得hashCode找到数组元素,即分桶(bucket),每个桶都是一个链表结果,遍历链表寻找具有相同的key的节点,如果找到覆盖其value
2)如果找不到,使用要插入的key和value新建一个节点,next指向原来头节点,将桶的头节点重新赋值为新的节点。
实现自己的HashMap:
这里的实现比较粗糙,只为了说明原理,更优的实现,请阅读Java官方源码。package collections; public class MyHashMap<K, V> { static final int DEFALUT_INITAL_CAPACITY = 16; private Entry<K, V>[] table; // Entry数组 private int size; public MyHashMap() { table = new Entry[DEFALUT_INITAL_CAPACITY]; } public int size() { return size; } public V put(K key, V value) { if (key == null) return null; int hash = key.hashCode(); int index = indexFor(hash, table.length); // 如果key已经存在,修改value for (Entry<K, V> e = table[index]; e != null;) { if (e.getKey().equals(key)) { V oldValue = e.getValue(); e.setValue(value); return oldValue; } e = e.next; } Entry<K, V> e = table[index]; // 如果key不存在,则新建一个Entry table[index] = new Entry<K, V>(key, value, e); size++; return null; } public V get(K key) { if (key == null) return null; int hashCode = key.hashCode(); int index = indexFor(hashCode, table.length); for (Entry<K, V> head = table[index]; head != null; head = head.next) { if (head.key.hashCode() == hashCode && head.key.equals(key)) { // 先比较hashCode为了加法比较效率 return head.getValue(); } } return null; } // 根据hashcode求数组的位置 static int indexFor(int h, int length) { return h % (length - 1); } private final class Entry<K, V> { final K key; V value; Entry<K, V> next; public Entry(K k, V v, Entry n) { this.key = k; this.value = v; this.next = n; } public final V getValue() { return value; } public final void setValue(V value) { this.value = value; } public final Entry<K, V> getNext() { return next; } public final void setNext(Entry<K, V> next) { this.next = next; } public final K getKey() { return key; } @Override public boolean equals(Object o) { if (!(o instanceof Entry)) { return false; } Entry e1 = (Entry) o; if (e1.getKey() == getKey() || (e1.getKey() != null) && e1.getKey().equals(e1.getKey())) { Object v1 = e1.getValue(); if (v1 == value || (v1 != null && v1.equals(value))) return true; } return false; } @Override public final int hashCode() { return (key == null ? 0 : key.hashCode() ^ (value == null ? 0 : value.hashCode())); } @Override public final String toString() { StringBuilder sb = new StringBuilder("["); sb.append(key); sb.append("="); sb.append(value); sb.append("]"); return sb.toString(); } } public static void main(String[] args) { MyHashMap<String, Object> map = new MyHashMap<>(); map.put("name", "cgz"); map.put("age", 25); System.out.println(map.get("name")); System.out.println(map.get("age")); System.out.println(map.size()); } }
相关文章推荐
- 【自己写个AS3组件库】之 “扩展Sprite类,实现销毁和UI重绘机制”
- 关于HashMap中的Hash算法和HashMap的自己的实现
- 自己动手写CPU之第七阶段(5)——流水线暂停机制的设计与实现
- HashMap的内部实现机制,Hash是怎样实现的,什么时候ReHash
- HashMap的内部实现机制,Hash是怎样实现的,什么时候ReHash
- 内存管理内幕【C/C++ 实现自己的内存管理机制】
- 深入理解Java中的HashMap的实现机制
- hashmap的内部实现机制,hash是怎样实现的,什么时候rehash
- HashMap的原理和内部实现机制
- 实现自己的hashset和hashmap
- 实例一——为自己的操作系统中加入中断(中断机制的实现)
- 实现自己的ArrayList和HashMap
- 实现自己的可重用拦截器机制
- java ConcurrentHashMap实现机制
- HashMap内部实现机制及优化----第一篇
- hashmap实现机制
- Binder机制分析(3)—— 实现自己的Service
- hashmap的内部实现机制
- 自己使用jdk实现aop机制
- 实现自己的HashMap