Java集合框架——实现

实现

Java平台包含了许多数据集接口的实现,主要分为通用实现和专业实现两

Java平台提供的通用实现如下表所示:

Interfaces

Hash table

Resizable array

Tree

Linked list

Hash table + Linked list

Set

HashSet

 

TreeSet

 

LinkedHashSet

List

 

ArrayList

 

LinkedList

 

Queue

     

Map

HashMap

 

TreeMap

 

LinkedHashMap

如上表所示,Java平台对SetListMap提供了多种通用实现。其中, HashSetArrayListHashMap被许多程序所广泛使用。需要注意的是,SortedSetSortedMap的接口实现并没有在表中列出来,Java平台中都各自提供了一个实现,分别为Set行的TreeSet和Map行的TreeMap。另外,Queue接口也有两个通用实现:LinkedListPriorityQueue,其中LinkedList提供先进先出访问顺序,而PriorityQueue提供按优先级访问顺序。

每种通用实现都实现了接口中规定的所有操作,都允许null键、值及元素,都不是线程安全的。iterator 方法返回的迭代器是快速失败的:在迭代器创建之后,如果对集合进行修改,除非通过迭代器自身的移除方法,其他任何时间任何方式的修改,迭代器都将抛出 ConcurrentModificationException。因此,面对并发的修改,迭代器很快就会完全失败,而不冒将来不确定的时间任意发生不确定行为的风险。 都是可序列化的和并都提供了公有的clone函数。

下面章节将简单的介绍各种实现。各种实现的性能主要和其实现的内部数据结构相关,这里只做简单介绍,并不深入讨论,如果你想了解它们的性能的更多方面的知识,建议读一些数据结构相关的书籍。

Set实现

Set实现可分为通用Set实现和专有Set实现两类。

通用Set实现

Java提供了三个通用 Set 实现—— HashSetTreeSetLinkedHashSet

HashSet采用散列函数对元素进行排序,是专门为快速查询而设计的。存入HashSet的对象必须定义hashCode()。

值得注意的是:由于HashSet在内存中数据空间为线性分配。因此,如果初始的容量过大则会浪费大量的空间和时间;如果初始容量过小将会因为增容而不得不复制已有的数据集到新缓冲区中,从而浪费时间。如果你不指定初始容量,默认为16,当容量不足时以成倍的形式增加容量。下列代码创建了一个初始容量为64的HashSet:

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

TreeSet采用红黑树的数据结构进行排序元素,能保证元素的次序,使用它可以从Set中提取有序的序列。

HashSet比TreeSet提供更快的访问速度,但无法保证访问顺序。用HashSet还是TreeSet取决于你是否需要顺序访问。

LinkedHashSet以链表的形式实现哈希表,它能提按照插入顺序迭代访问。

专有Set实现

专有Set实现有两种——EnumSetCopyOnWriteArraySet

EnumSet是为枚举类的Set提供的高性能Set。其成员在内部表示为位向量,具有很好的空间和时间性能。其迭代器能按其自然顺序(声明枚举常量的顺序)遍历这些元素。

EnumSet类提供了一个静态的函数range,用以简单的创建其实例:

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

此外,也可通过of函数来创建包含指定枚举的EnumSet:

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

CopyOnWriteArraySet 在内部通过copy-on-write数组实现。对Set的add, set, 及remove操作都会创建数组的副本。迭代操作甚至可以和插入删除操作同时进行。和大多数Set实现不同,add, remove和 contains操作需要的时间和set的大小成比例。该实现仅适用于那些很少修改,而需要经常进行迭代操作的地方。

List实现

通用List实现

通用List实现有两个——ArrayListLinkedList

大多数情况下,我们通常使用ArrayList。它能提供常数级按位置访问时间,增加元素时无需分配空间,并且可以使用System.arraycopy实现批量复制操作。可以将ArrayList看作是Vector的线程不安全版本。

如果你需要频繁在List的头中插入元素或通过迭代器删除元素,你可能需要考虑使用LinkedList,它能提供常数级操作时间。而使用ArrayList则有较大的开销。如果按位置访问LinkedList,则需要从头开始遍历LinkedList,不像ArrayList那样能随机访问。

专有List实现

专有List实现有一个——CopyOnWriteArrayList。和CopyOnWriteArraySet类似,它在内部也是通过copy-on-write数组实现,其功能和适用范围也非常相似,这里就不再累叙。

Map实现

通用Map 实现

通用Map实现有三个——HashMapTreeMapLinkedHashMap

Set实现类似,如果你需要进行SortedMap操作或按key大小顺序迭代操作,请使用TreeMap。如果你需要最快的查询速度而不关心迭代顺序,请使用HashMap。如果你需要接近HashMap的性能,同时需要按照插入顺序迭代,请使用LinkedHashMap。

专有Map实现

通用Map实现有三个——EnumMap, WeakHashMapIdentityHashMap

EnumMap在内部以数组方式实现,能为枚举提供高性能和线程安全的查询操作。如果想实现枚举类型的Map,EnumMap无疑是性能最佳的选择。

WeakHashMap也是Map接口的一种实现,它的Key值存储的只是对象的弱引用。这意味着:当某个键不再正常使用时,将自动移除其条目。更精确地说,对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的丢弃,这就使该键成为可终止的,被终止,然后被回收。丢弃某个键时,其条目从映射中有效地移除,因此,因此该类的行为与其他的 Map 实现有所不同。

IdentityHashMap通过哈希表实现 Map 接口,比较键(和值)时使用引用相等性代替对象相等性。在 IdentityHashMap 中,当且仅当 (k1==k2) 时,才认为两个键 k1 和 k2 相等(在正常 Map 实现(如 HashMap)中,当且仅当满足下列条件时才认为两个键 k1 和 k2 相等:(k1==null ? k2==null : e1.equals(e2)))。

此类不是 通用 Map 实现!此类实现 Map 接口时,它有意违反 Map 的常规协定,该协定在比较对象时强制使用 equals 方法。此类设计仅用于其中需要引用相等性语义的罕见情况。

此类的典型用法是拓扑保留对象图形转换,如序列化或深层复制。要执行这样的转换,程序必须维护用于跟踪所有已处理对象引用的"节点表"。节点表一定不等于不同对象,即使它们偶然相等也如此。此类的另一种典型用法是维护代理对象。例如,调试设施可能希望为正在调试程序中的每个对象维护代理对象。

Queue实现

通用Queue实现

如前所述,LinkedList 也实现了Queue 接口,提供了FIFO访问顺序及相关操作。

PriorityQueue 是一个基于优先级堆的极大优先级队列。此队列按照在构造时所指定的顺序对元素排序,既可以根据元素的自然顺序 来指定排序(参阅 Comparable),也可以根据 Comparator 来指定,这取决于使用哪种构造方法。优先级队列不允许 null 元素。依靠自然排序的优先级队列还不允许插入不可比较的对象(这样做可能导致 ClassCastException)。

此类及其迭代器实现了 CollectionIterator 接口的所有可选 方法。方法 iterator() 中提供的迭代器并不 保证以任意特定的顺序遍历优先级队列中的元素。如果需要按顺序遍历,请考虑使用 Arrays.sort(pq.toArray())。

封装实现

封装实现是通过对数据集实现进行外部封装,使其能实现特定的功能,它是典型的装饰器模式。

线程安全封装

线程安全封装可以将普通的非线程安全的数据集封装为线程安全的数据集。常用方法如下:

public static <T> Collection<T>
    synchronizedCollection(Collection<T> c);
public static <T> Set<T>
    synchronizedSet(Set<T> s);
public static <T> List<T>
    synchronizedList(List<T> list);
public static <K,V> Map<K,V>
    synchronizedMap(Map<K,V> m);
public static <T> SortedSet<T>
    synchronizedSortedSet(SortedSet<T> s);
public static <K,V> SortedMap<K,V>
    synchronizedSortedMap(SortedMap<K,V> m);

这些函数使用非常简单,如:

        List<Type> list = Collections.synchronizedList(new ArrayList<Type>());

就创建了一个线程安全的List(功能和Vector类似)。

不可更改封装

通过不可更改封装后的数据集将具有只读性,调用任何修改数据集的方法都将导致UnsupportedOperationException 异常(类似于C++的const引用,只不过是运行期出错)。

封装方法如下:

public static <T> Collection<T>
    unmodifiableCollection(Collection<? extends T> c);
public static <T> Set<T>
    unmodifiableSet(Set<? extends T> s);
public static <T> List<T>
    unmodifiableList(List<? extends T> list);
public static <K,V> Map<K, V>
    unmodifiableMap(Map<? extends K, ? extends V> m);
public static <T> SortedSet<T>
    unmodifiableSortedSet(SortedSet<? extends T> s);
public static <K,V> SortedMap<K, V>
    unmodifiableSortedMap(SortedMap<K, ? extends V> m);

 

实现小结

Java平台对各数据集接口提供了多种数据集实现。通用实现提供了接口定义的大多数操作,能广泛引用于各种应用程序,其操作,访问性能取决于各具体的数据结构;专有实现针对一些特定需求提供了高性能的实现。开发者可以根据具体需求灵活选用。

原文地址:https://www.cnblogs.com/TianFang/p/929646.html