Introduction to Memory-Mapped IO in Java, Page 3
Behavior similar to ByteBuffer object
Otherwise, MappedByteBuffer objects behave no differently than ordinary direct byte buffers.
Therefore, most of what you have previously learned about manipulating ByteBuffer objects, (such as getting different views of a ByteBuffer object), also applies to MappedByteBuffer objects.
The map method parameters
As you saw in Listing 7, the map method of the FileChannel class requires three parameters:
Using these parameters, the map method maps a region of the channel's file directly into memory.
A region of a file may be mapped into memory in one of three modes:
If the file is mapped in read-only mode, any attempt to modify the contents of the resulting buffer will cause an exception to be thrown. In this case, the channel must have been opened for reading.
In read/write mode, changes made to the contents of the buffer will be propagated to the file. (Note, however, that the changes may or may not be visible to other programs that may have mapped the same file.) In this case, the channel must have been opened for both reading and writing.
In private mode, changes made to the contents of the buffer will not be propagated to the file. Similarly, in this case, the channel must have been opened for both reading and writing.
The position and size parameters of the map method
The position parameter of the map method defines the starting point in the file that is mapped into the buffer.
The size property defines the number of bytes that will be mapped.
(The combination of the position and size parameters can be used to define a region of sequential bytes within the file that will be mapped into memory. This will be illustrated later in the sample program.)
Map the entire file into memory
The invocation of the map method in Listing 7 causes the entire file named junk.txt to be mapped in read/write mode into the ByteBuffer object referred to by the reference variable named mapFile.
(Since I had no plans to invoke any of the three methods defined in the MappedByteBuffer class, it was appropriate for me to save the reference as type ByteBuffer instead of as type MappedByteBuffer.)
The position, limit, and capacity properties
The MappedByteBuffer object returned by the map method will have a position of zero. Its limit and capacity will be equal to the value of the size parameter passed to the map method.
Can close the FileChannel object
Once a mapping is established, it is not dependent on the FileChannel object that was used to create it. Closing the channel has no effect upon the validity of the mapping.
Mapping files is expensive
According to Sun:
"For most operating systems, mapping a file into memory is more expensive than reading or writing a few tens of kilobytes of data via the usual read and write methods. From the standpoint of performance it is generally only worth mapping relatively large files into memory."
Display the memory map
The code in Listing 8 displays the contents of the memory map. Note that since the memory map is simply a ByteBuffer object, the same method used earlier to display the contents of a ByteBuffer object can be used to display the contents of the memory map.
showBufferData( mapFile,"mapFile"); Listing 8
The output produced by the code in Listing 8 is shown in Figure 3. Happily, the data in the memory map matches the original data indicating that no data corruption or modification has occurred to this point in the program.
Data for mapFile 65 66 67 68 69 70 Figure 3
Modify values in the memory map
The code in Listing 9 purposely modifies the contents of the memory map, which in turn, modifies the contents of the disk file represented by the memory map (changes to the map propagate to the file).
mapFile.position(2); mapFile.put((byte)1); mapFile.put((byte)2); Listing 9
(Note that the channel that was used to create the map was closed before the modifications were made in Listing 9. As mentioned earlier, once the map is established, the validity of the map is not dependent on the channel used to establish the map.)
The code in Listing 9 sets the position property of the map to a value of 2 and then executes two sequential relative put operations on the map. This causes the values in the third and fourth elements in the map to be changed from the values of 67 and 68 to the values of 1 and 2.
Display the modified map
The code in Listing 10 displays the contents of the modified memory map.
showBufferData( mapFile,"mod-mapFile"); Listing 10
This produces the output shown in Figure 4
Data for mod-mapFile 65 66 1 2 69 70 Figure 4
As you can see, the third and fourth values have been changed relative to their original value (boldface added for emphasis).
(At the risk of being redundant, I am going to stress that making this change to the values in the memory map causes the values in the associated disk file named junk.txt to change accordingly.)
Confirm modifications to the file
Now it's time to confirm that the modification of the values in the memory map caused the values stored in the file to be changed accordingly.
The code in Listing 11 gets a FileChannel object that can be used to read the contents of the file named junk.txt.
(Note that the channel was opened in read-only mode.)
FileChannel newInCh = new RandomAccessFile( "junk.txt","r"). getChannel(); Listing 11
Create a ByteBuffer object through allocation
The code in Listing 12 invokes the allocate method of the ByteBuffer class to create a new empty ByteBuffer object.
ByteBuffer newBuf = ByteBuffer.allocate( (int)fileSize); Listing 12
(What is an empty ByteBuffer object? Although I can't say for absolute certain, I suspect that each byte in the new ByteBuffer object will have a value of zero. I base this on the fact that the buffer will have a backing array of type byte. Normally a new array object of type byte will have each of its elements initialized to the default value of zero (unless they are purposely initialized to some other value). Thus, an allocated ByteBuffer object wraps a new array object, which has default initialization of its elements. A reference to the backing array can be obtained by invoking the array method on the ByteBuffer object.)
An alternative approach
The allocate method provides an alternative to the wrap method for creating a new ByteBuffer object. In this case, the size of the ByteBuffer object was set to match the size of the file, which was determined earlier in Listing 7.
Populate the ByteBuffer object
The code in Listing 13 uses the read method of the FileChannel class to read the file contents into the new ByteBuffer object.
System.out.println( "Bytes read = " + newInCh.read(newBuf)); newInCh.close(); Listing 13
After reading the data from the file into the ByteBuffer object, the code in Listing 13 closes the FileChannel object.
Display the data read from the file
The code in Listing 14 invokes the showBufferData method again, this time to display the data that was read from the disk file into the ByteBuffer object.
showBufferData(newBuf,"newBuf"); Listing 14
The output produced by the code in Listings 13 and 14 is shown in Figure 5 (boldface added for emphasis).
Bytes read = 6 Data for newBuf 65 66 1 2 69 70 Figure 5
File data was modified
As you can see, the third and fourth values in the disk file were modified earlier when changes were made to those two values in the memory map. This confirms that changes made to the memory map propagate to the disk file represented by the map.
Mapping a region of a file
You can map an entire file, or a region within a file into a memory map.
The code in Listing 15 gets a new read-only FileChannel object, and uses it to map a region of the same file named junk.txt into a new read-only ByteBuffer object.
FileChannel rCh = new RandomAccessFile( "junk.txt","r"). getChannel(); ByteBuffer roMapFile = rCh.map( FileChannel.MapMode.READ_ONLY, 1, fileSize-2); rCh.close(); Listing 15
The definition of the region
The selected region begins at a position value of 1 and includes a number of bytes that is two less than the number of bytes in the file.
(Thus, the region includes all but the first and last bytes in the file.)
Close the channel
Having mapped the file into memory, the code in Listing 15 closes the FileChannel object used to perform the mapping operation.
Display partial memory map
The code in Listing 16 displays the contents of the partial memory map.
showBufferData(roMapFile, "roMapFile"); Listing 16
The output produced by the code in Listing 16 is shown in Figure 6.
Data for roMapFile 66 1 2 69 Figure 6
As you can see, only four of the six bytes in the file were mapped into the memory map. (The first and last bytes in the file were excluded.)
A read-only map
If this were a read/write memory map, it would be possible to modify any of those four values in the map and have the changes propagate to the file.
However, this is a read-only map. Therefore, any attempt to modify any of the values in the map will cause a ReadOnlyBufferException to be thrown.
That's it for now
By now you should understand a quite a lot about the use of FileChannel
objects to create and manipulate memory maps of disk files.