一、前言
众所周知字符串String是不可变的,当你改变其字符串内容的时候,他的底层是重新创建一个新的字符串,并且让栈中的对象引用指向新的字符串的地址的,那到底这是怎么实现的呢?接下来我们一起去看看String字符串的底层源码是如何实现的。
二、String内部的变量有哪些?
在看String内部变量之前我们先看看String继承了哪些接口,其源码如下所示:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
String内的变量有如下所示:
/**用来存储字符串的,字符串会变成字符存在一个不可变的char数组里*/
private final char value[];
/** 其默认的hash值为0 */
private int hash;
/**序列化的值,用来反序列化时进行验证的,来判断其有没有修改过 */
private static final long serialVersionUID = -6849794470754667710L;
/**类字符串在序列化流协议中使用特殊大小写。 */
private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0];
/**这个是String的一个内部类CaseInsensitiveComparator()的实例,用来调用其内部类的compare()方法 */
public static final Comparator<String> CASE_INSENSITIVE_ORDER = new CaseInsensitiveComparator();
刚刚也说到了String里面有一个内部类,其继承了Comparator和 java.io.Serializable接口,实现了compare方法,其源码如下所示:
public static final Comparator<String> CASE_INSENSITIVE_ORDER
= new CaseInsensitiveComparator();
//内部类CaseInsensitiveComparator实现Comparator接口,重写其compare方法
private static class CaseInsensitiveComparator
implements Comparator<String>, java.io.Serializable {
// use serialVersionUID from JDK 1.2.2 for interoperability
private static final long serialVersionUID = 8575799808933029326L;
/**
* compare也是将参数s1和s2先转化为char数组,如何拿取指定下标的char来进行比较
* 不过它需要对其大小写都进行一次比较
* @param s1
* @param s2
* @return
*/
public int compare(String s1, String s2) {
int n1 = s1.length();
int n2 = s2.length();
int min = Math.min(n1, n2);
for (int i = 0; i < min; i++) {
char c1 = s1.charAt(i);
char c2 = s2.charAt(i);
if (c1 != c2) {
//转换为大写
c1 = Character.toUpperCase(c1);
c2 = Character.toUpperCase(c2);
if (c1 != c2) {
//转化为小写
c1 = Character.toLowerCase(c1);
c2 = Character.toLowerCase(c2);
if (c1 != c2) {
// No overflow because of numeric promotion
return c1 - c2;
}
}
}
}
return n1 - n2;
}
/** 替换反序列化的对象. */
private Object readResolve() { return CASE_INSENSITIVE_ORDER; }
}
三、String内的一些常用方法的源码实现
1.compareToIgnoreCase(String str)方法,从下面可知其实现原理就是调用String的内部类所实现的compare方法,compare回对其char数组内的char进行大小写转化并且判断。
/**
* 判断两个字符串的内容是否一样,不区分大小写
* @param str
* @return
*/
public int compareToIgnoreCase(String str) {
return CASE_INSENSITIVE_ORDER.compare(this, str);
}
2.startsWith(String prefix)方法,其作用是看是不是此字符串是以前缀prefix开头的,源码如下:
//其作用是看是不是此字符串是以前缀prefix开头的
public boolean startsWith(String prefix) {
return startsWith(prefix, 0);
}
可以看到其内部实现是通过**startsWith(String prefix, int toffset)**方法实现的,那次方法又是怎么实现的呢?源码如下所示:
//从字符串中,将指定的prefix字符串和toffset以后的字符串比较是否以prefix开头为前缀
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;
// 对offset的范围进行判断
if ((toffset < 0) || (toffset > value.length - pc)) {
return false;
}
while (--pc >= 0) {
if (ta[to++] != pa[po++]) {
return false;
}
}
return true;
}
3.endsWith(String suffix)方法其作用就是看字符串是不是以suffix结尾,其底层实现如下所示,可以看到其还是调用了**startsWith(String prefix, int toffset)**方法来实现的。
public boolean endsWith(String suffix) {
return startsWith(suffix, value.length - suffix.value.length);
}
4.hashCode()方法,其作用是获取当前字符串的一个hash值,源码如下所示:
public int hashCode() {
//一开始是0
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value
//用31*h加上其char上的Unicode编码值
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
5.indexOf(int ch)方法,获取字符串为ch的Unicode编码的字符的下标,其源码如下所示:
public int indexOf(int ch) {
return indexOf(ch, 0);
}
可以看出其内部实现是通过**indexOf(int ch, int fromIndex)**方法来实现的,那么这个方法的内部实现又是怎样呢?
public int indexOf(int ch, int fromIndex) {
final int max = value.length;
//对fromIndex进行范围的验证,看看有没有符合规范
if (fromIndex < 0) {
fromIndex = 0;
} else if (fromIndex >= max) {
// fromIndex可能接近-1>>>1
return -1;
}
//MIN_SUPPLEMENTARY_CODE_POINT = 0x010000,十进制值为16*16*16*16
if (ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
// handle most cases here (ch is a BMP code point or a
// negative value (invalid code point))
//这里处理大多数情况(ch是BMP代码点或一个负值(无效代码点))
final char[] value = this.value;
for (int i = fromIndex; i < max; i++)
if (value[i] == ch) {
return i;
}
}
return -1;
} else {
return indexOfSupplementary(ch, fromIndex);
}
}
6.lastIndexOf(int ch)标识从后往前数,为ch的Unicode编码的字符的下标,其源码如下所示:
public int lastIndexOf(int ch) {
return lastIndexOf(ch, value.length - 1);
}
可以看出其内部实现是通过**lastIndexOf(int ch, int fromIndex)**方法来实现的,那么这个方法的内部实现又是怎样呢?
public int lastIndexOf(int ch, int fromIndex) {
if (ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
// handle most cases here (ch is a BMP code point or a
// negative value (invalid code point))
final char[] value = this.value;
int i = Math.min(fromIndex, value.length - 1);
for (; i >= 0; i--) {
if (value[i] == ch) {
return i;
}
}
return -1;
} else {
return lastIndexOfSupplementary(ch, fromIndex);
}
}
7.substring(int beginIndex)是获取从beginIndex下标开始到最后一个位置的字符串,如何返回一个新的字符串,其源码如下所示:
public String substring(int beginIndex) {
//判断下标是否符合规范,有没有越界
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
//从begin到字符串总长度的范围
int subLen = value.length - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
//判断是不是从0开始,如果是就直接返回原字符串
return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}
8.concat(String str)则是将指定的字符串连接到此字符串的末尾,其源码如下所示:
//将指定的字符串连接到此字符串的末尾。
public String concat(String str) {
//获取要添加到当前字符串末尾的字符串的长度
int otherLen = str.length();
if (otherLen == 0) {
return this;
}
//获取当前字符串长度
int len = value.length;
//将当前字符串长度和添加的字符串长度根据Arrays.copyOf来获取一个新的char数组
char buf[] = Arrays.copyOf(value, len + otherLen);
str.getChars(buf, len);
//返回一个新的字符串
return new String(buf, true);
}
注意:从**return new String(buf, true);**可以看出,对String操作获取新的字符串其实是重新返回一个新的String对象,而不是在原字符串上修改的。
9.contains(CharSequence s)方法是判断字符串是否包含char值的指定序列,其源码如下所示:
public boolean contains(CharSequence s) {
//判断其所属的下标有没有大于-1,主要还是用到了indexOf方法
return indexOf(s.toString()) > -1;
}
10.toCharArray()方法,返回字符串对呀的char数组,其底层实现如下所示:
public char[] toCharArray() {
// Cannot use Arrays.copyOf because of class initialization order issues
char result[] = new char[value.length];
//使用System.arraycopy来进行复制
System.arraycopy(value, 0, result, 0, value.length);
return result;
}
11.valueOf(char data[])返回char数组的内容组成的字符串
public static String valueOf(char data[]) {
//根据其构造方法来实现的
return new String(data);
}
12.String还有一个本地方法intern(),用来返回字符串对象的规范表示形式,如下所示:
//返回字符串对象的规范表示形式。
public native String intern();
四、总结
String的源码一共三千多行,有些也挺难理解的,我只是把自己看String源码的一些感想或者说理解写出来,当一个笔记吧,等差不多忘记了也可以从这篇博客中回忆一下。