分享

map排序

 昵称14148836 2013-10-08

Java集合排序及java集合类详解:Map  

2010-10-28 17:17:14|  分类: Java|字号 订阅

1.4         Map 
1.4.1        概述 
数学中的映射关系在Java中就是通过Map来实现的。它表示,里面存储的元素是一个对(pair),我们通过一个对象,可以在这个映射关系中找到另外一个和这个对象相关的东西。 
前面提到的我们对于根据账号名得到对应的人员的信息,就属于这种情况的应用。我们讲一个人员的帐户名和这人员的信息作了一个映射关系,也就是说,我们把帐户名和人员信息当成了一个“键值对”,“键”就是帐户名,“值”就是人员信息。下面我们先看看Map 接口的常用方法。 
1.4.2         常用方法 
Map 接口不是 Collection 接口的继承。而是从自己的用于维护键-值关联的接口层次结构入手。按定义,该接口描述了从不重复的键到值的映射。 
我们可以把这个接口方法分成三组操作:改变、查询和提供可选视图。 
改变操作允许您从映射中添加和除去键-值对。键和值都可以为 null。但是,您不能把 Map 作为一个键或值添加给自身。 
       Object put(Object key,Object value):用来存放一个键-值对Map中 
       Object remove(Object key):根据key(键),移除一个键-值对,并将值返回 
       void putAll(Map mapping) :将另外一个Map中的元素存入当前的Map中 
       void clear() :清空当前Map中的元素 
查询操作允许您检查映射内容: 
       Object get(Object key) :根据key(键)取得对应的值 
       boolean containsKey(Object key) :判断Map中是否存在某键(key) 
       boolean containsValue(Object value):判断Map中是否存在某值(value) 
       int size():返回Map中 键-值对的个数 
       boolean isEmpty() :判断当前Map是否为空 
最后一组方法允许您把键或值的组作为集合来处理。 
       public Set keySet() :返回所有的键(key),并使用Set容器存放 
       public Collection values() :返回所有的值(Value),并使用Collection存放 
       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,大家可以了解一下: 
   WeakHashMap: WeakHashMap 是 Map 的一个特殊实现,它只用于存储对键的弱引用。当映射的某个键在 WeakHashMap 的外部不再被引用时,就允许垃圾收集器收集映射中相应的键值对。使用 WeakHashMap 有益于保持类似注册表的数据结构,其中条目的键不再能被任何线程访问时,此条目就没用了。 
   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,然后我们分别在该表中的格子中存放不同的元素。每个格子下面存放的元素又是以链表的方式存放元素。 
       当添加一个新的元素Entry 的时候,首先我们通过一个Hash函数计算出这个Entry元素的Hash值hashcode。通过该hashcode值,就可以直接定位出我们应该把这 个Entry元素存入到Hash表的哪个格子中,如果该格子中已经存在元素了,那么只要把新的Entry元存放到这个链表中即可。 
       如果要查找一个元素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()方法来决定的。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多