Java 集合框架(JCF)

根据Java SE源码和《Java核心技术-卷一》(第九版)整理而成



一、Iterator 迭代器

Iterator接口只有三个方法:

public Interface Iterator<E> {
    boolean hashNext();
    E next();
    void remove();
}

如果有了迭代器,就可以对集合采用这些方法进行遍历。一般采用while的形式。从 JavaSE 5.0开始,这种while循环可以采用一种更优雅的方式实现,即for each循环。编译器会将for each循环翻译为带有迭代器的循环。

但是并不是说任何情况下都适合用for each带起迭代器,这边就显示出其remove方法的作用了。

remove方法将会删除上次调用next方法时返回的元素。也就是说,remove方法时和next方法相互依赖的,要删除一个元素,必须先利用调用next将其返回。一个应用场景如下,删除map中value为null的元素,这里如果要用for each并不合适。

Iterator it = map.keySet().iterator();
while (it.hasNext()) {
    long id = (Long) it.next();
    if (map.get(id) == null) {
        it.remove();
    }
}

对于迭代器,下图可以形象的表示。迭代器位于两个元素之间,当调用next方法时,迭代器就会越过下一个元素,并且返回刚刚越过的那个元素的引用。


Iterable接口只提供一个方法,就是返回一个Iterator:

public Interface Iterable<E> {
    Iterator<E> iterator();
}

而Collection接口扩展了Iterable接口,就包含了iterator方法,因此都可以用for each循环遍历。可以这样理解,for each可以与任何实现了Iterable接口的对象一起工作。


二、Collection

是一个公用的接口,其扩展了Iterable接口,有一些通用的方法,List和Set都扩展了改接口。

而类AbstractCollection则实现了该接口的部分通用方法,有需要实现Collection接口的可以继承AbstractCollection类。比如ArrayList和Linked都间接继承了AbstractCollection,当然中间通过了AbstractList类。而HashSet和TreeSet都继承自AbstractSet,AbstractSet也继承自AbstractCollection。


三、List -- LinkedList

1、有一个迭代器,ListIterator,扩展了Iterator接口,添加了add方法。

2、链表不支持随机访问,get方法的效率比较低。


四、List -- ArrayList

1、也实现了List接口,表示动态数组。由于是基于数组实现的,其容量限制。

2、Vector的所有方法都是同步的,而ArrayList是非线程安全的,如果不需要同步,建议使用ArrayList。当然,即使为了线程安全,也不建议使用Vector。

3、ArrayList在执行插入元素的时候要扩容,在删除元素的时候并不会减少数组的容量(如果希望相应的缩小数组容量,则调用trimToSize())。

4、在查找元素的时候要遍历数组,对于非null的元素采取equals方式,对于null则只能用==。contains方法内部是使用了indexOf,结果“>=0”表示找到。

5、调用public <T> T[] toArray(T[] a)来转换为数组时,最好将参数里的数量填充好,这样效率会更高些。

 

    public <T> T[] toArray(T[] a) {
        if (a.length < size)
            // Make a new array of a's runtime type, but my contents:
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
	System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }

 

  

 


五、Set -- HashSet

1、依据对象的hashcode来定。

2、一般设置装填因子为75%,如果超过这个数,就会再散列。

3、注意,自己实现的hashcode必须与equals方法兼容,即如果a.equals(b)为true,则a和b应该有相同的hashcode。

4、HashSet依赖于HashMap,其实际上是通过HashMap实现的。


六、Set -- TreeSet

1、利用树结构(红黑树)实现,保证插入的元素有序,但这样也就导致了效率会差一点。

2、默认情况下,TreeSet假定插入的对象实现了Comparable接口,这个接口定义了一个方法。如果要插入自定义对象,就要通过实现Comparable接口自定义排列顺序。

public Interface Comparable<T> {
    int compareTo(T other)
}

3、但是Comparable接口有其局限性,对于一个给定的类,只能实现这个接口一次。如果想要有不同的比较方法,可以实现Comparator接口。

 

public Interface Comparator<T> {
    int compare(T a, T b);
}

 

  

 

4、从Java SE 6起,TreeSet类实现了NavigableSet接口,这个接口增加了几个便于定位元素以及反向遍历的方法。


5、TreeSet依赖于NavigableMap,其实际是由NavigableMap实现的。

 


七、Deque

1、双端队列,只支持在头部或者尾部添加或者删除元素,不支持在中间添加元素。

2、Deque在Java SE 6引入,并由ArrayDeque和LinkedList类实现。


八、PriorityQueue

1、优先级队列,采用堆实现。


九、Map

1、键必须唯一,不能对同一个键存放两个值。如果对同一个键调用两次put方法,第二个值将会取代第一个值。

2、映射表(map)包括三个视图Set<K> keySet(),Collection<V> values(),Set<Map.entry<K, V>> entrySet()。注意,keySet既不是HashSet,也不是TreeSet,而是实现了Set接口的某个其他类对象。Set接口扩展了Collection接口,因此可以与使用任何集合一样使用keySet,例如使用for each。

如果要同时查看键和值,可以通过entry来查看。entry接口是map接口内部的接口,其定义如下:

Interface Entry<K, V> {
     K getKey();
     V getValue();
     V setValue(V value);
     boolean equals(Object o);
     int hashCode();
}

  

这样,就可以利用for each形式,遍历map中的键值对了。

 

3、通常会用到HashMap和TreeMap两个类。


十、WeakHashMap

1、使用弱引用(weak reference)保存键。


十一、LinkedHashSet和LinkedHashMap

1、都是为了记住插入元素的顺序。

2、链接散列映射表将用访问顺序,而不是插入顺序,对映射条目进行迭代。每次调用put和get方法,收到影响的条目将从当前位置删除,并放到条目链表的尾部。

3、访问顺序对于实现高速缓存的LRU(最近最少使用)原则十分重要。


十二、EnumSet

1、是一个枚举类型元素集的高效实现。


十三、IdentityHashMap

1、标示散列映射表。根据对象的内存地址来计算散列码。对两个对象进行比较时,使用==,而不是equals。也就是说,不同的键对象,即使内容相同,也被视为不同的对象。

2、在实现对象遍历算法(如对象序列化)时,这个类非常有用,可以用来跟踪每个对象的遍历状况。


 

十四、fail - fast

1、对于并不是线程安全的集合,例如list和set,以及map,当有多个线程同时对其操作时会产生java.util.ConcurrentModificationException



 

public <T> T[] toArray(T[] a)
posted @ 2015-12-27 12:09  luceion  阅读(416)  评论(0编辑  收藏  举报