CompletableFuture

前言

老早就想学一下CompletableFuture,但是一直耽搁了,趁最近有时间,先把用法整理下来,方便以后查阅。

CompletableFuture 实现了Future,CompletionStage接口,CompletableFuture提供了4个静态方法来创建一个异步操作。

public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
	...
    public static CompletableFuture<Void> runAsync(Runnable runnable) {
        return asyncRunStage(asyncPool, runnable);
    }

	public static CompletableFuture<Void> runAsync(Runnable runnable,
                                                   Executor executor) {
        return asyncRunStage(screenExecutor(executor), runnable);
    }
    
	public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier){...}
	public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor){...}
}

runAsync方法不支持返回值。supplyAsync可以支持返回值。

没有指定Executor的方法会使用ForkJoinPool.commonPool() 作为它的线程池执行异步代码。如果指定线程池,则使用指定的线程池运行。

 /**
     * Default executor -- ForkJoinPool.commonPool() unless it cannot
     * support parallelism.
     */
    private static final Executor asyncPool = useCommonPool ?
        ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();

创建一个异步操作:runAsync(无返回值)

//无返回值
    public static void main(String[] args) {
        CompletableFuture completableFuture = runAsync();
        System.out.println("现在来等待任务结束:" + new Date());
        try {
            completableFuture.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println("任务结束");
    }

    public static CompletableFuture runAsync() {
        return CompletableFuture.runAsync(() -> {
            System.out.println(new Date());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("睡了3秒,现在是:" + new Date());
        });
    }
    

控制台信息

Mon Feb 17 17:11:18 CST 2020
现在来等待任务结束:Mon Feb 17 17:11:18 CST 2020
睡了3秒,现在是:Mon Feb 17 17:11:21 CST 2020
任务结束

创建一个异步操作:supplyAsync(无返回值)

//有返回值
   public static void main(String[] args) {
        CompletableFuture completableFuture = supplyAsync();
        System.out.println("现在来等待任务结束:" + new Date());
        try {
            System.out.println("线程睡了:" + completableFuture.get() + "毫秒");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println("任务结束");
    }

    public static CompletableFuture<Integer> supplyAsync() {
        return CompletableFuture.supplyAsync(() -> {
            System.out.println(new Date());
            int i = 0;
            try {
                i = new Random().nextInt(1000) * 5;
                Thread.sleep(i);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("睡了" + i + "毫秒,现在是:" + new Date());
            return i;
        });
    }
Mon Feb 17 17:18:40 CST 2020
现在来等待任务结束:Mon Feb 17 17:18:40 CST 2020
睡了4905毫秒,现在是:Mon Feb 17 17:18:45 CST 2020
线程睡了:4905毫秒
任务结束

计算结果完成时的回调方法:whenComplete

当CompletableFuture的计算结果完成,或者抛出异常的时候,可以执行特定的Action。
whenComplete无论有没有异常都执行

//whenComplete:执行当前任务的线程执行继续执行 whenComplete 的任务。
public CompletableFuture<T> whenComplete(BiConsumer<? super T,? super Throwable> action)

// whenCompleteAsync:执行把 whenCompleteAsync 这个任务继续提交给线程池来进行执行。
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action, Executor executor)

// 如果过程中抛出异常,可以用这个来进行处理
public CompletableFuture<T> exceptionally(Function<Throwable,? extends T> fn)

通过上面的Action的类型 BiConsumer<? super T,? super Throwable>可知它可以处理正常的计算结果,或者异常情况。
功能:当CompletableFuture的计算结果完成,或者抛出异常的时候,都可以进入whenComplete方法执行

CompletableFuture<String> futureA = CompletableFuture.
                supplyAsync(() -> "执行结果:" + (100 / 0)) //抛出异常
                .thenApply(s -> "apply result:" + s) //未执行
                .whenComplete((s, e) -> {
                    if (s != null) {
                        System.out.println(s);//未执行
                    }
                    if (e == null) {
                        System.out.println(s);//未执行
                    } else {
                        System.out.println(e.getMessage());//java.lang.ArithmeticException: / by zero
                    }
                })
                .exceptionally(e -> {
                    System.out.println("ex"+e.getMessage()); //ex:java.lang.ArithmeticException: / by zero
             return "futureA result: 100"; }); 
System.out.println(futureA.join());//futureA result: 100

根据控制台,我们可以看出执行流程是这样,supplyAsync->whenComplete->exceptionally,可以看出并没有进入thenApply执行,原因也显而易见,在supplyAsync中出现了异常,thenApply只有当正常返回时才会去执行.而whenComplete不管是否正常执行,还要注意一点,whenComplete是没有返回值的.

我们再来看一个例子,如果我们先调用exceptionally,再调用whenComplete

CompletableFuture<String> futureA = CompletableFuture.
                supplyAsync(() -> "执行结果:" + (100 / 0))
                .thenApply(s -> "apply result:" + s)
                .exceptionally(e -> {
                    System.out.println("ex:"+e.getMessage()); //ex:java.lang.ArithmeticException: / by zero
                    return "futureA result: 100";
                })
                .whenComplete((s, e) -> {
                    if (e == null) {
                        System.out.println(s);//futureA result: 100
                    } else {
                        System.out.println(e.getMessage());//未执行
                    }
                })
                ;
System.out.println(futureA.join());//futureA result: 100

代码先执行了exceptionally后执行whenComplete,可以发现,由于在exceptionally中对异常进行了处理,并返回了默认值,whenComplete中接收到的结果是一个正常的结果,被exceptionally美化过的结果,这一点需要留意一下.

多个任务串行化:thenApply

thenApply:当前任务正常完成以后执行,当前任务的执行的结果会作为下一任务的输入参数,有返回值.
适用场景 : 多个任务串联执行,下一个任务的执行依赖上一个任务的结果,每个任务都有输入和输出

public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn, Executor executor)

Function<? super T,? extends U> T:上一个任务返回结果的类型,U:当前任务的返回值类型

CompletableFuture<String> futureA = CompletableFuture.supplyAsync(() -> "hello");

CompletableFuture<String> futureB = futureA.thenApply(s->s + " world");

CompletableFuture<String> future3 = futureB.thenApply(String::toUpperCase);

System.out.println(future3.get());

后一个任务依赖前一个任务的结果。
上面的代码,我们当然可以先调用future.join()先得到任务A的返回值,然后再拿返回值做入参去执行任务B,而thenApply的存在就在于帮我简化了这一步,我们不必因为等待一个计算完成而一直阻塞着调用线程,而是告诉CompletableFuture你啥时候执行完就啥时候进行下一步. 就把多个任务串联起来了.

执行任务完成时对结果的处理(有返回值):handle

handle 方法和 thenApply 方法处理方式基本一样。不同的是 handle 是在任务完成后再执行,还可以处理异常的任务。thenApply 只可以执行正常的任务,任务出现异常则不执行 thenApply 方法。

//T 传入参数类型,Throwable 异常, U 输出类型
public <U> CompletionStage<U> handle(BiFunction<? super T, Throwable, ? extends U> fn);
public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn);
public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn,Executor executor);

当CompletableFuture的计算结果完成,或者抛出异常的时候,可以通过handle方法对结果进行处理

 CompletableFuture<String> futureA = CompletableFuture.
                supplyAsync(() -> "执行结果:" + (100 / 0)) //抛出异常
                .thenApply(s -> "apply result:" + s) //不运行
                .exceptionally(e -> {
                    System.out.println("ex:" + e.getMessage()); //java.lang.ArithmeticException: / by zero
                    return "futureA result: 100"; //美化异常
                })
                .handle((s, e) -> { // e接收不到异常
                    if (e == null) {
                        System.out.println(s);//futureA result: 100
                    } else {
                        System.out.println(e.getMessage());//未执行
                    }
                    return "handle result:" + (s == null ? "500" : s);
                });
  System.out.println(futureA.join());//handle result:futureA result: 100

通过控制台,我们可以看出,最后打印的是handle result:futureA result: 100,执行exceptionally后对异常进行了"美化",返回了默认值,那么handle得到的就是一个正常的返回,
我们再试下,先调用handle再调用exceptionally的情况.

 CompletableFuture<String> futureA = CompletableFuture.
                supplyAsync(() -> "执行结果:" + (100 / 0))
                .thenApply(s -> "apply result:" + s) // 未执行
                .handle((s, e) -> { // s == null
                    if (e == null) { // e != null
                        System.out.println(s);//未执行
                    } else {
                        System.out.println(e.getMessage());//java.lang.ArithmeticException: / by zero
                    }
                    return "handle result:" + (s == null ? "500" : s);
                })
                .exceptionally(e -> {
                    System.out.println("ex:" + e.getMessage()); //未执行
                    return "futureA result: 100";
                });
System.out.println(futureA.join());//handle result:500

根据控制台输出,可以看到先执行handle,打印了异常信息,并对接过设置了默认值500,exceptionally并没有执行,因为它得到的是handle返回给它的值

handle和whenComplete的区别
  1. 都是对结果进行处理,handle有返回值,whenComplete没有返回值
  2. 由于1的存在,使得handle多了一个特性,可在handle里实现exceptionally的功能

接收任务的处理结果,进行消费处理(无返回值):thenAccept

接收任务的处理结果,并消费处理,无返回结果。如果上个任务出现错误,则不会执行 。

public CompletionStage<Void> thenAccept(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action,Executor executor);

当前任务正常完成以后执行,当前任务的执行结果可以作为下一任务的输入参数,无返回值.

    CompletableFuture<Void> future = CompletableFuture.supplyAsync(()-> new Random().nextInt(10);)
    			 .thenAccept(integer -> System.out.println(integer););//上一个任务的返回结果可以传到下面这个来
    future.get(); 
}

该方法只是消费执行完成的任务,并可以根据上面的任务返回的结果进行处理。并没有后续的输错操作。

上一个任务完成就执行:thenRun

thenRun不关心任务的处理结果。没有输入,也没有返回值,只要上面的任务执行完成,就开始执行 thenAccept 。

public CompletionStage<Void> thenRun(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action,Executor executor);
public static void thenRun() throws Exception{
    CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> new Random().nextInt(10);)
      .thenRun(() -> {
        System.out.println("thenRun ...");
    });
    future.get();
}

该方法同thenAccept方法类似。不同的是上个任务处理完成后,并不会把计算的结果传给thenRun方法。只是处理玩任务后,执行thenRun的后续操作。

thenCombine 合并任务

thenCombine 会把 两个 CompletionStage 的任务都执行完成后,把两个任务的结果一块交给 thenCombine 来处理。

public <U,V> CompletionStage<V> thenCombine(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn);
public <U,V> CompletionStage<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn);
public <U,V> CompletionStage<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn,Executor executor);

BiFunction<? super T,? super U,? extends V> fn
T:第一个任务的返回值
U:第二个任务的返回值
V:处理后的返回值

//第一个任务
 CompletableFuture<Double> futurePrice = CompletableFuture.supplyAsync(() -> 100d);
 //第二个任务
 CompletableFuture<Double> futureDiscount = CompletableFuture.supplyAsync(() -> 0.8);
 CompletableFuture<Double> futureResult = futurePrice.thenCombine(futureDiscount, (price, discount) -> price * discount); 
 System.out.println("最终价格为:" + futureResult.join()); //最终价格为:80.0

两个任务执行完之后的结果一起处理(无返回值):thenAcceptBoth

当两个CompletionStage都执行完成后,把结果一块交给thenAcceptBoth来进行消耗,无返回值

public <U> CompletionStage<Void> thenAcceptBoth(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action);
public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action);
public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action,     Executor executor);
		CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(() ->{
		    int t = new Random().nextInt(3);
		    try {
		         TimeUnit.SECONDS.sleep(t);
		    } catch (InterruptedException e) {
		         e.printStackTrace();
		    }
		         System.out.println("f1="+t);
		         return t;
		    }
		});
        
        CompletableFuture<Integer> f2 = CompletableFuture.supplyAsync(() -> {
            int t = new Random().nextInt(3);
            try {
                TimeUnit.SECONDS.sleep(t);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("f2="+t);
            return t;
        }
    });
    f1.thenAcceptBoth(f2, (t,u)-> {
            System.out.println("f1="+t+";f2="+u+";");
    });
}

执行返回的结果快的进行下一步处理:applyToEither

两个CompletionStage,谁执行返回的结果快,我就用那个CompletionStage的结果进行下一步的转化操作。有返回值

public <U> CompletionStage<U> applyToEither(CompletionStage<? extends T> other,Function<? super T, U> fn);
public <U> CompletionStage<U> applyToEitherAsync(CompletionStage<? extends T> other,Function<? super T, U> fn);
public <U> CompletionStage<U> applyToEitherAsync(CompletionStage<? extends T> other,Function<? super T, U> fn,Executor executor);
CompletableFuture<String> futureA = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "通过方式A获取商品a";
        });
CompletableFuture<String> futureB = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "通过方式B获取商品a";
        });
CompletableFuture<String> futureC = futureA.applyToEither(futureB, product -> "结果:" + product);
System.out.println(futureC.join()); //结果:通过方式A获取商品a

执行返回的结果快的进行下一步处理:acceptEither

两个CompletionStage,谁执行返回的结果快,我就用那个CompletionStage的结果进行下一步的消耗操作,无返回值.

acceptEither和applyToEither差不多,只是有没有返回值的区别而已

public CompletionStage<Void> acceptEither(CompletionStage<? extends T> other,Consumer<? super T> action);
public CompletionStage<Void> acceptEitherAsync(CompletionStage<? extends T> other,Consumer<? super T> action);
public CompletionStage<Void> acceptEitherAsync(CompletionStage<? extends T> other,Consumer<? super T> action,Executor executor);

2个任务执行完一个就进行下一步处理:runAfterEither

两个CompletionStage,任何一个完成了都会执行下一步的操作(Runnable)
两个任务都没有返回值,最后的处理结果也没有返回值

public CompletionStage<Void> runAfterEither(CompletionStage<?> other,Runnable action);
public CompletionStage<Void> runAfterEitherAsync(CompletionStage<?> other,Runnable action);
public CompletionStage<Void> runAfterEitherAsync(CompletionStage<?> other,Runnable action,Executor executor);
  f1.runAfterEither(f2, new Runnable() {
        @Override
        public void run() {
            System.out.println("上面有一个已经完成了。");
        }
    });

两个任务都完成,才会进行下一步:runAfterBoth

两个CompletionStage,都完成了计算才会执行下一步的操作(Runnable)

public CompletionStage<Void> runAfterBoth(CompletionStage<?> other,Runnable action);
public CompletionStage<Void> runAfterBothAsync(CompletionStage<?> other,Runnable action);
public CompletionStage<Void> runAfterBothAsync(CompletionStage<?> other,Runnable action,Executor executor);
 f1.runAfterBoth(f2, ()->System.out.println("上面两个任务都执行完成了。"););

流水线操作:thenCompose

thenCompose 方法允许你对两个 CompletionStage 进行流水线操作,第一个操作完成时,将其结果作为参数传递给第二个操作。

功能:这个方法接收的输入是当前的CompletableFuture的计算值,返回结果将是一个新的CompletableFuture

这个方法和thenApply非常像,**都是接受上一个任务的结果作为入参,执行自己的操作,然后返回.**区别如下:

  • thenApply():它的功能相当于将CompletableFuture<T>转换成CompletableFuture<U> ,改变的是同一个CompletableFuture中的泛型类型
  • thenCompose():用来连接两个CompletableFuture,返回值是一个新的CompletableFuture
public <U> CompletableFuture<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn);
public <U> CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn) ;
public <U> CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn, Executor executor) ;

private static void thenCompose() throws Exception {
		//第一个任务
        CompletableFuture<Integer> f = CompletableFuture.supplyAsync(new Supplier<Integer>() {
            @Override
            public Integer get() {
                int t = new Random().nextInt(3);
                System.out.println("t1="+t);
                return t;
            }
        })
        //第二个任务
		.thenCompose(new Function<Integer, CompletionStage<Integer>>() {
			//传入第一个任务的返回值
            @Override
            public CompletionStage<Integer> apply(Integer param) {
                 //返回一个新的CompletableFuture 
                return CompletableFuture.supplyAsync(new Supplier<Integer>() {
                    @Override
                    public Integer get() {
                        int t = param *2;
                        System.out.println("t2="+t);
                        return t;
                    }
                });
            }
            
        });
        System.out.println("thenCompose result : "+f.get());
    }



CompletableFuture<String> futureA = CompletableFuture.supplyAsync(() -> "hello");

//返回新的CompletableFuture
CompletableFuture<String> futureB = futureA.thenCompose(s -> CompletableFuture.supplyAsync(() -> s + "world"));

CompletableFuture<String> future3 = futureB.thenCompose(s -> CompletableFuture.supplyAsync(s::toUpperCase));

System.out.println(future3.join());

获取计算结果

// 阻塞到获取结果
public T get()
// 超过给定时间抛出TimeOut异常
public T get(long timeout, TimeUnit unit)
// 如果结果已经计算完则返回结果或者抛出异常,否则返回给定的valueIfAbsent值
public T getNow(T valueIfAbsent)
// 和get差不多
public T join()

join()和get()的区别:抛出异常的区别

  • join返回计算的结果或者抛出一个unchecked异常(CompletionException),
		CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            int i = 1/0;
            return 100;
        });
        future.join();
Exception in thread "main" java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
....
Caused by: java.lang.ArithmeticException: / by zero
	at LearnCompletableFuture.lambda$main$0(LearnCompletableFuture.java:31)
	at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1590)
	... 5 more
  • get返回计算的结果或者抛出一个checked异常(ExecutionException)
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            int i = 1/0;
            return 100;
        });
        try {
            future.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero
	at java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:357)
	at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1895)
	at LearnCompletableFuture.main(LearnCompletableFuture.java:35)
	....
Caused by: java.lang.ArithmeticException: / by zero
	at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)

allOf /anyOf

//.get() 执行完全部才继续往下执行
public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs)
//.get() 执行完任意一个 返回其执行完那一个结果
public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs)

anyOfapplyToEither区别:

  • anyOf:接受任意多的CompletableFuture,返回值的计算结果是参数中其中一个CompletableFuture的计算结果
  • applyToEither:只是判断两个CompletableFuture,返回值的计算结果要经过fn处理的。
allof
  • CompletableFuture.allOf 静态方法,传入多个独立的 future ,并且返回一个新的CompletableFuture.
  • 当所有的 future 完成时,新的 future 同时完成
  • 不能传入空值
  • 当某个方法出现了异常时,新 future 会在所有 future 完成的时候完成,并且包含一个异常
anyof
  • CompletableFuture.anyOf 静态方法,传入多个独立的 future ,并且返回一个新的CompletableFuture
  • 当任何一个方法完成时,新的 future 完成,并且返回该值
  • 不能传入空值
  • 当某个方法出现了异常时,新 future 会立刻完成,并且携带一个异常
Random rand = new Random();
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
    try {
        Thread.sleep(10000 + rand.nextInt(1000));
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return 100;
});
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
    try {
        Thread.sleep(10000 + rand.nextInt(1000));
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return "abc";
});

//f.get() 所有future运行完才继续往下运行
//CompletableFuture<Void> f =  CompletableFuture.allOf(future1,future2);

运行结果有时是100,有时是"abc"。
CompletableFuture<Object> f =  CompletableFuture.anyOf(future1,future2);
System.out.println(f.get());

完成对象(Future)

CompletableFuture有两个完成方法:

public boolean complete(T value);
public boolean completeExceptionally(Throwable ex);
//mayInterruptIfRunning设成false话,不允许在线程运行时中断,设成true的话就允许。
public boolean cancel(boolean mayInterruptIfRunning);

complete:将future状态置为已完成,并且将参数注入,但如果这个future已经完成了,则不会产生任何变化
completeExceptionally :将future状态置为已完成,并且将异常参数注入,并且在get的时候回获取这个异常,但如果这个future已经完成了,则不会产生任何变化

检验方法(Future)

public boolean isDone();
public boolean isCancelled();
public boolean isCompletedExceptionally();

isDone: 用来返回 future 对象是否已经完成
isCancelled:用来返回 future 对象是否已经被取消
isCompletedExceptionally: 用来返回 future 是否出现了异常

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值