Java 常用的算法、集合数据结构及排序

一、Collection

1.1 List

ArrayList 和 LinkedList都是List类型,它们都是按照被插入的顺序保存元素,可以插入重复的元素; 

  • 需要进行大量的随机访问使用ArrayList;
  • 需要经常从表中插入和删除元素,应该使用LinkedList;

1.2 Set

用于存储无序(存入和取出的顺序不一定相同)元素,值不能重复。

HashSet、TreeSet、LinkedHashSet都是Set类型,每个相同的项只保存一次(元素不重复)。

  • HashSet使用的是散列函数,它 获取元素的方式是最快的,元素存放顺序是无序的。
  • TreeSet  底层实现的数据结构是红黑二叉树,可以有序的存放元素,需要被排序的元素或类实现Comparable<T>接口。
  • LinkedHashSet使用链表来维护元素的插入顺序,按照被添加的顺序保存对象。

1.3 Map

HashMap、TreeMap、LinkedHashMap、ConcurrentHashMap 都是Map类型,存放键值对。

  • HashMap与HashSet一样,提供了最快的查找技术,保存元素是无序的。
  • TreeMap底层实现的数据结构是红黑二叉树,默认按照比较结果的升序保存键,需要被排序的元素或类实现Comparable<T>接口。
  • LinkedHashMap按照插入顺序保存键。
  • HashMap是非synchronized,而Hashtable是synchronized,这意味着Hashtable是线程安全的,多个线程可以共享一个Hashtable;而如果没有正确的同步的话,多个线程是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。

1.4 Queue和Stack

LinkedList具有直接实现栈的所有功能的方法,它的底层实现是一个双向链表,因此可以直接将LinkedList作为栈使用;LinkedList也提供了方法以支持队列的行为,因此LinkedList可以作为Queue的一种实现。所以各种Queue以及栈的行为,由LinkedList提供支持。

常用API举例:

Collection<String> c = new ArrayList<String>(){{add("a");add("b");add("c");add("d");}};

System.out.println("max value:" + Collections.max(c));// 最大值 d

System.out.println("min value:" + Collections.min(c));// 最小值 a

List<String> newList = ((ArrayList<String>) c).subList(1,3);// 集合截取

Object[] array = c.toArray(); // 集合转换为数组

List<Object> transferList = Arrays.asList(array);// 数组转换为List集合

二、集合排序

2.1 List集合的排序

java提供了两种排序方式,分别是Collections.sort(List)和Collections.sort(List,Commparator)。示例如下:

        List<Integer> nameList = Arrays.asList(9,5,2,4,10,7,36);
   
        Collections.sort(nameList);// 升序
        for (Integer s : nameList) {
            System.out.println(s);
        }

        Collections.sort(nameList,Collections.reverseOrder());// 降序
        for (Integer s : nameList) {
            System.out.println(s);
        }

2.1.1 Collections.sort(List)

(1)如果List集合中的元素内容只有一个数据,就直接比较即可,前提是保证这个数据对应的类中有CompareTo方法;

(2)如果List集合中的元素内容有多个数据,就需要这个元素类型必须实现Comparable接口,并重写CompareTo方法,在此方法中指定排序原则。

2.1.2 Collections.sort(List,Commparator)

sort方法的参数有两个,一个是要排序的List集合,另一个参数是Comparator接口,在比较器中指定要排序的原则。
使用比较器方式就不用对要比较的集合的类类型实现Comparable接口;
可以实现多个比较器,每个比较器就是一种排序原则, 比较器的实现也有两种方式 ,如下:

 /**
     * 根据Comparator接口的子实现来指定排序的原则,策略模式
     * 按照年龄排序
     * @param stus
     */
    public void sortAge(List<Student> stus){
        Collections.sort(stus, new Comparator<Student>(){

            @Override
            public int compare(Student stu1, Student stu2) {
                // TODO Auto-generated method stub
                return stu1.getAge()-stu2.getAge();
            }

        });
        for(Student stu:stus){                   
     System.out.println("name="+stu.getName()+"age="+stu.getAge()+"stuNo="+stu.getStuNo());
        }
}
/××
×JAVA LIST 排序方法
×
××/
private class ComparatorUser implements Comparator {

        public int compare(Object arg0, Object arg1) {
            Object pd1 = (Object) arg0;
            Object pd2 = (Object) arg1;
            int flag = String.valueOf(获取pd2中依据排序字段).compareTo(String.valueOf(获取pd1中依据排序字段)); 
       return flag; 
     }
}

//调用方法
Collections.sort(sortList, new ComparatorUser());//调用

2.2 Set 集合排序

由于Set集合自身的特殊性,保存的元素都是不重复且无序的,所以Set 、HashSet 不能对存储的元素排序。

LinkedHashSet 和 TreeSet 可以实现元素的有序存储。

2.2.1 LinkedHashSet

会保存插入的顺序。

2.2.2 TreeSet

(1) 让元素自身具备比较性

也就是元素需要实现Comparable接口,覆盖compareTo 方法。这种方式也作为元素的自然排序,也可称为默认排序。

年龄按照搜要条件,年龄相同再比姓名。

class Person implements Comparable {

    private String name;
    private int age;
    private String gender;
    public Person() {
    }
    
    ...

    public int compareTo(Object obj) {
        Person p = (Person) obj;
        System.out.println(this+" compareTo:"+p);
        if (this.age > p.age) {
            return 1;
        }
        if (this.age < p.age) {
            return -1;
        }
        return this.name.compareTo(p.name);
    }
}

(2)让容器自身具备比较性,自定义比较器

需求:当元素自身不具备比较性,或者元素自身具备的比较性不是所需的。那么这时只能让容器自身具备。

定义一个类实现Comparator 接口,覆盖compare方法。并将该接口的子类对象作为参数传递给TreeSet集合的构造函数。

当Comparable比较方式,及Comparator比较方式同时存在,以Comparator比较方式为主。

public class Demo5 {
    public static void main(String[] args) {
        TreeSet ts = new TreeSet(new MyComparator());
        ts.add(new Book("think in java", 100));
        ts.add(new Book("java 核心技术", 75));
        ts.add(new Book("现代操作系统", 50));
        ts.add(new Book("java就业教程", 35));
        ts.add(new Book("think in java", 100));
        ts.add(new Book("ccc in java", 100));
        System.out.println(ts); 
    }
}
class MyComparator implements Comparator {
    public int compare(Object o1, Object o2) {
        Book b1 = (Book) o1;
        Book b2 = (Book) o2;
        System.out.println(b1+" comparator "+b2);
        if (b1.getPrice() > b2.getPrice()) {
            return 1;
        }
        if (b1.getPrice() < b2.getPrice()) {
            return -1;
        }
        return b1.getName().compareTo(b2.getName());
    }
}
class Book {
    private String name;
    private double price;
    public Book() {
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
this.name = name;
    }
    public double getPrice() {
        return price;
    }
    public void setPrice(double price) {
        this.price = price;
    }
    public Book(String name, double price) {
this.name = name;
        this.price = price;
    }
    @Override
    public String toString() {
        return "Book [name=" + name + ", price=" + price + "]";
    }
}

2.3 Map 排序

Map排序的方式有很多种,这里记录下自己总结的两种比较常用的方式:按键排序(sort by key), 按值排序(sort by value)。

2.3.1 TreeMap 排序

(1)按键排序

jdk内置的java.util包下的TreeMap<K,V>既可满足此类需求,向其构造方法 TreeMap(Comparator<? super K> comparator)  传入我们自定义的比较器即可实现按键排序。

public class MapSortDemo {
    public static void main(String[] args) {
        Map<String, String> map = new TreeMap<String, String>();
        map.put("KFC", "kfc");
        map.put("WNBA", "wnba");
        map.put("NBA", "nba");
        map.put("CBA", "cba");

        Map<String, String> resultMap = sortMapByKey(map);    //按Key进行排序
        for (Map.Entry<String, String> entry : resultMap.entrySet()) {
            System.out.println(entry.getKey() + " " + entry.getValue());
        }
    }

    /**
     * 使用 Map按key进行排序
     * @param map
     * @return
     */
    public static Map<String, String> sortMapByKey(Map<String, String> map) {
        if (map == null || map.isEmpty()) {
            return null;
        }

        Map<String, String> sortMap = new TreeMap<String, String>(new MapKeyComparator());
        sortMap.putAll(map);
        return sortMap;

       }
}

// 比较器类
class MapKeyComparator implements Comparator<String>{
    @Override
    public int compare(String str1, String str2) {
           return str1.compareTo(str2);
    }
}

(2)按值排序

Map本身按值排序是很有意义的,很多场合下都会遇到类似需求,可以认为其值是定义的某种规则或者权重。原理:将待排序Map中的所有元素置于一个列表中,接着使用Collections的一个静态方法 sort(List<T> list, Comparator<? super T> c) 
来排序列表,同样是用比较器定义比较规则。排序后的列表中的元素再依次装入Map,为了肯定的保证Map中元素与排序后的List中的元素的顺序一致,使用了LinkedHashMap数据类型。

public class MapSortDemo {
    public static void main(String[] args) {
        Map<String, String> map = new TreeMap<String, String>();
        map.put("KFC", "kfc");
        map.put("WNBA", "wnba");
        map.put("NBA", "nba");
        map.put("CBA", "cba");

  //      Map<String, String> resultMap = sortMapByKey(map);    //按Key进行排序
        Map<String, String> resultMap = sortMapByValue(map); //按Value进行排序
 
        for (Map.Entry<String, String> entry : resultMap.entrySet()) {
            System.out.println(entry.getKey() + " " + entry.getValue());
        }
    }


    /**
     * 使用 Map按value进行排序
     * @param map
     * @return
     */
    public static Map<String, String> sortMapByValue(Map<String, String> oriMap) {
        if (oriMap == null || oriMap.isEmpty()) {
            return null;
        }
        Map<String, String> sortedMap = new LinkedHashMap<String, String>();
        List<Map.Entry<String, String>> entryList = new ArrayList<Map.Entry<String, String>>(
        oriMap.entrySet());
        Collections.sort(entryList, new MapValueComparator());

        Iterator<Map.Entry<String, String>> iter = entryList.iterator();
        Map.Entry<String, String> tmpEntry = null;
        while (iter.hasNext()) {
            tmpEntry = iter.next();
            sortedMap.put(tmpEntry.getKey(), tmpEntry.getValue());
        }
        return sortedMap;
    }
}

// 比较器类
class MapValueComparator implements Comparator<Map.Entry<String, String>> {
    @Override
    public int compare(Entry<String, String> me1, Entry<String, String> me2) {
        return me1.getValue().compareTo(me2.getValue());
    }
}

(3)总结

看到array,就要想到角标。

看到link,就要想到first,last。

看到hash,就要想到hashCode,equals.

看到tree,就要想到两个接口。Comparable,Comparator。

三、集合遍历

3.1遍历Map

在Java中,使用迭代器遍历Map并移除不满足条件的元素时,需通过迭代器的remove()方法操作,避免直接修改Map导致ConcurrentModificationException异常。以下是具体实现方法:

步骤说明

  1. 获取Map的条目(Entry)迭代器
    通过map.entrySet().iterator()获取键值对的迭代器,直接操作条目。

  2. 遍历并检查条件
    使用while循环逐个检查条目,判断是否需要移除。

  3. 调用迭代器的remove()方法
    当条件满足时,通过迭代器删除当前条目,而非直接操作Map

代码示例

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class MapIteratorRemove {
    public static void main(String[] args) {
        // 初始化Map
        Map<String, Integer> map = new HashMap<>();
        map.put("A", 1);
        map.put("B", 2);
        map.put("C", 3);
        map.put("D", 4);

        // 获取Entry迭代器
        Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator();

        // 遍历并移除值为奇数的条目
        while (iterator.hasNext()) {
            Map.Entry<String, Integer> entry = iterator.next();
            if (entry.getValue() % 2 != 0) {
                iterator.remove(); // 关键:通过迭代器删除
            }
        }

        // 输出结果:{B=2, D=4}
        System.out.println(map);
    }
}

关键点解析

  • 迭代器来源
    必须通过map.entrySet().iterator()获取条目迭代器,而非keySet()values()的迭代器,否则无法直接删除整个键值对。

  • remove()的作用域
    iterator.remove()会删除当前迭代到的条目(即最后一次调用next()返回的条目)。

  • 避免直接操作Map
    若在遍历过程中使用map.remove(key),会触发并发修改异常,必须通过迭代器删除。

Java 8+简化方案(推荐)

使用removeIf方法结合Lambda表达式,代码更简洁:

map.entrySet().removeIf(entry -> entry.getValue() % 2 != 0);

常见错误示例

// 错误!直接调用Map.remove()会导致ConcurrentModificationException
for (Map.Entry<String, Integer> entry : map.entrySet()) {
    if (entry.getValue() % 2 != 0) {
        map.remove(entry.getKey()); // 抛出异常
    }
}

总结

方法适用场景优点缺点
传统迭代器Java 7及以下显式控制删除逻辑代码稍冗长
removeIf+LambdaJava 8及以上代码简洁,一行完成需熟悉函数式编程

根据项目环境选择合适方法,核心原则是通过迭代器或removeIf安全删除元素

四、数组转List的三种方式及对比

1)最常见方式(未必最佳)

通过 Arrays.asList(strArray) 方式,将数组转换List后,不能对List增删,只能查改,否则抛异常。关键代码:List list = Arrays.asList(strArray);

private void testArrayCastToListError() {
  String[] strArray = new String[2];
  List list = Arrays.asList(strArray);
  //对转换后的list插入一条数据
  list.add("1");
  System.out.println(list);
 }

执行结果

Exception in thread "main" java.lang.UnsupportedOperationException
 at java.util.AbstractList.add(AbstractList.java:148)
 at java.util.AbstractList.add(AbstractList.java:108)
 at com.darwin.junit.Calculator.testArrayCastToList(Calculator.java:19)
 at com.darwin.junit.Calculator.main(Calculator.java:44)

程序在list.add(“1”)处,抛出异常:UnsupportedOperationException。

原因解析:

Arrays.asList(strArray)返回值是java.util.Arrays类中一个私有静态内部类java.util.Arrays.ArrayList,它并非java.util.ArrayList类。java.util.Arrays.ArrayList类具有 set(),get(),contains()等方法,但是不具有添加add()或删除remove()方法,所以调用add()方法会报错。

使用场景:Arrays.asList(strArray)方式仅能用在将数组转换为List后,不需要增删其中的值,仅作为数据源读取使用。

(2)数组转为List后,支持增删改查的方式

通过ArrayList的构造器,将Arrays.asList(strArray)的返回值由java.util.Arrays.ArrayList转为java.util.ArrayList

关键代码:ArrayList<String> list = new ArrayList<String>(Arrays.asList(strArray)) ;

private void testArrayCastToListRight() {
  String[] strArray = new String[2];
  ArrayList<String> list = new ArrayList<String>(Arrays.asList(strArray)) ;
  list.add("1");
  System.out.println(list);
 }

使用场景:需要在将数组转换为List后,对List进行增删改查操作,在List的数据量不大的情况下,可以使用。

(3)通过集合工具类Collections.addAll()方法(最高效)

通过Collections.addAll(arrayList, strArray)方式转换,根据数组的长度创建一个长度相同的List,然后通过Collections.addAll()方法,将数组中的元素转为二进制,然后添加到List中,这是最高效的方法。

private void testArrayCastToListEfficient(){
  String[] strArray = new String[2];
  ArrayList< String> arrayList = new ArrayList<String>(strArray.length);
  Collections.addAll(arrayList, strArray);
  arrayList.add("1");
  System.out.println(arrayList);
 }

使用场景:需要在将数组转换为List后,对List进行增删改查操作,在List的数据量巨大的情况下,优先使用,可以提高操作速度。

(4)问题:数组类型如果是整型数组,转为List时,会报错?那么在声明数组时,用int[] 还是Integer[],哪种声明方式才能正确的转为List呢?
答案: 只能用Integer[]List<Integer>,即只能用基本数据类型的包装类型,才能直接转为List

  • 述源码中可以看出,List声明时,需要传递一个泛型<E>作为形参,asList()参数类型也是泛型中的通配类型<T>。Java中所有的泛型必须是引用类型。

  • 什么是引用类型?Integer是引用类型,那int是什么类型?int是基本数据类型,不是引用类型。这就是为什么java中没有List<int>,而只有List<Integer>

  • 举一反三:其他8种基本数据类型byte、short、int、long、float、double、char也都不是引用类型,所以8种基本数据类型都不能作为List的形参。但String、数组、class、interface是引用类型,都可以作为List的形参,所以存在List<Runnable>接口类型的集合、List<int[]>数组类型的集合、List<String>类的集合。但不存在list<byte>list<short> 等基本类型的集合。

  • 为什么int[]不能直接转换为List<Integer>,而Integer[]就可以转换为List<Integer>了吧。因为List中的泛型必须是引用类型,int是基本数据类型,不是引用类型,但int的包装类型Integerclass类型,属于引用类型,所以Integer可以作为List形参,List<Integer>在java中是可以存在的,但不存在List<int>类型。

    在编码时,我们不光要知其然,还要知其所以然,通过分析JDK源码,才能得出一手信息,不仅了解到了如何用,还能得出为何这样用。

五、常用排序算法实现

5.1  插入排序(InsertionSort)

一般也被称为直接插入排序。

(1)对于少量元素的排序,它是一个有效的算法。插入排序是一种最简单的排序方法,它的基本思想是将一个记录插入到已经排好序的有序表中,从而一个新的、记录数增 1 的有序表

。在其实现过程使用双层循环,外层循环对除了第一个元素之外的所有元素,内层循环对当前元素前面有序表进行待插入位置查找,并进行移动。

(2)插入排序的平均时间复杂度也是 O(n^2),空间复杂度为常数阶 O(1),具体时间复杂度和数组的有序性也是有关联的。

插入排序中,当待排序数组是有序时,是最优的情况,只需当前数跟前一个数比较一下就可以了,这时一共需要比较 N-1 次,时间复杂度为 O(N)。最坏的情况是待排序数组是逆序的,此时需要比较次数最多,最坏的情况是 O(n^2)。

(3)代码实现

/**
 * 插入排序
 *
 * @author yahui.xu
 * @date 2021/9/7 17:34
 */
public class InsertionSort {

    public static void main(String[] args) {
        Integer[] data = new Integer[]{5,10,8,6,9,1,7};
        sort(data);
        Arrays.stream(data).forEach(one -> System.out.println(one));
    }

    public static void sort(Comparable[] arr){
        int num = arr.length;
        for(int i=0;i<num;i++){
            for(int j = i;j>0;j--){
                if(arr[j].compareTo(arr[j-1])<0)
                    swapItem(arr,j,j-1);
                else
                    break;
            }
        }
    }

    public static void swapItem(Object[] arr,int i,int j){
        Object temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

}

六、集合数据合并(合计)

下面代码实现的是:将一个list的根据能源类型code分组之后,将其key的每个对象的能耗值累加到第一个元素的能耗值上,并且返回。其实就是一个数据合计的算法。

/**
     *  @author: xuyahui
     *  @Date: 2021/10/16 16:38
     *  @Description: 合计对象属性值
     */
    public static List<ConsumeVo> merge(List<ConsumeVo> consumeVoList){
        return consumeVoList.stream().
                collect(Collectors.toMap(ConsumeVo::getEnergyCode,k->k,(v1,v2)->{
                    v1.setConsume(v1.getConsume().add(v2.getConsume()));
                    return v1;
                })).values().stream().collect(Collectors.toList());
    }

七、算法

7.1 找最匹配的方案数据

直接算法实现,for循环,条件判定,先比较相等,再比较最接近。

 /**
     * 匹配流量最接近的方案
     * @param listBasePlans
     * @param totalFlow
     * @return
     */
    private BasePlanEO matchFlowBasePlan(List<BasePlanEO> listBasePlans, BigDecimal totalFlow){
        BigDecimal min = new BigDecimal(Long.MAX_VALUE);
        BasePlanEO resEo = null;
        for(BasePlanEO eo : listBasePlans){
			// 匹配与totalFlow值的相等的方案数据
            if(eo.calculateTotalFlow().compareTo(totalFlow)  == 0){
                return eo;
            }

            // 匹配最接近totalFlow值的方案数据
            if(eo.calculateTotalFlow().subtract(totalFlow).abs().compareTo(min) < 0){
                min = eo.calculateTotalFlow().subtract(totalFlow).abs();
                resEo = eo;
            }
        }
        return resEo;
    }

七、JAVA中如何正确地退出多层嵌套循环

使用一个标识符(如布尔变量)来检查是否应该退出循环。 当满足某个条件时,你可以设置这个标识符,并在每个循环的开始处检查它。

public class NestedLoopExitWithFlag {  
    public static void main(String[] args) {  
        boolean exitLoop = false; // 退出循环的标识符  
  
        for (int i = 0; i < 3 && !exitLoop; i++) {  
            for (int j = 0; j < 3 && !exitLoop; j++) {  
                if (i == 1 && j == 1) {  
                    exitLoop = true; // 设置标识符为 true,以退出循环  
                }  
                System.out.println("i: " + i + ", j: " + j);  
            }  
            // 检查是否需要退出循环(即使内层循环已经退出)  
            if (exitLoop) {  
                **break;** // 如果需要,退出外层循环  
            }  
        }  
    }  
}

这种方法的好处是它 更通用,可以在任何支持条件语句的编程语言中使用,并且它通常更容易阅读和维护。

八、页面的列表数据某一行上移、下移的算法 

在Java中实现页面列表数据中某行上移或下移的功能,可以通过交换列表中元素的索引位置来实现。这里我将提供一个简单的示例,演示如何使用Java代码来完成这个功能。

假设我们有一个列表List<String>,我们想要实现某个特定元素上移或下移的功能。

import java.util.ArrayList;
import java.util.List;
 
public class ListMover {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("Item 1");
        list.add("Item 2");
        list.add("Item 3");
        list.add("Item 4");
        list.add("Item 5");
 
        // 打印原始列表
        System.out.println("Original List: " + list);
 
        // 假设我们要移动"Item 3"上移和下移
        moveUp(list, "Item 3");
        System.out.println("After moving 'Item 3' up: " + list);
 
        moveDown(list, "Item 3");
        System.out.println("After moving 'Item 3' down: " + list);
    }
 
    public static void moveUp(List<String> list, String item) {
        int index = list.indexOf(item);
        if (index > 0) { // 确保不是第一个元素
            list.set(index, list.set(index - 1, item)); // 交换位置
        }
    }
 
    public static void moveDown(List<String> list, String item) {
        int index = list.indexOf(item);
        if (index < list.size() - 1) { // 确保不是最后一个元素
            list.set(index, list.set(index + 1, item)); // 交换位置
        }
    }
}

解释:

(1)moveUp 方法:这个方法首先找到要上移的元素索引,然后检查该元素是否不是列表中的第一个元素。如果不是第一个元素,它会将该元素与前一个元素的位置互换。这里使用了list.set(index, list.set(index - 1, item)),这种方法实际上是先获取当前位置的元素(为了后续赋值使用),然后将当前位置的元素设置为前一个元素的值,最后再将获取到的当前元素值赋给前一个位置。这样实现了上移。

(2)moveDown 方法:这个方法的工作方式类似于moveUp,但它检查元素是否不是列表中的最后一个元素。如果是,它将该元素与下一个元素的位置互换。同样使用了list.set(index, list.set(index + 1, item))来交换位置。

注意事项:

  • 使用list.indexOf(item)找到元素索引,确保了操作的准确性。如果元素不存在于列表中,indexOf将返回-1,但在实际操作中通常会有检查以确保操作的合法性(例如通过用户界面选择正确的项目)。

  • 在实际应用中,你可能需要处理用户界面的事件来调用这些方法,比如在Web前端或桌面应用中。确保在调用这些方法之前对列表的有效性进行检查(例如,确保用户选择了正确的项目)。

这种方法适用于任何类型的对象列表,不仅仅是字符串。你只需要将类型参数从String改为你的对象类型即可。例如,对于List<MyObject>,只需将字符串替换为相应的对象即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值