Мне нужно обработать большой файл, около 400 КБ строк и 200 М. Но иногда мне приходится обрабатывать снизу вверх. Как я могу использовать здесь итератор (yield return)? В основном я не люблю загружать все в память. Я знаю, что в .NET более эффективно использовать итератор.
87
Ответы:
Чтение текстовых файлов в обратном направлении действительно сложно, если вы не используете кодировку фиксированного размера (например, ASCII). Когда у вас есть кодировка переменного размера (например, UTF-8), вам все равно придется проверять, находитесь ли вы в середине символа или нет, когда вы извлекаете данные.
В фреймворк ничего не встроено, и я подозреваю, что вам придется делать отдельное жесткое кодирование для каждой кодировки переменной ширины.
РЕДАКТИРОВАТЬ: Это было несколько проверено, но это не значит, что у него все еще нет тонких ошибок. Он использует StreamUtil из MiscUtil, но я включил только необходимый (новый) метод оттуда внизу. О, и он требует рефакторинга - есть один довольно здоровенный метод, как вы увидите:
using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Text; namespace MiscUtil.IO { /// <summary> /// Takes an encoding (defaulting to UTF-8) and a function which produces a seekable stream /// (or a filename for convenience) and yields lines from the end of the stream backwards. /// Only single byte encodings, and UTF-8 and Unicode, are supported. The stream /// returned by the function must be seekable. /// </summary> public sealed class ReverseLineReader : IEnumerable<string> { /// <summary> /// Buffer size to use by default. Classes with internal access can specify /// a different buffer size - this is useful for testing. /// </summary> private const int DefaultBufferSize = 4096; /// <summary> /// Means of creating a Stream to read from. /// </summary> private readonly Func<Stream> streamSource; /// <summary> /// Encoding to use when converting bytes to text /// </summary> private readonly Encoding encoding; /// <summary> /// Size of buffer (in bytes) to read each time we read from the /// stream. This must be at least as big as the maximum number of /// bytes for a single character. /// </summary> private readonly int bufferSize; /// <summary> /// Function which, when given a position within a file and a byte, states whether /// or not the byte represents the start of a character. /// </summary> private Func<long,byte,bool> characterStartDetector; /// <summary> /// Creates a LineReader from a stream source. The delegate is only /// called when the enumerator is fetched. UTF-8 is used to decode /// the stream into text. /// </summary> /// <param name="streamSource">Data source</param> public ReverseLineReader(Func<Stream> streamSource) : this(streamSource, Encoding.UTF8) { } /// <summary> /// Creates a LineReader from a filename. The file is only opened /// (or even checked for existence) when the enumerator is fetched. /// UTF8 is used to decode the file into text. /// </summary> /// <param name="filename">File to read from</param> public ReverseLineReader(string filename) : this(filename, Encoding.UTF8) { } /// <summary> /// Creates a LineReader from a filename. The file is only opened /// (or even checked for existence) when the enumerator is fetched. /// </summary> /// <param name="filename">File to read from</param> /// <param name="encoding">Encoding to use to decode the file into text</param> public ReverseLineReader(string filename, Encoding encoding) : this(() => File.OpenRead(filename), encoding) { } /// <summary> /// Creates a LineReader from a stream source. The delegate is only /// called when the enumerator is fetched. /// </summary> /// <param name="streamSource">Data source</param> /// <param name="encoding">Encoding to use to decode the stream into text</param> public ReverseLineReader(Func<Stream> streamSource, Encoding encoding) : this(streamSource, encoding, DefaultBufferSize) { } internal ReverseLineReader(Func<Stream> streamSource, Encoding encoding, int bufferSize) { this.streamSource = streamSource; this.encoding = encoding; this.bufferSize = bufferSize; if (encoding.IsSingleByte) { // For a single byte encoding, every byte is the start (and end) of a character characterStartDetector = (pos, data) => true; } else if (encoding is UnicodeEncoding) { // For UTF-16, even-numbered positions are the start of a character. // TODO: This assumes no surrogate pairs. More work required // to handle that. characterStartDetector = (pos, data) => (pos & 1) == 0; } else if (encoding is UTF8Encoding) { // For UTF-8, bytes with the top bit clear or the second bit set are the start of a character // See http://www.cl.cam.ac.uk/~mgk25/unicode.html characterStartDetector = (pos, data) => (data & 0x80) == 0 || (data & 0x40) != 0; } else { throw new ArgumentException("Only single byte, UTF-8 and Unicode encodings are permitted"); } } /// <summary> /// Returns the enumerator reading strings backwards. If this method discovers that /// the returned stream is either unreadable or unseekable, a NotSupportedException is thrown. /// </summary> public IEnumerator<string> GetEnumerator() { Stream stream = streamSource(); if (!stream.CanSeek) { stream.Dispose(); throw new NotSupportedException("Unable to seek within stream"); } if (!stream.CanRead) { stream.Dispose(); throw new NotSupportedException("Unable to read within stream"); } return GetEnumeratorImpl(stream); } private IEnumerator<string> GetEnumeratorImpl(Stream stream) { try { long position = stream.Length; if (encoding is UnicodeEncoding && (position & 1) != 0) { throw new InvalidDataException("UTF-16 encoding provided, but stream has odd length."); } // Allow up to two bytes for data from the start of the previous // read which didn't quite make it as full characters byte[] buffer = new byte[bufferSize + 2]; char[] charBuffer = new char[encoding.GetMaxCharCount(buffer.Length)]; int leftOverData = 0; String previousEnd = null; // TextReader doesn't return an empty string if there's line break at the end // of the data. Therefore we don't return an empty string if it's our *first* // return. bool firstYield = true; // A line-feed at the start of the previous buffer means we need to swallow // the carriage-return at the end of this buffer - hence this needs declaring // way up here! bool swallowCarriageReturn = false; while (position > 0) { int bytesToRead = Math.Min(position > int.MaxValue ? bufferSize : (int)position, bufferSize); position -= bytesToRead; stream.Position = position; StreamUtil.ReadExactly(stream, buffer, bytesToRead); // If we haven't read a full buffer, but we had bytes left // over from before, copy them to the end of the buffer if (leftOverData > 0 && bytesToRead != bufferSize) { // Buffer.BlockCopy doesn't document its behaviour with respect // to overlapping data: we *might* just have read 7 bytes instead of // 8, and have two bytes to copy... Array.Copy(buffer, bufferSize, buffer, bytesToRead, leftOverData); } // We've now *effectively* read this much data. bytesToRead += leftOverData; int firstCharPosition = 0; while (!characterStartDetector(position + firstCharPosition, buffer[firstCharPosition])) { firstCharPosition++; // Bad UTF-8 sequences could trigger this. For UTF-8 we should always // see a valid character start in every 3 bytes, and if this is the start of the file // so we've done a short read, we should have the character start // somewhere in the usable buffer. if (firstCharPosition == 3 || firstCharPosition == bytesToRead) { throw new InvalidDataException("Invalid UTF-8 data"); } } leftOverData = firstCharPosition; int charsRead = encoding.GetChars(buffer, firstCharPosition, bytesToRead - firstCharPosition, charBuffer, 0); int endExclusive = charsRead; for (int i = charsRead - 1; i >= 0; i--) { char lookingAt = charBuffer[i]; if (swallowCarriageReturn) { swallowCarriageReturn = false; if (lookingAt == '\r') { endExclusive--; continue; } } // Anything non-line-breaking, just keep looking backwards if (lookingAt != '\n' && lookingAt != '\r') { continue; } // End of CRLF? Swallow the preceding CR if (lookingAt == '\n') { swallowCarriageReturn = true; } int start = i + 1; string bufferContents = new string(charBuffer, start, endExclusive - start); endExclusive = i; string stringToYield = previousEnd == null ? bufferContents : bufferContents + previousEnd; if (!firstYield || stringToYield.Length != 0) { yield return stringToYield; } firstYield = false; previousEnd = null; } previousEnd = endExclusive == 0 ? null : (new string(charBuffer, 0, endExclusive) + previousEnd); // If we didn't decode the start of the array, put it at the end for next time if (leftOverData != 0) { Buffer.BlockCopy(buffer, 0, buffer, bufferSize, leftOverData); } } if (leftOverData != 0) { // At the start of the final buffer, we had the end of another character. throw new InvalidDataException("Invalid UTF-8 data at start of stream"); } if (firstYield && string.IsNullOrEmpty(previousEnd)) { yield break; } yield return previousEnd ?? ""; } finally { stream.Dispose(); } } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } } // StreamUtil.cs: public static class StreamUtil { public static void ReadExactly(Stream input, byte[] buffer, int bytesToRead) { int index = 0; while (index < bytesToRead) { int read = input.Read(buffer, index, bytesToRead - index); if (read == 0) { throw new EndOfStreamException (String.Format("End of stream reached with {0} byte{1} left to read.", bytesToRead - index, bytesToRead - index == 1 ? "s" : "")); } index += read; } } }
Обратная связь очень приветствуется. Было весело :)
источник
Вы можете использовать File.ReadLines для получения итератора строк
foreach (var line in File.ReadLines(@"C:\temp\ReverseRead.txt").Reverse()) { if (noNeedToReadFurther) break; // process line here Console.WriteLine(line); }
РЕДАКТИРОВАТЬ:
Прочитав комментарий applejacks01 , я провожу несколько тестов, и похоже, что на
.Reverse()
самом деле загружается весь файл.Раньше я
File.ReadLines()
печатал первую строку файла размером 40 МБ - использование памяти консольным приложением составляло 5 МБ . Затем используетсяFile.ReadLines().Reverse()
для печати последней строки из того же файла - использование памяти было 95 МБ .источник
Чтобы создать файловый итератор, вы можете сделать это:
РЕДАКТИРОВАТЬ:
Это моя фиксированная версия обратного чтения файлов с фиксированной шириной:
public static IEnumerable<string> readFile() { using (FileStream reader = new FileStream(@"c:\test.txt",FileMode.Open,FileAccess.Read)) { int i=0; StringBuilder lineBuffer = new StringBuilder(); int byteRead; while (-i < reader.Length) { reader.Seek(--i, SeekOrigin.End); byteRead = reader.ReadByte(); if (byteRead == 10 && lineBuffer.Length > 0) { yield return Reverse(lineBuffer.ToString()); lineBuffer.Remove(0, lineBuffer.Length); } lineBuffer.Append((char)byteRead); } yield return Reverse(lineBuffer.ToString()); reader.Close(); } } public static string Reverse(string str) { char[] arr = new char[str.Length]; for (int i = 0; i < str.Length; i++) arr[i] = str[str.Length - 1 - i]; return new string(arr); }
источник
Я помещал файл в список построчно, затем использовал List.Reverse ();
StreamReader objReader = new StreamReader(filename); string sLine = ""; ArrayList arrText = new ArrayList(); while (sLine != null) { sLine = objReader.ReadLine(); if (sLine != null) arrText.Add(sLine); } objReader.Close(); arrText.Reverse(); foreach (string sOutput in arrText) {
...
источник
Вы можете читать файл по одному символу в обратном направлении и кэшировать все символы, пока не дойдете до возврата каретки и / или перевода строки.
Затем вы переворачиваете собранную строку и превращаете ее в линию.
источник
Я знаю, что этот пост очень старый, но поскольку я не мог найти, как использовать решение, получившее наибольшее количество голосов, я, наконец, нашел это: вот лучший ответ, который я нашел с низкой стоимостью памяти в VB и C #
http://www.blakepell.com/2010-11-29-backward-file-reader-vb-csharp-source
Надеюсь, я помогу другим с этим, потому что мне потребовались часы, чтобы наконец найти этот пост!
[Редактировать]
Вот код C #:
//********************************************************************************************************************************* // // Class: BackwardReader // Initial Date: 11/29/2010 // Last Modified: 11/29/2010 // Programmer(s): Original C# Source - the_real_herminator // http://social.msdn.microsoft.com/forums/en-US/csharpgeneral/thread/9acdde1a-03cd-4018-9f87-6e201d8f5d09 // VB Converstion - Blake Pell // //********************************************************************************************************************************* using System.Text; using System.IO; public class BackwardReader { private string path; private FileStream fs = null; public BackwardReader(string path) { this.path = path; fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); fs.Seek(0, SeekOrigin.End); } public string Readline() { byte[] line; byte[] text = new byte[1]; long position = 0; int count; fs.Seek(0, SeekOrigin.Current); position = fs.Position; //do we have trailing rn? if (fs.Length > 1) { byte[] vagnretur = new byte[2]; fs.Seek(-2, SeekOrigin.Current); fs.Read(vagnretur, 0, 2); if (ASCIIEncoding.ASCII.GetString(vagnretur).Equals("rn")) { //move it back fs.Seek(-2, SeekOrigin.Current); position = fs.Position; } } while (fs.Position > 0) { text.Initialize(); //read one char fs.Read(text, 0, 1); string asciiText = ASCIIEncoding.ASCII.GetString(text); //moveback to the charachter before fs.Seek(-2, SeekOrigin.Current); if (asciiText.Equals("n")) { fs.Read(text, 0, 1); asciiText = ASCIIEncoding.ASCII.GetString(text); if (asciiText.Equals("r")) { fs.Seek(1, SeekOrigin.Current); break; } } } count = int.Parse((position - fs.Position).ToString()); line = new byte[count]; fs.Read(line, 0, count); fs.Seek(-count, SeekOrigin.Current); return ASCIIEncoding.ASCII.GetString(line); } public bool SOF { get { return fs.Position == 0; } } public void Close() { fs.Close(); } }
источник
IDisposable
поля, вы также должны реализовать ихIDisposable
и правильно избавиться от этих полей.Очень быстрое решение для больших файлов . Используйте командлет PowerShell Get-Content с опцией Tail. Вызов powershell приведет к небольшим накладным расходам, но для огромных файлов это бесполезно
using System.Management.Automation; const string FILE_PATH = @"d:\temp\b_media_27_34_0000_25393.txt"; var ps = PowerShell.Create(); ps.AddCommand("Get-Content") .AddParameter("Path", FILE_PATH) .AddParameter("Tail", 1); var psResults = ps.Invoke(); var lastLine = psResults.FirstOrDefault()?.BaseObject.ToString(); ps.Dispose();
Обязательная ссылка на PowerShell
источник
Также добавляю свое решение. После прочтения некоторых ответов ничего не подходило для моего случая. Я читаю байт за байтом сзади, пока не найду LineFeed, затем я сохраняю собранные байты в виде строки без использования буферизации .
Применение:
var reader = new ReverseTextReader(path); while (!reader.EndOfStream) { Console.WriteLine(reader.ReadLine()); }
Реализация:
public class ReverseTextReader { private const int LineFeedLf = 10; private const int LineFeedCr = 13; private readonly Stream _stream; private readonly Encoding _encoding; public bool EndOfStream => _stream.Position == 0; public ReverseTextReader(Stream stream, Encoding encoding) { _stream = stream; _encoding = encoding; _stream.Position = _stream.Length; } public string ReadLine() { if (_stream.Position == 0) return null; var line = new List<byte>(); var endOfLine = false; while (!endOfLine) { var b = _stream.ReadByteFromBehind(); if (b == -1 || b == LineFeedLf) { endOfLine = true; } line.Add(Convert.ToByte(b)); } line.Reverse(); return _encoding.GetString(line.ToArray()); } } public static class StreamExtensions { public static int ReadByteFromBehind(this Stream stream) { if (stream.Position == 0) return -1; stream.Position = stream.Position - 1; var value = stream.ReadByte(); stream.Position = stream.Position - 1; return value; } }
источник
Я хотел сделать то же самое. Вот мой код. Этот класс будет создавать временные файлы, содержащие фрагменты большого файла. Это позволит избежать раздувания памяти. Пользователь может указать, хочет ли он / она реверсировать файл. Соответственно, он вернет контент в обратном порядке.
Этот класс также можно использовать для записи больших данных в один файл без увеличения объема памяти.
Пожалуйста, оставьте отзыв.
using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; namespace BigFileService { public class BigFileDumper { /// <summary> /// Buffer that will store the lines until it is full. /// Then it will dump it to temp files. /// </summary> public int CHUNK_SIZE = 1000; public bool ReverseIt { get; set; } public long TotalLineCount { get { return totalLineCount; } } private long totalLineCount; private int BufferCount = 0; private StreamWriter Writer; /// <summary> /// List of files that would store the chunks. /// </summary> private List<string> LstTempFiles; private string ParentDirectory; private char[] trimchars = { '/', '\\'}; public BigFileDumper(string FolderPathToWrite) { this.LstTempFiles = new List<string>(); this.ParentDirectory = FolderPathToWrite.TrimEnd(trimchars) + "\\" + "BIG_FILE_DUMP"; this.totalLineCount = 0; this.BufferCount = 0; this.Initialize(); } private void Initialize() { // Delete existing directory. if (Directory.Exists(this.ParentDirectory)) { Directory.Delete(this.ParentDirectory, true); } // Create a new directory. Directory.CreateDirectory(this.ParentDirectory); } public void WriteLine(string line) { if (this.BufferCount == 0) { string newFile = "DumpFile_" + LstTempFiles.Count(); LstTempFiles.Add(newFile); Writer = new StreamWriter(this.ParentDirectory + "\\" + newFile); } // Keep on adding in the buffer as long as size is okay. if (this.BufferCount < this.CHUNK_SIZE) { this.totalLineCount++; // main count this.BufferCount++; // Chunk count. Writer.WriteLine(line); } else { // Buffer is full, time to create a new file. // Close the existing file first. Writer.Close(); // Make buffer count 0 again. this.BufferCount = 0; this.WriteLine(line); } } public void Close() { if (Writer != null) Writer.Close(); } public string GetFullFile() { if (LstTempFiles.Count <= 0) { Debug.Assert(false, "There are no files created."); return ""; } string returnFilename = this.ParentDirectory + "\\" + "FullFile"; if (File.Exists(returnFilename) == false) { // Create a consolidated file from the existing small dump files. // Now this is interesting. We will open the small dump files one by one. // Depending on whether the user require inverted file, we will read them in descending order & reverted, // or ascending order in normal way. if (this.ReverseIt) this.LstTempFiles.Reverse(); foreach (var fileName in LstTempFiles) { string fullFileName = this.ParentDirectory + "\\" + fileName; // FileLines will use small memory depending on size of CHUNK. User has control. var fileLines = File.ReadAllLines(fullFileName); // Time to write in the writer. if (this.ReverseIt) fileLines = fileLines.Reverse().ToArray(); // Write the lines File.AppendAllLines(returnFilename, fileLines); } } return returnFilename; } } }
Эту услугу можно использовать следующим образом -
void TestBigFileDump_File(string BIG_FILE, string FOLDER_PATH_FOR_CHUNK_FILES) { // Start processing the input Big file. StreamReader reader = new StreamReader(BIG_FILE); // Create a dump file class object to handle efficient memory management. var bigFileDumper = new BigFileDumper(FOLDER_PATH_FOR_CHUNK_FILES); // Set to reverse the output file. bigFileDumper.ReverseIt = true; bigFileDumper.CHUNK_SIZE = 100; // How much at a time to keep in RAM before dumping to local file. while (reader.EndOfStream == false) { string line = reader.ReadLine(); bigFileDumper.WriteLine(line); } bigFileDumper.Close(); reader.Close(); // Get back full reversed file. var reversedFilename = bigFileDumper.GetFullFile(); Console.WriteLine("Check output file - " + reversedFilename); }
источник
Здесь уже есть хорошие ответы, и вот еще один LINQ-совместимый класс, который вы можете использовать, который фокусируется на производительности и поддержке больших файлов. Предполагается терминатор строки "\ r \ n".
Использование :
var reader = new ReverseTextReader(@"C:\Temp\ReverseTest.txt"); while (!reader.EndOfStream) Console.WriteLine(reader.ReadLine());
ReverseTextReader Класс :
/// <summary> /// Reads a text file backwards, line-by-line. /// </summary> /// <remarks>This class uses file seeking to read a text file of any size in reverse order. This /// is useful for needs such as reading a log file newest-entries first.</remarks> public sealed class ReverseTextReader : IEnumerable<string> { private const int BufferSize = 16384; // The number of bytes read from the uderlying stream. private readonly Stream _stream; // Stores the stream feeding data into this reader private readonly Encoding _encoding; // Stores the encoding used to process the file private byte[] _leftoverBuffer; // Stores the leftover partial line after processing a buffer private readonly Queue<string> _lines; // Stores the lines parsed from the buffer #region Constructors /// <summary> /// Creates a reader for the specified file. /// </summary> /// <param name="filePath"></param> public ReverseTextReader(string filePath) : this(new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read), Encoding.Default) { } /// <summary> /// Creates a reader using the specified stream. /// </summary> /// <param name="stream"></param> public ReverseTextReader(Stream stream) : this(stream, Encoding.Default) { } /// <summary> /// Creates a reader using the specified path and encoding. /// </summary> /// <param name="filePath"></param> /// <param name="encoding"></param> public ReverseTextReader(string filePath, Encoding encoding) : this(new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read), encoding) { } /// <summary> /// Creates a reader using the specified stream and encoding. /// </summary> /// <param name="stream"></param> /// <param name="encoding"></param> public ReverseTextReader(Stream stream, Encoding encoding) { _stream = stream; _encoding = encoding; _lines = new Queue<string>(128); // The stream needs to support seeking for this to work if(!_stream.CanSeek) throw new InvalidOperationException("The specified stream needs to support seeking to be read backwards."); if (!_stream.CanRead) throw new InvalidOperationException("The specified stream needs to support reading to be read backwards."); // Set the current position to the end of the file _stream.Position = _stream.Length; _leftoverBuffer = new byte[0]; } #endregion #region Overrides /// <summary> /// Reads the next previous line from the underlying stream. /// </summary> /// <returns></returns> public string ReadLine() { // Are there lines left to read? If so, return the next one if (_lines.Count != 0) return _lines.Dequeue(); // Are we at the beginning of the stream? If so, we're done if (_stream.Position == 0) return null; #region Read and Process the Next Chunk // Remember the current position var currentPosition = _stream.Position; var newPosition = currentPosition - BufferSize; // Are we before the beginning of the stream? if (newPosition < 0) newPosition = 0; // Calculate the buffer size to read var count = (int)(currentPosition - newPosition); // Set the new position _stream.Position = newPosition; // Make a new buffer but append the previous leftovers var buffer = new byte[count + _leftoverBuffer.Length]; // Read the next buffer _stream.Read(buffer, 0, count); // Move the position of the stream back _stream.Position = newPosition; // And copy in the leftovers from the last buffer if (_leftoverBuffer.Length != 0) Array.Copy(_leftoverBuffer, 0, buffer, count, _leftoverBuffer.Length); // Look for CrLf delimiters var end = buffer.Length - 1; var start = buffer.Length - 2; // Search backwards for a line feed while (start >= 0) { // Is it a line feed? if (buffer[start] == 10) { // Yes. Extract a line and queue it (but exclude the \r\n) _lines.Enqueue(_encoding.GetString(buffer, start + 1, end - start - 2)); // And reset the end end = start; } // Move to the previous character start--; } // What's left over is a portion of a line. Save it for later. _leftoverBuffer = new byte[end + 1]; Array.Copy(buffer, 0, _leftoverBuffer, 0, end + 1); // Are we at the beginning of the stream? if (_stream.Position == 0) // Yes. Add the last line. _lines.Enqueue(_encoding.GetString(_leftoverBuffer, 0, end - 1)); #endregion // If we have something in the queue, return it return _lines.Count == 0 ? null : _lines.Dequeue(); } #endregion #region IEnumerator<string> Interface public IEnumerator<string> GetEnumerator() { string line; // So long as the next line isn't null... while ((line = ReadLine()) != null) // Read and return it. yield return line; } IEnumerator IEnumerable.GetEnumerator() { throw new NotImplementedException(); } #endregion }
источник
На случай, если кто-то еще столкнется с этим, я решил это с помощью следующего сценария PowerShell, который можно легко преобразовать в сценарий C # с небольшими усилиями.
[System.IO.FileStream]$fileStream = [System.IO.File]::Open("C:\Name_of_very_large_file.log", [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite) [System.IO.BufferedStream]$bs = New-Object System.IO.BufferedStream $fileStream; [System.IO.StreamReader]$sr = New-Object System.IO.StreamReader $bs; $buff = New-Object char[] 20; $seek = $bs.Seek($fileStream.Length - 10000, [System.IO.SeekOrigin]::Begin); while(($line = $sr.ReadLine()) -ne $null) { $line; }
Это в основном начинает чтение с последних 10 000 символов файла с выводом каждой строки.
источник
.Seek(-10000, [System.IO.SeekOrigin]::End);
?