Android 面试之Glide做了哪些优化?

前言

Glide可以说是最常用的图片加载框架了,Glide链式调用使用方便,性能上也可以满足大多数场景的使用,Glide源码与原理也是面试中的常客。
但是Glide的源码内容比较多,想要学习它的源码往往千头万绪,一时抓不住重点.
本文以Glide做了哪些优化为切入点,介绍与学习Glide的源码与原理,如果对您有所帮助,欢迎点赞.

Glide做了哪些优化?

要想要回答这个问题,我们可以先想一想,如果我们自己要实现一个图片加载框架,我们会思考什么问题?
1.图片下载是个耗时过程,我们首先需要考虑的就是图片缓存的问题
2.图片加载也是个耗内存的操作,很多OOM都是图片加载导致的,所以我们也要考虑内存优化问题
3.图片加载到一半,页面关闭了,图片加载也应该中止,这又牵扯到了生命周期管理的问题
4.还有就是图片加载框架是否支持大图加载?大图情况下会有什么问题?

以上就是我们提出的有关于Glide的几个问题了,这样我们可以轻松得出本文主要包括的内容
1.Glide图片加载的总体流程介绍
2.Glide缓存机制做了哪些优化?
3.Glide做了哪些内存优化?
4.Glide如何管理生命周期?
5.Glide怎么做大图加载?

下面就带着问题进入正文~

1.Glide图片加载总体流程介绍

在开始了解Glide具体做了哪些优化之前,我们先对Glide图片加载的总体流程做一个简单的介绍,让大家首先有个整体概念。
同时在后面对Glide做的优化具体发生在哪一步也可以方便的知道.
概括来说,图片加载包含封装,解析,下载,解码,变换,缓存,显示等操作,如下图所示:
【一道面试题】Glide做了哪些优化?

  • 1.封装参数:从指定来源,到输出结果,中间可能经历很多流程,所以第一件事就是封装参数,这些参数会贯穿整个过程;
  • 2.解析路径:图片的来源有多种,格式也不尽相同,需要规范化;
  • 3.读取缓存:为了减少计算,通常都会做缓存;同样的请求,从缓存中取图片(Bitmap)即可;
  • 4.查找文件/下载文件:如果是本地的文件,直接解码即可;如果是网络图片,需要先下载;
  • 5.解码:这一步是整个过程中最复杂的步骤之一,有不少细节;
  • 6.变换:解码出Bitmap之后,可能还需要做一些变换处理(圆角,滤镜等);
  • 7.缓存:得到最终bitmap之后,可以缓存起来,以便下次请求时直接取结果;
  • 8.显示:显示结果,可能需要做些动画(淡入动画,crossFade等)。

以上就是Glide图片加载的总体流程,这里只做了简单的介绍,详情可见:聊一聊关于Glide在面试中的那些事

2.Glide缓存机制做了哪些优化?

我们知道,下载图片是非常耗费资源的,所以图片缓存机制是图片加载框架很重要的一部分,下面就以一张表格来说明下 Glide 缓存。

缓存类型缓存代表说明
活动缓存ActiveResources如果当前对应的图片资源是从内存缓存中获取的,那么会将这个图片存储到活动资源中。
内存缓存LruResourceCache图片解析完成并最近被加载过,则放入内存中
磁盘缓存-资源类型DiskLruCacheWrapper被解码后的图片写入磁盘文件中
磁盘缓存-原始数据DiskLruCacheWrapper网络请求成功后将原始数据在磁盘中缓存

在介绍具体缓存前,先来看一张加载缓存执行顺序,有个大概的印象

Glide的缓存机制,主要分为2种缓存,一种是内存缓存,一种是磁盘缓存。
之所以使用内存缓存的原因是:防止应用重复将图片读入到内存,造成内存资源浪费。
之所以使用磁盘缓存的原因是:防止应用重复的从网络或者其他地方下载和读取数据。
正式因为有着这两种缓存的结合,才构成了Glide极佳的缓存效果。

2.1 内存缓存

Glide默认开启内存缓存,我们也可以通过skipMemoryCache关闭
上面我们可以看到内存缓存其实分两个部分,ActiveResource缓存与LRU缓存
ActiveResources 就是一个弱引用的 HashMap ,用来缓存正在使用中的图片,使用 ActiveResources 来缓存正在使用中的图片,可以保护这些图片不会被 LruCache 算法回收掉

内存缓存加载顺序如下:
1.根据图片地址,宽高,变换,签名等生成key
2.第一次加载没有获取到活动缓存。
3.接着加载内存资源缓存,先清理掉内存缓存,在添加进行活动缓存。
4.第二次加载活动缓存已经存在。
5.当前图片引用为 0 的时候,清理活动资源,并且添加进内存资源。
6.又回到了第一步,然后就这样环环相扣。

总结为流程图如下:

这里没有贴出源码,如果想要看源码的同学可参考:从源码的角度分析 Glide 缓存策略

我们上面总结了Glide内存缓存加载的流程,看到这里我们很容易有个疑问,为什么Glide要设计两种内存缓存?

2.1.1 为什么设计两种内存缓存?

LruCache算法的实现,你会发现它其实是用一个Set来缓存对象的,每次内存超出缓存设定触发trim操作的时候,其实是对这个Set进行遍历,然后移除缓存。但是我们都知道Set是无序的,因此遍历的时候有可能会把正在使用的缓存给误伤了,我还在用着它呢就给移出去了。因此这个弱引用可能是对正在使用中的图片的一种保护,使用的时候先从LruCache里面移出去,用完了再把它重新加到缓存里面。

举个例子

比如我们 Lru 内存缓存 size 设置装 99 张图片,在滑动 RecycleView 的时候,如果刚刚滑动到 100 张,那么就会回收掉我们已经加载出来的第一张,这个时候如果返回滑动到第一张,会重新判断是否有内存缓存,如果没有就会重新开一个 Request 请求,很明显这里如果清理掉了第一张图片并不是我们要的效果。所以在从内存缓存中拿到资源数据的时候就主动添加到活动资源中,并且清理掉内存缓存中的资源。这么做很显然好处是 保护不想被回收掉的图片不被 LruCache 算法回收掉,充分利用了资源。

2.1.1 小结

本节主要总结了Glide内存缓存加载的流程
1.首先去获取活动缓存,如果加载到则直接返回,没有则进入下一步
2.接着去获取LRU缓存,在获取时会将其从LRU中删除并添加到活动缓存中
3.下次加载就可以直接加载活动缓存了
4.当图片引用为0时,会从活动缓存中清除并添加到LRU缓存中
5.之所以要设计两种内存缓存的原因是为了防止加载中的图片被LRU回收

2.2 磁盘缓存

首先了解一下磁盘缓存策略

  • DiskCacheStrategy.NONE: 表示不缓存任何内容。
  • DiskCacheStrategy.RESOURCE: 在资源解码后将数据写入磁盘缓存,即经过缩放等转换后的图片资源。
  • DiskCacheStrategy.DATA: 在资源解码前将原始数据写入磁盘缓存。
  • DiskCacheStrategy.ALL : 使用DATARESOURCE缓存远程数据,仅使用RESOURCE来缓存本地数据。
  • DiskCacheStrategy.AUTOMATIC:它会尝试对本地和远程图片使用最佳的策略。当你加载远程数据时,AUTOMATIC 策略仅会存储未被你的加载过程修改过的原始数据,因为下载远程数据相比调整磁盘上已经存在的数据要昂贵得多。对于本地数据,AUTOMATIC 策略则会仅存储变换过的缩略图,因为即使你需要再次生成另一个尺寸或类型的图片,取回原始数据也很容易。默认使用这种缓存策略

在了解磁盘缓存时我们主要需要明确一个概念,是当我们使用 Glide 去加载一张图片的时候,Glide 默认并不会将原始图片展示出来,而是会对图片进行压缩和转换,总之就是经过种种一系列操作之后得到的图片,就叫转换过后的图片。
我们既可以缓存变换之前的原始图片,也可以缓存变换后的图片

2.2.1 为什么需要两种磁盘缓存

上文已经说了,DiskCacheStrategy.RESOURCE缓存的是变换后的资源,DiskCacheStrategy.DATA缓存的是变换前的资源
举个例子,同一张图片,我们先在100*100View是展示,再在200*200View上展示
如果不缓存变换后的类型相当于每次都要进行一次变换操作,如果不缓存原始数据则每次都要去重新下载数据
如下可以看出,两种缓存的key不一样

DiskCacheStrategy.RESOURCE
currentKey = new ResourceCacheKey(helper.getArrayPool(),sourceId,helper.getSignature(),helper.getWidth(),helper.getHeight(),transformation,resourceClass,helper.getOptions());DiskCacheStrategy.DATA
DataCacheKey newOriginalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature());

2.2.2 小结

本节主要介绍了Glide磁盘缓存的几种策略并介绍了为什么需要两种磁盘缓存的原因
这里也没有贴什么源码,如果想要看源码的同学可参考:从源码的角度分析 Glide 缓存策略

3.Glide做了哪些内存优化?

Glide的内存优化主要也是对Bitmap的优化,在回答这个问题前,我们可以想想有哪些常见的Bitmap优化手段
1.当图片大小与View大小不一致时,可以用inSampleSize进行尺寸优化
2.图片所占内存即宽每像素所占内存大小,不同的模式每个像素所占的内存大小不同,我们可以利用inpreferredconfig配置
3.Bitmpa所占内存比较大,如果频繁创建回收Bitmap内存可能造成内存抖动,我们可以利用inBitmap利用Bitmap内存
4.内存缓存,上文我们已经介绍了Glide的弱引用缓存与LRU缓存

其实常见的Bitmap内存优化也就这么几种了,不过我们在工作中比较少直接使用他们。
下面我们就介绍下Glide中具体是怎么使用他们的.

3.1 尺寸优化

当装载图片的容器例如ImageView只有100*100,而图片的分辨率为800 * 800,这个时候将图片直接放置在容器上,很容易OOM,同时也是对图片和内存资源的一种浪费。当容器的宽高都很小于图片的宽高,其实就需要对图片进行尺寸上的压缩,将图片的分辨率调整为ImageView宽高的大小,一方面不会对图片的质量有影响,同时也可以很大程度上减少内存的占用

我们通常使用inSampleSizeBitmap进行尺寸缩放

如果inSampleSize 设置的值大于1,则请求解码器对原始的bitmap进行子采样图像,然后返回较小的图片来减少内存的占用,例如inSampleSize == 4,则采样后的图像宽高为原图像的1/4,而像素值为原图的1/16,也就是说采样后的图像所占内存也为原图所占内存的1/16;当inSampleSize <=1时,就当作1来处理也就是和原图一样大小。另外最后一句还注明,inSampleSize的值一直为2的幂,如1,2,4,8。任何其他的值也都是四舍五入到最接近2的幂。

    //1int widthScaleFactor = orientedSourceWidth / outWidth;int heightScaleFactor = orientedSourceHeight / outHeight;//2int scaleFactor =rounding == SampleSizeRounding.MEMORY? Math.max(widthScaleFactor, heightScaleFactor): Math.min(widthScaleFactor, heightScaleFactor);int powerOfTwoSampleSize;//3if (Build.VERSION.SDK_INT <= 23&& NO_DOWNSAMPLE_PRE_N_MIME_TYPES.contains(options.outMimeType)) {powerOfTwoSampleSize = 1;} else {//4powerOfTwoSampleSize = Math.max(1, Integer.highestOneBit(scaleFactor));//5if (rounding == SampleSizeRounding.MEMORY// exactScaleFactor由各个裁剪策略如CenterCrop重写得到,详情可见代码&& powerOfTwoSampleSize < (1.f / exactScaleFactor)) {powerOfTwoSampleSize = powerOfTwoSampleSize << 1;}}options.inSampleSize = powerOfTwoSampleSize;

如上就是Glide图片进行尺寸缩放相关的代码
1.首先计算出图片与View的宽高比
2.根据缩放策略是省内存还是高品质,决定取宽高比的最大值还是最小值
3.当Build.VERSION.SDK_INT<=23时,一些格式的图片不能缩放
4.highestOneBit的功能是把我们计算的比例四舍五入到最接近2的幂
5.如果缩放策略为省内存,并且我们计算的SampleSize<exactScaleFactor,将inSampleSize*2

如上就是Glide图片加载时做尺寸优化的大概逻辑

3.2 图片格式优化

我们知道,Bitmap所占内存大小,由宽*高*每像素所占内存决定
上面的尺寸优化决定宽高,图片格式优化决定每像素所占内存

API29中,将Bitmap分为ALPHA_8, RGB_565, ARGB_4444, ARGB_8888, RGBA_F16, HARDWARE六个等级。

  • ALPHA_8:不存储颜色信息,每个像素占1个字节;
  • RGB_565:仅存储RGB通道,每个像素占2个字节,对Bitmap色彩没有高要求,可以使用该模式;
  • ARGB_4444:已弃用,用ARGB_8888代替;
  • ARGB_8888:每个像素占用4个字节,保持高质量的色彩保真度,默认使用该模式;
  • RGBA_F16:每个像素占用8个字节,适合宽色域和HDR
  • HARDWARE:一种特殊的配置,减少了内存占用同时也加快了Bitmap的绘制。

每个等级每个像素所占用的字节也都不一样,所存储的色彩信息也不同。同一张100像素的图片,ARGB_8888就占了400字节,RGB_565才占200字节,RGB_565在内存上取得了优势,但是Bitmap的色彩值以及清晰度却不如ARGB_8888模式下的Bitmap

值得注意的是在Glide4.0之前,Glide默认使用RGB565格式,比较省内存
但是Glide4.0之后,默认格式已经变成了ARGB_8888格式了,这一优势也就不存在了。
这本身也就是质量与内存之间的取舍,如果应用所需图片的质量要求不高,也可以修改默认格式

//默认格式修改为了ARGB_8888public static final Option<DecodeFormat> DECODE_FORMAT =Option.memory("com.bumptech.glide.load.resource.bitmap.Downsampler.DecodeFormat", DecodeFormat.DEFAULT);

3.3 内存复用优化

Bitmap所占内存比较大,如果我们频繁创建与回收Bitmap,那么很容易造成内存抖动,所以我们应该尽量复用Bitmap内存
Glide主要使用了inBitmapBitmapPool来实现内存的复用

3.3.1 inBitmap介绍

Android 3.0(API 级别 11)开始,系统引入了 BitmapFactory.Options.inBitmap 字段。如果设置了此选项,那么采用 Options 对象的解码方法会在生成目标 Bitmap 时尝试复用 inBitmap,这意味着 inBitmap 的内存得到了重复使用,从而提高了性能,同时移除了内存分配和取消分配。不过 inBitmap 的使用方式存在某些限制,在 Android 4.4(API 级别 19)之前系统仅支持复用大小相同的位图,4.4 之后只要 inBitmap 的大小比目标 Bitmap 大即可

3.3.2 BitmapPool介绍

通过上文我们知道了可以通过inBitmap复用内存,但是还需要一个地方存储可复用的Bitmap,这就是BitmapPool
JDK 中的 ThreadPoolExecutor 相信大多数开发者都很熟悉,我们一般将之称为“线程池”。池化是一个很常见的概念,其目的都是为了实现对象复用,例如 ThreadPoolExecutor 就实现了线程的复用机制
BitmapPool即实现了Bitmap的池化

3.3.3 Glide的应用

  private static void setInBitmap(BitmapFactory.Options options, BitmapPool bitmapPool, int width, int height) {@Nullable Bitmap.Config expectedConfig = null;if (expectedConfig == null) {expectedConfig = options.inPreferredConfig;}// BitmapFactory will clear out the Bitmap before writing to it, so getDirty is safe.options.inBitmap = bitmapPool.getDirty(width, height, expectedConfig);}

如上即是Glide设置inBitmap的代码,向BitmapPool中传入宽高与格式,得到一个可复用的对象,这样就实现了Bitmap的内存复用
由于篇幅原因,详细的源码这里没有贴出来,想要了解更多的读者可参考:Coil 和 Glide 的 Bitmap 缓存复用机制

4.Glide如何管理生命周期?

当我们在做一个网络请示时,页面退出时应该中止请示,不然容易造成内存泄漏
对于图片加载也是如此,我们在页面退出时应该中止请示,销毁资源。
但是我们使用Glide的时候却不需要在页面退出时做什么操作,说明Glide可以做到在页面关闭时自动释放资源
下面我们一起看下Glide是如何实现的
主要是两步:
1.调用时通过Glide.with传入context,利用context构建一个Fragment
2.监听Fragment生命周期,销毁时释放Glide资源

4.1 传入context构建Fragment

//通过Activity拿到RequestManager
public RequestManager get(@NonNull Activity activity) {//拿到当前Activity的FragmentManagerandroid.app.FragmentManager fm = activity.getFragmentManager();//生成一个Fragment去绑定一个请求管理RequestManagerreturn fragmentGet(activity, fm, /*parentHint=*/ null, isActivityVisible(activity));}private RequestManager fragmentGet(@NonNull Context context,@NonNull android.app.FragmentManager fm,@Nullable android.app.Fragment parentHint,boolean isParentVisible) {//①在当前Activity添加一个Fragment用于管理请求的生命周期RequestManagerFragment current = getRequestManagerFragment(fm, parentHint, isParentVisible);//获取RequestManagerRequestManager requestManager = current.getRequestManager();//如果不存在RequestManager,则创建if (requestManager == null) {Glide glide = Glide.get(context);//②构建RequestManager  //current.getGlideLifecycle()就是ActivityFragmentLifecycle,也就是构建RequestManager时会传入fragment中的ActivityFragmentLifecyclerequestManager =factory.build(glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);//将构建出来的RequestManager绑定到fragment中current.setRequestManager(requestManager);}//返回当前请求的管理者return requestManager;}  

如上所示:
1.在当前Activity添加一个透明Fragment用于管理请示生命周期
2.构建RequestManager并传入Fragment生命周期

4.2 RequestManager监听生命周期

public class RequestManager implements LifecycleListener,ModelTypes<RequestBuilder<Drawable>> { RequestManager(Glide glide,Lifecycle lifecycle,RequestManagerTreeNode treeNode,RequestTracker requestTracker,ConnectivityMonitorFactory factory,Context context) {... //将当前对象注册到ActivityFragmentLifecyclelifecycle.addListener(this);}//...//RequestManager实现了fragment生命周期回调@Overridepublic synchronized void onStart() {resumeRequests();targetTracker.onStart();}@Overridepublic synchronized void onStop() {pauseRequests();targetTracker.onStop();}@Overridepublic synchronized void onDestroy() {targetTracker.onDestroy();}}public class RequestManagerFragment extends Fragment {//生命周期的关键就在ActivityFragmentLifecycleprivate final ActivityFragmentLifecycle lifecycle;public RequestManagerFragment() {this(new ActivityFragmentLifecycle());}RequestManagerFragment(@NonNull ActivityFragmentLifecycle lifecycle) {this.lifecycle = lifecycle;}@Overridepublic void onStart() {super.onStart();lifecycle.onStart();}@Overridepublic void onStop() {super.onStop();lifecycle.onStop();}@Overridepublic void onDestroy() {super.onDestroy();lifecycle.onDestroy();unregisterFragmentWithRoot();}//...
}

逻辑很简单:Fragment生命周期变化会回调RequestManager生命周期,然后在进行相关的资源释放工作

4.3 小结

Glide.with(this)绑定了Activity的生命周期。在Activity内新建了一个无UIFragment,这个Fragment持有一个Lifecycle,通过LifecycleFragment关键生命周期通知RequestManager进行相关从操作。在生命周期onStart时继续加载,onStop时暂停加载,onDestory时停止加载任务和清除操作。

5.Glide怎么做大图加载

对于图片加载还有种情况,就是单个图片非常巨大,并且还不允许压缩。比如显示:世界地图、清明上河图、微博长图等
首先不压缩,按照原图尺寸加载,那么屏幕肯定是不够大的,并且考虑到内存的情况,不可能一次性整图加载到内存中
所以这种情况的优化思路一般是局部加载,通过BitmapRegionDecoder来实现
这种情况下通常Glide只负责将图片下载下来,图片的加载由我们自定义的ImageView来实现

5.1 BitmapRegionDecoder介绍

BitmapRegionDecoder主要用于显示图片的某一块矩形区域,如果你需要显示某个图片的指定区域,那么这个类非常合适。
对于该类的用法,非常简单,既然是显示图片的某一块区域,那么至少只需要一个方法去设置图片;一个方法传入显示的区域即可
举个例子:

//设置显示图片的中心区域
BitmapRegionDecoder bitmapRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, false);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
Bitmap bitmap = bitmapRegionDecoder.decodeRegion(new Rect(width / 2 - 100, height / 2 - 100, width / 2 + 100, height / 2 + 100), options);
mImageView.setImageBitmap(bitmap);

更详细的实现可见:Android 高清加载巨图方案 拒绝压缩图片
不过这种方法虽然也能加载大图,但做的还不够,滑动时内存抖动,卡顿现象比较明显,不能用于线上

下面介绍一种可以用于线上的大图加载方案

5.2 可用于线上的大图加载方案

介绍一个开源库:subsampling-scale-image-view
SubsamplingScaleImageView将大图切片,再判断是否可见,如果可见则加入内存中,否则回收,减少了内存占用与抖动 同时根据不同的缩放比例选择合适的采样率,进一步减少内存占用 同时在子线程进行decodeRegion操作,解码成功后回调至主线程,减少UI卡顿.

总结

本文主要以Glide做了哪些优化为切入点,回答了如下几个问题
1.说一下Glide图片加载的总体流程
2.Glide缓存机制做了哪些优化?
3.Glide做了哪些内存优化?
4.Glide如何管理生命周期?
5.Glide怎么做大图加载?

下面我针对Android 中不同的技术模块,整理了一些相关的 学习文档笔记 及相应的 面试习题(含答案),大家可以针对性的进行参考学习与复习。

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

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

相关文章

shell脚本免交互

一.Here Document免交互 1.免交互概述 使用I/O重定向的方式将命令列表提供给交互式程序 是一种标准输入&#xff0c;只能接收正确的指令或命令 2.格式&#xff1a; 命令 <<标记 ....... 内容 #标记之间是传入内容 ....... 标记 注意事项 标记可以使用任意的合法…

mybatisplus批量写入

1.新建MybatisPlusConfig /*** MybatisPlusConfig.*/ Configuration MapperScan("com.test.mapper") public class MybatisPlusConfig {/*** 自定义批量插入 SQL 注入器.*/Beanpublic InsertBatchSqlInjector insertBatchSqlInjector() {return new InsertBatchSqlI…

WPF 项目中 MVVM模式 的简单例子说明

一、概述 MVVM 是 Model view viewModel 的简写。MVVM模式有助于将应用程序的业务和表示逻辑与用户界面清晰分离。 几个概念的说明&#xff1a; model :数据&#xff0c;界面中需要的数据&#xff0c;最好不要加逻辑代码view : 视图就是用户看到的UI结构 xaml 文件viewModel …

一文了解汽车芯片的分类及用途介绍

汽车芯片按其功能可分为控制类&#xff08;MCU和AI芯片&#xff09;、功率类、传感器和其他&#xff08;如存储器&#xff09;四种类型。市场基本被国际巨头所垄断。人们常说的汽车芯片是指汽车里的计算芯片&#xff0c;按集成规模可分为MCU芯片和AI芯片&#xff08;SoC芯片&am…

Lua之Lua源文件批量转换为luac字节码文件

准备的工具&#xff1a;luac.exe CSDNhttps://mp.csdn.net/mp_download/manage/download/UpDetailed Unity版: using System; using System.Collections; using System.Collections.Generic; using System.IO; using UnityEditor; using UnityEngine;public static class Bat…

(学习笔记-调度算法)进程调度算法

进程调度算法也称 CPU 调度算法&#xff0c;毕竟进程是由 CPU 调度的。 当 CPU 空闲时&#xff0c;操作系统就选择内存中标的某个 [就绪状态] 的进程&#xff0c;将其分配给 CPU。 什么时候会发生CPU调度呢&#xff1f;通常有以下情况&#xff1a; 当进程从运行状态转换到等待…

BATPowerShell实现本地文件自动上传FTP服务器

运维工作中&#xff0c;经常需要一些脚本来实现自动化&#xff0c;今天分享本地文件自动上传FTP的两种解决办法&#xff1a; 一、使用BAT自动上传FTP 使用批处理&#xff08;BAT&#xff09;命令文件将本地文件夹内容上传到FTP服务器需要使用Windows自带的命令行工具&#xf…

【卷积神经网络】经典网络之 LeNet-5, AlexNet 与 VGG-16

随着计算机硬件的升级与性能的提高&#xff0c;运算量已不再是阻碍深度学习发展的难题。卷积神经网络&#xff08;Convolution Neural Network&#xff0c;CNN&#xff09;是深度学习中一项代表性的工作&#xff0c;其雏形是 1998 年 LeCun 提出的 LeNet-5 模型。如今&#xff…

Python爬虫——scrapy_日志信息以及日志级别

日志级别&#xff08;由高到低&#xff09; CRITICAL&#xff1a; 严重错误 ERROR&#xff1a; 一般错误 WARNING&#xff1a; 警告 INFO&#xff1a; 一般警告 DEBUG&#xff1a; 调试信息 默认的日志等级是DEBUG 只要出现了DEBUG或者DEBUG以上等级的日志&#xff0c;那么这些…

[oneAPI] 基于BERT预训练模型的SQuAD问答任务

[oneAPI] 基于BERT预训练模型的SQuAD问答任务 Intel Optimization for PyTorch and Intel DevCloud for oneAPI基于BERT预训练模型的SQuAD问答任务语料介绍数据下载构建 模型 结果参考资料 比赛&#xff1a;https://marketing.csdn.net/p/f3e44fbfe46c465f4d9d6c23e38e0517 Int…

回归预测 | MATLAB实现GAM广义加性模型多输入单输出回归预测(多指标,多图)

回归预测 | MATLAB实现GAM广义加性模型多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09; 目录 回归预测 | MATLAB实现GAM广义加性模型多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09;效果一览基本介绍程序设计参考资料 效果一览 基本…

【24择校指南】华东师范大学计算机考研考情分析

华东师范大学(B) 考研难度&#xff08;☆☆☆☆&#xff09; 内容&#xff1a;23考情概况&#xff08;拟录取和复试分数人数统计&#xff09;、院校概况、23考试科目、23复试详情、各科目及专业考情分析。 正文2563字&#xff0c;预计阅读&#xff1a;3分钟。 2023考情概况…

机器学习深度学习——NLP实战(自然语言推断——注意力机制实现)

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位即将上大四&#xff0c;正专攻机器学习的保研er &#x1f30c;上期文章&#xff1a;机器学习&&深度学习——NLP实战&#xff08;自然语言推断——数据集&#xff09; &#x1f4da;订阅专栏&#xff1a;机器学习&…

C# 读取pcd、ply点云文件数据

最近研究了下用pcl读取点云数据&#xff0c;又做了个C#的dll&#xff0c;方便读取&#xff0c;同样这个dll基于pcl 最新版本1.13.1版本开发。 上次做的需要先得到点云长度&#xff0c;再获取数据。这次这个定义了一个PointCloudXYZ类来存数据。将下面的dll拷贝到可执行目录下&a…

Docker详解

文章目录 Docker详解一、Docker简介什么是容器 &#xff1f;容器技术有哪些优点 &#xff1f;什么是Docker &#xff1f;Docker的特点Docker的使用场景 二、Docker的基本组成Docker 客户端 / 守护进程Docker Image 镜像Docker Container 容器Docker Registry 仓库 三、Docker 依…

嵌入式系统中如何选择RTC电池?

RTC&#xff08;Real Time Clock&#xff09;是一种用于提供系统时间的独立定时器&#xff0c;它可以在系统断电或低功耗模式下继续运行&#xff0c;只需要一个后备电池作为供电源。在嵌入式系统中&#xff0c;选择合适的RTC电池时非常关键的&#xff0c;它会影响系统时间的准确…

二、SQL注入之联合查询

文章目录 1、SQL注入原理2、SQL注入的原因3、SQL注入的危害4、SQL注入基础4.1 MySQL相关4.2 SQL注入流程&#xff1a; 5、联合注入实例基本步骤6、总结 1、SQL注入原理 SQL注入(Sql Injection&#xff09;就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串&…

【从零学习python 】56. 异常处理在程序设计中的重要性与应用

文章目录 异常的概念读取文件异常try...except语句try...else语句try...finally语句 进阶案例 异常的概念 在程序运行过程中&#xff0c;由于编码不规范或其他客观原因&#xff0c;可能会导致程序无法继续运行&#xff0c;此时就会出现异常。如果不对异常进行处理&#xff0c;…

[C++] string类常用接口的模拟实现

文章目录 1、前言2、遍历2.1 operator[ ]下标方式2.2 迭代器2.3 范围for2.4 c_str 3、容量相关3.1 size&#xff08;大小&#xff09;3.2 capacity&#xff08;容量&#xff09;3.3 empty&#xff08;判空&#xff09;3.4 clear&#xff08;清理&#xff09;3.5 reserve3.6 res…

最新AI系统ChatGPT网站程序源码/搭建教程/支持GPT4.0/Dall-E2绘画/支持MJ以图生图/H5端/自定义训练知识库

一、正文 SparkAi系统是基于国外很火的ChatGPT进行开发的Ai智能问答系统。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。 那么如何搭建部署AI创作ChatGPT&#xff1f;小编这里写一个详细图文教程吧&#xff01…