一、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集合系列: