您的位置:首页 > 编程语言 > Java开发

Java源码解读扫盲【String篇】

2018-03-02 22:57 525 查看
摘要: String类被定义为final类,意味着它不能被继承,它是个不可变类,并发程序最喜欢不可变量了。

一.String类的定义

String类被定义为final类,意味着它不能被继承,它是个不可变类,并发程序最喜欢不可变量了。

String类实现了Serializable, Comparable<String>, CharSequence接口。

Comparable接口有compareTo(String s)方法,CharSequence接口有length(),charAt(int index),subSequence(int start,int end)方法。

public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {


二. String的属性

String类中包含一个不可变的char数组用来存放字符串,一个int型的变量hash用来存放计算后的哈希值。(String没有length属性,只有length方法,面试笔试常考)。

String的字符串内容用的char数组来保存,而这个char数据变量也是final的,意味着字符串是不可变的,所以一个String一旦被声明定义则是不可变的。更改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是不可变量
public String() {
this.value = new char[0];
}

//参数为String类型
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}

//参数为char数组,使用java.utils包中的Arrays类复制
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}

//从bytes数组中的offset位置开始,将长度为length的字节,以charsetName格式编码,拷贝到value,在编码转换中比较常用
public String(byte bytes[], int offset, int length, String charsetName)
throws UnsupportedEncodingException {
if (charsetName == null)
throw new NullPointerException("charsetName");
checkBounds(bytes, offset, length);
this.value = StringCoding.decode(charsetName, bytes, offset, length);
}

//调用public String(byte bytes[], int offset, int length, String charsetName)构造函数,在编码转换中比较常用
public String(byte bytes[], String charsetName)
throws UnsupportedEncodingException {
this(bytes, 0, bytes.length, charsetName);
}

其余的构造函数不常用。

四. String常用方法

String类的方法很多,但很多都是互相调用的,这里看几个常用方法的源码。

1.equals

String重写了Object的equals方法,可以发现比较的内容是字符串。先是看看是否同一个内存地址,然后再比较一下长度,最后再比较内容,非常严谨高效的逻辑。

内存地址相同,则为真。

如果对象类型不是String类型,则为假。否则继续判断。

如果对象长度不相等,则为假。否则继续判断。

从后往前,判断String类中char数组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;
}


2.length()

String类中获取字符串的长度通过length()方法,其实调用的是数组的length属性,所以String没有length属性。

public int length() {
return value.length;
}


3.substring

substring返回的也是一个新的String对象,因为从0开始计算,其中不包括endIndex位置的字符,几乎所有String操作都会涉及new一个String对象,在有些场合记得重新引用,原来字符串的是不会改变的,所以可以想象常量池里面的内容是多么的庞大,特别是大型的企业级项目,如果不注意合理使用String类的话,GC是非常频繁的。

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);
}


4.trim()

String是如何去掉前后的空格的?就是截取前后不属于空格内容的部分。

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;
}


5.replace()

String替换方法replace是在char数组内进行的,先定位到要替换字符的位置,然后把不需要替换的部分复制到一个新的char数组内,把要替换的部分替换成新的字符,然后利用新的char数组生成一个新的String对象返回。这个方法值替换第一个位置,全部替换有replaceAll()方法,而这是用正则表达式匹配替换的。

public String replace(char oldChar, char newChar) {
if (oldChar != newChar) {
int len = value.length;
int i = -1;
char[] val = value; /* avoid getfield opcode */

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;
}


6.compareTo

这个方法写的很巧妙,先从0开始判断字符大小。如果两个对象能比较字符的地方比较完了还相等,就直接返回自身长度减被比较对象长度,如果两个字符串长度相等,则返回的是0,巧妙地判断了三种情况。所以比较字符跟字符的长度没有什么关系,并不是字符串长度大的就会返回>0。

public int compareTo(String anotherString) {
//自身对象字符串长度len1
int len1 = value.length;
//被比较对象字符串长度len2
int len2 = anotherString.value.length;
//取两个字符串长度的最小值lim
int lim = Math.min(len1, len2);
char v1[] = value;
char v2[] = anotherString.value;

int k = 0;
//从value的第一个字符开始到最小长度lim处为止,如果字符不相等,返回自身(对象不相等处字符-被比较对象不相等字符)
while (k < lim) {
char c1 = v1[k];
char c2 = v2[k];
if (c1 != c2) {
return c1 - c2;
}
k++;
}
//如果前面都相等,则返回(自身长度-被比较对象长度)
return len1 - len2;
}


7.hashCode

String类重写了hashCode方法,Object中的hashCode方法是一个Native调用。String类的hash采用多项式计算得来,我们完全可以通过不相同的字符串得出同样的hash,所以两个String对象的hashCode相同,并不代表两个String是一样的。

public int hashCode() {
int h = hash;
//如果hash没有被计算过,并且字符串不为空,则进行hashCode计算
if (h == 0 && value.length > 0) {
char val[] = value;

//计算过程
//s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
//hash赋值
hash = h;
}
return h;
}


8.startsWith

起始比较和末尾比较都是比较经常用得到的方法,例如在判断一个字符串是不是http协议的,或者初步判断一个文件是不是mp3文件,都可以采用这个方法进行比较。

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.
//如果起始地址小于0或者(起始地址+所比较对象长度)大于自身对象长度,返回假
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);
}

public boolean endsWith(String suffix) {
return startsWith(suffix, value.length - suffix.value.length);
}


总结

String对象是不可变类型,返回类型为String的String方法每次返回的都是新的String对象,除了某些方法的某些特定条件返回自身。

String对象的三种比较方式:

==内存比较:直接对比两个引用所指向的内存值,精确简洁直接明了。

equals字符串值比较:比较两个引用所指对象字面值是否相等。

hashCode字符串数值化比较:将字符串数值化。两个引用的hashCode相同,不保证内存一定相同,不保证字面值一定相同。

希望看一遍源码,以后少在String中踩一下坑吧。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Java源码