• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

javaHashMap和ConcurrentHashMap实现原理,LinkedHashMap和TreeMap有序

武飞扬头像
Leo Han
帮助1

在java中,HashMap底层是通过 数组 链表实现的,如果当某个链表元素个数超过8个,则链表转化为红黑树。

学新通

其底层类似上述结构,一层采用数组结构,每个数组里面都是一个链表,当我们进行put的时候,计算key的hash值,和数组的长度-1取余得到待添加元素在数组中的位置,然后看该位置是否有元素,如果没有元素,那么作为链表的头加入,如果有,加入到已有的链表中,如果链表的元素大于等于8个,这时候将链表转换为红黑树,如果元素个数小于等于6个,会从红黑树退化为链表

 transient Node<K,V>[] table;

static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
        TreeNode<K,V> parent;  // red-black tree links
        TreeNode<K,V> left;
        TreeNode<K,V> right;
        TreeNode<K,V> prev;    // needed to unlink next upon deletion
        boolean red;
        TreeNode(int hash, K key, V val, Node<K,V> next) {
            super(hash, key, val, next);
        }
        .....
}
static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;

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

学新通

如上,Node代表的是普通链表,而TreeNode则是HashMap中红黑树的实现。
另外在HashMap中有一个比较重要的属性就是扩充因子,表示的是当Map中的容量到达初始容量的多少进行扩容,默认情况下,扩充因子是0.75,初始大小是16 ,当map大小超过容量的0.75时,会进行扩容,每次对之前容量*2 进行扩容,即: 16 ,32,64
如下是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;
        if ((tab = table) == null || (n = tab.length) == 0)
        	// 初始化数组
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
        	// 对应位置上没有元素,直接加入
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            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;
        // 超过容量限定,进行扩容
        if (  size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }
学新通

当使用红黑树进行插入的时候,我们知道红黑树是有序的,默认是使用hashCode来进行排序的,如果hashCode相同的话,那么还会判断Key是否是Comparable,如果不是,那么会判断二者是否是同一个类型,如果还不行,则调用System.identityHashCode来进行判断

JDK8中的HashMap采用了红黑树,避免了之前JDK7中使用普通链表当元素碰撞导致链表过长查询慢问题

HashMap是非线程安全的,当多线程访问的时候,会出现问题,基于此java提供了ConcurrentHashMap可以并发操作的Map。java本身也提供了HashTable,是一个线程安全的Map,但是HashTable在并发的情况下加锁的粒度比较大是整个Map,并发量会下降。

而ConcurrentHashMap则是根据HashMap底层数据结构的特点采用了分段上锁的逻辑,我们知道,当多个线程进行put的时候,每个key最终会落到数组其中一个元素上,如果多个线程每个操作的都是不同的数组元素,那么不存在竞争关系,那么只要每次操作对单个数组元素加锁,如果需要扩充Map,那么全局加锁即可。
ConcurrentHashMap的处理逻辑大致如上,上锁是结合CAS Synchronized锁机制。

final V putVal(K key, V value, boolean onlyIfAbsent) {
        if (key == null || value == null) throw new NullPointerException();
        int hash = spread(key.hashCode());
        int binCount = 0;
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            	// 数组位置没有元素,通过cas设置头结点
                if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))
                    break;                   // no lock when adding to empty bin
            }
            else if ((fh = f.hash) == MOVED)
            	// map在扩容,这里也会帮助扩容,思路和put差不多,只处理单个数组元素
                tab = helpTransfer(tab, f);
            else {
                V oldVal = null;
                // 已经存在链表,通过synchronized 上锁处理
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        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) {
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        addCount(1L, binCount);
        return null;
    }
学新通

可以看到,在对应位置没有元素时是通过cas来设置,但是存在元素插入的时候,则是通过synchronized的,这主要是,没有元素的时候设置是一个比较快的过程,这时候如果存在并发,通过cas,并发线程不会自旋等待很久,但是如果有元素进行插入的时候,尤其如果是红黑树还需要进行旋转,这时候如果继续cas空转等待的话,浪费cpu和时间,所以直接通过synchronized来上锁,如果等待时间比较长,锁升级为重量级锁,线程直接阻塞出让CPU。

对于初始化数组,直接通过cas来进行设置:

private final Node<K,V>[] initTable() {
        Node<K,V>[] tab; int sc;
        while ((tab = table) == null || tab.length == 0) {
            if ((sc = sizeCtl) < 0)
                Thread.yield(); // lost initialization race; just spin
            else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                try {
                    if ((tab = table) == null || tab.length == 0) {
                        int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                        @SuppressWarnings("unchecked")
                        Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                        table = tab = nt;
                        sc = n - (n >>> 2);
                    }
                } finally {
                    sizeCtl = sc;
                }
                break;
            }
        }
        return tab;
    }
学新通

有序Map

java中提供了两个有序Map,分别是TreeMap和LinkedHashMap。

TreeMap底层并没有使用数组加链表,而是直接使用红黑树,插入的时候通过key比较大小直接插入到红黑树中,而在get时,则直接搜索红黑树获得。

LinkedHashMap是直接继承自HashMap,但是其内部结构式基于HashMap.Node扩展了一个LinkedHashMap.Entry结构,这个结构增加了before和after节点,表示每个节点前驱后后继节点,变为了一个双向链表。

在LinkedHashMap内部有一个属性accessOrder如果设置为true,则会按照数据访问顺序进行排序,而如果为false,则是按照数据插入的顺序排序

LinkedHashMap对于迭代数据时,自己内部实现相关迭代逻辑,是按照链表的顺序进行迭代,因而能够有序。

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgfgjbb
系列文章
更多 icon
同类精品
更多 icon
继续加载