HashTable源码

来源:互联网 发布:提高数据运用灵活度 编辑:程序博客网 时间:2024/05/16 14:46

一直写一些工作日常笔记,最近想静下来看下一些常见的知识原理,在很多blog上都有看到过,不过看到的东西是别人的,只有自己去看了源码,写了demo,理解了才是自己的东西。

hashTable(jdk1.7)

定义:
继承与Dictionary,实现了Map的一些方法,标记了这个对象Clone,以及序列化Serializable

public class Hashtable<K,V>    extends Dictionary<K,V>    implements Map<K,V>, Cloneable, java.io.Serializable

构造器及初始化:
不传任何参数的构造器,系统默认容量为11,hash因子0.75

new Hashtable();//this(11, 0.75f);

传入容量大小的initialCapacity,

Hashtable(int initialCapacity) {        this(initialCapacity, 0.75f);    }

传入容量(initialCapacity)和hash因子(loadFactor)

 new Hashtable(int initialCapacity, float loadFactor) 
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);        //容量为0,设置一个容量为,保证传入不是IsEmpty        if (initialCapacity==0)            initialCapacity = 1;        this.loadFactor = loadFactor;        //创建一个Entry数组,Entry是一个单链表结构        table = new Entry[initialCapacity];        //设置一个阀值threshold=容量*hash因子,超过这个阀值,需要对数组扩容        threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);        initHashSeedAsNeeded(initialCapacity);}

构造其中主要是初始化容量值,hash因子,threshold,创建一个单链表对象数组。还看到一个initHashSeedAsNeeded方法,这个方法主要初始化hashSeed参数,其中hashSeed用于计算key的hash值,它与key的hashCode进行按位异或运算。这个hashSeed是一个与实例相关的随机值,主要用于解决hash冲突。

重要方法:

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;        //使用hashSend与运算key.hashCode        int hash = hash(key);        //hash & 0x7FFFFFFF防止hash为负值吧         //这里使用除法散列        int index = (hash & 0x7FFFFFFF) % tab.length;        for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {        //遇到hash值相同的插入,使用单链表连接起来,解决hash冲突            if ((e.hash == hash) && e.key.equals(key)) {                V old = e.value;                e.value = value;                return old;            }        }       //modCount没修改一次计数加1,与添加数量无关,用于遍历时候检查操作合法性        modCount++;        if (count >= threshold) {            // Rehash the table if the threshold is exceeded            //重hash 数组中元素超过阀值,需要再次计算容量,扩容数组            rehash();            tab = table;            hash = hash(key);            index = (hash & 0x7FFFFFFF) % tab.length;        }        // Creates the new entry.每次扩容创建新数组,开销比较大,所以给定好初始容量比较重要,减少数组扩容        Entry<K,V> e = tab[index];        tab[index] = new Entry<>(hash, key, value, e);        count++;        return null;    }

put方法比较简单,就是先对key值hash然后与hashSend这个随机值与运行,这样做的好处是减少hash重复。然后使用除法散列法,把对象放入到entry数组,如果遇到hash值重复,使用单链表把相同hash值的元素连接起来。但数组达到阀值(threshold),对创建一个新数组进行扩容。
接下来看下rehash

protected void rehash() {        int oldCapacity = table.length;        Entry<K,V>[] oldMap = table;        // overflow-conscious code        //计算新数组容量,左移1位,new=2*old+1        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<K,V>[] newMap = new Entry[newCapacity];        modCount++;        //重新生成阀值        threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);        boolean rehash = initHashSeedAsNeeded(newCapacity);        table = newMap;        //将原来的元素拷贝到新的HashTable中          for (int i = oldCapacity ; i-- > 0 ;) {            for (Entry<K,V> old = oldMap[i] ; old != null ; ) {                Entry<K,V> e = old;                old = old.next;                if (rehash) {                    e.hash = hash(e.key);                }                int index = (e.hash & 0x7FFFFFFF) % newCapacity;                e.next = newMap[index];                newMap[index] = e;            }        }    }

接下看看putAll,这个方法没什么说的,遍历传入集合,最终还是调用put方法

    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());    }

get方法 先hash转入key,然后找到对应的index,在entry数组中找到对应的entry对象,再通过hash值和key比较找到想要的对象值返回

    public synchronized V get(Object key) {        Entry tab[] = table;        int hash = hash(key);        int index = (hash & 0x7FFFFFFF) % tab.length;        for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {            //hash重复,判断key是否同一个对象            if ((e.hash == hash) && e.key.equals(key)) {                return e.value;            }        }        return null;    }

看完获取,添加元素后,再来看看删除
同样先hash转入key,然后找到对应的index,在entry数组中找到对应的entry对象,再通过hash值和key比较找到想要的对象,如果当前entry没有prev,直接把该对象的next指向tab[index],断开该对象的链表;
这里写图片描述
如果该对象有prev,和next就把该对象的prev.next指向该对象的next

这里写图片描述

    public synchronized V remove(Object key) {        Entry tab[] = table;        int hash = hash(key);        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;    }

clear没什么可说的 把所有的元素置null

   public synchronized void clear() {        Entry tab[] = table;        modCount++;        for (int index = tab.length; --index >= 0; )            tab[index] = null;        count = 0;    }

迭代器Enumeration:
hashTable使用的是Enumeration迭代器接口
 Enumeration接口是JDK1.0时推出的,最早使用Vector时就是使用Enumeration接口进行输出。虽然Enumeration是一个旧的类,但是在JDK1.5之后为Enumeration类进行了扩充,增加了泛型的操作应用。

 Enumeration接口常用的方法有hasMoreElements()(判断是否有下一个值)和 nextElement()(取出当前元素),这些方法的功能跟Iterator类似,只是Iterator中存在删除数据的方法,而此接口不存在删除操作。

HashTable与HashMap的区别

第一:我们从他们的定义就可以看出他们的不同,HashTable基于Dictionary类,而HashMap是基于AbstractMap。
(Dictionary是什么?它是任何可将键映射到相应值的类的抽象父类,而AbstractMap是基于Map接口的骨干实现,它以最大限度地减少实现此接口所需的工作。)

第二:HashMap可以允许存在一个为null的key和任意个为null的value,但是HashTable中的key和value都不允许为null。
第三:迭代器不同,hashTable使用的比较古老的Enumeration,而hashMap使用的是Interator
第四:Hashtable的方法是同步的,而HashMap的方法不是。所以有人一般都建议如果是涉及到多线程同步时采用HashTable,没有涉及就采用HashMap,

0 0