String
String的特性
String是不可变以及线程安全的
为什么String不可变
先来看一下String的源码
注意:此处源码为Java9以及之后的源码
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence,
Constable, ConstantDesc {
//@Stable注解表示从null和0变为非null和非0的值,只能有一次;也就是变量最多被修改一次,称为“稳定的”。
@Stable
private final byte[] value;
private final byte coder;
/** Cache the hash code for the string */
private int hash; // Default to 0
可以发现,String类是由final关键字修饰的类,并且通过final关键字修饰的byte数组来保存字符串
由final修饰的类不能继承,修饰的引用类型变量不能再指向其他对象。
不能被继承则保证子类不能破坏String的不可变性;保存字符串的byte数组被final修饰且为私有,通过查看String类源码并未找到能直接修改这个数组的方法。
需要注意:不论是String提供的replaceAll()等方法还是字符串拼接用的“+”,本质上还是利用StringBuilder类重新创建一个String对象
public String replaceAll(String replacement) {
reset();
boolean result = find();
if (result) {
StringBuilder sb = new StringBuilder();
do {
appendReplacement(sb, replacement);
result = find();
} while (result);
appendTail(sb);
return sb.toString();
}
return text.toString();
}
所以String不可变
String不可变有什么好处
线程安全性:由于String是不可变的,可以理解为常量,所以线程安全
支持hash映射和缓存:String的hash值经常被用作Map的键值,因为不可变的特性,所以hash的值也不会改变,不需重新计算
为什么要将String的实现从char[]修改为byte[]
以下是String在JDK8版本的实现
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
从char[]修改为byte[]主要是为了节约内存,对于新版的String,支持俩种编码方案,一种是Latin-1,另一种是UTF-16(两个字节),如果字符串中包含的字符没有超过 Latin-1 可表示范围内的字符,那就会使用 Latin-1 作为编码方案,超过则用UTF-16。
String的常用方法
length():返回字符串长度。
equals() :此字符串与指定的对象比较。
substring():截取字符串。 equals():字符串比较。
split():分割字符串,返回一个分割后的字符串数组。
indexOf():返回指定字符的索引。
charAt():返回指定索引处的字符。
replace():字符串替换。
trim():去除字符串两端空白。
getBytes():返回字符串的 byte 类型数组。
toLowerCase():将字符串转成小写字母。
toUpperCase():将字符串转成大写字符。
更具体的,请查看https://www.runoob.com/java/java-string.html
String的equals()方法
String 中的 equals 方法是重写过的,比较的是 String 字符串的值是否相等。
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
而对于Object 的 equals 方法,则是比较的对象的内存地址。
字符串常量池
字符串常量池 是 JVM 为了提升性能和减少内存消耗针对字符串(String 类)专门开辟的一块区域,主要目的是为了避免字符串的重复创建。
在创建字符串时,JVM会首先检查字符串常量池,如果字符串已经存在字符串常量池,则返回引用;若不存在,则将其放入常量池并放入其引用。
JDK1.7 之前,字符串常量池存放在永久代(方法区实现)。JDK1.7 字符串常量池和静态变量从永久代移动了 Java 堆中。
原因则是在永久代中,GC执行的效率远低于整堆执行的效率。在Java程序中往往需要大量的String字符串,这就导致也有大量的String字符串等待回收,所以将字符串常量池放入堆中更加。
String s1 = new String(“Hello”)创建了几个字符串对象?
如果字符串常量池存在该对象,则只需要在堆中创建一个字符串对象;若是字符串常量池不存在该对象,则先在字符串常量池中创建,然后在堆空间中创建,因此将创建总共 2 个字符串对象。
String、StringBuilder和StringBuffer的区别
可变性
String不可变
StringBulder和StringBuffer可变,例如他们提供的方法append()
public final class StringBuilder
extends AbstractStringBuilder
implements Serializable, CharSequence
public final class StringBuffer
extends AbstractStringBuilder
implements Serializable, CharSequence
abstract class AbstractStringBuilder implements Appendable, CharSequence {
/**
* The value is used for character storage.
*/
char[] value;
/**
* The count is the number of characters used.
*/
int count;
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
通过上述三段代码可以发现,StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类;在AbstractStringBuilder 类中也是利用数组来存储字符串的数值,但是并未用final修饰
线程安全性
String是线程安全的;StringBuffer使用synchronized将方法声明为同步方法,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
总结
(1)操作少量的数据用 String;
(2)多线程操作字符串缓冲区下操作大量数据 StringBuffer;
(3)单线程操作字符串缓冲区下操作大量数据 StringBuilder
考虑到以上特性
String由于是每次拼接都要重新生成新的对象,所以速度最慢;StringBuffer由于要考虑线程安全【append方法的使用synchronized关键字】,在拼接大量数据时,又比StringBuilder效率低一些
所以,一般情况下,字符串拼接速度从快到慢为StringBuilder > StringBuffer > String