Hashtable源码分析(基于jdk1.8,推选)

发布日期:2022-06-19 12:40    点击次数:193

Hashtable源码分析(基于jdk1.8,推选)

一、意志Hashtable

1、汲取联系

为了能好好的默契Hashtable,咱们先看一下他在总计聚积体系中的位置:

从上头的图咱们会发现,Hashtable和HashMap师出同门,不外这张图太宏观,咱们如故放小了看

这张图仍是很通晓了,汲取了Dictionary,完美了Map接口。

2、与HashMap的离别

若是你之前看过我写的那篇HashMap著述的话,在这里对他们俩的离别一定有了解,当今咱们对其进行一个整理(这里只看离别):

(1)HashMap允许key和value为空,然则Hashtable不允许。

(2)Hashtable是线程安全的,HashMap不是线程安全。

(3)ashtable汲取自Dictionary,HashMap汲取自AbstractMap。

(4)迭代器不同,Hashtable是enumerator迭代器,HashMap是Iterator迭代器。

3、Hashtable基本使用

publicclassHashtableTest{publicstaticvoidmain(String[]args){//新建神色Hashtabletable=newHashtable;Hashtabletable1=newHashtable(16);Hashtabletable2=newHashtable(16,0.75f);HashMapmap=newHashMap;Hashtabletable3=newHashtable(map);table.put("张三","1");table.put("李四","2");//这种神色会出现空指针特殊,因为Hashtable的key不可为空table.put(null,"3");System.out.println(table.toString);}}

底下咱们就通过源码来分析一下Hashtable。

二、源码分析

关于聚积类的源码分析,一般都是从参数、构造短处、还有增更动查的基础上进行分析,然后即是增多元素,增多了怎么处治。删除元素,删多了怎么办等等。底下咱们就按照这个思绪一步一步分析:

1、参数

HashTable的底层领受"拉链法"哈希表,况兼提供了5个主要的参数:

(1)table:为一个Entry[]数组类型,Entry代表了“拉链”的节点,每一个Entry代表了一个键值对。

(2)count:容器中包含Entry键值对的数目。

(3)threshold:阈值,大于这个阈值时需要调度容器容量。值="容量*加载因子"。

(4)loadFactor:加载因子。这个相比紧迫。

(5)modCount:用来完美“fail-fast”机制的。对容器任何增更动操作都会修改modCount。若是出错立即抛出ConcurrentModificationException特殊。

privatetransientEntry[]table;privatetransientintcount;privateintthreshold;privatefloatloadFactor;privatetransientintmodCount=0;

上头的是源码,你会发现table、count、modCount还都是transient修饰的,这也就意味着这三个参数是不可被系列化的。

2、构造短处

底下咱们望望其构造短处,源码中一共提供了4个构造短处:

(1)构造短处1

//构造一个空的Hashtable//默许容量是11,加载因子是0.75publicHashtable{this(11,0.75f);}

(2)构造短处2

//在构造Hashtable的时分,少妇极品熟妇人妻无码指定容量//加载因子如故0.75publicHashtable(intinitialCapacity){this(initialCapacity,0.75f);}

(3)构造短处3

publicHashtable(intinitialCapacity,floatloadFactor){//确保启动容量合适if(initialCapacity[initialCapacity];//汲引阈值:initialCapacity*loadFactor和MAX_ARRAY_SIZE+1的最小者threshold=(int)Math.min(initialCapacity*loadFactor,MAX_ARRAY_SIZE+1);}

这个构造短处最初排撤回一些特殊情况,然后新建一个table数组来装数据。

(4)构造短处4

//构造给定的Map具有疏通映射联系的新哈希表:也即是底下这种//HashMapmap=newHashMap;//Hashtabletable3=newHashtable(map);publicHashtable(Mapt){this(Math.max(2*t.size,11),0.75f);putAll(t);}

这个构造短处咱们可就要略微防范了,委果完美这个操作的是putAll(t)。想要弄明晰咱们不妨跟进去望望。

//把map通过for轮回一个一个存放在另外一个map中publicsynchronizedvoidputAll(Mapt){for(Map.Entrye:t.entrySet)put(e.getKey,e.getValue);}

上头的代码使用了泛型,而且如故泛型通配符,不外原理很明确,即是通过for轮回一个一个回荡到新的map中。

以上所述即是总计构造短处的机制。

3、增多一个元素

publicsynchronizedVput(Kkey,Vvalue){//第一部分:确保value不为空if(value==null){thrownewNullPointerException;}//第二部分:确保table中莫得刻下的keyEntrytab[]=table;inthash=key.hashCode;intindex=(hash&0x7FFFFFFF)%tab.length;@SuppressWarnings("unchecked")Entryentry=(Entry)tab[index];for(;entry!=null;entry=entry.next){if((entry.hash==hash)&&entry.key.equals(key)){Vold=entry.value;entry.value=value;returnold;}}//第三部分:增多一个元素的中枢addEntry(hash,key,value,index);returnnull;}

这些代码第一部分和第二部分都是为了莫得特殊,若是刻下容器有这个key,国产色婷婷五月精品综合在线那么平直以新值代替旧值即可,最主要的如故第三部分,添加一个元素的中枢addEntry短处。进去望望:

privatevoidaddEntry(inthash,Kkey,Vvalue,intindex){//modCount是为了安全机制modCount++;Entrytab[]=table;if(count>=threshold){//若是大于阈值,就会再行hash扩容rehash;tab=table;hash=key.hashCode;index=(hash&0x7FFFFFFF)%tab.length;}//莫得特殊情况,新建一个Entry字据hash值插入到指定位置即可@SuppressWarnings("unchecked")Entrye=(Entry)tab[index];tab[index]=newEntry(hash,key,value,e);count++;}

上头的这些代码的苟简原理即是若是容器内部莫得满,那就新建一个Entry字据hash值插入到指定位置。而且一滥觞还提供了modCount确保安全(快速失败机制)。如何去扩容呢?底下咱们接着讲。

2、扩容

扩容即是当容器中放满了,需要把容器扩大。咱们望望这个rehash是如何扩容的。

protectedvoidrehash{intoldCapacity=table.length;Entry[]oldMap=table;//新容量=旧容量*2+1:完美神色即是oldCapacity0){if(oldCapacity==MAX_ARRAY_SIZE)return;newCapacity=MAX_ARRAY_SIZE;}//字据新容量建一个新Map,并赋值给tableEntry[]newMap=newEntry[newCapacity];modCount++;//再行缠绵阈值threshold=(int)Math.min(newCapacity*loadFactor,MAX_ARRAY_SIZE+1);table=newMap;//将蓝本的元素拷贝到新的HashTable中for(inti=oldCapacity;i-->0;){for(Entryold=(Entry)oldMap[i];old!=null;){Entrye=old;old=old.next;intindex=(e.hash&0x7FFFFFFF)%newCapacity;e.next=(Entry)newMap[index];newMap[index]=e;}}}

上头代码的疑望仍是很明晰了,不外上头投诚你会有一个疑问,岂论是put一个元素如故扩容,在缠绵hash的时分都出现了(e.hash&0x7FFFFFFF),它的作用是什么呢?

你不错这么默契,hash值是int类型,而且一定是正数,和0x7FFFFFFF做与操作即是将负数酿成正数,确保了取得到的index是正数。

3、删除一个元素

publicsynchronizedVremove(Objectkey){Entrytab[]=table;inthash=key.hashCode;intindex=(hash&0x7FFFFFFF)%tab.length;@SuppressWarnings("unchecked")Entrye=(Entry)tab[index];for(Entryprev=null;e!=null;prev=e,e=e.next){if((e.hash==hash)&&e.key.equals(key)){modCount++;if(prev!=null){prev.next=e.next;}else{tab[index]=e.next;}count--;VoldValue=e.value;e.value=null;returnoldValue;}}returnnull;}

删除一个元素就相比浮浅,中枢即是通过key缠绵在容器中的位置,然后把这个位置上的Entry删除即可。由于使用的链表删除起来会更浮浅。将前一个元素指针平直指向下一个元素,跳过刻下元素e.next。

4、查询一个元素

publicsynchronizedVget(Objectkey){Entrytab[]=table;inthash=key.hashCode;intindex=(hash&0x7FFFFFFF)%tab.length;for(Entrye=tab[index];e!=null;e=e.next){if((e.hash==hash)&&e.key.equals(key)){return(V)e.value;}}returnnull;}

这个就太浮浅了,通过key缠绵hash值找到位置,平直通过e.value取得值即可。

5、迭代器

与HashMap不同的是,它的迭代器是Enumeration。在这里咱们不会陶冶Enumeration,仅仅给出其基本的使用短处,因为Enumeration我会在有益的著述内部会先容。这里就不再重叠了。

publicstaticvoidmain(String[]args){HashtablehashTable=newHashtable;hashTable.put("张三","1");hashTable.put("李四","2");hashTable.put("java的架构师技能栈","3");Enumeratione=hashTable.elements;while(e.hasMoreElements){System.out.println(e.nextElement);}}

这即是一个最浮浅的使用短处,

6、其他短处

publicsynchronizedbooleancontains(Objectvalue){if(value==null){thrownewNullPointerException;}Entrytab[]=table;for(inti=tab.length;i-->0;){for(Entrye=tab[i];e!=null;e=e.next){if(e.value.equals(value)){returntrue;}}}returnfalse;}

这个短处是判断这个容器中是否有value这个值,判断的短处相配痴呆,即是通过for轮回一个一个相比。

三、讲求

说真话这个Hashtable使用的场景如故很局限的,是以一般情况下基本上不会用到,岂论是恶果如故空间特质。因为上头的增更动查短处你会发现,全是一个一个比对的,关于数据量大的时分这詈骂常耗时的,而且存储空间亦然领受的链表。

一般来说非并发场景使用HashMap,并发场景不错使用Hashtable,然则推选使用ConcurrentHashMap,因为它锁粒度更低、恶果更高。

Hashtable经常集聚合者HashMap来出问题,但愿全球防范,最中枢的如故HashMap。