深入解析String,StringBuilder,StringBuffer

版权声明:文章为本人原创, 转载请注明出处(fxyh97.com), 以及作者(fxyh) https://blog.csdn.net/qq_37872792/article/details/83451582

欢迎大家来我的个人博客:https://www.fxyh97.com/index.php/archives/23/

  • String 字符串常量

    public final class String
        implements java.io.Serializable, Comparable<String>, CharSequence {
        /** The value is used for character storage. */
        private final char value[];
    }
    

    String其实是一经定义就不能改变的char数组。

    下面看下String类中的几个方法:

    public String substring(int beginIndex) {
        if (beginIndex < 0) {
        	throw new StringIndexOutOfBoundsException(beginIndex);
        }
        int subLen = value.length - beginIndex;
        if (subLen < 0) {
        	throw new StringIndexOutOfBoundsException(subLen);
        }
        return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
    }
    
    public String concat(String str) {
        int otherLen = str.length();
        if (otherLen == 0) {
        	return this;
        }
        int len = value.length;
        char buf[] = Arrays.copyOf(value, len + otherLen);
        str.getChars(buf, len);
        return new String(buf, true);
    }
    
    public String replace(char oldChar, char newChar) {
        if (oldChar != newChar) {
        	int len = value.length;
        	int i = -1;
        	char[] val = value; /* avoid getfield opcode */
    
        	while (++i < len) {
        		if (val[i] == oldChar) {
        			break;
        		}
        	}
        	if (i < len) {
        		char buf[] = new char[len];
        		for (int j = 0; j < i; j++) {
        			buf[j] = val[j];
        		}
        		while (i < len) {
        			char c = val[i];
        			buf[i] = (c == oldChar) ? newChar : c;
        			i++;
        		}
        		return new String(buf, true);
        	}
        }
        return this;
    }
    

    从上面三个方法可以看出,无论是进行substring,concat还是replace操作,都不会在原String上操作,而是重新生成了一个新的字符串对象。也就是说,进行这些操作后,一开始的字符串是没有改变的。

    在这永远要记住一点:对String对象的任何改变都不会影响到原对象,相关的任何更改操作都会生成新对象。

  • StringBuffer 字符串变量(线程安全)

  • StringBuilder 字符串变量(线程不安全)

  • 既然Java中已经有String类了,为什么还要StringBuilder和StringBuffer类呢?

    • 看这段代码:

      class StringDemo{
      
      	public static void main(String[] args){
      		String s = "";
      		for(int i = 0; i < 6666; i++){
      			s += "fxyh";
      		}
      	}
      }
      

      这句s += “fxyh”;的过程相当于将原有的s变量指向新的内容中的对象,然后再进行字符串相加操作,再把这个引用重新指向s变量。

      看反编译的代码:在这里插入图片描述

      这段反编译的class文件可以很清除的看出:整个循环的执行过程,并且每次循环都会new出一个StringBuilder对象,然后进行append操作,最后toString方法返回了String对象,也就是说,这个循环完毕后new了6666个对象,想一下,如果这些对象没有被回收,会造成多大的内存浪费。

      从反编译后的代码可以看出s += “fxyh”;的操作实际自动被JVM优化成了下面这个代码:

      StringBuilder str = new StringBuilder(s);
      str.append("fxyh");
      str.toString();
      

      再看下这段代码:

      class StringDemo{
      	public static void main(String[] args){
      		StringBuilder sb = new StringBuilder();
      		for(int i = 0; i < 6666; i++){
      			sb.append("fxyh");
      		}
      	}
      }
      

      看反编译的代码:在这里插入图片描述

      从这里可以明显看出,这段代码的for循环,一直是对new 的StringBuilder对象进行append操作,而且这里只new了一次,因此在循环了这么多次后,这段代码所占的资源明显比上面的小很多。

      那么既然有了StringBuilder,为啥还要StringBuffer类,看了源码就明白了,StringBuffer类的成员方法和属性和StringBuilder基本一样,但是StringBuffer的成员方法多了一个关键字:synchronized,这个关键字可以在多线程中起到安全保护作用,也就是StringBuffer是线程安全的。

  • 下面看几个测试方法,让我们更深入了解一下。

    package com.fxyh.stringtest;
    
    public class Test1 {
    
    	public static void main(String[] args) {
    		testString1();
    		testStringBuffer();
    		testStringBuilder();
    		testString2();
    		testString3();
    	}
    
    	public static void testString1() {
    		String s = "";
    		long start = System.currentTimeMillis();
    		for (int i = 0; i < 100000; i++) {
    			s += "fxyh";
    		}
    		long end = System.currentTimeMillis();
    		System.out.println("testString1使用时间为:" + (end - start) + "毫秒");
    	}
    
    	public static void testStringBuffer() {
    		StringBuffer sb = new StringBuffer();
    		long start = System.currentTimeMillis();
    		for (int i = 0; i < 100000; i++) {
    			sb.append("fxyh");
    		}
    		long end = System.currentTimeMillis();
    		System.out.println("testStringBuffer使用时间为:" + (end - start) + "毫秒");
    	}
    
    	public static void testStringBuilder() {
    		StringBuilder sb = new StringBuilder();
    		long start = System.currentTimeMillis();
    		for (int i = 0; i < 100000; i++) {
    			sb.append("fxyh");
    		}
    		long end = System.currentTimeMillis();
    		System.out.println("testStringBuilder使用时间为:" + (end - start) + "毫秒");
    	}
    
    	public static void testString2() {
    		long start = System.currentTimeMillis();
    		for (int i = 0; i < 100000; i++) {
    			String s = "I"+"am"+"fxyh";
    		}
    		long end = System.currentTimeMillis();
    		System.out.println("字符串直接相加操作使用时间为:" + (end - start) + "毫秒");
    	}
    	public static void testString3() {
    		String s1 = "I";
    		String s2 = "am";
    		String s3 = "fxyh";
    		long start = System.currentTimeMillis();
    		for (int i = 0; i < 100000; i++) {
    			String s = s1 + s2 + s3;
    		}
    		long end = System.currentTimeMillis();
    		System.out.println("字符串间接相加操作使用时间为:" + (end - start) + "毫秒");
    	}
    	
    }
    

    测试结果:在这里插入图片描述

    • 我们刚才说s+=“fxyh”;JVM会自动优化,看下面的这段代码

      package com.fxyh.stringtest;
      
      public class Test1 {
      
      	public static void main(String[] args) {
      		testString1();
      		testString();
      	}
      	public static void testString1() {
      		String s = "";
      		long start = System.currentTimeMillis();
      		for (int i = 0; i < 100000; i++) {
      			s += "fxyh";
      		}
      		long end = System.currentTimeMillis();
      		System.out.println("testString1使用时间为:" + (end - start) + "毫秒");
      	}
      	
      	public static void testString(){
      		String s = "";
      		long start = System.currentTimeMillis();
      		for (int i = 0; i < 100000; i++) {
      			StringBuilder sb = new StringBuilder(s);
                  sb.append("fxyh");
                  s=sb.toString();
      		}
      		long end = System.currentTimeMillis();
      		System.out.println("模拟jvm优化操作使用时间为:" + (end - start) + "毫秒");
      	}
      }
      

      测试结果:在这里插入图片描述

    • 两个执行时间,基本是一样,同样也验证了s+=“fxyh”;JVM会自动优化。

  • 下面对上面的执行结果进行一些推理:

    • 对于直接相加的字符串,效率很高,因为在编译的时候,便已经确定了值,即"I"+“am”+“fxyh”;这样的字符串相加,在编译的时候被优化成了“Iamfxyh”。

      反编译后:在这里插入图片描述

      对于间接相加,形如s1 + s2 + s3;效率要比直接相加底,因为编译器不会对引用变量进行优化。

      反编译后:在这里插入图片描述

    • String、StringBuilder、StringBuffer三者的执行效率:

      • StringBuilder>StringBuffer>String
      • 这个也不是一定的:比如String s = “I” + “am” + "fxyh"的效率就比StringBuilder sb = new StringBuilder().append(“I”).append(“am”).append(“fxyh”);要高。
      • 所以我们要更具适当的情况,选用不同的对象。
      • 当字符串相加操作或者改动少的情况下,建议使用String s = “fxyh”;这种形式;
      • 当字符串相加较多的情况下,建议使用StringBuilder,如果是多线程的话,则使用StringBuffer。
  • 在来踩几个坑:

    1. 下面这段代码的输出结果是什么?

      String a = “hello2″;   
      String b = “hello” + 2;   
      System.out.println((a == b));
      
      输出结果为:true。
      原因很简单,"hello"+2在编译的时候就已经优化成了"hello2",因此在运行期间,变量a和b是指向同一个对象。
      
    2. 下面这段代码的输出结果是什么?

      String a = “hello2″;    
      String b = “hello”;       
      String c = b + 2;       
      System.out.println((a == c));
      
      输出结果为:false。
      由于有符号引用存在,所以String c = b + 2;  在编译的时候不会优化成“hello2”,因此这种方式生成的对象是保存在堆上。所以a和c指向的并不是同一个对象。
      
    3. 下面这段代码的输出结果是什么?

      String a = “hello2″;     
      final String b = “hello”;       
      String c = b + 2;       
      System.out.println((a == c));
      
      输出结果为:true。
      对于被final修饰的变量,会在class文件的常量池中存在,也就是说,不会通过连接访问,对final变量的访问在编译期间都会直接被替换成真实的值。
      因此String c = b + 2;  在编译的时候就会被优化成String c = “hello” + 2; 
      

个人的一些见解,若有不对的地方,希望大家能帮我指正。

安利一波国内在维护的一款Linux系统-deepin,深度,好用!好用!好用!

猜你喜欢

转载自blog.csdn.net/qq_37872792/article/details/83451582
今日推荐