BitSet 的 size() 方法的原因是啥?

Posted

技术标签:

【中文标题】BitSet 的 size() 方法的原因是啥?【英文标题】:What is the reason for BitSet's size() method?BitSet 的 size() 方法的原因是什么? 【发布时间】:2013-05-28 16:59:39 【问题描述】:

java.util.BitSet 类上的 size() 方法是否有用例?

我的意思是——JavaDoc 清楚地表明它依赖于实现,它返回内部long[] 存储的大小(以位为单位)。从上面的内容可以得出结论,您将无法设置比size() 更高的索引的位,但事实并非如此,BitSet 可以自动增长:

BitSet myBitSet = new BitSet();
System.out.println(myBitSet.size());    // prints "64"
myBitSet.set(768);
System.out.println(myBitSet.size());    // prints "832"

在我一生中每次遇到BitSet 时,我一直想使用length(),因为它返回BitSet 的逻辑大小:

BitSet myBitSet = new BitSet();
System.out.println(myBitSet.length());    // prints "0"
myBitSet.set(768);
System.out.println(myBitSet.length());    // prints "769"

尽管过去 6 年我一直在编程 Java,但这两种方法总是让我很困惑。我经常把它们混在一起,顺便用错了,因为在我的脑海里,我认为BitSet 是一个聪明的Set<boolean>,我会使用size()

就像ArrayListlength() 返回元素的数量,size() 返回底层数组的大小。

现在,我缺少 size() 方法的用例吗?它有什么用处吗?有没有人用过它?对于一些手动操作或类似的东西可能很重要吗?


编辑(经过更多研究)

我意识到BitSet 是在 Java 1.0 中引入的,而包含我们使用的大多数类的 Collections 框架是在 Java 1.2 中引入的。所以基本上在我看来,size() 是由于遗留原因而被保留的,它没有真正的用处。新的 Collection 类没有这样的方法,而一些旧的(例如Vector)有。

【问题讨论】:

This bug 包含原始理由! Josh Blosh(被我缩短了,消息的精神保持不变):size 方法......被严重低估了。......假设 *ANYTHING* 关于返回的值是...危险...值可能...在平台之间有所不同。反复或两个BitSets 可能导致值...无限制地增长... ...无法修复.. .,因此我们添加了一个新的、精确指定的方法 (length),它返回有用的信息...... [并且] 取代了 size 方法。” 【参考方案1】:

0 和 1 的个数必须是 64 的倍数。你可以使用 cardinality() 来表示 1 的个数。

【讨论】:

我知道它的作用。但我想知道为什么有人会想要调用这样的方法。或者首先将其包含在 API 中。我对 BitSet 是老一代 util 类的假设是否正确? (见我的编辑) 不是“0 和 1 的个数”。是"the number of bits of space actually in use by this BitSet to represent bit values"。 @EJP 虽然这可能更清楚,但看不出有什么区别。有没有用 0 和 1 填充的空间?【参考方案2】:

我意识到 BitSet 是在 Java 1.0 中引入的,而包含我们使用的大多数类的 Collections 框架是在 Java 1.2 中引入的。

正确。

所以基本上在我看来 size() 是由于遗留原因而保留的,并且没有真正的用处。

是的,差不多。

另一个“大小”方法是length(),它为您提供设置位的最大索引。从逻辑上看,length()size() 更有用……但length() 仅在Java 1.2 中引入。

我能想到size() 可能比length() 更好的唯一(假设)用例是:

您正在尝试为集合中的位迭代建立一个“栅栏柱”,并且 很有可能您会在结束之前停止迭代,并且 稍微超出设置的最后一位并不重要。

在这种情况下,size() 可以说比length() 更好,因为它是一个更便宜的电话。 (看源代码……)但这还很微不足道。

(我猜,另一个类似的用例是当您创建一个新的BitSet 并根据现有size()size() 预分配它。同样,差异是微不足道的。)

但是你对兼容性的看法是对的。很明显,他们既不能摆脱size(),也不能在不产生兼容性问题的情况下改变其语义。所以他们大概决定不理会它。 (事实上​​,他们甚至没有看到有必要弃用它。在 API 中拥有一个不是特别有用的方法的“危害”是最小的。)

【讨论】:

看来我毕竟走在了正确的轨道上。如果有人对这种方法有革命性的用途,我会再等一会儿,但我想这个滴答声将是你的。很遗憾他们没有弃用它。用错了会节省我宝贵的时间。【参考方案3】:

如果 Java 创建者没有将 size 方法设计为公共的,那么它无疑仍会作为私有方法/字段存在。所以我们正在讨论它的可访问性,也许还有命名。

Java 1.0 从 C/C++ 中汲取了很多灵感,而不仅仅是过程语法。在 C++ 标准库中,也存在 BitSetlengthsize 的对应物。它们分别被称为sizecapacity。在 C++ 中使用 capacity 很少有任何硬性理由,在 Java 这样的垃圾收集语言中更是如此,但让该方法可访问仍然可以说是有用的。我会用 Java 术语来解释。

告诉我,执行BitSet 操作(例如set)所需的最大机器指令数是多少?有人想回答“只是少数”,但这仅在该特定操作不会导致整个底层数组的重新分配时才成立。从理论上讲,重新分配将恒定时间算法转变为线性时间算法。

这种理论上的差异是否有很大的实际影响?很少。数组通常不会增长太频繁。但是,当您有一个算法在逐渐增长的BitSet 上运行且最终大小大致已知时,如果您已经将最终大小传递给BitSet 的构造函数,您将节省重新分配。在某些非常特殊的情况下,这甚至可能会产生明显的影响,在大多数情况下不会造成伤害。

set 然后具有恒定的时间复杂度 - 调用它永远不会阻塞应用程序太久。 如果只有一个非常大的 BitSet 实例耗尽了所有可用内存(按设计),则交换可能会在稍后明显开始,具体取决于您的 JVM 如何实现增长操作(有或没有额外副本)。

现在假设您对许多 BitSet 进行操作,所有这些都已分配了目标大小。您正在从另一个构建一个 BitSet 实例,并且您希望新的实例共享旧实例的目标大小,因为您知道您将并排使用它们。将size 方法公开使这更容易干净地实现。

【讨论】:

【参考方案4】:

我认为它可能有用的主要原因之一是当我们需要扩展 BitSet 类并覆盖 length 方法时。在这种情况下,大小很有用。下面是 length 如何根据 size 方法返回值。

protected Set bitset;
public int length() 
  int returnValue = 0;
  // Make sure set not empty
  // Get maximum value +1
  if (bitset.size() > 0) 
     Integer max = (Integer)Collections.max(bitset);
     returnValue = max.intValue()+1;
  
  return returnValue;
 

【讨论】:

以上是关于BitSet 的 size() 方法的原因是啥?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 BitSet 不可迭代?

STL详解(十五)—— bitset(位图)的模拟实现

如何用bitset储存未知长度的序列?

bitset小总结

当前为浏览器导入 3rd 方 JS 节点模块的方法是啥?

bitset用法