Java Collections Framework
集合的概念
集合collection,有时叫做容器container,把多个元素组成一个单元。
早期的Java (pre-1.2) 中包含了Vector, Hashtable, 和array,但是没有包含一个统一的集合框架。
Java Collections Framework是一个统一的框架,为了表现和操纵集合。
所有的collections frameworks包括:接口(Interfaces)、实现(Implementations)和算法(Algorithms)。
泛型和类型安全的容器
使用Java SE5之前的容器的一个主要问题就是编译器允许你向容器中插入不正确的类型。
比如ArrayList,用add()插入对象,因为ArrayList保存的是Object,所以可以添加各种类型的对象,在编译期和运行时都不会产生问题。
当你用get()方法取出对象时,得到的只是Object类的引用,必须使用强制类型转换将其转换为特定的类型。在运行时,当你试图将一个类的对象转换为另一个类的对象时,就会得到一个异常。
可以在容器的尖括号中包含类型参数,指定容器实例可以保存的类型。
通过使用泛型,就可以在编译期防止将错误类型的对象放置到容器中。
这样的另外一个好处是,在将元素取出的时候,类型转换也不再是必须的了。因为容器知道它保存的是什么类型,因此它会在调用get()时替你执行转型。
接口
核心的集合接口封装了不同类型的集合。如下图所示:
核心集合接口
所有的核心接口都是泛型的,比如Collection接口:
public interface Collection<E>
<E>说明声明实例的时候需要指明对象类型,这样编译器就可以在编译期进行一个类型检查,减少运行时错误。
Collection
一个Collection代表了一组对象,这些对象被称为其元素(elements)。
这个接口提供了一些方法,告诉你有多少元素(size, isEmpty),检查一个对象是否包含在集合里(contains),为集合增加或者减少元素(add, remove),或者为集合提供迭代器(iterator)。
Set
一个不包含重复元素的集合。这个接口对数学中的集合的抽象概念进行了建模。
List
List定义一个有顺序的集合,有时也被称作一个序列(sequence)。List可以包含重复的元素。
Queue
通常,Queue中的元素都是按照FIFO (first-in-first-out)的方式,但是这不是必须的。一些优先级队列元素,按照它们的值排列。但是不管使用了什么排序方式,调用remove或poll方法时,移除的都是队列头元素。在FIFO队列中,新元素总是被插在队尾,但是其他类型的队列可能会使用不同的排列方式。
Map
Map中存储的是键值对的映射。一个Map不能包含重复的key,一个key可以对应至多一个值。
SortedSet和SortedMap
还有两个核心的接口是排序版本的Set和Map:SortedSet和SortedMap。SortedSet的元素是按照升序排列的,SortedMap是按照键的升序排列键值对的。
实现 (Implementations)
通用目的的实现:
Interfaces | Set | List | Map |
Hash table Implementations | HashSet |
| HashMap |
Resizable array Implementations |
| ArrayList |
|
Tree Implementations | TreeSet |
| TreeMap |
Linked list Implementations |
| LinkedList |
|
Hash table + Linked list Implementations | LinkedHashSet |
| LinkedHashMap |
Java容器类类库的用途是“保存对象”,并将其划分为两个不同的概念:
1.Collection
一个独立元素的序列,这些元素都服从一条或者多条规则。
List必须按照插入的顺序保存元素,而Set不能有重复元素。Queue按照排队规则来确定对象产生的顺序(通常与它们被插入的顺序相同)。
有两种类型的List:
ArrayList,它长于随机访问元素,但是在List的中间插入和删除元素时较慢。
LinkedList,它在随机访问方面相对比较慢,但是它进行插入和删除代价较低。
Set:
HashSet、TreeSet、和LinkedHashSet都是Set,每个相同的项只保存一次。
HashSet是最快的获取元素方式,存储的顺序看起来并无实际意义;TreeSet按照比较的结果升序保存对象;LinkedHashSet按照被添加的顺序保存对象。
2.Map
一组成对的“键值对”对象,允许你使用键来查找值。
映射表允许我们使用另一个对象来查找某个对象,它也被称为“关联数组”,因为它将某些对象与另外一些对象关联在了一起,或者被称为“字典”。
HashMap、TreeMap、和LinkedHashMap都是Map。
HashMap提供了最快的查找技术,也没有按照任何明显的顺序来保存其元素。
TreeMap按照比较结果的升序保存键。
LinkedHashMap则按照插入顺序保存键,同时还保留了HashMap的查询速度。
略去一些类和接口,一个简化版的类图关系如下:
算法
Collections
类提供了很多可复用的算法,它们都是静态方法的形式。
有代表性的几个算法:
排序Sorting
排序算法将集合重新排序,按照升序排列。
有两种形式:
sort(List<T> list)和sort(List<T> list, Comparator<? super T> c),前者按照元素的自然顺序排列,后者按照参数提供的具体的比较器进行排列。
关于排序顺序可见:
http://docs.oracle.com/javase/tutorial/collections/interfaces/order.html
排序算法使用的是归并算法(merge sort),因为它快速并且稳定(不会交换相等元素的位置),快速排序不稳定,并且也不能保证log(n)的效率。
洗牌Shuffling
shuffle算法打乱元素的顺序,制造一种随机无序的结果。
shuffle算法也有两种形式,一种使用默认的随机源,另一种使用用户传入的随机源(a source of randomness)。
常规数据操作Routine Data Manipulation
reverse:将List中的元素顺序反转。
fill:将每一个元素都用指定的值填充。
copy:拷贝源数据到目标数据List,目标List必须更长,这样目标List中前半段元素将被覆写,剩下的元素保持原状。
swap:交换List中两个指定位置的元素。
addAll:把指定的元素全都加入到指定的集合中去。
查找Searching
binarySearch算法在一个已经有序的List中用二分法查找一个特定的元素。
这个算法有两种形式,第一种传入一个List和一个指定的元素,这种形式假定这个List是按照其自然顺序的升序排列的。第二种形式还需要多传入一个比较器Comparator,并且假定List已经按照该比较器进行过排序,为升序。可以在binarySearch方法调用前先调用sort算法。
如果查找成功,返回索引,如果不成功,返回值是(-(insertion point) - 1),插入点(insertion point)是这个值应该插入的地方。这样就保证了如果查找成功,那么返回值是大于等于0的。
下面这个形式可以将查找失败的元素插入集合:
int pos = Collections.binarySearch(list, key); if (pos < 0) l.add(-pos-1);
组成Composition
对于集合的构成查询也存在一些方法:
frequency:计算某个特定元素在集合中出现的次数。
disjoint:判断两个集合是否分离,即它们是不是不含公共元素。
找极值Finding Extreme Values
min和max方法分别返回集合中的最小和最大元素。
它们都有两种形式,一种形式仅传入集合,然后根据元素的自然顺序返回极值;另一种形式需要再传入一个比较器Comparator,根据比较器设定的规则来返回极值。
参考资料
Java SE Documentation: The Collections Framework
http://docs.oracle.com/javase/7/docs/technotes/guides/collections/index.html
The Java Tutorials: Lesson: Interfaces
http://docs.oracle.com/javase/tutorial/collections/interfaces/index.html
The Java Tutorials: Lesson: Implementations
http://docs.oracle.com/javase/tutorial/collections/implementations/index.html
The Java Tutorials: Lesson: Algorithms
http://docs.oracle.com/javase/tutorial/collections/algorithms/index.html
--------------------------
集合中存放的依然是对象的引用而不是对象本身。
集合中存放的不能是原生数据类型,需要使用原生数据类型的包装类。
当遍历集合中的元素时,可以使用迭代器。
Collections这个类中包含了一系列的静态方法,用于操纵集合或者返回集合。
ArrayList和LinkedList的比较
ArrayList和LinkedList类都是List接口的实现类。
ArrayList底层采用数组实现,LinkedList底层采用双向链表实现。
插入、删除操作,LinkedList的效率更高。
当执行搜索操作时,ArrayList比较好。
当向ArrayList添加一个对象时,实际上就是将该对象放置到了ArrayList底层所维护的数组当中;当向LinkedList中添加一个对象时,实际上LinkedList内部会生成一个Entry对象,该Entry对象的结构为:
Entry
{
Entry previous;
Object element;
Entry next;
}
其中的Object类型的元素element就是我们向LinkedList中所添加的元素,然后Entry又构造好了向前与向后的引用previous、next,最后将生成的这个Entry对象加入到了链表当中。
换句话说,LinkedList中所维护的是一个个的Entry对象。
Set接口
实现类HashSet
元素是无序的,且不允许重复。
当使用add()方法把元素加入到Set中去时,会返回一个布尔值。当集合中不存在这个元素时,元素加入,元素加入成功时,返回true; 当集合中已经存在相同元素时,不加入当前元素,add()方法返回false。
当使用HashSet时,hashCode()方法就会得到调用,判断已经存储在集合中的对象的hash code值是否与增加的对象的hash code值一致;如果不一致,直接加进去;如果一致,再进行equals()方法的比较,equals()方法如果返回true,表示对象已经加进去了,就不会增加新的对象,否则加进去。
equals()方法和hashCode()方法都是Object类中的方法,如果没有进行覆写,则使用Object类中的实现。
如果我们重写equals()方法,那么也要重写hashCode()方法,反之亦然。
关于这两个方法的讨论可以参见Object类的讨论:
http://www.cnblogs.com/mengdd/archive/2013/01/03/2842809.html
Eclipse提供了自动生成hashCode()方法和equals()方法的功能:右键>Source>Generate hashCode() and equals()。
接口SortedSet
SortedSet继承了Set接口,SortedSet的实现类是TreeSet。
(可以参见前文Java 容器集合框架概览中的集合框架类图)。
放入集合中的元素按照一定的顺序排列起来,加入元素时要求元素必须是可比较的,否则加入第二个元素时会抛出ClassCastException异常,因为它无法和之前已经存在的元素比较。
TreeSet的构造方法中有一个
TreeSet(Comparator<? super E> comparator)
形式的方法,可以自己定义一个比较的方法传入:用一个类实现Comparator接口,然后重新定义其中的compare(T o1, T o2)方法,最后将这个类的对象传入上述TreeSet的构造方法。
Map
Map映射,将键映射到它的值上。不能包含重复的键,每一个键最多可以映射到一个值。
将键值对放入集合,用put方法:V put(K key, V value)。
如果之前已经包含这个key,则用新的值更新旧的值。
Map是一个接口,它的一个实现类是HashMap<K,V>。
取出值,用get方法,参数是Key,返回时Value。如果不包含这个键的映射,则返回null。
keySet()方法返回键的集合。返回Set<K>。
values()方法返回值的集合。返回Collection<V>。
说明键不能重复,但是值可以重复。
Interface Map.Entry<K,V>
entrySet()方法返回map映射的集合。
TreeMap
TreeMap是Map接口的另一个实现类,它的特点是对Key进行了比较,并且按照Key的顺序存放。
HashSet和HashMap
HashSet底层使用HashMap实现的。当使用add方法将对象添加到Set当中时,实际上是将该对象作为底层所维护的Map对象的key,而value则都是同一个Object对象(该对象我们用不上)。
// Dummy value to associate with an Object in the backing Map private static final Object PRESENT = new Object();
HashMap的底层实现:
HashMap底层维护一个数组,我们向HashMap中所放置的对象实际上是存储在该数组当中。
当向HashMap中put一对键值对时,它会根据key的hashCode值计算出一个位置,该位置就是此对象准备往数组中存放的位置。
如果该位置没有对象存在,就将此对象直接放进数组当中;如果该位置已经有对象存在了,则顺着此存在的对象的链开始寻找(Entry类有一个Entry类型的next成员变量,指向了该对象的下一个对象),如果此链上有对象的话,再去使用equals方法进行比较,如果对此链上的某个对象的equals方法比较为false,则将该对象放到数组当中,然后将数组中该位置以前存在的那个对象链接到此对象的后面。(哈希链表)。
Hashtable和Vector都是比较旧时代的东东,所以一般不用。
Properties是Hashtable的一个子类,经常会被使用。
参考资料
张龙老师视频教程。
Java容器集合类的区别用法:
http://www.cnblogs.com/sunliming/archive/2011/04/05/2005957.html
Java集合类源码分析汇总:
http://www.cnblogs.com/hzmark/archive/2013/01/05/JavaCollectionSum.html
Java集合总结汇总(链接)
http://www.cnblogs.com/Bob-FD/archive/2012/09/20/2695458.html
几个关键接口和类的官方文档:
Interface Collection<E>
http://docs.oracle.com/javase/7/docs/api/java/util/Collection.html
Interface List<E>
http://docs.oracle.com/javase/7/docs/api/java/util/List.html
Class ArrayList<E>
http://docs.oracle.com/javase/7/docs/api/java/util/ArrayList.html
Class LinkedList<E>
http://docs.oracle.com/javase/7/docs/api/java/util/LinkedList.html
Interface Set<E>
http://docs.oracle.com/javase/7/docs/api/java/util/Set.html
Class HashSet<E>
http://docs.oracle.com/javase/7/docs/api/java/util/HashSet.html
Class TreeSet<E>
http://docs.oracle.com/javase/7/docs/api/java/util/TreeSet.html
Interface Map<K,V>
http://docs.oracle.com/javase/7/docs/api/java/util/Map.html
Class HashMap<K,V>
http://docs.oracle.com/javase/7/docs/api/java/util/HashMap.html
Class TreeMap<K,V>
http://docs.oracle.com/javase/7/docs/api/java/util/TreeMap.html
======================================
集合转换
1.list转set
Set set = new HashSet(new ArrayList());
2.set转list
List list = new ArrayList(new HashSet());
3.数组转为list
List stooges = Arrays.asList("Larry", "Moe", "Curly");
此时stooges中有有三个元素。
4.数组转为set
int[] a = { 1, 2, 3 };
Set set = new HashSet(Arrays.asList(a));
5.map的相关操作。
- Map map = new HashMap();
- map.put("1", "a");
- map.put('2', 'b');
- map.put('3', 'c');
- System.out.println(map);
- // 输出所有的值
- System.out.println(map.keySet());
- // 输出所有的键
- System.out.println(map.values());
- // 将map的值转化为List
- List list = new ArrayList(map.values());
- System.out.println(list);
- // 将map的值转化为Set
- Set set = new HashSet(map.values());
- System.out.println(set);
6.list转数组
- List list = Arrays.asList("a","b");
- System.out.println(list);
- String[] arr = (String[])list.toArray(new String[list.size()]);
- System.out.println(Arrays.toString(arr));