String类详解

String类、StringBuilder类、StringBuffer类

一、什么是String?

1、字面解释

简单来说,用双引号引起来的几个字符组成的序列,比如:“abcdefg”、“今天天气不错呀” 这些就是字符串。

2、在Java中的角色

在Java语言中,字符串String是一个特殊的对象,属于引用类型

3、如何构造字符串(3种方式)?
String str1 = "这是第1种创建字符串的方式";
    
String str2 = new String("这是第2种创建字符串的方式")

//这是第3种创建字符串的方式:用字符数组创建字符串
char[] ch = {'h','e','l','l','o'};
String str2 = new String(ch);

二、源码以及分析

1、源码

//final关键字修饰的类、方法、变量都是不可变的。
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence,
               Constable, ConstantDesc {

    @Stable
    private final byte[] value;
                   
}
1.1、String类被关键字final修饰,是不可变类。

(这里final关键字的作用不懂的可以先去了解final)

  • String类不可被继承,所以没有子类。
  • String类的成员方法都默认为final方法,不能被覆盖(重写)。
  • String类中所有字符串都是常量,数据无法更改。
  • 我们要注意:字符串有无空格是不一样的两个字符串!
1.2、对String对象的任何改变都不会影响到原对象,相关的任何操作都会生成新的对象。
  • 不管我们对字符串做出什么操作,都不会改变原先的字符串,而是返回一个新的字符串对象。

  • 对String字符串的操作都不是在原字符串上进行的,而是重新生成了一个新的字符串对象。也就是说,对字符串进行操作后,原始字符串并没有被改变

    或者我们可以这样理解:

    ​ 每次对字符串(假设叫str)进行操作的时候,都是把str复制一份(strcopy),然后对strcopy操作。

1.3、为什么会有字符串常量池?

String类对象创建后,一旦被初始化就只有唯一的常量,不能再改变String对象的值或者数据。而字符串在Java程序中虽然是不可变的,但仍旧会被频繁使用,JVM为了提高效率并节约内存,会将内容相同的字符串共用一个String实例放在常量池中,常量池中的String常量可以共享。

2、底层(本质)

String类实际上通过char数组来保存字符串。

随机看一个源码中的方法:

public char charAt(int index) {
        if (isLatin1()) {
            return StringLatin1.charAt(value, index);
        } else {
            return StringUTF16.charAt(value, index);
        }
    }

三、深入理解String类

1、String类对象的创建

我们来看下面这段代码,并分析区别:

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

String str1 = “123”; ====> 先检测(内容)字符串常量池中是否存在str1,不存在就缓存到常量池。

String str2 = new String(“123”); ====> 直接在堆区创建新的对象。通过new关键字创建对象是在堆区进行,而在堆区创建新的对象并不会检测对象是否存在,是直接创建,即便字符串的内容相同。

2、String类的常见操作

2.1、比较字符串
  • equals()

    • 比较的是字符串内容是否相等。

    • //源码
      public boolean equals(Object anObject) {
              if (this == anObject) {
                  return true;
              }
              if (anObject instanceof String) {
                  String aString = (String)anObject;
                  if (!COMPACT_STRINGS || this.coder == aString.coder) {
                      return StringLatin1.equals(value, aString.value);
                  }
              }
              return false;
          }
      
    • 	//案例代码
      	String s1 = new String("123");
      	String s2 = "123";
      	boolean flag = s1.equals(s2);
      	System.out.print(flag);//结果为:true
      
  • ==

    • 比较的是字符串是否指向同一对象(对象地址或者叫哈希值是否相同)

    • 	String s1 = new String("123");
      	String s2 = "123";
      	boolean flag = (s1==s2);
      	System.out.print(flag);
      	//结果为:false
      
  • compareTo

    • 两个字符串相比较,返回值是int类型。
2.2、反转字符串

反转字符串的方式有很多,这里只说几种。

1、利用StringBuilder的reverse()方法

public class ReverseWithStringBuilderBuiltinMethod {
    public static void main(String[] args) {
        ReverseWithStringBuilderBuiltinMethod builtinMethod = new ReverseWithStringBuilderBuiltinMethod();
        builtinMethod.reverseWithStringBuilderBuiltinMethod("javaguides");
    }
    public String reverseWithStringBuilderBuiltinMethod(String string) {
        final StringBuilder builder = new StringBuilder(string);
        //reverse()
        display(string, builder.reverse().toString());
        
        return builder.reverse().toString();
    }
    private void display(String input, String output) {
        System.out.println(" input string :: " + input);
        System.out.println(" output string :: " + output);
    }
}

2、使用 Collections的reverse() 方法

public class ReverseStringUsingCollectionsReverseMethod {
    // Function to reverse a string in Java using Collections.reverse()
    public static String reverse(String str) {
        // base case: if string is null or empty
        if (str == null || str.equals(""))
            return str;
        // create an empty list of characters
        List < Character > list = new ArrayList < Character > ();
        // push every character of the given string into it
        for (char c: str.toCharArray())
            list.add(c);
        // reverse list using java.util.Collections reverse()
        Collections.reverse(list);
        // covert ArrayList into String using StringBuilder and return it
        StringBuilder builder = new StringBuilder(list.size());
        for (Character c: list)
            builder.append(c);
        return builder.toString();
    }
    public static void main(String[] args) {
        String str = "Java Guides";
        // String is immutable
        str = reverse(str);
        System.out.println("Reverse of the given string is : " + str);
    }
}

更多方式详见:https://www.cnblogs.com/taich-flute/p/10070143.html

四、StringBuilder类

1、源码

public final class StringBuilder
    extends AbstractStringBuilder
    implements java.io.Serializable, Comparable<StringBuilder>, CharSequence
{
    
}
  • StringBuilder类也被final关键字修饰。
  • AbstractStringBuilder是父类。

2、StringBuilder类和String类比较

我们来看这段String类代码:

String str = "123";
for(int =0;i<1000;i++){
	str += "hello";
}

分析:

循环内部的代码(str += “hello”;)实际上会自动被JVM优化!

优化成如下代码:

StringBuilder sb = new StringBuilder(str);
sb.append("hello");
sb.toString();

每次循环都会new一个StringBuilder对象出来,造成资源浪费。

所以,字符串拼接问题可以直接使用StringBuilder类解决。

五、StringBuffer类

1、源码

public final class StringBuffer
    extends AbstractStringBuilder
    implements java.io.Serializable, Comparable<StringBuffer>, CharSequence
{
    
}
  • StringBuilder类也被final关键字修饰。
  • AbstractStringBuilder是父类。

2、成员方法都是synchronized修饰的

   @Override
    public synchronized int compareTo(StringBuffer another) {
        return super.compareTo(another);
    }

    @Override
    public synchronized int length() {
        return count;
    }

    @Override
    public synchronized int capacity() {
        return super.capacity();
    }


    @Override
    public synchronized void ensureCapacity(int minimumCapacity) {
        super.ensureCapacity(minimumCapacity);
    }

六、StringBuilder和StringBuffer的区别

1、相同点

  • 拥有的成员属性和成员方法基本相同。

  • 都被final关键字修饰。

  • 父类都是AbstractStringBuilder。

    • abstract class AbstractStringBuilder implements Appendable, CharSequence {
          
      }
      

2、不同点:线程安全

StringBuffer类使用了关键字---->synchronized(同步),该关键字在多线程访问时起安全保护作用。

即:

StringBuffer是线程安全的,StringBuilder是线程不安全的。

@Override
    public synchronized void setCharAt(int index, char ch) {
        toStringCache = null;
        super.setCharAt(index, ch);
    }

    @Override
    public synchronized StringBuffer append(Object obj) {
        toStringCache = null;
        super.append(String.valueOf(obj));
        return this;
    }

    @Override
    @HotSpotIntrinsicCandidate
    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }

七、三个类的性能测试

三个类测试字符串相加的代码如下:

package string;

/**
 * @author HLM
 *	测试  不同场景下String、StringBuilder、StringBuffer的性能
 */
public class Test {
	
	private static int time = 50000;
	
	/*
	 * 测试String类的字符串相加
	 * */
	public void testString() {
		String str = " ";
		long begin = System.currentTimeMillis();
		for (int i = 0; i < time; i++) {
			str += "java";
		}
		long end = System.currentTimeMillis();
		System.out.println("String类字符串相加花费时间为:"+(end - begin)+"毫秒");
	}
	
	/*
	 * 测试StringBuilder类的字符串相加
	 * */
	public void testStringBuilder() {
		StringBuilder builder = new StringBuilder();
		long begin = System.currentTimeMillis();
		for (int i = 0; i < time; i++) {
			builder.append("java");
		}
		long end = System.currentTimeMillis();
		System.out.println("StringBuilder类字符串相加花费时间为:"+(end - begin)+"毫秒");
	}
	
	/*
	 * 测试StringBuffer类的字符串相加
	 * */
	public void testStringBuffer() {
		StringBuffer buffer = new StringBuffer();
		long begin = System.currentTimeMillis();
		for (int i = 0; i < time; i++) {
			buffer.append("java");
		}
		long end = System.currentTimeMillis();
		System.out.println("StringBuffer类字符串相加花费时间为:"+(end - begin)+"毫秒");
	}
	
	
	/*
	 * 测试String类的字符串直接相加
	 * */
	public void testZhijie() {
		long begin = System.currentTimeMillis();
		for (int i = 0; i < time; i++) {
			String str = "java" + "is" + "good";
		}
		long end = System.currentTimeMillis();
		System.out.println("String类字符串直接相加:"+(end - begin)+"毫秒");
	}
	
	
	/*
	 * 测试String类的字符串间接相加
	 * */
	public void testJianjie() {
		String str1 = "java";
		String str2 = "is";
		String str3 = "good";
		long begin = System.currentTimeMillis();
		for (int i = 0; i < time; i++) {
			String str = str1 + str2 + str3;
		}
		long end = System.currentTimeMillis();
		System.out.println("String类字符串间接相加:"+(end - begin)+"毫秒");
	}
	
	
	public static void main(String[] args) {
		Test test = new Test();
		test.testString();
		test.testStringBuilder();
		test.testStringBuffer();
		test.testZhijie();
		test.testJianjie();

	}

}

测试结果如图:

String类字符串相加花费时间为:2435毫秒
StringBuilder类字符串相加花费时间为:3毫秒
StringBuffer类字符串相加花费时间为:10毫秒
String类字符串直接相加:1毫秒
String类字符串间接相加:12毫秒

测试结果分析:

  1. String类直接相加字符串效率高,间接相加效率低。
  2. 一般性比较效率:StringBuilder > StringBuffer > String

八、常见面试题

1、字符串相等问题

有以下代码:

String a = "hellojava";
String b = "hello"+"java";
System.out.println(a == b);//true

解释:String b = “hello”+“java”; 该行代码在编译期间被JVM自动优化成“hellojava”,然后去常量池中查找"hellojava",因此变量a和b指向的是同一个对象。

又有以下代码:

String x = "hellojava";
String y = "hello";
String z = y + "java";
System.out.println(x == z);//false

解释:因为有符号引用,所以 String z = y + “java”; 编译期间不会被JVM优化。

所以生成新的对象。

再看以下代码:

String x = "hellojava";
final String y = "hello";
String z = y + "java";
System.out.println(x == z);//true

因为变量y被final修饰,final修饰的变量会在常量池保存副本,所以对final变量的访问在编译期间都会直接转换为真实值,那么 String x = “hellojava”; 在编译期间会被JVM自动优化。

有以下代码:

String x = "hellojava";
String y = new String("hellojava");
String z = y.intern();
System.out.println(x == z);//true

注意:

String类的intern方法是它的本地方法。

在JavaSE6之前,intern方法会在运行时常量池查找是否存在内容相同的字符串,如果存在就返回该字符串的引用;如果不存在,就会将该字符串存入常量池,并返回一个指向该字符串的引用。

2、创建对象的个数问题

有如下代码:

String str = new String("java");

在类加载过程中,是在运行时常量池中创建了 “java” 对象,而在代码执行过程中只创建了一个String对象。

所以,涉及两个对象,只创建一个对象。

你可能感兴趣的:(String(字符串),Java基础,字符串,java,面试)