学过c语言的都知道,如果两个字符指针char *s1, char *s2(假设已初始化过), 运算符== 是比较两个字符的首地址是否相同(java语言字符串比较则是引用是否相同),而不是比较本身字符串是否相等。我们也知道在采用段式存储管理中,局部自动变量是存储在栈上的,动态分配则在堆上分配,而局部常量往往存储在代码段,java 中jvm为了优化存储,专门有一个string pool,用于存储字符串常量,并且保证相同的字符串只有一个拷贝。而全局已初始化变量或者静态变量往往存储在数据段,未初始则存在在BSS段中。这些都是学过语言的一些基本常识。
那么根据以上知识,看看下面的代码:
#include <stdio.h> char *s4 = "hello"; char *s5 = "hello"; const int g = 5; const int h = 5; const int aa[] = {1, 2, 3}; const int bb[] = {1, 2, 3}; int main(int argc, char **argv) { char *s1 = "hello"; char *s2 = "hello"; const char s3[] = "hello"; int a = 5; int b = 5; const int c = 5; const int d = 5; static const int e = 5; static const int f = 5; printf("%d\n", s1 == s2); /* 1 */ printf("%d\n", s2 == s3); /* 0 */ printf("%d\n", s4 == s5); /* 1 */ printf("%d\n", &a == &b); /* 0 */ printf("%d\n", &c == &d); /* 0 */ printf("%d\n", &e == &f); /* 0 */ printf("%d\n", &g == &h); /* 0 */ printf("%d\n", aa == bb); /* 0 */ return 0; }
首先s1和s2是相同的字符串常量,保存在代码段,并且是只可读的,不允许修改,由程序的输出(编译时用的gcc),可知类似java的string pool,为了优化存储,对于相同的字符串,只有一个副本。s3是一个字符数组,虽然被声明为const只读,但我们还是可以认为s3是一个变量,只是认为加上了const变量,致使s3指向的内存不可修改。注意s1和s2是字面字符串,固化在代码段中,无论如何都不能修改,它不是人为控制的,而是编译器决定的。s4和s5显然虽然本身是全局变量,应该存储在数据段,但指向的内存却是字面字符串常量,因此实际上指向的是和s1,s2一样的字符串,即s1, s2, s4,s5指向同一个字符串,他们的值(即指向的内存地址)是一样的。
讨论过了字符串常量,那其他常量是否也有像字符串一样的特性呢?c 和 d 显然是常量,但类似s3它也可以看成是受限的变量.因此他们的地址是不同的。aa和bb也类似。
现在看一段java代码:
class Main { public static void main(String[] args) { String s1 = "hello"; String s2 = "hello"; System.out.println(s1 == s2); /* true */ System.out.println(s1 + s2 == "hellohello");/* ? */ final String s3 = "hello"; final String s4 = "hello"; System.out.println(s3 == s4); /* true */ System.out.println(s3 + s4 == "hellohello"); /* ? */ System.out.println(s1 == s3); /* true */ final String s5 = new String("hello"); final String s6 = new String("hello"); System.out.println(s5 == s6); /* false */ } }
java中 == 运算符是比较引用对象是否相同。为了存储优化,在方法区的常量池中有一块用于存储字符串常量的区,称为String Pool,并且在编译时就已经确定,保存在.class文件中,若有多个相同的变量引用相同的对象,池中只会存在一份拷贝,即在池中创建对象时,先看看池中是否存在值相同的常量,存在直接返回引用,不存在时在真正创建。因此显然s1 和 s2 是相同的引用,返回true。我们也知道,java中String对象是不允许修改的,可以看作是final的,任何对String对象进行操作,均返回一个新的对象,其本身并没有改变。s1 + s2显然需要创建新的String 对象,为了效率,首先会利用s1 new 一个StringBuilder对象,然后执行append操作,最后再调用toString方法返回新的String对象,因此这个新的对象应该是在堆中或栈中,而不在池中,而"hellohello"是在池中的,它们显然不是同一个对象。注意s1和s2引用的对象是常量,不允许修改,但他们本身还是变量,能够引用其他对象。
然而s3和s4被声明为final变量,即s3 和 s4本身也是不可修改的,一旦初始化,不能再引用其他对象。关键在于此时s3 + s4 == "hellohello"返回true还是false呢?java中声明为final的变量会被初始化为编译时常量(compile-time constant expression),当使用这些变量时其实只是内联过去,相当于替换,或者类似c中的宏,因此声明为final的s3和s3, s3 + s4 实际上运行时只能看到"hello" + "hello",依然是字符串常量,注意s1和s2编译时是无法确定它的值的,因此编译完后我们仍然看到的是s1 + s2。因此s1 + s2 == "hellohello"返回false,而s3 + s4返回true。
而s5和s6实际先在池中创建了一个字符串常量(若池中存在,则直接引用),然后再调用String的构造方法又创建了一个新的对象,这个对象是在堆上分配的,因此实际new String("string")创建了两个String对象。当然有一个没有用了,会由垃圾回收清理掉。
2021年9月16日 22:33
This article is an appealing wealth of useful informative that is interesting and well-written. I commend your hard work on this and thank you for this information. I know it very well that if anyone visits your blog, then he/she will surely revisit it again. soaptoday
2021年11月05日 04:05
Pretty good post. I just stumbled upon your blog and wanted to say that I have really enjoyed reading your blog posts. Any way I'll be subscribing to your feed and I hope you post again soon. Big thanks for the useful info. 오피 톡
2021年11月18日 16:05
Wow, What a Excellent post. I really found this to much informatics. It is what i was searching for.I would like to suggest you that please keep sharing such type of info.Thanks judislots
2021年12月06日 15:32
This is such a great resource that you are providing and you give it away for free. I love seeing blog that understand the value of providing a quality resource for free. toto togel