java-集合框架库-HashMap

目录

1方法

2源码实现

2.1继承关系

2.2构造函数

2.3属性及默认值

2.4底层数据结构

2.5扩容机制

2.6常用方法

2.6.1插入元素:put(key,value)

2.6.2获取元素:get(key)

2.6.3删除元素:remove(key)

3特点总结


 

1方法

方法描述
clear()删除 hashMap 中的所有键/值对
clone()复制一份 hashMap
isEmpty()判断 hashMap 是否为空
size()计算 hashMap 中键/值对的数量
put()将键/值对添加到 hashMap 中
putAll()将所有键/值对添加到 hashMap 中
putIfAbsent()如果 hashMap 中不存在指定的键,则将指定的键/值对插入到 hashMap 中。
remove()删除 hashMap 中指定键 key 的映射关系
containsKey()检查 hashMap 中是否存在指定的 key 对应的映射关系。
containsValue()检查 hashMap 中是否存在指定的 value 对应的映射关系。
replace()替换 hashMap 中是指定的 key 对应的 value。
replaceAll()将 hashMap 中的所有映射关系替换成给定的函数所执行的结果。
get()获取指定 key 对应对 value
getOrDefault()获取指定 key 对应对 value,如果找不到 key ,则返回设置的默认值
forEach()对 hashMap 中的每个映射执行指定的操作。
entrySet()返回 hashMap 中所有映射项的集合集合视图。
keySet()返回 hashMap 中所有 key 组成的集合视图。
values()返回 hashMap 中存在的所有 value 值。
merge()添加键值对到 hashMap 中
compute()对 hashMap 中指定 key 的值进行重新计算
computeIfAbsent()对 hashMap 中指定 key 的值进行重新计算,如果不存在这个 key,则添加到 hasMap 中
computeIfPresent()对 hashMap 中指定 key 的值进行重新计算,前提是该 key 存在于 hashMap 中。

2源码实现

2.1继承关系

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

HashMap继承自AbstractMap (该抽象类对Map接口的常用方法做了实现,方便子类复用

实现类Map接口,具有Map中提供的所有方法)

实现 Cloneable和Serializable接口

2.2构造函数

//通过初始容量和加载因子来实例HashMap对象
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,加载因子是默认值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);
        //初始化table、threshold参数,实例化哈希表
        inflateTable(threshold);
        //将map结合中的键值对存放到HashMap中
        putAllForCreate(m);
    }

2.3属性及默认值

//默认初始容量 :值为16 
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
//最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;

//默认的加载因子:0.75 
static final float DEFAULT_LOAD_FACTOR = 0.75f;

//默认空表
static final Entry<?,?>[] EMPTY_TABLE = {};

//存放数据 table属性,是entry类型数组
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;

//存放的键值对个数
transient int size;

//扩容阈值   ,在扩容时判断  threshold =table.length()*loadFactor
int threshold;

//加载因子
final float loadFactor;

//版本控制
transient int modCount;

//key哈希相关
static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;
//key哈希相关
transient int hashSeed = 0;

2.4底层数据结构

//存放数据位置  
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;

    static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;
        int hash;
    }

通过以上可知:HashMap底层存放数据是数组加链表形式,即是哈希表结构

2.5扩容机制

扩容时机:在size>threshold时,会进行扩容

2.6常用方法

2.6.1插入元素:put(key,value)

 public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            //当table数组为空时,进入初始化table数据,当第一次调用put操作会进入
            inflateTable(threshold);
        }
        
        //key为null,将数据存放在0号卡槽位置
        if (key == null)
            return putForNullKey(value);
        
        //key不为null的处理
        //通过key找到对应的存放卡槽位置
        int hash = hash(key);
        int i = indexFor(hash, table.length);
        
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            //通过位置i找到卡槽i位置的数据链表,从头遍历,找key是否存在
            //判断条件是hash和key,相等值更新
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
       //key在i位置不存在,需要新增entry节点存放数据
        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

private V putForNullKey(V value) {
      //key为null将哈希位置为0号位置,需要遍历0号卡槽链表、判断key为null是否存在
      //存在将entry中value进行跟新,返回旧的value中
      //不存在则新建entry实体,存放put的数据
        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;
    }


//通过key哈希找到对应卡槽
final int hash(Object k) {
        int h = hashSeed;
        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);
    }
   
通过key的哈希值找到在哈希表中的位置
static int indexFor(int h, int length) {
        // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
    
       容量是16
           0001 0000     16
           0000 1111     15
           1010 1001
           -------------
           0000 1001    9 
        return h & (length-1);
    }
    

    void addEntry(int hash, K key, V value, int bucketIndex) {
        //当存放数据size大于扩容阈值threshold,进行扩容
        if ((size >= threshold) && (null != table[bucketIndex])) {
            //对HashMap进行2倍的扩容
            resize(2 * table.length);
            hash = (null != key) ? hash(key) : 0;
            //获取当前key应该插入的新卡槽位置
            bucketIndex = indexFor(hash, table.length);
        }

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

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

       //新创建数组为原来的2倍
        Entry[] newTable = new Entry[newCapacity];
        //将原map中的每一个entry实体进行重新哈希到新的map中
        transfer(newTable, initHashSeedAsNeeded(newCapacity));
        table = newTable;
        //threshold = table.length*loadFactor
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    }


//采用头插法将数据插入
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++;
    }

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节点插入到给定的卡槽位置

2.6.2获取元素:get(key)

 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();
    }

private V getForNullKey() {
       //如果map为空,返回null
        if (size == 0) {
            return null;
        }
        //在0号卡槽位置,对链表遍历,查找key为null是否存在,存在则找entry中value返回,否则返回null
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null)
                return e.value;
        }
        return null;
    }
   

通过key找到对应entry实例
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;
    }

查询过程:

1、如果key为null,在0号卡槽位置遍历链表查询,key存在则返回entry的value,否则返回null

2、如果key不为null,对key进行哈希,找到对应的卡槽,遍历链表,判断key是否存在(hash,key.equals),返回entry的value否则返回null

2.6.3删除元素:remove(key)

通过key删除可以所在entry实体

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

final Entry<K,V> removeEntryForKey(Object key) {
      //如果集合为空,直接返回null
        if (size == 0) {
            return null;
        }
       //通过key哈希找到对应卡槽(key为null卡槽为0)
        int hash = (key == null) ? 0 : hash(key);
        int i = indexFor(hash, table.length);
        Entry<K,V> prev = table[i];
        Entry<K,V> e = prev;

       //删除节点即解决单向链表的删除问题:解决思路:给定两个指针,两指针一前一后,前指针表示要删除的节点,
    、  //通过后一个指针来将节点和前节点指针的next建立联系
        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)
                    table[i] = next;
                else
                    //后一个指针prex来将节点和前节点指针的next建立联系
                    prev.next = next;
                e.recordRemoval(this);
                return e;
            }
            prev = e;
            e = next;
        }

        return e;
    }

删除过程:

1、通过key哈希来找到卡槽位置(key为null在0号卡槽)

2、对应卡槽的链表进行遍历查找,给定两个指针一前一后,前指针找到要删除节点,后指针建立和下一个节点关系

 

3特点总结

1、存储的数据是键值对形式,key不能重复,value值可以重复

2、key和value都可以为null

3、不能保证内部元素的顺序

4、底层数据结构是哈希表

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值