JDK源码解析集合篇--综述

来源:互联网 发布:空间自动留言软件 编辑:程序博客网 时间:2024/05/22 01:54

Java集合工具包位于Java.util包下,包含了很多常用的数据结构,如数组、链表、栈、队列、集合、哈希表等。学习Java集合框架下大致可以分为如下五个部分:List列表、Set集合、Map映射、迭代器(Iterator、Enumeration)、工具类(Arrays、Collections)。在JDK1.5后,util包下加入了concurrent包,更加完善了集合框架对于并发多线程情况下的处理。
这里写图片描述
从上图可以看到,标蓝色的是对应接口的抽象类,它是缺省适配器设计模式的实现,提供了默认或一些共同操作,使得继承了接口的实现类不用实现接口全部的方法。这在前面的设计模式博文中提到过,设计模式–适配器模式(JDK中的应用)。
集合类主要分为两大类:Collection和Map。在这里没有把并发集合类放到一起来讨论,并发集合类在后边再进行讨论。上图中,我们可以看到set集合与map集合的依赖关系,Set接口通常表示一个集合,其中的元素不允许重复(通过hashcode和equals函数保证),常用实现类有HashSet和TreeSet,HashSet是通过Map中的HashMap实现的,而TreeSet是通过Map中的TreeMap实现的。另外,TreeSet还实现了SortedSet接口,因此是有序的集合(集合中的元素要实现Comparable接口,并覆写Compartor函数才行)。
注意:在上边的Vector,Stack和Hashtable类是jdk1.0提供的集合类,都是利用synchronized实现的线程安全的,图中其他类是线程不安全的,但效率较低,后来被ArrayList、LinkedList,HashMap替代了,这可用于没有线程安全的情景下,当需要保证线程安全时,Collections中有很多静态方法可以返回各集合类的synchronized版本,即线程安全的版本,如果要用线程安全的结合类,首选Concurrent并发包下的对应的集合类(并发包做了很多锁优化)。
集合框架用来存储一系列对象,当jdk1.5没有出现泛型之前,集合可以存储的对象类型都默认为Object类型,在取出时需要进行强制转换到相应的对象类型。数组的长度在编译期就确定且无法改变,集合框架最主要的特性就是长度可变的。

Enumeration和Iterator

Iterator是遍历集合的迭代器(不能遍历Map,只用来遍历Collection),Collection的实现类都实现了iterator()函数,它返回一个Iterator对象,用来遍历集合,ListIterator则专门用来遍历List。而Enumeration则是JDK1.0时引入的,作用与Iterator相同,但它的功能比Iterator要少,它只能再Hashtable、Vector和Stack中使用。
Enumeration的速度是Iterator的两倍,也使用更少的内存。Enumeration是非常基础的,也满足了基础的需要。但是,与Enumeration相比,Iterator更加安全,因为当一个集合正在被遍历的时候,它会阻止其它线程去修改集合。Iterator在集合框架中实现的是快速失败fail-fast机制,当在遍历时候,集合结构被改变时,会抛出ConcurrentModificationException。例如:假设存在两个线程(线程1、线程2),线程1通过Iterator在遍历集合A中的元素,在某个时候线程2修改了集合A的结构(是结构上面的修改,而不是简单的修改集合元素的内容),那么这个时候程序就会抛出 ConcurrentModificationException 异常,从而产生fail-fast机制。
而对Java1.5并发包(java.util.concurrent)包含线程安全集合类,则允许在迭代时修改集合。
拿ArrayList的迭代器实现为例:

  public Iterator<E> iterator() {        return new Itr();    }    /**     * An optimized version of AbstractList.Itr     */    private class Itr implements Iterator<E> {        int cursor;       // index of next element to return        int lastRet = -1; // index of last element returned; -1 if no such        int expectedModCount = modCount;        public boolean hasNext() {            return cursor != size;        }        @SuppressWarnings("unchecked")        public E next() {        //首先要检查是否集合结构被其他线程改变            checkForComodification();            int i = cursor;            if (i >= size)                throw new NoSuchElementException();            Object[] elementData = ArrayList.this.elementData;            if (i >= elementData.length)                throw new ConcurrentModificationException();            cursor = i + 1;            return (E) elementData[lastRet = i];        }        public void remove() {            if (lastRet < 0)                throw new IllegalStateException();            //删除时,要检查集合结构是否被其他线程改变            checkForComodification();            try {                ArrayList.this.remove(lastRet);                cursor = lastRet;                lastRet = -1;                //使用迭代器删除元素,改变集合结构时,这一步将expectedModCount                //重新赋值了,所以在后边的遍历调用next,remove方法是不会抛出异常的                expectedModCount = modCount;            } catch (IndexOutOfBoundsException ex) {                throw new ConcurrentModificationException();            }        }        @Override        @SuppressWarnings("unchecked")        public void forEachRemaining(Consumer<? super E> consumer) {            Objects.requireNonNull(consumer);            final int size = ArrayList.this.size;            int i = cursor;            if (i >= size) {                return;            }            final Object[] elementData = ArrayList.this.elementData;            if (i >= elementData.length) {                throw new ConcurrentModificationException();            }            while (i != size && modCount == expectedModCount) {                consumer.accept((E) elementData[i++]);            }            // update once at end of iteration to reduce heap write traffic            cursor = i;            lastRet = i - 1;            checkForComodification();        }        final void checkForComodification() {        //modCount是ArrayList的属性,如果被其他线程改变其结构时,modCount会发生变化,        //expectedModCount是在获得迭代器时的赋值的,在遍历中,被改变,则两者是不相等的,所以        //会抛出ConcurrentModificationException            if (modCount != expectedModCount)                throw new ConcurrentModificationException();        }    }

迭代器取代了Java集合框架中的Enumeration。迭代器允许调用者从集合中移除元素(有remove方法),而Enumeration不能做到。为了使它的功能更加清晰,迭代器方法名已经经过改善。
这有一个点:
在遍历过程中,要想删除元素必须是调用迭代器的remove方法才不会抛出ConcurrentModificationException异常,看remove方法的实现就可以明白,调用迭代器的remove方法,会对expectedModCount重新赋值,而如果直接调用集合地remove方法,如ArrayList的remove方法改变ArrayList集合结构,也就相当于改变了modCount,与多线程改变集合结构的效果一样,所以会抛出ConcurrentModificationException异常。

Collection/Map

Collection包含了List、Set、Queue三大分支。Collection 继承了Iterable接口,Iterable此接口的目的:实现了这个接口的集合对象支持迭代,是可迭代的。而Iterator则是迭代器,它就是提供迭代机制的对象,具体如何迭代,都是Iterator接口规范的。
(1)List:1.代表有序、重复的集合。2.像一个数组,可以记住每次添加元素的顺序(要以对象的形式来理解),且长度是可变的。3.访问的时候根据元素的索引值来访问。
(2)Set:1.代表无序、不可重复的集合。2.就像一个罐子,但元素单独存在。访问元素只能根据元素本身来访问。3.元素不可以重复
(3)Queue:1.代表队列集合。2.直接对应数据结构的队列。3.LinkedList类实现了Queue接口,因此我们可以把LinkedList当成Queue来用。
(4)Map:1.是一个映射接口,即key-value键值对。Map中的每一个元素包含“一个key”和“key对应的value”。2.AbstractMap是个抽象类,它实现了Map接口中的大部分API。而HashMap,TreeMap,WeakHashMap都是继承于AbstractMap。3. Hashtable虽然继承于Dictionary,但它实现了Map接口。
由于集合类的长度都是可变的,所以在每个实现类,如何实现扩容都是重点内容。

这里写图片描述
可以看出:Collection是一个高度抽象出来的集合,包含了集合的基本操作:添加、删除、清空、遍历、是否为空、获取大小等。Collection接口的所有子类(直接子类和间接子类)都必须实现2种构造函数:不带参数的构造函数和参数为Collection的构造函数。带参数的构造函数可以用来转换Collection的类型。
抽象类:AbstractCollection,它实现了Collection中除了iterator()和size()之外的所有方法。AbstractCollection的主要作用是方便其他类实现Collection.,比如ArrayList、LinkedList等。它们想要实现Collection接口,通过集成AbstractCollection就已经实现大部分方法了,再实现一下iterator()和size()即可。
在接下来的学习中,会详细分析相应的实现类的实现。

集合类特点和应用场景总结:
这里写图片描述
List接口中,比较常用的类有三个:ArrayList、Vactor、LinkedList。
ArrayList :线程不安全的,对元素的查询速度快。
Vector :线程安全的,多了一种取出元素的方式:枚举(Enumeration),但已被ArrayList取代。
LinkedList :链表结构,对元素的增删速度很快。
Set接口中,比较常用的类有两个:HashSet、TreeSet:
HashSet:要保证元素唯一性,需要覆盖掉Object中的equals和hashCode方法(因为底层是通过这两个方法来判断两个元素是否是同一个)。
TreeSet:以二叉树的结构对元素进行存储,可以对元素进行排序。
排序的两种方式:
1、元素自身具备比较功能,元素实现Comparable接口,覆盖compareTo方法。
2、建立一个比较器对象,该对象实现Comparator接口,覆盖compare方法,并将该对象作为参数传给TreeSet的构造函数(可以用匿名内部类)。
Map接口其特点是:元素是成对出现的,以键和值的形式体现出来,键要保证唯一性:常用类有:HashMap,Hashtable ,TreeMap。
HashMap:线程不安全等的,允许存放null键null值。
Hashtable:线程安全的,不允许存放null键null值。
TreeMap:可以对键进行排序(要实现排序方法同TreeSet)。
Collection和Map两个接口对元素操作的区别:
存入元素:
Collection接口下的实现类通过add方法来完成,而Map下是通过put方法来完成。
取出元素:
Collection接口下:List接口有两种方式:1、get(脚标);2、通过Iterator迭代方式获取元素;而Vactor多了一种枚举(Enumeration)的方式。Set接口通过迭代的方式获取元素。
Map接口下:先通地keySet获取键的系列,然后通过该系列使用Iterator迭代方式获取元素值。

原创粉丝点击