`
greemranqq
  • 浏览: 966503 次
  • 性别: Icon_minigender_1
  • 来自: 重庆
社区版块
存档分类
最新评论

JAVA深入集合--HashTable

阅读更多

一、介绍

       Hashtable 是早期实现的一个哈希存储方式的类,也就是键值对(key-value)的存放方式。实际上市键值对 和 链表的组合,相对同步安全的。

     特点:

            1.是key-value 方式存放的,并且是无序存放的

            2.线程安全的,性能较低

            3.key 不允许重复,否者会覆盖数据

            4.key 不能为null,否者会空指针异常,

 

二、源码解析

      

public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable 

   

 

    1.继承了Dictionary,实现 Map接口,和 Cloneable,这里简单说一下。

    Dictionary:JDK 1.6阐明是一个过时的,用于存键值对的抽象父类,定义了一些基本属性,有兴趣自己看API。后来建议去实现Map 接口,而不是扩展此类,估计是面向接口编程,怕太臃肿了。只有hashtable继承,不是到有啥用,早就该去掉了。

 

    Map :作为代替Dictionary 的出现,也是定义了键值对的一些方法,好处是便于各种键值对的类自己去实现,让代码更轻了。

   Cloneable: 是克隆需要,Vactor 已经说了,就不多讲了。

 

   2.主要构造方法:

     

// 我们获得的hashtable对象,无论有参数,无参数的 最终都调用的这个构造。
// 参数说明:
// initialCapacity : 初始容量,好比新建数组,最开始的长度多少,默认11
// loadFactor:增长因子,默认是0.75f,好比是 你的内容超过了 总长度了75%,就会自动扩容
public Hashtable(int initialCapacity, float loadFactor) {
        // 初始长度小于0 必然异常
	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  实际上是  private transient Entry[] table;
        // Entry 是一个内部键值对的内部类,下面说
        // 可以看到,hashTabale 实际上就是一个  Entry[]的数组
	table = new Entry[initialCapacity];
        // 这里就是扩容的位置了,也就是会说你的数组容量超过了 下面这个数,默认就会扩容了
	threshold = (int)(initialCapacity * loadFactor);
    }

  

    3.内部类Entry 解析:

    

 private static class Entry<K,V> implements Map.Entry<K,V> {
	int hash;
	K key;
	V value;
        // 链表的动作,Map 实际有键值对和链表组成,具体的将hash算法再讨论
	Entry<K,V> next;

 

 

  这里可以看到,这里是实现了Map.Entry 接口,定义了一些泛型属性,我们先看这个接口:

// Map 里面的内部接口
interface Entry<K,V> {
	K getKey();
	V getValue();
	V setValue(V value);
	boolean equals(Object o);
	int hashCode();
    }
}

 

  

// HashTable 内部类的主要方法。
	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 ^ (value==null ? 0 : value.hashCode());
	}

    }

 

     这里可能有些疑惑,既然实现了Map 接口,里面的getKey,getValue 等方法,为什么要用内部类去实现Map 的Entry接口,从而进行实现呢?不是很麻烦吗?

     这里我的理解是:提供了一个包含键值对的映射集,这东西更加方便我们进行迭代(遍历),提供一种统一的迭代方式(Iterator),我们获得这个映射之后,可以同时获得 单个的键和值,并且迭代期间任何非自身的操作,都会报错,保证这个映射的唯一性,当然只能通过内部setValue(),进行设置值。

      同时这个键值对在其他地方 有很灵活的使用,让你获得键 值 等个各种方式,更加灵活,后面会有

      

      其实HashTbale 自身提供了 获得key 和value 的方式,请看源码:

      

// 获得values
private transient volatile Collection<V> values = null;
 

 

   

// 这是获得所有值的方式
public Collection<V> values() {
	if (values==null)
        // 这里是通过一个内部,和 当前hashMap 对象,通过collection 方法获得值的集合
        // 这里使用的是线程安全的方法
	    values = Collections.synchronizedCollection(new ValueCollection(),
                                                        this);
        return values;
    }

    private class ValueCollection extends AbstractCollection<V> {
        public Iterator<V> iterator() {
	    return getIterator(VALUES);
        }
        public int size() {
            return count;
        }
        public boolean contains(Object o) {
            return containsValue(o);
        }
        public void clear() {
            Hashtable.this.clear();
        }
    }
 

 

    顺便看看Collections.synchronizedCollection 源码:

  

static <T> Collection<T> synchronizedCollection(Collection<T> c, Object mutex) {
	return new SynchronizedCollection<T>(c, mutex);
    }

final Collection<E> c;  // Backing Collection
	final Object mutex;     // Object on which to synchronize

// 这里就只有一个赋值操作
SynchronizedCollection(Collection<E> c, Object mutex) {
	    this.c = c;
            this.mutex = mutex;
        }
 

 

   从代码上看,相当于是把 ValueCollection 这个内部类 和hashtable 传入进去赋值给对象了。

   1.ValueCollection 里面有iterator(),size() 等方法。这里相当于colleaction 方法里面有几乎所有

     集合的基本方法接口,然后实现在各个集合里面。但是集合里面定义内部类 同样调用实现那些实现方      法的作用是为了将内部类 传回给colleaction ,让集合具有一种通用的访问这些元素的方法。

   比如:Collection 定义集合可以看电影。hashtable 实现 用电脑看电影。 然后定义内部类,里面把用电脑看电影这种方式也放进去,然后可以送给Collection 集合,那么它就可以知道是用电脑看电影了。 也就是说,看电影这种举动很多集合都有,Collection 作为一种工具,只要任何其他集合传入一个方式过来,就可以调用各自的看电影的方式了。

    2.参数this,实际上是一把对锁,实现线程安全的。这里看Collection 里面对这个内部类的操作就知道

 

    

// 这是获得,key 的集合
private transient volatile Set<K> keySet = null;
// 上面的key,values
private transient volatile Set<Map.Entry<K,V>> entrySet = null;
  
 public Set<K> keySet() {
	if (keySet == null)
	    keySet = Collections.synchronizedSet(new KeySet(), this);
	return keySet;
    }

    private class KeySet extends AbstractSet<K> {
        public Iterator<K> iterator() {
	    return getIterator(KEYS);
        }
        public int size() {
            return count;
        }
        public boolean contains(Object o) {
            return containsKey(o);
        }
        // 多一个方法
        public boolean remove(Object o) {
            return Hashtable.this.remove(o) != null;
        }
        public void clear() {
            Hashtable.this.clear();
        }
    }
 

 

  这里你会发现,获得keys 和values方法几乎一样,只是这里多提供了一个remove 方法,

  那么这两个相同的方法  是如何 分别获得key 和value 的呢?请看:

  

// 这是主要不同点:
public Iterator<K> iterator() {
	    return getIterator(KEYS);
        }

public Iterator<K> iterator() {
	    return getIterator(VALUES);
        }
 // 这几个参数 对应了 ,返回的类型,表示 枚举
 private static final int KEYS = 0;
 private static final int VALUES = 1;
 private static final int ENTRIES = 2;

 

   

// 方法,发现这里其实是返回的是一个内部类, count = 0 的也是内部类,这里不介绍了 ,里面就判// 定空值的,主要看Enumerator
 private <T> Iterator<T> getIterator(int type) {
	if (count == 0) {
	    return (Iterator<T>) emptyIterator;
	} else {
	    return new Enumerator<T>(type, true);
	}
  

 

   

private class Enumerator<T> implements Enumeration<T>, Iterator<T> {
        // 里面的数组
	Entry[] table = Hashtable.this.table;
	int index = table.length;
        // 键值对方式,内部类
	Entry<K,V> entry = null;
	Entry<K,V> lastReturned = null;
        // 类型
	int type;
....
....
// 构造
Enumerator(int type, boolean iterator) {
	    this.type = type;
	    this.iterator = iterator;
	}
   
   下面继续看看 type 这里在哪使用的:
// 在 获得下一个元素的时候 就能使用了	
public T nextElement() {
	    Entry<K,V> et = entry;
	    int i = index;
            // 将当前数组 给这个变量
	    Entry[] t = table;
	    /* Use locals for faster loop iteration */
            // 循环遍历 数组,并将数组里面的Entry<K,V> 类型的元素赋值
	    while (et == null && i > 0) {
		et = t[--i];
	    }
	    entry = et;
	    index = i;
	    if (et != null) {
		Entry<K,V> e = lastReturned = entry;
                // 获得为Entry<K,V> 类型的键值对
		entry = e.next;
                // 这里就可以返回 键  值  还是键值对了。这就明白当初定义内部类 Entry好处
		return type == KEYS ? (T)e.key : (type == VALUES ? (T)e.value : (T)e);
	    }
	    throw new NoSuchElementException("Hashtable Enumerator");
	}
  
   当然还有我们常用的键值对 遍历方式:
public Set<K> keySet() {
	if (keySet == null)
	    keySet = Collections.synchronizedSet(new KeySet(), this);
	return keySet;
    }
  包括获得枚举类型key 的方式:
 public synchronized Enumeration<K> keys() {
	return this.<K>getEnumeration(KEYS);
    }
  看到这里相信你会发现,所有的都是通过 内部类Enumerator<T> 操作内部类 Entry 完成的。而提供的各种实现方式,也是通过内部类送到各种集合里面,统一的接口方法(size() 等等),这就完成了各种类型的集合,可以通过迭代器统一遍历。
   下面介绍一下hashtable 其他的常用方法源码: 
   1.put(K key,V value):存放的方法
     
 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;
        // 下面实际上通过 一些算法计算存放位置,具体hash 等算法的研究,以后专门讲解
	int hash = key.hashCode();
	int index = (hash & 0x7FFFFFFF) % tab.length;
	for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
            //  这里通过hash 和 值的比较,如果重复就覆盖
	    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;
    }
 
   2. get(Object key):通过键 获得值的方法:
  public synchronized V get(Object key) {
	Entry tab[] = table;
	int hash = key.hashCode();
        // 和上面差不多,计算位置的
        // PS:JDK 这里写得重复了, 代码还是可以提炼的嘛  呵呵
	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;
    }
 
   3.containsValue(Object value),实际就是contains(Object value)
    
public synchronized boolean contains(Object value) {
	if (value == null) {
	    throw new NullPointerException();
	}

	Entry tab[] = table;
        // 说白了就是一个Entry 内部元素遍历。简单的说 就是数组遍历啦
	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;
    }
 
   4. containsKey(Object key)
public synchronized boolean containsKey(Object key) {
	Entry tab[] = table;
	int hash = key.hashCode();
	int index = (hash & 0x7FFFFFFF) % tab.length;
        // 其实还是Entry 元素访问,数组遍历,当然这里 对key 进行了hash 比较,有些位置重复嘛
        // PS:看来重复代码比较多,hashmap 估计会好很多
	for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
	    if ((e.hash == hash) && e.key.equals(key)) {
		return true;
	    }
	}
	return false;
    }
 
  差不多 就介绍完了,其他方法,都类似的。
  总结:
  1.hashtable 是一种键值对存放的数据结构
  2.它是有一个内部类 Entry 的的形式存放,将所有元素以Entry<K,V> 方式存放在数组table里面。
  3.对集合的遍历都是通过内部类Enumerator 进行,并且Enumerator 其实操作的也是table数组,操作的也是Entry 。
  4.这里面类的设计方法 还是比较灵活,值得学习和借鉴,内部类的使用,以及接口的各种实现。
  5.PS:index 计算代码重复,而且计算方式过于简单,我们平时设计 应该设计成尽量不重复的hashcode.
至于hash 算法的研究,以后专门讲解。写得不好的地方希望,大家批评指正!小弟一定虚心修改!

      

分享到:
评论

相关推荐

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

    1.1 深入JAVA API 1.1.1 Lang包 1.1.2 集合类 1.1.2.1.1 日期类Date 1.1.2.1.2 日历类Calendar 1.1.2.1.3 随机数类Random 1.1.2.1.4 向量类Vector 1.1.2.1.5 栈类Stack 1.1.2.1.6 哈希表类Hashtable 1.1.2.1.7 ...

    尚硅谷-深入java8的集合5:Hashtable的实现原理.pdf

    ·基于JDK 11,将Java8、Java9、Java10、Java11新特性一网打尽 ·课程中,Eclipse和IDEA这两种企业一线开发环境都使用到了 3.技术讲解更深入、更全面: ·课程共30天,715个知识视频小节,涉及主流Java使用的...

    java软件技术文档-深入java8的集合5:Hashtable的实现原理.pdf

    java软件技术文档

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

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

    程序员需要经常刷题吗-simple-java-zh-CN:SimpleJava是Java常见问题的集合。中文翻译

    需要程序员经常刷题吗simple-java-zh-CN Simple Java 是 Java 常见问题的集合。中文翻译 ##1。 字符串和数组字符串...深入理解Arrays.sort(T[], Comparator &lt; ? super T &gt; c) 常见排序,Collections、Arrays、Tre

    Java工程师面试复习指南

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

    高级java笔试题-interview:采访记录

    高级java笔试题 interview 2017届春招秋招公司的面试题目 #阿里内推(Android开发) ##笔试 ####选择题: 快速排序 二叉树遍历 UML类图 ...7.java集合类,哪些线程安全,哪些线程不安全 8.线程安全

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

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

    JAVA SE学习精华集锦

    1.1 深入JAVA API 2 1.1.1 Lang包 2 1.1.2 集合类 8 1.1.2.1.1 日期类Date 9 1.1.2.1.2 日历类Calendar 10 1.1.2.1.3 随机数类Random 11 1.1.2.1.4 向量类Vector 12 1.1.2.1.5 栈类Stack 13 1.1.2.1.6 哈希表类...

    JAVA_Thinking in Java(中文版 由yyc,spirit整理).chm

    JAVA_Thinking in Java(中文版 由yyc,spirit整理).chm ------------------------------------------------- 本教程由yyc,spirit整理 ------------------------------------------------- “Thinking in Java...

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

    第三题 对比HashTable HashMap TreeMap有什么不同.pdf 第二题 Exception Error区别.pdf 第五题 如何保证集合是线程安全的.pdf 第八题 Java并发类库提供的线程池有哪几种 分别有什么特点.pdf 第六题 synchronized和...

    java 编程入门思考

    8.4.4 Hashtable 8.4.5 再论枚举器 8.5 排序 8.6 通用集合库 8.7 新集合 8.7.1 使用Collections 8.7.2 使用Lists 8.7.3 使用Sets 8.7.4 使用Maps 8.7.5 决定实施方案 8.7.6 未支持的操作 8.7.7 排序和搜索 8.7.8 ...

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

    在以后的章节中,用通俗易懂的手法,紧密联系实际应用的方式,深入浅出地讲解了多线程,常用Java类,Java中的I/O(输入输出)编程,GUI与Applet,网络编程等方面的知识。 本书许多内容都来源于程序员圈子里的非正式...

    Java初学者入门教学

    8.4.4 Hashtable 8.4.5 再论枚举器 8.5 排序 8.6 通用集合库 8.7 新集合 8.7.1 使用Collections 8.7.2 使用Lists 8.7.3 使用Sets 8.7.4 使用Maps 8.7.5 决定实施方案 8.7.6 未支持的操作 8.7.7 排序和搜索 8.7.8 ...

    java联想(中文)

    8.4.4 Hashtable 8.4.5 再论枚举器 8.5 排序 8.6 通用集合库 8.7 新集合 8.7.1 使用Collections 8.7.2 使用Lists 8.7.3 使用Sets 8.7.4 使用Maps 8.7.5 决定实施方案 8.7.6 未支持的操作 8.7.7 排序和搜索 8.7.8 ...

    疯狂JAVA讲义

    7.1 Java集合概述 241 7.2 Collection和Iterator接口 243 7.2.1 使用Iterator接口遍历集合元素 244 7.2.2 使用foreach循环遍历集合元素 246 7.3 Set接口 247 7.3.1 HashSet类 247 学生提问:hashCode方法对于...

    Thinking in Java(中文版 由yyc,spirit整理).chm

    8.4.4 Hashtable 8.4.5 再论枚举器 8.5 排序 8.6 通用集合库 8.7 新集合 8.7.1 使用Collections 8.7.2 使用Lists 8.7.3 使用Sets 8.7.4 使用Maps 8.7.5 决定实施方案 8.7.6 未支持的操作 8.7.7 排序和搜索 8.7.8 ...

    JAVA_Thinking in Java

    8.4.4 Hashtable 8.4.5 再论枚举器 8.5 排序 8.6 通用集合库 8.7 新集合 8.7.1 使用Collections 8.7.2 使用Lists 8.7.3 使用Sets 8.7.4 使用Maps 8.7.5 决定实施方案 8.7.6 未支持的操作 8.7.7 排序和搜索 8.7.8 ...

Global site tag (gtag.js) - Google Analytics