Java基础之(三十三)Map

来源:互联网 发布:阿里云 centos7 lnmp 编辑:程序博客网 时间:2024/06/11 07:29

Map用于保存具有映射关系的数据,因此Map集合里保存这两组值,一组用于保存Map里的key,另一组值用于保存Map里的value,key和value都可以是任何引用类型的数据。Map的key不允许重复,即同一个Map对象的任何两个key通过equals方法比较总是返回false。

key和value之间存在单向一对一关系,即通过指定的key,总能找到唯一的、确定的value。如果把Map的两组值拆开来看,Map里的数据如下图所示的结构。
这里写图片描述

从上图可以看出,如果把Map里的所有key放在一起看,它们就组成了一个Set集合(所有的key没有顺序,key与key之间不能重复),实际上Map确实包含了一个keySet()方法,用于返回Map所有key做成的Set集合。

不仅如此,Map里key集合和Set集合里元素的存储方式也很像,Map子类和Set子类在名字上也惊人的相似:如Set接口下有HashSet、LinkedHashSet、SortedSet(接口)、TreeSet、EnumSet等实现类和子接口,而Map接口下则有HashMap、LinkedHashMap、SortedMap(接口)、TreeMap、EnumMap等实现类和子接口。正如它们的名字所暗示的,Map的这些实现
类和子接口中key集存储形式和对应Set集合中元素的存储形式完全相同。

如果把Map的value放在一起看,他们又非常类似于一个List:元素与元素之间可以重复,每个元素可以根据索引来查找,只是Map中的索引不再使用整数值,而是以另一个对象来作为索引。如果需要从List集合中取出元素,需要提供该元素的
数字索引;如果需要从Map中取出元素,需要提供该元素的key索引。因此,Map有时也被成为字典,或关联数组。

Map接口中定义了如下常用方法:

void clear(): //删除Map集合中的所有key-value对。

boolean containsKey(object key): //查询Map集合中是否包含指定的key,如果包含,返回true。

boolean containsValue(Object value): // 查询Map集合中是否包含指定的value,如果包含返回true。

set entrySet(): //返回Map中包含的key-value对所组成的Set集合,每个集合元素都是Map.Entry(Entry是Map的内部类)对象。

Object get(Object key): //返回指定的key所对应的value;如果此Map中不包括该key,则返回null。

boolean isEmpty(): //查询该Map是否为空,如果为空则返回null。

set keyset(): //返回该map中所有key所组成的集合。

Object put(Object key,Object value): //添加一个key-value对,如果当前Map中已有一个与该key相等的key-value对,则新的key-value会覆盖原来的key-value对。

void putAll(Map m): //将指定Map中key-value复制到当前Map中。

Object remove(Object key): //删除指定key所对应的key-value对,返回被删除key所对应的value,如果key不存在返回null。

boolean remove(Object key, Object value): //删除指定key,value所对应的key-value对。如果成功删除,则返回true,否则返回false。

int size(): //返回该Map中key-value的个数。

Collection values(): //返回该Map中所有value组成的collection。

Map接口提供了大量的实现类,典型实现如HashMap和Hashtable等、HashMap的子类LinkedHashMap,还有SortedMap子接口及该接口的实现类TreeMap,以及WeakHashMap、IdentityHashMap等,下面将详细介绍Map接口实现类。

Map中包括一个内部类Entry,该类封装了一个key-value对。Entry包含如下三个方法:

ject getKey(): //返回该Entry里包含的key值

Object getValue(): //返回该Entry里包含的value值

Object setValue(V value): //设置该Entry里包含的value值,并返回新设置的value值。

HashMap和Hashtable实现类

HashMap和Hashtable都是Map接口的典型实现类,他们之间的关系完全类似于ArrayList和Vector的关系:Hashtable是一个古老的Map实现类,它从JDK1.0起就已经出现了,当他出现时,Java还没有提供Map接口,所以他包含了两个繁琐的方法:elements()(类似于Map接口定义的values()方法)和keys()(类似于Map接口定义的keySet()方法),现在很少使用这两个方法。

除此之外,Hashtable和HashMap存在两点典型区别:

  • Hashtable是线程安全的Map实现。但HashMap是线程不安全的实现。所以
    HashMap比Hashtable的性能高一点;但如果多个线程访问同一个Map对象时,使用Hashtable实现类会更好。

  • Hashtable不允许使用null作为key和value,如果试图把null值放进Hashtable中,将会引发NullPointerException异常;但HashMap可以使用null作为key和value。

由于HashMap里的key不能重复,所以HashMap里最多只有一项key-value对的key为null,但可以有无数项key-value对的value为null。下面程序示范了用null值作为HashMap的key或value的情形。

public class NullInHashMap{    public static void main(String[] args)     {        HashMap hm = new HashMap();        //试图将2个key为null的key-value对放入HashMap中        hm.put(null , null);        hm.put(null , null);//@1        //将一个value为null的key-value对放入HashMap中        hm.put("a" , null);//@2        //输出Map对象        System.out.println(hm);    }}
输出结果:{null=null, a=null}

根据上面程序的打印结果可以看出,HashMap重写了toString()方法,实际所以Map实现类都重写了toString()方法,调用Map对象的toString()方法总是返回如下格式的字符串:{key1=value1, key2=value2} 。

为了成功的在HashMap、Hashtable中存储、获取对象,用作key的对象必须实现hashCode方法和equals方法

与HashSet集合不能保证元素的顺序一样,HashMap、Hashtable也不能保证其中key-value对的顺序。类似于HashSet的是,HashMap、Hashtable判断两个key相等的标准也是:两个key通过equals方法比较返回true,两个key的hashCode值也相等。

除此之外,HashMap、Hashtable中还包含一个containsValue方法用于判断是否包含指定value。

其实HashMap、Hashtable判断两个value相等的标准更简单:只要两个对象通过equals比较返回true即可。

class A{    int count;    public A(int count)    {        this.count = count;    }    public boolean equals(Object obj)    {        if (obj == this)        {            return true;        }        if (obj != null &&             obj.getClass() == A.class)        {            A a = (A)obj;            if (this.count == a.count)            {                return true;            }        }        return false;    }    public int hashCode()    {        return this.count;    }}class B{    public boolean equals(Object obj)    {        return true;    }}public class TestHashtable{    public static void main(String[] args)     {        Hashtable ht = new Hashtable();        ht.put(new A(60000) , "Struts2权威指南");        ht.put(new A(87563) , "轻量级J2EE企业应用实战");        ht.put(new A(1232) , new B());        System.out.println(ht);        //只要两个对象通过equals比较返回true,Hashtable就认为它们是相等的value。        //因为Hashtable中有一个B对象,它与任何对象通过equals比较都相等,所以下面输出true。        System.out.println(ht.containsValue("测试字符串"));        //只要两个A对象的count属性相等,它们通过equals比较返回true,且hashCode相等        //Hashtable即认为它们是相同的key,所以下面输出true。        System.out.println(ht.containsKey(new A(87563)));        //下面语句可以删除最后一个key-value对        ht.remove(new A(1232));        for (Object key : ht.keySet())        {            System.out.print(key + "---->");            System.out.print(ht.get(key) + "\n");        }    }}

上面程序定义了A、B两个类,其中个A类判断A对象相等的标准是count实例变量:只要两个A对象的count实例变量相等,则通过equals方法比较它们返回true,它们的hashCode值也相等;而B对象则可以与任何对象相等。

HashTable判断value相等的标准是:value与另外一个对象通过equals方法比较返回true即可。

程序最后还示范了如何遍历Map中的全部key-value对。通过Map对象的keySet()方法返回全部key组成的Set集合,通过遍历该Set集合的全部元素,就可以遍历Map中的所有key-value对。

与HashSet类似的是,如果使用可变对象作为HashMap、Hashtable的key,并且程序修改了作为key的可变对象,可能引发与HashSet类似的情形:程序无法准确访问到Map中被修改过key。 所以说尽量不要使用可变对象作为HashMapHashtable的key。

public class TestHashtableError{    public static void main(String[] args)     {        Hashtable ht = new Hashtable();        ht.put(new A(60000) , "Struts2权威指南");        //获得Hashtable的key Set集合对应的Iterator迭代器        Iterator it = ht.keySet().iterator();        //取出Map中第一个key        A first = (A)it.next();        first.count = 87563;        System.out.println(ht);        //只能删除没有被修改过的key所对应的key-value对        ht.remove(new A(87563));        System.out.println(ht);        //无法获取剩下的value,下面两行代码都将输出null。        System.out.println(ht.get(new A(87563)));        System.out.println(ht.get(new A(60000)));    }}

LinkedHashMap实现类

HashSet有一个子类是LinkedHashSet,HashMap也有一个LinkedHashMap子类;LinkedHashMap也使用双向链表来维护key-value对的次序;该链表负责维护key的迭代顺序,迭代顺序与key-value的插入顺序一致。

LinkedHashMap可以避免对HashMap、Hashtable里的key-value对进行排序(只要插入key-value对时保持顺序即可)。同时又可避免使用TreeMap所增加的成本。

LinkedHashMap需要维护元素的插入顺序,因此性能略低于HashMap的性能,但在迭代访问Map里的全部元素时将有很好的性能,因为它以链表来维护内部顺序。

下面程序示范了LinkedHashMap的功能:

public class TestLinkedHashMap{    public static void main(String[] args)     {        LinkedHashMap scores = new LinkedHashMap();        scores.put("语文" , 80);        scores.put("数学" , 76);        scores.put("英文" , 76);        //遍历scores里的所有的key-value对        for (Object key : scores.keySet())        {            System.out.print(key + "------>");            System.out.print(scores.get(key) + "\n");        }    }}
输出结果:语文------>80数学------>76英文------>76

LinkedHashMap可以记住key-value对的添加顺序。

使用Properties读取属性文件

Properties类是Hashtable类的子类,用于处理属性文件(例如Windows操作平台上的ini文件)。Properties类可以把Map对象和属性文件关联起来,从而可以把Map对象中的key-value对写入属性文件,也可以把属性文件中的属性名=属性值加载到Map对象中。由于属性文件里的属性名、属性值只能是字符串类型,所以Properties里的key、value都是字符串类型,该类提供了如下三个方法来修改Properties里的key、value值。

String getProperty(String key): //获取Properties中指定属性名对应的属性值,类似于Map的get(Object key)方法。

String getProperty(String key, String defaultValue): //该方法与前一个方法基本类似。该方法多一个功能,如果Properties中不存在指定key时,该方法返回默认值。

Object geProperty(String key、String value): //设置属性值,类似Hashtable的put方法。

提供两个读、写属性文件的方法:

void load(InputStream inStream): //从属性文件(以输入流表示)中加载属性名=属性值,把加载到的属性名=属性值对追加到Properties里(由于Properties是Hashtable)的子类,它不保证key-value对之间的次序)。

void Store(OutputStream out, String comment): //将Properties中的key-valu对写入指定属性文件(以输出流表示)。

public class TestProperties{    public static void main(String[] args) throws Exception    {        Properties props = new Properties();        //向Properties中增加属性        props.setProperty("username" , "yeeku");        props.setProperty("password" , "123456");        //将Properties中的属性保存到a.ini文件中        props.store(new FileOutputStream("a.ini") , "comment line");        //新建一个Properties对象        Properties props2 = new Properties();        //向Properties中增加属性        props2.setProperty("gender" , "male");        //将a.ini文件中的属性名-属性值追加到props2中        props2.load(new FileInputStream("a.ini") );        System.out.println(props2);    }}
输出结果:{password=123456, gender=male, username=yeeku}

SortedMap接口和TreeMap实现类

Map接口派生了一个SortedMap子接口,TreeMap为其实现类。类似TreeSet排序,TreeMap也是基于红黑树对TreeMap中所有key进行排序,从而保证TreeMap中所有key-value对处于有序状态。TreeMap两种排序方法:

  1. 自然排序:TreeMap的所有key必须实现Comparable接口,而且所有key应该是同一个类的对象,否则将会抛出ClassCastExcepiton异常。

  2. 定制排序:创建TreeMap时,传入一个Comparator对象,该对象负责对TreeMap中所有key进行排序。采用定制排序时不要求Map的key实现Comparable接口。

    TreeMap中判断两个key相等的标准是:两个key通过compareTo方法返回0,TreeMap即认为这两个key是相等的。

如果使用自定义的类作为TreeMap的key,应重新该类的equals方法和compareTo方法时应有一致的返回结果:即两个key通过equals方法比较返回true时,它们通过compareTo方法比较应该返回0。如果equals方法与compareTo方法的返回结果不一致,要么该TreeMap与Map接口的规则有出入(当equals比较返回true,但CompareTo比较不返回0时),要么TreeMap处理起来性能有所下降(当compareTo比较返回0,当equals比较不返回true时)。

TreeMap中提供了系列根据key顺序来访问Map中key-value对方法:

Map.Entry firstEntry():返回该Map中最小key所对应的key-value对,如果该Map为空,则返回null。

Object firstKey():返回该Map中的最小key值,如果该Map为空,则返回null。

Object lastKey():返回该Map中的最大key值,如果该Map为空,或不存在这样的key都返回null。

Map.Entry lastEntry():返回该Map中最大key所对应的key-value对,如果该Map为空,或不存在这样的key-value都返回null。

Object lastKey():返回该Map中的最大key值,如果该Map为空,或不存在这样的key都返回null。

Map.Entry lowerEntry(Object key):返回该Map中位于key前一位的key-value对(即小于指定key的最大key所对应的key-value对)。如果该Map为空,或不存在这样的key-value则返回null。

Map.Entry higherEntry(Object key):返回该Map中位于key后一位的key-value对(即大于指定key的最小key所对应的key-value对)。如果该Map为空,则返回null。

Object higherKey():返回该Map中位于key后一位的key值(即大于指定key的最小key值)。如果该Map为空,或不存在这样的key都返回null。

SorterMap subMap(Object fromKey, Object toKey):返回该Map的子Map,其key的范围从fromKey(包括)到toKey(不包括)。

Object lowerKey():返回该Map中位于key前一位的key值(即小于指定key的最大key值)。如果该Map为空,或不存在这样的key都返回null。

NavigableMap subMap(Object fromKey, boolean fromInclusive, Object
tokey, boolean tolnclusive):返回该Map的子Map,其key的范围从fromKey(是否包括取决于第二个参数)到tokey(是否包括取决于第四个参数)。

NavigableMap headMap(Object toKey, boolean lnclusive):返回该Map的子Map,其key的范围是小于fromKey(是否包括取决于第二个参数)的所有key。

SortedMap tailMap(Object fromKey,boolean inclusive):返回该Map的子Map,其key的范围是大于fromkey(是否包括取决于第二个参数)的所有key。

下面以自然排序为例,介绍TreeMap的基本用法:

//R类,重写了equals方法,如果count属性相等返回true//重写了compareTo(Object obj)方法,如果count属性相等返回0;class R implements Comparable{    int count;    public R(int count)    {        this.count = count;    }    public String toString()    {        return "R(count属性:" + count + ")";    }    //根据count来判断两个对象是否相等    public boolean equals(Object obj)    {        if (this == obj)        {            return true;        }        if (obj != null             && obj.getClass() == R.class)        {            R r = (R)obj;            if (r.count == this.count)            {                return true;            }        }        return false;    //来根据count属性值来判断两个对象的大小    public int compareTo(Object obj)    {        R r = (R)obj;        if (this.count > r.count)        {            return 1;        }        else if (this.count == r.count)        {            return 0;        }        else        {            return -1;        }    }}public class TestTreeMap{    public static void main(String[] args)     {        TreeMap tm = new TreeMap();        tm.put(new R(3) , "轻量级J2EE企业应用实战");        tm.put(new R(-5) , "Struts2权威指南");        tm.put(new R(9) , "ROR敏捷开发最佳实践");        System.out.println(tm);        //返回该TreeMap的第一个Entry对象        System.out.println(tm.firstEntry());        //返回该TreeMap的最后一个key值        System.out.println(tm.lastKey());        //返回该TreeMap的比new R(2)大的最小key值。        System.out.println(tm.higherKey(new R(2)));        //返回该TreeMap的比new R(2)小的最大的key-value对。        System.out.println(tm.lowerEntry(new R(2)));        //返回该TreeMap的子TreeMap        System.out.println(tm.subMap(new R(-1) , new R(4)));    }}

上面程序中定义了一个R类,该类重写了equals方法,并实现了Comparable接口,所以可以使用该R对象作为TreeMap的key,该TreeMap使用自然排序。

输出结果:{R(count属性:-5)=Struts2权威指南, R(count属性:3)=轻量级J2EE企业应用实战, R(count属性:9)=ROR敏捷开发最佳实践}R(count属性:-5)=Struts2权威指南R(count属性:9)R(count属性:3)R(count属性:-5)=Struts2权威指南{R(count属性:3)=轻量级J2EE企业应用实战}

WeakHashMap实现类

WeakHashMap其实和HashMap用法类似,他们之间的唯一的区别就是,HashMap中的key保存的是实际对象的强引用,因此只要对象不被销毁,即该key所对应的key-value都不会被垃圾回收机制回收,但是WeakHashMap是保存的实际对象的弱引用,这意味着只要该对象没有被强对象引用就有可能会被垃圾回收机制回收对应的Key-value。看如下程序:

    package collectionPackage;      import java.util.WeakHashMap;      /**      * WeakHashMap只是保留对象的弱引用      * @author Administrator      *      */      public class WeakHashMapTest {          public static void main(String[] args) {              WeakHashMap whm= new WeakHashMap();              //三个key-value中的key 都是匿名对象,没有强引用指向该实际对象              whm.put(new String("语文"),new String("优秀"));              whm.put(new String("数学"), new String("及格"));              whm.put(new String("英语"), new String("中等"));              //增加一个字符串的强引用              whm.put("java", new String("特别优秀"));              //输出whm对象,将看到四个key-value对            System.out.println(whm);              //通知垃圾回收机制来进行回收              System.gc();              System.runFinalization();              //再次输出whm,通常情况下,将只看到一个key-value对            System.out.println("第二次输出whm:"+whm);          }      }  
运行结果:{java=特别优秀, 数学=及格, 英语=中等, 语文=优秀}第二次输出whm:{java=特别优秀}

IdentityHashMap实现类

IdentityHashMap的用法和HashMap的用法差不多,他们之间最大的区别就是IdentityHashMap判断两个key是否相等,是通过严格相等即(key1==key2)来判断的,而HashMap是通过equals()方法和hashCode()这两个方法来判断key是否相等的。另外,IdentityHashMap也允许使用null作为key和value。

具体代码如下:

public class IdentityHashMapTest {      public static void main(String[] args) {          IdentityHashMap ihm= new IdentityHashMap();          //IdentityHashMap通过==来判断两个new String对象是不相等的,所以作为两个不同的对象加入          ihm.put(new String("语文"), 89);          ihm.put(new String("语文"), 93);          System.out.println(ihm);          //由于java是一个字符串,直接将它放在常量池中,故认为是两个相同的对象,所以就只会作为一个对象加入          ihm.put("java", 88);          ihm.put("java",90);          System.out.println("第二次的ihm"+ihm);      }  }  
输出结果:{java = 98,语文 = 78,语文 = 79}

EnumMap实现类

EnumMap是一个将Map和枚举类相关联的类。

1:在该EnumMap中不允许使用null作为key,但可以使用null作为value,如果试图使用null作为key,则将会报错NullPointException的错误,但是查询key值是否为空和删除key值为空的操作都不会报错。

2:EnumMap的所有的key 必须是枚举类的枚举值,创建EnumMap时候必须显式或者隐式的指明他的相关的枚举类

3:EnumMaP根据key的自然顺序(key在枚举类中的定义顺序)来对里面的值进行排序

下面程序示范了EnumMap的用法:

enum Season{      SPRING,SUMMER,FALL,WINTER;  }  public class EnumMapTest {     public static void main(String[] args) {     //创建一个EnumMap对象,该EnumMap的所有key都必须是Season枚举类的枚举值    EnumMap emap= new EnumMap(Season.class);      emap.put(Season.SPRING, "春暖花开");      emap.put(Season.SUMMER, "夏日炎炎");      System.out.println(emap);      }   }  
输出结果:{SPRING = 春暖花开,SUMMER = 夏日炎炎}

各Map实现类的性能分析

HashMap通常要比Hashtable要快一点,因为Hashtable需要额外的线程同步控制;

TreeMap通常要比HashMap和Hashtable要慢一些(尤其在插入、删除key-value对时更慢),但是使用TreeMap有一个好处:TreeMap中的key-value对总是处于有序状态,无须专门进行排序操作。当TreeMap被填充之后,就可以调用keySet(),取得由key组成的Set,然后由toArray方法生成key的数组,接下来使用Arrays的binarySearch()方法在已排序的数组中快速地查询对象。

对于一般的应用场景,程序应该多考虑使用HashMap,因为HashMap正是为快速查询来设计的。但如果程序需要一个总是排好序的Map时,则可以考虑使用TreeMap。

LinkedHashMap比HashMap慢一点,因为它需要维护链表来保持Map中key-value的添加顺序。

HashSet和HashMap的性能选项

对于HashSet及其子类而言,它们采用Hash算法来决定集合中元素的存储位置,并通过hash算法来控制集合的大小;对于HashMap、HashTable及其子类而言,它们采用hash算法来决定Map中key的存储,并通过hash算法来增加key集合的大小。

hash表里可以存储元素的位置被称为“桶”,在通常情况下,单个“桶”里存储一个元素,此时又最好的性能:hash算法可以根据hashCode值计算出“桶”的存储位置,接着从“桶”中取出元素。但hash表的状态为open:在发生“hash冲突”的情况下,单个桶会存储多个元素,这些元素以链表形式存储,必须按顺序搜索。如图是hash表保存各元素,且发生“hash冲突”的示意图:
这里写图片描述

因为HashSet、HashMap、Hashtable都使用hash算法来决定其元素的存储,因此HashSet、HashMap的hash表包含如下属性:

  • 容量:hash表中桶的数量
  • 初始化容量:创建hash表时桶的数量。HashSet和HashMap都允许在构造器中指定初始化容量。
  • 尺寸:当前散列表中记录的数量
  • 负载因子:负载因子等于“size/capacity”
0 0
原创粉丝点击