【Android春招每日一练】(二十六) LeetCode Hot 5题+Android框架

概览

LeetCode Hot:二叉树的中序遍历、不同的二叉搜索树、验证二叉搜索树、对称二叉树、二叉树的层序遍历
Android框架:建立连接、连接池

LeetCode Hot

2.36 二叉树的中序遍历

给定一个二叉树的根节点 root ,返回它的 中序 遍历。

//递归
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        inorder(root,res);
        return res;
    }

    void inorder(TreeNode root,List<Integer> res){
        if(root == null) return;
        inorder(root.left,res);
        res.add(root.val);
        inorder(root.right,res);
    }
}
//迭代
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<Integer>();
        Deque<TreeNode> stk = new LinkedList<TreeNode>();
        while (root != null || !stk.isEmpty()) {
            while (root != null) {
                stk.push(root);
                root = root.left;
            }
            root = stk.pop();
            res.add(root.val);
            root = root.right;	//下次循环入栈
        }
        return res;
    }
}
2.37 不同的二叉搜索树

给你一个整数 n ,求恰由 n 个节点组成且节点值从 1n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。

/*
假设 n 个节点存在二叉排序树的个数是 G (n),令 f(i) 为以 i 为根的二叉搜索树的个数,则
G(n) = f(1) + f(2) + f(3) + f(4) + ... + f(n)
当 i 为根节点时,其左子树节点个数为 i-1 个,右子树节点为 n-i,则
f(i) = G(i-1)*G(n-i)
综合两个公式可以得到 卡特兰数 公式
G(n) = G(0)*G(n-1)+G(1)*(n-2)+...+G(n-1)*G(0)
*/
//DP
class Solution {
    public int numTrees(int n) {
        int[] dp = new int[n+1];
        dp[0] = 1;
        dp[1] = 1;
        
        for(int i = 2; i < n + 1; i++)
            for(int j = 1; j < i + 1; j++) 
                dp[i] += dp[j-1] * dp[i-j];
        
        return dp[n];
    }
}
2.38 验证二叉搜索树

给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。

//中序遍历
class Solution {
    long pre = Long.MIN_VALUE;
    public boolean isValidBST(TreeNode root) {
        if (root == null) return true;
        
        // 访问左子树
        if (!isValidBST(root.left))  return false;

        // 访问当前节点:如果当前节点小于等于中序遍历的前一个节点,说明不满足BST,返回 false;否则继续遍历。
        if (root.val <= pre) return false;

        pre = root.val;
        // 访问右子树
        return isValidBST(root.right);
    }
}
2.39 对称二叉树

给你一个二叉树的根节点 root , 检查它是否轴对称。

输入:root = [1,2,2,3,4,4,3]
输出:true
class Solution {
	public boolean isSymmetric(TreeNode root) {
		if(root==null) {
			return true;
		}
		return dfs(root.left,root.right);
	}
	
	boolean dfs(TreeNode left, TreeNode right) {
		//递归的终止条件是两个节点都为空
		//或者两个节点中有一个为空
		//或者两个节点的值不相等
		if(left==null && right==null) {
			return true;
		}
		if(left==null || right==null) {
			return false;
		}
		if(left.val != right.val) {
			return false;
		}
		//再递归的比较 左节点的左孩子 和 右节点的右孩子
		//以及比较  左节点的右孩子 和 右节点的左孩子
		return dfs(left.left,right.right) && dfs(left.right,right.left);
	}
}
2.40 二叉树的层序遍历

给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。

//BFS
class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        Queue<TreeNode> queue = new LinkedList<>();
        List<List<Integer>> res = new ArrayList<>();
        TreeNode cur;
        int len;
        if(root == null) return res;
        
        queue.add(root);
        while(!queue.isEmpty()){
            List<Integer> level = new ArrayList<>();
            len = queue.size();			//队列大小在变,需要变量保存
            for(int i=0;i<len;i++){		//每一层需要循环操作
                cur = queue.poll();
                level.add(cur.val);
                if(cur.left != null) queue.add(cur.left);
                if(cur.right != null) queue.add(cur.right);
            }
            res.add(level);
        }
        return res;
    }
}

Android框架

建立连接

OkHttp 中默认会添加 RetryAndFollowUpInterceptor、BridgeInterceptor、CacheInterceptor、ConnectInterceptor 以及 CallServerInterceptor 这几个拦截器。本文主要看一下 RetryAndFollupInterceptor 并引出建立连接相关的分析。

RetryAndFollowUpInterceptor:

Interceptor 最主要的代码都在 intercept 中,下面是 RetryAndFollowUpInterceptor#intercept 中的部分代码:

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

    streamAllocation = new StreamAllocation(
        client.connectionPool(), createAddress(request.url()), callStackTrace);//创建一个StreamAllocation对象

    int followUpCount = 0;
    Response priorResponse = null;
    while (true) {
      if (canceled) {
        streamAllocation.release();  //调用release方法
        throw new IOException("Canceled");
      }
    ...
    //把StreamAllocation对象传给下一个 Interceptor
    response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null); 
    ...
}

StreamAlloction:

StreamAllocation 从名字上看是流分配器,其实它是统筹管理了几样东西。

简单来说, StreamAllocation 协调了 3 样东西:

  • Connections : 物理的 socket 连接
  • Streams:逻辑上的 HTTP request/response 对。每个 Connection 有个变量 allocationLimit ,用于定义可以承载的并发的 streams 的数量。HTTP/1.x 的 Connection 一次只能有一个 stream, HTTP/2 一般可以有多个。
  • CallsStreams 的序列。一个初始的 request 可能还会有后续的 request(如重定向)。OkHttp 倾向于让一个 call 所有的 streams 运行在同一个 connection 上。

StreamAllocation 提供了一些 API 来释放以上的资源对象。 在 RetryAndFollowUpInterceptor 中创建的 StreamAllocation 对象下一个用到的地方是 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");
    //Streams,逻辑上的 HTTP request/response 对。
    HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
    RealConnection connection = streamAllocation.connection();	//物理的 socket 连接

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

newStream:

StreamAllocation 中的 newStream 方法用于寻找新的 RealConnection 以及 HttpCodec,代码如下:

public HttpCodec newStream(OkHttpClient client, boolean doExtensiveHealthChecks) {
    int connectTimeout = client.connectTimeoutMillis();
    int readTimeout = client.readTimeoutMillis();
    int writeTimeout = client.writeTimeoutMillis();
    boolean connectionRetryEnabled = client.retryOnConnectionFailure();

    try {
      //通过 findHealthyConnection 找到可用的 Connection ,并用这个 Connection 生成一个 HttpCodec 对象
      RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
          writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
      HttpCodec resultCodec = resultConnection.newCodec(client, this);

      synchronized (connectionPool) {
        codec = resultCodec;
        return resultCodec;
      }
    } catch (IOException e) {
      throw new RouteException(e);
    }
  }

findHealthyConnection 是找到一个健康的连接,代码如下:

private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
      int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
      throws IOException {
    while (true) {
      RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
          connectionRetryEnabled);

      // If this is a brand new connection, we can skip the extensive health checks.
      synchronized (connectionPool) {
      // successCount == 0 表示还未使用过,则可以使用
        if (candidate.successCount == 0) {
          return candidate;
        }
      }

      // Do a (potentially slow) check to confirm that the pooled connection is still good. If it
      // isn't, take it out of the pool and start again.
      if (!candidate.isHealthy(doExtensiveHealthChecks)) {
        noNewStreams();
        continue;
      }

      return candidate;
    }
 }

public boolean isHealthy(boolean doExtensiveChecks) {
    if (socket.isClosed() || socket.isInputShutdown() || socket.isOutputShutdown()) {
      return false;
    }
    ... // 省略 Http2 代码
    return true;
  }

在一个无限循环中,通过 findConnection 寻找一个 connection,并判断是否可用,首先如果没有使用过的肯定是健康的可直接返回,否则调用 isHealthy,主要就是判断 socket 是否关闭。这里的 socket 是在 findConnection 中赋值的,再看看 findConnection 的代码:

private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
      boolean connectionRetryEnabled) throws IOException {
    Route selectedRoute;
    synchronized (connectionPool) {
      if (released) throw new IllegalStateException("released");
      if (codec != null) throw new IllegalStateException("codec != null");
      if (canceled) throw new IOException("Canceled");

      // Attempt to use an already-allocated connection.
      RealConnection allocatedConnection = this.connection;
      if (allocatedConnection != null && !allocatedConnection.noNewStreams) {
        return allocatedConnection;
      }

      // Attempt to get a connection from the pool.
      // 1. 从 ConnectionPool 取得 connection
      Internal.instance.get(connectionPool, address, this, null);
      if (connection != null) {
        return connection;
      }

      selectedRoute = route;
    }

    // If we need a route, make one. This is a blocking operation.
    if (selectedRoute == null) {
      selectedRoute = routeSelector.next();
    }

    RealConnection result;
    synchronized (connectionPool) {
      if (canceled) throw new IOException("Canceled");

      // Now that we have an IP address, make another attempt at getting a connection from the pool.
      // 2. 有了 ip 地址后再从 connectionpool中取一次
      // This could match due to connection coalescing.
      Internal.instance.get(connectionPool, address, this, selectedRoute);
      if (connection != null) return connection;

      // Create a connection and assign it to this allocation immediately. This makes it possible
      // for an asynchronous cancel() to interrupt the handshake we're about to do.
      route = selectedRoute;
      refusedStreamCount = 0;
      // 3. ConnectionPool 中没有,新创建一个
      result = new RealConnection(connectionPool, selectedRoute);
      // 3. 将 StreamAllocation 加入到 `RealConnection` 中的一个队列中
      acquire(result);
    }

    // Do TCP + TLS handshakes. This is a blocking operation.
    // 4. 建立连接,在其中创建 socket
    result.connect(connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled);
    routeDatabase().connected(result.route());

    Socket socket = null;
    synchronized (connectionPool) {
      // Pool the connection.
      // 5. 将新创建的 connection 放到 ConnectionPool 中 
      Internal.instance.put(connectionPool, result);

      // If another multiplexed connection to the same address was created concurrently, then
      // release this connection and acquire that one.
      if (result.isMultiplexed()) {
        socket = Internal.instance.deduplicate(connectionPool, address, this);
        result = connection;
      }
    }
    closeQuietly(socket);

    return result;
  }

上面 Connection 的创建大体是以下几个步骤:

  1. 调用 Intenal.get 方法从 ConnectionPool 中获取一个 Connection,主要根据 url 的 host 判断,相关代码在 ConnectionPool 中。
  2. 如果没有并且又获取了 IP 地址,则再获取一次。
  3. 如果 ConnectionPool 中没有, 则新创建一个 RealConnection,并调用 acquireStreamAllocation 中加入 RealConnection 中的一个队列中。
  4. 调用 RealConnection#connect 方法建立连接,在内部会创建 Socket
  5. 将新创建的 Connection 加入到 ConnectionPool 中。

获取到了 Connection 之后,再创建一个 HttpCodec 对象。

public HttpCodec newCodec(
      OkHttpClient client, StreamAllocation streamAllocation) throws SocketException {
    if (http2Connection != null) {
      return new Http2Codec(client, streamAllocation, http2Connection);
    } else {
      socket.setSoTimeout(client.readTimeoutMillis());
      source.timeout().timeout(client.readTimeoutMillis(), MILLISECONDS);
      sink.timeout().timeout(client.writeTimeoutMillis(), MILLISECONDS);
      return new Http1Codec(client, streamAllocation, source, sink);
    }
}

根据是 Http1 还是 Http2 创建对应的 HttpCodec, 其中的 socket 是在 RealConnection 中的 connect 方法创建的。

RealConnection:

RealConnection 封装的是底层的 Socket 连接,内部必然有一个 Socket 对象,下面是 RealConnection 内部的变量:

public final class RealConnection extends Http2Connection.Listener implements Connection {
  private static final String NPE_THROW_WITH_NULL = "throw with null exception";
  private final ConnectionPool connectionPool;
  //Route 表示的是与服务端建立的路径,其实内部封装了 Address,Address 则是封装了请求的 URL。
  private final Route route;

  //rawSocket 对象代表底层的连接,还有一个 socket 是用于 Https, 对于普通的 Http 请求来说,这两个对象是一样的。 
  private Socket rawSocket;

  private Socket socket;
  private Handshake handshake;
  private Protocol protocol;
  private Http2Connection http2Connection;
  //source 和 sink 则是利用 Okio 封装 socket 得到的输入输出流。
  private BufferedSource source;
  private BufferedSink sink;

  //noNewStream 对象用于标识这个 Connection 不能再用于 Http 请求了,一旦设置为 true, 则不会再变。
  public boolean noNewStreams;

  public int successCount;

  //allocationLimit 指的是这个 Connection 最多能同时承载几个 Http 流,对于 Http/1 来说只能是一个。
  public int allocationLimit = 1;

  //allocations 是一个 List 对象,里面保存着正在使用这个 Connection 的 StreamAllocation 的弱引用,当StreamAllocation 调用 acquire 时,便会将其弱引用加入这个 List,调用 release 则是移除引用。allocations 为空说明此 Connection 为闲置, ConnectionPool 利用这些信息来决定是否关闭这个连接。
  public final List<Reference<StreamAllocation>> allocations = new ArrayList<>();

  /** Nanotime timestamp when {@code allocations.size()} reached zero. */
  public long idleAtNanos = Long.MAX_VALUE;
  ...
}

connect

RealConnection 用于建立连接,里面有相应的 connect 方法:

public void connect(
      int connectTimeout, int readTimeout, int writeTimeout, boolean connectionRetryEnabled) {
    ...
    while (true) {
      try {
        if (route.requiresTunnel()) {
          connectTunnel(connectTimeout, readTimeout, writeTimeout);
        } else {
          // 创建socket,建立连接
          connectSocket(connectTimeout, readTimeout);
        }
        // 建立
        establishProtocol(connectionSpecSelector);
        break;
      }
    ...
}

private void connectSocket(int connectTimeout, int readTimeout) throws IOException {
    Proxy proxy = route.proxy();
    Address address = route.address();
    // 创建 socket
    rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
        ? address.socketFactory().createSocket()
        : new Socket(proxy);

    rawSocket.setSoTimeout(readTimeout);
    try {
      // 建立连接,相当于调用 socket 的 connect 方法
      Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
    } catch (ConnectException e) {
      ConnectException ce = new ConnectException("Failed to connect to " + route.socketAddress());
      ce.initCause(e);
      throw ce;
    }
    
    try {
      // 获取输入输出流
      source = Okio.buffer(Okio.source(rawSocket));
      sink = Okio.buffer(Okio.sink(rawSocket));
    } catch (NullPointerException npe) {
      if (NPE_THROW_WITH_NULL.equals(npe.getMessage())) {
        throw new IOException(npe);
      }
    }
}

如果不是 Https, 则调用 connectSocket,在内部创建 rawSocket 对象,设置超时时间。紧接着 Platform.get().connectSocket 根据不同的平台调用相应的 connect 方法,这样 rawSocket 就连接到服务端了。然后是用 Okio 封装 rawSocket 的输入输出流,这里的输入输出流最终是交给 HttpCodec 进行 Http 报文的写入都读取。通过以上步骤,就实现了 Http 请求的连接。

总结:

本文从 RetryAndFollowupIntercept 中创建 StreamAllocation 对象,到 Connection 中创建 RealConnectionHttpCodec,分析了 OkHttp 建立连接的基本过程。可以看出, OkHttp 中的连接由RealConnection 封装,Http 流的输入输出由 HttpCodec 操作,而 StreamAllocation 则统筹管理这些资源。在连接的寻找与创建过程,有个关键的东西是 ConnectionPool, 即连接池。它负责管理所有的 Connection,OkHttp 利用这个连接池进行 Connection 的重用以提高网络请求的效率

连接池

前面分析了 OkHttp 建立连接的过程,主要涉及到的几个类包括 StreamAllocationRealConnection 以及 HttpCodec,其中 RealConnection 封装了底层的 Socket。Socket 建立了 TCP 连接,这是需要消耗时间和资源的,而 OkHttp 则使用连接池来管理这里连接,进行连接的重用,提高请求的效率。OkHttp 中的连接池由 ConnectionPool 实现,本文主要是对这个类进行分析。

StreamAllocationfindConnection 方法中,有这样一段代码:

// Attempt to get a connection from the pool.
Internal.instance.get(connectionPool, address, this, null);
    if (connection != null) {
        return connection;
}

Internal.instance.get 最终是从 ConnectionPool 取得一个RealConnection, 如果有了则直接返回。下面是 ConnectionPool 中的代码:

@Nullable 
RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
    assert (Thread.holdsLock(this));
    //connections 是 ConnectionPool 中的一个双端队列(ArrayDeque)
    for (RealConnection connection : connections) {
      if (connection.isEligible(address, route)) {
        streamAllocation.acquire(connection);
        return connection;
      }
    }
    return null;
}

从队列中取出一个 Connection 之后,判断其是否能满足重用的要求:

public boolean isEligible(Address address, @Nullable Route route) {
    // 如果这个 Connection 已经分配的数量超过了分配限制或者被标记为不能再分配,则直接返回 false
    if (allocations.size() >= allocationLimit || noNewStreams) return false;

    // 判断 Address 中除了 host 以外的变量是否相同,如果有不同的,那么这个连接也不能重用
    if (!Internal.instance.equalsNonHost(this.route.address(), address)) return false;

    // 判断 host 是否相同,如果相同那么对于当前的 Address 来说, 这个 Connection 便是可重用的
    if (address.url().host().equals(this.route().address().url().host())) {
      return true; // This connection is a perfect match.
    }
   // 省略 http2 相关代码
   ...
}

boolean equalsNonHost(Address that) {
    return this.dns.equals(that.dns)
        && this.proxyAuthenticator.equals(that.proxyAuthenticator)
        && this.protocols.equals(that.protocols)
        && this.connectionSpecs.equals(that.connectionSpecs)
        && this.proxySelector.equals(that.proxySelector)
        && equal(this.proxy, that.proxy)
        && equal(this.sslSocketFactory, that.sslSocketFactory)
        && equal(this.hostnameVerifier, that.hostnameVerifier)
        && equal(this.certificatePinner, that.certificatePinner)
        && this.url().port() == that.url().port();
}

从上面的代码看来,get 逻辑还是比较简单明了的。

接下来看一下 put,在 StreamAllocationfindConnection 方法中,如果新创建了 Connection,则将其放到连接池中。

Internal.instance.put(connectionPool, result);

最终调用的是 ConnectionPool#put

void put(RealConnection connection) {
    assert (Thread.holdsLock(this));
    if (!cleanupRunning) {
      cleanupRunning = true;
      executor.execute(cleanupRunnable);
    }
    connections.add(connection);
}

首先判断其否启动了清理线程,如果没有则将 cleanupRunnable 放到线程池中。最后是将 RealConnection 放到队列中。

cleanup:

线程池需要对闲置的或者超时的连接进行清理,CleanupRunnable 就是做这件事的:

private final Runnable cleanupRunnable = new Runnable() {
    @Override public void run() {
      while (true) {
        long waitNanos = cleanup(System.nanoTime());
        if (waitNanos == -1) return;
        if (waitNanos > 0) {
          long waitMillis = waitNanos / 1000000L;
          waitNanos -= (waitMillis * 1000000L);
          synchronized (ConnectionPool.this) {
            try {
              ConnectionPool.this.wait(waitMillis, (int) waitNanos);
            } catch (InterruptedException ignored) {
            }
          }
        }
      }
    }
};

run 里面有个无限循环,调用 cleanup 之后,得到一个时间 waitNano,如果不为 -1 则表示线程的睡眠时间,接下来调用 wait 进入睡眠。如果是 -1,则表示当前没有需要清理的连接,直接返回即可。

清理的主要实现在 cleanup 方法中,下面是其代码:

long cleanup(long now) {
    int inUseConnectionCount = 0;
    int idleConnectionCount = 0;
    RealConnection longestIdleConnection = null;
    long longestIdleDurationNs = Long.MIN_VALUE;

    // Find either a connection to evict, or the time that the next eviction is due.
    synchronized (this) {
      for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
        RealConnection connection = i.next();

        // 1. 判断是否是空闲连接
        if (pruneAndGetAllocationCount(connection, now) > 0) {
          inUseConnectionCount++;
          continue;
        }

        idleConnectionCount++;

        // 2. 判断是否是最长空闲时间的连接
        long idleDurationNs = now - connection.idleAtNanos;
        if (idleDurationNs > longestIdleDurationNs) {
          longestIdleDurationNs = idleDurationNs;
          longestIdleConnection = connection;
        }
      }
      //  3. 如果最长空闲的时间超过了设定的最大值,或者空闲链接数量超过了最大数量,则进行清理,否则计算下一次需要清理的等待时间
      if (longestIdleDurationNs >= this.keepAliveDurationNs
          || idleConnectionCount > this.maxIdleConnections) {
        // We've found a connection to evict. Remove it from the list, then close it below (outside
        // of the synchronized block).
        connections.remove(longestIdleConnection);
      } else if (idleConnectionCount > 0) {
        // A connection will be ready to evict soon.
        return keepAliveDurationNs - longestIdleDurationNs;
      } else if (inUseConnectionCount > 0) {
        // All connections are in use. It'll be at least the keep alive duration 'til we run again.
        return keepAliveDurationNs;
      } else {
        // No connections, idle or in use.
        cleanupRunning = false;
        return -1;
      }
    }
     // 3. 关闭连接的socket
    closeQuietly(longestIdleConnection.socket());

    // Cleanup again immediately.
    return 0;
}

清理的逻辑大致是以下几步:

  1. 遍历所有的连接,对每个连接调用 pruneAndGetAllocationCount 判断其是否闲置的连接。如果是正在使用中,则直接遍历一下个。
  2. 对于闲置的连接,判断是否是当前空闲时间最长的。
  3. 对于当前空闲时间最长的连接,如果其超过了设定的最长空闲时间(5分钟)或者是最大的空闲连接的数量(5个),则清理此连接。否则计算下次需要清理的时间,这样 cleanupRunnable 中的循环变会睡眠相应的时间,醒来后继续清理。

pruneAndGetAllocationCount 用于清理可能泄露的 StreamAllocation 并返回正在使用此连接的 StreamAllocation 的数量,代码如下:

private int pruneAndGetAllocationCount(RealConnection connection, long now) {
    List<Reference<StreamAllocation>> references = connection.allocations;
    for (int i = 0; i < references.size(); ) {
      Reference<StreamAllocation> reference = references.get(i);

      if (reference.get() != null) {
        i++;
        continue;
      }

      // We've discovered a leaked allocation. This is an application bug.
      // 如果 StreamAlloction 引用被回收,但是 connection 的引用列表中扔持有,那么可能发生了内存泄露
      StreamAllocation.StreamAllocationReference streamAllocRef =
          (StreamAllocation.StreamAllocationReference) reference;
      String message = "A connection to " + connection.route().address().url()
          + " was leaked. Did you forget to close a response body?";
      Platform.get().logCloseableLeak(message, streamAllocRef.callStackTrace);

      references.remove(i);
      connection.noNewStreams = true;

      // If this was the last allocation, the connection is eligible for immediate eviction.
      if (references.isEmpty()) {
        connection.idleAtNanos = now - keepAliveDurationNs;
        return 0;
      }
    }

    return references.size();
}

如果 StreamAllocation 已经被回收,说明应用层的代码已经不需要这个连接,但是 Connection 仍持有 StreamAllocation 的引用,则表示StreamAllocationrelease(RealConnection connection) 方法未被调用,可能是读取 ResponseBody 没有关闭 I/O 导致的。

总结:

OkHttp 中的连接池主要就是保存一个正在使用的连接的队列,对于满足条件的同一个 host 的多个连接复用同一个 RealConnection,提高请求效率。此外,还会启动线程对闲置超时或者超出闲置数量的 RealConnection 进行清理。

@然则

博文地址:https://segmentfault.com/a/1190000012656606

总结

1.关于二叉树的这几道算法题较为简单,都是基础操作;
2.OkHttp源码分析基本到这,主要靠理解记忆并能用自己话讲述出来;

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

leisure-ZL

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值