一、想要理解string类,先看源码:
1 public final class String
2 implements java.io.Serializable, Comparable<String>, CharSequence {
3 /** The value is used for character storage. */
4 private final char value[];
5
6 /** Cache the hash code for the string */
7 private int hash; // Default to 0
8 ...
9 }
从上面可以看出
- String类被final关键字修饰,意味着String类不能被继承,并且它的成员方法都默认为final方法;字符串一旦创建就不能再修改。
- String实例的值是通过字符数组实现字符串存储的。
二、String 方法
下面是 String 类支持的方法,更多详细,参看 Java String API 文档:
三、字符串常量池
在Java的内存分配中,总共3种常量池,分别是Class常量池、运行时常量池、字符串常量池。
字符串的分配和其他对象分配一样,是需要消耗高昂的时间和空间的,而且字符串使用的非常多。JVM为了提高性能和减少内存的开销,在实例化字符串的时候进行了一些优化:使用字符串常量池。每当创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么就直接返回常量池中的实例引用。如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中。由于String字符串的不可变性,常量池中一定不存在两个相同的字符串。
1 public class String1 { 2 static String a = "AB"; 3 static String b = "AB"; 4 static String c = new String("AB"); 5 static String d = "A"+"B"; 6 static String e = "A"; 7 static String f = "B"; 8 static String g = e+f; 9 static String h = "A"+f; 10 public static void main(String[] args) { 11 System.out.println(a==b); //true 12 System.out.println(a==c); //false 13 System.out.println(a==d); //true 14 System.out.println(a==g); //false 15 System.out.println(a.equals(g)); //true 16 System.out.println(a.equals(h)); //true 17 System.out.println(a==h); //false 18 } 19 }
内存分析如图所示:
四、关于字符串拼接符“+”
把"java"、"language"和"specification"这三个字面量进行"+"操作得到一个"javalanguagespecification" 常量,并且直接将这个常量放入字符串池中,这样做实际上是一种优化,将3个字面量合成一个,避免了创建多余的字符串对象。而字符串引用的"+"运算是在Java运行期间执行的,即str + str2 + str3在程序执行期间才会进行计算,它会在堆内存中重新创建一个拼接后的字符串对象。总结来说就是:字符串常量"+"拼接是在编译期间进行的,拼接后的字符串存放在字符串池中;而字符串引用的"+"拼接运算实在运行时进行的,新创建的字符串存放在堆中。
对于直接相加字符串,效率很高,因为在编译器便确定了它的值,也就是说形如"I"+"love"+"java"; 的字符串相加,在编译期间便被优化成了"Ilovejava"。对于间接相加(即包含字符串引用),形如s1+s2+s3; 效率要比直接相加低,因为在编译器不会对引用变量进行优化。
五、、关于String.intern()
intern方法使用:一个初始为空的字符串池,它由类String独自维护。当调用 intern方法时,如果池已经包含一个等于此String对象的字符串(用equals(oject)方法确定),则返回池中的字符串。否则,将此String对象添加到池中,并返回此String对象的引用。
它遵循以下规则:对于任意两个字符串 s 和 t,当且仅当 s.equals(t) 为 true 时,s.intern() == t.intern() 才为 true。
String.intern();
再补充介绍一点:存在于.class文件中的常量池,在运行期间被jvm装载,并且可以扩充。String的intern()方法就是扩充常量池的一个方法;当一个String实例str调用intern()方法时,java查找常量池中是否有相同unicode的字符串常量,如果有,则返回其引用,如果没有,则在常量池中增加一个unicode等于str的字符串并返回它的引用。
例1:
1 /** 2 * 关于String.intern() 3 */ 4 public void test11(){ 5 String s0 = "kvill"; 6 String s1 = new String("kvill"); 7 String s2 = new String("kvill"); 8 System.out.println("===========test11============"); 9 System.out.println( s0 == s1 ); //false 10 System.out.println( "**********" ); 11 s1.intern(); //虽然执行了s1.intern(),但它的返回值没有赋给s1 12 s2 = s2.intern(); //把常量池中"kvill"的引用赋给s2 13 System.out.println( s0 == s1); //flase 14 System.out.println( s0 == s1.intern() ); //true//说明s1.intern()返回的是常量池中"kvill"的引用 15 System.out.println( s0 == s2 ); //true 16 }
结果:false、false、true、true。
例2:
1 public class String1 { 2 3 public static void main(String[] args) { 4 /** 5 * String a = "AB"; 6 * String b = "AB"; 7 * String c = new String("AB"); 8 * String d = "A"+"B"; 9 * String e = "A"; 10 * String f = "B"; 11 * String g = e+f; 12 * String h = "A"+f; 13 * System.out.println(a==b); //true 14 * System.out.println(a==c); //false 15 * System.out.println(a==d); //true 16 * System.out.println(a==g); //false 17 * System.out.println(a.equals(g)); //true 18 * System.out.println(a.equals(h)); //true 19 * System.out.println(a==h); //false 20 */ 21 String s1 = "AB"; 22 String s2 = new String("AB"); 23 String s3 = "A"; 24 String s4 = "B"; 25 String s5 = "A" + "B"; 26 String s6 = s3 + s4; 27 System.out.println(s1 == s2); //false 28 System.out.println(s1 == s5); //true 29 System.out.println(s1 == s6); //false 30 System.out.println(s1 == s6.intern()); //true 31 System.out.println(s2 == s2.intern()); //false 我的理解:左边s2=new String("AB"); 32 // 右边s2.intern()和String s2="AB"是一个意思, 33 //所以两边不相等 34 } 35 }
内存分析:
参考大神:https://blog.csdn.net/ifwinds/article/details/80849184
https://www.cnblogs.com/xiaoxi/p/6036701.html