okhttp3源码解析(3)-拦截器 II

2023-11-09

okhttp3源码解析(3)-拦截器 II

前言

上篇博文从RealInterceptorChain开始,讲解了RetryAndFollowUpInterceptor和BridgeInterceptor两个拦截器,后面还有三个系统拦截器,其实都类似,只是实现的功能不一样罢了,这篇文章将来介绍剩下的这几个系统拦截器。

okhttp3源码解析(2)-拦截器 I

CacheInterceptor

CacheInterceptor是一个缓存拦截器,主要功能就是根据缓存策略取缓存、发起请求、保存缓存,下面开讲。

InternalCache

CacheInterceptor通过持有的InternalCache对象来实现缓存:

  // OkHttpClient内
  InternalCache internalCache() {
    return cache != null ? cache.internalCache : internalCache;
  }

CacheInterceptor的InternalCache对象从构造函数传入,最终来源为OkHttpClient内的internalCache方法,这里有两个cache,我们看下他们来源:

    /** Sets the response cache to be used to read and write cached responses. */
    void setInternalCache(@Nullable InternalCache internalCache) {
      this.internalCache = internalCache;
      this.cache = null;
    }

    /** Sets the response cache to be used to read and write cached responses. */
    public Builder cache(@Nullable Cache cache) {
      this.cache = cache;
      this.internalCache = null;
      return this;
    }

这里绕的有点晕了,大致意思就是internalCache和cache两个不能同时存在吧,如果设置了就用其中有的那个。

看起来Cache对象包含了一个InternalCache成员,我们继续看下Cache类:

  final InternalCache internalCache = new InternalCache() {
    @Override public Response get(Request request) throws IOException {
      return Cache.this.get(request);
    }

    @Override public CacheRequest put(Response response) throws IOException {
      return Cache.this.put(response);
    }

    @Override public void remove(Request request) throws IOException {
      Cache.this.remove(request);
    }

    @Override public void update(Response cached, Response network) {
      Cache.this.update(cached, network);
    }

    @Override public void trackConditionalCacheHit() {
      Cache.this.trackConditionalCacheHit();
    }

    @Override public void trackResponse(CacheStrategy cacheStrategy) {
      Cache.this.trackResponse(cacheStrategy);
    }
  };

哦,原来这里Cache就是InternalCache的一个装饰模式,最终用InternalCache的接口方法实现了缓存功能,也就是说InternalCache是一个接口,而Cache是一个实现类。

不过这时候我还是有点懵逼,那到底谁是默认的缓存呢?

  // OkHttpClient内
  InternalCache internalCache() {
    return cache != null ? cache.internalCache : internalCache;
  }

再来看下OkHttpClient的internalCache方法,CacheInterceptor的InternalCache是这里提供的,cache是优先于internalCache的,那么cache和internalCache的默认值呢?

    public Builder() {
      dispatcher = new Dispatcher();
      protocols = DEFAULT_PROTOCOLS;
      connectionSpecs = DEFAULT_CONNECTION_SPECS;
      eventListenerFactory = EventListener.factory(EventListener.NONE);
      proxySelector = ProxySelector.getDefault();
      if (proxySelector == null) {
        proxySelector = new NullProxySelector();
      }
      cookieJar = CookieJar.NO_COOKIES;
      socketFactory = SocketFactory.getDefault();
      hostnameVerifier = OkHostnameVerifier.INSTANCE;
      certificatePinner = CertificatePinner.DEFAULT;
      proxyAuthenticator = Authenticator.NONE;
      authenticator = Authenticator.NONE;
      connectionPool = new ConnectionPool();
      dns = Dns.SYSTEM;
      followSslRedirects = true;
      followRedirects = true;
      retryOnConnectionFailure = true;
      callTimeout = 0;
      connectTimeout = 10_000;
      readTimeout = 10_000;
      writeTimeout = 10_000;
      pingInterval = 0;
    }

Sorry,OkHttpClient.Builder的构造函数并没有提供默认值,所以两者都没有默认值。

小结下,这里CacheInterceptor持有的InternalCache对象,需要自己在OkHttpClient.Builder中设置,Cache是一个实现类,我们用它就行了,如果有能力也可以实现InternalCache接口自己实现一套,通过setInternalCache方法设置。

这里花了很长篇幅研究了下CacheInterceptor持有的InternalCache对象,对于一般情况,缓存都是通过Cache类实现的,里面内容很多,也很有价值,但是现在是研究okhttp,所以这里不讲,希望读者自行查看。下面来讲拦截器的功能实现点:intercept方法。

第一步,获得请求的缓存,得到缓存策略(需要的网络请求、本地缓存的回复),并进行验证

    // 缓存中取出的Response候选者
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

    long now = System.currentTimeMillis();

    // 得到一个缓存策略
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    Request networkRequest = strategy.networkRequest;
    Response cacheResponse = strategy.cacheResponse;

    if (cache != null) {
      // 这里不是执行,而是对本次击中缓存的情况做记录
      cache.trackResponse(strategy);
    }

    // 没有本地缓存,可以关闭缓存了
    if (cacheCandidate != null && cacheResponse == null) {
      closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
    }

    // If we're forbidden from using the network and the cache is insufficient, fail.
    // 不需要网络请求,同时没有缓存,无法访问504
    if (networkRequest == null && cacheResponse == null) {
      return new Response.Builder()
          .request(chain.request())
          .protocol(Protocol.HTTP_1_1)
          .code(504)
          .message("Unsatisfiable Request (only-if-cached)")
          .body(Util.EMPTY_RESPONSE)
          .sentRequestAtMillis(-1L)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build();
    }

    // If we don't need the network, we're done.
    // 不需要网络请求,从缓存返回
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }

这里看得也很让我懵逼,逻辑的核心是理解CacheStrategy这个类,搞清楚它是干嘛的,先看下它自己的说明:

Given a request and cached response, this figures out whether to use the network, the cache, or both.
Selecting a cache strategy may add conditions to the request (like the “If-Modified-Since” header for conditional GETs) or warnings to the cached response (if the cached data is potentially stale).

意思就是(垃圾机翻):

  1. 给定一个请求和缓存的响应,这会确定是使用网络、缓存还是同时使用两者。
  2. 选择缓存策略可能会向请求添加条件(如条件 GET 的“If-Modified-Since”标头)或对缓存响应的警告(如果缓存数据可能过时)。

搞懂CacheStrategy这个类,上面的情况就简单了。通过CacheStrategy.Factory的get方法获得strategy后,会得到两个产物:networkRequest和cacheResponse。

networkRequest是可能用到的网络请求,如果它为空,那就不发送网络请求。而cacheResponse是对cacheCandidate(原生缓存的Response)校验后的结果,如果为空,就是没缓存或者cacheCandidate已经过期了。

第二步,根据需要的网络请求,通过责任链下一步发起请求,获得网络回复

Response networkResponse = null;
    try {
      networkResponse = chain.proceed(networkRequest);
    } finally {
      // If we're crashing on I/O or otherwise, don't leak the cache body.
      // 出现异常了,关闭缓存Response的body(是一个流)
      if (networkResponse == null && cacheCandidate != null) {
        closeQuietly(cacheCandidate.body());
      }
    }

    // If we have a cache response too, then we're doing a conditional get.
    // 获取网络回复后,还有本地缓存的response,根据条件处理(未修改: 更新header、缓存)
    if (cacheResponse != null) {
      if (networkResponse.code() == HTTP_NOT_MODIFIED) {
        Response response = cacheResponse.newBuilder()
            .headers(combine(cacheResponse.headers(), networkResponse.headers()))
            .sentRequestAtMillis(networkResponse.sentRequestAtMillis())
            .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
            .cacheResponse(stripBody(cacheResponse))
            .networkResponse(stripBody(networkResponse))
            .build();
        networkResponse.body().close();

        // Update the cache after combining headers but before stripping the
        // Content-Encoding header (as performed by initContentStream()).
        cache.trackConditionalCacheHit();
        cache.update(cacheResponse, response);
        return response;
      } else {
        closeQuietly(cacheResponse.body());
      }
    }

这里是处理需要网络请求的情况(networkRequest!=null),发起了网络请求,并对同时有cacheResponse的情况做了处理。

第三步,通过网络回复及缓存回复创建实际的response,并缓存

    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();

    // 更新缓存
    if (cache != null) {
      if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
        // Offer this request to the cache.
        CacheRequest cacheRequest = cache.put(response);
        return cacheWritingResponse(cacheRequest, response);
      }

      if (HttpMethod.invalidatesCache(networkRequest.method())) {
        try {
          cache.remove(networkRequest);
        } catch (IOException ignored) {
          // The cache cannot be written.
        }
      }
    }

    return response;

这里将cacheResponse和networkResponse去掉body后保存在新的response中(根据networkResponse创建,body为networkResponse的),并对缓存进行了更新。

小结

上面对CacheInterceptor进行了分析,只分析主要流程,很多细节都过了,比如通过CacheStrategy和Cache的原理,有时间再瞧瞧了。

ConnectInterceptor

ConnectInterceptor估计是这五个系统拦截器里面最简单的了,这里将intercept代码发出来就知道它的功能了。

  @Override public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Request request = realChain.request();
    StreamAllocation streamAllocation = realChain.streamAllocation();

    // We need the network to satisfy this request. Possibly for validating a conditional GET.
    boolean doExtensiveHealthChecks = !request.method().equals("GET");
    HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
    RealConnection connection = streamAllocation.connection();

    return realChain.proceed(request, streamAllocation, httpCodec, connection);
  }

这里其实就是创建了HttpCodec和RealConnection,并传入下一个拦截器,虽然很简单但还是挺重要的。

CallServerInterceptor

上面的ConnectInterceptor创建了HttpCodec和RealConnection并传递到了CallServerInterceptor,到这,剩下的三个核心类都齐了:

  1. StreamAllocation
  2. HttpCodec
  3. RealConnection
    不过这三个类我们要下篇博文详解,先来看CallServerInterceptor的intercept方法,下面也可能部分涉及这三个类。

第一步,获取httpCodec、streamAllocation、connection等,下面进行最后的请求

    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    HttpCodec httpCodec = realChain.httpStream();
    StreamAllocation streamAllocation = realChain.streamAllocation();
    RealConnection connection = (RealConnection) realChain.connection();
    Request request = realChain.request();

    long sentRequestMillis = System.currentTimeMillis();

第二步,使用httpCodec对request进行处理,对header进行写入(UTF8)

    // 通过httpCodec写入请求头
    realChain.eventListener().requestHeadersStart(realChain.call());
    httpCodec.writeRequestHeaders(request);
    realChain.eventListener().requestHeadersEnd(realChain.call(), request);

    // http 100-continue用于客户端在发送POST数据给服务器前,征询服务器情况,看服务器是否处理POST的数据,
    // 如果不处理,客户端则不上传POST数据,如果处理,则POST上传数据。在现实应用中,通过在POST大数据时,
    // 才会使用100-continue协议

    // 下面其实就是处理http 100-continue的情况:需要跳过一个回复,并多发一次request
    Response.Builder responseBuilder = null;
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
      // If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
      // Continue" response before transmitting the request body. If we don't get that, return
      // what we did get (such as a 4xx response) without ever transmitting the request body.
      if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
      
        // !!执行flushRequest,即发送了一次request
        httpCodec.flushRequest();
        realChain.eventListener().responseHeadersStart(realChain.call());
        
        // 传入true的时候会返回null
        responseBuilder = httpCodec.readResponseHeaders(true);
      }

      // 一般情况 或者 100-continue时(对于readResponseHeaders返回null),向服务器发送 requestBody
      if (responseBuilder == null) {
        // Write the request body if the "Expect: 100-continue" expectation was met.
        realChain.eventListener().requestBodyStart(realChain.call());
        long contentLength = request.body().contentLength();
        CountingSink requestBodyOut =
            new CountingSink(httpCodec.createRequestBody(request, contentLength));
        BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);

        request.body().writeTo(bufferedRequestBody);
        bufferedRequestBody.close();
        realChain.eventListener()
            .requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);
      } else if (!connection.isMultiplexed()) {
        // If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection
        // from being reused. Otherwise we're still obligated to transmit the request body to
        // leave the connection in a consistent state.
        streamAllocation.noNewStreams();
      }
    }
    
    // 刷新输出流,发送request
    httpCodec.finishRequest();

读懂这里的代码,首先要知道httpCodec的作用,它是一个流的处理工具,里面管理着socket的流,当我们调用flushRequest时,就会把request发送出去。

所以上面的代码就是通过httpCodec的writeRequestHeaders方法写入了header到发送流,又获得了requestBodyOut(输出body流)写入request的body,最后调用finishRequest发送出去。

这里 100-continue 的情况会多调用一次httpCodec的flushRequest方法,线性发送一次只包含header的request。

第三步,对response进行处理,从httpCodec输入流读取

    if (responseBuilder == null) {
      realChain.eventListener().responseHeadersStart(realChain.call());
      // 读取回复得状态信息创建responseBuilder
      responseBuilder = httpCodec.readResponseHeaders(false);
    }

    Response response = responseBuilder
        .request(request)
        .handshake(streamAllocation.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();

    // 对http 100-continue结果处理,再读取一次(前面多发了一次)
    int code = response.code();
    if (code == 100) {
      // server sent a 100-continue even though we did not request one.
      // try again to read the actual response
      responseBuilder = httpCodec.readResponseHeaders(false);

      // 紧接着再读取一次回复的header
      response = responseBuilder
              .request(request)
              .handshake(streamAllocation.connection().handshake())
              .sentRequestAtMillis(sentRequestMillis)
              .receivedResponseAtMillis(System.currentTimeMillis())
              .build();

      code = response.code();
    }

    realChain.eventListener()
            .responseHeadersEnd(realChain.call(), response);

    // 通过httpCodec处理response的body: openResponseBody(response)
    if (forWebSocket && code == 101) {
      // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
      response = response.newBuilder()
          .body(Util.EMPTY_RESPONSE)
          .build();
    } else {
      response = response.newBuilder()
          .body(httpCodec.openResponseBody(response))
          .build();
    }

    if ("close".equalsIgnoreCase(response.request().header("Connection"))
        || "close".equalsIgnoreCase(response.header("Connection"))) {
      streamAllocation.noNewStreams();
    }

    if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
      throw new ProtocolException(
          "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
    }

    // 实际得到的Response,读取了headers并处理且存储了,而ResponseBody封装了source,
    // 需要在OkHttpCall(Retrofit类)中parseResponse,将rawBody处理
    return response;

这里实际也和上面的步骤对应,先读取回复的状态信息(header)并创建了responseBuilder,构建最后的response,对http 100-continue特殊处理,最后通过httpCodec的openResponseBody方法向response中传入了socket输入流的一段(httpCodec中source的封装)。

到这里就把最终的response拿到了,并且这个response还读取好了header以及body(虽然还是流,需要进一步处理),OkHttpClient的主要就结束了。

小结

把源码看到这里,不知道读者是不是和我一样一脸懵逼:就这?这就完了吗?这是如何请求、如何发送出去的?okhttp牛逼的连接池呢?

唉,这里只是拦截器的功能结束了,我们漏了几个东西:

  1. StreamAllocation
  2. HttpCodec
  3. RealConnection
    发送请求、连接、路由、代理等等功能都在里面,我们下篇博文详细分析。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

okhttp3源码解析(3)-拦截器 II 的相关文章

  • 使用 facebook sdk 为应用程序生成哈希密钥

    我正在使用 facebook sdk 登录我的应用程序 该应用程序在 HTC 设备上运行良好 如果没有预装 Facebook 应用程序 该应用程序也可以在三星设备上正常运行 但是 如果移动设备上已经有 facebook 应用程序 然后用户安
  • 蓝牙适配器.getDefaultAdapter();返回空值

    我开始开发一个应用程序通过蓝牙与arduino设备进行通信 我正在初始化 bt 适配器 BluetoothAdapter btAdapter BluetoothAdapter getDefaultAdapter 问题是 btAdapter
  • 当用户快速滚动时,如何延迟列表适配器中视图的加载

    我的列表视图中有从互联网下载的图像 我想在 getView 中添加一些内容 以便在用户快速滑动 滚动时它不会下载图像 我怎样才能开始这样做呢 您可以按照以下步骤延迟 ListView 中视图的加载 首先 你应该让你的ListView对象和Y
  • java中将函数作为参数传递

    我正在熟悉 Android 框架和 Java 并希望创建一个通用的 NetworkHelper 类 该类将处理大部分网络代码 使我能够从中调用网页 我按照developer android com 上的这篇文章创建了我的网络类 http d
  • onSaveInstanceState 之后无法执行此操作(onClick 首选项)

    这是我的PreferenceActivity 我的主要活动的内部类 public static class TestSettings extends PreferenceActivity implements Preference OnPr
  • Eddystone Beacon 中广播的 MAC ID 会改变吗?

    我将描述我的设置 我制作了一个模拟 Eddystone 信标的 Android 应用程序 我能够使用 PlayStore 中的 Beacon Toy 应用程序检测手机上的 Eddystone 信标 但问题是 自上次检查以来 显示的 MAC
  • 为什么Volley的onResponse没有被调用

    我正在通过 Volley 获取 json 数据 我遇到的问题 那是凌空的onResponse从未被调用 因此不会解析和显示数据 JSON 数据示例 title This is a sample text title title cat or
  • Android Studio 1.2.1.1 中 Gradle 项目刷新失败

    我在全新安装的 Android Studio v 1 2 1 1 上创建了示例项目 但遇到了以下错误消息 Gradle project refresh failed in Android Studio 1 2 1 1 见下图 所以我试图在谷
  • OkHttp Authenticator 有时不会调用多个 Retrofit 实例的验证

    我有两个不同的Retrofit两个不同 API 的实例 我也有两个不同的OkHttp3客户 但他们共享相同的Authenticator因为两个 API 的身份验证令牌是相同的 问题是当令牌过期时有时 但几乎总是 其中之一Retrofit O
  • Kotlin + Room:java.lang.IllegalArgumentException:void 无法转换为元素

    我试图在我的 Java Kotlin 项目中添加 Room 但是当我尝试编译该项目时 它失败了 app kaptDebugKotlin出现以下错误 e java lang IllegalStateException failed to an
  • 在 Phonegap 3.x CLI 上构建 android 发布 apk

    如何使用 Phonegap 3 x CLI 在本地构建 Android 应用程序并准备发布 我检查了项目的platforms android目录中生成的bin文件夹 并且只有 debug APK 顺便说一句 我使用这个命令 phonegap
  • 使用 Android Exoplayer 调整 Dash 流音量

    我正在尝试设置一个搜索栏来控制 exoplayer 流式破折号实例的级别 我正在使用的设置是演示项目的修改版本 并且无法确定我应该尝试影响搜索栏输出的哪个元素 即如何正确使用 MSG SET VOLUME 等 任何意见将不胜感激 我正在寻找
  • Android 反向地理编码不适用于华为设备

    我正在尝试通过这段代码反转地理编码纬度 经度 Geocoder geocoder new Geocoder context Locale ENGLISH try List
  • 免费和付费版本 Android 应用程序的最佳方法?

    我开发了一个 Android 应用程序 我希望它可以作为免费版本和付费版本提供 最好的方法是什么 我可以想到三种解决方案 将项目分成两个分支并维护它们 创建一个库项目并有两个附加项目 一个 免费 版本和一个 付费 版本 使用应用内结算 问
  • 将 XML 从网站解析到 Android 设备

    我正在启动一个 Android 应用程序 它将解析来自网络的 XML 我创建了一些 Android 应用程序 但它们从未涉及解析 XML 我想知道是否有人对最佳方法有任何建议 这是一个例子 try URL url new URL your
  • 按歌曲获取封面图片

    是否可以按歌曲而不是按专辑获取封面图片 因为我有一张自编的歌曲专辑 而且它们都有不同的封面图片 但是当我想查询它们时 我总是得到相同的图片 String ARG STRING MediaStore Audio Media ALBUM ID
  • 如何使用特定选项卡启动活动?

    我已经浏览了许多示例 问题和教程 但我从未见过使用特定选项卡启动活动 启动新意图 我知道可以使用 setCurrentTab切换到选项卡 但这只能从父活动选项卡内部完成 从另一个活动启动一个活动中包含的特定选项卡怎么样 是否可以 如果是这样
  • IntelliJ 12 中的 Android Hello World - 找不到 android.app.Activity 类

    请看下文 我感觉 SDK 配置不正确 但我不知道如何解决 我尝试用谷歌搜索答案 但没有人遇到这个确切的问题 难道是我的Java版本不对 这两个 SDK 似乎可能相互冲突 我通过以下方式制作了这个项目 创建新项目 Android gt 应用模
  • Android:适合SystemWindows和换行符干扰bottomSheets

    我发现如果父布局包含android fitsSystemWindows true 当发生与视图相关的操作时 它会干扰我的 BottomSheets 定位 具体来说 我遇到的是 文本视图中的换行符将触发底页偏移系统 通知栏的高度 换行符 fi
  • Oreo:应用程序未运行时不会触发警报

    我有相对简单的设置 应该在一天中的特定时间触发警报并向用户显示通知 这是相关代码 设置闹钟 long inTime expirationTime Calendar getInstance getTimeInMillis 10000 Inte

随机推荐

  • 为什么c++输出char类型变量的地址出现的是乱码?

    char a h cout lt lt a 就会出现乱码 h烫烫烫烫篾 看了这个贴 https bbs csdn net topics 310062432 改成printf p a 或者std cout lt lt void a 就可以输出
  • Frida—HOOK 学习笔记2

    Android部分 基础知识 1 安卓分层 简单提一下安卓分层 这个点知道了更好 不知道也无所谓 毕竟我们不是开发 只是为了避免下述情况 我要学习so文件HOOK 一波百度 HOOK so层 之后 出现了一个 native 点进去一看 其内
  • Redis学习笔记(七):底层数据结构和对象

    第一章 数据结构与对象 一 简单动态字符串 SDS 在Redis中默认字符串的表示使用了简单动态字符串 Simple Dynamic String 而没有使用C语言中的传统字符串 字面量 string literal 字面量只用来表示一些无
  • 【RabbitMQ教程】- 实现延时队列

    目录 RabbitMQ实现延时队列 Maven依赖 代码实现 1 插件方式 RabbitMQ实现延时队列 RabbitMQ实现延时队列有两种方式 1 死信队列 2 下载插件 Maven依赖 Maven依赖
  • 力扣(LeetCode)每日一题 1921. 消灭怪物的最大数量

    只需要三个步骤 1 初始化 2 排序 3 遍历 class Solution public int eliminateMaximum int dist int speed 初始化 每个怪物到达城市所需要的回合数目 int times new
  • QEMU-运行一个字符驱动(3)

    上面是我的微信和QQ群 欢迎新朋友的加入 上代码 chardriver c include
  • Web 组件代码示例

    在本文中 我提供了对 Web 组件是什么以及如何使用它们的基本理解 使用现实生活中的示例 我将展示 Web 组件如何帮助使应用程序更可预测和更易于维护 此外 我分享了有关如何通过将 HTML CSS JS 代码隔离为 等待它 隔离的组件以供
  • 二叉查找树 数组实现 ArrayBinarySearchTree

    数组实现的二叉查找树 适用查找操作频繁 插入 删除操作较少的情况 代码 using System namespace DataStructure 数组实现的二叉查找树 输入一个数组 获得一颗二叉查找树 找parent 和 child ind
  • 华为OD机试 - 文件目录大小(Java & JS & Python)

    题目描述 一个文件目录的数据格式为 目录id 本目录中文件大小 子目录id列表 其中目录id全局唯一 取值范围 1 200 本目录中文件大小范围 1 1000 子目录id列表个数 0 10 例如 1 20 2 3 表示目录1中文件总大小是2
  • c++ 实现信号和槽机制

    主要通过 c 实现类型QT 信号和槽的问题 设计思路 1 利于模板函数和模板类的 通用性 2 BInd的时候 讲槽函数指针保存 触发时调用 代码如下 include
  • 用python函数写斐波那契数列的函数_python—函数进阶-斐波那契数列

    上次说到生成器的调用next 这样很不方便 需要手动调 我们一般是循环着调 while for都可以 a i for i in range 5 for i in a print i 0 1 2 3 4 执行结果 和手动调的区别是没了的话就会
  • [高级数据结构C++] 线段树(区间和的查询与修改)

    算法竞赛 file author jUicE g2R qq 3406291309 彬 bin 必应 一个某双流一大学通信与信息专业大二在读 brief 一直在算法竞赛学习的路上 copyright 2023 9 COPYRIGHT 原创技术
  • vue-quill-editor 富文本编辑器上传图片自base64改为上传到服务器

    就是要一个富文本编辑器 然后有图片上传功能 因为vue quill editor是将图片转为base64编码 所以当图片比较大时 提交后台时参数过长 导致提交失败 vue quill editor引入过程略 我其它文章里面有 废话不多说 上
  • 实战搞定gRPC之移植篇

    一 交叉编译protobuf 1 配置交叉编译器 export PATH PATH opt EC20 crosstool ql ol crosstool sysroots x86 64 oesdk linux usr bin opt EC2
  • C++安全编码-第一章

    1 优先C 特性而不是C特性 std string std string view char std vector std array 原生数组 namespace static 引用 智能指针 普通指针 iostream printf s
  • python+Selenium+无界面启动

    Selenium无界面启动 方法一 selenium version 3 141 0 谷歌浏览器 version 87 0 4280 88 正式版本 64 位 from selenium import webdriver opt webdr
  • 嵌入式C语言(一)

    嵌入式 C语言 一 一 Linux基础操作及C语言简单语法 万丈高楼平地起 学习还得靠自己 1 C语言的本质 C语言的本质就是操作内存 2 关键字和标识符 关键字 关键字是指编译器中已经定义好的特殊单词 注意区分大小写 标识符 标识符是用户
  • 6 个高影响力的大型语言模型应用,探索当今可行的现实且高影响力的LLM

    1 Research Assistant 研究助理 如果您曾经写过文章 论文或博客 您就会知道工作的很大一部分在于筛选大量出版物 确定要阅读并理解的相关研究可能是一项艰巨的任务 技术出版物的市场是巨大的 每个月提交到arXiv 的论文数量几
  • leetcode

    文章目录 买卖股票的最佳时机 买卖股票的最佳时机 II 买卖股票的最佳时机 III 买卖股票的最佳时机 IV 最佳买卖股票时机含冷冻期 买卖股票的最佳时机含手续费 买卖股票的最佳时机 前i天的最大收益 max 前i 1天的最大收益 第i天的
  • okhttp3源码解析(3)-拦截器 II

    okhttp3源码解析 3 拦截器 II 前言 上篇博文从RealInterceptorChain开始 讲解了RetryAndFollowUpInterceptor和BridgeInterceptor两个拦截器 后面还有三个系统拦截器 其实