工作笔记-【Hashtable】和【HashMap】,源码分析

本文详细对比了Java中Map接口的三种实现类:Hashtable,HashMap和ConcurrentHashMap。分别从线程安全性、对null的支持、哈希算法、数据结构、扩容策略等方面进行了深入解析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

花点时间通过读写方法对Map接口的这三个实现类做出比较:Hashtable,HashMap,ConcurrentHashMap

Hashtable是比较老的实现,HashMap对Hashtable做了优化,ConcurrentHashMap是HashMap的高并发实现。

以Put方法为例,以下是三者的实现及部分注释。

HashMap

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        /*获取Map容量,未初始化(tab==null)则进行初始化*/
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        /*通过hash与容量tabsize做与(&)运算获取index*/
        /*此处与Hashtable不同,后者是取模运算,效率低一些*/
        /*此时如果index处没有数据,则直接创建节点插入*/
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            /*比较index处的头节点(hash、key)*/
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            /*在同一节点处挂的数据达到一定数量时会转化为树结构*/
            /*向树中插入一个节点*/
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            /*遍历联表找到尾结点*/
            /*将数据节点插入到链表尾部或者覆盖节点数据*/
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            /*返回插入结果*/
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        /*如果达到最大容量则扩容,每次扩容为之前2倍*/
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

 

可以看出HashMap的主要特点

1. 非线程安全

2. 允许key和value的null值

3. 对Key的哈希值做哈希扩散/二次哈希,减少哈希碰撞

4. 节点数据超过1个使用单向链表存储,超过8个转化为红黑树(JDK1.8优化)。

5. 达到最大容量则扩容,容量为之前2倍。

6. 初始容量为16。

Hashtable

    /*对整个方法做同步*/
    public synchronized V put(K key, V value) {
        /*value不允许为null*/
        if (value == null) {
            throw new NullPointerException();
        }

        Entry<?,?> tab[] = table;
        /*计算key的hash值*/
        int hash = key.hashCode();
        /*将hash值对容量取模运算*/
        int index = (hash & 0x7FFFFFFF) % tab.length;
        @SuppressWarnings("unchecked")
        Entry<K,V> entry = (Entry<K,V>)tab[index];
        /*遍历链表找到要覆盖的值*/
        for(; entry != null ; entry = entry.next) {
            if ((entry.hash == hash) && entry.key.equals(key)) {
                V old = entry.value;
                entry.value = value;
                return old;
            }
        }
        
        /*没找到值,将值插入到链表尾部*/
        addEntry(hash, key, value, index);
        return null;
    }


    private void addEntry(int hash, K key, V value, int index) {
        modCount++;

        Entry<?,?> tab[] = table;
        /*如果容量超限,则扩容*/
        if (count >= threshold) {
            // Rehash the table if the threshold is exceeded
            rehash();

            tab = table;
            hash = key.hashCode();
            index = (hash & 0x7FFFFFFF) % tab.length;
        }

        // Creates the new entry.
        @SuppressWarnings("unchecked")
        Entry<K,V> e = (Entry<K,V>) tab[index];
        tab[index] = new Entry<>(hash, key, value, e);
        count++;
    }

可以看出Hashtable有以下特点:

1. 线程安全,对整个Put方法同步,写效率较低。

2. 不接受null值

2. 直接哈希并且取模运算找index,比较老的实现,效率不高。

3. 节点数据超过1个使用单向链表保存。

4. 达到最大容量时扩容,扩容后的数据位之前2倍+1。

5. 初始容量为默认11,负载因子0.75. 和HashMap和ConcurrentHashMap不同的是,Hashtable容量可以是任意正整数的值,而另外两个则必须是2的幂数。

ConcurrentHashMap

final V putVal(K key, V value, boolean onlyIfAbsent) {
        if (key == null || value == null) throw new NullPointerException();
        /*spread做哈希扩散,减少哈希碰撞*/
        int hash = spread(key.hashCode());
        int binCount = 0;
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            /*初始容量为16*/
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
            /*插入新节点,作为表头,加乐观锁(CAS)*/
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))
                    break;                   // no lock when adding to empty bin
            }
            /*正在进行rehash*/
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            else {
                V oldVal = null;
                /*对单条数据加锁*/
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        /*hash值大于0,链表类型,遍历新增数据或者覆盖旧数据*/
                        if (fh >= 0) {
                            binCount = 1;
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek;
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    oldVal = e.val;
                                    if (!onlyIfAbsent)
                                        e.val = value;
                                    break;
                                }
                                Node<K,V> pred = e;
                                if ((e = e.next) == null) {
                                    pred.next = new Node<K,V>(hash, key,
                                                              value, null);
                                    break;
                                }
                            }
                        }
                        /*红黑树,遍历插入新及诶单或者覆盖旧数据*/
                        else if (f instanceof TreeBin) {
                            Node<K,V> p;
                            binCount = 2;
                            if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                           value)) != null) {
                                oldVal = p.val;
                                if (!onlyIfAbsent)
                                    p.val = value;
                            }
                        }
                    }
                }
                if (binCount != 0) {
                    /*大于阈值(8),转化为树*/
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        addCount(1L, binCount);
        return null;
    }

 

ConcurrentHashMap是HashMap的高并发实现,除HashMap的特性以外,有以下特点:

1. 线程安全,对单条数据同步,效率高

2. 不接受null值

三者的写操作逻辑区别不大,都是通过哈希值确定节点索引再遍历搜索,仍然是哈希算法和遍历的数据结构方面的区别。

 

此外还有几个相关的Map类型

  • CheckedMap, 对Map的封装,指定和检查传入的参数类型
  • IdentifyHashMap, 使用“引用相等”而非“对象相等”
  • LinkedHashMap,记录元素顺序,可实现LRU,内部使用双向链表,有性能损耗
  • SynchronizedMap,HashMap的同步包装类
  • UnModifiableMap,对Map的只读封装
  • WeakHashMap,存储弱键,内部有个ReferenceQueue,保存被回收的Key,从Map中移除

写的比较简单,仅做记录。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值