String类被定义为final类,意味着它不能被继承,它是个不可变类,并发程序最喜欢不可变量了。java
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类中包含一个不可变的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;
//不含参数的构造函数,通常没什么用,由于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类的方法不少,但不少都是互相调用的,这里看几个经常使用方法的源码。this
String重写了Object的equals方法,能够发现比较的内容是字符串。先是看看是否同一个内存地址,而后再比较一下长度,最后再比较内容,很是严谨高效的逻辑。编码
内存地址相同,则为真。spa
若是对象类型不是String类型,则为假。不然继续判断。code
若是对象长度不相等,则为假。不然继续判断。
从后往前,判断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; }
String类中获取字符串的长度经过length()方法,其实调用的是数组的length属性,因此String没有length属性。
public int length() { return value.length; }
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); }
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; }
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; }
这个方法写的很巧妙,先从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; }
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; }
起始比较和末尾比较都是比较常常用获得的方法,例如在判断一个字符串是否是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中踩一下坑吧。