在我们程序开发过程中,经常会使用一些存储容器,而集合是最常见的存储容器,也是大家最了解的经常研究的系列接口,所以我也做一些初步总结,如果有问题希望能被及时指正。

Collection中常见接口及实现类

Iterable:
Collection:
List:
ArrayList, edList,Vector,Stack
Set:
HashSet,TreeSet, edHashSet
Iterable:在java5加入的总接口 ,实现它的实现类 都可以进行foreach循环迭代。
Collection 是一组集合的根接口,表示一组对象。它是List和Set 共同点抽象出来的超级接口,平时开发中很少直接用到。
List: 有序的,可重复的 。两个常用的实现类:ArrayList, edList,大家最常用的实现类之一。两个经典实现类:Vector,Stack,因为线程安全的极少出现在工作中,却经常应用。
Set: 无序的,不可重复的。两个常用的实现类:HashSet,TreeSet,大家最常用的实现类之一。 edHashSet实现则是既有有序的特点,又能实现不可重复的特点。
那么从实现类来了解两个集合的特性。

List的实现类

1. ArrayList

ArrayList通过源码了解底数据结构时数组,是一种可变长的数组实现,同时允许元素为空。
ArrayList成员变量:

	private static final int DEFAULT_CAPACITY = 10; // ArrayList默认容量(非实际元素数量)。
	private static final  [] EMPTY_ELEMENTDATA = {}; //通过有参构造(int且>=0) 而创建的数组,调用时 给出容量。当给出的容量为0时 返回该数组。
	private static final  [] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//最常用的返回,无参构造时 默认的返回数组。
	transient  [] elementData;	//存放元素的数组,即存储元素的最终位置,以上两个数组当有元素存入时,都最终放入该数组中。
					DEFAULTCAPACITY_EMPTY_ELEMENTDATA默认数组容量为DEFAULT_CAPACITY 或者容量为有参构造参数值。
 	private int size;//ArrayList 集合中存放元素实际数量(非容量)。

ArrayList常用方法:

        int size() // 获取集合中实际元素数量。
	boolean isEmpty() //判断集合是否为空集合。	
	int indexOf(  o) //返回 元素的下标。(第n+1个元素)
	 [] toArray() //通过Arrays工具类转型为数组。
	E get(int index) //通过下标获取对应元素。如果int值大于元素最大下标,会出现数组越界异常。
	E set(int index, E element) //插入制定下标下的元素,将该下标元素则覆盖,返回覆盖前的元素。如果int值大于元素最大下标,会出现数组越界异常。
	boolean add(E e) //添加元素。
	E remove(int index) //删除指定下标下的元素,返回被删除元素。如果int值大于元素最大下标,会出现数组越界异常。
	boolean remove(  o)// 删除指定元素,成功返回true。
	clear() //清空数组。
	void grow(int minCapacity) ArrayList 集合扩容方法。

ArrayList底层以数组实现,同时它实现了RandomAccess接口,可以通过下标快速查找相应元素,相当于数据库用索引查询数据。ArrayList增删除元素的时候,会进行一次全元素复制,如果要复制的元素很多,所以数据越多,增删会变得越慢。

2. edList

edList 与ArrayLisy一样实现了基本的List接口,但是他的中间插入和删除,效率比ArrayList更高效,在随机查询操作要逊色,主要的原因是 edList的底层数据结构时双向链表结构。
什么是双向链表结构?百度百科:是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点,一般我们都构造双向循环链表。就是 edList存储的每一个元素不仅仅有元素,同时存储上一个元素和下一个元素的地址。
edList成员变量:

	 transient int size = 0;// edList 集合中存放元素实际数量。初始默认值为0
	 transient Node<E> first; // 第一个元素。
	 transient Node<E> last; // 最后一个元素。

edList常用方法:

	 boolean add(E e) //将指定元素添加到此向量队列的末尾。 
	 void add(int index, E element) //在此向量队列的指定位置插入指定的元素。(插入后原index上和后面的元素后移一位)
	 void addFirst(E e) //将指定元素插入此列表的开头。 
	 void addLast(E e) //将指定元素添加到此列表的结尾。 
	 void clear() //从此列表中移除所有元素。 
	   clone() //返回此  edList 的浅表副本。 
	 E get(int index) //返回此列表中指定位置处的元素。 
 	 E poll() //获取并移除此列表的头(第一个元素) 
	 E pop()//从此列表所表示的堆栈处弹出一个元素。 
	 void push(E e) //将元素推入此列表所表示的堆栈。 
	 E remove() //获取并移除此列表的头(第一个元素)。
	 boolean remove(  o) //从此列表中移除首次出现的指定元素(如果存在)。
  	 E set(int index, E element)//将此列表中指定位置的元素替换为指定的元素。 (插入后,原index上的元素被覆盖,后面的元素不变)
	 int size() //返回此列表的元素数。 
	  [] toArray() //返回以适当顺序(从第一个元素到最后一个元素)包含此列表中所有元素的数组。 

edList 底层是链表结构,对数据插入和删除具有效率优势。另注意:如果是队尾插入数据,则与ArrayList效率几乎相同。 edList 的查询效率低。因为查询遍历时需要移动指针。
/PS:有趣的是*/
从结构上来看,如果用于查询只能通过一个一个元素的前驱或后驱来逐个查询,所以效率低。中间插入效率高,因为插入时只需要在插入位置把上一个元素的后驱和下一个元素的前驱指向插入元素,而插入元素自然前驱后驱指向上一个和下一个元素就完成了,而ArrayList中间插入元素 是要从新建一个数组,把两节数组与插入元素组合后形成新的ArrayList集合。
问题来了,往往测试时插入十万百万条数,看到的结果却是ArrayList效率更高,这是为什么呢?是因为ArrayList没有遍历操作,底层用arraycopy。而 edlist每次插入都需要进行遍历后再插入,所以数据越多,效率越差(本来查询性能就差)。ArrayList的效率高的原因则是以空间换时间,每次插入数据需要新建数组,原有的数组就废弃掉了。所以实际应用尽量要根据当时的实际情况来。

3.Vector

首先Vector(向量队列) 是线程安全的,从源码上看,大部分方法都加了锁,同时它的底层是数组,所以他是有序的。扩容方面,ArrayList自动增加原长度的一半,而Vector增加了一倍。它继承了AbstractList,实现了List,所以,它是一个队列,支持相关的添加、删除、修改、遍历等功能。它实现了RandmoAccess接口,即提供了随机访问功能。RandmoAccess是java中用来被List实现,为List提供快速访问功能的。在Vector中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问。它实现了Cloneable接口,即实现clone()函数。它能被克隆。
Vector成员变量:

       protected  [] elementData;//存储向量队列组件的数组缓冲区。保存vector中元素的数组。容量是数组的长度,数组的长度最小值是它的元素个数。
       protected int elementCount;//Vector 对象中的有效组件数。实际的元素个数。
       protected int capacityIncrement; // 向量队列的大小大于其容量时,容量自动增加的量。当它的实际容量elementCount将要大于它的最大容量时,vector自动增加的容量。
/**
  *int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity);从中看出如果没有设置自动增加的量,则默认 
  *增加个数是原长度大小,即成倍增加。
  */

Vector常用方法:

	boolean add(E e) //将指定元素添加到此向量队列的末尾。 
	void add(int index, E element) //在此向量队列的指定位置插入指定的元素。 
 	int capacity() //返回此向量队列的当前容量。 
 	void clear() //移除所有元素。 
	  clone() //返回该向量队列的一个副本对象。 
 	E get(int index)//返回向量队列中指定位置(下标)的元素。 
	E set(int index, E element) //用指定的元素替换此向量队列中指定(下标)处的元素。 
 	int size() //返回此向量队列中的组件数。 
 	 [] toArray() //返回一个数组,包含此向量队列中以恰当顺序存放的所有元素。 

因为 Vector添加了同步锁 所以是线程安全的。它是JDK1.0版本的老版本,效率低于ArrayList,所以在一般情况下都会用ArrayList。另1.6API中注释:Vector 的 iterator 和 listIterator 方法所返回的迭代器是快速失败的:如果在迭代器创建后的任意时间从结构上修改了向量(通过迭代器自身的 remove 或 add 方法之外的任何其他方式),则迭代器将抛出 ConcurrentModificationException。因此,面对并发的修改,迭代器很快就完全失败,而不是冒着在将来不确定的时间任意发生不确定行为的风险。Vector 的 elements 方法返回的 Enumeration 不是 快速失败的。

4.Stack

Stack(栈)从源码上看它继承了Vector,用源码的释义class represents a last-in-first-out (LIFO) stack of s 栈是一个先进后出的对象,原理和俄罗斯套娃相似。虽然Stack 继承了Vector 但是用到Stack时 一般都用它自己的方法即 push、pop、peek、empty、search这几个方法。
Stack常用方法:

	 boolean empty() //测试堆栈是否为空。 
	 E peek() //查看堆栈顶部的对象,但不从堆栈中移除它。
	 E pop() //移除堆栈顶部的对象,并作为此函数的值返回该对象。俗称出栈
	 E push(E item)//把项压入堆栈顶部。俗称入栈
	 int search(  o) //返回对象在堆栈中的位置,以 1 为基数。

Set的实现类

1.HashSet

API1.6中介绍HashSet“此类实现 Set 接口,由哈希表(实际上是一个 HashMap 实例)支持”,即表示了HashSet的底层是由HashMap来实现的。理解HashMap就能很好的理解HashSet。
HashSet成员变量:

	private transient HashMap<E, > map;//用于存放最终数据的。也证明了底层是HashMap
    	private static final   PRESENT = new  ();//是所有写入 map 的 value 值

HashSet常用方法:

	boolean add(E e) //如果此 set 中尚未包含指定元素,则添加指定元素。 
 	void clear() // 从此 set 中移除所有元素。 
 	  clone() //返回此 HashSet 实例的浅表副本:并没有复制这些元素本身。 
 	boolean contains(  o) //如果此 set 包含指定元素,则返回 true。 
 	boolean isEmpty()//如果此 set 不包含任何元素,则返回 true。 
 	Iterator<E> iterator() //返回对此 set 中元素进行迭代的迭代器。 
 	boolean remove(  o) //如果指定元素存在于此 set 中,则将其移除。 
 	int size() //返回此 set 中的元素的数量(set 的容量)。 

HashSet是无序不和重复的,当使用add()方法时,会根据HashCode和equals来进行判断,并去重的。

2.TreeSet

TreeSet集合是存储元素不可重复,TreeSet集合底层数据结构是红黑树,排序效率高。和HashSet类似,TreeSet基于TreeMap 的NavigableSet 实现。使用元素的自然顺序对元素进行排序,或者根据创建 set 时提供的 Comparator 进行排序,具体取决于使用的构造方法。
TreeSet成员变量:

	private transient NavigableMap<E, > m;//用于存放最终数据的。也证明了底层是TreeMap 
    	private static final   PRESENT = new  ();//是所有写入 map 的 value 值。

TreeSet常用方法:

	boolean add(E e)//将指定的元素添加到此 set(如果该元素尚未存在于 set 中)。 
	void clear() // 从此 set 中移除所有元素。 
 	  clone() //返回此 TreeSet 实例的浅表副本:并没有复制这些元素本身。 
  	Comparator<? super E> comparator() //返回对此 set 中的元素进行排序的比较器;如果此 set 使用其元素的自然顺序,则返回 null。 
 	boolean isEmpty() //如果此 set 不包含任何元素,则返回 true。
	boolean remove(  o) //将指定的元素从 set 中移除(如果该元素存在于此 set 中)。 
 	int size() //返回 set 中的元素数(set 的容量)。 

TreeSet是可排序不可重复的,所以速度上会劣于HashSet。TreeSet依靠comparato()方法返回值来判断是不是重复元素的,判重方式与HashSet不同。

3. edHashSet

edHashSet继承自HashSet,与HashSet的区别是 edHashSet内部使用的是 HashMap。所以 edHashSet中的元素顺序是可以保证的,也就是说遍历序和插入序是一致的。
edHashSet没有自己的成员变量,方法几乎都是基于HashSet方法中调用 edHashMap中的方法。所以这里就不多介绍,到学习 edHashMap时回顾一下。

本文参考文章

参考: [https://me.csdn.net/panweiwei1994] 潘威威集合相关博客
[https://blog.csdn.net/logan__/article/details/79403148] 浅谈List
[https://www.cnblogs.com/skywang12345/p/3308833.html] Java 集合系列06之Vector详细介绍(源码解析)和使用示例
[https://github.com/crossoverJie/JCSprout/tree/master/MD] 集合相关内容
[http://www.cnblogs.com/wlrhnh/p/7256969.html] 【Java集合系列四】HashSet和 edHashSet解析

收藏 打印