如何通过特定的行分隔符读取文本文件?

Posted

技术标签:

【中文标题】如何通过特定的行分隔符读取文本文件?【英文标题】:How to read text file by particular line separator character? 【发布时间】:2011-10-03 01:42:42 【问题描述】:

使用流式阅读器读取文本文件。

using (StreamReader sr = new StreamReader(FileName, Encoding.Default))

     string line = sr.ReadLine();

我想强制该行分隔符应该是\n 而不是\r。那我该怎么做呢?

【问题讨论】:

根据 ReadLine 文档“行定义为一系列字符,后跟换行符 ("\n")、回车符 ("\r") 或立即回车符后跟换行符 ("\r\n")" 所以它应该在 '\n' 处中断。如果您想做某种自定义行解析,我认为您必须自己读取每个字节并打破您希望“新行”所在的位置。 【参考方案1】:

我会实现类似 George 的回答,但作为一种扩展方法,可以避免一次加载整个文件(未经测试,但类似这样):

static class ExtensionsForTextReader

     public static IEnumerable<string> ReadLines (this TextReader reader, char delimiter)
     
            List<char> chars = new List<char> ();
            while (reader.Peek() >= 0)
            
                char c = (char)reader.Read ();

                if (c == delimiter) 
                    yield return new String(chars.ToArray());
                    chars.Clear ();
                    continue;
                

                chars.Add(c);
            
     

然后可以像这样使用:

using (StreamReader sr = new StreamReader(FileName, Encoding.Default))

     foreach (var line in sr.ReadLines ('\n'))
           Console.WriteLine (line);

【讨论】:

此代码是否有特定的“\r\n”解决方案? @skmasq 你试试 Environment.NewLine msdn.microsoft.com/en-us/library/… @JanneHarju:这行不通,因为delimeter 参数是一个字符,而"\r\n" 是一个由2 个字符组成的字符串。该函数的算法必须适用于搜索字符序列而不是单个字符。【参考方案2】:
string text = sr.ReadToEnd();
string[] lines = text.Split('\r');
foreach(string s in lines)

   // Consume

【讨论】:

这很简单,但如果文件包含 100 万行,这可能会结束 :) 是的,如果它假设包含 10、100、1,000 或 10,000,则不会。每个答案都有一个假设的缺点。 ;) 对,我添加了注释,因为通常如果您使用的是流,那么您一次处理一点字节,这样您就不必将整个文件加载到内存中(好吧,也许这里的“将军”对我来说是“将军”)。我倾向于处理大文件,因此将整个文件加载到内存中可能是个问题。 对于大文件,你应该去找马丁的答案 @pstrjds 我明白,但这真的取决于他的要求。如果此解决方案由于内存限制而不起作用,他可以轻松添加一些代码以流式传输数据块,并根据需要进行拆分,例如ReadBlock()。我就这样吧。他不需要接受这个答案,但它可能对其他可能不会面临同样限制的人有用。 :)【参考方案3】:

我喜欢@Pete 给出的答案。我只想提交一个小小的修改。这将允许您传递一个字符串分隔符,而不仅仅是一个字符:

using System;
using System.IO;
using System.Collections.Generic;
internal static class StreamReaderExtensions

    public static IEnumerable<string> ReadUntil(this StreamReader reader, string delimiter)
    
        List<char> buffer = new List<char>();
        CircularBuffer<char> delim_buffer = new CircularBuffer<char>(delimiter.Length);
        while (reader.Peek() >= 0)
        
            char c = (char)reader.Read();
            delim_buffer.Enqueue(c);
            if (delim_buffer.ToString() == delimiter || reader.EndOfStream)
            
                if (buffer.Count > 0)
                
                    if (!reader.EndOfStream)
                    
                        yield return new String(buffer.ToArray()).Replace(delimiter.Substring(0, delimiter.Length - 1), string.Empty);
                    
                    else
                    
                        buffer.Add(c);
                        yield return new String(buffer.ToArray());
                    
                    buffer.Clear();
                
                continue;
            
            buffer.Add(c);
        
    

    private class CircularBuffer<T> : Queue<T>
    
        private int _capacity;

        public CircularBuffer(int capacity)
            : base(capacity)
        
            _capacity = capacity;
        

        new public void Enqueue(T item)
        
            if (base.Count == _capacity)
            
                base.Dequeue();
            
            base.Enqueue(item);
        

        public override string ToString()
        
            List<String> items = new List<string>();
            foreach (var x in this)
            
                items.Add(x.ToString());
            ;
            return String.Join("", items);
        
    

【讨论】:

不错的解决方案...一个潜在的问题;这不包括除最后一个分隔符之外的所有字符;即,如果我有 4 个字符的分隔符,则返回的字符串仍将包含该分隔符的前 3 个字符。 @JohnLBevan 是的,你是对的。我正在尝试为此想一个好的解决方案。我想您可能想丢弃分隔符中的所有内容。 @JohnLBevan 已更新,现在应该可以使用了。我还注意到它丢弃了它应该返回的最后一个条目,现在也应该修复它。 您产生的替换很糟糕,因为它会删除一些您可能仍然想要的不需要的字符。在我的情况下,我只想通过“\r\n”阅读并保留替换/子字符串结束的内部“/r”。我以这种方式结束了产量: var s = new String(buffer.ToArray()); yield return s.Substring(0, s.Length - delimiter.Length + 1); 干得好;)【参考方案4】:

根据文档:

http://msdn.microsoft.com/en-us/library/system.io.streamreader.readline.aspx

行定义为一系列字符,后跟换行符 ("\n")、回车 ("\r") 或立即回车 后跟换行符 ("\r\n")。

默认情况下,StreamReader ReadLine 方法将通过 \n 或 \r 来识别一行

【讨论】:

你为什么不使用 StreamReader?【参考方案5】:

这是对 sovemp 答案的改进。抱歉,我很想发表评论,尽管我的名声不允许我这样做。这项改进解决了 2 个问题:

    带有分隔符“\r\n”的示例序列“text\rtest\r\n”也将 删除第一个不打算使用的“\r”。

    当流中的最后一个字符等于分隔符时,函数会 错误地返回包含分隔符的字符串。

    using System;
    using System.IO;
    using System.Collections.Generic;
    internal static class StreamReaderExtensions
    
        public static IEnumerable<string> ReadUntil(this StreamReader reader, string delimiter)
        
            List<char> buffer = new List<char>();
            CircularBuffer<char> delim_buffer = new CircularBuffer<char>(delimiter.Length);
            while (reader.Peek() >= 0)
            
                char c = (char)reader.Read();
                delim_buffer.Enqueue(c);
                if (delim_buffer.ToString() == delimiter || reader.EndOfStream)
                
                    if (buffer.Count > 0)
                    
                        if (!reader.EndOfStream)
                        
                            buffer.Add(c);
                            yield return new String(buffer.ToArray()).Substring(0, buffer.Count - delimeter.Length);
                        
                        else
                        
                            buffer.Add(c);
                            if (delim_buffer.ToString() != delimiter)
                                yield return new String(buffer.ToArray());
                            else
                                yield return new String(buffer.ToArray()).Substring(0, buffer.Count - delimeter.Length);
                        
                        buffer.Clear();
                    
                    continue;
                
                buffer.Add(c);
            
        
    
        private class CircularBuffer<T> : Queue<T>
        
            private int _capacity;
    
            public CircularBuffer(int capacity)
                : base(capacity)
            
                _capacity = capacity;
            
    
            new public void Enqueue(T item)
            
                if (base.Count == _capacity)
                
                    base.Dequeue();
                
                base.Enqueue(item);
            
    
            public override string ToString()
            
                List<String> items = new List<string>();
                foreach (var x in this)
                
                    items.Add(x.ToString());
                ;
                return String.Join("", items);
            
        
    
    

【讨论】:

【参考方案6】:

我需要一个读取到“\r\n”,而不是在“\n”处停止的解决方案。 jp1980 的解决方案有效,但在大文件上速度极慢。因此,我将 Mike Sackton 的解决方案转换为读取,直到找到指定的字符串。

public static string ReadLine(this StreamReader sr, string lineDelimiter)
    
        StringBuilder line = new StringBuilder();
        var matchIndex = 0;

        while (sr.Peek() > 0)
        
            var nextChar = (char)sr.Read();
            line.Append(nextChar);

            if (nextChar == lineDelimiter[matchIndex])
            
                if (matchIndex == lineDelimiter.Length - 1)
                
                    return line.ToString().Substring(0, line.Length - lineDelimiter.Length);
                
                matchIndex++;
            
            else
            
                matchIndex = 0;
                //did we mistake one of the characters as the delimiter? If so let's restart our search with this character...
                if (nextChar == lineDelimiter[matchIndex])
                
                    if (matchIndex == lineDelimiter.Length - 1)
                    
                        return line.ToString().Substring(0, line.Length - lineDelimiter.Length);
                    
                    matchIndex++;
                
            
        

        return line.Length == 0
            ? null
            : line.ToString();
    

而且是这样称呼的……

using (StreamReader reader = new StreamReader(file))

    string line;
    while((line = reader.ReadLine("\r\n")) != null)
    
        Console.WriteLine(line);
    

【讨论】:

完美。适用于自定义行分隔符,例如 Environment.NewLine + "go" + Environment.NewLine;【参考方案7】:

您必须自己逐字节解析流并处理拆分,或者您需要使用在 /r、/n 或 /r/n 上拆分的默认 ReadLine 行为。

如果你想逐字节解析流,我会使用类似下面的扩展方法:

 public static string ReadToChar(this StreamReader sr, char splitCharacter)
            
        char nextChar;
        StringBuilder line = new StringBuilder();
        while (sr.Peek() > 0)
                       
            nextChar = (char)sr.Read();
            if (nextChar == splitCharacter) return line.ToString();
            line.Append(nextChar);
        

        return line.Length == 0 ? null : line.ToString();
    

【讨论】:

【参考方案8】:

即使您说“使用 StreamReader”,因为您还说“我的情况,文件可以有大量记录......”,我建议您尝试 SSIS。它非常适合您尝试做的事情。您可以处理非常大的文件并轻松指定行/列分隔符。

【讨论】:

你的意思是Sql Server Integration Services?当您可以在每个字符上执行一个简单的蛮力循环并以这种方式构建行时,这似乎有点矫枉过正? @pstrjds :是的,我的意思是 Sql Server 集成服务 :-D 当然这可能有点矫枉过正,但真正触发我的建议的是“大量记录”部分。有时,我必须“解析”具有大约 18M 行和大量列(大约 450megs)的 csv 文件,我喜欢为此使用 SSIS。当然,我的使用也与 SQL 服务器有关,但我喜欢这个工具(尽管我不喜欢它的某些接口/行为。) @tipx 完整的源代码示例,其中包含使用 SSIS for read csv files 的良好模式和实践?【参考方案9】:

这段代码 sn-p 将从文件中读取一行,直到遇到“\n”。

using (StreamReader sr = new StreamReader(path)) 

     string line = string.Empty;
     while (sr.Peek() >= 0) 
     
          char c = (char)sr.Read();
          if (c == '\n')
          
              //end of line encountered
              Console.WriteLine(line);
              //create new line
              line = string.Empty;
          
          else
          
               line += (char)sr.Read();
          
     

由于此代码逐个字符读取,因此可以处理任意长度的文件,而不受可用内存的限制。

【讨论】:

以上是关于如何通过特定的行分隔符读取文本文件?的主要内容,如果未能解决你的问题,请参考以下文章

在 spark java 中读取具有固定宽度和分隔符的文本文件

文本IO 二进制IO

C++文件读写操作如何统计文本的行数及如何读取文件某一行内容

如何修改特定分隔符之间的文本(在文件中)? [关闭]

如何有效地读取 LARGE 文本文件中的行数

Zabbix自定义参数监控和awk命令