Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
169 changes: 167 additions & 2 deletions content/develop/data-types/bitmaps.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,15 +102,180 @@ the number of days a given user visited the web site, while with
a few [`BITPOS`]({{< relref "/commands/bitpos" >}}) calls, or simply fetching and analyzing the bitmap client-side,
it is possible to easily compute the longest streak.

### Bitwise operations

The [`BITOP`]({{< relref "/commands/bitop" >}}) command performs bitwise
operations over two or more source keys, storing the result in a destination key.

The examples below show the available operations using three keys: `A` (with bit pattern
`11011000`), `B` (`00011001`), and `C` (`01101100`).

{{< image filename="/images/dev/bitmap/BitopSetup.svg" alt="Bitop setup" >}}

Numbering the bits from left to right, starting at zero, the following `SETBIT` commands
will create these bitmaps:

{{< clients-example set="bitmap_tutorial" step="bitop_setup" >}}
> SETBIT A 0 1
(integer) 0
> SETBIT A 1 1
(integer) 0
> SETBIT A 3 1
(integer) 0
> SETBIT A 4 1
(integer) 0
> GET A
"\xd8"
# Hex value: 0xd8 = 0b11011000

> SETBIT B 3 1
(integer) 0
> SETBIT B 4 1
(integer) 0
> SETBIT B 7 1
(integer) 0
> GET B
"\x19"
# Hex value: 0x19 = 0b00011001

> SETBIT C 1 1
(integer) 0
> SETBIT C 2 1
(integer) 0
> SETBIT C 4 1
(integer) 0
> SETBIT C 5 1
(integer) 0
> GET C
"l"
# ASCII "l" = hex 0x6c = 0b01101100
{{< /clients-example >}}

#### `AND`

Set a bit in the destination key to 1 only if it is set in all the source keys.

{{< image filename="/images/dev/bitmap/BitopAnd.svg" alt="Bitop AND" >}}

{{< clients-example set="bitmap_tutorial" step="bitop_and" >}}
> BITOP AND R A B C
(integer) 1
> GET R
"\b"
# ASCII "\b" (backspace) = hex 0x08 = 0b00001000
{{< /clients-example >}}

#### `OR`
Set a bit in the destination key to 1 if it is set in at least one of the source keys.

{{< image filename="/images/dev/bitmap/BitopOr.svg" alt="Bitop OR" >}}

{{< clients-example set="bitmap_tutorial" step="bitop_or" >}}
> BITOP OR R A B C
(integer) 1
> GET R
"\xfd"
# Hex value: 0xfd = 0b11111101
{{< /clients-example >}}

#### `XOR`

For two source keys, set a bit in the destination key to 1 if the value of the bit is
different in the two keys. For three or more source keys, the result of XORing the first two
keys is then XORed with the next key, and so forth.

{{< image filename="/images/dev/bitmap/BitopXor.svg" alt="Bitop XOR" >}}

{{< clients-example set="bitmap_tutorial" step="bitop_xor" >}}
> BITOP XOR R A B
(integer) 1
> GET R
"\xc1"
# Hex value: 0xc1 = 0b11000001
{{< /clients-example >}}

#### `NOT`

Set a bit in the destination key to 1 if it is not set in the source key (this
is the only unary operator).

{{< image filename="/images/dev/bitmap/BitopNot.svg" alt="Bitop NOT" >}}

{{< clients-example set="bitmap_tutorial" step="bitop_not" >}}
> BITOP NOT R A
(integer) 1
> GET R
"'"
# ASCII "'" (single quote) = hex 0x27 = 0b00100111
{{< /clients-example >}}

#### `DIFF`

Set a bit in the destination key to 1 if it is set in the first source key, but not in any
of the other source keys.

{{< image filename="/images/dev/bitmap/BitopDiff.svg" alt="Bitop DIFF" >}}

{{< clients-example set="bitmap_tutorial" step="bitop_diff" >}}
> BITOP DIFF R A B C
(integer) 1
> GET R
"\x80"
# Hex value: 0x80 = 0b10000000
{{< /clients-example >}}

#### `DIFF1`

Set a bit in the destination key to 1 if it is not set in the first source key,
but set in at least one of the other source keys.

{{< image filename="/images/dev/bitmap/BitopDiff1.svg" alt="Bitop DIFF1" >}}

{{< clients-example set="bitmap_tutorial" step="bitop_diff1" >}}
> BITOP DIFF1 R A B C
(integer) 1
> GET R
"%"
# ASCII "%" (percent) = hex 0x25 = 0b00100101
{{< /clients-example >}}

#### `ANDOR`

Set a bit in the destination key to 1 if it is set in the first source key and also in at least one of the other source keys.

{{< image filename="/images/dev/bitmap/BitopAndOr.svg" alt="Bitop ANDOR" >}}

{{< clients-example set="bitmap_tutorial" step="bitop_andor" >}}
> BITOP ANDOR R A B C
(integer) 1
> GET R
"X"
# ASCII "X" = hex 0x58 = 0b01011000
{{< /clients-example >}}

#### `ONE`

Set a bit in the destination key to 1 if it is set in exactly one of the source keys.

{{< image filename="/images/dev/bitmap/BitopOne.svg" alt="Bitop ONE" >}}

{{< clients-example set="bitmap_tutorial" step="bitop_one" >}}
> BITOP ONE R A B C
(integer) 1
> GET R
"\xa5"
# Hex value: 0xa5 = 0b10100101
{{< /clients-example >}}

## Split bitmaps into multiple keys

Bitmaps are trivial to split into multiple keys, for example for
the sake of sharding the data set and because in general it is better to
avoid working with huge keys. To split a bitmap across different keys
instead of setting all the bits into a key, a trivial strategy is just
to store M bits per key and obtain the key name with `bit-number/M` and
the Nth bit to address inside the key with `bit-number MOD M`.



## Performance

[`SETBIT`]({{< relref "/commands/setbit" >}}) and [`GETBIT`]({{< relref "/commands/getbit" >}}) are O(1).
Expand Down
170 changes: 170 additions & 0 deletions local_examples/tmp/BitMapsExample.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
// EXAMPLE: bitmap_tutorial
package io.redis.examples;

import redis.clients.jedis.UnifiedJedis;
import redis.clients.jedis.args.BitOP;
// REMOVE_START
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
// REMOVE_END

public class BitMapsExample {

@Test
public void run() {
UnifiedJedis jedis = new UnifiedJedis("redis://localhost:6379");

// REMOVE_START
jedis.del("pings:2024-01-01-00:00");
// REMOVE_END

// STEP_START ping
boolean res1 = jedis.setbit("pings:2024-01-01-00:00", 123, true);
System.out.println(res1); // >>> false

boolean res2 = jedis.getbit("pings:2024-01-01-00:00", 123);
System.out.println(res2); // >>> true

boolean res3 = jedis.getbit("pings:2024-01-01-00:00", 456);
System.out.println(res3); // >>> false
// STEP_END

// REMOVE_START
assertFalse(res1);
assertTrue(res2);
assertFalse(res3);
// REMOVE_END

// STEP_START bitcount
long res4 = jedis.bitcount("pings:2024-01-01-00:00");
System.out.println(res4); // >>> 1
// STEP_END

// REMOVE_START
assertEquals(1, res4);
// REMOVE_END
// REMOVE_START
jedis.del("A", "B", "C", "R");
// REMOVE_END

// STEP_START bitop_setup
jedis.setbit("A", 0, true);
jedis.setbit("A", 1, true);
jedis.setbit("A", 3, true);
jedis.setbit("A", 4, true);

byte[] res5 = jedis.get("A".getBytes());
System.out.println(String.format("%8s", Integer.toBinaryString(res5[0] & 0xFF)).replace(' ', '0'));
// >>> 11011000

jedis.setbit("B", 3, true);
jedis.setbit("B", 4, true);
jedis.setbit("B", 7, true);

byte[] res6 = jedis.get("B".getBytes());
System.out.println(String.format("%8s", Integer.toBinaryString(res6[0] & 0xFF)).replace(' ', '0'));
// >>> 00011001

jedis.setbit("C", 1, true);
jedis.setbit("C", 2, true);
jedis.setbit("C", 4, true);
jedis.setbit("C", 5, true);

byte[] res7 = jedis.get("C".getBytes());
System.out.println(String.format("%8s", Integer.toBinaryString(res7[0] & 0xFF)).replace(' ', '0'));
// >>> 01101100
// STEP_END
// REMOVE_START
assertEquals(0b11011000, res5[0] & 0xFF);
assertEquals(0b00011001, res6[0] & 0xFF);
assertEquals(0b01101100, res7[0] & 0xFF);
// REMOVE_END

// STEP_START bitop_and
jedis.bitop(BitOP.AND, "R", "A", "B", "C");
byte[] res8 = jedis.get("R".getBytes());
System.out.println(String.format("%8s", Integer.toBinaryString(res8[0] & 0xFF)).replace(' ', '0'));
// >>> 00001000
// STEP_END
// REMOVE_START
assertEquals(0b00001000, res8[0] & 0xFF);
// REMOVE_END

// STEP_START bitop_or
jedis.bitop(BitOP.OR, "R", "A", "B", "C");
byte[] res9 = jedis.get("R".getBytes());
System.out.println(String.format("%8s", Integer.toBinaryString(res9[0] & 0xFF)).replace(' ', '0'));
// >>> 11111101
// STEP_END
// REMOVE_START
assertEquals(0b11111101, res9[0] & 0xFF);
// REMOVE_END

// STEP_START bitop_xor
jedis.bitop(BitOP.XOR, "R", "A", "B");
byte[] res10 = jedis.get("R".getBytes());
System.out.println(String.format("%8s", Integer.toBinaryString(res10[0] & 0xFF)).replace(' ', '0'));
// >>> 11000001
// STEP_END
// REMOVE_START
assertEquals(0b11000001, res10[0] & 0xFF);
// REMOVE_END

// STEP_START bitop_not
jedis.bitop(BitOP.NOT, "R", "A");
byte[] res11 = jedis.get("R".getBytes());
System.out.println(String.format("%8s", Integer.toBinaryString(res11[0] & 0xFF)).replace(' ', '0'));
// >>> 00100111
// STEP_END
// REMOVE_START
assertEquals(0b00100111, res11[0] & 0xFF);
// REMOVE_END

// STEP_START bitop_diff
jedis.bitop(BitOP.DIFF, "R", "A", "B", "C");
byte[] res12 = jedis.get("R".getBytes());
System.out.println(String.format("%8s", Integer.toBinaryString(res12[0] & 0xFF)).replace(' ', '0'));
// >>> 10000000
// STEP_END
// REMOVE_START
assertEquals(0b10000000, res12[0] & 0xFF);
// REMOVE_END

// STEP_START bitop_diff1
jedis.bitop(BitOP.DIFF1, "R", "A", "B", "C");
byte[] res13 = jedis.get("R".getBytes());
System.out.println(String.format("%8s", Integer.toBinaryString(res13[0] & 0xFF)).replace(' ', '0'));
// >>> 00100101
// STEP_END
// REMOVE_START
assertEquals(0b00100101, res13[0] & 0xFF);
// REMOVE_END

// STEP_START bitop_andor
jedis.bitop(BitOP.ANDOR, "R", "A", "B", "C");
byte[] res14 = jedis.get("R".getBytes());
System.out.println(String.format("%8s", Integer.toBinaryString(res14[0] & 0xFF)).replace(' ', '0'));
// >>> 01011000
// STEP_END
// REMOVE_START
assertEquals(0b01011000, res14[0] & 0xFF);
// REMOVE_END

// STEP_START bitop_one
jedis.bitop(BitOP.ONE, "R", "A", "B", "C");
byte[] res15 = jedis.get("R".getBytes());
System.out.println(String.format("%8s", Integer.toBinaryString(res15[0] & 0xFF)).replace(' ', '0'));
// >>> 10100101
// STEP_END
// REMOVE_START
assertEquals(0b10100101, res15[0] & 0xFF);
// REMOVE_END


// HIDE_START
jedis.close();
}
}
// HIDE_END
Loading