从书上和网上搜索各种资料整理而成,相信是比较全的了。
一、基本概念
1、迭代器
Iterator接口包含3个方法:
public interface Iterator{
E next();
boolean hasNext();
void remove();
}
通过反复调用next方法可以访问集合中每个元素,但需要在调用next之前调用hasNext方法。
remove方法将会删除上次调用next方法时返回的元素,如果调用remove之前没有调用next将是不合法的,正确的方法是:
Iterator it = c.iterator();
it.next();
it.remove();
2、集合的形式
集合有4种基本形式,其中前三种的父接口是Collection。
List 关注事物的索引列表(list的清单的意思,所以里面是一项一项列出来的)。
Set 关注事物的唯一性(set是集合的意思,所以里面不能有重复的(数学里面的集合里面不能有重复元素))。
Map 关注事物的映射和键值的唯一性(不是地图是映射的意思,所以里面是键值成对存在的)。
Queue 关注事物被处理时的顺序。
3、Hash表
Hash表是一种数据结构,用来查找对象。Hash表为每个对象计算出一个整数,称为Hash Code(哈希码)。Hash表是个链接式列表的阵列。每个列表称为一个buckets(哈希表元)。
当你添加元素时,有时你会遇到已经填充了元素的哈希表元,这种情况称为Hash Collisions(哈希冲突)。这时,你必须判断该元素是否已经存在于该哈希表中。
4、 Comparable接口和Comparator接口
在java.lang包中,Comparable接口适用于一个类有自然顺序的时候。假定对象集合是同一类型,该接口允许您把集合排序成自然顺序。int compareTo(Object o): 比较当前实例对象与对象o,如果位于对象o之前,返回负值,如果两个对象在排序中位置相同,则返回0,如果位于对象o后面,则返回正值
若一个类不能用于实现java.lang.Comparable,或者您不喜欢缺省的Comparable行为并想提供自己的排序顺序(可能多种排序方式),你可以实现Comparator接口,从而定义一个比较器。
二、集合之间的关系
集合之间的关系图:
下面这张图看起来层级结构更清晰些。
三、几个比较重要的接口和类简介
1、List接口
List 用于描述一个有序集合,关心的是索引,与其他集合相比,List特有的就是和索引相关的一些方法:get(int index) 、 add(int index,Object o) 、 indexOf(Object o) 。它有两种访问元素的协议:一种是用迭代器,另一种是用get和set方法随机地访问每个元素。后者不适用于链表,但对数组却很有用。
ArrayList 封装了一个动态再分配的Object[]数组,它提供快速迭代和快速随机访问的能力。
LinkedList 中的元素之间是双链接的,当需要快速插入和删除时LinkedList成为List中的不二选择。使用链表的唯一理由就是尽可能地减少在列表中间插入或删除元素所付出的代价。如果列表只有少数几个元素,完全可以使用ArrayList。避免使用以整数索引表示链表中位置的所有方法。如果需要对集合进行随机访问,就使用数组或ArrayList,而不要使用链表。它具有方法addFirst()、addLast()、getFirst()、getLast()、removeFirst()、removeLast(),这些方法(没有在任何接口或基类中定义过)使得LinkedList可以当作堆栈、队列和双向队列使用。
Vector 是ArrayList的线程安全版本,性能比ArrayList要低,现在已经很少使用
2、Set接口
Set关心唯一性,它不允许重复。set接口没有引入新方法,所以Set就是一个Collection,只不过其行为不同。
HashSet 当不希望集合中有重复值,并且不关心元素之间的顺序时可以使用此类。
LinkedHashset 当不希望集合中有重复值,并且希望按照元素的插入顺序进行迭代遍历时可采用此类。扩展HashSet,它提供了一个可以快速访问各个元素的有序集合。同时,它也增加了实现的代价,因为 哈希表元中的各个元素是通过双重链接式列表链接在一起的。
TreeSet 当不希望集合中有重复值,并且希望按照元素的自然顺序进行排序时可以采用此类。将一个元素添加到TreeSet要比添加到HashSet中慢。TreeSet是SortedSet接口的唯一实现类,TreeSet可以确保集合元素处于排序状态。TreeSet支持两种排序方式,自然排序 和定制排序,其中自然排序为默认的排序方式。向TreeSet中加入的应该是同一个类的对象。 TreeSet判断两个对象不相等的方式是两个对象通过equals方法返回false,或者通过CompareTo方法比较没有返回0 。
3、Map接口
Map关心的是唯一的标识符。他将唯一的键映射到某个元素。当然键和值都是对象。
HashMap 当需要键值对表示,又不关心顺序时可采用HashMap。为了优化HashMap空间的使用,您可以调优初始容量和负载因子。
Hashtable 注意Hashtable中的t是小写的,它是HashMap的线程安全版本,现在已经很少使用。
LinkedHashMap 当需要键值对,并且关心插入顺序时可采用它。扩展HashMap,以插入顺序将关键字/值对添加进链接哈希映像中。象LinkedHashSet一样,LinkedHashMap内部也采用双重链接式列表。
TreeMap 当需要键值对,并关心元素的自然排序时可采用它。TreeMap没有调优选项,因为该树总处于平衡状态。
WeakHashMap 是Map的一个特殊实现,它使用WeakReference(弱引用)来存放哈希表关键字。使用这种方式时,当映射的键在 WeakHashMap 的外部不再被引用时,垃圾收集器会将它回收,但它将把到达该对象的弱引用纳入一个队列。WeakHashMap的运行将定期检查该队列,以便找出新到达的 弱应用。当一个弱引用到达该队列时,就表示关键字不再被任何人使用,并且它已经被收集起来。然后WeakHashMap便删除相关的映射。
IdentityHashMap 也是Map的一个特殊实现。在这个类中,关键字的哈希码不应该由hashCode()方法来计算,而应该由 System.identityHashCode方法进行计算(即使已经重新定义了hashCode方法)。这是Object.hashCode根据对象 的内存地址来计算哈希码时使用的方法。另外,为了对各个对象进行比较,IdentityHashMap将使用==,而不使用equals方法。
4、Queue接口
Queue用于保存将要执行的任务列表。
LinkedList 同样实现了Queue接口,可以实现先进先出的队列。
PriorityQueue 用来创建自然排序的优先级队列。
四、总结
1、在Java程序设计语言中,所有的链表实际上都是双向链接的,即每个结点还存放着指向前驱结点的引用。
2、ArrayList与Vector区别
Vector也提供一个动态数组,但Vector类的所有方法都是同步的,可以由两个线程安全地访问一个Vector对象,但是如果由一个线程访问Vector,代码要在同步操作上耗费大量的时间,因此在不需要同步时使用ArrayList,而不要使用Vector。
3、使用原则
* 在各种Lists中,最好的做法是以ArrayList作为缺省选择。当插入、删除频繁时,使用LinkedList();Vector总是比ArrayList慢,所以要尽量避免使用。
* 在各种Sets中,HashSet通常优于HashTree(插入、查找)。只有当需要产生一个经过排序的序列,才用TreeSet。 HashTree存在的唯一理由:能够维护其内元素的排序状态。
* 在各种Maps中:HashMap用于快速查找。
* 当元素个数固定,用Array,因为Array效率是最高的。
结论:最常用的是ArrayList,HashSet,HashMap,Array。
3、set、list、map三者之间的关系。
Set与Map的关系。Map集合中所有key集中起来,就组成了一个set集合。所以Map集合提供Set keySet()方法返回所有key组成的set集合。由此可见,Map集合中的所有key具有set集合的特征,只要Map所有的key集中起来,它就是一个Set集合,这就实现了Map到Set的转换。同时,如果把Map中的元素看成key-value的set集合,也可以实现从Set到Map之间的转换。HashSet和HashMap分别作为它们的实现类。两者之间也挺相似的。HashSet的实现就是封装了HashMap对象来存储元素。它们的本质是一样的。类似于HashSet和HashMap的关系,其实TreeMap和TreeSet本质也差不多,TreeSet底层也是依赖TreeMap实现。
Map与List的关系。把Map的key-value分开来看,从另一个角度看,就可以把Map与List统一起来。
Map集合是一个关联数组,key可以组成Set集合,Map中的value可以重复,所以这些value可以组成一个List集合。但是需要注意的是,实质Map的values方法并未返回一个List集合。而是返回一个不存储元素的Collection集合,换一种角度来看对List集合,它也包含了两组值,其中一组就是虚拟的int类型的索引,另一组就是list集合元素,从这个意思上看,List就相当于所有key都是int型的Map。
4、几个相似类之间的差异。
ArrayList和LinkedList。ArrayList是一种顺序存储的线性表,其底层是采用数组实现的,而LinkedList是链式存储的线性表。其本质就是一个双向链表。对于随机存储比较频繁的元素操作应选用ArrayList,对于经常需要增加、删除元素应该选用LinkedList。但总的来说ArrayList的总体性能还是优于LinkedList。
HashSet与HashMap的性能选项。主要有两个方面:容量和负载因子(尺寸/容量)。较低负载因子会增加查询数据的性能,但是会降低hash表所占的内存开销。较高负载因子则反之,一般对数据的查询比较频繁,所以一般情况下初始容量应该大一点,但也不能太大,否则浪费内存空间。
五、集合类详解
1、HashSet
散列表用链表数组实现。每个列表被称为桶。要想找到表中对象的位置,就要先计算它的散列码,然后与桶的总数取余,所得到的结果就是保存这个元素的桶的索引。
HashSet中不允许有重复的元素。例如:
Set hashSet = new HashSet();
hashSet.add(new String("aaa"));
hashSet.add(new String("bbb"));
hashSet.add(new String("ccc"));
hashSet.add(new String("aaa"));
hashSet.add(new String("aaa"));
通过hashSet.size()获取它含有元素的个数,上面去掉重复的元素后,hashSet.size()=3。也就是说,在向HashSet中添加(add())元素的时候,对于重复的元素,只在HashSet中保留一个副本。
另外,HashSet中元素的顺序是随机的,包括添加(add())和输出都是无序的。只有不关心元素的顺序时才使用HashSet。
HashSet集合判断两个元素相等的标准是两个对象通过equals方法比较相等,并且两个对象的hashCode()方法返回值相 等 。注意,如果要把一个对象放入HashSet中,重写该对象对应类的equals方法,也应该重写其hashCode()方法。其规则是如果两个对 象通过equals方法比较返回true时,其hashCode也应该相同。另外,对象中用作equals比较标准的属性,都应该用来计算 hashCode的值。
构造两个Person的实例:
Person p1 = new Person();
p1.setName("shirdrn");
p1.setAge(new Integer(26));
Person p2 = new Person();
p2.setName("shirdrn");
p2.setAge(new Integer(26));
加入到HashSet中:
Set hashSet = new HashSet();
hashSet.add(p1);
hashSet.add(p2);
按理说,加入的这两个Person实例是相同的,HashSet应该只选择一个添加到集合里面。其实不然,此时的hashSet.size()=2。
这主要是由于Object拥有hashCode()和equals()两个方法,它认为具有相同的hashcode的对象才是同一个对象,即对同一个对象的引用。
所以必须在对应的实体类中重写hashCode()和equals()两个方法。在Person类中重写hashCode()和equals()两个方法,如下所示:
public boolean equals(Object o){
if(this == o){
return true;
}
if(! (o instanceof Person)){
return false;
}
final Person other = (Person)o;
if(this.name.equals(other.getName()) && this.age.equals(other.getAge())){
return true;
}
else{
return false;
}
}
public int hashCode(){
int result;
result = (name == null?0:name.hashCode());
result = 37*result + (age == null?0:age.hashCode());
return result;
}
这时,再进行上面的测试,发现hashSet.size()=1。此时,对象p1和p2具有相同的hashcode,HashSet认为添加的两个Person实例是同一个对象,只把一个添加到集合里面。
HashMap的基本使用:
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class Test {
public static void main(String[] args) {
Map map = new HashMap();
map.put(1, "白菜");
map.put(2, "萝卜");
map.put(3, "茄子");
map.put(4, null);
map.put(null, null);
map.put(null, null);
System.out.println("map.size()="+map.size());
System.out.println("map.containsKey(1)="+map.containsKey(2));
System.out.println("map.containsKey(null)="+map.containsKey(null));
System.out.println("map.get(null)="+map.get(null));
System.out.println("map.get(2)="+map.get(2));
map.put(null, "黄瓜");
System.out.println("map.get(null)="+map.get(null));
Set set = map.keySet();
System.out.println("set="+set);
Collection c = map.values();
System.out.println("Collection="+c);
}
}
编译并运行程序,查看结果:
map.size()=5
map.containsKey(1)=true
map.containsKey(null)=true
map.get(null)=null
map.get(2)=萝卜
map.get(null)=黄瓜
set=[null, 1, 2, 3, 4]
Collection=[黄瓜, 白菜, 萝卜, 茄子, null]