可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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:
- that isn't what exception handling is for I believe
- 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:
- '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.
- 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
- You could use
null
as EOF marker.
- 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:
- According to this documentation,
FileInputStream.available()
approximates the number of bytes that can be read without blocking
- The hard disk drive itself might change its operation mid-read (go from spinning to not spinning)
- 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?
- 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
- store the number of bytes the number itself will consume
- 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) {
// ...
}