C# 基礎知識系列- 17 小工具優化


0. 前言

不知道有沒有動手能力強的小伙伴照着上一篇的內容寫過程序呢?如果有的話,應該會在使用的時候發現以下幾個問題:

  1. 每次啟動都需要經過漫長的時間去遍歷磁盤里的文件目錄
  2. 因為數據是用的字典保存的,所以會消耗大量的內存空間
  3. 不能多次查詢

現在我們就針對這些問題,讓我們的小工具實用起來。

1. 分析與實現

在動手之前,我們先分析一下問題。在實際開發之前,無論是接到什么需求都要先仔細分析一下,確定好方案再動手方為開發的正道。嗯,沒毛病。因為開發過程中跟產品對線、跟客戶對線要占整個項目的一半左右時間。好了,不廢話了。繼續:

遍歷文件目錄的時間過長,那么我們是不是可以用異步並發去遍歷呢?

數據用字典保存會消耗內存空間,那么我們是不是可以用其他的方式保存呢?

不能多次查詢,是不是可以使用循環,然后設置一個退出條件?

1.1 C#的異步/並發實現

在C#里,異步和並發的實現是依據線程、任務來實現的。在之前《C# 基礎知識系列- 12 任務和多線程》里大概介紹了一下線程和任務,我們知道線程本身是沒法返回數據的,它與主線程進行數據交互的過程十分需要注意線程安全。而任務可以返回數據,不需要像線程一樣小心翼翼地與主線程進行數據交互。任務有一個優點,它比線程更輕量,所以在當前環境下我們可以試試任務。

當然,線程也有優點,那就是線程的運行環境相對更封閉一點,它能完成一個長的大型運算。

那么繼續上一篇的內容,先引用 :

using System.Threading.Tasks;

先提取一組根據可枚舉目錄集合創建任務組並取得結果的方法:

public static Dictionary<string,List<string>> OverDirectories(IEnumerable<DirectoryInfo> directories)
{
    var tasks = directories.Select(dir => Task.Run(()=>OverDirectories(dir))).ToArray();
    Task.WaitAll(tasks);// 這行的意思是等待所有任務完成
    return Concat(tasks.Select(t=>t.Result).ToArray());
}

然后改造原有的OverDirectories方法:

public static Dictionary<string,List<string>> OverDirectories(DirectoryInfo rootDirectory)
{
    Console.WriteLine($"正在遍歷目錄:{rootDirectory.FullName}");
    var dict = new Dictionary<string, List<string>>();
    IEnumerable<FileInfo> files = new List<FileInfo>();
    try
    {
        files = rootDirectory.EnumerateFiles();
    }
    catch(Exception e)
    {
        Console.WriteLine($"錯誤信息:{e}");//打印錯誤信息
    }

    foreach(var file in files)
    {
        var key = Path.GetFileNameWithoutExtension(file.Name);
        if(!dict.ContainsKey(key))
        {
            dict[key] = new List<string>();
        }
        dict[key].Add(file.FullName);
    }
    try
    {
        var dirs = rootDirectory.EnumerateDirectories();
        return Concat(dict, OverDirectories(dirs));// 采用線程版的方法進行遍歷
    }
    catch (System.Exception e)
    {
        Console.WriteLine($"錯誤信息:{e}");//打印錯誤信息
    }
    return dict;
}

1.2 數據復用

理想狀態下,我們的數據應該是保存在數據庫的,但因為數據庫的操作是在下一系列的教程中,所以目前只能舍棄這個設想。

那么,利用現有方式,我們可以使用文件作為緩存的方式,也就是說把數據保存在文件里,在需要的時候從文件中讀取出來。這時候就需要一組操作文件的方法。

首先,聲明一個靜態變量:

public static readonly string TempFile = "temp.txt";

然后編寫讀取、存放數據的方法:

public static void WriteLinesToTemp(List<string> lines)
{
    File.AppendAllLines(TempFile, lines);
}

public static List<string> Search(string file)
{
    var lines = File.ReadLines(file);
    var results = lines.Where(line=>Path.GetFileNameWithoutExtension(line).Contains(file));
    return results.ToList();
}

這時候在文件中存放的都是路徑文件,所以需要重新修改遍歷文件路徑的方法,只保留路徑:

public static List<string> OverDirectories(DirectoryInfo rootDirectory)
{
    Console.WriteLine($"正在遍歷目錄:{rootDirectory.FullName}");
    List<string> files = new List<string>();
    try
    {    
        files.AddRange(rootDirectory.GetFiles().Select(f=>f.FullName).ToList());
        Console.WriteLine($"在目錄:{rootDirectory.FullName} 下 找到 文件:{files.Count} 個");
    }
    catch(Exception e)
    {
        Console.WriteLine($"加載目錄:{rootDirectory.FullName} 中\t錯誤信息:{e}");//打印錯誤信息
    }
    try
    {
        var dirs = rootDirectory.GetDirectories();
        OverDirectories(dirs);
    }
    catch (System.Exception e)
    {
        Console.WriteLine($"在下探目錄{rootDirectory.FullName}時發生錯誤:{e}");
    }
    return files;
}

public static void OverDirectories(IEnumerable<DirectoryInfo> directories)
{
    var tasks =new List<Task<List<string>>>( directories.Select(dir => Task.Run(()=>OverDirectories(dir))));
    while(tasks.Any())
    {
        var completeds = tasks.Where(t=>t.IsCompleted).ToList(); // 提取所有已完成的任務
        foreach(var t in completeds)
        {
            WriteLinesToTemp(t.Result);// 保存文件列表
            tasks.Remove(t);//移除已處理的任務
        }
    }
}

最后修改主方法,設置啟動時遍歷路徑的規則:

static void Main(string[] args)
{
    if(!File.Exists(TempFile))// 緩存文件存在,則認為上次已經遍歷成功了
    {
        var drivers = GetDrivers();
        OverDirectories(drivers);
    }

    Console.WriteLine("請輸入要查詢的文件名:");
    var search = Console.ReadLine().Trim();

}

1.3 循環使用並設置退出條件

設置用戶輸入q或Q的時候退出程序,這時候就需要改造Main方法了:

static void Main(string[] args)
{
    Console.WriteLine("文件查詢小工具啟動了……");
    if(!File.Exists(TempFile))
    {
        Console.WriteLine("尚未加載緩存記錄,數據加載中……");
        var drivers = GetDrivers();
        OverDirectories(drivers);
        Console.WriteLine("數據加載完成");
        Thread.Sleep(500);
        Console.Clear();// 清除控制台
    }
    while(true)
    {
        Console.WriteLine("請輸入要查詢的文件名(輸入q/Q 退出):");
        var search = Console.ReadLine().Trim();// 去除多余的空白字符
        if(search == "q" || search == "Q")//添加退出條件
        {
            break;
        }
        Console.WriteLine("查詢中……");
        var results = Search(search);
        Console.WriteLine("查詢結果:");
        foreach(var r in results)
        {
            Console.WriteLine(r);
        }
    }
    Console.WriteLine("程序已退出!");
}

在main 方法里加了很多提示語句,以方便使用。

2. 總結

以上是第一次實戰課的所有內容。歡迎各位小伙伴們踴躍討論。這個小工具並不完善,但是隨着我們對.net core的了解和深入就會寫的得心應手了。

更多內容煩請關注我的博客《高先生小屋》

file


免責聲明!

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



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