数组与集合学习笔记

数组与集合学习笔记

集合和数组都是对多个数据进行存储操作的结构,简称Java容器!!

一.数组

数组也是对象!!!数组变量属于引用类型!!!

1.数组声明

//方式一:
dataType[] array;
//方式二:
dataType array[];

使用 array.length可以 获取数组长度

2.数组的三种初始化

  • 静态初始化(创建+赋值)
  • 动态初始化
  • 数组的默认初始化:数组是引用类型,它的元素相当于类的实例变量,因此数组一经分配空间,其中的每个元素也被按照实例变量同样的方式被隐式初始化。int 默认为 0
   public static void test(){
        //静态初始化
        int a[] ={1,2,3,4,5};
        //动态初始化(包含默认初始化)
        int[] b = new int[10];
        //
    }

3.内存分析

image-20210411102142359

4.Arrays类

  • 数组的工具类java.util.Arrays
  • 由于数组对象本身并没有什么方法可以供我们调用,但API中提供了一个工具类Arrays供我们使用,从而可以对数据对象进行一些基本的操作。
  • Arrays类中的方法都是static修饰的静态方法,在使用的时候可以直接使用类名进行调用,而“不用“使用对象来调用(注意:是“不用“而不是“不能")
  • 具有以下常用功能:
    • 给数组赋值:通过fill 方法。
    • 对数组排序:通过 sort 方法,按升序。
    • 比较数组:通过 equals方法比较数组中元素值是否相等。
    • 查找数组元素:通过binarySearch 方法能对排序好的数组进行二分查找法操作。
    public static void main(String[] args) {
        int[] a = {1,23,4,12,3,4,3,2,3,4,432,0};
        //打印数组元素 toString()方法
        System.out.println(Arrays.toString(a));
        //排序数组
        Arrays.sort(a);
        System.out.println(Arrays.toString(a));
        //fill 填充
        Arrays.fill(a,0);
        System.out.println(Arrays.toString(a));
    }

5.冒泡排序

public class Test2 {
    public static void main(String[] args) {
        int[] a = {2,3,4,2,34,2,31,2,1};
        sort(a);
    }
    public static void sort(int[] array){
        int temp;
        for (int i = 0; i <array.length-1; i++) {
            for (int j = 0;j<array.length-i-1;j++) {
                if(array[j]>array[j+1]){
                    temp = array[j];
                    array[j] = array[j+1];
                    array[j+1] = temp;
                }
            }
        }
        System.out.println(Arrays.toString(array));
    }
}

6.稀疏数组

image-20210413152441272

public class Test3 {
    public static void main(String[] args) {
        int [][] a= new int[11][11];
        a[1][2]=1;
        a[2][3]=2;
        for (int[] ints : a) {
            for (int anInt : ints) {
                System.out.print(anInt+"\t");           //注意是 print  不换行, println 才会换行
            }
            System.out.println();
        }

        //创建稀疏数组
        //1.获取非零元素个数
        int sum =0;
        for (int[] ints : a)
            for (int anInt : ints)
                if(anInt !=0) sum++;
        int [][] aa = new int[sum+1][3];
        aa[0][0] = 11;              //行
        aa[0][1] = 11;              //列
        aa[0][2] = sum;             //元素个数
        //存入数组
        int count=0;

        for (int i = 0; i <a.length ; i++) {
            for (int j=0;j<a[i].length;j++){
                if(a[i][j]!=0){
                    count++;
                    aa[count][0]=i;
                    aa[count][1]=j;
                    aa[count][2] = a[i][j];
                }
            }
        }
        System.out.println("==============================");
        //输出稀疏数组
        for (int i = 0; i <aa.length ; i++) {
            System.out.println(aa[i][0]+"\t"+aa[i][1]+"\t"+aa[i][2]);
        }
    }
}

二.补充(StringBuilder和StringBuffer)

1.StringBuilder 和 StringBuffer

  • String、StringBuffer、stringBuilder三者的异同?
    • String:不可变的字符序列;底层使用char[]存储
    • StringBuffer:可变的字符序列;线程安全的,效率低;底层使用char[]存储
    • stringBuilder:可变的字符序列;jdk5.e新增的,线程不安全的,效率高;底层使用char[]存储

开发中建议使用StringBuffer(int capacity)或者StringBuilder(int capacity)

  • StringBuffer常用方法(StringBuilder相同)
     public static void main(String[] args) {
        StringBuffer sb = new StringBuffer("hello");
        System.out.println(sb.append(",java"));//hello,java
        System.out.println("获取指定下标的字符串:"+sb.charAt(0));//h
        System.out.println("获取[0,5)的内容:"+sb.substring(0,5));//hello
        System.out.println("获取从指定位置到最后的内容:"+sb.substring(5));//,java
        System.out.println("java字符串是下标的位置:"+sb.indexOf("a"));//7
        System.out.println("插入字符串:"+sb.insert(0, "你好"));//你好hello,java
        System.out.println("删除字符串:"+sb.delete(0,2));//hello,java
        System.out.println("替换字符串"+sb.replace(0,2,"ab"));   //abllo,java
        System.out.println("反转字符串"+sb.reverse());//avaj,ollba
        System.out.println("长度:"+sb.length());//10
        Date date = new Date();
        System.out.println(System.currentTimeMillis());;
    }

效率从高到底:StringBuilder>StringBuffer>String

三.集合

java集合可以分为:Collection和Map两种体系

  • **Collection接口:**单列数据,定义了存取一组对象的方法的集合

    • List接口:元素有序、可重复的集合 ”动态“数组
      • ArrayList、LinkedList、Vector
    • Set接口:元素无序、不可重复的集合
      • HashSet、LinkedHashSet、TreeSet
  • Map接口:双列数据,保存具有映射关系"key-value对"的集合

    • HashMap、LinkedHashMap、TreeMap、HashTable、Properties

1.Collection接口


1.1.Collection的API

image-20210414153956512

  • 向colLection接口的实现类的对象中添加数据obj时,要求obj所在类要重写equals().

1.2.Iterator的使用


//移除ArrayList中指定元素
public class Test1 {
    @Test
    public void test(){
        Collection coll = new ArrayList();
        ((ArrayList) coll).add(123);
        ((ArrayList) coll).add(345);
        ((ArrayList) coll).add(789);

        Iterator iter = coll.iterator();
        while (iter.hasNext()){
            Object obj = iter.next();
            if(obj.equals(123)){
                iter.remove();
            }
        }

        iter = coll.iterator();
        while (iter.hasNext()){
            System.out.println(iter.next());
        }
    }
}

1.3.Collection子接口1:List


List接口:存储有序的、可重复的数据;

​ ● ArrayList:作为List接口的主要实现类;线程不安全的,效率高;底层使用Object[] 存储,扩容的时候1.5倍。

​ ● LinkedList:底层使用的双向链表存储;对于频繁的插入、删除操作,使用此类比ArrayList高

​ ● Vector:作为List接口的古老实现类;线程安全的,效率不高;底层使用Object[] 存储,扩容的时候2倍。

  • 面试题:ArrayList、LinkedList、Vector三者的异同?
    • 同:三个类都实现了List接口,存储数据的特点相同:存储有序、可重复的数据
    • 不同:见上!!
1.List接口常用方法

  • void add(int index,Object ele):在index位置插入ele元素;
  • boolean addALL(int index,Collection eles):从index位置开始将eles中的所有元素添加进来;
  • Object get(int index):获取指定index位置的元素;
  • int indexof(Object obj):返回obj在集合中首次出现的位置;
  • int LastIndexof(Object obj):返回obj在当前集合中末次出现的位置;
  • Object remove(int index):移除指定index位置的元素,并返回此元素;
  • Object set(int index,Object ele):设置指定index位置的元素为ele ;
  • List subList(int fromIndex,int toIndex):返回从fromIndex 到toIndex位置的子集合。(左闭右开)
2.总结

常用方法:

  • 增:
    • add(Object obj)
  • 删:
    • remove(int index)
    • remove(Object obj)
  • 改:
    • set(int index,Object obj)
  • 查:
    • get(int index)
  • 插:
    • add(int index,Object obj)
  • 长度:
    • size()
  • 遍历:
    • Iterator 迭代器方式
    • 增强for循环
    • 普通循环
 @Test
    public void test2(){

        List list = new ArrayList();
        list.add(1);
        list.add(2);
        list.add(3);

        //删除索引为2的元素
        list.remove(2);
        System.out.println(list);
        list.add(3);
        //删除值为2的元素
        list.remove(new Integer(2));
        System.out.println(list);
    }

1.4.List子接口2:Set


set接口:存储无序的,不可重复的数据;

​ ● HashSet:作为set接口的主要实现类;线程不安全的,可以存储null值

​ ● LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序去遍历

​ ● TreeSet:可以按照添加对象的指定属性,进行排序

注意:

  • set接口中没有额外定义新的方法,使用的都是collection中声明的方法;

  • **无序性:**不等于随机性!! (并非按照索引添加,而是根据数据的哈希值决定的!!!!)

  • **不可重复性:**保证添加的元素按照equals()判断时,不能返回true(注意重写equals和hashcode方法!!!)

1.set方法add()添加元素的过程(以HashSet为例)

我们向HashSet中添加元a,首先调用】用元素a所在类的hashCode()方法,计算元素a的哈希值,此哈希值接着通过某种算法(散列函数(比如求余16等))计算出在HashSet底层数组中的存放位置(即为:索引位置),首先判断数组此位置上是否已经有元素:

  • 如果此位置上没有其他元素,则元素a添加成功。->情况1
  • 如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值:
    • 如果hash值不相同,则元素a添加成功。–>情况2
    • 如果hash值相同,进而需要调用元素a所在类的equlas()方法:
      • equals()返回true,元素a添加失败
      • equals()返回false,则元a添加成功。->情况3

Note:

对于情况2和3而言,元素a与已经存放在指定位置上的数据以链表的形式连接。(七上八下)

  • jdk7:元素a放在数组中,指向其他元素
  • jdk8:原来的元素在数组中,指向a

要求:

(1)向set中添加数据,其所在的类一定要重写HashCode和equals方法

(2)重写hashCode和equals方法,尽可能保持一致性!(即相同的对象必须具有相等的散列码)

重写两个方法的小技巧:

对象中用作equals()方法比较得Field,都应该用来计算hashcode值!!

2.LinkedHashSet

仍然是无序的!!

LinkedHashSet作为HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,记录此数据的前一个数据和后一个数据!!对于比较频繁的遍历操作,LinkedHashSet高于HashSet!!

3.TreeSet

Note:

  • 向TreeSet中添加的数据,要求是相同类的对象
  • 自然排序中,比较两个对象是否相同的标准为:compareTo()方法返回0,不再是equals().

排序的两种方式:

在User类中继承Comparable,然后重写compareTo()。

  • 自然排序
///按照姓名由小到大(由大到小只需加一个 - )
@Override
public int compareTo(Object o) {
    if(o instanceof User) {
        User user = (User)o;
        return this.name.compareTo(user.name); //由小到大
       // return -this.name.compareTo(user.name); //由大到小
    }else {
        throw new RuntimeException("输入的类型不匹配!");
    }
}

当我们插入数据:set.add(new User(“Tom”,12));

​ set.add(new User(“Tom”,13));

发现插入不了,是因为:TreeSet中比较两个对象是否相同的标准为:compareTo()方法返回0,不再是equals().

解决办法:compareTo方法进行如下的重写。

@Override
public int compareTo(Object o) {
    if(o instanceof User) {
        User user = (User)o;
        int compare = this.name.compareTo(user.name);
        if(compare != 0 ) {
            return compare;
        }else {
            return Integer.compare(this.age,user.age);
        }
    }else {
        throw new RuntimeException("输入的类型不匹配!");
    }
}
  • 定制排序comparator(按照User年龄进行排序)

    定制排序中,同样比较两个对象是否相同的标准为:compareTo()方法返回0,不再是equals()

public void test2(){
    Comparator com = new Comparator() {
        @Override
        public int compare(Object o1, Object o2) {
            if(o1 instanceof User && o2 instanceof User){
                User user = (User) o1;
                User user2 = (User) o2;
                return Integer.compare(user.getAge(),user2.getAge());
            }else {
                throw new RuntimeException("类型不对!");
            }
        }
    };
    TreeSet set = new TreeSet(com);
    set.add(new User("Tom",10));
    set.add(new User("Jerry",26));
    set.add(new User("Jim",76));
    set.add(new User("Mike",26));
    set.add(new User("Jack",35));
    set.add(new User("Jack",48));
    Iterator iter = set.iterator();
    while (iter.hasNext()){
        System.out.println(iter.next());
    }
}

1.5.练习

1.集合Collection中存储的如果是自定义类的对象,需要自定义类重写哪个方法?为什么?

equals()方法。

  • List也需要重写equals()方法。比如判断 contains、remove等
  • Set:(HashSet、LinkedhashSet):equals()和hashCode()方法!!!
  • TreeSet:
    • 自然排序:实现Comparable:CompareTo( Object obj)方法
    • 自定义排序:实现Comparator:CompareTo(Object o1,Object o2)

2.在List中去除重复值,要求尽量简单。

public class exr {

    public static List duplicateList(List list){
        Set set = new HashSet();
        set.addAll(list);
        return new ArrayList(set);
    }
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add(new Integer(1));
        list.add(new Integer(3));
        list.add(new Integer(4));
        list.add(new Integer(6));
        list.add(new Integer(7));
        list.add(new Integer(15));
        list.add(new Integer(4));
        list.add(new Integer(3));
        list.add(new Integer(90));
        List list1 = duplicateList(list);
        for (Object o : list1) {
            System.out.println(o);
        }
    }
}

2.Map接口

一、Map实现类的结果

Map接口继承树:

image-20210418093916316

双列数据,存储key-value对的数据。

  • HashMap:作为Map的主要实现类,线程不安全的,效率高;存储null的key和value

    HashMap底层:

    ●jdk7以及之前:数组+链表

    ●jdk8:数组+链表+红黑树

    • LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序进行遍历。
      • 原因:在原有的HashMap底层他结构基础上,添加指针,指向前一个和后一个元素。对于频繁的遍历操作,此类执行效率高于HashMap.
  • TreeMap:保证按照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或者定制排序。

    • 底层使用红黑树。
  • Hashtable:作为古老的实现类,线程安全的,效率不高;不能存储null的key和value

    • Properties:常用来处理配置文件,key和value都是String类型。

面试题:

1.HashMap的底层实现原理?

2.HashMap和HashTable的异同?

二.Map结构的理解
  • Map 中的key:无序的、不可重复的,使用Set存储所有的key ->要求key所在的类(自定义对象)需要重写equals( )和hashCode( )方法.(以HashMap为例)

  • Map 中的value:无序的、可重复的,使用coLLection存储所有的value ->value所在的类要重写equals( )方法.

  • 一个键值对:key-value构成了一个Entry对象。

    • Map中的entry:无序的、不可重复的,使用set存储所有的entry.
三.HashMap的底层实现原理(高频)

以jdk7为例说明:

HashMap map=new HashMap():

在实例化以后,底层创建了长度是16的一维数组Entry[] table。
…可能已轻执行过多次put…
map.put(key1,value1):

首先,调用key1所在类的hashcode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置。
如果此位置上的数据为空,此时的key1-value1添加成功。-—-情况1

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

​ 如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals(key2)
​ 如果equals()返回false:此时key1-value1添加成功。-—-情况3

​ 如果equals()返回true:使用value1替换 value2。

扩容:

在不断的添加过程中,会涉及到扩容问题,默认的扩容方式:扩容为原来容量2倍,并将原有的数据复制过来。


jdk8 相较于jdk7在底层实现方面的不同:

  • 1.new HashMap():底层没有创建一个长度为16的数组
  • 2.jdk8底层的数组是:Node [ ],而非Entry[ ]
  • 3.首次调用put()方法时,底层创建长度为16的数组
  • 4.jdk7底层结构只有:数组+链表。jdk8中底层结构:数+链表+红黑树。
    • 当教组的某一个索引位置上的元素以链表形式存在的数据个数> 8且当前数组的长度>64时,此时此索引位置上的所有数据改为使用红黑树存储。
四、LinkedHashMap的底层实现原理(了解)

image-20210418112047266

求中after和before能够记录添加的元素的先后顺序。

五.Map的常用方法
1. hashMap
  • 添加、删除、修改操作:
    • object put(Obiect key,Object value):将指定key-value添加到(或修改)当前map对象中
    • void putALL(Map m):将m中的所有Key-value对存放到当前map中
    • Object remove(Object key):移除指定key i的key-value对,并返回value
    • void clear():清空当前map中的所有数据
  • 元素查询的操作:
    • Object get(Object key):获取指定key对应的value
    • Boolean containsKey(Object key):是否包含指定的key
    • Boolean containsValue(Object value):是否包含指定的的value
    • int size():返回map 中key-value对的个数
    • boolean isEmpty():为判断当前map是否为空
    • boolean equals(Object obj):判断当前map和参数对象obj是否相等
  • 元视图操作的方法:(遍历)
    • Set keySet():返回所有key构成的Set集合
    • Collection values():返回所有value构成的CoLLection集合
    • Set entrySet():返回所有key-value对构成的Set集合
@Test
public void test(){
    Map map = new HashMap();
    map.put("AA",123);
    map.put(45,123);
    map.put("BB",56);

    //遍历所有的key集,keySet()
    Set set = map.keySet();
    Iterator iter = set.iterator();
    while (iter.hasNext()){
        System.out.println(iter.next());
    }
    System.out.println();
    //遍历所有的value集,values()
    Collection values = map.values();
    for (Object value : values) {
        System.out.println(value);
    }

    System.out.println();
    //遍历所有的key-value
    Set entrySet = map.entrySet();
    Iterator iter1 = entrySet.iterator();
    while (iter1.hasNext()){
        Object o = iter1.next();
        //entrySet集合中的元素都是entry
        Map.Entry entry = (Map.Entry)o;
        System.out.println(((Map.Entry) o).getKey()+"-->"+((Map.Entry) o).getValue());
    }
}

总结常用的方法:

  • 添加:put(Objrct obj1,Object value);
  • 修改: put(Objrct obj1,Object value);
  • 查询:get(Object key)
  • 删除:remove(Object key)
  • 长度:size()
  • 遍历:keySet( )/values( )/entrySet( )
2.TreeMap
  • 向TreeMap 中添加key-value,要求key必须是由同一个类创建的对象

  • 因为要按照key进行排序:自然排序、定制排序

    • 自然排序:
    @Test
    public void test(){
        TreeMap map = new TreeMap();
        User user1 = new User("Tom",23);
        User user2 = new User("Jerry",32);
        User user3 = new User("Jack",20);
        User user4 = new User("Rose",18);
        map.put(user1,98);
        map.put(user2,89);
        map.put(user3,79);
        map.put(user4,100);
        Set entrySet = map.entrySet();
        Iterator iter = entrySet.iterator();
        while (iter.hasNext()){
            Object o = iter.next();
            Map.Entry entry = (Map.Entry)o;
            System.out.println(entry.getKey()+"-->"+entry.getValue());
        }
    }
    --------------------------------------------------------
     //User需要继承然后重写:
        public class User implements Comparable {
        private String name;
        private int age;
    
    
        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        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;
        }
    
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
    
            User user = (User) o;
    
            if (age != user.age) return false;
            return name != null ? name.equals(user.name) : user.name == null;
        }
    
        @Override
        public int hashCode() {
            int result = name != null ? name.hashCode() : 0;
            result = 31 * result + age;
            return result;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    
        ///按照姓名由小到大
        @Override
        public int compareTo(Object o) {
            if(o instanceof User) {
                User user = (User)o;
                int compare = this.name.compareTo(user.name);
                if(compare != 0 ) {
                    return compare;
                }else {
                    return Integer.compare(this.age,user.age);
                }
            }else {
                throw new RuntimeException("输入的类型不匹配!");
            }
        }
    }
    

定制排序:

public void test2(){
    Comparator com = new Comparator() {
        @Override
        public int compare(Object o1, Object o2) {
            if(o1 instanceof User && o2 instanceof User){
                User u1 = (User) o1;
                User u2 = (User) o2;
                return Integer.compare(u1.getAge(),u2.getAge());
            }
            throw new RuntimeException("类型不匹配!");
        }
    };
    TreeMap map = new TreeMap(com);
    User user1 = new User("Tom",23);
    User user2 = new User("Jerry",32);
    User user3 = new User("Jack",20);
    User user4 = new User("Rose",18);
    map.put(user1,98);
    map.put(user2,89);
    map.put(user3,79);
    map.put(user4,100);
    Set set = map.entrySet();
    Iterator iter = set.iterator();
    while (iter.hasNext()){
        Object o1 = iter.next();
        Map.Entry entry = (Map.Entry)o1;
        System.out.println(entry.getKey()+"-->"+entry.getValue());
    }
}
3.Properties
@Test
public void test(){
    FileInputStream fileInputStream = null;
    try {
        Properties properties = new Properties();
        fileInputStream  = new FileInputStream("jdbc.properties");
        properties.load(fileInputStream);
        String name = properties.getProperty("name");
        String psw = properties.getProperty("password");
        System.out.println("name = "+name+"psw = " + psw);
    }catch (IOException e){
        e.printStackTrace();
    }finally {
        if (fileInputStream != null) {
            try {
                fileInputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
六.Collections工具类

是一个操作List、Set和Map等集合的工具类。(既可以操作list接口也可以map接口)

  • reverse(List):反转List 中元素的顺序
  • shuffle(List):对List 集合元素进行随机排序
  • sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序 (注意只是list)
  • sort(List,Comparator):根据指定的 Comparator 产生的顺序对List 集合元素进行排序
  • swap(List,int,int):将指定list 集合中的i处元素和j处元素进行交换
  • object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
  • object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大
  • Object min(Collection)
  • object min(Collection,Comparator)
  • int frequency(Collection,object):返回指定集合中指定元素的出现次数
  • void copy(List dest,List src):将src中的内容复制到dest中
  • boolean replaceAll(List list,Object oldvalue,Object newValue):使用新值替换List 对

注意:Collections.sort(List list);此方法中参数类型为List。这说明此方法只能对list集合中的元素进行排序

Note:Copy的时候:

   @Test
    public void test(){
        List list = new ArrayList();
        list.add(123);
        list.add(34);
        list.add(3432);
        list.add(-1);
        list.add(343);
        //错误写法:java.lang.IndexOutOfBoundsException: Source does not fit in dest
      //  List dest = new ArrayList();
        //Collections.copy(dest,list);
        //处理技巧:
        List dest = Arrays.asList(new Object[list.size()]);
        Collections.copy(dest,list);
        System.out.println(dest);
    }

Collections 类中提供了多个synchronizedxxx()方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题.

List list = new ArrayList();
//返回的list1为安全的
List list1 = Collections.synchronizedList(list);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值