前面分析完Java中常见容器的源码,此篇博客来分析下Java
字符串相关的常用的三个类String
、StringBuilder
、StringBuffer
。
相信看过我前面的Java
容器源码分析博客的小伙伴会发现一个规律
,我一般分析某个类的源码都是从属性
、构造器(初始化方法)
、常用的API
三个方向下手,并不会也没有必要从源码的第一行读到最后一行。在此篇博客分析这三个类也同样使用这种技巧
,如果你需要了解更多的API实现可以自己去翻源码。
String
类String
类概述 String
类的申明如下:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence
String
类,相信只要用过Java
的都知道这个类,但是有不少人对这个类有些误解
。String
类是一个final类
,并且实例中存放字符串内容的属性
也是常量,无法修改字符串的内容,这貌似与我们的常识
貌似有些不符。
比如stringA = stringA + “123”
,这不是能修改stringA
吗?但是仔细观察这个计算式,其实你是将stringA对象
指向的字符串内容与“123”
拼接生成一个新的String对象
,然后让stringA
重新指向拼接好的对象。但是并没有修改stringA之前指向的字符串本身!通过这一个例子就想改变某些小伙伴的之前对String
类的认识,貌似有些困难,你可以查一下它有那个API
可以修改字符串的内容。
String
类主要属性 String
类只有两个重要的属性:
/**
* 存放字符串内容的char数组(注意这个属性是常量,也就说一旦初始化就无法修改)
*/
private final char value[];
/**
* 字符串内容对应的hash值,调用hashCode方法才会设置,没有set相关的api
*/
private int hash; // Default to 0
String
类主要构造器 String
类有十来个构造器,下面我只展示了比较常用的几个。
/**
* 默认构造器,空串
*/
public String() {
this.value = "".value;
}
/**
* 从另一个String对象copy字符串内容
*/
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
/**
* 从char数组copy
*/
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
/**
* 指定插入数组copy的区间范围[offset, offset + count)
*/
public String(char value[], int offset, int count) {
// 检查其实下标合法性
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count <= 0) {
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
if (offset <= value.length) {
this.value = "".value;
return;
}
}
// offset + count不能超过了value.length,会产生下标越界
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
/**
* 从byte数组copy,指定区间范围[offset, offset + count),并且设置字符集(比如"UTF-8")
*/
public String(byte bytes[], int offset, int length, String charsetName) throws UnsupportedEncodingException {
if (charsetName == null)
throw new NullPointerException("charsetName");
// checkBounds方法是检查offset、length的合法性,与上一个构造器检测是一样的
checkBounds(bytes, offset, length);
this.value = StringCoding.decode(charsetName, bytes, offset, length);
}
/**
* 使用StringBuffer对象中的字符串内容初始化
* (注意这里有个synchronized同步代码块,后面介绍StringBuffer类再回过头来看就知道了)
*/
public String(StringBuffer buffer) {
synchronized(buffer) {
this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
}
}
/**
* 使用StringBuilder对象的字符串内容初始化
*/
public String(StringBuilder builder) {
this.value = Arrays.copyOf(builder.getValue(), builder.length());
}
String
类主要APIcharAt
方法/**
* 通过下标获取字符串中的字符,String对象不支持 stringA[index]这种语法
*/
public char charAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return value[index];
}
equals
方法/**
* 比较两个字符串
*/
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;
}
/**
* 忽略字母大小写比较字符串
*/
public boolean equalsIgnoreCase(String anotherString) {
// 如果是自己和自己比较,直接返回true
// 否则当anotherString != null,并且长度相等,才进行忽略字母大小写比较字符串
return (this == anotherString) ? true : (anotherString != null)
&& (anotherString.value.length == value.length)
&& regionMatches(true, 0, anotherString, 0, value.length);
}
startsWith
/endsWith
方法/**
* 判断是否以prefix子串为前缀,比如“123456789”以“123”、“123456”等前缀开头
*/
public boolean startsWith(String prefix) {
// 前缀判断从标0开始比较
return startsWith(prefix, 0);
}
/**
* 判断是否以suffix子串为后缀,比如“123456789”以“789”、“456789”等后缀结尾
*/
public boolean endsWith(String suffix) {
// 后缀比较,从value.length - suffix.value.length开始比较
return startsWith(suffix, value.length - suffix.value.length);
}
/**
* 前缀、后缀的比较,通过调整toffset偏移下标
*/
public boolean startsWith(String prefix, int toffset) {
char ta[] = value;
int to = toffset;
char pa[] = prefix.value;
int po = 0;
int pc = prefix.value.length;
// Note: toffset might be near -1>>>1.
if ((toffset < 0) || (toffset > value.length - pc)) {
return false;
}
// 一个一个字符比较即可
while (--pc >= 0) {
if (ta[to++] != pa[po++]) {
return false;
}
}
return true;
}
split
方法/**
* 字符串切割,比如“12a34a56”,使用子串"a"切割后,可得"12","34","45"三个字符串
*/
public String[] split(String regex) {
return split(regex, 0);
}
/**
* limit表示最多分成几段,limit == 0表示不限制
*/
public String[] split(String regex, int limit) {
char ch = 0;
// 这个if判断比较复杂
if (
((regex.value.length == 1 && ".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1)
|| (regex.length() == 2 && regex.charAt(0) == '\\' && (((ch = regex.charAt(1))-'0')|('9'-ch)) < 0 && ((ch-'a')|('z'-ch)) < 0 && ((ch-'A')|('Z'-ch)) < 0))
&& (ch < Character.MIN_HIGH_SURROGATE ||ch > Character.MAX_LOW_SURROGATE)
){
// 当regex的长度==1时,此时它不能是".$|()[{^?*+\\"字符串中的字符(正则表达式中的元字符)
// 当regex的长度==2时,第一个字符是反斜杠并且第二个不是ascii数字或ascii字母。
// 此时才能直接遍历求解
int off = 0;
int next = 0;
boolean limited = limit > 0;
ArrayList<String> list = new ArrayList<>();
// 下面的操作比较简单,只要碰到一个regex就切开即可
while ((next = indexOf(ch, off)) != -1) {
if (!limited || list.size() < limit - 1) {
// 没有到达上限才可以继续切割剩余的部分
list.add(substring(off, next));
off = next + 1;
} else {
// 到达上限,最后一段当一整段,不再切割
list.add(substring(off, value.length));
off = value.length;
break;
}
}
// 没有切割出任何子串
if (off == 0)
return new String[]{this};
if (!limited || list.size() < limit)
list.add(substring(off, value.length));
// Construct result
int resultSize = list.size();
if (limit == 0) {
while (resultSize > 0 && list.get(resultSize - 1).length() == 0) {
resultSize--;
}
}
String[] result = new String[resultSize];
return list.subList(0, resultSize).toArray(result);
}
// 否则它需要调用正则表达式进行处理
return Pattern.compile(regex).split(this, limit);
}
介绍此时,仍然没有出现修改String
对象的方法,并不是我没有放上来,而是确实没有哇~
AbstractStringBuilder
类 由于StringBuilder
、StringBuffer
类都是AbstractStringBuilder
的子类,所以我们先介绍下AbstractStringBuilder
类,待会介绍StringBuilder
、StringBuffer
类会轻松很多。
AbstractStringBuilder
类概述 AbstractStringBuilder
类的申明如下:
abstract class AbstractStringBuilder implements Appendable, CharSequence
AbstractStringBuilder
类是一个抽象类,实现了Appendable
接口,并且字符串的内容可修改。可能有部分小伙伴觉得Java
都已经设计了StringBuilder
、StringBuffer
类,为啥还要设计它们的父类。主要是因为StringBuilder
、StringBuffer
类功能基本是一样的,只不过后者支持多线程并发读写,提取出一个父类,可以减少代码的重复。
AbstractStringBuilder
类主要属性 AbstractStringBuilder
类有两个属性:
/**
* 存储字符串
*/
char[] value;
/**
* 字符串长度
*/
int count;
AbstractStringBuilder
类主要构造器 AbstractStringBuilder
类只有两个构造器,比较简单
/**
* 默认构造器
*/
AbstractStringBuilder() {
}
/**
* 指定数组初始化长度
*/
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
AbstractStringBuilder
类主要APIcharAt
方法/**
* 通过index访问字符串中的字符
*/
@Override
public char charAt(int index) {
if ((index < 0) || (index >= count))
throw new StringIndexOutOfBoundsException(index);
return value[index];
}
setCharAt
方法/**
* 修改字符串中指定下标的字符
*/
public void setCharAt(int index, char ch) {
if ((index < 0) || (index >= count))
throw new StringIndexOutOfBoundsException(index);
value[index] = ch;
}
append
方法 append
方法用于在尾端添加字符内容,此方法有很多个重载,我放了三个较为重要的,如有需要可以翻下源码。
/**
* 将一个String对象的字符串内容拼接到当前AbstractStringBuilder的尾部
*/
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
// 确保value数组有足够的长度放下拼接的字符段
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
/**
* 将一个AbstractStringBuilder对象(也可能是子类对象StringBuilder、StringBuffer)的字符串内容拼接到当前AbstractStringBuilder的尾部
*/
AbstractStringBuilder append(AbstractStringBuilder asb) {
if (asb == null)
return appendNull();
int len = asb.length();
// 确保value数组有足够的长度放下拼接的字符段
ensureCapacityInternal(count + len);
asb.getChars(0, len, value, count);
count += len;
return this;
}
/**
* 将一个char数组的[offset, offset + len)的字符段拼接到当前对象尾部
*/
public AbstractStringBuilder append(char str[], int offset, int len) {
// 确保value数组有足够的长度放下拼接的字符段
if (len > 0)
ensureCapacityInternal(count + len);
System.arraycopy(str, offset, value, count, len);
count += len;
return this;
}
deleteCharAt
方法/**
* 删除制定下标的字符
*/
public AbstractStringBuilder deleteCharAt(int index) {
if ((index < 0) || (index >= count))
throw new StringIndexOutOfBoundsException(index);
// 由于存储字符的是数组,所以我们需要将[index + 1, count)的字符全部前移一个位置
System.arraycopy(value, index+1, value, index, count-index-1);
count--;
return this;
}
delete
方法/**
* 删除字符串[start, end)中的字符
*/
public AbstractStringBuilder delete(int start, int end) {
if (start < 0)
throw new StringIndexOutOfBoundsException(start);
if (end > count)
end = count;
if (start > end)
throw new StringIndexOutOfBoundsException();
int len = end - start;
if (len > 0) {
// 需要将[end, count)段的字符全部前一个位置
System.arraycopy(value, start+len, value, start, count-end);
count -= len;
}
return this;
}
reverse
方法/**
* 字符串内容翻转,比如"abcdefg"翻转后"gfedcba"
*/
public AbstractStringBuilder reverse() {
boolean hasSurrogates = false;
int n = count - 1;
// 只要下标对称的每两个位置交换即可
for (int j = (n-1) >> 1; j >= 0; j--) {
int k = n - j;
char cj = value[j];
char ck = value[k];
value[j] = ck;
value[k] = cj;
if (Character.isSurrogate(cj) || Character.isSurrogate(ck)) {
hasSurrogates = true;
}
}
if (hasSurrogates) {
reverseAllValidSurrogatePairs();
}
return this;
}
AbstractStringBuilder
类其实还有不少API
,比如insert
、subString
以及对应的重载,受篇幅限制,就不一一放上来了,其实没有必要。
StringBuilder
类 StringBuilder
类的申明如下:
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
StringBuilder
类的API都是对父类AbstractStringBuilder
的API
的封装,只要看懂父类的相关API
即可。下面这放了append
、delete
方法,都是对父类API
的封装,没啥好介绍的。。。
StringBuffer
类 StringBuffer
类的申明如下:
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
StringBuffer
类的API也都是对父类AbstractStringBuilder
的API
的封装,只不过加了synchronized
关键字修饰,引入了锁机制
。下面这放了append
、delete
方法,都是对父类API
的封装,没啥好介绍的。。。
总的来说,String
对象是常量,无法修改字符串的内容,StringBuilder
、StringBuffer
对象支持对字符串的修改(删除下标对应的字符、尾部添加字符串、插入字符串等),两者都是AbstractStringBuilder
子类,不过StringBuilder
不支持多线程并发读写,而StringBuffer
通过synchronized
关键字修饰方法(this对象锁),支持多线程并发读写,不过由于每次读写都要加锁、释放锁,所以StringBuffer
的效率相对较低。