zoukankan      html  css  js  c++  java
  • ObservableCollection与List在加载数据上的性能比较

    使用Listview等控件加载数据时,第一时间想到的就是ObservableCollection,这个东西蛮好,如果新增、删除、修改数据,都会自动更新UI。

    可是,如果不需要增删改,显示大数据量,这个东西的加载性能怎么样呢?

    做个实验。

    1.准备数据,在本地磁盘上创建20000个文件,将其加载到ListView中。

    Create file
      var testPath = @"D:\TestLargeData\Test10000";
                if (!Directory.Exists(testPath))
                    Directory.CreateDirectory(testPath);
                else
                {
                    MessageBox.Show("test file has been created");
                    return;
                }
                for (int i = 0; i < 20000; i++)
                    File.Create(Path.Combine(testPath, Path.GetRandomFileName()));

    2. 使用ObserableCollection加载

    ObservableCollection
        #region data source 1
            /// <summary>
            /// files and directories in current directory, display in listview
            /// </summary>
            ObservableCollection<FileItem> allFiles1 = new ObservableCollection<FileItem>();
            /// <summary>
            ///  files and directories in current directory, display in listview
            /// </summary>
            public ObservableCollection<FileItem> AllFiles1
            {
                get { return allFiles1; }
                set
                {
                    if (allFiles1 != value)
                        allFiles1 = value;
                    NotifyPropertyChanged("AllFiles1");
                }
            }
            #endregion
    
            #region load data method---1
            /// <summary>
            /// when current directory path change ,refresh listview
            /// </summary>
            public void Refresh1()
            {
                LoadLastFiles();
            }
    
            /// <summary>
            /// loading last file task 
            /// </summary>
            void LoadLastFiles()
            {
                var files = Directory.EnumerateFileSystemEntries(testPath);
              
                DateTime dtBegin = DateTime.Now;
                LogHelper.Log("1====loading begin:");
    
                foreach (var pageFile in files)
                {
                    Invoke(delegate
                    {
                        allFiles1.Add(GetFileItem(pageFile));
                    });
                }
    
                LogHelper.Log("1====loading ok:" + (DateTime.Now - dtBegin).TotalSeconds);
                LogHelper.Log("1====current ItemsCount:" + allFiles1.Count.ToString());
            }
            #endregion

    3.使用List加载

    List
            #region datasource 2
            /// <summary>
            /// files and directories in current directory, display in listview
            /// </summary>
            List<FileItem> allFiles2 = new List<FileItem>();
            /// <summary>
            ///  files and directories in current directory, display in listview
            /// </summary>
            public List<FileItem> AllFiles2
            {
                get { return allFiles2; }
                set
                {
                    if (allFiles2 != value)
                        allFiles2 = value;
                    NotifyPropertyChanged("AllFiles2");
                }
            }
            #endregion
    
            #region load data method---2
            /// <summary>
            /// when current directory path change ,refresh listview
            /// </summary>
            public void Refresh2()
            {
                LoadLastFiles2();
            }
    
            /// <summary>
            /// loading last file task 
            /// </summary>
            void LoadLastFiles2()
            {
                var files = Directory.EnumerateFileSystemEntries(testPath);
    
                AllFiles2 = new List<FileItem>();
                DateTime dtBegin = DateTime.Now;
                DateTime dtLastRefresh = DateTime.Now;
                LogHelper.Log("2====loading begin:");
    
                foreach (string file in files)
                {
                    AllFiles2.Add(GetFileItem(file));
                    NotifyPropertyChanged("AllFiles2");
                }
                LogHelper.Log("2====loading ok:" + (DateTime.Now - dtBegin).TotalSeconds);
                LogHelper.Log("2====current ItemsCount:" + AllFiles2.Count.ToString());
            }
            #endregion

    这次对比的结果是 :

    04:23:13 235 | 1====loading begin:

    04:23:13 850 | 1====loading ok:0.615

    04:23:13 851 | 1====current ItemsCount:73338

    04:23:15 458 | 2====loading begin:

    04:23:15 961 | 2====loading ok:0.503

    04:23:15 962 | 2====current ItemsCount:73338

    对比发现,两个差别不大的。

    4. 上面的实验,数据是在主线程加载的,实际的数据加载一般都是利用线程加载的,所以修改代码如下:

    5. ObserableCollection 加载放在线程中,需要Invoke了,如下:

    ObservableCollection
            #region load data method---1
            /// <summary>
            /// when current directory path change ,refresh listview
            /// </summary>
            public void Refresh1()
            {
                //LoadLastFiles();
                allFiles1.Clear();
                Thread thread1 = new Thread(new ThreadStart(LoadLastFiles));
                thread1.Start();
            }
    
            /// <summary>
            /// loading last file task 
            /// </summary>
            void LoadLastFiles()
            {
                var files = Directory.EnumerateFileSystemEntries(testPath);
                
                DateTime dtBegin = DateTime.Now;
                LogHelper.Log("1====loading begin:");
    
                foreach (var pageFile in files)
                {
                    Invoke(delegate
                    {
                        allFiles1.Add(GetFileItem(pageFile));
                    });
                }
    
                LogHelper.Log("1====loading ok:" + (DateTime.Now - dtBegin).TotalSeconds);
                LogHelper.Log("1====current ItemsCount:" + allFiles1.Count.ToString());
            }
            #endregion

    6.list 也放在线程中,这个需要传递Listview过来,刷新Items,如下

    List
       #region datasource 2
            /// <summary>
            /// files and directories in current directory, display in listview
            /// </summary>
            List<FileItem> allFiles2 = new List<FileItem>();
            /// <summary>
            ///  files and directories in current directory, display in listview
            /// </summary>
            public List<FileItem> AllFiles2
            {
                get { return allFiles2; }
                set
                {
                    if (allFiles2 != value)
                        allFiles2 = value;
                    NotifyPropertyChanged("AllFiles2");
                }
            }
            #endregion
    
            #region load data method---2
            /// <summary>
            /// when current directory path change ,refresh listview
            /// </summary>
            public void Refresh2()
            {
                //LoadLastFiles2();
                Thread thread2 = new Thread(new ThreadStart(LoadLastFiles2));
                thread2.Start();
            }
    
            /// <summary>
            /// loading last file task 
            /// </summary>
            void LoadLastFiles2()
            {
                var files = Directory.EnumerateFileSystemEntries(testPath);
    
               allFiles2   = new List<FileItem>();
                DateTime dtBegin = DateTime.Now;
                DateTime dtLastRefresh = DateTime.Now;
                LogHelper.Log("2====loading begin:");
    
                foreach (string file in files)
                {
                    allFiles2.Add(GetFileItem(file));
                    NotifyPropertyChanged("AllFiles2");
    
                    Invoke(delegate
                    {
                        this.tstLv.Items.Refresh();
                    });
                    dtLastRefresh = DateTime.Now;
                }
                NotifyPropertyChanged("AllFiles2");
                LogHelper.Log("2====loading ok:" + (DateTime.Now - dtBegin).TotalSeconds);
                LogHelper.Log("2====current ItemsCount:" + AllFiles2.Count.ToString());
            }
            #endregion

    经过多轮测试,发现list明显速度比较慢

    04:42:02 493 | 1====loading begin:

    04:42:05 287 | 1====loading ok:2.7932793

    04:42:05 288 | 1====current ItemsCount:73338

    04:42:07 192 | 2====loading begin:

    04:42:26 276 | 2====loading ok:19.0839082

    04:42:26 277 | 2====current ItemsCount:73338

    04:42:43 277 | 2====loading begin:

    04:43:04 188 | 2====loading ok:20.9110909

    04:43:04 189 | 2====current ItemsCount:73338

    04:43:05 838 | 1====loading begin:

    04:43:08 511 | 1====loading ok:2.6732673

    04:43:08 512 | 1====current ItemsCount:73338

    这一次,ObserableCollection 的优势非常明显。

    7. 使用list时,每增加一个数据,就refresh一下,这里有点浪费了。
    有两个办法:一是每加载若干数据(例如200个)refresh一次,而是每过0.1秒refresh一次。考虑到用户的操作,0.1秒内用户操作的可能性小的多,必将手没那么快。
    分时加载 list 优化的代码如下:

    List
     void LoadLastFiles2()
            {
                var files = Directory.EnumerateFileSystemEntries(testPath);
    
               allFiles2   = new List<FileItem>();
                DateTime dtBegin = DateTime.Now;
                DateTime dtLastRefresh = DateTime.Now;
                LogHelper.Log("2====loading begin:");
    
                foreach (string file in files)
                {
                    allFiles2.Add(GetFileItem(file));
                    NotifyPropertyChanged("AllFiles2");
    
                    if ((DateTime.Now - dtLastRefresh).TotalSeconds > 0.1)
                    {
                        Invoke(delegate
                        {
                            this.tstLv.Items.Refresh();
                        });
                        dtLastRefresh = DateTime.Now;
                    }
                }
                NotifyPropertyChanged("AllFiles2");
                LogHelper.Log("2====loading ok:" + (DateTime.Now - dtBegin).TotalSeconds);
                LogHelper.Log("2====current ItemsCount:" + AllFiles2.Count.ToString());
            }

    这次对比,实在是差别太大了:

    04:46:57 739 | 1====loading begin:

    04:47:00 388 | 1====loading ok:2.650265

    04:47:00 389 | 1====current ItemsCount:73338

    04:47:03 612 | 2====loading begin:

    04:47:03 853 | 2====loading ok:0.2410241

    04:47:03 854 | 2====current ItemsCount:73338

     04:47:20 351 | 2====loading begin:

    04:47:20 641 | 2====loading ok:0.290029

    04:47:20 662 | 2====current ItemsCount:73338

     04:47:23 235 | 1====loading begin:

    04:47:25 875 | 1====loading ok:2.640264

    04:47:25 876 | 1====current ItemsCount:73338  

    看来分时加载,有点意思。

    再来看看分数据量加载:

     代码:

    List
        void LoadLastFiles2()
            {
                var files = Directory.EnumerateFileSystemEntries(testPath);
    
                allFiles2 = new List<FileItem>();
                DateTime dtBegin = DateTime.Now;
                DateTime dtLastRefresh = DateTime.Now;
                LogHelper.Log("2====loading begin:");
                int i = 0;
    
                foreach (string file in files)
                {
                    allFiles2.Add(GetFileItem(file));
                    NotifyPropertyChanged("AllFiles2");
    
                    if (i >= 100)
                    {
                        Invoke(delegate
                        {
                            this.tstLv.Items.Refresh();
                        });
                        dtLastRefresh = DateTime.Now;
                        i = 0;
                    }
                    i++;
                }
                NotifyPropertyChanged("AllFiles2");
                LogHelper.Log("2====loading ok:" + (DateTime.Now - dtBegin).TotalSeconds);
                LogHelper.Log("2====current ItemsCount:" + AllFiles2.Count.ToString());
            }

    结果

    04:54:03 480 | 1====loading begin:

    04:54:06 056 | 1====loading ok:2.5762576

    04:54:06 057 | 1====current ItemsCount:73338

     04:54:07 112 | 2====loading begin:

    04:54:07 429 | 2====loading ok:0.3170317

    04:54:07 430 | 2====current ItemsCount:73338

     04:54:24 489 | 2====loading begin:

    04:54:24 789 | 2====loading ok:0.3010301

    04:54:24 790 | 2====current ItemsCount:73338

     04:54:26 486 | 1====loading begin:

    04:54:29 176 | 1====loading ok:2.690269

    04:54:29 177 | 1====current ItemsCount:73338

    看来还是要 设定refresh的时机, 这样子速度快了不少。

    至于分数据还是分时,看实际的需要了,个人认为分时比较好。

    分时的时间最好长一点,数据量分块也大一点,减少刷新UI的时间,加载会快。

    但是如果用户同时 拖拽滚动条, 体验就不好了,不流畅,滚动条会跳跃。

    8. ObservableCollection 也有优化的方案,如下,采用延迟通知数据变化的方案。网上看到的,很抱歉,没找到出处。

    RangeObservableCollection
     public class RangeObservableCollection<T>:ObservableCollection<T>
        {
            bool isDeferNotify = false;
    
            public void AddRange(IEnumerable<T> rangeData)
            {
                isDeferNotify = true;
    
                foreach (T data in rangeData)
                {
                    Add(data);
                }
                
                isDeferNotify = false;
            }
    
            public void RemoveRange(IEnumerable<T> rangeData)
            {
                isDeferNotify = true;
    
                foreach (T data in rangeData)
                {
                    Remove(data);
                }
    
                isDeferNotify = false;
            }
    
            protected override void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
            {
                //if use DeferNotify, UI Operation is  not fluent
                //if (!isDeferNotify)
                //{
                    base.OnCollectionChanged(e);
               // }
            }
    
        }

    修改代码如下:

    Load1
       void LoadLastFiles()
            {
                var files = Directory.EnumerateFileSystemEntries(testPath);
    
                DateTime dtBegin = DateTime.Now;
                LogHelper.Log("1====loading begin:");
    
                int pageIndex = 0;
                int pageSize = 2000;
                bool isEndPage = false;
    
                while (!isEndPage)
                {
                    var pageData = files.Skip(pageIndex * pageSize).Take(pageSize).Select(o=>GetFileItem(o));
                    if (pageData.Count() < pageSize)
                        isEndPage = true;
    
                    Invoke(delegate
                    {
                        allFiles1.AddRange(pageData);
                    });
                    pageIndex++;
                }
    
                LogHelper.Log("1====loading ok:" + (DateTime.Now - dtBegin).TotalSeconds);
                LogHelper.Log("1====current ItemsCount:" + allFiles1.Count.ToString());
            }

    测试后发现,效果并不明显:

    05:05:08 989 | 1====loading begin:

    05:05:13 189 | 1====loading ok:4.2

    05:05:13 191 | 1====current ItemsCount:73338

    所以,还是束之高阁吧。

    唠唠叨叨这么多,其实呢,对于大数据加载,MS提供的ObservableCollection方案还是不错的,但是呢,想要再快一点,还是自己来搞搞把。

    另外呢,ObservableCollection为什么快,网上不少资料,研究吧。

    欢迎拍砖!!

  • 相关阅读:
    android 如何添加第3方lib库到kernel中
    如何搭建modem编译环境
    /dev、/sys/dev 和/sys/devices 和udev的关系
    Makefile的obj-y 和 obj-m
    MTK Android添加驱动模块
    使用 /sys 文件系统访问 Linux 内核
    JNI设置C++与java的结合(2)
    android 常用方法集合
    操作系统(科普章节)
    前端面试之前要准备的那些事
  • 原文地址:https://www.cnblogs.com/xiaokang088/p/2471716.html
Copyright © 2011-2022 走看看