集合概述
java集合就像一种容器,我们可以把多个对象丢进该容器中。在编程时,我们需要存放多个数据,当然我们可以使用数组来保存多个对象,但是数组的长度不可变化,且无法保存具有映射关系的数据。集合类主要负责保存,盛装其他数据,因此集合类也被成为容器类。
集合类和数组不一样,数组即可以保存基本类型,也可也保存对象;而集合里保存的只能是对象。java的集合类主要由两个接口派生而出:Collection和Map。
Collection
collection有3个派生出来的子接口,分别是Set、Queue和List。Set与List接口分别代表了无序集合与有序集合。Queue时java提供的队列的实现。由于Collection是父接口,所以该接口定义的方法可同时用于Set、List和Queue。详细方法请查询JAVA API。
Tips1:当使用println方法来输出集合对象时,将输出[a,b,c,…..]形式,因为Collection实现类都重写了toString()方法,该方法可以一次性输出集合中的所有元素。
关于Collection遍历的通用方法:
Iterator迭代器
Iterator接口也是Java集合框架的成员,但是它与Collection与Map的集合不一样,Collection与Map主要用于盛装其他对象,而Iterator主要用于遍历(即迭代访问)Collection中的元素。
Iterator接口定义3个方法,hasNext:如果被迭代的集合元素还没有被遍历返回true、next:返回集合的下一个元素、remove:删除集合里上一次next返回的元素。
//创建一个集合
Collection collection = new HashSet();
//获取结合对应的迭代器
Iterator it = collection.iterator()
while(it.hasNext())
{
//tips:next()方法返回Object类型,需进行类型转换
String string = (String)it.next();
//从集合中删除上一次next方法返回的元素
it.remove();
}
若上述代码对变量string进行赋值,当再次遍历集合时,集合里的值不会发生改变。因为Iterator对集合进行迭代时,并不把元素本身传给了迭代变量,而是把集合元素的值给了迭代变量,所以修改迭代变量对于集合元素本身没有任何影响。
当Iterator迭代访问Collection集合元素时,Collection元素不能被改变,只有通过remove方法删除元素才可以,否则会引发java.util.ConcurrentModificationException异常。Iterator采用了快速失败(fail-fast)机制,一旦在迭代中检测到该集合已经被修改,程序立即引发ConcurrentModificationException异常,这样可以避免资源共享而引发的问题。
Collection中所有Iterator的实现都是按fail-fast来设计的(ConcurrentHashMap和CopyOnWriteArrayList这类并发集合类除外)。
Java.util包中的所有集合类都被设计为fail-fast的,而java.util.concurrent中的集合类都为fail-safe的。Fail-fast迭代器抛出ConcurrentModificationException,而fail-safe迭代器从不抛出ConcurrentModificationException。
为何Iterator接口没有具体的实现?
Iterator接口定义了遍历集合的方法,但它的实现则是集合实现类的责任。每个能够返回用于遍历的Iterator的集合类都有它自己的Iterator实现内部类。
这就允许集合类去选择迭代器是fail-fast还是fail-safe的。比如,ArrayList迭代器是fail-fast的,而CopyOnWriteArrayList迭代器是fail-safe的。
Tips2:Iterator必须依附于Collection对象,若有一个Iterator对象,则必然有一个与之关联的Collection对象。
foreach遍历
Collection collection = new HashSet();
for(Object obj : collection)
{
//读取集合元素
}
foreach循环中的迭代变量也不是元素本身,修改foreach元素中的迭代遍历也没有什么意义。当使用foreach循环迭代集合元素时,该集合也不能被改变,否则也会引发ConcurrentModificationException异常。
Set集合
Set集合中多个对象之间没有明显的顺序,Set集合与Collection集合基本完全一样,没有提供额外的方法,只是不允许包含相同的元素。Set判断两个对象相同不是使用==而是使用equals方法。
HashSet
HashSet使用Hash算法来存储集合中的元素,具有良好的存取和查找性能。特点:
- 不能够保证元素顺序;
- HashSet是非同步的;
- 集合元素值可以为null;
当HashSet集合存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,根据HashCode的值决定对象的存储位置。HashSet通过判断两个对象的equals与hashCode方法,两者都相等才判断为两个元素相等。
那么这里就有两种情况:
(1)equals相同、hashcode不同:当两个元素通过equals方法返回true但HashCode值不等,HashSet可以把它们添加在不同的位置。但这就与Set集合的规则有出入了。
(2)hashcode相同,equals不同:因为两个对象的hashcode值相同,HashSet将它们保存在同一位置,使用链式结构保存,而HashSet根据hashcode快速定位查找元素,两个对象拥有相同的hashcode会导致性能下降。
LinkedHashSet
LinkedHashSet是HashSet的子类,LinkedHashSet也是根据HashCode定位元素的存储位置,但它使用链表来维护元素次序。LinkedHashSet需要维护元素的插入顺序,性能略低于HashSet,但是在迭代全部元素时,拥有良好的性能,因为内部元素有序。
TreeSet
TreeSet是SortedSet的实现类,TreeSet可以确保元素处于排序状态。TreeSet采用红黑树的数据结构来存储集合元素。相比HashSet它提供了一些额外的与排序有关的方法。因为TreeSet是有序的,所以增加了访问第一个,前一个、后一个、最后一个元素的方法。
- 自然排序
TreeSet会调用集合元素的compareTo方法比较元素大小,按元素升序排列,这就是自然排序。
Java提供了一个Compare的接口,该接口定义了一个compareTo(Object obj)的方法,该方法返回一个整数值,0:两个对象相等;正整数:obj1大于obj2,;负整数:obj1小于obj2.
obj1.compareTo(obj2);
Tips3:如果把一个对象添加到TreeSet,该对象必须实现Comparable接口,否则程序会抛出异常
- 定制排序
如果需要集合的元素按照自定义的顺序排序,则需要通过Compatator接口,在创建TreeSet时创建一个匿名内部类对象,该对象负责元素的排序逻辑。
TreeSet treeSet = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
// TODO Auto-generated method stub
return 0;
}
});
EnumSet
EnumSet是一个专为枚举类设计的集合类,EnumSet中所有的元素都必须是指定枚举类的枚举值。EnumSet在内部以位向量的形式存储,这种存储形式紧凑高效。EnumSet也不允许加入null元素。
各Set实现类的性能分析
HashSet与TreeSet是Set的两个典型实现。HashSet的性能总是比TreeSet好,特别是常用的添加与查询操作,只有当需要一个Set有序时,才使用TreeSet,否则都应该使用HashSet。
LinkedHashSet对于普通的插入和删除操作要比HashSet稍微慢一点,但是由于有了链表,LinkedHashSet的遍历会更快。
EnumSet是所有Set实现类中性能最好的。
Tips4:HashSet、TreeSet、EnumSet与LinkedHashSet都是县城不安全的。
List集合
List是一个可重复、有序的集合,集合中每个元素都有其对应的顺序索引。List作为Collection的子接口,可以使用Collection的全部方法并增加了一个根据索引操作List的方法。List判断两个对象相等是根据equals方法。调用List的remove方法,若集合中有连个相同的对象,总是会删除第一个。
List还额外提供了一个ListIterator()方法,该方法继承了Iterator接口,专门操作List。ListIterator在Iterator的基础上增加了返回上一个元素与插入等相关方法。
Arrarys工具类中提供了一个能将数组或者指定数量对象转化为List集合的方法:asList()。
ArraryList与Vector
ArraryList与Vector都实现了List类,使用initialCapacity参数来设置该数组的长度,当向ArraryList与Vector中添加元素超出了该数组长度时,initialCapacity会自动增加。对于通常的场景,无需关心ArraryList与Vector的initialCapacity,但如果向ArraryList与Vector中添加大量元素时,可使用ensureCapacity(int minCapacity)方法一次行的增加initialCapacity,减少分配次数,提高性能。如果创建空的ArraryList与Vector时不指定initialCapacity参数,则数组长度默认为0。
ArraryList与Vector相比,ArraryList是线程不安全的。
Vector还提供了stack的子类,但是一般较少使用,一般使用LinkedList替代。
Queue集合
Queue用于模拟队列数据结构,先进先出FIFO。
PriorityQueue
PriorityQueue是Queue的一个实现类。PriorityQueue保存队列元素顺序并不是按照加入队列的顺序,而是按照队列元素大小进行重新排序的。所以PriorityQueue不允许插入null元素。它对于队列元素的要求基本与TreeSet一致。
Deque与ArraryDeque
Deque是Queue的子接口,它代表一个双向队列。ArraryDeque是它的实现类。
LinkedList
LinkedList是一个List接口的实现类,但是它还实现了Deque接口,因此它是一个List集合,也可以被当做双端队列来使用,当然模拟栈也可以。
LinkedList的实现机制与ArraryList、ArraryDeque完全不同,ArraryList、ArraryDeque使用数组来保存集合中的元素,因此拥有较好的随机访问性能;LinkedList使用链表实现,在删除与插入时性能优秀。
各线性表性能分析
类型 | 实现机制 | 随机访问排名 | 迭代操作排名 | 插入操作排名 | 删除操作排名 |
---|---|---|---|---|---|
数组 | 连续内存区保存元素 | 1 | 不支持 | 不支持 | 不支持 |
ArraryList/ArraryDeque | 以数组保存元素 | 2 | 2 | 2 | 2 |
Vector | 以数组保存元素 | 3 | 3 | 3 | 3 |
LinkedList | 以链表保存元素 | 4 | 1 | 1 | 1 |
List使用建议:
- 如需遍历List元素,对于ArraryList与Vector集合应使用随机访问的方法,LinkedList应采用迭代器的方法来遍历。
- 需要经常插入与删除的List,应使用LinkedList而不是ArraryList。
- 如果需多线程使用,应使用Collections进行包装。
Collection的同步控制
Collections类中提供了多个synchronized的方法,这些方法可以将指定的集合包装成为线程安全的集合,解决多线程的安全问题。