1. 程式人生 > >Java總結——常見Java集合實現細節(1)

Java總結——常見Java集合實現細節(1)

except empty 找到 dex index shc reat int .net

Java提高——常見Java集合實現細節(1)
2018年04月18日 15:07:35 閱讀數:25

技術分享圖片

集合關系圖

Set和Map

set代表一種集合元素無序、集合元素不可重復的集合

map代表一種由多個key-value對組成的集合

set和map的關系

技術分享圖片技術分享圖片

set和map的接口十分類似。

Map的key有一個特征:所有key不能重復,且key之間沒有順序,也就是說將所有key組合起來就是一個Set集合。

Map——>Set : Map中提供了 Set<k> keySet() 返回所有key集合

Set——>Map : 對於map而言,每個元素都是key-value的set集合。把value看成是key的附屬物。

為了把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();
    }
}
HashMap和HashSet

HashSet : 系統采用hash算法決定集合的存儲位置,這樣可以保證快速存、取元素;

HashMap:系統將value當成key的附屬品,系統根據hash算法決定key的位置,這樣可以保證快速存、取key,而value總是跟著key存儲。

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本質上是相同的。

轉載。 https://blog.csdn.net/qq_30604989/article/details/79928595

Java總結——常見Java集合實現細節(1)