没有理由一个布尔值一定要占用一个字节的空间。实际上,在某些情况下,布尔值可能已经不是1字节(有效)大小了:当它们与堆栈或对象中大于1字节的其他元素紧密排列时,它们很可能会变得更大(即,将它们添加到对象可能会导致大小增加超过一个字节)。
任何JVM都可以将布尔值实现为1位,但据我所知,没有选择这样做的,可能主要是因为:
- 访问一位通常比访问一个字节更昂贵,特别是在写入时。
阅读一点,使用“经典RISC”指令集的CPU通常需要额外的“and”指令来从一个打包的字节(或更大的字)中提取相关位。有些甚至需要额外的指令来加载一个常量到“and”。在索引一个布尔数组的情况下,其中位索引在编译时不固定,你需要一个可变的位移。一些CPU(如x86)更容易,因为它们具有内存源“test”指令,包括采用可变位置的特定位测试指令,如“bt”。这样的CPU可能在两种表示中具有类似的读取性能。
写入更糟糕:不再是简单的字节写入来设置布尔值,现在需要读取值,修改适当的位并将其写回。一些平台(如x86)具有内存源和目的地的RMW指令,如“and”和“or”,这将有所帮助,但这些指令仍然比普通写入要昂贵得多。在最坏的情况下,重复写入相同的元素将导致通过内存的依赖链,这可能使您的代码减慢一个数量级(一系列普通存储无法形成依赖链)。
更糟糕的是,上述的写入方法完全不安全。两个线程同时操作“独立”的布尔值可能会相互干扰,因此运行时必须使用原子更新操作来写入任何字段的位,以确保对象对于线程是本地的无法被证明。
通常情况下,除了数组之外的空间节省非常小,甚至可能为零:对齐问题意味着一个单独的位通常会占用与堆栈或对象布局中的一个字节相同的空间。只有当您在堆栈或对象上有许多原始的布尔值时,您才会看到节省空间的效果(例如,对象通常对齐到8字节边界,因此如果您有一个非布尔字段为int或更大的对象,您至少需要4个布尔值才能节省任何空间,而通常您需要8个)。
这使得位表示的布尔值在布尔数组中成为最后剩下的“大赢家”,在大型数组中,您可以节省8倍的空间。事实上,在C++世界中,这种情况足够引人注目,以至于那里的vector有一个“特殊”的实现,其中每个bool占用一个位 - 这是一个永无止境的头痛之源,因为所有所需的特殊情况和非直观行为(并且通常被用作无法删除的错误功能的示例)。
如果没有内存模型,我可以想象一个世界,在这个世界中,Java以位方式实现布尔数组。它们没有与vector相同的问题(主要是因为JIT提供的额外抽象层以及数组提供比vector更简单的接口),并且可以高效地完成。然而,存在一个麻烦的内存模型。该模型允许不同线程对不同数组元素的写入是安全的(即,它们在内存模型的目的下作为独立变量)。所有常见的CPU都直接支持这一点,如果将布尔实现为字节,则它们具有独立的字节访问。然而,没有CPU提供独立的位访问:您只能使用原子操作(x86提供了lock bt*操作,但速度较慢;其他平台的选项更糟糕)。这将破坏任何以位数组实现的布尔数组的性能。
最后,如上所述,将布尔实现为位具有显著的缺点-但是好处呢?
事实证明,如果用户真的想要这种位压缩的布尔表示,他们可以自己实现!他们可以将8个布尔值打包到一个字节(或32个值到一个整数或其他)的对象中(这在标志等情况下很常见),生成的访问器代码应该和JVM本地支持布尔作为位一样高效。实际上,当你确定你想要一个大量布尔值的位数组表示时,你可以简单地使用BitSet - 它具有你想要的表示,并通过不提供任何线程安全保证来避免原子问题。因此,通过将布尔实现为字节,您可以避开上述所有问题,但仍然让用户在不会有太多运行时开销的情况下选择位级表示。
boolean
?难道不能8个booleans
(例如在boolean
数组中)共享同一个地址,然后根据数组索引适当地进行移位吗? - flow2k