JDK1.8的新特性
2021-03-23 11:07:01 16 举报
AI智能生成
登录查看完整内容
请大家不要直接克隆,着手梳理一遍才会变成自己的知识
作者其他创作
大纲/内容
JDK1.8的新特性
lambda表达式
本质
函数式接口
效果
减少代码量
要求
有函数式接口
有且仅有一个抽象方法的接口
注解提示
@FunctionalInterface
如果不止一个抽象函数则报错
4种函数式接口
判定型接口(有参返回布尔值)Predicate<String> predicate = (Str)->{ return Str.isEmpty(); };
供给型接口(无参有返回)Supplier<Integer> supplier = ()->{ return 1024; };
消费型接口(有参无返回)Consumer<String> consumer = (str)->{ System.out.println(str); };
方法引用
更简洁易懂的lambda 表达式替换
代码可读性更高
方法引用中::后只是方法名,不能加();
示例
list.forEach(System.out::println)
StreamAPI
函数式编程方式在集合类上进行复杂操作的工具
更像一个高级版本的 Iterator
Stream 会隐式地在内部进行遍历,做出相应的数据转换
Stream可以并行化操作
使用
1、创建流
list.stream()
list.paralleStream()
2、中间操作
过滤filter
筛选limit
去重distinct
3、终止流
reduce
将流中元素反复结合起来,得到一个值
collect
给Stream中元素做汇总的方法
并行流和串行流
parallel()---并行流
sequential()---串行流
rangeClosed
在0到10亿中遍历
parallel
把遍历的每个对象转化成单个的数据流
⭐reduce
Long::sum方法引用参数相加
新时间日期API
被final修饰的不可变日期类,适合多线程开发
分类
LocalDate
日期
LocalTime
时间
LocalDateTime
日期和时间
⭐HashMap的底层实现⭐
基本属性
初始化时默认桶大小为16,构造函数的参数是次方,默认是4,最大为30
负载因子是0.75
链表转红黑树的阈值是8,当链表节点大于8的时候转换成红黑树
转红黑树时桶的长度最小是64
红黑树转链表的阈值是6,当红黑树节点小于等于6的时候退化成链表
总结JDK 1.8 主要进行了哪些优化
底层数据结构从“数组+链表”改成“数组+链表+红黑树”,主要是优化了 hash 冲突较严重时,链表过长的查找性能:O(n) -> O(logn)。
计算 table 初始容量的方式发生了改变老的方式是从1开始不断向左进行移位运算,直到找到大于等于入参容量的值;新的方式则是通过“5个移位+或等于运算”来计算。
优化了 hash 值的计算方式,新的只是简单的让高16位参与了运算。
扩容时插入方式从“头插法”改成“尾插法”,避免了并发下的死循环。
扩容时计算节点在新表的索引位置方式从“h & (length-1)”改成“hash & oldCap”
对答
为什么要改成“数组+链表/红黑树”
主要是为了提升在 hash 冲突严重时(链表过长)的查找性能,使用链表的查找性能是 O(n),而使用红黑树是 O(logn)
那在什么时候用链表?什么时候用红黑树?
同一个数组节点链表长度大于8,达到9时,并且桶大小大于等于64则进化红黑树
同一个数组节点链表长度小于等于6的时候退化成链表
为什么链表转红黑树的阈值是8
理想情况下,使用随机的哈希码,节点分布在 hash 桶中的频率遵循泊松分布,链表中节点个数为8时的概率为 0.00000006等节点数大于8时说明节点已经非常多了,红黑树的查询性能优势就体现出来了
那为什么转回链表节点是用的6而不是复用8
如果我们设置节点多于8个转红黑树,少于8个就马上转链表,当节点个数在8徘徊时,就会频繁进行红黑树和链表的转换,造成性能的损耗
HashMap 有哪些重要属性?分别用于做什么的?
1)size:HashMap 已经存储的节点个数;
2)threshold:扩容阈值,当 HashMap 的个数达到该值,触发扩容。
3)loadFactor:负载因子,扩容阈值 = 容量 * 负载因子。
threshold 除了用于存放扩容阈值还有其他作用吗?
在我们新建 HashMap 对象时, threshold 还会被用来存初始化时的容量。HashMap 直到我们第一次插入节点时,才会对 table 进行初始化,避免不必要的空间浪费。
HashMap 的默认初始容量是多少?HashMap 的容量有什么限制吗?
默认初始容量是16。HashMap 的容量必须是2的N次方HashMap 会根据我们传入的容量计算一个大于等于该容量的最小的2的N次方,例如传 9,容量为16。
这个N次方是怎么算的?
tableSizeFor(int cap)
你说 HashMap 的容量必须是 2 的 N 次方,这是为什么?
本质就是取余操作,但是计算机的位运算效率远高于取模操作,hash mod (length) = hash & (length-1)如果不考虑性能消耗,可以直接使用取模运算
负载因子默认初始值为什么是0.75而不是其他的?
在时间和空间上权衡的结果。如果值较高,例如1,此时会减少空间开销,但是 hash 冲突的概率会增大,增加查找成本;而如果值较低,例如 0.5 ,此时 hash 冲突会降低,但是有一半的空间会被浪费,所以折衷考虑 0.75 似乎是一个合理的值。
计算 key 的 hash 值,是怎么设计的?
拿到 key 的 hashCode,并将 hashCode 的高16位和 hashCode 进行异或(XOR)运算,得到最终的 hash 值。
红黑树和链表都是通过 e.hash & oldCap == 0 来定位在新表的索引位置,这是为什么?
扩容前 table 的容量为16,a 节点和 b 节点在扩容前处于同一索引位置。
扩容后,table 长度为32,新表的 n - 1 只比老表的 n - 1 在高位多了一个1
因为 2 个节点在老表是同一个索引位置,因此计算新表的索引位置时,只取决于新表在高位多出来的这一位,而这一位的值刚好等于 oldCap。
因为只取决于这一位,所以只会存在两种情况
1) (e.hash & oldCap) == 0 ,则新表索引位置为“原索引位置”
2)(e.hash & oldCap) != 0,则新表索引位置为“原索引 + oldCap 位置”
HashMap 是线程安全的吗?
不是。HashMap 在并发下存在数据覆盖、遍历的同时进行修改会抛出 ConcurrentModificationException 异常等问题,JDK 1.8 之前还存在死循环问题。
除了 HashMap,还用过哪些 Map,在使用时怎么选择
0 条评论
回复 删除
下一页