【问题标题】:Is there an option "go to line" in TextReader/StreamReader?TextReader/StreamReader 中是否有“转到行”选项?
【发布时间】:2009-05-31 12:30:18
【问题描述】:

我有一个包含 25k 行的巨大文本文件。在该文本文件中,每一行都以“1 \t (linenumber)”开头

例子:

1   1   ITEM_ETC_GOLD_01    골드(소)   xxx xxx xxx_TT_DESC 0   0   3   3   5   0   180000  3   0   1   0   0   255 1   1   0   0   0   0   0   0   0   0   0   0   -1  0   -1  0   -1  0   -1  0   -1  0   0   0   0   0   0   0   100 0   0   0   xxx item\etc\drop_ch_money_small.bsr    xxx xxx xxx 0   2   0   0   1   0   0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0   0   0   0   0   0   0   0   0   0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1   표현할 골드의 양(param1이상) -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx 0   0
1   2   ITEM_ETC_GOLD_02    골드(중)   xxx xxx xxx_TT_DESC 0   0   3   3   5   0   180000  3   0   1   0   0   255 1   1   0   0   0   0   0   0   0   0   0   0   -1  0   -1  0   -1  0   -1  0   -1  0   0   0   0   0   0   0   100 0   0   0   xxx item\etc\drop_ch_money_normal.bsr   xxx xxx xxx 0   2   0   0   1   0   0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0   0   0   0   0   0   0   0   0   0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1000    표현할 골드의 양(param1이상) -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx 0   0
1   3   ITEM_ETC_GOLD_03    골드(대)   xxx xxx xxx_TT_DESC 0   0   3   3   5   0   180000  3   0   1   0   0   255 1   1   0   0   0   0   0   0   0   0   0   0   -1  0   -1  0   -1  0   -1  0   -1  0   0   0   0   0   0   0   100 0   0   0   xxx item\etc\drop_ch_money_large.bsr    xxx xxx xxx 0   2   0   0   1   0   0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0   0   0   0   0   0   0   0   0   0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 10000   표현할 골드의 양(param1이상) -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx 0   0
1   4   ITEM_ETC_HP_POTION_01   HP 회복 약초    xxx SN_ITEM_ETC_HP_POTION_01    SN_ITEM_ETC_HP_POTION_01_TT_DESC    0   0   3   3   1   1   180000  3   0   1   1   1   255 3   1   0   0   1   0   60  0   0   0   1   21  -1  0   -1  0   -1  0   -1  0   -1  0   0   0   0   0   0   0   100 0   0   0   xxx item\etc\drop_ch_bag.bsr    item\etc\hp_potion_01.ddj   xxx xxx 50  2   0   0   1   0   0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0   0   0   0   0   0   0   0   0   0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 120 HP회복양   0   HP회복양(%)    0   MP회복양   0   MP회복양(%)    -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx 0   0
1   5   ITEM_ETC_HP_POTION_02   HP 회복약 (소)  xxx SN_ITEM_ETC_HP_POTION_02    SN_ITEM_ETC_HP_POTION_02_TT_DESC    0   0   3   3   1   1   180000  3   0   1   1   1   255 3   1   0   0   1   0   110 0   0   0   2   39  -1  0   -1  0   -1  0   -1  0   -1  0   0   0   0   0   0   0   100 0   0   0   xxx item\etc\drop_ch_bag.bsr    item\etc\hp_potion_02.ddj   xxx xxx 50  2   0   0   2   0   0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0   0   0   0   0   0   0   0   0   0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 220 HP회복양   0   HP회복양(%)    0   MP회복양   0   MP회복양(%)    -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx 0   0
1   6   ITEM_ETC_HP_POTION_03   HP 회복약 (중)  xxx SN_ITEM_ETC_HP_POTION_03    SN_ITEM_ETC_HP_POTION_03_TT_DESC    0   0   3   3   1   1   180000  3   0   1   1   1   255 3   1   0   0   1   0   200 0   0   0   4   70  -1  0   -1  0   -1  0   -1  0   -1  0   0   0   0   0   0   0   100 0   0   0   xxx item\etc\drop_ch_bag.bsr    item\etc\hp_potion_03.ddj   xxx xxx 50  2   0   0   3   0   0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0   0   0   0   0   0   0   0   0   0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 370 HP회복양   0   HP회복양(%)    0   MP회복양   0   MP회복양(%)    -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx 0   0
1   7   ITEM_ETC_HP_POTION_04   HP 회복약 (대)  xxx SN_ITEM_ETC_HP_POTION_04    SN_ITEM_ETC_HP_POTION_04_TT_DESC    0   0   3   3   1   1   180000  3   0   1   1   1   255 3   1   0   0   1   0   400 0   0   0   7   140 -1  0   -1  0   -1  0   -1  0   -1  0   0   0   0   0   0   0   100 0   0   0   xxx item\etc\drop_ch_bag.bsr    item\etc\hp_potion_04.ddj   xxx xxx 50  2   0   0   4   0   0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0   0   0   0   0   0   0   0   0   0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 570 HP회복양   0   HP회복양(%)    0   MP회복양   0   MP회복양(%)    -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx -1  xxx 0   0

问题:如何直接读取,例如第5行?

【问题讨论】:

    标签: c# .net text


    【解决方案1】:

    您可以使用我的LineReader 类(MiscUtilsimple version here 中的一个)来实现IEnumerable<string>,然后使用 LINQ:

    string line5 = new LineReader(file).Skip(4).First();
    

    这是假设 .NET 3.5,诚然。否则,打开一个TextReader(例如使用File.OpenText)并调用ReadLine() 四次以跳过您不想要的行,然后再次阅读第五行。

    除非您确切知道每行中有多少字节,否则没有办法“捷径”。

    【讨论】:

    • 在引擎盖下仍然逐行读取,直到到达您想要的行。有没有办法直接到第 5 行?
    • Stream 应该如何提前知道第 5 行的字节偏移量,BFree?
    • 是否有原因 LineReader 没有 Stream 构造函数重载,只有 Func
    • 不是我知道记事本的内部工作原理,但我猜它会将整个文件读入内存。在内存中搜索很快,(但显然会消耗内存)。此外,记事本需要知道所有换行符在哪里才能正确绘制文本,因此它很可能在程序启动时对其进行解析。
    • 绝对 - 它不像每次您说“转到行”时都会从磁盘加载文件。 (高级编辑器这样做 - 您尝试将一个 25K 行的文件加载到记事本中 - 它会在将大量文件加载到内存时挂起;更智能的编辑器会根据需要加载它。)跨度>
    【解决方案2】:

    如果您正在处理固定宽度的数据格式(即您知道所有行的长度相同),您可以将长度乘以所需的行号并使用 Stream.Seek 找到第 n 行。

    如果行不是固定长度,则需要找到正确数量的换行符,直到位于所需行的开头。使用 StreamReader.ReadLine 最容易做到这一点。 (您可以使用扩展方法使文件成为 IEnumerable ,正如 Jon Skeet 所建议的那样 - 这将使您获得更好的语法,但在幕后您将使用 ReadLine)。

    如果性能是一个问题,使用 Stream.Read 方法手动扫描文件中的 字节序列可能(稍微)更有效。我还没有测试过;但是StreamReader显然需要做一些工作来从字节序列中构造一个字符串——如果你不关心第一行,这个工作可以省去,所以理论上你应该可以做出一个性能更好的扫描方法.但是,这对您来说需要做更多的工作。

    【讨论】:

    • 这些行不是固定长度的,但是可以找到每行的长度。但是,对于25 000行的大文件,读取每一行需要时间。跨度>
    • 如果你只是去第 5 行,它不需要阅读所有的行虽然...
    • 如果您事先不知道每条线的长度,那么除了以一种或另一种方式遍历每条线以找到特定线之外,别无选择。没有神奇的捷径。如果这是一个附加的文件,并且您需要处理新数据,则可以存储读取之间的最后一个字节偏移量,并在下一次读取时从那里开始。
    【解决方案3】:

    除非每一行都是固定宽度并且您使用的是固定宽度编码(即不是 UTF-8 - 这是现在最常见的编码之一),否则您不能直接跳转到文本文件中的一行。

    这样做的唯一方法是读取行并丢弃不需要的行。

    或者,您可以在文件顶部(或在外部文件中)放置一个索引,告诉它(例如)第 1000 行从字节偏移 [x] 开始,第 2000 行从字节偏移 [y] 开始等。然后在FileStream上使用.Position.Seek()移动到最近的索引点,然后向前走。

    假设最简单的方法(无索引),Jon 示例中的代码应该可以正常工作。如果你不想要 LINQ,你可以在 .NET 2.0 + C# 2.0 中敲出类似的东西:

    // to read multiple lines in a block
    public static IEnumerable<string> ReadLines(
            string path, int lineIndex, int count) {
        if (string.IsNullOrEmpty(path)) throw new ArgumentNullException("path");
        if (lineIndex < 0) throw new ArgumentOutOfRangeException("lineIndex");
        if (count < 0) throw new ArgumentOutOfRangeException("count");
        using (StreamReader reader = File.OpenText(path)) {
            string line;
            while (count > 0 && (line = reader.ReadLine()) != null) {
                if (lineIndex > 0) {
                    lineIndex--; // skip
                    continue;
                }
                count--;
                yield return line;
            }
        }
    }
    // to read a single line
    public static string ReadLine(string path, int lineIndex) {
        foreach (string line in ReadLines(path, lineIndex, 1)) {
            return line;
        }
        throw new IndexOutOfRangeException();
    }
    

    如果您需要测试行的值(而不仅仅是行索引),那么这也很容易做到;只需调整迭代器块。

    【讨论】:

      【解决方案4】:

      如果您要从文件中查找很多不同的行(但不是全部),那么您可能会从构建索引中获得一些好处。使用这里已经存在的任何建议,但是随着您继续为您已经找到的任何行建立一个字节偏移数组,这样您就可以避免每次从头开始重新扫描文件。

      附录:
      如果您只需要偶尔的“随机”行,还有另一种方法可以快速完成,但代价是搜索更复杂(如果乔恩的答案足够快,为了简单起见,我肯定会坚持下去)。

      您可以进行“二分查找”,只需从文件的中途开始查找序列“1”,您找到的第一个匹配项将使您知道找到的行号;然后根据您要查找的行相对于您不断递归拆分的找到的数字的位置。

      为了获得额外的性能,您还可以假设行的长度大致相同,并让算法“猜测”您要查找的行相对于文件中总行数的大致位置,然后执行这个搜索从那里开始。如果您不想对文件的长度做出假设,您甚至可以通过先将其分成两半并使用它首先找到的行号作为文件中有多少行的近似值来使其自质为一个整体。

      实现起来绝对不是微不足道的,但如果您在包含大量行的文件中进行大量随机访问,则可能会在性能提升方面有所回报。

      【讨论】:

        【解决方案5】:

        如果您需要能够使用在后台执行 ReadLine() 的函数跳转到第 24,000 行,会有点慢。

        如果行号很高,您可能想要对文件中的行可能在哪里进行某种有根据的猜测,然后从那里开始读取。这样就可以到达第 24,567 行,您不必先阅读 24,566 行。你可以跳到中间的某个地方,根据/t后面的数字找出你在哪一行,然后从那里开始计数。

        不久前,我与一位开发人员一起工作,他必须在 RDBMS 很常见之前构建一个数据库。他对您的问题的解决方案与我刚刚写的类似,但在他的情况下,他将地图保存在单独的文件中。该地图可以将每一百行映射到其在文档中的位置。像这样的地图可以非常快速地加载,这可能会增加阅读时间。 当时他的系统对于只读数据非常快速和高效,但对于读/写数据却不是很好。 (每次换线都要换整张图,效率不高)

        【讨论】:

          猜你喜欢
          • 2012-05-20
          • 1970-01-01
          • 2013-02-08
          • 1970-01-01
          • 2016-08-10
          • 1970-01-01
          • 2013-02-06
          • 1970-01-01
          • 2014-08-13
          相关资源
          最近更新 更多