Java学习笔记09

JAVA学习笔记09

1. jar包

archive:归档

jar archive:jar包的归档

jar包—java压缩包
1. 方便项目的携带;
2. 方便于使用,只要在classpath路径设置jar路径基础;
3. 数据库驱动,SSH框架等都以jar包体现;

在cmd下把类打包成Jar包的第一种方式(将当前目录下的所有类文件打包到jar包中):

在cmd下把类打包成Jar包的第二种方式(将某个目录中的所有文件打包到jar中):

查看清单(jar -tf xxx.jar )并运行jar包文件命令(jar -cp xxx.jar xx.xx.xx.Xxxx<—完整类名):

指定清单文件(xxx.jar/META-INF/MENIFEST.MF)
jar [ctxui] [vfmn0PMe] [jar-file] [manifest-file] [entry-point] [-C dir] files…

在jar中指定函数的入口点之后,在随后运行jar内的类的时候可以省略一步:

最终完整的步骤为:

相对路径
1. .\ 当前文件夹下的文件
2. ..\当前文件夹的上一级文件的文件
3. cd . 当前文件夹
4. cd ..当前文件夹的上一级文件夹

2. 线程

  1. 进程:运行着的应用程序,进程间不能共享内存,因此通讯比较麻烦,一般需要套接字(socket)编程或者第三方软件通讯。
  2. 线程:在同一个应用程序内部并发执行的代码段,可以共享内存。

Java–>Thread线程类<—-java.lang.Thread

写线程必须继承Thread类并重写其中的run方法
其实栈也是一个线程(运行时概念)由方法帧组成,程序中main函数所在的线程为主线程
所谓的多线程就是同时开启了多个栈,在启动主栈时,由主栈开启了若干个分线程

示例代码:

package Java1;

public class firstThread {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        MyThread t1 = new MyThread();//在堆区
        YourThread t2 = new YourThread()
        t1.start();//在开启线程--->在分线程执行run函数
        t2.start();

    }

}
class MyThread extends Thread{
    public void run() {//分线程执行的
        while(true) {
            System.out.println("MyThread()");
        }
    }
}
class YourThread extends Thread{
    public void run() {//分线程执行的
        while(true) {
            System.out.println("YourThread()");
        }
    }
}

效果:


上面子线程无限循环,主线程永远关闭不了。

注:只有在所有的子线程关闭之后,主线程才能关闭。

yield();//在执行完上一步动作之后,执行该代码的瞬时放弃CPU的使用权,但是执行完毕之后立刻开抢。

观察以下代码:

package Java1;

public class firstThread {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        MyThread t1 = new MyThread();//在堆区
        YourThread t2 = new YourThread();
        t1.run();//在开启线程--->在分线程执行run函数
        t2.start();
    }

}
class MyThread extends Thread{
    public void run() {
        while(true) {
            System.out.println("MyThread()");
        }
    }
}
class YourThread extends Thread{
    public void run() {
        while(true) {
            System.out.println("YourThread()");
        }
    }
}
运行结果:
MyThread()
MyThread()
MyThread()
MyThread()
MyThread()
MyThread()
MyThread()
MyThread()
MyThread()
MyThread()
MyThread()

**
这个只有一个主线程,t1没有开启,t1.run()只是一个主线程中简单的函数调用。因为t1.run()是一个无线循环的方法,所以t2.start()一直无法执行,即t2的线程无法开启,因此只有一个主线程。**

Join()方法和sleep()方法示例

package Java1;

public class firstThread {

    public static void main(String[] args) {
        Player p1 = new Player("张三", 5000);
        Player p2 = new Player("李四", 8000);
        Player p3 = new Player("王五", 2000);
        Player p4 = new Player("钱六", 3000);

        p1.start();
        p2.start();
        p3.start();
        p4.start();

        //为了使一个线程执行完之后下一个线程才能执行
        try {
            p1.join();//子线程执行时,主线程暂时放弃CPU使用权,
            //子线程执行完毕主线程才有可能获得CPU 使用权,如果不引入join方法,则主线程
            //在获得CPU使用权时可能会直接执行System.out.println("人都到齐了,开始玩吧!");
            p2.join();
            p3.join();
            p4.join();
        } catch (Exception e) {
            // TODO: handle exception
        }
        System.out.println("人都到齐了,开始玩吧!");
    }

}
class Player extends Thread{
    private String name;
    private int time;
    public Player(String name, int time) {
        this.name = name;
        this.time = time;
    }
    public void run() {
        System.out.println("玩家:"+name+"出发了!");
        try {
            Thread.sleep(time);
        } catch (Exception e) {
        }
        System.out.println("玩家花费"+time+"毫秒到了。");
    }
}
输出:
玩家:王五出发了!
玩家:钱六出发了!
玩家:李四出发了!
玩家:张三出发了!
玩家花费2000毫秒到了。
玩家花费3000毫秒到了。
玩家花费5000毫秒到了。
玩家花费8000毫秒到了。
人都到齐了,开始玩吧!

观察下面的程序和上面有什么不同

package Java1;

public class firstThread {

    public static void main(String[] args) {
        Player p1 = new Player("张三", 5000);
        Player p2 = new Player("李四", 8000);
        Player p3 = new Player("王五", 2000);
        Player p4 = new Player("钱六", 3000);

        try {
            p1.start();
            p1.join();//p1开启线程,p1线程执行完毕之后,下一步动作

            p2.start();
            p2.join();//p2开启线程,p2线程执行完毕之后,下一步动作

            p3.start();
            p3.join();//p3开启线程,p3线程执行完毕之后,下一步动作

            p4.start();
            p4.join();//p4开启线程,p4线程执行完毕之后,下一步动作
        } catch (Exception e) {
            // TODO: handle exception
        }
        System.out.println("人都到齐了,开始玩吧!");
    }

}
class Player extends Thread{
    private String name;
    private int time;
    public Player(String name, int time) {
        this.name = name;
        this.time = time;
    }
    public void run() {
        System.out.println("玩家:"+name+"出发了!");
        try {
            Thread.sleep(time);
        } catch (Exception e) {
        }
        System.out.println("玩家花费"+time+"毫秒到了。");
    }
}
输出:
玩家:张三出发了!
玩家花费5000毫秒到了。
玩家:李四出发了!
玩家花费8000毫秒到了。
玩家:王五出发了!
玩家花费2000毫秒到了。
玩家:钱六出发了!
玩家花费3000毫秒到了。
人都到齐了,开始玩吧!

就像将并行的线程串行起来运行一样,用多线程实现单线程了,很尴尬。

守护线程daemon的概念

如果一个线程是守护线程或者程序中剩余的线程都是守护线程,那就不再运行了,程序结束。
例子:你KTV唱歌的时候,服务员会每隔十分钟给你播报一下时间,但是唱歌到点要走了,再播报时间就没意义了,那播报时间就是守护线程,唱完的时候应该只剩下守护线程了,该线程应该结束。

考虑如下示例–没有守护线程:

package Java1;

public class firstThread {

    public static void main(String[] args) {

        KtvRoom k1 = new KtvRoom(1, 15000);
        KtvWaiter k2 = new KtvWaiter();
        k1.start();
        k2.start();
    }

}
class KtvRoom extends Thread{
    private int number;
    private int time;
    public KtvRoom(int number, int time) {
        this.number = number;
        this.time = time;
    }
    public void run() {
        System.out.println(number+"号KTV包厢正在唱歌!");
        try {//这种异常时人家方法里面规定的,不抛你编译都通不过。
            Thread.sleep(time);
        } catch (Exception e) {
            // TODO: handle exception
        }
        System.out.println(number+"号KTV包厢的人已经唱完,准备走人!");
    }
}
class KtvWaiter extends Thread{
    public void run() {
        while(true) {
            System.out.println(new java.util.Date());//获取程序当前时间
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                // TODO: handle exception
            }
        }
    }
}
运行结果:
1号KTV包厢正在唱歌!
Thu Apr 05 22:16:14 CST 2018
Thu Apr 05 22:16:15 CST 2018
Thu Apr 05 22:16:16 CST 2018
Thu Apr 05 22:16:17 CST 2018
Thu Apr 05 22:16:18 CST 2018
Thu Apr 05 22:16:19 CST 2018
Thu Apr 05 22:16:20 CST 2018
Thu Apr 05 22:16:21 CST 2018
Thu Apr 05 22:16:22 CST 2018
Thu Apr 05 22:16:23 CST 2018
Thu Apr 05 22:16:24 CST 2018
Thu Apr 05 22:16:25 CST 2018
Thu Apr 05 22:16:26 CST 2018
Thu Apr 05 22:16:27 CST 2018
Thu Apr 05 22:16:28 CST 2018
1号KTV包厢的人已经唱完,准备走人!
Thu Apr 05 22:16:29 CST 2018
Thu Apr 05 22:16:30 CST 2018
Thu Apr 05 22:16:31 CST 2018
Thu Apr 05 22:16:32 CST 2018
Thu Apr 05 22:16:33 CST 2018

唱完都走了还在播报显然是不合理的。

而加入守护线程之后

package Java1;

public class firstThread {

    public static void main(String[] args) {

        KtvRoom k1 = new KtvRoom(1, 15000);
        KtvWaiter k2 = new KtvWaiter();
        //设置服务员为守护线程
        k2.setDaemon(true);
        k1.start();
        k2.start();
    }

}
class KtvRoom extends Thread{
    private int number;
    private int time;
    public KtvRoom(int number, int time) {
        this.number = number;
        this.time = time;
    }
    public void run() {
        System.out.println(number+"号KTV包厢正在唱歌!");
        try {
            Thread.sleep(time);
        } catch (Exception e) {
            // TODO: handle exception
        }
        System.out.println(number+"号KTV包厢的人已经唱完,准备走人!");
    }
}
class KtvWaiter extends Thread{
    public void run() {
        while(true) {
            System.out.println(new java.util.Date());//获取程序当前时间
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                // TODO: handle exception
            }
        }
    }
}
运行结果:
1号KTV包厢正在唱歌!
Thu Apr 05 22:22:52 CST 2018
Thu Apr 05 22:22:53 CST 2018
Thu Apr 05 22:22:54 CST 2018
Thu Apr 05 22:22:55 CST 2018
Thu Apr 05 22:22:56 CST 2018
Thu Apr 05 22:22:57 CST 2018
Thu Apr 05 22:22:58 CST 2018
Thu Apr 05 22:22:59 CST 2018
Thu Apr 05 22:23:00 CST 2018
Thu Apr 05 22:23:01 CST 2018
Thu Apr 05 22:23:02 CST 2018
Thu Apr 05 22:23:03 CST 2018
Thu Apr 05 22:23:04 CST 2018
Thu Apr 05 22:23:05 CST 2018
Thu Apr 05 22:23:06 CST 2018
1号KTV包厢的人已经唱完,准备走人!

唱完歌守护线程就结束了!!!

守护线程守护的是所有的非守护线程。

当然也可以在类里面的构造函数直接定义,示例如下:

package Java1;

public class firstThread {

    public static void main(String[] args) {

        KtvRoom k1 = new KtvRoom(1, 15000);
        KtvWaiter k2 = new KtvWaiter();

        k1.start();
        k2.start();
    }

}
class KtvRoom extends Thread{
    private int number;
    private int time;
    public KtvRoom(int number, int time) {
        this.number = number;
        this.time = time;
    }
    public void run() {
        System.out.println(number+"号KTV包厢正在唱歌!");
        try {
            Thread.sleep(time);
        } catch (Exception e) {
            // TODO: handle exception
        }
        System.out.println(number+"号KTV包厢的人已经唱完,准备走人!");
    }
}
class KtvWaiter extends Thread{
    public KtvWaiter(){
        //设置服务员守护线程------------------------------->看这里!!!!!
        this.setDaemon(true);
    }
    public void run() {
        while(true) {
            System.out.println(new java.util.Date());//获取程序当前时间
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                // TODO: handle exception
            }
        }
    }
}

运行结果和上面一个样子的。

多线程在并发的时候使用

在多线程编程创建对象的时候堆是共享的(创建对象全在堆里),而栈是独占的。

3. 锁:一种独占的行为

任意一个对象都是锁。信号灯,参照物。

以两个人卖10张票为例:

示例1

package Java1;

public class firstThread {

    public static void main(String[] args) {

        Salor s1 = new Salor("张三");
        Salor s2 = new Salor("李四");

        s1.start();
        s2.start();
    }

}
class Salor extends Thread{
    //定义两个售票员一共要卖的票数
    static int tickets = 10;
    private String name;
    int currentTickets = 0;
    public Salor(String name) {
        this.name = name;
    }
    public void run() {
        while(tickets>0) {
            currentTickets=tickets;
            System.out.println(name+": "+tickets);
            tickets = tickets -1;
        }
    }
}
输出:
张三: 10
李四: 10
张三: 9
李四: 8
张三: 7
李四: 6
张三: 5
李四: 4
李四: 2
李四: 1
张三: 3

分析:两个10,两个线程同时跑,在run开始的时候很有可能两个线程同时到达while循环中的第一条语句,所以可能都输出10

示例2:

package Java1;
public class firstThread {

    public static void main(String[] args) {

        Salor s1 = new Salor("张三");
        Salor s2 = new Salor("李四");

        s1.start();
        s2.start();
    }

}
class Salor extends Thread{
    //定义锁可以用任意类名定义这个锁比如String object....因为多线程共享一个锁所以要用静态的
    static Object lock = new Object();
    //定义两个售票员一共要卖的票数
    static int tickets = 10;
    private String name;
    public Salor(String name) {
        this.name = name;
    }
    public void run() {
        int currentTickets =0 ;
        while(tickets > 0) {
            synchronized(lock) {
                currentTickets=tickets;
                tickets = tickets -1;
                System.out.println(name+":"+currentTickets);
            }
        }
    }
}
运行结果:
李四:7
李四:6
李四:5
李四:4
李四:3
李四:2
李四:1
张三:0

问题:出现了0,因为在李四卖最后一张票的时候张三已经进入while并在锁的前面等着了,而此时李四正在锁里面执行语句tickets=tickets-1=0,所以当李四的买票结束,张三的tickets为0,输出张三卖了第0张票

为了克服这个问题,尝试把while也放进锁里面,改进如下(示例3):

package Java1;

public class firstThread {

    public static void main(String[] args) {

        Salor s1 = new Salor("张三");
        Salor s2 = new Salor("李四");

        s1.start();
        s2.start();
    }

}
class Salor extends Thread{
    //定义锁可以用任意类名定义这个锁比如String object....因为多线程共享一个锁所以要用静态的
    static Object lock = new Object();
    //定义两个售票员一共要卖的票数
    static int tickets = 10;
    private String name;
    public Salor(String name) {
        this.name = name;
    }
    public void run() {
        int currentTickets =0 ;
        synchronized (lock) {
            while(tickets > 0) {
                currentTickets=tickets;
                tickets = tickets -1;
                System.out.println(name+":"+currentTickets);
        }
        }
    }
}
输出:
张三:10
张三:9
张三:8
张三:7
张三:6
张三:5
张三:4
张三:3
张三:2
张三:1

**问题:结果全是张三卖的
分析:因为while在锁里面,当s1线程运行run的时候,先上锁,然后直接在锁里面的while中一直循环,而李四的线程根本就进不去,所以李四的线程根本就没有执行,直到锁里面的张三把所有票都卖完了,李四才能进入锁里面,但是都没票了还卖个锤子。所以只有张三卖票。
**

继续改进:主旨思想–>只将关键动作执行同步代码块(示例4:

package Java1;

public class firstThread {

    public static void main(String[] args) {

        Salor s1 = new Salor("张三");
        Salor s2 = new Salor("李四");
        Salor s3 = new Salor("王五");

        s1.start();
        s2.start();
        s3.start();
    }

}
class Salor extends Thread{
    //定义锁可以用任意类名定义这个锁比如String object....因为多线程共享一个锁所以要用静态的
    static Object lock = new Object();
    //定义两个售票员一共要卖的票数
    static int tickets = 10;
    private String name;
    public Salor(String name) {
        this.name = name;
    }
    public void run() {
        while(true) {
            int tick = getTicket();
            if (tick > 0) {
                System.out.println(name+": "+tick);
            }else {
                return;
            }
        }
    }
    public int getTicket() {//同步代码块,同一时刻只有一个线程会运行
        synchronized (lock) {
            int curTickets = tickets;
            tickets = tickets - 1;
            return curTickets;
        }
    }
}
结果:
张三: 9
王五: 8
李四: 10
王五: 6
张三: 7
王五: 4
李四: 5
王五: 2
张三: 3
李四: 1

总结:锁为什么要定义成成静态的–>静态方法与对象无关,只与类有关。

如果我不想定义静态的锁呢—->索性连锁都不要了,直接上静态同步方法(示例5)

package Java1;

public class firstThread {

    public static void main(String[] args) {

        Salor s1 = new Salor("张三");
        Salor s2 = new Salor("李四");
        Salor s3 = new Salor("王五");

        s1.start();
        s2.start();
        s3.start();
    }

}
class Salor extends Thread{
    //定义两个售票员一共要卖的票数
    static int tickets = 10;
    private String name;
    public Salor(String name) {
        this.name = name;
    }
    public void run() {
        while(true) {
            int tick = getTicket();
            if (tick > 0) {
                System.out.println(name+": "+tick);
            }else {
                return;
            }
        }
    }
    public static synchronized int getTicket() {//同步代码块,同一时刻只有一个线程会运行
        int curTickets = tickets;
        tickets = tickets - 1;
        return curTickets;
    }
}
输出:
张三: 10
王五: 8
李四: 9
王五: 6
张三: 7
王五: 4
李四: 5
李四: 1
王五: 2
张三: 3

同步方法是以对象自身为锁(印证上面所说上面所有对象都是锁),然后通过将对象锁定义为静态锁,使多个对象锁合并为一个公用锁—即把类作为锁(类的实例化–对象,对象的抽象–类),然后运行多线程。

锁操作一般都是面向对象的。

进一步如果票池(ticket = 10)我都不想定义成静态的呢—可以直接定义成一个对象加以操作,即整个过程都是对象的操作(示例6)

package Java1;

import java.util.jar.Attributes.Name;

public class firstThread {

    public static void main(String[] args) {
        //这就是全部面向对象的同步方法
        TicketPool pool = new TicketPool();
        Salor s1 = new Salor("张三", pool);
        Salor s2 = new Salor("李四", pool);
        Salor s3 = new Salor("王五", pool);

        s1.start();
        s2.start();
        s3.start();
    }

}
class Salor extends Thread{
    private String name;
    TicketPool ticketPool;
    public Salor(String name, TicketPool pool) {
        this.name = name;
        this.ticketPool = pool;
    }
    public void run() {
        while(true) {
            int tick = ticketPool.getTicket();
            if (tick > 0) {
                System.out.println(name+": "+tick);
            }else {
                return;
            }
        }
    }
}
class TicketPool{
    //定义票池票的数目
    private int ticketNum = 10;
    public synchronized int getTicket() {//定义取票这个动作
        int ticket = ticketNum;
        ticketNum = ticketNum - 1;
        return ticket;
    }   
}
结果:
张三: 10
王五: 6
李四: 7
王五: 4
张三: 5
张三: 1
王五: 2
李四: 3

练习

练习1的代码–同步代码块(没有答案,自己参考网上写的)

package Java1;

public class guosandong {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        String carveName = "秦岭隧道"; 
        Car car1 = new Car(10, carveName, "奔驰");
        Car car2 = new Car(5, carveName, "现代");
        Car car3 = new Car(3, carveName, "沃尔沃");
        Car car4 = new Car(2, carveName, "五菱通用");

        car1.start();
        car2.start();
        car3.start();
        car4.start();
    }

}
class Car extends Thread{
    private int time;
    private String carveName;
    private String carName;
    private static Object lock = new Object();
    public Car(int time,String carveName, String carName) {
        this.carName = carName;
        this.time = time;
        this.carveName = carveName;
    }
    public void run() {
        synchronized(lock) {
            System.out.println(carName+"已经开始进入"+carveName+"。");
            try {
                Thread.sleep(time*1000);
            } catch (Exception e) {
                // TODO: handle exception
            }
            System.out.println(carName+"已经通过了"+carveName+"隧道,用时"+time+"秒。");
        }       
    }
}
结果:
奔驰已经开始进入秦岭隧道。
奔驰已经通过了秦岭隧道隧道,用时10秒。
五菱通用已经开始进入秦岭隧道。
五菱通用已经通过了秦岭隧道隧道,用时2秒。
沃尔沃已经开始进入秦岭隧道。
沃尔沃已经通过了秦岭隧道隧道,用时3秒。
现代已经开始进入秦岭隧道。
现代已经通过了秦岭隧道隧道,用时5秒。

同步方法

package Java1;

public class guosandong {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Carve carve = new Carve();

        Car car1 = new Car(10, carve, "奔驰");
        Car car2 = new Car(5, carve, "现代");
        Car car3 = new Car(3, carve, "沃尔沃");
        Car car4 = new Car(2, carve, "五菱通用");

        car1.start();
        car2.start();
        car3.start();
        car4.start();
    }

}
class Car extends Thread{
    private int time;
    private Carve carve;
    private String carName;
    public Car(int time,Carve carve, String carName) {
        this.carName = carName;
        this.carve = carve;
        this.time = time;
    }
    public void run() {
        carve.crossCarve(time,carName); 
    }
}
class Carve{
    private String carveName = "秦岭隧道";
    public synchronized void crossCarve(int time,String carName) {
        System.out.println(carName+"已经开始进入"+carveName+"。");
        try {
            Thread.sleep(time*1000);
        } catch (Exception e) {
            // TODO: handle exception
        }
        System.out.println(carName+"已经通过了"+carveName+"隧道,用时"+time+"秒。");
    }
}
结果:
奔驰已经开始进入秦岭隧道。
奔驰已经通过了秦岭隧道隧道,用时10秒。
五菱通用已经开始进入秦岭隧道。
五菱通用已经通过了秦岭隧道隧道,用时2秒。
沃尔沃已经开始进入秦岭隧道。
沃尔沃已经通过了秦岭隧道隧道,用时3秒。
现代已经开始进入秦岭隧道。
现代已经通过了秦岭隧道隧道,用时5秒。

练习2–同步代码块

package Java1;

public class guosandong {

    public static void main(String[] args) {
        for(int i =0 ;i<50;i++) {
            new User("用户"+(i+1)).start();
        }
    }
}
class User extends Thread{
    static Object lock = new Object();
    static int userNum =1;
    int curUser = 0;
    String userName;
    public User(String userName) {
        this.userName = userName;
    }
    public void run() {
        synchronized(lock) {
                curUser = userNum;
                userNum = userNum + 1;
        }
        System.out.println(userName+"取到的票号为:"+curUser+"。");
    }
}
输出:
用户1取到的票号为:1。
用户2取到的票号为:2。
用户3取到的票号为:3。
用户4取到的票号为:4。
用户5取到的票号为:5。
用户6取到的票号为:6。
...
用户42取到的票号为:49。
用户46取到的票号为:46。
用户43取到的票号为:50。

同步方法

package Java1;

public class guosandong {

    public static void main(String[] args) {
        for(int i =0 ;i<50;i++) {
            new User("用户"+(i+1)).start();
        }
    }
}
class User extends Thread{
    static int userNum =1;
    String currentUser;
    int ticketNumber;
    public User(String currentUser) {
        this.currentUser = currentUser;
    }
    public void run() {
        ticketNumber = getTicket();
        System.out.println(currentUser+"取到的票号为:"+ticketNumber+"。");
    }
    public synchronized int getTicket() {
        int curUser = userNum;
        userNum = userNum + 1;
        return curUser;
    }
}
结果:
用户1取到的票号为:1。
用户2取到的票号为:2。
用户4取到的票号为:3。
用户5取到的票号为:4。
用户3取到的票号为:5。
用户6取到的票号为:6。
...
用户43取到的票号为:50。
用户44取到的票号为:49。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值