一文带你深入了解Java TreeMap

目录
  • 概述
  • TreeMap介绍
    • 构造方法
    • 关键方法
  • 使用案例
  • 核心机制
    • 实现原理
  • 源码解析
    • 成员变量
    • 查找get方法
    • 插入put方法
    • 删除remove方法

概述

TreeMap是Map家族中的一员,也是用来存放key-value键值对的。平时在工作中使用的可能并不多,它最大的特点是遍历时是有顺序的,根据key的排序规则来,那么它具体是如何使用,又是怎么实现的呢?本文基于jdk8做一个讲解。

TreeMap介绍

TreeMap是一个基于key有序的key value散列表。

  • map根据其键的自然顺序排序,或者根据map创建时提供的Comparator排序
  • 不是线程安全的
  • key 不可以存入null
  • 底层是基于红黑树实现的

以上是TreeMap的类结构图:

  • 实现了NavigableMap接口,NavigableMap又实现了Map接口,提供了导航相关的方法。
  • 继承了AbstractMap,该方法实现Map操作的骨干逻辑。
  • 实现了Cloneable接口,标记该类支持clone方法复制
  • 实现了Serializable接口,标记该类支持序列化

构造方法

TreeMap()

说明:使用键的自然排序构造一个新的空树映射。

TreeMap(Comparator<? super K> comparator)

说明:构造一个新的空树映射,根据给定的比较器排序。

TreeMap(Map<? extends K,? extends V> m)

说明:构造一个新的树映射,包含与给定映射相同的映射,按照键的自然顺序排序。

TreeMap(SortedMap<K,? extends V> m)

说明:构造一个新的树映射,包含相同的映射,并使用与指定排序映射相同的顺序。

关键方法

这边主要讲解下NavigableMap和SortedMap提供的一些方法,Map相关的方法大家应该都很熟悉了。

SortedMap接口:

Comparator<? super K> comparator()

返回用于排序此映射中的键的比较器,如果此映射使用其键的自然排序,则返回null。

Set<Map.Entry<K,V>> entrySet()

返回此映射中包含的映射的Set视图。

K firstKey()

返回当前映射中的第一个(最低)键。

K lastKey()

返回当前映射中的最后(最高)键。

NavigableMap接口:

Map.Entry<K,V> ceilingEntry(K key)

返回与大于或等于给定键的最小键相关联的键值映射,如果没有这样的键则返回null。

K ceilingKey(K key)

返回大于或等于给定键的最小键,如果没有这样的键,则返回null。

NavigableMap<K,V> descendingMap()

返回此映射中包含的映射的倒序视图。

Map.Entry<K,V> firstEntry()

返回与该映射中最小的键关联的键值映射,如果映射为空,则返回null。

Map.Entry<K,V> floorEntry(K key)

返回与小于或等于给定键的最大键相关联的键值映射,如果没有这样的键则返回null。

SortedMap<K,V> headMap(K toKey)

返回该映射中键严格小于toKey的部分的视图。

Map.Entry<K,V> higherEntry(K key)

返回与严格大于给定键的最小键关联的键值映射,如果没有这样的键,则返回null。

Map.Entry<K,V> lastEntry()

返回与此映射中最大键关联的键值映射,如果映射为空,则返回null。

Map.Entry<K,V> lowerEntry(K key)

返回与严格小于给定键的最大键关联的键值映射,如果没有这样的键,则返回null。

Map.Entry<K,V> pollFirstEntry()

删除并返回与该映射中最小的键关联的键值映射,如果映射为空,则返回null。

Map.Entry<K,V> pollLastEntry()

删除并返回与此映射中最大键关联的键值映射,如果映射为空,则返回null。

SortedMap<K,V> subMap(K fromKey, K toKey)

返回该映射中键范围从fromKey(包含)到toKey(独占)的部分的视图。

SortedMap<K,V> tailMap(K fromKey)

返回该映射中键大于或等于fromKey的部分的视图。

使用案例

验证顺序性

@Test
    public void test1() {
        Map<Integer, String> treeMap = new TreeMap<>();
        treeMap.put(16, "a");
        treeMap.put(1, "b");
        treeMap.put(4, "c");
        treeMap.put(3, "d");
        treeMap.put(8, "e");
        // 遍历
        System.out.println("默认排序:");
        treeMap.forEach((key, value) -> {
            System.out.println("key: " + key + ", value: " + value);
        });

        // 构造方法传入比较器
        Map<Integer, String> tree2Map = new TreeMap<>((o1, o2) -> o2 - o1);
        tree2Map.put(16, "a");
        tree2Map.put(1, "b");
        tree2Map.put(4, "c");
        tree2Map.put(3, "d");
        tree2Map.put(8, "e");
        // 遍历
        System.out.println("倒序排序:");
        tree2Map.forEach((key, value) -> {
            System.out.println("key: " + key + ", value: " + value);
        });
    }

运行结果:

验证不能存储null

@Test
    public void test2() {
        Map<Integer, String> treeMap = new TreeMap<>();
        treeMap.put(null, "a");
    }

运行结果:

验证NavigableMap相关方法

@Test
    public void test3() {
        NavigableMap<Integer, String> treeMap = new TreeMap<>();
        treeMap.put(16, "a");
        treeMap.put(1, "b");
        treeMap.put(4, "c");
        treeMap.put(3, "d");
        treeMap.put(8, "e");

        // 获取大于等于5的key
        Integer ceilingKey = treeMap.ceilingKey(5);
        System.out.println("ceilingKey 5 is " + ceilingKey);

        // 获取最大的key
        Integer lastKey = treeMap.lastKey();
        System.out.println("lastKey is " + lastKey);
    }

运行结果:

核心机制

实现原理

大家有想过TreeMap的底层是怎么实现的吗,是如何维护key的顺序呢?答案就是基于红黑树实现的。

那什么是红黑树呢?我们在这里简单的认识一下,了解一下红黑树的特点:红黑树是一颗自平衡的排序二叉树。我们就先从二叉树开始说起。

二叉树

二叉树很容易理解,就是一棵树分俩叉。

上面这颗就是一颗最普通的二叉树。但是你会发现看起来不那么美观,因为你以H为根节点,发现左右两边高低不平衡,高度相差达到了2。于是出现了平衡二叉树,使得左右两边高低差不多。

平衡二叉树

这下子应该能看到,不管是从任何一个字母为根节点,左右两边的深度差不了2,最多是1。这就是平衡二叉树。不过好景不长,有一天,突然要把字母变成数字,还要保持这种特性怎么办呢?于是又出现了平衡二叉排序树。

平衡二叉排序树

不管是从长相(平衡),还是从规律(排序)感觉这棵树超级完美。但是有一个问题,那就是在增加删除节点的时候,你要时刻去让这棵树保持平衡,需要做太多的工作了,旋转的次数超级多,于是乎出现了红黑树。

红黑树

这就是传说中的红黑树,和平衡二叉排序树的区别就是每个节点涂上了颜色,他有下列五条性质:

  • 每个节点都只能是红色或者黑色
  • 根节点是黑色
  • 每个叶节点(NIL节点,空节点)是黑色的。
  • 如果一个结点是红的,则它两个子节点都是黑的。也就是说在一条路径上不能出现相邻的两个红色结点。
  • 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。

这些性质有什么优点呢?就是插入效率超级高。因为在插入一个元素的时候,最多只需三次旋转,O(1)的复杂度,但是有一点需要说明他的查询效率略微逊色于平衡二叉树,因为他比平衡二叉树会稍微不平衡最多一层,也就是说红黑树的查询性能只比相同内容的avl树最多多一次比较。如何去旋转呢?如下图所示。

首先是左旋:

然后是右旋:

红黑树更详细的内容可以参考文章:Java红黑树的数据结构与算法解析

源码解析

成员变量

//这是一个比较器,方便插入查找元素等操作
private final Comparator<? super K> comparator;
//红黑树的根节点:每个节点是一个Entry
private transient Entry<K,V> root;
//集合元素数量
private transient int size = 0;
//集合修改的记录
private transient int modCount = 0;
  • comparator是一个排序器,作为key的排序规则
  • root是红黑树的根节点,说明的确底层用的红黑树作为数据结构。
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;
        Entry(K key, V value, Entry<K,V> parent) {
            this.key = key;
            this.value = value;
            this.parent = parent;
        }
        public K getKey() {
            return key;
        }
        public V getValue() {
            return value;
        }
        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;
        }
    }

查找get方法

TreeMap基于红黑树实现,而红黑树是一种自平衡二叉查找树,所以 TreeMap 的查找操作流程和二叉查找树一致。二叉树的查找流程是这样的,先将目标值和根节点的值进行比较,如果目标值小于根节点的值,则再和根节点的左孩子进行比较。如果目标值大于根节点的值,则继续和根节点的右孩子比较。在查找过程中,如果目标值和二叉树中的某个节点值相等,则返回 true,否则返回 false。TreeMap 查找和此类似,只不过在 TreeMap 中,节点(Entry)存储的是键值对<k,v>。在查找过程中,比较的是键的大小,返回的是值,如果没找到,则返回null。TreeMap 中的查找方法是get。

public V get(Object key) {
        //调用 getEntry方法查找
        Entry<K,V> p = getEntry(key);
        return (p==null ? null : p. value);
}

final Entry<K,V> getEntry(Object key) {
    / 如果比较器为空,只是用key作为比较器查询
    if (comparator != null)
        return getEntryUsingComparator(key);
    if (key == null)
        throw new NullPointerException();
    Comparable<? super K> k = (Comparable<? super K>) key;
    // 取得root节点
    Entry<K,V> p = root;
   //核心来了:从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;
    }

插入put方法

我们来看下关键的插入方法,在插入时候是如何维护key的。

public V put(K key, V value) {
        Entry<K,V> t = root;
       // 1.如果根节点为 null,将新节点设为根节点
        if (t == null) {
            compare(key, key); // type (and possibly null) check

            root = new Entry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
      //如果root不为null,说明已存在元素
        int cmp;
        Entry<K,V> parent;
        // split comparator and comparable paths
        Comparator<? super K> cpr = comparator;
    //如果比较器不为null 则使用比较器
        if (cpr != null) {
            //找到元素的插入位置
            do {
                parent = t;
                cmp = cpr.compare(key, t.key);
                 //当前key小于节点key 向左子树查找
                if (cmp < 0)
                    t = t.left;
                    //当前key大于节点key 向右子树查找
                else if (cmp > 0)
                    t = t.right;
                else
                    //相等的情况下 直接更新节点值
                    return t.setValue(value);
            } while (t != null);
        }
            //如果比较器为null 则使用默认比较器
        else {
            //如果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)
                    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方法, 看懂这个方法需要你对红黑树的机制比较了解。

private void fixAfterInsertion(Entry<K,V> x) {
    // 将新插入节点的颜色设置为红色
    x. color = RED;
    // while循环,保证新插入节点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)));
            // 如果新插入x的父节点是红色
            if (colorOf(y) == RED) {
                // 将x的父节点设置为黑色
                setColor(parentOf (x), BLACK);
                // 将x的叔叔节点设置为黑色
                setColor(y, BLACK);
                // 将x的祖父节点设置为红色
                setColor(parentOf (parentOf(x)), RED);
                // 将x指向祖父节点,如果x的祖父节点的父节点是红色,按照上面的步奏继续循环
                x = parentOf(parentOf (x));
            } else {
                // 如果新插入x的叔叔节点是黑色或缺少,且x的父节点是祖父节点的右孩子
                if (x == rightOf( parentOf(x))) {
                    // 左旋父节点
                    x = parentOf(x);
                    rotateLeft(x);
                }
                // 如果新插入x的叔叔节点是黑色或缺少,且x的父节点是祖父节点的左孩子
                // 将x的父节点设置为黑色
                setColor(parentOf (x), BLACK);
                // 将x的祖父节点设置为红色
                setColor(parentOf (parentOf(x)), RED);
                // 右旋x的祖父节点
                rotateRight( parentOf(parentOf (x)));
            }
        } else { // 如果新插入节点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 = parentOf(parentOf (x));
            } else {
                if (x == leftOf( parentOf(x))) {
                    x = parentOf(x);
                    rotateRight(x);
                }
                setColor(parentOf (x), BLACK);
                setColor(parentOf (parentOf(x)), RED);
                rotateLeft( parentOf(parentOf (x)));
            }
        }
    }
    // 最后将根节点设置为黑色
    root.color = BLACK;
}

删除remove方法

删除remove是最复杂的方法。

public V remove(Object key) {
        // 根据key查找到对应的节点对象
        Entry<K,V> p = getEntry(key);
        if (p == null)
            return null;

        // 记录key对应的value,供返回使用
        V oldValue = p. value;
        // 删除节点
        deleteEntry(p);
        return oldValue;
}
private void deleteEntry(Entry<K,V> p) {
        modCount++;
        //元素个数减一
        size--;
        // 如果被删除的节点p的左孩子和右孩子都不为空,则查找其替代节
        if (p.left != null && p. right != null) {
            // 查找p的替代节点
            Entry<K,V> s = successor (p);
            p. key = s.key ;
            p. value = s.value ;
            p = s;
        }
        Entry<K,V> replacement = (p. left != null ? p.left : p. right);
        if (replacement != null) {
            // 将p的父节点拷贝给替代节点
            replacement. parent = p.parent ;
            // 如果替代节点p的父节点为空,也就是p为跟节点,则将replacement设置为根节点
            if (p.parent == null)
                root = replacement;
            // 如果替代节点p是其父节点的左孩子,则将replacement设置为其父节点的左孩子
            else if (p == p.parent. left)
                p. parent.left   = replacement;
            // 如果替代节点p是其父节点的左孩子,则将replacement设置为其父节点的右孩子
            else
                p. parent.right = replacement;
            // 将替代节点p的left、right、parent的指针都指向空
            p. left = p.right = p.parent = null;
            // 如果替代节点p的颜色是黑色,则需要调整红黑树以保持其平衡
            if (p.color == BLACK)
                fixAfterDeletion(replacement);
        } else if (p.parent == null) { // return if we are the only node.
            // 如果要替代节点p没有父节点,代表p为根节点,直接删除即可
            root = null;
        } else {
            // 如果p的颜色是黑色,则调整红黑树
            if (p.color == BLACK)
                fixAfterDeletion(p);
            // 下面删除替代节点p
            if (p.parent != null) {
                // 解除p的父节点对p的引用
                if (p == p.parent .left)
                    p. parent.left = null;
                else if (p == p.parent. right)
                    p. parent.right = null;
                // 解除p对p父节点的引用
                p. parent = null;
            }
        }
    }

最终还是落到了对红黑树节点的删除上,需要维持红黑树的特性,做一系列的工作。

到此这篇关于一文带你深入了解Java TreeMap的文章就介绍到这了,更多相关Java TreeMap内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • java TreeMap源码解析详解

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

  • Java基础之TreeMap详解

    一.写在前面 TreeMap的底层数据结构是红黑树,且TreeMap可以实现集合元素的排序. 所以TreeMap的源码需要实现: 1.红黑树的数据结构,以及红黑树的节点插入,删除,以及红黑树的自平衡操作,如左旋,右旋,以及节点变色 2.红黑树需要支持按照指定的比较器进行排序,或者进行自然排序. 二.定义 public class TreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V>, Clo

  • 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中TreeMap集合的常用方法详解

    目录 public Map.Entry<K,V> ceilingEntry(K key) public K ceilingKey(K key) public Object clone() public Comparator<? super K> comparator() public NavigableSet<K> descendingKeySet() public NavigableMap<K,V> descendingMap() public Map.E

  • Java TreeMap排序算法实例

    本文实例讲述了Java TreeMap排序算法.分享给大家供大家参考,具体如下: TreeMap 和 HashMap 用法大致相同,但实际需求中,我们需要把一些数据进行排序: 以前在项目中,从数据库查询出来的数据放在List中,顺序都还是对的,但放在HashMap中,顺序就完全乱了. 为了处理排序的问题: 1. 对于一些简单的排序,如:数字,英文字母等 TreeMap hm = new TreeMap<String, String>(new Comparator() { public int

  • 一文带你深入了解Java TreeMap

    目录 概述 TreeMap介绍 构造方法 关键方法 使用案例 核心机制 实现原理 源码解析 成员变量 查找get方法 插入put方法 删除remove方法 概述 TreeMap是Map家族中的一员,也是用来存放key-value键值对的.平时在工作中使用的可能并不多,它最大的特点是遍历时是有顺序的,根据key的排序规则来,那么它具体是如何使用,又是怎么实现的呢?本文基于jdk8做一个讲解. TreeMap介绍 TreeMap是一个基于key有序的key value散列表. map根据其键的自然顺

  • 一文带你彻底理解Java序列化和反序列化

    Java序列化是什么? Java序列化是指把Java对象转换为字节序列的过程,Java反序列化是指把字节序列恢复为Java对象的过程. 反序列化: 客户端重文件,或者网络中获取到文件以后,在内存中重构对象. 序列化: 对象序列化的最重要的作用是传递和保存对象的时候,保证对象的完整性和可传递性.方便字节可以在网络上传输以及保存在本地文件. 为什么需要序列化和反序列化 实现分布式 核心在于RMI,可以利用对象序列化运行远程主机上的服务,实现运行的时候,就像在本地上运行Java对象一样. 实现递归保存

  • 一文带你深入了解Java泛型

    目录 什么是Java泛型 泛型的使用 泛型类 泛型接口 泛型方法 泛型的底层实现机制 ArrayList源码解析 什么是泛型擦除 泛型的边界 ?:无界通配符 extends 上边界通配符 super 下边界通配符 PECS原则 泛型是怎么擦除的 擦除类定义中的无限制类型参数 擦除类定义中的有限制类型擦除 擦除方法定义中的类型参数 桥接方法和泛型的多态 泛型擦除带来的限制与局限 泛型不适用基本数据类型 无法创建具体类型的泛型数组 反射其实可以绕过泛型的限制 什么是Java泛型 Java 泛型(ge

  • 一文带你真正理解Java中的内部类

    目录 概述 内部类介绍和分类 常规内部类 局部内部类 匿名内部类 静态内部类 静态内部类和普通内部类的区别 内部类的作用 概述 不知道大家在平时的开发过程中或者源码里是否留意过内部类,那有思考过为什么要有内部类,内部类都有哪几种形式,静态内部类和普通内部类有什么区别呢?本篇文章主要带领大家理解下这块内容. 内部类介绍和分类 顾名思义,内部类是指一个类在另外一个类的内部,是定义在另一个类中的类.根据类的位置和属性不同,可以分为下面几种. 常规内部类 @Data public class Tree

  • 一文带你玩转Java异常处理

    目录 1.前言 2. Exception 类的层次 2.1 Exception 类的层次简介 3. Java 内置异常类 3.1 Java 内置异常类简介 3.2 非检查异常类举例 3.3 检查性异常类表 4. 异常方法 4.1 Throwable 类的主要方法 5. 捕获异常 5.1 捕获异常简介 5.2 try/catch语法如下 5.3 多重捕获块语法说明 6. throws/throw 关键字 6.1 throws/throw 关键字简介 6.2 代码实例 7. finally关键字 7

  • 一文带你搞懂Java中的泛型和通配符

    目录 概述 泛型介绍和使用 泛型类 泛型方法 类型变量的限定 通配符使用 无边界通配符 通配符上界 通配符下界 概述 泛型机制在项目中一直都在使用,比如在集合中ArrayList<String, String>, Map<String,String>等,不仅如此,很多源码中都用到了泛型机制,所以深入学习了解泛型相关机制对于源码阅读以及自己代码编写有很大的帮助.但是里面很多的机制和特性一直没有明白,特别是通配符这块,对于通配符上界.下界每次用每次百度,经常忘记,这次我就做一个总结,加

  • 一文带你深入剖析Java线程池的前世今生

    目录 由线程到线程池 线程在做什么 为什么需要线程池 线程池实现原理 总结 由线程到线程池 线程在做什么 灵魂拷问:写了那么多代码,你能够用一句话简练描述线程在干啥吗? public class Demo01 {   public static void main(String[] args) {     var thread = new Thread(() -> {       System.out.println("Hello world from a Java thread"

  • 一文带你搞懂Java中Get和Post的使用

    目录 1 Get请求数据 1.1 Controller 1.2 Service 1.3 Application 1.4 Postman 2 Post接收数据 2.1 Controller 2.2 Service 2.3 Application 2.4 Postman 3 Post发送数据 3.1 Controller 3.2 Service 3.3 ResponseResult 3.4 Config 3.5 Application 3.6 Postman 1 Get请求数据 项目地址:https

  • 一文带你深入了解Java中延时任务的实现

    目录 概述 JAVA DelayQueue DelayQueue的实现原理 DelayQueue实现延时队列的优缺点 时间轮算法 时间轮的具体实现 进阶优化版时间轮算法 时间轮算法的应用 小结 redis延时队列 mq延时队列 rocketmq延时消息 rocketmq的精准延时消息 总结 概述 延时任务相信大家都不陌生,在现实的业务中应用场景可以说是比比皆是.例如订单下单15分钟未支付直接取消,外卖超时自动赔付等等.这些情况下,我们该怎么设计我们的服务的实现呢? 笨一点的方法自然是定时任务去数

  • 一文带你全面了解Java Hashtable

    目录 概述 介绍和使用 核心机制 实现机制 扩容机制 源码解析 成员变量 构造函数 put方法 get方法 remove方法 总结 概述 HashTable是jdk 1.0中引入的产物,基本上现在很少使用了,但是会在面试中经常被问到,你都知道吗: HashTable底层的实现机制是什么? HashTable的扩容机制是什么? HashTable和HashMap的区别是什么? 介绍和使用 和HashMap一样,Hashtable也是一个散列表,它存储的内容是键值对(key-value)映射, 重要

随机推荐