String 你真的懂了吗?

String理解

String是一个字符串类型的类,以前关于String的理解有这么几点:

  1. String 是final类,不可继承,且其内部一些重要方法被定义为final类型,不可重写
  2. String 类比较字符串相等时时不能用“ == ”,只能用  "equals"
  3. 内部实现Serializable接口(支持字符串序列化)和Comparable接口(支持字符串比较大小)。
  4. String类对象有两种实例化方式(直接复制 String a="abc";)和构造器复制(String b=new String("b"););

深入解析String#intern

      JAVA 语言中有8中基本类型和一种比较特殊的类型String。这些类型为了使他们在运行过程中速度更快,更节省内存,都提供了一种常量池的概念。常量池就类似一个JAVA系统级别提供的缓存。

8种基本类型的常量池都是系统协调的,String类型的常量池比较特殊。它的主要使用方法有两种:

  • 直接使用双引号声明出来的String对象会直接存储在常量池中。
  • 如果不是用双引号声明的String对象,可以使用String提供的intern方法。intern 方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中

intern方法 (1.7版本,返回常量池中该字符串的引用)

(1) 当常量池中不存在"abc"这个字符串的引用,将这个对象的引用加入常量池,返回这个对象的引用。
(2) 当常量池中存在"abc"这个字符串的引用,返回这个对象的引用;

接下来我们主要来谈一下String#intern方法。

/** 
 * Returns a canonical representation for the string object. 
 * <p> 
 * A pool of strings, initially empty, is maintained privately by the 
 * class <code>String</code>. 
 * <p> 
 * When the intern method is invoked, if the pool already contains a 
 * string equal to this <code>String</code> object as determined by 
 * the {@link #equals(Object)} method, then the string from the pool is 
 * returned. Otherwise, this <code>String</code> object is added to the 
 * pool and a reference to this <code>String</code> object is returned. 
 * <p> 
 * It follows that for any two strings <code>s</code> and <code>t</code>, 
 * <code>s.intern()&nbsp;==&nbsp;t.intern()</code> is <code>true</code> 
 * if and only if <code>s.equals(t)</code> is <code>true</code>. 
 * <p> 
 * All literal strings and string-valued constant expressions are 
 * interned. String literals are defined in section 3.10.5 of the 
 * <cite>The Java&trade; Language Specification</cite>. 
 * 
 * @return  a string that has the same contents as this string, but is 
 *          guaranteed to be from a pool of unique strings. 
 */  
public native String intern();

   String#intern方法中看到,这个方法是一个 native 的方法,但注释写的非常明了。“如果常量池中存在当前字符串, 就会直接返回当前字符串. 如果常量池中没有此字符串, 会将此字符串放入常量池中后, 再返回”。

public class HelloWorld{
    
    public static void main(String[] args){
    	String a="abc";
    	String b=new String("abc");
        b.intern();
    }  
}

       使用intern的好处:  如果有大量重复的字符串生成,使用String的new构造方法创建的话,就会在堆内存中产生大量重复的对象,占用程序内存,使用intern后,如果是重复的String对象都会放到常量池中,节省内存.
 

jdk1.6和jdk1.7的区别

  • 将String常量池 从 Perm 区移动到了 Java Heap区
  • String#intern 方法时,如果存在堆中的对象,会直接保存对象的引用,而不会重新创建对象。(jdk1.6中,因为常量池在perm区中,java heap的引用和perm的引用不一致,所以会再创建一个新的对象地址值)

 

到底创建了几个字符串

     注: String a="abc"; 其中 abc 会放在常量池中是只编译期生效的. 或者String a="ab"; String b="c";  String c=a+b 所产生的的字符串 abc 也是只有在编译期才会放到常量池,如果是运行期间的话就会放在堆内存中.

     相信很多 JAVA 程序员都做做类似 String s = new String("abc")这个语句创建了几个对象的题目。 这种题目主要就是为了考察程序员对字符串对象的常量池掌握与否。上述的语句中是创建了2个对象,第一个对象是"abc"字符串存储在常量池中,第二个对象在JAVA Heap中的 String 对象。

String str1 = "ABC";
String str2 = new String("ABC");

      String str1 = “ABC”;可能创建一个或者不创建对象,如果”ABC”这个字符串在java String池里不存在,会在java String池里创建一个创建一个String对象(“ABC”),然后str1指向这个内存地址,无论以后用这种方式创建多少个值为”ABC”的字符串对象,始终只有一个内存地址被分配,之后的都是String的拷贝,Java中称为“字符串驻留”,所有的字符串常量都会在编译之后自动地驻留。

      String str2 = new String(“ABC”);至少创建一个对象,也可能两个。因为用到new关键字,肯定会在heap中创建一个str2的String对象,它的value是“ABC”。同时如果这个字符串再java String池里不存在,会在java池里创建这个String对象“ABC”

String str1 = new String("ABC");
String str2 = new String("ABC");
System.out.println(str1 == str2); //false

String str3 = "ABC";
String str4 = "ABC";
String str5 =  "AB" + "C";
System.out.println(str3 == str4);   //true
System.out.println(str3 == str5);  // true


String a  = "ABC";
String b = "AB";
String c = b + "C";
System.out.println( a == c );//false

     注意最后一个a与c相等的判断:a、b在编译时就已经被确定了,而c是引用变量,不会在编译时就被确定。运行时b与“C”的拼接是通过StringBuilder(JDK1.5之前是StringBuffer)实现的,最后调用的StringBuilder的toString函数返回一个新的String对象

     应用的情况:建议在平时的使用中,尽量使用String = “abcd”;这种方式来创建字符串,而不是String = new String(“abcd”);这种形式,因为使用new构造器创建字符串对象一定会开辟一个新的heap空间,而双引号则是采用了String intern(字符串驻留)进行了优化,效率比构造器高。

看下面程序的运行

public class HelloWorld{
    
    public static void main(String[] args){
    	String a="abc";
    	String b="abc";
    	String c="ab"+"c";
    	System.out.println(a==b);
    	System.out.println(a==c);
    	String d="ab";
    	String e="c";
    	System.out.println(a==(d+e));
    	System.out.println(a==(d+e).intern());
    	
    }  
}

输出:
true
true
false
true

      原因: ==在比较引用对象时,比较的是地址值,如果两个对象的地址值一样,说明是同一个对象.a,b,c 三个字符串在运行期间就都会放到常量池中,指向同一个地址值,所以 a==b, a==c 都是 true.

     String d="ab";     

     String e="c"; 

     ( d+e) 是在运行期间创建的,组成的 abc 字符串 会作为String对象存储在堆内存中.然后使用常量池中的abc字符串地址和堆内存中的abc字符串地址对比,地址值不一样,所以 a==(d+e) 就为 false.
    通过我们上面的学习,知道intern()方法把一个运行时创建的字符串加到字符串常量池(如果它还没有入池);

    (d+e).intern() 方法执行时,会发现常量池中已经有一个字符串为abc了,就会把对应的地址值直接返回.这时 a==(d+e).intern() 比较时,地址值都是一样的,所以为 true;(注意如果是jdk1.6及以下的话,该结果为false,因为intern方法会把string对象引用放在Perm 区,perm区的java heap区的地址值 完全不一样,即使调用String.intern方法也是没有任何关系的  ).

  参照:https://tech.meituan.com/2014/03/06/in-depth-understanding-string-intern.html

String和StringBuilder,StringBuffer

String+和Stringbuffer的速度比较

public class HolloWorld{
    
    public static void main(String[] args){
        /*   1   */
        String string = "a" + "b" + "c";
        /*   2   */
        StringBuffer stringBuffer = new StringBuffer();
        stringBuffer.append("a");
        stringBuffer.append("b");
        stringBuffer.append("c");
        string = stringBuffer.toString();
    }
    
}

      上面的写法中,大家觉得是Sting的拼接速度快还是StringBuffer的拼接速度快呢,其实string+的写法要比stringbuffer快,是因为在编译这段程序的时候,编译器会进行常量优化,它会将a、b、c直接合成一个常量abc保存在对应的class文件当中。

      编译后的class文件的反编译代码,如下。

public class HolloWorld
{
  public static void main(String[] args)
  {
    String string = "abc";

    StringBuffer stringBuffer = new StringBuffer();
    stringBuffer.append("a");
    stringBuffer.append("b");
    stringBuffer.append("c");
    string = stringBuffer.toString();
  }
}

       可以看出,在编译这个java文件时,编译器已经直接进行了+运算,这是因为a、b、c这三个字符串都是常量,是可以在编译期由编译器完成这个运算的。假设我们换一种写法。

public class HolloWorld{
    
    public static void main(String[] args){
        /*   1   */
        String a = "a";
        String b = "b";
        String c = "c";
        String string = a + b + c;
        /*   2   */
        StringBuffer stringBuffer = new StringBuffer();
        stringBuffer.append(a);
        stringBuffer.append(b);
        stringBuffer.append(c);
        string = stringBuffer.toString();
    }
    
}

       此处的答案貌似应该是stringbuffer更快,因为此时a、b、c都是对象,编译器已经无法在编译期进行提前的运算优化了。

  但是,事实真的是这样的吗?

  其实答案依然是第一种写法更快,也就是string+的写法更快,这一点可能会有猿友比较疑惑。这个原因是因为string+其实是由stringbuilder完成的,而一般情况下stringbuilder要快于stringbuffer,这是因为stringbuilder线程不安全,少了很多线程锁的时间开销,因此这里依然是string+的写法速度更快。 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值