我有一个文本文件包含以下类似的行,例如500k行。
ADD GTRX:TRXID=0, TRXNAME="M_RAK_JeerExch_G_1879_18791_A-0", FREQ=81, TRXNO=0, CELLID=639, IDTYPE=BYID, ISMAINBCCH=YES, ISTMPTRX=NO, GTRXGROUPID=2556;
ADD GTRX:TRXID=1, TRXNAME="M_RAK_JeerExch_G_1879_18791_A-1", FREQ=24, TRXNO=1, CELLID=639, IDTYPE=BYID, ISMAINBCCH=NO, ISTMPTRX=NO, GTRXGROUPID=2556;
ADD GTRX:TRXID=5, TRXNAME="M_RAK_JeerExch_G_1879_18791_A-2", FREQ=28, TRXNO=2, CELLID=639, IDTYPE=BYID, ISMAINBCCH=NO, ISTMPTRX=NO, GTRXGROUPID=2556;
ADD GTRX:TRXID=6, TRXNAME="M_RAK_JeerExch_G_1879_18791_A-3", FREQ=67, TRXNO=3, CELLID=639, IDTYPE=BYID, ISMAINBCCH=NO, ISTMPTRX=NO, GTRXGROUPID=2556;
我的意图是首先获得FREQ
的价值,其中ISMAINBCCH=YES
我很容易做到,但如果ISMAINBCCH=NO
然后连接FREQ
值,我已经使用File.ReadLines
完成了,但它需要很长时间。有更好的方法吗?如果我采取FREQ
值为ISMAINBCCH=YES,然后连接值ISMAINBCCH=NO在上下10行的范围内,但我不知道如何实现它。也许我应该得到ISMAINBCCH=YES
为FREQ
的当前行。下面是我到目前为止所做的代码
using (StreamReader sr = File.OpenText(filename))
{
while ((s = sr.ReadLine()) != null)
{
if (s.Contains("ADD GTRX:"))
{
try
{
var gtrx = new Gtrx
{
CellId = int.Parse(PullValue(s, "CELLID")),
Freq = int.Parse(PullValue(s, "FREQ")),
//TrxNo = int.Parse(PullValue(s, "TRXNO")),
IsMainBcch = PullValue(s, "ISMAINBCCH").ToUpper() == "YES",
Commabcch = new List<string> { PullValue(s, "ISMAINBCCH") },
DEFINED_TCH_FRQ = null,
TrxName = PullValue(s, "TRXNAME"),
};
var result = String.Join(",",
from ss in File.ReadLines(filename)
where ss.Contains("ADD GTRX:")
where int.Parse(PullValue(ss, "CELLID")) == gtrx.CellId
where PullValue(ss, "ISMAINBCCH").ToUpper() != "YES"
select int.Parse(PullValue(ss, "FREQ")));
}
}
}
gtrx.DEFINED_TCH_FRQ = result;
}
from ss in File.ReadLines(filename)
读取整个文件,生成一个数组,然后在循环中使用该数组(本身来自读取同一文件),因此该数组被丢弃,然后再次创建。你正在读取同一个文件number_of_lines + 1次,当它在此期间没有改变。
因此,一个明显的提升将是只调用File.ReadLines(filename)
一次,存储数组,然后在循环中使用该数组而不是while ((s = sr.ReadLine()) != null)
,并在循环中使用该数组而不是重复调用ReadLines()
。
但是你的逻辑有一个缺陷,即使是反复看ReadLines()
;你已经浏览了整个文件,所以你会在后面遇到与同一个CELLID
相关的所有行:
var gtrxDict = new Dictionary<int, Gtrx>();
using (StreamReader sr = File.OpenText(filename))
{
while ((s = sr.ReadLine()) != null)
{
if (s.Contains("ADD GTRX:"))
{
int cellID = int.Parse(PullValue(s, "CELLID"));
Gtrx gtrx;
if(gtrxDict.TryGetValue(cellID, out gtrx)) // Found previous one
gtrx.DEFINED_TCH_FRQ += "," + int.Parse(PullValue(ss, "FREQ"));
else // First one for this ID, so create a new object
gtrxDict[cellID] = new Gtrx
{
CellId = cellID,
Freq = int.Parse(PullValue(s, "FREQ")),
IsMainBcch = PullValue(s, "ISMAINBCCH").ToUpper() == "YES",
Commabcch = new List<string> { PullValue(s, "ISMAINBCCH") },
DEFINED_TCH_FRQ = int.Parse(PullValue(ss, "FREQ")).ToString(),
TrxName = PullValue(s, "TRXNAME"),
};
}
}
}
通过这种方式,我们根本不需要在内存中保存文件中的多行,更不用说重复这样做了。运行完之后,gtrxDict
将为文件中的每个不同的CELLID
包含一个Gtrx
对象,DEFINED_TCH_FRQ
作为每个匹配行的逗号分隔值列表。
下面的代码片段可用于读取整个文本文件:
using System.IO;
/// Read Text Document specified by full path
private string ReadTextDocument(string TextFilePath)
{
string _text = String.Empty;
try
{
// open file if exists
if (File.Exists(TextFilePath))
{
using (StreamReader reader = new StreamReader(TextFilePath))
{
_text = reader.ReadToEnd();
reader.Close();
}
}
else
{
throw new FileNotFoundException();
}
return _text;
}
catch { throw; }
}
获取内存字符串,然后应用Split()
函数创建string[]
,并以与原始文本文件中的行相同的方式处理数组元素。在处理非常大的文件的情况下,该方法提供了按数据块读取它的选项,处理它们,然后在完成后处理(re: https://msdn.microsoft.com/en-us/library/system.io.streamreader%28v=vs.110%29.aspx)。
正如@Michael Liu在评论中提到的,还有另一个使用File.ReadAllText()
的选择,它提供了更紧凑的解决方案,可以代替reader.ReadToEnd()
使用。File
类的其他有用方法详见:https://msdn.microsoft.com/en-us/library/system.io.file%28v=vs.110%29.aspx
最后,FileStream
类可以用于不同粒度级别的文件读写操作(re: https://msdn.microsoft.com/en-us/library/system.io.filestream%28v=vs.110%29.aspx)。
为了回应有趣的评论线程,这里有一个简短的总结。
与PO问题中描述的过程相关的最大瓶颈是磁盘IO操作。这里有一些数字:在高质量的硬盘中,平均寻道时间大约是5毫秒加上实际的读取时间(每行)。很可能整个内存文件数据处理比单个HDD IO读取花费的时间更少(有时显着;顺便说一句,SSD工作得更好,但仍然不能与DDR3 RAM相匹配)。现代个人电脑的RAM内存大小相当显著(通常为4…8)GB RAM足以处理大多数文本文件)。因此,我的解决方案的核心思想是最小化磁盘IO读取操作,并在内存中完成整个文件数据处理。显然,实现可以有所不同。
希望这能有所帮助。最好的祝福,
我认为这或多或少能得到你想要的。
首先读取所有数据:
var data =
(
from s in File.ReadLines(filename)
where s != null
where s.Contains("ADD GTRX:")
select new Gtrx
{
CellId = int.Parse(PullValue(s, "CELLID")),
Freq = int.Parse(PullValue(s, "FREQ")),
//TrxNo = int.Parse(PullValue(s, "TRXNO")),
IsMainBcch = PullValue(s, "ISMAINBCCH").ToUpper() == "YES",
Commabcch = new List<string> { PullValue(s, "ISMAINBCCH") },
DEFINED_TCH_FRQ = null,
TrxName = PullValue(s, "TRXNAME"),
}
).ToArray();
根据加载的数据创建一个查找以返回基于每个单元格id的频率:
var lookup =
data
.Where(d => !d.IsMainBcch)
.ToLookup(d => d.CellId, d => d.Freq);
现在基于查找更新DEFINED_TCH_FRQ
:
foreach (var d in data)
{
d.DEFINED_TCH_FRQ = String.Join(",", lookup[d.CellId]);
}