集合關係圖html
set表明一種集合元素無序、集合元素不可重複的集合java
map表明一種由多個key-value對組成的集合
算法
set和map的接口十分相似。數組
Map的key有一個特徵:全部key不能重複,且key之間沒有順序,也就是說將全部key組合起來就是一個Set集合。app
Map——>Set : Map中提供了 Set<k> keySet() 返回全部key集合ide
Set——>Map : 對於map而言,每一個元素都是key-value的set集合。把value當作是key的附屬物。post
爲了把set擴展成map,能夠新增定義一個SimpleEntry類,該類表明一個key-value對:性能
class SimpleEntry<K,V> implements Map.Entry<K,V>,Serializable{ private final K key; private V value; //定義以下2個構造器 public SimpleEntry(K key, V value) { this.key = key; this.value = value; } public SimpleEntry(Map.Entry<? extends K,? extends V> entry){ this.key = (K) entry.getKey(); this.value = (V) entry.getValue(); } @Override public K getKey() { return key; } @Override public V getValue() { return value; } //改變key-value對的value值 @Override public V setValue(V value) { V oldValue = this.value; this.value = value; return oldValue; } //根據key比較兩個SimpleEntry是否相等 @Override public boolean equals(Object o){ if(o == this){ return true; } if(o.getClass() == SimpleEntry.class){ SimpleEntry se = (SimpleEntry) o; return se.getKey().equals(getKey()); } return false; } //根據key計算hashCode @Override public int hashCode(){ return key == null ? 0 : key.hashCode(); } @Override public String toString() { return key + "=" + value ; } } //繼承HashSet,實現一個map public class SetToMap<K,V> extends HashSet<SimpleEntry<K,V>> { //實現全部清空key-value對的方法 @Override public void clear(){ super.clear(); } //判斷是否包含某個key public boolean containsKey(Object key){ return super.contains(new SimpleEntry<K, V>((K) key,null)); } //判斷是否包含某個value public boolean containsValue(Object value){ for (SimpleEntry se: this) { if (se.getValue().equals(value)){ return true; } } return false; } //根據key支出相應的value public V getValue(Object key){ for (SimpleEntry se : this) { if (se.getKey().equals(key)) { return (V) se.getValue(); } } return null; } //將指定key-value放入集合中 public V put(K key,V value){ add(new SimpleEntry<K, V>(key,value)); return value; } //將另外一個Map的key-value放入該map中 public void putAll(Map<? extends K,? extends V> m){ for (K key:m.keySet()){ add(new SimpleEntry<K, V>(key,m.get(key))); } } //根據指定的key刪除指定的key-value public V removeEntry(Object key){ for (Iterator<SimpleEntry<K,V>> it = this.iterator();it.hasNext();){ SimpleEntry<K,V> en = it.next(); if (en.getKey().equals(key)){ V v = en.getValue(); it.remove(); return v; } } return null; } //獲取該map中包含多少個key-value對 public int getSize(){ return super.size(); } }
HashSet : 系統採用hash算法決定集合的存儲位置,這樣能夠保證快速存、取元素;flex
HashMap:系統將value當成key的附屬品,系統根據hash算法決定key的位置,這樣能夠保證快速存、取key,而value老是跟着key存儲。this
Java集合其實是多個引用變量所組成的集合,這些引用變量指向實際的Java對象。
class Apple{
double weight; public Apple(double weight) { this.weight = weight; } } public class ListTest { public static void main(String[] args) { //建立兩個Apple對象 Apple a = new Apple(1.2); Apple b = new Apple(2.2); List<Apple> appleList = new ArrayList<Apple>(4); //將兩個對象放入list中 appleList.add(a); appleList.add(b); //判斷從集合中取出的引用變量和原有的引用變量是否指向同一個元素 System.out.println(appleList.get(0)==a); System.out.println(appleList.get(1)==b); } }
HashMap類的put(K key,V value)源碼:
public V put(K key, V value) { //若是key爲null則調用putForNullKey方法 if (key == null) return putForNullKey(value); //根據key計算hash值 int hash = hash(key); //搜索指定hash值在table中對應的位置 int i = indexFor(hash, table.length); //若是i索引處的Entry不爲null,經過循環不斷遍歷e元素的下一個元素 for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; //找到指定key與須要放入的key相等(hash值相同,經過equals比較返回true) if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } //若是i索引處的key爲null,則代表此處尚未Entry modCount++; //將key、value添加到i索引處 addEntry(hash, key, value, i); return null; }
每一個map.entry就是一個key-value對。當hashmap存儲元素時,先計算key的hashCode值,決定Entry的存儲位置,若是兩個Entry的key的hashCode返回值相同,則存儲位置相同;若是這兩個Entry的key經過equals比較返回true,添加的Entry的value將會覆蓋集合中原有Entry的value,可是key不會覆蓋;若是equals返回false,則新添加的Entry與集合中原有的Entry將會造成Entry鏈,並且新添加的Entry位於鏈的頭部。
addEntry方法:
void addEntry(int hash, K key, V value, int bucketIndex) { //若是map中的Entry(key-value對)數量超過了極限 if ((size >= threshold) && (null != table[bucketIndex])) { //把table對象的長度擴充到2倍 resize(2 * table.length); hash = (null != key) ? hash(key) : 0; bucketIndex = indexFor(hash, table.length); } //建立新的entry createEntry(hash, key, value, bucketIndex); }
void createEntry(int hash, K key, V value, int bucketIndex) { //獲取指定bucketIndex索引處的Entry Entry<K,V> e = table[bucketIndex]; //將新建立的Entry放入bucketIndex索引處,讓新的Entry指向原來的Entry table[bucketIndex] = new Entry<>(hash, key, value, e); size++; }
系統將新添加的Entry對象放入table數組的bucketIndex索引處。若是bucketIndex索引處有一個Entry對象,新添加的Entry對象指向原有的Entry對象(產生一個Entry鏈);若是bucketIndex索引處沒有Entry對象,新建的Entry對象則指向null,沒有產生Entry鏈。
size:包含了HashMap中所包含的key-value對的數量。
table:一個普通的數組,每一個數組都有固定長度,這個數組的長度也就是HashMap的容量
threshold:包含了HashMap能容納key-value對的極限,它的值等於HashMap的容量乘以負載因子(load factor)。
HashMap包含的構造方法:
1)HashMap() : 構建一個初始容量爲16,負載因子爲0.75的HashMap
2)HashMap(int InitialCapacity) : 構建一個初始容量爲InitialCapacity,負載因子爲0.75的HashMap
3)HashMap(int InitialCapacity,float loadFactory) : 構建一個指定初始容量和負載因子的HashMap
//指定初始容量和負載因子建立HashMap
public HashMap(int initialCapacity, float loadFactor) { //初始容量不能爲負 if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); //若是初始容量大於最大容量,則讓初始容量等於最大容量 if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; //負載因子必須是大於0的值 if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); this.loadFactor = loadFactor; threshold = initialCapacity; init(); }
建立HashMap的實際容量並不等於HashMap的實際容量。一般來講,HashMap的實際容量總比initialCapacity大一些,除非指定的initialCapacity參數值正好是2的n次方。掌握了容量分配以後,應建立HashMap時將initialCapacity參數值指定爲2的n次方。
當系統開始初始化HashMap時,系統會建立一個長度爲capacity的Entry數組。這個數組裏能夠存儲元素的位置被稱爲」桶(bucket)「每一個bucket都有指定的索引,系統能夠根據索引快速訪問該bucket裏存儲的元素。
不管什麼時候,HashMap的每一個」bucket「中只能存儲一個元素(一個Entry)。因爲Entry對象能夠包含一個引用變量(就是Entry構造器的最後一個參數)用於指向下一個Entry,所以:HashMap中的bucket只有一個Entry,但這個Entry指向另外一個Entry,這就造成了一個Entry鏈。
HashMap的存儲:
當HashMap中沒有產生Entry鏈時,具備最好的性能。
HashMap類的get方法:
public V get(Object key) { //若是key是null,調用getForNullKey取出對應的value值 if (key == null) return getForNullKey(); Entry<K,V> entry = getEntry(key); return null == entry ? null : entry.getValue(); }
final Entry<K,V> getEntry(Object key) { if (size == 0) { return null; } //根據key值計算出hash碼 int hash = (key == null) ? 0 : hash(key); //直接取出table數組中指定索引處的值 for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; //搜索entry鏈的下一個entry e = e.next) { Object k; //若是該entry的key與被搜索的key相同 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } return null; }
若是HashMap的每一個bucket裏只有一個Entry,則能夠根據索引快速的取出。在發生「Hash衝突」的狀況下,單個bucket裏存儲的是一個Entry鏈,系統只能按順序遍歷每一個Entry,直到找到想要的Entry爲止。
總結:HashMap在底層將key-value當成一個總體進行處理,這個總體就是一個Entry對象。HashMap底層採用一個Entry[]數組保存全部的key-value對,當須要存儲一個Entry對象時,根據hash算法來決定其存儲位置;當須要取出一個Entry對象時,也會根據hash算法找到其存儲位置,直接取出該Entry。
建立一個HashMap時,有個默認的負載因子,其默認值爲0.75。這是時間和空間的折衷:增大負載因子能夠減小Hash表(就是Entry數組)所佔用的內存空間,但會增長查詢的時間開銷(最頻繁的put、get操做都要用到查詢);減少負載因子能夠提升查詢的性能,但會下降Hash表佔用的內存空間。
對於HashSet而言,他是基於HashMap實現的。HashSet底層採用HashMap來保存全部元素。
源碼解析:
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable { static final long serialVersionUID = -5024744406713321676L; //使用HashMap的key來保存HashSet中的全部元素 private transient HashMap<E,Object> map; // 定義一個虛擬的Object對象做爲HashMap的value private static final Object PRESENT = new Object(); /** * 初始化HashSet,底層會初始化一個HashMap * default initial capacity (16) and load factor (0.75). */ public HashSet() { map = new HashMap<>(); } /** * 以指定的initialCapacity、loadFactor建立HashSet * 其實就是以相應的參數建立HashMap */ public HashSet(Collection<? extends E> c) { map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16)); addAll(c); } public HashSet(int initialCapacity, float loadFactor) { map = new HashMap<>(initialCapacity, loadFactor); } public HashSet(int initialCapacity) { map = new HashMap<>(initialCapacity); } HashSet(int initialCapacity, float loadFactor, boolean dummy) { map = new LinkedHashMap<>(initialCapacity, loadFactor); } /** * 調用map的keySet方法來返回全部的key */ public Iterator<E> iterator() { return map.keySet().iterator(); } /** * 調用HashMap的size方法返回Entry的數量, * 獲得該Set裏元素的個數 */ public int size() { return map.size(); } /** * 調用HashMap的isEmpty判斷該HashSet是否爲空 * 當HashMap爲空時,對應的HashSet也爲空 */ public boolean isEmpty() { return map.isEmpty(); } /** * 調用HashMap的containsKey判斷是否包含指定的key * HashSet的全部元素是經過HashMap的key來保存的 */ public boolean contains(Object o) { return map.containsKey(o); } /** * 將指定元素放入HashSet中,也就是將該元素做爲key放入HashMap */ public boolean add(E e) { return map.put(e, PRESENT)==null; } /** * 調用HashMap的remove方法刪除指定的Entry對象,也就刪除了HashSet中對應的元素 */ public boolean remove(Object o) { return map.remove(o)==PRESENT; } /** * 調用map的clear方法清空全部的Entry,也就清空了HashSet中全部的元素 */ public void clear() { map.clear(); }
從源碼能夠看出HashSet只是封裝了一個HashMap對象來存儲全部集合元素。實際是由HashMap的key來保存的,而HashMap的value則是存儲了一個PRESENT,一個靜態的Object對象。HashSet絕大部方法是調用HashMap的方法來實現的,所以HashMap和HashSet本質上是相同的。