自定義實現類似WeakHashMap集合類
阿新 • • 發佈:2019-01-29
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>類後,好像在任何情況下不會進行弱引用的回收,不知道怎麼回事