DekGenius.com
[ Team LiB ] Previous Section Next Section

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.

    [ Team LiB ] Previous Section Next Section