2

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'
user207421
  • 305,947
  • 44
  • 307
  • 483
askaka12
  • 195
  • 2
  • 2
  • 9
  • 2
    Handling an exception does not automatically terminate a program. – Modus Tollens Dec 30 '12 at 23:23
  • 3
    Err, wait... You iterate over the map's entry set and you use `.get()` on the map to get the value? – fge Dec 30 '12 at 23:24
  • Handling an exception WILL terminate the program because once an exception is thrown, whatever is inside the "catch" block will be executed and the program will terminate.... For example, if I call the readContact() method from some other method and try to do something, it will terminate at the readContact() method and so I won't be able to do anything else. – askaka12 Dec 30 '12 at 23:34
  • @user1938670 Yes, but you don't have to make the `try` scope include all your important code. Just use the `try` block around the code that might fail, catch the exception and provide an alternative. You can also throw up the exception and handle it in the calling code. – Modus Tollens Dec 30 '12 at 23:36
  • Since you are don't need the key when you print out, you just loop with `for(Contact con: contactMap.values())` – Peter Lawrey Dec 30 '12 at 23:37
  • 1
    Off-topic, but `in.available()` doesn't strictly do what I think you're expecting. `available()` returns the number of bytes available to read without blocking - but that's not to say (strictly speaking) that that's the size of data, or that there's no more data there. – Greg Kopff Dec 31 '12 at 02:24
  • 2
    `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` - aren't you asking a question here to _defer_ to more knowledgable individuals? – Greg Kopff Dec 31 '12 at 02:26
  • 1
    @user1938670 This is not necessarily true. Why don't you try what you are telling us not to suggest so that you can see for yourself. (As an example to try, why not just put a `return;` in the `catch(EOFException exception)` block? As a matter of fact, why not try calling some more code after that method you have provided? If this fails (which it shouldn't!), why not post a screencap (or a link to it) showing us that it did as you feared it would? – Mike Warren Aug 06 '13 at 03:38

7 Answers7

7

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.

user207421
  • 305,947
  • 44
  • 307
  • 483
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();
        }
    }
}
Modus Tollens
  • 5,083
  • 3
  • 38
  • 46
  • something smells wrong, what would happen if any other exception occurs, unconditional while & return statement in catch ? oh no... – vels4j Dec 31 '12 at 03:26
  • 1
    @vels4j There is nothing wrong with using a return statement in a catch block. Using one in the finally block would be [bad](http://stackoverflow.com/q/5701793/1288408). All the checked exceptions are handled, and because the file will have an end, the EOFException will eventually be thrown by peekByte, so... I'm not too worried. – Modus Tollens Dec 31 '12 at 03:39
2
  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.
Kalle Richter
  • 8,008
  • 26
  • 77
  • 177
Evgeniy Dorofeev
  • 133,369
  • 30
  • 199
  • 275
  • Known problem or not, even if `ObjectInputStream.available()` didn't return zero it is still not a correct test for end of stream. – user207421 Nov 17 '14 at 11:15
2

- 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.

Kumar Vivek Mitra
  • 33,294
  • 6
  • 48
  • 75
2

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

vels4j
  • 11,208
  • 5
  • 38
  • 63
0

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.

Community
  • 1
  • 1
Mike Warren
  • 3,796
  • 5
  • 47
  • 99
  • it is also worthy to note that we could have simply exception-handled with condition `exception instanceof FileNotFound;`, which would have been a bit simpler. – Mike Warren Aug 07 '13 at 01:33
  • Whatever `available()` might or might not do, it is certainly not a test for end of stream, so it is irrelevent to the question. – user207421 Jun 17 '20 at 01:29
-1

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) {
  // ...
}
fejese
  • 4,601
  • 4
  • 29
  • 36
Akshay
  • 11
  • 2
  • What if you need to write a `null` in the middle of the file? What if the file got closed prematurely for some reason? You still have to catch `EOFException` and break, otherwise your loop runs forever. – user207421 Nov 17 '14 at 11:12
  • Considered that objects wont be null, and if file ends prematurely,it is most probably corrupt. – Akshay Nov 19 '14 at 11:36
  • @Ashkay But objects *can* be null, and removing that option from the API is a pointless restriction, unless you think your aim in life is to stamp out exceptions, instead of doing useful work. – user207421 Sep 18 '16 at 09:27