Java 8增强的Map集合
来源:互联网 发布:故宫淘宝实体店在哪 编辑:程序博客网 时间:2024/06/07 04:45
一、Map集合
Map用于保存具有映射关系的数据,因此Map集合里保存着两组值,一组值用于保存Map的key,另外一组值用于保存Map里的value,key和value都可以是任何引用类型的数据。Map的key不允许重复,即同一个Map对象的任何两个key通过equals方法比较总是返回false。
key和value之间存在单向一对一关系,即通过指定的key,总能找到唯一的、确定的value。从Map中取出数据时,总能找到唯一的、确定的value。从Map中取出数据时,只要给出指定的key,就可以取出对于的value。
如果把Map里所有key放在一起看,他们就组成了一个Set集合(所有的key没有顺序),可以与key之间不能重复,实际上Map确实包含了一个keySet()方法,用于返回Map里所有key组成的Set集合。
Map中定义了如下常用的的方法。
void clear()
boolean containsKey(Object key)
boolean containsValue(Object value)
Set entrySet()
Object get(Object key)
boolean isEmpty()
Set keySet()
Map提供了大量的实现类,典型实现如HashSet和Hashtable等、HashMap的子类LinkedHashMap,还有SortedMap子接口及该接口的实现类TreeMap,以及WeakHashMap、IdentityHashMap等。
**Map中包括了一个内部类Entry,该类封装了一个key-value对。**Entry还包含如下三个方法。
①Object getKey():返回Entry里包含的key值
②Object getValue():返回该Entry里包含的value值
③Object setValue(V value)设置该Entry里包含的value值,并返回新设置的value值。
Map集合最典型的用法就是成对地添加、删除key-value对,接下来即可判断该Map中是否包含指定key,是否包含指定value,也可以通过Map提供的keySet()方法获取所有key组成的集合,进而遍历Map中所有的key-value对。
下面程序示范了Map的基本功能。
public class MapTest { public static void main(String[] args) { Map map = new HashMap(); //成对放入多个key-value对 map.put("张三", 90); map.put("李四", 80); map.put("王五", 20); //对此放入key-value对value可重复 map.put("小二", 90); //放入重复的key-value对,新的value会覆盖原有的value //如果新的value覆盖了原有的value,该方法将会被返回被覆盖的value System.out.println(map.put("李四", 59));//输出80 System.out.println(map);//输出包含4个key-value对 //判断是否包含指定key System.out.println("是否包含值为 张三 这个key?" + map.containsKey("张三"));//输出true //获取Map集合所有key组成的集合,通过遍历key来实现遍历所有的key-value对 for(Object key : map.keySet()) { System.out.println(key + "-->" + map.get(key)); } map.remove("张三");//根据key来删除key-value对 System.out.println(map); }}
二、HashMap和Hashtable实现类
HashMap和HashTable都是Map接口的典型实现类,它们之间的关系类似于ArrayList和Vector的关系,HashTable是一个古老的Map实现类,他从JDK1.0开始就出现了,当他出现时,Java还没有提供Map接口,所以它包含了两个繁琐的方法,即elements()(类似于Map接口定义的values()方法和keys()类似于Map接口的keySet()方法)。
Java 8 改进了HashMap的实现,使用HashMao存在key冲突时具有更好的性能。
除此之外,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。
注意:
Hashtable的类名从类名上就可以看出它是一个古老的类,它的命名甚至没有遵守Java的命名规范:每个单词的首字母都应该大写。后来大量Java程序中使用了Hashtable类,所以这个类名也就不能改为HashTable了,否则将导致大量的程序需要改写。
为了成功地在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相等呢?HashMap、Hashtable判断两个value相等的标准更简单:只要两个对象通过equals()方法比较返回true即可。
import java.util.Hashtable;class A{ int count; public A(int count) { this.count = count; } //根据count的值来判断两个对象是否相等 public boolean equals(Object obj) { if(obj == this) return true; if(obj != null && obj.getClass() == A.class) { A a = (A)obj; return this.count == a.count; } return false; } //根据还有count值计算hashCode值 public int hashCode() { return this.count; }}class B{ //重写equals()方法,B对象与任何对象通过equals()方法比较都返回true public boolean equals(Object obj) { return true; }}public class HashtableTest { public static void main(String[] args) { Hashtable ht = new Hashtable(); ht.put(new A(6000), "Java"); ht.put(new A(875663), "C"); ht.put(new A(1232), new B()); System.out.println(ht); //只要两个对象通过equals()方法比较返回true //Hashtable就认为它们系相等的value //由于Hashtable中有一个B对象 //它与任何对象通过equals()方法比较都相等,所以下面输出true System.out.println(ht.containsValue("测试字符串"));//输出true //只要两个A对象的count相等,它们通过equals()方法返回true,且hashCode值相等 //HashCodetable即认为它们相同的key,所以下面输出true System.out.println(ht.containsValue(new A(87563))); //下面语句可以删除最后一个key-value队 ht.remove(new A(1232));//③ System.out.println(ht); }}
上面程序定义A类和B类,其中A类判断两个A对象相等的标准是count实例变量:只要两个A对象的count变量,则通过equals()方法比较它们返回true,它们的hashCode值也相等;而B对象则可以与任何对象相等。
Hashtable判断value相等的标准是:value与另外一个对象通过equals()方法比较返回true即可。上面程序中的ht对象中包含了一个B对象,它与任何对象通过equals()方法比较总是返回true,所以在①代码处返回true即可。上面程序中的ht对象中包含了一个B对象,它与任何对象通过equals()方法总是返回true,所以在①处代码处返回true。在这种情况下,无论传给ht对象的containsValue()方法参数是什么,程序总是返回true。
注意:当使用自定义类作为HashMap、Hashtable的key时,如果重写该类的equals(Object obj)和hashCode()方法,则应该保证两个方法的判断标准一致——当一个key通过equals()方法比较返回true时,两个key的hashCode()返回值也应该相同。
与HashSet类似的时,如果使用可变对象作为HashMap、Hashtable的key,并且程序修改了作为key的可变对象,则也可能出现与HashSet类似的情况,程序再也无法准确访问到Map中被修改过的key。
三、LinkedHashMap实现类
HashSet有一个LinkedHashSet子类,HashMap也有一个LinkedHash子类:LinkedHashMap也使用双向链表来维护key-value对的次序(其实只考虑key的次序),改链表负责维护Map的迭代顺序,迭代顺序与key-value对的插入顺序保持一致。
LinkedHashMap可以避免对HashMap、Hashtable的key-value对进行排序(只要插入key-value对时保持顺序即可),同事也可避免使用TreeMap所增加的成本。
LinkedHashMap需要维护元素的插入顺序,因此性能略低于HashMap的性能;但因为它以链表来维护内部顺序,所以在迭代访问Map里的全部元素时将有较好的性能。下面程序示范了LinkedHashMap的功能:迭代输出LinkedHashMap的元素时,将会按添加key-value对的顺序保持一致。
import java.util.LinkedHashMap;public class LinkedHashMapTest{ public static void main(String[] args) { LinkedHashMap scores = new LinkedHashMap(); scores.put("语文",80); scores.put("数学",82); scores.put("英文",76); scores.forEach((key,value) -> System.out.println(key + "-->" + value)); }}
上面最后一行使用了Java为Map新增的forEach()方法来遍历Map集合。
四、使用Properties读取属性文件
Properties类是Hashtable类的子类,正如它的名字所暗示的,该对象在处理属性文件时特别方便(Windows操作平台上的ini文件就是一种属性文件)。Properties类可以把Map对象和属性文件关联起来,从而可以把Map对象中的key-value对写入属性文件中,也可以把属性文件中“属性名=属性值”加载到Map对象中。由于属性文件里的属性名、属性值只能是字符串类型,所以Properties里的key、value都是字符串类型。该类提供了如下三个方法来修改Properties里的key、value值。
提示:
Properties相当于一个key、value都是String类型的Map。
①String getProperty(String key):获取Properties中指定属性名对应的属性值,类似于Map的get(Object obj)方法。
②String getProperty(String key,String defaultValue):该方法与前一个方法基本相似。该方法多一个功能,如果Properties中不存在指定的key时,则该方法指定默认值。
③Object setProperties(String key,String value):设置属性值,类似于Hashtable的put()方法。
④void load(InputStream inStream):从属性文件(输入流)中加载key-value对,吧加载的key-value对追加到Properties里(Properties树Hashtable的子类,它不保证key-value对之间的次序)。
⑤void store(OutputStream out , String comments):将Properties中的key-value对输出到指定的属性文件(以输出流表示)中。
import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import java.util.Properties;public class PropertiesTest { public static void main(String[] args) throws FileNotFoundException, IOException { Properties props = new Properties(); //向Properties中添加属性 props.setProperty("username", "glk"); props.setProperty("passwd", "aa"); //将Properties中的key-value对保存到a.ini文件中 props.store(new FileOutputStream("a.ini"), "comments line");//① //新建一个Properties对象 Properties props2 = new Properties(); //向Properties中添加属性 props2.setProperty("gendar", "male"); //将a.ini文件中的key-value对追加到prop2种 props2.load(new FileInputStream("a.ini"));//② System.out.println(props2); }}
上面程序中示范了Properties的用法,其中①代码处将Properties对象中的key-value对写入a.ini文件中。②处代码则从a.ini文件中读取key-value对,并添加到prop2对象中。
Properties可以把key-value对以XML文件的形式保存起来,也可以从XML文件中加载key-value对,用法与此类似。
五、SortedMap接口和TreeMap实现类
正如Set集合派生出SortedSet子接口,Sorted接口有一个TreeSet实现类一样,Map接口也派生出一个Sorted子接口,SortedMap接口也有一个TreeMap实现类。
TreeMap就是一个红黑树数据结构,每个key-value对(也就是Entry)即作为红黑树一个结点。TreeMap存储key-value对(结点)时,需要根据key对结点进行排序。TreeMap可以保证所有key-value对处于有序状态。TreeMap也有两种排序方式。
- 自然排序:TreeMap的所有key必须实现Comparable接口,而且所有key应该是一个同一个类的对象,否则将会抛出ClassCastException异常。
- 定制排序:创建TreeMap时,传入一个Compartor对象,该对象负责对TreeMap中所有key进行排序。采用定制排序不要求Map的key实现Comparable接口。
类似于TreeSet中判断两个元素的标准,TreeMap中判断两个key相等的标准是:两个key通过compareTo()方法返回0,TreeMap即认为这两个key是相等的。
如果使用自定义类作为TreeMap的key,且想让TreeMap良好地工作,则重写改类的equals()方法和compareTo()方法应保持一致的返回结果,两个key通过equals()方法比较返回true时,它们通过compareTo()方法比较应该返回0。如果equals()方法与compareTo()方法返回结果不一致,TreeMap与Map接口的规则就会冲突。
注意:再次强调,Set和Map的关系十分密切,Java源码就是先实现了HashMap、TreeMap等集合,然后通过包装一个所有的value都为null的Map集合实现了Set集合类。
六、WeakHashMap实现类
WeakHashMap与HashMap的用法基本相似。
与HashMap的区别在于,HashMap的key保留了对实际对象的强引用,这意味着只要该HashMap对象不被销毁,该HashMap的所有key所引用的对象就不会被回收,HashMap也不会自动删除这些key所对应的key-value对;
但WeakHashMap的key只保留了对实际对象的弱引用,这意味着如果WeakHashMap对象的key所引用的对象没有被其他强引用变量所引用,则这些key所引用的对象可能被垃圾回收,WeakHashMap也可能自动删除这些key所对应的key-value。
WeakHashMap中的每个key对象只有持有对实际对象的弱引用,因此,当垃圾回收该key所对应的实际对象之后,WeakHashMap会自动删除该key对应的key-value对。
看如下程序。
import java.util.WeakHashMap;public class WeakHashMapTest { public static void main(String[] args) { WeakHashMap whm = new WeakHashMap(); //向WeakHashMap中添加三个key-value对 //三个key都是匿名字符串对象(没有其他引用) whm.put(new String("语文"), new String("良好")); whm.put(new String("数学"), new String("及格")); whm.put(new String("英文"), new String("中单")); //向WeakHashMap中添加一个key-value对 //该key是一个系统缓存的字符串对象 whm.put("java" , new String("中等") );//① //输出whm对象,将看到4个key-value对象 System.out.println(whm); //通知系统进行垃圾回收 System.gc(); System.runFinalization(); //在通常情况下,将只看一个key-value对 System.out.println(whm); }}
编译,运行上面程序,看到如下运行结果:
{英文=中单, java=中等, 数学=及格, 语文=良好}{java=中等}
从上面运行结果可以看出,当系统进行垃圾回收时,删除了WeakHashMap对象的前三个key-value对。这是因为添加前三个key-value对(粗体字部分)时,这三个key都是匿名的字符串对象,WeakHashMap只保留了对它们的弱引用,这样垃圾回收时会自动删除这三个key-value对。
WeakHashMap对象第4组的key-value对(①号粗体代码行)的key时一个字符串直接量,(系统将会自动保留对该字符串对象的强引用),所以垃圾回收时不会回收它。
如果需要使用WeakHashMap的key来保留对象的弱引用,则不要让该key所引用的对象具有任何强引用,否则将失去使用WeakHashMap的意义。
七、IdentityHashMap实现类
这个Map实现类的实现机制与HashMap基本相似,但它在处理两个key相等时比较独特:在IdentityHashMap中,当且仅当两个key严格相等(key1 == key2)时,IdentityHashMap才认为两个key相等。对于普通的HashMap而言,只要key1和key2通过equals()方法比较返回true,且它们的hashCode值相等即可。
IdentityHashMap提供了与HashMap基本相似的方法,也允许使用null作为key和value。与HashMap相似:IdentityHashMap也不保证key-value对之间的顺序,更不能保证它们的顺序随时间的推移保持不变。
import java.util.IdentityHashMap;public class HashMapTest { public static void main(String[] args) { IdentityHashMap ihm = new IdentityHashMap(); //下面两行代码将会向IdentityHashMap对象中添加两个key-value对 ihm.put(new String("语文"),89); ihm.put(new String("语文"), 78); //下面两行代码只会向IdentityHashMap对象中添加也一个key-value对 ihm.put("java", 93); ihm.put("java", 98); System.out.println(ihm); }}
运行看到如下结果:
{语文=89, java=98, 语文=78}
上面试图向IdentityHashMap对象中添加4个key-value对,前两个key-value对中的key是新创建的字符串对象,它们通过==比较不相等,所以IdentityHashMap会把它们当成2个key来处理;后2个key-value对中的key都是字符串常量,而且它们的字符序列完全相同,Java使用常量池来管理字符串常量,所以它们通过==比较返回true,IdentityHashMap会认为它们是同一个key,因此只有一次可以添加成功。
八、EnumMap实现类
EnumMap是一个与枚举类一起使用的Map实现,EnumMap中的所有key都必须是单个枚举类的枚举值,创建枚举类必须显式或隐式地指定它对应的枚举类。EnumMap有如下特征:
- EnumMap在内部以数组形式保存,所以这种形式非常紧凑、高效。
- EnumMap根据key的自然顺序(即枚举值在枚举类中定义顺序)来维护key-value对顺序。当程序通过keySet()、entrySet()、values()等方法遍历EnumMap时可以看到这种顺序。
- EnumMap不允许使用null作为key,但允许使用null作为value。如果试图使用null作为key时将抛出NullPointerException一次。
与创建普通Map有所区别的是,创建EnumMap必须指定一个枚举类,从而将该EnumMap和指定枚举类关联起来。
import java.util.EnumMap;enum Season{ SPRING, SUMMER, FALL, WINTER}public class EnumMapTest { public static void main(String[] args) { //创建EnumMap对象,该EnumMap对象的所有key都是Season枚举类的枚举值 EnumMap enumMap = new EnumMap(Season.class); enumMap.put(Season.SUMMER, "夏日炎炎"); enumMap.put(Season.SPRING, "春暖花开"); System.out.println(enumMap); }}
上面创建了一个EnumMap对象, 创建该EnumMap对象时指定它的key只能是Season枚举类的枚举值。如果向该EnumMap中添加两个key-value对后,这两个key-value对将会以Season枚举值的自然顺序排序。
运行上面程序,看到如下运行结果:
{SPRING=春暖花开, SUMMER=夏日炎炎}
九、各Map实现类的性能分析
虽然HashMap和Hashtable的实现机制几乎一样,但由于Hashtable是一个古老的、线程安全的集合,因此HAshMap通常比Hashtable要快。
TreeMap通常比HashMap、Hashtable要慢(尤其是在插入、删除key-value对时更慢),因为TreeMap底层用红黑树来管理key-value对。
使用TreeMap有一个好处:TreeMap中的key-value对总是处于有序状态,无须专门进行排序操作。当TreeMap被填充之后,就可以调用keySet(),取得由key组成的Set,然后使用toArray()方法生成key的数组,接下来使用Arrays的binarySearch()在已排序的数组中快速查询对象。
对于一般的场景,程序多考虑使用HashMap,因为HashMap正是为快速查找设计的。但程序需要一个总是排好序的Map时,则可以考虑使用TreeMap。
LindedHashMap比HashMap慢一点,因为它需要维护链表来保持Map中key-value时的添加顺序。
EnumMap性能最好,但它只能使用同一个枚举类的枚举值作为key。
- Java 8增强的Map集合
- 增强For遍历Map集合的要点!
- java关于集合(list,set,map)的遍历与增强for循环的使用
- Java关于集合(list,set,map)的遍历与增强for循环的使用
- java增强for循环遍历Map集合问题
- JAVA使用增强for循环和迭代器遍历Map集合
- Java使用增强for循环和迭代器遍历Map集合
- 增强型for循环与Map集合的遍历
- java集合map的遍历
- Java集合 Map的遍历
- Java Map集合的详解
- java的集合list map
- JAVA的map集合练习
- Java集合之Map类型的集合
- java集合框架增强
- JAVA集合-Map集合
- java集合------Map集合
- Java集合----Map集合
- 极验验证——滑块拼图验证码
- C++ 函数模板和模板类
- 题目13:在O(1)时间删除链表结点
- QT中打印信息
- Android 利用Gradle实现app的环境分离
- Java 8增强的Map集合
- Rest API 开发 学习笔记
- uitextfield设置单行缩进
- 25条提高iOS App性能的技巧和诀窍 (部分内容重新翻译校对)
- 网易笔试
- 编程思维启蒙(Raptor)课程主页
- leetCode练习(19)
- 《The.Go.Programming.Language.2015.11》之 unsafe.Pointer
- 父进程,子进程,线程之间的关系