HashSet 源码

前言

    Set的实现类都是基于Map来实现的(HashSet是通过HashMap实现的)。
构造图如下:
    蓝色线条:继承
    绿色线条:接口实现

正文

    对于HashSet而言,它是基于HashMap来实现的,底层采用HashMap来保存元素。所以如果对HashMap比较熟悉,那么HashSet是so easy!!

HashSet简介

    HashSet定义

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

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

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

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

HashSet属性

// 底层使用HashMap来保存HashSet的元素
private transient HashMap<E,Object> map;

// 由于Set只使用到了HashMap的key,所以此处定义一个静态的常量Object类,来充当HashMap的value
private static final Object PRESENT = new Object();

    HashSet是用HashMap来保存数据,而主要使用到的就是HashMap的key。

    看到private static final Object PRESENT = new Object();

    这里使用一个静态的常量Object类来充当HashMap的value,既然这里map的value是没有意义的,为什么不直接使用null值来充当value呢?

    比如写成这样子private final Object PRESENT = null;我们都知道的是,Java首先将变量PRESENT分配在栈空间,而将new出来的Object分配到堆空间,这里的new Object()是占用堆内存的(一个空的Object对象占用8byte),而null值我们知道,是不会在堆空间分配内存的。那么想一想这里为什么不使用null值。想到什么吗,看一个异常类java.lang.NullPointerException。

    为了从根源上避免NullPointerException的出现,浪费8个byte又怎么样,在下面的代码中我再也不会写这样的代码啦if (xxx == null) { ... } else {....}。

HashSet构造函数

/**
* 使用HashMap的默认容量大小16和默认加载因子0.75初始化map,构造一个HashSet
*/
public HashSet() {
   map = new HashMap<E,Object>();
}

/**
* 构造一个指定Collection参数的HashSet,这里不仅仅是Set,只要实现Collection接口的容器都可以
*/
public HashSet(Collection<? extends E> c) {
   map = new HashMap<E,Object>(Math. max((int) (c.size()/.75f) + 1, 16));
   // 使用Collection实现的Iterator迭代器,将集合c的元素一个个加入HashSet中
   addAll(c);
}

/**
* 使用指定的初始容量大小和加载因子初始化map,构造一个HashSet
*/
public HashSet( int initialCapacity, float loadFactor) {
	map = new HashMap<E,Object>(initialCapacity, loadFactor);
}

/**
* 使用指定的初始容量大小和默认的加载因子0.75初始化map,构造一个HashSet
*/
public HashSet( int initialCapacity) {
	map = new HashMap<E,Object>(initialCapacity);
}

/**
* 不对外公开的一个构造方法(默认default修饰),底层构造的是LinkedHashMap,dummy只是一个标示参数,无具体意义
*/
HashSet( int initialCapacity, float loadFactor, boolean dummy) {
	map = new LinkedHashMap<E,Object>(initialCapacity, loadFactor);
}

从构造方法可以很轻松的看出,HashSet的底层是一个HashMap,理解了HashMap后,这里没什么可说的。只有最后一个构造方法有写区别,这里构造的是LinkedHashMap,该方法不对外公开,实际上是提供给LinkedHashSet使用的,而第三个参数dummy是无意义的,只是为了区分其他构造方法。

API方法摘要

HashSet源码解析(基于JDK1.6.0_45)

增加和删除

/**
 * 利用HashMap的put方法实现add方法
 */
public boolean add(E e) {
	return map.put(e, PRESENT)== null;
}

/**
 * 利用HashMap的remove方法实现remove方法
 */
public boolean remove(Object o) {
	return map.remove(o)==PRESENT;
}

/**
 * 添加一个集合到HashSet中,该方法在AbstractCollection中
 */
public boolean addAll(Collection<? extends E> c) {
	boolean modified = false;
   // 取得集合c迭代器Iterator
   Iterator<? extends E> e = c.iterator();
   // 遍历迭代器
	while (e.hasNext()) {
	   // 将集合c的每个元素加入到HashSet中
	   if (add(e.next()))
		  modified = true;
   }
	return modified;
}

/**
 * 删除指定集合c中的所有元素,该方法在AbstractSet中
 */
public boolean removeAll(Collection<?> c) {
	boolean modified = false;

	// 判断当前HashSet元素个数和指定集合c的元素个数,目的是减少遍历次数
	if (size() > c.size()) {
		// 如果当前HashSet元素多,则遍历集合c,将集合c中的元素一个个删除
		for (Iterator<?> i = c.iterator(); i.hasNext(); )
			modified |= remove(i.next());
	} else {
		// 如果集合c元素多,则遍历当前HashSet,将集合c中包含的元素一个个删除
		for (Iterator<?> i = iterator(); i.hasNext(); ) {
			if (c.contains(i.next())) {
				i.remove();
				modified = true;
			}
		}
	}
	return modified;
}

是否包含

/**
 * 利用HashMap的containsKey方法实现contains方法
 */
public boolean contains(Object o) {
	return map.containsKey(o);
}

/**
 * 检查是否包含指定集合中所有元素,该方法在AbstractCollection中
 */
public boolean containsAll(Collection<?> c) {
   // 取得集合c的迭代器Iterator
   Iterator<?> e = c.iterator();
   // 遍历迭代器,只要集合c中有一个元素不属于当前HashSet,则返回false
	while (e.hasNext())
	   if (!contains(e.next()))
		   return false;
	return true;
}

    由于HashMap基于hash表实现,hash表实现的容器最重要的一点就是可以快速存取,那么HashSet对于contains方法,利用HashMap的containsKey方法,效率是非常之快的。

容量检查

/**
 * Returns the number of elements in this set (its cardinality).
 *
 * @return the number of elements in this set (its cardinality)
 */
public int size() {
   return map.size();
}

/**
 * Returns <tt>true</tt> if this set contains no elements.
 *
 * @return <tt> true</tt> if this set contains no elements
 */
public boolean isEmpty() {
   return map.isEmpty();
}

HashSet遍历方式

    通过Iterator遍历HashSet

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

// 假设set是HashSet对象
for(Iterator iterator = set.iterator();
    iterator.hasNext(); ) { 
    iterator.next();
}
    通过for-each遍历HashSet

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

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

HashSet示例

下面我们通过实例学习如何使用HashSet

import java.util.Iterator;
import java.util.HashSet;

/*
 * HashSet常用API的使用。
 */
public class HashSetTest {
	public static void main(String[] args) {
		// HashSet常用API
		testHashSetAPIs() ;
	}
	/*
     * HashSet除了iterator()和add()之外的其它常用API
     */
	private static void testHashSetAPIs() {
		// 新建HashSet
		HashSet set = new HashSet();
		// 将元素添加到Set中
		set.add("a");
		set.add("b");
		set.add("c");
		set.add("d");
		set.add("e");
		// 打印HashSet的实际大小
		System.out.printf("size : %d\n", set.size());
		// 判断HashSet是否包含某个值
		System.out.printf("HashSet contains a :%s\n", set.contains("a"));
		System.out.printf("HashSet contains g :%s\n", set.contains("g"));
		// 删除HashSet中的“e”
		set.remove("e");
		// 将Set转换为数组
		String[] arr = (String[])set.toArray(new String[0]);
		for (String str:arr)
			System.out.printf("for each : %s\n", str);
		// 新建一个包含b、c、f的HashSet
		HashSet otherset = new HashSet();
		otherset.add("b");
		otherset.add("c");
		otherset.add("f");
		// 克隆一个removeset,内容和set一模一样
		HashSet removeset = (HashSet)set.clone();
		// 删除“removeset中,属于otherSet的元素”
		removeset.removeAll(otherset);
		// 打印removeset
		System.out.printf("removeset : %s\n", removeset);
		// 克隆一个retainset,内容和set一模一样
		HashSet retainset = (HashSet)set.clone();
		// 保留“retainset中,属于otherSet的元素”
		retainset.retainAll(otherset);
		// 打印retainset
		System.out.printf("retainset : %s\n", retainset);
		// 遍历HashSet
		for(Iterator iterator = set.iterator();iterator.hasNext();) 
			System.out.printf("iterator : %s\n", iterator.next());
		// 清空HashSet
		set.clear();
		// 输出HashSet是否为空
		System.out.printf("%s\n", set.isEmpty()?"set is empty":"set is not empty");
	}

}

运行结果:

size : 5
HashSet contains a :true
HashSet contains g :false
for each : d
for each : b
for each : c
for each : a
removeset : [d, a]
retainset : [b, c]
iterator : d
iterator : b
iterator : c
iterator : a
set is empty

总结

HashSet和HashMap、Hashtable的区别

转载于:https://my.oschina.net/langwanghuangshifu/blog/2246881

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值