> 文章列表 > Java核心技术 卷1-总结-14

Java核心技术 卷1-总结-14

Java核心技术 卷1-总结-14

Java核心技术 卷1-总结-14

  • 映射
    • 更新映射项
    • 弱散列映射
    • 链接散列集与映射
    • 枚举集与映射
  • 视图与包装器
    • 轻量级集合包装器

映射

更新映射项

处理映射时的一个难点就是更新映射项。正常情况下,可以得到与一个键关联的原值,完成更新,再放回更新后的值。不过,必须考虑一个特殊情况,即键第一次出现。下面这个例子,使用一个映射统计一个单词在文件中出现的频度。看到一个单词(word)时,将计数器加1,如下所示:

counts.put(word, counts.get(word) + 1);

这是可以的,不过有一种情况除外:就是第一次看到word时。在这种情况下,get会返回 null,因此会出现一个 NullPointerException异常。作为一个简单的补救,可以使用getOrDefault方法

counts.put(word, counts.getOrDefault(word, 0)1);

另一种方法是首先调用putlfAbsent方法。只有当键原先存在时才会放入一个值。

counts.putIfAbsent(word, 0);
counts.put(word, counts.get(word) + 1); // Now we know that get will succeed

merge方法可以简化这个常见的操作。如果键原先不存在,下面的调用:

counts.merge(word,1, Integer::sum);

将把word与1关联,否则使用Integer:sum函数组合原值和1(也就是将原值与1求和)。

弱散列映射

假定对某个键的最后一次引用已经消亡,不再有任何途径引用这个值的对象了。但是,由于在程序中的任何部分没有再出现这个键,所以,这个键/值对无法从映射中删除。垃圾回收器也不能够删除它。WeakHashMap类可以解决这个问题。

垃圾回收器跟踪活动的对象,只要映射对象是活动的,其中的所有桶也是活动的,它们不能被回收。因此,需要由程序负责从长期存活的映射表中删除那些无用的值。或者使用WeakHashMap完成这件事情。当对键的唯一引用来自散列条目时,这一数据结构将与垃圾回收器协同工作一起删除键/值对。

这种机制的内部运行情况如下:WeakHashMap 使用弱引用(weak references)保存键。弱引用对象将引用保存到另外一个对象中,在这里,就是散列键。对于这种类型的对象,垃圾回收器用一种特有的方式进行处理。通常,如果垃圾回收器发现某个特定的对象已经没有他人引用了,就将其回收。然而,如果某个对象只能由弱引用引用,垃圾回收器仍然回收它,但要将引用这个对象的弱引用放入队列中。WeakHashMap将周期性地检查队列,以便找出新添加的弱引用。一个弱引用进入队列意味着这个键不再被他人使用,并且已经被收集起来。于是,WeakHashMap将删除对应的条目。

链接散列集与映射

LinkedHashSetLinkedHashMap类用来记住插入元素项的顺序。这样就可以避免在散列表中的项从表面上看是随机排列的。当条目插入到表中时,就会并入到双向链表中。

例如,在程序清单9-6中包含下列映射表插入的处理:
Java核心技术 卷1-总结-14

例如:

Map<String,Employee>staff = new LinkedHashMap<>();
staff.put("144-25-5464", new Employee("Amy Lee"));
staff.put("567-24-2546", new Employee("Harry Hacker"));
staff.put("157-62-7935", new Employee("Gary Cooper"));
staff.put("456-62-5527", new Employee("Francesca Cruz"));

然后,staff.keySet().iterator()以下面的次序枚举键:

144-25-5464 
567-24-2546 
157-62-7935 
456-62-5527

并且staff.values().iterator()以下列顺序枚举这些值:

Amy Lee 
Harry Hacker
Gary Cooper 
Francesca Cruz

链接散列映射将用访问顺序,而不是插入顺序,对映射条目进行迭代。每次调用get或put,受到影响的条目将从当前的位置删除,并放到条目链表的尾部(只有条目在链表中的位置会受影响,而散列表中的桶不会受影响。一个条目总位于与键散列码对应的桶中)。要项构造这样一个的散列映射表,需要调用:

LinkedHashMap<K, V>(initialCapacity, loadFactor, true);

访问顺序对于实现高速缓存的"最近最少使用"原则十分重要。 例如,可能希望将访问频率高的元素放在内存中,而访问频率低的元素则从数据库中读取。当在表中找不到元素项且表又已经满时,可以将迭代器加入到表中,并将枚举的前几个元素删除掉。这些是近期最少使用的几个元素。

可以让这一过程自动化。即构造一个LinkedHashMap的子类,然后覆盖下面这个方法:

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

每当方法返回true 时,就添加一个新条目,从而导致删除 eldest条目。例如,下面的高速缓存可以存放100个元素:

Map<K,V> cache = new LinkedHashMap<>(128,0.75F,true) {protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {return size()>100;}
}();

另外,还可以对eldest条目进行评估,以此决定是否应该将它删除。例如,可以检查与这个条目一起存在的时间戳。

枚举集与映射

EnumSet是一个枚举类型元素集的高效实现。由于枚举类型只有有限个实例,所以EnumSet内部用位序列实现。如果对应的值在集中,则相应的位被置为1。
EnumSet类没有公共的构造器。可以使用静态工厂方法构造这个集:

enum Weekday [ MONDAY,TUESDAY,wEDNESDAY,THURSDAY,FRIDAY,SATURDAY,SUNDAY };
EnumSet<Weekday> always = EnumSet.allOf(Weekday.class);
EnumSet<Weekday> never = EnumSet.noneOf(Weekday.class);
EnumSet<Weekday> workday = EnumSet.range(Weekday.MONDAY,Weekday.FRIDAY);EnumSet<Weekday> 
mwf = EnumSet.of(Weekday.M(NDAY, Weekday.WEDNESDAY, Weekday.FRIDAY);

可以使用Set接口的常用方法来修改EnumSet。
EnumMap是一个键类型为枚举类型的映射。它可以直接且高效地用一个值数组实现。在使用时,需要在构造器中指定键类型:

EnumMap<Weekday, Employee> personInCharge = new EnumMap<>(Weekday.class);

视图与包装器

通过使用视图(views)可以获得其他的实现了Collection接口和Map接口的对象。 映射类的keySet方法就是一个这样的示例。初看起来,好像这个方法创建了一个新集,并将映射中的所有键都填进去,然后返回这个集。但是,情况并非如此。取而代之的是:keySet方法返回一个实现Set 接口的类对象,这个类的方法对原映射进行操作。这种集合称为视图。视图技术在集框架中有许多非常有用的应用。

轻量级集合包装器

Arrays类的静态方法asList将返回一个包装了普通Java数组的List包装器。这个方法可以将数组传递给一个期望得到列表或集合参数的方法。例如:

Card[] cardDeck = new Card[52];
List<Card> cardList = Arrays.asList(cardDeck);

返回的对象不是ArrayList。它是一个视图对象,带有访问底层数组的getset方法。改变数组大小的所有方法(例如,与迭代器相关的add和remove方法)都会抛出一个Unsupported OperationException异常。
asList方法可以接收可变数目的参数。例如:

List<String> names = Arrays.asList("Amy","Bob","Carl");

这个方法调用

Collections.nCopies(n,anObject)

将返回一个实现了List接口的不可修改的对象,并给人一种包含n个元素,每个元素都像是一个anObject的错觉。
例如,下面的调用将创建一个包含100个字符串的List,每个串都被设置为“DEFAULT”:

List<String> settings = Collections.nCopies(100, "DEFAULT");

存储代价很小。这是视图技术的一种巧妙应用。
注意:Collections类包含很多实用方法,这些方法的参数和返回值都是集合。不要与Collection接口混淆。
如果调用下列方法

Collections.singleton(anObject)

则将返回一个视图对象。这个对象实现了Set接口(与产生List的ncopies方法不同)。返回的对象实现了一个不可修改的单元素集,而不需要付出建立数据结构的开销。singletonList 方法与singletonMap方法类似。

类似地,对于集合框架中的每一个接口,还有一些方法可以生成空集、列表、映射,等等。特别是,集的类型可以推导得出:

Set<String> deepThoughts = Collections.emptySet();

子范围
可以为很多集合建立子范围(subrange)视图。例如,假设有一个列表staff,想从中取出第10个~第19个元素。可以使用subList方法来获得一个列表的子范围视图。
List group2=staff.subList(10,20);