redux源码阅读总结(一)- createStore.js详细解析与思考

news2024/11/26 23:44:51

redux数据流分析

在阅读redux源码之前,先整理一下redux的数据流,官网的数据流程图如下所示。该图十分清晰明了的展示了redux的数据流:

  1. 点击UI,发起一个存钱的点击事件。
  2. 在点击事件处理函数中,dispatch分发一个action到reducer中。
  3. reducer接收当前的state和dispatch发起的action,经过计算得到一个新的state。
  4. state状态被重新更新到UI上。
    这样就形成了一个完整的数据闭环。当然还有一些小的细节,例如:很多小的reducer会被包含在一个root reducer中;reducer存在于store中;state更新UI的方式是通过subscribe里的监听函数等。

虽然图像很简单,但是绘制这样的图需要较深入的流程理解,值的好好看一下。参考链接: https://redux.js.org/tutorials/fundamentals/part-2-concepts-data-flow
在这里插入图片描述

redux文件结构分析

在此基础上,在github redux 4.x 分支上,可以看到redux的相关代码。main分支代码已被全部更新为ts,介于我对ts掌握程度不够,所以阅读的是4.x分支的代码,该分支代码仍使用的js编写方式。

源码结构如下图所示,主要文件只有六个:

applyMiddleware.js - 用于中间件应用,也是生成createStore.js文件中enhancer的方法

bindActionCreators.js - 将dispatch和action绑定

combineReducers.js - 将reducer进行combine

compose.js - 封装了reduce函数,该文件用于applyMiddleware.js中逻辑的书写

createStore.js - store相关的一些主要逻辑

index.js - 导出文件

在这里插入图片描述

createStore.js代码结构分析

这里我重要阅读和梳理一个createStore.js的源码,剩余的代码下次再分析。之所以先梳理createStore.js,是因为它是相对比较重要的一个文件,文件内的一些函数的定义刚好可以对应上面展示的数据流程图。

首先还是从宏观上观察一下createStore.js内的结构,如下图所示:

ensureCanMutateNextListeners() - 将currentListeners数组浅复制给nextListeners

getState() - 获取当前的state

subscribe(listener) - 订阅函数,用于接收监听函数

dispatch(action) - 分发action

replaceReducer(nextReducer) - 更换reducer

observable() - 非相关业务代码,不重要,想研究可以根据官方注释跳转到该链接研究https://github.com/tc39/proposal-observable

在这里插入图片描述

createStore.js代码内容分析

宏观上对文件结构和代码结构进行分析之后,有个整体的概念,再深入阅读代码,会使代码阅读的逻辑更为清晰。

下面我通过在源码中添加中文注释的方式对代码进行解释和分析。另外源代码中的英文注释也写的很清楚,对于英文注释说明的内容我就不额外翻译了。

import $$observable from './utils/symbol-observable'

import ActionTypes from './utils/actionTypes'
import isPlainObject from './utils/isPlainObject'
import { kindOf } from './utils/kindOf'

/**
 * @deprecated
 *
 * **We recommend using the `configureStore` method
 * of the `@reduxjs/toolkit` package**, which replaces `createStore`.
 *
 * Redux Toolkit is our recommended approach for writing Redux logic today,
 * including store setup, reducers, data fetching, and more.
 *
 * **For more details, please read this Redux docs page:**
 * **https://redux.js.org/introduction/why-rtk-is-redux-today**
 *
 * `configureStore` from Redux Toolkit is an improved version of `createStore` that
 * simplifies setup and helps avoid common bugs.
 *
 * You should not be using the `redux` core package by itself today, except for learning purposes.
 * The `createStore` method from the core `redux` package will not be removed, but we encourage
 * all users to migrate to using Redux Toolkit for all Redux code.
 *
 * If you want to use `createStore` without this visual deprecation warning, use
 * the `legacy_createStore` import instead:
 *
 * `import { legacy_createStore as createStore} from 'redux'`
 *
 */
// 开头的文档注释反复强调了不推荐直接使用createStore,推荐使用@reduxjs/toolkit包
// 如果非要用createStore,也建议使用import { legacy_createStore as createStore} from 'redux'的方式
export function createStore(reducer, preloadedState, enhancer) {
  // 只能传入一个enhancer函数
  if (
    (typeof preloadedState === 'function' && typeof enhancer === 'function') ||
    (typeof enhancer === 'function' && typeof arguments[3] === 'function')
  ) {
    throw new Error(
      'It looks like you are passing several store enhancers to ' +
        'createStore(). This is not supported. Instead, compose them ' +
        'together to a single function. See https://redux.js.org/tutorials/fundamentals/part-4-store#creating-a-store-with-enhancers for an example.'
    )
  }

  // 只传reducer和一个函数的情况下,该函数是enhancer
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }

  // 传入的enhancer只能是函数
  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error(
        `Expected the enhancer to be a function. Instead, received: '${kindOf(
          enhancer
        )}'`
      )
    }

    return enhancer(createStore)(reducer, preloadedState)
  }

  // reducer必须是函数
  if (typeof reducer !== 'function') {
    throw new Error(
      `Expected the root reducer to be a function. Instead, received: '${kindOf(
        reducer
      )}'`
    )
  }

  // 变量顾名思义都很容易理解
  let currentReducer = reducer
  let currentState = preloadedState
  let currentListeners = []
  let nextListeners = currentListeners	// 思考点:为什么定义两个监听数组?(后文和注释中有解释)
  let isDispatching = false

  /**
   * This makes a shallow copy of currentListeners so we can use
   * nextListeners as a temporary list while dispatching.
   *
   * This prevents any bugs around consumers calling
   * subscribe/unsubscribe in the middle of a dispatch.
   */
  // 这段代码很好理解,就是一个简单的复制过程
  // 思考点:为什么要nextListeners === currentListeners判断后才赋值?(后文有解释)
  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

  /**
   * Reads the state tree managed by the store.
   *
   * @returns {any} The current state tree of your application.
   */
  // 注意这里是直接把currentState暴露了
  function getState() {
    if (isDispatching) {
      throw new Error(
        'You may not call store.getState() while the reducer is executing. ' +
          'The reducer has already received the state as an argument. ' +
          'Pass it down from the top reducer instead of reading it from the store.'
      )
    }

    return currentState
  }

  /**
   * Adds a change listener. It will be called any time an action is dispatched,
   * and some part of the state tree may potentially have changed. You may then
   * call `getState()` to read the current state tree inside the callback.
   *
   * You may call `dispatch()` from a change listener, with the following
   * caveats:
   *
   * 1. The subscriptions are snapshotted just before every `dispatch()` call.
   * If you subscribe or unsubscribe while the listeners are being invoked, this
   * will not have any effect on the `dispatch()` that is currently in progress.
   * However, the next `dispatch()` call, whether nested or not, will use a more
   * recent snapshot of the subscription list.
   *
   * 2. The listener should not expect to see all state changes, as the state
   * might have been updated multiple times during a nested `dispatch()` before
   * the listener is called. It is, however, guaranteed that all subscribers
   * registered before the `dispatch()` started will be called with the latest
   * state by the time it exits.
   *
   * @param {Function} listener A callback to be invoked on every dispatch.
   * @returns {Function} A function to remove this change listener.
   */
  // 上面那段英文注释真的写的特别的好,建议仔细阅读,它告诉我们在dispatch时,我们应用的是当前的subscribe快照
  // 如果调用dispatch期间改变了listener数组,其实是不会影响当前使用的快照的,而是应用到下一次dispatch中,因为下一次dispatch之前会产生一个新的快照,这个新快照就会包含改变的数组
  // 这其实也是为什么应用两个listener数组的原因,即保持当前dispatch进行中时遍历的listener数组的不变性
  function subscribe(listener) {
    if (typeof listener !== 'function') {
      throw new Error(
        `Expected the listener to be a function. Instead, received: '${kindOf(
          listener
        )}'`
      )
    }

    if (isDispatching) {
      throw new Error(
        'You may not call store.subscribe() while the reducer is executing. ' +
          'If you would like to be notified after the store has been updated, subscribe from a ' +
          'component and invoke store.getState() in the callback to access the latest state. ' +
          'See https://redux.js.org/api/store#subscribelistener for more details.'
      )
    }

    let isSubscribed = true

    // 增加监听函数
    ensureCanMutateNextListeners()
    nextListeners.push(listener)

    // 返回解除监听函数
    return function unsubscribe() {
      // 防止多次解除订阅
      if (!isSubscribed) {
        return
      }

      if (isDispatching) {
        throw new Error(
          'You may not unsubscribe from a store listener while the reducer is executing. ' +
            'See https://redux.js.org/api/store#subscribelistener for more details.'
        )
      }

      isSubscribed = false

      // 解除当前监听的函数
      ensureCanMutateNextListeners()
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
      // 思考点:这里数组为什么置null?(后文有解释)
      currentListeners = null
    }
  }

  /**
   * Dispatches an action. It is the only way to trigger a state change.
   *
   * The `reducer` function, used to create the store, will be called with the
   * current state tree and the given `action`. Its return value will
   * be considered the **next** state of the tree, and the change listeners
   * will be notified.
   *
   * The base implementation only supports plain object actions. If you want to
   * dispatch a Promise, an Observable, a thunk, or something else, you need to
   * wrap your store creating function into the corresponding middleware. For
   * example, see the documentation for the `redux-thunk` package. Even the
   * middleware will eventually dispatch plain object actions using this method.
   *
   * @param {Object} action A plain object representing “what changed”. It is
   * a good idea to keep actions serializable so you can record and replay user
   * sessions, or use the time travelling `redux-devtools`. An action must have
   * a `type` property which may not be `undefined`. It is a good idea to use
   * string constants for action types.
   *
   * @returns {Object} For convenience, the same action object you dispatched.
   *
   * Note that, if you use a custom middleware, it may wrap `dispatch()` to
   * return something else (for example, a Promise you can await).
   */
  function dispatch(action) {
    // 限制了action的类型
    if (!isPlainObject(action)) {
      throw new Error(
        `Actions must be plain objects. Instead, the actual type was: '${kindOf(
          action
        )}'. You may need to add middleware to your store setup to handle dispatching other values, such as 'redux-thunk' to handle dispatching functions. See https://redux.js.org/tutorials/fundamentals/part-4-store#middleware and https://redux.js.org/tutorials/fundamentals/part-6-async-logic#using-the-redux-thunk-middleware for examples.`
      )
    }

    // action必须要有type
    if (typeof action.type === 'undefined') {
      throw new Error(
        'Actions may not have an undefined "type" property. You may have misspelled an action type string constant.'
      )
    }

    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }

	// 触发reducer
    try {
      isDispatching = true
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }

	// subscribe时我们操作的都是nextListeners,因此要先把最新的nextListeners赋值给currentListeners,然后再遍历,这就是获取最新快照的过程
	// 由于我们遍历的是currentListeners,而subscribe时操作的是nextListeners,就这保证了dispatch时当前遍历数组的不变性,例如在遍历过程中如果又触发了额外的subscribe操作或者unsubscribe操作,也只会改变nextListeners,而这种改变不会影响当前的监听函数调用过程,只会影响下次dispatch时监听函数调用过程
    // 使用for循环遍历性能会远高于forEach
    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    return action
  }

  /**
   * Replaces the reducer currently used by the store to calculate the state.
   *
   * You might need this if your app implements code splitting and you want to
   * load some of the reducers dynamically. You might also need this if you
   * implement a hot reloading mechanism for Redux.
   *
   * @param {Function} nextReducer The reducer for the store to use instead.
   * @returns {void}
   */
  // 简单易懂的代码没什么好说的
  function replaceReducer(nextReducer) {
    if (typeof nextReducer !== 'function') {
      throw new Error(
        `Expected the nextReducer to be a function. Instead, received: '${kindOf(
          nextReducer
        )}`
      )
    }

    currentReducer = nextReducer

    // This action has a similiar effect to ActionTypes.INIT.
    // Any reducers that existed in both the new and old rootReducer
    // will receive the previous state. This effectively populates
    // the new state tree with any relevant data from the old one.
    dispatch({ type: ActionTypes.REPLACE })
  }

  /**
   * Interoperability point for observable/reactive libraries.
   * @returns {observable} A minimal observable of state changes.
   * For more information, see the observable proposal:
   * https://github.com/tc39/proposal-observable
   */
  // 非主业务代码,感兴趣的去看注释里的github链接,这里不深入研究
  function observable() {
    const outerSubscribe = subscribe
    return {
      /**
       * The minimal observable subscription method.
       * @param {Object} observer Any object that can be used as an observer.
       * The observer object should have a `next` method.
       * @returns {subscription} An object with an `unsubscribe` method that can
       * be used to unsubscribe the observable from the store, and prevent further
       * emission of values from the observable.
       */
      subscribe(observer) {
        if (typeof observer !== 'object' || observer === null) {
          throw new TypeError(
            `Expected the observer to be an object. Instead, received: '${kindOf(
              observer
            )}'`
          )
        }

        function observeState() {
          if (observer.next) {
            observer.next(getState())
          }
        }

        observeState()
        const unsubscribe = outerSubscribe(observeState)
        return { unsubscribe }
      },

      [$$observable]() {
        return this
      },
    }
  }

  // When a store is created, an "INIT" action is dispatched so that every
  // reducer returns their initial state. This effectively populates
  // the initial state tree.
  // 初始化一个state树,否则的话拿到的是一个undefine
  dispatch({ type: ActionTypes.INIT })

  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable,
  }
}

/**
 * Creates a Redux store that holds the state tree.
 *
 * **We recommend using `configureStore` from the
 * `@reduxjs/toolkit` package**, which replaces `createStore`:
 * **https://redux.js.org/introduction/why-rtk-is-redux-today**
 *
 * The only way to change the data in the store is to call `dispatch()` on it.
 *
 * There should only be a single store in your app. To specify how different
 * parts of the state tree respond to actions, you may combine several reducers
 * into a single reducer function by using `combineReducers`.
 *
 * @param {Function} reducer A function that returns the next state tree, given
 * the current state tree and the action to handle.
 *
 * @param {any} [preloadedState] The initial state. You may optionally specify it
 * to hydrate the state from the server in universal apps, or to restore a
 * previously serialized user session.
 * If you use `combineReducers` to produce the root reducer function, this must be
 * an object with the same shape as `combineReducers` keys.
 *
 * @param {Function} [enhancer] The store enhancer. You may optionally specify it
 * to enhance the store with third-party capabilities such as middleware,
 * time travel, persistence, etc. The only store enhancer that ships with Redux
 * is `applyMiddleware()`.
 *
 * @returns {Store} A Redux store that lets you read the state, dispatch actions
 * and subscribe to changes.
 */
// 这里的export呼应了文档开头推荐使用import { legacy_createStore as createStore} from 'redux'引入模块
export const legacy_createStore = createStore

在注释里我对代码进行了详细清晰的讲解,如有错误或者不理解的部分可以联系我一起探讨。

下面是几个我对代码的思考:

  1. gitState拿到的是什么?能否直接修改state?
  2. 为什么用到了两个listener数组?
  3. 为什么ensureCanMutateNextListeners函数中需要先进行nextListeners === currentListeners判断,然后才赋值nextListeners = currentListeners.slice()?
  4. 为什么dispatch触发reducer时不允许getState(),不允许subscribe(listener),不允许unsubscribe(),不允许dispatch(action)?
  5. 为什么unsubscribe时,currentListeners数组置null?

以下是我对上面几个问题思考的一些结果:

  1. getState可以直接拿到currentState,因此理论上来讲其实可以直接修改当前的state,但是如果直接修改了该值,就没有后续操作了。而redux之所以希望我们使用dispatch去修改state,是因为使用dispatch修改state之后,会依次调用订阅的listener函数,这样就能实时响应识别state的改变。
  2. 对于该问题我在注释中也进行了较详细的阐述。结合subscribe()和dispatch()注释中我的分析,总的来说就是保证在dispatch时,只会触发当前dispatch之前我们订阅的listener,而在dispatch过程中改变的监听数组会应用在下一次的dispatch中,保证当前监听数组的不变性,得到预期的结果。
  3. 该问题和上一个问题是相关的,因为ensureCanMutateNextListeners只在subscribe函数和unsubscribe函数中使用了,之所以要先判断相等才赋值是因为如果dispatch遍历currentListeners过程中又额外触发了subscribe或者unsubscribe操作,那么此时如果直接赋值就会导致更新的nextListeners又恢复到和原来的currentListeners一样了。举例说明:例如在dispatch过程中,遍历currentListeners时,又额外触发了两次subscribe函数,那么第一次进入subscribe时,在往nextListeners里push listener之前,调用ensureCanMutateNextListener,此时nextListeners和currentListeners是一样的,此时判不判断相等都不影响。而当我们把新的listener push到nextListeners之后,此时nextListeners就和currentListeners不同了,相当于比currentListeners多了一个listener。那么在这次subscribe结束后,第二次进入subscribe时,依旧是在往nextListeners里push listener之前,调用ensureCanMutateNextListeners函数时,此时两个数组不相等,如果在这种情况下进行赋值,就会将nextListeners恢复为第一次进入subscribe之前的状态。导致的结果就是,我们预期获得多两个listener的nextListeners数组,而没有了相等才赋值的判断之后,我们获得了只增加了最后一个listener的nextListeners数组,丢了一个想添加的listener。而unsubscribe甚至有currentListeners=null的操作,如果多次unsubscribe还强行赋值,nextListeners也跟着变成null了。
  4. reducer会造成currentState的改变,而getState()、subscribe(listener)、unsubscribe()和dispatch(action)会涉及到对状态的改变或者识别,我认为强行操作会导致结果无法预期。
  5. 这里之所以会有这个问题,是因为在unsubscribe的时候,redux会删除nextListeners数组中想要解除订阅的listener,而在dispatch时,会有种这样一个赋值操作currentListeners = nextListeners,全程currentListeners的改变都只来源于nextListeners的赋值,那么为什么还要多此一举在unsubscribe时将currentListeners=null呢?这里涉及到js的gc问题,由redux代码可知,currentListeners = nextListeners,即currentListeners永远来自nextListeners,而只有在调用subscribe或者unsubscribe,且满足nextListeners === currentListeners时,才有nextListeners = currentListeners.slice()。unsubscribe时执行currentListeners=null这会导致一种情况,就是当我们调用一个unsubscribe的后,就会产生nextListeners !== currentListeners的情况,而这种情况下,将绝对不会再用到currentListeners的值!无论之后我们是订阅、解除订阅还是执行dispatch(action),都不会再用到currentListeners的值,那么这种情况下对于currentListeners指向的那些存在堆里的值就完全没必要存储了,及时置null相当于手动让其进行垃圾回收。

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

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

相关文章

AE 3D粒子插件trapcode particular 新版本

Trapcode Particular for Mac是目前AE系列的插件中最火爆最流行的一款三维粒子插件&#xff0c;是属于Red Giant Trapcode Suite&#xff08;红巨人粒子特效套装插件&#xff09;中的一款粒子插件。该软件提供了多达一百余种粒子效果供用户使用&#xff0c;可以产生各种各样的自…

【算法基础:搜索与图论】3.4 求最短路算法(Dijkstrabellman-fordspfaFloyd)

文章目录 求最短路算法总览Dijkstra朴素 Dijkstra 算法&#xff08;⭐原理讲解&#xff01;⭐重要&#xff01;&#xff09;&#xff08;用于稠密图&#xff09;例题&#xff1a;849. Dijkstra求最短路 I代码1——使用邻接表代码2——使用邻接矩阵 补充&#xff1a;稠密图和稀疏…

(Chrome Ext)谷歌扩展程序-谷歌插件渗透测试方法记录

文章目录 前言一、本地获取谷歌插件/扩展程序源码二、工具化信息收集总结 前言 在工作岗位变更之后&#xff0c;越来越多“奇奇怪怪”的东西要去渗透和测试&#xff0c;在我之前干安服的时候&#xff0c;最多的就是测一下web&#xff0c;极少情况下测测app&#xff0c;但是现在…

信息与通信工程学科面试准备——信息论与编码|保研推免面试题

目录 第一章 绪论 1 信息的概念 1.1 香农对信息的定义 1.2 信息与消息之间的关系&#xff1f; 2 信息的性质 3 信息的分类 4 信息论与编码研究的主要内容 (1)狭义信息论 (2)一般信息论 (3)广义信息论 5 信息论与编码的发展和应用 6 通信系统模型构成 (1)信源 (2)…

微信小程序-----input数据双向绑定

简介&#xff1a; 这里介绍两种获取的方式&#xff1a; 1、用户每输入一个字节就获取一个字节 2、用户全部输入结束了之后&#xff0c;再一起获取整个input输入框的值 注意&#xff1a;第二种方式会比较节省内存资源 第一种方式: 原理&#xff1a;我们使用bindinput事件来获取…

2023年Q2京东洗衣机行业品牌销售排行榜(京东销售数据分析)

鲸参谋电商大数据2023年Q2京东平台“洗衣机”品类完整销售数据榜单出炉&#xff01; 根据鲸参谋电商数据显示&#xff0c;今年Q2京东平台上洗衣机行业的销量超过380万&#xff0c;环比下降19%&#xff0c;同比上升约2%&#xff1b;行业销售额达63亿&#xff0c;环比下降约14%&a…

idea+springboot+jpa+maven+jquery+mysql进销存管理系统源码

ideaspringbootjpamavenjquerymysql进销存管理系统 一、系统介绍1.环境配置 二、系统展示1. 管理员登录2.首页3.采购订单4.收货入库5. 采购退货6. 商品入库7. 商品出库8. 库存查询9.商品移库10.库存盘点11.销售订单12.发货出库13.销售退货14.商品查询15. 供应商查询16.客户查询…

3.输出printf() 与 输入scanf()

输入 与 输出 1.printf()2.scanf() 1.printf() 1.1 引用条件 printf()函数使用之前必须要引入<stdio.h>这个头文件 1.2 关于换行 printf()在打印的时候不会自动换行&#xff0c;所以各位需要在输出文本的末尾添加转义字符\n&#xff0c;也就是换行符&#xff0c;不然调…

通过Docker启动DB2,并在Spring Boot整合DB2(Druid连接池)

1 简介 DB2是IBM的一款优秀的关系型数据库&#xff0c;简单学习一下。 2 Docker安装DB2 为了快速启动&#xff0c;直接使用Docker来安装DB2。先下载镜像如下&#xff1a; docker pull ibmcom/db2 # or docker pull ibmcom/db2:11.5.0.0 启动数据库如下&#xff1a; docker …

【树上操作】定长裁剪 CF1833 G

Problem - G - Codeforces 题意&#xff1a; 给定一棵n个节点的树&#xff0c;请你减掉一些边&#xff0c;使得剪掉后的每个树只有三个节点&#xff0c; 如果可以&#xff0c;第一行返回减掉边的数量&#xff0c;第二行返回减掉边的编号&#xff1b;如果无解&#xff0c;输出…

opencv 图像腐蚀膨胀 erode dilate

#include "iostream" #include "opencv2/opencv.hpp" using namespace std; using namespace cv;int main() {Mat img, dst, dstbin, distancetransform,rel, rel2;img imread("m3.jpg");//转为灰度图cvtColor(img, dst, COLOR_BGR2GRAY);//二…

springcloudAlibaba之nacos集群部署和nginx负载均衡

1.环境准备 nacos server安装包&#xff1a;https://github.com/alibaba/nacos nginx安装包&#xff1a;https://nginx.org/en/download.html 2、nacos配置 将下载好的nacos-server的压缩包解压好以后&#xff0c;复制出N份&#xff08;这里取决于你集群的数量&#xff09;&…

AC自动机(java)

AC自动机 AC自动机介绍代码演示 indexTree AC自动机介绍 AC自动机算法是一种基于Trie树和有限状态机的字符串匹配算法。它在查找字符串时&#xff0c;利用额外的失配指针进行回退&#xff0c;转向其他分支&#xff0c;避免重复匹配前缀&#xff0c;从而提高算法效率。当一个字典…

编译内联导致内存泄漏的问题定位修复

作者&#xff1a;0x264 问题 线上长时间存在一个跟异步 inflate 相关的量级较大的内存泄漏&#xff0c;如下所示&#xff1a; 第一次分析 从内存泄漏粗略看有几个信息&#xff1a; 被泄漏的Activity有很多&#xff0c;所以可能跟某个具体业务的关系不大引用链特别短&#xf…

SkyWalking链路追踪中span全解

基本概念 在SkyWalking链路追踪中&#xff0c;Span&#xff08;跨度&#xff09;是Trace&#xff08;追踪&#xff09;的组成部分之一。Span代表一次调用或操作的单个组件&#xff0c;可以是一个方法调用、一个HTTP请求或者其他类型的操作。 每个Span都包含了一些关键的信息&am…

yaml语法详解

#kv #对空格的严格要求十分高 #注入到我们的配置类中 #普通的keyvalue name: qinjiang#对象 student:name: qingjiangage: 3#行内写法 student1: {name: qinjiang,age: 3}#数组 pets:- cat- dog- pigpet: [cat,dog,pig]yaml可以给实体类赋值 person:name: kuangshenage: 19happ…

css——box-sizing属性

含义 盒子模型由四部分构成&#xff0c;外边距(margin), 边框(border),内边距(padding), 内容content box-sizing 就是指定盒子的大小和结构的。 box-sizing: content-box; //默认值 内容真正宽度 设置的宽度box-sizing: border-box; // 内容真正宽度width 设置的width- 左右p…

LabVIEW可重入VI,VI模板和动态VI之间的差异

LabVIEW可重入VI&#xff0c;VI模板和动态VI之间的差异 应该在何时使用可重入VI、模板VI和动态调用VI&#xff1f;这三种类型之间有什么区别&#xff1f; 可重入VI 当想要同时运行同一VI的多个实例时&#xff0c;将使用可重入VI。当VI不可重入时&#xff0c;VI只有一个数据空…

浏览器对跨域请求携带Cookie的方法

文章目录 一、前后端协商配置1.1 前端页面搭建1.2后端服务器搭建 二、配置允许跨域浏览器三、Chrome浏览器安装ModHeader插件 企业开发时会分开发环境、测试环境以及生产环境&#xff0c;但是有的企业开发只有真正发布到线上的生产环境的流程才会严格配置&#xff0c;有的项目开…

C++线性技巧,STL

例题1&#xff1a;字串计算 样例输入 10101 样例输出 0 2 01 2 1 3 10 2 101 2 直接上代码&#xff1a; #include<iostream> #include<string> #include<map> using namespace std; map<string,int>mp;//用map存储每一个子串出现的次数 string str…