Seeking

How to jump around in a file
a.k.a. Random Access Files

Functions like <<, and fscanf, and fread, and fwrite, and fprintf, and all the other file IO functions in both C and C++, automatically move the file pointer forward as you read or write. By default, reading starts at the top of the file.

You do not have to start reading or writing at the top of a file. You can start in the middle, jump forward, and jump backward. In other words, you can control the file pointer.

When you open a file for reading or input, the value of the file pointer is automatically is set to "0", or the beginning of the input file. When you open a file for writing, the file pointer also starts at 0 and the size is set to 0. When you open a file for appending, the file pointer is set to the file size (which equals the end of the file). And, something you may not have known, you can also open a file for BOTH reading and writing.


Random Access Reading

The C function "fseek" moves the file pointer. The C++ method "seekg" move the "get" (or reading location) file pointer. Both the C and C++ versions of seek take two parameters: how far and from where. A positive value for how far moves the file pointer forward and a negative value moves backward. The "from where" can be from the beginning of the file, the current location, or the end of the file.

The predefined constants in C for "from where" are:
    SEEK_SET - from beginning of file
    SEEK_CUR - from current
    SEEK_END - from the end of file
The predefined constants in C++ are:
    ios::beg
    iso::cur
    iso::end


Example Reading Code

Here are two programs, one in C and one in C++, that do exactly the same thing. Both programs use the sorted records of faculty office information that you have used before. Remember that file starts with an integer count, then is followed by count number of records.

The programs fetch two records. The user enters the number of the record they want first, then indicate how many records to move forward or backwards.

If the records are numbered 1-N, then the starting location of record N is:
    sizeof(int) + ( (N-1) * sizeof(record) )
Since our strange file starts with a single integer we have to add that offset. Since we are numbering 1 to N, instead of 0 to N-1, we have to subtract 1 from N. For example, record number 1 is really 0 bytes after the integer header. To read record number 5, we must skip the first 4 records. The sizeof function tells us how big our records are, hence how far to move for each record.

Note: The following code is intentionally not well commented. If you want to know how these programs work then you need to come to class.

/**********************************
Program to demo using seek in C
**********************************/

#include <stdio.h>
#include <stdlib.h>

/********************************************/
struct faculty_type 
  {
   char fname[15],lname[15];
   int phone, office;
   char dept[5];
  };

/********************************************/

void main ()
{
   FILE                *infile;
   struct faculty_type temp;
   int                 status, count, rec_num;
   long                rec_location;


   infile = fopen ("faculty_sorted.dat","r");
   if (infile == NULL)
     { fprintf(stderr, "\nError opening input\n\n"); exit (1); }

   fread (&count, sizeof(int), 1, infile);
   printf ("\ninfile contains records 1-%d\n\n", count);

   printf ("Which record do you want first: ");
   scanf ("%d", &rec_num);
   rec_num--;
   rec_location = sizeof(int) + sizeof(struct faculty_type)*rec_num;
   fseek (infile, rec_location, SEEK_SET);
   fread (&temp, sizeof(struct faculty_type), 1, infile);
   printf("%s %s\n", temp.fname, temp.lname);

   printf ("Next record (negative = back, positive = forward): ");
   scanf ("%d", &rec_num);
   rec_location = sizeof(struct faculty_type) * rec_num;
   status = fseek (infile, rec_location, SEEK_CUR);
   if (status == -1)
      printf ("could not move there\n");
   else
     {
      fread (&temp, sizeof(struct faculty_type), 1, infile);
      printf("%s %s\n", temp.fname, temp.lname);
     }
}

/**********************************************
Demo of using seek in C++
*********************************************/
#include <iostream>
#include <fstream>
#include <cstdlib>
using namespace std;

/******************************************************/
struct faculty_type
  {
   char fname[15],lname[15];
   int phone, office;
   char dept[5];
  };
/******************************************************/

int main ()
{
   ifstream    infile;          // the only input file
   long        byte_location;   // where to move to
   int         count;           // record count from data file
   int         rec_number;      // input : record number
   struct faculty_type  record;

   infile.open ("faculty_sorted.dat", ios::in);
   if (!infile)
     { cerr << "Error opening input file"; exit (1); }

   infile.read(reinterpret_cast<char *> (&count), sizeof(int));
   cout << "\nRead records 1-" << count << endl;

   cout << "Which record first: ";
   cin >> rec_number;
   rec_number--;
   byte_location = sizeof(int) + sizeof(struct faculty_type) * rec_number;
   infile.seekg (byte_location, ios::beg);
   infile.read(reinterpret_cast<char *> (&record), sizeof(struct faculty_type));
   cout << record.fname << " " << record.lname << endl;

   cout << "Which record next (neg=back, pos=forward): ";
   cin >> rec_number;
   byte_location = sizeof(struct faculty_type) * rec_number;
   infile.seekg (byte_location, ios::cur);
   if (infile.fail())
      cout << "could not move there\n";
   else
     {
      infile.read(reinterpret_cast<char *> (&record), sizeof(struct faculty_type));
      cout << record.fname << " " << record.lname << endl;
     }
}



Here is an example run:

> data_viewer faculty_sorted.dat | head
Charles Alvis      2695   431   ACCT
Keith Benson       4834   504   HELT
David Bradbard     4801   430   MGMT
Bob Breakfield     2685   314   BLAW
Barbara Burgess    2690   427   MGMT
Patrice Burleson   4835   125   MRKT
Jordan Cao         2674   318   QMTH
Melisa Carsten     4817   518   HRMG
Clarence Coleman   2678   112   ACCT
Mike Cornick       4624   410   FINC

> a.out

infile contains records 1-47

Which record do you want first: 1
Charles Alvis
Next record (negative = back, positive = forward): 1
David Bradbard

> a.out

infile contains records 1-47

Which record do you want first: 5
Barbara Burgess
Next record (negative = back, positive = forward): -1
Barbara Burgess

> a.out

infile contains records 1-47

Which record do you want first: 1
Charles Alvis
Next record (negative = back, positive = forward): -2
could not move there


Random Access Reading and Writing

In C++, to both read and write a file, declare the file variable as fstream, instead of ifstream or ofstream. Also, use "in | out" as the open mode. (The | operator ORs the bits to make the file both in and out.)

While seekg sets the location to GET input, the method seekp sets the PUT location.

The following example will change the first name of a faculty member for an indicated record number. Not seekg and read retrieve the record, then seekp and write output the record. It is not safe to try to write just part of record. If possible read and write whole records.

/**********************************************
Demo of using seek to overwrite data in C++
*********************************************/
#include <iostream>
#include <fstream>
#include <cstdlib>
#include <string.h>
using namespace std;

/******************************************************/

struct faculty_type
  {
   char fname[15],lname[15];
   int phone, office;
   char dept[5];
  };

/******************************************************/
int main ()
{
   fstream myfile;        // the input and output file
   long byte_location;    // where to move to
   int count;             // record count from data file
   int rec_number;        // input : record number
   struct faculty_type record;
   char new_name[15];

   myfile.open ("faculty_sorted.dat", ios::in | ios::out);
   if (!myfile)
     { cerr << "Error opening input file"; exit (1); }

   /**** move fileptr to top and read record count *****/
   myfile.seekg (0, ios::beg);
   myfile.read(reinterpret_cast<char *> (&count), sizeof(int));
   cout << "\nRead records 1-" << count << endl;

   /***** get user input *****/
   cout << "Which record to change: ";
   cin >> rec_number;
   cout << "Enter new First Name: ";
   cin >> new_name;

   /***** Retrieve existing record *****/
   byte_location = sizeof(int) + (sizeof(struct faculty_type) * (rec_number - 1));     
   myfile.seekg (byte_location, ios::beg);
   myfile.read(reinterpret_cast<char *> (&record), sizeof(struct faculty_type));

   /***** Reset that record *****/
   strcpy (record.fname, new_name);
   myfile.seekp (byte_location, ios::beg);
   myfile.write(reinterpret_cast<char *> (&record), sizeof(struct faculty_type));
}

And here is the output from one run, changing Professor Alvis' name from Charles to Chuck.

> a.out

Read records 1-47
Which record to change: 1
Enter new First Name: Chuck

> data_viewer faculty_sorted.dat | head
Chuck          Alvis           2695  431  ACCT     
Keith          Benson          4834  504  HELT
David          Bradbard        4801  430  MGMT
Bob            Breakfield      2685  314  BLAW
Barbara        Burgess         2690  427  MGMT
Patrice        Burleson        4835  125  MRKT
Jordan         Cao             2674  318  QMTH
Melisa         Carsten         4817  518  HRMG
Clarence       Coleman         2678  112  ACCT
Mike           Cornick         4624  410  FINC