HashMap基础知识

在这里插入代码片## HashMap的定义
·HashMap 是基于哈希表的 Map 接口的实现的,实现了所有map的操作,允许key和value为null,但是key不能重复

HashMap的基本属性

// 默认的初始容量必须是2的幂 默认值为16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4
// 最大容量 容量的值必须是2的幂 最大值为2的30次幂
static final int MAXIMUM_CAPACITY = 1 << 30
//负载因子默认值为0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f
// 链表长度大于8的时候 转为红黑树结构
static final int TREEIFY_THRESHOLD = 8
// 长度小于等于6时转为链表
static final int UNTREEIFY_THRESHOLD = 6
// 树形结构时最小的容量
static final int MIN_TREEIFY_CAPACITY = 64

注意:

	1.容量、负载因子、元素个数关系
		 ·容量capacity:表示可以装多少个元素
	     ·元素个数size: 表示装了多少元素了
	     ·LoadFactor:负载因子,默认值0.75f
		  如当前容量大小为32  当存入了第25(32*0.75)个元素时,
		  hashmap就会进行resize(扩容)了

元素在HashMap中的位置计算

  hashmap在通过key的hashcode方法计算值之后,会调用indexfor方法将hashcode值转为数组的下标: h & (length-1) Java利用位运算来取代取模操作,因为位运算直接对内存数据进行操作,不需要转成十进制,之所以可以做等价代替,前提是要求HashMap的容量一定要是2^n

HashMap的数据结构

1.7 底数组+链表  采用头插法
1.8 数组+链表+红黑树 采用尾插法
HashMap 底层的数据是数组被称为哈希桶,每个桶存放的是链表,链表中的每个节点,就是 HashMap 中的每个元素。在 JDK 8 当链表长度大于等于 8 时,就会转成红黑树的数据结构,以提升查询和插入的效率
https://img-blog.csdnimg.cn/20201028143455147.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM2NjAzNTA1,size_16,color_FFFFFF,t_70#pic_center

重要方法

1.put方法
 public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

  
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        // 若table为空或长度为0,调用resize方法进行扩容创建新的数组
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        // 计算该键值对在数组中的存储位置,此位置为空则进行插入操作
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else { //该数组此位置已经有值,判断时链表还是红黑树
            Node<K,V> e; K k;
            // /判断是否为新增的元素是否为第一个元素(hash与key去判断),如果是则获取第一个元素
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
                // 该链为树,新增一个树街店
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
            	//链表节点
            	//遍历,直到链表末尾,添加新节点
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                   
                    // 若hash与key相同,则终止便利遍历
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                   // 将节点的引用后移
                    p = e;
                }
            }
            / 存在key对应的value时,按照条件替换并返回旧值
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

https://img-blog.csdnimg.cn/20201028160342612.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM2NjAzNTA1,size_16,color_FFFFFF,t_70#pic_center

2. get方法
public V get(Object key) {
  Node<K,V> e;
  return (e = getNode(hash(key), key)) == null ? null : e.value;
}
/**
* 该方法是 Map.get 方法的具体实现
* 接收两个参数
* @param hash key 的 hash 值,根据 hash 值在节点数组中寻址,该 hash 值是通过 hash(key) 得到的
* @param key key 对象,当存在 hash 碰撞时,要逐个比对是否相等
* @return 查找到则返回键值对节点对象,否则返回 null
*/
final Node<K,V> getNode(int hash, Object key) {
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k; // 声明节点数组对象、链表的第一个节点对象、循环遍历时的当前节点对象、数组长度、节点的键对象
    // 节点数组赋值、数组长度赋值、通过位运算得到求模结果确定链表的首节点
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & hash]) != null) {
        if (first.hash == hash && // 首先比对首节点,如果首节点的 hash 值和 key 的 hash 值相同,并且首节点的键对象和 key 相同(地址相同或 equals 相等),则返回该节点
            ((k = first.key) == key || (key != null && key.equals(k))))
            return first; // 返回首节点

        // 如果首节点比对不相同、那么看看是否存在下一个节点,如果存在的话,可以继续比对,如果不存在就意味着 key 没有匹配的键值对    
        if ((e = first.next) != null) {
            // 如果存在下一个节点 e,那么先看看这个首节点是否是个树节点
            if (first instanceof TreeNode)
                // 如果是首节点是树节点,那么遍历树来查找
                return ((TreeNode<K,V>)first).getTreeNode(hash, key); 

            // 如果首节点不是树节点,就说明还是个普通的链表,那么逐个遍历比对即可    
            do {
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k)))) // 比对时还是先看 hash 值是否相同、再看地址或 equals
                    return e; // 如果当前节点e的键对象和key相同,那么返回 e
            } while ((e = e.next) != null); // 看看是否还有下一个节点,如果有,继续下一轮比对,否则跳出循环
        }
    }
    return null; // 在比对完了应该比对的树节点 或者全部的链表节点 都没能匹配到 key,那么就返回 null
3. 扩容
	·计算新容量:根据老数组的容量确定扩容后的容量值,一般扩容为老容量的2倍
	·创建新数组:根据新的容量创建数组,需要尽量避免扩容的发生,因为产生新的数组必然是会消耗一定的内存空间。
	·元素放置到新数组:循环遍历老数组,将元素重新放置到新数组中
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值