HashSet作为一种最简单的java集合类,真的可以用三句话来概括一下:

第一句:存放不重复的数据。

第二句:底层基于hash表实现。

第三句:内部基于HashMap。

这也就是说,你想要完完全全彻彻底底地把HashSet吃透,就一定要先吃透HashMap。这篇文章将带着你从特点到存储,再到最后的实现,从源码角度来分析一下。

一、认识

HashSet其实就是一个没有重复数据的集合,基本用法很简单,我们直接给个例子。

public class Test {
    public static void main(String[] args) {
        HashSet set = new HashSet();
        // 将元素添加到Set中
        set.add("a");
        //加入一个存在的则会替换。
        set.add("a");
        //是否包含某个值
        System.out.println("是否包含了a:", set.contains("a"));
        // 删除HashSet中的“e”
        set.remove("e");
        // 将Set转换为数组
        String[] arr = (String[])set.toArray(new String[0]);
        // 遍历HashSet
        for(Iterator iterator = set.iterator();iterator.hasNext();) 
            System.out.println(iterator.next());
        // 清空HashSet
        set.clear();
    }
}

以上只是列出了其最简单的用法。下面我们看看其继承关系。HashSet主要继承了三个接口Serializable、Cloneable、Set,并且实现了抽象类AbstractSet。我们直接看看源码:

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

学过HashMap的人应该都知道HashMap实现的是Map接口,而HashSet是Set接口。

下面我们就从源码的角度来分析一下HashSet。

二、源码分析

1、参数变量

//这个HashMap就是实际保存HashSet元素的容器
private transient HashMap<E,Object> map;
//PRESENT表示的意思很简单,也就是我们的HashSet只使用到了HashMap的key,
//所以此处定义一个静态的常量Object类,来充当HashMap的value
private static final Object PRESENT = new Object();

这里有个问题,那就是既然HashSet只使用到了HashMap的key,为什么不使用null来充当HashMap的value,而使用了PRESENT这个对象呢?

答:想要深入这个问题,我们还需要深入到源码中看看:

public boolean add(E e) {
     return map.put(e, PRESENT)==null;
}
public boolean remove(Object o) {
     return map.remove(o)==PRESENT;
}

以上两个是增删方法,在add一个元素的时候,其实调用的就是map.put(e, PRESENT)==null,HashMap在put元素的时候会出现两种情况:

情况一:put的元素是新的,那么map.put会发现key没有,那么直接插入即可。return结果为true。

情况二:put的元素是旧的,那么map.put会发现key已有,则直接返回相应的value,也就是PRESENT,PRESENT不等于null,return的也就是false了,表示HashSet插入失败。如果我们这里使用null为map.put的参数呢?直接返回相应的value,也就是null,这时候null==null是true。竟然返回了true。很明显就是错误的返回结果呀。

这其实也是去重复的原理。对于删除方法其实也是一样的。

2、构造函数

public HashSet() {
    map = new HashMap<E,Object>();
}
public HashSet(Collection<? extends E> c) {
    map = new HashMap<E,Object>(Math. max((int) (c.size()/.75f) + 116));
    addAll(c);
}
public HashSetint initialCapacity, float loadFactor) {
    map = new HashMap<E,Object>(initialCapacity, loadFactor);
}
public HashSetint initialCapacity) {
    map = new HashMap<E,Object>(initialCapacity);
}
HashSet( int initialCapacity, float loadFactor, boolean dummy) {
    map = new LinkedHashMap<E,Object>(initialCapacity, loadFactor);
}

HashSet提供的构造方法很多,有5个,在这里我想说明的是每一种构造方法,其实都是创建的HashMap。这也证明了我们文章开头提到的内部基于HashMap。

3、其他方法

增删方法我们已经提到了,在这里我们主要看一下其他方法。

//底层利用的还是HashMap
public boolean contains(Object o) {
    return map .containsKey(o);
}
//检查是否包含指定集合中所有元素
public boolean containsAll(Collection<?> c) {
    Iterator<?> e = c.iterator();
    //只要集合c中有一个元素不属于HashSet,返回false
    while (e.hasNext())
        if (!contains(e.next()))
            return false;
    return true;
}

上面的方法还包含了遍历元素的方式。

HashSet就是这么简单,源码里面几乎所有的方法都是HashMap实现的。


更多相关文章

  1. 设计模式之模板方法模式
  2. Java实现定时任务的三种方法
  3. Selenium3自动化测试【12】元素定位认知
  4. 使用 ThreadLocal 变量的时机和方法
  5. clone 方法是如何工作的
  6. VS中scanf等函数报错解决方法
  7. Jquery对选取到的元素显示指定的长度,对于的字符串用“...”显示
  8. jQuery编程基础精华02(属性、表单过滤器,元素的each,表单选择器,子元
  9. scrollTop到溢出滚动div中的活动元素

随机推荐

  1. sqlserver2000的jdbc驱动一定要用.exe安
  2. 浅谈MYSQL索引应用(一)
  3. 从MySQL转储中删除DEFINER子句。
  4. 使用VB将Excel导入到Sql中
  5. [SQL Server] 数据库日志文件自动增长导
  6. 通过SQL语句访问远程数据库
  7. linux使用freetds 连接连远程服务器sqlse
  8. Statement及PreparedStatement执行多个sq
  9. win10+java+mysql+tomcat+jpress环境搭建
  10. SQLite格式编号始终为2位小数