HashMap

简介

hashmap基于哈希表的底层map实现。
哈希表控制键。
数组+链表 链表>8,数组长度大于64 数组大于64转为红黑树,O(n)变为O(log n),小于6从红黑树转为链表。
数组小于64,链表>8,不转红黑树,先扩容(重新计算哈希值,将原map内数据重新计算哈希值存放)
存取无序
k,v可以为null,key唯一,只能有一个null
继承abstract map,实现map,cloneable,Serializable

数据结构是存储数据的方式。

1.创建hashmap,构造方法中创建一个长度16的Entry[] table 用来存放键值对数据。
jdk1.8后不在构造方法中创造,在第一次put时创建Node(实现Map.Entry)[] table
2.存储数据,调用key的hashcode然后结合数组长度采用某种算法计算索引值。
若索引没数据,直接存。

哈希表底层采用什么算法计算哈希值
key.hashcode方法的值结合无符号右移(>>>),按位异或(^),按位与(&)计算索引
平方去中法,取余法,伪随机数法。取余效率低,位运算效率高。
存储数据计算的索引值的桶中有数据时,先看key的hash值是否相等,哈希值不相等,划出节点存储数据。
哈希值相同,发生哈希碰撞,判断key内容是否相等(==||equals),相等替换v,不相等循环链表(或者红黑树)比较key,都不相等直接存储。

扩容:超过阈值,扩容两倍

threshold临界值=容量*加载因子

成员变量

  • private static final long serialVersionUID = 362498820763181265L; 序列化版本号
  • static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
    集合的初始化容量,必须是2的次幂
    为了减少哈希碰撞,充分数组空间
    计算索引实际上是取模 hash%length 效率不高,源码优化为 hash&(length-1)
    数组长度为2^n时,length-1 为 011111,按位与运算都为1则为1,否则为0减少哈希碰撞。非2^n时极其容易发生哈希碰撞。hash冲突越大,数组链表越长,hashmap效率越低。

一般会通过%确定位置,但是性能不如&运算,在n=2^n时,hash&(length-1)==hash%length

构造函数给容量为10会变成16 最近的2的n次幂

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是为了避免传16容量变为32的情况。
右移16位,最高就是32个1,在进入前进行了判断大于最大值2的30次方,就用最大值,
此时移位为最大30个1,不会大于等于2的30次,加1后得到2的30次

  • static final float DEFAULT_LOAD_FACTOR = 0.75f; 加载因子

  • 为什么在链表长度大于8时变为红黑树
    树节点是常规节点的两倍,包含足够多的节点才会转变为红黑树。
    正常情况下,hashcode算法bin节点的频率会遵循泊松分布。一个bin中链表长度达到8个元素的概率为0.0000006,几乎不可能。
    log2 (8)=3 n/2=4

  • static final int UNTREEIFY_THRESHOLD = 6; 节点数小于6转换为链表

  • static final int MIN_TREEIFY_CAPACITY = 64; 数组大于64才会转为红黑树,否则扩容。

  • transient int size; 存放元素的个数

  • transient int modCount; 扩容或者更改map的计数器

  • int threshold 临界值

  • final float loadFactory 加载因子 越接近1说明存储越多,接近0说明越稀疏
    size/capacity
    尽量减少扩容次数。

成员方法

构造方法

无参构造 赋值默认加载因子
指定容量 赋值默认加载因子,计算阈值
指定容量和加载因子 计算阈值,赋值形参加载因子
形参map 循环放到hashmap中
putMapEntries
float ft = ((float)s / loadFactor) + 1.0F;
加1是为了减少扩容

treeifBin

链表大于8,数组长度小于64,扩容
数组长度大于64,循环链表建立红黑树,然后旋转至平衡

扩容方法 resize

超过容量*加载因子,扩容为2倍
删除节点个数小于6个变为链表,因为小于6链表遍历比红黑树更快

扩容是rehash都是翻倍,和原结果比多个bit位,要么在原来位置(高位0),要么被分配到 原位置+旧容量(高位1) 这个位置 均匀的将之前冲突的节点分配到新的桶中。

如果只有一个元素,直接存。树节点拆分,遍历链表。

putval方法

索引计算过程
(key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
i = (table.length - 1) & hash
等同于对数组长度取余

hashcode高位变化很大,低位变化很少或没有变化时,直接与操作会很容易哈希碰撞。

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;//先判断hashmap数组是否为空,为空则创建数组
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);//根据索引值找到对应桶为空,则插入新节点到桶
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;//头节点不为空,并且哈希值和key值相等则进行覆盖
            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);//插入后链表大于8,转化为红黑树,这个方法会判断数组长度,数组长度小于64时,先扩容
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;//次节点的hash值和key相等,跳出循环
                    p = e;//下个节点不为空,hash和key不相等,将p.next赋值给p,继续魂环
                }
            }
            if (e != null) { // p.next不为空时
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)//onlyIfAbsent 为true代表key相同,不修改value的值,putval中穿的false
                    e.value = value;//进行值的覆盖
                afterNodeAccess(e);//将当前节点挪到最后
                return oldValue;
            }
        }
        ++modCount;//修改计数器
        if (++size > threshold)
            resize();//添加元素后超过阈值扩容
        afterNodeInsertion(evict);
        return null;
    }

删除方法

计算哈希值,获取索引得到桶的位置,比较key是否相等,相等删除,不相等红黑树根据hash和key获取节点,链表循环到key相等的节点进行删除。

get方法

计算哈希,获取桶位置,是不是第一个元素,是返回,否则遍历红黑树(因为添加时保证有序因此查找使用折半查找)或者链表比较key值,相等返回

遍历方式

{
    public static void main(String[] args) {
        HashMap<String,Integer> map =new HashMap();
        map.put("1",1);
        map.put("2",2);
        map.put("3",3);
        map.put("4",4);

//        method(map);
//        method1(map);
//        method2(map);
        method3(map);

    }
//jdk8以后使用map中的默认方法
    private static void method3(HashMap<String, Integer> map) {
        map.forEach((key,value)->{
            System.out.println(key+"---"+value);
        });
    }

    //通过get方式   不建议,两次循环
    private static void method2(HashMap<String, Integer> map) {
        Set<String> keys=map.keySet();
        for(String key:keys){
            Integer v=map.get(key);
            System.out.println(key+"----"+v);
        }
    }

    //迭代器迭代
    private static void method1(HashMap<String, Integer> map) {
        Set<Map.Entry<String,Integer>> entries =map.entrySet();
        for (Iterator<Map.Entry<String,Integer>> it=entries.iterator();it.hasNext();){
            Map.Entry<String,Integer> entry=it.next();
            System.out.println(entry.getKey()+"-----"+entry.getValue());
        }
    }
//分别遍历key和value
    private static void method(HashMap map) {
        Set<String> keys=map.keySet();
        for (String key:keys){
            System.out.print("  key  "+key);
        }
        Collection<Integer> values=map.values();
        System.out.println("");
        for(Integer value : values){
            System.out.print("  value  "+ value);
        }
    }
}

使用技巧

确定个数,创建hashmap
存7,生成8,第6个就扩容。
初始化容量为 元素个数/初始化因子 +1,减少扩容概率
float ft = ((float)s / loadFactor) + 1.0F;
7/0.75+1=10 创建后为16

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小明和大树

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值