集合-HashTable源码详解(增删改查)以及HashMap与HashTable的异同点

版权声明:中华人民共和国持有版权 https://blog.csdn.net/Fly_Fly_Zhang/article/details/87932967
特点:
  • 底层数据结构:链表加数组;
  • null值:key,value均不能为null;
  • key重复性:key不能重复;
  • 有序性:不能保证插入有序;
  • 是线程安全的;
增长方式 :2*table.length+1;
继承关系:
public class Hashtable<K,V>
    extends Dictionary<K,V>  //jdk较早提供的一个实现类
    implements Map<K,V>,//拥有Map接口的属性
     Cloneable, //表明该类可以进行clone
     java.io.Serializable { //序列化
默认属性:
private transient Entry<K,V>[] table; //数组
private transient int count; //集合中元素个数
    private int threshold;//阈值
 private float loadFactor;//加载因子
  private transient int modCount = 0;//版本号 

默认值:
  • HashTable默认数组大小为 11;
  • 加载因子 0.75;
构造函数:
   public Hashtable(int initialCapacity, float loadFactor) { //数组大小,加载因子
        if (initialCapacity < 0)//传入数组大小小于0抛出异常
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        if (loadFactor <= 0 || Float.isNaN(loadFactor)) //加载因子异常
            throw new IllegalArgumentException("Illegal Load: "+loadFactor);

        if (initialCapacity==0)//传入数组大小为0默认创建长度为1的数组
            initialCapacity = 1;
        this.loadFactor = loadFactor;
        table = new Entry[initialCapacity]; //创建数组
        threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
        //得到新的阈值
        initHashSeedAsNeeded(initialCapacity);
    }

    public Hashtable(int initialCapacity) {
        this(initialCapacity, 0.75f);//只传入数组大小,默认加载因子
    }

    public Hashtable() {//无参,默认数组大小为11;
        this(11, 0.75f);
    }

    public Hashtable(Map<? extends K, ? extends V> t) { //传入一个Map集合
        this(Math.max(2*t.size(), 11), 0.75f); //创建一个传入集合元素二倍的数组
        putAll(t);//将集合中所有元素添加进新的集合
    }

CRUD(增删改查):

put(): 添加元素
  1. 判断value不能为null,若为null,则抛出异常;推断出value不能为null;
  2. 通过key进行hash获取该存储的索引位置;
  3. 该索引位置的链表进行遍历,获取key是否存在(key存在条件==》 hash相等且key.equeals为true);
  4. 在存在key的情况下,将value值进行更新且直接返回;
  5. key不存在则进行新结点插入逻辑;
    5.1:扩容考虑:entry节点个数大于阈值 ==》(count>threshold)进行扩容
    5.2:新容量为2*table.length+1;
    5.3: 将原hash表中的数据全部进行重新hash到新的hash表中;
    5.4:更新插入的key的新的位置;
    5.5:找到新节点位置,创建entry实体,通过头插法插入链表中;
      public synchronized V put(K key, V value) {
      //注意一点:那就是key和value都不能为null
        // Make sure the value is not null
        if (value == null) {//值不能为null
            throw new NullPointerException();
        }

        // Makes sure the key is not already in the hashtable.
        Entry tab[] = table;
        int hash = hash(key); //key也不能为null,key为null进行hash会抛出空指针问题;
        int index = (hash & 0x7FFFFFFF) % tab.length; //通过key的hash找到存储位置;
        for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
            if ((e.hash == hash) && e.key.equals(key)) { 
            //这里的判断条件于HashMap相比少了==判断,因为HashMap的值可以为null必须用==判断
                V old = e.value; //value值进行更新
                e.value = value;
                return old;
            }
        }

        modCount++;
        //走到这里说明原集合中没有key
        if (count >= threshold) {//集合中元素个数大于阈值,需要进行扩容;
            // Rehash the table if the threshold is exceeded
            rehash();

            tab = table;
            hash = hash(key);
            index = (hash & 0x7FFFFFFF) % tab.length;
        }

        // Creates the new entry.
        Entry<K,V> e = tab[index];
        tab[index] = new Entry<>(hash, key, value, e);//新结点头插到链表中
        count++;
        return null;
    }
    protected void rehash() { //对数组进行扩容
        int oldCapacity = table.length;
        Entry<K,V>[] oldMap = table;

        // overflow-conscious code
        int newCapacity = (oldCapacity << 1) + 1; //扩容方式:二倍+1
        if (newCapacity - MAX_ARRAY_SIZE > 0) {
        //新的数组长度大于默认最大长度,则数组长度取默认长度
            if (oldCapacity == MAX_ARRAY_SIZE)
                // Keep running with MAX_ARRAY_SIZE buckets
                return;
            newCapacity = MAX_ARRAY_SIZE;
        }
        Entry<K,V>[] newMap = new Entry[newCapacity];//创建新的数组长度

        modCount++;
        threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
        //计算出新的阈值
        
        boolean rehash = initHashSeedAsNeeded(newCapacity);
        //根据新的长度判断是否进行rehash操作;
        table = newMap;

        for (int i = oldCapacity ; i-- > 0 ;) {对旧数组遍历
            for (Entry<K,V> old = oldMap[i] ; old != null ; ) {对每个链表进行遍历
                Entry<K,V> e = old;
                old = old.next;

                if (rehash) {
                    e.hash = hash(e.key);//得到新的hash值
                }
                int index = (e.hash & 0x7FFFFFFF) % newCapacity;
                e.next = newMap[index];//将e进行头插到新的角标链表中
                newMap[index] = e;
            }
        }
    }

remove(Object key) :删除元素:删除操作本身也是线程安全的;
  1. 通过key找到当前存储的索引位置;
  2. 对该节点索引位置链表进行遍历,找到key锁对应的entry实体,并进行删除;
     public synchronized V remove(Object key) {
        Entry tab[] = table;
        int hash = hash(key);
        int index = (hash & 0x7FFFFFFF) % tab.length; 
        //通过Hash确定key在数组中的位置
        for (Entry<K,V> e = tab[index], prev = null ; e != null ; prev = e, e = e.next) {
        //遍历当前角标的链表哦
            if ((e.hash == hash) && e.key.equals(key)) {
            //因为没有null值所以不需要==判断null值
                modCount++;
                if (prev != null) {//非头结点
                    prev.next = e.next;
                } else {//头结点
                    tab[index] = e.next;
                }
                count--;
                V oldValue = e.value;
                e.value = null;//方便回收
                return oldValue;
            }
        }
        return null; //没有找到,返回null;
    }
get(Object key) : 通过key得到value; get方法本身也具有线程安全性;
get过程:
  1. 通过key来hash获取到存储位置==》key为null会在hash的时候抛出空指针异常;
  2. 遍历当前索引位置节点,判断是否相等(hash&&equals),找到返回value值;
  3. 未找到返回null值;
 public synchronized V get(Object key) {
        Entry tab[] = table;
        int hash = hash(key);
        int index = (hash & 0x7FFFFFFF) % tab.length;
        //通过Hash得到对应角标
        for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
        //遍历当前角标的链表,找到key,从而得到value
            if ((e.hash == hash) && e.key.equals(key)) {
                return e.value;
            }
        }
        return null; //没有找到
    }

HashMap与HashTable异同点

相同点:
  • 底层数组结构都是数组加链表;
  • key值都不能重复;
  • 插入元素都不能保证插入有序;
  • 哈希过程都是通过Key进行hash的;
不同点:
  • 安全性问题:HashMap不能保证线程安全;HashTable能保证线程安全
  • 效率问题:HashMap在单线程效率高;HashTable在单线程效率低:
  • 继承关系:HashMap继承自AbstractMap ; HashTable继承自Dictionary;
  • null值问题:HashMap==》 key,value都可以为null ;HashTable==》都不能为null;
  • 扩容方式: HashMap: 2table.length (2的指数级扩容) ; HashTable: 2table.length+1;
  • 默认值:HashMap默认数组大小为16; HashTable默认数组大小为11;
  • Hash算法不同;
拓展问题:HashMap不具有线程安全性,如何让其在多线程条件下具有安全性?

使用集合工具类Collections使HashMap具有线程安全: Collections.synchronizedMap();

HashTable能保证线程安全的原理:

HashTable对相应方法添加Synchronized关键字,该关键字是一种互斥锁,互斥锁的目的是保证同一时刻只能有一个线程对资源进行访问;
HashTable对put,get等一般方法添加Synchronized关键字,修饰的是类对象,该对象调用put操作即为该HashTable对象添加了一个互斥锁,那么在同一时刻只能有一个线程访问HashTable,从而保证添加元素不回出现异常;

猜你喜欢

转载自blog.csdn.net/Fly_Fly_Zhang/article/details/87932967