Merlin 的魔力: 维护插入顺序

级别: 初级                        John Zukowski                        2001 年 8 月 01 日

        J2SE 1.4 为 Java Collections Framework 引入了两个新实现 LinkedHashSetLinkedHashMap 。添加这两个新实现的好处是散列集合现在可以维护贯穿其元素的两条路径。除标准的散列关系之外,现在还有一个可遍历整个集合的链表。正常情况下,这个新的路径会遵循插入顺序,这意味着集合的迭代器将按照元素的插入顺序返回元素(而不按它们的散列码将其组合成一个集合的顺序);但 LinkedHashMap 支持第二种排序选项:按存取顺序而非插入顺序维护链表。

我们来看一下这些新类是如何工作的。

开始

开始使用这些新类非常容易, 只需导入 java.util 包。在示例中,我们将使用日历表的月份。在使用集时我们将使用英语月份名称,在使用映射表时用英语和意大利语的月份名称。

清单 1. 开始定义类

import java.util.*;
public class OrderedAccess {
    
public static void main(String args[]) {
        String months[] 
= new DateFormatSymbols().getMonths();
        String italianMonths[] 
= new DateFormatSymbols(Locale.ITALIAN).getMonths();
    }

}

 

假定您已经知道了英语月份的名称和顺序,对于那些不熟悉意大利语月份名称的人们,它们是:Gennaio、Febbraio、Marzo、 Aprile、Maggio、Giugno、Luglio、Agosto、Settembre、Ottobre、Novembre 和 Dicembre。 由于某些原因 getMonths() 返回的名称不是大写的。

使用新 HashSet

LinkedHashSetHashSet 类的一个子类。因此, 凡是 HashSet 能做的工作, LinkedHashSet 也能做到。 类中没有新方法,能得到的只有 4 个构造函数:

  • LinkedHashSet()
  • LinkedHashSet(Collection c)
  • LinkedHashSet(int initialCapacity)
  • LinkedHashSet(int initialCapacity, float loadFactor)

要向集中添加元素,我们可以为每个元素调用 add() ,或创建一个 Collection 并将它传递到构造函数。因为数组中已经有了元素,所以最简单的机制就是使用 Arrays.asList() ,它会返回一个包装成 List 中的数组,同时维持原始数组的顺序。通过将 list 传递到构造函数,可以很轻松地将相同的 list 添加到 LinkedHashSet 和简单的 HashSet 中。

清单 2. 填充集 


  List list  =  Arrays.asList(months);
  Set orderedSet 
=   new  LinkedHashSet(list);
  Set unorderedSet 
=   new  HashSet(list);

 

在填满了集之后,我们可以检查它们的元素,看看链接的集是否按插入顺序维护其元素, 然后与标准散列集比较结果。可以通过集的 iterator() 手工迭代每个集的各个元素,或只调用 toString() 方法(隐式地),实际上就是它为我们做了那些工作。

清单 3. 显示集结果


  System.out.println("Ordered:   " + orderedSet);
  System.
out.println("Unordered: " + unorderedSet);

 

已排序的集显示如下:

清单 4. 显示已排序的集结果

Ordered:   [1月, 2月, 3月, 4月, 5月, 6月, 7月, 8月, 9月, 10月, 11月, 12月, ]

 

而未排序的集显示如下:

清单 5. 显示未排序的集结果


Unordered: [10月, 7月, 9月, 1月, 3月, 4月, 8月, 11月, 5月, 2月, 12月, 6月, ]

 


使用新 HashMap

LinkedHashMap 的工作原理与 LinkedHashSet 本质相同, 但对每个元素都需要一个键和值。它也是 HashMap 的一个子类,但现在有 5 个构造函数:

  • LinkedHashMap()
  • LinkedHashMap(int initialCapacity)
  • LinkedHashMap(int initialCapacity, float loadFactor)
  • LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)
  • LinkedHashMap(Map)

添加的构造函数处理存取顺序选项。把 false 存取顺序作为缺省值, 要获得一个存取顺序的列表,您必须传入一个 true ;也即映射表的头部是使用时间距今最远的映射表条目。

常规 HashMap 的子类 LinkedHashMap 的一个优点是迭代次数不受映射表容量的影响。使用 LinkedHashMap 时,选择大容量对迭代遍历次数没有任何影响, 但使用常规 HashMap 时要影响到性能。

向映射表添加元素要比向集添加元素更加棘手一些,只是因为我们不得不单独 put() 每一对元素。下面的代码并没有什么特别之处, 只是我们要循环遍历月份名称,而不是只向构造函数传递一个 Map

清单 6. 填充映射表

  Map orderedMap = new LinkedHashMap();
  Map unorderedMap = 
new HashMap();
  
for (int i=0, n=months.length; i < n; i++) {
      orderedMap.put(months[i], italianMonths[i]);
      unorderedMap.put(months[i], italianMonths[i]);
  }

 

幸运的是,与集相同,可以只调用映射表的 toString() 方法来获取按插入顺序排列的映射表条目。这种调用将以 key=value 的形式返回每个键值对。

清单 7. 显示映射表结果

  System.out.println("Ordered:   " + orderedMap);
  System.
out.println("Unordered: " + unorderedMap);

 

已排序的映射表显示如下:

清单 8. 显示已排序的映射表结果

Ordered:   {1月=gennaio, 2月=febbraio, 3月=marzo, 4月=aprile, 5月=maggio, 6月=giugno, 7月=luglio, 8月=agosto, 9月=settembre, 10月=ottobre, 11月=novembre, 12月=dicembre, =}

 

而未排序的映射表显示如下:

清单 9. 显示未排序的映射表结果

Unordered: {10月=ottobre, 7月=luglio, 9月=settembre, 1月=gennaio, 3月=marzo, 4月=aprile, 8月=agosto, 11月=novembre, 5月=maggio, 2月=febbraio, 6月=giugno, 12月=dicembre, =}

 

要自己核对,您可以迭代。所有的迭代器都知道插入顺序, 所以在接收到值迭代器 ( values() ) 时,可以按插入顺序遍历那些值,如下所示:

清单 10. 迭代值

  Collection values = orderedMap.values();
  
for (Iterator i = values.iterator(); i.hasNext(); 
      System.
out.println(i.next()));

 

显示如下:

清单 11. 显示已排序的值

gennaio
febbraio
marzo
aprile
maggio
giugno
luglio
agosto
settembre
ottobre
novembre
dicembre

按存取顺序访问

我们将要讨论的新类的最后一个方面是 LinkedHashMap 的存取顺序选项。 将 true 传递到 LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) 构造函数使您能够保持映射表的链表的存取顺序,从使用时间距今最远的到最近使用的。换句话说,新项目被添加到尾部,映射表查找操作将项目移到链表的尾部。最后一点十分重要。因为映射表的典型的读存取操作会改变顺序,如果多个线程可以从映射表读取,就应该同步存取操作。

为了演示,我们可以看几个月份并按新顺序打印:

清单 12. 按存取顺序访问元素

  Map accessorderedMap = 
      
new LinkedHashMap(20, .80f, true);
  
for (int i=0, n=months.length; i < n; i++) {
      accessorderedMap.put(months[i], italianMonths[i]);
  }

  accessorderedMap.
get("6月");
  accessorderedMap.
get("4月");
  accessorderedMap.
get("2月");
  System.
out.println(accessorderedMap);

 

因为我们对 3 个月份进行了存取操作,这 3 个月份将被移到列表的尾部,二月在最后,四月在六月的后面:

清单 13. 显示存取顺序结果

{1月=gennaio, 3月=marzo, 5月=maggio, 7月=luglio, 8月=agosto, 9月=settembre, 10月=ottobre, 11月=novembre, 12月=dicembre, =, 6月=giugno, 4月=aprile, 2月=febbraio}

 

注意:您会注意到链接的映射表中的额外元素。 = 是一个额外条目, 它是由 getMethods() 方法返回的。由于某些原因, getMonths() 返回 13 个月,而不是 12 个月,其中最后一个月没有名称。 出于相同的原因,在链接的集示例中有一个逗号,而没有最后一个月份。

还有一点需要注意: LinkedHashMap 的 1.4 beta 2 版本添加了一个受保护的 removeEldestEntry() 方法。如果需要移去最老的节点,子类可以使该方法返回 true,比如为确保映射表不会获得多于 n个元素。


完整的示例

下面是完整的示例源代码。

清单 14. 完整的示例

import java.util.*;
import java.text.
*;
public class OrderedAccess {
    
public static void main(String args[]) {
        String months[] 
= new DateFormatSymbols().getMonths();
        String italianMonths[] 
= new DateFormatSymbols(Locale.ITALIAN).getMonths();
        List list 
= Arrays.asList(months);
        Set orderedSet 
= new LinkedHashSet(list);
        Set unorderedSet 
= new HashSet(list);
        System.
out.println("Ordered:   " + orderedSet);
        System.
out.println("Unordered: " + unorderedSet);
        Map orderedMap 
= new LinkedHashMap();
        Map unorderedMap 
= new HashMap();
        
for (int i=0, n=months.length; i < n; i++{
            orderedMap.put(months[i], italianMonths[i]);
            unorderedMap.put(months[i], italianMonths[i]);
        }

        System.
out.println("Ordered:   " + orderedMap);
        System.
out.println("Unordered: " + unorderedMap);
        Collection values 
= orderedMap.values();
        
for (Iterator i = values.iterator(); i.hasNext(); 
            System.
out.println(i.next()));
        Map accessorderedMap 
= new LinkedHashMap(20, .80f, true);
        
for (int i=0, n=months.length; i < n; i++{
            accessorderedMap.put(months[i], italianMonths[i]);
        }

        accessorderedMap.get("6月");
        accessorderedMap.
get("4月");
        accessorderedMap.
get("2月");

        System.
out.println(accessorderedMap);
   }

}

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值