HashMap、HashTable、HashSet的区别

1. HashMap与HashTable的区别

1.1 线程是否安全:

  • HashMap 是⾮线程安全的
  • HashTable 是线程安全的,
  • 因为 HashTable 内部的⽅法基本都经过 synchronized 修饰。
  • 如果你要保证线程安全的话可以使用ConcurrentHashMap ,具体可以参考ArrayList、HashSet、HashMap是线程不安全

1.2 效率:

  • 因为HashTable通过synchronized 保证了线程安全,所以从效率上看 ,HashMap 要⽐ HashTable 效率⾼⼀点。另外, HashTable现在基本被淘汰(不要在代码中使⽤它);

1.3 对 Null key 和 Null value 的⽀持:

  • HashMap 可以存储 null 的 key 和 value,但 null 作为键(key)只能有⼀个,null 作为值(value)可以有多个;

  • HashTable 不允许有 null 键(key)和 null 值(key),否则会抛出NullPointerException 。

Exception in thread "main" java.lang.NullPointerException

1.4 初始容量⼤⼩和每次扩充容量⼤⼩的不同 :

  • ① 创建时如果不指定容量初始值, Hashtable默认的初始⼤⼩为 11,之后每次扩充,容量变为原来的 2n+1。 HashMap 默认的初始化⼤小为 16。之后每次扩充,容量变为原来的 2 倍。

  • ② 创建时如果给定了容量初始值,那么Hashtable 会直接使⽤你给定的⼤⼩,⽽ HashMap 会将其扩充为 2 的幂次⽅⼤⼩。也就是说 HashMap 总是使⽤ 2 的幂作为哈希表的⼤⼩。

    /**
     * Constructs an empty <tt>HashMap</tt> with the specified initial
     * capacity and load factor.
     *
     * @param  initialCapacity the initial capacity
     * @param  loadFactor      the load factor
     * @throws IllegalArgumentException if the initial capacity is negative
     *         or the load factor is nonpositive
     */
    public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
    }

    /**
     * Constructs an empty <tt>HashMap</tt> with the specified initial
     * capacity and the default load factor (0.75).
     *
     * @param  initialCapacity the initial capacity.
     * @throws IllegalArgumentException if the initial capacity is negative.
     */
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

下⾯这个⽅法保证了 HashMap 总是使⽤2的幂作为哈希表的⼤⼩。

    /**
     * Returns a power of two size for the given target capacity.
     */
    static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

1.5 底层数据结构:

  • JDK1.8 以后的 HashMap 在解决哈希冲突时有了⼤的变化,当链表⻓度⼤于阈值(默认为 8)(不过将链表转换成红⿊树前会判断,如果当前数组的⻓度⼩于 64,那么会选择先进⾏数组扩容,⽽不是转换为红⿊树)时,将链表转化为红⿊树,以减少搜索时间。
  • 而Hashtable 没有这样的机制。

2. HashMap与HashSet的区别

HashSet 底层就是基于 HashMap 实现的。( HashSet 的源码⾮常⾮常少,因为除了 clone() 、 writeObject() 、 readObject() 是 HashSet⾃⼰不得不实现之外,其他⽅法都是直接调⽤ HashMap 中的⽅法。

2.1 接口

  • HashMap实现了Map接口,HashSet实现了Set接口
public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable
public class HashSet<E> extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable

HashSet实现了Set接口,其内部不允许出现重复的值,如果我们将一个对象存入HashSet,必须重写equals()和hashCode()方法,这样才能确保集合中不存在同一个元素。HashSet的内部是无序的,因此不能使用 hashset.get(index) 来获取元素。

HashMap实现了Map接口,其内容是键值对的映射(key->value),不允许出现相同的键(key)。在查询的时候会根据给出的键来查询对应的值。

我们可以认为,HashSet和HashMap增查操作的时间复杂度都是常数级的。

2.2 存储值的方式

  • HashMap以key,value的形式存储对象,HashSet仅以value的形式存储存储

2.3 添加元素的方式

  • HashMap调⽤ put() 向 map 中添加元素,HashSet调⽤ add() ⽅法向 Set 中添加元素(不过底层调用的还是HashMap的put方法)
    //HashMap的:
    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
    //HashSet的:
    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

2.4 HashCode计算的选取值

  • HashMap 使⽤键(Key)计算HashCode;
  • HashSet 使⽤成员对象来计算 HashCode值,对于两个对象来说HashCode可能相同,所以 equals() ⽅法⽤来判断对象的相等性,如果两个对象不同的话,那么返回false。

2.5 存储对象的过程

2.5.1 HashMap存储对象的过程

  1. 对HashMap的Key调用hashCode方法,返回int值,即对应的hashCode;
  2. 把此HashCode作为哈希表的索引,查询哈希表的相应位置,若当前位置内容为null,则把HashMap的Key、Value包装成Entry数组,放入当前位置;
  3. 若当前位置内容不为空,则继续查询当前索引存放的链表,利用equals方法,找到Key相对应的Entry数组,则用当前Value去替换旧的Value;
  4. 若未找到与当前Key值相同的对象,则把当前位置的链表后移(Entry数组持有一个指向下一个元素的引用),把新的Entry数组放在链表表头。

2.5.2 HashSet存储对象的过程

  1. 往HashSet添加元素的时候,HashSet会调用元素的hashCode方法得到元素的哈希值;
  2. 然后通过元素的哈希值经过移位等运算,就可以算出该元素在哈希表的存储位置,存储时会出现两种情况:
  • 情况1:如果算出元素存储位置目前没有任何元素存储,那么该元素可以直接存储到该位置上。
  • 情况2:如果算出该元素的存储位置目前已经存在有其他的元素,那么会调用该元素的equals方法与该位置的元素再比较一次,如果equals返回的是true,那么该元素与这个位置上的元素就视为重复元素,不需要添加,如果equals方法返回的是false,那么该元素运行添加。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值