【Redis】Redis缓存击穿
1. 概述
缓存击穿:缓存击穿问题也叫热点key问题,一个高并发的key或重建缓存耗时长(复杂)的key失效了,此时大量的请求给数据库造成巨大的压力。如下图,线程1还在构建缓存时,线程2,3,4也来查询缓存,未命中目标到达数据库中查询数据并重建缓存。
2. 解决方案
针对缓存击穿,有两种解决方案。分别是互斥锁和逻辑过期。
2.1 互斥锁
思想:在众多的线程中,只有一个线程可以获得锁,获得锁的线程才能够重建缓存,再释放锁。没有获得锁的线程让其休眠一段时间后再次查询缓存,如果命中目标就返回数据了,如果还是没有命中目标,就再次尝试获得锁,如果获得锁就可以重建缓存,否则再休眠一段时间去缓存中查询,查看是否能命中目标,一直这样循环直到获得目标数据。
获得锁:这个锁不是我们常用的lock, synchronized锁,这两种锁拿到了就会执行,没拿到就等待。但我们这里的锁需要自定义拿到锁和未拿到锁需要干什么。这学习redis基本语法的时候,redis中有个命令和上诉的功能类似,那就是 setnx,如果key不存在就添加,返回结果是1,否则不添加,返回结果是0。
释放锁:释放锁直接将其删除就好了。del setnx
注意:为了避免锁没有被释放而造成死锁原因,最好设置一个有效期作为兜底,即便没有释放锁,有效期过后自动删除,就不会造成死锁了。
这种方式有一个缺点,如果重建缓存比较久,因为加了锁的原因,重建缓存的这段时间其它线程只能等待,性能不高。万一某个因素导致锁没有释放,会发生死锁的情况。
2.2 逻辑过期
逻辑过期,顾名思义,不是真正意义的过期,也可简单理解为永不过期。出现缓存击穿的问题也是key失效导致的,那么我们就不给缓存设置过期时间ttl了。不设置过期时间怎么维护这些缓存呢?总不能一直存在缓存中吧?当然不是了,我们可以在存储数据时再额外存入一个过期时间,后续我们只要维护这个额外的过期时间就好了。
但是换一个角度来看,这个过期时间是由开发人员添加的,redis并不会帮我们管理这些数据,也就是说,这些数据一旦存入redis中,在某种意义上这些数据是持久性的。
一般来说,这些热点key都是在商品做活动的时候用的多,我们会提前把这些高并发数据导入到缓存中,导入数据时就为它们添加逻辑过期时间,等活动结束后,将它们移除即可。另外,查询这些数据理论上来说是一定能命中的,如果没有命中,说明这个数据不是活动数据。所以说只需要判断这些数据是否逻辑过期即可。
那逻辑过期了,也就是说缓存中的是旧数据,需要重建缓存,为了解决线程安全问题,这里也是需要加锁的,但值得一提的是,获得锁的线程(线程1)并不会自己去重建缓存,而是重开一个线程(线程2),委托新线程(线程2)去重建缓存,线程1会先凑合使用旧数据。如果线程2在重建缓存期间,来了一个线程3,因为缓存过期了,必然会尝试获取锁,但锁已经被线程2获取了,所以线程3肯定是获取锁失败的,此时线程3知道了有人帮我们做缓存更新了,于是线程3也拿到过期的数据返回了。就在这时,线程2已经重建好了缓存,并把锁释放了。刚好来了一个线程4,在缓存中命中了目标数据,并返回了最新的数据。
2.3 总结
互斥锁就是在缓存重建的过程,让其他线程进行等待,从而确保数据一致性,但线程需要等待,如果锁没有释放,还会导致服务阻塞,甚至不可用的状态。
逻辑过期是保证在缓存重建期间服务依然可用,但不能保证数据一致性。
3. 实现
3.1 基于互斥锁解决缓存击穿
思想:利用redis的setnx方法来表示获取锁,该方法含义是如果redis中没有这个key,则插入成功,返回1。但是在spring中它帮我们转为了Boolean,因此在stringRedisTemplate中返回true, 如果有这个key则插入失败,则返回0,在stringRedisTemplate返回false,我们可以通过true,或者是false,来表示是否有线程成功插入key,成功插入的key的线程我们认为他就是获得到锁的线程。
// tryLock:尝试获取锁。锁就是redis中的一个key,所以key由使用者传给我们,我们就不在这写死了
private boolean tryLock(String key) {// 执行setnx,ctrl + p查看参数,可以发现它在存的时候是可以同时设置有效期的// 有效期的时长跟你的业务有关,一般正常你的业务执行时间是多少,你这个锁的有效期就比它长一点,长个10倍20倍(避免异常情况),例如这里就设置为10秒钟Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);// 这里不要直接将flag返回,因为直接返回它是会做拆箱的,在拆箱的过程中是有可能出现空指针的,因此这里建议大家使用一个工具类BooleanUtil,是hutool包中的,它可以帮你做一个判断(isTrue、isFalse方法),返回的是一个基本数据类型;或者它也可以直接帮你拆箱(isBollean方法)return BooleanUtil.isTrue(flag);
}// unlock:释放锁
private void unlock(String key) {// 之前分析过了,方法锁就是将锁删掉stringRedisTemplate.delete(key);
}
缓存击穿和缓存穿透的逻辑非常相似,可以在缓存穿透的基础上按照上面的流程图修改。
实现类
public Shop queryWithMutex(Long id) {String key = CACHE_SHOP_KEY + id;// 1、从redis中查询商铺缓存String shopJson = stringRedisTemplate.opsForValue().get(key);// 2、判断是否存在if (StrUtil.isNotBlank(shopJson)) {// 存在,直接返回return JSONUtil.toBean(shopJson, Shop.class);}//判断命中的值是否是空值if (shopJson != null) {//返回一个错误信息return null;}// 4.实现缓存重构,缓存重建业务比较复杂,不是一步两步就能搞定的// 4.1 获取互斥锁,是一个keyString lockKey = "lock:shop:" + id;Shop shop = null;try {boolean isLock = tryLock(lockKey);// 4.2 判断否获取成功if(!isLock){// 4.3 失败,则休眠并重试// 休眠不要花费太长时间,这里可以先休眠50毫秒试一试,这个方法有异常,最后解决它Thread.sleep(50);// 重试就是递归即可return queryWithMutex(id);}// PS:获取锁成功应该再次检测redis缓存是否存在,做DoubleCheck。如果存在则无需重建缓存。但是这里先不检查了。// 4.4 成功,根据id查询数据库shop = getById(id);// 5.不存在,返回错误 // 这个是解决缓存穿透的if(shop == null){//将空值写入redisstringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL,TimeUnit.MINUTES);//返回错误信息return null;}// 6.写入redisstringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_NULL_TTL,TimeUnit.MINUTES);// 最后ctrl + T用try-catch-finally将代码包起来} catch (Exception e){// 这里异常我们就不去做处理了,因为sleep是打断的异常,直接往外抛即可throw new RuntimeException(e);}finally {// 7.释放互斥锁,因为抛异常的情况下,也是需要执行unlock的,因此需要放到unlockunlock(lockKey);}// 返回return shop;
}
根据上面的逻辑,为空直接返回null, 为了给用户一个良好的操作体验,查询数据时对返回结果做一个非空判断,给用户一个提示。
@Override
public Result queryById(Long id) {// 缓存穿透// Shop shop = queryWithPassThrough(id);// 互斥锁解决缓存击穿Shop shop = queryWithMutex(id);if (shop == null) {return Result.fail("店铺不存在!");}return Result.ok(shop);
}
3.2 基于逻辑过期解决缓存击穿
思想:当用户请求数据时,首先到redis缓存中查询,理论上讲这个是不会出现未命中的情况,因为现在key是不会过期的,因此我们可以认为,一旦这个key添加到了缓存里面,它应该会是永久存在的,除非活动结束,然后我们再删除。像这种热点key往往是一些参加活动的一些商品,我们会提前给它们加入缓存,在那个时候就会给它设置一下逻辑时间。但是在为了健壮性考虑,还是判断一下它有没有命中,真的未命中我们也不需要去做一些击穿、穿透这样的一些解决方案,我们直接给它返回空即可。
核心逻辑其实就是默认它命中了,在命中的情况下,我们需要判断的是它有没有过期,也就是它的逻辑过期时间,这个结果有两种:过期和不过期。如果没有过期,则直接返回redis中的数据,如果过期,那就说明它需要重新加载,去做缓存处理。但是不是任何线程都可以重建,因此这里需要有一个争抢,即它需要先尝试去获取互斥锁,然后判断获取是否成功,如果获取失败,说明在这之前有线程去获取数据库数据,那这个更新我们就不用管了,直接返回旧的即可。而获取锁成功的线程,就需要执行缓存重建,但是也不是自己去执行,而是开启一个独立的线程,由这个线程去执行缓存重建,它自己也是返回旧的数据先用着。
1. 设置逻辑过期时间
由于这个字段是我们为了解决缓存击穿才出现的,所以这个字段在实体类中必然是不存在的,有以下3中方式添加字段。
方式一:在实体类中添加字段,修改了原有代码,具有代码侵入性。(不推荐)
方式二:另外创建一个实体类存放逻辑过期字段,然后在实体类中继承新创建的类,也修改了原有代码,具有代码侵入性。(不推荐)
方式三:在 RedisData
中添加一个Object属性,也就是 RedisData
它自己带有过期时间,并且它里面带有数据,这个数据就是你想存进redis的数据,例如Shop、或者其他的数据,因此它是一个万能的存储对象。这种方案就完全不用对原来的实体类做任何修改
package com.hmdp.utils;@Data
public class RedisData {// 设置的逻辑过期时间private LocalDateTime expireTime;private Object data;
}
2. 缓存预热
这种热点数据,是需要提前将缓存导入进去的,实际开发中可能会有一个后台管理系统,可以把某一些热点提前在后台添加到缓存中,但由于我们现在没有一个后台管理的系统,因此基于单元测试方式来把数据加入到缓存中,充当是提前做一个缓存的预热。
下面这个方法将查询的数据写入到了缓存中,并为其封装了逻辑过期时间
// saveShop2Redis:将shop添加到redis中
public void saveShop2Redis(Long id, Long expireSeconds) {// 1.查询店铺数据Shop shop = getById(id);// 2.封装逻辑过期时间RedisData redisData = new RedisData();redisData.setData(shop);// 过期时间由参数传进来redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds));// 3.写入RedisstringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(redisData));
}
这里直接调用上面封装好的代码,模拟热点数据写入缓存中
@Test
void testSaveShop() {shopService.saveShop2Redis(1L, 10L);
}
3. 处理缓存击穿实现代码
3.1 设置一个常量类存放key, 和锁的过期时间
public static final String LOCK_SHOP_KEY = "lock:shop:"; // 店铺获取的锁(key)的前缀
public static final Long LOCK_SHOP_TTL = 10L; // 锁的过期时间
3.2 缓存穿透核心代码块
@Override
public Result queryById(Long id) {// 缓存穿透// Shop shop = queryWithPassThrough(id);// 互斥锁解决缓存击穿// Shop shop = queryWithMutex(id);// 逻辑过期解决缓存击穿Shop shop = queryWithLogicalExpire(id);if (shop == null) {return Result.fail("店铺不存在!");}return Result.ok(shop);
}private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);
public Shop queryWithLogicalExpire( Long id ) {String key = CACHE_SHOP_KEY + id;// 1.从redis查询商铺缓存String json = stringRedisTemplate.opsForValue().get(key);// 2.判断是否命中if (StrUtil.isBlank(json)) {// 3.未命中,直接返回nullreturn null;}// 4.命中,需要先把json反序列化为对象RedisData redisData = JSONUtil.toBean(json, RedisData.class);// redisData.getData()返回的是Object类型,因为RedisData中的data类型是Object,所以使用JSON工具在做反序列化的时候,它并不知道你的类型是不是店铺Shop。此时redisData.getData()的返回值的本质其实是JSONObject,因此这里可以直接强转JSONObject data = (JSONObject) redisData.getData();// 当拿到JSONObject类型后,依旧使用JSON工具类,toBean除了可以接收JSON字符串以外,还可以接收JSONObject,然后告诉它我的实际类型是店铺,此时它就能返回给你一个店铺结果了Shop shop = JSONUtil.toBean(data, Shop.class);// 当然上面两步有点多余,完全可以放一步,但这里为了方便理解,依旧分为两步// Shop shop = JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class);LocalDateTime expireTime = redisData.getExpireTime();// 5.判断是否过期:过期时间是不是在当前时间之后?if(expireTime.isAfter(LocalDateTime.now())) {// 5.1.未过期,直接返回店铺信息return shop;}// 5.2.已过期,需要缓存重建// 6.缓存重建// 6.1.获取互斥锁String lockKey = LOCK_SHOP_KEY + id;boolean isLock = tryLock(lockKey);// 6.2.判断是否获取锁成功if (isLock){// 获取锁成功应该再次检测redis缓冲是否过期,做DoubleCheck。如果存在则无需重建缓存。// 6.3 成功,开启独立线程实现缓存重建。建议:使用线程池,不要自己去写一个线程,那一定话性能不太好,经常的创建和销毁。// 提交任务,这个任务我们可以写成一个Lambda表达式的形式CACHE_REBUILD_EXECUTOR.submit(()->{try {// 重建缓存,直接调用之前封装好的方法即可。// 这里过期时间准确来讲应该设置为30分钟,但是我们为了等一会测试,就先设置成20秒,我们期待的是缓存到底了,然后看看它会不会触发缓存重建的线程安全问题,因此设置短一点,方便我们观察效果this.saveShop2Redis(id, 20L);} catch (Exception e){throw new RuntimeException(e);} finally {// 重建缓存一定要释放锁,并且释放锁的动作最好写到finally中unlock(lockKey);}});}// 6.4.返回过期的商铺信息return shop;
}
为了模拟重建缓存有延迟,这里休眠200毫秒。休眠时间越长,越容易引发线程安全问题。
相关文章:
【Redis】Redis缓存击穿
1. 概述 缓存击穿:缓存击穿问题也叫热点key问题,一个高并发的key或重建缓存耗时长(复杂)的key失效了,此时大量的请求给数据库造成巨大的压力。如下图,线程1还在构建缓存时,线程2,3&…...
厦门凯酷全科技有限公司深耕抖音电商运营
在数字经济飞速发展的今天,抖音电商平台以其独特的社交属性和庞大的用户基础,迅速成为众多品牌和商家的新战场。在这个充满机遇与挑战的市场中,厦门凯酷全科技有限公司凭借其专业的服务、创新的理念和卓越的执行力,成为了抖音电商…...
六西格玛DMAIC在企业得项目管理中有什么作用
六西格玛(Six Sigma)是一种以数据为基础的管理方法,旨在通过减少缺陷和变异来提高过程质量和效率。DMAIC 是六西格玛中一种常用的改进方法论,适用于现有过程的改进。DMAIC 代表五个阶段:定义(Define&#x…...
vscode借助插件调试OpenFoam的正确的.vscode配置文件
正确的备份文件位置: /home/jie/桌面/理解openfoam/正确的调试爆轰单进程案例/mydebugblastFoam 调试爆轰案例流体 并且工作区和用户区都是openfoam-7版本 问题:F5以debug模式启动后不停在断点 解决方法: 这里备份一下.vsode正确的配置&…...
SpringBoot整合JWT(JSON Web Token)生成token与验证
目录 JWT 什么是JWT JWT使用流程 确定要传递的信息: 生成JWT: JWT传输: 客户端保存JWT: 客户端发送JWT: 服务器验证JWT: 服务器响应: Token的使用示例: 工具类 R结果集 返回一个生成的token 创建拦截器 JWT 什么是JWT JWT(JSON Web Token)是是目前最…...
把帕拉丁需要的.rom文件转成.bin
# 输入文件名 input_file_name = fw_payload.bin.rom # 输出文件名 output_file_name = fw_payload.bin.rom2 # 打开输出文件,准备写入翻转后的十六进制字符串 with open(output_file_name, w) as output_file: # 打开输入文件读取十六进制字符串 with open(input_f…...
Nginx 缓存那些事儿:原理、配置和最佳实践
Nginx 缓存那些事儿:原理、配置和最佳实践 在当今的互联网世界,网站的访问量和数据处理量不断攀升,如何确保用户能够快速、稳定地访问我们的网站,已经成为每个运维工程师面临的挑战。幸运的是,Nginx 作为一款高性能的…...
vue发展史
Vue.js发展史 Vue.js是一个渐进式JavaScript框架,自发布以来受到了广泛的关注和喜爱。以下是Vue.js的发展史: 1. 起源(2013年) Vue.js的创始人尤雨溪(Evan You)在2013年开始构思这个项目。当时࿰…...
基于Java和Vue开发的校园跑腿软件校园跑腿小程序系统源码
市场前景 学生需求多样化: 随着校园生活节奏的加快和学生需求的多样化,跑腿服务逐渐成为一种新兴的商业模式。学生群体对于便捷、高效的日常服务需求不断增加,如外卖送餐、快递代取、文件传递等。市场规模持续增长: 大学校园作为…...
MySQL(五)--- 事务
1、CURD操作不加控制时,可能会出现什么问题 即:类似于线程安全问题,可能会导致数据不一致问题。 因为,MySQL内部本身就是多线程服务。 1.1、CURD满足什么属性时,才能避免上述问题 1、买票的过程得是原子的吧。 2、买票互相应该不能影响吧。 3、买完票应该要永久有效吧。…...
llm chat场景下的数据同步
背景 正常的chat/im通常是有单点登录或者利用类似广播的机制做多设备间内容同步的。而且由于长连接的存在,数据同步(想起来)相对简单。而llm的chat在缺失这两个机制的情况下,没见到特别好的做到了数据同步的产品。 llm chat主要两…...
机器学习经典算法
机器学习经典算法学习和分享。 k近邻算法 线性回归 梯度下降法 PCA主成分分析法 多项式回归 逻辑回归 支撑向量机SVM 决策树 随机森林 评价分类指标...
Scala中的泛型
类型参数 ---- 泛型(数据类型是变化的) (1) 可以有多个 (2) 名称合法就行,没有固定的,一般用T(Type) 在Scala中,用[]表示。在Java中用<>表示 1. 与数据类型的区别 List是数据类型,表示一个列表。[Int]表示泛型,它…...
数据分析特征标准化方法及其Python实现
数据分析特征标准化方法及其Python实现 1、概述 在数据分析中,对特征进行标准化主要是: 1、消除量纲影响 不同特征可能具有不同的量纲和数量级。 例如,一个特征可能是以米为单位的长度,而另一个特征可能是以秒为单位的时间。直接使用这些具有不同量纲的原始数据进行分析…...
UnityShaderLab 实现程序化形状(一)
1.实现一个长宽可变的矩形: 代码: fixed4 frag (v2f i) : SV_Target{return saturate(length(saturate(abs(i.uv - 0.5)-0.13)))/0.03;} 2.实现一个半径可变的圆形: 代码: fixed4 frag (v2f i) : SV_Target{return (distance(a…...
前端数据安全防护(控制台)
目录 前言 禁用右键菜单 禁用快捷键 监控控制台 完整逻辑 前言 前端的数据在浏览器中一直处于一个裸奔的状态,只要是稍微懂一点计算机的人,都可以在浏览器的控制台中拿到前端页面的所有数据,包括和后端的交互数据。为了…...
自己玩虚拟机:vagrant,virtual box,centos
vagrant 访问Vagrant官网 https://www.vagrantup.com/ 点击Download Windows,MacOS,Linux等 选择对应的版本 AMD64 (x86_64) I686 (x86) 傻瓜式安装 命令行输入vagrant,测试是否安装成功 vagrant -v 可以查看当前版本 virtual box 访…...
Frida框架HOOK RegisterNatives函数
使用Frida框架HOOK RegisterNatives函数,获取动态注册的函数地址、名称、签名、class名称、所属的so文件名称、so文件加载基址、函数在so文件中的地址。 废话不多说,上代码: 运行命令:frida -U -f in.****** -l RegisterNatives…...
[创业之路-189]:《华为战略管理法-DSTE实战体系》-2- 生存与发展的双重旋律:短期与长期、战术与战略的交响乐章
目录 生存与发展的双重旋律:短期与长期、战术与战略的交响乐章 一、生存:短期视角下的战术布局 二、发展:长期视角下的战略规划 三、短期与长期、战术与战略的融合与平衡 四、结语:在生存与发展的交响曲中奏响辉煌 生存与发展…...
TDengine 部署
TDengine是一款开源高性能的时序数据库,其部署过程可以根据不同的环境和需求进行灵活配置。以下将详细介绍TDengine的部署步骤,包括单节点部署和集群部署。 一、单节点部署 下载安装包: 访问TDengine的官方网站或GitHub仓库,下载…...
【前端】20种 Button 样式
20种 Button 样式 在前端开发中,Button 按钮的样式设计是提升用户交互体验的重要一环。以下是20种常见的Button样式,这些样式主要基于CSS实现,可以根据具体需求进行调整和组合。 1. 默认样式 CSS 样式:.button { background-co…...
机器人构建详解:售前售后服务客服机器人与广告生成机器人的微调数据处理方法
引言 大模型(如BERT、GPT等)在自然语言处理任务中展现了强大的能力,但为了使其更贴合特定应用场景,通常需要进行微调。本文将详细讲解如何为售前售后服务的客服机器人和广告生成机器人准备高质量的微调数据,并通过具体…...
mysql的执行计划分析和索引下推以及索引长度计算
1 执行计划介绍 执行计划(Execution Plan)是数据库查询优化的重要工具,用于展示数据库如何执行 SQL 查询的详细过程。它包含了查询操作的步骤、各个步骤的执行顺序、使用的索引、访问的表、连接方式、预计的成本等信息 可以显示SQL语句最终…...
C#中的string操作详解-截取、分割、连接、替换等
在C#中,string 类提供了许多用于操作字符串的方法,包括截取、分隔和连接等。以下是一些常用字符串操作的介绍和实例: 1. 截取字符串 Substring 方法 用于从字符串中截取子字符串。 语法: //从startIndex开始截取,…...
Redis Cluster 分片机制
Redis 集群是 Redis 提供的一种分布式实现,用于水平扩展数据存储能力。通过 Redis 集群,可以将数据分片存储在多个 Redis 节点上,同时提供高可用性和故障转移功能。 分片(Sharding): Redis 集群将数据划分…...
论文结论:GPTs and Hallucination Why do large language models hallucinate
GPTs and Hallucination 当一个主题有普遍共识,并且有大量语言可用于训练模型时,大模型的输出可以反映出该共识观点在没有足够关于主题的语言示例【晦涩/数据有限】,或者主题有争议,或是对主题没有明确共识的情况下,就…...
CSS在线格式化 - 加菲工具
CSS在线格式化 打开网站 加菲工具 选择“CSS在线格式化” 或者直接访问 https://www.orcc.online/tools/css 输入CSS代码,点击左上角的“格式化”按钮 得到格式化后的结果...
组件通信(父传子,子传父,跨组件通信)
组件(component)是vue.js最核心的功能,是可扩展的HTML元素。每个页面都是一个HTML。以.vue结尾的文件,都可以叫组件。 场景:将一个完整的项目,拆分成不同的功能模块。 注意:组件首字母要大写。 …...
JWT 令牌:原理、应用与安全考量
深入理解 JWT 令牌:原理、应用与安全考量 文章目录 深入理解 JWT 令牌:原理、应用与安全考量一、引言二、JWT 令牌与传统方式的区别(一)传统身份验证方式的特点与局限(二)JWT 令牌的优势 三、JWT 令牌的字段…...
YOLOv5+pyqt5+摄像头在特定条件下进行目标检测并采集原始数据
项目介绍 项目地址 GitHub - biabu0/Yolov5_D435i: 通过YOLOV5与pyqt5实现一个使用D435i深度摄像头采集特定需求与场景下的深度数据的小程序 通过YOLOV5对指定的区域进行检测,当检测到目标进入特定区域时,开始保存数据,摄像头采用D435i深度…...
郴州网站运营公司/百度搜索引擎的特点
1. Hbase 的Go客户端语言使用方法2. Hbase的Row使用注意事项 2.1. Row的前几个字段尽量散列2.2. Row的排序是把所有Row中的字符做字典排序我们最近在一个项目中使用Hbase做日志数据的存储,在其之上做一些数据分析工作,相对java来说,团队成员对…...
网站开发答辩会问哪些问题/全网营销渠道
1 IV的用途 IV的全称是Information Value,中文意思是信息价值,或者信息量。 我们在用逻辑回归、决策树等模型方法构建分类模型时,经常需要对自变量进行筛选。比如我们有200个候选自变量,通常情况下,不会直接把200个变量…...
用word做网站/香港百度广告
1.struct成员默认访问方式是public,而 class默认访问方式是private! 2.exit函数终止程序执行会调用析构函数 ,abort函数终止程序不会调用析构函数! 3.静态局部变量直到程序终止时才退出! 4.通过public 函数返回 private成员的引用有可能会破坏…...
芜湖网站建设费用/百度广告业务
java开放中,经常遇到判断。你在和equals之间如何选择?和equals区别6句话简单总结和equals的区别: 是判断两个变量或实例是不是指向同一个内存空间 是指对内存地址进行比较 是引用是否相同equals() 是判断两个变量或实例所指向的内存空间的值是…...
石家庄便宜做网站/seo关键词排名优化要多少钱
问题如图所示: 解决办法:这种情况只需要全选该段落,然后点击右键选择【段落】-【中文版式】-【允许西文在单词中间换行】即可,不要忘了给换行时被分隔的单词中间加一个连词符“-”。...
长沙ui设计公司/二级域名和一级域名优化难度
图片延迟加载效果(图片需自己添加) <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>图片的延迟加载效果</title><style>* {margin: 0;padding: 0;}#outer {width: …...