Recipe 11.6 Randomly Accessing Part of a File
Problem
When reading a file, you
sometimes need to move from the current position in a file to a
position some number of characters before or after the current
position, including to the beginning or the end of a file. After
moving to this point, you can add, modify, or read the information at
this new point in the file.
Solution
To move
around in a stream, use the Seek method. The
following method writes the string contained in the variables
theFirstLine and theSecondLine
to a file in this same order. The stream is then flushed to the file
on disk:
public void CreateFile(string theFirstLine, int theSecondLine)
{
FileStream fileStream = new FileStream("data.txt",
FileMode.Create,
FileAccess.ReadWrite,
FileShare.None);
StreamWriter streamWriter = new StreamWriter(fileStream);
streamWriter.WriteLine(theFirstLine);
streamWriter.WriteLine(theSecondLine);
streamWriter.Flush( );
streamWriter.Close( );
fileStream.Close( );
}
If the following code is used to call this method:
CreateFile("This is the first line.", 1020304050);
the resulting data.txt file will contain the
following text:
This is the first line.
1020304050
The following method, ModifyFile, uses the
Seek method to reposition the current file
position at the end of the first line. A new line of text is then
added between the first and second lines of text in the file.
Finally, the Seek method is used to place the
current position pointer in the file to the end, and a final line of
text is written to this file:
public void ModifyFile(int theSecondLine)
{
// open the file for read/write
FileStream fileStream =
File.Open("data.txt",
FileMode.Open,
FileAccess.ReadWrite,
FileShare.None);
StreamWriter streamWriter = new StreamWriter(fileStream);
// backup over the newline
int offset = streamWriter.NewLine.Length;
// backup over the second line
offset += (theSecondLine.ToString( ).Length);
// make negative
offset = offset - (2 * offset);
// move file pointer to just after first line
streamWriter.BaseStream.Seek(offset, SeekOrigin.End);
StringBuilder stringBuilder
= new StringBuilder("This line added by seeking ");
stringBuilder.AppendFormat("{0} chars from the end of this file.",offset);
streamWriter.WriteLine(sb.ToString( ));
streamWriter.Flush( );
streamWriter.BaseStream.Seek(0, SeekOrigin.End);
streamWriter.WriteLine("This is the last line" +
", added by seeking to the end of the file.");
streamWriter.Close( );
}
If the following code is used to call this method:
ModifyFile(1020304050);
the resulting data.txt file will contain the
following text:
This is the first line.
This line added by seeking -12 chars from the end of this file.
This is the last line, added by seeking to the end of the file.
The next method, ReadFile, reads the file that we
just created. First, the current position pointer in the file is
moved to the end of the first line (this line contains the string in
the variable theFirstLine). The
ReadToEnd method is invoked reading the rest of
the file (the second and third lines in the file) and the results are
displayed:
public void ReadFile(string theFirstLine)
{
StreamReader streamReader = new StreamReader("data.txt");
streamReader.BaseStream.Seek(
theFirstLine.Length + Environment.NewLine.Length, SeekOrigin.Begin);
Console.WriteLine(streamReader.ReadToEnd( ));
streamReader.Close( );
}
The following text is displayed:
This line added by seeking -12 chars from the end of this file.
This is the last line, added by seeking to the end of the file.
If you are wondering where the line of text that reads:
1020304050
is located, it was overwritten when we did the first
Seek while writing data to this file.
Discussion
File seeking is the placement of the pointer to
the current location in an opened file anywhere between—and
including—the beginning and ending bytes of a file. Seeking is
performed through the use of the Seek method.
This method returns the new location of the file pointer in the file.
Seeking is performed in one of three ways: as an offset from the
beginning of the file, as an offset from the end of the file, or as
an offset from the current location in the file, as shown
here:
public void MoveInFile(int offsetValue)
{
FileStream fileStream =
File.Open("data.txt",
FileMode.Open,
FileAccess.ReadWrite,
FileShare.None);
StreamWriter streamWriter = new StreamWriter(fileStream);
// move from the beginning of the file
streamWriter.BaseStream.Seek(offsetValue, SeekOrigin.Begin);
// move from the end of the file
streamWriter.BaseStream.Seek(offsetValue, SeekOrigin.End);
// move from the current file pointer location in the file
streamWriter.BaseStream.Seek(offsetValue, SeekOrigin.Current);
streamWriter.Close( );
}
offsetValue may be any positive or negative number
as long as it does not attempt to force the file pointer before the
beginning of the file or after the end. The
SeekOrigin.Begin
enumeration value starts the offset at the beginning of the file;
likewise, the SeekOrigin.End value starts the
offset at the end of the file. The
SeekOrigin.Current value starts the offset at the
current location of the file pointer. You must take extra care not to
force the file pointer to a point before the start of the file when
using the seek method with a negative offset, since this action could
move the file pointer before the beginning of the file. If you think
about it logically, you should be giving positive values when
specifying SeekOrigin.Begin, negative values when
specifying SeekOrigin.End, and any value makes
sense for SeekOrigin.Current, so long as it
doesn't cause the pointer to roll over the beginning
or the end. To prevent the
IOException from being thrown in this
circumstance, you can test for this condition in the following
manner:
long offsetValue = -20;
FileStream fileStream =
File.Open("data.txt",
FileMode.Open,
FileAccess.ReadWrite,
FileShare.None);
StreamWriter streamWriter = new StreamWriter(fileStream);
if ((offsetValue + streamWriter.BaseStream.Position) >= 0)
{
streamWriter.BaseStream.Seek(OffsetValue, SeekOrigin.Current);
}
else
{
Console.WriteLine("Cannot seek before the beginning of the file.");
}
if ((offsetValue + streamWriter.BaseStream.Length) >= 0)
{
streamWriter.BaseStream.Seek(offsetValue, SeekOrigin.End);
}
else
{
Console.WriteLine("Cannot seek before the beginning of the file.");
}
if (offsetValue >= 0)
{
streamWriter.BaseStream.Seek(offsetValue, SeekOrigin.Begin);
}
else
{
Console.WriteLine("Cannot seek before the beginning of the file.");
}
To seek to the beginning of a file, use the following code:
streamWriter.BaseStream.Seek(0, SeekOrigin.Begin);
To seek to the end of a file, use the following code:
streamWriter.BaseStream.Seek(0, SeekOrigin.End);
The SeekOrigin enumeration value sets the file
pointer to the beginning or end of a file and then the offset, which
is zero, does not force the file pointer to move. With this in mind,
realize that using zero as an offset to
SeekOrigin.Current is pointless because you just
don't move the pointer at all, and you are killing
clock cycles to no effect.
See Also
See the "FileStream Class,"
"StreamReader Class,"
"StreamWriter Class,"
"Binary-Reader Class,"
"BinaryWriter Class," and
"SeekOrigin Enumeration" topics in
the MSDN documentation.
|