Redis从入门到精通(十二)Redis实战(九)GEO查询附近商户、BitMap用户签到和统计、HLL的UV统计

news2025/2/24 23:57:38

↑↑↑请在文章开头处下载测试项目源代码↑↑↑

文章目录

    • 前言
    • 4.10 附近商户
      • 4.10.1 GEO介绍
      • 4.10.2 附近商户需求分析
      • 4.10.3 实现新增商户功能
      • 4.10.4 实现查询附近商户功能
    • 4.11 用户签到
      • 4.11.1 用户签到需求分析
      • 4.11.2 BitMap介绍
      • 4.11.3 实现用户签到
      • 4.11.4 实现用户签到统计
        • 4.11.4.1 需求分析
        • 4.11.4.2 代码实现
        • 4.11.4.3 功能测试
    • 4.12 UV统计和PV统计
      • 4.12.1 功能介绍
      • 4.12.2 HyperLogLog介绍
      • 4.12.3 测试百万数据的统计
    • 4.13 小结

前言

Redis实战系列文章:

Redis从入门到精通(四)Redis实战(一)短信登录
Redis从入门到精通(五)Redis实战(二)商户查询缓存
Redis从入门到精通(六)Redis实战(三)优惠券秒杀
Redis从入门到精通(七)Redis实战(四)库存超卖、一人一单与Redis分布式锁
Redis从入门到精通(八)Redis实战(五)分布式锁误删与原子性问题、Redisson
Redis从入门到精通(九)Redis实战(六)基于Redis队列实现异步秒杀下单
Redis从入门到精通(十)Redis实战(七)达人探店、点赞与点赞排行榜
Redis从入门到精通(十一)Redis实战(八)关注、共同关注和Feed流

4.10 附近商户

4.10.1 GEO介绍

GEO是Geolocation的简写形式,代表地理坐标。Redis在3.2版本中加入了对GEO的支持,允许存储地理坐标信息,帮助我们根据经纬度来检索数据。

GEO常见的命令有:

  • GEOADD key longitude latitude member

添加一个地理坐标信息,包含经度(longitude)、纬度(latitude)、值(member)。

127.0.0.1:6379> GEOADD test:geo 123.456 45.67 1
(integer) 1
127.0.0.1:6379> GEOADD test:geo 111.123 8.67 2
(integer) 1

查看Redis中的数据:

可见在Redis底层,GEO地理坐标信息是用SortedSet数据结构存储的,经纬度经过计算可以转换为唯一的score。

  • GEODIST key member1 member2

计算指定的两个点之间的距离并返回(默认单位:米)。

127.0.0.1:6379> GEODIST test:geo 1 2
"4281337.5859"
  • GEOHASH key member

将指定member的坐标转为hash字符串形式并返回。

127.0.0.1:6379> GEOHASH test:geo 1
1) "y8pgc3czj20"
  • GEOPOS key member

返回指定member的坐标。

127.0.0.1:6379> GEOPOS test:geo 1
1) 1) "123.45600038766860962"
   2) "45.66999878325155748"
  • GEOSEARCH key <FROMMEMBER member | FROMLONLAT longitude latitude> <BYRADIUS radius <M | KM | FT | MI> | BYBOX width height <M | KM | FT | MI>> [ASC | DESC] [COUNT count [ANY]] [WITHCOORD] [WITHDIST] [WITHHASH]

6.2版本新功能。在指定范围内搜索member,并按照与指定点之间的距离排序后返回。

范围中心可以指定一个member(FROMMEMBER)或者经纬度(FROMLONLAT)。
范围可以按按圆形(BYRADIUS)或矩形(BYBOX)。
排序可以按升序(ASC)或降序(DESC)。

# 获取以经纬度为(122.456,44.67)的地点为圆心,半径为10000km的圆形区域内的member,并显示距离
127.0.0.1:6379> GEOSEARCH test:geo FROMLONLAT 122.456 44.67 BYRADIUS 10000 km DESC WITHDIST
1) 1) "2"
   2) "4150.4721"
2) 1) "1"
   2) "136.0864"

4.10.2 附近商户需求分析

当我们点外卖时,进入美食页面后,会出现一系列商家,这些商家可以按照多种方式进行排序,其中就有根据距离进行排序。

根据距离进行排序,就用到了Redis的GEO。前端页面根据收集到的设备位置信息,调用后台接口,后台接口以该位置为中心,同时根据商户类型、分页信息等,查询出一定范围内的商户信息,排序后并返回。

在本项目中,添加商户信息时,除了把商户信息写入数据库,还要将商户信息中的商户类型、位置信息等写入到Redis中。那么在查询商户列表时,根据当前位置信息、商户类型、分页信息等条件查询数据即可。

4.10.3 实现新增商户功能

在ShopController类中编写一个add()方法,用于新增商户。其接口文档和代码如下:

项目说明
请求方法POST
请求路径/shop/add
请求参数shop:Shop类型,商户信息
返回值
// com.star.redis.dzdp.controller.ShopController

/**
 * 新增商户
 * @author hsgx
 * @since 2024/4/9 14:24
 * @param shop
 * @return com.star.redis.dzdp.pojo.BaseResult
 */
@PostMapping("/add")
public BaseResult add(@RequestBody Shop shop) {
    return shopService.addShop(shop);
}

然后在IShopService接口定义一个addShop()方法,并在ShopServiceImpl实现类中具体实现:

// com.star.redis.dzdp.service.impl.ShopServiceImpl

@Override
public BaseResult addShop(Shop shop) {
    log.info("add {}", shop.toString());
    // 1.保存商户信息
    boolean save = save(shop);
    if(save) {
        // 2.将商户的位置信息存入GEO
        // GEOADD shop:geo:{typeId} {x} {y} {id}
        String key = "shop:geo:" + shop.getTypeId();
        stringRedisTemplate.opsForGeo().add(key, new Point(shop.getX(), shop.getY()),
                shop.getId().toString());
        log.info("GEOADD {} {} {} {}", key, shop.getX(), shop.getY(), shop.getId());
        return BaseResult.setOk("添加商户成功!");
    }
    return BaseResult.setFail("添加商户失败!");
}

编写完成后进行功能测试:

此时Redis中保存的数据:

4.10.4 实现查询附近商户功能

在ShopController类中编写一个list()方法,用于查询附近商户列表。其接口文档和代码如下:

项目说明
请求方法GET
请求路径/shop/list
请求参数typeId:Integer,商户类型
current:Integer,当前页数
x:Double,经度
y:Double,纬度
返回值List<Shop>,符合条件的商户列表
// com.star.redis.dzdp.controller.ShopController

/**
 * 查询商户列表
 * @author xiaowd
 * @since 2024/4/9 16:15
 * @param typeId 商户类型
 * @param current 当前页数
 * @param x 经度
 * @param y 纬度
 * @return com.star.redis.dzdp.pojo.BaseResult<java.util.List<com.star.redis.dzdp.pojo.Shop>>
 */
@GetMapping("/list")
public BaseResult<List<Shop>> list(Integer typeId, Integer current,
                                   Double x, Double y) {
    return shopService.queryNearbyShops(typeId, current, x, y);
}

然后在IShopService接口定义一个queryNearbyShops()方法,并在ShopServiceImpl实现类中具体实现:

// com.star.redis.dzdp.service.impl.ShopServiceImpl

@Override
public BaseResult<List<Shop>> queryNearbyShops(Integer typeId, Integer current, Double x, Double y) {
    log.info("query Shops: typeId = {}, current = {}, x = {}, y = {}",
            typeId, current, x, y);
    // 1.判断是否需要根据坐标查询
    if(x == null || y == null) {
        // 不需要坐标查询,则直接按数据库查询(默认查5条,可以自定义)
        Page<Shop> shopPage = query().eq("type_id", typeId)
                .page(new Page<>(current, 5));
        // 返回数据
        return BaseResult.setOkWithData(shopPage.getRecords());
    }
    // 2.需要根据坐标查询,计算分页参数
    int from = (current - 1) * 5;
    int end = current * 5;
    log.info("from = {}, end = {}", from, end);
    // 3.查询Redis,按照距离排序
    // GEOSEARCH key FROMLONLAT x y BYRADIUS 5000 m WITHDIST
    String key = "shop:geo:" + typeId;
    GeoResults<RedisGeoCommands.GeoLocation<String>> results = stringRedisTemplate.opsForGeo().search(key, GeoReference.fromCoordinate(x, y),
            new Distance(2000),
            RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs().includeDistance().limit(end));
    log.info("GEOSEARCH {} FROMLONLAT {} {} BYRADIUS 2000 m WITHDIST", key, x, y);
    if(results == null) {
        // 没有查到数据
        return BaseResult.setOkWithData(Collections.emptyList());
    }
    List<GeoResult<RedisGeoCommands.GeoLocation<String>>> list = results.getContent();
    log.info("list form GEO : {}", list.size());
    // 数据总数都达不到开始序号,说明这一页没有数据
    if(list.size() <= from) {
        return BaseResult.setOkWithData(Collections.emptyList());
    }
    // 4.解析数据
    List<Long> ids = new ArrayList<>(list.size());
    Map<String, Distance> distanceMap = new HashMap<>(list.size());
    // 跳过前面from条数据
    list.stream().skip(from).forEach(result -> {
        // 解析商户ID
        String shopIdStr = result.getContent().getName();
        ids.add(Long.valueOf(shopIdStr));
        // 解析商户距离
        Distance distance = result.getDistance();
        distanceMap.put(shopIdStr, distance);
    });
    // 5.根据商户ID查询商户数据
    String idStr = StrUtil.join(",", ids);
    List<Shop> shopList = query().in("id", ids).last("ORDER BY FIELD(id," + idStr + ")").list();
    for (Shop shop : shopList) {
        // 设置商户距离
        shop.setDistance(distanceMap.get(shop.getId().toString()).getValue());
    }
    return BaseResult.setOkWithData(shopList);
}

编写完成后进行功能测试:

# 商户类型为1、圆心位置为(120.149,30.31)、半径2000m、查询具体距离
127.0.0.1:6379> GEOSEARCH shop:geo:1 FROMLONLAT 120.149 30.31 BYRADIUS 2000 m WITHDIST
1) 1) "4"
   2) "378.8642"
2) 1) "5"
   2) "845.9276"
3) 1) "1"
   2) "676.2196"
4) 1) "6"
   2) "959.2299"
5) 1) "3"
   2) "1688.9495"
6) 1) "8"
   2) "1700.3408"
7) 1) "9"
   2) "1703.1773"

由以上结果可知,根据以上条件查询第一页数据(默认查5个),可以查询出ID=[4,5,1,6,3]的商户数据:

4.11 用户签到

4.11.1 用户签到需求分析

要实现用户签到功能,完全可以在数据库中创建一张签到表来实现,例如:

CREATE TABLE `tb_sign` (
  `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',
  `user_id` BIGINT(20) UNSIGNED NOT NULL COMMENT '用户id',
  `year` YEAR(4) NOT NULL COMMENT '签到的年',
  `month` TINYINT(2) NOT NULL COMMENT '签到的月',
  `date` DATE NOT NULL COMMENT '签到的日期',
  `is_backup` TINYINT(1) UNSIGNED DEFAULT NULL COMMENT '是否补签',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT;

用户签到一次,就是一条记录。但有一个非常大的问题,假设有1000万人签到,每天签到2次,则这张表一年的数据量就是:1000万×2×200工作日=40亿。每签到一次需要使用(8+8+1+1+3+1)共22字节的空间,那么一年需要使用的空间是:40亿×22KB=880亿KB=85937500MB≈83923GB≈82T。

最大的问题就是数据量太大了,那该如何进行简化呢?

其实签到只是一个状态,要么签了,要么没签。因此,一个字节的8个bit位就可以存储8次签到数据了。我们把一个bit位对应一个月的一天,用0和1标识业务状态,这样就用极小的空间,来实现海量数据的存储。

对应到Redis,可以使用BitMap来实现这种数据存储。

4.11.2 BitMap介绍

BitMap的命令主要有:

  • SETBIT key offset value

向指定位置(offset)存入一个0或1。

127.0.0.1:6379> SETBIT test:bit 5 1
(integer) 0

此时Redis中保存的数据是:

可见在Redis底层,是使用String类型来实现BitMap的。

  • GETBIT key offset

获取指定位置(offset)的bit值。

127.0.0.1:6379> GETBIT test:bit 5
(integer) 1
127.0.0.1:6379> GETBIT test:bit 6
(integer) 0
  • BITCOUNT key

统计值为1的bit位的数量。

127.0.0.1:6379> BITCOUNT test:bit
(integer) 1
  • BITFIELD_RO key GET encoding offset

获取bit数组,并以十进制形式返回。

127.0.0.1:6379> BITFIELD_RO test:bit GET i8 1
1) (integer) 8
  • BITOP <AND | OR | XOR | NOT> destkey key [key ...]

将多个BitMap的结果做位运算(与 、或、异或)。

127.0.0.1:6379> SETBIT test:bit2 3 1
(integer) 0
127.0.0.1:6379> BITOP AND test:bit3 test:bit test:bit2
(integer) 1
127.0.0.1:6379> get test:bit3
"\x00"
  • BITPOS key bit

查找bit数组中指定范围内第一个0或1出现的位置。

127.0.0.1:6379> BITPOS test:bit 1
(integer) 5
  • BITFIELD key

操作(查询GET、修改SET、自增INCRBY)BitMap中bit数组中的指定位置(offset)的值。

# 查询GET
127.0.0.1:6379> BITFIELD test:bit GET i8 5
1) (integer) -128
# 修改SET
127.0.0.1:6379> BITFIELD test:bit SET i8 6 1
1) (integer) 0
# 自增INCRBY
127.0.0.1:6379> BITFIELD test:bit INCRBY i8 6 2
1) (integer) 3

4.11.3 实现用户签到

在UserController类中编写一个sign()方法,用于实现用户签到。其接口文档和代码如下:

项目说明
请求方法POST
请求路径/user/sign
请求参数
返回值
// com.star.redis.dzdp.controller.UserController

/**
 * 用户签到
 * @author xiaowd
 * @since 2024/4/9 18:22
 * @param request
 * @return com.star.redis.dzdp.pojo.BaseResult
 */
@PostMapping("/sign")
public BaseResult sign(HttpServletRequest request) {
    Long userId = (Long) request.getAttribute("userId");
    return userService.userSign(userId);
}

然后在IUserService接口定义一个userSign()方法,并在UserServiceImpl实现类中具体实现:

// com.star.redis.dzdp.service.impl.UserServiceImpl

@Resource
private StringRedisTemplate stringRedisTemplate;

private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMM");

@Override
public BaseResult userSign(Long userId) {
    log.info("userSign: userId = {}", userId);
    // 1.获取日期
    Date date = new Date();
    String keySuffix = sdf.format(date);
    // 2.拼接key
    String key = "sign:" + userId + ":" + keySuffix;
    int dayOfMonth = date.getDay();
    // 3.写入Redis:SETBIT key offset 1
    stringRedisTemplate.opsForValue().setBit(key, dayOfMonth - 1, true);
    log.info("SETBIT {} {} 1", key, dayOfMonth - 1);
    return BaseResult.setOk("签到成功!");
}

编写完成后进行功能测试:

此时Redis中的数据为:

4.11.4 实现用户签到统计

4.11.4.1 需求分析

下面有一个需求:统计用户本月的签到天数。

我们可以从以下几个问题来考虑:

问题1:如果得到本月签到天数?

获得当前月的最后一次签到数据,定义一个计数器,然后不停的向前统计,每得到一个非0的数字则计数器+1,每得到一个为0的数字则跳过,这样就可以获得当前月的签到总天数了。

问题2:如何得到本月到今天为止的所有签到数据?

假设今天是10号,那么BITFIELD key GET u10 0命令就表示从第0位开始,向右读取10位,那就得到了这10天的签到数据。

(PS:这篇文从9号写到了10号)

问题3:如何从后往前遍历每个bit位?

判断某一天的数据是0还是1,只需要和1做与运算。 由于1&1=1、0&1=0,因此结果为1则表示已签到,结果为0则表示未签到。

BITFIELD命令返回的数据是十进制的,如果将这个十进制数和1做与运算,则得到这个十进制数所对应的二进制数的最后一个bit位的数据。

例如:十进制8对应的二进制是1000,最后一个bit位为0,由于0&0=0。因此8&1=0;
十进制9对应的二进制是1001,最后一个bit位为1,由于1&0=1,因此9&1=1。

综上,从后往前遍历每个bit位的逻辑如下:让得到的十进制数与1做与运算,每与一次,就把数据向右移动一位,则参与下一次与运算的bit位就是原来的第2位,以此类推就完成了逐个遍历的效果。 例如:

public static void main(String[] args) {
    int num = 203; // 对应的二进制是:1100 1011
    for(int i = 0; i < 8; i++) {
        // 打印每个bit位和1做与运算的结果
        System.out.println(num & 1);
        // 右移一位
        num >>>= 1;
    }
}

程序执行结果是:

1
1
0
1
0
0
1
1
4.11.4.2 代码实现

方案敲定之后,下面来实现用户签到统计功能。在UserController类中编写一个signCount()方法,其接口文档和代码如下:

项目说明
请求方法POST
请求路径/user/sign/count
请求参数
返回值Long,本月签到天数
// com.star.redis.dzdp.controller.UserController

/**
 * 用户签到统计
 * @author xiaowd
 * @since 2024/4/10 9:40
 * @param request
 * @return com.star.redis.dzdp.pojo.BaseResult<java.lang.Integer>
 */
@GetMapping("/sign/count")
public BaseResult<Integer> signCount(HttpServletRequest request) {
    Long userId = (Long) request.getAttribute("userId");
    return userService.userSignCount(userId);
}

然后在ISserService接口中定义一个userSignCount()方法,并在UserServiceImpl实现类中具体实现:

// com.star.redis.dzdp.service.impl.UserServiceImpl

@Override
public BaseResult<Integer> userSignCount(Long userId) {
    log.info("userSignCount: userId = {}", userId);
    // 1.获取日期,拼接Key
    LocalDateTime now = LocalDateTime.now();
    String keySuffix = now.format(DateTimeFormatter.ofPattern("yyyyMM"));
    String key = "sign:" + userId + ":" + keySuffix;
    int dayOfMonth = now.getDayOfMonth();
    // 2.从Redis中获取本月到今天为止的所有签到数据,返回的是一个十进制数
    // BITFIELD sign:1012:202404 GET u10 0
    List<Long> result = stringRedisTemplate.opsForValue().bitField(key,
            BitFieldSubCommands.create()
                    .get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0));
    log.info("call Redis: BITFIELD {} GET u{} 0", key, dayOfMonth);
    if(result == null || result.isEmpty()) {
        log.info("call Redis result: null or empty.");
        return BaseResult.setOkWithData(0);
    }
    Long num = result.get(0);
    log.info("call Redis result: num = {}", num);
    if(num == null || num == 0) {
        return BaseResult.setOkWithData(0);
    }
    // 3.循环遍历每一个bit位
    int count = 0;
    for(int i = 0; i < dayOfMonth; i++) {
        // 十进制数和1做与运算,得到十进制数对应的二进制数的最后一个bit位
        if((num & 1) == 1) {
            // 如果不为0,说明已签到,计数器+1
            count++;
        }
        // 右移一位
        num >>>= 1;
    }
    log.info("count = {}", count);
    return BaseResult.setOkWithData(count);
}
4.11.4.3 功能测试

在Redis中已经保存了这样数据:

现在是4月10号,因此BITFIELD将获取前面10位:0100101010,转换为十进制数是298,其中1的个数为4,也就是签到总天数为4天。

日志如下:

[http-nio-8081-exec-1] userSignCount: userId = 1012
[http-nio-8081-exec-1] call Redis: BITFIELD sign:1012:202404 GET u10 0
[http-nio-8081-exec-1] call Redis result: num = 298
[http-nio-8081-exec-1] count = 4

可见,用户签到统计功能已实现。

4.12 UV统计和PV统计

4.12.1 功能介绍

  • UV:全称Unique Visitor,也叫独立访客量,是指通过互联网访问、浏览某个网页的自然人。一天内同一个用户多次访问该网站,只记录1次。

  • PV:全称Page View,也叫页面访问量或点击量,用户每访问网站的一个页面,记录1次PV,用户多次打开页面,则记录多次PV。往往用来衡量网站的流量。

UV统计和PV在服务端做会比较麻烦,因为要判断该用户是否已经统计过了,需要将统计过的用户信息保存。如果每个访问的用户都保存到Redis中,数据量则会非常大,那怎么处理呢?

可以使用Redis的HyperLogLog类实现。

4.12.2 HyperLogLog介绍

HyperLogLog(HLL),是LogLog算法的升级版,作用是能够提供不精确的去重计数。相关算法原理可以参考:https://juejin.cn/post/6844903785744056333#heading-0

HyperLogLog的主要命令有:

  • PFADD key element [element ...]:添加数据
  • PFCOUNT key:返回数据个数
  • PFMERGE destkey sourcekey1 sourcekey2:合并数据到指定key

4.12.3 测试百万数据的统计

下面直接利用单元测试,向HyperLogLog中添加100万条数据,看看内存占用和统计效果如何:

@Test
public void testHLL() {
    String[] users = new String[1000];
    int index = 0;
    for (int i = 1; i <= 1000000; i++) {
        users[index++] = "user_" + i;
        // 每1000条保存一次
        if(i % 1000 == 0) {
            index = 0;
            stringRedisTemplate.opsForHyperLogLog().add("test:hll", users);
        }
    }
    // 统计数量
    Long size = stringRedisTemplate.opsForHyperLogLog().size("test:hll");
    System.out.println("size = " + size);
}

程序运行结果:

size = 997593

此时在Redis中保存的数据如下:

由此可见,Redis中的HLL是基于String结构实现的。即使保存了100万数据,占用内存也只有12.02KB,但作为代价,其测量结果是有误差的,如上面的单元测试只统计出了997593条数据,不足100万。不过对于UV统计来说,这完全可以忽略。

4.13 小结

第4章到此就学习完毕了,本章的主题是:Redis实战项目。回顾一下本章的学习的内容:

(四)使用String结构保存短信验证码和登录用户信息。
(五)使用String结构保存商户信息的JSON字符串,并解决数据一致性问题、缓存穿透问题;利用SETNX方法设置互斥锁,解决缓存击穿问题。
(六)使用String结构保存秒杀优惠券的库存,用于判断库存是否充足。
(七)解决库存超卖问题;利用SETNX方法实现Redis分布式锁,处理一人一单问题。
(八)使用Lua脚本保证Redis命令的原子性;使用分布式锁-Redisson。
(九)使用基于Stream的消息队列实现异步秒杀下单。
(十)使用Set集合解决用户无限点赞问题;使用SortedSet集合实现点赞排行榜功能。
(十一)使用Set集合的交集实现共同关注好友功能;使用SortedSet集合实现Feed流功能。
(十二)使用GEO实现附近商户查询功能;使用BitMap实现用户签到和统计功能;使用HLL实现UV统计功能。

更多内容请查阅分类专栏:Redis从入门到精通

感兴趣的读者还可以查阅我的另外几个专栏:

  • SpringBoot源码解读与原理分析(已完结)
  • MyBatis3源码深度解析(已完结)
  • 再探Java为面试赋能(持续更新中…)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1585845.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vuforia AR篇(二)— 扫描指定图片播放视频

目录 一、 使用Vuforia SDK创建Vuforia账号下载Vuforia SDK包导入SDK到unity中 二、使用Vuforia扫描指定图片播放视频创建ARCamera创建 License创建ImageTarget生成识别数据库播放视频 三、 效果 一、 使用Vuforia SDK 创建Vuforia账号 Vuforia官网 登录官网创建一个账号&am…

【fiddler】弱网测试

目录 一、测试目的 二、步骤 2.1打开弱网模式 ​ 2.2设置网络参数 &#xff08;1&#xff09;打开Rules→Customize Rules&#xff1b; &#xff08;2&#xff09;找到下面框出的代码&#xff0c;在这里设置弱网参数值&#xff1b; &#xff08;3&#xff09;设置完成后&a…

【每日刷题】Day10

【每日刷题】Day10 &#x1f955;个人主页&#xff1a;开敲&#x1f349; &#x1f525;所属专栏&#xff1a;每日刷题&#x1f345; 目录 1. 环形链表的约瑟夫问题_牛客题霸_牛客网 (nowcoder.com) 2. 21. 合并两个有序链表 - 力扣&#xff08;LeetCode&#xff09; 3. 152…

【MATLAB源码-第9期】基于matlab的DQPSK的误码率BER和误符号率SER仿真。

1、算法描述 DQPSK信号的解调与2DPSK信号的解调类似&#xff0c;也有两种方法&#xff0c;分别是极性比较法和相位比较法 极性比较法。其原理方框图如下图所示。由于DQPSK信号可以看做是两路2DPSK信号的合成&#xff0c;解 调时也可以分别按两路2DPSK信号解调&#xff0c;因此…

Docker部署SpringBoo+Vue前后端分离项目

文章目录 1. 安装Docker1. 1 卸载旧版Docker1.2 配置yum仓库1.3 安装Docker1.4 添加自启动配置1.5 配置阿里云镜像加速1.6 测试 2. 安装Nginx2.1 拉取镜像2.2 安装Nginx2.3 测试 3. 安装MySQL3.1 拉取镜像3.2 安装MySQL3.3 连接MySQL 4. 部署SpringBoot项目4.1 Maven打包4.2 编…

java数组.day16(冒泡排序,稀疏数组)

冒泡排序 冒泡排序无疑是最为出名的排序算法之一&#xff0c;总共有八大排序! 冒泡的代码还是相当简单的&#xff0c;两层循环&#xff0c;外层冒泡轮数&#xff0c;里层依次比较&#xff0c;江湖中人人尽皆知。 我们看到嵌套循环&#xff0c;应该立马就可以得出这个算法的时…

VBA信息获取与处理第四节:获取唯一非重复随机值的返回数组

《VBA信息获取与处理》教程(版权10178984)是我推出第六套教程&#xff0c;目前已经是第一版修订了。这套教程定位于最高级&#xff0c;是学完初级&#xff0c;中级后的教程。这部教程给大家讲解的内容有&#xff1a;跨应用程序信息获得、随机信息的利用、电子邮件的发送、VBA互…

001-NodeJs全局对象

概念 node是一个运行js的平台&#xff0c;在node中&#xff0c;用global对象取代了Window这个对象。 node中的repl环境可以执行js,通过命令node进入到repl环境。repl环境类似于Chrome的开发人员工具。 全局对象global 可以参考一下它的文档global全局对象 node版本介绍&am…

C++ stl容器vector的底层模拟实现

目录 前言&#xff1a; 1.成员变量&#xff0c;容量与大小 2.构造函数 无参构造&#xff1a; 带参的使用值进行构造&#xff1a; 使用迭代器区间进行构造&#xff1a; 3.交换 4.拷贝构造 5.赋值重载 6.迭代器 7.扩容 reserve&#xff1a; resize&#xff1a; 8.…

深入OceanBase内部机制:系统架构与组件精讲

码到三十五 &#xff1a; 个人主页 心中有诗画&#xff0c;指尖舞代码&#xff0c;目光览世界&#xff0c;步履越千山&#xff0c;人间尽值得 ! 目录 1️⃣OceanBase 整体架构1.1 分区1.2 分片1.3 日志流1.4 对等节点1.5 多租户 2️⃣OceanBase 架构与组件详解2.1 存储层2.2 …

公司电脑如何对文件进行加密?

在现代企业中&#xff0c;文件加密是确保敏感数据安全的关键。使用华企盾DSC数据安全防泄密系统&#xff0c;公司电脑可以轻松地对文件进行加密&#xff0c;以防止未授权的访问和数据泄露。以下是对文件进行加密的步骤和方法&#xff1a; 智能半透明加密&#xff1a;这种模式允…

【java数据结构-二叉树(上)】

java数据结构-二叉树&#xff08;上&#xff09; 二叉树的概念二叉树的节点介绍 二叉树构造如何使用兄弟表示法构造二叉树两种特别的二叉树二叉树的基本性质&#xff1a; 二叉树的存储二叉树的遍历&#xff1a;前序遍历&#xff1a;中序遍历&#xff1a;后序遍历&#xff1a;层…

pygame发射子弹后绘制射线

import pygame import sys import mathpygame.init()screen pygame.display.set_mode((800, 600)) pygame.display.set_caption("Rotate and Shoot Bullets")# 定义子弹类 class Bullet:def __init__(self, x, y, angle):self.x xself.y yself.angle angleself.s…

python中的异常

1、NoSuchElementException 找不到元素 2、ElementNotInteractableException 元素无法交互 可能原因1&#xff1a;元素定位到以后&#xff0c;无法点击---元素未渲染完 解決&#xff1a;使用expected_conditions模块下的element_to_be_clickable来判断元素是否可被点击&#…

【MATLAB源码-第8期】基于matlab的DPSK的误码率仿真,差分编码使用汉明码(hanming)。

1、算法描述 差分相移键控常称为二相相对调相&#xff0c;记作2DPSK。它不是利用载波相位的绝对数值传送数字信息&#xff0c;而是用前后码元的相对载波相位值传送数字信息。所谓相对载波相位是指本码元初相与前一码元初相之差。差分相移键控信号的波形如概述图所示。 假设相对…

JavaEE 初阶篇-深入了解定时器、工厂模式和比较器

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 定时器概述 2.0 实现定时器 2.1 实现定时器 - 定义 MyTask 任务类 2.2 实现定时器 - MyTimer 定时器类存放任务的方法 2.3 实现定时器 - MyTimer 定时器类读取任务…

群晖虚拟机搭建Synology Drive并实现Obsidian笔记异地多端同步

文章目录 一、简介软件特色演示&#xff1a; 二、使用免费群晖虚拟机搭建群晖Synology Drive服务&#xff0c;实现局域网同步1 安装并设置Synology Drive套件2 局域网内同步文件测试 三、内网穿透群晖Synology Drive&#xff0c;实现异地多端同步Windows 安装 Cpolar步骤&#…

二叉树应用——最优二叉树(Huffman树)、贪心算法—— Huffman编码

1、外部带权外部路径长度、Huffman树 从图中可以看出&#xff0c;深度越浅的叶子结点权重越大&#xff0c;深度越深的叶子结点权重越小的话&#xff0c;得出的带权外部路径长度越小。 Huffman树就是使得外部带权路径最小的二叉树 2、如何构造Huffman树 &#xff08;1&#xf…

InternlM2

第一次作业 基础作业 进阶作业 1. hugging face下载 2. 部署 首先&#xff0c;从github上git clone仓库 https://github.com/InternLM/InternLM-XComposer.git然后里面的指引安装环境

鸿蒙4.2开放升级,首批公测包含24款机型,快来升级尝鲜

在今天下午刚刚结束的鸿蒙生态春季沟通会上&#xff0c;余承东提到了一个关于鸿蒙系统的细节&#xff0c;那就是目前top5000的App里&#xff0c;已经有4000多款App确定加入。 如此量级&#xff0c;预测基本能够覆盖常用的应用了&#xff0c;此前担心的应用生态问题也得到解决&a…