0

I have the following test program that is writing out a Person record's field values using java.io.DataOutputStream and it writes them out just fine. Then, after a pause that I put in to check the data file, it is supposed to read in the same three records using java.io.DataInputStream, but it reads in the first record, the Person.id of the second record and throws the EOFException, but is not at the end of file. Here's the code:

public class BinaryFileAccessTest {

    private static File dataFile = new File(System.getProperty("user.home") + File.separator + "dataFile.dat");
    
    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        List<Person> people = new ArrayList<>();
        
        people.add(new Person("Dough", "John", new Date(70, 03, 17), 23500.00d));
        people.add(new Person("Smith", "Sean", new Date(70, 05, 06), 53900.00d));
        people.add(new Person("Carrick", "Rebecca", new Date(59, 06, 13), 20000.00d));
        
        System.out.println("From List<Person> people:");
        for (Person p : people) {
            System.out.println("\tCreated: " + p.toString());
        }
        
        try (DataOutputStream out = new DataOutputStream(new FileOutputStream(dataFile))) {
            System.out.println("Writing Person to file:");
            for (Person p : people) {
                out.writeLong(p.getId());
                out.writeUTF(p.getLastName());
                out.writeUTF(p.getFirstName());
                out.write(p.getBirthDate().toString().getBytes());
                out.writeDouble(p.getSalary());
                
                System.out.println("\tWrote: " + p.toString());
            }
        } catch (IOException ex) {
            System.err.println("An error occurred writing to " + dataFile);
            System.err.println(ex);
            ex.printStackTrace(System.err);
        }
        
        Scanner keyboard = new Scanner(System.in);
        boolean exit = false;
        while (!exit) {
            String input = keyboard.nextLine();
            if (input != null) {
                exit = true;
            }
        }
        
        try (DataInputStream in = new DataInputStream(new FileInputStream(dataFile))) {
            people.clear();
            System.out.println("Reading Person from file:");
            
            while (true) {
                Person p = new Person();
                p.setId(in.readLong());
                p.setLastName(in.readUTF());
                p.setFirstName(in.readUTF());
                p.setBirthDate(new Date(in.read()));
                p.setSalary(in.readDouble());
                
                System.out.println("\tRead: " + p.toString());
                people.add(p);
            }
        } catch (IOException ex) {
//            if (!(ex instanceof EOFException)) {
                System.err.println("An error occurred writing to " + dataFile);
                System.err.println(ex);
                ex.printStackTrace(System.err);
//            } else {
//                System.out.println("End of file reached.");
//            }
        }
        
        System.out.println("From List<Person> people:");
        for (Person p : people) {
            System.out.println("\tContains: " + p.toString());
        }
       
    }

}

The output from this program is:

From List<Person> people:
    Created: Dough, John [birth=Fri Apr 17 00:00:00 CST 1970; salary=23500.0
    Created: Smith, Sean [birth=Sat Jun 06 00:00:00 CDT 1970; salary=53900.0
    Created: Carrick, Rebecca [birth=Mon Jul 13 00:00:00 CDT 1959; salary=20000.0
Writing Person to file:
    Wrote: Dough, John [birth=Fri Apr 17 00:00:00 CST 1970; salary=23500.0
    Wrote: Smith, Sean [birth=Sat Jun 06 00:00:00 CDT 1970; salary=53900.0
    Wrote: Carrick, Rebecca [birth=Mon Jul 13 00:00:00 CDT 1959; salary=20000.0

Reading Person from file:
    Read: Dough, John [birth=Wed Dec 31 18:00:00 CST 1969; salary=1.3403241663660614E243

In the second try...catch block, the first record (for John Dough) is read in and stored in the people list just fine. On the second iteration of the while loop, it reads in the ID value for Sean Smith, then when reading in the last name, it throws the EOFException, even though it is not at the end of the file. When I look at the contents of the file during the program execution pause, it has three blocks of data, which are clearly visible:

𐀀ꑢ?ꣁ???甔? 珐????? ꗌ?쵐?

Of course, it looks different in the editor in which I look at it, but there are three clear blocks of data present. Yet, after reading the one field from the second block of data, it somehow hits the EOF.

Can anyone explain why this would be? It seems that if these classes are working correctly, when data is written out as I did, then read in by the inverse class, it would read in exactly as it was written out, so it should read in the same number of datapoints as were written.

I guess the better question is, where did I screw this up? ;)

Thank you for any help you can provide!

-SC

[EDIT] I also just noticed that John Dough's salary is not being read back in as the same value as written, neither is his birthDate. Why would this be?

Thx!

[EDIT-2] Here's the Person Java Bean:

public class Person {
    
    private long id;
    private String lastName;
    private String firstName;
    private Date birthDate;
    private double salary;
    
    public Person() {
        
    }
    
    public Person (String lastName, String firstName, Date birthDate, double salary) {
        this.id = System.currentTimeMillis();
        this.lastName = lastName;
        this.firstName = firstName;
        this.birthDate = birthDate;
        this.salary = salary;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public Date getBirthDate() {
        return birthDate;
    }

    public void setBirthDate(Date birthDate) {
        this.birthDate = birthDate;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }
    
    @Override
    public String toString() {
        return getLastName() + ", " + getFirstName() + " [birth=" + getBirthDate() + "; salary=" + getSalary();
    }

}
8
  • Please add your Person class to the question to make it a minimal reproducible example and we can be sure that the problem is not within there. Commented Apr 10, 2022 at 13:35
  • I recommend you don’t use Date. That class is poorly designed and long outdated. And even if you insist on using Date, stay far away from its 3-arg constructor. It’s been deprecated for 25 years because it works unreliably across time zones. For a birthdate use LocalDate from java.time, the modern Java date and time API. Commented Apr 10, 2022 at 14:22
  • Why are you not using ObjectOutputStream? It would make things a lot easier Commented Apr 10, 2022 at 15:47
  • @cyberbrain: The Person class is just a standard Java Bean, but I'll edit and include it as well. Commented Apr 10, 2022 at 18:33
  • 1
    @g00se: I had been serializing the data lists to file and deserializing them back into list objects, but when I got to a complicated record (approximately 40 fields), it was not working for some reason, so I thought that I would give the Data*Streams a try...The frustrations are seeming to not be worth the effort. Commented Apr 10, 2022 at 18:36

2 Answers 2

3

As per java doc, the read() method:

Reads a byte of data from this input stream. This method blocks if no input is yet available.

So you are not reading the birth date string, but the first char in your birth date string.

I suggest writing your birthdate string using writeUTF() like you did with other strings and read it back using readUTF(). Otherwise you will need to explicitly state the number of characters (bytes) in the birthdate string to be able to recover it with read(byte[] b, int off, int len).

Sign up to request clarification or add additional context in comments.

1 Comment

You are right, the read() method of FileInputStream just read one byte. I edited my answer. My second point of requiring the string length to recover it via read(byte[] b, int off, int len) remains valid, though.
2

The problem with your code is that you use a byte array to write out the date, while you read back an int that is than (implicitly) casted to a long to recreate that Date object.

You can e.g. use

out.writeLong(p.getBirthDate().getTime());

to write your birthdate and

p.setBirthDate(new Date(in.readLong()));

to read it back.

But I actually would recommend that you use java.io.ObjectOutputStream to write and java.io.ObjectInputStream to read back your data. To use them, you will have to either add implements java.io.Serializable to your Person class (and nothing more), or you implement the interface java.io.Externalizable with its methods readExternal and writeExternal and maybe don't use binary representation for the contents of that file, but a String representation like XML, JSON, YAML or anything like that.

Then you can use the writeObject and readObject methods of the according streams once per object to write or read.

Please note that the code you are using for the test will always throw an EOFException once it has read back the objects you wrote because you read back in an endless loop and do neither stop after 3 records nor have written the number of objects first so you could read that back to stop. You even could define an artificial Person object as end marker after which you stop reading.

(With Object*Streams you could write out the whole ArrayList and also read it back in without any looping in your own code.)

7 Comments

I was using Object*Streams until they didn't work with a Java Bean that had around 40 fields in it. For some reason, when I'd serialize my ArrayList, it would seem to work fine (as in, no exceptions thrown). But, when I'd read the object back in using ObjectInputStream, the ArrayList would only have either the first record, or the last record, in it, for some reason depending upon whether there were an odd or even number of records stored in the ArrayList. after 10 records, only the last one would get deserialized from file...
Also, I'm trying to avoid storing in plain text (CSV, XML, JSON, etc). I know how I am about opening random files in editors to see what their made of and I want to keep the data protected from any type of modification from outside of my program...;)
Good luck with the protection part ;) If you want just to prevent accidental modifications I would recommend that you add some kind of checksum or digest to your file. To prevent intentional modification, you will have a hard time without some kind of protected environment where you can store that checksum for comparison. But that wasn't your question, sorry.
oh, and I just tested with Object*Stream to write and read the ArrayList and didn't have any problems, so I would encourage you to add another SO question with that problem.
Thanks for the luck! I just want to keep my users' data as secure as possible. To that end, the application I am creating only connects to the network to check for updates, and that's it. Personally, I have always been curious about what's in a file, so I have no problem attempting to open the file in a text editor, or just dumping its contents on the command line. Being that way myself, I am just trying to protect my data from people...well...like ME! :-P
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.