首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >面试刷题9:HashTable HashMap TreeMap的区别?

面试刷题9:HashTable HashMap TreeMap的区别?

作者头像
李福春
发布2025-07-01 16:29:48
发布2025-07-01 16:29:48
9300
代码可运行
举报
运行总次数:0
代码可运行
图片
图片

map是广义集合的一部分。 我是李福春,我在准备面试,今天我们来回答: HashTable,HashMap,TreeMap的区别? 共同点:都是Map的子类或者间接子类,以键值对的形式存储和操作数据。 区别如下表:

项目

线程安全

是否支持null键值

使用场景

HashTable

不支持

java早期hash实现,同步开销大不推荐被使用

HashMap

支持

大部分场景的首选put,get时间复杂度是常数级别

TreeMap

不支持

基于红黑树提供顺序访问的map,传入比较器来决定顺序,get,put,remove操作时间复杂度log(n)

下面分析一下面试官可能根据上面的问题进行一些扩展的点。

Map的类层级

图片
图片

HashTable是java早期的hash实现,实现了Dictionary接口; TreeMap是根据比较器来决定元素的顺序; LinkedHashMap 按照插入的顺序来遍历。下面的代码是一个不经常使用的资源自动释放的例子。

代码语言:javascript
代码运行次数:0
运行
复制
LinkedHashMap<String,String> linkedHashMap =

自动的把最早加入的元素自动删除了。

HashMap的源码分析

数据结构:Node[] table , 首先是一个数组,数组的元素是一个链表;

如下图:数组叫做桶,数组的单个元素中的链表叫做bin;

图片
图片

put操作涉及的关键源码如下:

代码语言:javascript
代码运行次数:0
运行
复制
final V putVal(int hash, K key, V value, boolean onlyIfAbent,boolean evit) {
    Node<K,V>[] tab; Node<K,V> p; int , i;
    if ((tab = table) == null || (n = tab.length) = )
        n = (tab = resize()).length;
    if ((p = tab[i = (n - ) & hash]) == ull)
        tab[i] = newNode(hash, key, value, nll);
    else {
        if (binCount >= TREEIFY_THRESHOLD - ) // -1 for first
           treeifyBin(tab, hash);
     }
}

路由规则: key计算hash值, hash值%数组长度= 数组的索引;

通过索引找到对应的数组元素,如果hash值相同,则在该链表上继续扩展。 如果链表的大小超过阈值,则链表会被树化。

hashMap的hash值的计算:

代码语言:javascript
代码运行次数:0
运行
复制

static final int hash(Object kye) {
    int h;
    return (key == null) ?  : (h = key.hashCode()) ^ (h >>>;
}

这么设置算法是为了降低hash碰撞的概率,数据计算出来的hash值差异一般是在高位,上面的代码是忽略容量以上的高位(进行了位移)。

扩容逻辑

代码语言:javascript
代码运行次数:0
运行
复制
final Node<K,V>[] resize() {
     if ((newCap = oldCap << ) < MAXIMUM_CAPACIY &&
                oldCap >= DEFAULT_INITIAL_CAPAITY)
        newThr = oldThr << ; // double there
    else if (oldThr > ) // initial capacity was placed in threshold
        newCap = oldThr;
    else {
        // zero initial threshold signifies using defaultsfults
        newCap = DEFAULT_INITIAL_CAPAITY;
        newThr = (int)(DEFAULT_LOAD_ATOR* DEFAULT_INITIAL_CAPACITY;
    }
    if (newThr ==) {
        float ft = (float)newCap * loadFator;
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?(int)ft : Integer.MAX_VALUE);
    }
    threshold = neThr;
    Node<K,V>[] newTab = (Node<K,V>[])new Node[newap];
    table = n;
    // 移动到新的数组结构e数组结构
   }

如果没指定容量和负载因子,按照默认的负载因子和容量初始化; 阀值=容量 * 负载因子,阀值按照倍数扩容 扩容后,会把老的数组中的元素复制到新的数组,这是扩容开销的主要来源

树化

代码语言:javascript
代码运行次数:0
运行
复制
final void treeifyBin(Node<K,V>[] tab, int hash) {
    int n, index; Node<K,V> e;
    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
        resize();
    else if ((e = tab[index = (n - ) & hash]) != null) {
        //树化改造逻辑
    }
}

哈希碰撞:元素在放入hashmap的过程中,如果一个对象hash冲突,被放置到同一个桶里面,会形成一个链表,链表的存取耗费性能,无法达到常数级别的时间复杂度;如果大量的hash冲突,则会形成一个长链表,如果客户端跟这些数据交互频繁,则会占用大量的cpu,导致服务器宕机甚至拒绝服务。 树化的目的是:为了安全,减少hash冲突; 小结

先从线程安全,是否允许null键值,使用场景方面说出来HashTable,HashMap,TreeMap的区别。 然后扩展到了Map的类层级,分析了面试官喜欢问的hashmap的数据结构,hash值计算,扩容,树化问题。

图片
图片
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-04-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 李福春持续输出 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Map的类层级
  • HashMap的源码分析
    • put操作涉及的关键源码如下:
    • hashMap的hash值的计算:
    • 扩容逻辑
    • 树化
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档