
前言:
Map集合的底层原理,源码解析 Cltr+F12 搜索 Cltr+B跟进 Cltr+Alt+左键,回到上一步 向上的箭头,表示该方法是重写后的 向后的箭头,表示该方法是来自哪个类或者哪个接口
解释:
HashMap 哈希值只跟键有关系,跟值没有一点关系 Node<K,V>[]table 哈希表结构中数组的名字 DEFAULT—INITIAL—CAPACITY 默认数组长度16 DEFAULT—LOAD—FACTOR默认加载因子为0.75
HashMap里面的每一个对象包含以下内容
包含:int hash //键的哈希值 finalK key //键 v value //值 Node<K,V>nex t //下一个节点的地址值
包含:int hash //键的哈希值 finalK key //键 v value //值 TreeNode<K,V>parent //父节点的地址值 TreeNode<K,V>left //左子节点的地址值 TreeNode<K,V>right//右子节点的地址值 boolean red 节点的颜色
数组里面的键值对对象需要分情况讨论
HashMap<String,Integer> hm = new HashMap<>();
hm.put("aaa" , 111); hm.put("bbb" , 222); hm.put("ccc" , 333); hm.put("ddd" , 444); hm.put("eee" , 555);
添加元素的时候至少考虑三种情况: 2.1数组位置为null 2.2数组位置不为null,键不重复,挂在下面形成链表或者红黑树 2.3数组位置不为null,键重复,元素覆盖
参数一:键
参数二:值
返回值:被覆盖元素的值,如果没有覆盖,返回null public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }
第一个参数hash(key),利用键计算出对应的哈希值,再把哈希值进行一系列的额外处理 简单理解 //参数一:键的哈希值 //参数二:键 //参数三:值 //参数四:如果键重复了是否保留 true表示老元素的值保留,不会覆盖 false,表示老元素的值不保留,会进行覆盖 //参数五暂时不了解
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) { //定义一个局部变量,用来记录哈希表中数组的地址值。 Node<K,V>[] tab; //临时的第三方变量,用来记录键值对对象的地址值 Node<K,V> p; //表示当前数组的长度 int n; //表示索引 int i; //把哈希表中数组的地址值,赋值给局部变量tab tab = table;
if (tab == null || (n = tab.length) == 0){ //1.如果当前是第一次添加数据,底层会创建一个默认长度为16,加载因子为0.75的数组 //2.如果不是第一次添加数据,会看数组中的元素是否达到了扩容的条件 //如果没有达到扩容条件,底层不会做任何操作 //如果达到了扩容条件,底层会把数组扩容为原先的两倍,并把数据全部转移到新的哈希表中 tab = resize(); //表示把当前数组的长度赋值给n n = tab.length; }
//拿着数组的长度跟键的哈希值进行计算,计算出当前键值对对象,在数组中应存入的位置 i = (n - 1) & hash;//index //获取数组中对应元素的数据 p = tab[i]; if (p == null){ //底层会创建一个键值对对象,直接放到数组当中(第一个元素) tab[i] = newNode(hash, key, value, null); //threshold:记录的就是数组的长度 * 0.75,哈希表的扩容时机 16 * 0.75 = 12 if (++size > threshold){ resize(); } //表示当前没有覆盖任何元素,返回null return null;