package com.ctriposs.bigmap;
import java.io.IOException;
import java.io.Serializable;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class BigConcurrentHashMapImpl implements IBigConcurrentHashMap {
private final static Logger logger = LoggerFactory.getLogger(BigConcurrentHashMapImpl.class);
/*
* The basic strategy is to subdivide the table among Segments,
* each of which itself is a concurrently readable hash table.
*/
/* ---------------- Constants -------------- */
/**
* The default initial capacity for this table,
* used when not otherwise specified in a constructor.
*/
static final int DEFAULT_INITIAL_CAPACITY = 16;
/**
* The default load factor for this table, used when not
* otherwise specified in a constructor.
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* The default concurrency level for this table, used when not
* otherwise specified in a constructor.
*/
static final int DEFAULT_CONCURRENCY_LEVEL = 16;
/**
* The default map entry expiration purge interval
*/
static final int DEFAULT_PURGE_INTERVAL = 1000 * 60 * 20;
/**
* Should the on disk map be reloaded into memory on map initialization
*/
static final boolean DEFAULT_RELOAD_ON_STARTUP = false;
/**
* The maximum capacity, used if a higher value is implicitly
* specified by either of the constructors with arguments. MUST
* be a power of two <= 1<<30 to ensure that entries are indexable
* using ints.
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* The maximum number of segments to allow; used to bound
* constructor arguments.
*/
static final int MAX_SEGMENTS = 1 << 16; // slightly conservative
/* ---------------- Fields -------------- */
/**
* Mask value for indexing into segments. The upper bits of a
* key's hash code are used to choose the segment.
*/
final int segmentMask;
/**
* Shift value for indexing within segments.
*/
final int segmentShift;
/**
* The segments, each of which is a specialized hash table
*/
final Segment<byte[]>[] segments;
/**
* Factory managing the creation, recycle/reuse of map entries.
*/
final IMapEntryFactory mapEntryFactory;
/**
* map file directory
*/
final String mapDir;
/**
* map name
*/
final String mapName;
/**
* Expiration purge timer
*/
Timer purgeTimer;
final BigConfig config;
final AtomicLong purgeCount = new AtomicLong(0);
/* ---------------- Small Utilities -------------- */
/**
* Returns the segment that should be used for key with given hash
* @param hash the hash code for the key
* @return the segment
*/
final Segment<byte[]> segmentFor(int hash) {
return segments[(hash >>> segmentShift) & segmentMask];
}
/* ---------------- Inner Classes -------------- */
/**
* BigConcurrentHashMap list entry. Note that this is never exported
* out as a user-visible Map.Entry.
*
*/
static final class HashEntry {
volatile long index;
final int hash;
HashEntry next;
HashEntry(long index, int hash, HashEntry next) {
this.index = index;
this.hash = hash;
this.next = next;
}
static HashEntry[] newArray(int i) {
return new HashEntry[i];
}
}
/**
* Segments are specialized versions of hash tables. This
* subclasses from ReentrantLock opportunistically, just to
* simplify some locking and avoid separate construction.
*/
static final class Segment<V> extends ReentrantLock implements Serializable {
private static final long serialVersionUID = 2249069246763182397L;
/**
* The number of elements in this segment's region.
*/
transient volatile int count;
/**
* The table is rehashed when its size exceeds this threshold.
* (The value of this field is always <tt>(int)(capacity *
* loadFactor)</tt>.)
*/
transient int threshold;
/**
* The per-segment table.
*/
transient volatile HashEntry[] table;
/**
* The load factor for the hash table. Even though this value
* is same for all segments, it is replicated to avoid needing
* links to outer object.
* @serial
*/
final float loadFactor;
/**
* Factory managing the creation, recycle/reuse of map entries mapped to disk files.
*/
final IMapEntryFactory mapEntryFactory;
Segment(int initialCapacity, float lf, IMapEntryFactory mapEntryFactory) {
super(false);
loadFactor = lf;
this.mapEntryFactory = mapEntryFactory;
setTable(HashEntry.newArray(initialCapacity));
}
@SuppressWarnings("unchecked")
static <V> Segment<V>[] newArray(int i) {
return new Segment[i];
}
/**
* Sets table to new HashEntry array.
* Call only while holding lock or in constructor.
*/
void setTable(HashEntry[] newTable) {
threshold = (int)(newTable.length * loadFactor);
table = newTable;
}
/**
* Returns properly casted first entry of bin for given hash.
*/
HashEntry getFirst(int hash) {
HashEntry[] tab = table;
return tab[hash & (tab.length - 1)];
}
/* Specialized implementations of map methods */
byte[] get(final byte[] key, int hash) throws IOException {
if (count != 0) { // read-volatile
lock();
try {
int c = count - 1;
HashEntry[] tab = table;
int index = hash & (tab.length - 1);
HashEntry e = tab[index];
while (e != null) {
MapEntry me = this.mapEntryFactory.findMapEntryByIndex(e.index);
if (e.hash == hash && Arrays.equals(key, me.getEntryKey())) {
if (this.isExpired(me)) {
this.mapEntryFactory.release(me);
this.removeEntry(tab, index, e);
count = c; // write-volatile
return null;
} else {
me.putLastAccessedTime(System.currentTimeMillis());
return me.getEntryValue();
}
}
e = e.next;
}
} finally {
unlock();
}
}
return null;
}
void removeEntry(HashEntry[] tab, int index, HashEntry e) {
HashEntry first = tab[index];
if (first == e) {
tab[index] = e.next;
e.next = null; // ready for GC
} else {
HashEntry p = first;
while(p.next != e) {
p = p.next;
}
p.next = e.next;
e.next = null; // ready for GC
}
}
boolean containsKey(final byte[] key, int hash) throws IOException {
if (count != 0) { // read-volatile
lock();
try {
int c = count - 1;
HashEntry[] tab = table;
int index = hash & (tab.length - 1);
HashEntry e = tab[index];
while (e != null) {
MapEntry me = this.mapEntryFactory.findMapEntryByIndex(e.index);
if (e.hash == hash && Arrays.equals(key, me.getEntryKey())) {
if (this.isExpired(me)) {
this.mapEntryFactory.release(me);
this.removeEntry(tab, index, e);
count = c; // write-volatile
return false;
} else {
me.putLastAccessedTime(System.currentTimeMillis());
return true;
}
}
e = e.next;
}
} finally {
unlock();
}
}
return false;
}
boolean replace(byte[] key, int hash, byte[] oldValue, byte[] newValue, long ttlInMs) throws IOException {
lock();
try {
HashEntry e = getFirst(hash);
MapEntry me = null;
while (e != null) {
me = this.mapEntryFactory.findMapEntryByIndex(e.index);
if (e.hash == hash && Arrays.equals(key, me.getEntryKey())) {
break;
}
e = e.next;
}
boolean replaced = false;
if (e != null && Arrays.equals(oldValue, me.getEntryValue())) {
replaced = true;
this.mapEntryFactory.release(me);
me = this.mapEntryFactory.acquire(key.length + newValue.length);
me.putKeyLength(key.length);
me.putValueLength(newValue.length);
me.putEntryKey(key);
me.putEntryValue(newValue);
me.putLastAccessedTime(System.currentTimeMillis());
me.putTimeToLive(ttlInMs);
e.index = me.getIndex();
}
return replaced;
} finally {
unlock();
}
}
byte[] replace(byte[] key, int hash, byte[] newValue, long ttlInMs) throws IOException {
lock();
try {
HashEntry e = getFirst(hash);
MapEntry me = null;
while (e != null) {
me = this.mapEntryFactory.findMapEntryByIndex(e.index);
if (e.hash == hash && Arrays.equals(key, me.getEntryKey())) {
break;
}
e = e.next;
}
byte[] oldValue = null;
if (e != null) {
oldValue = me.getEntryValue();
this.mapEntryFactory.release(me);
me = this.mapEntryFactory.acquire(key.length + newValue.length);
me.putKeyLength(key.length);
me.putValueLength(newValue.length);
me.putEntryKey(key);
me.putEntryValue(newValue);
me.putLastAccessedTime(System.currentTimeMillis());
me.putTimeToLive(ttlInMs);
e.index = me.getIndex();
}
return oldValue;
} finally {
unlock();
}
}
void restoreInUseMapEntry(MapEntry me, int hash) throws IOException {
lock();
try {
int c = count;
if (c++ > threshold) // ensure capacity
rehash();
HashEntry[] tab = table;
int index = hash & (tab.length - 1);
HashEntry first = tab[index];
tab[index] = new HashEntry(me.getIndex(), hash, first);
count = c; // write-volatile
} finally {
unlock();
}
}
byte[] put(byte[] key, int hash, byte[] value, boolean onlyIfAbsent, long ttlInMs) throws IOException {
lock();
try {
int c = count;
if (c++ > threshold) // ensure capacity
rehash();
HashEntry[] tab = table;
int index = hash & (tab.length - 1);
HashEntry first = tab[index];
HashEntry e = first;
MapEntry me = null;
while (e != null) {
me = this.mapEntryFactory.findMapEntryByIndex(e.index);
if (e.hash == hash && Arrays.equals(key, me.getEntryKey())) {
break;
}
e = e.next;
}
byte[] oldValue;
if (e != null) {
oldValue = me.getEntryValue();
if (!onlyIfAbsent) {
this.mapEntryFactory.release(me);
me = this.mapEntryFactory.acquire(key.length + value.length);
me.putKeyLength(key.length);
me.putValueLength(value.length);
me.putEntryKey(key);
me.putEntryValue(value);
me.putLastAccessedTime(System.currentTimeMillis());
me.putTimeToLive(ttlInMs);
e.index = me.getIndex();
}
}
else {
oldValue = null;
me = this.mapEntryFactory.acquire(key.length + value.length);
me.putKeyLength(key.length);
me.putValueLength(value.length);
me.putEntryKey(key);
me.putEntryValue(value);
me.putLastAccessedTime(System.currentTimeMillis());
me.putTimeToLive(ttlInMs);
tab[index] = new HashEntry(me.getIndex(), hash, first);
count = c; // write-volatile
}
return oldValue;
} finally {
unlock();
}
}
void rehash() {
HashEntry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity >= MAXIMUM_CAPACITY)
return;
HashEntry[] newTable = HashEntry.newArray(oldCapacity<<1);
threshold = (int)(newTable.length * loadFactor);
int sizeMask = newTable.length - 1;
for (HashEntry e : oldTable) {
if (e != null) {
HashEntry p = e;
HashEntry q = e.next;
while(true) {
int k = p.hash & sizeMask;
p.next = newTable[k];
newTable[k] = p;
p = q;
if (p == null) break;
else {
q = p.next;
}
}
}
}
table = newTable;
}
// Purge expired entries
void purge() throws IOException {
if (count != 0) {
lock();
try {
for(int index = 0; index < table.length; index++) {
HashEntry e = table[index];
while(e != null) {
MapEntry me = this.mapEntryFactory.findMapEntryByIndex(e.index);
HashEntry next = e.next;
if (this.isExpired(me)) {
this.mapEntryFactory.release(me);
this.removeEntry(table, index, e);
count--;
}
e = next;
}
}
} finally {
unlock();
}
}
}
/**
* Remove; match on key only if value null, else match both.
* @throws IOException
*/
byte[] remove(final byte[] key, int hash, byte[] value) throws IOException {
lock();
try {
int c = count - 1;
HashEntry[] tab = table;
int index = hash & (tab.length - 1);
HashEntry e = tab[index];
MapEntry me = null;
while (e != null) {
me = this.mapEntryFactory.findMapEntryByIndex(e.index);
if (e.hash == hash && Arrays.equals(key, me.getEntryKey())) {
break;
}
e = e.next;
}
byte[] oldValue = null;
if (e != null) {
if (value == null || Arrays.equals(value, me.getEntryValue())) {
oldValue = me.getEntryValue();
if (this.isExpired(me)) {
oldValue = null;
}
this.mapEntryFactory.release(me);
this.removeEntry(tab, index, e);
count = c; // write-volatile
}
}
return oldValue;
} finally {
unlock();
}
}
boolean isExpired(MapEntry me) {
// has the entry expired?
long ttlInMs = me.getTimeToLive();
boolean expired = ttlInMs > 0 && (System.currentTimeMillis() - me.getLastAccessedTime() > ttlInMs);
return expired;
}
void clear() {
if (count != 0) {
lock();
try {
HashEntry[] tab = table;
for (int i = 0; i < tab.length ; i++)
tab[i] = null;
count = 0; // write-volatile
} finally {
unlock();
}
}
}
}
/* ---------------- Public operations -------------- */
/**
* Creates a new, empty map with the specified map directory, map name, initial
* capacity, load factor and concurrency level.
*
* @param mapDir the target directory to store persisted map files
* @param mapName the name of the map
* @param config the hash map configuration including load factor, initial capacity,
* concurrency level and purgeInterval in milliseconds.
* @throws IOException the exception throws if failed to operate on memory mapped files
* @throws IllegalArgumentException if the initial capacity is
* negative or the load factor or concurrencyLevel or purgeIntervalInMs are
* nonpositive.
*/
public BigConcurrentHashMapImpl(String mapDir, String mapName, BigConfig config) throws IOException {
if (!(config.getLoadFactor() > 0) || config.getInitialCapacity() < 0 ||
config.getConcurrencyLevel() <= 0 || config.getPurgeIntervalInMs() <= 10)
throw new IllegalArgumentException();
this.mapDir = mapDir;
this.mapName = mapName;
this.config = config;
this.mapEntryFactory = new MapEntryFactoryImpl(mapDir, mapName);
// Find power-of-two sizes best matching arguments
int sshift = 0;
int ssize = 1;
while (ssize < config.getConcurrencyLevel()) {
++sshift;
ssize <<= 1;
}
segmentShift = 32 - sshift;
segmentMask = ssize - 1;
this.segments = Segment.newArray(ssize);
int c = config.getInitialCapacity() / ssize;
if (c * ssize < config.getInitialCapacity())
++c;
int cap = 1;
while (cap < c)
cap <<= 1;
for (int i = 0; i < this.segments.length; ++i)
this.segments[i] = new Segment<byte[]>(cap, config.getLoadFactor(), this.mapEntryFactory);
// reload on disk map entries into memory
if (!((MapEntryFactoryImpl)this.mapEntryFactory).isEmpty()) {
if (config.isReloadOnStartup()) {
this.reload();
} else {
this.mapEntryFactory.removeAll();
}
}
this.startPurgeTimer();
}
private void stopPurgeTimer() {
if (this.purgeTimer != null) {
this.purgeTimer.cancel();
this.purgeTimer = null;
}
}
private void startPurgeTimer() {
purgeTimer = new Timer(mapName + "_purgeTimer");
purgeTimer.schedule(new PurgeTimerTask(), config.getPurgeIntervalInMs(), config.getPurgeIntervalInMs());
}
/**
* Load the on-disk map entries and rebuild the in-memory index
*
* @throws IOException
*/
void reload() throws IOException {
MapEntryFactoryImpl factory = (MapEntryFactoryImpl)this.mapEntryFactory;
long index = 0;
while(index >= 0) {
MapEntry me = factory.findMapEntryByIndex(index);
if (me.isAllocated()) {
factory.restore(me);
if (me.isInUse()) {
this.restoreInUseMapEntry(me);
}
} else {
break;
}
index++;
}
}
/**
* Creates a new, empty map with default initial capacity (16), load factor (0.75),
* concurrencyLevel (16) and purgeIntervalInMs(20 minutes).
*
* @param mapDir the target directory to store persisted map files
* @param mapName the name of the map
* @throws IOException the exception throws if failed to operate on memory mapped files
*/
public BigConcurrentHashMapImpl(String mapDir, String mapName) throws IOException {
this(mapDir, mapName, new BigConfig());
}
class PurgeTimerTask extends TimerTask {
AtomicBoolean running = new AtomicBoolean(false);
@Override
public void run() {
try {
if (running.compareAndSet(false, true)) {
try {
purge();
purgeCount.incrementAndGet();
} finally {
running.set(false);
}
}
} catch (Throwable t) {
logger.error("Fail to purge expired map entries", t);
}
}
}
void purge() throws IOException {
for(Segment<byte[]> segment : segments) {
segment.purge();
}
}
/**
* Returns <tt>true</tt> if this map contains no key-value mappings.
*
* @return <tt>true</tt> if this map contains no key-value mappings
*/
@Override
public boolean isEmpty() {
for(Segment<byte[]> segment : segments) {
if (segment.count > 0) return false;
}
return true;
}
/**
* Returns the number of key-value mappings in this map. If the
* map contains more than <tt>Integer.MAX_VALUE</tt> elements, returns
* <tt>Integer.MAX_VALUE</tt>.
*
* @return the number of key-value mappings in this map
*/
@Override
public int size() {
long total = 0;
for(Segment<byte[]> segement : segments) {
total += segement.count;
}
return (int)Math.min(Integer.MAX_VALUE, total);
}
/**
* Returns the value to which the specified key is mapped,
* or {@code null} if this map contains no mapping for the key.
*
* <p>More formally, if this map contains a mapping from a key
* {@code k} to a value {@code keys} such that {@code key.equals(k)},
* then this method returns {@code keys}; otherwise it returns
* {@code null}. (There can be at most one such mapping.)
* @throws RuntimeException throws if file IO operation fail
* @throws NullPointerException if the specified key is null
*/
@Override
public byte[] get(byte[] key) {
if (key == null || key.length == 0) throw new NullPointerException("key is null or empty");
final int hash = Arrays.hashCode(key);
try {
return segmentFor(hash).get(key, hash);
} catch (IOException e) {
throw new RuntimeException("Fail to get key in the map", e);
}
}
/**
* Tests if the specified object is a key in this table.
*
* @param key possible key
* @return <tt>true</tt> if and only if the specified object
* is a key in this table, as determined by the
* <tt>equals</tt> method; <tt>false</tt> otherwise.
* @throws RuntimeException throws if file IO operation fail
* @throws NullPointerException if the specified key is null
*/
public boolean containsKey(byte[] key) {
if (key == null || key.length == 0) throw new NullPointerException("key is null or empty");
final int hash = Arrays.hashCode(key);
try {
return segmentFor(hash).containsKey(key, hash);
} catch (IOException e) {
throw new RuntimeException("Fail to run ontainsKey operation on the map", e);
}
}
/**
* Maps the specified key to the specified value in this table for the specified duration.
* If the map previously contained a mapping for the key, the old value is
* replaced by the specified value.
* Neither the key nor the value can be null.
*
* <p> The value can be retrieved by calling the <tt>get</tt> method
* with a key that is equal to the original key.
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
* @param ttlInMs the time in ms during which the entry can stay in the map (time-to-live). When
* this time has elapsed, the entry will be evicted from the map automatically. A value of 0 for
* this argument means "forever", i.e. <tt>put(key, value, 0)</tt> is equivalent to
* <tt>put(key, value).
* @return the previous value associated with <tt>key</tt>, or
* <tt>null</tt> if there was no mapping for <tt>key</tt>
* @throws RuntimeException throws if file IO operation fail
* @throws NullPointerException if the specified key or value is null
*/
@Override
public byte[] put(byte[] key, byte[] value, long ttlInMs) {
if (key == null || key.length == 0) throw new NullPointerException("key is null or empty");
if (value == null || value.length == 0) throw new NullPointerException("value is null or empty");
if (ttlInMs < 0) throw new IllegalArgumentException("Invalid time to live value " + ttlInMs + ", it must be >= 0.");
final int hash = Arrays.hashCode(key);
try {
return segmentFor(hash).put(key, hash, value, false, ttlInMs);
} catch (IOException e) {
throw new RuntimeException("Fail to put key/value in the map", e);
}
}
void restoreInUseMapEntry(MapEntry me) {
try {
byte[] key = me.getEntryKey();
if (key == null || key.length == 0) throw new NullPointerException("key is null or empty");
if (me.getEntryValue() == null || me.getEntryValue().length == 0) throw new NullPointerException("value is null or empty");
if (me.getTimeToLive() < 0) throw new IllegalArgumentException("Invalid time to live value " + me.getTimeToLive() + ", it must be >= 0.");
final int hash = Arrays.hashCode(key);
segmentFor(hash).restoreInUseMapEntry(me, hash);
} catch (IOException e) {
throw new RuntimeException("Fail to restore/put key/value in the map", e);
}
}
/**
* Maps the specified key to the specified value in this table.
* If the map previously contained a mapping for the key, the old value is
* replaced by the specified value.
* Neither the key nor the value can be null.
*
* <p> The value can be retrieved by calling the <tt>get</tt> method
* with a key that is equal to the original key.
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
* @return the previous value associated with <tt>key</tt>, or
* <tt>null</tt> if there was no mapping for <tt>key</tt>
* @throws RuntimeException throws if file IO operation fail
* @throws NullPointerException if the specified key or value is null
*/
public byte[] put(byte[] key, byte[] value) {
return this.put(key, value, 0L);
}
/**
* Maps the specified key to the specified value in this table for the specified duration only if the key is absent.
* Neither the key nor the value can be null.
*
* <p> The value can be retrieved by calling the <tt>get</tt> method
* with a key that is equal to the original key.
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
* @param ttlInMs the time in ms during which the entry can stay in the map (time-to-live). When
* this time has elapsed, the entry will be evicted from the map automatically. A value of 0 for
* this argument means "forever", i.e. <tt>putIfAbsent(key, value, 0)</tt> is equivalent to
* <tt>putIfAbsent(key, value).
* @return the previous value associated with <tt>key</tt>, or
* <tt>null</tt> if there was no mapping for <tt>key</tt>
* @throws RuntimeException throws if file IO operation fail
* @throws NullPointerException if the specified key or value is null
*/
@Override
public byte[] putIfAbsent(byte[] key, byte[] value, long ttlInMs) {
if (key == null || key.length == 0) throw new NullPointerException("key is null or empty");
if (value == null || value.length == 0) throw new NullPointerException("value is null or empty");
if (ttlInMs < 0) throw new IllegalArgumentException("Invalid time to live value " + ttlInMs + ", it must be >= 0.");
final int hash = Arrays.hashCode(key);
try {
return segmentFor(hash).put(key, hash, value, true, ttlInMs);
} catch (IOException e) {
throw new RuntimeException("Fail to putIfAbsent key/value in the map", e);
}
}
/**
* Maps the specified key to the specified value in this table only if the key is absent.
* Neither the key nor the value can be null.
*
* <p> The value can be retrieved by calling the <tt>get</tt> method
* with a key that is equal to the original key.
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
* @return the previous value associated with <tt>key</tt>, or
* <tt>null</tt> if there was no mapping for <tt>key</tt>
* @throws RuntimeException throws if file IO operation fail
* @throws NullPointerException if the specified key or value is null
*/
public byte[] putIfAbsent(byte[] key, byte[] value) {
return this.putIfAbsent(key, value, 0L);
}
/**
* Removes the key (and its corresponding value) from this map.
* This method does nothing if the key is not in the map.
*
* @param key the key that needs to be removed
* @return the previous value associated with <tt>key</tt>, or
* <tt>null</tt> if there was no mapping for <tt>key</tt>
* @throws RuntimeException throws if file IO operation fail
* @throws NullPointerException if the specified key is null
*/
@Override
public byte[] remove(byte[] key) {
if (key == null || key.length == 0) throw new NullPointerException("key is null or empty");
final int hash = Arrays.hashCode(key);
try {
return segmentFor(hash).remove(key, hash, null);
} catch (IOException e) {
throw new RuntimeException("Fail to remove key in the map", e);
}
}
/**
* Removes the key (and its corresponding value) from this map.
* This method does nothing if the key is not in the map.
*
* @param key the key that needs to be removed
* @return the previous value associated with <tt>key</tt>, or
* <tt>null</tt> if there was no mapping for <tt>key</tt>
* @throws RuntimeException throws if file IO operation fail
* @throws NullPointerException if the specified key is null
*/
public boolean remove(byte[] key, byte[] value) {
if (key == null || key.length == 0) throw new NullPointerException("key is null or empty");
if (value == null || value.length == 0) throw new NullPointerException("value is null or empty");
final int hash = Arrays.hashCode(key);
try {
return segmentFor(hash).remove(key, hash, value) != null;
} catch (IOException e) {
throw new RuntimeException("Fail to remove key/value in the map", e);
}
}
public boolean replace(byte[] key, byte[] oldValue, byte[] newValue) {
return this.replace(key, oldValue, newValue, 0L);
}
/**
*
*
* @throws NullPointerException if any of the arguments are null
*/
public boolean replace(byte[] key, byte[] oldValue, byte[] newValue, long ttlInMs) {
if (key == null || key.length == 0) throw new NullPointerException("key is null or empty");
if (oldValue == null || oldValue.length == 0) throw new NullPointerException("oldValue is null or empty");
if (newValue == null || newValue.length == 0) throw new NullPointerException("newValue is null or empty");
if (ttlInMs < 0) throw new IllegalArgumentException("Invalid time to live value " + ttlInMs + ", it must be >= 0.");
final int hash = Arrays.hashCode(key);
try {
return segmentFor(hash).replace(key, hash, oldValue, newValue, ttlInMs);
} catch (IOException e) {
throw new RuntimeException("Fail to replace key/value in the map", e);
}
}
public byte[] replace(byte[] key, byte[] value) {
return this.replace(key, value, 0L);
}
/**
*
*
* @throws NullPointerException if any of the arguments are null
*/
public byte[] replace(byte[] key, byte[] value, long ttlInMs) {
if (key == null || key.length == 0) throw new NullPointerException("key is null or empty");
if (value == null || value.length == 0) throw new NullPointerException("oldValue is null or empty");
if (ttlInMs < 0) throw new IllegalArgumentException("Invalid time to live value " + ttlInMs + ", it must be >= 0.");
final int hash = Arrays.hashCode(key);
try {
return segmentFor(hash).replace(key, hash, value, ttlInMs);
} catch (IOException e) {
throw new RuntimeException("Fail to replace key/value in the map", e);
}
}
/**
* Removes all of the mappings from this map.
*/
@Override
public void clear() {
for (Segment<byte[]> segment : segments) segment.clear();
}
@Override
public void close() throws IOException {
this.clear();
this.stopPurgeTimer();
this.mapEntryFactory.close();
}
@Override
public void removeAll() throws IOException {
this.clear();
this.mapEntryFactory.removeAll();
}
@Override
public IMMFStats getMemoryMappedFileStats() {
return this.mapEntryFactory;
}
}