Hashtable和HashMap(及扩展知识)

首先我们看一下java中的集合类,其类图如下


另外:
Hashtable和HashMap继承的类不同,其源码如下:
public class Hashtable
    extends Dictionary
    implements Map, Cloneable, java.io.Serializable
public class HashMap
    extends AbstractMap
    implements Map, Cloneable, Serializable

有关区别可以总结如下:
(1)Hashtable是线程同步的;key允许null,value不允许null;继承类Dictionary; ​HashTable中hash数组默认大小是11,增加的方式是 old*2+1;
(2)HashMap是非线程同步的;key和value允许null;继承类AbstractMap;HashMap中hash数组的默认大小是16,而且一定是2的指数;
注:执行效率:HashMap>HashTable(因为非线程同步执行效率较高点)

拓展点:
        哈希表的实现原理
        哈希表底层主要用数组和链表实现。
        首先看哈希表的工作原理图:



hashcode
        hashcode是一个int整数,对应哈希表底层的下标。
hashCode()函数的设计用于生成hashcode,设计原则应该尽可能的生成不重复的hashcode值,从而避免冲突。

首先可以看一下String类的hashCode()函数

  public int hashCode() {
        int h = hash;
        if (h == 0 && value. length > 0) {
            char val[] = value;
            for ( int i = 0; i < value. length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

其主要的数学公式是: S[0]*31n-1+S[1] *31 n-2 +S[2] *31 n-3 +…+S[n-1]

举例String str="abc";

hashcode值为:a*31*31+b*31+c

注:为什么是31? 31为素数,是个神奇的数字,因为任何数n * 31都可以被JVM优化为 (n << 5) -n,移位和减法的操作效率要比乘法的操作效率高的多。


HashTable和HashMap的数据存储采用的是哈希表的结构

    现摘抄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 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 e = tab[index];
    tab[index] = new Entry(hash, key, value, e);
    count++;
    return null;
  }

其中关键点:

(1)对hashcode冲突的情况,采用链表的方式解决。可以关注代码

 for (Entry 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;
      }
    }

        其中这段代码含义:判断当前确定的索引位置是否存在相同hashcode和相同key的元素,如果存在相同的hashcode和相同的key的元素,那么新值覆盖原来的旧值,并返回旧值。 

                              如果存在相同的hashcode,那么他们确定的索引位置就相同,这时判断他们的key是否相同,如果不相同,这时就是产生了hash冲突。 

                                        Hash冲突后,那么HashMap的单个bucket里存储的不是一个 Entry,而是一个 Entry 链。 

                                       系统只能必须按顺序遍历每个 Entry,直到找到想搜索的 Entry 为止——如果恰好要搜索的 Entry 位于该 Entry 链的最末端(该 Entry 是最早放入该 bucket 中), 

                                       那系统必须循环到最后才能找到该元素。

    当put时候解决冲突:tab[index]=new Entry(hash, key, value, e);会调用函数

 protected Entry(int hash, K key, V value, Entry<K,V> next) {
            this.hash = hash;
            this.key =  key;
            this.value = value;
            this.next = next;
        }

       系统总是将新添加的 Entry 对象放入 table 数组的 bucketIndex 索引处——如果 bucketIndex 索引处已经有了一个 Entry 对象,那新添加的 Entry 对象指向原有的 Entry 对象(产生一个 Entry 链), 如果 bucketIndex 索引处没有 Entry 对象,也就是上面程序代码的 e 变量是 null,也就是新放入的 Entry 对象的next指向 null,也就是没有产生 Entry 链。

(2)数组在扩容后,所有元素的hashcode重新计算(因为数组的容量改变了),元素对应的index也改变。
if (count >= threshold) {
      // Rehash the table if the threshold is exceeded
      rehash();
      tab = table;
      index = (hash & 0x7FFFFFFF) % tab.length;
    }

 在Hashtable中的index计算方法:
        <1>如果key是String类型则调用 String类的hashCode()函数获得hashcode;若key为Object对象,则调用其物理地址(Object对象的hashcode就是它的物理地址)进行一定的位运算获得对应hashcode。
 private int hash(Object k) {
        if (useAltHashing) {
            if (k.getClass() == String.class) {
                return sun.misc.Hashing.stringHash32((String) k);
            } else {
                int h = hashSeed ^ k.hashCode();
                // This function ensures that hashCodes that differ only by
                // constant multiples at each bit position have a bounded
                // number of collisions (approximately 8 at default load factor).
                h ^= (h >>> 20) ^ (h >>> 12);
                return h ^ (h >>> 7) ^ (h >>> 4);
             }
        } else  {
            return k.hashCode();
        }
    }

         <2>hashcode%容量即可得index,具体函数为
        int hash = key . hashCode ();  
        int index = ( hash & 0x7FFFFFFF ) % tab . length ;
                  其中关注一个特殊处理hash&0x7FFFFFFF,主要用于hash取整数。

注:Hashtable和HashMap扩容的小知识点
        HashTable中hash数组默认大小是11,增加的方式是 old*2+1;HashMap中hash数组的默认大小是16,而且一定是2的指数;
        扩容的临界点是加载因子loadFactor>0.75,其中loadFactor=size/capaticy。

 最后附上摘自jdk中的集合类的源码:http://download.csdn.net/detail/otengyue/7704807


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值