From 8fefb256fe342f67bd13a5f77d02783a938e3965 Mon Sep 17 00:00:00 2001 From: "Mark A. Gibbs" Date: Tue, 16 Feb 2016 01:11:14 -0500 Subject: [PATCH 01/12] Added read text file sample --- .../input-streams/read-text-file.cpp | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 1-common-tasks/input-streams/read-text-file.cpp diff --git a/1-common-tasks/input-streams/read-text-file.cpp b/1-common-tasks/input-streams/read-text-file.cpp new file mode 100644 index 0000000..69f8240 --- /dev/null +++ b/1-common-tasks/input-streams/read-text-file.cpp @@ -0,0 +1,42 @@ +// Read text file + +#include +#include +#include + +int main() +{ + std::ifstream file{"file.txt"}; + if (file) + { + std::ostringstream ss{}; + if (ss << file.rdbuf()) + { + std::string str = ss.str(); + } + } +} + +// Read an entire text file into a string. +// +// Sometimes we want to read data from a file +// [item-by-item](/common-tasks/read-line-of-values.html) or +// [line-by-line](/common-tasks/read-line-by-line.html). But sometimes +// we want to read an entire text file into a string variable, in one +// shot and without any formatting. +// +// On [9], we create an input stream for the file `file.txt`, and +// confirm the file was opened successfully on [10]. +// +// We create a [`std::ostringstream`](cpp/io/basic_ostringstream) on +// [12]; this is an output stream because we are reading *in* from the +// file stream, and writing what we read *out* to the string stream. We +// do this on [13], using file stream's +// [`rdbuf()`](cpp/io/basic_ifstream/rdbuf) function to get a pointer to +// its internal buffer. (We could also have used a +// [`std::filebuf`](cpp/io/basic_filebuf) object directly, without +// a `std::ifstream`.) +// +// If we were able to successfully read the file's contents, we can +// recover them as a string variable using `std::ostringstream`'s +// [`str()`](cpp/io/basic_ostringstream/str) function, as on [15]. From 7ee1e65c15bfe10257d2451bc10db5aa24319ae1 Mon Sep 17 00:00:00 2001 From: "Mark A. Gibbs" Date: Tue, 16 Feb 2016 02:13:10 -0500 Subject: [PATCH 02/12] Added read binary file sample --- .../input-streams/read-binary-file.cpp | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 1-common-tasks/input-streams/read-binary-file.cpp diff --git a/1-common-tasks/input-streams/read-binary-file.cpp b/1-common-tasks/input-streams/read-binary-file.cpp new file mode 100644 index 0000000..f9c8599 --- /dev/null +++ b/1-common-tasks/input-streams/read-binary-file.cpp @@ -0,0 +1,70 @@ +// Read binary file + +#include +#include +#include + +int main() +{ + std::ifstream file{"file.dat", std::ios_base::in | std::ios_base::binary}; + if (file && + file.ignore(std::numeric_limits::max())) + { + std::streamsize size = file.gcount(); + file.seekg(0); + + std::vector buffer{}; + buffer.resize(size); + + file.read(reinterpret_cast(buffer.data()), buffer.size()); + } +} + +// Read an entire binary file into a byte array. +// +// We have two basic options for reading the contents of a file into a +// variable in memory: we can try to read bit-by-bit growing our +// memory buffer as we read, or we can try to determine the size then +// allocate the entire memory buffer at once and do one giant read +// directly into it. Which technique we use depends on numerous factors, +// such as the size of the data, how fast/slow the device we are reading +// from is, and whether or not we can make multiple read passes (we +// could not read input from a network stream more than once, for +// example). This sample illustrates the second option. +// +// First we open an input file stream on [9]. Note that we open the +// stream in binary mode. +// +// On [10] we check to confirm the file opened successfully, then in a +// compound test (using the `&&` AND operator) we use the input stream's +// [`ignore()`](cpp/io/basic_istream/ignore) function on [11] and check +// the result to ensure there was no read error. +// +// If the previous operations were successful, we are now at the end of +// the input file, and we can use the +// [`gcount()`](cpp/io/basic_istream/gcount) function ([13]) to +// determine the number of bytes we ignored to get there - which is the +// size of the file's data. The reason we have to do it this way is +// because simply using seek functions to seek to the end of the file is +// unreliable. The end of the file is not always the same as the end of +// the file's data - some file systems put padding at the end of files +// for technical and performance reasons. +// +// After getting the number of bytes in the file ([13]), we seek back +// to the beginning of the file on [14], and begin preparing the memory +// buffer we will be reading into. +// +// We create a vector of bytes ([16-17]), and set its size to the number +// of bytes we determined on [13]. Then we use the stream's +// [`read()`](cpp/io/basic_istream/read) function to do a single giant +// read of all the file's data into the buffer ([19]). Because `read()` +// expects a character buffer while we wish to write to a byte buffer, +// we use a `reinterpret_cast` (which we know is safe, because `char` +// and `unsigned char` are always the same size). +// +// Note that we could use this technique to read text data into a +// string, because like `std::vector` `std::string` is a contiguous +// container type (other containers like `std::list` and `std::deque` +// are not contiguous, so we could not use those). However, [there is +// a better way to read the contents of a file into a +// string](/common-tasks/read-text-file.html). From 3d843fc09569c67685f24e80c83b73a1495a50a0 Mon Sep 17 00:00:00 2001 From: "Mark A. Gibbs" Date: Tue, 16 Feb 2016 17:49:00 -0500 Subject: [PATCH 03/12] Added read binary file without seek sample --- .../read-binary-file-without-seek.cpp | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 1-common-tasks/input-streams/read-binary-file-without-seek.cpp diff --git a/1-common-tasks/input-streams/read-binary-file-without-seek.cpp b/1-common-tasks/input-streams/read-binary-file-without-seek.cpp new file mode 100644 index 0000000..545a258 --- /dev/null +++ b/1-common-tasks/input-streams/read-binary-file-without-seek.cpp @@ -0,0 +1,89 @@ +// Read binary file + +#include +#include +#include +#include + +int main() +{ + using std::begin; + using std::end; + + std::ifstream file{"file.dat", std::ios_base::in | std::ios_base::binary}; + if (file) + { + std::deque buffer{}; + + std::array chunk{}; + + while (file.read(reinterpret_cast(chunk.data()), chunk.size()) || + file.gcount()) + { + buffer.insert(end(buffer), begin(chunk), begin(chunk) + file.gcount()); + } + } +} + +// Read an entire binary file into a byte array without seeking. +// +// We have two basic options for reading the contents of a file into a +// variable in memory: we can try to read bit-by-bit growing our +// memory buffer as we read, or we can try to determine the size then +// allocate the entire memory buffer at once and do one giant read +// directly into it. Which technique we use depends on numerous factors, +// such as the size of the data, how fast/slow the device we are reading +// from is, and whether or not we can make multiple read passes (we +// could not read input from a network stream more than once, for +// example). This sample illustrates the first option. +// +// Before we begin, we use a pair of `using` directives on [10-11] to +// bring `std::begin` and `std::end` into scope. We will be using those +// shortly. +// +// First we open an input file stream and confirm it opened successfully +// on [13-14]. Note that we open the stream in binary mode. +// +// On [16] we prepare the buffer we will be reading the data into. We +// could use any container type (or even custom class types), but +// [`std::deque`](cpp/container/deque) is uniquely suited for this task. +// The reason is that `std::deque` is *almost* a `std::vector` - it is +// just about as efficient as a vector, both in terms of memory usage +// and speed, and it allows random access to the contents - except +// instead of holding its contents in a single contiguous block of +// memory, `std::deque` uses multiple blocks. When a 100 kiB +// `std::vector` has to grow, it has to allocate an entirely new block +// of memory *larger* than 100 kiB - usually double, so 200 kiB, which +// means that a total of 300 kiB is being used - then copy all 100 kiB +// of data from the old block to the new one, then delete the old block. +// Meanwhile, when a 100 kiB `std::deque` has to grow, it merely adds a +// new block and continues - this requires far less memory, and nothing +// has to copied. +// +// In addition to our buffer, we prepare an array on [18] to act as a +// temporary buffer to hold each chunk as we read the file's data in +// chunk by chunk. We need to choose a reasonable size for the chunk +// buffer - too small and we're doing too many reads, too large and +// we're wasting memory. [`BUFSIZ`](cpp/io/c) is a reasonable default. +// +// Now that our buffers are read, we begin the read loop ([20-25]). The +// read loop is a `while` loop with a compound test that can be read +// like this: "While we successfully read a whole chunk's worth of data +// ([20]) *OR* we *didn't* read a whole chunk but we did read *some* +// data ([21]) - that is, we read *part* of a chunk - insert everything +// we read at the end of the buffer ([23])". (Because +// [`read()`](cpp/io/basic_istream/read) expects to write to a +// character (`char`) buffer, we must use a `reinterpret_cast` to write +// to a byte (`unsigned char`) buffer.) The loop will continue reading +// chunks (or parts of chunks) until either there is an error or the end +// of the file is reached - you can use the stream's +// [`eof()`](cpp/io/basic_ios/eof) or [`fail()`](cpp/io/basic_ios/fail) +// function to determine which happened. +// +// If the loop completed successfully, `buffer` will contain the entire +// contents of the binary file. +// +// We could use this technique to read text data into a string, too +// (by not opening the file in binary mode). However, [there is +// a better way to read the contents of a file into a +// string](/common-tasks/read-text-file.html). From a924f8ad6e9ff61bac0519b873d81b0013ca3e8f Mon Sep 17 00:00:00 2001 From: "Mark A. Gibbs" Date: Wed, 17 Feb 2016 22:06:11 -0500 Subject: [PATCH 04/12] Changed indenting from spaces to tabs --- .../read-binary-file-without-seek.cpp | 32 +++++++++---------- .../input-streams/read-binary-file.cpp | 24 +++++++------- .../input-streams/read-text-file.cpp | 18 +++++------ 3 files changed, 37 insertions(+), 37 deletions(-) diff --git a/1-common-tasks/input-streams/read-binary-file-without-seek.cpp b/1-common-tasks/input-streams/read-binary-file-without-seek.cpp index 545a258..2324394 100644 --- a/1-common-tasks/input-streams/read-binary-file-without-seek.cpp +++ b/1-common-tasks/input-streams/read-binary-file-without-seek.cpp @@ -7,22 +7,22 @@ int main() { - using std::begin; - using std::end; - - std::ifstream file{"file.dat", std::ios_base::in | std::ios_base::binary}; - if (file) - { - std::deque buffer{}; - - std::array chunk{}; - - while (file.read(reinterpret_cast(chunk.data()), chunk.size()) || - file.gcount()) - { - buffer.insert(end(buffer), begin(chunk), begin(chunk) + file.gcount()); - } - } + using std::begin; + using std::end; + + std::ifstream file{"file.dat", std::ios_base::in | std::ios_base::binary}; + if (file) + { + std::deque buffer{}; + + std::array chunk{}; + + while (file.read(reinterpret_cast(chunk.data()), chunk.size()) || + file.gcount()) + { + buffer.insert(end(buffer), begin(chunk), begin(chunk) + file.gcount()); + } + } } // Read an entire binary file into a byte array without seeking. diff --git a/1-common-tasks/input-streams/read-binary-file.cpp b/1-common-tasks/input-streams/read-binary-file.cpp index f9c8599..46be49d 100644 --- a/1-common-tasks/input-streams/read-binary-file.cpp +++ b/1-common-tasks/input-streams/read-binary-file.cpp @@ -6,18 +6,18 @@ int main() { - std::ifstream file{"file.dat", std::ios_base::in | std::ios_base::binary}; - if (file && - file.ignore(std::numeric_limits::max())) - { - std::streamsize size = file.gcount(); - file.seekg(0); - - std::vector buffer{}; - buffer.resize(size); - - file.read(reinterpret_cast(buffer.data()), buffer.size()); - } + std::ifstream file{"file.dat", std::ios_base::in | std::ios_base::binary}; + if (file && + file.ignore(std::numeric_limits::max())) + { + std::streamsize size = file.gcount(); + file.seekg(0); + + std::vector buffer{}; + buffer.resize(size); + + file.read(reinterpret_cast(buffer.data()), buffer.size()); + } } // Read an entire binary file into a byte array. diff --git a/1-common-tasks/input-streams/read-text-file.cpp b/1-common-tasks/input-streams/read-text-file.cpp index 69f8240..b9f26a5 100644 --- a/1-common-tasks/input-streams/read-text-file.cpp +++ b/1-common-tasks/input-streams/read-text-file.cpp @@ -6,15 +6,15 @@ int main() { - std::ifstream file{"file.txt"}; - if (file) - { - std::ostringstream ss{}; - if (ss << file.rdbuf()) - { - std::string str = ss.str(); - } - } + std::ifstream file{"file.txt"}; + if (file) + { + std::ostringstream ss{}; + if (ss << file.rdbuf()) + { + std::string str = ss.str(); + } + } } // Read an entire text file into a string. From 47a5876ee143d3950e80b2ec2bd8260d9a651028 Mon Sep 17 00:00:00 2001 From: "Mark A. Gibbs" Date: Thu, 18 Feb 2016 17:28:26 -0500 Subject: [PATCH 05/12] Refactored sample code as separate function --- .../input-streams/read-text-file.cpp | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/1-common-tasks/input-streams/read-text-file.cpp b/1-common-tasks/input-streams/read-text-file.cpp index b9f26a5..3db787c 100644 --- a/1-common-tasks/input-streams/read-text-file.cpp +++ b/1-common-tasks/input-streams/read-text-file.cpp @@ -4,17 +4,14 @@ #include #include -int main() +std::string read_text_file(std::string const& filename) { - std::ifstream file{"file.txt"}; - if (file) - { - std::ostringstream ss{}; - if (ss << file.rdbuf()) - { - std::string str = ss.str(); - } - } + std::ifstream file{filename}; + + std::ostringstream ss{}; + ss << file.rdbuf(); + + return ss.str(); } // Read an entire text file into a string. @@ -23,20 +20,25 @@ int main() // [item-by-item](/common-tasks/read-line-of-values.html) or // [line-by-line](/common-tasks/read-line-by-line.html). But sometimes // we want to read an entire text file into a string variable, in one -// shot and without any formatting. +// shot, without any parsing or formatting. // -// On [9], we create an input stream for the file `file.txt`, and -// confirm the file was opened successfully on [10]. +// On [9], we create an input stream for the file that we will be +// reading. // // We create a [`std::ostringstream`](cpp/io/basic_ostringstream) on -// [12]; this is an output stream because we are reading *in* from the +// [11]; this is an output stream because we are reading *in* from the // file stream, and writing what we read *out* to the string stream. We -// do this on [13], using file stream's +// do this on [12], using file stream's // [`rdbuf()`](cpp/io/basic_ifstream/rdbuf) function to get a pointer to // its internal buffer. (We could also have used a // [`std::filebuf`](cpp/io/basic_filebuf) object directly, without // a `std::ifstream`.) // // If we were able to successfully read the file's contents, we can -// recover them as a string variable using `std::ostringstream`'s -// [`str()`](cpp/io/basic_ostringstream/str) function, as on [15]. +// recover them as a string using `std::ostringstream`'s +// [`str()`](cpp/io/basic_ostringstream/str) function, as on [14]. + +int main() +{ + std::string str = read_text_file("file.txt"); +} From 84bc704b20fdff601885b08ee5b5991c17e7b617 Mon Sep 17 00:00:00 2001 From: "Mark A. Gibbs" Date: Fri, 19 Feb 2016 21:09:39 -0500 Subject: [PATCH 06/12] Combined both binary read methods into single sample --- .../input-streams/read-binary-file.cpp | 173 +++++++++++++----- 1 file changed, 123 insertions(+), 50 deletions(-) diff --git a/1-common-tasks/input-streams/read-binary-file.cpp b/1-common-tasks/input-streams/read-binary-file.cpp index 46be49d..57edc8f 100644 --- a/1-common-tasks/input-streams/read-binary-file.cpp +++ b/1-common-tasks/input-streams/read-binary-file.cpp @@ -1,70 +1,143 @@ // Read binary file +#include +#include // only for BUFSIZ +#include #include #include #include -int main() +std::vector read_binary_file1(std::string const& filename) { - std::ifstream file{"file.dat", std::ios_base::in | std::ios_base::binary}; - if (file && - file.ignore(std::numeric_limits::max())) - { - std::streamsize size = file.gcount(); - file.seekg(0); - - std::vector buffer{}; - buffer.resize(size); + std::ifstream file{filename, std::ios_base::in | std::ios_base::binary}; + + file.ignore(std::numeric_limits::max()); + + std::vector buffer{}; + buffer.resize(file.gcount()); + + file.seekg(0); + file.read(reinterpret_cast(buffer.data()), buffer.size()); + + return buffer; +} - file.read(reinterpret_cast(buffer.data()), buffer.size()); +std::deque read_binary_file2(std::string const& filename) +{ + using std::begin; + using std::end; + + std::ifstream file{filename, std::ios_base::in | std::ios_base::binary}; + + std::deque buffer{}; + + std::array chunk{}; + while (file.read(reinterpret_cast(chunk.data()), chunk.size()) || + file.gcount()) + { + buffer.insert(end(buffer), begin(chunk), begin(chunk) + file.gcount()); } + + return buffer; } // Read an entire binary file into a byte array. // // We have two basic options for reading the contents of a file into a -// variable in memory: we can try to read bit-by-bit growing our -// memory buffer as we read, or we can try to determine the size then -// allocate the entire memory buffer at once and do one giant read -// directly into it. Which technique we use depends on numerous factors, -// such as the size of the data, how fast/slow the device we are reading -// from is, and whether or not we can make multiple read passes (we -// could not read input from a network stream more than once, for -// example). This sample illustrates the second option. +// variable in memory: We can try to determine the size then allocate +// the entire memory buffer at once and do one giant read directly into +// it (as in `read_binary_file1()` on [10-23]), or we can try to read +// piece-by-piece growing our memory buffer as we read (as in +// `read_binary_file2()` on [25-41]). Which technique we use depends on +// numerous factors, such as the size of the data, how fast/slow the +// device we are reading from is, and whether or not we can make +// multiple read passes (we could not read input from a network stream +// more than once, for example). +// +// In method 1, first we open an input file stream on [12]. Note that we +// open the stream in binary mode. +// +// Once the file is open, the first thing we do is skip right to the end +// using the stream's [`ignore()`](cpp/io/basic_istream/ignore) +// function on [14]. It is important that we use the `ignore()` method +// rather than the `seekg(0, std::ios_base::end)` method, because the +// latter does not work reliably on all platforms. // -// First we open an input file stream on [9]. Note that we open the -// stream in binary mode. +// Next we create the buffer, and resize it to fit the entire file on +// [16-17]. Note that the buffer must be a contiguous container type, +// like `std::vector` or `std::array`, but not `std::list` or +// `std::deque` (because later we will be using `read()`, which writes +// to a contiguous memory block). The stream's +// [`gcount()`](cpp/io/basic_istream/gcount) function tells us the +// number of bytes read in the last input operation, and `ignore()` is +// an input operation that we just used to "read" the entire file. This +// ensures the buffer is now large enough to hold the entire file. // -// On [10] we check to confirm the file opened successfully, then in a -// compound test (using the `&&` AND operator) we use the input stream's -// [`ignore()`](cpp/io/basic_istream/ignore) function on [11] and check -// the result to ensure there was no read error. +// Then we seek back to the beginning of the file ([19]), and do the +// giant read of the whole file into the buffer on [20]. Note that we +// must use a `reinterpret_cast`, because `read()` expects a character +// (`char`) buffer (IOStreams are text-based by default), while we are +// reading bytes (`unsigned char`s). This cast is safe, because `char` +// and `unsigned char` are defined to be the same type, except +// (possibly) for signedness, which doesn't matter in this context. // -// If the previous operations were successful, we are now at the end of -// the input file, and we can use the -// [`gcount()`](cpp/io/basic_istream/gcount) function ([13]) to -// determine the number of bytes we ignored to get there - which is the -// size of the file's data. The reason we have to do it this way is -// because simply using seek functions to seek to the end of the file is -// unreliable. The end of the file is not always the same as the end of -// the file's data - some file systems put padding at the end of files -// for technical and performance reasons. +// If all of this was successful, `buffer` now contains the entire +// contents of the file. // -// After getting the number of bytes in the file ([13]), we seek back -// to the beginning of the file on [14], and begin preparing the memory -// buffer we will be reading into. +// In method 2, we again begin by opening a file input stream ([30]), +// but before that we use `using` declarations on [27-28] to bring +// `std::begin()` and `std::end()` into scope. We will be using them +// shortly. // -// We create a vector of bytes ([16-17]), and set its size to the number -// of bytes we determined on [13]. Then we use the stream's -// [`read()`](cpp/io/basic_istream/read) function to do a single giant -// read of all the file's data into the buffer ([19]). Because `read()` -// expects a character buffer while we wish to write to a byte buffer, -// we use a `reinterpret_cast` (which we know is safe, because `char` -// and `unsigned char` are always the same size). +// After we open the file, we prepare the buffer we will be writing +// the file's data into on [32]. Unlike with the previous method (which +// required a contiguous container type), we can use any container type +// (even custom user-defined containers). However, +// [`std::deque`](cpp/container/deque) is uniquely suited for this task. +// The reason is that `std::deque` is *almost* a `std::vector` - it is +// just about as efficient as a vector, both in terms of memory usage +// and speed, and it allows random access to the contents. The primary +// difference is that instead of holding its contents in a single +// contiguous block of memory, `std::deque` uses multiple blocks. When a +// 100 kiB `std::vector` has to grow, it has to allocate an entirely new +// block of memory *larger* than 100 kiB (usually double, so 200 kiB, +// which means that a total of 300 kiB is being used), then copy all +// 100 kiB of data from the old block to the new one, then delete the +// old block. Meanwhile, when a 100 kiB `std::deque` has to grow, it +// merely adds a new block and continues - this requires far less +// memory, and nothing has to copied. Thus, `std::deque` is a better +// choice for a container we know will be growing piece-by-piece. // -// Note that we could use this technique to read text data into a -// string, because like `std::vector` `std::string` is a contiguous -// container type (other containers like `std::list` and `std::deque` -// are not contiguous, so we could not use those). However, [there is -// a better way to read the contents of a file into a -// string](/common-tasks/read-text-file.html). +// In addition to our buffer, we prepare an array on [34] to act as a +// temporary buffer to hold each chunk as we read the file's data in +// chunk by chunk. We need to choose a reasonable size for the chunk +// buffer - too small and we're doing too many reads, too large and +// we're wasting memory. [`BUFSIZ`](cpp/io/c) is a reasonable default. +// +// Now that our buffers are ready, we begin the read loop ([35-39]). The +// read loop is a `while` loop with a compound test that can be read +// like this: "While we successfully read a whole chunk's worth of data +// ([35]) *OR* we *didn't* read a whole chunk but we did read *some* +// data ([36]) - that is, we read *part* of a chunk - insert everything +// we read at the end of the buffer ([38])". Once again, because +// [`read()`](cpp/io/basic_istream/read) expects to write to a +// character (`char`) buffer, we must use a `reinterpret_cast` to write +// to a byte (`unsigned char`) buffer. The loop will continue reading +// chunks (or parts of chunks) until either there is an error or the end +// of the file is reached - you can use the stream's +// [`eof()`](cpp/io/basic_ios/eof) or [`fail()`](cpp/io/basic_ios/fail) +// function to determine which happened. +// +// If the loop completed successfully, `buffer` will contain the entire +// contents of the binary file. +// +// Note that we could use either of these methods to read text data into +// a string (like `std::vector`, `std::string` is a contiguous +// container type). However, [there is a better way to read the contents +// of a file into a string](/common-tasks/read-text-file.html). + +int main() +{ + std::vector data1 = read_binary_file1("file.dat"); + std::deque data2 = read_binary_file2("file.dat"); +} From 83f19c3d8a3ec8285ac784993c3ff706b737c7c7 Mon Sep 17 00:00:00 2001 From: "Mark A. Gibbs" Date: Sat, 20 Feb 2016 14:09:55 -0500 Subject: [PATCH 07/12] Removed redundant binary stream sample --- .../read-binary-file-without-seek.cpp | 89 ------------------- 1 file changed, 89 deletions(-) delete mode 100644 1-common-tasks/input-streams/read-binary-file-without-seek.cpp diff --git a/1-common-tasks/input-streams/read-binary-file-without-seek.cpp b/1-common-tasks/input-streams/read-binary-file-without-seek.cpp deleted file mode 100644 index 2324394..0000000 --- a/1-common-tasks/input-streams/read-binary-file-without-seek.cpp +++ /dev/null @@ -1,89 +0,0 @@ -// Read binary file - -#include -#include -#include -#include - -int main() -{ - using std::begin; - using std::end; - - std::ifstream file{"file.dat", std::ios_base::in | std::ios_base::binary}; - if (file) - { - std::deque buffer{}; - - std::array chunk{}; - - while (file.read(reinterpret_cast(chunk.data()), chunk.size()) || - file.gcount()) - { - buffer.insert(end(buffer), begin(chunk), begin(chunk) + file.gcount()); - } - } -} - -// Read an entire binary file into a byte array without seeking. -// -// We have two basic options for reading the contents of a file into a -// variable in memory: we can try to read bit-by-bit growing our -// memory buffer as we read, or we can try to determine the size then -// allocate the entire memory buffer at once and do one giant read -// directly into it. Which technique we use depends on numerous factors, -// such as the size of the data, how fast/slow the device we are reading -// from is, and whether or not we can make multiple read passes (we -// could not read input from a network stream more than once, for -// example). This sample illustrates the first option. -// -// Before we begin, we use a pair of `using` directives on [10-11] to -// bring `std::begin` and `std::end` into scope. We will be using those -// shortly. -// -// First we open an input file stream and confirm it opened successfully -// on [13-14]. Note that we open the stream in binary mode. -// -// On [16] we prepare the buffer we will be reading the data into. We -// could use any container type (or even custom class types), but -// [`std::deque`](cpp/container/deque) is uniquely suited for this task. -// The reason is that `std::deque` is *almost* a `std::vector` - it is -// just about as efficient as a vector, both in terms of memory usage -// and speed, and it allows random access to the contents - except -// instead of holding its contents in a single contiguous block of -// memory, `std::deque` uses multiple blocks. When a 100 kiB -// `std::vector` has to grow, it has to allocate an entirely new block -// of memory *larger* than 100 kiB - usually double, so 200 kiB, which -// means that a total of 300 kiB is being used - then copy all 100 kiB -// of data from the old block to the new one, then delete the old block. -// Meanwhile, when a 100 kiB `std::deque` has to grow, it merely adds a -// new block and continues - this requires far less memory, and nothing -// has to copied. -// -// In addition to our buffer, we prepare an array on [18] to act as a -// temporary buffer to hold each chunk as we read the file's data in -// chunk by chunk. We need to choose a reasonable size for the chunk -// buffer - too small and we're doing too many reads, too large and -// we're wasting memory. [`BUFSIZ`](cpp/io/c) is a reasonable default. -// -// Now that our buffers are read, we begin the read loop ([20-25]). The -// read loop is a `while` loop with a compound test that can be read -// like this: "While we successfully read a whole chunk's worth of data -// ([20]) *OR* we *didn't* read a whole chunk but we did read *some* -// data ([21]) - that is, we read *part* of a chunk - insert everything -// we read at the end of the buffer ([23])". (Because -// [`read()`](cpp/io/basic_istream/read) expects to write to a -// character (`char`) buffer, we must use a `reinterpret_cast` to write -// to a byte (`unsigned char`) buffer.) The loop will continue reading -// chunks (or parts of chunks) until either there is an error or the end -// of the file is reached - you can use the stream's -// [`eof()`](cpp/io/basic_ios/eof) or [`fail()`](cpp/io/basic_ios/fail) -// function to determine which happened. -// -// If the loop completed successfully, `buffer` will contain the entire -// contents of the binary file. -// -// We could use this technique to read text data into a string, too -// (by not opening the file in binary mode). However, [there is -// a better way to read the contents of a file into a -// string](/common-tasks/read-text-file.html). From 1e4ee31def8d4346472e5edc28e001a3ff571226 Mon Sep 17 00:00:00 2001 From: "Mark A. Gibbs" Date: Sat, 20 Feb 2016 20:58:33 -0500 Subject: [PATCH 08/12] Renamed 'file' input to 'stream' input --- .../{read-binary-file.cpp => read-binary-stream.cpp} | 0 .../input-streams/{read-text-file.cpp => read-text-stream.cpp} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename 1-common-tasks/input-streams/{read-binary-file.cpp => read-binary-stream.cpp} (100%) rename 1-common-tasks/input-streams/{read-text-file.cpp => read-text-stream.cpp} (100%) diff --git a/1-common-tasks/input-streams/read-binary-file.cpp b/1-common-tasks/input-streams/read-binary-stream.cpp similarity index 100% rename from 1-common-tasks/input-streams/read-binary-file.cpp rename to 1-common-tasks/input-streams/read-binary-stream.cpp diff --git a/1-common-tasks/input-streams/read-text-file.cpp b/1-common-tasks/input-streams/read-text-stream.cpp similarity index 100% rename from 1-common-tasks/input-streams/read-text-file.cpp rename to 1-common-tasks/input-streams/read-text-stream.cpp From 859b68ce2506c9d294a458c2d0e7cb93fe2a8ec6 Mon Sep 17 00:00:00 2001 From: "Mark A. Gibbs" Date: Sat, 20 Feb 2016 21:06:39 -0500 Subject: [PATCH 09/12] Converted text input from 'file' to 'stream' --- .../input-streams/read-text-stream.cpp | 43 +++++++++---------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/1-common-tasks/input-streams/read-text-stream.cpp b/1-common-tasks/input-streams/read-text-stream.cpp index 3db787c..f119857 100644 --- a/1-common-tasks/input-streams/read-text-stream.cpp +++ b/1-common-tasks/input-streams/read-text-stream.cpp @@ -1,44 +1,41 @@ -// Read text file +// Read text stream #include #include #include -std::string read_text_file(std::string const& filename) +std::string read_text_stream(std::istream& in) { - std::ifstream file{filename}; - std::ostringstream ss{}; - ss << file.rdbuf(); + ss << in.rdbuf(); return ss.str(); } -// Read an entire text file into a string. +// Read entire content of a text stream into a string. // -// Sometimes we want to read data from a file +// Sometimes we want to read data from an input stream // [item-by-item](/common-tasks/read-line-of-values.html) or // [line-by-line](/common-tasks/read-line-by-line.html). But sometimes -// we want to read an entire text file into a string variable, in one -// shot, without any parsing or formatting. -// -// On [9], we create an input stream for the file that we will be -// reading. +// we want to read the entire stream's text content into a string +// variable, in one shot, without any parsing or formatting. // -// We create a [`std::ostringstream`](cpp/io/basic_ostringstream) on -// [11]; this is an output stream because we are reading *in* from the -// file stream, and writing what we read *out* to the string stream. We -// do this on [12], using file stream's -// [`rdbuf()`](cpp/io/basic_ifstream/rdbuf) function to get a pointer to -// its internal buffer. (We could also have used a -// [`std::filebuf`](cpp/io/basic_filebuf) object directly, without -// a `std::ifstream`.) +// On [9] we create a +// [`std::ostringstream`](cpp/io/basic_ostringstream). This is an output +// stream because we are reading *in* from the input stream (`in`), and +// writing what we read *out* to the string stream. We do this on [10], +// using input stream's [`rdbuf()`](cpp/io/basic_ifstream/rdbuf) +// function to get a pointer to its internal buffer. (We could also have +// used a [`std::streambuf`](cpp/io/basic_streambuf) object directly, +// without requiring a `std::istream` wrapper.) // -// If we were able to successfully read the file's contents, we can +// If we were able to successfully read the stream's contents, we can // recover them as a string using `std::ostringstream`'s -// [`str()`](cpp/io/basic_ostringstream/str) function, as on [14]. +// [`str()`](cpp/io/basic_ostringstream/str) function, as on [12]. int main() { - std::string str = read_text_file("file.txt"); + std::ifstream in{"file.txt"}; + + std::string str = read_text_stream(in); } From 1c9a1384fa0b5ecd775cd4a7cb6716a88cb882a1 Mon Sep 17 00:00:00 2001 From: "Mark A. Gibbs" Date: Sat, 20 Feb 2016 21:50:06 -0500 Subject: [PATCH 10/12] Converted binary input from 'file' to 'stream' --- .../input-streams/read-binary-stream.cpp | 143 +++++++++--------- 1 file changed, 72 insertions(+), 71 deletions(-) diff --git a/1-common-tasks/input-streams/read-binary-stream.cpp b/1-common-tasks/input-streams/read-binary-stream.cpp index 57edc8f..1fa2595 100644 --- a/1-common-tasks/input-streams/read-binary-stream.cpp +++ b/1-common-tasks/input-streams/read-binary-stream.cpp @@ -1,4 +1,4 @@ -// Read binary file +// Read binary stream #include #include // only for BUFSIZ @@ -7,74 +7,73 @@ #include #include -std::vector read_binary_file1(std::string const& filename) +std::vector read_binary_stream1(std::istream& in) { - std::ifstream file{filename, std::ios_base::in | std::ios_base::binary}; - - file.ignore(std::numeric_limits::max()); + std::istream::pos_type start = in.tellg(); + in.ignore(std::numeric_limits::max()); std::vector buffer{}; - buffer.resize(file.gcount()); + buffer.resize(in.gcount()); - file.seekg(0); - file.read(reinterpret_cast(buffer.data()), buffer.size()); + in.seekg(start); + in.read(reinterpret_cast(buffer.data()), buffer.size()); return buffer; } -std::deque read_binary_file2(std::string const& filename) +std::deque read_binary_stream2(std::istream& in) { using std::begin; using std::end; - std::ifstream file{filename, std::ios_base::in | std::ios_base::binary}; - std::deque buffer{}; std::array chunk{}; - while (file.read(reinterpret_cast(chunk.data()), chunk.size()) || - file.gcount()) + while (in.read(reinterpret_cast(chunk.data()), chunk.size()) || + in.gcount()) { - buffer.insert(end(buffer), begin(chunk), begin(chunk) + file.gcount()); + buffer.insert(end(buffer), begin(chunk), begin(chunk) + in.gcount()); } return buffer; } -// Read an entire binary file into a byte array. +// Read the entire contents of a binary stream into a buffer. // -// We have two basic options for reading the contents of a file into a +// We have two basic options for reading the contents of a stream into a // variable in memory: We can try to determine the size then allocate // the entire memory buffer at once and do one giant read directly into -// it (as in `read_binary_file1()` on [10-23]), or we can try to read +// it (as in `read_binary_stream1()` on [10-22]), or we can try to read // piece-by-piece growing our memory buffer as we read (as in -// `read_binary_file2()` on [25-41]). Which technique we use depends on +// `read_binary_stream2()` on [24-39]). Which technique we use depends on // numerous factors, such as the size of the data, how fast/slow the -// device we are reading from is, and whether or not we can make -// multiple read passes (we could not read input from a network stream -// more than once, for example). +// device we are reading from is, and whether or not the input stream +// is seekable (we could not seek back and forth arbitrarily on a +// network stream, for example). // -// In method 1, first we open an input file stream on [12]. Note that we -// open the stream in binary mode. +// In method 1, first we save the current location in the stream on +// [12]. This is so we can return to it after determining the size of +// stream // -// Once the file is open, the first thing we do is skip right to the end -// using the stream's [`ignore()`](cpp/io/basic_istream/ignore) -// function on [14]. It is important that we use the `ignore()` method -// rather than the `seekg(0, std::ios_base::end)` method, because the -// latter does not work reliably on all platforms. +// Then we skip right to the end using the stream's +// [`ignore()`](cpp/io/basic_istream/ignore) function on [13]. It is +// important that we use the `ignore()` method rather than the +// `seekg(0, std::ios_base::end)` method, because the latter does not +// work reliably on all platforms for technical reasons. // -// Next we create the buffer, and resize it to fit the entire file on -// [16-17]. Note that the buffer must be a contiguous container type, -// like `std::vector` or `std::array`, but not `std::list` or -// `std::deque` (because later we will be using `read()`, which writes -// to a contiguous memory block). The stream's +// Next we create the buffer, and resize it to fit the entire stream on +// [15-16]. Note that the buffer must be a contiguous container type, +// like `std::vector` or `std::array` but not `std::list` or +// `std::deque`, because later we will be using `read()`, which writes +// to a contiguous memory block. The stream's // [`gcount()`](cpp/io/basic_istream/gcount) function tells us the // number of bytes read in the last input operation, and `ignore()` is -// an input operation that we just used to "read" the entire file. This -// ensures the buffer is now large enough to hold the entire file. +// an input operation that we just used to "read" the entire stream. +// This ensures the buffer is now large enough to hold the entire +// stream. // -// Then we seek back to the beginning of the file ([19]), and do the -// giant read of the whole file into the buffer on [20]. Note that we +// Then we seek back to the location we started at on [18], and do the +// giant read of the whole stream into the buffer on [19]. Note that we // must use a `reinterpret_cast`, because `read()` expects a character // (`char`) buffer (IOStreams are text-based by default), while we are // reading bytes (`unsigned char`s). This cast is safe, because `char` @@ -82,62 +81,64 @@ std::deque read_binary_file2(std::string const& filename) // (possibly) for signedness, which doesn't matter in this context. // // If all of this was successful, `buffer` now contains the entire -// contents of the file. +// contents of the stream. // -// In method 2, we again begin by opening a file input stream ([30]), -// but before that we use `using` declarations on [27-28] to bring -// `std::begin()` and `std::end()` into scope. We will be using them -// shortly. +// In method 2, before we do anything, we use `using` declarations on +// [26-27] to bring `std::begin()` and `std::end()` into scope. We +// will be using them shortly. // -// After we open the file, we prepare the buffer we will be writing -// the file's data into on [32]. Unlike with the previous method (which -// required a contiguous container type), we can use any container type -// (even custom user-defined containers). However, -// [`std::deque`](cpp/container/deque) is uniquely suited for this task. -// The reason is that `std::deque` is *almost* a `std::vector` - it is -// just about as efficient as a vector, both in terms of memory usage -// and speed, and it allows random access to the contents. The primary -// difference is that instead of holding its contents in a single -// contiguous block of memory, `std::deque` uses multiple blocks. When a -// 100 kiB `std::vector` has to grow, it has to allocate an entirely new -// block of memory *larger* than 100 kiB (usually double, so 200 kiB, -// which means that a total of 300 kiB is being used), then copy all -// 100 kiB of data from the old block to the new one, then delete the -// old block. Meanwhile, when a 100 kiB `std::deque` has to grow, it -// merely adds a new block and continues - this requires far less -// memory, and nothing has to copied. Thus, `std::deque` is a better -// choice for a container we know will be growing piece-by-piece. +// On [29], we prepare the buffer we will be writing the stream's data +// into. Unlike with the previous method, which required a contiguous +// container type, we can use any container type - even custom +// user-defined containers. However, [`std::deque`](cpp/container/deque) +// is uniquely suited for this task. The reason is that `std::deque` is +// *almost* a `std::vector` - it is just about as efficient as a vector, +// both in terms of memory usage and speed, and it allows random access +// to the contents. The primary difference is that instead of holding +// its contents in a single contiguous block of memory, `std::deque` +// uses multiple blocks. When a 100 kiB `std::vector` has to grow, it +// has to allocate an entirely new block of memory *larger* than 100 kiB +// (usually double, so 200 kiB, which means that a total of 300 kiB is +// being used), then copy all 100 kiB of data from the old block to the +// new one, then delete the old block. Meanwhile, when a 100 kiB +// `std::deque` has to grow, it merely adds a new block to its internal +// list, and continues - this requires far less memory, and nothing has +// to copied. Thus, `std::deque` is a better choice for a container we +// know will be growing piece-by-piece. // -// In addition to our buffer, we prepare an array on [34] to act as a -// temporary buffer to hold each chunk as we read the file's data in +// In addition to our buffer, we prepare an array on [31] to act as a +// temporary buffer to hold each chunk as we read the stream's data in // chunk by chunk. We need to choose a reasonable size for the chunk // buffer - too small and we're doing too many reads, too large and // we're wasting memory. [`BUFSIZ`](cpp/io/c) is a reasonable default. // -// Now that our buffers are ready, we begin the read loop ([35-39]). The +// Now that our buffers are ready, we begin the read loop ([32-36]). The // read loop is a `while` loop with a compound test that can be read // like this: "While we successfully read a whole chunk's worth of data -// ([35]) *OR* we *didn't* read a whole chunk but we did read *some* -// data ([36]) - that is, we read *part* of a chunk - insert everything -// we read at the end of the buffer ([38])". Once again, because +// ([32]) *OR* we *didn't* read a whole chunk but we did read *some* +// data ([33]) - that is, we read *part* of a chunk - insert everything +// we read at the end of the buffer ([35])". Once again, because // [`read()`](cpp/io/basic_istream/read) expects to write to a // character (`char`) buffer, we must use a `reinterpret_cast` to write // to a byte (`unsigned char`) buffer. The loop will continue reading // chunks (or parts of chunks) until either there is an error or the end -// of the file is reached - you can use the stream's +// of the stream is reached - you can use the stream's // [`eof()`](cpp/io/basic_ios/eof) or [`fail()`](cpp/io/basic_ios/fail) // function to determine which happened. // // If the loop completed successfully, `buffer` will contain the entire -// contents of the binary file. +// contents of the stream. // // Note that we could use either of these methods to read text data into // a string (like `std::vector`, `std::string` is a contiguous // container type). However, [there is a better way to read the contents -// of a file into a string](/common-tasks/read-text-file.html). +// of a stream into a string](/common-tasks/read-text-stream.html). int main() { - std::vector data1 = read_binary_file1("file.dat"); - std::deque data2 = read_binary_file2("file.dat"); + std::ifstream file1{"file.dat", std::ios_base::in | std::ios_base::binary}; + std::vector data1 = read_binary_stream1(file1); + + std::ifstream file2{"file.dat", std::ios_base::in | std::ios_base::binary}; + std::deque data2 = read_binary_stream2(file2); } From 0e0dcc7672b15ec134b7e77139ae5ab8ef61cc33 Mon Sep 17 00:00:00 2001 From: "Mark A. Gibbs" Date: Tue, 10 Apr 2018 12:51:44 -0400 Subject: [PATCH 11/12] Remove ordering prefixes from directory names --- .../input-streams/read-binary-stream.cpp | 0 .../input-streams/read-text-stream.cpp | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {1-common-tasks => common-tasks}/input-streams/read-binary-stream.cpp (100%) rename {1-common-tasks => common-tasks}/input-streams/read-text-stream.cpp (100%) diff --git a/1-common-tasks/input-streams/read-binary-stream.cpp b/common-tasks/input-streams/read-binary-stream.cpp similarity index 100% rename from 1-common-tasks/input-streams/read-binary-stream.cpp rename to common-tasks/input-streams/read-binary-stream.cpp diff --git a/1-common-tasks/input-streams/read-text-stream.cpp b/common-tasks/input-streams/read-text-stream.cpp similarity index 100% rename from 1-common-tasks/input-streams/read-text-stream.cpp rename to common-tasks/input-streams/read-text-stream.cpp From 73d41223121ea74ca8bcc8aea605d34544ff99ac Mon Sep 17 00:00:00 2001 From: "Mark A. Gibbs" Date: Tue, 10 Apr 2018 13:33:07 -0400 Subject: [PATCH 12/12] Use spaces instead of tabs --- .../input-streams/read-binary-stream.cpp | 56 +++++++++---------- .../input-streams/read-text-stream.cpp | 14 ++--- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/common-tasks/input-streams/read-binary-stream.cpp b/common-tasks/input-streams/read-binary-stream.cpp index 1fa2595..1183ed7 100644 --- a/common-tasks/input-streams/read-binary-stream.cpp +++ b/common-tasks/input-streams/read-binary-stream.cpp @@ -9,33 +9,33 @@ std::vector read_binary_stream1(std::istream& in) { - std::istream::pos_type start = in.tellg(); - in.ignore(std::numeric_limits::max()); - - std::vector buffer{}; - buffer.resize(in.gcount()); - - in.seekg(start); - in.read(reinterpret_cast(buffer.data()), buffer.size()); - - return buffer; + std::istream::pos_type start = in.tellg(); + in.ignore(std::numeric_limits::max()); + + std::vector buffer{}; + buffer.resize(in.gcount()); + + in.seekg(start); + in.read(reinterpret_cast(buffer.data()), buffer.size()); + + return buffer; } std::deque read_binary_stream2(std::istream& in) { - using std::begin; - using std::end; - - std::deque buffer{}; - - std::array chunk{}; - while (in.read(reinterpret_cast(chunk.data()), chunk.size()) || - in.gcount()) - { - buffer.insert(end(buffer), begin(chunk), begin(chunk) + in.gcount()); - } - - return buffer; + using std::begin; + using std::end; + + std::deque buffer{}; + + std::array chunk{}; + while (in.read(reinterpret_cast(chunk.data()), chunk.size()) || + in.gcount()) + { + buffer.insert(end(buffer), begin(chunk), begin(chunk) + in.gcount()); + } + + return buffer; } // Read the entire contents of a binary stream into a buffer. @@ -136,9 +136,9 @@ std::deque read_binary_stream2(std::istream& in) int main() { - std::ifstream file1{"file.dat", std::ios_base::in | std::ios_base::binary}; - std::vector data1 = read_binary_stream1(file1); - - std::ifstream file2{"file.dat", std::ios_base::in | std::ios_base::binary}; - std::deque data2 = read_binary_stream2(file2); + std::ifstream file1{"file.dat", std::ios_base::in | std::ios_base::binary}; + std::vector data1 = read_binary_stream1(file1); + + std::ifstream file2{"file.dat", std::ios_base::in | std::ios_base::binary}; + std::deque data2 = read_binary_stream2(file2); } diff --git a/common-tasks/input-streams/read-text-stream.cpp b/common-tasks/input-streams/read-text-stream.cpp index f119857..bde9ac0 100644 --- a/common-tasks/input-streams/read-text-stream.cpp +++ b/common-tasks/input-streams/read-text-stream.cpp @@ -6,10 +6,10 @@ std::string read_text_stream(std::istream& in) { - std::ostringstream ss{}; - ss << in.rdbuf(); - - return ss.str(); + std::ostringstream ss{}; + ss << in.rdbuf(); + + return ss.str(); } // Read entire content of a text stream into a string. @@ -35,7 +35,7 @@ std::string read_text_stream(std::istream& in) int main() { - std::ifstream in{"file.txt"}; - - std::string str = read_text_stream(in); + std::ifstream in{"file.txt"}; + + std::string str = read_text_stream(in); }