Java 字符串比较、拼接问题

@html


/—————————————————— 字符串类型—————————————————/
Java中用于处理字符串经常使用的有三个类:

一、java.lang.Stringjava

二、java.lang.StringBuffer数组

三、java.lang.StrungBuilder安全

相同点: 都是final类, 不容许被继承;多线程

不一样点:app

  • StringBuffered/StringBuilder 都继承自抽象类AbstractStringBuilder
    (实现了Appendable, CharSequence接口),能够经过append()、indert()进行字符串的操做
  • String实现了三个接口: Serializable、Comparable 、CarSequence,
    String的实例能够经过compareTo方法进行比较
    StringBuilder/StringBuffer只实现了两个接口Serializable、CharSequence
  • StringBuffer是线程安全的(Synchronized 加锁),能够不须要额外的同步用于多线程中
    StringBuilder不是线程安全的,可是效率比StringBuffer高

/—————————————本篇主要讨论String类型————————————/性能

1.字符串的比较

1. 1 字符串常量池

字符串常量池(如下简称常量池/字符串池)的存在乎义:实际开发中,String类是使用频率很是高的一种引用对象类型。可是不断地建立新的字符串对象,会极大地消耗内存。所以,JVM为了提高性能和减小内存开销,内置了一块特殊的内存空间即常量池,以此来避免字符串的重复建立。JDK 1.8 后,常量池被放入到堆空间中。学习

字符串池中维护了共享的字符串对象,这些字符串不会被垃圾收集器回收。优化

1.2 String类型的比较方式

若直接使用“==”进行比较对象,则比较的是两个对象的引用地址;ui

若使用str1.equals(str2)方法进行比较,因为String类内部已经覆盖Object类中的equals()方法,实际比较的是两个字符串的值。

  • 比较原理:
    先判断对象地址是否相等,若相等则直接返回true;
    若不相等再去参数判断括号内传入的参数是否为String类型的:若不是字符串将最终返回false;如果字符串,再依次比较全部字符是否同样。
// 源码
public boolean equals(Object anObject) {
	if (this == anObject) {
    	return true;
    }
    if (anObject instanceof String) {
        String aString = (String)anObject;
        if (coder() == aString.coder()) {
        	return isLatin1() ? StringLatin1.equals(value, aString.value) : StringUTF16.equals(value, aString.value);
        }
     }
     return false;
}

1.3 String的建立方式

1.3.1 直接使用“=”进行赋值

String str_01 = "aa";
String str_02 = "aa";
System.out.println(str_01 == str_02);

使用这种方式建立字符串,会先在栈中建立一个引用变量str_01,再去常量池中寻找是否已存在值为"aa"的字符串:

  1. 若是不存在这样的字符串,则会在常量池中新建一个"aa"字符串对象,并把这个字符串对象的引用地址赋值给对象str_01;
  2. 若是常量池中寻找已存在这样的字符串,则不会再建立新的对象,直接返回已存在的对象地址,并将其赋值给对象str_02;
// result
true

1.3.2 使用“new”关键字建立新对象

String str_01 = new String("xyz");
String str_02 = new String("xyz");
System.out.println(str_01 == str_02);

这种方式至少会建立一个对象,由于本质是调用了String类的构造器方法public String(String original){...},在堆中必定会建立一个字符串对象。

使用"new"关键字创造对象主要分为三步:

  1. 在堆中会建立一个字符串对象;
  2. 判断常量池是否存在与构造器参数中的字符串值相等的常量;
  3. 若是常量池中已有这样的字符串存在,则直接返回堆中的字符串对象引用地址,赋值给栈中的变量;若是不存在,会先建立一个字符串对象在常量池中,而后返回堆中的对象引用地址,赋值给栈中的变量。
// result
false

1.3.3 intern()方法返回的引用地址

String str_01 = new String("abc").intern();
String str_02 = "abc";
String str_03 = new String("abc");
System.out.println(str_01 == str_02);
System.out.println(str_02 == str_03);

String str_04 = new String("cba");
String str_05 = new String("cba").intern();
System.out.println(str_04 == str_05);

当使用构造器建立字符串调用 intern()方法时,若是常量池中已经存在一个值相同的字符串(内部使用equals()方法来肯定),则返回常量池中的字符串对象的引用地址;不然,将堆中新建立的字符串对象添加到常量池中,并返回池中字符串对象的引用地址。

// result
true
false
false

2. 字符串类的可变性与不可变性

字符串的本质:char类型数组 private final char[] str
String类实现了CharSequence接口

String类型的不可变性指的是内存地址不可变,若是将一个对象从新赋值,则本质上是改变了其引用对象。

String a = "hello";
System.out.println(a.hashCode());
a = "hey";
System.out.println(a.hashCode());
// result
99162322
103196

StringBuffer类型和StringBuilder类型的字符串定义好后能够进行值改变,而且不会建立新的内存地址。

StringBuilder a = new StringBuilder();
System.out.println(a.hashCode());
a.append("Hello");
a.append("World");
System.out.println(a.hashCode());
// result
1395089624
1395089624

3. 字符串的相加/拼接

3.1 字符串与非字符串类型的相加/拼接

String类中的valueOf(Object obj)方法能够将任意一个对象转换为字符串类型。

// 源码
public static String valueOf(Object obj) {
  return (obj == null) ? "null" : obj.toString();
}

String类中,重载了+与+=运算,这也是Java中惟一重载的两个运算符。
两个字符串相加便是字符串的拼接,在进行拼接时,会先调用valueOf(Object obj)方法将其为字符串类型,再进行拼接。从源码能够看出,若是字符串为null,会将其转换为字面值为"null"的字符串。

String s = null;
s = s + "World";
System.out.println("Hello " +s);
// result: Hello nullWorld

所以在进行字符串拼接时,初始字符串应该设置成空字符串"",而非null。

3.2 两个String类型对象相加/拼接原理

在字符串间使用加法运算时:

  • 如果常量字符串相加,如: "AB"+"CD",则是编译优化。
    凡是单独使用双引号" "引用起来的内容直接拼接时,均会被编译优化,编译时就已经肯定其值,即为拼接后的值。
  • 如果字符串变量相加,如:
    String temp1 = "AB";
    String temp2 = "CD";
    String str = temp1 + temp2;
    则是在底层调用了StringBuilder类中的构造方法及append()方法来辅助完成:
    String str = new StringBuilder().append(temp1).append(temp2).toString();
String str1 = "ABCD";
		String str2 = "AB" + "CD";
		String str3 = "A" + "B" + "C" + "D";
		String temp1 = "AB";
		String temp2 = "CD";
		String str4 = temp1 + temp2;
		// String str4 = new StringBuilder().append(temp1).append(temp2).toString();
		
		String temp = "AB";
		String str5 = temp + "CD";
		// String str4 = new StringBuilder(String.valueOf(temp)).append("CD").toString();
		
		System.out.println(str1 == str2);
		System.out.println(str1 == str3);
		System.out.println(str1 == str4);
		System.out.println(str1 == str5);
// result
true
true
false
false

4. final类型的String类字符串

public class test {
	public static final String str1 = "abc";
	public static final String str2 = "def";
	public static void main(String[] args) {
		String str3 = str1 + str2;
		String str4 = "abcdef";
		System.out.println(str3 == str4);
	}
}

str1和str2都是final类型的,而且在编译阶段都是已经被赋值了,至关于一个常量,当执行Strings str3 = str1 + str2 的时候,str3已是"abcdef"常量了,已被建立在常量池中,因此地址是相等的。

// result
true
public class test {
public static final String s1;
public static final String s2;
	static{
	s1 = "ab";
	s2 = "cd";
	}
	public static void main(String[] args) {
		String s3 = s1 + s2;
		String s4 = "abcd";
		System.out.println(s3 == s4); 
	}
}

虽然s1和s2都是final类型,可是起初并无初始化,在编译期还不能肯定具体的值,此处是变量,因此这里会调用StringBuilder类中的构造方法及append()方法来建立新的字符串s3,返回的新字符串s3在堆中的地址,因此与s4不相等。

// result
false

参考内容:

  1. java-String常量池的知识点你知道多少?-结合jdk版本变动 by hz90s
  2. java中String、StringBuffer和StringBuilder的区别(简单介绍) by 韦邦杠
  3. Java String:字符串常量池(转)by 暖暖-木木

If you have any question, please let me know, your words are always welcome.* 新人入坑,若有错误/不妥之处,欢迎指出,共同窗习。

相关文章
相关标签/搜索