HashMap HashTable HashSet区别剖析
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,看下面的源码
- //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);
- }
转载于:https://www.cnblogs.com/dangzhenjiuhao/p/5416470.html
HashMap HashTable HashSet区别剖析相关推荐
- HashMap底层实现原理/HashMap与HashTable区别/HashMap与HashSet区别(转)
HashMap底层实现原理/HashMap与HashTable区别/HashMap与HashSet区别 文章来源:http://www.cnblogs.com/beatIteWeNerverGiveU ...
- HashMap底层实现原理/HashMap与HashTable区别/HashMap与HashSet区别
Hash算法 Hash,一般翻译做"散列",也有直接音译为"哈希"的,就是把任意长度的输入(又叫做预映射, pre-image),通过散列算法,变换成固定长度的 ...
- hashmap hashtable 的区别
为什么80%的码农都做不了架构师?>>> /*** HashMap_1.java* me** Function: TODO ** ver date author* ────── ...
- hashmap hashtable concurrenthashmap区别
https://www.cnblogs.com/heyonggang/p/9112731.html
- HashMap、HashTable、ConcurrentHashMap、HashSet区别 线程安全类
HashMap专题:HashMap的实现原理--链表散列 HashTable专题:Hashtable数据存储结构-遍历规则,Hash类型的复杂度为啥都是O(1)-源码分析 Hash,Tree数据结构时 ...
- Java—Map集合详解(HashMap/Hashtable/LinkedHashMap/Properties/TreeMap/WeakHashMap/IdentityHashMap/EnumMap)
关注微信公众号:CodingTechWork,一起学习进步. Map Map集合介绍 Map(也称为字典.关联数组)是用于保存具有映射关系的数据,保存两组值,key和value,这两组值可以是任何 ...
- HashMap和HashSet的区别
转自:http://www.importnew.com/6931.html HashMap和HashSet的区别是Java面试中最常被问到的问题.如果没有涉及到Collection框架以及多线程的面试 ...
- 【转】HashMap和HashSet的区别
原文网址:http://www.importnew.com/6931.html HashMap和HashSet的区别是Java面试中最常被问到的问题.如果没有涉及到Collection框架以及多线程的 ...
- Set集合[HashSet,TreeSet,LinkedHashSet],Map集合[HashMap,HashTable,TreeMap]
------------ Set ------------------- 有序: 根据添加元素顺序判定, 如果输出的结果和添加元素顺序是一样 无序: 根据添加元素顺序判定,如果输出的结果和添加元素的顺 ...
最新文章
- 【Spark Summit East 2017】Spark SQL:Tungsten之后另一个可以达到16倍速度的利器
- input框的内容变化监听
- MySQL中interactive_timeout和wait_timeout的区别
- Pig自定义过滤UDF和加载UDF
- APACHE-TOMCAT集群环境部署
- 挖掘频繁模式、关联和相关
- python颜色识别原理_电脑控制手机 Python实现颜色识别功能
- [网络安全自学篇] 三.Burp Suite安装配置、Proxy基础用法及流量分析示例
- hdu 3339 In Action 背包+flyod
- 剑指offer刷题(java)|二维数组中的查找|替换空格|leetcode刷题
- Android官方开发文档Training系列课程中文版:构建第一款安卓应用之工程创建
- Linux通过文件大小查找,linux 根据文件大小查找文件
- 软件工程复习提纲——第九章
- 95-31-020-ChannelGroup-DefaultChannelGroup
- 2017级C语言大作业 - 气球塔防
- python根据ip获取地理位置_使用python根据ip获取目标地理位置信息
- android字体等宽,Android等宽字体大小
- VS属性页的目录类型
- linux 编译程序的四个过程
- 基于android的社团管理app