哈希集合(一)

本文详细探讨了哈希表结构,包括哈希函数、哈希冲突及其解决方法。接着,深入分析了Java中的HashMap和HashTable,比较了它们的特性、源码结构、插入、删除和查找操作,以及在多线程环境下的表现。HashMap是非线程安全的,允许null键值,而HashTable则是线程安全且不允许null键值。最后,讨论了两者在扩容策略和哈希算法上的差异。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

哈希表结构

哈希表是一种通过关键码去寻找值的映射的数据结构

哈希表就需要通过哈希函数将特定的键映射到特定值的数据结构,维护了键和值之间的对应关系
:称之为关键字,唯一的表示要存储的数据,也可以是数据本身或者是数据的一部分
哈希函数:将键映射到数据对应存放的位置的函数
哈希冲突:哈希函数将两个不同或者更多的不同的键映射到同一个索引位置

哈希表结构解决两个问题:合理的构建哈希函数,如何解决哈希冲突

哈希函数
直接寻址法:H(key) = key或者H(key) = a.key+b(a,b为常量)
除留余数法:H(key) = key mod p(p<m,m表示表长)

哈希冲突解决
链地址法:在冲突的地方通过链表来解决
h(x) = x mod 10

线性探测法
在这里插入图片描述
线性探测:H(i) = i 或者H(i)=i*c(c是常量)
随机探测:H(i) =randon(i)

Map接口:

Map接口是顶层接口,特点是数据以键值对的形式存储

Map接口中提供的方法:
在这里插入图片描述在这里插入图片描述

    HashMap<String ,String> hashMap = new HashMap<>();
        /**
         * int size()
         * 表示Map集合中元素的个数
         * 返回int类型表示数据个数
         */
        int size = hashMap.size();
        System.out.println(size);
        /**
         * V put(K key, V value)
         * 将指定的键key和value存储到集合中,如果当前插入的数据的key已经存在,最新的value会覆盖原有的value
         * 返回值是键值对的值的类型
         *  map接口下集合特点:key不能重复
         * 如果当前key不存在时,插入数据返回为null
         * 如果当前key存在时,返回旧的value值
         */
        String put = hashMap.put("k1","v1");
        System.out.println(put);

        String put1 = hashMap.put("k1","v2");
        System.out.println(put1);
        /**
         * V get(Object key)
         * 获取键所映射的值,如果不存在则返回为null
         */
        String k1 = hashMap.get("k1");
        System.out.println(k1);
        String k2 = hashMap.get("k2");
        System.out.println(k2);
        /**
         * boolean isEmpty()
         * 判断集合是否为空
         * 返回时boolean类型,true表示空,false表示不为空
         */
        hashMap.isEmpty();
        /**
         * boolean containsKey(Object key)
         * 判断map集合中是否存在指定的键
         * 参数key:需要判断是否存在指定的键
         * 返回的是Boolean类型
         */
       boolean k11 =  hashMap.containsKey("k3");
        System.out.println(k11);

        /**
         * boolean containsValue(Object value)
         * *判断map集合中是否存在指定的值
         *参数:需要判断是否存在指定的值
         * 返回的是Boolean类型
         */
        boolean v11 = hashMap.containsValue("v1");
        System.out.println(v11);

        /**
         * V remove(Object key)
         * 通过key删除集合中该key对应的键值对
         * 参数key:指定的键
         * 返回值;是value值,如果key不存在返回null
         */
        String k12 = hashMap.remove("k1");

        /**
         * void clear()
         * 删除集合中所有的元素
         */
        hashMap.clear();
        /**
         * Set<Map.Entry<K,V>> entrySet()
         * 将map集合转化为set集合,多用于遍历键值对
         *
         */
        Set<Map.Entry<String,String>> entries = hashMap.entrySet();
        //map接口按照键值对遍历
       Iterator<Map.Entry<String,String>> iterator = hashMap.entrySet().iterator();
       while (iterator.hasNext()){
           Map.Entry<String,String> entry = iterator.next();
           String key = entry.getKey();
           String value = entry.getValue();
           System.out.println(key+"<:>"+value);
       }
        /**
         * Set<K> keySet()
         * 获取map集合中所有的键的set集合,多用于遍历键
         */
       hashMap.keySet();
       //遍历map中所有的键
        System.out.println("遍历键---");
       Iterator<String> iterator1 =  hashMap.keySet().iterator();
       while (iterator1.hasNext()){
           System.out.println(iterator1.next());
       }
        /**
         * Collection<V> values()
         * 获取map集合中所有值的collection的集合,多用于值的遍历
         */
       hashMap.values();

       //遍历map中所有的值
        System.out.println("值的遍历------");
        Iterator<String> iterator2 =  hashMap.values().iterator();
        while (iterator2.hasNext()){
            System.out.println(iterator2.next());
        }

Map集合中实现类遍历的三种方式:

        //map接口按照键值对遍历
       Iterator<Map.Entry<String,String>> iterator = hashMap.entrySet().iterator();
       while (iterator.hasNext()){
           Map.Entry<String,String> entry = iterator.next();
           String key = entry.getKey();
           String value = entry.getValue();
           System.out.println(key+"<:>"+value);
       }
     
       //遍历map中所有的键
        System.out.println("遍历键---");
       Iterator<String> iterator1 =  hashMap.keySet().iterator();
       while (iterator1.hasNext()){
           System.out.println(iterator1.next());
       }
      
       //遍历map中所有的值
        System.out.println("值的遍历------");
        Iterator<String> iterator2 =  hashMap.values().iterator();
        while (iterator2.hasNext()){
            System.out.println(iterator2.next());
        }

HashMap

1、HashMap中key不能重复、value值可以重复
2、HashMap中key和value都可以为null
3、不能保证插入的数据的顺序,随着时间的推移,同一个元素的位置可能会发生改变
4、底层采用的数据结构是哈希表结构

通过HashMap源码结构来研究HashMap的原理及特点:
1、继承机构
2、属性及默认值
3、构造函数
4、底层数据结构
5、扩容机制
6、常见方法解析

继承结构:

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable

HashMap实现了Map接口
实现了Cloneable接口,并且实现了Serializable接口,能够实现对象的序列化问题
HashMap同时继承自AbstractMap和Map接口,AbstractMap声明如下:

public abstract class AbstractMap<K,V> implements Map<K,V>

对于Map接口中通用的方法做了实现,方便子类字节复用,AbstractMap相当于辅助类,提供了一些操作的默认实现,具体的子类中如果没有特殊行为,可以直接使用AbstractMap提供的实现

属性及默认值:

   //map中默认的容量初始值:16  ->2^4
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

    //map的最大限制值 2^30
    static final int MAXIMUM_CAPACITY = 1 << 30;

    //默认的加载因子:和HashMap扩容相关的参数  
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    //空数组结构
    static final Entry<?,?>[] EMPTY_TABLE = {};

    //集合元素存放的位置table
    transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;

    //记录集合存储的数据个数,指的是entry实体的个数
    transient int size;

    //扩容阈值: 扩容阈值= capacity * load factor	
    int threshold;

    //加载因子 加载因子范围 0< loadFactor < 1
    final float loadFactor;

   //版本控制号,和业务无关
    transient int modCount;

底层的数据结构:

//集合元素存放的位置table:table本身是一个数组,数组中存储的数据类型Entry类型
transient Entry<K,V>[] table


 static class Entry<K,V> implements Map.Entry<K,V> {
        final K key; //关键字key
        V value; //键值对的value
        Entry<K,V> next; //下一个节点  
        int hash; //哈希值

        //entry构造函数
        Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }

        public final K getKey() {
            return key;
        }

        public final V getValue() {
            return value;
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }
   }

底层存储数据是table属性,table是一个数组,数组存放的数据类型是entry实体,而entry节点本身带有next指针,可以构建单向链表,即底层数据结构是数组+链表,本质上是哈希表,哈希冲突采用的是链地址法

构造函数:

//通过初始容量 initialCapacity和加载因子loadFactor来初始化容量
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;
        threshold = initialCapacity;
        init();
    //并未真正实例化HashMap实例,主要是进行参数设置,在插入元素(第一个元素)才进行真正实例化,延时实例化
    }

    //通过初始容量initialCapacity来实例化对象,加载因子是默认值:0.75
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

    //无参的构造函数 初始容量是16,加载因子是0.75
    public HashMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
    }

    

   //通过map的实例集合来实例化HashMap
    public HashMap(Map<? extends K, ? extends V> m) {
        this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
                      DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
        inflateTable(threshold);

        putAllForCreate(m);
    }

常见方法:
put方法:

public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            //在插入第一个元素时集合为空,做集合初始化
            inflateTable(threshold);
        }
        //当key为null时,将键值对特殊处理存放在索引位置为0的索引位置,直接返回
        if (key == null)
            return putForNullKey(value);
    
        //key不为null,按照以下逻辑处理
        //通过key进行哈希,找到当前key存放的位置i
        int hash = hash(key);
        int i = indexFor(hash, table.length);
        //通过位置找到该位置的起始节点,进行遍历,需要先判断key是否存在,key存在则更新value值
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                //key已经存在
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        //说明当前key不存在,需要新增一个节点来存放key-value键值对
        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

//新增节点
 void addEntry(int hash, K key, V value, int bucketIndex) {
        //扩容时机,当size大于扩容阈值时,要考虑扩容
        //将容量扩容为原大小的2倍,并且将原哈希表上的所有节点重新哈希到新哈希表中
        if ((size >= threshold) && (null != table[bucketIndex])) {
            resize(2 * table.length);
            hash = (null != key) ? hash(key) : 0;
            //待插入的节点是原节点的2倍关系,因此需要对映射的位置重新设定
            bucketIndex = indexFor(hash, table.length);
        }

        createEntry(hash, key, value, bucketIndex);
    }

//新构建一个entry节点,并将bucketIndex位置的头结点作为新节点的下一个节点,即新插入节点作为头结点
 void createEntry(int hash, K key, V value, int bucketIndex) {
        Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        size++;
    }

void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }

        //newCapacity容量为原大小的2倍,
        Entry[] newTable = new Entry[newCapacity];
        transfer(newTable, initHashSeedAsNeeded(newCapacity));
        table = newTable;
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    }

//节点重哈希,即遍历原哈希表中每一个节点,重新哈希到新的哈希表中
 void transfer(Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
        for (Entry<K,V> e : table) {
            while(null != e) {
                Entry<K,V> next = e.next;
                if (rehash) {
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                int i = indexFor(e.hash, newCapacity);
                e.next = newTable[i];
                newTable[i] = e;
                e = next;
            }
        }
    }

    final int hash(Object k) {
        int h = hashSeed;
        //如果是字符串,通过sun.misc.Hashing.stringHash32转化为一个32位的哈希值
        if (0 != h && k instanceof String) {
            return sun.misc.Hashing.stringHash32((String) k);
        }

        h ^= k.hashCode();

        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

    private void inflateTable(int toSize) {
        //辅助方法,实例化哈希表的数组
        // Find a power of 2 >= toSize
        //容量满足2的倍数关系,是大于给定容量的最小的2倍数值
        int capacity = roundUpToPowerOf2(toSize);

        //扩容阈值  假如容量是16,加载因子是0.75  阈值:capacity * loadFactor=12
        threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
        //实例化table数组
        table = new Entry[capacity];
        //和求哈希函数相关的操作
        initHashSeedAsNeeded(capacity);
    }

    //获取大于当前number的最小的2倍数关系
    private static int roundUpToPowerOf2(int number) {
        // assert number >= 0 : "number must be non-negative";
        return number >= MAXIMUM_CAPACITY
                ? MAXIMUM_CAPACITY
                : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
    }

//key为null是处理逻辑,key为null时,将键值对存在在0号索引位置
 private V putForNullKey(V value) {
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        modCount++;
        addEntry(0, null, value, 0);
        return null;
    }

哈希算法设计理念:

    final int hash(Object k) {
        int h = hashSeed;
        //如果是字符串,通过sun.misc.Hashing.stringHash32转化为一个32位的哈希值
        if (0 != h && k instanceof String) {
            return sun.misc.Hashing.stringHash32((String) k);
        }

        h ^= k.hashCode();


        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

    static int indexFor(int h, int length) {
        return h & (length-1);
    }
    让length为2的指数倍,通过Hashcode(key)&(length-1)
    通过位操作操作速度是很快的,结合异或操作能够使每个key都能冲突最小的情况下映射到[0,length)的索引位置上,设计理念就是将32位的哈希值的高位和低位做异或运算。能够将冲突降到最低。
8 
00101100   00101010
&
00000000 00000111
------------------
00000000 00000010  =2

特点总结:
1、底层数据结构是哈希表
2、初始的默认容量是16
3、HashMap中容量必须满足是2的倍数关系(2、4、8、16…),如果给定一个初始容量值,集合容量需要满足大于初始容量的最小的2的倍数值作为集合容量
4、HashMap中key不能重复,而value可以重复
5、HashMap中key和value都可以为null
6、当集合中元素个数size大于扩容阈值(扩容时机)考虑扩容,将新哈希容量设置为原有容量的2倍(2*table.length),将原有集合中的节点全部重新哈希

put操作步骤:
1、先判断key是否为null,特殊处理,存放在哈希表的0号卡槽位置
2、key为null的数据是否存在,遍历0号卡槽位置的链表,如果存在(==)则更新value,返回
3、如果不存在,新建节点(addentry)
4、key不为null,通过key来计算哈希值,找到在哈希表的卡槽位置(hash,indexFor)
5、在对应卡槽获取链表,遍历找key是否存在,如果存在(hash&&equals)则更新value,返回
6、key在链表不存在,新建节点(addEntry)
7、考虑是否扩容(size>threshold),需要扩容,将新的大小为原来的2倍,然后将原哈希表中的数据都重新哈希到新的哈希表中, 并更新当前插入节点的卡槽位置
8、 采用头插入将新entry节点插入到给定的卡槽位置

get方法:

public V get(Object key) {
       //需要判断如果key为null,需要在特定位置0号位置查找
        if (key == null)
            return getForNullKey();
        //key不为null的处理逻辑
        Entry<K,V> entry = getEntry(key);

        return null == entry ? null : entry.getValue();
    }


   //key为null的value获取,通过遍历0号索引链表,查找key==null是否存在,存在则返回value
    private V getForNullKey() {
        if (size == 0) {
            return null;
        }
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null)
                return e.value;
        }
        return null;
    }
//key不为null的处理逻辑
final Entry<K,V> getEntry(Object key) {
        if (size == 0) {
            return null;
        }

      //通过key的哈希操作找到key所对应的索引位置的头结点开始进行遍历
        int hash = (key == null) ? 0 : hash(key);
        for (Entry<K,V> e = table[indexFor(hash, table.length)];e != null;e = e.next) {
            Object k;
            if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
    }


remove删除:

    public V remove(Object key) {
        Entry<K,V> e = removeEntryForKey(key);
        return (e == null ? null : e.value);
    }

//通过key来删除对应键值对实体
final Entry<K,V> removeEntryForKey(Object key) {
        if (size == 0) {
            return null;
        }
    
    //通过key的哈希操作找到对应key的索引位置
        int hash = (key == null) ? 0 : hash(key);
        int i = indexFor(hash, table.length);
        Entry<K,V> prev = table[i];//后一个索引位置
        Entry<K,V> e = prev; //前一个索引位置
        
       //单向链表的删除逻辑
        while (e != null) {
            Entry<K,V> next = e.next;
            Object k;
            if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) {
                modCount++;
                size--;
                if (prev == e)
                    //要删除的是头结点,将next节点作为i索引位置的头结点
                    table[i] = next;
                else
                    //将e节点从链表中剔除
                    prev.next = next;
                e.recordRemoval(this);
                return e;
            }
            prev = e;
            e = next;
        }

        return e;
    }

HashMap应用场景:
做数据统计,存在一些数据,需要统计每一个数字出现的次数,key是每一个数字,value就表示数据的每个数据出现的次数

存在1~1000的数据共1万个,需要统计每个数据出现的次数?
需要初始化数据:数据范围1~1000,需要总量是1万个,借助于random产生随机数
考虑1万个数据存储,需要借助于集合(ArrayList或者LinkedList)
做数据统计,需要使用HashMap
打印各个数据出现的次数。 例如:数字1 出现99次

public class TestCount {
    private static ArrayList<Integer> data  = new ArrayList<Integer>(10000);
    //数据初始化
    public static void initData() {
        Random random = new Random();
        for(int i = 0;i <1000;i++){
           int i1 = random.nextInt(1000)+1; //[0,999]
            data.add(i1);
        }
    }
    public static void count(){
        //使用hashMap来计数,key:是数据,value:出现的次数
        HashMap<Integer,Integer> hashMap = new HashMap<>();
        Iterator iterator = data.iterator();
        //通过遍历集合获取所有数据出现的次数
        while(iterator.hasNext()){
            Integer i = (Integer) iterator.next();
            if(hashMap.containsKey(i)){
                hashMap.put(i,hashMap.get(i)+1);
            }else{
                hashMap.put(i,1);
            }
        }

        Iterator<Map.Entry<Integer,Integer>> iterator1 = hashMap.entrySet().iterator();
        while (iterator1.hasNext()){
            Map.Entry<Integer,Integer> entry = iterator1.next();
            System.out.println("数据:"+entry.getKey()+"出现:"+entry.getValue());
        }
    }
    public static void main(String[] args) {
        initData();
        count();
    }
}

HashTable源码分析

继承结构:

public class Hashtable<K,V>
    extends Dictionary<K,V>
    implements Map<K,V>, Cloneable, java.io.Serializable

//HashTable继承自Dictionary父类,该父类提供了很多方法的实现,可以直接复用,但该类在后续JDK中逐渐被AbstractMap替代
//实现了Map接口,实现了克隆,实现了序列化

属性及默认值:

   //存储数据    
    private transient Entry<K,V>[] table;
   //表示集合中元素的个数
    private transient int count;
   //扩容阈值
    private int threshold;
    //加载因子
    private float loadFactor;
    //版本号
    private transient int modCount = 0;

底层数据结构:

//存放数据位置table  是一个entry类型的数组
private transient Entry<K,V>[] table;


    private static class Entry<K,V> implements Map.Entry<K,V> {
        int hash;
        final K key;
        V value;
        Entry<K,V> next;
    }
底层数据结构是数组加链表,即哈希表结构

构造函数:

  //通过初始容量和加载因子构造 
  public Hashtable(int initialCapacity, float loadFactor) {
        //参数的合法性校验
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal Load: "+loadFactor);

        if (initialCapacity==0)
            initialCapacity = 1;
        this.loadFactor = loadFactor;
         //初始化table数组
        table = new Entry[initialCapacity];
        threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
        initHashSeedAsNeeded(initialCapacity);
    }


   //通过初始容量来实例化HashTable,默认的加载因子是0.75
    public Hashtable(int initialCapacity) {
        this(initialCapacity, 0.75f);
    }


   //通过无参的构造函数实例化HashTable  默认的初始容量是11
    public Hashtable() {
        this(11, 0.75f);
    }


   //通过一个map集合实例化HashTable实例
    public Hashtable(Map<? extends K, ? extends V> t) {
        this(Math.max(2*t.size(), 11), 0.75f);
        putAll(t);
    }

常用方法:
插入元素:put方法

 public synchronized V put(K key, V value) {
        //HashTable中key和value不能为null
        if (value == null) {
            throw new NullPointerException();
        }

        // Makes sure the key is not already in the hashtable.
        Entry tab[] = table;
        //通过key进行哈希找到key存放的位置
        int hash = hash(key);
        int index = (hash & 0x7FFFFFFF) % tab.length;
        for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
            //key在集合中已经存在,对value值进行更新
            if ((e.hash == hash) && e.key.equals(key)) {
                V old = e.value;
                e.value = value;
                return old;
            }
        }

        modCount++;
        //扩容时机,当存储数据量count大于等于扩容阈值,考虑扩容
        if (count >= threshold) {
            // Rehash the table if the threshold is exceeded
            rehash();

            //table大小改变,需要对新节点位置进行重新哈希查找
            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;

        //扩容大小:2*oldCapacity+1
        int newCapacity = (oldCapacity << 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);

        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);
                }
                int index = (e.hash & 0x7FFFFFFF) % newCapacity;
                e.next = newMap[index];
                newMap[index] = e;
            }
        }
    }

remove方法:

 public synchronized V remove(Object key) {
        Entry tab[] = table;
        //通过对key哈希找到对应位置
        int hash = hash(key);
        int index = (hash & 0x7FFFFFFF) % tab.length;
        //在对应位置的链表上删除节点,本质上是单链表删除问题
        for (Entry<K,V> e = tab[index], prev = null ; e != null ; prev = e, e = e.next) {
            if ((e.hash == hash) && e.key.equals(key)) {
                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;
    }

HashTable特点:
1、底层采用的数据结构是哈希表
2、默认的初始容量是11,默认的加载因子是0.75f
3、HashTable是线程安全的,在多线程下可以放心使用,不会出现并发异常问题(synchronized)
4、HashTable中key和value都不能为null
5、HashTable中key是不能重复的,value是可以重复的
6、HashTable的扩容是按照原大小的2倍+1

总结

HashTable和HashMap不同点:
1、继承结构不同,HashTable继承自Dictionary,HashMap继承自AbstractMap
2、初始容量默认值不同:HashTable的初始容量为11,HashMap的初始容量为16
3、能否存储null:HashTable的key和value都不能为null,HashMap的key和value都能为null
4、线程安全性:HashTable是线程安全的,HashMap是非线程安全的,能否使HashMap线程安全呢?可以,即 使用Collections.synchronizedMap(hashMap);
5、哈希算法不同
6、扩容大小不同:HashTable是按照2oldlength+1 进行扩容,HashMap是按照2oldlength即指数扩容

HashTable和HashMap相同点:
1、底层数据结构是哈希表(JDK 1.7)
2、key-value键值对中,key是不能重复的,value是可以重复的
3、HashMap和Hashtable都是插入无序的
4、默认加载因子都是0.75

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值