【JAVA】 容纳对象 映射 Maps

总概

a) 映射将对象与其他对象关联。
b) HashMap能快速访问,
c) TreeMap将键按顺序排列,不如HashMap快
d) LinkedHashMap保持元素的插入顺序,通过hashing达到快速访问。

Map

public interface Map<K, V>

介绍
映射键到值的对象。map不能包含重复的键,每个键只能映射最多一个值
该接口取代了Dictionary类
Map接口提供三种集合观(collection views),分别允许map的内容被看成是键的集合,值的集合或键值对映射的集合。map集合观的迭代器返回的元素顺序定义map的顺序。一些map实现如TreeMap对顺序做了明确的保障,而其他的实现如HashMap则没有

注意:要十分注意可变的对象(mutable objects)用作Map键时的情况。如果某个对象是Map的键,且该对象被更改并影响了equals的比较(即可能出现e1.equals(e2)),则此时Map的行为不明确(not specified)。该禁止的一个特例是:不允许Map将自己作为一个键
虽然允许map将自己作为值,但要特别小心:如果将map作为值,则该map的equals和hashCode方法不再被明确地定义。

所有通用的实现map的类应该提供两个“标准”构造方法:空的构造方法(用来创建空的map)和带单个参数、参数类型为Map的构造方法(用来创建一个与其参数相同的键-值映射的新map)。实际上,后一个构造方法允许用户复制任一map,从而生成和所需类等同的map。没有办法强制此建议(因为接口没有构造方法),但是JDK中所有通用的map实现都符合。

如果该map不支持该接口包含的“破坏性”方法,则指定抛出UnsupportedOperationException。如果是这种情况,且如果调用方法对map没有影响,则这些方法将(但不是一定要)抛出UnsupportedOperationException。例如,在不可修改的map上调用putAll(Map)方法,如果要“叠加”(superimposed)其映射的map为空,则可能(但不是必需)抛出异常

一些map实现对键和值有限制。例如,有的实现禁止空(null)键和空(null)值,一些对键的类型有限制。尝试添加不合要求的元素(ineligible)会抛出不确认的异常,通常是NullPointerException或者ClassCastException。试图查询不合要求元素的存在时可能会抛出异常,或者只是简单的返回false。更通常的情况是,尝试对不合格元素进行操作,如果完成操作不会导致不合格元素插入到集合中,则可能会引发异常,或者根据实现的选择(at the option of the implementation),可能会成功。在该接口的实现规范中这样的异常被标记为“可选”。

Collection框架接口中的许多方法都是根据equals方法定义的。例如,containsKey(Object key)方法的规范是“当且仅当该map包含(key==null?k==null : key.equals(k))的键k的映射时,返回true”。但不应将此理解为使用非空参数k调用Map.containsKey会导致使用任何键k都可以调用key.equals(k)。实现的类可以自由的实现优化从而避免调用equals(Implementation are free to implement optimizations whereby the equals invocation is avoided),例如比较两个键的哈希码。一般来说,各种集合框架接口的实现可以自由地利用底层Object方法的指定行为(specified behavior),只要实现者认为合适。

执行map递归遍历(recursive traversal)的某些map操作会直接或间接将map自己包含进来,从而抛出异常导致失败。这些操作包括clone(), equals(), hashCode()和toString()。实现的类可以有选择的处理自引用的情况,但当前大多数实现都不这样做。

该接口是Java Collections框架的一员。

常用方法

  1. int size()
    返回map的键值对数量。如果元素个数大于Integer.MAX_VALUE,返回Integer.MAX_VALUE
  2. boolean isEmpty()
    如果map不含有键值对则返回true
  3. boolean containsKey(Object key)
    如果该map含有特定的键,则返回true。或者更确切的说,当且仅当该map含有满足(key==null ? k=null : key.equals(k))的key时则返回true。(最多只有一个)
  4. boolean containsValue(Object value)
    如果该map含有一个或多个关联特定值的键,则返回true。或者更确切的说,当且仅当该map含有至少一个满足(value==null ? v=null : value.equals(v))的value时则返回true。对于Map接口的大多数实现,此操作可能需要map大小的线性时间(time linear in the map size)。
  5. V get(Object key)
    返回特定键映射的值,如果该map不包含该键则返回null
    更确切的说,如果该map包含键值对k-v其中k满足(key==null ? k==null ; key.equals(k))则返回v,否则返回null。
    如果该map允许null值,则返回null值并不意味着该map不含有指定的键;也可能是key对应的值就是null,使用containsKey() 来区分这两种情况。
  6. V put(K key, V value)
    将指定的值和指定的键连接起来(可选操作)。如果map之前含有该键的映射,则旧的值被指定的值替代。(当且仅当m.containsKey(k)返回true的时候,才能说map m含有key k的映射
    返回的值是:之前和键关联的值(the previous value associated with key),或者null(如果没有key的映射)。(如果map的实现支持null值,则返回的null指的也可以是之前和key关联的值为null)
  7. V remove(Object key)
    如果存在该键,则从该map移除该键的映射(可选操作)。更正式来说,如果该map包含键值对k-v其中k满足(key==null ? k==null ; key.equals(k)),则移除该键值对(map最多包含一个这样的键值对)
    返回之前和键关联的值,或者null如果没有该键的映射。如果该map允许null值,则返回的null指的也可以是之前和key关联的值为null。
  8. void putAll(Map<? extends K, ? extends V> m)
    将指定map的键值对复制到该map(可选操作)。该调用等同于为特定map的每个键值对调用put(k , v)。如果指定map在操作过程中被修改,则该操作的行为不可测。
  9. void clear()
    清除该map的所有键值对(可选操作)。在该调用返回后,map立刻变为空。
  10. Set keySet()
    返回一个该map键的Set集。set由map支持,因此对map的改动会反映在set中,反之亦然。如果在对set进行遍历的时候修改了map(除了通过迭代器自己的remove操作以外),则迭代的结果是未定义的。该set支持元素删除,即通过Iterator.remove, Set.remove, removeAll, retainAll和clear操作从map中删除相应的映射。不支持add或addAll操作。
  11. Collection values()
    返回一个该map值的Collection。collection由map支持,因此对map的改动会反映在collection中,反之亦然。如果在对collection进行遍历的时候修改了map(除了通过迭代器自己的remove操作以外),则迭代的结果是未定义的。该collection支持元素删除,即通过Iterator.remove, Collection.remove, removeAll, retainAll和clear操作从map中删除相应的映射。不支持add或addAll操作。
  12. Set<Map.Entry<K, V>> entrySet()
    返回一个该map键值对的Set集。set由map支持,因此对map的改动会反映在set中,反之亦然。如果在对set进行遍历的时候修改了map(通过迭代器自己的remove操作或者对迭代器返回的map entry进行setValue操作除外),则迭代的结果是未定义的。该set支持元素删除,即通过Iterator.remove, Set.remove, removeAll, retainAll和clear操作从map中删除相应的映射。不支持add或addAll操作。
  13. boolean equals(Object o)
    比较该map的特定对象。如果给定的对象也是map且两个map有相同的键值对,则返回true。更确切的说,如果两个map m1和m2有相同的键值对即 m1.entrySet().equals(m2.entrySet())。这能确保equals方法在Map接口不同的实现类中都能正常工作。
  14. int hashCode()
    返回该map的哈希码值。map的哈希码被定义为map的entrySet()中所有哈希码的总和。 这确保m1.equals(m2)意味着任何两个映射m1和m2的m1.hashCode()== m2.hashCode(),这是Object.hashCode()的常规规定所要求的。

HashMap

Class HashMap<K,V>
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable

介绍
哈希表基于Map接口的实现。该实现提供map的所有可选操作,并允许null值和null键。(HashMap大致相当于Hashtable,除了HashMap不是线程同步以及允许null值)该类不保证map的顺序,特别是,不能保证顺序在恒定时间内保持不变。

此类为基本操作(get和put)提供恒定时间性能(constant time performance),假设hash函数合理的分配元素。迭代此集合需要与HashMap实例的“容量(capacity)”(存储桶数(the number of buckets))加上其大小(即键值对的数量)之和成比例的时间。因此,如果要追求迭代的性能,注意不要把初始容量设置得太大也不要把负载系数(load factor)设置得太低。

HashMap实例有两个变量影响其性能:初始容量(initial capacity)和负载系数(load factor)。容量是hash表的存储桶数,而初始容量则是创建hash表时的容量。负载是在其容量自动增加之前衡量哈希表达到满载时能负载多少。当哈希表的输入量超过负载系数和现有容量,则哈希表被重哈希(rehashed)(也就是重建内部数据接口),所以哈希表有将近两倍的桶数。

通常,默认负载因数(0.75)能更好的权衡时间和空间成本。更高的负载因数(大于0.75)减少了空间开销但是增加了查找成本(lookup cost)(反映在HashMap类大多数操作上,包括get和put)。设置初始容量时要考虑map的输入量和负载因数,减少rehash操作的次数。如果初始容量比负载因数分割的最大输入数还要大的话,则不会发生rehas操作。

如果有许多键值对存储在HashMap实例中,则创建HashMap是设置足够大的容量要比map自动重哈希(rehashing)的效率高得多。注意用许多哈希码相同的键会降低哈希表的性能。当键具有可比性(are Comparable)时,可以使用键之间的比较顺序(comparison order)来帮助断开关系(break ties),缓解对性能的影响。

注意:此实现线程不同步。如果多个线程同时访问hash map,并且至少有一个线程在结构上修改了映射,则必须在外部进行同步。结构修改指的是添加或删除一个或多个键值对,仅仅改变实例的键对应的值不是结构性的修改。通常通过自然封装了映射的对象来实现线程同步。如果不存在这样的对象,则要使用Collections.synchronizedMap方法“包装”映射。该操作最好在创建时完成,以防止不同步的访问意外地发生:
Map m = Collections.synchronizedMap(new HashMap(…));

该类所有集合观的方法返回的迭代器是fail-fast。如果在迭代器创建后的任何时候对列表进行结构性的修改,则除了迭代器自身的remove方法外,其他所有方法都会导致迭代器抛出ConcurrentModificationException异常。因此,在出现并发修改的情况下,迭代器会快速地失败报错,从而避免了在将来未知的时候出现不确定的行为。

要注意的是fail-fast应该仅用于检测bug

该类是Java集合框架中的一员。

构造方法

  1. public HashMap(int initialCapacity, float loadFactor)
    用特定的初始容量和负载系数构造空的HashMap
  2. public HashMap(int initialCapacity)
    用特定的初始容量和默认的负载系数(0.75)构造空的HashMap
  3. public HashMap()
    构造一个空的HashMap,其中初始容量为默认的16,负载系数为默认的0.75
  4. public HashMap(Map<? extends K, ? extends V> m)
    用指定Map的键值对构造一个新的HashMap,即新HashMap的键值对和指定Map的一样。该HashMap的默认负载系数是0.75,容量是指定Map键值对的数量。

常用方法

  1. public int size()
    返回该map的键值对数量。
  2. public boolean isEmpty()
    如果该map不含有键值对则返回true
  3. public V get(Object key)
    返回特定键映射的值,如果该map不包含该键则返回null
    更确切的说,如果该map包含键值对k-v其中k满足**(keynull ? knull ; key.equals(k))**则返回v,否则返回null。
    如果该map允许null值,则返回null值并不意味着该map不含有指定的键;也可能是key对应的值就是null,使用containsKey() 来区分这两种情况。
  4. public boolean containsKey(Object key)
    如果该map含有特定的键,则返回true。
  5. public V put(K key, V value)
    将指定的值和指定的键连接起来。如果map之前含有该键的映射,则旧的值被指定的值替代。(当且仅当m.containsKey(k)返回true的时候,才能说map m含有key k的映射)
    返回的值是:
    之前和键关联的值(the previous value associated with key),或者null(如果没有key的映射)。(如果map的实现支持null值,则返回的null指的也可以是之前和key关联的值为null)
  6. public void putAll(Map<? extends K, ? extends V> m)
    将指定map的键值对复制到该map。
  7. public V remove(Object key)
    如果存在该键,则从该map移除该键的映射
  8. public void clear()
    清除该map的所有键值对。在该调用返回后,map立刻变为空。
  9. public boolean containsValue(Object value)
    如果该map含有一个或多个关联特定值的键,则返回true。
  10. public Set keySet()
    返回一个该map键的Set集。set由map支持,因此对map的改动会反映在set中,反之亦然。如果在对set进行遍历的时候修改了map(除了通过迭代器自己的remove操作以外),则迭代的结果是未定义的。该set支持元素删除,即通过Iterator.remove, Set.remove, removeAll, retainAll和clear操作从map中删除相应的映射。不支持add或addAll操作。
  11. public Collection values()
    返回一个该map值的Collection。collection由map支持,因此对map的改动会反映在collection中,反之亦然。如果在对collection进行遍历的时候修改了map(除了通过迭代器自己的remove操作以外),则迭代的结果是未定义的。该collection支持元素删除,即通过Iterator.remove, Collection.remove, removeAll, retainAll和clear操作从map中删除相应的映射。不支持add或addAll操作。
  12. public Set<Map.Entry<K, V>> entrySet()
    返回一个该map键值对的Set集。set由map支持,因此对map的改动会反映在set中,反之亦然。如果在对set进行遍历的时候修改了map(通过迭代器自己的remove操作或者对迭代器返回的map entry进行setValue操作除外),则迭代的结果是未定义的。该set支持元素删除,即通过Iterator.remove, Set.remove, removeAll, retainAll和clear操作从map中删除相应的映射。不支持add或addAll操作。

TreeMap

Class TreeMap<K,V>
public class TreeMap<K,V>
extends AbstractMap<K,V>
implements NavigateMap<K,V>, Cloneable, Serializable

介绍
红黑树基于NavigableMap实现。该map根据其键的自然排序或者根据map创建时根据构造方法提供的Comparator来排序。

该实现为containsKey.get, put和remove操作提供有保障的log(n)时间成本。

注意如果该排序的map(sorted map)要正确的实现Map接口,则不论是否显式地提供了comparator,和任一排序的map一样,tree map维护的顺序都要和equals保持一致(be consistent with equals),这是因为Map接口是由equals方法定义的,但是排序的map是用compareTo(或compare)方法来进行所有键的比较,所以从排序的map角度来看,用该方法比较的两个键是相等的。即便其排序和equals不一致,map的行为也是被明确定义的;只是没有遵循Map接口的一般要求

注意:此实现线程不同步。如果多个线程同时访问map,并且至少有一个线程在结构上修改了映射,则必须在外部进行同步。结构修改指的是添加或删除一个或多个键值对,仅仅改变实例的键对应的值不是结构性的修改。通常通过自然封装了映射的对象来实现线程同步。如果不存在这样的对象,则要使用Collections.synchronizedSortedMap方法“包装”映射。该操作最好在创建时完成,以防止不同步的访问意外地发生:
SortedMap m = Collections.synchronizedSortedMap(new TreeMap(…));

该类所有集合观的方法返回的迭代器是fail-fast。如果在迭代器创建后的任何时候对列表进行结构性的修改,则除了迭代器自身的remove方法外,其他所有方法都会导致迭代器抛出ConcurrentModificationException异常。因此,在出现并发修改的情况下,迭代器会快速地失败报错,从而避免了在将来未知的时候出现不确定的行为。

All Map.Entry pairs returned by methods in this class and its views represent snapshots of mappings at the time they were produced. They do not support the Entry.setValue method.
(注意可以使用put改变关联map的映射)

该类是Java集合框架中的一员。

构造方法:

  1. public TreeMap()
    构造一个新的,空的tree set,并根据键自然顺序排序。所有插入到该map的元素必须实现Comparable接口。更近一步说,所有的键都是可以相互比较的(mutually comparable):对于任意键k1和k2,k1.compareTo(k2)不准抛出ClassCastException。
    如果用户试图向map中添加键而违反了限制(例如键是integers,而用户添加的是string),则会抛出ClassCastException。
  2. public TreeMap(Comparator<? super E> comparator)
    构造一个新的,空的tree map,并根据给定的comparator排序。所有的键都是可以相互比较的:对于任意键k1和k2,k1.compareTo(k2)不准抛出ClassCastException。
    如果用户试图向map中添加键而违反了限制,则put(Object key, Object value)会抛出ClassCastException。
  3. public TreeMap(Map<? extends K, ? extends V> m)
    构造一个新的,包含指定集合元素的 tree set,并根据指定集合元素的自然顺序排序。所有插入到该map的元素必须实现Comparable接口。更近一步说,所有的键都是可以相互比较的(mutually comparable):对于任意键k1和k2,k1.compareTo(k2)不准抛出ClassCastException。该方法运行时间为n*log(n)
  4. public TreeMap(SortedMap<K, ? extends V> m)
    构造一个新的tree map包含指定sorted map的相同元素并按相同顺序排列。该方法运行为线性时间。

常用方法

  1. public int size()
    返回该map的键值对数量。
  2. public boolean containsKey(Object key)
    如果该map含有特定的键,则返回true。
  3. public boolean containsValue(Object value)
    如果该map含有一个或多个关联特定值的键,则返回true。
  4. public V get(Object key)
    返回特定键映射的值,如果该map不包含该键则返回null
    更确切的说,如果该map包含的键值对k-v,其中k按顺序和key比较是相等的,则返回v,否则返回null。
    如果该map允许null值,则返回null值并不意味着该map不含有指定的键;也可能是key对应的值就是null,使用containsKey() 来区分这两种情况。
  5. public Comparator<? super K> comparator()
    返回用来给该map键排序的comparator,或者null如果该map用的是键的自然排序
  6. public K firstKey()
    返回该map目前第一个(最低的)键
  7. public K lastKey()
    返回该map目前最后一个(最高的)键
  8. public void putAll(Map<? extends K, ? extends V> m)
    将指定map的键值对复制到该map。
  9. public V put(K key, V value)
    将指定的值和指定的键连接起来。如果map之前含有该键的映射,则旧的值被指定的值替代。(当且仅当m.containsKey(k)返回true的时候,才能说map m含有key k的映射)
    返回的值是:
    之前和键关联的值(the previous value associated with key),或者null(如果没有key的映射)。(如果map的实现支持null值,则返回的null指的也可以是之前和key关联的值为null)
  10. public V remove(Object key)
    如果指定的键在该TreeMap中存在则移除该键
  11. public void clear()
    移除该map中的所有键值对。在该调用返回后map将为空。
  12. public Object clone()
    返回该TreeMap实例的浅复制(shallow copy)。(键值对自身不被复制)
  13. public Map.Entry<K,V> firstEntry()
    返回该map最小键的键值对,或者null如果map为空
  14. public Map.Entry<K,V> lastEntry()
    返回该map最大键的键值对,或者null如果map为空
  15. public Set keySet()
    返回该map键的Set集
    该set迭代器返回的键是升序排列的。set的spliterator是late-binding, fail-fast,此外遇到的顺序为键的升序时报出Spliterator.SORTED和Spliterator.ORDERED。如果tree map的比较器是null,则spliterator的比较器也是null。spliterator的比较器和tree map的比较器一样。
    返回一个该map键的Set集。set由map支持,因此对map的改动会反映在set中,反之亦然。如果在对set进行遍历的时候修改了map(除了通过迭代器自己的remove操作以外),则迭代的结果是未定义的。该set支持元素删除,即通过Iterator.remove, Set.remove, removeAll, retainAll和clear操作从map中删除相应的映射。不支持add或addAll操作。
  16. public Collection values()
    返回一个该map值的Collection。
    该集合的迭代器返回的值是按相关键的升序排列的。集合的spliterator是late-binding, fail-fast,此外遇到的顺序为相关键的升序时报出Spliterator.ORDERED。
    collection由map支持,因此对map的改动会反映在collection中,反之亦然。如果在对collection进行遍历的时候修改了map(除了通过迭代器自己的remove操作以外),则迭代的结果是未定义的。该collection支持元素删除,即通过Iterator.remove, Collection.remove, removeAll, retainAll和clear操作从map中删除相应的映射。不支持add或addAll操作。
  17. public Set<Map.Entry<K, V>> entrySet()
    返回一个该map键值对的Set集。
    该set的迭代器返回的值是按相关键的升序排列的。集合的spliterator是late-binding, fail-fast,此外遇到的顺序为相关键的升序时报出Spliterator.SORTED和Spliterator.ORDERED。
    set由map支持,因此对map的改动会反映在set中,反之亦然。如果在对set进行遍历的时候修改了map(通过迭代器自己的remove操作或者对迭代器返回的map entry进行setValue操作除外),则迭代的结果是未定义的。该set支持元素删除,即通过Iterator.remove, Set.remove, removeAll, retainAll和clear操作从map中删除相应的映射。不支持add或addAll操作。

总结

Map(接口) 维持“键-值”对应关系(对),以便通过一个键查找相应的值
HashMap* 基于一个散列表实现(用它代替Hashtable)。针对“键-值”对的插入和检索,这种形式具有最稳定的性能。可通过构建器对这一性能进行调整,以便设置散列表的“能力”和“装载因子”
ArrayMap 由一个ArrayList后推得到的Map。对反复的顺序提供了精确的控制。面向非常小的Map设计,特别是那些需要经常创建和删除的。对于非常小的Map,创建和反复所付出的代价要比HashMap低得多。但在Map变大以后,性能也会相应地大幅度降低
TreeMap 在一个“红-黑”树的基础上实现。查看键或者“键-值”对时,它们会按固定的顺序排列(取决于Comparable或Comparator,稍后即会讲到)。TreeMap最大的好处就是我们得到的是已排好序的结果。TreeMap是含有subMap()方法的唯一一种Map,利用它可以返回树的一部分

例子

//: Map1.java
// Things you can do with Maps
package c08.newcollections;
import java.util.*;

public class Map1 {
  public final static String[][] testData1 = {
    { "Happy", "Cheerful disposition" },
    { "Sleepy", "Prefers dark, quiet places" },
    { "Grumpy", "Needs to work on attitude" },
    { "Doc", "Fantasizes about advanced degree"},
    { "Dopey", "'A' for effort" },
    { "Sneezy", "Struggles with allergies" },
    { "Bashful", "Needs self-esteem workshop"},
  };
  public final static String[][] testData2 = {
    { "Belligerent", "Disruptive influence" },
    { "Lazy", "Motivational problems" },
    { "Comatose", "Excellent behavior" }
  };
  public static Map fill(Map m, Object[][] o) {
    for(int i = 0; i < o.length; i++)
      m.put(o[i][0], o[i][1]);
    return m;
  }
  // Producing a Set of the keys:
  public static void printKeys(Map m) {
    System.out.print("Size = " + m.size() +", ");
    System.out.print("Keys: ");
    Collection1.print(m.keySet());
  }
  // Producing a Collection of the values:
  public static void printValues(Map m) {
    System.out.print("Values: ");
    Collection1.print(m.values());
  }
  // Iterating through Map.Entry objects (pairs):
  public static void print(Map m) {
    Collection entries = m.entries();
    Iterator it = entries.iterator();
    while(it.hasNext()) {
      Map.Entry e = (Map.Entry)it.next();
      System.out.println("Key = " + e.getKey() +
        ", Value = " + e.getValue());
    }
  }
  public static void test(Map m) {
    fill(m, testData1);
    // Map has 'Set' behavior for keys:
    fill(m, testData1);
    printKeys(m);
    printValues(m);
    print(m);
    String key = testData1[4][0];
    String value = testData1[4][1];
    System.out.println("m.containsKey(\"" + key +
      "\"): " + m.containsKey(key));
    System.out.println("m.get(\"" + key + "\"): "
      + m.get(key));
    System.out.println("m.containsValue(\"" 
      + value + "\"): " + 
      m.containsValue(value)); 
    Map m2 = fill(new TreeMap(), testData2);
    m.putAll(m2);
    printKeys(m);
    m.remove(testData2[0][0]);
    printKeys(m);
    m.clear();
    System.out.println("m.isEmpty(): " 
      + m.isEmpty());
    fill(m, testData1);
    // Operations on the Set change the Map:
    m.keySet().removeAll(m.keySet());
    System.out.println("m.isEmpty(): " 
      + m.isEmpty());
  }
  public static void main(String args[]) {
    System.out.println("Testing HashMap");
    test(new HashMap());
    System.out.println("Testing TreeMap");
    test(new TreeMap());
  }
} ///:~


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值