Skip to content
22 changes: 21 additions & 1 deletion fastfilter/src/main/java/org/fastfilter/Filter.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.fastfilter;

import java.nio.ByteBuffer;

/**
* An approximate membership filter.
*/
Expand All @@ -14,7 +16,7 @@ public interface Filter {
boolean mayContain(long key);

/**
* Get the number of bits in thhe filter.
* Get the number of bits in the filter.
*
* @return the number of bits
*/
Expand Down Expand Up @@ -65,4 +67,22 @@ default long cardinality() {
return -1;
}

/**
* Get the serialized size of the filter.
*
* @return the size in bytes
*/
default int getSerializedSize() {
return -1;
}

/**
* Serializes the filter state into the provided {@code ByteBuffer}.
*
* @param buffer the byte buffer where the serialized state of the filter will be written
* @throws UnsupportedOperationException if the operation is not supported by the filter implementation
*/
default void serialize(ByteBuffer buffer) {
throw new UnsupportedOperationException();
}
}
1 change: 0 additions & 1 deletion fastfilter/src/main/java/org/fastfilter/utils/Hash.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import java.util.Random;

public class Hash {

private static Random random = new Random();

public static void setSeed(long seed) {
Expand Down
53 changes: 53 additions & 0 deletions fastfilter/src/main/java/org/fastfilter/xor/Xor16.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.fastfilter.xor;

import java.nio.ByteBuffer;

import org.fastfilter.Filter;
import org.fastfilter.utils.Hash;

Expand Down Expand Up @@ -143,4 +145,55 @@ private int fingerprint(long hash) {
return (int) (hash & ((1 << BITS_PER_FINGERPRINT) - 1));
}

private Xor16(int blockLength, int bitCount, long seed, short[] fingerprints) {
this.blockLength = blockLength;
this.bitCount = bitCount;
this.seed = seed;
this.fingerprints = fingerprints;
}

@Override
public int getSerializedSize() {
return Integer.BYTES + Long.BYTES + Integer.BYTES + fingerprints.length * Short.BYTES;
}

@Override
public void serialize(ByteBuffer buffer) {
if (buffer.remaining() < getSerializedSize()) {
throw new IllegalArgumentException("Buffer too small");
}

buffer.putInt(blockLength);
buffer.putLong(seed);
buffer.putInt(fingerprints.length);
for (final short fp : fingerprints) {
buffer.putShort(fp);
}
}

public static Xor16 deserialize(ByteBuffer buffer) {
// Check minimum size for header (1 int + 1 long + 1 int for length)
if (buffer.remaining() < Integer.BYTES + Long.BYTES + Integer.BYTES) {
throw new IllegalArgumentException("Buffer too small");
}

final int blockLength = buffer.getInt();
final long seed = buffer.getLong();

final int len = buffer.getInt();

// Check if buffer has enough bytes for all fingerprints
if (buffer.remaining() < len * Short.BYTES) {
throw new IllegalArgumentException("Buffer too small");
}

final short[] fingerprints = new short[len];
for (int i = 0; i < len; i++) {
fingerprints[i] = buffer.getShort();
}

final int bitCount = len * BITS_PER_FINGERPRINT;

return new Xor16(blockLength, bitCount, seed, fingerprints);
}
}
48 changes: 48 additions & 0 deletions fastfilter/src/main/java/org/fastfilter/xor/Xor8.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.fastfilter.xor;

import java.io.*;
import java.nio.ByteBuffer;

import org.fastfilter.Filter;
import org.fastfilter.utils.Hash;
Expand Down Expand Up @@ -187,4 +188,51 @@ public Xor8(InputStream in) {
}
}

private Xor8(int size, long seed, byte[] fingerprints) {
this.size = size;
this.arrayLength = getArrayLength(size);
this.bitCount = arrayLength * BITS_PER_FINGERPRINT;
this.blockLength = arrayLength / HASHES;
this.seed = seed;
this.fingerprints = fingerprints;
}

@Override
public int getSerializedSize() {
return Integer.BYTES + Long.BYTES + Integer.BYTES + fingerprints.length * Byte.BYTES;
}

@Override
public void serialize(ByteBuffer buffer) {
if (buffer.remaining() < getSerializedSize()) {
throw new IllegalArgumentException("Buffer too small");
}

buffer.putInt(size);
buffer.putLong(seed);
buffer.putInt(fingerprints.length);
buffer.put(fingerprints);
}

public static Xor8 deserialize(ByteBuffer buffer) {
// Check minimum size for header (1 int + 1 long + 1 int for length)
if (buffer.remaining() < Integer.BYTES + Long.BYTES + Integer.BYTES) {
throw new IllegalArgumentException("Buffer too small");
}

final int size = buffer.getInt();
final long seed = buffer.getLong();

final int len = buffer.getInt();

// Check if buffer has enough bytes for all fingerprints
if (buffer.remaining() < len * Byte.BYTES) {
throw new IllegalArgumentException("Buffer too small");
}

final byte[] fingerprints = new byte[len];
buffer.get(fingerprints);

return new Xor8(size, seed, fingerprints);
}
}
70 changes: 62 additions & 8 deletions fastfilter/src/main/java/org/fastfilter/xor/XorBinaryFuse16.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package org.fastfilter.xor;

import java.nio.ByteBuffer;
import java.util.Arrays;

import org.fastfilter.Filter;
import org.fastfilter.utils.Hash;

Expand All @@ -20,19 +20,25 @@ public class XorBinaryFuse16 implements Filter {
private final short[] fingerprints;
private long seed;

public XorBinaryFuse16(int segmentCount, int segmentLength) {
private XorBinaryFuse16(int segmentCount, int segmentLength, long seed, short[] fingerprints) {
if (segmentLength < 0 || Integer.bitCount(segmentLength) != 1) {
throw new IllegalArgumentException("Segment length needs to be a power of 2, is " + segmentLength);
}
if (segmentCount <= 0) {
throw new IllegalArgumentException("Illegal segment count: " + segmentCount);
}
this.segmentLength = segmentLength;

this.segmentCount = segmentCount;
this.segmentLengthMask = segmentLength - 1;
this.segmentCountLength = segmentCount * segmentLength;
this.arrayLength = (segmentCount + ARITY - 1) * segmentLength;
this.fingerprints = new short[arrayLength];
this.segmentLength = segmentLength;
this.segmentLengthMask = segmentLength - 1;
this.arrayLength = fingerprints.length;
this.fingerprints = fingerprints;
this.seed = seed;
}

public XorBinaryFuse16(int segmentCount, int segmentLength) {
this(segmentCount, segmentLength, 0L, new short[(segmentCount + ARITY - 1) * segmentLength]);
}

public long getBitCount() {
Expand Down Expand Up @@ -204,9 +210,10 @@ private void addAll(long[] keys) {
// It's better fail that either produce non-functional or incorrect filter.
throw new IllegalArgumentException("could not construct filter");
}
// use a new random numbers
// use a new random number
seed = Hash.randomSeed();
}

alone = null;
t2count = null;
t2hash = null;
Expand Down Expand Up @@ -258,4 +265,51 @@ private short fingerprint(long hash) {
return (short) hash;
}

}
@Override
public int getSerializedSize() {
return 2 * Integer.BYTES + Long.BYTES + Integer.BYTES + fingerprints.length * Short.BYTES;
}

@Override
public void serialize(ByteBuffer buffer) {
if (buffer.remaining() < getSerializedSize()) {
throw new IllegalArgumentException("Buffer too small");
}

buffer.putInt(segmentLength);
buffer.putInt(segmentCountLength);
buffer.putLong(seed);
buffer.putInt(fingerprints.length);
for (final short fp : fingerprints) {
buffer.putShort(fp);
}
}

public static XorBinaryFuse16 deserialize(ByteBuffer buffer) {
// Check minimum size for header (2 ints + 1 long + 1 int for length)
if (buffer.remaining() < 2 * Integer.BYTES + Long.BYTES + Integer.BYTES) {
throw new IllegalArgumentException("Buffer too small");
}

final int segmentLength = buffer.getInt();
final int segmentCountLength = buffer.getInt();
final long seed = buffer.getLong();

final int len = buffer.getInt();

// Check if buffer has enough bytes for all fingerprints
if (buffer.remaining() < len * Short.BYTES) {
throw new IllegalArgumentException("Buffer too small");
}

final short[] fingerprints = new short[len];
for (int i = 0; i < len; i++) {
fingerprints[i] = buffer.getShort();
}

// Calculate segmentCount from segmentCountLength and segmentLength
final int segmentCount = segmentCountLength / segmentLength;

return new XorBinaryFuse16(segmentCount, segmentLength, seed, fingerprints);
}
}
64 changes: 59 additions & 5 deletions fastfilter/src/main/java/org/fastfilter/xor/XorBinaryFuse32.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.fastfilter.xor;

import java.nio.ByteBuffer;
import java.util.Arrays;

import org.fastfilter.Filter;
Expand All @@ -20,19 +21,25 @@ public class XorBinaryFuse32 implements Filter {
private final int[] fingerprints;
private long seed;

public XorBinaryFuse32(int segmentCount, int segmentLength) {
private XorBinaryFuse32(int segmentCount, int segmentLength, long seed, int[] fingerprints) {
if (segmentLength < 0 || Integer.bitCount(segmentLength) != 1) {
throw new IllegalArgumentException("Segment length needs to be a power of 2, is " + segmentLength);
}
if (segmentCount <= 0) {
throw new IllegalArgumentException("Illegal segment count: " + segmentCount);
}
this.segmentLength = segmentLength;

this.segmentCount = segmentCount;
this.segmentLengthMask = segmentLength - 1;
this.segmentCountLength = segmentCount * segmentLength;
this.arrayLength = (segmentCount + ARITY - 1) * segmentLength;
this.fingerprints = new int[arrayLength];
this.segmentLength = segmentLength;
this.segmentLengthMask = segmentLength - 1;
this.arrayLength = fingerprints.length;
this.fingerprints = fingerprints;
this.seed = seed;
}

public XorBinaryFuse32(int segmentCount, int segmentLength) {
this(segmentCount, segmentLength, 0L, new int[(segmentCount + ARITY - 1) * segmentLength]);
}

public long getBitCount() {
Expand Down Expand Up @@ -258,4 +265,51 @@ private int fingerprint(long hash) {
return (int) (hash ^ (hash >>> 32));
}

@Override
public int getSerializedSize() {
return 2 * Integer.BYTES + Long.BYTES + Integer.BYTES + fingerprints.length * Integer.BYTES;
}

@Override
public void serialize(ByteBuffer buffer) {
if (buffer.remaining() < getSerializedSize()) {
throw new IllegalArgumentException("Buffer too small");
}

buffer.putInt(segmentLength);
buffer.putInt(segmentCountLength);
buffer.putLong(seed);
buffer.putInt(fingerprints.length);
for (final int fp : fingerprints) {
buffer.putInt(fp);
}
}

public static XorBinaryFuse32 deserialize(ByteBuffer buffer) {
// Check minimum size for header (2 ints + 1 long + 1 int for length)
if (buffer.remaining() < 2 * Integer.BYTES + Long.BYTES + Integer.BYTES) {
throw new IllegalArgumentException("Buffer too small");
}

final int segmentLength = buffer.getInt();
final int segmentCountLength = buffer.getInt();
final long seed = buffer.getLong();

final int len = buffer.getInt();

// Check if buffer has enough bytes for all fingerprints
if (buffer.remaining() < len * Integer.BYTES) {
throw new IllegalArgumentException("Buffer too small");
}

final int[] fingerprints = new int[len];
for (int i = 0; i < len; i++) {
fingerprints[i] = buffer.getInt();
}

// Calculate segmentCount from segmentCountLength and segmentLength
final int segmentCount = segmentCountLength / segmentLength;

return new XorBinaryFuse32(segmentCount, segmentLength, seed, fingerprints);
}
}
Loading