JDK集合源码之解析TreeMap(一)

目录
  • 简介
  • 继承体系
  • 存储结构
  • 源码解析
    • 属性
    • Entry内部类
    • 构造方法
    • get(Object key)方法
    • 特性再回顾
    • 左旋
    • 右旋
    • 插入元素
    • 插入再平衡
    • 插入元素举例
  • 总结

简介

TreeMap使用红黑树存储元素,可以保证元素按key值的大小进行遍历。

继承体系

TreeMap实现了Map、SortedMap、NavigableMap、Cloneable、Serializable等接口。

SortedMap规定了元素可以按key的大小来遍历,它定义了一些返回部分map的方法。

public interface SortedMap<K,V> extends Map<K,V> {
    // key的比较器
    Comparator<? super K> comparator();
    // 返回fromKey(包含)到toKey(不包含)之间的元素组成的子map
    SortedMap<K,V> subMap(K fromKey, K toKey);
    // 返回小于toKey(不包含)的子map
    SortedMap<K,V> headMap(K toKey);
    // 返回大于等于fromKey(包含)的子map
    SortedMap<K,V> tailMap(K fromKey);
    // 返回最小的key
    K firstKey();
    // 返回最大的key
    K lastKey();
    // 返回key集合
    Set<K> keySet();
    // 返回value集合
    Collection<V> values();
    // 返回节点集合
    Set<Map.Entry<K, V>> entrySet();
}

NavigableMap是对SortedMap的增强,定义了一些返回离目标key最近的元素的方法。

public interface NavigableMap<K,V> extends SortedMap<K,V> {
    // 小于给定key的最大节点
    Map.Entry<K,V> lowerEntry(K key);
    // 小于给定key的最大key
    K lowerKey(K key);
    // 小于等于给定key的最大节点
    Map.Entry<K,V> floorEntry(K key);
    // 小于等于给定key的最大key
    K floorKey(K key);
    // 大于等于给定key的最小节点
    Map.Entry<K,V> ceilingEntry(K key);
    // 大于等于给定key的最小key
    K ceilingKey(K key);
    // 大于给定key的最小节点
    Map.Entry<K,V> higherEntry(K key);
    // 大于给定key的最小key
    K higherKey(K key);
    // 最小的节点
    Map.Entry<K,V> firstEntry();
    // 最大的节点
    Map.Entry<K,V> lastEntry();
    // 弹出最小的节点
    Map.Entry<K,V> pollFirstEntry();
    // 弹出最大的节点
    Map.Entry<K,V> pollLastEntry();
    // 返回倒序的map
    NavigableMap<K,V> descendingMap();
    // 返回有序的key集合
    NavigableSet<K> navigableKeySet();
    // 返回倒序的key集合
    NavigableSet<K> descendingKeySet();
    // 返回从fromKey到toKey的子map,是否包含起止元素可以自己决定
    NavigableMap<K,V> subMap(K fromKey, boolean fromInclusive,
                             K toKey,   boolean toInclusive);
    // 返回小于toKey的子map,是否包含toKey自己决定
    NavigableMap<K,V> headMap(K toKey, boolean inclusive);
    // 返回大于fromKey的子map,是否包含fromKey自己决定
    NavigableMap<K,V> tailMap(K fromKey, boolean inclusive);
    // 等价于subMap(fromKey, true, toKey, false)
    SortedMap<K,V> subMap(K fromKey, K toKey);
    // 等价于headMap(toKey, false)
    SortedMap<K,V> headMap(K toKey);
    // 等价于tailMap(fromKey, true)
    SortedMap<K,V> tailMap(K fromKey);
}

存储结构

TreeMap只使用到了红黑树,所以它的时间复杂度为O(log n),我们再来回顾一下红黑树的特性。

(1)每个节点或者是黑色,或者是红色。

(2)根节点是黑色。

(3)每个叶子节点(NIL)是黑色。(注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!)

(4)如果一个节点是红色的,则它的子节点必须是黑色的。

(5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。

源码解析

属性

/**
 * 比较器,如果没传则key要实现Comparable接口
 */
private final Comparator<? super K> comparator;
/**
 * 根节点
 */
private transient Entry<K,V> root;
/**
 * 元素个数
 */
private transient int size = 0;
/**
 * 修改次数
 */
private transient int modCount = 0;

(1)comparator

按key的大小排序有两种方式,一种是key实现Comparable接口,一种方式通过构造方法传入比较器。

(2)root

根节点,TreeMap没有桶的概念,所有的元素都存储在一颗树中。

Entry内部类

存储节点,典型的红黑树结构。

static final class Entry<K,V> implements Map.Entry<K,V> {
    K key;
    V value;
    Entry<K,V> left;
    Entry<K,V> right;
    Entry<K,V> parent;
    boolean color = BLACK;
}

构造方法

/**
 * 默认构造方法,key必须实现Comparable接口
 */
public TreeMap() {
    comparator = null;
}
/**
 * 使用传入的comparator比较两个key的大小
 */
public TreeMap(Comparator<? super K> comparator) {
    this.comparator = comparator;
}
/**
 * key必须实现Comparable接口,把传入map中的所有元素保存到新的TreeMap中
 */
public TreeMap(Map<? extends K, ? extends V> m) {
    comparator = null;
    putAll(m);
}
/**
 * 使用传入map的比较器,并把传入map中的所有元素保存到新的TreeMap中
 */
public TreeMap(SortedMap<K, ? extends V> m) {
    comparator = m.comparator();
    try {
        buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
    } catch (java.io.IOException cannotHappen) {
    } catch (ClassNotFoundException cannotHappen) {
    }
}

构造方法主要分成两类,一类是使用comparator比较器,一类是key必须实现Comparable接口

其实,笔者认为这两种比较方式可以合并成一种,当没有传comparator的时候,可以用以下方式来给comparator赋值,这样后续所有的比较操作都可以使用一样的逻辑处理了,而不用每次都检查comparator为空的时候又用Comparable来实现一遍逻辑。

// 如果comparator为空,则key必须实现Comparable接口,所以这里肯定可以强转
// 这样在构造方法中统一替换掉,后续的逻辑就都一致了
comparator = (k1, k2) -> ((Comparable<? super K>)k1).compareTo(k2);

get(Object key)方法

获取元素,典型的二叉查找树的查找方法。

public V get(Object key) {
    // 根据key查找元素
    Entry<K,V> p = getEntry(key);
    // 找到了返回value值,没找到返回null
    return (p==null ? null : p.value);
}
final Entry<K,V> getEntry(Object key) {
    // 如果comparator不为空,使用comparator的版本获取元素
    if (comparator != null)
        return getEntryUsingComparator(key);
    // 如果key为空返回空指针异常
    if (key == null)
        throw new NullPointerException();
    // 将key强转为Comparable
    @SuppressWarnings("unchecked")
    Comparable<? super K> k = (Comparable<? super K>) key;
    // 从根元素开始遍历
    Entry<K,V> p = root;
    while (p != null) {
        int cmp = k.compareTo(p.key);
        if (cmp < 0)
            // 如果小于0从左子树查找
            p = p.left;
        else if (cmp > 0)
            // 如果大于0从右子树查找
            p = p.right;
        else
            // 如果相等说明找到了直接返回
            return p;
    }
    // 没找到返回null
    return null;
}
final Entry<K,V> getEntryUsingComparator(Object key) {
    @SuppressWarnings("unchecked")
    K k = (K) key;
    Comparator<? super K> cpr = comparator;
    if (cpr != null) {
        // 从根元素开始遍历
        Entry<K,V> p = root;
        while (p != null) {
            int cmp = cpr.compare(k, p.key);
            if (cmp < 0)
                // 如果小于0从左子树查找
                p = p.left;
            else if (cmp > 0)
                // 如果大于0从右子树查找
                p = p.right;
            else
                // 如果相等说明找到了直接返回
                return p;
        }
    }
    // 没找到返回null
    return null;
}

(1)从root遍历整个树;

(2)如果待查找的key比当前遍历的key小,则在其左子树中查找;

(3)如果待查找的key比当前遍历的key大,则在其右子树中查找;

(4)如果待查找的key与当前遍历的key相等,则找到了该元素,直接返回;

(5)从这里可以看出是否有comparator分化成了两个方法,但是内部逻辑一模一样,因此可见笔者comparator = (k1, k2) -> ((Comparable<? super K>)k1).compareTo(k2);这种改造的必要性。

特性再回顾

(1)每个节点或者是黑色,或者是红色。

(2)根节点是黑色。

(3)每个叶子节点(NIL)是黑色。(注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!)

(4)如果一个节点是红色的,则它的子节点必须是黑色的。

(5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。

左旋

左旋,就是以某个节点为支点向左旋转。

整个左旋过程如下:

(1)将 y的左节点 设为 x的右节点,即将 β 设为 x的右节点;

(2)将 x 设为 y的左节点的父节点,即将 β的父节点 设为 x;

(3)将 x的父节点 设为 y的父节点;

(4)如果 x的父节点 为空节点,则将y设置为根节点;如果x是它父节点的左(右)节点,则将y设置为x父节点的左(右)节点;

(5)将 x 设为 y的左节点;

(6)将 x的父节点 设为 y;

让我们来看看TreeMap中的实现:

/**
 * 以p为支点进行左旋
 * 假设p为图中的x
 */
private void rotateLeft(Entry<K,V> p) {
    if (p != null) {
        // p的右节点,即y
        Entry<K,V> r = p.right;
        // (1)将 y的左节点 设为 x的右节点
        p.right = r.left;
        // (2)将 x 设为 y的左节点的父节点(如果y的左节点存在的话)
        if (r.left != null)
            r.left.parent = p;
        // (3)将 x的父节点 设为 y的父节点
        r.parent = p.parent;
        // (4)...
        if (p.parent == null)
            // 如果 x的父节点 为空,则将y设置为根节点
            root = r;
        else if (p.parent.left == p)
            // 如果x是它父节点的左节点,则将y设置为x父节点的左节点
            p.parent.left = r;
        else
            // 如果x是它父节点的右节点,则将y设置为x父节点的右节点
            p.parent.right = r;
        // (5)将 x 设为 y的左节点
        r.left = p;
        // (6)将 x的父节点 设为 y
        p.parent = r;
    }
}

右旋

右旋,就是以某个节点为支点向右旋转。

整个右旋过程如下:

(1)将 x的右节点 设为 y的左节点,即 将 β 设为 y的左节点;

(2)将 y 设为 x的右节点的父节点,即 将 β的父节点 设为 y;

(3)将 y的父节点 设为 x的父节点;

(4)如果 y的父节点 是 空节点,则将x设为根节点;如果y是它父节点的左(右)节点,则将x设为y的父节点的左(右)节点;

(5)将 y 设为 x的右节点;

(6)将 y的父节点 设为 x;

让我们来看看TreeMap中的实现:

/**
 * 以p为支点进行右旋
 * 假设p为图中的y
 */
private void rotateRight(Entry<K,V> p) {
    if (p != null) {
        // p的左节点,即x
        Entry<K,V> l = p.left;
        // (1)将 x的右节点 设为 y的左节点
        p.left = l.right;
        // (2)将 y 设为 x的右节点的父节点(如果x有右节点的话)
        if (l.right != null) l.right.parent = p;
        // (3)将 y的父节点 设为 x的父节点
        l.parent = p.parent;
        // (4)...
        if (p.parent == null)
            // 如果 y的父节点 是 空节点,则将x设为根节点
            root = l;
        else if (p.parent.right == p)
            // 如果y是它父节点的右节点,则将x设为y的父节点的右节点
            p.parent.right = l;
        else
            // 如果y是它父节点的左节点,则将x设为y的父节点的左节点
            p.parent.left = l;
        // (5)将 y 设为 x的右节点
        l.right = p;
        // (6)将 y的父节点 设为 x
        p.parent = l;
    }
}

插入元素

插入元素,如果元素在树中存在,则替换value;如果元素不存在,则插入到对应的位置,再平衡树。

public V put(K key, V value) {
    Entry<K,V> t = root;
    if (t == null) {
        // 如果没有根节点,直接插入到根节点
        compare(key, key); // type (and possibly null) check
        root = new Entry<>(key, value, null);
        size = 1;
        modCount++;
        return null;
    }
    // key比较的结果
    int cmp;
    // 用来寻找待插入节点的父节点
    Entry<K,V> parent;
    // 根据是否有comparator使用不同的分支
    Comparator<? super K> cpr = comparator;
    if (cpr != null) {
        // 如果使用的是comparator方式,key值可以为null,只要在comparator.compare()中允许即可
        // 从根节点开始遍历寻找
        do {
            parent = t;
            cmp = cpr.compare(key, t.key);
            if (cmp < 0)
                // 如果小于0从左子树寻找
                t = t.left;
            else if (cmp > 0)
                // 如果大于0从右子树寻找
                t = t.right;
            else
                // 如果等于0,说明插入的节点已经存在了,直接更换其value值并返回旧值
                return t.setValue(value);
        } while (t != null);
    }
    else {
        // 如果使用的是Comparable方式,key不能为null
        if (key == null)
            throw new NullPointerException();
        @SuppressWarnings("unchecked")
        Comparable<? super K> k = (Comparable<? super K>) key;
        // 从根节点开始遍历寻找
        do {
            parent = t;
            cmp = k.compareTo(t.key);
            if (cmp < 0)
                // 如果小于0从左子树寻找
                t = t.left;
            else if (cmp > 0)
                // 如果大于0从右子树寻找
                t = t.right;
            else
                // 如果等于0,说明插入的节点已经存在了,直接更换其value值并返回旧值
                return t.setValue(value);
        } while (t != null);
    }
    // 如果没找到,那么新建一个节点,并插入到树中
    Entry<K,V> e = new Entry<>(key, value, parent);
    if (cmp < 0)
        // 如果小于0插入到左子节点
        parent.left = e;
    else
        // 如果大于0插入到右子节点
        parent.right = e;
    // 插入之后的平衡
    fixAfterInsertion(e);
    // 元素个数加1(不需要扩容)
    size++;
    // 修改次数加1
    modCount++;
    // 如果插入了新节点返回空
    return null;
}

插入再平衡

插入的元素默认都是红色,因为插入红色元素只违背了第4条特性,那么我们只要根据这个特性来平衡就容易多了。

根据不同的情况有以下几种处理方式:

  • 插入的元素如果是根节点,则直接涂成黑色即可,不用平衡;
  • 插入的元素的父节点如果为黑色,不需要平衡;
  • 插入的元素的父节点如果为红色,则违背了特性4,需要平衡,平衡时又分成下面三种情况:

(如果父节点是祖父节点的左节点)

情况 策略
1)父节点为红色,叔叔节点也为红色 (1)将父节点设为黑色;
(2)将叔叔节点设为黑色;
(3)将祖父节点设为红色;
(4)将祖父节点设为新的当前节点,进入下一次循环判断;
2)父节点为红色,叔叔节点为黑色,且当前节点是其父节点的右节点 (1)将父节点作为新的当前节点;
(2)以新当节点为支点进行左旋,进入情况3);
3)父节点为红色,叔叔节点为黑色,且当前节点是其父节点的左节点 (1)将父节点设为黑色;
(2)将祖父节点设为红色;
(3)以祖父节点为支点进行右旋,进入下一次循环判断;

(如果父节点是祖父节点的右节点,则正好与上面反过来)

情况 策略
1)父节点为红色,叔叔节点也为红色 (1)将父节点设为黑色;
(2)将叔叔节点设为黑色;
(3)将祖父节点设为红色;
(4)将祖父节点设为新的当前节点,进入下一次循环判断;
2)父节点为红色,叔叔节点为黑色,且当前节点是其父节点的左节点 (1)将父节点作为新的当前节点;
(2)以新当节点为支点进行右旋;
3)父节点为红色,叔叔节点为黑色,且当前节点是其父节点的右节点 (1)将父节点设为黑色;
(2)将祖父节点设为红色;
(3)以祖父节点为支点进行左旋,进入下一次循环判断;

让我们来看看TreeMap中的实现:

/**
 * 插入再平衡
 *(1)每个节点或者是黑色,或者是红色。
 *(2)根节点是黑色。
 *(3)每个叶子节点(NIL)是黑色。(注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!)
 *(4)如果一个节点是红色的,则它的子节点必须是黑色的。
 *(5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
 */
private void fixAfterInsertion(Entry<K,V> x) {
    // 插入的节点为红节点,x为当前节点
    x.color = RED;
    // 只有当插入节点不是根节点且其父节点为红色时才需要平衡(违背了特性4)
    while (x != null && x != root && x.parent.color == RED) {
        if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
            // a)如果父节点是祖父节点的左节点
            // y为叔叔节点
            Entry<K,V> y = rightOf(parentOf(parentOf(x)));
            if (colorOf(y) == RED) {
                // 情况1)如果叔叔节点为红色
                // (1)将父节点设为黑色
                setColor(parentOf(x), BLACK);
                // (2)将叔叔节点设为黑色
                setColor(y, BLACK);
                // (3)将祖父节点设为红色
                setColor(parentOf(parentOf(x)), RED);
                // (4)将祖父节点设为新的当前节点
                x = parentOf(parentOf(x));
            } else {
                // 如果叔叔节点为黑色
                // 情况2)如果当前节点为其父节点的右节点
                if (x == rightOf(parentOf(x))) {
                    // (1)将父节点设为当前节点
                    x = parentOf(x);
                    // (2)以新当前节点左旋
                    rotateLeft(x);
                }
                // 情况3)如果当前节点为其父节点的左节点(如果是情况2)则左旋之后新当前节点正好为其父节点的左节点了)
                // (1)将父节点设为黑色
                setColor(parentOf(x), BLACK);
                // (2)将祖父节点设为红色
                setColor(parentOf(parentOf(x)), RED);
                // (3)以祖父节点为支点进行右旋
                rotateRight(parentOf(parentOf(x)));
            }
        } else {
            // b)如果父节点是祖父节点的右节点
            // y是叔叔节点
            Entry<K,V> y = leftOf(parentOf(parentOf(x)));
            if (colorOf(y) == RED) {
                // 情况1)如果叔叔节点为红色
                // (1)将父节点设为黑色
                setColor(parentOf(x), BLACK);
                // (2)将叔叔节点设为黑色
                setColor(y, BLACK);
                // (3)将祖父节点设为红色
                setColor(parentOf(parentOf(x)), RED);
                // (4)将祖父节点设为新的当前节点
                x = parentOf(parentOf(x));
            } else {
                // 如果叔叔节点为黑色
                // 情况2)如果当前节点为其父节点的左节点
                if (x == leftOf(parentOf(x))) {
                    // (1)将父节点设为当前节点
                    x = parentOf(x);
                    // (2)以新当前节点右旋
                    rotateRight(x);
                }
                // 情况3)如果当前节点为其父节点的右节点(如果是情况2)则右旋之后新当前节点正好为其父节点的右节点了)
                // (1)将父节点设为黑色
                setColor(parentOf(x), BLACK);
                // (2)将祖父节点设为红色
                setColor(parentOf(parentOf(x)), RED);
                // (3)以祖父节点为支点进行左旋
                rotateLeft(parentOf(parentOf(x)));
            }
        }
    }
    // 平衡完成后将根节点设为黑色
    root.color = BLACK;
}

插入元素举例

我们依次向红黑树中插入 4、2、3 三个元素,来一起看看整个红黑树平衡的过程。

三个元素都插入完成后,符合父节点是祖父节点的左节点,叔叔节点为黑色,且当前节点是其父节点的右节点,即情况2)。

情况2)需要做以下两步处理:

(1)将父节点作为新的当前节点;

(2)以新当节点为支点进行左旋,进入情况3);

情况3)需要做以下三步处理:

(1)将父节点设为黑色;

(2)将祖父节点设为红色;

(3)以祖父节点为支点进行右旋,进入下一次循环判断;

下一次循环不符合父节点为红色了,退出循环,插入再平衡完成。

总结

本篇文章就到这里了,希望能给你带来帮助,也希望您能够多多关注我们的更多内容!

(0)

相关推荐

  • 浅谈java中的TreeMap 排序与TreeSet 排序

    TreeMap: package com; import java.util.Comparator; import java.util.TreeMap; public class Test5 { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub TreeMap<String, String> tree = new TreeMap<String,

  • JDK集合源码之解析TreeMap(二)

    目录 删除元素 删除再平衡 删除元素举例 二叉树的遍历 TreeMap的遍历 总结 总结 删除元素 删除元素本身比较简单,就是采用二叉树的删除规则. 如果删除的位置有两个叶子节点,则从其右子树中取最小的元素放到删除的位置,然后把删除位置移到替代元素的位置,进入下一步. 如果删除的位置只有一个叶子节点(有可能是经过第一步转换后的删除位置),则把那个叶子节点作为替代元素,放到删除的位置,然后把这个叶子节点删除. 如果删除的位置没有叶子节点,则直接把这个删除位置的元素删除即可. 针对红黑树,如果删除位

  • Java源码解析TreeMap简介

    TreeMap是常用的排序树,本文主要介绍TreeMap中,类的注释中对TreeMap的介绍.代码如下. /** * A Red-Black tree based {@link NavigableMap} implementation. * The map is sorted according to the {@linkplain Comparable natural * ordering} of its keys, or by a {@link Comparator} provided at

  • 通过java.util.TreeMap源码加强红黑树的理解

    在此之前,我们已经为大家整理了很多关于经典问题红黑树的思路和解决办法.本篇文章,是通过分析java.util.TreeMap源码,让大家通过实例来对红黑树这个问题有更加深入的理解. 本篇将结合JDK1.6的TreeMap源码,来一起探索红-黑树的奥秘.红黑树是解决二叉搜索树的非平衡问题. 当插入(或者删除)一个新节点时,为了使树保持平衡,必须遵循一定的规则,这个规则就是红-黑规则:  1) 每个节点不是红色的就是黑色的  2) 根总是黑色的  3) 如果节点是红色的,则它的子节点必须是黑色的(反

  • java TreeMap源码解析详解

    java TreeMap源码解析详解 在介绍TreeMap之前,我们来了解一种数据结构:排序二叉树.相信学过数据结构的同学知道,这种结构的数据存储形式在查找的时候效率非常高. 如图所示,这种数据结构是以二叉树为基础的,所有的左孩子的value值都是小于根结点的value值的,所有右孩子的value值都是大于根结点的.这样做的好处在于:如果需要按照键值查找数据元素,只要比较当前结点的value值即可(小于当前结点value值的,往左走,否则往右走),这种方式,每次可以减少一半的操作,所以效率比较高

  • JDK集合源码之解析TreeMap(一)

    目录 简介 继承体系 存储结构 源码解析 属性 Entry内部类 构造方法 get(Object key)方法 特性再回顾 左旋 右旋 插入元素 插入再平衡 插入元素举例 总结 简介 TreeMap使用红黑树存储元素,可以保证元素按key值的大小进行遍历. 继承体系 TreeMap实现了Map.SortedMap.NavigableMap.Cloneable.Serializable等接口. SortedMap规定了元素可以按key的大小来遍历,它定义了一些返回部分map的方法. public

  • 基于ArrayList常用方法的源码全面解析

    我相信几乎所有的同学在大大小小的笔试.面试过程中都会被问及ArrayList与LinkedList之间的异同点.稍有准备的人这些问题早已烂熟于心,前者基于数组实现,后者基于链表实现:前者随机方法速度快删除和插入指定位置速度慢,后者随机访问速度慢删除和插入指定位置速度快:两者都是线程不安全的:列表与数组之间的区别等等. 列表与数组之间很大的一个区别就是:数组在其初始化就需要给它确定大小不能动态扩容,而列表则可以动态扩容.ArrayList是基于数组实现的,那么它是如何实现的动态扩容呢? 对于Arr

  • Java集合源码全面分析

    Java集合工具包位于Java.util包下,包含了很多常用的数据结构,如数组.链表.栈.队列.集合.哈希表等.学习Java集合框架下大致可以分为如下五个部分:List列表.Set集合.Map映射.迭代器(Iterator.Enumeration).工具类(Arrays.Collections). 从上图中可以看出,集合类主要分为两大类:Collection和Map. Collection是List.Set等集合高度抽象出来的接口,它包含了这些集合的基本操作,它主要又分为两大部分:List和Se

  • 关于Java Guava ImmutableMap不可变集合源码分析

    目录 Java Guava不可变集合ImmutableMap的源码分析 一.案例场景 二.ImmutableMap源码分析 Java Guava不可变集合ImmutableMap的源码分析 一.案例场景 遇到过这样的场景,在定义一个static修饰的Map时,使用了大量的put()方法赋值,就类似这样-- public static final Map<String,String> dayMap= new HashMap<>(); static { dayMap.put("

  • ahooks useRequest源码精读解析

    目录 前言 架构图 源码解析 Fetch onBefore onRequest onSuccess onFinally onError 其它 API 小结 plugins usePollingPlugin useRetryPlugin 小结 useRequest 对自定义 hook 的思考 总结 前言 自从 React v16.8 推出了 Hooks API,前端框架圈并开启了新的逻辑复用的时代,不再需要在意 HOC 的无限套娃导致性能差的问题,也解决了 mixin 的可阅读性差的问题.当然对于

  • Java OkHttp框架源码深入解析

    目录 1.OkHttp发起网络请求 可以通过OkHttpClient发起一个网络请求 通过Retrofit发起一个OkHttp请求 2.OkHttp的连接器 1.OkHttp发起网络请求 可以通过OkHttpClient发起一个网络请求 //创建一个Client,相当于打开一个浏览器 OkHttpClient okHttpClient = new OkHttpClient.Builder().build(); //创建一个请求. Request request = new Request.Bui

  • 【MyBatis源码全面解析】MyBatis一二级缓存介绍

    MyBatis缓存 我们知道,频繁的数据库操作是非常耗费性能的(主要是因为对于DB而言,数据是持久化在磁盘中的,因此查询操作需要通过IO,IO操作速度相比内存操作速度慢了好几个量级),尤其是对于一些相同的查询语句,完全可以把查询结果存储起来,下次查询同样的内容的时候直接从内存中获取数据即可,这样在某些场景下可以大大提升查询效率. MyBatis的缓存分为两种: 一级缓存,一级缓存是SqlSession级别的缓存,对于相同的查询,会从缓存中返回结果而不是查询数据库 二级缓存,二级缓存是Mapper

  • thinkphp3.2.0 setInc方法 源码全面解析

    我们先来看一下setInc的官方示例: 需要一个字段和一个自增的值(默认为1) 我们通过下面这个例子来一步步分析他的底层是怎么实现的: <?php namespace Home\Controller; use Think\Controller; class TestController extends Controller { public function test() { $tb_test = M('test'); $tb_test->where(['id'=>1])->set

  • Spring启动流程refresh()源码深入解析

    一.Spring容器的refresh() spring  version:4.3.12  ,尚硅谷Spring注解驱动开发-源码部分 //refresh():543, AbstractApplicationContext (org.springframework.context.support) public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdo

随机推荐