最近由于工作的需要, 要分析大量C#代码, 在数万个cs文件中搜索特定关键词. 这是一项非常耗时的工作, 用Notepad++要运行接近半个小时. 于是我利用WEBUS2.0 SDK创建了一个代码搜索器程序, 非常方便的完成了这项工作.
Code Search程序首先会在选定的目录中搜索所有cs文件:
private void btnOpen_Click(object sender, EventArgs e) { try { if (folderBrowserDialog1.ShowDialog() == System.Windows.Forms.DialogResult.OK) { Task.Factory.StartNew(IndexProc); //... }
然后创建IIndexer, 并在一个线程中为所有找到的文件编制索引:
void IndexProc() { var files = Directory.GetFiles(folderBrowserDialog1.SelectedPath, "*.cs", SearchOption.AllDirectories); if (files != null && files.Length > 0) { //...this.ResetIndex(); foreach (var file in files) { Document doc = new Document(); doc.Fields.Add(new Field("FileName", file, FieldAttributes.None)); doc.Fields.Add(new Field("Code", StringHelper.LoadString(file), FieldAttributes.AnalyseIndex)); m_Index.Add(doc); //... } } //... } void ResetIndex() { if (m_Index != null) { m_Index.Close(); } m_Index = new IndexManager(new CodeAnalyzer()); m_Index.DumpDocs = 3000; m_Index.DumpSize = 10; m_Index.MinIndexSize = int.MaxValue; m_Index.MaxIndexSize = int.MaxValue; m_Index.MergeFactor = int.MaxValue; m_Index.New(AppDomain.CurrentDomain.BaseDirectory + @"Index"); m_Searcher = new IndexSearcher(m_Index); }
通过调节DumpDocs和DumpSize, 可以优化程序的内存占用;
通过调节Min/MaxIndexSize和MergeFactor, 可以优化程序的IO性能, 目前我设置的MinIndexSize最大意味着自始至终只会生成一个索引片段; MergeFactor最大意味着从不合并索引片段.
在创建索引的时候, 我们使用的是专门为代码分析设计的IAnalyzer:
class CodeAnalyzer : IAnalyzer { //...public ITokenStream GetTokenStream(Webus.Documents.Field field) { if (field.Name == "Code") { return this.GetTokenStream(field.Value.ToString()); } else { return null; } } } class CodeTokenStream : ITokenStream { HashSet<string> stops = new HashSet<string>(new string[] { "abstract", "event", "new", //... "enum", "namespace", "string" }); Queue<Token> m_Buffer = new Queue<Token>(); public CodeTokenStream(string text) { MatchCollection mc = Regex.Matches(text, @"w+"); foreach (Match m in mc) { var key = m.Value.ToLower(); if (stops.Contains(key) == false) { m_Buffer.Enqueue(new Token(key, m.Index, m.Length)); } } } //...
}
这个分析器中包含了所有C#的关键词, 由于他们是绝对高频词并且没有搜索的意义, 因此在分析的时候会跳过这些词汇而不做任何处理.
在编制索引的时候通过事件将状态更新到UI上面:
private void frmCodeSearch_Load(object sender, EventArgs e) { try { this.StatusChanged += new StatusChangeEventHandler(frmCodeSearch_StatusChanged); //... } delegate void UpdateUI(); void frmCodeSearch_StatusChanged(object sender, string status) { this.Invoke(new UpdateUI(() => { this.txtStatus.Text = status; })); }
这里是跨线程更新UI, 因此需要使用this.Invoke来封送相应操作.
索引编制过程中就可以开始搜索了:
对应代码如下:
private void txtKeyword_TextChanged(object sender, EventArgs e) { try { TermQuery query = new TermQuery(new Term("Code", txtKeyword.Text.ToLower())); var hits = m_Searcher.Search(query); List<SearchResult> result = new List<SearchResult>(); foreach (HitDoc hit in hits) { StandardHighlighter hl = new StandardHighlighter(hit); result.Add(new SearchResult(hit)); } dgvResult.DataSource = result; } catch (Exception ex) { MessageBox.Show(ExceptionHelper.ToString(ex)); } }
创建一个TermQuery对象, 对Code字段进行搜索, 构建List<SearchResult>类型的结果集, 并且绑定到DataGridView上面, 大功告成! enjoy~!
2013-7-21补充:
增强了搜索功能, 支持WEBUS2.0 SDK的查询表达式, 能够完成各种复杂的搜索任务. 具体语法将在后面的文章中介绍.
2013-8-20补充:
Build - 选择一个文件夹开始编制索引, 编好的索引自动保存在当前目录的CodeSearch.Index子目录下面. 比如我们选择C:SourceCode来编制索引, 这索引数据会保存在C:SourceCodeCodeSearch.Index中.
Open - 打开已经存在的索引, 即上面所说的CodeSearch.Index文件夹.
关闭程序时会自动关闭当前索引. 索引关闭后全部数据都保存到磁盘上, 下次可以直接打开来继续使用.