现在的位置: 首页 > 综合 > 正文

HashSet与HashMap关系之源码分析

2013年01月05日 ⁄ 综合 ⁄ 共 4541字 ⁄ 字号 评论关闭

本文转帖自http://lukuijun.javaeye.com/blog/340756

 

题目:请说出hashCode方法,equals方法,HashSet,HasMap之间的关系? 
解答:策略,分析jdk的源代码:

1、HashSet底层是采用HashMap实现的。

1 public HashSet() { 2 map = new HashMap<E,Object>(); 3 }

 

1 public boolean add(E e) { 2 return map.put(e, PRESENT)==null; 3 }

 

private transient HashMap<E,Object> map;是HashSet类里面定义的一个私有的成员变量。并且是transient类型的,在序列化的时候是不会序列化到文件里面去的,信息会丢失。 HashMap<E,Object>里面的key为E,是HashSet<E>里面放置的对象E(对象的引用,为了简单方便起见 我说成是对象,一定要搞清楚,在集合里面放置的永远都是对象的引用,而不是对象,对象是在堆里面的,这个一定要注
意),HashMap<E,Object>的value是Object类型的。 

 

2、这个HashMap的key就是放进HashSet中对象,value是Object类型的。当我去使用一个默认的构造方法的时候,执行 public HashSet() {map = new HashMap<E,Object>();},会将HashMap实例化,将集合能容纳的内型作为key,Object作为它的value。 当我们去往HashSet里面放值的时候,这个HashMap<E,Object>里面的Object类型的value到底取什么值呢?这个 时候我们就需要看HashSet的add方法,因为add方法可以往里面增加对象,通过往里面增加对象,会导致底层的HashMap增加一个key和
value。这个时候我们看一下add方法:  public boolean add(E e) {return map.put(e, PRESENT)==null;},add方法接受一个E类型的参数,这个E类型就是我们在HashSet里面能容纳的类型,当我们往HashSet里面 add对象的时候,HashMap是往里面put(E,PRESET),增加E为key,PRESET为value,PRESET是这样定义 的:private
static final Object PRESENT = new Object();从这里我们知道PRESET是private static final Object的常量并且已经实例化好了。HashMap<E,Object>()中的key为HashSet中add的对象,不能重 复,value为PRESET,这是jdk为了方便将value设为常量PRESET,因为value是可以重复的。 

3、当调用HashSet的add方法时,实际上是向HashMap中增加了一行(key-value对),该行的key就是向HashSet增加的那个对象,该行的value就是一个Object类型的常量。 

接着,我们看看HashMap的源码,流程讲到map.put(e, PRESENT)==null,我们往HashSet里面add一个对象,那么HashMap就往里面put(key,value)。HashMap的put方法源码如下:

 

复制代码
1 public V put(K key, V value) { 2 if (key == null) 3 return putForNullKey(value); 4 int hash = hash(key.hashCode()); 5 int i = indexFor(hash, table.length); 6 for (Entry<K,V> e = table[i]; e != null; e = e.next) { 7 Object k; 8 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { 9 V oldValue = e.value; 10 e.value = value; 11 e.recordAccess(this); 12 return oldValue; 13 } 14 } 15 16 modCount++; 17 addEntry(hash, key, value, i); 18 return null; 19 }
复制代码

首先,如果key == null,这个key是往HashSet里面add的那个对象,它返回一个putForNullKey(value),它是一个方法,源码如下:

复制代码
1 private V putForNullKey(V value) { 2 for (Entry<K,V> e = table[0]; e != null; e = e.next) { 3 if (e.key == null) { 4 V oldValue = e.value; 5 e.value = value; 6 e.recordAccess(this); 7 return oldValue; 8 } 9 } 10 modCount++; 11 addEntry(0, null, value, 0); 12 return null; 13 }
复制代码

 这里面有一个Entry,我们看看它的源码:

复制代码
1 static class Entry<K,V> implements Map.Entry<K,V> { 2 final K key; 3 V value; 4 Entry<K,V> next; 5 final int hash; 6 7 /** 8 * Creates new entry. 9 */ 10 Entry(int h, K k, V v, Entry<K,V> n) { 11 value = v; 12 next = n; 13 key = k; 14 hash = h; 15 } 16 17 public final K getKey() { 18 return key; 19 } 20 21 public final V getValue() { 22 return value; 23 } 24 25 public final V setValue(V newValue) { 26 V oldValue = value; 27 value = newValue; 28 return oldValue; 29 } 30 31 public final boolean equals(Object o) { 32 if (!(o instanceof Map.Entry)) 33 return false; 34 Map.Entry e = (Map.Entry)o; 35 Object k1 = getKey(); 36 Object k2 = e.getKey(); 37 if (k1 == k2 || (k1 != null && k1.equals(k2))) { 38 Object v1 = getValue(); 39 Object v2 = e.getValue(); 40 if (v1 == v2 || (v1 != null && v1.equals(v2))) 41 return true; 42 } 43 return false; 44 } 45 46 public final int hashCode() { 47 return (key==null ? 0 : key.hashCode()) ^ 48 (value==null ? 0 : value.hashCode()); 49 } 50 51 public final String toString() { 52 return getKey() + "=" + getValue(); 53 } 54 55 /** 56 * This method is invoked whenever the value in an entry is 57 * overwritten by an invocation of put(k,v) for a key k that's already 58 * in the HashMap. 59 */ 60 void recordAccess(HashMap<K,V> m) { 61 } 62 63 /** 64 * This method is invoked whenever the entry is 65 * removed from the table. 66 */ 67 void recordRemoval(HashMap<K,V> m) { 68 } 69 }
复制代码

static class Entry表示它是一个静态的内部类,注意,外部类是不能用static的,对类来说,只有内部类能用static来修饰。这个Entry它实现了一个 Map.Entry<K,V>接口,我看看Map.Entry<K,V>的源码:

复制代码
1 interface Entry<K,V> { 2 K getKey(); 3 V getValue(); 4 V setValue(V value); 5 boolean equals(Object o); 6 int hashCode(); 7 }
复制代码

 

 Map.Entry<K,V> 它是一个接口,里面定义了几个方法,Map.Entry<K,V>它实际上是一个内部的接口(inner interface),Map.Entry干什么用的呢?我看看Map接口有一个 Set<Map.Entry<K, V>> entrySet();方法,它返回一个Set集合,它里面是Map.Entry<K, V>类型的,这个方法用得非常多。表示你在调用一个Map的entrySet()方法,它会返回一个Set集合,这个集合里面放置的就是你的Map
的key和value这两个对象所共同组成的Map.Entry类型的那样的对象,Map.Entry类型对象里面放置的就是你的HashMap里面的 key和value,所以说当你去调用一个Map(不管是HashMap还是Treemap)的entrySet()方法,会返回一个Set集合,个集合 里面放置的就是你的Map的key和value这两个对象所共同组成的Map.Entry类型的那样的对象。 

HashMap的底层是怎样维护的呢?我们看一下源码:

1 /** 2 * The table, resized as necessary. Length MUST Always be a power of two. 3 */ 4 transient Entry[] table;

 

它是一个Entry类型的数组,table数组里面放Entry类型的,Entry的源码有

1 final K key; 2 V value;

 这里的K表示HashMap的key,V表示HashMap的value,所以我们可以大胆的断定,HashMap的底层是用数组来维护的。理解这一点非常重要,因为java的集合,底层大部分都是用数组来维护的,顶层之所以有那么高级,是因为底层对其进行了封装。 

4、HashMap底层采用数组来维护。数组里面的每一个元素都是Entry,而这个Entry里面是Key和value组成的内容。 

我们看HashMap的put方法,如果key == null,返回putForNullKey(value),看putForNullKey方法:

 

复制代码
1 private V putForNullKey(V value) { 2 for (Entry<K,V> e = table[0]; e != null; e = e.next) { 3 if (e.key == null) { 4 V oldValue = e.value; 5 e.value = value; 6 e.recordAccess(this); 7 return oldValue; 8 } 9 } 10 modCount++; 11 addEntry(0, null, value, 0); 12 return null; 13 }
复制代码

抱歉!评论已关闭.