自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

小小 Stream,一篇文章拿捏它

開(kāi)發(fā) 前端
在之前的 Java 中的 Lambda 文章中,我簡(jiǎn)要提到了 Stream 的使用。在這篇文章中將深入探討它。

在之前的 Java 中的 Lambda文章中,我簡(jiǎn)要提到了 Stream 的使用。在這篇文章中將深入探討它。首先,我們以一個(gè)熟悉的Student類為例。假設(shè)有一組學(xué)生:

public class Student {
    private String name;
    private Integer age;

    public Student(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public Integer getAge() {
        return age;
    }
    // toString 方法
    @Override
    public String toString() {
        return"Student{" + "name='" + name + '\'' + ", age=" + age + '}';
    }
}
List<Student> students = new ArrayList<>();
students.add(new Student("Bob", 18));
students.add(new Student("Ted", 17));
students.add(new Student("Zeka", 19));

現(xiàn)在有這樣一個(gè)需求:從給定的學(xué)生列表中返回年齡大于等于 18 歲的學(xué)生,按年齡降序排列,最多返回 2 個(gè)。

在Java7 及更早的代碼中,我們會(huì)這樣實(shí)現(xiàn):

public static List<Student> getTwoOldestStudents(List<Student> students) { 
    List<Student> result = new ArrayList<>(); 
    // 1. 遍歷學(xué)生列表,篩選出符合年齡條件的學(xué)生
    for (Student student : students) { 
        if (student.getAge() >= 18) { 
            result.add(student); 
        } 
    } 
    // 2. 對(duì)符合條件的學(xué)生按年齡排序
    result.sort((s1, s2) -> s2.getAge() - s1.getAge()); 
    // 3. 如果結(jié)果大于 2 個(gè),截取前兩個(gè)數(shù)據(jù)并返回
    if (result.size() > 2) { 
        result = result.subList(0, 2); 
    } 
    return result; 
}

在Java8 及以后的版本中,借助 Stream,我們可以更優(yōu)雅地寫(xiě)出以下代碼:

public static List<Student> getTwoOldestStudentsByStream(List<Student> students) {
    return students.stream()
        .filter(s -> s.getAge() >= 18)
        .sorted((s1, s2) -> s2.getAge() - s1.getAge())
        .limit(2)
        .collect(Collectors.toList());
}

兩種方法的區(qū)別:

  • 從功能角度來(lái)看,過(guò)程式代碼實(shí)現(xiàn)將集合元素、循環(huán)迭代和各種邏輯判斷耦合在一起,暴露了太多細(xì)節(jié)。隨著需求的變化和復(fù)雜化,過(guò)程式代碼將變得難以理解和維護(hù)。
  • 函數(shù)式解決方案將代碼細(xì)節(jié)和業(yè)務(wù)邏輯解耦。類似于 SQL 語(yǔ)句,它表達(dá)的是“做什么”而不是“怎么做”,讓程序員更專注于業(yè)務(wù)邏輯,寫(xiě)出更簡(jiǎn)潔、易理解和維護(hù)的代碼。

基于我日常項(xiàng)目的實(shí)踐經(jīng)驗(yàn),我對(duì) Stream 的核心點(diǎn)、易混淆的用法、典型使用場(chǎng)景等做了詳細(xì)總結(jié)。希望能幫助大家更全面地理解 Stream,并在項(xiàng)目開(kāi)發(fā)中更高效地應(yīng)用它。

一、初識(shí) Stream

Java 8 新增了 Stream 特性,它使用戶能夠以函數(shù)式且更簡(jiǎn)單的方式操作 List、Collection 等數(shù)據(jù)結(jié)構(gòu),并在用戶無(wú)感知的情況下實(shí)現(xiàn)并行計(jì)算。

簡(jiǎn)而言之,Stream 操作被組合成一個(gè) Stream 管道。Stream 管道由以下三部分組成:

  • 創(chuàng)建 Stream(從源數(shù)據(jù)創(chuàng)建,源數(shù)據(jù)可以是數(shù)組、集合、生成器函數(shù)、I/O 通道等);
  • 中間操作(可能有零個(gè)或多個(gè),它們將一個(gè) Stream 轉(zhuǎn)換為另一個(gè) Stream,例如filter(Predicate));
  • 終止操作(產(chǎn)生結(jié)果從而終止 Stream,例如count()或forEach(Consumer))。

下圖展示了這些過(guò)程:

每個(gè)階段里的 Stream 操作都包含多個(gè)方法。我們先來(lái)簡(jiǎn)單了解下每個(gè)方法的功能。

1. 創(chuàng)建 Stream

主要負(fù)責(zé)直接創(chuàng)建一個(gè)新的 Stream,或基于現(xiàn)有的數(shù)組、List、Set、Map 等集合類型對(duì)象創(chuàng)建新的 Stream。

API

解釋

stream()

創(chuàng)建一個(gè)新的串行流對(duì)象

parallelStream()

創(chuàng)建一個(gè)可以并行執(zhí)行的流對(duì)象

Stream.of()

從給定的元素序列創(chuàng)建一個(gè)新的串行流對(duì)象

除了Stream,還有IntStream、LongStream和DoubleStream等基本類型的流,它們都稱為“流”。

2. 中間操作

這一步負(fù)責(zé)處理 Stream 并返回一個(gè)新的 Stream 對(duì)象。中間操作可以疊加。

API

解釋

filter()

過(guò)濾符合條件的元素并返回一個(gè)新的流

sorted()

按指定規(guī)則對(duì)所有元素排序并返回一個(gè)新的流

skip()

跳過(guò)集合前面的指定數(shù)量的元素并返回一個(gè)新的流

distinct()

去重并返回一個(gè)新的流

limit()

只保留集合前面的指定數(shù)量的元素并返回一個(gè)新的流

concat()

將兩個(gè)流的數(shù)據(jù)合并為一個(gè)新的流并返回

peek()

遍歷并處理流中的每個(gè)元素并返回處理后的流

map()

將現(xiàn)有元素轉(zhuǎn)換為另一種對(duì)象類型(一對(duì)一)并返回一個(gè)新的流

flatMap()

將現(xiàn)有元素轉(zhuǎn)換為另一種對(duì)象類型(一對(duì)多),即一個(gè)原始元素對(duì)象可能轉(zhuǎn)換為一個(gè)或多個(gè)新類型的元素,然后返回一個(gè)新的流

3. 終止操作

顧名思義,終止操作后 Stream 將結(jié)束,最后可能會(huì)執(zhí)行一些邏輯處理,或根據(jù)需求返回一些執(zhí)行結(jié)果。

API

解釋

findFirst()

找到第一個(gè)符合條件的元素時(shí)終止流處理

findAny()

找到任意一個(gè)符合條件的元素時(shí)終止流處理

anyMatch()

返回布爾值,類似于isContains(),用于判斷是否有符合條件的元素

allMatch()

返回布爾值,用于判斷是否所有元素都符合條件

noneMatch()

返回布爾值,用于判斷是否所有元素都不符合條件

min()

返回流處理后的最小值

max()

返回流處理后的最大值

count()

返回流處理后的元素?cái)?shù)量

collect()

將流轉(zhuǎn)換為指定類型,通過(guò)Collectors指定

toArray()

將流轉(zhuǎn)換為數(shù)組

iterator()

將流轉(zhuǎn)換為迭代器對(duì)象

forEach()

無(wú)返回值,遍歷元素并執(zhí)行給定的處理邏輯

二、代碼實(shí)戰(zhàn)

1. 創(chuàng)建 Stream

// Stream.of, IntStream.of...
Stream<String> nameStream = Stream.of("Bob", "Ted", "Zeka");
IntStream ageStream = IntStream.of(18, 17, 19);

// stream, parallelStream
Stream<Student> studentStream = students.stream();
Stream<Student> studentParallelStream = students.parallelStream();

在大多數(shù)情況下,我們基于現(xiàn)有的集合創(chuàng)建 Stream。

2. 中間操作

(1) map

map和flatMap都用于將現(xiàn)有元素轉(zhuǎn)換為其他類型。區(qū)別在于:

  • map必須是一對(duì)一的,即每個(gè)元素只能轉(zhuǎn)換為一個(gè)新元素;
  • flatMap可以是一對(duì)多的,即每個(gè)元素可以轉(zhuǎn)換為一個(gè)或多個(gè)新元素。

我們先來(lái)看map方法。當(dāng)前需求如下:將之前的學(xué)生對(duì)象列表轉(zhuǎn)換為學(xué)生姓名列表并輸出:

public static List<String> objectToString(List<Student> students) {
    return students.stream()
        .map(Student::getName)
        .collect(Collectors.toList());
}

輸出:

[Bob, Ted, Zeka]

可以看到,輸入中有三個(gè)學(xué)生,輸出也是三個(gè)學(xué)生姓名。

(2) flatMap

學(xué)校要求每個(gè)學(xué)生加入一個(gè)團(tuán)隊(duì)。假設(shè) Bob、Ted 和 Zeka 加入了籃球隊(duì),Alan、Anne 和 Davis 加入了足球隊(duì)。

public class Team {
    private String type;
    private List<Student> students;

    public Team(String type, List<Student> students) {
        this.type = type;
        this.students = students;
    }

    public String getType() {
        return type;
    }

    public List<Student> getStudents() {
        return students;
    }
@Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("Team{");
        sb.append("type='").append(type).append('\'');
        sb.append(", students=[");
        for (int i = 0; i < students.size(); i++) {
            Student student = students.get(i);
            sb.append("{name='").append(student.getName()).append("', age=").append(student.getAge()).append('}');
            if (i < students.size() - 1) {
                sb.append(", ");
            }
        }
        sb.append("]}");
        return sb.toString();
    }
}
List<Student> basketballStudents = new ArrayList<>();
basketballStudents.add(new Student("Bob", 18));
basketballStudents.add(new Student("Ted", 17));
basketballStudents.add(new Student("Zeka", 19));

List<Student> footballStudents = new ArrayList<>();
footballStudents.add(new Student("Alan", 19));
footballStudents.add(new Student("Anne", 21));
footballStudents.add(new Student("Davis", 21));

Team basketballTeam = new Team("basketball", basketballStudents);
Team footballTeam = new Team("football", footballStudents);

List<Team> teams = new ArrayList<>();
teams.add(basketballTeam);
teams.add(footballTeam);

現(xiàn)在我們需要統(tǒng)計(jì)所有團(tuán)隊(duì)中的學(xué)生,并將他們合并到一個(gè)列表中。你會(huì)如何實(shí)現(xiàn)這個(gè)需求?

在 Java7 及更早的版本中可以通過(guò)以下方式解決:

List<Student> allStudents = new ArrayList<>();
for (Team team : teams) {
    for (Student student : team.getStudents()) {
        allStudents.add(student);
    }
}

但這段代碼有兩個(gè)嵌套的 for 循環(huán),不夠優(yōu)雅。面對(duì)這個(gè)需求,flatMap可以派上用場(chǎng)。

List<Student> allStudents = teams.stream()
    .flatMap(t -> t.getStudents().stream())
    .collect(Collectors.toList());

一行代碼就搞定了。flatMap方法接受一個(gè) lambda 表達(dá)式函數(shù),函數(shù)的返回值必須是一個(gè) Stream 類型。flatMap方法最終會(huì)將所有返回的 Stream 合并生成一個(gè)新的 Stream,而map方法無(wú)法做到。

下圖清晰地展示了flatMap的處理邏輯:

(3) filter, distinct, sorted, limit

關(guān)于剛才所有團(tuán)隊(duì)中的學(xué)生列表,我們現(xiàn)在需要知道這些學(xué)生中第二和第三大的年齡。他們必須至少 18 歲。此外,如果有重復(fù)的年齡,只能算一個(gè)。

List<Integer> topTwoAges = allStudents.stream()
    .map(Student::getAge) // [18, 17, 19, 19, 21, 21]
    .filter(a -> a >= 18) // [18, 19, 19, 21, 21]
    .distinct() // [18, 19, 21]
    .sorted((a1, a2) -> a2 - a1) // [21, 19, 18]
    .skip(1) // [19, 18]
    .limit(2) // [19, 18]
    .collect(Collectors.toList());
System.out.println(topTwoAges);

輸出:

[19, 18]

注意:由于在skip方法操作后只剩下兩個(gè)元素,limit步驟實(shí)際上可以省略。

(4) peek, foreach

peek方法和foreach方法都可以用于遍歷元素并逐個(gè)處理,因此我們將它們放在一起進(jìn)行比較和講解。但值得注意的是,peek是一個(gè)中間操作方法,而foreach是一個(gè)終止操作方法。

中間操作只能作為 Stream 管道中間的處理步驟,不能直接執(zhí)行以獲取結(jié)果,必須與終止操作配合執(zhí)行。而foreach作為一個(gè)沒(méi)有返回值的終止方法,可以直接執(zhí)行相應(yīng)的操作。

比如,我們分別使用peek和foreach對(duì)籃球隊(duì)的每個(gè)學(xué)生說(shuō)“Hello, xxx…”。

// peek
System.out.println("---start peek---");
basketballTeam.getStudents().stream().peek(s -> System.out.println("Hello, " + s.getName()));
System.out.println("---end peek---");

// foreach
System.out.println("---start foreach---");
basketballTeam.getStudents().stream().forEach(s -> System.out.println("Hello, " + s.getName()));
System.out.println("---end foreach---");

從輸出中可以看出,peek在單獨(dú)調(diào)用時(shí)不會(huì)執(zhí)行,而foreach可以直接執(zhí)行:

---start peek---
---end peek---
---start foreach---
Hello, Bob
Hello, Ted
Hello, Zeka
---end foreach---

如果在peek后面加上終止操作,它就可以執(zhí)行。

System.out.println("---start peek---");
basketballTeam.getStudents().stream().peek(s -> System.out.println("Hello, " + s.getName())).count();
System.out.println("---end peek---");

// 輸出
---start peek---
Hello, Bob
Hello, Ted
Hello, Zeka
---end peek---

peek應(yīng)謹(jǐn)慎用于業(yè)務(wù)處理邏輯。因?yàn)閜eek方法是否執(zhí)行在各個(gè)版本并不一致。

例如,在 Java8 版本中,剛才的peek方法會(huì)正常執(zhí)行,但在 Java17 中,它會(huì)被自動(dòng)優(yōu)化,peek中的邏輯不會(huì)執(zhí)行。至于原因,你可以查看 JDK17 的官方 API 文檔。

三、終止操作

根據(jù)終止操作返回的結(jié)果類型大概分為兩類。

一類返回的是簡(jiǎn)單類型,主要包括max、min、count、findAny、findFirst、anyMatch、allMatch等方法。

另一類是返回的是集合類型。大多數(shù)場(chǎng)景是獲取集合類的結(jié)果對(duì)象,如 List、Set 或 HashMap 等,主要通過(guò)collect方法實(shí)現(xiàn)。

1. 簡(jiǎn)單結(jié)果類型

(1) max, min

max()和min()主要用于返回流處理后元素的最大值/最小值。返回結(jié)果由Optional包裝。關(guān)于Optional的使用,請(qǐng)參考之前的Java 中如何優(yōu)雅地處理 null 值文章 。

我們直接看例子:

找到足球隊(duì)中年齡最大和最小的是誰(shuí)?

// max
footballTeam.getStudents().stream()
    .map(Student::getAge)
    .max(Comparator.comparing(a -> a))
    .ifPresent(a -> System.out.println("足球隊(duì)中最大的年齡是:" + a));

// min
footballTeam.getStudents().stream()
    .map(Student::getAge)
    .min(Comparator.comparing(a -> a))
    .ifPresent(a -> System.out.println("足球隊(duì)中最小的年齡是:" + a));

輸出:

足球隊(duì)中最大的年齡是:21
足球隊(duì)中最小的年齡是:19

(2) findAny, findFirst

findAny()和findFirst()主要用于在找到符合條件的元素。對(duì)于串行 Stream,findAny()和findFirst()功能相同;對(duì)于并行 Stream,findAny()更高效。

假設(shè)籃球隊(duì)新增了一個(gè)學(xué)生 Tom,年齡為 19 歲。

List<Student> basketballStudents = new ArrayList<>();
basketballStudents.add(new Student("Bob", 18));
basketballStudents.add(new Student("Ted", 17));
basketballStudents.add(new Student("Zeka", 19));
basketballStudents.add(new Student("Tom", 19));

現(xiàn)在需要查找到:

  • 籃球隊(duì)中第一個(gè)年齡為 19 歲的學(xué)生姓名;
  • 籃球隊(duì)中任意一個(gè)年齡為 19 歲的學(xué)生姓名。
// findFirst
basketballStudents.stream()
    .filter(s -> s.getAge() == 19)
    .findFirst()
    .map(Student::getName)
    .ifPresent(name -> System.out.println("findFirst: " + name));

// findAny
basketballStudents.stream()
    .filter(s -> s.getAge() == 19)
    .findAny()
    .map(Student::getName)
    .ifPresent(name -> System.out.println("findAny: " + name));

輸出:

findFirst: Zeka
findAny: Zeka

可以看到,在串行 Stream 下,這兩個(gè)功能沒(méi)有區(qū)別。并行處理的區(qū)別將在后面介紹。

(3) count

籃球隊(duì)新增了一個(gè)學(xué)生,現(xiàn)在籃球隊(duì)有多少學(xué)生?

System.out.println("籃球隊(duì)的學(xué)生人數(shù):" + basketballStudents.stream().count());

輸出:

籃球隊(duì)的學(xué)生人數(shù):4

(4) anyMatch, allMatch, noneMatch

顧名思義,這三個(gè)方法用于判斷元素是否符合條件,并返回布爾值??匆韵氯齻€(gè)例子:

  • 足球隊(duì)中是否有名為 Alan 的學(xué)生?
  • 足球隊(duì)中的所有學(xué)生是否都小于 22 歲?
  • 足球隊(duì)中是否沒(méi)有年齡超過(guò) 20 歲的學(xué)生?
// anyMatch
System.out.println("anyMatch: " + footballStudents.stream().anyMatch(s -> s.getName().equals("Alan")));

// allMatch
System.out.println("allMatch: " + footballStudents.stream().allMatch(s -> s.getAge() < 22));

// noneMatch
System.out.println("noneMatch: " + footballStudents.stream().noneMatch(s -> s.getAge() > 20));

輸出:

anyMatch: true
allMatch: true
noneMatch: false

2. 結(jié)果集合類型

(1) 生成集合

生成集合應(yīng)該是collect最常用的場(chǎng)景。除了之前提到的 List,還可以生成 Set、Map 等,如下:

// 獲取籃球隊(duì)中學(xué)生年齡的分布,不允許重復(fù)
Set<Integer> ageSet = basketballStudents.stream()
    .map(Student::getAge)
    .collect(Collectors.toSet());
System.out.println("set: " + ageSet);

// 獲取籃球隊(duì)中所有學(xué)生的姓名和年齡的 Map
Map<String, Integer> nameAndAgeMap = basketballStudents.stream()
    .collect(Collectors.toMap(Student::getName, Student::getAge));
System.out.println("map: " + nameAndAgeMap);

輸出:

set: [17, 18, 19]
map: {Ted=17, Tom=19, Bob=18, Zeka=19}

(2) 生成字符串

除了生成集合,collect還可以用于拼接字符串。

例如,我們獲取籃球隊(duì)中所有學(xué)生的姓名后,希望用“,”將所有姓名拼接成一個(gè)字符串并返回。

System.out.println(basketballStudents.stream()
    .map(Student::getName)
    .collect(Collectors.joining(",")));

輸出:

Bob,Ted,Zeka,Tom

也許你會(huì)說(shuō),用String.join()不也能實(shí)現(xiàn)這個(gè)功能嗎?確實(shí),如果只是單純的字符串拼接,確實(shí)沒(méi)有必要使用Stream來(lái)實(shí)現(xiàn)。畢竟,殺雞焉用牛刀!

此外,Collectors.joining()還支持定義前綴和后綴,功能更強(qiáng)大。

System.out.println(basketballStudents.stream()
    .map(Student::getName)
    .collect(Collectors.joining(",", "(", ")")));

輸出:

(Bob,Ted,Zeka)

(3) 生成統(tǒng)計(jì)結(jié)果

還有一個(gè)在實(shí)際中可能很少用到的場(chǎng)景,就是使用collect生成數(shù)字?jǐn)?shù)據(jù)的統(tǒng)計(jì)結(jié)果。我們簡(jiǎn)單看一下。

// 計(jì)算平均年齡
System.out.println("平均年齡:" + basketballStudents.stream()
    .map(Student::getAge)
    .collect(Collectors.averagingInt(a -> a)));

// 統(tǒng)計(jì)匯總
IntSummaryStatistics summary = basketballStudents.stream()
    .map(Student::getAge)
    .collect(Collectors.summarizingInt(a -> a));
System.out.println("summary: " + summary);

在上面的例子中,使用collect對(duì)年齡進(jìn)行了一些數(shù)學(xué)運(yùn)算,結(jié)果如下:

平均年齡:18.0
summary: IntSummaryStatistics{count=3, sum=54, min=17, average=18.000000, max=19}

四、并行 Stream

使用并行流可以有效利用計(jì)算機(jī)性能,提高執(zhí)行速度。并行 Stream 將整個(gè)流分成多個(gè)片段,然后并行處理每個(gè)片段的流,最后將每個(gè)片段的執(zhí)行結(jié)果匯總成一個(gè)完整的 Stream。

如下圖所示,篩選出大于等于 18 的數(shù)字:

將原始任務(wù)拆分為多個(gè)任務(wù)。

[7, 18, 18]

每個(gè)任務(wù)并行執(zhí)行操作。

stream.filter(a -> a >= 18)

單個(gè)任務(wù)處理并匯總為單個(gè)結(jié)果。

[18, 18]

高效使用 findAny()

如上所述,findAny()在并行 Stream 中更高效,從 API 文檔中可以看出,每次執(zhí)行該方法的結(jié)果可能不同。

使用parallelStream執(zhí)行findAny()10 次,以找出任何滿足條件(名字是 Bob、Tom 或 Zeka)的學(xué)生名字。

for (int i = 0; i < 10; i++) {
    basketballStudents.parallelStream()
        .filter(s -> s.getAge() >= 18)
        .findAny()
        .map(Student::getName)
        .ifPresent(name -> System.out.println("并行流中的 findAny: " + name));
}

輸出:

并行流中的findAny: Zeka
并行流中的findAny: Zeka
并行流中的findAny: Tom
并行流中的findAny: Zeka
并行流中的findAny: Zeka
并行流中的findAny: Bob
并行流中的findAny: Zeka
并行流中的findAny: Zeka
并行流中的findAny: Zeka

這個(gè)輸出證實(shí)了findAny()的不穩(wěn)定性。

關(guān)于并行流的更多知識(shí),我將在后續(xù)文章中進(jìn)一步分析和討論。

五、注意事項(xiàng)

1. 延遲執(zhí)行

Stream 是惰性的;只有在啟動(dòng)終止操作時(shí)才會(huì)對(duì)源數(shù)據(jù)執(zhí)行計(jì)算,并且只在需要時(shí)才會(huì)消耗源元素。前面提到的peek方法就是一個(gè)很好的例子。

2. 避免執(zhí)行兩次終止操作

一旦 Stream 被終止,就不能再用于執(zhí)行其他操作,否則會(huì)報(bào)錯(cuò)??聪旅娴睦樱?/p>

Stream<Student> stream = students.stream();
stream.filter(s -> s.getAge() >= 18).count();
stream.filter(s -> s.getAge() >= 18).forEach(System.out::println); // 這里會(huì)報(bào)錯(cuò)

輸出:

java.lang.IllegalStateException: stream has already been operated upon or closed

因?yàn)橐坏?Stream 被終止,就不能再重復(fù)使用。

責(zé)任編輯:趙寧寧 來(lái)源: 程序猿技術(shù)充電站
相關(guān)推薦

2020-10-09 08:15:11

JsBridge

2018-09-26 16:04:04

NVMe主機(jī)控制器

2017-09-05 08:52:37

Git程序員命令

2022-02-21 09:44:45

Git開(kāi)源分布式

2023-05-12 08:19:12

Netty程序框架

2021-06-30 00:20:12

Hangfire.NET平臺(tái)

2019-04-17 15:16:00

Sparkshuffle算法

2024-06-25 08:18:55

2021-04-09 08:40:51

網(wǎng)絡(luò)保險(xiǎn)網(wǎng)絡(luò)安全網(wǎng)絡(luò)風(fēng)險(xiǎn)

2023-07-30 15:18:54

JavaScript屬性

2023-05-08 08:21:15

JavaNIO編程

2021-01-26 23:46:32

JavaScript數(shù)據(jù)結(jié)構(gòu)前端

2021-03-09 14:04:01

JavaScriptCookie數(shù)據(jù)

2021-06-24 09:05:08

JavaScript日期前端

2021-09-27 09:18:30

ListIterato接口方法

2023-09-06 14:57:46

JavaScript編程語(yǔ)言

2024-01-30 13:47:45

2024-04-19 14:23:52

SwitchJavaScript開(kāi)發(fā)

2020-12-08 08:09:49

SVG圖標(biāo)Web

2022-08-04 09:39:39

Kubernetes聲明式系統(tǒng)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)