Files and how they get stored by Computers

Revision Session: From Stream Objects to File Handling in C++

Dear Readers,

Welcome to our series where we review and revise the concepts we've learnt in our Computer Programming classes. The best way to understand a concept is to discuss it. We have here, our diligent students - Sam and Alex, who never shy away from discussing and clarifying their doubts. And we have our AI assistant, Compy, who's always ready to help them. The purpose of this discussion is to strengthen the understanding of the students, and also for you, the reader, to take part in their learning journey.

Today, we are diving into a conversation that took place on July 28, 2023, a vibrant discussion that transpired right after their lab session. The main focus was the stream objects - cin, cout, and cerr - and file handling, which is an integral part of any programming language. They also touched upon the finer aspects of file storage, data accessibility and file systems. So, without further ado, let's delve into their conversation.


Alex: Hey Compy, Sam and I were just discussing the lab session. We had discussed about the different ways files are accessed by the computer. We were wondering if there are other modes for file handling apart from the read, write, and append modes that we've learnt today.

Compy: Indeed, Alex. C++ provides several modes for file handling. Besides read ('r'), write ('w'), and append ('a'), we have:

  • 'r+' for both reading and writing, starting at the beginning of the file.
  • 'w+' for both reading and writing. This first truncates the file to zero length if it exists, or creates a file if it does not exist.
  • 'a+' opens or creates a file for update (both for input and output) with all output operations writing data at the end of the file.
  • 'b' can be appended to any of the above modes to open the file in binary mode rather than the default text mode.

Sam: That's pretty cool. Compy, can you explain a bit about sequential and random access? We've been hearing these terms quite a bit.

Compy: Of course, Sam. The terms sequential and random access refer to the ways in which data can be accessed in a file. Let's look at each of them in detail:

  • Sequential Access: In sequential access, you access data in a sequence, that is, one after the other. You start at the beginning and continue until you find the item you're looking for, or until you've read all the items. It's like reading a book from start to finish. In file handling, this mode of access is typically used when you need to process or read all data in a file.
  • Random Access: In contrast, random access allows you to jump directly to the data you want to read or write, without having to go through all the data in the sequence. It's like jumping to a particular page in a book. This mode is useful when you need to update certain parts of a file, or when you don't need to process all data in a file.

Alex: That's insightful. But how does it relate to the way computers store data on hard drives, SSDs and flash storage devices?

Compy: Good question, Alex. You see, how data is stored and accessed can significantly affect the performance of these storage devices.

Traditional hard drives store data magnetically on spinning platters, and they read/write data sequentially using a moving read/write head. This means accessing data that's scattered around the disk (random access) can be slow because the read/write head needs to physically move to each location.

In contrast, SSDs and flash storage devices don't have moving parts. They store data in grids of cells, and they can access any cell directly regardless of its location. This makes them much faster at random access. However, these devices have their own limitations, such as a finite number of write cycles.

Sam: Interesting. We learned about file handling using fstream in C++, how difficult would it be to handle files without using it?

Compy: Working with files without using fstream or similar libraries would indeed be a more complex task. Without these libraries, you would have to rely on lower-level system calls and handle all the details of file I/O operations manually. This includes buffering, encoding, error handling, and more.

For instance, in C programming, you might use functions such as fopen, fclose, fread, and fwrite for file operations. But these are lower-level compared to fstream functions in C++, and they don't provide many of the conveniences that fstream does. So while it's possible to handle files without using fstream, it's typically much easier and more efficient to use the higher-level functionalities that fstream provides.

Alex: Got it. Can you tell us a bit about different file systems like FAT and NTFS?

Compy: Absolutely, Alex. The file system is responsible for managing how files are stored and retrieved on a disk. There are several different file systems, and the one you use can impact performance and reliability. Let's talk about FAT and NTFS first:

  • FAT (File Allocation Table): This is an older file system that was used by MS-DOS and early versions of Windows. It's simple and robust, but it lacks many of the features of more modern file systems. For example, it doesn't support file permissions, and it has limitations on maximum file size and disk size.
  • NTFS (New Technology File System): This is the standard file system for modern Windows systems. It supports large file sizes, file permissions, and other advanced features. It also includes features for data recovery and has improved performance and reliability compared to FAT.

There are also many other file systems used by different operating systems and for different purposes. For instance, Linux typically uses ext4, while macOS uses APFS. There are also file systems designed specifically for network storage (like NFS), distributed systems (like HDFS for Hadoop), and flash storage (like F2FS).

Each file system has its own strengths and weaknesses, and the best choice depends on the specific use case.

Sam: So, different file systems for different purposes, eh? That makes sense. Compy, you mentioned flash storage - that's becoming increasingly common these days, right? With SSDs and all?

Compy: Yes, Sam, you're absolutely right. Flash storage, which includes SSDs (Solid State Drives), has gained significant popularity in recent years. They are significantly faster than traditional hard disk drives (HDDs) because they don't have any moving parts. While an HDD needs to physically move a magnetic head to read or write data, an SSD can access any part of the storage instantly.

However, flash storage comes with its own challenges. For instance, flash memory cells can wear out after a certain number of write cycles. But modern SSDs are designed to handle this issue efficiently, distributing writes across different cells in a process called wear leveling.

File systems used for flash storage are often specifically designed to work with the characteristics of SSDs. For instance, F2FS (Flash-Friendly File System) is a Linux file system that's designed to take into account the characteristics of flash memory.

Alex: Interesting! So the way data is stored and accessed can differ quite a bit depending on the hardware and the file system used.

Compy: Absolutely, Alex. And when you're working with files in your C++ programs, a lot of that complexity is handled for you by the fstream library and the operating system. Your program can simply read from and write to files using the fstream functions, and the OS takes care of the details of storing and retrieving the data on the actual physical medium, whether it's an HDD, SSD, or something else.

Sam: That's very handy! So, what about handling large files? I imagine that can get pretty complicated.

Compy: Indeed, Sam, dealing with large files can be a challenge. However, C++ provides mechanisms to manage that as well. For example, rather than reading an entire large file into memory at once (which might not even be possible if the file is larger than your available RAM), you can read and process the file a piece at a time.

Moreover, if you have a file with a structured format, like a database file, you might not need to read the entire file to find the information you're interested in. This is where random access can come into play. You can move the file pointer to different parts of the file, skipping over parts of the file you're not interested in.

Alex: Compy, you mentioned earlier about sequential and random access. Can you elaborate on that?

Compy: Certainly, Alex. Sequential access is when you start at the beginning of a file and read through to the end. It's like reading a book from start to finish. On the other hand, random access allows you to jump to any part of the file without reading the parts before it. It's like flipping directly to a specific page in a book.

Sequential access is generally faster and uses less memory than random access because it doesn't require you to maintain a map of where everything is. But random access is more flexible and can be more efficient if you only need a small part of a large file.

Sam: So, the choice between sequential and random access really depends on what you're trying to achieve with your file handling?

Compy: Precisely, Sam! If your program needs to process every piece of data in a file, then sequential access is usually the way to go. But if your program only needs to access certain pieces of data in a file, then random access could be more efficient.

Alex: Alright, got it. Now, can we discuss a little bit about the way data is stored on hard drives, SSDs, and flash storage devices?

Compy: Of course, Alex. Let's start with hard drives. Traditional hard drives, also known as Hard Disk Drives or HDDs, store data on one or more rigid, rapidly spinning disks coated with magnetic material. Data is read and written by heads that float above the disks on a cushion of air.

In contrast, Solid State Drives or SSDs don't have any moving parts. Instead, they store data on interconnected flash-memory chips. Because there are no moving parts, SSDs can read and write data much faster than HDDs. They are also more durable and consume less power.

Flash storage is a type of non-volatile storage that can be electrically erased and reprogrammed. It's used for general storage and transfer of data between devices. The most common type of flash memory is the flash drive, also known as a thumb drive or USB drive.

Sam: I've heard that SSDs have a limited number of write cycles. Is that true, Compy?

Compy: Yes, that's correct, Sam. SSDs have a finite number of program/erase (P/E) cycles because each cell in a flash memory bank can only be written and erased a certain number of times before it becomes unreliable. But don't worry too much about it - the average consumer would be hard-pressed to reach the limit in a normal lifespan of an SSD.

Alex: I see. Compy, could you also tell us about file systems? I've heard of FAT and NTFS but I'm not sure what they mean.

Compy: Sure, Alex. A file system is a way of organizing and storing data on a storage device like a hard drive or an SSD. It controls how data is stored and retrieved.

FAT, or File Allocation Table, is one of the oldest and simplest file systems. It was designed for small disks and simple folder structures. However, due to its limitations in terms of the maximum file size and the total volume size, it's not often used today for hard drives.

NTFS, or New Technology File System, is a file system that was introduced by Microsoft for its Windows NT operating system. It has several improvements over FAT, like support for larger files and volumes, better recovery, and security features such as permissions and encryption.

Compy: Speaking of interesting facts, did you know that the first hard drive was created by IBM in 1956 and it was the size of two refrigerators? Despite its size, it could only hold about 5 megabytes of data. We've come a long way since then. Now, you can hold terabytes of data in the palm of your hand with modern SSDs!

Here's another fun one - the 'save' icon in most applications is represented by a floppy disk, which is a storage device that's largely obsolete now. Many people using computers today have never actually used a floppy disk, but the icon lives on!

Alex: Wow, that's really interesting, Compy! It's crazy to think about how much technology has evolved over the years.

Sam: Yeah, and to think that we carry around more computing power in our pockets with our smartphones than the computers used for the Apollo moon missions!

Alex:Compy! Prof John spoke about the objects of the stream class. Can you please bring out the difference and similarities between stream and fstreams?

Compy: Absolutely! Both streams and file streams are used for dealing with data, but they do so in different ways.

In C++, stream is a general term for a sequence of bytes that you can either input to your program (an input stream) or output from your program (an output stream). This stream can be anything - data from the keyboard entered by the user, data sent to the screen, data from a file, or data sent to a file. The important point here is that a stream represents a sequence of bytes.

Now, let's talk about the objects of the stream class in C++. The most commonly used stream objects are 'cin', 'cout', 'cerr', and 'clog'. Here's what they do:

  • cin: Stands for 'character input'. It's used to read input from the keyboard.
  • cout: Stands for 'character output'. It's used to output data to the screen.
  • cerr: Stands for 'character error'. It's used for outputting error messages.
  • clog: Stands for 'character log'. It's also used for error output, but unlike 'cerr', 'clog' is buffered. This means that 'clog' will hold the error message and try to output it in one piece, which can be more efficient.

File streams, on the other hand, are a specific type of stream used for file input and output. These are part of the fstream library. The fstream library includes 'ifstream' for reading data from files, 'ofstream' for writing data to files, and 'fstream' which can do both.

So, in a nutshell, while both streams and file streams deal with data, the difference lies in where they are directing the data. Stream objects like 'cin' and 'cout' handle data coming in from the keyboard and going out to the screen, respectively, whereas file stream objects handle data coming in from files and going out to files.

Alex: Got it, Compy. So, we use 'cin' and 'cout' when we're dealing with data from the user, and we use 'ifstream', 'ofstream', and 'fstream' when we're dealing with data from files. Right?

Compy: Exactly, Alex! You've got the hang of it.

Compy: Sure, I can explain that. Buffered and unbuffered input/output refers to how data is handled by the system during input and output operations. When you type on your keyboard, the data isn't sent to your program one character at a time. Instead, it's stored in a buffer—a small area of memory—and sent to your program in larger chunks. This is what we call buffered input.

Buffered output works similarly. When you output data, it isn't sent directly to the screen or the file or whatever your output device is. Instead, it's stored in a buffer and sent in larger chunks. This can be more efficient, as accessing memory is generally faster than accessing an input or output device.

'Cerr' and 'clog' are good examples of this in action. Remember how I said 'cerr' is unbuffered, and 'clog' is buffered? That means when you output an error message with 'cerr', the message is sent to the screen right away. But when you output an error message with 'clog', the message is stored in a buffer and sent to the screen later, when the buffer is flushed.

In terms of comparing this with how a USB keyboard or a microcontroller like Arduino works, the concept is somewhat similar. When you type on a USB keyboard, the data isn't sent to the computer one keystroke at a time. Instead, the keyboard has a buffer where it stores the keystrokes and sends them to the computer in packets.

Arduino, on the other hand, operates a little differently. It can operate in both buffered and unbuffered modes, depending on how you've set it up. In buffered mode, Arduino stores input or output data in a buffer and processes it in chunks, just like 'cin' or 'cout'. In unbuffered mode, Arduino processes input or output data immediately, just like 'cerr'.

Alex: That's quite clear, Compy. Thanks! So the buffer helps in efficiency by storing data and then processing it, right?

Compy: Exactly, Alex! And one more thing to remember is that although buffered I/O is more efficient because it allows data to be processed in chunks, it does add a small delay because the data must be stored in the buffer before it can be processed.

Sam: That was a lot to process. I think I need a break now Compy.

Compy: Sure Sam. I as a computer, really don't need such breaks.

Compendium of Terms

  • Stream: A sequence of bytes that can either be input or output to/from a program.
  • File Stream: Specific type of stream used for file input and output.
  • Sequential Access: Reading or writing data in a sequential manner, from start to finish.
  • Random Access: Reading or writing data in a non-sequential manner, accessing any part directly.
  • HDD (Hard Disk Drive): Data storage device using magnetic storage.
  • SSD (Solid State Drive): Data storage device using interconnected flash-memory chips.
  • Flash Storage: Non-volatile storage that can be electrically erased and reprogrammed.
  • FAT (File Allocation Table): An older and simpler file system.
  • NTFS (New Technology File System): A modern file system with support for larger files, recovery, and security features.
  • Buffered I/O: Storing data in a buffer and processing it in chunks for efficiency.
  • Unbuffered I/O: Processing data immediately without storing it in a buffer.
  • cin: Character input from the keyboard.
  • cout: Character output to the screen.
  • cerr: Character error output.
  • clog: Character log, buffered error output.
  • ifstream: Reading data from files.
  • ofstream: Writing data to files.
  • fstream: Reading and writing data to/from files.
  • Program/Erase (P/E) Cycles: Finite number of write cycles in SSDs.
Related Projects