106742802 1 Chapter 14 Binary Files Bitsets 106742802 2 Binary Files Hardware level - file data records are stored on a disk in fixed-length data blocks (normally non-contiguous) . Logical View - sequence of records A text file contains ASCII characters with a newline sequence separating lines A binary file soncists of data objects that vary from a single character (byte) to more complex structures that include integers, floating-point values, programmer-generated class objects, and arrays. The Operating system allows a programmer to read and write individual data records and Maintains a file pointer. File systems allow direct access - measures the current position as an offset from the beginning of the file in number of bytes. File Pointer - maintains current position in the file File as a direct access structure: R0 R1 R2 R3 Rn-1 106742802 3 The fstream class The C++ fstream class describes file objects that can be used for both - input and output When creating the object the open method is used to attach a physical file named and access mode. Mode ios::in ios::out ios::trun ios::nocreate ios::binary Action open file for reading open file for writing erase file records before reading or writing if the file does not exist, do not crate an empty file. return a stream error condition. open file in binary mode not a text file #include <iostream> #include <fstream> 1. fstream f; // open textfile “ Phone” for input. If the file does not already exist, indicate // an error condition f.open ( “Phone”, ios::in | ios:: nocreate ); 2. fstream f; // open a binary file for input and output f.open ( “ DataBase”, ios::in | ios::out | ios::binary ); tellg() - returns in bytes the location of the current file pointer as an offset from the beginning of the file for an input file tellp( ) - returns in bytes the location of the current file pointer for an output file seekp( ) and seekg( ) - allow the user to reposition the current file pointers. The seek functions take an offset parameter that measures the number of bytes from the beginning (beg), ending (end) , or current position (cur ). If the file is for both, input and output use the seek functions tellg and seekg.. 106742802 4 Program 14-3 #ifndef ACCOUNT_CLASS #define ACCOUNT_CLASS #include <iostream> using namespace std; // maintain bank account information class account { public: account (int n = 0, double bal = 0.0): acctNo(n), balance (bal) {} // update balance with deposit (D) or withdrawal (W) void update (char type, double amt) { if (type == 'D') balance += amt; else balance -= amt; } // output account friend ostream& operator<< (ostream& ostr, const account& acct) { ostr << acct.acctNo << ": " << acct.balance; return ostr; } private: int acctNo; double balance; }; #endif// ACCOUNT_CLASS 106742802 // File: prg14_3.cpp // program reads and writes account records to and from file // accounts.dat"; initial write creates 5 records in file with // account numbers 0 to 4. user is prompted to enter a series of // account numbers, transactions (deposit 'D'/withdrawal 'W'), and // amounts. corresponding account is read from file and updated // based on transaction type. resulting data is then written back // to the file. program concludes by displaying current account // information after user enters account number -1 #include <iostream> #include <fstream> #include "d_acct.h" using namespace std; // output the records in a binary file of account objects void outputAccounts(fstream& f); int main() { // binary file of part values fstream acctFile; account acct; int i, n; char type; double amt; // open binary file for input and output. truncate earlier version acctFile.open("accounts.dat",ios::in | ios::out | ios::trunc | ios::binary); if (!acctFile) { cerr << "Cannot create 'accounts.dat'" << endl; exit(1); } 5 106742802 6 // File: prg14_3.cpp ( continued) // write 5 records with account number 0, 1, ..., 4 for (i=0;i < 5;i++) { acct = account(i); acctFile.write((char *)&acct, sizeof(account)); } // ask user to input account number and 'D' for deposit // and 'W' for withdrawal along with the ammount // update a record of the file; terminate with accout number -1 while(true) { cout << "Enter acct#, type (D or W), and amount: "; cin >> n; if (n == -1) break; cin >> type >> amt; // seek to record n acctFile.seekg(n*sizeof(account), ios::beg); // read the record and update the balance acctFile.read((char *)&acct, sizeof(account)); acct.update(type, amt); // seek back to the previous record acctFile.seekg(-int(sizeof(account)), ios::cur); acctFile.write((char *)& acct, sizeof(account)); } // output final state of the account database cout << endl << "Final state of the accounts" << endl; outputAccounts(acctFile); return 0; } 106742802 // File: prg14_3.cpp ( continued ) void outputAccounts(fstream& f) { account acct; // go to the end of the file f.seekg(0, ios::end); // n = number of records in the file int n = f.tellg()/sizeof(account), i; // go back to the beginning of the file f.seekg(0, ios::beg); // read and output n records for (i=0; i < n; i++) { f.read((char *)& acct, sizeof(account)); cout << acct << endl; } } /* Run: Enter acct#, type (D or W), and amount: 3 D 200.00 Enter acct#, type (D or W), and amount: 1 D 500.00 Enter acct#, type (D or W), and amount: 4 W 150.00 Enter acct#, type (D or W), and amount: 2 D 800.00 Enter acct#, type (D or W), and amount: 4 D 225.00 Enter acct#, type (D or W), and amount: 2 W 475.00 Enter acct#, type (D or W), and amount: 1 W 100.00 Enter acct#, type (D or W), and amount: -1 Final state of the accounts 0: 0 1: 400 2: 325 3: 200 4: 75 */ 7 106742802 8 BINARY CLASS SPECIFICATION #include <iostream.h> #include <fstream.h> #include <stdlib.h> #include “strclass.h” enum Access { IN, OUT, INOUT } ; // file access types enum SeekType { Beg, CUR, END } ; // type of seeks that can be performed template < class T> class BinFile { private: fstream f ; Access accessType; String Fname; int fileOpen; // C++ stream object with its name and access type // file access type // physical file name // is the file open? // parameter measuring file as a direct access structure int Tsize; // size of data record int fileSize; // number of file records // prints an error message and terminates the program void error ( char * msg ); public: // constructors and destructors BinFile ( const String& fileName, Access atype = OUT ); ~BinFile(void); BinFile(BinFile<T>& bf); // copy constructor - terminates programs // not allowed // file utility methods void Clear ( void ); // truncate records in the file - leaves file open void Delete(void); // close file and remove it from the file system void Close ( void ); // close the file int EndFile ( ); // tests EOF condition - for IN files - otherwise use loop to control writes long Size (); // returns number of file records void Reset (void); // reset the file to first record void Seek ( long pos, SeekType mode ); int Read( T *a, int A ); // block read of n data values into address A int Write ( T *A, int n ); // block write T peek ( void ); // value of record at current location void Write ( const T& data, long post );// copy data to record at index pos T Read ( long post ); // read record from index position void Append ( T item ); // write a record at the end of file }; Version 1.0.0 Draft - January 8, 1995 kfv-COD CIS-246 ADVANCED C++ COURSE PACKET 13-29 106742802 BINFILE IMPLEMENTATION ( for complete implementation see binfile.h) // constructor template < class T> BinFile<T>::BinFile ( const String& fileName, Access atype ) { // stream open operations // for IN, a file is not created if it does not exist // for OUT , any existing data are thrown away ( truncated ) // for INOUT , the file has input and output capability. if ( atype == IN ) fopen ( fileName, ios :: in | ios::nocreate | ios :: binary ); else if ( atype == OUT ) fopen ( filename, ios::out | ios::trunc | ios ::binary ); else fopen ( fileName, ios ::in | ios::out | ios :: binary ); if ( ! f ) Error ( “binFile constructor : file cannot be opened “ ); else fileOpen = 1; accessType = atype; // Compute number of records in the file // Tsize is the number of bytes in the data type Tsize = sizeof ( T ); if ( accessType == IN ) || accessType == INOUT ) f.seekg( 0, ios::end ); fileSize =f.tellg ( ) / Tsize; f.seekg ( 0, ios::beg ); } else filesize = 0; // size for OUT file // record physical file name fname = fileName; } 9 106742802 10 File Access - Read // read a record from the file - read takes the position parameter and by combining the data // size and position parameter and using seekg locates the current file pointer and extracts // the data value template < class T > T BinFile<T> :: Read ( long pos ) { T data; if ( !fileOpen ) Error (“ BinFile” Read ( int pos ) : file closed “ ); // read method invalid with OUT only file if ( accessType == OUT ) Error ( “Invalid file access operation “ ); else If ( pos < 0 || pos >= fileSize ) // test range 0 - fileSize-1 Error (“invalid file access operation “ ); // position the current file pointer and extract data using the fstream read method f.seekg ( pos * Tsize , ios::beg ); f.read ( (char * ) & data, Tsize ) ; // if the access mode is IN and we have read all records, set the stream to EOF if ( accessType == IN ) if ( f.tellg ( ) / Tsize >= fileSize ) f.clear ( ios :: eofbit ) ; // sets EOF bit return data; } 106742802 11 File Access - Write // writes an en-element list from array A to the file template < class T > void BinFile <T> :: Write ( T *A, int n ) { long previousRecords; // write is invalid with an IN only file if ( accessType == IN ) Error ( “Invalid file access operation “); if ( !fileOpen ) Error ( “binFile Write ( T* a, int n ) : file closed “); // compute the new file size, call tellg( ) to compute number of file records prior to // output point. determine if file size will expand. if so, increment fileSize by the // number of records that will be added. peviousRecords - f.tellp ( ) / Tsize ; if ( previousRecords + n > fileSize ) fileSize += previousRecords + n - fileSize; // the number of bytes to write is n * Tsize; f.write ( ( char * ) A, Tsize * n ); } Utility Method - Clear //Clear deletes file records by closing and then opening file template < class T> void BinFile <T>::Clear ( void ) { // an IN file cannot be cleared if ( accessType == IN ) Error ( “Invalid file access operation “ ); // close and then open file f.close ( ) ; if ( accessType == OUT ) f.open ( fname, ios::out | ios::trunc | ios::binary ); else f.open ( fname, ios :: in | ios :: out | ios :: trunc | ios :: binary ); if ( !f ) Error ( “BinFile Clear : cannot reopen the file “ ); fileSize = 0; } 106742802 12 BITSETS C++ allows manipulation of individual bits with the bit-wise operators OR | AND & NOT ~ EOR ^ (inclusive or) (exclusive or ) It also provides bit shift operators – typically used with unsigned numbers shift left << x << n multiplies x by 2n x>>n dividex x by 2n shifting to the left fills vacated bits with 0 shift right >> For unsigned numbers- shifting to the right fills vacated bits with 0 For signed numbers – shifting to the right fills vacated bits with 1 Example: Unsigned char x = 182, y = 154; In binary: x = 10110110 , y = 10011010 a) x 10110110 |y 10011010 10111110 b) x 10110110 &y 10011010 10010010 Bit shift operations: x = 10110110 X << 1 y = 10011010 y >> 2 = 01011010 = 00101101 c) x 10110110 ^y 10011010 00101100 106742802 13 Bitmaps Bitmaps are useful for managing a collection of resources of any type. For example, to determine if a resource is in use, you need to store only one bit of information per resource. Assume the figure below represents a file of records: Record 1 Record 2 Record 3 Record 4 Record 5 Each record is of the same size ( it could be a char, int , or object of any type) If a record is deleted and you want to add a new record you should reuse the empty space. You could keep a free list to determine the empty locations in the file. An alternative you could maintain another file called a bitmap. Bitmap 10101000 00000000 00000000 00000000 The bitmap has one bit associated with each record in the file, grouped together in banks of 32 bits. 106742802 14 Allocation using a bitmap: long FindEmpty() { openbit(); // open file that contains bitmap while ( (bank_no = read_bank(&bank)) != END_OF_BITMAP ) { // if room left in bank if ( (offset = find_set_next_zero( & bank)) != BANK_FULL) { return ( (long) ( bank * BITS_IN_LONG + offset )); } } bank_no = alloc_bank(&bank); allocate new bank offset = find_set_next_zero(&bank); // turn on first bit assert( offset == 0L); return (long)(bank * BITS_IN_LONG + offset) ); } find_set_next_zero( long *bank) { // bank is 32 bits BITS_IN_LONG int offset = 0; for ( offset = 0; offset < BITS_IN_LONG; offset++ ) { if ( (*bank & ( 1L << offset)) = = 0L ) // find 0 // set 0 to to 1 *bank != (1L << offset ); // call a function to write new bank to file update(bank); return offset; } } return BANK_FULL } Releasing the resource: 106742802 15 // find the bit in the bitmap corresponding to the resource. // the resource ( bit position) is the only argument to the function Long restorebit(long resource // bit position ) { long bank, bank_no, bit, find_bank(); openbit(); // open file containing bit map // fibd the correct group of 32 bits // calculate bank number bank_no = resource / BITS_IN_LONG; // calculate the offset within the bank using modulous // (remainder) operator bit = resource % BITS_IN_LONG; // the find_bank function returns the appropriate offset // in the file and reads in the bank bank_no = find_bank(bank_no, &bank ); // make sure resource was allocated assert( bank & ( 1L MM bit ) ); // The bit is turned off by “anding” a mask into the // bank. The mask (1L << bit ) is created using the offset to shift // a 32 – bit representation of 1 to the left, and then taking its // complement. All bits will be as before except for the // target bit which will be set to zero. bank &= ~(1L << bit ); // update the bitmap file and return the resource number released update ( &bank); return (resource); } 106742802 16