论坛首页 Java企业应用论坛

代码难道不是这么写的?

浏览 66465 次
该帖已经被评为良好帖
作者 正文
   发表时间:2010-07-28  
其实那个评判这样说,的的确确是有点过了,这样是不会耗多少的内存的,都只是在栈里面操作。
不过,如果把那个变量声明到外面会少分配内存好多次,如果真的是要遍历成千上万次的话,他的话不是没有道理的。
0 请登录后投票
   发表时间:2010-07-28  
FlounyCaesar 写道
XTU_xiaoxin 写道
今天代码评审时,评审官看见如下代码,把我是一顿好骂(代码是我写的),大致形式如下:

  
  for(int i = 0;i<arr.len;i++) {
		  A a = arr[i];
		  System.out.println(s);
	  }

  骂我内容:说我会不会编程,把变量写在for循环里面,说如果遍历多次的话会占用大量的堆内存。
  要我改成如下形式:

    
  
   A a = null;
  for(int i = 0;i<arr.len;i++) {
		  a = arr[i];
		  System.out.println(s);
	  }
 

其实我也不知道他说的对不对,但他让我改的那种方式是绝对没问题的。但我觉得如果变量写在for循环里面,如果没被用到,应该会被垃圾回收的,不会占用太多堆内存吧。

其实,我看了一些大牛写的代码,他们都把变量写在for里面,只是说不要在for里new对象就行了,我很纳闷。。。。。


从这个代码片段上看不出什么问题,因为对象a并未被使用,而楼主又要创建出arr.len个数量的a对象,不知道用意是什么,其实从这段代码看的话,arr.len在10万以内是不会有什么问题的,关键他创建这些对象的作用是什么

拜託,這有創建出arr.len个数量的a对象?
0 请登录后投票
   发表时间:2010-07-28  
aws 写道
临时变量超出作用域,其引用的内存就可以回收了,而且JVM对这种情况有优化
如果生命周期变长,反而有可能多次回收收不掉,进入老年代
按LZ评审官的写法,反而会多一条初始化A的指令
LZ的评审官多半是做C出来的吧

有两方面考虑
楼主的写法,栈变量会变多,栈空间消耗大,
而 面试官 的方式 有可能造成 变量进入老年代
如果内存资源有限的情况下,array比较大的时,楼主的情况会占大量栈内存.


但是,综上看 还是要看你的代码到底是哪种环境用,如果是j2me之类 除特殊要求 需要注意这些外,个人觉得楼主的代码更漂亮!
而且实际项目中,也很少这么抠,不然一个项目不知道会要写多久,而且程序员素质要多高!
0 请登录后投票
   发表时间:2010-07-28  
yangmen066 写道
aws 写道
临时变量超出作用域,其引用的内存就可以回收了,而且JVM对这种情况有优化
如果生命周期变长,反而有可能多次回收收不掉,进入老年代
按LZ评审官的写法,反而会多一条初始化A的指令
LZ的评审官多半是做C出来的吧

有两方面考虑
楼主的写法,栈变量会变多,栈空间消耗大,
而 面试官 的方式 有可能造成 变量进入老年代
如果内存资源有限的情况下,array比较大的时,楼主的情况会占大量栈内存.


但是,综上看 还是要看你的代码到底是哪种环境用,如果是j2me之类 除特殊要求 需要注意这些外,个人觉得楼主的代码更漂亮!
而且实际项目中,也很少这么抠,不然一个项目不知道会要写多久,而且程序员素质要多高!


请教下,怎么会大量占用栈内存?不就一个局部变量吗?何来的大量占用?
0 请登录后投票
   发表时间:2010-07-28  

为了免的某些人再扯淡,我将编译后的字节码贴出来:

 

public class Test {
    public static void main(String[] args) {

        for (int i = 0; i < args.length; i++) {
            String a = args[i];
            System.out.println(a);
        }
        String a;
        for (int i = 0; i < args.length; i++) {
            a = args[i];
            System.out.println(a);
        }
    }

}

 

JDK6编译的字节码:

 

public class Test {

  // compiled from: Test.java

  // access flags 1
  public <init>()V
   L0 (0)
    LINENUMBER 3 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init>()V
    RETURN
   L1 (4)
    LOCALVARIABLE this Lcom/taobao/notify/timeserver/client/Test; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 9
  public static main([Ljava/lang/String;)V
   L0 (0)
    LINENUMBER 6 L0
    ICONST_0
    ISTORE 1
   L1 (3)
    GOTO L2
   L3 (5)
    LINENUMBER 7 L3
    ALOAD 0
    ILOAD 1
    AALOAD
    ASTORE 2
   L4 (10)
    LINENUMBER 8 L4
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ALOAD 2
    INVOKEVIRTUAL java/io/PrintStream.println(Ljava/lang/String;)V
   L5 (14)
    LINENUMBER 6 L5
    IINC 1 1
   L2 (16)
    ILOAD 1
    ALOAD 0
    ARRAYLENGTH
    IF_ICMPLT L3
   L6 (21)
    LINENUMBER 11 L6
    ICONST_0
    ISTORE 2
   L7 (24)
    GOTO L8
   L9 (26)
    LINENUMBER 12 L9
    ALOAD 0
    ILOAD 2
    AALOAD
    ASTORE 1
   L10 (31)
    LINENUMBER 13 L10
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ALOAD 1
    INVOKEVIRTUAL java/io/PrintStream.println(Ljava/lang/String;)V
   L11 (35)
    LINENUMBER 11 L11
    IINC 2 1
   L8 (37)
    ILOAD 2
    ALOAD 0
    ARRAYLENGTH
    IF_ICMPLT L9
   L12 (42)
    LINENUMBER 15 L12
    RETURN
   L13 (44)
    LOCALVARIABLE args [Ljava/lang/String; L0 L13 0
    LOCALVARIABLE i I L1 L6 1
    LOCALVARIABLE a Ljava/lang/String; L4 L5 2
    LOCALVARIABLE a Ljava/lang/String; L10 L8 1
    LOCALVARIABLE i I L7 L12 2
    MAXSTACK = 2
    MAXLOCALS = 3
}
 

args.length就是一条指令ARRAYLENGTH,哪怕你存到变量len里,也要一条LOAD指令。

两个循环区别仅仅在局部变量的索引上。

1 请登录后投票
   发表时间:2010-07-28   最后修改:2010-07-30
yangmen066 写道
aws 写道
临时变量超出作用域,其引用的内存就可以回收了,而且JVM对这种情况有优化
如果生命周期变长,反而有可能多次回收收不掉,进入老年代
按LZ评审官的写法,反而会多一条初始化A的指令
LZ的评审官多半是做C出来的吧

有两方面考虑
楼主的写法,栈变量会变多,栈空间消耗大,
而 面试官 的方式 有可能造成 变量进入老年代
如果内存资源有限的情况下,array比较大的时,楼主的情况会占大量栈内存.

要知道这个是在一个线程里操作, 当一个局部变量不在使用,它其实就可以出栈(操作数栈)了,所以同一时间,该线程只会有一个这样个的变量在栈里,只是在不停的出栈和入栈。

而且评审官的方式,让变量所指之对象进老年代更无从谈起,因为A a只是一个引用,其所指的对象是在array中,所以面试官的方式缺点应该是扩大了a的可见性,其实没有这个必要, 当然面试官的方式更不会有什么内存扩大的事情出来。

0 请登录后投票
   发表时间:2010-07-28  
diferent 写道
这种事情是要看情况而定的
不能一概而论

1.for(int i = 0;i<arr.len;i++) {  
2.  A a = arr[i];  
3.  System.out.println(s);   // s? 当它不存在吧
4. }
上面这样做的优点是 缩小了引用A的作用范围.
1.A a = null;  
2.for(int i = 0;i<arr.len;i++) {  
3. a = arr[i];  
4. System.out.println(s);  
5.}
这样做的话 , 只声明了一次.
但是在一些特殊情况下 比如
所在的函数很长, 需要执行很多的时间.
那么,a变量将一直指向arr[arr.len-1],又恰好 arr[arr.len-1]在后来某操作
而被弹出了arr数组.也就是从arr数组中被删除了.
但是a还没有过期,所以即使GC在这段时间内工作了,这个对象也不会被回收.
大概就是如此,也不算是问题,除非极特殊,这个A对象是否回收对系统有相当大的影响.
所以评审这种代码是很无聊的.
评审的时候找出一代码潜在问题, 重新组织代码的结构远比要纠结这种问题有意义的多


你的解释没有说,不过在这个场景下面,上后两种语义是一致的。除非A a在后面又被用到。
0 请登录后投票
   发表时间:2010-07-28   最后修改:2010-07-30
dennis_zane 写道

为了免的某些人再扯淡,我将编译后的字节码贴出来:
args.length就是一条指令ARRAYLENGTH,哪怕你存到变量len里,也要一条LOAD指令。
两个循环区别仅仅在局部变量的索引上。

======================
有人是做j2me之类的移动设备的, 一般上他们认为
for (int k =0; k < ArrayList.getSize();k++)


int len = ArrayList.getSize()
for (int k =0; k < len;k++)
要多出多个个方法栈帧(方法调用栈)的操作。
但是在j2se上,这种消耗根本不值一提。

而对于array.length.这就是一个变量访问,这个是肯定的。不会有什么消耗(即使是在j2me中)
0 请登录后投票
   发表时间:2010-07-28  
ahuaxuan 写道
yangmen066 写道
aws 写道
临时变量超出作用域,其引用的内存就可以回收了,而且JVM对这种情况有优化
如果生命周期变长,反而有可能多次回收收不掉,进入老年代
按LZ评审官的写法,反而会多一条初始化A的指令
LZ的评审官多半是做C出来的吧

有两方面考虑
楼主的写法,栈变量会变多,栈空间消耗大,
而 面试官 的方式 有可能造成 变量进入老年代
如果内存资源有限的情况下,array比较大的时,楼主的情况会占大量栈内存.

要知道这个是在一个线程里操作, 当一个局部变量不在使用,它其实就可以出栈了,所以同一时间,该线程只会有一个这样个的变量在栈里,只是在不停的出栈和入栈。

而且评审官的方式,让变量所指之对象进老年代更无从谈起,因为A a只是一个引用,其所指的对象是在array中,所以面试官的方式缺点应该是扩大了a的可见性,其实没有这个必要, 当然面试官的方式更不会有什么内存扩大的事情出来。


呵呵,我说错了,误导大家了
0 请登录后投票
   发表时间:2010-07-28  
yangmen066 写道
aws 写道
临时变量超出作用域,其引用的内存就可以回收了,而且JVM对这种情况有优化
如果生命周期变长,反而有可能多次回收收不掉,进入老年代
按LZ评审官的写法,反而会多一条初始化A的指令
LZ的评审官多半是做C出来的吧

有两方面考虑
楼主的写法,栈变量会变多,栈空间消耗大,
而 面试官 的方式 有可能造成 变量进入老年代
如果内存资源有限的情况下,array比较大的时,楼主的情况会占大量栈内存.


但是,综上看 还是要看你的代码到底是哪种环境用,如果是j2me之类 除特殊要求 需要注意这些外,个人觉得楼主的代码更漂亮!
而且实际项目中,也很少这么抠,不然一个项目不知道会要写多久,而且程序员素质要多高!



其实,修改后,除了作用域的不同,没有什么不同。

关于对象的所在周期,不是有变量决定。而是看之前模个时候,arr[i] = new A();new出来的A对象时候否不再可达。这里的A a=arr[i],仅仅是一个引用。在栈中,这个变量的空间大小就是引用对象空间大小-4字节。当进入时,入栈。不再调用时,出栈。否者,递归程序没有办法完成任务
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics