字符串的两种定义方式

String s1 = "abc" ;      // 字面量的定义方式,字符串存储在常量池中
	 String s2 =  new String("hello");     // new 对象的方式

注意事项:

  1. String被声明为final的,不可被继承。
  2. String实现了Serializable接口:表示字符串是支持序列化的。实现了Comparable接口:表示String可以比较大小。
  3. Stringjdk8及以前内部定义了final char value[]用于存储字符串数据。JDK9时改为byte[]。改变的主要原因是节省空间,因为很多字符串都是一个字节。
  4. 字符串常量池不会存储相同内容的字符串。
  5. jdk7之后将字符串常量池放到了堆中,之前是在永久代里面。

字符串拼接操作注意事项:

  1. 常量与常量的拼接结果在常量池,原理是编译期优化,常量池中不会存在相同内容的变量。拼接的字符串中只要其中有一个是变量,结果就在堆中。
  2. 变量拼接的原理是StringBuilder,如果拼接的结果调用intern()方法,根据该字符串是否在常量池中存在,分为:
    2.1 存在: 则返回字符串在常量池中的地址如果字符串常量池中
    2.2 不存在: 则在常量池中创建一份,并返回此对象的地址
String s1 = "a" + "b" + "c";//编译期优化:等同于"abc"
      String s2 = "abc"; //"abc"一定是放在字符串常量池中,将此地址赋给s2

字符串拼接的底层操作

String s1 = "a";
        String s2 = "b";
        String s3 = "ab";
        /*
        如下的s1 + s2 的执行细节:(变量s是我临时定义的)
        ① StringBuilder s = new StringBuilder();
        ② s.append("a")
        ③ s.append("b")
        ④ s.toString()  --> 约等于 new String("ab"),但不等价 */
        String s4 = s1 + s2;//
        System.out.println(s3 == s4);//false
        // 但是如果用final修饰了,s4+s5就相当于"a"+"b"
        final String s4 = "a";
        final String s5 = "b";
        String s6 = "ab";
        String s7 = s4 + s5;
        System.out.println(s6 == s7);//true

注意:

  • 通过StringBuilderappend()的方式添加字符串的效率要远高于使用String的字符串拼接方式!
  • 原因1:StringBuilderappend()的方式:自始至终中只创建过一个StringBuilder的对象
  • 原因2:使用String的字符串拼接方式:创建过多个StringBuilderString(调的toString方法)的对象,内存占用更大;如果进行GC,需要花费额外的时间(在拼接的过程中产生的一些中间字符串可能永远也用不到,会产生大量垃圾字符串)。

intern方法

  1. intern是一个native方法,调用的是底层C的方法字符串常量池池最初是空的,由String类私有地维护。
  2. 在调用intern方法时,如果池中已经包含了由equals(object)方法确定的与该字符串内容相等的字符串,则返回池中的字符串地址。否则,该字符串对象将被添加到池中,并返回对该字符串对象的地址。

new String()的说明

  1. new String("ab")会创建几个对象?
  • 一个对象是:new关键字在堆空间创建的
  • 另一个对象是:字符串常量池中的对象"ab"。(如果之前字符串常量池中没有“ab” 的话)
  1. new String(“a”) + new String(“b”)会创建几个对象?代码
  • 对象1:new StringBuilder()
  • 对象2: new String("a")
  • 对象3: 常量池中的"a"(如果之前字符串常量池中没有“a”的话)
  • 对象4: new String("b")
  • 对象5: 常量池中的"b"(如果之前字符串常量池中没有 “b”的话)
  • 对象6 :new String("ab"),此处注意在字符串常量池中,没有生成"ab"
  1. 特殊情况
String s = new String("1");
        s.intern();//调用此方法之前,字符串常量池中已经存在了"1"
        String s2 = "1";
        System.out.println(s == s2);//jdk6:false   jdk7/8:false
        /*
         1、s3变量记录的地址为:new String("11")
         2、经过上面的分析,我们已经知道执行完pos_1的代码,在堆中有了一个new String("11")
         这样的String对象。但是在字符串常量池中没有"11"
         3、接着执行s3.intern(),在字符串常量池中生成"11"
           3-1、在JDK6的版本中,字符串常量池还在永久代,所以直接在永久代生成"11",也就有了新的地址
           3-2、而在JDK7的后续版本中,字符串常量池被移动到了堆中,此时堆里已经有new String("11")了于节省空间的目的,直接将堆中的那个字符串的引用地址储存在字符串常量池中。没错,字符串常量池存的是new String("11")在堆中的地址,所以s4指向了new String("11")。
         4、所以在JDK7后续版本中,s3和s4指向的完全是同一个地址。
         */
		String s3 = new String("1") + new String("1");//pos_1
		s3.intern();
		String s4 = "11";//s4变量记录的地址:使用的是上一行代码代码执行时,在常量池中生成的"11"的地址
		System.out.println(s3 == s4);//jdk6:false  jdk7/8:true