从 C# 中的文本文件一次读取 10 行

Posted

技术标签:

【中文标题】从 C# 中的文本文件一次读取 10 行【英文标题】:Read 10 lines at a time from text file in C# 【发布时间】:2021-05-08 13:09:18 【问题描述】:

我正在寻找一种解决方案,从文本文件中读取 10 行,然后再读取 10 行直到文件末尾。 这是我开始的,当然它显示了前 10 行,但是我怎样才能在接下来的 10 行等中重复这个过程,直到文件结束?

private void openFile_Click(object sender, EventArgs e)

    int counter = 0;
    string line;

    using (var file =
       new System.IO.StreamReader(@"C:\\Users\\LJ_TEX\\Desktop\\Book1.txt"))
    
        while ((line = file.ReadLine()) != null)
        
            counter++;
            if (counter <= 10)
            
                tboxreadData.AppendText(line + '\r' + '\n');
            
            if (counter == 10)
            
                tboxreadData.AppendText("NEXT");
            
        
    

编辑

所以我设法做了一点改变,用这个按钮点击显示一行

 System.IO.StreamReader file = null;

 private void openFile_Click(object sender, EventArgs e)
    
        string line;

        if (file == null)
            file = new System.IO.StreamReader(@"C:\\Users\\LJ_TEX\\Desktop\\Book1.txt");

        if (!file.EndOfStream)
        
            line = file.ReadLine();
            tboxreadData.AppendText(line + '\r' + '\n');
        
        else
        
            tboxreadData.AppendText("End");
        
       
    

当然,它一次只显示一行。如果有人知道如何显示更多行(5 行或 10 行),请随时分享。

谢谢, 乔纳森

【问题讨论】:

在第二个if-statement 中重置计数器:counter = 0; 我看到你的 openFile_Click 方法是一个点击事件。如果我正确理解您的问题,您想在每次点击时显示接下来的 10 行吗?如果是这样,您需要将您的计数器变量设为全局变量或从 EventArgs e 传递它。 通常情况下,您不会这样做。您将读取固定数量的字节,即所谓的“缓冲区”,然后根据那里有多少行来检查它。这是为了提高效率,它可以让您处理非常大的文件(TB 级数据),因为每时每刻您都不需要更多的缓冲区。如果您不需要效率,您可以只读取所有行,然后将它们批处理到内存中。 您能否更详细地解释一下,@Müller 如何做到这一点? 【参考方案1】:

您可以创建自己的扩展方法,该方法采用IEnumerbale&lt;T&gt; 并将其划分为块。从那里您可以使用 foreach 或任何标准 linq 样式链方法对其进行迭代

给定

// updated, this is completely lazy loaded
public static class Extensions

   public static IEnumerable<IEnumerable<TValue>> Partition<TValue>(this IEnumerable<TValue> source, int size)
   
      using var enumerator = source.GetEnumerator();
      static IEnumerable<T> GetChunk<T>(IEnumerator<T> enumerator, int chunkSize)
      
         for (var i = 0; i < chunkSize && enumerator.MoveNext(); i++)
            yield return enumerator.Current;
      

      while (enumerator.MoveNext())
         yield return GetChunk(enumerator, size);
   

用法

var partitions = File
     .ReadLines(@"D:\ids.txt")
     .Partition(10);

foreach (var partition in partitions)

   Console.WriteLine("Start partition");
   foreach (var line in partition )
      Console.WriteLine(line);

输出

Start partition
1516556 E763794 1773595 33EBM5015703
1516556 E763794 1773657 33EBM5015703
1518933 E764710 1776347 33EBM5015705
1519147 H182989 1776617 33EBM5015702
1519264 H183025 1776745 33EBM5015686
1519310 X186946 1776805 33EBM5015695
1519622 X186992 1777159 33EBM5015704
1519623 E765015 1777160 33EBM5015716
1519818 H183205 1777375 33EBM5015681
1519932 E765141 1777546 33EBM5015700
Start partition
1520345 E765295 1778070 33EBM5015715
1520366 E765303 1778097 33EBM5015684
1520385 X187075 1778117 33EBM5015675
1520420 H183413 1778157 33EBM5015662
1520429 H183418 1778166 33EBM5015654
1520466 X187085 1778203 33EBM5015663
1520468 E765345 1778205 33EBM5015658
1520476 E765349 1778214 33EBM5015677
1520486 H183441 1778224 33EBM5015664
1520496 H183444 1778234 33EBM5015671
Start partition
1520506 E765361 1778244 33EBM5015666
1520510 E765364 1778248 33EBM5015670
1520528 H183462 1778270 33EBM5015680
1520550 H183474 1778292 33EBM5015653
1520553 X187092 1778295 33EBM5015706
1520558 E765382 1778300 33EBM5015650
1520574 E765389 1778316 33EBM5015656
1520585 E765396 1778327 33EBM5015669
1520618 X187102 1778360 33EBM5015682
1520621 E765408 1778363 33EBM5015667

【讨论】:

【参考方案2】:

您可以通过使用模运算符 (%) 来实现。所以你检查你的counter%10=0 是否打印出“NEXT”-Line。

您的示例可能如下所示:

private void openFile_Click(object sender, EventArgs e)
    
        int counter = 0;
        string line;

        System.IO.StreamReader file = new System.IO.StreamReader(@"C:\\Users\\LJ_TEX\\Desktop\\Book1.txt");

        while ((line = file.ReadLine()) != null)
        
            counter++;               
            tboxreadData.AppendText(line + '\r' + '\n');
            if(counter%10 == 0)
                tboxreadData.AppendText("NEXT");
            
        
    

编辑:

对于每次读取 10 行(如果存在),您可以使用以下 sn-p。它不是高效的,因为您每次都会从头开始阅读:

int _lineCounter = 0;

private void openFile_Click(object sender, EventArgs e)
    
        int counter = 0;
        string line;

        System.IO.StreamReader file = new System.IO.StreamReader(@"C:\\Users\\LJ_TEX\\Desktop\\Book1.txt");

        while ((line = file.ReadLine()) != null)
        
            counter++;
            if(counter < _linecounter)
                continue;
            tboxreadData.AppendText(line + '\r' + '\n');
            if(counter%10 == 0)
                tboxreadData.AppendText("NEXT");
                break;
            
        
        _lineCounter = counter;
    

【讨论】:

是的,它确实有效,正如您所说,第 10 行已替换为“NEXT”。我怎样才能避免这种情况? 查看我的编辑。您只需移动 If 语句 它解决了问题,但是如果我想查看接下来的 10 行,每次点击按钮时怎么办? 你原来的问题,不包括这个场景。要么创建一个新问题,要么尝试从这个示例继续前进。您可以将 Stream 的位置保存在类变量中,然后在读取之前,将流的位置设置为最后一次保存。但请注意,文件可能已更改。另一种方法是,如果文件不是那么大,则将整个文件加载到内存中一次,然后遍历这些行。为此,您可以将行保存在 List 或一些类似的模型中 查看我的编辑以了解您的新问题的可能解决方案【参考方案3】:

你想要的很常见:你想“每页”读取你的输入。

换句话说:你有一系列相似的项目,你想把它分成大小相等的子组。

如果您将经常使用它,请考虑为其创建一些类。通过这种方式,您可以将它用于需要“每页”获取项目的几个问题。

我经常使用它从每页的数据库中获取项目。由于是泛型类,我可以将 IQueryable / IEnumerable 放在 PageCollection 类中并询问页数和 Page[3]。

如果你做得很聪明,你就不必提取你不使用的项目,也不会重新提取你已经提取的项目。

我们隐藏页面的内部结构。因此我们创建了一个接口:

interface IPage<T> : IReadOnlyCollection<T>, IEnumerable<T>

    int PageNr get;              // the zero based number of the page
    int PageCount get;           // the total number of available pages
    int PageLength get;          // The number of items on this page

我选择实现IReadOnlyCollection&lt;T&gt;而不是IReadOnlyList&lt;T&gt;,因为索引通常给人的印象不正确。

例如,如果您有一个ProductPages 的集合,那么每个ProductPage 都有零个或多个Products。但是,如果您在ProductPage[10] 上并要求Product[3],您会期待什么?有些人可能会将其与带有主键 3 的 Product 混淆。

也许以下方法也很方便:

    IPage<T> PreviousPage get;
    IPage<T> NextPage get;
    IPage<T> GetPage(int pageNr);
    IReadOnlyList<T> PageCollection get;

首先让我们创建 PageCollection。 PageCollection 将创建页面:

class PageCollection<T> : IReadOnlyList<T>

    private readonly IDictionary<int, IPage<T>> fetchedPages
        = new Dictionary<int, IPage<T>();

    private int pageCount = -1; // not known yet

    public PageCollection<IEnumerable<T> query, pageSize)
    
        // TODO: check input parameters for incorrect values
        this.Query = query;
        this.PageSize = pageSize;
    

    public IEnumerable<T> Query get; 
    // TODO: consider IQueryable if you use databases / entity framework

    public int PageSize get;
    ...

我们需要方法来获取页面的数量,并通过索引来获取页面:

public int Count

    get
    
        if (this.pageCount < 0)
           this.pageCount = this.Query.Count();
        return this.pageCount;
    


public IPage this[int pageIndex] => this.GetPage(pageIndex);

最后我们来到创建页面的部分:

public IPage<T> GetPage(int pageIndex)

    if (0 < pageIndex || pageIndex >= this.Count)
    
        // pageIndex out of range.
        // TODO: decide whether to return null or throw exception
    

    if (!this.fetchedPages.TryGetValue(pageIndex, out Page<T> fetchedPage)
    
        // page never fetched before, fetch it now
        fetchedPage = this.FetchPage(pageIndex);
        this.FetchedPages.Add(pageIndex, fetchedPage);
    
    return fetchedPage;

我决定将获取的页面保存在字典中,而不是列表中。这样,您可以在获取页面 0 到 4 之前请求 Page[5]。

private Page<T> FetchPage(int pageIndex)

    return new Page(this, pageIndex);

嗯,这并没有多大作用:显然它是完成所有工作的页面。 是时候创建页面了。

您必须自己决定是立即阅读完整页面,还是仅在您要求时阅读

class Page<T> : IPage<T>, IReadOnlyCollection<T>, IEnumerable<T>

    public Page(PageCollection<T> pageCollection, int pageNr)
    
         this.PageCollection = pageCollection;
         this.PageNr = pageNr;

         // if you want to fetch the data immediately:
         this.PageContent = this.Query.ToList();
    

    public PageCollection<T> PageCollection get;
    public int PageNr get;
    public int PageCount => this.PageCollection.Count;
    public IReadOnlyCollection<T> PageContent get;

    public IEnumerable<T> Query => this.PageCollection.Query
        .Skip(this.PageNr * this.PageSize)
        .Take(this.PageSize);

IReadOnlyCollection&lt;T&gt;IEnumerable&lt;T&gt; 的实现相当简单,方法都调用this.PageContent

IEnumerator<T> GetEnumerator() return this.PageContent.GetEnumerator();
int Count => this.PageContent.Count;

等等

PreviousPage / NextPage / GetPage 之类的“很高兴拥有”过程是单行的,因为它们可以通过询问 PageCollection 来处理:

IPage<T> PreviousPage => this.PageCollection.GetPage(this.PageNr-1);

当然,如果页面超出范围,您必须决定该怎么做:异常还是返回 null?

最后的用法:

const int pageSize = 25;
IEnumerable<Product> queryProducts = ...
PageCollection<Product> productPages =
   new PageCollection<Product>(queryProducts, pageSize);

Page<Product> productPage = productPages.FirstOrDefault();
// this page can be used as a collection of Products
DisplayProducts(productPage);

// get the next page:
Page<Product> nextPage = productPage.NextPage;

【讨论】:

以上是关于从 C# 中的文本文件一次读取 10 行的主要内容,如果未能解决你的问题,请参考以下文章

在一个功能中添加 2 个逐行文本阅读器

AS3:如何将数据网格中的数据保存到逐行文本文件中?

按键精灵怎么逐行自动读取文本中的文字,并自动输入某处

sed行文本处理工具

如何在 Swing 中读取和显示大文本文件?

将文本文件中的特定行读取到批处理文件中的变量