首先,我们看看桶的主要源码
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
int hash;
/**
* Creates new entry.
*/
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
}
在这里很多人都已经知道,其实就是一个链表
我们由外部的
transient Entry<K,V>[] table;
得知HashMap是数组加链表,我们为方便常常把这个链表看成桶
下面我们写一个测试用例来进行测试
package Map;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
public class HashMapTest {
public static void main(String[] args) {
Map<Integer,Integer> m=new HashMap<>();
m.put(1, 1);
m.put(1, 2);//由于1和1的hashcode相等,用equels判断又相等,所以直接替换
m.put(3, 3);
m.put(0, 3);
Iterator iter=m.entrySet().iterator();
while(iter.hasNext()){
Map.Entry<Integer,Integer> entry=(Entry<Integer, Integer>) iter.next();
System.out.println(entry.getKey()+":"+entry.getValue()+
" hashCode="+entry.getKey().hashCode());
}
Map<Person,Integer> pm=new HashMap<>();
Person p1=new Person("小明",19);
Person p2=new Person("小强",19);
Person p3=new Person("大雄",19);
Person p4=new Person("安妮",5);
pm.put(p1, 1);
pm.put(p2, 2);//由于p1和p2的hashcode相等,用equels判断不相等,所以在p1的桶后面继续添加p2进桶
pm.put(p3, 3);
pm.put(p4, 4);
Iterator piter=pm.entrySet().iterator();
while(piter.hasNext()){
Map.Entry<Person,Integer> entry=(Entry<Person, Integer>) piter.next();
System.out.println(entry.getKey().name+":"+entry.getValue()+
" hashCode="+entry.getKey().hashCode());
//输出时发现小强在小明前面,证明小强在链头,而小明在后面
}
}
}
class Person{
String name;
int age;
Person(String name,int age){
this.name=name;
this.age=age;
}
public int hashCode(){
return age;
}
public boolean equals(Object o){
if(o instanceof Person){
return name.equals(((Person) o).name);
}else{
return false;
}
}
}
输出结果为:
0:3 hashCode=0
1:2 hashCode=1
3:3 hashCode=3
大雄:3 hashCode=19
小强:2 hashCode=19
小明:1 hashCode=19
安妮:4 hashCode=5
从其中我们可以看出,HashMap在加入一个key的时候,先查看key的hashcode是否已存在,如果已存在,则通过equals判断key是否相等:如果相等,那么直接替换对应值;
如果不相等,则加入同一个桶内,并且放在链表的表头,将桶内所有数据后移一位。
我们观察HashMap添加数据时的源码
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key);
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;
}
我们发现在循环中做判断key是否相等,如果不相等,则通过addEntry来添加数据,源码如下:
void addEntry(int hash, K key, V value, int bucketIndex) { if ((size >= threshold) && (null != table[bucketIndex])) { resize(2 * table.length); hash = (null != key) ? hash(key) : 0; bucketIndex = indexFor(hash, table.length); } createEntry(hash, key, value, bucketIndex); } void createEntry(int hash, K key, V value, int bucketIndex) { Entry<K,V> e = table[bucketIndex]; table[bucketIndex] = new Entry<>(hash, key, value, e); size++; }
我们也可以发现HashMap并非完全无序,在链表部分是有序的,但是我们要尽可能做到减小链表的长度,最好链表长度为1,这样才能保证查询效率为O(1),不被链表O(n)所干扰。
浙公网安备 33010602011771号