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,看下面的源码

  1. //HashSet类的部份源代码
  2. public class HashSet<E>
  3. extends AbstractSet<E>
  4. implements Set<E>, Cloneable, java.io.Serializable
  5. {   //用于类的序列化,可以不用管它
  6. static final long serialVersionUID = -5024744406713321676L;
  7. //从这里可以看出HashSet类里面真的是采用HashMap来实现的
  8. private transient HashMap<E,Object> map;
  9. // Dummy value to associate with an Object in the backing Map
  10. //这里是生成一个对象,生成这个对象的作用是将每一个键的值均关联于此对象,以满足HashMap的键值对
  11. private static final Object PRESENT = new Object();
  12. /**
  13. * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
  14. * default initial capacity (16) and load factor (0.75).
  15. */
  16. //这里是一个构造函数,开构生成一个HashMap对象,用来存放数据
  17. public HashSet() {
  18. map = new HashMap<E,Object>();
  19. }

从上面的代码中得出的结论是HashSet的确是采用HashMap来实现的,而且每一个键都关键同一个Object类的对象,因此键所关联的值没有意义,真正有意义的是键。而HashMap里的键是不允许重复的,因此1也就很容易明白了。

对于区别2,继续看源代码如下

  1. //从这里可以看得出Hashtable是继承于Dictionary,实现了Map接口
  2. public class Hashtable<K,V>
  3. extends Dictionary<K,V>
  4. implements Map<K,V>, Cloneable, java.io.Serializable {
  1. //这里可以看出的是HashMap是继承于AbstractMap类,实现了Map接口
  2. //因此与Hashtable继承的父类不同
  3. public class HashMap<K,V>
  4. extends AbstractMap<K,V>
  5. implements Map<K,V>, Cloneable, Serializable

区别3,找一个具有针对性的方法看看,这个方法就是put

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

区别4在上面的代码中,已经分析了,可以再细看一下

区别5内存初化大小不同,看看两者的源代码:

  1. public Hashtable() {
  2. //从这里可以看出,默认的初始化大小11,这里的11并不是11个字节,而是11个Entry,这个Entry是
  3. //实现链表的关键结构
  4. //这里的0.75代表的是装载因子
  5. this(11, 0.75f);
  6. }
  1. //这里均是一些定义
  2. public HashMap() {
  3. //这个默认的装载因子也是0.75
  4. this.loadFactor = DEFAULT_LOAD_FACTOR;
  5. //默认的痤为0.75*16
  6. threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
  7. //这里开始是默认的初始化大小,这里大小是16
  8. table = new Entry[DEFAULT_INITIAL_CAPACITY];
  9. init();
  10. }

从上面的代码中,可以看出的是两者的默认大小是不同的,一个是11,一个是16

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

  1. //Hashtable中调整内存的函数,这个函数没有synchronize关键字,但是protected呦
  2. protected void rehash() {
  3. //获取原来的表大小
  4. int oldCapacity = table.length;
  5. Entry[] oldMap = table;
  6. //设置新的大小为2*oldCapacity+1
  7. int newCapacity = oldCapacity * 2 + 1;
  8. //开设空间
  9. Entry[] newMap = new Entry[newCapacity];
  10. //以下就不用管了。。。
  11. modCount++;
  12. threshold = (int)(newCapacity * loadFactor);
  13. table = newMap;
  14. for (int i = oldCapacity ; i-- > 0 ;) {
  15. for (Entry<K,V> old = oldMap[i] ; old != null ; ) {
  16. Entry<K,V> e = old;
  17. old = old.next;
  18. int index = (e.hash & 0x7FFFFFFF) % newCapacity;
  19. e.next = newMap[index];
  20. newMap[index] = e;
  21. }
  22. }
  23. }
  1. //HashMap中要简单的多了,看看就知道了
  2. void addEntry(int hash, K key, V value, int bucketIndex) {
  3. Entry<K,V> e = table[bucketIndex];
  4. table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
  5. //如果超过了阀值
  6. if (size++ >= threshold)
  7. //就将大小设置为原来的2倍
  8. resize(2 * table.length);
  9. }

是吧,没什么区别吧

对于区别7的哈希值计算方法的不同,源码面前,同样是了无秘密

  1. //Hashtable中可以看出的是直接采用关键字的hashcode作为哈希值
  2. int hash = key.hashCode();
  3. //然后进行模运算,求出所在哗然表的位置
  4. int index = (hash & 0x7FFFFFFF) % tab.length;
  1. //HashMap中的实现
  2. //这两行代码的意思是先计算hashcode,然后再求其在哈希表的相应位置
  3. int hash = hash(key.hashCode());
  4. int i = indexFor(hash, table.length);

上面的HashMap中可以看出关键在两个函数hash与indexFor

源码如下:

  1. static int hash(int h) {
  2. // This function ensures that hashCodes that differ only by
  3. // constant multiples at each bit position have a bounded
  4. // number of collisions (approximately 8 at default load factor).
  5. //这个我就不多说了,>>>这个是无符号右移运算符,可以理解为无符号整型
  6. h ^= (h >>> 20) ^ (h >>> 12);
  7. return h ^ (h >>> 7) ^ (h >>> 4);
  8. }
  1. //求位于哈希表中的位置
  2. static int indexFor(int h, int length) {
  3. return h & (length-1);
  4. }

Java中HashSet,HashMap和HashTable的区别的更多相关文章

  1. java中的hashmap与hashtable的区别

    HashMap和Hashtable的区别 HashMap和Hashtable都实现了Map接口,但决定用哪一个之前先要弄清楚它们之间的分别.主要的区别有:线程安全性,同步(synchronizatio ...

  2. java面试题——HashMap和Hashtable 的区别

    一.HashMap 和Hashtable 的区别 我们先看2个类的定义 public class Hashtable extends Dictionary implements Map, Clonea ...

  3. Java中的HashMap和Hashtable

    代码: import java.util.*; public class test{ public static void main(String[] args) { HashMap hm = new ...

  4. Java 中的 equals,==与 hashCode 的区别与联系

    一. 关系操作符 ==:若操作数的类型是基本数据类型,则该关系操作符判断的是左右两边操作数的值是否相等若操作数的类型是引用数据类型,则该关系操作符判断的是左右两边操作数的内存地址是否相同.也就是说,若 ...

  5. HashMap与HashTable的区别、HashMap与HashSet的关系

    http://blog.csdn.net/wl_ldy/article/details/5941770 HashTable的应用非常广泛,HashMap是新框架中用来代替HashTable的类,也就是 ...

  6. 沉淀再出发:java中的HashMap、ConcurrentHashMap和Hashtable的认识

    沉淀再出发:java中的HashMap.ConcurrentHashMap和Hashtable的认识 一.前言 很多知识在学习或者使用了之后总是会忘记的,但是如果把这些只是背后的原理理解了,并且记忆下 ...

  7. Java 集合系列 11 hashmap 和 hashtable 的区别

    java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...

  8. java集合框架collection(4)HashMap和Hashtable的区别

    HashMap和Hashtable的区别 HashMap和Hashtable都实现了Map接口,但决定用哪一个之前先要弄清楚它们之间的分别.主要的区别有:线程安全性,同步(synchronizatio ...

  9. Java集合详解4:一文读懂HashMap和HashTable的区别以及常见面试题

    <Java集合详解系列>是我在完成夯实Java基础篇的系列博客后准备开始写的新系列. 这些文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查 ...

  10. 一天十道Java面试题----第二天(HashMap和hashTable的区别--------》sleep、wait、join)

    这里是参考B站上的大佬做的面试题笔记.大家也可以去看视频讲解!!! 文章目录 11.HashMap和HashTable的区别及底层实现 12.ConcurrentHashMap原理简述,jdk7和jd ...

随机推荐

  1. 【Android疑难杂症】GridView动态设置Item的宽高导致第一个Item不响应或显示不正常的问题

    前言 这个问题在之前做一个盒子项目时遇到过,最近又遇到了,使用GridView遇到的非常奇葩的问题,这里记录分享一下. 声明 欢迎转载,但请保留文章原始出处:)  博客园:http://www.cnb ...

  2. Android源码分析之SharedPreferences

    在Android的日常开发中,相信大家都用过SharedPreferences来保存用户的某些settings值.Shared Preferences 以键值对的形式存储私有的原生类型数据,这里的私有 ...

  3. 深入理解Objective-C Runtime

    一.简介 主要特点: 在OC语言中,函数的调用是属于动态调用的,编译阶段并不确定要调用的函数,在真正的运行时才会根据函数名查找要调用哪个函数. 而在C语言中,函数的调用是在编译阶段就已经确定要调用哪个 ...

  4. 【转】Android开发之如何保证Service不被杀掉(broadcast+system/app)

    Service简介 1.Service 每个Service必须在manifest中 通过<service>来声明. 可以通过contect.startservice和contect.bin ...

  5. 关于激活Bentley软件详细步骤介绍(再补充一个)

    在安装完ContextCapture软件之后,大家怀着迫不及待的心情双击了运行快捷键.但是很遗憾的是,会产生下面的提示窗口: 也许大家并不在意,就觉得关掉这个窗口不就行了.然而,头疼的问题来了.这个窗 ...

  6. Sandcastle----强大的C#文档生成工具

    最近客户索要产品的二次开发类库文档,由于开发过程中并没有考虑过此类文档,而且项目规范比较,持续时间比较长,经手人比较多,还真是麻烦,如果人工制作文档需要是一个比较大的工程.还好有这个文档生成工具,能够 ...

  7. Java基础知识学习(二)

    Java语法基础 数据类型.类型转换.运算符.逻辑运算符.参考C#,基本一致 输入输出 输出 System.out.print("abc"); System.out.printf( ...

  8. MapReduce实例-基于内容的推荐(一)

    环境: Hadoop1.x,CentOS6.5,三台虚拟机搭建的模拟分布式环境 数据:下载的amazon产品共同采购网络元数据(需FQ下载)http://snap.stanford.edu/data/ ...

  9. 【译】Java中的枚举

    前言 译文链接:http://www.programcreek.com/2014/01/java-enum-examples/ Java中的枚举跟其它普通类很像,在其内部包含了一堆预先定义好的对象集合 ...

  10. MongoDB使用汇总贴

    金天:学习一个新东西,就要持有拥抱的心态,如果固守在自己先前的概念体系,就会有举步维艰的感觉.应用mongodb(NoSQL)开发,首先要打破原先的关系思维.范式思维. 本文作为使用mongodb一路 ...