大型文本文件中的词频

2024-03-11

我试图读取一个大文本文件并输出其中的不同单词及其计数。到目前为止,我已经尝试了几次,这是迄今为止我想出的最快的解决方案。

private static readonly char[] separators = { ' ' };

public IDictionary<string, int> Parse(string path)
{
    var wordCount = new Dictionary<string, int>();

    using (var fileStream = File.Open(path, FileMode.Open, FileAccess.Read))
    using (var streamReader = new StreamReader(fileStream))
    {
        string line;
        while ((line = streamReader.ReadLine()) != null)
        {
            var words = line.Split(separators, StringSplitOptions.RemoveEmptyEntries);

            foreach (var word in words)
            {
                if (wordCount.ContainsKey(word))
                {
                    wordCount[word] = wordCount[word] + 1;
                }
                else
                {
                    wordCount.Add(word, 1);
                }
            }
        }
    }

    return wordCount;
}

我如何衡量我的解决方案

我有一个 200MB 的文本,我知道它的总字数(通过文本编辑器)。我正在使用Stopwatch class计算字数以确保准确性并测量所花费的时间。到目前为止,大约需要 9 秒。

其他尝试

  • 我尝试利用多线程通过 TPL 库。这涉及批处理多行,发送 将批量行处理为单独的任务并锁定 字典中的读/写操作。然而,这似乎并不 为我提供任何性能改进。
  • 大约花了30秒。我怀疑锁定读/写 字典成本太高而无法获得任何性能。
  • 我也看了ConcurrentDictionary类型,但是AddOrUpdate方法确实需要调用代码来处理 据我了解,同步并没有带来任何性能 益处。

我确信有更快的方法来实现这一目标!有没有更好的数据结构来解决这个问题?

欢迎对我的解决方案提出任何建议/批评 - 尝试在这里学习和改进!

Cheers.

更新:这是link https://www.dropbox.com/s/0k36gkqo1dxk3bg/test.txt?dl=0到我正在使用的测试文件。


我能给出的最好的简短答案是测量、测量、测量。Stopwatch很高兴能了解时间花在哪里,但最终你会用它来散布大量的代码,或者你将不得不为此目的找到一个更好的工具。我建议为此使用一个专用的分析器工具,有许多可用于 C# 和 .NET。


我通过三个步骤成功地减少了大约 43% 的总运行时间。

首先我测量了你的代码并得到了这个:

这似乎表明这里有两个我们可以尝试解决的热点:

  1. 字符串分割(SplitInternal)
  2. 字典维护(FindEntry、Insert、get_Item)

最后花费的时间是读取文件,我真的怀疑我们可以通过更改这部分代码来获得很多好处。这里的另一个答案提到使用特定的缓冲区大小,我尝试了这一点,但无法获得可测量的差异。

第一个,字符串分割,有点简单,但涉及重写一个非常简单的调用string.Split进入更多的代码。处理一行的循环我重写为:

while ((line = streamReader.ReadLine()) != null)
{
    int lastPos = 0;
    for (int index = 0; index <= line.Length; index++)
    {
        if (index == line.Length || line[index] == ' ')
        {
            if (lastPos < index)
            {
                string word = line.Substring(lastPos, index - lastPos);
                // process word here
            }
            lastPos = index + 1;
        }
    }
}

然后我将一个单词的处理重写为:

int currentCount;
wordCount.TryGetValue(word, out currentCount);
wordCount[word] = currentCount + 1;

这取决于以下事实:

  1. TryGetValue比检查单词是否存在然后检索其当前计数便宜
  2. If TryGetValue获取值失败(key不存在)则初始化currentCount此处的变量为其默认值,即 0。这意味着我们实际上不需要检查该单词是否确实存在。
  3. 我们可以通过索引器向字典添加新单词(它将覆盖现有值或向字典添加新的键+值)

最终的循环如下所示:

while ((line = streamReader.ReadLine()) != null)
{
    int lastPos = 0;
    for (int index = 0; index <= line.Length; index++)
    {
        if (index == line.Length || line[index] == ' ')
        {
            if (lastPos < index)
            {
                string word = line.Substring(lastPos, index - lastPos);
                int currentCount;
                wordCount.TryGetValue(word, out currentCount);
                wordCount[word] = currentCount + 1;
            }
            lastPos = index + 1;
        }
    }
}

新的测量表明:

Details:

  1. 我们从 6876 毫秒变为 5013 毫秒
  2. 我们失去了花在SplitInternal, FindEntry and get_Item
  3. 我们赢得了花在TryGetValue and Substring

以下是差异细节:

正如您所看到的,我们损失的时间比获得的新时间多,这导致了净改进。

然而,我们可以做得更好。我们在这里进行两次字典查找,其中涉及计算单词的哈希码,并将其与字典中的键进行比较。第一个查找是TryGetValue第二个是wordCount[word] = ....

我们可以通过在字典内创建更智能的数据结构来删除第二次字典查找,但代价是使用更多的堆内存。

我们可以使用 Xanatos 的技巧将计数存储在对象内,以便我们可以删除第二个字典查找:

public class WordCount
{
    public int Count;
}

...

var wordCount = new Dictionary<string, WordCount>();

...

string word = line.Substring(lastPos, index - lastPos);
WordCount currentCount;
if (!wordCount.TryGetValue(word, out currentCount))
    wordCount[word] = currentCount = new WordCount();
currentCount.Count++;

这只会从字典中检索计数,添加 1 次额外出现不涉及字典。该方法的结果也会更改为返回此WordCount键入作为字典的一部分而不仅仅是int.

最终结果:节省约 43%。

最后一段代码:

public class WordCount
{
    public int Count;
}

public static IDictionary<string, WordCount> Parse(string path)
{
    var wordCount = new Dictionary<string, WordCount>();

    using (var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.None, 65536))
    using (var streamReader = new StreamReader(fileStream, Encoding.Default, false, 65536))
    {
        string line;
        while ((line = streamReader.ReadLine()) != null)
        {
            int lastPos = 0;
            for (int index = 0; index <= line.Length; index++)
            {
                if (index == line.Length || line[index] == ' ')
                {
                    if (lastPos < index)
                    {
                        string word = line.Substring(lastPos, index - lastPos);
                        WordCount currentCount;
                        if (!wordCount.TryGetValue(word, out currentCount))
                            wordCount[word] = currentCount = new WordCount();
                        currentCount.Count++;
                    }
                    lastPos = index + 1;
                }
            }
        }
    }

    return wordCount;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

大型文本文件中的词频 的相关文章

随机推荐

  • 寻找 Wii 兼容的 Javascript Flash mp3 播放器

    我正在寻找一款能够在 Wii 上基于 Opera 的浏览器中运行的 flash mp3 播放器 播放器需要启用 javascript 支持播放 停止等方法 mp3 曲目列表将使用与播放器相同的页面上的 ajax 动态构建 因此当曲目完成播放
  • Android:在 Android 设备选择器中,同一设备会出现多次

    我正在使用 Eclipse 为 Android 操作系统编程 我使用真实设备来测试我的应用程序 为了测试我的应用程序 我单击 运行 然后单击我想要运行的目标项目 然后弹出 Android 设备选择器 我可以在其中选择要运行该应用程序的设备或
  • 脚本标签中的文本属性 - 澄清?

    在阅读 Angular 的指令代码时 我看到this https github com angular angular js blob master src ng directive script js L43 var scriptDire
  • SVG 在视网膜屏幕上作为边框图像

    请考虑我们有简单的 SVG 文件 其中包含圆角半径等于 10 的圆角矩形的代码
  • Delphi通用约束问题

    我正在尝试创建一个与 tiOPF delphi www tiopf com 的对象持久框架 一起使用的通用列表类 具体来说 我试图采用现有的泛型类 TtiObjectList 并制作一个使用 TtiObject 后代的泛型版本 我更改基类的
  • prolog中输入/输出参数的区别

    Prolog谓词定义中的输入和输出参数有什么区别吗 这与其他语言 例如Scheme 和C 相比如何 我希望我理解你的问题 您应该研究一下 Prolog 中如何实现统一 因为它会让事情变得更清晰 反正 简而言之 没有内置方法可以将 Prolo
  • 为什么我不能在类上下文中引用 DATA?

    在 Ruby 中 存储静态文本真的很方便 END 通过任意使用DATAIO对象 puts DATA read Prints This is the stuff END This is the stuff 然而 当我尝试从新类的上下文中引用
  • JavaScript 可以捕获语法错误吗?

    MDN 状态 https developer mozilla org en US docs Web JavaScript Reference Global Objects SyntaxError 当 JavaScript 引擎在解析代码时遇
  • 如何在父级中绑定子用户控件数据上下文

  • 在 R 的 JAGS 或 BUGS 中指定离散威布尔分布

    我使用 R 中的 JAGS 将威布尔模型拟合到离散值 将威布尔模型拟合到连续数据没有问题 但当我切换到离散值时 我遇到了麻烦 以下是在 JAGS 中拟合威布尔模型的一些数据和代码 draw data from a weibull distr
  • C++ 变量声明语法

    我最近遇到了这个结构 整数 米 这似乎相当于 整数米 奇怪的是 我以前从未见过这个特殊的成语 有人可以给我指出一个参考资料 我可以在其中阅读相关规范 或者直接解释一下吗 这也适用于直 C 吗 谢谢 困惑的开发者 这不是一个 习语 它只是一对
  • 使用数据注释进行模型验证的错误消息

    给定以下课程 using System ComponentModel DataAnnotations public class Book public Contact PrimaryContact get set public Contac
  • React JS 不支持 Html“align”属性

    我是 ReactJS 的新手 在反应组件中 我已经 var SaveOrganization React createClass render function return div align center a href addVenue
  • Nuxt 3 - 如何每n分钟刷新一次获取的数据

    因此 在我的数据库中 数据每分钟都会刷新 数据实际上更新 我检查过 然后我在页面上显示这些数据 当我在页面之间切换以及手动刷新页面时 数据会被获取 但如果我坐在一个页面上例如 5 分钟 即使数据库中的数据更新 数据也不会在页面端刷新 是否可
  • IE8 CSS 和 html 与 IE7 对比

    请原谅这里的任何鲁莽 我正无能为力地寻找答案 我正在寻找从 IE7 更改为 IE8 的特定 html 和 css 标签的列表 如果存在 或一些资源指南 具体来说 我想看到类似 此代码在 IE7 中有效 但在 IE8 中无效 这是损坏的标签相
  • Javascript document.getSelection

    我正在尝试使用 document getSelection 来选择我在所见即所得编辑器的文本区域中输入的文本 但只有当您选择文本区域之外的文本时 它才有效 不知道有没有办法让它选择文本区域内的文本 下面是所见即所得文本编辑器的文本区域 您需
  • 从按分钟计算的原始数据中按小时聚合 MySQL 数据

    我有一个表 table 1 其中包含每分钟的数据 如下所示 date time value 2015 06 05 18 00 00 222 663 2015 06 05 18 01 00 222 749 2015 06 05 18 02 0
  • Mac系统上检测框架使用情况?

    我想在 OSX 上开发示例框架 并要求在任何时候该框架只能由单个客户端使用 我不知道如何实现这一点 他们有 API 来检测框架正在使用的天气吗 我们可以为此使用一些与文件相关的 API 吗 我看过一个 Windows 示例 其中使用以下命令
  • 在 jQuery 1.8 中的自定义过滤器选择器中获取“匹配”对象

    作为参考 这里有一篇文章使用 jQuery 创建自定义过滤器选择器 http answers oreilly com topic 1055 creating a custom filter selector with jquery 介绍 对
  • 大型文本文件中的词频

    我试图读取一个大文本文件并输出其中的不同单词及其计数 到目前为止 我已经尝试了几次 这是迄今为止我想出的最快的解决方案 private static readonly char separators public IDictionary