1、新的日期时间API(我们为什么要引入新的日期时间API呢)
Java现有的日期和时间相关类存在一些问题,比如:
- 对于时间、时间戳、格式化和解析,没有明确定义的类。
- 所有 Date 类都是可变的,因此它们不是线程安全的。这是 Java Date 和 Calendar 类的最大问题之一。
- Date 类不提供国际化,没有时区支持。
这些问题,所以我们引入了新的日期时间API,他的特点有:
- 不变性:新日期时间 API 中的所有类都是不可变的,并且适用于多线程环境。
- 关注点分离:新 API 明确区分了人性化日期时间和机器时间(Unix 时间戳)。它为日期、时间、日期时间、时间戳、时区等定义了单独的类。
- 清晰度:方法定义清晰,并在所有类中执行相同的操作。例如,要获取当前实例,我们有now() 方法。所有这些类中都定义了 format() 和 parse() 方法,而不是为它们单独创建一个类。
- 所有类都使用 工厂模式 和 策略模式 以便更好地处理。一旦您使用了其中一个类中的方法,使用其他类就不会很难。
- 实用操作:所有新的日期时间 API 类都带有执行常见任务的方法,例如加、减、格式化、解析、获取日期/时间中的单独部分等。
- 可扩展:新的日期时间 API 适用于 ISO-8601 日历系统,但我们也可以将其与其他非 ISO 日历一起使用。
2、Stream API
Stream 流是一个用于处理集合数据的 API,它提供了一种简洁而灵活的方式来处理与集合相关的操作。在Java 8中引入的Stream API,使得集合操作更加高效和易于编写。
Stream 流提供了一系列的中间操作和终端操作,可以在不修改源集合的情况下对元素进行过滤、映射、排序、聚合等操作。中间操作返回一个新的流,这样可以进行链式的操作;而终端操作则会产生最终结果或副作用,结束流的处理。
Stream 流具有惰性求值的特点,也就是说在遇到终端操作之前,中间操作不会立即执行,而是等到需要结果时才进行处理。这种特性可以提高性能,并且允许我们以更简洁的方式来处理大量的数据。
stream流有很多常用方法,比如:
- forEach遍历:用来遍历流中的数据(注:是一个终结方法,遍历之后就不能继续调用Stream流中的其他方法)
- filter过滤:用于对Stream流中的数据进行过滤
- distinct去重
- skip跳过:用于跳过元素
- limit截取:用于截取流中的元素
- 最值max,min
3、为什么要用元空间代替永久代
方法区和堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。
在之前的版本中,字符串常量池存在于永久代中,在大量使用字符串的情况下,非常容易出现OOM的异常。此外,JVM加载的class的总数,方法的大小等都很难确定,因此对永久代大小的指定难以确定。太小的永久代容易导致永久代内存溢出,太大的永久代则容易导致虚拟机内存紧张。
4、垃圾回收算法
标记-清除算法
标记-清除(Mark-and-Sweep)算法分为“标记(Mark)”和“清除(Sweep)”阶段:首先标记出所有不需要回收的对象,在标记完成后统一回收掉所有没有被标记的对象。
它是最基础的收集算法,后续的算法都是对其不足进行改进得到。这种垃圾收集算法会带来两个明显的问题:效率问题:标记和清除两个过程效率都不高。空间问题:标记清除后会产生大量不连续的内存碎片。
复制算法
为了解决标记-清除算法的效率和内存碎片问题,复制(Copying)收集算法出现了。它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。
可用内存变小:可用内存缩小为原来的一半。
不适合老年代:如果存活对象数量比较大,复制性能会变得很差。
标记-整理算法
标记-整理(Mark-and-Compact)算法是根据老年代的特点提出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。由于多了整理这一步,因此效率也不高,适合老年代这种垃圾回收频率不是很高的场景。
分代收集
分代收集是根据对象的存活时间把内存分为新生代和老年代,根据各个代对象的存活特点,每个代采用不同的垃圾回收算法。
新生代采用复制算法,老年代采用标记—整理算法。垃圾算法的实现涉及大量的程序细节,而且不同的虚拟机平台实现的方法也各不相同。
5、StringBuilder和StringBuffer
线程安全性
StringBuffer是线程安全的。它的方法是通过synchronized关键字进行同步的,因此在多线程环境下使用是安全的。多个线程可以同时访问和修改同一个StringBuffer对象,不会导致数据冲突或并发修改的问题。
StringBuilder是非线程安全的。它的方法没有进行同步,没有额外的同步开销,因此在单线程环境下可以获得更好的性能。但是,在多线程环境下,如果多个线程同时访问和修改同一个StringBuilder对象,可能会导致数据不一致的问题。
性能
StringBuffer的方法使用synchronized关键字进行同步,因此在多线程环境下安全可靠,但会带来一定的性能开销。如果不涉及多线程操作,使用StringBuffer可能会导致性能略低。
StringBuilder的方法没有进行同步,没有额外的同步开销,因此在单线程环境下性能更好。因为它不需要处理线程安全性,所以通常比StringBuffer快。
可变性
StringBuffer和StringBuilder都提供了可变的字符串操作,如append()、insert()、delete()等。它们可以方便地进行字符串的修改和拼接。StringBuffer的方法都是线程安全的,因此在修改字符串时需要进行额外的同步操作,导致性能稍低。StringBuilder的方法没有添加同步操作,因此在单线程环境下可直接进行更快的操作。
使用场景
如果需要进行多线程环境下的字符串操作,或者关注线程安全性,应使用StringBuffer,它能保证数据的一致性,适用于并发操作或共享对象的场景。
如果在单线程环境下进行字符串操作,并且需要更好的性能,可以选择使用StringBuilder,它没有同步开销,并且适用于在单线程环境中构建和修改字符串。
总结起来,StringBuffer适用于多线程或需要线程安全的场景,而StringBuilder适用于单线程、性能要求高的场景。在大多数情况下,如果没有线程安全的要求,推荐使用StringBuilder,因为它的性能更高。
6、HashMap中链表转红黑树
HashMap是通过链地址法解决哈希冲突的,也就是当发生冲突时,新的元素会挂到当前桶的链表中。这样就会有一个问题,当hash冲突过多,链表就会挂的很长,我们知道,链表越长,它的查询性能就无限趋于O(N),红黑树则是log(N)。当数组长度大于64&&链表长度大于8,会转为红黑树,当红黑树节点小于等于6时又会转为链表.