本篇文章给大家带来的内容是java中hashmap和hashtable的区别是什么?hashmap和hashtable的简单比较。有一定的参考价值,有需要的朋友可以参考一下,希望对你们有所助。
1.首先来看下继承结构:
hashmap
public class hashmap<k,v> extends abstractmap<k,v> implements map<k,v>, cloneable, serializable
hashtable
public class hashtable<k,v> extends dictionary<k,v> implements map<k,v>, cloneable, java.io.serializable
1、首先通过名字我们可以看出hashmap符合驼峰命名规则,hashtable不符合驼峰命名规则,通过继承结构我们发现hashmap继承自abstractmap<k,v>,而hashtable继承自dictionnary<k,v>,dictionary是一个已经被废弃的类,所有hashtable一般不推荐使用,多线程环境下可以使用同步容器concurrenthashmap<k,v>类。
2、通过类的属性可以发现,在jdk1.8,当hashmap中的某条链中存储的结点数大于等于8时并且链表数组长度大于64时转化为红黑树,而hashtable并不会转化为红黑树。
3、hashtable的put()与hashmap的put()
hashtable的put操作:
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; int hash = key.hashcode(); int index = (hash & 0x7fffffff) % tab.length; @suppresswarnings(unchecked) entry<k,v> entry = (entry<k,v>)tab[index]; for(; entry != null ; entry = entry.next) { if ((entry.hash == hash) && entry.key.equals(key)) { v old = entry.value; entry.value = value; return old; } } addentry(hash, key, value, index); return null; }
hashtable中的方法添加了synchronized 关键字,所以它是一个同步方法。
通过 if (value == null) throw new nullpointerexception();} 可以看出他不允许value的值为空,而当key为null时,调用key.hashcode();会抛出null指针异常,所以hashtable中存储的entry中的key和值都不能为空。还有hashtable取链表下标是通过%运算来取的。
下面看hashmap
public v put(k key, v value) { return putval(hash(key), key, value, false, true); }static final int hash(object key) { int h; return (key == null) ? 0 : (h = key.hashcode()) ^ (h >>> 16); }
可以看到hashmap中key可以有一个null,当key为null空时它的hash值为0,而且hashmap获取链表数组下标的方法与hashtable不同,它是使用(n-1)&hash来计算,因为hashmap的数组链表长度为2的n次方。
总结:hashmap中的key可以有一个null,值可以有多个null,hashtable中的key和value都不能为空。定位链的方式不同,hashmap通过&运算来获取下标,而hashtable通过%来获取下标,&运算更快。
4、hashmap和hashtable的扩容方式不同。
hashtable扩容:
@suppresswarnings(unchecked) protected void rehash() { int oldcapacity = table.length; entry<?,?>[] oldmap = table; // overflow-conscious code int newcapacity = (oldcapacity << 1) + 1; //max_array_size = int的最大值-8 if (newcapacity - max_array_size > 0) { if (oldcapacity == max_array_size) // keep running with max_array_size buckets return; newcapacity = max_array_size; } entry<?,?>[] newmap = new entry<?,?>[newcapacity]; modcount++; threshold = (int)math.min(newcapacity * loadfactor, max_array_size + 1); table = newmap; //从链表数组尾到头遍历 for (int i = oldcapacity ; i-- > 0 ;) { for (entry<k,v> old = (entry<k,v>)oldmap[i] ; old != null ; ) { entry<k,v> e = old; old = old.next; //从新定位链位置 int index = (e.hash & 0x7fffffff) % newcapacity; e.next = (entry<k,v>)newmap[index]; newmap[index] = e; } } }
通过源码我们发现hashtable链表数组的最大长度为int类型的最大值-8,hashtable的扩容会把长度变为原来的二倍加1,而且扩容后需要从新定位链表。而且扩容后数组链表的顺序变成了原顺序的倒序。
hashmap的扩容:
final node<k,v>[] resize() { node<k,v>[] oldtab = table; int oldcap = (oldtab == null) ? 0 : oldtab.length; int oldthr = threshold; int newcap, newthr = 0; //如果远链表数组长度大于零 if (oldcap > 0) { //如果原长度大于或等于maximum_capacity(2^30),则将threshold(闸值)设为integer.max_value大约为maximum_capacity(2^30)的二倍 if (oldcap >= maximum_capacity) { threshold = integer.max_value; return oldtab; } //让新的容量变为旧的二倍 else if ((newcap = oldcap << 1) < maximum_capacity && oldcap >= default_initial_capacity) //新的闸值也变为原来的二倍 newthr = oldthr << 1; // double threshold } //老的链表数组长度小于等于0,老的闸值大于零,这种情况是初始化时传入一个map导致的构造器为public hashmap(map<? extends k, ? extends v> m) else if (oldthr > 0) // initial capacity was placed in threshold //让新的容量等于旧的闸值 newcap = oldthr; //下面的else是默认的构造器,即public hashmap() else { // zero initial threshold signifies using defaults newcap = default_initial_capacity; newthr = (int)(default_load_factor * default_initial_capacity); } //新的闸值为零时(也就是(newcap = oldcap << 1) >= maximum_capacity的情况),这时需要赋予newthr正确的值。 if (newthr == 0) { float ft = (float)newcap * loadfactor; //闸值=链表数组长度*加载因子。 newthr = (newcap < maximum_capacity && ft < (float)maximum_capacity ? (int)ft : integer.max_value); } threshold = newthr; //扩容,如果原来的链表数组中有数据,就复杂到table中 @suppresswarnings({rawtypes,unchecked}) node<k,v>[] newtab = (node<k,v>[])new node[newcap]; table = newtab; if (oldtab != null) { //遍历老的链表数组 for (int j = 0; j < oldcap; ++j) { node<k,v> e; //当oldtab[j]这条链不为空时 if ((e = oldtab[j]) != null) { oldtab[j] = null; //如果这条链只有首节点有数据,把它添加进新链表数组中 if (e.next == null) //因为数组的容量时2的n次方,所以使用hash&(newcap-1)来计算出在那条链中。 newtab[e.hash & (newcap - 1)] = e; //如果老的链在红黑树中,使用split()方法来复制 else if (e instanceof treenode) ((treenode<k,v>)e).split(this, newtab, j, oldcap); //当前链中不只只有一个链表头数据时,遍历链表来复制 else { // preserve order //数据的复制有两种情况,第一种是原位置不变,第二种是位置改变 lohead代表和原链相同位置的链,hihead代表是原链加上原容量的链,因为扩容后长度为原长度的二倍,一个链中的节点要不在原位置的链中,要么在原位置加原容量的链中 node<k,v> lohead = null, lotail = null; node<k,v> hihead = null, hitail = null; node<k,v> next; do { next = e.next; //通过e.hash和oldcap进行&运算来得出位置是否需要改变。 比如原数组容量为16(10000)和hash值进行&运算,如果高位1未参加运算,则为0即位置不变,如果高位参加了运算值不等于0,需要改变位置。 //lohead和hihead分别代表原位置的链和新位置的链 if ((e.hash & oldcap) == 0) { if (lotail == null) lohead = e; else lotail.next = e; lotail = e; } else { if (hitail == null) hihead = e; else hitail.next = e; hitail = e; } } while ((e = next) != null); if (lotail != null) { lotail.next = null; //原位置为j newtab[j] = lohead; } if (hitail != null) { hitail.next = null; //新位置为j+oldcap newtab[j + oldcap] = hihead; } } } } } return newtab; }
可以看到hashmap的扩容容量变为原来的二倍,而且它不需要从新定位链表,因为扩容后的位置要么在原位置,要么在原位置+原容量,通过hash和链表数组的长度进行与运算即可判断,如果数组长度的高位参加了运算就在原位置+原容量,高位没参加运算在原位置。而且hashmap扩容后链表数据顺序不变。
5.hashmap和hashtable的初始容量不同。
hashtable的初始容量为11,hashmap的初始容量为16.
以上就是java中hashmap和hashtable的区别是什么?hashmap和hashtable的简单比较的详细内容。