учиться никто конец территория , и июнь общий нежелание .
Связанные серии
вводить
Согласно официальному сайту, растровые изображения Redis Bitmaps не являются фактическим типом данных, а набором определенных в строковом типе.бит-ориентированные операции. Ограничение строк в Redis max составляет512MB, поэтому максимум можно установить в растровом изображении2^32разные биты(4,29 миллиарда). Наименьшая единица бита — бит, и значение каждого бита может быть только 0 или 1.
Расчет размера хранилища растровых изображений: (maxOffset / 8 / 1024 / 1024) МБ. где maxOffset — максимальное количество бит растрового изображения.
Основное использование
SETBIT key offset value
Установить битовое значение указанного значения ключа по смещению, смещение начинается с 0. Возвращаемое значение является исходным значением бита со смещением
# 通过位操作将 h 改成 i
127.0.0.1:6379> SET h h # 二进制为 01101000
OK
127.0.0.1:6379> SETBIT h 7 1 # 将最后一位改成1 => 01101001
(integer) 0
127.0.0.1:6379> GET h
"i"
GETBIT key offset
Получить битовое значение указанного значения ключа по смещению, смещение начинается с 0. Возвращает 0, если смещение выходит за пределы диапазона текущего растрового изображения.
127.0.0.1:6379> set i i # 二进制为 01101001
OK
127.0.0.1:6379> getbit i 0 # 第1位为0
(integer) 0
127.0.0.1:6379> getbit i 1 # 第2位为0
(integer) 1
127.0.0.1:6379> getbit i 7 # 第8位为0
(integer) 1
BITCOUNT key [start end]
Подсчитайте количество битов, установленных в 1 в указанном значении ключа. Статистический диапазон можно ограничить, указав параметры star и end.
Обратите внимание, что звездочка и конец здесь относятся не к индексу бита, а к индексу байта. Например, если начало равно 1, фактический соответствующий битовый индекс равен 8 (1 байт = 8 бит).
127.0.0.1:6379> set hi hi # 二进制为 0110100001101001
OK
127.0.0.1:6379> bitcount hi # 所有是1的位数:7个
(integer) 7
127.0.0.1:6379> bitcount hi 1 2 # 即统计 01101001 中1的位数
(integer) 4
BITPOS key bit [start] [end]
Считая первое вхождение бита 0 или 1, диапазон может быть указан по началу и концу, что также относится к нижнему индексу байта.
- Найти 1 в несуществующем ключе или пустой строке, вернуть -1
- В случае, когда все биты равны 1, а бит равен 0, вернуть первый пустой бит в крайнем правом углу строки.
127.0.0.1:6379> get nilkey # 不存在的key
(nil)
127.0.0.1:6379> bitpos nilkey 1 # 在不存在的key中查首次出现1的位
(integer) -1
127.0.0.1:6379> setbit nilkey 0 0 # 空字符串
(integer) 0
127.0.0.1:6379> get nilkey
"\x00"
127.0.0.1:6379> bitpos nilkey 1
(integer) -1
Ключ назначения операции BITOP [клавиша …]
Работает с одной или несколькими строками битов и сохраняет результат в ключе назначения. Когда длины определенной строки недостаточно, соответствующий бит заполняется 0
- И (логическое И): вернуть 1, если оба равны 1, иначе вернуть 0
127.0.0.1:6379> set a a # 二进制 01100001
OK
127.0.0.1:6379> set c c # 二进制 01100011
OK
127.0.0.1:6379> bitop and destkey a c # 与操作 01100001 -> a
(integer) 1
127.0.0.1:6379> get destkey
"a"
- ИЛИ (логическое ИЛИ): возвращает 1, если есть 1, в противном случае возвращает 0
127.0.0.1:6379> set a a # 二进制 01100001
OK
127.0.0.1:6379> set b b # 二进制 01100010
OK
127.0.0.1:6379> bitop or destkey a b # 或操作 01100011 -> c
(integer) 1
127.0.0.1:6379> get destkey
"c"
127.0.0.1:6379>
- XOR (исключающее логическое ИЛИ): возвращает 0, когда оба равны 0 или 1, в противном случае возвращает 1.
127.0.0.1:6379> set a a # 二进制 01100001
OK
127.0.0.1:6379> set z Z # 二进制 01011010 (大写的Z)
OK
127.0.0.1:6379> bitop xor destkey a z # 异或 00111011 -> ; 分号
(integer) 1
127.0.0.1:6379> get destkey
";"
- НЕ (логическое НЕ): Отрицательное значение, 1 становится 0, 0 становится 1.Можно передать только один ключ для работы.
01010101 -> 10101010
Сцена боя
Вот пример входа пользователя, чтобы объяснить, как применить его в реальном бою.
- Реализовать вход пользователя
- Подсчитайте все проверки сегодня
- Получить количество чекинов указанного пользователя за весь год
- Подсчитайте количество пользователей, которые непрерывно регистрировались в течение последних 7 дней.
- Подсчитайте количество пользователей, которые зарегистрировались в этом месяце
- Подсчитайте количество пользователей, которые вошли в систему за последние 7 дней.
Преимущества использования растровых изображений:
- Самая интуитивная точка для занятияменьше памяти, данные одного человека за год — 365 бит, 46 байт;
- Манипулировать несколькими строками с помощью побитовых операций,эффективный;
- Когда другие все еще используют базу данных для записи информации о регистрации, вы используете операции с битовыми картами,сильныйподняться сразу;
Вот демонстрация на основе SpringBoot:
- Ситуация ежедневной регистрации используется в качестве записи, а формат ключа
sign:{yyyyMMdd}
- ID пользователя как смещение
Вход пользователя
Используя идентификатор пользователя в качестве смещения, передайтеsetBit
Установите значение этой позиции на 1
Проверьте, вошел ли пользователь сегодня
Используя идентификатор пользователя в качестве смещения, передайтеgetBit
Запросить, равно ли значение в этой позиции 1
Подсчитайте все проверки сегодня
пройти черезbitCount
внедрить статистику
Подсчитайте количество чекинов указанных пользователей в течение года
Redis не обеспечивает операции суммирования нескольких двоичных битовых строк, нам нужно их посчитать самостоятельно. Идеи:
- Получите список ключей всех записей регистрации в этом году, т.е.
sign:2020
Ключ в начале можно передать командой Rediskeys sign:2020*
Получать - Просмотрите полученный список ключей и подсчитайте количество ключей, которые были подписаны.
Подсчитайте количество пользователей, которые непрерывно регистрировались в течение последних 7 дней.
- Записи о регистрации за последние 7 дней
逻辑与
Операция для создания записи о семи последовательных проверках - BitCount сгенерированные записи
Подсчитайте количество пользователей, которые вошли в систему за последние 7 дней.
Это то же самое, что и идея подсчета последовательных входов в систему в течение 7 дней, но она используется здесь.逻辑或
действовать
полный код
Service
@Service
public class RedisService {
private final StringRedisTemplate stringRedisTemplate;
public RedisService(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
/**
* 获取指定格式的key
*
* @param pattern 格式
* @return set
*/
public Set<String> getKeys(String pattern) {
return stringRedisTemplate.keys(pattern);
}
/**
* 设置指定位的值
*
* @param key 键
* @param offset 偏移量 0开始 对应bit的位置
* @param value true为1,false为0
* @return boolean
*/
public Boolean setBit(String key, long offset, boolean value) {
return stringRedisTemplate.opsForValue().setBit(key, offset, value);
}
/**
* 获取指定位的值
*
* @param key 键
* @param offset 偏移量 0开始
* @return boolean
*/
public Boolean getBit(String key, long offset) {
return stringRedisTemplate.opsForValue().getBit(key, offset);
}
/**
* 统计字符串被设置为1的bit数
*
* @param key 键
* @return long
*/
public Long bitCount(String key) {
return stringRedisTemplate.execute(
(RedisCallback<Long>) connection -> connection.bitCount(key.getBytes())
);
}
/**
* 统计字符串指定位上被设置为1的bit数
*
* @param key 键
* @param start 开始位置 注意对应byte的位置,是bit位置*8
* @param end 结束位置
* @return long
*/
public Long bitCount(String key, long start, long end) {
return stringRedisTemplate.execute(
(RedisCallback<Long>) connection -> connection.bitCount(key.getBytes(), start, end)
);
}
/**
* 不同字符串之间进行位操作
*
* @param op 操作类型:与、或、异或、否
* @param destKey 最终存放结构的键
* @param keys 要操作的键
* @return Long
*/
public Long bitOp(RedisStringCommands.BitOperation op, String destKey, Collection<String> keys) {
int size = keys.size();
byte[][] bytes = new byte[size][];
int index = 0;
for (String key : keys) {
bytes[index++] = key.getBytes();
}
return stringRedisTemplate.execute((RedisCallback<Long>) con -> con.bitOp(op, destKey.getBytes(), bytes));
}
/**
* 对符合指定格式的key值进行未操作
*
* @param op 操作类型:与、或、异或、否
* @param destKey 存放结果的键
* @param pattern key格式
* @return Long
*/
public Long bitOp(RedisStringCommands.BitOperation op, String destKey, String pattern) {
Set<String> keys = getKeys(pattern);
int size = keys.size();
if (size == 0) {
return 0L;
}
byte[][] bytes = new byte[size][];
int index = 0;
for (String key : keys) {
bytes[index++] = key.getBytes();
}
return stringRedisTemplate.execute((RedisCallback<Long>) con -> con.bitOp(op, destKey.getBytes(), bytes));
}
}
controller
@RestController
@RequestMapping("/redis/bit")
public class BitMapController {
private final DateTimeFormatter formatters = DateTimeFormatter.ofPattern("yyyyMMdd");
/**
* 定义签到前缀
* key格式为 sing:{yyyyMMdd}
*/
private static final String SIGN_PREFIX = "sign:";
/**
* 连续一周签到
*/
private static final String SIGN_ALL_WEEK_KEY = "signAllWeek";
/**
* 连续一个月签到
*/
private static final String SIGN_ALL_MONTH_KEY = "signAllMonth";
/**
* 一周内有签到过的
*/
private static final String SIGN_IN_WEEK_KEY = "signInWeek";
private final RedisService redisService;
public BitMapController(RedisService redisService) {
this.redisService = redisService;
}
/**
* 初始化本年今天之前的测试数据
*/
@GetMapping("/init")
public void initData() {
// 获取本年的日期列表
List<String> dateKeyList = new ArrayList<>();
LocalDate curDate = LocalDate.now();
LocalDate beginDate = LocalDate.parse("2020-01-01");
while (beginDate.isBefore(curDate)) {
dateKeyList.add(SIGN_PREFIX + beginDate.format(formatters));
beginDate = beginDate.plusDays(1);
}
// 是否签到
boolean isSign;
StringBuilder signInfo;
for (int i = 1; i < 6; i++) {
signInfo = new StringBuilder("用户【").append(i).append("】:");
for (String dateKey : dateKeyList) {
if (i == 1) {
// 用户1全部签到
isSign = true;
} else {
// 其他用户随机
isSign = Math.random() > 0.5;
}
redisService.setBit(dateKey, i, isSign);
signInfo.append(isSign ? 1 : 0).append(", ");
}
System.out.println(signInfo.toString());
}
}
/**
* 用户当天签到
* 用户ID作为位图的偏移量
*/
@GetMapping("/sign/{userId}")
public String sign(@PathVariable Long userId) {
redisService.setBit(SIGN_PREFIX + getCurDate(), userId, true);
return "签到成功";
}
/**
* 查询用户今天是否已经签到了
*/
@GetMapping("/isSign/{userId}")
public String isSign(@PathVariable Long userId) {
Boolean isSign = redisService.getBit(SIGN_PREFIX + getCurDate(), userId);
if (isSign) {
return String.format("用户【%d】今日已签到", userId);
}
return String.format("用户【%d】今日尚未签到,请签到", userId);
}
/**
* 统计今天所有的签到数量
*/
@GetMapping("/todayCount")
public String todayCount() {
return String.format("今日已签到人数: %d", redisService.bitCount(SIGN_PREFIX + getCurDate()));
}
/**
* 统计指定用户全年的签到数
*/
@GetMapping("/userYearSign/{userId}")
public String userYearSign(@PathVariable Long userId) {
int year = LocalDate.now().getYear();
// 获取所有的key
Set<String> keys = redisService.getKeys(SIGN_PREFIX + year + "*");
/*
* 可以使用BitSet 去存储用户每天的签到信息,用于其他的操作
* BitSet users = new BitSet();
* 统计所有已经签到的数量 对应 redis的bitCount
* users.cardinality()
*/
int signCount = 0;
for (String key : keys) {
if (redisService.getBit(key, userId)) {
signCount++;
}
}
return String.format("本年已累计签到: %d 次", signCount);
}
/**
* 统计近7天连续签到的用户数量
* 逻辑与
*/
@GetMapping("/signAllWeek")
public String signAllWeek() {
List<String> weekDays = getWeekKeys();
redisService.bitOp(RedisStringCommands.BitOperation.AND, SIGN_ALL_WEEK_KEY, weekDays);
return String.format("近7天连续签到用户数:%d", redisService.bitCount(SIGN_ALL_WEEK_KEY));
}
/**
* 统计本月全部签到过的用户数量
*/
@GetMapping("/signAllMonth")
public String signAllMonth() {
redisService.bitOp(
RedisStringCommands.BitOperation.AND,
SIGN_ALL_MONTH_KEY,
SIGN_PREFIX + LocalDate.now().getYear()
);
return String.format("月全部签到过的用户数:%d", redisService.bitCount(SIGN_ALL_MONTH_KEY));
}
/**
* 统计近7天有过签到的用户数量,只签到1次也算
* 逻辑或
*/
@GetMapping("/signInWeek")
public String signInWeek() {
List<String> weekDays = getWeekKeys();
redisService.bitOp(RedisStringCommands.BitOperation.OR, SIGN_IN_WEEK_KEY, weekDays);
return String.format("近7天有过签到的用户数:%d", redisService.bitCount(SIGN_IN_WEEK_KEY));
}
/**
* 获取当天的日期
*
* @return yyyyMMdd
*/
private String getCurDate() {
return LocalDate.now().format(formatters);
}
/**
* 获取近一周的日期对应的key
*/
private List<String> getWeekKeys() {
List<String> dateList = new ArrayList<>();
LocalDate curDate = LocalDate.now();
dateList.add(SIGN_PREFIX + curDate.format(formatters));
for (int i = 1; i < 7; i++) {
dateList.add(SIGN_PREFIX + curDate.plusDays(-i).format(formatters));
}
return dateList;
}
}
Пополнить
- В приведенном выше примере наш идентификатор пользователя по умолчанию является числовым типом.Если ваш идентификатор пользователя представляет собой строку, вы можете использовать идентификатор пользователя в качестве ключа и взять день года в качестве смещения, чтобы запись одного дня это целый год одного человека.
- в ходе выполнения
BITOP
Во время операции ключ результата будет сгенерирован заново, и ключ результата может быть сгенерирован путем подсчета предыдущих записей с помощью запланированной задачи рано утром каждого дня, чтобы ключ результата можно было использовать для непосредственного подсчета данных в бизнес.
Доступ к исходному коду
Весь код загружен на Github для быстрого доступа
>>>>>> Боевой Redis — растровое изображение
ежедневные комплименты
Это не легко создать, если вы найдете это полезным,попросить лайкслужба поддержки
Поиск внимания
Публичный аккаунт WeChat: Ю Дасянь