Java集合之HashSet源碼分析


一、HashSet簡介

  HashSet是Set接口典型實現,它按照Hash算法來存儲集合中的元素,具有很好的存取和查找性能。主要具有以下特點:

  • 不保證set的迭代順序
  • HashSet不是同步的,如果多個線程同時訪問一個HashSet,要通過代碼來保證其同步
  • 集合元素值可以是null

  當向HashSet集合中存入一個元素時,HashSet會調用該對象的hashCode()方法來得到該對象的hashCode值,然后根據該值確定對象在HashSet中的存儲位置。在Hash集合中,不能同時存放兩個相等的元素,而判斷兩個元素相等的標准是兩個對象通過equals方法比較相等並且兩個對象的HashCode方法返回值也相等。

  下面的例子說明了上述特性:

public class Person
{
    String name;
    int age;
    
    public Person(String name,int age)
    {
        this.name=name;
        this.age=age;
    }
    
    public String getName()
    {
        return name;
    }

    public void setName(String name)
    {
        this.name = name;
    }

    public int getAge()
    {
        return age;
    }

    public void setAge(int age)
    {
        this.age = age;
    }

    //當對象的名字和姓名相同即返回true
    public boolean equals(Object obj)
    {
        if(obj==null)
            return false;
        if((this.name.equals(((Person)obj).name) && this.age==((Person)obj).age))
                return true;
        else
            return false;
    }
    
}

  此時添加兩個name和age均相同的Person對象實例到HashSet中:

public class HashSetDemo
{

    public static void main(String[] args)
    {
        HashSet<Person> hs = new HashSet<>();
        
        Person p1=new Person("xujian", 23);
        Person p2=new Person("xujian", 23);
        hs.add(p1);
        hs.add(p2);
        for(Person p:hs)
        {
            System.out.println(p.name+"---"+p.age);
        }
    }
}

  

  可見,HashSet中存放了兩個name和age均相同的Person對象。

  接下來我們重寫一下Person類的hashCode方法,使其返回相同的HashCode。

public class Person
{
    String name;
    int age;
    
    public Person(String name,int age)
    {
        this.name=name;
        this.age=age;
    }
    public String getName()
    {
        return name;
    }
    public void setName(String name)
    {
        this.name = name;
    }
    public int getAge()
    {
        return age;
    }
    public void setAge(int age)
    {
        this.age = age;
    }

    public int hashCode()
    {
        // TODO 自動生成的方法存根
        return 1;
    }
    //當對象的名字和姓名相同即返回true
    public boolean equals(Object obj)
    {
        if(obj==null)
            return false;
        if((this.name.equals(((Person)obj).name) && this.age== ((Person)obj).age))
                return true;
        else
            return false;
    }
    
}

  再次執行向HashSet添加元素操作,會發現此時HashSet只保存了一個。

  

  HashSet中每一能存儲元素的槽位通常稱為“桶”,如果有多個元素的hashCode相同,但是通過equals方法比較返回false,就需要在一個桶上存放多個元素。

二、HashSet源碼分析

  1、構造函數

  HashSet的底層實際上是由HashMap實現的。其四個構造函數分別對應相應的HashMap。

  //構造一個新的,空的HashSet,其底層 HashMap實例的默認初始容量是 16,加載因子是 0.75
    public HashSet() 
    {
        map = new HashMap<>();
    }

    //構造一個包含指定 collection 中的元素的新 set
    public HashSet(Collection<? extends E> c) 
    {
        map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
        addAll(c);
    }

   //構造一個新的空 set,其底層 HashMap 實例具有指定的初始容量和指定的加載因子
    public HashSet(int initialCapacity, float loadFactor) {
        map = new HashMap<>(initialCapacity, loadFactor);
    }

    //構造一個新的空 set,其底層 HashMap 實例具有指定的初始容量和默認的加載因子0.75
    public HashSet(int initialCapacity)
    {
        map = new HashMap<>(initialCapacity);
    }

  2、HashSet常用方法

  boolean add(E e): 如果此 set 中尚未包含指定元素,則添加指定元素

public boolean add(E e) 
    {
        //調用map的put方法,其中value值為靜態的Object對象
        return map.put(e, PRESENT)==null;
    }

  void clear():從此 set 中移除所有元素

 public void clear() 
    {
        map.clear();
    }

  Object clone():返回此 HashSet 實例的淺表副本

 public Object clone()
    {
        try 
        {
            //調用父類的clone方法
            HashSet<E> newSet = (HashSet<E>) super.clone();
            newSet.map = (HashMap<E, Object>) map.clone();
            return newSet;
        } 
        catch (CloneNotSupportedException e)
        {
            throw new InternalError(e);
        }
    }

  boolean contains(Object o):如果此 set 包含指定元素,則返回 true

public boolean contains(Object o) 
    {
        return map.containsKey(o);
    }

  boolean isEmpty():如果此 set 不包含任何元素,則返回 true

public boolean isEmpty() 
    {
        return map.isEmpty();
    }

  Iterator<E> iterator():返回對此 set 中元素進行迭代的迭代器

 public Iterator<E> iterator()
    {
        return map.keySet().iterator();
    }

  boolean remove(Object o):如果指定元素存在於此 set 中,則將其移除

 public boolean remove(Object o) 
    {
        return map.remove(o)==PRESENT;
    }

  int size():返回此 set 中的元素的數量

    public int size() 
    {
        return map.size();
    }

三、HashSet的應用示例代碼

public class HashSetDemo
{
    public static void main(String[] args)
    {
        HashSet<String> hs1 = new HashSet<>();   //無參構造函數新建一個默認大小為16,裝載因子為0.75的HashSet
        System.out.println("調用add函數");
        hs1.add("Hello");
        hs1.add("World");
        hs1.add("nihao");
        
        HashSet<String> hs2 = new HashSet<>(hs1); //構造一個包含hs1中元素的HashSet
        
        System.out.println("調用remove函數");
        hs1.remove("Hello");
        for(String str:hs1)
            System.out.println(str);
    
        System.out.println("調用clone函數");
        HashSet<String> hs3=(HashSet<String>) hs2.clone();
        for(String str:hs3)
            System.out.println(str);
        
        System.out.println("利用迭代器遍歷HashSet中元素");
        Iterator<String> it=hs2.iterator();
        while(it.hasNext())
        {
            System.out.println(it.next());
        }
        
        System.out.println("調用size函數");
        
        System.out.print(hs2.size());
    }
}

  執行結果如圖:

  

Java集合系列:

    Java集合系列之HashMap源碼分析

       Java集合系列之LinkedList源碼分析

   Java集合系列之ArrayList源碼分析


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM