在编程过程中,会很频繁的使用集合,集合的相关知识也非常重要,也是每一个开发人员必须掌握的知识。

一:集合的概念

集合:保存数量不确定的数据,以及保存具有映射关系的数据的容器,简单的理解就是用于存储数量不等的多个对象的容器。

集合和数组不一样,数组元素既可以是基本类型的值,也可以是对象(对象的引用变量);而集合里只能保存对象(对象的引用变量)。

Java集合类主要由两个集合框架的根接口派生而出:Collection和Map

Java中Collection接口的体系机构:

Collection接口和Iterator接口:

Collection接口是List、Set和Queue接口的父接口,该接口中定义了一些操作集合元素的方法:

下面列举一些常见的方法:

boolean add(Object o):该方法用于向集合里添加一个元素。

boolean addAll(Collection c):该方法把集合c里的所有元素添加到指定的集合里。

void clean():清除集合里的所有元素,将集合长度变为0。

boolean contains():返回集合里是否包含指定元素。

boolean containsAll():返回集合里是否包含集合c里的所有元素。

boolean isEmpty():返回集合是否为空。当集合长度为0时返回true,否则返回false。

Iterator iterator():返回一个Iterator对象,用于遍历集合中的元素。

boolean remove(Object o):删除集合中指定的元素o,当集合中包含了一个或多个元素o时,这些元素将被删除,该方法将返回true。

boolean removeAll(Collection c):从集合中删除集合c里包含的所有元素。

boolean retainAll(Collection c):从集合中删除集合c里不包含的元素(相当于取得把调用该方法的集合变为该集合和集合c的交集),如果该操作改变了调用该方法的集合,返回true。

int  size():该方法返回集合里元素的个数。

Object[]  toArray():该方法把集合转换成一个数组,所有集合元素变成对应的数组元素。


Iterator接口:

Iterator接口也是Java集合框架的成员,但它与Collection系列、Map系列的集合不一样:Collection系列集合和Map系列的集合主要用于盛装其他对象,而Iterator则主要用于遍历集合的元素。又叫迭代器。

Iterator接口隐藏了各种Collection实现类的底层细节,只向应用程序提供遍历集合元素的统一编程接口。

Iterator接口定义了三个方法:

boolean hasNext():如果被迭代的集合元素还有没遍历,则返回true。

Object next():返回集合里的下一个元素。

void remove():删除集合里上一次next方法返回的元素。

Iterator仅用于遍历集合,如果需要创建Iterator对象,则必须有一个被迭代的集合。没有集合和Iterator仿佛无本之木,没有存在的意义。

当使用Iterator对集合元素进行迭代时,Iterator并不是把集合元素本事传给了迭代变量,而是把集合元素的值传给了迭代变量,所以修改迭代变量的值对集合元素本事没有影响。

当使用Iterator来迭代访问Collection集合元素时,Collection集合里的元素不能被修改,只能通过remove方法删除上次next方法返回的集合元素才可以,否则报java.util.ConcurrentModificationException异常。

Iterator迭代器采用的是快速失败(fail-fast)机制,一旦在迭代过程中检测到该集合已经被修改(通常是其他线程进行修改),程序将报java.util.ConcurrentModificationException异常。而不是显示修改后的结果,这样可以避免共享资源而引发的问题。

foreach循环变量集合元素

格式:

for(类型   新对象:集合){

用法和Iterator类似。


二:Set接口

Set集合是无顺序的,其集合元素不允许重复。

Set集合判断两个对象相同不是使用==运算符,而是根据equals方法。简单的说,如果只要两个对象用equals方法比较返回true,Set集合就不会接受这两个对象;反之,则成立。

HashSet类:

HashSet是Set集合的实现类,HashSet按Hash算法来存储集合中的元素,因此具有很好的存取和查找性能。

HashSet集合特点:

(1)不能保证元素的排列顺序,顺序有可能发生变化。

(2)HashSet不是同步的,如果多个线程同时访问一个HashSet,如果有2条或2条以上线程同时修改HashSet集合时,必须通过代码实现线程同步。

(3)集合元素值可以是null。

向HastSet集合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据该HashCode值来决定该对象在HashSet中存储位置。如果有两个元素通过equals方法比较返回true,但它们的hashCode()方法返回值不相等,HashSet将会把它们存储在不同的位置,就可以添加成功(简单的理解HastSet集合判断两个元素是否相等的标准是两个对象通过equals方法比较相等,且两个对象的hashCode()方法返回的值也要相等)。

TreeSet类:

TreeSet是SortedSet接口的唯一实现,TreeSet可以确保集合元素处于排序状态。

TreeSet新添的方法:

(1)Comparator  comparator():返回当前Set使用的Comparator,或者返回null,表示以自然方式排序。

(2)Object  first():返回集合中的第一个元素。

(3)Object  last():返回集合中的最后一个元素。

(4)Object  lower(Object e):返回集合中位于指定元素之前的元素。

(5)Object  higher(Object e):返回集合中位于指定元素之后的元素。

(6)SortedSet  subSet(fromElement,toElement):返回此Set的子集合,范围冲fromElement(包含)到toElement(不包含)。

(7)SortedSet  headSet(toElement):返回此Set的子集,由小于toElement的元素组成。

(8)SortedSet  tailSet(fromElement):返回此Set的子集,由大于或等于fromElement的元素组成。

TreeSet集合并不是根据元素的插入顺序进行排序,而是根据元素实际值进行排序。

TreeSet集合采用红黑树的数据结构对元素进行排序。TreeSet集合支持两种排序方法:自然排序和定制排序。默认使用自然排序。

自然排序

TreeSet调用集合元素的compareTo(Object obj)方法来比较元素之间大小关系,然后将集合元素按升序排列,这就是自然排序。

Java提供了一个Comparable接口,该接口里定义了一个compareTo(Object obj)方法,该方法返回一个整数,实现该接口的类必须实现该方法,当一个对象调用该方法与另一个对象进行比较时,比如:obj1.compareTo(obj2),如果该方法返回0,则表明两个对象相等,如果返回是一个正整数,则表明obj1大于obj2,如果返回是一个负数,则表明obj1小于obj2。

注意:向TreeSet集合中添加元素时,只有第一个元素可以不实现Comparable接口,后面添加的所有元素都必须实现Comparable接口。否则报ClassCastException异常。

当把一个对象加入TreeSet集合中时,TreeSet调用该对象的compareTo方法与集合中的其他对象比较大小,然后根据红黑树算法决定它存储的位置。如果两个对象相等,TreeSet将把他们存储在同一位置。TreeSet集合比较两个对象相等的标准是:两个对象通过equals方法返回true,或通过compareTo方法比较返回0,则认为两个对象是同一个对象。

定制排序

通过实现Comparator接口中的compare方法来实现集合的定制排序,

int   compare(T o1,T o2)方法比较大小,如果返回是正整数,则表明o1大于o2,如果返回0,则表明两个对象相等,如果返回负数,则表明o1小于o2。

如果需要实现定制排序,则需要在创建TreeSet集合对象时,并提供一个Comparator对象与该集合关联,由该Comparator对象负责集合的排序逻辑。

经典实例

public class TreeSetTest {

	public static void main(String[] args) {
		//定义TreeSet集合,并为集合提供一个排序的Comparator对象(这里是匿名内部类)。
		TreeSet<M> ts = new TreeSet<M>(new Comparator<M>() {
			@Override
			public int compare(M m1, M m2) {
				// TODO Auto-generated method stub
				if (m1.num > m2.num) {
					return -1;//这是按降序排序,如果想按升序排序,这里返回正整数,下面else中返回负数,即可。
				} else if (m1.num == m2.num) {
					return 0;
				} else {
					return 1;
				}
			}
		});
		//向集合中添加数据
		ts.add(new M(5));
		ts.add(new M(-3));
		ts.add(new M(8));
		ts.add(new M(6));
		System.out.println(ts);

	}
}

class M {
	// 定义一个变量
	int num;

	public M(int num) {
		// 构造函数中为变量赋值
		this.num = num;
	}

	// 重写toString方法
	public String toString() {
		return "M对象num的值为" + this.num;
	}
}

EnumSet类

EnumSet类是一个专为枚举类设计的集合类,EnumSet中所有的元素都必须是指定枚举类型的枚举值,该枚举类型在创建EnumSet时显式或隐式地指定。EnumSet的集合元素也是有序的,EnumSet以枚举值在Enum类内的定义顺序来决定集合元素的顺序。EnumSet集合不允许插入null元素。

EnumSet集合的内部一位向量的形式存储,这种存储形式非常紧凑、高效,因此EnumSet对象占用内存很小,而且运行效率很好。具体体现在批量操作集合时。

EnumSet类没有暴露任何构造函数来创建该类的实例,程序通过它提供的static方法来创建EnumSet对象。

EnumSet集合常用的方法

(1)static EnumSet  allOf(Class elementType):创建一个包含指定枚举类里所有枚举值的EnumSet集合。

(2)static EnumSet  complementOf(EnumSet s):创建一个其元素类型与指定EnumSet里元素类型相同的EnumSet,新EnumSet集合包含原EnumSet集合所不包含的、此枚举类剩下的枚举值。

(3)static EnumSet  copyOf(Collection c):使用一个普通集合来创建EnumSet集合。

(4)static EnumSet  copyOf(EnumSet s):创建一个与指定EnumSet具有相同元素类型、相同集合元素的EnumSet。

(5)static EnumSet  noneOf(Class elementType):创建一个元素类型为指定枚举类型的空EnumSet。

(6)static EnumSet  of(E first,E...rest):创建一个包含一个或多个枚举值的EnunSet,传入的多个枚举值必须属于同一个枚举类。

(7)static EnumSet  range(E from,E to):创建包含从from枚举值,到to枚举值范围内所有枚举值的EnumSet集合。

经典实例:

public class EnumSetTest {

	public static void main(String[] args) {
		EnumSet<Season> es=EnumSet.allOf(Season.class);
		System.out.println(es);//打印[SPRING, SUMMER, FAILL, WINTER]
		EnumSet<Season> es1=EnumSet.of(Season.SPRING,Season.SUMMER,Season.WINTER);
		System.out.println(es1);//打印[SPRING, SUMMER, WINTER]
		EnumSet<Season> es2=EnumSet.range(Season.SUMMER, Season.WINTER);
		System.out.println(es2);//打印[SUMMER, FAILL, WINTER]
		EnumSet<Season> es3=EnumSet.complementOf(es2);
		System.out.println(es3);//打印[SPRING]
	}
}

enum Season{
	SPRING,SUMMER,FAILL,WINTER
}

总结

HashSet和TreeSet是Set的两个典型实现,HashSet的性能总比TreeSet好(添加,查询操作),因为TreeSet需要额外的红黑树算法来维护集合元素的次序。TreeSet是有序的集合。

HashSet还有一个子类LinkedHashSet,对于普通的插入,删除操作,LinkedHashSet比HashSet要慢一点点,这是因为维护链表所带来的开销。不过遍历集合时,LinkedHashSet就非常块 。

EnumSet是所有Set实现类中性能最好 ,但它只能保存同一个枚举类的枚举值作为集合元素。

注意:Set的三个实现类HashSet 、TreeSet、EnumSet都是线程不安全的。如果有多条线程同事访问一个Set集合,并且有超过一条线程修改集合的值,则必须手动保证集合的同步性,否则将无法访问修改后的集合。


List接口

List集合代表一个有序的集合 ,集合中每个元素都有其对应的顺序索引。List集合允许使用重复元素,通过索引来访问指定位置的集合元素。

List集合在Collection的基础上新添加的方法:

(1)void  add (int index,Object element):将元素element插入在List集合的index处。

(2)boolean  addAll(int index,Collection c):将集合c所包含的所有元素都插入在List集合的Index处。

(3)Object  get(int index):返回集合index索引处的元素。

(4)int  indexOf(Object o):返回对象o在List集合中出现的位置索引。

(5)int  lastIndexOf(Object o):返回对象o在List集合中最后一次出现的位置索引。

(6)Object  remove(int index):删除并返回index索引处的元素。

(7)Object  set(int index,Object element):将index索引处的元素替换成element对象,返回新元素。

(8)List subList(int fromIndex,int toIndex):返回从索引fromIndex(包含)到索引toIndex(不包含)处所有集合元素组成的子集合。


ListIterator接口:

ListIterator接口是Iterator的子接口,提供了专门操作List的方法。

ListIterator接口新增的方法:

(1)boolean  hasPrevious():返回该迭代器关联的集合是否还有上一个元素。

(2)Object  previous():返回该迭代器的上一个元素。

(3)void  add():在指定位置插入一个元素。

根据上面的三个方法和解释,不难发现ListIterator新增加了向前迭代的功能,而且ListIterator还可以通过add方法向List集合中添加元素。

经典实例:

public class ListTest {

	public static void main(String[] args) {
		List<String> list = new ArrayList<String>();
		list.add("smarhit");
		list.add("heima");
		list.add("sichuan");
		list.add("chengdu");
		ListIterator<String> lit = list.listIterator();
		while (lit.hasNext()) {
			System.out.print(lit.next()+"\t");
		}
		System.out.println("\n----------开始反向迭代--------------");
		while (lit.hasPrevious()) {
			System.out.print(lit.previous()+"\t");
		}
		/*
		 * 打印的结果是:
		 * smarhit heima sichuan chengdu
		 *  ----------开始反向迭代--------------
		 * chengdu sichuan heima smarhit
		 */

	}
}

ArrayList和Vector实现类

ArrayList和Vector类都是基于数组实现的List类,ArrayList和Vector类封装了一个动态在分配的Object[ ]数组。每个ArrayList或Vector对象有一个capacity属性,该属性表示他们所封装的数组的长度,当想ArrayList或Vector中添加元素时,它们的capacity会自动增加。对于通常编程中,程序员无须关心ArrayList或Vector的capacity属性。但如果向ArrayList集合或Vector集合中添加大量元素时,可使用ensureCapacity方法一次性的增加capacity。这样可以提高性能。

ArrayList和Vector在用法上几乎完全相同(Vector是古老的集合,JDK1.0就存在了),但ArrayList集合是线程不安全的,当多条线程访问同一个集合时,如果有超过一条线程修改了ArrayList集合,则程序必须手动保证该集合的同步性。而Vector集合则是线程安全的。无须程序保证集合的同步性。所以Vector的性能比ArrayList的性能低。

Vector还提供了一个Stack子类,它用于模拟了“栈”这中数据结构,“栈”通常是指“后进先出”的容器。最后“push”进栈的元素,将最先被“pop”出栈。

Stack类提供的方法:

(1)Object  peek():返回“栈”的第一个元素,但并不将该元素"pop"出栈。

(2)Object  pop():返回"栈"的第一个元素,并将该元素"pop"出栈。

(3)void  push(Object item):将一个元素“push”进栈,最后一个进“栈”的元素总是位于“栈”顶。

经典实例:

public class StackTest {
	
	public static void main(String[] args) {
		Stack<String> s=new Stack<String>();
		s.push("smarhit");
		s.push("beijin");
		s.push("neijiang");
		s.push("chengdu");
		System.out.println(s);//打印[smarhit, beijin, neijiang, chengdu]
		System.out.println(s.peek());//打印chengdu
		System.out.println(s);//打印[smarhit, beijin, neijiang, chengdu]
		System.out.println(s.pop());//打印chengdu
		System.out.println(s);//打印[smarhit, beijin, neijiang]
	}
}

固定长度的List

数组的工具类 Arrays提供的asList()方法是把一个数组或指定个数的对象转换成一个List集合, 但这个List集合既不是ArrayList实现类的实例,也不是Vector实现类的实例,而是Arrays的内部类ArrayList实例。Arrays.ArrayList是一个固定长度的List集合,程序只能遍历访问该集合里的元素,不可增加,删除该集合中的元素。

Queue接口

Queue模拟了队列这中数据结构,队列通常是"先进先出"的容器。

Queue接口提供的方法:

(1)void  add(Object e):将指定元素加入此队列的尾部。

(2)Object  element():获取队列头部的元素,但不是删除该元素。

(3)boolean  offer(Object e):将指定元素加入此队列的尾部。当使用有容器限制的队列时,此方法通常比add(Object e)方法更好。

(4)Object  peek():获取队列头部的元素,但是不删除该元素。如果此队列为空,则返回null。

(5)Object  poll():获取队列头部的元素,并删除该元素。如果此队列为空,则返回null。

(6)Object  remove():获取队列头部元素,并删除该元素。


LinkedList实现类:

LinkedList类是一个比较特殊的类,它实现了List接口,还实现了Deque接口,Deque接口是Queue接口的子接口,它代表一个双向队列。

LinkedList类提供的方法:

(1)void  addFirst(Object e):将指定元素插入该双向队列的开头。

(2)void addLast(Object e):将指定元素插入该双向队列的尾部。

(3)Iterator descendingIterator():返回以该双向队列对应的迭代器,该迭代器将以逆向顺序来迭代队列中的元素。

(4)Object  getFirst():获取、但不删除双向队列的第一个元素。

(5)Object  getLast():获取、但不删除双向队列的最后一个元素。

(6)boolean  offerFirst(Object e):将指定的元素插入该双向队列的开头。

(7)boolean  offerLast(Object e):将指定的元素插入该双向队列的尾部。

(8)Object   peekFirst():获取、但不删除该双向队列的第一个元素;如果此双向队列为空,则返回null。

(9)Object   peekLast():获取、但不删除该双向队列的最后一个元素;如果此双向队列为空,则返回null。

(10)Object  pollFirst():获取、并删除该双向队列的第一个元素;如果此双向队列为空,则返回null。

(11)Object  pollLast():获取、并删除该双向队列的最后一个元素;如果此双向队列为空,则返回null。

(12)Object  pop():pop出该双向队列所表示的栈中第一个元素。

(13)void  push(Object e):将一个元素push进该双向队列所表示的栈中。

(14)Object  removeFirst():获取、并删除该双向队列的第一个元素。

(15)Object  removeFirstOccurrence(Object o):删除该双向队列的第一个出现元素o。

(16)removeLast():获取、并删除该双向队列的最后一个元素。

(17)removeLastOccurrence(Object  o):删除该双向队列的最后一次出现元素o。

总结:LinkedList与ArrayList、Vector的实现机制完全不同。ArrayList、Vector内部以数组的形式来保存集合中的元素,因此随机访问集合元素上有较好的性能;而linkedList内部以链表的形式来保存集合中的元素,因此随机方法集合元素时性能较差,但在插入、删除元素时性能非常出色(只需改变指针所指的地址即可)。


PriorityQueue实现类

PriorityQueue是一个比较标准的队列实现类,PriorityQueue保存队列元素的顺序并不是按加入队列的顺序,而是按队列元素的大小进行重新排序。

PriorityQueue不允许插入null元素,它需要对队列元素进行排序。

排序的方式:自然排序,定制排序。

上面是对List集合的详解总结。在开发过程中,我们可能只运用到一些常见的结合,但其他集合也应做相应的了解。


Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐