add maxCapacity to LongLongHashMap
This allows us to define an upper limit for the memory usage.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
|
||||
group='org.lucares'
|
||||
version = '0.2'
|
||||
version = '0.3'
|
||||
|
||||
dependencies {
|
||||
jmh 'org.eclipse.collections:eclipse-collections:10.2.0'
|
||||
|
||||
@@ -46,6 +46,8 @@ public class LongLongHashMap {
|
||||
*/
|
||||
private Long removedKeyValue = null;
|
||||
|
||||
private int maxCapacity = MAX_ARRAY_SIZE;
|
||||
|
||||
/**
|
||||
* Create a new {@link LongLongHashMap} with the given initial capacity and load
|
||||
* factor.
|
||||
@@ -78,6 +80,30 @@ public class LongLongHashMap {
|
||||
this(8, 0.75);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the maximum capacity.<p>
|
||||
* This restricts the maximum memory used by this map. The memory consumption can be twice as much during grow or shrink phases.
|
||||
* <p>
|
||||
* Note that the performance can suffer if the map contains more keys than capacity time loadFactor.
|
||||
* <p>
|
||||
* Note an automatic {@link #rehash()} is triggered if the new maxCapacity is smaller than the current capacity.
|
||||
* But there is not automatic rehash when the new maxCapacity is greater than the current capacity.
|
||||
*
|
||||
* @param maxCapacity new maximum capacity
|
||||
* @throws IllegalArgumentException if {@code maxCapacity} is smaller than {@link #size()}
|
||||
*/
|
||||
public void setMaxCapacity(int maxCapacity) {
|
||||
if (maxCapacity < size) {
|
||||
throw new IllegalArgumentException("maxCapacity must equal or larger than current size of the map");
|
||||
}
|
||||
this.maxCapacity = maxCapacity;
|
||||
|
||||
if (maxCapacity < keys.length) {
|
||||
rehash(maxCapacity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of entries in this map.
|
||||
*
|
||||
@@ -144,9 +170,14 @@ public class LongLongHashMap {
|
||||
*
|
||||
* @param key the key
|
||||
* @param value the value
|
||||
* @throws IllegalStateException if the map is full, see {@link #setMaxCapacity(int)}
|
||||
*/
|
||||
public void put(final long key, final long value) {
|
||||
|
||||
if (size == maxCapacity) {
|
||||
throw new IllegalStateException("map is full");
|
||||
}
|
||||
|
||||
if (key == NULL_KEY) {
|
||||
size += zeroValue == null ? 1 : 0;
|
||||
zeroValue = value;
|
||||
@@ -448,9 +479,11 @@ public class LongLongHashMap {
|
||||
}
|
||||
|
||||
private void growAndRehash() {
|
||||
final int newSize = Math.min(keys.length * 2, MAX_ARRAY_SIZE);
|
||||
final int newSize = Math.min(keys.length * 2, maxCapacity);
|
||||
if(newSize != keys.length) {
|
||||
rehash(newSize);
|
||||
}
|
||||
}
|
||||
|
||||
private void rehash(int newSize) {
|
||||
final long[] oldKeys = keys;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package org.lucares.collections;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.stream.LongStream;
|
||||
|
||||
@@ -54,14 +56,25 @@ public class LongLongHashMapTest {
|
||||
final LongLongHashMap map = new LongLongHashMap();
|
||||
final int values = 100;
|
||||
|
||||
fillMap(map, values); // fill with keys 0...99
|
||||
// fill with keys 0...99
|
||||
for (int i = 0; i < values; i++) {
|
||||
map.put(i, i);
|
||||
}
|
||||
|
||||
map.remove(values); // key does not exist
|
||||
Assertions.assertEquals(values, map.size(), "size after removing non existing key 100");
|
||||
|
||||
// -1 is a sentinel key and has special handling
|
||||
map.remove(-1); // key does not exist
|
||||
Assertions.assertEquals(values, map.size(), "size after removing non existing key -1");
|
||||
|
||||
map.put(-1, -1);
|
||||
Assertions.assertEquals(values+1, map.size(), "size after adding key -1");
|
||||
|
||||
map.remove(-1); // key exists
|
||||
Assertions.assertEquals(values, map.size(), "size after removing key -1");
|
||||
|
||||
// 0 is a sentinel key and has special handling
|
||||
map.remove(0); // key exists
|
||||
Assertions.assertEquals(values - 1, map.size(), "size after removing existing key 0");
|
||||
|
||||
@@ -70,7 +83,9 @@ public class LongLongHashMapTest {
|
||||
|
||||
for (int i = 1; i < 100; i++) {
|
||||
map.remove(i);
|
||||
Assertions.assertEquals(values-i-1, map.size(), "size after removing key "+i);
|
||||
}
|
||||
Assertions.assertEquals(0, map.size(), "size after removing all keys");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -353,6 +368,42 @@ public class LongLongHashMapTest {
|
||||
Assertions.assertEquals(6, LongLongHashMap.findPosOfFirstPositiveKey(new long[] { -1, 0, 0, 0, 0, 0, 1 }));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMaxCapacity() {
|
||||
LongLongHashMap map = new LongLongHashMap(6,0.75);
|
||||
Assertions.assertEquals(6, map.getCapacity());
|
||||
|
||||
// capacity is reduced to 5 - possible, because map is empty
|
||||
map.setMaxCapacity(5);
|
||||
Assertions.assertEquals(5, map.getCapacity());
|
||||
|
||||
map.put(1, 0);
|
||||
map.put(2, 0);
|
||||
map.put(3, 0);
|
||||
map.put(4, 0);
|
||||
map.put(5, 0);
|
||||
Assertions.assertEquals(5, map.getCapacity());
|
||||
Assertions.assertEquals(5, map.size());
|
||||
|
||||
// ensure we cannot add more values than the capacity allows
|
||||
// 0 and -1 are sentinels, we have to check them separately
|
||||
assertThrows(IllegalStateException.class, () -> map.put(0, 55));
|
||||
assertThrows(IllegalStateException.class, () -> map.put(-1, 55));
|
||||
assertThrows(IllegalStateException.class, () -> map.put(6, 55));// key is negative to ensure we actually could add it if the capacity restriction was not there
|
||||
|
||||
Assertions.assertEquals(5, map.size()); // we still have only 5 keys in the map
|
||||
|
||||
// check that we can increase the maxCapacity
|
||||
map.setMaxCapacity(map.getCapacity()+1);
|
||||
Assertions.assertEquals(5, map.getCapacity()); // capacity was not updated, because there was not need - you would have to manually call rehash()
|
||||
Assertions.assertEquals(5, map.size());
|
||||
map.put(6, 0);
|
||||
assertThrows(IllegalStateException.class, () -> map.put(7, 55));
|
||||
|
||||
// check we cannot make the capacity smaller than the current size
|
||||
assertThrows(IllegalArgumentException.class, ()->map.setMaxCapacity(map.size()-1));
|
||||
}
|
||||
|
||||
private LongList findKeysWithSameSpread(final LongLongHashMap map) {
|
||||
final LongList result = new LongList();
|
||||
final int spread = map.spread(1);
|
||||
@@ -369,10 +420,4 @@ public class LongLongHashMapTest {
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void fillMap(LongLongHashMap map, int numEntries) {
|
||||
for (int i = 0; i < numEntries; i++) {
|
||||
map.put(i, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user