/**
   * {@inheritDoc}
   *
   * @throws NullPointerException if any of the arguments are null
   */
  @Override
  public boolean replace(
      @NotNull final K key, @NotNull final V oldValue, @NotNull final V newValue) {

    if (key == null) throw new NullPointerException("'key' can not be null");

    if (oldValue == null) throw new NullPointerException("'oldValue' can not be null");

    if (newValue == null) throw new NullPointerException("'newValue' can not be null");

    return oldValue.equals(replaceIfValueIs(key, oldValue, newValue));
  }
    /**
     * implementation for map.replace(Key,Value) and map.replace(Key,Old,New)
     *
     * @param keyBytes the key of the entry to be replaced
     * @param expectedValue the expected value to replaced
     * @param newValue the new value that will only be set if the existing value in the map equals
     *     the {@param expectedValue} or {@param expectedValue} is null
     * @param hash2 a hash code relating to the {@keyBytes} ( not the natural hash of {@keyBytes} )
     * @return null if the value was not replaced, else the value that is replaced is returned
     */
    V replace(
        final DirectBytes keyBytes, final V expectedValue, final V newValue, final int hash2) {
      lock();
      try {

        hashLookup.startSearch(hash2);
        while (true) {

          final int pos = hashLookup.nextPos();

          if (pos < 0) {
            return null;

          } else {

            final long offset = entriesOffset + pos * entrySize;
            tmpBytes.storePositionAndSize(bytes, offset, entrySize);

            if (!keyEquals(keyBytes, tmpBytes)) continue;

            final long keyLength = keyBytes.remaining();
            tmpBytes.skip(keyLength);
            long valuePosition = tmpBytes.position();
            tmpBytes.readStopBit();
            final long alignPosition = align(tmpBytes.position());
            tmpBytes.position(alignPosition);

            final V valueRead = readObjectUsing(null, offset + keyLength);

            if (valueRead == null) return null;

            if (expectedValue == null || expectedValue.equals(valueRead)) {
              tmpBytes.position(valuePosition);
              appendInstance(keyBytes, newValue);
            }

            return valueRead;
          }
        }
      } finally {
        unlock();
      }
    }
    /**
     * implementation for map.remove(Key,Value)
     *
     * @param keyBytes the key of the entry to remove
     * @param expectedValue the entry will only be removed if the {@param existingValue} equals null
     *     or the {@param existingValue} equals that of the entry.value
     * @param hash2 a hash code relating to the {@keyBytes} ( not the natural hash of {@keyBytes} )
     * @return
     */
    V remove(final DirectBytes keyBytes, final V expectedValue, int hash2) {
      lock();
      try {
        hash2 = hashLookup.startSearch(hash2);
        while (true) {

          final int pos = hashLookup.nextPos();
          if (pos < 0) {
            return null;

          } else {
            final long offset = entriesOffset + pos * entrySize;
            tmpBytes.storePositionAndSize(bytes, offset, entrySize);
            if (!keyEquals(keyBytes, tmpBytes)) continue;
            final long keyLength =
                align(keyBytes.remaining() + tmpBytes.position()); // includes the stop bit length.
            tmpBytes.position(keyLength);
            V valueRemoved =
                expectedValue == null && removeReturnsNull
                    ? null
                    : readObjectUsing(null, offset + keyLength);

            if (expectedValue != null && !expectedValue.equals(valueRemoved)) return null;

            hashLookup.remove(hash2, pos);
            decrementSize();

            freeList.clear(pos);
            if (pos < nextSet) nextSet = pos;

            return valueRemoved;
          }
        }
      } finally {
        unlock();
      }
    }