本章讲解Java中的容器类
自从Java1.2 之后 Java 版本统称为 Java2 ,Java2 中的容器类库才可以说是一种真正意义上的集合框架的实现。基本完全重新设计,但是又对 Java1 中的一些容器类库在新的设计上进行了保留,这主要是为了向下兼容的目的,当用 Java2 开发程序时,应尽量避免使用它们,Java2 的集合框架已经完全可以满足你的需求。
有一点需要提醒的是,在 Java1 中容器类库是同步化的,而 Java2 中的容器类库都是非同步化,这可能是对执行效率进行考虑的结果。
结构图
Java2容器类类库的用途是“保存对象”,它分为两类:
Collection----一组独立的元素,通常这些元素都服从某种规则。List必须保持元素特定的顺序,而Set不能有重复元素。
Map----一组成对的“键值对”对象,即其元素是成对的对象,最典型的应用就是数据字典,并且还有其它广泛的应用。另外,Map可以返回其所有键组成的Set和其所有值组成的Collection,或其键值对组成的Set,并且还可以像数组一样扩展多维Map,只要让Map中键值对的每个“值”是一个Map即可。
1.迭代器
迭代器是一种设计模式,它是一个对象,它可以遍历并选择序列中的对象,而开发人员不需要了解该序列的底层结构。迭代器通常被称为“轻量级”对象,因为创建它的代价小。
Java中的Iterator功能比较简单,并且只能单向移动:
(1) 使用方法iterator()要求容器返回一个Iterator。第一次调用Iterator的next()方法时,它返回序列的第一个元素。
(2) 使用next()获得序列中的下一个元素。
(3) 使用hasNext()检查序列中是否还有元素。
(4) 使用remove()将迭代器新返回的元素删除。
Iterator是Java迭代器最简单的实现,为List设计的ListIterator具有更多的功能,它可以从两个方向遍历List,也可以从List中插入和删除元素。
2.List的功能方法
List(interface): 次序是List最重要的特点;它确保维护元素特定的顺序。List为Collection添加了许多方法,使得能够向List中间插入与移除元素(只推荐LinkedList使用)。一个List可以生成ListIterator,使用它可以从两个方向遍历List,也可以从List中间插入和删除元素。
List 接口从 Collection 接口中分立出来是因为 List 的特点——有序的集合。这里指的有序并不是按照大小排好序的( Sorted ),而是指集合是可以以确定的顺序访问的序列。针对 List 的这个特点,它比 Collection 接口增加了通过索引进行操作的方法。例如, add 、 remove 、 get 、 set 等方法的参数表中都可以加入索引的数值,从而操作处在索引位置处的元素。
ArrayList: 由数组实现的List。它允许对元素进行快速随机访问,但是向List中间插入与移除元素的速度很慢。ListIterator只应该用来由后向前遍历ArrayList,而不是用来插入和删除元素,因为这比LinkedList开销要大很多。
LinkedList: 对顺序访问进行了优化,向List中间插入与删除得开销不大,随机访问则相对较慢(可用ArrayList代替)。它具有方法addFirst()、addLast()、getFirst()、getLast()、removeFirst()、removeLast(),这些方法(没有在任何接口或基类中定义过)使得LinkedList可以当作堆栈、队列和双向队列使用。
ArrayList 与 LinkedList都是实现了 List 接口的类,是有序集。 List 接口支持通过索引的方法来访问元素,对于这一点, ArrayList 没有任何问题;但是对于 LinkedList 则有很大的问题,链表本身不应该支持随机存储,但是作为 List 的一个实现,链表也提供了对随机访问的支持,但是效率很低。每次通过索引的方法都是进行一次遍历。我认为,其实就不应该让链表支持随机访问;而 Java 这样实现我想是因为整个集合框架的体系,使得链表与数组可以使用同样的方法使用。综上所述,对于 LinkedList 最好不使用随机访问,而使用迭代器。
3.Set的功能方法
Set(interface): 存入Set的每个元素必须是唯一的,因为Set不保存重复元素。加入Set的Object必须定义equals()方法以确保对象的唯一性。Set与Collection有完全一样的接口。Set接口不保证维护元素的次序。
Set 接口与 List 的不同,它里面的元素是无序的;所以,不能通过任何索引的方法来操作 Set 对象
HashSet: 为快速查找而设计的Set。存入HashSet的对象必须定义hashCode()。
HashSet 是非常高效的数据结构,与 TreeSet 不同, HashSet 是比较对象的 equals() 方法来区分不同的对象。这样只有真正不同的对象才能不被重复的加入到集合中。
需要注意的是: HashSet 效率非常高,但是对象的 hashCode 函数不好确定。一般默认的对象的 hashCode 函数是根据对象的内存地址得到的。好的 hashCode 函数是 HashSet 成功运用的关键。
TreeSet: 保持次序的Set,底层为树结构。使用它可以从Set中提取有序的序列。
TreeSet 是 SortedSet 的一个实现。根据数据结构的知识可以知道,树的效率非常高,而且 Java 标准库中有 TreeSet 这样的类,以后应该尽量使用 TreeSet 来提高程序的效率。
需要注意的是: TreeSet 作为有序集,它通过 compareTo 或者 Comparator 来将集合元素排序。任何具有相同比较值的元素(无论它们是否 equals() ),在 TreeSet 中都作为同一个元素,从而不能有重复。这样以来,即使是不同的对象也不能加入到集合中,这一点有时候很不方便。我在编写 A* 算法时,不同状态有时候对应着同一个启发函数值,那么这些不同的状态就无法加入到 TreeSet 中。
LinkedHashSet: 具有HashSet的查询速度,且内部使用链表维护元素的顺序(插入的次序)。于是在使用迭代器遍历Set时,结果会按元素插入的次序显示。
HashSet采用散列函数对元素进行排序,这是专门为快速查询而设计的;TreeSet采用红黑树的数据结构进行排序元素;LinkedHashSet内部使用散列以加快查询速度,同时使用链表维护元素的次序,使得看起来元素是以插入的顺序保存的。需要注意的是,生成自己的类时,Set需要维护元素的存储顺序,因此要实现Comparable接口并定义compareTo()方法。