'Binary File Input/Output with Data*Stream Classes
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();
}
}
Solution 1:[1]
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).
Solution 2:[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.)
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | cyberbrain |
