Android Picasso 网络请求模块深度剖析(四)

Android Picasso 网络请求模块深度剖析

本人掘金号,欢迎点击关注:掘金号地址

本人公众号,欢迎点击关注:公众号地址

一、引言

在 Android 开发领域,图片加载是极为常见的功能需求。用户在浏览各类应用时,会频繁地遇到图片展示的场景,如社交媒体应用中的用户头像、商品图片,新闻资讯应用中的配图等。高效、稳定地加载图片对于提升应用的用户体验至关重要。Picasso 作为一款知名的 Android 图片加载库,凭借其简洁的 API 和出色的性能,在开发者群体中广泛应用。其中,网络请求模块是 Picasso 实现图片从网络加载到本地展示的核心部分,它负责与远程服务器进行通信,获取图片资源。深入剖析 Picasso 的网络请求模块,有助于开发者更好地理解其工作原理,进而优化应用的图片加载功能,提升应用的整体性能。

二、Picasso 网络请求模块概述

2.1 模块功能概述

Picasso 的网络请求模块主要承担从网络服务器获取图片数据的任务。当开发者调用 Picasso 的 API 加载一张网络图片时,该模块会根据图片的 URL 发起网络请求,与服务器建立连接,获取图片的二进制数据,并将其传递给后续的解码和显示模块。同时,该模块还具备缓存处理、错误处理等功能,以提高图片加载的效率和稳定性。

2.2 主要类和接口介绍

在 Picasso 的网络请求模块中,涉及多个关键的类和接口,下面对它们进行简要介绍:

  • Downloader 接口:这是网络请求的核心接口,定义了下载图片的基本方法。任何实现该接口的类都可以作为 Picasso 的网络请求器,负责从网络或其他数据源下载图片。
  • OkHttp3Downloader 类Downloader 接口的具体实现类,使用 OkHttp3 库进行网络请求。OkHttp3 是一个高效的 HTTP 客户端库,具有连接池管理、缓存支持、拦截器等特性,能够提升网络请求的性能和稳定性。
  • NetworkRequestHandler 类 :负责处理网络请求的逻辑,它会调用 Downloader 进行实际的下载操作。当 Picasso 接收到一个网络图片请求时,会将请求传递给 NetworkRequestHandler,由它来决定是否可以处理该请求,并执行下载流程。
  • Response 类:表示网络请求的响应结果,包含了图片数据和相关的元信息,如是否来自缓存、响应的状态码等。

三、Downloader 接口分析

3.1 接口定义

java 复制代码
// Downloader.java
package com.squareup.picasso;

import java.io.IOException;

/**
 * 定义了下载图片的基本方法,是网络请求的核心接口。
 */
public interface Downloader {
    /**
     * 从指定的 URI 下载图片,并返回响应结果。
     * @param uri 图片的 URI
     * @param networkPolicy 网络策略,用于控制缓存和请求行为
     * @return 网络请求的响应结果
     * @throws IOException 如果下载过程中出现 I/O 错误
     */
    Response load(Uri uri, int networkPolicy) throws IOException;

    /**
     * 关闭下载器,释放相关资源。
     */
    void shutdown();
}

3.2 接口方法详解

3.2.1 load 方法
  • 参数
    • uri:表示要下载图片的统一资源标识符,通常是一个网络 URL。
    • networkPolicy:网络策略参数,用于控制请求的缓存行为。例如,可以设置为不使用缓存、仅从缓存获取等。
  • 返回值 :返回一个 Response 对象,包含了下载的图片数据和相关元信息。
  • 异常处理 :如果在下载过程中出现 I/O 错误,会抛出 IOException 异常。
3.2.2 shutdown 方法

该方法用于关闭下载器,释放相关的资源,如网络连接、线程池等。在应用退出或不再需要进行网络请求时,调用该方法可以避免资源泄漏。

3.3 接口的作用

Downloader 接口为 Picasso 的网络请求提供了统一的抽象,使得 Picasso 可以使用不同的网络请求库来实现图片的下载。开发者可以根据自己的需求,实现该接口并使用自定义的下载器,或者选择使用 Picasso 提供的默认实现,如 OkHttp3Downloader

四、OkHttp3Downloader 类分析

4.1 类的初始化

java 复制代码
// OkHttp3Downloader.java
package com.squareup.picasso;

import android.content.Context;
import okhttp3.Cache;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import java.io.File;
import java.io.IOException;

/**
 * 使用 OkHttp3 库实现的 Downloader 接口。
 */
public class OkHttp3Downloader implements Downloader {
    private final OkHttpClient client;
    private final Cache cache;

    /**
     * 构造函数,使用默认的 OkHttpClient 实例。
     * @param context 应用的上下文对象
     */
    public OkHttp3Downloader(final Context context) {
        // 获取应用的缓存目录
        this(Utils.createDefaultCacheDir(context));
    }

    /**
     * 构造函数,指定缓存目录。
     * @param cacheDir 缓存目录
     */
    public OkHttp3Downloader(final File cacheDir) {
        // 创建默认的缓存大小为 50MB
        this(cacheDir, Utils.calculateDiskCacheSize(cacheDir));
    }

    /**
     * 构造函数,指定缓存目录和缓存大小。
     * @param cacheDir 缓存目录
     * @param maxSize 缓存的最大大小
     */
    public OkHttp3Downloader(final File cacheDir, final long maxSize) {
        // 创建 OkHttp 的缓存对象
        this.cache = new Cache(cacheDir, maxSize);
        // 创建 OkHttp 客户端实例,并设置缓存
        this.client = new OkHttpClient.Builder().cache(cache).build();
    }

    /**
     * 构造函数,使用自定义的 OkHttpClient 实例。
     * @param client 自定义的 OkHttpClient 实例
     */
    public OkHttp3Downloader(OkHttpClient client) {
        this.client = client;
        this.cache = client.cache();
    }
}
4.1.1 构造函数详解
  • OkHttp3Downloader(Context context) :该构造函数接收一个 Context 对象,通过 Utils.createDefaultCacheDir(context) 方法获取应用的默认缓存目录,然后调用另一个构造函数进行初始化。
  • OkHttp3Downloader(File cacheDir) :该构造函数接收一个 File 对象,表示缓存目录。它会调用 Utils.calculateDiskCacheSize(cacheDir) 方法计算默认的缓存大小(通常为 50MB),然后创建 Cache 对象和 OkHttpClient 实例。
  • OkHttp3Downloader(File cacheDir, long maxSize) :该构造函数接收缓存目录和最大缓存大小作为参数,创建 Cache 对象并将其设置到 OkHttpClient 中。
  • OkHttp3Downloader(OkHttpClient client) :该构造函数接收一个自定义的 OkHttpClient 实例,直接使用该实例进行网络请求,并获取其缓存对象。

4.2 load 方法实现

java 复制代码
// OkHttp3Downloader.java
@Override
public Response load(Uri uri, int networkPolicy) throws IOException {
    // 创建 OkHttp 的请求构建器
    Request.Builder requestBuilder = new Request.Builder().url(uri.toString());

    // 根据网络策略设置请求头
    if (NetworkPolicy.isOfflineOnly(networkPolicy)) {
        requestBuilder.cacheControl(ONLY_IF_CACHED);
    } else {
        CacheControl cacheControl = null;
        if (networkPolicy != 0) {
            if (NetworkPolicy.shouldReadFromDiskCache(networkPolicy)) {
                if (!NetworkPolicy.shouldWriteToDiskCache(networkPolicy)) {
                    cacheControl = NO_CACHE;
                }
            } else {
                cacheControl = NO_STORE;
            }
        }
        if (cacheControl != null) {
            requestBuilder.cacheControl(cacheControl);
        }
    }

    // 构建 OkHttp 的请求对象
    Request request = requestBuilder.build();
    // 执行 OkHttp 的请求
    okhttp3.Response response = client.newCall(request).execute();
    // 获取响应的状态码
    int responseCode = response.code();
    // 检查响应状态码是否为 200(成功)
    if (responseCode >= 300) {
        // 关闭响应体
        response.body().close();
        throw new ResponseException(responseCode + " " + response.message(), networkPolicy, responseCode);
    }

    // 检查响应是否来自缓存
    boolean fromCache = response.cacheResponse() != null;
    // 返回 Picasso 的响应对象
    return new Response(response.body().byteStream(), fromCache);
}
4.2.1 构建请求
  • 首先,使用 Request.Builder 创建一个 OkHttp 的请求构建器,并设置请求的 URL 为图片的 URI。
  • 然后,根据 networkPolicy 参数设置请求头的缓存控制信息。如果是离线模式(NetworkPolicy.isOfflineOnly(networkPolicy)true),则设置缓存控制为 ONLY_IF_CACHED,表示只从缓存中获取数据。否则,根据是否需要从磁盘缓存读取和写入,设置不同的缓存控制策略。
4.2.2 执行请求
  • 调用 requestBuilder.build() 方法构建 OkHttp 的请求对象。
  • 使用 client.newCall(request).execute() 方法执行请求,并获取响应对象。
4.2.3 处理响应
  • 检查响应的状态码,如果状态码大于等于 300,表示请求失败,关闭响应体并抛出 ResponseException 异常。
  • 检查响应是否来自缓存,通过 response.cacheResponse() != null 判断。
  • 最后,返回一个 Response 对象,包含响应的输入流和是否来自缓存的信息。

4.3 shutdown 方法实现

java 复制代码
// OkHttp3Downloader.java
@Override
public void shutdown() {
    // 获取缓存对象
    Cache cache = client.cache();
    if (cache != null) {
        try {
            // 关闭缓存
            cache.close();
        } catch (IOException ignored) {
        }
    }
}

shutdown 方法用于关闭下载器,释放相关资源。它会获取 OkHttpClient 的缓存对象,并尝试关闭该缓存。如果关闭过程中出现 IOException 异常,会忽略该异常。

五、NetworkRequestHandler 类分析

5.1 canHandleRequest 方法

java 复制代码
// NetworkRequestHandler.java
package com.squareup.picasso;

import android.net.Uri;
import java.io.IOException;

/**
 * 负责处理网络请求的逻辑,调用 Downloader 进行实际的下载操作。
 */
public class NetworkRequestHandler extends RequestHandler {
    private final Downloader downloader;
    private final Stats stats;

    /**
     * 构造函数,初始化下载器和统计信息对象。
     * @param downloader 下载器实例
     * @param stats 统计信息对象
     */
    public NetworkRequestHandler(Downloader downloader, Stats stats) {
        this.downloader = downloader;
        this.stats = stats;
    }

    @Override
    public boolean canHandleRequest(Request data) {
        // 获取请求的 URI
        String scheme = data.uri.getScheme();
        // 检查 URI 的协议是否为 http 或 https
        return (SCHEME_HTTP.equals(scheme) || SCHEME_HTTPS.equals(scheme));
    }
}
5.1.1 方法功能

canHandleRequest 方法用于判断当前的 RequestHandler 是否能够处理指定的请求。在 NetworkRequestHandler 类中,它会检查请求的 URI 的协议是否为 httphttps,如果是,则返回 true,表示可以处理该请求;否则返回 false

5.1.2 代码详解
  • data.uri.getScheme():获取请求的 URI 的协议部分,如 httphttps
  • SCHEME_HTTP.equals(scheme) || SCHEME_HTTPS.equals(scheme):判断协议是否为 httphttps

5.2 load 方法

java 复制代码
// NetworkRequestHandler.java
@Override
public Result load(Request request, int networkPolicy) throws IOException {
    // 记录开始时间
    long started = System.nanoTime();
    // 调用下载器的 load 方法进行下载
    Response response = downloader.load(request.uri, networkPolicy);
    if (response == null) {
        return null;
    }
    // 检查响应是否来自缓存
    boolean fromCache = response.cached;
    // 获取响应的输入流
    InputStream is = response.getInputStream();
    if (is == null) {
        return null;
    }
    // 记录结束时间
    long size = is instanceof ContentLengthInputStream
           ? ((ContentLengthInputStream) is).getContentLength() : -1;
    // 统计下载信息
    stats.dispatchDownloadFinished(System.nanoTime() - started, size, fromCache);
    // 返回加载结果
    return new Result(is, fromCache? DISK : NETWORK);
}
5.2.1 下载流程
  • 记录下载开始时间,使用 System.nanoTime() 获取当前时间。
  • 调用 downloader.load(request.uri, networkPolicy) 方法进行实际的下载操作,获取 Response 对象。
  • 检查 Response 对象是否为空,如果为空则返回 null
5.2.2 处理响应
  • 检查响应是否来自缓存,通过 response.cached 判断。
  • 获取响应的输入流,如果输入流为空则返回 null
5.2.3 统计信息
  • 记录下载结束时间,计算下载耗时。
  • 如果输入流是 ContentLengthInputStream 类型,则获取其内容长度。
  • 调用 stats.dispatchDownloadFinished 方法统计下载信息,包括下载耗时、下载大小和是否来自缓存。
5.2.4 返回结果

返回一个 Result 对象,包含输入流和数据来源(磁盘缓存或网络)。

六、Response 类分析

6.1 类的定义

java 复制代码
// Response.java
package com.squareup.picasso;

import java.io.InputStream;

/**
 * 表示网络请求的响应结果,包含了图片数据和相关的元信息。
 */
public class Response {
    final InputStream stream;
    final boolean cached;

    /**
     * 构造函数,初始化响应结果。
     * @param stream 响应的输入流
     * @param cached 响应是否来自缓存
     */
    public Response(InputStream stream, boolean cached) {
        this.stream = stream;
        this.cached = cached;
    }

    /**
     * 获取响应的输入流。
     * @return 响应的输入流
     */
    public InputStream getInputStream() {
        return stream;
    }

    /**
     * 判断响应是否来自缓存。
     * @return 如果响应来自缓存则返回 true,否则返回 false
     */
    public boolean isCached() {
        return cached;
    }
}

6.2 类的属性和方法详解

6.2.1 属性
  • stream:响应的输入流,用于读取图片的二进制数据。
  • cached:表示响应是否来自缓存的布尔值。
6.2.2 构造函数

Response(InputStream stream, boolean cached):用于初始化 Response 对象,接收输入流和是否来自缓存的信息。

6.2.3 方法
  • getInputStream():返回响应的输入流。
  • isCached():返回响应是否来自缓存的布尔值。

6.3 类的作用

Response 类封装了网络请求的响应结果,为后续的图片解码和处理提供了统一的接口。通过 getInputStream 方法可以获取响应的输入流,通过 isCached 方法可以判断响应是否来自缓存,方便开发者进行不同的处理。

七、网络请求模块的工作流程

7.1 请求的发起

当开发者调用 Picasso 的 load 方法加载一张网络图片时,Picasso 会创建一个 Request 对象,该对象包含了图片的 URI、尺寸、变换等信息。然后,Picasso 会遍历所有的 RequestHandler,调用它们的 canHandleRequest 方法,判断哪个 RequestHandler 可以处理该请求。如果 NetworkRequestHandlercanHandleRequest 方法返回 true,则表示该请求是一个网络请求,将由 NetworkRequestHandler 进行处理。

7.2 请求的处理

NetworkRequestHandler 接收到请求后,会调用 load 方法进行实际的下载操作。在 load 方法中,它会调用 Downloaderload 方法,将请求的 URI 和网络策略传递给 Downloader

7.3 网络请求的执行

Downloader(通常是 OkHttp3Downloader)会根据请求的 URI 和网络策略,使用 OkHttp3 库发起网络请求。它会构建 OkHttp 的请求对象,设置请求头的缓存控制信息,然后执行请求并获取响应。

7.4 响应的处理

Downloader 获取到响应后,会检查响应的状态码,如果状态码大于等于 300,表示请求失败,会抛出异常。如果请求成功,会检查响应是否来自缓存,并返回一个 Response 对象。NetworkRequestHandler 接收到 Response 对象后,会进行统计信息的记录,并返回一个 Result 对象。

7.5 结果的返回

NetworkRequestHandlerResult 对象返回给 Picasso,Picasso 会根据 Result 对象中的输入流进行图片解码和显示。如果响应来自缓存,则可以直接从缓存中获取图片数据,避免了网络请求,提高了加载速度。

八、网络请求模块的优化策略

8.1 缓存策略优化

8.1.1 合理设置缓存大小

在初始化 OkHttp3Downloader 时,可以根据应用的需求合理设置磁盘缓存的大小。如果缓存大小设置过小,可能会导致频繁的网络请求;如果设置过大,会占用过多的磁盘空间。可以根据应用的使用场景和用户设备的存储情况进行调整。

java 复制代码
// 设置磁盘缓存大小为 100MB
File cacheDir = new File(context.getCacheDir(), "picasso_cache");
long cacheSize = 100 * 1024 * 1024; // 100MB
OkHttp3Downloader downloader = new OkHttp3Downloader(cacheDir, cacheSize);
8.1.2 灵活使用网络策略

Picasso 提供了多种网络策略,可以根据不同的情况灵活使用。例如,在网络不稳定的情况下,可以优先从缓存中获取图片;在需要更新图片时,可以强制从网络获取。

java 复制代码
// 仅从缓存获取图片
Picasso.get().load("http://example.com/image.jpg")
       .networkPolicy(NetworkPolicy.OFFLINE)
       .into(imageView);

// 强制从网络获取图片
Picasso.get().load("http://example.com/image.jpg")
       .networkPolicy(NetworkPolicy.NO_CACHE)
       .into(imageView);

8.2 超时设置优化

在使用 OkHttp3Downloader 时,可以通过自定义 OkHttpClient 实例来设置超时时间,避免长时间的网络请求导致应用卡顿。可以设置连接超时、读取超时和写入超时时间。

java 复制代码
// 创建自定义的 OkHttpClient 实例,并设置超时时间
OkHttpClient client = new OkHttpClient.Builder()
       .connectTimeout(10, TimeUnit.SECONDS)
       .readTimeout(10, TimeUnit.SECONDS)
       .writeTimeout(10, TimeUnit.SECONDS)
       .build();

// 使用自定义的 OkHttpClient 实例初始化 OkHttp3Downloader
OkHttp3Downloader downloader = new OkHttp3Downloader(client);

// 创建 Picasso 实例,并设置下载器
Picasso picasso = new Picasso.Builder(context)
       .downloader(downloader)
       .build();

// 使用自定义的 Picasso 实例加载图片
picasso.load("http://example.com/image.jpg").into(imageView);

8.3 错误处理优化

在网络请求过程中,可能会出现各种错误,如网络连接失败、服务器返回错误等。可以通过 Picassoerror 方法设置错误处理逻辑,当网络请求失败时显示错误图片或提示信息。

java 复制代码
Picasso.get().load("http://example.com/image.jpg")
       .error(R.drawable.error_image)
       .into(imageView);

同时,可以通过捕获 IOException 异常,进行更详细的错误处理和日志记录。

java 复制代码
try {
    Picasso.get().load("http://example.com/image.jpg").into(imageView);
} catch (IOException e) {
    // 记录错误日志
    Log.e("Picasso", "Network request error: " + e.getMessage());
    // 显示错误提示信息
    Toast.makeText(context, "Failed to load image", Toast.LENGTH_SHORT).show();
}

8.4 并发请求优化

在处理多个图片请求时,可以通过调整 OkHttpClient 的线程池配置,优化并发请求的性能。例如,可以增加线程池的最大线程数,提高并发处理能力。

java 复制代码
// 创建自定义的 OkHttpClient 实例,并设置线程池配置
OkHttpClient client = new OkHttpClient.Builder()
       .dispatcher(new Dispatcher())
       .build();
Dispatcher dispatcher = client.dispatcher();
dispatcher.setMaxRequests(10); // 设置最大并发请求数
dispatcher.setMaxRequestsPerHost(5); // 设置每个主机的最大并发请求数

// 使用自定义的 OkHttpClient 实例初始化 OkHttp3Downloader
OkHttp3Downloader downloader = new OkHttp3Downloader(client);

// 创建 Picasso 实例,并设置下载器
Picasso picasso = new Picasso.Builder(context)
       .downloader(downloader)
       .build();

九、与其他网络请求库的对比

9.1 与 Volley 的对比

9.1.1 功能特点
  • Picasso:专注于图片加载,提供了简洁的 API 来加载、转换和显示图片。它集成了网络请求和缓存功能,能够自动处理图片的解码和缩放,适用于简单的图片加载场景。
  • Volley:是一个通用的网络请求库,支持多种类型的请求,如 JSON 请求、图片请求等。它提供了请求队列管理、缓存、重试机制等功能,适用于复杂的网络请求场景。
9.1.2 性能表现
  • Picasso:在图片加载方面性能较好,尤其是在处理大量图片时,能够有效利用缓存,减少网络请求。它的图片解码和缩放算法经过优化,能够快速加载和显示图片。
  • Volley:在处理小型 JSON 请求时性能较高,因为它的请求队列管理和缓存机制能够提高请求的效率。但在处理大型图片请求时,可能会因为内存管理和图片解码的问题导致性能下降。
9.1.3 使用场景
  • Picasso:适用于以图片展示为主的应用,如社交媒体应用、图片浏览器等。
  • Volley:适用于需要处理多种类型请求的应用,如新闻资讯应用、电商应用等。

9.2 与 Retrofit 的对比

9.2.1 功能特点
  • Picasso:主要用于图片加载,提供了简单易用的 API 来加载和显示图片。它的网络请求模块基于 OkHttp,能够自动处理图片的缓存和下载。
  • Retrofit:是一个类型安全的 HTTP 客户端,用于与 RESTful API 进行交互。它通过注解的方式定义接口,将网络请求转换为 Java 方法调用,方便开发者进行网络请求的管理。
9.2.2 性能表现
  • Picasso:在图片加载方面性能稳定,能够快速加载和显示图片。它的缓存机制能够有效减少网络请求,提高图片加载的效率。
  • Retrofit:在处理 RESTful API 请求时性能较高,因为它使用了动态代理和注解处理器,能够自动生成网络请求代码。但在处理图片请求时,需要结合其他图片加载库使用。
9.2.3 使用场景
  • Picasso:适用于需要大量图片加载的应用,如图片社交应用、图库应用等。
  • Retrofit:适用于与 RESTful API 进行交互的应用,如电商应用、金融应用等。

十、总结与展望

10.1 总结

通过对 Android Picasso 网络请求模块的深入分析,我们了解到该模块是 Picasso 实现图片从网络加载到本地展示的核心部分。它通过 Downloader 接口提供了统一的网络请求抽象,使用 OkHttp3Downloader 实现了具体的网络请求逻辑,通过 NetworkRequestHandler 处理网络请求的流程,最终通过 Response 类封装了网络请求的响应结果。该模块具备缓存处理、错误处理等功能,能够提高图片加载的效率和稳定性。同时,我们还介绍了一些优化策略,如缓存策略优化、超时设置优化、错误处理优化等,这些策略可以帮助开发者进一步提升应用的图片加载性能。

10.2 展望

未来,Android Picasso 的网络请求模块可以在以下几个方面进行改进和扩展:

  • 支持更多的网络协议 :除了 httphttps,可以考虑支持其他网络协议,如 ftpsftp 等,以满足不同场景下的图片加载需求。
  • 优化缓存算法:可以进一步优化缓存算法,提高缓存的命中率和空间利用率。例如,采用更智能的缓存淘汰策略,根据图片的使用频率、大小等因素进行缓存管理。
  • 增强错误处理机制:提供更详细的错误信息和更灵活的错误处理方式,方便开发者进行调试和优化。例如,在网络请求失败时,能够返回具体的错误原因和建议解决方案。
  • 支持并发请求优化:进一步优化并发请求的性能,提高多图片加载的效率。可以通过调整线程池配置、优化请求队列管理等方式,减少请求的等待时间。
  • 集成更多的网络请求库:除了 OkHttp3,还可以支持其他流行的网络请求库,如 Volley、Retrofit 等,让开发者可以根据自己的需求选择合适的网络请求库。

总之,Android Picasso 的网络请求模块是一个功能强大、灵活可扩展的模块,通过不断的改进和优化,将为 Android 开发者提供更好的图片加载体验。

相关推荐
笑川 孙6 分钟前
为什么Makefile中的clean需要.PHONY
开发语言·c++·面试·makefile·make·技术
Goboy8 分钟前
SQL面试实战,30分钟征服美女面试官
后端·面试·架构
天天扭码9 分钟前
【硬核教程】从入门到入土!彻底吃透 JavaScript 中 this 关键字这一篇就够了
前端·javascript·面试
꧁༺朝花夕逝༻꧂11 分钟前
随机面试--<二>
linux·运维·数据库·nginx·面试
移动开发者1号1 小时前
System.currentTimeMillis()与elapsedRealtime()区别
android
顾林海1 小时前
Android Retrofit原理解析
android·面试·源码
V少年1 小时前
深入浅出安卓Jetpack组件
android
知其然亦知其所以然1 小时前
面试官问我 Java 原子操作,我一句话差点让他闭麦!
java·后端·面试
wayhome在哪1 小时前
大厂面试题分享(纯干货)
面试
我是哪吒1 小时前
分布式微服务系统架构第118集:Future池管理容器-CompletableFuture
后端·面试·github