• JAVA基础知识之Map集合


    • Map的内部结构Entry
    1. Set与Map的关系
    2. Map的内部类Entry
    3. Map的通用方法及Map的简单用法
    • HashMap和HashTable的区别
    1. HashMap和HashTable判断元素相等的标准
    2. 可变对象对HashMap的影响
    • LinkedHashMap的特征
    • Properties 的特征
    • SortedMap接口和TreeMap类的特征
    • WeekHashMap
    • IdentifyHashMap与HashMap的区别
    • EnumMap的特征
    • 各种Map实现类的性能分析

    Map的内部结构

    Set和Map的关系

    Map由key-value对组成,key不可以重复(因为无序),Map接口定义了一个Set keySet();方法用来返回所有key.即所有key组成了一个Set集合。

    Map中key集合与集合容器Set非常相似,因此Map和Set的子集也非常相似,从名字上看,Set集合有HashSet, LinkeHashSet, SortedSet, TreeSet, EnumSet;而Map有HashMap, LinkedHashMap, SortedMap, TreeMap, EnumMap等子接口和实现类。Set和Map中的key集合的存储方式完全相同,从JDK源码来看,JAVA是先实现了Map, 然后包装一个value集为null的Map就实现了Set.

    Map的内部类Entry

    Map的内部有一个内部类Entry,用来封装key-value对。Entry中的部分方法,Object getKey(); Object getValue(); Object setValue(V value);下面是Map接口JDK源代码中的定义,

     1 public interface Map
     2 {
     3     public static interface Entry
     4     {
     5 
     6         public abstract Object getKey();
     7 
     8         public abstract Object getValue();
     9 
    10         public abstract Object setValue(Object obj);
    11 
    12         public abstract boolean equals(Object obj);
    13 
    14         public abstract int hashCode();
    15     }
    16 
    17 ....

    下面是Map接口的一个实现类HashMap, 可以看到HashMap的源码中,在内部类Entry中增加了几个私有成员,

     1 public class HashMap extends AbstractMap
     2     implements Map, Cloneable, Serializable
     3 {
     4     static class Entry
     5         implements Map.Entry
     6     {
     7 
     8 ......
     9 
    10         final Object key;
    11         Object value;
    12         Entry next;
    13         int hash;
    14 
    15 //初始化内部类
    16         Entry(int i, Object obj, Object obj1, Entry entry)
    17         {
    18             value = obj1;
    19             next = entry;
    20             key = obj;
    21             hash = i;
    22         }
    23     }
    24 
    25 ......

    当HashMap要put进新元素时,会初始化一个Entry类来保存key-value对。如下面的JDK源代码,

     1     public Object put(Object obj, Object obj1)
     2     {
     3         if(table == EMPTY_TABLE)
     4             inflateTable(threshold);
     5         if(obj == null)
     6             return putForNullKey(obj1);
     7         int i = hash(obj);
     8         int j = indexFor(i, table.length);
     9         for(Entry entry = table[j]; entry != null; entry = entry.next)
    10         {
    11             Object obj2;
    12             if(entry.hash == i && ((obj2 = entry.key) == obj || obj.equals(obj2)))
    13             {
    14                 Object obj3 = entry.value;
    15                 entry.value = obj1;
    16                 entry.recordAccess(this);
    17                 return obj3;
    18             }
    19         }
    20 
    21         modCount++;
    22         addEntry(i, obj, obj1, j);
    23         return null;
    24     }

    第22行会初始化一个Entry对象,

     1 ......
     2     void addEntry(int i, Object obj, Object obj1, int j)
     3     {
     4         if(size >= threshold && null != table[j])
     5         {
     6             resize(2 * table.length);
     7             i = null == obj ? 0 : hash(obj);
     8             j = indexFor(i, table.length);
     9         }
    10         createEntry(i, obj, obj1, j);
    11     }
    12 ......
    13     void createEntry(int i, Object obj, Object obj1, int j)
    14     {
    15         Entry entry = table[j];
    16         table[j] = new Entry(i, obj, obj1, entry);
    17         size++;
    18     }
    19 ......

    Map接口中定义的方法及Map的简单用法

    基本方法有get(Object obj), put(V value), remove(Object obj), putAll(Map map)...

    其他方法,故名思议,

    containsKey(Object obj);containsValue(Object obj);clear();equals(Object obj);

    另外一些方法,解释如下,

    public abstract Set keySet(); 返回该Map中所有key组成的Set集合
    public abstract Collection values();返回该Map中所有value组成的collection集合
    public abstract Set entrySet();返回Map中的所有Entry对象组成的Set集合,一个Entry对象是由key-value对组成。

    下面是Map的一个简单用法,

     1 package collection.map;
     2 
     3 import java.util.HashMap;
     4 import java.util.Map;
     5 
     6 public class Maps {
     7     public static void main(String[] args) {
     8         Map map = new HashMap();
     9         map.put("a",1);
    10         map.put("b",2);
    11         map.put("c",3);
    12         //key不同时,value可以重复
    13         map.put("d",4);
    14         System.out.println(map);
    15         //key重复时,新的value会覆盖旧的value,同时返回旧的value
    16         System.out.println(map.put("d", 5));
    17         System.out.println(map);
    18         System.out.println(map.containsKey("b"));
    19         System.out.println(map.containsValue(3));
    20         
    21         for(Object key : map.keySet()) {
    22             //map.get(key)方法获取value
    23             System.out.print(map.get(key)+",");
    24         }
    25         System.out.print("
    ");
    26         map.remove("c");
    27         System.out.println(map);
    28     }
    29 }

    执行结果,

    1 {d=4, b=2, c=3, a=1}
    2 4
    3 {d=5, b=2, c=3, a=1}
    4 true
    5 true
    6 5,2,3,1,
    7 {d=5, b=2, a=1}

     HashMap和Hashtable

    HashMap和Hashtable是Map的两个典型的实现类,它们之间的关系与HashSet和Vector完全类似。Hashtable是一个古老的Map实现类,虽然支持安全线程,但现在已经可以通过Collections工具类来同步容器的线程了,所以Hashtable用得非常少。

    HashMap和HashTable判断元素相等的标准

    与HashSet一样,HashMap和Hashtable判断元素是否相等的标准,也是需要同时满足两个key通过equals方法对比返回true,两个key的hashCode值要相等。在Map接口中已经定义了equals和hashCode方法,所以在HashMap和Hashtable中,必须重写这两个方法,且必须满足上面的规则。下面是一个例子,

     1 package collection.map;
     2 
     3 import java.util.HashMap;
     4 import java.util.Hashtable;
     5 
     6 class A {
     7     int count;
     8     public A(int count) {
     9         this.count = count;
    10     }
    11     //根据count的值来判断两个对象是否相等
    12     public boolean equals(Object obj) {
    13         if (this == obj) return true;
    14         if (obj != null && obj.getClass() == A.class) {
    15             A a = (A)obj;
    16             return this.count == a.count;
    17         }
    18         return false;
    19     }
    20     
    21     //根据count值来计算hashCode
    22     public int hashCode() {
    23         return this.count;
    24     }
    25 }
    26 
    27 class B {
    28     //重写equals, B与任何对象比较都返回true
    29     public boolean equals(Object obj) {
    30         return true;
    31     }
    32 }
    33 
    34 public class HashTables {
    35     public static void main(String[] args) {
    36         Hashtable ht = new Hashtable();
    37         ht.put(new A(100), "a");
    38         ht.put(new A(200), "b");
    39         ht.put(new A(300), new B());
    40         System.out.println(ht);
    41         //只要两个对象通过equal比较返回true,Hashtable就认为它们的value相等
    42         //由于此处Hashtable会调用value为B对象的equals做比较,所以总返回true,在HashMap中则返回false
    43         //这也是Hashtable和HashMap底层containsValue方法的区别
    44         System.out.println(ht.containsValue("test"));
    45         //比较key则需要equal相等且hashCode相等
    46         System.out.println(ht.containsKey(new A(300)));
    47         System.out.println(ht.containsKey(new A(305)));
    48         ht.remove(new A(200));
    49         System.out.println(ht);
    50     }
    51 
    52 }

    执行结果,

    1 {collection.map.A@12c=collection.map.B@1b5998f, collection.map.A@c8=b, collection.map.A@64=a}
    2 true
    3 true
    4 false
    5 {collection.map.A@12c=collection.map.B@1b5998f, collection.map.A@64=a}

    上面这个例子中可见,Hashtable判断两个元素是否相等(即判断Key是否相等)的标准是,即要equals返回true,又要hashCode相等,而Hashtable判断两个元素的value是否相等,只需要通过equals比较两个value是否返回true,并且这个equals是可以重载的。

    可变对象对HashMap的影响

    与HashSet类似的是,如果使用可变对象作为HashMap,Hashtable的key,如果程序修改了组成key的可变变量,则也可能会破坏HashMap和Hashtable,导致无法正常访问。

    比如下面的例子,

     1 package collection.map;
     2 
     3 import java.util.HashMap;
     4 import java.util.Iterator;
     5 
     6 class C {
     7     int count;
     8     public C(int count) {
     9         this.count = count;
    10     }
    11     //根据count的值来判断两个对象是否相等
    12     public boolean equals(Object obj) {
    13         if (this == obj) return true;
    14         if (obj != null && obj.getClass() == C.class) {
    15             C a = (C)obj;
    16             return this.count == a.count;
    17         }
    18         return false;
    19     }
    20     
    21     //根据count值来计算hashCode
    22     public int hashCode() {
    23         return this.count;
    24     }
    25 }
    26 
    27 public class HashMapErr {
    28     public static void main(String[] args) {
    29         HashMap hm = new HashMap();
    30         hm.put(new C(100),1);
    31         hm.put(new C(200), 2);
    32         hm.put(new C(300), 3);
    33         Iterator it = hm.keySet().iterator();
    34         C first = (C)it.next();
    35         System.out.println(first.count);
    36         first.count = 500;
    37         System.out.println(hm);
    38         //key被修改后,对应的元素无法删除,元素也无法取出
    39         hm.remove(new C(100));
    40         System.out.println(hm);
    41         System.out.println(hm.get(new C(1)));
    42         
    43         //未被修改的key对应的元素可以被删除
    44         hm.remove(new C(200));
    45         System.out.println(hm);    
    46     
    47     }
    48 }

    执行结果,

    1 100
    2 {collection.map.C@1f4=1, collection.map.C@c8=2, collection.map.C@12c=3}
    3 {collection.map.C@1f4=1, collection.map.C@c8=2, collection.map.C@12c=3}
    4 null
    5 {collection.map.C@1f4=1, collection.map.C@12c=3}

    LinkedHashMap的特征

    HashSet有一个子类LinkedHashSet,与此类似的是HashMap有一个子类LinkedHashMap。 LinkedHashMap也是一个有序集合,其内部由一个双向链表来维护元素顺序(只需要维护key的顺序),默认顺序为插入元素的顺序,Map的迭代顺序也是根据此双向链表决定的。

    LinkedHashMap因为要维护元素顺序,因此插入和删除的性能比起HashMap略低,但是迭代遍历的性能会更高。 下面是一个用LinkedHashMap迭代遍历的例子,

     1 package collection.map;
     2 
     3 import java.util.LinkedHashMap;
     4 import java.util.Map;
     5 
     6 public class LinkedHashMaps {
     7     public static void main(String[] args) {
     8         Map lhm= new LinkedHashMap();
     9         lhm.put("a", 1);
    10         lhm.put("b", 2);
    11         lhm.put("c", 3);
    12         System.out.println(lhm);
    13         lhm.remove("b");
    14         lhm.put("b", 2);
    15         System.out.println(lhm);
    16     }
    17 }

    执行结果,

    1 {a=1, b=2, c=3}
    2 {a=1, c=3, b=2}

    Properties 的特征

    Properties是Hashtable的一个子类,它的key和value都必须是String类型,它可以将一个Map对象和文件IO (inputStream, outputStream)关联起来,通过store(...)方法将Map对象的数据写入文件或者通过load(...)方法从文件读取数据写入Map. Properties还提供了以下方法,getProperty(...), setProperty(...)。下面是它的典型用法,

     1 package collection.map;
     2 
     3 import java.io.FileInputStream;
     4 import java.io.FileNotFoundException;
     5 import java.io.FileOutputStream;
     6 import java.io.IOException;
     7 import java.util.Properties;
     8 
     9 public class Propertieses {
    10     public static void main(String[] args) throws FileNotFoundException, IOException {
    11         Properties pos = new Properties();
    12         pos.setProperty("a", "1");
    13         pos.setProperty("b", "2");
    14         //write pos to output.txt
    15         pos.store(new FileOutputStream("output.txt"), "here is a comment line");
    16         
    17         Properties pos2 = new Properties();
    18         pos2.setProperty("c", "3");
    19         // read from output.txt and set to pos2
    20         pos2.load(new FileInputStream("output.txt"));
    21         System.out.println(pos2);
    22     }
    23 }

    执行结果,

    {b=2, a=1, c=3}

    同时,output.txt中写入了pos的内容如下,

    #here is a comment line
    #Tue Nov 01 14:01:09 CST 2016
    b=2
    a=1
    

    SortedMap接口和TreeMap类的特征

    Set接口派生了一个SortedSet子接口,SortedSet 有一个实现类TreeSet, 与此类似的是,Map接口派生了一个SortedMap子接口,SortedMap子接口有一个实现类TreeMap. TreeMap底层是一个红黑树数据结构,每个节点就是一个key-value对,节点按照key进行排序,TreeMap中和TreeSet一样,也有两种排序方式,即自然排序(由元素实现Comparable控制排序逻辑)和定制排序(在TreeMap集合中传入一个排序逻辑Comparator), 由于前面已经多次提到这两种排序方式,下面直接给出例子,

     1 package collection.map;
     2 
     3 import java.util.TreeMap;
     4 
     5 class T implements Comparable {
     6     int count;
     7     public T(int count) {
     8         this.count = count;
     9     }
    10     public String toString() {
    11         return "T[count:"+count+"]";
    12     }
    13     public boolean equals(Object obj) {
    14         if(this==obj) return true;
    15         if(obj != null && obj.getClass() == T.class) {
    16             T t = (T)obj;
    17             return this.count == t.count;
    18         }
    19         return false;
    20     }
    21     @Override
    22     public int compareTo(Object obj) {
    23         T t = (T)obj;
    24         return this.count - t.count;
    25     }
    26 }
    27 
    28 public class TreeMaps {
    29     public static void main(String[] args) {
    30         TreeMap tm = new TreeMap();
    31         tm.put(new T(5), "a");
    32         tm.put(new T(-3), "b");
    33         tm.put(new T(9), "c");
    34         System.out.println(tm);
    35         System.out.println(tm.firstEntry());
    36         System.out.println(tm.lastKey());
    37         System.out.println(tm.higherKey(new T(-3)));
    38         System.out.println(tm.lowerKey(new T(9)));
    39         //return sub map
    40         System.out.println(tm.subMap(new T(-3), new T(9)));
    41     }
    42 }

    执行结果

    1 {T[count:-3]=b, T[count:5]=a, T[count:9]=c}
    2 T[count:-3]=b
    3 T[count:9]
    4 T[count:5]
    5 T[count:5]
    6 {T[count:-3]=b, T[count:5]=a}

    定制排序和HashSet中的定制排序一样,即在TreeMap初始化的时候,传入一个Comparator对象作为参数, 排序逻辑在Comparator中写。

    从上面的例子中还可以看到,TreeMap判断两个元素相等的标准是,通过key进行equals比较返回true, 同时通过CompareTo比较key也要返回true。 因此在重写了key中的equals之后,一定要保证CompareTo方法也要返回跟equals一致的结果。

    WeakHashMap

    WeakHashMap是一种弱引用Map,意思是集合中的key对实际对象只有弱引用, 因此当垃圾回收了该key所对应的实际对象后, WeakHashMap会自动删除该key对应的key-value。 而HashMap是一种强引用,只要HashMap对象没被销毁,key所引用的对象是不会被垃圾回收器回收的,因此HashMap也不会自动删除key-value对。 下面的例子演示了WeakHashMap弱引用,

     1 package collection.map;
     2 
     3 import java.util.WeakHashMap;
     4 
     5 public class WeekHashMaps {
     6     public static void main(String[] args) {
     7         WeakHashMap wm = new WeakHashMap();
     8         //new String("aa")是匿名对象, WeakHashMap只保留它的弱引用
     9         wm.put(new String("aa"), 1);
    10         wm.put(new String("bb"), 2);
    11         //"cc"是字符串直接量,WeakHashMap保留它的强引用
    12         wm.put("cc", 3);
    13         System.out.println(wm);
    14         //通知系统回收垃圾
    15         System.gc();
    16         System.runFinalization();
    17         //前两个key-value元素将被删除,只能看到最后一个元素
    18         System.out.println(wm);
    19     }
    20 }

    执行结果,

    1 {cc=3, bb=2, aa=1}
    2 {cc=3}

    IdentifyHashMap

    IdentifyHashMap与HashMap的实现机制基本相同,不同点是IdentifyHashMap判断两个元素(即两个key)相等的标准不同。HashMap要求两个元素的key通过equals对比返回true,通过hashCode返回的值也相等,则认为这两个元素相等; 而IdentifyHashMap判断两个元素相等的标准是, 当且仅当两个元素的key严格相等(key1 == key2)时,才认为两个元素相等。下面演示IdentifyHashMap的用法,

     1 package collection.map;
     2 
     3 import java.util.IdentityHashMap;
     4 
     5 public class IdentityHashMaps {
     6     public static void main(String[] args) {
     7         IdentityHashMap im = new IdentityHashMap();
     8         im.put(new String("aa"),1);
     9         im.put(new String("aa"),2);
    10         System.out.println(im);
    11         im.put("bb",3);
    12         im.put("bb",4);
    13         System.out.println(im);
    14     }
    15 }

    可以看到,对于两个匿名对象new String("aa")的key,key1 != key2 ,因此IdentityHashMap会将它们看作不同的key,都添加进集合中。只有下面的两个常量字符串"bb",由于满足了key1==k2,所以IdentityHashMap只能添加其中一个(第二个覆盖了第一个),执行结果如下,

    1 {aa=2, aa=1}
    2 {aa=2, aa=1, bb=4}


    EnumMap的特征

    EnumMap必须与枚举对象一起使用,EnumMap中所有key必须是单个枚举类的枚举值,EnumMap根据key的自然顺序维护集合顺序。EnumMap内部以数组形式保存,所以性能好。下面示范用法,

     1 package collection.map;
     2 
     3 import java.util.EnumMap;
     4 
     5 enum Season
     6 {
     7     SPRING, SUMMER, FALL, WINTER
     8 }
     9 
    10 public class EnumMaps {
    11     public static void main(String[] args) {
    12         EnumMap em = new EnumMap(Season.class);
    13         em.put(Season.SPRING, 1);
    14         em.put(Season.SUMMER, 2);
    15         System.out.println(em);
    16     }
    17 }

    执行结果,

    {SPRING=1, SUMMER=2}


    各种Map实现类的性能分析

    对于Map的两个常用实现类HashMap和Hashtable, 其实现机制几乎一样,但是Hashtable是一个古老,保证线程安全的集合,所以要比HashMap性能差。

    TreeMap通常要比HashMap和Hashtable性能差,因为TreeMap底层要用红黑树来保持元素的顺序。

    TreeMap的一个典型用法是,由于TreeMap中的key总是处于排序状态,可以用keySet()方法获取所有key, 接着用toArray()方法生成一个数组,再用Arrays工具类中的binaryArray()方法对已经排序的key数组进行快速查询对象。

    对于一般情况,推荐使用HashMap,HashMap的查询速度也比较快(底层由object[]数组保存Entry对象),只是HashMap没有像TreeMap已经排好序的属性。

    LinkedHashMap比HashMap要慢,因为它需要链表来保存元素的添加顺序(用来做集合迭代)。

    IdentityHashMap与HashMap底层实现方式相似,性能上没有什么特别之处,只是两者判断元素是否相等的标准不同。

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


     

  • 相关阅读:
    Eclipse解决Ctrl+c很卡的方法
    关于编程,大学没有传授的十件事-月光博客
    最牛B的编码套路
    (CareerCup)find the largest repetitive sequence
    (CareerCup)Find next higher number with same digits
    2013年HTML5峰会 一场守望者的盛宴
    Youzi2D推出开源HTML5游戏加速引擎
    HTML5与原生APP之争胜负已出?
    编程的未来
    拖拽即可创建HTML5网站的建站平台
  • 原文地址:https://www.cnblogs.com/fysola/p/6016357.html
Copyright © 2020-2023  润新知