├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── pom.xml
└── src
├── main
└── java
│ └── com
│ └── github
│ └── wxisme
│ └── bloomfilter
│ ├── bitset
│ ├── BaseBitSet.java
│ ├── JavaBitSet.java
│ └── RedisBitSet.java
│ └── common
│ ├── BloomFilter.java
│ └── MessageDigestUtils.java
└── test
└── java
└── com
└── github
└── wxisme
└── bloomfilter
└── bitset
├── JavaBitSetTest.java
├── RedisBitSetTest.java
├── YourBitSet.java
└── YourBitSetTest.java
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | .apt_generated
3 | .classpath
4 | .factorypath
5 | .project
6 | .settings
7 | .springBeans
8 |
9 | .idea
10 | *.iws
11 | *.iml
12 | *.ipr
13 |
14 | target/
15 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 | jdk:
3 | - oraclejdk8
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
166 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # bloomfilter
2 | [](https://travis-ci.org/wxisme/bloomfilter)  
3 |
4 | An empty Bloom filter is a bit array of m bits, all set to 0. There must also be hashNumber different hash functions defined, each of which maps or hashes some set element to one of the m array positions, generating a uniform random distribution. Typically, hashNumber is a constant, much smaller than m, which is proportional to the number of elements to be added; the precise choice of hashNumber and the constant of proportionality of m are determined by the intended false positive probability of the filter.
5 | If you are not familiar with it, you can refer to [Wikipedia's detailed analysis](https://en.wikipedia.org/wiki/Bloom_filter).
6 | If you use python, you can refer to the [bloomfilter written in python](https://github.com/wxisme/py-bloomfilter)
7 |
8 | ## Overview
9 |
10 | This project implements bloomfilter and can select and extend different BitSets or other storage methods. For example, BitSet supports `Java` and `Redis`. It can also be applied to scenarios where various data levels and different false positive probability are required.
11 |
12 | Release Log:
13 | bloomfilter version 1.0.0 Released
14 |
15 | [Download](https://github.com/wxisme/bloomfilter/releases) source code and binary jar package on the [release page](https://github.com/wxisme/bloomfilter/releases).
16 |
17 |
18 | ## Quick Start
19 |
20 | ### ~~Download & Install~~
21 |
22 | This is not necessary now. The dependency uploaded to the maven central repository.
23 |
24 | ```
25 | git clone https://github.com/wxisme/bloomfilter.git
26 |
27 | cd bloomfilter
28 |
29 | mvn clean install
30 | ```
31 |
32 | You can also download source code or binary jar package on the [release page](https://github.com/wxisme/bloomfilter/releases) directly.
33 |
34 | ### Add maven dependency
35 |
36 | ```xml
37 |
38 |
39 | com.github.wxisme
40 | bloomfilter
41 | 1.0.0
42 |
43 |
44 | ```
45 |
46 | ### Test
47 |
48 | ```java
49 | public class JavaBitSetTest {
50 |
51 | public static void main(String[] args) {
52 | //(falsePositiveProbability, expectedNumberOfElements)
53 | BloomFilter filter = new BloomFilter(0.0001, 10000);
54 | filter.bind(new JavaBitSet());
55 |
56 | filter.add("filter");
57 | System.out.println(filter.contains("filter"));
58 | System.out.println(filter.contains("bloom"));
59 | filter.add("bitset");
60 | filter.add("redis");
61 | System.out.println(filter.contains("bitset"));
62 | System.out.println(filter.contains("redis"));
63 | System.out.println(filter.contains("mysql"));
64 | System.out.println(filter.contains("linux"));
65 | System.out.println(filter.count());
66 | System.out.println(filter.isEmpty());
67 | filter.clear();
68 | System.out.println(filter.isEmpty());
69 | System.out.println(filter.contains("filter"));
70 |
71 | }
72 |
73 | }
74 | ```
75 | Expected test result is :
76 | ```
77 | true
78 | false
79 | true
80 | true
81 | false
82 | false
83 | 3
84 | false
85 | true
86 | false
87 | ```
88 |
89 | ### Redis and other extensions
90 |
91 |
92 | #### Redis
93 |
94 | You can easily use bloomfilter on redis,here is a simple example:
95 |
96 | ```java
97 | public class RedisBitSetTest {
98 |
99 | public static void main(String[] args) {
100 |
101 | //Don't forget auth password, you better use the configured redis client connection.
102 | //It should be noted that bloomfilter is not responsible for closing and returning redis connection resources.
103 |
104 | //(falsePositiveProbability, expectedNumberOfElements)
105 | BloomFilter filter = new BloomFilter(0.0001, 10000);
106 | Jedis jedis = new Jedis("127.0.0.1", 6379);
107 | jedis.auth("1234");
108 | filter.bind(new RedisBitSet(jedis, "bloomfilter:key:name"));
109 |
110 | //if you have a redis cluster
111 | //Set nodes = new HashSet<>();
112 | //nodes.add(new HostAndPort("127.0.0.1", 6379));
113 |
114 | //filter.bind(new RedisBitSet(new JedisCluster(nodes), "bloomfilter:key:name"));
115 |
116 | //you can also use jedispool
117 | //JedisPool jedisPool = new JedisPool("127.0.0.1", 6379);
118 | //Jedis jedis = jedisPool.getResource();
119 | //filter.bind(new RedisBitSet(jedis, "bloomfilter:key:name"));
120 |
121 | filter.add("filter");
122 | System.out.println(filter.contains("filter"));
123 | System.out.println(filter.contains("bloom"));
124 | filter.add("bitset");
125 | filter.add("redis");
126 | System.out.println(filter.contains("bitset"));
127 | System.out.println(filter.contains("redis"));
128 | System.out.println(filter.contains("mysql"));
129 | System.out.println(filter.contains("linux"));
130 | System.out.println(filter.count());
131 | System.out.println(filter.isEmpty());
132 | filter.clear();
133 | System.out.println(filter.isEmpty());
134 | System.out.println(filter.contains("filter"));
135 | }
136 | }
137 | ```
138 | Don't forget auth password, you better use the configured redis client connection. It should be noted that bloomfilter is not responsible for closing and returning redis connection resources.
139 | If you are not familiar with redis, you can refer to the following hyperlink:
140 | https://redis.io
141 | https://github.com/xetorthio/jedis
142 |
143 | Test result is :
144 | ```
145 | true
146 | false
147 | true
148 | true
149 | false
150 | false
151 | 3
152 | false
153 | true
154 | false
155 | ```
156 | #### Extensions
157 |
158 | If you want to use your own data structure, you can directly implement the `BaseBitSet` interface.
159 | Example:
160 |
161 | ```java
162 | public class YourBitSet implements BaseBitSet {
163 |
164 | private int[] data;//boolean array
165 |
166 | public YourBitSet(int size) {
167 | data = new int[size];
168 | }
169 |
170 | @Override
171 | public void set(int bitIndex) {
172 | data[bitIndex] = 1;
173 | }
174 |
175 | @Override
176 | public void set(int bitIndex, boolean value) {
177 | if (value)
178 | data[bitIndex] = 1;
179 | else data[bitIndex] = 0;
180 | }
181 |
182 | @Override
183 | public boolean get(int bitIndex) {
184 | return data[bitIndex] == 1;
185 | }
186 |
187 | @Override
188 | public void clear(int bitIndex) {
189 | data[bitIndex] = 0;
190 | }
191 |
192 | @Override
193 | public void clear() {
194 | Arrays.fill(data, 0);
195 | }
196 |
197 | @Override
198 | public long size() {
199 | long size = 0;
200 | for (int d : data)
201 | if (d == 1)
202 | size++;
203 | return size;
204 | }
205 |
206 | @Override
207 | public boolean isEmpty() {
208 | return size() <= 0;
209 | }
210 | }
211 | ```
212 | Test:
213 | ```java
214 | public class YourBitSetTest {
215 |
216 | public static void main(String[] args) {
217 | //(falsePositiveProbability, expectedNumberOfElements)
218 | BloomFilter filter = new BloomFilter(0.0001, 10000);
219 | filter.bind(new YourBitSet(1000000));
220 |
221 | filter.add("filter");
222 | System.out.println(filter.contains("filter"));
223 | System.out.println(filter.contains("bloom"));
224 | filter.add("bitset");
225 | filter.add("redis");
226 | System.out.println(filter.contains("bitset"));
227 | System.out.println(filter.contains("redis"));
228 | System.out.println(filter.contains("mysql"));
229 | System.out.println(filter.contains("linux"));
230 | System.out.println(filter.count());
231 | System.out.println(filter.isEmpty());
232 | filter.clear();
233 | System.out.println(filter.isEmpty());
234 | System.out.println(filter.contains("filter"));
235 |
236 | }
237 |
238 | }
239 | ```
240 | Test result is :
241 | ```
242 | true
243 | false
244 | true
245 | true
246 | false
247 | false
248 | 3
249 | false
250 | true
251 | false
252 | ```
253 |
254 | #### Welcome to submit your extension code or put any question issues on github.
255 |
256 | ### License
257 |
258 | Bloomfilter is released under the [GNU Lesser General Public License v3.0](http://www.gnu.org/licenses/).
259 | You can just use maven to import dependency package or use your own compiled jar package.
260 | You may copy this code directly into your project if you leave the LGPL-comment in place and reference the following hyperlink:
261 | https://github.com/MagnusS/Java-BloomFilter
262 | https://github.com/wxisme/bloomfilter
263 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | com.github.wxisme
8 | bloomfilter
9 | 1.0.0
10 |
11 |
12 | org.sonatype.oss
13 | oss-parent
14 | 7
15 |
16 |
17 |
18 |
19 |
20 | org.apache.maven.plugins
21 | maven-compiler-plugin
22 |
23 | 1.8
24 | 1.8
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | redis.clients
33 | jedis
34 | 2.8.1
35 |
36 |
37 |
38 |
39 |
40 | GNU Lesser General Public License Version 3
41 | http://www.gnu.org/licenses/lgpl.txt
42 | repo
43 |
44 |
45 |
46 | master
47 | https://github.com/wxisme/bloomfilter.git
48 | scm:git:https://github.com/wxisme/bloomfilter.git
49 | scm:git:https://github.com/wxisme/bloomfilter.git
50 |
51 |
52 |
53 | Wang Xu
54 | wangxucoop@126.com
55 | wxisme
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/src/main/java/com/github/wxisme/bloomfilter/bitset/BaseBitSet.java:
--------------------------------------------------------------------------------
1 | /**
2 | * This program is free software: you can redistribute it and/or modify
3 | * it under the terms of the GNU Lesser General Public License as published by
4 | * the Free Software Foundation, either version 3 of the License, or
5 | * (at your option) any later version.
6 | *
7 | * This program is distributed in the hope that it will be useful,
8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 | * GNU Lesser General Public License for more details.
11 | *
12 | * You should have received a copy of the GNU Lesser General Public License
13 | * along with this program. If not, see .
14 | */
15 |
16 | package com.github.wxisme.bloomfilter.bitset;
17 |
18 | /**
19 | * Base bit set interface. If you want to use your own data structure you can implement this interface.
20 | */
21 | public interface BaseBitSet extends Cloneable, java.io.Serializable {
22 |
23 | /**
24 | * Set a single bit in the Bloom filter, value default is true.
25 | *
26 | * @param bitIndex bit index.
27 | */
28 | public void set(int bitIndex);
29 |
30 | /**
31 | * Set a single bit in the Bloom filter, value is true or false.
32 | *
33 | * @param bitIndex bit index.
34 | * @param value value true or false.
35 | */
36 | public void set(int bitIndex, boolean value);
37 |
38 | /**
39 | * Return the bit set used to store the Bloom filter.
40 | *
41 | * @param bitIndex bit index.
42 | * @return the bit set used to store the Bloom filter.
43 | */
44 | public boolean get(int bitIndex);
45 |
46 | /**
47 | * Clear the bit set on the index, so the bit set value is false on index.
48 | *
49 | * @param bitIndex bit index.
50 | */
51 | public void clear(int bitIndex);
52 |
53 | /**
54 | * Clear the bit set, so the bit set value is all false.
55 | */
56 | public void clear();
57 |
58 | /**
59 | * Returns the number of bits in the Bloom filter.
60 | *
61 | * @return the number of bits in the Bloom filter.
62 | */
63 | public long size();
64 |
65 | /**
66 | * Returns is the bit set empty, bit set is empty means no any elements added to bit set.
67 | *
68 | * @return is the bit set empty.
69 | */
70 | public boolean isEmpty();
71 | }
72 |
--------------------------------------------------------------------------------
/src/main/java/com/github/wxisme/bloomfilter/bitset/JavaBitSet.java:
--------------------------------------------------------------------------------
1 | /**
2 | * This program is free software: you can redistribute it and/or modify
3 | * it under the terms of the GNU Lesser General Public License as published by
4 | * the Free Software Foundation, either version 3 of the License, or
5 | * (at your option) any later version.
6 | *
7 | * This program is distributed in the hope that it will be useful,
8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 | * GNU Lesser General Public License for more details.
11 | *
12 | * You should have received a copy of the GNU Lesser General Public License
13 | * along with this program. If not, see .
14 | */
15 |
16 | package com.github.wxisme.bloomfilter.bitset;
17 |
18 | import java.util.BitSet;
19 |
20 | /**
21 | * Implement bloom filter on native java bitset.
22 | */
23 | public class JavaBitSet implements BaseBitSet {
24 |
25 | private BitSet bitSet;
26 |
27 | public JavaBitSet() {
28 | this.bitSet = new BitSet();
29 | }
30 |
31 | public JavaBitSet(BitSet bitSet) {
32 | if (bitSet == null) {
33 | this.bitSet = new BitSet();
34 | } else {
35 | this.bitSet = bitSet;
36 | }
37 | }
38 |
39 | public void set(int bitIndex) {
40 | this.bitSet.set(bitIndex);
41 | }
42 |
43 | public void set(int bitIndex, boolean value) {
44 | this.bitSet.set(bitIndex, value);
45 | }
46 |
47 | public boolean get(int bitIndex) {
48 | return this.bitSet.get(bitIndex);
49 | }
50 |
51 | public void clear(int bitIndex) {
52 | this.bitSet.clear(bitIndex);
53 | }
54 |
55 | public void clear() {
56 | this.bitSet.clear();
57 | }
58 |
59 | public long size() {
60 | return this.bitSet.size();
61 | }
62 |
63 | public boolean isEmpty() {
64 | return this.isEmpty();
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/main/java/com/github/wxisme/bloomfilter/bitset/RedisBitSet.java:
--------------------------------------------------------------------------------
1 | /**
2 | * This program is free software: you can redistribute it and/or modify
3 | * it under the terms of the GNU Lesser General Public License as published by
4 | * the Free Software Foundation, either version 3 of the License, or
5 | * (at your option) any later version.
6 | *
7 | * This program is distributed in the hope that it will be useful,
8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 | * GNU Lesser General Public License for more details.
11 | *
12 | * You should have received a copy of the GNU Lesser General Public License
13 | * along with this program. If not, see .
14 | */
15 |
16 | package com.github.wxisme.bloomfilter.bitset;
17 |
18 | import redis.clients.jedis.Jedis;
19 | import redis.clients.jedis.JedisCluster;
20 |
21 | /**
22 | * Implement bloom filter on redis bitset.
23 | */
24 | public class RedisBitSet implements BaseBitSet {
25 |
26 | private JedisCluster jedisCluster;
27 | private Jedis jedis;
28 | private String name;
29 |
30 | private boolean isCluster = true;
31 |
32 | private RedisBitSet() {
33 | }
34 |
35 | /**
36 | * Create a redis bitset.
37 | * @param jedisCluster jedis cluster client.
38 | * @param name the redis bit key name.
39 | */
40 | public RedisBitSet(JedisCluster jedisCluster, String name) {
41 | this.jedisCluster = jedisCluster;
42 | this.name = name;
43 | this.isCluster = true;
44 | }
45 |
46 | /**
47 | * Create a redis bitset.
48 | * @param jedis jedis client.
49 | * @param name the redis bit key name.
50 | */
51 | public RedisBitSet(Jedis jedis, String name) {
52 | this.jedis = jedis;
53 | this.name = name;
54 | this.isCluster = false;
55 | }
56 |
57 |
58 | public void set(int bitIndex) {
59 | if (this.isCluster) {
60 | this.jedisCluster.setbit(this.name, bitIndex, true);
61 | } else {
62 | this.jedis.setbit(this.name, bitIndex, true);
63 | }
64 | }
65 |
66 | public void set(int bitIndex, boolean value) {
67 | if (this.isCluster) {
68 | this.jedisCluster.setbit(this.name, bitIndex, value);
69 | } else {
70 | this.jedis.setbit(this.name, bitIndex, value);
71 | }
72 | }
73 |
74 | public boolean get(int bitIndex) {
75 | if (this.isCluster) {
76 | return this.jedisCluster.getbit(this.name, bitIndex);
77 | } else {
78 | return this.jedis.getbit(this.name, bitIndex);
79 | }
80 | }
81 |
82 | public void clear(int bitIndex) {
83 | if (this.isCluster) {
84 | this.jedisCluster.setbit(this.name, bitIndex, false);
85 | } else {
86 | this.jedis.setbit(this.name, bitIndex, false);
87 | }
88 | }
89 |
90 | public void clear() {
91 | if (this.isCluster) {
92 | this.jedisCluster.del(this.name);
93 | } else {
94 | this.jedis.del(this.name);
95 | }
96 | }
97 |
98 | public long size() {
99 | if (this.isCluster) {
100 | return this.jedisCluster.bitcount(this.name);
101 | } else {
102 | return this.jedis.bitcount(this.name);
103 | }
104 | }
105 |
106 | public boolean isEmpty() {
107 | return size() <= 0;
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/main/java/com/github/wxisme/bloomfilter/common/BloomFilter.java:
--------------------------------------------------------------------------------
1 | /**
2 | * This program is free software: you can redistribute it and/or modify
3 | * it under the terms of the GNU Lesser General Public License as published by
4 | * the Free Software Foundation, either version 3 of the License, or
5 | * (at your option) any later version.
6 | *
7 | * This program is distributed in the hope that it will be useful,
8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 | * GNU Lesser General Public License for more details.
11 | *
12 | * You should have received a copy of the GNU Lesser General Public License
13 | * along with this program. If not, see .
14 | */
15 |
16 | package com.github.wxisme.bloomfilter.common;
17 |
18 | import com.github.wxisme.bloomfilter.bitset.BaseBitSet;
19 |
20 | import java.io.Serializable;
21 | import java.util.Collection;
22 |
23 | /**
24 | * This program refers to the java-bloomfilter,you can get its details form https://github.com/MagnusS/Java-BloomFilter.
25 | * You have any questions about this program please put issues on github to
26 | * https://github.com/wxisme/bloomfilter
27 | *
28 | * @param Element type
29 | */
30 | public class BloomFilter implements Cloneable, Serializable {
31 |
32 | private BaseBitSet bitSet;
33 | private int bitSetSize;
34 | private double bitsPerElement;
35 | private int expectedNumberOfFilterElements; // expected (maximum) number of elements to be added
36 | private int numberOfAddedElements; // number of elements actually added to the Bloom filter
37 | private int k; // number of hash functions
38 |
39 |
40 | /**
41 | * Bind a bit set for Bloom filter. It can be any data structure that implements the BaseBitSet interface.
42 | * @param bitSet
43 | */
44 | public void bind(BaseBitSet bitSet) {
45 | this.bitSet = bitSet;
46 | }
47 |
48 | /**
49 | * Constructs an empty Bloom filter. The total length of the Bloom filter will be
50 | * c*n.
51 | *
52 | * @param c is the number of bits used per element.
53 | * @param n is the expected number of elements the filter will contain.
54 | * @param k is the number of hash functions used.
55 | */
56 | public BloomFilter(double c, int n, int k) {
57 | this.expectedNumberOfFilterElements = n;
58 | this.k = k;
59 | this.bitsPerElement = c;
60 | this.bitSetSize = (int) Math.ceil(c * n);
61 | numberOfAddedElements = 0;
62 | }
63 |
64 | /**
65 | * Constructs an empty Bloom filter. The optimal number of hash functions (k) is estimated from the total size of the Bloom
66 | * and the number of expected elements.
67 | *
68 | * @param bitSetSize defines how many bits should be used in total for the filter.
69 | * @param expectedNumberOElements defines the maximum number of elements the filter is expected to contain.
70 | */
71 | public BloomFilter(int bitSetSize, int expectedNumberOElements) {
72 | this(bitSetSize / (double) expectedNumberOElements,
73 | expectedNumberOElements,
74 | (int) Math.round((bitSetSize / (double) expectedNumberOElements) * Math.log(2.0)));
75 | }
76 |
77 | /**
78 | * Constructs an empty Bloom filter with a given false positive probability. The number of bits per
79 | * element and the number of hash functions is estimated
80 | * to match the false positive probability.
81 | *
82 | * @param falsePositiveProbability is the desired false positive probability.
83 | * @param expectedNumberOfElements is the expected number of elements in the Bloom filter.
84 | */
85 | public BloomFilter(double falsePositiveProbability, int expectedNumberOfElements) {
86 | this(Math.ceil(-(Math.log(falsePositiveProbability) / Math.log(2.0))) / Math.log(2.0), // c = k / ln(2)
87 | expectedNumberOfElements,
88 | (int) Math.ceil(-(Math.log(falsePositiveProbability) / Math.log(2.0)))); // k = ceil(-log_2(false prob.))
89 | }
90 |
91 | /**
92 | * Construct a new Bloom filter based on existing Bloom filter data.
93 | *
94 | * @param bitSetSize defines how many bits should be used for the filter.
95 | * @param expectedNumberOfFilterElements defines the maximum number of elements the filter is expected to contain.
96 | * @param actualNumberOfFilterElements specifies how many elements have been inserted into the filterData BitSet.
97 | * @param filterData a BitSet representing an existing Bloom filter.
98 | */
99 | public BloomFilter(int bitSetSize, int expectedNumberOfFilterElements, int actualNumberOfFilterElements, BaseBitSet filterData) {
100 | this(bitSetSize, expectedNumberOfFilterElements);
101 | this.bitSet = filterData;
102 | this.numberOfAddedElements = actualNumberOfFilterElements;
103 | }
104 |
105 |
106 | /**
107 | * Compares the contents of two instances to see if they are equal.
108 | *
109 | * @param obj is the object to compare to.
110 | * @return True if the contents of the objects are equal.
111 | */
112 | @Override
113 | public boolean equals(Object obj) {
114 | if (obj == null) {
115 | return false;
116 | }
117 | if (getClass() != obj.getClass()) {
118 | return false;
119 | }
120 | final BloomFilter other = (BloomFilter) obj;
121 | if (this.expectedNumberOfFilterElements != other.expectedNumberOfFilterElements) {
122 | return false;
123 | }
124 | if (this.k != other.k) {
125 | return false;
126 | }
127 | if (this.bitSetSize != other.bitSetSize) {
128 | return false;
129 | }
130 | if (this.bitSet != other.bitSet && (this.bitSet == null || !this.bitSet.equals(other.bitSet))) {
131 | return false;
132 | }
133 | return true;
134 | }
135 |
136 | /**
137 | * Calculates a hash code for this class.
138 | *
139 | * @return hash code representing the contents of an instance of this class.
140 | */
141 | @Override
142 | public int hashCode() {
143 | int hash = 7;
144 | hash = 61 * hash + (this.bitSet != null ? this.bitSet.hashCode() : 0);
145 | hash = 61 * hash + this.expectedNumberOfFilterElements;
146 | hash = 61 * hash + this.bitSetSize;
147 | hash = 61 * hash + this.k;
148 | return hash;
149 | }
150 |
151 |
152 | /**
153 | * Calculates the expected probability of false positives based on
154 | * the number of expected filter elements and the size of the Bloom filter.
155 | *
156 | * The value returned by this method is the expected rate of false
157 | * positives, assuming the number of inserted elements equals the number of
158 | * expected elements. If the number of elements in the Bloom filter is less
159 | * than the expected value, the true probability of false positives will be lower.
160 | *
161 | *
162 | * @return expected probability of false positives.
163 | */
164 | public double expectedFalsePositiveProbability() {
165 | return getFalsePositiveProbability(expectedNumberOfFilterElements);
166 | }
167 |
168 | /**
169 | * Calculate the probability of a false positive given the specified
170 | * number of inserted elements.
171 | *
172 | * @param numberOfElements number of inserted elements.
173 | * @return probability of a false positive.
174 | */
175 | public double getFalsePositiveProbability(double numberOfElements) {
176 | // (1 - e^(-k * n / m)) ^ k
177 | return Math.pow((1 - Math.exp(-k * (double) numberOfElements
178 | / (double) bitSetSize)), k);
179 |
180 | }
181 |
182 | /**
183 | * Get the current probability of a false positive. The probability is calculated from
184 | * the size of the Bloom filter and the current number of elements added to it.
185 | *
186 | * @return probability of false positives.
187 | */
188 | public double getFalsePositiveProbability() {
189 | return getFalsePositiveProbability(numberOfAddedElements);
190 | }
191 |
192 |
193 | /**
194 | * Returns the value chosen for K.
195 | *
196 | * K is the optimal number of hash functions based on the size
197 | * of the Bloom filter and the expected number of inserted elements.
198 | *
199 | * @return optimal k.
200 | */
201 | public int getK() {
202 | return k;
203 | }
204 |
205 | /**
206 | * Sets all bits to false in the Bloom filter.
207 | */
208 | public void clear() {
209 | bitSet.clear();
210 | numberOfAddedElements = 0;
211 | }
212 |
213 | /**
214 | * Adds an object to the Bloom filter. The output from the object's
215 | * toString() method is used as input to the hash functions.
216 | *
217 | * @param element is an element to register in the Bloom filter.
218 | */
219 | public void add(E element) {
220 | add(element.toString().getBytes(MessageDigestUtils.CHARSET));
221 | }
222 |
223 | /**
224 | * Adds an array of bytes to the Bloom filter.
225 | *
226 | * @param bytes array of bytes to add to the Bloom filter.
227 | */
228 | public void add(byte[] bytes) {
229 | int[] hashes = MessageDigestUtils.createHashes(bytes, k);
230 | for (int hash : hashes)
231 | bitSet.set(Math.abs(hash % bitSetSize), true);
232 | numberOfAddedElements++;
233 | }
234 |
235 | /**
236 | * Adds all elements from a Collection to the Bloom filter.
237 | *
238 | * @param c Collection of elements.
239 | */
240 | public void addAll(Collection extends E> c) {
241 | for (E element : c)
242 | add(element);
243 | }
244 |
245 | /**
246 | * Returns true if the element could have been inserted into the Bloom filter.
247 | * Use getFalsePositiveProbability() to calculate the probability of this
248 | * being correct.
249 | *
250 | * @param element element to check.
251 | * @return true if the element could have been inserted into the Bloom filter.
252 | */
253 | public boolean contains(E element) {
254 | return contains(element.toString().getBytes(MessageDigestUtils.CHARSET));
255 | }
256 |
257 | /**
258 | * Returns true if the array of bytes could have been inserted into the Bloom filter.
259 | * Use getFalsePositiveProbability() to calculate the probability of this
260 | * being correct.
261 | *
262 | * @param bytes array of bytes to check.
263 | * @return true if the array could have been inserted into the Bloom filter.
264 | */
265 | public boolean contains(byte[] bytes) {
266 | int[] hashes = MessageDigestUtils.createHashes(bytes, k);
267 | for (int hash : hashes) {
268 | if (!bitSet.get(Math.abs(hash % bitSetSize))) {
269 | return false;
270 | }
271 | }
272 | return true;
273 | }
274 |
275 | /**
276 | * Returns true if all the elements of a Collection could have been inserted
277 | * into the Bloom filter. Use getFalsePositiveProbability() to calculate the
278 | * probability of this being correct.
279 | *
280 | * @param c elements to check.
281 | * @return true if all the elements in c could have been inserted into the Bloom filter.
282 | */
283 | public boolean containsAll(Collection extends E> c) {
284 | for (E element : c)
285 | if (!contains(element))
286 | return false;
287 | return true;
288 | }
289 |
290 | /**
291 | * Read a single bit from the Bloom filter.
292 | *
293 | * @param bit the bit to read.
294 | * @return true if the bit is set, false if it is not.
295 | */
296 | public boolean getBit(int bit) {
297 | return bitSet.get(bit);
298 | }
299 |
300 | /**
301 | * Set a single bit in the Bloom filter.
302 | *
303 | * @param bit is the bit to set.
304 | * @param value If true, the bit is set. If false, the bit is cleared.
305 | */
306 | public void setBit(int bit, boolean value) {
307 | bitSet.set(bit, value);
308 | }
309 |
310 | /**
311 | * Return the bit set used to store the Bloom filter.
312 | *
313 | * @return bit set representing the Bloom filter.
314 | */
315 | public BaseBitSet getBitSet() {
316 | return bitSet;
317 | }
318 |
319 | /**
320 | * Returns the number of bits in the Bloom filter. Use count() to retrieve
321 | * the number of inserted elements.
322 | *
323 | * @return the size of the bitSet used by the Bloom filter.
324 | */
325 | public int size() {
326 | return this.bitSetSize;
327 | }
328 |
329 | /**
330 | * Returns the number of elements added to the Bloom filter after it
331 | * was constructed or after clear() was called.
332 | *
333 | * @return number of elements added to the Bloom filter.
334 | */
335 | public int count() {
336 | return this.numberOfAddedElements;
337 | }
338 |
339 | /**
340 | * Returns is the bit set empty, bit set is empty means no any elements added to bloom filter.
341 | *
342 | * @return is the bit set empty
343 | */
344 | public boolean isEmpty() {
345 | return count() <= 0;
346 | }
347 |
348 | /**
349 | * Returns the expected number of elements to be inserted into the filter.
350 | * This value is the same value as the one passed to the constructor.
351 | *
352 | * @return expected number of elements.
353 | */
354 | public int getExpectedNumberOfElements() {
355 | return expectedNumberOfFilterElements;
356 | }
357 |
358 | /**
359 | * Get expected number of bits per element when the Bloom filter is full. This value is set by the constructor
360 | * when the Bloom filter is created. See also getBitsPerElement().
361 | *
362 | * @return expected number of bits per element.
363 | */
364 | public double getExpectedBitsPerElement() {
365 | return this.bitsPerElement;
366 | }
367 |
368 | /**
369 | * Get actual number of bits per element based on the number of elements that have currently been inserted and the length
370 | * of the Bloom filter. See also getExpectedBitsPerElement().
371 | *
372 | * @return number of bits per element.
373 | */
374 | public double getBitsPerElement() {
375 | return this.bitSetSize / (double) numberOfAddedElements;
376 | }
377 |
378 | }
--------------------------------------------------------------------------------
/src/main/java/com/github/wxisme/bloomfilter/common/MessageDigestUtils.java:
--------------------------------------------------------------------------------
1 | /**
2 | * This program is free software: you can redistribute it and/or modify
3 | * it under the terms of the GNU Lesser General Public License as published by
4 | * the Free Software Foundation, either version 3 of the License, or
5 | * (at your option) any later version.
6 | *
7 | * This program is distributed in the hope that it will be useful,
8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 | * GNU Lesser General Public License for more details.
11 | *
12 | * You should have received a copy of the GNU Lesser General Public License
13 | * along with this program. If not, see .
14 | */
15 |
16 | package com.github.wxisme.bloomfilter.common;
17 |
18 | import java.nio.charset.Charset;
19 | import java.security.MessageDigest;
20 | import java.security.NoSuchAlgorithmException;
21 |
22 | /**
23 | * This program refers to the java-bloomfilter,you can get its details form https://github.com/MagnusS/Java-BloomFilter.
24 | * You have any questions about this program please put issues on github to
25 | * https://github.com/wxisme/bloomfilter
26 | */
27 | public class MessageDigestUtils {
28 |
29 | private static final String MESSAGE_DIGEST_ALGORITHM_NAME = "MD5";//SHA1,SHA256
30 | public static final Charset CHARSET = Charset.forName("UTF-8"); // encoding used for storing hash values as strings
31 |
32 |
33 | private static final MessageDigest messageDigest;
34 |
35 | static {
36 | MessageDigest tempMessageDigest = null;
37 | try {
38 | tempMessageDigest = MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM_NAME);
39 | } catch (NoSuchAlgorithmException e) {
40 | e.printStackTrace();
41 | }
42 | messageDigest = tempMessageDigest;
43 | }
44 |
45 | /**
46 | * Generates a digest based on the contents of a String.
47 | *
48 | * @param val specifies the input data.
49 | * @param charset specifies the encoding of the input data.
50 | * @return digest as long.
51 | */
52 | public static int createHash(String val, Charset charset) {
53 | return createHash(val.getBytes(charset));
54 | }
55 |
56 | /**
57 | * Generates a digest based on the contents of a String.
58 | *
59 | * @param val specifies the input data. The encoding is expected to be UTF-8.
60 | * @return digest as long.
61 | */
62 | public static int createHash(String val) {
63 | return createHash(val, CHARSET);
64 | }
65 |
66 | /**
67 | * Generates a digest based on the contents of an array of bytes.
68 | *
69 | * @param data specifies input data.
70 | * @return digest as long.
71 | */
72 | public static int createHash(byte[] data) {
73 | return createHashes(data, 1)[0];
74 | }
75 |
76 | /**
77 | * Generates digests based on the contents of an array of bytes and splits the result into 4-byte int's and store them in an array. The
78 | * digest function is called until the required number of int's are produced. For each call to digest a salt
79 | * is prepended to the data. The salt is increased by 1 for each call.
80 | *
81 | * @param data specifies input data.
82 | * @param hashes number of hashes/int's to produce.
83 | * @return array of int-sized hashes
84 | */
85 | public static int[] createHashes(byte[] data, int hashes) {
86 | int[] result = new int[hashes];
87 |
88 | int k = 0;
89 | byte salt = 0;
90 | while (k < hashes) {
91 | byte[] digest;
92 | synchronized (messageDigest) {
93 | messageDigest.update(salt);
94 | salt++;
95 | digest = messageDigest.digest(data);
96 | }
97 |
98 | for (int i = 0; i < digest.length / 4 && k < hashes; i++) {
99 | int h = 0;
100 | for (int j = (i * 4); j < (i * 4) + 4; j++) {
101 | h <<= 8;
102 | h |= ((int) digest[j]) & 0xFF;
103 | }
104 | result[k] = h;
105 | k++;
106 | }
107 | }
108 | return result;
109 | }
110 |
111 | }
112 |
--------------------------------------------------------------------------------
/src/test/java/com/github/wxisme/bloomfilter/bitset/JavaBitSetTest.java:
--------------------------------------------------------------------------------
1 | package com.github.wxisme.bloomfilter.bitset;
2 |
3 | import com.github.wxisme.bloomfilter.common.BloomFilter;
4 |
5 | public class JavaBitSetTest {
6 |
7 | public static void main(String[] args) {
8 | //(falsePositiveProbability, expectedNumberOfElements)
9 | BloomFilter filter = new BloomFilter(0.0001, 10000);
10 | filter.bind(new JavaBitSet());
11 |
12 | filter.add("filter");
13 | System.out.println(filter.contains("filter"));
14 | System.out.println(filter.contains("bloom"));
15 | filter.add("bitset");
16 | filter.add("redis");
17 | System.out.println(filter.contains("bitset"));
18 | System.out.println(filter.contains("redis"));
19 | System.out.println(filter.contains("mysql"));
20 | System.out.println(filter.contains("linux"));
21 | System.out.println(filter.count());
22 | System.out.println(filter.isEmpty());
23 | filter.clear();
24 | System.out.println(filter.isEmpty());
25 | System.out.println(filter.contains("filter"));
26 |
27 | /**
28 | Test results:
29 | true
30 | false
31 | true
32 | true
33 | false
34 | false
35 | 3
36 | false
37 | true
38 | false
39 | */
40 |
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/src/test/java/com/github/wxisme/bloomfilter/bitset/RedisBitSetTest.java:
--------------------------------------------------------------------------------
1 | package com.github.wxisme.bloomfilter.bitset;
2 |
3 | import com.github.wxisme.bloomfilter.common.BloomFilter;
4 | import redis.clients.jedis.HostAndPort;
5 | import redis.clients.jedis.Jedis;
6 | import redis.clients.jedis.JedisCluster;
7 | import redis.clients.jedis.JedisPool;
8 |
9 | import java.util.HashSet;
10 | import java.util.Set;
11 |
12 | public class RedisBitSetTest {
13 |
14 |
15 | public static void main(String[] args) {
16 |
17 | //Don't forget auth password, you better use the configured redis client connection.
18 | //It should be noted that bloomfilter is not responsible for closing and returning redis connection resources.
19 |
20 | //(falsePositiveProbability, expectedNumberOfElements)
21 | BloomFilter filter = new BloomFilter(0.0001, 10000);
22 | Jedis jedis = new Jedis("127.0.0.1", 6379);
23 | jedis.auth("1234");
24 | filter.bind(new RedisBitSet(jedis, "bloomfilter:key:name"));
25 |
26 | //if you have a redis cluster
27 | //Set nodes = new HashSet<>();
28 | //nodes.add(new HostAndPort("127.0.0.1", 6379));
29 |
30 | //filter.bind(new RedisBitSet(new JedisCluster(nodes), "bloomfilter:key:name"));
31 |
32 | //you can also use jedispool
33 | //JedisPool jedisPool = new JedisPool("127.0.0.1", 6379);
34 | //Jedis jedis = jedisPool.getResource();
35 | //filter.bind(new RedisBitSet(jedis, "bloomfilter:key:name"));
36 |
37 | filter.add("filter");
38 | System.out.println(filter.contains("filter"));
39 | System.out.println(filter.contains("bloom"));
40 | filter.add("bitset");
41 | filter.add("redis");
42 | System.out.println(filter.contains("bitset"));
43 | System.out.println(filter.contains("redis"));
44 | System.out.println(filter.contains("mysql"));
45 | System.out.println(filter.contains("linux"));
46 | System.out.println(filter.count());
47 | System.out.println(filter.isEmpty());
48 | filter.clear();
49 | System.out.println(filter.isEmpty());
50 | System.out.println(filter.contains("filter"));
51 |
52 | /**
53 | Test results:
54 | true
55 | false
56 | true
57 | true
58 | false
59 | false
60 | 3
61 | false
62 | true
63 | false
64 | */
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/test/java/com/github/wxisme/bloomfilter/bitset/YourBitSet.java:
--------------------------------------------------------------------------------
1 | package com.github.wxisme.bloomfilter.bitset;
2 |
3 | import java.util.Arrays;
4 |
5 | public class YourBitSet implements BaseBitSet {
6 |
7 | private int[] data;//boolean array
8 |
9 | public YourBitSet(int size) {
10 | data = new int[size];
11 | }
12 |
13 | @Override
14 | public void set(int bitIndex) {
15 | data[bitIndex] = 1;
16 | }
17 |
18 | @Override
19 | public void set(int bitIndex, boolean value) {
20 | if (value)
21 | data[bitIndex] = 1;
22 | else data[bitIndex] = 0;
23 | }
24 |
25 | @Override
26 | public boolean get(int bitIndex) {
27 | return data[bitIndex] == 1;
28 | }
29 |
30 | @Override
31 | public void clear(int bitIndex) {
32 | data[bitIndex] = 0;
33 | }
34 |
35 | @Override
36 | public void clear() {
37 | Arrays.fill(data, 0);
38 | }
39 |
40 | @Override
41 | public long size() {
42 | long size = 0;
43 | for (int d : data)
44 | if (d == 1)
45 | size++;
46 | return size;
47 | }
48 |
49 | @Override
50 | public boolean isEmpty() {
51 | return size() <= 0;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/test/java/com/github/wxisme/bloomfilter/bitset/YourBitSetTest.java:
--------------------------------------------------------------------------------
1 | package com.github.wxisme.bloomfilter.bitset;
2 |
3 | import com.github.wxisme.bloomfilter.common.BloomFilter;
4 |
5 | public class YourBitSetTest {
6 |
7 | public static void main(String[] args) {
8 | //(falsePositiveProbability, expectedNumberOfElements)
9 | BloomFilter filter = new BloomFilter(0.0001, 10000);
10 | filter.bind(new YourBitSet(1000000));
11 |
12 | filter.add("filter");
13 | System.out.println(filter.contains("filter"));
14 | System.out.println(filter.contains("bloom"));
15 | filter.add("bitset");
16 | filter.add("redis");
17 | System.out.println(filter.contains("bitset"));
18 | System.out.println(filter.contains("redis"));
19 | System.out.println(filter.contains("mysql"));
20 | System.out.println(filter.contains("linux"));
21 | System.out.println(filter.count());
22 | System.out.println(filter.isEmpty());
23 | filter.clear();
24 | System.out.println(filter.isEmpty());
25 | System.out.println(filter.contains("filter"));
26 |
27 | /**
28 | Test results:
29 | true
30 | false
31 | true
32 | true
33 | false
34 | false
35 | 3
36 | false
37 | true
38 | false
39 | */
40 |
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------