Merlin 的魔力: 维护插入顺序
J2SE 1.4 为 Java Collections Framework 引入了两个新实现,
LinkedHashSet 和
LinkedHashMap
。添加这两个新实现的好处是散列集合现在可以维护贯穿其元素的两条路径。除标准的散列关系之外,现在还有一个可遍历整个集合的链表。正常情况下,这个新的
第二路径会遵循插入顺序,这意味着集合的迭代器将按照元素的插入顺序返回元素(而不按它们的散列码将其组合成一个集合的顺序),但 LinkedHashMap 支持第二种排序选项:按存取顺序而非插入顺序维护链表。
我们来看一下这些新类是如何工作的。
开始
开始使用这些新类非常容易。 只需导入
java.util 包并找到一组要使用的项目。在我们的示例中,我们将使用日历表的月份。在使用集时我们将使用英语月份名称,在使用映射表时用英语和意大利语的月份名称。
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
LinkedHashSet 是基本
HashSet 类的一个子类。因此, 凡是
HashSet 能做的工作,
LinkedHashSet 也能做到。 类中没有新方法。您能得到的只有 4 个构造函数:
-
LinkedHashSet()
-
LinkedHashSet(Collection c)
-
LinkedHashSet(int initialCapacity)
-
LinkedHashSet(int initialCapacity, float loadFactor)
要向集中添加元素,我们可以为每个元素调用
add() ,或创建一个
Collection 并将它传递到构造函数。因为数组中已经有了元素,所以最简单的机制就是使用
Arrays.asList() ,它会返回一个包装成
List 中的数组,同时维持原始数组的顺序。通过将 list 传递到构造函数,我们可以很轻松地将相同的 list 添加到
LinkedHashSet 和简单的
HashSet 中。
List list = Arrays.asList(months); Set orderedSet = new LinkedHashSet(list); Set unorderedSet = new HashSet(list); |
在填满了集之后,我们可以检查它们的元素,看看链接的集是否按插入顺序维护其元素, 然后与标准散列集比较结果。您可以通过集的
iterator() 手工迭代每个集的各个元素,或只调用
toString() 方法(隐式地),实际上就是它为我们做了那些工作。
System.out.println("Ordered: " + orderedSet); System.out.println("Unordered: " + unorderedSet); |
Ordered: [January, February, March, April, May, June, July, August, September, October, November, December, ] |
而未排序的集显示如下:
Unordered: [March, April, November, October, January, July, September, February, December, May, June, August, ] |
使用新 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 。
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 的形式返回每个键值对。
System.out.println("Ordered: " + orderedMap); System.out.println("Unordered: " + unorderedMap); |
已排序的映射表显示如下:
Ordered: {January=gennaio, February=febbraio, March=marzo, April=aprile, May=maggio, June=giugno, July=luglio, August=agosto, September=settembre, October=ottobre, November=novembre, December=dicembre, =} |
而未排序的映射表显示如下:
Unordered: {August=agosto, July=luglio, November=novembre, June=giugno, October=ottobre, April=aprile, May=maggio, March=marzo, January=gennaio, February=febbraio, =, December=dicembre, September=settembre} |
要自己核对,您可以迭代。所有的迭代器都知道插入顺序, 所以在接收到值迭代器 (
values() ) 时,您可以按插入顺序遍历那些值,如下所示:
Collection values = orderedMap.values(); for (Iterator i = values.iterator(); i.hasNext(); System.out.println(i.next())); |
显示如下:
gennaio febbraio marzo aprile maggio giugno luglio agosto settembre ottobre novembre dicembre |
按存取顺序访问
我们将要讨论的新类的最后一个方面是
LinkedHashMap 的存取顺序选项。 将
true 传递到
LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) 构造函数使您能够保持映射表的链表的存取顺序,从使用时间距今最远的到最近使用的。换句话说,新项目被添加到尾部,映射表查找操作将项目移到链表的尾部。最后一点十分重要。因为映射表的典型的读存取操作会改变顺序,如果多个线程可以从映射表读取,就应该同步存取操作。
为了演示,我们可以看几个月份并按新顺序打印:
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("June"); accessorderedMap.get("April"); accessorderedMap.get("February"); System.out.println(accessorderedMap); |
因为我们对 3 个月份进行了存取操作,这 3 个月份将被移到列表的尾部,二月在最后,四月在六月的前面:
{January=gennaio, March=marzo, May=maggio, July=luglio, August=agosto, September=settembre, October=ottobre, November=novembre, December=dicembre, =, June=giugno, April=aprile, February=febbraio} |
注意:您会注意到链接的映射表中的额外元素。
= 是一个额外条目, 它是由
getMethods() 方法返回的。由于某些原因,
getMonths() 返回 13 个月,而不是 12 个月,其中最后一个月没有名称。 出于相同的原因,在链接的集示例中有一个逗号,而没有最后一个月份。
还有一点需要注意:
LinkedHashMap 的 1.4 beta 2 版本添加了一个受保护的
removeEldestEntry() 方法。如果需要移去最老的节点,子类可以使该方法返回 true,比如为确保映射表不会获得多于
n个元素。
完整的示例
下面是完整的示例源代码。
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("June"); accessorderedMap.get("April"); accessorderedMap.get("February"); System.out.println(accessorderedMap); } } |
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/374079/viewspace-130099/
,如需转载,请注明出处,否则将追究法律责任。