您的位置:首页 > 编程语言 > Java开发

java中的HashMap解析

2015-04-09 00:32 323 查看
这篇文章准备从源码的角度带大家分析一下java中的hashMap的原理,在了解源码之前,我们先根据自己的理解创建一个hashMap。

先说明一下创建的具体原理是这样的,所谓hashMap,必然是用hash方法来区分不同的key值。学过hash的都知道,我们解决hash冲突的一种方法就是使用散列和桶,首先确定所在的桶号,然后在桶里面逐个查找。其实我们也可以单纯使用数组实现map,使用散列是为了获得更高的查询效率。

要写自己的hashmap前,必须说明一下两个方法,就是hashcode()和equals()方法,要在map里面判断两个key是否相等,关键在于这个两个函数的返回值一定要相等(只有一个相等是没有用的,因为hashmap会先根据hashcode()方法查找桶,然后根据equals()方法获取value)

如果我们没有复写这个两个方法,object类是根据类所在内存地址来产生hashcode的,所以一般比较是不会相同的,又正因为这样,我们在使用自己构造的类当key值的时候,有时是有必要复写这两个方法的。下面是一个例子

class myClass{
int i = 0;
public myClass(int i) {
this.i = i;
}
@Override
public int hashCode() {
return i;
}

@Override
public boolean equals(Object obj) {
return obj instanceof myClass && i == ((myClass)obj).i;
}
}


注意上面的instanceof,我们首先要判断参数的类是否相同,这个非常重要,不过容易被忽略。(因为有可能是两个不同的类,有相同的属性,连属性值都相同,这样我们判断就会失误了)。另外我们要注意String类型重载了这两个方法,所以两个new String("aa")是相同的
在以下类中,我使用了一个arraylist来充当链,首先我们来看一个键值对类,用来保存键和值,这个是一个内部类,还有要实现hashmap必须先继承一个AbstractMap<K,V>的抽象类

import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Map;
import java.util.Set;

public class MyHashMap<K, V> extends AbstractMap<K, V> {
//链表长度
final static int SIZE = 999;
private List<K> keys = new ArrayList<K>();
    private List<V> values = new ArrayList<V>();
 /**
* Entry类,用于保存键值对
* @author Administrator
*
* @param <K>
* @param <V>
*/
static class MyEntry<K,V> implements Map.Entry<K, V>{
private K key;
private V value;

public MyEntry(K key,V value) {
this.key = key;
this.value = value;
}

@Override
public K getKey() {
return key;
}

@Override
public V getValue() {
return value;
}

@Override
public V setValue(V v) {
V oldValue = value;
value = v;
return oldValue;
}

@Override
public int hashCode() {
//使用key和value的hashcode共同构造新的hashcode
return (key==null?0:key.hashCode())^(value==null?0:value.hashCode());
}

@Override
public boolean equals(Object obj) {
//注意要检查类型是否相同
if(!(obj instanceof MyEntry)) return false;
MyEntry en = (MyEntry)obj;
//注意空值的情况
return (key==null?en.getKey()==key:key.equals(en.getKey())) &&
(value==null?en.getKey()==value:value.equals(en.getValue()));
}
}

@SuppressWarnings("unchecked")
ArrayList<MyEntry<K,V>>[] buckets = new ArrayList[SIZE];

@Override
public Set<java.util.Map.Entry<K, V>> entrySet() {
// TODO Auto-generated method stub
return null;
}

}
对于上面的键值对类MyEntry,我们要实现一个接口Map.Entry,因为我们一般使用hashmap都可以获得它的Entryset,继承这个类正是为了这个做准备

接下来我们先来实现put方法

/**
     * put方法
     */
    public V put(K key,V value){
        //原值用于返回
        V oldValue = null;
        //防止越界
        int index = Math.abs(key.hashCode())%SIZE;
        //检查是否有桶,没有创建一个
        if(buckets[index]==null){
            buckets[index] = new ArrayList<MyEntry<K,V>>();
        }
        ArrayList<MyEntry<K,V>> bucket = buckets[index];
        //创建键值对对象entry
        MyEntry<K, V> pair = new MyEntry<K, V>(key, value);
        boolean found = false;
        ListIterator<MyEntry<K, V>> it = bucket.listIterator();
        //遍历桶
        while(it.hasNext()){
            MyEntry<K, V> iPair = it.next();
            //如果已经在map里面,更新
            if(iPair.getKey().equals(key)){
                oldValue = iPair.getValue();
                it.set(pair);
                values.set(keys.indexOf(key),value);        
                found = true;
                break;
            }
        }
        //不在map里面,新增
        if(!found){
            keys.add(key);
            values.add(value);
            bucket.add(pair);
        }
        return oldValue;
    }


这上面的思路应该说是非常清晰,首先查找桶,没有则新建,然后在桶里面查找key值,如果已经存在map里面了,更新,否则新增。
再来看get方法,就更加清晰了

/**
* get方法
*/
public V get(Object key){
int index = Math.abs(key.hashCode())%SIZE;
if(buckets[index]==null) return null;
for(MyEntry<K, V> pair:buckets[index]){
if(pair.getKey().equals(key)){
return pair.getValue();
}
}
return null;
}
上面首先查找对应桶,没有返回null,如果有则在桶内遍历查找
最后再来看一下entrySet类

private class MyEntrySet extends AbstractSet<Map.Entry<K, V>>{

        @Override
        public Iterator<java.util.Map.Entry<K, V>> iterator() {
            return new Iterator<java.util.Map.Entry<K, V>>() {
                private int index = -1;
                boolean canRemove;
                @Override
                public boolean hasNext() {
                    return index<keys.size()-1;                    
                }

                @Override
                public MyEntry<K, V> next() {
                    boolean canRemove = true;
                    ++index;                    
                    return new MyEntry<K, V>(keys.get(index), values.get(index));
                }

                @Override
                public void remove() {
                    if(!canRemove){
                        throw new IllegalStateException();
                    }
                    canRemove = false;
                    keys.remove(index);
                    values.remove(index--);
                }
            };            
        }

        @Override
        public int size() {            
            return keys.size();
        }        
    }
这个内部类主要是为我们提供entry用于外部遍历使用
下面是完整代码,大家可以测试一下

package test;

import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;

public class MyHashMap<K, V> extends AbstractMap<K, V> {
    //链表长度
    final static int SIZE = 999;
    private List<K> keys = new ArrayList<K>();
    private List<V> values = new ArrayList<V>();
    
    /**
     * Entry类,用于保存键值对
     * @author Administrator
     *
     * @param <K>
     * @param <V>
     */
    static class MyEntry<K,V> implements Map.Entry<K, V>{
        private K key;
        private V value;
        
        public MyEntry(K key,V value) {
            this.key = key;
            this.value = value;
        }
        
        @Override
        public K getKey() {            
            return key;
        }

        @Override
        public V getValue() {            
            return value;
        }

        @Override
        public V setValue(V v) {        
            V oldValue = value;
            value = v;
            return oldValue;
        }
        
        @Override
        public int hashCode() {
            //使用key和value的hashcode共同构造新的hashcode
            return (key==null?0:key.hashCode())^(value==null?0:value.hashCode());
        }
        
        @Override
        public boolean equals(Object obj) {
            //注意要检查类型是否相同
            if(!(obj instanceof MyEntry)) return false;        
            MyEntry en = (MyEntry)obj;
            //注意空值的情况
            return (key==null?en.getKey()==key:key.equals(en.getKey())) &&
                    (value==null?en.getKey()==value:value.equals(en.getValue()));
        }
    }
    
    @SuppressWarnings("unchecked")
    ArrayList<MyEntry<K,V>>[] buckets = new ArrayList[SIZE];
    
    /**      * put方法      */     public V put(K key,V value){         //原值用于返回         V oldValue = null;         //防止越界         int index = Math.abs(key.hashCode())%SIZE;         //检查是否有桶,没有创建一个         if(buckets[index]==null){             buckets[index] = new ArrayList<MyEntry<K,V>>();         }         ArrayList<MyEntry<K,V>> bucket = buckets[index];         //创建键值对对象entry         MyEntry<K, V> pair = new MyEntry<K, V>(key, value);         boolean found = false;         ListIterator<MyEntry<K, V>> it = bucket.listIterator();         //遍历桶         while(it.hasNext()){             MyEntry<K, V> iPair = it.next();             //如果已经在map里面,更新             if(iPair.getKey().equals(key)){                 oldValue = iPair.getValue();                 it.set(pair);                 values.set(keys.indexOf(key),value);                         found = true;                 break;             }         }         //不在map里面,新增         if(!found){             keys.add(key);             values.add(value);             bucket.add(pair);         }         return oldValue;     }
    
    /**
     * get方法
     */
    public V get(Object key){
        int index = Math.abs(key.hashCode())%SIZE;
        if(buckets[index]==null) return null;
        for(MyEntry<K, V> pair:buckets[index]){
            if(pair.getKey().equals(key)){
                return pair.getValue();
            }
        }
        return null;
    }
    
    private class MyEntrySet extends AbstractSet<Map.Entry<K, V>>{         @Override         public Iterator<java.util.Map.Entry<K, V>> iterator() {             return new Iterator<java.util.Map.Entry<K, V>>() {                 private int index = -1;                 boolean canRemove;                 @Override                 public boolean hasNext() {                     return index<keys.size()-1;                                     }                 @Override                 public MyEntry<K, V> next() {                     boolean canRemove = true;                     ++index;                                         return new MyEntry<K, V>(keys.get(index), values.get(index));                 }                 @Override                 public void remove() {                     if(!canRemove){                         throw new IllegalStateException();                     }                     canRemove = false;                     keys.remove(index);                     values.remove(index--);                 }             };                     }         @Override         public int size() {                         return keys.size();         }             }
    
    private MyEntrySet myEntrySet = new MyEntrySet();
    @Override
    public Set<java.util.Map.Entry<K, V>> entrySet() {        
        return myEntrySet;
    }

}


OK,定义了我们自己hashmap以后,我们再来对照着看源代码,就比较容易,虽然还有些区别,但是希望加深大家的理解
首先来看get方法

/**
* Returns the value of the mapping with the specified key.
*
* @param key
*            the key.
* @return the value of the mapping with the specified key, or {@code null}
*         if no mapping for the specified key is found.
*/
public V get(Object key) {
//检查key为null
        if (key == null) {
HashMapEntry<K, V> e = entryForNullKey;
return e == null ? null : e.value;
}

// Doug Lea's supplemental secondaryHash function (inlined)
//利用key的hashcode,计算新的hash
 int hash = key.hashCode();
hash ^= (hash >>> 20) ^ (hash >>> 12);
hash ^= (hash >>> 7) ^ (hash >>> 4);
//遍历数组查找是否存在对应值
HashMapEntry<K, V>[] tab = table;
for (HashMapEntry<K, V> e = tab[hash & (tab.length - 1)];
e != null; e = e.next) {
K eKey = e.key;
if (eKey == key || (e.hash == hash && key.equals(eKey))) {
return e.value;
}
}
return null;
}


用源代码跟我们写的代码比较,发现也是先处理null值,源码中使用了一个特定的对象来代表key为Null的entry
然后是计算新的hash,这个怎么计算我们不理它,只要知道为了hash更加完美,我们需要根据key的hashcode重新一次hash值

然后及时遍历查找对应value

接下来看put方法

/**
* Maps the specified key to the specified value.
*
* @param key
*            the key.
* @param value
*            the value.
* @return the value of any previous mapping with the specified key or
*         {@code null} if there was no such mapping.
*/
@Override public V put(K key, V value) {
//如果新增的key为null,直接返回新生成的一个特定对象
 if (key == null) {
return putValueForNullKey(value);
}
//重新计算hash值
int hash = secondaryHash(key.hashCode());
HashMapEntry<K, V>[] tab = table;
int index = hash & (tab.length - 1);
//遍历,如果存在就更新
for (HashMapEntry<K, V> e = tab[index]; e != null; e = e.next) {
if (e.hash == hash && key.equals(e.key)) {
preModify(e);
V oldValue = e.value;
e.value = value;
return oldValue;
}
}

// No entry for (non-null) key is present; create one
modCount++;
if (size++ > threshold) {
tab = doubleCapacity();
index = hash & (tab.length - 1);
}
//没有就新增
 addNewEntry(key, value, hash, index);
return null;
}
/**
*为控制生产一个特定对象
*/
private V putValueForNullKey(V value) {
        HashMapEntry<K, V> entry = entryForNullKey;
        if (entry == null) {
            addNewEntryForNullKey(value);
            size++;
            modCount++;
            return null;
        } else {
            preModify(entry);
            V oldValue = entry.value;
            entry.value = value;
            return oldValue;
        }
    }
对比我们的代码来看,思路差不多,就是处理null值的时候有不同

最后来看我们的entrySet
private final class EntrySet extends AbstractSet<Entry<K, V>> {
public Iterator<Entry<K, V>> iterator() {
return newEntryIterator();
}
public boolean contains(Object o) {
if (!(o instanceof Entry))
return false;
Entry<?, ?> e = (Entry<?, ?>) o;
return containsMapping(e.getKey(), e.getValue());
}
public boolean remove(Object o) {
if (!(o instanceof Entry))
return false;
Entry<?, ?> e = (Entry<?, ?>)o;
return removeMapping(e.getKey(), e.getValue());
}
public int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
public void clear() {
HashMap.this.clear();
}
}


必须实现的方法有对应的实现,其中size是另外记录的一个变量,用来记录数据条数
这个必须结合iterator一起看,查找源代码以后,发现对应的是这个class

private final class EntryIterator extends HashIterator
implements Iterator<Entry<K, V>> {
public Entry<K, V> next() { return nextEntry(); }
}
继承自HashIterator
private abstract class HashIterator {
int nextIndex;
HashMapEntry<K, V> nextEntry = entryForNullKey;
HashMapEntry<K, V> lastEntryReturned;
int expectedModCount = modCount;

HashIterator() {
if (nextEntry == null) {
HashMapEntry<K, V>[] tab = table;
HashMapEntry<K, V> next = null;
while (next == null && nextIndex < tab.length) {
next = tab[nextIndex++];
}
nextEntry = next;
}
}

public boolean hasNext() {
return nextEntry != null;
}

HashMapEntry<K, V> nextEntry() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (nextEntry == null)
throw new NoSuchElementException();

HashMapEntry<K, V> entryToReturn = nextEntry;
HashMapEntry<K, V>[] tab = table;
HashMapEntry<K, V> next = entryToReturn.next;
while (next == null && nextIndex < tab.length) {
next = tab[nextIndex++];
}
nextEntry = next;
return lastEntryReturned = entryToReturn;
}

public void remove() {
if (lastEntryReturned == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
HashMap.this.remove(lastEntryReturned.key);
lastEntryReturned = null;
expectedModCount = modCount;
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: