上节课我们讲了String是什么,怎么使用,这节课我们就来分析分析String的底层源码。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
可以看到String类实现了三个接口,前面两个我们已经见过了,就是序列化和可比较,最后一个CharSequence是字符队列,接下来看看CharSequence的代码。
public interface CharSequence {
int length();
char charAt(int index);
CharSequence subSequence(int start, int end);
public String toString();
public default IntStream chars() {
class CharIterator implements PrimitiveIterator.OfInt {
int cur = 0;
public boolean hasNext() {
return cur < length();
}
public int nextInt() {
if (hasNext()) {
return charAt(cur++);
} else {
throw new NoSuchElementException();
}
}
@Override
public void forEachRemaining(IntConsumer block) {
for (; cur < length(); cur++) {
block.accept(charAt(cur));
}
}
}
return StreamSupport.intStream(() ->
Spliterators.spliterator(
new CharIterator(),
length(),
Spliterator.ORDERED),
Spliterator.SUBSIZED | Spliterator.SIZED | Spliterator.ORDERED,
false);
}
public default IntStream codePoints() {
class CodePointIterator implements PrimitiveIterator.OfInt {
int cur = 0;
@Override
public void forEachRemaining(IntConsumer block) {
final int length = length();
int i = cur;
try {
while (i < length) {
char c1 = charAt(i++);
if (!Character.isHighSurrogate(c1) || i >= length) {
block.accept(c1);
} else {
char c2 = charAt(i);
if (Character.isLowSurrogate(c2)) {
i++;
block.accept(Character.toCodePoint(c1, c2));
} else {
block.accept(c1);
}
}
}
} finally {
cur = i;
}
}
public boolean hasNext() {
return cur < length();
}
public int nextInt() {
final int length = length();
if (cur >= length) {
throw new NoSuchElementException();
}
char c1 = charAt(cur++);
if (Character.isHighSurrogate(c1) && cur < length) {
char c2 = charAt(cur);
if (Character.isLowSurrogate(c2)) {
cur++;
return Character.toCodePoint(c1, c2);
}
}
return c1;
}
}
return StreamSupport.intStream(() ->
Spliterators.spliteratorUnknownSize(
new CodePointIterator(),
Spliterator.ORDERED),
Spliterator.ORDERED,
false);
}
}
解释一下为什么接口里的一些方法有具体实现。
可以看到这些被实现了的方法在声明的时候都加了default关键字,这个是jdk1.8的新特性,表示默认方法,加了这个关键字方法就可以在接口里实现
我们主要看那些未被实现的方法。
int length(); //返回字符队列的长度
char charAt(int index); //返回index位置的字符
CharSequence subSequence(int start, int end); //提取字串,返回值类型为CharSequence
public String toString(); //将本对象转换成字符串
接下来看看String类
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L;
这些是String类的属性
主要看value
value属性是干什么的?
value是一个字符数组char[],用于储存字符串的值,一切对字符串的操作本质是对这个字符操作。
接下来看几个构造器
public String() {
this.value = "".value;
}
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
说明一下,Arrays.copyOf的功能是构造一个新的数组,然后将原数组里面的元素复制一份放到新数组里,具体源码太复杂了,就不看了。
我们主要用到的就这三个构造器,其他的用的不多。
接下来看一些方法的实现。
public int length() {
return value.length;
}
可以看到length方法就是直接返回数组的长度,这也说明了value就是保存字符串内容的对象,而对字符串的操作本质上是对value的操作
public boolean isEmpty() {
return value.length == 0;
}
isEmpty方法用于判断字符串是否是空串(“”),如果为空则返回true,否则返回false
public char charAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return value[index];
}
throw是抛异常,这个以后说,可以看到返回值就是value[index]
void getChars(char dst[], int dstBegin) {
System.arraycopy(value, 0, dst, dstBegin, value.length);
}
getChars()方法用于提取字串到dst,具体作用是复制value到dst的dstBegin位置
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
if (srcBegin < 0) {
throw new StringIndexOutOfBoundsException(srcBegin);
}
if (srcEnd > value.length) {
throw new StringIndexOutOfBoundsException(srcEnd);
}
if (srcBegin > srcEnd) {
throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
}
System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}
这个很显然是提取子串复制到dst的dstBegin位置
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;
}
判断字符串和anObject对象的值是否相等,具体表现为
首先看本引用变量和anObject是否引用同一对象,如果是,则返回true,如果不是
看anObject是不是String类型,如果不是直接返回false,否则
强转一下类型,然后比较本对象和anotherString的value是否一样,不一样就 返回false,否则返回true
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;
}
public boolean startsWith(String prefix) {
return startsWith(prefix, 0);
}
startwith,判断字符串是否以某个字符串开头
还有个endWith是判断字符串是否以某个字符串结尾。
public int indexOf(String str)
返回str第一次在本字符串对象中出现的位置,如果不存在返回-1
还有个lastIndexOf,返回str最后一次在本字符串对象中出现的位置,如果不存在返回-1
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);
}
substring,提取字串,可以看到返回的是否new了一个新的字串
public String substring(int beginIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
int subLen = value.length - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}
substring如果只传一个参数就提取beinIndex直到字符串结尾的字串,返回的时候还是new了一个新的String
public CharSequence subSequence(int beginIndex, int endIndex) {
return this.substring(beginIndex, endIndex);
}
这个不用我说都看得懂
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);
}
这个就是连接字符串
public String replace(CharSequence target, CharSequence replacement)
由于String实现了CharSequence接口,可以直接传String对象,作用是用replacement替换target,然后返回被替换后的String对象,注意,replace不会对原对象造成影响,而是new一个新的String对象来进行replace操作
之后还有replaceFirst和replaceAll,作用分别是替换第一次和替换所有
public String[] split(String regex)
将字符串对象以regex分割成字符串数组
比如:
public static void main(String[] args) {
String s = "com.helloworld.teach";
String[] split = s.split("\\."); //这个\\.是转义字符,regex是一个正则表达式,这个以后再讲
for (String s1 : split) {
System.out.println(s1);
}
//再举一个例子
String str = "aaaa/b/ccccd/b/aaaccc";
String[] bs = str.split("b");
for (String b : bs) {
System.out.println(b);
}
}
运行结果:
com
helloworld
teach
aaaa/
/ccccd/
/aaaccc
最后说一点:
字符串可以用 + 来连接
字符串和数字做+运算会先将数字变成字符串然后进行字符串连接操作