1. 程式人生 > >自定義實現類似WeakHashMap集合類

自定義實現類似WeakHashMap集合類

import java.lang.ref.WeakReference;

/*
 *  below methods have public idenifer that can be invoked by outer
 */

public class UserDefinedMap {

    // 當entry.get()==null 時,說明這個key不存在引用,可以從引用陣列中移除
    static class Entry  extends WeakReference<Key>{
        Object value;
        Entry(Key k, Object v) {
//        	a = new WeakReference<String>(k);  
//        	k=null;  
        	super(k);
            value = v;
        }
//        public String get(){
//        	if(a.get() != null)
//        	     return a.get();
//			return null;
//        }
        public Object getValue(){
        	return value;
        }
    }
    
    private static final int INITIAL_CAPACITY = 16;// 初始化的容量
    private Entry[] table;
    private int size = 0;  // table中儲存的數量
    private int threshold; // 擴容因子,初始化為0

    private void setThreshold(int len) {
        threshold = len * 2 / 3;
    }

    private static int nextIndex(int i, int len) {
        return ((i + 1 < len) ? i + 1 : 0);
    }

    private static int prevIndex(int i, int len) {
        return ((i - 1 >= 0) ? i - 1 : len - 1);
    }

/*    LocalMap(String firstKey, Object firstValue) {
        table = new Entry[INITIAL_CAPACITY];
        int i = firstKey.hashCode() & (INITIAL_CAPACITY - 1);
        table[i] = new Entry(firstKey, firstValue);
        size = 1;
        setThreshold(INITIAL_CAPACITY);
    }*/
    
    UserDefinedMap() {
        table = new Entry[INITIAL_CAPACITY];
//        table[1] = new Entry("ab", new chunkMemory());
//        table[2] = new Entry("ac", new chunkMemory());
//        table[3] = new Entry("ad", new chunkMemory());
//        table[4] = new Entry("ae", new chunkMemory());
//        table[5] = new Entry("af", new chunkMemory());
//        size = 5;
        setThreshold(INITIAL_CAPACITY);
    }


    // 直接命中時返回,否則呼叫getEntryAfterMiss()方法進行尋找    
    public Entry getEntry(Key key) {
        int i = key.hashCode() & (table.length - 1);
        Entry e = table[i];
        
        if (e != null && e.get() != null && e.get().equals(key))
            return e;
        else
            return getEntryAfterMiss(key, i, e);
    }
    
    // 從i處理查詢的值為e,但是e.getKey()!=key
    // 規定傳入的key值不為null
    private Entry getEntryAfterMiss(Key key, int i, Entry e) {
        Entry[] tab = table;
        int len = tab.length;

        while (e != null) {
        	Key k = e.get();
            if (k != null && k.equals(key))
                return e;
            if (k == null)  // String虛引用可能被回收
                expungeStaleEntry(i);
            else
                i = nextIndex(i, len);
            e = tab[i];
        }
        /*
         * 有兩種可能返回值為null
         * 1、直接定位到的位置無儲存元素
         * 2、直接定位的位置不為空,但是後續的連續元素也沒有匹配的
         */
        return null; 
    }

    public void set(Key key, Object value) {

        Entry[] tab = table;
        int len = tab.length;
        int i = key.hashCode() & (len-1);

        // 可以看出,如果此時的key值相等,則value值會覆蓋
        for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
        	Key k = e.get();
            
            if (k != null && k.equals(key)) {
                e.value = value;
                return;
            }
            // Entry不為null,但是k卻為null
            if (k == null) {
                replaceStaleEntry(key, value, i);
                return;
            }
        }

        tab[i] = new Entry(key, value);
        int sz = ++size;
        if (!cleanSomeSlots(i, sz) && sz >= threshold)
            rehash();
    }

    // Remove the entry for key.
    private void remove(Key key) {
        Entry[] tab = table;
        int len = tab.length;
        int i = key.hashCode() & (len-1);
        for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
            if (e.get() == key) {
                e.clear();
                expungeStaleEntry(i);
                return;
            }
        }
    }

    /**
     * Replace a stale entry encountered during a set operation
     * with an entry for the specified key.  The value passed in
     * the value parameter is stored in the entry, whether or not
     * an entry already exists for the specified key.
     *
     * As a side effect, this method expunges all stale entries in the
     * "run" containing the stale entry.  (A run is a sequence of entries
     * between two null slots.)
     *
     * key和value表示要放入的key值和value值
     * staleSlot表示在搜尋key時遇到的第一個stale Entry的位置
     *       
     */
    private void replaceStaleEntry(Key key, Object value,int staleSlot) {
        Entry[] tab = table;
        int len = tab.length;
        Entry e;

        // We clean out whole runs at a time to avoid continual
        // incremental rehashing due to garbage collector freeing
        // up refs in bunches (whenever the collector runs).
        
        /*
         * 在StringMap的儲存策略中,衝突的元素一定會相鄰(trail和head的不為空Entry也是相鄰)
         * 這樣在插入時如果遇到stale Entry就應該呼叫這一段的元素
         * 找到第一個stale Entry元素位置為slotToExpunge
         */
        int slotToExpunge = staleSlot;
        for (int i = prevIndex(staleSlot, len); (e = tab[i]) != null; i = prevIndex(i, len))
            if (e.get() == null)
                slotToExpunge = i;

        // Find either the key or trailing null slot of run, whichever
        // occurs first
        for (int i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) {
        	Key k = e.get();

            // If we find key, then we need to swap it
            // with the stale entry to maintain hash table order.
            // The newly stale slot, or any other stale slot
            // encountered above it, can then be sent to expungeStaleEntry
            // to remove or rehash all of the other entries in run.
            if (k == key) {
                e.value = value;

                tab[i] = tab[staleSlot];
                tab[staleSlot] = e;

                // Start expunge at preceding stale entry if it exists
                if (slotToExpunge == staleSlot)
                       slotToExpunge = i;
                cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
                return;
            }

            // If we didn't find stale entry on backward scan, the
            // first stale entry seen while scanning for key is the
            // first still present in the run.
            if (k == null && slotToExpunge == staleSlot)
                slotToExpunge = i;
        }

        // If key not found, put new entry in stale slot
        tab[staleSlot].value = null;
        tab[staleSlot] = new Entry(key, value);

        // If there are any other stale entries in run, expunge them
        if (slotToExpunge != staleSlot)
            cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
    }

    /*
      null | Entry!=null | Entry!=null | null
      
     */
    public int expungeStaleEntry(int staleSlot) {
        Entry[] tab = table;
        int len = tab.length;

        // expunge entry at staleSlot
        tab[staleSlot].value = null;
        tab[staleSlot] = null;
        size--;
        
        // Rehash until we encounter null
        Entry e;
        int i;
        for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) {
        	Key k = e.get();
            if (k == null) {
                e.value = null;
                tab[i] = null;
                size--;
            } else {
                int h = k.hashCode() & (len - 1);
                /*
            	 * 當h!=i時,說明這個元素在儲存時遇到了衝突,現在清除了這個區段的一些元素
            	 * 本來的位置可能已經為空了。
            	 */
                if (h != i) {
                    tab[i] = null;

                    /*
                     * 
                     * 如果產生衝突,則向後掃描,直到找到一個空的位置來放Entry。注意在判斷
                     * 時使用的是tab[h]而不是Entry的key
                     * 
                     * 這樣可能會產生一個問題,如tab[h]的Entry的key為空,那麼這本來就已經
                     * 變成了一個stale entry,e元素應該儲存到這裡。但是使用的是tab[h]!=null
                     * 來判斷,難道這就是註釋所說的意思:
                     * 
                     * Unlike Knuth 6.4 Algorithm R, we must scan until null
                     * because multiple entries could have been stale. ???
                     * 
                     */
                    while (tab[h] != null)
                        h = nextIndex(h, len);
                    tab[h] = e;
                }
            }// end else
        }
        /*
         * 如上程式碼段掃描從staleSlot到i的元素,將key引用為null的從table陣列中
         * 移除,並且重新計算不為空Entry的儲存位置。
         */
        return i; // 此時tab[i]為空
    }

    /**
     * 一種移除無用Entry的策略,在新增或者移除無用Entry時被呼叫
     *
     *
     * @param n scan control: <tt>log2(n)</tt> cells are scanned,
     * unless a stale entry is found, in which case
     * <tt>log2(table.length)-1</tt> additional cells are scanned.
     * When called from insertions, this parameter is the number
     * of elements, but when from replaceStaleEntry, it is the
     * table length. (Note: all this could be changed to be either
     * more or less aggressive by weighting n instead of just
     * using straight log n. But this version is simple, fast, and
     * seems to work well.)
     *
     * 
     * 
     * .如果 a^x=N(a>0,且a≠1),那麼數x叫做以a為底N的對數(logarithm)
     *  param i:不是stale Entry,從後一個開始掃描
     *  param n:log2(n)個Entry將被掃描
     * 
     */
    private boolean cleanSomeSlots(int i, int n) {
        boolean removed = false;
        Entry[] tab = table;
        int len = tab.length;
        do {
            i = nextIndex(i, len);
            Entry e = tab[i];
            if (e != null && e.get() == null) {
                n = len;
                removed = true;
                i = expungeStaleEntry(i);
            }
        } while ( (n >>>= 1) != 0);
        return removed;
    }

    // 清除無用物件,如果不能有效縮小size數量,則進行2倍擴容
    private void rehash() {
        expungeStaleEntries();  //  首先掃描整個table陣列清除無用的Entry
        // Use lower threshold for doubling to avoid hysteresis
        if (size >= threshold - threshold / 4)
            resize();
    }

    // 擴容為原來的2倍
    private void resize() {
        Entry[] oldTab = table;
        int oldLen = oldTab.length;
        int newLen = oldLen * 2;
        Entry[] newTab = new Entry[newLen];
        int count = 0;  // 臨時輔助計算size

        for (int j = 0; j < oldLen; ++j) { // 擴容以後需要重新定位Entry的儲存位置
            Entry e = oldTab[j];
            if (e != null) {
            	Key k = e.get();
                if (k == null) {
                    e.value = null; // Help the GC
                } else {
                    int h = k.hashCode() & (newLen - 1);
                    while (newTab[h] != null)
                        h = nextIndex(h, newLen);
                    newTab[h] = e;
                    count++;
                }
            }
        }
        setThreshold(newLen); // 重新計算擴容因子
        size = count;
        table = newTab;
    }

    /*
     * 掃描整個table表來清除無用的Entry引用
     */
    private void expungeStaleEntries() {
        Entry[] tab = table;
        int len = tab.length;
        for (int j = 0; j < len; j++) {
            Entry e = tab[j];
            if (e != null && e.get() == null) // e不為空,但是已經沒有對e的引用時
                expungeStaleEntry(j);
        }
    }
}
public class chunkMemory {
	    private static final int SIZE = 500000;  
	    // 屬性d使得每個Grocery物件佔用較多記憶體,有80K左右  
	    private double[] d = new double[SIZE];  
	    private String id;  
	    public chunkMemory(String id){
	    	this.id = id;
	    }
	    
	    public String toString(){
	    	return id;
	    }
	    public void finalize() {  
	        System.out.println("Finalizing " + id);  
	    }  
}
public class Key {
    String id;  
    
    public Key(String id) {  
        this.id = id;  
    }  
    public String toString() {  
        return id;  
    }  
    public int hashCode() {  
        return id.hashCode();  
    }  
    public boolean equals(Object r) {  
        return (r instanceof Key) && id.equals(((Key) r).id);  
    }  
    public void finalize() {  
        System.out.println("Finalizing Key " + id);  
    }  
}
@Test
	public void add() throws InterruptedException {
		UserDefinedMap x = new UserDefinedMap();
		x.set(new Key("ab"), new chunkMemory("ab1"));
		x.set(new Key("ac"),  new chunkMemory("ac"));
		x.set(new Key("ad"),  new chunkMemory("ad"));
		x.set(new Key("ab"), new chunkMemory("ab2"));
		Thread.currentThread().sleep(8000);
		Entry value = x.getEntry(new Key("ab"));
	    System.out.println(value.getValue().toString());
	}

原本打算key為String型別,但Entry在繼承了WeakReference<String>類後,好像在任何情況下不會進行弱引用的回收,不知道怎麼回事