WeakCache二级缓存

news/2024/5/20 14:39:02

WeakCache弱缓存略析

  • 一、WeakCache类总览
  • 二、WeakCache属性
  • 三、WeakCache构造方法
  • 四、WeakCache的内部类
    • 4.1 Value接口
    • 4.2 Factory内部类
      • 4.2.1 Factory属性
      • 4.2.1 Factory公开方法
    • 4.3 CacheValue内部类
    • 4.4 CacheKey内部类
    • 4.5 LookupValue内部类
  • 五、WeakCache的公开方法
  • 六、提问
  • 七、使用场景
  • 【参考资料】

一、WeakCache类总览

WeakCache是由java.lang.reflect反射包下提供的二级缓存。二级缓存的结构为<Key,Sub-Key,Value>,其中Key和Value均为弱引用,而Sub-Key为强引用。获取缓存值的get()方法时不仅需要Key,还需要多添加一个参数(P类型)。通过这个参数和Key就可以计算出Sub-Key。WeakCache对象的构造方法中会传入两个工厂对象,其中一个为subKeyFactory用于计算Sub-Key的,另外一个为valueFactory用于计算最终的结果的。Key可以为Null但是Sub-key和Value不能为Null。弱引用对象被GC回收掉后不会立马清除缓存,而是会在WeakCache的公开方法方法中手动懒式清除。

final class WeakCache<K, P, V> {
...
}

如上代码所示,WeakCache使用关键字final修饰,不允许被继承。并且会声明三种泛型参数,K为Key的类型,P为参数的类型,V为最终结果的类型。

二、WeakCache属性

WeakCache共有5个私有属性,分别为:

private final ReferenceQueue<K> refQueue = new ReferenceQueue<>();
// the keytypeis Object for supporting null key
private final ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map= new ConcurrentHashMap<>();
private final ConcurrentMap<Supplier<V>, Boolean> reverseMap= new ConcurrentHashMap<>();
private final BiFunction<K, P, ?> subKeyFactory;
private final BiFunction<K, P, V> valueFactory;
  • refQueue: 用于存放GC后被回收的一级Key(使用WeakReference包装)。Reference相关资料
  • map:由嵌套ConcurentMap构成的二级缓存,其中第一层Key由K类型参数生成,第二层Key有K类型参数及P类型参数共同计算的结果。注意这里的value的实际类型可能会有多种情况。
  • reverseMap:由于两层缓存,因此通过倒排Map加快判断某value结果是否已经存在。
  • subKeyFactory:BiFunction类型,根据K类型参数及P类型参数构造二级Key
  • valueFactory:BiFunctionle类型,根据K类型参数及P类型参数获取Value.

三、WeakCache构造方法

WeakCache类仅提供了一个构造方法:

public WeakCache(BiFunction<K, P, ?> subKeyFactory,BiFunction<K, P, V> valueFactory) {this.subKeyFactory = Objects.requireNonNull(subKeyFactory);this.valueFactory = Objects.requireNonNull(valueFactory);
}

构造方法入参需提供两个BiFunction类型的非null函数式实例,其中一个subKeyFactory用于根据K类型参数、P类型参数生成二级Key,另外一个valueFactory用于根据K类型参数和P类型参数生成V类型的具体值,即Value。

四、WeakCache的内部类

WeakCache内部声明了5个私有内部类,其中1个内部接口,1个非静态内部类,3个静态内部类。

4.1 Value接口

    /*** Common type of value suppliers that are holding a referent.* The {@link #equals} and {@link #hashCode} of implementations is defined* to compare the referent by identity.*/private interface Value<V> extends Supplier<V> {}

这里定义了WeakCache中二级缓存的value的类型(Value<V>),实际上是Supplier类型。为什么不直接使用Supplier<V>呢?在前面WeakCache属性中我们提到reverseMap适用于倒排Map查找缓存值使用,但是reverseMap的类型为ConcurrentMap<Supplier<V>, Boolean>。这问题就来了,在这个map进行hash时是按照Supplier实例进行的,但是Supplier实际上仅是我们对V类型值的延迟封装,真正hash应该按照value值进行,即我们不关心Supplier对value封装的具体形式,只要是supplier1.get()== supplier2.get()也认为是同一个value。
因此需要自定义Value继承Supplier,任何实现Value接口都应该重写hashCode()及equals()方法。下面会说到CacheValue、LookupValue都会实现Value接口,也都重写两个方法。

4.2 Factory内部类

private final class Factory implements Supplier<V> {
...
}

Factory作为私有内部类,实现了Supplier<V>的接口。在WeakCache中的主要作用是完成了对结果Value生成过程、延迟构建的封装。二级缓存的更新也会由Factory完成。

4.2.1 Factory属性

Factory共有4个私有属性,分别为:

private final K key;
private final P parameter;
private final Object subKey;
private final ConcurrentMap<Object, Supplier<V>> valuesMap;

对于二级缓存的Value来说,最终要的四个属性包括:

  1. key:K类型的一级Key
  2. parameter:P类型的参数
  3. subKey:由以上二者生成后的二级Key
  4. valuesMap:当前一级Key下所有的缓存Map

这四个属性均为Factory构建value的必要参数,因此Factory仅提供了一个构造函数,形参需传入这四个属性。

4.2.1 Factory公开方法

Factory仅提供了一个实现Supplier接口的get方法,主要是用于根据以上四个缓存的属性延迟获取真正的value对象。代码及注释贴下:

public synchronized V get() { 	// 序列化进入,防止并发// 按照惯例,加锁内部重新检查[锁等待期间可能情况不一样了]Supplier<V> supplier = valuesMap.get(subKey);if (supplier != this) {// 检查失败,缓存内部二级Key对应的Value已经不是当前的Factory对象了。后续无法继续处理,只能返回null,由调用方法处理这种内部检查不符合预期的情况。// 可能我们将Factory值替换为了CacheValue值了,返回null可以直接取cacheValue即可// 也可能是valueFactory根据K\P生成value时出现异常或null,此时的Factory对象会被认为为无效值。结果从二级缓存中移除并且抛异常,下次在进入get时,就可能会为null。return null;}// else still us (supplier == this)// 校验通过后,根据this进行获取V类型的Value值V value = null;try {// 使用valueFactory根据K和P来生成Valuevalue = Objects.requireNonNull(valueFactory.apply(key, parameter));} finally {if (value == null) { // 生成的Value为null时,必须把当前Factory对象缓存移除掉valuesMap.remove(subKey, this);}}// 必须保证valueFactory生成的Value不会nullassert value != null;// 对生成的value使用内部类CacheValue封装,CacheValue是继承了WeakReference,也属于弱引用,后续会单独提到CacheValue。CacheValue<V> cacheValue = new CacheValue<>(value);// 将cacheValue放置在倒排Map中,方便后续判断reverseMap.put(cacheValue, Boolean.TRUE);// 使用cacheValue替换二级Key对应的缓存值if (!valuesMap.replace(subKey, this, cacheValue)) {	// 细节,调用底层方法时,返回值各种情况都应该考虑到throw new AssertionError("Should not reach here");}// 返回真实的V类型的value值return value;
}

JDK的代码都是由很多性能优化及细节值得我们学习的,所以看源码并不能止步于看懂,而是真正理解,理解后知道我们下次碰见类似的情况是否也能这样思考,是否也能注意容易忽略的细节部分。下面根据一些问题我们展开思考:

  1. 根据Factory#get()方法内部都做了些什么?
    主要负责使用valueFactory根据K和P来获取Value的实际逻辑。并且获取到value后会把对应的值重新封装为CacheValue弱引用缓存起来,在下次GC之前获取Value就可以通过缓存了。当然,此外还会把CacheValue放入倒排Map中便于后续判断缓存是否存在指定的value值。
  2. Factory为什么是内部类而不是静态内部类?
    Factory对象get()方法内部需要使用到外部类非静态属性-valueFactory;
  3. WeakCache为什么不直接调用valueFactory生成Value,而要设计Factory内部类?
    第一个原因是封装性好。将valueFactory生成Value的逻辑以及将value包装为弱引用,且记录倒排map这些一气呵成的逻辑封装起来。
    第二个原因-延迟获取缓存值,节省内存,提升速度。valueFactory是外界提供的,生成value实际大小不明确。WeakCache#map缓存值使用的策略是先使用用Factory对象占坑(Factory对象创建时仅缓存所需要的数据即可,内存占用小),真正需要获取value时才使用Factory#get()方法获取实际的Value。获取真正value之后,会把value封装为弱引用(CacheValue)替换掉Factory对象。这样做,既满足了缓存的需求,又不会浪费内存空间。【在很多底层设计中都有类似的操作,一定要学会】
  4. Factory#get()方法为什么要使用synchronized关键字?
    主要是为了保证缓存map(WeakCache#map)和倒排map(WeakCache#reverseMap)操作的原子性(or,一致性)。单个map都是concurrentMap类型,get\put操作均是线程安全的。但是Factory#get()获取到value之后会不会存在reverseMap有对应的cacheValue,而在valuesMap#replace覆盖不成功呢?在无synchronized时是有可能的,假设存在2个线程同是执行get,其中一个线程执行至reverseMap#put(),另外一个线程执行valueFactory#apply异常,就会存在这种问题。
  5. 同步代码块应注意哪些问题?
    在synchronized同步代码块中一定要进行二次检查,二次检查主要是为了防止等待锁的过程中,外部数据的变化与同步代码逻辑不兼容性。比如在单例模式(DCL)中,也会二次检查对象是否已经创建-已经创建了就不能再创建了,即不必执行同步代码。在Factory#get()方法中检查二级缓存的值是否为当前Factory对象,校验成功后使用Factory对象执行下面同步代码逻辑。需要注意的是,同步代码块正常执行或任何异常都必须保证该检查条件成立。正常执行后会将缓存中value替换为CacheValue,valueFactory获取value出现异常后会将二级缓存清除。

为什么"throw new AssertionError(“Should not reach here”)"不清除缓存?
这种情况是绝对的error,valueFactory获取value出现异常会清除重试

4.3 CacheValue内部类

private static final class CacheValue<V>extends WeakReference<V> implements Value<V> {
...
}

WeakCache声明了私有静态内部类CacheValue,是用于封装缓存value的类型。CacheValue继承了WeakReference,因此类CacheValue包装的数据均为弱引用,生命周期为下一次GC之前。CacheValue实现了Value接口,但是接口内get()方法实际上是由WeakReference继承实现的。
CacheValue判等逻辑为“==”操作。hashCode()返回内存地址,equals()返回逻辑有两点:① CacheValue对象==;② 均为Value内部类型且Value#get()返回类型V的实际对象==。
疑问:似乎CacheValue不实现Value接口也能行?确实,但是Value接口的含义在于重写hashcode()与equals()方法。

4.4 CacheKey内部类

private static final class CacheKey<K> extends WeakReference<K> {
...
}

CacheKey也是私有静态内部类,同样也继承了WeakReference,因此使用CacheKey是为了包装K类型的Key为弱引用类型,生命周期为下一次GC之前。
CacheKey和CacheValue还有三点主要差异:(不算CacheValue实现了Value接口)

  1. CacheKey允许Key为null
    当Key为null时,使用NULL_KEY空对象代替。
  2. CacheKey使用了ReferenceQueue,当GC回收后,可以通过引用队列手动清楚掉。(为啥手动清楚?为了清空WeakCache#map、WeakCache#reverseMap)
  3. CacheKey还提供了expungeFrom方法。手动清空ReferenceQueue时,会清空WeakCache#map、WeakCache#reverseMap。

CacheKey的equals()方法中使用this.getClass判断类对象,其实可以替换为“obj instance CacheKey”。但是一般instance用于判断基类引用的真实类型,这里CacheKey类为final不存在继承、多态。可能这里使用instance有点奇怪吧。

4.5 LookupValue内部类

private static final class LookupValue<V> implements Value<V> {
...
}

LookupValue也是私有静态内部类,实现了Value接口。从LookupValue类名上看,是查找Value的意思。其实这就是为了方便从倒排Map(reverseMap)中查询Value。前面讲过,reverseMap查找Value的hash不能使用Supplier默认方法(继承自Object),而应该实现Value接口重写hashcode()与equals()方法。
为什么不复用CacheValue而是新增LookupValue呢?查找Value是否存在仅是为了查找而不会存储,因此没必要用弱引用包装,LookupValue仅实现Value接口,性能肯定更好。

五、WeakCache的公开方法

WeakCache共提供了3个公开方法:

  • get(K key, P parameter):根据K类型Key值即P类型参数来查找缓存的Value值。
  • containsValue(V value):判断缓存中是否包含某个Vlaue
  • size():返回所有缓存的Value值。

所有的公开方法被访问时都会调用私有方法expungeStaleEntries()去根据WeakCache#refQueue队列poll出已清除的CacheKey来清空WeakCache#map、WeakCache#reverseMap中的无效缓存引用。
这里仅给出get方法的代码解析,其他两个代码简单,不再赘述。

public V get(K key, P parameter) {
Objects.requireNonNull(parameter); // 例行校验P类型参数不能为null
expungeStaleEntries();	// 公开方法均会先清楚已回收的Key及缓存// 使用CacheKey创建(一级)Key的弱引用,并且使用了引用队列。
// 【之所以返回Object,兼容key为null的情况】
Object cacheKey = CacheKey.valueOf(key, refQueue);// 根据cacheKey获取二级map-valuesMap。
ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
if (valuesMap == null) {	// 注意:这里可以仅使用下面一块代码,提前get增加性能优化。// cacheKey不存在,这里必须使用putIfAbsent,而不能使用put进行初始化ConcurrentMap<Object, Supplier<V>> oldValuesMap= map.putIfAbsent(cacheKey,valuesMap = new ConcurrentHashMap<>());if (oldValuesMap != null) {		// putIfabsent存在返回已有值,不存在返回nullvaluesMap = oldValuesMap;}
}// create subKey and retrieve the possible Supplier<V> stored by that
// subKey from valuesMap
// 一级上面处理完了,下面就是处理二级map-valuesMap的过程。
// 首先获取二级Key-subKey。二级Key的生成逻辑由调用方提供的subKeyFactory、K、P决定。
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
// 从二级map中尝试获取subKey的值,这里的值可能是null也可能是Factory也可能是CacheValue
// 注意:supplier即便不为null,也可能get()==null,原因是GC已经回收了
Supplier<V> supplier = valuesMap.get(subKey);
Factory factory = null;		// 声明缓存Value的工厂前身(见Factory类解析)while (true) {if (supplier != null) {// supplier可能是Factory也可能是CacheValue// 注意:supplier即便不为null,也可能get()==null,原因是GC已经回收了V value = supplier.get();if (value != null) {	// 不为null,说明通过Factory或CacheValue获得了value,直接返回return value;}}/*** 三种情况:* ① 二级缓存map中没有subKey对应的值,即supplier==null 【唯第一次循环】* ② supplier从二级缓存map中获取不为Null,但由于GC回收,get()==null 【不确定】* ③ supplier instanceof Factory 且 Factory.get()在异常时会返回null。【非第一次循环】*/if (factory == null) {	// 初始化Factory对象,包含value创建的所有必要数据 【唯第一次循环】factory = new Factory(key, parameter, subKey, valuesMap);}if (supplier == null) {// supplier为null,尝试向二级map中添加<subKey, factory>【唯第一次循环】supplier = valuesMap.putIfAbsent(subKey, factory);if (supplier == null) {// 成功向二级map添加factorysupplier = factory;}// 多线程原因,二级map中已经有了subKey的supplier。// 同样的,这里虽不为null,也并不代表它不会是已GC回收的弱引用} else {/*** supplier不为null,有两种可能:* ① supplier从二级缓存map中获取不为Null,但由于GC回收,get()==null 【不确定】* ② supplier instanceof Factory 且 Factory.get()在异常时会返回null。【非第一次循环】*/if (valuesMap.replace(subKey, supplier, factory)) {// 这里是情况①,不会是②,Factory.get()在异常时会在finally中从二级map中清除// supplier因为被回收,只能重置为factorysupplier = factory;} else {// Factory.get()异常,重试supplier = valuesMap.get(subKey);}}
}
}

上面方法注释的十分详细了,这里就简要说下几个部分:
(1)代码逻辑中设置map数据均使用的是putIfAbsent方法,不能使用put方法。在处理一级Key使用putIfAbsent之前还使用了get方法判断是否存在二级map。这里使用get()方法也是为了优化性能。
(2)在处理二级map获取value的过程,注意三种情况:① supplier == null ② supplier不为null 但已被回收 ③ supplier不为null、未被回收,但内部出错重试。
(3) 从获取缓存value的逻辑来看,Factory的封装性更加重要,反而延迟性(节省内存,避免不必要的初始化)实际上体现的并不明显。由于Factory将value初始化所需要的数据封装起来,赋值给supplier进行兜底(如null或已被回收),转换后统一使用supplier#get获取value值。

六、提问

WeakCache的基本内容和代码都讲解完了,为了更好的认知WeakCache的使用场景,还有几个问题需要我们明确并学习下。

  1. 为什么WeakCache的key、value设置为弱引用,而sub-Key为强引用?
    缓存值value设置弱引用的的目的是控制缓存的有效期,至下一个GC之前。
    缓存一级key也设置为弱引用是为了跟踪缓存值回收后记录到队列中,手动清除各map中的引用。
    sub-Key不需要弱引用,手动清楚引用时,二级key也会被删除。
  2. 为什么WeakCache一级key允许为null,而sub-Key、value均不允许为null
    value在WeakCache中设计为弱引用,当value为null的时候就会引起歧义,即value是已经被回收了还是本身就是null,无法区分。
    sub-Key是由使用方提供的subKeyFactory以及一级Key、P类型参数共同生成的。subKeyFactory返回的null谁也不知道是异常还是正常null值,可能后引起非常难排查的bug。【不信任调用方,那就不支持,干脆直接约定不能为Null】
    一级key是确定的,由调用方直接传过来的(不含任何中间逻辑),不会出现sub-Key的歧义问题。
    Tip:支持或不支持null,很多时候都是因为歧义性风险的取舍
  3. 为什么Value实现类型需要重写hashcode()及equals()?
    缓存的value值是通过弱引用类型封装的,真实value是其内部get()获取的值。因此必须重写hashcode()及equals()修改为==逻辑(包括两个部分,上面已有不再赘述)
  4. 为什么Factory不需要继承Value?
    Factory作为cacheValue的前身,为什么Factory不实现接口Value呢?如果Factory作为延迟构建性,我想Factory应该也会实现接口Value,并重写hashCode与equals(),但其实不然,虽然Factory对象会被暂时性的存储在WeakCache#map中,但是下一步就是会把Factory对象转换为CacheValue,所以Factory仅是构建value过程和数据的封装,并不是真正的缓存值value。
    同时可以看到,WeakCache#size()方法统计的个数视为CacheValue的个数,而不包含Factory对象。所以Factory不需要重写hashCode与equals(),Factory也不会通过WeakCache#map中get()获取到。

在大部分的延迟缓存的设计中,是需要把Factory对象作为缓存值看待,真正需要的时候,再将Factory转换为实际可用value。

七、使用场景

WeakCache是由java.lang.reflect反射包下提供的二级弱缓存类,其主要的应用场景即使JDK动态代理。在JDK动态代理中,用于缓存已经生成的代理类的class对象。其中类型K为ClassLoader(类加载器),P类型为Class<?>[](接口列表),V类型为Class<?>(代理类class对象)。
WeakCache需要两个构造参数subKeyFactory、valueFactory,分别用于构建sub-Key和最终值。因此,JDK动态代理的一级Key就是类加载器实例,二级Key由类加载器和接口列表构成(实际上,只与接口列表相关),最终代理类class对象是由valueFactory生成。

【参考资料】

  1. 知乎-WeakCache介绍
  2. 一文点破WeakCache缓存那点事儿

http://www.mrgr.cn/p/24355703

相关文章

HTML+CSS+JavaScript:轮播图自动播放

一、需求 轮播图如下图所示&#xff0c;需求是每隔一秒轮播图自动切换一次 二、代码素材 以下是缺失JS部分的代码&#xff0c;感兴趣的小伙伴可以先自己试着写一写 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /&…

【期末课程设计】学生成绩管理系统

因其独特&#xff0c;因其始终如一 文章目录 一、学生成绩管理系统介绍 二、学生成绩管理系统设计思路 三、源代码 1. test.c 2. Student Management System.c 3.Stu_System.c 4.Teacher.c 5.Student Management System.h 前言&#xff1a; 学生成绩管理系统含教师…

Android 第三方库CalendarView

Android 第三方库CalendarView 根据需求和库的使用方式&#xff0c;自己弄了一个合适自己的日历&#xff0c;仅记录下&#xff0c;方便下次弄其他样式的日历。地址 需求&#xff1a; 只显示当月的数据 默认的月视图有矩形的线 选中的天数也要有选中的矩形框 今天的item需要…

浏览器安装selenium IDE插件并进行网页测试记录

Chrome开发者工具插件,谷歌浏览器开发者工具插件推荐下载_安装_教程-扩展迷 去官网直接搜索下载需要的插件就可。 插件下载安装-Chrome-扩展迷 下载好后解压&#xff1a; 打开Chrome谷歌浏览器&#xff1a; 设置>拓展程序>打开"开发者模式”>将下载好的seleni…

python结合tesseract-ocr识别汉字的训练库过程

一、安装python 例如&#xff0c;安装路径为&#xff1a;C:\rtkapp\python-3.8.0 二、安装opencv 三、安装tesseract-ocr 安装完成后&#xff0c;在系统环境变量path中&#xff0c;添加安装路径C:\rtkapp\Tesseract-OCR 四、打开python安装pytesseract 五、安装java运行环境…

状态机实现N位按键消抖

状态机实现N位按键消抖 1、原理 利用状态机实现按键的消抖&#xff0c;具体的原理可参考 (50条消息) 基于FPGA的按键消抖_fpga 按键消抖_辣子鸡味的橘子的博客-CSDN博客 状态机简介&#xff1a; 状态机分类可以主要分为两类&#xff1a;moore和mealy 根据三段式状态机最后…

婚庆服务小程序app开发方案详解

开发一款婚庆行业服务小程序有哪些功能呢&#xff1f; 1、选择分类 选择婚庆、婚车、婚宴、司仪、彩妆、婚庆用品、跟拍、摄影等&#xff0c;筛选出对应的商家 2、选择商家 选择分类后&#xff0c;可以选择商家&#xff0c;查看各个商家的详细介绍情况。 3、选择服务套餐 各…

飞书ChatGPT机器人 – 打造智能问答助手实现无障碍交流

文章目录 前言环境列表1.飞书设置2.克隆feishu-chatgpt项目3.配置config.yaml文件4.运行feishu-chatgpt项目5.安装cpolar内网穿透6.固定公网地址7.机器人权限配置8.创建版本9.创建测试企业10. 机器人测试 前言 在飞书中创建chatGPT机器人并且对话&#xff0c;在下面操作步骤中…

gin框架内容(三)--中间件

gin框架内容&#xff08;三&#xff09;--中间件 Gin框架允许开发者在处理请求的过程中&#xff0c;加入用户自己的函数。这个函数就叫中间件&#xff0c;中间件适合处理一些公共的业务逻辑&#xff0c;比如登录认证、权限校验、数据分页、记录日志、耗时统计等 即比如&#x…

Generative Diffusion Prior for Unified Image Restoration and Enhancement 论文阅读笔记

这是CVPR2023的一篇用diffusion先验做图像修复和图像增强的论文 之前有一篇工作做了diffusion先验&#xff08;Bahjat Kawar, Michael Elad, Stefano Ermon, and Jiaming Song, “Denoising diffusion restoration models,” arXiv preprint arXiv:2201.11793, 2022. 2, 4, 6,…

rcu链表综合实践

基础知识 rcu-read copy update的缩写。和读写锁起到相同的效果。据说牛逼一点。对于我们普通程序员&#xff0c;要先学会使用&#xff0c;再探究其内部原理。 链表的数据结构&#xff1a; struct list_head {struct list_head *next, *prev; };还有一种&#xff1a;struct h…

【分布鲁棒、状态估计】分布式鲁棒优化电力系统状态估计研究[几种算法进行比较](Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

VAE-根据李宏毅视频总结的最通俗理解

1.VAE的直观理解 先简单了解一下自编码器&#xff0c;也就是常说的Auto-Encoder。Auto-Encoder包括一个编码器&#xff08;Encoder&#xff09;和一个解码器&#xff08;Decoder&#xff09;。其结构如下&#xff1a; 自编码器是一种先把输入数据压缩为某种编码, 后仅通过该编…

Python - Opencv + pyzbar实时摄像头识别二维码

直接上代码&#xff1a; import cv2 from pyzbar.pyzbar import decodecap cv2.VideoCapture(0) # 打开摄像头while True: # 循环读取摄像头帧ret, frame cap.read()# 在循环中&#xff0c;将每一帧作为图像输入&#xff0c;使用pyzbar的decode()函数识别二维码barcodes …

np.bincount、np.digitize、np.unique、np.histogram、np.searchsorted

np.bincount 简介 np.bincount是统计数组中数字出现数量的函数&#xff0c;数值n在输入数组x中每出现1次&#xff0c;则输出o的o[n]1。 函数 官方文档 函数参数&#xff1a; x: 输入&#xff0c;1维非负数组weights: 权重数组, 可选参数&#xff0c;如果指定了这一参数&am…

函数指针数组

前面学习过数组 指针数组&#xff1a;用来存放数组指针&#xff08;地址&#xff09;的数组 int main() {int arr1[] { 0 };int arr2[] { 0 };int arr3[] { 0 };int* p[3] { arr1,arr2,arr3 };//指针数组return 0; }那么函数指针数组&#xff0c;就是用来存放几个类型相同…

PyTorch - GPU入门教程1

1. 安装GPU版本的PyTorch 登录PyTorch官网https://pytorch.org/&#xff0c;下载对应CUDA版本的PyTorch【不能直接pip install&#xff0c;否则安装上的是CPU版本的】 2. 查看GPU信息 &#xff08;1&#xff09;重要信息 !nvidia-smi我的GPU版本很垃圾&#xff0c;本blog仅…

GitHub上怎么寻找项目?

前言 下面由我精心整理的关于github项目资源搜索的一些方法&#xff0c;这些方法可以帮助你更快更精确的搜寻到你需要的符合你要求的项目。 写文章不易&#xff0c;如果这一篇问文章对你有帮助&#xff0c;求点赞求收藏~ 好&#xff0c;下面我们直接进入正题——> 首先我…

RS485或RS232转ETHERCAT连接ethercat转换器

最近&#xff0c;生产管理设备中经常会遇到两种协议不相同的情况&#xff0c;这严重阻碍了设备之间的通讯&#xff0c;串口设备的数据不能直接传输给ETHERCAT。这可怎么办呢&#xff1f; 别担心&#xff0c;捷米JM-ECT-RS485/232来了&#xff01;这是一款自主研发的ETHERCAT从站…

多线程案例 | 单例模式、阻塞队列、定时器、线程池

多线程案例 1、案例一&#xff1a;线程安全的单例模式 单例模式 单例模式是设计模式的一种 什么是设计模式&#xff1f; 设计模式好比象棋中的 “棋谱”&#xff0c;红方当头炮&#xff0c;黑方马来跳&#xff0c;针对红方的一些走法&#xff0c;黑方应招的时候有一些固定的…