24、HashSet与HashMap源代码分析

1、HashSet底层是使用HashMap实现的。当使用add方法将对象添加到set当中时,实际上是将该对象作为底层所维护的Map对象的Key,,而value则都是同一个Object对象(该对象我们用不上);

HashSet部分源代码:

[java] view plain copy print ?
  1. private transient HashMap<E,Object> map; 
[java] view plain copy print ?
  1. public HashSet() { 
  2. map = new HashMap<E,Object>(); 
  3.    } 
[java] view plain copy print ?
  1. public boolean add(E e) { 
  2. return map.put(e, PRESENT)==null
  3.    } 



HashSet的默认构造方法是生成一个HashMap对象,赋给变量map,HashSet的add()方法是向生成的HashMap中放入一个键值对,这个键值对的键key就是我们要存入HashSet的数据,值value是一个常量PRESENT,这个PRESENT是这样定义的:

[java] view plain copy print ?
  1. private static final Object PRESENT = new Object(); 


其他方法的定义:

[java] view plain copy print ?
  1. public boolean remove(Object o) { 
  2. return map.remove(o)==PRESENT; 
  3.    } 
  4.  
  5. public void clear() { 
  6. map.clear(); 
  7.    } 
  8.  
  9. public boolean isEmpty() { 
  10. return map.isEmpty(); 
  11.    } 
  12.  
  13. public int size() { 
  14. return map.size(); 
  15.    } 
  16.  
  17. public Iterator<E> iterator() { 
  18. return map.keySet().iterator(); 
  19.    } 

HashSet的底层都是借助于HashMap来实现的。

2、HashMap的源代码分析(这个很复杂)。先看一下最后面的内存映像图,理解HashMap的存储形式。

首先HashMap定义了几个重要的成员变量

[java] view plain copy print ?
  1. static final int DEFAULT_INITIAL_CAPACITY = 16//默认初始容量,是一个常量16 
  2.  
  3. static final float DEFAULT_LOAD_FACTOR = 0.75f;  //默认负载因子,常量float 0.75,就是说数组数据达到数组容量75%时,扩展数组 
  4.  
  5. transient Entry[] table;   //用来存放数据的数组,数组元素为Entry类型的。这是存放Map键值对的主体。 
  6.  
  7. int threshold; 
  8.  
  9. transient int size; 
  10.  
  11. final float loadFactor; 
[java] view plain copy print ?
  1.   

HashMap底层维护一个数组(Entry[]),我们向HashMap中所放置的对象实际上是存储在该数组中。

再看一下Entry的定义:

[java] view plain copy print ?
  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.         } 

Entry定义了四个成员变量,key和value分别用来存放键值对映射的键和值,这里注意key的定义是final的,也就是说key初始化完毕后就不能改变了,也因此HashMap只提供key的getter方法,value则提供了setter和getter方法。换句话说,如果我的HashMap中保存了“aaa”键和“value1”值这样一个键值对,而我想改成键为“bbb”而值不变依然为“value1”,只能通过先删除aaa键值对,再增加“bbb”键值对,不能直接修改“aaa”这个键。next指向另一个Entry'对象(相当于链表的指针),hash为一个final的int变量,也是只有在初始化时赋值,他是键key的hash值,Entry只有一个带参数的构造方法,所以生成新对象时就确定了key和hash。

HashMap默认构造方法:

[java] view plain copy print ?
  1. public HashMap() { 
  2.         this.loadFactor = DEFAULT_LOAD_FACTOR; 
  3.         threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR); 
  4.         table = new Entry[DEFAULT_INITIAL_CAPACITY]; 
  5.         init(); 
  6.     } 


生成一个默认长度(16)的Entry数组。

往HashMap对象中存放键值对方法put():

[java] view plain copy print ?
  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的键值对,调用了putForNullKey()方法:

[java] view plain copy print ?
  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.     } 


从这个方法看,好像是key为null的键值对是固定保存在table[0]的。(这里有疑问)。e.recordAccess()方法不明白意思。最后是调用了addEntry(0, null, value, 0);这个方法(应该就是table[0]处,说明key为null的hash值为0,保存在数组table第一位):

[java] view plain copy print ?
  1. void addEntry(int hash, K key, V value, int bucketIndex) { 
  2. Entry<K,V> e = table[bucketIndex]; 
  3.        table[bucketIndex] = new Entry<K,V>(hash, key, value, e); 
  4.        if (size++ >= threshold) 
  5.            resize(2 * table.length); 
  6.    } 

这个方法先把bucketIndex处的对象赋值给e(bucketIndex也就是要添加的键值对在table数组的下标,从put方法看,就是i,i = indexFor(hash, table.length);),然后生成一个新的Entry对象,存放在table[bucketIndex]处,而这个新Entry对象的next指向了原来的那个Entry对象,就是e。

put()方法中的for循环用来判断链上是否已经存有相同键(key)的元素,判断是否相等,就是要hash值相等并且((k = e.key) == key || key.equals(k)),如果有相同的键存在,就修改其值,反之就是没有相同的键,那么就执行addEntry()方法,添加新的键值对。

HashMap在内存中的示意映像图:

24、HashSet与HashMap源代码分析_第1张图片

HashMap底层维护一个数组(Entry[]),我们向HashMap中所放置的对象实际上是存储在该数组中。

当向HashMap中put一对键值时,他会根据key的hashCode值计算出一个位置,该位置就是此对象准备往数组中存放的位置。

如果该位置没有对象存在就将此对象直接放进数组当中;如果该位置已经有对象存在了,则顺着此存在的对象的链开始寻找(Entry类有一个Entry类型的next成员变量,指向了该对象的下一个对象),如果此链上有对象的话,再去使用equals方法进行比较,如果对此链上的某个对象的equals方法比较为false,则将该对象放到数组当中,然后将数组中该位置以前存在的那个对象连接到此对象的后面。


3、Properties类

主要用于一些属性文件,key = value形式
Properties的key和value都是String的

获取环境变量:

[java] view plain copy print ?
  1. import java.util.Iterator; 
  2. import java.util.Properties; 
  3. import java.util.Set; 
  4.  
  5. public class PropertiesTest 
  6.     public static void main(String[] args) 
  7.     { 
  8.         Properties p = System.getProperties(); 
  9.          
  10.         Set set = p.keySet(); 
  11.          
  12.         for(Iterator iter = set.iterator();iter.hasNext();) 
  13.         { 
  14.             String key = (String)iter.next(); 
  15.             String value = p.getProperty(key); 
  16.              
  17.             System.out.println(key + "=" + value); 
  18.         } 
  19.     } 

你可能感兴趣的:(java开发工具)