String并不是基础数据类型之一,而是一个封装的字符串类型(java.lang.String),这个类应该是我们使用频次最高的类了,那么这个类我们如何去理解使用和原理呢?
1、内存模型(重点讲述常量池);
2、String使用的基本场景及底层原理;
3、intern方法的理解;
一、内存模型
简单描述下JVM的内存模型包含:程序计数器、本地方法栈、方法区、虚拟机栈、堆;其中重点要描述的是常量池,常量池jdk7以前(jdk6及以前)存放在方法区中,jdk1.7及之后存放在堆中,如下图:

为啥要介绍常量池呢,因为字符串常量是存放在常量池中,一个特殊的区域。了解这个点之后,才能讲述后面的底层原理。
二、String使用的基本场景及底层原理
2.1 String的比较equals 与 ==
equals从Object下继承的效果与==是一样的,但是String重写了equals方法,进行了字符串值的比较。如下是:String.equals方法
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
如下例:
public static void compare(){
String s1 = "abc";
String s2 = new String("abc");
String s3= "abc";
System.out.println(s1 == s2); // false
System.out.println(s1 == s3); // true
System.out.println(s1.equals(s2)); // true
}
上述的例子原理如下:
1、s1 == s2 为false:s1是直接赋值了abc常量,该常量存储在常量池中;s2指向的是堆里面的new String对象,然后new String对象指向了abc,那么s1和s2指向都不是同一个东西,自然不相等;
2、s1 == s3 为true,是因为s1和s3都指向了abc常量池的位置;
3、s1.equals(s2)为true,是因为String重写了equals方法,比较的只是字符串的值,并不是引用地址,因此是true都是abc。
内存结构如下(JDK7):(s1==s3, s2 != s1 or s3)
![]()
2.2 String的“+”
String与String之间,String对象与常量字符串、常量字符串与常量字符串之间是如何完成字符串拼接的。
public static void append() {
String s1 = "ab";
String s2 = "abc";
String s3 = s1 + "c";
System.out.println(s3 == s2); // false
final String s4 = "ab";
final String s5 = "c";
String s6 = s4 + s5;
System.out.println(s6 == s2); // true
}
(1) “+” 编译器会做特殊处理,如果是s1 + "c",实际上上会新建一个StringBuilder对象,然后append("c")字符,然后toString返回一个新的字符串(new StringBuidler("ab").append("c").toString()的效果,因此s3指向的实际上是一个堆中的StringBuilder对象;那么s2 != s3;(下图摘自字节指令)
64: invokevirtual #11 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
67: ldc #12 // String c
69: invokevirtual #11 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
72: invokevirtual #13 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
(2)那为啥s6 == s2,因为final关键字标识了s4和s5,他们在编译期就写入到了常量池,然后s4+s5就会在编译期就直接拼接好,写入到常量池,最后就是一个常量池,等价于s6=“abc”;
(3)内存模式:

三、intern方法的理解
介绍String的时候,就不得不介绍intern方法了,如下图:String.intern是一个native方法。

有啥用呢???----- Jdk1.7以前之后又区别,例如:
public static void internTest(){
String s1 = new StringBuilder().append("String").append("Test").toString();
System.out.println(s1.intern() == s1);
String s2 = "hello";
String s3 = new StringBuilder().append("hel").append("lo").toString();
System.out.println(s3.intern() == s3);
}
3.1 jdk 1.7之前版本(例如:JDK1.6)
调用String.intern方法,首先常量池在方法区。
如果常量池内没有字符串,那么会将字符串复制到常量池,并返回指向当前字符串常量的引用;如果常量池中已存在,那么返回常量池该字符串的引用。
如果在jdk1.6中执行,结果会是:false,false,这是为啥呢?
(1)StringTest在常量池中不存在,因此会复制StringTest字符串在常量池中,这个时候返回的是这个字符串的引用,那就是在堆中的StringBuilder;s1.intern != s1
(2)java在常量池早已存在,这个时候返回的s3.intern()指向常量池中的hello,s3 != s3.intern()

3.2 jdk 1.7及以后版本
调用String.intern方法,常量池在堆中。(true、false)
如果常量池没有字符串,那么会将字符串的引用复制到常量池中,并返回指向当前字符串常量的引用;如果常量池中已存在,那么返回常量池该字符串的引用;
发现了一个区别没有,如果没有某个字符串,是在常量池中写入了:字符串的引用,而不是字符串,因为都在堆区,目的是节省空间,就是因为这个变动,导致了版本的差异。
(1)StringTest常量池不存在,那么常量池中会存放一个StringTest字符串引用,指向StringTest字符串,而不是StringTest字符串,也就是:s1.intern() = s1,指向同一个对象;
(2)这个时候s3.intern()调用的时候常量池中存在hello字符串,那么返回常量池中的java字符串引用,即:s3.intern() --> hello,那么s3 --> StringBuilder,那么s3 != s3.intern()
2825

被折叠的 条评论
为什么被折叠?



