HashMapmap = new HashMap<>(); String put1 = map.put(new Student("Mary", '女', 22, "2205", "001"),"听歌"); System.out.println(put1);//null String put2 = map.put(new Student("Green", '女', 20, "2205", "002"),"看书"); System.out.println(put2);//null String put3 = map.put(new Student("Lisa", '女', 23, "2205", "003"),"旅行"); System.out.println(put3);//null String put4 = map.put(new Student("Lisa", '女', 23, "2205", "003"),"品茗"); System.out.println(put4);//旅行 String put5 = map.put(null, "闻香"); System.out.println(put5);//null String put6 = map.put(null, "赏花"); System.out.println(put6);//闻香
public class HashMapextends AbstractMap implements Map { //默认初始化长度(必须是2的幂) static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 16 //最大长度 static final int MAXIMUM_CAPACITY = 1 << 30; //默认负载因子 static final float DEFAULT_LOAD_FACTOR = 0.75f; //空内容的数组 static final Entry,?>[] EMPTY_TABLE = {}; //hash数组、hash表 transient Entry [] table = (Entry []) EMPTY_TABLE;// new Entry[16]; //元素个数 transient int size; //阈值 int threshold;// 12 //负载因子 final float loadFactor;// 0.75f //操作数 transient int modCount; //hash种子数 transient int hashSeed = 0; public HashMap() { this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR); } // initialCapacity - 16 // loadFactor - 0.75f 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))//NaN - Not a Number throw new IllegalArgumentException("Illegal load factor: " + loadFactor); this.loadFactor = loadFactor; threshold = initialCapacity; init(); } // key - null // value - "赏花" public V put(K key, V value) { //第一次添加元素时进入的判断 if (table == EMPTY_TABLE) { inflateTable(threshold); } if (key == null) return putForNullKey(value); //获取key的hash值 int hash = hash(key); //通过hash值获取在数组中的下标 int i = indexFor(hash, table.length); //说明hash碰撞了 for (Entry e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { // oldValue - 旅游 V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue;//返回被替换掉的值 } } modCount++; addEntry(hash, key, value, i); return null; } // value - "赏花" private V putForNullKey(V value) { for (Entry 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; } // hash - 0 // key - null // value - "闻香" // bucketIndex - 0 void addEntry(int hash, K key, V value, int bucketIndex) { //判断是否扩容(元素个数大于阈值 并且 下标上的元素不为null) if ((size >= threshold) && (null != table[bucketIndex])) { //扩容的预处理 resize(2 * table.length); //重新计算key的hash值 hash = (null != key) ? hash(key) : 0; //重新计算在数组中的下标 bucketIndex = indexFor(hash, table.length); } createEntry(hash, key, value, bucketIndex); } // newCapacity - 32 void resize(int newCapacity) { Entry[] oldTable = table; // oldCapacity - 16 int oldCapacity = oldTable.length; //判断原数组的长度已经等于 数组的最大值 if (oldCapacity == MAXIMUM_CAPACITY) { //int最大值赋值给阈值 threshold = Integer.MAX_VALUE; return; } //创建新数组 Entry[] newTable = new Entry[newCapacity]; //1.重新计算hash种子数 2.将原数组的数据迁移到新数组中 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 e : table) { while(null != e) { Entry 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; } } } // hash - 0 // key - null // value - "闻香" // bucketIndex - 0 void createEntry(int hash, K key, V value, int bucketIndex) { Entry e = table[bucketIndex]; table[bucketIndex] = new Entry<>(hash, key, value, e); size++; } static int indexFor(int h, int length) { return h & (length-1); } final int hash(Object k) { int h = hashSeed; 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); } // toSize - 16 private void inflateTable(int toSize) { // capacity - 16 int capacity = roundUpToPowerOf2(toSize); threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1); table = new Entry[capacity]; initHashSeedAsNeeded(capacity); } //获取hash种子数 final boolean initHashSeedAsNeeded(int capacity) { boolean currentAltHashing = hashSeed != 0; boolean useAltHashing = sun.misc.VM.isBooted() && (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD); boolean switching = currentAltHashing ^ useAltHashing; if (switching) { hashSeed = useAltHashing ? sun.misc.Hashing.randomHashSeed(this) : 0; } return switching; } // number - 16 private static int roundUpToPowerOf2(int number) { return number >= MAXIMUM_CAPACITY ? MAXIMUM_CAPACITY : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1; } //映射关系类/节点类 static class Entry implements Map.Entry { final K key; ------- key V value; ----------- value Entry next; --- 下一个节点的地址 int hash; ---------- key的hash值 Entry(int h, K k, V v, Entry n) { value = v; next = n; key = k; hash = h; } } }
1.JDK1.7版本的HashMap底层数据结构是什么?
一维数组+单向链表
2.HashMap的默认初始化容量是多少?
1<<4 --> 16
3.HashMap容量为什么是2的幂?
因为获取元素在数组中的下标的算法是:key的hash值 & length-1
如果数组长度不是2的幂,length-1的二进制表示中就会出现0,导致该位置上永远取不到
4.Hashmap最大容量是多少?
1<<30 --> 1073741824
5.Hashmap最大容量为什么是1<<30?
容量为int类型,并且容量必须是2的幂,而int类型取值范围内最大的2的幂的数字就是1<<30
6.HashMap中默认的负载因子是多少?起到什么作用?
默认的负载因子为0.75f
负载因子是控制扩容(数组长度*负载因子)
7.HashMap中默认的负载因子为什么是0.75f?
取得时间和空间的平衡
如果负载因子过小,存放一点数据就扩容,牺牲了空间,利用了时间
如果负载因子过大,将数组存满后才扩容,牺牲了时间,利用了空间
8.HashMap何时扩容?
元素个数大于等于阈值,并且插入的位置上不等于空
9.HashMap的扩容机制?
原来数组的两倍
10.什么是hash碰撞?
key的hash值相同,在数组中的下标也是相同的,并且比较的过程叫做hash碰撞
11.怎么提高HashMap的效率?
重写hashCode和equals,减少hash碰撞的次数
12.HashMap存储null键的位置?
hash数组中下标为0的位置
13.什么是Hash回环?
多线程下会出现的问题
一个线程不断的添加,导致map扩容
一个线程不断的遍历数据
扩容的时候指针变成闭环
这个问题应该是程序员背的锅,因为HashMap明确声明该实现不是一个线程安全的,所以多线程下必须使用线程安全的map -- ConcurrentHashMap
14.JDK1.7版本的HashMap和JDK1.8版本的HashMap有什么区别?
JDK1.7版本的HashMap:一维数组+单向链表,头插法
JDK1.8版本的HashMap:一维数组+单向链表+红黑树,获取hash值(高16位^低16位),尾插法
15.JDK1.8版本的HashMap的数据结构?
一维数组+单向链表
当数组长度大于64并且单向链表大于8,从一维数组+单向链表转换为一维数组+红黑树
这样的设计是为了效率
单向链表小于6,又会从一维数组+红黑树 变会到 一维数组+单向链表
16.为什么JDK1.8中HashMap的单向链表大于8变成红黑树?
因为泊松分布
看底层的学习方法:
-
找场景
-
类的继承关系
-
看属性
-
创建对象 -- 成员变量初始化、构造方法中的逻辑
-
添加数据(3个、4个、添加特殊的数据-null)
-
扩容
-
删除、查询、修改....



