Tuesday, October 18, 2011

Text File I/O with Strings that Contain Spaces in Java

Let's suppose that we the following class:
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.StringTokenizer;

public class Person 
{
    private String name;
    private int age;
    
    public Person(String name, int age)
    {
        this.name = name;
        this.age = age;
    }
    
    public String getName()
    {
        return name;
    }
    
    public int getAge()
    {
        return age;
    }
    
    public void writeToFile(String path) throws Exception
    {
        PrintStream fout = new PrintStream(new FileOutputStream(path));
        fout.println(name + " " + age); //Problem 1
        fout.close();
    }
    
    public void readFromFile(String path) throws Exception
    {
        BufferedReader fin= new BufferedReader(new InputStreamReader
                (new FileInputStream(path)));
        String line = fin.readLine();
        StringTokenizer strtok = new StringTokenizer(line," ");//Problem 2
        this.name = strtok.nextToken().toString();
        this.age = Integer.parseInt(strtok.nextToken().toString());
        fin.close();  
    }
    
    public String toString()
    {
        return name + " " + age;
    }
}
We want to write to a text file the name and the age of the person and then we want to read it again (if you don't know how to do that check out the Text File I/O article).

The problem is that if your Person has a name like "John Doe" of age 23, when you will read the text file, you will trigger a NumberFormat exception. Why this happens? This happens because your text file looks like this: John Doe 23. The StringTokenizer produces then 3 tokens "John", "Doe" and "23" instead the 2 tokens you expect "John Doe" "23".

First solution:
To solve this issue, you will need to replace " " from the parameters of the String Tokenizer with other regex like ";" (the delimiter must be a character that is almost never used in your String. Since most names don't contain ";", this choice is pretty good).
public void readFromFile(String path) throws IOException
    {
        BufferedReader fin= new BufferedReader(new InputStreamReader
                (new FileInputStream(path)));
        String line = fin.readLine();
        StringTokenizer strtok = new StringTokenizer(line,";");//OK
        this.name = strtok.nextToken().toString();
        this.age = Integer.parseInt(strtok.nextToken().toString());
        fin.close();
    }
You will also need to modify the writeToFile method to comply with the readFromFile method:
public void writeToFile(String path) throws IOException
    {
        PrintStream fout = new PrintStream(new FileOutputStream(path));
        fout.println(name + ";" + age +";"); //OK
        fout.close();
    }
This solution is good for simple cases, but you may not want it when you will deal with classes that are more complex.

Second solution:
The second solution consists in replacing at writing/reading time the " " characters with another less common character that is almost never found in the string.
In order to do that we will create first, a "file-friendly" overloaded version of the toString method:
public String toString(boolean isFileFriendly)
    {
        if(isFileFriendly == true)
        {
            String fileFriendlyName = name;
            fileFriendlyName = name.replaceAll(" ",";");
            return fileFriendlyName + " " + age;
        }
        else
            return name + " " + age;
    }
The next stop is the writeToFile method which now will need to call the "file-friendly" toString:
public void writeToFile(String path) throws IOException
    {
        PrintStream fout = new PrintStream(new FileOutputStream(path));
        fout.println(this.toString(true); //Calling "file-friendly" toString
        fout.close();

    }
Another similar modification to the "file-friendly" toString will be needed in the readFromFile method:
public void readFromFile(String path) throws IOException
    {
        BufferedReader fin= new BufferedReader(new InputStreamReader
                (new FileInputStream(path)));
        String line = fin.readLine();
        StringTokenizer strtok = new StringTokenizer(line," ");
        //The new token will contain the file friendly name
        //not the name you want
        String fileFriendlyName = strtok.nextToken().toString(); 
        //Transforming the file friendly name into the name you
        //will want to use later in your program
        this.name = fileFriendlyName.replaceAll(";"," "); 
        this.age = Integer.parseInt(strtok.nextToken().toString());
        fin.close();
    }

No comments:

Post a Comment

Got a question regarding something in the article? Leave me a comment and I will get back at you as soon as I can!

Related Posts Plugin for WordPress, Blogger...
Recommended Post Slide Out For Blogger