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為什么快,網上不少資料,研究吧。

歡迎拍磚!!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM