Java集合(五):Set集

在上一讲中介绍了散列映射表HashMap和树映射表TreeMap,知道了HashMap的底层实现机制。这一讲将介绍Set接口和实现类:HashSet和TreeSet。由于HashSet的实现是基于HashMap的,TreeSet的实现是基于TreeMap的,所以这里不做过多底层的讨论,毕竟这部分已经在Java集合(四):Map映射中讨论过了。

1 散列表与Set接口

链表和数组可以按照人们的意愿排列元素的顺序。但是,如果想要查看某个指定的元素,但却忘了它的位置,就需要访问所有的元素,直到找到为止。如果集合中的元素很多,将会消耗很长时间。如果不在意元素的顺序,可以有几种能够快速查找元素的数据结构。但是缺点是不能控制元素的顺序。它们将按照有利于其操作目的的原则组织数据。

有一种常见的数据结构,就是散列表(hash table)。散列表可以根据每个对象计算一个整数,称为散列码(hash code)。不同的对象产生不同的散列码。

Java中,散列表用链表数组实现。每个列表称为桶,这个已经在Map映射中介绍了。

散列表的特点就是:元素没有顺序,元素不能重复。

Set接口的定义如下:

  1. public interface java.util.Set<E> extends java.util.Collection<E> {  
  2.   public abstract int size();  
  3.   public abstract boolean isEmpty();  
  4.   public abstract boolean contains(java.lang.Object);  
  5.   public abstract java.util.Iterator<E> iterator();  
  6.   public abstract java.lang.Object[] toArray();  
  7.   public abstract <T> T[] toArray(T[]);  
  8.   public abstract boolean add(E);  
  9.   public abstract boolean remove(java.lang.Object);  
  10.   public abstract boolean containsAll(java.util.Collection<?>);  
  11.   public abstract boolean addAll(java.util.Collection<? extends E>);  
  12.   public abstract boolean retainAll(java.util.Collection<?>);  
  13.   public abstract boolean removeAll(java.util.Collection<?>);  
  14.   public abstract void clear();  
  15.   public abstract boolean equals(java.lang.Object);  
  16.   public abstract int hashCode();  
  17.   public java.util.Spliterator<E> spliterator();  
  18. }  
public interface java.util.Set<E> extends java.util.Collection<E> {
  public abstract int size();
  public abstract boolean isEmpty();
  public abstract boolean contains(java.lang.Object);
  public abstract java.util.Iterator<E> iterator();
  public abstract java.lang.Object[] toArray();
  public abstract <T> T[] toArray(T[]);
  public abstract boolean add(E);
  public abstract boolean remove(java.lang.Object);
  public abstract boolean containsAll(java.util.Collection<?>);
  public abstract boolean addAll(java.util.Collection<? extends E>);
  public abstract boolean retainAll(java.util.Collection<?>);
  public abstract boolean removeAll(java.util.Collection<?>);
  public abstract void clear();
  public abstract boolean equals(java.lang.Object);
  public abstract int hashCode();
  public java.util.Spliterator<E> spliterator();
}
这些方法的含义也很简单。

2 HashSet类

Java集合类库中提供了HashSet类实现了Set接口。这个类的底层是使用HashMap实现的:

  1. private transient HashMap<E,Object> map;  
  2.   
  3. // Dummy value to associate with an Object in the backing Map  
  4. private static final Object PRESENT = new Object();  
private transient HashMap<E,Object> map;

// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();

我们知道,HashMap是一个键值对,而HashSet存储的不是键值对,因此为了使用HashMap存储HashSet的元素,就需要构造一个键值对。可以使用需要存储在HashSet中的元素作为键,而上面的PRESENT作为值,就构成了一个键值对,这样就可以存在HashMap中了。

也就是说,HashSet与HashMap的原理一样,不同的是HashSet的值都一样,都是PRESENT。

由于在前一节中已经详细介绍了HashMap的原理,这里不再叙述了。只说一下HashSet的使用。

下面的代码从System.in中读取单词,然后将它们添加到HashSet中,再打印出所有的单词。由于HashSet中不存储相同的元素,所以打印出来的单词是不重复的。运行这个程序时使用下面的命令行:

  1. java SetTest < alice.txt  
java SetTest < alice.txt

这样就把alice.txt作为输入,程序就会读取所有的单词。代码如下:

  1. import java.util.*;  
  2.   
  3. public class SetTest  
  4. {  
  5.    public static void main(String[] args)  
  6.    {  
  7.       Set<String> words = new HashSet<>(); // HashSet implements Set  
  8.       long totalTime = 0;  
  9.   
  10.       Scanner in = new Scanner(System.in);  
  11.       while (in.hasNext())  
  12.       {  
  13.          String word = in.next();  
  14.          long callTime = System.currentTimeMillis();  
  15.          words.add(word);  
  16.          callTime = System.currentTimeMillis() - callTime;  
  17.          totalTime += callTime;  
  18.       }  
  19.   
  20.       Iterator<String> iter = words.iterator();  
  21.       for (int i = 1; i <= 20 && iter.hasNext(); i++)  
  22.          System.out.println(iter.next());  
  23.       System.out.println(". . .");  
  24.       System.out.println(words.size() + " distinct words. " + totalTime + " milliseconds.");  
  25.    }  
  26. }  
import java.util.*;

public class SetTest
{
   public static void main(String[] args)
   {
      Set<String> words = new HashSet<>(); // HashSet implements Set
      long totalTime = 0;

      Scanner in = new Scanner(System.in);
      while (in.hasNext())
      {
         String word = in.next();
         long callTime = System.currentTimeMillis();
         words.add(word);
         callTime = System.currentTimeMillis() - callTime;
         totalTime += callTime;
      }

      Iterator<String> iter = words.iterator();
      for (int i = 1; i <= 20 && iter.hasNext(); i++)
         System.out.println(iter.next());
      System.out.println(". . .");
      System.out.println(words.size() + " distinct words. " + totalTime + " milliseconds.");
   }
}

结果如下:

可以看见,一共有5392个不同的单词。

3 TreeSet类

TreeSet和HashSet类似,不过,它比HashSet有所改进。TreeSet是一个有序集合,可以以任意顺序将元素插入到集合中。在对集合进行遍历时,每个值将自动按照排序后的顺序呈现。例如,假设插入三个字符串,然后访问添加的所有元素:

  1. SortedSet<String> sorter=new TreeSet<>();  
  2. sorter.add("B");  
  3. sorter.add("A");  
  4. sorter.add("C");  
  5. for(String s:sorter)System.out.println(s);  
SortedSet<String> sorter=new TreeSet<>();
sorter.add("B");
sorter.add("A");
sorter.add("C");
for(String s:sorter)System.out.println(s);

结果是:A B C

TreeSet的底层是使用TreeMap实现的,是一个红黑树。每次添加一个元素到树中时,都被放置在正确的排序位置上。因此,迭代器总是以排好序的顺序访问每个元素。

将一个元素添加到树中要比添加到一个散列表中要慢,但是,与将元素添加到数组中或链表中要快。如果树中一共有n个元素,将元素插入到正确位置的时间为logn。

与TreeMap一样,构造一个TreeSet也需要一个比较器,可以使用默认的比较器,也可以使用自己的比较器。使用自己的比较器时,需要给TreeSet的构造器传递一个Comparator对象。

下面的程序创建了两个Item对象的树集。第一个按照部件编号排序,这是Item对象的默认顺序。第二个通过使用一个定制的比较器来按照描述信息排序:

  1. import java.util.*;  
  2.   
  3.   
  4. public class TreeSetTest  
  5. {    
  6.    public static void main(String[] args)  
  7.    {    
  8.       SortedSet<Item> parts = new TreeSet<>();  
  9.       parts.add(new Item("Toaster"1234));  
  10.       parts.add(new Item("Widget"4562));  
  11.       parts.add(new Item("Modem"9912));  
  12.       System.out.println(parts);  
  13.   
  14.       SortedSet<Item> sortByDescription = new TreeSet<>(new  
  15.          Comparator<Item>()  
  16.          {    
  17.             public int compare(Item a, Item b)  
  18.             {    
  19.                String descrA = a.getDescription();  
  20.                String descrB = b.getDescription();  
  21.                return descrA.compareTo(descrB);  
  22.             }  
  23.          });  
  24.   
  25.       sortByDescription.addAll(parts);  
  26.       System.out.println(sortByDescription);  
  27.    }  
  28. }  
import java.util.*;


public class TreeSetTest
{  
   public static void main(String[] args)
   {  
      SortedSet<Item> parts = new TreeSet<>();
      parts.add(new Item("Toaster", 1234));
      parts.add(new Item("Widget", 4562));
      parts.add(new Item("Modem", 9912));
      System.out.println(parts);

      SortedSet<Item> sortByDescription = new TreeSet<>(new
         Comparator<Item>()
         {  
            public int compare(Item a, Item b)
            {  
               String descrA = a.getDescription();
               String descrB = b.getDescription();
               return descrA.compareTo(descrB);
            }
         });

      sortByDescription.addAll(parts);
      System.out.println(sortByDescription);
   }
}

  1. import java.util.*;  
  2.   
  3. /** 
  4.  * An item with a description and a part number. 
  5.  */  
  6. public class Item implements Comparable<Item>  
  7. {  
  8.    private String description;  
  9.    private int partNumber;  
  10.   
  11.    /** 
  12.     * Constructs an item. 
  13.     *  
  14.     * @param aDescription 
  15.     *           the item's description 
  16.     * @param aPartNumber 
  17.     *           the item's part number 
  18.     */  
  19.    public Item(String aDescription, int aPartNumber)  
  20.    {  
  21.       description = aDescription;  
  22.       partNumber = aPartNumber;  
  23.    }  
  24.   
  25.    /** 
  26.     * Gets the description of this item. 
  27.     *  
  28.     * @return the description 
  29.     */  
  30.    public String getDescription()  
  31.    {  
  32.       return description;  
  33.    }  
  34.   
  35.    public String toString()  
  36.    {  
  37.   
  38.       return "[\n\tdescripion=" + description + ",\n\tpartNumber=" + partNumber +"\n]\n";  
  39.    }  
  40.   
  41.    public boolean equals(Object otherObject)  
  42.    {  
  43.       if (this == otherObject) return true;  
  44.       if (otherObject == nullreturn false;  
  45.       if (getClass() != otherObject.getClass()) return false;  
  46.       Item other = (Item) otherObject;  
  47.       return Objects.equals(description, other.description) && partNumber == other.partNumber;  
  48.    }  
  49.   
  50.    public int hashCode()  
  51.    {  
  52.       return Objects.hash(description, partNumber);  
  53.    }  
  54.   
  55.    public int compareTo(Item other)  
  56.    {  
  57.       return Integer.compare(partNumber, other.partNumber);  
  58.    }  
  59. }  
import java.util.*;

/**
 * An item with a description and a part number.
 */
public class Item implements Comparable<Item>
{
   private String description;
   private int partNumber;

   /**
    * Constructs an item.
    * 
    * @param aDescription
    *           the item's description
    * @param aPartNumber
    *           the item's part number
    */
   public Item(String aDescription, int aPartNumber)
   {
      description = aDescription;
      partNumber = aPartNumber;
   }

   /**
    * Gets the description of this item.
    * 
    * @return the description
    */
   public String getDescription()
   {
      return description;
   }

   public String toString()
   {

      return "[\n\tdescripion=" + description + ",\n\tpartNumber=" + partNumber +"\n]\n";
   }

   public boolean equals(Object otherObject)
   {
      if (this == otherObject) return true;
      if (otherObject == null) return false;
      if (getClass() != otherObject.getClass()) return false;
      Item other = (Item) otherObject;
      return Objects.equals(description, other.description) && partNumber == other.partNumber;
   }

   public int hashCode()
   {
      return Objects.hash(description, partNumber);
   }

   public int compareTo(Item other)
   {
      return Integer.compare(partNumber, other.partNumber);
   }
}

结果如下:



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值