Java复习笔记第二篇--集合学习之实现原理二

引言:上篇文章中我们学习了List和Set接口下面的集合的实现原理,这篇文章我们主要来学习Map接口下面的各个集合的实现原理。

Java复习笔记第二篇--集合学习之实现原理二_第1张图片
image.png

1、HashMap

1.1、特性
  • 采用键值对存储
  • 非线程安全的集合
  • 允许值为null,只允许一个键为null
  • 可以通过Collections.synchronizedMap将其变为线程安全的集合,或者使用ConcurrentHashMap代替。
  • 初始容量为16,初始加载因子为0.75
1.2、实现原理

hash作为程序员使用最多的一种键值式的存储集合;它的底层是通过数组+链表+红黑树(JDK8优化之后);它根据键的hashCode值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定的。HashMap非线程安全,即任一时刻可以有多个线程同时写HashMap,可能会导致数据的不一致。如果需要满足线程安全,可以用 Collections的synchronizedMap方法使HashMap具有线程安全的能力,或者使用ConcurrentHashMap。

  • a、hashMap 的实例有两个参数影响其性能:初始容量和加载因子
  • b、存储数据的格式:通过一个静态内部类Entry存储数据;


    Java复习笔记第二篇--集合学习之实现原理二_第2张图片
    hashMap的存储形式.jpg
static class Entry implements Map.Entry{
}
  • c、加载因子:默认为0.75;
    主要在扩容时起作用;一般也是扩容为原来的2倍
 void resize(int newCapacity) {
        Entry[] oldTable = table;
      //获取扩容之前的容量
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }
        Entry[] newTable = new Entry[newCapacity];
        //将原来数组的元素拷贝到新的元素中;
        transfer(newTable, initHashSeedAsNeeded(newCapacity));
        table = newTable;
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    }
  • d、关于hash函数:
    hashMap在存储数据时会先根据它的key值调用一次hash函数;将得到的值作为真实的存储数据的键;当然不排除两个不同的key值产生相同的键值情况;我们称之为hash冲突;常见的解决hash冲突的方式主要有如下:
    • 拉链法:将冲突的节点组织成一条链表
    • 二次hash法:采用另外的散列函数对冲突结果进行处理的方法
    • 开发地址法:
      • 线性探测:从冲突的位置依次向下移动;
        D = H(key);
        ND = (D+di)%m; di取1,2,3,……,m-1
      • 二次探测:
        D = H(key);
        ND = (D+di)%m; di取11,-11,22,-22,……,KK,-KK  (K≤m/2)
      • 双散列法
        D = H1(key);
        p = H2(key);
        ND = (D+p)%m;
int hash = hash(key);

和之前一样,为了方便深入理解hashmap这个数据结构,我们还是针对它的添加元素和删除元素的方法进行详细探讨:
插入元素:

 public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
 }
 /**
     * Implements Map.put and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent if true, don't change existing value
     * @param evict if false, the table is in creation mode.
     * @return previous value, or null if none
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node[] tab; Node 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 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)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;
    }
Java复习笔记第二篇--集合学习之实现原理二_第3张图片
HashMap的put函数.png

①.判断键值对数组table[i]是否为空或为null,否则执行resize()进行扩容;

②.根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建节点添加,转向⑥,如果table[i]不为空,转向③;

③.判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,否则转向④,这里的相同指的是hashCode以及equals;

④.判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对,否则转向⑤;

⑤.遍历table[i],判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作;遍历过程中若发现key已经存在直接覆盖value即可;

⑥.插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold,如果超过,进行扩容。

删除元素

    public V remove(Object key) {
        Node e;
        return (e = removeNode(hash(key), key, null, false, true)) == null ?
            null : e.value;
    }
 /**
     * Implements Map.remove and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to match if matchValue, else ignored
     * @param matchValue if true only remove if value is equal
     * @param movable if false do not move other nodes while removing
     * @return the node, or null if none
     */
    final Node removeNode(int hash, Object key, Object value,
                               boolean matchValue, boolean movable) {
        Node[] tab; Node p; int n, index;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (p = tab[index = (n - 1) & hash]) != null) {
            Node node = null, e; K k; V v;
    //判断链表对应的位置是否为null        
    if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                node = p;
      //是否已经形成链表
            else if ((e = p.next) != null) {
//是否已经行为树形结构
                if (p instanceof TreeNode)
                    node = ((TreeNode)p).getTreeNode(hash, key);
                else {
                    do {
                        if (e.hash == hash &&
                            ((k = e.key) == key ||
                             (key != null && key.equals(k)))) {
                            node = e;
                            break;
                        }
                        p = e;
                    } while ((e = e.next) != null);
                }
            }
            if (node != null && (!matchValue || (v = node.value) == value ||
                                 (value != null && value.equals(v)))) {
                if (node instanceof TreeNode)
                    ((TreeNode)node).removeTreeNode(this, tab, movable);
                else if (node == p)
                    tab[index] = node.next;
                else
                    p.next = node.next;
                ++modCount;
                --size;
                afterNodeRemoval(node);
                return node;
            }
        }
        return null;
    }

2、TreeMap

2.1、特性
  • 插入的元素不能重复
  • 插入的元素默认按照key有序(默认按照升序)
2.2、实现原理

底层通过红黑树实现的,

  static final class Entry implements Map.Entry {
        K key;
        V value;
        Entry left; // 左孩子
        Entry right;//右孩子
        Entry parent;// 父亲节点
        boolean color = BLACK;

和之前一样我们还是来看看TreeMap的添加元素和删除元素的方法来了解TreeMap的实现原理

添加元素:在树种找到要插入的位置,即该节点的父亲节点,然后根据comp的具体值决定是要插入到left还是right

    public V put(K key, V value) {
        Entry t = root;
        if (t == null) {
            compare(key, key); // type (and possibly null) check
            root = new Entry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
        int cmp;
        Entry parent;
        // split comparator and comparable paths
        Comparator cpr = comparator;
        if (cpr != null) {
            do {
                parent = t;
                cmp = cpr.compare(key, t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        else {
            if (key == null)
                throw new NullPointerException();
            @SuppressWarnings("unchecked")
                Comparable k = (Comparable) key;
            do {
                parent = t;
                cmp = k.compareTo(t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        Entry e = new Entry<>(key, value, parent);
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
        fixAfterInsertion(e);
        size++;
        modCount++;
        return null;
    }

删除节点:本质上就是红黑树的节点删除操作,想要理解TreeMap的删除操作最好深入理解红黑树的删除操作

    public V remove(Object key) {
        Entry p = getEntry(key);
        if (p == null)
            return null;

        V oldValue = p.value;
        deleteEntry(p);
        return oldValue;
    }

    /**
     * Delete node p, and then rebalance the tree.
     */
    private void deleteEntry(Entry p) {
        modCount++;
        size--;

        // If strictly internal, copy successor's element to p and then make p
        // point to successor.
        if (p.left != null && p.right != null) {
            Entry s = successor(p);
            p.key = s.key;
            p.value = s.value;
            p = s;
        } // p has 2 children

        // Start fixup at replacement node, if it exists.
        Entry replacement = (p.left != null ? p.left : p.right);

        if (replacement != null) {
            // Link replacement to parent
            replacement.parent = p.parent;
            if (p.parent == null)
                root = replacement;
            else if (p == p.parent.left)
                p.parent.left  = replacement;
            else
                p.parent.right = replacement;

            // Null out links so they are OK to use by fixAfterDeletion.
            p.left = p.right = p.parent = null;

            // Fix replacement
            if (p.color == BLACK)
                fixAfterDeletion(replacement);
        } else if (p.parent == null) { // return if we are the only node.
            root = null;
        } else { //  No children. Use self as phantom replacement and unlink.
            if (p.color == BLACK)
                fixAfterDeletion(p);

            if (p.parent != null) {
                if (p == p.parent.left)
                    p.parent.left = null;
                else if (p == p.parent.right)
                    p.parent.right = null;
                p.parent = null;
            }
        }
    }

参考文献:
链接:https://zhuanlan.zhihu.com/p/21673805
http://alex09.iteye.com/blog/539549

你可能感兴趣的:(Java复习笔记第二篇--集合学习之实现原理二)