Redis bitmaps

Introduction to Redis bitmaps

Bitmaps are not an actual data type, but a set of bit-oriented operations defined on the String type which is treated like a bit vector. Since strings are binary safe blobs and their maximum length is 512 MB, they are suitable to set up to 2^32 different bits.

You can perform bitwise operations on one or more strings. Some examples of bitmap use cases include:

  • Efficient set representations for cases where the members of a set correspond to the integers 0-N.
  • Object permissions, where each bit represents a particular permission, similar to the way that file systems store permissions.

Example

Suppose you have 1000 cyclists racing through the country-side, with sensors on their bikes labeled 0-999. You want to quickly determine whether a given sensor has pinged a tracking server within the hour to check in on a rider.

You can represent this scenario using a bitmap whose key references the current hour.

  • Rider 123 pings the server on January 1, 2024 within the 00:00 hour. You can then confirm that rider 123 pinged the server. You can also check to see if rider 456 has pinged the server for that same hour.
Foundational: Set and get individual bits using SETBIT and GETBIT to track binary states
> SETBIT pings:2024-01-01-00:00 123 1
(integer) 0
> GETBIT pings:2024-01-01-00:00 123
1
> GETBIT pings:2024-01-01-00:00 456
0
res1 = r.setbit("pings:2024-01-01-00:00", 123, 1)
print(res1)  # >>> 0

res2 = r.getbit("pings:2024-01-01-00:00", 123)
print(res2)  # >>> 1

res3 = r.getbit("pings:2024-01-01-00:00", 456)
print(res3)  # >>> 0
const res1 = await client.setBit("pings:2024-01-01-00:00", 123, 1)
console.log(res1)  // >>> 0

const res2 = await client.getBit("pings:2024-01-01-00:00", 123)
console.log(res2)  // >>> 1

const res3 = await client.getBit("pings:2024-01-01-00:00", 456)
console.log(res3)  // >>> 0
        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
            CompletableFuture<Void> ping = async.setbit("pings:2024-01-01-00:00", 123, 1).thenCompose(res1 -> {
                System.out.println(res1); // >>> 0
                return async.getbit("pings:2024-01-01-00:00", 123);
            }).thenCompose(res2 -> {
                System.out.println(res2); // >>> 1
                return async.getbit("pings:2024-01-01-00:00", 456);
            }).thenAccept(res3 -> {
                System.out.println(res3); // >>> 0
            }).toCompletableFuture();
            Mono<Void> ping = reactive.setbit("pings:2024-01-01-00:00", 123, 1).doOnNext(res1 -> {
                System.out.println(res1); // >>> 0
            }).flatMap(v -> reactive.getbit("pings:2024-01-01-00:00", 123)).doOnNext(res2 -> {
                System.out.println(res2); // >>> 1
            }).flatMap(v -> reactive.getbit("pings:2024-01-01-00:00", 456)).doOnNext(res3 -> {
                System.out.println(res3); // >>> 0
            }).then();
	res1, err := rdb.SetBit(ctx, "pings:2024-01-01-00:00", 123, 1).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res1) // >>> 0

	res2, err := rdb.GetBit(ctx, "pings:2024-01-01-00:00", 123).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res2) // >>> 1

	res3, err := rdb.GetBit(ctx, "pings:2024-01-01-00:00", 456).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res3) // >>> 0
        bool res1 = db.StringSetBit("pings:2024-01-01-00:00", 123, true);
        Console.WriteLine(res1);    // >>> 0

        bool res2 = db.StringGetBit("pings:2024-01-01-00:00", 123);
        Console.WriteLine(res2);    // >>> True

        bool res3 = db.StringGetBit("pings:2024-01-01-00:00", 456);
        Console.WriteLine(res3);    // >>> False
        $res1 = $r->setbit('pings:2024-01-01-00:00', 123, 1);
        echo $res1 . PHP_EOL;
        // >>> 0

        $res2 = $r->getbit('pings:2024-01-01-00:00', 123);
        echo $res2 . PHP_EOL;
        // >>> 1

        $res3 = $r->getbit('pings:2024-01-01-00:00', 456);
        echo $res3 . PHP_EOL;
        // >>> 0

Bit Operations

Bit operations are divided into two groups: constant-time single bit operations, like setting a bit to 1 or 0, or getting its value, and operations on groups of bits, for example counting the number of set bits in a given range of bits (e.g., population counting).

One of the biggest advantages of bitmaps is that they often provide extreme space savings when storing information. For example in a system where different users are represented by incremental user IDs, it is possible to remember a single bit information (for example, knowing whether a user wants to receive a newsletter) of 4 billion users using just 512 MB of memory.

The SETBIT command takes as its first argument the bit number, and as its second argument the value to set the bit to, which is 1 or 0. The command automatically enlarges the string if the addressed bit is outside the current string length.

GETBIT just returns the value of the bit at the specified index. Out of range bits (addressing a bit that is outside the length of the string stored into the target key) are always considered to be zero.

There are three commands operating on group of bits:

  1. BITOP performs bit-wise operations between different strings. The provided operators are AND, OR, XOR, NOT, DIFF, DIFF1, ANDOR, and ONE.
  2. BITCOUNT performs population counting, reporting the number of bits set to 1.
  3. BITPOS finds the first bit having the specified value of 0 or 1.

Both BITPOS and BITCOUNT are able to operate with byte ranges of the string, instead of running for the whole length of the string. We can trivially see the number of bits that have been set in a bitmap.

Bit counting: Use BITCOUNT to count the number of set bits in a bitmap when you need to get population counts
> BITCOUNT pings:2024-01-01-00:00
(integer) 1
r.setbit("pings:2024-01-01-00:00", 123, 1)
res4 = r.bitcount("pings:2024-01-01-00:00")
print(res4)  # >>> 1
await client.setBit("pings:2024-01-01-00:00", 123, 1)
const res4 = await client.bitCount("pings:2024-01-01-00:00")
console.log(res4)  // >>> 1
        long res4 = jedis.bitcount("pings:2024-01-01-00:00");
        System.out.println(res4); // >>> 1
            CompletableFuture<Void> bitcount = async.bitcount("pings:2024-01-01-00:00").thenAccept(res4 -> {
                System.out.println(res4); // >>> 1
            }).toCompletableFuture();
            Mono<Void> bitcount = reactive.bitcount("pings:2024-01-01-00:00").doOnNext(res4 -> {
                System.out.println(res4); // >>> 1
            }).then();
	res4, err := rdb.BitCount(ctx, "pings:2024-01-01-00:00",
		&redis.BitCount{
			Start: 0,
			End:   456,
		}).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res4) // >>> 1
        bool res4 = db.StringSetBit("pings:2024-01-01-00:00", 123, true);
        long res5 = db.StringBitCount("pings:2024-01-01-00:00");
        Console.WriteLine(res5);    // >>> 1
        // Ensure the bit is set
        $r->setbit('pings:2024-01-01-00:00', 123, 1);
        $res4 = $r->bitcount('pings:2024-01-01-00:00');
        echo $res4 . PHP_EOL;
        // >>> 1

For example imagine you want to know the longest streak of daily visits of your web site users. You start counting days starting from zero, that is the day you made your web site public, and set a bit with SETBIT every time the user visits the web site. As a bit index you simply take the current unix time, subtract the initial offset, and divide by the number of seconds in a day (normally, 3600*24).

This way for each user you have a small string containing the visit information for each day. With BITCOUNT it is possible to easily get the number of days a given user visited the web site, while with a few BITPOS calls, or simply fetching and analyzing the bitmap client-side, it is possible to easily compute the longest streak.

Bitwise operations

The 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).

Bitop setup

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

Setup for bitwise operations: Create multiple bitmaps using SETBIT to prepare for demonstrating bitwise operations
> 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
r.setbit("A", 0, 1)
r.setbit("A", 1, 1)
r.setbit("A", 3, 1)
r.setbit("A", 4, 1)

res5 = r.get("A")
print("{:08b}".format(int.from_bytes(res5, "big")))
# >>> 11011000

r.setbit("B", 3, 1)
r.setbit("B", 4, 1)
r.setbit("B", 7, 1)

res6 = r.get("B")
print("{:08b}".format(int.from_bytes(res6, "big")))
# >>> 00011001

r.setbit("C", 1, 1)
r.setbit("C", 2, 1)
r.setbit("C", 4, 1)
r.setbit("C", 5, 1)

res7 = r.get("C")
print("{:08b}".format(int.from_bytes(res7, "big")))
# >>> 01101100
await client.setBit("A", 0, 1)
await client.setBit("A", 1, 1)
await client.setBit("A", 3, 1)
await client.setBit("A", 4, 1)

const res5 = await client.get("A")
console.log(res5.readUInt8(0).toString(2).padStart(8, '0'))
// >>> 11011000

await client.setBit("B", 3, 1)
await client.setBit("B", 4, 1)
await client.setBit("B", 7, 1)

const res6 = await client.get("B")
console.log(res6.readUInt8(0).toString(2).padStart(8, '0'))
// >>> 00011001

await client.setBit("C", 1, 1)
await client.setBit("C", 2, 1)
await client.setBit("C", 4, 1)
await client.setBit("C", 5, 1)

const res7 = await client.get("C")
console.log(res7.readUInt8(0).toString(2).padStart(8, '0'))
// >>> 01101100
        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
            CompletableFuture<Void> setup = async.setbit("A", 0, 1).thenCompose(v -> async.setbit("A", 1, 1))
                    .thenCompose(v -> async.setbit("A", 3, 1)).thenCompose(v -> async.setbit("A", 4, 1))
                    .thenCompose(v -> async.setbit("B", 3, 1)).thenCompose(v -> async.setbit("B", 4, 1))
                    .thenCompose(v -> async.setbit("B", 7, 1)).thenCompose(v -> async.setbit("C", 1, 1))
                    .thenCompose(v -> async.setbit("C", 2, 1)).thenCompose(v -> async.setbit("C", 4, 1))
                    .thenCompose(v -> async.setbit("C", 5, 1)).thenCompose(v -> asyncBytes.get("A".getBytes()))
                    .thenApply(res -> {
                        byte b = (res != null && res.length > 0) ? res[0] : 0;
                        String bitsA = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
                        System.out.println(bitsA); // >>> 11011000
                        return bitsA;
                    }).thenCompose(v -> asyncBytes.get("B".getBytes())).thenApply(res -> {
                        byte b = (res != null && res.length > 0) ? res[0] : 0;
                        String bitsB = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
                        System.out.println(bitsB); // >>> 00011001
                        return bitsB;
                    })
                    // Print C
                    .thenCompose(v -> asyncBytes.get("C".getBytes())).thenAccept(res -> {
                        byte b = (res != null && res.length > 0) ? res[0] : 0;
                        String bitsC = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
                        System.out.println(bitsC); // >>> 01101100
                    }).toCompletableFuture();
            Mono<Void> setup = reactive.setbit("A", 0, 1).then(reactive.setbit("A", 1, 1)).then(reactive.setbit("A", 3, 1))
                    .then(reactive.setbit("A", 4, 1)).then(reactive.setbit("B", 3, 1)).then(reactive.setbit("B", 4, 1))
                    .then(reactive.setbit("B", 7, 1)).then(reactive.setbit("C", 1, 1)).then(reactive.setbit("C", 2, 1))
                    .then(reactive.setbit("C", 4, 1)).then(reactive.setbit("C", 5, 1)).then(reactiveBytes.get("A".getBytes()))
                    .doOnNext(res -> {
                        byte b = (res != null && res.length > 0) ? res[0] : 0;
                        String bitsA = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
                        System.out.println(bitsA); // >>> 11011000
                    }).then(reactiveBytes.get("B".getBytes())).doOnNext(res -> {
                        byte b = (res != null && res.length > 0) ? res[0] : 0;
                        String bitsB = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
                        System.out.println(bitsB); // >>> 00011001
                    }).then(reactiveBytes.get("C".getBytes())).doOnNext(res -> {
                        byte b = (res != null && res.length > 0) ? res[0] : 0;
                        String bitsC = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
                        System.out.println(bitsC); // >>> 01101100
                    }).then();
	rdb.SetBit(ctx, "A", 0, 1)
	rdb.SetBit(ctx, "A", 1, 1)
	rdb.SetBit(ctx, "A", 3, 1)
	rdb.SetBit(ctx, "A", 4, 1)
	ba, _ := rdb.Get(ctx, "A").Bytes()
	fmt.Printf("%08b\n", ba[0])
	// >>> 11011000

	rdb.SetBit(ctx, "B", 3, 1)
	rdb.SetBit(ctx, "B", 4, 1)
	rdb.SetBit(ctx, "B", 7, 1)
	bb, _ := rdb.Get(ctx, "B").Bytes()
	fmt.Printf("%08b\n", bb[0])
	// >>> 00011001

	rdb.SetBit(ctx, "C", 1, 1)
	rdb.SetBit(ctx, "C", 2, 1)
	rdb.SetBit(ctx, "C", 4, 1)
	rdb.SetBit(ctx, "C", 5, 1)
	bc, _ := rdb.Get(ctx, "C").Bytes()
	fmt.Printf("%08b\n", bc[0])
	// >>> 01101100

        // Bitwise operations are not supported in NRedisStack.

        $r->setbit('A', 0, 1);
        $r->setbit('A', 1, 1);
        $r->setbit('A', 3, 1);
        $r->setbit('A', 4, 1);

        $res5 = $r->get('A');
        echo str_pad(decbin(ord($res5)), 8, '0', STR_PAD_LEFT) . PHP_EOL;
        // >>> 11011000

        $r->setbit('B', 3, 1);
        $r->setbit('B', 4, 1);
        $r->setbit('B', 7, 1);

        $res6 = $r->get('B');
        echo str_pad(decbin(ord($res6)), 8, '0', STR_PAD_LEFT) . PHP_EOL;
        // >>> 00011001

        $r->setbit('C', 1, 1);
        $r->setbit('C', 2, 1);
        $r->setbit('C', 4, 1);
        $r->setbit('C', 5, 1);

        $res7 = $r->get('C');
        echo str_pad(decbin(ord($res7)), 8, '0', STR_PAD_LEFT) . PHP_EOL;
        // >>> 01101100

AND

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

Bitop AND
AND operation: Use BITOP AND to find bits set in all source bitmaps when you need to find common bits across multiple sets
> BITOP AND R A B C
(integer) 1
> GET R
"\b"
# ASCII "\b" (backspace) = hex 0x08 = 0b00001000
r.bitop("AND", "R", "A", "B", "C")
res8 = r.get("R")
print("{:08b}".format(int.from_bytes(res8, "big")))
# >>> 00001000
await client.bitOp("AND", "R", ["A", "B", "C"])
const res8 = await client.get("R")
console.log(res8.readUInt8(0).toString(2).padStart(8, '0'))
// >>> 00001000
        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
            CompletableFuture<Void> andOp = async.bitopAnd("R", "A", "B", "C")
                    .thenCompose(len -> asyncBytes.get("R".getBytes())).thenAccept(res -> {
                        byte b = (res != null && res.length > 0) ? res[0] : 0;
                        String bits = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
                        System.out.println(bits); // >>> 00001000
                    }).toCompletableFuture();
            Mono<Void> andOp = reactive.bitopAnd("R", "A", "B", "C").then(reactiveBytes.get("R".getBytes())).doOnNext(res -> {
                byte b = (res != null && res.length > 0) ? res[0] : 0;
                String bits = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
                System.out.println(bits); // >>> 00001000
            }).then();
	rdb.BitOpAnd(ctx, "R", "A", "B", "C")
	br, _ := rdb.Get(ctx, "R").Bytes()
	fmt.Printf("%08b\n", br[0])
	// >>> 00001000

        // Bitwise operations are not supported in NRedisStack.

        $r->bitop('AND', 'R', 'A', 'B', 'C');
        $res8 = $r->get('R');
        echo str_pad(decbin(ord($res8)), 8, '0', STR_PAD_LEFT) . PHP_EOL;
        // >>> 00001000

OR

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

Bitop OR
OR operation: Use BITOP OR to find bits set in at least one source bitmap when you need to combine multiple sets
> BITOP OR R A B C
(integer) 1
> GET R
"\xfd"
# Hex value: 0xfd = 0b11111101
r.bitop("OR", "R", "A", "B", "C")
res9 = r.get("R")
print("{:08b}".format(int.from_bytes(res9, "big")))
# >>> 11111101
await client.bitOp("OR", "R", ["A", "B", "C"])
const res9 = await client.get("R")
console.log(res9.readUInt8(0).toString(2).padStart(8, '0'))
// >>> 11111101
        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
            CompletableFuture<Void> orOp = async.bitopOr("R", "A", "B", "C").thenCompose(len -> asyncBytes.get("R".getBytes()))
                    .thenAccept(res -> {
                        byte b = (res != null && res.length > 0) ? res[0] : 0;
                        String bits = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
                        System.out.println(bits); // >>> 11111101
                    }).toCompletableFuture();
            Mono<Void> orOp = reactive.bitopOr("R", "A", "B", "C").then(reactiveBytes.get("R".getBytes())).doOnNext(res -> {
                byte b = (res != null && res.length > 0) ? res[0] : 0;
                String bits = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
                System.out.println(bits); // >>> 11111101
            }).then();
	rdb.BitOpOr(ctx, "R", "A", "B", "C")
	br, _ = rdb.Get(ctx, "R").Bytes()
	fmt.Printf("%08b\n", br[0])
	// >>> 11111101

        // Bitwise operations are not supported in NRedisStack.

        $r->bitop('OR', 'R', 'A', 'B', 'C');
        $res9 = $r->get('R');
        echo str_pad(decbin(ord($res9)), 8, '0', STR_PAD_LEFT) . PHP_EOL;
        // >>> 11111101

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.

Bitop XOR
XOR operation: Use BITOP XOR to find bits that differ between bitmaps when you need to identify differences
> BITOP XOR R A B
(integer) 1
> GET R
"\xc1"
# Hex value: 0xc1 = 0b11000001
r.bitop("XOR", "R", "A", "B")
res10 = r.get("R")
print("{:08b}".format(int.from_bytes(res10, "big")))
# >>> 11000001
await client.bitOp("XOR", "R", ["A", "B"]) // XOR uses two keys here
const res10 = await client.get("R")
console.log(res10.readUInt8(0).toString(2).padStart(8, '0'))
// >>> 11000001
        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
            CompletableFuture<Void> xorOp = async.bitopXor("R", "A", "B").thenCompose(len -> asyncBytes.get("R".getBytes()))
                    .thenAccept(res -> {
                        byte b = (res != null && res.length > 0) ? res[0] : 0;
                        String bits = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
                        System.out.println(bits); // >>> 11000001
                    }).toCompletableFuture();
            Mono<Void> xorOp = reactive.bitopXor("R", "A", "B").then(reactiveBytes.get("R".getBytes())).doOnNext(res -> {
                byte b = (res != null && res.length > 0) ? res[0] : 0;
                String bits = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
                System.out.println(bits); // >>> 11000001
            }).then();
	rdb.BitOpXor(ctx, "R", "A", "B")
	br, _ = rdb.Get(ctx, "R").Bytes()
	fmt.Printf("%08b\n", br[0])
	// >>> 11000001

        // Bitwise operations are not supported in NRedisStack.

        $r->bitop('XOR', 'R', 'A', 'B');
        $res10 = $r->get('R');
        echo str_pad(decbin(ord($res10)), 8, '0', STR_PAD_LEFT) . PHP_EOL;
        // >>> 11000001

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).

Bitop NOT
NOT operation: Use BITOP NOT to invert all bits in a bitmap when you need to negate a set
> BITOP NOT R A
(integer) 1
> GET R
"'"
# ASCII "'" (single quote) = hex 0x27 = 0b00100111
r.bitop("NOT", "R", "A")
res11 = r.get("R")
print("{:08b}".format(int.from_bytes(res11, "big")))
# >>> 00100111
await client.bitOp("NOT", "R", "A")
const res11 = await client.get("R")
console.log(res11.readUInt8(0).toString(2).padStart(8, '0'))
// >>> 00100111
        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
            CompletableFuture<Void> notOp = async.bitopNot("R", "A").thenCompose(len -> asyncBytes.get("R".getBytes()))
                    .thenAccept(res -> {
                        byte b = (res != null && res.length > 0) ? res[0] : 0;
                        String bits = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
                        System.out.println(bits); // >>> 00100111
                    }).toCompletableFuture();
            Mono<Void> notOp = reactive.bitopNot("R", "A").then(reactiveBytes.get("R".getBytes())).doOnNext(res -> {
                byte b = (res != null && res.length > 0) ? res[0] : 0;
                String bits = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
                System.out.println(bits); // >>> 00100111
            }).then();
	rdb.BitOpNot(ctx, "R", "A")
	br, _ = rdb.Get(ctx, "R").Bytes()
	fmt.Printf("%08b\n", br[0])
	// >>> 00100111

        // Bitwise operations are not supported in NRedisStack.

        $r->bitop('NOT', 'R', 'A');
        $res11 = $r->get('R');
        echo str_pad(decbin(ord($res11)), 8, '0', STR_PAD_LEFT) . PHP_EOL;
        // >>> 00100111

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.

Bitop DIFF
DIFF operation: Use BITOP DIFF to find bits set in the first bitmap but not in others when you need set difference
> BITOP DIFF R A B C
(integer) 1
> GET R
"\x80"
# Hex value: 0x80 = 0b10000000
r.bitop("DIFF", "R", "A", "B", "C")
res12 = r.get("R")
print("{:08b}".format(int.from_bytes(res12, "big")))
# >>> 10000000
await client.bitOp("DIFF", "R", ["A", "B", "C"])
const res12 = await client.get("R")
console.log(res12.readUInt8(0).toString(2).padStart(8, '0'))
// >>> 10000000
        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
            CompletableFuture<Void> diffOp = async.bitopDiff("R", "A", "B", "C")
                    .thenCompose(len -> asyncBytes.get("R".getBytes())).thenAccept(res -> {
                        byte b = (res != null && res.length > 0) ? res[0] : 0;
                        String bits = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
                        System.out.println(bits); // >>> 10000000
                    }).toCompletableFuture();
            Mono<Void> diffOp = reactive.bitopDiff("R", "A", "B", "C").then(reactiveBytes.get("R".getBytes())).doOnNext(res -> {
                byte b = (res != null && res.length > 0) ? res[0] : 0;
                String bits = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
                System.out.println(bits); // >>> 10000000
            }).then();
	rdb.BitOpDiff(ctx, "R", "A", "B", "C")
	br, _ = rdb.Get(ctx, "R").Bytes()
	fmt.Printf("%08b\n", br[0])
	// >>> 10000000

        // Bitwise operations are not supported in NRedisStack.

        $r->bitop('DIFF', 'R', 'A', 'B', 'C');
        $res12 = $r->get('R');
        echo str_pad(decbin(ord($res12)), 8, '0', STR_PAD_LEFT) . PHP_EOL;
        // >>> 10000000

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.

Bitop DIFF1
DIFF1 operation: Use BITOP DIFF1 to find bits not in the first bitmap but in at least one other when you need inverse difference
> BITOP DIFF1 R A B C
(integer) 1
> GET R
"%"
# ASCII "%" (percent) = hex 0x25 = 0b00100101
r.bitop("DIFF1", "R", "A", "B", "C")
res13 = r.get("R")
print("{:08b}".format(int.from_bytes(res13, "big")))
# >>> 00100101
await client.bitOp("DIFF1", "R", ["A", "B", "C"])
const res13 = await client.get("R")
console.log(res13.readUInt8(0).toString(2).padStart(8, '0'))
// >>> 00100101
        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
            CompletableFuture<Void> diff1Op = async.bitopDiff1("R", "A", "B", "C")
                    .thenCompose(len -> asyncBytes.get("R".getBytes())).thenAccept(res -> {
                        byte b = (res != null && res.length > 0) ? res[0] : 0;
                        String bits = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
                        System.out.println(bits); // >>> 00100101
                    }).toCompletableFuture();
            Mono<Void> diff1Op = reactive.bitopDiff1("R", "A", "B", "C").then(reactiveBytes.get("R".getBytes()))
                    .doOnNext(res -> {
                        byte b = (res != null && res.length > 0) ? res[0] : 0;
                        String bits = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
                        System.out.println(bits); // >>> 00100101
                    }).then();
	rdb.BitOpDiff1(ctx, "R", "A", "B", "C")
	br, _ = rdb.Get(ctx, "R").Bytes()
	fmt.Printf("%08b\n", br[0])
	// >>> 00100101

        // Bitwise operations are not supported in NRedisStack.

        $r->bitop('DIFF1', 'R', 'A', 'B', 'C');
        $res13 = $r->get('R');
        echo str_pad(decbin(ord($res13)), 8, '0', STR_PAD_LEFT) . PHP_EOL;
        // >>> 00100101

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.

Bitop ANDOR
ANDOR operation: Use BITOP ANDOR to find bits in the first bitmap and at least one other when you need intersection with union
> BITOP ANDOR R A B C
(integer) 1
> GET R
"X"
# ASCII "X" = hex 0x58 = 0b01011000
r.bitop("ANDOR", "R", "A", "B", "C")
res14 = r.get("R")
print("{:08b}".format(int.from_bytes(res14, "big")))
# >>> 01011000
await client.bitOp("ANDOR", "R", ["A", "B", "C"])
const res14 = await client.get("R")
console.log(res14.readUInt8(0).toString(2).padStart(8, '0'))
// >>> 01011000
        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
            CompletableFuture<Void> andorOp = async.bitopAndor("R", "A", "B", "C")
                    .thenCompose(len -> asyncBytes.get("R".getBytes())).thenAccept(res -> {
                        byte b = (res != null && res.length > 0) ? res[0] : 0;
                        String bits = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
                        System.out.println(bits); // >>> 01011000
                    }).toCompletableFuture();
            Mono<Void> andorOp = reactive.bitopAndor("R", "A", "B", "C").then(reactiveBytes.get("R".getBytes()))
                    .doOnNext(res -> {
                        byte b = (res != null && res.length > 0) ? res[0] : 0;
                        String bits = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
                        System.out.println(bits); // >>> 01011000
                    }).then();
	rdb.BitOpAndOr(ctx, "R", "A", "B", "C")
	br, _ = rdb.Get(ctx, "R").Bytes()
	fmt.Printf("%08b\n", br[0])
	// >>> 01011000

        // Bitwise operations are not supported in NRedisStack.

        $r->bitop('ANDOR', 'R', 'A', 'B', 'C');
        $res14 = $r->get('R');
        echo str_pad(decbin(ord($res14)), 8, '0', STR_PAD_LEFT) . PHP_EOL;
        // >>> 01011000

ONE

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

Bitop ONE
ONE operation: Use BITOP ONE to find bits set in exactly one bitmap when you need exclusive membership
> BITOP ONE R A B C
(integer) 1
> GET R
"\xa5"
# Hex value: 0xa5 = 0b10100101
r.bitop("ONE", "R", "A", "B", "C")
res15 = r.get("R")
print("{:08b}".format(int.from_bytes(res15, "big")))
# >>> 10100101
await client.bitOp("ONE", "R", ["A", "B", "C"])
const res15 = await client.get("R")
console.log(res15.readUInt8(0).toString(2).padStart(8, '0'))
// >>> 10100101
        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
            CompletableFuture<Void> oneOp = async.bitopOne("R", "A", "B", "C")
                    .thenCompose(len -> asyncBytes.get("R".getBytes())).thenAccept(res -> {
                        byte b = (res != null && res.length > 0) ? res[0] : 0;
                        String bits = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
                        System.out.println(bits); // >>> 10100101
                    }).toCompletableFuture();
            Mono<Void> oneOp = reactive.bitopOne("R", "A", "B", "C").then(reactiveBytes.get("R".getBytes())).doOnNext(res -> {
                byte b = (res != null && res.length > 0) ? res[0] : 0;
                String bits = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
                System.out.println(bits); // >>> 10100101
            }).then();
	rdb.BitOpOne(ctx, "R", "A", "B", "C")
	br, _ = rdb.Get(ctx, "R").Bytes()
	fmt.Printf("%08b\n", br[0])
	// >>> 10100101

        // Bitwise operations are not supported in NRedisStack.

        $r->bitop('ONE', 'R', 'A', 'B', 'C');
        $res15 = $r->get('R');
        echo str_pad(decbin(ord($res15)), 8, '0', STR_PAD_LEFT) . PHP_EOL;
        // >>> 10100101

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 and GETBIT are O(1). BITOP is O(n), where n is the length of the longest string in the comparison.

Learn more

RATE THIS PAGE
Back to top ↑