Java集合之Map

Map

Map用于保存具有映射关系的数据,因此Map集合里保存这两组值,一组值用于保存Map里的key,另一组值用于保存Map里的value,key和value都是可以是任何引用类型的数据。

注意:
Map 的key不允许重复,因为同一个Map对象的任何两个对象key通过equals方法比较总是返回false。
key和value之间存在单向一对一关系,即通过指定key,总能找到唯一、确定的value。

如果把Map里所有key放在一起来看,它们就组成了一个Set集合(所有的key没有顺序,key与key之间不能重复),实际上Map中确实包含了一个keySet()方法,用于返回Map里所有key 组成的Set集合。而Map中所有的value看起来非常类似一个List,元素与元素之间可以重复,每个元素可以根据索引来查找,只是Map中的索引不再使用整数值,而是以另外一个对象作为索引。

例如:如果需要从List集合中取出元素,则需要提供该元素的数字索引;如果需要从Map中取出元素,则需要提供该元素的key索引。

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是否为空(即不包含认可key-value对),如果为空则返回true。
  • Sey keySet()
    返回该Map中所有key组成的Set集合。
  • 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、value所阴影的key-value对。如果从该Map中成功的删除该key-value对,该方法返回true,否则返回false。
  • int size()
    返回Map里的key-value对的个数。
  • Collection values()
    返回Map里所有value组成的Collection。
  • Object getKey()
    返回Entry里包含的key值。
  • Object getValue()
    返回Entry里包含的value值。
  • Object setValue(V value)
    设置该Entry里包含的value值,并返回新设置的value值。

示例:

package com.map;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

public class MapTest {

    public static void main(String[] args) {
         Map map = new HashMap();
         map.put("Java疯狂讲义", 99);
         map.put("C++疯狂讲义", 89);
         map.put("C#疯狂讲义", 99);
         map.put("Java之大话西游", 109);
         System.out.println(map);

         //如果新的value覆盖旧的value,则返回旧的value 109
         System.out.println(map.put("Java之大话西游", 19));
         System.out.println(map.containsKey("C#疯狂讲义"));//true
         System.out.println(map.containsValue(99));//true

         for (Object key : map.keySet()) {
             System.out.println(key);
         }

         map.remove("C++疯狂讲义");
         //{Java之大话西游=19, Java疯狂讲义=99, C#疯狂讲义=99}
         System.out.println(map);

         Set<Map.Entry<String, Integer>> set = map.entrySet();
         Iterator iterator = set.iterator();
         while(iterator.hasNext()){
             Map.Entry<String, Integer> maps = (Entry<String, Integer>) iterator.next();
             System.out.println(maps.getKey() + ":"+maps.getValue());
             /**
              * 输出:
              * Java之大话西游:19
                Java疯狂讲义:99
                C#疯狂讲义:99
              */

         }
    }
}

Java8为Map新增的方法

  • Object compute(Object key,BiFunction remappingFunction)
    该方法使用remappingFunction根据原key-value对计算一个新的value。只要新value不为null,就使用新value覆盖原value;如果原value不为null,但新value为null,则删除原key-value对:如果原value、新value同时为null,那么该方法不改变任何key-value对,直接返回null。
  • Object computeIfAbsent(Object key ,Function mappingFunction)
    如果传给该方法的key参数在Map中对应的value为null,则使用mappingFunction根据key计算一个新的结果,如果计算结果不为null,则用计算结果覆盖原有value。如果原Map原来不包含该key,那么该方法可能会添加一组key-value对。
  • Object computeIfPresent(Object key ,BiFunction remappingFunction)
    如果传给该方法的key参数在Map中对应的value不为null,该方法将使用remappingFunction根据原key、value计算一个新的结果,如果计算结果不为null,则使用该结果覆盖原来的value,如果计算的结果为null,则删除原key-value对。
  • void forEach(BiConsumer action)
    该方法是Java 8为Map新增的一个遍历key-value对的方法。
  • Object getOrDefault(Object key ,V defaultValue)
    获取指定key对应的value。如果该key不存在则返回defaultValue。
  • Object merge(Object key , Object value ,BiFunction remappingFunction)
    该方法会先根据Key参数获取该Map中对应的value。如果获得value为null,则直接用传入的value覆盖原有的value(在这中情况下,可能要添加一组key-value对);如果获取的value不为null,则使用remappingFunction 函数根据原value、新value计算一新的结果,并用得到的结果去覆盖原有的value。
  • Object putIfAbsent(Object key ,Object value)
    该方法会自动检测指定key对应的value是否为null,如果该key对应的value为null,该方法将会用新value代替原来的null值。
  • Object replace(Object key ,Object value)
    将Map中指定key对应的value替换成新的value。与传统的put方法不同的是,该方法不可能添加新的key-value对。如果尝试替换的key在原Map中不存在,该方法不会添加key-value对,而是返回null。
  • boolean replace(K key ,V oldValue, V newValue)
    将Map中指定key-value对的原value提换成新value。如果在Map中找到指定的key-value对,则执行替换并返回true,否额返回false。
  • replaceAll(Bifunction function)
    该方法使用Bifunction 对原key-value对执行计算,并将计算结果作为key-value对的value的值。

HashMap和Hashtable

HashMap和Hashtable都是Map接口典型的实现类,它们之间的关系类似于ArrayList和Vector的关系。Hashtable是一个古老的Map实现类,它从JDK 1.0起就已经出现了。

HashMap与Hashtable区别:

  • 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。

注意:
Hashtable类是一个古老的类,它的命名甚至没有遵守Java的命名规范:每个单词的首字母都应该大写。大量的程序中使用了Hashtable类,所以这个类名也就不能改为HashTable了,否则将导致大量程序需要改写。与vector类似的是,尽量少用Hashtable实现类,即使需要创建线程安全的Map实现类,也无需使用Hashtable实现类,可以使用Collections工具类把HashMap变成线程安全的。

为了成功的在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即可

示例:

package com.map;

import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

public class HashtableTest {

    public static void main(String[] args) {

        Hashtable htb = new Hashtable();
        htb.put(new A(100), "Java讲义");
        htb.put(new A(90), "Html5精讲");
        htb.put(new A(1234), new B());

        System.out.println(htb);
        //只要亮的对象通过equals()方法,返回true
        //Hashtable就认为它们相等
        //由于Hashtable中有一个对象B
        //它与任何对象通过equals()方法都想等,所以下面返回true
        System.out.println( htb.containsValue("测试字符串"));
        //只要两个对象的count相等,它们通过equals()方法比较返回true,且hashCode()值相等
        //Hasntable 即认为它们是相同的key,返回true
        System.out.println( htb.containsKey(new A(100)));
        System.out.println( htb.remove(new A(1234)));
        System.out.println(htb);
    }
}

class A {
    int count;

    public A(int count) {
        this.count = count;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;

        if (obj != null && obj.getClass() == A.class) {
            A a = (A) obj;
            return a.count == count;
        }
        return false;
    }

    @Override
    public int hashCode() {
        return count;
    }
}

class B {
    @Override
    public boolean equals(Object obj) {
        return true;
    }
}

与HashSet类似的是,如果使用可变对象作为HashMap、Hashtable的key,并且程序修改了作为key的可变对象,则也可能出现与HashSet类似的情形:程序再也无法准确访问Map中被修改过的key。

示例:

package com.map;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

public class HashMapErrTest {

    public static void main(String[] args) {
        HashMap map = new HashMap();
        book book1 = new book(123);
        map.put(book1, "java大话西游");
        book book2 = new book(300);
        map.put(book2, "java精要");
        System.out.println(map);
        //{com.map.book@7b=java大话西游, com.map.book@12c=java精要}

        Iterator iterator = map.keySet().iterator();
        book first = (book) iterator.next();
        first.count = 666;
        map.remove(book1);
        System.out.println(map.get(book1));
        //null
        System.out.println(map);
        //{com.map.book@29a=java大话西游, com.map.book@12c=java精要}

    }
}

class book {
    int count;

    public book(int count){
        this.count = count;
    }

    @Override
    public boolean equals(Object obj) {
        if(obj == this)
            return true;

        if(obj != null && obj.getClass() == A.class){
            A a = (A) obj;
            return a.count == count;
        }
        return super.equals(obj);
    }

    @Override
    public int hashCode() {
        return count;
    }
}

注意:
如果确实需要使用可变对象的作为HashMap、Hashtable的key,则尽量不要在程序中修改作为key的可变的对象。

Properties

Properties类是Hashtable类的子类,该对象在处理属性文件时特别方便(Windows操作平台上ini文件就是一种属性文件)。
Properties类可以把Map对象和属性文件关联起来,从而可以把Map对象中的key-value对写入属性文件中,也可以把属性文件中的“属性名=属性值”加载到Map对象中。
由于属性文件里的属性名、属性值只能是字符串类型,所以Properties里的key、value都是字符串类型。

  • String getProperty(String key)
    获取Properties中指定属性名对应的属性值,类似与Map的get(Object key)。
  • String getProperty(String key,String defalultValue)
    该方法与前一个方法基本相似。该方法多一个功能,如果Properties中不存在指定key时,则该方法指定默认值。
  • Object setProperty(String key,String value)
    设置属性值,类似与Hashtable的put()方法。
  • void load(InputStream is)
    从属性文件(以输入流表示)中加载key-value对,把加载到的key-value对追加到Properties里(Properties是Hashtable的子类,它不保证key-value对之间的次序)。
  • void store(OutputStream out,String comments)
    将Properties中的key-value对输出到指定的属性文件(以输出流表示)中。

示例:

package com.map;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Properties;

public class HashtablePropertiesTest {

    public static void main(String[] args) {
        Properties proper = new Properties();
        proper.setProperty("username", "wu");
        proper.setProperty("password", "123");
        try {
            //将Properties中的key-value对输出到指定的属性文件(以输出流表示)中
            proper.store(new FileOutputStream("a.ini"), "connmen line");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println(proper);
        //{password=123, username=wu}

        Properties proper2 = new Properties();
        proper2.setProperty("gender", "male");
        try {
            //从属性文件(以输入流表示)中加载key-value对,把加载到的key-value对追加到Properties里
            //(Properties是Hashtable的子类,它不保证key-value对之间的次序)。
            proper2.load(new FileInputStream("a.ini"));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        System.out.println(proper2);
        //{password=123, gender=male, username=wu}
    }
}

各Map实现类的性能分析

Hashtable由于是一个古老的、线程安全的,因此HashMap通常比Hashtable要快。
TreeMap通常比HashMap、Hashtable要慢(尤其是在插入、删除key-value对时更慢),因为TreeMap底层采用红黑树来管理Key-value对(红黑树每个节点就是一个key-value对)。

优点:
TreeMap有一个好处,TreeMap中的key-value对总是处于有序状态,无序专门进行排序操作。
当TreeMap被填充之后,就可以调用keySet(),取得由key组成的Set,然后使用toArray()方法生成key的数组,接下来使用Arrays的binarySearch()方法在已排序的数组中快速查询对象。

一般场景应该使用HashMap,因为HashMap正式为快速查询设计的(HashMap底层其实也是采用数组来存储key-value对)。但如果程序需要一个总是排序,则应该考虑使用TreeMap。

LinkedHashMap比HashMap慢一点,因为它需要维护链表来保持Map中key-value时的日添加顺序。IdentityHashMap性能没有特别出色之处,因为它采用与HashMap基本类似的实现,只是它使用==而不是equals()方法来判断元素相等。

EnumMap的性能最好,但它只能使用同一个枚举类的枚举值作为key。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值