java 集合 —— Set 实现类之 HashSet

源码基于jdk 1.7.81

HashSet 简介

HashSet 是一个元素不能重复的集合。

HashSet 中当添加的元素有重复时,添加失败。

HashSet 是 Set 的一个实现类,而 Set 又继承了Collection 方法,并且没有添加多余的方法。

HashSet 继承了AbstractSet 类。

实现了 Cloneable 接口,说明它重写了clone()方法,可以被克隆。

实现了 java.io.Serializable 接口,说明它可以被序列化。

public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable

HashSet 成员变量

    static final long serialVersionUID = -5024744406713321676L;
	
    private transient HashMap<E,Object> map;

    private static final Object PRESENT = new Object();

 map 存储的是键值对,在这里我们只用到了它的 key ,Object 是用来给它的 value 赋值的。所以 HashSet 拥有 HashMap 的所有特性,是用数组加链表的数据结构,并且每个 key 值是唯一的,在 HashSet 里体现的是它的值使唯一的。

HashSet 的构造函数

	//默认构造函数
    public HashSet() {
		map = new HashMap<E,Object>();
    }

	//保存Collection 里面所有的数据
    public HashSet(Collection<? extends E> c) {
    	  // 创建map。
          // 为什么要调用Math.max((int) (c.size()/.75f) + 1, 16),从 (c.size()/.75f) + 1 和 16 中选择一个比较大的树呢?        
          // 首先,说明(c.size()/.75f) + 1
          //   因为从HashMap的效率(时间成本和空间成本)考虑,HashMap的加载因子是0.75。
          //   当HashMap的“阈值”(阈值=HashMap总的大小*加载因子) < “HashMap实际大小”时,
          //   就需要将HashMap的容量翻倍。
          //   所以,(c.size()/.75f) + 1 计算出来的正好是总的空间大小。
          // 接下来,说明为什么是 16 。
          //   HashMap的总的大小,必须是2的指数倍。若创建HashMap时,指定的大小不是2的指数倍;
          //   HashMap的构造函数中也会重新计算,找出比“指定大小”大的最小的2的指数倍的数。
          //   所以,这里指定为16是从性能考虑。避免重复计算。
		map = new HashMap<E,Object>(Math.max((int) (c.size()/.75f) + 1, 16));
		 //增加 c 的元素在 map 里。
		addAll(c);
    }

	//自动义容量和加载因子。
    public HashSet(int initialCapacity, float loadFactor) {
		map = new HashMap<E,Object>(initialCapacity, loadFactor);
    }
	
	//自定义容量
    public HashSet(int initialCapacity) {
		map = new HashMap<E,Object>(initialCapacity);
    }
	
	//自动义容量和加载因子。
    HashSet(int initialCapacity, float loadFactor, boolean dummy) {
		map = new LinkedHashMap<E,Object>(initialCapacity, loadFactor);
    }

HashSet 的构造方法都是调用 HashMap 的构造方法,学习HashSet 的时候可以对照着HashMap 来学习。

HashSet 的迭代器

//返回HashSet的迭代器
    public Iterator<E> iterator() {
		return map.keySet().iterator();
    }

HashSet 的迭代器是调用了HashMap 通过键遍历的迭代器。

HashSet 的基本操作

     /*
     * 不能重复
     * 去重
     * key 允许为 null
     * 线程不安全
     */
    public boolean add(E e) {
		return map.put(e, PRESENT)==null;//调用了HashMap的add方法
    }

    public V put(K key, V value) {
    	//当 key 为 null
        if (key == null)
            return putForNullKey(value);
		//得到 hash 值
        int hash = hash(key.hashCode());
		//获得索引
        int i = indexFor(hash, table.length);
		//遍历索引位置的链表
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
			//如果链表中已经存在当前的key,覆盖即可
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
		//如果当前链表没有就增加这个键值对到集合中
        addEntry(hash, key, value, i);
        return null;    // 当原来没有这个元素时,返回null.
    }

当被添加的元素已经存在在集合中,put 方法返回的 是原来的 value 值,在add 方法中,put 方法返回的 oldvalue 值 != null ,返回false,则添加失败。

当被添加的元素不存在集合中,添加元素在map 里,返回 null。

猜你喜欢

转载自blog.csdn.net/Alyson_jm/article/details/81171054