【问题标题】:Override StreamReader's ReadLine method覆盖 StreamReader 的 ReadLine 方法
【发布时间】:2011-06-13 12:56:37
【问题描述】:

我正在尝试覆盖 StreamReader 的 ReadLine 方法,但由于无法访问某些私有变量而难以这样做。这可能吗,还是我应该编写自己的 StreamReader 类?

【问题讨论】:

  • 你想达到什么目的?应该有更好的方法。
  • 你到底想做什么?
  • 基本上,我有一系列具有固定文件结构的文件,完全为这个客户定制。每个换行符由一个单引号定义:' 并且转义字符是一个问号。我希望能够逐行流式读取(文件很大,因此需要通过流来完成)。
  • 使用封装,而不是继承。

标签: c# streamreader


【解决方案1】:

假设您希望自定义 StreamReader 可用于任何可以使用 TextReader 的地方,通常有两个选项。

  1. 从 StreamReader 继承并覆盖您希望以不同方式工作的函数。在您的情况下,这将是 StreamReader.ReadLine。

  2. 从 TextReader 继承并完全按照您的要求实现阅读器功能。

注意:对于上面的选项 2,您可以维护对 StreamReader 实例的内部引用,并将所有功能委托给内部实例,但要替换的功能除外。在我看来,这只是选项 2 的实现细节,而不是第 3 选项。

根据您的问题,我假设您已尝试过选项 1,并发现重写 StreamReader.ReadLine 相当困难,因为您无法访问该类的内部。对于 StreamReader,您很幸运,并且无需访问 StreamReader 的内部实现即可实现这一目标。

这是一个简单的例子:

免责声明:ReadLine() 实现仅用于演示目的,并非旨在成为健壮或完整的实现。

class CustomStreamReader : StreamReader
{
  public CustomStreamReader(Stream stream)
    : base(stream)
  {
  }

  public override string ReadLine()
  {
    int c;

    c = Read();
    if (c == -1)
    {
      return null;
    }

    StringBuilder sb = new StringBuilder();
    do
    {
      char ch = (char)c;
      if (ch == ',')
      {
        return sb.ToString();
      }
      else
      {
        sb.Append(ch);
      }
    } while ((c = Read()) != -1);
    return sb.ToString();
  }
}

您会注意到我只是使用 StreamReader.Read() 方法从流中读取字符。虽然性能肯定不如直接使用内部缓冲区,但Read() 方法确实使用了内部缓冲,因此仍应产生相当不错的性能,但应进行测试以确认。

为了好玩,这里是选项2的例子。我使用封装的StreamReader来减少实际代码,这个根本没有测试..

class EncapsulatedReader : TextReader
{
  private StreamReader _reader;

  public EncapsulatedReader(Stream stream)
  {
    _reader = new StreamReader(stream);      
  }

  public Stream BaseStream
  {
    get
    {
      return _reader.BaseStream;
    }
  }

  public override string ReadLine()
  {
    int c;

    c = Read();
    if (c == -1)
    {
      return null;
    }
    StringBuilder sb = new StringBuilder();
    do
    {
      char ch = (char)c;
      if (ch == ',')
      {
        return sb.ToString();
      }
      else
      {
        sb.Append(ch);
      }
    } while ((c = Read()) != -1);
    return sb.ToString();
  }

  protected override void Dispose(bool disposing)
  {
    if (disposing)
    {
      _reader.Close();
    }
    base.Dispose(disposing);
  }

  public override int Peek()
  {
    return _reader.Peek();
  }

  public override int Read()
  {
    return _reader.Read();
  }

  public override int Read(char[] buffer, int index, int count)
  {
    return _reader.Read(buffer, index, count);
  }

  public override int ReadBlock(char[] buffer, int index, int count)
  {
    return _reader.ReadBlock(buffer, index, count);
  }

  public override string ReadToEnd()
  {
    return _reader.ReadToEnd();
  }

  public override void Close()
  {
    _reader.Close();
    base.Close();
  }
}

【讨论】:

  • 感谢 Chris,刚刚完成解析器,您的代码帮助我得到了我需要的东西!
  • @JustinN,太好了,我很高兴这有帮助。
【解决方案2】:

试试这个,我写这个是因为我有一些非常大的'|'在某些列中有 \r\n 的分隔文件,我需要使用 \r\n 作为行尾分隔符。我试图使用 SSIS 包导入一些文件,但由于文件中的一些损坏的数据我无法导入。该文件超过 5 GB,因此太大而无法打开和手动修复。我通过查看大量论坛以了解流的工作原理并最终提出了一个解决方案,该解决方案读取文件中的每个字符并根据我添加到其中的定义吐出该行,从而找到了答案。这是在命令行应用程序中使用的,带有帮助:)。我希望这对其他人有所帮助,尽管这些想法是受此论坛和其他人的启发,但我在其他任何地方都没有找到与此类似的解决方案。这不会修复它只会拆分它们的文件...请注意,这仍在进行中:)。

课堂节目 { 静态长_fileposition = 0; 静态无效主要(字符串 [] 参数) { // 检查传入的信息 if (args.Any()) { if (args[0] == "/?") { var message = "将文件分割成小块"; 消息 += "\n"; 消息 += "\n"; 消息+=“SplitFile [sourceFileName] [destinationFileName] [RowBatchAmount] [FirstRowHasHeader]”; 消息 += "\n"; 消息 += "\n"; message += " [sourceFileName] (STRING) required"; 消息 += "\n"; message += " [destinationFileName] (STRING) 将默认与 sourceFileName 位于同一位置"; 消息 += "\n"; message += " [RowBatchAmount] (INT) 将创建具有这么多行的文件"; 消息 += "\n"; message += " [FirstRowHasHeader] (True/False) 将为每个新文件添加标题行"; Console.WriteLine(消息); } 别的 { 字符串源文件名 = args[0]; 字符串 destFileLocation = args.Count() >= 2 ? args[1] : sourceFileName.Substring(0, sourceFileName.LastIndexOf("\\")); int RowCount = args.Count() >= 3 ? int.Parse(args[2]) : 500000; bool FirstRowHasHeader = true; FirstRowHasHeader = args.Count() != 4 || bool.Parse(args[3]); // 如果需要,创建目录 if (!Directory.Exists(destFileLocation)) { Directory.CreateDirectory(destFileLocation); } 字符串行 = ""; int 行数 = 0; int 文件编号 = 1; 字符串 newFileName = Path.Combine(destFileLocation, Path.GetFileNameWithoutExtension(sourceFileName)); newFileName += FileNum + Path.GetExtension(sourceFileName); // 总是添加标题行 字符串 HeaderLine = GetLine(sourceFileName, _fileposition); int HeaderCount = HeaderLine.Split('|').Count(); 做 { // 添加标题行 if ((linecount == 0 & FirstRowHasHeader) | (_fileposition == 1 & !FirstRowHasHeader)) { 使用 (FileStream NewFile = new FileStream(newFileName, FileMode.Append)) { System.Text.ASCIIEncoding 编码 = new System.Text.ASCIIEncoding(); Byte[] 字节 = encoding.GetBytes(HeaderLine); int 长度 = encoding.GetByteCount(HeaderLine); NewFile.Write(字节, 0, 长度); } } //评估线 line = GetLine(sourceFileName, _fileposition, HeaderCount); if (line == null) 继续; // 如果文件不存在则创建文件并写入 使用 (FileStream NewFile = new FileStream(newFileName, FileMode.Append)) { System.Text.ASCIIEncoding 编码 = new System.Text.ASCIIEncoding(); Byte[] bytes = encoding.GetBytes(line); int length = encoding.GetByteCount(line); NewFile.Write(字节, 0, 长度); } //添加到行数 行数++; //如果需要,创建新的文件名 如果(行数 == 行数) { 文件编号++; // 创建一个新的子文件,并读入它 newFileName = Path.Combine(destFileLocation, Path.GetFileNameWithoutExtension(sourceFileName)); newFileName += FileNum + Path.GetExtension(sourceFileName); 行数 = 0; } } while (line != null); } } 别的 { Console.WriteLine("您必须提供源文件!"); Console.WriteLine("使用 /? 寻求帮助"); } } static string GetLine(string sourceFileName, long position, int NumberOfColumns = 0) { 字节[]缓冲区=新字节[65536]; var builder = new StringBuilder(); var完成线=假; 使用(流源 = File.OpenRead(sourceFileName)) { source.Position = 位置; var crlf = "\r\n"; var lf = "\n"; 变长=来源.长度; while (source.Position = 0 & finishedline == false & _fileposition = NumberOfColumns) | NumberOfColumns == 0) { // 删除行尾之前的所有控制换行符。 builder = builder.Replace(crlf, lf); // 添加最终控制换行符 var x = (char)NewLine.Read(); builder.Append(x); 完成线=真; _文件位置++; 继续; } } 休息; } 默认: builder.Append(c); 休息; } } } 休息; } } return (builder.ToString() == "" ? null: builder.ToString()); } }

参考:http://social.msdn.microsoft.com/forums/en-US/csharpgeneral/thread/b0d4cba1-471a-4260-94c1-fddd4244fa23/

这个对我帮助最大:https://stackoverflow.com/a/668003/1582188

【讨论】:

    【解决方案3】:

    这门课可以帮到你

    public class MyStreamReader : System.IO.StreamReader
    {
        public MyStreamReader(string path)
            : base(path)
        {
    
        }
        public override string ReadLine()
        {
            string result = string.Empty;
            int b = base.Read();
            while ((b != (int)',') && (b > 0))
            {
                result += this.CurrentEncoding.GetString(new byte[] { (byte)b });
                b = base.Read();
            }
            return result;
        }
    }
    

    【讨论】:

    • 这里有三件事。 1. 您应该使用 StringBuilder 来进行如此有力的字符串连接。 2. 通过转换为字节,您将阅读器限制为仅处理 ASCII 字符。 3. ReadLine 应该返回null 而不是String.Empty 当没有更多数据要读取时,空行后跟非空行是非常可行的,因此空行不能用作流结束标记。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多