Java集合之Set

来源:互联网 发布:什么是科学精神知乎 编辑:程序博客网 时间:2024/05/16 15:10

转载请注明出处

Java集合框架支持三种主要类型的集合:规则集(Set)、线性表(List)和队列(Queue)。Set实例用于存储一组不重复的元素,List的实例用于存储一个由元素组成的有序集合,Queue的实例用于存储用先进先出方式处理的对象。这些集合的通用特性都被定义在接口中,而它的实现是在具体类中提供的。

Java集合框架的设计是使用接口、抽象类和具体类的一个很好的例子。用接口定义框架。为方便起见,用抽象类提供这个接口的部分实现。具体类用具体的数据结构实现这个接口。Java集合框架中的所有类都实现了java.lang.Cloneable和java.io.Serializable接口,所以,它们的实例都是可复制和可序列化的。它的所有接口和类都存储在java.util包中。

Collection接口是处理对象集合的根接口,AbstractCollection类是提供Collection接口部分实现的便利类。除了size方法和iterator方法之外,它实现了Collection接口中的所有方法。方法size和iterator在合适的子类中实现。Collection接口中的有些方法是不能在具体子类中实习的。在这种情况下,这些方法会抛出异常java.lang.UnsupportedOperationException,它是RuntimeException异常类的一个子类。这样设计很好,可以在自己的项目中使用。

Set接口扩展了Collection接口。它并没有引入新的方法或常量,只是规定Set的实例不包含重复的元素。AbstractSet类是一个便利类,它扩展AbstractCollection类并实现Set接口,且提供equal方法和hashCode方法的具体实现。一个规则集的散列码是这个规则集中所有元素散列码的和。由于AbstractSet类并没有实现size方法和iterator方法,所以,AbstractSet类是一个抽象类。
Set接口的三个具体类是:散列类HashSet、链式散列集LinkedHashSet和树形集TreeSet。

HashSet类可以使用它的无参构造方法来创建空的散列集,也可以由一个现有的集合创建散列集。默认情况下,初始容量为16而客座率是0.75。如果知道集合的大小就可以在构造方法中指定初始容量和客座率。否则,就使用默认的设置。客座率的值在0.0到1.0之间。客座率(load factor)测量在增加规则集的容量之前,改规则集的饱满程度。当元素个数超过了容量与客座率的乘积,容量就会自动翻倍。例如,默认容量16客座率0.75,则尺寸为16x0.75=12,当尺寸达到12时,容量将会翻倍到32。比较高的客座率会降低空间开销,但是会增加查找时间。通常情况下,默认的客座率为0.75,它是在时间开销和空间开销上一个很好的权衡。
HashSet可以用来存储互不相同的任何元素。考虑到效率的因素,添加到散列集中的对象必须以一种正确分散散列码的方式实现hashCode方法。如果两个对象相等,那么这两个对象的散列码必须一样。两个不相等的对象可能会有相同的散列码,因此应该实现hashCode方法以避免出现太多这样的情况。

下面给出简单的HashSet存储程序

import java.util.HashSet;import java.util.Iterator;import java.util.Set;public class TestHashSet {    public static void main(String[] args) {        Set<String> set = new HashSet<String>();        set.add("A");        set.add("B");        set.add("D");        set.add("C");        set.add("B");        set.add("C");        System.out.println(set);        Iterator<String> iterator = set.iterator();        while (iterator.hasNext()) {            System.out.print(iterator.next().toUpperCase() + "、");        }    }}   

结果:
[A, B, C, D]
A、B、C、D、
如输出所示,字符串B、C只有一个被存储,因为规则集不允许有重复的元素。而且字符串并没有按照它们被插入规则集时的顺序存储,因为散列集中的元素是没有特定的顺序的。要强加给它们一个顺序,就需要使用LinkedHashSet类了。

链式散列集LinkedHashSet类用一个链表实现来扩展HashSet类,它支持对规则集内的元素排序。HashSet中的元素是没有被排序的,而LinkedHashSet中的元素可以按照它们插入规则集的顺序提取。
下面给出简单的LinkedHashSet存储程序

import java.util.LinkedHashSet;import java.util.Set;public class TestLinkedHashSet {    public static void main(String[] args) {        Set<String> set = new LinkedHashSet<String>();        set.add("BeiJing");        set.add("ShangHai");        set.add("GuangZhou");        set.add("ShenZhen");        set.add("ChengDu");        set.add("ChengDu");        set.add("DaLian");        System.out.println(set);        for (String s: set) {            System.out.print(s.toLowerCase() + "、");        }    }}

结果:
[BeiJing, ShangHai, GuangZhou, ShenZhen, ChengDu, DaLian]
beijing、shanghai、guangzhou、shenzhen、chengdu、dalian、
LinkedHashSet保持了元素插入时的顺序。要强加一个不同的顺序(例如升序或降序),可以使用TreeSet类。

SortedSet接口是Set的一个子接口,它可以确保规则集中的元素是有序的。另外,它还提供first()和last()方法以返回规则集中的第一个元素和最后一个元素,以及方法headSet(toElement)和tailSet(fromElement)以返回规则集中元素小于toElement和大于或等于fromElement的那一部分。NavigableSet扩展了SortedSet,并提供导航方法lower(e)、floor(e)、ceiling(e)和higher(e)以分别返回小于、小于或等于、大于或等于以及大于一个给定的元素。如果没有这样的元素,方法就返回null。方法pollFirst()和pollLast()会分别删除和返回树形集的第一个元素和最后一个元素。TreeSet实现了SortedSet接口的一个具体类。只要对象是可以互相比较的,就可以将它们添加到一个树形集中。下面是两种比较对象的方法。
- 使用Comparable接口。由于添加到规则集中的对象都是Comparable的实例,所以,可以使用compareTo方法对它们进行比较。在许多JavaAPI以及所有基本数据类型的包装类,都实现了Comparable接口。这种方法定义的顺序通常称为自然顺序
- 如果类的元素没有实现Comparable接口,或者在实现Comparable接口的类中不想使用compareTo方法进行比较,那么可以给规则集中的元素指定一个比较器。这种方法定义的顺序称为比较器顺序
下面给出Comparable接口对元素进行排序的例子。

import java.util.HashSet;import java.util.Set;import java.util.TreeSet;public class TestTreeSet {    public static void main(String[] args) {        Set<String> set = new HashSet<String>();        set.add("BeiJing");        set.add("ShangHai");        set.add("GuangZhou");        set.add("ShenZhen");        set.add("ChengDu");        set.add("DaLian");        set.add("BeiJing");        TreeSet<String> treeSet = new TreeSet<String>(set);        System.out.println("Sorted tree set: " + treeSet);        //第一个元素        System.out.println("first(): " + treeSet.first());        //最后一个元素        System.out.println("last(): " + treeSet.last());        //小于该元素的那一部分        System.out.println("headSet(\"DaLian\"): " + treeSet.headSet("DaLian"));        //大于或等于该元素的那一部分        System.out.println("tailSet(\"DaLian\"): " + treeSet.tailSet("DaLian"));        //小于D        System.out.println("lower(\"D\"): " + treeSet.lower("D"));        //小于或等于D        System.out.println("higher(\"D\"): " + treeSet.higher("D"));        //大于或等于D        System.out.println("floor(\"D\"): " + treeSet.floor("D"));        //大于D        System.out.println("ceiling(\"D\"): " + treeSet.ceiling("D"));        //删除和返回树形集的第一个元素        System.out.println("pollFirst(): " + treeSet.pollFirst());        //删除和返回树形集的最后一个元素        System.out.println("pollLast(): " + treeSet.pollLast());        System.out.println("New tree set: " + treeSet);    }}

结果:
Sorted tree set: [BeiJing, ChengDu, DaLian, GuangZhou, ShangHai, ShenZhen]
first(): BeiJing
last(): ShenZhen
headSet(“DaLian”): [BeiJing, ChengDu]
tailSet(“DaLian”): [DaLian, GuangZhou, ShangHai, ShenZhen]
lower(“D”): ChengDu
higher(“D”): DaLian
floor(“D”): ChengDu
ceiling(“D”): DaLian
pollFirst(): BeiJing
pollLast(): ShenZhen
New tree set: [ChengDu, DaLian, GuangZhou, ShangHai]

该程序创建了一个由字符串构成的散列集,然后创建一个由相同字符构成的树形集,使用Comparable接口中的compareTo方法对树形集中的字符串进行排序。当使用new TreeSet(hashSet)从一个HashSet对象创建一个TreeSet对象时,规则集中的元素就只被排序一次。可以改写这个程序,使用TreeSet的无参构造方法创建一个TreeSet的实例,然后将字符串添加到这个实例中。这样,每次给TreeSet对象添加字符串的时候,树形集中的字符串都会被重新排序。该例子中使用的方法在通常情况下效率都会比较高,因为只对字符串进行一次排序。treeSet.lower(“D”)返回TreeSet中小于”D”的最大元素,treeSet.higher(“D”)返回TreeSet中大于”D”的最小元素。treeSet.floor(“D”)返回TreeSet小于或等于”D”的最大元素。treeSet.ceiling(“D”)返回treeSet中大于或等于”D”的最小元素。
Java集合框架中的所有具体类都至少有两个构造方法:一个是创建空集合的无参构造方法,一个是用某个集合来创建实例的构造方法。当更新一个规则集时,如果不需要保持元素的排序关系,就应该使用散列集,因为在散列集中插入和删除元素所花的时间比较少。当需要一个排好序的集合时,可以从这个散列集创建一个树形集。

1 0