java集合(六)---HashSet、TreeSet和LinkedHashSet

一、之间的简单关系:

(1)HashSet、TreeSet是Set的两个典型实现,LinkedHashSet是HashSet的实现类。简单来说,HashSet的性能总是比TreeSet好(特别是最常用的添加、查询元素等操作),因为TreeSet需要额外的红黑树算法来维护集合元素的次序。只有当需要一个排序的Set时,才应该使用TreeSet,否则都应该使用HashSet。
(2)对于LinkedHashSet,对于普通的插入、删除操作,LinkedHashSet比HashSet要略微慢一点,这是因为维护链表所带来的额外的开销造成的,但由于有了链表,遍历LinkedHashSet会更快。
(3)还有一点需要指出的是:Set的三个实现类(HashSet、TreeSet和EnumSet)都是线程不安全的,如果有多个线程同时访问一个Set集合,并且有超过一个线程修改了该Set集合,则必须手动保证Set集合的同步性。通常可以通过Collections工具类的sysnchronizedSortedSet方法来“包装”该Set集合。所以,最好在创建的时候进行,以防止对Set集合的意外非同步访问。

二、辨析其区别

1,HashSet类:

该类实现的接口:Serializable, Cloneable, Iterable, Collection, Set
HashSet原理:

  • HashSet是基于HashMap来实现的,操作很简单,更像是对HashMap做了一次“封装”,而且只使用了HashMap的key来实现各种特性
  • HashSet是通过HashMap实现,整个HashSet的核心就是HashMap。HashMap中的键作为HashSet中的值,HashMap中的值通过创建一个假的value来实现
  • HashSet的部分源码:
private transient HashMap<E,Object> map;

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

/**
 * Constructs a new, empty set; the backing HashMap instance has
 * default initial capacity (16) and load factor (0.75).
 * 首先有一个HashMap成员变量,我们在HashSet的构造函数中将其初始化,默认情况下用的是Initial capacity为16,load factory加载因子为0.75
 */
public HashSet() {
    map = new HashMap<>();
}


public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}
public boolean remove(Object o) {
    return map.remove(o)==PRESENT;
}
public boolean contains(Object o) {
    return map.containsKey(o);
}
public int size() {
    return map.size();
}

从中可以看出,HashSet中的一些基本操作都是调用HashMap来实现的。

HashSet具有以下特点:

  • 不能保证元素的排列顺序,顺序可能与添加顺序不同,顺序也有可能发生变化
  • HashSet不是同步的。如果多个线程同时访问一个HashSet,假设有两个或者两个以上线程同时修改了HashSet集合时,则必须通过代码来保证同步
  • 集合元素值可以是null
  • HashSet类判断两个元素是否相等,需要两个条件,第一个条件:equals()的结果为true,第二个条件:hashCode()的值相等,即对应的元素的hashCode码要相同
  • 下面一个示例代码:
package set;

import java.util.HashSet;
import java.util.Set;

public class HashSetTest {

    public static void main(String[] agrs){
        Set s = new HashSet();
        s.add(new EqualsObj());
        s.add(new EqualsObj());
        s.add(new HashCodeObj());
        s.add(new HashCodeObj());
        s.add(new HashSetObj());
        s.add(new HashSetObj());

        System.out.println("HashSet Elements:");
        System.out.print("\t" + s + "\n");
    }
}

class EqualsObj {

    public boolean equals(Object obj) {
        return true;
    }
}

class HashCodeObj {
    public int hashCode() {
        return 1;
    }
}

class HashSetObj {
    public boolean equals(Object obj) {
        return true;
    }

    public int hashCode() {
        return 2;
    }
}

输出的结果如下:

HashSet Elements:
    [set.EqualsObj@7852e922, set.HashCodeObj@1, set.HashCodeObj@1, set.HashSetObj@2, set.EqualsObj@6d06d69c]

从以上结果可以看出**,1.HashSet类存储数据的顺序是不确定的,2. 该类只认为hashCode和equals方法的值都不同的对象为不同的对象**

当我们使用HashSet集合时,需要注意:将对象存储在HashSet之前,要确保对象重写了equals()hashCode()方法,
即如果要把一个对象放在HashSet中,如果重写了该对象的equals()方法,也要重写该对象的hashCode()方法,
修改的规则是:如果两个对象通过equals方法比较返回true时,这两个对象的hashCode一定也要相同。

HashSet集合的优点:
它可以通过一个对象快速查找到集合中的对象。hash算法的价值在于查找速度很快:它是通过将对象转变为对应的hashCode值,然后按照hashCode值在桶中对应位置取出该元素。

再看一个程序,程序来自:https://blog.csdn.net/xiaojie_570/article/details/79196249

package set;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class HashSetTest2 {
    public static void main(String[] args) {
        Set sets = new HashSet();
        sets.add(new HashSet2(1));
        sets.add(new HashSet2(-11));
        sets.add(new HashSet2(21));
        sets.add(new HashSet2(13));
        sets.add(new HashSet2(-1));

        //HashSet是无序集合,因此打印的结果是无序的
        System.out.println(sets);

        Iterator i = sets.iterator();
        //取出集合中第一个元素元素
        HashSet2 hs = (HashSet2) i.next();
        //将取出的元素赋新值,赋的值与集合中原有的元素之相同

        hs.count = 21;
        //集合中有两个元素一样
        System.out.println(sets);

        //从集合中移出值
        sets.remove(new HashSet2(21));

        System.out.println(sets);
        //集合中不包含21,因为按照hashCode值找到的槽内没有该值。
        System.out.println("sets.contains(new HashSets(21):"+sets.contains(new HashSet2(21)));
    }
}

class HashSet2 {
    int count;

    public HashSet2(int count) {
        super();
        this.count = count;
    }

    public String toString() {
        return "HashSet2 [count=" + count + "]" ;
    }

    public boolean equals(Object obj) {
        if(obj instanceof HashSet2){
            HashSet2 hs = (HashSet2) obj;
            if(this.count == hs.count)
                return true;
            return false;
        }
        return false;
    }

    public int hashCode() {
        return this.count;
    }
}

运行结果:

[HashSet2 [count=-1], HashSet2 [count=1], HashSet2 [count=21], HashSet2 [count=-11], HashSet2 [count=13]]
[HashSet2 [count=21], HashSet2 [count=1], HashSet2 [count=21], HashSet2 [count=-11], HashSet2 [count=13]]
[HashSet2 [count=21], HashSet2 [count=1], HashSet2 [count=-11], HashSet2 [count=13]]
sets.contains(new HashSets(21):false

2,TreeSet类:

该类实现的接口:Serializable, Cloneable, Iterable, Collection, NavigableSet, Set, SortedSet
TreeSet原理:

  • TreeSet是SortedSet接口的实现类,TreeSet可以确保集合元素处于排序状态。
  • TreeSet并不是根据元素的插入顺序进行排序的,而是根据元素的实际值大小来进行排序。
  • 与HashSet集合采用的hash算法来决定元素的存储位置不同,TreeSet采用红黑树的数据结构来存储集合元素。其中,TreeSet支持两种排序方法:自然排序和定制排序,在默认情况下,TreeSet采用自然排序。
  • 使用该集合的类必须实现Comparable接口中的compare To()方法,因为该类是有序的
    举例一下代码:
package set;

import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

public class TreeSetTest {
    public static void main(String[] args) {
        TreeSet nums = new TreeSet(); 
        nums.add(new Bird(1));
        nums.add(new Bird(2));
        nums.add(new Bird(3));
        nums.add(new Bird(4));

        System.out.println(nums);
    }
}

class Bird {
    int size;

    public Bird(int size) {
        super();
        this.size = size;
    }

    public String toString() {
        return "Bird [size=" + size + "]";
    }

}

结果发现编译器报错:

Exception in thread "main" java.lang.ClassCastException: set.Bird cannot be cast to java.lang.Comparable
    at java.util.TreeMap.put(TreeMap.java:542)
    at java.util.TreeSet.add(TreeSet.java:238)
    at set.TreeSetTest.main(TreeSetTest.java:11)

这主要是因为,TreeSet是实现SortedSet接口的类,因此他有排序功能,对应的传递进来的对象也要有需要可比较。先修改上述代码如下

package set;

import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

public class TreeSetTest {
    public static void main(String[] args) {
        TreeSet nums = new TreeSet(); 
        nums.add(new Bird(1));
        nums.add(new Bird(2));
        nums.add(new Bird(3));
        nums.add(new Bird(4));

        System.out.println(nums);
    }
}

class Bird implements Comparable<Bird>{
    int size;

    public Bird(int size) {
        super();
        this.size = size;
    }

    public String toString() {
        return "Bird [size=" + size + "]";
    }

    public int compareTo(Bird o) {
        // TODO Auto-generated method stub
        return this.size - o.size;
    }
}

运行结果如下:

[Bird [size=1], Bird [size=2], Bird [size=3], Bird [size=4]]

三、LinkedHashSet:

该类实现的接口:Serializable, Cloneable, Iterable, Collection, Set
LinkedHashSet原理:

  • LinkedHashSet是HashSet接口的实现类,LinkedHashSet集合也是根据元素的hashCode值来决定元素的存储位置,但他同时使用链表维护元素的次序,这样使得元素看起来是以插入的顺序保存的。也就是说,当遍历LinkedHashSet集合里的元素时,LinkedHashSet将会按照元素的添加顺序来访问集合里的元素。
  • LinkedHashSet需要维护元素的插入顺序,因此性能略低于HashSet的性能,但在迭代访问Set里的全部元素时将会有很好的性能,因为它以链表来维护内部的顺序。
  • 虽然LinkedHashSet使用了链表记录集合元素的添加顺序,但LinkedHashSet依然是HashSet,因此它仍然是不允许集合元素重复。
  • 下面看实例代码:
package set;

import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;

public class LinkedHashSet1 {
    public static void main(String[] args) {
        Set s = new LinkedHashSet();
        s.add(new String("e"));
        s.add(new String("b"));
        s.add(new String("a"));
        s.add(new String("c"));

        System.out.println(s);
    }
}

运行结果:

[e, b, a, c]

参考:
感谢:https://blog.csdn.net/xiaojie_570/article/details/79196249

你可能感兴趣的:(java集合源码)