将之前做的java8新特性的笔记搬运到博客上来。
目录:
一、JDK8数据结构上的变化:
【关于HashMap、LinkedHashMap、TreeMap见自己写的博客:】
https://blog.****.net/ScorpC/article/details/90288467
1、HashMap:
-----------------------------------------------------------------
1.8之前:
数组+链表,默认大小:16,0.75开始扩容(2倍);
(链表元素是加在表头)
-----------------------------------------------------------------
1.8:
Node数组+链表+红黑树;
当链表:大于8、总大小大于64,链表变为红黑树;
若链表大小小于6了,就会再次变回链表;
Node数组满了,也是会满足16,0.75开始扩容(2倍)这个过程叫 resize+rehash
(链表元素是加在表尾)
如何减少碰撞:
使用不可变的、声明作final对象,并且采用合适的equals()和 hashCode() 方法,将会减少碰撞的发生;
hash()函数时如何实现的:
key.hashCode()得到哈希值,
哈希值的低16位和高16位做异或,
再乘(n-1)得到位置;
重新调整HashMap大小resize+rehash存在的问题:
多线程环境下会存在条件竞争;
在调整大小的过程中,存储在链表中的元素的次序会反过来。因为移动到新 的 bucket 位置的时候,HashMap 并不会将元素放在链表的尾部,而是放在 头部。这是为了避免尾部遍历(tail traversing)。如果条件竞争发生了, 那么就死循环了。多线程的环境下不使用 HashMap。
hashmap扩容过程:
满足条件:16*0.75后,
先二倍扩容,
然后再rehash原来的数据到新的数组;
-----------------------------------------------------------------
hashtable特辑 复习这一块就有必要提hashtable了;
实现:数组+链表;
默认大小:11;
put操作:
首先进行索引计算:
(key.hashCode() & 0x7FFFFFFF)% table.length;
(注:0x7FFFFFFF代表int的最大值)
若在链表中找到了,则替换旧值,若未找到则继续;
当总元 素个数超过 容量 * 加载因子 时,扩容为原来 2 倍+1并重新 散列;
将新元素加到链表头部;
对修改 Hashtable 内部共享数据的方法添加了 synchronized,保证线程安 全;
HashMap 与 HashTable 区别:
默认容量不同,扩容不同
线程安全性:HashTable 安全
效率不同:HashTable 要慢,因为加锁;
Hashtable扩容的数组长度为什么时旧数组长度乘以2加1?
Hashtable中数组的长度尽量为素数或者奇数,同时Hashtable采用取模的 方式来计算数组下标,这样减少Hash碰撞,计算出来的数组下标更加均匀。 但是这样效率会比HashMap利用位运算计算数组下标低。
Hashtable为什么采用头插法的方式迁移数组?
采用头插法的方式效率更高。如果采用尾插法需要遍历数组将元素放置到 链表的末尾,而采用头插法将结点放置到链表的头部,减少了遍历数组的时 间,效率更高。
JDK1.8前HashMap也是采用头插法迁移数据,多线程情况下会造成死循环, JDK1.8对HashMap做出了优化,为什么JDK1.8Hashtable还是采用头插法 的方式迁移数据?
Hashtable是线程安全的,所以Hashtable不需要考虑并发冲突问题,可以 采用效率更高的头插法。
为什么Hashtable渐渐被弃用?
Hashtable使用synchronized来实现线程安全,在多并发的情况下效率低 下。
-----------------------------------------------------------------
2、ConcurrentHashMap:
1.7:
Segment数组(继承自ReetrantLock)+ HashEntry,
核心数据如 value,以及链表都是 volatile 修饰的,保证了获取时的可见 性;
默认大小:16、初始化时可以指定,指定后不可变,
而数组大小又决定并发程度;
虽然 HashEntry 中的 value 是用 volatile 关键词修饰的,但是并不能保 证并发的原子性,所以 put 操作时仍然需要加锁处理;
加锁:自旋获取,若超次数,改为阻塞锁获取:
首先第一步的时候会尝试获取锁,如果获取失败肯定就有其他线程存在竞争, 则利用scanAndLockForPut() 自旋获取锁;
尝试自旋获取锁;
如果重试的次数达到了 MAX_SCAN_RETRIES 则改为阻塞锁获取,保证能获取 成功。最后解除当前 Segment 的锁;
1.8:
Node数组,跑起了原来的segment分段锁;
CAS方式扩容 + synchronized保证线程的整体安全,
其中的 val next 都用了 volatile 修饰,保证了可见性。
CAS:无锁算法
借助 Unsafe 来实现 native code。CAS有3个操作数,内存值 V、旧的预 期值 A、要修改的新值 B。
当且仅当预期值 A 和内存值 V 相同时,将内存值V修改为 B,否则什么都 不做。
Unsafe 借助 CPU 指令 cmpxchg 来实现。
CAS 会出现的问题:ABA
什么是ABA:时间差引起
CAS算法实现一个重要前提需要取出内存中某时刻的数据,而在下时刻比较 并替换,那么在这个时间差类会导致数据的变化。
比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内 存中取出A,并且two进行了一些操作变成了B,然后two又将V位置的数 据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后one操 作成功。尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题 的。如果链表的头在变化了两次后恢复了原值,但是不代表链表就没有变化。
解决:增加标识,具体:对变量增加一个版本号,每次修改,版本号加 1, 比较的时候比较版 本号。
二、JDK8内存结构的变化:
将原来的方法区改为MetaSpace元空间元空间是直接在物理(本地)内存上实现的;
三、Lambda表达式
四、函数式接口
接口中只有一个抽象方法的接口 @FunctionalIterface
定义一个函数式接口:
@FunctionalInterface
public interface MyFun {
Integer count(Integer a, Integer b);
}
用一下:
@Test
public void test05(){
MyFun myFun1 = (a, b) -> a + b;
MyFun myFun2 = (a, b) -> a - b;
MyFun myFun3 = (a, b) -> a * b;
MyFun myFun4 = (a, b) -> a / b;
}
再用一下:
public Integer operation(Integer a, Integer b, MyFun myFun){
return myFun.count(a, b);
}
@Test
public void test06(){
Integer result = operation(1, 2, (x, y) -> x + y);
System.out.println(result);
}
Java内置四大核心函数式接口:
Consumer消费型接口
Supplier提供型接口
Function<T, R>函数型接口
Predicate断言型接口
五、引用
5.1:方法引用
若 Lambda 表达式体中的内容已有方法实现,则我们可以使用“方法引用”;
语法格式:
对象 :: 实例方法
对象::println;
类 :: 静态方法
Integer::compare;
类 :: 实例方法
String::equals;
5.2:构造器引用
格式:
ClassName :: new
ArrayList::new;
5.3、数组引用:
语法:
Type :: new;
创建流的几种方式:
/**
* 创建流
*/
@Test
public void test01(){
/**
* 集合流
* - Collection.stream() 穿行流
* - Collection.parallelStream() 并行流
*/
List<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();
//数组流
//Arrays.stream(array)
String[] strings = new String[10];
Stream<String> stream2 = Arrays.stream(strings);
//Stream 静态方法
//Stream.of(...)
Stream<Integer> stream3 = Stream.of(1, 2, 3);
//无限流
//迭代
Stream<Integer> stream4 = Stream.iterate(0, (i) -> ++i+i++);
stream4.forEach(System.out::println);
//生成
Stream.generate(() -> Math.random())
.limit(5)
.forEach(System.out::println);
}
// map:
@Test
public void test02(){
List<String> list = Arrays.asList("a", "b", "c");
list.stream()
.map((str) -> str.toUpperCase())
.forEach(System.out::println);
}
// flatMap:
public Stream<Character> filterCharacter(String str){
List<Character> list = new ArrayList<>();
for (char c : str.toCharArray()) {
list.add(c);
}
return list.stream();
}
@Test
public void test03(){
List<String> list = Arrays.asList("a", "b", "c");
Test02 test02 = new Test02();
list.stream()
.flatMap(test02::filterCharacter)
.forEach(System.out::println);
}
6.4、排序:
sorted():自然排序
sorted(Comparator c):定制排序
// Comparable自然排序:
@Test
public void test04(){
List<Integer> list = Arrays.asList(1,2,3,4,5);
list.stream()
.sorted() //comparaTo()
.forEach(System.out::println);
}
// Comparable定制排序:
@Test
public void test05(){
emps.stream()
.sorted((e1, e2) -> { //compara()
if (e1.getAge().equals(e2.getAge())){
return e1.getName().compareTo(e2.getName());
} else {
return e1.getAge().compareTo(e2.getAge());
}
})
.forEach(System.out::println);
}
6.5、查找/匹配:终止操作:
allMatch:检查是否匹配所有元素
anyMatch:检查是否至少匹配一个元素
noneMatch:检查是否没有匹配所有元素
findFirst:返回第一个元素
findAny:返回当前流中的任意元素
count:返回流中元素的总个数
max:返回流中最大值
min:返回流中最小值
public enum Status {
FREE, BUSY, VOCATION;
}
@Test
public void test01(){
List<Status> list = Arrays.asList(Status.FREE, Status.BUSY, Status.VOCATION);
boolean flag1 = list.stream()
.allMatch((s) -> s.equals(Status.BUSY));
System.out.println(flag1);
boolean flag2 = list.stream()
.anyMatch((s) -> s.equals(Status.BUSY));
System.out.println(flag2);
boolean flag3 = list.stream()
.noneMatch((s) -> s.equals(Status.BUSY));
System.out.println(flag3);
// 避免空指针异常
Optional<Status> op1 = list.stream()
.findFirst();
// 如果Optional为空 找一个替代的对象
Status s1 = op1.orElse(Status.BUSY);
System.out.println(s1);
Optional<Status> op2 = list.stream()
.findAny();
System.out.println(op2);
long count = list.stream()
.count();
System.out.println(count);
}
6.6、规约、收集:
归约:reduce(T identity, BinaryOperator) / reduce(BinaryOperator) 可以将流中的数据反复结合起来,得到一个值;
收集:collect 将流转换成其他形式;接收一个 Collector 接口的实现,用于给流中元素做汇总的方法;
collect:
/**
* Java:
* - reduce:需提供默认值(初始值)
* Kotlin:
* - fold:不需要默认值(初始值)
* - reduce:需提供默认值(初始值)
*/
@Test
public void test01(){
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
Integer integer = list.stream()
.reduce(0, (x, y) -> x + y);
System.out.println(integer);
}
collect:
List<Employee> emps = Arrays.asList(
new Employee(101, "Z3", 19, 9999.99),
new Employee(102, "L4", 20, 7777.77),
new Employee(103, "W5", 35, 6666.66),
new Employee(104, "Tom", 44, 1111.11),
new Employee(105, "Jerry", 60, 4444.44)
);
@Test
public void test02(){
//放入List
List<String> list = emps.stream()
.map(Employee::getName)
.collect(Collectors.toList());
list.forEach(System.out::println);
//放入Set
Set<String> set = emps.stream()
.map(Employee::getName)
.collect(Collectors.toSet());
set.forEach(System.out::println);
//放入LinkedHashSet
LinkedHashSet<String> linkedHashSet = emps.stream()
.map(Employee::getName)
.collect(Collectors.toCollection(LinkedHashSet::new));
linkedHashSet.forEach(System.out::println);
}
@Test
public void test03(){
//总数
Long count = emps.stream()
.collect(Collectors.counting());
System.out.println(count);
//平均值
Double avg = emps.stream()
.collect(Collectors.averagingDouble(Employee::getSalary));
System.out.println(avg);
//总和
Double sum = emps.stream()
.collect(Collectors.summingDouble(Employee::getSalary));
System.out.println(sum);
//最大值
Optional<Employee> max = emps.stream()
.collect(Collectors.maxBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));
System.out.println(max.get());
//最小值
Optional<Double> min = emps.stream()
.map(Employee::getSalary)
.collect(Collectors.minBy(Double::compare));
System.out.println(min.get());
}
@Test
public void test04(){
//分组
Map<Integer, List<Employee>> map = emps.stream()
.collect(Collectors.groupingBy(Employee::getId));
System.out.println(map);
//多级分组
Map<Integer, Map<String, List<Employee>>> mapMap = emps.stream()
.collect(Collectors.groupingBy(Employee::getId, Collectors.groupingBy((e) -> {
if (e.getAge() > 35) {
return "开除";
} else {
return "继续加班";
}
})));
System.out.println(mapMap);
//分区
Map<Boolean, List<Employee>> listMap = emps.stream()
.collect(Collectors.partitioningBy((e) -> e.getSalary() > 4321));
System.out.println(listMap);
}
@Test
public void test05(){
//总结
DoubleSummaryStatistics dss = emps.stream()
.collect(Collectors.summarizingDouble(Employee::getSalary));
System.out.println(dss.getMax());
System.out.println(dss.getMin());
System.out.println(dss.getSum());
System.out.println(dss.getCount());
System.out.println(dss.getAverage());
//连接
String str = emps.stream()
.map(Employee::getName)
.collect(Collectors.joining("-")); //可传入分隔符
System.out.println(str);
}
案例一:**给定一个数字列表,如何返回一个由每个数的平方构成的列表呢?(如:给定【1,2,3,4,5】,返回【1,4,9,16,25】)
@Test
public void test01(){
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
list.stream()
.map((x) -> x * x)
.forEach(System.out::println);
}
案例二:**怎样使用 map 和 reduce 数一数流中有多少个 Employee 呢?
List<Employee> emps = Arrays.asList(
new Employee(101, "Z3", 19, 9999.99),
new Employee(102, "L4", 20, 7777.77),
new Employee(103, "W5", 35, 6666.66),
new Employee(104, "Tom", 44, 1111.11),
new Employee(105, "Jerry", 60, 4444.44)
);
@Test
public void test02(){
Optional<Integer> result = emps.stream()
.map((e) -> 1)
.reduce(Integer::sum);
System.out.println(result.get());
}
6.7、并行流:
并行流:就是把一个内容分成几个数据块,并用不同的线程分别处理每个数据块的流;
Java 8 中将并行进行了优化,我们可以很容易的对数据进行操作;Stream API 可以声明性地通过 parallel() 与 sequential() 在并行流与串行流之间切换;
Fork / Join 框架:
Fork / Join 框架与传统线程池的区别:采用了“工作窃取”模式
// Fork/Join实现:
public class ForkJoinCalculate extends RecursiveTask<Long> {
private static final long serialVersionUID = 1234567890L;
private long start;
private long end;
private static final long THRESHPLD = 10000;
public ForkJoinCalculate(long start, long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
long length = end - start;
if (length <= THRESHPLD) {
long sum = 0;
for (long i = start; i <= end; i++) {
sum += i;
}
} else {
long middle = (start + end) / 2;
ForkJoinCalculate left = new ForkJoinCalculate(start, end);
left.fork(); //拆分子任务 压入线程队列
ForkJoinCalculate right = new ForkJoinCalculate(middle + 1, end);
right.fork();
return left.join() + right.join();
}
return null;
}
}
public class TestForkJoin {
/**
* ForkJoin 框架
*/
@Test
public void test01(){
Instant start = Instant.now();
ForkJoinPool pool = new ForkJoinPool();
ForkJoinCalculate task = new ForkJoinCalculate(0, 100000000L);
Long sum = pool.invoke(task);
System.out.println(sum);
Instant end = Instant.now();
System.out.println(Duration.between(start, end).getNano());
}
/**
* 普通 for循环
*/
@Test
public void test02(){
Instant start = Instant.now();
Long sum = 0L;
for (long i = 0; i < 100000000L; i++) {
sum += i;
}
Instant end = Instant.now();
System.out.println(Duration.between(start, end).getNano());
}
}
Java 8 并行流 / 串行流:
@Test
public void test03(){
//串行流(单线程):切换为并行流 parallel()
//并行流:切换为串行流 sequential()
LongStream.rangeClosed(0, 100000000L)
.parallel() //底层:ForkJoin
.reduce(0, Long::sum);
}
7、Optional:
定义:Optional 类 (java.util.Optional) 是一个容器类,代表一个值存在或不存在,原来用 null 表示一个值不存在,现在用 Optional 可以更好的表达这个概念;并且可以避免空指针异常
常用方法:
- Optional.of(T t):创建一个 Optional 实例
- Optional.empty(T t):创建一个空的 Optional 实例
- Optional.ofNullable(T t):若 t 不为 null,创建 Optional 实例,否则空实例
- isPresent():判断是否包含某值
- orElse(T t):如果调用对象包含值,返回该值,否则返回 t
- orElseGet(Supplier s):如果调用对象包含值,返回该值,否则返回 s 获取的值
- map(Function f):如果有值对其处理,并返回处理后的 Optional,否则返回 Optional.empty()
- flatmap(Function mapper):与 map 相似,要求返回值必须是 Optional
// Optional.of(T t):
@Test
public void test01(){
Optional<Employee> op = Optional.of(new Employee());
Employee employee = op.get();
}
// Optional.empty(T t):
@Test
public void test02(){
Optional<Employee> op = Optional.empty();
Employee employee = op.get();
}
// Optional.ofNullable(T t):
@Test
public void test03(){
Optional<Employee> op = Optional.ofNullable(new Employee());
Employee employee = op.get();
}
// isPresent():
@Test
public void test03(){
Optional<Employee> op = Optional.ofNullable(new Employee());
if (op.isPresent()) {
Employee employee = op.get();
}
}
8、接口
8.1、默认方法:
public interface MyFun {
default String getName(){
return "libo";
}
default Integer getAge(){
return 22;
}
}
类优先原则:
8.2、静态方法:
public interface MyFun {
static void getAddr(){
System.out.println("addr");
}
static String Hello(){
return "Hello World";
}
}
9、新的时间日期API:Date/Time API
9.1、线程安全问题:
// 传统的时间日期:存在线程安全问题
@Test
public void test01(){
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Callable<Date> task = () -> sdf.parse("20200517");
ExecutorService pool = Executors.newFixedThreadPool(10);
ArrayList<Future<Date>> result = new ArrayList<>();
for (int i = 0; i < 10; i++) {
result.add(pool.submit(task));
}
for (Future<Date> future : result) {
try {
System.out.println(future.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
pool.shutdown();
}
// 解决方式:使用ThreadLocal
public class DateFormatThreadLocal {
private static final ThreadLocal<DateFormat> df = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
public static Date convert(String source) throws ParseException{
return df.get().parse(source);
}
}
@Test
public void test02(){
Callable<Date> task = () -> DateFormatThreadLocal.convert("20200517");
ExecutorService pool = Executors.newFixedThreadPool(10);
ArrayList<Future<Date>> result = new ArrayList<>();
for (int i = 0; i < 10; i++) {
result.add(pool.submit(task));
}
for (Future<Date> future : result) {
try {
System.out.println(future.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
pool.shutdown();
}
现在使用DateTimeFormatter:
@Test
public void test03(){
DateTimeFormatter dtf = DateTimeFormatter.ISO_LOCAL_DATE;
Callable<LocalDate> task = () -> LocalDate.parse("20200517",dtf);
ExecutorService pool = Executors.newFixedThreadPool(10);
ArrayList<Future<LocalDate>> result = new ArrayList<>();
for (int i = 0; i < 10; i++) {
result.add(pool.submit(task));
}
for (Future<LocalDate> future : result) {
try {
System.out.println(future.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
pool.shutdown();
}
9.2、本地时间 / 日期
ISO 标准:
常用方法:
方法名 | 返回值类型 | 解释 |
---|---|---|
now( ) | static LocalDateTime | 从默认时区的系统时钟获取当前日期 |
of(int year, int month, int dayOfMonth, int hour, int minute, int second) | static LocalDateTime | 从年,月,日,小时,分钟和秒获得 LocalDateTime的实例,将纳秒设置为零 |
plus(long amountToAdd, TemporalUnit unit) | LocalDateTime | 返回此日期时间的副本,并添加指定的数量 |
get(TemporalField field) | int | 从此日期时间获取指定字段的值为 int |
@Test
public void test01(){
//获取当前时间日期 now
LocalDateTime ldt1 = LocalDateTime.now();
System.out.println(ldt1);
//指定时间日期 of
LocalDateTime ldt2 = LocalDateTime.of(2020, 05, 17, 16, 24, 33);
System.out.println(ldt2);
//加 plus
LocalDateTime ldt3 = ldt2.plusYears(2);
System.out.println(ldt3);
//减 minus
LocalDateTime ldt4 = ldt2.minusMonths(3);
System.out.println(ldt4);
//获取指定的你年月日时分秒... get
System.out.println(ldt2.getDayOfYear());
System.out.println(ldt2.getHour());
System.out.println(ldt2.getSecond());
}
9.3、时间戳:
/**
* Instant:以 Unix 元年 1970-01-01 00:00:00 到某个时间之间的毫秒值
*/
@Test
public void test02(){
// 默认获取 UTC 时区 (UTC:世界协调时间)
Instant ins1 = Instant.now();
System.out.println(ins1);
//带偏移量的时间日期 (如:UTC + 8)
OffsetDateTime odt1 = ins1.atOffset(ZoneOffset.ofHours(8));
System.out.println(odt1);
//转换成对应的毫秒值
long milli1 = ins1.toEpochMilli();
System.out.println(milli1);
//构建时间戳
Instant ins2 = Instant.ofEpochSecond(60);
System.out.println(ins2);
}
9.4、时间 / 日期 差
Duration:计算两个时间之间的间隔
Period:计算两个日期之间的间隔
@Test
public void test03(){
//计算两个时间之间的间隔 between
Instant ins1 = Instant.now();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Instant ins2 = Instant.now();
Duration dura1 = Duration.between(ins1, ins2);
System.out.println(dura1.getSeconds());
System.out.println(dura1.toMillis());
}
@Test
public void test04(){
LocalDate ld1 = LocalDate.of(2016, 9, 1);
LocalDate ld2 = LocalDate.now();
Period period = Period.between(ld1, ld2); // ISO 标准
System.out.println(period.getYears());
System.out.println(period.toTotalMonths());
}
9.5、时间校正器
日期操纵:
9.6、时区:
ZonedDate
ZonedTime
ZonedDateTime
10、注解:
10.1、可重复的注解:
// 定义注解:
@Repeatable(MyAnnotations.class) //指定容器类
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value() default "Java 8";
}
// 定义容器:
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotations {
MyAnnotation[] value();
}
// 测试使用可重复注解:
public class Test01 {
//重复注解
@Test
@MyAnnotation("Hello")
@MyAnnotation("World")
public void test01() throws NoSuchMethodException {
Class<Test01> clazz = Test01.class;
Method test01 = clazz.getMethod("test01");
MyAnnotation[] mas = test01.getAnnotationsByType(MyAnnotation.class);
for (MyAnnotation ma : mas) {
System.out.println(ma.value());
}
}
}
10.1、可用于类型的注解:
Java 8 新增注解:新增ElementType.TYPE_USE 和ElementType.TYPE_PARAMETER(在Target上);
参考:
https://www.runoob.com/java/java8-new-features.html
https://blog.****.net/weixin_45225595/article/details/106203264