Java中的String类平时用的多,但对其内部实现机制并不了解,本着知其然,更要知其所以然的学习态度,今天就研究下String源码。以1.7.0_80为例。
String类定义
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence{}
这是String类的定义,final说明该类不允许被继承,如果有一个String的引用,那么肯定是String的引用,而不会是其他的类。接口方面实现了以下三个接口:
- Serializable:序列化接口,该接口无任何方法和域,仅用于说明此类可以序列化。
- Comparable<String>:用来实现字符串比较,需要实现其compareTo(T o)方法。
- 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
/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L;
主要的成员变量就是上面的三个,value数组是用来保存字符串的,也就是字符串是以char[]数组的方式保存的,使用了private修饰符,而且并没有提供任何其他访问方法,使用了final关键字修饰,使得value的引用不可改变(引用的具体内容是可以改变的,不过因为外部无法获取value引用,所以也无法改变其内容)。
hash保存了字符串的hashcode值,将其缓存起来,提高效率。
构造方法
看几个构造方法:
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
这个方法仅仅是将original字符串的value引用和hash复制了一下,因为string的不可变性,所以使用这一方法没有必要。
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
这个方法是复制了一份字符数组给value。
public String(char value[], int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
// Note: offset or count might be near -1>>>1.
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
所有的构造方法均是给value设置值。
方法
public int length() {
return value.length;
}
public boolean isEmpty() {
return value.length == 0;
}
length()和isEmpty()都是使用的value数组的属性。
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;
}
equals(Object anObject)方法写的很经典,首先判断这两个对象地址是否一样,如果一样那肯定就相等了,其次判断anObject是否是string类型的,然后对其中的value数组进行长度判断,通过后逐一字符是否相等。
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
hashcode计算利用了 s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
公式
现在IDE都可以自动生成equals(Object o)和hashCode()方法。
public int compareTo(String anotherString) {
int len1 = value.length;
int len2 = anotherString.value.length;
int lim = Math.min(len1, len2);
char v1[] = value;
char v2[] = anotherString.value;
int k = 0;
while (k < lim) {
char c1 = v1[k];
char c2 = v2[k];
if (c1 != c2) {
return c1 - c2;
}
k++;
}
return len1 - len2;
}
字符串比较方法,按照ascii码的顺序和长度进行比较。
public int compareToIgnoreCase(String str) {
return CASE_INSENSITIVE_ORDER.compare(this, str);
}
使用了一个静态内部类来做忽略字符大小写的比较。
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);
}
连接字符串,如果str是空的,就返回当前字符串,否则就使用Arrays.copy()在建立一个buf数组,将str也保存到buf中,在建立一个新的字符串。
static int indexOf(char[] source, int sourceOffset, int sourceCount,
char[] target, int targetOffset, int targetCount,
int fromIndex) {
if (fromIndex >= sourceCount) {
return (targetCount == 0 ? sourceCount : -1);
}
if (fromIndex < 0) {
fromIndex = 0;
}
if (targetCount == 0) {
return fromIndex;
}
char first = target[targetOffset];
int max = sourceOffset + (sourceCount - targetCount);
for (int i = sourceOffset + fromIndex; i <= max; i++) {
/* Look for first character. */
if (source[i] != first) {
while (++i <= max && source[i] != first);
}
/* Found first character, now look at the rest of v2 */
if (i <= max) {
int j = i + 1;
int end = j + targetCount - 1;
for (int k = targetOffset + 1; j < end && source[j]
== target[k]; j++, k++);
if (j == end) {
/* Found whole string. */
return i - sourceOffset;
}
}
}
return -1;
}
这个是查找子串的方法,这里使用了最基础的查找,首先找到第一个匹配的字符,接着看剩下的字符是否匹配,如果不匹配,则从下一个字符重新查找。这里为啥不使用kmp算法呢?这里有说明。stackoverflow
大概意思就是一般字符串比较短,这种暴力算法就可以了,如果字符串很长,那么会用其他的数据结构来做,KMP算法有一定的预处理操作以及空间占用。
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);
}
获取子串。这个方法在1.7里进行了调整,之前的版本里string里有一个offset和count成员变量,来标识此字符串是value数组里的哪段。这里导致内存泄漏的问题,在对长字符串进行substring()操作时,直接修改了offset和count属性,这种模式是享元模式,两个字符串共享同一个底层数组,当长字符串被回收后,子串会保留长字符串的完整的数组,所以会出现内存泄漏问题。1.7里通过复制所需要的数组来解决这个问题,虽然多了些空间消耗,但解决了内存泄漏的问题。
public String trim() {
int len = value.length;
int st = 0;
char[] val = value; /* avoid getfield opcode */
while ((st < len) && (val[st] <= ' ')) {
st++;
}
while ((st < len) && (val[len - 1] <= ' ')) {
len--;
}
return ((st > 0) || (len < value.length)) ? substring(st, len) : this;
}
消除字符串两端空白字符。
总结下,String类实质是一个private final char[] value数组,所有的都是围绕value来处理的。
网友评论