Java把内存划分成为两种:一种是堆,一种是栈。与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆。
栈:存放一些基本类型的变量和对象的引用变量。
优点:java自动释放掉所分配的空间,该内存空间可以立即被另作他用,存取速度比堆要快。
缺点:存在栈中的数据大小与生存期必须确定的,缺乏灵活性。
假设我们同时定义:
int a=3;
int b=3;<span style="font-family: SimSun;"> </span>
编译器先处理int a= 3; 首先它会在栈中创建一个变量为a的引用, 然后查找有没有字面值为3的地址,没找到,就开辟一个存放3这个字面值的地址,然后将a指向3的地址。接着处理int b= 3;在创建完b的引用变量后,由于在栈中已经有3这个字面值,便将b直接指向3的地址。这样,就出现了a与b同时均指向3的情况。(过程大致是:先创建变量引用,然后查找有没有值相同的,有的话把该引用指向相同变量值的地址,如果没有先开辟一个地址,然后更改引用。)
特别注意的是,这种字面值的引用与类对象的引用不同。假定两个类对象的引用同时指向一个对象,如果一个对象引用变量修改了这个对象的内部状态,那么另一个对象引用变量也即刻反映出这个变化。相反,通过字面值的引用来修改其值,不会导致另一个指向此字面值的引用的值也跟着改变的情况。如上例,我们定义完a与b的值后,再令a=4;那么,b不会等于4,还是等于3。在编译器内部,遇到a=4;时,它就会重新搜索栈中是否有4的字面值,如果没有,重新开辟地址存放4的值;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响到b的值。
另一种是包装类数据,如Integer,String, Double等将相应的基本数据类型包装起来的类。这些类数据全部存在于【堆】中,Java用new()语句来显示地告诉编译器,在运行时才根据需要动态创建,因此比较灵活,但缺点是要占用更多的时间。
堆:存放由new创建的对象和数组(可以在栈中定义一个特殊的变量,让栈中这个变量的取值等于数组或对象在堆内存中的首地址,栈中这个变量就成了数组或对象的引用变量,就相对于一个名字。以后就可以使用栈中的引用变量来访问堆中的数组或对象。)。是有java虚拟机自动垃圾回收器来管理的。运行时动态分配空间。因为运行时分配内存,存取速度较慢。
String str = new String("abc"); 这种是用new()来新建对象的,它会存放在堆中,每调用一次就会创建一个新的对象。
String str = "abc"; 这种是存放在栈中,创建一个String类的对象引用变量str,然后在栈中查找有没有存放abc,如果没有,则将abc存放在栈中,并令str指向abc,如果有abc,则直接令str指向abc。
String str1 = "abc";
String str2 = "abc";
System.out.println(str1==str2); //true;
可以看出str1和str2是指向同一个对象的。
这里是创建了两个abc字符串,在内存中其实只存在一个对象,这种写法有利于节省内存空间,同时它也可以在一定程度上提高程序的运行速度。因为jvm会自动根据栈中数据的实际情况来决定是否有必要创建新对象。
String str1 = new String("abc");
String str2 = new String("abc");
String str3 = "abc";
System.out.println(str1==str2); //false;
System.out.println(str1==str3); //false
因为用了new,生成了两个不同的对象,是在堆中创建新的对象,而不管其他字符串值是否相等,是有必要创建新对象,从而加重了程序的负担。
由于String类是不变性质,当String变量需要经常变换其值时,应该考虑使用StringBuffer类,以提高程序效率。
比较类里面的数值是否相等,用equals()方法,比较两个包装类的引用是否指向同一个对象时,用==。
String str1 = "abc";
String str2 = "abc";
String str3 = "a";
String str4 = "bc";
String a = new String("abc");
String c = new String("abc");
String d = new String("a");
String e = new String("bc");
System.out.println(str1 == str2);//true
System.out.println(str1.equals(str2));//true
System.out.println(str1 == str3+str4);//false
System.out.println(str1.equals(str3+str4));//true
System.out.println(str1==a);//false
System.out.println(str1.equals(a));//true
System.out.println(a == c);//false
System.out.println(a.equals(c));//true
System.out.println(a == d+e);//false
System.out.println(a.equals(d+e));//true
结论和建议:
- 我们在使用诸如String str = "abc";的格式定义类时,总是想当然地认为,我们创建了String类的对象str。担心陷阱!对象可能并没有被创建!唯一可以肯定的是,指向String类的引用被创建了。至于这个引用到底是否指向了一个新的对象,必须根据上下文来考虑,除非你通过new()方法来显要地创建一个新的对象。因此,更为准确的说法是,我们创建了一个指向String类的对象的引用变量str,这个对象引用变量指向了某个值为"abc"的String类。清醒地认识到这一点对排除程序中难以发现的bug是很有帮助的。
- 使用String str = "abc";的方式,可以在一定程度上提高程序的运行速度,因为JVM会自动根据栈中数据的实际情况来决定是否有必要创建新对象。而对于String str = new String("abc");的代码,则一概在堆中创建新对象,而不管其字符串值是否相等,是否有必要创建新对象,从而加重了程序的负担。这个思想应该是享元模式的思想,但JDK的内部在这里实现是否应用了这个模式,不得而知。
- 当比较包装类里面的数值是否相等时,用equals()方法;当测试两个包装类的引用是否指向同一个对象时,用==。
- 由于String类的不可改变的性质,当String变量需要经常变换其值时,应该考虑使用StringBuffer类,以提高程序效率。