一、集合类简介与框图
数组是很常见的一种数据结构,我们用它可以满足很多的功能,但是,有时我们会遇到如下问题:
1、我们需要该容器的长度是不确定的。
2、我们需要它能自动排序。
3、我们需要存储以键值对方式存在的数据。
如果遇到上述的情况,数组是很难满足需求的,java集合类是一种特别有用的工具类,可用于存储数量不等的对象,并可以实现常用的数据结构。
java集合类继承树:
1.遍历集合元素
1.1调用Iterable(迭代器)中方法遍历集合
Iterable(迭代器)接口是Collection接口的父接口,主要用于遍历Collection集合中的元素,本身不能提供承装对象的能力,Iterator必须依附于Collection对象;Iterator并不是把集合元素本身传给迭代变量,而是把集合元素的值传递给迭代变量;当使用Iterator迭代访问Collection集合元素时,Collection集合里的元素不能被改变,否则会产生ConcurrentModificationException异常。
boolean hasNext() | 如果被迭代集合元素没遍历完,返回true |
Object next() | 返回集合里的下一个元素 |
void remove() | 删除集合里上一次next方法返回的元素 |
void forEachRemaining(Consumer action) | 可使用Lambda表达式来遍历集合元素,Consumer是函数式接口 |
1.2使用foreach循环遍历集合元素
import java.util.Collection;
import java.util.HashSet;
public class foreachTest
{
public static void main(String arg[])
{
Collection weeks= new HashSet();
weeks.add("星期一");
weeks.add("星期二");
weeks.add("星期三");
weeks.add("星期四");
weeks.add("星期五");
for(Object obj:weeks)
{
String week=(String)obj;
System.out.println(week);
}
}
}
1.2操作集合
1.2.1使用Collection接口中定义的操作集合元素的方法操作集合。(具体见java的API文档)
1.2.2使用Java8新增的Predicate操作集合
removelf(Predicate filter)将会批量删除符合filter条件的所有元素。Predicate是对象也是函数式接口
1.2.3使用Java8新增的Stream操作集合
基本步骤:
(1)使用Stream的bulider()类创建该Stream对应的Bulider
(2)重复调用Builder的add()方法向该流中添加多个元素
(3)调用Builder的build()方法获取对应的Stream
(4)调用Stream的聚合方法
2.Collection接口,子接口及其实现类的继承树:
接口:
Collection接口 | Collection是最基本的集合接口,所有实现Collection接口的类都必须提供两个标准的构造函数:无参数的构造函数用于创建一个空的Collection,有一个 Collection参数的构造函数用于创建一个新的Collection,这个新的Collection与传入的Collection有相同的元素。后一个构造函数允许用户复制一个Collection。 |
Iterator接口 | Iterator(迭代器)接口是Collection接口的父接口,主要用于遍历Collection集合中的元素 |
Set接口 | Set是一种不包含重复的元素的Collection,即任意的两个元素e1和e2都有e1.equals(e2)=false,Set最多有一个null元素。 |
Queue接口 | 通常与泛型配合使用 |
List接口 | List是有序的Collection,使用此接口能够精确的控制每个元素插入的位置。用户能够使用索引(元素在List中的位置,类似于数组下标)来访问List中的元素,这类似于Java的数组。 |
Map接口 | Map没有继承Collection接口,Map提供key到value的映射。一个Map中不能包含相同的key,每个key只能映射一个 value。Map接口提供3种集合的视图,Map的内容可以被当作一组key集合,一组value集合,或者一组key-value映射。 |
2.1set集合(不允许包含重复元素)
2.1.1 set实现类
TreeSet类 | 保证集合元素处于排序状态,采用红黑树的数据结构存储元素集合,支持自然排序和定制排序两种排序方法,默认采用自然排序。 |
HashSet类 | 根据hashCode值决定对象在HashSet中的存储位置;集合元素值可以为null;不能保证元素排列顺序;不是同步的; |
LinkHashSet类 | 根据hashCode值决定元素存储位置,但使用链表维护元素次序 |
EnumSet类 | EnumSet中的所有元素都必须是指定枚举类型的枚举值,该枚举类型在创建时显示或隐式地指定;对象占用内存小,运行效率高;不允许加入null元素 |
2.1.2自然排序和定制排序
自然排序:调用集合元素的compareTo(Object obj)方法来比较元素之间的大小关系,然后将集合元素按照升序排列。Obj1.compareTo(Obj2)返回0,则表明这两个对象相等。返回正数,则表明Obj1大于Obj2,尽量不要修改放入TreeSet和HashSet集合中元素的关键实例变量。
定制排序:需要在创建TreeSet集合时,提供一个Comparator对象与该TreeSet集合关联,由该Comparator对象负责集合元素的排序逻辑。Comparator也是一个函数式接口。
2.1.3 各set 实现类的性能分析
(1)HashSet 性能总比TreeSet 好(特别是添加、查询等操作),TreeSet 需要额外红黑树算法来维护集合元素的次序,只有当需要一个保持排序的Set时才该使用TreeSet,否则都使用HashSet.;
(2)对于普通的插入,删除操作,LinkedHashSet比HashSet略慢,但遍历时LinkedHashSet会更快;
(3)EnumSet是所有set中性能最好的。
2.2 List集合
2.2.1List实现类
ArrayList类 | ArrayList实现了可变大小的数组。它允许所有元素,包括null。ArrayList没有同步 |
Vetctor类 | Vector非常类似ArrayList,但是Vector是同步的 |
LinkedList类 | LinkedList实现了List接口,允许null元素。此外LinkedList提供额外的get,remove,insert方法在 LinkedList的首部或尾部。这些操作使LinkedList可被用作堆栈(stack),队列(queue)或双向队列(deque),没有同步,基于线性表实现 |
Static类 | Stack继承自Vector,实现一个后进先出的堆栈。Stack提供5个额外的方法使得Vector得以被当作堆栈使用,Stack刚创建后是空栈 |
2.2.2使用List集合建议
(1)如果需要遍历List集合元素,对于ArrayList、Vector集合使用随机方法(get)来遍历集合元素;对于LinkedList集合,则采用迭代器(Iterator)来遍历集合;
(2)如果需要经常执行插入、删除操作来改变包含大量数据的List集合的大小,可考虑使用LinkedList集合;
(3)如果有多个线程需要同时访问List集合中的元素时,考虑使用Collection将集合包装成线程安全的集合。
2.3Queue集合
3.Map体系的继承树:
3.1Map实现类
- TreeMap:键以某种排序规则排序,内部以red-black(红-黑)树数据结构实现,实现了SortedMap接口,具体可参《RED-BLACK(红黑)树的实现TreeMap源码阅读 》
- HashMap: 以哈希表数据结构实现,查找对象时通过哈希函数计算其位置,它是为快速查询而设计的,其内部定义了一个hash表数组(Entry[] table),元素会通过哈希转换函数将元素的哈希地址转换成数组中存放的索引,如果有冲突,则使用散列链表的形式将所有相同哈希地址的元素串起来,可能通过查看HashMap.Entry的源码它是一个单链表结构。
- Hashtable:也是以哈希表数据结构实现的,解决冲突时与HashMap也一样也是采用了散列链表的形式,不过性能比HashMap要低。
- LinkedHashMap:继承HashMap,内部实体LinkedHashMap.Entry继承自HashMap.Entry,LinkedHashMap.Entry在HashMap.Entry的基础上新增了两个实体引用(Entry before, after),这样实体可以相互串链起来形成链,并且在LinkedHashMap中就定义了一个头节点(Entry header)用来指向循环双向链的第一个元素(通过after指向)与最后一个元素(通过before指向)。在添加一个元素时,先会通过父类HashMap将元素加入到hash表数组里,然后再会在链尾(header.before指向位置)添加(当然这一过程只是调整LinkedHashMap.Entry对象内部的before, after而已,而不是真真创建一个什么新的链表结构向里加那样);删除先从hash表数组中删除,再将被删除的元素彻底的从双向链中断开。其实在链中添加与删除操作与LinkedList是一样的,可以参考《Java集合框架之LinkedList及ListIterator实现源码分析 》
3.2对Map的选择:
- Hashtable和HashMap的效率大致相同(通常HashMap更快一点,所以HashMap有意取代Hashtable)。
- TreeMap通常比HashMap慢,因为要维护排序。
- HashMap正是为快速查询而设计的。
- LinkedHashMap比HashMap慢一点,因为它维护散列数据结构的同时还要维护链表。
-
4.操作集合的工具类Collections:
排序操作,查找和替换操作,同步控制(HashSet,TreeSet,ArrayList,ArrayDeque,LinkedList,HashMap和TreeMap都是线程不安全的,直接将创建的集合对象传给Collections的synchronizedXxx方法,就可以直接获取List,Set和Map的线程安全实现版本)设置不可变集合(emptyXxx()返回一个空的,不可变的集合对象;singletonXxx()返回一个包含指定对象的,不可变的集合对象;unmodifiableXxx()返回指定集合对象的不可变视图)
1、集合中键值是否允许null小结
- List:可以有多个null,可以有重复值。
- HashSet:能插入一个null(因为内部是以 HashMap实现 ),忽略不插入重复元素。
- TreeSet:不能插入null (因为内部是以 TreeMap 实现 ) ,元素不能重复,如果待插入的元素存在,则忽略不插入,对元素进行排序。
- HashMap:允许一个null键与多个null值,若重复键,则覆盖以前值。
- TreeMap:不允许null键(实际上可以插入一个null键,如果这个Map里只有一个元素是不会报错的,因为一个元素时没有进行排序操作,也就不会报空指针异常,但如果插入第二个时就会立即报错),但允许多个null值,覆盖已有键值。
- HashTable:不允许null键与null值(否则运行进报空指针异常)。也会覆盖以重复值。基于线程同步。
- 刚实例化的迭代器如果还没有进行后移(next)操作是不能马上进行删除与修改操作的。
- 可以用ListIterator对集合连续添加与修改,但不能连续删除。
- 进行添加操作后是不能立即进行删除与修改操作的。
- 进行删除操作后可以进行添加,但不能进行修改操作。
- 进行修改后是可以立即进行删除与添加操作的。
3、当以自己的对象做为HashMap、HashTable、LinkedHashMap、HashSet 、LinkedHashSet 的键时,一定要重写hashCode ()与equals ()方法,因为Object的hashCode()是返回内存地址,且equals()方法也是比较内存地址,所以当要在这些hash集合中查找时,如果是另外new出的新对象是查不到的,除非重写这两个方法。因为AbstractMap类的containsKey(Object key)方法实现如下:
- if (e.hash == hash && eq(k, e.key))//先比对hashcode,再使用equals
- return true;
- static boolean eq(Object x, Object y) {
- return x == y || x.equals(y);
- }
String对象是可以准确做为键的,因为已重写了这两个方法。
因此,Java中的集合框架中的哈希是以一个对象查找另外一个对象,所以重写hasCode与equals方法很重要。 4、重写hashCode()与equals()这两个方法是针对哈希类,至于其它集合,如果要用public boolean contains(Object o)或containsValue(Object value)查找时,只需要实现equals()方法即可,他们都只使用对象的 equals方法进行比对,没有使用hashCode方法。 5、TreeMap/TreeSet:放入其中的元素一定要具有自然比较能力(即要实现java.lang.Comparable接口)或者在构造TreeMap/TreeSet时传入一个比较器(实现java.util.Comparator接口),如果在创建时没有传入比较器,而放入的元素也没有自然比较能力时,会出现类型转换错误(因为在没有较器时,会试着转成Comparable型)。
两种比较接口:
- //自然比较器
- public interface java.lang.Comparable {
- public int compareTo(Object o);
- }
- public interface java.util.Comparator {
- int compare(Object o1, Object o2);
- boolean equals(Object obj);
- }
6、Collection或Map的同步控制:可以使用Collections类的相应静态方法来包装相应的集合类,使他们具线程安全,如public static Collection synchronizedCollection (Collection c)方法实质返回的是包装后的SynchronizedCollection子类,当然你也可以使用Collections的synchronizedList、synchronizedMap、synchronizedSet方法来获取不同的经过包装了的同步集合,其代码片断:
- public class Collections {
- //...
- static Collection synchronizedCollection(Collection c, Object mutex) {
- return new SynchronizedCollection(c, mutex);
- }
- public static List synchronizedList(List list) {
- //...
- }
- static Set synchronizedSet(Set s, Object mutex) {
- //...
- }
- public static Map synchronizedMap(Map m) {
- return new SynchronizedMap(m);
- }
- //...
- static class SynchronizedCollection implements Collection, Serializable {
- Collection c; // 对哪个集合进行同步(包装)
- Object mutex; // 对象锁,可以自己设置
- //...
- SynchronizedCollection(Collection c, Object mutex) {
- this.c = c;
- this.mutex = mutex;
- }
- public int size() {
- synchronized (mutex) {
- return c.size();
- }
- }
- public boolean isEmpty() {
- synchronized (mutex) {
- return c.isEmpty();
- }
- }
- //...
- }
- static class SynchronizedList extends SynchronizedCollection implements List {
- List list;
- SynchronizedList(List list, Object mutex) {
- super(list, mutex);
- this.list = list;
- }
- public Object get(int index) {
- synchronized (mutex) {
- return list.get(index);
- }
- }
- //...
- }
- static class SynchronizedSet extends SynchronizedCollection implements Set {
- SynchronizedSet(Set s) {
- super(s);
- }
- //...
- }
- //...
- }
由包装的代码可以看出只是把原集合的相应方法放在同步块里调用罢了。
7、通过迭代器修改集合结构在使用迭代器遍历集合时,我们不能通过集合本身来修改集合的结构(添加、删除),只能通过迭代器来操作,下面是拿对HashMap删除操作的测试,其它集合也是这样:
- public static void main(String[] args) {
- Map map = new HashMap();
- map.put(1, 1);
- map.put(2, 3);
- Set entrySet = map.entrySet();
- Iterator it = entrySet.iterator();
- while (it.hasNext()) {
- Entry entry = (Entry) it.next();
- /*
- * 可以通过迭代器来修改集合结构,但前提是要在已执行过 next 或
- * 前移操作,否则会抛异常:IllegalStateException
- */
- // it.remove();
- /*
- * 抛异常:ConcurrentModificationException 因为通过 迭代 器操
- * 作时,不能使用集合本身来修
- * 改集合的结构
- */
- // map.remove(entry.getKey());
- }
- System.out.println(map);
- }
public class foreachTest
{
public static void main(String arg[])
{
Collection weeks= new HashSet();
weeks.add("星期一");
weeks.add("星期二");
weeks.add("星期三");
weeks.add("星期四");
weeks.add("星期五");
for(Object obj:weeks)
{
String week=(String)obj;
System.out.println(week);
}
}
}