diff --git a/binary.go b/binary.go new file mode 100644 index 0000000..527196b --- /dev/null +++ b/binary.go @@ -0,0 +1,48 @@ +package bloom + +import ( + "bytes" + "encoding/gob" +) + +const binVer0 = 0 + +// Data to be included in binary representation of Filter. +type marshalFilter struct { + Version int + Data []uint64 + Lookups int + Count int64 +} + +// MarshalBinary returns a binary representation of the filter. +// +// This method implements the encoding.BinaryMarshaler interface. +// The packages encoding/gob, encoding/json, and encoding/xml +// all check for this interface. +func (f *Filter) MarshalBinary() ([]byte, error) { + mf := marshalFilter{ + Version: binVer0, + Data: f.data, + Lookups: f.lookups, + Count: f.count, + } + var b bytes.Buffer + err := gob.NewEncoder(&b).Encode(mf) + return b.Bytes(), err +} + +// UnmarshalBinary imports binary data created by MarshalBinary +// into an empty filter. If the filter is not empty, all previous +// entries are overwritten. +func (f *Filter) UnmarshalBinary(data []byte) error { + var mf marshalFilter + err := gob.NewDecoder(bytes.NewBuffer(data)).Decode(&mf) + if err != nil { + return err + } + f.data = mf.Data + f.lookups = mf.Lookups + f.count = mf.Count + return nil +} diff --git a/example_test.go b/example_test.go index fad6580..8cf1058 100644 --- a/example_test.go +++ b/example_test.go @@ -1,8 +1,11 @@ package bloom_test import ( + "bytes" + "encoding/gob" "fmt" "github.com/yourbasic/bloom" + "log" "math/rand" "strconv" ) @@ -72,3 +75,30 @@ func ExampleFilter_Union() { fmt.Println("f1 ∪ f2:", f1.Union(f2).Count()) // Output: f1 ∪ f2: 505 } + +// Send a filter over a network using the encoding/gob package. +func ExampleFilter_MarshalBinary_network() { + // Create a mock network and a new Filter. + var network bytes.Buffer + f1 := bloom.New(1000, 100) + f1.Add("Hello, filter!") + + // Create an encoder and send the filter to the network. + enc := gob.NewEncoder(&network) + if err := enc.Encode(f1); err != nil { + log.Fatal("encode error:", err) + } + + // Create a decoder and receive the filter from the network. + dec := gob.NewDecoder(&network) + var f2 bloom.Filter + if err := dec.Decode(&f2); err != nil { + log.Fatal("decode error:", err) + } + + // Check that we got the same filter back. + if f2.Test("Hello, filter!") { + fmt.Println("Filter arrived safely.") + } + // Output: Filter arrived safely. +} diff --git a/filter.go b/filter.go index 243dca4..4056c0f 100644 --- a/filter.go +++ b/filter.go @@ -5,10 +5,9 @@ // A Bloom filter is a fast and space-efficient probabilistic data structure // used to test set membership. // -// A membership test returns either -// ”likely member” or ”definitely not a member”. Only false positives -// can occur: an element that has been added to the filter -// will be identified as ”likely member”. +// A membership test returns either ”likely member” or ”definitely not +// a member”. Only false positives can occur: an element that has been added +// to the filter will always be identified as ”likely member”. // // Elements can be added, but not removed. With more elements in the filter, // the probability of false positives increases. @@ -97,11 +96,13 @@ func (f *Filter) add(h1, h2 uint64) bool { } // TestByte tells if b is a likely member of the filter. +// If true, b is probably a member; if false, b is definitely not a member. func (f *Filter) TestByte(b []byte) bool { return f.test(hash(b)) } // Test tells if s is a likely member of the filter. +// If true, s is probably a member; if false, s is definitely not a member. func (f *Filter) Test(s string) bool { return f.test(hashString(s)) } diff --git a/filter_test.go b/filter_test.go index a2c5def..3649b97 100644 --- a/filter_test.go +++ b/filter_test.go @@ -1,6 +1,9 @@ package bloom import ( + "bytes" + "encoding/gob" + "reflect" "testing" ) @@ -140,6 +143,27 @@ func TestUnion(t *testing.T) { } } +func TestMarshal(t *testing.T) { + var network bytes.Buffer + f1 := New(10000, 100) + f1.Add("Hello, filter!") + + enc := gob.NewEncoder(&network) + if err := enc.Encode(f1); err != nil { + t.Errorf("Encode->err = %v; want nil\n", err) + } + + dec := gob.NewDecoder(&network) + var f2 *Filter + if err := dec.Decode(&f2); err != nil { + t.Errorf("Decode->err = %v; want nil\n", err) + } + + if !reflect.DeepEqual(f1, f2) { + t.Errorf("Encode(Code(f)) = %v; want %v\n", f1, f2) + } +} + var fox string = "The quick brown fox jumps over the lazy dog." func BenchmarkAdd(b *testing.B) {