【问题标题】:Java: OutOfMemory exception when trying to read objects from a very large fileJava:尝试从非常大的文件中读取对象时出现 OutOfMemory 异常
【发布时间】:2020-03-15 08:38:18
【问题描述】:

我使用下面的代码在一个文件中保存了近 10k 个对象:

boolean exists = Tools.getDeviceInfo().exists();
        FileOutputStream fos = new FileOutputStream(Tools.getDeviceInfo(), true);
        ObjectOutputStream oos = exists ?
                new ObjectOutputStream(fos) {
                    protected void writeStreamHeader() throws IOException {
                        reset();
                    }
                }:new ObjectOutputStream(fos);
        oos.writeObject(deviceInfos);

现在,当我尝试读取文件时,它会抛出 OutOfMemory 异常。我该如何解决这个问题。下面是读取对象的代码。

FileInputStream fis = new FileInputStream(Tools.getDeviceInfo());
        ObjectInputStream ois = new ObjectInputStream(fis);
        ArrayList<DeviceInfo> arrayList = new ArrayList<>();
        try {
            arrayList.addAll((ArrayList<DeviceInfo>)ois.readObject());
        } catch (Exception e) {
            e.printStackTrace();
        }
        ois.close();
        return arrayList;

我想读取所有对象的原因是我想在 tableview 中显示所有数据,所以我需要将所有(10k 行)添加到 arraylist 并在 tableview 上显示。 有没有限制 readObject() 的选项,所以它只能从一个非常大的文件中检索几个对象。 我怎样才能改善这一点?请帮忙。

【问题讨论】:

    标签: java arrays arraylist out-of-memory fileinputstream


    【解决方案1】:

    您可以一次性对整个集合进行序列化和反序列化。如果您编写单个对象(而不是包含 ArrayList),您将能够一次读取一个范围。

    【讨论】:

      【解决方案2】:

      你有点吃不消了。

      看起来您已将所有对象放入一个列表中,然后使用单个 writeObject 调用将该列表保存到文件中。

      如果您这样做,您别无选择,只能回读整个列表。我想,可以想象,您可以实现一个 hack(使用自定义读取对象方法或类似方法)以将不需要的 DeviceInfo 对象“读取”为 null 以节省空间。但这可能有不利的一面。

      一种解决方案是通过单独序列化元素来序列化原始列表。然后你可以一次读一遍,忽略那些你不想要的。它效率不高,但您应该能够避免用当前不需要的对象填充堆。

      更好的解决方案是使用另一种保存数据的方法:

      • 您可以使用 RDBMS、NoSQL 数据库或平面文件数据库(如 BerkleyDB)并只选择要显示的行。

      • 您可以使用另一种序列化格式,例如 JSON、XML 或 CSV,它们具有流式 API。然后您流式传输整个文件并仅具体化您想要显示的元素。

      最好有某种数据库。

      【讨论】:

      • 嗯。有道理,现在我要将我的数据存储在本地数据库(h2 数据库)中。我可以通过流 api 从 H2 数据库中读取数据吗?因为我想读取所有行并将其显示在表格上
      • 当然。 H2 支持 JDBC 和 JDBC 结果集流行。但你可以做得更好。您可以只选择要显示的行。
      【解决方案3】:

      要从文件中读取 10k 个对象,请尝试使用 java.util.scanner

      FileInputStream fInputStream = null;
          Scanner scanner = null;
          try {
              fInputStream = new FileInputStream(path);//file path
              scanner = new Scanner(fInputStream, "UTF-8");
               ArrayList<DeviceInfo> arrayList = new ArrayList<>();
              while (scanner.hasNextLine()) {
              DeviceInfo dObj = new DeviceInfo();
              dObj = scanner.nextLine();
              arrayList.addall(dObj);
              }
              // note that Scanner suppresses out of memory exception
              if (scanner.ioException() != null) {
                  throw scanner.ioException();
              }
          } finally {
              if (fInputStream != null) {
                  fInputStream.close();
              }
              if (scanner != null) {
                  scanner.close();
              }
              }
      

      上面的代码将有助于处理大文件中的行(每行一个对象),而无需迭代,不会耗尽可用内存。

      【讨论】:

      • 您不能使用Scanner 来读取使用ObjectOutputStream 编写的对象。它是一种二进制格式。而且,如果您要提出替代表示,则有比“将其写为文本行”更好的方法……然后编写一个手工构建的解析器来解析这些行。
      • 我尝试了这种方法,但这不起作用,因为我使用了 ObjectOutputStream 并保存了一个对象集合。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-04-09
      • 1970-01-01
      • 2018-04-28
      • 2011-09-17
      • 2018-10-09
      相关资源
      最近更新 更多