Redis(十五)Bitmap、Hyperloglog、GEO案例、布隆过滤器

news2024/9/9 5:28:57

文章目录

  • 面试题
  • 常见统计类型
    • 聚合统计
    • 排序统计
    • 二值统计
    • 基数统计
  • Hyperloglog
    • 专有名词
      • UV(Unique Visitor)独立访客
      • PV(Page View)页面浏览量
      • DAU(Daily Active User)日活跃用户量
      • MAU(Monthly Active User)
    • 需求
    • 原理
    • 亿级UV的Redis统计方案
  • GEO
    • 面试题
    • 命令
      • GEOADD
      • 获取某位置的经纬度GEOPOS
      • 返回坐标的Geohash表示GEOHASH
      • 两个位置之间距离GEODIST
      • 半径范围内的坐标GEORADIUS
      • 半径范围内坐标中心点是给定元素GEORADIUSBYMEMBER
  • Bitmap
    • 面试题
    • 作用场景
  • 布隆过滤器BloomFilter
    • 需求
    • 概述
    • 作用
    • 原理
      • 概述原理
      • 添加、查询元素过程
      • 使用步骤
      • 使用场景
    • 手写布隆过滤器案例
      • 优缺点
      • 布谷鸟过滤器:解决布隆过滤器不能删除缺点

面试题

  1. 抖音电商直播,主播介绍的商品有评论,1个商品对应了1系列的评论,排序+展现+取前10条记录
  2. 用户在手机App上的签到打卡信息:1天对应1系列用户的签到记录,新浪微博、钉钉打卡签到,来没来如何统计?
  3. 应用网站上的网页访问信息:1个网页对应1系列的访问点击,淘宝网首页,每天有多少人浏览首页?
  4. 你们公司系统上线后,说一下UV、PV、DAU分别是多少?

记录对集合中的数据进行统计

  1. 在移动应用中,需要统计每天的新增用户数和第2天的留存用户数;
  2. 在电商网站的商品评论中,需要统计评论列表中的最新评论;
  3. 在签到打卡中,需要统计一个月内连续打卡的用户数;
  4. 在网页访问记录中,需要统计独立访客(Unique Visitor,UV)量。

需求
亿级数据的收集+清洗+统计+展现

常见统计类型

亿级系统常见统计方式

聚合统计

统计多个集合元素的聚合结果,就是前面讲解过的交差并等集合统计
在这里插入图片描述
交并差集和聚合函数的应用

排序统计

  • 抖音短视频最新评论留言的场景,请你设计一个展现列表。

在面对需要展示最新列表、排行榜等场景时,如果数据更新频繁或者需要分页显示,建议使用ZSet

二值统计

集合元素的取值就只有0和1两种
在钉钉上班签到打卡的场景中,我们只用记录有签到(1)或没签到(0)
bitmap

基数统计

指统计一个集合中不重复的元素个数
见hyperloglog

Hyperloglog

去重统计估计算法

专有名词

UV(Unique Visitor)独立访客

需要去重考虑

PV(Page View)页面浏览量

不用去重

DAU(Daily Active User)日活跃用户量

登录或者使用了某个产品的用户数(去重复登录的用户)
常用于反映网站、互联网应用或者网络游戏的运营情况

MAU(Monthly Active User)

月活跃用户量

需求

  1. 很多计数类场景,比如 每日注册 IP 数、每日访问 IP 数、页面实时访问数 PV、访问用户数 UV等。
  2. 因为主要的目标高效、巨量地进行计数,所以对存储的数据的内容并不太关心。
  3. 也就是说它只能用于统计巨量数量,不太涉及具体的统计对象的内容和精准性。
  4. 统计单日一个页面的访问量(PV),单次访问就算一次。
  5. 统计单日一个页面的用户访问量(UV),即按照用户为维度计算,单个用户一天内多次访问也只算一次。
  6. 多个key的合并统计,某个门户网站的所有模块的PV聚合统计就是整个网站的总PV。

问题
如果数据显较大亿级统计,使用bitmaps同样会有问题。

bitmap是通过用位bit数组来表示各元素是否出现,每个元素对应一位,所需的总内存为N个bit。

基数计数则将每一个元素对应到bit数组中的其中一位,比如bit数组010010101(按照从零开始下标,有的就是1、4、6、8)。

新进入的元素只需要将已经有的bit数组和新加入的元素进行按位或计算就行。这个方式能大大减少内存占用且位操作迅速。

但是,假设一个样本案例就是一亿个基数位值数据,一个样本就是一亿
如果要统计1亿个数据的基数位值,大约需要内存100000000/8/1024/1024约等于12M,内存减少占用的效果显著。

这样得到统计一个对象样本的基数值需要12M。
如果统计10000个对象样本(1w个亿级),就需要117.1875G将近120G,可见使用bitmaps还是不适用大数据量下(亿级)的基数计数场景,

但是bitmaps方法是精确计算的。

原理

概率算法
通过牺牲准确率来换取空间,对于不要求绝对准确率的场景下可以使用,因为概率算法不直接存储数据本身。
通过一定的概率统计方法预估基数值,同时保证误差在一定范围内,由于又不储存数据故此可以大大节约内存。
HyperLogLog就是一种概率算法的实现。

只是进行不重复的基数统计,不是集合也不保存数据,只记录数量而不是具体内容。
Hyperloglog提供不精确的去重计数方案
牺牲准确率来换取空间,误差仅仅只是0.81%左右
http://antirez.com/news/75

亿级UV的Redis统计方案

UV的统计需要去重,一个用户一天内的多次访问只能算作一次
淘宝、天猫首页的UV,平均每天是1~1.5个亿左右
每天存1.5个亿的IP,访问者来了后先去查是否存在,不存在加入

// 用于产生模拟后台访问数据
@Service
@Slf4j
public class HyperLogLogService
{
    @Resource
    private RedisTemplate redisTemplate;

    /**
     * 模拟后台有用户点击首页,每个用户来自不同ip地址
     */
    @PostConstruct
    public void init()
    {
        log.info("------模拟后台有用户点击首页,每个用户来自不同ip地址");
        new Thread(() -> {
            String ip = null;
            for (int i = 1; i <=200; i++) {
                Random r = new Random();
                ip = r.nextInt(256) + "." + r.nextInt(256) + "." + r.nextInt(256) + "." + r.nextInt(256);

                Long hll = redisTemplate.opsForHyperLogLog().add("hll", ip);
                log.info("ip={},该ip地址访问首页的次数={}",ip,hll);
                //暂停几秒钟线程
                try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
            }
        },"t1").start();
    }

}
@Api(description = "淘宝亿级UV的Redis统计方案")
@RestController
@Slf4j
public class HyperLogLogController
{
    @Resource
    private RedisTemplate redisTemplate;

    @ApiOperation("获得IP去重后的首页访问量")
    @RequestMapping(value = "/uv",method = RequestMethod.GET)
    public long uv()
    {
        //pfcount
        return redisTemplate.opsForHyperLogLog().size("hll");
    }

}

GEO

是zset

面试题

外卖软件中附近的美食店铺、打车软件附近的车辆等等。那这种附近各种形形色色的XXX地址位置选择

  1. 查询性能问题,如果并发高,数据量大这种查询是要搞垮mysql数据库的
  2. 一般mysql查询的是一个平面矩形访问,而叫车服务要以我为中心N公里为半径的圆形覆盖。
  3. 精准度的问题,我们知道地球不是平面坐标系,而是一个圆球,这种矩形计算在长距离计算时会有很大误差,mysql不合适

命令

GEOADD

GEOADD city 116.403963 39.915119 "天安门" 116.403414 39.924091 "故宫" 116.024067 40.362639 "长城"

在这里插入图片描述

获取某位置的经纬度GEOPOS

GEOPOS city 天安门 故宫

在这里插入图片描述

返回坐标的Geohash表示GEOHASH

GEOHASH city 天安门 故宫 长城

在这里插入图片描述

两个位置之间距离GEODIST

GEODIST city 天安门 长城 km

m 米
km 千米
ft 英尺
mi 英里
在这里插入图片描述

半径范围内的坐标GEORADIUS

GEORADIUS city 116.418017 39.914402 10 km withdist withcoord count 10 withhash desc
  • WITHDIST: 在返回位置元素的同时, 将位置元素与中心之间的距离也一并返回。 距离的单位和用户给定的范围单位保持一致。
  • WITHCOORD: 将位置元素的经度和维度也一并返回。
  • WITHHASH: 以 52 位有符号整数的形式, 返回位置元素经过原始 geohash 编码的有序集合分值。 这个选项主要用于底层应用或者调试, 实际中的作用并不大
  • COUNT 限定返回的记录数。

半径范围内坐标中心点是给定元素GEORADIUSBYMEMBER

GEORADIUSBYMEMBER city 天安门 km withdist withcoord count 10 withhash desc

在这里插入图片描述

Bitmap

面试题

  1. 日活统计
  2. 连续签到打卡
  3. 最近一周的活跃用户
  4. 统计指定用户一年之中的登陆天数
  5. 某用户哪几天登陆过,哪几天没有登陆,全年中登录的天数共计多少

作用场景

  1. 用户是否登陆过Y、N,比如京东每日签到送京豆
  2. 电影、广告是否被点击播放过
  3. 钉钉打卡上下班,签到统计

布隆过滤器BloomFilter

需求

  1. 现有50亿个电话号码,现有10万个电话号码,如何要快速准确的判断这些电话号码是否已经存在?
  2. 安全连接网址,全球数10亿的网址判断
  3. 黑名单校验,识别垃圾邮件
  4. 白名单校验,识别出合法用户进行后续处理

概述

由一个初值都为零的bit数组和多个哈希函数构成,1用来快速判断集合中是否存在某个元素
本质就是判断具体数据是否存在于一个大的集合中

布隆过滤器是一种类似set的数据结构,国只是统计结果在巨量数据下有点小瑕疵,不够完美

作用

高效地插入和查询,占用空间少,返回的结果是不确定性+不够完美。

重点

  1. 一个元素如果判断结果:存在时,元素不一定存在但是判断结果为不存在时,则一定不存在
  2. 布隆过滤器可以添加元素,但是不能删除元素由于涉及hashcode判断依据,删掉元素会导致误判率增加。

原理

概述原理

布隆过滤器是专门用来解决去重问题的高级数据结构

实质就是一个大型位数组和几个不同的无偏hash函数(无偏表示分布均匀)。由一个初值都为零的bit数组和多个个哈希函数构成,用来快速判断某个数据是否存在。但是跟 HyperLogLog 一样,它也一样有那么一点点不精确,也存在一定的误判概率

添加、查询元素过程

  • 添加key时
    使用多个hash函数对key进行hash运算得到一个整数索引值,对位数组长度进行取模运算得到一个位置,每个hash函数都会得到一个不同的位置,将这几个位置都置1就完成了add操作。

  • 查询key时
    只要有其中一位是零就表示这个key不存在,但如果都是1,则不一定存在对应的key。

结论:有,是可能有 无,是肯定无

举例
当有变量被加入集合时,通过N个映射函数将这个变量映射成位图中的N个点,把它们置为 1(假定有两个变量都通过 3 个映射函数)。
在这里插入图片描述
如果这些点,有任何一个为零则被查询变量一定不在,如果都是 1,则被查询变量很可能存在

  • 为什么说是可能存在,而不是一定存在呢?那是因为映射函数本身就是散列函数,散列函数是会有碰撞的。(见上图3号坑两个对象都1)

缓存穿透解决方案:
正是基于布隆过滤器的快速检测特性,我们可以在把数据写入数据库时,使用布隆过滤器做个标记。当缓缺失后,应用查询数据库时,可以通过查询布降过滤器快速判断数据是否存在。如果不存在,就不用再去据库中查询了。这样一来,即使发生缓存穿透了,大量请求只会查询Redis和布隆过滤器,而不会积压到据库,也就不会影响数据库的正常运行。布降过滤器可以使用Redis实现,本身就能承担较大的并发访问力。

Hash冲突:https://segmentfault.com/a/1190000022553292

使用步骤

  1. 初始化Bitmap
    布隆过滤器 本质上 是由长度为 m 的位向量或位列表(仅包含 0 或 1 位值的列表)组成,最初所有的值均设置为 0
    在这里插入图片描述
  2. 添加对应占位

当我们向布隆过滤器中添加数据时,为了尽量地址不冲突,会使用多个 hash 函数对 key 进行运算,算得一个下标索引值,然后对位数组长度进行取模运算得到一个位置,每个 hash 函数都会算得一个不同的位置。再把位数组的这几个位置都置为 1 就完成了 add 操作。

例如:我们添加一个字符串wmyskxz,对字符串进行多次hash(key) → 取模运行→ 得到坑位
在这里插入图片描述

  1. 判断是否存在
    向布隆过滤器查询某个key是否存在时,先把这个 key 通过相同的多个 hash 函数进行运算,查看对应的位置是否都为 1,
    只要有一个位为零,那么说明布隆过滤器中这个 key 不存在;
    如果这几个位置全都是 1,那么说明极有可能存在;
    因为这些位置的 1 可能是因为其他的 key 存在导致的

例如:在 add 了字符串wmyskxz数据之后,很明显下面1/3/5 这几个位置的 1 是因为第一次添加的 wmyskxz 而导致的;此时我们查询一个没添加过的不存在的字符串inexistent-key,它有可能计算后坑位也是1/3/5
在这里插入图片描述

使用场景

  1. 解决缓存穿透问题:redis结合Bitmap使用

把已存在数据的key存在布隆过滤器中,相当于redis前面挡着一个布隆过滤器。

当有新的请求时,先到布隆过滤器中查询是否存在:
如果布隆过滤器中不存在该条数据则直接返回;
如果布隆过滤器中已存在,才去查询缓存redis,如果redis里没查询到则再查询Mysql数据库
在这里插入图片描述

  1. 黑名单校验
  2. 安全连接网址,亿级数量判断

手写布隆过滤器案例

在这里插入图片描述
初始化白名单用户到布隆过滤器

@PostConstruct
public void init()
 {
     //白名单客户预加载到布隆过滤器
     String uid = "customer:12";
     //1 计算hashcode,由于可能有负数,直接取绝对值
     int hashValue = Math.abs(uid.hashCode());
     //2 通过hashValue和2的32次方取余后,获得对应的下标坑位
     long index = (long) (hashValue % Math.pow(2, 32));
     log.info(uid+" 对应------坑位index:{}",index);
     //3 设置redis里面bitmap对应坑位,该有值设置为1
     redisTemplate.opsForValue().setBit("whitelistCustomer",index,true);
 }

布隆过滤器检查

@Component
@Slf4j
public class CheckUtils
{
    @Resource
    private RedisTemplate redisTemplate;

    public boolean checkWithBloomFilter(String checkItem,String key)
    {
        int hashValue = Math.abs(key.hashCode());
        long index = (long) (hashValue % Math.pow(2, 32));
        boolean existOK = redisTemplate.opsForValue().getBit(checkItem, index);
        log.info("----->key:"+key+"\t对应坑位index:"+index+"\t是否存在:"+existOK);
        return existOK;
    }
}

Controller

@RestController
@Slf4j
public class CustomerController
{
    @Resource private CustomerSerivce customerSerivce;

    @ApiOperation("数据库初始化2条Customer数据")
    @RequestMapping(value = "/customer/add", method = RequestMethod.POST)
    public void addCustomer() {
        for (int i = 0; i < 2; i++) {
            Customer customer = new Customer();

            customer.setCname("customer"+i);
            customer.setAge(new Random().nextInt(30)+1);
            customer.setPhone("1381111xxxx");
            customer.setSex((byte) new Random().nextInt(2));
            customer.setBirth(Date.from(LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant()));

            customerSerivce.addCustomer(customer);
        }
    }

    @ApiOperation("单个用户查询,按customerid查用户信息")
    @RequestMapping(value = "/customer/{id}", method = RequestMethod.GET)
    public Customer findCustomerById(@PathVariable int id) {
        return customerSerivce.findCustomerById(id);
    }

    @ApiOperation("BloomFilter案例讲解")
    @RequestMapping(value = "/customerbloomfilter/{id}", method = RequestMethod.GET)
    public Customer findCustomerByIdWithBloomFilter(@PathVariable int id) throws ExecutionException, InterruptedException
    {
        return customerSerivce.findCustomerByIdWithBloomFilter(id);
    }
}

Service

@Service
@Slf4j
public class CustomerSerivce
{
    public static final String CACHA_KEY_CUSTOMER = "customer:";

    @Resource
    private CustomerMapper customerMapper;
    @Resource
    private RedisTemplate redisTemplate;
    @Resource
    private CheckUtils checkUtils;

    /**
     * 写操作
     * @param customer
     */
    public void addCustomer(Customer customer)
    {
        int i = customerMapper.insertSelective(customer);

        if(i > 0)
        {
            //mysql插入成功,写进redis
            Customer result = customerMapper.selectByPrimaryKey(customer.getId());
            //redis缓存key
            String key = CACHA_KEY_CUSTOMER+customer.getId();
            //写进redis
            redisTemplate.opsForValue().set(key,result);
        }
    }

    public Customer findCustomerById(Integer customreId)
    {
        Customer customer = null;
        //缓存redis的key名称
        String key = CACHA_KEY_CUSTOMER+customreId;
        customer = getCustomer(customreId, key);
        return customer;
    }

    /**
     * 白名单:whitelistCustomer
     */
    public Customer findCustomerByIdWithBloomFilter (Integer customerId)
    {
        Customer customer = null;
        //缓存key的名称
        String key = CACHA_KEY_CUSTOMER + customerId;

        //布隆过滤器check,无是绝对无,有是可能有
        //===============================================
        if(!checkUtils.checkWithBloomFilter("whitelistCustomer",key))
        {
            log.info("白名单无此顾客,不可以访问: "+key);
            return null;
        }
        //===============================================

        //1 查询redis
        customer = getCustomer(customerId, key);
        return customer;
    }

    private Customer getCustomer(Integer customreId, String key) {
        Customer customer;//1 先去redis查询
        customer = (Customer) redisTemplate.opsForValue().get(key);

        //2 redis有直接返回,没有再进去查询mysql
        if (customer == null) {
            // 3 再去查询我们的mysql
            customer = customerMapper.selectByPrimaryKey(customreId);
            // 3.1 mysql有,redis无
            if (customer != null) {
                //3.2 把mysq查询出来的数据回写redis,保持一致性
                redisTemplate.opsForValue().set(key, customer);
            }
        }
        return customer;
    }
}

优缺点

优点
高效地插入和查询,内存占用bit空间少
缺点

  1. 不能删除元素。
  2. 因为删掉元素会导致误判率增加,因为hash冲突同一个位置可能存的东西是多个共有的你删除一个元素的同时可能也把其它的删除了。
  3. 存在误判,不能精准过滤:有,是很可能有;无,是肯定无。

布谷鸟过滤器:解决布隆过滤器不能删除缺点

为了解决布隆过滤器不能删除元素的问题,布谷鸟过滤器横空出世。

论文:
《Cuckoo Filter:Better Than Bloom》

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

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

相关文章

【2024软件测试面试必会技能】Appium自动化(6):原生app元素定位方法

元素定位方法介绍及应用&#xff1a; Appium方法定位原生app元素: 通过appium inspector工具&#xff0c;可以获取元素的相关信息&#xff1b;在appium中提供了一系列的元素定位API&#xff0c;通过在这些API中输入指定的元素信息&#xff0c;就能完成元素定位&#xff0c;定…

学习Redis基础篇

1.初识Redis 1.认识NoSQL 2.认识Redis 3.连接redis命令 4.数据结构的介绍 5.通用命令 2.数据类型 1.String类型 常见命令&#xff1a;例子&#xff1a;set key value

【Linux 内核源码分析】内存管理——伙伴分配器

在Linux操作系统中&#xff0c;内存分配通常由内核中的内存管理模块完成。以下是三个主要的内存分配器&#xff1a; 伙伴系统 (Buddy System)&#xff1a;这是内核中最基本的分配器&#xff0c;用于分配物理内存。伙伴系统将内存块组织成不同大小的伙伴&#xff0c;以便有效地分…

电脑c盘太满了怎么办?5个必备的好方法~

随着我们在电脑上存储和安装越来越多的文件和程序&#xff0c;C盘的空间可能会迅速减少&#xff0c;甚至变得过于拥挤。当C盘空间不足时&#xff0c;会影响电脑的运行速度和性能&#xff0c;甚至导致系统崩溃。本文将介绍一些解决C盘空间不足问题的方法&#xff0c;帮助你更好地…

git中将所有修改的文件上传到暂存区

案例&#xff1a; 我将本地的多个文件进行了修改&#xff0c;导致文件发生了变化。使用git status命令&#xff0c;查看文件的状态&#xff0c;发现有多个文件是modified&#xff0c;即被修改了。 本地文件发生了变化&#xff0c;需要将modified的文件添加到暂存区&#xff0c…

【Git工具实战】实用真实 Git 开发工作流程

前言 最近工作中发现&#xff0c;很多开发人员连最基本的Git怎么使用都不知道&#xff0c;比如什么时候切分支&#xff0c;什么时候合并代码&#xff0c;代码遇到冲突怎么办&#xff0c;经常出现掉代码&#xff0c;代码合并后丢失的情况。以下为个人总结的常规Git开发工作流程…

Python实战:读取MATLAB文件数据(.mat文件)

Python实战&#xff1a;读取MATLAB文件数据(.mat文件) &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程 &#x1f448; 希望得到您的订阅…

七分钟交友匿名聊天室源码

应用介绍 本文来自&#xff1a;七分钟交友匿名聊天室源码 - 源码1688 简介&#xff1a; 多人在线聊天交友工具&#xff0c;无需注册即可畅所欲言&#xff01;你也可以放心讲述自己的故事&#xff0c;说出自己的秘密&#xff0c;因为谁也不知道对方是谁。 运行说明&#xff…

docker镜像和容器的关系

背景 镜像和容器都是docker中非常重要的概念&#xff0c;镜像是静态的&#xff0c;而容器是动态的&#xff0c;两者的关系就类似类和实例的关系&#xff0c;本文就来分析下两者的关联 镜像和容器 我们知道镜像是存放在仓库中的静态的文件&#xff0c;而容器是运行中的进程&a…

厌倦了混乱的代码?掌握编写干净代码库的艺术

对于入门的开发人员来说&#xff0c;虽然克服了最初的障碍&#xff0c;学会了编程&#xff0c;找到了理想的工作。但其编程旅程并没有就此结束。他们面临真正的挑战&#xff1a;如何编写更好的代码。这不仅仅是为了完善功能&#xff0c;还要编写出经得起时间考验的优雅、可维护…

HTML5 Canvas 限定文本区域大小,文字自动换行,自动缩放

<!DOCTYPE html> <html> <body><h1>HTML5 Canvas 限定文本展示范围、自动计算缩放字体大小</h1><div id"tips">0</div> <div id"content">良田千顷不过一日三餐广厦万间只睡卧榻三尺良田千顷不过一日三餐…

发电机组启动前的准备和检查注意事项

发电机组启动前的准备&#xff1a; 1.检查润滑油的油位、 冷却液液位、燃油量&#xff1b; 2.检查机的供油、润滑、冷却等系统各个管路及接头有无漏油漏水现象&#xff1b; 3.检查电气线路有无破皮等漏电隐患&#xff0c;接地线电气线路是否松动&#xff0c;机组与基础的连接是…

MES系统中的手动排产和自动排产-助力生产效率

企业在排产管理中面临的问题&#xff1a; 大多数的企业在调度排产过程中&#xff0c;都会遇到以下问题。首先是插单非常的多&#xff0c;计划调整困难&#xff0c;会经常性的发生原材料、零部件的备货不足。计划按MRP或库存展示计算出需求后将产生大量工单&#xff0c;这些工单…

transformer 最简单学习1 输入层embeddings layer,词向量的生成和位置编码

词向量的生成可以通过嵌入层&#xff08;Embedding Layer&#xff09;来完成。嵌入层是神经网络中的一种常用层&#xff0c;用于将离散的词索引转换为密集的词向量。以下是一个典型的步骤&#xff1a; 建立词表&#xff1a;首先&#xff0c;需要从训练数据中收集所有的词汇&…

vue 常用库

vue-cropper 一个优雅的图片裁剪插件 dayjs Day.js 是一个轻量的处理时间和日期的 JavaScript 库&#xff0c;和 Moment.js 的 API 设计保持完全一样 NutUI-Bingo 基于 NutUI 的抽奖组件库&#xff0c;助力营销活动和小游戏场景。

java面试题之mybatis篇

什么是ORM&#xff1f; ORM&#xff08;Object/Relational Mapping&#xff09;即对象关系映射&#xff0c;是一种数据持久化技术。它在对象模型和关系型数据库直接建立起对应关系&#xff0c;并且提供一种机制&#xff0c;通过JavaBean对象去操作数据库表的数据。 MyBatis通过…

内容检索(2024.02.23)

随着创作数量的增加&#xff0c;博客文章所涉及的内容越来越庞杂&#xff0c;为了更为方便地阅读&#xff0c;后续更新发布的文章将陆续在此汇总并附上原文链接&#xff0c;感兴趣的小伙伴们可持续关注文章发布动态&#xff01; 本期更新内容&#xff1a; 1. 电磁兼容理论与实…

C语言——指针——第2篇——(第20篇)

坚持就是胜利 文章目录 一、指针和数组二、二级指针1、什么是 二级指针&#xff1f;2、二级指针 解引用 三、指针数组模拟二维数组 一、指针和数组 问&#xff08;1&#xff09;&#xff1a;指针和数组之间是什么关系呢&#xff1f; 答&#xff1a;指针变量就是指针变量&…

【spring】 ApplicationListener的使用及原理简析

文章目录 使用示例&#xff1a;原理简析&#xff1a; 前言&#xff1a;ApplicationListener 是spring提供的一个监听器&#xff0c;它可以实现一个简单的发布-订阅功能&#xff0c;用有点外行但最简单通俗的话来解释&#xff1a;监听到主业务在执行到了某个节点之后&#xff0c…

GitHub热门项目之Memos 打造私有备忘录

效果 1. 写备忘录或简单笔记&#xff0c;支持Markdown 2. 时间线 3. 探索可以看到其他用户公开的内容 项目地址 usememos/memos&#xff1a;一种开源的轻量级笔记服务。轻松捕捉和分享您的伟大想法。 (github.com)https://github.com/usememos/memos 体验地址 Memoshttp://…