HashSet 的实现

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

对于 HashSet 而言,它是基于 HashMap 实现的,HashSet 底层采用 HashMap 来保存所有元素,因此 HashSet 的实现比较简单,查看 HashSet 的源代码,可以看到如下代码:

// 使用 HashMap 的 key 保存 HashSet 中所有元素
private transient HashMap<E,Object> map;
// 定义一个虚拟的 Object 对象作为 HashMap 的 value
private static final Object PRESENT = new Object();
...
// 初始化 HashSet,底层会初始化一个 HashMap
public HashSet()
{
map = new HashMap<E,Object>();
}
// 以指定的 initialCapacity、loadFactor 创建 HashSet
// 其实就是以相应的参数创建 HashMap
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);
}
// 调用 map 的 keySet 来返回所有的 key
public Iterator<E> iterator()
{
return map.keySet().iterator();
}
// 调用 HashMap 的 size() 方法返回 Entry 的数量,就得到该 Set 里元素的个数
public int size()
{
return map.size();
}
// 调用 HashMap 的 isEmpty() 判断该 HashSet 是否为空,
// 当 HashMap 为空时,对应的 HashSet 也为空
public boolean isEmpty()
{
return map.isEmpty();
}
// 调用 HashMap 的 containsKey 判断是否包含指定 key
//HashSet 的所有元素就是通过 HashMap 的 key 来保存的
public boolean contains(Object o)
{
return map.containsKey(o);
}
// 将指定元素放入 HashSet 中,也就是将该元素作为 key 放入 HashMap
public boolean add(E e)
{
return map.put(e, PRESENT) == null;
}
// 调用 HashMap 的 remove 方法删除指定 Entry,也就删除了 HashSet 中对应的元素
public boolean remove(Object o)
{
return map.remove(o)==PRESENT;
}
// 调用 Map 的 clear 方法清空所有 Entry,也就清空了 HashSet 中所有元素
public void clear()
{
map.clear();
}

由上面源程序可以看出,HashSet 的实现其实非常简单,它只是封装了一个 HashMap 对象来存储所有的集合元素,所有放入 HashSet 中的集合元素实际上由 HashMap 的 key 来保存,而 HashMap 的 value 则存储了一个 PRESENT,它是一个静态的 Object 对象。

HashSet 的绝大部分方法都是通过调用 HashMap 的方法来实现的,因此 HashSet 和 HashMap 两个集合在实现本质上是相同的。 
掌握上面理论知识之后,接下来看一个示例程序,测试一下自己是否真正掌握了 HashMap 和 HashSet 集合的功能。

 class Name
{
private String first;
private String last; public Name(String first, String last)
{
this.first = first;
this.last = last;
} public boolean equals(Object o)
{
if (this == o)
{
return true;
} if (o.getClass() == Name.class)
{
Name n = (Name)o;
return n.first.equals(first)
&& n.last.equals(last);
}
return false;
}
} public class HashSetTest
{
public static void main(String[] args)
{
Set<Name> s = new HashSet<Name>();
s.add(new Name("abc", "123"));
System.out.println(
s.contains(new Name("abc", "123")));
}
}

上面程序中向 HashSet 里添加了一个 new Name("abc", "123") 对象之后,立即通过程序判断该 HashSet 是否包含一个 new Name("abc", "123") 对象。粗看上去,很容易以为该程序会输出 true。

实际运行上面程序将看到程序输出 false,这是因为 HashSet 判断两个对象相等的标准除了要求通过 equals() 方法比较返回 true 之外,还要求两个对象的 hashCode() 返回值相等。而上面程序没有重写 Name 类的 hashCode() 方法,两个 Name 对象的 hashCode() 返回值并不相同,因此 HashSet 会把它们当成 2 个对象处理,因此程序返回 false。

由此可见,当我们试图把某个类的对象当成 HashMap 的 key,或试图将这个类的对象放入 HashSet 中保存时,重写该类的 equals(Object obj) 方法和 hashCode() 方法很重要,而且这两个方法的返回值必须保持一致:当该类的两个的 hashCode() 返回值相同时,它们通过 equals() 方法比较也应该返回 true。通常来说,所有参与计算 hashCode() 返回值的关键属性,都应该用于作为 equals() 比较的标准。 
如下程序就正确重写了 Name 类的 hashCode() 和 equals() 方法,程序如下:

class Name
{
private String first;
private String last;
public Name(String first, String last)
{
this.first = first;
this.last = last;
}
// 根据 first 判断两个 Name 是否相等
public boolean equals(Object o)
{
if (this == o)
{
return true;
}
if (o.getClass() == Name.class)
{
Name n = (Name)o;
return n.first.equals(first);
}
return false;
} // 根据 first 计算 Name 对象的 hashCode() 返回值
public int hashCode()
{
return first.hashCode();
} public String toString()
{
return "Name[first=" + first + ", last=" + last + "]";
}
} public class HashSetTest2
{
public static void main(String[] args)
{
HashSet<Name> set = new HashSet<Name>();
set.add(new Name("abc" , "123"));
set.add(new Name("abc" , "456"));
System.out.println(set);
}
}

上面程序中提供了一个 Name 类,该 Name 类重写了 equals() 和 toString() 两个方法,这两个方法都是根据 Name 类的 first 实例变量来判断的,当两个 Name 对象的 first 实例变量相等时,这两个 Name 对象的 hashCode() 返回值也相同,通过 equals() 比较也会返回 true。

程序主方法先将第一个 Name 对象添加到 HashSet 中,该 Name 对象的 first 实例变量值为"abc",接着程序再次试图将一个 first 为"abc"的 Name 对象添加到 HashSet 中,很明显,此时没法将新的 Name 对象添加到该 HashSet 中,因为此处试图添加的 Name 对象的 first 也是" abc",HashSet 会判断此处新增的 Name 对象与原有的 Name 对象相同,因此无法添加进入,程序在①号代码处输出 set 集合时将看到该集合里只包含一个 Name 对象,就是第一个、last 为"123"的 Name 对象。

Java-HashSet的更多相关文章

  1. Java HashSet和LinkedHashSet的用法

    Java HashSet和LinkedHashSet的用法 类HashSet和LinkedHashSet都是接口Set的实现,两者都不能保存重复的数据.主要区别是HashSet不保证集合中元素的顺序, ...

  2. Java HashSet和HashMap源码剖析

    转自: Java HashSet和HashMap源码剖析 总体介绍 之所以把HashSet和HashMap放在一起讲解,是因为二者在Java里有着相同的实现,前者仅仅是对后者做了一层包装,也就是说Ha ...

  3. Java HashSet对txt文本内容去重(统计小说用过的字或字数)

    Java HashSet对txt文本内容去重(统计小说用过的字或字数) 基本思路: 1.字节流读需要去重的txt文本.(展示demo为当前workspace下名为utf-8.txt的文本) 2.对读取 ...

  4. Java HashSet和TreeSet【笔记】

    Java HashSet和TreeSet[笔记] PS:HashSet.TreeSet 两个类是在 Map 的基础上组装起来的类 HashSet 类注释 1.底层实现基于 HashMap,所以迭代时不 ...

  5. (转)JAVA HashSet 去除重复值原理

    Java中的set是一个不包含重复元素的集合,确切地说,是不包含e1.equals(e2)的元素对.Set中允许添加null.Set不能保证集合里元素的顺序. 在往set中添加元素时,如果指定元素不存 ...

  6. Java - HashSet源码解析

    java提高篇(二四)-----HashSet 一.定义 public class HashSet<E> extends AbstractSet<E> implements S ...

  7. 一个有意思的 Java HashSet 问题

    昨天,在百度的 java吧 看到有人问关于 HashSet 的问题.下面是他贴出的代码: import java.util.HashSet; public class JavaTest { publi ...

  8. Java——HashSet和TreeSet的区别

    HashSetHashSet有以下特点 不能保证元素的排列顺序,顺序有可能发生变化 不是同步的 集合元素可以是null,但只能放入一个null当向HashSet集合中存入一个元素时,HashSe ...

  9. True or False? and WHY??? Java HashSet Contains

    import java.util.HashSet; public class MyClass { public String s; public MyClass(String s) { this.s ...

  10. java——HashSet类中的常见方法

    package com.xt.set; import java.util.HashSet; import java.util.Iterator; import java.util.Set; publi ...

随机推荐

  1. 【C#】2.算法温故而知新 - 冒泡排序

    冒泡排序可以很好的解决前面提到的简单桶排序的2个问题,冒泡排序的基本思想是:每次比较两个相邻的元素,如果它们的顺序错误就把它们交换过来. 该算法的核心部分是双重嵌套循环,其时间复杂度是O(N²). 缺 ...

  2. Remote Displayer for Android

    应用截图: 作者:sunrain_hjb      QQ:2157825357                 Email:sunrain_hjb@aliyun.com 版本:1.0.188     ...

  3. 斯坦福大学 iOS 7应用开发 ppt

    上网的找了很久都不全,最后发现原来网易那个视频下面就有完整的PPT..

  4. http协议详解<一>

    原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://7826443.blog.51cto.com/7816443/1729227 写在 ...

  5. TabControl的SelectionChanged事件

    DataGrid作为TabControl控件的TabItem的content元素. 当操作DataGrid的不同cell时,会引发了TabControl的SelectionChanged事件的问题. ...

  6. OAF TABLE中添加序号列

    在实际的OAF页面TABLE的使用中,会有很多时候需要在前台页面中显示序号,虽然在sql中可以使用rownum来获得序号,但是rounum的优先级比order by 高,所以在语句中order by ...

  7. WCF4.0 –- RESTful WCF Services (1) (入门)

    WCF 很好的支持了 REST 的开发, 而 RESTful 的服务通常是架构层面上的考虑. 因为它天生就具有很好的跨平台跨语言的集成能力,几乎所有的语言和网络平台都支持 HTTP 请求,无需去实现复 ...

  8. c#中的var优缺点和适用场景

    var是c# 3.0新加的特性,叫做隐式类型局部变量,大家都知道c#其实是一种强类型的语言,为什么会引入匿名类型呢? 我猜测是因为linq的原因吧,因为感觉var在linq中被大量使用.下面说下var ...

  9. 城市区号SQL

    今天写代码的时候需要用到全国城市区号,网上找了好久没有现成的SQL,于是自己录数据写了一个,和大家共享! 目前还只有300个城市的区号 文件下载地址放在最后! GO FROM sysobjects W ...

  10. 安装包制作工具 SetupFactory使用2 API清单

    2014-11-19 SetupFactory中可以通过其API控制很复杂的业务需求. 下图中展示了其内置的API种类与具体分类函数.   序号 API名称 API说明 1 Application.E ...