TextReader/StreamReader中是否有“跳转到行”的选项?

9
我有一个包含2.5万行的大文本文件。在该文本文件中,每一行都以“1 \t(行号)”开头。
例如:
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个回答

11

您可以使用我的LineReader类(无论是在MiscUtil中还是在此处的简单版本中)来实现IEnumerable<string>,然后使用LINQ:

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

这假设使用的是.NET 3.5,否则请打开TextReader(例如使用File.OpenText)并调用ReadLine()四次跳过不需要的行,再读取第五行。除非你确切地知道每行有多少字节,否则无法"捷径"。

2
流应该如何提前知道第5行的字节偏移量,BFree? - Matthew Flaschen
LineReader为什么没有Stream构造函数重载,只有Func<Stream>? - Matthew Flaschen
@Matthew.. 是的,我也是这么想的,只是想知道是否有我不知道的方法。 - BFree
1
我并不了解记事本的内部工作原理,但我猜测它会将整个文件读入内存。在内存中搜索是快速的(但显然会消耗内存)。此外,记事本需要知道所有换行符的位置才能正确绘制文本,因此它很可能会在程序启动时对其进行解析。 - driis
1
当然可以 - 它不会每次你说“转到行”时都从磁盘加载文件。(但高级编辑器可能会这样做 - 你试着将一个有25K行的文件加载到记事本中 - 它会在加载整个文件到内存时挂起;更加智能的编辑器会根据需要加载它。) - Jon Skeet
显示剩余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();
}

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


3
如果你处理的是固定宽度的数据格式(即你知道所有行的长度相同),你可以将长度乘以所需行数,并使用Stream.Seek找到第n行的起始点。
如果行不是固定长度的,你需要找到正确数量的换行符,直到到达所需行的开头。最简单的方法是使用StreamReader.ReadLine。(你可以像Jon Skeet建议的那样创建一个扩展方法,将文件作为IEnumerable返回 - 这会让语法更加优美,但在底层你仍然会使用ReadLine)。
如果性能是一个问题,手动使用Stream.Read方法在文件中扫描字节序列可能会(略微)更有效率。我没有测试过这个方法,但是StreamReader显然需要一些工作来将字节序列构造成字符串 - 如果你不关心前几行,这项工作可以被省略,因此理论上应该能够制作出更高效的扫描方法。然而,这对你来说需要做更多的工作。

这些行的长度不是固定的,但可以找到每行的长度。然而,对于包含 25,000 行的大文件,读取每一行需要时间。 - Ivan Prodanov
如果你只需要到第五行,它不需要读取所有的行... - Jon Skeet
如果您事先不知道每行的长度,那么除了逐行查找特定行之外,没有其他选择。没有魔法快捷方式。如果这是一个被附加数据的文件,并且您需要处理新数据,则可以在读取之间存储最后一个字节偏移量,并从下一次读取开始。 - driis

1

如果你需要查找文件中的许多不同行(但不是全部),那么在查找时建立索引可能会有所帮助。使用这里提供的任何建议,但是在进行查找时,为已经定位到的任何行建立一个字节偏移量数组,以便您可以避免每次重新扫描文件。

补充:
如果你只需要偶尔查找“随机”行,还有一种更复杂的搜索方式可以快速完成(如果Jon的答案足够快,出于简单起见,我肯定会坚持使用它)。

你可以通过“二分查找”来实现,只需从文件中间开始查找序列“1”,你找到的第一个出现位置将给你一个大致的行号;然后根据你要查找的行相对于找到的数字的位置进行递归拆分。

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

实现起来绝对不是简单的,但如果您需要在具有大量行数的文件中进行随机访问,则可能会带来性能上的收益。


0

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

如果行号很高,您可能希望根据文件中可能存在的位置进行某种有根据的猜测,并从那里开始阅读。这样,要到达第24,567行,您不必先读取24,566行。您可以跳到中间某个位置,根据/t后面的数字确定所在行,然后从那里开始计数。

一段时间以前,我与一位开发人员合作,在关系型数据库管理系统普及之前,他必须构建一个数据库。他解决您的问题的方法类似于我刚才写的,但在他的情况下,他在单独的文件中保留了一个映射。该映射可以将每一百行映射到文档中的位置。这样的映射可以非常快速地加载,这可能会增加读取时间。 当时,他的系统对于只读数据非常快速和高效,但对于读/写数据并不是很好。(每次更改行时,您都必须更改整个映射,这不是很有效率)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接