JavaSE(7)-万字带你深入了解Java中的数组

目录

数组的基本概念

什么是数组

数组的创建及初始化

数组的使用 

数组是引用类型 

初始JVM的内存分布

基本类型变量与引用类型变量的区别

再谈引用变量

认识 null

补充

数组的应用

保存数据

作为函数的参数

作为函数的返回值

数组拓展

数组转字符串

数组拷贝

查找数组中指定元素(顺序查找)

查找数组中指定元素(二分查找)

介绍几种Arrays的方法

冒泡排序

数组逆序

二维数组

概念

语法

使用

升华


数组的基本概念

什么是数组

概念

数组可以看成是相同类型元素的一个集合。它在内存中是一段连续存放的空间。

注意
  • 数组中存放的元素其类型相同
  • 数组元素的空间是连在一起的
  • 数组每个元素的空间有自己的编号,初始位置的编号为0,即数组的下标。
那在程序中如何创建数组呢?

数组的创建及初始化

数组的创建
Type[] 数组名 = new Type[N];
  • Type:表示数组中存放元素的类型
  • Type[]:表示数组的类型
  • N:表示数组的长度(元素个数)   
//创建数组示例:
int[] array1 = new int[10]; // 创建一个可以容纳10个int类型元素的数组
double[] array2 = new double[5]; // 创建一个可以容纳5个double类型元素的数组
String[] array3 = new double[3]; // 创建一个可以容纳3个字符串元素的数组

数组的初始化
1. 动态初始化:在创建数组时,直接指定数组中元素的个数
int[] array = new int[10];
2. 静态初始化:在创建数组时不直接指定数据元素个数,而直接将具体的数据内容进行指定
语法格式: Type[] 数组名称 =new Type[] {data1, data2, data3, ..., dataN};
静态初始化的赋值只有这一次直接赋值的机会。
int[] array1 = new int[]{0,1,2,3,4,5,6,7,8,9};
double[] array2 = new double[]{1.0, 2.0, 3.0, 4.0, 5.0};
String[] array3 = new String[]{"hell", "Java", "!!!"};
【注意事项】
  • 静态初始化虽然没有指定数组的长度,编译器在编译时会根据{}中元素个数来确定数组的长度。
  • 静态初始化时, {}中数据类型必须与[]前数据类型一致。
  • 静态初始化可以简写,省去后面的new Type[]
    // 注意:虽然省去了new Type[], 但是编译器编译代码时还是会还原
    int[] array1 = {0,1,2,3,4,5,6,7,8,9};
    double[] array2 = {1.0, 2.0, 3.0, 4.0, 5.0};
    String[] array3 = {"hello", "World", "!!!"};
  • 数组也可以按照C语言创建(如int arr[]={1,2,3}),不推荐。因为容易误认为数组的类型是Type,实际上是Type[]。
  • 如果不确定数组当中内容时,使用动态初始化,否则建议使用静态初始化。
  • 静态和动态初始化也可以分为两步
//动态初始化
   int[] array1;
   array1 = new int[10];
//静态初始化
   int[] array2;
   array2 = new int[]{10, 20, 30};
// 注意省略格式不可以拆分, 否则编译失败
// int[] array3;
// array3 = {1, 2, 3}
  • 如果没有对数组进行初始化,数组中元素赋值为默认值
  • 如果数组中存储元素类型为基本数据类型,默认值为其对应的默认值
  •  如果数组中存储元素类型为引用类型,默认值为null

数组的使用 

数组中元素访问

数组在内存中是一段连续的空间,空间的编号都是从0开始的,依次递增,该编号称为数组的下标,数组可以通过下标访问其任意位置的元素。

int[]array = new int[]{10, 20, 30};
System.out.println(array[0]);
System.out.println(array[1]);
System.out.println(array[2]);
// 也可以通过下标引用操作符[]对数组中的元素进行修改
array[0] = 100;
System.out.println(array[0]);

注意事项

  • 数组是一段连续的内存空间,因此支持随意访问,即通过下标访问快速访问数组中任意位置的元素
  • 下标从0开始,介于[0, N)之间不包含N,N为元素个数,下标访问不能越界,否则会报出下标越界异常(java.lang.ArrayIndexOutOfBoundsException)。
int[] array = {1, 2, 3};
System.out.println(array[3]); // 数组中只有3个元素,下标一次为:0 1 2,array[3]下标越界
// 执行结果
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 100
at Test.main(Test.java:4)
 

遍历数组

所谓 "遍历" 是指将数组中的所有元素都访问一遍,访问是指对数组中的元素进行某种操作,比如对数组的元素进行打印。如果对数组中每个元素的操作都是相同的,则可以使用循环来进行打印。
  • 在数组中可以通过 数组对象.length 来获取数组的长度,实现循环遍历数组,代码示例如下
int[]array = new int[]{10, 20, 30, 40, 50};
for(int i = 0; i < array.length; i++){
System.out.println(array[i]);
}
  • 使用 for-each 遍历数组,for-each 是 for 循环的升级版,obj会将数组里面的每个元素赋值给x

for-each的语句格式:

for(元素类型type  元素变量x: 遍历对象obj) { 
    引用x的java语句;
}

使用示例(遍历array数组,分行打印123):

int[] array = {1, 2, 3}; 
for (int x : array) {
     System.out.println(x+""); 
}

如何直接打印整个数组

import java.util.Arrays;
public class test {
    public static void main(String[] args) {
        int[] array={1,2,3,4,5,6};
        System.out.println(Arrays.toString(array));
    }
}

运行结果如下图所示:

 Arrays是一个工具类,主要作用是用来操作数组。而为什么打印出来会有[]和逗号呢,这是由方法内部进行了这样的组装。

数组是引用类型 

初始JVM的内存分布

内存是一段连续的存储空间,主要用来存储程序运行时数据的。比如:
  • 程序运行时代码需要加载到内存
  • 程序运行产生的中间数据要存放在内存
  • 程序中的常量也要保存
  • 有些数据可能需要长时间存储,而有些数据当方法运行结束后就要被销毁
如果对内存中存储的数据不加区分的随意存储,那会非常的混乱,对内存的管理也将会非常麻烦。因此JVM对所使用的内存按照功能的不同进行了划分,以此对内存管理就更加便捷:
  • 程序计数器:程序计数器是用于存放下一条指令所在单元的地址的地方。
  • 虚拟机栈: 存放与方法调用相关的一些信息。每个方法在执行时,都会先在虚拟机栈中创建一个栈帧,栈帧中包含有局部变量表、操作数栈、动态链接、返回地址以及其他的一些信息,保存的都是与方法执行时相关的一些信息。比如:局部变量。当方法运行结束后,栈帧就被销毁了,即栈帧中保存的数据也被销毁了。
  • 本地方法栈:本地方法栈与虚拟机栈的作用类似,只不过保存的内容是Native方法的局部变量。运行的是一些由C/C++编写的软件。在有些版本的 JVM 实现中(如HotSpot),本地方法栈和虚拟机栈是一起的。
  • 堆: JVM所管理的最大内存区域。使用 new 创建的对象都是在堆上保存 (例如前面的 new int[]{1, 2, 3} ),堆是随着程序开始运行时而创建,随着程序的退出而销毁,堆中的数据只要还有在使用,就不会被销毁。堆上开辟的内存,不像C语言一样需要我们手动释放。一旦不被使用了,就被垃圾回收器自动识别回收掉了。
  • 方法区: 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法编译出的的字节码就是保存在这个区域
目前阶段我们只需要关心堆和虚拟机栈这两块空间,后序学习JVM会更详细的介绍。

基本类型变量与引用类型变量的区别

  • 基本数据类型创建的变量,称为基本变量,该变量空间中直接存放的是其所对应的值
  • 而引用数据类型创建的变量,一般称为对象的引用,其空间中存储的是对象所在空间的地址
public static void func() {
        int a = 10;
        int b = 20;
        int[] array = new int[]{1,2,3};
    }
上述代码中a、b、array都是函数内部的变量,因此其空间都在main方法对应的栈帧中分配。 a,b是内置类型的变量,因此其空间中保存的就是给该变量初始化的值。array是数组类型的引用变量,其内部保存的内容可以简单理解成是数组在堆空间中的首地址。其在内存中的情况如下图所示:
从上图可以看到,引用变量array并不直接存储数组对象本身, 可以简单理解成存储的是对象在堆中空间的起始地址。通过该地址,引用变量便可以去操作对象 。有点类似C语言中的指针,但是Java中引用要比指针的操作更简单。

再谈引用变量

public static void func() {
        int[] array1 = new int[3];
        array1[0] = 10;
        array1[1] = 20;
        array1[2] = 30;
        int[] array2 = new int[]{1,2,3,4,5};
        array2[0] = 100;
        array2[1] = 200;
        array1 = array2;
        array1[2] = 300;
        array1[3] = 400;
        array2[4] = 500;
        for (int i = 0; i < array2.length; i++) {
            System.out.println(array2[i]);
        }
}
我们来分析一下这段代码,如下图所示:
array1=array2不能说成array1指向了array2,而是array1引用了array2指向的对象,引用是不能指向引用的。

认识 null

null 在 Java 中表示 "空引用" ,也就是一个不指向任何对象的引用。

int[] arr = null;
        System.out.println(arr[0]);
// 执行结果
        Exception in thread "main" java.lang.NullPointerException
        at Test.main(Test.java:6)
null 的作用类似于 C 语言中的 NULL (空指针), 都是表示一个无效的内存位置。因此不能对这个内存进行任何读写操作。
一旦尝试读写,就会抛出 NullPointerException 空指针异常。
注意: Java 中并没有约定 null 和 0 号地址的内存有任何关联。

补充

  • 引用不能指向引用
  • 名词的引用是一个变量,里面存的是堆里面的对象的地址。例如int [] array1={1,2,3,4};可以理解为array1这个引用指向/引用{1,2,3,4}对象。
  • 动词的引用 和 指向 是一个意思
  • 一个引用不能同时指向多个对象
  • 引用不一定在栈上,目前还没接触成员变量
  • 对引用赋值null代表整个引用不指向任何对象

数组的应用

保存数据

public static void main(String[] args) {
            int[] array = {1, 2, 3};
            for(int i = 0; i < array.length; ++i){
                System.out.println(array[i] + " ");
            }
}

作为函数的参数

1. 参数传基本数据类型

    public static void main(String[] args) {
        int num = 0;
        func(num);
        System.out.println("num = " + num);
    }
    public static void func(int x) {
        x = 10;
        System.out.println("x = " + x);
    }
// 执行结果
    x = 10
    num = 0

我们发现在func方法中修改形参 x 的值,不会影响实参的 num 的值。形参是实参的一份拷贝,形参只有在方法在被调用的时候才会实例化,即只有被调用的时候才会开辟空间。穿基类的时候,可以理解为C语言的传值调用,但在Java中本质上传的都是值。


2. 参数传数组类型(引用数据类型)

  public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        func(arr);
        System.out.println("arr[0] = " + arr[0]);
    }
    public static void func(int[] a) {
        a[0] = 10;
        System.out.println("a[0] = " + a[0]);
    }
// 执行结果
    a[0] = 10
    arr[0] = 10

发现在func方法内部修改数组的内容,方法外部的数组内容也发生改变。因为数组是引用类型,按照引用类型来进行传递,是可以修改其中存放的内容的。本质上来说,是因为a和arr引用都是指向堆里面同一个数组对象,他们所引用的地址是同一个空间。在java中传引用类型可以理解为C语言的传址调用。


3.总结
所谓的 "名词引用" 本质上只是存了一个地址,Java 将数组设定成引用类型, 这样的话后续进行数组参数传参,其实只是将数组的地址传入到函数形参中。这样可以避免对整个数组的拷贝(数组可能比较长, 那么拷贝开销就会很大)。

作为函数的返回值

获取斐波那契数列的前N项
    public class TestArray {
        public static int[] fib(int n){
            if(n <= 0){
                return null;
            }
            int[] arr = new int[n];
            arr[0] = arr[1] = 1;
            for(int i = 2; i < n; ++i){
                arr[i] = arr[i-1] + arr[i-2];
            }
            return arr;
        }
        public static void main(String[] args) {
            int[] array = fib(10);
            for (int i = 0; i < array.length; i++) {
                System.out.println(array[i]);
            }
        }
    }

我们来分析这个程序,首先,fib方法的返回值是数组类型,参数是int类型。如果n<=0,则说明传入的斐波那契项数不合法,返回一个null。如果合法,则在堆上创建一个int[n],在java中,数组中的元素个数可以是变量。然后给数组里面的每个元素依次遍历,按斐波那契数列的规则进行赋值,最后返回array。值得一提的是:虽说fib方法在return的时候,在栈上开辟的数据销毁掉了,但在堆上开辟的数据是没有销毁的,所以能够让array引用arr指向的在堆上开辟的数组对象。

数组拓展

数组转字符串

示例代码

import java.util.Arrays;
public class test {
    public static void main(String[] args) {
        int[] arr = {1,2,3,4,5,6};
        String newArr = Arrays.toString(arr);
        System.out.println(newArr);
    }
}
// 执行结果:[1, 2, 3, 4, 5, 6]
使用toString方法后续打印数组就更方便一些。Java 中提供了 java.util.Arrays 包,其中包含了一些操作数组的常用方法。

实现自己版本的数组转字符串

 public static String myToString(int[] array) {
        String str = "[";
        for (int i = 0;  i < array.length; i++) {
            str = str+array[i];
            if(i != array.length-1) {
                str += ",";
            }
        }
        str += "]";
        return str;
    }

    public static void main12(String[] args) {
        int[] array = {1,2,3,4};
        String ret = myToString(array);
        System.out.println(ret);
    }

数组拷贝

for循环拷贝

public static void main13(String[] args) {
        int[] array = {1,2,3,4};
        int[] copy = new int[array.length];
        for (int i = 0; i < array.length; i++) {
            copy[i] = array[i];
        }
        System.out.println(Arrays.toString(copy));
}
for循环的代码显得有些繁琐,还有没有其他实现方式呢?

代码示例

import java.util.Arrays;
public static void func(){
        int[] arr = {1,2,3,4,5,6};
        int[] newArr = arr;
        newArr[0] = 10;
        System.out.println("newArr: " + Arrays.toString(arr));

newArr和arr引用的是在堆上的同一个数组对象,因此newArr修改空间中内容之后,arr也可以看到修改的结果。但这不能说是拷贝,因为没有拷贝空间。


Array.copyof(你要拷贝的数组,新的长度)

public static void main14(String[] args) {
        int[] array = {1,2,3,4};
        //虽然是发生了拷贝  其实 也可以看做是 扩容
        int[] copy = Arrays.copyOf(array,2*array.length);
        System.out.println(Arrays.toString(copy));
    }

Array.copyOfRange(你要拷贝的数组,拷贝下标起始位置,拷贝结束位置)

Array.copyOfRange拷贝的范围是一个左闭右开[)区间


System.arraycopy(原数组,拷贝的原数组初始下标,目地的数组,拷贝的目的地下标,拷贝长度)  底层是C/C++代码写的,性能更高

public static void main15(String[] args) {
        int[] array = {1,2,3,4};
        int[] copy = new int[array.length];

        System.arraycopy(array,0,copy,0,array.length);

        System.out.println(Arrays.toString(copy));

    }

数组名.clone()  这个是最常用的

public static void main(String[] args) {
        int[] array = {1,2,3,4};
        int[] copy = array.clone();
        System.out.println(Arrays.toString(copy));
        System.out.println(array);
        System.out.println(copy);
    }
注意:数组当中存储的是基本类型数据时,不论怎么拷贝基本都不会出现什么问题,但如果存储的是引用数据类型, 拷贝时需要考虑深浅拷贝的问题,关于深浅拷贝在后续详细给大家介绍

查找数组中指定元素(顺序查找)

给定一个数组, 再给定一个元素,找出该元素在数组中的下标

  public static void main(String[] args) {
        int[] arr = {1,2,3,10,5,6};
        System.out.println(find(arr, 10));
    }
    public static int find(int[] arr, int data) {
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] == data) {
                return i;
            }
        }
        return -1; // 表示没有找到
    }
// 执行结果
3

这种遍历写法固然简洁易懂,但如果是在一亿个数字里面找一个数字呢?那无疑是海里捞针,在特定的情况下,我们可以用二分查找。

查找数组中指定元素(二分查找)

概念
我们针对有序数组, 可以使用更高效的二分查找:
何谓有序数组?
有序分为 "升序" 和 "降序":
如 1 2 3 4 ,依次递增即为升序。
如 4 3 2 1 ,依次递减即为降序。
以升序数组为例,二分查找的思路是先取中间位置的元素,然后使用待查找元素与数组中间元素进行比较:
如果相等,即找到了返回该元素在数组中的下标
如果小于,以类似方式到数组左半侧查找
如果大于,以类似方式到数组右半侧查找
随着数组元素个数越多,二分查找的优势就越大。

示例代码

public static void main(String[] args) {
        int[] arr = {1,2,3,4,5,6};
        System.out.println(binarySearch(arr, 6));
        }
public static int binarySearch(int[] arr, int toFind) {
        int left = 0;
        int right = arr.length - 1;
        while (left <= right) {
        int mid = (left + right) / 2;
        if (toFind < arr[mid]) {
// 去左侧区间找
        right = mid - 1;
        } else if (toFind > arr[mid]) {
// 去右侧区间找
        left = mid + 1;
        } else {
// 相等, 说明找到了
        return mid;
        }
        }
// 循环结束, 说明没找到
        return -1;
        }
// 执行结果
        5

介绍几种Arrays的方法

排序Arrays.sort(数组名)

这个方法可以帮我们的数组进行排序,默认是升序。目前的知识储备我们无法实现用这个方法对简单数据类型进行降序。想要实现指定的升序和降序我们需要学到接口才能实现。只有当我们的排序的数据是引用类型数据的时候,才能做到指定的升序和降序,具体的做法在抽象类和接口章将会介绍。

public static void main(String[] args) {
       int[] arr = {9, 5, 2, 7};
       Arrays.sort(arr);
       System.out.println(Arrays.toString(arr));
}

Arrays.binarySearch(数组名,查找元素)


直接查找有序数组的下标。如果找不回,就会返回-(最后一次low的位置+1)

public static void main(String[] args) {
       int[] arr = {9, 5, 2, 7};
       Arrays.sort(arr);//排序
       Arrays.binarySearch(arr,9)//排序后查找
}

Arrays.equals(数组名1,数组名2)

判断两个数组内容是否相同,如果相等返回true,否则返回false。(两个数组类型必须相同)

  public static void main2(String[] args) {
        int[] array1 = {1,2,31,14,5};
        int[] array2 = {1,2,3,14,5};
        boolean flg = Arrays.equals(array1,array2);
        System.out.println(flg);
  }

Arrays.fill

这个方法的作用是对数组进行填充

①Arrays.fill(数组名,填充内容),这样的写法默认是全部填充

②Arrays.fill(数组名,起始下标,结束下标,填充内容),这样的写法则是选择范围进行填充,这个范围也是左闭右开区间。

        int[] array = new int[10];
        System.out.println("填充之前的数组:"+Arrays.toString(array));
        Arrays.fill(array,0,array.length,19);
        System.out.println("填充之后的数组:"+Arrays.toString(array));

冒泡排序

需求

给定一个数组,让数组升序 (降序) 排序。

算法思路

假设排升序:
1. 将数组中相邻元素从前往后依次进行比较,如果前一个元素比后一个元素大,则交换两个元素的位置,一趟下来后最大元素就在数组的末尾
2. 依次从上上述过程,直到数组中所有的元素都排列好
如果我们数组中存放的值是9 8 7 6 5 4 3 2 1 0共10个元素,我们需要先拿9和8比较,如果9>8则交换位置,然后拿9和7比较,如果9>7则交换位置…直到9和全部数都比较完之后,这一趟冒泡排序就结束了。然后我们思考,10个数字,就要进行9次冒泡排序,因为最后一个数必然是最小的,不需要再排序了。所以冒泡排序的趟数等于元素个数-1。然后我们分析冒泡排序的内部,第一次冒泡排序的时候,有10个数字,所以要拿第一个元素和其余9个元素进行比较,然后交换;第二次冒泡排序的时候,拿第一个元素和其余8个元素进行比较…也就是说,冒泡排序的判断次数应该是小于元素个数-1然后再减去趟数的,因为进行的趟数就是不需要再判断的元素的个数。思路大致如此,于是我们还应考虑效率问题,如果数组内存的值为1 2 3 4 5 6 7 8 9这10个数,显然这是已经排序好了的,可是我们的程序如果只是这样的话,它还是会不断的判断,只是不会进行交换了,但这大大的降低了我们程序的效率。于是我们抓住关键点:已经排序好了的数组不会再进行交换了,也就是不会再进入交换的判断部分了,于是我们可以写一个标记变量赋值为1,如果发生了交换,则标记变量的值改变为0,然后如果程序运行到交换部分,没有发生交换,则break;
示例代码
   public static void bubbleSort(int[] array) {
        //1、 [0---len-1)  i代表的是趟数
        for (int i = 0; i < array.length-1; i++) {
            //3、
            boolean flg = false;
            //2、j < 4  在这里 可以不减i  减i 说明就是进行了优化
            for (int j = 0; j < array.length-1-i; j++) {
                if(array[j] > array[j+1]) {
                    int tmp = array[j];
                    array[j] = array[j+1];
                    array[j+1] = tmp;
                    flg = true;
                }
            }
            if(flg == false) {
                break;
            }
        }
    }

数组逆序

需求

给定一个数组, 将里面的元素逆序排列
思路
设定两个下标,分别指向第一个元素和最后一个元素。交换两个位置的元素。
然后让前一个下标自增,后一个下标自减,循环继续即可。
示例代码
public static void main(String[] args) {
        int[] arr = {1, 2, 3, 4};
        reverse(arr);
        System.out.println(Arrays.toString(arr));
    }
    public static void reverse(int[] arr) {
        int left = 0;
        int right = arr.length - 1;
        while (left < right) {
            int tmp = arr[left];
            arr[left] = arr[right];
            arr[right] = tmp;
            left++;
            right--;
        }
}

二维数组

概念

二维数组本质上也就是一维数组,只不过每个元素又是一个一维数组

语法

以下是几种创建二位数组的方式:

   public static void main1(String[] args) {
        int[][] array = {{1,2,3},{4,5,6}};
        int[][] array2 = new int[][]{{1,2,3},{4,5,6}};
        int[][] array3 = new int[2][3];
    }

使用

  public static void main2(String[] args) {
        int[][] array = {{1,2,3},{4,5,6}};
        for (int i = 0; i < array.length; i++) {//使用for循环打印
            for (int j = 0; j < array[i].length; j++) {
                System.out.print(array[i][j] +" ");
            }
            System.out.println();
        }
        System.out.println("使用foreach来进行打印:");
        for(int[] tmp : array) {
            for(int x : tmp) {
                System.out.print(x+" ");
            }
            System.out.println();
        }
        System.out.println("使用toString方法进行打印:");
        System.out.println(Arrays.deepToString(array));
    }

升华

    public static void main(String[] args) {
        int[][] array = {{1,2},{4,5,6}};
        int[][] array2 = new int[2][];
//二位数组的创建列可以省略,这里默认创建两行,两行存的都是null
//然后我们可以创建列,由于内存不连续,所以可以创建不同的列数
        array2[0] = new int[2];
        array2[1] = new int[4];

        for (int i = 0; i < array2.length; i++) {
            for (int j = 0; j < array2[i].length; j++) {
                System.out.print(array2[i][j] +" ");
            }
            System.out.println();
        }
    
二维数组的用法和一维数组并没有明显差别,因此我们不再赘述。
同理,还存在 "三维数组","四维数组" 等更复杂的数组,只不过出现频率都很低。

 本章完,愿你有所收获,如果再也不能见到你,也祝你早安午安,还有晚安。

  • 21
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 36
    评论
评论 36
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

叶轻衣。

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值