Java集合框架

1 篇文章 0 订阅
1 篇文章 0 订阅

写在前面

在深入了解Java集合前,有必要先来了解它的设计哲学。引用Java集合框架项目带头人

Joshua Bloch的一句话:

"The main design goal of Collections Framework was to produce an API that was

reasonably small, both in size, and, more importantly, in conceptual weight."

这里的重点是conceptual weight,即概念上轻量。概念上的轻量意味着使用者们可以“不必

掌握大量信息,就能使用该API完成复杂的功能”。在Java集合框架中最直观的表现就是:

轻量、骨感的接口等级结构。为此,Java集合框架项目组做出了大量的取舍。这也是该

集合与著名的C++ STL的重要区别,同时也是Java这么多年一直有趣、易用、受欢迎的原因

之一。

在开始之前先让我们思考一个问题,为什么一门流行的编程语言需要至少一个公共或标准

集合包?

这是因为,如果没有这样一个包,就可能会存在大量的第三方集合库。而使用这些集合库的

代码想要达到互相复用,则需要互相编写适配器。需要多少个适配器呢?当n个代码模块一共

使用了n个集合库时,则需要n的阶乘(n!)个适配器。这显然是不可接受的。因此一门语言

想要被广泛地使用,就需要拥有一个被广泛接受的集合。

两道开胃菜

开胃菜1:在Java编程中为什么总是在红色代码执行完后的第二次循环,总是倾向于(而非总是)抛出

ConcurrentModificationException(strList的实现是ArrayList)?

这就是fail-fast机制。在strList的内部维护了一个Modification Counter,它会在非迭代器

修改元素的情况下+1。并在迭代器初始化时以final的形式赋给了迭代器对象。之后,每次迭代

时都会比较这两个counter,如果不相等,就尽可能早的抛出ConcurrentModificationException,

以避免程序在未来引起令人困惑行为。

    for(String str : strList){

        if(condition)

     strList.remove(str)

   }

开胃菜2:为什么在Java集合框架的接口中会出现大量的选择性实现的方法(optional methods)?

这些方法通常是修改或删除元素的方法。当某一个类不想要实现一个接口中的选择性方法时,就可

以简单粗暴地抛出UnsupportedOperationException(这是一个RuntimeException,你不应该捕获它

)。

这个问题的答案是,如果不这么做,Java集合框架接口结构就会呈现爆炸式地增长。比如对于一个

List是否可修改元素,你可能会需要:UnmodifiableList和ModifiableList两个接口。当一切都乘以2

之后,你可能仍然无法避免RuntimeException。例如当我们需要增加一个类似日志的数据结构,它需

要不断地append元素,但是它不应该支持修改元素和删除元素以及在尾部以外任何位置插入元素。

这时你该怎么办?难道再增加一个对应的接口吗?别忘了我们的设计哲学,概念上的轻量

三条总的原则

1. 永远使用接口去定义你的类型。包括变量声明,参数类型和方法返回类型

Effective Java的item 19曾介绍使用接口定义类型的重要性。把这一条作为第一条原则的原因

是,在集合的领域,它更加重要,而且更加严格。也即,当使用集合类型作为你的变量声明类型、参数

类型或方法返回类型时,你永远可以,也永远应该使用接口而非抽象类或实体类来定义该类型

这样做能够为你带来最大程度的灵活性,这在涉及到集合时尤为重要。当你切换到一个新的数据结构时,

你的外层代码永远不需要做出不必要的改动。

2. 在多线程环境下,优先选择concurrent的家族成员

Concurrnt家族下,没有一个实现类是简单地使用内部锁(独占锁)来保证线程安全。它们一般会通过

大锁化小锁,final,valitile关键字或者copy on write甚至无锁技术来保证线程安全。它们带来的并

行度提升通常是十分可观的。当然,它们受到使用场景的限制。

3. 在相应的场景下,选择最为合适集合实现类

这是一个相当粗犷的说法。但确实是集合运用进阶的分水岭。当没有一个实现类能够针对你的使用场景

时,你可能需要考虑重新设计一个非常能够针对你的问题的数据结构。当然,为了互通有无,和不影响外层

代码,你也许应该考虑将它引入到Java集合框架中来。本篇分享的剩余部分会重点讨论这个问题。

16条建议(General Programming 8 条, Concurrent 4条,Expanding 4条)

建议1:放弃Emmeration,开始使用Iterator

Iterator设计的目的就是为了取代Emmeration,它拥有更为清新的方法名和一个新增的删除元素的方法。

建议2:在遍历一个List的时候,可以使用ListIterator来增加一个元素和双向遍历

ListIterator保证有序遍历,而Iterator不保证。

建议3: 在面对稀疏数据结构时,可考虑使用HashSet

例如,当一个BitSet拥有99%的false时,你可以使用HashSet来替代它。

建议4:当你需要频繁地查找某个元素是否在一个List或数组中时,最好的方法是先对它进行排序,然后
使用binarySearch
建议5:使用集合的转化构造器在运行时改变你的实现类,从而带来执行效率提升

例如,当你需要使用TreeMap,而且需要往这个数据结构中添加大量的元素。TreeMap的put操作时十分昂贵

的,这个时候的最佳做法时。先加所有的元素添加到一个HashMap,然后再使用:

   TreeMap treeMap = new TreeMap(hashMap);

来拿到你的TreeMap实例。

建议6:当你需要元素的有序遍历时,考虑使用“Linked”家族

诸如LinkedHashSet和LinkedHashMap能保证元素按照插入的顺序遍历,而这样做带来的性能损耗相比HashSet和

HashMap来说是十分微小的。

建议7:使用LinkedHashMap做出简单的LRU cache

代码如下:

public class LruCache<K, V> extends LinkedHashMap<K, V> {

  private static final long serialVersionUID = 1L;

  private final int maxCapacity;

  public LruCache(int maxCapacity) {

    super(maxCapacity, 0.7F, true);

    this.maxCapacity = maxCapacity;

  }

  @Override

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

    return size() > maxCapacity;

  }

}

建议8:使用WeakHashMap来维护短暂使用的数据

WeakHashMap中的entry会在它的key不再被任何线程的执行栈中引用到时,在下一次垃圾回收的周期

被GC回收掉。它非常适合实时性消费数据,例如画图数据。

建议9:在并发环境下,当List中的元素被频繁遍历而少量修改时,可考虑使用CopyOnWriteArrayList

例如,观察者模式中的Event handlers

建议10:在并发环境下,如果不需要强一致性的遍历器,可考虑使用ConcurrentHashMap,否则,请使用HashTable
建议11:在生产者消费者模式下,考虑使用BlockingQueue

通常,请考虑使用LinkedBlockingQueue,因为它拥有更大的吞吐量。

建议12:想要在并发环境下使用TreeMap,请考虑ConcurrentSkipListSet和ConcurrentSkipListMap

它们实现基于经典的无锁队列实现。

建议13:不要使用继承来“加强”一个既有的实现,composition更适合你

这里的加强指的是提供新的API。来看一个例子:

/**

 * This class is useful when you need to store a List value 

 * in a map. It saves many redundant operations.

 * 

 * @author eric.zhang

 *

 * @param <K> type of the key

 * @param <V> value type in a list

 */

public class HashMapForListValue <K,V>{

private Map<K,List<V>> innerMap;

public HashMapForListValue(){

this.innerMap = new HashMap<K,List<V>>();

}

public List<V> put(K key, V value){

List<V> list = null;

if(!innerMap.containsKey(key)){

list = new ArrayList<V>();

list.add(value);

innerMap.put(key, list);

}else{

list = innerMap.get(key);

list.add(value);

}

return list;

}

public List<V> get(K key){

return innerMap.get(key);

}

public Map<K,List<V>> getMap(){

return innerMap;

}

}

这个类在一个key对应一个List value时会帮你省去大量冗余代码。

建议14:使用decorator和builder模式来为既有实现“添加新的功能”

这里的添加新的功能是指,经过装饰或build的实现完全兼有原来实现的功能,

它们对外提供的API没有变化,只是支持了一些额外的功能。

例如:Collections.synchronizedList

      Collections.unmodifiableList

又比如builder模式:

建议15:通过继承abstract家族,可以大大简化设计新的实现的过程

当你需要设计新的的Map时,考虑继承AbstractMap。

建议16:通过写适配器可以将第三方的数据结构引入到Java集合框架中来,以达到互通有无

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值