Java堆和栈的区别(转)

来源:互联网 发布:知轩藏书下载不了 编辑:程序博客网 时间:2024/05/18 02:20
 

堆与栈

Java的堆是一个运行时数据区,对象从中分配空间。这些对象通过new、newarray、anewarray和multianewarray等指令建立,它们不需要程序代码来显式地释放。堆是由垃圾回收来负责的,堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。

栈的优势是,存取速度比堆要快,仅次于寄存器,栈数据可以共享。但缺点是,存在栈中的数据大小与生存期必须是 确定的,缺乏灵活性。栈中主要存放一些基本类型的变量数据(int,short,long,byte,float,double,boolean,char)和对象引用。

栈有一个很重要的特殊性,就是存在栈中的数据可以共享。假设我们同时定义:

Java代码
  1. int a = 3;  
  2. int b = 3;  

 

编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找栈中是否有3这个值,如果没找到,就将3存放进来,然后将a指向3。接着处理int b = 3;在创建完b的引用变量后,因为在栈中已经有3这个值,便将b直接指向3。这样,就出现了a与b同时均指向3的情况。

这时,如果再令 a=4;那么编译器会重新搜索栈中是否有4值,如果没有,则将4存放进来,并令a指向4;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响到b的值。

要注意这种数据的共享与两个对象的引用同时指向一个对象的这种共享是不同的,因为这种情况a的修改并不会影响到b,它是由编译器完成的,它有利于节省空间。

 

字符串
1. 首先String不属于8种基本数据类型,String是一个对象。因为对象的默认值是null,所以String的默认值也是null。但它又是一种特殊的对象,有其它对象没有的一些特性。

2. new String()和new String("")都是申明一个新的空字符串,是空串不是null。

3. String str="kvill"和String str=new String("kvill")的区别

示例:

Java代码
  1. String s0="kvill";   
  2. String s1="kvill";   
  3. String s2="kv" + "ill";   
  4. System.out.println(s0==s1);   
  5. System.out.println(s0==s2);   

 

结果为:
true
true

首先,我们要知结果为道Java会确保一个字符串常量只有一个拷贝。

因为例子中的s0和s1中的"kvill"都是字符串常量,它们在编译期就被确定了,所以s0==s1为true。而"kv"和"ill"也都是字符串常量,当一个字符串由多个字符串常量连接而成时,它自己肯定也是字符串常量,所以s2也同样在编译期就被解析为一个字符串常量,所以s2也是常量池中"kvill"的一个引用。所以我们得出s0==s1==s2。用new String()创建的字符串不是常量,不能在编译期就确定,所以new String() 创建的字符串不放入常量池中,它们有自己的地址空间。

示例:

Java代码
  1. String s0="kvill";   
  2. String s1=new String("kvill");   
  3. String s2="kv" + new String("ill");   
  4. System.out.println(s0==s1);   
  5. System.out.println(s0==s2);   
  6. System.out.println(s1==s2);   

 

结果为:
false
false
false

例2中s0还是常量池中"kvill"的应用,s1因为无法在编译期确定,所以是运行时创建的新对象"kvill"的引用,s2因为有后半部分new String("ill")所以也无法在编译期确定,所以也是一个新创建对象"kvill"的应用。

4. String.intern()
再补充介绍一点:存在于.class文件中的常量池,在运行期被JVM装载,并且可以扩充。String的 intern()方法就是扩充常量池的一个方法。当一个String实例str调用intern()方法时,Java查找常量池中是否有相同 Unicode的字符串常量,如果有,则返回其的引用,如果没有,则在常量池中增加一个Unicode等于str的字符串并返回它的引用。

示例:

Java代码
  1. String s0= "kvill";  
  2. String s1=new String("kvill");  
  3. String s2=new String("kvill");  
  4. System.out.println(s0==s1);  
  5. System.out.println("**********");  
  6. s1.intern();  
  7. s2=s2.intern();  
  8. System.out.println(s0==s1);  
  9. System.out.println(s0==s1.intern());  
  10. System.out.println(s0==s2);  

 

结果为:
false
false //虽然执行了s1.intern(),但它的返回值没有赋给s1
true //说明s1.intern()返回的是常量池中"kvill"的引用
true

5. 关于equals()和==
这个对于String简单来说就是比较两字符串的Unicode序列是否相当,如果相等返回true。而==是比较两字符串的地址是否相同,也就是是否是同一个字符串的引用。

6. 关于String是不可变的
这一说又要说很多,大家只要知道String的实例一旦生成就不会再改变了。比如说:String str="kv"+"ill"+" "+"ans"; 就是有4个字符串常量,首先"kv"和"ill"生成了"kvill"存在内存中,然后"kvill"又和" "生成 "kvill"存在内存中,最后又和"ans"生成了"kvill ans";并把这个字符串的地址赋给了str,就是因为String的"不可变"产生了很多临时变量,这也就是为什么建议用StringBuffer的原因了,因为StringBuffer是可改变的。

下面是一些String相关的常见问题:

String中的final用法和理解

Java代码
  1. final StringBuffer a = new StringBuffer("111");  
  2. final StringBuffer b = new StringBuffer("222");  
  3. a=b;//此句编译不通过  
  4. final StringBuffer a = new StringBuffer("111");  
  5. a.append("222");//编译通过  

 

可见,final只对引用的"值"(即内存地址)有效,它迫使引用只能指向初始指向的那个对象,改变它的指向会导致编译期错误。至于它所指向的对象的变化,final是不负责的。

总结
栈中用来存放一些原始数据类型的局部变量数据和对象的引用(String、数组、对象等等)但不存放对象内容。

堆中存放使用new关键字创建的对象。

字符串是一个特殊包装类,其引用是存放在栈里的,而对象内容必须根据创建方式不同定义(常量池和堆)。有的是编译期就已经创建好,存放在字符串常量池中,而有的是运行时才被创建。