我有一个txt文件。现在,我需要逐行加载它,并检查整个文件中“@”的次数。
所以,基本上,我有一个单行字符串,如何快速获得'@'的出现次数?
我需要快速计算,因为我们有很多像这样的文件,每个文件大约300-400MB。
我搜索过,似乎直截了当的方式是最快的方法:
int num = 0;
foreach (char c in line)
{
if (c == '@') num++;
}
有没有比这更快的方法?还有其他建议吗?
由于
答案 0 :(得分:4)
最快的方法实际上与I / O功能和计算速度有关。通常,了解什么是最快技术的最佳方法是对它们进行基准测试。
免责声明:结果(当然)绑定到我的机器,并且可能在不同的硬件上有很大差异。为了测试,我使用了大约400MB的单个文本文件。如果感兴趣,可以下载文件here(压缩)。可执行文件编译为x86。
long count = 0;
var text = File.ReadAllText("C:\\tmp\\test.txt");
for(var i = 0; i < text.Length; i++)
if (text[i] == '@')
count++;
结果:
5828 ms
1674 MB
这是“天真”方法,它读取内存中的整个文件,然后使用for
循环(明显快于foreach
或LINQ)。
正如预期的那样,进程占用的内存非常高(大约是文件大小的4倍),这可能是由内存中的字符串大小(更多信息here)和字符串处理开销的组合引起的。
long count = 0;
using(var file = File.OpenRead("C:\\tmp\\test.txt"))
using(var reader = new StreamReader(file))
{
const int size = 500000; // chunk size 500k chars
char[] buffer = new char[size];
while(!reader.EndOfStream)
{
var read = await reader.ReadBlockAsync(buffer, 0, size); // read chunk
for(var i = 0; i < read; i++)
if(buffer[i] == '@')
count++;
}
}
结果:
4819 ms
7.48 MB
这是出乎意料的。在这个版本中,我们以500k字符的块来读取文件,而不是将其完全加载到内存中,并且执行时间甚至比前一种方法更低。请注意,减少块大小会增加执行时间(因为开销)。内存消耗极低(正如预期的那样,我们只将大约500kB / 1MB的内存直接加载到char数组中)。
通过更改块大小可以获得更好(或更差)的性能。
long count = 0;
using(var file = File.OpenRead("C:\\tmp\\test.txt"))
using(var reader = new StreamReader(file))
{
const int size = 2000000; // this is roughly 4 times the single threaded value
const int parallelization = 4; // this will split chunks in sub-chunks processed in parallel
char[] buffer = new char[size];
while(!reader.EndOfStream)
{
var read = await reader.ReadBlockAsync(buffer, 0, size);
var sliceSize = read/parallelization;
var counts = new long[parallelization];
Parallel.For(0, parallelization, i => {
var start = i * sliceSize;
var end = start + sliceSize;
if(i == parallelization)
end += read % parallelization;
long localCount = 0;
for(var j = start; j < end; j++)
{
if(buffer[(int)j] == '@')
localCount++;
}
counts[i] = localCount;
});
count += counts.Sum();
}
}
结果:
3363 ms
10.37 MB
正如预期的那样,这个版本的单线程版本表现更好,但不如我们想象的那么好4倍。与第一个版本相比,内存消耗再次非常低(与之前相同),我们正在利用多核环境。
块大小和并行任务数等参数可能会显着改变结果,您应该通过反复试验来找到最适合您的组合。
我倾向于认为“加载内存中的所有内容”版本是最快的,但这实际上取决于字符串处理和I / O速度的开销。并行分块的方法似乎是我机器中最快的,这应该引导你一个想法:当有疑问时,只需对它进行基准测试。
答案 1 :(得分:1)
您可以测试它是否更快,但更短的编写方式是:
int num = File.ReadAllText(filePath).Count(i => i == '@');
嗯,但我刚看到你也需要线数,所以这是类似的。同样,需要与你拥有的东西进行比较:
var fileLines = File.ReadAllLines(filePath);
var count = fileLines.Length();
var num = fileLines.Sum(line => line.Count(i => i == '@'));
答案 2 :(得分:-2)
你可以使用指针。我不知道这会更快。你必须做一些测试:
static void Main(string[] args)
{
string str = "This is @ my st@ing";
int numberOfCharacters = 0;
unsafe
{
fixed (char *p = str)
{
char *ptr = p;
while (*ptr != '\0')
{
if (*ptr == '@')
numberOfCharacters++;
ptr++;
}
}
}
Console.WriteLine(numberOfCharacters);
}
请注意,您必须进入项目属性并允许不安全的代码才能使此代码正常工作。