【问题标题】:how to get text file rows with no delimiter into array如何将没有分隔符的文本文件行放入数组
【发布时间】:2013-04-14 21:41:35
【问题描述】:

我有一个文本文件,我正试图将它输入到一个名为列的数组中。 文本文件中的每一行都属于我创建的子类中的不同属性。

例如,我的文本文件中的第 2 行是我想忽略的日期...我不想使用拆分,因为我没有分隔符,但我不知道替代方案。如果有人可以提供帮助,我不完全理解以下内容。当我尝试运行它时,它说 columns[1] 超出了它的范围...谢谢。

StreamReader textIn = 
    new StreamReader(
    new FileStream(path, FileMode.OpenOrCreate, FileAccess.Read));

//create the list
List<Event> events = new List<Event>();

while (textIn.Peek() != -1)
{
    string row = textIn.ReadLine();
    string[] columns = row.Split(' ');
    Event special = new Event();
    special.Day = Convert.ToInt32(columns[0]);
    special.Time = Convert.ToDateTime(columns[1]);
    special.Price = Convert.ToDouble(columns[2]);
    special.StrEvent = columns[3];
    special.Description = columns[4];
    events.Add(special);
}

输入文件示例:

1 8:00 PM 25.00 贝多芬第九交响曲 聆听路德维希·范·贝多芬的第九部也是最后一部杰作。 2 下午 6:00 15.00 棒球比赛 快来观看冠军球队打他们的劲敌——保证不会停工。

【问题讨论】:

  • 您能否显示您正在解析的数据示例行?
  • 您必须有分隔符或已知长度。否则你怎么能区分行?
  • 任何不使用现有序列化格式(和相应的类)的理由 - JSON 或 XML 更结构化,更容易转换为对象,至少 CSV 对于表格数据比属性列表要好得多.
  • 一个文本文件中有多个事件的可能性,还是每个文件一个事件?
  • 一个文本文件中有多个事件。 Grrr,我不知道如何在自己的行中获取我的示例,所以我在每一行都使用“/”....这是文本文件的示例:1/ 8:00 PM/25.00/Beethoven's 9th交响乐/聆听路德维希·范·贝多芬的第九部也是最后一部杰作。/ 2/ 下午 6:00/ 15:00/ 棒球比赛/ 来观看冠军球队打他们的劲敌——保证不会停工。/

标签: c#


【解决方案1】:

如果您的侧列值没有与 char 相交或具有固定大小,则可以删除分隔符。在这种情况下,您可以读取文件并在其上拆分字段。
如果您想从文件中读取数据并将数据自动加载到变量中,我建议将变量序列化和反序列化到文件,但该文件不是文本文件!

【讨论】:

  • 谢谢你们的帮助。
【解决方案2】:

好吧,一种方法(虽然有点难看)是使用File.ReadAllLines,然后循环遍历数组,如下所示:

string[] lines = File.ReadAllLines(path);

int index = 0;

while (index < lines.Length)
{

    Event special = new Event();
    special.Day = Convert.ToInt32(lines[index]);
    special.Time = Convert.ToDateTime(lines[index + 1]);
    special.Price = Convert.ToDouble(lines[index + 2]);
    special.StrEvent = lines[index + 3];
    special.Description = lines[index + 4];
    events.Add(special);

    lines = lines + 5;
}

这是非常脆弱的代码 - 很多东西都会破坏它。如果其中一个事件缺少一行怎么办?如果里面有多个空行怎么办?如果其中一个 Convert.Toxxx 方法抛出错误怎么办?

如果您可以选择更改文件的格式,我强烈建议您至少对其进行分隔。如果您无法更改格式,则需要使上面的代码示例更加健壮,以便它可以处理空行、转换失败、缺少行等。

使用分隔文件要容易得多。使用 XML 或 JSON 文件更容易。

分隔文件 (CSV)

假设您有相同的示例输入,但这次是 CSV 文件,如下所示:

1,8:00 PM,25.00,“贝多芬第九交响曲”,“聆听路德维希·范·贝多芬的第九首也是最后一首杰作。” 2,6:00 PM,15.00,“棒球比赛”,“来看看冠军球队打他们的劲敌——保证不会停工”

我在最后两项上加上引号,以防其中有逗号,它不会破坏解析。

对于 CSV 文件,我喜欢使用 Microsoft.VisualBasic.FileIO.TextFieldParser 类,尽管它的名称可以在 C# 中使用。不要忘记添加对 Microsoft.VisualBasic 的引用和 using 指令 ​​(using Microsoft.VisualBasic.FileIO;)。

以下代码将允许您解析上述 CSV 示例:

using (TextFieldParser parser = new TextFieldParser(path))
{

    parser.Delimiters = new string[] {","};
    parser.TextFieldType = Delimited;
    parser.HasFieldsEnclosedInQuotes = true;
    string[] parsedLine;

    while (!parser.EndOfData)
    {
        parsedLine = parser.ReadFields();

        Event special = new Event();
        special.Day = Convert.ToInt32(parsedLine[0]);
        special.Time = Convert.ToDateTime(parsedLine[1]);
        special.Price = Convert.ToDouble(parsedLine[2]);
        special.StrEvent = parsedLine[3];
        special.Description = parsedLine[4];
        events.Add(special);    
    }
}

这仍然有一些问题 - 您需要处理缺少字段的情况,我建议使用 TryParse 方法而不是 Convert.Toxxx,但它比非分隔符更容易(我认为)示例。

XML 文件(使用 LINQ to XML)

现在让我们用一个 XML 文件尝试一下,并使用 LINQ to XML 来获取数据:

<Events>
  <Event>
    <Day>1</Day>
    <Time>8:00 PM</Time>
    <Price>25.00</Price>
    <Title><![CDATA[Beethoven's 9th Symphone]]></Title>
    <Description><![CDATA[Listen to the ninth and final masterpiece by Ludwig van Beethoven.]]></Description>
  </Event>
  <Event>
    <Day>2</Day>
    <Time>6:00 PM</Time>
    <Price>15.00</Price>
    <Title><![CDATA[Baseball Game]]></Title>
    <Description><![CDATA[Come watch the championship team play their archrival--No work stoppages, guaranteed]]></Description>
  </Event>
</Events>

我使用 CDATA 作为标题和描述,这样特殊字符就不会破坏 XML 解析。

这很容易通过以下代码解析为您的事件:

XDocument doc = XDocument.Load(path);

List<Event> events = (from x in doc.Descendants("Event")
                     select new Event {
                                Day = Convert.ToInt32(x.Element("Day").Value),
                                Time = Convert.ToDateTime(x.Element("Time").Value),
                                Price = Convert.ToDouble(x.Element("Price").Value),
                                StrEvent = x.Element("Title").Value,
                                Description = x.Element("Description").Value
                     }).ToList();

当然,这仍然不是完美的,因为您仍然有可能转换失败或缺少元素。

管道分隔文件示例

根据我们在 cmets 中的讨论,如果您想使用管道 (|),您需要将每个事件(全部)放在一行中,如下所示:

1|8:00 PM|25.00|贝多芬第九交响曲|聆听路德维希·凡·贝多芬的第九部也是最后一部杰作。 2|6:00 PM|15.00,|棒球比赛|来观看冠军球队打他们的劲敌--保证不会停工

如果您愿意,您仍然可以使用上面的 TextFieldParser 示例(只需将分隔符从 , 更改为 |,或者如果您愿意,您可以使用原始代码。

一些最后的想法

我还想解决原始代码并说明它为什么不起作用。主要原因是您一次读取一行,然后在“ ”上拆分。如果所有字段都在同一行上,这将是一个好的开始(尽管由于 Time、StrEvent 和 Description 字段中的空格仍然会出现问题),但事实并非如此。

因此,当您阅读第一行(1)并在“”上拆分时,您会得到一个值(1)。当您尝试访问拆分数组的下一个元素时,您得到了索引超出范围错误,因为该行没有列 [1]。

基本上,您试图将每一行视为其中包含所有字段,而实际上每行一个字段。

【讨论】:

  • 我可以在我尝试过的文本中添加分隔符,但是当我尝试运行它时出现错误。我试过分隔符“|”通过在我的 row.Split('|') 中添加它并将其添加到每一行的末尾,我得到的错误是 special.Time = Convert.ToDateTime(columns[1]);不是有效的日期时间。
  • @JaeChang - 分隔符应该在每个字段之间,而不是在行尾。例如:1|8:00 PM|25.00|Beethoven's 9th Symphony|Listen to the ninth and final masterpiece by Ludwig van Beethoven..
  • 至于 not a valid DateTime 错误,我不知道你为什么会这样......如果该行是,例如8:00 PM|,我会期望索引超出 ranger 错误列 [1],假设您仅在 | 上拆分。如果你在 |和 ' ',然后是的,你会得到一个无效的 DateTime 格式错误,因为 columns[1] 将等于“PM”。
  • 非常感谢您的所有帮助和选择。我是一个新手,除了处理文本文件的课程外,我什么都没学到。我将尝试取消您对我的分隔符在每个字段之间的评论。谢谢蒂姆!
  • 不客气。不要忘记将所有字段放在一行上(换句话说,每个事件一行)。我会在我的回答中为你举另一个例子。
【解决方案3】:

对于您给定的示例文件,类似于

string[] lines = File.ReadAllLines(path);

for (int index = 4; index < lines.Length; index += 5)
{
    Event special = new Event();
    special.Day = Convert.ToInt32(lines[index - 4]);
    special.Time = Convert.ToDateTime(lines[index - 3]);
    special.Price = Convert.ToDouble(lines[index - 2]);
    special.StrEvent = lines[index - 1];
    special.Description = lines[index];
    events.Add(special);
}

会做这项工作,但就像蒂姆已经提到的那样,您应该考虑更改文件格式。

【讨论】:

  • 没想到这样使用 for 循环 - 我想知道 while 循环和 for 循环哪个更有效?
  • @Tim 就性能而言,在大多数情况下使用 while 循环还是 for 循环并不重要。一个例外是当您遍历一个数组并在 for 循环中使用循环条件中的“长度”字段时(在这种情况下,省略了对正文中数组访问的范围检查,请参阅link)。但我不知道这是否适用于像我在这里做的那样访问数组。
  • @Tobias 你能解释一下你给定的例子吗?我不确定索引 += 5 是如何工作的。为什么它增加5?我想将第 1 5 行作为一个对象,将第 2 5 行作为另一个对象等,因此我可以根据用户输入将其填充到文本框中。谢谢。
  • @Jae 基本思想是,for 循环的主体对文件中的每个Event 执行一次。所以它必须在每次迭代中读取 5 行,因此将 index(代表当前行)增加 5。使用 4 初始化索引,然后使用 [index-4][index-3]、... 和[index] 结合检查 index &lt; lines.length 意味着数组访问永远不会超出范围。
  • @Tobias Ahhh,我了解索引现在如何工作,但是每个循环如何区分?如果我想访问第三个循环的事件并将其显示在我的主窗体的文本框中。我将如何引用它?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-01-02
  • 1970-01-01
  • 2017-06-09
  • 1970-01-01
  • 2012-04-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多