Java 集合框架:TreeMap 的介绍、使用、原理与源码解析

news2024/9/22 15:50:39

大家好,我是栗筝i,这篇文章是我的 “栗筝i 的 Java 技术栈” 专栏的第 021 篇文章,在 “栗筝i 的 Java 技术栈” 这个专栏中我会持续为大家更新 Java 技术相关全套技术栈内容。专栏的主要目标是已经有一定 Java 开发经验,并希望进一步完善自己对整个 Java 技术体系来充实自己的技术栈的同学。与此同时,本专栏的所有文章,也都会准备充足的代码示例和完善的知识点梳理,因此也十分适合零基础的小白和要准备工作面试的同学学习。当然,我也会在必要的时候进行相关技术深度的技术解读,相信即使是拥有多年 Java 开发经验的从业者和大佬们也会有所收获并找到乐趣。

在 Java 的集合框架中,TreeMap 是一种重要的数据结构,它基于红黑树实现,提供了有序的键值对存储方式。与 HashMap 不同,TreeMap 保证了元素的顺序性,使得我们可以在集合中按自然顺序或自定义顺序进行排序和查找。这使得 TreeMap 在需要排序的数据操作中表现出色,尤其适合处理有序的数据集合和范围查询。

TreeMap 的实现原理涉及红黑树,一种自平衡的二叉搜索树。这种树结构能够在 O(log n) 时间复杂度内完成插入、删除和查找操作。与之相比,HashMap 的操作虽然平均时间复杂度为 O(1),但不保持元素的顺序。因此,TreeMap 在需要保持键顺序的场景中显得尤为重要。

本文将深入探讨 TreeMap 的使用方法、内部原理及其源码实现。我们将从基本概念和常见操作入手,逐步分析 TreeMap 的实现细节。通过对源码的解析,您将能够深入理解 TreeMap 的工作机制,从而在实际编程中更好地利用这一强大的数据结构。无论您是 Java 初学者还是有经验的开发者,都能通过本篇文章获得对 TreeMap 更加深入的认识和实践经验。


文章目录

      • 1、TreeMap 概述
        • 1.1、TreeMap 介绍
        • 1.2、红黑树回顾
      • 2、TreeMap 底层实现
        • 2.1、TreeMap 底层结构
        • 2.2、添加元素
        • 2.3、删除元素
        • 2.4、查询元素
      • 3、TreeMap 相关知识点
        • 3.1、`HashMap` 和 `TreeMap` 的实现
        • 3.2、`HashMap` 和 `TreeMap` 的线程安全性
        • 3.3、`SortedMap` 接口
        • 3.4、自定义比较器实现降序排序


1、TreeMap 概述

1.1、TreeMap 介绍

Map 在 Java 里面分为两种:HashMap 和 TreeMap,区别就是 TreeMap 有序,HashMap 无序。如果只需要存映射,那么 HashMap 就够了,但是如果需要存有顺序的 key 那么就用 TreeMap。

TreeMap 存储 K-V 键值对,通过红黑树(R-B tree)实现。TreeMap 继承了 NavigableMap 接口,NavigableMap 接口继承了 SortedMap 接口,可支持一系列的导航定位以及导航操作的方法,当然只是提供了接口,需要 TreeMap 自己去实现;TreeMap 实现了 Cloneable 接口,可被克隆,实现了 Serializable 接口,可序列化;TreeMap 因为是通过红黑树实现,红黑树结构天然支持排序,默认情况下通过 Key 值的自然顺序进行排序。

TreeMap 是一个能比较元素大小的 Map 集合,会对传入的 key 进行了大小排序。可以使用元素的自然顺序,也可以使用集合中自定义的比较器来进行排序。

TreeMap 的特点:

  1. TreeMap 是有序的 key-value 集合,通过红黑树实现。根据键的自然顺序进行排序或根据提供的 Comparator 进行排序。
  2. TreeMap 继承了 AbstractMap,实现了 NavigableMap 接口,支持一系列的导航方法,给定具体搜索目标,可以返回最接近的匹配项。如 floorEntry()ceilingEntry() 分别返回小于等于、大于等于给定键关联的 Map.Entry() 对象,不存在则返回 null。lowerKey()floorKeyceilingKeyhigherKey()只返回关联的key。
1.2、红黑树回顾

红⿊树和 AVL 树 类似,都是在进⾏插⼊和删除时通过旋转保持⾃身平衡,从⽽获得较⾼的查找性能。与 AVL 树 相⽐,红⿊树不追求所有递归⼦树的⾼度差不超过 1,保证从根节点到叶尾的最⻓路径不超过最短路径的 2 倍,所以最差时间复杂度是 O(logn)。

红⿊树通过重新着⾊和左右旋转,更加⾼效地完成了插⼊和删除之后的⾃平衡调整。红⿊树在本质上还是⼆叉查找树,它额外引⼊了 5 个约束条件: ① 节点只能是红⾊或⿊⾊。 ② 根节点必须是⿊⾊。 ③ 所有 NIL 节点都是⿊⾊的。 ④ ⼀条路径上不能出现相邻的两个红⾊节点。 ⑤ 在任何递归⼦树中,根节点到叶⼦节点的所有路径上包含相同数⽬的⿊⾊节点。

这五个约束条件保证了红⿊树的新增、删除、查找的最坏时间复杂度均为 O(logn)。如果⼀个树的左⼦节点或右⼦节点不存在,则均认定为⿊⾊。红⿊树的任何旋转在 3 次之内均可完成。


2、TreeMap 底层实现

2.1、TreeMap 底层结构

TreeMap 类是一个基于红黑树的实现,TreeMap 维护了键的有序性。提供了几种构造函数来初始化映射,包括使用自然顺序、指定比较器、从已有映射构造等。内部的 Entry 类表示树中的节点,每个节点包含了键、值、子节点和父节点的引用,以及节点的颜色标记(红黑树特性)。

public class TreeMap<K,V>
    extends AbstractMap<K,V>
    implements NavigableMap<K,V>, Cloneable, java.io.Serializable
{
    /**
     * 用于维护树映射中顺序的比较器,如果为 null,则使用键的自然顺序。
     *
     * @serial
     */
    private final Comparator<? super K> comparator;

    private transient Entry<K,V> root; // 树的根节点,使用 transient 以防序列化

    /**
     * 树中的条目数量
     */
    private transient int size = 0;

    /**
     * 树的结构性修改次数
     */
    private transient int modCount = 0;

    /**
     * 构造一个新的空的树映射,使用键的自然顺序。所有插入到映射中的键必须实现 {@link Comparable} 接口。
     * 此外,所有这些键必须是 <em>相互可比较</em>:{@code k1.compareTo(k2)} 对于映射中的任何键 {@code k1} 和 {@code k2}
     * 不得抛出 {@code ClassCastException}。如果用户尝试将一个违反此约束的键插入到映射中(例如,用户尝试
     * 将字符串键插入到一个键为整数的映射中),{@code put(Object key, Object value)} 调用将抛出 {@code ClassCastException}。
     */
    public TreeMap() {
        comparator = null;
    }

    /**
     * 构造一个新的空的树映射,按照给定的比较器排序。所有插入到映射中的键必须通过给定的比较器进行 <em>相互可比较</em>:
     * {@code comparator.compare(k1, k2)} 对于映射中的任何键 {@code k1} 和 {@code k2} 不得抛出 {@code ClassCastException}。
     * 如果用户尝试将一个违反此约束的键插入到映射中,{@code put(Object key, Object value)} 调用将抛出 {@code ClassCastException}。
     *
     * @param comparator 用于排序此映射的比较器。如果 {@code null},则使用 {@linkplain Comparable 自然顺序}。
     */
    public TreeMap(Comparator<? super K> comparator) {
        this.comparator = comparator;
    }

    /**
     * 构造一个新的树映射,包含与给定映射相同的映射,根据键的 <em>自然顺序</em> 进行排序。
     * 所有插入到新映射中的键必须实现 {@link Comparable} 接口。此外,所有这些键必须是 <em>相互可比较</em>:
     * {@code k1.compareTo(k2)} 对于映射中的任何键 {@code k1} 和 {@code k2} 不得抛出 {@code ClassCastException}。
     * 此方法的运行时间为 n*log(n)。
     *
     * @param  m 要放置在此映射中的映射
     * @throws ClassCastException 如果 m 中的键不是 {@link Comparable},或不是相互可比较的
     * @throws NullPointerException 如果指定的映射为 null
     */
    public TreeMap(Map<? extends K, ? extends V> m) {
        comparator = null;
        putAll(m);
    }

    /**
     * 构造一个新的树映射,包含与指定的排序映射相同的映射,并使用相同的排序。
     * 此方法的运行时间为线性时间。
     *
     * @param  m 要放置在此映射中的排序映射,及其比较器将用于排序此映射
     * @throws NullPointerException 如果指定的映射为 null
     */
    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) {
        }
    }
}

Entry 类:表示树中的一个节点,包含键、值、左右子节点和父节点的引用。提供了基本的 Map.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; // 节点颜色,红黑树的颜色标记

    /**
     * 创建一个具有给定键、值和父节点的新的节点,并且子节点链接为 {@code null},颜色为黑色。
     */
    Entry(K key, V value, Entry<K,V> parent) {
        this.key = key;
        this.value = value;
        this.parent = parent;
    }

    /**
     * 返回键。
     *
     * @return 键
     */
    public K getKey() {
        return key;
    }

    /**
     * 返回与键关联的值。
     *
     * @return 与键关联的值
     */
    public V getValue() {
        return value;
    }

    /**
     * 用给定的值替换当前与键关联的值。
     *
     * @return 调用此方法之前与键关联的值
     */
    public V setValue(V value) {
        V oldValue = this.value;
        this.value = value;
        return oldValue;
    }

    public boolean equals(Object o) {
        if (!(o instanceof Map.Entry))
            return false;
        Map.Entry<?,?> e = (Map.Entry<?,?>)o;

        return valEquals(key,e.getKey()) && valEquals(value,e.getValue());
    }

    public int hashCode() {
        int keyHash = (key==null ? 0 : key.hashCode());
        int valueHash = (value==null ? 0 : value.hashCode());
        return keyHash ^ valueHash;
    }

    public String toString() {
        return key + "=" + value;
    }
}

2.2、添加元素

put 方法在 TreeMap 中用于插入或更新键值对。首先,它检查树是否为空,若为空则创建一个新根节点。如果树非空,它根据提供的比较器或自然排序路径遍历树,找到适合插入的位置。如果键已存在,则更新对应的值。插入新节点后,调用 fixAfterInsertion 方法修复树的结构以维持红黑树的平衡性,并更新树的大小和结构修改次数。

/**
 * 将指定的值与指定的键关联在此映射中。
 * 如果映射中之前包含该键的映射,则替换旧值。
 *
 * @param key 与指定的值关联的键
 * @param value 要与指定键关联的值
 *
 * @return 之前与 {@code key} 关联的值,如果 {@code key} 没有映射,则返回 {@code null}。
 *         ({@code null} 的返回值也可能表示映射中之前与 {@code key} 关联了 {@code null}。)
 * @throws ClassCastException 如果指定的键无法与当前映射中的键进行比较
 * @throws NullPointerException 如果指定的键为 {@code null} 并且此映射使用自然排序,或其比较器
 *         不允许 {@code null} 键
 */
public V put(K key, V value) {
    // 获取树的根节点
    Entry<K,V> t = root;

    // 如果树为空,则插入新的根节点
    if (t == null) {
        // 检查键的类型(以及可能的 null 值)
        compare(key, key);

        // 创建一个新的根节点
        root = new Entry<>(key, value, null);
        size = 1;  // 更新树的大小
        modCount++;  // 记录结构修改次数
        return null;
    }

    int cmp;
    Entry<K,V> parent;
    // 使用比较器或自然排序路径
    Comparator<? super K> 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();  // 不允许 null 键

        @SuppressWarnings("unchecked")
        Comparable<? super K> k = (Comparable<? super K>) 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<K,V> e = new Entry<>(key, value, parent);
    if (cmp < 0)
        parent.left = e;  // 插入到左子树
    else
        parent.right = e;  // 插入到右子树

    // 修复插入后的树结构以保持红黑树的性质
    fixAfterInsertion(e);
    size++;  // 更新树的大小
    modCount++;  // 记录结构修改次数
    return null;
}

fixAfterInsertion 方法用于在 TreeMap 中插入新节点后修复红黑树的平衡性。它通过调整节点颜色和旋转操作来维护红黑树的性质,确保树的高度平衡。具体来说,当插入节点的父节点为红色时,根据其叔父节点的颜色和位置,进行节点颜色调整和必要的旋转操作,最后确保根节点始终为黑色。

/**
 * 插入节点后的修复方法,用于保持红黑树的平衡性。
 * 
 * @param x 插入的节点
 */
private void fixAfterInsertion(Entry<K,V> x) {
    // 将新插入节点的颜色设为红色
    x.color = RED;

    // 当 x 节点存在且不是根节点,且 x 节点的父节点是红色时
    while (x != null && x != root && x.parent.color == RED) {
        // 如果 x 节点的父节点是祖父节点的左子节点
        if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
            // 获取 x 节点的叔父节点(祖父节点的右子节点)
            Entry<K,V> y = rightOf(parentOf(parentOf(x)));
            // 如果叔父节点是红色
            if (colorOf(y) == RED) {
                // 将父节点和叔父节点设为黑色,祖父节点设为红色
                setColor(parentOf(x), BLACK);
                setColor(y, BLACK);
                setColor(parentOf(parentOf(x)), RED);
                // 将祖父节点作为新的 x 节点继续修复
                x = parentOf(parentOf(x));
            } else {
                // 如果 x 节点是其父节点的右子节点
                if (x == rightOf(parentOf(x))) {
                    // 将 x 节点的父节点作为新的 x 节点,进行左旋操作
                    x = parentOf(x);
                    rotateLeft(x);
                }
                // 将父节点设为黑色,祖父节点设为红色,进行右旋操作
                setColor(parentOf(x), BLACK);
                setColor(parentOf(parentOf(x)), RED);
                rotateRight(parentOf(parentOf(x)));
            }
        } else { // 如果 x 节点的父节点是祖父节点的右子节点
            // 获取 x 节点的叔父节点(祖父节点的左子节点)
            Entry<K,V> y = leftOf(parentOf(parentOf(x)));
            // 如果叔父节点是红色
            if (colorOf(y) == RED) {
                // 将父节点和叔父节点设为黑色,祖父节点设为红色
                setColor(parentOf(x), BLACK);
                setColor(y, BLACK);
                setColor(parentOf(parentOf(x)), RED);
                // 将祖父节点作为新的 x 节点继续修复
                x = parentOf(parentOf(x));
            } else {
                // 如果 x 节点是其父节点的左子节点
                if (x == leftOf(parentOf(x))) {
                    // 将 x 节点的父节点作为新的 x 节点,进行右旋操作
                    x = parentOf(x);
                    rotateRight(x);
                }
                // 将父节点设为黑色,祖父节点设为红色,进行左旋操作
                setColor(parentOf(x), BLACK);
                setColor(parentOf(parentOf(x)), RED);
                rotateLeft(parentOf(parentOf(x)));
            }
        }
    }
    // 将根节点的颜色设为黑色
    root.color = BLACK;
}
2.3、删除元素

TreeMap 的删除操作包括三个主要步骤:首先,通过 getEntry 方法查找指定键的节点。如果节点存在,调用 deleteEntry 方法删除该节点。在删除过程中,如果节点有两个子节点,将其替换为后继节点;如果只有一个子节点或没有子节点,则直接更新父节点的链接,并处理可能引发的树结构不平衡问题,最终确保红黑树的性质。删除操作完成后,更新树的大小和修改计数器。

static final class Entry<K,V> implements Map.Entry<K,V> {
    public V remove(Object key) {
        TreeMap.Entry<K,V> p = getEntry(key); // 查找指定键对应的节点
        if (p == null)
            return null; // 如果找不到节点,返回 null

        V oldValue = p.value; // 记录旧值
        deleteEntry(p); // 删除节点
        return oldValue; // 返回被删除节点的值
    }

    /**
     * 删除 TreeMap 中所有的映射,清空 map。
     * 调用此方法后,map 为空。
     */
    public void clear() {
        modCount++; // 增加修改计数
        size = 0; // 清空大小
        root = null; // 设置根节点为 null
    }

    /**
     * 删除节点 p,并重新平衡树。
     */
    private void deleteEntry(TreeMap.Entry<K,V> p) {
        modCount++; // 增加修改计数
        size--; // 减少元素数量

        // 如果节点 p 有两个子节点
        if (p.left != null && p.right != null) {
            TreeMap.Entry<K,V> s = successor(p); // 找到 p 的后继节点
            p.key = s.key; // 用后继节点的键替换当前节点的键
            p.value = s.value; // 用后继节点的值替换当前节点的值
            p = s; // 将 p 指向后继节点
        } // p 现在只有一个子节点或没有子节点

        TreeMap.Entry<K,V> replacement = (p.left != null ? p.left : p.right); // 确定替换节点

        if (replacement != null) {
            // 将替换节点链接到 p 的父节点
            replacement.parent = p.parent;
            if (p.parent == null)
                root = replacement; // 如果 p 是根节点,更新根节点
            else if (p == p.parent.left)
                p.parent.left = replacement; // 如果 p 是父节点的左子节点
            else
                p.parent.right = replacement; // 如果 p 是父节点的右子节点

            // 清除 p 的链接以便后续修复使用
            p.left = p.right = p.parent = null;

            // 修复替换节点
            if (p.color == BLACK)
                fixAfterDeletion(replacement);
        } else if (p.parent == null) { // 如果节点 p 是唯一的节点
            root = null; // 设置根节点为 null
        } else { // 节点 p 没有子节点
            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; // 清除父节点
            }
        }
    }
}
2.4、查询元素

TreeMapget 方法通过调用 getEntry 查找指定键的 Entry 节点。getEntry 根据是否使用比较器来决定使用哪种查找策略:若使用比较器,则调用 getEntryUsingComparator 进行查找;否则,通过比较键的自然顺序在树中遍历。最终,get 返回找到的节点的值,或在键不存在时返回 null

static final class Entry<K,V> implements Map.Entry<K,V> {
	  /**
     * 根据指定的键获取对应的值。
     * 如果树中存在该键,则返回对应的值;否则返回 null。
     *
     * @param key 要查找的键
     * @return 如果键存在,则返回与键对应的值;否则返回 null
     */
    public V get(Object key) {
        TreeMap.Entry<K,V> p = getEntry(key);
        return (p == null ? null : p.value);
    }

    /**
     * 返回该映射中给定键的条目,如果该键不存在,则返回 {@code null}。
     *
     * @param key 要查找的键
     * @return 该映射中给定键的条目,如果该键不存在,则返回 {@code null}
     * @throws ClassCastException 如果指定的键无法与当前映射中的键进行比较
     * @throws NullPointerException 如果指定的键为 null 并且该映射使用自然排序,或者其比较器不允许 null 键
     */
    final TreeMap.Entry<K,V> getEntry(Object key) {
        // 针对使用比较器的版本进行优化以提高性能
        if (comparator != null)
            return getEntryUsingComparator(key);
        if (key == null)
            throw new NullPointerException();
        @SuppressWarnings("unchecked")
        Comparable<? super K> k = (Comparable<? super K>) key;
        TreeMap.Entry<K,V> p = root;
        while (p != null) {
            int cmp = k.compareTo(p.key);
            if (cmp < 0)
                p = p.left;
            else if (cmp > 0)
                p = p.right;
            else
                return p;
        }
        return null;
    }

    /**
     * 使用比较器版本的 getEntry 方法。为了性能优化,单独分离出来。
     *
     * @param key 要查找的键
     * @return 给定键的条目,如果该键不存在,则返回 {@code null}
     */
    final TreeMap.Entry<K,V> getEntryUsingComparator(Object key) {
        @SuppressWarnings("unchecked")
        K k = (K) key;
        Comparator<? super K> cpr = comparator;
        if (cpr != null) {
            TreeMap.Entry<K,V> p = root;
            while (p != null) {
                int cmp = cpr.compare(k, p.key);
                if (cmp < 0)
                    p = p.left;
                else if (cmp > 0)
                    p = p.right;
                else
                    return p;
            }
        }
        return null;
    }
}

3、TreeMap 相关知识点

3.1、HashMapTreeMap 的实现

HashMap:

  • 基于哈希表实现。
  • 键需要实现 hashCode()equals() 方法(可以重写)。
  • 支持通过调节初始容量和负载因子来优化空间使用。
  • 构造方法:
    • HashMap(): 构建一个空的哈希映射。
    • HashMap(Map m): 构建一个哈希映射,并添加映射 m 的所有映射。
    • HashMap(int initialCapacity): 构建一个具有特定容量的空哈希映射。
    • HashMap(int initialCapacity, float loadFactor): 构建一个具有特定容量和负载因子的空哈希映射。

TreeMap:

  • 基于红黑树实现。
  • 自动保持树的平衡状态,无需手动调整。
  • 构造方法:
    • TreeMap(): 构建一个空的映射树。
    • TreeMap(Map m): 构建一个映射树,并添加映射 m 中的所有元素。
    • TreeMap(Comparator c): 构建一个映射树,并使用特定的比较器对键进行排序。
    • TreeMap(SortedMap s): 构建一个映射树,并添加映射树 s 中的所有映射,使用与 s 相同的比较器排序。
3.2、HashMapTreeMap 的线程安全性

两者均为非线程安全。若需线程安全的操作,可以使用 Collections.synchronizedMapConcurrentHashMap

3.3、SortedMap 接口

SortedMap 接口保证了键的有序性。它提供了视图(子集)和访问方法,但元素必须实现 Comparable 接口,或提供一个 Comparator 实现。TreeMapSortedMap 的唯一实现。

3.4、自定义比较器实现降序排序

通过实现 Comparator 接口,可以定义自定义比较器来控制 TreeMap 的排序方式。例如,以下代码演示了如何实现降序排序:

import java.util.*;

public class MapTest {

    public static void main(String[] args) {
        // 初始化自定义比较器
        MyComparator comparator = new MyComparator();
        // 初始化一个map集合
        Map<String, String> map = new TreeMap<>(comparator);
        // 存入数据
        map.put("a", "a");
        map.put("b", "b");
        map.put("f", "f");
        map.put("d", "d");
        map.put("c", "c");
        map.put("g", "g");
        // 遍历输出
        Iterator<String> iterator = map.keySet().iterator();
        while (iterator.hasNext()) {
            String key = iterator.next();
            System.out.println(map.get(key));
        }
    }

    static class MyComparator implements Comparator<String> {
        @Override
        public int compare(String o1, String o2) {
            // 反向比较实现降序排序
            return -o1.compareTo(o2);
        }
    }
}

总结:

  • HashMap 提供快速的查找、插入和删除操作,适用于无序数据;而 TreeMap 提供有序的键值对存储,支持范围查找和排序操作。
  • HashMap 需要正确实现 hashCode()equals()TreeMap 需要实现 Comparable 接口或提供 Comparator 进行排序。
  • 两者都非线程安全,如果需要线程安全的操作,需使用其他线程安全的实现。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1943275.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot原理解析(二)- Spring Bean的生命周期以及后处理器和回调接口

SpringBoot原理解析&#xff08;二&#xff09;- Spring Bean的生命周期以及后处理器和回调接口 文章目录 SpringBoot原理解析&#xff08;二&#xff09;- Spring Bean的生命周期以及后处理器和回调接口1.Bean的实例化阶段1.1.Bean 实例化的基本流程1.2.Bean 实例化图例1.3.实…

leetcode算法题之接雨水

这是一道很经典的题目&#xff0c;问题如下&#xff1a; 题目地址 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 解法1&#xff1a;动态规划 动态规划的核心就是将问题拆分成若干个子问题求解&#…

相信开源的力量,MoonBit 构建系统正式开源

MoonBit 构建系统正式开源 作为由 AI 驱动的云服务和边缘计算开发者平台&#xff0c;MoonBit 自设计之初便注重工具链与语言的协同效果。MoonBit 为开发者提供了一套开箱即用的工具链&#xff0c;包括集成开发环境&#xff08;IDE&#xff09;、编译器、构建系统和包管理器&…

内网隧道——HTTP隧道

文章目录 一、ReGeorg二、Neo-reGeorg三、Pivotnacci 实验网络拓扑如下&#xff1a; 攻击机kali IP&#xff1a;192.168.111.0 跳板机win7 IP&#xff1a;192.168.111.128&#xff0c;192.168.52.143 靶机win server 2008 IP&#xff1a;192.168.52.138 攻击机与Web服务器彼此之…

Unity 之 【Android Unity 共享纹理】之 Android 共享图片给 Unity 显示

Unity 之 【Android Unity 共享纹理】之 Android 共享图片给 Unity 显示 目录 Unity 之 【Android Unity 共享纹理】之 Android 共享图片给 Unity 显示 一、简单介绍 二、共享纹理 1、共享纹理的原理 2、共享纹理涉及到的关键知识点 3、什么可以实现共享 不能实现共享…

越权与逻辑漏洞

目录 越权漏洞 1、越权原理概述 2、越权分类 2.1、平行越权 2.2、垂直越权 3、越权防范&#xff1a; 逻辑漏洞 1、常见的逻辑漏洞 2、逻辑漏洞概述 3、逻辑漏洞防范&#xff1a; 越权漏洞 1、越权原理概述 如果使用A用户的权限去操作B用户的数据&#xff0c;A的权限…

静态路由技术

一、路由的概念 路由是指指导IP报文发送的路径信息。 二、路由表的结构 1、Destination/Mask:IP报文的接收方的IP地址及其子网掩码; 2、proto:协议(Static:静态路由协议,Direct:表示直连路由) 3、pref:优先级(数值和优先级成反比) 4、cost:路由开销(从源到目的…

jQuery下落撞击散乱动画

jQuery下落撞击散乱动画https://www.bootstrapmb.com/item/14767 在jQuery中实现一个下落撞击后散乱的动画效果&#xff0c;你可以结合CSS动画和jQuery的动画函数来完成。不过&#xff0c;由于jQuery本身并不直接支持复杂的物理效果&#xff08;如撞击后的散乱&#xff09;&a…

Nessus-M 暴力破解Nessus漏扫后台登录工具

项目地址:https://github.com/MartinxMax/Nessus-M Nessus-M Nessus漏洞扫描程序登录界面的暴力破解工具 帮助信息 $ python3 nessus-m.py -h 暴力破解 $ python3 nessus-m.py 192.168.101.156 8834 username.txt /usr/share/wordlists/rockyou.txt --protocol https

贪心系列专题篇二

增减字符串匹配 题目 思路 贪心策略&#xff1a;对于[0,n]&#xff0c;当遇到“I”时&#xff0c;把所剩的数中最小的拿来使用&#xff1b; 当遇到“D”时&#xff0c;把所剩的数中最大的拿来使用&#xff0c;最后还剩一个数&#xff0c;放末尾。 代码 class Solution { pu…

sbti科学碳目标倡议是什么

在科学界、工业界以及全球政策制定者的共同努力下&#xff0c;一个名为“科学碳目标倡议”&#xff08;Science Based Targets initiative&#xff0c;简称SBTi&#xff09;的全球性合作平台应运而生。这一倡议旨在推动企业和组织设定符合气候科学要求的减排目标&#xff0c;以…

Nginx 如何处理请求的限速?

&#x1f345;关注博主&#x1f397;️ 带你畅游技术世界&#xff0c;不错过每一次成长机会&#xff01; 文章目录 Nginx 如何处理请求的限速一、为什么需要对请求进行限速&#xff08;一&#xff09;服务器过载&#xff08;二&#xff09;资源竞争&#xff08;三&#xff09;服…

数据接入开放协议-GRPC接入

协议定义 一、接入认证 message VerifyRequest { string authToken 1; // 接入管理分配的UUID string endpointName 2; // 定义的接入设备名 string endpointIdentify 3; // 接入设备的ID int64 leaseValue 4; // 租约时间,接入侧申明数据上送间隔最大时间&…

JWT令牌在项目中的实战操作

一.什么是JWT令牌&#xff1f; JWT&#xff0c;全称JSON Web Token&#xff0c;官网&#xff08;https://jwt.io/&#xff09;&#xff0c;定义了一种间接的&#xff0c;自包含的格式&#xff0c;用于在通信双方以json数据格式安全的传输信息。由于数字签名的存在&#xff0c;…

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] 卡牌游戏(200分) - 三语言AC题解(Python/Java/Cpp)

🍭 大家好这里是清隆学长 ,一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 💻 ACM银牌🥈| 多次AK大厂笔试 | 编程一对一辅导 👏 感谢大家的订阅➕ 和 喜欢💗 🍿 最新华为OD机试D卷目录,全、新、准,题目覆盖率达 95% 以上,支持题目在线…

哪些工具能分析反向链接?

这里推荐两个工具&#xff0c;Ahrefs以及SEMrush&#xff0c;Ahrefs 是一个全面的SEO工具&#xff0c;特别擅长反向链接分析。它可以显示谁在链接到你的网站&#xff0c;以及这些链接的质量和数量。Ahrefs 提供详细的报告&#xff0c;包括每个反向链接的锚文本、来源网站的权重…

内部函数和外部函数(例子为C语言)

​​​​ 怎么来的&#xff1f; 函数本质上是全局的&#xff0c;因为定义一个函数的目的就是这个函数与其他函数之间相互调用&#xff0c;如果不声明的话&#xff0c;一个函数既可以被本文件中的其他函数调用&#xff0c;也可以被其他文件中的函数调用。但是可以指定某些函数…

php 存储复杂的json格式查询(如:经纬度)

在开发中&#xff0c;有时我们可能存了一些复杂json格式不知道怎么查。我这里提供给大家参考下&#xff1a; 一、先上表数据格式&#xff08;location字段的possiton经纬度以逗号分开的&#xff09; {"title":"澳海文澜府","position":"11…

JSON 文件存储

JSON 全称为&#xff1a; JavaScript Object Notation 也就是 javaScript 对象标记&#xff0c;通过对象和数组的组合来表示数据&#xff0c; 虽然构造简洁&#xff0c;但是结构化程度非常高&#xff0c; 是一种轻量级的数据交换格式 对象和数组 在 JavaScript 语言中&#…

MAT使用

概念 Shallow heap & Retained Heap Shallow Heap就是对象本身占用内存的大小。 Retained Heap就是当前对象被GC后&#xff0c;从Heap上总共能释放掉的内存(表示如果一个对象被释放掉&#xff0c;那会因为该对象的释放而减少引用进而被释放的所有的对象&#xff08;包括…