Coming Soon

This lesson is currently being developed

Random file I/O

Access file data at arbitrary positions.

Input and Output (I/O)
Chapter
Beginner
Difficulty
40min
Estimated Time

What to Expect

Comprehensive explanations with practical examples

Interactive coding exercises to practice concepts

Knowledge quiz to test your understanding

Step-by-step guidance for beginners

Development Status

In Progress

Content is being carefully crafted to provide the best learning experience

Preview

Early Preview Content

This content is still being developed and may change before publication.

28.7 — Random file I/O

In this lesson, you'll learn about random (direct) file access, which allows you to read from and write to any position in a file without processing it sequentially from the beginning.

What is random file I/O?

Random file I/O (also called direct access or random access) allows you to jump to any position in a file to read or write data. This is different from sequential access, where you must read through the file from beginning to end.

Think of random access like using bookmarks in a book - you can quickly jump to any page you want, rather than reading every page from the start.

Sequential vs. Random access

Sequential access (what we've done so far)

File: [A][B][C][D][E][F]
Read:  A → B → C → D → E → F (in order)

Random access

File: [A][B][C][D][E][F]
Read:  Jump to D, then A, then F (any order)

File position pointers

Every file stream maintains position pointers:

  • Get pointer (g): Current position for reading
  • Put pointer (p): Current position for writing

For std::fstream (read/write streams), both pointers exist. For std::ifstream and std::ofstream, only the relevant pointer exists.

Seeking to file positions

Basic seeking with seekg() and seekp()

#include <fstream>
#include <iostream>

int main()
{
    // Create a file with some data first
    std::ofstream createFile("data.txt");
    createFile << "ABCDEFGHIJKLMNOP";
    createFile.close();
    
    // Now open for reading with random access
    std::ifstream file("data.txt");
    
    if (!file.is_open())
    {
        std::cerr << "Error opening file!" << std::endl;
        return 1;
    }
    
    char ch;
    
    // Read character at position 0 (first character)
    file.seekg(0);
    file >> ch;
    std::cout << "Position 0: " << ch << std::endl;
    
    // Jump to position 5
    file.seekg(5);
    file >> ch;
    std::cout << "Position 5: " << ch << std::endl;
    
    // Jump to position 10
    file.seekg(10);
    file >> ch;
    std::cout << "Position 10: " << ch << std::endl;
    
    return 0;
}

Output:

Position 0: A
Position 5: F
Position 10: K

Seek origins

You can specify the origin for seeking using these constants:

  • std::ios::beg - Beginning of file (default)
  • std::ios::cur - Current position
  • std::ios::end - End of file
#include <fstream>
#include <iostream>

int main()
{
    std::ifstream file("data.txt");
    
    if (!file.is_open())
    {
        std::cerr << "Error opening file!" << std::endl;
        return 1;
    }
    
    char ch;
    
    // Seek from beginning
    file.seekg(3, std::ios::beg);
    file >> ch;
    std::cout << "3 from beginning: " << ch << std::endl;
    
    // Seek from current position
    file.seekg(2, std::ios::cur);
    file >> ch;
    std::cout << "2 from current: " << ch << std::endl;
    
    // Seek from end (negative offset)
    file.seekg(-1, std::ios::end);
    file >> ch;
    std::cout << "1 from end: " << ch << std::endl;
    
    return 0;
}

Output:

3 from beginning: D
2 from current: G
1 from end: P

Getting current file position

Use tellg() and tellp() to get current positions:

#include <fstream>
#include <iostream>

int main()
{
    std::ifstream file("data.txt");
    
    if (!file.is_open())
    {
        std::cerr << "Error opening file!" << std::endl;
        return 1;
    }
    
    // Show initial position
    std::cout << "Initial position: " << file.tellg() << std::endl;
    
    // Read some data
    char buffer[5];
    file.read(buffer, 4);
    buffer[4] = '\0';
    
    std::cout << "Read: " << buffer << std::endl;
    std::cout << "Current position: " << file.tellg() << std::endl;
    
    // Jump to position 10
    file.seekg(10);
    std::cout << "After seeking to 10: " << file.tellg() << std::endl;
    
    return 0;
}

Output:

Initial position: 0
Read: ABCD
Current position: 4
After seeking to 10: 10

Binary file random access

Random access is most useful with binary files where you know the exact size of data records:

#include <fstream>
#include <iostream>
#include <string>

struct Employee
{
    int id;
    char name[20];
    double salary;
    
    Employee() : id(0), salary(0.0)
    {
        name[0] = '\0';
    }
    
    Employee(int empId, const std::string& empName, double empSalary)
        : id(empId), salary(empSalary)
    {
        // Copy name safely
        size_t len = std::min(empName.length(), sizeof(name) - 1);
        empName.copy(name, len);
        name[len] = '\0';
    }
};

int main()
{
    const std::string filename = "employees.dat";
    
    // Create binary file with employee data
    {
        std::ofstream outFile(filename, std::ios::binary);
        if (!outFile)
        {
            std::cerr << "Error creating file!" << std::endl;
            return 1;
        }
        
        Employee employees[] = {
            Employee(101, "Alice Johnson", 75000.0),
            Employee(102, "Bob Smith", 65000.0),
            Employee(103, "Carol Brown", 80000.0),
            Employee(104, "David Wilson", 70000.0),
            Employee(105, "Eve Davis", 85000.0)
        };
        
        for (const auto& emp : employees)
        {
            outFile.write(reinterpret_cast<const char*>(&emp), sizeof(Employee));
        }
    }
    
    // Now read specific employees using random access
    std::ifstream inFile(filename, std::ios::binary);
    if (!inFile)
    {
        std::cerr << "Error opening file for reading!" << std::endl;
        return 1;
    }
    
    // Read employee at position 2 (third employee)
    Employee emp;
    inFile.seekg(2 * sizeof(Employee), std::ios::beg);
    inFile.read(reinterpret_cast<char*>(&emp), sizeof(Employee));
    
    std::cout << "Employee at position 2:" << std::endl;
    std::cout << "ID: " << emp.id << std::endl;
    std::cout << "Name: " << emp.name << std::endl;
    std::cout << "Salary: $" << emp.salary << std::endl << std::endl;
    
    // Read last employee
    inFile.seekg(-static_cast<int>(sizeof(Employee)), std::ios::end);
    inFile.read(reinterpret_cast<char*>(&emp), sizeof(Employee));
    
    std::cout << "Last employee:" << std::endl;
    std::cout << "ID: " << emp.id << std::endl;
    std::cout << "Name: " << emp.name << std::endl;
    std::cout << "Salary: $" << emp.salary << std::endl;
    
    return 0;
}

Output:

Employee at position 2:
ID: 103
Name: Carol Brown
Salary: $80000

Last employee:
ID: 105
Name: Eve Davis
Salary: $85000

Random write access

You can also write to specific positions in a file:

#include <fstream>
#include <iostream>

int main()
{
    const std::string filename = "numbers.dat";
    
    // Create file with initial data
    {
        std::ofstream file(filename, std::ios::binary);
        int numbers[] = {10, 20, 30, 40, 50};
        for (int num : numbers)
        {
            file.write(reinterpret_cast<const char*>(&num), sizeof(int));
        }
    }
    
    // Update specific positions
    std::fstream file(filename, std::ios::binary | std::ios::in | std::ios::out);
    if (!file)
    {
        std::cerr << "Error opening file!" << std::endl;
        return 1;
    }
    
    // Update number at position 1 (second number)
    int newValue = 999;
    file.seekp(1 * sizeof(int), std::ios::beg);
    file.write(reinterpret_cast<const char*>(&newValue), sizeof(int));
    
    // Update number at position 3 (fourth number)
    newValue = 888;
    file.seekp(3 * sizeof(int), std::ios::beg);
    file.write(reinterpret_cast<const char*>(&newValue), sizeof(int));
    
    // Read and display all numbers
    file.seekg(0, std::ios::beg);
    int number;
    int position = 0;
    
    std::cout << "Updated numbers:" << std::endl;
    while (file.read(reinterpret_cast<char*>(&number), sizeof(int)))
    {
        std::cout << "Position " << position << ": " << number << std::endl;
        position++;
    }
    
    return 0;
}

Output:

Updated numbers:
Position 0: 10
Position 1: 999
Position 2: 30
Position 3: 888
Position 4: 50

File size calculation

To work with random access effectively, you often need to know the file size:

#include <fstream>
#include <iostream>

size_t getFileSize(const std::string& filename)
{
    std::ifstream file(filename, std::ios::binary);
    if (!file)
    {
        return 0;
    }
    
    // Seek to end and get position
    file.seekg(0, std::ios::end);
    size_t size = file.tellg();
    
    return size;
}

int main()
{
    const std::string filename = "employees.dat";
    
    size_t fileSize = getFileSize(filename);
    size_t recordSize = sizeof(Employee);
    size_t recordCount = fileSize / recordSize;
    
    std::cout << "File size: " << fileSize << " bytes" << std::endl;
    std::cout << "Record size: " << recordSize << " bytes" << std::endl;
    std::cout << "Number of records: " << recordCount << std::endl;
    
    return 0;
}

Practical example: Simple database

#include <fstream>
#include <iostream>
#include <string>
#include <vector>

struct Student
{
    int id;
    char name[30];
    double gpa;
    
    Student() : id(0), gpa(0.0)
    {
        name[0] = '\0';
    }
    
    Student(int studentId, const std::string& studentName, double studentGpa)
        : id(studentId), gpa(studentGpa)
    {
        size_t len = std::min(studentName.length(), sizeof(name) - 1);
        studentName.copy(name, len);
        name[len] = '\0';
    }
    
    void display() const
    {
        std::cout << "ID: " << id << ", Name: " << name << ", GPA: " << gpa << std::endl;
    }
};

class StudentDatabase
{
private:
    std::string filename;
    
public:
    StudentDatabase(const std::string& dbFile) : filename(dbFile) {}
    
    void addStudent(const Student& student)
    {
        std::ofstream file(filename, std::ios::binary | std::ios::app);
        if (file)
        {
            file.write(reinterpret_cast<const char*>(&student), sizeof(Student));
            std::cout << "Student added successfully!" << std::endl;
        }
    }
    
    bool getStudent(size_t index, Student& student)
    {
        std::ifstream file(filename, std::ios::binary);
        if (!file)
        {
            return false;
        }
        
        file.seekg(index * sizeof(Student), std::ios::beg);
        return file.read(reinterpret_cast<char*>(&student), sizeof(Student)).good();
    }
    
    bool updateStudent(size_t index, const Student& student)
    {
        std::fstream file(filename, std::ios::binary | std::ios::in | std::ios::out);
        if (!file)
        {
            return false;
        }
        
        file.seekp(index * sizeof(Student), std::ios::beg);
        file.write(reinterpret_cast<const char*>(&student), sizeof(Student));
        return file.good();
    }
    
    void displayAll()
    {
        std::ifstream file(filename, std::ios::binary);
        if (!file)
        {
            std::cout << "Error opening database file!" << std::endl;
            return;
        }
        
        Student student;
        size_t index = 0;
        
        std::cout << "All students in database:" << std::endl;
        while (file.read(reinterpret_cast<char*>(&student), sizeof(Student)))
        {
            std::cout << "Index " << index << " - ";
            student.display();
            index++;
        }
    }
    
    size_t getRecordCount()
    {
        std::ifstream file(filename, std::ios::binary);
        if (!file) return 0;
        
        file.seekg(0, std::ios::end);
        return file.tellg() / sizeof(Student);
    }
};

int main()
{
    StudentDatabase db("students.db");
    
    // Add some students
    db.addStudent(Student(1001, "Alice Johnson", 3.8));
    db.addStudent(Student(1002, "Bob Smith", 3.2));
    db.addStudent(Student(1003, "Carol Brown", 3.9));
    
    // Display all students
    db.displayAll();
    
    std::cout << "\nTotal records: " << db.getRecordCount() << std::endl;
    
    // Get specific student
    Student student;
    if (db.getStudent(1, student))  // Get student at index 1
    {
        std::cout << "\nStudent at index 1: ";
        student.display();
    }
    
    // Update a student
    Student updatedStudent(1002, "Robert Smith", 3.5);
    if (db.updateStudent(1, updatedStudent))
    {
        std::cout << "\nStudent updated successfully!" << std::endl;
        
        // Display updated student
        if (db.getStudent(1, student))
        {
            std::cout << "Updated student: ";
            student.display();
        }
    }
    
    return 0;
}

Output:

Student added successfully!
Student added successfully!
Student added successfully!
All students in database:
Index 0 - ID: 1001, Name: Alice Johnson, GPA: 3.8
Index 1 - ID: 1002, Name: Bob Smith, GPA: 3.2
Index 2 - ID: 1003, Name: Carol Brown, GPA: 3.9

Total records: 3

Student at index 1: ID: 1002, Name: Bob Smith, GPA: 3.2

Student updated successfully!
Updated student: ID: 1002, Name: Robert Smith, GPA: 3.5

When to use random access

Use random access when:

  • Working with fixed-size records
  • Need to access specific records quickly
  • Building database-like applications
  • Implementing file-based data structures
  • Need to update specific parts of large files

Use sequential access when:

  • Processing entire file contents
  • File format is variable-length
  • Memory constraints are important
  • Simple text file operations

Best practices for random file I/O

1. Always use binary mode for fixed-size data

std::fstream file("data.dat", std::ios::binary | std::ios::in | std::ios::out);

2. Check file operations for success

if (!file.seekg(position))
{
    std::cerr << "Seek operation failed!" << std::endl;
}

3. Know your record sizes

const size_t RECORD_SIZE = sizeof(MyStruct);

4. Handle end-of-file conditions

if (file.eof())
{
    std::cout << "Reached end of file" << std::endl;
}

Summary

Random file I/O allows direct access to any position in a file using seekg(), seekp(), tellg(), and tellp() functions. It's most effective with binary files containing fixed-size records. Random access enables efficient database-like operations, selective reading/writing, and file updates without processing entire files sequentially.

Quiz

  1. What's the difference between seekg() and seekp()?
  2. What are the three seek origins and when would you use each?
  3. Why is random access more suitable for binary files than text files?
  4. How do you determine the current position in a file?
  5. What's the advantage of random access over sequential access?

Practice exercises

  1. Create a program that maintains a binary file of book records and allows users to search, add, and update books by their position in the file.

  2. Write a simple file-based phone book that stores contact information and allows random access to any contact.

  3. Implement a program that reads a large text file and creates an index showing the byte position of each line, then allows jumping to any line number.

  4. Create a binary file editor that displays file contents in hexadecimal format and allows editing individual bytes at specific positions.

Continue Learning

Explore other available lessons while this one is being prepared.

View Course

Explore More Courses

Discover other available courses while this lesson is being prepared.

Browse Courses

Lesson Discussion

Share your thoughts and questions

💬

No comments yet. Be the first to share your thoughts!

Sign in to join the discussion