我正在解析精度不是我主要关注的数据。我经常得到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。
不会因动态调整大小而造成浪费。 但是您需要在分配时指定正确的大小。
解决 方案:
ArrayList<Float>
与ArrayList<Double>
没有区别。 这不是一个解决方案为了最大限度地节省成本,请根据您的精度要求使用
float[]
或double[]
。 预分配数组以保存所需元素的确切数量。如果您想要动态调整大小的灵活性,可以使用第三方库来实现节省空间的基元类型列表。 或者实现您自己的。 但是,您将无法使用标准的
List<...>
API,因为这会迫使您走上使用Float
或Double
的道路。
1 - 实际使用的空间取决于ArrayList
的创建和填充方式。 如果您预先分配了具有正确容量的ArrayList
,您将使用我上面所说的空间。 如果通过重复追加到具有缺省初始容量的ArrayList
来构建阵列,那么对于 32 位 JVM,您将平均使用 N * 2 字节的额外空间。 这是由于ArrayList
用于在后备阵列已满时增大后备阵列的启发式方法。
2 - 在 64 位 JVM 上,指针占用 8 个字节而不是 4 个字节 ...除非您使用的是压缩的 OOP。
3 - 它需要相同数量的字节的原因是,在典型的 JVM 上,由于堆节点填充,Float
和Double
都是 16 字节。
4 - 每个阵列的标头开销(通常)为 12 个字节,并且阵列的堆节点大小填充为 8 字节的倍数。
如果您的内存使用量与大量(数百万)浮点数相关(可以使用体面的内存分析器进行验证),那么您很可能将它们存储在一些数据结构中,如数组或列表。
建议(我猜,您已经遵循了其中的大部分...
- 如果数字范围和精度足够,则更喜欢
float
而不是double
,因为这只消耗一半的大小。 - 不要使用
java.lang.Float
或java.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
)。