A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

本帖最后由 戴振良 于 2013-1-28 17:17 编辑

    前几天我发了一个练习的帖子给大家:http://bbs.itheima.com/thread-36998-1-1.html,说让大家可以赚点技术分,勇于尝试的人也确实赚到了,没赚到的也收获了金币。
       看了大家画的图,意思是有了,可我觉得没人画出来可以很容易让大家理解的,因此我在这里再详细的说说Java变量在内存中的分配情况。(注:有些同学理解能力很强,看老毕视频后已经理解的很清楚了,那就没必要看这贴子,因为我这也是看了老毕的视频后自己经过试验的理解)
学Java,必定常会用到赋值,其实如果我们把赋值操作理解清楚了,那么Java变量在内存的分配情况就清楚了。在这里我说说我对赋值的理解,不一定对,大家多多批评指正:
内存
赋值、赋值,得有值,赋到哪去啊,很多人知道是赋到变量去,在这里我觉的大家可以试着改变一下不使用别人定义好的名词,这样更容易理解事情的本质,比如说把值赋给了变量x,如把1赋给了x,x是个什么东西呢?它是变量,这个比较抽象不好理解,我们说把一棵巧柯立放进一个盒子里,那么盒子是那个四四方方的东西,我们看的见所以容易理解,盒子只是那东西的名字,变量看不见,说变量不容易理解,既然变量能装”值”,它必定是一种能装值的东西的名字而已,那它是什么东西的名字呢,答:内存的名字。  变量是内存的名字这个大家都知道,只是思考问题的时候就模糊了.
内存地址
再来看x = 1,那么这个x变量我们看不到呀,老说把1存到了x里,我觉得这样对于我们理解不太好,应该这样理解,x = 1,是把1这个值存到了变量x所绑定的内存地址里,因为大家都知道,电脑的所有一切数据,要么放在硬盘、U盘、光盘等,要么放在内存里,所以我们说把1放在内存里,这样比较容量理解本质。把1放在内存里,要有个条件——“必须要先知道要存到内存哪个地方”,打个比方,我们说把巧柯立放进盒子里,必须要先知道盒子在哪里吧!有些没怎么学过电脑的人会说内存就一块,地址就是主板那块内存,地址就一个,这是不对的,一块容量为2G的内存条,它就相当于一个海南省,它里面有好多的地方(地址),而一块4G的内存条就好比一个四川省,它的地方(地址)更多了。当然了,2G和4G的内存的容量大小虽然不同,可是他们的物理形状是一样大小的,至于为什么它们里面的容量(地址)的大小不同,这就是高科技的结果了,没必要去深究了。我们不说把1放在变量p中而说把1放在内存中,这样比较容易理解本质。
     赋值、赋值,再来说说这个值,这值又是些什么东西呢,看过老毕基础视频的都知道,电脑的一切数据都是010101110110这样的二进制数,那么说简单一点,值就是8种基本数据类型的值,如intfloatchar,再简单点说值就两样东西,数字(如123.5等)和字符(如’a’,’b’,’c’等),再简单点说值就一样东西,二进制数据(01010101)
值、内存、内存地址,应该大家都是知道的,好了,到这里已经废话连编了,这里总结一下,我们一说到赋值,分析时就要想到3样东西:值、内存、内存地址,花了这么多废话给大家讲这个大家都知道的东西,我只是想告诉大家这三个非常重要。
到此,我自己总结了一个结论,应该是不对的,大牛们常说“一切都是对象”,那么我来一句“一切都是赋值操作”。有了这个理论我们分析内存就简单了,因为我上面说赋值就3样东西:值、内存、内存地址,因此我们了解好这3样东西就够了
   再来一个我自己的总结:所有的变量(包括基本类型变量和对象类型变量)只能保存8种基本类型的值,不能保存对象。
好了,变量不能保存对象,那对象保存到哪去啊???想不到原因的是因为我们学了基础知识却不会拿来用,我上面说了电脑里所有的数据,要么保存在硬盘、U盘、光盘等,要么保存在内存条,所以对象当然是保存在内存中了。这个思想我们要熟记。来看下面的问题:
       Personp1 = new Person();
       Personp2 = p1;
       PersonP3 = p1;
第一句代码执行后,我们会说p1就是一个Person对象,3句代码都执行完后,有人会说有3Person对象。
根据我上面总结的理论,我说变量只能保存值,所以p1没有保存对象,所以p1不是Person对象,p1只是一个对象类型的变量,它保存了一个值,但这个值不是Person对象。
说有3Person对象,我再总结一句:相同的对象,在任何时候都只有一个,而且它在内存的位置不变。


评分

参与人数 2黑马币 +42 收起 理由
黄锦成 + 12 看到累啊!更何况写呢
Rancho_Gump + 30 很给力!

查看全部评分

19 个回复

倒序浏览
本帖最后由 戴振良 于 2013-1-28 17:23 编辑

上面说的都是一堆废话,因为是我自己总结的,我相信很多人看不懂,没关系,下面开始画图了,有图一定So esay!
   我上面说了什么是值,还有对象没说,那么什么是对象呢?我们知道数组是对象,其实本质上他也是一些值,对象一般一创建就有不只一个值,画个内存图分析如下:
int[] x = new int[2];
int[] x 这句代码在栈内存中申请了一个空间,这个空间专门用来保存数组的首地址
从上图,我们可以看到声明变量x后,在内存申请了一块空间,这时里面什么都没有存。变量x在栈内存中的地址是:0xf8a4

接下来看new int[2]的内存图:
从上图中我们可以看到其实对象跟变量差不多,都是分配内存空间,只不过对象分配空间时一般是一个对象就分配好几个连续内存空间。
我们知道数组元素中的默认值为0,数据存在内存中应该是01010101010这样的二进制形式,为了方便我就写数字0了,还有内存的地址,它实际上也是010110101001的形式,为了方便,我们一般把二进制转换成16进制来表示(如0x6ac5),这样书写就比较方便。
从上图,我们可以看到这个对象有两个地存地址,那么第一个0x6ac5就叫做 new int[2]这个对象的首地址。这个对象在堆内存中申请好空间后就把它的首地址0x6ac5的值赋给了变量x,也就是保存到了栈空间内存地址为0xf8a4的内存里。我的理论说变量只能保存值,这里变量x保存的内存地址0xf8a4,它也是一个值,因为我们知道把它转换成10进制后,它就是856这样形式的值(这里是打比方,转换后不是这个值)。
new int[2]对象成功申请内存空间后,把首地址赋给了变量x,内存图哪下:

有了数组对象的内存图,我假设所有对象的内存图与数组对象的一样,如下:
class Person {
   String name = "张三";
   int age = 250;
}
上面有两个Person对象,虽然他们的内容一样(名字和年龄),可是他们不是同一个对象。把上图换成代码形式为:
Person p = 0x5501;
Person p2 = 0x7301;
所以我说任何的变量保存的都是值,只是对象类型的变量保存的值是一个数值,这个数值还有另外的含意,它是一个对象的首地址。
首地址
再来说说对象的这个首地址,我们的变量为什么保存了首地址就能访问整个对象了呢?(访问对象中的所有地址),打个比方,老毕、老张、老王、老方,他们住在学校宿舍楼里,分别住01020304号房,那么我们只要知道了老毕住01号房的地址,我们要访问老张、老王、老方,都是轻而易举的事了。大家看我们上面画的内存图,同一个对象中的多个成员他们的内存地址是相连的,所以变量只保存对象的首地址就可以了。
回复 使用道具 举报
本帖最后由 戴振良 于 2013-1-28 17:58 编辑

接下来再讲讲一切都是赋值操作就结束本贴吧
class Person {}
class QuoteDemo {
   public static void main(String[] args) {
      Person p1 = null;
      newPerson(person);
      System.out.println(p1);
   }
  
   public static void newPerson(Person p2) {
      p2 = new Person();
   }
}
main方法中调用newPerson(person);可以变化下面的赋值语句
Person p2 = p1;
p2 = new Person();
我们把这两句代码和main方法中的放到一起为:
Person p1 = null;
Person p2 = p1;
p2 = new Person();
System.out.println(p1);
用我的理论,不用画内存图也能分析出结果了,p1是变量,那么它保存的是基本类型的值,null能赋值给p1,那么它也是个对象,就算不是,我就当它是吧,我假如null对象在堆内存中的首地址为:0x0000;我假如“new Person()”语句产生的Person对象在内存的首地址为:0x0001,那么放到代码中就是:
Person p1 = 0x0000;
Person p2 = p10x0000;
p2 = 0x0001;
System.out.println(0x0000);//0x0000null的内存地址,所以这里打印null
再来分析上次我出的例子:
例子1
class Demo {
    public static void main(String[] args) {
            ArrayList<String> al = fun();//1句代码
            String s = al.get(0);       //7句代码
            System.out.println(s);      //8句代码
    }
   
    public static ArrayList<String> fun(){
            ArrayList<String> al = new ArrayList<String>();//2句代码
            String s = "张三" ; //3句代码
            al.add(s);         //4句代码
            s = "李四";        //5句代码
            return al;         //6句代码
    }
}
运行后打印的结果是:  张三  ,在没有画内存图之前我不知道大家是怎么思考的,我当时是这样思考的:如果输出的是张三,说明是把“张三”这对象保存到了集合中,如果输出的是“李四”那么说明是把s这个变量保存到了集合中。   不知道大家是不是这样思考的,如果是,我可以说这样的思考是绝对的错误的。根据我上面的理论,一切都是赋值操作变量是不能保存对象的,还有同一个对象在任何时都只有一个,而且位置不变,所以“张三”这个对象一产生就只有一个,所以不能说把“张三”这个对象添加到了集合中。说把变量s添加到了集合中也是错误的,原因往下看:
回复 使用道具 举报
本帖最后由 戴振良 于 2013-1-28 18:05 编辑

我数了一下,上面的代码运行时,总共要执行8句代码,我们一句一句的分析:

首先从main方法执行:1、 执行第一句代码:ArrayList<String> al = fun();

ArrayList<String> al这里声明了一个变量al,内存图如下:


再来看fun();,这里就进入了方法内部执行代码,首先执行方法代码:ArrayList<String> al = new ArrayList<String>();

2、执行第二句代码:ArrayList<String> al = new ArrayList<String>();

这句代码的左边的ArrayList<String>al,在栈内存中声明了一个变量al,内存图如下:


从上图可看到这两个变量都没有值 再看代码的右边: new ArrayList<String>();在堆内存中创建集合对象,内存图如下:


从上图可看到这时集合中没有保存任何东西。该集合对象的首地址是:0x3434

ArrayList<String> al = newArrayList<String>();这一整句代码的内存图为:


3、 执行第三句代码:String s ="张三" ;在栈内存为变量s申请了内存空间,在堆内存为对象“张三”申请内存空间。内存图如下:


这里“张三”这个对象我就不管什么常量池这些了,我假设它也在堆内存中

4、执行4句代码:al.add(s);,我的理论说一切都是赋值操作,这里就可以用上了,我们不要说把一个s对象添加到了集合中,这里也是赋值,把s的值(0x8888)赋给了集合中的某个变量保存起来了,当然我们不知道这个变量的名字是什么,也不需要知道。内存图如下:


上图可以看到在堆内存中ArrayList对象中有一个变量(这个变量的名字就不用管了)保存了fun方法中变量s的值(0x8888),而这个值是对象“张三”的首地址值,所以这时集合中的这个变量也指向了对象“张三”

5、执行5句代码:s = "李四";在堆内存中产生一个李四对象,然后把这个对象的首地址赋给了变量s,内存图如下:


回复 使用道具 举报
本帖最后由 戴振良 于 2013-1-28 18:13 编辑

6、执行第6句代码:return al;这里大家千万不要说把一个集合对象al返回给main方法中的变量al。这返回的是al对象的首地址0x3434,一切都是赋值操作,在这里又有体现了,可写成这样:
public static void main(String[] args) {
                ArrayList<String> al = 0x3434;
到这里fun方法就执行完了,大家知道方法里的局部变量就会销毁,即fun方法中的变量al、s在内存中被清除了,但是对象“张三”、“李四”还在堆内存中,ArrayList对象中的某个变量保存有对象“张三”的内存地址,而没有任何变量保存有对象“李四”的地址,所以现在的“李四”是个垃圾,它随时有可能被垃圾回收器收走,内存图如下图:

7、执行第7句代码:String s = al.get(0);代码左边“String s”在栈内存申请内存空间,内存图如下:
再看“al.get(0)”,这里我们也不要说是把集合中的第0位置上的对象取出来赋给变量s,我们要改变一下我们以往的思想:任何的集合(包括对象类型的数组)都不能保存对象,只能保存对象的首地址。从上图我们知道变量al现在的值是0x3434,这个值是ArrayList集合对象的内存首地址,通过这个内存地址我们取出来了保存在集合内部0x3435地址中保存的值0x8888,然后是把0x8888赋给变量s,所以这时s的值也指向了对象“张三”,内存图如下:
8、执行第8句代码:System.out.println(s);从上图我们知道现在s中保存的值是0x8888,它是对象“张三”在堆内存中的首地址,因此打印结果是:“张三”。
回复 使用道具 举报
本帖最后由 戴振良 于 2013-1-28 18:32 编辑

至此练习1的内存图就画完了,从这些图中,可以验证我说的一切都是赋值操作任何变量(包括对象类型的变量)只能保存值,不能保存对象。
还有我前面说大家可以试着改变一下不使用别人定义好的名词,这样更容易理解事情的本质,大家可以看到我上面的讲解中,我没有使用“引用变量、传值、传址等”一些常用的名词,这理解起来更容易了,因为不用去理解和记这些名词什么意思,还有那些传参数如果传的是基本数据类型,方法就改变不了方法外的变量,如果变量是引用数据类型,方法就可以改变外面的变量等,这些都是让人产生误解的语句。
看完这个内存图,大家再看我上面发的那堆废话,应该就很容易理解了。
能看明白的,真的看明白的给我回复一串111111111111111..)好吗?不明白的可以回答一串01是真,0是假嘛~~~
下面奉上7个小练习,相信大家都可以轻而易举的画出内存图,并找出输出结果的原因。大家先不要用电脑运行,先自己心算,看看是不是输出自己想要的结果,当你不相信这个结果的时候才用电脑验证一下。
  1. class Demo1 {
  2.         public static void changeX(int x) {
  3.                 x = 100;
  4.         }
  5.        
  6.     public static void main(String[] args) {
  7.                 int x = 50;
  8.                 changeX(x);
  9.                 System.out.println(x);        //输出:50
  10.         }
  11. }

  12. class Person {
  13.         String name;
  14.        
  15.         public String toString() {
  16.                 return name;
  17.         }
  18. }

  19. class Demo2 {
  20.         public static void changeName(Person p) {
  21.                 p.name = "李四";
  22.         }
  23.        
  24.     public static void main(String[] args) {
  25.                 Person p = new Person();
  26.                 p.name = "张三";
  27.                 changeName(p);
  28.                 System.out.println(p);        //输出:李四
  29.         }
  30. }

  31. //上面例子,大家都知道如果传的参数是对象,那么方法内可以改变方法外的对象,而下面例子中,字符串也是对象,为什么就改变不了呢?所以说这种说法不可信,大家还是画内存图比较好理解。
  32. class Demo3 {
  33.         public static void changString(String str) {
  34.                 str = "李四";
  35.         }
  36.        
  37.         public static void main(String[] args) {
  38.                 String str = "张三";
  39.                 changString(str);
  40.                 System.out.println(str);  //输出:张三
  41.         }
  42. }

  43. class Demo4 {
  44.         public static void deletePerson(Person p) {
  45.                 p = null;
  46.         }
  47.        
  48.         public static void main(String[] args) {
  49.                 Person p = new Person();
  50.                 p.name = "张三";
  51.                 deletePerson(p);
  52.                 System.out.println(p);        //输出:张三
  53.         }
  54. }

  55. class Demo5 {
  56.         public static void newPerson(Person p) {
  57.                 p = new Person();
  58.         }
  59.        
  60.     public static void main(String[] args) {
  61.                 Person p = null;
  62.                 newPerson(p);
  63.                 System.out.println(p);        //输出:null
  64.         }
  65. }
  66. class Demo6 {
  67.         public static void main(String[] args) {
  68.                 String[] s = giveMeAString();
  69.                 System.out.println(s[0]);//输出:张三
  70.         }
  71.        
  72.         public static String[] giveMeAString() {
  73.                 String[] strs = new String[2];
  74.                 String s = new String();
  75.                 s = "张三";
  76.                 strs[0] = s;
  77.                 s = "李四";
  78.                 return strs;
  79.         }
  80. }

  81. class Demo7 {
  82.         public static void main(String[] args) {
  83.                 Person[] ps = giveMeAPerson();
  84.                 System.out.println(ps[0]);//输出:李四
  85.         }
  86.        
  87.         public static Person[] giveMeAPerson() {
  88.                 Person[] ps = new Person[2];
  89.                 Person p = new Person();
  90.                 p.name = "张三";
  91.                 ps[0] = p;
  92.                 p.name = "李四";
  93.                 return ps;
  94.         }
  95. }
复制代码
回复 使用道具 举报
先存一下 看看
回复 使用道具 举报
最后讲得好
Person p1 = new Person();
Person p2 = p1;
Person P3 = p1;
p1,p2,p3保存的都是new Person();这个对象的内存地址值
回复 使用道具 举报
戴振良 发表于 2013-1-28 17:27
上面说的都是一堆废话,因为是我自己总结的,我相信很多人看不懂,没关系,下面开始画图了,有图一定So esa ...

加深印象了。
回复 使用道具 举报
戴振良 发表于 2013-1-28 17:34
至此练习1的内存图就画完了,从这些图中,可以验证我说的一切都是赋值操作;任何变量(包括对象类型的变量 ...

1111111111111111111111111111
回复 使用道具 举报
苦逼了,画了前3个例子的内存图,先上传上来。剩下的,下次再弄了。

test1.PNG (22.68 KB, 下载次数: 40)

第一题

第一题

test2.PNG (35.04 KB, 下载次数: 37)

第二题

第二题

test3.PNG (33.37 KB, 下载次数: 39)

第三题

第三题

评分

参与人数 1黑马币 +60 收起 理由
戴振良 + 60 很给力!

查看全部评分

回复 使用道具 举报
本帖最后由 戴振良 于 2013-1-29 11:27 编辑
黄锦成 发表于 2013-1-28 22:14
苦逼了,画了前3个例子的内存图,先上传上来。剩下的,下次再弄了。

看来你已经掌握了思想,看你test3说的String的内容一旦确定,就不可以改变,以前也听说过,还听说过StringBuffer是可以改变的,于是想到写代码验证一下:

一、String对象的内容确定后就不可以改变了,下面的变量 str1 连接上" world!" 字符串后就变成了另一个对象了。

class Demo8 {
   public static void main(String[] args) {
      String str1 = new String();
      str1 = "hello";
      String str2 = str1;                     //把变量str1中保存的首地址赋给str2
      str1 = str1.concat(" world!");      //连接字符串
      System.out.println(str1 == str2);//输出:false ,证明这两个变量保存的是两个不同对象的首地址。
      System.out.println(str2);           //输出:hello
      
   }
}


二、StringBuffer对象的内容确定后还可以改变

class Demo9 {
   public static void main(String[] args) {
      StringBuffer sb1 = new StringBuffer();
      sb1.append("hello");                 //连接字符串
      StringBuffer sb2 = sb1;             //把变量sb1中保存的首地址赋给sb2
      sb1.append(" world!");              //连接字符串
      System.out.println(sb1 == sb2);//输出:true ,证明这两个变量保存的是同一个对象的首地址
      System.out.println(sb2);           //输出:hello world!
   }
}

这样一想,String这个对象还是少用吧,因为如果String的内容要改变的话,变一次就在堆内存中多一个对象出来,浪费内存,而用StringBuffer就可以避免这样的现象出现。

评分

参与人数 1黑马币 +21 收起 理由
黄锦成 + 21

查看全部评分

回复 使用道具 举报
戴振良 发表于 2013-1-29 11:15
看来你已经掌握了思想,看你test3说的String的内容一旦确定,就不可以改变,以前也听说过,还听说过Strin ...

能得到师兄的肯定,很高兴啊。以前学习过C#,说有值类型和引用类型,那时分不清,后来学习java时,也分不清,就专门找一些资料来学习了
回复 使用道具 举报
1111111111111111111111111111

点评

^_^  发表于 2013-1-29 13:26
回复 使用道具 举报
正在回家路上,西安转车呢,下午的车,没事来网吧上会网,没在家里方便,不能做你的题了,可惜了金币哈,,,哈哈,
回复 使用道具 举报
          awesome
回复 使用道具 举报
好帖子,顶起来,受用匪浅
回复 使用道具 举报
内容有点长···略过···
回复 使用道具 举报
曹睿翔 来自手机 金牌黑马 2013-1-31 19:16:13
19#
手机实在太悲催,先支持再说,回头看,相信沉不掉
回复 使用道具 举报
保存了,稍后再看
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马