《Java源码分析》:Hashtable
2016-07-20 21:53
363 查看
《Java源码分析》:Hashtable
Hashtable类的实现也是基于“数组和链表”来实现的。Hashtable的继承关系为:
public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, java.io.Serializable
继承的是Dictionary类,实现了Map、Cloneable、Serializable。
注意:这也是Hashtable和HashMap第一个不同的地方,HashMap实现的是AbstractMap类。
1、Hashtable的几个属性
1)、private transient Entry<?,?>[] table;//用来实现Hashtable所借助的数组,默认大小为11.
注意:这是Hashtable与HashMap第二个不同的地方,Hashtable的默认容量大小为11,HashMap的默认容量大小为16且扩容的大小必须一直是2的幂次方。
2)、
private transient int count;//用来记录table数组中存储元素的个数
3)、
private int threshold;//扩容的门限,即如果数组table中存储的元素个数大于threshold,则扩大数组table的大小
4)、
private float loadFactor;//加载因子,默认值为0.75f,扩容门线threshold的值等于loadFactor与数组table的容量的乘积.
2、Hashtable的构造函数
/* *我们获得的hashtable对象,无论有参数,无参数的 最终都调用的这个构造 *initialCapacity为初始容量,默认大小为 11 *loadFactor为加载因子,默认大小为0.75f,好比是你的内容超过了 总长度了75%,就会自动扩容 */ public Hashtable(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal Load: "+loadFactor); if (initialCapacity==0) initialCapacity = 1; this.loadFactor = loadFactor; table = new Entry<?,?>[initialCapacity]; //扩容门限 threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1); }
Hashtable几个常见的方法
1、put(K key, V value)put方法存储数据的思想如下:首先根据拿到key的hashcode,然后根据hashCode找到此元素即将要存储的位置index.如果位置index已经有元素链表了,则在此链表中,寻找是否有key已经存在了,如果存在,则更新value值,如果不存在,则将此value存储在index位置上。
源码如下:
从源码中可以看到,put方法首先检查value是否为null,如果为空,则抛异常。
注意:这也是Hashtable与HashMap的另一个不同点,在HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。
另一个区别
在Hashtable中所有的方法都是用synchronized关键字进行了同步,而HashMap中的方法在缺省情况下是非同步的。在多线程并发的环境下,可以直接使用Hashtable,但是要使用HashMap的话就要自己增加同步处理了。
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]; //如果数组table中有值,则检查下是否有此key,如果有,则更新value for(; entry != null ; entry = entry.next) { if ((entry.hash == hash) && entry.key.equals(key)) { V old = entry.value; entry.value = value; return old; } } //如果没有此key,则添加此节点 addEntry(hash, key, value, index); return null; }
在上面的put方法中在添加新节点时,调用了addEntry方法,下面就来看下这个方法的源码,看看这个方法是如何将一个节点添加到数组table中的。
2、addEntry(int hash, K key, V value, int index)
给数组table的index位置添加元素。
思想如下:首先检查下数组table存储的元素个数是否大于扩容门限threshold.如果大于,则扩容,扩容之后,重新获取key的hashcode,并根据hashcode重新计算要存储的位置index.最后将要存储的数据存储到table[index]中。这里要注意的一点是,如果table[index]中已经有其它元素了,那么在同一个位子上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。
源码如下:(在代码中进行了一定的注释)
private void addEntry(int hash, K key, V value, int index) { modCount++; Entry<?,?> tab[] = table; //检查数组table中存储的数据个数>=门限,如果大于等于,则对数组进行扩容 if (count >= threshold) { // Rehash the table if the threshold is exceeded rehash(); //扩容之后,对key的hashcode、以及即将要存储的位置index重新进行更新 tab = table; hash = key.hashCode(); index = (hash & 0x7FFFFFFF) % tab.length; } // Creates the new entry. @SuppressWarnings("unchecked") //获取table数组在index的元素,如果非空,则新节点放在这个节点的前面,成为新的链表头 Entry<K,V> e = (Entry<K,V>) tab[index]; tab[index] = new Entry<>(hash, key, value, e); count++; }
由于addEntry方法里面有一个rehash方法,rehash方法是用来对数组table进行扩容的,下面我们来看看这个方法里面是如何来实现的。
3、rehash()
rehash方法的实现思想如下:首先对原来的长度乘以2+1就为即将扩容的长度。然后新建一个newCapacity的数组,将原来的table数组的元素拷贝到新的数组中去即可。
具体实现源码如下:
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; @SuppressWarnings("unchecked") protected void rehash() { int oldCapacity = table.length; Entry<?,?>[] oldMap = table; // overflow-conscious code int newCapacity = (oldCapacity << 1) + 1; 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; } } }
4、内部类Entry
内部类Entry,都说Hashtable是数组和链表的结合体,而Entry就类似于起着链表的作用。
源码如下:(比较简单)
private static class Entry<K,V> implements Map.Entry<K,V> { final int hash; final K key; V value; Entry<K,V> next; protected Entry(int hash, K key, V value, Entry<K,V> next) { this.hash = hash; this.key = key; this.value = value; this.next = next; } @SuppressWarnings("unchecked") protected Object clone() { return new Entry<>(hash, key, value, (next==null ? null : (Entry<K,V>) next.clone())); } // Map.Entry Ops public K getKey() { return key; } public V getValue() { return value; } public V setValue(V value) { if (value == null) throw new NullPointerException(); V oldValue = this.value; this.value = value; return oldValue; } public boolean equals(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry<?,?> e = (Map.Entry<?,?>)o; return (key==null ? e.getKey()==null : key.equals(e.getKey())) && (value==null ? e.getValue()==null : value.equals(e.getValue())); } public int hashCode() { return hash ^ Objects.hashCode(value); } public String toString() { return key.toString()+"="+value.toString(); } }
5、get方法
上面说了put方法,我们也就不得不说下get方法了
get方法的思想也比较简单,如下:首先获取key的hashcode,然后根据hashcode得到存储位置index,最后在table[index]取出元素即可,不过要注意的是,这里可能存储了多个元素构成了一个链表,因此要进行一个key和hash的判断。
@SuppressWarnings("unchecked") public synchronized V get(Object key) { Entry<?,?> tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { return (V)e.value; } } return null; }
以上基本就对Hashtable有了一个大致的了解,而类似于remove、contain方法都比较简单,这里不再进行介绍。
相关文章推荐
- 安卓模拟器运行慢解决办法
- Java学习笔记1——集合
- java连接mysql(一)
- java程序运行时内存分配详解
- java常量池概念
- Spring 框架之 基于注解式编程的spring mvc
- Spring学习-第3天
- 【spring】Spring reference doc 4.3.1 研读 <四> Transaction Management
- java 的static关键字
- Spring AOP的底层实现技术---JDK动态代理
- MyEclipse:(4)MyEclipse中怎么设置Add cast to Clazz 快捷键
- 还是进制之间的转换,这次全部是自己写的没有用Java的Integer
- 九九乘法表
- java正则表达式
- java中的sleep()和wait()的区别
- Spring整合ORM技术 -- 集成Hibernate
- SpringBoot多数据源及MyBatis配置详解
- JAVA集合类之ArrayList和LinkedList性能比较
- Maven实战(一)--Why Maven
- 第18章-Java IO系统