2015 年 11 月

第 30 卷,第 12 期

本文章是由機器翻譯。

Windows 10 - 使用搜尋索引子加速檔案作業

Adam Wilson | 2015 年 11 月

搜尋索引子已經提供了許多 Windows 的一部分現在釋放及支援從 IE 的網址列文件庫檢視在檔案總管中的所有項目以及 [開始] 功能表和 Outlook 提供搜尋功能。與 Windows 10 索引子的電源已經從桌上型電腦到所有的通用 Windows 平台 (UWP) 應用程式可以使用受限於。雖然這也可讓 Cortana 執行更好的搜尋,這項改進揭開最令人興奮的一部分是它可大幅提升應用程式與檔案系統互動的方式。

索引子可讓應用程式進行排序及群組檔案和追蹤如何變更檔案系統等更有趣的作業。大部分的索引子 Api 可供 UWP 應用程式可透過 Windows.Storage 和 Windows.Storage.Search 命名空間。應用程式已經使用索引子來啟用其使用者的絕佳體驗。在本文中我會逐步引導如何使用索引子檔案系統上追蹤變更、 快速呈現檢視並提供一些基本的技巧如何改善應用程式的查詢。

快速存取檔案和中繼資料

大部分使用者裝置包含數百或數千個包含使用者的最 cherished 的圖片和最愛的歌曲的媒體檔案。可快速地逐一查看檔案在裝置和提供鼓勵檔案之間的互動的應用程式是在任何平台上的最愛的應用程式之間。UWP 提供一系列類別可以用來存取不論外型規格的任何裝置上的檔案。

Windows.Storage 命名空間包含用於存取檔案和資料夾以及大部分的應用程式如何處理它們的基底作業的基本類別。但是如果您的應用程式需要存取大量的檔案或中繼資料然後這些類別不會提供的效能特性使用者要求。

例如,呼叫 StorageFolder.GetFilesAsync 不堪設想如果是無法控制要列舉的資料夾。使用者可以將數十億個檔案放在單一目錄中,但嘗試為每個建立 StorageFile 物件將會導致應用程式非常快速地用完記憶體。即使在較少極端的情況下呼叫會仍然傳回非常緩慢因為系統建立的檔案控制代碼的千分位及它們封送處理回應用程式容器。為了避免此錯誤的應用程式,系統提供的 StorageFileQueryResults 和 StorageFolderQueryResults 類別。

StorageFileQueryResults 是移至類別時寫入控制代碼多個簡單的多個檔案的應用程式。不僅它會提供很好的方法來列舉及修改複雜的搜尋查詢的結果但因為 API 會列舉型別要求將查詢視為"*,"它也適用於更佳的效能的情況。

如有提供使用索引子是加速您的應用程式的第一個步驟。現在,聽起來很像我採用,自我服務 plea 但沒有邏輯的理由來自索引子專案經理,我說。記住索引子所設計的 StorageFile 和 StorageFolder 物件。從索引子可以快速擷取物件中快取屬性。如果您未使用索引子,系統沒有從磁碟和登錄其為 O-大量且會造成效能問題的應用程式和系統查閱的值。

若要確定即將使用索引子,建立 QueryOptions 物件並設定為只使用索引子的 QueryOptions.IndexerOption 屬性:

QueryOptions options = new QueryOptions();
options.IndexerOption = IndexerOption.OnlyUseIndexer;

或者,使用索引子可用時:

options.IndexerOption = IndexerOption.UseIndexerWhenAvailable;

建議的用法是緩慢的檔案作業不會鎖定您的應用程式或委任使用 IndexerOption.UseIndexerWhenAvailable UX。這會嘗試使用索引子列舉檔案,但是改為使用較慢磁碟作業,如有需要。IndexerOption.OnlyUseIndexer 最適合比等待速度緩慢的檔案作業沒有傳回結果時。如果索引子會停用,但很快就會傳回,仍可讓使用者做出反應的應用程式系統會傳回零筆結果。

有些時候,建立 QueryOptions 物件看起來有點過度的只是快速的列舉型別和在這些情況下,它可能意義不擔心是否有索引子。您用來控制資料夾的內容的情況下,呼叫 StorageFolder.GetItemsAsync 合理的。它是簡單的一行的程式碼撰寫和任何效能問題將會隱藏在情況下有只將少數檔案的目錄中。

另一種方式來加快檔案列舉型別是不會建立不必要的 StorageFile 或 StorageFolder 物件。即使是使用索引子、 開啟 StorageFile 要求系統建立的檔案控制代碼、 收集某些屬性資料以及封入應用程式的程序。此 IPC 隨附固有的延遲,可以在許多情況下,只不會建立物件在第一時間避免。

請注意索引子所支援的 StorageFileQueryResult 物件並不會在內部建立任何 StorageFiles 重點。視需要透過 GetFilesAsync 時建立。屆時系統只會保留一份檔案中是相當輕量的記憶體。

列舉大量檔案的建議的方式是作為批次處理功能 GetFilesAsync 上的檔案群組中的頁面無妨。如此一來,您的應用程式可以執行背景處理的檔案時它正在等待下一次建立設定。中的程式碼 [圖 1 示範如何完成的簡單範例。

[圖 1 GetFilesAsync

uint index = 0, stepSize = 10;
IReadOnlyList<StorageFile> files = await queryResult.GetFilesAsync(index, stepSize);
index += 10;          
while (files.Count != 0)
{
  var fileTask = queryResult.GetFilesAsync(index, stepSize).AsTask();
  foreach (StorageFile file in files)
  {
    // Do the background processing here   
  }
  files = await fileTask;
  index += 10;
}

這是由許多應用程式已在 Windows 上的相同程式碼撰寫模式。藉由變化的間距大小他們能夠拉出正確的應用程式中有回應的第一次檢視時快速於背景中檔案的其餘部分的項目數目。

預先擷取的屬性是另一個簡單的方式來加速您的應用程式。預先擷取的屬性可讓您的應用程式通知系統它所需要的一組給定檔案屬性。系統將雖然它會列舉一組檔案擷取這些屬性與索引子和快取它們 StorageFile 物件中。這可提供與只收集屬性分次檔案會傳回簡單的效能提升。

QueryOptions 物件中設定的預先擷取屬性值。為一些常見案例受到使用 PropertyPrefetchOptions 但應用程式也可以自訂要求 Windows 所支援的任何值的屬性。若要這樣做的程式碼很簡單:

QueryOptions options = new QueryOptions();
options.SetPropertyPrefetch(PropertyPrefetchOptions.ImageProperties,
  new String[] { });

在此情況下,應用程式會使用映像屬性並不需要任何其他屬性。系統會針對列舉查詢的結果,它就會快取在記憶體中的映像屬性以便稍後您立即使用。

最後要注意的是屬性中有儲存在索引為預先提取提供以改善效能。否則,系統仍然必須存取該檔案來尋找值,這是相當緩慢。屬性系統的 Microsoft Windows 開發人員中心頁面 (bit.ly/1LuovhT) 具有所有屬性的相關資訊可在 Windows 索引子。只尋找 isColumn = true 中會指出屬性會使用要預先提取屬性描述。

結合使用所有這些增強功能可讓您執行得更快的程式碼。如需簡單範例中,我寫了擷取我的電腦以及其垂直高度上的所有圖片的應用程式。這是相片檢視應用程式必須執行才能顯示使用者的相片集合的第一個步驟。

我在三個執行嘗試不同的檔案列舉和顯示兩者之間的差異。第一項測試使用單純的程式碼與啟用中所示的索引子 [圖 2。第二個測試使用的程式碼所示 [圖 3, ,它會預先提取屬性和檔案中的頁面。和第三個使用預先提取屬性和分頁檔,但停用的索引子。此程式碼是相同 [圖 3, ,但變更註解中註明的一行。

[圖 2 貝氏程式碼列舉文件庫

StorageFolder folder = KnownFolders.PicturesLibrary;
QueryOptions options = new QueryOptions(
  CommonFileQuery.OrderByDate, new String[] { ".jpg", ".jpeg", ".png" });          
options.IndexerOption = IndexerOption.OnlyUseIndexer;
StorageFileQueryResult queryResult = folder.CreateFileQueryWithOptions(options);
Stopwatch watch = Stopwatch.StartNew();          
IReadOnlyList<StorageFile> files = await queryResult.GetFilesAsync();
foreach (StorageFile file in files)
{                
  IDictionary<string, object> size =
    await file.Properties.RetrievePropertiesAsync(
    new String[] { "System.Image.VerticalSize" });
  var sizeVal = size["System.Image.VerticalSize"];
}           
watch.Stop();
Debug.WriteLine("Time to run the slow way: " + watch.ElapsedMilliseconds + " ms");

[圖 3 最佳化程式碼列舉文件庫

StorageFolder folder = KnownFolders.PicturesLibrary;
QueryOptions options = new QueryOptions(
  CommonFileQuery.OrderByDate, new String[] { ".jpg", ".jpeg", ".png" });
// Change to DoNotUseIndexer for trial 3
options.IndexerOption = IndexerOption.OnlyUseIndexer;
options.SetPropertyPrefetch(PropertyPrefetchOptions.None, new String[] { "System.Image.VerticalSize" });
StorageFileQueryResult queryResult = folder.CreateFileQueryWithOptions(options);
Stopwatch watch = Stopwatch.StartNew();
uint index = 0, stepSize = 10;
IReadOnlyList<StorageFile> files = await queryResult.GetFilesAsync(index, stepSize);
index += 10;
// Note that I'm paging in the files as described
while (files.Count != 0)
{
  var fileTask = queryResult.GetFilesAsync(index, stepSize).AsTask();
  foreach (StorageFile file in files)
  {
// Put the value into memory to make sure that the system really fetches the property
    IDictionary<string,object> size =
      await file.Properties.RetrievePropertiesAsync(
      new String[] { "System.Image.VerticalSize" });
    var sizeVal = size["System.Image.VerticalSize"];                   
  }
  files = await fileTask;
  index += 10;
}
watch.Stop();
Debug.WriteLine("Time to run: " + watch.ElapsedMilliseconds + " ms");

看一下,如果沒有預先擷取的結果,效能差異是很清楚中所示 [圖 4

[圖 4 結果而預先擷取

測試案例 (在桌上型電腦上的 2,600 影像) 平均執行時間超過 10 個範例
貝氏 code + 索引子 9,318ms
所有最佳化 + 索引子 5,796ms
最佳化 + 沒有索引子 20,248ms (48,420ms 冷)

很有可能幾乎藉由套用本文所述的簡單最佳化加倍貝氏程式碼的效能。模式是經過實際環境測試以及。之前釋放任何版本的 Windows 我們與應用程式團隊合作進行確定相片 Groove 音樂和其他的工作以及可能。這是這些模式來自何處。它們 cribbed 直接從 UWP 的第一個 UWP 應用程式的程式碼和可直接套用到您的應用程式。

在檔案系統中追蹤變更

如此處所示列舉所有的檔案的位置是需要大量資源的程序。大部分的情況下,您的使用者甚至不會有興趣較舊的檔案。他們想所花費的圖片、 剛才下載的歌曲或最近編輯文件。為了將最新的檔案移至上層,您的應用程式可以追蹤檔案系統的變更和尋找最新建立或輕易修改檔案。

有兩種方法來變更追蹤取決於您的應用程式是否在背景或前景。在前景應用程式時,它可以使用 StorageFileQueryResult 物件的 ContentsChanged 事件來變更在給定查詢的通知。在背景中應用程式時,它可以登錄 StorageLibraryContentChangedTrigger 以項目變更時收到通知。這兩者都是翻通知可讓應用程式知道有變更,但並不包含已變更之檔案的相關資訊。

若要尋找哪些檔案已修改或最近建立的系統會提供 System.Search.GatherTime 屬性。屬性是設定索引的位置中的所有檔案和追蹤索引子注意到修改檔案的最後一個時間。這個屬性就會不斷地更新為基礎的系統時鐘,不過讓手動時區交換器、 日光節約時間及使用者相關的一般免責聲明變更系統時間仍適用於信任此應用程式中的值。

正在註冊的變更追蹤在前景中的事件很容易。建立您的應用程式有興趣的範圍涵蓋 StorageFileQueryResult 物件之後,就會登錄 ContentsChanged 事件,如下所示:

StorageFileQueryResult resultSet = photos.CreateFileQueryWithOptions(option);
resultSet.ContentsChanged += resultSet_ContentsChanged;

此事件會引發任何設定變更的結果中的時間。應用程式接下來可以尋找最近變更的檔案。

變更追蹤與背景是稍微更複雜。應用程式都能夠在裝置上的程式庫的檔案有所變更時接獲通知註冊。沒有支援更複雜的查詢或範圍,這表示應用程式會負責執行一些工作來確定變更是它們是真正有興趣的項目。

做為一項有趣的擱置在一旁,應用程式可以的原因只有暫存器的程式庫的變更通知並不會根據檔案類型是因為索引子的設計方式。篩選查詢為基礎的磁碟上的檔案位置的速度比查詢的相符項目根據檔案類型。而且它在我們的初始測試裝置的效能下拖曳。我將討論更多的效能秘訣更新版本中,但這是一個重大記住: 篩選查詢結果的檔案位置是極快速相較於其他類型的篩選作業。

我已經概述了註冊的部落格文章中的程式碼範例與背景工作的步驟 (bit.ly/1iPUVIo),但是讓我們逐步解說幾個更有趣的步驟。應用程式必須做的第一件事是建立背景觸發程序:

StorageLibrary library =
  await StorageLibrary.GetLibraryAsync(KnownLibraryId.Pictures);
StorageLibraryContentChangedTrigger trigger =
  StorageLibraryContentChangedTrigger.Create(library);

也可以將觸發程序建立集合中的程式庫,如果應用程式會有興趣追蹤多個位置。在此情況下,應用程式只會查看圖片媒體櫃,這是其中一個應用程式最常見的案例。您必須確定應用程式必須能夠存取嘗試將曲目; 變更媒體櫃的正確功能否則,系統會傳回存取應用程式嘗試建立 StorageLibrary 物件時拒絕例外狀況。

在 Windows mobile 裝置,這是做為圖片的文件庫位置下撰寫要保證新圖片從裝置的系統功能十分強大。這是不論使用者選擇 [設定] 頁面中變更哪些資料夾很文件庫的一部分。

應用程式必須註冊使用 BackgroundExecutionManager 的背景工作並具有內建於應用程式的背景工作。當應用程式是在前景,因此任何程式碼必須留意可能發生競爭情形的檔案或登錄存取,無法啟動背景工作。

完成註冊後,您的應用程式會呼叫每次變更下的登錄中的程式庫。這可能包括您的應用程式並不感興趣的檔案或無法處理。在此情況下,套用嚴格的篩選器時觸發背景工作的最佳辦法請確定有沒有浪費的背景處理。

尋找最最近已修改或加入檔案是針對索引子的單一查詢一樣容易。直接要求蒐集時間落在應用程式想要在其中的範圍中的所有檔案。相同排序與群組功能可供其他查詢也可以使用這裡一樣,如有需要。請注意索引子會在內部使用祖魯文的時間,因此請務必之前使用它們將所有時間字串都轉換成祖魯文。以下是建置查詢的方式:

QueryOptions options = new QueryOptions();
DateTimeOffset lastSearchTime = DateTimeOffset.UtcNow.AddHours(-1);
// This is the conversion to Zulu time, which is used by the indexer
string timeFilter = "System.Search.GatherTime:>=" +
  lastSearchTime.ToString("yyyy\\-MM\\-dd\\THH\\:mm\\:ss\\Z")
options.ApplicationSearchFilter += timeFilter;

在此情況下應用程式要從過去一小時取得所有結果。在某些應用程式中合理的詳細節省時間的最後一次的查詢並將它用於查詢反而但任何 DateTimeOffset 會運作。一旦應用程式都有它可以加以列舉稍早討論過的檔案後或使用清單來追蹤哪些檔案是新手的清單。

結合的變更追蹤搭配蒐集時間的兩個方法可讓 UWP 應用程式能夠變更追蹤檔案系統和回應在磁碟上輕鬆地變更。這些可能是相當新的 Api 記錄中的 Windows,但它們已經內建於 Windows 10 相片、 Groove 音樂、 OneDrive、 Cortana,及電影和電視應用程式中使用。您可以確信納入您的應用程式知道他們要加強這些絕佳的體驗。

一般最佳作法

有任何使用索引子的應用程式應該要注意若要避免產生任何錯誤並確定應用程式是盡量快速的幾件事。其中包括避免任何不必要地複雜的查詢效能嚴重不足部分的應用程式中正確使用屬性的列舉型別並熟知要留意的編製索引的延遲。

設計查詢的方式可以對其效能明顯的影響。當索引子執行查詢時對其備份資料庫時,某些查詢速度較快,因為如何在磁碟上配置資訊。檔案位置為基礎的篩選為一律快速索引子是能夠快速地消除大量的組件從查詢的索引。這樣可以節省處理器和 I/O 時間因為有較少需要能夠擷取及搜尋相符項目查詢詞彙時比較的字串。

這個索引子是強大到足以處理規則運算式,但其中一些表單惡名昭彰的導致速度變慢。可以包含在索引子查詢糟的情況就是後置詞搜尋。這是查詢具有給定值結束的所有詞彙。使用範例會查詢"* tion,"看起來似乎所有包含的文件單字以"tion"。 在每個語彙基元中的第一個字母排序索引,因為沒有快速方法來尋找符合此查詢的條款。它必須解碼整個索引中的每個權杖,並比較搜尋詞彙,這是非常慢。

列舉型別可以加速查詢但有在國際組建中未預期的行為。已建置搜尋系統的任何人都知道如何更快速執行列舉型別為基礎的比較會比執行字串比較。而這是索引子、 以及中則為 true。若要方便您的應用程式,屬性系統會提供數個列舉型別來篩選結果以較少的項目之前啟動昂貴的字串比較。一個常見的範例是使用 System.Kind 篩選來限制結果只是檔案的應用程式可以處理的種類,例如音樂或文件。

沒有的哪一個任何人使用列舉型別必須了解常見的錯誤。如果您的使用者應該要注意只有音樂檔案,然後在 en-us-我們 System.Kind:=music 新增至查詢的 Windows 版本要限制搜尋結果並加速查詢正常運作。這也適用於某些語言版本 — 甚至還不足以傳遞國際化測試 — 但是系統無法辨識為英文的詞彙 「 音樂 」 而改用加以剖析以當地語言將會失敗。

使用例如 System.Kind 列舉的正確方式是以清楚地表示應用程式要使用的值為列舉型別而非搜尋詞彙。這是使用列舉型別 #value 語法。例如,只是音樂結果篩選的正確方式會撰寫 System.Kind:=System.Kind#Music。這會在 Windows 隨附的所有語言中運作,並會將結果篩選到只是檔案系統可辨識為音樂檔案。

正確逸出進階查詢語法 (AQS) 可協助您確定您的使用者未遇到硬重現查詢問題。AQS 有一些功能可讓使用者包括引號或括號來影響查詢處理的方式。這表示應用程式必須要很小心以逸出任何可能包含這些字元的查詢詞彙。例如,搜尋文件 (8).docx 會產生剖析錯誤並傳回不正確的結果。而是應用程式應該逸出為 Document%288%29.docx 詞彙。這會傳回項目在索引中的符合搜尋詞彙,而不是需要系統嘗試剖析括號做為查詢的一部分。

AQS 以及如何確定您的查詢是正確的不同功能最深入報導,您可以簽出的文件 bit.ly/1Fhacfl。它有許多很棒的資訊,包括更多詳細此處所提及的秘訣。

關於索引延遲的附註: 索引不是立即表示出現在索引或索引子為基礎的通知中的項目要延遲稍微時寫入檔案。在一般的系統負載下的延遲要在大約是 100 毫秒,速度比大部分的應用程式可以查詢檔案系統中,所以可能明顯。有使用者可能會移轉數千個解決他們的機器的檔案而且索引子落後明顯。

在這些情況下,有兩件事要建議使用應用程式: 首先,它們應該保留查詢開啟透過應用程式是最想要的檔案系統位置。通常這是透過檔案系統應用程式要搜尋的位置建立 StorageFileQueryResult 物件。當索引子會看到應用程式有開啟的查詢時,它會透過所有其他範圍編制索引在這些範圍中的優先順序。但請務必不要這樣做較大的範圍超過所需。索引子會停止接受系統輪詢和處理的變更以最快速度它可以讓使用者可能會發現對系統效能的影響而發生這種情況的使用者正在使用中通知。

其他建議是警告使用者系統正在趕上與檔案作業。某些應用程式,例如 Cortana 顯示頂端的 UI,而其他人將會停止執行複雜的查詢並顯示簡易的經驗。是由您決定最適合您的應用程式使用經驗。

總結

這只是可供取用者的索引子和 Windows 10 中的 Windows 儲存體 Api 功能的快速教學。如需有關如何使用查詢來傳遞內容上的應用程式啟動或背景觸發程序的程式碼範例的詳細資訊,請檢查在小組的部落格 bit.ly/1iPUVIo。我們經常使用的是開發人員以確保搜尋 Api 最適合用來使用。我們很希望聽到您的意見反應執行什麼工作和您想要查看加入至介面。


Adam Wilson是 Windows 開發人員生態系統與平台小組的專案經理。他負責的 Windows 索引子和推播通知可靠性。他之前曾在儲存體 Api for Windows Phone 8.1。與他連絡 adwilso@microsoft.com

感謝以下技術專家對本文的審閱: Sami Khoury
Sami Khoury 是 Windows 開發人員生態系統與平台小組工程師。他會導致 Windows 索引子的開發