HashMap 和 Hashtable 有什么区别?我们如何决定使用 HashMap 还是 TreeMap?

发布于:2022-11-28 ⋅ 阅读:(168) ⋅ 点赞:(0)

HashMap:

HashMap是一个散列表,它存储的数据内容是键值对(Key-Value)的映射.

HashMap实现了Map接口, 根据键的HashCode值存储数据, 具有很快的访问速度, 最多允许一条记录的键为null值, 不支持线程同步.

HashMap是无序的,即 不会记录插入的顺序.

HashMap继承自AbstractMap,都实现了Map,  cloneable(可克隆),  Serializable(可序列化)  接口

Hash Table:

Hashtable是原始的java.util的一部分, 是一个Dictionary(排序接口)具体的实现.

然而, java 2 重构的Hashtable实现了Map接口, 因此, Hashtable现在集成到了集合框架中.它和HashMap类很相似, 但是它支持同步,HashMap不支持同步。

像HashMap一样, Hashtable在哈希表中存储键值对. 当使用一个哈希表, 要指定用作键的对象, 以及要连接到该键的值. 然后, 该键经过哈希处理, 所得到的散列码被用作存储在该表中值的索引.

两者的区别:

1.继承的父类不同

Hashtable继承于Dictionary类, 而HashMap继承于AbstractMap类. 但是两者都同时实现了Map, Cloneable(可复制), Serializable(可序列化)这三个接口.

2.内部实现使用的数组初始化和扩容方式不同

HashMap原始的数组长度为16, 而HashTable原始的数组长度为11.

HashMap要求底层数组的容量必须是2的整数次幂. HashTable不要求底层数组的容量必须是2的整数次幂.

HashMap的扩容机制是当前数组长度*2, 而HashTable扩容机制是当前长度*2-1,当数组容量达到当前数组长度的四分之三时,就会开始扩容.

创建的时候如果给定了容量的初始值,那么HashMap会将其扩充为2的幂次方大小,而HashTable会直接使用你给定的大小.

HashMap总是使用2的幂次作为哈希表的大小,而HashTable会尽量使用素数(质数),奇数.

3.底层不同

HashMap:

        在JDK 8之前: 底层由数组+链表构成.

        在JDK 8之后:底层由数组+链表+红黑树构成.

HashTable:

        底层由数组+链表组成.

HashMap使用红黑树的时机

红黑树是一个趋于自平衡的二叉排序树(左右子树深度差的绝对值不超过1).当HashMap链表长度超过8的时候,底层会自动转换成红黑树.

4.添加Key  Value的hash算法不同

HashMap添加元素时,是使用自定义的哈希算法,哈希算法是一种加密的摘要算法,哈希算法的加密是单向的,不可用密文解密得到明文;其作用是对任意的数据输入,计算得到一个固定长度的输出摘要;目的是为了校验数据是否被篡改,常用的哈希算法有4种

1.MD5算法

  1. 通过MessageDigest类的单例模式创建其对象
  2. 通过对象的update()方法更新原始数据
  3. 通过对象的digest()方法加密,返回值为字节数组
  4. 可通过StringBuilder类,将其转换成十六进制的字符串
  5. MD5加密后的字节数组为16字节,其转换成十六进制的字符串长度为32位
  6. 代码演示
  7. public static void main(String[] args) throws NoSuchAlgorithmException {
        MessageDigest md = MessageDigest.getInstance("MD");
        md.update("helloWord".getBytes());
        byte[] result = md.digest();
        StringBuilder sb = new StringBuilder();
        for (byte bite : result) {
            sb.append(String.format("%02x", bite));
        }
        System.out.print(sb);
    }

2.SHA-1算法
通过MessageDigest类的单例模式创建其对象
通过其对象的update()方法更新原始数据
通过其对象的digest()方法加密,其返回值为字节数组
通过StringBuilder类,将字节数组转换成字符串
SHA-1算法加密后的字节数组为20字节,其转换成十六进制的字符串长度为40位

代码演示

public static void main(String[] args) throws NoSuchAlgorithmException {
    String password = "重生之我的程序人生";
    MessageDigest digest = MessageDigest.getInstance("SHA-1");
    digest.update(password.getBytes());
    byte[] Array = digest.digest();
    StringBuilder sb = new StringBuilder();
    for (byte bite : Array) {
        sb.append(String.format("%02x", bite));
    }
    System.out.println(sb);
}

哈希算法加密中的MD5算法思路和SHA-1算法思路大同小异,两者除了对明文进行摘要加密还可以进行“加盐”;通过获取随机的数据,将此数据更新至明文中,一起加密;可在一定程度上防“彩虹表”的攻击。

3.HmacMD5算法

在Hmac算法加密时,分成密钥和加密两个部分:

    生成密钥:
        通过KeyGenerator类的单例模式创建其对象
        其对象通过generateKey()方法生成Key
        通过Key的getEncoded()方法生成密钥的字节数组
        通过StringBuilder类,将密钥的字节数组转换成十六进行的字符串
    加密:
    1.通过Mac类的单例模式创建其对象
    2.通过对象的Init()方法初始化密钥
    3.通过对象的update()方法更新数据
    4.通过对象的doFinal()方法加密,其返回值为加密的字节数组
    5.通过StringBuilder类,将加密的字节数组转换成十六进制的字符串
    生成的密钥为字节数为64字节,其加密后的字节数组为16字节

代码演示

String passworf = "重生之我的程序人生";

// 生成密钥
KeyGenerator keyGen = KeyGenerator.getInstance("HmacMD5");
SecretKey key = keyGen.generateKey();
byte[] keyByteArray = key.getEncoded();
// 加密
Mac mac = Mac.getInstance("HmacMD5");
mac.init(key);
mac.update(passworf.getBytes());
byte[] resultArray = mac.doFinal();
StringBuilder sb = new StringBuilder();
for(byte bite:resultArray) {
    sb.append(String.format("%02x", bite));
}
System.out.println("加密的内容:"+sb);


4.RipeMD160算法

此算法的加密基于BouncyCastle提供的第三方库

    注册BouncyCastle提供的注册类对象BouncyCastleProvider
    通过MessageDigest类的单例模式创建其对象
    通过其对象的update()方法,更新原始数据
    通过其对象的digest()方法,进行加密,其返回值为字节数组
    可通过BigInteger()方法,将字节数组转换成十六进制的字符串
    加密后字节数组为20字节,其转换成十六进制为40位
代码演示

// 添加第三方库的对象
Security.addProvider(new BouncyCastleProvider());
MessageDigest digest = MessageDigest.getInstance("RipeMD160");
digest.update("helloworld".getBytes());
byte[] result = digest.digest();
String hex = new BigInteger(1, result).toString(16);
System.out.println("字符串的内容" + hex);

哈希算法进行加密,都是摘要加密;加密后不可逆,也就是不可通过解密得到明文。

2.而HashTable是直接采用key的hashCode()

5.线程安全性

HashMap是线程不安全的,多线程的情况之下会造成并发的冲突,但是单线程的情况之下运行的效率比较高.

HashTable是线程安全的,很多方法都是用synchronized修饰,但因为加了锁导致并发的效率比较低,单线程环境效率也很低.

另外在HashMap结构中,是允许保存null值的,Entry.Key和Entry.Value均可以为null.但是HashTable中是不允许保存null的.

6.实现方式不同

HashMap: 基于哈希表实现。使用HashMap要求添加的键类明确定义了hashCode()和equals()[可以重写hashCode()和equals()],为了优化HashMap空间的使用,我们可以调优初始容量和负载因子。

HashMap(): 构建一个空的哈希映像
HashMap(Map m): 构建一个哈希映像,并且添加映像m的所有映射
HashMap(int initialCapacity): 构建一个拥有特定容量的空的哈希映像
HashMap(int initialCapacity, float loadFactor): 构建一个拥有特定容量和加载因子的空的哈希映像


TreeMap: 基于红黑树实现。TreeMap没有调优选项,因为该树总处于平衡状态。

TreeMap():构建一个空的映像树
TreeMap(Map m): 构建一个映像树,并且添加映像m中所有元素
TreeMap(Comparator c): 构建一个映像树,并且使用特定的比较器对关键字进行排序
TreeMap(SortedMap s): 构建一个映像树,添加映像树s中所有映射,并且使用与有序映像s相同的比较器排序

我们如何决定使用HashMap还是TreeMap呢?

HashMap<K,V>的Key值实现散列HashCode(),分布是散列的,均匀的,并不支持排序,数据结构主要是数组,链表,红黑树. 适用于在Map中进行插入,删除和定位元素的操作.

TreeMap<K,V>的Key值是要求实现java.lang.Comparable(可比较),所以TreeMap迭代的时候默认的是Key值升序排序的,TreeMap的实现是基于红黑树结构.适用于按自然顺序或者自定义顺序来遍历Key值.

因为HashMap中的元素的排列顺序是不固定的,所以如果你需要得到一个有序的结果则建议使用TreeMap.

但是HashMap有更好的性能,所以大多不需要排序的时候,则建议使用HashMap。
 

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到