HashSet源码分析

来源:互联网 发布:设计淘宝店要多少钱 编辑:程序博客网 时间:2024/05/20 08:22

转自;

HashSet 是一个没有重复元素的集合
它是由HashMap实现的,不保证元素的顺序,而且HashSet允许使用 null 元素
HashSet非同步的。如果多个线程同时访问一个哈希 set,而其中至少一个线程修改了该 set,那么它必须 保持外部同步。这通常是通过对自然封装该 set 的对象执行同步操作来完成的。如果不存在这样的对象,则应该使用 Collections.synchronizedSet 方法来“包装” set。最好在创建时完成这一操作,以防止对该 set 进行意外的不同步访问:

  1. Set s = Collections.synchronizedSet(new HashSet(...)); 

HashSet通过iterator()返回的迭代器是fail-fast的。


  1. public class HashSet<E>  
  2.     extends AbstractSet<E>  
  3.     implements Set<E>, Cloneable, java.io.Serializable  
  4. {  
  5.     static final long serialVersionUID = -5024744406713321676L;  
  6.  
  7.     // HashSet是通过map(HashMap对象)保存内容的  
  8.     private transient HashMap<E,Object> map;  
  9.  
  10.     // PRESENT是向map中插入key-value对应的value  
  11.     // 因为HashSet中只需要用到key,而HashMap是key-value键值对;  
  12.     // 所以,向map中添加键值对时,键值对的值固定是PRESENT  
  13.     private static final Object PRESENT = new Object();  
  14.  
  15.     // 默认构造函数  
  16.     public HashSet() {  
  17.         // 调用HashMap的默认构造函数,创建map  
  18.         map = new HashMap<E,Object>();  
  19.     }  
  20.  
  21.     // 带集合的构造函数  
  22.     public HashSet(Collection<? extends E> c) {  
  23.         // 创建map。  
  24.         // 为什么要调用Math.max((int) (c.size()/.75f) + 1, 16),从 (c.size()/.75f) + 1 和 16 中选择一个比较大的树呢?          
  25.         // 首先,说明(c.size()/.75f) + 1  
  26.         //   因为从HashMap的效率(时间成本和空间成本)考虑,HashMap的加载因子是0.75。  
  27.         //   当HashMap的“阈值”(阈值=HashMap总的大小*加载因子) < “HashMap实际大小”时,  
  28.         //   就需要将HashMap的容量翻倍。  
  29.         //   所以,(c.size()/.75f) + 1 计算出来的正好是总的空间大小。  
  30.         // 接下来,说明为什么是 16 。  
  31.         //   HashMap的总的大小,必须是2的指数倍。若创建HashMap时,指定的大小不是2的指数倍;  
  32.         //   HashMap的构造函数中也会重新计算,找出比“指定大小”大的最小的2的指数倍的数。  
  33.         //   所以,这里指定为16是从性能考虑。避免重复计算。  
  34.         map = new HashMap<E,Object>(Math.max((int) (c.size()/.75f) + 116));  
  35.         // 将集合(c)中的全部元素添加到HashSet中  
  36.         addAll(c);  
  37.     }  
  38.  
  39.     // 指定HashSet初始容量和加载因子的构造函数  
  40.     public HashSet(int initialCapacity, float loadFactor) {  
  41.         map = new HashMap<E,Object>(initialCapacity, loadFactor);  
  42.     }  
  43.  
  44.     // 指定HashSet初始容量的构造函数  
  45.     public HashSet(int initialCapacity) {  
  46.         map = new HashMap<E,Object>(initialCapacity);  
  47.     }  
  48.  
  49.     HashSet(int initialCapacity, float loadFactor, boolean dummy) {  
  50.         map = new LinkedHashMap<E,Object>(initialCapacity, loadFactor);  
  51.     }  
  52.  
  53.     // 返回HashSet的迭代器  
  54.     public Iterator<E> iterator() {  
  55.         // 实际上返回的是HashMap的“key集合的迭代器”  
  56.         return map.keySet().iterator();  
  57.     }  
  58.  
  59.     public int size() {  
  60.         return map.size();  
  61.     }  
  62.  
  63.     public boolean isEmpty() {  
  64.         return map.isEmpty();  
  65.     }  
  66.  
  67.     public boolean contains(Object o) {  
  68.         return map.containsKey(o);  
  69.     }  
  70.  
  71.     // 将元素(e)添加到HashSet中  
  72.     public boolean add(E e) {  
  73.         return map.put(e, PRESENT)==null;  
  74.     }  
  75.  
  76.     // 删除HashSet中的元素(o)  
  77.     public boolean remove(Object o) {  
  78.         return map.remove(o)==PRESENT;  
  79.     }  
  80.  
  81.     public void clear() {  
  82.         map.clear();  
  83.     }  
  84.  
  85.     // 克隆一个HashSet,并返回Object对象  
  86.     public Object clone() {  
  87.         try {  
  88.             HashSet<E> newSet = (HashSet<E>) super.clone();  
  89.             newSet.map = (HashMap<E, Object>) map.clone();  
  90.             return newSet;  
  91.         } catch (CloneNotSupportedException e) {  
  92.             throw new InternalError();  
  93.         }  
  94.     }  
  95.  
  96.     // java.io.Serializable的写入函数  
  97.     // 将HashSet的“总的容量,加载因子,实际容量,所有的元素”都写入到输出流中  
  98.     private void writeObject(java.io.ObjectOutputStream s)  
  99.         throws java.io.IOException {  
  100.         // Write out any hidden serialization magic  
  101.         s.defaultWriteObject();  
  102.  
  103.         // Write out HashMap capacity and load factor  
  104.         s.writeInt(map.capacity());  
  105.         s.writeFloat(map.loadFactor());  
  106.  
  107.         // Write out size  
  108.         s.writeInt(map.size());  
  109.  
  110.         // Write out all elements in the proper order.  
  111.         for (Iterator i=map.keySet().iterator(); i.hasNext(); )  
  112.             s.writeObject(i.next());  
  113.     }  
  114.  
  115.  
  116.     // java.io.Serializable的读取函数  
  117.     // 将HashSet的“总的容量,加载因子,实际容量,所有的元素”依次读出  
  118.     private void readObject(java.io.ObjectInputStream s)  
  119.         throws java.io.IOException, ClassNotFoundException {  
  120.         // Read in any hidden serialization magic  
  121.         s.defaultReadObject();  
  122.  
  123.         // Read in HashMap capacity and load factor and create backing HashMap  
  124.         int capacity = s.readInt();  
  125.         float loadFactor = s.readFloat();  
  126.         map = (((HashSet)thisinstanceof LinkedHashSet ?  
  127.                new LinkedHashMap<E,Object>(capacity, loadFactor) :  
  128.                new HashMap<E,Object>(capacity, loadFactor));  
  129.  
  130.         // Read in size  
  131.         int size = s.readInt();  
  132.  
  133.         // Read in all elements in the proper order.  
  134.         for (int i=0; i<size; i++) {  
  135.             E e = (E) s.readObject();  
  136.             map.put(e, PRESENT);  
  137.         }  
  138.     }  

说明

HashSet的代码实际上非常简单,通过上面的注释应该很能够看懂。它是通过HashMap实现的,若对HashSet的理解有困难,建议先学习以下HashMap;学完HashMap之后,在学习HashSet就非常容易了。


通过Iterator遍历HashSet

第一步:根据iterator()获取HashSet的迭代器。
第二步:遍历迭代器获取各个元素

  1. // 假设set是HashSet对象  
  2. for(Iterator iterator = set.iterator();  
  3.        iterator.hasNext(); ) {   
  4.     iterator.next();  
  5. }     

3.2 通过for-each遍历HashSet

第一步:根据toArray()获取HashSet的元素集合对应的数组。
第二步:遍历数组,获取各个元素。

  1. // 假设set是HashSet对象,并且set中元素是String类型  
  2. String[] arr = (String[])set.toArray(new String[0]);  
  3. for (String str:arr)  
  4.     System.out.printf("for each : %s\n", str);  

HashSet的遍历测试程序如下: 

  1. import java.util.Random;  
  2. import java.util.Iterator;  
  3. import java.util.HashSet;  
  4.  
  5. /*  
  6.  * @desc 介绍HashSet遍历方法  
  7.  *  
  8.  * @author skywang  
  9.  */ 
  10. public class HashSetIteratorTest {  
  11.  
  12.     public static void main(String[] args) {  
  13.         // 新建HashSet  
  14.         HashSet set = new HashSet();  
  15.  
  16.         // 添加元素 到HashSet中  
  17.         for (int i=0; i<5; i++)  
  18.             set.add(""+i);  
  19.  
  20.         // 通过Iterator遍历HashSet  
  21.         iteratorHashSet(set) ;  
  22.  
  23.         // 通过for-each遍历HashSet  
  24.         foreachHashSet(set);  
  25.     }  
  26.  
  27.     /*  
  28.      * 通过Iterator遍历HashSet。推荐方式  
  29.      */ 
  30.     private static void iteratorHashSet(HashSet set) {  
  31.         for(Iterator iterator = set.iterator();  
  32.                iterator.hasNext(); ) {  
  33.             System.out.printf("iterator : %s\n", iterator.next());  
  34.         }  
  35.     }  
  36.  
  37.     /*  
  38.      * 通过for-each遍历HashSet。不推荐!此方法需要先将Set转换为数组  
  39.      */ 
  40.     private static void foreachHashSet(HashSet set) {  
  41.         String[] arr = (String[])set.toArray(new String[0]);  
  42.         for (String str:arr)  
  43.             System.out.printf("for each : %s\n", str);  
  44.     }  


0 0
原创粉丝点击