当前位置:   article > 正文

java集合HashMap、HashTable、HashSet详解_java hashmap hashset hashtable

java hashmap hashset hashtable

一、Set和Map关系

Set代表集合元素无序,集合元素不可重复的集合,Map代表一种由多个key-value组成的集合,map集合是set集合的扩展只是名称不同,对应如下

二、HashMap的工作原理

        HashMap基于hashing原理,通过put()和get()方法储存和获取对象。

        put()方法: 它调用键对象的hashCode()方法来计算hashcode值,系统根据hashcode值决定该元素在bucket位置。如果两个对象key的hashcode返回值相同,那他们的存储位置相同,如果这两个Entry的key通过equals比较返回true,新添加Entry的value将覆盖集合中原有Entry的value,但key不会覆盖;如果这两个Entry的key通过equals比较返回false,新添加的Entry将与集合中原有Entry形成Entry链,而且新添加Entry位于Entry链的头部。put源码如下: 

复制代码

  1. public V put(K paramK, V paramV) {
  2. //如果key为空,调用putForNullKey方法
  3. if (paramK == null)
  4. return putForNullKey(paramV);
  5. //根据key的keyCode计算Hash值
  6. int i = hash(paramK.hashCode());
  7. //搜索指定hash值的对应在table中的索引
  8. int j = indexFor(i, this.table.length);
  9. //如果j索引处的Entry不为空,通过循环遍历localEntry元素的下一个元素
  10. for (Entry localEntry = this.table[j]; localEntry != null; localEntry = localEntry.next) {
  11. Object localObject1;
  12. //找到指定key与放入key相等(hash值相同,通过equals比较返回true)
  13. if ((localEntry.hash == i)
  14. && ((((localObject1 = localEntry.key) == paramK) || (paramK
  15. .equals(localObject1))))) {
  16. Object localObject2 = localEntry.value;
  17. localEntry.value = paramV;
  18. localEntry.recordAccess(this);
  19. return localObject2;
  20. }
  21. }
  22. //如果j索引Entry为null,此处没有Entry
  23. this.modCount += 1;
  24. //将key、value添加到i索引处
  25. addEntry(i, paramK, paramV, j);
  26. return null;
  27. }
  28. void addEntry(int paramInt1, K paramK, V paramV, int paramInt2) {
  29. //获取指定bucketIndex索引处Entry
  30. Entry localEntry = this.table[paramInt2];
  31. //将新创建的Entry放入bucketIndex索引处,并让新的Entry指向原来的Entry
  32. this.table[paramInt2] = new Entry(paramInt1, paramK, paramV, localEntry);
  33. //如果map中的key-value数量超过
  34. if (this.size++ >= this.threshold)
  35. //table对象的长度扩充到2倍
  36. resize(2 * this.table.length);
  37. }

复制代码

put方法三种情况,如图:

 

get()方法:当HashMap的每个bucket里存储的Entry只是单个Entry,即没有通过指针产生Entry链时,此时HashMap具有最好的性能。当程序通过key取出对应value时,系统先计算出该key的hashCode()返回值,再根据该hashCode返回值找出该key在table数组中的索引,然后取出该索引处的Entry,最后返回该key对应的value值。get源码如下:

复制代码

  1. public V get(Object paramObject) {
  2. //如果key为空,调用getForNullKey取出对应的value
  3. if (paramObject == null)
  4. return getForNullKey();
  5. //根据key的hashCode值计算hash码
  6. int i = hash(paramObject.hashCode());
  7. //直接取出table数组中指定索引处的值
  8. Entry localEntry = this.table[indexFor(i, this.table.length)];
  9. while (localEntry != null) {
  10. Object localObject;
  11. //如果该Entry的key与被搜索key相同
  12. if ((localEntry.hash == i)
  13. && ((((localObject = localEntry.key) == paramObject) || (paramObject
  14. .equals(localObject)))))
  15. return localEntry.value;
  16. //搜索该Entry链的下一个
  17. localEntry = localEntry.next;
  18. }
  19. return null;
  20. }
  21. 从代码看出,HashMap的每个bucket里只有一个Entry,HashMap可以根据索引快速取出该bucket里的Entry。
  22. 在发生Hash冲突的情况下,单个bucket里存储的不是一个Entry,而是一个Entry链,系统只能按顺序遍历每个Entry,直到找到想搜索的Entry。

复制代码

HashMap有两个参数影响其性能:

1. 初始容量和加载因子。默认初始容量是16,加载因子是0.75。容量是哈希表中桶(Entry数组)的数量,初始容量只是哈希表在创建时的容量。加载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,通过调用rehash 方法将容量翻倍。

2. 加载因子过高虽然减少了空间开销,但同时也增加了查询成本(加载因子是表示Hsah表中元素的填满的程度.若:加载因子越大,填满的元素越多,好处是,空间利用率高了,但:冲突的机会加大了.反之,加载因子越小,填满的元素越少,好处是:冲突的机会减小了,但:空间浪费多了)。在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地降低rehash 操作次数。如果初始容量大于最大条目数除以加载因子(实际上就是最大条目数小于初始容量*加载因子),则不会发生 rehash 操作。

3.HashMap存放的元素越来越多,到达临界值(阀值)threshold时,就要对Entry数组扩容,这是Java集合类框架最大的魅力,HashMap在扩容时,新数组的容量将是原来的2倍,由于容量发生变化,原有的每个元素需要重新计算bucketIndex,再存放到新数组中去,也就是所谓的rehash。HashMap默认初始容量16,加载因子0.75,也就是说最多能放16*0.75=12个元素,当put第13个时,HashMap将发生rehash,rehash的一系列处理比较影响性能,所以当我们需要向HashMap存放较多元素时,最好指定合适的初始容量和加载因子,否则HashMap默认只能存12个元素,将会发生多次rehash操作。

三、HashMap和Hashtable的区别

HashMap和Hashtable都实现了Map接口,主要的区别有:线程安全性,同步(synchronization),以及速度。HashMap几乎可以等价于Hashtable,除了HashMap是非synchronized的,并可以接受null(HashMap可以接受为null的键值(key)和值(value),而Hashtable则不行)。

HashMap是非synchronized,而Hashtable是synchronized,意味着Hashtable是线程安全的,多个线程可以共享一个Hashtable;而多个线程是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。另一个区别是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器。HashMap可以通过下面的语句进行同步:Map m = Collections.synchronizeMap(hashMap);

 

(一)继承的历史不同

  1. public class Hashtable extends Dictionary implements Map
  2. public class HashMap extends AbstractMap implements Map
  • 1
  • 2

    Hashtable是继承自Dictionary类的,而HashMap则是Java 1.2引进的Map接口的一个实现。

(二)安全性不同

    HashMap是非synchronized,而HashTable在默认的情况下是synchronized,这意味着HashTable是线程安全的,多个线程可以共享一个HashTable;而如果没有正确的同步的话,多个线程是不能共享HashMap的。Java 5以后提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。当然,我们可以通过以下方法让HashMap同步:

Map m = Collections.synchronizeMap(hashMap);
  • 1

(三)是否可为空值的异同

    HashMap可以让你将空值作为一个表条目的key或value。HashMap中只有一条记录可以是一个空的key,但任意数量的条目可以是空的value。这就是说,如果在表中没有发现搜索键,或者如果发现了搜索键,但它是一个空的值,那么get()将返回null;而HashTable则不行,key和value都不允许出现null值。

(四)二者的遍历方式的内部实现上不同

    Hashtable、HashMap都使用了 Iterator迭代器,HashMap的迭代器(Iterator)是fail-fast迭代器,而HashTable的enumerator迭代器不是fail-fast的。而由于历史原因,Hashtable还使用了Enumeration的方式 。

(五)哈希值的使用不同

    HashTable直接使用对象的hashCode,而HashMap则需要重新计算hash值。

(六)二者内部实现方式的数组的初始大小和扩容的方式不同

    HashTable中hash数组默认大小是11,增加的方式是 old*2+1;HashMap中hash数组的默认大小是16,而且一定是2的指数。

 

四、HashMap和HashSet的区别

HashSet实现了Set接口,它不允许集合中有重复的值,HashMap实现了Map接口,Map接口对键值对进行映射。

HashSet扩展了HashMap,所以底层还是用到map存储,存储实现同map一致,HashMap储存键值,HashSet存储对象。

那么hashMap的工作原理是什么?

当系统开始初始化HashMap的时候,系统会创建一个长度为capacity的Entry数组。这个数组存储的元素是一个系列元素的索引,也称为“桶”,当一个元素要增加的时候,会计算他的hashcode,然后再数组中寻找他的位置,比如,他的位置有元素占据了,那么会在该元素上,扩展出一条索引链,将数据插入到这个索引链上。

 

 

HashSet和HashMap的区别

*HashMap**HashSet*
HashMap实现了Map接口HashSet实现了Set接口
HashMap储存键值对HashSet仅仅存储对象
使用put()方法将元素放入map中使用add()方法将元素放入set中
HashMap中使用键对象来计算hashcode值HashSet使用成员对象来计算hashcode值,对于两个对象来说hashcode可能相同,所以equals()方法用来判断对象的相等性,如果两个对象不同的话,那么返回false
HashMap比较快,因为是使用唯一的键来获取对象HashSet较HashMap来说比较慢
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/643875
推荐阅读
相关标签
  

闽ICP备14008679号