`
wangyanlong0107
  • 浏览: 499823 次
  • 性别: Icon_minigender_1
  • 来自: 沈阳
社区版块
存档分类
最新评论

【转】Java Hashtable分析

 
阅读更多

Hashtable的结构,采用的是数据结构中所说的链地址法处理冲突的方法

从上面的结构图可以看出,Hashtable的实质就是一个数组+链表。图中的Entry就是链表的实现,Entry的结构中包含了对自己的另一个实例的引用next,用以指向另外一个Entry。而图中标有数字的部分是一个Entry数组,数字就是这个Entry数组的index。那么往Hashtable增加键值对的时候,index会根据键的hashcode、Entry数组的长度共同决定,从而决定键值对存放在Entry数组的哪个位置。从这种意义来说,当键一定,Entry数组的长度一定的情况下,所得到的index肯定是相同的,也就是说插入顺序应该不会影响输出的顺序才对。然而,还有一个重要的因素没有考虑,就是计算index出现相同值的情况。譬如代码中 "sichuan" 和 "anhui",所得到的index是相同的,在这个时候,Entry的链表功能就发挥作用了:put方法通过Entry的next属性获得对另外一个Entry的引用,然后将后来者放入其中。根据debug得出的结果,"sichuan", "anhui"的index同为2,"hunan"的index为6,"beijing"的index为1,在输出的时候,会以index递减的方式获得键值对。很明显,会改变的输出顺序只有"sichuan"和"anhui"了,也就是说输出只有两种可能:"hunan" - "sichuan" - "anhui" - "beijing"和"hunan" - "anhui" - "sichuan" - "beijing"。以下是运行了示例代码之后,Hashtable的结果:


在Hashtable的实现代码中,有一个名为rehash的方法用于扩充Hashtable的容量。很明显,当rehash方法被调用以后,每一个键值对相应的index也会改变,也就等于将键值对重新排序了。这也是往不同容量的Hashtable放入相同的键值对会输出不同的键值对序列的原因。在Java中,触发rehash方法的条件很简单:hahtable中的键值对超过某一阀值。默认情况下,该阀值等于hashtable中Entry数组的长度×0.75。

 

自 Java 2 平台 v1.2 以来,此类已经改进为可以实现 Map,因此它变成了 Java Collections Framework 的一部分。与新集合的实现不同,Hashtable 是同步的。

由迭代器返回的 Iterator 和由所有 Hashtable 的“collection 视图方法”返回的 Collection 的 listIterator 方法都是快速失败 的:在创建 Iterator 之后,如果从结构上对 Hashtable 进行修改,除非通过 Iterator 自身的移除或添加方法,否则在任何时间以任何方式对其进行修改,Iterator 都将抛出 ConcurrentModificationException因此,面对并发的修改,Iterator 很快就会完全失败,而不冒在将来某个不确定的时间发生任意不确定行为的风险。由 Hashtable 的键和值方法返回的 Enumeration 不 是快速失败的。

注意,迭代器的快速失败行为无法得到保证,因为一般来说,不可能对是否出现不同步并发修改做出任何硬性保证。快速失败迭代器会尽最大努力抛出 ConcurrentModificationException。因此,为提高这类迭代器的正确性而编写一个依赖于此异常的程序是错误做法:迭代器的快速失败行为应该仅用于检测程序错误。

TestHashTable
Hashtable中定义几个内部类(包括静态的嵌套类和普通的内部类)

Hashtable中的Entry数据结构
Entry

put方法:key的hash值不同但是可能放入的index相同,并且在放入之前需要判断
put方法

 1    public synchronized Enumeration<K> keys() {
 2    return this.<K>getEnumeration(KEYS);
 3    }

 4
 5    private <T> Enumeration<T> getEnumeration(int type) {
 6    if (count == 0{
 7        return (Enumeration<T>)emptyEnumerator;
 8    }
 else {
 9        return new Enumerator<T>(type, false);
10    }

11    }

12
13    public synchronized Enumeration<V> elements() {
14    return this.<V>getEnumeration(VALUES);
15    }

16
17Enumerator是Hashtable定义的一个内部类
18    private class Enumerator<T> implements Enumeration<T>, Iterator<T> {
19    Entry[] table = Hashtable.this.table;//访问宿主类的成员变量
20    int index = table.length;
21    Entry<K,V> entry = null;
22    Entry<K,V> lastReturned = null;
23    int type;
24
25    /**
26     * Indicates whether this Enumerator is serving as an Iterator
27     * or an Enumeration.  (true -> Iterator).
28     */

29    boolean iterator;
30
31    /**
32     * The modCount value that the iterator believes that the backing
33     * Hashtable should have.  If this expectation is violated, the iterator
34     * has detected concurrent modification.
35     */

36    protected int expectedModCount = modCount;
37}

38内部类中提供了访问了hashtable中的entry数组的方法.
39在实现Iterator接口中的方法时使用了expectedModCount变量来判断是否有并发修改而导致fast-fail,而在Enumeration的接口方法实现中没有判断
40
41
42
43    public Set<K> keySet() {
44    if (keySet == null)
45        keySet = Collections.synchronizedSet(new KeySet(), this);
46    return keySet;
47    }

48    private class KeySet extends AbstractSet<K> {
49        public Iterator<K> iterator() {
50        return
 getIterator(KEYS);
51        }

52        public int size() {
53            return count;
54        }

55        public boolean contains(Object o) {
56            return containsKey(o);
57        }

58        public boolean remove(Object o) {
59            return Hashtable.this.remove(o) != null;
60        }

61        public void clear() {
62            Hashtable.this.clear();
63        }

64    }

65内部类KeySet中有iterator接口方法的实现,调用的宿主类的getIterator(KEYS)
66    private <T> Iterator<T> getIterator(int type) {
67    if (count == 0{
68        return (Iterator<T>) emptyIterator;
69    }
 else {
70        return new Enumerator<T>(type, true);
71    }

72    }

73getIterator中new 了一个新的内部类Enumerator的对象,最终使用Enumerator来访问hashtable的entry数组,能不能在内部类中直接创建一个内部类的的实例???
74
75
76
77    public Collection<V> values() {
78    if (values==null)
79        values = Collections.synchronizedCollection(new ValueCollection(),
80                                                        this);
81        return values;
82    }

83ValueCollection也是一个内部类,结构和KeySet功能差不多
84    public Set<Map.Entry<K,V>> entrySet() {
85    if (entrySet==null)
86        entrySet = Collections.synchronizedSet(new EntrySet(), this);
87    return entrySet;
88    }

89EntrySet也是内部类,结构和KeySet功能差不多

posted on 2009-07-03 13:24 Frank_Fang 阅读(4287) 评论(1)  编辑  收藏 所属分类: Java编程


评论:
 
# re: Java Hashtable分析 2009-07-15 00:11 | Frank_Fang
public class HashMap<K,V>extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable

基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。(除了不同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同。)此类不保证映射的顺序,特别是它不保证该顺序恒久不变。

此实现假定哈希函数将元素正确分布在各桶之间,可为基本操作(get 和 put)提供稳定的性能。迭代集合视图所需的时间与 HashMap 实例的“容量”(桶的数量)及其大小(键-值映射关系数)的和成比例。所以,如果迭代性能很重要,则不要将初始容量设置得太高(或将加载因子设置得太低)。

HashMap 的实例有两个参数影响其性能:初始容量 和加载因子容量 是哈希表中桶的数量,初始容量只是哈希表在创建时的容量。加载因子 是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,通过调用 rehash 方法将容量翻倍。

通常,默认加载因子 (.75) 在时间和空间成本上寻求一种折衷。加载因子过高虽然减少了空间开销,但同时也增加了查询成本(在大多数 HashMap 类的操作中,包括 get 和 put 操作,都反映了这一点)。在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地降低 rehash 操作次数。如果初始容量大于最大条目数除以加载因子,则不会发生 rehash 操作。

如果很多映射关系要存储在 HashMap 实例中,则相对于按需执行自动的 rehash 操作以增大表的容量来说,使用足够大的初始容量创建它将使得映射关系能更有效地存储。

注意,此实现不是同步的。如果多个线程同时访问此映射,而其中至少一个线程从结构上修改了该映射,则它必须 保持外部同步。(结构上的修改是指添加或删除一个或多个映射关系的操作;仅改变与实例已经包含的键关联的值不是结构上的修改。)这一般通过对自然封装该映射的对象进行同步操作来完成。如果不存在这样的对象,则应该使用 Collections.synchronizedMap 方法来“包装”该映射。最好在创建时完成这一操作,以防止对映射进行意外的不同步访问,如下所示:

 Map m = Collections.synchronizedMap(new HashMap(...));

由所有此类的“集合视图方法”所返回的迭代器都是快速失败 的:在迭代器创建之后,如果从结构上对映射进行修改,除非通过迭代器自身的 remove 或 add 方法,其他任何时间任何方式的修改,迭代器都将抛出 ConcurrentModificationException。因此,面对并发的修改,迭代器很快就会完全失败,而不冒在将来不确定的时间任意发生不确定行为的风险。

注意,迭代器的快速失败行为不能得到保证,一般来说,存在不同步的并发修改时,不可能作出任何坚决的保证。快速失败迭代器尽最大努力抛出 ConcurrentModificationException。因此,编写依赖于此异常程序的方式是错误的,正确做法是:迭代器的快速失败行为应该仅用于检测程序错误。



public class LinkedHashMap<K,V>extends HashMap<K,V> implements Map<K,V>

Map 接口的哈希表和链接列表实现,具有可预知的迭代顺序。此实现与 HashMap 的不同之处在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序通常就是将键插入到映射中的顺序(插入顺序)。注意,如果在映射中重新插入 键,则插入顺序不受影响。(如果在调用 m.put(k, v) 前 m.containsKey(k) 返回了 true,则调用时会将键 k 重新插入到映射 m 中。)

此实现可以让客户避免未指定的、由 HashMap(及 Hashtable)所提供的通常为杂乱无章的排序工作,同时无需增加与 TreeMap 相关的成本。使用它可以生成一个与原来顺序相同的映射副本,而与原映射的实现无关:

     void foo(Map m) {
Map copy = new LinkedHashMap(m);
...
}
如果模块通过输入得到一个映射,复制这个映射,然后返回由此副本确定其顺序的结果,这种情况下这项技术特别有用。(客户通常期望返回的内容与其出现的顺序相同。)

提供特殊的构造方法来创建链接哈希映射,该哈希映射的迭代顺序就是最后访问其条目的顺序,从近期访问最少到近期访问最多的顺序(访问顺序)。这种映射很适合构建 LRU 缓存。调用 put 或get 方法将会访问相应的条目(假定调用完成后它还存在)。putAll 方法以指定映射的条目集合迭代器提供的键-值映射关系的顺序,为指定映射的每个映射关系生成一个条目访问。任何其他方法均不生成条目访问。特别是,collection 视图上的操作 影响底层映射的迭代顺序。

可以重写 removeEldestEntry(Map.Entry) 方法来实施策略,以便在将新映射关系添加到映射时自动移除旧的映射关系。

此类提供所有可选的 Map 操作,并且允许 null 元素。与 HashMap 一样,它可以为基本操作(addcontains 和 remove)提供稳定的性能,假定哈希函数将元素正确分布到桶中。由于增加了维护链接列表的开支,其性能很可能比 HashMap 稍逊一筹,不过这一点例外:LinkedHashMap 的 collection 视图迭代所需时间与映射的大小 成比例。HashMap 迭代时间很可能开支较大,因为它所需要的时间与其容量 成比例。

链接的哈希映射具有两个影响其性能的参数:初始容量加载因子。它们的定义与 HashMap 极其相似。要注意,为初始容量选择非常高的值对此类的影响比对 HashMap 要小,因为此类的迭代时间不受容量的影响。

注意,此实现不是同步的。如果多个线程同时访问链接的哈希映射,而其中至少一个线程从结构上修改了该映射,则它必须 保持外部同步。这一般通过对自然封装该映射的对象进行同步操作来完成。如果不存在这样的对象,则应该使用 Collections.synchronizedMap 方法来“包装”该映射。最好在创建时完成这一操作,以防止意外的非同步访问:

    Map m = Collections.synchronizedMap(new LinkedHashMap(...));
结构修改是指添加或删除一个或多个映射关系,或者在按访问顺序链接的哈希映射中影响迭代顺序的任何操作。在按插入顺序链接的哈希映射中,仅更改与映射中已包含键关联的值不是结构修改。在按访问顺序链接的哈希映射中,仅利用 get 查询映射不是结构修改。

Collection(由此类的所有 collection 视图方法所返回)的 iterator 方法返回的迭代器都是快速失败 的:在迭代器创建之后,如果从结构上对映射进行修改,除非通过迭代器自身的移除方法,其他任何时间任何方式的修改,迭代器都将抛出 ConcurrentModificationException。因此,面对并发的修改,迭代器很快就会完全失败,而不冒将来不确定的时间任意发生不确定行为的风险。

注意,迭代器的快速失败行为无法得到保证,因为一般来说,不可能对是否出现不同步并发修改做出任何硬性保证。快速失败迭代器会尽最大努力抛出 ConcurrentModificationException。因此,编写依赖于此异常的程序的方式是错误的,正确做法是:迭代器的快速失败行为应该仅用于检测程序错误。

此类是 Java Collections Framework 的成员。

分享到:
评论

相关推荐

    java hashtable实现代码

    Java中的`Hashtable`类是Java集合框架的一部分,它是一个基于哈希表的Map接口实现。这个数据结构允许我们以键值对的形式存储数据,并且提供了快速的插入、删除和查找操作。`Hashtable`是线程安全的,因此可以在多...

    实战应用Java算法分析与设计-1算计概述与抽象数据类型

    《实战应用Java算法分析与设计(链表、二叉树、哈夫曼树、图、动态规划、HashTable算法)》 课程简介: 算法分析与设计Java版,是一套实用型算法课程。通过本课程的学习,学员可以掌握以下技术点:线性结构与顺序表...

    HashMap与HashTable的区别(含源码分析)

    在Java编程语言中,`HashMap`和`HashTable`都是实现键值对存储的数据结构,但它们之间存在一些显著的区别,这些区别主要体现在线程安全性、性能、null值处理以及一些方法特性上。以下是对这两个类的详细分析: 1. ...

    易语言哈希表例程 据java的HashTable编写

    通过阅读和分析这份代码,我们可以深入了解易语言如何借鉴Java的HashTable实现哈希表,以及易语言特有的编程风格和技巧。这有助于提升我们对易语言的理解,并且能够运用到自己的项目中,优化数据存储和检索的效率。

    Java集合专题总结:HashMap 和 HashTable 源码学习和面试总结

    Java集合专题总结:HashMap和HashTable源码学习和面试总结 本文总结了Java集合专题中的HashMap和HashTable,涵盖了它们的源码学习和面试总结。HashMap是一种基于哈希表的集合类,它的存储结构是一个数组,每个元素...

    hashtable存储数据.rar

    在Java编程语言中,`Hashtable`是一个非常基础且重要的数据结构,它属于集合框架的一部分,提供了键值对(key-value pairs)的存储功能。...通过分析压缩包中的示例代码,你可以更好地掌握`Hashtable`的实际应用。

    hashtable和hashmap的区别

    接下来,我们将详细探讨`Hashtable`和`HashMap`之间的区别,并分析它们各自的优缺点。 #### 1. 线程安全性 - **Hashtable**: 是线程安全的。`Hashtable`的所有关键操作(如`put()`、`get()`)都是同步的,这意味着...

    HashMap与HashTable和HashSet的区别

    本文将重点分析这三种数据结构之间的区别,特别是针对`HashTable`不支持空键值对而`HashMap`支持这一点进行深入探讨。 #### 二、HashTable `HashTable`是基于哈希表实现的一个线程安全的`Map`容器,它不允许`key`...

    实战应用Java算法分析与设计-3图的概念以及图的邻接矩阵类实现

    《实战应用Java算法分析与设计(链表、二叉树、哈夫曼树、图、动态规划、HashTable算法)》 算法分析与设计Java版,是一套实用型算法课程。通过本课程的学习,学员可以掌握以下技术点:线性结构与顺序表、单向链表、...

    java中HashMap,LinkedHashMap,TreeMap,HashTable的区别

    本文将详细分析四种常用的`Map`实现类:`HashMap`, `LinkedHashMap`, `TreeMap`以及`HashTable`之间的区别。 #### 1. HashMap `HashMap`是一种基于哈希表实现的`Map`接口,提供了一个非同步的、允许使用`null`键和...

    Java容器之Hashtable源码分析

    【Java容器之Hashtable源码分析】 在Java编程中,`Hashtable`是一个古老的容器类,它继承自`Dictionary`接口,并实现了`Map`接口,同时也实现了`Cloneable`和`Serializable`接口。`Hashtable`与`HashMap`类似,都是...

    HashTable排序.txt

    通过上述分析,我们了解到如何使用Java对`HashTable`中的元素进行按键或值的排序。这通常涉及到将`Hashtable`的条目集转换为数组,然后使用自定义比较器对数组进行排序。这种方法不仅适用于`Hashtable`,也适用于...

    最新Java面试题视频网盘,Java面试题84集、java面试专属及面试必问课程

    │ Java面试题11.HashMap和HashTable的区别.mp4 │ Java面试题12.实现一个拷贝文件的类使用字节流还是字符串.mp4 │ Java面试题13.线程的实现方式 怎么启动线程怎么区分线程.mp4 │ Java面试题14.线程并发库和线程池...

    java中Hashtable和HashMap的区别分析

    Java中的`Hashtable`和`HashMap`都是`Map`接口的实现类,它们都用于存储键值对数据。然而,这两个类在设计和功能上有显著的差异,这些差异主要体现在线程安全性、空值处理、迭代器类型以及哈希表的初始化和扩容策略...

    java数据结构课件与分析

    本课件集合主要针对Java中的数据结构及其算法进行了深入的探讨和分析。 在"java数据结构课件与分析"中,我们可以期待学习到以下几个关键的知识点: 1. **数组**:Java中的基础数据结构,用于存储固定数量的相同...

    (转)java读取properties文件

    本篇将详细讲解如何在Java中读取`properties`文件,并通过提供的`SysPropertiesUtil.java`源代码进行分析。 1. `properties`文件简介: `properties`文件是Java中的配置文件,它以键值对的形式存储数据,键和值...

    2023黑马面试宝典-Java面试宝典大全-java面试宝典黑马

    2. **集合框架**:Java集合框架是面试的重点,包括List(ArrayList、LinkedList、Vector)、Set(HashSet、TreeSet)和Map(HashMap、TreeMap、Hashtable)。了解它们的特点、区别及应用场景,如线程安全、遍历方式...

    java HashMap和HashTable的区别详解

    Java中的HashMap和HashTable是两种常见的基于哈希表的数据结构,它们在使用场景、线程安全性、数据处理方式以及API设计等方面存在显著差异。下面将详细分析这些区别。 首先,从继承关系来看,HashMap和HashTable的...

    干货!资深java工程师面试要点大全+一年整理.pdf

    Java的JVM调优也是一个高阶知识点,它通常涉及垃圾回收(GC)、内存泄漏的分析以及性能监控等。掌握JVM调优技巧可以确保Java应用程序在高负载下依然能稳定运行。 在安全方面,SQL注入是需要掌握的知识点。为了防止...

Global site tag (gtag.js) - Google Analytics