谈到 Java 的基础数据类型(Primitive Data Types),可以找到很多关于其种类以及内存大小的文章。基础数据类型对应的包装类(Wrapper Classes)也会介绍自动装箱(Autoboxing)和拆箱(Unboxing),以及带来的效率上的影响。但可能是因为Java一般所使用的环境都有比较充足的内存,似乎从来没有人讨论过每种包装类所占的内存大小以及对内存溢出可能造成的影响。
到了 Android 应用程序开发领域,由于设备硬件本身以及系统对各个程序在资源使用上的约束,就会出现很多特别的使用习惯,比如推荐使用Typedef来替代枚举类型(enum)来减少内存使用和方法数。因此关于基础数据类型的包装类所占内存大小就值得写篇博客来研究一下。
包装类所占内存大小
网上我找了很多计算类内存大小的方法,但是既然我是 Android 工程师,我就用 Android Studio 提供的 Memory Profiler 来查看内存使用情况
下边就是我用来做内存检查的类,它包含了所有基础数据类型和对于的包类型
[codesyntax lang=”java” lines=”normal”]
public class Primitives { boolean pBoolean; byte pByte; char pChar; double pDouble; float pFloat; int pInt; long pLong; short pShort; Boolean cBoolean; Byte cByte; Character cChar; Double cDouble; Float cFloat; Integer cInt; Long cLong; Short cShort; public Primitives() {} public Primitives(Void v) { cBoolean = false; cByte = 0; cChar = '\0'; cDouble = 0.0; cFloat = 0.0f; cInt = 0; cLong = 0L; cShort = 0; } }
[/codesyntax]
接着在 MainActivity 中创建两个 Primitives 类的字段:一个没有初始化对象类型,一个将所有对象类型初始化成默认值。然后运行程序,在 Android Studio 中打开 Profiler 查看内存时”Dump Java heap” 就可以看到这两个 Primitives 对象内存使用情况
从图中的结果可以看出如果对象的值是 null
,那他们是不占用空间。在这一点上看,如果字段大部分时间都存的是空值或者以空值指代默认值,那相对比总是会被赋予默认值的基础数据类型在内存使用方面要好一些。
对于包装类,它们都比其对应基础数据类型要占用更多的内存空。仔细对比不难计算出他们的差别都是 8 bytes,正好是一个普通 Object
实例的大小。这也证明的包装类只是对基础数据类型的简单包装。
基础数据类型 | 包装类型 | ||
---|---|---|---|
类型 | 大小 | 类型 | 大小 |
boolean | 1 byte | Boolean | 9 bytes |
byte | 1 byte | Byte | 9 bytes |
char | 2 bytes | Character | 10 bytes |
double | 8 bytes | Double | 16 bytes |
float | 4 bytes | Float | 12 bytes |
int | 4 bytes | Integer | 12 bytes |
long | 8 bytes | Long | 16 bytes |
short | 2 bytes | Short | 10 bytes |
所以,使用包装类不仅会有自动装箱和拆箱产生的效率上的影响,对内存的使用也有很大的影响。而 Android 的内存又是稀缺资源,所以 Android 库里多了特殊的数据类型来优化内存的使用,比如 SparseArray
替换以 int
为主键的 HashMap
。
真实案例
在最近工作的 Android 项目中,我们需要存5000个存从 Integer 到 Pair<Integer, Interger>
的键值对。最直观的数据类型就是 HashMap<Integer, Pair<integer, Integer>>
,它可以让我们快速重用现有库提供的类,部署之后运行也很稳定。但是当我们做 Memory Profiling 时发现其是内存使用最高的字段之一。
于是我们尝试着把HashMap
改成了SparseArray
,同时定义了IntPair
来直接存放两个 int 类型的字段,最终的数据类型变成了SparseArray<IntPair>
。在这块上的内存使用一下子减少了15%多,差不多少50kb。
虽然SparseArray内部使用了二分查找,可能没有HashMap快,但是对于该情况每次最多找13次 (2 ^ 13 = 8192 > 5000)
就能找到,所以基本也可以视为线性的速度。