Java集合类的学习(五)——Map

BLDC六步换相驱动实战:从霍尔真值表到PWM调速的代码实现 本文详细解析了BLDC六步换相驱动原理,从霍尔真值表解读到PWM调速的代码实现,提供了基于STM32的实战方案。重点介绍了霍尔信号解码、换相状态机设计以及PWM动态调速策略,帮助开发者快速掌握无刷电机驱动技术。 阅读详情

1.4         Map 

1.4.1        概述

数学中的映射关系在Java中就是通过Map来实现的。它表示,里面存储的元素是一个对(pair),我们通过一个对象,可以在这个映射关系中找到另外一个和这个对象相关的东西。

前面提到的我们对于根据帐号名得到对应的人员的信息,就属于这种情况的应用。我们讲一个人员的帐户名和这人员的信息作了一个映射关系,也就是说,我们把帐户名和人员信息当成了一个“键值对”,“键”就是帐户名,“值”就是人员信息。下面我们先看看Map 接口的常用方法。

1.4.2         常用方法

Map 接口不是 Collection 接口的继承。而是从自己的用于维护键-值关联的接口层次结构入手。按定义,该接口描述了从不重复的键到值的映射。

我们可以把这个接口方法分成三组操作:改变、查询和提供可选视图。

改变操作允许您从映射中添加和除去键-值对。键和值都可以为 null。但是,您不能把 Map 作为一个键或值添加给自身。

u       Object put(Object key,Object value):用来存放一个键-值对Map中

u       Object remove(Object key):根据key(键),移除一个键-值对,并将值返回

u       void putAll(Map mapping) :将另外一个Map中的元素存入当前的Map中

u       void clear() :清空当前Map中的元素

查询操作允许您检查映射内容:

u       Object get(Object key) :根据key(键)取得对应的值

u       boolean containsKey(Object key) :判断Map中是否存在某键(key)

u       boolean containsValue(Object value):判断Map中是否存在某值(value)

u       int size():返回Map中 键-值对的个数

u       boolean isEmpty() :判断当前Map是否为空

最后一组方法允许您把键或值的组作为集合来处理。

u       public Set keySet() :返回所有的键(key),并使用Set容器存放

u       public Collection values() :返回所有的值(Value),并使用Collection存放

u       public Set entrySet() :返回一个实现 Map.Entry 接口的元素 Set

因为映射中键的集合必须是唯一的,就使用 Set 来支持。因为映射中值的集合可能不唯一,就使用 Collection 来支持。最后一个方法返回一个实现 Map.Entry 接口的元素 Set。

我们看看Map的常用实现类的比较,如下表:

 

简述 

实现 

操作特性 

成员要求 

Map

保存键值对成员,基于键找值操作,使用compareTo或compare方法对键进行排序

HashMap

能满足用户对Map的通用需求 

键成员可为任意Object子类的对象,但如果覆盖了equals方法,同时注意修改hashCode方法。 

TreeMap

支持对键有序地遍历,使用时建议先用HashMap增加和删除成员,最后从HashMap生成TreeMap; 附加实现了SortedMap接口,支持子Map等要求顺序的操作 

键成员要求实现Comparable接口,或者使用Comparator构造TreeMap键成员一般为同一类型。 

LinkedHashMap

保留键的插入顺序,用equals 方法检查键和值的相等性 

成员可为任意Object子类的对象,但如果覆盖了equals方法,同时注意修改hashCode方法。 

下面我们看一个简单的例子:

import java.util.*; 

public class MapTest {

public static void main(String[] args) {

Map map1 = new HashMap();

Map map2 = new HashMap();

map1.put("1","aaa1");

map1.put("2","bbb2");

map2.put("10","aaaa10");

map2.put("11","bbbb11");

//根据键 "1" 取得值:"aaa1"

System.out.println("map1.get(\"1\")="+map1.get("1"));

// 根据键 "1" 移除键值对"1"-"aaa1"

System.out.println("map1.remove(\"1\")="+map1.remove("1"));

System.out.println("map1.get(\"1\")="+map1.get("1"));

map1.putAll(map2);//将map2全部元素放入map1中

map2.clear();//清空map2

System.out.println("map1 IsEmpty?="+map1.isEmpty());

System.out.println("map2 IsEmpty?="+map2.isEmpty());

System.out.println("map1 中的键值对的个数size = "+map1.size());

System.out.println("KeySet="+map1.keySet());//set

System.out.println("values="+map1.values());//Collection

System.out.println("entrySet="+map1.entrySet());

System.out.println("map1 是否包含键:11 = "+map1.containsKey("11"));

System.out.println("map1 是否包含值:aaa1 = "+map1.containsValue("aaa1"));

}

}

运行输出结果为:

map1.get("1")=aaa1

map1.remove("1")=aaa1

map1.get("1")=null

map1 IsEmpty?=false

map2 IsEmpty?=true

map1 中的键值对的个数size = 3

KeySet=[10, 2, 11]

values=[aaaa10, bbb2, bbbb11]

entrySet=[10=aaaa10, 2=bbb2, 11=bbbb11]

map1 是否包含键:11 = true

map1 是否包含值:aaa1 = false

在该例子中,我们创建一个HashMap,并使用了一下Map接口中的各个方法。

其中Map中的entrySet()方法先提一下,该方法返回一个实现 Map.Entry 接口的对象集合。集合中每个对象都是底层 Map 中一个特定的键-值对。

Map.Entry 接口是Map 接口中的一个内部接口,该内部接口的实现类存放的是键值对。在下面的实现原理中,我们会对这方面再作介绍,现在我们先不管这个它的具体实现。

我们再看看排序的Map是如何使用:

import java.util.*;

 

public class MapSortExample {

  public static void main(String args[]) {

    Map map1 = new HashMap(); 

    Map map2 = new LinkedHashMap();

    for(int i=0;i<10;i++){

     double s=Math.random()*100;//产生一个随机数,并将其放入Map中

      map1.put(new Integer((int) s),"第 "+i+" 个放入的元素:"+s+"\n");

      map2.put(new Integer((int) s),"第 "+i+" 个放入的元素:"+s+"\n");

    }

    

    System.out.println("未排序前HashMap:"+map1);

    System.out.println("未排序前LinkedHashMap:"+map2);

    //使用TreeMap来对另外的Map进行重构和排序

    Map sortedMap = new TreeMap(map1);

    System.out.println("排序后:"+sortedMap);

    System.out.println("排序后:"+new TreeMap(map2));

  }

}

该程序的一次运行结果为:

未排序前HashMap:{64=第 1 个放入的元素:64.05341725531845

, 15=第 9 个放入的元素:15.249165766266382

, 2=第 4 个放入的元素:2.66794706854534

, 77=第 0 个放入的元素:77.28814965781416

, 97=第 5 个放入的元素:97.32893518378948

, 99=第 2 个放入的元素:99.99412014935982

, 60=第 8 个放入的元素:60.91451419025399

, 6=第 3 个放入的元素:6.286974058646977

, 1=第 7 个放入的元素:1.8261658496439903

, 48=第 6 个放入的元素:48.736039522423106

}

未排序前LinkedHashMap:{77=第 0 个放入的元素:77.28814965781416

, 64=第 1 个放入的元素:64.05341725531845

, 99=第 2 个放入的元素:99.99412014935982

, 6=第 3 个放入的元素:6.286974058646977

, 2=第 4 个放入的元素:2.66794706854534

, 97=第 5 个放入的元素:97.32893518378948

, 48=第 6 个放入的元素:48.736039522423106

, 1=第 7 个放入的元素:1.8261658496439903

, 60=第 8 个放入的元素:60.91451419025399

, 15=第 9 个放入的元素:15.249165766266382

}

排序后:{1=第 7 个放入的元素:1.8261658496439903

, 2=第 4 个放入的元素:2.66794706854534

, 6=第 3 个放入的元素:6.286974058646977

, 15=第 9 个放入的元素:15.249165766266382

, 48=第 6 个放入的元素:48.736039522423106

, 60=第 8 个放入的元素:60.91451419025399

, 64=第 1 个放入的元素:64.05341725531845

, 77=第 0 个放入的元素:77.28814965781416

, 97=第 5 个放入的元素:97.32893518378948

, 99=第 2 个放入的元素:99.99412014935982

}

排序后:{1=第 7 个放入的元素:1.8261658496439903

, 2=第 4 个放入的元素:2.66794706854534

, 6=第 3 个放入的元素:6.286974058646977

, 15=第 9 个放入的元素:15.249165766266382

, 48=第 6 个放入的元素:48.736039522423106

, 60=第 8 个放入的元素:60.91451419025399

, 64=第 1 个放入的元素:64.05341725531845

, 77=第 0 个放入的元素:77.28814965781416

, 97=第 5 个放入的元素:97.32893518378948

, 99=第 2 个放入的元素:99.99412014935982

}

从运行结果,我们可以看出,HashMap的存入顺序和输出顺序无关。而LinkedHashMap 则保留了键值对的存入顺序。TreeMap则是对Map中的元素进行排序。在实际的使用中我们也经常这样做:使用HashMap或者LinkedHashMap 来存放元素,当所有的元素都存放完成后,如果使用则是需要一个经过排序的Map的话,我们再使用TreeMap来重构原来的Map对象。这样做的好处是:因为HashMap和LinkedHashMap 存储数据的速度比直接使用TreeMap 要快,存取效率要高。当完成了所有的元素的存放后,我们再对整个的Map中的元素进行排序。这样可以提高整个程序的运行的效率,缩短执行时间。

这里需要注意的是,TreeMap中是根据键(Key)进行排序的。而如果我们要使用TreeMap来进行正常的排序的话,Key 中存放的对象必须实现Comparable 接口。

我们简单介绍一下这个接口:

1.4.3        Comparable 接口 

在 java.lang 包中,Comparable 接口适用于一个类有自然顺序的时候。假定对象集合是同一类型,该接口允许您把集合排序成自然顺序。

它只有一个方法:compareTo() 方法,用来比较当前实例和作为参数传入的元素。如果排序过程中当前实例出现在参数前(当前实例比参数大),就返回某个负值。如果当前实例出现在参数后(当前实例比参数小),则返回正值。否则,返回零。如果这里不要求零返回值表示元素相等。零返回值可以只是表示两个对象在排序的时候排在同一个位置。

上面例子中的整形的包装类:Integer 就实现了该接口。我们可以看一下这个类的源码:

可以看到compareTo 方法里面通过判断当前的Integer对象的值是否大于传入的参数的值来得到返回值的。

在 Java 2 SDK,版本 1.2 中有十四个类实现 Comparable 接口。下表展示了它们的自然排序。虽然一些类共享同一种自然排序,但只有相互可比的类才能排序。

类

排序

BigDecimal, BigInteger, Byte, Double, Float, Integer, Long, Short 

按数字大小排序

Character 

按 Unicode 值的数字大小排序

CollationKey 

按语言环境敏感的字符串排序

Date 

按年代排序

File 

按系统特定的路径名的全限定字符的 Unicode 值排序

ObjectStreamField 

按名字中字符的 Unicode 值排序

String 

按字符串中字符 Unicode 值排序

 

这里只是简单的介绍一下排序接口,如果要详细的了解排序部分内容的话,可以参考文章最后的附录部分对于排序的更加详细的描述。

我们再回到Map中来,Java提高的API中除了上面介绍的几种Map比较常用以为还有一些Map,大家可以了解一下:

u   WeakHashMap: WeakHashMap 是 Map 的一个特殊实现,它只用于存储对键的弱引用。当映射的某个键在 WeakHashMap 的外部不再被引用时,就允许垃圾收集器收集映射中相应的键值对。使用 WeakHashMap 有益于保持类似注册表的数据结构,其中条目的键不再能被任何线程访问时,此条目就没用了。

u   IdentifyHashMap: Map的一种特性实现,关键属性的hash码不是由hashCode()方法计算,而是由System.identityHashCode 方法计算,使用==进行比较而不是equals()方法。

通过简单的对与Map中各个常用实现类的使用,为了更好的理解Map,下面我们再来了解一下Map的实现原理。

1.4.4         实现原理

有的人可能会认为 Map 会继承 Collection。在数学中,映射只是对(pair)的集合。但是,在“集合框架”中,接口 Map 和 Collection 在层次结构没有任何亲缘关系,它们是截然不同的。这种差别的原因与 Set 和 Map 在 Java 库中使用的方法有关。Map 的典型应用是访问按关键字存储的值。它支持一系列集合操作的全部,但操作的是键-值对,而不是单个独立的元素。因此 Map 需要支持 get() 和 put() 的基本操作,而 Set 不需要。此外,还有返回 Map 对象的 Set 视图的方法:

Set set = aMap.keySet();

下面我们以HashMap为例,对Map的实现机制作一下更加深入一点的理解。

因为HashMap里面使用Hash算法,所以在理解HashMap之前,我们需要先了解一下Hash算法和Hash表。

Hash,一般翻译做“散列”,也有直接音译为"哈希"的,就是把任意长度的输入(又叫做 预映射, pre-image),通过散列算法,变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能 会散列成相同的输出,而不可能从散列值来唯一的确定输入值。

说的通俗一点,Hash算法的意义在于提供了一种快速存取数据的方法,它用一种算法建立键值与真实值之间的对应关系,(每一个真实值只能有一个键值,但是一个键值可以对应多个真实值),这样可以快速在数组等里面存取数据。

我们建立一个HashTable(哈希表),该表的长度为N,然后我们分别在该表中的格子中存放不同的元素。每个格子下面存放的元素又是以链表的方式存放元素。

u       当添加一个新的元素Entry 的时候,首先我们通过一个Hash函数计算出这个Entry元素的Hash值hashcode。通过该hashcode值,就可以直接定位出我们应该把这个Entry元素存入到Hash表的哪个格子中,如果该格子中已经存在元素了,那么只要把新的Entry元存放到这个链表中即可。

u       如果要查找一个元素Entry的时候,也同样的方式,通过Hash函数计算出这个Entry元素的Hash值hashcode。然后通过该hashcode值,就可以直接找到这个Entry是存放到哪个格子中的。接下来就对该格子存放的链表元素进行逐个的比较查找就可以了。

 举一个比较简单的例子来说明这个算法的运算方式:

假定我们有一个长度为8的Hash表(可以理解为一个长度为8的数组)。在这个Hash表中存放数字:如下表

0

1

2

3

4

5

6

7

假定我们的Hash函数为:

Hashcode = X%8    -------- 对8 取余数

其中X就是我们需要放入Hash表中的数字,而这个函数返回的Hashcode就是Hash码。

假定我们有下面10个数字需要依次存入到这个Hash表中:

11 , 23 , 44 , 9 , 6 , 32 , 12 , 45 , 57 , 89

通过上面的Hash函数,我们可以得到分别对应的Hash码:

11――3 ; 23――7 ;44――4 ;9――1;6――6;32――0;12――4;45――5;57――1;89――1;

计算出来的Hash码分别代表,该数字应该存放到Hash表中的哪个对应数字的格子中。如果改格子中已经有数字存在了,那么就以链表的方式将数字依次存放在该格子中,如下表:

0

1

2

3

4

5

6

7

32

9

 

11

44

45

6

23

 

57

 

 

12

 

 

 

 

89

 

 

 

 

 

 

Hash表和Hash算法的特点就是它的存取速度比数组差一些,但是比起单纯的链表,在查找和存储方面却要好很多。同时数组也不利于数据的重构而排序等方面的要求。

更具体的说明,读者可以参考数据结构相关方面的书籍。

 

简单的了解了一下Hash算法后,我们就来看看HashMap的属性有哪些:

里面最重要的3个属性:

        transient Entry[] table: 用来存放键值对的对象Entry数组,也就是Hash表

         transient int size:当前Map中存放的键值对的个数

         final float loadFactor:负载因子,用来决定什么情况下应该对Entry进行扩容

我们Entry 对象是Map接口中的一个内部接口。即是使用它来保存我们的键值对的。

我们看看这个Entry 内部接口在HashMap中的实现:

 

通过查看源码,我们可以看到Entry类有点类似一个单向链表。其中:

        final Object key 和 Object value;存放的就是我们放入Map中的键值对。

而属性Entry next;表示当前键值对的下一个键值对是哪个Entry。

接下来,我们看看HashMap的主要的构造函数:

我们主要看看 public HashMap(int initialCapacity, float loadFactor)

因为,另外两个构造函数实行也是同样的方式进行构建一个HashMap 的。

该构造函数:

1.         首先是判断参数int initialCapacity和float loadFactor是否合法

2.         然后确定Hash表的初始化长度。确定的策略是:通过传进来的参数initialCapacity 来找出第一个大于它的2的次方的数。比如说我们传了18这样的一个initialCapacity 参数,那么真实的table数组的长度为2的5次方,即32。之所以采用这种策略来构建Hash表的长度,是因为2的次方的运算对于现代的处理器来说,可以通过一些方法得到更加好的执行效率。

3.         接下来就是得到重构因子(threshold)了,这个属性也是HashMap中的一个比较重要的属性,它表示,当Hash表中的元素被存放了多少个之后,我们就需要对该Hash表进行重构。

4.         最后就是使用得到的初始化参数capacity 来构建Hash表:Entry[] table。

 下面我们看看一个键值对是如何添加到HashMap中的。

   该put方法是用来添加一个键值对(key-value)到Map中,如果Map中已经存在相同的键的键值对的话,那么就把新的值覆盖老的值,并把老的值返回给方法的调用者。如果不存在一样的键,那么就返回null 。我们看看方法的具体实现:

1.         首先我们判断如果key为null则使用一个常量来代替该key值,该行为在方法maskNull()终将key替换为一个非null 的对象k。

2.         计算key值的Hash码:hash

3.         通过使用Hash码来定位,我们应该把当前的键值对存放到Hash表中的哪个格子中。indexFor()方法计算出的结果:i 就是Hash表(table)中的下标。

4.         然后遍历当前的Hash表中table[i]格中的链表。从中判断已否已经存在一样的键(Key)的键值对。如果存在一样的key的键,那么就用新的value覆写老的value,并把老的value返回

5.         如果遍历后发现没有存在同样的键值对,那么就增加当前键值对到Hash表中的第i个格子中的链表中。并返回null 。

最后我们看看一个键值对是如何添加到各个格子中的链表中的:

我们先看void addEntry(int hash, Object key, Object value, int bucketIndex)方法,该方法的作用就用来添加一个键值对到Hash表的第bucketIndex个格子中的链表中去。这个方法作的工作就是:

1.         创建一个Entry对象用来存放键值对。

2.         添加该键值对 ---- Entry对象到链表中

3.         最后在size属性加一,并判断是否需要对当前的Hash表进行重构。如果需要就在void resize(int newCapacity)方法中进行重构。

之所以需要重构,也是基于性能考虑。大家可以考虑这样一种情况,假定我们的Hash表只有4个格子,那么我们所有的数据都是放到这4个格子中。如果存储的数据量比较大的话,例如100。这个时候,我们就会发现,在这个Hash表中的4个格子存放的4个长长的链表。而我们每次查找元素的时候,其实相当于就是遍历链表了。这种情况下,我们用这个Hash表来存取数据的性能实际上和使用链表差不多了。 

但是如果我们对这个Hash表进行重构,换为使用Hash表长度为200的表来存储这100个数据,那么平均2个格子里面才会存放一个数据。这个时候我们查找的数据的速度就会非常的快。因为基本上每个格子中存放的链表都不会很长,所以我们遍历链表的次数也就很少,这样也就加快了查找速度。但是这个时候又存在了另外的一个问题。我们使用了至少200个数据的空间来存放100个数据,这样就造成至少100个数据空间的浪费。 在速度和空间上面,我们需要找到一个适合自己的中间值。在HashMap中我们通过负载因子(loadFactor)来决定应该什么时候应该重构我们的Hash表,以达到比较好的性能状态。

我们再看看重构Hash表的方法:void resize(int newCapacity)是如何实现的:

它的实现方式比较简单:

1.      首先判断如果Hash表的长度已经达到最大值,那么就不进行重构了。因为这个时候Hash表的长度已经达到上限,已经没有必要重构了。

2.      然后就是构建新的Hash表

3.      把老的Hash表中的对象数据全部转移到新的Hash表newTable中,并设置新的重构因子threshold

对于HashMap中的实现原理,我们就分析到这里。大家可能会发现,HashCode的计算,是用来定位我们的键值对应该放到Hash表中哪个格子中的关键属性。而这个HashCode的计算方法是调用的各个对象自己的实现的hashCode()方法。而这个方法是在Object对象中定义的,所以我们自己定义的类如果要在集合中使用的话,就需要正确的覆写hashCode() 方法。下面就介绍一下应该如何正确覆写hashCode()方法。

1.4.5        覆写hashCode()

在明白了HashMap具有哪些功能,以及实现原理后,了解如何写一个hashCode()方法就更有意义了。当然,在HashMap中存取一个键值对涉及到的另外一个方法为equals (),因为该方法的覆写在高级特性已经讲解了。这里就不做过多的描述。

设计hashCode()时最重要的因素就是:无论何时,对同一个对象调用hashCode()都应该生成同样的值。如果在将一个对象用put()方法添加进HashMap时产生一个hashCode()值,而用get()取出时却产生了另外一个hashCode()值,那么就无法重新取得该对象了。所以,如果你的hashCode()方法依赖于对象中易变的数据,那用户就要小心了,因为此数据发生变化时,hashCode()就会产生一个不同的hash码,相当于产生了一个不同的“键”。

此外,也不应该使hashCode()依赖于具有唯一性的对象信息,尤其是使用this的值,这只能产生很糟糕的hashCode()。因为这样做无法生成一个新的“键”,使之与put()种原始的“键值对”中的“键”相同。例如,如果我们不覆写Object的hashCode()方法,那么调用该方法的时候,就会调用Object的hashCode()方法的默认实现。Object的hashCode()方法,返回的是当前对象的内存地址。下次如果我们需要取一个一样的“键”对应的键值对的时候,我们就无法得到一样的hashCode值了。因为我们后来创建的“键”对象已经不是存入HashMap中的那个内存地址的对象了。

我们看一个简单的例子,就能更加清楚的理解上面的意思。假定我们写了一个类:Person (人),我们判断一个对象“人”是否指向同一个人,只要知道这个人的身份证号一直就可以了。

先看我们没有实现hashCode的情况:

package c08.hashEx;

import java.util.*;

//身份证类

class Code{

final int id;//身份证号码已经确认,不能改变

Code(int i){

id=i;

}

//身份号号码相同,则身份证相同

public boolean equals(Object anObject) {

if (anObject instanceof Code){

Code other=(Code) anObject;

return this.id==other.id;

}

return false;

}

public String toString() {

return "身份证:"+id;

}

}

//人员信息类

class Person {

Code id;// 身份证

String name;// 姓名

public Person(String name, Code id) {

this.id=id;

this.name=name;

}

//如果身份证号相同,就表示两个人是同一个人

public boolean equals(Object anObject) {

if (anObject instanceof Person){

Person other=(Person) anObject;

return this.id.equals(other.id);

}

return false;

}

public String toString() {

return "姓名:"+name+" 身份证:"+id.id+"\n";

}

}

public class HashCodeEx {

public static void main(String[] args) {

HashMap map=new HashMap();

Person p1=new Person("张三",new Code(123));

map.put(p1.id,p1);//我们根据身份证来作为key值存放到Map中

Person p2=new Person("李四",new Code(456));

map.put(p2.id,p2);

Person p3=new Person("王二",new Code(789));

map.put(p3.id,p3);

System.out.println("HashMap 中存放的人员信息:\n"+map);

// 张三 改名为:张山 但是还是同一个人。

Person p4=new Person("张山",new Code(123));

map.put(p4.id,p4);

System.out.println("张三改名后 HashMap 中存放的人员信息:\n"+map);

//查找身份证为:123 的人员信息

System.out.println("查找身份证为:123 的人员信息:"+map.get(new Code(123)));

}

}

运行结果为:

HashMap 中存放的人员信息:

{身份证:456=姓名:李四 身份证:456

, 身份证:123=姓名:张三 身份证:123

, 身份证:789=姓名:王二 身份证:789

}

张三改名后 HashMap 中存放的人员信息:

{身份证:456=姓名:李四 身份证:456

, 身份证:123=姓名:张三 身份证:123

, 身份证:123=姓名:张山 身份证:123

, 身份证:789=姓名:王二 身份证:789

}

查找身份证为:123 的人员信息:null

上面的例子的演示的是,我们在一个HashMap中存放了一些人员的信息。并以这些人员的身份证最为人员的“键”。当有的人员的姓名修改了的情况下,我们需要更新这个HashMap。同时假如我们知道某个身份证号,想了解这个身份证号对应的人员信息如何,我们也可以根据这个身份证号在HashMap中得到对应的信息。

而例子的输出结果表示,我们所做的更新和查找操作都失败了。失败的原因就是我们的身份证类:Code 没有覆写hashCode()方法。这个时候,当查找一样的身份证号码的键值对的时候,使用的是默认的对象的内存地址来进行定位。这样,后面的所有的身份证号对象new Code(123) 产生的hashCode()值都是不一样的。所以导致操作失败。

下面,我们给Code类加上hashCode()方法,然后再运行一下程序看看:

//身份证类

class Code{

final int id;//身份证号码已经确认,不能改变

Code(int i){

id=i;

}

//身份号号码相同,则身份证相同

public boolean equals(Object anObject) {

if (anObject instanceof Code){

Code other=(Code) anObject;

return this.id==other.id;

}

return false;

}

public String toString() {

return "身份证:"+id;

}

//覆写hashCode方法,并使用身份证号作为hash值

public int hashCode(){

return id;

}

}

再次执行上面的HashCodeEx 的结果就为:

HashMap 中存放的人员信息:

{身份证:456=姓名:李四 身份证:456

, 身份证:789=姓名:王二 身份证:789

, 身份证:123=姓名:张三 身份证:123

}

张三改名后 HashMap 中存放的人员信息:

{身份证:456=姓名:李四 身份证:456

, 身份证:789=姓名:王二 身份证:789

, 身份证:123=姓名:张山 身份证:123

}

查找身份证为:123 的人员信息:姓名:张山 身份证:123

这个时候,我们发现。我们想要做的更新和查找操作都成功了。

对于Map部分的使用和实现,主要就是需要注意存放“键值对”中的对象的equals()方法和hashCode()方法的覆写。如果需要使用到排序的话,那么还需要实现Comparable 接口中的compareTo() 方法。我们需要注意Map中的“键”是不能重复的,而是否重复的判断,是通过调用“键”对象的equals()方法来决定的。而在HashMap中查找和存取“键值对”是同时使用hashCode()方法和equals()方法来决定的。

多算法数据预测——BP、ELM、LSTM、BiLSTM和SAELSTM,附Matlab源码 本文将为大家介绍基于BP、ELM、LSTM、BiLSTM和SAELSTM多种算法的数据预测,并提供相应的Matlab源码。SAELSTM算法是一种结合自动编码器和LSTM神经网络的深度学习方法,用于时间序列数据预测。ELM算法是一种单层前向神经网络,在单次训练过程中直接产生输出权重,从而具有快速学习和良好推广性能。多算法数据预测——BP、ELM、LSTM、BiLSTM和SAELSTM,附Matlab源码。总体来看,采用多种算法进行数据预测可以提高预测准确率,并可根据不同场景选择适合的算法。 阅读详情

相关推荐

在Windows 11上用VirtualBox搞定WRF-Hydro 5.2.0:一个水文模型小白的Ubuntu 22.04虚拟机避坑实录

本文详细记录了在Windows 11上使用VirtualBox和Ubuntu 22.04虚拟机成功编译安装WRF-Hydro 5.2.0水文模型的全过程。从虚拟环境配置、依赖库安装到环境变量设置,作者分享了针对Linux新手的实用避坑技巧和关键命令,帮助科研人员高效完成水文模型的环境搭建。

weixin_30733003的博客 354

java map练习_Java练习题 - Map集合

练习一:Map接口的特点一、请简述Map 的特点。Map每个元素由键与值两部分组成Map键不能重复,每个键对应一个值键和值可以为null练习二:Entry键值对对象二、说出Entry键值对对象遍历Map集合的原理。Map中存放的是两种对象,一种称为key(键),一种称为value(值),它们在在Map中是一一对应关系,这一对对象又称做Map 中的一个Entry(项)。Entry将键值对的对应关系封...

weixin_39717443的博客 1035

SAP MM 物料描述MAKTX 40个字符不够用的问题处理

SAP物料描述表,长度40个字符一般情况下,40个字符在描述中也够用了,但是要加上规格之类的额外描述就不一定了,特别是对于辅助件-刀具。解决方案一:对maktx增强,换成CHAR80或者CHAR100的,但是风险较大,涉及到好多地方,另外SAP+ 算号器 也不支持。解决方案二:增强字段。内部顾问,建议在 那就在这里加一个字段,具体过程如下:1、创建域2、创建架构3、附加架构4、GUI 设置 5、后台设置6、MASS/MM17字段新增。过程1、创建域 SE112、创建结构 se11 3、MARA附加架构

GAVIN 4728

Map双列集合作业复习

作业 1./*分析以下需求,并用代码实现: (1)统计每个单词出现的次数 (2)有如下字符串"If you want to change your fate I think you must come to the ujiuye to learn java"(用空格间隔) (3)打印格式: to=3 think=1 you=2*/ importjava.util.LinkedHa...

qq_40245464的博客 289

Silvaco Atlas新手入门:从安装到第一个半导体器件仿真(附DeckBuild配置)

本文为Silvaco Atlas新手提供了一份从软件安装、DeckBuild环境配置到完成第一个半导体器件仿真的实战指南。通过构建一个经典的PN结二极管案例,详细讲解了Atlas仿真语句的语法、核心命令顺序以及结果调试与可视化方法,帮助初学者快速上手并理解TCAD仿真流程。

sql99的博客 332

anaconda安装-超详细版

文章目录一、anaconda安装1.1 anaconda安装地址1.2 安装详细步骤检验安装是否成功更改conda源(后续安装第三方库可以加快速度) 一、anaconda安装 Anaconda 是一个用于科学计算的 Python 发行版,支持 Linux, Mac, Windows, 包含了众多流行的科学计算、数据分析的 Python 包。装anaconda,就不需要单独装python了。 anaconda优点: 1、anaconda里面集成了很多关于python科学计算的第三方库,主要是安装方便,而p

in546的博客 25万+

Java面试题大全带答案

本人发现网上虽然有不少Java相关的面试题,但第一未必全,第二未必有答案,第三虽然有答案,但未必能在面试中说,所以在本文里,会不断收集各种面试题,并站在面试官的立场上,给出我自己的答案。 第一部分、Java 基础 1. JDK 和 JRE 有什么区别? JDK是java的开发工具包,有JDK8,9甚至到14的差别,安装以后,不仅包含了java 的开发环境,比如java.exe,还包含了运行环境(jre)相关包。 JRE是java 运行环境,一般装好JDK后,系统里会有对应的JRE环境。 2. .

hsm_computer的博客 3万+

HBase学习二:RegionServer详解

RegionServer是HBase系统中最核心的组件,主要负责用户数据写入、读取等基础操作。RegionServer组件实际上是一个综合体系,包含多个各司其职的核心模块:HLog、MemStore、HFile以及BlockCache。一个RegionServer由一个(或多个)HLog、一个BlockCache以及多个Region组成。其中,HLog用来保证数据写入的可靠性;BlockCache可以将数据块缓存在内存中以提升数据读取性能;

qwerty1372431588的博客 747

hashmap put过程_HashMap面试题,看这一篇解决所有疑惑

欢迎关注头条号:Java小野猫序言在后端的日常开发工作中,集合是使用频率相当高的一个工具,而其中的HashMap,则更是我们用以处理业务逻辑的好帮手,同时HashMap的底层实现和原理,也成了面试题中的常客。以前曾有详细了解过HashMap的实现原理,看过源码(JDK7版本)。但随着jdk版本的飞速迭代(现在都到JDK13了,但新特性还从没用过。。),主流的jdk使用版本也终于从JDK7挪到了JD...

weixin_39581318的博客 1372

MIKE水动力笔记7_实测数据与模型输出结果的拟合对比

上一博文【MIKE水动力笔记6】主要讲解如何自己制作实测数据的时间序列文件dfs0,而本博文讲解如何进行实测数据与模型输出结果的拟合对比。

晏长街的博客 1万+

【超分辨率(Super-Resolution)】关于【超分辨率重建】专栏的相关说明,包含专栏简介、专栏亮点、适配人群、相关说明、阅读顺序、超分理解、实现流程、研究方向、论文代码数据集汇总等

本专栏研究领域为【超分辨率重建】,涵盖图像超分、视频超分,实时超分,4K修复等方面。主要内容包括主流算法模型的论文精读、论文复现、毕业设计、涨点手段、调参技巧、论文写作、应用落地等方面。算法模型从SRCNN开始更新至今,一般是一篇论文精读对应一篇论文复现。论文精读详解理论,归化繁为简,归纳核心,积累词句,培养阅读论文和论文写作能力。论文复现依托Pytorch代码,实现完整的模型训练流程,总结调参方法,记录碰到的bug,论文插图可视化,培养读写代码能力、做实验的能力、以及应用落地能力。

主要更新底层视觉(去噪、超分等)相关的科研内容,形式为【论文精读】+【论文复现】 3万+

Java Map 集合类简介

java.util 中的集合类包含 Java 中某些最常用的类。 最常用的集合类是 List 和 Map。 List 的具体实现包括 ArrayList 和 Vector,它们是可变大小的列表,比较适合构建、存储和操作任何类型对象的元素列表。 List 适用于按数值索引访问元素的情形。 Map 提供了一个更通用的元素存储方法。 Map 集合类用于存储元素对(称作“键”和“值”),其中每个键

幽客独往 10万+

CodeQL查询时遇到报错:A fatal error occurred: Could not process query metadata for...如何解决?

🏆本文收录于 《全栈Bug调优(实战版)》 专栏,该专栏专注于分享我在真实项目开发中遇到的各类疑难Bug及其深层成因,并系统提供高效、可复现的解决思路和实操方案。无论你是刚入行的新手开发者,还是拥有多年项目经验的资深工程师,本专栏都将为你提供一条系统化、高质量的问题排查与优化路径,助力你加速成长,攻克技术壁垒,迈向技术价值最大化与职业发展的更高峰🚀!

**My Coding Family** 1084

Java集合类: Set、List、Map、Queue区别及应用

Java集合类基本概念 Java集合类架构层次关系 Java集合类的应用场景代码

专注自动化、性能测试、测试架构学习交流 3124

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

Java集合类之Map

本文主要介绍了Java集合类中的Map相关知识,包括Map的增删改查API,HashMap存储数据的特点,HashMap的底层实现,LinkedHashMap和TreeMap存储数据的特点。供个人学习使用。

专注Java和MySQL领域知识分享 1393

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

一文搞定Java集合类,你还在为Java集合类而烦恼吗?

常用集合类

轻松玩编程 1189

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

Java的集合类:List、Set、Map

Java 中的集合类可以分为 List、Set 和 Map 三个主要类别。在使用集合类时,请注意选择适当的实现类,并根据需要使用 List、Set 和 Map 接口。同时,还要注意集合类的动态性,以便在运行时动态调整其大小。

小虎哥的技术博客 787

Java集合类面试题

参考答案它基于hash算法,通过put方法和get方法存储和获取对象。存储对象时,我们将K/V传给put方法时,它调用K的hashCode计算hash从而得到bucket位置,进一步存储,HashMap会根据当前bucket的占用情况自动调整容量(超过Load Facotr则resize为原来的2倍)。获取对象时,我们将K传给get,它调用hashCode计算hash从而得到bucket位置,并进一步调用 equals()方法确定键值对。

qq_45270148的博客 1363

Java中有哪些集合类?常用的几种java集合类总结 详细解读 Java 的集合类

Java 的集合框架是一个统一的架构,用于表示和操作一组对象。它提供了多种接口和类,遵循了一定的设计原则,具备更高的灵活性和可扩展性。Collection:集合的根接口。List:有序集合,允许重复元素。Set:不允许重复元素的集合。Map:键值对集合,键唯一但值可以重复。Queue:用于表示数据结构中的队列。Java 集合类框架为开发者提供了强大的工具,以方便有效地操作数据。通过理解每种集合接口及其实现的特性,可以帮助我们在开发过程中做出更好的选择。

智通全网络工作室的博客 2096

Java的集合类介绍

关于面试刷题也是有方法可言的,建议最好是按照专题来进行,然后由基础到高级,由浅入深来,效果会更好。Java基础部分算法与编程数据库部分流行的框架与新技术(Spring+SpringCloud+SpringCloudAlibaba)这份面试文档当然不止这些内容,实际上像JVM、设计模式、ZK、MQ、数据结构等其他部分的面试内容均有涉及,因为文章篇幅,就不全部在这里阐述了。

2401_83817916的博客 1009

java map存储list集合_Java集合类(List、map、list集合)

1.1集合类的概述Java.util包中提供了一些集合类,这些集合类右被称为容器。提到容器不难想到数组,集合类与数组不同之处是:数组的长度是固定长度,集合的长度是可变的;数组是用来存放基本数据类型,集合是用来存放对象的引用。常用的集合有List集合、Set集合和Map集合,其中List和Set继承了Collection接口,各接口还提供了不同的实现类。上述集合类的继承关系如图14.1所示:1.1C...

weixin_30706851的博客 5243

五、Java集合类(三)Map

今天的博客主题 基础篇 --》常用类 --》Java集合类(三)Map Map public interface Map<K,V> {} Map直接就是一个接口,已经约定KV结构的存储。 Map也是一个集合和Collection是一个级别的。 Map集合是双列的,键值对的方式存在的。 Map的体系结构大概是这样子的。 特点 Map属于是一个映射...

小萝卜.的博客 276

Java——集合类

Java 集合类是用于存储和操作数据的核心工具,主要分为Collection和Map两大接口体系,每个接口下有多种实现类。

weixin_44762713的博客 666

java 集合类Array、List、Map区别和优缺点

java 集合类Array、List、Map区别和优缺点 Java集合类主要分为以下三类: 第一类:Array、Arrays 第二类:Collection :List、Set 第三类:Map :HashMap、HashTable 一、Array , Arrays Java所有“存储及随机访问一连串对象”的做法,array是最有效率的一种。 1、 效率高,但容量固定且无法动态改变。 array还有一个缺点是,无法判断其中实际存有多少元素,length只是告诉我们array的容量。 2、Java中有一个Arra

White_55open的博客 3066

Java集合类:Set、List、Map、Queue 详解

Java集合类: Set、List、Map、Queue使用场景梳理 相关学习资料 http://files.cnblogs.com/LittleHann/java集合排序及java集合类详解(collection、list、map、set).rar http://blog.sina.com.cn/s/blog_a345a8960101k9vx.html http://f51889920.iteye...

CHANGE ING的博客 641

五、Java集合类

今天的博客主题 基础篇 --》常用类 --》Java集合类 集合类 集合类存放于java.util包中 集合的由来:面向对象语言对事物都是以对象的形式来体现,为了方便对多个对象的操作,就需要将对象进行存储,集合就是存储对象最常用的一种方式。 集合类存放的都是对象的引用,而非对象本身,出于表达上的便利,我们称集合中的对象就是指集合中对象的引用(reference)。 集合类型...

小萝卜.的博客 228
上一篇: Java集合类的学习(四)——Set
下一篇: Java集合类的学习(六)——总结
Jmark_GC
博客等级 码龄14年 32粉丝 35原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值