news 2026/5/25 12:15:20

列表虚拟化的实现-百万数据轻松展示

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
列表虚拟化的实现-百万数据轻松展示

一、列表虚拟化与海量数据展示

在tds中,当用户在关键词后加了/a参数,会列出所有的文件。此时可能会有上百万个。为了流畅操作和显示这些数据,只能借助列表虚拟化技术来实现。

result

列表虚拟化是一种优化技术,用于处理大量数据时提高性能和用户体验。它通过实时计算来模拟海量数据的展示,此时的性能流畅度与数据大小无关,仅与实时计算需要的执行时间有关。其核心思想是按需加载和按需渲染。

在用户滚动列表时,虚拟化技术会自动将大量数据分成多个小块(或页面),每次只加载和渲染当前视图范围内的数据块。这个过程是事件驱动,当用户滚动列表时,这些事件通知应用程序加载和渲染新的数据块。在后台,虚拟化管理模块还将即将进入视图范围的数据项缓存起来,以便快速访问。这减少了对数据源的频繁访问,提高了性能。

主要原理很简单:

视口渲染:列表虚拟化技术的核心是只渲染用户当前可视区域内的元素,而不是一次性渲染整个列表。当用户滚动时,动态地加载和卸载元素。

占位元素:为了保持列表的滚动条高度和布局正确,虚拟化列表会使用占位元素来表示未渲染部分的高度。

元素复用:虚拟化列表通常会重用相同的组件实例(数据或UI元素)来渲染不同的数据项,通过缓存从而减少开销。

Winform和Avalonia各自的列表虚拟化技术实现不太一样,Winform需要考虑对实时事件手动处理,Avalonia则可以依赖自带的响应式模式绑定自动完成。我一起来看看具体都实现吧。

二、Winform 与 Avalonia 实现的对比

2.1 Winform虚拟列表,以ListView为例

Winform的虚拟列表的开启需要将控件ListView的VirtualMode属性设置为true。在虚拟化过程中,用户滚动列表时,ListView会触发两个关键事件,需要我们进行实现:

ListView.CacheVirtualItems事件:当用户滚动列表时,此事件会被触发。它通知我们哪些项即将进入视图范围,我们可以在这个事件中缓存这些项。例如,可以使用一个数组来存储这些即将显示的项,作为我们的cache。这样可以减少对数据源的频繁访问,提高性能。

ListView.RetrieveVirtualItem事件:当ListView需要将某个项渲染到UI上时,会触发此事件。我们可以通过从缓存中读取对应的项并返回给UI来实现。如果缓存中没有找到对应的项,我们也可以选择动态生成它。

CacheVirtualItems有时候也可以不实现,RetrieveVirtualItem也可以实时处理动态生成ListViewItem。

以下是简化后的代码实现,需要详细实现朋友们可以参考项目源码。

private ListViewItem[] CurrentCacheItemsSource; // 用于存放缓存的数组

private int firstitem; // 缓存的起始索引

private bool refcache = false; // 标记是否需要重新缓存

// 动态获取对象并提供给UI

private void ListView1_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)

{

// 如果缓存中有对应的项,直接从缓存中获取

if (CurrentCacheItemsSource != null && e.ItemIndex >= firstitem && e.ItemIndex < firstitem + CurrentCacheItemsSource.Length)

{

e.Item = CurrentCacheItemsSource[e.ItemIndex - firstitem];

}

else

{

// 如果缓存中没有对应的项,动态生成一个

e.Item = GenerateListViewItem(e.ItemIndex);

}

// 如果动态生成失败,返回一个空对象以避免异常

if (e.Item == null)

{

e.Item = new ListViewItem(new string[] { "加载失败", "", "" });

}

}

// 生成缓存

private void ListView1_CacheVirtualItems(object sender, CacheVirtualItemsEventArgs e)

{

// 如果要缓存的范围已经在缓存中,直接返回

if (e.StartIndex >= firstitem && e.EndIndex <= firstitem + CurrentCacheItemsSource.Length)

{

return;

}

// 更新缓存的起始索引和长度

firstitem = e.StartIndex;

int length = e.EndIndex - e.StartIndex + 1;

// 重新生成缓存

CurrentCacheItemsSource = new ListViewItem[length];

for (int i = 0; i < length; i++)

{

// 从数据源中获取对应的项并存入缓存

CurrentCacheItemsSource[i] = GenerateListViewItem(firstitem + i);

}

// 标记缓存已更新

refcache = false;

// 自动调整列宽以适应内容

ListView1.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent);

}

// 根据索引生成ListViewItem

private ListViewItem GenerateListViewItem(int index)

{

// 这里可以根据实际的数据源来生成ListViewItem

// 示例:从一个列表中获取数据

if (index < vresultNum)

{

FrnFileOrigin f = vlist[index];

return new ListViewItem(new string[] { f.Name, f.Path, f.Size.ToString() });

}

return null;

}

实际使用中,ListViewItem虚拟化的缓存是我们手动将数据关联到一个数组或List中的。当数据发生变化时,除了自动刷新各个索引处对象的值外,还需要控制长度。

如果说缓存有100个元素,我们可以通过设定ListView的VirtualListSize属性来改变要显示的元素个数,比如只显示前10个,这样就可以在数据变小的时避免重新创建数组/列表对象。

5.2 Avalonia的虚拟面板 以为例

与Winform不同,Avalonia的ListBox等控件中没有VirtualMode属性,需要通过VirtualizingStackPanel等方式开启,xml代码如下。

<ListBox x:Name="fileListBox" ItemsSource="{Binding Items.DisplayedData, Mode=OneWay}" >

<ListBox.ItemsPanel>

<ItemsPanelTemplate>

<VirtualizingStackPanel /> <!-- 虚拟化面板 -->

</ItemsPanelTemplate>

</ListBox.ItemsPanel>

<ListBox.ItemTemplate>

<DataTemplate>

<!-- 自定义实现 -->

</DataTemplate>

</ListBox.ItemTemplate>

</ListBox>

绑定的核心思想是,我们将真实数据的引用存起来,Listbox则是绑定到一个IEnumerable<T>对象接口中。当真实数据需要更新时,我们控制IEnumerable<T>的更新。

由于IEnumerable<T>是延迟实现的(yield return)因此不会产生额外数组开销。可以通过Take(DisplayCount)的方法去动态控制所展示数据的长度,类似于Winform的VirtualListSize, 这样能够有效提升性能,尤其是在处理大量数据以及处理数据长度频繁变化的场景。

在下面的代码中给出了一个ViewModel的示例实现,需要用到Avalonia.ReactiveUI库(这个库需要单独在Nuget上下载)。

public class DataViewModel : ReactiveObject

{

private IList<FrnFileOrigin> _allData = [];

private IEnumerable<FrnFileOrigin> _displayedData = [];

private int _displayCount = 100;

public IEnumerable<FrnFileOrigin> DisplayedData

{

get => _displayedData;

private set => this.RaiseAndSetIfChanged(ref _displayedData, value);

}

bool isShowOpenWith = true;

public bool IsShowOpenWith

{

get => isShowOpenWith;

set

{

this.RaiseAndSetIfChanged(ref isShowOpenWith, value);

}

}

public int DisplayCount

{

get => _displayCount;

private set

{

_displayCount = value;

}

}

public DataViewModel()

{

}

public void Bind(IList<FrnFileOrigin> _allData)

{

if (this._allData != _allData)

{

// 生成测试数据(实际中可能从文件或数据库加载)

this._allData = _allData;

//UpdateDisplayedData();

}

}

public void UpdateDisplayedData()

{

// 使用 LINQ 的 Take(),这是惰性求值的,性能很好

DisplayedData = _allData.Take(DisplayCount);

}

// 快速切换到不同数量级

public void SetDisplayCount(int count)

{

DisplayCount = count;

}

}

在上述代码中,DisplayedData 属性通过 RaiseAndSetIfChanged 方法来实现属性值的更新和通知,确保绑定到该属性的界面元素能够及时响应数据的变化。而 DisplayCount 属性在更新时会调用 UpdateDisplayedData() 方法,从而保证 DisplayedData 的内容始终与 DisplayCount 保持一致。

还有一个问题就是,Avalonia在VirtualizingStackPanel中,虚拟化后可能数据不是瓶颈,UI显示同样会造成卡顿,尤其是在应用虚拟化时实时渲染发复杂的布局。因此我们可以设置VirtualizingStackPanel.CacheLength属性。

这个属性是一个double值,决定了在视口上方和下方(或左方和右方)要保持多少额外的空间。值为 0.5 表示系统在每一侧(上下或左右)将缓冲视口大小的一半,此时将实例化更多UI元素。尽管会占更多内存,但大大减少Measure-Arrange循环的次数(measure:确定控件所需的最小宽度和高度; arrange:将控件放置在父控件中,并确定其最终的位置和大小;Arrange:) 否则,在UI复杂程度较高时,GC压力巨大。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/25 19:00:06

插座工程量一键识别-图块统计告别人工点数

插座工程量一键识别-图块统计告别人工点数 电气图纸中插座数量种类繁多&#xff0c;传统人工逐个点数易疲劳、易出错。借助CAD快速看图的【图形识别】&#xff0c;可自动识别并分类统计各类插座工程量&#xff0c;一键生成工程量汇总表&#xff0c;实现插座工程量的高效精准计…

作者头像 李华
网站建设 2026/5/25 14:09:04

SQL必会必知整理-11-分组数据

11.1 数据分组SQL聚集函数可用来汇总数据。这使我们能够对行进行计数&#xff0c;计算和与平均数&#xff0c;获得最大和最小值而不用检索所有数据。但如果要返回每个供应商提供的产品数&#xff0c;或者返回只提供单项产品的供应商所提供的产品&#xff0c;或返回提供10个以上…

作者头像 李华
网站建设 2026/5/24 1:59:31

企业绩效管理痛点如何破?一体化智能平台实操方案

在企业人力资源管理中&#xff0c;绩效管理是连接员工发展与组织目标的关键环节。传统绩效管理模式常面临流程割裂、数据分散、反馈滞后等问题&#xff0c;导致绩效评估流于形式&#xff0c;难以真正发挥激励作用。而一体化智能绩效管理平台通过整合绩效流程、智能数据分析、全…

作者头像 李华
网站建设 2026/5/25 0:00:35

工业边缘节点应用:DeepSeek处理实时产线数据的低功耗配置方案

工业边缘节点应用&#xff1a;DeepSeek处理实时产线数据的低功耗配置方案摘要随着工业4.0和智能制造的深入发展&#xff0c;工业边缘计算作为连接物理世界与数字世界的桥梁&#xff0c;其重要性日益凸显。工业边缘节点部署于生产现场&#xff0c;负责实时采集、处理和分析产线数…

作者头像 李华
网站建设 2026/5/24 6:34:05

小程序毕设项目:基于springboot+微信小程序的公务员助学系统小程序的设计与实现(源码+文档,讲解、 调试运行,定制等)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/5/25 0:38:41

Java计算机毕设之基于springboot+vue的少儿编程知识刷题学习系统基于Java的scratch少儿编程学习网站系统的设计与实现(完整前后端代码+说明文档+LW,调试定制等)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华