`
yuanc00
  • 浏览: 29389 次
社区版块
存档分类
最新评论

Java HashTable学习

    博客分类:
  • java
阅读更多

注:这里使用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集合专题总结:HashMap 和 HashTable 源码学习和面试总结

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

    使用JavaScript数组模拟Java的Hashtable集合

    因为,JavaScript的数组非常特殊,而且如果你能够理解它,那么对于我们学习JSON对象语法就非常容易理解了--因为JSON就是一个数组--我们也可以把它看成一个Hashtable集合对象!本人认为,理解JavaScript的数组是学习...

    JAVA SE学习精华集锦

    1.5 J2SE学习中的30个基本概念 58 1.6 Java线程 60 1.7 Java 5.0多线程编程 65 1.8 Java Socket编程 80 1.9 Java的内存泄漏 85 1.10 抽象类与接口的区别 86 1.11 Java变量类型间的相互转换 87 2 JAVA与WEB 87 2.1 ...

    【Java面试+Java学习指南】 一份涵盖大部分Java程序员所需要掌握的核心知识

    Java集合详解4:HashMap和HashTable Java集合详解5:深入理解LinkedHashMap和LRU缓存 Java集合详解6:TreeMap和红黑树 Java集合详解7:HashSet,TreeSet与LinkedHashSet Java集合详解8:Java集合类细节精讲 JavaWeb

    Java 集合学习指南 - v1.1.pdf

    Java的集合类总结,包括HashMap、HashSet、HashTable、LinkedHashMap、LinkedHashSet、ArrayList、LinkedList、ConcurrentHashMap的实现原理,很详实,面试的话可以认真看看

    Java工程师面试复习指南

    【Java工程师面试复习指南】本仓库架构大部分Java工程师所需要掌握的核心知识,整合了互联网上的很多优质Java技术文章,力求打造为最完整最实用的...Java集合详解:HashMap和HashTable Java集合详解:深入理解LinkedHas

    java summary(java笔记)

    学习java的一些笔记和个人总结 9、Collection 和 Collections的区别。  Collection是集合类的上级接口,继承与他的接口主要有Set 和List.。Collections是针对集合类的一个帮助类,他提供一系列静态方法实现对各种...

    (超赞)JAVA精华之--深入JAVA API

    1.5 J2SE学习中的30个基本概念 1.6 Java线程 1.7 Java 5.0多线程编程 1.8 Java Socket编程 1.9 Java的内存泄漏 1.10 抽象类与接口的区别 1.11 Java变量类型间的相互转换 2 JAVA与WEB 2.1 JMX规范 2.1.1 JMX概述 ...

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

    通过本课程的学习,学员可以掌握以下技术点:线性结构与顺序表、单向链表、循环链表、栈的基本概念、链式堆栈、中缀表达式、队列、链式队列、串、MyString、Brute-Force算法、MySet类实现、矩阵类、递归算法、哈夫曼...

    动力节点_Java基础视频教程139_HashTable与HashMap的异同点

    动力节点的Java课程适合绝对零基础的观看,教程中讲解了Java开发环境搭建、Java的基础语法、Java的面向对象。每一个知识点都讲解的非常细腻,由浅入深。适合非计算机专业,想转行做Java开发的朋友,或者想让Java基础...

    java面试笔试资料包括JAVA基础核心知识点深度学习Spring面试题等资料合集.zip

    java面试笔试资料包括JAVA基础核心知识点深度学习Spring面试题等资料合集: JAVA核心知识点整理-282页 Java与哈希算法.docx Java中Lambda表达式的使用.docx JAVA多线程之线程间的通信方式.docx Java注解详解.docx ...

    java8源码-JavaRobot:Java学习笔记,JavaLearningNote

    学习笔记(持续更新中) 所有文章均同步发布到微信公众号【JavaRobot】,关注微信公众号,及时得到文章推送,谢谢支持。 说明:如无特别说明,所有代码都基于JDK8 JavaSE(Java基础) Java Core 关键字 synchronized...

    张孝祥Java就业培训教程.pdf

    本书不仅全面的介绍了Java语言本身,最重要还交会读者去掌握编程思想,找到编程感觉,而不是死记硬背语言本身,书中涉及到的应用问题分析,远远超了一个Java程序员在学习和应用Java过程中所有可能碰到的问题。...

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

    通过本课程的学习,学员可以掌握以下技术点:线性结构与顺序表、单向链表、循环链表、栈的基本概念、链式堆栈、中缀表达式、队列、链式队列、串、MyString、Brute-Force算法、MySet类实现、矩阵类、递归算法、哈夫曼...

    java 编程入门思考

    2. Java的学习 3. 目标 4. 联机文档 5. 章节 6. 练习 7. 多媒体CD-ROM 8. 源代码 9. 编码样式 10. Java版本 11. 课程和培训 12. 错误 13. 封面设计 14. 致谢 第1章 对象入门 1.1 抽象的进步 1.2 对象的接口 1.3 ...

    疯狂JAVA讲义

    学生提问:老师,我想学习Java编程,到底是学习Eclipse好呢,还是学习JBuilder好呢? 21 1.9 本章小结 22 本章练习 22 第2章 理解面向对象 23 2.1 面向对象 24 2.1.1 结构化程序设计简介 24 2.1.2 程序的三种...

    Java初学者入门教学

    2. Java的学习 3. 目标 4. 联机文档 5. 章节 6. 练习 7. 多媒体CD-ROM 8. 源代码 9. 编码样式 10. Java版本 11. 课程和培训 12. 错误 13. 封面设计 14. 致谢 第1章 对象入门 1.1 抽象的进步 1.2 对象的接口 1.3 ...

    java联想(中文)

    2. Java的学习 3. 目标 4. 联机文档 5. 章节 6. 练习 7. 多媒体CD-ROM 8. 源代码 9. 编码样式 10. Java版本 11. 课程和培训 12. 错误 13. 封面设计 14. 致谢 第1章 对象入门 1.1 抽象的进步 1.2 对象的接口 1.3 ...

    Java并发编程(学习笔记).xmind

    Hashtable 实现线程安全的方式 将状态封装起来,对每个公有方法都进行同步 存在的问题 复合操作 修正方式 客户端加锁 迭代器 并发容器 ConcurrentHashMap 用于...

    JAVA_Thinking in Java

    2. Java的学习 3. 目标 4. 联机文档 5. 章节 6. 练习 7. 多媒体CD-ROM 8. 源代码 9. 编码样式 10. Java版本 11. 课程和培训 12. 错误 13. 封面设计 14. 致谢 第1章 对象入门 1.1 抽象的进步 1.2 对象的接口 1.3 ...

Global site tag (gtag.js) - Google Analytics