HashMap1.7底层
//1.HashMap的K,V的值,在创建对象的时候确定:K:Integer V:String //HashMap的父类AbstractMap已经实现类Map接口,但是源码中又单独实现了Map接口 //这个操作就是一个多余的操作----》集合的创作者承认了 public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable //重要属性 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16定义了一个16,就是数组的长度 static final int MAXIMUM_CAPACITY = 1 << 30;//定义了一个很大很大的数 static final float DEFAULT_LOAD_FACTOR = 0.75f;//定义了一个值取值0.75 负载因子,加载因子 transient Entry<K,V>[] table;//底层数组 transient int size;//添加的元素数量 int threshold;//定义个变量,没赋值默认为0,---》这个变量用来表示数组扩容的边界值,门槛值 final float loadFactor;//这个变量用来接收负载因子,加载因子 装填因子 //一下三个属性为JDK1.8源码下面的 static final int TREEIFY_THRESHOLD = 8;//红黑树阈值,当链表长度大于8专为红黑树 static final int UNTREEIFY_THRESHOLD = 6;//红黑树还原链表的阈值 static final int MIN_TREEIFY_CAPACITY = 64;//当Hash表中 //空构造器 public HashMap() { // 16 0.75 this(DEFAULT_INITIAL_CAPACITY,DEFAULT_LOAD_FACTOR) } //带参数构造器--》不建议使用者调用这个构造器 public HashMap(int initialCapacity,float loadFactor){ if(initialCapacity>MAXIMUM_CAPACITY) initialCapacity=MAXIMUM_CAPACITY; //capacity最后的结果一定是2的整数倍 (2^n)??? int capacity=1; while(capacity<initialCapacity) capacity<<=1; //确定了装填因子,负载因子,加载因子:0.75 this.loadFactor=loadFactor; //threshold=capacity*loadFactor=16*0.75=12 //threshold=12--->数组扩容的边界值 threshold=(int)Math.min(capacity*loadFactor,MAXIMUM_CAPACITY+1); //创建主数组,主数组的长度定义为16 table=new Entity[capacity]; useAltHashing=sun.misc.VM.isBooted()&& (capacity>=Holder.ALTERNATIVE_HASHING_THRESHOLD); init(); } //存储数据的方法 public V put(K key, V value){//K :Integer V:String //对空值进行判断---允许key的值为NULL if(key==null) return putForNullKey(value); //获取哈希码 int hash=hash(key); //得到元素在数组中的位置 int i=indexFor(hash,table.length); //如果放入的数组的位置上没有元素的话,那么直接添加就行了,不走这个for循环 //e!=null 满足的话,就证明这个位置上已经有东西了 for(Entry<K,V> e=table[i];e!=null;e=e.next){ Object k; //发生哈希碰撞的时候,会先比较哈希值 //比较key是否是一个对象,如果key是一个对象的话,equals就不比较了 //如果不是同一个对象,会比较equals方法 //如果hash值一样,equals方法比较的结果也一样,那么才会走这个if方法 if(e.hash==hash&&((k=e.key)==key||key.equals(k))){ //获取老的value V oldValue=e.value; e.value=value;//新value替换老value--》只替换value不替换Key e.recordAccess(this); return oldValue;//将老value返回 } } modCount++; addEntry(hash,key,value,i); return null; } final int hash(Object k) { int h = hashSeed; if (0 != h && k instanceof String) { return sun.misc.Hashing.stringHash32((String) k); } //二次散列,没有直接用hash的值,解决哈希冲突 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); } //indexFor 根据hash值以及数组长度,获取当前key在数组中下标 static int indexFor(int h, int length) { // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2"; return h & (length-1);//结算位置对应的公式,等效于h%length取余数---》效率低用于运算符效率高 } //添加元的方法: void addEntry(int hash, K key, V value, int bucketIndex) { //如果size >= threshold这个变量不满足,这个if不走 if ((size >= threshold) && (null != table[bucketIndex])) { resize(2 * table.length); hash = (null != key) ? hash(key) : 0; bucketIndex = indexFor(hash, table.length); } //走这里创建一个Entry对象 createEntry(hash, key, value, bucketIndex); } void createEntry(int hash, K key, V value, int bucketIndex) { //将下标位置上的元素给e Entry<K,V> e = table[bucketIndex]; //封装对象,将这个对象给table[bucketIndex] --->链表的头插法 table[bucketIndex] = new Entry<>(hash, key, value, e); //元素数量加1操作 size++; } /*经典面试题: (1)装填因子,负载因子,加载因子为什么是0.75: 装填因子设置为1:空间利用率得到了很大的满足,很容易碰撞,产生链表--》查询效率低 装填因子设置为0.5:碰撞的概率低,产生链表的概率低,查询效率高,空间利用率太低 0.5---1取中间值:0.75 (2)主数组的长度为什么是2^n: 原因1:防止哈希冲突,位置冲突*/
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本