Java或Scala程序员的位操作技巧

4

有没有人知道一些好的教程或者一本好的书来掌握位级操作?我的意思是,对于每个操作(例如在Java中),几乎都清楚它们是什么或者在哪里可以找到正确的文档,但是我对这个主题非常陌生,而我想知道像以下这样的东西:

// Find a power of 2 >= initialCapacity
int capacity = 1;
while (capacity < initialCapacity)
    capacity <<= 1;

工作(从HashMap复制)。我无法想象整数、长整型或任何数据类型如何受到位运算的影响 :-(

我的意思是,我不想知道每种操作,只想知道对于Java或Scala高级程序员来说似乎是基本的,就像提供的示例一样。

另一个例子是:

/**
 * Applies a supplemental hash function to a given hashCode, which
 * defends against poor quality hash functions.  This is critical
 * because HashMap uses power-of-two length hash tables, that
 * otherwise encounter collisions for hashCodes that do not differ
 * in lower bits. Note: Null keys always map to hash 0, thus index 0.
 */
static int hash(int h) {
    // This function ensures that hashCodes that differ only by
    // constant multiples at each bit position have a bounded
    // number of collisions (approximately 8 at default load factor).
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}

只是看起来像魔法一样 :(

1
黑客的乐趣 [ capacity <<= 1 实际上等同于 capacity *= 2 ],顺便说一下,该算法并不是寻找下一个大于等于capacity的2的幂最有效的算法,例如这个应该更好:1 << (32 - Integer.numberOfLeadingZeros(initialCapacity - 1)) 或者甚至 initialCapacity>1?Integer.highestOneBit(initialCapacity-1)<<1:initialCapacity - bestsss
我已经听说过这本书,但我不确定它是否适合初学者? - Johannes
1
你想要“掌握”它,作为入门,任何解释二进制算术的内容都可以。 - bestsss
关于编辑和魔法,额外的哈希确实有点神奇,并尝试将“更多”信息传播到较低的位中,因为高位实际上是未使用的。因此,即使好的哈希函数生成良好分布的哈希值,但在较低的位数相似,HashMap可能会退化为链接列表。 - bestsss
TAOCP v4A也有一个很好的章节。 - harold
3个回答

6

要理解基础知识,您需要了解数据的表示方式。这需要了解二进制和通常使用的二进制补码

一旦您理解了基础知识,就可以在普遍存在的斯坦福资源中找到许多有用的技巧。


3

1
这是一个关于位运算符的Java教程,网址为http://www.java2s.com/Tutorial/Java/0060__Operators/0300__Bitwise-Operators.htm。 - Nate

0

无法抗拒...

一个简短的位操作教程

整数用二进制代码表示。二进制补码不是唯一可能的编码方式,但它现在(几乎?)被广泛使用。

对于3位,二进制代码如下:

000  0
001  1
010  2
011  3
100  4
101  5
110  6
111  7

如果我们将1加到7,我们会得到代码1000,但我们只有3位,所以我们得到000。这被称为溢出。听起来可能很奇怪,但在编译器级别上几乎总是忽略溢出(*)。程序员需要选择具有适当范围的数据类型(int、long、BigInteger等)。
但我们只看到了正数,那么负数从哪里来?符号位浮现在脑海中,但我们不能再添加更多位,我们必须使用已有的位。因此,我们将左侧最高位为1的代码分配给负数:
code  signed  unsigned
100     -4
101     -3
110     -2
111     -1
000      0       0
001      1       1
010      2       2
011      3       3
------------------
100              4
101              5
110              6
111              7

我们看到一些不对称:有一个-4的代码,但没有4的代码。这是因为我们必须使用一个代码来表示0。

使用带符号的3位数学,如果我们将1加到3上,我们得到-4。这是溢出。并且在编译器级别被忽略。

因此,无符号溢出发生在111和000之间,而有符号溢出发生在011和100之间。这就是为什么两种溢出都被忽略(*):如果我们忽略溢出,我们可以对有符号和无符号数字使用相同的加法。(这也适用于减法和乘法;只有除法对有符号和无符号数字有所不同。)

现在,让我们了解一下位操作基础知识:

"bit is set" 意味着该位是1,"cleared"或"reset"意味着该位是0 所有负数的最左位(最高有效位,MSB)为1,正数的MSB=0 所有奇数的最低位(LSB,最右边的位)为1,偶数的LSB被清除(设置为零) (x & 1) == 1 表示x为奇数,(x & 1) == 0表示x为偶数 x & -2 清除了x的最低位;如果x是奇数,则(x & -2) == x-1,如果x是偶数,则(x & -2) == x x & -4 清除x的两个最低位,以此类推 (x << 1) == x*2 (x >> 1) == x/2 (x >>> 1) == x/2(无符号的) 在Java和Scala中可以使用+ - *进行无符号运算,但需要自己编写unsignedToString转换,同时无符号比较也会成为问题。 (x ^ -1) == ~x == -x-1 -x == (~x) + 1 2**N有一个设置位后面跟着N个零(这里的**表示幂),例如,2**4 = 16 = 10000 2**N - 1是一个值为N的二进制位都为1的数,例如,2^4 - 1 = 15 = 1111
关于哈希函数,它们并不获取任何有意义的值,它们只获取许多位参与的值。你看,哈希值应该(理想情况下)对于不同的有意义的值是不同的。如果它们只取一些最低有效位,所有像1K、2K、3K这样的数字都会进入相同的HashMap桶中,因此它们使用来自数字代码不同部分的位。他们可以使用加法来组合这些位,但他们更喜欢使用异或(eXclusive OR)。
(*) 所有CPU都有用于有符号和无符号溢出的“标志”,但这些标志不能从高级语言中访问,因为这些标志是CPU指令(如加法、减法等)的副作用,而r=r+(-1)r=r-1之后的标志将不同(在第一种情况下,如果r!=0,我们会得到无符号溢出;在第二种情况下,如果r==0,我们会得到无符号溢出)。嗯,“无符号溢出”被称为进位/借位,而“有符号溢出”通常被称为溢出。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接