说一说Redis的Bitmaps和HyperLoLog?

本篇内容对应 “Redis高级数据类型”小节 和 “7.5 网站数据统计”小节
对应视频:
Redis高级数据结构
网站数据统计

1 什么是UV和DAU?

DAUUV
英文全称Daily Active UserUnique Visotr
中文全称日活跃用户量独立访客
如何统计数据通过用户ID排重统计数据通过用户IP排重统计数据
记录的是什么记录的是用户记录的是设备
使用的Redis数据结构BitmapsHyperLogLog

2 什么是Redis的Bitmap和HyperLoLog?

2.1 Bitmaps

计算机是用二进制作为信息的基础单位,1个字节是8个bit位。例如"big"字符由3个字节组成,实际在计算机存储时将其用二进制表示。"big"中每个字符分别对应的ASCII码是98,105和103,这些十进制数对应的8位二进制数如下:
在这里插入图片描述

许多开发语言都提供了此操作位的功能,例如Java语言中:

i >> 1   // 右移1位  等价于 i / 2
i << 1   // 左移1位  等价于 i * 2
i & j    // 按符号与
i | j    // 按符号或
i ^ j    // 按符号异或
~i       // 按位取非

合理地使用位操作能够有效地提高内存使用率和开发效率。Redis也提供了操作位的功能,即利用Bitmaps这个“数据结构”实现对位的操作,这里使用Bitmaps时要注意两点:

  1. Bitmaps本身不是一种数据结构,实际上它就是字符串,只不过它可以对字符串的位进行操作。
  2. Bitmaps单独提供了一套命令,所以在Redis中使用Bitmaps和使用字符串的方法不太相同。可以把Bitmaps想象成一个以位为单位的数组,数组的每个单元只能存储0和1,数组的下标在Bitmaps中叫偏移量。

在这里插入图片描述

将每个独立用户是否访问过网站存放在Bitmaps中,将访问的用户记作1,没有访问的用户记作0,用偏移量作为用户的id。

1.设置值

设置键的第offset个位的值(从0开始算起),假设现在有20个用户,userid=0, 5, 11, 15, 19 的用户对网站进行了访问,我们使用以下Redis命令向Bitmaps中添加这几个访问用户的id,2024-04-03表示这天的独立访问的用户的Bitmaps

setbit unique:users:2024-04-03 0 1
setbit unique:users:2024-04-03 5 1
setbit unique:users:2024-04-03 11 1
setbit unique:users:2024-04-03 15 1
setbit unique:users:2024-04-03 19 1

我使用lua脚本,在redis中可以直接将这5条命令一次执行:

EVAL "
redis.call('SETBIT', 'unique:users:2024-04-03', 0, 1)
redis.call('SETBIT', 'unique:users:2024-04-03', 5, 1)
redis.call('SETBIT', 'unique:users:2024-04-03', 11, 1)
redis.call('SETBIT', 'unique:users:2024-04-03', 15, 1)
redis.call('SETBIT', 'unique:users:2024-04-03', 19, 1)
" 0

在这里插入图片描述

当前Bitmaps初始化结果是:

在这里插入图片描述
如果此时有一个userid=50的用户访问了网站,会将这个用户添加到Btimaps中:

setbit unique:users:2024-04-03 50 1

在这里插入图片描述

对应的Bitmaps应该长这样:

在这里插入图片描述
在使用Bitmaps统计独立访问的用户时,需要注意以下几点:

  • 一些应用的用户id可能以一个指定的数字开头(例如1000),直接使用这样的用户id作为偏移量会造成一定的浪费,可以考虑减去一个特定的数字。
  • 在第一次初始化Bitmaps,如果偏移量较大,整个初始化过程会比较慢,可能造成Redis的阻塞。

2.获取值

获取userid=8的用户在2024-04-03这天是否访问过网站:

getbit unique:users:2024-04-03 8

在这里插入图片描述

结果返回0,表示该用户在2024-04-03这天没有访问过网站,由于前面我们没有添加过userid=8的这个用户,所以这个结果时符合逻辑的。

获取一个不存在的userid,比如偏移量是10000,结果也是返回0:

getbit unique:users:2024-04-03 10000

在这里插入图片描述

3.获取Bitmaps指定范围值为1的个数

下面这个命令会计算2024-04-03这天网站的独立访问用户数量:

bitcount unique:users:2024-04-03

在这里插入图片描述
通过bitcout命令就可以计算出2024-04-03这天这天网站的DAU,值是6

4.Bitmaps间的运算

假设
2024-04-04这天访问网站的用户是userid=1, 2, 5, 9;
2024-04-05这天访问网站的用户是userid=0, 1, 4, 9
现在要计算2024-04-04和2024-04-05这两天都访问过网站的用户数量,首先初始化这连天的Bitmaps,可以使用bitio and命令将这两个Bitmaps取交集。
图示过程如下:

在这里插入图片描述

再用前面介绍的bitcout命令即可获取与操作之后的Btimaps数量。

Bitmaps性能分析

假设网站由一亿用户,每天独立访问的用户有5千万。分别用集合类型和Bitmaps类型计算DAU,性能比较如下:

数据类型每个用户id占用的空间需要存储的用户量全部内存量
集合64 位5千万64位 X 5千万=400MB
Bitmaps1 位1亿1位X1亿=12.5MB

首先,我们知道:
1 Byte = 8 Bit
1 KB = 1024 Byte
1 MB = 1024 KB

那么,64乘以5千万个bit就是:
64 * 50,000,000 Bit

将Bit转换为Byte:
64 * 50,000,000 Bit / 8 = 400,000,000 Byte

再将Byte转换为KB:
400,000,000 Byte / 1024 = 390,625 KB

最后将KB转换为MB:
390,625 KB / 1024 = 381.47 MB

最后结果大约在400M

但是这并不说明Bitmaps是万金油,如果用户量还是1亿,但是每天访问的用户只有10万,也就是说注册的用户大量是僵尸用户。这时他们之间的性能对比如下:

数据类型每个用户id占用的空间需要存储的用户量全部内存量
集合64 位10万64位 X 10万=800KB
Bitmaps1 位1亿1位X1亿=12.5MB

因为Bitmaps不管你每天具体独立访问的用户数量,而是关系用户id,如果有一个id很大的用户访问一次,那么Bitmaps也会变得非常大。

2.2 HyperLogLog

HyperLogLog是一种基数算法,实际数据类型还是字符串类型,通过HyperLogLog可以利用很小的内存空间完成独立总数的统计,数据集可以是IP、Email、ID等。

Philippe Flajolet教授发表的论文首次提出的HyperLogLog基数算法
论文:The analysis of a near-optimal cardinality estimation algorithm

1.添加
pfadd用于向HyperLogLog添加元素,如果添加成功返回1:

 pfadd 2024_04_03:unique:ids uuid-1 uuid-2 uuid-3 uuid-4

2.计算独立用户数
pfcount用于计算一个或多个HyperLogLog的独立总数:

pfcount 2024_04_03:unique:ids

3.HyperLogLog内存占用实验

一两条数据看不出HyperLogLog内存占用小的优势,我们使用linux的bash脚本,向redis插入100万条用户uuid。
首先我们查询插入前的内存大小,我们执行关注前两行数据即可:

info memory

在这里插入图片描述
向redis HyperLogLog中的2024_04_04:unique:ids键插入100万个用户id的bash脚本如下

#!/bin/bash# 初始化变量
elements=""
key="2024_04_04:unique:ids"# 循环生成并插入用户ID
for i in $(seq 1 1000000); doelements="${elements} user-"${i}if [[ $((i%1000))  == 0 ]]; then# 使用Redis的pfadd命令插入用户redis-cli pfadd ${key} ${elements}# 清空elements变量,准备下一次插入elements=""fi
done# 如果最后一次循环的用户数量不足1000,将剩余的用户插入
if [[ -n "$elements" ]]; thenredis-cli pfadd ${key} ${elements}
fi

我们将该bash脚本命名为insert_users.sh,然后在Linux运行该脚本
在这里插入图片描述

./insert_users.sh

如果报bash: ./insert_users.sh: Permission denied 错误,说明这个文件没有执行权限
使用chmod +x insert_users.sh命令给该文件添加执行权限,然后再次运行即可

执行完毕后到redis中,再次执行查询内存的命令

info memory

在这里插入图片描述
1108384b - 1091920b = 16464b = 16.46KB
所以存入100万个用户的uuid之后,内存只增加了16.46KB。可以看到内存占用非常小。我们再来看一下统计结果是否正确,用pfcount命令统计以下:

pfcount 2024_04_04:unique:ids

在这里插入图片描述
可以看到结果是1003860,并不是精确的100万,但是也很接近了。考虑到占用的内存有很大的优势,对精确值要求敏感的业务完全可以用HyperLogLog。

再来看一下如果用集合来存储着100万个用户uuid,然后统计总数,bash脚本如下:

#!/bin/bash# 初始化变量
elements=""
key="2024_04_04:unique:ids:set"# 循环生成并插入用户ID
for i in $(seq 1 1000000); doelements="${elements} user-"${i}if [[ $((i%1000))  == 0 ]]; then# 使用Redis的sadd命令插入用户redis-cli sadd ${key} ${elements}# 清空elements变量,准备下一次插入elements=""fi
done# 如果最后一次循环的用户数量不足1000,将剩余的用户插入
if [[ -n "$elements" ]]; thenredis-cli pfadd ${key} ${elements}
fi

存入insert_users_set.sh脚本文件中,然后运行。

./insert_users_set.sh

执行完后,到redis执行info memory命令,与上一次的内存大中比较:

info memory

在这里插入图片描述
89496112b - 1108384b = 88387728b = 84.28M
可以看到与前一次相比,内存增加了84M左右。但是用集合统计的结果是精确的:

scard 2024_04_04:unique:ids:set

在这里插入图片描述
根据上面的实验结果,这里列出了使用集合类型和HyperLogLog类型统计百万级用户的占用空间对比:

数据类型1天1个月1年
集合80M2.4G28G
HyperLogLog15K450K5M

可以看到HyperLogLog内存占用量小得惊人,用如此小的空间来估算如此巨大的数据,必然不是100%的精确,存在一定的误差率。Redis官方给出的数字是0.81%的失误率。

4.合并
pfmerge可以求出多个HyperLogLog的并集并赋值给destkey,例如要计算2016年3月5日和3月6日的访问独立用户数,可以按照如下方式来执行:

pfadd 2016_03_06:unique:ids "uuid-1" "uuid-2" "uuid-3" "uuid-4"
pfadd 2016_03_05:unique:ids "uuid-4" "uuid-5" "uuid-6" "uuid-7"
pfmerge 2016_03_05_06:unique:ids 2016_03_05:unique:ids 2016_03_06:unique:ids
pfcount 2016_03_05_06:unique:ids

3 网站数据统计

为什么UV要用ip作为统计访客的依据?

因为我们希望将一些匿名的用户,或者一些没有登录的用户或一些没有账号的游客也统计进来。 UV这个统计值关注的是访问量。而不是关注到底注没注册、登没登录。不管是谁,只要你访问就算。所以,我们要对每次访问都要进行统计,每次访问都把这个用户的ip存到HyperLogLog里,HyperLogLog会统计不同ip的个数,得到最终去重以后的结果,虽然有误差率,但是其极致的内存利用率,这点误差率可以忍受的。

为什么HyperLogLog适合用统计UV的数据结构?

因为HyperLogLog最大的特点是性能好,存储空间非常小。而我们网站的访问量,加上游客的话,可能会非常大,每天可能有几十万甚至上百万的访问量。因为即使是同一个ip访问,由于服务器事先不知道,也会取把这个ip记录到数据结构中。所以访问量才这么大。而我们对UV这个统计值要求不是那么精确,HyperLogLog的误差率足以忍受,所以我们使用HyperLogLog来统计UV。

你的网站如何定义用户是否活跃?

我的网站定义只要今天访问一次就算是一个活跃用户。

DAU与UV的区别?

我们统计DAU是根据用户id来进行统计的,也就是说一些没有登录的游客,DAU是不统计的。

为什么DAU要用Bitmaps?

因为我们在统计DAU时更关注用户的有效性,通过Bitmaps不仅可以统计某天的活跃用户数量,还可以通过命令判断某天,某个用户是不是活跃的等功能,也就是Bitmaps虽然空间利用率没有HyperLogLog高,但是一个网站真正注册登录的用户其实相较于访问量会小很多,所以对空间的占用不会特别高。而且Bitmaps统计是精确的结构。我们关注用户的核心程度,所以对于这样的数据,要求它是一个精确的结果。Bitmaps本身统计的内存占用也不大,性能也比较好,能统计出精确的结果。

你如何实现用Bitmaps存储用户id?

在数据库表设计时,我将用户表的userid设计成为int类型,从1开始自增的主键,所以可以直接将用户id作为偏移量,是否访问了网站分别设置为1和0来存入bitmaps中。
由于userid是整数,而我们要记录的是这个userid访没访问过,访问过就是1,没访问过就是0。1就是活跃,0就是不活跃。
例如:bitmaps的key = dau:2024-04-04,然后userid=101的用户在2024-04-04这条访问过我们的网站,我就将Bitmaps的101位设置成1
这样我们每天可能有几万个用户访问了网站,我只需要用一个Bitmaps,这个Bitmaps的长度=max(userid)。所以,Bitmaps也是比较节约空间的,这么多用户我只需用这么些位来统计即可。

下面我们开始编码实现,因为要用到redis来存储数据,所以首先需要定义redisk key。在RedisKeyUtil类里面添加UV和DAU的key:

    private static final String PREFIX_UV = "uv";private static final String PREFIX_DAU = "dau";

如何查看一周的UV?

由于HyperLogLog支持合并,我们只需使用pfmerge命令求出多个HyperLogLog的并集。例如要计算2024-04-04到2024-04-11这一周的UV,我们可以将这一周7天的7个HyperLogLog进行合并。

我们按照单日UV的方式来记录:

	// 单日UVpublic static String getUVKey(String date) {return PREFIX_UV + SPLIT + date;}

单日UV的key类似于:“uv:2024-04-04”。

如果想统计一周,一个区间的UV,可以对多个HyperLogLog合并,合并的适合会产生一个新的数据,这个数据要指定一个新的key,所以我们要构造一周UV的key,即区间UV。

	// 区间UVpublic static String getUVKey(String startDate, String endDate) {return PREFIX_UV + SPLIT + startDate + SPLIT + endDate;}

区间UV的key类似于 “uv:2024-04-04:2024-04-11”。

同理,统计活跃用户也需要但是DAU和区间DAU

    // 单日活跃用户public static String getDAUKey(String date) {return PREFIX_DAU + SPLIT + date;}// 区间活跃用户public static String getDAUKey(String startDate, String endDate) {return PREFIX_DAU + SPLIT + startDate + SPLIT + endDate;}

定义完以后,我们开发首先开发数据访问层,因为我们用的是Redis,所以我们数据访问层不用单独一个组件,直接上来些Service即可。取调用这个RedisTemplate就行了,创建一个新的Server:DataService类。

首先将RedisTemplate 对象注入进来,由于创建DAU和UV的key时要不断的和日期打交道,所以也将SimpleDateFormat 注入进来,日期格式是"yyyyMMdd"

	@Autowiredprivate RedisTemplate redisTemplate;private SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd");

要统计这个数据包括两个方面:

  1. 我要把这个数据记录下来,在每一次请求当中,需要截获这次请求,把相关数据记录到redis。(记)
  2. 当我想看的时候,能够提供一个查询的方法,能够访问到。(查)

首先做UV记的方法:

    // 将指定的IP计入UVpublic void recordUV(String ip) {String redisKey = RedisKeyUtil.getUVKey(df.format(new Date()));redisTemplate.opsForHyperLogLog().add(redisKey, ip);}

UV区间查的方法:

// 统计指定日期范围内的UVpublic long calculateUV(Date start, Date end) {if (start == null || end == null) {throw new IllegalArgumentException("参数不能为空!");}// 整理该日期范围内的keyList<String> keyList = new ArrayList<>();Calendar calendar = Calendar.getInstance();calendar.setTime(start);while (!calendar.getTime().after(end)) {String key = RedisKeyUtil.getUVKey(df.format(calendar.getTime()));keyList.add(key);calendar.add(Calendar.DATE, 1);}/** 如果是同一天,2024-04-03 00:00:00~ 2024-04-03 23:59:59* 合并生成的redisKey是vu:2024-04-03:2024-04-04* keyList的值是[vu:2024-04-03],union操作只union了一个* 实现了一个方法统计单日UV和区间UV* 下面DAU查的方式也是一样的* */// 合并这些数据String redisKey = RedisKeyUtil.getUVKey(df.format(start), df.format(end));redisTemplate.opsForHyperLogLog().union(redisKey, keyList.toArray());// 返回统计的结果return redisTemplate.opsForHyperLogLog().size(redisKey);}

DAU的记和查的逻辑类似:

// 将指定用户计入DAUpublic void recordDAU(int userId) {String redisKey = RedisKeyUtil.getDAUKey(df.format(new Date()));redisTemplate.opsForValue().setBit(redisKey, userId, true);}// 统计指定日期范围内的DAUpublic long calculateDAU(Date start, Date end) {if (start == null || end == null) {throw new IllegalArgumentException("参数不能为空!");}// 整理该日期范围内的keyList<byte[]> keyList = new ArrayList<>();Calendar calendar = Calendar.getInstance();calendar.setTime(start);while (!calendar.getTime().after(end)) {String key = RedisKeyUtil.getDAUKey(df.format(calendar.getTime()));keyList.add(key.getBytes());calendar.add(Calendar.DATE, 1);}/** redisTemplate.opsForValue().setBit 可以设置位* 但是要使用or运算必须使用底层连接* */// 进行OR运算return (long) redisTemplate.execute(new RedisCallback() {@Overridepublic Object doInRedis(RedisConnection connection) throws DataAccessException {String redisKey = RedisKeyUtil.getDAUKey(df.format(start), df.format(end));connection.bitOp(RedisStringCommands.BitOperation.OR,redisKey.getBytes(), keyList.toArray(new byte[0][0]));return connection.bitCount(redisKey.getBytes());}});}

业务层逻辑写好以后,接下来就可以写表现层的逻辑。表现从逻辑一分为二:

  1. 什么时候去记录这个值
  2. 什么时候去查看这个值

记录这个值需要每次请求都进行记录,因为每次请求都有可能是一个新的访问,所以我们在拦截器里面写这个代码。请求到底Handler之前将请求拦截
在这里插入图片描述

创建一个DataInterceptor类,只需要重写preHandle方法即可,在该方法里面进行DAU和UV的统计。

    @Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 统计UVString ip = request.getRemoteHost();dataService.recordUV(ip);// 统计DAUUser user = hostHolder.getUser();if (user != null) {dataService.recordDAU(user.getId());}return true;}

然后将该拦截器注册。

之后就是统计方法,比较简单。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python Django全文搜索库之django-haystack使用详解

概要 Django Haystack库是一个用于在Django项目中实现全文搜索功能的强大工具。它集成了各种搜索引擎,如Elasticsearch、Whoosh等,为开发者提供了灵活且高效的搜索解决方案。在本文中,将深入探讨Django Haystack库的安装、配置和应用,以及如何利用其丰富的功能来实现高级全…

LabVIEW齿轮箱噪声监测系统

LabVIEW齿轮箱噪声监测系统 齿轮箱作为机械设备的“心脏”&#xff0c;其健康状态对设备的性能有着重要的影响。传统的齿轮箱监测方法依赖于直接的振动信号分析&#xff0c;但这种方法不仅成本高昂&#xff0c;而且在安装和拆卸过程中可能对设备造成损害。针对这些问题&#x…

Ai音乐大师演示(支持H5、小程序)独立部署源码

Ai音乐大师演示&#xff08;支持H5、小程序&#xff09;独立部署源码

华为服务器RAID配置教程 服务器硬盘故障处理帮助 浪潮RAID配置教程 磁盘阵列配置通用教程

前言&#xff08;本文档持续更新&#xff09; 本文主要记录服务器配置RAID&#xff08;磁盘阵列&#xff09;过程中存在的细节问题及官方文档无法解决的问题的解决方案 配置环境 华为 RH2288 v3服务器配置RAID组 如何快速配置 1.找到服务器品牌的阵列卡型号&#xff0c;找不到…

格力臻新风柜机:春风送暖,健康鲜氧守护家

春天&#xff0c;万物复苏&#xff0c;但也带来了流感和雾霾的频发。在这样的季节里&#xff0c;一款能够提供健康空气的空调就显得尤为重要。而格力臻新风变频冷暖3匹1级能效柜机空调KFR-72LW/(72582)FNhCb-B1(WIFI)&#xff08;珊瑚玉&#xff09;&#xff0c;就如同一位贴心…

使用Java拓展本地开源大模型的网络搜索问答能力

背景 开源大模型通常不具备最新语料的问答能力。因此需要外部插件的拓展&#xff0c;目前主流的langChain框架已经集成了网络搜索的能力。但是作为一个倔强的Java程序员&#xff0c;还是想要用Java去实现。 注册SerpAPI Serpapi 提供了多种搜索引擎的搜索API接口。 访问 Ser…

MySQL数据库 数据库基本操作(二):表的增删查改(上)

1. CRUD CRUD 即增加(Create)、查询(Retrieve)、更新(Update)、删除(Delete)四个单词的首字母缩写,就是数据库基本操作中针对表的一系列操作. 2. 新增(create) -->insert 语法: insert into 表名 [列名1,列名2…] values (val1,val2…) [注意] 列名可以没有,如果没有列名…

Docker 部署开源远程桌面工具 RustDesk

RustDesk是一款远程控制&#xff0c;远程协助的开源软件。完美替代TeamViewer &#xff0c;ToDesk&#xff0c;向日葵等平台。关键支持自建服务器&#xff0c;更安全私密远程控制电脑&#xff01;官网地址&#xff1a;https://rustdesk.com/ 环境准备 1、阿里云服务器一 台&a…

从零到一:基于 K3s 快速搭建本地化 kubeflow AI 机器学习平台

背景 Kubeflow 是一种开源的 Kubernetes 原生框架&#xff0c;可用于开发、管理和运行机器学习工作负载&#xff0c;支持诸如 PyTorch、TensorFlow 等众多优秀的机器学习框架&#xff0c;本文介绍如何在 Mac 上搭建本地化的 kubeflow 机器学习平台。 注意&#xff1a;本文以 …

标题:探索AI绘画:使用深度学习生成艺术

正文&#xff1a; 随着计算机技术的发展&#xff0c;人工智能在各个领域取得了显著的成果。通过训练深度学习模型&#xff0c;AI可以学习大量的艺术作品&#xff0c;从而生成具有独特风格和创意的新作品。 本文将介绍如何使用Python和TensorFlow实现一个简单的AI绘画程序。 二、…

运算符规则

console.log(null undefined) null和undefined都是原始类型&#xff0c;然后把这两个转换为数字。是0NaN.看规则有一个NaN的话就得到NaN. console.log({} []); 把{}和[]转换为原始类型分别为和[Object Object]。然后特殊情况有字符串&#xff0c;那就拼接字符串返回[Object…

【嵌入式DIY实例】-使用SCT-013 传感器测量交流电流

使用SCT-013 传感器测量交流电流 文章目录 使用SCT-013 传感器测量交流电流1、SCT-013介绍2、硬件准备2、如何计算电气设备消耗的电流3、代码实现SCT-013电流互感器在家用电能表中很常见。 它是一种无需断开电路即可测量导线中电流的组件。在本文中,我们将介绍如何使用 Arduin…

hexo博客7:构建简单的多层安全防御体系

【hexo博客7】构建简单的多层安全防御体系 写在最前面理解全面安全策略的重要性防御常见的网络攻击1. SQL注入攻击2. 文件上传漏洞3. 跨站脚本攻击&#xff08;XSS&#xff09;4. 跨站请求伪造&#xff08;CSRF&#xff09;5. 目录遍历/本地文件包含&#xff08;LFI/RFI&#x…

基础篇3 浅试Python爬虫爬取视频,m3u8标准的切片视频

浅试Python爬取视频 1.页面分析 使用虾米视频在线解析使用方式&#xff1a;https://jx.xmflv.cc/?url目标网站视频链接例如某艺的视频 原视频链接 解析结果: 1.1 F12查看页面结构 我们发现页面内容中什么都没有&#xff0c;video标签中的src路径也不是视频的数据。 1.2 …

异常

1&#xff0e;异常是什么? 程序中可能出现的问题 2&#xff0e;异常体系的最上层父类是谁?异常分为几类? 父类:Exception。 异常分为两类:编译时异常、运行时异常 3&#xff0e;编译时异常和运行时异常的区别? 编译时异常:没有继承RuntimeExcpetion的异常&#xff0c;直接…

开源博客项目Blog .NET Core源码学习(13:App.Hosting项目结构分析-1)

开源博客项目Blog的App.Hosting项目为MVC架构的&#xff0c;主要定义或保存博客网站前台内容显示页面及后台数据管理页面相关的控制器类、页面、js/css/images文件&#xff0c;页面使用基于layui的Razor页面&#xff08;最早学习本项目就是想学习layui的用法&#xff0c;不过最…

数据结构记录

之前记录的数据结构笔记&#xff0c;不过图片显示不了了 数据结构与算法(C版) 1、绪论 1.1、数据结构的研究内容 一般应用步骤&#xff1a;分析问题&#xff0c;提取操作对象&#xff0c;分析操作对象之间的关系&#xff0c;建立数学模型。 1.2、基本概念和术语 数据&…

UE4_普通贴图制作法线Normal材质

UE4 普通贴图制作法线Normal材质 2021-07-02 10:46 导入一张普通贴图&#xff1a; 搜索节点&#xff1a;NormalFromHeightmap 搜索节点&#xff1a;TextureObjectparameter&#xff0c;并修改成导入的普通贴图&#xff0c;连接至HeightMap中 创建参数normal&#xff0c;连接…

软件杯 深度学习YOLO抽烟行为检测 - python opencv

文章目录 1 前言1 课题背景2 实现效果3 Yolov5算法3.1 简介3.2 相关技术 4 数据集处理及实验5 部分核心代码6 最后 1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 基于深度学习YOLO抽烟行为检测 该项目较为新颖&#xff0c;适合作为竞赛课…

反截屏控制技术如何防止信息通过手机拍照泄漏?

反截屏控制技术为企业数据安全提供了重要的防护措施。通过以下几点&#xff0c;有效阻止了信息通过拍照等方式的泄漏&#xff1a; 反截屏控制开启&#xff0c;用户启动截屏操作时&#xff0c;允许非涉密内容截屏操作&#xff0c;但所有涉密内容窗口会自动隐藏&#xff0c;防止涉…