HashMap的是基于Hash的规则。
Hash简单的说就是对变量/对象的属性应用某种算法的后得到的唯一的串,用这个串来确定变量/对象的唯一性。一个正确的哈希函数必须遵守这个准则。
当哈希函数应用在相同的对象或者恶趣啊了的对象的时候,每次执行都应该返回相同的值。换句话说,两个相等的对象应该又相同的hashcode。
应当注意:所有Java对象都从Object类继承了一个默认的hashCode()方法。这个方法将对象在内存中的地址作为整数返回,这是一个很好的hash实现,他确保了不同的对象拥有不同的hashcode。
map的定义是:一个映射键(key)到值(value)的对象。
所以,在HashMap中一定又一定的机制来存储这些键值对。因此HshMap有一个内部累Entry。
static class Entry<K,V> implements Map.Entry<K,V> { final K key; V value; Entry<K,V> next; final int hash; ...//More code goes here }
当然,Entry类有属性用来存储键值对映射,key被final标记,除了key和value,我们还能看到两个变量next和hash。put()方法的实现:
首选,检查key是否为null,如果key是null值被存在table[0]的位置,因为null的hashcode始终为0。接下来,通过key的hashCode()方法计算了这个key的hash值,这个hash值被用来计算存储Entry对象的数组中的位置。JDK的设计者假设会有一些人可能写出非常差的hashCode()方法,会出现一些非常大的或者非常小的hash值。为了解决这个问题,他们引入了另外一个hash函数,接受对抗的hashCode()。并转换到适合数组的容量大小。/** * 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; }
接着是indexFor(hash,table,length)方法,这个方法计算了entry对象存储的准确位置。我们都知道两个不相等的对象可能拥有过相同的hashCode值,者两个不同的对象怎么存储在相同的位置[bucket]。答案是,采用LinkedList,Entry类有一个next变量,这个变量总是指向链中的下一个变量,这个完全符合链表的特点。
因此,发生碰撞的时候,entry对象会被以链表的形式存储起来,当一个Entry对象需要被存储的时候,hashmap检查该位置是否已经有了一个entry对象,如果没有就存在那里,如果有就检查他的next属性,如果是空,当前的entry对象就作为已经存储的entry对象的下一个节点,一次类推。
如果,我么已经给存在的key存入另外一个value会怎样,逻辑上,旧的值将被替换掉。在检测了Entry对象的存储位置后,hashmap将会遍历那个位置的entry链表,对每一个entry调用equals方法,这个链表中的多有对象都具有相同的hashCode而equals方法都不相等。如果发现equals方法有相等的就执行替换。
在这种方式虾HashMap就能保证key的唯一性。
geet方法的工作机制
如果传入的key有匹配的就将该位置的value返回如果没有就返回null。
/** * Returns the value to which the specified key is mapped, * or {@code null} if this map contains no mapping for the key. * * <p>More formally, if this map contains a mapping from a key * {@code k} to a value {@code v} such that {@code (key==null ? k==null : * key.equals(k))}, then this method returns {@code v}; otherwise * it returns {@code null}. (There can be at most one such mapping.) * * <p>A return value of {@code null} does not <i>necessarily</i> * indicate that the map contains no mapping for the key; it's also * possible that the map explicitly maps the key to {@code null}. * The {@link #containsKey containsKey} operation may be used to * distinguish these two cases. * * @see #put(Object, Object) */ public V get(Object key) { if (key == null) return getForNullKey(); int hash = hash(key.hashCode()); for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) return e.value; } return null; }
代码与put()方法很像,除了 if (e.hash == hash && ((k = e.key) == key || key.equals(k)))。注意的点:
1. 存储Entry对象的数据结构是一个叫做Entry类型的table数组。
2. 数组中一个特定的索引位置成为[bucket],因为它可以容纳一个LinkedList的第一个元素的对象。
3. key对象的hashCode()需要用来计算Entry对象的存储位置。
4. key对象的equals()方法需要用来维持Map中对象的唯一性。
5. get()和put()方法跟Value对象的hashCOde和equals方法无关。
6. null的hashCode总是0,这样的entry对象总是被存储在数组的第一个位置。
hashmap的内部实现机制
最新推荐文章于 2023-07-07 15:20:57 发布