组件是怎样写的(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

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

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

相关文章

个性化的配置AndroidStudio

Android Studio 提供诸多向导和模板&#xff0c;可用于验证 Java 开发套件 (JDK) 和可用 RAM 等系统要求&#xff0c;以及配置默认设置&#xff0c;例如经过优化的默认 Android 虚拟设备 (AVD) 模拟和更新的系统映像。本文档介绍了可用于自定义 Android Studio 使用方式的其他配…

人类行为的原动力是自我保存-来自ChatGPT

自我保存&#xff08;Self-Preservation&#xff09;确实可以说是人类行为最原始、最底层的驱动力。 简单来说&#xff1a; 无论我们做什么&#xff0c;表面看动机五花八门&#xff0c;实际上归根到底都绕不开活下去、保护自己。 &#x1f4a1; 从不同层面理解这个观点&#…

SystemVerilog语法之内建数据类型

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

蓝光三维扫描技术:高效精密测量相机镜头底座注塑件

如今越来越多的摄影爱好者、vlog拍摄者使用数码相机以及无人机&#xff0c;随时随地记录生活中的每一刻美好瞬间&#xff0c;对相机设备的要求也不断提高。 — 案例背景 — 相机镜头底座涉及镜头装置可靠、螺丝位置度连接以及壳体组装&#xff0c;镜头底座注塑件生产厂商&…

【前端】【面试】【业务场景】前端如何获取并生成设备唯一标识

✅ 总结 问题&#xff1a;前端如何获取并生成设备唯一标识&#xff1f; 核心要点&#xff1a;浏览器原生信息有限&#xff0c;但通过组合多个维度可生成设备指纹&#xff08;Device Fingerprint&#xff09;&#xff0c;用于唯一标识设备。 常见方式&#xff1a; 浏览器信息&…

极刻AI搜v1.0 问一次问题 AI工具一起答

软件名&#xff1a;极刻AI搜 版本&#xff1a;v1.0 功能&#xff1a;囊括了互联网上比较好用的一些支持”搜索“的网站或者工具 开发平台&#xff1a;nodepythonweb 分类有&#xff1a; AI搜索&#xff08;支持智能问答的AI搜索引擎&#xff09; 常规搜索&#xff1a;&#xff…

《2025最新Java面试题全解析:从基础到高并发架构设计》

25年Java开发者面试中最常考察的100道面试题&#xff0c;涵盖Java基础、JVM、多线程、Spring框架、分布式系统等核心知识点&#xff0c;并结合大厂真实面试案例进行深度解析&#xff0c;助你顺利通过技术面试。 一、Java基础篇&#xff08;高频15问&#xff09; 1. HashMap底层…

[c语言日寄]免费文档生成器——Doxygen在c语言程序中的使用

【作者主页】siy2333 【专栏介绍】⌈c语言日寄⌋&#xff1a;这是一个专注于C语言刷题的专栏&#xff0c;精选题目&#xff0c;搭配详细题解、拓展算法。从基础语法到复杂算法&#xff0c;题目涉及的知识点全面覆盖&#xff0c;助力你系统提升。无论你是初学者&#xff0c;还是…

51c嵌入式~单片机~合集5~DMA

我自己的原文哦~ https://blog.51cto.com/whaosoft/12940885 一、DMA DMA&#xff0c;全称Direct Memory Access&#xff0c;即直接存储器访问。 DMA传输将数据从一个地址空间复制到另一个地址空间&#xff0c;提供在外设和存储器之间或者存储器和存储器之间的高速数据传输…

Linux随记(十七)

一、综合报错&#xff1a;fork: Cannot allocatte memory 和 modues is unknwon 和 pam_limits(crond:session) : unknwon limit item ‘noproc’ 1.1 fork: Cannot allocatte memory 处理 - 随记 排查时间2025年4月。 环境描述&#xff1a; 2014年左右的服务器&#xff0c;…

支持mingw g++14.2 的c++23 功能print的vscode tasks.json生成调试

在mingw14.2版本中, print库的功能默认没有开启, 生成可执行文件的tasks.json里要显式加-lstdcexp, 注意放置顺序. tasks.json (支持mingw g14.2 c23的print ) {"version": "2.0.0","tasks": [{"type": "cppbuild","…

赋能能源 | 智慧数据,构建更高效智能的储能管理系统

行业背景 随着新能源产业的快速发展&#xff0c;大规模储能系统在电力调峰、调频及可再生能源消纳等领域的重要性日益凸显。 储能电站作为核心基础设施&#xff0c;其能量管理系统&#xff08;EMS&#xff09;需要处理海量实时数据&#xff0c;包括电池状态、功率变化、环境监…

使用 Flutter 遇坑小计

前言 首先, 谷哥很贴心地为国内用户准备了一份使用手册 不过很遗憾 就算你照着它的手册来了, 还是会在后续使用中遇到其它的坑 今天我踩了, 保不齐明天就是其他人(lol) running gradle task ‘assembledebug’ stuck 首先去确定下当下Android Studio(或者说你目前的Flutter项…

链表与文件

链表 单链表 1.链表的初始化 typedef struct node {char name[100];int number;struct node *next; }Node,*LinkList;}Node;2.链表的初始化函数(Initlist) LinkList InitList() {LinkList head;head(Node*)malloc(sizeof(Node));head->nextNULL;return head; }3.建立链…

uniapp打ios包

uniapp在windows电脑下申请证书并打包上架 前言 该开发笔记记录了在window系统下&#xff0c;在苹果开发者网站生成不同证书&#xff0c;进行uniapp打包调试和上线发布&#xff0c;对window用户友好 注&#xff1a;苹果打包涉及到两种证书&#xff1a;开发证书 和 分发证书 …

OpenCV 图形API(48)颜色空间转换-----将 LUV 颜色空间的图像数据转换为 BGR 颜色空间函数LUV2BGR()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 将图像从LUV颜色空间转换为BGR颜色空间。 该函数将输入图像从LUV颜色空间转换为BGR。B、G和R通道值的常规范围是0到255。 输出图像必须是8位无符…

HOW MUCH POSITION INFORMATION DO CONVOLUTIONAL NEURAL NETWORKS ENCODE?

1. 动机: 卷积神经网络中的卷积操作实际上是一个局部的操作,这样的话就会使得它虽然知道自己看的是什么,但是却不清楚他在图像中的位置信息,但是位置信息实际上是很有用的,因此CNN可能潜在的学习到了如何去编码这种位置信息。所以这篇论文就是为了研究这种位置信息是如何在…

56、如何快速让⼀个盒⼦⽔平垂直居中

在网页开发中&#xff0c;有多种方式能让一个盒子实现水平垂直居中。下面为你介绍几种常见且快速的方法。 1. 使用 Flexbox 布局 Flexbox 是一种非常便捷的布局模型&#xff0c;能够轻松实现元素的水平和垂直居中。 html <!DOCTYPE html> <html lang"en"&…

RAG应用过程监控系统选型:LangFuse

Langfuse 是一个开源的大语言模型&#xff08;LLM&#xff09;工程平台&#xff0c;旨在协助团队构建、调试和改进由人工智能驱动的应用程序。凭借其全面的工具套件&#xff0c;Langfuse 使开发者能够深入洞察其 LLM 应用程序&#xff0c;并优化性能。 Stars 数10,522Forks 数9…

Java+nanomsg快速实现去broker的数据通信

先说一下nanomsgJava需要做什么&#xff1a; 1、nanomsg的so文件的制作与放置路径 2、Java代码引入nanomsg的依赖 3、支持Socket参数的调节&#xff08;包括ipv4/ipv6的网络支持&#xff09; 在我目前的认知范围内要与一个通讯目标实现数据交互通常有这些方式 1、broker中间人…