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'
|
group='org.lucares'
|
||||||
version = '0.2'
|
version = '0.3'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
jmh 'org.eclipse.collections:eclipse-collections:10.2.0'
|
jmh 'org.eclipse.collections:eclipse-collections:10.2.0'
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ public class LongLongHashMap {
|
|||||||
*/
|
*/
|
||||||
private Long removedKeyValue = null;
|
private Long removedKeyValue = null;
|
||||||
|
|
||||||
|
private int maxCapacity = MAX_ARRAY_SIZE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new {@link LongLongHashMap} with the given initial capacity and load
|
* Create a new {@link LongLongHashMap} with the given initial capacity and load
|
||||||
* factor.
|
* factor.
|
||||||
@@ -78,6 +80,30 @@ public class LongLongHashMap {
|
|||||||
this(8, 0.75);
|
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.
|
* The number of entries in this map.
|
||||||
*
|
*
|
||||||
@@ -144,9 +170,14 @@ public class LongLongHashMap {
|
|||||||
*
|
*
|
||||||
* @param key the key
|
* @param key the key
|
||||||
* @param value the value
|
* @param value the value
|
||||||
|
* @throws IllegalStateException if the map is full, see {@link #setMaxCapacity(int)}
|
||||||
*/
|
*/
|
||||||
public void put(final long key, final long value) {
|
public void put(final long key, final long value) {
|
||||||
|
|
||||||
|
if (size == maxCapacity) {
|
||||||
|
throw new IllegalStateException("map is full");
|
||||||
|
}
|
||||||
|
|
||||||
if (key == NULL_KEY) {
|
if (key == NULL_KEY) {
|
||||||
size += zeroValue == null ? 1 : 0;
|
size += zeroValue == null ? 1 : 0;
|
||||||
zeroValue = value;
|
zeroValue = value;
|
||||||
@@ -448,8 +479,10 @@ public class LongLongHashMap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void growAndRehash() {
|
private void growAndRehash() {
|
||||||
final int newSize = Math.min(keys.length * 2, MAX_ARRAY_SIZE);
|
final int newSize = Math.min(keys.length * 2, maxCapacity);
|
||||||
rehash(newSize);
|
if(newSize != keys.length) {
|
||||||
|
rehash(newSize);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void rehash(int newSize) {
|
private void rehash(int newSize) {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package org.lucares.collections;
|
package org.lucares.collections;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.stream.LongStream;
|
import java.util.stream.LongStream;
|
||||||
|
|
||||||
@@ -54,14 +56,25 @@ public class LongLongHashMapTest {
|
|||||||
final LongLongHashMap map = new LongLongHashMap();
|
final LongLongHashMap map = new LongLongHashMap();
|
||||||
final int values = 100;
|
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
|
map.remove(values); // key does not exist
|
||||||
Assertions.assertEquals(values, map.size(), "size after removing non existing key 100");
|
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
|
map.remove(-1); // key does not exist
|
||||||
Assertions.assertEquals(values, map.size(), "size after removing non existing key -1");
|
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
|
map.remove(0); // key exists
|
||||||
Assertions.assertEquals(values - 1, map.size(), "size after removing existing key 0");
|
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++) {
|
for (int i = 1; i < 100; i++) {
|
||||||
map.remove(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
|
@Test
|
||||||
@@ -352,6 +367,42 @@ public class LongLongHashMapTest {
|
|||||||
Assertions.assertEquals(5, LongLongHashMap.findPosOfFirstPositiveKey(new long[] { -1, 0, 0, 0, 0, 1, 1 }));
|
Assertions.assertEquals(5, LongLongHashMap.findPosOfFirstPositiveKey(new long[] { -1, 0, 0, 0, 0, 1, 1 }));
|
||||||
Assertions.assertEquals(6, LongLongHashMap.findPosOfFirstPositiveKey(new long[] { -1, 0, 0, 0, 0, 0, 1 }));
|
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) {
|
private LongList findKeysWithSameSpread(final LongLongHashMap map) {
|
||||||
final LongList result = new LongList();
|
final LongList result = new LongList();
|
||||||
@@ -369,10 +420,4 @@ public class LongLongHashMapTest {
|
|||||||
|
|
||||||
return result;
|
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