c# - txt - notepad++ abrir archivos grandes
Obtenga las Ășltimas 10 lĂneas de archivo de texto muy grande> 10 GB (19)
¿Cola? Tail es un comando de Unix que mostrará las últimas líneas de un archivo. Hay una versión de Windows en el kit de recursos de Windows 2003 Server .
¿Cuál es la forma más eficiente de mostrar las últimas 10 líneas de un archivo de texto muy grande (este archivo en particular tiene más de 10 GB)? Estaba pensando simplemente en escribir una aplicación C # simple, pero no estoy seguro de cómo hacerlo de manera efectiva.
¿Por qué no usar file.readalllines que devuelve una cadena []?
Luego puede obtener las últimas 10 líneas (o miembros de la matriz), lo que sería una tarea trivial.
Este enfoque no tiene en cuenta los problemas de codificación y no estoy seguro de la eficacia exacta de este enfoque (tiempo necesario para completar el método, etc.).
Abra el archivo y comience a leer líneas. Después de leer 10 líneas, abra otro puntero, comenzando por la parte delantera del archivo, de modo que el segundo puntero quede rezagado con el primero por 10 líneas. Sigue leyendo, moviendo los dos punteros al unísono, hasta que el primero llegue al final del archivo. Luego usa el segundo puntero para leer el resultado. Funciona con cualquier archivo de tamaño, incluso vacío y más corto que la longitud de la cola. Y es fácil de ajustar para cualquier longitud de cola. La desventaja, por supuesto, es que terminas leyendo todo el archivo y eso puede ser exactamente lo que intentas evitar.
Acabo de tener el mismo problema, un gran archivo de registro al que se debe acceder a través de una interfaz REST. Por supuesto, cargarlo en cualquier memoria y enviarlo completo a través de http no fue una solución.
Como señaló Jon, esta Solución tiene un uso muy específico. En mi caso, estoy seguro (y comprobado), que la codificación es utf-8 (¡con BOM!) Y por lo tanto puede beneficiarse de todas las bendiciones de UTF. Seguramente no es una solución de propósito general.
Esto es lo que funcionó para mí muy bien y rápido (se me olvidó cerrar la secuencia, corregido ahora):
private string tail(StreamReader streamReader, long numberOfBytesFromEnd)
{
Stream stream = streamReader.BaseStream;
long length = streamReader.BaseStream.Length;
if (length < numberOfBytesFromEnd)
numberOfBytesFromEnd = length;
stream.Seek(numberOfBytesFromEnd * -1, SeekOrigin.End);
int LF = ''/n'';
int CR = ''/r'';
bool found = false;
while (!found) {
int c = stream.ReadByte();
if (c == LF)
found = true;
}
string readToEnd = streamReader.ReadToEnd();
streamReader.Close();
return readToEnd;
}
Primero buscamos algo cerca del final con BaseStream, y cuando tenemos la posición correcta de la transmisión, leemos hasta el final con el StreamReader habitual.
Esto realmente no permite especificar la cantidad de líneas desde el final, lo que no es una buena idea de todos modos, ya que las líneas podrían ser arbitrariamente largas y, por lo tanto, matar el rendimiento de nuevo. Así que especifico la cantidad de bytes, leí hasta que lleguemos a la primera línea nueva y la lectura cómodamente hasta el final. Teóricamente, también podría buscar CarriageReturn, pero en mi caso, eso no fue necesario.
Si usamos este código, no molestará a un hilo de escritor:
FileStream fileStream = new FileStream(
filename,
FileMode.Open,
FileAccess.Read,
FileShare.ReadWrite);
StreamReader streamReader = new StreamReader(fileStream);
Como han sugerido los demás, puede ir al final del archivo y leer hacia atrás de manera efectiva. Sin embargo, es un poco complicado, sobre todo porque si tiene una codificación de longitud variable (como UTF-8) debe ser astuto para asegurarse de obtener caracteres "enteros".
Creo que el siguiente código resolverá el problema con cambios sutiles regrabando la codificación
StreamReader reader = new StreamReader(@"c:/test.txt"); //pick appropriate Encoding
reader.BaseStream.Seek(0, SeekOrigin.End);
int count = 0;
while ((count < 10) && (reader.BaseStream.Position > 0))
{
reader.BaseStream.Position--;
int c = reader.BaseStream.ReadByte();
if (reader.BaseStream.Position > 0)
reader.BaseStream.Position--;
if (c == Convert.ToInt32(''/n''))
{
++count;
}
}
string str = reader.ReadToEnd();
string[] arr = str.Replace("/r", "").Split(''/n'');
reader.Close();
Creo que los otros carteles han demostrado que no hay un atajo real.
Puede usar una herramienta como tail (o powershell) o puede escribir un código tonto que busque el final del archivo y luego busque n nuevas líneas.
Hay muchas implementaciones de cola en la web, eche un vistazo al código fuente para ver cómo lo hacen. ¡Tail es bastante eficiente (incluso en archivos muy grandes) y deben haberlo hecho bien cuando lo escribieron!
Debería poder utilizar FileStream.Seek() para desplazarse hasta el final del archivo y, luego, abrirse camino hacia atrás, buscando / n hasta que tenga suficientes líneas.
En caso de que necesite leer cualquier cantidad de líneas en reversa desde un archivo de texto, aquí hay una clase compatible con LINQ que puede usar. Se centra en el rendimiento y soporte para archivos de gran tamaño. Puede leer varias líneas y llamar a Reverse () para obtener las últimas líneas en orden de reenvío:
Uso :
var reader = new ReverseTextReader(@"C:/Temp/ReverseTest.txt");
while (!reader.EndOfStream)
Console.WriteLine(reader.ReadLine());
ReverseTextReader Class :
/// <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
}
Eso es lo que hace el comando tail de Unix. Ver http://en.wikipedia.org/wiki/Tail_(Unix)
Hay muchas implementaciones de código abierto en Internet y aquí hay una para win32: Tail for WIn32
No estoy seguro de lo eficiente que será, pero en Windows PowerShell obtener las últimas diez líneas de un archivo es tan fácil como
Get-Content file.txt | Select-Object -last 10
Probablemente solo lo abriría como una secuencia binaria, buscaría hasta el final y luego volvería a buscar saltos de línea. Haga una copia de seguridad de 10 (u 11 dependiendo de la última línea) para encontrar sus 10 líneas, luego simplemente lea hasta el final y use Encoding.GetString en lo que lee para ponerlo en un formato de cadena. Dividir según lo deseado.
Puede usar la versión de Windows del comando tail y simplemente enviar su salida a un archivo de texto con el símbolo> o verlo en la pantalla según sus necesidades.
Si abre el archivo con FileMode.Append, buscará el final del archivo por usted. Entonces podría buscar la cantidad de bytes que desea y leerlos. Puede que no sea rápido, independientemente de lo que hagas, ya que es un archivo bastante masivo.
Si tiene un archivo que tiene un formato par por línea (como un sistema daq), simplemente use el lector de secuencias para obtener la longitud del archivo, luego tome una de las líneas, ( readline()
).
Divida la longitud total por la longitud de la cuerda. Ahora tiene un número largo general para representar el número de líneas en el archivo.
La clave es que use readline()
antes de obtener sus datos para su matriz o lo que sea. Esto asegurará que comience al principio de una nueva línea, y no obtenga ningún dato sobrante de la anterior.
StreamReader leader = new StreamReader(GetReadFile);
leader.BaseStream.Position = 0;
StreamReader follower = new StreamReader(GetReadFile);
int count = 0;
string tmper = null;
while (count <= 12)
{
tmper = leader.ReadLine();
count++;
}
long total = follower.BaseStream.Length; // get total length of file
long step = tmper.Length; // get length of 1 line
long size = total / step; // divide to get number of lines
long go = step * (size - 12); // get the bit location
long cut = follower.BaseStream.Seek(go, SeekOrigin.Begin); // Go to that location
follower.BaseStream.Position = go;
string led = null;
string[] lead = null ;
List<string[]> samples = new List<string[]>();
follower.ReadLine();
while (!follower.EndOfStream)
{
led = follower.ReadLine();
lead = Tokenize(led);
samples.Add(lead);
}
Un método útil es FileInfo.Length
. Da el tamaño de un archivo en bytes.
¿Qué estructura es tu archivo? ¿Estás seguro de que las últimas 10 líneas estarán cerca del final del archivo? Si tiene un archivo con 12 líneas de texto y 10 GB de 0, entonces mirar el final no será tan rápido. Por otra parte, es posible que tenga que mirar todo el archivo.
Si está seguro de que el archivo contiene numerosas cadenas cortas, cada una en una nueva línea, busque hasta el final, luego vuelva atrás hasta que haya contado 11 fin de línea. Luego puede leer hacia adelante las siguientes 10 líneas.
Utilizando la respuesta de Sisutil como punto de partida, podría leer el archivo línea por línea y cargarlo en una Queue<String>
. Lee el archivo desde el principio, pero tiene la virtud de no intentar leer el archivo al revés. Esto puede ser realmente difícil si tiene un archivo con una codificación de ancho de caracteres variable como UTF-8 como señaló Jon Skeet. Tampoco hace suposiciones sobre la longitud de la línea.
Probé esto en un archivo de 1.7GB (no tenía una de 10GB) y me llevó unos 14 segundos. Por supuesto, las advertencias habituales se aplican cuando se comparan los tiempos de carga y lectura entre computadoras.
int numberOfLines = 10;
string fullFilePath = @"C:/Your/Large/File/BigFile.txt";
var queue = new Queue<string>(numberOfLines);
using (FileStream fs = File.Open(fullFilePath, FileMode.Open, FileAccess.Read, FileShare.Read))
using (BufferedStream bs = new BufferedStream(fs)) // May not make much difference.
using (StreamReader sr = new StreamReader(bs)) {
while (!sr.EndOfStream) {
if (queue.Count == numberOfLines) {
queue.Dequeue();
}
queue.Enqueue(sr.ReadLine());
}
}
// The queue now has our set of lines. So print to console, save to another file, etc.
do {
Console.WriteLine(queue.Dequeue());
} while (queue.Count > 0);
aquí está la versión mía. HTH
using (StreamReader sr = new StreamReader(path))
{
sr.BaseStream.Seek(0, SeekOrigin.End);
int c;
int count = 0;
long pos = -1;
while(count < 10)
{
sr.BaseStream.Seek(pos, SeekOrigin.End);
c = sr.Read();
sr.DiscardBufferedData();
if(c == Convert.ToInt32(''/n''))
++count;
--pos;
}
sr.BaseStream.Seek(pos, SeekOrigin.End);
string str = sr.ReadToEnd();
string[] arr = str.Split(''/n'');
}
Lea hasta el final del archivo, luego busque hacia atrás hasta que encuentre diez nuevas líneas, y luego lea hacia adelante hasta el final teniendo en cuenta varias codificaciones. Asegúrese de manejar los casos donde el número de líneas en el archivo es menor que diez. A continuación hay una implementación (en C # cuando etiquetó esto), generalizada para encontrar el último numberOfTokens
en el archivo ubicado en la path
codificada en la encoding
donde el separador de tokens está representado por tokenSeparator
; el resultado se devuelve como una string
(esto podría mejorarse al devolver una IEnumerable<string>
que enumera los tokens).
public static string ReadEndTokens(string path, Int64 numberOfTokens, Encoding encoding, string tokenSeparator) {
int sizeOfChar = encoding.GetByteCount("/n");
byte[] buffer = encoding.GetBytes(tokenSeparator);
using (FileStream fs = new FileStream(path, FileMode.Open)) {
Int64 tokenCount = 0;
Int64 endPosition = fs.Length / sizeOfChar;
for (Int64 position = sizeOfChar; position < endPosition; position += sizeOfChar) {
fs.Seek(-position, SeekOrigin.End);
fs.Read(buffer, 0, buffer.Length);
if (encoding.GetString(buffer) == tokenSeparator) {
tokenCount++;
if (tokenCount == numberOfTokens) {
byte[] returnBuffer = new byte[fs.Length - fs.Position];
fs.Read(returnBuffer, 0, returnBuffer.Length);
return encoding.GetString(returnBuffer);
}
}
}
// handle case where number of tokens in file is less than numberOfTokens
fs.Seek(0, SeekOrigin.Begin);
buffer = new byte[fs.Length];
fs.Read(buffer, 0, buffer.Length);
return encoding.GetString(buffer);
}
}