注:这里使用java 1.6版本
Hashtable和HashMap很相似;最开始使用的是Hashtable,后来HashMap被设计出来替代它。目前在使用中建议使用HashMap,有同步需求时建议使用ConcurrentHashMap。目前已经不建议使用Hashtable了。
下面看看Hashtable的实现情况。注意到,在定义名称的时候,Hashtable的table是小写字母开头,而HashMap是更标准的驼峰定义。
1. Hashtable继承Dictionary类,实现Map,Cloneable和Serializable接口。
2. Hashtable的内部实现仍然是Entry数组,同样有count、threshold、loadFactor和modCount变量。
Hashtable的Entry和HashMap的Entry不同,这里说两点:
1) Hashtable的Entry不接受null值,当value为null时,会抛出NPE异常。这也是Hashtable整体上不接受null的原因。不接受null值,是Hashtable和HashMap不同的地方之一。
public V setValue(V value) { if (value == null) throw new NullPointerException(); V oldValue = this.value; this.value = value; return oldValue; }
2) Hashtable的Entry有clone方法,会把next指向的所有Entry都进行一次clone。
protected Object clone() { return new Entry<K,V>(hash, key, value, (next==null ? null : (Entry<K,V>) next.clone())); }
3. Hashtable的初始化
Hashtable同样拥有4中初始化的方法:
1) public Hashtable(int initialCapacity, float loadFactor)
2) public Hashtable(int initialCapacity)
3) public Hashtable()
4) public Hashtable(Map<? extends K, ? extends V> t)
前三种初始化是简单的通过定义Hashtable的基本参数的方法进行初始化的;最后的一个初始化是通过现有的Map进行初始化。
默认情况下,Hashtable的loadFactor为0.75F,和HashMap一样;Hashtable的默认大小是11,而HashMap是16,这一点不同。
通过现有Map初始化一个Hashtable,流程上,先会初始化一个Hashtable,然后通过putAll方法,将Map中的内容写入Hashtable。如下。
/** * Constructs a new hashtable with the same mappings as the given * Map. The hashtable is created with an initial capacity sufficient to * hold the mappings in the given Map and a default load factor (0.75). * * @param t the map whose mappings are to be placed in this map. * @throws NullPointerException if the specified map is null. * @since 1.2 */ public Hashtable(Map<? extends K, ? extends V> t) { this(Math.max(2*t.size(), 11), 0.75f); putAll(t); }
将所有元素写入Hashtable的putAll方法,仅仅是一个简单的循环。这里用的是常见的EntrySet的方法。
/** * Copies all of the mappings from the specified map to this hashtable. * These mappings will replace any mappings that this hashtable had for any * of the keys currently in the specified map. * * @param t mappings to be stored in this map * @throws NullPointerException if the specified map is null * @since 1.2 */ public synchronized void putAll(Map<? extends K, ? extends V> t) { for (Map.Entry<? extends K, ? extends V> e : t.entrySet()) put(e.getKey(), e.getValue()); }
如果深入去对比会发现,Hashtable的这种putAll的方法会触发Hashtable的结构变化;而HashMap的putAllForCreate并不会。当然,开始的时候定义的初始化数据的长度足够的话,不会马上触发到。但是为什么HashMap会分开两种方法进行,而Hashtable用一种方法来处理?
下面看看Hashtable的put方法。
4. 将元素写入Hashtable
实现过程如下所示。
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; for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) { 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; } // Creates the new entry. Entry<K,V> e = tab[index]; tab[index] = new Entry<K,V>(hash, key, value, e); count++; return null; }
第一步,检查将写入的value非空;再次确认,Hashtable不允许出现null值;
第二步,查找对应的key,如果key存在,则将value写入,同时将旧值返回;如果key不存在,则转第三步;
第三部,此时需要进行hash结构的变化,所以先进行modCount++的操作。如果此时需要进行hash结构的变化(count>=threshold);否则新建一个Entry对象,将它插入key对应的链表的首部。完成后,count++,表示元素增加;同时返回null。
需要注意两点:a.如果put返回的结果是null,表示正常的加入成功;否则是value的值的替换。b.整个Hashtable在进行put操作的过程中是使用synchronize进行同步的,所以可能性能比较差。后续我们还会看到Hashtable的多个操作中都会有synchronize的同步。使用synchronize进行同步,保证了Hashtable是线程安全的,但是也降低了Hashtable的性能,这是Hashtable和HashMap重要的一个不同点。
5. Rehash
当Hashtable或者HashMap的容量不足的时候,它们会通过rehash进行扩容。这是一种非常消耗性能的操作。下面看看Hashtable是怎么进行这个操作的。
/** * Increases the capacity of and internally reorganizes this * hashtable, in order to accommodate and access its entries more * efficiently. This method is called automatically when the * number of keys in the hashtable exceeds this hashtable's capacity * and load factor. */ protected void rehash() { int oldCapacity = table.length; Entry[] oldMap = table; 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; } } }
每个rehash都会出发一次modCount的自增;
Hashtable的扩展是两倍的旧值再加一;而HashMap每次扩展都是旧值的两倍,保证了其大小始终是2的幂,可以尽量的提升性能。
另外,在将Map的元素写入Hashtable的时候,使用的还是头插法,即写入的新元素,每次都是写在链表的头部,这一点和HashMap是一致的。
6. 一些简单方法的实现
1) size(),获取Hashtable的元素个数;实际是count的数量,synchronize同步。
2) isEmpty(),判断Hashtable是否为空;检查count是否为0,synchronize同步。
3) keys(),获取Hashtable的所有的key集合,返回结果是一个Enumeration,synchronize同步。
4) elements(),获取Hashtable的所有value,也是Enumeration,synchronize同步。
5) clear(),清空Hashtable的元素,和HashMap的实现一致,只是方法上使用了synchronize同步。
7. Hashtable的Enumeration
不同于HashMap,Hashtable使用Enumeration进行内部的元素的迭代访问,而HashMap使用的是Iterator。
关于Enumeration更详细的可以看看Hashtable的实现代码;在遍历的时候,可以传入type进行区分,是需要下一个key,还是下一个value,亦或是下一个Entry。
8. 判断元素是否存在
Contains方法:
public synchronized boolean contains(Object value) { if (value == null) { throw new NullPointerException(); } Entry tab[] = table; for (int i = tab.length ; i-- > 0 ;) { for (Entry<K,V> e = tab[i] ; e != null ; e = e.next) { if (e.value.equals(value)) { return true; } } } return false; }
简单的遍历实现,没有特别新奇的地方;另外,还是不接受null值,其实这里可以直接返回false,而不是抛出异常的。这里的方法同样是synchronize同步的。
containsValue方法:
对于contains方法的再次调用。
containsKey方法:
也是遍历实现。
在HashMap中,没有contains方法,这样确认看起来清晰一点。
9. Get方法
实现如下:
public synchronized V get(Object key) { Entry tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { return e.value; } } return null; }
除了hash方法不同,并且使用synchronize同步之外,就是简单的遍历。
单独拿出来说仅仅是因为该方法对于map来说比较重要。
10. Remove方法
实现如下:
public synchronized V remove(Object key) { Entry tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; for (Entry<K,V> e = tab[index], prev = 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--; V oldValue = e.value; e.value = null; return oldValue; } } return null; }
11. Clone方法
实现如下。可以看到,元素的clone还是通过Entry的clone方法实现的。Entry的clone具有传递性,它会将链表指针指向的Entry都进行clone。
/** * Creates a shallow copy of this hashtable. All the structure of the * hashtable itself is copied, but the keys and values are not cloned. * This is a relatively expensive operation. * * @return a clone of the hashtable */ public synchronized Object clone() { try { Hashtable<K,V> t = (Hashtable<K,V>) super.clone(); t.table = new Entry[table.length]; for (int i = table.length ; i-- > 0 ; ) { t.table[i] = (table[i] != null) ? (Entry<K,V>) table[i].clone() : null; } t.keySet = null; t.entrySet = null; t.values = null; t.modCount = 0; return t; } catch (CloneNotSupportedException e) { // this shouldn't happen, since we are Cloneable throw new InternalError(); } }
12. 其他的一些内部的类,平时很少使用到,这里不再详细说明了。
13. 为了支持序列化和反序列化,Hashtable最后实现了readObject和writeObject。
Hashtable中几乎所有的操作都需要进行同步,保证了一致性的同时,损失了很多性能。估计这也是它被HashMap替代的主要原因。
Hashtable 和 HashMap 还有一点很重要的不同,即是hash方法的不同。有兴趣可以深入了解。
相关推荐
在Java编程语言中,哈希表(HashTable)是一种常见的数据结构,它提供了高效的数据存储和检索功能。哈希表基于哈希函数将键(Key)映射到数组的索引位置,通过键值对(Key-Value Pair)来存储数据。这种数据结构允许...
Java集合专题总结:HashMap和HashTable源码学习和面试总结 本文总结了Java集合专题中的HashMap和HashTable,涵盖了它们的源码学习和面试总结。HashMap是一种基于哈希表的集合类,它的存储结构是一个数组,每个元素...
因为,JavaScript的数组非常特殊,而且如果你能够理解它,那么对于我们学习JSON对象语法就非常容易理解了--因为JSON就是一个数组--我们也可以把它看成一个Hashtable集合对象!本人认为,理解JavaScript的数组是学习...
这个压缩包“Java 实例 - 遍历 HashTable 的键值源代码+详细教程.zip”包含了关于如何遍历`HashTable`的详细教程和源代码,对于学习Java的初学者或者需要深入了解`HashTable`操作的开发者来说,这是一个非常宝贵的...
在易语言中,哈希表的实现可以参考其他编程语言,如Java的HashTable类。 Java的HashTable是一个同步的键值对容器,它维护了一个内部数组,用于存储键值对。每个键值对都由一个唯一的键标识,键不能为null,而值可以...
Java并发编程是Java开发中的重要领域,涉及到多线程、线程安全以及系统性能优化等多个方面。本学习总结将深入探讨并发容器、同步容器、同步工具...通过不断实践和学习,可以进一步提升对Java并发编程的理解和应用能力。
在Java编程语言中,`Hashtable`是一个非常基础且重要的数据结构...学习并理解`Hashtable`的工作原理和用法对于提升Java编程能力是非常有价值的。通过分析压缩包中的示例代码,你可以更好地掌握`Hashtable`的实际应用。
在深入学习Java集合时,我们需要特别关注HashMap和HashTable这两个重要的类。虽然它们都是用于存储键值对的数据结构,但它们在设计和使用上有显著的区别。 HashMap是Java 1.2引入的,它是Map接口的一个实现,提供了...
通过本教程的源代码实例,你可以更直观地理解`HashTable`和`Enumeration`的使用方式,这对于学习Java基础和提升编程技能非常重要。记得实际动手操作,编写并运行代码,这样能更好地巩固你的理解。同时,查阅相关的...
总的来说,哈希表(`Hashtable`)和枚举器是Java早期编程中的关键概念,理解它们的工作原理和用法对于深入学习Java集合框架至关重要。随着Java的发展,虽然新的数据结构如`HashMap`和迭代器接口已经取代了它们的一些...
最后,学习集合框架的源码不仅有助于提升对Java内存模型的理解,还能帮助我们在设计和优化算法时作出更好的决策。对于Java初学者来说,掌握集合类是进阶的关键步骤,因为它们在日常开发中广泛使用,如泛型、Web框架...
在Java编程语言中,`HashMap`和`HashTable`都是实现键值对存储的数据结构,但它们之间存在一些显著的区别,这些区别主要体现在...对于学习和理解,源码阅读是非常有价值的,可以帮助深入理解Java集合框架的设计原理。
Java集合详解4:HashMap和HashTable Java集合详解5:深入理解LinkedHashMap和LRU缓存 Java集合详解6:TreeMap和红黑树 Java集合详解7:HashSet,TreeSet与LinkedHashSet Java集合详解8:Java集合类细节精讲 JavaWeb
5. Java集合框架:熟悉Java集合框架,掌握Set、List、Map、Iterator和Enumeration接口及其各自实现类(例如,HashSet、ArrayList、Vector、HashMap、HashTable、ArrayList中的ArrayList、Vector等),了解它们的特点...
- **哈希表类Hashtable**:线程安全的键值对存储,但不支持null键或值,推荐使用`java.util.concurrent.ConcurrentHashMap`替代。 - **位集合类BitSet**:用于存储和操作位数组。 2. **与时间有关的类Date, ...
【Java工程师面试复习指南】本仓库架构大部分Java工程师所需要掌握的核心知识,整合了互联网上的很多优质Java技术文章,力求打造为最完整最实用的...Java集合详解:HashMap和HashTable Java集合详解:深入理解LinkedHas
本文将深入探讨Java基础学习中的几个关键知识点,包括Properties的使用、装饰者模式以及缓冲流的运用。 首先,Properties是Java中用于存储键值对的一个类,常用于配置文件的管理。它基于HashTable,提供了一种持久...
Java 问题集合旨在帮助Java学习者巩固基础知识,其中包括类的作用域、集合框架的区别、字符编码、多线程实现以及垃圾回收机制等核心概念。 1. **类的作用域**: - `public`:任何地方都能访问。 - `private`:...
标题 "Javapython for leetcode 1 array2 list3 string4 hashtable5 m.zip" 提供的信息表明,这个压缩包包含了一系列与LeetCode题目相关的Java和Python编程解决方案,重点涉及了数组、列表、字符串、哈希表(或字典...
文档可能还鼓励读者参与到Java开源社区中,通过给项目加星(Star)、提交Issue或Pull Request来贡献或学习。 文档内容还提到了“Offer”这个词,这可能意味着文档中包含了一些面试题或面试技巧的讨论。在Java面试中...