Java之LinkedHashMap

来源:互联网 发布:中国经济现状数据 编辑:程序博客网 时间:2024/06/01 09:35

——————————————————————————————————————————————

一、类的继承和实现关系:

public class LinkedHashMap<K,V> 

extends HashMap<K,V>

implements Map<K,V>

——————————————————————————————————————————————

二、构造方法摘要:

LinkedHashMap() 

          构造一个带默认初始容量 (16) 和默认加载因子 (0.75) 的空插入顺序 LinkedHashMap 实例。 

LinkedHashMap(int initialCapacity) 

          构造一个带指定初始容量和默认加载因子 (0.75) 的空插入顺序 LinkedHashMap 实例。 

LinkedHashMap(int initialCapacity, float loadFactor) 

          构造一个带指定初始容量和指定加载因子的空插入顺序 LinkedHashMap 实例。 

LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) 

          构造一个带指定初始容量、指定加载因子和排序模式的空 LinkedHashMap 实例。

          accessOrder - 排序模式 - 对于访问顺序,为 true;对于插入顺序,则为 false

LinkedHashMap(Map<? extends K,? extends V> m) 

          构造一个映射关系与指定映射相同的插入顺序 LinkedHashMap实例。 所创建的 LinkedHashMap 实例具有默认的加载因子 (0.75) 和足以容纳指定映射中映射关系的初始容量。

——————————————————————————————————————————————

三、方法摘要:
 void clear() 
          从该映射中移除所有映射关系。 

 boolean containsValue(Object value) 

          如果此映射将一个或多个键映射到指定值,则返回 true。 

 V get(Object key) 

          返回此映射中映射到指定键的值。 如果此映射中没有该键的映射关系,则返回 null。返回 null 值并不一定 表明此映射不包含该键的映射关系;

也可能此映射将该键显式地映射为 null。可使用 containsKey 操作来区分这两种情况。

protected  boolean removeEldestEntry(Map.Entry<K,V> eldest) 

          如果此映射移除其最旧的条目,则返回 true。 

——————————————————————————————————————————————

四、相对于Map的其他实现类的一些特性:

(常用的四个实现类:HashMap、Hashtable、LinkedHashMap、TreeMap)

  • Map主要用于存储健值对,并根据键得到值,因此不允许键重复(重复了覆盖了),但允许值重复。

1.一般情况下,我们用的最多的是HashMap。

2.在Map 中插入、删除和定位元素,HashMap 是最好的选择。

3.如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。

4.如果需要输出的顺序和输入的相同,那么用LinkedHashMap可以实现,它还可以按读取顺序来排列。


HashMap:

1.HashMap是一个最常用的Map,它根据键的hashCode值存储数据,具有很快的访问速度。

2.HashMap允许一条记录的键为NULL,允许多条记录的值为NULL。

3.HashMap不支持线程同步,即任一时刻可以有多个线程同时写HashMap,可能会导致数据的不一致性。如果需要同步,可以用Collections的synchronizedMap方法使HashMap具有同步的能力。

4.HashMap里面存储的数据是无序的,HashMap在put的时候是根据key的hashcode进行排序,然后放入对应的地方。

所以在按照一定顺序put进HashMap中,然后遍历出HashMap的顺序跟put的顺序不同(除非在put的时候key已经按照hashcode排序号了,这种几率非常小)。

单纯的HashMap是无法实现排序的,这儿的排序是指,我们将键值对按照一定的顺序put进HashMap里,然后在进行取键值对的操作的时候,是按照put进去的顺序把键值对取出来的。

5.因为HashMap无法排序,为了解决这个问题,JAVA在JDK1.4以后提供了LinkedHashMap来帮助我们解决排序问题。


Hashtable:

1.Hashtable与HashMap类似,Hashtable继承自Dictionary类,Java 2以后才实现了Map接口。

2.Hashtable与HashMap不同的是:Hashtable不允许记录的键或者值为空。

3.Hashtable支持线程同步,因此也导致了Hashtable在写入时会比较慢。

LinkedHashMap:

1.LinkedHashMap是HashMap的一个子类,但是不同的是LinkedHashMap保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的。

2.LinkedHashMap在遍历的时候会比HashMap慢,不过当容量很大,实际数据较少时例外,遍历起来可能会比HashMap快,

因为LinkedHashMap的遍历速度只和实际数据有关,和容量无关,而HashMap的遍历速度和他的容量有关。

链接的哈希映射具有两个影响其性能的参数:初始容量和加载因子。为初始容量选择非常高的值对此类的影响比对 HashMap 要小,因为此类的迭代时间不受容量的影响。

3.如果多个线程同时访问链接的哈希映射,而其中至少一个线程从结构上修改了该映射,则它必须保持外部同步

4.它的父类是HashMap,使用双向链表来维护键值对的次序,迭代顺序与键值对的插入顺序保持一致。

5.LinkedHashMap需要维护元素的插入顺序,so性能略低于HashMap,但在迭代访问元素时有很好的性能,因为它是以链表来维护内部顺序。

6.重写 removeEldestEntry(Map.Entry) 方法来实施策略,以便在将新映射关系添加到映射自动移除旧的映射关系

7.LinkedHashMap与 HashMap 的不同之处在于,LinkedHashMap维护着一个运行于所有条目的双重链接列表。

此链接列表定义了迭代顺序,该迭代顺序通常就是将键插入到映射中的顺序(插入顺序)。


TreeMap:

1.TreeMap在遍历的时候会比HashMap慢

2.TreeMap能够把它保存的数据根据键排序,默认是按升序排序,也可以另外指定排序的比较器。

3.当用Iterator遍历TreeMap时,得到的记录是排过序的。

4.Map接口派生了一个SortMap子接口,SortMap的实现类为TreeMap。

5.TreeMap也是基于红黑树对所有的key进行排序,有两种排序方式:自然排序和定制排序。

6.TreeMap的key以TreeSet的形式存储,对key的要求与TreeSet对元素的要求基本一致。


HashMap与HashTable的区别:

1.同步性:Hashtable是同步的,而HashMap则是异步的。

2.执行效率:因为同步的要求会影响执行的效率,所以如果你不需要线程安全的集合那么使用HashMap是一个很好的选择,这样可以避免由于同步带来的不必要的性能开销,从而提高效率.............

3.值:HashMap可以让你将空值作为一个表的条目的key或value,但是Hashtable是不能放入空值的。

HashMap最多只有一个key值为null,但可以有无数多个value值为null,而Hashtable不允许键和值为null。

注意:

1.用作key的对象必须实现hashCode和equals方法。
2.两者都不能保证其中的键值对的顺序
3尽量不要使用可变对象作为它们的key值。


EnumMap类:

1.EnumMap中所有key都必须是单个枚举类的枚举值,创建EnumMap时必须显示或隐式指定它对应的枚举类。

2.EnumMap根据key的自然顺序,即枚举值在枚举类中定义的顺序,来维护键值对的次序。

3.EnumMap不允许使用null作为key值,但value可以。


WeakHashMap:

1.WeakHashMap与HashMap的用法基本相同,区别在于:

 后者的key保留对象的强引用,即只要HashMap对象不被销毁,其对象所有key所引用的对象不会被垃圾回收,HashMap也不会自动删除这些key所对应的键值对对象。

 但WeakHashMap的key所引用的对象没有被其他强引用变量所引用,则这些key所引用的对象可能被回收。

2.WeakHashMap中的每个key对象保存了实际对象的弱引用,当回收了该key所对应的实际对象后,WeakHashMap会自动删除该key所对应的键值对。

IdentityHashMap类:

1.IdentityHashMap也HashMap基本相似,只是当两个key严格相等时,即key1==key2时,它才认为两个key是相等的 。

2.IdentityHashMap也允许使用null,但不保证键值对之间的顺序。

——————————————————————————————————————————————

五、LinkedHashMap里面使用的LRU算法:

LRU算法摘自:http://blog.csdn.net/scelong/article/details/7187142

LRU(Least Recently Used),又叫:最近最少使用算法

1.将最近一段时间内最少被访问过的行淘汰出局。

2.因此需要为每行设置一个计数器,LRU算法是把命中行的计数器清零,其他各行计数器加1

3.当需要替换时淘汰行计数器计数值最大的数据行出局。

4.这是一种高效、科学的算法,其计数器清零过程可以把一些频繁调用后再不需要的数据淘汰出Cache,提高Cache的利用率。

5.利用LinkedHashMap实现LRU算法,主要是重写boolean removeEldestEntry(Map.Entry<K,V> eldest)方法。

如果应该从映射移除最旧的条目,则返回 true;如果应该保留,则返回 false。


重写 removeEldestEntry(Map.Entry) 方法来实施策略,以便在将新映射关系添加到映射时自动移除旧的映射关系。

此方法可以提供在每次添加新条目时移除最旧条目的实现程序。如果映射表示缓存,则此方法非常有用:

它允许映射通过删除旧条目来减少内存损耗。 

public class LRULinkedHashMap<K, V> extends LinkedHashMap<K, V> {  

    private static final long serialVersionUID = 2490814505277321242L;  
    private final int maxCapacity;  
    private static final float DEFAULT_LOAD_FACTOR = 0.75f;  
    private final Lock lock = new ReentrantLock();  
      
    public LRULinkedHashMap(int maxCapacity) {  
        super(maxCapacity, DEFAULT_LOAD_FACTOR, true);  
        this.maxCapacity = maxCapacity;  
    }  
      
    @Override  
    protected boolean removeEldestEntry(java.util.Map.Entry<K, V> eldest) {  
        return size() > maxCapacity;  
    }  
    @Override  
    public V get(Object key) {  
        try {  
            lock.lock();  
            return super.get(key);  
        }finally {  
            lock.unlock();  
        }  
    }  
      
    public V put(K key, V value) {  
        try {  
            lock.lock();  
            return super.put(key, value);  
        } finally {  
            lock.unlock();  
        }  
    };  
}  

在缓存系统中,实现的算法,如下(常见的)cahce算法cache algorithm或者叫替换算法:

1.最近最少使用算法 Least Recently Used (LRU):

最直观的结构应该是List,采取的算法是:每次访问一个元素后把这个元素放在 List一端,这样一来最远使用的元素自然就被放到List的另一端。每次evict的时候就把那最远使用的元素remove掉。

但是现实中常采用的数据结构是HashMap + List。因为List太慢,List只能提供O(n)的算法,要使得它的add,remove和get的算法为O(1)就必须使用HashMap。

最简 单的实现就是利用JDK自带的LinkedHashMap,你可以把它看作普通的HashMap之外,每个元素的key都用链表连接起来从而实现顺序结 构。

LinkedHashMap默认的元素顺序是put的顺序,但是如果使用带参数的构造函数,那么LinkedHashMap会根据访问顺序来调整内部 顺序。

 LinkedHashMap的get()方法除了返回元素之外还可以把被访问的元素放到链表的底端,这样一来每次顶端的元素就是remove的元素。

2. First In, First Out算法:

这个比较直观,就是个Queue但是还是为了保证O(1)的效率,还是要用LinkedHashMap。但是这次使用默认的无

参数的构造函数,LinkedHashMap内部使用的是put的顺序。因此每次remove顶端即可。

3. 使用次数最小算法 Least Frequently Used (LFU):

这 个算法的核心是每次访问元素的时候,这个元素的次数属性加1。所以每次remove操作就是次数属性最小的

元素。这次没法用LinkedHashMap来 实现了,因为LinkedHashMap没有接受comparator参数的功能。有些程序

是用LinkedList + HashMap来实现。这样add和get操作还是O(1),只是remove操作的时候先要排序然后再remove,

最快也就是O(n*log n),譬如利用快速排序。或者干脆在remove的时候只是做查找最小元素的算法来除去访问

次数最小的元素。

——————————————————————————————————————————————

六、LinkedHashMap源码分析:

下面内容摘自:http://zhangshixi.iteye.com/blog/673789

1. LinkedHashMap概述:

LinkedHashMap是Map接口的哈希表和链接列表实现,具有可预知的迭代顺序。

允许使用多个null值和多个null键。

LinkedHashMap实现与HashMap的不同之处在于,LinkedHashMap维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序可以是插入顺序或者是访问顺序。
LinkedHashMap实现是不同步的。如果多个线程同时访问链接的哈希映射,而其中至少一个线程从结构上修改了该映射,则它必须保持外部同步。HashMap也不同步。

 2. LinkedHashMap的实现:

对于LinkedHashMap而言,它继承了HashMap,底层使用哈希表与双向链表来保存所有元素。

其基本操作与父类HashMap相似,它通过重写父类相关的方法,来实现自己的链接列表特性。下

面我们来分析LinkedHashMap的源代码:

   1) Entry元素:

   LinkedHashMap采用的hash算法和HashMap相同,但是它重新定义了数组中保存的元素Entry,该Entry除了保存当前对象的引用外,还保存了其上一个元素before和下一个元素after的引用,从而在哈希表的基础上又构成了双向链接列表。看源代码:

Java代码  收藏代码
  1. /** 
  2.  * 双向链表的表头元素。 
  3.  */  
  4. private transient Entry<K,V> header;  
  5.   
  6. /** 
  7.  * LinkedHashMap的Entry元素。 
  8.  * 继承HashMap的Entry元素,又保存了其上一个元素before和下一个元素after的引用。 
  9.  */  
  10. private static class Entry<K,V> extends HashMap.Entry<K,V> {  
  11.     Entry<K,V> before, after;  
  12.     ……  
  13. }  

    2) 初始化:

   通过源代码可以看出,在LinkedHashMap的构造方法中,实际调用了父类HashMap的相关构造方法来构造一个底层存放的table数组。如:

Java代码  收藏代码
  1. public LinkedHashMap(int initialCapacity, float loadFactor) {  
  2.     super(initialCapacity, loadFactor);  
  3.     accessOrder = false;  
  4. }  

    HashMap中的相关构造方法:

Java代码  收藏代码
  1. public HashMap(int initialCapacity, float loadFactor) {  
  2.     if (initialCapacity < 0)  
  3.         throw new IllegalArgumentException("Illegal initial capacity: " +  
  4.                                            initialCapacity);  
  5.     if (initialCapacity > MAXIMUM_CAPACITY)  
  6.         initialCapacity = MAXIMUM_CAPACITY;  
  7.     if (loadFactor <= 0 || Float.isNaN(loadFactor))  
  8.         throw new IllegalArgumentException("Illegal load factor: " +  
  9.                                            loadFactor);  
  10.   
  11.     // Find a power of 2 >= initialCapacity  
  12.     int capacity = 1;  
  13.     while (capacity < initialCapacity)  
  14.         capacity <<= 1;  
  15.   
  16.     this.loadFactor = loadFactor;  
  17.     threshold = (int)(capacity * loadFactor);  
  18.     table = new Entry[capacity];  
  19.     init();  
  20. }  

我们已经知道LinkedHashMap的Entry元素继承了HashMap的Entry元素,并提供了双向链表的功能。在上述HashMap的构造器
中,最后会调用init()方法,进行相关(双向链表)的初始化,这个方法在HashMap的实现中并无意义,只是提供给子类实现相关的初始化调用。
LinkedHashMap重写了init()方法,在调用父类的构造方法完成构造后,进一步实现了对其元素Entry的初始化操作。

Java代码  收藏代码
  1. void init() {  
  2.     header = new Entry<K,V>(-1nullnullnull);  
  3.     header.before = header.after = header;  
  4. }  

    3) 存储:

   LinkedHashMap并未重写父类HashMap的put方法,而是重写了父类HashMap的put方法调用的子方法void addEntry(int hash, K key, V value, int bucketIndex) 和void createEntry(int hash, K key, V value, int bucketIndex),提供了自己特有的双向链接列表的实现

Java代码  收藏代码
  1. void addEntry(int hash, K key, V value, int bucketIndex) {  
  2.     // 调用create方法,将新元素以双向链表的的形式加入到映射中。  
  3.     createEntry(hash, key, value, bucketIndex);  
  4.   
  5.     // 删除最近最少使用元素的策略定义  
  6.     Entry<K,V> eldest = header.after;  
  7.     if (removeEldestEntry(eldest)) {  
  8.         removeEntryForKey(eldest.key);  
  9.     } else {  
  10.         if (size >= threshold)  
  11.             resize(2 * table.length);  
  12.     }  
  13. }  
Java代码  收藏代码
  1. void createEntry(int hash, K key, V value, int bucketIndex) {  
  2.     HashMap.Entry<K,V> old = table[bucketIndex];  
  3.     Entry<K,V> e = new Entry<K,V>(hash, key, value, old);  
  4.     table[bucketIndex] = e;  
  5.     // 调用元素的addBrefore方法,将元素加入到哈希、双向链接列表。  
  6.     e.addBefore(header);  
  7.     size++;  
  8. }  
Java代码  收藏代码
  1. private void addBefore(Entry<K,V> existingEntry) {  
  2.     after  = existingEntry;  
  3.     before = existingEntry.before;  
  4.     before.after = this;  
  5.     after.before = this;  
  6. }  

    4) 读取:

LinkedHashMap重写了父类HashMap的get方法,实际在调用父类getEntry()方法取得查找的元素后,再判断当排序模式accessOrder为true时,记录访问顺序,将最新访问的元素添加到双向链表的表头,并从原来的位置删除。由于的链表的增加、删除操作是常量级的,故并不会带来性能的损失。

Java代码  收藏代码
  1. public V get(Object key) {  
  2.     // 调用父类HashMap的getEntry()方法,取得要查找的元素。  
  3.     Entry<K,V> e = (Entry<K,V>)getEntry(key);  
  4.     if (e == null)  
  5.         return null;  
  6.     // 记录访问顺序。  
  7.     e.recordAccess(this);  
  8.     return e.value;  
  9. }  
Java代码  收藏代码
  1. void recordAccess(HashMap<K,V> m) {  
  2.     LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;  
  3.     // 如果定义了LinkedHashMap的迭代顺序为访问顺序,  
  4.     // 则删除以前位置上的元素,并将最新访问的元素添加到链表表头。  
  5.     if (lm.accessOrder) {  
  6.         lm.modCount++;  
  7.         remove();  
  8.         addBefore(lm.header);  
  9.     }  
  10. }  

    5) 排序模式:

   LinkedHashMap定义了排序模式accessOrder,该属性为boolean型变量,对于访问顺序,为true;对于插入顺序,则为false。

Java代码  收藏代码
  1. private final boolean accessOrder;  

 一般情况下,不必指定排序模式,其迭代顺序即为默认为插入顺序。看LinkedHashMap的构造方法,如:

Java代码  收藏代码
  1. public LinkedHashMap(int initialCapacity, float loadFactor) {  
  2.     super(initialCapacity, loadFactor);  
  3.     accessOrder = false;  
  4. }  

这些构造方法都会默认指定排序模式为插入顺序。如果你想构造一个LinkedHashMap,并打算按从近期访问最少到近期访问最多的顺序(即访问顺)来保存元素,那么请使用下面的构造方法构造LinkedHashMap:

Java代码  收藏代码
  1. public LinkedHashMap(int initialCapacity,  
  2.                      float loadFactor,  
  3.                      boolean accessOrder) {  
  4.     super(initialCapacity, loadFactor);  
  5.     this.accessOrder = accessOrder;  
  6. }  

该哈希映射的迭代顺序就是最后访问其条目的顺序,这种映射很适合构建LRU缓存。LinkedHashMap提供了removeEldestEntry(Map.Entry<K,V> eldest)方法,在将新条目插入到映射后,put和 putAll将调用此方法。该方法可以提供在每次添加新条目时移除最旧条目的实现程序,默认返回false,这样,此映射的行为将类似于正常映射,即永远不能移除最旧的元素(想要删除最旧条目的时候,要重写removeEldestEntry方法)

Java代码  收藏代码
  1. protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {  
  2.     return false 
  3. }  

    此方法通常不以任何方式修改映射,相反允许映射在其返回值的指引下进行自我修改。如果用此映射构建LRU缓存,则非常方便,它允许映射通过删除旧条目来减少内存损耗。
   例如:重写此方法,维持此映射只保存100个条目的稳定状态,在每次添加新条目时删除最旧的条目。

Java代码  收藏代码
  1. private static final int MAX_ENTRIES = 100;  
  2. protected boolean removeEldestEntry(Map.Entry eldest) {  
  3.     return size() > MAX_ENTRIES;  


有错误的地方欢迎大家指正。



0 0
原创粉丝点击