JAVA基础面试笔记

该博客围绕Java基础展开,涵盖面向对象编程特性、字符串类区别、自动装箱拆箱等内容。介绍了重载重写、接口抽象类差异,还涉及线程、异常处理、序列化等知识,以及IO流分类、移位运算符区别等,为Java学习提供全面基础参考。

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

1.重载和重写的区别

    重载就是同一个类中多个同名方法根据不同的传参来执行不同的逻辑处理。
   重写是子类对父类方法的重新改造,外部样子不变,内部逻辑可以改变

在这里插入图片描述在这里插入图片描述

2. java面向对象编程三大特性:封装 继承 多态

  • 封装
       把一个对象的属性私有化,同时提供一些可以被外界访问属性的方法,如果属性不想被外界访问,也可以不提供方法。

  • 继承
       使用已存在的类作为基础建立新的类。java只能继承一个父类,但是可以继承多个接口。

  • 多态
       所谓多态就是指程序中定义的引⽤变量所指向的具体类型和通过该引⽤变量发出的⽅法调⽤在编程时并不确定,⽽是在程序运⾏期间才确定,即⼀个引⽤变量到底会指向哪个类的实例对象,该引⽤变量发出的⽅法调⽤到底是哪个类中实现的⽅法,必须在由程序运⾏期间才能决定。
       在 Java 中有两种形式可以实现多态:继承(多个⼦类对同⼀⽅法的重写)和接⼝(实现接⼝并覆盖接⼝中同⼀⽅法)

3.String StringBuffer和StringBuilder的区别?String为什么是不可变的?

定义

   String 类中使⽤ final 关键字修饰字符数组来保存字符串,语法就是private final char value[],所以String对象是不可变的。在 Java 9 之后, String 类的实现改⽤ byte 数组存储字符串private final byte[] value
   ⽽ StringBuilder 与 StringBuffer 都继承⾃ AbstractStringBuilder 类,在AbstractStringBuilder 中也是使⽤字符数组保存字符串 char[]value 但是没有⽤ final 关键字修饰,所以这两种对象都是可变的。

线程安全性

   String 中的对象是不可变的,也就可以理解为常量,线程安全。 AbstractStringBuilder 是StringBuilder 与 StringBuffer 的公共⽗类,定义了⼀些字符串的基本操作,如 expandCapacity、append、 insert、 indexOf 等公共⽅法。StringBuffer 对⽅法加了同步锁或者对调⽤的⽅法加了同步锁,所以是线程安全的。 StringBuilder 并没有对⽅法进⾏加同步锁,所以是⾮线程安全的

性能

   每次对 String 类型进⾏改变的时候,都会⽣成⼀个新的 String 对象,然后将指针指向新的 String对象。 字符串池。
   StringBuffer 每次都会对 StringBuffer 对象本身进⾏操作,⽽不是⽣成新的对象并改变对象引⽤。相同情况下使⽤ StringBuilder 相⽐使⽤ StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的⻛险。

对于三者使⽤的总结:

  1. 操作少量的数据: 适⽤ String
  2. 单线程操作字符串缓冲区下操作⼤量数据: 适⽤ StringBuilder
  3. 多线程操作字符串缓冲区下操作⼤量数据: 适⽤ StringBuffer

4.自动装箱与拆箱

装箱:将基本类型⽤它们对应的引⽤类型包装起来;
拆箱:将包装类型转换为基本数据类型

5.在一个静态方法内调用一个非静态成员为什么是非法的?

   由于静态⽅法可以不通过对象进⾏调⽤(不需要实例,而非静态变量,实例的时候才加载),因此在静态⽅法⾥,不能调⽤其他⾮静态变量,也不可以访问⾮静态变量成员。

6.在 Java 中定义⼀个不做事且没有参数的构造⽅法的作⽤

   Java 程序在执⾏⼦类的构造⽅法之前,如果没有⽤ super() 来调⽤⽗类特定的构造⽅法,则会调⽤⽗类中“没有参数的构造⽅法”。因此,如果⽗类中只定义了有参数的构造⽅法,⽽在⼦类的构造⽅法中⼜没有⽤ super() 来调⽤⽗类中特定的构造⽅法,则编译时将发⽣错误,因为 Java 程序在⽗类中找不到没有参数的构造⽅法可供执⾏。解决办法是在⽗类⾥加上⼀个不做事且没有参数的构造⽅法。

7.接⼝和抽象类的区别是什么?

  1. 接口的方法默认是public,所有方法在接口中不能有实现(java 8开始接口方法可以有默认实现),而抽象类可以有非抽象的方法
  2. 接口中除了final static 变量,不能有其他变量,而抽象类则不一定
  3. 一个类可以实现多个接口,但只能实现一个抽象类。接口本身可以通过extends关键字扩展多个接口
  4. 接⼝⽅法默认修饰符是 public,抽象⽅法可以有 public、 protected 和 default 这些修饰符(抽象⽅法就是为了被重写所以不能使⽤ private 关键字修饰!)
  5. 从设计层面来说,抽象方法是对类的抽象,是一种模板设计,而接口是对行为的抽象,是一种行为的规范。

总结⼀下 jdk7~jdk9 Java 中接⼝概念的变化(相关阅读):

  1. 在 jdk 7 或更早版本中,接⼝⾥⾯只能有常量变量和抽象⽅法。这些接⼝⽅法必须由选择实现
    接⼝的类实现。
  2. jdk8 的时候接⼝可以有默认⽅法和静态⽅法功能。
  3. Jdk 9 在接⼝中引⼊了私有⽅法和私有静态⽅法。

8.成员变量与局部变量的区别有哪些?

  1. 从语法形式上看:成员变量是属于类的,⽽局部变量是在⽅法中定义的变量或是⽅法的参数;成员变量可以被 public,private,static 等修饰符所修饰,⽽局部变量不能被访问控制修饰符及static 所修饰;但是,成员变量和局部变量都能被 final 所修饰。
  2. 从变量在内存中的存储⽅式来看:如果成员变量是使⽤ static 修饰的,那么这个成员变量是属于类的,如果没有使⽤ static 修饰,这个成员变量是属于实例的。对象存于堆内存,如果局部变量类型为基本数据类型,那么存储在栈内存,如果为引⽤数据类型,那存放的是指向堆内存对象的引⽤或者是指向常量池中的地址。
  3. 从变量在内存中的⽣存时间上看:成员变量是对象的⼀部分,它随着对象的创建⽽存在,⽽局部变量随着⽅法的调⽤⽽⾃动消失。
  4. 成员变量如果没有被赋初值:则会⾃动以类型的默认值⽽赋值(⼀种情况例外:被 final 修饰的成员变量也必须显式地赋值),⽽局部变量则不会⾃动赋值。

9.对象的相等与指向他们的引⽤相等,两者有什么不同?

   对象的相等,⽐的是内存中存放的内容是否相等。⽽引⽤相等,⽐较的是他们指向的内存地址是否相等。

10.在调⽤⼦类构造⽅法之前会先调⽤⽗类没有参数的构造⽅法,其⽬的是?

   帮助⼦类做初始化⼯作

11.== 与 equals(重要)

   == : 它的作⽤是判断两个对象的地址是不是相等。即,判断两个对象是不是同⼀个对象(基本数据类型 == ⽐较的是值,引⽤数据类型 == ⽐较的是内存地址)。
   quals() : 它的作⽤也是判断两个对象是否相等。但它⼀般有两种使⽤情况:

  1. 类没有覆盖equals方法,效果和**==**一样
  2. 类覆盖了equals方法,按照定义的内容比较。

12.hashCode 与 equals (重要)

⾯试官可能会问你: “你重写过 hashcode 和 equals 么,为什么重写 equals 时必须重写 hashCode⽅法? ”

hashCode()介绍

   hashCode() 的作⽤是获取哈希码,也称为散列码;它实际上是返回⼀个 int 整数。这个哈希码的作⽤是确定该对象在哈希表中的索引位置。 hashCode() 定义在 JDK 的 Object.java 中,这就意味着 Java中的任何类都包含有 hashCode() 函数。
   散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利⽤到了散列码!(可以快速找到所需要的对象)

为什么要有 hashCode

   我们先以“HashSet 如何检查重复”为例⼦来说明为什么要有 hashCode: 当你把对象加⼊ HashSet时, HashSet 会先计算对象的 hashcode 值来判断对象加⼊的位置,同时也会与该位置其他已经加⼊的对象的 hashcode 值作⽐较,如果没有相符的 hashcode, HashSet 会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调⽤ equals() ⽅法来检查 hashcode 相等的对象是否真的相同。如果两者相同, HashSet 就不会让其加⼊操作成功。如果不同的话,就会重新散列到其他位置。(摘⾃我的 Java 启蒙书《Head first java》第⼆版)。这样我们就⼤⼤减少了 equals 的次数,相应就⼤⼤提⾼了执⾏速度

hashCode()与 equals()的相关规定

  1. 如果两个对象相等,则 hashcode ⼀定也是相同的
  2. 两个对象相等,对两个对象分别调⽤ equals ⽅法都返回 true
  3. 两个对象有相同的 hashcode 值,它们也不⼀定是相等的
  4. 因此, equals ⽅法被覆盖过,则 hashCode ⽅法也必须被覆盖
  5. hashCode() 的默认⾏为是对堆上的对象产⽣独特值。如果没有重写 hashCode(),则该 class的两个对象⽆论如何都不会相等(即使这两个对象指向相同的数据)

13.为什么java中只有值传递?

值传递和引用传递之前的区别的重点是什么
在这里插入图片描述Java中其实还是值传递的,只不过对于对象参数,值的内容是对象的引用

14.简述线程、程序、进程的基本概念。以及他们之间关系是什么?

   线程与进程相似,但线程是⼀个⽐进程更⼩的执⾏单位。⼀个进程在其执⾏的过程中可以产⽣多个线程。与进程不同的是同类的多个线程共享同⼀块内存空间和⼀组系统资源,所以系统在产⽣⼀个线程,或是在各个线程之间作切换⼯作时,负担要⽐进程⼩得多,也正因为如此,线程也被称为轻量级进程。
   程序是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码
   进程是程序的⼀次执⾏过程,是系统运⾏程序的基本单位,因此进程是动态的。系统运⾏⼀个程序即是⼀个进程从创建,运⾏到消亡的过程。简单来说,⼀个进程就是⼀个执⾏中的程序,它在计算机中⼀个指令接着⼀个指令地执⾏着,同时,每个进程还占有某些系统资源如 CPU 时间,内存空间,⽂件,输⼊输出设备的使⽤权等等。换句话说,当程序在执⾏时,将会被操作系统载⼊内存中。 线程是进程划分成的更⼩的运⾏单位。线程和进程最⼤的不同在于基本上各进程是独⽴的,⽽各线程则不⼀定,因为同⼀进程中的线程极有可能会相互影响。从另⼀⻆度来说,进程属于操作系统的范畴,主要是同⼀段时间内,可以同时执⾏⼀个以上的程序,⽽线程则是在同⼀程序内⼏乎同时执⾏⼀个以上的程序段。

15.线程有哪些基本状态?

   java 线程在运⾏的⽣命周期中的指定时刻只可能处于下⾯ 6 种不同状态的其中⼀个状态
在这里插入图片描述   线程在⽣命周期中并不是固定处于某⼀个状态⽽是随着代码的执⾏在不同状态之间切换。 Java 线程状态变迁如下图所示
在这里插入图片描述由上图可以看出:
   线程创建之后它将处于 NEW(新建) 状态,调⽤ start() ⽅法后开始运⾏,线程这时候处于READY(可运⾏) 状态。可运⾏状态的线程获得了 cpu 时间⽚(timeslice)后就处于 RUNNING(运⾏) 状态。
   当线程执⾏ wait() ⽅法之后,线程进⼊ WAITING(等待) 状态。进⼊等待状态的线程需要依靠其他
线程的通知才能够返回到运⾏状态,⽽ TIME_WAITING(超时等待) 状态相当于在等待状态的基础上增加了超时限制,⽐如通过 sleep(long millis) ⽅法或 wait(long millis) ⽅法可以将 Java线程置于 TIMED WAITING 状态。当超时时间到达后 Java 线程将会返回到 RUNNABLE 状态。当线程调⽤同步⽅法时,在没有获取到锁的情况下,线程将会进⼊到 BLOCKED(阻塞) 状态。线程在执⾏Runnable 的 run() ⽅法之后将会进⼊到 TERMINATED(终⽌) 状态。

16.关于final关键字的一些总结

final关键字主要用在三个地方:变量、方法、类

  • 对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象
  • 当用final修饰一个类时,表明这个类不能被继承。final类中的所有成员方法都会被隐式地指定为final方法。
  • 使用final方法的原因有两个。第一个是把方法锁定,以防任何继承类修改他的含义;第二个原因是效率。在早期的 Java 实现版本中,会将 final ⽅法转为内嵌调⽤。但是如果⽅法过于庞⼤,可能看不到内嵌调⽤带来的任何性能提升(现在的 Java 版本已经不需要使⽤ final⽅法进⾏这些优化了)。类中所有的 private ⽅法都隐式地指定为 final。

17.java中的异常处理

java异常类层次结构图

在这里插入图片描述   在 Java 中,所有的异常都有⼀个共同的祖先 java.lang 包中的 Throwable 类。 Throwable: 有两个重要的⼦类: Exception(异常) 和 Error(错误) ,⼆者都是 Java 异常处理的重要⼦类,各⾃都包含⼤量⼦类。
   Error(错误) :是程序⽆法处理的错误,表示运⾏应⽤程序中较严重问题。⼤多数错误与代码编写者执⾏的操作⽆关,⽽表示代码运⾏时 JVM(Java 虚拟机)出现的问题。例如, Java 虚拟机运⾏错误(Virtual MachineError),当 JVM 不再有继续执⾏操作所需的内存资源时,将出现OutOfMemoryError。这些异常发⽣时, Java 虚拟机(JVM)⼀般会选择线程终⽌。
   这些错误表示故障发⽣于虚拟机⾃身、或者发⽣在虚拟机试图执⾏应⽤时,如 Java 虚拟机运⾏错误(Virtual MachineError)、类定义错误(NoClassDefFoundError)等。这些错误是不可查的,因为它们在应⽤程序的控制和处理能⼒之 外,⽽且绝⼤多数是程序运⾏时不允许出现的状况。对于设计合理的应⽤程序来说,即使确实发⽣了错误,本质上也不应该试图去处理它所引起的异常状况。在 Java
中,错误通过 Error 的⼦类描述。
   Exception(异常) :是程序本身可以处理的异常。 Exception 类常见的⼦类有:RuntimeException。 RuntimeException 异常由 Java 虚拟机抛出。 NullPointerException(要访问的变量没有引⽤任何对象时,抛出该异常)、 ArithmeticException(算术运算异常,⼀个整数除以 0时,抛出该异常)和 ArrayIndexOutOfBoundsException (下标越界异常)
注意:异常和错误的区别:异常能被程序本身处理,错误是⽆法处理

Throwable类常用方法

  • public string getMessage():返回异常发⽣时的简要描述
  • public string toString():返回异常发⽣时的详细信息
  • public string getLocalizedMessage():返回异常对象的本地化信息。使⽤ Throwable 的⼦类覆盖这个⽅法,可以⽣成本地化信息。如果⼦类没有覆盖该⽅法,则该⽅法返回的信息与getMessage()返回的结果相同
  • public void printStackTrace():在控制台上打印 Throwable 对象封装的异常信息

异常处理总结

  • try 块: ⽤于捕获异常。其后可接零个或多个 catch 块,如果没有 catch 块,则必须跟⼀个finally 块。
  • catch 块: ⽤于处理 try 捕获到的异常。
  • finally 块: ⽆论是否捕获或处理异常, finally 块⾥的语句都会被执⾏。当在 try 块或catch 块中遇到 return 语句时, finally 语句块将在⽅法返回之前被执⾏
    在以下 4 种特殊情况下, finally 块不会被执⾏:
  1. 在 finally 语句块第⼀⾏发⽣了异常。 因为在其他⾏, finally 块还是会得到执⾏
  2. 在前⾯的代码中⽤了 System.exit(int)已退出程序。 exit 是带参函数 ;若该语句在异常语句之后, finally 会执⾏
  3. 程序所在的线程死亡。
  4. 关闭 CPU。
    注意: 当 try 语句和 finally 语句中都有 return 语句时,在⽅法返回之前, finally 语句的内容将被执⾏,并且 finally 语句的返回值将会覆盖原始的返回值。如下:
	public static int f(int value) {
		try {
		return value * value;
		} finally {
		if (value == 2) {
		return 0;
		}
	}
}

如果调⽤ f(2) ,返回值将是 0,因为 finally 语句的返回值覆盖了 try 语句块的返回值。

18.序列化和反序列化的定义:

Java序列化就是指把Java对象转换为字节序列的过程
Java反序列化就是指把字节序列恢复为Java对象的过程。
序列化最重要的作用:在传递和保存对象时.保证对象的完整性和可传递性。对象转换为有序字节流,以便在网络上传输或者保存在本地文件中。
反序列化的最重要的作用:根据字节流中保存的对象状态及描述信息,通过反序列化重建对象。
**总结:**核心作用就是对象状态的保存和重建。(整个过程核心点就是字节流中所保存的对象状态及描述信息)

19.Java 序列化中如果有些字段不想进⾏序列化,怎么办?

   对于不想进⾏序列化的变量,使⽤ transient 关键字修饰。
   transient 关键字的作⽤是:阻⽌实例中那些⽤此关键字修饰的的变量序列化;当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复。 transient 只能修饰变量,不能修饰类和⽅法。

20.获取⽤键盘输⼊常⽤的两种⽅法

⽅法 1:通过 Scanner

Scanner input = new Scanner(System.in);
String s = input.nextLine();
input.close();

⽅法 2:通过 BufferedReader

BufferedReader input = new BufferedReader(new
InputStreamReader(System.in));
String s = input.readLine();

21.Java 中 IO 流

Java 中 IO 流分为⼏种?

  • 按照流的流向分,可以分为输⼊流和输出流;
  • 按照操作单元划分,可以划分为字节流和字符流;
  • 按照流的⻆⾊划分为节点流和处理流。
    Java Io 流共涉及 40 多个类,这些类看上去很杂乱,但实际上很有规则,⽽且彼此之间存在⾮常紧密的联系, Java I0 流的 40 多个类都是从如下 4 个抽象类基类中派⽣出来的。
  • InputStream/Reader: 所有的输⼊流的基类,前者是字节输⼊流,后者是字符输⼊流。
  • OutputStream/Writer:所有输出流的基类,前者是字节输出流,后者是字符输出流。
    按操作⽅式分类结构图:
    在这里插入图片描述按操作对象分类结构图:
    在这里插入图片描述既然有了字节流,为什么还要有字符流?
    问题本质想问: 不管是⽂件读写还是⽹络发送接收,信息的最⼩存储单元都是字节,那为什么 I/O 流操作要分为字节流操作和字符流操作呢?
    回答: 字符流是由 Java 虚拟机将字节转换得到的,问题就出在这个过程还算是⾮常耗时,并且,如果我们不知道编码类型就很容易出现乱码问题。所以, I/O 流就⼲脆提供了⼀个直接操作字符的接⼝,⽅便我们平时对字符进⾏流操作。如果⾳频⽂件、图⽚等媒体⽂件⽤字节流⽐较好,如果涉及到字符的话使⽤字符流⽐较好

BIO,NIO,AIO 有什么区别?

   BIO (Blocking I/O): 同步阻塞 I/O 模式,数据的读取写⼊必须阻塞在⼀个线程内等待其完成。在活动连接数不是特别⾼(⼩于单机 1000)的情况下,这种模型是⽐较不错的,可以让每⼀个连接专注于⾃⼰的 I/O 并且编程模型简单,也不⽤过多考虑系统的过载、限流等问题。线程池本身就是⼀个天然的漏⽃,可以缓冲⼀些系统处理不了的连接或请求。但是,当⾯对⼗万甚⾄百万级连接的时候,传统的 BIO 模型是⽆能为⼒的。因此,我们需要⼀种更⾼效的 I/O 处理模型来应对更⾼的并发量。
   NIO (Non-blocking/New I/O): NIO 是⼀种同步⾮阻塞的 I/O 模型,在 Java 1.4 中引⼊了NIO 框架,对应 java.nio 包,提供了 Channel , Selector, Buffer 等抽象。 NIO 中的 N 可以理解为 Non-blocking,不单纯是 New。它⽀持⾯向缓冲的,基于通道的 I/O 操作⽅法。 NIO提供了与传统 BIO 模型中的 Socket 和    ServerSocket 相对应的 SocketChannel 和ServerSocketChannel 两种不同的套接字通道实现,两种通道都⽀持阻塞和⾮阻塞两种模式。阻塞模式使⽤就像传统中的⽀持⼀样,⽐较简单,但是性能和可靠性都不好;⾮阻塞模式正好与之相反。对于低负载、低并发的应⽤程序,可以使⽤同步阻塞 I/O 来提升开发速率和更好的维护性;对于⾼负载、⾼并发的(⽹络)应⽤,应使⽤ NIO 的⾮阻塞模式来开发
   AIO (Asynchronous I/O): AIO 也就是 NIO 2。在 Java 7 中引⼊了 NIO 的改进版 NIO 2,它是异步⾮阻塞的 IO 模型。异步 IO 是基于事件和回调机制实现的,也就是应⽤操作之后会直接返回,不会堵塞在那⾥,当后台处理完成,操作系统会通知相应的线程进⾏后续的操作。 AIO 是异步 IO 的缩写,虽然 NIO 在⽹络操作中,提供了⾮阻塞的⽅法,但是 NIO 的 IO ⾏为还是同步
的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程⾃⾏进⾏ IO 操作, IO 操作本身是同步的。查阅⽹上相关资料,我发现就⽬前来说 AIO 的应⽤还不是很⼴泛, Netty 之前也尝试使⽤过 AIO,不过⼜放弃了。

22.Java>>与>>>的区别

’>>:带符号右移。正数右移高位补0,负数右移高位补1。比如:4 >> 1,结果是2;-4 >> 1,结果是-2。-2 >> 1,结果是-1。
‘>>>:无符号右移。无论是正数还是负数,高位通通补0。

对于正数而言,>>和>>>没区别。

对于负数而言,-2 >>> 1,结果是2147483647(Integer.MAX_VALUE),-1 >>> 1,结果是2147483647(Integer.MAX_VALUE)。

以下代码可以判断两个数的符号是否相等

return ((a >> 31) ^ (b >> 31)) == 0;

23.单引号‘ ’和双引号“ ”的区别

单引号是char类型,双引号是String类型

char表示字符,定义时使用用单引号表示,只能存储一个字符。

String表示字符串,定义时使用双引号表示,可以存储0个或多个字符,其实string类型就是char类型的数组表现形式。

char s = 'a';
char s = 'abc';//定义时会报错
 
String m = "a";
String m = 'a';//定义时会报错
 
char n[] = "abc";//String类型是char类型的数组表现形式

24.java三种移位云算法

  1. “<<” : 左移运算符,等同于乘2的n次⽅
  2. “>>”: 右移运算符,等同于除2的n次⽅
  3. “>>>” ⽆符号右移运算符,不管移动前最⾼位是0还是1,右移后左侧产⽣的空位部分都以0来填充。与jk类似。 例: int a = 16; int b = a << 2;//左移2,等同于16 * 2的2次⽅,也就是16 * 4 int c = a >> 2;//右移2,等同于16 / 2的2次⽅,也就是16 / 4
    by the way:java里没有“<<<”
    在这里插入图片描述输出是这样的,比较特别的是第二个,因为二进制存储的是反码的补码,所以有很多个1,一旦最前面的符号位变成0,就变成了很多个1的整数,转换成10进制就变得这么大了。
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值