在编程过程中,会很频繁的使用集合,集合的相关知识也非常重要,也是每一个开发人员必须掌握的知识。
一:集合的概念
集合:保存数量不确定的数据,以及保存具有映射关系的数据的容器,简单的理解就是用于存储数量不等的多个对象的容器。
集合和数组不一样,数组元素既可以是基本类型的值,也可以是对象(对象的引用变量);而集合里只能保存对象(对象的引用变量)。
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集合的详解总结。在开发过程中,我们可能只运用到一些常见的结合,但其他集合也应做相应的了解。