1. 程式人生 > >散列和散列碼(1)

散列和散列碼(1)

相同 ole urn put arr map this () binarys

一.前言

1.1== 符號

==比較的對象內存地址,也就是兩個引用指向的地址,相同即指向同一個對象則返回true。Object中的equals(Object obj)方法默認使用的是==方法比較兩個對象:

    public boolean equals(Object obj) {
        return (this == obj);
    }
1.2散列數據結構中鍵類的equals()、hashCode()方法必須重載,否則這些數據結構便不能正確處理鍵;
1.3重載equals(Object obj)原則

重載的equals方法需要滿足一下條件:

  1. 自反性:x.equals(x)返回true;
  2. 對稱性:x.equals(y)與y.equals(x)返回值相同;
  3. 傳遞性:x.equals(y)為true且y.equals(z)為true,則x.equals(y)應該為true;
  4. 一致性:用於比較等價的信息未變則不管調用多少次x.equals(y)一直不應該改變;
  5. x不為null,則x.equals(null)一定為false,否則為TRUE;
1.4 instanceof關鍵字:object instanceof ClassZ

instanceof關鍵字會先檢查左側對象是否為null,為null則返回false;

二.基本思想

2.1散列速度

如下示例SlowMap慢的原因在於對於鍵的查詢使用的是線性查詢——線性查詢是最慢的查詢方式

對於鍵的查詢,保持鍵的排序而且使用Collections.binarySearch()進行查詢解決速度問題的方案之一。

散列的思想是:數組是最快的數據結構將鍵的信息保存在數組中,鍵調用方法int hashCode()返回的數字(即散列碼)就是數組下標。

因為Map可以保存數量不確定的值,而數組不能調整容量,因此我們允許不同鍵產生相同下標,使用“外部鏈接”解決沖突,即數組保存list,如下圖:
技術分享圖片

因此一次查詢流程是:

  1. 使用對象計算散列碼,然後O(1)時間找打數組下標;
  2. 如果找到了例如上圖下標為11的位置,則對list使用equals()方法進行先行的查詢,找到對象對應的確切位置,也就是我們要找的鍵信息

以上,只對很少的元素進行比較,這也是HashMap速度快的原因,簡單實現見第二塊代碼區。

package containers;

import java.util.*;

public class SlowMap<K,V> extends AbstractMap<K,V> {
    private List<K> keys = new ArrayList<>();
    private List<V> values=new ArrayList<>();

    @Override
    public V put(K key, V value) {
        V oldValue=get(key);//如果key不存在則返回null;
        if(!keys.contains(key)){//不存在則添加
            keys.add(key);
            values.add(value);
        }else {//存在著覆蓋
            values.set(keys.indexOf(key), value);
        }
        return oldValue;
    }

    @Override
    public V get(Object key){
        if(!keys.contains(key)) {//不存在則返回null
            return null;
        }
        return values.get(keys.indexOf(key));// fixme 第幾個key就返回第幾個key,不管key的具體內容;
    }

    @Override
    public Set<Map.Entry<K, V>> entrySet() {
        Set<Map.Entry<K,V>> set=new HashSet<>();

        Iterator<K> ki=keys.iterator();
        Iterator<V> vi=values.iterator();

        while (ki.hasNext()){
            set.add(new SimpleEntry<K, V>(ki.next(),vi.next()));
        }
        return set;
    }
}

HashMap的簡單實現

package containers;

import java.util.*;

/**
 * @author 杜艮魁
 * @date 2018/1/25
 */
public class SimpleHashMap<K,V> extends AbstractMap<K,V>{

    //為hash_table設置一個初始值:通常使用質數來保證散列均勻
    static final int SIZE=997;


    //存放鍵值對,但是key決定存放位置:buckets是數組,而且元素時鏈表
    LinkedList<SimpleEntry<K,V>> buckets[]=new LinkedList[SIZE];

    @SuppressWarnings("unchecked")
    @Override
    public V put(K key, V value) {
        V oldValue=null;
        int index=Math.abs(key.hashCode())%SIZE;//fixme key值hashCode信息在數組中存放位置,也就是數組下標
        if(buckets[index]==null)//如果數組這個位置鏈表沒有節點,則對這個位置頭節點進行初始化
            buckets[index]=new LinkedList<>();
        LinkedList<SimpleEntry<K,V>> bucket=buckets[index];//bucket指向元素要存放的數組下標對應的頭結點
        SimpleEntry<K,V> pair=new SimpleEntry<K, V>(key,value);//要存放的鍵值對放進數據存放形式的元素裏
        boolean found=false;//數據是否已經出現在了Map中
        ListIterator<SimpleEntry<K,V>> it=bucket.listIterator();//遍歷鍵對應的數組位置鏈表,將其放進list中
        while(it.hasNext()){//遍歷這個位置的list
            SimpleEntry<K,V> iPair=it.next();
            if(iPair.getKey().equals(key)){//如果鍵equals相等則新值替代舊值,則—之前通過找數組下標的hashCode()已經相等
                oldValue=iPair.getValue();
                it.set(pair);//用新值代替舊值
                found=true;
                break;
            }
        }
        if(!found){//如果沒有找到,則添加到末尾
            buckets[index].add(pair);
        }
        return oldValue;
    }

    @Override
    public V get(Object key) {//註意參數不是K
        int index=Math.abs(key.hashCode())%SIZE;
        if(buckets[index]==null) return null;//如果定位到的鏈表為null,則返回null
        for (SimpleEntry<K,V> iPair:buckets[index])//否則遍歷這個鏈表,比較key值
            if(iPair.getKey().equals(key))
                return iPair.getValue();
        return null;
    }

    @Override
    public Set<Entry<K, V>> entrySet() {
        Set<Entry<K,V>> set=new HashSet<>();
        for (LinkedList<SimpleEntry<K,V>>bucket:buckets) {//定位數組位置
            if(bucket==null) continue;//如果這個數組所在
            for (SimpleEntry<K,V> mpair:bucket)//不為空的話則遍歷裏邊元素放進set
                set.add(mpair);
        }
        return set;
    }
}

散列和散列碼(1)