java中HashSet详解

HashSet 的实现

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

Java代码  

  1. public class HashSet<E>
  2. extends AbstractSet<E>
  3. implements Set<E>, Cloneable, java.io.Serializable
  4. {
  5. // 使用 HashMap 的 key 保存 HashSet 中所有元素
  6. private transient HashMap<E,Object> map;
  7. // 定义一个虚拟的 Object 对象作为 HashMap 的 value
  8. private static final Object PRESENT = new Object();
  9. ...
  10. // 初始化 HashSet,底层会初始化一个 HashMap
  11. public HashSet()
  12. {
  13. map = new HashMap<E,Object>();
  14. }
  15. // 以指定的 initialCapacity、loadFactor 创建 HashSet
  16. // 其实就是以相应的参数创建 HashMap
  17. public HashSet(int initialCapacity, float loadFactor)
  18. {
  19. map = new HashMap<E,Object>(initialCapacity, loadFactor);
  20. }
  21. public HashSet(int initialCapacity)
  22. {
  23. map = new HashMap<E,Object>(initialCapacity);
  24. }
  25. HashSet(int initialCapacity, float loadFactor, boolean dummy)
  26. {
  27. map = new LinkedHashMap<E,Object>(initialCapacity
  28. , loadFactor);
  29. }
  30. // 调用 map 的 keySet 来返回所有的 key
  31. public Iterator<E> iterator()
  32. {
  33. return map.keySet().iterator();
  34. }
  35. // 调用 HashMap 的 size() 方法返回 Entry 的数量,就得到该 Set 里元素的个数
  36. public int size()
  37. {
  38. return map.size();
  39. }
  40. // 调用 HashMap 的 isEmpty() 判断该 HashSet 是否为空,
  41. // 当 HashMap 为空时,对应的 HashSet 也为空
  42. public boolean isEmpty()
  43. {
  44. return map.isEmpty();
  45. }
  46. // 调用 HashMap 的 containsKey 判断是否包含指定 key
  47. //HashSet 的所有元素就是通过 HashMap 的 key 来保存的
  48. public boolean contains(Object o)
  49. {
  50. return map.containsKey(o);
  51. }
  52. // 将指定元素放入 HashSet 中,也就是将该元素作为 key 放入 HashMap
  53. public boolean add(E e)
  54. {
  55. return map.put(e, PRESENT) == null;
  56. }
  57. // 调用 HashMap 的 remove 方法删除指定 Entry,也就删除了 HashSet 中对应的元素
  58. public boolean remove(Object o)
  59. {
  60. return map.remove(o)==PRESENT;
  61. }
  62. // 调用 Map 的 clear 方法清空所有 Entry,也就清空了 HashSet 中所有元素
  63. public void clear()
  64. {
  65. map.clear();
  66. }
  67. ...
  68. }

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

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

Java代码  

  1. class Name
  2. {
  3. private String first;
  4. private String last;
  5. public Name(String first, String last)
  6. {
  7. this.first = first;
  8. this.last = last;
  9. }
  10. public boolean equals(Object o)
  11. {
  12. if (this == o)
  13. {
  14. return true;
  15. }
  16. if (o.getClass() == Name.class)
  17. {
  18. Name n = (Name)o;
  19. return n.first.equals(first)
  20. && n.last.equals(last);
  21. }
  22. return false;
  23. }
  24. }
  25. public class HashSetTest
  26. {
  27. public static void main(String[] args)
  28. {
  29. Set<Name> s = new HashSet<Name>();
  30. s.add(new Name("abc", "123"));
  31. System.out.println(
  32. s.contains(new Name("abc", "123")));
  33. }
  34. }

上面程序中向 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() 方法,程序如下:

Java代码  

  1. class Name
  2. {
  3. private String first;
  4. private String last;
  5. public Name(String first, String last)
  6. {
  7. this.first = first;
  8. this.last = last;
  9. }
  10. // 根据 first 判断两个 Name 是否相等
  11. public boolean equals(Object o)
  12. {
  13. if (this == o)
  14. {
  15. return true;
  16. }
  17. if (o.getClass() == Name.class)
  18. {
  19. Name n = (Name)o;
  20. return n.first.equals(first);
  21. }
  22. return false;
  23. }
  24. // 根据 first 计算 Name 对象的 hashCode() 返回值
  25. public int hashCode()
  26. {
  27. return first.hashCode();
  28. }
  29. public String toString()
  30. {
  31. return "Name[first=" + first + ", last=" + last + "]";
  32. }
  33. }
  34. public class HashSetTest2
  35. {
  36. public static void main(String[] args)
  37. {
  38. HashSet<Name> set = new HashSet<Name>();
  39. set.add(new Name("abc" , "123"));
  40. set.add(new Name("abc" , "456"));
  41. System.out.println(set);
  42. }
  43. }

上面程序中提供了一个 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 对象。

时间: 2024-10-02 04:43:12

java中HashSet详解的相关文章

java中HashSet详解(转)

HashSet 的实现 对于 HashSet 而言,它是基于 HashMap 实现的,HashSet 底层采用 HashMap 来保存所有元素,因此 HashSet 的实现比较简单,查看 HashSet 的源代码,可以看到如下代码: Java代码   public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable { // 使用 HashM

【转】 java中HashMap详解

原文网址:http://blog.csdn.net/caihaijiang/article/details/6280251 java中HashMap详解 HashMap 和 HashSet 是 Java Collection Framework 的两个重要成员,其中 HashMap 是 Map 接口的常用实现类,HashSet 是 Set 接口的常用实现类.虽然 HashMap 和 HashSet 实现的接口规范不同,但它们底层的 Hash 存储机制完全一样,甚至 HashSet 本身就采用 H

java中运算符详解

前言 运算符用于执行程序代码运算,会针对一个以上操作数项目来进行运算.JAVA中常见的运算符有很多种,大致分为以下几种,常见的几种运算符如下图: 算术运算符 加.减.乘.除.求余.例++.--.%./.  赋值运算符 为变量或常量起到赋值作用的.例如=.+=.*= 关系运算符 判断数据大小的,结果为一个布尔值.例如>.>=.!=.== 逻辑运算符 进行逻辑运算,运算的成员为布尔值,结果也为布尔值.例如&&.||.!. 条件运算符 也称三目运算符,表达式为(a<b)?a:b

JAVA中priorityqueue详解

Java中PriorityQueue通过二叉小顶堆实现,可以用一棵完全二叉树表示.本文从Queue接口函数出发,结合生动的图解,深入浅出地分析PriorityQueue每个操作的具体过程和时间复杂度,将让读者建立对PriorityQueue建立清晰而深入的认识. 总体介绍 前面以Java?ArrayDeque_为例讲解了_Stack_和_Queue,其实还有一种特殊的队列叫做_PriorityQueue_,即优先队列.优先队列的作用是能保证每次取出的元素都是队列中权值最小的(Java的优先队列每

java中HashMap详解

原文:http://alex09.iteye.com/blog/539545 HashMap 和 HashSet 是 Java Collection Framework 的两个重要成员,其中 HashMap 是 Map 接口的常用实现类,HashSet 是 Set 接口的常用实现类.虽然 HashMap 和 HashSet 实现的接口规范不同,但它们底层的 Hash 存储机制完全一样,甚至 HashSet 本身就采用 HashMap 来实现的. 通过 HashMap.HashSet 的源代码分析

java中HashMap详解(转)

HashMap 和 HashSet 是 Java Collection Framework 的两个重要成员,其中 HashMap 是 Map 接口的常用实现类,HashSet 是 Set 接口的常用实现类.虽然 HashMap 和 HashSet 实现的接口规范不同,但它们底层的 Hash 存储机制完全一样,甚至 HashSet 本身就采用 HashMap 来实现的. 通过 HashMap.HashSet 的源代码分析其 Hash 存储机制 实际上,HashSet 和 HashMap 之间有很多

Java中MVC详解以及优缺点总结

 概念:  MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务数据.逻辑.界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑.MVC被独特的发展起来用于映射传统的输入.处理和输出功能在一个逻辑的图形化用户界面的结构中. 最典型的MVC就是Jsp + Servlet + JavaBean的模式 MVC开始是存在于桌面程

Java中Volatile详解

当前的Java内存模型下,线程可以把变量保存在本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写.这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成数据的不一致. 要解决这个问题,只需要像在本程序中的这样,把该变量声明为volatile(不稳定的)即可,这就指示JVM,这个变量是不稳定的,每次使用它都到主存中进行读取.一般说来,多任务环境下各任务间共享的标志都应该加volatile修饰. Volatile修饰的成员变量在每次被线程访问

java中static详解

static表示“全局”或者“静态”的意思,用来修饰成员变量和成员方法,也可以形成静态static代码块,但是Java语言中没有全局变量的概念. 被static修饰的成员变量和成员方法独立于该类的任何对象.也就是说,它不依赖类特定的实例,被类的所有实例共享. 只要这个类被加载,Java虚拟机就能根据类名在运行时数据区的方法区内定找到他们.因此,static对象可以在它的任何对象创建之前访问,无需引用任何对象. 用public修饰的static成员变量和成员方法本质是全局变量和全局方法,当声明它类