当前位置: 首页 > news >正文

组件是怎样写的(1):虚拟列表-VirtualList

本篇文章是《组件是怎样写的》系列文章的第一篇,该系列文章主要说一下各组件实现的具体逻辑,组件种类取自 element-plus 和 antd 组件库。

每个组件都会有 vue 和 react 两种实现方式,可以点击 https://hhk-png.github.io/components-show/ 查看,项目的 github 地址为:https://github.com/hhk-png/components-show。

简介

本片文章讲解一下 虚拟列表 的实现,代码主要来源于https://juejin.cn/post/7232856799170805820,然后在其基础上做了一些优化。

如果在浏览器中渲染有大量数据数据的列表,比如 100 万条,并且设置滚动,在打开这个页面的时候,浏览器所承担的渲染压力将会急速放大,浏览器将会崩溃。虚拟列表应对该种情况的处理方式是将列表渲染时的计算量从渲染进程中转换到了 js 中,从而降低浏览器的渲染压力,使这种数量的列表可以正常渲染。

在用户端,用户对序列表做的操作主要是使用鼠标的滚轮滑动列表,或者通过拖拽滚动条的方式,两者都会反映到元素的 scroll 事件上。因此,在实现虚拟列表时,主要是根据滑动距离挑选出特定的需要展示的列表项,每次滑动都执行该操作。

本文中,虚拟列表分为定高列表与不定高列表,仅考虑上下滑动的情况。在挑选需要展示的列表项时,要先获取到列表项的起始位置与结束位置,然后将这一部分的元素截取出来。列表项的数量是手动设定的,对于定高列表,由于元素高度固定,所以元素总的高度也是固定的,选择起始与结束位置时的时间复杂度和数组一样是 O(1)。对于不定高列表,因为元素高度不确定,所以会在内部维护一个元素高度的缓存,需要根据该缓存得到要展示元素的起始坐标,元素高度通过 ResizeObserver 监听元素获取。

固定高度的虚拟列表 React 实现

本小节讲一下 react 版本的定高虚拟列表的实现。虚拟列表和列表项的 props interface 如下:

export interface FixedRow {index: number // idstyle: React.CSSProperties
}export interface FixedSizeList {height: number // 虚拟列表所占用的高度width: number // 虚拟列表所占用的宽度itemSize: number // 列表项高度itemCount: number // 列表项数量children: React.ComponentType<FixedRow> // 被虚拟化的列表项
}

其中 FixedSizeList 为虚拟列表的 props interface,其中各变量的解释以注释的形式给出。children 为要虚拟的列表项,该值对应一个组件,其参数为 FixedRow

组件的主要代码如下,省略了 getCurrentChildren 的内容。

export const FixedSizeList: React.FC<FixedSizeList> = (props) => {const { height, width, itemCount, itemSize, children: Child } = propsconst [scrollOffset, setScrollOffset] = useState<number>(0)const cacheRef = useRef<Map<number, React.ReactNode>>(new Map())const containerStyle: CSSProperties = {position: 'relative',width,height,overflow: 'auto',}const contentStyle: CSSProperties = {height: itemSize * itemCount,width: '100%',}const getCurrentChildren = () => {/* ....省略 */}const scrollHandle = (event: React.UIEvent<HTMLDivElement>): void => {const { scrollTop } = event.currentTargetsetScrollOffset(scrollTop)}return (<div style={containerStyle} onScroll={scrollHandle}><div style={contentStyle}>{getCurrentChildren()}</div></div>)
}

html 的结构主要分为三个部分,最外层的 container 用于设置虚拟列表的宽高,对应的 style 为containerStyle,其中的 width 和 height 是从 props 中取出,然后设置了position: absoluteoverflow:auto,这两个属性是为了模拟滚动条,并且可以监听到 scroll 事件。第二部分是夹在中间的 content,目的是撑开外面的 container,使可以显示出滚动条。在 contentStyle 中,宽度设置为了 100%,高度为列表项的数量乘以列表项的高度。最后一部分是虚拟化的列表项,通过 getCurrentChildren 函数获得。

FixedSizeList 内部维护了一个 scrollOffset 状态,onScroll 事件绑定在了 container 元素上,用户触发滚动、触发 scroll 事件之后,会通过 setScrollOffset 重新指定 scrollOffset。状态更新后,react 会重新渲染该组件,也会重新执行 getCurrentChildren 函数,getCurrentChildren 的返回值由 scrollOffset 状态计算,所以在状态更新之后就能够看到预期的列表中的元素更新。getCurrentChildren 的实现如下:

const getCurrentChildren = () => {const startIndex = Math.floor(scrollOffset / itemSize)const finalStartIndex = Math.max(0, startIndex - 2)const numVisible = Math.ceil(height / itemSize)const endIndex = Math.min(itemCount, startIndex + numVisible + 2)const items = []for (let i = finalStartIndex; i < endIndex; i++) {if (cacheRef.current.has(i)) {items.push(cacheRef.current.get(i))} else {const itemStyle: React.CSSProperties = {position: 'absolute',height: itemSize,width: '100%',top: itemSize * i,}const item = <Child key={i} index={i} style={itemStyle}></Child>cacheRef.current.set(i, item)items.push(item)}}return items
}

getCurrentChildren 的目的是为了获取在当前的 scrollOffset 下,后面需要展示的几个连续的列表项,在这之后的列表项与 scrollOffset 之前的不予展示。起始索引为 Math.floor(scrollOffset / itemSize),中间要展示的列表项的个数为 Math.ceil(height / itemSize),结束位置的索引为 startIndex + numVisible,在起始位置之上加上要展示的项数。此处为了方式滑动时造成的空白区域,又将截取区间向外扩展了 2。

上述代码中的 items 为要收集的列表项数组。每个列表项为一个组件,通过 position:absolute 的方式定位到展示区域,该子元素相对于前面讲的最外层的 container 进行定位,top 设置为 itemSize * i 。子元素的索引作为子元素的 id,通过 cacheRef 缓存。

FixedSizeList 的使用方式如下:

const FixedRow: React.FC<FixedRow> = ({ index, style }) => {const backgroundColorClass = index % 2 === 0 ? 'bg-blue-100' : 'bg-white'return (<divclassName={`w-full ${backgroundColorClass} flex items-center justify-center`}style={{ ...style }}>Row {index}</div>)
}// ...;<FixedSizeList height={300} width={300} itemSize={50} itemCount={1000}>{FixedRow}
</FixedSizeList>

不固定高度的虚拟列表 React 实现

不定高的虚拟列表的实现逻辑与定高列表相似,但因为列表项的高度不固定,要做很多额外的处理。DynamicSizeList 的部分代码如下:

interface MeasuredData {size: numberoffset: number
}type MeasuredDataMap = Record<number, MeasuredData>export interface DynamicRow {index: number
}export interface DynamicSizeListProps {height: numberwidth: numberitemCount: numberitemEstimatedSize?: numberchildren: React.ComponentType<DynamicRow>
}export const DynamicSizeList: React.FC<DynamicSizeListProps> = (props) => {const {height,width,itemCount,itemEstimatedSize = 50,children: Child,} = propsconst [scrollOffset, setScrollOffset] = useState(0)// 为了在接收到列表项高度发生变化时,触发组件强制更新const [, setState] = useState({})// 缓存const measuredDataMap = useRef<MeasuredDataMap>({})const lastMeasuredItemIndex = useRef<number>(-1)const containerStyle: CSSProperties = {position: 'relative',width,height,overflow: 'auto',}const contentStyle: CSSProperties = {height: estimateHeight(itemEstimatedSize,itemCount,lastMeasuredItemIndex,measuredDataMap),width: '100%',}const sizeChangeHandle = (index: number, domNode: HTMLElement) => {/* ....省略 */}const getCurrentChildren = () => {/* ....省略 */}const scrollHandle = (event: React.UIEvent<HTMLDivElement>) => {const { scrollTop } = event.currentTargetsetScrollOffset(scrollTop)}return (<div style={containerStyle} onScroll={scrollHandle}><div style={contentStyle}>{getCurrentChildren()}</div></div>)
}

代码的整体结构与之前的定高列表几乎相同,在组件初始化时,组件并不知道列表项的高度,为了弥补这一缺陷,设定了一个默认的预测高度 itemEstimatedSize,在组件挂载后再将真实的列表项高度反映到缓存中。

上述代码中的 measuredDataMap 用于缓存列表项的数据,其键为列表项的索引,值为一个包含项偏移与高度的对象。lastMeasuredItemIndex 为最后一个测量到的元素的索引。这两个缓存项也可以直接放到组件外面,但如果这样做的话,如果页面上有多个 DynamicSizeList 组件实例,就会导致缓存污染。如果虚拟列表实例频繁挂载/卸载,就会导致缓存的项数只增不减,缓存也不会被释放,造成内存泄漏。因此将两者放到组件内部,并使用 useRef 包裹,这样可以确保每个实例使用的是不同的缓存,且缓存可以通过垃圾回收释放。

此处用于撑起 container 的 content 中间层的高度通过 estimateHeight 函数计算,在计算时,如果没有获取到某个元素的高度,就会使用默认高度来填补其空缺,其实现如下所示:

const estimateHeight = (defaultItemSize: number = 50,itemCount: number,lastMeasuredItemIndex: React.RefObject<number>,measuredDataMap: React.RefObject<MeasuredDataMap>
): number => {let measuredHeight: number = 0if (lastMeasuredItemIndex.current >= 0) {const lastMeasuredItem =measuredDataMap.current[lastMeasuredItemIndex.current]measuredHeight = lastMeasuredItem.offset + lastMeasuredItem.size}const unMeasutedItemsCount = itemCount - lastMeasuredItemIndex.current - 1return measuredHeight + unMeasutedItemsCount * defaultItemSize
}

lastMeasuredItemIndex 之前的元素的高度是已知的,截至到该元素,所有元素的累计高度为该元素的偏移 offset 加上其对应的 size。lastMeasuredItemIndex 后面的元素高度没有获得,数量为 itemCount - lastMeasuredItemIndex.current - 1,因此使用默认高度 defaultItemSize 计算。lastMeasuredItemIndex 的值小于 0,代表还没有初始化,因此会将所有元素的高度都看作为 defaultItemSize。此种方式计算的总高度是一个近似的大小,随着用户滑动列表,由该函数计算的总高度也会逐渐逼近真实的总高度。也因为这种处理方式,在用户拖动滚动条时,会出现鼠标与滚动条脱离的情况。

由于 lastMeasuredItemIndex 和 measuredDataMap 用 useRef 包裹,放在组件当中,所以在分离逻辑的时候要以参数的形式传递,才可以实现状态的共享。

下面介绍一下 getCurrentChildren 函数:

const getCurrentChildren = () => {const [startIndex, endIndex] = getRangeToRender(props,scrollOffset,lastMeasuredItemIndex,measuredDataMap)const items: ReactNode[] = []for (let i = startIndex; i <= endIndex; i++) {const item = getItemLayoutdata(props,i,lastMeasuredItemIndex,measuredDataMap)const itemStyle: CSSProperties = {position: 'absolute',height: item.size,width: '100%',top: item.offset,}items.push(<ListItemkey={i}index={i}style={itemStyle}ChildComp={Child}onSizeChange={sizeChangeHandle}/>)}return items
}

函数中,获取截取区间的逻辑被抽象为了 getRangeToRender 函数,并且由于获取列表项的几何属性时需要处理缓存问题,该操作也被抽象为了 getItemLayoutdata 函数,列表项 style 的处理与定高列表几乎相同。

不定高虚拟列表使用 ResizeObserver 来获取元素的真实高度,通过在要显示的列表项之外包一层 ListItem 组件来实现。ListItem 组件中,在列表项的组件挂载后,通过 sizeChangeHandle 回调来更新列表项几何属性的缓存,然后触发组件强制更新。ListItem 组件如下:

interface ListItemProps {index: numberstyle: React.CSSPropertiesChildComp: React.ComponentType<{ index: number }>onSizeChange: (index: number, domNode: HTMLElement) => void
}const ListItem: React.FC<ListItemProps> = React.memo(({ index, style, ChildComp, onSizeChange }) => {const domRef = useRef<HTMLDivElement>(null)useEffect(() => {if (!domRef.current) returnconst domNode = domRef.current.firstChild as HTMLElementconst resizeObserver = new ResizeObserver(() => {onSizeChange(index, domNode)})resizeObserver.observe(domNode)return () => {resizeObserver.unobserve(domNode)}}, [index, onSizeChange])return (<div style={style} ref={domRef}><ChildComp key={index} index={index} /></div>)},(prevProps, nextProps) =>prevProps.index === nextProps.index &&prevProps.style.top === nextProps.style.top &&prevProps.style.height === nextProps.style.height
)const sizeChangeHandle = (index: number, domNode: HTMLElement) => {const height = domNode.offsetHeightif (measuredDataMap.current[index]?.size !== height) {measuredDataMap.current[index].size = heightlet offset = measuredDataMap.current[index].offset + heightfor (let i = index + 1; i <= lastMeasuredItemIndex.current; i++) {const layoutData = measuredDataMap.current[i]layoutData.offset = offsetoffset += layoutData.size}setState({})}
}

ListItem 外面添加了一层 React.memo 缓存,设置为在 props 的 index 等属性改变后进行缓存的更新。在 ResizeObserver 检测到组件长宽发生变化后,就会调用 onSizeChange 回调更新元素高度。

在 sizeChangeHandle 函数中,在接收到更新后的元素高度后,会首先更新对应缓存中元素的高度,然后依此更新该位置之后元素的 offset,因为 index 位置元素高度的变化只会影响到该元素之后所有元素的 offset。更新完成之后通过更新之前定义的一个空状态触发组件的强制更新,即 setState({})

getItemLayoutdata 函数用于获取元素的几何属性,首先通过与 lastMeasuredItemIndex 判断,查看 index 位置的元素是否已经获取到,如果是,则直接返回结果。在 index 位置的元素的几何属性没有被初始化时,则从 lastMeasuredItemIndex 开始更新这之间元素的几何属性缓存,元素的 size,也就是高度,被初始化为默认的值 itemEstimatedSize。之后将 lastMeasuredItemIndex 调整为 index,返回结果。直到元素挂载后,通过 sizeChangeHandle 才能获取到真实值,更新到视图上。

const getItemLayoutdata = (props: DynamicSizeListProps,index: number,lastMeasuredItemIndex: React.RefObject<number>,measuredDataMap: React.RefObject<MeasuredDataMap>
): MeasuredData => {const { itemEstimatedSize = 50 } = propsif (index > lastMeasuredItemIndex.current) {let offset = 0if (lastMeasuredItemIndex.current >= 0) {const lastItem = measuredDataMap.current[lastMeasuredItemIndex.current]offset += lastItem.offset + lastItem.size}for (let i = lastMeasuredItemIndex.current + 1; i <= index; i++) {measuredDataMap.current[i] = { size: itemEstimatedSize, offset }offset += itemEstimatedSize}lastMeasuredItemIndex.current = index}return measuredDataMap.current[index]
}

获取当前 scrollOffset 下所需要展示的列表项的 getRangeToRender 函数如下所示,其中又分为 getStartIndex 和 getEndIndex,在其中如果要获取元素的 offset 和 size,都需要经过 getItemLayoutdata。

getStartIndex 是为了获取 scrollOffset 对应位置元素的索引,如果最后一个测量的元素的 offset 大于 scrollOffset,则直接启动二分查找,如果不是,则使用指数查找,该算法在后面介绍。

getEndIndex 依赖于 getStartIndex,其 startIndex 参数为 getStartIndex 的返回值,在函数中 startIndex 对应 startItem。该函数的目的是获取到 startItemoffset + height 位置对应的元素索引。

const getStartIndex = (props: DynamicSizeListProps,scrollOffset: number,lastMeasuredItemIndex: React.RefObject<number>,measuredDataMap: React.RefObject<MeasuredDataMap>
) => {if (scrollOffset === 0) {return 0}if (measuredDataMap.current[lastMeasuredItemIndex.current].offset >=scrollOffset) {return binarySearch(props,0,lastMeasuredItemIndex.current,scrollOffset,lastMeasuredItemIndex,measuredDataMap)}return expSearch(props,Math.max(0, lastMeasuredItemIndex.current),scrollOffset,lastMeasuredItemIndex,measuredDataMap)
}const getEndIndex = (props: DynamicSizeListProps,startIndex: number,lastMeasuredItemIndex: React.RefObject<number>,measuredDataMap: React.RefObject<MeasuredDataMap>
): number => {const { height, itemCount } = propsconst startItem = getItemLayoutdata(props,startIndex,lastMeasuredItemIndex,measuredDataMap)const maxOffset = startItem.offset + heightlet offset = startItem.offset + startItem.sizelet endIndex = startIndexwhile (offset <= maxOffset && endIndex < itemCount - 1) {endIndex++const currentItemLayout = getItemLayoutdata(props,endIndex,lastMeasuredItemIndex,measuredDataMap)offset += currentItemLayout.size}return endIndex
}const getRangeToRender = (props: DynamicSizeListProps,scrollOffset: number,lastMeasuredItemIndex: React.RefObject<number>,measuredDataMap: React.RefObject<MeasuredDataMap>
): [number, number] => {const { itemCount } = propsconst startIndex = getStartIndex(props,scrollOffset,lastMeasuredItemIndex,measuredDataMap)const endIndex = getEndIndex(props,startIndex,lastMeasuredItemIndex,measuredDataMap)return [Math.max(0, startIndex - 2), Math.min(itemCount - 1, endIndex + 2)]
}

getStartIndex 函数中,expSearch 是二分查找的一个变体,但也只能用于有序列表。其首先指数级的扩大查找范围,然后确定了元素在某个范围之后,再在这个范围中进行二分查找。在前面的实现中,expSearch 的第二个参数 index 并不为 0,这可以理解为在进行查找之前设定了一个偏移,如果没设置就会从 0 位置开始查找,如果设置,就会从 index 位置开始查找。

const expSearch = (props: DynamicSizeListProps,index: number,target: number,lastMeasuredItemIndex: React.RefObject<number>,measuredDataMap: React.RefObject<MeasuredDataMap>
) => {const { itemCount } = propslet exp = 1while (index < itemCount &&getItemLayoutdata(props, index, lastMeasuredItemIndex, measuredDataMap).offset < target) {index += expexp *= 2}return binarySearch(props,Math.floor(index / 2),Math.min(index, itemCount - 1),target,lastMeasuredItemIndex,measuredDataMap)
}const binarySearch = (props: DynamicSizeListProps,low: number,high: number,target: number,lastMeasuredItemIndex: React.RefObject<number>,measuredDataMap: React.RefObject<MeasuredDataMap>
) => {while (low <= high) {const mid = low + Math.floor((high - low) / 2)const currentOffset = getItemLayoutdata(props,mid,lastMeasuredItemIndex,measuredDataMap).offsetif (currentOffset === target) {return mid} else if (currentOffset < target) {low = mid + 1} else {high = mid - 1}}return Math.max(low - 1)
}

Vue 版本的虚拟列表实现

vue 版本的虚拟列表使用 SFC 实现,与 tsx 所不相同的是一个文件只能放置一个组件,因此需要将 tsx 中的组件拆到单个文件中。然后 vue 中嵌套组件需要通过 slot 的方式来实现。

vue 版本的具体实现逻辑与之前讲的几乎相同,因为写代码的时间距离写博客相差较远,所以基本上忘了两者的异同,可以点击http://localhost:5173/components-show或者https://github.com/hhk-png/components-show/tree/main/vue-components/src/components-show/VirtualList以查看具体实现,在此不作讲述。

参考资料

https://juejin.cn/post/7232856799170805820

相关文章:

组件是怎样写的(1):虚拟列表-VirtualList

本篇文章是《组件是怎样写的》系列文章的第一篇&#xff0c;该系列文章主要说一下各组件实现的具体逻辑&#xff0c;组件种类取自 element-plus 和 antd 组件库。 每个组件都会有 vue 和 react 两种实现方式&#xff0c;可以点击 https://hhk-png.github.io/components-show/ …...

CGAL 计算直线之间的距离(3D)

文章目录 一、简介二、实现代码三、实现效果一、简介 这里的计算思路很简单: 1、首先将两个三维直线均平移至过原点处,这里两条直线可以构成一个平面normal。 2、如果两个直线平行,那么两条直线之间的距离就转换为直线上一点到另一直线的距离。 3、如果两个直线不平行,则可…...

定期检查滚珠丝杆的频率是多久?

定期检查滚珠丝杆的频率通常是每半年进行一次‌&#xff0c;根据不同的使用环境和设备类型&#xff0c;滚珠丝杆的检查周期有所不同。接下来我们一起看看滚珠丝杆的维护保养方法&#xff1a;‌ 1、‌清洗‌&#xff1a;每隔一段时间对滚珠丝杆进行清洁&#xff0c;将滚珠丝杆拆…...

Spark-SQL连接Hive全攻略

在大数据处理领域&#xff0c;Spark-SQL与Hive的结合能发挥强大的功能。今天就来给大家分享一下Spark-SQL连接Hive的多种方式。 Spark SQL编译时可选择包含Hive支持&#xff0c;这样就能使用Hive表访问、UDF、HQL等特性&#xff0c;而且无需提前安装Hive。其连接方式丰富多样…...

在Ubuntu 18.04下编译OpenJDK 11

在Ubuntu 18.04下编译OpenJDK 11 源码下载地址&#xff1a; 链接: https://pan.baidu.com/s/1QAdu-B6n9KqeBakGlpBS3Q 密码: 8lho Linux下的环境要求 不同版本的jdk会要求在不同版本的Ubuntu下编译&#xff0c;不要用太高版本的Ubuntu或者gcc&#xff0c;特别是gcc&#xf…...

Spring MVC 一个简单的多文件上传

原始代码逐行解释 PostMapping("/uploads") // ① 声明处理POST请求&#xff0c;路径为"/uploads" ResponseBody // ② 直接返回数据到响应体&#xff0c;不进行视图解析 public String uploads(MultipartFile[] files, // …...

FreeRTos学习记录--1.工程创建与源码概述

1.工程创建与源码概述 1.1 工程创建 使用STM32CubeMX&#xff0c;可以手工添加任务、队列、信号量、互斥锁、定时器等等。但是本课程不想严重依赖STM32CubeMX&#xff0c;所以不会使用STM32CubeMX来添加这些对象&#xff0c;而是手写代码来使用这些对象。 使用STM32CubeMX时&…...

Vmware esxi 给现有磁盘增加空间后并扩展系统里磁盘空间

当前EXSI上虚拟机所在的单独数据磁盘空间满了&#xff0c;需要对空间进行扩容&#xff0c;我们先在主机对磁盘容量进行调整&#xff0c;然后在系统里面对磁盘空间进行拓展&#xff0c;这些操作需要保留数据并且不改变现有的磁盘格局。 遵循大致操作流程是&#xff1a; 1.先登录…...

Linux基础学习--linux的文件权限与目录配置

linux的文件权限与目录配置 1.用户与用户组 在Linux中&#xff0c;每个文件都有相当多的属性和权限&#xff0c;其中最重要的概念就是文件的拥有者。 1.1 文件拥有者 Linux是一个多人多任务的系统&#xff0c;常常有多人共用一台主机的情况出现&#xff0c;因此在系统中可以…...

LLM大模型中的基础数学工具—— 约束优化

Q26: 推导拉格朗日乘子法 的 KKT 条件 拉格朗日乘子法与 KKT 条件是啥&#xff1f; 拉格朗日乘子法是解决约束优化问题的利器。比如&#xff0c;想最小化函数 &#xff0c;同时满足约束 &#xff0c;就构造拉格朗日函数 &#xff08; 是乘子&#xff09;。KKT 条件是解这类问…...

涨薪技术|0到1学会性能测试第20课-关联技术

前面的推文我们掌握了性能测试脚本开发参数化技术一系列知识,今天开始给大家分享关联技术知识,后续文章都会系统分享干货,带大家从0到1学会性能测试! 关联是LoadRunner中一个很重要的应用,对于初学者来说也是最容易犯错的地方,但是很遗憾的是,并没有任何特定的错误与关联…...

SpringAI入门示例

AI编程简介 纯Prompt模式 纯Prompt模式是AI编程中最基础的交互架构。用户通过输入自然语言文本&#xff08;即Prompt&#xff09;向AI模型发出指令&#xff0c;模型依据自身预训练所积累的知识和语言理解能力&#xff0c;直接生成相应的文本响应。其工作原理是&#xff0c;用…...

SQL 中 ROLLUP 的使用方法

ROLLUP 是 SQL 中一种分组操作&#xff0c;它生成多个分组集的小计行和总计行&#xff0c;提供层次化的汇总数据。 基本语法 SELECT column1, column2, ..., aggregate_function(column) FROM table GROUP BY ROLLUP (column1, column2, ...); 使用示例 假设有一个销售表 sal…...

Web前端:Overflow属性(超出裁剪属性)

一、什么是 Overflow&#xff1f; 在网页布局中&#xff0c;容器&#xff08;如 <div>、<section> 等&#xff09;通常有固定尺寸&#xff08;如 width 和 height&#xff09;。当容器内的内容&#xff08;文本、图片等&#xff09;超出容器边界时&#xff0c;就会…...

20250421在荣品的PRO-RK3566开发板的Android13下使用io命令控制GPIO

20250421在荣品的PRO-RK3566开发板的Android13下使用io命令控制GPIO 2025/4/21 10:44 【本文只打开了io命令。通过io控制GPIO放到下一篇了】 缘起&#xff1a;需要在荣品的PRO-RK3566开发板的Android13的u-boot中来控制GPIO3A1【配置以太网RTL8211F-CG】。 直接使用GPIO库函数 …...

20250421在荣品的PRO-RK3566开发板的Android13下频繁重启RKNPU fde40000.npu: Adding to iommu gr

20250421在荣品的PRO-RK3566开发板的Android13下频繁重启RKNPU fde40000.npu: Adding to iommu gr 2025/4/21 14:50 缘起&#xff1a;电池没电了&#xff0c;导致荣品的PRO-RK3566的核心板频繁重启。 内核时间4s就重启。100%复现。 PRO-RK3566 Android13启动到这里 复位&#…...

在 8MHz 的时钟电路中挂接电阻,电容

匹配电阻&#xff1a;在晶体振荡电路中&#xff0c;用于匹配晶体和振荡电路的阻抗&#xff0c;确保振荡的稳定性&#xff0c;阻值通常在几十千欧到几百千欧&#xff0c;例如 1MΩ、33KΩ、47KΩ 等。 在一些电子电路中&#xff0c;尤其是涉及到时钟信号的产生和传输时&#xf…...

卸载工具:IObit Uninstaller Pro v14.3.0 中文绿色专业便携版

IObit Uninstaller 是一种功能强大的卸载工具&#xff0c;可帮助您快速方便地从计算机中移除不需要的程序和文件夹。它不仅仅可以从计算机中卸载应用程序&#xff0c;还可以移除它们的卸载残留。可以检测和分类所有已安装的程序&#xff0c;并可以批量卸载&#xff0c;只需一键…...

【目标检测】目标检测综述 目标检测技巧

I. 目标检测中标注的关键作用 A. 目标检测数据标注的定义 目标检测是计算机视觉领域的一项基础且核心的任务&#xff0c;其目标是在图像或视频中准确识别并定位出预定义类别的目标实例 1。数据标注&#xff0c;在目标检测的语境下&#xff0c;指的是为原始视觉数据&#xff0…...

c++基础·move作用,原理

目录 一、代码结构概览 二、逐层解析实现逻辑 1. 模板参数推导 2. 返回类型设计 3. 类型转换逻辑 三、关键特性与设计思想 1. 移动语义的本质 2. 为何必须用 remove_reference 3. 万能引用的兼容性 四、边界场景与注意事项 1. 对 const 对象的处理 2. 返回值优化&a…...

考研系列-计算机网络-第四章、网络层

一、网络层的概述和功能 1.功能概述 2.SDN的基本概念...

服务器在国外国内用户访问慢会影响谷歌排名吗?

谷歌明确将“页面加载速度”和“用户体验”作为排名核心指标&#xff0c;但当服务器物理距离过远时&#xff0c;国内用户动辄3秒以上的加载延迟&#xff0c;可能导致跳出率飙升、爬虫抓取困难等连锁反应。 但盲目将服务器迁回国内&#xff0c;又会面临备案成本、运维门槛等新难…...

iFable,AI角色扮演互动平台,自动生成沉浸式故事游戏

iFable是什么 iFable 是一个以动漫角色为主题的互动角色扮演游戏平台&#xff0c;旨在为用户提供沉浸式的故事冒险体验。平台允许玩家通过简单的创意输入&#xff0c;利用AI技术生成独特的互动故事与游戏体验。iFable 的设计宗旨在于帮助玩家与虚拟角色建立情感连接&#xff0…...

Nginx反向代理用自定义Header参数

【啰嗦两句】 也不知道为啥&#xff0c;我仅仅想在Nginx的反向代理中使用自己定义的“x-api-key”做Header参数&#xff0c;却发现会被忽略&#xff0c;网上搜的资料都是说用“proxy_set_header”&#xff0c;却只愿意介绍最基本的几个参数&#xff0c;你懂的&#xff0c;那些资…...

Spark SQL概述(专业解释+生活化比喻)

专业解释 一、什么是Spark SQL&#xff1f; 一句话定义&#xff1a; Spark SQL是Apache Spark中专门处理结构化数据的模块&#xff0c;可以让你像操作数据库表一样处理数据&#xff0c;支持用SQL查询或编程API&#xff08;DataFrame/DataSet&#xff09;分析数据。 通俗理解…...

LX3-初识是单片机

初识单片机 一 什么是单片机 单片机:单片微型计算机单片机的组成:CPU,RAM(内存),flash(硬盘),总线,时钟,外设…… 二 Coretex-M系列介绍 了解ARM公司与ST公司ARM内核系列: A 高性能应用,如手机,电脑…R 实时性强,如汽车电子,军工…M 超低功耗,如消费电子,家电,医疗器械 三…...

第二章 Logback的架构(一)

Logback的架构 Logback作为一个通用框架&#xff0c;可以应对不同场景的日志记录。目前&#xff0c;Logback 被划分为三个模块&#xff1a;logback-core、logback-classic 和 logback-access。 Logback的core模块为其他两个模块提供基础支持。classic模块扩展了core模块&…...

开发指南:构建结合数字孪生、大语言模型与知识图谱的智能设备日志分析及生产异常预警系统

1. 引言&#xff1a;数字孪生、大语言模型与知识图谱在智能制造中的融合 智能制造和工业4.0的浪潮正在重塑全球制造业格局&#xff0c;其核心在于利用先进的数字技术实现生产过程的实时决策、效率提升、灵活性增强和敏捷性改进。在这一转型过程中&#xff0c;数字孪生&#xf…...

【TeamFlow】4.1 Git使用指南

以下是 Git 在 Windows 系统上的配置和使用指南&#xff0c;包含详细步骤和注意事项&#xff1a; 安装 Git for Windows 下载与安装 前往 Git 官网 下载 Windows 版安装包 双击安装&#xff0c;关键选项建议&#xff1a; 选择 Use Git from Git Bash only&#xff08;推荐&…...

HADOOP 3.4.1安装和搭建(尚硅谷版~)

目录 1.配置模版虚拟机 2.克隆虚拟机 3.在hadoop102安装JDK 4.完全分布式运行模式 1.配置模版虚拟机 1.安装模板虚拟机&#xff0c;IP地址192.168.10.100、主机名称hadoop100、内存2G、硬盘20G&#xff08;有需求的可以配置4G内存&#xff0c;50G硬盘&#xff09; 2.hado…...

通过Docker Desktop配置OpenGauss数据库的方法(详细版+图文结合)

文章目录 通过Docker Desktop配置OpenGauss数据库的方法**一、下载Docker Desktop&#xff0c;并完成安装**docker官网&#xff1a;https://www.docker.com/ **二、下载OpenGauss压缩包**安装包下载链接&#xff1a;https://opengauss.obs.cn-south-1.myhuaweicloud.com/7.0.0-…...

文件有几十个T,需要做rag,用ragFlow能否快速落地呢?

一、RAGFlow的优势 1、RAGFlow处理大规模数据性能&#xff1a; &#xff08;1&#xff09;、RAGFlow支持分布式索引构建&#xff0c;采用分片技术&#xff0c;能够处理TB级数据。 &#xff08;2&#xff09;、它结合向量搜索和关键词搜索&#xff0c;提高检索效率。 &#xf…...

SystemVerilog语法之内建数据类型

简介&#xff1a;SystemVerilog引进了一些新的数据类型&#xff0c;具有以下的优点&#xff1a;&#xff08;1&#xff09;双状态数据类型&#xff0c;更好的性能&#xff0c;更低的内存消耗&#xff1b;&#xff08;2&#xff09;队列、动态和关联数组&#xff0c;减少内存消耗…...

TensorFlow和PyTorch学习原理解析

这里写目录标题 TensorFlow和PyTorch学习&原理解析TensorFlow介绍原理部署适用场景 PyTorch介绍原理部署适用场景 Keras模型格式SavedModelONNX格式 TensorFlow和PyTorch学习&原理解析 TensorFlow 介绍 由 Google Brain 团队开发并于 2015 年开源。由于 Google 的强…...

悬空引用和之道、之禅-《分析模式》漫谈57

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 “Analysis Patterns”的第5章“对象引用”原文&#xff1a; Unless you can catch all such references, there is the risk of a dangling reference, which often has painful con…...

江湖密码术:Rust中的 bcrypt 加密秘籍

前言 江湖险恶,黑客如雨,昔日密码“123456”早被各路大侠怒斥为“纸糊轻功”。若还执迷不悟,用明文密码闯荡江湖,无异于身披藏宝图在集市上狂奔,目标大到闪瞎黑客双眼。 为护你安然度过每一场数据风波,特献上一门绝学《Rust加密神功》。核心招式正是传说中的 bcrypt 密…...

NLP高频面试题(四十八)大语言模型中的思维链(CoT)技术详解

引言 大语言模型(LLM)在近年的飞速发展,让机器在各种任务上表现出令人瞩目的能力。然而,与人类不同,传统的语言模型往往倾向于直接给出答案,而缺乏可解释的中间推理过程。这在复杂推理任务中成为瓶颈:模型可能由于一步推理不当而得出错误结论,却没有过程可供检查。为了…...

对接点餐接口需要有哪些准备?

以下是一般点餐接口对接的相关信息&#xff0c;包括常见的接口功能、对接步骤及注意事项等&#xff1a; 常见接口功能 餐厅信息查询&#xff1a;获取合作餐厅的基本信息&#xff0c;如餐厅名称、地址、营业时间、联系电话、菜单等。菜品查询&#xff1a;查询具体餐厅的菜品详情…...

LintCode第192题-通配符匹配

描述 给定一个字符串 s 和一个字符模式 p &#xff0c;实现一个支持 ? 和 * 的通配符匹配。匹配规则如下&#xff1a; ? 可以匹配任何单个字符。* 可以匹配任意字符串&#xff08;包括空字符串&#xff09;。 两个串完全匹配才算匹配成功。 样例 样例1 输入: "aa&q…...

uv运行一个MCP Server的完整流程

uv是一个高性能的Python包管理器&#xff0c;专注于性能提升。与pip相比&#xff0c;uv利用全局模块缓存&#xff0c;减少磁盘空间使用&#xff0c;并支持Linux、Windows和macOS系统。安装uv可以通过多种方式实现&#xff0c;例如使用Homebrew、Pacman、pip等。 step 1 安装uv:…...

ts中的类型

在 TypeScript 中&#xff0c;类型是静态类型系统的核心&#xff0c;用于在编译阶段检查代码的正确性。TypeScript 提供了丰富的类型系统&#xff0c;包括基本的原始类型、复合类型、以及用户自定义的类型。以下是对 TypeScript 中各种类型的详细分类和说明&#xff1a; 1. 原…...

把dll模块注入到游戏进程的方法_基于文件修改的注入方式

1、概述 本文主要是介绍两种基于文件修改的注入方式,一种是“DLL劫持”,另一种是“修改导入表”。这两种注入方式都是利用操作系统加载PE时的特点来实现的,我们在实现这两种注入方式时只需专注于注入dll的实现,而不用花费额外的精力去关注注入器的实现。要想深入了解这两种…...

判断点是否在多边形内

代码段解析: const intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); 第一部分:(yi > y) !== (yj > y) 作用:检查点 (x,y) 的垂直位置是否跨越多边形的当前边。 yi > y 和 yj > y 分别检查边的两个端…...

【形式化验证基础】活跃属性Liveness Property和安全性质(Safety Property)介绍

文章目录 一、Liveness Property1、概念介绍2、形式化定义二、Safety Property1. 定义回顾2. 核心概念解析3. 为什么强调“有限前缀”4. 示例说明4.1 示例1:交通信号灯系统4.2 示例2:银行账户管理系统5. 实际应用的意义三. 总结一、Liveness Property 1、概念介绍 在系统的…...

Linux——信号(2)信号保存与捕捉

一、信号的保存 上次我们说到&#xff0c;捕捉一个信号后有三种处理方式&#xff1a;默认、忽略、自定义&#xff0c;其中自定义我们用signal系统调用完成&#xff0c;至于忽略信号&#xff0c;也需要signal实现&#xff0c;比如我现在想忽略2号信号&#xff0c;则&#xff1a…...

Vue的模板编译过程

&#x1f468; 作者简介&#xff1a;大家好&#xff0c;我是Taro&#xff0c;全栈领域创作者 ✒️ 个人主页&#xff1a;唐璜Taro &#x1f680; 支持我&#xff1a;点赞&#x1f44d;&#x1f4dd; 评论 ⭐️收藏 文章目录 前言一、编程范式的分类1.编程范式分为声明式和命令…...

空间应用中心AI4S空间科学实验研究成果发表于《中国科学院院刊》

编者寄语&#xff1a; 和鲸基于旗下数据科学协同平台ModelWhale赋能&#xff0c;助力了中国科学院空间应用工程与技术中心系统开展了基于空间科学实验领域的AI4S创新研究。中国科学院空间应用工程与技术中心在空间科学实验领域的研究覆盖了多模态空间科学实验数据模式挖掘、领…...

【Python网络爬虫开发】从基础到实战的完整指南

目录 前言&#xff1a;技术背景与价值当前技术痛点解决方案概述目标读者说明 一、技术原理剖析核心概念图解核心作用讲解关键技术模块技术选型对比 二、实战演示环境配置要求核心代码实现&#xff08;10个案例&#xff09;案例1&#xff1a;基础静态页面抓取案例2&#xff1a;动…...

乐家桌面纯净版刷机ROM下载 乐家桌面纯净版2025官方最新下载

还在苦苦寻找一款好用的电视桌面&#xff0c;为智能电视焕新体验&#xff1f;别在乐家桌面纯净版刷机 ROM 下载和官方最新版下载上纠结啦&#xff0c;试试乐看家桌面&#xff0c;给你带来意想不到的惊喜&#xff01; 乐家桌面纯净版或许曾吸引过你&#xff0c;但乐看家桌面在众…...

深度学习-全连接神经网络

四、参数初始化 神经网络的参数初始化是训练深度学习模型的关键步骤之一。初始化参数&#xff08;通常是权重和偏置&#xff09;会对模型的训练速度、收敛性以及最终的性能产生重要影响。下面是关于神经网络参数初始化的一些常见方法及其相关知识点。 官方文档参考&#xff1…...