JDK8新特性之Stream API(三)

本文详细介绍了Java8的Stream API,包括Stream API的概念、与Collection的区别、操作的三个步骤以及特点。通过实例展示了创建Stream、中间操作(如筛选、映射、排序)和终止操作(如匹配、查找、规约、收集)。最后强调了理解和熟练使用Stream API的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

JDK8新特性之Stream API(三)

JAVA8中有两个最为重要的改变第一个是Lambda表达式,这个在JDK8新特性之Lambda表达式(一)JDK8新特性之方法引用(二)这两篇文章中介绍过,另一个则是Stream API。

Stream API 介绍

Stream API(java.util.stream)是Java8中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射等操作,简而言之,Stream API提供了一种高效且易于使用的处理数据的方式。

Stream和Collection集合的区别

Collection是一种静态的内存数据结构简单说就是一个存放数据的容器,而Stream是有关计算的。前者主要是面向内存的,存储在内存中,后者主要是面向CPU,通过CPU实现计算。

Stream操作的三个步骤

Steam操作流程

  1. 创建Stream:一个数据源(如集合、数组),获取一个流
  2. 中间操作:一个中间操作链,对数据源的数据进行处理
  3. 终止操作:一旦执行终止操作,就执行中间操作链,并产生结果,之后不再被使用(意思就是之前所创建的stream已经无法使用了,要想继续操作必须重新执行三个步骤)
Stream 特点
  1. Stream自己不会存储元素(它不是一个容器)
  2. 不会改变源对象,相反他们会返回一个持有结果的新Stream(比如上图的操作1完成后会返回一个持有结果的新的stream)
  3. Stream操作是延迟执行的,这意味着它们会等到需要结果的时候才执行(只有在做终止操作的时候才会执行中间操作,换句话说如果不执行终止操作的话中间一些列操作是不会执行的)
Stream Api的使用

上面说了这么多都是概念类的东西,下面我们通过代码来解释上面的概念

步骤一:创建Stream

首先根据上面所说的Stream操作的三个步骤,第一个步骤自然就是创建Stream了,创建Stream主要有三种方式,通过集合创建、通过数组创建、通过Stream的of创建,由于这里仅仅只是创建Stream所以大家看不到效果,具体效果要等到终止操作那个步骤才能体现。
通过集合创建Stream

 @Test
    public void test01(){
        List<String> list = new ArrayList<>();
        list.add("张三");
        list.add("李四");
        list.add("王五");
        // 返回一个顺序流,顺序流就会安装集合的顺序进行操作
        Stream<String> stream = list.stream();
        // 返回一个并行流,并行流的意思就好比是多个同时操作,并不会保证顺序
        Stream<String> stringStream = list.parallelStream();
    }

通过数组创建Stream

 @Test
    public void test02(){
        Integer[] arr = new Integer[]{3,5,23,55,12};
        Stream<Integer> stream = Arrays.stream(arr);
    }

通过Stream的of

 @Test
    public void test03(){
        Stream<Integer> stream = Stream.of(34,12,23,2,4);
    }
Stream的中间操作

下面介绍stream中间操作的一些方法,首先我们创建一些数据集来方便我们完成中间操作,先创建一个User类,并重写toString、hashCode、equals方法

public class User {
    private String username;
    private int age;

    public User(String username, int age) {
        this.username = username;
        this.age = age;
    }

    public String getUsername() {
        return username;
    }

    public int getAge() {
        return age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return age == user.age &&
                Objects.equals(username, user.username);
    }

    @Override
    public int hashCode() {
        return Objects.hash(username, age);
    }

    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                ", age=" + age +
                '}';
    }
}

存放user数据的list

public class UserInfos {
    public static List<User> userList = new ArrayList<>();

    static {
        userList.add(new User("詹姆斯",36));
        userList.add(new User("韦德",28));
        userList.add(new User("科比",24));
        userList.add(new User("安东尼",34));
        userList.add(new User("保罗",31));
    }

}
筛选与切片

filter(Predicate p):接收Lambda,从流中排除某些元素
distinct():筛选,通过流所生成元素的hashCode()和equals()去除重复元素
limit(long maxSize):截断流,使其元素不超过给定数量
skip(long n):跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流。与limit(n)互补

因为要执行终止操作才会执行中间操作链,所以下面例子加上了终止操作.foreach(),这个是遍历操作,关于终止操作后面会介绍
filter(Predicate p):接收Lambda,从流中过滤某些元素

    /**
     * 过滤掉年龄小于30岁的人
     */
    @Test
    public void test1() {
        List<User> userList = UserInfos.userList;
        System.out.println(Arrays.toString(userList.toArray()));
        System.out.println("=========================================");
        // 1.获取stream流
        Stream<User> stream = userList.stream();
        // 2.中间操作,每次中间操作都返回一个新的stream
        Stream<User> userStream = stream.filter(user -> user.getAge() > 30);
        // 3.终止操作
        userStream.forEach(user -> System.out.print(user+","));
    }

结果:

[User{username='詹姆斯', age=36}, User{username='韦德', age=28}, User{username='科比', age=24}, User{username='安东尼', age=34}, User{username='保罗', age=31}, User{username='韦德', age=28}]
=========================================
User{username='詹姆斯', age=36},User{username='安东尼', age=34},User{username='保罗', age=31},

limit(n):截断流,使其元素不超过给定数量

/**
     * 只获取前2个元素
     */
    @Test
    public void test2()
    {
        List<User> userList = UserInfos.userList;
        System.out.println(Arrays.toString(userList.toArray()));
        // 1.获取stream流
        Stream<User> stream = userList.stream();
        // 2.中间操作,每次中间操作都返回一个新的stream:limit,限制元素个数
        Stream<User> userStream = stream.limit(2);
        // 3.终止操作
        userStream.forEach(user -> System.out.print(user+","));
    }

结果:

[User{username='詹姆斯', age=36}, User{username='韦德', age=28}, User{username='科比', age=24}, User{username='安东尼', age=34}, User{username='保罗', age=31}, User{username='韦德', age=28}]
=========================================
User{username='詹姆斯', age=36},User{username='韦德', age=28},

skip(n):跳过元素,这个和limit(n)正好相反,返回一个扔掉了前n个元素的流,若流中元素不足n个,则返回一个空流

 /**
     * 去掉前4个元素
     */
    @Test
    public void test3()
    {
        List<User> userList = UserInfos.userList;
        System.out.println(Arrays.toString(userList.toArray()));
        System.out.println("=========================================");
        // 1.获取stream流
        Stream<User> stream = userList.stream();
        // 2.中间操作,每次中间操作都返回一个新的stream:skip,跳过指定元素个数
        Stream<User> userStream = stream.skip(4);
        // 3.终止操作
        userStream.forEach(user -> System.out.print(user+","));
    }

结果:

[User{username='詹姆斯', age=36}, User{username='韦德', age=28}, User{username='科比', age=24}, User{username='安东尼', age=34}, User{username='保罗', age=31}, User{username='韦德', age=28}]
=========================================
User{username='保罗', age=31},User{username='韦德', age=28},

distinct():筛选,通过流所生成元素的hashCode()和equals()去除重复元素

    /**
     * 去掉重复元素
     */
    @Test
    public void test4()
    {
        List<User> userList = UserInfos.userList;
        System.out.println(Arrays.toString(userList.toArray()));
        System.out.println("=========================================");
        // 1.获取stream流
        Stream<User> stream = userList.stream();
        // 2.中间操作,每次中间操作都返回一个新的stream:distinct(),去掉重复元素
        Stream<User> userStream = stream.distinct();
        // 3.终止操作
        userStream.forEach(user -> System.out.print(user+","));
    }

结果:

[User{username='詹姆斯', age=36}, User{username='韦德', age=28}, User{username='科比', age=24}, User{username='安东尼', age=34}, User{username='保罗', age=31}, User{username='韦德', age=28}]
=========================================
User{username='詹姆斯', age=36},User{username='韦德', age=28},User{username='科比', age=24},User{username='安东尼', age=34},User{username='保罗', age=31},
Stream中间操作——映射

关于映射操作这里只讲两个常用的方法,map(Function f)和flatMap(Function f),同样为了看到效果我们会在下面代码展示的时候加上终止操作.foreach()

map(Function f):接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素

/**
     * 将集合中的字符转成大写字符
     */
    @Test
    public void test5(){
        String[] arr = {"wade","james","kobe","paul","anthony"};
        System.out.println(Arrays.toString(arr));
        List<String> strList = Arrays.asList(arr);
        //1.获取stream
        Stream<String> stream = strList.stream();
        //2.中间操作,map映射:将元素转成大写
        Stream<String> stringStream = stream.map(str -> str.toUpperCase());
        // 终止操作
        stringStream.forEach(str -> System.out.print(str+" "));
    }

结果:

[wade, james, kobe, paul, anthony]
WADE JAMES KOBE PAUL ANTHONY 

在上面的基础上我们再多加一个过滤的中间操作

 /**
     * 将集合中的字符转成大写字符
     */
    @Test
    public void test6(){
        String[] arr = {"wade","james","kobe","paul","anthony"};
        System.out.println(Arrays.toString(arr));
        List<String> strList = Arrays.asList(arr);
        //1.获取stream
        Stream<String> stream = strList.stream();
        //2.中间操作,map映射:将元素转成大写
        Stream<String> stringStream = stream.map(str -> str.toUpperCase());
        // 中间操作,filter过滤,去掉名字是paul的元素
        Stream<String> newStream = stringStream.filter(str -> !str.equalsIgnoreCase("paul"));
        // 终止操作
        newStream.forEach(str -> System.out.print(str+" "));
    }

简化版本

/**
     * 将集合中的字符转成大写字符
     */
    @Test
    public void test7(){
        String[] arr = {"wade","james","kobe","paul","anthony"};
        System.out.println(Arrays.toString(arr));
        List<String> strList = Arrays.asList(arr);
       strList.stream()
               .map(str -> str.toUpperCase())
               .filter(str -> !str.equalsIgnoreCase("paul"))
               .forEach(str -> System.out.print(str+" "));
    }

结果:

[wade, james, kobe, paul, anthony]
WADE JAMES KOBE ANTHONY 

flatMap(Function f):接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
关于flatMap其实还是挺不好理解的,这里其实讲的也不好,大家就看一下大概的区别吧,具体区别是什么可以去搜一搜,因为我也不是特别懂

/**
     * 将字符串list中的所有字符串转成一个个字符,比如:
     * ["hello","world"] -> ['h','e','l','l','o','w','o','r','l','d']
     */
    @Test
    public void test8() {
        // 创建一个字符串数组
        String[] strArr = new String[]{"curry", "durant", "allen", "bosh"};
        List<String> strList = new ArrayList<>(Arrays.asList(strArr));
        System.out.println("=================使用中间操作:map()完成=============");
        // 1. 获取stream
        Stream<String> stream1 = strList.stream();
        // 2. 中间操作map
        Stream<Stream<Character>> streamStream = stream1.map(str -> toCharStream(str));
        // 3. 由于上一步操作完返回的是Stream<Stream<Character>>并不是Stream<Character>,因此我们遍历的时候需要两次foreach,嵌套
        streamStream.forEach(characterStream -> {
            characterStream.forEach(character -> System.out.println(character));
        });
        System.out.println("=================使用中间操作:flatMap()完成=============");
        // 1.获取stream
        Stream<String> stream2 = strList.stream();
        // 2. 中间操作flatMap,注意这里和上面map操作时返回的stream是不一样的,这也是flatMap的作用
        Stream<Character> characterStream = stream2.flatMap(str -> toCharStream(str));
        // 返回的是 Stream<Character>可以直接进行遍历
        characterStream.forEach(character -> System.out.println(character));
    }

    /**
     * 将字符串中的多个字符构成的集合转成对应的stream实例
     *
     * @param str
     * @return
     */
    public Stream<Character> toCharStream(String str) {
        List<Character> characterList = new ArrayList<>();
        char[] chars = str.toCharArray();
        for (char c : chars) {
            characterList.add(c);
        }

        return characterList.stream();
    }

结果:

=================使用中间操作:map()完成=============
c
u
r
r
y
d
u
r
a
n
t
a
l
l
e
n
b
o
s
h
=================使用中间操作:flatMap()完成=============
c
u
r
r
y
d
u
r
a
n
t
a
l
l
e
n
b
o
s
h

Stream中间操作——排序

关于排序这里也只有两个方法分别是:
sorted():产生一个新流,按自然方式排序
sorted(comparator com) :产生一个新流,按照比较器方式排序
同样为了看到效果我们会在下面代码展示的时候加上终止操作.foreach()
sorted():产生一个新流,按自然方式排序

 @Test
    public void test9()
    {
        List<Integer> list = new ArrayList<>(Arrays.asList(3,12,7,9,22,6,1));
        // 简写方式
        list.stream().sorted().forEach(num -> System.out.print(num+" "));
    }

结果:

1 3 6 7 9 12 22 

sorted(comparator com) :产生一个新流,按照比较器方式排序

 @Test
    public void test10() {
        // User和UserInfos在最开始的时候新建的数据
        List<User> userList = UserInfos.userList;
        System.out.println(Arrays.toString(userList.toArray()));
        System.out.println("=========================================");
        // 通过比较器的方式根据年龄给user排序
        userList.stream()
                .sorted((user1, user2) -> {
                    if (user1.getAge() > user2.getAge()) {
                        return 1;
                    } else if (user1.getAge() < user2.getAge()) {
                        return -1;
                    } else {
                        return 0;
                    }
                }).forEach(num -> System.out.print(num + " "));
    }

结果:

1 3 6 7 9 12 22 
Stream的终止操作

关于终止操作,只有执行了终止操作才会进行进行一系列的中间操作也就是说其实中间操作是延迟的,其次就是在终止操作完成后stream流将不能再次使用。其实在上面的stream中间操作时为了看到效果我们都用了终止操作,只不过我们用的都是forEach(),下面将介绍Stream的一些终止操作。

Stream的终止操作——匹配与查找

allMatch(Predicate p):检查是否匹配所有元素
anyMatch(Predicate p):检查是否匹配至少一个元素
noneMatch(Predicate p):检查是否没有匹配所有元素
findFirst():返回第一个元素
findAny():返回当前元素的任意元素
count():返回流中元素的总个数
max(Comparator c):返回流中最大值
min(Comparator c):返回流中最小值
forEach(consumer c):内部迭代

前面用了很多次的forEach,我们先来看看它的用法,以及说明一下在终止操作完成后stream流将不能再次使用是啥意思

/**
     * forEach(consumer c):内部迭代
     */
    @Test
    public void test11() {
        List<String> strList = new ArrayList<>(Arrays.asList("curry", "durant", "allen", "bosh"));
        System.out.println("遍历集合所有元素:");
        Stream<String> stream = strList.stream();
        stream.forEach(str -> System.out.println(str));
        // 由于forEach是终止操作,这时候stream已经不能再次使用了,下面这个代码执行是会报错:java.lang.IllegalStateException: stream has already been operated upon or closed
//        stream.forEach(str -> System.out.println(str));
    }

结果:

遍历集合所有元素:
curry
durant
allen
bosh
	/**
     * allMatch(Predicate p):检查是否匹配所有元素
     * anyMatch(Predicate p):检查是否匹配至少一个元素
     * noneMatch(Predicate p):检查是否没有匹配所有元素
     * findFirst():返回第一个元素
     * findAny():返回当前元素的任意元素
     * count():返回流中元素的总个数
     * max(Comparator c):返回流中最大值
     * min(Comparator c):返回流中最小值
     * forEach(consumer c):内部迭代
     */
    @Test
    public void test11() {
        List<String> strList = new ArrayList<>(Arrays.asList("curry", "durant", "allen", "bosh"));
        boolean flag1 = strList.stream().allMatch(str -> str.contains("a"));
        System.out.println("所有元素是否都含有字母a:" + flag1);

        boolean flag2 = strList.stream().anyMatch(str -> str.contains("a"));
        System.out.println("是否至少一个元素含有字母a:" + flag2);

        boolean flag3 = strList.stream().noneMatch(str -> str.contains("hh"));
        System.out.println("是否所有元素都不包含hh的元素:" + flag3);

        // 这里Optional不做介绍,大家可以自行百度
        Optional<String> first = strList.stream().findFirst();
        String element = first.get();
        System.out.println("集合中的第一个元素是:" + element);

        // 这里用parallelStream能体现效果
        Optional<String> any = strList.parallelStream().findAny();
        String el = any.get();
        System.out.println("集合中的任意一个元素是:" + el);

        long count = strList.stream().count();
        System.out.println("集合中的总个数是:" + count);


        List<Integer> intList = new ArrayList<>(Arrays.asList(3, 12, 7, 9, 22, 6, 1));
        // 1.匿名函数方式
        Optional<Integer> max = intList.stream().max(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return Integer.compare(o1, o2);
            }
        });
        // 2.lambda表达式方式
//        intList.stream().max((num1,num2)->{
//            return Integer.compare(num1,num2);
//        });
        System.out.println("集合中的最大值是:" + max.get());

        // 1.匿名函数方式
        Optional<Integer> min = intList.stream().min(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return Integer.compare(o1, o2);
            }
        });
        // 2.lambda表达式方式
//        intList.stream().max((num1,num2)->{
//            return Integer.compare(num1,num2);
//        });
        System.out.println("集合中的最小值是:" + min.get());
    }

结果:

所有元素是否都含有字母a:false
是否至少一个元素含有字母a:true
是否所有元素都不包含hh的元素:true
集合中的第一个元素是:curry
集合中的任意一个元素是:allen
集合中的总个数是:4
集合中的最大值是:22
集合中的最小值是:1
Stream的终止操作——规约

Optional reduce(BinaryOperator accumulator):可以将流中元素反复结合起来,得到一个值。返回Optional

@Test
    public void test12(){
        List<Integer> list = Arrays.asList(1,2,3,4,5);
        /*
            给定初始值和集合中的元素进行求和:
            reduce操作其实内部进行的是:10+num1,(10+num1)+num2,(10+num1+num2)+num3....
         */
        Integer result = list.stream().reduce(10, (num1, num2) -> {
            return Integer.sum(num1, num2);
        });
        System.out.println(result);
    }

结果:25

T reduce(T identity, BinaryOperator accumulator):可以将流中元素反复结合起来,得到一个值。返回T

@Test
    public void test13(){
        List<Integer> list = Arrays.asList(1,2,3,4,5);
        /*
            给定初始值和集合中的元素进行求和,和上一个例子不同的是它不需要传递一个初始值,而且返回的是个Optional
         */
        Optional<Integer> result = list.stream().reduce((num1, num2) -> {
            return Integer.sum(num1, num2);
        });
        System.out.println(result.get());
    

结果:15

Stream的终止操作——收集

collect(Collector c):将流转换成为其他形式。接收一个Collector接口的实现,用于给Stream中元素做汇总方法。
Collector接口中方法的实现决定了如何对流执行收集的操作(如收集到List、Set、Map),另外Collectors实用类提供了很多静态方法,可以方便地创建常见收集器时期,我们下面以常用的Collectors.toList和Collectors.toSet举例,其他的可以自行看API

 @Test
    public void test14() {
        List<String> list = Arrays.asList("a", "ac", "bc", "de", "ea", "a");
        System.out.println("原集合:" + list);
        //1.获取一个只包含字母a的字符串的List集合
        List<String> aLIst = list.stream().filter(str -> str.contains("a")).collect(Collectors.toList());
        System.out.println("只包含字母a字符串的新List集合:" + aLIst);

        //2.获取一个值包含字母a的字符串的Set集合
        Set<String> aSet = list.stream().filter(str -> str.contains("a")).collect(Collectors.toSet());
        System.out.println("只包含字母a字符串的新Set集合:" + aSet);


    }

结果:

原集合:[a, ac, bc, de, ea, a]
只包含字母a字符串的新List集合:[a, ac, ea, a]
只包含字母a字符串的新Set集合:[a, ac, ea]
总结

在使用Stream API的时候要注意先要了解Lambda表达式,如果对Lambda表达式还看的不是太懂了话可能对于Stream API的使用有一定困难,Stream API只是一个API,刚开始看的时候可能很不习惯甚至别扭,只要多练、多用、多看,看得多了练得多了用起来自然也就熟练了。

<think>好的,我现在需要帮助用户解决关于Dify循环实现的问题。首先,我需要明确用户的具体需求。他们提到“dify 循环实现”或“dify 如何处理循环”,但Dify本身可能没有直接称为“循环”的功能,所以需要结合现有文档中的信息来推断可能的解决方案。 根据用户提供的引用内容,Dify工作流中有迭代节点[^1]。这可能类似于编程中的循环结构,用于重复执行某个任务。例如,在处理列表数据或需要多次执行同一操作时,迭代节点可以派上用场。我需要详细解释迭代节点的配置和使用步骤,确保用户能够理解如何设置循环次数或遍历数据集。 另外,用户可能对如何处理循环中的错误或性能问题感兴趣。引用[2]提到在Dify中处理PEM文件的问题,这可能涉及在循环中正确处理数据格式,避免错误积累。需要提醒用户在循环中注意错误处理和资源管理,确保每次迭代的稳定性。 引用[3]中的例子展示了如何结合FastAPI和Selenium进行实时监控并生成GIF,这可能涉及循环截取浏览器内容。虽然Dify本身可能不直接控制这类循环,但可以通过外部脚本或定时任务触发工作流,模拟循环执行。这部分需要说明如何将外部循环机制与Dify结合使用。 接下来,我需要组织回答结构。首先介绍迭代节点的基本概念和配置步骤,然后讨论循环中的数据处理和错误处理,最后补充外部触发循环的方法。同时,生成相关问题以帮助用户进一步探索,比如优化循环性能或处理复杂数据结构。 需要确保回答符合格式要求,使用正确的中文表达,行内公式用$...$,独立公式用$$...$$,但当前问题不涉及数学公式,所以可能不需要使用。同时,引用标识要自然地添加到相关段落末尾,如、[^2]、[^3]。 最后,检查是否有遗漏点,比如Dify的其他循环相关功能或用户可能遇到的常见问题,确保回答全面且易于理解。</think>在Dify中处理循环逻辑主要通过**迭代节点**实现,以下是具体实现方式和应用场景的解析: ### 一、Dify循环实现机制 Dify通过**工作流设计器**中的迭代节点处理循环需求,其核心原理类似编程中的`for循环`。迭代节点可遍历以下数据类型: - 数组列表:`["A","B","C"]` - 字典集合:`{"key1":"value1", "key2":"value2"}` - 数值范围:通过`range()`函数生成序列 配置示例: ```python # 模拟迭代节点的数据输入 input_data = { "dataset": [1,2,3,4,5], "process_logic": "item * 2" # 对每个元素执行乘以2的操作 } ``` ### 二、迭代节点的关键配置步骤 1. **数据源绑定**:将数组/字典类型变量连接到迭代节点的输入端口 2. **循环变量命名**:设定当前元素的变量名(默认为`item`) 3. **子流程设计**:在迭代节点内部构建需要重复执行的逻辑模块 4. **结果聚合**:通过`outputs`收集所有迭代结果,支持数组或对象格式 $$ \text{总耗时} = \sum_{i=1}^{n}(单次迭代时间_i) + 系统开销 $$ ### 三、循环中的特殊处理 1. **错误中断控制**: - 启用`continueOnError`参数可跳过失败迭代 - 通过`try-catch`模块包裹敏感操作 2. **并行优化**: ```python # 伪代码示例 Parallel.forEach(dataset, lambda item: process(item)) ``` 3. **结果过滤**: ```python filtered = filter(lambda x: x%2==0, processed_results) ``` ### 四、应用场景案例 1. **批量文件处理**:遍历存储桶中的文件列表进行格式转换 2. **数据清洗**:对数据库查询结果集进行逐条校验 3. **API轮询**:定时循环调用第三方接口直到满足特定条件
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值