= Specialized Working with Files = == Random IO with Separated Buffers == === Why? === One reason is that it is a flexible method of reading and writing random IO that's probably as efficient as any other. It also isn't much more complex. One plausible reason for maintaining separated buffers is to maintain a linkage between the file position and the data. When data is read by a Reader it is buffered in for efficiency. The file pointer is then advanced one buffer load (or to the end of file). This causes no problems for sequential reads, but it after a buffer is read the file pointer points either just beyond the end of the buffer or to the end of file. If you care where the next read will start, this is a significant problem. Using a separated buffer during reads and writes solves the problem with little overhead. === Setting up the class === This is just one of many good ways to set up the class...but it's one that has been tested. The __this__ methods are omitted, as there is nothing special about them.[[BR]] Note that error checks have been omitted for the sake of clarity, not because they should be omitted. {{{ #!d import tango.io.Buffer; import tango.io.FileConduit; import tango.io.GrowBuffer; import tango.io.protocol.Reader; import tango.io.protocol.Writer; class ClassName { uint byteLengthEstimate() { /* Use some way to estimate the byte length of this class. * Overestimate rather than underestimate. * This is used in allocation of the buffers. */ } }}} === Writing out the class === This code assumes that you have a Writer method defined for the components of the class. Check out the Writer Class for a list of the types that are predefined. {{{ #!d // write data at a position ulong put(FileConduit fc, ulong pos) { FileConduit fc; // The file associated with the class ulong fPos; // The current position of the file uint[] buff; // this is the buffer that will hold the data to be written // note that this allocates four times the estimated requirement (buff is uints rather than bytes) buff.length = byteLengthEstimate; fc.seek(pos); // attempt to move file to desired position fpos = fc.getPosition; // set file position variable to actual position // Here I reserve some bytes at the start of the buffer for use outside of the writer method Buffer bf = new Buffer(buff, 4); // reserve 4 bytes of space Writer write = new Writer(bf); // create a writer on the Buffer write (dataItem_1); // using the writer on a data item write (dataItem_...); // using the writer on a data item write (dataItem_n); // using the writer on a data item uint bfEnd = cast(uint)bf.readable; // determine how many bytes have been written buff[0] = bfEnd; // here I use the reserved space fc.write(buff[0..(bfEnd+3)/4]); // write the buffer to the fileConduit return fPos; // this method choses to return the address of the START of the data } }}} === Reading the class back in === Note that in this read method the buffer is not separated from the FileConduit. Also the buffer chosen is a GrowBuffer. This is to allow the handling of records whose size is not known ahead of time. {{{ #!d ClassName get(FileContuit fc, ulong pos) { fc.seek(pos); GrowBuffer gb = new GrowBuffer(fc); Reader read = new Reader(gb); uint len; // When I wrote the record, the first thing I wrote was the length read(len); read(data item_1); read(data item_...); read(data item_n); ulong oEnd = gb.getPosition; // One can accurately tell the position in the buffer if (len != oEnd) // Since I knew the lenght written, length read can be an error check { // Handle error somehow } return this; // The value read from the file is now the value of this class } }}}