本章要点
- 集合的概念和作用
- java的集合体系
- Collection集合的常规用法
- 使用Iterator和foreach循环遍历Collection集合
- HashSet,LinkedHashSet的用法
- TreeSet的用法
- EnumSet的用法
- List集合的常规用法
- ArrayList和Vector
- 固定长度的List集合
- LinkedList集合的用法
- Queue接口
- PriorityQueue的用法
- Map的概念和常规用法
- HashMap和Hashtable
- TreeMap的用法
- 几种特殊的Map实现类
- Hash算法对HashSet,HashMap性能的影响
- Collections工具类的用法
- Enumeration迭代器的用法
java的集合类是一种特别有用的工具类,它可以用于存储数量不等的多个对象,并可以实现常用数据结构,如栈,队列等。除此之外,java结合还可用于保存具有映射关系的关联数组。java的集合大致上可分为:Set,List和Map三种体系,其中Set代表无序,不可重复的集合list代表有序,重复的集合;而Map则代表具有映射关系的集合。从JDK1.5以后,java又增加了Queue体系集合,代表一种队列集合实现。
7.1 java集合概述
为了保存数量不确定的数据,以及保存具有映射关系的数据(也被称为关联数组),java提供集合类。集合类主要负责保存,盛装其他数据,因此集合类也被称为容器类。所有集合类都位于java.util包下。
集合类和数组不一样,数组元素既可以是基本类型的值,也可以是对象(实际上保存的是对象的引用变量);而集合里只能保存对象(实际上也是保存对象的引用变量,但通常习惯上认为集合里保存的是对象)。
java的集合类主要由两个接口派生而出:Collection和Map,Collection和Map是java集合框架的跟接口,这两个接口又包含了一些接口或实现类。
7.2 Collection和Iterator接口
Collection接口是List,set和Queue接口的父接口,该接口里定义的方法即可用于操作set集合,也可用于操作list和queue集合。Collection接口里定义了如下操作集合元素的方法:
- boolean add(Object o):该方法用于向集合里添加一个元素。如果集合对象被添加操作改变了则返回true。
- boolean addAll(Collection c)
- void clear()
- boolean contains(Object o)
- boolean containsAll(Collection c)
- boolean isEmpty()
- iterator iterator()
- boolean remove(Object o)
- boolean removeAll(Collection c)
- boolean retainAll(Collection c)
- int size()
- Object[] toArray()
7.2.1 使用Iterator接口遍历集合元素
Iterator接口隐藏了各种Collection实现类的底层细节,向应用程序提供了遍历Collection集合元素的统一编程接口,Iterator接口里定义了如下三个方法:
- boolean hasNext():如果被迭代的集合元素还没有被遍历,则返回true。
- Object next():返回集合里下一个元素。
- void remove():删除集合里上一次next方法返回的元素。
Iterator必须依附于Collection对象。有一个Iterator对象,则必然有一个与之关联的Collection对象。
7.2.2 使用foreach循环遍历集合元素
7.3 set接口
前面已经介绍过set集合,它类似于一个罐子,一旦把对象“丢进”set集合,集合里多个对象之间没有明显的顺序。set集合与collection基本上完全一样,它没有提供任何额外的方法。实际上set就是collection,只是行为不同(set不允许包含重复元素)。
set判断两个对象相同不是使用==运算符,而是根据equals方法。
7.3.1 HashSet类
HashSet按Hash算法来存储集合中的元素,因此具有很好的存取和查找性能。
HashSet具有以下的特点:
- 不能保证元素的排列顺序,顺序有可能发生变化。
- HashSet不是同步的,如果多个线程同时访问一个HashSet,如果有2条或者2条以上线程同时修改了HashSet集合时,必须通过代码保证其同步。
- 集合元素值可以是null。
当向HashSet集合中存入一个元素时,hashset会调用该对象的hashCode()方法得到该对象额hashCode值,然后根据该hashCode值来决定该对象在hashSet中存储位置。如果有两个元素通过equals方法比较返回true,但它们的hashCode()方法返回值不相等,hashSet将会把它们存储在不同位置,也就可以添加成功。
简单地说,hashSet集合判断两个元素相等的标准是两个对象通过equals方法比较相等,并且两个对象的hashCode()方法返回值也相等。
注意:
如果需要某个类的对象保存到hashSet集合中,重写这个类的equals()方法和hashCode()方法时,应该尽量保证两个对象通过equals比较返回true时,他们的hashCode方法返回值也相等。
hashCode方法对于HashSet的作用是什么?
我们先要理解hash(也被翻译为哈希,散列)算法的功能:他能保证通过一个对象快速查找到另一个对象。hash算法的价值在于速度,它可以保证查询得到快速执行。当需要查询集合中某个元素时,hash算法可以直接根据该元素的值得到该元素保存在何处,从而可以让程序快速找到该元素。为了理解这个概念,我们先看数组(数组是所有能存储一组元素里最快的数据结构):数组可以包含多个元素,每个元素也有索引,如果需要访问某个数组元素,只需提供该元素的索引,该索引即指出了该元素在数组内存区里的存储位置。
表面上看起来,hashSet集合里的元素都没有索引,实际上当程序向hashSet集合中添加元素时,hashset会根据该元素的hashcode值来决定它的存储位置--也就是说,每个元素的hashcode就是它的“索引”。
为什么不直接使用数组,还需要使用hashSet呢?因为数组元素的索引是连续的,而且数组的长度是固定的,无法自由增加数组的长度。而hashset就不一样了,hashset采用每个元素的hashcode作为其索引,从而可以自由增加hashset的长度,并可以根据元素的hashcode值来访问元素。因此,当从hashset中访问元素时,hahsset先计算该元素的hashcode值(也就是调用该对象的hashcode()方法的返回值),然后直接到该hashcode对应的位置去取出该元素--这就是hashset速度很快的原因。
hashset中每个能存储元素的“槽位(slot)”通常称为“桶”(bucket),如果有多个元素的hashcode相同,但它们通过equals方法比较返回false,就需要在一个“桶”里放多个元素,从而导致性能下降。
重写hashCode()方法的基本规则:
- 当两个对象通过equals方法比较返回true时,这两个对象的hashcode应该相等。
- 对象中用做equals比较标准的属性,都应该用来计算hashcode值。
当向hashset中添加可变对象时,必须十分小心。如果修改hashset集合中的对象,有可能导致该对象与集合中其他对象相等,从而导致hashset无法准确访问该对象。
7.3.2 TreeSet类
当需要把一个对象放入treeset中时,重写该对象对应类的equals()方法时,应保证该方法与compareTo(Object obj)方法有一致的结果,其规则是:如果两个对象通过equals方法比较返回true时,这两个对象通过compareTo(Object obj)方法应返回0.
7.3.3 EnumSet 类
EnumSet是一个专为枚举类设计的集合类,EnumSet中所有元素都必须是指定枚举类型的枚举值,该枚举类型在创建EnumSet时显式或隐式地指定。Enumset的集合元素也是有序的,EnumSet以枚举值在Enum类内的定义顺序来决定集合元素的顺序。
7.4 List接口
List集合代表一个有序集合,集合中每个元素都有其对应的顺序索引。list集合允许使用重复元素,可以通过索引来访问指定位置的集合元素。因为list集合默认按元素的添加顺序设置元素的索引。
7.4.1 list接口和listIterator接口
7.4.2 ArrayList和Vector实现类
7.4.3 固定长度的List
7.5 Queue接口
7.6 Map
映射关系的数据