// creates a new BloomFilter - access via BloomFilter.createOptimal(...)
  private BloomFilter(
      File f,
      int buckets,
      int hashFns,
      boolean force,
      int seekThreshold,
      BucketSize countBits,
      Allocator cacheAllocator,
      CloseCallback callback)
      throws IOException {
    this.closeCallback = callback;
    this.seekThreshold = seekThreshold;
    this.metadata = BloomMetadata.createNew(buckets, hashFns, countBits);
    hash = new RepeatedMurmurHash(hashFns, this.metadata.getBucketCount());

    // creating a new filter - so I can just be lazy and start it zero'd
    cache = cacheAllocator.apply(this.metadata.getTotalLength() - this.metadata.getHeaderLength());
    cacheDirty = true;

    open = true;

    if (f != null) {
      if (f.exists()) {
        if (force) {
          if (!f.delete()) {
            throw new IOException("Couldn't delete old file at " + f.getAbsolutePath());
          }
        } else {
          throw new IllegalArgumentException(
              "Can't create a new BloomFilter at "
                  + f.getAbsolutePath()
                  + " since it already exists");
        }
      }

      file = new RandomAccessFile(f, "rw");
      this.metadata.writeToFile(file);
      file.setLength(metadata.getTotalLength());
      file.getFD().sync();
      unflushedChanges = new ConcurrentSkipListMap<Integer, Byte>();

      if (f.length() != metadata.getTotalLength()) {
        throw new RuntimeException(
            "Bad size - expected " + metadata.getTotalLength() + " but got " + f.length());
      }
    } else {
      unflushedChanges =
          null; // don't bother keeping track of unflushed changes if this is memory only
      file = null;
    }
  }
  // Opens an existing bloom filter.  Access via BloomFilter.openExisting(...)
  private BloomFilter(
      File f, int seekThreshold, Allocator cacheAllocator, CloseCallback closeCallback)
      throws IOException {
    assert f.exists() && f.isFile() && f.canRead() && f.canWrite()
        : "Trying to open a non-existent bloom filter";
    this.seekThreshold = seekThreshold;
    this.closeCallback = closeCallback;

    file = new RandomAccessFile(f, "rw");
    this.metadata = BloomMetadata.readHeader(file);
    unflushedChanges = new ConcurrentSkipListMap<Integer, Byte>();

    // load the cache with the on disk data
    cache = cacheAllocator.apply(metadata.getTotalLength() - metadata.getHeaderLength());
    int readRes = file.read(cache);
    assert readRes == (metadata.getTotalLength() - metadata.getHeaderLength())
        : "I only read "
            + readRes
            + " bytes, but was expecting "
            + (metadata.getTotalLength() - metadata.getHeaderLength());

    hash = new RepeatedMurmurHash(metadata.getHashFns(), metadata.getBucketCount());
    open = true;
  }