探索Glide对Gif图片资源的获取、解析过程

先预祝大家汤圆节快乐!很久没写博客了。今天我们来探索一下Glide是如何支持Gif图片加载的。

本篇博客的目的

  • 了解代码分析的基本思路与方法
  • 了解Glide是如何对Gif图片进行支持的

探索背景

为什么会有这么一个想法呢,一来一直对Glide是知其名而不知其所以然,二来还主要是工作中需要对它研究研究,以便更好的支持工作内容。

我想很多同学都希望自己可以对某种著名的开源框架了解贯通,但是很多时候研究一款框架实在是费神费力,很容易就会放弃。

造成这样的困局主要有三点:

  • 一来因为我们在探究源码时没有明确的目标。
  • 二来是因为我们没有合适顺手的工具。
  • 三来是因为找不到重点,容易被其它不相干代码迷惑。

接下来我们就对上面这些问题一一带入。

探索准备工作

1,首先我的目标很明确,我需要了解Glide是否支持Gif图片,以及它是如何支持Gif图片的。这样我才可以在应用层对其做良好的支持。

因为我们的工作要求是:所有的ImageView都必须支持Gif图片

我的解决办法有三种:

  • 1.如果Glide支持Gif图片,那么我只需要在图片调用层全部加上Gif支持开关。(事实上Glide默认就支持Gif,不需要我单独添加控制。)
  • 2.如果Glide支持Gif图片,但是它的检测开销成本很大,那我就必须手动对资源进行解析,判断是否是Gif,如果是,则调用Gif图片的加载逻辑。如果不是,则走一般的图片加载逻辑。
  • 3.如果Glide不支持Gif图片,那么我必须对ImageView进行扩展,然后更改应用内所有的ImageView的继承关系。这个工作量是巨大的。

因为有以上判断条件,所以我决定先从Glide的Gif支持入手。

2,因为我们需要对Glide研究、分析,那么手上必须有Glide的最新代码。我们在Glide的主页上找到源代码的下载地址,下载即可。

Glide首页: https://github.com/bumptech/glide/releases
Glide源码地址: https://github.com/bumptech/glide/releases/download/v3.7.0/glide-3.7.0-sources.jar

3.准备工作已经做的差不多了,最后还剩代码分析利器Android Studio以及Source Insight,当然放在手边为我们做辅助记录的笔和纸是少不了的。

Source Insight的主页为:https://www.sourceinsight.com/ Source Insight的功能很强大,我也只是懂一点点基本用法而已,不过足够用了。下载好的代码需要使用Source Insight打开,我们需要实时检索文件使用。这里不再说明Source Insight的用法,请自行学习了解。它在这里的作用是帮我们做一些引用关系检查。

除了Source Insight之外,我们主要使用Android Studio进行代码分析调试。需要将刚刚下载好的源代码解压,然后作为我们工程的一部分:
这里写图片描述

然后按照Glide的使用说明开始我们的分析入口编写:

// For a simple view:
@Override public void onCreate(Bundle savedInstanceState) {...ImageView imageView = (ImageView) findViewById(R.id.my_image_view);Glide.with(this).load("http://qq.yh31.com/tp/zjbq/201612231514480890.gif").into(imageView);
}

为了辅助我们一次次分析Glide的网络访问,我们在onDestroy方法中加入以下代码:

protected void onDestroy() {super.onDestroy();Glide.get(this).clearMemory();Glide.get(this).clearDiskCache();
}

开始探索之旅

我们如果需要了解Glide是否默认支持Gif图片,那么只需要在load方法内替换成gif图片的地址即可。

我们发现,它支持。

那么它是如何完成网络资源获取、Gif类型识别、Gif资源解析这些工作的呢?下面让我们一起来一探究竟。

Glide的网络资源获取

Glide对Gif资源的获取也是Glide网络请求的核心,我想大家对这些框架一般都看中的是这部分。让我们从这里究其所以然。

在这里声明一下,我们刚开始拿到代码时,就算会使用,也不知道真正的分析入口在哪里。但是不要灰心,就算是对代码再熟悉的人,也会迷失在这结构复杂的代码海洋里。请记住,分析的过程是总是需要来回反复查看、尝试的。所以手边的纸和笔对我们的帮助就体现出来了,我们需要通过纸和笔来记录我们走过的重要流程。

PS: 以后的分析过程会将没有歧义的过程自动略过,并且会将无关代码自动省略。

PS: 我们的分析手段主要有两种,一是通过断点调试来分析,二是通过上下文来分析。其中第一种比较方便,文章中主要采用第一种方法。

我们先来分析这段代码:

Glide.with(this)

由于我们是在Activity中使用的,所以这里的this应当是Activity,我们进入这个方法查看:

    public static RequestManager with(FragmentActivity activity) {RequestManagerRetriever retriever = RequestManagerRetriever.get();return retriever.get(activity);}

好,从上面得知,这个方法返回了一个RequestManager对象,接下来分析

.load("http://qq.yh31.com/tp/zjbq/201612231514480890.gif")

这里的load方法则调用的是RequestManager的load方法,我们看一下:

    public DrawableTypeRequest<String> load(String string) {return (DrawableTypeRequest<String>) fromString().load(string);}

我们看到,load方法返回了一个DrawableTypeRequest对象,我们先记住它。接下来我们需要分析

.into(new ImageView(this));

我们跟着这个into方法一路追踪,最后来到了GenericRequestBuilder的into方法:

    public <Y extends Target<TranscodeType>> Y into(Y target) {...Request request = buildRequest(target);target.setRequest(request);lifecycle.addListener(target);requestTracker.runRequest(request);return target;}

这里我们看到构建了一个Request对象,我们进去看一下是如何构建这个对象的,最后我们在GenericRequestBuilder类中定位到了这个方法:

    private Request obtainRequest(Target<TranscodeType> target, float sizeMultiplier, Priority priority,RequestCoordinator requestCoordinator) {return GenericRequest.obtain(loadProvider,model,signature,context,priority,target,sizeMultiplier,placeholderDrawable,placeholderId,errorPlaceholder,errorId,fallbackDrawable,fallbackResource,requestListener,requestCoordinator,glide.getEngine(),transformation,transcodeClass,isCacheable,animationFactory,overrideWidth,overrideHeight,diskCacheStrategy);}

看来上面提到的Request对象实则为GenericRequest的实例,我们先记下。

然后返回进入requestTracker.runRequest(request)中查看,看起来像是运行这个请求的意思。

runRequest的内部实现是这样的:

    public void runRequest(Request request) {requests.add(request);if (!isPaused) {request.begin();} else {pendingRequests.add(request);}}

它内部调用了request对象的begin方法,也就是说这里调用了GenericRequest的begin()方法。我们找到这个方法:

    public void begin() {...if (Util.isValidDimensions(overrideWidth, overrideHeight)) {onSizeReady(overrideWidth, overrideHeight);} else {target.getSize(this);}...}

在这里走的else条件,我们可能已经不太记得target到底是谁实现的,它只是个接口,幸好有AS,我们通过调试知道这个target其实为:GlideDrawableImageViewTarget,具体它是什么时候被设置到这里的,我们先不去深究它,肯定能找到地方,但找它不是我们的目的。

我们找到它对应的getSize()方法:

    public void getSize(SizeReadyCallback cb) {sizeDeterminer.getSize(cb);}

我们不要在这里停留,继续往下走,最后我们会走到com.bumptech.glide.request.GenericRequest的onSizeReady方法中,我们在这里注意重点部分:

    public void onSizeReady(int width, int height) {...loadStatus = engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder,priority, isMemoryCacheable, diskCacheStrategy, this);...}

从Engine的load方法我们进去看,这里是我们继续执行的重点,我们进入到com.bumptech.glide.load.engine.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) {...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);...return new LoadStatus(cb, engineJob);}

在这路上一定不能被其它代码迷惑,要感知哪部分是重点,尝试自己分析一下这部分。有没有很像任务及线程池?没错,你如果看各个类之间的继承关系的话,它们确实是,我们就不再看它们之间的关系,我们只用看EngineRunnable的run()方法。

    public void run() {...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 (resource == null) {onLoadFailed(exception);} else {onLoadComplete(resource);}}

这段代码主要由两部分组成,这先简单描述一下它们的工作流程,首先进入decode方法尝试从缓存中获取资源,第一次当然是null,然后进入onLoadFailed方法。onLoadFailed会将这个任务再次提交,再次重新执行,这次会进入decodeFromSource方法:

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

我们一路向下,最后来到com.bumptech.glide.load.engine.DecodeJob的decodeSource方法,这个过程千万别掉队了,这里马上就要见到如何访问网络了:

    private Resource<T> decodeSource() throws Exception {...final A data = fetcher.loadData(priority);...decoded = decodeFromSourceData(data);...return decoded;}

这里有两部分重点,一个是获取资源,一个是对资源进行解析。这里的fetcher也是一个接口,它的实现类中有HttpUrlFetcher,很明显的网络资源获取类,我们通过调试也发现这里的对象是ImageVideoFetcher,而它的内部正是调用了HttpUrlFetcher的loadData方法,我们再继续往下,我们很快就发现了Glide的网络访问核心方法:

    private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl, Map<String, String> headers)throws IOException {...urlConnection = connectionFactory.build(url);for (Map.Entry<String, String> headerEntry : headers.entrySet()) {urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());}urlConnection.setConnectTimeout(2500);urlConnection.setReadTimeout(2500);urlConnection.setUseCaches(false);urlConnection.setDoInput(true);...final int statusCode = urlConnection.getResponseCode();if (statusCode / 100 == 2) {return getStreamForSuccessfulRequest(urlConnection);} ...}

好,是不是很熟悉呢?原来Glide内部使用了Android的HttpURLConnection来进行网络访问,而且这里的访问访问超时时间是固定的:2500毫秒。

到目前为止,我们所处的位置为HttpUrlFetcher的loadDataWithRedirects方法,当然,我们并不在主线程:

      at com.bumptech.glide.load.data.HttpUrlFetcher.loadDataWithRedirects(HttpUrlFetcher.java:49)at com.bumptech.glide.load.data.HttpUrlFetcher.loadData(HttpUrlFetcher.java:44)at com.bumptech.glide.load.data.HttpUrlFetcher.loadData(HttpUrlFetcher.java:20)at com.bumptech.glide.load.model.ImageVideoModelLoader$ImageVideoFetcher.loadData(ImageVideoModelLoader.java:70)at com.bumptech.glide.load.model.ImageVideoModelLoader$ImageVideoFetcher.loadData(ImageVideoModelLoader.java:53)at com.bumptech.glide.load.engine.DecodeJob.decodeSource(DecodeJob.java:170)at com.bumptech.glide.load.engine.DecodeJob.decodeFromSource(DecodeJob.java:128)at com.bumptech.glide.load.engine.EngineRunnable.decodeFromSource(EngineRunnable.java:122)at com.bumptech.glide.load.engine.EngineRunnable.decode(EngineRunnable.java:101)at com.bumptech.glide.load.engine.EngineRunnable.run(EngineRunnable.java:58)

所以,到目前为止,我们已经知道了Glide是如何访问网络的。

Glide对Gif资源的识别方式

接着上面的部分继续,因为我们已经得到了从网络传回的数据流,那么接下来就需要对这些数据进行解析,我们回到com.bumptech.glide.load.engine.DecodeJo的decodeSource方法,也就是回到这里:

    private Resource<T> decodeSource() throws Exception {Resource<T> decoded = null;try {long startTime = LogTime.getLogTime();final A data = fetcher.loadData(priority);...decoded = decodeFromSourceData(data);} finally {fetcher.cleanup();}return decoded;}

因为我们是从fetcher.loadData中返回的,所以接下来我们需要进入decodeFromSourceData方法内,然后再一路向下追踪,最后来到com.bumptech.glide.load.resource.gifbitmap.GifBitmapWrapperResourceDecode的decodeStream方法内:

    private GifBitmapWrapper decodeStream(ImageVideoWrapper source, int width, int height, byte[] bytes)throws IOException {InputStream bis = streamFactory.build(source.getStream(), bytes);bis.mark(MARK_LIMIT_BYTES);ImageHeaderParser.ImageType type = parser.parse(bis);bis.reset();...return result;}

我们会注意到有段代码,将InputStream解析为了ImageHeaderParser.ImageType类型的对象,我们可以猜测,这极有可能是对各种网络流进行分类的地方,我们进去继续向下追踪一探究竟,最后来到com.bumptech.glide.load.resource.bitmap.ImageHeaderParser的getType方法:

    public ImageType getType() throws IOException {int firstTwoBytes = streamReader.getUInt16();// JPEG.if (firstTwoBytes == EXIF_MAGIC_NUMBER) {return JPEG;}final int firstFourBytes = firstTwoBytes << 16 & 0xFFFF0000 | streamReader.getUInt16() & 0xFFFF;// PNG.if (firstFourBytes == PNG_HEADER) {// See: http://stackoverflow.com/questions/2057923/how-to-check-a-png-for-grayscale-alpha-color-typestreamReader.skip(25 - 4);int alpha = streamReader.getByte();// A RGB indexed PNG can also have transparency. Better safe than sorry!return alpha >= 3 ? PNG_A : PNG;}// GIF from first 3 bytes.if (firstFourBytes >> 8 == GIF_HEADER) {return GIF;}return UNKNOWN;}

果不其然,在这个方法内部对所有的数据进行识别,我们在最后面看到了gif数据的识别原理:firstFourBytes >> 8 == GIF_HEADER。

Glide对Gif资源的解析方式

好,既然知道了现在的数据流是gif了,那么接下来就是解析过程了,我们回到com.bumptech.glide.load.resource.gifbitmap.GifBitmapWrapperResourceDecoder的decodeStream方法处,继续往下走,我们很快就在该方法内看到有这么一行代码:

        if (type == ImageHeaderParser.ImageType.GIF) {result = decodeGifWrapper(bis, width, height);}

原来这个方法对GIF类型的图片做了专门的处理,我们进入这个方法并一路向下,最后我们会来到com.bumptech.glide.load.resource.gif.GifResourceDecoder的decode(byte[] data, int width, int height, GifHeaderParser parser, GifDecoder decoder)方法:

    private GifDrawableResource decode(byte[] data, int width, int height, GifHeaderParser parser, GifDecoder decoder) {...Bitmap firstFrame = decodeFirstFrame(decoder, header, data);...GifDrawable gifDrawable = new GifDrawable(context, provider, bitmapPool, unitTransformation, width, height,header, data, firstFrame);return new GifDrawableResource(gifDrawable);}

我们注意到在这个方法内解析了Gif资源的第一帧。我们进到decodeFirstFrame方法看一下它是如何解析的:

    private Bitmap decodeFirstFrame(GifDecoder decoder, GifHeader header, byte[] data) {decoder.setData(header, data);decoder.advance();return decoder.getNextFrame();}

这里最后调用了decoder.getNextFrame()方法,这里的decoder为GifDecoder,也就是专门用于解析Gif资源的解码器,我们进入getNextFrame()方法一探究竟:

    public synchronized Bitmap getNextFrame() {...status = STATUS_OK;GifFrame currentFrame = header.frames.get(framePointer);GifFrame previousFrame = null;int previousIndex = framePointer - 1;if (previousIndex >= 0) {previousFrame = header.frames.get(previousIndex);}...// Transfer pixel data to image.Bitmap result = setPixels(currentFrame, previousFrame);...return result;}

这里的代码还挺长的,我们只挑最主要的看,它最后调用了setPixels()方法:

   private Bitmap setPixels(GifFrame currentFrame, GifFrame previousFrame) {...// Decode pixels for this frame  into the global pixels[] scratch.decodeBitmapData(currentFrame);// Copy each source line to the appropriate place in the destination.int pass = 1;int inc = 8;int iline = 0;for (int i = 0; i < currentFrame.ih; i++) {int line = i;if (currentFrame.interlace) {if (iline >= currentFrame.ih) {pass++;switch (pass) {case 2:iline = 4;break;case 3:iline = 2;inc = 4;break;case 4:iline = 1;inc = 2;break;default:break;}}line = iline;iline += inc;}line += currentFrame.iy;if (line < header.height) {int k = line * header.width;// Start of line in dest.int dx = k + currentFrame.ix;// End of dest line.int dlim = dx + currentFrame.iw;if ((k + header.width) < dlim) {// Past dest edge.dlim = k + header.width;}// Start of line in source.int sx = i * currentFrame.iw;while (dx < dlim) {// Map color and insert in destination.int index = ((int) mainPixels[sx++]) & 0xff;int c = act[index];if (c != 0) {dest[dx] = c;}dx++;}}}...// Set pixels for current image.Bitmap result = getNextBitmap();result.setPixels(dest, 0, width, 0, 0, width, height);return result;}

这段代码还是很长,我们将不主要的代码隐去,中间很长一部分推测应该是进行数据转换。最终是调用了Bitmap的setPixels方法完成位图的创建。

好,到此为止,我们知道了Gif图是如何解析成位图的了,然后我们返回,回到com.bumptech.glide.load.resource.gif.GifResourceDecoder的decode方法继续向下走:

    private GifDrawableResource decode(byte[] data, int width, int height, GifHeaderParser parser, GifDecoder decoder) {...Bitmap firstFrame = decodeFirstFrame(decoder, header, data);//这里是刚刚出来的地方,从这里继续向下if (firstFrame == null) {return null;}Transformation<Bitmap> unitTransformation = UnitTransformation.get();GifDrawable gifDrawable = new GifDrawable(context, provider, bitmapPool, unitTransformation, width, height,header, data, firstFrame);return new GifDrawableResource(gifDrawable);}

我们很快就发现,刚才解析好的位图被用作创建了GifDrawable对象,然后GifDrawable对象又用来创建了GifDrawableResource对象,然后返回,回到最开始的com.bumptech.glide.load.engine.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 (resource == null) {onLoadFailed(exception);} else {onLoadComplete(resource);//然后代码继续向下执行会从这里走}}

我们回到最开始的EngineRunnable的run方法。然后我们知道这里的resource不是null,所以进入onLoadComplete方法。到这里为止,我们就完成了Gif资源的解析过程分析。

从onLoadComplete方法开始就是Gif资源的轮播流程了,由于篇幅有限,在这里就不再涉及,有兴趣的同学可以自行分析锻炼一下。

最后希望同学们可以尝试使用本方法举一反三,分析一下其它框架,反复学习,加深印象。


我建了一个QQ群,欢迎对学习有兴趣的同学加入。我们可以一起探讨、深究、掌握那些我们会用到的技术,让自己不至于太落伍。
这里写图片描述

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

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

相关文章

美团“猜你喜欢”深度学习排序模型实践

引言 推荐作为解决信息过载和挖掘用户潜在需求的技术手段&#xff0c;在美团点评这样业务丰富的生活服务电子商务平台&#xff0c;发挥着重要的作用。在美团App里&#xff0c;首页的“猜你喜欢”、运营区、酒店旅游推荐等重要的业务场景&#xff0c;都是推荐的用武之地。 目前&…

如何关闭docker容器里的进程

如何关闭docker容器里的进程 1、使用docker exec 容器名 ps -ef命令查看进程信息 示例&#xff1a; 创建名为"redis"的容器&#xff0c;并在容器内部和宿主机中查看容器中的进程信息&#xff1a; 2、然后进入该容器中&#xff0c;执行如下命令即可停掉进程&…

论文浅尝 | 一日看尽长安花--NLP可解释研究梳理

本文是对TACL2019《Analysis Methods in Neural Language Processing: A Survey》的翻译和整理。本文转载自知乎&#xff0c;文章链接&#xff1a;https://zhuanlan.zhihu.com/p/265815975导读NLP领域发展迅猛&#xff0c;其模型分析方法也正逐渐火热。为什么要研究NLP的可解释…

简明扼要的反射入门教程

反射 反射作为RTTI语言&#xff08;比如Java&#xff09;的基础之一被很多人所熟知&#xff0c;但是有些同学对反射本身还是懵懵懂懂的&#xff0c;不是很清楚它到底有什么用。今天这节课我们就对反射本身来一个通体的认知。 定义 反射所在的包为&#xff1a;java.lang.refl…

人在斯坦福,刚上CS224n

文 | Jazon大家好&#xff0c;我是Jazon&#xff0c;现在是Stanford计算机硕士项目的一只学生&#xff0c;非常荣幸加入小夕的大家庭&#xff01;请各路大神多多指教呀。2021年1月12日&#xff0c;又一季Stanford CS224n——自然语言处理开课了&#xff0c;我和很多MSCS同学一起…

美团外卖骑手背后的AI技术

背景 随着数字化时代的到来&#xff0c;外卖市场近年来发展非常迅猛。对外卖物流系统而言&#xff0c;配送效率和用户体验至关重要。而实际配送过程是由配送员&#xff08;骑手&#xff09;最终完成的&#xff0c;因此&#xff0c;想要真正提升配送效率&#xff0c;不但要在智能…

软件设计师:下午试题类型以及问题总结+软件设计师考试下午题统计分析(06到2019年)

‘’’ 首次更新下午试题 ‘’’ 下午考试时间为150分钟&#xff08;14:00–16:30&#xff09; 试题一到四为必答题&#xff0c;试题五和六为选答一个&#xff0c;每题15分&#xff0c;总共75分&#xff0c;每题大概3小问 第一题为结构化分析与设计&#xff0c;考查数据流…

LeetCode 494. 目标和(DFS+DP)

文章目录1. 题目2. 解题2.1 递归2.2 DP1. 题目 给定一个非负整数数组&#xff0c;a1, a2, …, an, 和一个目标数&#xff0c;S。现在你有两个符号 和 -。对于数组中的任意一个整数&#xff0c;你都可以从 或 -中选择一个符号添加在前面。 返回可以使最终数组和为目标数 S 的…

使用Kotlin开发Android应用初体验

使用Kotlin开发Android应用初体验 昨晚&#xff0c;最近一届的谷歌IO大会正式将Kotlin确定为了官方开发语言&#xff0c;作为一名Android开发鸟&#xff0c;怎么能不及时尝尝鲜呢? Kotlin的简要介绍 在开发之前&#xff0c;很多同学一定有很多疑问&#xff0c;Kotlin到底有…

论文浅尝 - ICML2020 | 基于子图推理的归纳式关系预测

论文笔记整理&#xff1a;陈名杨&#xff0c;浙江大学在读博士生&#xff0c;主要研究方向为知识图谱表示学习。论文来源&#xff1a;ICML 2020Introduction当前在知识图谱上&#xff08;KnowledgeGraph&#xff0c;KG&#xff09;进行关系预测的很多方法都依靠在对知识图谱中的…

CVPR 二十年,影响力最大的 10 篇论文!

文 | 二玖极市平台此前&#xff0c;极市盘点了图像分割在过去二十年中影响力最大的10篇论文&#xff0c;得到了许多开发者的支持。今天&#xff0c;我们将对计算机视觉领域三大顶会之一CVPR在近二十年来中产生的优秀论文进行一个全面的盘点与总结。CVPR是计算机视觉领域三大顶会…

DataMan-美团旅行数据质量监管平台实践

背景 数据&#xff0c;已经成为互联网企业非常依赖的新型重要资产。数据质量的好坏直接关系到信息的精准度&#xff0c;也影响到企业的生存和竞争力。Michael Hammer&#xff08;《Reengineering the Corporation》一书的作者&#xff09;曾说过&#xff0c;看起来不起眼的数据…

jupyter notebook切换到其他配置好的conda虚拟环境

1 手把手教你如何把jupyter notebook切换到其他配置好的conda虚拟环境 https://blog.csdn.net/weixin_41813895/article/details/84750990 2 Jupyter notebook切换Python环境 https://www.jianshu.com/p/8188c32a3a34

LeetCode 133. 克隆图(图的BFS/DFS)

1. 题目 给定无向连通图中一个节点的引用&#xff0c;返回该图的深拷贝&#xff08;克隆&#xff09;。图中的每个节点都包含它的值 val&#xff08;Int&#xff09; 和其邻居的列表&#xff08;list[Node]&#xff09;。 class Node { public:int val;vector<Node*> n…

七天搞定java接口自动化测试实战,一文搞定...

前言 无论是自动化测试还是自动化部署&#xff0c;撸码肯定少不了&#xff0c;所以下面的基于java语言的接口自动化测试&#xff0c;要想在业务上实现接口自动化&#xff0c;前提是要有一定的java基础。 如果没有java基础&#xff0c;也没关系。这里小编也为大家提供了一套jav…

Android自动化测试探索

前言 通常来说&#xff0c;我们开发完成产品之后&#xff0c;都是由测试组或者是我们自己点一点&#xff0c;基本上没有问题了就开始上线。但是&#xff0c;随着时间的堆叠&#xff0c;一款产品的功能也越来越多。这时&#xff0c;我们为了保证产品的质量&#xff0c;就需要在…

我整理了100道大厂算法岗面试必考题!

最近&#xff0c;很多小伙伴给我留言去面试被面试官吊打了&#xff0c;尤其是一些去面大厂的朋友&#xff0c;甚至一面都没有过...来&#xff0c;别灰心&#xff0c;我以过往经验告诉你大厂面试的通关秘诀&#xff01;对&#xff0c;就是算法&#xff01;大厂面试必考算法&…

参会邀请 - ISWC2020 | 第十九届国际语义网会议

第十九届国际语义网会议&#xff08;ISWC2020&#xff09;将于11月1日至6日远程召开。国际语义网会议是全球最重要的且最有影响力的国际学术会议&#xff0c;主要聚焦语义网&#xff0c;知识图谱&#xff0c;本体&#xff0c;链接数据等面向互联网的人工智能技术。国际语义网会…

美团配送资金安全治理之对账体系建设

前言 随着美团配送业务的飞速发展&#xff0c;单量已经达到千万级别&#xff0c;同时每天产生的资金额已经超过几千万&#xff0c;清结算系统在保证线上服务稳定可靠的前提下&#xff0c;如何系统化的保障资金安全是非常核心且重要的课题&#xff0c;配送清结算系统经过近3年的…

Githug第42关rebase_onto通关秘籍

Githug是一个用来了解、熟悉Git的一个非常好的游戏。 目前网站上收录的都是之前只有55关的解题方法&#xff0c;没有新增的rebase_onto这一关的内容。现在Githug一共有56关。现将新增的42关的解答内容更新如下&#xff1a; 第42关的题目如下: Name: rebase_onto Level: 41 D…