Сплит текстовый файл, самый быстрый метод - PullRequest
0 голосов
/ 03 марта 2012

Утро

Я пытаюсь разбить большой текстовый файл (15 000 000 строк) с помощью StreamReader / StreamWriter. Есть ли более быстрый способ?

Я протестировал его с 130 000 строк, и это заняло 2 минуты 40 секунд, что означает, что 15 000 000 строк займет около 5 часов, что кажется немного чрезмерным.

//Perform split.
public void SplitFiles(int[] newFiles, string filePath, int processorCount)
{
    using (StreamReader Reader = new StreamReader(filePath))
    {
        for (int i = 0; i < newFiles.Length; i++)
        {
            string extension = System.IO.Path.GetExtension(filePath);
            string temp = filePath.Substring(0, filePath.Length - extension.Length)
                              + i.ToString();
            string FilePath = temp + extension;

            if (!File.Exists(FilePath))
            {
                for (int x = 0; x < newFiles[i]; x++)
                {
                    DataWriter(Reader.ReadLine(), FilePath);
                }
            }
            else
            {
                return;
            }
        }
    }
}

public void DataWriter(string rowData, string filePath)
{
    bool appendData = true;
    using (StreamWriter sr = new StreamWriter(filePath, appendData))
    {
        {
            sr.WriteLine(rowData);
        }
    }
}

Спасибо за вашу помощь.

Ответы [ 3 ]

1 голос
/ 03 марта 2012

Вы не очень ясно дали понять, но я предполагаю, что значение каждого элемента массива newFiles - это количество строк, которые нужно скопировать из оригинала в этот файл. Обратите внимание, что в настоящее время вы не обнаруживаете ситуацию, когда в конце входного файла есть либо дополнительные данные, либо они короче, чем ожидалось. Я подозреваю, что вы хотите что-то вроде этого:

public void SplitFiles(int[] newFiles, string inputFile)
{
    string baseName = Path.GetFileNameWithoutExtension(inputFile);
    string extension = Path.GetExtension(inputFile);
    using (TextReader reader = File.OpenText(inputFile))
    {
        for (int i = 0; i < newFiles.Length; i++)
        {
            string outputFile = baseName + i + extension;
            if (File.Exists(outputFile))
            {
                // Better than silently returning, I'd suggest...
                throw new IOException("File already exists: " + outputFile);
            }

            int linesToCopy = newFiles[i];
            using (TextWriter writer = File.CreateText(outputFile))
            {
                for (int j = 0; i < linesToCopy; j++)
                {
                    string line = reader.ReadLine();
                    if (line == null)
                    {
                        return; // Premature end of input
                    }
                    writer.WriteLine(line);
                }
            }
        }
    }
}

Обратите внимание, что это все равно не обнаружит, если есть какой-либо неиспользованный ввод ... неясно, что вы хотите сделать в этой ситуации.

Один вариант для ясности кода состоит в том, чтобы выделить середину этого в отдельный метод:

public void SplitFiles(int[] newFiles, string inputFile)
{
    string baseName = Path.GetFileNameWithoutExtension(inputFile);
    string extension = Path.GetExtension(inputFile);
    using (TextReader reader = File.OpenText(inputFile))
    {
        for (int i = 0; i < newFiles.Length; i++)
        {
            string outputFile = baseName + i + extension;
            // Could put this into the CopyLines method if you wanted
            if (File.Exists(outputFile))
            {
                // Better than silently returning, I'd suggest...
                throw new IOException("File already exists: " + outputFile);
            }

            CopyLines(reader, outputFile, newFiles[i]);
        }
    }
}

private static void CopyLines(TextReader reader, string outputFile, int count)
{
    using (TextWriter writer = File.CreateText(outputFile))
    {
        for (int i = 0; i < count; i++)
        {
            string line = reader.ReadLine();
            if (line == null)
            {
                return; // Premature end of input
            }
            writer.WriteLine(line);
        }
    }
}
1 голос
/ 03 марта 2012

Существуют утилиты для разделения файлов, которые могут превзойти ваше решение - например, поиск "разбить файл на строку".

Если они не подходят, есть решения для загрузки всего исходного файла в память и последующей записи файлов, но это, вероятно, не подходит, учитывая размер исходного файла.

С точки зрения улучшения вашего кода, незначительным улучшением будет генерация пути к целевому файлу (а также прояснение путаницы между исходным filePath, который вы используете, и целевыми файлами). Вам не нужно повторно устанавливать расширение исходного файла каждый раз в цикле.

Второе улучшение (и, возможно, более значительное улучшение - как отмечено комментаторами) - это то, как вы записываете файлы назначения - у них, похоже, различное количество строк от источника (значение в каждой записи newFiles), которые Вы указываете, что вы хотите в отдельных файлах назначения? Поэтому я бы посоветовал для каждой записи прочитать весь исходный файл, относящийся к следующему файлу назначения, , а затем выводить пункт назначения, а не многократно открывать файл назначения. Вы можете «собрать» строки в StringBuilder / List и т. Д. - в качестве альтернативы просто записать их прямо в файл назначения (но только открыв его один раз )

    public void SplitFiles(int[] newFiles, string sourceFilePath, int processorCount)
    {
        string sourceDirectory = System.IO.Path.GetDirectoryName(sourceFilePath);
        string sourceFileName = System.IO.Path.GetFileNameWithoutExtension(sourceFilePath);
        string extension = System.IO.Path.GetExtension(sourceFilePath);

        using (StreamReader Reader = new StreamReader(sourceFilePath))
        {
            for (int i = 0; i < newFiles.Length; i++)
            {
                string destinationFileNameWithExtension = string.Format("{0}{1}{2}", sourceFileName, i, extension);

                string destinationFilePath = System.IO.Path.Combine(sourceDirectory, destinationFileNameWithExtension);

                if (!File.Exists(destinationFilePath))
                {
                    // Read all the lines relevant to this destination file
                    // and temporarily store them in memory
                    StringBuilder destinationText = new StringBuilder();
                    for (int x = 0; x < newFiles[i]; x++)
                    {
                        destinationText.Append(Reader.ReadLine());
                    }
                    DataWriter(destinationFilePath, destinationText.ToString());
                }
                else
                {
                    return;
                }
            }
        }
    }

private static void DataWriter(string destinationFilePath, string content)
{
    using (StreamWriter sr = new StreamWriter(destinationFilePath))
    {
        {
            sr.Write(content);
        }
    }
}
0 голосов
/ 05 декабря 2014

Мне недавно пришлось сделать это для нескольких сотен файлов размером до 2 ГБ каждый (до 1,92 ГБ), и самый быстрый способ, который я нашел (если у вас есть доступная память), это StringBuilder.Все остальные методы, которые я пробовал, были мучительно медленными.

Обратите внимание, что это зависит от памяти.Настройте « CurrentPosition = 130000 » соответственно.

        string CurrentLine = String.Empty;
        int CurrentPosition = 0;
        int CurrentSplit = 0;

        foreach (string file in Directory.GetFiles(@"C:\FilesToSplit"))
        {
            StringBuilder sb = new StringBuilder();
            using (StreamReader sr = new StreamReader(file))
            {
                while ((CurrentLine = sr.ReadLine()) != null)
                {
                    if (CurrentPosition == 130000) // Or whatever you want to split by.
                    {
                        using (StreamWriter sw = new StreamWriter(@"C:\FilesToSplit\SplitFiles\" + Path.GetFileNameWithoutExtension(file) + "-" + CurrentSplit + "." + Path.GetExtension(file)))
                        {
                            // Append this line too, so we don't lose it.
                            sb.Append(CurrentLine);
                            // Write the StringBuilder contents
                            sw.Write(sb.ToString());
                            // Clear the StringBuilder buffer, so it doesn't get too big. You can adjust this based on your computer's available memory.
                            sb.Clear();
                            // Increment the CurrentSplit number.
                            CurrentSplit++;
                            // Reset the current line position. We've found 130,001 lines of text.
                            CurrentPosition = 0;
                        }
                    }
                    else
                    {
                        sb.Append(CurrentLine);
                        CurrentPosition++;
                    }
                }
            }
            // Reset the integers at the end of each file check, otherwise it can quickly go out of order.
            CurrentPosition = 0;
            CurrentSplit = 0;
        }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...