Java基础

Java基础

access-level

官方

JCP

Java Community Proposal (Java社区提案)

官网:

The Java Community Process(SM) Program (jcp.org)

JSR

JSR : Java Specification(规范) Request

官网:

The Java Community Process(SM) Program - JSRs: Java Specification Requests - List of all JSRs (jcp.org)

image-20230425085827149

Java Platform Standard Edition 8 Documentation (oracle.com)

基础:

对象实例信息:

java对象结构-CSDN博客

这里写图片描述

日期类 - java.time/util

Java处理时间的日期类主要包括以下几种:

  1. Date类:是Java早期版本的日期类,主要用于获取当前时间和进行日期格式转换,但由于其线程不安全,并且设计较差,使用已经逐渐被弃用。

  2. Calendar类:是Java处理日期比较方便的类,可以进行日期的比较、设置、获取等操作。但使用起来还是比较繁琐,也存在很多缺陷。

  3. SimpleDateFormat类:是Java中一个用于格式化和解析日期的类,可以将日期格式化成想要的形式,也可以将字符串转换为日期对象。但在多线程环境下使用需要注意线程安全问题。

  4. LocalDate、LocalDateTime、LocalTime类:是JDK 1.8引入的新的日期时间类,提供了更加便捷和线程安全的解决方案,可以处理日期时间的加减、格式化、比较等操作。

  5. Instant类:是JDK 1.8中新增的类,可以获取精确到纳秒的时间戳。

  6. Duration类:可以用于计算两个日期之间的间隔时间,包括天数、小时数、分钟数和秒数等。

  7. StopWatch,它是Spring Framework中的一个计时工具类。它可以帮助我们在代码中方便地计算方法的执行时间,用于性能调优。

在实际开发中,建议使用JDK 1.8中新增的LocalDateTime、LocalTime等类,能够满足大部分的日期处理需求,也是线程安全的。

示例代码

  1. Date类示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
// 获取当前时间
Date now = new Date();
System.out.println(now);

// 将字符串转换成Date对象
String dateString = "2021-08-17 15:30:00";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = sdf.parse(dateString);
System.out.println(date);

// 将Date对象转换成字符串
String formattedDateString = sdf.format(date);
System.out.println(formattedDateString);
  1. Calendar类示例:
1
2
3
4
5
6
7
8
9
10
// 获取指定日期的年月日时分秒
Calendar calendar = Calendar.getInstance();
calendar.set(2021, 7, 17, 15, 30, 0); // 月份从0开始,这里设置的是8月份,因此为7
int year = calendar.get(Calendar.YEAR);
int month = calendar.get(Calendar.MONTH) + 1; // 月份从0开始,需要加1
int day = calendar.get(Calendar.DAY_OF_MONTH);
int hour = calendar.get(Calendar.HOUR_OF_DAY);
int minute = calendar.get(Calendar.MINUTE);
int second = calendar.get(Calendar.SECOND);
System.out.println(year + "-" + month + "-" + day + " " + hour + ":" + minute + ":" + second);
  1. SimpleDateFormat类示例:
1
2
3
4
5
6
7
8
9
// 将字符串转换为Date对象
String dateString = "2021-08-17 15:30:00";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = sdf.parse(dateString);
System.out.println(date);

// 将Date对象格式化为字符串
String formattedDateString = sdf.format(date);
System.out.println(formattedDateString);
  1. LocalDate、LocalDateTime、LocalTime类示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 获取当前日期和时间
LocalDateTime now = LocalDateTime.now();
System.out.println(now);

// 将时间字符串解析成LocalDateTime对象
String dateTimeString = "2021-08-17T15:30:00";
LocalDateTime dateTime = LocalDateTime.parse(dateTimeString);
System.out.println(dateTime);

// 计算两个日期间隔天数
LocalDate date1 = LocalDate.of(2021, 8, 17);
LocalDate date2 = LocalDate.of(2021, 8, 19);
long days = ChronoUnit.DAYS.between(date1, date2);
System.out.println(days);
  1. Instant类示例:
1
2
3
4
5
6
7
8
// 获取当前时间戳
Instant now = Instant.now();
System.out.println(now);

// 将时间字符串解析成Instant对象
String timestampString = "202-08-17T15:30:00Z";
Instant timestamp = Instant.parse(timestampString);
System.out.println(timestamp);
  1. Duration类示例:
1
2
3
4
5
6
// 计算两个时间的间隔
LocalTime time1 = LocalTime.of(10, 0, 0);
LocalTime time2 = LocalTime.of(11, 30, 0);
Duration duration = Duration.between(time1, time2);
long minutes = duration.toMinutes();
System.out.println(minutes);
  1. StopWatch
1
2
3
4
5
6
7
8
9
StopWatch stopWatch = new StopWatch();
stopWatch.start();

// 这里是需要计时的方法调用
Thread.sleep(3000);

stopWatch.stop();
long time = stopWatch.getTotalTimeMillis();
System.out.println(time + " ms");

上述代码中,调用stopWatch.start()开始计时,调用stopWatch.stop()结束计时,通过getTotalTimeMillis()获取计时过程总共耗费的时间,时间单位为毫秒。

LocalDate - java.time

类型 – java.lang

基础类型

Java的 8 种基本数据类型

引用类型

Collection – java.util

API:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
size
isEmpty
contains
iterator
toArray
toArray
add
remove
containsAll
addAll
removeAll
removeIf
retainAll
clear
equals
hashCode
spliterator
stream
parallelStream

image-20230824080234815

总览

List、Set、Map都是接口,前两个继承至Collection接口,Map为独立接口
Set下有HashSet,LinkedHashSet,TreeSet
List下有ArrayList、Vector、LinkedList
Map下有HashTable、LinkHashMap、HashMap、TreeMap
Collection接口下还有个Queue接口,有PriorityQueue类


img

img

img

(1条消息) Collection接口架构图以及常用类介绍_collection结构图_Rango_kjc的博客-CSDN博客

(6条消息) Java集合中List,Set以及Map的所有子类及实现等集合体系详细解析和框架图展示(最详细)_list set map 类有哪些 至少两个子类_心之所向-的博客-CSDN博客

接口,定义集合的共有方法

image-20230421091819780

List

API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
List
size
isEmpty
contains
iterator
toArray
toArray
add
remove
containsAll
addAll
addAll
removeAll
retainAll
replaceAll
sort
clear
equals
hashCode
get
set
add
remove
indexOf
lastIndexOf
listIterator
listIterator
subList
spliterator

image-20230824080738526

一、List有序,可重复

ArrayList
优点:底层数据结构是数组,查询快,增删慢。

缺点:线程不安全,效率高

Vector
优点:底层数据结构是数组,查询快。增删慢。

缺点:线程安全,效率低

Stack继承自Vector,实现一个后进先出的堆栈。

LinkedList
优点:底层数据结构是链表,查询慢,增删快。

缺点:线程不安全,效率高

List集合特有的一些方法,如sort默认实现

1
2
3
4
5
6
7
8
9
10
11
12
13

default void sort(Comparator<? super E> c) {
// 转为数组
Object[] a = this.toArray();
// 数组排序
Arrays.sort(a, (Comparator) c);
ListIterator<E> i = this.listIterator();
// 数组复制到集合
for (Object e : a) {
i.next();
i.set((E) e);
}
}

image-20230421102923677

AbstractList

image-20230421102910290

Set

二、Set无序,唯一 (排序就用TreeSet或者LinkeHashSet 否则就HashSet)

HashSet (无序,唯一)
是哈希表实现的,HashSet中的数据是无序的,可以放入null,但只能放入一个null,两者中的值都不能重复,就如数据库中唯一约束 。

LinkeedHashSet (FIFO插入有序,唯一)
使用链表扩展实现HashSet类,支持对元素的排序

1.由链表保证元素有序
2.由哈希表保证元素唯一
TreeSet (唯一,有序)
是红黑树实现的,Treeset中的数据是自动排好序的,不允许放入null值 。

如何保证元素排序的呢?

自然排序

比较器排序

2.如何保证元素唯一性的呢?

根据比较的返回值是否是0来决定

AbstractSet

只是重写equals、hashCode、removeAll方法;每个子类的实现逻辑不一样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 移除集合中的元素 -- Set独有
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
boolean modified = false;

if (size() > c.size()) {
for (Iterator<?> i = c.iterator(); i.hasNext(); )
modified |= remove(i.next());
} else {
for (Iterator<?> i = iterator(); i.hasNext(); ) {
if (c.contains(i.next())) {
// 防止fail-fast机制 用迭代器的remove方法
i.remove();
modified = true;
}
}
}
return modified;
}

image-20230421102556800

SortSet

image-20230421102336945

TreeSet

继承自

image-20230421102416154

HashSet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
{
// 都是基于HashMap的操作
private transient HashMap<E,Object> map;
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}
}

底层使用HashMap的key作为存入的值,保证不重复

image-20230421091531975

LinkedHashSet

继承自HashSet,维护双向列表

目的:HashSet不保存元素的存取顺序,元素存取无序;LinkedHashSet则保存,元素存取有序。

img

Queue -> Collection

API

先进先出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public interface Queue<E> extends Collection<E> {

boolean add(E e);

boolean offer(E e);

E remove();

E poll();

/**
* Retrieves, but does not remove, the head of this queue. This method
* differs from {@link #peek peek} only in that it throws an exception
* if this queue is empty.
*
* @return the head of this queue
* @throws NoSuchElementException if this queue is empty
*/
E element();

E peek();
}

Deque - 接口

API

image-20230824080623338

基础数据结构的实现

基础数据结构(Java实现)(1)_基础数据结构在jdk中的实现方式-CSDN博客

Map – java.util

API

Map

API

image-20230824081057816

三、Map键值对(TreeMap是有序的,HashMap和HashTable是无序的。Hashtable的方法是同步的,HashMap的方法不是同步的。Hashtable是线程安全的,HashMap不是线程安全的HashMap效率较高,Hashtable效率较低。)

HashMap
存储数据采用的哈希表结构,元素的存取顺序不能保证一致。由于要保证键的唯一、不重复,需要重写键的hashCode()方法、equals()方法。

Hashtable
Hashtable是线程安全的,其他的和HashMap相似。

LinkedHashMap
保存了记录的插入顺序,在用Iteraor遍历LinkedHashMap时,先得到的记录肯定是先插入的,在遍历的时候会比HashMap慢,有HashMap的全部特性。通过哈希表结构可以保证的键的唯一、不重复,需要重写键的hashCode()方法、equals()方法。

TreeMap–>SortedMap

 TreeMap实现SortMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序(自然顺序),也可以指定排序的比较器,当用Iterator遍历TreeMap时,得到的记录是排过序的。不允许key值为空,非同步的;

image-20230421093453501

Entry

image-20230421093513565

HashMap

put

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
// put 红黑树
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else { // Node 为链表
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
// 当前链表数 长度为 等于 7(不算当前的新Node)
// 即 原来为7,添加之后为8,则判断是否需要 树化
// 则 判断 扩容还是 树化红黑树
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
// 判断 当为同一个key的时候,是取旧值,还是新值
if (e != null) { // existing mapping for key
V oldValue = e.value;
//onlyIfAbsent – if true, don't change existing value
// false 替换为新值;true 不替换
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}

final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
// 当 数组长度 小于 64 的时候,扩容
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
// 大于等于 64,树化
else if ((e = tab[index = (n - 1) & hash]) != null) {
TreeNode<K,V> hd = null, tl = null;
do {
TreeNode<K,V> p = replacementTreeNode(e, null);
if (tl == null)
hd = p;
else {
p.prev = tl;
tl.next = p;
}
tl = p;
} while ((e = e.next) != null);
if ((tab[index] = hd) != null)
hd.treeify(tab);
}
}

LinkedHashMap

实现LRU - 最近最久未使用

关键:

遍历从head == > tail, 但是当size不够,可以实现 removeEldestEntry() =>返回为true,会将head删除

accessOrder = true (for access-order) 保持访问顺序

​ get会将Node放置到链表的tail,

accessOrder = false (for insertion-order.) 保持插入顺序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 简单用LinkedHashMap来实现的LRU算法的缓存
*/
public class LRUCache<K, V> extends LinkedHashMap<K, V> {
private int cacheSize;
public LRUCache(int cacheSize) {
// true 插入顺序排序
super(16, (float) 0.75, true);
this.cacheSize = cacheSize;
}
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > cacheSize;
}
}

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LRUCacheTest {
private static final Logger log = LoggerFactory.getLogger(LRUCacheTest.class);
private static LRUCache<String, Integer> cache = new LRUCache<>(10);

public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
cache.put("k" + i, i);
}
log.info("all cache :'{}'",cache);
cache.get("k3");
log.info("get k3 :'{}'", cache);
cache.get("k4");
log.info("get k4 :'{}'", cache);
cache.get("k4");
log.info("get k4 :'{}'", cache);
cache.put("k" + 10, 10);
log.info("After running the LRU algorithm cache :'{}'", cache);
}
}

小顶锥 + hash实现 LFU 最近最少使用算法 frequency-频率

Java内存模型操作

(1条消息) Java内存模型_yimuss的博客-CSDN博客

lock(锁定):作用于主内存的变量,它把一条变量标记为本线程所独占的。

unlock(解锁):作用于主内存的变量,它把一条处于锁定状态的变量释放出来,其他线程才可以继续锁定该变量。

read(读取):作用于主内存的变量,它把一条变量从主内存传输到线程的工作内存中,以便随后的load动作使用。

load(载入):作用于工作内存的变量,它把read操作从主内存得到的值放入工作内存的变量副本中。

use(使用):作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用变量值的字节码指令时会执行这个操作。

assign(赋值):作用于工作内存的变量,它把从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个需要给变量赋值的字节码指令时会执行这个操作。

store(存储):作用于工作内存的变量,它把工作内存中的变量传送到主内存,以便随后的write操作使用。

write(写入):作用主内存的变量,它把store操作从工作内存得到的值放入主内存的变量中。

如果需要把一个变量从主内存复制到工作内存中,按顺序操作read、load,不需要连续操作

如果需要把一个变量从工作内存复制到主内存中,按顺序操作store、write,不需要连续操作

iterator - java.util

迭代器,用于集合的遍历

Iterable - java.lang

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public interface Iterable<T> {
Iterator<T> iterator();

/*
*
* @implSpec
* <p>The default implementation behaves as if:
* <pre>{@code
* for (T t : this)
* action.accept(t);
* }</pre>
*
* @param action The action to be performed for each element
* @throws NullPointerException if the specified action is null
* @since 1.8
*/
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}

/**
* Creates a {@link Spliterator} over the elements described by this
* {@code Iterable}.
*
* @implSpec
* The default implementation creates an
* <em><a href="Spliterator.html#binding">early-binding</a></em>
* spliterator from the iterable's {@code Iterator}. The spliterator
* inherits the <em>fail-fast</em> properties of the iterable's iterator.
*
* @implNote
* The default implementation should usually be overridden. The
* spliterator returned by the default implementation has poor splitting
* capabilities, is unsized, and does not report any spliterator
* characteristics. Implementing classes can nearly always provide a
* better implementation.
*
* @return a {@code Spliterator} over the elements described by this
* {@code Iterable}.
* @since 1.8
*/
default Spliterator<T> spliterator() {
return Spliterators.spliteratorUnknownSize(iterator(), 0);
}
}

Fast-Fail

(7条消息) java中集合的的fast-fall机制_泥坑腕豪的博客-CSDN博客

fail-fast 机制,即快速失败机制,是java集合(Collection)中的一种错误检测机制。当在迭代集合的过程中该集合在结构上发生改变的时候,就有可能会发生fail-fast,即抛出 ConcurrentModificationException异常。fail-fast机制并不保证在不同步的修改下一定会抛出异常,它只是尽最大努力去抛出,所以这种机制一般仅用于检测bug。

  1. 首先要了解什么是modCount,ArrayList中的 modCount 参数是继承自AbstractList中的,modCount记录的是集合被修改的次数。(当调用remove,clear等方法时,modCount都会+1)
1
2
3
//AbstractList
protected transient int modCount = 0;
12

在这里插入图片描述
在这里插入图片描述

  1. fail-fast 出现场景
    首先fail-fast基本出现在那些不支持并发的集合类上,如ArrayList,HashMap等。
    以下以ArrayList为例:
    创建两个线程,一个线程读取集合信息,一个线程修改线程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/**
* @program: algorithm
* @description:
* @author: Mr.XYC
* @create: 2021-05-26 19:45
**/
public class demo2 {
//定义变量list为static
public static List<Integer> list = new ArrayList<>();

private static class T1 extends Thread{
@Override
public void run() {
//获取迭代器
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
//每获取一个值就睡1秒
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
private static class T2 extends Thread{
@Override
public void run() {
//睡2秒
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//等两秒之后将集合中第三个值删掉
list.remove(3);
}
}

public static void main(String[] args) {
//想集合中添加10个元素
for (int i = 0; i < 10; i++) {
list.add(i);
}
//创建T1,T2线程并开启
T1 t1 = new T1();
T2 t2 = new T2();
t1.start();
t2.start();
}
}
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152

*结果*
在这里插入图片描述
当输出两个值后,报ConcurrentModificationException(并发修改异常)

  1. fail-fast原理

fail-fast抛出 ConcurrentModificationException 异常

首先我们要清楚Iterator只是一个接口
在这里插入图片描述
所以它的具体实现另有其他,在ArrayList中它的实现类为ArrayList.Itr这个内部类
当调用list.iterator()方法时,源码是

1
2
3
4
public Iterator<E> iterator() {
return new Itr();
}
123

Itr继承了Iterator,源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;

Itr() {}

public boolean hasNext() {
return cursor != size;
}

@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}

public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();

try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}

@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}

final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566

在这里插入图片描述
接下来我们来分析一下异常原因

1
2
3
4
public boolean hasNext() {
return cursor != size;
}
123

如果cursor等于集合的size时返回false,表示已经遍历完集合
接下来看next方法

1
2
3
4
5
6
7
8
9
10
11
12
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
1234567891011

每次使用next方法是都会调用一个方法checkForComodification(),查看源码

1
2
3
4
5
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
1234

可以看出这个判断条件就是抛出异常的关键,modCount != expectedModCount。
当调用ArrayList的remove,clear等方法时会修改modCount的值,造成两值不对等,进而抛出异常
ConcurrentModificationException。

  1. 避免方法
    首先可以调用Itr的remove方法,它也会调用ArrayList中的remove方法,但是会把expectedModCount重新赋值为modCount,防止抛出异常
    在这里插入图片描述

第二种方法呢就是不使用ArrayList,使用别的并发类,例如
list:CopyOnWriteArrayList,Collections.synchronizedList(new ArrayList()),Vector()
set: Collections.synchronizedSet(new HashSet<>()),CopyOnWriteArraySet<>()
map:HashTable,Collections.synchronizedMap(new HashMap<>()),ConcurrentHashMap<>()

Iterator

集合迭代

image-20230505090506099

Spliterator

(7条消息) Java8中Spliterator详解_LifeIsForSharing的博客-CSDN博客

image-20230505090408579

java.util.concurrent

JUC

函数式接口 – java.util.function

问题:

一、问题:内部类 访问 外部类 局部变量

(8条消息) 【Java异常】Variable used in lambda expression should be final or effectively final_No8g攻城狮的博客-CSDN博客

jdk1.7 时,如果匿名内部类访问局部变量,会报错

1
Variable used in lambda expression should be final or effectively final

jdk1.8后,编译器会自动加上final修饰,所以不会报错,但是不能修改局部变量的值(因为为final)

为 Java 8 之后,在匿名类或 Lambda 表达式中访问的局部变量,如果不是 final 类型的话,编译器自动加上 final 修饰符,即Java8新特性:effectively final。

二、问题:为什么,内部类 访问 外部类 局部变量必须为final

因为不允许内部类改变局部变量的值,原因: 内部类的作用域在栈中,外部类的作用域在堆中,如果栈中修改堆中 数据,堆中数据变为垃圾,但是,栈的存活时间比堆短,

1
2
3
4
5
6
7
8
9
10
11
前面一直说 Lambda 表达式或者匿名内部类不能访问非 final 的局部变量,这是为什么呢?

首先思考外部的局部变量finalI和匿名内部类里面的finalI是否是同一个变量?

我们知道,每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接,方法出口等信息,每个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程(《深入理解Java虚拟机》第2.2.2节 Java虚拟机栈)。

就是说在执行方法的时候,局部变量会保存在栈中,方法结束局部变量也会出栈,随后会被垃圾回收掉,而此时,内部类对象可能还存在,如果内部类对象这时直接去访问局部变量的话就会出问题,因为外部局部变量已经被回收了,解决办法就是把匿名内部类要访问的局部变量复制一份作为内部类对象的成员变量,查阅资料或者通过反编译工具对代码进行反编译会发现,底层确实定义了一个新的变量,通过内部类构造函数将外部变量复制给内部类变量。

为何还需要用final修饰?
其实复制变量的方式会造成一个数据不一致的问题,在执行方法的时候局部变量的值改变了却无法通知匿名内部类的变量,随着程序的运行,就会导致程序运行的结果与预期不同,于是使用final修饰这个变量,使它成为一个常量,这样就保证了数据的一致性。

1
2
java.util.function
https://blog.csdn.net/hbdhaj/article/details/119564310

Supplier接口

Supplier< T >:包含一个无参的方法

T get():获得结果
该方法不需要参数,他会按照某种实现逻辑(由Lambda表达式实现)返回一个数据
Supplier< T >接口也被称为生产型接口,如果我们指定了接口的泛型是什么类型,那么接口中的get方法就会产生什么类型的数据供我们使用

1
2
3
4
5
6
7
8
@FunctionalInterface
public interface Supplier<T> {

/**
* Gets a result.
*/
T get();
}

Consumer接口

Consumer< T >:包含两个方法

void accept(T t):对给定的参数执行此操作
default Consumer < T > andThen(Consumer after):返回一个组合的Consumer,依次执行此操作,然后执行after操作
Consumer< T >接口也被称为消费型接口,它消费的数据类型由泛型指定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@FunctionalInterface
public interface Consumer<T> {

/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);

/**
* Returns a composed {@code Consumer} that performs, in sequence, this
* operation followed by the {@code after} operation. If performing either
* operation throws an exception, it is relayed to the caller of the
* composed operation. If performing this operation throws an exception,
* the {@code after} operation will not be performed.
*
* @param after the operation to perform after this operation
* @return a composed {@code Consumer} that performs in sequence this
* operation followed by the {@code after} operation
* @throws NullPointerException if {@code after} is null
// con1.accept(name);
// con2.accept(name);
// 返回一个组合的Consumer
con1.andThen(con2) -- 封装为一个Consumer = .accept(name); -- 接收参数
// 将两个Consumer封装为一个Consumer,
// 在一个Consumer中调用两个的accept(t),传递参数
*/
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}

Predicate接口

Predicate< T >:常用的四个方法

boolean test(T t):对给定的参数进行判断(判断逻辑由Lambda表达式实现),返回一个布尔值
default Predicate< T > negate():返回一个逻辑的否定,对应逻辑非
default Predicate< T > and():返回一个组合判断,对应短路与
default Predicate< T > or():返回一个组合判断,对应短路或
isEqual():测试两个参数是否相等
Predicate< T >:接口通常用于判断参数是否满足指定的条件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@FunctionalInterface
public interface Predicate<T> {

// 重写判断方法,返回true,false
boolean test(T t);
// 所有的 方法都是封装Predicate为 新的 Predicate ,然后 & | !三种操纵,
// &
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
// !
default Predicate<T> negate() {
return (t) -> !test(t);
}

// |
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
// 判断相等
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}

Function接口

Function<T,R>:常用的两个方法

R apply(T t):将此函数应用于给定的参数
default< V >:Function andThen(Function after):返回一个组合函数,首先将该函数应用于输入,然后将after函数应用于结果
Function<T,R>:接口通常用于对参数进行处理,转换(处理逻辑由Lambda表达式实现),然后返回一个新值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@FunctionalInterface
// 两个参数 T为传入参数,R为 返回值参数
// Target Return
public interface Function<T, R> {
// 带返回值
R apply(T t);

// 接收先前的 Function的返回值,进行运用
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
// 接收当前的 Function的返回值,进行运用
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}

// 返回给定值
static <T> Function<T, T> identity() {
return t -> t;
}
}

Stream 流

总结:没有返回Stream 的即为 终结方法,其他未非终结方法;

Stream是Java 8引入的一个用于处理集合数据的API。它提供了一系列方法来对集合进行转换、过滤、映射等操作。Stream的方法可以分为两类:终结方法和非终结方法。

  1. 终结方法(Terminal Operations):
    • forEach:对流中的每个元素执行指定的操作。
    • toArray:将流中的元素转换为数组。
    • reduce:将流中的元素进行归约操作,得到一个结果。
    • collect:将流中的元素收集到一个集合中。
    • min:返回流中的最小元素。
    • max:返回流中的最大元素。
    • count:返回流中的元素个数。
    • anyMatch:判断流中是否存在满足指定条件的元素。
    • allMatch:判断流中的所有元素是否都满足指定条件。
    • noneMatch:判断流中是否没有满足指定条件的元素。
    • findFirst:返回流中的第一个元素。
    • findAny:返回流中的任意一个元素。
  2. 非终结方法(Intermediate Operations):
    • filter:根据指定的条件过滤流中的元素。
    • map:对流中的每个元素进行映射转换。
    • flatMap:将流中的每个元素转换为一个新的流,并将所有新的流合并为一个流。
    • distinct:去除流中的重复元素。
    • sorted:对流中的元素进行排序。
    • peek:对流中的每个元素执行指定的操作,但不改变流中的元素。
    • limit:限制流中元素的数量。
    • skip:跳过流中的前几个元素。
    • unordered:取消流中元素的顺序。

终结方法是对流进行最终操作,执行后会返回一个结果或者副作用,终结方法会触发流的遍历和计算。而非终结方法是对流进行中间操作,执行后会返回一个新的流,非终结方法不会触发流的遍历和计算,只有在终结方法被调用时才会执行。

需要注意的是,Stream的方法可以链式调用,形成一个流水线,每个方法的返回值都是一个新的流,可以继续调用其他方法。

image-20230930152710337

方法引用

Java的Stream流提供了一系列接收Lambda表达式作为参数的方法,这些方法可以用于对流中的元素进行处理、转换和过滤等操作。其中,类似于System.out::println的方法被称为方法引用(Method Reference)。

方法引用是一种简化Lambda表达式的语法形式,它可以直接引用已经存在的方法或构造函数。在方法引用中,System.out::println表示引用了System.out对象的println方法。当流中的元素被传递给这个方法引用时,实际上是调用了System.out.println方法来输出元素。

方法引用的好处有以下几点:

  1. 简洁性:方法引用可以使代码更加简洁易读,尤其是对于一些常见的操作,如输出、转换等。
  2. 可读性:方法引用可以更直观地表达代码的意图,使代码更易于理解和维护。
  3. 代码复用:方法引用可以重复使用已经存在的方法,避免了重复编写相同的代码。

除了System.out::println,还有其他常见的接收Lambda参数的方法,例如:

  • forEach:对流中的每个元素执行指定的操作。
  • map:对流中的每个元素进行映射转换。
  • filter:根据指定的条件过滤流中的元素。
  • reduce:将流中的元素进行归约操作,得到一个结果。

这些方法引用和Lambda表达式的结合,使得我们可以更方便地对流进行处理,提高代码的简洁性和可读性。

希望以上信息对你有所帮助。如果你有其他问题,请随时提问。

方法引用 使用要求

当使用方法引用时,参数的匹配与方法引用的类型有关。下面我将分别举例说明匹配和不匹配的情况。

  1. 匹配的例子:
    • 静态方法引用:假设有一个函数式接口Converter,其中定义了一个抽象方法int convert(String str)。我们可以使用静态方法引用来匹配这个抽象方法,例如Integer::parseInt,它接收一个字符串参数并返回一个整数。
    • 实例方法引用:假设有一个函数式接口Comparator,其中定义了一个抽象方法int compare(String str1, String str2)。我们可以使用实例方法引用来匹配这个抽象方法,例如String::compareToIgnoreCase,它接收两个字符串参数并返回一个整数。
    • 对象方法引用:假设有一个函数式接口Consumer,其中定义了一个抽象方法void accept(String str)。我们可以使用对象方法引用来匹配这个抽象方法,例如System.out::println,它接收一个字符串参数并将其打印到控制台。
  2. 不匹配的例子:
    • 参数个数不匹配:假设有一个函数式接口Predicate,其中定义了一个抽象方法boolean test(String str1, String str2)。如果我们尝试使用实例方法引用String::equals来匹配这个抽象方法,就会出现参数个数不匹配的情况,因为equals方法只接收一个参数。
    • 参数类型不匹配:假设有一个函数式接口Function,其中定义了一个抽象方法String apply(Integer num)。如果我们尝试使用静态方法引用Math::sqrt来匹配这个抽象方法,就会出现参数类型不匹配的情况,因为sqrt方法接收的是一个double类型的参数。

需要注意的是,方法引用的参数匹配是严格的,包括参数的个数、类型和顺序。如果参数不匹配,编译器会报错。

希望以上例子能够帮助你理解方法引用的匹配和不匹配的情况。如果你有其他问题,请随时提问。

Optional - java.util

java.util包

用于处理可能为null的判断 — 封装为一个方法

image-20230505080931154

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public final class Optional<T> {
//Null指针的封装
private static final java.util.Optional<?> EMPTY = new java.util.Optional<>();
//内部包含的值对象
private final T value;
private Optional() ;
//返回EMPTY对象
public static<T> java.util.Optional<T> empty() ;
//构造函数,但是value为null,会报NPE
private Optional(T value);
//静态工厂方法,但是value为null,会报NPE
public static <T> java.util.Optional<T> of(T value);
//静态工厂方法,value可以为null
public static <T> java.util.Optional<T> ofNullable(T value) ;
//获取value,但是value为null,会报NoSuchElementException
public T get() ;
//返回value是否为null
public boolean isPresent();
//如果value不为null,则执行consumer式的函数,为null不做事
public void ifPresent(Consumer<? super T> consumer) ;
//过滤,如果value不为null,则根据条件过滤,为null不做事
public java.util.Optional<T> filter(Predicate<? super T> predicate) ;
//转换,在其外面封装Optional,如果value不为null,则map转换,为null不做事
public<U> java.util.Optional<U> map(Function<? super T, ? extends U> mapper);
//转换,如果value不为null,则map转换,为null不做事
public<U> java.util.Optional<U> flatMap(Function<? super T, java.util.Optional<U>> mapper) ;
//value为null时,默认提供other值
public T orElse(T other);
//value为null时,默认提供other值
public T orElseGet(Supplier<? extends T> other);
//value为null时,默认提供other值
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) ;
}

IO – java.io

Java IO学习整理 - 知乎 (zhihu.com)

IO流基本类

(通过适配器的方式,将InputStream的实现类,转为其他资源模式, InputStreamReader 将 InputStream转为 Reader实现类,OutputStreamWriter将OutputStream转为 Writer的实现类)

File – 文件类

RandomAccessFile – 随机存储文件类

InputStream – 字节输入流

OutputStream – 字节输出流

Reader – 字符输入流

Writer – 字符输出流

img

InputStream

image-20230422085820960

ObjectInputStream

ObjectOutputStream

对象序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
private Object readObject0(boolean unshared) throws IOException {
boolean oldMode = bin.getBlockDataMode();
if (oldMode) {
int remain = bin.currentBlockRemaining();
if (remain > 0) {
throw new OptionalDataException(remain);
} else if (defaultDataEnd) {
/*
* Fix for 4360508: stream is currently at the end of a field
* value block written via default serialization; since there
* is no terminating TC_ENDBLOCKDATA tag, simulate
* end-of-custom-data behavior explicitly.
*/
throw new OptionalDataException(true);
}
bin.setBlockDataMode(false);
}

byte tc;
while ((tc = bin.peekByte()) == TC_RESET) {
bin.readByte();
handleReset();
}

depth++;
totalObjectRefs++;
try {
// 通过不同的方式解析 InputStream转化为类对象
switch (tc) {
case TC_NULL:
return readNull();

case TC_REFERENCE:
return readHandle(unshared);

case TC_CLASS:
return readClass(unshared);

case TC_CLASSDESC:
case TC_PROXYCLASSDESC:
return readClassDesc(unshared);

case TC_STRING:
case TC_LONGSTRING:
return checkResolve(readString(unshared));

case TC_ARRAY:
return checkResolve(readArray(unshared));

case TC_ENUM:
return checkResolve(readEnum(unshared));

case TC_OBJECT:
return checkResolve(readOrdinaryObject(unshared));

case TC_EXCEPTION:
IOException ex = readFatalException();
throw new WriteAbortedException("writing aborted", ex);

case TC_BLOCKDATA:
case TC_BLOCKDATALONG:
if (oldMode) {
bin.setBlockDataMode(true);
bin.peek(); // force header read
throw new OptionalDataException(
bin.currentBlockRemaining());
} else {
throw new StreamCorruptedException(
"unexpected block data");
}

case TC_ENDBLOCKDATA:
if (oldMode) {
throw new OptionalDataException(true);
} else {
throw new StreamCorruptedException(
"unexpected end of block data");
}

default:
throw new StreamCorruptedException(
String.format("invalid type code: %02X", tc));
}
} finally {
depth--;
bin.setBlockDataMode(oldMode);
}
}

image-20230425145612255

OutputStream

Flushable

1
2
3
4
5
6
7
8
public interface Flushable {

/**
* Flushes this stream by writing any buffered output to the underlying
* stream.
*/
void flush() throws IOException;
}

image-20230422090807781

Reader

image-20230422090545110

Writer

image-20230422090522062

AutoCloseable

当 IO 模式的资源(InputStream,FileInputStream,OutputStream,Writer,Reader等)实现类该接口,则可以用通过

try - with - resource 语句块,进行自动关闭资源的操作(资源在语句块中定义 )

但是 NIO 模式的资源,功能会失效

1
2
3
4
5
6
7
/* The {@link #close()} method of an {@code AutoCloseable}
* object is called automatically when exiting a {@code
* try}-with-resources block for which the object has been declared in
* the resource specification header.*/
public interface AutoCloseable {
void close() throws Exception;
}

image-20230425145011749

CharSequence

用于

image-20230422091310688

image-20230425144509705

Appendable

对AbstractStringBuilder,CharBuffer,Writer,PrintStream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public interface Appendable {

/**
* Appends the specified character sequence to this <tt>Appendable</tt>.
*/
Appendable append(CharSequence csq) throws IOException;

/**
* Appends a subsequence of the specified character sequence to this
* <tt>Appendable</tt>.
*
* <p> An invocation of this method of the form <tt>out.append(csq, start,
* end)</tt> when <tt>csq</tt> is not <tt>null</tt>, behaves in
* exactly the same way as the invocation
*/
Appendable append(CharSequence csq, int start, int end) throws IOException;

Appendable append(char c) throws IOException;
}

image-20230425144354116

Buffer

image-20230422090926125

获取流

File,URI

IOStream

Charset - 字符集

1
2
3
Byte byte Char char String

encode - 编码, decode - 解码
StringCoding

通过byte [] 数组 初始化String,需要通过StringCoding进行 decode解码

image-20230402113730016

Buffer
1
2
3
4
5
6
7
8
9
10
11
12
13
// Invariants: mark <= position <= limit <= capacity
private int mark = -1;
public final Buffer mark() -> mark = position; // 标记 当前写到位置
private int position = 0;
public final Buffer reset() ->> position = mark; // 重新写
public final Buffer rewind() -> position = 0; // 重写
private int limit;
public final Buffer flip() -> // 限定 读边界 重新读
limit = position, position = 0;
private int capacity;

public final int remaining() -> // 剩余可 写/读 容量
public final Buffer clear() -> // 所有参数初始化,重新写

字节流

InputStream + OutputStream

字符流

Reader + Writer

java.util.stream

理解:流处理:即通过将传入的filter,map等lambda内部类在AbstractPipelineHelper中形成执行链,通过继承 Consumer的方法accept,将上一次的结果作为下一次的条件,进行传递调用,从而实现流处理数据的过程。

IntPipeline分为 Head — source,Stateful – 有状态的流处理,Stateless — 无状态的流处理

Spliterator用于将 集合分割为小的区间(task),进行并行运行

问:

PipelineHelper所用方法和作用,Spiterator的作用,BaseStream与MapReduce思想关系,IntPipeline和DoublePipeline、LongPipeline的区别,BaseStream + PipelineHelper的共同作用,他们的具体使用场景有哪些

PipelineHelper是Java 8 Stream API 中的一个工具类,用于为流操作提供一些辅助方法。它的作用包括创建Stream、终止Stream操作等等。

Spliterator是Java 8中新增的接口,主要用于对集合进行迭代,通过遍历元素并将其分割成多个部分来支持高效并行处理的任务分解框架。它的作用在于提供了一种在迭代过程中同时处理多个数据项的能力。

BaseStream是所有Stream流的基类,它定义了许多通用方法,如filter、map、reduce等,用于支持流的中间操作和结束操作。MapReduce思想是函数式编程中广泛使用的一种数据处理思路,即将处理过程分为map和reduce两个部分,其中map将输入转换为中间结果,reduce将中间结果合并为最终输出,与BaseStream的各种方法的运用都贴近了这种思想。

IntPipeline、DoublePipeline、LongPipeline是基本数据类型的流,它们与普通的对象流相比,可以避免底层自动装箱和拆箱的开销,提供更高效的性能和更小的内存占用。

BaseStream和PipelineHelper的共同作用在于实现流操作。BaseStream提供了丰富的中间操作和结束操作,而PipelineHelper则提供了创建流、终止流等帮助方法。它们的具体使用场景包括对于大数据集的处理、并行计算以及实现复杂的数据转换逻辑等等。通过它们可以实现高效且易于维护的代码,提高软件开发的效率和速度。

BaseStream

image-20230425090523430

Stream

方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
Stream
Builder
filter
map
mapToInt
mapToLong
mapToDouble
flatMap
flatMapToInt
flatMapToLong
flatMapToDouble
distinct
sorted
sorted
peek
limit
skip
forEach
forEachOrdered
toArray
toArray
reduce
reduce
reduce
collect
collect
min
max
count
anyMatch
allMatch
noneMatch
findFirst
findAny
builder
empty
of
of
iterate
generate
concat

image-20230406155355103

PipelineHelper

(8条消息) Java8 Stream API 之 IntPipeline(一) 源码解析_孙大圣666的博客-CSDN博客

用于创建流和终止流

PipelineHelper是Java 8 Stream API 中的一个工具类,用于为流操作提供一些辅助方法。它的源码解读如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
java复制代码public interface PipelineHelper<S> {

public StreamShape getSourceShape();

public int getStreamAndOpFlags();

public <P_IN> Node<P_IN> evaluate(
Spliterator<P_IN> spliterator,
boolean flatten,
IntFunction<PipelineHelper<Long>> longToIntFunction);

public <P_IN, S_OUT> Node<S_OUT> evaluateToNode(
Spliterator<P_IN> spliterator,
boolean flatten,
IntFunction<PipelineHelper<Long>> longToIntFunction);

public long exactOutputSizeIfKnown(Spliterator<?> spliterator);

public Comparator<? super S> getComparator();
}
  • getSourceShape() 方法的作用是返回输入源的形状,例如,输入源可能是数组、集合、文件等。
  • getStreamAndOpFlags() 方法返回一个整数,其中包含有关当前流以及正在应用的操作的标志信息。
  • evaluate() 方法将指定的 Spliterator 转换为中间节点,并执行所有中间操作和终止操作以生成结果 Stream。 参数flatten表示是否需要进行扁平化以减少内部数据依赖性,参数longToIntFunction是从long值到PipelineHelper对象的函数。
  • evaluateToNode() 方法与evaluate()方法类似,但其不会创建完整的Stream流并返回最终结果,而是返回中间节点的有限表示,以进行下一步计算。
  • exactOutputSizeIfKnown() 方法估计已知Spliterator的精确输出大小。
  • getComparator() 方法返回一个该流所使用的比较器,如果没有定义,则返回 null。

下面为每个方法提供一个具体的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
PipelineHelper<Integer> pipelineHelper = ((Stream<Integer>)list.stream()).spliterator();
System.out.println(pipelineHelper.getSourceShape()); // 输出:REFERENCE

int flags = pipelineHelper.getStreamAndOpFlags();
boolean parallel = StreamOpFlag.SHORT_CIRCUIT.isKnown(flags) && StreamOpFlag.NOT_ORDERED.isKnown(flags);
System.out.println("Parallel: " + parallel); // 输出:Parallel: false

Node<Integer> node = pipelineHelper.evaluate(list.spliterator(), true, (size) -> null);
node.forEach(System.out::println); // 输出:1, 2, 3, 4, 5

Node<String> node2 = pipelineHelper.evaluateToNode(list.spliterator(), true, (size) -> null);
node2.forEach(System.out::println); // 输出:IntPipeline$Head@ea26995

long size = pipelineHelper.exactOutputSizeIfKnown(list.spliterator());
System.out.println(size); // 输出:5

Comparator<Integer> comparator = pipelineHelper.getComparator();
System.out.println(comparator); // 输出:null

在以上代码中,我们首先创建了一个包含数值的列表 list。然后通过 stream() 方法获取一个 Stream 流,并将其转换为 PipelineHelper 对象。接着使用 getStreamAndOpFlags() 方法获取当前流的标志信息,使用 evaluate() 方法执行所有中间操作和终止操作以生成结果 Stream,使用 evaluateToNode() 方法返回中间节点的有限表示进行下一步计算,使用 exactOutputSizeIfKnown() 方法估计已知Spliterator的精确输出大小,使用 getComparator() 方法获取该流所使用的比较器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
// @param <P_OUT> type of output elements from the pipeline
abstract class PipelineHelper<P_OUT> {

/**
* Gets the stream shape for the source of the pipeline segment.
*
* @return the stream shape for the source of the pipeline segment.
*/
abstract StreamShape getSourceShape();

/**
* Gets the combined stream and operation flags for the output of the described
* pipeline. This will incorporate stream flags from the stream source, all
* the intermediate operations and the terminal operation.
*
* @return the combined stream and operation flags
* @see StreamOpFlag
*/
abstract int getStreamAndOpFlags();

/**
* Returns the exact output size of the portion of the output resulting from
* applying the pipeline stages described by this {@code PipelineHelper} to
* the the portion of the input described by the provided
* {@code Spliterator}, if known. If not known or known infinite, will
* return {@code -1}.
*
* @apiNote
* The exact output size is known if the {@code Spliterator} has the
* {@code SIZED} characteristic, and the operation flags
* {@link StreamOpFlag#SIZED} is known on the combined stream and operation
* flags.
*
* @param spliterator the spliterator describing the relevant portion of the
* source data
* @return the exact size if known, or -1 if infinite or unknown
*/
abstract<P_IN> long exactOutputSizeIfKnown(Spliterator<P_IN> spliterator);

/**
* Applies the pipeline stages described by this {@code PipelineHelper} to
* the provided {@code Spliterator} and send the results to the provided
* {@code Sink}.
*
* @implSpec
* The implementation behaves as if:
* <pre>{@code
* intoWrapped(wrapSink(sink), spliterator);
* }</pre>
*
* @param sink the {@code Sink} to receive the results
* @param spliterator the spliterator describing the source input to process
*/
abstract<P_IN, S extends Sink<P_OUT>> S wrapAndCopyInto(S sink, Spliterator<P_IN> spliterator);

/**
* Pushes elements obtained from the {@code Spliterator} into the provided
* {@code Sink}. If the stream pipeline is known to have short-circuiting
* stages in it (see {@link StreamOpFlag#SHORT_CIRCUIT}), the
* {@link Sink#cancellationRequested()} is checked after each
* element, stopping if cancellation is requested.
*
* @implSpec
* This method conforms to the {@code Sink} protocol of calling
* {@code Sink.begin} before pushing elements, via {@code Sink.accept}, and
* calling {@code Sink.end} after all elements have been pushed.
*
* @param wrappedSink the destination {@code Sink}
* @param spliterator the source {@code Spliterator}
*/
abstract<P_IN> void copyInto(Sink<P_IN> wrappedSink, Spliterator<P_IN> spliterator);

/**
* Pushes elements obtained from the {@code Spliterator} into the provided
* {@code Sink}, checking {@link Sink#cancellationRequested()} after each
* element, and stopping if cancellation is requested.
*
* @implSpec
* This method conforms to the {@code Sink} protocol of calling
* {@code Sink.begin} before pushing elements, via {@code Sink.accept}, and
* calling {@code Sink.end} after all elements have been pushed or if
* cancellation is requested.
*
* @param wrappedSink the destination {@code Sink}
* @param spliterator the source {@code Spliterator}
*/
abstract <P_IN> void copyIntoWithCancel(Sink<P_IN> wrappedSink, Spliterator<P_IN> spliterator);

/**
* Takes a {@code Sink} that accepts elements of the output type of the
* {@code PipelineHelper}, and wrap it with a {@code Sink} that accepts
* elements of the input type and implements all the intermediate operations
* described by this {@code PipelineHelper}, delivering the result into the
* provided {@code Sink}.
*
* @param sink the {@code Sink} to receive the results
* @return a {@code Sink} that implements the pipeline stages and sends
* results to the provided {@code Sink}
*/
abstract<P_IN> Sink<P_IN> wrapSink(Sink<P_OUT> sink);

/**
*
* @param spliterator
* @param <P_IN>
* @return
*/
abstract<P_IN> Spliterator<P_OUT> wrapSpliterator(Spliterator<P_IN> spliterator);

/**
* Constructs a @{link Node.Builder} compatible with the output shape of
* this {@code PipelineHelper}.
*
* @param exactSizeIfKnown if >=0 then a builder will be created that has a
* fixed capacity of exactly sizeIfKnown elements; if < 0 then the
* builder has variable capacity. A fixed capacity builder will fail
* if an element is added after the builder has reached capacity.
* @param generator a factory function for array instances
* @return a {@code Node.Builder} compatible with the output shape of this
* {@code PipelineHelper}
*/
abstract Node.Builder<P_OUT> makeNodeBuilder(long exactSizeIfKnown,
IntFunction<P_OUT[]> generator);

/**
* Collects all output elements resulting from applying the pipeline stages
* to the source {@code Spliterator} into a {@code Node}.
*
* @implNote
* If the pipeline has no intermediate operations and the source is backed
* by a {@code Node} then that {@code Node} will be returned (or flattened
* and then returned). This reduces copying for a pipeline consisting of a
* stateful operation followed by a terminal operation that returns an
* array, such as:
* <pre>{@code
* stream.sorted().toArray();
* }</pre>
*
* @param spliterator the source {@code Spliterator}
* @param flatten if true and the pipeline is a parallel pipeline then the
* {@code Node} returned will contain no children, otherwise the
* {@code Node} may represent the root in a tree that reflects the
* shape of the computation tree.
* @param generator a factory function for array instances
* @return the {@code Node} containing all output elements
*/
abstract<P_IN> Node<P_OUT> evaluate(Spliterator<P_IN> spliterator,
boolean flatten,
IntFunction<P_OUT[]> generator);
}

StreamShape

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
enum StreamShape {
/**
* The shape specialization corresponding to {@code Stream} and elements
* that are object references.
*/
REFERENCE,
/**
* The shape specialization corresponding to {@code IntStream} and elements
* that are {@code int} values.
*/
INT_VALUE,
/**
* The shape specialization corresponding to {@code LongStream} and elements
* that are {@code long} values.
*/
LONG_VALUE,
/**
* The shape specialization corresponding to {@code DoubleStream} and
* elements that are {@code double} values.
*/
DOUBLE_VALUE
}

Sink

image-20230425153432179

AbstractPipeline

接口的具体实现,所有的Strea最终都由 Int,Long,Double的管道实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
/* @param <E_IN>  type of input elements
* @param <E_OUT> type of output elements
* @param <S> type of the subclass implementing {@code BaseStream}
*/
abstract class AbstractPipeline<E_IN, E_OUT, S extends BaseStream<E_OUT, S>>
extends PipelineHelper<E_OUT> implements BaseStream<E_OUT, S> {
/**
* 初始的包含待处理元素的流
*/
@SuppressWarnings("rawtypes")
private final AbstractPipeline sourceStage;

/**
* 上一个流处理动作,如果当前实例是第一个流处理动作,则该属性为null
*/
@SuppressWarnings("rawtypes")
private final AbstractPipeline previousStage;

/**
* 当前流处理动作的操作标识
*/
protected final int sourceOrOpFlags;

/**
* 当前流处理动作的下一个流处理动作
*/
@SuppressWarnings("rawtypes")
private AbstractPipeline nextStage;

/**
* 非终止类型的流处理动作的个数
*/
private int depth;

/**
* 在此之前的流处理动作包括当前流处理的所有操作标识
*/
private int combinedFlags;

/**
* 当前流处理对应的Spliterator
*/
private Spliterator<?> sourceSpliterator;

/**
* The source supplier. Only valid for the head pipeline. Before the
* pipeline is consumed if non-null then {@code sourceSpliterator} must be
* null. After the pipeline is consumed if non-null then is set to null.
*/
private Supplier<? extends Spliterator<?>> sourceSupplier;

/**
* 表示流处理是否已经开始
*/
private boolean linkedOrConsumed;

/**
* True if there are any stateful ops in the pipeline; only valid for the
* source stage.
*/
private boolean sourceAnyStateful;

private Runnable sourceCloseAction;

/**
* 是否并行流处理
*/
private boolean parallel;
}

image-20230425090631846

DoublePipeline

image-20230425090529658

RefrencePipeline

image-20230406155732251

Node

image-20230425153408914

StreamOpFlag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 与流和操作的特征相对应的标志。流框架利用标志来控制、专门化或优化计算。
enum StreamOpFlag {

/*
* Each characteristic takes up 2 bits in a bit set to accommodate
* preserving, clearing and setting/injecting information.
*
* This applies to stream flags, intermediate/terminal operation flags, and
* combined stream and operation flags. Even though the former only requires
* 1 bit of information per characteristic, is it more efficient when
* combining flags to align set and inject bits.
*
* Characteristics belong to certain types, see the Type enum. Bit masks for
* the types are constructed as per the following table:
*
* DISTINCT SORTED ORDERED SIZED SHORT_CIRCUIT
* SPLITERATOR 01 01 01 01 00
* STREAM 01 01 01 01 00
* OP 11 11 11 10 01
* TERMINAL_OP 00 00 10 00 01
* UPSTREAM_TERMINAL_OP 00 00 10 00 00
*
* 01 = set/inject
* 10 = clear
* 11 = preserve
*
* Construction of the columns is performed using a simple builder for
* non-zero values.
*/
}

IntStream

(11条消息) Java8 Stream API 之 IntStream 用法全解_孙大圣666的博客-CSDN博客

Reflect - 反射 - java.lang

反射

获取一个类的Class对象的三种方法,对应的通过Class对象创建类实例也有三种方法:

1
2
3
4
5
6
7
8
9
10
// 1
Class<TestClass> class = Class.forName("com.zkw.TestClass");
// 2
Clas<TestClass> class = TestClass.class;
// 3
TestClass tc = new TestClass();
Class<TestClass> class = tc.getClass();

// 通过Class对象创建类实例
TestClass testClass = class.newInstance();

Class##ReflectionData

1
2
3
4
5
6
7
8
9
10
11
private static class ReflectionData<T> {
volatile Field[] declaredFields;
volatile Field[] publicFields;
volatile Method[] declaredMethods;
volatile Method[] publicMethods;
volatile Constructor<T>[] declaredConstructors;
volatile Constructor<T>[] publicConstructors;
// Intermediate results for getFields and getMethods
volatile Field[] declaredPublicFields;
volatile Method[] declaredPublicMethods;
volatile Class<?>[] interfaces;

Construct

1
ConstructorAccessor#newInstance()

image-20230402092343076

Method

1
2
3
Class内部类 MethodArray 
private Method[] methods;
MethodAccessor#invoke()

image-20230402092507991

Field

Annotation

1
2
Class内部类:AnnotationData
AnnotationType
1
2
3
4
5
Class.forName(className)方法,内部实际调用的方法是  Class.forName(className,true,classloader);

2个boolean参数表示类是否需要初始化, Class.forName(className)默认是需要初始化。一旦初始化,就会触发目标对象的 static块代码执行,static参数也也会被再次初始化。
ClassLoader.loadClass(className)方法,内部实际调用的方法是 ClassLoader.loadClass(className,false);
2个 boolean参数,表示目标对象是否进行链接,false表示不进行链接,由上面介绍可以,不进行链接意味着不进行包括初始化等一些列步骤,那么静态块和静态对象就不会得到执行

自定义注解是Java高阶语言特性,在各种三方框架中经常能见到它们的身影。自定义注解能够使你编写高可维护性、更具灵活性的代码。在这篇博文中,我们将探索Java的自定义注解世界,并学习如何有效地利用它们

1.什么是自定义注解

注解Annotation是以元数据的形式在Java5中引入,它添加到Java代码里,如Java类(class)、方法、属性甚至是其他注解。它们给代码提供了额外的信息,在三方框架和库用于生成代码、执行验证或在运行时应用某些行为时进行使用。自定义注解顾名思义,是你自己定义的注解。它们允许你根据自己的需求,扩展Java现有的注解集合。

2.Java自带注解

Java常见的一些注解有,@Override:用于检查方法是否是重写父类的方法。@FunctionalInterface:用于检查接口是否是函数式接口。@Deprecated:用于标注方法已经过时,不推荐使用。除了这些注解还有其他注解,

3.什么是元注解

用于其他注解的注解称为元注解。在java.lang.annotation中定义了几种元注解类型。

  • @Retention
  • @Documented
  • @Target
  • @Inherited
  • @Repeatable

3.1@Retention注解

RetentionPolicy.SOURCE:注解只保留在源码中,编译器会丢弃掉

RetentionPolicy.CLASS:注解将保留在.class文件里,但在运行时不会保留

RetentionPolicy.RUNTIME:注解将保留在.class文件中,并且jvm会保留,可以通过反射读取

如果注解类型声明中不存在Retention注解,则Retention默认为 RetentionPolicy.CLASS

3.2 @Documented注解

带有该类型的注解会被javadoc和其他类似工具进行文档化。

3.3 @Target注解

此注解用来进行限定可以使用该注解的Java元素类型,它有以下几种类型

ElementType.TYPE:允许被修饰的注解作用在类、接口(包括注解类型)和枚举上

ElementType.FIELD:允许作用在属性字段上(包括枚举常量)

ElementType.METHOD:允许作用在方法上

ElementType.PARAMETER:允许作用在方法参数上

ElementType.CONSTRUCTOR:允许作用在构造器上

ElementType.LOCAL_VARIABLE:允许作用在本地局部变量上

ElementType.ANNOTATION_TYPE:允许作用在注解上

ElementType.PACKAGE:允许作用在包上

ElementType.TYPE_PARAMETER 应用于类型变量的声明语句前。

ElementType.TYPE_USE 应用于所有使用类型的任何语句(如:泛型,类型转换等)

3.4 @Inherited注解

如果一个类用上了@Inherited修饰的注解,那么其子类也会继承这个注解

  1. 接口用上个@Inherited修饰的注解,其实现类不会继承这个注解
  2. 父类的方法用了@Inherited修饰的注解,子类也不会继承这个注解

3.5 @Repeatable注解

Java8 之前禁止对同样的注解类型声明多次,用这个元注解表示申明的注解是可以重复的,举个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(AnoType.class)
public @interface AnoType{
String value() default "value";
}

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AnoType{
AnoType[] value();
}

public class AnnotationClass {

@AnoType("hello")
@AnoType("world")
public void test(String var1, String var2) {
System.out.println(var1 + " " + var2);
}
}

4.如何定义一个自定义注解

1
2
3
4
5
6
7
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TaskCondition {

boolean index() default false;
}

如上面的代码TaskCondition就是一个自定义注解,自定义注解是@interface关键词开头。加上元注解进行搭配

5.如何获取自己定义的注解

从字段上获取自定义注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MyClass implements MyInterface {
@MyAnnotation("My Class")
private int myField;

@Override
public void myMethod() {}


public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException {

Field field = MyClass.class.getDeclaredField("myField");

MyAnnotation annotation = field.getAnnotation(MyAnnotation.class);

System.out.println(annotation.value());

}

}

从方法上获取自定义注解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MyClass {

@MyAnnotation("Hello World")
public void myMethod() {}


public static void main(String[] args) throws NoSuchMethodException {

Method method = MyClass.class.getMethod("myMethod");

MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);

System.out.println(annotation.value());

}

}

类加载器 - java.lang

很深:

深入理解JVM(十八)一一 再谈类的加载器 - 掘金 (juejin.cn)

继承关系

  • SecureClassLoader:扩展了ClassLoader,并为定义具有相关代码源和权限的类提供了额外支持,这些代码源和权限默认情况下由系统策略检索。
  • URLClassLoader:继承自SecureClassLoader,支持从jar文件和文件夹中获取class,继承于classload,加载时首先去classload里判断是否由启动类加载器加载过。

Class.forName()与ClassLoader.loadClass()

  • Class.forName():是一个静态方法,最常用的是Class.forName(String className);根据传入的类的全限定名返回一个Class 对象。该方法在将Class文件加载到内存的同时,*会执行类的初始化*。如:Class.forName(“com.atguigu.java.Helloworld”);
  • ClassLoader.loadClass():这是一个实例方法,需要一个ClassLoader对象来调用该方法。该方法将Class文件加载到内存时,并不会执行类的初始化,直到这个类第一次使用时才进行初始化。该方法因为需要得到一个ClassLoader对象,所以可以根据需要指定使用哪个类加载器.如:ClassLoader cl=XX;cl.loadClass(“com.ljw.ava.Helloworld” );

ClassLoader

ClassLoader的主要方法

  • 抽象类classLoader的主要方法:(内部没有抽象方法)
    • public final classLoader getParent():返回该类加载器的超类加载器
  • public class<?> loadClass(string name) throws ClassNotFoundException
    • 加载名称为name的类,返回结果为java.lang .Class类的实例。如果找不到类,则返回ClassNotFoundException异常。该方法中的逻辑就是双亲委派模式的实现
  • protected class<?> findclass(String name) throws ClassNotFoundException
    • 查找二进制名称为name的类,返回结果为java.lang.Class类的实例。这是一个受保护的方法,JVM鼓励我们重写此方法,需要自定义加载器遵循双亲委托机制,该方法会在检查完父类加载器之后被loadclass()方法调用。
    • 在DK1.2之前,在自定义类加载时,总会去继承ClassLoader类并重写loadClass方法,从而实现自定义的类加载类。但是在JDK1.2之后已不再建议用户去覆盖loadClass()方法,而是建议把自定义的类加载逻辑写在findClass()方法中,从前面的分析可知,findClass()方法是在loadClass()方法中被调用的,当loadClass()方法中父加载器加载失败后,则会调用自己的findClass()方法来完成类加载,这样就可以保证自定义的类加载器也符合双亲委托模式。
    • 需要注意的是ClassLoader类中并没有实现findClass()方法的具体代码逻辑,取而代之的是抛出ClassNotFoundException异常,同时应该知道的是findClass方法通常是和defineClass方法一起使用的。一般情况下,在自定义类加载器时,会直接覆盖ClassLoader的findClass()方法并编写加载规则,取得要加载类的字节码后转换成流,然后调用defineClass()方法生成类的Class对象。
  • protected final Class<?>defineClass(String name,byte[] b, int off, int len)根据给定的字节数组b转换为Class的实例,off和1en参数表示实际Class信息在byte数组中的位置和长度,其中byte数组b是ClassLoader从外部获取的。这是受保护的方法,只有在自定义ClassLoader子类中可以使用。
    • defineClass()方法是用来将byte字节流解析成JVM能够识别的Class对象(ClassLoader中己实现该方法逻辑),通过这个方法不仅能够通过class文件实例化class对象,也可以通过其他方式实例化class对象,如通过网络接收一个类的字节码,然后转换为byte字节流创建对应的Class对象。
    • defineClass()方法通常与findClass()方法一起使用,一般情况下,在自定义类加载器时,会直接覆盖ClassLoader的FindClass()方法并编写加载规则,取得要加载类的字节码后转换成流,然后调用defineClass()方法生成类的Class对象
  • protected final void resolveclass(class<?> c)
    • 链接指定的一个Java类。使用该方法可以使用类的Class对象创建完成的同时也被解析。前面我们说链接阶段主要是对字节码进行验证,为类变量分配内存并设置初始值同时将字节码文件中的符号引用转换为直接引用。
  • protected final class<?> findLoadedclass(string name)
    • 查找名称为name的已经被加载过的类,返回结果为java.lang.Class类的实例。这个方法是final方法,无法被修改。
  • private final ClassLoader parent;
    • 它也是一个ClassLoader的实例,这个字段所表示的ClassLoader也称为这个ClassLoader的双亲。在类加载的过程中,classLoader可能会将某些请求交予自己的双亲处理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
public abstract class ClassLoader {
// 执行一次
static {
registerNatives();
}
// 注册本地方法
private static native void registerNatives();


// The parent class loader for delegation
// Note: VM hardcoded the offset of this field, thus all new fields
// must be added *after* it.
private final ClassLoader parent;
// 用于类加载 ---双亲委派机制,不建议重写
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}

protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
// 委派父加载器 parent加载
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
// 如果 父加载器不能加载,则交给当前加载器加载(用于重写加载器)
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();

// 调用子类的自定义的类加载器方法
c = findClass(name);

// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
// 建议子类重写 实现自定义类加载器机制
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}

// 被ExtClassLoader 的static方法调用
protected static boolean registerAsParallelCapable() {
Class<? extends ClassLoader> callerClass =
Reflection.getCallerClass().asSubclass(ClassLoader.class);
return ParallelLoaders.register(callerClass);
}

ParallelLoaders

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
    private static class ParallelLoaders {
private ParallelLoaders() {}

// the set of parallel capable loader types
private static final Set<Class<? extends ClassLoader>> loaderTypes =
Collections.newSetFromMap(
new WeakHashMap<Class<? extends ClassLoader>, Boolean>());
static {
// 静态代码块,用于添加类加载器类型
synchronized (loaderTypes) { loaderTypes.add(ClassLoader.class); }
}

/**
* Registers the given class loader type as parallel capabale.
将给定的 类加载器(AppClassLoader,ExtClassLoader) 注册为可并行
* Returns {@code true} is successfully registered;
如果父类加载器为注册,返回false
{@code false} if loader's super class is not registered.
*/
static boolean register(Class<? extends ClassLoader> c) {
synchronized (loaderTypes) {
if (loaderTypes.contains(c.getSuperclass())) {
// register the class loader as parallel capable
// if and only if all of its super classes are.
// Note: given current classloading sequence, if
// the immediate super class is parallel capable,
// all the super classes higher up must be too.
loaderTypes.add(c);
return true;
} else {
return false;
}
}
}
}

双亲委派机制

双亲委派机制的作用:

1
2
3
避免同一类被多次加载:双亲委派机制的作用在于这样可以避免类的冲突和数据不一致问题。例如,在一个Java应用程序中,如果同一个类被不同的类加载器加载,可能会导致不同的版本存在,从而导致运行时错误。通过双亲委派机制,Java虚拟机会保证同一个类只会被加载一次,这样可以避免这种错误的发生。

提高类加载的效率:因为在加载一个类时,Java虚拟机会优先查找父类加载器中是否已经加载了该类,如果已经加载,则直接返回已经加载的类对象。这样可以减少重复加载的开销,提高类加载的效率

img

SecureClassLoader

SecureClassLoader与沙箱安全机制

1
2
3
4
5
SecurityClassLoader是Java中的一个类加载器,它主要用于实现Java安全管理。在Java沙箱机制中,每个应用程序都有自己的安全上下文,在这个上下文中定义了该应用程序所能够执行的操作和访问的资源。SecurityClassLoader可以为每个应用程序提供一个独立的类加载器,确保应用程序只能够访问到其安全上下文中被授权的类和资源。

具体来说,当一个应用程序需要加载一些类时,SecurityClassLoader会首先检查该类是否在应用程序的安全上下文中被授权。如果是,就使用SecurityClassLoader自身的加载机制进行加载,否则抛出SecurityException异常。

总的来说,SecurityClassLoader与Java沙箱机制密切相关,它通过提供独立的类加载器来保证应用程序的安全性,防止应用程序越权访问系统资源。

JVM系列(四):沙箱安全机制笔记 - 腾讯云开发者社区-腾讯云 (tencent.com)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*这个类扩展了ClassLoader,为定义具有关联代码源和默认情况下由系统策略检索的权限的类提供了额外的支持。*/
public class SecureClassLoader extends ClassLoader {
/*
* If initialization succeed this is set to true and security checks will
* succeed. Otherwise the object is not initialized and the object is
* useless.
*/
private final boolean initialized;

// HashMap that maps CodeSource to ProtectionDomain
// @GuardedBy("pdcache")
private final HashMap<CodeSource, ProtectionDomain> pdcache =
new HashMap<>(11);

private static final Debug debug = Debug.getInstance("scl");

static {
ClassLoader.registerAsParallelCapable();
}

img

URLClassLoader

类加载器之URLClassLoader - 腾讯云开发者社区-腾讯云 (tencent.com)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class URLClassLoader extends SecureClassLoader implements Closeable {
/* The search path for classes and resources -- 类和资源 搜索路径*/
private final URLClassPath ucp;
/* The context to be used when loading classes and resources
加载类和资源时使用的上下文
*/
private final AccessControlContext acc;
static {
sun.misc.SharedSecrets.setJavaNetAccess (
new sun.misc.JavaNetAccess() {
public URLClassPath getURLClassPath (URLClassLoader u) {
return u.ucp;
}

public String getOriginalHostName(InetAddress ia) {
return ia.holder.getOriginalHostName();
}
}
);
// 注册为并行类加载器
ClassLoader.registerAsParallelCapable();
}
}

image-20230421110253881

Launcher

ExtClassLoader,AppClassLoader - >URLClassLoader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
public class Launcher {
private static URLStreamHandlerFactory factory = new Launcher.Factory();
private static Launcher launcher = new Launcher();
private static String bootClassPath = System.getProperty("sun.boot.class.path");
private ClassLoader loader;
private static URLStreamHandler fileHandler;

static class AppClassLoader extends URLClassLoader {
final URLClassPath ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);
static {
ClassLoader.registerAsParallelCapable();
}
}
static class ExtClassLoader extends URLClassLoader {
static {
ClassLoader.registerAsParallelCapable();
}

public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
/// 获取路径,用于加载jdk文件夹的扩展文件 jar
final File[] var0 = getExtDirs();
/// xxxxx
}
private static File[] getExtDirs() {
String var0 = System.getProperty("java.ext.dirs");
File[] var1;
if (var0 != null) {
StringTokenizer var2 = new StringTokenizer(var0, File.pathSeparator);
int var3 = var2.countTokens();
var1 = new File[var3];

for(int var4 = 0; var4 < var3; ++var4) {
var1[var4] = new File(var2.nextToken());
}
} else {
var1 = new File[0];
}

return var1;
}
}

private static class BootClassPathHolder {
static final URLClassPath bcp;

private BootClassPathHolder() {
}

static {
URL[] var0;
if (Launcher.bootClassPath != null) {
var0 = (URL[])AccessController.doPrivileged(new PrivilegedAction<URL[]>() {
public URL[] run() {
File[] var1 = Launcher.getClassPath(Launcher.bootClassPath);
int var2 = var1.length;
HashSet var3 = new HashSet();

for(int var4 = 0; var4 < var2; ++var4) {
File var5 = var1[var4];
if (!var5.isDirectory()) {
var5 = var5.getParentFile();
}

if (var5 != null && var3.add(var5)) {
MetaIndex.registerDirectory(var5);
}
}

return Launcher.pathToURLs(var1);
}
});
} else {
var0 = new URL[0];
}

bcp = new URLClassPath(var0, Launcher.factory, (AccessControlContext)null);
bcp.initLookupCache((ClassLoader)null);
}
}
}

image-20230421111446826

img

Tomcat自定类加载器

image-20230401225412176

沙箱机制

(1条消息) java中的安全模型(沙箱机制)_java 安全模型_改变ing的博客-CSDN博客

沙箱安全机制的基本组件

5.1 字节码校验器(bytecode verifier)

确保lava类文件遵循lava语言规范。这样可以帮助Java程序实现内存保护。但并不是所有的类文件都会经过字节码校验,比如核心类。

5.2 类装载器(class loader)
  • 防止恶意代码去干涉善意的代码,比如:双亲委派机制
  • 守护了被信任的类库边界;
  • 将代码归入保护域,确定了代码的权限范围可以进行哪些资源操作
5.3 存取控制器(access controller)

存取控制器可以控制核心API对操作系统的存取权限,用户可以设定控制策略。

5.4 安全管理器(security manager)

安全管理器主要是核心API和操作系统之间的主要接口。比如实现权限控制,比存取控制器优先级高。

5.5 安全软件包(security package) :

java.security下的类和扩展包下的类,允许用户为应用增加所需要安全特性:安全提供者、消息摘要、数字签名keytools、加密、鉴别。

Throwable – java.lang.Throwable

Error

错误

Exception

异常

数据结构 API

1
车到山前必有

SPI 机制

深入理解 Java 中 SPI 机制 - 知乎 (zhihu.com)

– 桥接模式 :适配多个video 解析器

前言:

SPI(Service Provider Interface),是JDK内置的一种服务提供发现机制,本文由浅入深地介绍了Java SPI机制。

一、简介

SPI(Service Provider Interface),是JDK内置的一种服务提供发现机制,可以用来启用框架扩展和替换组件,主要是被框架的开发人员使用,比如java.sql.Driver接口,其他不同厂商可以针对同一接口做出不同的实现,MySQL和PostgreSQL都有不同的实现提供给用户,而Java的SPI机制可以为某个接口寻找服务实现。Java中SPI机制主要思想是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要,其核心思想就是解耦

SPI与API区别:

  • API是调用并用于实现目标的类、接口、方法等的描述;
  • SPI是扩展和实现以实现目标的类、接口、方法等的描述;

换句话说,API 为操作提供特定的类、方法,SPI 通过操作来符合特定的类、方法。

参考:https://stackoverflow.com/questions/2954372/difference-between-spi-and-api?answertab=votes#tab-top

SPI整体机制图如下:

img

当服务的提供者提供了一种接口的实现之后,需要在classpath下的META-INF/services/目录里创建一个以服务接口命名的文件,这个文件里的内容就是这个接口的具体的实现类。当其他的程序需要这个服务的时候,就可以通过查找这个jar包(一般都是以jar包做依赖)的META-INF/services/中的配置文件,配置文件中有接口的具体实现类名,可以根据这个类名进行加载实例化,就可以使用该服务了。JDK中查找服务的实现的工具类是:java.util.ServiceLoader。

二、应用场景

SPI扩展机制应用场景有很多,比如Common-Logging,JDBC,Dubbo等等。

SPI流程:

  1. 有关组织和公式定义接口标准
  2. 第三方提供具体实现: 实现具体方法, 配置 META-INF/services/${interface_name} 文件
  3. 开发者使用

比如JDBC场景下:

  • 首先在Java中定义了接口java.sql.Driver,并没有具体的实现,具体的实现都是由不同厂商提供。
  • 在MySQL的jar包mysql-connector-java-6.0.6.jar中,可以找到META-INF/services目录,该目录下会有一个名字为java.sql.Driver的文件,文件内容是com.mysql.cj.jdbc.Driver,这里面的内容就是针对Java中定义的接口的实现。
  • 同样在PostgreSQL的jar包PostgreSQL-42.0.0.jar中,也可以找到同样的配置文件,文件内容是org.postgresql.Driver,这是PostgreSQL对Java的java.sql.Driver的实现。

三、使用demo

1.定义一个接口HelloSPI。

1
2
3
4
package com.vivo.study.spidemo.spi;
public interface HelloSPI {
void sayHello();
}

2.完成接口的多个实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.vivo.study.spidemo.spi.impl;
import com.vivo.study.spidemo.spi.HelloSPI;
public class ImageHello implements HelloSPI {
public void sayHello() {
System.out.println("Image Hello");
}
}
package com.vivo.study.spidemo.spi.impl;
import com.vivo.study.spidemo.spi.HelloSPI;
public class TextHello implements HelloSPI {
public void sayHello() {
System.out.println("Text Hello");
}
}

在META-INF/services/目录里创建一个以com.vivo.study.spidemo.spi.HelloSPI的文件,这个文件里的内容就是这个接口的具体的实现类。

img

具体内容如下:

1
2
com.vivo.study.spidemo.spi.impl.ImageHello
com.vivo.study.spidemo.spi.impl.TextHello

3.使用 ServiceLoader 来加载配置文件中指定的实现

1
2
3
4
5
6
7
8
9
10
11
12
package com.vivo.study.spidemo.test
import java.util.ServiceLoader;
import com.vivo.study.spidemo.spi.HelloSPI;
public class SPIDemo {
public static void main(String[] args) {
ServiceLoader<HelloSPI> serviceLoader = ServiceLoader.load(HelloSPI.class);
// 执行不同厂商的业务实现,具体根据业务需求配置
for (HelloSPI helloSPI : serviceLoader) {
helloSPI.sayHello();
}
}
}

输出结果如下:

1
2
Image Hello
Text Hello

四、源码分析 - 重点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
// ServiceLoader实现了Iterable接口,可以遍历所有的服务实现者
public final class ServiceLoader<S> implements Iterable<S>
{
// 查找配置文件的目录
private static final String PREFIX = "META-INF/services/";
// 表示要被加载的服务的类或接口
private final Class<S> service;
// 这个ClassLoader用来定位,加载,实例化服务提供者
private final ClassLoader loader;
// 访问控制上下文
private final AccessControlContext acc;
// 缓存已经被实例化的服务提供者,按照实例化的顺序存储
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// 迭代器
private LazyIterator lookupIterator;
}
// 服务提供者查找的迭代器
public Iterator<S> iterator() {
return new Iterator<S>() {
Iterator<Map.Entry<String,S>> knownProviders
= providers.entrySet().iterator();
// hasNext方法
public boolean hasNext() {
if (knownProviders.hasNext())
return true;
return lookupIterator.hasNext();
}
// next方法
public S next() {
if (knownProviders.hasNext())
return knownProviders.next().getValue();
return lookupIterator.next();
}
};
}
// 服务提供者查找的迭代器
private class LazyIterator implements Iterator<S> {
// 服务提供者接口
Class<S> service;
// 类加载器
ClassLoader loader;
// 保存实现类的url
Enumeration<URL> configs = null;
// 保存实现类的全名
Iterator<String> pending = null;
// 迭代器中下一个实现类的全名
String nextName = null;

public boolean hasNext() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}

public S next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,"Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
fail(service, "Provider " + cn + " not a subtype");
}
try {
S p = service.cast(c.newInstance());
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service, "Provider " + cn + " could not be instantiated: " + x, x);
}
throw new Error(); // This cannot happen
}
}

首先,ServiceLoader实现了Iterable接口,所以它有迭代器的属性,这里主要都是实现了迭代器的hasNext和next方法。这里主要都是调用的lookupIterator的相应hasNext和next方法,lookupIterator是懒加载迭代器。

其次,LazyIterator中的hasNext方法,静态变量PREFIX就是”META-INF/services/”目录,这也就是为什么需要在classpath下的META-INF/services/目录里创建一个以服务接口命名的文件。

最后,通过反射方法Class.forName()加载类对象,并用newInstance方法将类实例化,并把实例化后的类缓存到providers对象中,(LinkedHashMap<String,S>类型) 然后返回实例对象。

五、不足

1.不能按需加载,需要遍历所有的实现,并实例化,然后在循环中才能找到我们需要的实现。如果不想用某些实现类,或者某些类实例化很耗时,它也被载入并实例化了,这就造成了浪费。

2.获取某个实现类的方式不够灵活,只能通过 Iterator 形式获取,不能根据某个参数来获取对应的实现类。

3.多个并发多线程使用 ServiceLoader 类的实例是不安全的。

六、规避

针对以上的不足点,我们在SPI机制选择时,可以考虑使用dubbo实现的SPI机制。

七、API 对比 SPI

API图解:

img

SPI图解:

img

如上面所示:

API依赖的接口位于实现者的包中,概念上更接近于实现方,组织上存在于实现者的包中,实现和接口同时存在在实现者的包中

SPI依赖的接口在调用方的包中,概念上更接近于调用方,组织上位于调用者的包中,实现逻辑在单独的包中,实现可插拔。

  • SPI机制的缺陷

通过 SPI 机制能够大大地提高接口设计的灵活性,但是 SPI 机制也存在一些缺点,比如:

1.不能按需加载,需要遍历所有的实现,并实例化,然后在循环中才能找到我们需要的实现。如果不想用某些实现类,或者某些类实例化很耗时,它也被载入并实例化了,这就造成了浪费。

2.获取某个实现类的方式不够灵活,只能通过 Iterator 形式获取,不能根据某个参数来获取对应的实现类。(Spring 的BeanFactory,ApplicationContext 就要高级一些了。)

3.多个并发多线程使用 ServiceLoader 类的实例是不安全的。


Java基础
http://example.com/2023/06/01/Java精通/Java基础/
作者
where
发布于
2023年6月1日
许可协议