【问题标题】:Java- for loop using << operatorJava-for循环使用<<运算符
【发布时间】:2013-07-25 15:25:47
【问题描述】:

我正在研究这段代码,但我不明白这一行的作用:[(y &lt;&lt; 3) + x]

    for (int y = 0; y <= 7; ++y) {
            for (int x = 0; x <= 7; ++x) {
                final String pieceCode = pieceCodes[(y << 3) + x];
                if (pieceCode.length() != 2) throw new IllegalArgumentException();
                if (!pieceCode.equals("--")) {
                    pieces[((7 - y) << 3) + x] = CheckersPiece.valueOf(pieceCode.charAt(0), pieceCode.charAt(1));   
                }
            }
        }

【问题讨论】:

标签: java


【解决方案1】:

这是乘以 8 的一种混淆方式。因此,(y &lt;&lt; 3) + x 等于 8 * y + x。

y &lt;&lt; 3 等于乘以 8 的原因是因为&lt;&lt; 是左移运算符:它将y 的所有位左移一位。以同样的方式,如果你取一个以 10 为底的数字并左移一位,你就有乘以 10,以 2 为底的左移相当于乘以 2。因此,左移三位相当于乘以2 * 2 * 2 = 8。一般情况下,左移n 位置相当于乘以2^n(只要你没有从左端掉位)。

在过去,程序员编写这样的代码是因为左移速度非常快,比乘法更快,因此8 * y 不如y &lt;&lt; 3 最优。但如今,编译器非常擅长确定何时将 8 * y 替换为 y &lt;&lt; 3。

因此,我说它被混淆了,因为8 * y 更清楚地表达了意图:(y &lt;&lt; 3) + x 的意图是跳过y 8 个块,并在该块中占据xth 的位置。这通过8 * y + x 更清楚地表达了更多。请记住,我们使用高级语言编写代码,以便人们阅读和理解代码。我们的代码应该是为人类编写的。编译器可以完成它的工作,制作好的机器指令供机器理解。

这样做是因为它试图假装pieceCodes 是一个二维数组,只是映射到一个一维数组。

即piecesCode看起来像这样

x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x

但我们可以假装它看起来像这样

x x x x x x x x 
x x x x x x x x
x x x x x x x x
x x x x x x x x
x x x x x x x x
x x x x x x x x
x x x x x x x x
x x x x x x x x

看,给定(x, y) -&gt; 8y + x,我们访问xth 列,yth 行piecesCode。也就是说,y 告诉我们要跳过多少个 8 块,x 告诉我们在那个块内去哪里。

【讨论】:

  • 不一定是混淆的。
  • 我已经够大了,它没有被混淆:(
  • 具体来说,(y &lt;&lt; n) + x 等价于 (y * (2^n)) + x
  • 我说它没有被混淆,因为每个软件工程师都应该像使用十进制系统一样熟悉二进制系统。例如,通过将y &lt;&lt; 3 更改为y &lt;&lt; 4 来调整相关数组的大小比将8*y 更改为16*y 更自然。那是因为 8 和 16 是写下两个 round 数字的一种非常尴尬的方式。
  • @Ian,关于围棋板,我同意你的看法。
【解决方案2】:

(y &lt;&lt; 3) 表示向左移位 3 次。这与乘以 2^3 = 8 相同。因此,整个表达式 (y &lt;&lt; 3) + x 变为 y * 8 + x。

它应该写成y * 8 + x 的形式,因为它更具可读性并且很可能没有性能提升。 Premature optimization is the root of all evil。最好将这种微优化留给编译器(或 JVM)。

此外,电路板大小可以存储在一个常数中,只在一个地方:

final int SIZE = 8;
// ...
for (int y = 0; y < SIZE; y++) {
    for (int x = 0; x < SIZE; x++) {
        final String pieceCode = pieceCodes[y * SIZE + x];

y * 8 + x 只是迭代一个(逻辑上)2D 表,该表有 8 行和列,存储为 1D,有 64 个单元格。

最后,我想指出,在给定的代码中,pieceCodes 是一个字符串数组... 但实际上,它是一个片段代码的数组。不仅仅是 一些 字符串。现在,"--" 就像某种魔法状态一样工作,除了程序员之外没有人知道它的含义。 if (pieceCode.length() != 2) 看起来也很糟糕。因此,应该有一个对象PieceCode,并且数组将被声明为PieceCode[] pieceCodes。在PieceCode 中,我们可以实现正确的equals() 方法。如果PieceCode 只是一个状态,它可以是一个枚举。例如EMPTY, WHITE_PAWN, WHITE_QUEEN, BLACK_PAWN, BLACK_QUEEN。比较字符串不如比较枚举快。我们还必须注意写equals(),而不是==。

【讨论】:

    【解决方案3】:

    来自规范:

    n

    【讨论】:

      【解决方案4】:

      > 是位移运算符。在这种情况下,它将 y 转换为二进制并“移位”3 个位置,根据需要在末尾添加新位

      例如,如果 y 为 8,则其值为 1000

      y

      【讨论】:

        【解决方案5】:

        这称为bitwise and bit shift operator。另外,请查看wiki。

        文档摘要

        Java 编程语言还提供了对整数类型执行按位和位移位运算的运算符。本节讨论的运算符不太常用。

        一元按位补码运算符“~”反转位模式。有符号左移运算符“>”将位模式右移。

        按位 & 运算符执行按位与运算。

        按位 ^ 运算符执行按位异或运算。

        按位 |运算符执行按位包含 OR 运算。

        示例代码:

        class BitDemo {
            public static void main(String[] args) {
                int bitmask = 0x000F;
                int val = 0x2222;
                // prints "2"
                System.out.println(val & bitmask);
            }
        }
        

        那么...什么是按位和位移运算符?

        为了节省时间和篇幅,我将简单地附上这个article,深入解释所有运算符!

        【讨论】:

        • 似乎是一个完全不相关的例子,还有一堆描述其他东西的词。
        • 是的,保持主题是一个愚蠢的想法。对不起。
        • 嘿,不过我只是想帮忙。
        • 是的,这很好。我的观点是,对实际问题的直接解释以及指向进一步支持文档的链接是“正确”的答案。不同意也没关系。
        • 感谢您的关注,因为他得到了答复,现在看来不需要了。
        【解决方案6】:

        代码使用一种优化技术,将二维数组[m][n] 表示为一维数组[m*n]。这里的 m 和 n 似乎都是 8(可能是 8-queens,国际象棋?)。

        诀窍是将索引元组 (i,j) 转置为一维数组的索引。

        大多数时候,你通过将 i 乘以 n 并加上 j 来做到这一点。

        由于 n=8,在这种情况下,乘法可以通过左移 3 位来表示。这传达了这样一个信息:“我们正在这里对一些大小合适(即,根据 2 的幂)数组进行地址算术运算。”,至少对于非新手而言。

        【讨论】:

        • 我猜是跳棋,因为CheckersPiece。只是预感。
        • 我不明白为什么每个人都认为换档是由优化驱动的。在我的代码中,为了清楚起见,我使用它:强调所讨论数字的二进制圆度。例如,1&lt;&lt;31 比 2147483638 更简单、更易读、更明显。
        【解决方案7】:

        快速回答,这是将数字乘以 8 (2^3=8) 的有效方法

        【讨论】:

        • 为什么高效?
        • 我真的怀疑移位速度是否比乘以 8 快。而且,它的可读性较差。
        • @DaveNewton 通常,寄存器移位比乘法快得多。但是,(JIT)编译器编写者也知道这一点,并将用最快的操作码序列替换 8*n。也许像load a, n; add a,a; add a,a; add a,a 这样的东西更快。话虽如此,对于我们这些年长的人来说,(a &lt;&lt; n) + b 符号非常熟悉和易读,它告诉我们它很可能是一个正在进行的索引操作(而不是一些“数字”计算)。如果他们也学会了这一点,也不会伤害新手。
        • @Michael.M 我的重点是它是可读的,而且非常可读。你不能说它不可读,因为在印度等国家有数以亿计的高手不理解它。我们为什么要向下调整?他们不能学吗?这不是火箭科学,但是如果您密切关注,您会发现许多“开发人员”对操作系统、机器体系结构、命令行的基本处理等一无所知。一旦他们的 IDE 停止工作,他们就完全脱离了运气。不必如此。
        • @Ingo 不想让你失望,但也有很多非印度人同样一无所知——这与原籍国无关。
        【解决方案8】:

        y

        如果您进行右移 (y >> 3),那将是整数除以 8,但也很有用,因为位会从末尾脱落,并且如果您循环,您会“排出”位。

        过去(很久以前)CPU 移位比乘法快,所以使用“x

        我曾经在代码中看到诸如“x

        【讨论】:

          【解决方案9】:

          http://en.wikipedia.org/wiki/Bitwise_operation ... 在 Java 中,所有整数类型都是有符号的,并且“>”运算符执行算术移位。 Java增加了运算符“>>>”来进行逻辑右移,但是由于逻辑和算术左移操作是相同的,所以Java中没有“

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-09-18
            • 1970-01-01
            相关资源
            最近更新 更多