Important: We will reveal the full solution here,
more or less. However, in each step before we reveal the
next step, we're going to ask you to give it a try. You will
learn
Let's start with some ideas:
- What's nice in this problem is that the two methods seem
relatively independent. That is, one we call
openFile()
once, we don't need it again.
- We already know how to read a file line-by-line. How do
we turn this into delivering char-by-char?
- We need to hold on to a line after we've pulled it from a
file, and then deliver chars in that line one by one.
- This means that each time
getChar()
is called, we have to keep track of where in the
line we are.
Let's flesh this out with a little code:
- First, we'll tackle
openFile()
static void openFile (String filename)
{
try {
// Make a file scanner as in Module 7.
Scanner fileScanner = new Scanner (new File (filename));
}
catch (IOException e) {
System.out.println ("Could not open file: " + filename);
System.exit (0);
}
}
- At this moment, all we've done is create a scanner that can
pull from the file.
- It will turn out that the above is incomplete, but let's get
to that later.
- Now let's work on
getChar():
static char getNextChar ()
{
// Is this enough?
String line = fileScanner.nextLine ();
// What char do we return?
}
- Recall: the purpose of
getChar()
is to return the next char so that it can be called
repeatedly as in:
while (c != '#') {
System.out.print (c);
c = getNextChar ();
}
A2.2 Exercise:
Before you read further, try to puzzle out what else needs
to be in
getChar().
There is another issue to worry about:
- Let's go back to the code inside
openFile():
static void openFile (String filename)
{
try {
// Make a file scanner as in Module 7.
Scanner fileScanner = new Scanner (new File (filename));
}
catch (IOException e) {
System.out.println ("Could not open file: " + filename);
System.exit (0);
}
}
The variable
fileScanner
is declared inside the method
openFile(),
which means we can't access this variable inside
getChar()
as we'd hoped to:
static char getNextChar ()
{
String line = fileScanner.nextLine ();
}
- The solution? Declare the variable as global so that it
can be shared across the two methods.
- There's a second aspect to this problem, which we'll
illustrate with the line
static char getNextChar ()
{
String line = fileScanner.nextLine ();
}
- Here, the variable
line
is declared inside the method
getChar(),
which means that it "disappears" after each call
to
getChar().
- Thus, this variable also needs to be made global.
Now, let's put the above two pieces together:
public class ReadFileByChars {
static Scanner fileScanner;
static String line;
static void openFile (String filename)
{
try {
// Make a file scanner as in Module 7.
fileScanner = new Scanner (new File (filename));
}
catch (IOException e) {
System.out.println ("Could not open file: " + filename);
System.exit (0);
}
}
static char getNextChar ()
{
// What do we do with this? And when do we return a char?
line = fileScanner.nextLine ();
// This won't work:
char c = line.charAt(0);
return c;
}
}
We need to keep track of where we are in the line: another global
variable is needed:
- Let's keep a global variable that tracks which char in the current line
is next to return.
- We'll start this at 0, then gradually increment until the
end of the line, something like
static char getNextChar ()
{
char c = line.charAt (currentPositionInLine);
currentPositionInLine ++;
if ( ... reached end of line ...) {
// Get the next line.
line = fileScanner.nextLine ();
}
return c;
}
A2.3 Exercise:
Examine the code in
getChar()
and answer these questions:
- How do we know if we've reached the end of the line?
- Suppose we've reached the end of the line. Then, inside
the if-block, we're reading the next line from the file.
How do we know there are more lines to read? And if there are more
lines, when and where do we read them?
- What would happen if, instead of at the end of
getChar()
the return statement occured in the second line:
static char getNextChar ()
{
char c = line.charAt (currentPositionInLine);
return c;
currentPositionInLine ++;
if ( ... reached end of line ...) {
// Get the next line.
line = fileScanner.nextLine ();
}
}
We're now in a position to address these issues:
public class ReadFileByChars {
static Scanner fileScanner;
static String line;
static int currentPositionInLine = 0;
static void openFile (String filename)
{
try {
// Make a file scanner as in Module 7.
fileScanner = new Scanner (new File (filename));
}
catch (IOException e) {
System.out.println ("Could not open file: " + filename);
System.exit (0);
}
}
static char getNextChar ()
{
char c = line.charAt (currentPositionInLine);
currentPositionInLine ++;
if ( currentPositionInLine >= line.length() )
// Get the next line if there are any.
if ( fileScanner.hasNextLine() ) {
line = fileScanner.nextLine ();
}
else {
// Nothing further so return '#', our special END marker:
return '#';
}
}
return c;
}
A2.4 Exercise:
There are two problems with the code above.
Before reading further, can you look over and try to find them?
We're not going to address these here, but will do later.
The harder parts
One problem with the above approach:
- We signal the end of "chars" with a special char, the '#' character.
- But what if the '#' was in the file itself?
- In fact, since any char could be in the file, how do we
signal the end has been reached?
- There are two ways typically used for this type of problem:
- Use two methods, one to check whether the end has been
reached, and the other to get the chars.
- Use something that's fully outside the range of regular chars.
- The former approach is the one used by
Scanner
itself.
- We'll use the latter approach.
We will exploit the fact that all chars are int's but not vice-versa:
- This means we can use a non-char integer (like 0) to signal the end.
- Here's what it would look like in the loop that calls
getChar():
openFile ("somefile.txt");
int i = getNextChar ();
while (i >= 0) {
// Convert int to char:
char c = (char) i;
System.out.print (c);
i = getNextChar ();
}
- Because -1 does not correspond to any char, we are using
it as the END signal.
Here's the program:
import java.util.*;
import java.io.*;
public class ReadFileByChar {
static Scanner fileScanner = null;
static String line = null;
static int currentPositionInLine = 0;
public static void main (String[] argv)
{
// Set up the scanner for this file and get the first line:
openFile ("somefile.txt");
// Loop through one char at a time
int i = getNextChar ();
while (i >= 0) {
char c = (char) i;
System.out.print (c);
i = getNextChar ();
}
System.out.println ();
}
static void openFile (String filename)
{
try {
fileScanner = new Scanner (new File (filename));
// Start by getting the first line:
if (fileScanner.hasNextLine()) {
line = fileScanner.nextLine ();
}
}
catch (IOException e) {
System.out.println ("Could not open file: " + filename);
System.exit (0);
}
}
static int getNextChar ()
{
// If we're already at the end, look for the next line.
if (currentPositionInLine >= line.length()) {
if (fileScanner.hasNextLine()) {
line = fileScanner.nextLine ();
currentPositionInLine = 0;
}
else {
// If no more lines, we're done.
return -1;
}
}
char c = line.charAt (currentPositionInLine);
currentPositionInLine ++;
return (int) c;
}
}
A2.5 Exercise:
There is one bug in the above program. Write it up, and as you do so,
add proactive println's to identify and fix the bug.
Back to the assignment