??????? java.util包中的集合类包含 Java 中某些最常用的类。最常用的集合类是 List 和 Map。List 的具体实现包括 ArrayList 和 Vector,它们是可变大小的列表,比较适合构建、存储和操作任何类型对象元素列表。List 适用于按数值索引访问元素的情形。
??????? Map 则提供了一个更通用的元素存储方法。Map 集合类用于存储元素对(称作“键”和“值”),其中每个键映射到一个值。从概念上而言,您可以将 List 看作是具有数值键的 Map。而实际上,除了 List 和 Map 都在定义 java.util 中外,两者并没有直接的联系。
??????? Map接口的实现类有很多,其中HashMap就是比较重要的一个实现,本文就以HashMap为主重点介绍。
??????? HashMap是基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。(除了非同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同。)此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
??????? HashMap结合了ArrayList与LinkedList两个实现的优点,,虽然HashMap并不会向List的两种实现那样在某项操作上性能较高,但是在基本操作(get 和 put)上具有稳定的性能。
?
??????? 首先从成员变量开始一点点的来了解HashMap和上述几个概念。
?
??????? 1.HashMap的成员变量:
/** * 初始默认容量(必须为2的倍数) */ static final int DEFAULT_INITIAL_CAPACITY = 16; /** * 最大容量,如果被指定为一个更高的值必须为2的倍数,并且小于1073741824.(1<<30) */ static final int MAXIMUM_CAPACITY = 1 << 30; /** * 默认负载因子/负载系数 */ static final float DEFAULT_LOAD_FACTOR = 0.75f; /** * 内部实现表, 必要时调整大小,其长度亦为2的倍数 */ transient Entry[] table; /** * map中添加的元素个数 */ transient int size; /** * 扩容临界值,当size达到此值时进行扩容 (容量乘以负载因子). */ int threshold; /** * 内部实现表的负载因子 */ final float loadFactor; /** * 操作数,可以理解为map实例被操作的次数,包括添加,删除等等 */ transient volatile int modCount;
??????? HashMap其内部实现是一个Entry数组table,而Entry就是保存相应键值的实体。table数组默认大小为16,我们也可以在初始化时指定更大的值,但指定值必须为2的倍数。
??????? 通过对ArrayList的学习了解到ArrayList其内部实现也是数组,当被添加的元素超出数组的容纳极限时,ArrayList会对内部数组进行一次“扩容”,从而可以添加新的元素。
??????? 在HashMap中也有类似的概念,HashMap并不会像ArrayList一样直到数组都满了的情况下才去“扩容”,而是根据负载因子(load factor)来进行判断。
??????? 举例来说:HashMap实例中table数组的默认大小为16,负载因子为0.75,当添加元素个数大于等于12(16*0.75)时就会进行扩容。
??????? 所以说容量和负载因子直接影响着table数组是否扩容,什么时机扩容,进而影响这HashMap实例的性能。
?
??????? 2.Entry?元素
??????? 与LinkedList类似,HashMap也是采用Entry内部类来存储实际元素信息,以下是Entry的源代码(省略部分代码):
static class Entry<K, V> implements Map.Entry<K, V> { final K key; V value; Entry<K, V> next; final int hash; }
??????? Entry中包括4个成员变量,其中key为键,value为值,next指向下一个节点元素,hash为hash值。Entry通过next属性可以寻找到下一个节点的元素,进而通过遍历就可以找到相应key下存储的信息。
?
??????? 3.HashMap设置元素
??????? Map通过put方法来在Map实例中关联指定值与指定键。如果该实例已经包含了一个该键的映射关系,则旧值被替换。
??????? 示例如下:
Map map = new HashMap(); map.put("user1", "小明"); map.put("user2", "小强"); map.put("user3", "小红"); System.out.println("user1:" + map.get("user1")); System.out.println("user2:" + map.get("user2")); System.out.println("user3:" + map.get("user3")); map.put("user2", "小龙"); System.out.println("user1:" + map.get("user1")); //打印结果 user1:小明 user2:小强 user3:小红 user1:小明
??????? 首先,创建了一个HashMap的实例map,此时map实例中的table数组会默认初始化,创建一个长度为DEFAULT_INITIAL_CAPACITY=16的空数组。
??????? 然后,调用put方法将一对键、值(key,value)保存。当已存在Map实例中已存在指定key的映射时,会将新指定的value覆盖原value。
??????? 与LIst的相关实现add方法一样,HashMap的put方法是设置元素的入口,在put的过程中会进行一系列的判断与操作,所以只有将put方法理解透彻后HashMap的内部结构与机制才会更加清晰。
??????? HashMap进行put操作时按以下步骤执行:
??????? 1)判断key是否为空,如果为空则调用设置null的专有方法。
??????? 2)计算key的hash值。
??????? 3)通过hash与table数组的长度计算出该元素所要放置的数组下标。
??????? 4)遍历该下标下的Entry元素链,如果找到与指定key相同的Entry则直接替换该Entry的value值并返回。
??????? 5)如果未找到则添加一个新元素至该下标下的元素链前端。
??????? 以下是一张官网上对于put操作流程的描述图片,可以作为参考:
??????? 以下是put方法的源代码,其中我已经加入了相关描述便于大家理解:
/** * 设置指定值 */ public V put(K key, V value) { //1.首先判断key是否为null if (key == null) //如果为null则调用putForNullKey方法 return putForNullKey(value); //2.计算key的hash值 int hash = hash(key.hashCode()); //3.根据计算后的hash值与table数组长度计算该key应放置到table数组的那个下标位置 int i = indexFor(hash, table.length); //4.遍历该下标下的所有Entry,如果key已存在则覆盖该key所在Entry的value值 for (Entry<K, V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; //5.如果该key不存在则新添加Entry元素至数组指定位置,并且该Entry作为此下标元素链的头部 addEntry(hash, key, value, i); return null; }?
this.loadFactor = DEFAULT_LOAD_FACTOR;//0.75f threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);//16*0.75=12 table = new Entry[DEFAULT_INITIAL_CAPACITY];//16
Entry<K,V> e = table[bucketIndex]; table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } }????????如果HashMap实例中已经put了该key则只需遍历找到该节点Entry,更新其value并返回,所以更新已有key的操作不会调用addEntry方法。
/** * 返回指定key的value */ public V get(Object key) { // 1.判断可以是否为null if (key == null) return getForNullKey(); // 2.计算key的hash值 int hash = hash(key.hashCode()); // 3.遍历table指定下标下的Entry链 for (Entry<K, V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) { Object k; // 4.如果找到则返回该Entry的value if (e.hash == hash && ((k = e.key) == key || key.equals(k))) return e.value; } // 5.未找到则返回null return null; }
Map map = new HashMap(); map.put("user1", "小明"); map.put("user2", "小强"); map.put("user3", "小红"); map.remove("user2"); System.out.println("user1:" + map.get("user1")); System.out.println("user2:" + map.get("user2")); System.out.println("user3:" + map.get("user3")); //打印结果: user1:小明 user2:null user3:小红??????? 当主动调用remove方法时,会根据指定的key删除该节点元素。
/** * 删除指定key下内容 */ public V remove(Object key) { Entry<K, V> e = removeEntryForKey(key); return (e == null ? null : e.value); } /** * 根据指定key删除元素 */ final Entry<K, V> removeEntryForKey(Object key) { int hash = (key == null) ? 0 : hash(key.hashCode()); int i = indexFor(hash, table.length); Entry<K, V> prev = table[i]; Entry<K, V> e = prev; while (e != null) { Entry<K, V> next = e.next; Object k; if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { modCount++; size--; if (prev == e) table[i] = next; else prev.next = next; e.recordRemoval(this); return e; } prev = e; e = next; } return e; }??????? remove方法调用了另一个方法removeEntryForKey,removeEntryForKey方法会循环遍历指定下标下所有Entry节点元素,如果该key存在则修改该节点前一个节点的next指向,从而达到把该Entry节点移除Entry链的目的。
Map map = new HashMap(); map.put("user1", "小明"); map.put("user2", "小强"); map.put("user3", "小红"); Iterator iter = map.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = (Map.Entry) iter.next(); Object key = entry.getKey(); Object value = entry.getValue(); System.out.println("key:" + key + ";value:" + value); // 然后移除元素 if (key.toString().equals("user1")) { iter.remove(); } else if (key.toString().equals("user2")) { entry.setValue("小海"); } } System.out.println(map.get("user1")); System.out.println(map.get("user2")); System.out.println(map.get("user3")); // 打印结果: key:user2;value:小强 key:user1;value:小明 key:user3;value:小红 null 小海 小红??????? 此种方式操作简单,代码量少,效率较高,且可以直接操作元素,是常用的手段之一。
Map map = new HashMap(); map.put("user1", "小明"); map.put("user2", "小强"); map.put("user3", "小红"); Iterator iter = map.keySet().iterator(); while (iter.hasNext()) { Object key = iter.next(); Object value = map.get(key); System.out.println("key:" + key + ";value:" + value); // 然后移除元素 if (key.toString().equals("user1")) { iter.remove(); } } System.out.println(map.get("user1")); System.out.println(map.get("user2")); System.out.println(map.get("user3")); // 打印结果: key:user2;value:小强 key:user1;value:小明 key:user3;value:小红 null 小强 小红??????? 此种方式先需要将所有key遍历后返回,再通过get方法来获取元素,如果单纯需要操作Map实例中的个别节点元素时效率尚可,如果需要大规模获取和修改时效率不如第一种。所以两种方式选择那种需要视情况而言,并没有绝对。
Map map = new HashMap(); map.put("user1", "小明"); map.put("user2", "小强"); map.put("user3", "小红"); //转换成数组 String[] names= (String[]) map.values().toArray(new String[map.size()]); for (String name : names){ System.out.println(name); } //采用迭代 Collection nameArray =? map.values(); Iterator iter = nameArray.iterator(); while (iter.hasNext()) { ?String name=iter.next().toString(); ?System.out.println(name); } // 打印结果: 小强 小明 小红??????? 此种方式简单明了,适用于直接获取所有value的情况,可以直接迭代或者转换成数组,当直接显示value的情况下比较适用。
原文:http://286.iteye.com/blog/2187873