String类核心讲解
在Java中,String
对象是不可变的,一旦String
对象被创建,其值就不能被修改。String
类提供了一些对字符串操作的方法,如比较字符串、查找字符串、提取子串等方法。
主要方法
返回值类型 | 方法 | 描述 |
---|---|---|
char | charAt(int index) | 返回指定索引的单个字符 |
String | concat(String str) | 在原字符串的末尾连接指定的字符串 |
boolean | equals(Object anObject) | 比较当前字符串与指定对象是否相等 |
int | indexOf(int ch) | 返回指定字符在字符串中第一次出现的索引值 |
String | replace(char oldChar, char newChar) | 用新的字符替换原来的字符,并返回替换后的字符串 |
String[] | split(String regex) | 按照匹配所给的正则表达式分割字符串,返回值为字符串数组 |
String | substring(int beginIndex, int endIndex) | 返回索引值为beginIndex 到endIndex 之间的字符串 |
Char[] | toCharArray() | 将字符串转换为一个字符数组 |
String | trim() | 去除字符串首尾的空格 |
String对象不可变的原因
JDK8中String
的部分源码如下
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** 字符串的值用一个字符数组来存储 */
private final char value[];
......
public String() {
this.value = "".value;
}
/** contact方法 **/
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);
}
/** replace方法 **/
public String replace(char oldChar, char newChar) {
if (oldChar != newChar) {
int len = value.length;
int i = -1;
char[] val = value;
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方法 **/
public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > value.length) {
throw new StringIndexOutOfBoundsException(endIndex);
}
int subLen = endIndex - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return ((beginIndex == 0) && (endIndex == value.length)) ? this
: new String(value, beginIndex, subLen);
}
......
}
从源码中我们可以看到String
这个类是用一个char
数组存储字符,并且使用了final
关键字修饰,而final
修饰的类、方法、变量均是不可修改的。而contact
、replace
、substring
等方法都不是在原有的字符串上进行的,而是重新生成了一个新的字符串对象。也就是说进行这些操作后,最原始的字符串并没有被改变。
String s=new String()与String s = ""的区别
public static void main(String[] args) {
String s1 = "Java";
String s2 = new String("Java");
String s3 = "Java";
String s4 = new String("Java");
System.out.println(s1==s2);
System.out.println(s1==s3);
System.out.println(s2==s4);
}
上述代码的输出结果为:
String s1 = "Java"
会先去String常量池
中查找有没有已经存在的引用,如果没有,声明的s1
会直接生成一个String对象
,并且会在String常量池
中存入一个引用指向这个String对象。
之后直接声明的字符串同样也会遵循上面的步骤,所以第二次String s3 = "Java"
从String常量池
中找到了一个引用指向第一次声明的字符串对象。而new String("Java")
这样会直接在堆中创建新的对象。
StringBuilder
与StringBuffer
String
对象重载了+
操作符,可用于连接String
,比如这句
String string = ""; string += "Java";
string += "Java"
,这行代码的过程相当于将原有的string变量指向的对象内容取出与"Java"作字符串相加操作再存进另一个新的String对象当中,再让string变量指向新生成的对象。
Test.Java
String s0 = "";
for (int i = 0; i < 10; i++)
s0 += "java";
对上述代码进行反编译,得到如下输出:
从第11行开始到44行是循环代码的执行过程,我们可以看到每次循环会new出一个StringBuilder
对象,然后进行append
操作,最后通过toString
方法返回String对象
,在源代码中并没有使用到StringBuilder
这个类,但是编译器却使用了它,这是因为StringBuilder
更高效。
StringBuilder
StringBuilder
是可变的字符序列,主要的操作有append
和insert
这两个方法,append
方法通常在字符串最后添加字符,insert
方法是在指定索引处插入字符。每个String builder都有一个容量值,只要string builder中的字符序列的长度没有超过容量值就不需要分配新的缓冲区,如果缓冲区溢出它会自动调节变得更大些。StringBuilder
中默认的构造方法中初始化的容量为16
,因此在使用的时候尽量考虑字符串的长度是否超过了默认的容量,或者我们可以自己指定初始化的容量,在一定程度上能提升程序的性能。需要注意的是StringBuilder
是非线程安全的,只能在单线程的环境下使用,如果需要在多线程中保持同步,请使用StringBuffer
。
StringBuilder
线程不安全的原因
public static void main(String[] args) throws InterruptedException {
StringBuilder stringBuilder = new StringBuilder();
//创建10个线程
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {
fixedThreadPool.execute(new Runnable() {
@Override
public void run() {
stringBuilder.append("A");
}
});
}
System.out.println(stringBuilder.length());
}
输出结果总是小于预期值1000,说明多线程环境下StringBuilder
是非线程安全,具体原因我们分析一下append
方法源码
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence{
//StringBuilder中的append方法,通过调用父类AbstractStringBuilder中的append实现
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
}
AbstractStringBuilder
中append
方法实现源码:
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;
}
count += len
这行代码不是原子操作,因此多线程环境下存在线程不安全问题。
StringBuffer
StringBuffer
与StringBuilder
最大的区别就是前者是线程安全的,阅读StringBuffer
的源码可知,它在每个操作方法前都使用了synchronized
关键字修饰以保证多线程环境下的数据同步。但是这样明显影响性能,因此,在单线程环境中使用StringBuilder
即可。
参考资料
《Java编程思想》