I have a problem with my code since it allows creating documents which have Strings with measurements defined by the code itself. When I try to display them on the screen, the program gives me the following error:
java.io.EOFException
at java.base/java.io.RandomAccessFile.readFully(RandomAccessFile.java:498)
at java.base/java.io.RandomAccessFile.readFully(RandomAccessFile.java:472)
at RandomFileHandlerAlex.decodeFiles(RandomFileHandlerAlex.java:104)
at RandomFileHandlerAlex.main(RandomFileHandlerAlex.java:20)
I have been looking for ways to solve it by specifying the measurement by doing an error check to see if it meets the measurement or not, but nothing has worked for me. I am attaching the code below to see if someone can help me out.
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Scanner;
public class RandomFileHandlerAlex {
public static Scanner input = new Scanner(System.in);
public static void main(String[] args) throws IOException {
int selection = -1;
while (selection != 0) {
showMenu();
selection = input.nextInt();
input.nextLine(); // Clear buffer
if (selection == 1) {
handlePopulations();
} else if (selection == 2) {
handleRegion();
} else if (selection == 3) {
decodeFiles();
} else if (selection == 4) {
modifyFifthPopulationRecord();
} else if (selection == 5) {
addPopulationOrRegion();
}
}
}
// Method to display menu options
public static void showMenu() {
System.out.println("1. Enter populations");
System.out.println("2. Enter region");
System.out.println("3. Decode file");
System.out.println("4. Edit 5th position");
System.out.println("5. Add record");
System.out.println("0. Exit");
}
// Method to handle population input and save it to a file
public static void handlePopulations() {
try (RandomAccessFile file = new RandomAccessFile("population.dat", "rw")) {
Scanner scanner = new Scanner(System.in);
System.out.println("---------Enter populations--------");
file.seek(file.length());
for (int i = 0; i < 8; i++) {
System.out.println("Enter name:");
String name = scanner.nextLine();
name = String.format("%-60s", name); // Set fixed length
System.out.println("Enter number of inhabitants:");
int inhabitants = scanner.nextInt();
System.out.println("Enter rt:");
double rt = scanner.nextDouble();
scanner.nextLine(); // Clear buffer
System.out.println("Enter postal code:");
String postalCode = scanner.nextLine();
postalCode = String.format("%-5s", postalCode);
file.writeUTF(name); // Write name as UTF-8
file.writeInt(inhabitants); // Write inhabitants
file.writeDouble(rt); // Write rt
file.writeUTF(postalCode); // Write postal code as UTF-8
}
} catch (IOException e) {
e.printStackTrace();
}
}
// Method to handle region input and save it to a file
public static void handleRegion() {
try (RandomAccessFile file = new RandomAccessFile("region.dat", "rw")) {
System.out.println("---------Enter regions--------");
file.seek(file.length());
for (int i = 0; i < 8; i++) {
System.out.println("Enter name:");
String name = input.nextLine();
name = String.format("%-30s", name); // Set fixed length
System.out.println("Enter number of inhabitants:");
int inhabitants = input.nextInt();
System.out.println("Enter rt:");
double rt = input.nextDouble();
input.nextLine(); // Clear buffer
file.writeUTF(name);
file.writeInt(inhabitants);
file.writeDouble(rt);
}
} catch (IOException e) {
e.printStackTrace();
}
}
// Method to read and display content of population file
public static void decodeFiles() {
try {
File populationFile = new File("population.dat");
if (populationFile.exists() && populationFile.isFile()) {
try (RandomAccessFile file = new RandomAccessFile(populationFile, "r")) {
while (file.getFilePointer() < file.length()) {
byte[] nameBytes = new byte[60];
file.readFully(nameBytes);
String name = new String(nameBytes).trim();
int inhabitants = file.readInt();
double rt = file.readDouble();
byte[] postalCodeBytes = new byte[5];
file.readFully(postalCodeBytes);
String postalCode = new String(postalCodeBytes).trim();
// Display read data
System.out.println("Name: " + name);
System.out.println("Inhabitants: " + inhabitants);
System.out.println("RT: " + rt);
System.out.println("Postal Code: " + postalCode);
System.out.println("---------------------------");
}
} catch (IOException e) {
e.printStackTrace();
}
} else {
System.out.println("File population.dat does not exist.");
}
} catch (Exception e) {
e.printStackTrace();
}
}
// Method to modify the fifth population record
public static void modifyFifthPopulationRecord() {
try {
File populationFile = new File("population.dat");
if (populationFile.exists() && populationFile.isFile()) {
try (RandomAccessFile file = new RandomAccessFile(populationFile, "rw")) {
// Exact size of each record (without UTF headers)
long recordSize = 60 + 4 + 8 + 5;
long recordCount = file.length() / recordSize;
if (recordCount < 5) {
System.out.println("File does not contain enough records (at least 5 required)");
return;
}
file.seek(recordSize * 4); // Position of the fifth record
byte[] nameBytes = new byte[60];
file.readFully(nameBytes);
String name = new String(nameBytes).trim();
int inhabitants = file.readInt();
double rt = file.readDouble();
byte[] postalCodeBytes = new byte[5];
file.readFully(postalCodeBytes);
String postalCode = new String(postalCodeBytes).trim();
// Modify values
inhabitants += 100;
rt -= 0.05;
// Overwrite fifth record with modified values
file.seek(recordSize * 4);
file.writeBytes(String.format("%-60s", name));
file.writeInt(inhabitants);
file.writeDouble(rt);
file.writeBytes(String.format("%-5s", postalCode));
System.out.println("Fifth record updated");
} catch (IOException e) {
e.printStackTrace();
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// Method to add a new population or region record
public static void addPopulationOrRegion() {
System.out.println("1.population 2.region");
int option = input.nextInt();
if (option == 1) {
try (RandomAccessFile file = new RandomAccessFile("population.dat", "rw")) {
Scanner scanner = new Scanner(System.in);
System.out.println("---------Enter a population--------");
file.seek(file.length());
System.out.println("Enter name:");
String name = scanner.nextLine();
name = String.format("%-60s", name);
System.out.println("Enter number of inhabitants:");
int inhabitants = scanner.nextInt();
System.out.println("Enter rt:");
double rt = scanner.nextDouble();
scanner.nextLine();
System.out.println("Enter postal code:");
String postalCode = scanner.nextLine();
postalCode = String.format("%-5s", postalCode);
file.writeUTF(name);
file.writeInt(inhabitants);
file.writeDouble(rt);
file.writeUTF(postalCode);
} catch (IOException e) {
e.printStackTrace();
}
} else if (option == 2) {
try (RandomAccessFile file = new RandomAccessFile("region.dat", "rw")) {
System.out.println("---------Enter a region--------");
file.seek(file.length());
System.out.println("Enter name:");
String name = input.nextLine();
name = String.format("%-30s", name);
System.out.println("Enter number of inhabitants:");
int inhabitants = input.nextInt();
System.out.println("Enter rt:");
double rt = input.nextDouble();
input.nextLine();
file.writeUTF(name);
file.writeInt(inhabitants);
file.writeDouble(rt);
} catch (IOException e) {
e.printStackTrace();
}
} else {
System.out.println("Invalid option");
}
}
}
The problem is caused by two factors:
First, you are using writeUtf
:
file.writeUTF(name);
As the documentation of writeUTF says:
Writes two bytes of length information to the output stream, followed by the modified UTF-8 representation of every character in the string s.
So, although your name
is padded to a fixed 60 characters long, it is always stored with at least 62 bytes, possibly more if it contains any non-ASCII characters. This means you are not using fixed-length records.
Then, you have this while loop:
while (file.getFilePointer() < file.length()) {
Your file pointer is using a multiple of what you think is a fixed size, but file.length
is actually slightly larger because of the use of writeUTF
. Therefore you enter the loop even when you have already processed the last record, and you try to read another record but there aren't enough bytes left in the file.
Note: what I've written regarding name
also applies to all strings used by your application, including post code and region.
You have many options for fixing this problem. Here are a few:
name
a fixed number of bytes (after encoding it as UTF-8), instead of a fixed number of chars. That way, you can still use writeUTF
but now you know that it will have fixed byte length in the file.For the third option, to pad name
to 60 bytes, instead of this:
String name = scanner.nextLine();
name = String.format("%-60s", name); // Set fixed length
you should do something like this:
String name = scanner.nextLine();
byte[] nameBytes = name.getBytes();
byte[] paddedNameBytes = new byte[60];
System.arraycopy(paddedNameBytes, 0, nameBytes, 0, nameBytes.length);
...
file.write(paddedNameBytes)
This uses your default charset, which could be different for each user of your program. It may be better to be explicit, so instead of name.getBytes()
do name.getBytes(StandardCharsets.UTF_8)
and when reading the file, use String(nameBytes, StandardCharsets.UTF_8).trim()
.
Note: I've left out error handling, which you should add. For example, check that the name actually fits into 60 bytes. Also, you should define a constant final static int NAME_LENGTH = 60
instead of using 60 everywhere.