HashMap中put与get的实现

      java容器中,Map是用来存储键值对的,Map是一个接口,java为他实现了好几种实现,有HashMap、LinkedHashMap、TreeMap、WeakHashMap等,一般情况下,HashMap是最常用的,因为他的存取速度最快,这和他存取的方法有关。下面我们来看看HashMap是如何实现快速存取的。

       下面是《thinkinjava》中关于Map的一个实现:

public class SlowMap extends AbstractMap { private List keys = new ArrayList(); private List values = new ArrayList(); public V put(K key, V value) { V oldValue = get(key); // The old value or null if(!keys.contains(key)) { keys.add(key); values.add(value); } else values.set(keys.indexOf(key), value); return oldValue; } public V get(Object key) { // key is type Object, not K if(!keys.contains(key)) return null; return values.get(keys.indexOf(key)); } public Set> entrySet() { Set> set= new HashSet>(); Iterator ki = keys.iterator(); Iterator vi = values.iterator(); while(ki.hasNext()) set.add(new MapEntry(ki.next(), vi.next())); return set; } }

这个实现用list来分别存储键和值,这个实现虽然简单,但不是一个恰当的实现,因为他创建了键和值的副本,而且在查询键值时,用的是list的方法,只能进行简单的线性查询,速度较慢。

      HashMap的解决方案就是用散列来实现键和值的存取。散列将键保存在某处,以便能够很快找到。而存储一组元素最快的数据结构是数组,所以用他来表示键的信息。数组并不保存键的值,而是保存键的信息,注意,这里是散列的关键。

     我们通过键对象生成一个数字,将其作为数组的下标,这个数就是散列码,由定义在Object中的、且可能由你的类覆盖的hashcode()方法生成。为了解决数组容量被固定的问题,不同的键可以产生相同的下标,也就是可以有冲突。因此,数组多大就不太重要了了,任何键总能在数组中找到他的位置。

    于是,查询一个值的过程首先就是计算散列码,然后使用散列码查询数组,这样我们就不是线性查询数组,而是快速的跳到数组的某个位置,只对少数元素进行比较。这就是HashMap会如此快的原因。

     下面是JDK1.6中HashMap的put和get的实现,我将以注释的形式解释它的编码

public class HashMap extends AbstractMap implements Map, Cloneable, Serializable { //table是用来存储键值对的数组,Entry是Map.Entry在HashMap中的实现 transient Entry[] table; //hash方法用来计算散列的,对于这个算法,其实现是为实现散列的唯一性 static int hash(int h) { h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } //根据散列码和数组的长度,返回一个数组的下标索引值,即散列码 static int indexFor(int h, int length) { return h & (length-1); } //根据散列索引添加键值对到数组的方法 void addEntry(int hash, K key, V value, int bucketIndex) { Entry e = table[bucketIndex]; table[bucketIndex] = new Entry(hash, key, value, e); if (size++ >= threshold)//判断数组是否将要满,如果是,将数组扩大为现在的2倍 resize(2 * table.length); } 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 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; } } //在for循环中,e = table[i]就是跟据散列码i的值查找i对应的键值,如果不存在的话,e为null,直接执行下面的addEntry()方法,直接添加;如果该键存在,则e!=null,则执行for循环体里的语句,if (e.hash == hash && ((k = e.key) == key || key.equals(k))) 用来判断用散列码i获得的键值是否是要查询的键值,如果是,用新的值取代旧的值,并返回旧的值;如果不是,说明有俩个键的散列码相同,但键不同,于是我们执行e=e.next,如果此时e为null,我们就执行下面的addEntry,添加一个新的键值对;如果e!=null,就继续用if判断,知道将新的值写进数组 modCount++; addEntry(hash, key, value, i); return null; } //get方法与put方法原理相同,put是找到后插入值,get是找到后返回值 public V get(Object key) { if (key == null) return getForNullKey(); int hash = hash(key.hashCode()); for (Entry 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; } }

你可能感兴趣的:(HashMap中put与get的实现)