Android Glide图片加载框架(三)缓存机制

文章目录

  • 一、缓存简介
  • 二、缓存用法
    • 内存缓存方式
    • 磁盘缓存方式
  • 三、缓存KEY
  • 四、内存缓存
    • 内存缓存流程
  • 五、磁盘缓存
    • 磁盘缓存流程


Android Glide图片加载框架系列文章

Android Glide图片加载框架(一)基本用法

Android Glide图片加载框架(二)源码解析之with()

Android Glide图片加载框架(二)源码解析之load()

Android Glide图片加载框架(二)源码解析之into()

Android Glide图片加载框架(三)缓存机制


一、缓存简介


Glide的缓存设计可以说是非常先进的,考虑的场景也很周全。在缓存这一功能上,Glide又将它分成了两个模块,一个是 内存缓存 ,一个是 磁盘缓存

这两个缓存模块的作用各不相同,

  • 内存缓存 的主要作用是防止应用重复将图片数据读取到内存当中;

  • 磁盘缓存 的主要作用是防止应用重复从网络或其他地方重复下载和读取数据。

内存缓存硬盘缓存 的相互结合才构成了Glide极佳的图片缓存效果,那么接下来我们就分别来分析一下这两种缓存的使用方法以及它们的实现原理。


二、缓存用法


内存缓存方式

RequestOptions options = new RequestOptions();
// 禁止内存缓存
options.skipMemoryCache(true);Glide.with(this).load(url).apply(options).into(imageView);

磁盘缓存方式

RequestOptions options = new RequestOptions();
// 磁盘不缓存
options.diskCacheStrategy(DiskCacheStrategy.NONE);Glide.with(this).load(url).apply(options).into(imageView);

可以设置5种模式:

  • DiskCacheStrategy.NONE: 表示不缓存任何内容

  • DiskCacheStrategy.DATA: 表示只缓存原始图片

  • DiskCacheStrategy.RESOURCE: 表示只缓存转换过后的图片

  • DiskCacheStrategy.ALL: 表示既缓存原始图片,也缓存转换过后的图片

  • DiskCacheStrategy.AUTOMATIC: 表示让Glide根据图片资源智能地选择使用哪一种缓存策略(默认选项)


三、缓存KEY


既然是缓存功能,就必然会有用于进行缓存的Key。那么Glide的缓存Key是怎么生成的呢?我不得不说,Glide的缓存Key生成规则非常繁琐,决定缓存Key的参数竟然有8个之多。不过繁琐归繁琐,至少逻辑还是比较简单的,我们先来看一下Glide缓存Key的生成逻辑。

生成缓存Key的代码在 Engine 类的 load() 方法当中,这部分代码我们在上一篇文章当中已经分析过了,只不过当时忽略了缓存相关的内容,那么我们现在重新来看一下:

public class Engine implements EngineJobListener,MemoryCache.ResourceRemovedListener,EngineResource.ResourceListener {...public <R> LoadStatus load(GlideContext glideContext,Object model,Key signature,int width,int height,Class<?> resourceClass,Class<R> transcodeClass,Priority priority,DiskCacheStrategy diskCacheStrategy,Map<Class<?>, Transformation<?>> transformations,boolean isTransformationRequired,boolean isScaleOnlyOrNoTransform,Options options,boolean isMemoryCacheable,boolean useUnlimitedSourceExecutorPool,boolean useAnimationPool,boolean onlyRetrieveFromCache,ResourceCallback cb) {Util.assertMainThread();long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,resourceClass, transcodeClass, options);...}...
}

第27行可见,决定缓存Key的条件非常多,即使你用override()方法改变了一下图片的width或者height,也会生成一个完全不同的缓存Key。

缓存key是一个 EngineKey 对象,该类重写了 equals()hashCode() 方法,保证只有传入EngineKey的所有参数都相同的情况下才认为是同一个 EngineKey 对象,代码如下:

class EngineKey implements Key {...public boolean equals(Object o) {if (o instanceof EngineKey) {EngineKey other = (EngineKey) o;return model.equals(other.model)&& signature.equals(other.signature)&& height == other.height&& width == other.width&& transformations.equals(other.transformations)&& resourceClass.equals(other.resourceClass)&& transcodeClass.equals(other.transcodeClass)&& options.equals(other.options);}return false;}@Overridepublic int hashCode() {if (hashCode == 0) {hashCode = model.hashCode();hashCode = 31 * hashCode + signature.hashCode();hashCode = 31 * hashCode + width;hashCode = 31 * hashCode + height;hashCode = 31 * hashCode + transformations.hashCode();hashCode = 31 * hashCode + resourceClass.hashCode();hashCode = 31 * hashCode + transcodeClass.hashCode();hashCode = 31 * hashCode + options.hashCode();}return hashCode;}...
}

四、内存缓存


默认情况下,Glide自动就是开启内存缓存的 。也就是说,当我们使用Glide加载了一张图片之后,这张图片就会被缓存到内存当中,只要在它还没从内存中被清除之前,下次使用Glide再加载这张图片都会直接从内存当中读取,而不用重新从网络或硬盘上读取了,这样无疑就可以大幅度提升图片的加载效率。比方说你在一个RecyclerView当中反复上下滑动,RecyclerView中只要是Glide加载过的图片都可以直接从内存当中迅速读取并展示出来,从而大大提升了用户体验。

而Glide最为人性化的是,你甚至不需要编写任何额外的代码就能自动享受到这个极为便利的内存缓存功能,因为Glide默认就已经将它开启了。

那么既然已经默认开启了这个功能,还有什么可讲的用法呢?只有一点,如果你有什么特殊的原因需要禁用内存缓存功能,Glide对此提供了接口:

RequestOptions options = new RequestOptions();
options.skipMemoryCache(true);Glide.with(this).load(url).apply(options).into(img);

可以看到,只需要调用skipMemoryCache()方法并传入true,就表示禁用掉Glide的内存缓存功能

接下来就让我们就通过阅读源码来分析一下Glide的内存缓存功能是如何实现的。

内存缓存使用弱引用和LruCache算法结合完成的,弱引用来缓存的是正在使用中的图片。图片封装类Resources内部有个计数器判断是该图片否正在使用。


内存缓存流程

  • 读: 是先从弱引用中取,取不到再从lruCache取;

  • 存: 内存缓存取不到,从网络拉取回来先放在弱引用里,渲染图片,图片对象Resources使用计数加一;

  • 渲染完图片: 图片对象Resources使用计数减一,如果计数为0,图片缓存从弱引用中删除,放入lruCache缓存。

上篇提到,Engine 在加载流程的中的入口方法是 load 方法:

public class Engine implements EngineJobListener,MemoryCache.ResourceRemovedListener,EngineResource.ResourceListener {...public <R> LoadStatus load(GlideContext glideContext,Object model,Key signature,int width,int height,Class<?> resourceClass,Class<R> transcodeClass,Priority priority,DiskCacheStrategy diskCacheStrategy,Map<Class<?>, Transformation<?>> transformations,boolean isTransformationRequired,boolean isScaleOnlyOrNoTransform,Options options,boolean isMemoryCacheable,boolean useUnlimitedSourceExecutorPool,boolean useAnimationPool,boolean onlyRetrieveFromCache,ResourceCallback cb) {Util.assertMainThread();long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;// 生成缓存keyEngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,resourceClass, transcodeClass, options);// 从弱引用获取图片EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);if (active != null) {cb.onResourceReady(active, DataSource.MEMORY_CACHE);if (VERBOSE_IS_LOGGABLE) {logWithTimeAndKey("Loaded resource from active resources", startTime, key);}return null;}// 从LruCache获取缓存图片EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);if (cached != null) {cb.onResourceReady(cached, DataSource.MEMORY_CACHE);if (VERBOSE_IS_LOGGABLE) {logWithTimeAndKey("Loaded resource from cache", startTime, key);}return null;}EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);if (current != null) {current.addCallback(cb);if (VERBOSE_IS_LOGGABLE) {logWithTimeAndKey("Added to existing load", startTime, key);}return new LoadStatus(cb, current);}EngineJob<R> engineJob =engineJobFactory.build(key,isMemoryCacheable,useUnlimitedSourceExecutorPool,useAnimationPool,onlyRetrieveFromCache);DecodeJob<R> decodeJob =decodeJobFactory.build(glideContext,model,key,signature,width,height,resourceClass,transcodeClass,priority,diskCacheStrategy,transformations,isTransformationRequired,isScaleOnlyOrNoTransform,onlyRetrieveFromCache,options,engineJob);jobs.put(key, engineJob);engineJob.addCallback(cb);engineJob.start(decodeJob);if (VERBOSE_IS_LOGGABLE) {logWithTimeAndKey("Started new load", startTime, key);}return new LoadStatus(cb, engineJob);}...
}

上面是从内存缓存中读取图片的主流程:

  • 生成缓存的key。

  • 从弱引用获取图片。

  • 弱引用没取到,在从LruCache获取缓存图片。

  • 内存缓存取不到,进入异步处理。

我们具体看取图片的两个方法 loadFromActiveResources()loadFromCache()

  • loadFromActiveResources 使用的就是弱引用。

  • loadFromCache 使用的就是LruCache算法。

我们来看一下它们的源码:

public class Engine implements EngineJobListener,MemoryCache.ResourceRemovedListener,EngineResource.ResourceListener {...private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {if (!isMemoryCacheable) {return null;}EngineResource<?> active = activeResources.get(key);if (active != null) {active.acquire();}return active;}private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {if (!isMemoryCacheable) {return null;}EngineResource<?> cached = getEngineResourceFromCache(key);if (cached != null) {cached.acquire();activeResources.activate(key, cached);}return cached;}private EngineResource<?> getEngineResourceFromCache(Key key) {Resource<?> cached = cache.remove(key);final EngineResource<?> result;if (cached == null) {result = null;} else if (cached instanceof EngineResource) {// Save an object allocation if we've cached an EngineResource (the typical case).result = (EngineResource<?>) cached;} else {result = new EngineResource<>(cached, true /*isMemoryCacheable*/, true /*isRecyclable*/);}return result;}...
}

loadFromActiveResources() 方法:

  • 首先就判断 isMemoryCacheable 是不是 false ,如果是false的话就直接返回null。这就是 skipMemoryCache() 方法设置的是否内存缓存已被禁用。

  • 然后从 activeResources 当中取值,使用activeResources来缓存正在使用中的图片,用来保护正在使用中的图片不会被LruCache算法回收掉。


loadFromCache() 方法:

  • 首先就判断 isMemoryCacheable 是不是 false ,如果是false的话就直接返回null。这就是 skipMemoryCache() 方法设置的是否内存缓存已被禁用。

  • 然后调用 getEngineResourceFromCache() 方法来获取缓存。在这个方法中,会从中获取图片缓存 LruResourceCache ,LruResourceCache其实使用的就是LruCache算法实现的缓存。

  • 当我们从 LruResourceCache 中获取到缓存图片之后会将它从缓存中移除,将缓存图片存储到 activeResources 当中。activeResources就是弱引用的HashMap,用来缓存正在使用中的图片。


这样我们把从内存读取图片缓存的流程搞清了,那是什么时候存储的呢。想想什么时候合适?是不是应该在异步处理获取到图片后,再缓存到内存?

EngineJob 获取到图片后 会回调Engine的 onEngineJobComplete() 。我们来看下做了什么:

public class Engine implements EngineJobListener,MemoryCache.ResourceRemovedListener,EngineResource.ResourceListener {...public void onEngineJobComplete(EngineJob<?> engineJob, Key key, EngineResource<?> resource) {Util.assertMainThread();// A null resource indicates that the load failed, usually due to an exception.if (resource != null) {resource.setResourceListener(key, this);if (resource.isCacheable()) {activeResources.activate(key, resource);}}jobs.removeIfCurrent(key, engineJob);}...
}

onEngineJobComplete() 方法里将正在加载的图片放到弱引用缓存。那什么时候放在LruCache里呢?当然是在使用完,那什么时候使用完呢?

那我们来看 EngineResource 这个类是怎么标记自己是否在被使用的。EngineResource是用一个acquired变量用来记录图片被引用的次数,调用acquire()方法会让变量加1,调用release()方法会让变量减1,代码如下所示:

class EngineResource<Z> implements Resource<Z> {...private int acquired;void acquire() {if (isRecycled) {throw new IllegalStateException("Cannot acquire a recycled resource");}if (!Looper.getMainLooper().equals(Looper.myLooper())) {throw new IllegalThreadStateException("Must call acquire on the main thread");}++acquired;}void release() {if (acquired <= 0) {throw new IllegalStateException("Cannot release a recycled or not yet acquired resource");}if (!Looper.getMainLooper().equals(Looper.myLooper())) {throw new IllegalThreadStateException("Must call release on the main thread");}if (--acquired == 0) {listener.onResourceReleased(key, this);}}...
}

可以看出当引用计数acquired变量为0,就是没有在使用了,然后调用了 listener.onResourceReleased(key, this);

这个 listener 就是 Engine 对象,我们来看下它的 onResourceReleased() 方法:

public class Engine implements EngineJobListener,MemoryCache.ResourceRemovedListener,EngineResource.ResourceListener {...public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {Util.assertMainThread();activeResources.deactivate(cacheKey);if (resource.isCacheable()) {cache.put(cacheKey, resource);} else {resourceRecycler.recycle(resource);}}...
}

做了三件事:

  • 从弱引用删除图片缓存

  • 是否支持缓存,缓存到LruCache缓存

  • 不支持缓存直接调用垃圾回收,回收图片

到这里内存缓存的读和存的流程就介绍完了,根据源码回头看看我们之前列的Glide内存缓存流程,就清晰很多了。


五、磁盘缓存


磁盘缓存流程

  • 读: 先找处理后(result)的图片,没有的话再找原图。

  • 存: 先存原图,再存处理后的图。

注: diskCacheStrategy设置的的缓存模式即影响读取,也影响存储。

在判断了两级内存缓存之后,如果拿不到缓存,就会接着创建 EngineJobDecodeJob ,然后接着就会调用进 DecodeJob 线程的 run() 方法:

class DecodeJob<R> implements DataFetcherGenerator.FetcherReadyCallback,Runnable,Comparable<DecodeJob<?>>,Poolable {...@Overridepublic void run() {// This should be much more fine grained, but since Java's thread pool implementation silently// swallows all otherwise fatal exceptions, this will at least make it obvious to developers// that something is failing.GlideTrace.beginSectionFormat("DecodeJob#run(model=%s)", model);// Methods in the try statement can invalidate currentFetcher, so set a local variable here to// ensure that the fetcher is cleaned up either way.DataFetcher<?> localFetcher = currentFetcher;try {if (isCancelled) {notifyFailed();return;}runWrapped();} catch (Throwable t) {// Catch Throwable and not Exception to handle OOMs. Throwables are swallowed by our// usage of .submit() in GlideExecutor so we're not silently hiding crashes by doing this. We// are however ensuring that our callbacks are always notified when a load fails. Without this// notification, uncaught throwables never notify the corresponding callbacks, which can cause// loads to silently hang forever, a case that's especially bad for users using Futures on// background threads.if (Log.isLoggable(TAG, Log.DEBUG)) {Log.d(TAG, "DecodeJob threw unexpectedly"+ ", isCancelled: " + isCancelled+ ", stage: " + stage, t);}// When we're encoding we've already notified our callback and it isn't safe to do so again.if (stage != Stage.ENCODE) {throwables.add(t);notifyFailed();}if (!isCancelled) {throw t;}} finally {// Keeping track of the fetcher here and calling cleanup is excessively paranoid, we call// close in all cases anyway.if (localFetcher != null) {localFetcher.cleanup();}GlideTrace.endSection();}}private void runWrapped() {switch (runReason) {case INITIALIZE:stage = getNextStage(Stage.INITIALIZE);currentGenerator = getNextGenerator();runGenerators();break;case SWITCH_TO_SOURCE_SERVICE:runGenerators();break;case DECODE_DATA:decodeFromRetrievedData();break;default:throw new IllegalStateException("Unrecognized run reason: " + runReason);}}...
}

run() 中主要还是调用的 runWrapper() 方法,继而调用 runGenerator()

class DecodeJob<R> implements DataFetcherGenerator.FetcherReadyCallback,Runnable,Comparable<DecodeJob<?>>,Poolable {...private void runGenerators() {currentThread = Thread.currentThread();startFetchTime = LogTime.getLogTime();boolean isStarted = false;while (!isCancelled && currentGenerator != null&& !(isStarted = currentGenerator.startNext())) {stage = getNextStage(stage);currentGenerator = getNextGenerator();if (stage == Stage.SOURCE) {reschedule();return;}}// We've run out of stages and generators, give up.if ((stage == Stage.FINISHED || isCancelled) && !isStarted) {notifyFailed();}// Otherwise a generator started a new load and we expect to be called back in// onDataFetcherReady.}...
}

这里调用了一个循环获取解析生成器 Generator 的方法,而解析生成器有多个实现类:ResourcesCacheGeneratorSourceGeneratorDataCacheGenerator,它们负责各种硬盘缓存策略下的缓存管理,所以这里关键的条件在于 currentGenerator.startNext() 循环获取每个Generator能否获取到缓存,获取不到就通过 getNextGenerator() 进行下一种:

class DecodeJob<R> implements DataFetcherGenerator.FetcherReadyCallback,Runnable,Comparable<DecodeJob<?>>,Poolable {...private DataFetcherGenerator getNextGenerator() {switch (stage) {case RESOURCE_CACHE:return new ResourceCacheGenerator(decodeHelper, this);case DATA_CACHE:return new DataCacheGenerator(decodeHelper, this);case SOURCE:return new SourceGenerator(decodeHelper, this);case FINISHED:return null;default:throw new IllegalStateException("Unrecognized stage: " + stage);}}...
}

所以我们看看 ResourceCacheGenerator.startNext() ,看下它是用什么来缓存的,其中部分代码如下:

class ResourceCacheGenerator implements DataFetcherGenerator,DataFetcher.DataCallback<Object> {...public boolean startNext() {...while (modelLoaders == null || !hasNextModelLoader()) {...Key sourceId = sourceIds.get(sourceIdIndex);Class<?> resourceClass = resourceClasses.get(resourceClassIndex);Transformation<?> transformation = helper.getTransformation(resourceClass);currentKey =new ResourceCacheKey(// NOPMD AvoidInstantiatingObjectsInLoopshelper.getArrayPool(),sourceId,helper.getSignature(),helper.getWidth(),helper.getHeight(),transformation,resourceClass,helper.getOptions());cacheFile = helper.getDiskCache().get(currentKey);if (cacheFile != null) {sourceKey = sourceId;modelLoaders = helper.getModelLoaders(cacheFile);modelLoaderIndex = 0;}}...return started;}...
}

这里通过一个资源的关键信息生成key,然后调用 helper.getDiskCache().get() ,我们跟进去 DiskCache 看看:

final class DecodeHelper<Transcode> {...DiskCache getDiskCache() {return diskCacheProvider.getDiskCache();}...
}
class DecodeJob<R> implements DataFetcherGenerator.FetcherReadyCallback,Runnable,Comparable<DecodeJob<?>>,Poolable {...interface DiskCacheProvider {DiskCache getDiskCache();}...
}

可以看到最终是调用了 DiskCacheProvider 接口的 getDiskCache() 方法获取一个 DiskCache 对象,那么这个D对象又是什么来头呢?

public interface DiskCache {...
}

可以看到这是一个用来缓存硬盘数据的接口,那么它的实现就是我们要找的最终目标:

public class DiskLruCacheWrapper implements DiskCache {...private DiskLruCache diskLruCache;...
}

里面的就不详细分析下去了,这里主要维护了一个 DiskLruCache ,Glide就是通过这个来实现硬盘缓存的。

可以看到Glide的硬盘缓存是依靠DiskLruCache来进行缓存的,同样也是Lru算法。

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

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

相关文章

计算机操作系统 死锁问题

概念 条件是基础&#xff0c;在一定的原因下&#xff0c;产生结果死锁三胞胎 死锁 僵持&#xff0c;消耗时间&#xff0c;双方都占用了部分资源&#xff0c;不释放活锁 双方互相谦让&#xff0c;都不占用资源饥饿 谦让的一方一直等待&#xff0c;无法占有资源&#xff0c;导致…

C++ 力扣剑指Offer16-数值的整数次方

题目详情 /* * 实现函数double Power(double base, int exponent)&#xff0c; * 求base的exponent次方。不得使用库函数&#xff0c;同时不需要考虑大数问题。示例 1: 输入: 2.00000, 10 输出: 1024.00000示例 2: 输入: 2.10000, 3 输出: 9.26100 * 示例 3: 输入: 2.00000, -…

Android Glide图片加载框架(四)回调与监听

文章目录Android Glide图片加载框架系列文章 Android Glide图片加载框架&#xff08;一&#xff09;基本用法 Android Glide图片加载框架&#xff08;二&#xff09;源码解析之with() Android Glide图片加载框架&#xff08;二&#xff09;源码解析之load() Android Glide图…

算法章节 数组、链表、栈、队列

数组 概念与特性 1&#xff0c;数组是线性表&#xff0c;用一组连续的内存空间存储⼀组具有相同类型的数据 2&#xff0c;最大的特性是⽀持按照下标O(1)时间复杂度内快速访问数组元素 3&#xff0c;⼀维数组寻址公式&#xff1a;a[i]_addr base_addr i * data_type_size 操作…

武忠祥.高等数学.基础课-第一章函数 极限 连续P10

sin(1/x) 详细解析网址 1.图像 2.极限 x–>0时,函数极限不存在 sin2x 详细作图网址 1.图像 2.周期为Π f(x)周期为T,f(axb)周期为T/|a| 所以sinx周期为2Π,sin2x周期为2Π/2Π |sinx| 详细讲解网址 1.图像 2.周期:Π 3.绝对值 &#xff08;1&#xff09;y|sinx|的图…

Java命令:jstat — 查看JVM的GC信息

文章目录一、简介二、常用命令1、jstat -class pid : class loader行为统计2、jstat -compiler pid : JIT编译器行为统计3、jstat -gc pid 5000 20 : 垃圾回收堆行为统计4、jstat -gccapacity pid 5000 20 : 堆内存统计5、jstat -gcutil pid 5000 20 : 总结垃圾回收统计6、jsta…

算法章节 递归、排序、⼆分查找

递归 概念与特性函数调⽤函数⾃身的编程⽅式叫做递归&#xff0c;调⽤为”递“&#xff0c;返回为”归“三个条件1. ⼀个问题的解可以分解为多个⼦问题的解&#xff1b; 2. 分解之后的⼦问题&#xff0c;除了数据规模不同&#xff0c;求解思路跟原问题相同&#xff1b; 3. 存在…

codeforces 50A-C语言解题报告

50A题目网址 解题报告-others 题目解析 1.输入n x m大小的木板,使用21大小的多米诺去填满,求最多的多米诺数目 2.通过分析把木板分为奇数和偶数的情况 1)有一边是偶数的情况: 使用2去填满 2)两个边都是奇数 奇数-1偶数 还是让木板的(奇数-1)边去和2平行,再加上 (m-1)/2(n/1)…

Java命令:jps — 查看进程信息

文章目录一、简介二、常用命令1、jps2、jps -l3、jps -q4、jps -m5、jps -v6、jps失效一、简介 JVM Process Status Tool&#xff0c;显示指定系统内所有的HotSpot虚拟机进程。 功能&#xff1a; 显示当前所有java进程pid的命令&#xff0c;我们可以通过这个命令来查看到底启…

操作系统概述 记录操作系统相关知识

操作系统 现代计算机系统由一个或多个处理器、主存、打印机、键盘、鼠标、显示器、网络接口以及各种输入/输出设备构成。上面提到的这些东西都属于硬件资源&#xff0c;用户不会直接和硬件进行交互&#xff0c;计算机安装了一层软件&#xff0c;这层软件能够通过响应用户输入的…

2014年英语一作文partA

作文讲解网址 题目 Write a letter of about 100 words to the president of your university, suggesting how to improve students’ physical condition. You should include the details you think necessary. You should write neatly on the ANSWER SHEET. Do not sign…

JDK工具使用大全

文章目录一、简介一、简介 在JDK的bin目录下有很多命令行工具&#xff1a; 常用工具使用详解如下&#xff1a; Java命令&#xff1a;jps — 查看进程信息 Java命令&#xff1a;jstack — 获取线程dump信息 Java命令&#xff1a;jmap — 打印指定进程的共享对象内存映射或…

Linux进程 excel族函数的用法

介绍 使用fork创建一个进程之后&#xff0c;经常会在新进程中调用exec函数执行别的程序当前进程调用exec函数之后&#xff0c;这个进程会被完全替代换成新的程序&#xff0c;即便如此仍然是同一个进程&#xff0c;进程ID不变函数族 execl execlp execle execvp execvpe头文件 …

codeforces 118A-C语言解题报告

118A题目网址 题目解析 1.输入一个英语字符串,要求把其中的元音字母删去(元音是字母“A”、“O”、“Y”、“E”、“U”、“I”包括大小写&#xff0c;其余的是辅音),把剩余的辅音字母全部变为小写,并在每一个辅音字母之前加上一个. 如: 输入: Codeforces 输出: .c.d.f.r.c.s…

ArrayList和HashMap遍历比较

目录一、ArrayList遍历方式1、普通for循环遍历2、增强for循环遍历3、Iterator迭代器遍历4、三种方式比较二、Map遍历方式1、增强for循环 keySet() 遍历2、增强for循环 entrySet() 遍历3、Iterator keySet() 遍历4、Itorator entrySet() 遍历5、四种方式比较三、java开发手册…

C++primer 12章 动态内存和智能指针

C引入智能指针的目的 使用智能指针来管理动态分配的对象&#xff0c;当一个对象应该被释放的时候&#xff0c;指向他的智能指针确保自动释放它 内存分配 静态内存&#xff1a;局部static对象、类static数据成员、定义在任何函数之外的变量栈内存&#xff1a;定义在函数内的非…

Mac下iTerm2的安装与配置

目录一、iTerm2简介二、下载以及安装三、iTerm2主题配置四、配置Oh My Zsh1、安装方式&#xff08;1&#xff09;一键安装&#xff08;2&#xff09;手动安装3、切换zsh4、修改主题五、配置Meslo字体六、声明高亮七、自动建议填充八、iTerm2快速隐藏和显示九、iTerm2隐藏用户名…

codeforces 282A-C语言解题报告

282A题目网址 题目解析 1.第一行输入n(表示有n条语句都要执行),再输入X,X(都表示X1),–X,X–(都表示X-1),最初X0,输出X的值 2.使用字符数组去存放每一行的字符串,因为字符串,所以直接整体存入scanf("%s",c); 3.因为字符数组最后一个是’\0’去表示末尾,所以要开辟…

Java命令:jinfo — 查看进程参数

目录一、简介二、常用命令1、jinfo -flags pid : 打印当前指定java进程中已经设定的所有JVM参数信息2、jinfo -flag pid : 打印指定名称的参数3、jinfo -flag [|-] pid : 打开或关闭参数4、jinfo -sysprops pid : 打印当前java进程中设定的系统环境参数一、简介 jinfo 是 JDK …

C++primer第八章 IO库 8.1 IO类

IO库设施 istream &#xff08;输入流&#xff09;类型&#xff0c;提供输入操作。ostream &#xff08;输出流&#xff09;类型&#xff0c;提供输出操作。cin,—个 istream对象&#xff0c;从标准输入读取数据。cout, 一个ostream对象&#xff0c;向标准输出写入数据。cerr…