【问题标题】:Reading Objects until End of File in java在java中读取对象直到文件结束
【发布时间】:2012-12-30 23:19:22
【问题描述】:

我正在尝试编写一个程序,用户可以:1) 将一个人添加到联系人(姓名、电话、电子邮件),2) 从联系人中删除一个人,3) 从联系人中读取所有内容。

我这样做的方式是要求用户做出选择,然后分别做任何事情。对于写作,我只是将一个对象写入文件。对于删除,我想我会询问用户“姓氏”,它将用作 KEY(因为我使用的是 TreeMap)并将删除键处的值(对象)。

所以我在这里阅读时遇到了问题。我正在尝试像这样读取对象:

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

在我收到EOFException 之前,请不要建议执行while(true) 之类的操作,因为:

  1. 我相信这不是异常处理的目的
  2. 在此之后我还有更多事情要做,所以我不能让程序终止'

【问题讨论】:

  • 处理异常不会自动终止程序。
  • 呃,等等...您遍历地图的条目集并在地图上使用.get() 来获取值?
  • 离题,但in.available() 并没有严格按照我认为您的预期进行。 available() 返回可在不阻塞的情况下读取的字节数 - 但这并不是说(严格来说)这是数据的大小,或者那里没有更多数据。
  • 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 - 你在这里问问题不是为了推迟给更有知识的人吗?
  • @user1938670 这不一定是真的。您为什么不尝试您告诉我们不要建议的内容,以便您自己看到。 (作为一个尝试的例子,为什么不把return; 放在catch(EOFException exception) 块中?事实上,为什么不尝试在你提供的方法之后调用更多代码?如果失败(它应该' t!),为什么不发布一个屏幕截图(或指向它的链接),向我们展示它做了你担心的事情?

标签: java serialization objectinputstream eofexception


【解决方案1】:

在我收到 EOFException 之前,请不要建议执行类似 while(true) 的操作

这正是我的建议。当您搜索答案时,根据这样的任意标准来限制解决方案空间会适得其反。

因为:

我相信这不是异常处理的目的

当您调用的 API 抛出异常时,就像这个一样,您别无选择,只能捕获它。无论您对“异常处理的用途”有何看法,API 的设计者他们设计 API 时都会考虑到您的想法。

在这之后我还有很多事情要做,所以我不能让程序终止'

所以不要终止它。捕获EOFException, 关闭输入,然后跳出循环。

我看到在“异常处理的用途”上浪费的编程时间比我真正相信的要多。

【讨论】:

    【解决方案2】:

    我知道您正在寻找不使用异常处理的答案,但我相信在这种情况下使用EOFException 来确定何时读取所有输入是正确的方法。

    EOFException 的 JavaDoc 声明

    此异常主要用于数据输入流来表示流结束。请注意,许多其他输入操作在流结束时返回一个特殊值,而不是引发异常。

    因此,有些输入流使用其他方式表示文件结束,但ObjectInputStream#readObject 使用ObjectInputStream$BlockDataInputStream#peekByte 来确定是否还有更多数据要读取,而peekByte 在结束时抛出EOFException已达到流的数量。

    因此可以将此异常用作已到达文件末尾的指示符。

    为了在不中断程序流程的情况下处理异常,一些可能的异常应该在层次结构中向上传递。它们可以由调用readContact() 的代码中的try - catch 块处理。

    EOFException 可以简单地用作我们已完成读取对象的指示器。

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

    【讨论】:

    • 有些东西闻起来不对劲,如果发生任何其他异常,catch 中的无条件 while 和 return 语句会怎样?哦不...
    • @vels4j 在 catch 块中使用 return 语句没有任何问题。在 finally 块中使用一个将是bad。所有checked的异常都处理好了,而且因为文件会有结束,EOFException最终会被peekByte抛出,所以……我也不是太担心。
    【解决方案3】:

    -Exceptions不仅用于在调用方法时出现问题时发出警报,还用于Threads和@ 987654323@ 有各种其他用途。

    -您可以使用Exception 表示文件结束

    - 使用try-catch 组合来配合上述操作,以保持程序流畅。

    【讨论】:

      【解决方案4】:

      为什么从文件读取对象时这么麻烦,只需将哈希映射保存到文件中,然后从文件中读取一次,然后执行任何操作。

      另外,我建议使用任何一种面向对象的数据库,例如 Db4o 来快速完成此操作,这样您就不必担心文件结束异常

      【讨论】:

        【解决方案5】:
        1. 'ObjectInputStream.available 返回 0' 是一个已知问题,请参阅 http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4954570,由于我们无法使用它,我认为 EOFException 在您的情况下是一种合理的方法。捕获EOFExcepion 不会终止您的程序。
        2. 您可以使用ObjectOutputStream.writeInt 将对象数量写入文件,然后使用ObjectInputStream.readInt 读取此数字并知道要读取多少对象
        3. 您可以使用 null 作为 EOF 标记。
        4. 您可以将对象保存为数组或列表甚至映射,然后使用readObject 读取它们。

        【讨论】:

        • 已知问题与否,即使ObjectInputStream.available() 没有返回零,它仍然不是流结束的正确测试。
        【解决方案6】:

        你的发现

        您发现 FileInputStream.available() 返回 0 即使文件中有未读字节!为什么是这样?这可能会发生(导致 FileInputStream.available() 不可靠有几个原因:

        1. 根据这个documentationFileInputStream.available() 近似可以在不阻塞的情况下读取的字节数
        2. 硬盘驱动器本身可能会在读取过程中改变其操作(从旋转变为不旋转)
        3. 您尝试访问的文件是远程系统上的文件或设备文件:May the FileInputStream.available foolish me?
        4. FileInputStream 可能被阻止

        一些替代方法

        作为依赖 EOFException 关闭文件的替代方法,您可以使用(非常小的!)二进制文件来跟踪文件中的对象数量。 (从您的代码来看,您似乎只是将对象写入文件。)我使用它的方式只是为了

        1. 存储数字本身将消耗的字节数
        2. 使用该字节数,存储该数字本身

        比如第一次创建序列化文件,我可以让二进制文件存储1 1(指定序列化文件中的Objects个数占1个字节,这个个数为1)。这样,在 255 个对象之后(记住,一个无符号字节最多只能存储 28-1 == 255),如果我写另一个对象(对象编号 256 到 2562-1 == 65535),二进制文件将有,作为内容,2 1 0,它指定数字占用 2 个字节,是 1*2561+0 == 256。前提是序列化是可靠的(祝你好运确保:http://www.ibm.com/developerworks/library/j-serialtest/index.html),此方法将允许您存储(和检测)最多 256255-1 个字节(这几乎意味着此方法可以无限期地工作)。


        代码本身

        如何实现类似的东西是这样的:

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

        现在,我们有了文件中对象的数量(我留给你弄清楚如何更新字节以指示当yourOutputStream 调用writeObject(yourObject); 时又写入了一个对象;我得走了打卡)

        编辑:yourOutputStream 要么将覆盖 binaryFile 中的所有数据,要么将数据附加到它。我刚刚发现RandomAccessFile 是一种将数据插入文件中任何位置的方法。再一次,我把细节留给你。不管你想做什么。

        【讨论】:

        • 还值得注意的是,我们可以简单地使用条件exception instanceof FileNotFound; 进行异常处理,这样会更简单一些。
        • 无论available() 可能会或可能不会做,它肯定不是对流结束的测试,因此与问题无关。
        【解决方案7】:

        您可以将最后一个对象写为 null。 然后迭代直到你在阅读端得到一个空值。 例如

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

        【讨论】:

        • 如果需要在文件中间写一个null怎么办?如果文件由于某种原因过早关闭怎么办?您仍然必须抓住 EOFException 并中断,否则您的循环将永远运行。
        • 考虑到对象不会为空,如果文件过早结束,很可能是损坏的。
        • @Ashkay 但是对象 可以 为空,从 API 中删除该选项是毫无意义的限制,除非您认为您的生活目标是消除异常,而不是做有用的工作。
        猜你喜欢
        • 2020-09-22
        • 1970-01-01
        • 2012-10-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-01-25
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多