javadocumentrandomaccessfilefixed-length-record

Problems reading a file using java


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");
        }
    }
}


Solution

  • 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:

    1. Instead of using a binary file with fixed-size records, use a JSON file, and use one of the many available JSON libraries to write and read it.
    2. Instead of using a RandomAccessFile, use ObjectOutputStream.
    3. If you must use fixed-size records, make your 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.