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

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

文章目录

    • 前言
    • 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为面试赋能(持续更新中…)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

备战蓝桥杯---数学刷题3

话不多说&#xff0c;直接看题&#xff1a; 1. 我们可以得到大致一个思路&#xff0c;就是先枚举1-1e6的质数&#xff0c;然后看看有几个即可。 我们怎么知道个数呢&#xff1f; 首先我们知道1---n中有n/p的下取整个为p的倍数。 因此&#xff0c;p的个数至少是n/p的下取整个…

损失函数-交叉熵 梯度下降

文章目录 1、交叉熵的简单例子1.2、Classification Error&#xff08;分类错误率&#xff09;1.3、Mean Squared Error (均方误差)1.4、交叉熵损失函数1.5、二分类 2、什么是梯度下降法&#xff1f;2.2、梯度下降法的运行过程2.3、二元函数的梯度下降 1、交叉熵的简单例子 参考…

多模态小记:CLIP、BLIP与BLIP2

CLIP 使用网络上爬取得到的大量图文对进行对比学习&#xff0c;图文匹配的是正样本&#xff0c;图文不匹配的是负样本&#xff0c;使匹配样本的embedding之间的距离尽可能小&#xff0c;不匹配样本间的距离尽可能大。 缺点&#xff1a;网上爬的数据质量差&#xff0c;不能进行…

SOCKS代理是如何提高网络性能和兼容性的?

SOCKS代理作为一种网络协议中间件&#xff0c;不仅在提升网络隐私和安全性方面发挥着重要作用&#xff0c;也在提高网络性能和兼容性方面有着不容忽视的影响&#x1f680;。本文将深入探讨SOCKS代理如何通过减少网络延迟&#x1f680;、优化数据传输&#x1f504;、提高跨平台兼…

十进制,二进制,八进制,十六进制之间转换

一. 十进制转二进制 二. 二进制转十进制 三. 十进制转八进制 四. 八进制转十进制 五. 十进制转十六进制

数字档案馆升级改造的意义

数字档案馆升级改造的意义在于提升档案管理的效率和质量&#xff0c;更好地满足各方面的需求&#xff0c;并为数字时代的档案管理提供更好的支持和保障。具体意义包括&#xff1a; 1. 提高档案存储、检索和利用效率&#xff1a;玖拓智能数字化档案馆可以实现电子存储和快速检索…

el-tree如何修改节点点击颜色

el-tree修改点击节点颜色三大步 使用elementui库时&#xff0c;有时候我们会对里面提供的组件做一些样式修改。如果我们想要修改el-tree组件点击节点时的颜色&#xff0c;可以使用下面这种方式实现&#xff1a;

最新国产中文版官网chatGPT镜像网站

分享5个国产中文版chatGPT镜像网站&#xff0c;希望可以帮助到您&#xff01; 1️⃣ HiClaude3基于国外原版GPT模型、Claude模型开发&#xff0c;是资源丰富的全能镜像&#xff0c;适合各行各业的工作者。不仅有gpt&#xff0c;而且还支持图片对话、文件对话&#xff0c;轻松解…

项目存放在git上,在jenkins使用docker打包并推送到Ubuntu上运行

项目添加dockerfile 在需要打包的工程的根目录添加Dockerfile文件&#xff0c;文件内容&#xff1a; # 设置JAVA版本 FROM openjdk:8 # 指定存储卷&#xff0c;任何向/tmp写入的信息都不会记录到容器存储层 VOLUME /tmp# 拷贝运行JAR包 ARG JAR_FILE COPY ${JAR_FILE} app.jar…

08 - 镜像管理之:镜像仓库harbor介绍

本文参考&#xff1a;原文1 1 Harbor仓库介绍 Docker容器应用的开发和运行离不开可靠的镜像管理&#xff0c;虽然Docker官方也提供了公共的镜像仓库&#xff0c;但是从安全和效率等方面考虑&#xff0c;部署我们私有环境内的Registry 也是非常必要的。 之前介绍了Docker私有仓…

适用于W波段GaAs开关设计的可扩展p-i-n二极管建模与参数提取技术

来源&#xff1a;Scalable p-i-n Diode Modeling and Parameter Extraction for Use in the Design of W-Band GaAs Switch&#xff08;TIE 21年&#xff09; 摘要 本文介绍了一种针对W波段开关设计的基于毫米波GaAs的p-i-n二极管的可扩展建模与参数提取方法。采用基于晶圆上…

创新指南|战略衡量的增长组织:用人工智能增强关键绩效指标(KPI)

传统的关键绩效指标 (KPI)越来越无法提供领导者取得成功所需的信息和见解。他们在跟踪进展、协调人员和流程、确定资源优先级以及推进问责制方面存在不足。本文是 2024 年第一份麻省理工学院 SMR - BCG 人工智能和商业战略全球高管学习和研究项目的调查结果——人工智能和业务战…

鸿蒙开发学习笔记第一篇--TypeScript基础语法

目录 前言 一、ArkTS 二、基础语法 1.基础类型 1.布尔值 2.数字 3.字符串 4.数组 5.元组 6.枚举 7.unkown 8.void 9.null和undefined 10.联合类型 2.条件语句 1.if语句 1.最简单的if语句 2.if...else语句 3.if...else if....else 语句 2.switch语句 5.函数…

上位机图像处理和嵌入式模块部署(qmacvisual动态插件卸载)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 前面我们讨论过&#xff0c;qmacvisual虽然提供了很多的功能&#xff0c;包括的种类很多&#xff0c;但是总有一些功能是客户希望定制的。这些都是…

抖音小店无货源爆发期过了吗?现在还能做吗?

大家好&#xff0c;我是电商花花。 抖音小店爆发期过了吗&#xff1f; 我并不觉得&#xff0c;反而抖音小店的流量越来越大了&#xff0c;今年抖音小店日活跃用户已经突破到了9亿&#xff0c;有更大的市场和流量了&#xff0c;且现在做店越来越多了&#xff0c;再加上平台的支…

django 模板js文件为什么最后引入

<!-- 引入Bootstrap JS --> <script src"https://cdn.jsdelivr.net/npm/bootstrap5.3.0/dist/js/bootstrap.bundle.min.js"></script> 为什么最后引入例子 <!-- templates/inspection_records.html --><!DOCTYPE html> <html lang…

zookeeper解析

目录 zookeeper定义 zookeeper定义 Zookeeper是一个开源的分布式的&#xff0c;为分布式框架提供协调服务的Apache项目 Zookeeper工作机制 zookeeper从设计模式角度来理解&#xff1a; 是一个基于观察者模式设计的分布式服务管理框架&#xff0c;它负责存储和管理大家都关心…

arm内核驱动-中断

先介绍个东西 ctags 这个工具可以像keil一样在工程里查找跳转&#xff0c;帮我们找到我们想要的东西。 安装教程可以找到&#xff0c;这里只讲怎么用。 在工程目录&#xff08;包含所有你会用到的头文件等&#xff09;下&#xff0c;先加载这个命令&#xff0c;可能要等待…

VSCode中调试C++程序

目录 一、准备工作&#xff1a;安装插件 1、C/C插件 ​编辑 2、CMake插件 3、CMake tool插件 二、调试过程 1、debug 2、打断点 3、调C/C文件 每次重新调试的时候都忘了具体步骤&#xff0c;直接给自己写个备忘录好了。 一、准备工作&#xff1a;安装插件 1、C/C插件…

李沐29_残差网络ResNet——自学笔记

残差网络 残差网络的核心思想是&#xff1a;每个附加层都应该更容易地包含原始函数作为其元素之一。 残差块 串联一个层改变函数类&#xff0c;我们希望扩大函数类&#xff0c;残差块加入快速通道来得到f(x)xg(x)的结果 ResNet块 1.高宽减半的ResNet块&#xff08;步幅2&a…