【问题标题】:What is size of my Bitset?我的 Bitset 的大小是多少?
【发布时间】:2016-01-19 23:34:01
【问题描述】:

我想以尽可能小的空间将System.currentTimeInMillis 存储在内存中。因为我必须在内存中存储数百万个。

我将它转换为binaryString,这给了我41 bits

这是我的程序

public class BitSetSize {
    public static void main(final String[] args) {
        final long currentTimeMillis = System.currentTimeMillis();
        final String currentTimeToBinaryString = Long.toBinaryString(currentTimeMillis);
        System.out.println("Size in bits: " + currentTimeToBinaryString.length());

        final BitSet bitSet = BitSet.valueOf(new long[]{currentTimeMillis});
        System.out.println("Bitset length: " + bitSet.length());
        System.out.println("Bitset size: " + bitSet.size());

        System.out.println("Size of biset object(bytes): " + MemoryMeasurer.measureBytes(bitSet));
    }
}

但是当我运行它时,我得到了

Size in bits: 41
Bitset length: 41
Bitset size: 64
Size of biset object(bytes): 48

问题
- 为什么bitSet.length()bitSet.size() 不同?我认为length() 是正确的?
- 我用memory-measurer来了解bitSet的大小,但它告诉我48 bytes,为什么不是(41/8) byte

我很困惑

【问题讨论】:

  • 64 位(可能是long)是 BitSet 实际用于保存数据的位数。 (它不能分配 41 位。)
  • 已知的时间是否在一定范围内?你能在不丢失信息的情况下丢弃每个long 的高字节吗?

标签: java memory memory-management data-structures bits


【解决方案1】:

为什么 bitSet.length() 和 bitSet.size() 不同?我假设 length() 是正确的?

BitSet.size() 是它用来存储位值的内部数据结构的大小。由于BitSet 内部使用long[] 数组,因此大小始终是64 位的倍数。例如。如果您在 BitSet 中设置第 64 位,BitSet 必须增加 long[] 数组的容量才能存储该值,因为每个 long 可以“仅”存储 64 位。例如

BitSet bitSet = new BitSet();
for (int i = 0; i <= 64; i++) {
  bitSet.set(i, true);
  System.out.println(bitSet.size());
}

BitSet.length() 返回BitSet 中实际占用的位。因此,如果您创建一个新的BitSet,它的长度为 0。如果您随后设置第 4 位,则长度将为 5。size 将保持 64,因为只需要一个 long 来存储 5 位。

BitSet bitSet = new BitSet();
System.out.println(bitSet.length()); // 0
bitSet.set(4, true);
System.out.println(bitSet.size());  // 64
System.out.println(bitSet.length()); // 5

我正在使用memory-measurer来了解bitSet的大小,但它告诉我48字节,为什么不是(41/8)字节?

因为内存填充。也称为data structure alignmentBitSet 对象在内存中需要数学上的 41 个字节。

  • 8 个字节的对象头
  • long[] 为 20 个字节
  • 数组中的long 8 个字节
  • wordsInUseint 变量为 4 个字节
  • sizeIsSticky boolean 1 个字节

但 jvm 无法分配 41 位,因此它会将其四舍五入为 8 的下一个倍数。即 48。

此大小可能会有所不同,因为对象标头大小可能因一个 JVM 实现而异。所以如果对象头是16个字节。总数为 49,jvm 将其四舍五入为 8 的下一个倍数。在本例中为 56。

【讨论】:

    【解决方案2】:

    首先,我想建议使用正确的工具来分析 JVM 中的对象布局方案 - JOL。在您的情况下(java -jar jol-cli/target/jol-cli.jar internals java.util.BitSet),JOL 产生以下结果:

    Running 64-bit HotSpot VM.
    Using compressed references with 3-bit shift.
    Objects are 8 bytes aligned.
    Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
    Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
    
    java.util.BitSet object internals:
     OFFSET  SIZE    TYPE DESCRIPTION                    VALUE
          0     4         (object header)                01 00 00 00 (00000001 00000000 00000000 00000000) (1)
          4     4         (object header)                00 00 00 00 (00000000 00000000 00000000 00000000) (0)
          8     4         (object header)                f4 df 9f e0 (11110100 11011111 10011111 11100000) (-526393356)
         12     4     int BitSet.wordsInUse              0
         16     1 boolean BitSet.sizeIsSticky            false
         17     3         (alignment/padding gap)        N/A
         20     4  long[] BitSet.words                   [0]
    Instance size: 24 bytes (reported by Instrumentation API)
    Space losses: 3 bytes internal + 0 bytes external = 3 bytes total
    

    由于静态字段,您的计算不正确,因此空的 BitSet 本身保留 24 个字节。请注意,这些计算不是 100% 准确的,因为它没有考虑到 long[] 对象的大小。所以正确的结果是java -jar jol-cli/target/jol-cli.jar externals java.util.BitSet:

    Running 64-bit HotSpot VM.
    Using compressed references with 3-bit shift.
    Objects are 8 bytes aligned.
    Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
    Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
    
    java.util.BitSet@6b25f76bd object externals:
              ADDRESS       SIZE TYPE             PATH                           VALUE
            7ae321a48         24 java.util.BitSet                                (object)
            7ae321a60         24 [J               .words                         [0]
    

    这意味着一个空的BitSet 本身使用 48 个字节,包括长数组。你也可以得到不同VM模式下的估计对象布局java -jar jol-cli/target/jol-cli.jar estimates java.util.BitSet

    【讨论】:

      【解决方案3】:

      您当前的代码无法存储数百万个long (System.currentTimeInMillis)。您可以使用 trove TLongHashSet 或者您应该查看sparse bitset。但是 BitSet 具有 int 索引,因此您应该将 long 从 currentTimeInMillis 压缩为 int。例如。 bitSetIndex = (int)(currentTimeInMillis - initialTime)。从初始时间开始,它为您提供 2^32 毫秒(约 50 天)的时间间隔。

      //store sample for bitset:
      bitSet.set(System.currentTimeInMillis());
      

      编辑

      一个 BitSet 对象在堆上分配超过 100 个字节。所以你应该为很多长值重用一个 BitSet 对象。最简单的方法是在 BitSet 中使用 long value 作为索引,并在该索引处设置 value 为 true。但是有几个问题(我在上面描述过):

      1. BitSet 的 int 索引不长
      2. java.util.BitSet 内存效率不高。

      【讨论】:

      • can't be storage for millions of long,你能解释一下原因吗?
      【解决方案4】:

      参见BitSet的java文档。

      每个位集都有一个当前大小,即空间的位数 当前由位设置使用。请注意,大小与 位集的实现,因此它可能会随着实现而改变。这 位集的长度与位集的逻辑长度有关,并且是 独立于实现定义。

      【讨论】:

        【解决方案5】:

        正如 BetaRide 所提到的,BitSet 占用的实际大小是特定于实现的。也就是说,在 Oracle/OpenJDK 实现中(至少在 6、7 和 8 中),状态的基本元素是long[] of words。这意味着大小始终是 64 的倍数。

        至于48字节,我算在代码里:

        • 16 字节for the BitSet object itself
        • long[] 对象为 20 个字节(对象为 16 个字节,长度为 4 个字节)
        • 8 个字节的数组内容(每个元素是 8 个字节,但你只有一个)
        • int wordsInUse 的 4 个字节
        • boolean sizeIsSticky 1 个字节

        产生 49 - 与您看到的 48 相差不远。如果那些object headers are compressed,但也引入了填充,那么这可能就是 48 的来源。

        【讨论】:

          猜你喜欢
          • 2012-09-09
          • 1970-01-01
          • 2021-09-22
          • 1970-01-01
          • 1970-01-01
          • 2013-02-26
          • 2012-06-30
          • 2011-03-13
          • 1970-01-01
          相关资源
          最近更新 更多