600字范文,内容丰富有趣,生活中的好帮手!
600字范文 > 撸啊撸 再次撸HashMap源码 踩坑源码中构造方法!!!每次都有收获

撸啊撸 再次撸HashMap源码 踩坑源码中构造方法!!!每次都有收获

时间:2019-10-01 18:27:34

相关推荐

撸啊撸 再次撸HashMap源码 踩坑源码中构造方法!!!每次都有收获

前言

点赞在看,养成习惯。

点赞收藏,人生辉煌。

点击关注【微信搜索公众号:编程背锅侠】,第一时间获得最新文章。

HashMap系列文章

第一篇HashMap源码中的成员变量你还不懂? 来来来!!!整理好的成员变量源码解析

第二篇撸啊撸,再次撸HashMap源码,踩坑源码中构造方法!!!每次都有收获

构造方法

构造一个空的HashMap,默认初始容量(16)和默认负载因⼦(0.75)

源码解析

// 构造一个无参数的构造方法public HashMap() {// 将默认的加载因子0.75赋值给loadFactor,并没有创建数组this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted}

案例演示

@Testpublic void test_hash_map_con_no(){HashMap<Integer, String> map = new HashMap<>();}

构造⼀个具有指定的初始容量和默认负载因子(0.75)HashMap

源码解析

// 构造一个指定容量⼤⼩的构造函数public HashMap(int initialCapacity) {this(initialCapacity, DEFAULT_LOAD_FACTOR);}

案例演示

@Testpublic void test_hash_map_con_in(){HashMap<Integer, String> map = new HashMap<>(16);}

构造一个具有指定的初始容量和负载因⼦的HashMap

源码解析

// 指定容量⼤小和加载因⼦的构造函数 initialCapacity: 指定的容量 loadFactor:指定的加载因⼦public HashMap(int initialCapacity, float loadFactor) {// 判断初始化容量initialCapacity是否小于0if (initialCapacity < 0)// 如果⼩于0,则抛出非法的参数异常IllegalArgumentExceptionthrow new IllegalArgumentException("Illegal initial capacity: " +initialCapacity);// 判断初始化容量initialCapacity是否⼤于集合的最大容量MAXIMUM_CAPACITY->2的30次幂if (initialCapacity > MAXIMUM_CAPACITY)// 如果超过MAXIMUM_CAPACITY,会将MAXIMUM_CAPACITY赋值给initialCapacityinitialCapacity = MAXIMUM_CAPACITY;// 判断负载因⼦loadFactor是否小于等于0或者是否是⼀个⾮数值if (loadFactor <= 0 || Float.isNaN(loadFactor))// 如果满足上述其中之一,则抛出非法的参数异常IllegalArgumentExceptionthrow new IllegalArgumentException("Illegal load factor: " +loadFactor);// 将指定的加载因⼦赋值给HashMap成员变量的负载因子loadFactorthis.loadFactor = loadFactor;// tableSizeFor(initialCapacity) 判断指定的初始化容量是否是2的n次幂,如果不是那么会变为⽐指定初始化容量大的最小的2的n次幂。this.threshold = tableSizeFor(initialCapacity);}

案例演示

@Testpublic void test_hash_map_con_in_lo(){// 自定义初始化容量和加载因子HashMap<Integer, String> map = new HashMap<>(16, 0.75f);}

疑惑代码

this.threshold = tableSizeFor(initialCapacity);以上代码是源码中的最后一句代码,为啥不是this.threshold = tableSizeFor(initialCapacity) * this.loadFactor ?这样才符合threshold的意思(当HashMap的size到达threshold这个阈值时会扩容)。 但是,请注意,在jdk8以后的构造方法中,并没有对table这个成员变量进行初始化,table的初始化被推迟到了put方法中,在put⽅法中会对threshold重新计算,put⽅法的【resize()方法】具体实现我会在这个系列其他文章会进行讲解。

总结

如果这个构造函数的initialCapacity小于0,将会抛出非法异常IllegalArgumentException。如果loadFactor的值是isNaN,则会抛出非法异常IllegalArgumentException。

构造一个包含另一个Map的构造函数和默认负载因子(0.75)

源码解析

public HashMap(Map<? extends K, ? extends V> m) {this.loadFactor = DEFAULT_LOAD_FACTOR;// 负载因子loadFactor变为默认的负载因子0.75putMapEntries(m, false);}

案例演示

@Testpublic void test_hash_map_con_map(){HashMap<Integer, String> map = new HashMap<>();map.put(1, "aaa");map.put(2, "bbb");map.put(3, "ccc");// 使用构造方法HashMap<Integer, String> all = new HashMap<>(map);// 遍历查看all.forEach((k, v) -> System.out.println("k: " + k + " v: " + v));}

tableSizeFor方法,返回比指定初始化容量大的最小的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;}

测试案例

// 搞个测试类【模拟上面的源码】 n >>> 1右移1位 |=按位运算@Testpublic void test_wei(){int n = 14 - 1; // 13n |= n >>> 1; // 15n |= n >>> 2; // 15n |= n >>> 4; // 15n |= n >>> 8; // 15n |= n >>> 16;// 15int i = (n < 0) ? 1 : (n >= 128) ? 128 : n + 1; // 16System.out.println(i);// 16}

符号解析

>>>是右移符号。比如给定的值为5【0101】,右移一位为2【0010】。

|符号为或运算。比如给定的值11 | 15,11对应的二进制【1011】,15对应的二进制【1111】,11 | 15结果为【1111】15。

源码解析

当在实例化HashMap实例时,如果给定了initialCapacity(假设是5),由于HashMap的 capacity必须都是2的幂,因此这个方法用于找到大于等于initialCapacity(假设是5)的最小的2的幂。

initialCapacity如果就是2的幂,则返回的还是这个数)。

为什么要对cap做减1操作【int n = cap - 1】?

这是为了防⽌,如果cap已经是2的幂, ⼜没有执行这个减1操作,则执行完后面的几条无符号右移操作之后,返回的capacity将是这个cap的2倍。假如cap的值为8,经过上面的计算得到的还是8。

计算举例

以方法tableSizeFor(int cap)举例测试的数 cap = 65int n = cap - 1; ===>>>> n = 65 - 1 = 6464 对应二进制 0100 0000 n >>> 1 右移1位0100 0000 ===>>>> 0010 0000n |= n >>> 1 对应于 0100 0000 | 0010 0000 = 0110 0000 【96】n >>> 2 右移2位0110 0000 ===>>>> 0001 1000 n |= n >>> 2 对应于 0110 0000 | 0001 1000 = 0111 1000 【120】n >>> 4 右移4位0111 1000 ===>>>> 0000 0111n |= n >>> 4 对应于 0111 1000 | 0000 0111 = 0111 1111 【127】n >>> 8右移8位0111 1111 ===>>>> 0000 0000n |= n >>> 8 对应于 0111 1111 | 0000 0000 = 0111 1111 【127】n >>> 16右移16位0111 1111 ===>>>> 0000 0000n |= n >>> 16 对应于 0111 1111 | 0000 0000 = 0111 1111 【127】最后执行 return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; 返回128【128为2的7次幂,加一的原因是凑成整数次幂】

putMapEntries添加键值对到集合中

// m:给定的集合。evict:最初构造此映射时为false。如果给定的集合为null,将会抛出空指针异常NullPointerExceptionfinal void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {// 获取给定集合的长度int s = m.size();// 判断给定的集合长度是否大于0if (s > 0) {// 判断table是否已经初始化if (table == null) {// pre-size// 未初始化,s为m的实际元素个数。预先计算一个容量ft。这里为什么加1呢?有啥特殊的含义吗?float ft = ((float)s / loadFactor) + 1.0F;// 上面计算的容量不小于最大值将这个值赋值给t,否则赋值给最大值int t = ((ft < (float)MAXIMUM_CAPACITY) ?(int)ft : MAXIMUM_CAPACITY);// 判断这个容量是否大于0,大于就对这个容量进行格式化,格式为2的幂if (t > threshold)threshold = tableSizeFor(t);}// 之前的数组中有元素,判断参数中的数组长度是否大于数组容量else if (s > threshold)// 扩容resize();// 遍历给定的集合for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {// 获取给定集合每个键值对的k和vK key = e.getKey();V value = e.getValue();// 将每一个entry的键值对放到数组中putVal(hash(key), key, value, false, evict);}}}

float ft = ((float)s / loadFactor) + 1.0F这一行代码中为什么要加1.0F?

问题出现的源码float ft = ((float)s / loadFactor) + 1.0F;int t = ((ft < (float)MAXIMUM_CAPACITY) ? (int)ft : MAXIMUM_CAPACITY);

s/loadFactor的结果是⼩数,加1.0F(int)ft相当于是对小数做一个向上取整以尽可能的保证更大容量,更大的容量能够减少resize的调用次数。所以+ 1.0F是为了获取更大的容量。

例如:原来集合的元素个数是6个,那么6/0.75是8,是2的n次幂,那么新的数组⼤小就是8了。

然后原来数组的数据就会存储到长度是8的新的数组中了,这样会导致在存储元素的时候,容量不够,还得继续扩容,那么性能就会降低了,而如果+1呢,数组长度直接变为16了,这样可以减少数组的扩容次数,从而提高效率。

谢谢点赞

创作不易, 非常欢迎大家的点赞、评论和关注(^_−)☆你的点赞、评论以及关注是对我最大的支持和鼓励,而你的支持和鼓励我继续创作高质量博客的动力 !!!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。
相关阅读
撸啊撸攻略

撸啊撸攻略

2021-01-23

我看撸啊撸

我看撸啊撸

2021-12-12

撸啊撸手机游戏

撸啊撸手机游戏

2023-04-24

天天撸啊撸攻略

天天撸啊撸攻略

2022-03-17