当前位置:  开发笔记 > 编程语言 > 正文

Java integer ++我没有改变这个值

如何解决《Javainteger++我没有改变这个值》经验,为你挑选了4个好方法。

我刚刚制作了这个简单的"程序":

public static void main(String[] args) {
        int i = 1;
        int k = 0;
        while (true) {
            if(++i==0) System.out.println("loop: " + ++k);
        }
    }

运行此程序后,我立即得到输出:

(...)
loop: 881452
loop: 881453
loop: 881454
loop: 881455
loop: 881456
loop: 881457
loop: 881458
(...)

好像i总是0.

事实上,当我在Eclipse中调试时,在暂停程序时,i总是为零.单步执行循环时,i会递增,但在恢复和挂起调试器时,i再次为0.

当我改变i为long时,在运行程序时我需要等待一段时间才能看到第一个loop: 1.在调试器中,在暂停程序时,i会增加:它不是0,所以它可以正常工作.

++i作为int有什么问题?



1> erickson..:

如果继续递增整数类型,它最终会溢出,成为一个大的负值.如果你继续前进,它最终将再次变为0,并且循环将重复.

有一些方便的方法可以帮助避免意外溢出,例如Math.addExact(),这些方法通常不会在循环中使用.


我知道它溢出来了.我只是感到困惑,它快速溢出.我发现很奇怪,每次我暂停调试器时,我都是0.

暂停正在运行的线程时,请考虑线程缓慢调用println()遍历大量Java和本机操作系统代码的可能性,而不是在while循环测试中着陆的可能性,这只会增加本地变量.你必须有一个非常快速的触发手指来看除print语句以外的任何东西.尝试单步执行.

当事情连续发生40亿次时,这是一个很好的猜测它将在下次发生.在任何情况下,分支预测都会有所帮助,优化运行时可能会删除增量操作并完全测试,因为i从不读取中间值.


即使在现代处理器上,增加40亿次整数也需要相当长的时间才能完成.在我看来,除了分支预测之外,还有其他的东西.
具体来说,我怀疑编译器优化了递增和条件分支,因为它可以证明除了减慢速度之外它们没有可见的效果.

2> Marco13..:

正如JohannesD在评论中所建议的那样,几乎不可能从0到Integer.MAX_VALUE(以及在溢出之后-Integer.MAX_VALUE再次从0再次计数)这么快.

为了验证JIT在这里做了一些魔术优化的假设,我创建了一个稍微修改过的程序,引入一些方法使得更容易识别部分代码:

class IntOverflowTest
{
    public static void main(String[] args) {
        runLoop();
    }

    public static void runLoop()
    {
        int i = 1;
        int k = 0;
        while (true) {
            if(++i==0) doPrint(++k);
        }
    }

    public static void doPrint(int k)
    {
        System.out.println("loop: " + k);
    }

}

发出并显示的字节码javap -c IntOverflowTest不会带来任何意外:

class IntOverflowTest {
  IntOverflowTest();
    Code:
       0: aload_0
       1: invokespecial #1                  
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: invokestatic  #2                  
       3: return

  public static void runLoop();
    Code:
       0: iconst_1
       1: istore_0
       2: iconst_0
       3: istore_1
       4: iinc          0, 1
       7: iload_0
       8: ifne          4
      11: iinc          1, 1
      14: iload_1
      15: invokestatic  #3                  
      18: goto          4

  public static void doPrint(int);
    Code:
       0: getstatic     #4                  
       3: new           #5                  
       6: dup
       7: invokespecial #6                  
      10: ldc           #7                  
      12: invokevirtual #8                  
      15: iload_0
      16: invokevirtual #9                  
      19: invokevirtual #10                 
      22: invokevirtual #11                 
      25: return
}

它显然会增加局部变量(runLoop,偏移4和11).

但是,-XX:+UnlockDiagnosticVMOptions -XX:+LogCompilation -XX:+PrintAssembly在Hotspot反汇编程序中运行代码时,机器代码最终会变为以下内容:

Decoding compiled method 0x00000000025c2c50:
Code:
[Entry Point]
[Verified Entry Point]
[Constants]
  # {method} {0x000000001bb40408} 'runLoop' '()V' in 'IntOverflowTest'
  #           [sp+0x20]  (sp of caller)
  0x00000000025c2da0: mov    %eax,-0x6000(%rsp)
  0x00000000025c2da7: push   %rbp
  0x00000000025c2da8: sub    $0x10,%rsp         ;*synchronization entry
                                                ; - IntOverflowTest::runLoop@-1 (line 10)

  0x00000000025c2dac: mov    $0x1,%ebp          ;*iinc
                                                ; - IntOverflowTest::runLoop@11 (line 13)

  0x00000000025c2db1: mov    %ebp,%edx
  0x00000000025c2db3: callq  0x00000000024f6360  ; OopMap{off=24}
                                                ;*invokestatic doPrint
                                                ; - IntOverflowTest::runLoop@15 (line 13)
                                                ;   {static_call}
  0x00000000025c2db8: inc    %ebp               ;*iinc
                                                ; - IntOverflowTest::runLoop@11 (line 13)

  0x00000000025c2dba: jmp    0x00000000025c2db1  ;*invokestatic doPrint
                                                ; - IntOverflowTest::runLoop@15 (line 13)

  0x00000000025c2dbc: mov    %rax,%rdx
  0x00000000025c2dbf: add    $0x10,%rsp
  0x00000000025c2dc3: pop    %rbp
  0x00000000025c2dc4: jmpq   0x00000000025b0d20  ;   {runtime_call}
  0x00000000025c2dc9: hlt

人们可以清楚地看到它不再增加外部变量i.它只调用doPrint方法,递增单个变量(k在代码中),然后立即跳回到doPrint调用之前的点.

所以JIT确实似乎检测到打印输出没有真正的"条件",并且代码相当于只打印和增加单个变量的无限循环.

这对我来说似乎是一个相当复杂的优化.我希望能够检测出这样的情况并非易事.但显然,他们设法做到了......



3> mk...:

你的循环溢出i.你没有break,所以在一段时间后,i回到0,这会打印语句和增量k.这也解释了为什么更改int为a long会导致打印速度变慢:long值溢出需要更长的时间.



4> plugwash..:

首先让我们看一下逻辑上的循环.

i会反复溢出.循环的每2 32(约40亿)次迭代将打印输出并且k将递增.

这是逻辑观点.但是,允许编译器和运行时进行优化,如果每秒钟后得到的值超过一个值,那么很明显必须进行这样的优化.即使使用现代分支预测,乱序执行等,我发现CPU不太可能在每个时钟周期内绕过一个紧密的循环(甚至我认为不太可能).事实上,在调试器中你从未看到过零以外的东西,这强化了代码被优化掉的想法.

你提到使用"long"并且你确实看到其他值时需要更长的时间.如果在未经优化的循环中使用"长"计数器,则可以预期值之间存在数十年.显然,优化正在进行,但似乎优化者在完全优化掉无意义的迭代之前就放弃了.

推荐阅读
路人甲
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有