由String看Java堆栈问题,包括==以及equal()。
首先看代码:
1
public class TestString {
	public static void main(String[] args) {
		String a0 = "abc";
		String b0 = "abc";
		if (a0 == b0) {
			System.out.print("==");
		} else {
			System.out.print("!=");
		}
	}
}

执行结果为:
==

2
public class TestString {
	public static void main(String[] args) {
		String a0 = String.valueOf("abc");
		String b0 = String.valueOf("abc");
		if (a0 == b0) {
			System.out.print("==");
		} else {
			System.out.print("!=");
		}
	}
}

执行结果为:
==

3
public class TestString {
	public static void main(String[] args) {
		String a0 = "abc"+"def";
		String b0 = "abcdef";
		if(a0==b0){
			System.out.print("==");
		}else{
			System.out.print("!=");
		}
	}
}

执行结果为:
==

4
public class TestString {
	public static void main(String[] args) {
                  String a0 = "abc";
		String b0 = "def";
		String c0 = "abcdef";
		String d0 = a0 + b0;
		if (c0 == d0) {
			System.out.print("==");
		} else {
			System.out.print("!=");
		}
	}
}

执行结果为:
!=

5
public class TestString {
	public static void main(String[] args) {
		String a0 = new String("abc");
		String b0 = new String("abc");
		if (a0 == b0) {
			System.out.print("==");
		} else {
			System.out.print("!=");
		}
	}
}

执行结果为:
!=

6
public class TestString {

	public static void main(String[] args) {

		String a0 = String.valueOf("abc")+String.valueOf("def");
		String b0 = String.valueOf("abcdef");

		if (a0 == b0) {
			System.out.print("==");
		} else {
			System.out.print("!=");
		}
	}

}

执行结果为:
!=

我们的都知道,Java的内存分配策略:栈中存放基本数据类型(或者叫内置类型)以及引用类型(或者叫对象句柄),而堆中存放对象数据。
String很特殊,根据Think In Java介绍:“通过编译器和特殊的覆盖或过载运算符+和+=,可将引号字符串转换成一个String”。可见引号字符串本身并不是一个String,而是通过运算符的重载转换成了String。注意:没有=号。
故此:我们要问,引号字符串通过重载以后转换成String存在于什么位置?
查询网上没有具体的分析,书籍上也没有准确的说明。
根据执行结果猜想:
学习过类加载可能知道,有个常量区。
暂且假设将此部分数据存放于此空间中,看能否得到合理的解释。

Java的内存分配栈中存放两种类型数据:
1、基本数据类型:此处疑问?存放的是具体的数据还是也是一个内存地址。
假设1:栈中存放的是具体的数据,当然所有出现在基本类型的操作均可以解释。
假设2:数据存放于上边提到的常量区,而栈中存放的是此常量区对应数据的地址。所有基本数据类型操作似乎也可以解释。
其实Java设计师们很注重内存的利用,我们可能看见很多文章曾提到这样一个概念,当int n =1;时,Java会在内存中搜索,看有没有1的存在,假如有的话,则不会重新分配空间建立1,如果没有的话,则会建立1,假如int m=1;则Java不会再建立1的空间。可见似乎内存中存在一个存放数据的地方。如果用次观点解释假设2完全成立。
现在以下的结论均在假设2下展开。
2、引用类型:栈中存放的是对象的引用,此引用是通过new创建的对象在对中分配的地址。
现在分析String。
属于什么类型?基本数据类型、引用类型。
当作为String str = "abc";建立时,作为基本类型的假设2合理一些(参见第1部分代码)。
当作为String str = new String("abc");建立时,毫无疑问作为引用类型处理(参见第5部分代码)。

我们暂且将+作为一种Java特殊的处理机制,当它处理引号字符串时不会采用new方式建立String(即像"abc"+"def"),而对于栈中数据处理时会采用new方式建立String(即像a+b)。(参见第3、4、6部分代码)。

当作为String str = String.valueOf("abc");建立或者执行+时,完全可以解释通过。
其实String.valueOf()可以看做是对"abc"的封装,但不是new的(参见第2、6部分代码)。


其实现在要是按照以上的观点,可以得出这样一种结论:
栈中存放的是句柄,包括基本数据类型。内存中存在另外一块区域(常量区),存放基本数据类型数据以及引号字符串数据。
堆中存放的是对象,且必须是new的或者通过+运算符的重载隐式new的。

乱想了一通,与一些理论违背,因为好多东西解释不通,所以胡思乱想了。
欢迎评论,谢谢。
评论
gao_20022002 2008-07-02
几天没有来了,呵呵。
感谢各位的支持。
让高手们见笑了。
zhangxi123 2008-07-01
今天是第二次看到这样的内容了!其实他们原理是很简单的没必要这么深究!《JAVA优化编程》对String的讲解非常详细!
如果真想研究java的堆栈的话请读《深入java虚拟机》!如果想学习java基础方面的东西《java编程思想》是必不可少的!
bearice 2008-06-29
JVM的实现不保证具有相同内容的字符串一定具有相同的引用,一般来说,在类初始化时根据常量构造的相同字符串引用是相同的,后续构造出的字符串引用可能不同。要保证相同字符串引用相同,请使用intern方法。其他信息请参看JLS
storm0912 2008-06-27
其实要搞清楚拿==和equals()去进行比较的时候,比较的基准是什么?“==”其实比较的是对象,也就是通常意义上内存地址,而equals()比较的是值,针对String来说也就是字面值。
示例1:
String a0 = "abc";
String b0 = "abc";

毫无疑问,a0和bo是引用类型,那他们引用的对象是哪里来的呢,原来是对"abc”进行了引用,String内部其实有一个属性final char value[], 这个不变值的数组对象就对"abc"进行了引用封装,而"abc"是一个存放在内存常量区的一个常量,这个常量是在类进行加载的时候创建的,java为了优化内存分配,对以此种方式进行的对象创建,任何"abc"的引用都来自同一块内存,那a0和bo其实引用的是一个东西,所以1的结果显而易见。
示例2:String.valueOf("abc")也会产生对象,也只是简单的对"abc"的封装引用,也是一样的道理。
示例5就不一样了,通过显式的调用new操作,java不会再进行常量区查找以优化内存,而是直接在堆中分配,每个对象内部的final char value[]都被分配引用了一块不同的内存,虽然用以初始化的值("abc")是一样的。
示例3比较特殊,当java遇见引号字符串("abc"+"def")的相加时,他首先会去看常量区中有没有相加以后的值存在,如果有就引用他,没有,就自己创建。
示例4和6其实是一个道理,两个String对象相加一定会导致内存的重新分配,一般重新分配内存,那就导致示例5情况的出现

抛砖引玉啦,可能有分析的不对的对方
mercyblitz 2008-06-27
不客气!
gao_20022002 2008-06-27
非常感谢mercyblitz的分析。

无意之间看到一篇关于此问题的文章,与大家共同分享。
http://gao-20022002.javaeye.com/blog/208788
找不到原文了,自己转载一下。
mercyblitz 2008-06-27
我非常欣赏你的这种深入精神,不过我要指出一点你的误解:
引用
当作为String str = "abc";建立时,作为基本类型的假设2合理一些(参见第1部分代码)。

首先,String一定是对象,是引用类型(大概你知道Java底层有三种数据类型,这里不错说了),你看看String的实现,他其实在类中有一个
 private final char value[];
,作为类的一个属性,这有可以说明为什么String不变,若在大于默认规定大小,String会动态的变化,但是一旦new只有里面的value是不会变化的,除非创建第二个对象。
引用

我们暂且将+作为一种Java特殊的处理机制,当它处理引号字符串时不会采用new方式建立String(即像"abc"+"def"),而对于栈中数据处理时会采用new方式建立String(即像a+b)。(参见第3、4、6部分代码)。


在String两个常量“+”操作时,在底层的确在new String,实际上建立这一个StringBuilder()(参看Java语言规范);以你的例子:
在此层操作为
new StringBuilder().append("abc").append("def").toString(),
参考StringBuilder的toString(),实际上它又是对原字符的复制,而不是重新,他会复制字符串的地址。
这样的代码3就可以得到解释,对于非常量,如代码4中,即对象的“+”操作,这个实际上是C++的符号重载(实际上面也是重载),编译器会去判断(他们是对象,非常量),所以不会相等。代码6是同理,看看String.valueOf()方法的实现:
 public static String valueOf(Object obj) {
	return (obj == null) ? "null" : obj.toString();
    }
wangshu3000 2008-06-27
看不太明白。。。台基础了。。。对于“”+“”之类的不太懂。。。
yeyongjin 2008-06-27
Randy Ren 2008-06-27
看了以后觉得不错,我也对相关程序进行了测试,仍持有个人看法:4,5,6三段程序中的if条件改成这样
if (a0.equals(b0)) {   
            System.out.print("==");   
        } else {   
            System.out.print("!=");   
        }   
的话,结果就大相径庭了。在JAVA中,==比较的是地址,而equals()比较的是字面值。基本数据类型确实是一个内存地址,所以在示例1,2,3中可以完全成立。拿4来说吧,d0存方的是"abc"和"def"的两个地址,而c0只有"abcdef"一个地址,条件自然不能成立。
发表评论

您还没有登录,请登录后发表评论

gao_20022002
搜索本博客
最近加入圈子
存档
最新评论