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. 【iOS】block的使用

    Block 是iOS在4.0之后新增的程式语法,一般用于回调方法,功能上和delegate类似.本文将讲解block的几种常见的使用方法,当然,block中最值得注意的还是它的内存管理,我将在< ...

  2. 内外分离接口依赖及UIScrollView知识点

    1:Class Extension 还能巧妙的解决一个接口暴露问题 有些属性或者方法对外可以提供,有些只针对内部的类进行调用: // Sark.framework/Sark.h @interface ...

  3. 坑!坑!坑!防不胜防的unsigned int的运算

    我很早之前就知道,unsigned int与int运算的时候,int会被转化为unsigned int来进行运算.一直觉得定这条规则的人是极度反人类的,虽说unsigned int可以表示更大的正值, ...

  4. jenkins打包成功,部署失败

    环境一直正常,更新了tomcat版本后自动部署报错 ERROR: Publisher hudson.plugins.deploy.DeployPublisher aborted due to exce ...

  5. SQL Server(三)——增、删、改、查

    一.数据库操作 create database 数据库名称 ——创建drop database 数据库名称 ——删除use 数据库名称 ——使用go 两条SQL语句之间分隔 二.表的操作 create ...

  6. Proactor VS Reactor

    proactor vs reactor 先发几本proactor 与 reactor 相关的电子书: http://files.cnblogs.com/files/f1194361820/reacto ...

  7. 烂泥:rsync配置文件详解

    本文由秀依林枫提供友情赞助,首发于烂泥行天下. 对于rsync服务器来说,最重要和复杂的就是它的配置了.rsync服务器的配置文件为/etc/rsyncd.conf,其控制认证.访问.日志记录等等. ...

  8. nginx日志切割脚本

    #!/bin/bash ip=`ifconfig eth0 | grep "inet addr" | cut -f 2 -d ":" | cut -f 1 -d ...

  9. tools:context=".MainActivity的作用(转)

    android:layout_width="match_parent" android:layout_height="match_parent" android ...

  10. 虚拟机centos6.5 --ssh免密码登录

    说明:此处是以hadoop虚拟机3台机器为背景进行设置,即master.slave01.slave02 1.在master上,生成公钥.私钥 用hadoop用户登录系统,进入个人主目录,即/home/ ...