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

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

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

 //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,继续看源代码如下

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

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

 //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;
}
 //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内存初化大小不同,看看两者的源代码:

  public Hashtable() {
//从这里可以看出,默认的初始化大小11,这里的11并不是11个字节,而是11个Entry,这个Entry是
//实现链表的关键结构
//这里的0.75代表的是装载因子
this(11, 0.75f);
}
 //这里均是一些定义
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,你说大吗:)

 //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;
}
}
}
 //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的哈希值计算方法的不同:

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

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

源码如下:

 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);
}
 //求位于哈希表中的位置
static int indexFor(int h, int length) {
return h & (length-1);

转载链接:http://blog.csdn.net/jianyuerensheng/article/details/51593118

Java数据机构系列:简约人生的博客

六.HashMap HashTable HashSet区别剖析总结的更多相关文章

  1. [置顶] HashMap HashTable HashSet区别剖析

    HashMap.HashSet.HashTable之间的区别是Java程序员的一个常见面试题目,在此仅以此博客记录,并深入源代码进行分析: 在分析之前,先将其区别列于下面 1:HashSet底层采用的 ...

  2. HashMap HashTable HashSet区别剖析

    HashMap.HashSet.HashTable之间的区别是Java程序员的一个常见面试题目,在此仅以此博客记录,并深入源代码进行分析: 在分析之前,先将其区别列于下面 1:HashSet底层采用的 ...

  3. HashMap底层实现原理/HashMap与HashTable区别/HashMap与HashSet区别(转)

    HashMap底层实现原理/HashMap与HashTable区别/HashMap与HashSet区别 文章来源:http://www.cnblogs.com/beatIteWeNerverGiveU ...

  4. HashMap & HashTable的区别

    HashMap & HashTable的区别主要有以下: 1.HashMap是线程不安全的,HashTable是线程安全的.由这点区别可以知道,不考虑线程安全的情况下使用HashMap的效率明 ...

  5. HashMap底层实现原理/HashMap与HashTable区别/HashMap与HashSet区别

    ①HashMap的工作原理 HashMap基于hashing原理,我们通过put()和get()方法储存和获取对象.当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算h ...

  6. (转)HashMap底层实现原理/HashMap与HashTable区别/HashMap与HashSet区别

    ①HashMap的工作原理 HashMap基于hashing原理,我们通过put()和get()方法储存和获取对象.当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算h ...

  7. HashMap底层实现原理以及HashMap与HashTable区别以及HashMap与HashSet区别

    ①HashMap的工作原理 HashMap基于hashing原理,我们通过put()和get()方法储存和获取对象.当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算h ...

  8. HashMap,HashTable,TreeMap区别和用法

    开始学HashTable,HashMap和TreeMap的时候比较晕,觉得作用差不多,但是到实际运用的时候又发现有许多差别的.需要大家注意,在实际开发中以需求而定. java为数据结构中的映射定义了一 ...

  9. HashMap HashTable HashSet

    原文转载自 http://blog.csdn.net/wl_ldy/article/details/5941770 HashMap是新框架中用来代替HashTable的类 也就是说建议使用HashMa ...

随机推荐

  1. 【BZOJ5471】[FJOI2018]邮递员问题(动态规划)

    [BZOJ5471][FJOI2018]邮递员问题(动态规划) 题面 BZOJ 洛谷 给定平面上若干个点,保证这些点在两条平行线上,给定起点终点,求从起点出发,遍历所有点后到达终点的最短路径长度. 题 ...

  2. 【BZOJ5316】[JSOI2018]绝地反击(网络流,计算几何,二分)

    [BZOJ5316][JSOI2018]绝地反击(网络流,计算几何,二分) 题面 BZOJ 洛谷 题解 很明显需要二分一个答案. 那么每个点可以确定的范围就是以当前点为圆心,二分出来的答案为半径画一个 ...

  3. 一点理解之 CmBacktrace: ARM Cortex-M 系列 MCU 错误追踪库

    @2019-02-14 [小记] CmBacktrace: ARM Cortex-M 系列 MCU 错误追踪库,用来将单片机故障状态寄存器值翻译出来输出至终端上以便排错 CmBacktrace: AR ...

  4. [APIO2008]免费道路(生成树)

    新亚(New Asia)王国有 N 个村庄,由 M 条道路连接.其中一些道路是鹅卵石路,而其它道路是水泥路.保持道路免费运行需要一大笔费用,并且看上去 王国不可能保持所有道路免费.为此亟待制定一个新的 ...

  5. js 判断数据是否为空

    js 判断数据是否为空 // var a = ""; // var a = " "; // var a = null; // var a = undefined ...

  6. 论一个蒟蒻的脑子里可以有多少坑(貌似咕了……目前更新保持在noip阶段)

    就是错题整理了,其实也会把一些不该犯的失误整进来. 其实之前一直拖着不想写,直到某次模拟赛,看错了2道题,顺便爆了一道题的int(没错第一个点就会爆)之后爆零了,吓得我赶紧把这篇博客搞出来了..... ...

  7. 洛谷 P1919 【模板】A*B Problem升级版(FFT快速傅里叶)

    题目来源 吐槽下P3803都是紫题... 真心好写,本想一遍过的...但是 我真是太菜了... #include<bits/stdc++.h> using namespace std; ; ...

  8. RAP 接口Mock示例

    前后端分离式开发的思考 目前大部分公司都实行了前后端分离开发.然而在项目开发过程当中,经常会遇到以下几个尴尬的场景: 1.前端开发依赖于后端接口数据,需要与后端接口联调才能获得数据展示,从而拖慢了开发 ...

  9. mybatis 直接执行sql 【我】

    Connection conn = getConnection();//            Connection conn = this.ss.getConnection(); 返回Connect ...

  10. free(): invalid next size (fast): 0x000000xxx

    记录一次错误,一开始看到这个错误,第一反应是不是释放了两次,后来检测绝对没有,然后又检查了下是不是new/malloc和delete/free没配对, 发现也不是,最后是发现new[x]中x是0的缘故 ...