HashTable、HashMap、TreeMap的区别

在不考虑线程安全使用的时候,HashMap使用的时候居多,是因为它具有的API都很直接很方便,在面试的时候这些集合属性也是面试的焦点,但是也多半是问一下线程安全方面的问题,对比之间的不同,现在不会主要去讲述线程安全,而是从基础的角度出发,对这三种集合属性进行一下简单的介绍。

简介

  • 三者都是一些常见的集合属性的具体实现,以键值对的方式进行存储并进行对数据的操作。
  • HashMap是应用十分广泛的一个哈希表实现,对数据的存储和操作十分方便,而且在初学的时候,也会大量使用到,所以看起来是十分入门的一个Map的实现方法。但和HashTable的主要区别在于,HashMap是异步的,支持Null值插入,且get或者put操作性能很强。
  • HashTable是早期Java类库提供的一个哈希表的实现,本身就是同步的,使用的时候对性能开销很大,无法插入Null值。
  • TreeMap是基于红黑树的Map实现,但是get或者put时间复杂度相对比较高。

扩展

从刚才的描述不难看出,HashMap看起来是Map实现的最好的方法,时间复杂度低,性能高,并且消耗的资源还少。

但是只限于单线程,学者们很清楚HashMap属于线程不安全的实现,因此程序中难免会出现无限循环占用CPU、size不准确等等一些的问题产生。

接下来对Map进行一下细节的了解:

首先,我们要对Map的相关类型有一个整体的了解,Map虽然通常被包括在Java集合框架中,但是其本身并不是狭义上的集合类型(Collection),具体可以看这张图:
在这里插入图片描述

HashTable是相对比较特别的方法,它作为类似Vector、Stack的早期集合相关类型,扩展了Dictionary类,在类结构上与HashMap之类明显不同。

而其他的Map实现,比如说HashMap则是扩展了AbstractMap,里面包含了很多通用方法的抽象类。不同的Map用途,在类图结构上就能体现出来,设计的目的已经体现在不同的接口上。

一般使用Map的也就是放入、访问或者删除,这些操作对顺序并没有特殊的要求,因此才会让HashMap成为最常用的Map实现方法。但是HashMap的性能更多还是依赖于哈希码的有效性,所以要掌握hashCode和equals的一些基本约定,比如:

  • equals相等,hashCode也一定要相等
  • 重写了hashCode也要重写equals
  • hashCode保持一致性,状态改变返回的哈希值也仍然要一致
  • equals的对称、反射、传递等特性

虽然创建一个实现顺序存储的Map实现可以用LinkedHashMap和TreeMap都可以保证某种顺序,但是二者还是有一定的不同:LinkedHashMap通常提供的是遍历顺序符合插入顺序,它的实现是通过条目(键值对)维护双向链表。注意,通过特定构造函数,我们可以创建反映访问顺序的实例,所谓的put、get、compute等,都算作“访问”。

这种行为适用于一些特定应用场景,构建一个空间占用敏感的资源池,希望可以自动将最不常被访问的对象释放掉,这就可以利用LinkedHashMap提供的机制来实现:

import java.util.LinkedHashMap;
import java.util.Map;

public class linkedHashMapSample {
    public static void main(String[] args) {
        //实现自定义删除策略,否则行为就和普遍Map没有区别 
        LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap<String,String>(16, 0.75F, true){
            @Override
            protected boolean removeEldestEntry(Map.Entry<String, String> eldest) {
                return size() > 3;
            }
        };
        linkedHashMap.put("project1", "Valhalla");
        linkedHashMap.put("project2", "Panama");
        linkedHashMap.put("project3", "Loom");
        linkedHashMap.forEach((k,v)->{
            System.out.println(k + ":" + v);
        });
        //模拟访问
        linkedHashMap.get("project1");
        linkedHashMap.get("project2");
        linkedHashMap.get("project3");
        System.out.println("Iterate over should be not affected:");
        linkedHashMap.forEach((k,v)->{
            System.out.println(k + ":" + v);
        });
        //触发删除
        linkedHashMap.put("project4", "Mission Control");
        System.out.println("Oldest entry should be removed:");
        linkedHashMap.forEach((k, v) ->{
            System.out.println(k + ":" + v);
        });
    }
}

/**
 * output:
 * project1:Valhalla
 * project2:Panama
 * project3:Loom
 * Iterate over should be not affected:
 * project1:Valhalla
 * project2:Panama
 * project3:Loom
 * Oldest entry should be removed:
 * project2:Panama
 * project3:Loom
 * project4:Mission Control
 */

可以发现,当实现条件(存储单元超过3就自动删除第一个)就可以类似队列做到先进先出。

而对于TreeMap,它的整体顺序是由键的顺序关系决定的,通过Comparator或Comparable(自然顺序)来决定。

类似于刚才提到的HashCode与equals的约定,为了避免模糊不清的情况,自然顺序同样需要符合一个约定,就是compareTo的返回值需要和equals一致,否则就会出现模棱两可的情况。
可以分析TreeMap的put方法:

public V put(K var1, V var2) {
        TreeMap.Entry var3 = this.root;
        if (var3 == null) {
            this.compare(var1, var1);
            this.root = new TreeMap.Entry(var1, var2, (TreeMap.Entry)null);
            this.size = 1;
            ++this.modCount;
            return null;
        } else {
            Comparator var6 = this.comparator;
            int var4;
            TreeMap.Entry var5;
            if (var6 != null) {
                do {
                    var5 = var3;
                    var4 = var6.compare(var1, var3.key);
                    if (var4 < 0) {
                        var3 = var3.left;
                    } else {
                        if (var4 <= 0) {
                            return var3.setValue(var2);
                        }

                        var3 = var3.right;
                    }
                } while(var3 != null);
            } else {
                if (var1 == null) {
                    throw new NullPointerException();
                }

                Comparable var7 = (Comparable)var1;

                do {
                    var5 = var3;
                    var4 = var7.compareTo(var3.key);
                    if (var4 < 0) {
                        var3 = var3.left;
                    } else {
                        if (var4 <= 0) {
                            return var3.setValue(var2);
                        }

                        var3 = var3.right;
                    }
                } while(var3 != null);
            }

            TreeMap.Entry var8 = new TreeMap.Entry(var1, var2, var5);
            if (var4 < 0) {
                var5.left = var8;
            } else {
                var5.right = var8;
            }

            this.fixAfterInsertion(var8);
            ++this.size;
            ++this.modCount;
            return null;
        }
    }

从底层代码不难看出,如果不遵守约定,就会被当做同一个。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值