Redis 实战篇 ——《黑马点评》(下)

news2025/4/8 3:30:13

《引言》

        (下)篇将记录 Redis 实战篇 最后的一些学习内容,希望大家能够点赞、收藏支持一下 Thanks♪ (・ω・)ノ,谢谢大家。

传送门(上):Redis 实战篇 ——《黑马点评》(上)

传送门(中):Redis 实战篇 ——《黑马点评》(中)

传送门(下):当-前-页


四、好友关注

1.关注和取关

        在业务中,用户之间存在着常见的关注功能,通过关注 up主来及时的获取其更新的内容。我们可以关注取关对应的用户。

        想要实现关注与取关功能,首先会在访问页面时查询当前用户与展示的用户之间的关系并展示出来,点击后根据现有关系发送请求进行关注或是取关。

public Result isFollow(Long followUserId) {
    Long userId = UserHolder.getUser().getId();
    Integer count = query().eq("user_id", userId).eq("follow_user_id", followUserId).count();
    return Result.ok(count > 0);
}

        查询用户之间的关系,先获取当前用户的 Id,通过其进行查询得到个数,如果查询得到的个数大于 0 则存在返回 true 表示状态为已关注,反之则为 false 表示未关注。

public Result follow(Long followUserId, Boolean isFollow) {
    Long userId = UserHolder.getUser().getId();
    if (isFollow){
        //关注
        Follow follow = new Follow();
        follow.setUserId(userId);
        follow.setFollowUserId(followUserId);
        boolean isSuccess = save(follow);
    }else {
        //取消关注
        remove(new QueryWrapper<Follow>()
                .eq("user_id",userId).eq("follow_user_id",followUserId));
    }
    return Result.ok();
}

        该方法用于进行关注与取关操作,当点击按钮发送请求后,根据传来的 isFollow 值来判断当前进行的操作是关注或是取关。关注操作需要现将其存入数据库中,取关就是根据 Id 从数据库中删除相应的数据。

         点击关注后效果如下所示,上方提示操作成功,同时按钮状态变为取消关注,再次点击后取消关注


2.共同关注

        想要实现查看共同关注功能,需要先完善查看其他用户主页的功能,其主要通过查询用户及其发布的博客来展示在用户的主页上。

@GetMapping("/{id}")
public Result queryUserById(@PathVariable("id") Long userId){
    // 查询详情
    User user = userService.getById(userId);
    if (user == null) {
        return Result.ok();
    }
    UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
    // 返回
    return Result.ok(userDTO);
}

        根据用户 Id 查询用户的信息并返回。

@GetMapping("/of/user")
public Result queryBlogByUserId(
        @RequestParam(value = "current", defaultValue = "1") Integer current,
        @RequestParam("id") Long id) {
    // 根据用户查询
    Page<Blog> page = blogService.query()
            .eq("user_id", id).page(new Page<>(current, SystemConstants.MAX_PAGE_SIZE));
    // 获取当前页数据
    List<Blog> records = page.getRecords();
    return Result.ok(records);
}

        根据用户 Id 查询用户的博客 信息并返回。

点击其他用户头像可以查看其个人信息,效果如下所示

        接下来,就需要实现查看共同关注的功能。而我们可以借助 Redis 中 Set 数据类型的 SINTER 方法来查看两个 Set 集合中的交集部分,我们利用这一点,就可以简单地实现共同关注功能。

        首先,我们需要在关注用户时将信息同步的存入到 Redis 的对应用户的集合中,其中存入 Redis 中的 key 前缀为 follows 后接当前用户 Id 以作区分 。所以在原先的关注功能中关注后存入 Redis 中,同样的,在取消关注的操作成功后,将 Redis 中的数据也删除掉。

public Result follow(Long followUserId, Boolean isFollow) {
    Long userId = UserHolder.getUser().getId();
    String followKey = "follows:" + userId;
    if (isFollow){
        //关注
        Follow follow = new Follow();
        follow.setUserId(userId);
        follow.setFollowUserId(followUserId);
        boolean isSuccess = save(follow);
        if(isSuccess){
            //关注成功后,存入 Redis 中
            stringRedisTemplate.opsForSet().add(followKey,followUserId.toString());
        }
    }else {
        //取消关注
        boolean isSuccess = remove(new QueryWrapper<Follow>()
                .eq("user_id",userId).eq("follow_user_id",followUserId));
        if (isSuccess) {
            stringRedisTemplate.opsForSet().remove(followKey, followUserId.toString());
        }
    }
    return Result.ok();
}

        接着,我们就要去实现查询共同关注的功能:

public Result followCommons(Long id) {
    //获取当前用户
    Long userId = UserHolder.getUser().getId();
    String key = "follows:" + userId;
    String key2 = "follows:" + id;
    //求交集
    Set<String> intersect = stringRedisTemplate.opsForSet().intersect(key, key2);
    if (intersect == null || intersect.isEmpty()){
        return Result.ok(Collections.emptyList());
    }
    //解析 Id
    List<Long> ids = intersect.stream().map(Long::valueOf).collect(Collectors.toList());
    //查询用户
    List<UserDTO> users = userService.listByIds(ids)
            .stream()
            .map(user -> BeanUtil.copyProperties(user, UserDTO.class))
            .collect(Collectors.toList());
    return Result.ok(users);
}

        获取当前用户 Id 后,拼接对应的字符串,分别对应自己与正在查看的用户,后用 intersect 方法求取交集,由于其中可能包含多个 Id,通过 stream 流的方式可以高效便捷的处理查询得到的 Id 并通过其进行查询得到对应的用户信息返回,用于展示在页面上。

最终效果如下所示


3.关注推送

        在我们关注某一用户后,当其再次发布新的博客后,系统应将其推送给所有关注他的用户,例如微信的朋友圈。这种方式也称作 Feed 流,可以将内容自动推送给用户。Feed 流种常见的模式:

① TimeLine:对内容进行简单的筛选,按照内容发布时间排序,常用于好友或关注。例如朋友圈

  • 优点:信息全面,不会有缺失。并且实现也相对简单
  • 缺点:信息噪音较多,用户不一定感兴趣,内容获取效率低

② 智能排序:利用智能算法屏蔽掉违规的、用户不感兴趣的内容,推送用户感兴趣信息来吸引用户。例如抖音

  • 优点:投喂用户感兴趣信息,用户粘度很高,容易沉迷
  • 缺点:如果算法不精准,可能起到反作用

        这里选择使用 TimeLine 模式来实现关注后推送最新博客的功能,而这种模式的实现方式又分为种:

  • 拉模式:也称做读扩散,被关注者会将消息先存入发件箱中,关注者获取消息时会从发件箱中拉取消息到收件箱中。但这种模式在用户关注大量用户的情况下拉取消息时的延迟较大。
  • 推模式:也称作写扩散,被关注者会直接将消息推送到关注者的收件箱中,关注者只需查看收件箱即可。但这种模式在用户被大量用户关注的情况下会发送大量消息,导致内存的大量消耗。适合用户量少,没有大量粉丝的用户的场景。
  • 推拉结合模式:根据具体的情况来分配给不同用户不同的模式。对于访问频繁、活跃的用户,对其使用推模式,保证其第一时间获取消息;对于不经常活跃的用户,对其使用拉模式,只有在其登录上线时主动的拉取发件箱的消息到收件箱中。适合用户量庞大,存在拥有大量粉丝的用户的场景。

        在了解了上述的三种实现方式的优劣后,选择通过推模式来实现关注推送的功能。而在选择 Redis 中的数据结构来保存消息时,尤其是消息会随着时间不断的更新的情况下,不会选择使用 List 而是使用 SortedSet,因为 List 不支持滚动分页查询,其只支持角标或是首尾查询;而 SortedSet 可以通过分数(score)来排序,支持范围查询和分页查询,更适合处理消息随时间更新的情况。

        ● 第一步:需要先改造原先的发布博客的部分代码,在发布后将对应博客的 Id 发送给所有粉丝来实现推送。

public Result saveBlog(Blog blog) {
    // 1.获取登录用户
    UserDTO user = UserHolder.getUser();
    blog.setUserId(user.getId());
    // 2.保存探店博文
    boolean isSuccess = save(blog);
    if (!isSuccess){
        return Result.fail("新增笔记失败");
    }
    // 3.查询笔记作者的所有粉丝
    List<Follow> follows = followService.query().eq("follow_user_id", user.getId()).list();
    // 4.推送笔记
    for (Follow follow : follows) {
        Long userId = follow.getId();
        String key = "feed:" + userId;
        stringRedisTemplate.opsForZSet().add(key, blog.getId().toString(), System.currentTimeMill
    }
    // 5.返回id
    return Result.ok(blog.getId());
}

        首先,获取当前登录用户的 Id 并保存博客信息。接着查询当前用户的所用粉丝信息,循环推送给每一个粉丝。其中 key 为 feed:+ 粉丝 Id 组成,对应每一个粉丝都有一个收件箱,value 为博客的 Id,分数为当前的时间戳,便于在展示时进行排序。 

        ● 第二步:在粉丝查询关注的用户更新的博客时,要实现对于博客内容的分页查询,而由于收件箱中的博客是按照时间来进行排序的,每当有新的内容时,按照角标查询得到的内容可能就会出现重复内容的错误,所以就需要通过分数来进行查询,记录每次查询的最后位置,从该位置继续查询,这样就避免了出现重复内容的错误。

Redis 中的对应指令

ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]

max:分数的最大值

min:分数的最小值

[WITHSCORES]:可选,查询内容是否带上分数

LIMIT:

        offset:偏移量,从小于等于最大值的第 N + 1 个元素开始查。

        count:查询的数量

        其中,我们只需关注 max offset 这两个参数即可。min 默认为 0count 按照规定确定。max 是当前查询时的时间戳,之后的查询中为上一次查询的数据中最小的得分offset 在第一次查询时为 0之后的查询中为 1,表示跳过上次查询的最后一个数据,同时如果出现查询出来得分相同的数据这种状况时,重复出现 n 次,偏移量(offset)就为 n 次。

@Data
public class ScrollResult {
    private List<?> list;
    private Long minTime;
    private Integer offset;
}

        首先,创建一个类来存储用于返回滚动分页查询的数据。

@Override
public Result queryBlogOfFollow(Long max, Integer offset) {
    //1.获取当前用户
    Long userId = UserHolder.getUser().getId();
    //2.查询收件箱
    String key = FEED_KEY + userId;
    Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet()
            .reverseRangeByScoreWithScores(key, 0, max, offset, 3);
    //3.解析数据
    if (typedTuples == null || typedTuples.isEmpty()){
        return Result.ok();
    }
    List<Long> ids = new ArrayList<>(typedTuples.size());
    long minTime = 0;
    int os = 1;
    for (ZSetOperations.TypedTuple<String> tuple : typedTuples) {
        ids.add(Long.valueOf(tuple.getValue()));
        long time = tuple.getScore().longValue();
        if (time == minTime) {
            os++;
        }else {
            minTime = time;
            os = 1;
        }
    }
    //4.根据 Id 查询 blog
    String idStr = StrUtil.join(",", ids);
    List<Blog> blogs = query().in("id", ids)
            .last("order by field(id," + idStr + ")").list();
    for (Blog blog : blogs) {
        //查询笔记状态
        queryBlogUser(blog);
        isBlogLiked(blog);
    }
    //5.封装并返回
    ScrollResult r = new ScrollResult();
    r.setList(blogs);
    r.setOffset(os);
    r.setMinTime(minTime);
    return Result.ok(r);
}

        传入的两个参数分别对应 Redis 命令中的 max offset,获取到当前用户的 id 与前缀进行拼接后查询收件箱得到对应关注用户的更新博客的 id 集合。判断数据非空后对集合进行处理,主要是将其中的 id 存入新的集合中,并用 os 统计其中具有相同最小时间的数据的个数作为下一次查询的 offset

        接着就是根据博客的 id 从数据库中进行查询得到所有博客,但 MP 中普通的 listById 方法是根据 in 来进行查询的,并不能进行排序,所以需要通过 order by 来排序,其中的 idStr 通过 StrUtil 中的 join 方法拼接后得到。

private void queryBlogUser(Blog blog) {
    Long userId = blog.getUserId();
    User user = userService.getById(userId);
    blog.setName(user.getNickName());
    blog.setIcon(user.getIcon());
}
private void isBlogLiked(Blog blog) {
    if (UserHolder.getUser() == null) {
        //用户未登录时无需查询是否点赞
        return;
    }
    Long userId = UserHolder.getUser().getId();
    String key = BLOG_LIKED_KEY + blog.getId();
    Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());
    blog.setIsLike(score != null);
}

        最后,所有的博客的需要进行状态的判断,用于在页面中展示正确的信息,判断后将数据分装进先前定义好的实体类中返回。

最终效果如下所示


五、附近商户

1.Redis 中的 GEO 数据结构

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

  • GEOADD:添加一个地理空间信息,包含:经度(longitude) 、纬度(latitude) 、值(member)
  • GEODIST:计算指定的两个点之间的距离并返回
  • GEOHASH:将指定 member 的坐标转为hash字符串形式并返回
  • GEOPOS:返回指定 member 的坐标
  • GEORADIUS:指定圆心、半径,找到该圆内包含的所有 member,并按照与圆心之间的距离排序后返回。
  • GEOSEARCH:在指定范围内搜索 member,并按照与指定点之间的距离排序后返回。范围可以是圆形矩形
  • GEOSEARCHSTORE:GEOSEARCH 功能一致,不过可以把结果存储到一个指定的 key。

        利用 Redis 中 GEO 这种数据结构,可以简单的实现查询附近商铺这种与地理位置信息有关的需求,并且可以根据用户当前位置获取附近商铺的信息。


2.附近商户搜索

        在原先的页面中查看商铺时选择根据距离进行查询时就需要根据地理位置来进行搜索,并在每个商铺右边显示出相距的距离大小,我们可以使用 Redis 中的 GEO 来十分简单的实现这个功能。 

@Test
void loadShopData() {
    //1.查询店铺信息
    List<Shop> list = shopService.list();
    //2.根据类型分组,得到类型id与店铺的映射关系(stream流)
    Map<Long, List<Shop>> map = list.stream().collect(Collectors.groupingBy(Shop::getTypeId));
    for (Map.Entry<Long, List<Shop>> entry : map.entrySet()) {
        Long typeId = entry.getKey();
        String key = "shop:geo:" + typeId;
        List<Shop> value = entry.getValue();
        List<RedisGeoCommands.GeoLocation<String>> locations = new ArrayList<>(value.size());
        //3.写入redis
        for (Shop shop : value) {
            locations.add(new RedisGeoCommands.GeoLocation<>(
                    shop.getId().toString(),
                    new Point(shop.getX(), shop.getY())));
        }
        stringRedisTemplate.opsForGeo().add(key, locations);
    }
}

        而我们需要先将数据库中商铺的信息存入 Redis 中,并且根据商铺的类型(typeId)分别进行存储,这样在查询时也避免了需要先分类再进行查询,直接按照类型进行查询即可。

注意:因为版本的问题,Redis 中 GEO 类型的一些命令是 6.2 版本后提供的,所以需要保证 SpringBoot 中 SpringDataRedis 的版本支持这些命令

@Override
public Result queryShopByTypeId(Integer typeId, Integer current, Double x, Double y) {
    // 检查输入坐标是否为null,如果是,则执行类型分页查询
    if (x == null || y == null){
        // 根据类型分页查询
        Page<Shop> page = query()
                .eq("type_id", typeId)
                .page(new Page<>(current, SystemConstants.DEFAULT_PAGE_SIZE));
        // 返回数据
        return Result.ok(page.getRecords());
    }
    // 计算查询的起始和结束范围
    int from = (current - 1) * SystemConstants.DEFAULT_PAGE_SIZE;
    int end = current * SystemConstants.DEFAULT_PAGE_SIZE;
    ...

        在 Impl 层中,首先对传入的坐标进行判断,不为空则接着计算分页的参数。

...
// 拼接Redis地理信息键名
        String key = SHOP_GEO_KEY + typeId;
        // 使用Redis地理信息命令,查询指定坐标附近的商店
        GeoResults<RedisGeoCommands.GeoLocation<String>> results = stringRedisTemplate.opsForGeo()
                .search(key,
                        GeoReference.fromCoordinate(new Point(x, y)),
                        new Distance(5000),
                        RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs().includeDistance().limit(end));
        // 如果查询结果为空,返回空列表
        if (results == null){
            return Result.ok(Collections.emptyList());
        }
        List<GeoResult<RedisGeoCommands.GeoLocation<String>>> list = results.getContent();
        // 检查结果列表是否足够覆盖查询的起始范围
        if (list.size() <= from){
            return Result.ok(Collections.emptyList());
        }
...

        接着,将商铺类型 id 与前缀进行拼接后进行查询得到地理位置信息,其中 Distance 表示查询的范围。对得到的结果进行非空判断,然后检查结果是否小于起始值,因为这个分页查询是先查询出总数据后按照范围取出数据展示,如果起始值大于总数,则会造成程序出错

    ...
    // 初始化商店ID列表和距离映射
    List<Long> ids = new ArrayList<>(list.size());
    Map<String, Distance> distanceMap = new HashMap<>(list.size());
    // 遍历结果列表,提取商店ID和距离信息
    list.stream().skip(from).forEach(result -> {
        String shopIdStr = result.getContent().getName();
        ids.add(Long.valueOf(shopIdStr));
        Distance distance = result.getDistance();
        distanceMap.put(shopIdStr, distance);
    });
    String idStr = StrUtil.join(",", ids);
    // 根据商店ID查询商店信息
    List<Shop> shops = query().in("id", ids).last("ORDER BY FIELD(id," + idStr + ")").list();
    // 为每个商店设置距离信息
    for (Shop shop : shops) {
        shop.setDistance(distanceMap.get(shop.getId().toString()).getValue());
    }
    // 返回
    return Result.ok(shops);
}

         最后将商店 Id 列表和距离初始化后遍历查询结果并将其存入先前初始化好的列表中。再利用 query 方法根据商铺 Id 查询商铺信息,最后将数据返回即可。

最终效果如下所示


六、用户签到

1.BitMap

        签到功能作为一个耳熟能详的功能常常出现在一些游戏和软件中,作为其中的常客,其主要是通过一些签到奖励来激励用户登录。使用数据库实现时其每个用户每天的签到情况会产生大量的数据,而在 Redis 中可以使用 String 类型来实现 BitMap(位图)这种思路,来简单的存储用户的签到信息。

         通过上面的结构,我们可以简单的用少量的内存存储大量的签到信息。、

BitMap的操作命令有:

  • SETBIT:向指定位置(offset)存入一个 0 或 1
  • GETBIT:获取指定位置(offset)的 bit 值
  • BITCOUNT:统计 BitMap 中值为 1 的 bit 位的数量
  • BITFIELD:操作(查询、修改、自增)  BitMap 中 bit 数组中的指定位置(offset)的值
  • BITFIELD_RO:获取 BitMap 中 bit 数组,并以十进制形式返回
  • BITOP:将多个 BitMap 的结果做位运算(与、或、异或)
  • BITPOS:查找 bit 数组中指定范围内第一个 0 或 1 出现的位置

        其中用到的主要是 SETBIT BITFIELD

SETBIT:

        操作如上图所示,需要注意需要选择 Binary 才能看到对应的格式。

BITFIELD:

         其中 type 表示获取的数量,offset 表示从第几个开始。


2.实现签到功能

        通过 Redis 来实现签到功能,不需要任何参数,只需通过用户 Id 当前时间进行拼接后作为 key 来存储即可。

public Result sign() {
    //1.获取当前登录用户
    Long userId = UserHolder.getUser().getId();
    //2.获取日期
    LocalDateTime now = LocalDateTime.now();
    //3.拼接 Key
    String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
    String key = USER_SIGN_KEY + userId + keySuffix;
    //4.获取今天是本月的第几天
    int dayOfMonth = now.getDayOfMonth();
    //5.写入 Redis
    stringRedisTemplate.opsForValue().setBit(key, dayOfMonth - 1, true);
    return Result.ok();
}

        首先获取当前登录用户的 Id 以及当前时间。拼接后再获取今天是本月第几天,用于确定在 BitMap 签到的位置,且注意 getDayOfMonth 方法获取的数是从 1 开始的,而 BitMap 的下标是从 0 开始的,所以方法结果需要 - 1 后再写入。

最终效果如下所示

        发送签到请求后成功的写入到了 Redis 中。注意需要在请求头中添加 authorization 参数否则请求会失败。


3.统计连续签到次数

·        想要统计从本月开始到今天为止的连续签到次数,就需要先先获取本月到今天为止的所有签到信息,然后遍历信息,统计连续签到的次数。其中,可以使用位运算来实现遍历功能,完成连续签到次数的统计。

public Result signCount() {
    //1.获取当前登录用户
    Long userId = UserHolder.getUser().getId();
    //2.获取日期
    LocalDateTime now = LocalDateTime.now();
    //3.拼接 Key
    String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
    String key = USER_SIGN_KEY + userId + keySuffix;
    //4.获取今天是本月的第几天
    int dayOfMonth = now.getDayOfMonth();
    List<Long> result = stringRedisTemplate.opsForValue()
            .bitField(
                    key,
                    BitFieldSubCommands.create()
                            .get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0)
            );
    ...

        首先还是获取到当前登录的用户 Id 和当前时间,拼接后查询从本月的第一天到今天的所有签到信息。其中 bitField 中包含两个参数,一个是 key,一个是子命令。子命令中设置范围

    ...
    if (result == null || result.isEmpty()){
        return Result.ok(0);
    }
    Long num = result.get(0);
    if (num == null){
        return Result.ok(0);
    }
    int count = 0;
    while(true){
        if((num & 1) == 0) {
            //为 0 未签到,结束
            break;
        }else {
            //不为 0 已签到,计数器++
            count++;
        }
        //将数字无符号右移一位抛弃最后一个 bit 位
        num >>>= 1;
    }
    return Result.ok(count);
}

        之后对查询得到的数据进行非空校验,通过后开始遍历。与 1 进行与运算获取最后一位并对其进行判断,为 0 则直接退出循环,不为 0 则计数后将数字右移移除最后一位数继续循环。结束统计后直接返回计数结果。

最终效果如下所示

        测试后可以看到成功统计出了本月的连续签到次数。


七、UV 统计

        首先,需要了解什么是 UV

  • UV:全称 Unique Visitor,也叫独立访客量,是指通过互联网访问、浏览这个网页的自然人。1天内同一个用户多次访问该网站,只记录1次
  • PV:全称Page View,也叫页面访问量或点击量,用户每访问网站的一个页面,记录1次PV,用户多次打开页面,则记录多次PV。往往用来衡量网站的流量。

        想要统计这种大量的数据,就需要使用到 Redis 中的 HyperLogLog 了。其基于 String 类型实现,其占用的内存极小,而代价就是其测量的结果是概率性的,但对于 UV 统计这种大数据量来说误差是可以忽略的。

        根据上图可知,HyperLogLog 不会将重复的内容统计。

后续的测试  o(´^`)o跳过


【下】完结

 传送门(上):Redis 实战篇 ——《黑马点评》(上)

 传送门(中):Redis 实战篇 ——《黑马点评》(中)

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

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

相关文章

蓝桥杯自我复习打卡

总复习&#xff0c;打卡1. 一。排序 1。选段排序 太可恶了&#xff0c;直接全排输出&#xff0c;一个测试点都没过。 AC 首先&#xff0c;这个【l,r】区间一定要包含p,或者q&#xff0c;pq一个都不包含的&#xff0c;[l,r]区间无论怎么变&#xff0c;都对ans没有影响。 其次&…

Java零基础入门笔记:(6)面向对象

前言 本笔记是学习狂神的java教程&#xff0c;建议配合视频&#xff0c;学习体验更佳。 【狂神说Java】Java零基础学习视频通俗易懂_哔哩哔哩_bilibili 第1-2章&#xff1a;Java零基础入门笔记&#xff1a;(1-2)入门&#xff08;简介、基础知识&#xff09;-CSDN博客 第3章…

【3天快速入门WPF】13-MVVM进阶

目录 1. 窗体设置2. 字体图标3. 控件模板4. 页面逻辑4.1. 不使用MVVM4.2. MVVM模式实现本篇我们开发一个基于MVVM的登录页面,用来回顾下之前学习的内容 登录页面如下: 窗体取消了默认的标题栏,调整为带阴影的圆角窗体,左侧放一张登录背景图,右边自绘了一个关闭按钮,文本框…

【MongoDB】在Windows11下安装与使用

官网下载链接&#xff1a;Download MongoDB Community Server 官方参考文档&#xff1a;https://www.mongodb.com/zh-cn/docs/manual/tutorial/install-mongodb-on-windows/#std-label-install-mdb-community-windows 选择custom类型&#xff0c;其他默认 注意&#xff0c;此选…

deepseek使用记录18——文化基因美食篇

子篇&#xff1a;薪火相传的味觉辩证法——从燧人氏到预制菜的文化突围 一、石器时代的启蒙&#xff1a;食物探索中的原始辩证法 在贾湖遗址的陶罐残片上&#xff0c;碳化稻米与蜂蜜的结晶层相互交叠&#xff0c;这是9000年前先民对"甘"与"饱"的首次辩证…

2025学年安徽省职业院校技能大赛 “信息安全管理与评估”赛项 比赛样题任务书

2024-2025 学年广东省职业院校技能大赛 “信息安全管理与评估”赛项 技能测试试卷&#xff08;五&#xff09; 第一部分&#xff1a;网络平台搭建与设备安全防护任务书第二部分&#xff1a;网络安全事件响应、数字取证调查、应用程序安全任务书任务1 &#xff1a;内存取证&…

在 Ansys Maxwell 中分析磁场

在 Ansys Maxwell 中分析磁场 分析磁场的能力对于理解电磁系统至关重要。Ansys Maxwell 为工程师提供了强大的工具&#xff0c;帮助他们探索磁场数据并从中提取有价值的见解。在本指南中&#xff0c;我将深入研究 Ansys Maxwell 中的几种基本技术和方法&#xff0c;以有效地分…

DeepSeek FlashMLA:用技术创新破解大模型落地难题

注&#xff1a;此文章内容均节选自充电了么创始人&#xff0c;CEO兼CTO陈敬雷老师的新书《自然语言处理原理与实战》&#xff08;人工智能科学与技术丛书&#xff09;【陈敬雷编著】【清华大学出版社】 文章目录 DeepSeek大模型技术系列十四DeepSeek大模型技术系列十四》DeepS…

[补充]原码、反、补、移码的转换

近期在学习Java的类型转换的知识&#xff0c;强制类型转换的时候会遇到数据&#xff08;丢失&#xff09;溢出的问题。 最后在IDEA控制台输出的时候&#xff0c;出现了负数。了解了一下强制类型转换在计算机中的原理&#xff0c;随后就复习了一下原码、反、补、移码的转换的知…

安装Node.js

1.打开官网&#xff0c;下载安装包 2.安装过程中&#xff0c;全部默认&#xff0c;next. 3.在安装根目录下&#xff0c;新建两个文件夹【node_cache】和【node_global】 4.检测是否安装成功 打开控制台&#xff0c;node -v, npm -v, 显示版本号。 5.配置环境变量 1>从no…

【力扣】堆相关总结

priority_queue std::priority_queue 是 C 标准库中的一个容器适配器&#xff0c;提供了堆&#xff08;Heap&#xff09;数据结构的功能。它通常用于实现优先队列&#xff0c;允许你高效地插入元素和访问最大或最小元素。 头文件 #include <queue> 基本定义 std::pri…

【前端基础】3、HTML的常用元素(h、p、img、a、iframe、div、span)、不常用元素(strong、i、code、br)

HTML结构 一个HTML包含以下部分&#xff1a; 文档类型声明html元素 head元素body元素 例&#xff08;CSDN&#xff09;&#xff1a; 一、文档类型声明 HTML最一方的文档称为&#xff1a;文档类型声明&#xff0c;用于声明文档类型。即&#xff1a;<!DOCTYPE html>…

【漫话机器学习系列】113.逻辑回归(Logistic Regression) VS 线性回归(Linear Regression)

逻辑回归 vs 线性回归&#xff1a;详解对比 在机器学习和统计学中&#xff0c;逻辑回归&#xff08;Logistic Regression&#xff09; 和 线性回归&#xff08;Linear Regression&#xff09; 都是非常常见的模型。尽管它们的数学表达式有一定的相似性&#xff0c;但它们的应用…

Redis---缓存穿透,雪崩,击穿

文章目录 缓存穿透什么是缓存穿透&#xff1f;缓存穿透情况的处理流程是怎样的&#xff1f;缓存穿透的解决办法缓存无效 key布隆过滤器 缓存雪崩什么是缓存雪崩&#xff1f;缓存雪崩的解决办法 缓存击穿什么是缓存击穿&#xff1f;缓存击穿的解决办法 区别对比 在如今的开发中&…

Skywalking介绍,Skywalking 9.4 安装,SpringBoot集成Skywalking

一.Skywalking介绍 Apache SkyWalking是一个开源的分布式追踪与性能监视平台&#xff0c;特别适用于微服务架构、云原生环境以及基于容器&#xff08;如Docker、Kubernetes&#xff09;的应用部署。该项目由吴晟发起&#xff0c;并已加入Apache软件基金会的孵化器&#xff0c;…

Thonny+MicroPython+ESP32开发环境搭建

1、下载&安装Thonny 安装成功后&#xff0c;会在桌面生成快捷键 双击快捷键&#xff0c;打开程序&#xff0c;界面如下 2、下载MicroPython 下载地址&#xff1a;MicroPython - Python for microcontrollers v1.19版(推荐&#xff0c;此版本稳定)&#xff1a; https://do…

数据结构:反射 和 枚举

目录 一、反射 1、定义 2、反射相关的类 3、Class类 &#xff08;2&#xff09;常用获得类中属性相关的方法&#xff1a; &#xff08;3&#xff09;获得类中注解相关的方法&#xff1a; &#xff08;4&#xff09;获得类中构造器相关的方法&#xff1a; &#xff08;…

前缀和算法 算法4

算法题中帮助复习的知识 vector<int > dp( n ,k); n为数组大小 ,k为初始化 哈希表unordered_map<int ,int > hash; hash.find(k)返回值是迭代器 ,找到k返回其迭代器 没找到返回hash.end() hash.count(k)返回值是数字 ,找到k返回1 ,没找到返回0. C和java中 负数…

USRP7440-通用软件无线电平台

1、产品描述 USRP7440基于第三代XILINX Zynq UltraScale RFSoC架构&#xff0c;它将射频ADC、DAC、ARM、FPGA等集成一体&#xff0c;瞬时带宽可以达到2.5GHz&#xff0c;尤其适合于射频直采应用&#xff0c;比如通信与雷达。 第一代RFSOC高达4GHz • 8x 或 16x 6.554GSPS DAC…

yunedit-post ,api测试比postman更好

postman应该是大家最熟悉的api测试软件了&#xff0c;但是由于它是外国软件&#xff0c;使用它的高端功能注册和缴费都比较麻烦。生成在线文档分享也经常无法访问被拦截掉。 这里可以推荐一下yunedit-post&#xff0c;该有的功能都有。 https://www.yunedit.com/postdetail …