existingEntry) {
363 | after = existingEntry;
364 | before = existingEntry.before;
365 | before.after = this;
366 | after.before = this;
367 | }
368 |
369 | /**
370 | * Returns the key corresponding to this entry.
371 | *
372 | * @return the key corresponding to this entry
373 | */
374 | public int getKey() {
375 | return key;
376 | }
377 |
378 | /**
379 | * Returns the value corresponding to this entry.
380 | *
381 | * @return the value corresponding to this entry
382 | */
383 | public V getValue() {
384 | return value;
385 | }
386 | }
387 |
388 | // ========== Linked List
389 |
390 | /**
391 | * Returns true if this map should remove its eldest entry.
392 | * This method is invoked by put and putAll after
393 | * inserting a new entry into the map. It provides the implementor
394 | * with the opportunity to remove the eldest entry each time a new one
395 | * is added. This is useful if the map represents a cache: it allows
396 | * the map to reduce memory consumption by deleting stale entries.
397 | *
398 | *
399 | * Sample use: this override will allow the map to grow up to 100 entries and then delete the eldest entry
400 | * each time a new entry is added, maintaining a steady state of 100 entries.
401 | *
402 | *
403 | * private static final int MAX_ENTRIES = 100;
404 | *
405 | * protected boolean removeEldestEntry(IntLinkedEntry eldest) {
406 | * return size() > MAX_ENTRIES;
407 | * }
408 | *
409 | *
410 | *
411 | * This method typically does not modify the map in any way, instead allowing the map to modify itself as
412 | * directed by its return value. It is permitted for this method to modify the map directly, but if
413 | * it does so, it must return false (indicating that the map should not attempt any
414 | * further modification). The effects of returning true after modifying the map from within this
415 | * method are unspecified.
416 | *
417 | *
418 | * This implementation merely returns false (so that this map acts like a normal map - the eldest
419 | * element is never removed).
420 | *
421 | * @param eldest The least recently inserted entry in the map, or if
422 | * this is an access-ordered map, the least recently accessed
423 | * entry. This is the entry that will be removed it this
424 | * method returns true. If the map was empty prior
425 | * to the put or putAll invocation resulting
426 | * in this invocation, this will be the entry that was just
427 | * inserted; in other words, if the map contains a single
428 | * entry, the eldest entry is also the newest.
429 | * @return true if the eldest entry should be removed
430 | * from the map; false if it should be retained.
431 | */
432 | protected boolean removeEldestEntry(IntLinkedEntry eldest) {
433 | return false;
434 | }
435 |
436 | // ========== Prime Finder
437 |
438 | private static final int primeSize(final int capacity) {
439 | // return java.math.BigInteger.valueOf((long)capacity).nextProbablePrime().intValue();
440 | return PrimeFinder.nextPrime(capacity);
441 | }
442 |
443 | // ========== Iterator
444 |
445 | /**
446 | * @returns iterator over values in map
447 | */
448 | public Iterator iterator() {
449 | return new IntLinkedHashMapIterator(this);
450 | }
451 |
452 | static class IntLinkedHashMapIterator implements Iterator {
453 | final IntLinkedHashMap associatedMap;
454 | IntLinkedEntry nextEntry = null;
455 | IntLinkedEntry lastReturned = null;
456 |
457 | public IntLinkedHashMapIterator(final IntLinkedHashMap associatedMap) {
458 | this.associatedMap = associatedMap;
459 | nextEntry = associatedMap.header.after;
460 | }
461 |
462 | public boolean hasNext() {
463 | return nextEntry != associatedMap.header;
464 | }
465 |
466 | public void remove() {
467 | if (lastReturned == null)
468 | throw new IllegalStateException();
469 |
470 | associatedMap.remove(lastReturned.key);
471 | lastReturned = null;
472 | }
473 |
474 | IntLinkedEntry nextEntry() {
475 | if (nextEntry == associatedMap.header)
476 | throw new NoSuchElementException();
477 |
478 | IntLinkedEntry e = lastReturned = nextEntry;
479 | nextEntry = e.after;
480 | return e;
481 | }
482 |
483 | public V next() {
484 | return nextEntry().value;
485 | }
486 | }
487 |
488 | // =========================================
489 |
490 | public V[] getValues() {
491 | final V[] array = factory.newArray(elementCount);
492 | int i = 0;
493 | for (final V v : this) {
494 | array[i++] = v;
495 | }
496 | return array;
497 | }
498 |
499 | // =========================================
500 |
501 | public static void main(String[] args) {
502 | IntLinkedHashMap hash = new IntLinkedHashMap(16, Integer.class, true);
503 | for (int i = 1; i < 6; i++) { // 1...4
504 | hash.put(i, i);
505 | }
506 | hash.put(3, 3);
507 | hash.put(3, 3);
508 | hash.put(3, 3);
509 | hash.put(3, 3);
510 | hash.get(3);
511 | // hash.remove(3);
512 | for (Integer i : hash) {
513 | System.out.println(i);
514 | }
515 | System.out.println("---");
516 | while (hash.size() > 0) {
517 | System.out.println("remove value=" + hash.removeEldest());
518 | }
519 | System.out.println("---");
520 |
521 | for (Integer i : hash) {
522 | System.out.println(i);
523 | }
524 | }
525 | }
526 |
--------------------------------------------------------------------------------
/src/main/java/org/javastack/kvstore/structures/hash/WeakSet.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | *
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Unless required by applicable law or agreed to in writing, software
9 | * distributed under the License is distributed on an "AS IS" BASIS,
10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | * See the License for the specific language governing permissions and
12 | * limitations under the License.
13 | *
14 | */
15 | package org.javastack.kvstore.structures.hash;
16 |
17 | import java.lang.ref.ReferenceQueue;
18 | import java.lang.ref.WeakReference;
19 | import java.util.Arrays;
20 |
21 | import org.javastack.kvstore.utils.PrimeFinder;
22 |
23 | /**
24 | * A hashtable-based Set implementation with weak values. An entry in a
25 | * WeakSet will automatically be removed when its value is no longer in
26 | * ordinary use. More precisely, the presence of a given value will
27 | * not prevent the value from being discarded by the garbage collector, that is,
28 | * made finalizable, finalized, and then reclaimed. When a value has been
29 | * discarded its entry is effectively removed from the set, so this class
30 | * behaves somewhat differently from other Set implementations.
31 | *
32 | * This class is NOT Thread-Safe
33 | *
34 | * @see java.util.WeakHashMap
35 | *
36 | * @author Guillermo Grandes / guillermo.grandes[at]gmail.com
37 | */
38 | public class WeakSet {
39 |
40 | private int elementCount;
41 | private Entry[] elementData;
42 |
43 | private final float loadFactor;
44 | private int threshold;
45 | private int defaultSize = 17;
46 |
47 | /**
48 | * Reference queue for cleared WeakEntry
49 | */
50 | private final ReferenceQueue queue = new ReferenceQueue();
51 |
52 | /**
53 | * Constructs a new {@code WeakSet} instance with the specified capacity.
54 | *
55 | * @param capacity the initial capacity of this set.
56 | * @param type class for values
57 | */
58 | public WeakSet(final int capacity) {
59 | defaultSize = primeSize(capacity);
60 | if (capacity >= 0) {
61 | elementCount = 0;
62 | elementData = newElementArray(capacity < 0 ? 1 : capacity);
63 | loadFactor = 0.75f; // Default load factor of 0.75
64 | computeMaxSize();
65 | } else {
66 | throw new IllegalArgumentException();
67 | }
68 | }
69 |
70 | /**
71 | * Constructs a new {@code WeakSet} instance with default capacity (17).
72 | */
73 | public WeakSet() {
74 | this(17);
75 | }
76 |
77 | /**
78 | * Check for equal objects
79 | *
80 | * @param o1
81 | * @param o2
82 | * @return true if equals
83 | */
84 | private final static boolean eq(Object o1, Object o2) {
85 | return ((o1 == o2) || o1.equals(o2));
86 | }
87 |
88 | /**
89 | * Removes all values from this WeakSet, leaving it empty.
90 | *
91 | * @see #isEmpty
92 | * @see #size
93 | */
94 | public final void clear() {
95 | clear(true);
96 | }
97 |
98 | /**
99 | * Clear the set
100 | *
101 | * @param shrink if true, shrink the set to initial size
102 | */
103 | public void clear(final boolean shrink) {
104 | while (queue.poll() != null) {
105 | ;
106 | }
107 | if (elementCount > 0) {
108 | elementCount = 0;
109 | }
110 | if (shrink && (elementData.length > 1024) && (elementData.length > defaultSize)) {
111 | elementData = newElementArray(defaultSize);
112 | } else {
113 | Arrays.fill(elementData, null);
114 | }
115 | computeMaxSize();
116 | while (queue.poll() != null) {
117 | ;
118 | }
119 | }
120 |
121 | /**
122 | * Returns the specified value.
123 | *
124 | * @param value the value.
125 | * @return the value, or {@code null} if not found the specified value
126 | */
127 | public T get(final T value) {
128 | expungeStaleEntries();
129 | //
130 | final int index = (value.hashCode() & 0x7FFFFFFF) % elementData.length;
131 | Entry m = elementData[index];
132 | while (m != null) {
133 | if (eq(value, m.get())) {
134 | return m.get();
135 | }
136 | m = m.nextInSlot;
137 | }
138 | return null;
139 | }
140 |
141 | /**
142 | * Returns whether this set is empty.
143 | *
144 | * @return {@code true} if this set has no elements, {@code false} otherwise.
145 | * @see #size()
146 | */
147 | public final boolean isEmpty() {
148 | return (size() == 0);
149 | }
150 |
151 | /**
152 | * Puts the specified value in the set.
153 | *
154 | * @param value
155 | * the value.
156 | * @return the value of any previous put or {@code null} if there was no such value.
157 | */
158 | public T put(final T value) {
159 | expungeStaleEntries();
160 | //
161 | final int hash = value.hashCode();
162 | int index = (hash & 0x7FFFFFFF) % elementData.length;
163 | Entry entry = elementData[index];
164 | while (entry != null && !eq(value, entry.get())) {
165 | entry = entry.nextInSlot;
166 | }
167 |
168 | if (entry == null) {
169 | if (++elementCount > threshold) {
170 | expandElementArray(elementData.length);
171 | index = (hash & 0x7FFFFFFF) % elementData.length;
172 | }
173 | entry = createHashedEntry(value, index);
174 | return null;
175 | }
176 |
177 | final T result = entry.get();
178 | return result;
179 | }
180 |
181 | private final Entry createHashedEntry(final T valye, final int index) {
182 | Entry