首页 > 代码库 > 集合2

集合2

Map接口的基本操作

Map接口的基本操作(put(),get(),containsKey(),containsValue(),size(),isEmpty())非常类似于Hashtable中相应的操作。下面程序生成一个参数列表中出现的单词的频率表。在频率表中,将每一个单词映射到它在参数列表中的次数。编写统计命令行参数字符串出现次数的程序。代码如下:

import java.util.*;
public class freq {
    public static void main(String []args){
        Map<String, Integer> m=new HashMap<String, Integer>();
        //从命令行初始化频率表
        for (String a :args){
            Integer freq=m.get(a); //获得键为a的元素的值
            //设置a出现的次数,保存在Map对象中
            m.put(a, (freq ==null)? 1:freq+1);
        }
        System.out.println(m.size()+"个不同的单词:");
        System.out.println(m);
        }
    }

上述程序需要注意的是put语句的第二个参数。该参数是一个条件表达式,用来设置一个单词出现的频率。如果这个单词从来没有出现过,则设置为1,如果已经出现过,则设置为当前值增1.

编译并运行此程序

java Freq if it is to be it is up to me to delegate

程序的输出结果如下:

8个不同的单词:
{to=3,delegate=1,be=1,it=2,up=1,if=1,me=1,is=2}

假如想按字母顺序看此频度表,那么就将Map的实现类型由HashMap改为TreeMap。将上述程序中的HashMap改为TreeMap,重新编译并以同样的命令行参数运行,输出结果如下:

8个不同的单词:
{be=1,delegate=1,if=1,is=2,it=2,me=1,to=3,up=1}

同样,若想使程序按单词在命令行参数中的顺序输出频度,只需将Map实现类型改为LinkedHashMap。那么上述程序的输出结果为:

8个不同的单词:
{if=1,it=2,is=2,to=3,be=1,up=1,me=1,delegate=1}

与Set和List接口类似,Map也加强了对equals()和hashCode()方法的要求,这样两个Map对象可以进行逻辑相等比较,而不必考虑它们的实现类型。如果两个Map实例代表相同的键值映射,那么它们就是相等的。

按惯例,所有通过的Maap实现都提供接收一个Map对象作为参数的构造方法,并初始化新的Map使其包含指定Map中所有键-值对。这个标准Map转换构造方法完全与标准Collection的构造方法类似:允许调用者创建一个希望实现的类型的Map,初始化包含另一个Map的所有映射,而不必考虑另一个映射的实现类型。例如,假设一个名为m的Map。下面这行代码创建一个新的HashMap,初始化包含m中的所有相同的键-值映射。

Map<K,V>copy =new HashMap<K,V>(m);

Map接口的批量操作

Map接口的批量操作主要有clear()方法,putAll()方法。其中clear()方法从Map中删除所有的映射,putAll()方法是Map中类似于Collection接口的addAll()方法。

putAll除了将一个Map填充进另一个Map外,它还有第二种更为巧妙的用法。假设一个Map被用于带博鳌一个属性-值对的集合:putAll()方法与Map转化构造方法结合使用,提供另一种方法实现使用默认值的属性映射。下面使用这种技术的一个静态工厂方法的示例。

static<K,V>Map<K,V>newAttributeMap(Map<K,V>defaults,Map<K,V>overrides){
Map<K,V>result=new HashMap<K,V>(defaults);//创建一个新的Map对象
rusults.putAll(oerrides);
return result;
}

实现

实现时用于存储集合的数据对象实现了前面所说的一些接口。通俗来讲,实现是指这样一些类:这些类实现了各种集合框架的接口,在实际开发程序时,集合主要使用这些实现了集合接口的集合类。

实现类型

对于集合框架中集合接口的实现,主要包括如下几种类型。

通过目的的实现:是最经常使用的实现,是为日常使用而实现的。

特殊目的的实现:被设计用于特殊情况,并显示了非标准的执行特征、使用约束或行为。

并发实现:被设计用于高并发性,特别是单线程开销的情况下。这些实现时java.util.concurrent包的一部分。

其他实现:用于与其他特定目的的实现。

java集合框架提供了数个对Set、List和Map接口的通用实现。这些接口中每一个都有一个实现(TreeSet和TreeMap),在Set好人Map行列出。有两个通用的对Queue的实现:LinkedList(它同时也是List的实现)和PriorityQueue。这两个实现提供了不同的语义:Linked是FIFO的,而PriorityQueue根据元素的值排序。

每一个通用的目的的实现都提供了所有其接口中包含的操作:都允许null元素,键,值,且都不是同步的(线程安全的);都能在迭代其间检测非法的并发修改,且迅速失败并清除:都是可序列化的并且都支持公共的clone()方法。

事实上,这些实现都是非同步的,这与以前就版本的实现是不同的:遗留下来的集合Vector和Hashtable是同步的。之所以这样改变,是因为当集合被频繁地使用时,同步是无益的,这样的使用包括单线程使用,制度使用及作为大的数据对象的一部分使用(该大的数据对象有自己的同步)。一般来说,这是一个好的API设计的实践,用户不会为它们不使用的特征而付出额外的开销。进一步来讲,不必要的同步在一定的情况下会导致死锁。

如果需要线程安全的集合,可以使用同步包装的方法,允许任何集合被转换为一个同步的集合。此外,java.util.concurrent包提供BlockingQueue接口的同步实现,该接口扩展了Queue,以及对ConcurrentMap接口的实现,该接口扩展自Map。这些实现比同步实现提供更高的并发性。

作为一个原则,在使用集合时, 应该考虑的是接口,而不是实现。这就是为什么没有程序实例的原因。大多数情况下,对于实现的选择,只影响执行性能。当创建一个即可欧并立刻赋予一个新的集合给相对应的接口类型的变量时,选择实现(或者传递一个集合到一个需要接口类型参数的方法中)。以这种方式,程序并不依赖于一个给定实现当中的任何增加方法,这样,程序可以在任何时候因性能原因或行为细节而改变实现。。

Set接口的实现

Set接口不允许有重复的元素,因此Set接口的实现类要遵循这样的原则。Set实现分为通用实现和特殊实现。

1:通用Set实现

有3个通用的Set实现:HashSet,TreeSet,和LinkedHashSet。HashSet比TreeSet快,但是不提供顺序保证。如果需要使用SortedSet接口中的操作或要求按值的顺序迭代,则使用TreeSet;否则,使用HashSet。大多数情况下使用HashSet。

LinkedHashSet在某种意义上位于HashSet和TreeSet的中间。作为带有链表的哈希表,它提供插入顺序的迭代,运行速度接近于HashSet。LinkedHashSet实现避免了HashSet提供的混乱顺序,同时又没有想TreeSet那样增加高昂的成本。

更需要注意的是,HashSet在条目数量和容量数量上呈线状的迭代。因为初始容量选择太高会浪费时间和空间。另一方面,选择一个过低的容量会在强制增加一个容量时浪费复制数据结构的时间。如果不指定一个初始的容量,则默认是16.之前选择一个素数作为初始容量有一些好处,但是现在不是这样了。HashSet的容量总是以2 的几何级数增长,使用int构造方法指定初始容量。下面的代码,给定一个HashSet分配初始容量为64:

Set<String>s=new HashSet<String >(64);

HashSet类另一个调整的参数,称为“加载因子”。否则,仅接受默认值,默认值总是最好的选择。LinkedHashSet有和HashSet一样的调整因子,但是迭代时间不受容量的影响。TreeSet没有调整因子。

2:特殊目的的Set实现

有两个特殊目的的Set实现:EnumSet和CopyOnWriteArraySet。EnumSet是用于枚举类型的一个高性能的Set实现。一个枚举Set集合中的所有成员必须是相同的枚举类型。EnumSet支持在枚举类型上的范围迭代。例如,给定一个用于一周的天数的枚举声明,可以在周一至周五之前迭代。EnumSet类提供一个静态的工厂方法,代码如下:

for (Day d :EnumSet.range(Day.MONDAY,Day.FRIDAY))
System.out.println(d);

EnumSet还提供一个丰富的类型安全的对传统的位标志的替代。

EnumSet.of(Style.BOLD,Style.ITALIC)

CopyOnWriteArraySet是一个由拷贝数组支持的Set实现。所有的可变操作(如add()、set()和remove())通过对原数组的一次新的拷贝元素来实现:这个实现只适用于很少修改但经常迭代的Set集合。它适用于维护事件处理列表。

List接口的实现

对List接口的实现也是分为两类:通过目的的实现和特殊目的的实现。

1:通过目的的List实现

有两个通用目的的List实现:ArrayList和LinkedList。大多数时候,程序员或许都使用ArrayList。它不必为List中的每一个元素分配节点对象,并且它要在同一时刻移动多个元素时,可以使用System.arraycopy().将ArrayList当做不同步的向量Vector。

如果频繁地添加元素到List的开始位置或迭代List以从List的内部删除元素,应该考虑使用LinkedList。实际应用中,如果想使用LinkedList,在作出决定之前,先用LinkedList和ArrayList进行应用程序的性能测试:ArrayList通常快一点。

ArrayList有一个调整的参数----初始容量,它代表ArrayList在增长之前可以持有的元素的数量。LinkedList没有调整参数,有7个可选择的操作,其中一个是clone()。其他6个是addFirst(),getFirst(),removeFirst(),addLast(),getLast(),和removeLast()。LinkedList还实现了Queue接口。

2:特殊目的的List实现

CopyOnWriteArrayList是一个由拷贝数组支持的List实现。其本质上与CopyOnWriteArrayList相似。甚至在迭代其间,同步也是不是必须的,而且迭代保证永远不抛出ConcurrentM欧弟覅cationException。这个实现很适用于维护事件处理列表。在时间处理列表中很少发生改变,而遍历很频繁并且很耗时。

Map接口的实现

对Map接口的实现可分为三类:通用目的,特殊目的和并发实现。

1:通用目的的Map实现

3个通用目的的Map实现分别是HashMap,TreeMap和LinkedHashMap。

如果需要SortedMap操作,或者按键顺序的视图迭代,使用TreeMap。

如果想要最大的速度而不关心迭代的顺序,使用HashMap。

如果想要接近HashSet性能和按插入顺序迭代,使用LinkedHashMap。

在这方面,Map情形与Set类似同样在“Set实现”中的任何事情都可应用有Map实现。

LinkedHashMap提供两个LinkedHashSet不具备的性能。当创建一个LinkedHashMap时,可以基于键的访问顺序来排序,而不是基于插入顺序。换句话来说,仅查找与一个键关联的值引起该键到Map的结尾,另外,LinkedHashMap提供removeEldestEntry()方法,该方法可以被覆盖以强加一个策略,该策略用于当新的映射被添加到Map中时,自动地删除过时的映射。这使得实现一个自定义缓冲非常容易。

例如下面的代码覆盖将允许Map增长到90条目的大小,然后每次一个新的条目被加入时,Map将删除最老的那个条目,维护90个条目的稳定状态。

private static final int MAX_ENTRIES=90;

protected boolean removeEldestEntry(Map.Entry eldest){
return size() >MAX_ENTRIES;
}

2:特殊目的的Map实现

有3个特殊目的的Map实现:EnumMap,WeakHashMap,IdentityHashMap.

3:并发的Map实现

java.util.conrrent包中含有ConcurrentMap接口,它扩展自Map,带有putIfAbsent(),remove(),replace()方法,以及该接口ConcurrentHashMap实现。

集合2