java学习脚印:集合(Collection)之实现类

java学习脚印:集合(Collection)之实现类

写在前面

    上接《java学习脚印:集合(Collection)之接口》。集合框架是很庞大的,面面俱到只会使问题变得很复杂,所以首先还是宏观上把握,把重点放在形成一个一般化的认识体系上。如果遇到具体的问题,再去查询API文档和相关话题的专业文献吧。


1.总览集合框架中类

1.1 类的关系图


java集合框架中常用的类以及历史遗留的类的关系图如下:





1.2 集合类的分类及特点


  集合框架中为了满足应用需要(思考一下,为什么有这么多种实现?),提供了6个类别的实现类。分别是通常的实现,特殊用途的实现,同步实现,包装器实现,方便性实现和抽象实现。

  • 通常实现(General-purpose implementations):它们包括最常用的集合类,例如HashSet;
  • 特殊用途的实现(Special-purpose implementations),它们用于特殊场合,具有非标准的性能特点和限制性的使用,例如EnumSet和CopyOnWriteArraySet.;
  • 同步实现(Concurrent implementations),它们是java.util.concurrent包的一部分,被用来设计支持同步,例如ConcurrentHashMap;
  • 包装器实现(Wrapper implementations),这些类通常结合常用的集合类,来增加或者限制集合类的功能,例如同步包装实现类synchronizedSet,只读包装实现类unmodifiableSet;
  • 方便性实现(Convenience implementations),可称为Mini版本,通常由静态工厂方法提供,作为一些通常类的高效便捷的替代类,例如singleton Set和emptySet;
  • 抽象实现(Abstract implementations),用来为自定义集合类作准备,通常大多数人不需要了解,例如AbstractSet。


根据不同需求,构建了很多的实现类,我们首先做个整体把握。 图表1给出了常用集合类以及必要的说明。

(翻译整理自:http://docs.oracle.com/javase/tutorial/collections/implementations/index.html)



                                                                                                             图表1  常用集合类


关于这个图中的表格说明如下:

1) SortedSetand the SortedMap接口未列出,但是它们都有一个实现,即TreeSet和TreeMap。

2) Queue也有两种实现LinkedList和PriorityQueue未列出,因为它们语义很不相同,LinkedList采用先进先出(FIFO)方式,而PriorityQueue按元素的值的特征进行相关操作。

3) 每个常用集合类提供了接口中所有的可选操作,并允许null元素,键和值;同时它们都实现了Serializable接口,并支持public的Clone方法。

4) 每个常用集合类都不是同步的,也就说都不是线程安全的。

在迭代过程中,如果检测到非法的同步修改,所有常用集合类的迭代器,会即时告之失败,例如抛出ConcurrentModificationException。迭代器不会冒着在将来某个未定时刻出现随意且不确定性行为的风险,它们尽快通告失败。


     事实上,java早期版本遗留下来的Hashtable和Vector类都是同步的,而现在的这些常用集合类都未采用同步方式,主要是因为集合类是经常使用的,而同步并不是经常需要的,例如单线程、只读操作,以及作为已经使用了同步技术的稍大的数据对象的一个部分时,就不需要再实现同步了。总之,不要为用户不需要的特性付出额外的代价,这才是好的API设计实践方式。此外,不必要的同步在一些情形下会导致死锁。


    如果需要线程安全的集合,可以通过包装器将任意集合类转换为线程安全的。线程安全对于常用集合实现类,但对于遗留的类则是强制要求的。另外,java.util.concurrent包里提供了同步的BlockingQueue接口,该接口从Queue派生而来,以及ConcurrentMap,它从Map接口派生而来。这些实现比简单的同步实现类,提供了更高要求的同步。


作为一个准则,我们应该考虑的是接口而不是实现。

     通常情况下,选择哪种实现只会影响性能,而选择接口则通常会影响功能。

首选的一种代码风格,就是选择一个接口,然后创建该接口的一个具体实现类并把它赋给这个接口类型变量。这种方式下,代码就不依赖于具体实现中增加的方法,在任何时候出于性能考虑或者操作的细节的原因都可以自由切换具体实现。


2.重点把握实现各个接口类的特点

   下面整理一下各个接口的实现类,它们的具体使用方法不在这里一一展开,需要时可查询相关的API文档。


2.1 Set接口的实现类

1)常用实现类

  实现Set的三个通常类是HashSet, TreeSet, 和 LinkedHashSet。


   其中HashSet利用哈希表存贮元素性能最好,但是不保证元素的迭代顺序;

TreeSet利用红黑树存贮元素,元素以值为参考排序,由于每次添加的元素都要放在正确的排序位置上,因此比HashSet明显要慢;


    而LinkedHashSet可以看做是在HashSet的基础上通过双向链表保存了元素顺序的Set。通过使用双向链表将添加到哈希表中的元素按添加顺序链接到一起,就保存了元素添加到哈希表时的顺序。关于这三个类的特点,可参见《java学习脚印:集合(Collection)之接口》中实例代码,以方便理解。

另外关于哈希表,要注意其容量和装填因子对性能的影响,这个特点可以参考数据结构相关书籍来好好理解,这里不再展开。



2)特殊用途实现类

    Set包括两个特殊用途实现类EnumSet and CopyOnWriteArraySet.

EnumSet是元素为枚举类型的集,CopyOnWriteArraySet是由写入时复制技术(copy-on-write array)的数组实现的集。

关于写入时复制技术可参考: cnblogs冷竹 博客《Copy-On-Write技术》


2.2 List接口的实现类


1)常用实现类

    List常用实现了包括可变数组实现的ArrayList和链式实现的LinkedList。这两者的区别就是ArrayList支持随机访问,而LinkedList支持 快速插入和删除。

2)特殊用途实现类

    与set类似,List实现也有一个CopyOnWriteArrayList,他也是采用写时复制技术的数组实现的。


2.3 Queue接口实现的类


1)通常实现类

    通常实现类包括LinkedList和PriorityQueue。其中LinkedList实现了FIFO操作,而PriorityQueue是建立在堆数据结构基础上的优先级队列,它按元素值的特征进行相关操作。


2)同步实现

java.util.concurrent包中包含了一些同步实现,有一个从Queue接口派生的BlockingQueue接口,用于阻塞队列操作。另外还有一个从BlockingQueue派生的TransferQueue接口。


阻塞队列就是当队列中没有元素可取时则等待直到有元素可取为止,当队列满时则阻塞入操作直到有空的位置可用为止。

实现了BlockingQueue接口的类有:

  • LinkedBlockingQueue — 链式FIFO阻塞队列,它支持可选的有界设置
  • ArrayBlockingQueue —  数组实现的有界FIFO阻塞队列
  • PriorityBlockingQueue — 堆实现的无界,优先级阻塞队列
  • DelayQueue — 由堆实现的基于时间调度的队列
  • SynchronousQueue — 利用BlockingQueue接口实现简单会合机制(rendezvous mechanism)的队列
  • LinkedTransferQueue — 链式的无界 TransferQueue 队列
关于它们的使用可查询相关API文档。


2.4 Deque接口实现的类

1)常用实现类

ArrayDeque是该接口的可变数组实现类,LinkedList是链式实现类。

2)同步实现

LinkedBlockingDeque是一个阻塞的双端队列。


2.5 Map接口实现的类


1)常用实现类

   实现Map的三个常用类是HashMap,TreeMap,LinkedHashMap。这与实现Set的三个通常类非常相似,你可以类比Set上的HashSet,TreeSet和LinkedHashSet来学习他们。


2)特殊用途的实现类

   Map接口包括三个特殊用途的类,EnumMap, WeakHashMap 和IdentityHashMap

EnumMap,内部是由数组实现的,使用枚举类型键值的高性能集合类。如果使用枚举类型的键映射到值,你就应该选择EnumMap。

WeakHashMap与通常的Map不同的是,当键不在被其他(除了自身以外)地方引用时,那么这个键值对就会被垃圾收集器回收。

IdentityHashMap 是一种用== ,而不是equals比较键值的映射表。


3)同步实现

java.util.concurrent包中,包含了一个从Map接口继承的ConcurrentMap接口,ConcurrentHashMap类实现了该接口,它是一个有哈希表实现的高性能的支持高并发的集合类,实现了原子性的putIfAbsent, remove,和replace方法。

2.6 包装器实现

   所谓包装器实现,就是结合了一些常用的实现类,然后对其功能增强或者进行限制而来的。

主要包括三种包装器,即同步包装器(Synchronization Wrappers),只读包装器(Unmodifiable Wrappers)和类型安全检查包装器(Checked Interface Wrappers)。

每个接口都对应有一个包装器实现类或者接口。


例如同步包装器有

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 <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);

等等静态方法,这些静态方法返回参数中指定的集合的只读视图,任何对在视图上进行的修改操作都将抛出UnsupportedOperationException,这样就可以阻止修改操作。

类型安全检查包装器有

static <E> Collection<E>     checkedCollection(Collection<E> c, Class<E> type)
static <E> List<E>     checkedList(List<E> list, Class<E> type)
static <K,V> Map<K,V>     checkedMap(Map<K,V> m, Class<K> keyType, Class<V> valueType)

等等静态方法,这静态方法返回参数中指定集合的一个动态类型安全的视图,当使用集合的客户往集合中添加错误类型的元素时将抛出ClassCastException异常,以此来增加类型安全性。

2.7 方便性实现

   方便性实现,去除了常用集合类的一些其他强大功能,保留了一些既方便又高效的特性。他们都是通过Collections的静态方法来提供的。


1)从数组对象获取固定大小的列表对象

   为了方便地在基于数组的和基于集合的API之间进行互操作,引入了Arrays的

public static <T> List<T> asList(T... a);方法。

   Arrays.asList返回一个数组的列表视图,这个列表视图可以当作实现了List接口的集合来使用,也可以用于构造其他集合。

    在该视图上所作的修改将直接反映到原始的数组中,值得注意的是,这个方法返回的集合大小是数组的大小且不可变,试图在返回的集合上执行add或者remove操作将会引发UnsupportedOperationException异常。

    另外,如果你想创建一个固定大小的List,那么使用Arrays.asList也是比任何常用实现类更高效的方式,可以使用语句: List<String> list = Arrays.asList(new String[size]);来完成。


2)获得不可变的一值多拷贝的列表对象

 static <T> List<T>     nCopies(int n, T o);

  方法返回参数中指定值O的n份拷贝的不可变列表,通常可以使用这个方法来初始化新建的集合对象,例如:

List<Type> list = new ArrayList<Type>(Collections.nCopies(1000, (Type)null);

或者向已有集合对象中添加重复的元素,例如 :

lovablePets.addAll(Collections.nCopies(69, "fruit bat"));


3)获得不可变的单个元素的列表对象

   例如方法static <T> Set<T>     singleton(T o);

返回不可变的集,只包含参数o指定的元素。

利用这个方法可以从集合对象中移除指定的元素,例如:

c.removeAll(Collections.singleton(e));

此外还有singletonListsingletonMap等方法实现类似的功能。


4)获取空集,空列表以及空映射表

Collections类提供了emptySet, emptyList, and emptyMap方法来获取空的集合对象,这些方法主要用来生成一个空的集合对象来传递给一个需要集合对象的方法,而我们又不希望给出任何初值的情形,例如:

tourist.declarePurchases(Collections.emptySet());


2.8 抽象实现

    集合接口有大量的方法,这些方法可以通过更基本的方法加以实现,抽象类就是实现了这些例行方法的一些类,主要包括:AbstractCollection、AbstractSet 、AbstractList
AbstractSequentialList 、AbstractQueue 、AbstractMap 6个抽象类。
     这些类可以拓展用来定义自己的集合类。通常使用集合框架中预定义的集合类已经够用了,如果你有让集合类持久化,或者是特定应用所需要,亦或者是为了追求高性能的需要,你可能会自定义集合类。

       定义自己的集合类,需要注意选择合适的抽象类来拓展,同时实现抽象方法,根据你的集合类是否允许修改还需要覆盖一个或者多个具体方法,这需要参考相关的API文档。然后调试和测试你的集合类,有关性能方面的调整也需要阅读相关API文档来做出修改。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值