Android Okhttp 源码浅析三

核心方法 getResponseWithInterceptorChain() 
 internal fun getResponseWithInterceptorChain(): Response {// Build a full stack of interceptors.val interceptors = mutableListOf<Interceptor>()interceptors += client.interceptorsinterceptors += RetryAndFollowUpInterceptor(client)interceptors += BridgeInterceptor(client.cookieJar)interceptors += CacheInterceptor(client.cache)interceptors += ConnectInterceptorif (!forWebSocket) {interceptors += client.networkInterceptors}interceptors += CallServerInterceptor(forWebSocket)val chain = RealInterceptorChain(call = this,interceptors = interceptors,index = 0,exchange = null,request = originalRequest,connectTimeoutMillis = client.connectTimeoutMillis,readTimeoutMillis = client.readTimeoutMillis,writeTimeoutMillis = client.writeTimeoutMillis)var calledNoMoreExchanges = falsetry {val response = chain.proceed(originalRequest)if (isCanceled()) {response.closeQuietly()throw IOException("Canceled")}return response} catch (e: IOException) {calledNoMoreExchanges = truethrow noMoreExchanges(e) as Throwable} finally {if (!calledNoMoreExchanges) {noMoreExchanges(null)}}}
val interceptors = mutableListOf<Interceptor>()
//三个模型
interceptors += client.interceptors
interceptors += RetryAndFollowUpInterceptor(client)
interceptors += BridgeInterceptor(client.cookieJar)
interceptors += CacheInterceptor(client.cache)
interceptors += ConnectInterceptor
if (!forWebSocket) {interceptors += client.networkInterceptors
}
//一个模型
interceptors += CallServerInterceptor(forWebSocket)

添加网络事件拦截器 Interceptor

val chain = RealInterceptorChain(
        call = this,
        interceptors = interceptors,
        index = 0,
        exchange = null,
        request = originalRequest,
        connectTimeoutMillis = client.connectTimeoutMillis,
        readTimeoutMillis = client.readTimeoutMillis,
        writeTimeoutMillis = client.writeTimeoutMillis
    )
 创建Chain

  var calledNoMoreExchanges = false
    try {

链式执行 

//originalRequest 还没有执行的request
      val response = chain.proceed(originalRequest)


      if (isCanceled()) {
        response.closeQuietly()
        throw IOException("Canceled")
      }
      return response
    } catch (e: IOException) {
      calledNoMoreExchanges = true
      throw noMoreExchanges(e) as Throwable
    } finally {
      if (!calledNoMoreExchanges) {
        noMoreExchanges(null)
      }
    }

 @Throws(IOException::class)override fun proceed(request: Request): Response {check(index < interceptors.size)calls++if (exchange != null) {check(exchange.finder.sameHostAndPort(request.url)) {"network interceptor ${interceptors[index - 1]} must retain the same host and port"}check(calls == 1) {"network interceptor ${interceptors[index - 1]} must call proceed() exactly once"}}// Call the next interceptor in the chain.val next = copy(index = index + 1, request = request)val interceptor = interceptors[index]@Suppress("USELESS_ELVIS")val response = interceptor.intercept(next) ?: throw NullPointerException("interceptor $interceptor returned null")if (exchange != null) {check(index + 1 >= interceptors.size || next.calls == 1) {"network interceptor $interceptor must call proceed() exactly once"}}check(response.body != null) { "interceptor $interceptor returned a response with no body" }return response}

val response = interceptor.intercept(next) ?: throw NullPointerException(
        "interceptor $interceptor returned null")

拦截器开始拦截

拦截器调用process index 会+1 执行后面的拦截器,正面执行完会反向执行

RetryAndFollowUpInterceptor:

class RetryAndFollowUpInterceptor(private val client: OkHttpClient) : Interceptor {@Throws(IOException::class)override fun intercept(chain: Interceptor.Chain): Response {val realChain = chain as RealInterceptorChainvar request = chain.requestval call = realChain.callvar followUpCount = 0var priorResponse: Response? = nullvar newExchangeFinder = truevar recoveredFailures = listOf<IOException>()while (true) {call.enterNetworkInterceptorExchange(request, newExchangeFinder)var response: Responsevar closeActiveExchange = truetry {if (call.isCanceled()) {throw IOException("Canceled")}try {response = realChain.proceed(request)newExchangeFinder = true} catch (e: RouteException) {// The attempt to connect via a route failed. The request will not have been sent.if (!recover(e.lastConnectException, call, request, requestSendStarted = false)) {throw e.firstConnectException.withSuppressed(recoveredFailures)} else {recoveredFailures += e.firstConnectException}newExchangeFinder = falsecontinue} catch (e: IOException) {// An attempt to communicate with a server failed. The request may have been sent.if (!recover(e, call, request, requestSendStarted = e !is ConnectionShutdownException)) {throw e.withSuppressed(recoveredFailures)} else {recoveredFailures += e}newExchangeFinder = falsecontinue}// Attach the prior response if it exists. Such responses never have a body.if (priorResponse != null) {response = response.newBuilder().priorResponse(priorResponse.newBuilder().body(null).build()).build()}val exchange = call.interceptorScopedExchangeval followUp = followUpRequest(response, exchange)if (followUp == null) {if (exchange != null && exchange.isDuplex) {call.timeoutEarlyExit()}closeActiveExchange = falsereturn response}val followUpBody = followUp.bodyif (followUpBody != null && followUpBody.isOneShot()) {closeActiveExchange = falsereturn response}response.body?.closeQuietly()if (++followUpCount > MAX_FOLLOW_UPS) {throw ProtocolException("Too many follow-up requests: $followUpCount")}request = followUppriorResponse = response} finally {call.exitNetworkInterceptorExchange(closeActiveExchange)}}}

错误重试和重定向Follow

while (true) 

call.enterNetworkInterceptorExchange(request, newExchangeFinder) 连接前的准备工作,call = RealCall,创建一个ExchangeFinder, 寻找可用的http / ssl 等一个可用连接

RouteException 某条链接线路失败  --->重试 recover 会判断Client 的 retryOnConnectFailure 

isRecoverable 是否可以修正的错误 ,在retryOnConnectFailure 为true的前提下

request = followUp 执行下一次请求

若没有出错或者重定向 则返回下一个链式拦截器

BridgeInterceptor:

 requestBuilder.header("Content-Type", contentType.toString())}val contentLength = body.contentLength()if (contentLength != -1L) {requestBuilder.header("Content-Length", contentLength.toString())requestBuilder.removeHeader("Transfer-Encoding")} else {requestBuilder.header("Transfer-Encoding", "chunked")requestBuilder.removeHeader("Content-Length")}}if (userRequest.header("Host") == null) {requestBuilder.header("Host", userRequest.url.toHostHeader())}if (userRequest.header("Connection") == null) {requestBuilder.header("Connection", "Keep-Alive")}// If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing// the transfer stream.var transparentGzip = falseif (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {transparentGzip = truerequestBuilder.header("Accept-Encoding", "gzip")}

添加head / body /host等

压缩类型 Accept-Encodeing = gzip 自动压缩和解压

CacheInterceptor
 override fun intercept(chain: Interceptor.Chain): Response {val call = chain.call()val cacheCandidate = cache?.get(chain.request())val now = System.currentTimeMillis()val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()val networkRequest = strategy.networkRequestval cacheResponse = strategy.cacheResponsecache?.trackResponse(strategy)val listener = (call as? RealCall)?.eventListener ?: EventListener.NONEif (cacheCandidate != null && cacheResponse == null) {// The cache candidate wasn't applicable. Close it.cacheCandidate.body?.closeQuietly()}// If we're forbidden from using the network and the cache is insufficient, fail.if (networkRequest == null && cacheResponse == null) {return Response.Builder().request(chain.request()).protocol(Protocol.HTTP_1_1).code(HTTP_GATEWAY_TIMEOUT).message("Unsatisfiable Request (only-if-cached)").body(EMPTY_RESPONSE).sentRequestAtMillis(-1L).receivedResponseAtMillis(System.currentTimeMillis()).build().also {listener.satisfactionFailure(call, it)}}// If we don't need the network, we're done.if (networkRequest == null) {return cacheResponse!!.newBuilder().cacheResponse(stripBody(cacheResponse)).build().also {listener.cacheHit(call, it)}}if (cacheResponse != null) {listener.cacheConditionalHit(call, cacheResponse)} else if (cache != null) {listener.cacheMiss(call)}var networkResponse: Response? = nulltry {networkResponse = chain.proceed(networkRequest)} finally {// If we're crashing on I/O or otherwise, don't leak the cache body.if (networkResponse == null && cacheCandidate != null) {cacheCandidate.body?.closeQuietly()}}

先查找有没有缓存 有的话直接返回 没有就继续请求

核心类:CacheStrategy

   for (i in 0 until headers.size) {val fieldName = headers.name(i)val value = headers.value(i)when {fieldName.equals("Date", ignoreCase = true) -> {servedDate = value.toHttpDateOrNull()servedDateString = value}fieldName.equals("Expires", ignoreCase = true) -> {expires = value.toHttpDateOrNull()}fieldName.equals("Last-Modified", ignoreCase = true) -> {lastModified = value.toHttpDateOrNull()lastModifiedString = value}fieldName.equals("ETag", ignoreCase = true) -> {etag = value}fieldName.equals("Age", ignoreCase = true) -> {ageSeconds = value.toNonNegativeInt(-1)}}}

 when {etag != null -> {conditionName = "If-None-Match"conditionValue = etag}lastModified != null -> {conditionName = "If-Modified-Since"conditionValue = lastModifiedString}servedDate != null -> {conditionName = "If-Modified-Since"conditionValue = servedDateString}else -> return CacheStrategy(request, null) // No condition! Make a regular request.}

先拿到可用的 CacheStrategy 有则返回没有就继续请求 然后put缓存

 

ConnectInterceptor

请求的核心类

@Throws(IOException::class)override fun intercept(chain: Interceptor.Chain): Response {val realChain = chain as RealInterceptorChainval exchange = realChain.call.initExchange(chain)val connectedChain = realChain.copy(exchange = exchange)return connectedChain.proceed(realChain.request)}
  internal fun initExchange(chain: RealInterceptorChain): Exchange {synchronized(this) {check(expectMoreExchanges) { "released" }check(!responseBodyOpen)check(!requestBodyOpen)}val exchangeFinder = this.exchangeFinder!!val codec = exchangeFinder.find(client, chain)val result = Exchange(this, eventListener, exchangeFinder, codec)this.interceptorScopedExchange = resultthis.exchange = resultsynchronized(this) {this.requestBodyOpen = truethis.responseBodyOpen = true}if (canceled) throw IOException("Canceled")return result}

initExchange  初始化

val codec = exchangeFinder.find(client, chain)------> 编码 解码 coder & decoder

find = ExChangeFinder 的 find 函数

find源码:

  fun find(client: OkHttpClient,chain: RealInterceptorChain): ExchangeCodec {try {val resultConnection = findHealthyConnection(connectTimeout = chain.connectTimeoutMillis,readTimeout = chain.readTimeoutMillis,writeTimeout = chain.writeTimeoutMillis,pingIntervalMillis = client.pingIntervalMillis,connectionRetryEnabled = client.retryOnConnectionFailure,doExtensiveHealthChecks = chain.request.method != "GET")return resultConnection.newCodec(client, chain)} catch (e: RouteException) {trackFailure(e.lastConnectException)throw e} catch (e: IOException) {trackFailure(e)throw RouteException(e)}}

找到可用的连接,源码:

 private fun findHealthyConnection(connectTimeout: Int,readTimeout: Int,writeTimeout: Int,pingIntervalMillis: Int,connectionRetryEnabled: Boolean,doExtensiveHealthChecks: Boolean): RealConnection {while (true) {val candidate = findConnection(connectTimeout = connectTimeout,readTimeout = readTimeout,writeTimeout = writeTimeout,pingIntervalMillis = pingIntervalMillis,connectionRetryEnabled = connectionRetryEnabled)// Confirm that the connection is good.if (candidate.isHealthy(doExtensiveHealthChecks)) {return candidate}// If it isn't, take it out of the pool.candidate.noNewExchanges()// Make sure we have some routes left to try. One example where we may exhaust all the routes// would happen if we made a new connection and it immediately is detected as unhealthy.if (nextRouteToTry != null) continueval routesLeft = routeSelection?.hasNext() ?: trueif (routesLeft) continueval routesSelectionLeft = routeSelector?.hasNext() ?: trueif (routesSelectionLeft) continuethrow IOException("exhausted all routes")}}

 val candidate = findConnection >  if (candidate.isHealthy(doExtensiveHealthChecks)) ( 先拿到一个可用连接 再判断健康与否 ,如果为false 就再重新取 

源码:

  @Throws(IOException::class)private fun findConnection(connectTimeout: Int,readTimeout: Int,writeTimeout: Int,pingIntervalMillis: Int,connectionRetryEnabled: Boolean): RealConnection {if (call.isCanceled()) throw IOException("Canceled")// Attempt to reuse the connection from the call.val callConnection = call.connection // This may be mutated by releaseConnectionNoEvents()!if (callConnection != null) {var toClose: Socket? = nullsynchronized(callConnection) {if (callConnection.noNewExchanges || !sameHostAndPort(callConnection.route().address.url)) {toClose = call.releaseConnectionNoEvents()}}// If the call's connection wasn't released, reuse it. We don't call connectionAcquired() here// because we already acquired it.if (call.connection != null) {check(toClose == null)return callConnection}// The call's connection was released.toClose?.closeQuietly()eventListener.connectionReleased(call, callConnection)}// We need a new connection. Give it fresh stats.refusedStreamCount = 0connectionShutdownCount = 0otherFailureCount = 0// Attempt to get a connection from the pool.if (connectionPool.callAcquirePooledConnection(address, call, null, false)) {val result = call.connection!!eventListener.connectionAcquired(call, result)return result}// Nothing in the pool. Figure out what route we'll try next.val routes: List<Route>?val route: Routeif (nextRouteToTry != null) {// Use a route from a preceding coalesced connection.routes = nullroute = nextRouteToTry!!nextRouteToTry = null} else if (routeSelection != null && routeSelection!!.hasNext()) {// Use a route from an existing route selection.routes = nullroute = routeSelection!!.next()} else {// Compute a new route selection. This is a blocking operation!var localRouteSelector = routeSelectorif (localRouteSelector == null) {localRouteSelector = RouteSelector(address, call.client.routeDatabase, call, eventListener)this.routeSelector = localRouteSelector}val localRouteSelection = localRouteSelector.next()routeSelection = localRouteSelectionroutes = localRouteSelection.routesif (call.isCanceled()) throw IOException("Canceled")// Now that we have a set of IP addresses, make another attempt at getting a connection from// the pool. We have a better chance of matching thanks to connection coalescing.if (connectionPool.callAcquirePooledConnection(address, call, routes, false)) {val result = call.connection!!eventListener.connectionAcquired(call, result)return result}route = localRouteSelection.next()}// Connect. Tell the call about the connecting call so async cancels work.val newConnection = RealConnection(connectionPool, route)call.connectionToCancel = newConnectiontry {newConnection.connect(connectTimeout,readTimeout,writeTimeout,pingIntervalMillis,connectionRetryEnabled,call,eventListener)} finally {call.connectionToCancel = null}call.client.routeDatabase.connected(newConnection.route())// If we raced another call connecting to this host, coalesce the connections. This makes for 3// different lookups in the connection pool!if (connectionPool.callAcquirePooledConnection(address, call, routes, true)) {val result = call.connection!!nextRouteToTry = routenewConnection.socket().closeQuietly()eventListener.connectionAcquired(call, result)return result}synchronized(newConnection) {connectionPool.put(newConnection)call.acquireConnectionNoEvents(newConnection)}eventListener.connectionAcquired(call, newConnection)return newConnection}

通过五种方式拿到可用连接:

if (call.isCanceled()) throw IOException("Canceled")
val callConnection = call.connection // This may be mutated by releaseConnectionNoEvents()! 判断call里面是否有一个已经建立的连接,如果没有就继续往下执行获取连接
refusedStreamCount = 0
connectionShutdownCount = 0
otherFailureCount = 0// Attempt to get a connection from the pool.
if (connectionPool.callAcquirePooledConnection(address, call, null, false)) {val result = call.connection!!eventListener.connectionAcquired(call, result)return result
}

尝试获取连接 callAcquirePooledConnection 

  fun callAcquirePooledConnection(address: Address,call: RealCall,routes: List<Route>?,requireMultiplexed: Boolean): Boolean {for (connection in connections) {synchronized(connection) {if (requireMultiplexed && !connection.isMultiplexed) return@synchronizedif (!connection.isEligible(address, routes)) return@synchronizedcall.acquireConnectionNoEvents(connection)return true}}return false}

对所有连接池里进行循环

        if (!connection.isEligible(address, routes)) return@synchronized 是否可用 ,false 则继续取下一个 直到取到可用连接

  fun acquireConnectionNoEvents(connection: RealConnection) {connection.assertThreadHoldsLock()check(this.connection == null)this.connection = connectionconnection.calls.add(CallReference(this, callStackTrace))}

    this.connection = connection 复制connection

然后创建编码解码器 return resultConnection.newCodec(client, chain)

val result = Exchange(this, eventListener, exchangeFinder, codec)

通过函数判断是否可用

  internal fun isEligible(address: Address, routes: List<Route>?): Boolean {assertThreadHoldsLock()// If this connection is not accepting new exchanges, we're done.if (calls.size >= allocationLimit || noNewExchanges) return false// If the non-host fields of the address don't overlap, we're done.if (!this.route.address.equalsNonHost(address)) return false// If the host exactly matches, we're done: this connection can carry the address.if (address.url.host == this.route().address.url.host) {return true // This connection is a perfect match.}// At this point we don't have a hostname match. But we still be able to carry the request if// our connection coalescing requirements are met. See also:// https://hpbn.co/optimizing-application-delivery/#eliminate-domain-sharding// https://daniel.haxx.se/blog/2016/08/18/http2-connection-coalescing/// 1. This connection must be HTTP/2.if (http2Connection == null) return false// 2. The routes must share an IP address.if (routes == null || !routeMatchesAny(routes)) return false// 3. This connection's server certificate's must cover the new host.if (address.hostnameVerifier !== OkHostnameVerifier) return falseif (!supportsUrl(address.url)) return false// 4. Certificate pinning must match the host.try {address.certificatePinner!!.check(address.url.host, handshake()!!.peerCertificates)} catch (_: SSLPeerUnverifiedException) {return false}return true // The caller's address can be carried by this connection.}

    if (calls.size >= allocationLimit || noNewExchanges) return false 当前请求的数量是否超过限制 http2 以下为1个

  // If the non-host fields of the address don't overlap, we're done.
    if (!this.route.address.equalsNonHost(address)) return false

    // If the host exactly matches, we're done: this connection can carry the address.
    if (address.url.host == this.route().address.url.host) {
      return true // This connection is a perfect match.
    }

internal fun equalsNonHost(that: Address): Boolean {return this.dns == that.dns &&this.proxyAuthenticator == that.proxyAuthenticator &&this.protocols == that.protocols &&this.connectionSpecs == that.connectionSpecs &&this.proxySelector == that.proxySelector &&this.proxy == that.proxy &&this.sslSocketFactory == that.sslSocketFactory &&this.hostnameVerifier == that.hostnameVerifier &&this.certificatePinner == that.certificatePinner &&this.url.port == that.url.port
}

判断是否为统一连接 /代理 主机名 等

http2 判断只要IP地址相同 但是必须判断证书是否相同  就复用连接

判断是否为同一个路径 url

如果没有取到可用连接,则往下执行在获取一次:      if (connectionPool.callAcquirePooledConnection(address, call, routes, false)) {
 相比上次多了个route 通过RouseSelection 来自于RouteSelect,一个slect 包含多个selection ,一个selection 包含route,最后一个boolean参数 为是否共用同一个连接 host

supportsUrl 函数判断 主机名和端口必须一样 或者符号其他要求

nextRouteToTry = selectRoute ,当连接建立,若有连接已经建立,会缓存当前的,因为如果网络不稳定或者已经建立的无法连接,还会需要当前建立的,基于多路复用

address.dns.lockUp 解析地址

Address 关键参数 uriHost / uriPort 也就是主机名和端口

call 取消 则抛出异常

RouteSelector

httpTunnel 代理https 的一种方式 

rawSocket 是实际connetc 的tcp端口

RealConnection

connectTls 连接tls

headShake 验证:

handshake = Handshake(unverifiedHandshake.tlsVersion, unverifiedHandshake.cipherSuite,unverifiedHandshake.localCertificates) {certificatePinner.certificateChainCleaner!!.clean(unverifiedHandshake.peerCertificates,address.url.host)
}

tls版本 以及其他信息

raw socket = tcp 连接  socke : https t= ssl socket

isHealthy 是否为健康连接 :判断socket 是否关闭, http2的心跳验证

查找一个可用且健康的连接 findHealthConnection 

newCodec 如果是http2 返回http2的编码解码 否则返回http1的,writeUtf-8 ,http1 明文传输,http2通过stream 

ExChange ---ConnectChain

CallServerInterceptor---- 发送请求和读取结果,通过stream 和 io ,然后缓存,通过gzip 解压缩,

stetho 网络调试库 fackbook



 

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

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

相关文章

springBoot 配置文件引入 redis 的相关参数说明

在Spring Boot应用中使用Redis作为缓存或数据存储时&#xff0c;可以在应用的配置文件中配置相关参数。下面是常用的Redis配置参数及其说明&#xff1a; spring.redis.host: Redis服务器主机地址&#xff0c;默认为localhost。spring.redis.port: Redis服务器端口&#xff0c;…

Wlan——STA上线流程与802.11MAC帧讲解

目录 802.11MAC帧基本概念 802.11帧结构 802.11MAC帧的分类 管理帧 控制帧 数据帧 STA接入无线网络流程 信号扫描—管理帧 链路认证—管理帧 用户关联—管理帧 用户上线 802.11MAC帧基本概念 802.11协议在802家族中的角色位置 其中802.3标准属于以太网的一种帧格式…

HPE服务器常见报错信息以及解决方案

General controller issues 常规控制器问题 Controllers are no longer redundant 控制器不再冗余 HPE Dynamic Smart Array B140i drives are not found when RAID mode is disabled 禁用 RAID 模式时找不到 HPE 动态智能阵列 B140i 驱动器 Data located on drives accessed i…

React前端开发架构:构建现代响应式用户界面

在当今的Web应用开发中&#xff0c;React已经成为最受欢迎的前端框架之一。它的出色性能、灵活性和组件化开发模式&#xff0c;使得它成为构建现代响应式用户界面的理想选择。在这篇文章中&#xff0c;我们将探讨React前端开发架构的核心概念和最佳实践&#xff0c;以帮助您构建…

[Machine Learning] 损失函数和优化过程

文章目录 机器学习算法的目的是找到一个假设来拟合数据。这通过一个优化过程来实现&#xff0c;该过程从预定义的 hypothesis class&#xff08;假设类&#xff09;中选择一个假设来最小化目标函数。具体地说&#xff0c;我们想找到 arg min ⁡ h ∈ H 1 n ∑ i 1 n ℓ ( X i…

Revit 3D高效处理:cad exchanger sdk 3.21 Crack

3D 格式概述&#xff1a;Revit Revit 已成为寻求高效、准确的建筑信息建模的专业人士的首选解决方案。在这篇引人入胜的功能概述中了解 Revit 的特性和影响。 什么是Revit&#xff1f; Autodesk Revit 是一款流行的 CAD 软件&#xff0c;重点关注 BIM&#xff0c;被建筑师、工…

Oracle DG复制中断:RMAN-03015和RMAN-06094

Oracle DG复制中断&#xff1a;RMAN-03015和RMAN-06094 背景与错误信息主库备份数据文件备库恢复数据文件DG Broker创建DG更新备库数据文件名解决归档日志不传输 ⭐️DB_UNIQUE_NAME 主库&#xff1a;ORCLDB_0备库&#xff1a;ORCLDB_1 背景与错误信息 通过RMAN DUPLICATE搭建…

uniapp scroll-view横向滚动无效,scroll-view子元素flex布局不生效

要素排查&#xff1a; 1.scroll-x属性需要开启&#xff0c;官方类型是Boolean&#xff0c;实际字符串也行。 2scroll-view标签需要给予一个固定宽度&#xff0c;可以是百分百也可以是固定宽度或者100vw。 3.子元素需要设置display: inline-block&#xff08;行内块元素&#x…

时序预测 | MATLAB实现SO-CNN-BiLSTM蛇群算法优化卷积双向长短期记忆神经网络时间序列预测

时序预测 | MATLAB实现SO-CNN-BiLSTM蛇群算法优化卷积双向长短期记忆神经网络时间序列预测 目录 时序预测 | MATLAB实现SO-CNN-BiLSTM蛇群算法优化卷积双向长短期记忆神经网络时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 时序预测 | MATLAB实现SO-CNN-BiL…

ffmpeg从摄像头获取视频流

使用FFmpeg获取本地摄像头设备 ffmpeg -list_devices true -f dshow -i dummy ffmpeg -f dshow -i video"e2eSoft iVCam" -vcodec libx264 -preset:v ultrafast -tune:v zerolatency -rtsp_transport tcp -f rtsp rtsp://127.0.0.1/test ffmpeg -f dshow -i video&qu…

如何让智能搜索引擎更灵活、更高效?

随着互联网的发展和普及&#xff0c;搜索引擎已经成为人们获取信息、解决问题的主要工具之一。 然而&#xff0c;传统的搜索引擎在面对大数据时&#xff0c;往往存在着搜索效率低下、搜索结果精准度不够等问题。 为了解决这些问题&#xff0c;越来越多的企业开始采用智能搜索技…

Python Pandas 处理Excel数据 制图

目录 1、饼状图 2、条形统计图 1、饼状图 import pandas as pd import matplotlib.pyplot as plt import numpy as np #from matplotlib.ticker import MaxNLocator # 解决中文乱码 plt.rcParams[font.sans-serif][SimHei] plt.rcParams[font.sans-serif]Microsoft YaHei …

Git学习笔记

Git学习笔记 文章目录 Git学习笔记一、版本控制二、Linux基础命令三、Git的环境配置四、Git的基本理论&#xff08;核心&#xff09;五、Git项目的搭建六、Git文件操作七、使用码云八、IDEA集成git九、Git分支 一、版本控制 什么是版本控制 版本控制&#xff08;Revision contr…

遥感云大数据在灾害、水体与湿地领域典型案例实践及GPT模型

近年来遥感技术得到了突飞猛进的发展&#xff0c;航天、航空、临近空间等多遥感平台不断增加&#xff0c;数据的空间、时间、光谱分辨率不断提高&#xff0c;数据量猛增&#xff0c;遥感数据已经越来越具有大数据特征。遥感大数据的出现为相关研究提供了前所未有的机遇&#xf…

[git]github上传大文件

github客户端最高支持100Mb文件上传&#xff0c;如果要>100M只能用git-lfs&#xff0c;但是测试发现即使用git lfs&#xff0c;我上传2.5GB也不行&#xff0c;测试737M文件可以&#xff0c;GitHub 目前 Git LFS的总存储量为1G左右&#xff0c;超过需要付费。(上传失败时&…

git管理代码

理论上改代码前要pull一次&#xff0c;然后在push前在pull一次 改代码前pull一次是为了获取最新的同步&#xff0c;但是coding也是需要时间的&#xff0c;难保敲代码的这段时间没有人动远程仓库的东西&#xff0c;所以在改完代码要push的时候也应该再pull一下看有无冲突&#x…

卷积神经网络——上篇【深度学习】【PyTorch】

文章目录 5、卷积神经网络5.1、卷积5.1.1、理论部分5.1.2、代码实现5.1.3、边缘检测 5.2、填充和步幅5.2.1、理论部分5.2.2、代码实现 5.3、多输入多输出通道5.3.1、理论部分5.3.2、代码实现 5.4、池化层 | 汇聚层5.4.1、理论部分5.4.2、代码实现 5、卷积神经网络 5.1、卷积 …

es入门实战

创建索引 PUT /hotel/ { “mappings”:{ “properties”:{ “title”:{ “type”:“text” }, “city”:{ “type”:“keyword” }, “price”:{ “type”:“double” } } } } 给索引写入数据 POST /hotel/_doc/001 { “title”:“好再来大酒店”, “city”:“东京”, “pri…

Java学习笔记——(21)运算符分类

分类一&#xff1a; 单目运算符&#xff1a;! ~ – -双目运算符&#xff1a;* / % - << >> < < > > ! & ^ | && || * / % - << >> & ^ | >>> ~三目运算符(条件运算符)&#xff1a;? :其他运算符&#xff1…

自然语言处理从入门到应用——LangChain:索引(Indexes)-[文档加载器(Document Loaders)]

分类目录&#xff1a;《自然语言处理从入门到应用》总目录 合并语言模型和我们自己的文本数据是区分它们的一种强大方式&#xff0c;这样做的第一步是将数据加载到“文档”中&#xff0c;文档加载器的作用就是使这个过程变得简单。 LangChain提供了三种文档加载器&#xff1a;…