java容器之Map接口

HashMap類

在介紹hashMap之前,有必要介紹下關於散列表的知識。(太久沒用到散列表,沒想到一時竟然忘記了它的存在的意義了,看來不多做筆記真不行)

散列表:支持以常數時間對任何命名項的檢索或刪除。爲什麼能夠這麼會有這種效果呢?原理是:定義一個空的tablesize大小數組,每個要插入元素根據散列函數取得數組的下標,所以要能根據元素進行線性的檢索;

衝突:就是不同的項通過散列函數取得相同的下標(專業術語:相撞),就會造成衝突,我們可以通過線性探測,二次探測等方法來獲得新的下標,也可以用hashMap類中的解決方案,如果出現不同的項有相同的下標,將第二項掛在第一項下,(就是第一項的next指向第二項)依次類推;

但是散列表犧牲了元素插入的有序性;


hashMap簡單介紹:

          hashMap是基於散列表的原理,類中定義了變量table數組來存放數據,數組類型爲Entry類型,(Entry是hashMap的一個靜態內部類);包含這些基本的元素,capacity(容量),loadFactor(加載因子),size,threshold;Capacity:即數組的長度(初始值爲16),loadFactor來決定threshold的大小(默認爲0.75),Threshold = loadFactor* Capacity;也就是說如果size>=Threshold,那麼我們需要擴充數組的大小capacity,如果此時的capacity的大小已經達到最大值Integer.MAX_VALUE,那麼不需改變數據大小,繼續添加數組即可;

         每組數據,對應一個Entry類和一個table的下標,這個小標的取值跟key的hash值和table的長度有關;所以有一點特別重要,在擴充數組的時候,定義了一個新的數組後,由於table的長度變化,需要對每一個Entry對象重新計算小標值,重新將數據填入數組中;

hashMap的實現不是同步的。

         它的部分源代碼如下:我們一一分析:

//繼承類AbstractMap和接口Map,Cloneable,Serializable;

public class HashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable

類包含的成員

//散列表
static final Entry<?,?>[] EMPTY_TABLE = {};
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
//元素的個數
transient int size;
//加載因子,就是說如果要存放size=4的數據,根據加載因子,我們定義散列表的大小爲4/loadFactor;
//加載因子的默認值是0.75
final float loadFactor;
//值爲 (capacity * load factor).
int threshold;


存放一組元素的操作如下:
//向map裏頭存放數據
public V put(K key, V value) {
	//如果數組爲空,初始化數組inflateTable(),第一次存放//時出現
    if (table == EMPTY_TABLE) {
        inflateTable(threshold);
    }
    //允許key爲空,此時的數組下標爲0,(剩下的)操作相同;
    if (key == null)
        return putForNullKey(value);
    int hash = hash(key);
    int i = indexFor(hash, table.length);
    //這段代碼是重點,告訴我們散列表存數的形式,找到下標後,判斷該位置是否已經有數據,如果有掛載在已有的數據下;
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }
modCount++;
//該方法,將這組數據創建一個Entry對象保存,同時存放了hash值和小標值,當然要先判斷是否數組達到了上限,具體請看方法的內容
    addEntry(hash, key, value, i);
    return null;
}
void addEntry(int hash, K key, V value, int bucketIndex) {
    if ((size >= threshold) && (null != table[bucketIndex])) {
    	//用來擴充數組的,如果數組已經達到上限,立刻return;
    	//如果沒有,那麼遍歷原來的數組,重新定義下標。。。
        resize(2 * table.length);
        hash = (null != key) ? hash(key) : 0;
        //重新定義下標
        bucketIndex = indexFor(hash, table.length);
    }
    
    createEntry(hash, key, value, bucketIndex);
}
//新建一個Entry對象,然後存放在table中
void createEntry(int hash, K key, V value, int bucketIndex) {
    Entry<K,V> e = table[bucketIndex];
    table[bucketIndex] = new Entry<>(hash, key, value, e);
    size++;
}

接下來,是時候介紹小Entry類了,其實它僅僅就是用來存放一組數據和一些基本信息的結構而已哈;

//內部類 Entry,繼承接口Map.Entry<K,V>,簡化了部分代碼
static class Entry<K,V> implements Map.Entry<K,V> {
    final K key;
    V value;
    Entry<K,V> next;
    int hash;
    Entry(int h, K k, V v, Entry<K,V> n) {
        value = v;
        next = n;
        key = k;
        hash = h;
    }

    public final K getKey() {
        return key;
    }
    public final V getValue() {
        return value;
    }
    public final V setValue(V newValue) {
        V oldValue = value;
        value = newValue;
        return oldValue;
    }
}

接下來介紹下,如果根據key來取得value值,知道原理後,我們應該知道怎麼取值了吧,由key獲得hash值,在與table.length計算出下標,從該位置上找出key對應的value。

  public V get(Object key) {
        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;
        }

        int hash = (key == null) ? 0 : hash(key);
        for (Entry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            Object k;
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
}

    ListHashMap類


簡單的介紹:

LinkedHashMap繼承類HashMap,在HashMap的基礎上提供了記錄輸入順序的方法,也就是用鏈表的方式,對每一次輸入進行記錄;


//定義了成員變量,記錄鏈表的頭部

private transient Entry<K,V> header;

//覆蓋了HashMap的init()的方法,原本在HashMap中它是一個空方法,但是在第一次初始化數組的時候會被調用;

  @Override
    void init() {
        header = new Entry<>(-1, null, null, null);
        header.before = header.after = header;
    }

//當數組需要調整大小的時候,會調用這個方法,(覆蓋的原因僅僅是因爲用鏈表來遍歷效率更高一點哈)

  @Override
    void transfer(HashMap.Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
        for (Entry<K,V> e = header.after; e != header; e = e.after) {
            if (rehash)
                e.hash = (e.key == null) ? 0 : hash(e.key);
            int index = indexFor(e.hash, newCapacity);
            e.next = newTable[index];
            newTable[index] = e;
        }
}

//這是ListHashMap中類Entry<K,V>一個方法,當插入新數據的時候會調用該方法,此方法在HashMap. Entry類中是一個空的方法;在此,該方法是爲了維護數據的有序性

void recordAccess(HashMap<K,V> m) {
            LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
            if (lm.accessOrder) {
                lm.modCount++;
                remove();
                addBefore(lm.header);
            }
        }

TreeMap


TreeMap是基於紅黑樹實現的,紅黑樹的具體分析在以後的章節會詳細介紹,現在大致說下紅黑樹的特點:

二叉查找樹特點:1,父節點的值大於兩個兒子的值;2,同一個父親,右兒子的值大於左兒子的值;二叉查找樹有個明顯的缺陷,就是不斷的操作最終會導致樹非常不平衡(也就是樹的左右孩子的深度不一樣),所以誕生了平衡二叉樹,但是平衡二叉樹對樹的要求比較高;由此產生了紅黑樹;對於紅黑樹的操作的時間複雜度,構造樹的方法,以後再詳細介紹;

針對TreeMap分析:

類成員變量
private final Comparator<? super K> comparator;
private transient Entry<K,V> root = null;
構造函數:
//如果不傳入參數,則默認按照自然順序(就是hashcode的大小)排序
    public TreeMap() {
        comparator = null;
    }
    public TreeMap(Comparator<? super K> comparator) {
        this.comparator = comparator;
    }

如果compare後,返回0那麼認爲這兩個數是一樣的,在TreeMap中對同樣的數,覆蓋原則哈,

測試代碼

public class person1{

		int height;
		int age;
		
		person1(int height,int age){
			this.height = height;
			this.age = age;
		}
		
	
	public static void main(String args[]){
	
		TreeMap<person1,String> tr =  new TreeMap<person1, String>(	new personComparator());
		person1 p1 = new person1(1,1);
		person1 p2 = new person1(1,2);
		person1 p3 = new person1(2,3);
		tr.put(p1, "p1");
		tr.put(p2, "p2");
		tr.put(p3, "p3");
		
		System.out.print((tr.get(p1)+tr.get(p2)+tr.get(p3)));
	}
	
}
class personComparator implements Comparator<person1>{

	@Override 
	public int compare(person1 o1, person1 o2) {
		// TODO Auto-generated method stub
		if(o1.height==o2.height){
			return 0;
		}else if(o1.height>o2.height){
			return 1;
		}else{
			return -1;
		}
	}
	
}

測試結果如下:

p2p2p3
























發佈了70 篇原創文章 · 獲贊 2 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章