Skip to content

Instantly share code, notes, and snippets.

@jaredpar
Created January 9, 2025 01:25
Show Gist options
  • Save jaredpar/68a5af0ab5983a285107356e53042d9b to your computer and use it in GitHub Desktop.
Save jaredpar/68a5af0ab5983a285107356e53042d9b to your computer and use it in GitHub Desktop.
Read a file line by line without allocating too muuch
public ref struct LineEnumerator(string filePath) : IDisposable
{
private static SearchValues<char> NewLines = SearchValues.Create('\r', '\n');
private StreamReader _reader = new StreamReader(filePath);
private char[] _array = ArrayPool<char>.Shared.Rent(1024);
// This represents the data read from the file that hasn't been processed yet. This will
// be a subset of _array
private Span<char> _buffer = Span<char>.Empty;
// Have we read everything from the file
private bool _endOfFile;
private Span<char> _current;
public Span<char> Current => _current;
public bool MoveNext()
{
if (_endOfFile && _buffer.IsEmpty)
{
return false;
}
again:
var newLineIndex = _buffer.IndexOfAny(NewLines);
if (newLineIndex >= 0 &&
!(!_endOfFile && newLineIndex == _buffer.Length - 1 && _buffer[newLineIndex] == '\r'))
{
_current = _buffer[0..newLineIndex];
MovePastNewLine(ref _buffer, newLineIndex);
return true;
}
if (_buffer.Length == _array.Length)
{
var newArray = ArrayPool<char>.Shared.Rent(_array.Length * 2);
_buffer.CopyTo(newArray);
_buffer = newArray.AsSpan(0, _buffer.Length);
ArrayPool<char>.Shared.Return(_array);
_array = newArray;
}
else
{
// Move the array contents to the front
_buffer.CopyTo(_array.AsSpan());
var readBuffer = _array.AsSpan(_buffer.Length, _array.Length - _buffer.Length);
var read = _reader.Read(readBuffer);
if (read == 0)
{
_endOfFile = true;
_current = _buffer;
_buffer = Span<char>.Empty;
return true;
}
_buffer = _array.AsSpan(0, _buffer.Length + read);
}
goto again;
static void MovePastNewLine(ref Span<char> buffer, int newLineIndex)
{
var c = buffer[newLineIndex];
if (c == '\r' &&
newLineIndex + 1 < buffer.Length &&
buffer[newLineIndex + 1] == '\n')
{
buffer = buffer[(newLineIndex + 2)..];
}
else
{
buffer = buffer[(newLineIndex + 1)..];
}
}
}
public void Dispose()
{
_reader.Dispose();
ArrayPool<char>.Shared.Return(_array);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment