java基础面试题

1.基本数据类型

整数类型:byte、short、int、long

浮点型:float、double

布尔类型:boolean

字符类型:char

2.包装数据类型

整数型:Byte、Short、Integer、Long

浮点型:Float、Double

布尔类型:Boolean

字符类型:Character

3.基本数据类型和包装数据类型的区别

a、包装数据类型属于引用数据类型,其具体内容是存储在堆中的,栈中存储的是其内容的引用(堆中内容的地址),在方法中定义的非全局基本数据类型是具体内容是存储在栈中的

b、包装类型需要先初始化再赋值(属于对象),基本数据类型可以直接赋值;Integer a = Integer("a")会报异常java.lang.NumberFormatException;

c、基本数据类型和包装数据类型之间可以自动拆箱、装箱(在使用算数运算符时),"=="判断时不会触发自动拆箱,仍会判断栈中存储的地址;

d、java将Integer的[-128,127]的包装对象默认创建好放在了堆中的常量池中,每次使用这个范围的int包装类型的值时直接从常量池取值,所以这个范围内的Integer用"=="判断时只要值相同就是true(值相同,则地址相同);

e、包装类提供了一些常用的方法,例如使用int的最大值,可以直接使用Integer.MAX_VALUE方法;

f、有些类型,如集合类型,要求容器的类型必须是object,所以无法直接使用int或者double等,必须要使用包装类型来代替。

g、包装类型和引用类型直接的互相转换:装箱,Integer.value of (128);拆箱,intvalue()方法。

4.四个修饰符:public、default(默认)、protected、private

public对所有类可见。使用对象:类、接口、方法、变量
default同一包内可见,可以不写(默认)。使用对象:类、接口、方法、变量、
protected对同一包内的类和所有子类可见。使用对象:变量、方法。不能修饰外部类
private同一类内可见。使用对象:变量、方法。注意:不能修饰外部类

5、流程控制语句break,continue,return的区别和作用

break结束当前的循环体
continue跳出当次循环,进入下一次循环
return程序返回,结束当前方法

6、抽象类和接口的对比

抽象类是用来捕捉子类的通用特性的,接口是抽象方法的集合。从设计层面来说,抽象类是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。

相同点:都不能直接实例化(需要实现抽象方法);都位于继承的顶端,用于被其他实现或继承;都包含抽象方法,子类都必须重写这些方法

不同点:

接口抽象类
使用Interface关键字声明使用bastract关键字声明
接口被使用时用implements关键字,使用接口的子类必须实现接口中所有声明的方法子类使用extends关键字继承,子类如果不是抽象类需要重写所有的抽象方法
接口没有构造器抽象类可以有构造器
java8以前的接口都是public的,且不允许使用private和protected,java9支持private抽象类中的方法可以是任意修饰符
一个类可以实现多个接口一个类最多只能继承一个抽象类
接口中的字段都是final和static的抽象类中的字段可以是任意的

7、内部类

在java中可以将一个类定义在另一个类的内部,这就是内部类。内部类本身就是类的一个属性,与其他属性定义方式一致。内部类可以分成四种:成员内部类、局部内部类、匿名内部类和静态内部类

1)成员内部类的特点:

外部类中的任何成员都可以在普通内部类方法中直接被访问 ;

普通内部类所处的成员与外部类成员位置相同,因此也受public、private等访问限定符的约束 ;

在内部类方法中访问同名的成员时,优先访问自己的,如果要访问外部类同名的成员 ,需采用:外部类名称.this.方法名

普通内部类对象必须在先有外部类对象前提下才能被创建 ;new 外部类().new 内部类()

外部类中不能直接访问内部类中的成员,如果要访问必须先要创建内部类的对象 ;

成员内部类编译以后也会生成一个 .class文件,文件名是  外部类名$内部类名.class

2)静态内部类的特点:

在内部类中只能访问外部类中的静态成员(包括静态成员变量和静态成员方法) ;

创建内部类对象时,不需要先创建外部类对象 ;

成员内部类,经过编译之后会生成独立的字节码文件,命名格式为:外部类名称$内部类名称

3)局部内部类的特点:

局部内部类定义在外部类的方法中,就像局部变量一样,并不是外部类的成员。

局部内部类前面不能有权限修饰符

局部内部类里不能使用static声明变量

局部内部类可以访问外部类的静态成员

如果这个局部内部类所在的方法是静态的,它无法访问外部类的非静态成员

局部内部类可以访问外部函数的局部变量,但 这个局部变量必须要非final修饰。JDK8以后,final可以省略匿4)匿名内部类特点:

匿名内部类就是一种特殊的局部内部类,只不过没有名称而已,基本特点和局部内部类一致

匿名内部类不能有构造器,匿名内部类没有类名,肯定无法声明构造器

匿名内部类的前提是,这个内部类必须要继承自一个父类或者父接口

匿名内部类是接口的一种常见简化写法,也是我们开发中最常使用的一种内部类。它的本质是一个实现类父类或者父类接口具体方法的一个匿名对象。

可以将一个内部类对象的方法当作另一个方法的参数使用

8、内部类的优点

a、一个内部类对象可以访问创建它的外部类的属性,包括私有属性

b、内部类不为同一包内的其他类可见,具有很好的封装性

c、内部类有效实现了”多重继承“,优化了java单继承的缺陷

d、匿名内部类可以很方便的定义回调

9、==和equals的区别

a、==判断基本数据类型是比较的是值是否相等,判断引用类型是比较的是连个对象的地址是否相等,可以理解为==判断的是栈的内容。基本数据类型存储在栈中的是具体内容(值),引用数据类型存储在栈中的是对象的地址,对象的具体内容存储在堆中。==是一种运算符。

b、equals()方法只能用来判断引用数据类型。该类未重写equals()方法时,来判断两个对象等价于==。常用类的equals()方法都重写了(String、Integer、Date等等),重写后的equals()方法比较是堆中的内容(即对象的值)

c、如果equals返回true,则两个对象在堆中的hashcode值相同,反之则不一定。hashcode值相同只是散列码相同,键值对不一定相等,例如hashMap中的哈希冲突

d、一个规定:重写equals()方法时,必须重写hashcode()方法。如果不重写hashcode(),则该类的两个对象无论如何也不会相等,因为hashcode()的默认行为是对堆上的对象产生独特值。

10、HashMap的底层数据结构

HashMap在JDK1.8以后,底层数据结构是数组+链表/数组+红黑树。一般情况下,HashMap的底层仅仅是数组(数组中的每个元素是一个Entry对象,每个Entry对象包含四个属性:[key、value、next、hash]),向HashMap容器中插入Entry对象时,会先使用对象的key调用哈希函数计算出一个hash值,然后使用这个hash值和[数组长度-1] 做位与运算得到一个数组下标。如果数组该下标的位置为空,就插入,如果下标位置上已有其它Entry对象,说明发生了hash冲突(也叫hash碰撞),就把要插入的Entry对象和该位置上的其它Entry对象通过next属性连接起来形成链表。当链表长度大于8,且数组的长度大于64时,链表会转化为红黑树(若链表长度大于8,数组长度小于64时,会先进行扩容),当结点数目降到6个时,红黑树转换成链表。

HashMap的默认初始容量是16,且容量必须是2的n次方。默认扩容因子为0.75,即hashMap的元素个数为13时,就会进行第一次扩容,容量变为16*2=32。每次扩容是非常消耗性能的,所有尽量预设扩容。

扩容的原理分为两步。第一步,创建一个新的Entry数组,长度是原数组的两倍。第二步,ReHash,遍历原Entry数组,把所有的Entry重新Hash到新数组。为什么要重新Hash,不直接复制过去呢?因为长度扩大以后,Hash的规则也随之改变。

11、数据结构

线性数据结构:数组、链表、栈、队列

非线性数据结构:树、哈希表、图。

12、数组和链表的区别

数组Array是有序的元素序列,在内存中开辟一段连续的空间,并在此空间存放元素。数据长度固定,就可以根据数组的开头位置和偏移量可以直接计算出数据地址。数组查找元素更快,可以根据下标直接访问指定位置的元素。数组在指定位置增删元素较慢,如果要在指定索引处增加一个元素,需要新创建一个数组,把原数组复制过去,再在创建的新数组末尾添加新元素,然后再次创建一个新数组,把指定索引位置前的数组直接复制过去,再在指定的索引处加入新添加的元素,剩余的元素索引加一,复制到对应的索引位置处。如果要在指定索引处删除一个元素,需要新创建一个数组,把指定索引位置前的数组直接复制过去,然后跳过指定的索引处的元素,剩余的元素索引减一,复制到对应的索引位置处。如果是删除最后一个元素,或者直接添加一个元素(在数组末尾处添加),速度也是较快的。

链表LinkedList是由一系列节点node(链表中每一个元素为一个节点)组成,节点可以在运行是动态生成。每个节点包括两个部分:一个是存储数据元素的数据域,一个是存储下一个节点地址的指针域。

单向链表:查找元素慢,想要查找某个元素,需要通过连接的节点,从第一个节点开始依次向后查询。增删元素快,只需要修改连接的下个元素的地址即可。

双向链表:

13、String类

a、字符串常量池

字符串常量池位于堆内存中,专门用来存储字符串常量,可以提高内存的使用率,避免开辟多块空间存储相同的字符串。在创建字符串时,JVM会先检查字符串常量池,如果该字符串已经存在池中,则返回它的引用,如果不存在,则实例化一个对象放到池中,并返回其引用。String a = new String("str")实际可能生成 了两个对象吗,若字符串常量池中无"str"字符串,则在堆中创建一个字符串字面量"str"所对应的、驻留(intern)在一个全局共享的字符串常量池中的实例,字符串常量池只放引用,再在堆中生成一个new String()创建并初始化的,且内容与"str"相同的实例,变量a指向堆内存中的对象;如果存在就只生成一个对象,在堆内存中开辟一块存储空间,将字符串常量池中的值复制一份到堆内存的存储区中,此时变量a指向的也是堆内存中的对象。每一次new一个String对象,都在堆中重新开辟了存储空间,所以每次new的对象的地址都不一样。多次创建 String a = "str"(产生0个或1个对象),变量指向的都是常量池中的对象"str",地址是一定的,用==比较时会是true。

b、String类的特点

String类是final的,不能被继承。每次对String进行操作时,都会调用StringBulider()来拼接字符串生成一个新的String对象("+"操作时例外,编译器会自动完成拼接,不会调用StringBulider())。String的不可变指的是存储在堆中的内容不可变,但是栈中的引用可以变。

StringBulider线程不安全可变长字符串,性能比StringBuffer高20%左右;StringBuffer线程安全可变长字符串

c、String类的常用方法

方法定义
reverse()字符串反转
IndexOf()获取指定字符的下标
charAt()返回指定索引处的字符
replace()字符串替换
spilt()根据参数分割字符串
getBytes()返回字符串的byte类型数组
SubString()截取字符串
toLowerCase()转换成小写
toUpperCase()转换成大写
trim()去除字符串两端空白

14、java的运行时内存

java中,java内存主要分为5个部分。分别为:方法区、堆、栈、PC寄存器和本地方法区

a、栈

内存的一种结构,先入后出。每一个线程都维护一个私有的JVM栈,被用来存储与它们的静态内存分配i相关的变量

b、栈

是JVM一启动就创建的内存区域,它会一直存在,直到JVM被销毁。与栈不同的是,栈是单个线程的属性,堆实际上是由JVM本身管理的全局内存,此内存在运行时用于为对象分配内存,简而言之,使用new关键字创建的任何对象都存储在堆内存中。

15、反射

java反射机制是在运行状态中,对于任意一个类吗,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象方法的功能称为java语言的反射机制。

a、java获取反射的三种方法

通过new对象实现,Student stu = new Student();stu.getclass()。

通过相对路径,Class.forName("com.wan.Student")。

通过类名,Student.class

b、反射机制的应用场景

反射是框架设计的灵魂,动态代理模式,Spring/Hibernate等框架都是依赖反射实现的。

Spring通过XML配置模式装载Bean的过程

1)将程序内所有XML或Properties配置文件加载到内存中;

2)java类里面解析XML或Properties里面的内容,得到对应实体类的字节码字符串以及相关的属性信息;

3)使用反射机制,根据这个字符串得到某个类的Class实例

4)动态配置实例的属性

16、静态代码块

静态变量只会初始化(执行)一次。当有父类时,完整的初始化顺序为:父类静态变量(静态代码块)->子类静态变量(静态代码块)->父类非静态变量(非静态代码块)->父类构造器->子类非静态变量(非静态代码块)->子类构造器

17、线程

线程的正常流程:初始(new)、启动(Thread.start())、运行Runnable(运行中、就绪)、Treminated终止。

uploading.4e448015.gif正在上传…重新上传取消

一个线程可能处于的状态(线程在给定的时间点只能处于一种状态,这些状态是虚拟机状态,不反映任何操作系统线程状态):

NEW:新建但是尚未启动的线程处于此状态,没有调用 start() 方法。

RUNNABLE:包含就绪(READY)和运行中(RUNNING)两种状态。线程调用 start() 方法会会进入就绪(READY)状态,等待获取 CPU 时间片。如果成功获取到 CPU 时间片,则会进入运行中(RUNNING)状态。

BLOCKED:线程在进入同步方法/同步块(synchronized)时被阻塞,等待同步锁的线程处于此状态。

WAITING:无限期等待另一个线程执行特定操作的线程处于此状态,需要被显示的唤醒,否则会一直等待下去。例如对于 Object.wait(),需要等待另一个线程执行 Object.notify() 或 Object.notifyAll();对于 Thread.join(),则需要等待指定的线程终止。

TIMED_WAITING:在指定的时间内等待另一个线程执行某项操作的线程处于此状态。跟 WAITING 类似,区别在于该状态有超时时间参数,在超时时间到了后会自动唤醒,避免了无期限的等待。

TERMINATED:执行完毕已经退出的线程处于此状态。

18、wait()和sleep()方法的区别

a、来源不同:sleep()来自Thread类,wait()来自Object类

b、对于同步锁的影响不同:sleep()不会改变同步锁(synchronization lock)的行为,如果当前线程持有同步锁,那么sleep()不会让线程释放同步锁。wait()会影响同步锁,让其他线程进入synchronized代码块执行

c、使用范围不同:sleep()可以在任何地方使用。wait()只能在同步控制方法或者同步控制块中使用,否则会判处IllegalMonitorStateEXception。

d、恢复方式不同:两者都会暂停当前线程,但是在恢复上不太一样。sleep()在时间到了以后会重新恢复;wait()则需要其他线程调用同一对象的notify()/notifyAll()才能重新恢复

19、线程的sleep()、yield()、join()方法

a、线程执行sleep()方法后进入超时等待状态,而执行yield()方法后进入就绪状态

b、sleep()方法给其他线程运行机会时不考虑线程的优先级,因而会给优先级低的线程运行机会;yield()方法只会给相同优先级或者更高优先级的线程运行的机会

c、如果一个线程A执行了threadB.join()语句,当前线程A等待threadB线程终止以后才会继续执行自己的代码

20、Thread 调用 start() 方法和调用 run() 方法的区别

run():普通的方法调用,在主线程中执行,不会新建一个线程来执行。

start():新启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到 CPU 时间片,就开始执行 run() 方法。

21、synchronized 和 Lock 的区别

1)Lock 是一个接口;synchronized 是 Java 中的关键字,synchronized 是内置的语言实现;

2)Lock 在发生异常时,如果没有主动通过 unLock() 去释放锁,很可能会造成死锁现象,因此使用 Lock 时需要在 finally 块中释放锁;synchronized 不需要手动获取锁和释放锁,在发生异常时,会自动释放锁,因此不会导致死锁现象发生;

3)Lock 的使用更加灵活,可以有响应中断、有超时时间等;而 synchronized 却不行,使用 synchronized 时,等待的线程会一直等待下去,直到获取到锁;

4)在性能上,随着近些年 synchronized 的不断优化,Lock 和 synchronized 在性能上已经没有很明显的差距了,所以性能不应该成为我们选择两者的主要原因。官方推荐尽量使用 synchronized,除非 synchronized 无法满足需求时,则可以使用 Lock。 5)Lock锁只有代码块锁,synchronized有代码块锁和方法锁

6)Lock锁适合大量同步代码块的同步问题,synchronized锁适合少量的同步问题,使用lock锁,JVM将花费较少的时间来调度线程,性能更好,性能更好,并且具有更好的扩展性(提供更多子类)

22、synchronized 各种加锁场景的作用范围

1)作用于非静态方法,锁住的是对象实例(this),每一个对象实例有一个锁。

public synchronized void method() {} 2)作用于静态方法,锁住的是类的Class对象,因为Class的相关数据存储在永久代元空间,元空间是全局共享的,因此静态方法锁相当于类的一个全局锁,会锁所有调用该方法的线程。

public static synchronized void method() {} 3)作用于 Lock.class,锁住的是 Lock 的Class对象,也是全局只有一个。

synchronized (Lock.class) {} 4)作用于 this,锁住的是对象实例,每一个对象实例有一个锁。

synchronized (this) {} 5)作用于静态成员变量,锁住的是该静态成员变量对象,由于是静态变量,因此全局只有一个。

public static Object monitor = new Object(); synchronized (monitor) {}

23、如何检测死锁

死锁的四个必要条件:

1)互斥条件:进程对所分配到的资源进行排他性控制,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。

2)请求和保持条件:进程已经获得了至少一个资源,但又对其他资源发出请求,而该资源已被其他进程占有,此时该进程的请求被阻塞,但又对自己获得的资源保持不放。

3)不可剥夺条件:进程已获得的资源在未使用完毕之前,不可被其他进程强行剥夺,只能由自己释放。

4)环路等待条件:存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被 链中下一个进程所请求。

24、实现多线程的方式

1)实现Runnable()接口

2)继承Thread类。Thread类其实也是实现了Runnable()接口

3)实现Callable()接口。Runnable()和Callable()接口的主要区别是Callable()接口有返回值

4)使用线程池

25、线程池的核心属性

uploading.4e448015.gif正在上传…重新上传取消

1)threadFactory(线程工厂):用于创建工作线程的工厂。

2)corePoolSize(核心线程数):当线程池运行的线程少于 corePoolSize 时,将创建一个新线程来处理请求,即使其他工作线程处于空闲状态。

3)workQueue(队列):用于保留任务并移交给工作线程的阻塞队列。

4)maximumPoolSize(最大线程数):线程池允许开启的最大线程数。

5)handler(拒绝策略):往线程池添加任务时,将在下面两种情况触发拒绝策略:1)线程池运行状态不是 RUNNING;2)线程池已经达到最大线程数,并且阻塞队列已满时。

6)keepAliveTime(保持存活时间):如果线程池当前线程数超过 corePoolSize,则多余的线程空闲时间超过 keepAliveTime 时会被终止。

26、线程池的拒绝策略

1)AbortPolicy:中止策略。默认的拒绝策略,直接抛出 RejectedExecutionException。调用者可以捕获这个异常,然后根据需求编写自己的处理代码。

2)DiscardPolicy:抛弃策略。什么都不做,直接抛弃被拒绝的任务。

3)DiscardOldestPolicy:抛弃最老策略。抛弃阻塞队列中最老的任务,相当于就是队列中下一个将要被执行的任务,然后重新提交被拒绝的任务。如果阻塞队列是一个优先队列,那么“抛弃最旧的”策略将导致抛弃优先级最高的任务,因此最好不要将该策略和优先级队列放在一起使用。

4)CallerRunsPolicy:调用者运行策略。在调用者线程中执行该任务。该策略实现了一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将任务回退到调用者(调用线程池执行任务的主线程),由于执行任务需要一定时间,因此主线程至少在一段时间内不能提交任务,从而使得线程池有时间来处理完正在执行的任务。

27、使用线程池的优点

1)降低资源消耗。通过重复利用已创建的线程,降低线程创建和销毁造成的消耗。

2)提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。

3)增加线程的可管理型。线程是稀缺资源,使用线程池可以进行统一分配,调优和监控。

28、JDBC连接池

a、单个JDBC的缺点:网络IO较多;数据库的负载较高;响应时间较长及QPS较低;应用频繁的创建连接和关闭连接,导致临时对象较多,GC频繁;在关闭连接后,会出现大量TIME_WAIT的TCP状态(在2个MSL后关闭)

b、基本思想:就是为数据库连接建立一个”缓冲池“。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从缓冲池中取出一个,使用完毕后再放回去。数据库连接池负责分配、管理和释放数据库连接,它允许应用重复使用一个现有的数据库连接,而不是重新建立一个。对于多于连接池中连接数的并发请求,应该在请求队列中排队等待,并且应用程序可以根据池中连接的使用率,动态增加或减少池中的连接数。

c、核心属性:

1)连接数据库时四要素:驱动名称、数据库地址、用户名、密码;

2)初始化连接数;

3)最大连接数;

4)最小连接数

5)最大空闲时间:如果获取了连接对象,在指定时间内没有任何操作,就会自动释放连接

6)最大等待时间:在指定时间内,尝试获取连接,如果超出指定时间,就会提示失败

d、JDBC连接池的优点:资源重用、更快的系统反应速度;新的资源分配手段;统一的连接管理,避免数据库连接泄露

e、与线程池的异同点:

相同点:都是事先准备好资源,避免频繁创建和销毁的代价。

不同点:JDBC连接池是面向数据库连接的,是为了优化数据库连接资源,类似于在客户端做优化;线程池是面向后台程序的,为了提高内存和CPU效率,类似于在服务端做优化;JDBC连接需要手动关闭或异常关闭才能断开连接,线程是执行完毕就会自动关闭线程;线程池使用生产者、消费者模式,生产者生产任务提交到线程池的阻塞队列,线程池开启N个线程不停的从阻塞队列中获取任务并执行。

29、JDK1.8新特性

Lamdba表达式

函数式接口

方法引用和构造引用

Stream API

接口中的默认方法和静态方法

新时间日期API

OPtional

30、Stream API

1)什么是Stream?

Java8中两个最为重要特性:第一个的是Lambda表达式,另一个是Stream API。

StreamAPI它位于java.util.stream包中,StreamAPI帮助我们更好地对数据进行集合操作,它本质就是对数据的操作进行流水线式处理,也可以理解为一个更加高级的迭代器,主要作用是遍历其中每一个元素。简而言之,StreamAP提供了一种高效且易于使用的处理数据方式。

2)stream特点

a、Stream自己不会存储数据。

b、Stream不会改变源对象。

c、Stream操作是延迟执行的。只有调用终端操作时,中间操作才会执行; 这就意位着,等到他们有结果才执行。

d、终端操作:会消费流,这种操作会产生一个结果的,如果一个流被消费过了,那它就不能被重用的。 中间操作:中间操作会产生另一个流。因此中间操作可以用来创建执行一系列动作的管道。一个特别需要注意的点是:中间操作不是立即发生的。相反,当在中间操作创建的新流上执行完终端操作后,中间操作指定的操作才会发生。所以中间操作是延迟发生的,中间操作的延迟行为主要是让流API能够更加高效地执行。

e、stream不可复用,对一个已经进行过终端操作的流再次调用,会抛出异常。

3)Stream操作分类

Stream的操作可以分为两大类:中间操作、终结操作

中间操作可分为:

无状态(Stateless)操作:指元素的处理不受之前元素的影响 有状态(Stateful)操作:指该操作只有拿到所有元素之后才能继续下去

终结操作可分为:

短路(Short-circuiting)操作:指遇到某些符合条件的元素就可以得到最终结果 非短路(Unshort-circuiting)操作:指必须处理完所有元素才能得到最终结果

操作类型状态分类
中间操作无状态操作unorderecd()、filter()、map()、peek()...
 有状态操作distinct()、sorted()、limit()、skip()
终结操作非短路操作forEach()、forEachOrdered、toAraay()、reduce()、collect()、max()、
 非短路操作min()、count()
 短路操作anyMatch()、allMatch()、findFirst()、findAny()、noneMatch()

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值