0%

Java String

String

java中的String不是基本类型,是一个不可变对象,String类是final类型的,不可被继承。

null,””,new String()的区别

null 表示String还没有new,也就是说对象的引用还没有创建,也没有分配内存空间给它;而””、new String()则说明String已经new了,只不过内部为空,但是它创建了对象的引用,是需要分配内存空间的。

字符串池

每当我们创建一个字符串对象时,首先就会检查字符串池中是否存在字面值相等的字符串。如果有,则不再创建,直接返回字符串池中该对象的引用,若没有则创建新对象然后放入到字符串池中并且返回新建对象的引用。这个机制对提高效率,减少内存空间的占用有很大作用。所以在使用字符串的过程中,推荐使用直接赋值(即String s="aa"),除非有必要才去新建一个String对象(即String s = new String("aa")) 看下面的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class TestString {
public static void main(String[] args){
String str1 = "hello";
String str2 = new String("hello");
String str3 = "hel"+"lo";
String str4 = new String("hello");
String str5 = "hel";
String str6 = str5 + "lo";

System.out.println(str1 == str2);
System.out.println(str1 == str3);
System.out.println(str2 == str4);
System.out.println(str6 == str1);
}
}

输出结果为:

1
2
3
4
false
true
false
false

首先来看,str1 == str2,str1使用字面常量创建字符串hello保存在字符串常量池中,str2使用new关键字创建一个值为hello的字符串对象,保存在堆中。两者的内存地址自然不同。str1 == str3呢?这里要注意的是在使用+拼接字面字符串时,jvm会在编译时执行拼接操作,将结果保存在str3中,由于字符串常量池中已经存在hello字符串了,所以在创建变量str3时,jvm返回了hello的地址,一次打印出true。而str2==str4,str2与str4都使用new关键字创建了值为hello的字符串对象,但两者在堆上的地址并不相同。最后,str6==str1,str6是通过str5和“lo”计算得到的字符串,这种程序中计算得到的字符串是不会放在字符串常量池的,所以其引用地址与str1不同

intern方法:

1
2
3
4
String str1 = "hello";
String str2 = new String("hello");
System.out.println(str1 == str2);
System.out.println(str1 == str2.intern());

结果:

1
2
false
true

在使用intern方法后,返回一个字符串常量池中的字符串,此时str与1与str.intern()返回的字符串的地址相同。

StringBuffer

StringBuffer和String一样都是用来存储字符串的,只不过由于他们内部的实现方式不同,导致他们的使用范围不同。对于StringBuffer而言,他在处理字符串时,若是对其进行修改操作,它并不会产生一个新的字符串对象,所以说在内存使用方面它是优于String的

StringBuilder

StringBuilder也是一个可变的字符串对象,他与StringBuffer不同之处就在于它是线程不安全的,基于这点,它的速度一般都比StringBuffer快。与StringBuffer一样,StringBuider的主要操作也是append与insert方法。这两个方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符添加或插入到字符串生成器中

三者比较

类型 可变 线程 操作
String 字符串常量 不可变 线程安全 产生一个新对象
StringBuffer 字符串变量 可变 线程安全 改变自身内容
StringBuilder 字符串变量 可变 线程安全 改变自身内容

并不是所有的String字符串操作都会比StringBuffer慢,在某些特殊的情况下,String字符串的拼接会被JVM解析成StringBuilder对象拼接,在这种情况下String的速度比StringBuffer的速度快。如:

1
2
String name = ”I  ” + ”am ” + ”chenssy ” ;
StringBuffer name = new StringBuffer(”I ”).append(” am ”).append(” chenssy ”);

对于这两种方式,你会发现第一种比第二种快太多了,在这里StringBuffer的优势荡然无存。其真实的原因就在于JVM做了一下优化处理,其实String name = ”I ” + ”am ” + ”chenssy ” ;在JVM眼中就是String name = ”I am chenssy ” ;这样的方式对于JVM而言,真的是不要什么时间。但是如果我们在这个其中增加一个String对象,那么JVM就会按照原来那种规范来构建String对象了

字符串拼接方式

java中有三种拼装的方法:+、concat()以及append()方法

+ 拼接字符串

我们知道编译器对+进行了优化,它是使用StringBuilder的append()方法来进行处理的,我们知道StringBuilder的速度比StringBuffer的速度更加快,但是为何运行速度还是那样呢?主要是因为编译器使用append()方法追加后要同toString()转换成String字符串,也就说 str +=”b”等同于str = new StringBuilder(str).append("b").toString();
它变慢的关键原因就在于new StringBuilder()toString()

concat()

1
2
3
4
5
6
7
8
9
10
public String concat(String str) {
int otherLen = str.length();
if (otherLen == 0) {
return this;
}
char buf[] = new char[count + otherLen];
getChars(0, count, buf, 0);
str.getChars(0, otherLen, buf, count);
return new String(0, count + otherLen, buf);
}

这是concat()的源码,它看上去就是一个数字拷贝形式,我们知道数组的处理速度是非常快的,但是由于该方法最后是这样的:return new String(0, count + otherLen, buf);这同样也创建了10W个字符串对象,这是它变慢的根本原因

append()

1
2
3
4
public synchronized StringBuffer append(String str) {
super.append(str);
return this;
}

StringBuffer的append()方法是直接使用父类AbstractStringBuilder的append()方法,该方法的源码如下

1
2
3
4
5
6
7
8
9
10
11
public AbstractStringBuilder append(String str) {
if (str == null) str = "null";
int len = str.length();
if (len == 0) return this;
int newCount = count + len;
if (newCount > value.length)
expandCapacity(newCount);
str.getChars(0, len, value, count);
count = newCount;
return this;
}

与concat()方法相似,它也是进行字符数组处理的,加长,然后拷贝,但是请注意它最后是返回并没有返回一个新串,而是返回本身,也就说这这个10W次的循环过程中,它并没有产生新的字符串对象