HashSet与HashMap的区别

来源:互联网 发布:欧洲专利局数据库 编辑:程序博客网 时间:2024/05/17 22:24

在网上找了好久终于找到了详尽的解释,记录下来备忘。。

HashSet 的实现

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

  1. publicclass HashSet<E>
  2. extends AbstractSet<E>
  3. implements Set<E>, Cloneable, java.io.Serializable
  4. {
  5. // 使用 HashMap 的 key 保存 HashSet 中所有元素
  6. privatetransient HashMap<E,Object> map;
  7. // 定义一个虚拟的 Object 对象作为 HashMap 的 value
  8. privatestatic 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. publicint size()
  37. {
  38. return map.size();
  39. }
  40. // 调用 HashMap 的 isEmpty() 判断该 HashSet 是否为空,
  41. // 当 HashMap 为空时,对应的 HashSet 也为空
  42. publicboolean isEmpty()
  43. {
  44. return map.isEmpty();
  45. }
  46. // 调用 HashMap 的 containsKey 判断是否包含指定 key
  47. //HashSet 的所有元素就是通过 HashMap 的 key 来保存的
  48. publicboolean contains(Object o)
  49. {
  50. return map.containsKey(o);
  51. }
  52. // 将指定元素放入 HashSet 中,也就是将该元素作为 key 放入 HashMap
  53. publicboolean add(E e)
  54. {
  55. return map.put(e, PRESENT) ==null;
  56. }
  57. // 调用 HashMap 的 remove 方法删除指定 Entry,也就删除了 HashSet 中对应的元素
  58. publicboolean remove(Object o)
  59. {
  60. return map.remove(o)==PRESENT;
  61. }
  62. // 调用 Map 的 clear 方法清空所有 Entry,也就清空了 HashSet 中所有元素
  63. publicvoid clear()
  64. {
  65. map.clear();
  66. }
  67. ...
  68. }

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

  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. publicboolean equals(Object o)
  11. {
  12. if (this == o)
  13. {
  14. returntrue;
  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. returnfalse;
  23. }
  24. }
  25. publicclass HashSetTest
  26. {
  27. publicstatic 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. }

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


正确的使用HashMap
1:不要在并发场景中使用HashMap
HashMap是线程不安全的,如果被多个线程共享的操作,将会引发不可预知的问题,据sun的说法,在扩容时,会引起链表的闭环,在get元素时,就会无限循环,后果是cpu100%。
看看get方法的红色部分

Java代码 HashSet与HashMap的区别 - 华农09计机4班 - Class 4s zone
  1. public V get(Object key) {
  2. if (key == null)
  3. return getForNullKey();
  4. int hash = hash(key.hashCode());
  5. for (Entry<K,V> e = table[indexFor(hash, table.length)];
  6. e != null;
  7. e = e.next) {
  8. Object k;
  9. if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
  10. return e.value;
  11. }
  12. return null;
  13. }
public V get(Object key) {      if (key == null)          return getForNullKey();          int hash = hash(key.hashCode());          for (Entry<K,V> e = table[indexFor(hash, table.length)];               e != null;               e = e.next) {              Object k;              if (e.hash == hash && ((k = e.key) == key || key.equals(k)))                  return e.value;          }          return null;      }


2:如果数据大小是固定的,那么最好给HashMap设定一个合理的容量值
根据上面的分析,HashMap的初始默认容量是16,默认加载因子是0.75,也就是说,如果采用HashMap的默认构造函数,当增加数据时,数据实际容量超过10*0.75=12时,HashMap就扩容,扩容带来一系列的运算,新建一个是原来容量2倍的数组,对原有元素全部重新哈希,如果你的数据有几千几万个,而用默认的HashMap构造函数,那结果是非常悲剧的,因为HashMap不断扩容,不断哈希,在使用HashMap的场景里,不会是多个线程共享一个HashMap,除非对HashMap包装并同步,由此产生的内存开销和cpu开销在某些情况下可能是致命的。

 

0 0
原创粉丝点击