Coming Soon
This lesson is currently being developed
Random file I/O
Access file data at arbitrary positions.
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
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 positionstd::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
- What's the difference between
seekg()
andseekp()
? - What are the three seek origins and when would you use each?
- Why is random access more suitable for binary files than text files?
- How do you determine the current position in a file?
- What's the advantage of random access over sequential access?
Practice exercises
-
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.
-
Write a simple file-based phone book that stores contact information and allows random access to any contact.
-
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.
-
Create a binary file editor that displays file contents in hexadecimal format and allows editing individual bytes at specific positions.
Explore More Courses
Discover other available courses while this lesson is being prepared.
Browse CoursesLesson Discussion
Share your thoughts and questions