Reading Objects until End of File in java

2019-07-25 17:14发布

问题:

I'm trying to write a program where the user can: 1) Add a person to the contact (name, phone, email), 2) Remove a person from the contacts, 3) Read all from contact.

The Way I'm doing this is I'm asking for the user for their choice and respectively does whatever. For writing, I simply write an object to the file. For removing, I think I'll be asking the user for "last name" which will be used as the KEY (since I'm using a TreeMap)and will remove the value (object) at the key.

So I'm having a problem with reading here. I'm trying to read the object like so:

public void readContact()
{
  TreeMap<String, Contact> contactMap = new TreeMap<String, Contact>();
  try 
  {
    ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(
                                                   new FileInputStream(file)));

    while( in.available() > 0 ) //This line does NOT read  
    {
      Contact c = (Contact)in.readObject();
      contactMap.put(c.getLastName(), c);           
    }

    for(Map.Entry contact : contactMap.entrySet() ) 
    {
      Contact con = contactMap.get( contact.getKey() );
      System.out.println( con.getLastName() + ", " + con.getFirstName() + ": " + con.getPhoneNumber() + "\t" + con.getEmail());
    }
  }
  catch(Exception e)
  {
    System.out.println("Exception caught");
  } 
}

Please do not suggest doing something like while(true) until I get the EOFException because:

  1. that isn't what exception handling is for I believe
  2. I still have more things to do after this so I can't have the program terminating'

回答1:

Please do not suggest doing something like while(true) until I get the EOFException

That is exactly what I suggest. When you are searching for answers it is counter-productive to circumscribe the solution space according to arbitrary criteria like this.

because:

that isn't what exception handling is for I believe

When an API that you are calling throws an exception, as this one does, you don't have any choice but to catch it. Whatever you may think about 'what exception handling is for', you are subject to what the designers of the API thought when they designed the API.

I still have more things to do after this so I can't have the program terminating'

So don't terminate it. Catch EOFException, close the input, and break out of the loop.

I have seen more costly programming time wasted over 'what exception handling is for' than I can really credit.



回答2:

I know that you are looking for an answer that is not using exception handling, but I believe in this case using EOFException to determine when all input has been read is the right way.

The JavaDoc of EOFException states that

This exception is mainly used by data input streams to signal end of stream. Note that many other input operations return a special value on end of stream rather than throwing an exception.

So, there are input streams that use other means to signal an end of file, but ObjectInputStream#readObject uses ObjectInputStream$BlockDataInputStream#peekByte to determine if there is more data to read, and peekByte throws an EOFException when the end of the stream has been reached.

So it is feasible to use this exception as an indicator that the end of the file has been reached.

To handle the exceptions without interrupting the program flow, some of the possible exceptions should be passed up in the hierarchy. They can be handled by a try - catch block in the code that calls readContact().

The EOFException can simply be used as an indicator that we are done reading the objects.

public TreeMap<String, Contact> readContact() throws FileNotFoundException,
            IOException, ClassNotFoundException {

    TreeMap<String, Contact> contactMap = new TreeMap<String, Contact>();

    // The following call can throw a FileNotFoundException or an IOException.
    // Since this is probably better dealt with in the calling function, 
    // readContact is made to throw these exceptions instead.
    ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(
                new FileInputStream(file)));

    while (true) {
        try {
            // Read the next object from the stream. If there is none, the
            // EOFException will be thrown.
            // This call might also throw a ClassNotFoundException, which can be passed
            // up or handled here.
            Contact c = (Contact) in.readObject();
            contactMap.put(c.getLastName(), c);

            for (Map.Entry<String, Contact> contact : contactMap.entrySet()) {
                Contact con = contact.getValue();
                System.out.println(con.getLastName() + ", "
                          + con.getFirstName() + ": " + con.getPhoneNumber()
                          + "\t" + con.getEmail());
            }
        } catch (EOFException e) {
            // If there are no more objects to read, return what we have.
            return contactMap;
        } finally {
            // Close the stream.
            in.close();
        }
    }
}


回答3:

- Exceptions are not only used in order to raise an alarm when something goes wrong while calling a method, but are also used in Threads and IO with various other uses.

- You can use Exception to indicate end of the file.

- Use the try-catch combo to work along with the above to keep the flow of the program smooth.



回答4:

Why so much trouble while reading object from file, just save a hash map into a file and read the same once from file then perform any operation.

Also I would suggest to use any one of object oriented database like Db4o to do this quickly then you never worry about end of file exception



回答5:

  1. 'ObjectInputStream.available returns 0' is a known problem, see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4954570, and since we cannot use it I think EOFException would be a reasonable approach in your situation. Catching EOFExcepion will not terminate your program.
  2. You could write the number of objects to your file with ObjectOutputStream.writeInt, and then you would read this number with ObjectInputStream.readInt and know how many objects to read
  3. You could use null as EOF marker.
  4. You could save your objects as an array or List or even Map and then read them with one readObject.


回答6:

What you have discovered

You found out about FileInputStream.available() returning 0 even though there are unread bytes in the file! Why is this? This could happen (rendering FileInputStream.available() unreliable for a couple of reasons:

  1. According to this documentation, FileInputStream.available() approximates the number of bytes that can be read without blocking
  2. The hard disk drive itself might change its operation mid-read (go from spinning to not spinning)
  3. The file you are trying to access is either a file on a remote system or a device file : May the FileInputStream.available foolish me?
  4. The FileInputStream might be blocked

Some alternative way

As an alternative to relying on EOFException to close the file, you could use a (very-small!) binary file that keeps track of the number of Objects in your file. (Judging from your code, it looks like you are simply writing Objects to the file.) The way I have used this is just to

  1. store the number of bytes the number itself will consume
  2. using that number of bytes, store the number itself

For example, the first time the serialization file is created, I could make the binary file store 1 1 (which specifies that the number of Objects in the serialization file takes up 1 byte, and that number is 1). This way, after 255 Objects (remember, an unsigned byte can only store up to 28-1 == 255), if I write another Object (Object number 256 up to 2562-1 == 65535), the binary file will have, as contents, 2 1 0, which specifies that the number takes up 2 bytes and is 1*2561+0 == 256. Provided that the serialization is reliable (good luck on ensuring that: http://www.ibm.com/developerworks/library/j-serialtest/index.html), this method will let you store (and detect) up to 256255-1 bytes (which pretty much means that this method works indefinitely).


The code itself

How something like that would be implemented would be something like:

ObjectOutputStream yourOutputStream = new ObjectOutputStream(new FileOutputStream(workingDirectory + File.separatorChar + yourFileName);  //The outputStream
File binaryFile = new File(workingDirectory + File.separatorChar + nameOfFile); //the binary file
int numOfObjects = 0, numOfBytes; //The number of Objects in the file
//reading the number of Objects from the file (if the file exists)
try
{
    FileInputStream byteReader = new FileInputStream(binaryFile);
    numOfBytes = byteReader.read();
    //Read the rest of the bytes (the number itself)
    for (int exponent = numOfBytes; exponent >= 0; exponent--)
    {
        numOfObjects += byteReader.read() * Math.pow(256,exponent);
    }
}
catch (IOException exception)
{
    //if an exception was thrown due to the file not existing
    if (exception.getClass() == FileNotFoundException.class)
    {
        //we simply create the file (as mentioned earlier in this answer)
         try 
         {
             FileOutputStream fileCreator = new FileOutputStream(binaryFile);
             //we write the integers '1','1' to the file 
             for (int x = 0; x < 2; x++) fileCreator.write(1);
             //attempt to close the file
             fileCreator.close();
         }
         catch (IOException innerException)
         {
              //here, we need to handle this; something went wrong
              innerException.printStackTrace();
              System.exit(-1);
         }
    }
    else
    {
         exception.printStackTrace();
         System.exit(-2);
    }
}

Now, we have the number of Objects in the file (I leave it to you to figure out how to update the bytes to indicate that one more Object has been written when yourOutputStream calls writeObject(yourObject);; I have to go clock in.)

Edit: yourOutputStream is either going to write over all the data in the binaryFile or append data to it. I just found out that RandomAccessFile would be a way to insert data into anywhere in the file. Again, I leave details to you. However you want to do it.



回答7:

You can write the last object as null. And then iterate till you get a null at the reading side. e.g.

while ((object = inputStream.readObject()) != null) {
  // ...
}