更加完整详细内容可查看【免费版Java学习笔记】和【免费版Java面试题】
免费版Java学习笔记(28w字)链接:https://www.yuque.com/aoyouaoyou/sgcqr8
免费版Java面试题(20w字)链接:https://www.yuque.com/aoyouaoyou/wh3hto
完整版Java学习笔记200w字,附有代码实现,图解清楚,仅需9.9
完整版Java面试题,150w字,高频面试题,内容详细,仅需9.9
完整版链接:
https://www.xiaohongshu.com/user/profile/63c2d512000000002601232c
祝您新的一年事事马到成功,身体健康,阖家幸福,大展宏图!
package com.ao.commonUtil.util.redis;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DataAccessException;
import org.springframework.data.geo.*;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.*;
import org.springframework.data.redis.connection.RedisGeoCommands;
@Slf4j
@Component
public class RedisUtil {
private final RedisTemplate<String, Object> redisTemplate;
private final ValueOperations<String, Object> valueOps;
private final ListOperations<String, Object> listOps;
private final SetOperations<String, Object> setOps;
private final HashOperations<String, String, Object> hashOps;
private final ZSetOperations<String, Object> zSetOps;
private final GeoOperations<String, Object> geoOps;
private final HyperLogLogOperations<String, Object> hllOps;
public RedisUtil(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
this.valueOps = redisTemplate.opsForValue();
this.listOps = redisTemplate.opsForList();
this.setOps = redisTemplate.opsForSet();
this.hashOps = redisTemplate.opsForHash();
this.zSetOps = redisTemplate.opsForZSet();
this.geoOps = redisTemplate.opsForGeo();
this.hllOps = redisTemplate.opsForHyperLogLog();
}
// ============================ 通用命令 ============================
/**
* 删除key
*/
public Boolean delete(String key) {
return redisTemplate.delete(key);
}
/**
* 批量删除key
*/
public Long delete(Collection<String> keys) {
return redisTemplate.delete(keys);
}
/**
* 判断key是否存在
*/
public Boolean exists(String key) {
return redisTemplate.hasKey(key);
}
/**
* 设置过期时间
*/
public Boolean expire(String key, long timeout, TimeUnit unit) {
return redisTemplate.expire(key, timeout, unit);
}
/**
* 设置过期时间(秒)
*/
public Boolean expire(String key, long seconds) {
return expire(key, seconds, TimeUnit.SECONDS);
}
/**
* 获取过期时间
*/
public Long getExpire(String key, TimeUnit unit) {
return redisTemplate.getExpire(key, unit);
}
/**
* 获取过期时间(秒)
*/
public Long getExpire(String key) {
return getExpire(key, TimeUnit.SECONDS);
}
/**
* 移除key的过期时间
*/
public Boolean persist(String key) {
return redisTemplate.persist(key);
}
/**
* 获取key的类型
*/
public String type(String key) {
return redisTemplate.type(key).code();
}
// ============================ String ============================
/**
* 设置字符串值
*/
public void set(String key, Object value) {
valueOps.set(key, value);
}
/**
* 设置字符串值并设置过期时间
*/
public void set(String key, Object value, long timeout, TimeUnit unit) {
valueOps.set(key, value, timeout, unit);
}
/**
* 设置字符串值并设置过期时间(秒)
*/
public void set(String key, Object value, long seconds) {
set(key, value, seconds, TimeUnit.SECONDS);
}
/**
* 当key不存在时设置字符串值
*/
public Boolean setIfAbsent(String key, Object value) {
return valueOps.setIfAbsent(key, value);
}
/**
* 当key不存在时设置字符串值并设置过期时间
*/
public Boolean setIfAbsent(String key, Object value, long timeout, TimeUnit unit) {
return valueOps.setIfAbsent(key, value, timeout, unit);
}
/**
* 获取字符串值
*/
public Object get(String key) {
return valueOps.get(key);
}
/**
* 获取字符串值并转换为指定类型
*/
@SuppressWarnings("unchecked")
public <T> T get(String key, Class<T> clazz) {
return (T) valueOps.get(key);
}
/**
* 自增
*/
public Long increment(String key) {
return valueOps.increment(key);
}
/**
* 自增指定值
*/
public Long increment(String key, long delta) {
return valueOps.increment(key, delta);
}
/**
* 自减
*/
public Long decrement(String key) {
return valueOps.decrement(key);
}
/**
* 自减指定值
*/
public Long decrement(String key, long delta) {
return valueOps.decrement(key, delta);
}
/**
* 追加字符串
*/
public Integer append(String key, String value) {
return valueOps.append(key, value);
}
/**
* 获取字符串长度
*/
public Long stringSize(String key) {
return valueOps.size(key);
}
// ============================ List ============================
/**
* 从左侧插入元素
*/
public Long lPush(String key, Object value) {
return listOps.leftPush(key, value);
}
/**
* 从左侧批量插入元素
* @param key
* @param values 数组形式
*/
public Long lPushAll(String key, Object… values) {
return listOps.leftPushAll(key, values);
}
/**
* 从右侧插入元素
*/
public Long rPush(String key, Object value) {
return listOps.rightPush(key, value);
}
/**
* 从右侧批量插入元素
*/
public Long rPushAll(String key, Object… values) {
return listOps.rightPushAll(key, values);
}
/**
* 从左侧弹出元素
*/
public Object lPop(String key) {
return listOps.leftPop(key);
}
/**
* 从左侧弹出元素(带超时)
*/
public Object lPop(String key, long timeout, TimeUnit unit) {
return listOps.leftPop(key, timeout, unit);
}
/**
* 从右侧弹出元素
*/
public Object rPop(String key) {
return listOps.rightPop(key);
}
/**
* 从右侧弹出元素(带超时)
*/
public Object rPop(String key, long timeout, TimeUnit unit) {
return listOps.rightPop(key, timeout, unit);
}
/**
* 获取列表长度
*/
public Long lLen(String key) {
return listOps.size(key);
}
/**
* 获取列表指定范围的元素
*/
public List<Object> lRange(String key, long start, long end) {
return listOps.range(key, start, end);
}
/**
* 根据索引获取列表元素
*/
public Object lIndex(String key, long index) {
return listOps.index(key, index);
}
/**
* 设置列表指定索引的元素
*/
public void lSet(String key, long index, Object value) {
listOps.set(key, index, value);
}
/**
* 移除列表中与value相等的元素
*/
public Long lRemove(String key, long count, Object value) {
return listOps.remove(key, count, value);
}
/**
* 裁剪列表,只保留指定范围内的元素
*/
public void lTrim(String key, long start, long end) {
listOps.trim(key, start, end);
}
/**
* 从源队列右侧弹出元素并压入目标队列左侧(原子操作)
* @param sourceKey 源队列Key
* @param destinationKey 目标队列Key
* @return 弹出并压入的元素,如果源队列为空返回null
*/
public Object rPopLPush(String sourceKey, String destinationKey) {
return redisTemplate.opsForList().rightPopAndLeftPush(sourceKey, destinationKey);
}
/**
* 阻塞式右弹出元素
* @param timeout 超时时间(秒)
* @param keys 一个或多个队列key
* @return 包含队列名和元素的列表(第一个元素是队列名,第二个是元素值)
*/
public List<Object> bRPop(int timeout, String… keys) {
return redisTemplate.execute(new RedisCallback<List<Object>>() {
@Override
public List<Object> doInRedis(RedisConnection connection) throws DataAccessException {
RedisSerializer<String> keySerializer = getKeySerializer();
byte[][] keyBytes = Arrays.stream(keys)
.map(keySerializer::serialize)
.toArray(byte[][]::new);
List<byte[]> result = connection.bRPop(timeout, keyBytes);
return deserializeResult(keySerializer, result);
}
});
}
/**
* 阻塞式左弹出元素(带详细日志)
* @param timeout 超时时间(秒)
* @param keys 一个或多个队列key
* @return 包含队列名和元素的列表,格式为 [队列名, 元素值]
*
* 示例:
* 1. bLPop(30, "queue1", "queue2")
* – 先检查queue1,如果非空则弹出元素
* – 如果queue1为空,则检查queue2
* – 如果都为空则阻塞等待30秒
* 2. 返回值示例:["queue1", "message123"]
*/
public List<Object> bRPopWithLog(int timeout, String… keys) {
log.debug("准备执行BRPOP,超时时间:{}秒,队列:{}", timeout, Arrays.toString(keys));
List<Object> result = redisTemplate.execute(new RedisCallback<List<Object>>() {
@Override
public List<Object> doInRedis(RedisConnection connection) throws DataAccessException {
long startTime = System.currentTimeMillis();
RedisSerializer<String> keySerializer = getKeySerializer();
byte[][] keyBytes = Arrays.stream(keys)
.peek(key -> log.debug("序列化队列key:{}", key))
.map(keySerializer::serialize)
.toArray(byte[][]::new);
List<byte[]> rawResult = connection.bRPop(timeout, keyBytes);
log.debug("准备执行BRPOP执行耗时:{}ms", System.currentTimeMillis() – startTime);
return deserializeResult(keySerializer, rawResult);
}
});
if (result != null) {
log.info("从队列 [{}] 获取到消息:{}", result.get(0), result.get(1));
} else {
log.debug("BLPOP操作超时,未获取到消息");
}
return result;
}
/**
* 阻塞式左弹出元素
* @param timeout 超时时间(秒)
* @param keys 一个或多个队列key
* @return 包含队列名和元素的列表(第一个元素是队列名,第二个是元素值)
*/
public List<Object> bLPop(int timeout, String… keys) {
return redisTemplate.execute(new RedisCallback<List<Object>>() {
@Override
public List<Object> doInRedis(RedisConnection connection) throws DataAccessException {
RedisSerializer<String> keySerializer = getKeySerializer();
byte[][] keyBytes = Arrays.stream(keys)
.map(keySerializer::serialize)
.toArray(byte[][]::new);
List<byte[]> result = connection.bLPop(timeout, keyBytes);
return deserializeResult(keySerializer, result);
}
});
}
/**
* 阻塞式左弹出元素(带详细日志)
* @param timeout 超时时间(秒)
* @param keys 一个或多个队列key
* @return 包含队列名和元素的列表,格式为 [队列名, 元素值]
*
* 示例:
* 1. bLPop(30, "queue1", "queue2")
* – 先检查queue1,如果非空则弹出元素
* – 如果queue1为空,则检查queue2
* – 如果都为空则阻塞等待30秒
* 2. 返回值示例:["queue1", "message123"]
*/
public List<Object> bLPopWithLog(int timeout, String… keys) {
log.debug("准备执行BLPOP,超时时间:{}秒,队列:{}", timeout, Arrays.toString(keys));
List<Object> result = redisTemplate.execute(new RedisCallback<List<Object>>() {
@Override
public List<Object> doInRedis(RedisConnection connection) throws DataAccessException {
long startTime = System.currentTimeMillis();
RedisSerializer<String> keySerializer = getKeySerializer();
byte[][] keyBytes = Arrays.stream(keys)
.peek(key -> log.debug("序列化队列key:{}", key))
.map(keySerializer::serialize)
.toArray(byte[][]::new);
List<byte[]> rawResult = connection.bLPop(timeout, keyBytes);
log.debug("BLPOP执行耗时:{}ms", System.currentTimeMillis() – startTime);
return deserializeResult(keySerializer, rawResult);
}
});
if (result != null) {
log.info("从队列 [{}] 获取到消息:{}", result.get(0), result.get(1));
} else {
log.debug("BLPOP操作超时,未获取到消息");
}
return result;
}
/**
* 阻塞式从源队列右弹出元素并左压入目标队列
* @param sourceKey 源队列key
* @param destinationKey 目标队列key
* @param timeout 超时时间(秒)
* @return 被移动的元素值
*/
public Object bRPopLPush(String sourceKey, String destinationKey, int timeout) {
return redisTemplate.execute(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
RedisSerializer<String> keySerializer = getKeySerializer();
byte[] source = keySerializer.serialize(sourceKey);
byte[] destination = keySerializer.serialize(destinationKey);
byte[] result = connection.bRPopLPush(timeout, source, destination);
return result != null ?
redisTemplate.getValueSerializer().deserialize(result) :
null;
}
});
}
private RedisSerializer<String> getKeySerializer() {
@SuppressWarnings("unchecked")
RedisSerializer<String> keySerializer =
(RedisSerializer<String>) redisTemplate.getKeySerializer();
return keySerializer;
}
private List<Object> deserializeResult(RedisSerializer<String> keySerializer, List<byte[]> result) {
if (result == null || result.size() < 2) {
return null;
}
List<Object> deserialized = new ArrayList<>(2);
deserialized.add(keySerializer.deserialize(result.get(0)));
deserialized.add(redisTemplate.getValueSerializer().deserialize(result.get(1)));
return deserialized;
}
// ============================ Set ============================
/**
* 向集合添加元素
*/
public Long sAdd(String key, Object… values) {
return setOps.add(key, values);
}
/**
* 从集合中移除元素
*/
public Long sRemove(String key, Object… values) {
return setOps.remove(key, values);
}
/**
* 从集合中随机移除并返回一个元素
*/
public Object sPop(String key) {
return setOps.pop(key);
}
/**
* 从集合中随机获取一个元素
*/
public Object sRandomMember(String key) {
return setOps.randomMember(key);
}
/**
* 从集合中随机获取多个元素(允许重复)
*/
public List<Object> sRandomMembers(String key, long count) {
return setOps.randomMembers(key, count);
}
/**
* 从集合中随机获取多个元素(不重复)
*/
public Set<Object> sDistinctRandomMembers(String key, long count) {
return setOps.distinctRandomMembers(key, count);
}
/**
* 获取集合所有元素
*/
public Set<Object> sMembers(String key) {
return setOps.members(key);
}
/**
* 判断元素是否在集合中
*/
public Boolean sIsMember(String key, Object value) {
return setOps.isMember(key, value);
}
/**
* 获取集合元素数量
*/
public Long sCard(String key) {
return setOps.size(key);
}
/**
* 移动元素到另一个集合
*/
public Boolean sMove(String key, Object value, String destKey) {
return setOps.move(key, value, destKey);
}
/**
* 求两个集合的差集
*/
public Set<Object> sDifference(String key, String otherKey) {
return setOps.difference(key, otherKey);
}
/**
* 求多个集合的差集
*/
public Set<Object> sDifference(String key, Collection<String> otherKeys) {
return setOps.difference(key, otherKeys);
}
/**
* 求两个集合的差集并存储到目标集合
*/
public Long sDifferenceAndStore(String key, String otherKey, String destKey) {
return setOps.differenceAndStore(key, otherKey, destKey);
}
/**
* 求多个集合的差集并存储到目标集合
*/
public Long sDifferenceAndStore(String key, Collection<String> otherKeys, String destKey) {
return setOps.differenceAndStore(key, otherKeys, destKey);
}
/**
* 求两个集合的交集
*/
public Set<Object> sIntersect(String key, String otherKey) {
return setOps.intersect(key, otherKey);
}
/**
* 求多个集合的交集
*/
public Set<Object> sIntersect(String key, Collection<String> otherKeys) {
return setOps.intersect(key, otherKeys);
}
/**
* 求两个集合的交集并存储到目标集合
*/
public Long sIntersectAndStore(String key, String otherKey, String destKey) {
return setOps.intersectAndStore(key, otherKey, destKey);
}
/**
* 求多个集合的交集并存储到目标集合
*/
public Long sIntersectAndStore(String key, Collection<String> otherKeys, String destKey) {
return setOps.intersectAndStore(key, otherKeys, destKey);
}
/**
* 求两个集合的并集
*/
public Set<Object> sUnion(String key, String otherKey) {
return setOps.union(key, otherKey);
}
/**
* 求多个集合的并集
*/
public Set<Object> sUnion(String key, Collection<String> otherKeys) {
return setOps.union(key, otherKeys);
}
/**
* 求两个集合的并集并存储到目标集合
*/
public Long sUnionAndStore(String key, String otherKey, String destKey) {
return setOps.unionAndStore(key, otherKey, destKey);
}
/**
* 求多个集合的并集并存储到目标集合
*/
public Long sUnionAndStore(String key, Collection<String> otherKeys, String destKey) {
return setOps.unionAndStore(key, otherKeys, destKey);
}
// ============================ Hash ============================
/**
* 设置哈希字段值
*/
public void hSet(String key, String hashKey, Object value) {
hashOps.put(key, hashKey, value);
}
/**
* 批量设置哈希字段值
*/
public void hMSet(String key, Map<String, Object> map) {
hashOps.putAll(key, map);
}
/**
* 当哈希字段不存在时设置值
*/
public Boolean hPutIfAbsent(String key, String hashKey, Object value) {
return hashOps.putIfAbsent(key, hashKey, value);
}
/**
* 获取哈希字段值
*/
public Object hGet(String key, String hashKey) {
return hashOps.get(key, hashKey);
}
/**
* 获取哈希多个字段值
*/
public List<Object> hMGet(String key, Collection<String> hashKeys) {
return hashOps.multiGet(key, hashKeys);
}
/**
* 获取哈希所有字段和值
*/
public Map<String, Object> hGetAll(String key) {
return hashOps.entries(key);
}
/**
* 获取哈希表中所有字段和值(带泛型支持)
* @param key Redis键
* @return 包含所有字段和值的Map
*
* Redis命令:
* HGETALL key
*/
public <HK, HV> Map<HK, HV> hGetAll(String key, Class<HK> hashKeyType, Class<HV> valueType) {
return redisTemplate.<HK, HV>opsForHash().entries(key);
}
/**
* 删除哈希字段
*/
public Long hDelete(String key, Object… hashKeys) {
return hashOps.delete(key, hashKeys);
}
/**
* 判断哈希字段是否存在
*/
public Boolean hHasKey(String key, String hashKey) {
return hashOps.hasKey(key, hashKey);
}
/**
* 获取哈希所有字段
*/
public Set<String> hKeys(String key) {
return hashOps.keys(key);
}
/**
* 获取哈希所有值
*/
public List<Object> hValues(String key) {
return hashOps.values(key);
}
/**
* 获取哈希字段数量
*/
public Long hLen(String key) {
return hashOps.size(key);
}
/**
* 哈希字段自增
*/
public Long hIncrementLong(String key, String hashKey, long delta) {
return hashOps.increment(key, hashKey, delta);
}
/**
* 哈希字段自增(浮点数)
*/
public Double hIncrementDouble(String key, String hashKey, double delta) {
return hashOps.increment(key, hashKey, delta);
}
// ============================ ZSet ============================
/**
* 向有序集合添加元素
*/
public Boolean zAdd(String key, Object value, double score) {
return zSetOps.add(key, value, score);
}
/**
* 批量向有序集合添加元素
*/
public Long zAdd(String key, Set<ZSetOperations.TypedTuple<Object>> tuples) {
return zSetOps.add(key, tuples);
}
/**
* 批量向有序集合添加元素 添加元素类型为Map key是元素 value是分数
*/
public Boolean zAddMap(String key, Map<Object, Double> scoreMembers) {
if (scoreMembers == null || scoreMembers.isEmpty()) {
return false;
}
Set<ZSetOperations.TypedTuple<Object>> tuples = new HashSet<>();
for (Map.Entry<Object, Double> entry : scoreMembers.entrySet()) {
ZSetOperations.TypedTuple<Object> tuple =
new DefaultTypedTuple<>(entry.getKey(), entry.getValue());
tuples.add(tuple);
}
redisTemplate.opsForZSet().add(key, tuples);
return true;
}
/**
* 从有序集合中移除元素
*/
public Long zRemove(String key, Object… values) {
return zSetOps.remove(key, values);
}
/**
* 增加元素的分数
*/
public Double zIncrementScore(String key, Object value, double delta) {
return zSetOps.incrementScore(key, value, delta);
}
/**
* 获取元素的排名(从小到大)
*/
public Long zRank(String key, Object value) {
return zSetOps.rank(key, value);
}
/**
* 获取元素的排名(从大到小)
*/
public Long zRevRank(String key, Object value) {
return zSetOps.reverseRank(key, value);
}
/**
* 获取有序集合元素数量
*/
public Long zCard(String key) {
return zSetOps.zCard(key);
}
/**
* 获取元素的分数
*/
public Double zScore(String key, Object value) {
return zSetOps.score(key, value);
}
/**
* 获取有序集合指定范围的元素(按分数从小到大)
*/
public Set<Object> zRange(String key, long start, long end) {
return zSetOps.range(key, start, end);
}
/**
* 获取有序集合指定范围的元素(按分数从大到小)
*/
public Set<Object> zReverseRange(String key, long start, long end) {
return zSetOps.reverseRange(key, start, end);
}
/**
* 获取有序集合指定分数范围的元素
*/
public Set<Object> zRangeByScore(String key, double min, double max) {
return zSetOps.rangeByScore(key, min, max);
}
/**
* 获取有序集合指定分数范围的元素(带偏移量和数量)
*/
public Set<Object> zRangeByScore(String key, double min, double max, long offset, long count) {
return zSetOps.rangeByScore(key, min, max, offset, count);
}
/**
* 获取有序集合指定分数范围的元素(带偏移量和数量,按分数从大到小)
*/
public Set<Object> zReverseRangeByScore(String key, double min, double max, long offset, long count) {
return zSetOps.reverseRangeByScore(key, min, max, offset, count);
}
/**
* 获取有序集合中指定区间的元素(逆序,带分数)
* @param key Redis键
* @param start 开始索引(包含)
* @param end 结束索引(包含)
* @return 包含成员和分数的集合,格式为:Map.Entry<成员, 分数>
*/
public Set<Map.Entry<Object, Double>> zReverseRangeWithScores(String key, long start, long end) {
Set<ZSetOperations.TypedTuple<Object>> typedTuples = redisTemplate.opsForZSet()
.reverseRangeWithScores(key, start, end);
if (typedTuples == null) {
return Collections.emptySet();
}
Set<Map.Entry<Object, Double>> result = new LinkedHashSet<>(typedTuples.size());
for (ZSetOperations.TypedTuple<Object> tuple : typedTuples) {
result.add(new AbstractMap.SimpleEntry<>(tuple.getValue(), tuple.getScore()));
}
return result;
}
/**
* 获取有序集合指定分数范围的元素(带分数)
*/
public Set<ZSetOperations.TypedTuple<Object>> zRangeByScoreWithScores(String key, double min, double max) {
return zSetOps.rangeByScoreWithScores(key, min, max);
}
/**
* 获取有序集合指定分数范围的元素(带分数,带偏移量和数量)
*/
public Set<ZSetOperations.TypedTuple<Object>> zRangeByScoreWithScores(String key, double min, double max, long offset, long count) {
return zSetOps.rangeByScoreWithScores(key, min, max, offset, count);
}
/**
* 获取有序集合指定分数范围的元素(带分数,按分数从大到小)
*/
public Set<ZSetOperations.TypedTuple<Object>> zReverseRangeByScoreWithScores(String key, double min, double max) {
return zSetOps.reverseRangeByScoreWithScores(key, min, max);
}
/**
* 获取有序集合指定分数范围的元素(带分数,带偏移量和数量,按分数从大到小)
*/
public Set<ZSetOperations.TypedTuple<Object>> zReverseRangeByScoreWithScores(String key, double min, double max, long offset, long count) {
return zSetOps.reverseRangeByScoreWithScores(key, min, max, offset, count);
}
/**
* 统计有序集合指定分数范围内的元素数量
*/
public Long zCount(String key, double min, double max) {
return zSetOps.count(key, min, max);
}
/**
* 移除有序集合指定排名范围的元素
*/
public Long zRemoveRange(String key, long start, long end) {
return zSetOps.removeRange(key, start, end);
}
/**
* 移除有序集合指定分数范围的元素
*/
public Long zRemoveRangeByScore(String key, double min, double max) {
return zSetOps.removeRangeByScore(key, min, max);
}
// ============================ Geo ============================
/**
* 添加地理位置
*/
public Long geoAdd(String key, double longitude, double latitude, Object member) {
return geoOps.add(key, new RedisGeoCommands.GeoLocation<>(member, new Point(longitude, latitude)));
}
/**
* 批量添加地理位置
*/
public Long geoAdd(String key, Map<String, Point> memberCoordinateMap) {
List<RedisGeoCommands.GeoLocation<Object>> locations = new ArrayList<>();
memberCoordinateMap.forEach((member, point) ->
locations.add(new RedisGeoCommands.GeoLocation<>(member, point)));
return geoOps.add(key, locations);
}
/**
* 获取地理位置
*/
public List<Point> geoPos(String key, Object… members) {
return geoOps.position(key, members);
}
/**
* 计算两个地理位置之间的距离
*/
public Distance geoDist(String key, Object member1, Object member2, Metric metric) {
return geoOps.distance(key, member1, member2, metric);
}
/**
* 获取指定位置半径范围内的地理位置
*/
public GeoResults<RedisGeoCommands.GeoLocation<Object>> geoRadius(String key, double longitude, double latitude,
double distance, Metric metric,
RedisGeoCommands.GeoRadiusCommandArgs args) {
return geoOps.radius(key, new Circle(new Point(longitude, latitude), new Distance(distance, metric)), args);
}
/**
* 获取指定成员半径范围内的地理位置
*/
public GeoResults<RedisGeoCommands.GeoLocation<Object>> geoRadiusByMember(String key, Object member,
double distance, Metric metric,
RedisGeoCommands.GeoRadiusCommandArgs args) {
return geoOps.radius(key, member, new Distance(distance, metric), args);
}
/**
* 删除目标坐标
* @param key GEO键
* @param members 目标ID(可变参数)
* @return 删除成功的数量
*/
public Long geoRem(String key, String… members) {
return geoOps.remove(key, members);
}
// ============================ BitMap ============================
/**
* 设置位图
*/
public Boolean setBit(String key, long offset, boolean value) {
return redisTemplate.opsForValue().setBit(key, offset, value);
}
/**
* 获取位图
*/
public Boolean getBit(String key, long offset) {
return redisTemplate.opsForValue().getBit(key, offset);
}
/**
* 统计位图中值为1的位数
*/
public Long bitCount(String key) {
return redisTemplate.execute((RedisCallback<Long>) connection -> connection.bitCount(key.getBytes()));
}
/**
* 统计位图指定范围内值为1的位数
*/
public Long bitCount(String key, long start, long end) {
return redisTemplate.execute((RedisCallback<Long>) connection ->
connection.bitCount(key.getBytes(), start, end));
}
/**
* 位图操作(AND/OR/XOR/NOT)
*/
public Long bitOp(RedisStringCommands.BitOperation op, String destKey, String… srcKeys) {
return redisTemplate.execute((RedisCallback<Long>) connection ->
connection.bitOp(op, destKey.getBytes(), Arrays.stream(srcKeys).map(String::getBytes).toArray(byte[][]::new)));
}
/**
* 查找位图中第一个等于指定值的位的位置
*
* @param key 键名
* @param bitValue 查找的位值(true表示1,false表示0)
* @return 返回第一个匹配位的位置(从0开始),如果未找到则返回-1
*/
public Long bitPos(String key, boolean bitValue) {
return redisTemplate.execute((RedisCallback<Long>) connection ->
connection.bitPos(key.getBytes(), bitValue));
}
/**
* 查找 Bitmap 中第一个符合条件的位的位置
*
* @param key Redis键
* @param bitValue 查找的位值(true为1,false为0)
* @param start 起始位索引(包含)
* @param end 结束位索引(包含)
* @return 第一个符合条件的位的位置,如果未找到则返回null
*/
public Long bitPos(String key, boolean bitValue, long start, long end) {
return redisTemplate.execute((RedisCallback<Long>) connection -> {
// 将参数转换为 Redis 命令所需的字节数组
byte[][] args = new byte[4][];
args[0] = key.getBytes();
args[1] = String.valueOf(bitValue ? 1 : 0).getBytes();
args[2] = String.valueOf(start).getBytes();
args[3] = String.valueOf(end).getBytes();
// 执行原生 Redis 命令: BITPOS key bit start end
Object result = connection.execute("BITPOS", args);
// 处理返回结果
if (result instanceof Long) {
Long position = (Long) result;
return position == -1 ? null : position;
}
return null;
});
}
// ============================ HyperLogLog ============================
/**
* 添加元素到HyperLogLog
*/
public Long pfAdd(String key, Object… values) {
return hllOps.add(key, values);
}
/**
* 获取HyperLogLog的基数估算值
*/
public Long pfCount(String key) {
return hllOps.size(key);
}
/**
* HyperLogLog 统计基数
* @param keys 一个或多个键
* @return 基数(独立元素数量)
*/
public Long pfCount(String… keys) {
return hllOps.size(keys);
}
/**
* 合并多个HyperLogLog
*/
public Long pfMerge(String destKey, String… sourceKeys) {
return hllOps.union(destKey, sourceKeys);
}
}
一、概述
RedisUtil 是基于 Spring Data Redis 封装的 Redis 操作工具类,专为 Spring 项目设计(标注 @Component 注解)。其核心功能是通过 Spring 提供的 RedisTemplate 统一管理 Redis 连接与序列化,并封装 Redis 全类型数据结构(String、List、Set、Hash、ZSet 等)及通用命令的操作方法,简化 Redis 开发流程。
二、优势
- Spring 无缝集成:标注 @Component,可通过 @Autowired 依赖注入,无需手动实例化,符合 Spring 依赖注入规范。
- 全数据结构支持:覆盖 Redis 所有核心数据结构(String、List、Set、Hash、ZSet、Geo、BitMap、HyperLogLog),满足多样化业务需求。
- 简化操作流程:封装复杂的序列化、连接管理逻辑,提供简洁 API,开发者无需关注底层实现细节。
三、核心组件与初始化
1. 核心属性
工具类通过 RedisTemplate 初始化各类数据结构的操作对象(Operations),核心属性如下:
|
属性名 |
类型 |
作用描述 |
对应 Redis 数据结构 |
|
valueOps |
ValueOperations<String, Object> |
字符串(String)类型操作对象 |
String |
|
listOps |
ListOperations<String, Object> |
列表(List)类型操作对象 |
List |
|
setOps |
SetOperations<String, Object> |
集合(Set)类型操作对象 |
Set |
|
hashOps |
HashOperations<String, String, Object> |
哈希(Hash)类型操作对象 |
Hash |
|
zSetOps |
ZSetOperations<String, Object> |
有序集合(ZSet)类型操作对象 |
ZSet |
|
geoOps |
GeoOperations<String, Object> |
地理信息(Geo)类型操作对象 |
Geo |
|
hllOps |
HyperLogLogOperations<String, Object> |
基数统计(HyperLogLog)类型操作对象 |
HyperLogLog |
2. 初始化流程
通过构造函数注入 RedisTemplate<String, Object>,并初始化上述操作对象,确保依赖注入的完整性:
public RedisUtil(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
this.valueOps = redisTemplate.opsForValue();
this.listOps = redisTemplate.opsForList();
// 其他操作对象初始化…
}
四、核心方法分类解析
工具类按功能模块划分为 9 大类别,覆盖 Redis 全类型操作:
1. 通用命令
提供 Redis 全局操作方法,适用于所有数据结构:
|
方法名 |
功能描述 |
关键参数说明 |
|
delete(String key) |
删除指定键 |
key:目标键名 |
|
delete(Collection<String> keys) |
批量删除键 |
keys:待删除键的集合 |
|
exists(String key) |
判断键是否存在 |
返回 true 表示存在,false 不存在 |
|
expire(String key, long timeout, TimeUnit unit) |
设置键过期时间 |
timeout:超时时间;unit:时间单位(秒/分等) |
|
getExpire(String key) |
获取剩余过期时间(秒) |
返回剩余秒数,-1 表示永久有效 |
|
persist(String key) |
移除键的过期时间(设为永久) |
– |
2. String 类型操作
针对 Redis 字符串类型的基础操作,支持设置值、过期时间、自增自减等:
|
核心方法 |
功能描述 |
典型场景 |
|
set(String key, Object value) |
设置键值对 |
存储用户会话、缓存简单数据 |
|
set(String key, Object value, long timeout, TimeUnit unit) |
带过期时间的键值对 |
临时验证码、限时缓存 |
|
setIfAbsent(String key, Object value) |
键不存在时设置值(原子操作) |
分布式锁、防止重复提交 |
|
get(String key) |
获取键对应的值 |
读取缓存数据 |
|
increment(String key) / decrement(String key) |
数值自增/自减 |
计数器(访问量、订单数) |
3. List 类型操作
支持列表的双向插入、弹出、范围查询等操作,适用于消息队列、历史记录等场景:
|
核心方法 |
功能描述 |
特色功能 |
|
lPush(String key, Object value) / rPush(…) |
从左侧/右侧插入元素 |
支持批量插入(lPushAll/rPushAll) |
|
lPop(String key) / rPop(…) |
从左侧/右侧弹出元素 |
支持阻塞式弹出(lPop 带超时参数) |
|
lRange(String key, long start, long end) |
获取指定范围元素 |
start/end 为索引(0 开始,-1 表示末尾) |
|
bLPopWithLog(int timeout, String… keys) |
带日志的阻塞式左弹出 |
支持多队列监听,超时自动返回,便于调试 |
|
rPopLPush(String sourceKey, String destinationKey) |
原子性移动物品 |
从源队列右弹并左压入目标队列,适用于可靠消息队列 |
4. Set 类型操作
针对无序不可重复集合的操作,支持交集、并集、差集等运算:
|
核心方法 |
功能描述 |
实用场景 |
|
sAdd(String key, Object… values) |
向集合添加元素 |
标签管理、数据去重 |
|
sMembers(String key) |
获取集合所有元素 |
查看集合完整数据 |
|
sIsMember(String key, Object value) |
判断元素是否在集合中 |
权限校验、数据存在性检查 |
|
sIntersect(String key, String otherKey) |
求两个集合的交集 |
共同好友、相似内容推荐 |
|
sUnion(String key, Collection<String> otherKeys) |
求多个集合的并集 |
数据合并、全量统计 |
5. Hash 类型操作
模拟“键-字段-值”三层结构,适用于存储对象属性(如用户信息、商品详情):
|
核心方法 |
功能描述 |
优势特点 |
|
hSet(String key, String hashKey, Object value) |
设置哈希字段值 |
单个属性更新 |
|
hMSet(String key, Map<String, Object> map) |
批量设置哈希字段值 |
初始化对象多个属性 |
|
hGetAll(String key) |
获取所有字段和值 |
完整获取对象信息 |
|
hKeys(String key) / hValues(String key) |
获取所有字段/值 |
遍历对象属性或属性值 |
|
hIncrementLong(String key, String hashKey, long delta) |
字段值自增 |
计数器(如商品销量、用户积分) |
6. ZSet 类型操作
基于分数(score)排序的有序集合,适用于排行榜、优先级队列等场景:
|
核心方法 |
功能描述 |
关键参数 |
|
zAdd(String key, Object value, double score) |
添加元素及分数 |
score:排序权重分数 |
|
zRange(String key, long start, long end) |
按分数升序获取元素 |
start/end:排名范围 |
|
zReverseRange(String key, long start, long end) |
按分数降序获取元素 |
适用于 TopN 排行榜 |
|
zScore(String key, Object value) |
获取元素分数 |
查看元素权重值 |
|
zIncrementScore(String key, Object value, double delta) |
增加元素分数 |
动态调整排名(如积分增加) |
|
zRangeByScore(String key, double min, double max) |
按分数范围获取元素 |
筛选指定分数区间的元素 |
7. Geo 地理信息操作
支持地理位置存储、距离计算、范围搜索,适用于“附近的人”、POI 搜索等场景:
|
核心方法 |
功能描述 |
关键参数 |
|
geoAdd(String key, double longitude, double latitude, Object member) |
添加地理位置 |
longitude:经度;latitude:纬度 |
|
geoPos(String key, Object… members) |
获取地理位置坐标 |
返回经纬度坐标(Point)列表 |
|
geoDist(String key, Object m1, Object m2, Metric metric) |
计算两点距离 |
Metric:距离单位(米/千米等) |
|
geoRadius(String key, double lon, double lat, double distance, Metric metric, …) |
搜索指定半径内的位置 |
按中心点和半径搜索周边位置 |
|
geoRem(String key, String… members) |
删除地理位置 |
移除无效的地理坐标 |
8. BitMap 位图操作
通过位运算高效存储二进制数据,适用于签到、活跃用户统计等场景:
|
核心方法 |
功能描述 |
优势 |
|
setBit(String key, long offset, boolean value) |
设置指定位的值 |
节省空间(1字节=8位) |
|
getBit(String key, long offset) |
获取指定位的值 |
快速查询单个位状态 |
|
bitCount(String key) |
统计值为1的位数 |
统计活跃天数、用户数等 |
|
bitOp(RedisStringCommands.BitOperation op, String destKey, String… srcKeys) |
位图运算(AND/OR等) |
批量计算(如交集统计活跃用户) |
|
bitPos(String key, boolean bitValue) |
查找第一个指定值的位位置 |
快速定位有效数据起始位置 |
9. HyperLogLog 基数统计
以极小空间实现海量数据的基数估算(如独立访客数 UV):
|
核心方法 |
功能描述 |
适用场景 |
|
pfAdd(String key, Object… values) |
添加元素到 HyperLogLog |
记录访问用户ID、浏览内容ID |
|
pfCount(String key) |
获取基数估算值 |
统计独立访客数、不重复内容数 |
|
pfCount(String… keys) |
合并多个键的基数统计 |
跨时间段/渠道的 UV 合并统计 |
|
pfMerge(String destKey, String… sourceKeys) |
合并多个 HyperLogLog |
将多个数据集合并为一个新集合 |
五、使用示例
在 Spring 服务中通过依赖注入使用 RedisUtil:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserService {
@Autowired
private RedisUtil redisUtil;
// 示例1:存储用户信息到 Hash
public void saveUser(Long userId, String username, Integer age) {
String key = "user:" + userId;
redisUtil.hSet(key, "username", username);
redisUtil.hSet(key, "age", age);
redisUtil.expire(key, 86400); // 设置24小时过期
}
// 示例2:使用 List 实现消息队列
public void sendMessage(String queueName, String message) {
redisUtil.rPush(queueName, message); // 右压入队列
}
// 示例3:使用 ZSet 实现排行榜
public void addScore(String rankKey, String userId, double score) {
redisUtil.zAdd(rankKey, userId, score); // 添加用户分数
}
public List<Object> getTop10Rank(String rankKey) {
// 获取分数从高到低的前10名
return redisUtil.zReverseRange(rankKey, 0, 9);
}
}
六、单元测试代码
package com.ao.commonUtil.util.redis;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.geo.Point;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.domain.geo.Metrics;
import java.util.*;
/**
* StringRedisTemplate 测试
*/
@SpringBootTest
public class RedisUtilTest {
@Autowired
private RedisUtil redisUtil;
@Test
// ==================== 通用操作示例 ====================
public void commonOperationsExample() {
String key = "example:common:key1";
String value = "common_value";
System.out.println("===== 通用操作示例 =====");
// 设置键值
redisUtil.set(key, value);
System.out.println("设置键值: " + key + " = " + value);
// 获取键值
Object retrievedValue = redisUtil.get(key);
System.out.println("获取键值: " + key + " = " + retrievedValue);
// 设置过期时间
redisUtil.expire(key, 60);
System.out.println("设置键过期时间: 60秒");
// 检查键是否存在
boolean exists = redisUtil.exists(key);
System.out.println("键是否存在: " + exists);
// 获取剩余过期时间
long ttl = redisUtil.getExpire(key);
System.out.println("键剩余过期时间: " + ttl + "秒");
// 获取键类型
String type = redisUtil.type(key);
System.out.println("键类型: " + type);
// 删除键
redisUtil.delete(key);
System.out.println("删除键: " + key);
}
@Test
// ==================== String 类型示例 ====================
public void stringOperationsExample() {
String key = "example:string:key1";
String value = "string_value";
System.out.println("\\n===== String 操作示例 =====");
// 设置字符串值
redisUtil.set(key, value);
System.out.println("设置字符串: " + key + " = " + value);
// 获取字符串值
Object retrieved = redisUtil.get(key);
System.out.println("获取字符串: " + retrieved);
// 自增操作
String counterKey = "example:string:counter";
long incrResult = redisUtil.increment(counterKey);
System.out.println("自增结果: " + incrResult);
// 自增指定值
long incrByResult = redisUtil.increment(counterKey, 5);
System.out.println("自增5结果: " + incrByResult);
// 自减操作
long decrResult = redisUtil.decrement(counterKey);
System.out.println("自减结果: " + decrResult);
// 自减指定值
long decrByResult = redisUtil.decrement(counterKey, 3);
System.out.println("自减3结果: " + decrByResult);
// 追加字符串
redisUtil.append(key, "_appended");
System.out.println("追加字符串后: " + redisUtil.get(key));
// 获取字符串长度
long size = redisUtil.stringSize(key);
System.out.println("字符串长度: " + size);
// 清理
redisUtil.delete(Arrays.asList(key, counterKey));
}
@Test
// ==================== List 类型示例 ====================
public void listOperationsExample() {
String key = "example:list:key1";
System.out.println("\\n===== List 操作示例 =====");
// 右推入元素
redisUtil.rPush(key, "A");
redisUtil.rPush(key, "B");
redisUtil.rPush(key, "C");
System.out.println("右推入元素: A, B, C");
// 左推入元素
redisUtil.lPush(key, "X");
System.out.println("左推入元素: X");
// 获取列表范围
List<Object> range = redisUtil.lRange(key, 0, -1);
System.out.println("列表内容: " + range);
// 左弹出元素
Object leftPop = redisUtil.lPop(key);
System.out.println("左弹出元素: " + leftPop);
// 右弹出元素
Object rightPop = redisUtil.rPop(key);
System.out.println("右弹出元素: " + rightPop);
// 获取列表大小
long size = redisUtil.lLen(key);
System.out.println("列表大小: " + size);
// 根据索引获取元素
Object element = redisUtil.lIndex(key, 0);
System.out.println("索引0的元素: " + element);
// 设置指定索引的元素
redisUtil.lSet(key, 0, "new_value");
System.out.println("设置索引0的元素为new_value");
// 移除元素
redisUtil.lRemove(key, 1, "B");
System.out.println("移除1个B元素");
// 清理
redisUtil.delete(key);
}
@Test
// ==================== Hash 类型示例 ====================
public void hashOperationsExample() {
String key = "example:hash:user:1001";
System.out.println("\\n===== Hash 操作示例 =====");
// 设置单个字段
redisUtil.hSet(key, "name", "张三");
redisUtil.hSet(key, "age", 30);
redisUtil.hSet(key, "email", "zhangsan@example.com");
System.out.println("设置哈希字段: name, age, email");
// 获取单个字段
Object name = redisUtil.hGet(key, "name");
System.out.println("获取name字段: " + name);
// 获取所有字段
Map<String, Object> userMap = redisUtil.hGetAll(key);
System.out.println("获取所有字段: " + userMap);
// 检查字段是否存在
boolean hasEmail = redisUtil.hHasKey(key, "email");
System.out.println("email字段是否存在: " + hasEmail);
// 批量设置字段
Map<String, Object> newFields = new HashMap<>();
newFields.put("phone", "13800138000");
newFields.put("address", "北京");
redisUtil.hMSet(key, newFields);
System.out.println("批量设置字段: phone, address");
// 删除字段
redisUtil.hDelete(key, "phone");
System.out.println("删除phone字段");
// 字段自增
long newAge = redisUtil.hIncrementLong(key, "age", 1);
System.out.println("age字段自增1后: " + newAge);
// 清理
redisUtil.delete(key);
}
@Test
// ==================== Set 类型示例 ====================
public void setOperationsExample() {
String key = "example:set:key1";
System.out.println("\\n===== Set 操作示例 =====");
// 添加元素
redisUtil.sAdd(key, "A", "B", "C");
System.out.println("添加元素: A, B, C");
// 获取所有元素
Set<Object> members = redisUtil.sMembers(key);
System.out.println("集合成员: " + members);
// 检查元素是否存在
boolean containsB = redisUtil.sIsMember(key, "B");
System.out.println("集合是否包含B: " + containsB);
// 获取集合大小
long size = redisUtil.sCard(key);
System.out.println("集合大小: " + size);
// 随机获取元素
Object random = redisUtil.sRandomMember(key);
System.out.println("随机元素: " + random);
// 移除元素
redisUtil.sRemove(key, "A");
System.out.println("移除元素A");
// 移动元素到另一个集合
String destKey = "example:set:key2";
redisUtil.sMove(key, "B", destKey);
System.out.println("移动元素B到" + destKey);
// 清理
redisUtil.delete(Arrays.asList(key, destKey));
}
@Test
// ==================== ZSet 类型示例 ====================
public void zSetOperationsExample() {
String key = "example:zset:key1";
System.out.println("\\n===== ZSet 操作示例 =====");
// 添加带分数的元素
redisUtil.zAdd(key, "A", 10);
redisUtil.zAdd(key, "B", 20);
redisUtil.zAdd(key, "C", 30);
redisUtil.zAdd(key, "D", 40);
System.out.println("添加带分数元素: A(10), B(20), C(30), D(40)");
// 获取分数范围内的元素
Set<Object> range = redisUtil.zRangeByScore(key, 15, 35);
System.out.println("分数15-35之间的元素: " + range);
// 获取元素排名
Long rank = redisUtil.zRank(key, "B");
System.out.println("元素B的排名: " + rank);
// 获取元素分数
Double score = redisUtil.zScore(key, "C");
System.out.println("元素C的分数: " + score);
// 增加元素分数
Double newScore = redisUtil.zIncrementScore(key, "A", 5);
System.out.println("元素A分数增加5后: " + newScore);
// 获取集合大小
long size = redisUtil.zCard(key);
System.out.println("有序集合大小: " + size);
// 删除元素
redisUtil.zRemove(key, "A");
System.out.println("删除元素A");
// 清理
redisUtil.delete(key);
}
@Test
// ==================== BitMap 类型示例 ====================
public void bitmapOperationsExample() {
String key = "example:bitmap:user:sign:1001";
System.out.println("\\n===== BitMap 操作示例 =====");
// 设置位图
redisUtil.setBit(key, 1, true);
redisUtil.setBit(key, 3, true);
redisUtil.setBit(key, 5, true);
System.out.println("设置位图: 第1,3,5位为1");
// 获取位图
boolean bit1 = redisUtil.getBit(key, 1);
boolean bit2 = redisUtil.getBit(key, 2);
System.out.println("第1位: " + bit1);
System.out.println("第2位: " + bit2);
// 统计位数为1的数量
long count = redisUtil.bitCount(key);
System.out.println("位数为1的总数: " + count);
// 清理
redisUtil.delete(key);
}
@Test
// ==================== Geo 类型示例 ====================
public void geoOperationsExample() {
String key = "example:geo:cities";
System.out.println("\\n===== Geo 操作示例 =====");
// 添加地理位置
redisUtil.geoAdd(key, 116.404, 39.915, "北京");
redisUtil.geoAdd(key, 121.474, 31.230, "上海");
redisUtil.geoAdd(key, 113.264, 23.129, "广州");
System.out.println("添加地理位置: 北京, 上海, 广州");
// 获取地理位置
List<Point> positions = redisUtil.geoPos(key, "北京", "上海");
System.out.println("北京位置: " + positions.get(0));
System.out.println("上海位置: " + positions.get(1));
// 计算距离
Distance distance = redisUtil.geoDist(key, "北京", "上海", Metrics.KILOMETERS);
System.out.println("北京到上海距离: " + distance.getValue() + "公里");
// 获取半径范围内的位置
GeoResults<RedisGeoCommands.GeoLocation<Object>> results = redisUtil.geoRadius(
key, 116.404, 39.915, 1000, Metrics.KILOMETERS,
RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeDistance().limit(5));
System.out.println("北京1000公里范围内的城市:");
results.forEach(geoResult -> {
RedisGeoCommands.GeoLocation<Object> location = geoResult.getContent();
System.out.println(location.getName() + " – 距离: " + geoResult.getDistance().getValue() + "公里");
});
// 清理
redisUtil.delete(key);
}
@Test
// ==================== HyperLogLog 类型示例 ====================
public void hyperLogLogOperationsExample() {
String key = "example:hll:uv";
System.out.println("\\n===== HyperLogLog 操作示例 =====");
// 添加元素
redisUtil.pfAdd(key, "user1", "user2", "user3", "user4", "user5");
System.out.println("添加5个用户到HyperLogLog");
// 获取基数估算值
long count = redisUtil.pfCount(key);
System.out.println("独立用户数估算: " + count);
// 添加更多元素
redisUtil.pfAdd(key, "user6", "user7", "user3", "user8");
System.out.println("再添加4个用户(其中1个重复)");
// 获取新的基数估算值
long newCount = redisUtil.pfCount(key);
System.out.println("新的独立用户数估算: " + newCount);
// 合并HyperLogLog
String key2 = "example:hll:uv2";
redisUtil.pfAdd(key2, "user9", "user10", "user3");
String mergedKey = "example:hll:uv_merged";
redisUtil.pfMerge(mergedKey, key, key2);
System.out.println("合并后的独立用户数估算: " + redisUtil.pfCount(mergedKey));
// 清理
redisUtil.delete(Arrays.asList(key, key2, mergedKey));
}
}


