集合框架学习笔记:Collection体系和Map体系、Collections工具类

集合框架

Java是面向对象编程,万事万物皆“对象”,为了方便对“对象”进行操作,需要对“对象”进行存储,而Java集合就是存储“对象”的容器,可以动态地把多个对象存入容器中。Java集合类可以用于存储数量不等的多个对象,还可以用于保存具有映射关系的关联数组。

Collection接口

  • 单列集合,用来存储”一个一个“的对象。

  • 向Collection接口的实现类的对象添加数据(obj)时,要求obj所在的类重写equals()。(不重写的话Collection中有些方法的判断会有影响。)

  • Collection接口中的方法:

  1. add(Object obj):添加一个对象。
  2. addAll(Collection coll):添加一个集合。
  3. int size():获取集合内有效元素的个数。
  4. void clear():清空集合。
  5. boolean isEmpty():判断集合是否为空。
  6. boolean contains(Object obj):判断集合中是否包含obj对象。(调用equals()进行判断)
  7. boolean containsAll(Collection coll):判断集合中是否包含coll集合内的所有对象。(调用equals()进行判断)
  8. boolean remove(Object obj):删除集合中第一个与obj对象相同的对象元素。(调用equals()进行判断)
  9. boolean removeAll(Collection coll):取当前集合与coll集合的差集。
  10. boolean retainAll(Collection coll):取当前集合与coll的交集。
  11. boolean equals(Object obj):判断集合是否相等。(注意有序性和无序性)
  12. hashCode():获取集合对象的哈希值。
  13. Object[] toArray():将集合转换成Object类型数组。(调用Arrays.asList(),可以把数组转换成集合)
  14. iterator():返回迭代器对象,用于集合的遍历。

迭代器使用注意点:(迭代器是设计模式中的一种)

Collection接口实现了java.lang.Iterable接口,该接口中有iterator(),所有实现了Collection接口的实现类都有一个iterator()方法。

  1. 集合对象每次调用iterator(),都会得到一个全新的迭代器对象,默认游标在第一个元素之前
  2. Iterator仅用于遍历集合,Iterator本身并不提供承装对象的能力。
  3. 迭代器中的重要的两个方法:hasNext()和next()。
  4. hasNext()判断是否还有下一个元素。
  5. 调用next()后,迭代器游标下移,并返回下移之后集合位置上对应的元素。如果没有使用hasNext()判断下一条记录是否有效,且下一条记录无效,调用next()后,则抛出异常(NoSuchElementException)。
  6. 迭代器也可以删除集合中的元素,调用的是迭代器中的remove(),不是集合的remove()。
  7. 如果没有调用next()或在上一次调用next()方法后已经调用了remove(),再次调用remove()都会抛出异常(IllegalStateException)。

迭代器遍历集合示例:

@Test
    public void test3(){
        Collection c = new ArrayList();
        c.add(123);
        c.add(3.14);
        c.add('A');
        Iterator iterator1 = c.iterator();
        while (iterator1.hasNext()) {
            System.out.println(iterator1.next());
        }
        System.out.println("=========================================");
        Iterator iterator2 = c.iterator();
        while (iterator2.hasNext()){
            Object o = iterator2.next();
            if (o.equals(123)){
                iterator2.remove();
            }
        }
        Iterator iterator3 = c.iterator();
        while(iterator3.hasNext()){
            System.out.println(iterator3.next());
        }
    }

在这里插入图片描述

List接口

  • 存放的对象特点:有序,可重复
  • List接口下有三个实现类:ArrayList、LinkedList、Vector。

List接口中新提供的方法:

  1. void add(int index,Object obj):在下标为index位置插入obj元素。
  2. boolean addAll(int index,Collection coll):从下标为index处,将集合coll所有的元素添加进来。
  3. Object get(int index):获取下标为index的元素。
  4. int indexOf(Object obj):返回obj对象在当前集合第一次出现的位置下标。
  5. int lastIndexOf(Object obj):返回obj对象在当前集合最后一次出现的位置下标。
  6. Object remove(int index):删除index位置对应的元素,并返回该元素。(注意区分Collection接口下的remove())
  7. Object set(int index,Object obj):设置指定index位置的元素为obj。
  8. List subList(int fromIndex,int toIndex):返回从fromIndex到toIndex位置的子集合。
ArrayList
  • 适用于经常需要进行查询操作的对象集合。

  • JDK1.7及1.2之前,new ArrayList();创建ArrayList对象,底层就创建了长度为10的Object[]数组elementData。JDK1.8后,new ArrayList();创建ArrayList对象,底层Object[] elementData初始化为{},并没有创建长度为10的数组,直到ArrayList对象第一次调用add()时,才创建长度为10的数组,并把add()的参数添加到数组中。

  • 底层为用Object[] elementData数组进行存储。不带参数的构造方法创建的ArrayList对象默认容量为10(initialCapacity),也可以使用带参构造方法创建ArrayList的对象,创建的时候就指定容量。
    在这里插入图片描述

  • 扩容:变为原来的1.5倍(原容量加上原容量右移1位,即1+(1/2)),并把原数组的元素复制到新数组中。

在这里插入图片描述

  • 查询效率高;插入、删除效率低。

Arrays.asList()使用注意点

    //asList()注意点
    @Test
    public void test2(){
        Collection coll = new LinkedList();
        coll.add(123);
        List list1 = Arrays.asList(new int[]{1, 2, 3, 4, 5, 6, 7});
        // 不自动装箱的话把整个int[]当成一个对象
        System.out.println(list1.size());   //1
        // 需要手动装箱
        List list2 = Arrays.asList(new Integer[]{1,2,3,4,5,6,7});
        System.out.println(list2.size());   //7
        System.out.println(list2);
    }
LinkedList
  • 使用于经常需要插入和删除操作的对象集合。

  • 底层用双向链表进行存储,没有定义数组,定义了Node类型的first和last记录首末元素,同时定义内部类Node,作为LinkedList中保存数据的基本结构,还定义了两个变量prev和next,分别记录前一个和后一个元素的位置。

在这里插入图片描述

Vector

线程安全,效率低,底层使用Objece[] elementData数组进行存储。

在JDK1.7和1.8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组。在扩容方面,默认扩容为原来数组的两倍。


Set接口

  • 存放对象的特点:无序(存入和取出的顺序可能不一致),不可重复,如果尝试添加两个相同的元素,则添加操作失败。
  • Set接口下的实现类有:HashSet,LinkedHashSet,TreeSet。
  • Set接口没有提供额外的方法,所以用的都是Collection接口中的方法。
  • Set中的元素判断两个对象是否相同用的是equals(),而不是“==”。
  • 对于存放在Set容器中的对象:一定要重写hashCode()和equals(),以实现对象相等规则,即相等的对象必须具有相等的散列码(哈希值)。

重写hashCode()的原则:

  1. 程序运行时,同一个对象多次调用hashCode()应该返回相同的值。
  2. 当两个对象的equals()比较返回true时,两个对象的hashCode()返回值也应该相同
  3. 对象中用作equals()方法比较的属性,都应该用来计算hashCode值。
  4. hashCode()的默认行为是对堆上的对象产生独特值,如果没有重写hashCode(),则该class的两个对象的hashcode无论如何都不会相等,即使这两个对象的数据相同。

重写equals()的原则:

  1. 当一个类有自己特有的“逻辑相等”概念,当改写equals()的时候,总是要改写hashCode(),根据一个类改写后的equals(),两个截然不同的对象有可能在逻辑上是相等的,但是根据Object.hashCode(),他们仅仅是两个对象,因此违反了“相等的对象必须具有相等的散列码”。
  2. 重写equals()方法的时候一般需要重写hashCode(),通常参与计算hashCode的对象的属性也应该参与到equals()中进行计算。
HashSet
  • HashSet底层实现是:HashMap。底层存储结构是数组,初始容量为16,扩容为原来的2倍
  • 不能保证元素的排列顺序。(不意味着每次遍历集合,对象的顺序都会改变)
  • HashSet不是线程安全的
  • 可以存储null值

示例:

@Test
public void test1(){
	HashSet set = new HashSet();
	set.add(123);
	set.add(456);
	set.add("abc");
	set.add(null);
	set.add(5858);
	set.add(5858);
	System.out.println(set);
    }

在这里插入图片描述

LinkedHashSet
  • LinkedHashSet底层实现是:LinkedHashMap

  • LinkedHashSet不是线程安全的

  • LinkedHashSet是HashSet的子类。

  • 以元素插入的顺序来维护集合的链表。

  • LinkedHashSet类里面添加了两个引用,用来记录当前元素的前一个元素和后一个元素(双向链表),因此LinkedHashSet可以按照元素添加的前后顺序遍历集合中所有的元素(这并不意味着有序性)。

  • 对于频繁遍历操作,LinkedHashSet的效率高于HashSet。插入元素的性能略低于HashSet。

  • 可以存储null值

示例:

@Test
public void test1(){
	LinkedHashSet set = new LinkedHashSet();
	set.add(123);
	set.add(456);
	set.add("abc");
	set.add(null);
	System.out.println(set);
}

在这里插入图片描述

TreeSet
  • TreeSet底层实现是:红黑树
  • 只能存储相同类型的引用类型数据
  • 有两种排序方式,自然排序和定制排序(涉及Comparable和Comparator),默认情况下为自然排序。
  • TreeSet不是线程安全的
  • 不可以存储null值,会抛出异常(NullPointException)。

示例:TreeSet按照两种排序存储数据。

Employee类:实现了Comparable接口。

/**
 * @Author:xiezr
 * @Creat:2021-07-14 20:57
 */
public class Employee implements Comparable {
    private String name;
    private int age;
    private MyDate birthday;

    public Employee() {
    }

    public Employee(String name, int age, MyDate birthday) {
        this.name = name;
        this.age = age;
        this.birthday = birthday;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public MyDate getBirthday() {
        return birthday;
    }

    public void setBirthday(MyDate birthday) {
        this.birthday = birthday;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", birthday=" + birthday +
                '}' +
                '\n';
    }
    // 按名称从低到高排序,再按年龄从高到低排序
    @Override
    public int compareTo(Object o) {
        if (o instanceof Employee){
            Employee e1 = (Employee) o;
            if (this.name.equals(e1.name)){
                return -this.age - e1.age;
            }else {
                return this.name.compareTo(e1.name);
            }
        }
        throw new RuntimeException("输入的类型错误!");
    }
}

MyDate类:无实现Comparable或Comparator接口。

/**
 * @Author:xiezr
 * @Creat:2021-07-14 20:57
 */
public class MyDate {
    private int year;
    private int month;
    private int day;

    public MyDate() {
    }

    public MyDate(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }

    public int getYear() {
        return year;
    }

    public void setYear(int year) {
        this.year = year;
    }

    public int getMonth() {
        return month;
    }

    public void setMonth(int month) {
        this.month = month;
    }

    public int getDay() {
        return day;
    }

    public void setDay(int day) {
        this.day = day;
    }

    @Override
    public String toString() {
        return "MyDate{" +
                "year=" + year +
                ", month=" + month +
                ", day=" + day +
                '}';
    }
}

测试:

import org.junit.Test;

import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;

/**
 * @Author:xiezr
 * @Creat:2021-07-14 21:17
 */
public class TreeSetTest {
    // 按名称从低到高排序,再按年龄从高到低排序
    @Test
    public void test1(){
        Employee e1 = new Employee("Adel", 25, new MyDate(1998, 8, 31));
        Employee e2 = new Employee("Maroon5",35,new MyDate(1987,6,6));
        Employee e3 = new Employee("LinKinPark",28,new MyDate(1999,2,9));
        Employee e4 = new Employee("Passenger",27,new MyDate(1999,5,6));
        Employee e5 = new Employee("Eminem",40,new MyDate(1979,2,6));
        Employee e6 = new Employee("Adel", 52, new MyDate(1945, 8, 31));

        TreeSet treeSet = new TreeSet();
        treeSet.add(e1);
        treeSet.add(e2);
        treeSet.add(e3);
        treeSet.add(e4);
        treeSet.add(e5);
        treeSet.add(e6);
        System.out.println(treeSet);
    }
    // 按照出生年份从大到小排,月份天数从小到大排
    @Test
    public void test2(){
        Employee e1 = new Employee("Adel", 25, new MyDate(1998, 8, 31));
        Employee e2 = new Employee("Maroon5",35,new MyDate(1987,6,6));
        Employee e3 = new Employee("LinKinPark",28,new MyDate(1999,2,9));
        Employee e4 = new Employee("Passenger",27,new MyDate(1999,2,6));
        Employee e5 = new Employee("Eminem",40,new MyDate(1979,2,6));
        Employee e6 = new Employee("Adel", 52, new MyDate(1945, 8, 31));

        TreeSet treeSet = new TreeSet(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                if (o1 instanceof Employee && o2 instanceof Employee){
                    Employee e1 = (Employee) o1;
                    Employee e2 = (Employee) o2;
                    MyDate m1 = e1.getBirthday();
                    MyDate m2 = e2.getBirthday();
                    int minusYear = m1.getYear() - m2.getYear();
                    if (minusYear != 0){
                        return -minusYear;
                    }else {
                        int minusMonth = m1.getMonth() - m2.getMonth();
                        if (minusMonth != 0){
                            return minusMonth;
                        }else {
                            return m1.getDay() - m2.getDay();
                        }
                    }
                }
                throw new RuntimeException("输入类型异常!");
            }
        });
        treeSet.add(e1);
        treeSet.add(e2);
        treeSet.add(e3);
        treeSet.add(e4);
        treeSet.add(e5);
        treeSet.add(e6);

        Iterator iterator = treeSet.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }
}

Map接口

  • 双列,用来存储的一对一对(key-value)的对象。
  • Map中的key是无序的,不重复的,使用Set存储所有的key,所以key所在的类要重写hashCode()和equals()。
  • Map中的value是无序的,可重复的,使用Collection存储所有的value,所以value所在的类要重写equals()。
  • 一个键值对key-value构成一个Entry对象,Map中的entry是无序的,不可重复的,使用Set存储所有的entry对象。
Map接口中常用的方法
  • 添加、删除、修改操作:
  1. Object put(Object key,Object value):将指定的key和value添加到当前map对象中,如果key相同,则value覆盖掉原来的value。
  2. putAll(Map map):把map中所有的key-value键值对存放到当前集合对象中。
  3. Object remove(Object key):删除指定key对应的key和value,并返回被删除的value。
  4. void clear():清除当前集合对象中所有的数据。
  • 查询操作:
  1. Object get(Object key):根据指定的key,获取对应的value。
  2. containsKey(Object key):判断当前集合对象是否含有指定的key。
  3. containsValue(Object value):判断当前集合对象是否含有指定的value。
  4. int size():返回集合对象中的key-value个数。
  5. boolean isEmpty():判断当前集合对象是否为空。
  6. boolean equals(Object obj):判断当前集合对象和obj对象是否相等。
  • 元视图操作(用于遍历):
  1. Set keySet():返回所有key构成的Set集合。
  2. Collection Values():返回所有value构成的Collection集合。
  3. Set entrySet():返回所有key-value构成的Set集合。

HashMap

  • HashMap作为Map接口的主要实现类,底层的实现是:数组+链表+红黑树(JDK1.8)。(JDK1.7及之前,底层的实现是数组+链表)
  • new HashMap():底层并没有创建一个长度为16的数组,当HashMap的对象首次调用put()添加数据时,创建长度为16的数组。(不同于JDK.7)
  • 底层的数组默认容量为16,需要扩容则扩容为原来的两倍(*2),并将原有的数据赋值过来,此时数据的存放位置可能跟在原先数组的存放位置不一致(需要重新计算元素在新数组中的位置,再进行存储)。
  • 底层数组是Node[],而非Entry[]。(JDK1.7和1.8的区别之一)
  • 以key-value这种键值对的形式存储数据。
  • 线程不安全,效率高。可存储null的key和value

HashMap底层的实现:map.put(key1,value1);

首先,调用key1所在类的hashCode()计算key1的哈希值,然后该哈希值经过某种算法计算以后,得到在Node[]数组中的存放位置(即索引位置)。

  • 情况1:如果该位置的数据为空,此时key1-value1添加成功。(以数组的方式存储)

  • 情况2:如果此位置上的数据不为空,意味着此位置上存在一个或多个数据(以链表的方式存在),此时,比较key1和已经存在的一个或多个数据的哈希值,如果key1的哈希值与已存在的任何一个数据都不相同,则key1-value1添加成功。(以链表的方式存储)

  • 情况3:如果key1的哈希值与已存在的某一个数据(key2-value2)的哈希值相同,继续进行比较,调用key1所在类的equals(key2),如果equals()返回false,则key1-value1添加成功。(以链表的方式存储)

  • 情况4:如果equals()返回true,则使用value1覆盖掉value2。

  • 注意:当Node[]数组某一索引位置上的元素以链表存在的形式的数据个数大于8,且此时数组的长度大于64时,此时索引位置上的所有数据改为用红黑树存储

HashMap的扩容:

  • 扩容涉及到容量,装填因子以及临界值。底层Node[]数组的默认容量为16,默认装填因子为0.75,临界值为容量*装填因子,即默认为12。

  • 当往HashMap集合中添加数据,此时数据以数组的方式存储,而不是以链表的方式存储时,添加数据后,Node[]的长度大于临界值(12)时,Node[]数组进行扩容,*2,容量变成原来的两倍即32,然后把原有的数据赋值,添加到扩容后的数组中,此时临界值重新计算,变成了24。

装填因子的大小对HashMap的影响:

  1. 装填因子的大小决定HashMap数据的密度。

  2. 装填因子越大,密度越大,发生冲突(不同元素,哈希值相同)的几率越大,数组中的链表越容易长,造成查询或插入时比较的次数增多,性能会下降。

  3. 装填因子越小,临界值越小,越容易引发扩容,数据密度也越小,意味着发生冲突的几率越小,数组中的链表也越短,查询和插入时比较的次数也越少,性能会更高,但是会浪费一定的内存空间,而且经常扩容也会影响性能。

  4. 按照其他语言的参考以及研究经验,考虑将装填因子设置为0.7~0.75,此时平均检索长度接近于常数。

HashMap的遍历操作:

public class HashMapTest {
    @Test
    public void test1(){
        HashMap hashMap1 = new HashMap();
        // 往当前集合对象中添加数据
        hashMap1.put(1001,"旺财");
        hashMap1.put(1002,"来福");
        hashMap1.put(1003,"富贵");

        HashMap hashMap2 = new HashMap();
        hashMap2.put(1004,"常威");

        // 将形参集合中的所有key-value添加到当前集合对象中
        hashMap2.putAll(hashMap1);
        System.out.println(hashMap2);

        // 移除指定的key对应的key-value
//        hashMap2.remove(1003);

        // 遍历集合中的key-value
        // 方式一:两个迭代器分别获取key和value
        Set set = hashMap2.keySet();
        Collection values = hashMap2.values();
        Iterator iterator1 = set.iterator();
        Iterator iterator2 = values.iterator();
//        while (iterator2.hasNext()){
//            System.out.println(iterator2.next());
//        }
        while (iterator1.hasNext() && iterator2.hasNext()){
            System.out.println(iterator1.next() + "====" + iterator2.next());
        }

        System.out.println();

         // 方式二:通过Entry的getKey()和getValue()获取key和value的值
        Set set1 = hashMap2.entrySet();
        Iterator iterator = set1.iterator();
        while (iterator.hasNext()){
            Map.Entry entry = (Map.Entry) iterator.next();
            System.out.println(entry.getKey() + "---->" + entry.getValue());
        }

        System.out.println();

        // 方式三:通过迭代器获取key的值,调用Map接口下的get()方法,根据Key获取value的值。
        Iterator iterator3 = set.iterator();
        while (iterator3.hasNext()){
            Object key = iterator3.next();
            Object value = hashMap2.get(key);
            System.out.println(key + "<====>" + value);
        }
    }
}

LinkedHaspMap

  • LinkedHashMap是HashMap的子类,底层实现于HashMap一样。
  • LinkedHashMap类里面定义了内部类Entry,该内部类继承了HashMap.Node<k,v>,Entry类里边定义了两个变量before和after,用于记录当前元素的前一个元素和后一个元素,使得LinkedHashMap可以按照元素添加的顺序进行遍历

TreeMap

  • TreeMap是按照元素的key值进行排序的,因此要求用TreeMap集合存储的对象的key值必须是相同类型
  • TreeMap可以根据key实现排序遍历,此时考虑key的自然排序或者定制排序。(涉及Comparable和Comparator接口)

Hashtable

  • 古老实现类,JDK1.0就存在,在JDK1.2之后被HashMap代替。

  • 线程安全,效率低。不可以存储null的key和value

Properties
  • 常用来处理配置文件,key和value都是String类型的
  • 存取数据时,建议使用setProperty(String key,String value)和getProperty(String key)。

Collections工具类

  • Collections是用于操作Collection(List、Set)和Map的工具类。

Collections中的常用方法:

  • 排序操作:(均为static方法)
  1. reverse(List list):反转list集合中的所有元素。
  2. shuffle(List list):对list集合中的所有元素进行随机排序。
  3. sort(List list):根据元素的自然顺序对指定list集合按升序排序。
  4. sort(List list,Comparator com):根据com定制的排序对指定的list集合进行定制排序。
  5. swap(List list,int i,int j):将list集合中下标为i的元素和下标为j的元素进行交换。
  • 查找、替换:
  1. Object max(Collection coll):根据元素的自然排序,返回给定集合中的最大元素。
  2. Object max(Collection coll,Comparator com):根据com的定制排序,返回给定集合中的最大元素。
  3. Object min(Collection coll):根据元素的自然排序,返回给定集合中的最小元素。
  4. Object min(Collection coll,Comparator com):根据com的定制排序,返回给定集合中的最小元素。
  5. int frequency(Collection coll,Object obj):返回指定对象obj在指定集合coll中出现的次数。
  6. boolean replaceAll(List list,Object oldVal,Object newVal):使用newVal替换集合list中的oldVal。
  7. void copy(List dest,List src):将src中的内容复制到dest中。(注意底层的实现)
  • copy()注意点:
public class CopyTest {
    @Test
    public void test1(){
        ArrayList list = new ArrayList();
        list.add(626);
        list.add(628);
        list.add("I'm");
        list.add("Iron");
        list.add("Man");

//        ArrayList list1 = new ArrayList();
        // 错误,IndexOutOfBoundsException: Source does not fit in dest,

        List list1 = Arrays.asList(new Object[list.size()]);

        Collections.copy(list1,list);
        System.out.println(list1);
    }
}

在这里插入图片描述
这里的dest相当于list1,src相当于list,如果按照注释掉的代码,一开始,list1.size()是为0的,即dest.size()为0,list中有五个元素,即src.size()为5,即srcSize为5,那么就会抛出异常,所以,要进行复制操作时,可以给空的新集合里面填一个Object[]数组,撑起里面的size,给Object[]数组的长度赋值为被复制的集合的size(),即被复制的集合中有x个元素,就用一个长度为x的Object[]把新数组的size撑起来等于被复制的集合的size。

  • 将指定集合包装成线程同步的集合:(可以解决多线程并发访问集合中的安全问题)
  1. synchronizedList():将指定集合包装成线程同步的List集合。
  2. synchronizedSet():将指定集合包装成线程同步的Set集合。
  3. SynchronizedMap():将指定集合包装成线程同步的Map集合。

即使ArrayList和HashMap是线程不安全的,Vector和Hashtable是线程安全的,但是实际开发中依旧不会去似乎用Vector和Hashtable,而是调用Collections工具类下的指定方法,把ArrayList和HashMap等包装成线程安全的,然后再使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

moonlight_Answer

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

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

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

打赏作者

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

抵扣说明:

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

余额充值