深度解析React性能优化API
性能优化一直是前端领域讨论的一个热门问题,但在平时沟通及code review过程中发现很多人对于React中性能优化理解很模糊,讲不清楚组件什么时候更新,为什么会更新,关于React性能优化的文章虽然比较多,但大多数都是在罗列一些优化的点,本文将以React底层更新过程为基础,层层递进,将性能优化相关的用法、原理串联起来,让读者真正理解为什么需要性能优化以及如何使用。
更新流程
React使得前端开发者不再跟DOM打交道,只需要控制组件及其状态来完成应用开发。React在这背后做的最主要的工作就是保持组件状态与用户界面的一致性:将组件状态构建成用于描述用户界面的UI Tree(或者叫Virtual Dom)并反应的浏览器中,这也是React更新的一个最宏观的过程。
我们向下一层,看一下当更新发生时的具体过程。
如下图所示,假设用户蓝色的结点中发生了一次onClick,对应组件触发了相应setState,React就会重新开始构建整颗UI Tree。因为构建都是从根节点发生的,所以会先调用getRootForUpdateFiber
找到根节点,并触发ScheduleUpdateOnFiber
进入Scheduler
进行调度开始更新过程;更新主要分为两个阶段,Render Phase
和Commit Phase
,其中
-
Render阶段就是根据每个组件中的状态构建出一个新的UI Tree,也叫WorkInProgress Tree,并为每一个结点对应的操作打上EffectTag,即更新、删除、新增。全部构建完成后就进入下一阶段。
-
Commit阶段就是将构建好的WIP Tree反应到浏览器中,即React为我们自动进行相应的dom操作,保持UI一致性。
当提交完成后,实际上一次更新就完成了,用户可以进行交互,可能又会触发新的更新,从而循环这个过程。
上图的这个过程,实际上就是React更新的核心Loop。开发者或者是React团队本身所做的所有关于性能优化的事情,本质上都是通过加速这个loop的过程,从而实现用户界面的高响应。比如Scheduler中Time Slice机制实际上就是减少每次循环的工作量,当然这个过程对我们开发者是无感的,不再详细展开,我们今天重点的关注是作为开发者,如何加速这一过程,也就是实现性能优化。
如何加速
这里我们再向下一层,看一下Render阶段到底是如何构建UI Tree的。因为结点会组成一个树结构,所以构建的过程本身是一个遍历的过程,每个阶段结点都会经历beginWork
和completeWork
,大致遍历过程如下图所示
遍历过程并非本篇重点,过程有简化
默认优化策略
然而对于一个实际的应用来说,涉及节点众多,而实际一个用户操作,往往只会影响个别的节点,如果挨个遍历,全部重新构建一遍显然会有些浪费,当然React自己也知道这件事,具体是怎么优化的呢?
我们干脆再深入一层,直接到源码看看React在beginWork
里都做了些什么
为方便读者理解,源码有一定程度简化
// ReactFiberBeginWork.new.js
function beginWork(current, workInPrgress, renderLanes) {// 检查props和context是否发生改变if (oldProps !== newProps || hasLegacyContextChanged()) { didReceiveUpdate = true;} else { // props或者context都未改变的时候,检查是否有pending中的updateconst hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(current,renderLanes,);if (!hasScheduledUpdateOrContext) {didReceiveUpdate = false;// 当前Fiber可以复用,进入bailout流程return attemptEarlyBailoutIfNoScheduledUpdate(current, workInProgress, renderLanes);}}// 无法bailout,真正进入beginWork流程
}
代码中关于这部分可以看到名称叫LegacyContext
,所以这里的context实际上是指旧版的context,新版的context是否发生变化实际上会反应到pending update
中,但这里直接理解成context并不影响本文内容,因此不再展开
可以看到,React在真正进入beginWork构建之前,实际上会有一层逻辑判断,这就是React自带的性能优化策略。对于那些props
state(pending update)
context
没有发生变化的结点会进入bailout
流程,中文翻译过来为“急救”,可以简单理解成这个结点没有发生变化,还可以抢救一下,没必要让他重生。
我们再进一步看看attemptEarlyBailoutIfNoScheduledUpdate
里面做了什么,核心其实下面这个方法,主要是看子节点state
是否发生变化,如果没有,直接返回null
,代表当前节点的子树都可以bailout
,也就是跳过构建
function bailoutOnAlreadyFinishedWork( current: Fiber | null,workInProgress: Fiber,renderLanes: Lanes, ): Fiber | null {// 检查下children是否有pending workif (!includesSomeLane(renderLanes, workInProgress.childLanes)) {return null}// 当前结点无任务工作要做,但是子树有,克隆子结点,继续Reconciler的过程cloneChildFibers(current, workInProgress);return workInProgress.child;
}
🤔 为什么看子树有没有发生变化时为什么没有比较props
到这里我们就搞明白了React自带的性能优化策略,简单概括下如下
接下来我们看一个非常简单的demo,思考一下当点击update按钮时,Child
中的"child render"会被打印吗
const Child = () => {console.log('child render');return <div>I am child</div>;
};export default function App() {const [count, setCount] = useState(0);return (<><button onClick={() => setCount(count + 1)}>update</button><Child /></>);
}
根据我们刚才的分析,当Child
组件进入beginWork
流程时,因为props state context都没有变,应该会被跳过,即走Bailout
流程才对。但是实际上可以看到每次点击,Child
组件都会重新渲染,这是为什么呢
让我们带着疑问,直接到源码里面调试一下
可以看到,Child
组件确实没有满足React默认优化策略的条件,而不满足的原因是props发生了变化。
我们知道,JSX
本质上是React.createElement
的语法糖,所以调用Child
的地方(App组件内),本质上是调用React.createElement
,传递的props为一个空对象,App两次渲染传递给子组件的props并不相等 {} !== {}
export default function App() {const [count, setCount] = useState(0);return (<><button onClick={() => setCount(count + 1)}>update</button>{React.createElement('Child', {})}<Child /></>);
}
手动跳过构建
既然Child
与状态count
无关,理论上来讲肯定是可以被跳过的重新渲染呢,怎么做呢?这里就要引入第一个性能优化API React.memo
,如下面的例子通过memo
包裹来组件之后,发现这时候点击后,Child
不会再重新渲染了。
const Child = memo(() => {console.log('child render');return <div>I am child</div>;
});export default function App() {const [count, setCount] = useState(0);return (<><button onClick={() => setCount(count + 1)}>update</button><Child /></>);
}
我们直接进入源码中看看memo
到底做了什么
类似效果的API还有
PureComponent
、ShouldComponentUpdate
,由于现在基本都已经拥抱函数式组件,此处以memo为例
function updateSimpleMemoComponent( current: Fiber | null,workInProgress: Fiber,Component: any,nextProps: any,renderLanes: Lanes, ): null | Fiber {const prevProps = current.memoizedProps;// 使用浅比较代替了全等比较if (shallowEqual(prevProps, nextProps)) { didReceiveUpdate = false;}// 检查是否有pending的更新if (!checkScheduledUpdateOrContext(current, renderLanes)) {return bailoutOnAlreadyFinishedWork(current,workInProgress,renderLanes,);}// 无法bailout, 即进入render阶段return updateFunctionComponent(current,workInProgress,Component,nextProps,renderLanes,);
}
从源码中我们可以看到,当自动bailout
不满足时,memo
实际上提供了另一条路径进入bailout
,而要求跟默认优化策略非常类似,唯一的区别是第10行,用shallowEqual
替换了原先的props全等比较。而对于我们上面demo的情况,由于新旧props都是空对象,因此通过浅比较就满足了优化策略,从而跳过了构建过程。
🤔 那是不是给每个组件都包裹一下memo,来尽可能的命中bailout?
第三条路
上面的问题显然答案是否定的,因为如果是的话,那React为什么不直接默认给所有组件都包裹一下,还需要开发者手动来不是多此一举么?
不这么做的原因是,memo
并不是免费的,shallowEqual
会去挨个遍历props并进行比较,这个成本可要比全等大多了,那有没有办法不使用memo
又能命中bailout
的第三条路呢,这里给大家介绍两种方式
Move state down - 状态下放
还是拿刚才那个例子,我们可以看到App组件更新的原因是内部的count
发生了变化,而Child
虽然跟count
没有任何关系,但是由于同属于一个组件,也被带着重新渲染了
const Child = () => {console.log('child render');return <div>I am child</div>;
};export default function App() {const [count, setCount] = useState(0);return (<><button onClick={() => setCount(count + 1)}>update</button><Child /></>);
}
我们稍微改造一下,把count
及其相关的逻辑抽离到另外一个子组件Counter
中
const Child = () => {console.log('child render');return <div>I am child</div>;
};const Counter = () => {const [count, setCount] = useState(0);return <button onClick={() => setCount(count + 1)}>update</button>;
}export default function App() {return (<><Counter /><Child /></>);
}
可以实际测试一下上面的代码,虽然只是简单调整了一下组件结构,Child
居然不再重新渲染了
原因就在于变化的内容现在在Counter
内部,App组件会由于满足了默认的性能优化策略不再重新渲染,因此传递给Child
的props
就不会发生变化,从而Child
也就满足了默认的性能优化策略,这种逻辑是具有传递性的,即如果Child
还有子组件,也会因为Child
没有重渲染,继续满足默认性能优化策略而都被跳过。
大家是否有听说过这样的建议要避免大组件,将组件的粒度控制要尽可能的细。
在React中,组件本质上就是函数,函数有单一职责原则,组件也适用 。 想必通过上面的例子,我们对于这句话能有更深的理解,这么做不仅仅是便于维护,而是会直接影响到性能优化。
Lift content up - 内容提升
当然也有状态下放不适用的情况,比如但是当遇到下面这个case,Child
的外层div中也用到了count
,如果将Child
全部拆分过去到Counter
中,实际Counter
变化,Child
还是会重新渲染,这时候就可以用另外一种方法 内容提升
const Child = () => {console.log('child render');return <div>I am child</div>;
};export default function App() {const [count, setCount] = useState(0);return (<div classname={count}><button onClick={() => setCount(count + 1)}>update</button><Child /></div>);
}
简而言之就是虽然将Child
拆分到Counter
中,但是Child
得渲染不依赖任何Counter
的内容,可以将Child
提升到App
中,以children
的方式进行传递
const Child = () => {console.log('child render');return <div>I am child</div>;
};const Counter = ({children}) => {const [count, setCount] = useState(0);return (<div classname={count}><button onClick={() => setCount(count + 1)}>update</button>{children} </div> );
}export default function App() {return (<Counter><Child /></Counter>);
}
通过上面这种方式,其实Child
也不会重新渲染,调试一下看看Counter
的props,发现children
实际上就是Child
,能看到两次渲染Child
内容并没有变
原因是Child
现在是作为Counter
组件的props,props的内容是在App
组件中传递的,因此可以理解成Child
依然是直接依赖于App
组件,由于App
没有重新渲染,因此Child
也满足了默认的性能优化策略。
跳过局部构建
通过上面的内容,我们知道了每次更新时会去重新构建组件树,当然我们也可以通过命中bailout
来避免组件重新构建,但组件内确实发生了状态变化就无法bailout
,这时候就会进入一个组件的render阶段
function updateSimpleMemoComponent( current: Fiber | null,workInProgress: Fiber,Component: any,nextProps: any,renderLanes: Lanes, ): null | Fiber {// ...// 无法bailout, 即进入render阶段return updateFunctionComponent(current,workInProgress,Component,nextProps,renderLanes,);
}
跟随代码继续走我们可以看到render阶段实际上就是调用组件的渲染方法
export function renderWithHooks(current: Fiber | null,workInProgress: Fiber,Component: (p: Props, arg: SecondArg) => any,props: Props,secondArg: SecondArg,nextRenderLanes: Lanes,
): any {// ...let children = Component(props, secondArg);// ...
}
到这里就要轮到两个性能优化的API出场了: useMemo
useCallback
,背后的逻辑是如果一个组件必须重新绘制时,我们可以尽可能加速这个绘制的过程。
useMemo
的基本用法相关资料很多这里不再赘述,重点说明下使用useMemo
的两种场景
- 避免耗时的逻辑重复计算
如下面的例子所示,heavyCalc
是一个耗时比较严重的逻辑运算,我们期望与state2无关的reRender能够跳过这次运算,通过useMemo
包裹heavyCalc
能够实现只有当state2变化时才重新计算val。
const Comp = () => {const [state1, setState1] = useState();const [state2, setState2] = useState();const val = heavyCalc(state2);return (// ...);
}
- 防止子组件的缓存击穿
list由于是Child
组件的props,而每次Comp
更新都会生成一个全新的对象,这会导致Child
即使使用了性能优化策略,如使用React.memo
也无法命中bailout
,而通过userMemo
返回list,可以实现每次渲染都返回同一个值。
const Comp = () => {// ...// 通过缓存这个值来避免子组件memo失效const list = []return (// ...<Child list={list} />);
}
useMemo
是如何做到的呢,我们直接看看源码:
function updateMemo(nextCreate, deps) {// ...const prevDeps: Array<mixed> | null = prevState[1];// 对于数组中的每一项全等比较if (areHookInputsEqual(nextDeps, prevDeps)) {return prevState[0];}// 调用函数返回新创建的值const nextValue = nextCreate();hook.memoizedState = [nextValue, nextDeps];return nextValue;
}
实现比较简单,最关键的就是第四行,做的事情就是将依赖项中的每一个挨个和上一次渲染时传递的依赖项进行全等比较,如果都没有发生变化,直接将存储的缓存值进行返回,否则重新计算。
那我们再看看顺便看看useCallback
的实现
function updateCallback(callback, deps) {// ...const nextDeps = deps === undefined ? null : deps;const prevState = hook.memoizedState;// 对于数组中的每一项全等比较const prevDeps: Array<mixed> | null = prevState[1];if (areHookInputsEqual(nextDeps, prevDeps)) {return prevState[0];}// 直接返回callbackhook.memoizedState = [callback, nextDeps];return callback;
}
可以看出,两者唯一的区别在于一个存储函数的本身(useCallback
) 一个存储函数返回的值(useMemo
)
从实现上我们其实能看出useCallback
中不包含任何运算逻辑,因此使用场景要比useMemo
更少,只适用于第二种场景,即防止子组件的缓存击穿
,通过将父组件中声明的回调函数进行缓存来保持子组件props的不变。
const Comp = () => {// ...// 通过缓存这个值来避免子组件memo失效const handleClick = useCallback(() => {}, []);return (// ...<Child onClick={handleClick} />);
}
总结和建议
回顾
先简单回顾一下整体的更新逻辑:当用户触发更新操作时,
-
React会首先尝试应用默认的性能优化策略尝试对组件进行
bailout
,这一阶段我们在编码过程中可以尽可能的应用状态下放
和内容提升
满足默认性能优化策略条件提高bailout
命中率 -
默认
bailout
不满足时,我们也可以使用像PureComponent
ShouldComponentUpdate
React.memo
来降低匹配条件,再次进行bailout
-
当必不可少的需要重新渲染时,我们可以使用
useMemo
useCallback
来减少渲染的时间
当全部渲染完成后,实际上就构建好了一颗新的UI Tree,React会去对比新旧两颗Tree来找出需要对哪些dom结点进行何种操作,这个过程也被称为reconcile
,大家比较熟悉的diff算法
就是发生在这里,但是作为开发者这一做的事情很有限,我们唯一可以做的事是在通过循环添加组件时,注意为组件添加有效的key
,来让React进行diff的时候少做一些比较,减少不必要的dom操作。
建议
了解这些性能优化的手段后,迫不及待在自己的组件中整改一顿?这边的建议是注意不要过度优化
为什么这么说呢
-
一方面我们能够发现
useMemo
和useCallback
本身也不是免费的,需要开辟空间去存储依赖并且每次都要去比较 -
另外,从实际的开发体验上,在组件中大量使用
useMemo
和useCallback
,会导致代码比较臃肿可读性变差,对开发者心智要求比较高,维护依赖项
我们要知道React本身的性能优化已经做的很好了,正确的逻辑应该是当实际性能问题发生时,我们需要去定位发生问题的组件,再应用上文提到的性能优化方法进行优化。
最后出现性能问题时的组件定位,这边推荐一下React Profiler,相关文档比较清楚这边不再赘述,简单分享些小tip,可以勾一下这两选项,可以方便的看到组件重新渲染的原因和操作时发生重新渲染的组件
以上就是本次分享的全部内容,读者如果觉得有帮助可以顺手点个赞,觉得哪里有不理解的也可以评论区留言讨论:)
最后
最近还整理一份JavaScript与ES的笔记,一共25个重要的知识点,对每个知识点都进行了讲解和分析。能帮你快速掌握JavaScript与ES的相关知识,提升工作效率。
有需要的小伙伴,可以点击下方卡片领取,无偿分享
相关文章:
深度解析React性能优化API
性能优化一直是前端领域讨论的一个热门问题,但在平时沟通及code review过程中发现很多人对于React中性能优化理解很模糊,讲不清楚组件什么时候更新,为什么会更新,关于React性能优化的文章虽然比较多,但大多数都是在罗列…...
算法刷题打卡第91天:统计一个圆中点的数目
统计一个圆中点的数目 难度:中等 给你一个数组 points ,其中 points[i] [xi, yi] ,表示第 i 个点在二维平面上的坐标。多个点可能会有 相同 的坐标。 同时给你一个数组 queries ,其中 queries[j] [xj, yj, rj] ,表…...
sentinel持久化方案
一.sentinel规则推送原理 1.原有内存规则存储原理 (1)dashborad中请求到服务器后,在controller中通过http把规则直接推送给client,client接收后把规则放入内存; 2.持久化推送规则原理 ![在这里插入代码片](https://img-blog.csdnimg.cn/1…...
软件项目进度安排与跟踪:关键路径的计算
在一个软件项目中,管理人员需要按时了解项目进度,制定项目计划,同时需要及时发现所遇到的问题,然后和团队成员制定解决方案,确保整个计划可以顺利的进行,因此项目进度安排与跟踪是项目管理中的一个重要环节…...
mac m2 处理器 iterm2 sz rz 出错/无限重试
mac m2 处理器 iterm2 sz rz 出错/无限重试 1、背景 apple m 系列处理器安装的 homebrew 跟 intel 处理器略有不同,其中安装目录的区别: m 系列处理器安装目录为 /usr/local/bin/homebrewintel 处理器安装目录为 /opt/homebrew 其中 m 系列处理器安装…...
Mysql 与 磁盘交互的过程
从之前的Mysql架构可以了解到,Mysql 客户端不是直接和磁盘打交道,我们在客户端输入的sql语句会被发送给服务端,服务端对sql语句进行解析、缓存等操作,然后再交由存储引擎去读写磁盘。这其实是从 C/S 的角度去了解Mysql。 站在OS的…...
Spring Cloud Gateway集成Nacos实现负载均衡
💡Nacas可以用于实现Spring Cloud Gateway中网关动态路由功能,也可以基于Nacos来实现对后端服务的负载均衡,前者利用Nacos配置中心功能,后者利用Nacos服务注册功能。接下来我们来看下Gateway集成Nacos实现负载均衡的架构图一. 环境…...
Excel图表教程_编程入门自学教程_菜鸟教程-免费教程分享
教程简介 Excel图表初学者教程 - 从简单和简单的步骤学习Excel图表从基本概念到高级概念,包括简介,创建图表,类型,柱形图,折线图,饼图,圆环图,条形图,面积图,…...
2023最新的接口自动化测试面试题
1、请结合你熟悉的项目,介绍一下你是怎么做测试的? -首先要自己熟悉项目,熟悉项目的需求、项目组织架构、项目研发接口等 -功能 接口 自动化 性能 是怎么处理的? -第一步: 进行需求分析,需求评审&#…...
AcWing语法基础课笔记 第一章 C++入门及简单的顺序结构
第一章 C入门及简单的顺序结构 编程是一种控制计算机的方式,和我们平时双击打开文件、关机、重启没有任何区别。 ———闫学灿 C中常用的变量类型 和所占字节大小 输出变量地址符: 软件环境 作业的评测与提交 在线练习地址:www.acwing.com …...
【并发编程】【2】进程与线程
并发编程 2.进程与线程 2.1 进程与线程 进程 程序由指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至 CPU,数据加载至内存。在 指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管…...
MySQL获取当前时间的各种方式
1 获取当前完整时间1.1 now()函数select now();输出:2023-02-15 10:46:171.2 sysdate()函数select sysdate();输出:2023-02-15 10:47:131.3 current_timestamp或current_timestamp()current_timestamp和current_timestamp()函数的效果是一样的,只不过一个是关键字&a…...
redis持久化之AOF(Append Only File)及其总结
1.是什么? 以日志的形式来记录每个写操作,将redis执行过的所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的…...
LeetCode 刷题之队列
5. 队列 队列(queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。 队列是一种先进先出的(First In First Out)的线性表,简称 FIFO。允许插入的一端为队尾,允许删除的一端为队…...
互联网摸鱼日报(2023-02-15)
互联网摸鱼日报(2023-02-15) InfoQ 热门话题 ChatGPT火爆全球后,OpenAI CEO称“它很酷,但却是个糟糕的产品” 微软发言人证实旗下LinkedIn平台开始裁员 Akamai 推出 Akamai Connected Cloud 和全新云计算服务 AI赋能元宇宙游戏…...
聊聊外包和远程项目的敏捷管理(合辑共7篇)
这是鼎叔的第五十一篇原创文章。行业大牛和刚毕业的小白,都可以进来聊聊。欢迎关注本专栏和微信公众号《敏捷测试转型》,大量原创思考文章陆续推出。第四个合辑完工了,咱们介绍了外包管理或远程项目如何敏捷交付,满足管理层预期。…...
2023-2-15 刷题情况
检查「好数组」 题目描述 给你一个正整数数组 nums,你需要从中任选一些子集,然后将子集中每一个数乘以一个 任意整数,并求出他们的和。 假如该和结果为 1,那么原数组就是一个「好数组」,则返回 True;否则…...
汉诺塔递归算法精讲
文章目录前言一、汉诺塔是个啥?二、手动解法三、解法抽象四、递归解法五、总结前言 递归算法是计算机算法中的基础算法,也是非常重要的算法,从某种程度上讲,它有一点儿AI的影子。人脑是可以完成递归思路的,但是对不起…...
vue的$nextTick的原理
参考:https://cloud.tencent.com/developer/article/1633546 总结一下:就是$nextTick将回调函数放到微任务或者宏任务当中以延迟它地执行顺序;(总结的也比较懒👶) 重要的是理解源码中它的三个参数的意思&a…...
前端学习第一阶段——第五章CSS(下)
5-9 浮动 08-浮动导读 09-传统网页布局三种方式 10-为什么需要浮动 11-什么是浮动 12-浮动特性-脱标 13-浮动特性-浮动元素一行显示 14-浮动特性-浮动元素具有行内块特性 15-浮动元素经常搭配标准流的父元素 16-浮动布局练习1 <!DOCTYPE html> <html lang"en&quo…...
基于django搭建简单的个人博客
文章目录第一步、在Ubuntu中安装虚拟环境并进入第二步、安装blog所需要的包,在requirements.txt中安装mysqlclient可能会报错,输入下列命令后在安装即可成功第三步、创建好数据库,把测试数据导入第四步、修改DjangoBlog包中 settings中数据库…...
JVM解释器与JIT编译器如何并存?
[1] JVM解释器 JVM设计的初衷仅仅只是为了满足Java程序实现跨平台特性,因此避免采用静态编译的方式直接生成本地机器指令,从而诞生了实现解释器在运行时采用逐行解释字节码的执行程序。 解释器真正意义上所承担的角色就是一个运行时“翻译者”࿰…...
生产者消费者模型
目录 一、生产者消费者模型的概念 二、生产者消费者模型的特点 三、生产者消费者模型优点 四、基于BlockingQueue的生产者消费者模型 4.1 基本认识 4.2 模拟实现 一、生产者消费者模型的概念 生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题 生产者和…...
mysql索引--实例
学生表:Student (Sno, Sname, Ssex , Sage, Sdept) 学号,姓名,性别,年龄,所在系 Sno为主键 课程表:Course (Cno, Cname,) 课程号,课程名 Cno为主键 学生选课表:SC (Sno, Cno, Score)…...
浅聊一下,可中断锁(ReentrantLock)
前言 今天早上上厕所,上的我痔疮犯了,屁股一坐下去就感觉一根针在刺我,得的是外痔,之前还坚持用痔疮膏来着,但是感觉涂药的那个姿势以及位置我实在无法忍受,就把它给断了,到头来还是屁股糟了罪&…...
关于Arcgis林业数据处理的62个常用技巧
一、计算面积 ( 可以帮我们计算小班面积 ) 添加 AREA 字段,然后右键点击字段列,然后点击 CALCULATE VALUES; ---> 选择 ADVANCED --》把下面的代码输入,然后在最下面 处写 OUTPUT 点击 OK 就 OK 了。 Dim Outp…...
一些NLP术语
一些NLP术语pre-training(预训练)fine-tuning(微调)下游任务Few-shot Learning(少样本学习)Prompt?(自然语言提示信息)二级标题三级标题pre-training(预训练&…...
Session详解,学习 Session对象一篇文章就够了
目录 1 Session概述 2 Session原理 3 Session使用 3.1 获取Session 3.2 Session保存数据 3.3 Session获取数据 3.4 Session移除数据 4 Session与Request应用区别 4.1 Session和request存储数据 4.2 获取session和request中的值 4.3 session和request区别效果 5 Sess…...
Java——不同的子序列
题目链接 leetcode在线oj题——不同的子序列 题目描述 给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数。 字符串的一个 子序列 是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新…...
Git 基本操作之Git GUI界面和git命令行如何选择
1. 为啥推荐使用git命令行 我发现公司有很多的同事都喜欢使用git的GUI界面工具,喜欢鼠标点点点就完成了代码的提交,这种方式的确是比较简单便捷,但是却存在风险。先上一个事故给大家醒醒脑。 VScode Git 界面操作引发的惨案 上面的惨案是VS…...
东营哪里做网站/seo工程师招聘
这里采用的是Edushi接口(详情请看http://www.edushi.com/api/freeAPI.htm) 目前(2007.03.07)只支持以下城市:杭州(hz) 上海(sh) 青岛(qd) 深圳(sz) 西安(xian) 长沙(changsha)成都(chengdu) 广州(guangzhou) 嘉兴(jiaxing) 佛山(fs) 温州(wz)上虞(sy) 余姚(yy) 丽水(lishui)1…...
凡总创业网站/百度快照入口官网
/*** 根据日期获取当天是周几* param datetime 日期* return 周几*/public static String dateToWeek(String datetime) {SimpleDateFormat sdf new SimpleDateFormat("yyyy-MM-dd");String[] weekDays {"星期日", "星期一", "星期二&quo…...
炽乐清网站建设/网页制作的软件有哪些
日志按天记录,自动生成当天的记录文件 日志分级存储(info,error) Springboot有自带日志,但只是类似于System.out.printl();的简单输出: //增加日志 private final Logger log LoggerFactory.g…...
什么自己做网站吗/代做关键词收录排名
1、JSP页面 <a href"javascript:void();" id"PicName" > 选择图片</a> <input type"file" id"fileToUpload" name"fileToUpload" ><!--需要设置name属性值,不然上传不了 --> <butto…...
上海网站建设企/网站优化方案范文
电脑蓝屏原因01软件兼容性问题引起电脑蓝屏刚安装的系统,若驱动与硬件不兼容可能会导致蓝屏现象,建议安装硬件赠送光盘中的驱动(要与所用操作系统相符),且驱动没必要追求最新!长时间使用电脑都没有出现蓝屏现象,而某天…...
怎样做模具钢网站/seo范畴有哪些
注意 presto中都是用单引号: select dt from tb1 where dt date 2021-02-01;...