JDK1.8的新特性
2021-03-23 11:07:01 16 举报
AI智能生成
请大家不要直接克隆,着手梳理一遍才会变成自己的知识
作者其他创作
大纲/内容
lambda表达式
本质
函数式接口
效果
减少代码量
要求
有函数式接口
函数式接口
本质
有且仅有一个抽象方法的接口
注解提示
@FunctionalInterface
如果不止一个抽象函数则报错
4种函数式接口
函数式接口(有参有返回)
Function<String,String> function = (str)->{ return str+"ok"; };
Function<String,String> function = (str)->{ return str+"ok"; };
判定型接口(有参返回布尔值)
Predicate<String> predicate = (Str)->{ return Str.isEmpty(); };
Predicate<String> predicate = (Str)->{ return Str.isEmpty(); };
供给型接口(无参有返回)
Supplier<Integer> supplier = ()->{ return 1024; };
Supplier<Integer> supplier = ()->{ return 1024; };
消费型接口(有参无返回)
Consumer<String> consumer = (str)->{ System.out.println(str); };
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()---串行流
示例
LongStream.rangeClosed(0L,1000000000).parallel().reduce(0,Long::sum);
rangeClosed
在0到10亿中遍历
parallel
把遍历的每个对象转化成单个的数据流
⭐reduce
合并数据流的计算,终止流的标志
Long::sum方法引用参数相加
新时间日期API
本质
被final修饰的不可变日期类,适合多线程开发
分类
LocalDate
日期
LocalTime
时间
LocalDateTime
日期和时间
⭐HashMap的底层实现⭐
基本属性
初始化时默认桶大小为16,构造函数的参数是次方,默认是4,最大为30
负载因子是0.75
链表转红黑树的阈值是8,当链表节点大于8的时候转换成红黑树
转红黑树时桶的长度最小是64
红黑树转链表的阈值是6,当红黑树节点小于等于6的时候退化成链表
初始化时默认桶大小为16,构造函数的参数是次方,默认是4,最大为30
总结JDK 1.8 主要进行了哪些优化
底层数据结构从“数组+链表”改成“数组+链表+红黑树”,主要是优化了 hash 冲突较严重时,链表过长的查找性能:O(n) -> O(logn)。
计算 table 初始容量的方式发生了改变
老的方式是从1开始不断向左进行移位运算,直到找到大于等于入参容量的值;
新的方式则是通过“5个移位+或等于运算”来计算。
老的方式是从1开始不断向左进行移位运算,直到找到大于等于入参容量的值;
新的方式则是通过“5个移位+或等于运算”来计算。
优化了 hash 值的计算方式,新的只是简单的让高16位参与了运算。
扩容时插入方式从“头插法”改成“尾插法”,避免了并发下的死循环。
扩容时计算节点在新表的索引位置方式从“h & (length-1)”改成“hash & oldCap”
对答
为什么要改成“数组+链表/红黑树”
主要是为了提升在 hash 冲突严重时(链表过长)的查找性能,使用链表的查找性能是 O(n),而使用红黑树是 O(logn)
那在什么时候用链表?什么时候用红黑树?
同一个数组节点链表长度大于8,达到9时,并且桶大小大于等于64则进化红黑树
同一个数组节点链表长度小于等于6的时候退化成链表
为什么链表转红黑树的阈值是8
理想情况下,使用随机的哈希码,节点分布在 hash 桶中的频率遵循泊松分布,链表中节点个数为8时的概率为 0.00000006
等节点数大于8时说明节点已经非常多了,红黑树的查询性能优势就体现出来了
等节点数大于8时说明节点已经非常多了,红黑树的查询性能优势就体现出来了
那为什么转回链表节点是用的6而不是复用8
如果我们设置节点多于8个转红黑树,少于8个就马上转链表,当节点个数在8徘徊时,就会频繁进行红黑树和链表的转换,造成性能的损耗
HashMap 有哪些重要属性?分别用于做什么的?
1)size:HashMap 已经存储的节点个数;
2)threshold:扩容阈值,当 HashMap 的个数达到该值,触发扩容。
3)loadFactor:负载因子,扩容阈值 = 容量 * 负载因子。
threshold 除了用于存放扩容阈值还有其他作用吗?
在我们新建 HashMap 对象时, threshold 还会被用来存初始化时的容量。
HashMap 直到我们第一次插入节点时,才会对 table 进行初始化,避免不必要的空间浪费。
HashMap 直到我们第一次插入节点时,才会对 table 进行初始化,避免不必要的空间浪费。
HashMap 的默认初始容量是多少?HashMap 的容量有什么限制吗?
默认初始容量是16。HashMap 的容量必须是2的N次方
HashMap 会根据我们传入的容量计算一个大于等于该容量的最小的2的N次方,例如传 9,容量为16。
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 似乎是一个合理的值。
如果值较高,例如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 条评论
下一页