A couple of weeks ago, I demonstrated how to read in file data one line at a time using either ColdFusion's new CFLoop functionality or Java's LineNumberReader. Recently, however, Martin reminded me that there are also new function-based equivalents to the CFLoop behaviors that I showcased. I've always been a tag-based coder so I sometimes forget that certain functions exist. As such, I thought I'd take a moment to redo my previous demo using the new file-oriented functions.
Just as we did before, we are going to create a text file, populate it with some data, read that data back in, and display the contents on the page. The file reading will be done in two different ways - by line and by character chunk.
<!--- We are going to be reading in a file, line by line, so first, let's create a file to read. Define the path to the file we are going to populate. ---> <cfset filePath = expandPath( "./data.txt" ) /> <!--- Delete the file if it exists so that we don't keep populating the same document. ---> <cfif fileExists( filePath )> <cfset fileDelete( filePath ) /> </cfif> <!--- Create a file file object that we can write to. We have to explicitly define the Mode as Append otherwise the file will be opened on read mode. ---> <cfset dataFile = fileOpen( filePath, "append" ) /> <!--- Write some data to the file. ---> <cfloop index="i" from="1" to="10" step="1"> <!--- We could use either fileWrite() or fileWriteLine() here. However, fileWriteLine() will automatically append a line- break after each file write. ---> <cfset fileWriteLine( dataFile, "This is line #i# of 10 in this file." ) /> </cfloop> <!--- Now that we have finished writing the file, let's close it. This will close the stream which should prevent any unintented locking on the file access. ---> <cfset fileClose( dataFile ) /> <!--- ----------------------------------------------------- ---> <!--- ----------------------------------------------------- ---> <cfoutput> <!--- Now, we are going to read the file in line-by-line using ColdFusion 8's new file functions. First, we need to open our file and get a handle on the input stream. NOTE: We could have left out the "Read" mode as it is the default mode of the file object. ---> <cfset dataFile = fileOpen( filePath, "read" ) /> <!--- Read the file in one line at a time. When we do this, we can only read until we have reached the End of File (EOF); otherwise, we'll get a ColdFusion error for "End of file reached." ---> <cfloop condition="!fileIsEOF( dataFile )"> <!--- Read the next line. ---> <cfset line = fileReadLine( dataFile ) /> Line: #line#<br /> </cfloop> <!--- Close the file stream to prevent locking. ---> <cfset fileClose( dataFile ) /> <br /> <!--- The new file functions also allow us to read in chunks of a file at a time (not just lines... which are chunks in a sense). Here, we are going to read the file in 50 characters at a time. Again, we have to open the file for reading. ---> <cfset dataFile = fileOpen( filePath, "read" ) /> <!--- Keep looping until we reach the end of the file. ---> <cfloop condition="!fileIsEOF( dataFile )"> <!--- Read in upto 50 characters. ---> <cfset chunk = fileRead( dataFile, 50 ) /> 50 Char Chunk: #chunk#<br /> </cfloop> <!--- Close the file stream to prevent locking. ---> <cfset fileClose( dataFile ) /> </cfoutput>
As you can see, we are making use of many of the new file-oriented functions provided in ColdFusion 8. The first half of the demo reads in the file one one line at a time (as delimited by the new line and carriage return characters). The second half of the demo reads in the file 50 characters at a time. When I run the above code, we get the following page output:
Line: This is line 1 of 10 in this file.
Line: This is line 2 of 10 in this file.
Line: This is line 3 of 10 in this file.
Line: This is line 4 of 10 in this file.
Line: This is line 5 of 10 in this file.
Line: This is line 6 of 10 in this file.
Line: This is line 7 of 10 in this file.
Line: This is line 8 of 10 in this file.
Line: This is line 9 of 10 in this file.
Line: This is line 10 of 10 in this file.
50 Char Chunk: This is line 1 of 10 in this file. This is line 2
50 Char Chunk: of 10 in this file. This is line 3 of 10 in this f
50 Char Chunk: ile. This is line 4 of 10 in this file. This is li
50 Char Chunk: ne 5 of 10 in this file. This is line 6 of 10 in t
50 Char Chunk: his file. This is line 7 of 10 in this file. This
50 Char Chunk: is line 8 of 10 in this file. This is line 9 of 10
50 Char Chunk: in this file. This is line 10 of 10 in this file.
50 Char Chunk:
This works quite nicely. And, while there are more functions available to us, the only ones that I needed for this demo were:
- fileExists( path )
- fileDelete( path )
- fileOpen( path [, mode, charset ] )
- fileWriteLine( file, data )
- fileReadLine( file )
- fileRead( file [, charset ] )
- fileClose( file )
- fileIsEOF( file )
NOTE: fileRead() also accepts a file path instead of a file object. Using it in this way returns the entire content of the file. While this is very useful, it does not pertain to either of the approaches demonstrated in this blog post.
When we open a file using fileOpen(), ColdFusion creates either an input or output stream for that file and returns a reference to the file object. Not only is this file object used in conjunction with many of the file functions, it also provides us with information about the file in question. CFDump'ing the resultant file object gives us something that looks like this:
The status of the file can be either "open" or "closed". If it the file is closed, you can no longer perform any actions on it. If the file is open, the actions that you are able to perform are based on the mode in which the file was opened. By default, the fileOpen() function opens the file in "read" mode which only allows you to invoke read-based functions on it. Any attempt to write to a file that has been opened in "read" mode will result in the following ColdFusion error:
Write cannot be called when the file is opened in read mode.
Conversely, any attempt to read from a file that has been opened in "write" mode will result in a similar error:
Read cannot be called when the file is opened in write mode.
If you want to write to a file, you need to open it in either "write" or "append" mode. If you open a file in "write" mode, write-based actions will overwrite the entire contents of the existing file (if it exists). If you open a file in "append" mode, on the other hand, write-based actions will simply append the content to the existing file (if it exists).
When you are done reading from or writing to a file, it is best to close the file stream using fileClose(). Doing this allows the operating system to release its hold on the file which will help to prevent unexpected file locking. While I was not able to actually demonstrate (deliberately cause) any file locking problems, it is definitely a best practice to explicitly relinquish control over a given file when it is no longer required.
NOTE: You still have access to the file object properties after the file stream has been closed.
I know there are a lot of people who swear by Script-based programming and don't much care for using CFML tags; but, if you compare this demo to my previous demo, you'll notice that using the file functions in lieu of the file tags actually requires us to use a bit more logic. The CFFile and CFLoop tags nicely encapsulate all of the file-stream management so that we don't have to care about locking or modes (as much). Now, I'm not saying you shouldn't use these functions - I'm just saying that sometimes, CFML tags provide a really nice interface to core functionality. The best solution is going to involve knowing when to use the most appropriate approach.
Want to use code from this post? Check out the license.