在存储和内存方面为浮点型或双倍型



我正在解析精度不是我主要关注的数据。我经常得到java.lang.OutOfMemoryError,即使我使用最大Java堆大小。所以我在这里主要关心的是内存使用和 java 堆空间。我应该使用双精度数据类型还是浮点数据类型?

我经常收到 OOM 异常,因为我使用大量带有数字的 ArrayLists。

好吧,那是你的问题!

N 个 32 位
  • 浮点值的ArrayList在 32 位 JVM 中至少需要120 * N 个字节,在 64 位 JVM2中至少需要 24 * N 个字节。

  • N 个 64 位浮点值的ArrayList占用的空间量相同3

  • 以上仅考虑后备数组和列表元素。 如果有大量的小ArrayList对象,则ArrayList对象本身的开销可能很大。 (为每个ArrayList对象添加 16 或 24 个字节。

  • 如果使用动态调整大小,则可能会随着后备数组的增长而生成对象改动。 在某些时候,后备阵列的大小可能是其需要的两倍。

相比之下:

  • 一个 32 位浮点值数组大约需要 4 * N 个字节4

  • 一个 64 位浮点值数组大约需要 8 * N 个字节4

  • 不会因动态调整大小而造成浪费。 但是您需要在分配时指定正确的大小。

解决 方案:

  1. ArrayList<Float>ArrayList<Double>没有区别。 这不是一个解决方案

  2. 为了最大限度地节省成本,请根据您的精度要求使用float[]double[]。 预分配数组以保存所需元素的确切数量。

  3. 如果您想要动态调整大小的灵活性,可以使用第三方库来实现节省空间的基元类型列表。 或者实现您自己的。 但是,您将无法使用标准的List<...>API,因为这会迫使您走上使用FloatDouble的道路。


1 - 实际使用的空间取决于ArrayList的创建和填充方式。 如果您预先分配了具有正确容量的ArrayList,您将使用我上面所说的空间。 如果通过重复追加到具有缺省初始容量的ArrayList来构建阵列,那么对于 32 位 JVM,您将平均使用 N * 2 字节的额外空间。 这是由于ArrayList用于在后备阵列已满时增大后备阵列的启发式方法。
2 - 在 64 位 JVM 上,指针占用 8 个字节而不是 4 个字节 ...除非您使用的是压缩的 OOP。
3 - 它需要相同数量的字节的原因是,在典型的 JVM 上,由于堆节点填充,FloatDouble都是 16 字节。
4 - 每个阵列的标头开销(通常)为 12 个字节,并且阵列的堆节点大小填充为 8 字节的倍数。

如果您的内存使用量与大量(数百万)浮点数相关(可以使用体面的内存分析器进行验证),那么您很可能将它们存储在一些数据结构中,如数组或列表。

建议(我猜,您已经遵循了其中的大部分...

  • 如果数字范围和精度足够,则更喜欢float而不是double,因为这只消耗一半的大小。
  • 不要使用java.lang.Floatjava.lang.Double类进行存储,因为与裸标量值相比,它们具有相当大的内存开销。
  • 确保使用数组,而不是像java.util.List这样的集合,因为它们存储盒装java.lang.Float实例而不是裸数字。

但除此之外,有一个不错的内存分析器向您展示哪些实例占用了大部分内存。也许除了浮点/双精度数据之外还有其他内存消费者。

编辑:

OP最近的评论"我总是得到OOM异常,因为我使用大量带有数字的ArrayList"清楚地表明了这一点。 与float[]相比,ArrayList<Float>浪费了大量内存(Stephen C 在他的答案中给出了详细的数字),但提供了动态调整大小的好处。

因此,我看到了以下可能性:

  • 如果可以从一开始就分辨数组大小,请立即使用float[]数组。
  • 如果在初始化实例时需要动态大小,请在构建一个对象时使用ArrayList<Float>(当大小仍然增加时),然后将内容复制到float[]数组中以便长期存储。那么浪费的 ArrayList 只存在于有限的时间跨度内。
  • 如果您需要在数据的整个生命周期内动态大小,请基于float[]数组创建自己的FloatArrayList类,类似于代码需要的ArrayList<Float>(范围可以从非常浅的实现到功能齐全的列表,可能基于AbstractList)。

最新更新