Android学习总结之Glide自定义三级缓存(实战篇)

news2025/5/17 4:55:23

一、为什么需要三级缓存

内存缓存(Memory Cache)

内存缓存旨在快速显示刚浏览过的图片,例如在滑动列表时来回切换的图片。在 Glide 中,内存缓存使用 LruCache 算法(最近最少使用),能自动清理长时间未使用的图片,以此确保内存的合理利用。通常,内存缓存限制在手机可用内存的 15%。举例来说,若手机拥有 8GB 内存,内存缓存大约为 1.2GB。同时,为了进一步优化,图片会按屏幕尺寸进行压缩,比如原图为 2000px,而手机屏幕为 1000px,那么只存储 1000px 版本的图片。当内存缓存超出限制时,会自动清理超出部分的图片。

代码实现

import android.content.Context;
import com.bumptech.glide.Glide;
import com.bumptech.glide.GlideBuilder;
import com.bumptech.glide.load.engine.cache.LruResourceCache;
import com.bumptech.glide.module.AppGlideModule;

public class CustomGlideModule extends AppGlideModule {
    @Override
    public void applyOptions(Context context, GlideBuilder builder) {
        // 获取设备的最大内存
        int maxMemory = (int) Runtime.getRuntime().maxMemory();
        // 计算内存缓存的大小,这里设置为最大内存的15%
        int memoryCacheSize = maxMemory / 1024 / 1024 * 15;
        // 创建LruResourceCache对象
        builder.setMemoryCache(new LruResourceCache(memoryCacheSize));
    }
}

磁盘缓存(Disk Cache)

磁盘缓存用于存储常用但当前不在内存中的图片,像用户经常访问的商品详情页图片。Glide 通过 DiskLruCache 将图片存储在手机硬盘上,总容量一般设置为 100MB,并且优先存储高质量图片。为了优化存储,图片按 URL 哈希值命名文件,这样可以避免重复存储相同图片。同时,对于超过 7 天未使用的图片,会自动进行清理。

代码实现

import android.content.Context;
import com.bumptech.glide.Glide;
import com.bumptech.glide.GlideBuilder;
import com.bumptech.glide.load.engine.cache.DiskLruCacheFactory;
import com.bumptech.glide.module.AppGlideModule;

public class CustomDiskCacheGlideModule extends AppGlideModule {
    @Override
    public void applyOptions(Context context, GlideBuilder builder) {
        // 设置磁盘缓存的路径
        String diskCachePath = context.getCacheDir().getPath() + "/glide_cache";
        // 设置磁盘缓存的大小为100MB
        int diskCacheSize = 1024 * 1024 * 100;
        // 创建DiskLruCacheFactory对象
        builder.setDiskCache(new DiskLruCacheFactory(diskCachePath, diskCacheSize));
    }
}

网络缓存(Network Cache)

网络缓存的作用是避免重复从服务器下载相同图片,这需要结合 HTTP 缓存头来实现。Glide 借助 OkHttp 的缓存机制,将图片存储在路由器或基站缓存中,总容量设置为 50MB,优先存储高频访问的图片。通过根据 HTTP 的 Cache-Control 头设置缓存时间(例如设置为 1 天),以及在图片 URL 中添加版本号(如 image_v2.jpg),当版本更新时强制重新下载,从而实现高效的网络缓存管理。

代码实现

import android.content.Context;
import com.bumptech.glide.Glide;
import com.bumptech.glide.GlideBuilder;
import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.module.AppGlideModule;
import okhttp3.Cache;
import okhttp3.OkHttpClient;

import java.io.InputStream;

public class CustomNetworkCacheGlideModule extends AppGlideModule {
    @Override
    public void registerComponents(Context context, Glide glide, Registry registry) {
        // 设置网络缓存的路径
        Cache cache = new Cache(context.getCacheDir(), 1024 * 1024 * 50);
        // 创建OkHttpClient对象并设置缓存
        OkHttpClient client = new OkHttpClient.Builder()
               .cache(cache)
               .build();
        // 注册OkHttpUrlLoader,让Glide使用OkHttp进行网络请求
        registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory(client));
    }
}

整合代码: 

import android.content.Context;
import com.bumptech.glide.Glide;
import com.bumptech.glide.GlideBuilder;
import com.bumptech.glide.load.engine.cache.LruResourceCache;
import com.bumptech.glide.module.AppGlideModule;

/**
 * 自定义Glide内存缓存配置
 * 通过LruCache算法实现最近最少使用的图片自动回收
 */
public class CustomGlideModule extends AppGlideModule {
    @Override
    public void applyOptions(Context context, GlideBuilder builder) {
        // 获取应用可使用的最大内存(单位:字节)
        int maxMemory = (int) Runtime.getRuntime().maxMemory();
        // 计算内存缓存大小(15%的可用内存)
        int memoryCacheSize = maxMemory / 1024 / 1024 * 15;
        // 创建LruResourceCache并设置缓存大小
        builder.setMemoryCache(new LruResourceCache(memoryCacheSize));
    }
}

import android.content.Context;
import com.bumptech.glide.Glide;
import com.bumptech.glide.GlideBuilder;
import com.bumptech.glide.load.engine.cache.DiskLruCacheFactory;
import com.bumptech.glide.module.AppGlideModule;

/**
 * 自定义Glide磁盘缓存配置
 * 使用DiskLruCache将图片持久化到本地存储
 */
public class CustomDiskCacheGlideModule extends AppGlideModule {
    @Override
    public void applyOptions(Context context, GlideBuilder builder) {
        // 设置磁盘缓存路径(应用缓存目录下的glide_cache文件夹)
        String diskCachePath = context.getCacheDir().getPath() + "/glide_cache";
        // 设置磁盘缓存大小(100MB)
        int diskCacheSize = 1024 * 1024 * 100;
        // 创建DiskLruCache工厂并设置路径和大小
        builder.setDiskCache(new DiskLruCacheFactory(diskCachePath, diskCacheSize));
    }
}

import android.content.Context;
import com.bumptech.glide.Glide;
import com.bumptech.glide.GlideBuilder;
import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.module.AppGlideModule;
import okhttp3.Cache;
import okhttp3.OkHttpClient;

import java.io.InputStream;

/**
 * 自定义Glide网络缓存配置
 * 结合OkHttp实现HTTP级别的网络缓存
 */
public class CustomNetworkCacheGlideModule extends AppGlideModule {
    @Override
    public void registerComponents(Context context, Glide glide, Registry registry) {
        // 创建OkHttp缓存(50MB,位于应用缓存目录)
        Cache cache = new Cache(context.getCacheDir(), 1024 * 1024 * 50);
        // 构建带缓存的OkHttpClient
        OkHttpClient client = new OkHttpClient.Builder()
               .cache(cache)
               .build();
        // 注册OkHttp为Glide的网络请求引擎
        registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory(client));
    }
}

常见问题解决方案

  1. 缓存穿透:缓存穿透指查询一个一定不存在的数据,由于缓存不命中需要从数据库查询,查不到数据则不写入缓存,导致该不存在的数据每次请求都要到数据库查询,给数据库带来压力。在 Glide 中,可以通过设置错误占位图、加载占位图和空值占位图来解决部分问题。例如:
Glide.with(context)
   .load(url)
   .error(R.drawable.ic_error) // 设置错误占位图
   .placeholder(R.drawable.ic_loading) // 设置加载占位图
   .fallback(R.drawable.ic_fallback) // 设置空值占位图
   .into(imageView);

在大厂面试中,关于缓存穿透常被问到的问题有:“请简述缓存穿透的概念以及可能的解决方案”。回答时,除了像上述代码那样通过 Glide 的占位图设置来应对外,还可以提及如使用布隆过滤器(Bloom Filter)等方案。布隆过滤器是一种空间效率极高的概率型数据结构,它利用位数组和哈希函数来判断一个元素是否在一个集合中。将所有已存在的数据 key 放入布隆过滤器中,当新的请求到来时,先通过布隆过滤器判断该 key 是否存在。如果不存在,直接返回,避免查询数据库,从而有效减少不必要的数据库查询,提高系统性能。

  1. 缓存雪崩:缓存雪崩是指在某一时刻,大量缓存同时失效,导致大量请求直接访问数据库,造成数据库压力过大甚至崩溃。可以通过设置不同的缓存过期时间来避免,例如:
int cacheDuration = TimeUnit.HOURS.toMillis(24) + new Random().nextInt(3600000);

面试中可能会被问到:“如何防止缓存雪崩的发生”。除了上述设置随机过期时间的方法外,还可以采用二级缓存策略,即设置主缓存和备用缓存。主缓存失效后,先从备用缓存获取数据,同时对主缓存进行异步更新,这样可以在一定程度上缓解大量请求直接冲击数据库的问题。另外,使用互斥锁也是一种思路,在缓存失效时,只有一个线程能够获取锁去更新缓存,其他线程等待,避免大量线程同时查询数据库。

  1. OOM 预防:OOM(Out Of Memory,内存溢出)在图片加载中较为常见,因为图片占用内存较大。可以通过使用 RGB_565 格式减少内存占用,例如:
// 使用RGB_565格式减少内存占用
Glide.with(context)
   .load(url)
   .format(DecodeFormat.PREFER_RGB_565)
   .into(imageView);

面试官可能会问:“在 Glide 中,如何预防 OOM 问题”。除了设置图片格式外,还可以根据设备内存情况动态调整图片尺寸。例如,获取设备的可用内存,当内存较低时,对图片进行更大比例的压缩。同时,合理配置 Glide 的内存缓存大小也很关键,避免缓存占用过多内存。此外,及时释放不再使用的图片资源,Glide 通过与 Activity 或 Fragment 的生命周期绑定,在界面不可见时及时清理相关图片资源,防止内存泄漏。

关键指标的获取途径

  1. 冷启动加载时间:借助 Android Profiler 的 Timeline 功能来精准测量。在应用启动时,启动 Profiler 并记录图片加载所耗费的时长。代码示例如下:
long startTime = System.currentTimeMillis();
Glide.with(this).load(url).into(imageView);
long duration = System.currentTimeMillis() - startTime;
Log.d("GlideTest", "加载耗时: " + duration + "ms");
  1. 内存峰值占用情况:使用 Android Profiler 的 Memory Monitor 进行监测。在滑动列表时,留意 Heap Size 的变化趋势,对比开启缓存前后 Bitmap 内存占用的差异,以此来优化内存使用。
  2. 缓存命中率计算:通过 Glide 的日志输出(设置 Glide.get (context).setLogLevel (Log.DEBUG)),从日志中筛选出 Fetched 和 Decoded 相关的条目。缓存命中率 = (内存命中数 + 磁盘命中数)÷ 总请求数 × 100%。
  3. FPS 帧率监控:采用 Android Profiler 的 FrameMetrics 功能。在滑动列表的过程中,记录丢帧的数量,确保平均帧率稳定在 55fps 以上,以保证流畅的用户体验。

二、自定义图片缓存框架

设计思路

  1. 内存缓存:运用 LruCache(Least Recently Used Cache,最近最少使用缓存)实现内存缓存,它能够自动回收最近最少使用的图片,保障内存的合理使用。
  2. 磁盘缓存:利用 DiskLruCache 实现磁盘缓存,将图片持久化到本地磁盘,方便在网络不可用或需要重复使用图片时快速获取。
  3. 多级缓存策略:首先从内存缓存中查找图片,若未找到则从磁盘缓存中查找,最后才从网络请求图片。当从网络获取到图片后,同时将其存入内存缓存和磁盘缓存。

代码实现

import android.graphics.Bitmap;
import android.util.LruCache;

/**
 * 内存缓存实现
 * 使用LruCache(最近最少使用)算法管理内存中的图片
 */
public class MemoryCache {
    // LruCache实例,用于存储图片(键为图片URL,值为Bitmap)
    private LruCache<String, Bitmap> lruCache;

    public MemoryCache() {
        // 获取应用最大可用内存(KB)
        int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        // 设置缓存大小为最大内存的1/8
        int cacheSize = maxMemory / 8;

        // 初始化LruCache并重写sizeOf方法计算每个Bitmap的大小
        lruCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                // 返回Bitmap占用的内存大小(KB)
                return bitmap.getByteCount() / 1024;
            }
        };
    }

    // 向缓存添加图片
    public void put(String key, Bitmap bitmap) {
        if (get(key) == null) {
            lruCache.put(key, bitmap);
        }
    }

    // 从缓存获取图片
    public Bitmap get(String key) {
        return lruCache.get(key);
    }

    // 从缓存移除图片
    public void remove(String key) {
        lruCache.remove(key);
    }
}

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Environment;
import com.jakewharton.disklrucache.DiskLruCache;

import java.io.*;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * 磁盘缓存实现
 * 使用DiskLruCache将图片持久化到本地存储
 */
public class DiskCache {
    // 应用版本(用于缓存版本控制)
    private static final int APP_VERSION = 1;
    // 每个缓存项对应的值数量
    private static final int VALUE_COUNT = 1;
    // 磁盘缓存最大容量(10MB)
    private static final long CACHE_SIZE = 10 * 1024 * 1024;

    // DiskLruCache实例
    private DiskLruCache diskLruCache;

    public DiskCache(Context context) {
        try {
            // 获取缓存目录
            File cacheDir = getDiskCacheDir(context, "bitmap");
            if (!cacheDir.exists()) {
                cacheDir.mkdirs();
            }
            // 打开DiskLruCache实例
            diskLruCache = DiskLruCache.open(cacheDir, APP_VERSION, VALUE_COUNT, CACHE_SIZE);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 向磁盘缓存添加图片
    public void put(String key, Bitmap bitmap) {
        DiskLruCache.Editor editor = null;
        try {
            // 获取缓存编辑器
            editor = diskLruCache.edit(hashKeyForDisk(key));
            if (editor != null) {
                // 获取输出流并写入图片(JPEG格式,质量100%)
                OutputStream outputStream = editor.newOutputStream(0);
                if (bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)) {
                    editor.commit();
                } else {
                    editor.abort();
                }
                outputStream.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 从磁盘缓存获取图片
    public Bitmap get(String key) {
        try {
            // 获取缓存快照
            DiskLruCache.Snapshot snapshot = diskLruCache.get(hashKeyForDisk(key));
            if (snapshot != null) {
                // 从输入流解码Bitmap
                InputStream inputStream = snapshot.getInputStream(0);
                return BitmapFactory.decodeStream(inputStream);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    // 从磁盘缓存移除图片
    public void remove(String key) {
        try {
            diskLruCache.remove(hashKeyForDisk(key));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 获取磁盘缓存目录
    private File getDiskCacheDir(Context context, String uniqueName) {
        String cachePath;
        // 判断外部存储是否可用
        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
                ||!Environment.isExternalStorageRemovable()) {
            cachePath = context.getExternalCacheDir().getPath();
        } else {
            cachePath = context.getCacheDir().getPath();
        }
        return new File(cachePath + File.separator + uniqueName);
    }

    // 生成URL的MD5哈希值作为缓存键
    private String hashKeyForDisk(String key) {
        String cacheKey;
        try {
            // 使用MD5算法生成哈希值
            final MessageDigest mDigest = MessageDigest.getInstance("MD5");
            mDigest.update(key.getBytes());
            cacheKey = bytesToHexString(mDigest.digest());
        } catch (NoSuchAlgorithmException e) {
            // 若不支持MD5,使用普通哈希码
            cacheKey = String.valueOf(key.hashCode());
        }
        return cacheKey;
    }

    // 字节数组转十六进制字符串
    private String bytesToHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            String hex = Integer.toHexString(0xFF & b);
            if (hex.length() == 1) {
                sb.append('0');
            }
            sb.append(hex);
        }
        return sb.toString();
    }
}

import android.content.Context;
import android.graphics.Bitmap;

/**
 * 多级缓存管理器
 * 统一管理内存缓存和磁盘缓存
 */
public class ImageCacheManager {
    // 内存缓存实例
    private MemoryCache memoryCache;
    // 磁盘缓存实例
    private DiskCache diskCache;

    public ImageCacheManager(Context context) {
        memoryCache = new MemoryCache();
        diskCache = new DiskCache(context);
    }

    // 同时存入内存缓存和磁盘缓存
    public void put(String key, Bitmap bitmap) {
        memoryCache.put(key, bitmap);
        diskCache.put(key, bitmap);
    }

    // 优先从内存缓存获取,再从磁盘缓存获取
    public Bitmap get(String key) {
        Bitmap bitmap = memoryCache.get(key);
        if (bitmap != null) {
            return bitmap;
        }
        bitmap = diskCache.get(key);
        if (bitmap != null) {
            // 从磁盘读取后存入内存,提升下次访问速度
            memoryCache.put(key, bitmap);
        }
        return bitmap;
    }
}
  1. 请简述三级缓存(内存缓存、磁盘缓存、网络缓存)的作用和原理。

    • 内存缓存:旨在快速显示刚浏览过的图片,使用 LruCache 算法(最近最少使用),自动清理长时间未使用的图片,确保内存的合理利用。通常限制在手机可用内存的 15%。
    • 磁盘缓存:用于存储常用但当前不在内存中的图片,通过 DiskLruCache 将图片存储在手机硬盘上,设置总容量(如 100MB),优先存储高质量图片,按 URL 哈希值命名文件以避免重复存储,超过 7 天未使用的图片会自动清理。
    • 网络缓存:避免重复从服务器下载相同图片,结合 HTTP 缓存头,借助 OkHttp 的缓存机制,将图片存储在路由器或基站缓存中,设置总容量(如 50MB),优先存储高频访问的图片,根据 HTTP 的 Cache-Control 头设置缓存时间,并在图片 URL 中添加版本号以强制重新下载。
  2. 在自定义图片缓存框架中,LruCache 和 DiskLruCache 分别是如何实现的?

    • LruCache:在内存缓存类中,获取应用程序运行时的最大可用内存,使用最大可用内存的一部分(如 1/8)作为 LruCache 的缓存大小。重写 sizeOf 方法,计算每个图片对象占用的内存大小,通过 put 方法添加图片到缓存,get 方法获取图片,remove 方法移除图片。
    • DiskLruCache:在磁盘缓存类中,初始化时获取磁盘缓存的目录,打开 DiskLruCache 实例。put 方法通过获取编辑器和输出流,将图片以 JPEG 格式压缩并写入;get 方法通过获取快照和输入流,将输入流解码为 Bitmap 对象;remove 方法移除指定的图片。对键进行 MD5 哈希处理,确保键的唯一性。
  3. 如何防止缓存穿透、缓存雪崩和 OOM 问题?

    • 缓存穿透:在 Glide 中,可以通过设置错误占位图、加载占位图和空值占位图来解决部分问题。另外,可以使用布隆过滤器,将所有已存在的数据 key 放入布隆过滤器中,当新的请求到来时,先通过布隆过滤器判断该 key 是否存在,避免不必要的数据库查询。
    • 缓存雪崩:可以通过设置不同的缓存过期时间来避免,例如在设置缓存过期时间时,添加一个随机值。另外,采用二级缓存策略,设置主缓存和备用缓存,主缓存失效后,先从备用缓存获取数据,同时对主缓存进行异步更新。使用互斥锁,在缓存失效时,只有一个线程能够获取锁去更新缓存,其他线程等待。
    • OOM:在 Glide 中,可以使用 RGB_565 格式减少内存占用,根据设备内存情况动态调整图片尺寸,合理配置 Glide 的内存缓存大小,避免缓存占用过多内存。及时释放不再使用的图片资源,Glide 通过与 Activity 或 Fragment 的生命周期绑定,在界面不可见时及时清理相关图片资源,防止内存泄漏。

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

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

相关文章

LeetCode100.2 字母异位词分组

观察题目&#xff0c;需要把strs中的元素按照字母进行归类&#xff0c;一个朴素的思路是&#xff1a;遍历strs&#xff0c;对每个元素排序后插入哈希表中&#xff0c;随后再遍历一遍表将其转化为vector<vector<string>>。 class Solution { public:vector<vect…

显示的图标跟UI界面对应不上。

图片跟UI界面不符合。 要找到对应dp的值。UI的dp要跟代码里的xml文件里的dp要对应起来。 蓝湖里设置一个宽度给对应上。然后把对应的值填入xml. 一个屏幕上的图片到底是用topmarin来设置&#xff0c;还是用bottommarin来设置。 因为第一节&#xff0c;5&#xff0c;7 车厢的…

高并发内存池(三):TLS无锁访问以及Central Cache结构设计

目录 前言&#xff1a; 一&#xff0c;thread cache线程局部存储的实现 问题引入 概念说明 基本使用 thread cache TLS的实现 二&#xff0c;Central Cache整体的结构框架 大致结构 span结构 span结构的实现 三&#xff0c;Central Cache大致结构的实现 单例模式 thr…

在Taro中开发一个跨端Svg组件,同时支持小程序、H5、React Native

Taro系列中一直没有跨端的绘图工具&#xff0c;小程序端支持canvas但是不支持svg&#xff0c;RN端有 react-native-svg 支持svg&#xff0c;但是没有很好原生的canvas插件&#xff0c;社区的canvas都是基于WebView实现的&#xff0c;或者skia&#xff0c;这个插件的书写方式和c…

【办公类-100-01】20250515手机导出教学照片,自动上传csdn+最大化、最小化Vs界面

背景说明&#xff1a; 每次把教学照片上传csdn&#xff0c;都需要打开相册&#xff0c;一张张截图&#xff0c;然后ctrlV黏贴到CSDN内&#xff0c;我觉得太烦了。 改进思路&#xff1a; 是否可以先把所有照片都上传到csdn&#xff0c;然后再一张张的截图&#xff08;去掉幼儿…

Python零基础入门到高手8.4节: 元组与列表的区别

目录 8.4.1 不可变数据类型 8.4.2 可变数据类型 8.4.3 元组与列表的区别 8.4.4 今天彩票没中奖 8.4.1 不可变数据类型 不可变数据类型是指不可以对该数据类型进行原地修改&#xff0c;即只读的数据类型。迄今为止学过的不可变数据类型有字符串&#xff0c;元组。 在使用[]…

深度学习入门:深度学习(完结)

目录 1、加深网络1.1 向更深的网络出发1.2 进一步提高识别精度1.3 加深层的动机 2、深度学习的小历史2.1 ImageNet2.2 VGG2.3 GoogleNet2.4 ResNet 3、深度学习的高速化3.1 需要努力解决的问题3.2 基于GPU的高速化3.3 分布式学习3.4 运算精度的位数缩减 4、深度学习的应用案例4…

使用Scrapeless Scraping Browser的自动化和网页抓取最佳实践

引言&#xff1a;人工智能时代浏览器自动化和数据收集的新范式 随着生成性人工智能、人工智能代理和数据密集型应用程序的快速崛起&#xff0c;浏览器正在从传统的“用户互动工具”演变为智能系统的“数据执行引擎”。在这一新范式中&#xff0c;许多任务不再依赖单一的API端点…

java数组题(5)

&#xff08;1&#xff09;&#xff1a; 思路&#xff1a; 1.首先要对数组nums排序&#xff0c;这样两数之间的差距最小。 2.题目要求我们通过最多 k 次递增操作&#xff0c;使数组中某个元素的频数&#xff08;出现次数&#xff09;最大化。经过上面的排序&#xff0c;最大数…

物联网无线传感方向专业词汇解释

涡旋电磁波(VEMW)&#xff1a;一种具有轨道角动量的电磁波&#xff0c;其特性在于能够在传播过程中携带额外的相位信息&#xff0c;从而增加通信系统的容量和灵活性。波前&#xff1a;波动传播过程中&#xff0c;同一时刻振动相位相同的所有点构成的几何曲面&#xff0c;代表波…

Maven 插件参数注入与Mojo开发详解

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;精通Java编…

2024年全国青少年信息素养大赛——算法创意实践挑战赛复赛真题(小学组)——玫瑰花地的面积

2024年全国青少年信息素养大赛——算法创意实践挑战赛复赛真题(小学组)——玫瑰花地的面积 上面试卷可点下方&#xff0c;支持在线编程&#xff0c;在线测评&#xff5e; 2024年全国信息素养大赛 算法创意实践挑战赛复赛(小学组)_c_少儿编程题库学习中心-嗨信奥 5月17号 全国青…

【补充笔记】修复“NameError: name ‘ZhNormalizer‘ is not defined”的直接方法

#工作记录 一、问题描述 在运行CosyVoice_For_Windows项目时&#xff0c;出现以下报错&#xff1a; File "F:\PythonProjects\CosyVoice_For_Windows\cosyvoice\cli\frontend.py", line 74, in __init__ self.zh_tn_model ZhNormalizer(remove_erhuaFalse, fu…

预训练模型实战手册:用BERT/GPT-2微调实现10倍效率提升,Hugging Face生态下的迁移学习全链路实践

更多AI大模型应用开发学习内容&#xff0c;尽在聚客AI学院。 一. 预训练模型&#xff08;PTM&#xff09;核心概念 1.1 什么是预训练模型&#xff1f; 预训练模型&#xff08;Pre-trained Model, PTM&#xff09;是在大规模通用数据上预先训练的模型&#xff0c;通过自监督学…

mac docker弹窗提示Docker 启动没有响应

一、原因分析 这台笔记电脑是Mac M3操作系统,安装Docker之后,Docker应用程序一直启动不起来。 二、解决办法 sudo rm /Library/PrivilegedHelperTools/com.docker.vmnetd sudo cp /Applications/Docker.app/Contents/Library/LaunchServices/com.docker.vmnetd /Library/Pri…

Ubuntu 22.04搭建OpenStreeMap地址解析服务(保姆级教程)

1.数据准备 1.1.全球数据 下载地址&#xff1a;https://planet.openstreetmap.org/ 1.2.特定区域的数据 下载地址&#xff1a;Geofabrik Download Server 2.安装必要的软件包 2.1.更新系统软件包 sudo apt updatesudo apt upgrade 2.2.安装所需要的软件包 执行下面的命…

sqli—labs第五关——报错注入

一&#xff1a;判断输入类型 首先测试 ?id1 回显You are in... 渐进测试?id1 报错分析&#xff1a; 出现引号提示——“”&#xff0c;可能是字符型 继续测试?id1--&#xff08;用注释符修复了语法错误&#xff09; 回显You are in... 说明就是字符型 因为能用注释符…

从海洋生物找灵感:造个机器人RoboPteropod,它能在水下干啥?

大家好&#xff01;在如今人类对水下环境探索不断深入的时代&#xff0c;从水下考古到珊瑚礁考察&#xff0c;各种任务都离不开水下机器人的助力。但传统水下机器人尺寸较大&#xff0c;在狭窄的水下空间施展不开。今天&#xff0c;我们就来认识一款受海洋小生物启发而设计的仿…

FastAPI系列16:从API文档到TypeScript 前端客户端(SDKs)

从API文档到TypeScript 前端客户端&#xff08;SDKs&#xff09; 快速入门生成一个TypeScript 客户端测试生成的TypeScript 客户端 API标签与客户端生成生成带有标签的 TypeScript 客户端 自定义Operation ID使用自定义Operation ID生成TypeScript客户端 在 FastAPI系列15&…

CS016-2-unity ecs

目录 【23】射击改进 【24】僵尸生成器 ​编辑【25】随机行走 【27】射击光效 【23】射击改进 a. 当距离目标太远的时候&#xff0c;要继续移动。而当距离目标到达攻击距离之后&#xff0c;则停止移动。 上图中的if&#xff1a;判断自身和目标的距离是否大于攻击距离&#…