##Typo##

This probably happened when you cut and pasted your code, but your `contains` function had a few lines switched in their ordering.

##Operations taking O(size) time, could be faster##

Your add, remove, and contains operations all do approximately the same thing.  They create a hash where the hash contains up to `bpe` bits set in an array of `size` bits.  Then they iterate over the `size` bits.

The important thing here is that `bpe` is much smaller than `size`.  For any size larger than 64, `bpe` will be `sqrt(size)`.  Since only `bpe` bits are set, you only need to take `O(bpe)` time instead of `O(size)` time.

##Operations using 32x the required space##

Also, when you create your array of bits, you use one int per bit even though you only ever set the int to 1 or 0.  If size is very large, such as 100 million, you could be using 400 MB when you only need to be using 12.5 MB.

##Putting it all together##

I rewrote your functions, and also combined all of their functionality into the hashing function since all 3 functions were so similar.  In the new code, the loops only run through `bpe` iterations instead of `size` iterations.  I also packed the bits to minimize the space used.


    /**
     * Performs one of three operations: add, remove, or contains.
     *
     * @param        v                The element to add/remove/test.
     * @param        operation        1 = add, -1 = remove, 0 = contains
     * @return                        If operation is "contains", returns true
     *                                if v is contained in the bloom filter, and
     *                                false if it is not.  If operation is
     *                                anything else, returns true.
     */
    private boolean hashOperation(V v, int operation)
    {
        Random r         = new Random(v.hashCode());
        int[]  bitsFound = new int[(size+31)/32];

        for(int i=0;i<bpe;i++){
            int bitNum   = r.nextInt(size);
            int bitIndex = bitNum >> 5;
            int bit      = (1 << (bitNum & 31));
            if ((bitsFound[bitIndex] & bit) == 0) {
                // New bit.  Perform operation.
                bitsFound[bitIndex] |= bit;
                if (operation == 0) {
                    if(data[bitNum] == 0)
                        return false;
                } else {
                    data[bitNum] += operation;
                }
            }
        }
        return true;
    }

    public boolean remove(V v)
    {
        if (contains(v)) {
            hashOperation(v, -1);
            count--;
            return true;
        }
        return false;
    }

    public void insert(V v)
    {
        hashOperation(v, 1);
        count++;
    }

    public boolean contains(V v)
    {
        return hashOperation(v, 0);
    }

##Addendum: Testing the fastest way to hash using maaartinus's method##

Maaartinus showed a fast way to hash using just 2 hash functions.  I decided to test out 4 variants:

1. Using a power of 2 size (should be fastest)
2. Using modulus operator every loop (should be slowest)
3. Using a compare + subtract
4. Doing the subtract without using any compare

Note, the following is all done in C:

**Variant 1**

All variants use the following setup.  The thing that varies will be in the loop:

    uint32_t hash, hash1, hash2, size;

    size  = rand();
    hash1 = rand() % size;
    hash2 = rand() % size;

    hash = hash1;
    uint32_t sizeMask = 0x3fffffff;  // Variant 1 only
    for (i=0;i<iterations;i++) {
        hash = (hash + hash2) & (sizeMask);
    }

**Variant 2**

    for (i=0;i<iterations;i++) {
        hash = (hash + hash2) % size;
    }

**Variant 3**

    // If size is < 0x80000000
    for (i=0;i<iterations;i++) {
        hash += hash2;
        if (hash > size)
            hash -= size;
    }

    // This works for all cases (and has the same runtime as the one above)
    for (i=0;i<iterations;i++) {
        if (size - hash > hash2)
            hash += hash2;
        else
            hash -= (size - hash2);
    }

**Variant 4**

    // In C, >> is not guaranteed to do an arithmetic shift, so
    // we use a logical shift instead.  The compiler optimizes this code
    // to do an arithmetic shift anyways.  In java, change the ">>" here
    // to a ">>>".
    for (i=0;i<iterations;i++) {
        hash += hash2;
        hash -= (-((size - hash) >> 31)) & size;
    }

    // In java, where >> is arithmetic shift, you could write it like this.
    for (i=0;i<iterations;i++) {
        hash += hash2;
        hash -= ((size - hash) >> 31) & size;
    }

##Timing results##

Running each variant for 1 billion iterations (results in seconds):

<!-- language: lang-none -->

    Variant 1 (& power of 2): 0.58
    Variant 2 (% size)      : 6.56
    Variant 3 (if, subtract): 1.25
    Variant 4 (no if)       : 1.44

However, this is a bit misleading because there was nothing in the loop except for the hash calculation.  If I add in the part where we use the hash on each loop:

        bits[hash>>5] |= (1 << (hash & 31));

Then the timings all become much closer:

<!-- language: lang-none -->

    Size = 1 billion
    Variant 1 (& power of 2): 15.92
    Variant 2 (% size)      : 18.92
    Variant 3 (if, subtract): 17.48
    Variant 4 (no if)       : 16.76

    Size = 4 million
    Variant 1 (& power of 2): 2.48
    Variant 2 (% size)      : 7.11
    Variant 3 (if, subtract): 2.58
    Variant 4 (no if)       : 2.52

So it appears that at large sizes, the memory manipulation of the bit array will be the limiting factor and not the hash calculation.  So you can choose from the above 4 variants based on:

1. Whether you can force the size to be a power of 2 or not.
2. How much you need that extra bit of speed versus how clear/simple you want your code to be.