图片加载框架Glide解析

2024-06-02 00:18

本文主要是介绍图片加载框架Glide解析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

对比Picasso内存占用

Glide是一个高效、开源、 Android设备上的媒体管理框架,它遵循BSD、MIT以及Apache 2.0协议发布。Glide具有获取、解码和展示视频剧照、图片、动画等功能,它还有灵活的API,这些API使开发者能够将Glide应用在几乎任何网络协议栈里。创建Glide的主要目的有两个,一个是实现平滑的图片列表滚动效果,另一个是支持远程图片的获取、大小调整和展示。近日,Glide 3.0发布,现已提供jar包下载,同时还支持使用Gradle以及Maven进行构建。该版本包括很多值得关注的新功能,如支持Gif 动画和视频剧照解码、智能的暂停和重新开始请求、支持缩略图等,具体新增功能如下如下:GIF动画的解码:通过调用Glide.with(context).load(“图片路径“)方法,GIF动画图片可以自动显示为动画效果。如果想有更多的控制,还可以使用Glide.with(context).load(“图片路径“).asBitmap()方法加载静态图片,使用Glide.with(context).load(“图片路径“).asGif()方法加载动画图片



1.本地视频剧照的解码:通过调用Glide.with(context).load(“图片路径“)方法,Glide能够支持Android设备中的所有视频剧照的加载和展示

2.缩略图的支持:为了减少在同一个view组件里同时加载多张图片的时间,可以调用Glide.with(context).load(“图片路径“).thumbnail(“缩略比例“).into(“view组件“)方法加载一个缩略图,还可以控制thumbnail()中的参数的大小,以控制显示不同比例大小的缩略图

3.Activity生命周期的集成:当Activity暂停和重启时,Glide能够做到智能的暂停和重新开始请求,并且当Android设备的连接状态变化时,所有失败的请求能够自动重新请求
4.转码的支持:Glide的toBytes() 和transcode() 两个方法可以用来获取、解码和变换背景图片,并且transcode() 方法还能够改变图片的样式
5.动画的支持:新增支持图片的淡入淡出动画效果(调用crossFade()方法)和查看动画的属性的功能
6.OkHttp和Volley的支持:默认选择HttpUrlConnection作为网络协议栈,还可以选择OkHttp和Volley作为网络协议

7.其他功能:如在图片加载过程中,使用Drawables对象作为占位符、图片请求的优化、图片的宽度和高度可重新设定、缩略图和原图的缓存等功能


Glide.class


利用这个方法可以获得一个RequestManager对象.


RequestManager.class

这个类主要是用来处理低内存的时候的处理;管理请求,比如请求的开始,暂停,结束;还有通过加载方法获得一个DrawableTypeRequest对象。

低内存处理方法:


管理请求的方法:与activity或者fragment的生命周期同步。


加载的方法:


load方法都是设置了一个泛型,最后都调用了:

DrawableRequestBuilder.class

 @Overridepublic DrawableRequestBuilder<ModelType> load(ModelType model) {super.load(model);return this;}
接着调用了GenericRequestBuilder类里面的方法,就是设置加载的参数到GenericRequestBuilder的this.model里:

GenericRequestBuilder.class

 public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> load(ModelType model) {this.model = model;isModelSet = true;return this;}


这里接触了两个类,一个是GenericRequestBuilder,一个是DrawableRequestBuilder。

由GenericRequestBuilder得四种泛型参数可以看出, 这个类提供了加载各种资源的方法,是各种资源请求构建类的父类:


通过以上方法来加载缩略图。



第一个是对原数据的解码。

第二个是对磁盘缓存的数据进行解码。

第三个是对请求的数据进行编码,然后存到缓存里,之后从缓存中拿出的时候要通过解码。


设置磁盘缓存的内容,DiskCacheStrategy类是个枚举类,他提供了四个参数:

/** Caches with both {@link #SOURCE} and {@link #RESULT}. */ALL(true, true),/** Saves no data to cache. */NONE(false, false),/** Saves just the original data to cache. */SOURCE(true, false),/** Saves the media item after all transformations to cache. */RESULT(false, true);
all:缓存源资源和转换后的资源

none:不作任何磁盘缓存

source:缓存源资源

result:缓存转换后的资源

priority(Priority priority)
设置当次请求的优先级。

transform(Transformation<ResourceType>... transformations)
设置对资源进行转换的接口:

对于加载一般的图片到Imageview中,默认提供了CenterCrop和FitCenter两种实现。

dontTransform()
移除当前的转换。

transcoder(ResourceTranscoder<ResourceType, TranscodeType> transcoder)


设置资源加载完成后的动画,不包括从内存缓存中获取。

dontAnimate()
移除设置的动画。

placeholder(int resourceId)
placeholder(Drawable drawable)
设置加载的时候的图片。

error(int resourceId)
error(Drawable drawable)
设置加载失败后显示的图片

listener(RequestListener<? super ModelType, TranscodeType> requestListener)
设置加载监听。

这个监听接口中有两个回调方法:

skipMemoryCache(boolean skip)
设置是否跳过内存缓存。

override(int width, int height)
设置加载资源图片的像素宽高。

signature(Key signature)


load(ModelType model)
设置要加载的内容。

通过上面的方法开启加载,中间的方法直接加载到view上,前后的方法用于预加载。


预加载,真正调用的是上面的两个into方法。


接着看子类DrawableRequestBuilder里面特有的方法:

DrawableRequestBuilder类不仅继承了父类GenericRequestBuilder里面的方法,还显示了BitmapOptions和DrawableOptions两个接口:



一个是控制bitmap的显示的位置或者大小(就是bitmap的转换),一个是控制图片显示出来时候的动画效果(不包括从内存中获取的图片资源)。接着看这些方法的具体实现:

 public DrawableRequestBuilder<ModelType> centerCrop() {return transform(glide.getDrawableCenterCrop());}
public DrawableRequestBuilder<ModelType> fitCenter() {return transform(glide.getDrawableFitCenter());}
将Glide中两个BitmapTransformation进行包装后,通过间接调用父类的transform设置到父类的transformation属性中。

public final DrawableRequestBuilder<ModelType> crossFade() {super.animate(new DrawableCrossFadeFactory<GlideDrawable>());return this;}

而设置淡入淡出效果其实是设置了默认动画实现的DrawableCrossFadeFactory类到父类animationFactory属性中。


 public <Y extends Target<TranscodeType>> Y into(Y target) {Util.assertMainThread();if (target == null) {throw new IllegalArgumentException("You must pass in a non null Target");}if (!isModelSet) {throw new IllegalArgumentException("You must first set a model (try #load())");}Request previous = target.getRequest();if (previous != null) {previous.clear();requestTracker.removeRequest(previous);previous.recycle();}Request request = buildRequest(target);target.setRequest(request);lifecycle.addListener(target);requestTracker.runRequest(request);return target;}
这里构建好request的是个GenericRequest类型,然后就通过requestTracker.runRequest(request);来调用GenericRequest里的begin方法:

  @Overridepublic void begin() {startTime = LogTime.getLogTime();if (model == null) {onException(null);return;}status = Status.WAITING_FOR_SIZE;if (Util.isValidDimensions(overrideWidth, overrideHeight)) {onSizeReady(overrideWidth, overrideHeight);} else {target.getSize(this);}if (!isComplete() && !isFailed() && canNotifyStatusChanged()) {target.onLoadStarted(getPlaceholderDrawable());}if (Log.isLoggable(TAG, Log.VERBOSE)) {logV("finished run method in " + LogTime.getElapsedMillis(startTime));}}
这里的target.getSize(this);最终也是调用onSizeReady()方法:

 public void onSizeReady(int width, int height) {if (Log.isLoggable(TAG, Log.VERBOSE)) {logV("Got onSizeReady in " + LogTime.getElapsedMillis(startTime));}if (status != Status.WAITING_FOR_SIZE) {return;}status = Status.RUNNING;width = Math.round(sizeMultiplier * width);height = Math.round(sizeMultiplier * height);ModelLoader<A, T> modelLoader = loadProvider.getModelLoader();final DataFetcher<T> dataFetcher = modelLoader.getResourceFetcher(model, width, height);if (dataFetcher == null) {onException(new Exception("Got null fetcher from model loader"));return;}ResourceTranscoder<Z, R> transcoder = loadProvider.getTranscoder();if (Log.isLoggable(TAG, Log.VERBOSE)) {logV("finished setup for calling load in " + LogTime.getElapsedMillis(startTime));}loadedFromMemoryCache = true;loadStatus = engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder,priority, isMemoryCacheable, diskCacheStrategy, this);loadedFromMemoryCache = resource != null;if (Log.isLoggable(TAG, Log.VERBOSE)) {logV("finished onSizeReady in " + LogTime.getElapsedMillis(startTime));}}
接着调用了Engine类的load方法,这个类就是加载数据的引擎类:

 public <T, Z, R> LoadStatus load(Key signature, int width, int height, DataFetcher<T> fetcher,DataLoadProvider<T, Z> loadProvider, Transformation<Z> transformation, ResourceTranscoder<Z, R> transcoder,Priority priority, boolean isMemoryCacheable, DiskCacheStrategy diskCacheStrategy, ResourceCallback cb) {Util.assertMainThread();long startTime = LogTime.getLogTime();final String id = fetcher.getId();EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),transcoder, loadProvider.getSourceEncoder());EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);if (cached != null) {cb.onResourceReady(cached);if (Log.isLoggable(TAG, Log.VERBOSE)) {logWithTimeAndKey("Loaded resource from cache", startTime, key);}return null;}EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);if (active != null) {cb.onResourceReady(active);if (Log.isLoggable(TAG, Log.VERBOSE)) {logWithTimeAndKey("Loaded resource from active resources", startTime, key);}return null;}EngineJob current = jobs.get(key);if (current != null) {current.addCallback(cb);if (Log.isLoggable(TAG, Log.VERBOSE)) {logWithTimeAndKey("Added to existing load", startTime, key);}return new LoadStatus(cb, current);}EngineJob engineJob = engineJobFactory.build(key, isMemoryCacheable);DecodeJob<T, Z, R> decodeJob = new DecodeJob<T, Z, R>(key, width, height, fetcher, loadProvider, transformation,transcoder, diskCacheProvider, diskCacheStrategy, priority);EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);jobs.put(key, engineJob);engineJob.addCallback(cb);engineJob.start(runnable);if (Log.isLoggable(TAG, Log.VERBOSE)) {logWithTimeAndKey("Started new load", startTime, key);}return new LoadStatus(cb, engineJob);}
这个方法会先从内存缓存中去获取,没有就构建一个EngineJob引擎任务类,这个类会将一个任务(runnable对象)提交到线程池中,然后线程池经过一系列的调度,调用了EngineRunnable对象的run方法:

    public void run() {if (isCancelled) {return;}Exception exception = null;Resource<?> resource = null;try {resource = decode();} catch (Exception e) {if (Log.isLoggable(TAG, Log.VERBOSE)) {Log.v(TAG, "Exception decoding", e);}exception = e;}if (isCancelled) {if (resource != null) {resource.recycle();}return;}if (resource == null) {onLoadFailed(exception);} else {onLoadComplete(resource);}}
这里的decode()方法会先从磁盘缓存中去取数据,如果没有就源资源(网络中下载的资源)中获取:

 private Resource<?> decode() throws Exception {if (isDecodingFromCache()) {return decodeFromCache();} else {return decodeFromSource();}}
1.首先来看从磁盘缓存中获取资源:
 private Resource<?> decodeFromCache() throws Exception {Resource<?> result = null;try {result = decodeJob.decodeResultFromCache();} catch (Exception e) {if (Log.isLoggable(TAG, Log.DEBUG)) {Log.d(TAG, "Exception decoding result from cache: " + e);}}if (result == null) {result = decodeJob.decodeSourceFromCache();}return result;}
这里有两个方法:第一个,decodeJob.decodeResultFromCache(),如果设置缓存的是DiskCacheStrategy.RESULT,那么磁盘缓存中缓存的是transformed后的结果,所以就会调用该方法:

  public Resource<Z> decodeResultFromCache() throws Exception {if (!diskCacheStrategy.cacheResult()) {return null;}long startTime = LogTime.getLogTime();Resource<T> transformed = loadFromCache(resultKey);if (Log.isLoggable(TAG, Log.VERBOSE)) {logWithTimeAndKey("Decoded transformed from cache", startTime);}startTime = LogTime.getLogTime();Resource<Z> result = transcode(transformed);if (Log.isLoggable(TAG, Log.VERBOSE)) {logWithTimeAndKey("Transcoded transformed from cache", startTime);}return result;}
这里首先通过loadFromCache(resultKey)方法从磁盘缓存文件中decode已经经过transformed的资源(默认是使用UnitTransformation,在这里面没有做任何的资源转换),然后调用transcode(transformed)方法:该方法是把从磁盘decode出来的资源通过transcoder.transcode(transformed)来进行转换(这个方法是用来转换资源的类型用的,默认是使用UnitTranscoder,没有做任何操作)

第二个,decodeJob.decodeSourceFromCache(),如果设置缓存的是DiskCacheStrategy.SOURCE,那么磁盘缓存中缓存的是原始数据,所以就会调用该方法:

public Resource<Z> decodeSourceFromCache() throws Exception {if (!diskCacheStrategy.cacheSource()) {return null;}long startTime = LogTime.getLogTime();Resource<T> decoded = loadFromCache(resultKey.getOriginalKey());if (Log.isLoggable(TAG, Log.VERBOSE)) {logWithTimeAndKey("Decoded source from cache", startTime);}return transformEncodeAndTranscode(decoded);}
这里首先从磁盘缓存中decode原数据,然后再调用transformEncodeAndTranscode(decoded)方法先进行transform,transform后会通过writeTransformedToCache(transformed);将经过转换的资源存入磁盘缓存,然后再进行transcode:

 private Resource<Z> transformEncodeAndTranscode(Resource<T> decoded) {long startTime = LogTime.getLogTime();Resource<T> transformed = transform(decoded);if (Log.isLoggable(TAG, Log.VERBOSE)) {logWithTimeAndKey("Transformed resource from source", startTime);}writeTransformedToCache(transformed);startTime = LogTime.getLogTime();Resource<Z> result = transcode(transformed);if (Log.isLoggable(TAG, Log.VERBOSE)) {logWithTimeAndKey("Transcoded transformed from source", startTime);}return result;}
2.再来看从源资源获取资源:

private Resource<?> decodeFromSource() throws Exception {return decodeJob.decodeFromSource();}

 public Resource<Z> decodeFromSource() throws Exception {Resource<T> decoded = decodeSource();return transformEncodeAndTranscode(decoded);}
这里调用了decodeSource(),这个方法是从网络中获取data资源:

 private Resource<T> decodeSource() throws Exception {Resource<T> decoded = null;try {long startTime = LogTime.getLogTime();final A data = fetcher.loadData(priority);if (Log.isLoggable(TAG, Log.VERBOSE)) {logWithTimeAndKey("Fetched data", startTime);}if (isCancelled) {return null;}decoded = decodeFromSourceData(data);} finally {fetcher.cleanup();}return decoded;}
获取到网络资源后调用decodeFromSourceData(data)方法:

private Resource<T> decodeFromSourceData(A data) throws IOException {final Resource<T> decoded;if (diskCacheStrategy.cacheSource()) {decoded = cacheAndDecodeSourceData(data);} else {long startTime = LogTime.getLogTime();decoded = loadProvider.getSourceDecoder().decode(data, width, height);if (Log.isLoggable(TAG, Log.VERBOSE)) {logWithTimeAndKey("Decoded from source", startTime);}}return decoded;}
这个方法里通过diskCacheStrategy.cacheSource()判断资源是否是将源数据保存在磁盘里,如果是则会调用cacheAndDecodeSourceData(A data):

private Resource<T> cacheAndDecodeSourceData(A data) throws IOException {long startTime = LogTime.getLogTime();SourceWriter<A> writer = new SourceWriter<A>(loadProvider.getSourceEncoder(), data);diskCacheProvider.getDiskCache().put(resultKey.getOriginalKey(), writer);if (Log.isLoggable(TAG, Log.VERBOSE)) {logWithTimeAndKey("Wrote source to cache", startTime);}startTime = LogTime.getLogTime();Resource<T> result = loadFromCache(resultKey.getOriginalKey());if (Log.isLoggable(TAG, Log.VERBOSE) && result != null) {logWithTimeAndKey("Decoded source from cache", startTime);}return result;}
这里先将原数据保存在磁盘缓存里,然后再decode出来。

但是如果不需要保存源数据,就直接decode源数据:

 private Resource<T> decodeFromSourceData(A data) throws IOException {final Resource<T> decoded;if (diskCacheStrategy.cacheSource()) {decoded = cacheAndDecodeSourceData(data);} else {long startTime = LogTime.getLogTime();decoded = loadProvider.getSourceDecoder().decode(data, width, height);if (Log.isLoggable(TAG, Log.VERBOSE)) {logWithTimeAndKey("Decoded from source", startTime);}}return decoded;}


decodeSource()方法执行完之后,接着执行transformEncodeAndTranscode(decoded):

 private Resource<Z> transformEncodeAndTranscode(Resource<T> decoded) {long startTime = LogTime.getLogTime();Resource<T> transformed = transform(decoded);if (Log.isLoggable(TAG, Log.VERBOSE)) {logWithTimeAndKey("Transformed resource from source", startTime);}writeTransformedToCache(transformed);startTime = LogTime.getLogTime();Resource<Z> result = transcode(transformed);if (Log.isLoggable(TAG, Log.VERBOSE)) {logWithTimeAndKey("Transcoded transformed from source", startTime);}return result;}

这里是将直接从源数据decode出来资源后,经过transform的转换,然后将transform后的资源存进磁盘缓存,然后在经过transcode方法来进行资源类型的转换(默认是使用UnitTranscoder,没有做任何操作)。还是提供逻辑图,一目了然吧:


这个是基本流程图:

使用方法:

1.设置的不是imageview,可以用SimpleTarget来接受,然后在回调里面设置view的现实(但是缺少目标view的设置,似乎在列表中会出现错乱)

Glide.with(StaggeredGridViewDemo.this).load(mImages.get(arg0).getPic()).asBitmap().override(250, 250).into(new SimpleTarget<Bitmap>() {@Overridepublic void onResourceReady(Bitmap arg0, GlideAnimation<? super Bitmap> arg1) {Log.i("llj", "arg0.getWidth()+arg0.getHeight()" + arg0.getWidth() + arg0.getHeight());holder.textView.setBackgroundDrawable(new BitmapDrawable(arg0));}});
但是override(250, 250)方法没有起作用:

也可以通过SimpleTarget的构造函数传参数:

Glide.with(StaggeredGridViewDemo.this).load(mImages.get(arg0).getPic()).asBitmap().into(new SimpleTarget<Bitmap>(250, 250) {@Overridepublic void onResourceReady(Bitmap arg0, GlideAnimation<? super Bitmap> arg1) {Log.i("llj", "arg0.getWidth()+arg0.getHeight()" + arg0.getWidth() + ";" + arg0.getHeight());// holder.textView.setBackgroundDrawable(new BitmapDrawable(arg0));}});

但是SimpleTarget的构造函数穿进去的参数也没有起作用:

2.设置预加载:(可以提前将资源加载到磁盘缓存中,默认的缓存目录是data\data下的250M空间,自己可以根据需求更改)

final PreloadTarget<Bitmap> target = PreloadTarget.obtain(250, 250);Glide.with(StaggeredGridViewDemo.this).load(mImages.get(arg0).getPic()).asBitmap().listener(new RequestListener<String, Bitmap>() {@Overridepublic boolean onException(Exception e, String model, Target<Bitmap> target, boolean isFirstResource) {return false;}@Overridepublic boolean onResourceReady(Bitmap resource, String model, Target<Bitmap> target, boolean isFromMemoryCache, boolean isFirstResource) {Log.i("llj", "arg0.getWidth()+arg0.getHeight()" + resource.getWidth() + resource.getHeight());return false;}}).into(target);
这里在PreloadTarget中设置的像素的缩放还是没有用:

final PreloadTarget<Bitmap> target = PreloadTarget.obtain(0, 0);Glide.with(StaggeredGridViewDemo.this).load(mImages.get(arg0).getPic()).asBitmap().override(250, 250).listener(new RequestListener<String, Bitmap>() {@Overridepublic boolean onException(Exception e, String model, Target<Bitmap> target, boolean isFirstResource) {return false;}@Overridepublic boolean onResourceReady(Bitmap resource, String model, Target<Bitmap> target, boolean isFromMemoryCache, boolean isFirstResource) {Log.i("llj", "arg0.getWidth()+arg0.getHeight()" + resource.getWidth() + resource.getHeight());return false;}}).into(target);
这里在override(250, 250)设置依然没有作用:

3.加载到imageview上

Glide.with(StaggeredGridViewDemo.this).load(mImages.get(arg0).getPic()).asBitmap().override(250, 250).listener(new RequestListener<String, Bitmap>() {@Overridepublic boolean onException(Exception e, String model, Target<Bitmap> target, boolean isFirstResource) {return false;}@Overridepublic boolean onResourceReady(Bitmap resource, String model, Target<Bitmap> target, boolean isFromMemoryCache, boolean isFirstResource) {Log.i("llj", "arg0.getWidth()+arg0.getHeight()" + resource.getWidth() + resource.getHeight());return false;}}).into(holder.imageview);
拿到的Bitmap的宽高经过了转换,但转换后的比例并不是250:250:

但是如果设置了centerCrop():



而用Picasso转换后的图片是250:250:

Picasso.with(StaggeredGridViewDemo.this).load(mImages.get(arg0).getPic()).resize(250, 250).into(new com.squareup.picasso.Target() {@Overridepublic void onPrepareLoad(Drawable arg0) {}@Overridepublic void onBitmapLoaded(Bitmap arg0, LoadedFrom arg1) {Log.i("llj", "arg0.getWidth()+arg0.getHeight()" + arg0.getWidth() + ";" + arg0.getHeight());}@Overridepublic void onBitmapFailed(Drawable arg0) {}});

说明两者的按比例缩放的方法不一样!








这篇关于图片加载框架Glide解析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1022509

相关文章

python常见环境管理工具超全解析

《python常见环境管理工具超全解析》在Python开发中,管理多个项目及其依赖项通常是一个挑战,下面:本文主要介绍python常见环境管理工具的相关资料,文中通过代码介绍的非常详细,需要的朋友... 目录1. conda2. pip3. uvuv 工具自动创建和管理环境的特点4. setup.py5.

全面解析HTML5中Checkbox标签

《全面解析HTML5中Checkbox标签》Checkbox是HTML5中非常重要的表单元素之一,通过合理使用其属性和样式自定义方法,可以为用户提供丰富多样的交互体验,这篇文章给大家介绍HTML5中C... 在html5中,Checkbox(复选框)是一种常用的表单元素,允许用户在一组选项中选择多个项目。本

Python实现精准提取 PDF中的文本,表格与图片

《Python实现精准提取PDF中的文本,表格与图片》在实际的系统开发中,处理PDF文件不仅限于读取整页文本,还有提取文档中的表格数据,图片或特定区域的内容,下面我们来看看如何使用Python实... 目录安装 python 库提取 PDF 文本内容:获取整页文本与指定区域内容获取页面上的所有文本内容获取

Python包管理工具核心指令uvx举例详细解析

《Python包管理工具核心指令uvx举例详细解析》:本文主要介绍Python包管理工具核心指令uvx的相关资料,uvx是uv工具链中用于临时运行Python命令行工具的高效执行器,依托Rust实... 目录一、uvx 的定位与核心功能二、uvx 的典型应用场景三、uvx 与传统工具对比四、uvx 的技术实

SpringBoot排查和解决JSON解析错误(400 Bad Request)的方法

《SpringBoot排查和解决JSON解析错误(400BadRequest)的方法》在开发SpringBootRESTfulAPI时,客户端与服务端的数据交互通常使用JSON格式,然而,JSON... 目录问题背景1. 问题描述2. 错误分析解决方案1. 手动重新输入jsON2. 使用工具清理JSON3.

Python基于微信OCR引擎实现高效图片文字识别

《Python基于微信OCR引擎实现高效图片文字识别》这篇文章主要为大家详细介绍了一款基于微信OCR引擎的图片文字识别桌面应用开发全过程,可以实现从图片拖拽识别到文字提取,感兴趣的小伙伴可以跟随小编一... 目录一、项目概述1.1 开发背景1.2 技术选型1.3 核心优势二、功能详解2.1 核心功能模块2.

Redis过期删除机制与内存淘汰策略的解析指南

《Redis过期删除机制与内存淘汰策略的解析指南》在使用Redis构建缓存系统时,很多开发者只设置了EXPIRE但却忽略了背后Redis的过期删除机制与内存淘汰策略,下面小编就来和大家详细介绍一下... 目录1、简述2、Redis http://www.chinasem.cn的过期删除策略(Key Expir

Go学习记录之runtime包深入解析

《Go学习记录之runtime包深入解析》Go语言runtime包管理运行时环境,涵盖goroutine调度、内存分配、垃圾回收、类型信息等核心功能,:本文主要介绍Go学习记录之runtime包的... 目录前言:一、runtime包内容学习1、作用:① Goroutine和并发控制:② 垃圾回收:③ 栈和

Go语言如何判断两张图片的相似度

《Go语言如何判断两张图片的相似度》这篇文章主要为大家详细介绍了Go语言如何中实现判断两张图片的相似度的两种方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 在介绍技术细节前,我们先来看看图片对比在哪些场景下可以用得到:图片去重:自动删除重复图片,为存储空间"瘦身"。想象你是一个

springboot加载不到nacos配置中心的配置问题处理

《springboot加载不到nacos配置中心的配置问题处理》:本文主要介绍springboot加载不到nacos配置中心的配置问题处理,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑... 目录springboot加载不到nacos配置中心的配置两种可能Spring Boot 版本Nacos