首先需要知道的是HashMap实现了Map接口,而LinkedHashMap是HashMap的子类。
1.HashMap:
HashMap实现了Map接口,因此Map包含的方法,HashMap都能予以实现(从这一点来说,HashMap与HashTable类似,但HashMap是不同步的,除此之外,HashMap允许至多一个key为null,允许多个value为null。而HashTable不仅是同步的,同时不允许null的出现)。
HashMap不能保证map中成员的顺序,并且随着时间的推移,map中成员的顺序还可能发生改变。
在多线程的环境下,如果多个线程同时访问一个HashMap,并且至少有一个线程对一个HashMap进行“删除or添加键值对”操作,如果没有进行同步操作的话,可以在创建对象时使用Collections.synchronizedMap方法:
Map m = Collections.synchronizedMap(new HashMap(....));
HashMap包含的方法(只挑一些常见的方法进行说明,方法的详细内容可以查看官方文档):
2.LinkedHashMap:通俗地讲LinkedHashMap是使用哈希表和链表实现的Map,与HashMap不同的是,它实现了一个双向链表,并且LinkedHashMap有固定的顺序,通常这个顺序与插入entry的顺序保持一致。
前面我们提到过,HashMap的顺序可能会随着时间的推移发生变化。设想这样一个场景:我们将用户提交的内容写进一个HashMap里,在很短的时间里,用户再度取回他提交的数据,这时用户发现他的数据顺序发生了变化。尽管数据的内容并没有改变,但是明明没有对数据进行修改,数据的顺序却发生了变化,这是用户所不希望看见的。这时,可以考虑使用LinkedHashMap来保存这个map的副本,这样map中键值对顺序就不会发生改变。代码如下:
void foo(Map m) {
Map copy = new LinkedHashMap(m);
...
}
在官方文档中,我们看见LinkedHashMap比HashMap多出一个方法:removeEldestEntry(Map.Entry)
protected boolean removeEldestEntry(Map.Entry eldest)
//Returns true if this map should remove its eldest entry.
这个方法的意思是:通过覆盖这个方法可以实现“当这个方法返回true时,map中最老的键值对会被删除“的功能,举例如下:
class FixedSizeLinkedHashMap extends LinkedHashMap{
private static final long serialVersionUID = 6918023506928428613L;
private static int MAX_ENTRIES = 10;
/**
* 获得允许存放的最大容量
* @return int
*/
public static int getMAX_ENTRIES() {
return MAX_ENTRIES;
}
/**
* 设置允许存放的最大容量
* @param int max_entries
*/
public static void setMAX_ENTRIES(int max_entries) {
MAX_ENTRIES = max_entries;
}
/**
* 如果Map的尺寸大于设定的最大长度,返回true,再新加入对象时删除最老的对象
* @param Map.Entry eldest
* @return int
*/
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > MAX_ENTRIES;
}
}
在上面的类中,我们自己定义了一个类FixedSizeLinkedHashMap,继承自LinkedHashMap,并且重写了removeEldestEntry方法。该方法表示如果map的size大于设定的最大长度则返回true。因此当我们向该类的一个对象中添加键值对时,如果键值对的个数大于10,那么最旧的键值对就会被删除掉。
LinkedHashMap的性能可能略低于HashMap,因为维护链表需要额外的开销。但是有一个例外,对map的collection-views进行迭代时,LinkedHashMap的性能与map的大小成正比,与容量无关;当迭代的对象是HashMap时,需要的时间可能会更多,因为迭代的时间跟map的容量成正比。
在考虑并发时,HashMap中需要注意的structural modification(暂译为结构修改)主要包括add or delete 一个或多个键值对。而在LinkedHashMap中,structural modification需要根据构造函数参数的不同进行区分,当构造函数包含accessOrder时(如下图):
LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)
//Constructs an empty LinkedHashMap instance with the specified initial capacity, load factor and ordering mode.
如果accessOrder为false,表示基于插入顺序(即map中键值对的顺序和插入的顺序保持一致,不会发生变化),那么structural modification就不包括其他的操作;如果accessOrder为true,表示LinkedHashMap基于访问顺序(也就是说当使用put和get方法访问一个键值对时,该键值对会被放在map的最尾端),这样的话structural modification就包括任何能够访问map键值对的方法,例如get方法,因为get(K key)方法改变了map的迭代次序(把访问的那个元素移到了map的最后)。
一个思考:当accessOrder==true&&开发者对removeEldestEntry()方法进行相应的覆盖时,是不是可以用LinkedHashMap实现LRU算法呢?(这个用作读者们自己思考,相应的测试代码放在文章的最后。)
3.总结:
写在最后的话:
import java.util.*;
public class TestHash {
@SuppressWarnings("unchecked")
public static void main(String[] args)
{
Map m = new FixedSizeLinkedHashMap(10,0.75f,true);
for(int i=0;i<10;i++)
{
m.put(String.valueOf(i), i);
}
//初始化的map
Iterator it = m.entrySet().iterator();
System.out.println("--------------------");
while(it.hasNext())
{
System.out.println(it.next().toString());
}
//对键"0"进行访问
Integer j = m.get("0");
//put第11个元素
m.put("10", 10);
//put第11个元素后map的变化(map的最大元素个数为10)
it = m.entrySet().iterator();
System.out.println("--------------------");
while(it.hasNext())
{
System.out.println(it.next().toString());
}
}
}
class FixedSizeLinkedHashMap extends LinkedHashMap{
private static final long serialVersionUID = 6918023506928428613L;
private final int maxCapacity = 10;
public FixedSizeLinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)
{
super(initialCapacity,loadFactor,accessOrder);
}
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > maxCapacity;
}
}
如有错误,欢迎指正。