Solution for Programming Exercise 11.2
This page contains a sample solution to one of the exercises from Introduction to Programming Using Java.
Exercise 11.2:
Write a program that will count the number of lines in each file that is specified on the command line. Assume that the files are text files. Note that multiple files can be specified, as in:
java LineCounts file1.txt file2.txt file3.txt
Write each file name, along with the number of lines in that file, to standard output. If an error occurs while trying to read from one of the files, you should print an error message for that file, but you should still process all the remaining files. Do not use TextIO to process the files; use a Scanner or a BufferedReader to process each file.
The main() routine for this program is a simple for loop that processes the command-line arguments. Each argument is supposed to be the name of a file. The loop just prints the file name and calls a subroutine that counts the number of lines in the file and outputs the result:
for (int i = 0; i < args.length; i++) { System.out.print(args[i] + ": "); countLines(args[i]); }
The countLines() subroutine will catch any errors that occur when it tries to access the file. If an error occurs, it will output an error message instead of the number of lines. Since the error is handled in the subroutine, it won't crash the program or stop the main() routine from going on to process any remaining files.
The countLines() subroutine can be written using either a a BufferedReader or a Scanner to read lines from the file; each of these classes has a simple method for reading a complete line of text from the file. (An alternative approach would read individual characters from the file and count the number of end-of-line markers. Unfortunately, ends-of-line can be marked by any of the strings "\n", "\r", or "\r\n" and that would add to the difficulty of the task. The line reading methods in BufferedReader and Scanner will work no matter which end-of-line marker is used.)
In my solution, I use a BufferedReader. The countLines() method creates a BufferedReader stream to read from the file. It then calls the readLine() routine from the BufferedReader class to read lines of text from the file, until the end of the file is encountered. For a BufferedReader, the end-of-file can be recognized when readLine() returns null. The method counts each line as it reads it. In the end, the value of the counter is the number of lines in the file. The subroutine writes this value to standard output. All this is done in a try..catch statement, treating the BufferedReader as a resource, so that an error can be handled if it occurs. It's pretty straightforward. The complete solution is given below.
An interesting variation on this would be to use the stream API from Section 10.6. A BufferedReader, in, has a method in.lines() that creates a stream of strings containing the lines of the file. We can find out how many lines there are just by applying the count() stream operation to that stream. So, using the stream API, the lines could be counted using
long lineCt = in.lines().count();
One note about the command line for calling this program. If you are using UNIX, including MacOS or Linux, you can take advantage of something called "wildcards" in the command-line arguments. For example, if you say, "java LineCounts *.txt", the "*" is a wildcard. The operating system will expand "*.txt" into a list of all the files in the current directory that end in ".txt". Similarly, "*xx*" expands into a list of all file names that contain "xx", and "fil*dat" expands into the list of file names that begin with "fil" and end with "dat". The "*" matches any number of characters in the file name, including zero characters. This expansion is done before your program sees the command-line arguments, so you don't have to worry about it in your program. Typing the command "java LineCounts *.txt" would be exactly equivalent to typing something like "java LineCounts file1.txt file2.txt file3.txt". This type of expansion happens for any command in UNIX, not just for the "java" command.
I should also note that if a file name contains spaces or other special characters, then the file name should be enclosed in quotation marks when it is used as an argument on the command line.
import java.io.*; /** * This program reports the number of lines in each of the files * that are specified as command line arguments. The files are * assumed to be text files. If a file does not exist, or if * some error occurs when the attempt is made to read the file, * then an error message is printed (but the other files are * still processed). */ public class LineCounts { /** * The main() routine simply gets the file names from the * command line and calls the countLines() routine to process * each name. Since any errors are handled in the countLines() * routine, the main program will continue after an error occurs * while trying to process one of the files. */ public static void main(String[] args) { if (args.length == 0) { // This program must have at least one command-line // argument to work with. System.out.println("Usage: java LineCounts <file-names>"); return; } for (int i = 0; i < args.length; i++) { System.out.print(args[i] + ": "); countLines(args[i]); } } // end main() /** * Count the number of lines in the specified file, and * print the number to standard output. If an error occurs * while processing the file, print an error message instead. * Two try..catch statements are used so I can give a * different error message in each case. */ private static void countLines(String fileName) { try(BufferedReader in = new BufferedReader( new FileReader(fileName) )) { int lineCount = 0; // number of lines read from the file String line = in.readLine(); // Read the first line. while (line != null) { lineCount++; // Count this line. line = in.readLine(); // Read the next line. } System.out.println(lineCount + " lines"); } catch (FileNotFoundException e) { System.out.println("Error. Can't open file."); } catch (Exception e) { System.out.println("Error. Problem with reading from file."); } } // end countLines() } // end class LineCounts