Класс FileReader в C#

Я ищу быстрый класс для работы с текстовыми файлами и удобного чтения различных объектов (методы, такие как NextInt32, NextDouble, NextLine и т. д.). Можете ли вы мне что-нибудь посоветовать?

Редактировать: BinaryReader — плохой класс в моем случае. Формат моих данных не двоичный. у меня есть файл типа

1 2 3
FirstToken NextToken
1.23 2,34

И я хочу прочитать этот файл с кодом вроде:

int a = FileReader.NextInt32();
int b = FileReader.NextInt32();
int c = FileReader.NextInt32();
int d = FileReader.NextString();
int e = FileReader.NextString();
int f = FileReader.NextDouble();
int g = FileReader.NextDouble();

Edit2: я ищу аналоговый сканер от Java


person AndreyAkinshin    schedule 13.10.2009    source источник
comment
Из примера я понимаю это: все пробелы (пробел, новая строка и т. д.) являются разделителями, и вы знаете порядок, в котором будут отображаться данные (типы). Вы можете это подтвердить?   -  person Henk Holterman    schedule 13.10.2009
comment
Кстати, 2,34 в вашем примере входных данных следует рассматривать как одно десятичное число 2.24 (т. е. вы хотите обрабатывать как точку, так и запятую как десятичную точку), или как два целых числа 2 и 24, или как строку "2,24"? Если вам нужны оба типа десятичных разделителей, учитывали ли вы тот факт, что другие локали могут использовать для этой цели другие символы (ни запятую, ни точку)?   -  person Pavel Minaev    schedule 13.10.2009
comment
@Pavel Minaev, в идеальном случае параметры конструктора содержат набор разделителей токенов и набор разделителей десятичных знаков. 2.24 мы можем читать как строку и можем читать как двойную (оба варианта допустимы)   -  person AndreyAkinshin    schedule 13.10.2009
comment
Передача десятичных разделителей звучит как плохая идея, потому что это потребует специального кода для синтаксического анализа чисел. Как насчет передачи CultureInfo вместо этого?   -  person Pavel Minaev    schedule 13.10.2009
comment
@Pavel Minaev: Я думаю, это хорошая идея   -  person AndreyAkinshin    schedule 13.10.2009


Ответы (7)


Я собираюсь добавить это как отдельный ответ, потому что он сильно отличается от ответа, который я уже дал. Вот как вы можете начать создавать свой собственный класс Scanner:

class Scanner : System.IO.StringReader
{
  string currentWord;

  public Scanner(string source) : base(source)
  {
     readNextWord();
  }

  private void ReadNextWord()
  {
     System.Text.StringBuilder sb = new StringBuilder();
     char nextChar;
     int next;
     do
     {
        next = this.Read();
        if (next < 0)
           break;
        nextChar = (char)next;
        if (char.IsWhiteSpace(nextChar))
           break;
        sb.Append(nextChar);
     } while (true);
     while((this.Peek() >= 0) && (char.IsWhiteSpace((char)this.Peek())))
        this.Read();
     if (sb.Length > 0)
        currentWord = sb.ToString();
     else
        currentWord = null;
  }

  public bool HasNextInt()
  {
     if (currentWord == null)
        return false;
     int dummy;
     return int.TryParse(currentWord, out dummy);
  }

  public int NextInt()
  {
     try
     {
        return int.Parse(currentWord);
     }
     finally
     {
        readNextWord();
     }
  }

  public bool HasNextDouble()
  {
     if (currentWord == null)
        return false;
     double dummy;
     return double.TryParse(currentWord, out dummy);
  }

  public double NextDouble()
  {
     try
     {
        return double.Parse(currentWord);
     }
     finally
     {
        readNextWord();
     }
  }

  public bool HasNext()
  {
     return currentWord != null;
  }
}
person Alon Gubkin    schedule 13.10.2009

Я считаю, что этот метод расширения для TextReader поможет:

public static class TextReaderTokenizer
{
    // Adjust as needed. -1 is EOF.
    private static int[] whitespace = { -1, ' ', '\r' , '\n', '\t' };

    public static T ReadToken<T>(this TextReader reader)
    {
        StringBuilder sb = new StringBuilder();
        while (Array.IndexOf(whitespace, reader.Peek()) < 0)
        {
            sb.Append((char)reader.Read());
        }
        return (T)Convert.ChangeType(sb.ToString(), typeof(T));
    }    
}

Его можно использовать таким образом:

TextReader reader = File.OpenText("foo.txt");
int n = reader.ReadToken<int>();
string s = reader.ReadToken<string>();

[EDIT] Как и просили в комментариях к вопросу, вот версия оболочки экземпляра выше, которая параметризована разделителями и CultureInfo:

public class TextTokenizer
{
    private TextReader reader;
    private Predicate<char> isDelim;
    private CultureInfo cultureInfo;

    public TextTokenizer(TextReader reader, Predicate<char> isDelim, CultureInfo cultureInfo)
    {
        this.reader = reader;
        this.isDelim = isDelim;
        this.cultureInfo = cultureInfo;
    }

    public TextTokenizer(TextReader reader, char[] delims, CultureInfo cultureInfo)
    {
        this.reader = reader;
        this.isDelim = c => Array.IndexOf(delims, c) >= 0;
        this.cultureInfo = cultureInfo;
    }

    public TextReader BaseReader
    {
        get { return reader; }
    }

    public T ReadToken<T>()
    {
        StringBuilder sb = new StringBuilder();
        while (true)
        {
            int c = reader.Peek();
            if (c < 0 || isDelim((char)c))
            {
                break;
            }
            sb.Append((char)reader.Read());
        }
        return (T)Convert.ChangeType(sb.ToString(), typeof(T));
    }    
}

Пример использования:

TextReader reader = File.OpenText("foo.txt");
TextTokenizer tokenizer = new TextTokenizer(
    reader,
    new[] { ' ', '\r', '\n', '\t' },
    CultureInfo.InvariantCulture);
int n = tokenizer.ReadToken<int>();
string s = tokenizer.ReadToken<string>();
person Pavel Minaev    schedule 13.10.2009

Вы должны определить точно, как должен выглядеть ваш формат файла. Как бы вы представили строку с пробелом в ней? Что определяет, куда идут терминаторы строк?

В общем, вы можете использовать TextReader и его метод ReadLine, а затем double.TryParse, int.TryParse и т. д., но сначала вам нужно будет уточнить формат.

person Jon Skeet    schedule 13.10.2009
comment
Чтобы добавить к этому, можно добавить класс декоратора для TextReader, чтобы обеспечить удобные методы ReadInt32 и т. д.; или методы расширения на самом TextReader. Однако я не знаю ни о каких таких классах акций. - person Pavel Minaev; 13.10.2009
comment
@DreamWalker: причина, по которой для этого нет стандартного класса, заключается в том, что существует множество немного разных текстовых форматов без четкого стандарта: пробелы или запятые для разделителей; строки в кавычках необязательны/обязательны/не поддерживаются; и т. д. Наиболее распространенным является CSV (en.wikipedia.org/wiki/Comma-separated_values), и даже тогда есть вариации; но, по крайней мере, если у вас есть CSV, существует довольно много готовых парсеров. Если вы хотите что-то другое, вам, скорее всего, придется написать это самостоятельно. - person Pavel Minaev; 13.10.2009
comment
@Pavel Minaev: Я надеялся, что класс уже написан. Это не обязательно стандартный класс. Это может быть класс из какого-то фреймворка с открытым исходным кодом или что-то в этом роде. - person AndreyAkinshin; 13.10.2009

Вы проверили класс BinaryReader? Да, это текстовый файл, но ничто не мешает вам рассматривать его как двоичные данные и, следовательно, использовать BinaryReader. В нем есть все методы, которые вы ищете, за исключением ReadLine. Однако было бы несложно реализовать этот метод поверх BinaryReader.

person JaredPar    schedule 13.10.2009
comment
Согласен, за исключением того, что реализация ReadLine поверх BinaryReader будет сложной задачей, если вы спроектируете его для обработки произвольных кодировок и не прерываете чтение двоичных данных. - person Noldorin; 13.10.2009
comment
@Хенк, да. Он просто видит поток байтов. Пока кодировка правильная, он их читает. - person JaredPar; 13.10.2009
comment
@Noldorin, да, сделать его независимым от кодировки было бы немного сложно. - person JaredPar; 13.10.2009
comment
@JaredPar: использование BinaryReader.ReadDouble не будет читать текст и анализировать его. Он будет рассматривать этот текст как байты, что не является идеей. - person Jon Skeet; 13.10.2009
comment
Джаредпар, это не то, что я бы назвал файлом Text. BinaryReader более полезен, но только если OP владеет форматом. - person Henk Holterman; 13.10.2009

Если вам действительно нужны текстовые файлы (например, в кодировке UTF-8 или ASCII), то модуль записи двоичных файлов не будет работать.

Вы можете использовать TextReader, но в отличие от BinaryReader и TextWriter он не поддерживает никаких типов, кроме Line и char. Вам нужно будет определить, какие разделители разрешены, и самостоятельно проанализировать базовые данные Line.

person Henk Holterman    schedule 13.10.2009
comment
Я думаю, что string.Split() очень медленный метод. мне нужна скорость - person AndreyAkinshin; 13.10.2009

Вам нужен класс System.IO.BinaryReader.

Пример реализации метода ReadLine:

public static class Extensions
{
    public static String ReadLine(this BinaryReader binaryReader)
    {
        var bytes = new List<Byte>();
        byte temp;

        while ((temp = (byte)binaryReader.Read()) < 10)
            bytes.Add(temp);

        return Encoding.Default.GetString(bytes.ToArray());
    }
}

Пример использования этого класса:

using System;
using System.IO;
using System.Security.Permissions;

class Test
{
    static void Main()
    {
        // Load application settings.
        AppSettings appSettings = new AppSettings();
        Console.WriteLine("App settings:\nAspect Ratio: {0}, " +
            "Lookup directory: {1},\nAuto save time: {2} minutes, " +
            "Show status bar: {3}\n",
            new Object[4]{appSettings.AspectRatio.ToString(),
            appSettings.LookupDir, appSettings.AutoSaveTime.ToString(),
            appSettings.ShowStatusBar.ToString()});

        // Change the settings.
        appSettings.AspectRatio   = 1.250F;
        appSettings.LookupDir     = @"C:\Temp";
        appSettings.AutoSaveTime  = 10;
        appSettings.ShowStatusBar = true;

        // Save the new settings.
        appSettings.Close();
    }
}

// Store and retrieve application settings.
class AppSettings
{
    const string fileName = "AppSettings#@@#.dat";
    float  aspectRatio;
    string lookupDir;
    int    autoSaveTime;
    bool   showStatusBar;

    public float AspectRatio
    {
        get{ return aspectRatio; }
        set{ aspectRatio = value; }
    }

    public string LookupDir
    {
        get{ return lookupDir; }
        set{ lookupDir = value; }
    }

    public int AutoSaveTime
    {
        get{ return autoSaveTime; }
        set{ autoSaveTime = value; }
    }

    public bool ShowStatusBar
    {
        get{ return showStatusBar; }
        set{ showStatusBar = value; }
    }

    public AppSettings()
    {
        // Create default application settings.
        aspectRatio   = 1.3333F;
        lookupDir     = @"C:\AppDirectory";
        autoSaveTime  = 30;
        showStatusBar = false;

        if(File.Exists(fileName))
        {
            BinaryReader binReader =
                new BinaryReader(File.Open(fileName, FileMode.Open));
            try
            {
                // If the file is not empty,
                // read the application settings.
                // First read 4 bytes into a buffer to
                // determine if the file is empty.
                byte[] testArray = new byte[3];
                int count = binReader.Read(testArray, 0, 3);

                if (count != 0)
                {
                    // Reset the position in the stream to zero.
                    binReader.BaseStream.Seek(0, SeekOrigin.Begin);

                    aspectRatio   = binReader.ReadSingle();
                    lookupDir     = binReader.ReadString();
                    autoSaveTime  = binReader.ReadInt32();
                    showStatusBar = binReader.ReadBoolean();
                }
            }

            // If the end of the stream is reached before reading
            // the four data values, ignore the error and use the
            // default settings for the remaining values.
            catch(EndOfStreamException e)
            {
                Console.WriteLine("{0} caught and ignored. " +
                    "Using default values.", e.GetType().Name);
            }
            finally
            {
                binReader.Close();
            }
        }

    }

    // Create a file and store the application settings.
    public void Close()
    {
        using(BinaryWriter binWriter =
            new BinaryWriter(File.Open(fileName, FileMode.Create)))
        {
            binWriter.Write(aspectRatio);
            binWriter.Write(lookupDir);
            binWriter.Write(autoSaveTime);
            binWriter.Write(showStatusBar);
        }
    }
}
person Alon Gubkin    schedule 13.10.2009
comment
Это очень странная реализация ReadLine, а также использование кодировки по умолчанию, а не кодировки бинарного ридера. Как вы ожидаете, что он когда-либо вернет какой-либо печатный текст? - person Jon Skeet; 13.10.2009
comment
Кроме того, BinaryReader не подходит для текстовых файлов. - person Jon Skeet; 13.10.2009

Вероятно, вы можете использовать System.IO.File Класс для чтения файла и System.Convert для анализа строк, которые вы читаете из файла.

string line = String.Empty;
while( (line = file.ReadLine()).IsNullOrEmpty() == false )
{
   TYPE value = Convert.ToTYPE( line );
}

Где TYPE - это любой тип, с которым вы имеете дело в этой конкретной строке/файле.

Если в одной строке есть несколько значений, вы можете разделить и прочитать отдельные значения, например.

string[] parts = line.Split(' ');
if( parts.Length > 1 )
{
   foreach( string item in parts )
   {
      TYPE value = Convert.ToTYPE( item );
   }
}
else
{
   // Use the code from before
}
person TJB    schedule 13.10.2009
comment
Это будет обрабатывать каждую строку как одно значение. Он хочет, чтобы одна строка foo 123 bar рассматривалась как 3 различных значения. - person Pavel Minaev; 13.10.2009
comment
@Pavel Minaev Добавлена ​​поддержка, спасибо за предложение - person TJB; 14.10.2009