`

HashMap HashTable HashSet区别剖析

 
阅读更多
转载:http://blog.csdn.net/zwjlpeng/article/details/9746425

HashMap、HashSet、HashTable之间的区别是Java程序员的一个常见面试题目,在此仅以此博客记录,并深入源代码进行分析:

在分析之前,先将其区别列于下面

1:HashSet底层采用的是HashMap进行实现的,但是没有key-value,只有HashMap的key set的视图,HashSet不容许重复的对象

2:Hashtable是基于Dictionary类的,而HashMap是基于Map接口的一个实现

3:Hashtable里默认的方法是同步的,而HashMap则是非同步的,因此Hashtable是多线程安全的

4:HashMap可以将空值作为一个表的条目的key或者value,HashMap中由于键不能重复,因此只有一条记录的Key可以是空值,而value可以有多个为空,但HashTable不允许null值(键与值均不行)

5:内存初始大小不同,HashTable初始大小是11,而HashMap初始大小是16

6:内存扩容时采取的方式也不同,Hashtable采用的是2*old+1,而HashMap是2*old。

7:哈希值的计算方法不同,Hashtable直接使用的是对象的hashCode,而HashMap则是在对象的hashCode的基础上还进行了一些变化

源代码分析:

对于区别1,看下面的源码


[java] view plaincopy
//HashSet类的部份源代码 
public class HashSet<E> 
    extends AbstractSet<E> 
    implements Set<E>, Cloneable, java.io.Serializable 
{   //用于类的序列化,可以不用管它 
    static final long serialVersionUID = -5024744406713321676L; 
    //从这里可以看出HashSet类里面真的是采用HashMap来实现的 
    private transient HashMap<E,Object> map; 
 
    // Dummy value to associate with an Object in the backing Map 
    //这里是生成一个对象,生成这个对象的作用是将每一个键的值均关联于此对象,以满足HashMap的键值对 
    private static final Object PRESENT = new Object(); 
 
    /**
     * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
     * default initial capacity (16) and load factor (0.75).
     */ 
    //这里是一个构造函数,开构生成一个HashMap对象,用来存放数据 
    public HashSet() { 
    map = new HashMap<E,Object>(); 
    } 
从上面的代码中得出的结论是HashSet的确是采用HashMap来实现的,而且每一个键都关键同一个Object类的对象,因此键所关联的值没有意义,真正有意义的是键。而HashMap里的键是不允许重复的,因此1也就很容易明白了。
对于区别2,继续看源代码如下


[java] view plaincopy
//从这里可以看得出Hashtable是继承于Dictionary,实现了Map接口 
public class Hashtable<K,V> 
    extends Dictionary<K,V> 
    implements Map<K,V>, Cloneable, java.io.Serializable { 
[java] view plaincopy
//这里可以看出的是HashMap是继承于AbstractMap类,实现了Map接口 
//因此与Hashtable继承的父类不同 
public class HashMap<K,V> 
    extends AbstractMap<K,V> 
    implements Map<K,V>, Cloneable, Serializable 
区别3,找一个具有针对性的方法看看,这个方法就是put

[java] view plaincopy
//Hashtable里的向集体增加键值对的方法,从这里可以明显看到的是 
//采用了synchronized关键字,这个关键字的作用就是用于线程的同步操作 
//因此下面这个方法对于多线程来说是安全的,但这会影响效率    
public synchronized V put(K key, V value) { 
    // Make sure the value is not null 
    //如果值为空的,则会抛出异常 
    if (value == null) { 
        throw new NullPointerException(); 
    } 
 
    // Makes sure the key is not already in the hashtable. 
    Entry tab[] = table; 
    //获得键值的hashCode,从这里也可以看得出key!=null,否则的话会抛出异常的呦 
    int hash = key.hashCode(); 
    //获取键据所在的哈希表的位置 
    int index = (hash & 0x7FFFFFFF) % tab.length; 
    //从下面这个循环中可以看出的是,内部实现采用了链表,即桶状结构 
    for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) { 
        //如果向Hashtable中增加同一个元素时,则会重新更新元素的值  
        if ((e.hash == hash) && e.key.equals(key)) { 
                V old = e.value; 
                e.value = value; 
                return old; 
        } 
    } 
    //后面的暂时不用管它,大概的意思就是内存的个数少于某个阀值时,进行重新分配内存 
    modCount++; 
    if (count >= threshold) { 
        // Rehash the table if the threshold is exceeded 
        rehash(); 
 
            tab = table; 
            index = (hash & 0x7FFFFFFF) % tab.length; 
    } 
[java] view plaincopy
//HashMap中的实现则相对来说要简单的很多了,如下代码 
//这里的代码中没有synchronize关键字,即可以看出,这个关键函数不是线程安全的 
    public V put(K key, V value) { 
    //对于键是空时,将向Map中放值一个null-value构成的键值对 
    //对值却没有进行判空处理,意味着可以有多个具有键,键所对应的值却为空的元素。 
        if (key == null) 
            return putForNullKey(value); 
    //算出键所在的哈希表的位置 
        int hash = hash(key.hashCode()); 
        int i = indexFor(hash, table.length); 
    //同样从这里可以看得出来的是采用的是链表结构,采用的是桶状 
        for (Entry<K,V> e = table[i]; e != null; e = e.next) { 
            Object k; 
            //对于向集体中增加具有相同键的情况时,这里可以看出,并不增加进去,而是进行更新操作 
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { 
                V oldValue = e.value; 
                e.value = value; 
                e.recordAccess(this); 
                return oldValue; 
            } 
        } 
        //开始增加元素 
        modCount++; 
        addEntry(hash, key, value, i); 
        return null; 
    } 
区别4在上面的代码中,已经分析了,可以再细看一下
区别5内存初化大小不同,看看两者的源代码:


[java] view plaincopy
public Hashtable() { 
   //从这里可以看出,默认的初始化大小11,这里的11并不是11个字节,而是11个Entry,这个Entry是 
   //实现链表的关键结构 
   //这里的0.75代表的是装载因子 
this(11, 0.75f); 

[java] view plaincopy
//这里均是一些定义 
public HashMap() { 
//这个默认的装载因子也是0.75 
     this.loadFactor = DEFAULT_LOAD_FACTOR; 
//默认的痤为0.75*16 
     threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR); 
//这里开始是默认的初始化大小,这里大小是16 
     table = new Entry[DEFAULT_INITIAL_CAPACITY]; 
     init(); 

从上面的代码中,可以看出的是两者的默认大小是不同的,一个是11,一个是16
区别6内存的扩容方式,看一看源代码也是很清楚的,其实区别是不大的,看到网上一哥们写的,说两者有区别,其实真正深入源码,区别真不大,一个是2*oldCapacity+1, 一个是2*oldCapacity,你说大吗:)


[java] view plaincopy
//Hashtable中调整内存的函数,这个函数没有synchronize关键字,但是protected呦 
protected void rehash() { 
    //获取原来的表大小 
    int oldCapacity = table.length; 
    Entry[] oldMap = table; 
  //设置新的大小为2*oldCapacity+1 
    int newCapacity = oldCapacity * 2 + 1; 
    //开设空间 
    Entry[] newMap = new Entry[newCapacity]; 
  //以下就不用管了。。。 
    modCount++; 
    threshold = (int)(newCapacity * loadFactor); 
    table = newMap; 
 
    for (int i = oldCapacity ; i-- > 0 ;) { 
        for (Entry<K,V> old = oldMap[i] ; old != null ; ) { 
        Entry<K,V> e = old; 
        old = old.next; 
 
        int index = (e.hash & 0x7FFFFFFF) % newCapacity; 
        e.next = newMap[index]; 
        newMap[index] = e; 
        } 
    } 
    } 
[java] view plaincopy
//HashMap中要简单的多了,看看就知道了 
void addEntry(int hash, K key, V value, int bucketIndex) { 
Entry<K,V> e = table[bucketIndex]; 
       table[bucketIndex] = new Entry<K,V>(hash, key, value, e); 
       //如果超过了阀值 
       if (size++ >= threshold) 
       //就将大小设置为原来的2倍 
           resize(2 * table.length); 
   } 
是吧,没什么区别吧
对于区别7的哈希值计算方法的不同,源码面前,同样是了无秘密


[java] view plaincopy
//Hashtable中可以看出的是直接采用关键字的hashcode作为哈希值 
int hash = key.hashCode(); 
//然后进行模运算,求出所在哗然表的位置  
int index = (hash & 0x7FFFFFFF) % tab.length; 
[java] view plaincopy
//HashMap中的实现 
//这两行代码的意思是先计算hashcode,然后再求其在哈希表的相应位置       
int hash = hash(key.hashCode()); 
int i = indexFor(hash, table.length); 
上面的HashMap中可以看出关键在两个函数hash与indexFor
源码如下:


[java] view plaincopy
static int hash(int h) { 
    // This function ensures that hashCodes that differ only by 
    // constant multiples at each bit position have a bounded 
    // number of collisions (approximately 8 at default load factor). 
    //这个我就不多说了,>>>这个是无符号右移运算符,可以理解为无符号整型 
    h ^= (h >>> 20) ^ (h >>> 12); 
    return h ^ (h >>> 7) ^ (h >>> 4); 

[java] view plaincopy
//求位于哈希表中的位置 
static int indexFor(int h, int length) { 
     return h & (length-1); 
}
分享到:
评论

相关推荐

    HashMap与HashTable和HashSet的区别

    本文将重点分析这三种数据结构之间的区别,特别是针对`HashTable`不支持空键值对而`HashMap`支持这一点进行深入探讨。 #### 二、HashTable `HashTable`是基于哈希表实现的一个线程安全的`Map`容器,它不允许`key`...

    Java集合专题总结:HashMap 和 HashTable 源码学习和面试总结

    Java集合专题总结:HashMap和HashTable源码...本文总结了HashMap和HashTable的源码学习和面试总结,涵盖了它们的存储结构、构造方法、get和put方法的源码分析、Hash表的特点和缺点、HashTable和HashMap的区别等内容。

    java中HashMap,LinkedHashMap,TreeMap,HashTable的区别

    本文将详细分析四种常用的`Map`实现类:`HashMap`, `LinkedHashMap`, `TreeMap`以及`HashTable`之间的区别。 #### 1. HashMap `HashMap`是一种基于哈希表实现的`Map`接口,提供了一个非同步的、允许使用`null`键和...

    对java基础集合部分(List、HashMap、HashSet、ArrayList等)底层源码的分析与总结

    HashMap不是线程安全的,如果需要线程安全的Map,可以使用Hashtable。 LinkedHashMap与HashMap类似,但保持了插入顺序或访问顺序。TreeMap使用红黑树,保证了键的排序。 总的来说,理解这些集合的底层实现对于优化...

    hashmap面试题_hashmap_

    4. HashMap与Hashtable的区别? 答:HashMap非线程安全,而Hashtable是线程安全的;HashMap允许null键值,Hashtable不允;HashMap迭代器在修改时不会抛出ConcurrentModificationException,而Hashtable会。 5. ...

    深入解读大厂java面试必考点之HashMap全套学习资料

    - HashMap和Hashtable的区别? - HashMap与HashSet的关系? - 如何解决哈希冲突? - 如何自定义键的哈希码生成方式? - 如何避免和处理HashMap中的循环链表? 通过深入学习和理解这些知识点,你将能够在面试中自信...

    Java集合框架源码剖析:HashSet 和 HashMap

     之所以把HashSet和HashMap放在一起讲解,是因为二者在Java里有着相同的实现,前者仅仅是对后者做了一层包装,也是说HashSet里面有一个HashMap(适配器模式)。因此本文将重点分析HashMap。  HashMap实现了Map...

    阿里巴巴电话面试试题.doc

    本资源摘要信息涵盖了 Java 集合框架的基本概念和实现细节,着重介绍了 Java 集合框架中的 HashMap、Hashtable、ArrayList、LinkedList 等常用类,并对比了 Hashtable 和 HashMap 的区别,详细分析了两者在源代码...

    java开始面试的第45天.doc

    Java面试的第45天,我们来探讨一些关键的编程概念和API,主要涉及Java集合框架中的List、Map和Set接口,以及HashMap和Hashtable的区别。此外,还会分析一段代码,理解其中类和变量的用途。 1. **List、Map、Set接口...

    通过分析 JDK 源代码研究 Hash 存储机制

    JDK(Java Development Kit)是Java编程语言的标准开发工具集,其源代码中包含了多种哈希存储实现,例如HashMap、HashSet等。本文将深入探讨JDK源代码中的哈希存储机制,以帮助我们理解这些数据结构的工作原理。 ...

    21-面试宝典(进一般互联网公司必看).docx

    * HashMap 和 Hashtable 的区别 * HashSet 和 HashMap 的区别 * HashMap 和 ConcurrentHashMap 的区别 * HashMap 的工作原理及代码实现 * ConcurrentHashMap 的工作原理及代码实现 * 线程创建的方式及实现 * sleep()...

    Java开发工程师试卷六(~).doc

    Java开发工程师试卷六包含了多个Java编程相关的问题,涵盖了反射、多线程、ExecutorService接口、JavaScript语法、数据类型、线程安全、Bean作用域、Map容器、同步关键字以及HashMap和HashTable的区别等多个知识点。...

    基础知识.pdf

    探讨了集合框架中List、Set、Map的使用与区别,包括ArrayList与LinkedList、HashMap与Hashtable、HashSet与HashMap、HashMap与ConcurrentHashMap等的区别和原理。详细讲解了HTTP请求的GET与POST方式的区别,session...

    Java常见面试题解答.pdf

    6. **HashMap和HashSet的区别**:HashSet实际上是基于HashMap实现的,HashSet的元素作为HashMap的键,值是一个静态的虚拟对象。 ### 进程和线程 1. **线程和进程的概念**:进程是程序的一次执行,线程是进程内的一...

    java面试精选必备题集

    * HashMap和Hashtable的区别 + HashMap:基于哈希表,线程不安全 + Hashtable:基于哈希表,线程安全 * HashSet和HashMap的区别 + HashSet:基于哈希表,集合操作 + HashMap:基于哈希表,键值对操作 * HashMap...

    经典面试题,不看后悔!有相应的参考答案

    #### 一、HashMap、HashSet与Hashtable的区别 1. **安全性**:`Hashtable`是线程安全的,而`HashSet`和`HashMap`不是线程安全的。`Hashtable`中的所有方法都是`synchronized`修饰的,确保了多线程环境下的安全性。 ...

    Java 集合学习指南 - v1.1.pdf

    最后,针对集合的学习指南还涉及了对集合类之间的对比分析,如HashSet与HashMap的比较。它们在实现上有着类似的机制,但HashSet专注于存储不重复的元素,而HashMap专注于存储键值对映射。 本学习指南适合已经具备...

    Java后端技术面试汇总-副本.pdf

    不同集合类的性能特点,如ArrayList与LinkedList、HashMap与Hashtable、HashSet与HashMap、HashMap与ConcurrentHashMap的区别;以及并发环境下HashMap的死循环问题和HashDOS攻击问题的讨论。此外,还提供了对...

    Java interview

    - 包含List(如LinkedList、ArrayList和Vector)、Set(如HashSet、TreeSet)和Map(如Hashtable、HashMap、WeakHashMap)等接口和实现类。自定义数据结构通常涉及实现这些接口。 9. **异常处理**: - Java使用...

    Java面试总结.pdf

    16. HashMap和HashTable的区别:HashMap是线程不安全的,允许key或value为null;HashTable是线程安全的,是早期的Java集合实现,且不允许key或value为null。 17. HashMap的实现原理:HashMap基于哈希表实现,使用...

Global site tag (gtag.js) - Google Analytics