繁体   English   中英

BitSet.size() 返回负值。 已知错误?

[英]BitSet.size() returns negative value. Known bug?

new BitSet(Integer.MAX_VALUE).size()报告负值:

import java.util.BitSet;

public class NegativeBitSetSize {
    public static void main(String[] args) {
        BitSet a;

        a = new BitSet(Integer.MAX_VALUE);
        System.out.println(a.size()); // -2147483648

        a = new BitSet(Integer.MAX_VALUE - 50);
        System.out.println(a.size()); // -2147483648

        a = new BitSet(Integer.MAX_VALUE - 62);
        System.out.println(a.size()); // -2147483648

        a = new BitSet(Integer.MAX_VALUE - 63);
        System.out.println(a.size()); // 2147483584
    }
}

在测试系统上:

$ java -version
openjdk version "11.0.14" 2022-01-18
OpenJDK Runtime Environment (build 11.0.14+9-Ubuntu-0ubuntu2.18.04)
OpenJDK 64-Bit Server VM (build 11.0.14+9-Ubuntu-0ubuntu2.18.04, mixed mode, sharing)

我找不到这方面的错误报告。 这是已知的还是有记录的?

我怀疑这会被记录下来。 它肯定不会被“修复”,因为没有不破坏向后兼容性的合理修复,而且它远没有足够的相关性来采取如此激烈的步骤。

深入挖掘——为什么会这样?

虽然 API 文档没有做出这样的保证,但size()效果是它只是返回您在构造BitSet实例时传递的nBits值...但四舍五入到下一个可以被 64 整除的值:

sysout(new BitSet(1).size());   // 64
sysout(new BitSet(63).size());  // 64
sysout(new BitSet(64).size());  // 64
sysout(new BitSet(65).size());  // 128
sysout(new BitSet(100).size()); // 128
sysout(new BitSet(128).size()); // 128
sysout(new BitSet(129).size()); // 192

这是合乎逻辑的; 该实现使用一个long值数组来存储这些位(因为这比使用例如boolean[]更有效(8 倍),因为每个 boolean 仍然占用数组中的一个字节,并且整个 long 的价值作为独立变量的位)。

该规范并不能保证这一点,但它解释了为什么会发生这种情况。

然后它还解释了为什么你正在见证你是什么: Integer.MAX_VALUE是 2147483647。将其四舍五入到最接近的 64 的倍数,你得到...... 2147483648。其中溢出int - 和Integer.MAX_VALUE + 1 / (int) 2147483648L - 都是相同的值:-2147483648。 那是一个存在于有符号int空间中的值,作为负数没有匹配的正数(这也是有道理的:一些位序列需要表示既不是正数也不是负数的 0。按照惯例/按照 2s 补码的规则,这就是 java 以位形式表示所有数字的方式,0 在“正”空间中(假设它都是 0 位)。因此它从那里“浸出”一个数字,这个数字是 2147483648。

让我们修复它!

一个简单的解决方法是让size()方法返回一个long ,它可以简单地表示 2147483648,这是正确的答案。 不幸的是,这不向后兼容。 因此,如果有人要求进行更改,则极不可能成功。

另一个修复方法是创建第二个方法,使用一些随意的名称,例如accurateSize()或诸如此类的东西,以便size()保持不受干扰,从而保留向后兼容性,它确实返回long 但这永远弄脏了 API,因为除了您可以要求的最大 63 个数字之外,这个细节与所有情况都不相关。 (Integer.MAX_VALUE-62 到 Integer.MAX_VALUE 是您可以为 nBits 传递的唯一值,这会导致size()返回负值。返回的负值将始终为Integer.MIN_VALUE 。我怀疑他们会那样做。

第三种修复方法是撒谎并返回 Integer.MAX_VALUE,这不是正确的值(因为位空间中实际上“可用”了 1 位)。 鉴于您实际上无法“设置”该位值,因为您无法将 2147483648 传递给构造函数(因为您必须传递一个int ,该数字不能作为 int 传递,如果您尝试以 -2147483648 结束,这是负面的并导致构造函数抛出,因此不给你一个实例:没有黑客,例如使用反射来设置私有字段,API 不需要地址,你不能创建一个可以实际存储值的 BitSet第 2147483648 位。

这让我们明白了size()的意义所在。 是为了告诉你BitSet object占用的字节数吗? 如果这就是重点,那么关于它的 go 从来都不是一个好方法: JVM 不能保证long[]的 memory 大小是 arrSize*8 字节(尽管所有 JVM impls 都有这个,+一些低开销数组的 header 结构)。

相反,它可能只是让您知道您可以用它做什么。 即使您调用new BitSet(5) ,您仍然可以设置第 6 位(因为为什么不 - 它不会“花费”任何东西,我想这就是意图)。 您可以设置从 0 到.size()负 1 的所有位。

这让我们得到了真正的答案!

size() 实际上并没有坏。 返回的数字是完全正确的:也就是说,实际上是大小。 只是当你打印它时,它“打印错误”——因为size()的返回值应该被解释为unsigned size()的 javadoc 明确指出了它的唯一要点,即取该数字并减去 1:这会告诉您可以设置的最大元素。

这很好用

BitSet x = new BitSet(Integer.MAX_VALUE);
int maxIndex = x.size() - 1;
System.out.println(maxIndex);
x.set(maxIndex);

上面的代码工作正常。 正如预期的那样,maxIndex 值为 2147483647(即 Integer.MAX_VALUE)。

因此,这里真的没有什么可做的:API 可以正常使用,并且可以按照它建议的方式准确使用它。 您想要提出的“更好”的任何 API 都将向后不兼容; 更改 BitSet 不是一个好主意,添加更多方法, java.util.Vector样式会使 API 变丑,这绝对是治愈比疾病更糟的情况。

这只剩下向文档添加注释。 如果你在文档中深入研究这种级别的外来事物,你最终会得到大量的文档,这又是一种比疾病更糟糕的治疗方法。 可持续的解决方案可能是让 javadoc 获得编写深奥脚注的基本能力,例如javadoc工具可以通过默认折叠的“折叠”弹出界面元素将其转换为 HTML(即奇异的脚注不是可见),但如果您真的想阅读详细信息,可以展开。

Javadoc 没有这个。

结论:可以很容易地争辩说 API 根本没有坏; size()中没有任何内容明确说明返回值应解释为带符号的 int; 唯一明确的 promise 是您可以从结果中减去 1 并将其用作索引,这很好用。 充其量,您可以提交错误报告来更新文档,但这不是一个好主意,因为不可能(很容易)将这些深奥的内容添加到文档中。 如果您确实想要 go 沿着这条路走下去,那么 JDK 库中还有很多此类内容也没有记录。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM